summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@theqtcompany.com>2016-07-01 12:20:27 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2016-07-01 10:39:40 +0000
commit7366110654eec46f21b6824f302356426f48cd74 (patch)
treef2ff1845183f6117a692bb0c705475c8c13556d5
parentb92421879c003a0857b2074f7e05b3bbbb326569 (diff)
downloadqtwebengine-chromium-7366110654eec46f21b6824f302356426f48cd74.tar.gz
BASELINE: Update Chromium to 51.0.2704.106
Also add a few extra files we might need for future features. Change-Id: I517c35e43221c610976d347c966d070ad44d4c2b Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
-rw-r--r--chromium/DEPS2
-rw-r--r--chromium/base/win/win_util.cc4
-rw-r--r--chromium/build/util/LASTCHANGE2
-rw-r--r--chromium/build/util/LASTCHANGE.blink2
-rw-r--r--chromium/cc/trees/layer_tree_impl.cc15
-rw-r--r--chromium/cc/trees/layer_tree_impl_unittest.cc88
-rw-r--r--chromium/chrome/VERSION2
-rw-r--r--chromium/components/google/DEPS5
-rw-r--r--chromium/components/google/OWNERS2
-rw-r--r--chromium/components/google/core/browser/BUILD.gn47
-rw-r--r--chromium/components/google/core/browser/DEPS5
-rw-r--r--chromium/components/google/core/browser/google_pref_names.cc17
-rw-r--r--chromium/components/google/core/browser/google_pref_names.h17
-rw-r--r--chromium/components/google/core/browser/google_switches.cc12
-rw-r--r--chromium/components/google/core/browser/google_switches.h16
-rw-r--r--chromium/components/google/core/browser/google_url_tracker.cc193
-rw-r--r--chromium/components/google/core/browser/google_url_tracker.h132
-rw-r--r--chromium/components/google/core/browser/google_url_tracker_client.cc11
-rw-r--r--chromium/components/google/core/browser/google_url_tracker_client.h48
-rw-r--r--chromium/components/google/core/browser/google_url_tracker_unittest.cc353
-rw-r--r--chromium/components/google/core/browser/google_util.cc232
-rw-r--r--chromium/components/google/core/browser/google_util.h112
-rw-r--r--chromium/components/google/core/browser/google_util_unittest.cc425
-rw-r--r--chromium/components/metrics/BUILD.gn367
-rw-r--r--chromium/components/metrics/DEPS14
-rw-r--r--chromium/components/metrics/OWNERS6
-rw-r--r--chromium/components/metrics/README23
-rw-r--r--chromium/components/metrics/call_stack_profile_metrics_provider.cc396
-rw-r--r--chromium/components/metrics/call_stack_profile_metrics_provider.h83
-rw-r--r--chromium/components/metrics/call_stack_profile_metrics_provider_unittest.cc619
-rw-r--r--chromium/components/metrics/clean_exit_beacon.cc80
-rw-r--r--chromium/components/metrics/clean_exit_beacon.h45
-rw-r--r--chromium/components/metrics/client_info.cc13
-rw-r--r--chromium/components/metrics/client_info.h36
-rw-r--r--chromium/components/metrics/cloned_install_detector.cc101
-rw-r--r--chromium/components/metrics/cloned_install_detector.h60
-rw-r--r--chromium/components/metrics/cloned_install_detector_unittest.cc53
-rw-r--r--chromium/components/metrics/daily_event.cc106
-rw-r--r--chromium/components/metrics/daily_event.h92
-rw-r--r--chromium/components/metrics/daily_event_unittest.cc94
-rw-r--r--chromium/components/metrics/data_use_tracker.cc205
-rw-r--r--chromium/components/metrics/data_use_tracker.h99
-rw-r--r--chromium/components/metrics/data_use_tracker_unittest.cc206
-rw-r--r--chromium/components/metrics/drive_metrics_provider.cc97
-rw-r--r--chromium/components/metrics/drive_metrics_provider.h100
-rw-r--r--chromium/components/metrics/drive_metrics_provider_android.cc16
-rw-r--r--chromium/components/metrics/drive_metrics_provider_ios.mm16
-rw-r--r--chromium/components/metrics/drive_metrics_provider_linux.cc65
-rw-r--r--chromium/components/metrics/drive_metrics_provider_mac.mm76
-rw-r--r--chromium/components/metrics/drive_metrics_provider_unittest.cc20
-rw-r--r--chromium/components/metrics/drive_metrics_provider_win.cc52
-rw-r--r--chromium/components/metrics/file_metrics_provider.cc291
-rw-r--r--chromium/components/metrics/file_metrics_provider.h157
-rw-r--r--chromium/components/metrics/file_metrics_provider_unittest.cc192
-rw-r--r--chromium/components/metrics/gpu/DEPS4
-rw-r--r--chromium/components/metrics/gpu/gpu_metrics_provider.cc36
-rw-r--r--chromium/components/metrics/gpu/gpu_metrics_provider.h29
-rw-r--r--chromium/components/metrics/histogram_encoder.cc57
-rw-r--r--chromium/components/metrics/histogram_encoder.h29
-rw-r--r--chromium/components/metrics/histogram_encoder_unittest.cc71
-rw-r--r--chromium/components/metrics/leak_detector/OWNERS2
-rw-r--r--chromium/components/metrics/leak_detector/call_stack_manager.cc72
-rw-r--r--chromium/components/metrics/leak_detector/call_stack_manager.h102
-rw-r--r--chromium/components/metrics/leak_detector/call_stack_manager_unittest.cc260
-rw-r--r--chromium/components/metrics/leak_detector/call_stack_table.cc77
-rw-r--r--chromium/components/metrics/leak_detector/call_stack_table.h87
-rw-r--r--chromium/components/metrics/leak_detector/call_stack_table_unittest.cc364
-rw-r--r--chromium/components/metrics/leak_detector/custom_allocator.cc61
-rw-r--r--chromium/components/metrics/leak_detector/custom_allocator.h50
-rw-r--r--chromium/components/metrics/leak_detector/leak_analyzer.cc139
-rw-r--r--chromium/components/metrics/leak_detector/leak_analyzer.h87
-rw-r--r--chromium/components/metrics/leak_detector/leak_analyzer_unittest.cc366
-rw-r--r--chromium/components/metrics/leak_detector/leak_detector.cc340
-rw-r--r--chromium/components/metrics/leak_detector/leak_detector.h174
-rw-r--r--chromium/components/metrics/leak_detector/leak_detector_impl.cc241
-rw-r--r--chromium/components/metrics/leak_detector/leak_detector_impl.h185
-rw-r--r--chromium/components/metrics/leak_detector/leak_detector_impl_unittest.cc629
-rw-r--r--chromium/components/metrics/leak_detector/leak_detector_unittest.cc119
-rw-r--r--chromium/components/metrics/leak_detector/leak_detector_value_type.cc47
-rw-r--r--chromium/components/metrics/leak_detector/leak_detector_value_type.h52
-rw-r--r--chromium/components/metrics/leak_detector/ranked_set.cc52
-rw-r--r--chromium/components/metrics/leak_detector/ranked_set.h96
-rw-r--r--chromium/components/metrics/leak_detector/ranked_set_unittest.cc324
-rw-r--r--chromium/components/metrics/leak_detector/stl_allocator.h61
-rw-r--r--chromium/components/metrics/machine_id_provider.h46
-rw-r--r--chromium/components/metrics/machine_id_provider_stub.cc24
-rw-r--r--chromium/components/metrics/machine_id_provider_win.cc118
-rw-r--r--chromium/components/metrics/machine_id_provider_win_unittest.cc28
-rw-r--r--chromium/components/metrics/metrics_log.cc423
-rw-r--r--chromium/components/metrics/metrics_log.h209
-rw-r--r--chromium/components/metrics/metrics_log_manager.cc130
-rw-r--r--chromium/components/metrics/metrics_log_manager.h118
-rw-r--r--chromium/components/metrics/metrics_log_manager_unittest.cc306
-rw-r--r--chromium/components/metrics/metrics_log_unittest.cc465
-rw-r--r--chromium/components/metrics/metrics_log_uploader.cc21
-rw-r--r--chromium/components/metrics/metrics_log_uploader.h44
-rw-r--r--chromium/components/metrics/metrics_pref_names.cc181
-rw-r--r--chromium/components/metrics/metrics_pref_names.h64
-rw-r--r--chromium/components/metrics/metrics_provider.cc54
-rw-r--r--chromium/components/metrics/metrics_provider.h86
-rw-r--r--chromium/components/metrics/metrics_reporting_scheduler.cc181
-rw-r--r--chromium/components/metrics/metrics_reporting_scheduler.h99
-rw-r--r--chromium/components/metrics/metrics_reporting_scheduler_unittest.cc71
-rw-r--r--chromium/components/metrics/metrics_service.cc1193
-rw-r--r--chromium/components/metrics/metrics_service.h501
-rw-r--r--chromium/components/metrics/metrics_service_accessor.cc70
-rw-r--r--chromium/components/metrics/metrics_service_accessor.h77
-rw-r--r--chromium/components/metrics/metrics_service_client.cc26
-rw-r--r--chromium/components/metrics/metrics_service_client.h127
-rw-r--r--chromium/components/metrics/metrics_service_unittest.cc413
-rw-r--r--chromium/components/metrics/metrics_state_manager.cc314
-rw-r--r--chromium/components/metrics/metrics_state_manager.h173
-rw-r--r--chromium/components/metrics/metrics_state_manager_unittest.cc386
-rw-r--r--chromium/components/metrics/metrics_switches.cc15
-rw-r--r--chromium/components/metrics/metrics_switches.h18
-rw-r--r--chromium/components/metrics/net/DEPS6
-rw-r--r--chromium/components/metrics/net/net_metrics_log_uploader.cc77
-rw-r--r--chromium/components/metrics/net/net_metrics_log_uploader.h55
-rw-r--r--chromium/components/metrics/net/net_metrics_log_uploader_unittest.cc59
-rw-r--r--chromium/components/metrics/net/network_metrics_provider.cc265
-rw-r--r--chromium/components/metrics/net/network_metrics_provider.h88
-rw-r--r--chromium/components/metrics/net/version_utils.cc41
-rw-r--r--chromium/components/metrics/net/version_utils.h29
-rw-r--r--chromium/components/metrics/net/wifi_access_point_info_provider.cc25
-rw-r--r--chromium/components/metrics/net/wifi_access_point_info_provider.h54
-rw-r--r--chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.cc123
-rw-r--r--chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.h48
-rw-r--r--chromium/components/metrics/persisted_logs.cc171
-rw-r--r--chromium/components/metrics/persisted_logs.h143
-rw-r--r--chromium/components/metrics/persisted_logs_unittest.cc289
-rw-r--r--chromium/components/metrics/profiler/content/DEPS6
-rw-r--r--chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.cc97
-rw-r--r--chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.h52
-rw-r--r--chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.cc35
-rw-r--r--chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.h40
-rw-r--r--chromium/components/metrics/profiler/profiler_metrics_provider.cc135
-rw-r--r--chromium/components/metrics/profiler/profiler_metrics_provider.h65
-rw-r--r--chromium/components/metrics/profiler/profiler_metrics_provider_unittest.cc277
-rw-r--r--chromium/components/metrics/profiler/tracking_synchronizer.cc378
-rw-r--r--chromium/components/metrics/profiler/tracking_synchronizer.h167
-rw-r--r--chromium/components/metrics/profiler/tracking_synchronizer_delegate.h31
-rw-r--r--chromium/components/metrics/profiler/tracking_synchronizer_observer.cc21
-rw-r--r--chromium/components/metrics/profiler/tracking_synchronizer_observer.h88
-rw-r--r--chromium/components/metrics/profiler/tracking_synchronizer_unittest.cc154
-rw-r--r--chromium/components/metrics/proto/BUILD.gn24
-rw-r--r--chromium/components/metrics/proto/call_stack_profile.proto63
-rw-r--r--chromium/components/metrics/proto/cast_logs.proto182
-rw-r--r--chromium/components/metrics/proto/chrome_user_metrics_extension.proto76
-rw-r--r--chromium/components/metrics/proto/histogram_event.proto49
-rw-r--r--chromium/components/metrics/proto/memory_leak_report.proto81
-rw-r--r--chromium/components/metrics/proto/omnibox_event.proto286
-rw-r--r--chromium/components/metrics/proto/omnibox_input_type.proto39
-rw-r--r--chromium/components/metrics/proto/perf_data.proto412
-rw-r--r--chromium/components/metrics/proto/perf_stat.proto52
-rw-r--r--chromium/components/metrics/proto/profiler_event.proto127
-rw-r--r--chromium/components/metrics/proto/sampled_profile.proto83
-rw-r--r--chromium/components/metrics/proto/system_profile.proto775
-rw-r--r--chromium/components/metrics/proto/user_action_event.proto23
-rw-r--r--chromium/components/metrics/serialization/metric_sample.cc197
-rw-r--r--chromium/components/metrics/serialization/metric_sample.h118
-rw-r--r--chromium/components/metrics/serialization/serialization_utils.cc224
-rw-r--r--chromium/components/metrics/serialization/serialization_utils.h48
-rw-r--r--chromium/components/metrics/serialization/serialization_utils_unittest.cc171
-rw-r--r--chromium/components/metrics/stability_metrics_helper.cc221
-rw-r--r--chromium/components/metrics/stability_metrics_helper.h63
-rw-r--r--chromium/components/metrics/stability_metrics_helper_unittest.cc96
-rw-r--r--chromium/components/metrics/system_memory_stats_recorder.h30
-rw-r--r--chromium/components/metrics/system_memory_stats_recorder_linux.cc98
-rw-r--r--chromium/components/metrics/system_memory_stats_recorder_win.cc47
-rw-r--r--chromium/components/metrics/test_metrics_provider.cc35
-rw-r--r--chromium/components/metrics/test_metrics_provider.h57
-rw-r--r--chromium/components/metrics/test_metrics_service_client.cc93
-rw-r--r--chromium/components/metrics/test_metrics_service_client.h69
-rw-r--r--chromium/components/metrics/ui/DEPS3
-rw-r--r--chromium/components/metrics/ui/screen_info_metrics_provider.cc92
-rw-r--r--chromium/components/metrics/ui/screen_info_metrics_provider.h42
-rw-r--r--chromium/components/metrics/ui/screen_info_metrics_provider_unittest.cc66
-rw-r--r--chromium/components/metrics/url_constants.cc13
-rw-r--r--chromium/components/metrics/url_constants.h18
-rw-r--r--chromium/components/variations/BUILD.gn126
-rw-r--r--chromium/components/variations/DEPS14
-rw-r--r--chromium/components/variations/OWNERS3
-rw-r--r--chromium/components/variations/active_field_trials.cc74
-rw-r--r--chromium/components/variations/active_field_trials.h65
-rw-r--r--chromium/components/variations/active_field_trials_unittest.cc53
-rw-r--r--chromium/components/variations/android/BUILD.gn18
-rw-r--r--chromium/components/variations/android/DEPS3
-rw-r--r--chromium/components/variations/android/variations_associated_data_android.cc47
-rw-r--r--chromium/components/variations/android/variations_associated_data_android.h23
-rw-r--r--chromium/components/variations/android/variations_seed_bridge.cc104
-rw-r--r--chromium/components/variations/android/variations_seed_bridge.h43
-rw-r--r--chromium/components/variations/caching_permuted_entropy_provider.cc108
-rw-r--r--chromium/components/variations/caching_permuted_entropy_provider.h68
-rw-r--r--chromium/components/variations/caching_permuted_entropy_provider_unittest.cc54
-rw-r--r--chromium/components/variations/entropy_provider.cc132
-rw-r--r--chromium/components/variations/entropy_provider.h97
-rw-r--r--chromium/components/variations/entropy_provider_unittest.cc371
-rw-r--r--chromium/components/variations/experiment_labels.cc116
-rw-r--r--chromium/components/variations/experiment_labels.h33
-rw-r--r--chromium/components/variations/experiment_labels_unittest.cc196
-rw-r--r--chromium/components/variations/metrics_util.cc29
-rw-r--r--chromium/components/variations/metrics_util.h21
-rw-r--r--chromium/components/variations/metrics_util_unittest.cc35
-rw-r--r--chromium/components/variations/net/BUILD.gn22
-rw-r--r--chromium/components/variations/net/DEPS5
-rw-r--r--chromium/components/variations/net/variations_http_headers.cc110
-rw-r--r--chromium/components/variations/net/variations_http_headers.h41
-rw-r--r--chromium/components/variations/net/variations_http_headers_unittest.cc118
-rw-r--r--chromium/components/variations/pref_names.cc45
-rw-r--r--chromium/components/variations/pref_names.h27
-rw-r--r--chromium/components/variations/processed_study.cc166
-rw-r--r--chromium/components/variations/processed_study.h71
-rw-r--r--chromium/components/variations/proto/BUILD.gn14
-rw-r--r--chromium/components/variations/proto/client_variations.proto20
-rw-r--r--chromium/components/variations/proto/permuted_entropy_cache.proto19
-rw-r--r--chromium/components/variations/proto/study.proto298
-rw-r--r--chromium/components/variations/proto/variations_seed.proto26
-rw-r--r--chromium/components/variations/service/BUILD.gn49
-rw-r--r--chromium/components/variations/service/DEPS10
-rw-r--r--chromium/components/variations/service/generate_ui_string_overrider.gni71
-rwxr-xr-xchromium/components/variations/service/generate_ui_string_overrider.py319
-rwxr-xr-xchromium/components/variations/service/generate_ui_string_overrider_unittest.py132
-rw-r--r--chromium/components/variations/service/ui_string_overrider.cc51
-rw-r--r--chromium/components/variations/service/ui_string_overrider.h59
-rw-r--r--chromium/components/variations/service/ui_string_overrider_unittest.cc64
-rw-r--r--chromium/components/variations/service/variations_service.cc865
-rw-r--r--chromium/components/variations/service/variations_service.h322
-rw-r--r--chromium/components/variations/service/variations_service_client.h66
-rw-r--r--chromium/components/variations/service/variations_service_unittest.cc731
-rw-r--r--chromium/components/variations/study_filtering.cc305
-rw-r--r--chromium/components/variations/study_filtering.h85
-rw-r--r--chromium/components/variations/study_filtering_unittest.cc582
-rw-r--r--chromium/components/variations/synthetic_trials.cc16
-rw-r--r--chromium/components/variations/synthetic_trials.h43
-rw-r--r--chromium/components/variations/variations_associated_data.cc238
-rw-r--r--chromium/components/variations/variations_associated_data.h149
-rw-r--r--chromium/components/variations/variations_associated_data_unittest.cc349
-rw-r--r--chromium/components/variations/variations_experiment_util.cc54
-rw-r--r--chromium/components/variations/variations_experiment_util.h25
-rw-r--r--chromium/components/variations/variations_http_header_provider.cc232
-rw-r--r--chromium/components/variations/variations_http_header_provider.h119
-rw-r--r--chromium/components/variations/variations_http_header_provider_unittest.cc138
-rw-r--r--chromium/components/variations/variations_request_scheduler.cc73
-rw-r--r--chromium/components/variations/variations_request_scheduler.h71
-rw-r--r--chromium/components/variations/variations_request_scheduler_mobile.cc68
-rw-r--r--chromium/components/variations/variations_request_scheduler_mobile.h54
-rw-r--r--chromium/components/variations/variations_request_scheduler_mobile_unittest.cc145
-rw-r--r--chromium/components/variations/variations_request_scheduler_unittest.cc25
-rw-r--r--chromium/components/variations/variations_seed_processor.cc310
-rw-r--r--chromium/components/variations/variations_seed_processor.h90
-rw-r--r--chromium/components/variations/variations_seed_processor_unittest.cc754
-rw-r--r--chromium/components/variations/variations_seed_simulator.cc258
-rw-r--r--chromium/components/variations/variations_seed_simulator.h107
-rw-r--r--chromium/components/variations/variations_seed_simulator_unittest.cc384
-rw-r--r--chromium/components/variations/variations_seed_store.cc537
-rw-r--r--chromium/components/variations/variations_seed_store.h150
-rw-r--r--chromium/components/variations/variations_seed_store_unittest.cc422
-rw-r--r--chromium/components/variations/variations_switches.cc22
-rw-r--r--chromium/components/variations/variations_switches.h20
-rw-r--r--chromium/components/variations/variations_url_constants.cc13
-rw-r--r--chromium/components/variations/variations_url_constants.h14
-rw-r--r--chromium/components/variations/variations_util.cc20
-rw-r--r--chromium/components/variations/variations_util.h18
-rw-r--r--chromium/content/browser/appcache/appcache_storage_impl.cc24
-rw-r--r--chromium/content/browser/appcache/appcache_storage_impl.h1
-rw-r--r--chromium/content/browser/frame_host/navigation_controller_impl.cc10
-rw-r--r--chromium/content/browser/frame_host/navigation_controller_impl.h19
-rw-r--r--chromium/content/browser/frame_host/navigation_controller_impl_browsertest.cc138
-rw-r--r--chromium/content/browser/frame_host/navigation_controller_impl_unittest.cc62
-rw-r--r--chromium/content/browser/frame_host/navigator_impl.cc3
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_manager.cc17
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_manager.h15
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_manager_unittest.cc49
-rw-r--r--chromium/content/renderer/media/webmediaplayer_ms_compositor.cc18
-rw-r--r--chromium/content/renderer/media/webmediaplayer_ms_unittest.cc36
-rw-r--r--chromium/extensions/BUILD.gn379
-rw-r--r--chromium/extensions/DEPS59
-rw-r--r--chromium/extensions/OWNERS9
-rw-r--r--chromium/extensions/README3
-rw-r--r--chromium/extensions/browser/BUILD.gn113
-rw-r--r--chromium/extensions/browser/DEPS34
-rw-r--r--chromium/extensions/browser/OWNERS16
-rw-r--r--chromium/extensions/browser/PRESUBMIT.py43
-rw-r--r--chromium/extensions/browser/api/DEPS4
-rw-r--r--chromium/extensions/browser/api/activity_log/web_request_constants.cc33
-rw-r--r--chromium/extensions/browser/api/activity_log/web_request_constants.h36
-rw-r--r--chromium/extensions/browser/api/alarms/OWNERS1
-rw-r--r--chromium/extensions/browser/api/alarms/alarm_manager.cc473
-rw-r--r--chromium/extensions/browser/api/alarms/alarm_manager.h246
-rw-r--r--chromium/extensions/browser/api/alarms/alarms_api.cc204
-rw-r--r--chromium/extensions/browser/api/alarms/alarms_api.h93
-rw-r--r--chromium/extensions/browser/api/alarms/alarms_api_unittest.cc700
-rw-r--r--chromium/extensions/browser/api/api_resource.cc21
-rw-r--r--chromium/extensions/browser/api/api_resource.h43
-rw-r--r--chromium/extensions/browser/api/api_resource_manager.h438
-rw-r--r--chromium/extensions/browser/api/api_resource_manager_unittest.cc58
-rw-r--r--chromium/extensions/browser/api/app_current_window_internal/OWNERS3
-rw-r--r--chromium/extensions/browser/api/app_current_window_internal/app_current_window_internal_api.cc379
-rw-r--r--chromium/extensions/browser/api/app_current_window_internal/app_current_window_internal_api.h191
-rw-r--r--chromium/extensions/browser/api/app_runtime/app_runtime_api.cc216
-rw-r--r--chromium/extensions/browser/api/app_runtime/app_runtime_api.h82
-rw-r--r--chromium/extensions/browser/api/app_window/OWNERS3
-rw-r--r--chromium/extensions/browser/api/app_window/app_window_api.cc558
-rw-r--r--chromium/extensions/browser/api/app_window/app_window_api.h43
-rw-r--r--chromium/extensions/browser/api/app_window/app_window_apitest.cc219
-rw-r--r--chromium/extensions/browser/api/async_api_function.cc68
-rw-r--r--chromium/extensions/browser/api/async_api_function.h61
-rw-r--r--chromium/extensions/browser/api/audio/OWNERS1
-rw-r--r--chromium/extensions/browser/api/audio/audio_api.cc141
-rw-r--r--chromium/extensions/browser/api/audio/audio_api.h73
-rw-r--r--chromium/extensions/browser/api/audio/audio_apitest.cc300
-rw-r--r--chromium/extensions/browser/api/audio/audio_service.cc55
-rw-r--r--chromium/extensions/browser/api/audio/audio_service.h83
-rw-r--r--chromium/extensions/browser/api/audio/audio_service_chromeos.cc288
-rw-r--r--chromium/extensions/browser/api/bluetooth/OWNERS4
-rw-r--r--chromium/extensions/browser/api/bluetooth/bluetooth_api.cc203
-rw-r--r--chromium/extensions/browser/api/bluetooth/bluetooth_api.h137
-rw-r--r--chromium/extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.cc112
-rw-r--r--chromium/extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.h55
-rw-r--r--chromium/extensions/browser/api/bluetooth/bluetooth_api_utils.cc146
-rw-r--r--chromium/extensions/browser/api/bluetooth/bluetooth_api_utils.h30
-rw-r--r--chromium/extensions/browser/api/bluetooth/bluetooth_apitest.cc457
-rw-r--r--chromium/extensions/browser/api/bluetooth/bluetooth_event_router.cc476
-rw-r--r--chromium/extensions/browser/api/bluetooth/bluetooth_event_router.h192
-rw-r--r--chromium/extensions/browser/api/bluetooth/bluetooth_event_router_unittest.cc129
-rw-r--r--chromium/extensions/browser/api/bluetooth/bluetooth_extension_function.cc78
-rw-r--r--chromium/extensions/browser/api/bluetooth/bluetooth_extension_function.h51
-rw-r--r--chromium/extensions/browser/api/bluetooth/bluetooth_private_api.cc625
-rw-r--r--chromium/extensions/browser/api/bluetooth/bluetooth_private_api.h190
-rw-r--r--chromium/extensions/browser/api/bluetooth/bluetooth_private_apitest.cc303
-rw-r--r--chromium/extensions/browser/api/bluetooth_socket/OWNERS2
-rw-r--r--chromium/extensions/browser/api/bluetooth_socket/bluetooth_api_socket.cc197
-rw-r--r--chromium/extensions/browser/api/bluetooth_socket/bluetooth_api_socket.h162
-rw-r--r--chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_api.cc672
-rw-r--r--chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_api.h354
-rw-r--r--chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_apitest.cc217
-rw-r--r--chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.cc377
-rw-r--r--chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.h120
-rw-r--r--chromium/extensions/browser/api/cast_channel/DEPS3
-rw-r--r--chromium/extensions/browser/api/cast_channel/OWNERS3
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_auth_util.cc185
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_auth_util.h80
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_auth_util_unittest.cc191
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_channel_api.cc563
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_channel_api.h298
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_channel_api_unittest.cc33
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_channel_apitest.cc583
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_framer.cc182
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_framer.h102
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_framer_unittest.cc140
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_message_util.cc161
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_message_util.h45
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_socket.cc622
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_socket.h385
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_socket_unittest.cc888
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_test_util.cc49
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_test_util.h126
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_transport.cc477
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_transport.h227
-rw-r--r--chromium/extensions/browser/api/cast_channel/cast_transport_unittest.cc686
-rw-r--r--chromium/extensions/browser/api/cast_channel/keep_alive_delegate.cc206
-rw-r--r--chromium/extensions/browser/api/cast_channel/keep_alive_delegate.h117
-rw-r--r--chromium/extensions/browser/api/cast_channel/keep_alive_delegate_unittest.cc169
-rw-r--r--chromium/extensions/browser/api/cast_channel/logger.cc367
-rw-r--r--chromium/extensions/browser/api/cast_channel/logger.h147
-rw-r--r--chromium/extensions/browser/api/cast_channel/logger_unittest.cc326
-rw-r--r--chromium/extensions/browser/api/cast_channel/logger_util.cc67
-rw-r--r--chromium/extensions/browser/api/cast_channel/logger_util.h40
-rw-r--r--chromium/extensions/browser/api/declarative/declarative_api.cc226
-rw-r--r--chromium/extensions/browser/api/declarative/declarative_api.h69
-rw-r--r--chromium/extensions/browser/api/declarative/declarative_rule.h506
-rw-r--r--chromium/extensions/browser/api/declarative/declarative_rule_unittest.cc433
-rw-r--r--chromium/extensions/browser/api/declarative/deduping_factory.h182
-rw-r--r--chromium/extensions/browser/api/declarative/deduping_factory_unittest.cc201
-rw-r--r--chromium/extensions/browser/api/declarative/rules_cache_delegate.cc232
-rw-r--r--chromium/extensions/browser/api/declarative/rules_cache_delegate.h113
-rw-r--r--chromium/extensions/browser/api/declarative/rules_registry.cc437
-rw-r--r--chromium/extensions/browser/api/declarative/rules_registry.h304
-rw-r--r--chromium/extensions/browser/api/declarative/rules_registry_service.cc227
-rw-r--r--chromium/extensions/browser/api/declarative/rules_registry_service.h153
-rw-r--r--chromium/extensions/browser/api/declarative/rules_registry_unittest.cc342
-rw-r--r--chromium/extensions/browser/api/declarative/test_rules_registry.cc56
-rw-r--r--chromium/extensions/browser/api/declarative/test_rules_registry.h51
-rw-r--r--chromium/extensions/browser/api/declarative_content/content_rules_registry.h65
-rw-r--r--chromium/extensions/browser/api/declarative_webrequest/request_stage.cc29
-rw-r--r--chromium/extensions/browser/api/declarative_webrequest/request_stage.h36
-rw-r--r--chromium/extensions/browser/api/declarative_webrequest/webrequest_action.cc1167
-rw-r--r--chromium/extensions/browser/api/declarative_webrequest/webrequest_action.h471
-rw-r--r--chromium/extensions/browser/api/declarative_webrequest/webrequest_condition.cc204
-rw-r--r--chromium/extensions/browser/api/declarative_webrequest/webrequest_condition.h119
-rw-r--r--chromium/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.cc883
-rw-r--r--chromium/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.h269
-rw-r--r--chromium/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute_unittest.cc719
-rw-r--r--chromium/extensions/browser/api/declarative_webrequest/webrequest_condition_unittest.cc406
-rw-r--r--chromium/extensions/browser/api/declarative_webrequest/webrequest_constants.cc92
-rw-r--r--chromium/extensions/browser/api/declarative_webrequest/webrequest_constants.h89
-rw-r--r--chromium/extensions/browser/api/declarative_webrequest/webrequest_rules_registry.cc390
-rw-r--r--chromium/extensions/browser/api/declarative_webrequest/webrequest_rules_registry.h197
-rw-r--r--chromium/extensions/browser/api/device_permissions_manager.cc733
-rw-r--r--chromium/extensions/browser/api/device_permissions_manager.h230
-rw-r--r--chromium/extensions/browser/api/device_permissions_prompt.cc404
-rw-r--r--chromium/extensions/browser/api/device_permissions_prompt.h165
-rw-r--r--chromium/extensions/browser/api/device_permissions_prompt_unittest.cc79
-rw-r--r--chromium/extensions/browser/api/diagnostics/diagnostics_api.cc55
-rw-r--r--chromium/extensions/browser/api/diagnostics/diagnostics_api.h55
-rw-r--r--chromium/extensions/browser/api/diagnostics/diagnostics_api_chromeos.cc87
-rw-r--r--chromium/extensions/browser/api/display_source/OWNERS2
-rw-r--r--chromium/extensions/browser/api/display_source/display_source_api.cc100
-rw-r--r--chromium/extensions/browser/api/display_source/display_source_api.h52
-rw-r--r--chromium/extensions/browser/api/display_source/display_source_apitest.cc99
-rw-r--r--chromium/extensions/browser/api/display_source/display_source_connection_delegate.cc25
-rw-r--r--chromium/extensions/browser/api/display_source/display_source_connection_delegate.h130
-rw-r--r--chromium/extensions/browser/api/display_source/display_source_connection_delegate_factory.cc60
-rw-r--r--chromium/extensions/browser/api/display_source/display_source_connection_delegate_factory.h47
-rw-r--r--chromium/extensions/browser/api/display_source/display_source_event_router.cc102
-rw-r--r--chromium/extensions/browser/api/display_source/display_source_event_router.h60
-rw-r--r--chromium/extensions/browser/api/display_source/display_source_event_router_factory.cc59
-rw-r--r--chromium/extensions/browser/api/display_source/display_source_event_router_factory.h48
-rw-r--r--chromium/extensions/browser/api/display_source/wifi_display/wifi_display_session_service_impl.cc205
-rw-r--r--chromium/extensions/browser/api/display_source/wifi_display/wifi_display_session_service_impl.h75
-rw-r--r--chromium/extensions/browser/api/dns/dns_api.cc103
-rw-r--r--chromium/extensions/browser/api/dns/dns_api.h52
-rw-r--r--chromium/extensions/browser/api/dns/dns_apitest.cc96
-rw-r--r--chromium/extensions/browser/api/dns/host_resolver_wrapper.cc29
-rw-r--r--chromium/extensions/browser/api/dns/host_resolver_wrapper.h53
-rw-r--r--chromium/extensions/browser/api/dns/mock_host_resolver_creator.cc71
-rw-r--r--chromium/extensions/browser/api/dns/mock_host_resolver_creator.h52
-rw-r--r--chromium/extensions/browser/api/document_scan/DEPS3
-rw-r--r--chromium/extensions/browser/api/document_scan/document_scan_api.cc124
-rw-r--r--chromium/extensions/browser/api/document_scan/document_scan_api.h55
-rw-r--r--chromium/extensions/browser/api/document_scan/document_scan_api_unittest.cc120
-rw-r--r--chromium/extensions/browser/api/document_scan/document_scan_interface.cc28
-rw-r--r--chromium/extensions/browser/api/document_scan/document_scan_interface.h60
-rw-r--r--chromium/extensions/browser/api/document_scan/document_scan_interface_chromeos.cc131
-rw-r--r--chromium/extensions/browser/api/document_scan/document_scan_interface_chromeos.h49
-rw-r--r--chromium/extensions/browser/api/document_scan/document_scan_interface_chromeos_unittest.cc127
-rw-r--r--chromium/extensions/browser/api/document_scan/document_scan_interface_nonchromeos.cc44
-rw-r--r--chromium/extensions/browser/api/document_scan/mock_document_scan_interface.cc19
-rw-r--r--chromium/extensions/browser/api/document_scan/mock_document_scan_interface.h36
-rw-r--r--chromium/extensions/browser/api/execute_code_function.cc249
-rw-r--r--chromium/extensions/browser/api/execute_code_function.h85
-rw-r--r--chromium/extensions/browser/api/extensions_api_client.cc101
-rw-r--r--chromium/extensions/browser/api/extensions_api_client.h136
-rw-r--r--chromium/extensions/browser/api/guest_view/OWNERS5
-rw-r--r--chromium/extensions/browser/api/guest_view/app_view/app_view_guest_internal_api.cc48
-rw-r--r--chromium/extensions/browser/api/guest_view/app_view/app_view_guest_internal_api.h43
-rw-r--r--chromium/extensions/browser/api/guest_view/extension_view/extension_view_internal_api.cc88
-rw-r--r--chromium/extensions/browser/api/guest_view/extension_view/extension_view_internal_api.h69
-rw-r--r--chromium/extensions/browser/api/guest_view/guest_view_internal_api.cc142
-rw-r--r--chromium/extensions/browser/api/guest_view/guest_view_internal_api.h60
-rw-r--r--chromium/extensions/browser/api/guest_view/web_view/web_view_internal_api.cc969
-rw-r--r--chromium/extensions/browser/api/guest_view/web_view/web_view_internal_api.h491
-rw-r--r--chromium/extensions/browser/api/hid/OWNERS3
-rw-r--r--chromium/extensions/browser/api/hid/hid_api.cc386
-rw-r--r--chromium/extensions/browser/api/hid/hid_api.h216
-rw-r--r--chromium/extensions/browser/api/hid/hid_apitest.cc266
-rw-r--r--chromium/extensions/browser/api/hid/hid_connection_resource.cc40
-rw-r--r--chromium/extensions/browser/api/hid/hid_connection_resource.h47
-rw-r--r--chromium/extensions/browser/api/hid/hid_device_manager.cc338
-rw-r--r--chromium/extensions/browser/api/hid/hid_device_manager.h133
-rw-r--r--chromium/extensions/browser/api/idle/OWNERS1
-rw-r--r--chromium/extensions/browser/api/idle/idle_api.cc62
-rw-r--r--chromium/extensions/browser/api/idle/idle_api.h43
-rw-r--r--chromium/extensions/browser/api/idle/idle_api_constants.cc15
-rw-r--r--chromium/extensions/browser/api/idle/idle_api_constants.h22
-rw-r--r--chromium/extensions/browser/api/idle/idle_api_unittest.cc556
-rw-r--r--chromium/extensions/browser/api/idle/idle_manager.cc263
-rw-r--r--chromium/extensions/browser/api/idle/idle_manager.h148
-rw-r--r--chromium/extensions/browser/api/idle/idle_manager_factory.cc56
-rw-r--r--chromium/extensions/browser/api/idle/idle_manager_factory.h41
-rw-r--r--chromium/extensions/browser/api/management/management_api.cc908
-rw-r--r--chromium/extensions/browser/api/management/management_api.h297
-rw-r--r--chromium/extensions/browser/api/management/management_api_constants.cc48
-rw-r--r--chromium/extensions/browser/api/management/management_api_constants.h40
-rw-r--r--chromium/extensions/browser/api/management/management_api_delegate.h140
-rw-r--r--chromium/extensions/browser/api/messaging/OWNERS2
-rw-r--r--chromium/extensions/browser/api/messaging/native_message_host.cc21
-rw-r--r--chromium/extensions/browser/api/messaging/native_message_host.h62
-rw-r--r--chromium/extensions/browser/api/messaging/native_messaging_channel.h46
-rw-r--r--chromium/extensions/browser/api/mime_handler_private/OWNERS1
-rw-r--r--chromium/extensions/browser/api/mime_handler_private/mime_handler_private.cc125
-rw-r--r--chromium/extensions/browser/api/mime_handler_private/mime_handler_private.h59
-rw-r--r--chromium/extensions/browser/api/mime_handler_private/mime_handler_private_unittest.cc113
-rw-r--r--chromium/extensions/browser/api/networking_config/OWNERS2
-rw-r--r--chromium/extensions/browser/api/networking_config/networking_config_api.cc127
-rw-r--r--chromium/extensions/browser/api/networking_config/networking_config_api.h54
-rw-r--r--chromium/extensions/browser/api/networking_config/networking_config_chromeos_apitest.cc27
-rw-r--r--chromium/extensions/browser/api/networking_config/networking_config_service.cc234
-rw-r--r--chromium/extensions/browser/api/networking_config/networking_config_service.h148
-rw-r--r--chromium/extensions/browser/api/networking_config/networking_config_service_chromeos_unittest.cc75
-rw-r--r--chromium/extensions/browser/api/networking_config/networking_config_service_factory.cc83
-rw-r--r--chromium/extensions/browser/api/networking_config/networking_config_service_factory.h41
-rw-r--r--chromium/extensions/browser/api/networking_private/DEPS7
-rw-r--r--chromium/extensions/browser/api/networking_private/OWNERS1
-rw-r--r--chromium/extensions/browser/api/networking_private/network_config_dbus_constants_linux.cc42
-rw-r--r--chromium/extensions/browser/api/networking_private/network_config_dbus_constants_linux.h40
-rw-r--r--chromium/extensions/browser/api/networking_private/networking_private_api.cc773
-rw-r--r--chromium/extensions/browser/api/networking_private/networking_private_api.h508
-rw-r--r--chromium/extensions/browser/api/networking_private/networking_private_chromeos.cc674
-rw-r--r--chromium/extensions/browser/api/networking_private/networking_private_chromeos.h128
-rw-r--r--chromium/extensions/browser/api/networking_private/networking_private_delegate.cc84
-rw-r--r--chromium/extensions/browser/api/networking_private/networking_private_delegate.h224
-rw-r--r--chromium/extensions/browser/api/networking_private/networking_private_delegate_factory.cc113
-rw-r--r--chromium/extensions/browser/api/networking_private/networking_private_delegate_factory.h88
-rw-r--r--chromium/extensions/browser/api/networking_private/networking_private_delegate_observer.h39
-rw-r--r--chromium/extensions/browser/api/networking_private/networking_private_event_router.h38
-rw-r--r--chromium/extensions/browser/api/networking_private/networking_private_event_router_chromeos.cc280
-rw-r--r--chromium/extensions/browser/api/networking_private/networking_private_event_router_factory.cc61
-rw-r--r--chromium/extensions/browser/api/networking_private/networking_private_event_router_factory.h53
-rw-r--r--chromium/extensions/browser/api/networking_private/networking_private_event_router_nonchromeos.cc165
-rw-r--r--chromium/extensions/browser/api/networking_private/networking_private_linux.cc1207
-rw-r--r--chromium/extensions/browser/api/networking_private/networking_private_linux.h283
-rw-r--r--chromium/extensions/browser/api/networking_private/networking_private_service_client.cc474
-rw-r--r--chromium/extensions/browser/api/networking_private/networking_private_service_client.h188
-rw-r--r--chromium/extensions/browser/api/power/OWNERS1
-rw-r--r--chromium/extensions/browser/api/power/power_api.cc131
-rw-r--r--chromium/extensions/browser/api/power/power_api.h122
-rw-r--r--chromium/extensions/browser/api/power/power_api_unittest.cc278
-rw-r--r--chromium/extensions/browser/api/printer_provider/OWNERS2
-rw-r--r--chromium/extensions/browser/api/printer_provider/printer_provider_api.cc772
-rw-r--r--chromium/extensions/browser/api/printer_provider/printer_provider_api.h104
-rw-r--r--chromium/extensions/browser/api/printer_provider/printer_provider_api_factory.cc55
-rw-r--r--chromium/extensions/browser/api/printer_provider/printer_provider_api_factory.h48
-rw-r--r--chromium/extensions/browser/api/printer_provider/printer_provider_apitest.cc859
-rw-r--r--chromium/extensions/browser/api/printer_provider/printer_provider_print_job.cc18
-rw-r--r--chromium/extensions/browser/api/printer_provider/printer_provider_print_job.h59
-rw-r--r--chromium/extensions/browser/api/printer_provider_internal/OWNERS2
-rw-r--r--chromium/extensions/browser/api/printer_provider_internal/printer_provider_internal_api.cc270
-rw-r--r--chromium/extensions/browser/api/printer_provider_internal/printer_provider_internal_api.h182
-rw-r--r--chromium/extensions/browser/api/printer_provider_internal/printer_provider_internal_api_observer.h71
-rw-r--r--chromium/extensions/browser/api/runtime/runtime_api.cc586
-rw-r--r--chromium/extensions/browser/api/runtime/runtime_api.h246
-rw-r--r--chromium/extensions/browser/api/runtime/runtime_api_delegate.cc20
-rw-r--r--chromium/extensions/browser/api/runtime/runtime_api_delegate.h82
-rw-r--r--chromium/extensions/browser/api/runtime/runtime_apitest.cc131
-rw-r--r--chromium/extensions/browser/api/serial/DEPS3
-rw-r--r--chromium/extensions/browser/api/serial/OWNERS3
-rw-r--r--chromium/extensions/browser/api/serial/serial_api.cc495
-rw-r--r--chromium/extensions/browser/api/serial/serial_api.h289
-rw-r--r--chromium/extensions/browser/api/serial/serial_apitest.cc205
-rw-r--r--chromium/extensions/browser/api/serial/serial_connection.cc420
-rw-r--r--chromium/extensions/browser/api/serial/serial_connection.h222
-rw-r--r--chromium/extensions/browser/api/serial/serial_event_dispatcher.cc169
-rw-r--r--chromium/extensions/browser/api/serial/serial_event_dispatcher.h83
-rw-r--r--chromium/extensions/browser/api/serial/serial_service_factory.cc40
-rw-r--r--chromium/extensions/browser/api/serial/serial_service_factory.h22
-rw-r--r--chromium/extensions/browser/api/socket/OWNERS4
-rw-r--r--chromium/extensions/browser/api/socket/app_firewall_hole_manager.cc166
-rw-r--r--chromium/extensions/browser/api/socket/app_firewall_hole_manager.h98
-rw-r--r--chromium/extensions/browser/api/socket/socket.cc143
-rw-r--r--chromium/extensions/browser/api/socket/socket.h159
-rw-r--r--chromium/extensions/browser/api/socket/socket_api.cc1075
-rw-r--r--chromium/extensions/browser/api/socket/socket_api.h560
-rw-r--r--chromium/extensions/browser/api/socket/socket_apitest.cc71
-rw-r--r--chromium/extensions/browser/api/socket/tcp_socket.cc363
-rw-r--r--chromium/extensions/browser/api/socket/tcp_socket.h186
-rw-r--r--chromium/extensions/browser/api/socket/tls_socket.cc308
-rw-r--r--chromium/extensions/browser/api/socket/tls_socket.h120
-rw-r--r--chromium/extensions/browser/api/socket/udp_socket.cc307
-rw-r--r--chromium/extensions/browser/api/socket/udp_socket.h118
-rw-r--r--chromium/extensions/browser/api/sockets_tcp/OWNERS1
-rw-r--r--chromium/extensions/browser/api/sockets_tcp/sockets_tcp_api.cc542
-rw-r--r--chromium/extensions/browser/api/sockets_tcp/sockets_tcp_api.h272
-rw-r--r--chromium/extensions/browser/api/sockets_tcp/sockets_tcp_api_unittest.cc51
-rw-r--r--chromium/extensions/browser/api/sockets_tcp/sockets_tcp_apitest.cc125
-rw-r--r--chromium/extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.cc204
-rw-r--r--chromium/extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.h97
-rw-r--r--chromium/extensions/browser/api/sockets_tcp_server/OWNERS1
-rw-r--r--chromium/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.cc297
-rw-r--r--chromium/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.h179
-rw-r--r--chromium/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_apitest.cc100
-rw-r--r--chromium/extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc204
-rw-r--r--chromium/extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h101
-rw-r--r--chromium/extensions/browser/api/sockets_udp/OWNERS1
-rw-r--r--chromium/extensions/browser/api/sockets_udp/sockets_udp_api.cc526
-rw-r--r--chromium/extensions/browser/api/sockets_udp/sockets_udp_api.h300
-rw-r--r--chromium/extensions/browser/api/sockets_udp/sockets_udp_api_unittest.cc50
-rw-r--r--chromium/extensions/browser/api/sockets_udp/sockets_udp_apitest.cc112
-rw-r--r--chromium/extensions/browser/api/sockets_udp/udp_socket_event_dispatcher.cc188
-rw-r--r--chromium/extensions/browser/api/sockets_udp/udp_socket_event_dispatcher.h98
-rw-r--r--chromium/extensions/browser/api/storage/OWNERS1
-rw-r--r--chromium/extensions/browser/api/storage/local_value_store_cache.cc91
-rw-r--r--chromium/extensions/browser/api/storage/local_value_store_cache.h53
-rw-r--r--chromium/extensions/browser/api/storage/settings_namespace.cc46
-rw-r--r--chromium/extensions/browser/api/storage/settings_namespace.h34
-rw-r--r--chromium/extensions/browser/api/storage/settings_observer.h29
-rw-r--r--chromium/extensions/browser/api/storage/settings_quota_unittest.cc597
-rw-r--r--chromium/extensions/browser/api/storage/settings_storage_quota_enforcer.cc262
-rw-r--r--chromium/extensions/browser/api/storage/settings_storage_quota_enforcer.h92
-rw-r--r--chromium/extensions/browser/api/storage/settings_test_util.cc123
-rw-r--r--chromium/extensions/browser/api/storage/settings_test_util.h63
-rw-r--r--chromium/extensions/browser/api/storage/storage_api.cc275
-rw-r--r--chromium/extensions/browser/api/storage/storage_api.h123
-rw-r--r--chromium/extensions/browser/api/storage/storage_api_unittest.cc127
-rw-r--r--chromium/extensions/browser/api/storage/storage_frontend.cc186
-rw-r--r--chromium/extensions/browser/api/storage/storage_frontend.h101
-rw-r--r--chromium/extensions/browser/api/storage/storage_frontend_unittest.cc221
-rw-r--r--chromium/extensions/browser/api/storage/value_store_cache.cc13
-rw-r--r--chromium/extensions/browser/api/storage/value_store_cache.h57
-rw-r--r--chromium/extensions/browser/api/storage/weak_unlimited_settings_storage.cc67
-rw-r--r--chromium/extensions/browser/api/storage/weak_unlimited_settings_storage.h54
-rw-r--r--chromium/extensions/browser/api/system_cpu/OWNERS1
-rw-r--r--chromium/extensions/browser/api/system_cpu/cpu_info_provider.cc74
-rw-r--r--chromium/extensions/browser/api/system_cpu/cpu_info_provider.h61
-rw-r--r--chromium/extensions/browser/api/system_cpu/cpu_info_provider_linux.cc77
-rw-r--r--chromium/extensions/browser/api/system_cpu/cpu_info_provider_mac.cc55
-rw-r--r--chromium/extensions/browser/api/system_cpu/cpu_info_provider_win.cc77
-rw-r--r--chromium/extensions/browser/api/system_cpu/system_cpu_api.cc34
-rw-r--r--chromium/extensions/browser/api/system_cpu/system_cpu_api.h25
-rw-r--r--chromium/extensions/browser/api/system_cpu/system_cpu_apitest.cc55
-rw-r--r--chromium/extensions/browser/api/system_display/OWNERS1
-rw-r--r--chromium/extensions/browser/api/system_display/display_info_provider.cc95
-rw-r--r--chromium/extensions/browser/api/system_display/display_info_provider.h78
-rw-r--r--chromium/extensions/browser/api/system_display/system_display_api.cc64
-rw-r--r--chromium/extensions/browser/api/system_display/system_display_api.h45
-rw-r--r--chromium/extensions/browser/api/system_display/system_display_apitest.cc302
-rw-r--r--chromium/extensions/browser/api/system_info/OWNERS1
-rw-r--r--chromium/extensions/browser/api/system_info/system_info_api.cc266
-rw-r--r--chromium/extensions/browser/api/system_info/system_info_api.h46
-rw-r--r--chromium/extensions/browser/api/system_info/system_info_provider.cc69
-rw-r--r--chromium/extensions/browser/api/system_info/system_info_provider.h99
-rw-r--r--chromium/extensions/browser/api/system_memory/OWNERS1
-rw-r--r--chromium/extensions/browser/api/system_memory/memory_info_provider.cc43
-rw-r--r--chromium/extensions/browser/api/system_memory/memory_info_provider.h48
-rw-r--r--chromium/extensions/browser/api/system_memory/system_memory_api.cc33
-rw-r--r--chromium/extensions/browser/api/system_memory/system_memory_api.h26
-rw-r--r--chromium/extensions/browser/api/system_memory/system_memory_apitest.cc48
-rw-r--r--chromium/extensions/browser/api/system_network/OWNERS1
-rw-r--r--chromium/extensions/browser/api/system_network/system_network_api.cc81
-rw-r--r--chromium/extensions/browser/api/system_network/system_network_api.h38
-rw-r--r--chromium/extensions/browser/api/system_network/system_network_apitest.cc61
-rw-r--r--chromium/extensions/browser/api/system_storage/OWNERS3
-rw-r--r--chromium/extensions/browser/api/system_storage/storage_api_test_util.cc28
-rw-r--r--chromium/extensions/browser/api/system_storage/storage_api_test_util.h33
-rw-r--r--chromium/extensions/browser/api/system_storage/storage_info_provider.cc119
-rw-r--r--chromium/extensions/browser/api/system_storage/storage_info_provider.h83
-rw-r--r--chromium/extensions/browser/api/system_storage/system_storage_api.cc145
-rw-r--r--chromium/extensions/browser/api/system_storage/system_storage_api.h63
-rw-r--r--chromium/extensions/browser/api/system_storage/system_storage_apitest.cc151
-rw-r--r--chromium/extensions/browser/api/system_storage/system_storage_eject_apitest.cc111
-rw-r--r--chromium/extensions/browser/api/usb/OWNERS5
-rw-r--r--chromium/extensions/browser/api/usb/usb_api.cc1263
-rw-r--r--chromium/extensions/browser/api/usb/usb_api.h378
-rw-r--r--chromium/extensions/browser/api/usb/usb_apitest.cc285
-rw-r--r--chromium/extensions/browser/api/usb/usb_device_resource.cc48
-rw-r--r--chromium/extensions/browser/api/usb/usb_device_resource.h49
-rw-r--r--chromium/extensions/browser/api/usb/usb_event_router.cc134
-rw-r--r--chromium/extensions/browser/api/usb/usb_event_router.h66
-rw-r--r--chromium/extensions/browser/api/usb/usb_guid_map.cc86
-rw-r--r--chromium/extensions/browser/api/usb/usb_guid_map.h75
-rw-r--r--chromium/extensions/browser/api/usb/usb_manual_apitest.cc18
-rw-r--r--chromium/extensions/browser/api/virtual_keyboard_private/OWNERS2
-rw-r--r--chromium/extensions/browser/api/virtual_keyboard_private/virtual_keyboard_delegate.h71
-rw-r--r--chromium/extensions/browser/api/virtual_keyboard_private/virtual_keyboard_private_api.cc186
-rw-r--r--chromium/extensions/browser/api/virtual_keyboard_private/virtual_keyboard_private_api.h173
-rw-r--r--chromium/extensions/browser/api/vpn_provider/DEPS5
-rw-r--r--chromium/extensions/browser/api/vpn_provider/OWNERS3
-rw-r--r--chromium/extensions/browser/api/vpn_provider/vpn_provider_api.cc337
-rw-r--r--chromium/extensions/browser/api/vpn_provider/vpn_provider_api.h84
-rw-r--r--chromium/extensions/browser/api/vpn_provider/vpn_service.cc578
-rw-r--r--chromium/extensions/browser/api/vpn_provider/vpn_service.h253
-rw-r--r--chromium/extensions/browser/api/vpn_provider/vpn_service_factory.h49
-rw-r--r--chromium/extensions/browser/api/web_contents_capture_client.cc143
-rw-r--r--chromium/extensions/browser/api/web_contents_capture_client.h59
-rw-r--r--chromium/extensions/browser/api/web_request/form_data_parser.cc601
-rw-r--r--chromium/extensions/browser/api/web_request/form_data_parser.h82
-rw-r--r--chromium/extensions/browser/api/web_request/form_data_parser_unittest.cc252
-rw-r--r--chromium/extensions/browser/api/web_request/upload_data_presenter.cc164
-rw-r--r--chromium/extensions/browser/api/web_request/upload_data_presenter.h132
-rw-r--r--chromium/extensions/browser/api/web_request/upload_data_presenter_unittest.cc82
-rw-r--r--chromium/extensions/browser/api/web_request/web_request_api.cc2304
-rw-r--r--chromium/extensions/browser/api/web_request/web_request_api.h553
-rw-r--r--chromium/extensions/browser/api/web_request/web_request_api_constants.cc82
-rw-r--r--chromium/extensions/browser/api/web_request/web_request_api_constants.h89
-rw-r--r--chromium/extensions/browser/api/web_request/web_request_api_helpers.cc1327
-rw-r--r--chromium/extensions/browser/api/web_request/web_request_api_helpers.h360
-rw-r--r--chromium/extensions/browser/api/web_request/web_request_event_details.cc197
-rw-r--r--chromium/extensions/browser/api/web_request/web_request_event_details.h144
-rw-r--r--chromium/extensions/browser/api/web_request/web_request_event_router_delegate.cc20
-rw-r--r--chromium/extensions/browser/api/web_request/web_request_event_router_delegate.h58
-rw-r--r--chromium/extensions/browser/api/web_request/web_request_permissions.cc152
-rw-r--r--chromium/extensions/browser/api/web_request/web_request_permissions.h52
-rw-r--r--chromium/extensions/browser/api/web_request/web_request_time_tracker.cc253
-rw-r--r--chromium/extensions/browser/api/web_request/web_request_time_tracker.h130
-rw-r--r--chromium/extensions/browser/api/web_request/web_request_time_tracker_unittest.cc150
-rw-r--r--chromium/extensions/browser/api/webcam_private/OWNERS1
-rw-r--r--chromium/extensions/browser/api/webcam_private/v4l2_webcam.cc207
-rw-r--r--chromium/extensions/browser/api/webcam_private/v4l2_webcam.h59
-rw-r--r--chromium/extensions/browser/api/webcam_private/visca_webcam.cc500
-rw-r--r--chromium/extensions/browser/api/webcam_private/visca_webcam.h143
-rw-r--r--chromium/extensions/browser/api/webcam_private/visca_webcam_unittest.cc141
-rw-r--r--chromium/extensions/browser/api/webcam_private/webcam.cc33
-rw-r--r--chromium/extensions/browser/api/webcam_private/webcam.h91
-rw-r--r--chromium/extensions/browser/api/webcam_private/webcam_private_api.h177
-rw-r--r--chromium/extensions/browser/api/webcam_private/webcam_private_api_chromeos.cc439
-rw-r--r--chromium/extensions/browser/api_activity_monitor.h39
-rw-r--r--chromium/extensions/browser/api_test_utils.cc246
-rw-r--r--chromium/extensions/browser/api_test_utils.h130
-rw-r--r--chromium/extensions/browser/api_unittest.cc122
-rw-r--r--chromium/extensions/browser/api_unittest.h104
-rw-r--r--chromium/extensions/browser/app_sorting.h112
-rw-r--r--chromium/extensions/browser/app_window/app_delegate.h89
-rw-r--r--chromium/extensions/browser/app_window/app_web_contents_helper.cc115
-rw-r--r--chromium/extensions/browser/app_window/app_web_contents_helper.h74
-rw-r--r--chromium/extensions/browser/app_window/app_window.cc1126
-rw-r--r--chromium/extensions/browser/app_window/app_window.h579
-rw-r--r--chromium/extensions/browser/app_window/app_window_browsertest.cc71
-rw-r--r--chromium/extensions/browser/app_window/app_window_client.cc28
-rw-r--r--chromium/extensions/browser/app_window/app_window_client.h54
-rw-r--r--chromium/extensions/browser/app_window/app_window_contents.cc139
-rw-r--r--chromium/extensions/browser/app_window/app_window_contents.h65
-rw-r--r--chromium/extensions/browser/app_window/app_window_geometry_cache.cc309
-rw-r--r--chromium/extensions/browser/app_window/app_window_geometry_cache.h159
-rw-r--r--chromium/extensions/browser/app_window/app_window_geometry_cache_unittest.cc433
-rw-r--r--chromium/extensions/browser/app_window/app_window_interactive_uitest.cc189
-rw-r--r--chromium/extensions/browser/app_window/app_window_registry.cc270
-rw-r--r--chromium/extensions/browser/app_window/app_window_registry.h157
-rw-r--r--chromium/extensions/browser/app_window/native_app_window.h100
-rw-r--r--chromium/extensions/browser/app_window/size_constraints.cc83
-rw-r--r--chromium/extensions/browser/app_window/size_constraints.h57
-rw-r--r--chromium/extensions/browser/app_window/test_app_window_contents.cc44
-rw-r--r--chromium/extensions/browser/app_window/test_app_window_contents.h46
-rw-r--r--chromium/extensions/browser/bad_message.cc24
-rw-r--r--chromium/extensions/browser/bad_message.h47
-rw-r--r--chromium/extensions/browser/blacklist_state.h24
-rw-r--r--chromium/extensions/browser/blob_holder.cc87
-rw-r--r--chromium/extensions/browser/blob_holder.h63
-rw-r--r--chromium/extensions/browser/blocked_action_type.h21
-rw-r--r--chromium/extensions/browser/browser_context_keyed_api_factory.h142
-rw-r--r--chromium/extensions/browser/browser_context_keyed_service_factories.cc94
-rw-r--r--chromium/extensions/browser/browser_context_keyed_service_factories.h16
-rw-r--r--chromium/extensions/browser/component_extension_resource_manager.h32
-rw-r--r--chromium/extensions/browser/computed_hashes.cc197
-rw-r--r--chromium/extensions/browser/computed_hashes.h74
-rw-r--r--chromium/extensions/browser/computed_hashes_unittest.cc125
-rw-r--r--chromium/extensions/browser/content_hash_fetcher.cc504
-rw-r--r--chromium/extensions/browser/content_hash_fetcher.h84
-rw-r--r--chromium/extensions/browser/content_hash_reader.cc122
-rw-r--r--chromium/extensions/browser/content_hash_reader.h94
-rw-r--r--chromium/extensions/browser/content_hash_tree.cc54
-rw-r--r--chromium/extensions/browser/content_hash_tree.h39
-rw-r--r--chromium/extensions/browser/content_hash_tree_unittest.cc77
-rw-r--r--chromium/extensions/browser/content_verifier.cc290
-rw-r--r--chromium/extensions/browser/content_verifier.h112
-rw-r--r--chromium/extensions/browser/content_verifier_delegate.h88
-rw-r--r--chromium/extensions/browser/content_verifier_io_data.cc60
-rw-r--r--chromium/extensions/browser/content_verifier_io_data.h53
-rw-r--r--chromium/extensions/browser/content_verify_job.cc220
-rw-r--r--chromium/extensions/browser/content_verify_job.h153
-rw-r--r--chromium/extensions/browser/crx_file_info.cc36
-rw-r--r--chromium/extensions/browser/crx_file_info.h34
-rw-r--r--chromium/extensions/browser/declarative_user_script_manager.cc62
-rw-r--r--chromium/extensions/browser/declarative_user_script_manager.h68
-rw-r--r--chromium/extensions/browser/declarative_user_script_manager_factory.cc51
-rw-r--r--chromium/extensions/browser/declarative_user_script_manager_factory.h41
-rw-r--r--chromium/extensions/browser/declarative_user_script_master.cc62
-rw-r--r--chromium/extensions/browser/declarative_user_script_master.h75
-rw-r--r--chromium/extensions/browser/deferred_start_render_host.h34
-rw-r--r--chromium/extensions/browser/deferred_start_render_host_observer.h34
-rw-r--r--chromium/extensions/browser/entry_info.h28
-rw-r--r--chromium/extensions/browser/error_map.cc216
-rw-r--r--chromium/extensions/browser/error_map.h90
-rw-r--r--chromium/extensions/browser/error_map_unittest.cc171
-rw-r--r--chromium/extensions/browser/event_listener_map.cc285
-rw-r--r--chromium/extensions/browser/event_listener_map.h199
-rw-r--r--chromium/extensions/browser/event_listener_map_unittest.cc389
-rw-r--r--chromium/extensions/browser/event_page_tracker.h39
-rw-r--r--chromium/extensions/browser/event_router.cc920
-rw-r--r--chromium/extensions/browser/event_router.h438
-rw-r--r--chromium/extensions/browser/event_router_factory.cc52
-rw-r--r--chromium/extensions/browser/event_router_factory.h38
-rw-r--r--chromium/extensions/browser/event_router_unittest.cc279
-rw-r--r--chromium/extensions/browser/extension_api_frame_id_map.cc354
-rw-r--r--chromium/extensions/browser/extension_api_frame_id_map.h214
-rw-r--r--chromium/extensions/browser/extension_api_frame_id_map_unittest.cc278
-rw-r--r--chromium/extensions/browser/extension_dialog_auto_confirm.cc29
-rw-r--r--chromium/extensions/browser/extension_dialog_auto_confirm.h34
-rw-r--r--chromium/extensions/browser/extension_error.cc193
-rw-r--r--chromium/extensions/browser/extension_error.h165
-rw-r--r--chromium/extensions/browser/extension_error_test_util.cc62
-rw-r--r--chromium/extensions/browser/extension_error_test_util.h36
-rw-r--r--chromium/extensions/browser/extension_event_histogram_value.h426
-rw-r--r--chromium/extensions/browser/extension_function.cc566
-rw-r--r--chromium/extensions/browser/extension_function.h688
-rw-r--r--chromium/extensions/browser/extension_function_dispatcher.cc504
-rw-r--r--chromium/extensions/browser/extension_function_dispatcher.h171
-rw-r--r--chromium/extensions/browser/extension_function_registry.cc63
-rw-r--r--chromium/extensions/browser/extension_host.cc495
-rw-r--r--chromium/extensions/browser/extension_host.h227
-rw-r--r--chromium/extensions/browser/extension_host_delegate.h76
-rw-r--r--chromium/extensions/browser/extension_host_observer.h49
-rw-r--r--chromium/extensions/browser/extension_host_queue.h28
-rw-r--r--chromium/extensions/browser/extension_icon_image.cc265
-rw-r--r--chromium/extensions/browser/extension_icon_image.h129
-rw-r--r--chromium/extensions/browser/extension_icon_image_unittest.cc553
-rw-r--r--chromium/extensions/browser/extension_icon_placeholder.cc103
-rw-r--r--chromium/extensions/browser/extension_icon_placeholder.h54
-rw-r--r--chromium/extensions/browser/extension_message_filter.cc321
-rw-r--r--chromium/extensions/browser/extension_message_filter.h99
-rw-r--r--chromium/extensions/browser/extension_pref_store.cc45
-rw-r--r--chromium/extensions/browser/extension_pref_store.h39
-rw-r--r--chromium/extensions/browser/extension_pref_value_map.cc398
-rw-r--r--chromium/extensions/browser/extension_pref_value_map.h211
-rw-r--r--chromium/extensions/browser/extension_pref_value_map_factory.cc42
-rw-r--r--chromium/extensions/browser/extension_pref_value_map_factory.h33
-rw-r--r--chromium/extensions/browser/extension_pref_value_map_unittest.cc443
-rw-r--r--chromium/extensions/browser/extension_prefs.cc1983
-rw-r--r--chromium/extensions/browser/extension_prefs.h695
-rw-r--r--chromium/extensions/browser/extension_prefs_factory.cc68
-rw-r--r--chromium/extensions/browser/extension_prefs_factory.h39
-rw-r--r--chromium/extensions/browser/extension_prefs_observer.h46
-rw-r--r--chromium/extensions/browser/extension_prefs_scope.h27
-rw-r--r--chromium/extensions/browser/extension_protocols.cc585
-rw-r--r--chromium/extensions/browser/extension_protocols.h40
-rw-r--r--chromium/extensions/browser/extension_registry.cc217
-rw-r--r--chromium/extensions/browser/extension_registry.h204
-rw-r--r--chromium/extensions/browser/extension_registry_factory.cc47
-rw-r--r--chromium/extensions/browser/extension_registry_factory.h43
-rw-r--r--chromium/extensions/browser/extension_registry_observer.h83
-rw-r--r--chromium/extensions/browser/extension_registry_unittest.cc277
-rw-r--r--chromium/extensions/browser/extension_request_limiting_throttle.cc58
-rw-r--r--chromium/extensions/browser/extension_request_limiting_throttle.h52
-rw-r--r--chromium/extensions/browser/extension_scoped_prefs.h56
-rw-r--r--chromium/extensions/browser/extension_system.cc26
-rw-r--r--chromium/extensions/browser/extension_system.h141
-rw-r--r--chromium/extensions/browser/extension_system_provider.cc18
-rw-r--r--chromium/extensions/browser/extension_system_provider.h35
-rw-r--r--chromium/extensions/browser/extension_throttle_entry.cc287
-rw-r--r--chromium/extensions/browser/extension_throttle_entry.h166
-rw-r--r--chromium/extensions/browser/extension_throttle_entry_interface.h78
-rw-r--r--chromium/extensions/browser/extension_throttle_manager.cc217
-rw-r--r--chromium/extensions/browser/extension_throttle_manager.h185
-rw-r--r--chromium/extensions/browser/extension_throttle_simulation_unittest.cc745
-rw-r--r--chromium/extensions/browser/extension_throttle_test_support.cc22
-rw-r--r--chromium/extensions/browser/extension_throttle_test_support.h33
-rw-r--r--chromium/extensions/browser/extension_throttle_unittest.cc470
-rw-r--r--chromium/extensions/browser/extension_user_script_loader.cc239
-rw-r--r--chromium/extensions/browser/extension_user_script_loader.h77
-rw-r--r--chromium/extensions/browser/extension_util.cc47
-rw-r--r--chromium/extensions/browser/extension_util.h40
-rw-r--r--chromium/extensions/browser/extension_web_contents_observer.cc288
-rw-r--r--chromium/extensions/browser/extension_web_contents_observer.h138
-rw-r--r--chromium/extensions/browser/extension_zoom_request_client.cc27
-rw-r--r--chromium/extensions/browser/extension_zoom_request_client.h37
-rw-r--r--chromium/extensions/browser/extensions_browser_client.cc45
-rw-r--r--chromium/extensions/browser/extensions_browser_client.h264
-rw-r--r--chromium/extensions/browser/extensions_test.cc60
-rw-r--r--chromium/extensions/browser/extensions_test.h72
-rw-r--r--chromium/extensions/browser/external_install_info.cc49
-rw-r--r--chromium/extensions/browser/external_install_info.h67
-rw-r--r--chromium/extensions/browser/external_provider_interface.h104
-rw-r--r--chromium/extensions/browser/file_highlighter.cc228
-rw-r--r--chromium/extensions/browser/file_highlighter.h114
-rw-r--r--chromium/extensions/browser/file_highlighter_unittest.cc120
-rw-r--r--chromium/extensions/browser/file_reader.cc32
-rw-r--r--chromium/extensions/browser/file_reader.h46
-rw-r--r--chromium/extensions/browser/file_reader_unittest.cc105
-rw-r--r--chromium/extensions/browser/granted_file_entry.cc15
-rw-r--r--chromium/extensions/browser/granted_file_entry.h24
-rw-r--r--chromium/extensions/browser/guest_view/OWNERS6
-rw-r--r--chromium/extensions/browser/guest_view/app_view/app_view_apitest.cc185
-rw-r--r--chromium/extensions/browser/guest_view/app_view/app_view_constants.cc20
-rw-r--r--chromium/extensions/browser/guest_view/app_view/app_view_constants.h25
-rw-r--r--chromium/extensions/browser/guest_view/app_view/app_view_guest.cc290
-rw-r--r--chromium/extensions/browser/guest_view/app_view/app_view_guest.h93
-rw-r--r--chromium/extensions/browser/guest_view/app_view/app_view_guest_delegate.cc12
-rw-r--r--chromium/extensions/browser/guest_view/app_view/app_view_guest_delegate.h33
-rw-r--r--chromium/extensions/browser/guest_view/extension_options/DEPS3
-rw-r--r--chromium/extensions/browser/guest_view/extension_options/extension_options_apitest.cc81
-rw-r--r--chromium/extensions/browser/guest_view/extension_options/extension_options_constants.cc14
-rw-r--r--chromium/extensions/browser/guest_view/extension_options/extension_options_constants.h17
-rw-r--r--chromium/extensions/browser/guest_view/extension_options/extension_options_guest.cc241
-rw-r--r--chromium/extensions/browser/guest_view/extension_options/extension_options_guest.h74
-rw-r--r--chromium/extensions/browser/guest_view/extension_options/extension_options_guest_delegate.cc17
-rw-r--r--chromium/extensions/browser/guest_view/extension_options/extension_options_guest_delegate.h44
-rw-r--r--chromium/extensions/browser/guest_view/extension_view/extension_view_constants.cc19
-rw-r--r--chromium/extensions/browser/guest_view/extension_view/extension_view_constants.h22
-rw-r--r--chromium/extensions/browser/guest_view/extension_view/extension_view_guest.cc149
-rw-r--r--chromium/extensions/browser/guest_view/extension_view/extension_view_guest.h65
-rw-r--r--chromium/extensions/browser/guest_view/extension_view/whitelist/OWNERS11
-rw-r--r--chromium/extensions/browser/guest_view/extension_view/whitelist/extension_view_whitelist.cc44
-rw-r--r--chromium/extensions/browser/guest_view/extension_view/whitelist/extension_view_whitelist.h17
-rw-r--r--chromium/extensions/browser/guest_view/extensions_guest_view_manager_delegate.cc104
-rw-r--r--chromium/extensions/browser/guest_view/extensions_guest_view_manager_delegate.h39
-rw-r--r--chromium/extensions/browser/guest_view/extensions_guest_view_message_filter.cc187
-rw-r--r--chromium/extensions/browser/guest_view/extensions_guest_view_message_filter.h79
-rw-r--r--chromium/extensions/browser/guest_view/guest_view_events.cc110
-rw-r--r--chromium/extensions/browser/guest_view/guest_view_events.h23
-rw-r--r--chromium/extensions/browser/guest_view/mime_handler_view/OWNERS1
-rw-r--r--chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_stream_manager.cc206
-rw-r--r--chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_stream_manager.h71
-rw-r--r--chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_browsertest.cc158
-rw-r--r--chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_constants.cc11
-rw-r--r--chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_constants.h16
-rw-r--r--chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.cc244
-rw-r--r--chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h106
-rw-r--r--chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest_delegate.cc15
-rw-r--r--chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest_delegate.h39
-rw-r--r--chromium/extensions/browser/guest_view/mime_handler_view/test_mime_handler_view_guest.cc80
-rw-r--r--chromium/extensions/browser/guest_view/mime_handler_view/test_mime_handler_view_guest.h64
-rw-r--r--chromium/extensions/browser/guest_view/web_view/javascript_dialog_helper.cc104
-rw-r--r--chromium/extensions/browser/guest_view/web_view/javascript_dialog_helper.h53
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_ui/web_ui_url_fetcher.cc52
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_ui/web_ui_url_fetcher.h56
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_view_apitest.cc743
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_view_apitest.h66
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_view_constants.cc145
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_view_constants.h157
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_view_content_script_manager.cc251
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_view_content_script_manager.h110
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_view_find_helper.cc290
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_view_find_helper.h194
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_view_guest.cc1506
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_view_guest.h389
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_view_guest_delegate.h50
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_view_media_access_apitest.cc172
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_view_permission_helper.cc412
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_view_permission_helper.h168
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_view_permission_helper_delegate.cc21
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_view_permission_helper_delegate.h94
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_view_permission_types.h41
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_view_renderer_state.cc155
-rw-r--r--chromium/extensions/browser/guest_view/web_view/web_view_renderer_state.h112
-rw-r--r--chromium/extensions/browser/image_loader.cc333
-rw-r--r--chromium/extensions/browser/image_loader.h121
-rw-r--r--chromium/extensions/browser/image_loader_factory.cc47
-rw-r--r--chromium/extensions/browser/image_loader_factory.h45
-rw-r--r--chromium/extensions/browser/image_loader_unittest.cc313
-rw-r--r--chromium/extensions/browser/info_map.cc246
-rw-r--r--chromium/extensions/browser/info_map.h127
-rw-r--r--chromium/extensions/browser/info_map_unittest.cc134
-rw-r--r--chromium/extensions/browser/install/crx_install_error.h48
-rw-r--r--chromium/extensions/browser/install/extension_install_ui.cc18
-rw-r--r--chromium/extensions/browser/install/extension_install_ui.h70
-rw-r--r--chromium/extensions/browser/install_flag.h34
-rw-r--r--chromium/extensions/browser/io_thread_extension_message_filter.cc108
-rw-r--r--chromium/extensions/browser/io_thread_extension_message_filter.h66
-rw-r--r--chromium/extensions/browser/lazy_background_task_queue.cc193
-rw-r--r--chromium/extensions/browser/lazy_background_task_queue.h109
-rw-r--r--chromium/extensions/browser/lazy_background_task_queue_factory.cc50
-rw-r--r--chromium/extensions/browser/lazy_background_task_queue_factory.h40
-rw-r--r--chromium/extensions/browser/lazy_background_task_queue_unittest.cc221
-rw-r--r--chromium/extensions/browser/load_monitoring_extension_host_queue.cc120
-rw-r--r--chromium/extensions/browser/load_monitoring_extension_host_queue.h116
-rw-r--r--chromium/extensions/browser/load_monitoring_extension_host_queue_unittest.cc355
-rw-r--r--chromium/extensions/browser/management_policy.cc133
-rw-r--r--chromium/extensions/browser/management_policy.h164
-rw-r--r--chromium/extensions/browser/management_policy_unittest.cc242
-rw-r--r--chromium/extensions/browser/mock_extension_system.cc84
-rw-r--r--chromium/extensions/browser/mock_extension_system.h91
-rw-r--r--chromium/extensions/browser/mojo/DEPS4
-rw-r--r--chromium/extensions/browser/mojo/keep_alive_impl.cc53
-rw-r--r--chromium/extensions/browser/mojo/keep_alive_impl.h58
-rw-r--r--chromium/extensions/browser/mojo/keep_alive_impl_unittest.cc169
-rw-r--r--chromium/extensions/browser/mojo/service_registration.cc72
-rw-r--r--chromium/extensions/browser/mojo/service_registration.h21
-rw-r--r--chromium/extensions/browser/mojo/stash_backend.cc175
-rw-r--r--chromium/extensions/browser/mojo/stash_backend.h57
-rw-r--r--chromium/extensions/browser/mojo/stash_backend_unittest.cc301
-rw-r--r--chromium/extensions/browser/null_app_sorting.cc95
-rw-r--r--chromium/extensions/browser/null_app_sorting.h58
-rw-r--r--chromium/extensions/browser/pref_names.cc59
-rw-r--r--chromium/extensions/browser/pref_names.h120
-rw-r--r--chromium/extensions/browser/process_manager.cc986
-rw-r--r--chromium/extensions/browser/process_manager.h339
-rw-r--r--chromium/extensions/browser/process_manager_delegate.h34
-rw-r--r--chromium/extensions/browser/process_manager_factory.cc55
-rw-r--r--chromium/extensions/browser/process_manager_factory.h41
-rw-r--r--chromium/extensions/browser/process_manager_observer.h52
-rw-r--r--chromium/extensions/browser/process_manager_unittest.cc251
-rw-r--r--chromium/extensions/browser/process_map.cc146
-rw-r--r--chromium/extensions/browser/process_map.h142
-rw-r--r--chromium/extensions/browser/process_map_factory.cc46
-rw-r--r--chromium/extensions/browser/process_map_factory.h43
-rw-r--r--chromium/extensions/browser/process_map_unittest.cc65
-rw-r--r--chromium/extensions/browser/quota_service.cc139
-rw-r--r--chromium/extensions/browser/quota_service.h214
-rw-r--r--chromium/extensions/browser/quota_service_unittest.cc334
-rw-r--r--chromium/extensions/browser/renderer_startup_helper.cc120
-rw-r--r--chromium/extensions/browser/renderer_startup_helper.h75
-rw-r--r--chromium/extensions/browser/requirements_checker.h38
-rw-r--r--chromium/extensions/browser/resources/default_100_percent/app_default_icon.pngbin0 -> 4453 bytes
-rw-r--r--chromium/extensions/browser/resources/default_100_percent/extension_action_plain_background.pngbin0 -> 142 bytes
-rw-r--r--chromium/extensions/browser/resources/default_100_percent/extension_default_icon.pngbin0 -> 3287 bytes
-rw-r--r--chromium/extensions/browser/resources/default_100_percent/extension_icon_plain_background.pngbin0 -> 145 bytes
-rw-r--r--chromium/extensions/browser/resources/default_200_percent/app_default_icon.pngbin0 -> 14779 bytes
-rw-r--r--chromium/extensions/browser/resources/default_200_percent/extension_action_plain_background.pngbin0 -> 142 bytes
-rw-r--r--chromium/extensions/browser/resources/default_200_percent/extension_default_icon.pngbin0 -> 6873 bytes
-rw-r--r--chromium/extensions/browser/resources/default_200_percent/extension_icon_plain_background.pngbin0 -> 235 bytes
-rw-r--r--chromium/extensions/browser/resources/extensions_browser_resources.grd20
-rw-r--r--chromium/extensions/browser/runtime_data.cc84
-rw-r--r--chromium/extensions/browser/runtime_data.h92
-rw-r--r--chromium/extensions/browser/runtime_data_unittest.cc106
-rw-r--r--chromium/extensions/browser/sandboxed_unpacker.cc934
-rw-r--r--chromium/extensions/browser/sandboxed_unpacker.h317
-rw-r--r--chromium/extensions/browser/sandboxed_unpacker_unittest.cc191
-rw-r--r--chromium/extensions/browser/script_execution_observer.h44
-rw-r--r--chromium/extensions/browser/script_executor.cc278
-rw-r--r--chromium/extensions/browser/script_executor.h118
-rw-r--r--chromium/extensions/browser/serial_extension_host_queue.cc84
-rw-r--r--chromium/extensions/browser/serial_extension_host_queue.h52
-rw-r--r--chromium/extensions/browser/service_worker_manager.cc51
-rw-r--r--chromium/extensions/browser/service_worker_manager.h40
-rw-r--r--chromium/extensions/browser/state_store.cc186
-rw-r--r--chromium/extensions/browser/state_store.h116
-rw-r--r--chromium/extensions/browser/suggest_permission_util.cc65
-rw-r--r--chromium/extensions/browser/suggest_permission_util.h28
-rw-r--r--chromium/extensions/browser/test_extension_registry_observer.cc118
-rw-r--r--chromium/extensions/browser/test_extension_registry_observer.h69
-rw-r--r--chromium/extensions/browser/test_extensions_browser_client.cc221
-rw-r--r--chromium/extensions/browser/test_extensions_browser_client.h135
-rw-r--r--chromium/extensions/browser/test_image_loader.cc58
-rw-r--r--chromium/extensions/browser/test_image_loader.h45
-rw-r--r--chromium/extensions/browser/test_management_policy.cc88
-rw-r--r--chromium/extensions/browser/test_management_policy.h69
-rw-r--r--chromium/extensions/browser/test_runtime_api_delegate.cc53
-rw-r--r--chromium/extensions/browser/test_runtime_api_delegate.h36
-rw-r--r--chromium/extensions/browser/uninstall_ping_sender.cc33
-rw-r--r--chromium/extensions/browser/uninstall_ping_sender.h51
-rw-r--r--chromium/extensions/browser/uninstall_reason.h50
-rw-r--r--chromium/extensions/browser/update_observer.h27
-rw-r--r--chromium/extensions/browser/updater/extension_cache.h66
-rw-r--r--chromium/extensions/browser/updater/extension_downloader.cc947
-rw-r--r--chromium/extensions/browser/updater/extension_downloader.h337
-rw-r--r--chromium/extensions/browser/updater/extension_downloader_delegate.cc39
-rw-r--r--chromium/extensions/browser/updater/extension_downloader_delegate.h134
-rw-r--r--chromium/extensions/browser/updater/manifest_fetch_data.cc179
-rw-r--r--chromium/extensions/browser/updater/manifest_fetch_data.h140
-rw-r--r--chromium/extensions/browser/updater/null_extension_cache.cc43
-rw-r--r--chromium/extensions/browser/updater/null_extension_cache.h39
-rw-r--r--chromium/extensions/browser/updater/request_queue.h144
-rw-r--r--chromium/extensions/browser/updater/request_queue_impl.h156
-rw-r--r--chromium/extensions/browser/updater/safe_manifest_parser.cc80
-rw-r--r--chromium/extensions/browser/updater/safe_manifest_parser.h54
-rw-r--r--chromium/extensions/browser/updater/update_client_config.cc23
-rw-r--r--chromium/extensions/browser/updater/update_client_config.h38
-rw-r--r--chromium/extensions/browser/updater/update_data_provider.cc71
-rw-r--r--chromium/extensions/browser/updater/update_data_provider.h69
-rw-r--r--chromium/extensions/browser/updater/update_install_shim.cc83
-rw-r--r--chromium/extensions/browser/updater/update_install_shim.h77
-rw-r--r--chromium/extensions/browser/updater/update_service.cc70
-rw-r--r--chromium/extensions/browser/updater/update_service.h70
-rw-r--r--chromium/extensions/browser/updater/update_service_factory.cc43
-rw-r--r--chromium/extensions/browser/updater/update_service_factory.h38
-rw-r--r--chromium/extensions/browser/updater/update_service_unittest.cc351
-rw-r--r--chromium/extensions/browser/url_request_util.cc112
-rw-r--r--chromium/extensions/browser/url_request_util.h36
-rw-r--r--chromium/extensions/browser/user_script_loader.cc409
-rw-r--r--chromium/extensions/browser/user_script_loader.h185
-rw-r--r--chromium/extensions/browser/value_store/lazy_leveldb.cc283
-rw-r--r--chromium/extensions/browser/value_store/lazy_leveldb.h98
-rw-r--r--chromium/extensions/browser/value_store/legacy_value_store_factory.cc264
-rw-r--r--chromium/extensions/browser/value_store/legacy_value_store_factory.h111
-rw-r--r--chromium/extensions/browser/value_store/leveldb_scoped_database.cc168
-rw-r--r--chromium/extensions/browser/value_store/leveldb_scoped_database.h81
-rw-r--r--chromium/extensions/browser/value_store/leveldb_scoped_database_unittest.cc205
-rw-r--r--chromium/extensions/browser/value_store/leveldb_value_store.cc301
-rw-r--r--chromium/extensions/browser/value_store/leveldb_value_store.h83
-rw-r--r--chromium/extensions/browser/value_store/leveldb_value_store_unittest.cc190
-rw-r--r--chromium/extensions/browser/value_store/test_value_store_factory.cc190
-rw-r--r--chromium/extensions/browser/value_store/test_value_store_factory.h95
-rw-r--r--chromium/extensions/browser/value_store/testing_value_store.cc130
-rw-r--r--chromium/extensions/browser/value_store/testing_value_store.h61
-rw-r--r--chromium/extensions/browser/value_store/testing_value_store_unittest.cc24
-rw-r--r--chromium/extensions/browser/value_store/value_store.cc60
-rw-r--r--chromium/extensions/browser/value_store/value_store.h215
-rw-r--r--chromium/extensions/browser/value_store/value_store_change.cc57
-rw-r--r--chromium/extensions/browser/value_store/value_store_change.h62
-rw-r--r--chromium/extensions/browser/value_store/value_store_change_unittest.cc97
-rw-r--r--chromium/extensions/browser/value_store/value_store_factory.h67
-rw-r--r--chromium/extensions/browser/value_store/value_store_factory_impl.cc53
-rw-r--r--chromium/extensions/browser/value_store/value_store_factory_impl.h56
-rw-r--r--chromium/extensions/browser/value_store/value_store_frontend.cc143
-rw-r--r--chromium/extensions/browser/value_store/value_store_frontend.h62
-rw-r--r--chromium/extensions/browser/value_store/value_store_frontend_unittest.cc120
-rw-r--r--chromium/extensions/browser/value_store/value_store_unittest.cc480
-rw-r--r--chromium/extensions/browser/value_store/value_store_unittest.h72
-rw-r--r--chromium/extensions/browser/verified_contents.cc328
-rw-r--r--chromium/extensions/browser/verified_contents.h96
-rw-r--r--chromium/extensions/browser/verified_contents_unittest.cc153
-rw-r--r--chromium/extensions/browser/view_type_utils.cc47
-rw-r--r--chromium/extensions/browser/view_type_utils.h24
-rw-r--r--chromium/extensions/browser/warning_service.cc135
-rw-r--r--chromium/extensions/browser/warning_service.h100
-rw-r--r--chromium/extensions/browser/warning_service_factory.cc49
-rw-r--r--chromium/extensions/browser/warning_service_factory.h38
-rw-r--r--chromium/extensions/browser/warning_service_unittest.cc123
-rw-r--r--chromium/extensions/browser/warning_set.cc237
-rw-r--r--chromium/extensions/browser/warning_set.h121
-rw-r--r--chromium/extensions/browser/web_ui_user_script_loader.cc153
-rw-r--r--chromium/extensions/browser/web_ui_user_script_loader.h76
-rw-r--r--chromium/extensions/common/BUILD.gn90
-rw-r--r--chromium/extensions/common/DEPS9
-rw-r--r--chromium/extensions/common/OWNERS12
-rw-r--r--chromium/extensions/common/PRESUBMIT.py14
-rw-r--r--chromium/extensions/common/api/BUILD.gn46
-rw-r--r--chromium/extensions/common/api/OWNERS6
-rw-r--r--chromium/extensions/common/api/PRESUBMIT.py37
-rw-r--r--chromium/extensions/common/api/README1
-rw-r--r--chromium/extensions/common/api/_api_features.json489
-rw-r--r--chromium/extensions/common/api/_behavior_features.json55
-rw-r--r--chromium/extensions/common/api/_manifest_features.json338
-rw-r--r--chromium/extensions/common/api/_permission_features.json495
-rw-r--r--chromium/extensions/common/api/alarms.idl96
-rw-r--r--chromium/extensions/common/api/app_current_window_internal.idl67
-rw-r--r--chromium/extensions/common/api/app_runtime.idl111
-rw-r--r--chromium/extensions/common/api/app_view_guest_internal.json56
-rw-r--r--chromium/extensions/common/api/app_window.idl485
-rw-r--r--chromium/extensions/common/api/audio.idl115
-rw-r--r--chromium/extensions/common/api/bluetooth.idl155
-rw-r--r--chromium/extensions/common/api/bluetooth/bluetooth_manifest_data.cc77
-rw-r--r--chromium/extensions/common/api/bluetooth/bluetooth_manifest_data.h62
-rw-r--r--chromium/extensions/common/api/bluetooth/bluetooth_manifest_handler.cc47
-rw-r--r--chromium/extensions/common/api/bluetooth/bluetooth_manifest_handler.h42
-rw-r--r--chromium/extensions/common/api/bluetooth/bluetooth_manifest_permission.cc200
-rw-r--r--chromium/extensions/common/api/bluetooth/bluetooth_manifest_permission.h67
-rw-r--r--chromium/extensions/common/api/bluetooth_private.idl167
-rw-r--r--chromium/extensions/common/api/bluetooth_socket.idl316
-rw-r--r--chromium/extensions/common/api/cast_channel.idl235
-rw-r--r--chromium/extensions/common/api/cast_channel/BUILD.gn14
-rw-r--r--chromium/extensions/common/api/cast_channel/OWNERS3
-rw-r--r--chromium/extensions/common/api/cast_channel/authority_keys.proto17
-rw-r--r--chromium/extensions/common/api/cast_channel/cast_channel.proto91
-rw-r--r--chromium/extensions/common/api/cast_channel/logging.proto167
-rw-r--r--chromium/extensions/common/api/declarative/declarative_manifest_data.cc181
-rw-r--r--chromium/extensions/common/api/declarative/declarative_manifest_data.h46
-rw-r--r--chromium/extensions/common/api/declarative/declarative_manifest_handler.cc36
-rw-r--r--chromium/extensions/common/api/declarative/declarative_manifest_handler.h39
-rw-r--r--chromium/extensions/common/api/declarative/declarative_manifest_unittest.cc182
-rw-r--r--chromium/extensions/common/api/declarative_web_request.json597
-rw-r--r--chromium/extensions/common/api/diagnostics.idl37
-rw-r--r--chromium/extensions/common/api/display_source.idl143
-rw-r--r--chromium/extensions/common/api/dns.idl27
-rw-r--r--chromium/extensions/common/api/document_scan.idl37
-rw-r--r--chromium/extensions/common/api/events.json335
-rw-r--r--chromium/extensions/common/api/extension_options_internal.idl24
-rw-r--r--chromium/extensions/common/api/extension_types.json69
-rw-r--r--chromium/extensions/common/api/extension_view_internal.json66
-rw-r--r--chromium/extensions/common/api/extensions_manifest_types.json225
-rw-r--r--chromium/extensions/common/api/externs_checker.py36
-rwxr-xr-xchromium/extensions/common/api/externs_checker_test.py70
-rw-r--r--chromium/extensions/common/api/guest_view_internal.json116
-rw-r--r--chromium/extensions/common/api/hid.idl171
-rw-r--r--chromium/extensions/common/api/idle.json68
-rw-r--r--chromium/extensions/common/api/management.json457
-rw-r--r--chromium/extensions/common/api/messaging/message.h22
-rw-r--r--chromium/extensions/common/api/mime_handler.mojom40
-rw-r--r--chromium/extensions/common/api/mime_handler_private.idl41
-rw-r--r--chromium/extensions/common/api/mime_handler_view_guest_internal.json11
-rw-r--r--chromium/extensions/common/api/mojo_private.idl23
-rw-r--r--chromium/extensions/common/api/networking_config.idl103
-rw-r--r--chromium/extensions/common/api/networking_private.idl1053
-rw-r--r--chromium/extensions/common/api/power.idl27
-rw-r--r--chromium/extensions/common/api/printer_provider.idl109
-rw-r--r--chromium/extensions/common/api/printer_provider/usb_printer_manifest_data.cc71
-rw-r--r--chromium/extensions/common/api/printer_provider/usb_printer_manifest_data.h46
-rw-r--r--chromium/extensions/common/api/printer_provider/usb_printer_manifest_handler.cc37
-rw-r--r--chromium/extensions/common/api/printer_provider/usb_printer_manifest_handler.h26
-rw-r--r--chromium/extensions/common/api/printer_provider/usb_printer_manifest_unittest.cc45
-rw-r--r--chromium/extensions/common/api/printer_provider_internal.idl71
-rw-r--r--chromium/extensions/common/api/runtime.json533
-rw-r--r--chromium/extensions/common/api/schemas.gni17
-rw-r--r--chromium/extensions/common/api/serial.idl354
-rw-r--r--chromium/extensions/common/api/socket.idl360
-rw-r--r--chromium/extensions/common/api/sockets/sockets_manifest_data.cc52
-rw-r--r--chromium/extensions/common/api/sockets/sockets_manifest_data.h53
-rw-r--r--chromium/extensions/common/api/sockets/sockets_manifest_handler.cc47
-rw-r--r--chromium/extensions/common/api/sockets/sockets_manifest_handler.h42
-rw-r--r--chromium/extensions/common/api/sockets/sockets_manifest_permission.cc319
-rw-r--r--chromium/extensions/common/api/sockets/sockets_manifest_permission.h65
-rw-r--r--chromium/extensions/common/api/sockets/sockets_manifest_permission_unittest.cc407
-rw-r--r--chromium/extensions/common/api/sockets_tcp.idl273
-rw-r--r--chromium/extensions/common/api/sockets_tcp_server.idl195
-rw-r--r--chromium/extensions/common/api/sockets_udp.idl321
-rw-r--r--chromium/extensions/common/api/storage.json224
-rw-r--r--chromium/extensions/common/api/system_cpu.idl55
-rw-r--r--chromium/extensions/common/api/system_display.idl155
-rw-r--r--chromium/extensions/common/api/system_memory.idl21
-rw-r--r--chromium/extensions/common/api/system_network.idl30
-rw-r--r--chromium/extensions/common/api/system_storage.idl83
-rw-r--r--chromium/extensions/common/api/test.json437
-rw-r--r--chromium/extensions/common/api/usb.idl403
-rw-r--r--chromium/extensions/common/api/virtual_keyboard_private.json244
-rw-r--r--chromium/extensions/common/api/vpn_provider.idl193
-rw-r--r--chromium/extensions/common/api/web_request.json582
-rw-r--r--chromium/extensions/common/api/web_request_internal.json62
-rw-r--r--chromium/extensions/common/api/web_view_internal.json729
-rw-r--r--chromium/extensions/common/api/web_view_request.json10
-rw-r--r--chromium/extensions/common/api/webcam_private.idl46
-rw-r--r--chromium/extensions/common/cast/cast_cert_validator.cc383
-rw-r--r--chromium/extensions/common/cast/cast_cert_validator.h94
-rw-r--r--chromium/extensions/common/cast/cast_cert_validator_unittest.cc522
-rw-r--r--chromium/extensions/common/common_manifest_handlers.cc62
-rw-r--r--chromium/extensions/common/common_manifest_handlers.h17
-rw-r--r--chromium/extensions/common/csp_validator.cc378
-rw-r--r--chromium/extensions/common/csp_validator.h67
-rw-r--r--chromium/extensions/common/csp_validator_unittest.cc456
-rw-r--r--chromium/extensions/common/dom_action_types.h26
-rw-r--r--chromium/extensions/common/draggable_region.cc13
-rw-r--r--chromium/extensions/common/draggable_region.h21
-rw-r--r--chromium/extensions/common/error_utils.cc57
-rw-r--r--chromium/extensions/common/error_utils.h44
-rw-r--r--chromium/extensions/common/event_filter.cc187
-rw-r--r--chromium/extensions/common/event_filter.h130
-rw-r--r--chromium/extensions/common/event_filter_unittest.cc262
-rw-r--r--chromium/extensions/common/event_filtering_info.cc75
-rw-r--r--chromium/extensions/common/event_filtering_info.h84
-rw-r--r--chromium/extensions/common/event_matcher.cc111
-rw-r--r--chromium/extensions/common/event_matcher.h67
-rw-r--r--chromium/extensions/common/extension.cc786
-rw-r--r--chromium/extensions/common/extension.h575
-rw-r--r--chromium/extensions/common/extension_api.cc397
-rw-r--r--chromium/extensions/common/extension_api.h161
-rw-r--r--chromium/extensions/common/extension_builder.cc80
-rw-r--r--chromium/extensions/common/extension_builder.h65
-rw-r--r--chromium/extensions/common/extension_icon_set.cc80
-rw-r--r--chromium/extensions/common/extension_icon_set.h64
-rw-r--r--chromium/extensions/common/extension_l10n_util.cc467
-rw-r--r--chromium/extensions/common/extension_l10n_util.h130
-rw-r--r--chromium/extensions/common/extension_l10n_util_unittest.cc642
-rw-r--r--chromium/extensions/common/extension_message_generator.cc34
-rw-r--r--chromium/extensions/common/extension_message_generator.h9
-rw-r--r--chromium/extensions/common/extension_messages.cc342
-rw-r--r--chromium/extensions/common/extension_messages.h833
-rw-r--r--chromium/extensions/common/extension_paths.cc33
-rw-r--r--chromium/extensions/common/extension_paths.h27
-rw-r--r--chromium/extensions/common/extension_resource.cc132
-rw-r--r--chromium/extensions/common/extension_resource.h91
-rw-r--r--chromium/extensions/common/extension_resource_unittest.cc167
-rw-r--r--chromium/extensions/common/extension_set.cc155
-rw-r--r--chromium/extensions/common/extension_set.h156
-rw-r--r--chromium/extensions/common/extension_set_unittest.cc148
-rw-r--r--chromium/extensions/common/extension_urls.cc99
-rw-r--r--chromium/extensions/common/extension_urls.h87
-rw-r--r--chromium/extensions/common/extension_utility_messages.h97
-rw-r--r--chromium/extensions/common/extensions_client.cc39
-rw-r--r--chromium/extensions/common/extensions_client.h143
-rw-r--r--chromium/extensions/common/feature_switch.cc275
-rw-r--r--chromium/extensions/common/feature_switch.h111
-rw-r--r--chromium/extensions/common/features/api_feature.cc32
-rw-r--r--chromium/extensions/common/features/api_feature.h30
-rw-r--r--chromium/extensions/common/features/base_feature_provider.cc198
-rw-r--r--chromium/extensions/common/features/base_feature_provider.h51
-rw-r--r--chromium/extensions/common/features/base_feature_provider_unittest.cc148
-rw-r--r--chromium/extensions/common/features/behavior_feature.cc18
-rw-r--r--chromium/extensions/common/features/behavior_feature.h30
-rw-r--r--chromium/extensions/common/features/complex_feature.cc117
-rw-r--r--chromium/extensions/common/features/complex_feature.h62
-rw-r--r--chromium/extensions/common/features/complex_feature_unittest.cc134
-rw-r--r--chromium/extensions/common/features/feature.cc53
-rw-r--r--chromium/extensions/common/features/feature.h170
-rw-r--r--chromium/extensions/common/features/feature_provider.cc124
-rw-r--r--chromium/extensions/common/features/feature_provider.h66
-rw-r--r--chromium/extensions/common/features/feature_util.h31
-rw-r--r--chromium/extensions/common/features/json_feature_provider_source.cc53
-rw-r--r--chromium/extensions/common/features/json_feature_provider_source.h40
-rw-r--r--chromium/extensions/common/features/manifest_feature.cc53
-rw-r--r--chromium/extensions/common/features/manifest_feature.h30
-rw-r--r--chromium/extensions/common/features/permission_feature.cc52
-rw-r--r--chromium/extensions/common/features/permission_feature.h30
-rw-r--r--chromium/extensions/common/features/simple_feature.cc638
-rw-r--r--chromium/extensions/common/features/simple_feature.h215
-rw-r--r--chromium/extensions/common/features/simple_feature_filter.cc37
-rw-r--r--chromium/extensions/common/features/simple_feature_filter.h64
-rw-r--r--chromium/extensions/common/features/simple_feature_unittest.cc752
-rw-r--r--chromium/extensions/common/file_util.cc629
-rw-r--r--chromium/extensions/common/file_util.h157
-rw-r--r--chromium/extensions/common/file_util_unittest.cc552
-rw-r--r--chromium/extensions/common/guest_view/OWNERS16
-rw-r--r--chromium/extensions/common/guest_view/extensions_guest_view_messages.h47
-rw-r--r--chromium/extensions/common/host_id.cc33
-rw-r--r--chromium/extensions/common/host_id.h35
-rw-r--r--chromium/extensions/common/image_util.cc109
-rw-r--r--chromium/extensions/common/image_util.h32
-rw-r--r--chromium/extensions/common/image_util_unittest.cc122
-rw-r--r--chromium/extensions/common/install_warning.cc32
-rw-r--r--chromium/extensions/common/install_warning.h48
-rw-r--r--chromium/extensions/common/manifest.cc258
-rw-r--r--chromium/extensions/common/manifest.h203
-rw-r--r--chromium/extensions/common/manifest_constants.cc750
-rw-r--r--chromium/extensions/common/manifest_constants.h500
-rw-r--r--chromium/extensions/common/manifest_handler.cc252
-rw-r--r--chromium/extensions/common/manifest_handler.h173
-rw-r--r--chromium/extensions/common/manifest_handler_helpers.cc65
-rw-r--r--chromium/extensions/common/manifest_handler_helpers.h36
-rw-r--r--chromium/extensions/common/manifest_handler_unittest.cc281
-rw-r--r--chromium/extensions/common/manifest_handlers/app_isolation_info.cc99
-rw-r--r--chromium/extensions/common/manifest_handlers/app_isolation_info.h45
-rw-r--r--chromium/extensions/common/manifest_handlers/background_info.cc342
-rw-r--r--chromium/extensions/common/manifest_handlers/background_info.h101
-rw-r--r--chromium/extensions/common/manifest_handlers/content_capabilities_handler.cc117
-rw-r--r--chromium/extensions/common/manifest_handlers/content_capabilities_handler.h51
-rw-r--r--chromium/extensions/common/manifest_handlers/content_capabilities_manifest_unittest.cc90
-rw-r--r--chromium/extensions/common/manifest_handlers/csp_info.cc162
-rw-r--r--chromium/extensions/common/manifest_handlers/csp_info.h55
-rw-r--r--chromium/extensions/common/manifest_handlers/default_locale_handler.cc119
-rw-r--r--chromium/extensions/common/manifest_handlers/default_locale_handler.h47
-rw-r--r--chromium/extensions/common/manifest_handlers/default_locale_manifest_unittest.cc23
-rw-r--r--chromium/extensions/common/manifest_handlers/externally_connectable.cc229
-rw-r--r--chromium/extensions/common/manifest_handlers/externally_connectable.h99
-rw-r--r--chromium/extensions/common/manifest_handlers/externally_connectable_unittest.cc340
-rw-r--r--chromium/extensions/common/manifest_handlers/file_handler_info.cc186
-rw-r--r--chromium/extensions/common/manifest_handlers/file_handler_info.h63
-rw-r--r--chromium/extensions/common/manifest_handlers/file_handler_manifest_unittest.cc73
-rw-r--r--chromium/extensions/common/manifest_handlers/icons_handler.cc88
-rw-r--r--chromium/extensions/common/manifest_handlers/icons_handler.h53
-rw-r--r--chromium/extensions/common/manifest_handlers/icons_handler_unittest.cc69
-rw-r--r--chromium/extensions/common/manifest_handlers/incognito_info.cc83
-rw-r--r--chromium/extensions/common/manifest_handlers/incognito_info.h51
-rw-r--r--chromium/extensions/common/manifest_handlers/incognito_manifest_unittest.cc45
-rw-r--r--chromium/extensions/common/manifest_handlers/kiosk_mode_info.cc150
-rw-r--r--chromium/extensions/common/manifest_handlers/kiosk_mode_info.h74
-rw-r--r--chromium/extensions/common/manifest_handlers/kiosk_mode_info_unittest.cc56
-rw-r--r--chromium/extensions/common/manifest_handlers/launcher_page_info.cc76
-rw-r--r--chromium/extensions/common/manifest_handlers/launcher_page_info.h46
-rw-r--r--chromium/extensions/common/manifest_handlers/mime_types_handler.cc132
-rw-r--r--chromium/extensions/common/manifest_handlers/mime_types_handler.h76
-rw-r--r--chromium/extensions/common/manifest_handlers/nacl_modules_handler.cc91
-rw-r--r--chromium/extensions/common/manifest_handlers/nacl_modules_handler.h41
-rw-r--r--chromium/extensions/common/manifest_handlers/oauth2_manifest_handler.cc100
-rw-r--r--chromium/extensions/common/manifest_handlers/oauth2_manifest_handler.h48
-rw-r--r--chromium/extensions/common/manifest_handlers/oauth2_manifest_unittest.cc319
-rw-r--r--chromium/extensions/common/manifest_handlers/offline_enabled_info.cc76
-rw-r--r--chromium/extensions/common/manifest_handlers/offline_enabled_info.h46
-rw-r--r--chromium/extensions/common/manifest_handlers/options_page_info.cc231
-rw-r--r--chromium/extensions/common/manifest_handlers/options_page_info.h88
-rw-r--r--chromium/extensions/common/manifest_handlers/permissions_parser.cc341
-rw-r--r--chromium/extensions/common/manifest_handlers/permissions_parser.h63
-rw-r--r--chromium/extensions/common/manifest_handlers/requirements_info.cc159
-rw-r--r--chromium/extensions/common/manifest_handlers/requirements_info.h50
-rw-r--r--chromium/extensions/common/manifest_handlers/sandboxed_page_info.cc124
-rw-r--r--chromium/extensions/common/manifest_handlers/sandboxed_page_info.h55
-rw-r--r--chromium/extensions/common/manifest_handlers/shared_module_info.cc239
-rw-r--r--chromium/extensions/common/manifest_handlers/shared_module_info.h74
-rw-r--r--chromium/extensions/common/manifest_handlers/shared_module_manifest_unittest.cc124
-rw-r--r--chromium/extensions/common/manifest_handlers/web_accessible_resources_info.cc101
-rw-r--r--chromium/extensions/common/manifest_handlers/web_accessible_resources_info.h51
-rw-r--r--chromium/extensions/common/manifest_handlers/webview_info.cc177
-rw-r--r--chromium/extensions/common/manifest_handlers/webview_info.h60
-rw-r--r--chromium/extensions/common/manifest_test.cc274
-rw-r--r--chromium/extensions/common/manifest_test.h172
-rw-r--r--chromium/extensions/common/manifest_url_handlers.cc196
-rw-r--r--chromium/extensions/common/manifest_url_handlers.h103
-rw-r--r--chromium/extensions/common/message_bundle.cc346
-rw-r--r--chromium/extensions/common/message_bundle.h175
-rw-r--r--chromium/extensions/common/message_bundle_unittest.cc437
-rw-r--r--chromium/extensions/common/mojo/keep_alive.mojom10
-rw-r--r--chromium/extensions/common/mojo/stash.mojom34
-rw-r--r--chromium/extensions/common/mojo/wifi_display_session_service.mojom45
-rw-r--r--chromium/extensions/common/one_shot_event.cc102
-rw-r--r--chromium/extensions/common/one_shot_event.h114
-rw-r--r--chromium/extensions/common/one_shot_event_unittest.cc112
-rw-r--r--chromium/extensions/common/permissions/OWNERS2
-rw-r--r--chromium/extensions/common/permissions/PRESUBMIT.py29
-rw-r--r--chromium/extensions/common/permissions/api_permission.cc118
-rw-r--r--chromium/extensions/common/permissions/api_permission.h439
-rw-r--r--chromium/extensions/common/permissions/api_permission_set.cc312
-rw-r--r--chromium/extensions/common/permissions/api_permission_set.h172
-rw-r--r--chromium/extensions/common/permissions/api_permission_set_unittest.cc288
-rw-r--r--chromium/extensions/common/permissions/base_set_operators.h289
-rw-r--r--chromium/extensions/common/permissions/extensions_api_permissions.cc141
-rw-r--r--chromium/extensions/common/permissions/extensions_api_permissions.h21
-rw-r--r--chromium/extensions/common/permissions/manifest_permission.cc55
-rw-r--r--chromium/extensions/common/permissions/manifest_permission.h86
-rw-r--r--chromium/extensions/common/permissions/manifest_permission_set.cc95
-rw-r--r--chromium/extensions/common/permissions/manifest_permission_set.h47
-rw-r--r--chromium/extensions/common/permissions/manifest_permission_set_unittest.cc222
-rw-r--r--chromium/extensions/common/permissions/media_galleries_permission.cc171
-rw-r--r--chromium/extensions/common/permissions/media_galleries_permission.h61
-rw-r--r--chromium/extensions/common/permissions/media_galleries_permission_data.cc61
-rw-r--r--chromium/extensions/common/permissions/media_galleries_permission_data.h50
-rw-r--r--chromium/extensions/common/permissions/permission_message.cc23
-rw-r--r--chromium/extensions/common/permissions/permission_message.h68
-rw-r--r--chromium/extensions/common/permissions/permission_message_provider.cc18
-rw-r--r--chromium/extensions/common/permissions/permission_message_provider.h58
-rw-r--r--chromium/extensions/common/permissions/permission_message_test_util.cc322
-rw-r--r--chromium/extensions/common/permissions/permission_message_test_util.h91
-rw-r--r--chromium/extensions/common/permissions/permission_message_util.cc87
-rw-r--r--chromium/extensions/common/permissions/permission_message_util.h24
-rw-r--r--chromium/extensions/common/permissions/permission_set.cc268
-rw-r--r--chromium/extensions/common/permissions/permission_set.h175
-rw-r--r--chromium/extensions/common/permissions/permissions_data.cc368
-rw-r--r--chromium/extensions/common/permissions/permissions_data.h267
-rw-r--r--chromium/extensions/common/permissions/permissions_info.cc96
-rw-r--r--chromium/extensions/common/permissions/permissions_info.h82
-rw-r--r--chromium/extensions/common/permissions/permissions_provider.h40
-rw-r--r--chromium/extensions/common/permissions/set_disjunction_permission.h171
-rw-r--r--chromium/extensions/common/permissions/settings_override_permission.cc91
-rw-r--r--chromium/extensions/common/permissions/settings_override_permission.h46
-rw-r--r--chromium/extensions/common/permissions/socket_permission.cc63
-rw-r--r--chromium/extensions/common/permissions/socket_permission.h44
-rw-r--r--chromium/extensions/common/permissions/socket_permission_data.cc159
-rw-r--r--chromium/extensions/common/permissions/socket_permission_data.h89
-rw-r--r--chromium/extensions/common/permissions/socket_permission_entry.cc215
-rw-r--r--chromium/extensions/common/permissions/socket_permission_entry.h84
-rw-r--r--chromium/extensions/common/permissions/socket_permission_unittest.cc330
-rw-r--r--chromium/extensions/common/permissions/usb_device_permission.cc80
-rw-r--r--chromium/extensions/common/permissions/usb_device_permission.h44
-rw-r--r--chromium/extensions/common/permissions/usb_device_permission_data.cc104
-rw-r--r--chromium/extensions/common/permissions/usb_device_permission_data.h72
-rw-r--r--chromium/extensions/common/permissions/usb_device_permission_unittest.cc19
-rw-r--r--chromium/extensions/common/stack_frame.cc79
-rw-r--r--chromium/extensions/common/stack_frame.h46
-rw-r--r--chromium/extensions/common/stack_frame_unittest.cc87
-rw-r--r--chromium/extensions/common/switches.cc119
-rw-r--r--chromium/extensions/common/switches.h49
-rw-r--r--chromium/extensions/common/test_util.cc59
-rw-r--r--chromium/extensions/common/test_util.h35
-rw-r--r--chromium/extensions/common/update_manifest.cc287
-rw-r--r--chromium/extensions/common/update_manifest.h96
-rw-r--r--chromium/extensions/common/update_manifest_unittest.cc185
-rw-r--r--chromium/extensions/common/url_pattern_set.cc289
-rw-r--r--chromium/extensions/common/url_pattern_set.h125
-rw-r--r--chromium/extensions/common/url_pattern_set_unittest.cc479
-rw-r--r--chromium/extensions/common/url_pattern_unittest.cc850
-rw-r--r--chromium/extensions/common/user_script.cc298
-rw-r--r--chromium/extensions/common/user_script.h329
-rw-r--r--chromium/extensions/common/user_script_unittest.cc233
-rw-r--r--chromium/extensions/common/value_builder.cc103
-rw-r--r--chromium/extensions/common/value_builder.h99
-rw-r--r--chromium/extensions/common/value_builder_unittest.cc36
-rw-r--r--chromium/extensions/common/value_counter.cc53
-rw-r--r--chromium/extensions/common/value_counter.h52
-rw-r--r--chromium/extensions/common/view_type.cc18
-rw-r--r--chromium/extensions/common/view_type.h43
-rw-r--r--chromium/extensions/components/DEPS7
-rw-r--r--chromium/extensions/components/README11
-rw-r--r--chromium/extensions/components/javascript_dialog_extensions_client/BUILD.gn18
-rw-r--r--chromium/extensions/components/javascript_dialog_extensions_client/DEPS6
-rw-r--r--chromium/extensions/components/javascript_dialog_extensions_client/OWNERS2
-rw-r--r--chromium/extensions/components/javascript_dialog_extensions_client/javascript_dialog_extension_client_impl.cc86
-rw-r--r--chromium/extensions/components/javascript_dialog_extensions_client/javascript_dialog_extension_client_impl.h15
-rw-r--r--chromium/extensions/components/native_app_window/BUILD.gn20
-rw-r--r--chromium/extensions/components/native_app_window/DEPS7
-rw-r--r--chromium/extensions/components/native_app_window/OWNERS2
-rw-r--r--chromium/extensions/components/native_app_window/README2
-rw-r--r--chromium/extensions/components/native_app_window/native_app_window_views.cc448
-rw-r--r--chromium/extensions/components/native_app_window/native_app_window_views.h191
-rw-r--r--chromium/extensions/extensions.gni25
-rw-r--r--chromium/extensions/extensions_resources.grd19
-rw-r--r--chromium/extensions/extensions_strings.grd344
-rw-r--r--chromium/extensions/renderer/BUILD.gn44
-rw-r--r--chromium/extensions/renderer/DEPS25
-rw-r--r--chromium/extensions/renderer/OWNERS2
-rw-r--r--chromium/extensions/renderer/activity_log_converter_strategy.cc83
-rw-r--r--chromium/extensions/renderer/activity_log_converter_strategy.h50
-rw-r--r--chromium/extensions/renderer/activity_log_converter_strategy_unittest.cc172
-rw-r--r--chromium/extensions/renderer/api/automation/automation_api_helper.cc90
-rw-r--r--chromium/extensions/renderer/api/automation/automation_api_helper.h34
-rw-r--r--chromium/extensions/renderer/api/display_source/OWNERS2
-rw-r--r--chromium/extensions/renderer/api/display_source/display_source_session.cc43
-rw-r--r--chromium/extensions/renderer/api/display_source/display_source_session.h116
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.cc166
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h122
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor_unittest.cc151
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.cc46
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h58
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.cc188
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.h72
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.cc247
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.h69
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.cc110
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.h61
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer_unittest.cc905
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_session.cc225
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_session.h95
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_stream_packet_part.h43
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.cc727
-rw-r--r--chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h176
-rw-r--r--chromium/extensions/renderer/api/mojo_private/mojo_private_unittest.cc59
-rw-r--r--chromium/extensions/renderer/api/serial/DEPS3
-rw-r--r--chromium/extensions/renderer/api/serial/data_receiver_unittest.cc202
-rw-r--r--chromium/extensions/renderer/api/serial/data_sender_unittest.cc213
-rw-r--r--chromium/extensions/renderer/api/serial/serial_api_unittest.cc738
-rw-r--r--chromium/extensions/renderer/api_activity_logger.cc82
-rw-r--r--chromium/extensions/renderer/api_activity_logger.h52
-rw-r--r--chromium/extensions/renderer/api_definitions_natives.cc37
-rw-r--r--chromium/extensions/renderer/api_definitions_natives.h34
-rw-r--r--chromium/extensions/renderer/api_test_base.cc240
-rw-r--r--chromium/extensions/renderer/api_test_base.h124
-rw-r--r--chromium/extensions/renderer/api_test_base_unittest.cc34
-rw-r--r--chromium/extensions/renderer/app_window_custom_bindings.cc82
-rw-r--r--chromium/extensions/renderer/app_window_custom_bindings.h32
-rw-r--r--chromium/extensions/renderer/binding_generating_native_handler.cc112
-rw-r--r--chromium/extensions/renderer/binding_generating_native_handler.h38
-rw-r--r--chromium/extensions/renderer/blob_native_handler.cc54
-rw-r--r--chromium/extensions/renderer/blob_native_handler.h30
-rw-r--r--chromium/extensions/renderer/console.cc127
-rw-r--r--chromium/extensions/renderer/console.h46
-rw-r--r--chromium/extensions/renderer/console_apitest.cc16
-rw-r--r--chromium/extensions/renderer/content_watcher.cc125
-rw-r--r--chromium/extensions/renderer/content_watcher.h73
-rw-r--r--chromium/extensions/renderer/context_menus_custom_bindings.cc32
-rw-r--r--chromium/extensions/renderer/context_menus_custom_bindings.h21
-rw-r--r--chromium/extensions/renderer/css_native_handler.cc36
-rw-r--r--chromium/extensions/renderer/css_native_handler.h28
-rw-r--r--chromium/extensions/renderer/dispatcher.cc1629
-rw-r--r--chromium/extensions/renderer/dispatcher.h318
-rw-r--r--chromium/extensions/renderer/dispatcher_delegate.h59
-rw-r--r--chromium/extensions/renderer/display_source_custom_bindings.cc306
-rw-r--r--chromium/extensions/renderer/display_source_custom_bindings.h63
-rw-r--r--chromium/extensions/renderer/document_custom_bindings.cc42
-rw-r--r--chromium/extensions/renderer/document_custom_bindings.h25
-rw-r--r--chromium/extensions/renderer/dom_activity_logger.cc133
-rw-r--r--chromium/extensions/renderer/dom_activity_logger.h91
-rw-r--r--chromium/extensions/renderer/event_bindings.cc367
-rw-r--r--chromium/extensions/renderer/event_bindings.h89
-rw-r--r--chromium/extensions/renderer/event_unittest.cc259
-rw-r--r--chromium/extensions/renderer/extension_frame_helper.cc277
-rw-r--r--chromium/extensions/renderer/extension_frame_helper.h148
-rw-r--r--chromium/extensions/renderer/extension_groups.h21
-rw-r--r--chromium/extensions/renderer/extension_helper.cc76
-rw-r--r--chromium/extensions/renderer/extension_helper.h37
-rw-r--r--chromium/extensions/renderer/extension_injection_host.cc88
-rw-r--r--chromium/extensions/renderer/extension_injection_host.h45
-rw-r--r--chromium/extensions/renderer/extensions_render_frame_observer.cc100
-rw-r--r--chromium/extensions/renderer/extensions_render_frame_observer.h38
-rw-r--r--chromium/extensions/renderer/extensions_renderer_client.cc26
-rw-r--r--chromium/extensions/renderer/extensions_renderer_client.h39
-rw-r--r--chromium/extensions/renderer/file_system_natives.cc124
-rw-r--r--chromium/extensions/renderer/file_system_natives.h31
-rw-r--r--chromium/extensions/renderer/gc_callback.cc58
-rw-r--r--chromium/extensions/renderer/gc_callback.h56
-rw-r--r--chromium/extensions/renderer/gc_callback_unittest.cc161
-rw-r--r--chromium/extensions/renderer/guest_view/OWNERS5
-rw-r--r--chromium/extensions/renderer/guest_view/extensions_guest_view_container.cc64
-rw-r--r--chromium/extensions/renderer/guest_view/extensions_guest_view_container.h50
-rw-r--r--chromium/extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.cc26
-rw-r--r--chromium/extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.h23
-rw-r--r--chromium/extensions/renderer/guest_view/guest_view_internal_custom_bindings.cc438
-rw-r--r--chromium/extensions/renderer/guest_view/guest_view_internal_custom_bindings.h98
-rw-r--r--chromium/extensions/renderer/guest_view/mime_handler_view/OWNERS1
-rw-r--r--chromium/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.cc340
-rw-r--r--chromium/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.h132
-rw-r--r--chromium/extensions/renderer/i18n_custom_bindings.cc245
-rw-r--r--chromium/extensions/renderer/i18n_custom_bindings.h26
-rw-r--r--chromium/extensions/renderer/id_generator_custom_bindings.cc31
-rw-r--r--chromium/extensions/renderer/id_generator_custom_bindings.h25
-rw-r--r--chromium/extensions/renderer/injection_host.cc12
-rw-r--r--chromium/extensions/renderer/injection_host.h47
-rw-r--r--chromium/extensions/renderer/json_schema_unittest.cc111
-rw-r--r--chromium/extensions/renderer/lazy_background_page_native_handler.cc46
-rw-r--r--chromium/extensions/renderer/lazy_background_page_native_handler.h23
-rw-r--r--chromium/extensions/renderer/logging_native_handler.cc78
-rw-r--r--chromium/extensions/renderer/logging_native_handler.h53
-rw-r--r--chromium/extensions/renderer/messaging_bindings.cc492
-rw-r--r--chromium/extensions/renderer/messaging_bindings.h73
-rw-r--r--chromium/extensions/renderer/messaging_utils_unittest.cc196
-rw-r--r--chromium/extensions/renderer/module_system.cc757
-rw-r--r--chromium/extensions/renderer/module_system.h253
-rw-r--r--chromium/extensions/renderer/module_system_test.cc261
-rw-r--r--chromium/extensions/renderer/module_system_test.h111
-rw-r--r--chromium/extensions/renderer/module_system_unittest.cc518
-rw-r--r--chromium/extensions/renderer/mojo/keep_alive_client_unittest.cc100
-rw-r--r--chromium/extensions/renderer/mojo/stash_client_unittest.cc55
-rw-r--r--chromium/extensions/renderer/native_handler.cc22
-rw-r--r--chromium/extensions/renderer/native_handler.h49
-rw-r--r--chromium/extensions/renderer/object_backed_native_handler.cc222
-rw-r--r--chromium/extensions/renderer/object_backed_native_handler.h123
-rw-r--r--chromium/extensions/renderer/print_native_handler.cc32
-rw-r--r--chromium/extensions/renderer/print_native_handler.h21
-rw-r--r--chromium/extensions/renderer/process_info_native_handler.cc98
-rw-r--r--chromium/extensions/renderer/process_info_native_handler.h43
-rw-r--r--chromium/extensions/renderer/programmatic_script_injector.cc185
-rw-r--r--chromium/extensions/renderer/programmatic_script_injector.h84
-rw-r--r--chromium/extensions/renderer/render_frame_observer_natives.cc107
-rw-r--r--chromium/extensions/renderer/render_frame_observer_natives.h38
-rw-r--r--chromium/extensions/renderer/renderer_extension_registry.cc105
-rw-r--r--chromium/extensions/renderer/renderer_extension_registry.h62
-rw-r--r--chromium/extensions/renderer/request_sender.cc143
-rw-r--r--chromium/extensions/renderer/request_sender.h107
-rw-r--r--chromium/extensions/renderer/resource_bundle_source_map.cc55
-rw-r--r--chromium/extensions/renderer/resource_bundle_source_map.h45
-rw-r--r--chromium/extensions/renderer/resources/OWNERS4
-rw-r--r--chromium/extensions/renderer/resources/app_runtime_custom_bindings.js83
-rw-r--r--chromium/extensions/renderer/resources/app_window_custom_bindings.js410
-rw-r--r--chromium/extensions/renderer/resources/async_waiter.js93
-rw-r--r--chromium/extensions/renderer/resources/binding.js574
-rw-r--r--chromium/extensions/renderer/resources/browser_test_environment_specific_bindings.js15
-rw-r--r--chromium/extensions/renderer/resources/context_menus_custom_bindings.js26
-rw-r--r--chromium/extensions/renderer/resources/context_menus_handlers.js141
-rw-r--r--chromium/extensions/renderer/resources/data_receiver.js336
-rw-r--r--chromium/extensions/renderer/resources/data_sender.js335
-rw-r--r--chromium/extensions/renderer/resources/declarative_webrequest_custom_bindings.js96
-rw-r--r--chromium/extensions/renderer/resources/display_source_custom_bindings.js69
-rw-r--r--chromium/extensions/renderer/resources/entry_id_manager.js52
-rw-r--r--chromium/extensions/renderer/resources/event.js520
-rw-r--r--chromium/extensions/renderer/resources/extension.css352
-rw-r--r--chromium/extensions/renderer/resources/extension_custom_bindings.js111
-rw-r--r--chromium/extensions/renderer/resources/extension_fonts.css12
-rw-r--r--chromium/extensions/renderer/resources/extensions_renderer_resources.grd111
-rw-r--r--chromium/extensions/renderer/resources/greasemonkey_api.js82
-rw-r--r--chromium/extensions/renderer/resources/guest_view/OWNERS4
-rw-r--r--chromium/extensions/renderer/resources/guest_view/app_view/app_view.js80
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_options/extension_options.js52
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_attributes.js43
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_constants.js14
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_events.js40
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_view/extension_view.js139
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_api_methods.js45
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_attributes.js55
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_constants.js14
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_events.js32
-rw-r--r--chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_internal.js7
-rw-r--r--chromium/extensions/renderer/resources/guest_view/guest_view.js355
-rw-r--r--chromium/extensions/renderer/resources/guest_view/guest_view_attributes.js142
-rw-r--r--chromium/extensions/renderer/resources/guest_view/guest_view_container.js311
-rw-r--r--chromium/extensions/renderer/resources/guest_view/guest_view_deny.js58
-rw-r--r--chromium/extensions/renderer/resources/guest_view/guest_view_events.js178
-rw-r--r--chromium/extensions/renderer/resources/guest_view/guest_view_iframe.js108
-rw-r--r--chromium/extensions/renderer/resources/guest_view/guest_view_iframe_container.js30
-rw-r--r--chromium/extensions/renderer/resources/guest_view/web_view/web_view.js235
-rw-r--r--chromium/extensions/renderer/resources/guest_view/web_view/web_view_action_requests.js296
-rw-r--r--chromium/extensions/renderer/resources/guest_view/web_view/web_view_api_methods.js204
-rw-r--r--chromium/extensions/renderer/resources/guest_view/web_view/web_view_attributes.js277
-rw-r--r--chromium/extensions/renderer/resources/guest_view/web_view/web_view_constants.js40
-rw-r--r--chromium/extensions/renderer/resources/guest_view/web_view/web_view_events.js301
-rw-r--r--chromium/extensions/renderer/resources/guest_view/web_view/web_view_experimental.js24
-rw-r--r--chromium/extensions/renderer/resources/guest_view/web_view/web_view_internal.js7
-rw-r--r--chromium/extensions/renderer/resources/guest_view/web_view/web_view_request_custom_bindings.js55
-rw-r--r--chromium/extensions/renderer/resources/i18n_custom_bindings.js49
-rw-r--r--chromium/extensions/renderer/resources/image_util.js82
-rw-r--r--chromium/extensions/renderer/resources/json_schema.js525
-rw-r--r--chromium/extensions/renderer/resources/keep_alive.js41
-rw-r--r--chromium/extensions/renderer/resources/last_error.js143
-rw-r--r--chromium/extensions/renderer/resources/media_router_bindings.js777
-rw-r--r--chromium/extensions/renderer/resources/messaging.js446
-rw-r--r--chromium/extensions/renderer/resources/messaging_utils.js53
-rw-r--r--chromium/extensions/renderer/resources/mime_handler_private_custom_bindings.js75
-rw-r--r--chromium/extensions/renderer/resources/mojo_private_custom_bindings.js23
-rw-r--r--chromium/extensions/renderer/resources/permissions_custom_bindings.js92
-rw-r--r--chromium/extensions/renderer/resources/platform_app.css35
-rw-r--r--chromium/extensions/renderer/resources/platform_app.js232
-rw-r--r--chromium/extensions/renderer/resources/printer_provider_custom_bindings.js123
-rw-r--r--chromium/extensions/renderer/resources/runtime_custom_bindings.js206
-rw-r--r--chromium/extensions/renderer/resources/schema_utils.js159
-rw-r--r--chromium/extensions/renderer/resources/send_request.js151
-rw-r--r--chromium/extensions/renderer/resources/serial_custom_bindings.js117
-rw-r--r--chromium/extensions/renderer/resources/serial_service.js554
-rw-r--r--chromium/extensions/renderer/resources/service_worker_bindings.js67
-rw-r--r--chromium/extensions/renderer/resources/set_icon.js112
-rw-r--r--chromium/extensions/renderer/resources/stash_client.js168
-rw-r--r--chromium/extensions/renderer/resources/storage_area.js40
-rw-r--r--chromium/extensions/renderer/resources/test_custom_bindings.js364
-rw-r--r--chromium/extensions/renderer/resources/uncaught_exception_handler.js110
-rw-r--r--chromium/extensions/renderer/resources/utils.js240
-rw-r--r--chromium/extensions/renderer/resources/web_request_custom_bindings.js23
-rw-r--r--chromium/extensions/renderer/resources/web_request_internal_custom_bindings.js196
-rw-r--r--chromium/extensions/renderer/resources/window_controls.js78
-rw-r--r--chromium/extensions/renderer/resources/window_controls_template.html52
-rw-r--r--chromium/extensions/renderer/runtime_custom_bindings.cc184
-rw-r--r--chromium/extensions/renderer/runtime_custom_bindings.h34
-rw-r--r--chromium/extensions/renderer/safe_builtins.cc259
-rw-r--r--chromium/extensions/renderer/safe_builtins.h47
-rw-r--r--chromium/extensions/renderer/safe_builtins_unittest.cc66
-rw-r--r--chromium/extensions/renderer/scoped_web_frame.cc24
-rw-r--r--chromium/extensions/renderer/scoped_web_frame.h34
-rw-r--r--chromium/extensions/renderer/script_context.cc489
-rw-r--r--chromium/extensions/renderer/script_context.h259
-rw-r--r--chromium/extensions/renderer/script_context_browsertest.cc91
-rw-r--r--chromium/extensions/renderer/script_context_set.cc223
-rw-r--r--chromium/extensions/renderer/script_context_set.h145
-rw-r--r--chromium/extensions/renderer/script_context_set_unittest.cc62
-rw-r--r--chromium/extensions/renderer/script_context_unittest.cc24
-rw-r--r--chromium/extensions/renderer/script_injection.cc319
-rw-r--r--chromium/extensions/renderer/script_injection.h150
-rw-r--r--chromium/extensions/renderer/script_injection_callback.cc23
-rw-r--r--chromium/extensions/renderer/script_injection_callback.h42
-rw-r--r--chromium/extensions/renderer/script_injection_manager.cc514
-rw-r--r--chromium/extensions/renderer/script_injection_manager.h126
-rw-r--r--chromium/extensions/renderer/script_injector.h98
-rw-r--r--chromium/extensions/renderer/scripts_run_info.cc77
-rw-r--r--chromium/extensions/renderer/scripts_run_info.h64
-rw-r--r--chromium/extensions/renderer/send_request_natives.cc79
-rw-r--r--chromium/extensions/renderer/send_request_natives.h37
-rw-r--r--chromium/extensions/renderer/set_icon_natives.cc155
-rw-r--r--chromium/extensions/renderer/set_icon_natives.h33
-rw-r--r--chromium/extensions/renderer/static_v8_external_one_byte_string_resource.cc26
-rw-r--r--chromium/extensions/renderer/static_v8_external_one_byte_string_resource.h35
-rw-r--r--chromium/extensions/renderer/test_extensions_renderer_client.cc22
-rw-r--r--chromium/extensions/renderer/test_extensions_renderer_client.h28
-rw-r--r--chromium/extensions/renderer/test_features_native_handler.cc32
-rw-r--r--chromium/extensions/renderer/test_features_native_handler.h22
-rw-r--r--chromium/extensions/renderer/test_native_handler.cc24
-rw-r--r--chromium/extensions/renderer/test_native_handler.h29
-rw-r--r--chromium/extensions/renderer/user_gestures_native_handler.cc55
-rw-r--r--chromium/extensions/renderer/user_gestures_native_handler.h24
-rw-r--r--chromium/extensions/renderer/user_script_injector.cc268
-rw-r--r--chromium/extensions/renderer/user_script_injector.h86
-rw-r--r--chromium/extensions/renderer/user_script_set.cc235
-rw-r--r--chromium/extensions/renderer/user_script_set.h100
-rw-r--r--chromium/extensions/renderer/user_script_set_manager.cc159
-rw-r--r--chromium/extensions/renderer/user_script_set_manager.h113
-rw-r--r--chromium/extensions/renderer/utils_native_handler.cc29
-rw-r--r--chromium/extensions/renderer/utils_native_handler.h30
-rw-r--r--chromium/extensions/renderer/utils_unittest.cc77
-rw-r--r--chromium/extensions/renderer/v8_context_native_handler.cc64
-rw-r--r--chromium/extensions/renderer/v8_context_native_handler.h29
-rw-r--r--chromium/extensions/renderer/v8_helpers.h166
-rw-r--r--chromium/extensions/renderer/v8_schema_registry.cc142
-rw-r--r--chromium/extensions/renderer/v8_schema_registry.h55
-rw-r--r--chromium/extensions/renderer/wake_event_page.cc200
-rw-r--r--chromium/extensions/renderer/wake_event_page.h116
-rw-r--r--chromium/extensions/renderer/web_ui_injection_host.cc34
-rw-r--r--chromium/extensions/renderer/web_ui_injection_host.h33
-rw-r--r--chromium/extensions/renderer/worker_script_context_set.cc82
-rw-r--r--chromium/extensions/renderer/worker_script_context_set.h46
-rw-r--r--chromium/extensions/shell/BUILD.gn226
-rw-r--r--chromium/extensions/shell/DEPS25
-rw-r--r--chromium/extensions/shell/OWNERS2
-rw-r--r--chromium/extensions/shell/README22
-rw-r--r--chromium/extensions/shell/app/DEPS8
-rw-r--r--chromium/extensions/shell/app/README5
-rw-r--r--chromium/extensions/shell/app/app-Info.plist34
-rw-r--r--chromium/extensions/shell/app/framework-Info.plist18
-rw-r--r--chromium/extensions/shell/app/helper-Info.plist30
-rw-r--r--chromium/extensions/shell/app/paths_mac.h22
-rw-r--r--chromium/extensions/shell/app/paths_mac.mm52
-rw-r--r--chromium/extensions/shell/app/shell_main.cc46
-rw-r--r--chromium/extensions/shell/app/shell_main_delegate.cc175
-rw-r--r--chromium/extensions/shell/app/shell_main_delegate.h66
-rw-r--r--chromium/extensions/shell/app/shell_main_mac.cc16
-rw-r--r--chromium/extensions/shell/app/shell_main_mac.h16
-rw-r--r--chromium/extensions/shell/app_shell.gni16
-rw-r--r--chromium/extensions/shell/app_shell_resources.grd15
-rw-r--r--chromium/extensions/shell/browser/DEPS48
-rw-r--r--chromium/extensions/shell/browser/api/identity/identity_api.cc160
-rw-r--r--chromium/extensions/shell/browser/api/identity/identity_api.h105
-rw-r--r--chromium/extensions/shell/browser/api/identity/identity_api_unittest.cc131
-rw-r--r--chromium/extensions/shell/browser/api/vpn_provider/OWNERS3
-rw-r--r--chromium/extensions/shell/browser/api/vpn_provider/vpn_service_factory.cc59
-rw-r--r--chromium/extensions/shell/browser/default_shell_browser_main_delegate.cc81
-rw-r--r--chromium/extensions/shell/browser/default_shell_browser_main_delegate.h32
-rw-r--r--chromium/extensions/shell/browser/desktop_controller.cc32
-rw-r--r--chromium/extensions/shell/browser/desktop_controller.h65
-rw-r--r--chromium/extensions/shell/browser/geolocation/geolocation_apitest.cc17
-rw-r--r--chromium/extensions/shell/browser/media_capture_util.cc89
-rw-r--r--chromium/extensions/shell/browser/media_capture_util.h38
-rw-r--r--chromium/extensions/shell/browser/shell_app_delegate.cc104
-rw-r--r--chromium/extensions/shell/browser/shell_app_delegate.h63
-rw-r--r--chromium/extensions/shell/browser/shell_app_view_guest_delegate.cc27
-rw-r--r--chromium/extensions/shell/browser/shell_app_view_guest_delegate.h30
-rw-r--r--chromium/extensions/shell/browser/shell_app_window_client.cc36
-rw-r--r--chromium/extensions/shell/browser/shell_app_window_client.h38
-rw-r--r--chromium/extensions/shell/browser/shell_app_window_client_aura.cc23
-rw-r--r--chromium/extensions/shell/browser/shell_app_window_client_mac.mm23
-rw-r--r--chromium/extensions/shell/browser/shell_audio_controller_chromeos.cc93
-rw-r--r--chromium/extensions/shell/browser/shell_audio_controller_chromeos.h40
-rw-r--r--chromium/extensions/shell/browser/shell_audio_controller_chromeos_unittest.cc140
-rw-r--r--chromium/extensions/shell/browser/shell_browser_context.cc95
-rw-r--r--chromium/extensions/shell/browser/shell_browser_context.h44
-rw-r--r--chromium/extensions/shell/browser/shell_browser_context_keyed_service_factories.cc22
-rw-r--r--chromium/extensions/shell/browser/shell_browser_context_keyed_service_factories.h18
-rw-r--r--chromium/extensions/shell/browser/shell_browser_main_delegate.h36
-rw-r--r--chromium/extensions/shell/browser/shell_browser_main_parts.cc299
-rw-r--r--chromium/extensions/shell/browser/shell_browser_main_parts.h120
-rw-r--r--chromium/extensions/shell/browser/shell_browser_main_parts_mac.h15
-rw-r--r--chromium/extensions/shell/browser/shell_browser_main_parts_mac.mm16
-rw-r--r--chromium/extensions/shell/browser/shell_browsertest.cc38
-rw-r--r--chromium/extensions/shell/browser/shell_content_browser_client.cc263
-rw-r--r--chromium/extensions/shell/browser/shell_content_browser_client.h84
-rw-r--r--chromium/extensions/shell/browser/shell_desktop_controller_aura.cc348
-rw-r--r--chromium/extensions/shell/browser/shell_desktop_controller_aura.h143
-rw-r--r--chromium/extensions/shell/browser/shell_desktop_controller_aura_unittest.cc76
-rw-r--r--chromium/extensions/shell/browser/shell_desktop_controller_mac.h45
-rw-r--r--chromium/extensions/shell/browser/shell_desktop_controller_mac.mm53
-rw-r--r--chromium/extensions/shell/browser/shell_device_client.cc39
-rw-r--r--chromium/extensions/shell/browser/shell_device_client.h36
-rw-r--r--chromium/extensions/shell/browser/shell_display_info_provider.cc40
-rw-r--r--chromium/extensions/shell/browser/shell_display_info_provider.h32
-rw-r--r--chromium/extensions/shell/browser/shell_extension_host_delegate.cc73
-rw-r--r--chromium/extensions/shell/browser/shell_extension_host_delegate.h44
-rw-r--r--chromium/extensions/shell/browser/shell_extension_system.cc199
-rw-r--r--chromium/extensions/shell/browser/shell_extension_system.h99
-rw-r--r--chromium/extensions/shell/browser/shell_extension_system_factory.cc52
-rw-r--r--chromium/extensions/shell/browser/shell_extension_system_factory.h41
-rw-r--r--chromium/extensions/shell/browser/shell_extension_web_contents_observer.cc20
-rw-r--r--chromium/extensions/shell/browser/shell_extension_web_contents_observer.h30
-rw-r--r--chromium/extensions/shell/browser/shell_extensions_api_client.cc25
-rw-r--r--chromium/extensions/shell/browser/shell_extensions_api_client.h24
-rw-r--r--chromium/extensions/shell/browser/shell_extensions_browser_client.cc252
-rw-r--r--chromium/extensions/shell/browser/shell_extensions_browser_client.h115
-rw-r--r--chromium/extensions/shell/browser/shell_nacl_browser_delegate.cc196
-rw-r--r--chromium/extensions/shell/browser/shell_nacl_browser_delegate.h57
-rw-r--r--chromium/extensions/shell/browser/shell_nacl_browser_delegate_unittest.cc27
-rw-r--r--chromium/extensions/shell/browser/shell_native_app_window.cc198
-rw-r--r--chromium/extensions/shell/browser/shell_native_app_window.h80
-rw-r--r--chromium/extensions/shell/browser/shell_native_app_window_aura.cc71
-rw-r--r--chromium/extensions/shell/browser/shell_native_app_window_aura.h36
-rw-r--r--chromium/extensions/shell/browser/shell_native_app_window_aura_unittest.cc75
-rw-r--r--chromium/extensions/shell/browser/shell_native_app_window_mac.h65
-rw-r--r--chromium/extensions/shell/browser/shell_native_app_window_mac.mm120
-rw-r--r--chromium/extensions/shell/browser/shell_network_controller_chromeos.cc215
-rw-r--r--chromium/extensions/shell/browser/shell_network_controller_chromeos.h89
-rw-r--r--chromium/extensions/shell/browser/shell_network_delegate.cc132
-rw-r--r--chromium/extensions/shell/browser/shell_network_delegate.h59
-rw-r--r--chromium/extensions/shell/browser/shell_oauth2_token_service.cc53
-rw-r--r--chromium/extensions/shell/browser/shell_oauth2_token_service.h43
-rw-r--r--chromium/extensions/shell/browser/shell_oauth2_token_service_delegate.cc59
-rw-r--r--chromium/extensions/shell/browser/shell_oauth2_token_service_delegate.h55
-rw-r--r--chromium/extensions/shell/browser/shell_oauth2_token_service_unittest.cc38
-rw-r--r--chromium/extensions/shell/browser/shell_prefs.cc91
-rw-r--r--chromium/extensions/shell/browser/shell_prefs.h36
-rw-r--r--chromium/extensions/shell/browser/shell_prefs_unittest.cc80
-rw-r--r--chromium/extensions/shell/browser/shell_runtime_api_delegate.cc67
-rw-r--r--chromium/extensions/shell/browser/shell_runtime_api_delegate.h36
-rw-r--r--chromium/extensions/shell/browser/shell_screen.cc107
-rw-r--r--chromium/extensions/shell/browser/shell_screen.h62
-rw-r--r--chromium/extensions/shell/browser/shell_screen_unittest.cc40
-rw-r--r--chromium/extensions/shell/browser/shell_special_storage_policy.cc45
-rw-r--r--chromium/extensions/shell/browser/shell_special_storage_policy.h33
-rw-r--r--chromium/extensions/shell/browser/shell_speech_recognition_manager_delegate.cc132
-rw-r--r--chromium/extensions/shell/browser/shell_speech_recognition_manager_delegate.h61
-rw-r--r--chromium/extensions/shell/browser/shell_update_query_params_delegate.cc24
-rw-r--r--chromium/extensions/shell/browser/shell_update_query_params_delegate.h29
-rw-r--r--chromium/extensions/shell/browser/shell_url_request_context_getter.cc45
-rw-r--r--chromium/extensions/shell/browser/shell_url_request_context_getter.h59
-rw-r--r--chromium/extensions/shell/browser/shell_web_contents_modal_dialog_manager.cc18
-rw-r--r--chromium/extensions/shell/common/DEPS5
-rw-r--r--chromium/extensions/shell/common/api/BUILD.gn24
-rw-r--r--chromium/extensions/shell/common/api/_api_features.json22
-rw-r--r--chromium/extensions/shell/common/api/identity.idl40
-rw-r--r--chromium/extensions/shell/common/api/schemas.gni17
-rw-r--r--chromium/extensions/shell/common/shell_content_client.cc121
-rw-r--r--chromium/extensions/shell/common/shell_content_client.h40
-rw-r--r--chromium/extensions/shell/common/shell_content_client_unittest.cc31
-rw-r--r--chromium/extensions/shell/common/shell_extensions_client.cc215
-rw-r--r--chromium/extensions/shell/common/shell_extensions_client.h58
-rw-r--r--chromium/extensions/shell/common/switches.cc26
-rw-r--r--chromium/extensions/shell/common/switches.h22
-rw-r--r--chromium/extensions/shell/common/version.h.in13
-rw-r--r--chromium/extensions/shell/renderer/DEPS9
-rw-r--r--chromium/extensions/shell/renderer/shell_content_renderer_client.cc150
-rw-r--r--chromium/extensions/shell/renderer/shell_content_renderer_client.h71
-rw-r--r--chromium/extensions/shell/renderer/shell_extensions_renderer_client.cc27
-rw-r--r--chromium/extensions/shell/renderer/shell_extensions_renderer_client.h28
-rw-r--r--chromium/extensions/shell/utility/DEPS3
-rw-r--r--chromium/extensions/shell/utility/shell_content_utility_client.cc23
-rw-r--r--chromium/extensions/shell/utility/shell_content_utility_client.h28
-rw-r--r--chromium/extensions/strings/BUILD.gn72
-rw-r--r--chromium/extensions/strings/extensions_strings_am.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_ar.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_bg.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_bn.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_ca.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_cs.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_da.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_de.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_el.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_en-GB.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_es-419.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_es.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_et.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_fa.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_fi.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_fil.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_fr.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_gu.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_hi.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_hr.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_hu.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_id.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_it.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_iw.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_ja.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_kn.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_ko.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_lt.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_lv.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_ml.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_mr.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_ms.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_nl.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_no.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_pl.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_pt-BR.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_pt-PT.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_ro.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_ru.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_sk.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_sl.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_sr.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_sv.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_sw.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_ta.xtb57
-rw-r--r--chromium/extensions/strings/extensions_strings_te.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_th.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_tr.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_uk.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_vi.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_zh-CN.xtb56
-rw-r--r--chromium/extensions/strings/extensions_strings_zh-TW.xtb56
-rw-r--r--chromium/extensions/utility/BUILD.gn25
-rw-r--r--chromium/extensions/utility/DEPS6
-rw-r--r--chromium/extensions/utility/unpacker.cc298
-rw-r--r--chromium/extensions/utility/unpacker.h111
-rw-r--r--chromium/extensions/utility/unpacker_unittest.cc191
-rw-r--r--chromium/extensions/utility/utility_handler.cc115
-rw-r--r--chromium/extensions/utility/utility_handler.h47
-rw-r--r--chromium/mojo/edk/system/data_pipe_consumer_dispatcher.cc5
-rw-r--r--chromium/mojo/edk/system/data_pipe_producer_dispatcher.cc8
-rw-r--r--chromium/mojo/edk/system/message_pipe_dispatcher.cc53
-rw-r--r--chromium/mojo/edk/system/node_channel.cc8
-rw-r--r--chromium/net/quic/quic_flags.cc2
-rw-r--r--chromium/third_party/WebKit/Source/bindings/core/v8/ToV8.cpp13
-rw-r--r--chromium/third_party/WebKit/Source/bindings/core/v8/WindowProxy.cpp9
-rw-r--r--chromium/third_party/WebKit/Source/core/dom/Document.cpp1
-rw-r--r--chromium/third_party/WebKit/Source/core/dom/Document.h2
-rw-r--r--chromium/third_party/WebKit/Source/core/dom/MainThreadTaskRunner.cpp20
-rw-r--r--chromium/third_party/WebKit/Source/core/dom/MainThreadTaskRunner.h17
-rw-r--r--chromium/third_party/WebKit/Source/core/dom/MainThreadTaskRunnerTest.cpp12
-rw-r--r--chromium/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp10
-rw-r--r--chromium/third_party/WebKit/Source/core/frame/DOMWindow.cpp10
-rw-r--r--chromium/third_party/WebKit/Source/core/frame/FrameView.cpp21
-rw-r--r--chromium/third_party/WebKit/Source/core/frame/FrameView.h2
-rw-r--r--chromium/third_party/WebKit/Source/platform/fonts/Font.cpp4
-rw-r--r--chromium/third_party/WebKit/Source/platform/fonts/shaping/CachingWordShaper.cpp8
-rw-r--r--chromium/third_party/WebKit/Source/platform/heap/Heap.cpp27
-rw-r--r--chromium/third_party/WebKit/Source/platform/heap/PersistentNode.h31
-rw-r--r--chromium/third_party/libaddressinput/BUILD.gn227
-rw-r--r--chromium/third_party/libaddressinput/LICENSE202
-rw-r--r--chromium/third_party/libaddressinput/OWNERS2
-rw-r--r--chromium/third_party/libaddressinput/README.chromium21
-rw-r--r--chromium/third_party/libaddressinput/chromium/DEPS8
-rw-r--r--chromium/third_party/libaddressinput/chromium/addressinput_util.cc77
-rw-r--r--chromium/third_party/libaddressinput/chromium/addressinput_util.h42
-rw-r--r--chromium/third_party/libaddressinput/chromium/addressinput_util_unittest.cc41
-rw-r--r--chromium/third_party/libaddressinput/chromium/canonicalize_string.cc72
-rw-r--r--chromium/third_party/libaddressinput/chromium/chrome_address_validator.cc164
-rw-r--r--chromium/third_party/libaddressinput/chromium/chrome_address_validator.h203
-rw-r--r--chromium/third_party/libaddressinput/chromium/chrome_address_validator_unittest.cc892
-rw-r--r--chromium/third_party/libaddressinput/chromium/chrome_metadata_source.cc112
-rw-r--r--chromium/third_party/libaddressinput/chromium/chrome_metadata_source.h66
-rw-r--r--chromium/third_party/libaddressinput/chromium/chrome_metadata_source_unittest.cc100
-rw-r--r--chromium/third_party/libaddressinput/chromium/chrome_rule_test.cc60
-rw-r--r--chromium/third_party/libaddressinput/chromium/chrome_storage_impl.cc74
-rw-r--r--chromium/third_party/libaddressinput/chromium/chrome_storage_impl.h62
-rw-r--r--chromium/third_party/libaddressinput/chromium/chrome_storage_impl_unittest.cc35
-rw-r--r--chromium/third_party/libaddressinput/chromium/fallback_data_store.cc203
-rw-r--r--chromium/third_party/libaddressinput/chromium/fallback_data_store.h22
-rw-r--r--chromium/third_party/libaddressinput/chromium/fallback_data_store_unittest.cc50
-rw-r--r--chromium/third_party/libaddressinput/chromium/input_suggester.cc522
-rw-r--r--chromium/third_party/libaddressinput/chromium/input_suggester.h135
-rw-r--r--chromium/third_party/libaddressinput/chromium/json.cc109
-rw-r--r--chromium/third_party/libaddressinput/chromium/override/basictypes_override.h30
-rw-r--r--chromium/third_party/libaddressinput/chromium/storage_test_runner.cc88
-rw-r--r--chromium/third_party/libaddressinput/chromium/storage_test_runner.h47
-rw-r--r--chromium/third_party/libaddressinput/chromium/string_compare.cc69
-rw-r--r--chromium/third_party/libaddressinput/chromium/string_compare_unittest.cc27
-rwxr-xr-xchromium/third_party/libaddressinput/chromium/tools/require_fields.py51
-rwxr-xr-xchromium/third_party/libaddressinput/chromium/tools/update-strings.py36
-rw-r--r--chromium/third_party/libaddressinput/chromium/trie.cc83
-rw-r--r--chromium/third_party/libaddressinput/chromium/trie.h54
-rw-r--r--chromium/third_party/libaddressinput/chromium/trie_unittest.cc109
-rw-r--r--chromium/third_party/libaddressinput/src/AUTHORS9
-rw-r--r--chromium/third_party/libaddressinput/src/CONTRIBUTORS20
-rw-r--r--chromium/third_party/libaddressinput/src/LICENSE202
-rw-r--r--chromium/third_party/libaddressinput/src/README.md46
-rw-r--r--chromium/third_party/libaddressinput/src/android/README45
-rw-r--r--chromium/third_party/libaddressinput/src/android/build.gradle60
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/androidTest/AndroidManifest.xml26
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/main/AndroidManifest.xml11
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/main/res/layout/address_edittext.xml25
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/main/res/layout/address_layout.xml25
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/main/res/layout/address_spinner.xml24
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/main/res/layout/address_textview.xml26
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/main/res/values/address_strings.xml104
-rw-r--r--chromium/third_party/libaddressinput/src/build.gradle25
-rw-r--r--chromium/third_party/libaddressinput/src/common/README26
-rw-r--r--chromium/third_party/libaddressinput/src/common/build.gradle64
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/LICENSE.chromium27
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/README82
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_data.h96
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_field.h47
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_formatter.h51
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_input_helper.h67
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_metadata.h38
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_normalizer.h49
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_problem.h68
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_ui.h49
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_ui_component.h49
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_validator.h113
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/callback.h95
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/localization.h94
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/null_storage.h48
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/ondemand_supplier.h66
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/preload_supplier.h103
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/region_data.h77
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/region_data_builder.h80
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/source.h56
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/storage.h64
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/supplier.h54
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/util/basictypes.h213
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/util/scoped_ptr.h444
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/util/template_util.h111
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/res/messages.grd34
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/res/messages.grdp286
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/address_data.cc153
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/address_field.cc47
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/address_field_util.cc113
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/address_field_util.h44
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/address_formatter.cc230
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/address_input_helper.cc186
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/address_metadata.cc64
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/address_normalizer.cc89
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/address_problem.cc44
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/address_ui.cc147
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/address_validator.cc49
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/format_element.cc52
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/format_element.h70
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/grit.h36
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/language.cc102
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/language.h49
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/localization.cc190
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/lookup_key.cc153
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/lookup_key.h75
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/null_storage.cc41
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/ondemand_supplier.cc68
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/ondemand_supply_task.cc139
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/ondemand_supply_task.h71
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/post_box_matchers.cc132
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/post_box_matchers.h43
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/preload_supplier.cc373
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/region_data.cc50
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/region_data_builder.cc189
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/region_data_constants.cc1508
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/region_data_constants.h42
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/retriever.cc118
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/retriever.h68
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/rule.cc306
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/rule.h165
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/rule_retriever.cc80
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/rule_retriever.h59
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/util/cctype_tolower_equal.cc44
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/util/cctype_tolower_equal.h35
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/util/json.cc133
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/util/json.h68
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/util/lru_cache_using_std.h170
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/util/md5.cc301
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/util/md5.h74
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/util/re2ptr.h46
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/util/string_compare.cc102
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/util/string_compare.h52
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/util/string_split.cc37
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/util/string_split.h34
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/util/string_util.cc68
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/util/string_util.h28
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/validating_storage.cc97
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/validating_storage.h64
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/validating_util.cc145
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/validating_util.h60
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/validation_task.cc268
-rw-r--r--chromium/third_party/libaddressinput/src/cpp/src/validation_task.h101
-rw-r--r--chromium/third_party/libaddressinput/src/settings.gradle21
-rw-r--r--chromium/ui/base/BUILD.gn2
-rw-r--r--chromium/v8/include/v8-version.h2
-rw-r--r--chromium/v8/src/compiler/ia32/instruction-selector-ia32.cc20
-rw-r--r--chromium/v8/src/compiler/instruction-selector.cc6
-rw-r--r--chromium/v8/src/compiler/s390/code-generator-s390.cc46
-rw-r--r--chromium/v8/src/compiler/x64/instruction-selector-x64.cc20
-rw-r--r--chromium/v8/src/compiler/x87/instruction-selector-x87.cc20
-rw-r--r--chromium/v8/src/debug/debug-evaluate.cc3
-rw-r--r--chromium/v8/src/heap/mark-compact.cc20
-rw-r--r--chromium/v8/src/heap/mark-compact.h2
-rw-r--r--chromium/v8/src/heap/page-parallel-job.h12
-rw-r--r--chromium/v8/src/heap/spaces.cc4
-rw-r--r--chromium/v8/src/js/messages.js14
-rw-r--r--chromium/v8/src/parsing/parser.cc23
-rw-r--r--chromium/v8/src/s390/macro-assembler-s390.cc38
-rw-r--r--chromium/v8/src/s390/macro-assembler-s390.h3
2070 files changed, 283431 insertions, 364 deletions
diff --git a/chromium/DEPS b/chromium/DEPS
index a14cf1b16d4..76413e9bd6c 100644
--- a/chromium/DEPS
+++ b/chromium/DEPS
@@ -162,7 +162,7 @@ deps = {
'src/tools/swarming_client':
(Var("chromium_git")) + '/external/swarming.client.git@df6e95e7669883c8fe9ef956c69a544154701a49',
'src/v8':
- (Var("chromium_git")) + '/v8/v8.git@6d5aaf00b41a6477a61f6e03d89633959fbe074a'
+ (Var("chromium_git")) + '/v8/v8.git@7bd2476771628ccf089c5efa37e1bb612ee663dc'
}
deps_os = {
diff --git a/chromium/base/win/win_util.cc b/chromium/base/win/win_util.cc
index 4e18d40601e..75f1e53c141 100644
--- a/chromium/base/win/win_util.cc
+++ b/chromium/base/win/win_util.cc
@@ -504,9 +504,7 @@ bool IsTabletDevice(std::string* reason) {
bool slate_power_profile = (role == PlatformRoleSlate);
bool is_tablet = false;
- bool is_tablet_pc = false;
if (mobile_power_profile || slate_power_profile) {
- is_tablet_pc = !GetSystemMetrics(SM_TABLETPC);
is_tablet = !GetSystemMetrics(SM_CONVERTIBLESLATEMODE);
if (!is_tablet) {
if (reason) {
@@ -524,7 +522,7 @@ bool IsTabletDevice(std::string* reason) {
if (reason)
*reason += "Device role is not mobile or slate.\n";
}
- return is_tablet && is_tablet_pc;
+ return is_tablet;
}
bool DisplayVirtualKeyboard() {
diff --git a/chromium/build/util/LASTCHANGE b/chromium/build/util/LASTCHANGE
index ee60410168a..1e0229f8bcf 100644
--- a/chromium/build/util/LASTCHANGE
+++ b/chromium/build/util/LASTCHANGE
@@ -1 +1 @@
-LASTCHANGE=ba34b71c19bbb4dbdffeaa90c17920732309855f
+LASTCHANGE=01eb5e4de97f17eea3fc0b5f0e2a067c76dc92a7
diff --git a/chromium/build/util/LASTCHANGE.blink b/chromium/build/util/LASTCHANGE.blink
index ee60410168a..1e0229f8bcf 100644
--- a/chromium/build/util/LASTCHANGE.blink
+++ b/chromium/build/util/LASTCHANGE.blink
@@ -1 +1 @@
-LASTCHANGE=ba34b71c19bbb4dbdffeaa90c17920732309855f
+LASTCHANGE=01eb5e4de97f17eea3fc0b5f0e2a067c76dc92a7
diff --git a/chromium/cc/trees/layer_tree_impl.cc b/chromium/cc/trees/layer_tree_impl.cc
index 0fc15c4b0f0..aeca3f8de5f 100644
--- a/chromium/cc/trees/layer_tree_impl.cc
+++ b/chromium/cc/trees/layer_tree_impl.cc
@@ -1657,17 +1657,9 @@ static bool PointIsClippedByAncestorClipNode(
gfx::Rect combined_clip_in_target_space =
gfx::ToEnclosingRect(clip_node->data.combined_clip_in_target_space);
- const LayerImpl* target_layer =
- layer->layer_tree_impl()->LayerById(transform_node->owner_id);
- DCHECK(transform_node->id == 0 || target_layer->render_surface());
- gfx::Transform surface_screen_space_transform =
- transform_node->id == 0
- ? gfx::Transform()
- : SurfaceScreenSpaceTransform(target_layer, transform_tree);
- if (!PointHitsRect(screen_space_point, surface_screen_space_transform,
- combined_clip_in_target_space, NULL)) {
+ if (!PointHitsRect(screen_space_point, transform_node->data.to_screen,
+ combined_clip_in_target_space, NULL))
return true;
- }
}
const LayerImpl* clip_node_owner =
layer->layer_tree_impl()->LayerById(clip_node->owner_id);
@@ -1700,9 +1692,8 @@ static bool PointHitsLayer(const LayerImpl* layer,
const ClipTree& clip_tree) {
gfx::Rect content_rect(layer->bounds());
if (!PointHitsRect(screen_space_point, layer->ScreenSpaceTransform(),
- content_rect, distance_to_intersection)) {
+ content_rect, distance_to_intersection))
return false;
- }
// At this point, we think the point does hit the layer, but we need to walk
// up the parents to ensure that the layer was not clipped in such a way
diff --git a/chromium/cc/trees/layer_tree_impl_unittest.cc b/chromium/cc/trees/layer_tree_impl_unittest.cc
index 984ad4766e2..8781d2fc702 100644
--- a/chromium/cc/trees/layer_tree_impl_unittest.cc
+++ b/chromium/cc/trees/layer_tree_impl_unittest.cc
@@ -1878,94 +1878,6 @@ TEST_F(LayerTreeImplTest, HitCheckingTouchHandlerRegionsForSimpleClippedLayer) {
EXPECT_EQ(456, result_layer->id());
}
-TEST_F(LayerTreeImplTest,
- HitCheckingTouchHandlerRegionsForClippedLayerWithDeviceScale) {
- // The layer's device_scale_factor and page_scale_factor should scale the
- // content rect and we should be able to hit the touch handler region by
- // scaling the points accordingly.
- std::unique_ptr<LayerImpl> root =
- LayerImpl::Create(host_impl().active_tree(), 1);
-
- gfx::Transform identity_matrix;
- gfx::Point3F transform_origin;
- // Set the bounds of the root layer big enough to fit the child when scaled.
- SetLayerPropertiesForTesting(root.get(), identity_matrix, transform_origin,
- gfx::PointF(), gfx::Size(100, 100), true, false,
- true);
- std::unique_ptr<LayerImpl> surface =
- LayerImpl::Create(host_impl().active_tree(), 2);
- SetLayerPropertiesForTesting(surface.get(), identity_matrix, transform_origin,
- gfx::PointF(), gfx::Size(100, 100), true, false,
- true);
- {
- std::unique_ptr<LayerImpl> clipping_layer =
- LayerImpl::Create(host_impl().active_tree(), 123);
- // This layer is positioned, and hit testing should correctly know where the
- // layer is located.
- gfx::PointF position(25.f, 20.f);
- gfx::Size bounds(50, 50);
- SetLayerPropertiesForTesting(clipping_layer.get(), identity_matrix,
- transform_origin, position, bounds, true,
- false, false);
- clipping_layer->SetMasksToBounds(true);
-
- std::unique_ptr<LayerImpl> child =
- LayerImpl::Create(host_impl().active_tree(), 456);
- Region touch_handler_region(gfx::Rect(0, 0, 300, 300));
- position = gfx::PointF(-50.f, -50.f);
- bounds = gfx::Size(300, 300);
- SetLayerPropertiesForTesting(child.get(), identity_matrix, transform_origin,
- position, bounds, true, false, false);
- child->SetDrawsContent(true);
- child->SetTouchEventHandlerRegion(touch_handler_region);
- clipping_layer->AddChild(std::move(child));
- surface->AddChild(std::move(clipping_layer));
- root->AddChild(std::move(surface));
- }
-
- float device_scale_factor = 3.f;
- float page_scale_factor = 1.f;
- float max_page_scale_factor = 1.f;
- gfx::Size scaled_bounds_for_root = gfx::ScaleToCeiledSize(
- root->bounds(), device_scale_factor * page_scale_factor);
- host_impl().SetViewportSize(scaled_bounds_for_root);
-
- host_impl().active_tree()->SetDeviceScaleFactor(device_scale_factor);
- host_impl().active_tree()->SetRootLayer(std::move(root));
- host_impl().active_tree()->SetViewportLayersFromIds(Layer::INVALID_ID, 1, 1,
- Layer::INVALID_ID);
- host_impl().active_tree()->BuildPropertyTreesForTesting();
- host_impl().active_tree()->PushPageScaleFromMainThread(
- page_scale_factor, page_scale_factor, max_page_scale_factor);
- host_impl().active_tree()->SetPageScaleOnActiveTree(page_scale_factor);
- host_impl().UpdateNumChildrenAndDrawPropertiesForActiveTree();
-
- // Sanity check the scenario we just created.
- ASSERT_EQ(2u, RenderSurfaceLayerList().size());
-
- // Hit checking for a point outside the layer should return a null pointer.
- // Despite the child layer being very large, it should be clipped to the root
- // layer's bounds.
- gfx::PointF test_point(24.f, 24.f);
- test_point =
- gfx::ScalePoint(test_point, device_scale_factor * page_scale_factor);
- LayerImpl* result_layer =
- host_impl().active_tree()->FindLayerThatIsHitByPointInTouchHandlerRegion(
- test_point);
- EXPECT_FALSE(result_layer);
-
- // Hit checking for a point inside the touch event handler region should
- // return the child layer.
- test_point = gfx::PointF(25.f, 25.f);
- test_point =
- gfx::ScalePoint(test_point, device_scale_factor * page_scale_factor);
- result_layer =
- host_impl().active_tree()->FindLayerThatIsHitByPointInTouchHandlerRegion(
- test_point);
- ASSERT_TRUE(result_layer);
- EXPECT_EQ(456, result_layer->id());
-}
-
TEST_F(LayerTreeImplTest, HitCheckingTouchHandlerOverlappingRegions) {
gfx::Transform identity_matrix;
gfx::Point3F transform_origin;
diff --git a/chromium/chrome/VERSION b/chromium/chrome/VERSION
index bac404734cf..4dadb8fc257 100644
--- a/chromium/chrome/VERSION
+++ b/chromium/chrome/VERSION
@@ -1,4 +1,4 @@
MAJOR=51
MINOR=0
BUILD=2704
-PATCH=79
+PATCH=106
diff --git a/chromium/components/google/DEPS b/chromium/components/google/DEPS
new file mode 100644
index 00000000000..21c1e50361a
--- /dev/null
+++ b/chromium/components/google/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+components/prefs",
+ "+components/url_formatter",
+ "+net",
+]
diff --git a/chromium/components/google/OWNERS b/chromium/components/google/OWNERS
new file mode 100644
index 00000000000..2c1c5e279f9
--- /dev/null
+++ b/chromium/components/google/OWNERS
@@ -0,0 +1,2 @@
+isherman@chromium.org
+pkasting@chromium.org \ No newline at end of file
diff --git a/chromium/components/google/core/browser/BUILD.gn b/chromium/components/google/core/browser/BUILD.gn
new file mode 100644
index 00000000000..ad60061652e
--- /dev/null
+++ b/chromium/components/google/core/browser/BUILD.gn
@@ -0,0 +1,47 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+static_library("browser") {
+ sources = [
+ "google_pref_names.cc",
+ "google_pref_names.h",
+ "google_switches.cc",
+ "google_switches.h",
+ "google_url_tracker.cc",
+ "google_url_tracker.h",
+ "google_url_tracker_client.cc",
+ "google_url_tracker_client.h",
+ "google_util.cc",
+ "google_util.h",
+ ]
+
+ deps = [
+ "//base",
+ "//components/data_use_measurement/core",
+ "//components/keyed_service/core",
+ "//components/pref_registry",
+ "//components/prefs",
+ "//components/strings",
+ "//components/url_formatter",
+ "//net",
+ "//url",
+ ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+
+ sources = [
+ "google_url_tracker_unittest.cc",
+ "google_util_unittest.cc",
+ ]
+
+ deps = [
+ ":browser",
+ "//base",
+ "//components/prefs:test_support",
+ "//net:test_support",
+ "//testing/gtest",
+ ]
+}
diff --git a/chromium/components/google/core/browser/DEPS b/chromium/components/google/core/browser/DEPS
new file mode 100644
index 00000000000..afcec3dc7e7
--- /dev/null
+++ b/chromium/components/google/core/browser/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+components/data_use_measurement/core",
+ "+components/keyed_service/core",
+ "+components/pref_registry",
+]
diff --git a/chromium/components/google/core/browser/google_pref_names.cc b/chromium/components/google/core/browser/google_pref_names.cc
new file mode 100644
index 00000000000..92787d0c6d6
--- /dev/null
+++ b/chromium/components/google/core/browser/google_pref_names.cc
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/google/core/browser/google_pref_names.h"
+
+namespace prefs {
+
+// String containing the last known Google URL. We re-detect this on startup in
+// most cases, and use it to send traffic to the correct Google host or with the
+// correct Google domain/country code for whatever location the user is in.
+const char kLastKnownGoogleURL[] = "browser.last_known_google_url";
+
+// String containing the last prompted Google URL.
+const char kLastPromptedGoogleURL[] = "browser.last_prompted_google_url";
+
+} // namespace prefs
diff --git a/chromium/components/google/core/browser/google_pref_names.h b/chromium/components/google/core/browser/google_pref_names.h
new file mode 100644
index 00000000000..2c58dd29187
--- /dev/null
+++ b/chromium/components/google/core/browser/google_pref_names.h
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_GOOGLE_CORE_BROWSER_GOOGLE_PREF_NAMES_H_
+#define COMPONENTS_GOOGLE_CORE_BROWSER_GOOGLE_PREF_NAMES_H_
+
+namespace prefs {
+
+// Alphabetical list of preference names specific to the Google
+// component. Keep alphabetized, and document each in the .cc file.
+extern const char kLastKnownGoogleURL[];
+extern const char kLastPromptedGoogleURL[];
+
+} // namespace prefs
+
+#endif // COMPONENTS_GOOGLE_CORE_BROWSER_GOOGLE_PREF_NAMES_H_
diff --git a/chromium/components/google/core/browser/google_switches.cc b/chromium/components/google/core/browser/google_switches.cc
new file mode 100644
index 00000000000..2daf83673a5
--- /dev/null
+++ b/chromium/components/google/core/browser/google_switches.cc
@@ -0,0 +1,12 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/google/core/browser/google_switches.h"
+
+namespace switches {
+
+// Specifies an alternate URL to use for speaking to Google. Useful for testing.
+const char kGoogleBaseURL[] = "google-base-url";
+
+} // namespace switches
diff --git a/chromium/components/google/core/browser/google_switches.h b/chromium/components/google/core/browser/google_switches.h
new file mode 100644
index 00000000000..eadc91a59c7
--- /dev/null
+++ b/chromium/components/google/core/browser/google_switches.h
@@ -0,0 +1,16 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_GOOGLE_CORE_BROWSER_GOOGLE_SWITCHES_H_
+#define COMPONENTS_GOOGLE_CORE_BROWSER_GOOGLE_SWITCHES_H_
+
+namespace switches {
+
+// All switches in alphabetical order. The switches should be documented
+// alongside the definition of their values in the .cc file.
+extern const char kGoogleBaseURL[];
+
+} // namespace switches
+
+#endif // COMPONENTS_GOOGLE_CORE_BROWSER_GOOGLE_SWITCHES_H_
diff --git a/chromium/components/google/core/browser/google_url_tracker.cc b/chromium/components/google/core/browser/google_url_tracker.cc
new file mode 100644
index 00000000000..9a21ca4934f
--- /dev/null
+++ b/chromium/components/google/core/browser/google_url_tracker.cc
@@ -0,0 +1,193 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/google/core/browser/google_url_tracker.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_util.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/data_use_measurement/core/data_use_user_data.h"
+#include "components/google/core/browser/google_pref_names.h"
+#include "components/google/core/browser/google_switches.h"
+#include "components/google/core/browser/google_util.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "net/base/load_flags.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_status.h"
+
+
+const char GoogleURLTracker::kDefaultGoogleHomepage[] =
+ "https://www.google.com/";
+const char GoogleURLTracker::kSearchDomainCheckURL[] =
+ "https://www.google.com/searchdomaincheck?format=domain&type=chrome";
+
+GoogleURLTracker::GoogleURLTracker(scoped_ptr<GoogleURLTrackerClient> client,
+ Mode mode)
+ : client_(std::move(client)),
+ google_url_(mode == UNIT_TEST_MODE ? kDefaultGoogleHomepage
+ : client_->GetPrefs()->GetString(
+ prefs::kLastKnownGoogleURL)),
+ fetcher_id_(0),
+ in_startup_sleep_(true),
+ already_fetched_(false),
+ need_to_fetch_(false),
+ weak_ptr_factory_(this) {
+ net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
+ client_->set_google_url_tracker(this);
+
+ // Because this function can be called during startup, when kicking off a URL
+ // fetch can eat up 20 ms of time, we delay five seconds, which is hopefully
+ // long enough to be after startup, but still get results back quickly.
+ // Ideally, instead of this timer, we'd do something like "check if the
+ // browser is starting up, and if so, come back later", but there is currently
+ // no function to do this.
+ //
+ // In UNIT_TEST_MODE, where we want to explicitly control when the tracker
+ // "wakes up", we do nothing at all.
+ if (mode == NORMAL_MODE) {
+ static const int kStartFetchDelayMS = 5000;
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, base::Bind(&GoogleURLTracker::FinishSleep,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(kStartFetchDelayMS));
+ }
+}
+
+GoogleURLTracker::~GoogleURLTracker() {
+}
+
+// static
+void GoogleURLTracker::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) {
+ registry->RegisterStringPref(prefs::kLastKnownGoogleURL,
+ GoogleURLTracker::kDefaultGoogleHomepage);
+ registry->RegisterStringPref(prefs::kLastPromptedGoogleURL, std::string());
+}
+
+void GoogleURLTracker::RequestServerCheck(bool force) {
+ // If this instance already has a fetcher, SetNeedToFetch() is unnecessary,
+ // and changing |already_fetched_| is wrong.
+ if (!fetcher_) {
+ if (force)
+ already_fetched_ = false;
+ SetNeedToFetch();
+ }
+}
+
+scoped_ptr<GoogleURLTracker::Subscription> GoogleURLTracker::RegisterCallback(
+ const OnGoogleURLUpdatedCallback& cb) {
+ return callback_list_.Add(cb);
+}
+
+void GoogleURLTracker::OnURLFetchComplete(const net::URLFetcher* source) {
+ // Delete the fetcher on this function's exit.
+ scoped_ptr<net::URLFetcher> clean_up_fetcher(fetcher_.release());
+
+ // Don't update the URL if the request didn't succeed.
+ if (!source->GetStatus().is_success() || (source->GetResponseCode() != 200)) {
+ already_fetched_ = false;
+ return;
+ }
+
+ // See if the response data was valid. It should be ".google.<TLD>".
+ std::string url_str;
+ source->GetResponseAsString(&url_str);
+ base::TrimWhitespaceASCII(url_str, base::TRIM_ALL, &url_str);
+ if (!base::StartsWith(url_str, ".google.",
+ base::CompareCase::INSENSITIVE_ASCII))
+ return;
+ GURL url("https://www" + url_str);
+ if (!url.is_valid() || (url.path().length() > 1) || url.has_query() ||
+ url.has_ref() ||
+ !google_util::IsGoogleDomainUrl(url, google_util::DISALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS))
+ return;
+
+ if (url != google_url_) {
+ google_url_ = url;
+ client_->GetPrefs()->SetString(prefs::kLastKnownGoogleURL,
+ google_url_.spec());
+ callback_list_.Notify();
+ }
+}
+
+void GoogleURLTracker::OnNetworkChanged(
+ net::NetworkChangeNotifier::ConnectionType type) {
+ // Ignore destructive signals.
+ if (type == net::NetworkChangeNotifier::CONNECTION_NONE)
+ return;
+ already_fetched_ = false;
+ StartFetchIfDesirable();
+}
+
+void GoogleURLTracker::Shutdown() {
+ client_.reset();
+ fetcher_.reset();
+ weak_ptr_factory_.InvalidateWeakPtrs();
+ net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
+}
+
+void GoogleURLTracker::SetNeedToFetch() {
+ need_to_fetch_ = true;
+ StartFetchIfDesirable();
+}
+
+void GoogleURLTracker::FinishSleep() {
+ in_startup_sleep_ = false;
+ StartFetchIfDesirable();
+}
+
+void GoogleURLTracker::StartFetchIfDesirable() {
+ // Bail if a fetch isn't appropriate right now. This function will be called
+ // again each time one of the preconditions changes, so we'll fetch
+ // immediately once all of them are met.
+ //
+ // See comments in header on the class, on RequestServerCheck(), and on the
+ // various members here for more detail on exactly what the conditions are.
+ if (in_startup_sleep_ || already_fetched_ || !need_to_fetch_)
+ return;
+
+ // Some switches should disable the Google URL tracker entirely. If we can't
+ // do background networking, we can't do the necessary fetch, and if the user
+ // specified a Google base URL manually, we shouldn't bother to look up any
+ // alternatives or offer to switch to them.
+ if (!client_->IsBackgroundNetworkingEnabled() ||
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kGoogleBaseURL))
+ return;
+
+ already_fetched_ = true;
+ fetcher_ = net::URLFetcher::Create(fetcher_id_, GURL(kSearchDomainCheckURL),
+ net::URLFetcher::GET, this);
+ data_use_measurement::DataUseUserData::AttachToFetcher(
+ fetcher_.get(),
+ data_use_measurement::DataUseUserData::GOOGLE_URL_TRACKER);
+ ++fetcher_id_;
+ // We don't want this fetch to set new entries in the cache or cookies, lest
+ // we alarm the user.
+ fetcher_->SetLoadFlags(net::LOAD_DISABLE_CACHE |
+ net::LOAD_DO_NOT_SAVE_COOKIES);
+ fetcher_->SetRequestContext(client_->GetRequestContext());
+
+ // Configure to retry at most kMaxRetries times for 5xx errors.
+ static const int kMaxRetries = 5;
+ fetcher_->SetMaxRetriesOn5xx(kMaxRetries);
+
+ // Also retry kMaxRetries times on network change errors. A network change can
+ // propagate through Chrome in various stages, so it's possible for this code
+ // to be reached via OnNetworkChanged(), and then have the fetch we kick off
+ // be canceled due to e.g. the DNS server changing at a later time. In general
+ // it's not possible to ensure that by the time we reach here any requests we
+ // start won't be canceled in this fashion, so retrying is the best we can do.
+ fetcher_->SetAutomaticallyRetryOnNetworkChanges(kMaxRetries);
+
+ fetcher_->Start();
+}
diff --git a/chromium/components/google/core/browser/google_url_tracker.h b/chromium/components/google/core/browser/google_url_tracker.h
new file mode 100644
index 00000000000..c79754aa8a6
--- /dev/null
+++ b/chromium/components/google/core/browser/google_url_tracker.h
@@ -0,0 +1,132 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_GOOGLE_CORE_BROWSER_GOOGLE_URL_TRACKER_H_
+#define COMPONENTS_GOOGLE_CORE_BROWSER_GOOGLE_URL_TRACKER_H_
+
+#include "base/callback_forward.h"
+#include "base/callback_list.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "components/google/core/browser/google_url_tracker_client.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "net/base/network_change_notifier.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "url/gurl.h"
+
+class PrefService;
+
+namespace infobars {
+class InfoBar;
+}
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+// This object is responsible for checking the Google URL once per network
+// change. The current value is saved to prefs.
+//
+// Most consumers should only call google_url(). Consumers who need to be
+// notified when things change should register a callback that provides the
+// original and updated values via RegisterCallback().
+//
+// To protect users' privacy and reduce server load, no updates will be
+// performed (ever) unless at least one consumer registers interest by calling
+// RequestServerCheck().
+class GoogleURLTracker
+ : public net::URLFetcherDelegate,
+ public net::NetworkChangeNotifier::NetworkChangeObserver,
+ public KeyedService {
+ public:
+ // Callback that is called when the Google URL is updated. The arguments are
+ // the old and new URLs.
+ typedef base::Callback<void()> OnGoogleURLUpdatedCallback;
+ typedef base::CallbackList<void()> CallbackList;
+ typedef CallbackList::Subscription Subscription;
+
+ // The constructor does different things depending on which of these values
+ // you pass it. Hopefully these are self-explanatory.
+ enum Mode {
+ NORMAL_MODE,
+ UNIT_TEST_MODE,
+ };
+
+ static const char kDefaultGoogleHomepage[];
+
+ // Only the GoogleURLTrackerFactory and tests should call this.
+ GoogleURLTracker(scoped_ptr<GoogleURLTrackerClient> client, Mode mode);
+
+ ~GoogleURLTracker() override;
+
+ // Register user preferences for GoogleURLTracker.
+ static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+
+ // Returns the current Google homepage URL.
+ const GURL& google_url() const { return google_url_; }
+
+ // Requests that the tracker perform a server check to update the Google URL
+ // as necessary. If |force| is false, this will happen at most once per
+ // network change, not sooner than five seconds after startup (checks
+ // requested before that time will occur then; checks requested afterwards
+ // will occur immediately, if no other checks have been made during this run).
+ // If |force| is true, and the tracker has already performed any requested
+ // check, it will check again.
+ void RequestServerCheck(bool force);
+
+ scoped_ptr<Subscription> RegisterCallback(
+ const OnGoogleURLUpdatedCallback& cb);
+
+ private:
+ friend class GoogleURLTrackerTest;
+ friend class SyncTest;
+
+ static const char kSearchDomainCheckURL[];
+
+ // net::URLFetcherDelegate:
+ void OnURLFetchComplete(const net::URLFetcher* source) override;
+
+ // NetworkChangeNotifier::IPAddressObserver:
+ void OnNetworkChanged(
+ net::NetworkChangeNotifier::ConnectionType type) override;
+
+ // KeyedService:
+ void Shutdown() override;
+
+ // Sets |need_to_fetch_| and attempts to start a fetch.
+ void SetNeedToFetch();
+
+ // Called when the five second startup sleep has finished. Runs any pending
+ // fetch.
+ void FinishSleep();
+
+ // Starts the fetch of the up-to-date Google URL if we actually want to fetch
+ // it and can currently do so.
+ void StartFetchIfDesirable();
+
+ CallbackList callback_list_;
+
+ scoped_ptr<GoogleURLTrackerClient> client_;
+
+ GURL google_url_;
+ scoped_ptr<net::URLFetcher> fetcher_;
+ int fetcher_id_;
+ bool in_startup_sleep_; // True if we're in the five-second "no fetching"
+ // period that begins at browser start.
+ bool already_fetched_; // True if we've already fetched a URL once this run;
+ // we won't fetch again until after a restart.
+ bool need_to_fetch_; // True if a consumer actually wants us to fetch an
+ // updated URL. If this is never set, we won't
+ // bother to fetch anything.
+ // Consumers should register a callback via
+ // RegisterCallback().
+ base::WeakPtrFactory<GoogleURLTracker> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(GoogleURLTracker);
+};
+
+#endif // COMPONENTS_GOOGLE_CORE_BROWSER_GOOGLE_URL_TRACKER_H_
diff --git a/chromium/components/google/core/browser/google_url_tracker_client.cc b/chromium/components/google/core/browser/google_url_tracker_client.cc
new file mode 100644
index 00000000000..c3c6c23f297
--- /dev/null
+++ b/chromium/components/google/core/browser/google_url_tracker_client.cc
@@ -0,0 +1,11 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/google/core/browser/google_url_tracker_client.h"
+
+GoogleURLTrackerClient::GoogleURLTrackerClient() : google_url_tracker_(NULL) {
+}
+
+GoogleURLTrackerClient::~GoogleURLTrackerClient() {
+}
diff --git a/chromium/components/google/core/browser/google_url_tracker_client.h b/chromium/components/google/core/browser/google_url_tracker_client.h
new file mode 100644
index 00000000000..58f51a7d74b
--- /dev/null
+++ b/chromium/components/google/core/browser/google_url_tracker_client.h
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_GOOGLE_GOOGLE_URL_TRACKER_CLIENT_H_
+#define COMPONENTS_GOOGLE_GOOGLE_URL_TRACKER_CLIENT_H_
+
+#include "base/macros.h"
+#include "url/gurl.h"
+
+class GoogleURLTracker;
+class PrefService;
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+// Interface by which GoogleURLTracker communicates with its embedder.
+class GoogleURLTrackerClient {
+ public:
+ GoogleURLTrackerClient();
+ virtual ~GoogleURLTrackerClient();
+
+ // Sets the GoogleURLTracker that is associated with this client.
+ void set_google_url_tracker(GoogleURLTracker* google_url_tracker) {
+ google_url_tracker_ = google_url_tracker;
+ }
+
+ // Returns whether background networking is enabled.
+ virtual bool IsBackgroundNetworkingEnabled() = 0;
+
+ // Returns the PrefService that the GoogleURLTracker should use.
+ virtual PrefService* GetPrefs() = 0;
+
+ // Returns the URL request context information that the GoogleURLTracker
+ // should use.
+ virtual net::URLRequestContextGetter* GetRequestContext() = 0;
+
+ protected:
+ GoogleURLTracker* google_url_tracker() { return google_url_tracker_; }
+
+ private:
+ GoogleURLTracker* google_url_tracker_;
+
+ DISALLOW_COPY_AND_ASSIGN(GoogleURLTrackerClient);
+};
+
+#endif // COMPONENTS_GOOGLE_GOOGLE_URL_TRACKER_CLIENT_H_
diff --git a/chromium/components/google/core/browser/google_url_tracker_unittest.cc b/chromium/components/google/core/browser/google_url_tracker_unittest.cc
new file mode 100644
index 00000000000..03b34e242d2
--- /dev/null
+++ b/chromium/components/google/core/browser/google_url_tracker_unittest.cc
@@ -0,0 +1,353 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/google/core/browser/google_url_tracker.h"
+
+#include <string>
+#include <utility>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/google/core/browser/google_pref_names.h"
+#include "components/google/core/browser/google_url_tracker_client.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/testing_pref_service.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// TestCallbackListener ---------------------------------------------------
+
+class TestCallbackListener {
+ public:
+ TestCallbackListener();
+ virtual ~TestCallbackListener();
+
+ bool HasRegisteredCallback();
+ void RegisterCallback(GoogleURLTracker* google_url_tracker);
+
+ bool notified() const { return notified_; }
+ void clear_notified() { notified_ = false; }
+
+ private:
+ void OnGoogleURLUpdated();
+
+ bool notified_;
+ scoped_ptr<GoogleURLTracker::Subscription> google_url_updated_subscription_;
+};
+
+TestCallbackListener::TestCallbackListener() : notified_(false) {
+}
+
+TestCallbackListener::~TestCallbackListener() {
+}
+
+void TestCallbackListener::OnGoogleURLUpdated() {
+ notified_ = true;
+}
+
+bool TestCallbackListener::HasRegisteredCallback() {
+ return google_url_updated_subscription_.get();
+}
+
+void TestCallbackListener::RegisterCallback(
+ GoogleURLTracker* google_url_tracker) {
+ google_url_updated_subscription_ =
+ google_url_tracker->RegisterCallback(base::Bind(
+ &TestCallbackListener::OnGoogleURLUpdated, base::Unretained(this)));
+}
+
+
+// TestGoogleURLTrackerClient -------------------------------------------------
+
+class TestGoogleURLTrackerClient : public GoogleURLTrackerClient {
+ public:
+ explicit TestGoogleURLTrackerClient(PrefService* prefs_);
+ ~TestGoogleURLTrackerClient() override;
+
+ bool IsBackgroundNetworkingEnabled() override;
+ PrefService* GetPrefs() override;
+ net::URLRequestContextGetter* GetRequestContext() override;
+
+ private:
+ PrefService* prefs_;
+ scoped_refptr<net::TestURLRequestContextGetter> request_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestGoogleURLTrackerClient);
+};
+
+TestGoogleURLTrackerClient::TestGoogleURLTrackerClient(PrefService* prefs)
+ : prefs_(prefs),
+ request_context_(new net::TestURLRequestContextGetter(
+ base::ThreadTaskRunnerHandle::Get())) {
+}
+
+TestGoogleURLTrackerClient::~TestGoogleURLTrackerClient() {
+}
+
+bool TestGoogleURLTrackerClient::IsBackgroundNetworkingEnabled() {
+ return true;
+}
+
+PrefService* TestGoogleURLTrackerClient::GetPrefs() {
+ return prefs_;
+}
+
+net::URLRequestContextGetter* TestGoogleURLTrackerClient::GetRequestContext() {
+ return request_context_.get();
+}
+
+
+} // namespace
+
+// GoogleURLTrackerTest -------------------------------------------------------
+
+class GoogleURLTrackerTest : public testing::Test {
+ protected:
+ GoogleURLTrackerTest();
+ ~GoogleURLTrackerTest() override;
+
+ // testing::Test
+ void SetUp() override;
+ void TearDown() override;
+
+ net::TestURLFetcher* GetFetcher();
+ void MockSearchDomainCheckResponse(const std::string& domain);
+ void RequestServerCheck();
+ void FinishSleep();
+ void NotifyNetworkChanged();
+ void set_google_url(const GURL& url) {
+ google_url_tracker_->google_url_ = url;
+ }
+ GURL google_url() const { return google_url_tracker_->google_url(); }
+ bool listener_notified() const { return listener_.notified(); }
+ void clear_listener_notified() { listener_.clear_notified(); }
+
+ private:
+ base::MessageLoop message_loop_;
+ TestingPrefServiceSimple prefs_;
+
+ // Creating this allows us to call
+ // net::NetworkChangeNotifier::NotifyObserversOfNetworkChangeForTests().
+ scoped_ptr<net::NetworkChangeNotifier> network_change_notifier_;
+ net::TestURLFetcherFactory fetcher_factory_;
+ GoogleURLTrackerClient* client_;
+ scoped_ptr<GoogleURLTracker> google_url_tracker_;
+ TestCallbackListener listener_;
+};
+
+GoogleURLTrackerTest::GoogleURLTrackerTest() {
+ prefs_.registry()->RegisterStringPref(
+ prefs::kLastKnownGoogleURL,
+ GoogleURLTracker::kDefaultGoogleHomepage);
+}
+
+GoogleURLTrackerTest::~GoogleURLTrackerTest() {
+}
+
+void GoogleURLTrackerTest::SetUp() {
+ network_change_notifier_.reset(net::NetworkChangeNotifier::CreateMock());
+ // Ownership is passed to google_url_tracker_, but a weak pointer is kept;
+ // this is safe since GoogleURLTracker keeps the client for its lifetime.
+ client_ = new TestGoogleURLTrackerClient(&prefs_);
+ scoped_ptr<GoogleURLTrackerClient> client(client_);
+ google_url_tracker_.reset(new GoogleURLTracker(
+ std::move(client), GoogleURLTracker::UNIT_TEST_MODE));
+}
+
+void GoogleURLTrackerTest::TearDown() {
+ google_url_tracker_->Shutdown();
+}
+
+net::TestURLFetcher* GoogleURLTrackerTest::GetFetcher() {
+ // This will return the last fetcher created. If no fetchers have been
+ // created, we'll pass GetFetcherByID() "-1", and it will return NULL.
+ return fetcher_factory_.GetFetcherByID(google_url_tracker_->fetcher_id_ - 1);
+}
+
+void GoogleURLTrackerTest::MockSearchDomainCheckResponse(
+ const std::string& domain) {
+ net::TestURLFetcher* fetcher = GetFetcher();
+ if (!fetcher)
+ return;
+ fetcher_factory_.RemoveFetcherFromMap(fetcher->id());
+ fetcher->set_url(GURL(GoogleURLTracker::kSearchDomainCheckURL));
+ fetcher->set_response_code(200);
+ fetcher->SetResponseString(domain);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+ // At this point, |fetcher| is deleted.
+}
+
+void GoogleURLTrackerTest::RequestServerCheck() {
+ if (!listener_.HasRegisteredCallback())
+ listener_.RegisterCallback(google_url_tracker_.get());
+ google_url_tracker_->SetNeedToFetch();
+}
+
+void GoogleURLTrackerTest::FinishSleep() {
+ google_url_tracker_->FinishSleep();
+}
+
+void GoogleURLTrackerTest::NotifyNetworkChanged() {
+ net::NetworkChangeNotifier::NotifyObserversOfNetworkChangeForTests(
+ net::NetworkChangeNotifier::CONNECTION_UNKNOWN);
+ // For thread safety, the NCN queues tasks to do the actual notifications, so
+ // we need to spin the message loop so the tracker will actually be notified.
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Tests ----------------------------------------------------------------------
+
+TEST_F(GoogleURLTrackerTest, DontFetchWhenNoOneRequestsCheck) {
+ EXPECT_EQ(GURL(GoogleURLTracker::kDefaultGoogleHomepage), google_url());
+ FinishSleep();
+ // No one called RequestServerCheck() so nothing should have happened.
+ EXPECT_FALSE(GetFetcher());
+ MockSearchDomainCheckResponse(".google.co.uk");
+ EXPECT_EQ(GURL(GoogleURLTracker::kDefaultGoogleHomepage), google_url());
+ EXPECT_FALSE(listener_notified());
+}
+
+TEST_F(GoogleURLTrackerTest, Update) {
+ RequestServerCheck();
+ EXPECT_FALSE(GetFetcher());
+ EXPECT_EQ(GURL(GoogleURLTracker::kDefaultGoogleHomepage), google_url());
+ EXPECT_FALSE(listener_notified());
+
+ FinishSleep();
+ MockSearchDomainCheckResponse(".google.co.uk");
+ EXPECT_EQ(GURL("https://www.google.co.uk/"), google_url());
+ EXPECT_TRUE(listener_notified());
+}
+
+TEST_F(GoogleURLTrackerTest, DontUpdateWhenUnchanged) {
+ GURL original_google_url("https://www.google.co.uk/");
+ set_google_url(original_google_url);
+
+ RequestServerCheck();
+ EXPECT_FALSE(GetFetcher());
+ EXPECT_EQ(original_google_url, google_url());
+ EXPECT_FALSE(listener_notified());
+
+ FinishSleep();
+ MockSearchDomainCheckResponse(".google.co.uk");
+ EXPECT_EQ(original_google_url, google_url());
+ // No one should be notified, because the new URL matches the old.
+ EXPECT_FALSE(listener_notified());
+}
+
+TEST_F(GoogleURLTrackerTest, DontUpdateOnBadReplies) {
+ GURL original_google_url("https://www.google.co.uk/");
+ set_google_url(original_google_url);
+
+ RequestServerCheck();
+ EXPECT_FALSE(GetFetcher());
+ EXPECT_EQ(original_google_url, google_url());
+ EXPECT_FALSE(listener_notified());
+
+ // Old-style URL string.
+ FinishSleep();
+ MockSearchDomainCheckResponse("https://www.google.com/");
+ EXPECT_EQ(original_google_url, google_url());
+ EXPECT_FALSE(listener_notified());
+
+ // Not a Google domain.
+ FinishSleep();
+ MockSearchDomainCheckResponse(".google.evil.com");
+ EXPECT_EQ(original_google_url, google_url());
+ EXPECT_FALSE(listener_notified());
+
+ // Doesn't start with .google.
+ NotifyNetworkChanged();
+ MockSearchDomainCheckResponse(".mail.google.com");
+ EXPECT_EQ(original_google_url, google_url());
+ EXPECT_FALSE(listener_notified());
+
+ // Non-empty path.
+ NotifyNetworkChanged();
+ MockSearchDomainCheckResponse(".google.com/search");
+ EXPECT_EQ(original_google_url, google_url());
+ EXPECT_FALSE(listener_notified());
+
+ // Non-empty query.
+ NotifyNetworkChanged();
+ MockSearchDomainCheckResponse(".google.com/?q=foo");
+ EXPECT_EQ(original_google_url, google_url());
+ EXPECT_FALSE(listener_notified());
+
+ // Non-empty ref.
+ NotifyNetworkChanged();
+ MockSearchDomainCheckResponse(".google.com/#anchor");
+ EXPECT_EQ(original_google_url, google_url());
+ EXPECT_FALSE(listener_notified());
+
+ // Complete garbage.
+ NotifyNetworkChanged();
+ MockSearchDomainCheckResponse("HJ)*qF)_*&@f1");
+ EXPECT_EQ(original_google_url, google_url());
+ EXPECT_FALSE(listener_notified());
+}
+
+TEST_F(GoogleURLTrackerTest, RefetchOnNetworkChange) {
+ RequestServerCheck();
+ FinishSleep();
+ MockSearchDomainCheckResponse(".google.co.uk");
+ EXPECT_EQ(GURL("https://www.google.co.uk/"), google_url());
+ EXPECT_TRUE(listener_notified());
+ clear_listener_notified();
+
+ NotifyNetworkChanged();
+ MockSearchDomainCheckResponse(".google.co.in");
+ EXPECT_EQ(GURL("https://www.google.co.in/"), google_url());
+ EXPECT_TRUE(listener_notified());
+}
+
+TEST_F(GoogleURLTrackerTest, DontRefetchWhenNoOneRequestsCheck) {
+ FinishSleep();
+ NotifyNetworkChanged();
+ // No one called RequestServerCheck() so nothing should have happened.
+ EXPECT_FALSE(GetFetcher());
+ MockSearchDomainCheckResponse(".google.co.uk");
+ EXPECT_EQ(GURL(GoogleURLTracker::kDefaultGoogleHomepage), google_url());
+ EXPECT_FALSE(listener_notified());
+}
+
+TEST_F(GoogleURLTrackerTest, FetchOnLateRequest) {
+ FinishSleep();
+ NotifyNetworkChanged();
+ MockSearchDomainCheckResponse(".google.co.jp");
+
+ RequestServerCheck();
+ // The first request for a check should trigger a fetch if it hasn't happened
+ // already.
+ MockSearchDomainCheckResponse(".google.co.uk");
+ EXPECT_EQ(GURL("https://www.google.co.uk/"), google_url());
+ EXPECT_TRUE(listener_notified());
+}
+
+TEST_F(GoogleURLTrackerTest, DontFetchTwiceOnLateRequests) {
+ FinishSleep();
+ NotifyNetworkChanged();
+ MockSearchDomainCheckResponse(".google.co.jp");
+
+ RequestServerCheck();
+ // The first request for a check should trigger a fetch if it hasn't happened
+ // already.
+ MockSearchDomainCheckResponse(".google.co.uk");
+ EXPECT_EQ(GURL("https://www.google.co.uk/"), google_url());
+ EXPECT_TRUE(listener_notified());
+ clear_listener_notified();
+
+ RequestServerCheck();
+ // The second request should be ignored.
+ EXPECT_FALSE(GetFetcher());
+ MockSearchDomainCheckResponse(".google.co.in");
+ EXPECT_EQ(GURL("https://www.google.co.uk/"), google_url());
+ EXPECT_FALSE(listener_notified());
+}
diff --git a/chromium/components/google/core/browser/google_util.cc b/chromium/components/google/core/browser/google_util.cc
new file mode 100644
index 00000000000..fc253a310f0
--- /dev/null
+++ b/chromium/components/google/core/browser/google_util.cc
@@ -0,0 +1,232 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/google/core/browser/google_util.h"
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/google/core/browser/google_switches.h"
+#include "components/google/core/browser/google_url_tracker.h"
+#include "components/url_formatter/url_fixer.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "net/base/url_util.h"
+#include "url/gurl.h"
+
+// Only use Link Doctor on official builds. It uses an API key, too, but
+// seems best to just disable it, for more responsive error pages and to reduce
+// server load.
+#if defined(GOOGLE_CHROME_BUILD)
+#define LINKDOCTOR_SERVER_REQUEST_URL "https://www.googleapis.com/rpc"
+#else
+#define LINKDOCTOR_SERVER_REQUEST_URL ""
+#endif
+
+
+// Helpers --------------------------------------------------------------------
+
+namespace {
+
+bool gUseMockLinkDoctorBaseURLForTesting = false;
+
+bool IsPathHomePageBase(base::StringPiece path) {
+ return (path == "/") || (path == "/webhp");
+}
+
+// True if |host| is "[www.]<domain_in_lower_case>.<TLD>" with a valid TLD. If
+// |subdomain_permission| is ALLOW_SUBDOMAIN, we check against host
+// "*.<domain_in_lower_case>.<TLD>" instead.
+bool IsValidHostName(base::StringPiece host,
+ base::StringPiece domain_in_lower_case,
+ google_util::SubdomainPermission subdomain_permission) {
+ size_t tld_length = net::registry_controlled_domains::GetRegistryLength(
+ host,
+ net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
+ net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
+ if ((tld_length == 0) || (tld_length == std::string::npos))
+ return false;
+
+ // Removes the tld and the preceding dot.
+ base::StringPiece host_minus_tld =
+ host.substr(0, host.length() - tld_length - 1);
+ if (base::LowerCaseEqualsASCII(host_minus_tld, domain_in_lower_case))
+ return true;
+
+ if (subdomain_permission == google_util::ALLOW_SUBDOMAIN) {
+ std::string dot_domain(".");
+ domain_in_lower_case.AppendToString(&dot_domain);
+ return base::EndsWith(host_minus_tld, dot_domain,
+ base::CompareCase::INSENSITIVE_ASCII);
+ }
+
+ std::string www_domain("www.");
+ domain_in_lower_case.AppendToString(&www_domain);
+ return base::LowerCaseEqualsASCII(host_minus_tld, www_domain);
+}
+
+// True if |url| is a valid URL with HTTP or HTTPS scheme. If |port_permission|
+// is DISALLOW_NON_STANDARD_PORTS, this also requires |url| to use the standard
+// port for its scheme (80 for HTTP, 443 for HTTPS).
+bool IsValidURL(const GURL& url, google_util::PortPermission port_permission) {
+ return url.is_valid() && url.SchemeIsHTTPOrHTTPS() &&
+ (url.port().empty() ||
+ (port_permission == google_util::ALLOW_NON_STANDARD_PORTS));
+}
+
+} // namespace
+
+
+namespace google_util {
+
+// Global functions -----------------------------------------------------------
+
+bool HasGoogleSearchQueryParam(base::StringPiece str) {
+ url::Component query(0, static_cast<int>(str.length())), key, value;
+ while (url::ExtractQueryKeyValue(str.data(), &query, &key, &value)) {
+ if (value.is_nonempty()) {
+ base::StringPiece key_str = str.substr(key.begin, key.len);
+ if (key_str == "q" || key_str == "as_q")
+ return true;
+ }
+ }
+ return false;
+}
+
+GURL LinkDoctorBaseURL() {
+ if (gUseMockLinkDoctorBaseURLForTesting)
+ return GURL("http://mock.linkdoctor.url/for?testing");
+ return GURL(LINKDOCTOR_SERVER_REQUEST_URL);
+}
+
+void SetMockLinkDoctorBaseURLForTesting() {
+ gUseMockLinkDoctorBaseURLForTesting = true;
+}
+
+std::string GetGoogleLocale(const std::string& application_locale) {
+ // Google does not recognize "nb" for Norwegian Bokmal; it uses "no".
+ return (application_locale == "nb") ? "no" : application_locale;
+}
+
+GURL AppendGoogleLocaleParam(const GURL& url,
+ const std::string& application_locale) {
+ return net::AppendQueryParameter(
+ url, "hl", GetGoogleLocale(application_locale));
+}
+
+std::string GetGoogleCountryCode(const GURL& google_homepage_url) {
+ base::StringPiece google_hostname = google_homepage_url.host_piece();
+ const size_t last_dot = google_hostname.find_last_of('.');
+ if (last_dot == std::string::npos) {
+ NOTREACHED();
+ }
+ base::StringPiece country_code = google_hostname.substr(last_dot + 1);
+ // Assume the com TLD implies the US.
+ if (country_code == "com")
+ return "us";
+ // Google uses the Unicode Common Locale Data Repository (CLDR), and the CLDR
+ // code for the UK is "gb".
+ if (country_code == "uk")
+ return "gb";
+ // Catalonia does not have a CLDR country code, since it's a region in Spain,
+ // so use Spain instead.
+ if (country_code == "cat")
+ return "es";
+ return country_code.as_string();
+}
+
+GURL GetGoogleSearchURL(const GURL& google_homepage_url) {
+ // To transform the homepage URL into the corresponding search URL, add the
+ // "search" and the "q=" query string.
+ GURL::Replacements replacements;
+ replacements.SetPathStr("search");
+ replacements.SetQueryStr("q=");
+ return google_homepage_url.ReplaceComponents(replacements);
+}
+
+const GURL& CommandLineGoogleBaseURL() {
+ // Unit tests may add command-line flags after the first call to this
+ // function, so we don't simply initialize a static |base_url| directly and
+ // then unconditionally return it.
+ CR_DEFINE_STATIC_LOCAL(std::string, switch_value, ());
+ CR_DEFINE_STATIC_LOCAL(GURL, base_url, ());
+ std::string current_switch_value(
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kGoogleBaseURL));
+ if (current_switch_value != switch_value) {
+ switch_value = current_switch_value;
+ base_url = url_formatter::FixupURL(switch_value, std::string());
+ if (!base_url.is_valid() || base_url.has_query() || base_url.has_ref())
+ base_url = GURL();
+ }
+ return base_url;
+}
+
+bool StartsWithCommandLineGoogleBaseURL(const GURL& url) {
+ const GURL& base_url(CommandLineGoogleBaseURL());
+ return base_url.is_valid() &&
+ base::StartsWith(url.possibly_invalid_spec(), base_url.spec(),
+ base::CompareCase::SENSITIVE);
+}
+
+bool IsGoogleHostname(base::StringPiece host,
+ SubdomainPermission subdomain_permission) {
+ const GURL& base_url(CommandLineGoogleBaseURL());
+ if (base_url.is_valid() && (host == base_url.host_piece()))
+ return true;
+
+ return IsValidHostName(host, "google", subdomain_permission);
+}
+
+bool IsGoogleDomainUrl(const GURL& url,
+ SubdomainPermission subdomain_permission,
+ PortPermission port_permission) {
+ return IsValidURL(url, port_permission) &&
+ IsGoogleHostname(url.host(), subdomain_permission);
+}
+
+bool IsGoogleHomePageUrl(const GURL& url) {
+ // First check to see if this has a Google domain.
+ if (!IsGoogleDomainUrl(url, DISALLOW_SUBDOMAIN, DISALLOW_NON_STANDARD_PORTS))
+ return false;
+
+ // Make sure the path is a known home page path.
+ base::StringPiece path(url.path_piece());
+ return IsPathHomePageBase(path) ||
+ base::StartsWith(path, "/ig", base::CompareCase::INSENSITIVE_ASCII);
+}
+
+bool IsGoogleSearchUrl(const GURL& url) {
+ // First check to see if this has a Google domain.
+ if (!IsGoogleDomainUrl(url, DISALLOW_SUBDOMAIN, DISALLOW_NON_STANDARD_PORTS))
+ return false;
+
+ // Make sure the path is a known search path.
+ base::StringPiece path(url.path_piece());
+ bool is_home_page_base = IsPathHomePageBase(path);
+ if (!is_home_page_base && (path != "/search"))
+ return false;
+
+ // Check for query parameter in URL parameter and hash fragment, depending on
+ // the path type.
+ return HasGoogleSearchQueryParam(url.ref_piece()) ||
+ (!is_home_page_base && HasGoogleSearchQueryParam(url.query_piece()));
+}
+
+bool IsYoutubeDomainUrl(const GURL& url,
+ SubdomainPermission subdomain_permission,
+ PortPermission port_permission) {
+ return IsValidURL(url, port_permission) &&
+ IsValidHostName(url.host_piece(), "youtube", subdomain_permission);
+}
+
+} // namespace google_util
diff --git a/chromium/components/google/core/browser/google_util.h b/chromium/components/google/core/browser/google_util.h
new file mode 100644
index 00000000000..53e58bc0e85
--- /dev/null
+++ b/chromium/components/google/core/browser/google_util.h
@@ -0,0 +1,112 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Some Google related utility functions.
+
+#ifndef COMPONENTS_GOOGLE_CORE_BROWSER_GOOGLE_UTIL_H_
+#define COMPONENTS_GOOGLE_CORE_BROWSER_GOOGLE_UTIL_H_
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+
+class GURL;
+
+// This namespace provides various helpers around handling Google-related URLs.
+namespace google_util {
+
+// True iff |str| contains a "q=" or "as_q=" query parameter with a non-empty
+// value. |str| should be a query or a hash fragment, without the ? or # (as
+// returned by GURL::query() or GURL::ref().
+bool HasGoogleSearchQueryParam(base::StringPiece str);
+
+// The query key that identifies a Google Extended API request for Instant.
+const char kInstantExtendedAPIParam[] = "espv";
+
+GURL LinkDoctorBaseURL();
+void SetMockLinkDoctorBaseURLForTesting();
+
+// Returns the Google locale corresponding to |application_locale|. This is
+// the same string as AppendGoogleLocaleParam adds to the URL, only without the
+// leading "hl".
+std::string GetGoogleLocale(const std::string& application_locale);
+
+// Adds the Google locale string to the URL (e.g., hl=en-US). This does not
+// check to see if the param already exists.
+GURL AppendGoogleLocaleParam(const GURL& url,
+ const std::string& application_locale);
+
+// Returns the Google country code string for the given Google homepage URL.
+std::string GetGoogleCountryCode(const GURL& google_homepage_url);
+
+// Returns the Google search URL for the given Google homepage URL.
+GURL GetGoogleSearchURL(const GURL& google_homepage_url);
+
+// Returns the Google base URL specified on the command line, if it exists.
+// This performs some fixup and sanity-checking to ensure that the resulting URL
+// is valid and has no query or ref.
+const GURL& CommandLineGoogleBaseURL();
+
+// Returns true if a Google base URL was specified on the command line and |url|
+// begins with that base URL. This uses a simple string equality check.
+bool StartsWithCommandLineGoogleBaseURL(const GURL& url);
+
+// WARNING: The following IsGoogleXXX() functions use heuristics to rule out
+// "obviously false" answers. They do NOT guarantee that the string in question
+// is actually on a Google-owned domain, just that it looks plausible.
+
+// Designate whether or not a URL checking function also checks for specific
+// subdomains, or only "www" and empty subdomains.
+enum SubdomainPermission {
+ ALLOW_SUBDOMAIN,
+ DISALLOW_SUBDOMAIN,
+};
+
+// Designate whether or not a URL checking function also checks for standard
+// ports (80 for http, 443 for https), or if it allows all port numbers.
+enum PortPermission {
+ ALLOW_NON_STANDARD_PORTS,
+ DISALLOW_NON_STANDARD_PORTS,
+};
+
+// True if |host| is "[www.]google.<TLD>" with a valid TLD. If
+// |subdomain_permission| is ALLOW_SUBDOMAIN, we check against host
+// "*.google.<TLD>" instead.
+//
+// If the Google base URL has been overridden on the command line, this function
+// will also return true for any URL whose hostname exactly matches the hostname
+// of the URL specified on the command line. In this case,
+// |subdomain_permission| is ignored.
+bool IsGoogleHostname(base::StringPiece host,
+ SubdomainPermission subdomain_permission);
+
+// True if |url| is a valid URL with a host that returns true for
+// IsGoogleHostname(), and an HTTP or HTTPS scheme. If |port_permission| is
+// DISALLOW_NON_STANDARD_PORTS, this also requires |url| to use the standard
+// port for its scheme (80 for HTTP, 443 for HTTPS).
+//
+// Note that this only checks for google.<TLD> domains, but not other Google
+// properties. There is code in variations_http_header_provider.cc that checks
+// for additional Google properties, which can be moved here if more callers
+// are interested in this in the future.
+bool IsGoogleDomainUrl(const GURL& url,
+ SubdomainPermission subdomain_permission,
+ PortPermission port_permission);
+
+// True if |url| represents a valid Google home page URL.
+bool IsGoogleHomePageUrl(const GURL& url);
+
+// True if |url| represents a valid Google search URL.
+bool IsGoogleSearchUrl(const GURL& url);
+
+// True if |url| is a valid youtube.<TLD> URL. If |port_permission| is
+// DISALLOW_NON_STANDARD_PORTS, this also requires |url| to use the standard
+// port for its scheme (80 for HTTP, 443 for HTTPS).
+bool IsYoutubeDomainUrl(const GURL& url,
+ SubdomainPermission subdomain_permission,
+ PortPermission port_permission);
+
+} // namespace google_util
+
+#endif // COMPONENTS_GOOGLE_CORE_BROWSER_GOOGLE_UTIL_H_
diff --git a/chromium/components/google/core/browser/google_util_unittest.cc b/chromium/components/google/core/browser/google_util_unittest.cc
new file mode 100644
index 00000000000..f5bf1ab2d5c
--- /dev/null
+++ b/chromium/components/google/core/browser/google_util_unittest.cc
@@ -0,0 +1,425 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "components/google/core/browser/google_switches.h"
+#include "components/google/core/browser/google_url_tracker.h"
+#include "components/google/core/browser/google_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using google_util::IsGoogleDomainUrl;
+
+
+// Helpers --------------------------------------------------------------------
+
+namespace {
+
+const std::string kValidSearchSchemes[] = {
+ "http",
+ "https"
+};
+
+const std::string kValidSearchQueryParams[] = {
+ "q",
+ "as_q" // Advanced search uses "as_q" instead of "q" as the query param.
+};
+
+// These functions merely provide brevity in the callers.
+
+bool IsHomePage(const std::string& url) {
+ return google_util::IsGoogleHomePageUrl(GURL(url));
+}
+
+bool IsSearch(const std::string& url) {
+ return google_util::IsGoogleSearchUrl(GURL(url));
+}
+
+bool StartsWithBaseURL(const std::string& url) {
+ return google_util::StartsWithCommandLineGoogleBaseURL(GURL(url));
+}
+
+} // namespace
+
+
+// Actual tests ---------------------------------------------------------------
+
+TEST(GoogleUtilTest, GoodHomePagesNonSecure) {
+ // Valid home page hosts.
+ EXPECT_TRUE(IsHomePage(GoogleURLTracker::kDefaultGoogleHomepage));
+ EXPECT_TRUE(IsHomePage("http://google.com"));
+ EXPECT_TRUE(IsHomePage("http://www.google.com"));
+ EXPECT_TRUE(IsHomePage("http://www.google.ca"));
+ EXPECT_TRUE(IsHomePage("http://www.google.co.uk"));
+ EXPECT_TRUE(IsHomePage("http://www.google.com:80/"));
+
+ // Only the paths /, /webhp, and /ig.* are valid. Query parameters are
+ // ignored.
+ EXPECT_TRUE(IsHomePage("http://www.google.com/"));
+ EXPECT_TRUE(IsHomePage("http://www.google.com/webhp"));
+ EXPECT_TRUE(IsHomePage("http://www.google.com/webhp?rlz=TEST"));
+ EXPECT_TRUE(IsHomePage("http://www.google.com/ig"));
+ EXPECT_TRUE(IsHomePage("http://www.google.com/ig/foo"));
+ EXPECT_TRUE(IsHomePage("http://www.google.com/ig?rlz=TEST"));
+ EXPECT_TRUE(IsHomePage("http://www.google.com/ig/foo?rlz=TEST"));
+}
+
+TEST(GoogleUtilTest, GoodHomePagesSecure) {
+ // Valid home page hosts.
+ EXPECT_TRUE(IsHomePage("https://google.com"));
+ EXPECT_TRUE(IsHomePage("https://www.google.com"));
+ EXPECT_TRUE(IsHomePage("https://www.google.ca"));
+ EXPECT_TRUE(IsHomePage("https://www.google.co.uk"));
+ EXPECT_TRUE(IsHomePage("https://www.google.com:443/"));
+
+ // Only the paths /, /webhp, and /ig.* are valid. Query parameters are
+ // ignored.
+ EXPECT_TRUE(IsHomePage("https://www.google.com/"));
+ EXPECT_TRUE(IsHomePage("https://www.google.com/webhp"));
+ EXPECT_TRUE(IsHomePage("https://www.google.com/webhp?rlz=TEST"));
+ EXPECT_TRUE(IsHomePage("https://www.google.com/ig"));
+ EXPECT_TRUE(IsHomePage("https://www.google.com/ig/foo"));
+ EXPECT_TRUE(IsHomePage("https://www.google.com/ig?rlz=TEST"));
+ EXPECT_TRUE(IsHomePage("https://www.google.com/ig/foo?rlz=TEST"));
+}
+
+TEST(GoogleUtilTest, BadHomePages) {
+ EXPECT_FALSE(IsHomePage(std::string()));
+
+ // If specified, only the "www" subdomain is OK.
+ EXPECT_FALSE(IsHomePage("http://maps.google.com"));
+ EXPECT_FALSE(IsHomePage("http://foo.google.com"));
+
+ // No non-standard port numbers.
+ EXPECT_FALSE(IsHomePage("http://www.google.com:1234"));
+ EXPECT_FALSE(IsHomePage("https://www.google.com:5678"));
+
+ // Invalid TLDs.
+ EXPECT_FALSE(IsHomePage("http://www.google.example"));
+ EXPECT_FALSE(IsHomePage("http://www.google.com.example"));
+ EXPECT_FALSE(IsHomePage("http://www.google.example.com"));
+ EXPECT_FALSE(IsHomePage("http://www.google.ab.cd"));
+ EXPECT_FALSE(IsHomePage("http://www.google.uk.qq"));
+
+ // Must be http or https.
+ EXPECT_FALSE(IsHomePage("ftp://www.google.com"));
+ EXPECT_FALSE(IsHomePage("file://does/not/exist"));
+ EXPECT_FALSE(IsHomePage("bad://www.google.com"));
+ EXPECT_FALSE(IsHomePage("www.google.com"));
+
+ // Only the paths /, /webhp, and /ig.* are valid.
+ EXPECT_FALSE(IsHomePage("http://www.google.com/abc"));
+ EXPECT_FALSE(IsHomePage("http://www.google.com/webhpabc"));
+ EXPECT_FALSE(IsHomePage("http://www.google.com/webhp/abc"));
+ EXPECT_FALSE(IsHomePage("http://www.google.com/abcig"));
+ EXPECT_FALSE(IsHomePage("http://www.google.com/webhp/ig"));
+
+ // A search URL should not be identified as a home page URL.
+ EXPECT_FALSE(IsHomePage("http://www.google.com/search?q=something"));
+
+ // Path is case sensitive.
+ EXPECT_FALSE(IsHomePage("https://www.google.com/WEBHP"));
+}
+
+TEST(GoogleUtilTest, GoodSearches) {
+ const std::string patterns[] = {
+ // Queries with path "/search" need to have the query parameter in either
+ // the url parameter or the hash fragment.
+ "%s://www.google.com/search?%s=something",
+ "%s://www.google.com/search#%s=something",
+ "%s://www.google.com/search?name=bob&%s=something",
+ "%s://www.google.com/search?name=bob#%s=something",
+ "%s://www.google.com/search?name=bob#age=24&%s=thng",
+ "%s://www.google.co.uk/search?%s=something",
+ // It's actually valid for both to have the query parameter.
+ "%s://www.google.com/search?%s=something#q=other",
+
+ // Queries with path "/webhp", "/" or "" need to have the query parameter in
+ // the hash fragment.
+ "%s://www.google.com/webhp#%s=something",
+ "%s://www.google.com/webhp#name=bob&%s=something",
+ "%s://www.google.com/webhp?name=bob#%s=something",
+ "%s://www.google.com/webhp?name=bob#age=24&%s=thing",
+
+ "%s://www.google.com/#%s=something",
+ "%s://www.google.com/#name=bob&%s=something",
+ "%s://www.google.com/?name=bob#%s=something",
+ "%s://www.google.com/?name=bob#age=24&%s=something",
+
+ "%s://www.google.com#%s=something",
+ "%s://www.google.com#name=bob&%s=something",
+ "%s://www.google.com?name=bob#%s=something",
+ "%s://www.google.com?name=bob#age=24&%s=something"
+ };
+
+ for (const std::string& pattern : patterns) {
+ for (const std::string& scheme : kValidSearchSchemes) {
+ for (const std::string& query_param : kValidSearchQueryParams) {
+ EXPECT_TRUE(IsSearch(base::StringPrintf(pattern.c_str(),
+ scheme.c_str(),
+ query_param.c_str())));
+ }
+ }
+ }
+}
+
+TEST(GoogleUtilTest, BadSearches) {
+ // A home page URL should not be identified as a search URL.
+ EXPECT_FALSE(IsSearch(GoogleURLTracker::kDefaultGoogleHomepage));
+
+ // Must be http or https.
+ EXPECT_FALSE(IsSearch("ftp://www.google.com/search?q=something"));
+ EXPECT_FALSE(IsSearch("file://does/not/exist/search?q=something"));
+ EXPECT_FALSE(IsSearch("bad://www.google.com/search?q=something"));
+ EXPECT_FALSE(IsSearch("www.google.com/search?q=something"));
+
+ // Empty URL is invalid.
+ EXPECT_FALSE(IsSearch(std::string()));
+
+ const std::string patterns[] = {
+ "%s://google.com",
+ "%s://www.google.com",
+ "%s://www.google.com/search",
+ "%s://www.google.com/search?"
+ };
+
+ for (const std::string& pattern : patterns) {
+ for (const std::string& scheme : kValidSearchSchemes) {
+ EXPECT_FALSE(IsSearch(base::StringPrintf(pattern.c_str(),
+ scheme.c_str())));
+ }
+ }
+
+ const std::string patterns_q[] = {
+ // Can't have an empty query parameter.
+ "%s://www.google.com/search?%s=",
+ "%s://www.google.com/search?name=bob&%s=",
+ "%s://www.google.com/webhp#%s=",
+ "%s://www.google.com/webhp#name=bob&%s=",
+
+ // Home page searches without a hash fragment query parameter are invalid.
+ "%s://www.google.com/webhp?%s=something",
+ "%s://www.google.com/webhp?%s=something#no=good",
+ "%s://www.google.com/webhp?name=bob&%s=something",
+ "%s://www.google.com/?%s=something",
+ "%s://www.google.com?%s=something",
+
+ // Some paths are outright invalid as searches.
+ "%s://www.google.com/notreal?%s=something",
+ "%s://www.google.com/chrome?%s=something",
+ "%s://www.google.com/search/nogood?%s=something",
+ "%s://www.google.com/webhp/nogood#%s=something",
+
+ // Case sensitive paths.
+ "%s://www.google.com/SEARCH?%s=something",
+ "%s://www.google.com/WEBHP#%s=something"
+ };
+
+ for (const std::string& pattern : patterns_q) {
+ for (const std::string& scheme : kValidSearchSchemes) {
+ for (const std::string& query_param : kValidSearchQueryParams) {
+ EXPECT_FALSE(IsSearch(base::StringPrintf(pattern.c_str(),
+ scheme.c_str(),
+ query_param.c_str())));
+ }
+ }
+ }
+}
+
+TEST(GoogleUtilTest, GoogleDomains) {
+ // Test some good Google domains (valid TLDs).
+ EXPECT_TRUE(IsGoogleDomainUrl(GURL("http://www.google.com"),
+ google_util::ALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_TRUE(IsGoogleDomainUrl(GURL("http://google.com"),
+ google_util::ALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_TRUE(IsGoogleDomainUrl(GURL("http://www.google.ca"),
+ google_util::ALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_TRUE(IsGoogleDomainUrl(GURL("http://www.google.biz.tj"),
+ google_util::ALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_TRUE(IsGoogleDomainUrl(GURL("http://www.google.com/search?q=thing"),
+ google_util::ALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_TRUE(IsGoogleDomainUrl(GURL("http://www.google.com/webhp"),
+ google_util::ALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+
+ // Test some bad Google domains (invalid TLDs).
+ EXPECT_FALSE(IsGoogleDomainUrl(GURL("http://www.google.notrealtld"),
+ google_util::ALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_FALSE(IsGoogleDomainUrl(GURL("http://www.google.faketld/search?q=q"),
+ google_util::ALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_FALSE(IsGoogleDomainUrl(GURL("http://www.yahoo.com"),
+ google_util::ALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+
+ // Test subdomain checks.
+ EXPECT_TRUE(IsGoogleDomainUrl(GURL("http://images.google.com"),
+ google_util::ALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_FALSE(IsGoogleDomainUrl(GURL("http://images.google.com"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_TRUE(IsGoogleDomainUrl(GURL("http://google.com"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_TRUE(IsGoogleDomainUrl(GURL("http://www.google.com"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+
+ // Port and scheme checks.
+ EXPECT_TRUE(IsGoogleDomainUrl(GURL("http://www.google.com:80"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_FALSE(IsGoogleDomainUrl(GURL("http://www.google.com:123"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_TRUE(IsGoogleDomainUrl(GURL("https://www.google.com:443"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_FALSE(IsGoogleDomainUrl(GURL("http://www.google.com:123"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_TRUE(IsGoogleDomainUrl(GURL("http://www.google.com:123"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::ALLOW_NON_STANDARD_PORTS));
+ EXPECT_TRUE(IsGoogleDomainUrl(GURL("https://www.google.com:123"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::ALLOW_NON_STANDARD_PORTS));
+ EXPECT_TRUE(IsGoogleDomainUrl(GURL("http://www.google.com:80"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::ALLOW_NON_STANDARD_PORTS));
+ EXPECT_TRUE(IsGoogleDomainUrl(GURL("https://www.google.com:443"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::ALLOW_NON_STANDARD_PORTS));
+ EXPECT_FALSE(IsGoogleDomainUrl(GURL("file://www.google.com"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_FALSE(IsGoogleDomainUrl(GURL("doesnotexist://www.google.com"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+}
+
+TEST(GoogleUtilTest, GoogleBaseURLNotSpecified) {
+ // When no command-line flag is specified, no input to
+ // StartsWithCommandLineGoogleBaseURL() should return true.
+ EXPECT_FALSE(StartsWithBaseURL(std::string()));
+ EXPECT_FALSE(StartsWithBaseURL("http://www.foo.com/"));
+ EXPECT_FALSE(StartsWithBaseURL("http://www.google.com/"));
+
+ // By default, none of the IsGoogleXXX functions should return true for a
+ // "foo.com" URL.
+ EXPECT_FALSE(IsGoogleHostname("www.foo.com",
+ google_util::DISALLOW_SUBDOMAIN));
+ EXPECT_FALSE(IsGoogleDomainUrl(GURL("http://www.foo.com/xyz"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_FALSE(IsGoogleDomainUrl(GURL("https://www.foo.com/"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_FALSE(IsHomePage("https://www.foo.com/webhp"));
+ EXPECT_FALSE(IsSearch("http://www.foo.com/search?q=a"));
+
+ // Override the Google base URL on the command line.
+ base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ switches::kGoogleBaseURL, "http://www.foo.com/");
+
+ // Only URLs which start with exactly the string on the command line should
+ // cause StartsWithCommandLineGoogleBaseURL() to return true.
+ EXPECT_FALSE(StartsWithBaseURL(std::string()));
+ EXPECT_TRUE(StartsWithBaseURL("http://www.foo.com/"));
+ EXPECT_TRUE(StartsWithBaseURL("http://www.foo.com/abc"));
+ EXPECT_FALSE(StartsWithBaseURL("https://www.foo.com/"));
+ EXPECT_FALSE(StartsWithBaseURL("http://www.google.com/"));
+
+ // The various IsGoogleXXX functions should respect the command-line flag.
+ EXPECT_TRUE(IsGoogleHostname("www.foo.com", google_util::DISALLOW_SUBDOMAIN));
+ EXPECT_FALSE(IsGoogleHostname("foo.com", google_util::ALLOW_SUBDOMAIN));
+ EXPECT_TRUE(IsGoogleDomainUrl(GURL("http://www.foo.com/xyz"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_TRUE(IsGoogleDomainUrl(GURL("https://www.foo.com/"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_TRUE(IsHomePage("https://www.foo.com/webhp"));
+ EXPECT_FALSE(IsHomePage("http://www.foo.com/xyz"));
+ EXPECT_TRUE(IsSearch("http://www.foo.com/search?q=a"));
+}
+
+TEST(GoogleUtilTest, GoogleBaseURLDisallowQuery) {
+ base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ switches::kGoogleBaseURL, "http://www.foo.com/?q=");
+ EXPECT_FALSE(google_util::CommandLineGoogleBaseURL().is_valid());
+}
+
+TEST(GoogleUtilTest, GoogleBaseURLDisallowRef) {
+ base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ switches::kGoogleBaseURL, "http://www.foo.com/#q=");
+ EXPECT_FALSE(google_util::CommandLineGoogleBaseURL().is_valid());
+}
+
+TEST(GoogleUtilTest, GoogleBaseURLFixup) {
+ base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ switches::kGoogleBaseURL, "www.foo.com");
+ ASSERT_TRUE(google_util::CommandLineGoogleBaseURL().is_valid());
+ EXPECT_EQ("http://www.foo.com/",
+ google_util::CommandLineGoogleBaseURL().spec());
+}
+
+TEST(GoogleUtilTest, YoutubeDomains) {
+ EXPECT_TRUE(IsYoutubeDomainUrl(GURL("http://www.youtube.com"),
+ google_util::ALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_TRUE(IsYoutubeDomainUrl(GURL("http://youtube.com"),
+ google_util::ALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_TRUE(IsYoutubeDomainUrl(GURL("http://youtube.com/path/main.html"),
+ google_util::ALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_FALSE(IsYoutubeDomainUrl(GURL("http://notyoutube.com"),
+ google_util::ALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+
+ // TLD checks.
+ EXPECT_TRUE(IsYoutubeDomainUrl(GURL("http://www.youtube.ca"),
+ google_util::ALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_TRUE(IsYoutubeDomainUrl(GURL("http://www.youtube.co.uk"),
+ google_util::ALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_FALSE(IsYoutubeDomainUrl(GURL("http://www.youtube.notrealtld"),
+ google_util::ALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+
+ // Subdomain checks.
+ EXPECT_TRUE(IsYoutubeDomainUrl(GURL("http://images.youtube.com"),
+ google_util::ALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_FALSE(IsYoutubeDomainUrl(GURL("http://images.youtube.com"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+
+ // Port and scheme checks.
+ EXPECT_TRUE(IsYoutubeDomainUrl(GURL("http://www.youtube.com:80"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_TRUE(IsYoutubeDomainUrl(GURL("https://www.youtube.com:443"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_FALSE(IsYoutubeDomainUrl(GURL("http://www.youtube.com:123"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+ EXPECT_TRUE(IsYoutubeDomainUrl(GURL("http://www.youtube.com:123"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::ALLOW_NON_STANDARD_PORTS));
+ EXPECT_FALSE(IsYoutubeDomainUrl(GURL("file://www.youtube.com"),
+ google_util::DISALLOW_SUBDOMAIN,
+ google_util::DISALLOW_NON_STANDARD_PORTS));
+}
diff --git a/chromium/components/metrics/BUILD.gn b/chromium/components/metrics/BUILD.gn
new file mode 100644
index 00000000000..224070db0d4
--- /dev/null
+++ b/chromium/components/metrics/BUILD.gn
@@ -0,0 +1,367 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+declare_args() {
+ # Overrides os name in uma metrics log to "Blimp".
+ metrics_use_blimp = false
+}
+
+# GYP version: components/metrics.gypi:metrics
+source_set("metrics") {
+ sources = [
+ "call_stack_profile_metrics_provider.cc",
+ "call_stack_profile_metrics_provider.h",
+ "clean_exit_beacon.cc",
+ "clean_exit_beacon.h",
+ "client_info.cc",
+ "client_info.h",
+ "cloned_install_detector.cc",
+ "cloned_install_detector.h",
+ "daily_event.cc",
+ "daily_event.h",
+ "data_use_tracker.cc",
+ "data_use_tracker.h",
+ "drive_metrics_provider.cc",
+ "drive_metrics_provider.h",
+ "drive_metrics_provider_android.cc",
+ "drive_metrics_provider_ios.mm",
+ "drive_metrics_provider_linux.cc",
+ "drive_metrics_provider_mac.mm",
+ "drive_metrics_provider_win.cc",
+ "file_metrics_provider.cc",
+ "file_metrics_provider.h",
+ "histogram_encoder.cc",
+ "histogram_encoder.h",
+ "machine_id_provider.h",
+ "machine_id_provider_stub.cc",
+ "machine_id_provider_win.cc",
+ "metrics_log.cc",
+ "metrics_log.h",
+ "metrics_log_manager.cc",
+ "metrics_log_manager.h",
+ "metrics_log_uploader.cc",
+ "metrics_log_uploader.h",
+ "metrics_pref_names.cc",
+ "metrics_pref_names.h",
+ "metrics_provider.cc",
+ "metrics_provider.h",
+ "metrics_reporting_scheduler.cc",
+ "metrics_reporting_scheduler.h",
+ "metrics_service.cc",
+ "metrics_service.h",
+ "metrics_service_accessor.cc",
+ "metrics_service_accessor.h",
+ "metrics_service_client.cc",
+ "metrics_service_client.h",
+ "metrics_state_manager.cc",
+ "metrics_state_manager.h",
+ "metrics_switches.cc",
+ "metrics_switches.h",
+ "persisted_logs.cc",
+ "persisted_logs.h",
+ "stability_metrics_helper.cc",
+ "stability_metrics_helper.h",
+ "system_memory_stats_recorder.h",
+ "system_memory_stats_recorder_linux.cc",
+ "system_memory_stats_recorder_win.cc",
+ "url_constants.cc",
+ "url_constants.h",
+ ]
+
+ public_deps = [
+ "//components/metrics/proto",
+ ]
+ deps = [
+ "//base",
+ "//base:base_static",
+ "//components/prefs",
+ "//components/variations",
+ "//third_party/zlib:compression_utils",
+ ]
+
+ if (is_chromeos) {
+ deps += [ ":serialization" ]
+ }
+
+ if (is_mac) {
+ libs = [
+ # The below are all needed for drive_metrics_provider_mac.mm.
+ "CoreFoundation.framework",
+ "DiskArbitration.framework",
+ "Foundation.framework",
+ "IOKit.framework",
+ ]
+ }
+
+ if (is_win) {
+ sources -= [ "machine_id_provider_stub.cc" ]
+ }
+}
+
+if (!is_ios) {
+ # GYP version: components/metrics.gypi:metrics_gpu
+ source_set("gpu") {
+ sources = [
+ "gpu/gpu_metrics_provider.cc",
+ "gpu/gpu_metrics_provider.h",
+ ]
+
+ public_deps = [
+ ":metrics",
+ ]
+ deps = [
+ "//base",
+ "//content/public/browser",
+ "//gpu/config",
+ ]
+ }
+}
+
+if (is_chromeos) {
+ # GYP version: components/metrics.gypi:metrics_leak_detector
+ source_set("leak_detector") {
+ sources = [
+ "leak_detector/call_stack_manager.cc",
+ "leak_detector/call_stack_manager.h",
+ "leak_detector/call_stack_table.cc",
+ "leak_detector/call_stack_table.h",
+ "leak_detector/custom_allocator.cc",
+ "leak_detector/custom_allocator.h",
+ "leak_detector/leak_analyzer.cc",
+ "leak_detector/leak_analyzer.h",
+ "leak_detector/leak_detector.cc",
+ "leak_detector/leak_detector.h",
+ "leak_detector/leak_detector_impl.cc",
+ "leak_detector/leak_detector_impl.h",
+ "leak_detector/leak_detector_value_type.cc",
+ "leak_detector/leak_detector_value_type.h",
+ "leak_detector/ranked_set.cc",
+ "leak_detector/ranked_set.h",
+ ]
+
+ deps = [
+ "//base",
+ "//content/public/browser",
+ ]
+ }
+}
+
+# GYP version: components/metrics.gypi:metrics_net
+static_library("net") {
+ sources = [
+ "net/net_metrics_log_uploader.cc",
+ "net/net_metrics_log_uploader.h",
+ "net/network_metrics_provider.cc",
+ "net/network_metrics_provider.h",
+ "net/version_utils.cc",
+ "net/version_utils.h",
+ "net/wifi_access_point_info_provider.cc",
+ "net/wifi_access_point_info_provider.h",
+ ]
+
+ public_deps = [
+ ":metrics",
+ ]
+ allow_circular_includes_from = [ ":metrics" ]
+
+ deps = [
+ "//base",
+ "//components/data_use_measurement/core",
+ "//components/version_info",
+ "//net",
+ "//url",
+ ]
+
+ if (is_chromeos) {
+ sources += [
+ "net/wifi_access_point_info_provider_chromeos.cc",
+ "net/wifi_access_point_info_provider_chromeos.h",
+ ]
+ deps += [ "//chromeos" ]
+ }
+}
+
+# GYP version: components/metrics.gypi:metrics_profiler
+source_set("profiler") {
+ sources = [
+ "profiler/profiler_metrics_provider.cc",
+ "profiler/profiler_metrics_provider.h",
+ "profiler/tracking_synchronizer.cc",
+ "profiler/tracking_synchronizer.h",
+ "profiler/tracking_synchronizer_delegate.h",
+ "profiler/tracking_synchronizer_observer.cc",
+ "profiler/tracking_synchronizer_observer.h",
+ ]
+
+ public_deps = [
+ ":metrics",
+ ]
+ deps = [
+ "//base",
+ "//components/variations",
+ ]
+}
+
+# GYP version: components/metrics.gypi:metrics_ui
+source_set("ui") {
+ sources = [
+ "ui/screen_info_metrics_provider.cc",
+ "ui/screen_info_metrics_provider.h",
+ ]
+
+ public_deps = [
+ ":metrics",
+ ]
+ deps = [
+ "//base",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ ]
+}
+
+if (!is_ios) {
+ # GYP version: components/metrics.gypi:metrics_profiler_content
+ source_set("profiler_content") {
+ sources = [
+ "profiler/content/content_tracking_synchronizer_delegate.cc",
+ "profiler/content/content_tracking_synchronizer_delegate.h",
+ ]
+
+ public_deps = [
+ ":profiler",
+ ]
+ deps = [
+ "//base",
+ "//components/nacl/common:process_type",
+ "//content/public/browser",
+ "//content/public/common",
+ ]
+ }
+} else {
+ # GYP version: components/metrics.gypi:metrics_profiler_ios
+ source_set("profiler_ios") {
+ sources = [
+ "profiler/ios/ios_tracking_synchronizer_delegate.cc",
+ "profiler/ios/ios_tracking_synchronizer_delegate.h",
+ ]
+
+ public_deps = [
+ ":profiler",
+ ]
+ deps = [
+ "//base",
+ ]
+ }
+}
+
+# GYP version: components/metrics.gypi:metrics_test_support
+source_set("test_support") {
+ sources = [
+ "test_metrics_provider.cc",
+ "test_metrics_provider.h",
+ "test_metrics_service_client.cc",
+ "test_metrics_service_client.h",
+ ]
+
+ public_deps = [
+ ":metrics",
+ ]
+ deps = [
+ "//base",
+ ]
+}
+
+if (is_linux) {
+ # GYP version: components/metrics.gypi:metrics_serialization
+ source_set("serialization") {
+ sources = [
+ "serialization/metric_sample.cc",
+ "serialization/metric_sample.h",
+ "serialization/serialization_utils.cc",
+ "serialization/serialization_utils.h",
+ ]
+ deps = [
+ "//base",
+ ]
+ }
+}
+
+if (is_chromeos) {
+ source_set("leak_detector_unit_tests") {
+ testonly = true
+ sources = [
+ "leak_detector/call_stack_manager_unittest.cc",
+ "leak_detector/call_stack_table_unittest.cc",
+ "leak_detector/leak_analyzer_unittest.cc",
+ "leak_detector/leak_detector_impl_unittest.cc",
+ "leak_detector/leak_detector_unittest.cc",
+ "leak_detector/ranked_set_unittest.cc",
+ ]
+
+ deps = [
+ ":leak_detector",
+ "//base",
+ "//content/test:test_support",
+ "//testing/gtest",
+ ]
+ }
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "call_stack_profile_metrics_provider_unittest.cc",
+ "cloned_install_detector_unittest.cc",
+ "daily_event_unittest.cc",
+ "data_use_tracker_unittest.cc",
+ "drive_metrics_provider_unittest.cc",
+ "histogram_encoder_unittest.cc",
+ "machine_id_provider_win_unittest.cc",
+ "metrics_log_manager_unittest.cc",
+ "metrics_log_unittest.cc",
+ "metrics_reporting_scheduler_unittest.cc",
+ "metrics_service_unittest.cc",
+ "metrics_state_manager_unittest.cc",
+ "net/net_metrics_log_uploader_unittest.cc",
+ "persisted_logs_unittest.cc",
+ "profiler/profiler_metrics_provider_unittest.cc",
+ "profiler/tracking_synchronizer_unittest.cc",
+ "stability_metrics_helper_unittest.cc",
+ "ui/screen_info_metrics_provider_unittest.cc",
+ ]
+
+ deps = [
+ ":metrics",
+ ":net",
+ ":profiler",
+ ":test_support",
+ ":ui",
+ "//base/test:test_support",
+ "//components/prefs:test_support",
+ "//components/variations",
+ "//net:test_support",
+ "//testing/gtest",
+ "//third_party/zlib:compression_utils",
+ "//ui/gfx/geometry",
+ ]
+
+ if (is_linux) {
+ sources += [ "serialization/serialization_utils_unittest.cc" ]
+ deps += [ ":serialization" ]
+ }
+
+ if (is_chromeos) {
+ deps += [ ":leak_detector_unit_tests" ]
+ }
+
+ # These are only used by the blimp team, which has entirely migrated to gn,
+ # so this logic is not replicated in the gyp file.
+ if (metrics_use_blimp) {
+ defines = [
+ "OVERRIDE_OS_NAME_TO_BLIMP",
+ "ENABLE_REPORTING_BLIMP",
+ ]
+ }
+}
+# TODO(GYP): metrics_chromeos
diff --git a/chromium/components/metrics/DEPS b/chromium/components/metrics/DEPS
new file mode 100644
index 00000000000..8960a59060b
--- /dev/null
+++ b/chromium/components/metrics/DEPS
@@ -0,0 +1,14 @@
+# This component is shared with the Chrome OS build, so it's important to limit
+# dependencies to a minimal set.
+include_rules = [
+ "-components",
+ "+components/compression",
+ "+components/metrics",
+ "+components/prefs",
+ "+components/variations",
+ "+components/version_info",
+ "+content/public/browser",
+ "+content/public/test",
+ "+third_party/zlib/google",
+ "-net",
+]
diff --git a/chromium/components/metrics/OWNERS b/chromium/components/metrics/OWNERS
new file mode 100644
index 00000000000..e17cf1464a7
--- /dev/null
+++ b/chromium/components/metrics/OWNERS
@@ -0,0 +1,6 @@
+asvitkine@chromium.org
+holte@chromium.org
+isherman@chromium.org
+jar@chromium.org
+mpearson@chromium.org
+rkaplow@chromium.org
diff --git a/chromium/components/metrics/README b/chromium/components/metrics/README
new file mode 100644
index 00000000000..5a2abbb2f69
--- /dev/null
+++ b/chromium/components/metrics/README
@@ -0,0 +1,23 @@
+This component contains the base classes for the metrics service and only
+depends on //base. It is used by ChromeOS as the base for a standalone service
+that will upload the metrics when ChromeOS is not installed (headless install).
+
+This is the first step towards the componentization of metrics that will happen
+later this spring.
+
+A proposed structure for the metrics component is:
+//components/metrics/base,
+ Depends on base only. Contains the protobuf definitions.
+//components/metrics/core
+ Depends on everything iOS depends on
+//components/metrics/content
+ Depends on content
+
+Ideally, the component would abstract the network stack and have a clean
+separation between the metrics upload logic (protbuf generation, retry, etc...),
+the chrome part (gathering histogram from all the threads, populating the
+log with hardware characteristics, plugin state, etc.).
+
+It is a plus if the code currently in the component (i.e., the code that can
+depend only on //base) stays in a single directory as it would be easier
+for ChromeOS to pull it :).
diff --git a/chromium/components/metrics/call_stack_profile_metrics_provider.cc b/chromium/components/metrics/call_stack_profile_metrics_provider.cc
new file mode 100644
index 00000000000..7ad942d3fca
--- /dev/null
+++ b/chromium/components/metrics/call_stack_profile_metrics_provider.cc
@@ -0,0 +1,396 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/call_stack_profile_metrics_provider.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <cstring>
+#include <map>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/profiler/stack_sampling_profiler.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+
+using base::StackSamplingProfiler;
+
+namespace metrics {
+
+namespace {
+
+// ProfilesState --------------------------------------------------------------
+
+// A set of profiles and the CallStackProfileMetricsProvider state associated
+// with them.
+struct ProfilesState {
+ ProfilesState(const CallStackProfileMetricsProvider::Params& params,
+ const base::StackSamplingProfiler::CallStackProfiles& profiles,
+ base::TimeTicks start_timestamp);
+
+ // The metrics-related parameters provided to
+ // CallStackProfileMetricsProvider::GetProfilerCallback().
+ CallStackProfileMetricsProvider::Params params;
+
+ // The call stack profiles collected by the profiler.
+ base::StackSamplingProfiler::CallStackProfiles profiles;
+
+ // The time at which the CallStackProfileMetricsProvider became aware of the
+ // request for profiling. In particular, this is when callback was requested
+ // via CallStackProfileMetricsProvider::GetProfilerCallback(). Used to
+ // determine if collection was disabled during the collection of the profile.
+ base::TimeTicks start_timestamp;
+};
+
+ProfilesState::ProfilesState(
+ const CallStackProfileMetricsProvider::Params& params,
+ const base::StackSamplingProfiler::CallStackProfiles& profiles,
+ base::TimeTicks start_timestamp)
+ : params(params),
+ profiles(profiles),
+ start_timestamp(start_timestamp) {
+}
+
+// PendingProfiles ------------------------------------------------------------
+
+// Singleton class responsible for retaining profiles received via the callback
+// created by CallStackProfileMetricsProvider::GetProfilerCallback(). These are
+// then sent to UMA on the invocation of
+// CallStackProfileMetricsProvider::ProvideGeneralMetrics(). We need to store
+// the profiles outside of a CallStackProfileMetricsProvider instance since
+// callers may start profiling before the CallStackProfileMetricsProvider is
+// created.
+//
+// Member functions on this class may be called on any thread.
+class PendingProfiles {
+ public:
+ static PendingProfiles* GetInstance();
+
+ void Clear();
+ void Swap(std::vector<ProfilesState>* profiles);
+
+ // Enables the collection of profiles by CollectProfilesIfCollectionEnabled if
+ // |enabled| is true. Otherwise, clears current profiles and ignores profiles
+ // provided to future invocations of CollectProfilesIfCollectionEnabled.
+ void SetCollectionEnabled(bool enabled);
+
+ // True if profiles are being collected.
+ bool IsCollectionEnabled() const;
+
+ // Adds |profile| to the list of profiles if collection is enabled.
+ void CollectProfilesIfCollectionEnabled(const ProfilesState& profiles);
+
+ // Allows testing against the initial state multiple times.
+ void ResetToDefaultStateForTesting();
+
+ private:
+ friend struct base::DefaultSingletonTraits<PendingProfiles>;
+
+ PendingProfiles();
+ ~PendingProfiles();
+
+ mutable base::Lock lock_;
+
+ // If true, profiles provided to CollectProfilesIfCollectionEnabled should be
+ // collected. Otherwise they will be ignored.
+ bool collection_enabled_;
+
+ // The last time collection was disabled. Used to determine if collection was
+ // disabled at any point since a profile was started.
+ base::TimeTicks last_collection_disable_time_;
+
+ // The set of completed profiles that should be reported.
+ std::vector<ProfilesState> profiles_;
+
+ DISALLOW_COPY_AND_ASSIGN(PendingProfiles);
+};
+
+// static
+PendingProfiles* PendingProfiles::GetInstance() {
+ // Leaky for performance rather than correctness reasons.
+ return base::Singleton<PendingProfiles,
+ base::LeakySingletonTraits<PendingProfiles>>::get();
+}
+
+void PendingProfiles::Clear() {
+ base::AutoLock scoped_lock(lock_);
+ profiles_.clear();
+}
+
+void PendingProfiles::Swap(std::vector<ProfilesState>* profiles) {
+ base::AutoLock scoped_lock(lock_);
+ profiles_.swap(*profiles);
+}
+
+void PendingProfiles::SetCollectionEnabled(bool enabled) {
+ base::AutoLock scoped_lock(lock_);
+
+ collection_enabled_ = enabled;
+
+ if (!collection_enabled_) {
+ profiles_.clear();
+ last_collection_disable_time_ = base::TimeTicks::Now();
+ }
+}
+
+bool PendingProfiles::IsCollectionEnabled() const {
+ base::AutoLock scoped_lock(lock_);
+ return collection_enabled_;
+}
+
+void PendingProfiles::CollectProfilesIfCollectionEnabled(
+ const ProfilesState& profiles) {
+ base::AutoLock scoped_lock(lock_);
+
+ // Only collect if collection is not disabled and hasn't been disabled
+ // since the start of collection for this profile.
+ if (!collection_enabled_ ||
+ (!last_collection_disable_time_.is_null() &&
+ last_collection_disable_time_ >= profiles.start_timestamp)) {
+ return;
+ }
+
+ profiles_.push_back(profiles);
+}
+
+void PendingProfiles::ResetToDefaultStateForTesting() {
+ base::AutoLock scoped_lock(lock_);
+
+ collection_enabled_ = true;
+ last_collection_disable_time_ = base::TimeTicks();
+ profiles_.clear();
+}
+
+// |collection_enabled_| is initialized to true to collect any profiles that are
+// generated prior to creation of the CallStackProfileMetricsProvider. The
+// ultimate disposition of these pre-creation collected profiles will be
+// determined by the initial recording state provided to
+// CallStackProfileMetricsProvider.
+PendingProfiles::PendingProfiles() : collection_enabled_(true) {}
+
+PendingProfiles::~PendingProfiles() {}
+
+// Functions to process completed profiles ------------------------------------
+
+// Invoked on the profiler's thread. Provides the profiles to PendingProfiles to
+// append, if the collecting state allows.
+void ReceiveCompletedProfiles(
+ const CallStackProfileMetricsProvider::Params& params,
+ base::TimeTicks start_timestamp,
+ const StackSamplingProfiler::CallStackProfiles& profiles) {
+ PendingProfiles::GetInstance()->CollectProfilesIfCollectionEnabled(
+ ProfilesState(params, profiles, start_timestamp));
+}
+
+// Invoked on an arbitrary thread. Ignores the provided profiles.
+void IgnoreCompletedProfiles(
+ const StackSamplingProfiler::CallStackProfiles& profiles) {
+}
+
+// Functions to encode protobufs ----------------------------------------------
+
+// The protobuf expects the MD5 checksum prefix of the module name.
+uint64_t HashModuleFilename(const base::FilePath& filename) {
+ const base::FilePath::StringType basename = filename.BaseName().value();
+ // Copy the bytes in basename into a string buffer.
+ size_t basename_length_in_bytes =
+ basename.size() * sizeof(base::FilePath::CharType);
+ std::string name_bytes(basename_length_in_bytes, '\0');
+ memcpy(&name_bytes[0], &basename[0], basename_length_in_bytes);
+ return base::HashMetricName(name_bytes);
+}
+
+// Transcode |sample| into |proto_sample|, using base addresses in |modules| to
+// compute module instruction pointer offsets.
+void CopySampleToProto(
+ const StackSamplingProfiler::Sample& sample,
+ const std::vector<StackSamplingProfiler::Module>& modules,
+ CallStackProfile::Sample* proto_sample) {
+ for (const StackSamplingProfiler::Frame& frame : sample) {
+ CallStackProfile::Entry* entry = proto_sample->add_entry();
+ // A frame may not have a valid module. If so, we can't compute the
+ // instruction pointer offset, and we don't want to send bare pointers, so
+ // leave call_stack_entry empty.
+ if (frame.module_index == StackSamplingProfiler::Frame::kUnknownModuleIndex)
+ continue;
+ int64_t module_offset =
+ reinterpret_cast<const char*>(frame.instruction_pointer) -
+ reinterpret_cast<const char*>(modules[frame.module_index].base_address);
+ DCHECK_GE(module_offset, 0);
+ entry->set_address(static_cast<uint64_t>(module_offset));
+ entry->set_module_id_index(frame.module_index);
+ }
+}
+
+// Transcode |profile| into |proto_profile|.
+void CopyProfileToProto(
+ const StackSamplingProfiler::CallStackProfile& profile,
+ bool preserve_sample_ordering,
+ CallStackProfile* proto_profile) {
+ if (profile.samples.empty())
+ return;
+
+ if (preserve_sample_ordering) {
+ // Collapse only consecutive repeated samples together.
+ CallStackProfile::Sample* current_sample_proto = nullptr;
+ for (auto it = profile.samples.begin(); it != profile.samples.end(); ++it) {
+ if (!current_sample_proto || *it != *(it - 1)) {
+ current_sample_proto = proto_profile->add_sample();
+ CopySampleToProto(*it, profile.modules, current_sample_proto);
+ current_sample_proto->set_count(1);
+ } else {
+ current_sample_proto->set_count(current_sample_proto->count() + 1);
+ }
+ }
+ } else {
+ // Collapse all repeated samples together.
+ std::map<StackSamplingProfiler::Sample, int> sample_index;
+ for (auto it = profile.samples.begin(); it != profile.samples.end(); ++it) {
+ auto location = sample_index.find(*it);
+ if (location == sample_index.end()) {
+ CallStackProfile::Sample* sample_proto = proto_profile->add_sample();
+ CopySampleToProto(*it, profile.modules, sample_proto);
+ sample_proto->set_count(1);
+ sample_index.insert(
+ std::make_pair(
+ *it, static_cast<int>(proto_profile->sample().size()) - 1));
+ } else {
+ CallStackProfile::Sample* sample_proto =
+ proto_profile->mutable_sample()->Mutable(location->second);
+ sample_proto->set_count(sample_proto->count() + 1);
+ }
+ }
+ }
+
+ for (const StackSamplingProfiler::Module& module : profile.modules) {
+ CallStackProfile::ModuleIdentifier* module_id =
+ proto_profile->add_module_id();
+ module_id->set_build_id(module.id);
+ module_id->set_name_md5_prefix(HashModuleFilename(module.filename));
+ }
+
+ proto_profile->set_profile_duration_ms(
+ profile.profile_duration.InMilliseconds());
+ proto_profile->set_sampling_period_ms(
+ profile.sampling_period.InMilliseconds());
+}
+
+// Translates CallStackProfileMetricsProvider's trigger to the corresponding
+// SampledProfile TriggerEvent.
+SampledProfile::TriggerEvent ToSampledProfileTriggerEvent(
+ CallStackProfileMetricsProvider::Trigger trigger) {
+ switch (trigger) {
+ case CallStackProfileMetricsProvider::UNKNOWN:
+ return SampledProfile::UNKNOWN_TRIGGER_EVENT;
+ break;
+ case CallStackProfileMetricsProvider::PROCESS_STARTUP:
+ return SampledProfile::PROCESS_STARTUP;
+ break;
+ case CallStackProfileMetricsProvider::JANKY_TASK:
+ return SampledProfile::JANKY_TASK;
+ break;
+ case CallStackProfileMetricsProvider::THREAD_HUNG:
+ return SampledProfile::THREAD_HUNG;
+ break;
+ }
+ NOTREACHED();
+ return SampledProfile::UNKNOWN_TRIGGER_EVENT;
+}
+
+} // namespace
+
+// CallStackProfileMetricsProvider::Params ------------------------------------
+
+CallStackProfileMetricsProvider::Params::Params(
+ CallStackProfileMetricsProvider::Trigger trigger)
+ : Params(trigger, false) {
+}
+
+CallStackProfileMetricsProvider::Params::Params(
+ CallStackProfileMetricsProvider::Trigger trigger,
+ bool preserve_sample_ordering)
+ : trigger(trigger),
+ preserve_sample_ordering(preserve_sample_ordering) {
+}
+
+// CallStackProfileMetricsProvider --------------------------------------------
+
+const char CallStackProfileMetricsProvider::kFieldTrialName[] =
+ "StackProfiling";
+const char CallStackProfileMetricsProvider::kReportProfilesGroupName[] =
+ "Report profiles";
+
+CallStackProfileMetricsProvider::CallStackProfileMetricsProvider() {
+}
+
+CallStackProfileMetricsProvider::~CallStackProfileMetricsProvider() {
+}
+
+// This function can be invoked on an abitrary thread.
+base::StackSamplingProfiler::CompletedCallback
+CallStackProfileMetricsProvider::GetProfilerCallback(const Params& params) {
+ // Ignore the profiles if the collection is disabled. If the collection state
+ // changes while collecting, this will be detected by the callback and
+ // profiles will be ignored at that point.
+ if (!PendingProfiles::GetInstance()->IsCollectionEnabled())
+ return base::Bind(&IgnoreCompletedProfiles);
+
+ return base::Bind(&ReceiveCompletedProfiles, params, base::TimeTicks::Now());
+}
+
+void CallStackProfileMetricsProvider::OnRecordingEnabled() {
+ PendingProfiles::GetInstance()->SetCollectionEnabled(true);
+}
+
+void CallStackProfileMetricsProvider::OnRecordingDisabled() {
+ PendingProfiles::GetInstance()->SetCollectionEnabled(false);
+}
+
+void CallStackProfileMetricsProvider::ProvideGeneralMetrics(
+ ChromeUserMetricsExtension* uma_proto) {
+ std::vector<ProfilesState> pending_profiles;
+ PendingProfiles::GetInstance()->Swap(&pending_profiles);
+
+ DCHECK(IsReportingEnabledByFieldTrial() || pending_profiles.empty());
+
+ for (const ProfilesState& profiles_state : pending_profiles) {
+ for (const StackSamplingProfiler::CallStackProfile& profile :
+ profiles_state.profiles) {
+ SampledProfile* sampled_profile = uma_proto->add_sampled_profile();
+ sampled_profile->set_trigger_event(ToSampledProfileTriggerEvent(
+ profiles_state.params.trigger));
+ CopyProfileToProto(profile,
+ profiles_state.params.preserve_sample_ordering,
+ sampled_profile->mutable_call_stack_profile());
+ }
+ }
+}
+
+// static
+void CallStackProfileMetricsProvider::ResetStaticStateForTesting() {
+ PendingProfiles::GetInstance()->ResetToDefaultStateForTesting();
+}
+
+// static
+bool CallStackProfileMetricsProvider::IsReportingEnabledByFieldTrial() {
+ const std::string group_name = base::FieldTrialList::FindFullName(
+ CallStackProfileMetricsProvider::kFieldTrialName);
+ return group_name ==
+ CallStackProfileMetricsProvider::kReportProfilesGroupName;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/call_stack_profile_metrics_provider.h b/chromium/components/metrics/call_stack_profile_metrics_provider.h
new file mode 100644
index 00000000000..746b82928ed
--- /dev/null
+++ b/chromium/components/metrics/call_stack_profile_metrics_provider.h
@@ -0,0 +1,83 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_CALL_STACK_PROFILE_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_CALL_STACK_PROFILE_METRICS_PROVIDER_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/profiler/stack_sampling_profiler.h"
+#include "components/metrics/metrics_provider.h"
+
+namespace metrics {
+class ChromeUserMetricsExtension;
+
+// Performs metrics logging for the stack sampling profiler.
+class CallStackProfileMetricsProvider : public MetricsProvider {
+ public:
+ // The event that triggered the profile collection.
+ // This enum should be kept in sync with content/common/profiled_stack_state.h
+ enum Trigger {
+ UNKNOWN,
+ PROCESS_STARTUP,
+ JANKY_TASK,
+ THREAD_HUNG,
+ TRIGGER_LAST = THREAD_HUNG
+ };
+
+ // Parameters to pass back to the metrics provider.
+ struct Params {
+ explicit Params(Trigger trigger);
+ Params(Trigger trigger, bool preserve_sample_ordering);
+
+ // The triggering event.
+ Trigger trigger;
+
+ // True if sample ordering is important and should be preserved when the
+ // associated profiles are compressed. This should only be set to true if
+ // the intended use of the requires that the sequence of call stacks within
+ // a particular profile be preserved. The default value of false provides
+ // better compression of the encoded profile and is sufficient for the
+ // typical use case of recording profiles for stack frequency analysis in
+ // aggregate.
+ bool preserve_sample_ordering;
+ };
+
+ CallStackProfileMetricsProvider();
+ ~CallStackProfileMetricsProvider() override;
+
+ // Get a callback for use with StackSamplingProfiler that provides completed
+ // profiles to this object. The callback should be immediately passed to the
+ // StackSamplingProfiler, and should not be reused between
+ // StackSamplingProfilers. This function may be called on any thread.
+ static base::StackSamplingProfiler::CompletedCallback GetProfilerCallback(
+ const Params& params);
+
+ // MetricsProvider:
+ void OnRecordingEnabled() override;
+ void OnRecordingDisabled() override;
+ void ProvideGeneralMetrics(ChromeUserMetricsExtension* uma_proto) override;
+
+ protected:
+ // Finch field trial and group for reporting profiles. Provided here for test
+ // use.
+ static const char kFieldTrialName[];
+ static const char kReportProfilesGroupName[];
+
+ // Reset the static state to the defaults after startup.
+ static void ResetStaticStateForTesting();
+
+ private:
+ // Returns true if reporting of profiles is enabled according to the
+ // controlling Finch field trial.
+ static bool IsReportingEnabledByFieldTrial();
+
+ DISALLOW_COPY_AND_ASSIGN(CallStackProfileMetricsProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_CALL_STACK_PROFILE_METRICS_PROVIDER_H_
diff --git a/chromium/components/metrics/call_stack_profile_metrics_provider_unittest.cc b/chromium/components/metrics/call_stack_profile_metrics_provider_unittest.cc
new file mode 100644
index 00000000000..e361137e168
--- /dev/null
+++ b/chromium/components/metrics/call_stack_profile_metrics_provider_unittest.cc
@@ -0,0 +1,619 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/call_stack_profile_metrics_provider.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "base/metrics/field_trial.h"
+#include "base/profiler/stack_sampling_profiler.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "build/build_config.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+#include "components/variations/entropy_provider.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StackSamplingProfiler;
+using Frame = StackSamplingProfiler::Frame;
+using Module = StackSamplingProfiler::Module;
+using Profile = StackSamplingProfiler::CallStackProfile;
+using Profiles = StackSamplingProfiler::CallStackProfiles;
+using Sample = StackSamplingProfiler::Sample;
+
+namespace metrics {
+
+using Params = CallStackProfileMetricsProvider::Params;
+
+// This test fixture enables the field trial that
+// CallStackProfileMetricsProvider depends on to report profiles.
+class CallStackProfileMetricsProviderTest : public testing::Test {
+ public:
+ CallStackProfileMetricsProviderTest()
+ : field_trial_list_(nullptr) {
+ base::FieldTrialList::CreateFieldTrial(
+ TestState::kFieldTrialName,
+ TestState::kReportProfilesGroupName);
+ TestState::ResetStaticStateForTesting();
+ }
+
+ ~CallStackProfileMetricsProviderTest() override {}
+
+ // Utility function to append profiles to the metrics provider.
+ void AppendProfiles(const Params& params, const Profiles& profiles) {
+ CallStackProfileMetricsProvider::GetProfilerCallback(params).Run(profiles);
+ }
+
+ private:
+ // Exposes field trial/group names from the CallStackProfileMetricsProvider.
+ class TestState : public CallStackProfileMetricsProvider {
+ public:
+ using CallStackProfileMetricsProvider::kFieldTrialName;
+ using CallStackProfileMetricsProvider::kReportProfilesGroupName;
+ using CallStackProfileMetricsProvider::ResetStaticStateForTesting;
+ };
+
+ base::FieldTrialList field_trial_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallStackProfileMetricsProviderTest);
+};
+
+// Checks that all properties from multiple profiles are filled as expected.
+TEST_F(CallStackProfileMetricsProviderTest, MultipleProfiles) {
+ const uintptr_t module1_base_address = 0x1000;
+ const uintptr_t module2_base_address = 0x2000;
+ const uintptr_t module3_base_address = 0x3000;
+
+ const Module profile_modules[][2] = {
+ {
+ Module(
+ module1_base_address,
+ "ABCD",
+#if defined(OS_WIN)
+ base::FilePath(L"c:\\some\\path\\to\\chrome.exe")
+#else
+ base::FilePath("/some/path/to/chrome")
+#endif
+ ),
+ Module(
+ module2_base_address,
+ "EFGH",
+#if defined(OS_WIN)
+ base::FilePath(L"c:\\some\\path\\to\\third_party.dll")
+#else
+ base::FilePath("/some/path/to/third_party.so")
+#endif
+ ),
+ },
+ {
+ Module(
+ module3_base_address,
+ "MNOP",
+#if defined(OS_WIN)
+ base::FilePath(L"c:\\some\\path\\to\\third_party2.dll")
+#else
+ base::FilePath("/some/path/to/third_party2.so")
+#endif
+ ),
+ Module( // Repeated from the first profile.
+ module1_base_address,
+ "ABCD",
+#if defined(OS_WIN)
+ base::FilePath(L"c:\\some\\path\\to\\chrome.exe")
+#else
+ base::FilePath("/some/path/to/chrome")
+#endif
+ )
+ }
+ };
+
+ // Values for Windows generated with:
+ // perl -MDigest::MD5=md5 -MEncode=encode
+ // -e 'for(@ARGV){printf "%x\n", unpack "Q>", md5 encode "UTF-16LE", $_}'
+ // chrome.exe third_party.dll third_party2.dll
+ //
+ // Values for Linux generated with:
+ // perl -MDigest::MD5=md5
+ // -e 'for(@ARGV){printf "%x\n", unpack "Q>", md5 $_}'
+ // chrome third_party.so third_party2.so
+ const uint64_t profile_expected_name_md5_prefixes[][2] = {
+ {
+#if defined(OS_WIN)
+ 0x46c3e4166659ac02ULL, 0x7e2b8bfddeae1abaULL
+#else
+ 0x554838a8451ac36cUL, 0x843661148659c9f8UL
+#endif
+ },
+ {
+#if defined(OS_WIN)
+ 0x87b66f4573a4d5caULL, 0x46c3e4166659ac02ULL
+#else
+ 0xb4647e539fa6ec9eUL, 0x554838a8451ac36cUL
+#endif
+ }};
+
+ // Represents two stack samples for each of two profiles, where each stack
+ // contains three frames. Each frame contains an instruction pointer and a
+ // module index corresponding to the module for the profile in
+ // profile_modules.
+ //
+ // So, the first stack sample below has its top frame in module 0 at an offset
+ // of 0x10 from the module's base address, the next-to-top frame in module 1
+ // at an offset of 0x20 from the module's base address, and the bottom frame
+ // in module 0 at an offset of 0x30 from the module's base address
+ const Frame profile_sample_frames[][2][3] = {
+ {
+ {
+ Frame(module1_base_address + 0x10, 0),
+ Frame(module2_base_address + 0x20, 1),
+ Frame(module1_base_address + 0x30, 0)
+ },
+ {
+ Frame(module2_base_address + 0x10, 1),
+ Frame(module1_base_address + 0x20, 0),
+ Frame(module2_base_address + 0x30, 1)
+ }
+ },
+ {
+ {
+ Frame(module3_base_address + 0x10, 0),
+ Frame(module1_base_address + 0x20, 1),
+ Frame(module3_base_address + 0x30, 0)
+ },
+ {
+ Frame(module1_base_address + 0x10, 1),
+ Frame(module3_base_address + 0x20, 0),
+ Frame(module1_base_address + 0x30, 1)
+ }
+ }
+ };
+
+ base::TimeDelta profile_durations[2] = {
+ base::TimeDelta::FromMilliseconds(100),
+ base::TimeDelta::FromMilliseconds(200)
+ };
+
+ base::TimeDelta profile_sampling_periods[2] = {
+ base::TimeDelta::FromMilliseconds(10),
+ base::TimeDelta::FromMilliseconds(20)
+ };
+
+ std::vector<Profile> profiles;
+ for (size_t i = 0; i < arraysize(profile_sample_frames); ++i) {
+ Profile profile;
+ profile.modules.insert(
+ profile.modules.end(), &profile_modules[i][0],
+ &profile_modules[i][0] + arraysize(profile_modules[i]));
+
+ for (size_t j = 0; j < arraysize(profile_sample_frames[i]); ++j) {
+ profile.samples.push_back(Sample());
+ Sample& sample = profile.samples.back();
+ sample.insert(sample.end(), &profile_sample_frames[i][j][0],
+ &profile_sample_frames[i][j][0] +
+ arraysize(profile_sample_frames[i][j]));
+ }
+
+ profile.profile_duration = profile_durations[i];
+ profile.sampling_period = profile_sampling_periods[i];
+
+ profiles.push_back(profile);
+ }
+
+ CallStackProfileMetricsProvider provider;
+ provider.OnRecordingEnabled();
+ AppendProfiles(
+ Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false),
+ profiles);
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ ASSERT_EQ(static_cast<int>(arraysize(profile_sample_frames)),
+ uma_proto.sampled_profile().size());
+ for (size_t i = 0; i < arraysize(profile_sample_frames); ++i) {
+ SCOPED_TRACE("profile " + base::SizeTToString(i));
+ const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(i);
+ ASSERT_TRUE(sampled_profile.has_call_stack_profile());
+ const CallStackProfile& call_stack_profile =
+ sampled_profile.call_stack_profile();
+
+ ASSERT_EQ(static_cast<int>(arraysize(profile_sample_frames[i])),
+ call_stack_profile.sample().size());
+ for (size_t j = 0; j < arraysize(profile_sample_frames[i]); ++j) {
+ SCOPED_TRACE("sample " + base::SizeTToString(j));
+ const CallStackProfile::Sample& proto_sample =
+ call_stack_profile.sample().Get(j);
+ ASSERT_EQ(static_cast<int>(arraysize(profile_sample_frames[i][j])),
+ proto_sample.entry().size());
+ ASSERT_TRUE(proto_sample.has_count());
+ EXPECT_EQ(1u, proto_sample.count());
+ for (size_t k = 0; k < arraysize(profile_sample_frames[i][j]); ++k) {
+ SCOPED_TRACE("frame " + base::SizeTToString(k));
+ const CallStackProfile::Entry& entry = proto_sample.entry().Get(k);
+ ASSERT_TRUE(entry.has_address());
+ const char* instruction_pointer = reinterpret_cast<const char*>(
+ profile_sample_frames[i][j][k].instruction_pointer);
+ const char* module_base_address = reinterpret_cast<const char*>(
+ profile_modules[i][profile_sample_frames[i][j][k].module_index]
+ .base_address);
+ EXPECT_EQ(
+ static_cast<uint64_t>(instruction_pointer - module_base_address),
+ entry.address());
+ ASSERT_TRUE(entry.has_module_id_index());
+ EXPECT_EQ(profile_sample_frames[i][j][k].module_index,
+ static_cast<size_t>(entry.module_id_index()));
+ }
+ }
+
+ ASSERT_EQ(static_cast<int>(arraysize(profile_modules[i])),
+ call_stack_profile.module_id().size());
+ for (size_t j = 0; j < arraysize(profile_modules[i]); ++j) {
+ SCOPED_TRACE("module " + base::SizeTToString(j));
+ const CallStackProfile::ModuleIdentifier& module_identifier =
+ call_stack_profile.module_id().Get(j);
+ ASSERT_TRUE(module_identifier.has_build_id());
+ EXPECT_EQ(profile_modules[i][j].id, module_identifier.build_id());
+ ASSERT_TRUE(module_identifier.has_name_md5_prefix());
+ EXPECT_EQ(profile_expected_name_md5_prefixes[i][j],
+ module_identifier.name_md5_prefix());
+ }
+
+ ASSERT_TRUE(call_stack_profile.has_profile_duration_ms());
+ EXPECT_EQ(profile_durations[i].InMilliseconds(),
+ call_stack_profile.profile_duration_ms());
+ ASSERT_TRUE(call_stack_profile.has_sampling_period_ms());
+ EXPECT_EQ(profile_sampling_periods[i].InMilliseconds(),
+ call_stack_profile.sampling_period_ms());
+ ASSERT_TRUE(sampled_profile.has_trigger_event());
+ EXPECT_EQ(SampledProfile::PROCESS_STARTUP, sampled_profile.trigger_event());
+ }
+}
+
+// Checks that all duplicate samples are collapsed with
+// preserve_sample_ordering = false.
+TEST_F(CallStackProfileMetricsProviderTest, RepeatedStacksUnordered) {
+ const uintptr_t module_base_address = 0x1000;
+
+ const Module modules[] = {
+ Module(
+ module_base_address,
+ "ABCD",
+#if defined(OS_WIN)
+ base::FilePath(L"c:\\some\\path\\to\\chrome.exe")
+#else
+ base::FilePath("/some/path/to/chrome")
+#endif
+ )
+ };
+
+ // Duplicate samples in slots 0, 2, and 3.
+ const Frame sample_frames[][1] = {
+ { Frame(module_base_address + 0x10, 0), },
+ { Frame(module_base_address + 0x20, 0), },
+ { Frame(module_base_address + 0x10, 0), },
+ { Frame(module_base_address + 0x10, 0) }
+ };
+
+ Profile profile;
+ profile.modules.insert(profile.modules.end(), &modules[0],
+ &modules[0] + arraysize(modules));
+
+ for (size_t i = 0; i < arraysize(sample_frames); ++i) {
+ profile.samples.push_back(Sample());
+ Sample& sample = profile.samples.back();
+ sample.insert(sample.end(), &sample_frames[i][0],
+ &sample_frames[i][0] + arraysize(sample_frames[i]));
+ }
+
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
+
+ CallStackProfileMetricsProvider provider;
+ provider.OnRecordingEnabled();
+ AppendProfiles(
+ Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false),
+ std::vector<Profile>(1, profile));
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ ASSERT_EQ(1, uma_proto.sampled_profile().size());
+ const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(0);
+ ASSERT_TRUE(sampled_profile.has_call_stack_profile());
+ const CallStackProfile& call_stack_profile =
+ sampled_profile.call_stack_profile();
+
+ ASSERT_EQ(2, call_stack_profile.sample().size());
+ for (int i = 0; i < 2; ++i) {
+ SCOPED_TRACE("sample " + base::IntToString(i));
+ const CallStackProfile::Sample& proto_sample =
+ call_stack_profile.sample().Get(i);
+ ASSERT_EQ(static_cast<int>(arraysize(sample_frames[i])),
+ proto_sample.entry().size());
+ ASSERT_TRUE(proto_sample.has_count());
+ EXPECT_EQ(i == 0 ? 3u : 1u, proto_sample.count());
+ for (size_t j = 0; j < arraysize(sample_frames[i]); ++j) {
+ SCOPED_TRACE("frame " + base::SizeTToString(j));
+ const CallStackProfile::Entry& entry = proto_sample.entry().Get(j);
+ ASSERT_TRUE(entry.has_address());
+ const char* instruction_pointer = reinterpret_cast<const char*>(
+ sample_frames[i][j].instruction_pointer);
+ const char* module_base_address = reinterpret_cast<const char*>(
+ modules[sample_frames[i][j].module_index].base_address);
+ EXPECT_EQ(
+ static_cast<uint64_t>(instruction_pointer - module_base_address),
+ entry.address());
+ ASSERT_TRUE(entry.has_module_id_index());
+ EXPECT_EQ(sample_frames[i][j].module_index,
+ static_cast<size_t>(entry.module_id_index()));
+ }
+ }
+}
+
+// Checks that only contiguous duplicate samples are collapsed with
+// preserve_sample_ordering = true.
+TEST_F(CallStackProfileMetricsProviderTest, RepeatedStacksOrdered) {
+ const uintptr_t module_base_address = 0x1000;
+
+ const Module modules[] = {
+ Module(
+ module_base_address,
+ "ABCD",
+#if defined(OS_WIN)
+ base::FilePath(L"c:\\some\\path\\to\\chrome.exe")
+#else
+ base::FilePath("/some/path/to/chrome")
+#endif
+ )
+ };
+
+ // Duplicate samples in slots 0, 2, and 3.
+ const Frame sample_frames[][1] = {
+ { Frame(module_base_address + 0x10, 0), },
+ { Frame(module_base_address + 0x20, 0), },
+ { Frame(module_base_address + 0x10, 0), },
+ { Frame(module_base_address + 0x10, 0) }
+ };
+
+ Profile profile;
+ profile.modules.insert(profile.modules.end(), &modules[0],
+ &modules[0] + arraysize(modules));
+
+ for (size_t i = 0; i < arraysize(sample_frames); ++i) {
+ profile.samples.push_back(Sample());
+ Sample& sample = profile.samples.back();
+ sample.insert(sample.end(), &sample_frames[i][0],
+ &sample_frames[i][0] + arraysize(sample_frames[i]));
+ }
+
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
+
+ CallStackProfileMetricsProvider provider;
+ provider.OnRecordingEnabled();
+ AppendProfiles(Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, true),
+ std::vector<Profile>(1, profile));
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ ASSERT_EQ(1, uma_proto.sampled_profile().size());
+ const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(0);
+ ASSERT_TRUE(sampled_profile.has_call_stack_profile());
+ const CallStackProfile& call_stack_profile =
+ sampled_profile.call_stack_profile();
+
+ ASSERT_EQ(3, call_stack_profile.sample().size());
+ for (int i = 0; i < 3; ++i) {
+ SCOPED_TRACE("sample " + base::IntToString(i));
+ const CallStackProfile::Sample& proto_sample =
+ call_stack_profile.sample().Get(i);
+ ASSERT_EQ(static_cast<int>(arraysize(sample_frames[i])),
+ proto_sample.entry().size());
+ ASSERT_TRUE(proto_sample.has_count());
+ EXPECT_EQ(i == 2 ? 2u : 1u, proto_sample.count());
+ for (size_t j = 0; j < arraysize(sample_frames[i]); ++j) {
+ SCOPED_TRACE("frame " + base::SizeTToString(j));
+ const CallStackProfile::Entry& entry = proto_sample.entry().Get(j);
+ ASSERT_TRUE(entry.has_address());
+ const char* instruction_pointer = reinterpret_cast<const char*>(
+ sample_frames[i][j].instruction_pointer);
+ const char* module_base_address = reinterpret_cast<const char*>(
+ modules[sample_frames[i][j].module_index].base_address);
+ EXPECT_EQ(
+ static_cast<uint64_t>(instruction_pointer - module_base_address),
+ entry.address());
+ ASSERT_TRUE(entry.has_module_id_index());
+ EXPECT_EQ(sample_frames[i][j].module_index,
+ static_cast<size_t>(entry.module_id_index()));
+ }
+ }
+}
+
+// Checks that unknown modules produce an empty Entry.
+TEST_F(CallStackProfileMetricsProviderTest, UnknownModule) {
+ const Frame frame(0x1000, Frame::kUnknownModuleIndex);
+
+ Profile profile;
+
+ profile.samples.push_back(Sample(1, frame));
+
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
+
+ CallStackProfileMetricsProvider provider;
+ provider.OnRecordingEnabled();
+ AppendProfiles(
+ Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false),
+ std::vector<Profile>(1, profile));
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ ASSERT_EQ(1, uma_proto.sampled_profile().size());
+ const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(0);
+ ASSERT_TRUE(sampled_profile.has_call_stack_profile());
+ const CallStackProfile& call_stack_profile =
+ sampled_profile.call_stack_profile();
+
+ ASSERT_EQ(1, call_stack_profile.sample().size());
+ const CallStackProfile::Sample& proto_sample =
+ call_stack_profile.sample().Get(0);
+ ASSERT_EQ(1, proto_sample.entry().size());
+ ASSERT_TRUE(proto_sample.has_count());
+ EXPECT_EQ(1u, proto_sample.count());
+ const CallStackProfile::Entry& entry = proto_sample.entry().Get(0);
+ EXPECT_FALSE(entry.has_address());
+ EXPECT_FALSE(entry.has_module_id_index());
+}
+
+// Checks that pending profiles are only passed back to ProvideGeneralMetrics
+// once.
+TEST_F(CallStackProfileMetricsProviderTest, ProfilesProvidedOnlyOnce) {
+ CallStackProfileMetricsProvider provider;
+ for (int i = 0; i < 2; ++i) {
+ Profile profile;
+ profile.samples.push_back(
+ Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex)));
+
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
+ // Use the sampling period to distinguish the two profiles.
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(i);
+
+ provider.OnRecordingEnabled();
+ AppendProfiles(
+ Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false),
+ std::vector<Profile>(1, profile));
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ ASSERT_EQ(1, uma_proto.sampled_profile().size());
+ const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(0);
+ ASSERT_TRUE(sampled_profile.has_call_stack_profile());
+ const CallStackProfile& call_stack_profile =
+ sampled_profile.call_stack_profile();
+ ASSERT_TRUE(call_stack_profile.has_sampling_period_ms());
+ EXPECT_EQ(i, call_stack_profile.sampling_period_ms());
+ }
+}
+
+// Checks that pending profiles are provided to ProvideGeneralMetrics
+// when collected before CallStackProfileMetricsProvider is instantiated.
+TEST_F(CallStackProfileMetricsProviderTest,
+ ProfilesProvidedWhenCollectedBeforeInstantiation) {
+ Profile profile;
+ profile.samples.push_back(
+ Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex)));
+
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
+
+ AppendProfiles(
+ Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false),
+ std::vector<Profile>(1, profile));
+
+ CallStackProfileMetricsProvider provider;
+ provider.OnRecordingEnabled();
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ EXPECT_EQ(1, uma_proto.sampled_profile_size());
+}
+
+// Checks that pending profiles are not provided to ProvideGeneralMetrics
+// while recording is disabled.
+TEST_F(CallStackProfileMetricsProviderTest, ProfilesNotProvidedWhileDisabled) {
+ Profile profile;
+ profile.samples.push_back(
+ Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex)));
+
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
+
+ CallStackProfileMetricsProvider provider;
+ provider.OnRecordingDisabled();
+ AppendProfiles(
+ Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false),
+ std::vector<Profile>(1, profile));
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ EXPECT_EQ(0, uma_proto.sampled_profile_size());
+}
+
+// Checks that pending profiles are not provided to ProvideGeneralMetrics
+// if recording is disabled while profiling.
+TEST_F(CallStackProfileMetricsProviderTest,
+ ProfilesNotProvidedAfterChangeToDisabled) {
+ Profile profile;
+ profile.samples.push_back(
+ Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex)));
+
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
+
+ CallStackProfileMetricsProvider provider;
+ provider.OnRecordingEnabled();
+ base::StackSamplingProfiler::CompletedCallback callback =
+ CallStackProfileMetricsProvider::GetProfilerCallback(
+ Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false));
+
+ provider.OnRecordingDisabled();
+ callback.Run(std::vector<Profile>(1, profile));
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ EXPECT_EQ(0, uma_proto.sampled_profile_size());
+}
+
+// Checks that pending profiles are not provided to ProvideGeneralMetrics if
+// recording is enabled, but then disabled and reenabled while profiling.
+TEST_F(CallStackProfileMetricsProviderTest,
+ ProfilesNotProvidedAfterChangeToDisabledThenEnabled) {
+ Profile profile;
+ profile.samples.push_back(
+ Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex)));
+
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
+
+ CallStackProfileMetricsProvider provider;
+ provider.OnRecordingEnabled();
+ base::StackSamplingProfiler::CompletedCallback callback =
+ CallStackProfileMetricsProvider::GetProfilerCallback(
+ Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false));
+
+ provider.OnRecordingDisabled();
+ provider.OnRecordingEnabled();
+ callback.Run(std::vector<Profile>(1, profile));
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ EXPECT_EQ(0, uma_proto.sampled_profile_size());
+}
+
+// Checks that pending profiles are not provided to ProvideGeneralMetrics
+// if recording is disabled, but then enabled while profiling.
+TEST_F(CallStackProfileMetricsProviderTest,
+ ProfilesNotProvidedAfterChangeFromDisabled) {
+ Profile profile;
+ profile.samples.push_back(
+ Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex)));
+
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
+
+ CallStackProfileMetricsProvider provider;
+ provider.OnRecordingDisabled();
+ base::StackSamplingProfiler::CompletedCallback callback =
+ CallStackProfileMetricsProvider::GetProfilerCallback(
+ Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false));
+
+ provider.OnRecordingEnabled();
+ callback.Run(std::vector<Profile>(1, profile));
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ EXPECT_EQ(0, uma_proto.sampled_profile_size());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/clean_exit_beacon.cc b/chromium/components/metrics/clean_exit_beacon.cc
new file mode 100644
index 00000000000..f90f2d00626
--- /dev/null
+++ b/chromium/components/metrics/clean_exit_beacon.cc
@@ -0,0 +1,80 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/clean_exit_beacon.h"
+
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/pref_service.h"
+
+#if defined(OS_WIN)
+#include "base/metrics/histogram.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/registry.h"
+#endif
+
+namespace metrics {
+
+CleanExitBeacon::CleanExitBeacon(const base::string16& backup_registry_key,
+ PrefService* local_state)
+ : local_state_(local_state),
+ initial_value_(local_state->GetBoolean(prefs::kStabilityExitedCleanly)),
+ backup_registry_key_(backup_registry_key) {
+ DCHECK_NE(PrefService::INITIALIZATION_STATUS_WAITING,
+ local_state_->GetInitializationStatus());
+
+#if defined(OS_WIN)
+ // An enumeration of all possible permutations of the the beacon state in the
+ // registry and in Local State.
+ enum {
+ DIRTY_DIRTY,
+ DIRTY_CLEAN,
+ CLEAN_DIRTY,
+ CLEAN_CLEAN,
+ MISSING_DIRTY,
+ MISSING_CLEAN,
+ NUM_CONSISTENCY_ENUMS
+ } consistency = DIRTY_DIRTY;
+
+ base::win::RegKey regkey;
+ DWORD value = 0u;
+ if (regkey.Open(HKEY_CURRENT_USER,
+ backup_registry_key_.c_str(),
+ KEY_ALL_ACCESS) == ERROR_SUCCESS &&
+ regkey.ReadValueDW(
+ base::ASCIIToUTF16(prefs::kStabilityExitedCleanly).c_str(), &value) ==
+ ERROR_SUCCESS) {
+ if (value)
+ consistency = initial_value_ ? CLEAN_CLEAN : CLEAN_DIRTY;
+ else
+ consistency = initial_value_ ? DIRTY_CLEAN : DIRTY_DIRTY;
+ } else {
+ consistency = initial_value_ ? MISSING_CLEAN : MISSING_DIRTY;
+ }
+
+ UMA_HISTOGRAM_ENUMERATION(
+ "UMA.CleanExitBeaconConsistency", consistency, NUM_CONSISTENCY_ENUMS);
+#endif
+}
+
+CleanExitBeacon::~CleanExitBeacon() {
+}
+
+void CleanExitBeacon::WriteBeaconValue(bool value) {
+ local_state_->SetBoolean(prefs::kStabilityExitedCleanly, value);
+
+#if defined(OS_WIN)
+ base::win::RegKey regkey;
+ if (regkey.Create(HKEY_CURRENT_USER,
+ backup_registry_key_.c_str(),
+ KEY_ALL_ACCESS) == ERROR_SUCCESS) {
+ regkey.WriteValue(
+ base::ASCIIToUTF16(prefs::kStabilityExitedCleanly).c_str(),
+ value ? 1u : 0u);
+ }
+#endif
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/clean_exit_beacon.h b/chromium/components/metrics/clean_exit_beacon.h
new file mode 100644
index 00000000000..6b896fa1784
--- /dev/null
+++ b/chromium/components/metrics/clean_exit_beacon.h
@@ -0,0 +1,45 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_CLEAN_EXIT_BEACON_H_
+#define COMPONENTS_METRICS_CLEAN_EXIT_BEACON_H_
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+
+class PrefService;
+
+namespace metrics {
+
+// Reads and updates a beacon used to detect whether the previous browser
+// process exited cleanly.
+class CleanExitBeacon {
+ public:
+ // Instantiates a CleanExitBeacon whose value is stored in |local_state|.
+ // |local_state| must be fully initialized.
+ // On Windows, |backup_registry_key| is used to store a backup of the beacon.
+ // It is ignored on other platforms.
+ CleanExitBeacon(
+ const base::string16& backup_registry_key,
+ PrefService* local_state);
+
+ ~CleanExitBeacon();
+
+ // Returns the original value of the beacon.
+ bool exited_cleanly() const { return initial_value_; }
+
+ // Writes the provided beacon value.
+ void WriteBeaconValue(bool exited_cleanly);
+
+ private:
+ PrefService* const local_state_;
+ const bool initial_value_;
+ const base::string16 backup_registry_key_;
+
+ DISALLOW_COPY_AND_ASSIGN(CleanExitBeacon);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_CLEAN_EXIT_BEACON_H_
diff --git a/chromium/components/metrics/client_info.cc b/chromium/components/metrics/client_info.cc
new file mode 100644
index 00000000000..57ad3947c1b
--- /dev/null
+++ b/chromium/components/metrics/client_info.cc
@@ -0,0 +1,13 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/client_info.h"
+
+namespace metrics {
+
+ClientInfo::ClientInfo() : installation_date(0), reporting_enabled_date(0) {}
+
+ClientInfo::~ClientInfo() {}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/client_info.h b/chromium/components/metrics/client_info.h
new file mode 100644
index 00000000000..7dcf5d8de39
--- /dev/null
+++ b/chromium/components/metrics/client_info.h
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_CLIENT_INFO_H_
+#define COMPONENTS_METRICS_CLIENT_INFO_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/macros.h"
+
+namespace metrics {
+
+// A data object used to pass data from outside the metrics component into the
+// metrics component.
+struct ClientInfo {
+ public:
+ ClientInfo();
+ ~ClientInfo();
+
+ // The metrics ID of this client: represented as a GUID string.
+ std::string client_id;
+
+ // The installation date: represented as an epoch time in seconds.
+ int64_t installation_date;
+
+ // The date on which metrics reporting was enabled: represented as an epoch
+ // time in seconds.
+ int64_t reporting_enabled_date;
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_CLIENT_INFO_H_
diff --git a/chromium/components/metrics/cloned_install_detector.cc b/chromium/components/metrics/cloned_install_detector.cc
new file mode 100644
index 00000000000..7e9be8c3f10
--- /dev/null
+++ b/chromium/components/metrics/cloned_install_detector.cc
@@ -0,0 +1,101 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/cloned_install_detector.h"
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task_runner_util.h"
+#include "components/metrics/machine_id_provider.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+namespace metrics {
+
+namespace {
+
+uint32_t HashRawId(const std::string& value) {
+ uint64_t hash = base::HashMetricName(value);
+
+ // Only use 24 bits from the 64-bit hash.
+ return hash & ((1 << 24) - 1);
+}
+
+// State of the generated machine id in relation to the previously stored value.
+// Note: UMA histogram enum - don't re-order or remove entries
+enum MachineIdState {
+ ID_GENERATION_FAILED,
+ ID_NO_STORED_VALUE,
+ ID_CHANGED,
+ ID_UNCHANGED,
+ ID_ENUM_SIZE
+};
+
+// Logs the state of generating a machine id and comparing it to a stored value.
+void LogMachineIdState(MachineIdState state) {
+ UMA_HISTOGRAM_ENUMERATION("UMA.MachineIdState", state, ID_ENUM_SIZE);
+}
+
+} // namespace
+
+ClonedInstallDetector::ClonedInstallDetector(MachineIdProvider* raw_id_provider)
+ : raw_id_provider_(raw_id_provider), weak_ptr_factory_(this) {
+}
+
+ClonedInstallDetector::~ClonedInstallDetector() {
+}
+
+void ClonedInstallDetector::CheckForClonedInstall(
+ PrefService* local_state,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+ base::PostTaskAndReplyWithResult(
+ task_runner.get(),
+ FROM_HERE,
+ base::Bind(&MachineIdProvider::GetMachineId, raw_id_provider_),
+ base::Bind(&ClonedInstallDetector::SaveMachineId,
+ weak_ptr_factory_.GetWeakPtr(),
+ local_state));
+}
+
+void ClonedInstallDetector::SaveMachineId(PrefService* local_state,
+ const std::string& raw_id) {
+ if (raw_id.empty()) {
+ LogMachineIdState(ID_GENERATION_FAILED);
+ local_state->ClearPref(prefs::kMetricsMachineId);
+ return;
+ }
+
+ int hashed_id = HashRawId(raw_id);
+
+ MachineIdState id_state = ID_NO_STORED_VALUE;
+ if (local_state->HasPrefPath(prefs::kMetricsMachineId)) {
+ if (local_state->GetInteger(prefs::kMetricsMachineId) != hashed_id) {
+ id_state = ID_CHANGED;
+ // TODO(jwd): Use a callback to set the reset pref. That way
+ // ClonedInstallDetector doesn't need to know about this pref.
+ local_state->SetBoolean(prefs::kMetricsResetIds, true);
+ } else {
+ id_state = ID_UNCHANGED;
+ }
+ }
+
+ LogMachineIdState(id_state);
+
+ local_state->SetInteger(prefs::kMetricsMachineId, hashed_id);
+}
+
+// static
+void ClonedInstallDetector::RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterIntegerPref(prefs::kMetricsMachineId, 0);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/cloned_install_detector.h b/chromium/components/metrics/cloned_install_detector.h
new file mode 100644
index 00000000000..0609122cdaf
--- /dev/null
+++ b/chromium/components/metrics/cloned_install_detector.h
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_CLONED_INSTALL_DETECTOR_H_
+#define COMPONENTS_METRICS_CLONED_INSTALL_DETECTOR_H_
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace metrics {
+
+class MachineIdProvider;
+
+// A class for detecting if an install is cloned. It does this by detecting
+// when the hardware running Chrome changes.
+class ClonedInstallDetector {
+ public:
+ explicit ClonedInstallDetector(MachineIdProvider* raw_id_provider);
+ virtual ~ClonedInstallDetector();
+
+ // Posts a task to |task_runner| to generate a machine ID and store it to a
+ // local state pref. If the newly generated ID is different than the
+ // previously stored one, then the install is considered cloned. The ID is a
+ // 24-bit value based off of machine characteristics. This value should never
+ // be sent over the network.
+ // TODO(jwd): Implement change detection.
+ void CheckForClonedInstall(
+ PrefService* local_state,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ClonedInstallDetectorTest, SaveId);
+ FRIEND_TEST_ALL_PREFIXES(ClonedInstallDetectorTest, DetectClone);
+
+ // Converts raw_id into a 24-bit hash and stores the hash in |local_state|.
+ // |raw_id| is not a const ref because it's passed from a cross-thread post
+ // task.
+ void SaveMachineId(PrefService* local_state, const std::string& raw_id);
+
+ scoped_refptr<MachineIdProvider> raw_id_provider_;
+ base::WeakPtrFactory<ClonedInstallDetector> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClonedInstallDetector);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_CLONED_INSTALL_DETECTOR_H_
diff --git a/chromium/components/metrics/cloned_install_detector_unittest.cc b/chromium/components/metrics/cloned_install_detector_unittest.cc
new file mode 100644
index 00000000000..91b24e8bf57
--- /dev/null
+++ b/chromium/components/metrics/cloned_install_detector_unittest.cc
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/cloned_install_detector.h"
+
+#include "components/metrics/machine_id_provider.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_state_manager.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+const std::string kTestRawId = "test";
+// Hashed machine id for |kTestRawId|.
+const int kTestHashedId = 2216819;
+
+} // namespace
+
+// TODO(jwd): Change these test to test the full flow and histogram outputs. It
+// should also remove the need to make the test a friend of
+// ClonedInstallDetector.
+TEST(ClonedInstallDetectorTest, SaveId) {
+ TestingPrefServiceSimple prefs;
+ ClonedInstallDetector::RegisterPrefs(prefs.registry());
+
+ scoped_ptr<ClonedInstallDetector> detector(
+ new ClonedInstallDetector(MachineIdProvider::CreateInstance()));
+
+ detector->SaveMachineId(&prefs, kTestRawId);
+
+ EXPECT_EQ(kTestHashedId, prefs.GetInteger(prefs::kMetricsMachineId));
+}
+
+TEST(ClonedInstallDetectorTest, DetectClone) {
+ TestingPrefServiceSimple prefs;
+ MetricsStateManager::RegisterPrefs(prefs.registry());
+
+ // Save a machine id that will cause a clone to be detected.
+ prefs.SetInteger(prefs::kMetricsMachineId, kTestHashedId + 1);
+
+ scoped_ptr<ClonedInstallDetector> detector(
+ new ClonedInstallDetector(MachineIdProvider::CreateInstance()));
+
+ detector->SaveMachineId(&prefs, kTestRawId);
+
+ EXPECT_TRUE(prefs.GetBoolean(prefs::kMetricsResetIds));
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/daily_event.cc b/chromium/components/metrics/daily_event.cc
new file mode 100644
index 00000000000..2ab54909251
--- /dev/null
+++ b/chromium/components/metrics/daily_event.cc
@@ -0,0 +1,106 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/daily_event.h"
+
+#include <utility>
+
+#include "base/metrics/histogram.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+namespace metrics {
+
+namespace {
+
+enum IntervalType {
+ FIRST_RUN,
+ DAY_ELAPSED,
+ CLOCK_CHANGED,
+ NUM_INTERVAL_TYPES
+};
+
+void RecordIntervalTypeHistogram(const std::string& histogram_name,
+ IntervalType type) {
+ base::Histogram::FactoryGet(
+ histogram_name,
+ 1,
+ NUM_INTERVAL_TYPES,
+ NUM_INTERVAL_TYPES + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)->Add(type);
+}
+
+} // namespace
+
+DailyEvent::Observer::Observer() {
+}
+
+DailyEvent::Observer::~Observer() {
+}
+
+DailyEvent::DailyEvent(PrefService* pref_service,
+ const char* pref_name,
+ const std::string& histogram_name)
+ : pref_service_(pref_service),
+ pref_name_(pref_name),
+ histogram_name_(histogram_name) {
+}
+
+DailyEvent::~DailyEvent() {
+}
+
+// static
+void DailyEvent::RegisterPref(PrefRegistrySimple* registry,
+ const char* pref_name) {
+ registry->RegisterInt64Pref(pref_name, base::Time().ToInternalValue());
+}
+
+void DailyEvent::AddObserver(scoped_ptr<DailyEvent::Observer> observer) {
+ DVLOG(2) << "DailyEvent observer added.";
+ DCHECK(last_fired_.is_null());
+ observers_.push_back(std::move(observer));
+}
+
+void DailyEvent::CheckInterval() {
+ base::Time now = base::Time::Now();
+ if (last_fired_.is_null()) {
+ // The first time we call CheckInterval, we read the time stored in prefs.
+ last_fired_ = base::Time::FromInternalValue(
+ pref_service_->GetInt64(pref_name_));
+ DVLOG(1) << "DailyEvent time loaded: " << last_fired_;
+ if (last_fired_.is_null()) {
+ DVLOG(1) << "DailyEvent first run.";
+ RecordIntervalTypeHistogram(histogram_name_, FIRST_RUN);
+ OnInterval(now);
+ return;
+ }
+ }
+ int days_elapsed = (now - last_fired_).InDays();
+ if (days_elapsed >= 1) {
+ DVLOG(1) << "DailyEvent day elapsed.";
+ RecordIntervalTypeHistogram(histogram_name_, DAY_ELAPSED);
+ OnInterval(now);
+ } else if (days_elapsed <= -1) {
+ // The "last fired" time is more than a day in the future, so the clock
+ // must have been changed.
+ DVLOG(1) << "DailyEvent clock change detected.";
+ RecordIntervalTypeHistogram(histogram_name_, CLOCK_CHANGED);
+ OnInterval(now);
+ }
+}
+
+void DailyEvent::OnInterval(base::Time now) {
+ DCHECK(!now.is_null());
+ last_fired_ = now;
+ pref_service_->SetInt64(pref_name_, last_fired_.ToInternalValue());
+
+ // Notify all observers
+ for (ScopedVector<DailyEvent::Observer>::iterator it = observers_.begin();
+ it != observers_.end();
+ ++it) {
+ (*it)->OnDailyEvent();
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/daily_event.h b/chromium/components/metrics/daily_event.h
new file mode 100644
index 00000000000..24b31dc58ea
--- /dev/null
+++ b/chromium/components/metrics/daily_event.h
@@ -0,0 +1,92 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_DAILY_EVENT_H_
+#define COMPONENTS_METRICS_DAILY_EVENT_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/time/time.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace metrics {
+
+// DailyEvent is used for throttling an event to about once per day, even if
+// the program is restarted more frequently. It is based on local machine
+// time, so it could be fired more often if the clock is changed.
+//
+// The service using the DailyEvent should first provide all of the Observers
+// for the interval, and then arrange for CheckInterval() to be called
+// periodically to test if the event should be fired.
+class DailyEvent {
+ public:
+ // Observer receives notifications from a DailyEvent.
+ // Observers must be added before the DailyEvent begins checking time,
+ // and will be owned by the DailyEvent.
+ class Observer {
+ public:
+ Observer();
+ virtual ~Observer();
+
+ // Called when the daily event is fired.
+ virtual void OnDailyEvent() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Observer);
+ };
+
+ // Constructs DailyEvent monitor which stores the time it last fired in the
+ // preference |pref_name|. |pref_name| should be registered by calling
+ // RegisterPref before using this object.
+ // Caller is responsible for ensuring that |pref_service| and |pref_name|
+ // outlive the DailyEvent.
+ // |histogram_name| is the name of the UMA metric which record when this
+ // interval fires, and should be registered in histograms.xml
+ DailyEvent(PrefService* pref_service,
+ const char* pref_name,
+ const std::string& histogram_name);
+ ~DailyEvent();
+
+ // Adds a observer to be notified when a day elapses. All observers should
+ // be registered before the the DailyEvent starts checking time.
+ void AddObserver(scoped_ptr<Observer> observer);
+
+ // Checks if a day has elapsed. If it has, OnDailyEvent will be called on
+ // all observers.
+ void CheckInterval();
+
+ // Registers the preference used by this interval.
+ static void RegisterPref(PrefRegistrySimple* registry, const char* pref_name);
+
+ private:
+ // Handles an interval elapsing.
+ void OnInterval(base::Time now);
+
+ // A weak pointer to the PrefService object to read and write preferences
+ // from. Calling code should ensure this object continues to exist for the
+ // lifetime of the DailyEvent object.
+ PrefService* pref_service_;
+
+ // The name of the preference to store the last fired time in.
+ // Calling code should ensure this outlives the DailyEvent.
+ const char* pref_name_;
+
+ // The name of the histogram to record intervals.
+ std::string histogram_name_;
+
+ // A list of observers.
+ ScopedVector<Observer> observers_;
+
+ // The time that the daily event was last fired.
+ base::Time last_fired_;
+
+ DISALLOW_COPY_AND_ASSIGN(DailyEvent);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_DAILY_EVENT_H_
diff --git a/chromium/components/metrics/daily_event_unittest.cc b/chromium/components/metrics/daily_event_unittest.cc
new file mode 100644
index 00000000000..4adbaaf92be
--- /dev/null
+++ b/chromium/components/metrics/daily_event_unittest.cc
@@ -0,0 +1,94 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/daily_event.h"
+
+#include "base/macros.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+const char kTestPrefName[] = "TestPref";
+const char kTestMetricName[] = "TestMetric";
+
+class TestDailyObserver : public DailyEvent::Observer {
+ public:
+ TestDailyObserver() : fired_(false) {}
+
+ bool fired() const { return fired_; }
+
+ void OnDailyEvent() override { fired_ = true; }
+
+ void Reset() {
+ fired_ = false;
+ }
+
+ private:
+ // True if this event has been observed.
+ bool fired_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestDailyObserver);
+};
+
+class DailyEventTest : public testing::Test {
+ public:
+ DailyEventTest() : event_(&prefs_, kTestPrefName, kTestMetricName) {
+ DailyEvent::RegisterPref(prefs_.registry(), kTestPrefName);
+ observer_ = new TestDailyObserver();
+ event_.AddObserver(make_scoped_ptr(observer_));
+ }
+
+ protected:
+ TestingPrefServiceSimple prefs_;
+ TestDailyObserver* observer_;
+ DailyEvent event_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DailyEventTest);
+};
+
+} // namespace
+
+// The event should fire if the preference is not available.
+TEST_F(DailyEventTest, TestNewFires) {
+ event_.CheckInterval();
+ EXPECT_TRUE(observer_->fired());
+}
+
+// The event should fire if the preference is more than a day old.
+TEST_F(DailyEventTest, TestOldFires) {
+ base::Time last_time = base::Time::Now() - base::TimeDelta::FromHours(25);
+ prefs_.SetInt64(kTestPrefName, last_time.ToInternalValue());
+ event_.CheckInterval();
+ EXPECT_TRUE(observer_->fired());
+}
+
+// The event should fire if the preference is more than a day in the future.
+TEST_F(DailyEventTest, TestFutureFires) {
+ base::Time last_time = base::Time::Now() + base::TimeDelta::FromHours(25);
+ prefs_.SetInt64(kTestPrefName, last_time.ToInternalValue());
+ event_.CheckInterval();
+ EXPECT_TRUE(observer_->fired());
+}
+
+// The event should not fire if the preference is more recent than a day.
+TEST_F(DailyEventTest, TestRecentNotFired) {
+ base::Time last_time = base::Time::Now() - base::TimeDelta::FromMinutes(2);
+ prefs_.SetInt64(kTestPrefName, last_time.ToInternalValue());
+ event_.CheckInterval();
+ EXPECT_FALSE(observer_->fired());
+}
+
+// The event should not fire if the preference is less than a day in the future.
+TEST_F(DailyEventTest, TestSoonNotFired) {
+ base::Time last_time = base::Time::Now() + base::TimeDelta::FromMinutes(2);
+ prefs_.SetInt64(kTestPrefName, last_time.ToInternalValue());
+ event_.CheckInterval();
+ EXPECT_FALSE(observer_->fired());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/data_use_tracker.cc b/chromium/components/metrics/data_use_tracker.cc
new file mode 100644
index 00000000000..a74bdf117eb
--- /dev/null
+++ b/chromium/components/metrics/data_use_tracker.cc
@@ -0,0 +1,205 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/data_use_tracker.h"
+
+#include <string>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/variations/variations_associated_data.h"
+
+namespace metrics {
+
+namespace {
+
+// This function is for forwarding metrics usage pref changes to the appropriate
+// callback on the appropriate thread.
+// TODO(gayane): Reduce the frequency of posting tasks from IO to UI thread.
+void UpdateMetricsUsagePrefs(
+ const UpdateUsagePrefCallbackType& update_on_ui_callback,
+ scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
+ const std::string& service_name,
+ int message_size,
+ bool is_cellular) {
+ ui_task_runner->PostTask(
+ FROM_HERE, base::Bind(update_on_ui_callback, service_name, message_size,
+ is_cellular));
+}
+
+} // namespace
+
+DataUseTracker::DataUseTracker(PrefService* local_state)
+ : local_state_(local_state), weak_ptr_factory_(this) {}
+
+DataUseTracker::~DataUseTracker() {}
+
+// static
+scoped_ptr<DataUseTracker> DataUseTracker::Create(PrefService* local_state) {
+ scoped_ptr<DataUseTracker> data_use_tracker;
+ if (variations::GetVariationParamValue("UMA_EnableCellularLogUpload",
+ "Uma_Quota") != "" &&
+ variations::GetVariationParamValue("UMA_EnableCellularLogUpload",
+ "Uma_Ratio") != "") {
+ data_use_tracker.reset(new DataUseTracker(local_state));
+ }
+ return data_use_tracker;
+}
+
+// static
+void DataUseTracker::RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterDictionaryPref(metrics::prefs::kUserCellDataUse);
+ registry->RegisterDictionaryPref(metrics::prefs::kUmaCellDataUse);
+}
+
+UpdateUsagePrefCallbackType DataUseTracker::GetDataUseForwardingCallback(
+ scoped_refptr<base::SequencedTaskRunner> ui_task_runner) {
+ DCHECK(ui_task_runner->RunsTasksOnCurrentThread());
+
+ return base::Bind(
+ &UpdateMetricsUsagePrefs,
+ base::Bind(&DataUseTracker::UpdateMetricsUsagePrefsOnUIThread,
+ weak_ptr_factory_.GetWeakPtr()),
+ ui_task_runner);
+}
+
+bool DataUseTracker::ShouldUploadLogOnCellular(int log_bytes) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ RemoveExpiredEntries();
+
+ int uma_weekly_quota_bytes;
+ if (!GetUmaWeeklyQuota(&uma_weekly_quota_bytes))
+ return true;
+
+ int uma_total_data_use = ComputeTotalDataUse(prefs::kUmaCellDataUse);
+ int new_uma_total_data_use = log_bytes + uma_total_data_use;
+ // If the new log doesn't increase the total UMA traffic to be above the
+ // allowed quota then the log should be uploaded.
+ if (new_uma_total_data_use <= uma_weekly_quota_bytes)
+ return true;
+
+ double uma_ratio;
+ if (!GetUmaRatio(&uma_ratio))
+ return true;
+
+ int user_total_data_use = ComputeTotalDataUse(prefs::kUserCellDataUse);
+ // If after adding the new log the uma ratio is still under the allowed ratio
+ // then the log should be uploaded and vice versa.
+ return new_uma_total_data_use /
+ static_cast<double>(log_bytes + user_total_data_use) <=
+ uma_ratio;
+}
+
+void DataUseTracker::UpdateMetricsUsagePrefsOnUIThread(
+ const std::string& service_name,
+ int message_size,
+ bool is_celllular) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!is_celllular)
+ return;
+
+ UpdateUsagePref(prefs::kUserCellDataUse, message_size);
+ if (service_name == "UMA")
+ UpdateUsagePref(prefs::kUmaCellDataUse, message_size);
+}
+
+void DataUseTracker::UpdateUsagePref(const std::string& pref_name,
+ int message_size) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ DictionaryPrefUpdate pref_updater(local_state_, pref_name);
+ int todays_traffic = 0;
+ std::string todays_key = GetCurrentMeasurementDateAsString();
+
+ const base::DictionaryValue* user_pref_dict =
+ local_state_->GetDictionary(pref_name);
+ user_pref_dict->GetInteger(todays_key, &todays_traffic);
+ pref_updater->SetInteger(todays_key, todays_traffic + message_size);
+}
+
+void DataUseTracker::RemoveExpiredEntries() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ RemoveExpiredEntriesForPref(prefs::kUmaCellDataUse);
+ RemoveExpiredEntriesForPref(prefs::kUserCellDataUse);
+}
+
+void DataUseTracker::RemoveExpiredEntriesForPref(const std::string& pref_name) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ const base::DictionaryValue* user_pref_dict =
+ local_state_->GetDictionary(pref_name);
+ const base::Time current_date = GetCurrentMeasurementDate();
+ const base::Time week_ago = current_date - base::TimeDelta::FromDays(7);
+
+ base::DictionaryValue user_pref_new_dict;
+ for (base::DictionaryValue::Iterator it(*user_pref_dict); !it.IsAtEnd();
+ it.Advance()) {
+ base::Time key_date;
+ base::Time::FromUTCString(it.key().c_str(), &key_date);
+ if (key_date > week_ago)
+ user_pref_new_dict.Set(it.key(), it.value().CreateDeepCopy());
+ }
+ local_state_->Set(pref_name, user_pref_new_dict);
+}
+
+// Note: We compute total data use regardless of what is the current date. In
+// scenario when user travels back in time zone and current date becomes earlier
+// than latest registered date in perf, we still count that in total use as user
+// actually used that data.
+int DataUseTracker::ComputeTotalDataUse(const std::string& pref_name) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ int total_data_use = 0;
+ const base::DictionaryValue* pref_dict =
+ local_state_->GetDictionary(pref_name);
+ for (base::DictionaryValue::Iterator it(*pref_dict); !it.IsAtEnd();
+ it.Advance()) {
+ int value = 0;
+ it.value().GetAsInteger(&value);
+ total_data_use += value;
+ }
+ return total_data_use;
+}
+
+bool DataUseTracker::GetUmaWeeklyQuota(int* uma_weekly_quota_bytes) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ std::string param_value_str = variations::GetVariationParamValue(
+ "UMA_EnableCellularLogUpload", "Uma_Quota");
+ if (param_value_str.empty())
+ return false;
+
+ base::StringToInt(param_value_str, uma_weekly_quota_bytes);
+ return true;
+}
+
+bool DataUseTracker::GetUmaRatio(double* ratio) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ std::string param_value_str = variations::GetVariationParamValue(
+ "UMA_EnableCellularLogUpload", "Uma_Ratio");
+ if (param_value_str.empty())
+ return false;
+ base::StringToDouble(param_value_str, ratio);
+ return true;
+}
+
+base::Time DataUseTracker::GetCurrentMeasurementDate() const {
+ return base::Time::Now().LocalMidnight();
+}
+
+std::string DataUseTracker::GetCurrentMeasurementDateAsString() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ base::Time::Exploded today_exploded;
+ GetCurrentMeasurementDate().LocalExplode(&today_exploded);
+ return base::StringPrintf("%04d-%02d-%02d", today_exploded.year,
+ today_exploded.month, today_exploded.day_of_month);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/data_use_tracker.h b/chromium/components/metrics/data_use_tracker.h
new file mode 100644
index 00000000000..5682a0c0720
--- /dev/null
+++ b/chromium/components/metrics/data_use_tracker.h
@@ -0,0 +1,99 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_DATA_USE_TRACKER_H_
+#define COMPONENTS_METRICS_DATA_USE_TRACKER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequenced_task_runner.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+namespace metrics {
+
+typedef base::Callback<void(const std::string&, int, bool)>
+ UpdateUsagePrefCallbackType;
+
+// Records the data use of user traffic and UMA traffic in user prefs. Taking
+// into account those prefs it can verify whether certain UMA log upload is
+// allowed.
+class DataUseTracker {
+ public:
+ explicit DataUseTracker(PrefService* local_state);
+ ~DataUseTracker();
+
+ // Returns an instance of |DataUseTracker| with provided |local_state| if
+ // users data use should be tracked and null pointer otherwise.
+ static scoped_ptr<DataUseTracker> Create(PrefService* local_state);
+
+ // Registers data use prefs using provided |registry|.
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ // Returns a callback to data use pref updating function. Should be called on
+ // UI thread.
+ UpdateUsagePrefCallbackType GetDataUseForwardingCallback(
+ scoped_refptr<base::SequencedTaskRunner> ui_task_runner);
+
+ // Returns whether a log with provided |log_bytes| can be uploaded according
+ // to data use ratio and UMA quota provided by variations.
+ bool ShouldUploadLogOnCellular(int log_bytes);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(DataUseTrackerTest, CheckUpdateUsagePref);
+ FRIEND_TEST_ALL_PREFIXES(DataUseTrackerTest, CheckRemoveExpiredEntries);
+ FRIEND_TEST_ALL_PREFIXES(DataUseTrackerTest, CheckComputeTotalDataUse);
+ FRIEND_TEST_ALL_PREFIXES(DataUseTrackerTest, CheckCanUploadUMALog);
+
+ // Updates data usage prefs on UI thread according to what Prefservice
+ // expects.
+ void UpdateMetricsUsagePrefsOnUIThread(const std::string& service_name,
+ int message_size,
+ bool is_cellular);
+
+ // Updates provided |pref_name| for a current date with the given message
+ // size.
+ void UpdateUsagePref(const std::string& pref_name, int message_size);
+
+ // Removes entries from the all data use prefs.
+ void RemoveExpiredEntries();
+
+ // Removes entries from the given |pref_name| if they are more than 7 days
+ // old.
+ void RemoveExpiredEntriesForPref(const std::string& pref_name);
+
+ // Computes data usage according to all the entries in the given dictionary
+ // pref.
+ int ComputeTotalDataUse(const std::string& pref_name);
+
+ // Returns the weekly allowed quota for UMA data use.
+ virtual bool GetUmaWeeklyQuota(int* uma_weekly_quota_bytes) const;
+
+ // Returns the allowed ratio for UMA data use over overall data use.
+ virtual bool GetUmaRatio(double* ratio) const;
+
+ // Returns the current date for measurement.
+ virtual base::Time GetCurrentMeasurementDate() const;
+
+ // Returns the current date as a string with a proper formatting.
+ virtual std::string GetCurrentMeasurementDateAsString() const;
+
+ PrefService* local_state_;
+
+ base::ThreadChecker thread_checker_;
+
+ base::WeakPtrFactory<DataUseTracker> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DataUseTracker);
+};
+
+} // namespace metrics
+#endif // COMPONENTS_METRICS_DATA_USE_TRACKER_H_
diff --git a/chromium/components/metrics/data_use_tracker_unittest.cc b/chromium/components/metrics/data_use_tracker_unittest.cc
new file mode 100644
index 00000000000..ed32a4a2024
--- /dev/null
+++ b/chromium/components/metrics/data_use_tracker_unittest.cc
@@ -0,0 +1,206 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/data_use_tracker.h"
+
+#include "base/strings/stringprintf.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+const char kTodayStr[] = "2016-03-16";
+const char kYesterdayStr[] = "2016-03-15";
+const char kExpiredDateStr1[] = "2016-03-09";
+const char kExpiredDateStr2[] = "2016-03-01";
+
+class TestDataUsePrefService : public TestingPrefServiceSimple {
+ public:
+ TestDataUsePrefService() {
+ registry()->RegisterDictionaryPref(metrics::prefs::kUserCellDataUse);
+ registry()->RegisterDictionaryPref(metrics::prefs::kUmaCellDataUse);
+ }
+
+ void ClearDataUsePrefs() {
+ ClearPref(metrics::prefs::kUserCellDataUse);
+ ClearPref(metrics::prefs::kUmaCellDataUse);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestDataUsePrefService);
+};
+
+class FakeDataUseTracker : public DataUseTracker {
+ public:
+ FakeDataUseTracker(PrefService* local_state) : DataUseTracker(local_state) {}
+
+ bool GetUmaWeeklyQuota(int* uma_weekly_quota_bytes) const override {
+ *uma_weekly_quota_bytes = 200;
+ return true;
+ }
+
+ bool GetUmaRatio(double* ratio) const override {
+ *ratio = 0.05;
+ return true;
+ }
+
+ base::Time GetCurrentMeasurementDate() const override {
+ base::Time today_for_test;
+ base::Time::FromUTCString(kTodayStr, &today_for_test);
+ return today_for_test;
+ }
+
+ std::string GetCurrentMeasurementDateAsString() const override {
+ return kTodayStr;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FakeDataUseTracker);
+};
+
+// Sets up data usage prefs with mock values so that UMA traffic is above the
+// allowed ratio.
+void SetPrefTestValuesOverRatio(PrefService* local_state) {
+ base::DictionaryValue user_pref_dict;
+ user_pref_dict.SetInteger(kTodayStr, 2 * 100);
+ user_pref_dict.SetInteger(kYesterdayStr, 2 * 100);
+ user_pref_dict.SetInteger(kExpiredDateStr1, 2 * 100);
+ user_pref_dict.SetInteger(kExpiredDateStr2, 2 * 100);
+ local_state->Set(prefs::kUserCellDataUse, user_pref_dict);
+
+ base::DictionaryValue uma_pref_dict;
+ uma_pref_dict.SetInteger(kTodayStr, 50);
+ uma_pref_dict.SetInteger(kYesterdayStr, 50);
+ uma_pref_dict.SetInteger(kExpiredDateStr1, 50);
+ uma_pref_dict.SetInteger(kExpiredDateStr2, 50);
+ local_state->Set(prefs::kUmaCellDataUse, uma_pref_dict);
+}
+
+// Sets up data usage prefs with mock values which can be valid.
+void SetPrefTestValuesValidRatio(PrefService* local_state) {
+ base::DictionaryValue user_pref_dict;
+ user_pref_dict.SetInteger(kTodayStr, 100 * 100);
+ user_pref_dict.SetInteger(kYesterdayStr, 100 * 100);
+ user_pref_dict.SetInteger(kExpiredDateStr1, 100 * 100);
+ user_pref_dict.SetInteger(kExpiredDateStr2, 100 * 100);
+ local_state->Set(prefs::kUserCellDataUse, user_pref_dict);
+
+ // Should be 4% of user traffic
+ base::DictionaryValue uma_pref_dict;
+ uma_pref_dict.SetInteger(kTodayStr, 4 * 100);
+ uma_pref_dict.SetInteger(kYesterdayStr, 4 * 100);
+ uma_pref_dict.SetInteger(kExpiredDateStr1, 4 * 100);
+ uma_pref_dict.SetInteger(kExpiredDateStr2, 4 * 100);
+ local_state->Set(prefs::kUmaCellDataUse, uma_pref_dict);
+}
+
+} // namespace
+
+TEST(DataUseTrackerTest, CheckUpdateUsagePref) {
+ TestDataUsePrefService local_state;
+ FakeDataUseTracker data_use_tracker(&local_state);
+ local_state.ClearDataUsePrefs();
+
+ int user_pref_value = 0;
+ int uma_pref_value = 0;
+
+ data_use_tracker.UpdateMetricsUsagePrefsOnUIThread("", 2 * 100, true);
+ local_state.GetDictionary(prefs::kUserCellDataUse)
+ ->GetInteger(kTodayStr, &user_pref_value);
+ EXPECT_EQ(2 * 100, user_pref_value);
+ local_state.GetDictionary(prefs::kUmaCellDataUse)
+ ->GetInteger(kTodayStr, &uma_pref_value);
+ EXPECT_EQ(0, uma_pref_value);
+
+ data_use_tracker.UpdateMetricsUsagePrefsOnUIThread("UMA", 100, true);
+ local_state.GetDictionary(prefs::kUserCellDataUse)
+ ->GetInteger(kTodayStr, &user_pref_value);
+ EXPECT_EQ(3 * 100, user_pref_value);
+ local_state.GetDictionary(prefs::kUmaCellDataUse)
+ ->GetInteger(kTodayStr, &uma_pref_value);
+ EXPECT_EQ(100, uma_pref_value);
+}
+
+TEST(DataUseTrackerTest, CheckRemoveExpiredEntries) {
+ TestDataUsePrefService local_state;
+ FakeDataUseTracker data_use_tracker(&local_state);
+ local_state.ClearDataUsePrefs();
+ SetPrefTestValuesOverRatio(&local_state);
+ data_use_tracker.RemoveExpiredEntries();
+
+ int user_pref_value = 0;
+ int uma_pref_value = 0;
+
+ local_state.GetDictionary(prefs::kUserCellDataUse)
+ ->GetInteger(kExpiredDateStr1, &user_pref_value);
+ EXPECT_EQ(0, user_pref_value);
+ local_state.GetDictionary(prefs::kUmaCellDataUse)
+ ->GetInteger(kExpiredDateStr1, &uma_pref_value);
+ EXPECT_EQ(0, uma_pref_value);
+
+ local_state.GetDictionary(prefs::kUserCellDataUse)
+ ->GetInteger(kExpiredDateStr2, &user_pref_value);
+ EXPECT_EQ(0, user_pref_value);
+ local_state.GetDictionary(prefs::kUmaCellDataUse)
+ ->GetInteger(kExpiredDateStr2, &uma_pref_value);
+ EXPECT_EQ(0, uma_pref_value);
+
+ local_state.GetDictionary(prefs::kUserCellDataUse)
+ ->GetInteger(kTodayStr, &user_pref_value);
+ EXPECT_EQ(2 * 100, user_pref_value);
+ local_state.GetDictionary(prefs::kUmaCellDataUse)
+ ->GetInteger(kTodayStr, &uma_pref_value);
+ EXPECT_EQ(50, uma_pref_value);
+
+ local_state.GetDictionary(prefs::kUserCellDataUse)
+ ->GetInteger(kYesterdayStr, &user_pref_value);
+ EXPECT_EQ(2 * 100, user_pref_value);
+ local_state.GetDictionary(prefs::kUmaCellDataUse)
+ ->GetInteger(kYesterdayStr, &uma_pref_value);
+ EXPECT_EQ(50, uma_pref_value);
+}
+
+TEST(DataUseTrackerTest, CheckComputeTotalDataUse) {
+ TestDataUsePrefService local_state;
+ FakeDataUseTracker data_use_tracker(&local_state);
+ local_state.ClearDataUsePrefs();
+ SetPrefTestValuesOverRatio(&local_state);
+
+ int user_data_use =
+ data_use_tracker.ComputeTotalDataUse(prefs::kUserCellDataUse);
+ EXPECT_EQ(8 * 100, user_data_use);
+ int uma_data_use =
+ data_use_tracker.ComputeTotalDataUse(prefs::kUmaCellDataUse);
+ EXPECT_EQ(4 * 50, uma_data_use);
+}
+
+TEST(DataUseTrackerTest, CheckShouldUploadLogOnCellular) {
+ TestDataUsePrefService local_state;
+ FakeDataUseTracker data_use_tracker(&local_state);
+ local_state.ClearDataUsePrefs();
+ SetPrefTestValuesOverRatio(&local_state);
+
+ bool can_upload = data_use_tracker.ShouldUploadLogOnCellular(50);
+ EXPECT_TRUE(can_upload);
+ can_upload = data_use_tracker.ShouldUploadLogOnCellular(100);
+ EXPECT_TRUE(can_upload);
+ can_upload = data_use_tracker.ShouldUploadLogOnCellular(150);
+ EXPECT_FALSE(can_upload);
+
+ local_state.ClearDataUsePrefs();
+ SetPrefTestValuesValidRatio(&local_state);
+ can_upload = data_use_tracker.ShouldUploadLogOnCellular(100);
+ EXPECT_TRUE(can_upload);
+ // this is about 0.49%
+ can_upload = data_use_tracker.ShouldUploadLogOnCellular(200);
+ EXPECT_TRUE(can_upload);
+ can_upload = data_use_tracker.ShouldUploadLogOnCellular(300);
+ EXPECT_FALSE(can_upload);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/drive_metrics_provider.cc b/chromium/components/metrics/drive_metrics_provider.cc
new file mode 100644
index 00000000000..22ad0891919
--- /dev/null
+++ b/chromium/components/metrics/drive_metrics_provider.cc
@@ -0,0 +1,97 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+#include "base/base_paths.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/path_service.h"
+#include "base/task_runner_util.h"
+#include "base/time/time.h"
+
+namespace metrics {
+
+DriveMetricsProvider::DriveMetricsProvider(
+ scoped_refptr<base::SequencedTaskRunner> file_thread,
+ int local_state_path_key)
+ : file_thread_(file_thread),
+ local_state_path_key_(local_state_path_key),
+ weak_ptr_factory_(this) {}
+
+DriveMetricsProvider::~DriveMetricsProvider() {}
+
+void DriveMetricsProvider::ProvideSystemProfileMetrics(
+ metrics::SystemProfileProto* system_profile_proto) {
+ auto* hardware = system_profile_proto->mutable_hardware();
+ FillDriveMetrics(metrics_.app_drive, hardware->mutable_app_drive());
+ FillDriveMetrics(metrics_.user_data_drive,
+ hardware->mutable_user_data_drive());
+}
+
+void DriveMetricsProvider::GetDriveMetrics(const base::Closure& done_callback) {
+ base::PostTaskAndReplyWithResult(
+ file_thread_.get(), FROM_HERE,
+ base::Bind(&DriveMetricsProvider::GetDriveMetricsOnFileThread,
+ local_state_path_key_),
+ base::Bind(&DriveMetricsProvider::GotDriveMetrics,
+ weak_ptr_factory_.GetWeakPtr(), done_callback));
+}
+
+DriveMetricsProvider::SeekPenaltyResponse::SeekPenaltyResponse()
+ : success(false) {}
+
+// static
+DriveMetricsProvider::DriveMetrics
+DriveMetricsProvider::GetDriveMetricsOnFileThread(int local_state_path_key) {
+ DriveMetricsProvider::DriveMetrics metrics;
+ QuerySeekPenalty(base::FILE_EXE, &metrics.app_drive);
+ QuerySeekPenalty(local_state_path_key, &metrics.user_data_drive);
+ return metrics;
+}
+
+// static
+void DriveMetricsProvider::QuerySeekPenalty(
+ int path_service_key,
+ DriveMetricsProvider::SeekPenaltyResponse* response) {
+ DCHECK(response);
+
+ base::FilePath path;
+ if (!PathService::Get(path_service_key, &path))
+ return;
+
+ base::TimeTicks start = base::TimeTicks::Now();
+
+ response->success = HasSeekPenalty(path, &response->has_seek_penalty);
+
+ UMA_HISTOGRAM_TIMES("Hardware.Drive.HasSeekPenalty_Time",
+ base::TimeTicks::Now() - start);
+ UMA_HISTOGRAM_BOOLEAN("Hardware.Drive.HasSeekPenalty_Success",
+ response->success);
+ if (response->success) {
+ UMA_HISTOGRAM_BOOLEAN("Hardware.Drive.HasSeekPenalty",
+ response->has_seek_penalty);
+ }
+}
+
+void DriveMetricsProvider::GotDriveMetrics(
+ const base::Closure& done_callback,
+ const DriveMetricsProvider::DriveMetrics& metrics) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ metrics_ = metrics;
+ done_callback.Run();
+}
+
+void DriveMetricsProvider::FillDriveMetrics(
+ const DriveMetricsProvider::SeekPenaltyResponse& response,
+ metrics::SystemProfileProto::Hardware::Drive* drive) {
+ if (response.success)
+ drive->set_has_seek_penalty(response.has_seek_penalty);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/drive_metrics_provider.h b/chromium/components/metrics/drive_metrics_provider.h
new file mode 100644
index 00000000000..cab799d40ea
--- /dev/null
+++ b/chromium/components/metrics/drive_metrics_provider.h
@@ -0,0 +1,100 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_DRIVE_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_DRIVE_METRICS_PROVIDER_H_
+
+#include "base/callback_forward.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequenced_task_runner.h"
+#include "base/threading/thread_checker.h"
+#include "components/metrics/metrics_provider.h"
+#include "components/metrics/proto/system_profile.pb.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace metrics {
+
+// Provides metrics about the local drives on a user's computer. Currently only
+// checks to see if they incur a seek-time penalty (e.g. if they're SSDs).
+//
+// Defers gathering metrics until after "rush hour" (startup) so as to not bog
+// down the file thread.
+class DriveMetricsProvider : public metrics::MetricsProvider {
+ public:
+ DriveMetricsProvider(scoped_refptr<base::SequencedTaskRunner> file_thread,
+ int local_state_path_key);
+ ~DriveMetricsProvider() override;
+
+ // metrics::MetricsDataProvider:
+ void ProvideSystemProfileMetrics(
+ metrics::SystemProfileProto* system_profile_proto) override;
+
+ // Called to start gathering metrics.
+ void GetDriveMetrics(const base::Closure& done_callback);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(DriveMetricsProviderTest, HasSeekPenalty);
+
+ // A response to querying a drive as to whether it incurs a seek penalty.
+ // |has_seek_penalty| is set if |success| is true.
+ struct SeekPenaltyResponse {
+ SeekPenaltyResponse();
+ bool success;
+ bool has_seek_penalty;
+ };
+
+ struct DriveMetrics {
+ SeekPenaltyResponse app_drive;
+ SeekPenaltyResponse user_data_drive;
+ };
+
+ // Determine whether the device that services |path| has a seek penalty.
+ // Returns false if it couldn't be determined (e.g., |path| doesn't exist).
+ static bool HasSeekPenalty(const base::FilePath& path,
+ bool* has_seek_penalty);
+
+ // Gather metrics about various drives on |file_thread_|.
+ static DriveMetrics GetDriveMetricsOnFileThread(int local_state_path_key);
+
+ // Tries to determine whether there is a penalty for seeking on the drive that
+ // hosts |path_service_key| (for example: the drive that holds "Local State").
+ static void QuerySeekPenalty(int path_service_key,
+ SeekPenaltyResponse* response);
+
+ // Called when metrics are done being gathered from the FILE thread.
+ // |done_callback| is the callback that should be called once all metrics are
+ // gathered.
+ void GotDriveMetrics(const base::Closure& done_callback,
+ const DriveMetrics& metrics);
+
+ // Fills |drive| with information from successful |response|s.
+ void FillDriveMetrics(const SeekPenaltyResponse& response,
+ metrics::SystemProfileProto::Hardware::Drive* drive);
+
+ // The thread on which file operations are performed (supplied by the
+ // embedder).
+ scoped_refptr<base::SequencedTaskRunner> file_thread_;
+
+ // The key to give to base::PathService to obtain the path to local state
+ // (supplied by the embedder).
+ int local_state_path_key_;
+
+ // Information gathered about various important drives.
+ DriveMetrics metrics_;
+
+ base::ThreadChecker thread_checker_;
+ base::WeakPtrFactory<DriveMetricsProvider> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DriveMetricsProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_DRIVE_METRICS_PROVIDER_H_
diff --git a/chromium/components/metrics/drive_metrics_provider_android.cc b/chromium/components/metrics/drive_metrics_provider_android.cc
new file mode 100644
index 00000000000..a653dd6f3ef
--- /dev/null
+++ b/chromium/components/metrics/drive_metrics_provider_android.cc
@@ -0,0 +1,16 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+namespace metrics {
+
+// static
+bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path,
+ bool* has_seek_penalty) {
+ *has_seek_penalty = false;
+ return true;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/drive_metrics_provider_ios.mm b/chromium/components/metrics/drive_metrics_provider_ios.mm
new file mode 100644
index 00000000000..a653dd6f3ef
--- /dev/null
+++ b/chromium/components/metrics/drive_metrics_provider_ios.mm
@@ -0,0 +1,16 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+namespace metrics {
+
+// static
+bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path,
+ bool* has_seek_penalty) {
+ *has_seek_penalty = false;
+ return true;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/drive_metrics_provider_linux.cc b/chromium/components/metrics/drive_metrics_provider_linux.cc
new file mode 100644
index 00000000000..35c505a3492
--- /dev/null
+++ b/chromium/components/metrics/drive_metrics_provider_linux.cc
@@ -0,0 +1,65 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+#include <linux/kdev_t.h> // For MAJOR()/MINOR().
+#include <sys/stat.h>
+#include <string>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+
+#if defined(OS_CHROMEOS)
+#include "base/sys_info.h"
+#endif
+
+namespace metrics {
+
+namespace {
+
+// See http://www.kernel.org/doc/Documentation/devices.txt for more info.
+const int kFirstScsiMajorNumber = 8;
+const int kPartitionsPerScsiDevice = 16;
+const char kRotationalFormat[] = "/sys/block/sd%c/queue/rotational";
+
+} // namespace
+
+// static
+bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path,
+ bool* has_seek_penalty) {
+#if defined(OS_CHROMEOS)
+ std::string board = base::SysInfo::GetLsbReleaseBoard();
+ if (board != "unknown" && board != "parrot") {
+ // All ChromeOS devices have SSDs. Except some parrots.
+ *has_seek_penalty = false;
+ return true;
+ }
+#endif
+
+ base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+ if (!file.IsValid())
+ return false;
+
+ struct stat path_stat;
+ int error = fstat(file.GetPlatformFile(), &path_stat);
+ if (error < 0 || MAJOR(path_stat.st_dev) != kFirstScsiMajorNumber) {
+ // TODO(dbeam): support more SCSI major numbers (e.g. /dev/sdq+) and LVM?
+ return false;
+ }
+
+ char sdX = 'a' + MINOR(path_stat.st_dev) / kPartitionsPerScsiDevice;
+ std::string rotational_path = base::StringPrintf(kRotationalFormat, sdX);
+ std::string rotates;
+ if (!base::ReadFileToString(base::FilePath(rotational_path), &rotates))
+ return false;
+
+ *has_seek_penalty = rotates.substr(0, 1) == "1";
+ return true;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/drive_metrics_provider_mac.mm b/chromium/components/metrics/drive_metrics_provider_mac.mm
new file mode 100644
index 00000000000..a6a37614d0c
--- /dev/null
+++ b/chromium/components/metrics/drive_metrics_provider_mac.mm
@@ -0,0 +1,76 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <DiskArbitration/DiskArbitration.h>
+#include <Foundation/Foundation.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/storage/IOStorageDeviceCharacteristics.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include "base/files/file_path.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/mac/scoped_ioobject.h"
+
+namespace metrics {
+
+// static
+bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path,
+ bool* has_seek_penalty) {
+ struct stat path_stat;
+ if (stat(path.value().c_str(), &path_stat) < 0)
+ return false;
+
+ const char* dev_name = devname(path_stat.st_dev, S_IFBLK);
+ if (!dev_name)
+ return false;
+
+ std::string bsd_name("/dev/");
+ bsd_name.append(dev_name);
+
+ base::ScopedCFTypeRef<DASessionRef> session(
+ DASessionCreate(kCFAllocatorDefault));
+ if (!session)
+ return false;
+
+ base::ScopedCFTypeRef<DADiskRef> disk(
+ DADiskCreateFromBSDName(kCFAllocatorDefault, session, bsd_name.c_str()));
+ if (!disk)
+ return false;
+
+ base::mac::ScopedIOObject<io_object_t> io_media(DADiskCopyIOMedia(disk));
+ base::ScopedCFTypeRef<CFDictionaryRef> characteristics(
+ static_cast<CFDictionaryRef>(IORegistryEntrySearchCFProperty(
+ io_media, kIOServicePlane, CFSTR(kIOPropertyDeviceCharacteristicsKey),
+ kCFAllocatorDefault,
+ kIORegistryIterateRecursively | kIORegistryIterateParents)));
+ if (!characteristics)
+ return false;
+
+ CFStringRef type_ref = base::mac::GetValueFromDictionary<CFStringRef>(
+ characteristics, CFSTR(kIOPropertyMediumTypeKey));
+ if (!type_ref)
+ return false;
+
+ NSString* type = base::mac::CFToNSCast(type_ref);
+ if ([type isEqualToString:@kIOPropertyMediumTypeRotationalKey]) {
+ *has_seek_penalty = true;
+ return true;
+ } else if ([type isEqualToString:@kIOPropertyMediumTypeSolidStateKey]) {
+ *has_seek_penalty = false;
+ return true;
+ }
+
+ // TODO(dbeam): should I look for these Rotational/Solid State keys in
+ // |characteristics|? What if I find device characteristic but there's no
+ // type? Assume rotational?
+ return false;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/drive_metrics_provider_unittest.cc b/chromium/components/metrics/drive_metrics_provider_unittest.cc
new file mode 100644
index 00000000000..142faf72be1
--- /dev/null
+++ b/chromium/components/metrics/drive_metrics_provider_unittest.cc
@@ -0,0 +1,20 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+TEST(DriveMetricsProviderTest, HasSeekPenalty) {
+ base::FilePath tmp_path;
+ ASSERT_TRUE(base::GetTempDir(&tmp_path));
+ bool unused;
+ DriveMetricsProvider::HasSeekPenalty(tmp_path, &unused);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/drive_metrics_provider_win.cc b/chromium/components/metrics/drive_metrics_provider_win.cc
new file mode 100644
index 00000000000..360b80e690a
--- /dev/null
+++ b/chromium/components/metrics/drive_metrics_provider_win.cc
@@ -0,0 +1,52 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+#include <windows.h>
+#include <winioctl.h>
+#include <vector>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/strings/stringprintf.h"
+#include "base/win/windows_version.h"
+
+namespace metrics {
+
+// static
+bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path,
+ bool* has_seek_penalty) {
+ if (base::win::GetVersion() < base::win::VERSION_WIN7) {
+ // TODO(dbeam): re-enable XP and Vista detection in a utility process.
+ return false;
+ }
+
+ std::vector<base::FilePath::StringType> components;
+ path.GetComponents(&components);
+
+ base::File volume(base::FilePath(L"\\\\.\\" + components[0]),
+ base::File::FLAG_OPEN);
+ if (!volume.IsValid())
+ return false;
+
+ STORAGE_PROPERTY_QUERY query = {};
+ query.QueryType = PropertyStandardQuery;
+ query.PropertyId = StorageDeviceSeekPenaltyProperty;
+
+ DEVICE_SEEK_PENALTY_DESCRIPTOR result;
+ DWORD bytes_returned;
+
+ BOOL success = DeviceIoControl(
+ volume.GetPlatformFile(), IOCTL_STORAGE_QUERY_PROPERTY, &query,
+ sizeof(query), &result, sizeof(result), &bytes_returned, NULL);
+
+ if (success == FALSE || bytes_returned < sizeof(result))
+ return false;
+
+ *has_seek_penalty = result.IncursSeekPenalty != FALSE;
+ return true;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/file_metrics_provider.cc b/chromium/components/metrics/file_metrics_provider.cc
new file mode 100644
index 00000000000..78fb90a1211
--- /dev/null
+++ b/chromium/components/metrics/file_metrics_provider.cc
@@ -0,0 +1,291 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/file_metrics_provider.h"
+
+#include "base/command_line.h"
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/persistent_histogram_allocator.h"
+#include "base/metrics/persistent_memory_allocator.h"
+#include "base/task_runner.h"
+#include "base/time/time.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_service.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+
+namespace metrics {
+
+// This structure stores all the information about the files being monitored
+// and their current reporting state.
+struct FileMetricsProvider::FileInfo {
+ FileInfo() {}
+ ~FileInfo() {}
+
+ // Where on disk the file is located.
+ base::FilePath path;
+
+ // How to access this file (atomic/active).
+ FileType type;
+
+ // Name used inside prefs to persistent metadata.
+ std::string prefs_key;
+
+ // The last-seen time of this file to detect change.
+ base::Time last_seen;
+
+ // Once a file has been recognized as needing to be read, it is |mapped|
+ // into memory. If that file is "atomic" then the data from that file
+ // will be copied to |data| and the mapped file released. If the file is
+ // "active", it remains mapped and nothing is copied to local memory.
+ std::vector<uint8_t> data;
+ scoped_ptr<base::MemoryMappedFile> mapped;
+ scoped_ptr<base::PersistentHistogramAllocator> allocator;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileInfo);
+};
+
+FileMetricsProvider::FileMetricsProvider(
+ const scoped_refptr<base::TaskRunner>& task_runner,
+ PrefService* local_state)
+ : task_runner_(task_runner),
+ pref_service_(local_state),
+ weak_factory_(this) {
+}
+
+FileMetricsProvider::~FileMetricsProvider() {}
+
+void FileMetricsProvider::RegisterFile(const base::FilePath& path,
+ FileType type,
+ const base::StringPiece prefs_key) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ scoped_ptr<FileInfo> file(new FileInfo());
+ file->path = path;
+ file->type = type;
+ file->prefs_key = prefs_key.as_string();
+
+ // |prefs_key| may be empty if the caller does not wish to persist the
+ // state across instances of the program.
+ if (pref_service_ && !prefs_key.empty()) {
+ file->last_seen = base::Time::FromInternalValue(
+ pref_service_->GetInt64(metrics::prefs::kMetricsLastSeenPrefix +
+ file->prefs_key));
+ }
+
+ files_to_check_.push_back(std::move(file));
+}
+
+// static
+void FileMetricsProvider::RegisterPrefs(PrefRegistrySimple* prefs,
+ const base::StringPiece prefs_key) {
+ prefs->RegisterInt64Pref(metrics::prefs::kMetricsLastSeenPrefix +
+ prefs_key.as_string(), 0);
+}
+
+// static
+void FileMetricsProvider::CheckAndMapNewMetricFilesOnTaskRunner(
+ FileMetricsProvider::FileInfoList* files) {
+ // This method has all state information passed in |files| and is intended
+ // to run on a worker thread rather than the UI thread.
+ for (scoped_ptr<FileInfo>& file : *files) {
+ AccessResult result = CheckAndMapNewMetrics(file.get());
+ // Some results are not reported in order to keep the dashboard clean.
+ if (result != ACCESS_RESULT_DOESNT_EXIST &&
+ result != ACCESS_RESULT_NOT_MODIFIED) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "UMA.FileMetricsProvider.AccessResult", result, ACCESS_RESULT_MAX);
+ }
+ }
+}
+
+// This method has all state information passed in |file| and is intended
+// to run on a worker thread rather than the UI thread.
+// static
+FileMetricsProvider::AccessResult FileMetricsProvider::CheckAndMapNewMetrics(
+ FileMetricsProvider::FileInfo* file) {
+ DCHECK(!file->mapped);
+ DCHECK(file->data.empty());
+
+ base::File::Info info;
+ if (!base::GetFileInfo(file->path, &info))
+ return ACCESS_RESULT_DOESNT_EXIST;
+
+ if (info.is_directory || info.size == 0)
+ return ACCESS_RESULT_INVALID_FILE;
+
+ if (file->last_seen >= info.last_modified)
+ return ACCESS_RESULT_NOT_MODIFIED;
+
+ // A new file of metrics has been found. Map it into memory.
+ // TODO(bcwhite): Make this open read/write when supported for "active".
+ file->mapped.reset(new base::MemoryMappedFile());
+ if (!file->mapped->Initialize(file->path)) {
+ file->mapped.reset();
+ return ACCESS_RESULT_SYSTEM_MAP_FAILURE;
+ }
+
+ // Ensure any problems below don't occur repeatedly.
+ file->last_seen = info.last_modified;
+
+ // Test the validity of the file contents.
+ if (!base::FilePersistentMemoryAllocator::IsFileAcceptable(*file->mapped))
+ return ACCESS_RESULT_INVALID_CONTENTS;
+
+ // For an "atomic" file, immediately copy the data into local memory and
+ // release the file so that it is not held open.
+ if (file->type == FILE_HISTOGRAMS_ATOMIC) {
+ file->data.assign(file->mapped->data(),
+ file->mapped->data() + file->mapped->length());
+ file->mapped.reset();
+ }
+
+ return ACCESS_RESULT_SUCCESS;
+}
+
+void FileMetricsProvider::ScheduleFilesCheck() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (files_to_check_.empty())
+ return;
+
+ // Create an independent list of files for checking. This will be Owned()
+ // by the reply call given to the task-runner, to be deleted when that call
+ // has returned. It is also passed Unretained() to the task itself, safe
+ // because that must complete before the reply runs.
+ FileInfoList* check_list = new FileInfoList();
+ std::swap(files_to_check_, *check_list);
+ task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&FileMetricsProvider::CheckAndMapNewMetricFilesOnTaskRunner,
+ base::Unretained(check_list)),
+ base::Bind(&FileMetricsProvider::RecordFilesChecked,
+ weak_factory_.GetWeakPtr(), base::Owned(check_list)));
+}
+
+void FileMetricsProvider::RecordHistogramSnapshotsFromFile(
+ base::HistogramSnapshotManager* snapshot_manager,
+ FileInfo* file) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ base::PersistentHistogramAllocator::Iterator histogram_iter(
+ file->allocator.get());
+
+ int histogram_count = 0;
+ while (true) {
+ scoped_ptr<base::HistogramBase> histogram = histogram_iter.GetNext();
+ if (!histogram)
+ break;
+ if (file->type == FILE_HISTOGRAMS_ATOMIC)
+ snapshot_manager->PrepareAbsoluteTakingOwnership(std::move(histogram));
+ else
+ snapshot_manager->PrepareDeltaTakingOwnership(std::move(histogram));
+ ++histogram_count;
+ }
+
+ DVLOG(1) << "Reported " << histogram_count << " histograms from "
+ << file->path.value();
+}
+
+void FileMetricsProvider::CreateAllocatorForFile(FileInfo* file) {
+ DCHECK(!file->allocator);
+
+ // File data was validated earlier. Files are not considered "untrusted"
+ // as some processes might be (e.g. Renderer) so there's no need to check
+ // again to try to thwart some malicious actor that may have modified the
+ // data between then and now.
+ if (file->mapped) {
+ DCHECK(file->data.empty());
+ // TODO(bcwhite): Make this do read/write when supported for "active".
+ file->allocator.reset(new base::PersistentHistogramAllocator(
+ make_scoped_ptr(new base::FilePersistentMemoryAllocator(
+ std::move(file->mapped), 0, ""))));
+ } else {
+ DCHECK(!file->mapped);
+ file->allocator.reset(new base::PersistentHistogramAllocator(
+ make_scoped_ptr(new base::PersistentMemoryAllocator(
+ &file->data[0], file->data.size(), 0, 0, "", true))));
+ }
+}
+
+void FileMetricsProvider::RecordFilesChecked(FileInfoList* checked) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Move each processed file to either the "to-read" list (for processing) or
+ // the "to-check" list (for future checking).
+ for (auto iter = checked->begin(); iter != checked->end();) {
+ auto temp = iter++;
+ const FileInfo* file = temp->get();
+ if (file->mapped || !file->data.empty())
+ files_to_read_.splice(files_to_read_.end(), *checked, temp);
+ else
+ files_to_check_.splice(files_to_check_.end(), *checked, temp);
+ }
+}
+
+void FileMetricsProvider::RecordFileAsSeen(FileInfo* file) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (pref_service_ && !file->prefs_key.empty()) {
+ pref_service_->SetInt64(metrics::prefs::kMetricsLastSeenPrefix +
+ file->prefs_key,
+ file->last_seen.ToInternalValue());
+ }
+}
+
+void FileMetricsProvider::OnDidCreateMetricsLog() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Move finished metric files back to list of monitored files.
+ for (auto iter = files_to_read_.begin(); iter != files_to_read_.end();) {
+ auto temp = iter++;
+ const FileInfo* file = temp->get();
+ if (!file->allocator && !file->mapped && file->data.empty())
+ files_to_check_.splice(files_to_check_.end(), files_to_read_, temp);
+ }
+
+ // Schedule a check to see if there are new metrics to load. If so, they
+ // will be reported during the next collection run after this one. The
+ // check is run off of the worker-pool so as to not cause delays on the
+ // main UI thread (which is currently where metric collection is done).
+ ScheduleFilesCheck();
+}
+
+void FileMetricsProvider::RecordHistogramSnapshots(
+ base::HistogramSnapshotManager* snapshot_manager) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ for (scoped_ptr<FileInfo>& file : files_to_read_) {
+ // If the file is mapped or loaded then it needs to have an allocator
+ // attached to it in order to read histograms out of it.
+ if (file->mapped || !file->data.empty())
+ CreateAllocatorForFile(file.get());
+
+ // A file should not be under "files to read" unless it has an allocator
+ // or is memory-mapped (at which point it will have received an allocator
+ // above). However, if this method gets called twice before the scheduled-
+ // files-check has a chance to clean up, this may trigger. This also
+ // catches the case where creating an allocator from the file has failed.
+ if (!file->allocator)
+ continue;
+
+ // Dump all histograms contained within the file to the snapshot-manager.
+ RecordHistogramSnapshotsFromFile(snapshot_manager, file.get());
+
+ // Atomic files are read once and then ignored unless they change.
+ if (file->type == FILE_HISTOGRAMS_ATOMIC) {
+ DCHECK(!file->mapped);
+ file->allocator.reset();
+ file->data.clear();
+ RecordFileAsSeen(file.get());
+ }
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/file_metrics_provider.h b/chromium/components/metrics/file_metrics_provider.h
new file mode 100644
index 00000000000..88c025f0ec1
--- /dev/null
+++ b/chromium/components/metrics/file_metrics_provider.h
@@ -0,0 +1,157 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_FILE_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_FILE_METRICS_PROVIDER_H_
+
+#include <list>
+#include <string>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "components/metrics/metrics_provider.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace base {
+class MemoryMappedFile;
+class PersistentMemoryAllocator;
+class TaskRunner;
+}
+
+namespace metrics {
+
+// FileMetricsProvider gathers and logs histograms written to files on disk.
+// Any number of files can be registered and will be polled once per upload
+// cycle (at startup and periodically thereafter -- about every 30 minutes
+// for desktop) for data to send.
+class FileMetricsProvider : public metrics::MetricsProvider {
+ public:
+ enum FileType {
+ // "Atomic" files are a collection of histograms that are written
+ // completely in a single atomic operation (typically a write followed
+ // by an atomic rename) and the file is never updated again except to
+ // be replaced by a completely new set of histograms. This is the only
+ // option that can be used if the file is not writeable by *this*
+ // process.
+ FILE_HISTOGRAMS_ATOMIC,
+
+ // "Active" files may be open by one or more other processes and updated
+ // at any time with new samples or new histograms. Such files may also be
+ // inactive for any period of time only to be opened again and have new
+ // data written to them. The file should probably never be deleted because
+ // there would be no guarantee that the data has been reported.
+ // TODO(bcwhite): Enable when read/write mem-mapped files are supported.
+ //FILE_HISTOGRAMS_ACTIVE,
+ };
+
+ FileMetricsProvider(const scoped_refptr<base::TaskRunner>& task_runner,
+ PrefService* local_state);
+ ~FileMetricsProvider() override;
+
+ // Indicates a file to be monitored and how the file is used. Because some
+ // metadata may persist across process restarts, preferences entries are
+ // used based on the |prefs_key| name. Call RegisterPrefs() with the same
+ // name to create the necessary keys in advance. Set |prefs_key| empty
+ // if no persistence is required.
+ void RegisterFile(const base::FilePath& path,
+ FileType type,
+ const base::StringPiece prefs_key);
+
+ // Registers all necessary preferences for maintaining persistent state
+ // about a monitored file across process restarts. The |prefs_key| is
+ // typically the filename.
+ static void RegisterPrefs(PrefRegistrySimple* prefs,
+ const base::StringPiece prefs_key);
+
+ private:
+ friend class FileMetricsProviderTest;
+ FRIEND_TEST_ALL_PREFIXES(FileMetricsProviderTest, AccessMetrics);
+
+ // The different results that can occur accessing a file.
+ enum AccessResult {
+ // File was successfully mapped.
+ ACCESS_RESULT_SUCCESS,
+
+ // File does not exist.
+ ACCESS_RESULT_DOESNT_EXIST,
+
+ // File exists but not modified since last read.
+ ACCESS_RESULT_NOT_MODIFIED,
+
+ // File is not valid: is a directory or zero-size.
+ ACCESS_RESULT_INVALID_FILE,
+
+ // System could not map file into memory.
+ ACCESS_RESULT_SYSTEM_MAP_FAILURE,
+
+ // File had invalid contents.
+ ACCESS_RESULT_INVALID_CONTENTS,
+
+ ACCESS_RESULT_MAX
+ };
+
+ // Information about files being monitored; defined and used exclusively
+ // inside the .cc file.
+ struct FileInfo;
+ using FileInfoList = std::list<scoped_ptr<FileInfo>>;
+
+ // Checks a list of files (on a task-runner allowed to do I/O) to see if
+ // any should be processed during the next histogram collection.
+ static void CheckAndMapNewMetricFilesOnTaskRunner(FileInfoList* files);
+
+ // Checks an individual file as part of CheckAndMapNewMetricFilesOnTaskRunner.
+ static AccessResult CheckAndMapNewMetrics(FileInfo* file);
+
+ // Creates a task to check all monitored files for updates.
+ void ScheduleFilesCheck();
+
+ // Creates a PersistentMemoryAllocator for a file that has been marked to
+ // have its metrics collected.
+ void CreateAllocatorForFile(FileInfo* file);
+
+ // Records all histograms from a given file via a snapshot-manager.
+ void RecordHistogramSnapshotsFromFile(
+ base::HistogramSnapshotManager* snapshot_manager,
+ FileInfo* file);
+
+ // Takes a list of files checked by an external task and determines what
+ // to do with each.
+ void RecordFilesChecked(FileInfoList* checked);
+
+ // Updates the persistent state information to show a file as being read.
+ void RecordFileAsSeen(FileInfo* file);
+
+ // metrics::MetricsDataProvider:
+ void OnDidCreateMetricsLog() override;
+ void RecordHistogramSnapshots(
+ base::HistogramSnapshotManager* snapshot_manager) override;
+
+ // A task-runner capable of performing I/O.
+ scoped_refptr<base::TaskRunner> task_runner_;
+
+ // A list of files not currently active that need to be checked for changes.
+ FileInfoList files_to_check_;
+
+ // A list of files that have data to be read and reported.
+ FileInfoList files_to_read_;
+
+ // The preferences-service used to store persistent state about files.
+ PrefService* pref_service_;
+
+ base::ThreadChecker thread_checker_;
+ base::WeakPtrFactory<FileMetricsProvider> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileMetricsProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_FILE_METRICS_PROVIDER_H_
diff --git a/chromium/components/metrics/file_metrics_provider_unittest.cc b/chromium/components/metrics/file_metrics_provider_unittest.cc
new file mode 100644
index 00000000000..0949c2d3690
--- /dev/null
+++ b/chromium/components/metrics/file_metrics_provider_unittest.cc
@@ -0,0 +1,192 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/file_metrics_provider.h"
+
+#include "base/files/file_util.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_flattener.h"
+#include "base/metrics/histogram_snapshot_manager.h"
+#include "base/metrics/persistent_histogram_allocator.h"
+#include "base/metrics/persistent_memory_allocator.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+const char kMetricsName[] = "TestMetrics";
+const char kMetricsFilename[] = "file.metrics";
+} // namespace
+
+namespace metrics {
+
+class HistogramFlattenerDeltaRecorder : public base::HistogramFlattener {
+ public:
+ HistogramFlattenerDeltaRecorder() {}
+
+ void RecordDelta(const base::HistogramBase& histogram,
+ const base::HistogramSamples& snapshot) override {
+ recorded_delta_histogram_names_.push_back(histogram.histogram_name());
+ }
+
+ void InconsistencyDetected(base::HistogramBase::Inconsistency problem)
+ override {
+ ASSERT_TRUE(false);
+ }
+
+ void UniqueInconsistencyDetected(
+ base::HistogramBase::Inconsistency problem) override {
+ ASSERT_TRUE(false);
+ }
+
+ void InconsistencyDetectedInLoggedCount(int amount) override {
+ ASSERT_TRUE(false);
+ }
+
+ std::vector<std::string> GetRecordedDeltaHistogramNames() {
+ return recorded_delta_histogram_names_;
+ }
+
+ private:
+ std::vector<std::string> recorded_delta_histogram_names_;
+
+ DISALLOW_COPY_AND_ASSIGN(HistogramFlattenerDeltaRecorder);
+};
+
+class FileMetricsProviderTest : public testing::Test {
+ protected:
+ FileMetricsProviderTest()
+ : task_runner_(new base::TestSimpleTaskRunner()),
+ thread_task_runner_handle_(task_runner_),
+ prefs_(new TestingPrefServiceSimple) {
+ EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
+ FileMetricsProvider::RegisterPrefs(prefs_->registry(), kMetricsName);
+ }
+
+ TestingPrefServiceSimple* prefs() { return prefs_.get(); }
+ base::FilePath temp_dir() { return temp_dir_.path(); }
+ base::FilePath metrics_file() {
+ return temp_dir_.path().AppendASCII(kMetricsFilename);
+ }
+
+ FileMetricsProvider* provider() {
+ if (!provider_)
+ provider_.reset(new FileMetricsProvider(task_runner_, prefs()));
+ return provider_.get();
+ }
+
+ void RunTasks() {
+ task_runner_->RunUntilIdle();
+ }
+
+ private:
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+ base::ThreadTaskRunnerHandle thread_task_runner_handle_;
+
+ base::ScopedTempDir temp_dir_;
+ scoped_ptr<TestingPrefServiceSimple> prefs_;
+ scoped_ptr<FileMetricsProvider> provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileMetricsProviderTest);
+};
+
+
+TEST_F(FileMetricsProviderTest, AccessMetrics) {
+ ASSERT_FALSE(PathExists(metrics_file()));
+
+ {
+ // Get this first so it isn't created inside the persistent allocator.
+ base::GlobalHistogramAllocator::GetCreateHistogramResultHistogram();
+
+ base::GlobalHistogramAllocator::CreateWithLocalMemory(
+ 64 << 10, 0, kMetricsName);
+ base::HistogramBase* foo =
+ base::Histogram::FactoryGet("foo", 1, 100, 10, 0);
+ base::HistogramBase* bar =
+ base::Histogram::FactoryGet("bar", 1, 100, 10, 0);
+ foo->Add(42);
+ bar->Add(84);
+
+ scoped_ptr<base::PersistentHistogramAllocator> histogram_allocator =
+ base::GlobalHistogramAllocator::ReleaseForTesting();
+ base::PersistentMemoryAllocator* allocator =
+ histogram_allocator->memory_allocator();
+ base::File writer(metrics_file(),
+ base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+ ASSERT_TRUE(writer.IsValid());
+ ASSERT_EQ(static_cast<int>(allocator->used()),
+ writer.Write(0, (const char*)allocator->data(),
+ allocator->used()));
+ }
+
+ // Register the file and allow the "checker" task to run.
+ ASSERT_TRUE(PathExists(metrics_file()));
+ provider()->RegisterFile(metrics_file(),
+ FileMetricsProvider::FILE_HISTOGRAMS_ATOMIC,
+ kMetricsName);
+
+ // Record embedded snapshots via snapshot-manager.
+ provider()->OnDidCreateMetricsLog();
+ RunTasks();
+ {
+ HistogramFlattenerDeltaRecorder flattener;
+ base::HistogramSnapshotManager snapshot_manager(&flattener);
+ snapshot_manager.StartDeltas();
+ provider()->RecordHistogramSnapshots(&snapshot_manager);
+ snapshot_manager.FinishDeltas();
+ EXPECT_EQ(2U, flattener.GetRecordedDeltaHistogramNames().size());
+ }
+
+ // Make sure a second call to the snapshot-recorder doesn't break anything.
+ {
+ HistogramFlattenerDeltaRecorder flattener;
+ base::HistogramSnapshotManager snapshot_manager(&flattener);
+ snapshot_manager.StartDeltas();
+ provider()->RecordHistogramSnapshots(&snapshot_manager);
+ snapshot_manager.FinishDeltas();
+ EXPECT_EQ(0U, flattener.GetRecordedDeltaHistogramNames().size());
+ }
+
+ // Second full run on the same file should produce nothing.
+ provider()->OnDidCreateMetricsLog();
+ RunTasks();
+ {
+ HistogramFlattenerDeltaRecorder flattener;
+ base::HistogramSnapshotManager snapshot_manager(&flattener);
+ snapshot_manager.StartDeltas();
+ provider()->RecordHistogramSnapshots(&snapshot_manager);
+ snapshot_manager.FinishDeltas();
+ EXPECT_EQ(0U, flattener.GetRecordedDeltaHistogramNames().size());
+ }
+
+ // Update the time-stamp of the file to indicate that it is "new" and
+ // must be recorded.
+ {
+ base::File touch(metrics_file(),
+ base::File::FLAG_OPEN | base::File::FLAG_WRITE);
+ ASSERT_TRUE(touch.IsValid());
+ base::Time next = base::Time::Now() + base::TimeDelta::FromSeconds(1);
+ touch.SetTimes(next, next);
+ }
+
+ // This run should again have "new" histograms.
+ provider()->OnDidCreateMetricsLog();
+ RunTasks();
+ {
+ HistogramFlattenerDeltaRecorder flattener;
+ base::HistogramSnapshotManager snapshot_manager(&flattener);
+ snapshot_manager.StartDeltas();
+ provider()->RecordHistogramSnapshots(&snapshot_manager);
+ snapshot_manager.FinishDeltas();
+ EXPECT_EQ(2U, flattener.GetRecordedDeltaHistogramNames().size());
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/gpu/DEPS b/chromium/components/metrics/gpu/DEPS
new file mode 100644
index 00000000000..c2ff8a0af67
--- /dev/null
+++ b/chromium/components/metrics/gpu/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+content/public/browser",
+ "+gpu/config",
+]
diff --git a/chromium/components/metrics/gpu/gpu_metrics_provider.cc b/chromium/components/metrics/gpu/gpu_metrics_provider.cc
new file mode 100644
index 00000000000..19cf9b49c8e
--- /dev/null
+++ b/chromium/components/metrics/gpu/gpu_metrics_provider.cc
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/gpu/gpu_metrics_provider.h"
+
+#include "components/metrics/proto/system_profile.pb.h"
+#include "content/public/browser/gpu_data_manager.h"
+#include "gpu/config/gpu_info.h"
+
+namespace metrics {
+
+GPUMetricsProvider::GPUMetricsProvider() {
+}
+
+GPUMetricsProvider::~GPUMetricsProvider() {
+}
+
+void GPUMetricsProvider::ProvideSystemProfileMetrics(
+ SystemProfileProto* system_profile_proto) {
+ SystemProfileProto::Hardware* hardware =
+ system_profile_proto->mutable_hardware();
+
+ const gpu::GPUInfo& gpu_info =
+ content::GpuDataManager::GetInstance()->GetGPUInfo();
+ SystemProfileProto::Hardware::Graphics* gpu =
+ hardware->mutable_gpu();
+ gpu->set_vendor_id(gpu_info.gpu.vendor_id);
+ gpu->set_device_id(gpu_info.gpu.device_id);
+ gpu->set_driver_version(gpu_info.driver_version);
+ gpu->set_driver_date(gpu_info.driver_date);
+ gpu->set_gl_vendor(gpu_info.gl_vendor);
+ gpu->set_gl_renderer(gpu_info.gl_renderer);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/gpu/gpu_metrics_provider.h b/chromium/components/metrics/gpu/gpu_metrics_provider.h
new file mode 100644
index 00000000000..581c7651ce6
--- /dev/null
+++ b/chromium/components/metrics/gpu/gpu_metrics_provider.h
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_GPU_GPU_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_GPU_GPU_METRICS_PROVIDER_H_
+
+#include "base/macros.h"
+#include "components/metrics/metrics_provider.h"
+
+namespace metrics {
+
+// GPUMetricsProvider provides GPU-related metrics.
+class GPUMetricsProvider : public MetricsProvider {
+ public:
+ GPUMetricsProvider();
+ ~GPUMetricsProvider() override;
+
+ // MetricsProvider:
+ void ProvideSystemProfileMetrics(
+ SystemProfileProto* system_profile_proto) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(GPUMetricsProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_GPU_GPU_METRICS_PROVIDER_H_
diff --git a/chromium/components/metrics/histogram_encoder.cc b/chromium/components/metrics/histogram_encoder.cc
new file mode 100644
index 00000000000..595fb539867
--- /dev/null
+++ b/chromium/components/metrics/histogram_encoder.cc
@@ -0,0 +1,57 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/histogram_encoder.h"
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/metrics_hashes.h"
+
+using base::SampleCountIterator;
+
+namespace metrics {
+
+void EncodeHistogramDelta(const std::string& histogram_name,
+ const base::HistogramSamples& snapshot,
+ ChromeUserMetricsExtension* uma_proto) {
+ DCHECK_NE(0, snapshot.TotalCount());
+ DCHECK(uma_proto);
+
+ // We will ignore the MAX_INT/infinite value in the last element of range[].
+
+ HistogramEventProto* histogram_proto = uma_proto->add_histogram_event();
+ histogram_proto->set_name_hash(base::HashMetricName(histogram_name));
+ if (snapshot.sum() != 0)
+ histogram_proto->set_sum(snapshot.sum());
+
+ for (scoped_ptr<SampleCountIterator> it = snapshot.Iterator(); !it->Done();
+ it->Next()) {
+ base::Histogram::Sample min;
+ base::Histogram::Sample max;
+ base::Histogram::Count count;
+ it->Get(&min, &max, &count);
+ HistogramEventProto::Bucket* bucket = histogram_proto->add_bucket();
+ bucket->set_min(min);
+ bucket->set_max(max);
+ // Note: The default for count is 1 in the proto, so omit it in that case.
+ if (count != 1)
+ bucket->set_count(count);
+ }
+
+ // Omit fields to save space (see rules in histogram_event.proto comments).
+ for (int i = 0; i < histogram_proto->bucket_size(); ++i) {
+ HistogramEventProto::Bucket* bucket = histogram_proto->mutable_bucket(i);
+ if (i + 1 < histogram_proto->bucket_size() &&
+ bucket->max() == histogram_proto->bucket(i + 1).min()) {
+ bucket->clear_max();
+ } else if (bucket->max() == bucket->min() + 1) {
+ bucket->clear_min();
+ }
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/histogram_encoder.h b/chromium/components/metrics/histogram_encoder.h
new file mode 100644
index 00000000000..8ef528de2b7
--- /dev/null
+++ b/chromium/components/metrics/histogram_encoder.h
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines an utility function that records any changes in a given
+// histogram for transmission.
+
+#ifndef COMPONENTS_METRICS_HISTOGRAM_ENCODER_H_
+#define COMPONENTS_METRICS_HISTOGRAM_ENCODER_H_
+
+#include <string>
+
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+
+namespace base {
+class HistogramSamples;
+}
+
+namespace metrics {
+
+// Record any changes (histogram deltas of counts from |snapshot|) into
+// |uma_proto| for the given histogram (|histogram_name|).
+void EncodeHistogramDelta(const std::string& histogram_name,
+ const base::HistogramSamples& snapshot,
+ ChromeUserMetricsExtension* uma_proto);
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_HISTOGRAM_ENCODER_H_
diff --git a/chromium/components/metrics/histogram_encoder_unittest.cc b/chromium/components/metrics/histogram_encoder_unittest.cc
new file mode 100644
index 00000000000..dfe7f847d5d
--- /dev/null
+++ b/chromium/components/metrics/histogram_encoder_unittest.cc
@@ -0,0 +1,71 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/histogram_encoder.h"
+
+#include <string>
+
+#include "base/metrics/bucket_ranges.h"
+#include "base/metrics/sample_vector.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+TEST(HistogramEncoder, HistogramBucketFields) {
+ // Create buckets: 1-5, 5-7, 7-8, 8-9, 9-10, 10-11, 11-12.
+ base::BucketRanges ranges(8);
+ ranges.set_range(0, 1);
+ ranges.set_range(1, 5);
+ ranges.set_range(2, 7);
+ ranges.set_range(3, 8);
+ ranges.set_range(4, 9);
+ ranges.set_range(5, 10);
+ ranges.set_range(6, 11);
+ ranges.set_range(7, 12);
+
+ base::SampleVector samples(1, &ranges);
+ samples.Accumulate(3, 1); // Bucket 1-5.
+ samples.Accumulate(6, 1); // Bucket 5-7.
+ samples.Accumulate(8, 1); // Bucket 8-9. (7-8 skipped)
+ samples.Accumulate(10, 1); // Bucket 10-11. (9-10 skipped)
+ samples.Accumulate(11, 1); // Bucket 11-12.
+
+ ChromeUserMetricsExtension uma_proto;
+ EncodeHistogramDelta("Test", samples, &uma_proto);
+
+ const HistogramEventProto& histogram_proto =
+ uma_proto.histogram_event(uma_proto.histogram_event_size() - 1);
+
+ // Buckets with samples: 1-5, 5-7, 8-9, 10-11, 11-12.
+ // Should become: 1-/, 5-7, /-9, 10-/, /-12.
+ ASSERT_EQ(5, histogram_proto.bucket_size());
+
+ // 1-5 becomes 1-/ (max is same as next min).
+ EXPECT_TRUE(histogram_proto.bucket(0).has_min());
+ EXPECT_FALSE(histogram_proto.bucket(0).has_max());
+ EXPECT_EQ(1, histogram_proto.bucket(0).min());
+
+ // 5-7 stays 5-7 (no optimization possible).
+ EXPECT_TRUE(histogram_proto.bucket(1).has_min());
+ EXPECT_TRUE(histogram_proto.bucket(1).has_max());
+ EXPECT_EQ(5, histogram_proto.bucket(1).min());
+ EXPECT_EQ(7, histogram_proto.bucket(1).max());
+
+ // 8-9 becomes /-9 (min is same as max - 1).
+ EXPECT_FALSE(histogram_proto.bucket(2).has_min());
+ EXPECT_TRUE(histogram_proto.bucket(2).has_max());
+ EXPECT_EQ(9, histogram_proto.bucket(2).max());
+
+ // 10-11 becomes 10-/ (both optimizations apply, omit max is prioritized).
+ EXPECT_TRUE(histogram_proto.bucket(3).has_min());
+ EXPECT_FALSE(histogram_proto.bucket(3).has_max());
+ EXPECT_EQ(10, histogram_proto.bucket(3).min());
+
+ // 11-12 becomes /-12 (last record must keep max, min is same as max - 1).
+ EXPECT_FALSE(histogram_proto.bucket(4).has_min());
+ EXPECT_TRUE(histogram_proto.bucket(4).has_max());
+ EXPECT_EQ(12, histogram_proto.bucket(4).max());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/OWNERS b/chromium/components/metrics/leak_detector/OWNERS
new file mode 100644
index 00000000000..d8bfc45aa54
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/OWNERS
@@ -0,0 +1,2 @@
+sque@chromium.org
+wfh@chromium.org
diff --git a/chromium/components/metrics/leak_detector/call_stack_manager.cc b/chromium/components/metrics/leak_detector/call_stack_manager.cc
new file mode 100644
index 00000000000..dfebd3c7251
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/call_stack_manager.cc
@@ -0,0 +1,72 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/call_stack_manager.h"
+
+#include <algorithm> // For std::copy.
+#include <new>
+
+#include "base/hash.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+
+namespace metrics {
+namespace leak_detector {
+
+CallStackManager::CallStackManager() {}
+
+CallStackManager::~CallStackManager() {
+ // Free all call stack objects and clear |call_stacks_|. Make sure to save the
+ // CallStack object pointer and remove it from the container before freeing
+ // the CallStack memory.
+ while (!call_stacks_.empty()) {
+ CallStack* call_stack = *call_stacks_.begin();
+ call_stacks_.erase(call_stacks_.begin());
+
+ CustomAllocator::Free(call_stack->stack,
+ call_stack->depth * sizeof(*call_stack->stack));
+ call_stack->stack = nullptr;
+ call_stack->depth = 0;
+
+ CustomAllocator::Free(call_stack, sizeof(CallStack));
+ }
+}
+
+const CallStack* CallStackManager::GetCallStack(size_t depth,
+ const void* const stack[]) {
+ // Temporarily create a call stack object for lookup in |call_stacks_|.
+ CallStack temp;
+ temp.depth = depth;
+ temp.stack = const_cast<const void**>(stack);
+ // This is the only place where the call stack's hash is computed. This value
+ // can be reused in the created object to avoid further hash computation.
+ temp.hash =
+ base::Hash(reinterpret_cast<const char*>(stack), sizeof(*stack) * depth);
+
+ auto iter = call_stacks_.find(&temp);
+ if (iter != call_stacks_.end())
+ return *iter;
+
+ // Since |call_stacks_| stores CallStack pointers rather than actual objects,
+ // create new call objects manually here.
+ CallStack* call_stack =
+ new (CustomAllocator::Allocate(sizeof(CallStack))) CallStack;
+ call_stack->depth = depth;
+ call_stack->hash = temp.hash; // Don't run the hash function again.
+ call_stack->stack = reinterpret_cast<const void**>(
+ CustomAllocator::Allocate(sizeof(*stack) * depth));
+ std::copy(stack, stack + depth, call_stack->stack);
+
+ call_stacks_.insert(call_stack);
+ return call_stack;
+}
+
+bool CallStackManager::CallStackPointerEqual::operator()(
+ const CallStack* c1,
+ const CallStack* c2) const {
+ return c1->depth == c2->depth && c1->hash == c2->hash &&
+ std::equal(c1->stack, c1->stack + c1->depth, c2->stack);
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/call_stack_manager.h b/chromium/components/metrics/leak_detector/call_stack_manager.h
new file mode 100644
index 00000000000..a411bcab8d9
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/call_stack_manager.h
@@ -0,0 +1,102 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_CALL_STACK_MANAGER_H_
+#define COMPONENTS_METRICS_LEAK_DETECTOR_CALL_STACK_MANAGER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/containers/hash_tables.h"
+#include "base/macros.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "components/metrics/leak_detector/stl_allocator.h"
+
+// Summary of structures:
+//
+// struct CallStack:
+// Represents a unique call stack, defined by its raw call stack (array of
+// pointers), and hash value. All CallStack objects are owned by class
+// CallStackManager. Other classes may hold pointers to them but should not
+// attempt to create or modify any CallStack objects.
+//
+// class CallStackManager:
+// Creates CallStack objects to represent unique call stacks. Owns all
+// CallStack objects that it creates, storing them internally.
+//
+// Returns the call stacks as const pointers because no caller should take
+// ownership of them and modify or delete them. The lifetime of all CallStack
+// objects is limited to that of the CallStackManager object that created
+// them. When the CallStackManager is destroyed, the CallStack objects will be
+// invalidated. Hence the caller should make sure that it does not use
+// CallStack objects beyond the lifetime of the CallStackManager that created
+// them.
+
+namespace metrics {
+namespace leak_detector {
+
+// Struct to represent a call stack.
+struct CallStack {
+ // Call stack as an array of pointers, |stack|. The array length is stored in
+ // |depth|. There is no null terminator.
+ const void** stack;
+ size_t depth;
+
+ // Hash of call stack. It is cached here so that it doesn not have to be
+ // recomputed each time.
+ size_t hash;
+};
+
+// Maintains and owns all unique call stack objects.
+// Not thread-safe.
+class CallStackManager {
+ public:
+ CallStackManager();
+ ~CallStackManager();
+
+ // Returns a CallStack object for a given raw call stack. The first time a
+ // particular raw call stack is passed into this function, it will create a
+ // new CallStack object to hold the raw call stack data, and then return it.
+ // The CallStack object is stored internally.
+ //
+ // On subsequent calls with the same raw call stack, this function will return
+ // the previously created CallStack object.
+ const CallStack* GetCallStack(size_t depth, const void* const stack[]);
+
+ size_t size() const { return call_stacks_.size(); }
+
+ private:
+ // Allocator class for unique call stacks. Uses CustomAllocator to avoid
+ // recursive malloc hook invocation when analyzing allocs and frees.
+ using CallStackPointerAllocator = STLAllocator<CallStack*, CustomAllocator>;
+
+ // Hash operator for call stack object given as a pointer.
+ // Does not actually compute the hash. Instead, returns the already computed
+ // hash value stored in a CallStack object.
+ struct CallStackPointerStoredHash {
+ size_t operator()(const CallStack* call_stack) const {
+ return call_stack->hash;
+ }
+ };
+
+ // Equality comparator for call stack objects given as pointers. Compares
+ // their stack trace contents.
+ struct CallStackPointerEqual {
+ bool operator()(const CallStack* c1, const CallStack* c2) const;
+ };
+
+ // Holds all call stack objects. Each object is allocated elsewhere and stored
+ // as a pointer because the container may rearrange itself internally.
+ base::hash_set<CallStack*,
+ CallStackPointerStoredHash,
+ CallStackPointerEqual,
+ CallStackPointerAllocator> call_stacks_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallStackManager);
+};
+
+} // namespace leak_detector
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_LEAK_DETECTOR_CALL_STACK_MANAGER_H_
diff --git a/chromium/components/metrics/leak_detector/call_stack_manager_unittest.cc b/chromium/components/metrics/leak_detector/call_stack_manager_unittest.cc
new file mode 100644
index 00000000000..f71bbfff47d
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/call_stack_manager_unittest.cc
@@ -0,0 +1,260 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/call_stack_manager.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+namespace leak_detector {
+
+namespace {
+
+// Some test call stacks. The addresses are 64-bit but they should automatically
+// be truncated to 32 bits on a 32-bit machine.
+const void* kRawStack0[] = {
+ reinterpret_cast<const void*>(0x8899aabbccddeeff),
+ reinterpret_cast<const void*>(0x0000112233445566),
+ reinterpret_cast<const void*>(0x5566778899aabbcc),
+ reinterpret_cast<const void*>(0x9988776655443322),
+};
+// This is similar to kRawStack0, differing only in one address by 1. It should
+// still produce a distinct CallStack object and hash.
+const void* kRawStack1[] = {
+ kRawStack0[0], kRawStack0[1],
+ reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(kRawStack0[2]) +
+ 1),
+ kRawStack0[3],
+};
+const void* kRawStack2[] = {
+ reinterpret_cast<const void*>(0x900df00dcab58888),
+ reinterpret_cast<const void*>(0x00001337cafedeed),
+ reinterpret_cast<const void*>(0x0000deafbabe1234),
+};
+const void* kRawStack3[] = {
+ reinterpret_cast<const void*>(0x0000000012345678),
+ reinterpret_cast<const void*>(0x00000000abcdef01),
+ reinterpret_cast<const void*>(0x00000000fdecab98),
+ reinterpret_cast<const void*>(0x0000deadbeef0001),
+ reinterpret_cast<const void*>(0x0000900ddeed0002),
+ reinterpret_cast<const void*>(0x0000f00dcafe0003),
+ reinterpret_cast<const void*>(0x0000f00d900d0004),
+ reinterpret_cast<const void*>(0xdeedcafebabe0005),
+};
+
+// Creates a copy of a call stack as a scoped_ptr to a raw stack. The depth is
+// the same as the original stack, but it is not stored in the result.
+scoped_ptr<const void* []> CopyStack(const CallStack* stack) {
+ scoped_ptr<const void* []> stack_copy(new const void*[stack->depth]);
+ std::copy(stack->stack, stack->stack + stack->depth, stack_copy.get());
+ return stack_copy;
+}
+
+} // namespace
+
+class CallStackManagerTest : public ::testing::Test {
+ public:
+ CallStackManagerTest() {}
+
+ void SetUp() override { CustomAllocator::Initialize(); }
+ void TearDown() override { EXPECT_TRUE(CustomAllocator::Shutdown()); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CallStackManagerTest);
+};
+
+TEST_F(CallStackManagerTest, NewStacks) {
+ CallStackManager manager;
+ EXPECT_EQ(0U, manager.size());
+
+ // Request some new stacks and make sure their creation is reflected in the
+ // size of |manager|.
+ const CallStack* stack0 =
+ manager.GetCallStack(arraysize(kRawStack0), kRawStack0);
+ EXPECT_EQ(arraysize(kRawStack0), stack0->depth);
+ EXPECT_EQ(1U, manager.size());
+
+ const CallStack* stack1 =
+ manager.GetCallStack(arraysize(kRawStack1), kRawStack1);
+ EXPECT_EQ(arraysize(kRawStack1), stack1->depth);
+ EXPECT_EQ(2U, manager.size());
+
+ const CallStack* stack2 =
+ manager.GetCallStack(arraysize(kRawStack2), kRawStack2);
+ EXPECT_EQ(arraysize(kRawStack2), stack2->depth);
+ EXPECT_EQ(3U, manager.size());
+
+ const CallStack* stack3 =
+ manager.GetCallStack(arraysize(kRawStack3), kRawStack3);
+ EXPECT_EQ(arraysize(kRawStack3), stack3->depth);
+ EXPECT_EQ(4U, manager.size());
+
+ // Call stack objects should be unique.
+ EXPECT_NE(stack0, stack1);
+ EXPECT_NE(stack0, stack2);
+ EXPECT_NE(stack0, stack3);
+ EXPECT_NE(stack1, stack2);
+ EXPECT_NE(stack1, stack3);
+ EXPECT_NE(stack2, stack3);
+}
+
+TEST_F(CallStackManagerTest, Hashes) {
+ CallStackManager manager;
+
+ const CallStack* stack0 =
+ manager.GetCallStack(arraysize(kRawStack0), kRawStack0);
+ const CallStack* stack1 =
+ manager.GetCallStack(arraysize(kRawStack1), kRawStack1);
+ const CallStack* stack2 =
+ manager.GetCallStack(arraysize(kRawStack2), kRawStack2);
+ const CallStack* stack3 =
+ manager.GetCallStack(arraysize(kRawStack3), kRawStack3);
+
+ // Hash values should be unique. This test is not designed to make sure the
+ // hash function is generating unique hashes, but that CallStackManager is
+ // properly storing the hashes in CallStack structs.
+ EXPECT_NE(stack0->hash, stack1->hash);
+ EXPECT_NE(stack0->hash, stack2->hash);
+ EXPECT_NE(stack0->hash, stack3->hash);
+ EXPECT_NE(stack1->hash, stack2->hash);
+ EXPECT_NE(stack1->hash, stack3->hash);
+ EXPECT_NE(stack2->hash, stack3->hash);
+}
+
+TEST_F(CallStackManagerTest, MultipleManagersHashes) {
+ CallStackManager manager1;
+ const CallStack* stack10 =
+ manager1.GetCallStack(arraysize(kRawStack0), kRawStack0);
+ const CallStack* stack11 =
+ manager1.GetCallStack(arraysize(kRawStack1), kRawStack1);
+ const CallStack* stack12 =
+ manager1.GetCallStack(arraysize(kRawStack2), kRawStack2);
+ const CallStack* stack13 =
+ manager1.GetCallStack(arraysize(kRawStack3), kRawStack3);
+
+ CallStackManager manager2;
+ const CallStack* stack20 =
+ manager2.GetCallStack(arraysize(kRawStack0), kRawStack0);
+ const CallStack* stack21 =
+ manager2.GetCallStack(arraysize(kRawStack1), kRawStack1);
+ const CallStack* stack22 =
+ manager2.GetCallStack(arraysize(kRawStack2), kRawStack2);
+ const CallStack* stack23 =
+ manager2.GetCallStack(arraysize(kRawStack3), kRawStack3);
+
+ // Different CallStackManagers should still generate the same hashes.
+ EXPECT_EQ(stack10->hash, stack20->hash);
+ EXPECT_EQ(stack11->hash, stack21->hash);
+ EXPECT_EQ(stack12->hash, stack22->hash);
+ EXPECT_EQ(stack13->hash, stack23->hash);
+}
+
+TEST_F(CallStackManagerTest, HashWithReducedDepth) {
+ CallStackManager manager;
+ const CallStack* stack =
+ manager.GetCallStack(arraysize(kRawStack3), kRawStack3);
+
+ // Hash function should only operate on the first |CallStack::depth| elements
+ // of CallStack::stack. To test this, reduce the depth value of one of the
+ // stacks and make sure the hash changes.
+ EXPECT_NE(stack->hash,
+ manager.GetCallStack(stack->depth - 1, stack->stack)->hash);
+ EXPECT_NE(stack->hash,
+ manager.GetCallStack(stack->depth - 2, stack->stack)->hash);
+ EXPECT_NE(stack->hash,
+ manager.GetCallStack(stack->depth - 3, stack->stack)->hash);
+ EXPECT_NE(stack->hash,
+ manager.GetCallStack(stack->depth - 4, stack->stack)->hash);
+
+ // Also try subsets of the stack that don't start from the beginning.
+ EXPECT_NE(stack->hash,
+ manager.GetCallStack(stack->depth - 1, stack->stack + 1)->hash);
+ EXPECT_NE(stack->hash,
+ manager.GetCallStack(stack->depth - 2, stack->stack + 2)->hash);
+ EXPECT_NE(stack->hash,
+ manager.GetCallStack(stack->depth - 3, stack->stack + 3)->hash);
+ EXPECT_NE(stack->hash,
+ manager.GetCallStack(stack->depth - 4, stack->stack + 4)->hash);
+}
+
+TEST_F(CallStackManagerTest, DuplicateStacks) {
+ CallStackManager manager;
+ EXPECT_EQ(0U, manager.size());
+
+ // Calling manager.GetCallStack() multiple times with the same raw stack
+ // arguments will not result in creation of new call stack objects after the
+ // first call. Instead, the previously created object will be returned, and
+ // the size of |manager| will remain unchanged.
+ //
+ // Thus a call to GetCallStack() will always return the same result, given the
+ // same inputs.
+
+ // Add stack0.
+ const CallStack* stack0 =
+ manager.GetCallStack(arraysize(kRawStack0), kRawStack0);
+
+ scoped_ptr<const void* []> rawstack0_duplicate0 = CopyStack(stack0);
+ const CallStack* stack0_duplicate0 =
+ manager.GetCallStack(arraysize(kRawStack0), rawstack0_duplicate0.get());
+ EXPECT_EQ(1U, manager.size());
+ EXPECT_EQ(stack0, stack0_duplicate0);
+
+ // Add stack1.
+ const CallStack* stack1 =
+ manager.GetCallStack(arraysize(kRawStack1), kRawStack1);
+ EXPECT_EQ(2U, manager.size());
+
+ scoped_ptr<const void* []> rawstack0_duplicate1 = CopyStack(stack0);
+ const CallStack* stack0_duplicate1 =
+ manager.GetCallStack(arraysize(kRawStack0), rawstack0_duplicate1.get());
+ EXPECT_EQ(2U, manager.size());
+ EXPECT_EQ(stack0, stack0_duplicate1);
+
+ scoped_ptr<const void* []> rawstack1_duplicate0 = CopyStack(stack1);
+ const CallStack* stack1_duplicate0 =
+ manager.GetCallStack(arraysize(kRawStack1), rawstack1_duplicate0.get());
+ EXPECT_EQ(2U, manager.size());
+ EXPECT_EQ(stack1, stack1_duplicate0);
+
+ // Add stack2 and stack3.
+ const CallStack* stack2 =
+ manager.GetCallStack(arraysize(kRawStack2), kRawStack2);
+ const CallStack* stack3 =
+ manager.GetCallStack(arraysize(kRawStack3), kRawStack3);
+ EXPECT_EQ(4U, manager.size());
+
+ scoped_ptr<const void* []> rawstack1_duplicate1 = CopyStack(stack1);
+ const CallStack* stack1_duplicate1 =
+ manager.GetCallStack(arraysize(kRawStack1), rawstack1_duplicate1.get());
+ EXPECT_EQ(4U, manager.size());
+ EXPECT_EQ(stack1, stack1_duplicate1);
+
+ scoped_ptr<const void* []> rawstack0_duplicate2 = CopyStack(stack0);
+ const CallStack* stack0_duplicate2 =
+ manager.GetCallStack(arraysize(kRawStack0), rawstack0_duplicate2.get());
+ EXPECT_EQ(4U, manager.size());
+ EXPECT_EQ(stack0, stack0_duplicate2);
+
+ scoped_ptr<const void* []> rawstack3_duplicate0 = CopyStack(stack3);
+ const CallStack* stack3_duplicate0 =
+ manager.GetCallStack(arraysize(kRawStack3), rawstack3_duplicate0.get());
+ EXPECT_EQ(4U, manager.size());
+ EXPECT_EQ(stack3, stack3_duplicate0);
+
+ scoped_ptr<const void* []> rawstack2_duplicate0 = CopyStack(stack2);
+ const CallStack* stack2_duplicate0 =
+ manager.GetCallStack(arraysize(kRawStack2), rawstack2_duplicate0.get());
+ EXPECT_EQ(4U, manager.size());
+ EXPECT_EQ(stack2, stack2_duplicate0);
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/call_stack_table.cc b/chromium/components/metrics/leak_detector/call_stack_table.cc
new file mode 100644
index 00000000000..8f9540bc0d4
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/call_stack_table.cc
@@ -0,0 +1,77 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/call_stack_table.h"
+
+#include <algorithm>
+
+#include "components/metrics/leak_detector/call_stack_manager.h"
+
+namespace metrics {
+namespace leak_detector {
+
+namespace {
+
+using ValueType = LeakDetectorValueType;
+
+// During leak analysis, we only want to examine the top
+// |kMaxCountOfSuspciousStacks| entries.
+const int kMaxCountOfSuspciousStacks = 16;
+
+const int kInitialHashTableSize = 1999;
+
+} // namespace
+
+size_t CallStackTable::StoredHash::operator()(
+ const CallStack* call_stack) const {
+ // The call stack object should already have a hash computed when it was
+ // created.
+ //
+ // This is NOT the actual hash computation function for a new call stack.
+ return call_stack->hash;
+}
+
+CallStackTable::CallStackTable(int call_stack_suspicion_threshold)
+ : num_allocs_(0),
+ num_frees_(0),
+ entry_map_(kInitialHashTableSize),
+ leak_analyzer_(kMaxCountOfSuspciousStacks,
+ call_stack_suspicion_threshold) {}
+
+CallStackTable::~CallStackTable() {}
+
+void CallStackTable::Add(const CallStack* call_stack) {
+ ++entry_map_[call_stack];
+ ++num_allocs_;
+}
+
+void CallStackTable::Remove(const CallStack* call_stack) {
+ auto iter = entry_map_.find(call_stack);
+ if (iter == entry_map_.end())
+ return;
+ uint32_t& count_for_call_stack = iter->second;
+ --count_for_call_stack;
+ ++num_frees_;
+
+ // Delete zero-alloc entries to free up space.
+ if (count_for_call_stack == 0)
+ entry_map_.erase(iter);
+}
+
+void CallStackTable::TestForLeaks() {
+ // Add all entries to the ranked list.
+ RankedSet ranked_entries(kMaxCountOfSuspciousStacks);
+ GetTopCallStacks(&ranked_entries);
+ leak_analyzer_.AddSample(std::move(ranked_entries));
+}
+
+void CallStackTable::GetTopCallStacks(RankedSet* top_entries) const {
+ for (const auto& call_stack_and_count : entry_map_) {
+ top_entries->AddCallStack(call_stack_and_count.first,
+ call_stack_and_count.second);
+ }
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/call_stack_table.h b/chromium/components/metrics/leak_detector/call_stack_table.h
new file mode 100644
index 00000000000..77a4b2a3ed7
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/call_stack_table.h
@@ -0,0 +1,87 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_CALL_STACK_TABLE_H_
+#define COMPONENTS_METRICS_LEAK_DETECTOR_CALL_STACK_TABLE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <functional> // For std::equal_to.
+
+#include "base/containers/hash_tables.h"
+#include "base/macros.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "components/metrics/leak_detector/leak_analyzer.h"
+#include "components/metrics/leak_detector/ranked_set.h"
+#include "components/metrics/leak_detector/stl_allocator.h"
+
+namespace metrics {
+namespace leak_detector {
+
+struct CallStack;
+
+// Contains a hash table where the key is the call stack and the value is the
+// number of allocations from that call stack.
+// Not thread-safe.
+class CallStackTable {
+ public:
+ struct StoredHash {
+ size_t operator()(const CallStack* call_stack) const;
+ };
+
+ explicit CallStackTable(int call_stack_suspicion_threshold);
+ ~CallStackTable();
+
+ // Add/Remove an allocation for the given call stack.
+ // Note that this class does NOT own the CallStack objects. Instead, it
+ // identifies different CallStacks by their hashes.
+ void Add(const CallStack* call_stack);
+ void Remove(const CallStack* call_stack);
+
+ // Check for leak patterns in the allocation data.
+ void TestForLeaks();
+
+ // Get the top N entries in the CallStackTable, ranked by net number of
+ // allocations. N is given by |top_entries->max_size()|, so |*top_entries|
+ // must already be initialized with that number.
+ void GetTopCallStacks(RankedSet* top_entries) const;
+
+ const LeakAnalyzer& leak_analyzer() const { return leak_analyzer_; }
+
+ size_t size() const { return entry_map_.size(); }
+ bool empty() const { return entry_map_.empty(); }
+
+ uint32_t num_allocs() const { return num_allocs_; }
+ uint32_t num_frees() const { return num_frees_; }
+
+ private:
+ // Total number of allocs and frees in this table.
+ uint32_t num_allocs_;
+ uint32_t num_frees_;
+
+ // Hash table containing entries. Uses CustomAllocator to avoid recursive
+ // malloc hook invocation when analyzing allocs and frees.
+ using TableEntryAllocator =
+ STLAllocator<std::pair<const CallStack* const, uint32_t>,
+ CustomAllocator>;
+
+ // Stores a mapping of each call stack to the number of recorded allocations
+ // made from that call site.
+ base::hash_map<const CallStack*,
+ uint32_t,
+ StoredHash,
+ std::equal_to<const CallStack*>,
+ TableEntryAllocator> entry_map_;
+
+ // For detecting leak patterns in incoming allocations.
+ LeakAnalyzer leak_analyzer_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallStackTable);
+};
+
+} // namespace leak_detector
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_LEAK_DETECTOR_CALL_STACK_TABLE_H_
diff --git a/chromium/components/metrics/leak_detector/call_stack_table_unittest.cc b/chromium/components/metrics/leak_detector/call_stack_table_unittest.cc
new file mode 100644
index 00000000000..6600ef714ac
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/call_stack_table_unittest.cc
@@ -0,0 +1,364 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/call_stack_table.h"
+
+#include <set>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/metrics/leak_detector/call_stack_manager.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+namespace leak_detector {
+
+namespace {
+
+// Default threshold used for leak analysis.
+const int kDefaultLeakThreshold = 5;
+
+// Some test call stacks.
+const void* kRawStack0[] = {
+ reinterpret_cast<const void*>(0xaabbccdd),
+ reinterpret_cast<const void*>(0x11223344),
+ reinterpret_cast<const void*>(0x55667788),
+ reinterpret_cast<const void*>(0x99887766),
+};
+const void* kRawStack1[] = {
+ reinterpret_cast<const void*>(0xdeadbeef),
+ reinterpret_cast<const void*>(0x900df00d),
+ reinterpret_cast<const void*>(0xcafedeed),
+ reinterpret_cast<const void*>(0xdeafbabe),
+};
+const void* kRawStack2[] = {
+ reinterpret_cast<const void*>(0x12345678),
+ reinterpret_cast<const void*>(0xabcdef01),
+ reinterpret_cast<const void*>(0xfdecab98),
+};
+const void* kRawStack3[] = {
+ reinterpret_cast<const void*>(0xdead0001),
+ reinterpret_cast<const void*>(0xbeef0002),
+ reinterpret_cast<const void*>(0x900d0003),
+ reinterpret_cast<const void*>(0xf00d0004),
+ reinterpret_cast<const void*>(0xcafe0005),
+ reinterpret_cast<const void*>(0xdeed0006),
+ reinterpret_cast<const void*>(0xdeaf0007),
+ reinterpret_cast<const void*>(0xbabe0008),
+};
+
+} // namespace
+
+class CallStackTableTest : public ::testing::Test {
+ public:
+ CallStackTableTest()
+ : stack0_(nullptr),
+ stack1_(nullptr),
+ stack2_(nullptr),
+ stack3_(nullptr) {}
+
+ void SetUp() override {
+ CustomAllocator::Initialize();
+
+ manager_.reset(new CallStackManager);
+
+ // The unit tests expect a certain order to the call stack pointers. It is
+ // an important detail when checking the output of LeakAnalyzer's suspected
+ // leaks, which are ordered by the leak value (call stack pointer). Use a
+ // set to sort the pointers as they are created.
+ std::set<const CallStack*> stacks;
+ stacks.insert(manager_->GetCallStack(arraysize(kRawStack0), kRawStack0));
+ stacks.insert(manager_->GetCallStack(arraysize(kRawStack1), kRawStack1));
+ stacks.insert(manager_->GetCallStack(arraysize(kRawStack2), kRawStack2));
+ stacks.insert(manager_->GetCallStack(arraysize(kRawStack3), kRawStack3));
+ ASSERT_EQ(4U, stacks.size());
+
+ std::set<const CallStack*>::const_iterator iter = stacks.begin();
+ stack0_ = *iter++;
+ stack1_ = *iter++;
+ stack2_ = *iter++;
+ stack3_ = *iter++;
+ }
+
+ void TearDown() override {
+ // All call stacks generated by |manager_| will be invalidated when it is
+ // destroyed.
+ stack0_ = nullptr;
+ stack1_ = nullptr;
+ stack2_ = nullptr;
+ stack3_ = nullptr;
+
+ // Destroy the call stack manager before shutting down the allocator.
+ manager_.reset();
+
+ EXPECT_TRUE(CustomAllocator::Shutdown());
+ }
+
+ protected:
+ // Unit tests should directly reference these pointers to CallStack objects.
+ const CallStack* stack0_;
+ const CallStack* stack1_;
+ const CallStack* stack2_;
+ const CallStack* stack3_;
+
+ private:
+ scoped_ptr<CallStackManager> manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallStackTableTest);
+};
+
+TEST_F(CallStackTableTest, PointerOrder) {
+ EXPECT_LT(stack0_, stack1_);
+ EXPECT_LT(stack1_, stack2_);
+ EXPECT_LT(stack2_, stack3_);
+}
+
+TEST_F(CallStackTableTest, EmptyTable) {
+ CallStackTable table(kDefaultLeakThreshold);
+ EXPECT_TRUE(table.empty());
+
+ EXPECT_EQ(0U, table.num_allocs());
+ EXPECT_EQ(0U, table.num_frees());
+
+ // The table should be able to gracefully handle an attempt to remove a call
+ // stack entry when none exists.
+ table.Remove(stack0_);
+ table.Remove(stack1_);
+ table.Remove(stack2_);
+ table.Remove(stack3_);
+
+ EXPECT_EQ(0U, table.num_allocs());
+ EXPECT_EQ(0U, table.num_frees());
+}
+
+TEST_F(CallStackTableTest, InsertionAndRemoval) {
+ CallStackTable table(kDefaultLeakThreshold);
+
+ table.Add(stack0_);
+ EXPECT_EQ(1U, table.size());
+ EXPECT_EQ(1U, table.num_allocs());
+ table.Add(stack1_);
+ EXPECT_EQ(2U, table.size());
+ EXPECT_EQ(2U, table.num_allocs());
+ table.Add(stack2_);
+ EXPECT_EQ(3U, table.size());
+ EXPECT_EQ(3U, table.num_allocs());
+ table.Add(stack3_);
+ EXPECT_EQ(4U, table.size());
+ EXPECT_EQ(4U, table.num_allocs());
+
+ // Add some call stacks that have already been added. There should be no
+ // change in the number of entries, as they are aggregated by call stack.
+ table.Add(stack2_);
+ EXPECT_EQ(4U, table.size());
+ EXPECT_EQ(5U, table.num_allocs());
+ table.Add(stack3_);
+ EXPECT_EQ(4U, table.size());
+ EXPECT_EQ(6U, table.num_allocs());
+
+ // Start removing entries.
+ EXPECT_EQ(0U, table.num_frees());
+
+ table.Remove(stack0_);
+ EXPECT_EQ(3U, table.size());
+ EXPECT_EQ(1U, table.num_frees());
+ table.Remove(stack1_);
+ EXPECT_EQ(2U, table.size());
+ EXPECT_EQ(2U, table.num_frees());
+
+ // Removing call stacks with multiple counts will not reduce the overall
+ // number of table entries, until the count reaches 0.
+ table.Remove(stack2_);
+ EXPECT_EQ(2U, table.size());
+ EXPECT_EQ(3U, table.num_frees());
+ table.Remove(stack3_);
+ EXPECT_EQ(2U, table.size());
+ EXPECT_EQ(4U, table.num_frees());
+
+ table.Remove(stack2_);
+ EXPECT_EQ(1U, table.size());
+ EXPECT_EQ(5U, table.num_frees());
+ table.Remove(stack3_);
+ EXPECT_EQ(0U, table.size());
+ EXPECT_EQ(6U, table.num_frees());
+
+ // Now the table should be empty, but attempt to remove some more and make
+ // sure nothing breaks.
+ table.Remove(stack0_);
+ table.Remove(stack1_);
+ table.Remove(stack2_);
+ table.Remove(stack3_);
+
+ EXPECT_TRUE(table.empty());
+ EXPECT_EQ(6U, table.num_allocs());
+ EXPECT_EQ(6U, table.num_frees());
+}
+
+TEST_F(CallStackTableTest, MassiveInsertionAndRemoval) {
+ CallStackTable table(kDefaultLeakThreshold);
+
+ for (int i = 0; i < 100; ++i)
+ table.Add(stack3_);
+ EXPECT_EQ(1U, table.size());
+ EXPECT_EQ(100U, table.num_allocs());
+
+ for (int i = 0; i < 100; ++i)
+ table.Add(stack2_);
+ EXPECT_EQ(2U, table.size());
+ EXPECT_EQ(200U, table.num_allocs());
+
+ for (int i = 0; i < 100; ++i)
+ table.Add(stack1_);
+ EXPECT_EQ(3U, table.size());
+ EXPECT_EQ(300U, table.num_allocs());
+
+ for (int i = 0; i < 100; ++i)
+ table.Add(stack0_);
+ EXPECT_EQ(4U, table.size());
+ EXPECT_EQ(400U, table.num_allocs());
+
+ // Remove them in a different order, by removing one of each stack during one
+ // iteration. The size should not decrease until the last iteration.
+ EXPECT_EQ(0U, table.num_frees());
+
+ for (int i = 0; i < 100; ++i) {
+ table.Remove(stack0_);
+ EXPECT_EQ(4U * i + 1, table.num_frees());
+
+ table.Remove(stack1_);
+ EXPECT_EQ(4U * i + 2, table.num_frees());
+
+ table.Remove(stack2_);
+ EXPECT_EQ(4U * i + 3, table.num_frees());
+
+ table.Remove(stack3_);
+ EXPECT_EQ(4U * i + 4, table.num_frees());
+ }
+ EXPECT_EQ(400U, table.num_frees());
+ EXPECT_TRUE(table.empty());
+
+ // Try to remove some more from an empty table and make sure nothing breaks.
+ table.Remove(stack0_);
+ table.Remove(stack1_);
+ table.Remove(stack2_);
+ table.Remove(stack3_);
+
+ EXPECT_TRUE(table.empty());
+ EXPECT_EQ(400U, table.num_allocs());
+ EXPECT_EQ(400U, table.num_frees());
+}
+
+TEST_F(CallStackTableTest, DetectLeak) {
+ CallStackTable table(kDefaultLeakThreshold);
+
+ // Add some base number of entries.
+ for (int i = 0; i < 60; ++i)
+ table.Add(stack0_);
+ for (int i = 0; i < 50; ++i)
+ table.Add(stack1_);
+ for (int i = 0; i < 64; ++i)
+ table.Add(stack2_);
+ for (int i = 0; i < 72; ++i)
+ table.Add(stack3_);
+
+ table.TestForLeaks();
+ EXPECT_TRUE(table.leak_analyzer().suspected_leaks().empty());
+
+ // Use the following scheme:
+ // - stack0_: increase by 4 each time -- leak suspect
+ // - stack1_: increase by 3 each time -- leak suspect
+ // - stack2_: increase by 1 each time -- not a suspect
+ // - stack3_: alternate between increasing and decreasing - not a suspect
+ bool increase_kstack3 = true;
+ for (int i = 0; i < kDefaultLeakThreshold; ++i) {
+ EXPECT_TRUE(table.leak_analyzer().suspected_leaks().empty());
+
+ for (int j = 0; j < 4; ++j)
+ table.Add(stack0_);
+
+ for (int j = 0; j < 3; ++j)
+ table.Add(stack1_);
+
+ table.Add(stack2_);
+
+ // Alternate between adding and removing.
+ if (increase_kstack3)
+ table.Add(stack3_);
+ else
+ table.Remove(stack3_);
+ increase_kstack3 = !increase_kstack3;
+
+ table.TestForLeaks();
+ }
+
+ // Check that the correct leak values have been detected.
+ const auto& leaks = table.leak_analyzer().suspected_leaks();
+ ASSERT_EQ(2U, leaks.size());
+ // Suspected leaks are reported in increasing leak value -- in this case, the
+ // CallStack object's address.
+ EXPECT_EQ(stack0_, leaks[0].call_stack());
+ EXPECT_EQ(stack1_, leaks[1].call_stack());
+}
+
+TEST_F(CallStackTableTest, GetTopCallStacks) {
+ CallStackTable table(kDefaultLeakThreshold);
+
+ // Add a bunch of entries.
+ for (int i = 0; i < 60; ++i)
+ table.Add(stack0_);
+ for (int i = 0; i < 50; ++i)
+ table.Add(stack1_);
+ for (int i = 0; i < 64; ++i)
+ table.Add(stack2_);
+ for (int i = 0; i < 72; ++i)
+ table.Add(stack3_);
+
+ // Get the call sites ordered from least to greatest number of entries.
+ RankedSet top_four(4);
+ table.GetTopCallStacks(&top_four);
+ ASSERT_EQ(4U, top_four.size());
+ auto iter = top_four.begin();
+ EXPECT_EQ(72, iter->count);
+ EXPECT_EQ(stack3_, iter->value.call_stack());
+ ++iter;
+ EXPECT_EQ(64, iter->count);
+ EXPECT_EQ(stack2_, iter->value.call_stack());
+ ++iter;
+ EXPECT_EQ(60, iter->count);
+ EXPECT_EQ(stack0_, iter->value.call_stack());
+ ++iter;
+ EXPECT_EQ(50, iter->count);
+ EXPECT_EQ(stack1_, iter->value.call_stack());
+
+ // Get the top three call sites ordered from least to greatest number of
+ // entries.
+ RankedSet top_three(3);
+ table.GetTopCallStacks(&top_three);
+ ASSERT_EQ(3U, top_three.size());
+ iter = top_three.begin();
+ EXPECT_EQ(72, iter->count);
+ EXPECT_EQ(stack3_, iter->value.call_stack());
+ ++iter;
+ EXPECT_EQ(64, iter->count);
+ EXPECT_EQ(stack2_, iter->value.call_stack());
+ ++iter;
+ EXPECT_EQ(60, iter->count);
+ EXPECT_EQ(stack0_, iter->value.call_stack());
+
+ // Get the top two call sites ordered from least to greatest number of
+ // entries.
+ RankedSet top_two(2);
+ table.GetTopCallStacks(&top_two);
+ ASSERT_EQ(2U, top_two.size());
+ iter = top_two.begin();
+ EXPECT_EQ(72, iter->count);
+ EXPECT_EQ(stack3_, iter->value.call_stack());
+ ++iter;
+ EXPECT_EQ(64, iter->count);
+ EXPECT_EQ(stack2_, iter->value.call_stack());
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/custom_allocator.cc b/chromium/components/metrics/leak_detector/custom_allocator.cc
new file mode 100644
index 00000000000..1f80a9712f8
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/custom_allocator.cc
@@ -0,0 +1,61 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/custom_allocator.h"
+
+#include <stddef.h>
+
+namespace metrics {
+namespace leak_detector {
+
+namespace {
+
+// Wrappers around new and delete.
+void* DefaultAlloc(size_t size) {
+ return new char[size];
+}
+void DefaultFree(void* ptr, size_t /* size */) {
+ delete[] reinterpret_cast<char*>(ptr);
+}
+
+CustomAllocator::AllocFunc g_alloc_func = nullptr;
+CustomAllocator::FreeFunc g_free_func = nullptr;
+
+} // namespace
+
+// static
+void CustomAllocator::Initialize() {
+ Initialize(&DefaultAlloc, &DefaultFree);
+}
+
+// static
+void CustomAllocator::Initialize(AllocFunc alloc_func, FreeFunc free_func) {
+ g_alloc_func = alloc_func;
+ g_free_func = free_func;
+}
+
+// static
+bool CustomAllocator::Shutdown() {
+ g_alloc_func = nullptr;
+ g_free_func = nullptr;
+ return true;
+}
+
+// static
+bool CustomAllocator::IsInitialized() {
+ return g_alloc_func && g_free_func;
+}
+
+// static
+void* CustomAllocator::Allocate(size_t size) {
+ return g_alloc_func(size);
+}
+
+// static
+void CustomAllocator::Free(void* ptr, size_t size) {
+ g_free_func(ptr, size);
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/custom_allocator.h b/chromium/components/metrics/leak_detector/custom_allocator.h
new file mode 100644
index 00000000000..fdbfc779b9f
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/custom_allocator.h
@@ -0,0 +1,50 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_CUSTOM_ALLOCATOR_H_
+#define COMPONENTS_METRICS_LEAK_DETECTOR_CUSTOM_ALLOCATOR_H_
+
+#include <stddef.h>
+
+#include <type_traits>
+
+namespace metrics {
+namespace leak_detector {
+
+// Custom allocator class to be passed to STLAllocator as a template argument.
+//
+// By default, CustomAllocator uses the default allocator (new/delete), but the
+// caller of Initialize ()can provide a pair of alternative alloc/ free
+// functions to use as an external allocator.
+//
+// This is a stateless class, but there is static data within the module that
+// needs to be created and deleted.
+//
+// Not thread-safe.
+class CustomAllocator {
+ public:
+ using AllocFunc = std::add_pointer<void*(size_t)>::type;
+ using FreeFunc = std::add_pointer<void(void*, size_t)>::type;
+
+ // Initialize CustomAllocator to use the default allocator.
+ static void Initialize();
+
+ // Initialize CustomAllocator to use the given alloc/free functions.
+ static void Initialize(AllocFunc alloc_func, FreeFunc free_func);
+
+ // Performs any cleanup required, e.g. unset custom functions. Returns true
+ // on success or false if something failed.
+ static bool Shutdown();
+
+ static bool IsInitialized();
+
+ // These functions must match the specifications in STLAllocator.
+ static void* Allocate(size_t size);
+ static void Free(void* ptr, size_t size);
+};
+
+} // namespace leak_detector
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_LEAK_DETECTOR_CUSTOM_ALLOCATOR_H_
diff --git a/chromium/components/metrics/leak_detector/leak_analyzer.cc b/chromium/components/metrics/leak_detector/leak_analyzer.cc
new file mode 100644
index 00000000000..54568ac212e
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_analyzer.cc
@@ -0,0 +1,139 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/leak_analyzer.h"
+
+#include <set>
+
+namespace metrics {
+namespace leak_detector {
+
+namespace {
+
+using RankedEntry = RankedSet::Entry;
+
+// Increase suspicion scores by this much each time an entry is suspected as
+// being a leak.
+const int kSuspicionScoreIncrease = 1;
+
+} // namespace
+
+LeakAnalyzer::LeakAnalyzer(uint32_t ranking_size,
+ uint32_t num_suspicions_threshold)
+ : ranking_size_(ranking_size),
+ score_threshold_(num_suspicions_threshold),
+ ranked_entries_(ranking_size),
+ prev_ranked_entries_(ranking_size) {
+ suspected_leaks_.reserve(ranking_size);
+}
+
+LeakAnalyzer::~LeakAnalyzer() {}
+
+void LeakAnalyzer::AddSample(RankedSet ranked_set) {
+ // Save the ranked entries from the previous call.
+ prev_ranked_entries_ = std::move(ranked_entries_);
+
+ // Save the current entries.
+ ranked_entries_ = std::move(ranked_set);
+
+ RankedSet ranked_deltas(ranking_size_);
+ for (const RankedEntry& entry : ranked_entries_) {
+ // Determine what count was recorded for this value last time.
+ uint32_t prev_count = 0;
+ if (GetPreviousCountForValue(entry.value, &prev_count))
+ ranked_deltas.Add(entry.value, entry.count - prev_count);
+ }
+
+ AnalyzeDeltas(ranked_deltas);
+}
+
+void LeakAnalyzer::AnalyzeDeltas(const RankedSet& ranked_deltas) {
+ bool found_drop = false;
+ RankedSet::const_iterator drop_position = ranked_deltas.end();
+
+ if (ranked_deltas.size() > 1) {
+ RankedSet::const_iterator entry_iter = ranked_deltas.begin();
+ RankedSet::const_iterator next_entry_iter = ranked_deltas.begin();
+ ++next_entry_iter;
+
+ // If the first entry is 0, that means all deltas are 0 or negative. Do
+ // not treat this as a suspicion of leaks; just quit.
+ if (entry_iter->count > 0) {
+ while (next_entry_iter != ranked_deltas.end()) {
+ const RankedEntry& entry = *entry_iter;
+ const RankedEntry& next_entry = *next_entry_iter;
+
+ // Find the first major drop in values (i.e. by 50% or more).
+ if (entry.count > next_entry.count * 2) {
+ found_drop = true;
+ drop_position = next_entry_iter;
+ break;
+ }
+ ++entry_iter;
+ ++next_entry_iter;
+ }
+ }
+ }
+
+ // All leak values before the drop are suspected during this analysis.
+ std::set<ValueType, std::less<ValueType>, Allocator<ValueType>>
+ current_suspects;
+ if (found_drop) {
+ for (RankedSet::const_iterator ranked_set_iter = ranked_deltas.begin();
+ ranked_set_iter != drop_position; ++ranked_set_iter) {
+ current_suspects.insert(ranked_set_iter->value);
+ }
+ }
+
+ // Reset the score to 0 for all previously suspected leak values that did
+ // not get suspected this time.
+ auto iter = suspected_histogram_.begin();
+ while (iter != suspected_histogram_.end()) {
+ const ValueType& value = iter->first;
+ // Erase entries whose suspicion score reaches 0.
+ auto erase_iter = iter++;
+ if (current_suspects.find(value) == current_suspects.end())
+ suspected_histogram_.erase(erase_iter);
+ }
+
+ // For currently suspected values, increase the leak score.
+ for (const ValueType& value : current_suspects) {
+ auto histogram_iter = suspected_histogram_.find(value);
+ if (histogram_iter != suspected_histogram_.end()) {
+ histogram_iter->second += kSuspicionScoreIncrease;
+ } else if (suspected_histogram_.size() < ranking_size_) {
+ // Create a new entry if it didn't already exist.
+ suspected_histogram_[value] = kSuspicionScoreIncrease;
+ }
+ }
+
+ // Now check the leak suspicion scores. Make sure to erase the suspected
+ // leaks from the previous call.
+ suspected_leaks_.clear();
+ for (const auto& entry : suspected_histogram_) {
+ if (suspected_leaks_.size() > ranking_size_)
+ break;
+
+ // Only report suspected values that have accumulated a suspicion score.
+ // This is achieved by maintaining suspicion for several cycles, with few
+ // skips.
+ if (entry.second >= score_threshold_)
+ suspected_leaks_.emplace_back(entry.first);
+ }
+}
+
+bool LeakAnalyzer::GetPreviousCountForValue(const ValueType& value,
+ uint32_t* count) const {
+ // Determine what count was recorded for this value last time.
+ for (const RankedEntry& entry : prev_ranked_entries_) {
+ if (entry.value == value) {
+ *count = entry.count;
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/leak_analyzer.h b/chromium/components/metrics/leak_detector/leak_analyzer.h
new file mode 100644
index 00000000000..55ca297cf6a
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_analyzer.h
@@ -0,0 +1,87 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_ANALYZER_H_
+#define COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_ANALYZER_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <vector>
+
+#include "base/macros.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "components/metrics/leak_detector/leak_detector_value_type.h"
+#include "components/metrics/leak_detector/ranked_set.h"
+#include "components/metrics/leak_detector/stl_allocator.h"
+
+namespace metrics {
+namespace leak_detector {
+
+// This class looks for possible leak patterns in allocation data over time.
+// Not thread-safe.
+class LeakAnalyzer {
+ public:
+ using ValueType = LeakDetectorValueType;
+
+ // This class uses CustomAllocator to avoid recursive malloc hook invocation
+ // when analyzing allocs and frees.
+ template <typename Type>
+ using Allocator = STLAllocator<Type, CustomAllocator>;
+
+ LeakAnalyzer(uint32_t ranking_size, uint32_t num_suspicions_threshold);
+ ~LeakAnalyzer();
+
+ // Take in a RankedSet of allocations, sorted by count. Removes the contents
+ // of |ranked_entries| to be stored internally, which is why it is not passed
+ // in as a const reference.
+ void AddSample(RankedSet ranked_entries);
+
+ // Used to report suspected leaks. Reported leaks are sorted by ValueType.
+ const std::vector<ValueType, Allocator<ValueType>>& suspected_leaks() const {
+ return suspected_leaks_;
+ }
+
+ private:
+ // Analyze a list of allocation count deltas from the previous iteration. If
+ // anything looks like a possible leak, update the suspicion scores.
+ void AnalyzeDeltas(const RankedSet& ranked_deltas);
+
+ // Returns the count for the given value from the previous analysis in
+ // |count|. Returns true if the given value was present in the previous
+ // analysis, or false if not.
+ bool GetPreviousCountForValue(const ValueType& value, uint32_t* count) const;
+
+ // Look for the top |ranking_size_| entries when analyzing leaks.
+ const uint32_t ranking_size_;
+
+ // Report suspected leaks when the suspicion score reaches this value.
+ const uint32_t score_threshold_;
+
+ // A mapping of allocation values to suspicion score. All allocations in this
+ // container are suspected leaks. The score can increase or decrease over
+ // time. Once the score reaches |score_threshold_|, the entry is reported as
+ // a suspected leak in |suspected_leaks_|.
+ std::map<ValueType,
+ uint32_t,
+ std::less<ValueType>,
+ Allocator<std::pair<const ValueType, uint32_t>>>
+ suspected_histogram_;
+
+ // Array of allocated values that passed the suspicion threshold and are being
+ // reported.
+ std::vector<ValueType, Allocator<ValueType>> suspected_leaks_;
+
+ // The most recent allocation entries, since the last call to AddSample().
+ RankedSet ranked_entries_;
+ // The previous allocation entries, from before the last call to AddSample().
+ RankedSet prev_ranked_entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(LeakAnalyzer);
+};
+
+} // namespace leak_detector
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_ANALYZER_H_
diff --git a/chromium/components/metrics/leak_detector/leak_analyzer_unittest.cc b/chromium/components/metrics/leak_detector/leak_analyzer_unittest.cc
new file mode 100644
index 00000000000..71555b4c21d
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_analyzer_unittest.cc
@@ -0,0 +1,366 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/leak_analyzer.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+
+#include "base/macros.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "components/metrics/leak_detector/ranked_set.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+namespace leak_detector {
+
+namespace {
+
+// Default ranking size and threshold used for leak analysis.
+const int kDefaultRankedSetSize = 10;
+const int kDefaultLeakThreshold = 5;
+
+// Makes it easier to instantiate LeakDetectorValueTypes. Instantiates with an
+// integer value that indicates an allocation size. Storing the size allows us
+// to track the storage of the LeakDetectorValueType object within LeakAnalyzer.
+//
+// There is no need to test this with call stacks in addition to sizes because
+// call stacks will be contained in a LeakDetectorValueType object as well.
+LeakDetectorValueType Size(uint32_t value) {
+ return LeakDetectorValueType(value);
+}
+
+} // namespace
+
+class LeakAnalyzerTest : public ::testing::Test {
+ public:
+ LeakAnalyzerTest() {}
+
+ void SetUp() override { CustomAllocator::Initialize(); }
+ void TearDown() override { EXPECT_TRUE(CustomAllocator::Shutdown()); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LeakAnalyzerTest);
+};
+
+TEST_F(LeakAnalyzerTest, Empty) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+}
+
+TEST_F(LeakAnalyzerTest, SingleSize) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ for (int i = 0; i < kDefaultLeakThreshold + 20; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 10);
+ analyzer.AddSample(std::move(set));
+
+ // No leaks should have been detected.
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ }
+}
+
+TEST_F(LeakAnalyzerTest, VariousSizesWithoutIncrease) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ for (int i = 0; i < kDefaultLeakThreshold + 20; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 30);
+ set.Add(Size(32), 10);
+ set.Add(Size(56), 90);
+ set.Add(Size(64), 40);
+ analyzer.AddSample(std::move(set));
+
+ // No leaks should have been detected.
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ }
+}
+
+TEST_F(LeakAnalyzerTest, VariousSizesWithEqualIncrease) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ for (int i = 0; i < kDefaultLeakThreshold + 20; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 30 + i * 10);
+ set.Add(Size(32), 10 + i * 10);
+ set.Add(Size(56), 90 + i * 10);
+ set.Add(Size(64), 40 + i * 10);
+ analyzer.AddSample(std::move(set));
+
+ // No leaks should have been detected.
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ }
+}
+
+TEST_F(LeakAnalyzerTest, NotEnoughRunsToTriggerLeakReport) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ // Run this one iteration short of the number of cycles needed to trigger a
+ // leak report. Because LeakAnalyzer requires |kDefaultLeakThreshold|
+ // suspicions based on deltas between AddSample() calls, the below loop needs
+ // to run |kDefaultLeakThreshold + 1| times to trigger a leak report.
+ for (int i = 0; i <= kDefaultLeakThreshold - 1; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 30 + i * 10); // This one has a potential leak.
+ set.Add(Size(32), 10 + i * 2);
+ set.Add(Size(56), 90 + i);
+ set.Add(Size(64), 40 + i / 2);
+ analyzer.AddSample(std::move(set));
+
+ // No leaks should have been detected.
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ }
+}
+
+TEST_F(LeakAnalyzerTest, LeakSingleSize) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ // Run this past the number of iterations required to trigger a leak report.
+ for (int i = 0; i < kDefaultLeakThreshold + 10; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(32), 10);
+ set.Add(Size(56), 90);
+ set.Add(Size(24), 30 + i * 10); // This one has a potential leak.
+ set.Add(Size(64), 40);
+ analyzer.AddSample(std::move(set));
+
+ // No leaks should have been detected initially...
+ if (i < kDefaultLeakThreshold) {
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ } else {
+ // ... but there should be reported leaks once the threshold is reached.
+ const auto& leaks = analyzer.suspected_leaks();
+ ASSERT_EQ(1U, leaks.size());
+ EXPECT_EQ(24U, leaks[0].size());
+ }
+ }
+}
+
+TEST_F(LeakAnalyzerTest, LeakSingleSizeOthersAlsoIncreasing) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ for (int i = 0; i < kDefaultLeakThreshold + 10; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 30 + i * 10); // This one has a potential leak.
+ set.Add(Size(32), 10 + i * 2);
+ set.Add(Size(56), 90 + i);
+ set.Add(Size(64), 40 + i / 2);
+ analyzer.AddSample(std::move(set));
+
+ // No leaks should have been detected initially...
+ if (i < kDefaultLeakThreshold) {
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ } else {
+ // ... but there should be reported leaks once the threshold is reached.
+ const auto& leaks = analyzer.suspected_leaks();
+ ASSERT_EQ(1U, leaks.size());
+ EXPECT_EQ(24U, leaks[0].size());
+ }
+ }
+}
+
+TEST_F(LeakAnalyzerTest, LeakMultipleSizes) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ for (int i = 0; i < kDefaultLeakThreshold + 10; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 30 + i * 5);
+ set.Add(Size(32), 10 + i * 40);
+ set.Add(Size(56), 90 + i * 30);
+ set.Add(Size(64), 40 + i * 20);
+ set.Add(Size(80), 20 + i * 3);
+ analyzer.AddSample(std::move(set));
+
+ // No leaks should have been detected initially...
+ if (i < kDefaultLeakThreshold) {
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ } else {
+ // ... but there should be reported leaks once the threshold is reached.
+ const auto& leaks = analyzer.suspected_leaks();
+ ASSERT_EQ(3U, leaks.size());
+ // These should be in order of increasing allocation size.
+ EXPECT_EQ(32U, leaks[0].size());
+ EXPECT_EQ(56U, leaks[1].size());
+ EXPECT_EQ(64U, leaks[2].size());
+ }
+ }
+}
+
+TEST_F(LeakAnalyzerTest, LeakMultipleSizesValueOrder) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ for (int i = 0; i <= kDefaultLeakThreshold; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ // These are similar to LeakMultipleSizes, but the relative order of
+ // allocation increases is different from the relative order of sizes.
+ set.Add(Size(24), 30 + i * 5);
+ set.Add(Size(32), 10 + i * 20);
+ set.Add(Size(56), 90 + i * 40);
+ set.Add(Size(64), 40 + i * 30);
+ set.Add(Size(80), 20 + i * 3);
+ analyzer.AddSample(std::move(set));
+ }
+
+ const auto& leaks = analyzer.suspected_leaks();
+ ASSERT_EQ(3U, leaks.size());
+ // These should be in order of increasing allocation size, NOT in order of
+ // allocation count or deltas.
+ EXPECT_EQ(32U, leaks[0].size());
+ EXPECT_EQ(56U, leaks[1].size());
+ EXPECT_EQ(64U, leaks[2].size());
+}
+
+TEST_F(LeakAnalyzerTest, EqualIncreasesNoLeak) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ for (int i = 0; i < kDefaultLeakThreshold + 20; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 30 + i * 10);
+ set.Add(Size(32), 10 + i * 10);
+ set.Add(Size(56), 90 + i * 10);
+ set.Add(Size(64), 40 + i * 10);
+ set.Add(Size(80), 20 + i * 10);
+ analyzer.AddSample(std::move(set));
+
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ }
+}
+
+TEST_F(LeakAnalyzerTest, NotBigEnoughDeltaGap) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ for (int i = 0; i < kDefaultLeakThreshold + 20; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ // These all have different increments but there is no clear group of
+ // increases that are larger than the rest.
+ set.Add(Size(24), 30 + i * 80);
+ set.Add(Size(32), 10 + i * 45);
+ set.Add(Size(56), 90 + i * 25);
+ set.Add(Size(64), 40 + i * 15);
+ set.Add(Size(80), 20 + i * 10);
+ analyzer.AddSample(std::move(set));
+
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ }
+}
+
+TEST_F(LeakAnalyzerTest, RepeatedRisesUntilLeakFound) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ // Remember, there is an extra iteration beyond |kDefaultLeakThreshold| needed
+ // to actually trigger the leak detection.
+ for (int i = 0; i <= kDefaultLeakThreshold - 2; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 30 + i * 10);
+ set.Add(Size(32), 10);
+ set.Add(Size(56), 90);
+ set.Add(Size(64), 40);
+ set.Add(Size(80), 20);
+ analyzer.AddSample(std::move(set));
+
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ }
+
+ // Drop back down to 30.
+ for (int i = 0; i <= kDefaultLeakThreshold - 1; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 30 + i * 10);
+ set.Add(Size(32), 10);
+ set.Add(Size(56), 90);
+ set.Add(Size(64), 40);
+ set.Add(Size(80), 20);
+ analyzer.AddSample(std::move(set));
+
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ }
+
+ // Drop back down to 30.
+ for (int i = 0; i <= kDefaultLeakThreshold; ++i) {
+ // Initially there should not be any leak detected.
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 30 + i * 10);
+ set.Add(Size(32), 10);
+ set.Add(Size(56), 90);
+ set.Add(Size(64), 40);
+ set.Add(Size(80), 20);
+ analyzer.AddSample(std::move(set));
+ }
+ const auto& leaks = analyzer.suspected_leaks();
+ ASSERT_EQ(1U, leaks.size());
+ EXPECT_EQ(24U, leaks[0].size());
+}
+
+TEST_F(LeakAnalyzerTest, LeakWithMultipleGroupsOfDeltas) {
+ const int kRankedSetSize = 20;
+ LeakAnalyzer analyzer(kRankedSetSize, kDefaultLeakThreshold);
+
+ for (int i = 0; i <= kDefaultLeakThreshold; ++i) {
+ RankedSet set(kRankedSetSize);
+ set.Add(Size(24), 30 + i * 10); // A group of smaller deltas.
+ set.Add(Size(32), 10 + i * 3);
+ set.Add(Size(80), 20 + i * 5);
+ set.Add(Size(40), 30 + i * 7);
+ set.Add(Size(56), 90);
+ set.Add(Size(64), 40);
+ set.Add(Size(128), 100);
+ set.Add(Size(44), 100 + i * 10); // A group of medium deltas.
+ set.Add(Size(16), 60 + i * 50);
+ set.Add(Size(4), 20 + i * 40);
+ set.Add(Size(8), 100 + i * 60);
+ set.Add(Size(48), 100);
+ set.Add(Size(72), 60 + i * 240); // A group of largest deltas.
+ set.Add(Size(28), 100);
+ set.Add(Size(100), 100 + i * 200);
+ set.Add(Size(104), 60 + i * 128);
+ analyzer.AddSample(std::move(set));
+ }
+ // Only the group of largest deltas should be caught.
+ const auto& leaks = analyzer.suspected_leaks();
+ ASSERT_EQ(3U, leaks.size());
+ // These should be in order of increasing allocation size.
+ EXPECT_EQ(72U, leaks[0].size());
+ EXPECT_EQ(100U, leaks[1].size());
+ EXPECT_EQ(104U, leaks[2].size());
+}
+
+TEST_F(LeakAnalyzerTest, LeakMultipleSizesWithLargeThreshold) {
+ const int kLeakThreshold = 50;
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kLeakThreshold);
+
+ for (int i = 0; i <= kLeakThreshold + 10; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ // * - Cluster of larger deltas
+ set.Add(Size(24), 30 + i * 5);
+ set.Add(Size(32), 10 + i * 40); // *
+ set.Add(Size(56), 90 + i * 30); // *
+ set.Add(Size(40), 30 + i * 7);
+ set.Add(Size(64), 40 + i * 25); // *
+ set.Add(Size(80), 20 + i * 3);
+ set.Add(Size(128), 100);
+ set.Add(Size(44), 100 + i * 10);
+ set.Add(Size(16), 60 + i * 50); // *
+ analyzer.AddSample(std::move(set));
+
+ // No leaks should have been detected initially...
+ if (i < kLeakThreshold) {
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ } else {
+ // ... but there should be reported leaks once the threshold is reached.
+ const auto& leaks = analyzer.suspected_leaks();
+ ASSERT_EQ(4U, leaks.size());
+ // These should be in order of increasing allocation size.
+ EXPECT_EQ(16U, leaks[0].size());
+ EXPECT_EQ(32U, leaks[1].size());
+ EXPECT_EQ(56U, leaks[2].size());
+ EXPECT_EQ(64U, leaks[3].size());
+ }
+ }
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/leak_detector.cc b/chromium/components/metrics/leak_detector/leak_detector.cc
new file mode 100644
index 00000000000..a7151a86382
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_detector.cc
@@ -0,0 +1,340 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/leak_detector.h"
+
+#include <stdint.h>
+
+#include "base/allocator/allocator_extension.h"
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/threading/thread_local.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "components/metrics/leak_detector/leak_detector_impl.h"
+#include "content/public/browser/browser_thread.h"
+
+#if defined(OS_CHROMEOS)
+#include <link.h> // for dl_iterate_phdr
+#else
+#error "Getting binary mapping info is not supported on this platform."
+#endif // defined(OS_CHROMEOS)
+
+namespace metrics {
+
+using LeakReport = LeakDetector::LeakReport;
+using InternalLeakReport = leak_detector::LeakDetectorImpl::LeakReport;
+template <typename T>
+using InternalVector = leak_detector::LeakDetectorImpl::InternalVector<T>;
+
+namespace {
+
+// Add the thread-local alloc size count to the shared alloc size count
+// (LeakDetector::total_alloc_size_) whenever the local counter reaches
+// |LeakDetector::analysis_interval_bytes_| divided by this value. Choose a
+// high enough value that there is plenty of granularity, but low enough that a
+// thread is not frequently updating the shared counter.
+const int kTotalAllocSizeUpdateIntervalDivisor = 1024;
+
+#if defined(OS_CHROMEOS)
+// For storing the address range of the Chrome binary in memory.
+struct MappingInfo {
+ uintptr_t addr;
+ size_t size;
+};
+#endif // defined(OS_CHROMEOS)
+
+// Local data to be used in the alloc/free hook functions to keep track of
+// things across hook function calls.
+struct HookData {
+ // The total number of bytes nominally allocated from the allocator on the
+ // current thread.
+ size_t alloc_size;
+
+ // Flag indicating that one of the alloc hooks have already been entered. Used
+ // to handle recursive hook calls. Anything allocated when this flag is set
+ // should also be freed when this flag is set.
+ bool entered_hook;
+};
+
+#if defined(OS_CHROMEOS)
+// Callback for dl_iterate_phdr() to find the Chrome binary mapping.
+int IterateLoadedObjects(struct dl_phdr_info* shared_object,
+ size_t /* size */,
+ void* data) {
+ for (int i = 0; i < shared_object->dlpi_phnum; i++) {
+ // Find the ELF segment header that contains the actual code of the Chrome
+ // binary.
+ const ElfW(Phdr)& segment_header = shared_object->dlpi_phdr[i];
+ if (segment_header.p_type == SHT_PROGBITS && segment_header.p_offset == 0 &&
+ data) {
+ MappingInfo* mapping = reinterpret_cast<MappingInfo*>(data);
+
+ // Make sure the fields in the ELF header and MappingInfo have the
+ // same size.
+ static_assert(sizeof(mapping->addr) == sizeof(shared_object->dlpi_addr),
+ "Integer size mismatch between MappingInfo::addr and "
+ "dl_phdr_info::dlpi_addr.");
+ static_assert(sizeof(mapping->size) == sizeof(segment_header.p_offset),
+ "Integer size mismatch between MappingInfo::size and "
+ "ElfW(Phdr)::p_memsz.");
+
+ mapping->addr = shared_object->dlpi_addr + segment_header.p_offset;
+ mapping->size = segment_header.p_memsz;
+ return 1;
+ }
+ }
+ return 0;
+}
+#endif // defined(OS_CHROMEOS)
+
+// Convert a pointer to a hash value. Returns only the upper eight bits.
+inline uint64_t PointerToHash(const void* ptr) {
+ // The input data is the pointer address, not the location in memory pointed
+ // to by the pointer.
+ // The multiplier is taken from Farmhash code:
+ // https://github.com/google/farmhash/blob/master/src/farmhash.cc
+ const uint64_t kMultiplier = 0x9ddfea08eb382d69ULL;
+ return reinterpret_cast<uint64_t>(ptr) * kMultiplier;
+}
+
+// Converts a vector of leak reports generated by LeakDetectorImpl
+// (InternalLeakReport) to a vector of leak reports suitable for sending to
+// LeakDetector's observers (LeakReport).
+void GetReportsForObservers(
+ const InternalVector<InternalLeakReport>& leak_reports,
+ std::vector<LeakReport>* reports_for_observers) {
+ reports_for_observers->clear();
+ reports_for_observers->reserve(leak_reports.size());
+ for (const InternalLeakReport& report : leak_reports) {
+ reports_for_observers->push_back(LeakReport());
+ LeakReport* new_report = &reports_for_observers->back();
+
+ new_report->alloc_size_bytes = report.alloc_size_bytes();
+ if (!report.call_stack().empty()) {
+ new_report->call_stack.resize(report.call_stack().size());
+ memcpy(new_report->call_stack.data(), report.call_stack().data(),
+ report.call_stack().size() * sizeof(report.call_stack()[0]));
+ }
+ }
+}
+
+// The only instance of LeakDetector that should be used.
+base::LazyInstance<LeakDetector>::Leaky g_instance = LAZY_INSTANCE_INITIALIZER;
+
+// Thread-specific data to be used by hook functions.
+base::LazyInstance<base::ThreadLocalPointer<void>>::Leaky g_hook_data_tls =
+ LAZY_INSTANCE_INITIALIZER;
+
+// Returns the contents of |g_hook_data_tls| as a HookData structure.
+inline HookData LoadHookDataFromTLS() {
+ uintptr_t ptr_value =
+ reinterpret_cast<uintptr_t>(g_hook_data_tls.Get().Get());
+
+ // The lower bit of |ptr_value| indicates whether a hook has already been
+ // entered. The remaining bits store the alloc size.
+ HookData result;
+ result.entered_hook = ptr_value & 0x01;
+ result.alloc_size = ptr_value >> 1;
+ return result;
+}
+
+// Stores a HookData structure in |g_hook_data_tls|. HookData is a trivial
+// struct so it is faster to pass by value.
+inline void StoreHookDataToTLS(HookData hook_data) {
+ // NOTE: |alloc_size| loses its upper bit when it gets stored in the TLS here.
+ // The effective max value of |alloc_size| is thus half its nominal max value.
+ uintptr_t ptr_value =
+ (hook_data.entered_hook ? 1 : 0) | (hook_data.alloc_size << 1);
+ g_hook_data_tls.Get().Set(reinterpret_cast<void*>(ptr_value));
+}
+
+} // namespace
+
+LeakDetector::LeakReport::LeakReport() {}
+
+LeakDetector::LeakReport::LeakReport(const LeakReport& other) = default;
+
+LeakDetector::LeakReport::~LeakReport() {}
+
+// static
+LeakDetector* LeakDetector::GetInstance() {
+ return g_instance.Pointer();
+}
+
+void LeakDetector::Init(float sampling_rate,
+ size_t max_call_stack_unwind_depth,
+ uint64_t analysis_interval_bytes,
+ uint32_t size_suspicion_threshold,
+ uint32_t call_stack_suspicion_threshold) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(sampling_rate > 0) << "Sampling rate cannot be zero or negative.";
+
+ sampling_factor_ = base::saturated_cast<uint64_t>(sampling_rate * UINT64_MAX);
+
+ analysis_interval_bytes_ = analysis_interval_bytes;
+ max_call_stack_unwind_depth_ = max_call_stack_unwind_depth;
+
+ MappingInfo mapping = {0};
+#if defined(OS_CHROMEOS)
+ // Locate the Chrome binary mapping info.
+ dl_iterate_phdr(IterateLoadedObjects, &mapping);
+#endif // defined(OS_CHROMEOS)
+
+ // CustomAllocator can use the default allocator, as long as the hook
+ // functions can handle recursive calls.
+ leak_detector::CustomAllocator::Initialize();
+
+ // The initialization should be done only once. Check for this by examining
+ // whether |impl_| has already been initialized.
+ CHECK(!impl_.get()) << "Cannot initialize LeakDetector more than once!";
+ impl_.reset(new leak_detector::LeakDetectorImpl(
+ mapping.addr, mapping.size, size_suspicion_threshold,
+ call_stack_suspicion_threshold));
+
+ // Register allocator hook functions. This must be done last since the
+ // preceding code will need to call the allocator.
+ base::allocator::SetHooks(&AllocHook, &FreeHook);
+}
+
+void LeakDetector::AddObserver(Observer* observer) {
+ base::AutoLock lock(observers_lock_);
+ observers_.AddObserver(observer);
+}
+
+void LeakDetector::RemoveObserver(Observer* observer) {
+ base::AutoLock lock(observers_lock_);
+ observers_.RemoveObserver(observer);
+}
+
+LeakDetector::LeakDetector()
+ : total_alloc_size_(0),
+ last_analysis_alloc_size_(0),
+ analysis_interval_bytes_(0),
+ max_call_stack_unwind_depth_(0),
+ sampling_factor_(0) {}
+
+LeakDetector::~LeakDetector() {}
+
+// static
+void LeakDetector::AllocHook(const void* ptr, size_t size) {
+ HookData hook_data = LoadHookDataFromTLS();
+ if (hook_data.entered_hook)
+ return;
+
+ hook_data.alloc_size += size;
+
+ LeakDetector* detector = GetInstance();
+ if (!detector->ShouldSample(ptr)) {
+ StoreHookDataToTLS(hook_data);
+ return;
+ }
+
+ hook_data.entered_hook = true;
+ StoreHookDataToTLS(hook_data);
+
+ // Get stack trace if necessary.
+ std::vector<void*> stack;
+ int depth = 0;
+ if (detector->impl_->ShouldGetStackTraceForSize(size)) {
+ stack.resize(detector->max_call_stack_unwind_depth_);
+ depth = base::allocator::GetCallStack(stack.data(), stack.size());
+ }
+
+ {
+ base::AutoLock lock(detector->recording_lock_);
+ detector->impl_->RecordAlloc(ptr, size, depth, stack.data());
+
+ const auto& analysis_interval_bytes = detector->analysis_interval_bytes_;
+ auto& total_alloc_size = detector->total_alloc_size_;
+ // Update the shared counter, |detector->total_alloc_size_|, once the local
+ // counter reaches a threshold that is a fraction of the analysis interval.
+ // The fraction should be small enough (and hence the value of
+ // kTotalAllocSizeUpdateIntervalDivisor should be large enough) that the
+ // shared counter is updated with sufficient granularity. This way, even if
+ // a few threads were slow to reach the threshold, the leak analysis would
+ // not be delayed by too much.
+ if (hook_data.alloc_size >=
+ analysis_interval_bytes / kTotalAllocSizeUpdateIntervalDivisor) {
+ total_alloc_size += hook_data.alloc_size;
+ hook_data.alloc_size = 0;
+ }
+
+ // Check for leaks after |analysis_interval_bytes_| bytes have been
+ // allocated since the last time that was done.
+ if (total_alloc_size >
+ detector->last_analysis_alloc_size_ + analysis_interval_bytes) {
+ // Try to maintain regular intervals of size |analysis_interval_bytes_|.
+ detector->last_analysis_alloc_size_ =
+ total_alloc_size - total_alloc_size % analysis_interval_bytes;
+
+ InternalVector<InternalLeakReport> leak_reports;
+ detector->impl_->TestForLeaks(&leak_reports);
+
+ // Pass leak reports to observers.
+ std::vector<LeakReport> leak_reports_for_observers;
+ GetReportsForObservers(leak_reports, &leak_reports_for_observers);
+ detector->NotifyObservers(leak_reports_for_observers);
+ }
+ }
+
+ {
+ // The internal memory of |stack| should be freed before setting
+ // |entered_hook| to false at the end of this function. Free it here by
+ // moving the internal memory to a temporary variable that will go out of
+ // scope.
+ std::vector<void*> dummy_stack;
+ dummy_stack.swap(stack);
+ }
+
+ hook_data.entered_hook = false;
+ StoreHookDataToTLS(hook_data);
+}
+
+// static
+void LeakDetector::FreeHook(const void* ptr) {
+ LeakDetector* detector = GetInstance();
+ if (!detector->ShouldSample(ptr))
+ return;
+
+ HookData hook_data = LoadHookDataFromTLS();
+ if (hook_data.entered_hook)
+ return;
+
+ hook_data.entered_hook = true;
+ StoreHookDataToTLS(hook_data);
+
+ {
+ base::AutoLock lock(detector->recording_lock_);
+ detector->impl_->RecordFree(ptr);
+ }
+
+ hook_data.entered_hook = false;
+ StoreHookDataToTLS(hook_data);
+}
+
+inline bool LeakDetector::ShouldSample(const void* ptr) const {
+ return PointerToHash(ptr) < sampling_factor_;
+}
+
+void LeakDetector::NotifyObservers(const std::vector<LeakReport>& reports) {
+ if (reports.empty())
+ return;
+
+ if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI, FROM_HERE,
+ base::Bind(&LeakDetector::NotifyObservers, base::Unretained(this),
+ reports));
+ return;
+ }
+
+ for (const LeakReport& report : reports) {
+ base::AutoLock lock(observers_lock_);
+ FOR_EACH_OBSERVER(Observer, observers_, OnLeakFound(report));
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/leak_detector.h b/chromium/components/metrics/leak_detector/leak_detector.h
new file mode 100644
index 00000000000..8dcba5e9de2
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_detector.h
@@ -0,0 +1,174 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_H_
+#define COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <list>
+#include <vector>
+
+#include "base/feature_list.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_checker.h"
+
+namespace base {
+template <typename T>
+struct DefaultLazyInstanceTraits;
+}
+
+namespace metrics {
+
+namespace leak_detector {
+class LeakDetectorImpl;
+}
+
+// LeakDetector is an interface layer that connects the allocator
+// (base::allocator), the leak detector logic (LeakDetectorImpl), and any
+// external classes interested in receiving leak reports (extend the Observer
+// class).
+//
+// Only one instance of this class can exist. Access this instance using
+// GetInstance(). Do not create an instance of this class directly.
+//
+// These member functions are thread-safe:
+// - AllocHook
+// - FreeHook
+// - AddObserver
+// - RemoveObserver
+//
+// All other functions must always be called from the same thread. This is
+// enforced with a DCHECK.
+class LeakDetector {
+ public:
+ // Contains a report of a detected memory leak.
+ struct LeakReport {
+ LeakReport();
+ LeakReport(const LeakReport& other);
+ ~LeakReport();
+
+ size_t alloc_size_bytes;
+
+ // Unlike the CallStack struct, which consists of addresses, this call stack
+ // will contain offsets in the executable binary.
+ std::vector<uintptr_t> call_stack;
+ };
+
+ // Interface for receiving leak reports.
+ class Observer {
+ public:
+ virtual ~Observer() {}
+
+ // Called by leak detector to report a leak.
+ virtual void OnLeakFound(const LeakReport& report) = 0;
+ };
+
+ // Returns the sole instance, or creates it if it hasn't already been created.
+ static LeakDetector* GetInstance();
+
+ // Initializer arguments:
+ // sampling_rate:
+ // Pseudorandomly sample a fraction of the incoming allocations and frees,
+ // based on hash values. Setting to 0 means no allocs/frees are sampled.
+ // Setting to 1.0 or more means all allocs/frees are sampled. Anything in
+ // between will result in an approximately that fraction of allocs/frees
+ // being sampled.
+ // max_call_stack_unwind_depth:
+ // The max number of call stack frames to unwind.
+ // analysis_interval_bytes:
+ // Perform a leak analysis each time this many bytes have been allocated
+ // since the previous analysis.
+ // size_suspicion_threshold, call_stack_suspicion_threshold:
+ // A possible leak should be suspected this many times to take action on i
+ // For size analysis, the action is to start profiling by call stack.
+ // For call stack analysis, the action is to generate a leak report.
+ void Init(float sampling_rate,
+ size_t max_call_stack_unwind_depth,
+ uint64_t analysis_interval_bytes,
+ uint32_t size_suspicion_threshold,
+ uint32_t call_stack_suspicion_threshold);
+
+ // Add |observer| to the list of stored Observers, i.e. |observers_|, to which
+ // the leak detector will report leaks.
+ void AddObserver(Observer* observer);
+
+ // Remove |observer| from |observers_|.
+ void RemoveObserver(Observer* observer);
+
+ private:
+ friend base::DefaultLazyInstanceTraits<LeakDetector>;
+ FRIEND_TEST_ALL_PREFIXES(LeakDetectorTest, NotifyObservers);
+
+ // Keep these private, as this class is meant to be initialized only through
+ // the lazy instance, and never destroyed.
+ LeakDetector();
+ ~LeakDetector();
+
+ // Allocator hook function that processes each alloc. Performs sampling and
+ // unwinds call stack if necessary. Passes the allocated memory |ptr| and
+ // allocation size |size| along with call stack info to RecordAlloc().
+ static void AllocHook(const void* ptr, size_t size);
+
+ // Allocator hook function that processes each free. Performs sampling and
+ // passes the allocation address |ptr| to |impl_|.
+ static void FreeHook(const void* ptr);
+
+ // Give an pointer |ptr|, computes a hash of the pointer value and compares it
+ // against |sampling_factor_| to determine if it should be sampled. This
+ // allows the same pointer to be sampled during both alloc and free.
+ bool ShouldSample(const void* ptr) const;
+
+ // Notifies all Observers in |observers_| with the given vector of leak
+ // reports.
+ void NotifyObservers(const std::vector<LeakReport>& reports);
+
+ // List of observers to notify when there's a leak report.
+ // TODO(sque): Consider using ObserverListThreadSafe instead.
+ base::ObserverList<Observer> observers_;
+
+ // For atomic access to |observers_|.
+ base::Lock observers_lock_;
+
+ // Handles leak detection logic. Must be called under lock as LeakDetectorImpl
+ // uses shared resources.
+ scoped_ptr<leak_detector::LeakDetectorImpl> impl_;
+
+ // For thread safety.
+ base::ThreadChecker thread_checker_;
+
+ // Total number of bytes allocated, computed before sampling.
+ size_t total_alloc_size_;
+
+ // The value of |total_alloc_size_| the last time there was a leak analysis,
+ // rounded down to the nearest multiple of |analysis_interval_bytes_|.
+ size_t last_analysis_alloc_size_;
+
+ // For atomic access to |impl_|, |total_alloc_size_| and
+ // |last_analysis_alloc_size_|.
+ base::Lock recording_lock_;
+
+ // Perform a leak analysis each time this many bytes have been allocated since
+ // the previous analysis.
+ size_t analysis_interval_bytes_;
+
+ // When unwinding call stacks, unwind no more than this number of frames.
+ size_t max_call_stack_unwind_depth_;
+
+ // Sampling factor used by ShouldSample(). It's full range of values
+ // corresponds to the allowable range of |sampling_rate| passed in during
+ // initialization: [0.0f, 1.0f] -> [0, UINT64_MAX].
+ uint64_t sampling_factor_;
+
+ DISALLOW_COPY_AND_ASSIGN(LeakDetector);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_H_
diff --git a/chromium/components/metrics/leak_detector/leak_detector_impl.cc b/chromium/components/metrics/leak_detector/leak_detector_impl.cc
new file mode 100644
index 00000000000..e174cc20cc8
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_detector_impl.cc
@@ -0,0 +1,241 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "leak_detector_impl.h"
+
+#include <inttypes.h>
+#include <stddef.h>
+
+#include <algorithm>
+#include <new>
+
+#include "base/hash.h"
+#include "base/process/process_handle.h"
+#include "components/metrics/leak_detector/call_stack_table.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "components/metrics/leak_detector/ranked_set.h"
+
+namespace metrics {
+namespace leak_detector {
+
+namespace {
+
+// Look for leaks in the the top N entries in each tier, where N is this value.
+const int kRankedSetSize = 16;
+
+// Initial hash table size for |LeakDetectorImpl::address_map_|.
+const int kAddressMapNumBuckets = 100003;
+
+// Number of entries in the alloc size table. As sizes are aligned to 32-bits
+// the max supported allocation size is (kNumSizeEntries * 4 - 1). Any larger
+// sizes are ignored. This value is chosen high enough that such large sizes
+// are rare if not nonexistent.
+const int kNumSizeEntries = 2048;
+
+// Record only the first |kNumSizeEntriesInHistory| size classes in
+// |LeakDetectorImpl::size_breakdown_history_|.
+const int kNumSizeEntriesInHistory = 32;
+
+// |LeakDetectorImpl::size_breakdown_history_| can have up to this many entries.
+// Any older entries must be discarded to make way for new ones.
+const int kMaxNumHistoryEntries = 32;
+
+using ValueType = LeakDetectorValueType;
+
+// Functions to convert an allocation size to/from the array index used for
+// |LeakDetectorImpl::size_entries_|.
+size_t SizeToIndex(const size_t size) {
+ int result = static_cast<int>(size / sizeof(uint32_t));
+ if (result < kNumSizeEntries)
+ return result;
+ return 0;
+}
+
+size_t IndexToSize(size_t index) {
+ return sizeof(uint32_t) * index;
+}
+
+} // namespace
+
+LeakDetectorImpl::LeakReport::LeakReport() : alloc_size_bytes_(0) {}
+
+LeakDetectorImpl::LeakReport::LeakReport(const LeakReport& other) = default;
+
+LeakDetectorImpl::LeakReport::~LeakReport() {}
+
+bool LeakDetectorImpl::LeakReport::operator<(const LeakReport& other) const {
+ if (alloc_size_bytes_ != other.alloc_size_bytes_)
+ return alloc_size_bytes_ < other.alloc_size_bytes_;
+ for (size_t i = 0; i < call_stack_.size() && i < other.call_stack_.size();
+ ++i) {
+ if (call_stack_[i] != other.call_stack_[i])
+ return call_stack_[i] < other.call_stack_[i];
+ }
+ return call_stack_.size() < other.call_stack_.size();
+}
+
+LeakDetectorImpl::LeakDetectorImpl(uintptr_t mapping_addr,
+ size_t mapping_size,
+ int size_suspicion_threshold,
+ int call_stack_suspicion_threshold)
+ : num_allocs_(0),
+ num_frees_(0),
+ alloc_size_(0),
+ free_size_(0),
+ num_allocs_with_call_stack_(0),
+ num_stack_tables_(0),
+ address_map_(kAddressMapNumBuckets),
+ size_leak_analyzer_(kRankedSetSize, size_suspicion_threshold),
+ size_entries_(kNumSizeEntries),
+ mapping_addr_(mapping_addr),
+ mapping_size_(mapping_size),
+ call_stack_suspicion_threshold_(call_stack_suspicion_threshold) {}
+
+LeakDetectorImpl::~LeakDetectorImpl() {
+ // Free any call stack tables.
+ for (AllocSizeEntry& entry : size_entries_) {
+ CallStackTable* table = entry.stack_table;
+ if (!table)
+ continue;
+ table->~CallStackTable();
+ CustomAllocator::Free(table, sizeof(CallStackTable));
+ }
+ size_entries_.clear();
+}
+
+bool LeakDetectorImpl::ShouldGetStackTraceForSize(size_t size) const {
+ return size_entries_[SizeToIndex(size)].stack_table != nullptr;
+}
+
+void LeakDetectorImpl::RecordAlloc(const void* ptr,
+ size_t size,
+ int stack_depth,
+ const void* const stack[]) {
+ AllocInfo alloc_info;
+ alloc_info.size = size;
+
+ alloc_size_ += alloc_info.size;
+ ++num_allocs_;
+
+ AllocSizeEntry* entry = &size_entries_[SizeToIndex(size)];
+ ++entry->num_allocs;
+
+ if (entry->stack_table && stack_depth > 0) {
+ alloc_info.call_stack =
+ call_stack_manager_.GetCallStack(stack_depth, stack);
+ entry->stack_table->Add(alloc_info.call_stack);
+
+ ++num_allocs_with_call_stack_;
+ }
+
+ uintptr_t addr = reinterpret_cast<uintptr_t>(ptr);
+ address_map_.insert(std::pair<uintptr_t, AllocInfo>(addr, alloc_info));
+}
+
+void LeakDetectorImpl::RecordFree(const void* ptr) {
+ // Look up address.
+ uintptr_t addr = reinterpret_cast<uintptr_t>(ptr);
+ auto iter = address_map_.find(addr);
+ // TODO(sque): Catch and report double frees.
+ if (iter == address_map_.end())
+ return;
+
+ const AllocInfo& alloc_info = iter->second;
+
+ AllocSizeEntry* entry = &size_entries_[SizeToIndex(alloc_info.size)];
+ ++entry->num_frees;
+
+ const CallStack* call_stack = alloc_info.call_stack;
+ if (call_stack) {
+ if (entry->stack_table)
+ entry->stack_table->Remove(call_stack);
+ }
+ ++num_frees_;
+ free_size_ += alloc_info.size;
+
+ address_map_.erase(iter);
+}
+
+void LeakDetectorImpl::TestForLeaks(InternalVector<LeakReport>* reports) {
+ // Add net alloc counts for each size to a ranked list.
+ RankedSet size_ranked_set(kRankedSetSize);
+ for (size_t i = 0; i < size_entries_.size(); ++i) {
+ const AllocSizeEntry& entry = size_entries_[i];
+ ValueType size_value(IndexToSize(i));
+ size_ranked_set.Add(size_value, entry.GetNetAllocs());
+ }
+ size_leak_analyzer_.AddSample(std::move(size_ranked_set));
+
+ // Record a snapshot of the current size table.
+ InternalVector<uint32_t> current_size_table_record;
+ current_size_table_record.reserve(kNumSizeEntriesInHistory);
+ for (const AllocSizeEntry& entry : size_entries_) {
+ if (current_size_table_record.size() == kNumSizeEntriesInHistory)
+ break;
+ current_size_table_record.push_back(entry.GetNetAllocs());
+ }
+ size_breakdown_history_.emplace_back(std::move(current_size_table_record));
+ if (size_breakdown_history_.size() > kMaxNumHistoryEntries)
+ size_breakdown_history_.pop_front();
+
+ // Get suspected leaks by size.
+ for (const ValueType& size_value : size_leak_analyzer_.suspected_leaks()) {
+ uint32_t size = size_value.size();
+ AllocSizeEntry* entry = &size_entries_[SizeToIndex(size)];
+ if (entry->stack_table)
+ continue;
+ entry->stack_table = new (CustomAllocator::Allocate(sizeof(CallStackTable)))
+ CallStackTable(call_stack_suspicion_threshold_);
+ ++num_stack_tables_;
+ }
+
+ // Check for leaks in each CallStackTable. It makes sense to this before
+ // checking the size allocations, because that could potentially create new
+ // CallStackTable. However, the overhead to check a new CallStackTable is
+ // small since this function is run very rarely. So handle the leak checks of
+ // Tier 2 here.
+ reports->clear();
+ for (size_t i = 0; i < size_entries_.size(); ++i) {
+ const AllocSizeEntry& entry = size_entries_[i];
+ CallStackTable* stack_table = entry.stack_table;
+ if (!stack_table || stack_table->empty())
+ continue;
+
+ size_t size = IndexToSize(i);
+
+ // Get suspected leaks by call stack.
+ stack_table->TestForLeaks();
+ const LeakAnalyzer& leak_analyzer = stack_table->leak_analyzer();
+ for (const ValueType& call_stack_value : leak_analyzer.suspected_leaks()) {
+ const CallStack* call_stack = call_stack_value.call_stack();
+
+ // Return reports by storing in |*reports|.
+ reports->resize(reports->size() + 1);
+ LeakReport* report = &reports->back();
+ report->alloc_size_bytes_ = size;
+ report->call_stack_.resize(call_stack->depth);
+ for (size_t j = 0; j < call_stack->depth; ++j) {
+ report->call_stack_[j] = GetOffset(call_stack->stack[j]);
+ }
+ // Copy over the historical size data.
+ report->size_breakdown_history_.reserve(size_breakdown_history_.size());
+ report->size_breakdown_history_.assign(size_breakdown_history_.begin(),
+ size_breakdown_history_.end());
+ }
+ }
+}
+
+size_t LeakDetectorImpl::AddressHash::operator()(uintptr_t addr) const {
+ return base::Hash(reinterpret_cast<const char*>(&addr), sizeof(addr));
+}
+
+uintptr_t LeakDetectorImpl::GetOffset(const void* ptr) const {
+ uintptr_t ptr_value = reinterpret_cast<uintptr_t>(ptr);
+ if (ptr_value >= mapping_addr_ && ptr_value < mapping_addr_ + mapping_size_)
+ return ptr_value - mapping_addr_;
+ return ptr_value;
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/leak_detector_impl.h b/chromium/components/metrics/leak_detector/leak_detector_impl.h
new file mode 100644
index 00000000000..fafd7b814f4
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_detector_impl.h
@@ -0,0 +1,185 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_IMPL_H_
+#define COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_IMPL_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <list>
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "base/macros.h"
+#include "components/metrics/leak_detector/call_stack_manager.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "components/metrics/leak_detector/leak_analyzer.h"
+#include "components/metrics/leak_detector/stl_allocator.h"
+
+namespace metrics {
+namespace leak_detector {
+
+class CallStackTable;
+
+// Class that contains the actual leak detection mechanism.
+// Not thread-safe.
+class LeakDetectorImpl {
+ public:
+ // STL types that are safe to use within the memory leak detector. They use
+ // CustomAllocator to avoid recursive malloc hook invocation when analyzing
+ // allocs and frees.
+ template <typename T>
+ using InternalList = std::list<T, STLAllocator<T, CustomAllocator>>;
+ template <typename T>
+ using InternalVector = std::vector<T, STLAllocator<T, CustomAllocator>>;
+
+ // Leak report generated by LeakDetectorImpl.
+ class LeakReport {
+ public:
+ LeakReport();
+ LeakReport(const LeakReport& other);
+ ~LeakReport();
+
+ size_t alloc_size_bytes() const { return alloc_size_bytes_; }
+
+ const InternalVector<uintptr_t>& call_stack() const { return call_stack_; }
+
+ const InternalVector<InternalVector<uint32_t>>& size_breakdown_history()
+ const {
+ return size_breakdown_history_;
+ }
+
+ // Used to compare the contents of two leak reports.
+ bool operator<(const LeakReport& other) const;
+
+ private:
+ // LeakDetectorImpl needs access to class members when creating a new leak
+ // report.
+ friend class LeakDetectorImpl;
+
+ // Number of bytes allocated by the leak site during each allocation.
+ size_t alloc_size_bytes_;
+
+ // Unlike the CallStack struct, which consists of addresses, this call stack
+ // will contain offsets in the executable binary.
+ InternalVector<uintptr_t> call_stack_;
+
+ // A snapshot of LeakDetectorImpl::size_breakdown_history_ when this report
+ // was generated. See comment description of that variable.
+ InternalVector<InternalVector<uint32_t>> size_breakdown_history_;
+ };
+
+ LeakDetectorImpl(uintptr_t mapping_addr,
+ size_t mapping_size,
+ int size_suspicion_threshold,
+ int call_stack_suspicion_threshold);
+ ~LeakDetectorImpl();
+
+ // Indicates whether the given allocation size has an associated call stack
+ // table, and thus requires a stack unwind.
+ bool ShouldGetStackTraceForSize(size_t size) const;
+
+ // Record allocs and frees.
+ void RecordAlloc(const void* ptr,
+ size_t size,
+ int stack_depth,
+ const void* const call_stack[]);
+ void RecordFree(const void* ptr);
+
+ // Run check for possible leaks based on the current profiling data.
+ void TestForLeaks(InternalVector<LeakReport>* reports);
+
+ private:
+ // A record of allocations for a particular size.
+ struct AllocSizeEntry {
+ // Number of allocations and frees for this size.
+ uint32_t num_allocs;
+ uint32_t num_frees;
+
+ // A stack table, if this size is being profiled for stack as well.
+ CallStackTable* stack_table;
+
+ // Returns net number of allocs.
+ uint32_t GetNetAllocs() const { return num_allocs - num_frees; }
+ };
+
+ // Info for a single allocation.
+ struct AllocInfo {
+ AllocInfo() : call_stack(nullptr) {}
+
+ // Number of bytes in this allocation.
+ size_t size;
+
+ // Points to a unique call stack.
+ const CallStack* call_stack;
+ };
+
+ // Allocator class for allocation entry map. Maps allocated addresses to
+ // AllocInfo objects.
+ using AllocationEntryAllocator =
+ STLAllocator<std::pair<const uintptr_t, AllocInfo>, CustomAllocator>;
+
+ // Hash class for addresses.
+ struct AddressHash {
+ size_t operator()(uintptr_t addr) const;
+ };
+
+ // Returns the offset of |ptr| within the current binary. If it is not in the
+ // current binary, just return |ptr| as an integer.
+ uintptr_t GetOffset(const void* ptr) const;
+
+ // Owns all unique call stack objects, which are allocated on the heap. Any
+ // other class or function that references a call stack must get it from here,
+ // but may not take ownership of the call stack object.
+ CallStackManager call_stack_manager_;
+
+ // Allocation stats.
+ uint64_t num_allocs_;
+ uint64_t num_frees_;
+ uint64_t alloc_size_;
+ uint64_t free_size_;
+
+ uint32_t num_allocs_with_call_stack_;
+ uint32_t num_stack_tables_;
+
+ // Stores all individual recorded allocations.
+ base::hash_map<uintptr_t,
+ AllocInfo,
+ AddressHash,
+ std::equal_to<uintptr_t>,
+ AllocationEntryAllocator> address_map_;
+
+ // Used to analyze potential leak patterns in the allocation sizes.
+ LeakAnalyzer size_leak_analyzer_;
+
+ // Allocation stats for each size.
+ InternalVector<AllocSizeEntry> size_entries_;
+
+ // Tracks the net number of allocations per size over time. Each list item is
+ // a vector containing the allocation counts for each size. The vector element
+ // with index i corresponds to sizes |i * 4| to |i * 4 + 3|. The oldest size
+ // breakdowns is at the head of the list, and new size breakdowns should be
+ // added to the tail of the list.
+ InternalList<InternalVector<uint32_t>> size_breakdown_history_;
+
+ // Address mapping info of the current binary.
+ uintptr_t mapping_addr_;
+ size_t mapping_size_;
+
+ // Number of consecutive times an allocation size must trigger suspicion to be
+ // considered a leak suspect.
+ int size_suspicion_threshold_;
+
+ // Number of consecutive times a call stack must trigger suspicion to be
+ // considered a leak suspect.
+ int call_stack_suspicion_threshold_;
+
+ DISALLOW_COPY_AND_ASSIGN(LeakDetectorImpl);
+};
+
+} // namespace leak_detector
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_IMPL_H_
diff --git a/chromium/components/metrics/leak_detector/leak_detector_impl_unittest.cc b/chromium/components/metrics/leak_detector/leak_detector_impl_unittest.cc
new file mode 100644
index 00000000000..7a7e30a4413
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_detector_impl_unittest.cc
@@ -0,0 +1,629 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/leak_detector_impl.h"
+
+#include <math.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <complex>
+#include <new>
+#include <set>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+namespace leak_detector {
+
+using InternalLeakReport = LeakDetectorImpl::LeakReport;
+template <typename T>
+using InternalVector = LeakDetectorImpl::InternalVector<T>;
+
+namespace {
+
+// Makes working with complex numbers easier.
+using Complex = std::complex<double>;
+
+// The mapping location in memory for a fictional executable.
+const uintptr_t kMappingAddr = 0x800000;
+const size_t kMappingSize = 0x200000;
+
+// Some call stacks within the fictional executable.
+// * - outside the mapping range, e.g. JIT code.
+const uintptr_t kRawStack0[] = {
+ 0x800100, 0x900000, 0x880080, 0x810000,
+};
+const uintptr_t kRawStack1[] = {
+ 0x940000, 0x980000,
+ 0xdeadbeef, // *
+ 0x9a0000,
+};
+const uintptr_t kRawStack2[] = {
+ 0x8f0d00, 0x803abc, 0x9100a0,
+};
+const uintptr_t kRawStack3[] = {
+ 0x90fcde,
+ 0x900df00d, // *
+ 0x801000, 0x880088,
+ 0xdeadcafe, // *
+ 0x9f0000, 0x8700a0, 0x96037c,
+};
+const uintptr_t kRawStack4[] = {
+ 0x8c0000, 0x85d00d, 0x921337,
+ 0x780000, // *
+};
+const uintptr_t kRawStack5[] = {
+ 0x990000, 0x888888, 0x830ac0, 0x8e0000,
+ 0xc00000, // *
+};
+
+// This struct makes it easier to pass call stack info to
+// LeakDetectorImplTest::Alloc().
+struct TestCallStack {
+ const uintptr_t* stack; // A reference to the original stack data.
+ size_t depth;
+};
+
+const TestCallStack kStack0 = {kRawStack0, arraysize(kRawStack0)};
+const TestCallStack kStack1 = {kRawStack1, arraysize(kRawStack1)};
+const TestCallStack kStack2 = {kRawStack2, arraysize(kRawStack2)};
+const TestCallStack kStack3 = {kRawStack3, arraysize(kRawStack3)};
+const TestCallStack kStack4 = {kRawStack4, arraysize(kRawStack4)};
+const TestCallStack kStack5 = {kRawStack5, arraysize(kRawStack5)};
+
+// The interval between consecutive analyses (LeakDetectorImpl::TestForLeaks),
+// in number of bytes allocated. e.g. if |kAllocedSizeAnalysisInterval| = 1024
+// then call TestForLeaks() every 1024 bytes of allocation that occur.
+const size_t kAllocedSizeAnalysisInterval = 8192;
+
+// Suspicion thresholds used by LeakDetectorImpl for size and call stacks.
+const uint32_t kSizeSuspicionThreshold = 4;
+const uint32_t kCallStackSuspicionThreshold = 4;
+
+// Returns the offset within [kMappingAddr, kMappingAddr + kMappingSize) if
+// |addr| falls in that range. Otherwise, returns |addr|.
+uintptr_t GetOffsetInMapping(uintptr_t addr) {
+ if (addr >= kMappingAddr && addr < kMappingAddr + kMappingSize)
+ return addr - kMappingAddr;
+ return addr;
+}
+
+// Copied from leak_detector_impl.cc. Converts a size to a size class index.
+// Any size in the range [index * 4, index * 4 + 3] falls into that size class.
+uint32_t SizeToIndex(size_t size) {
+ return size / sizeof(uint32_t);
+}
+
+} // namespace
+
+// This test suite will test the ability of LeakDetectorImpl to catch leaks in
+// a program. Individual tests can run leaky code locally.
+//
+// The leaky code must call Alloc() and Free() for heap memory management. It
+// should not call See comments on those
+// functions for more details.
+class LeakDetectorImplTest : public ::testing::Test {
+ public:
+ LeakDetectorImplTest()
+ : total_num_allocs_(0),
+ total_num_frees_(0),
+ total_alloced_size_(0),
+ next_analysis_total_alloced_size_(kAllocedSizeAnalysisInterval) {}
+
+ void SetUp() override {
+ CustomAllocator::Initialize();
+
+ detector_.reset(new LeakDetectorImpl(kMappingAddr, kMappingSize,
+ kSizeSuspicionThreshold,
+ kCallStackSuspicionThreshold));
+ }
+
+ void TearDown() override {
+ // Free any memory that was leaked by test cases. Do not use Free() because
+ // that will try to modify |alloced_ptrs_|.
+ for (void* ptr : alloced_ptrs_)
+ delete[] reinterpret_cast<char*>(ptr);
+ alloced_ptrs_.clear();
+
+ // Must destroy all objects that use CustomAllocator before shutting down.
+ detector_.reset();
+ stored_reports_.clear();
+
+ EXPECT_TRUE(CustomAllocator::Shutdown());
+ }
+
+ protected:
+ // Alloc and free functions that allocate and free heap memory and
+ // automatically pass alloc/free info to |detector_|. They emulate the
+ // alloc/free hook functions that would call into LeakDetectorImpl in
+ // real-life usage. They also keep track of individual allocations locally, so
+ // any leaked memory could be cleaned up.
+ //
+ // |stack| is just a nominal call stack object to identify the call site. It
+ // doesn't have to contain the stack trace of the actual call stack.
+ void* Alloc(size_t size, const TestCallStack& stack) {
+ void* ptr = new char[size];
+ detector_->RecordAlloc(ptr, size, stack.depth,
+ reinterpret_cast<const void* const*>(stack.stack));
+
+ EXPECT_TRUE(alloced_ptrs_.find(ptr) == alloced_ptrs_.end());
+ alloced_ptrs_.insert(ptr);
+
+ ++total_num_allocs_;
+ total_alloced_size_ += size;
+ if (total_alloced_size_ >= next_analysis_total_alloced_size_) {
+ InternalVector<InternalLeakReport> reports;
+ detector_->TestForLeaks(&reports);
+ for (const InternalLeakReport& report : reports) {
+ auto iter = stored_reports_.find(report);
+ if (iter == stored_reports_.end()) {
+ stored_reports_.insert(report);
+ } else {
+ // InternalLeakReports are uniquely identified by |alloc_size_bytes_|
+ // and |call_stack_|. See InternalLeakReport::operator<().
+ // If a report with the same size and call stack already exists,
+ // overwrite it with the new report, which has a newer history.
+ stored_reports_.erase(iter);
+ stored_reports_.insert(report);
+ }
+ }
+
+ // Determine when the next leak analysis should occur.
+ while (total_alloced_size_ >= next_analysis_total_alloced_size_)
+ next_analysis_total_alloced_size_ += kAllocedSizeAnalysisInterval;
+ }
+ return ptr;
+ }
+
+ // See comment for Alloc().
+ void Free(void* ptr) {
+ auto find_ptr_iter = alloced_ptrs_.find(ptr);
+ EXPECT_FALSE(find_ptr_iter == alloced_ptrs_.end());
+ if (find_ptr_iter == alloced_ptrs_.end())
+ return;
+ alloced_ptrs_.erase(find_ptr_iter);
+ ++total_num_frees_;
+
+ detector_->RecordFree(ptr);
+
+ delete[] reinterpret_cast<char*>(ptr);
+ }
+
+ // TEST CASE: Simple program that leaks memory regularly. Pass in
+ // enable_leaks=true to trigger some memory leaks.
+ void SimpleLeakyFunction(bool enable_leaks);
+
+ // TEST CASE: Julia set fractal computation. Pass in enable_leaks=true to
+ // trigger some memory leaks.
+ void JuliaSet(bool enable_leaks);
+
+ // Instance of the class being tested.
+ scoped_ptr<LeakDetectorImpl> detector_;
+
+ // Number of pointers allocated and freed so far.
+ size_t total_num_allocs_;
+ size_t total_num_frees_;
+
+ // Keeps count of total size allocated by Alloc().
+ size_t total_alloced_size_;
+
+ // The cumulative allocation size at which to trigger the TestForLeaks() call.
+ size_t next_analysis_total_alloced_size_;
+
+ // Stores all pointers to memory allocated by by Alloc() so we can manually
+ // free the leaked pointers at the end. This also serves as redundant
+ // bookkeepping: it stores all pointers that have been allocated but not yet
+ // freed.
+ std::set<void*> alloced_ptrs_;
+
+ // Store leak reports here. Use a set so duplicate reports are not stored.
+ std::set<InternalLeakReport> stored_reports_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LeakDetectorImplTest);
+};
+
+void LeakDetectorImplTest::SimpleLeakyFunction(bool enable_leaks) {
+ std::vector<uint32_t*> ptrs(7);
+
+ const int kNumOuterIterations = 20;
+ for (int j = 0; j < kNumOuterIterations; ++j) {
+ // The inner loop allocates 256 bytes. Run it 32 times so that 8192 bytes
+ // (|kAllocedSizeAnalysisInterval|) are allocated for each iteration of the
+ // outer loop.
+ const int kNumInnerIterations = 32;
+ static_assert(kNumInnerIterations * 256 == kAllocedSizeAnalysisInterval,
+ "Inner loop iterations do not allocate the correct number of "
+ "bytes.");
+ for (int i = 0; i < kNumInnerIterations; ++i) {
+ size_t alloc_size_at_beginning = total_alloced_size_;
+
+ ptrs[0] = new(Alloc(16, kStack0)) uint32_t;
+ ptrs[1] = new(Alloc(32, kStack1)) uint32_t;
+ ptrs[2] = new(Alloc(48, kStack2)) uint32_t;
+ // Allocate two 32-byte blocks and record them as from the same call site.
+ ptrs[3] = new(Alloc(32, kStack3)) uint32_t;
+ ptrs[4] = new(Alloc(32, kStack3)) uint32_t;
+ // Allocate two 48-byte blocks and record them as from the same call site.
+ ptrs[5] = new(Alloc(48, kStack4)) uint32_t;
+ ptrs[6] = new(Alloc(48, kStack4)) uint32_t;
+
+ // Now free these pointers.
+ Free(ptrs[0]);
+ if (!enable_leaks) // Leak with size=32, call_stack=kStack1.
+ Free(ptrs[1]);
+ if (!enable_leaks) // Leak with size=48, call_stack=kStack2.
+ Free(ptrs[2]);
+ Free(ptrs[3]);
+ Free(ptrs[4]);
+ Free(ptrs[5]);
+ Free(ptrs[6]);
+
+ // Make sure that the above code actually allocates 256 bytes.
+ EXPECT_EQ(alloc_size_at_beginning + 256, total_alloced_size_);
+ }
+ }
+}
+
+void LeakDetectorImplTest::JuliaSet(bool enable_leaks) {
+ // The center region of the complex plane that is the basis for our Julia set
+ // computations is a circle of radius kRadius.
+ constexpr double kRadius = 2;
+
+ // To track points in the complex plane, we will use a rectangular grid in the
+ // range defined by [-kRadius, kRadius] along both axes.
+ constexpr double kRangeMin = -kRadius;
+ constexpr double kRangeMax = kRadius;
+
+ // Divide each axis into intervals, each of which is associated with a point
+ // on that axis at its center.
+ constexpr double kIntervalInverse = 64;
+ constexpr double kInterval = 1.0 / kIntervalInverse;
+ constexpr int kNumPoints = (kRangeMax - kRangeMin) / kInterval + 1;
+
+ // Contains some useful functions for converting between points on the complex
+ // plane and in a gridlike data structure.
+ struct ComplexPlane {
+ static int GetXGridIndex(const Complex& value) {
+ return (value.real() + kInterval / 2 - kRangeMin) / kInterval;
+ }
+ static int GetYGridIndex(const Complex& value) {
+ return (value.imag() + kInterval / 2 - kRangeMin) / kInterval;
+ }
+ static int GetArrayIndex(const Complex& value) {
+ return GetXGridIndex(value) + GetYGridIndex(value) * kNumPoints;
+ }
+ static Complex GetComplexForGridPoint(size_t x, size_t y) {
+ return Complex(kRangeMin + x * kInterval, kRangeMin + y * kInterval);
+ }
+ };
+
+ // Make sure the choice of interval doesn't result in any loss of precision.
+ ASSERT_EQ(1.0, kInterval * kIntervalInverse);
+
+ // Create a grid for part of the complex plane, with each axis within the
+ // range [kRangeMin, kRangeMax].
+ constexpr size_t width = kNumPoints;
+ constexpr size_t height = kNumPoints;
+ std::vector<Complex*> grid(width * height);
+
+ // Initialize an object for each point within the inner circle |z| < kRadius.
+ for (size_t i = 0; i < width; ++i) {
+ for (size_t j = 0; j < height; ++j) {
+ Complex point = ComplexPlane::GetComplexForGridPoint(i, j);
+ // Do not store any values outside the inner circle.
+ if (abs(point) <= kRadius) {
+ grid[i + j * width] =
+ new (Alloc(sizeof(Complex), kStack0)) Complex(point);
+ }
+ }
+ }
+ EXPECT_LE(alloced_ptrs_.size(), width * height);
+
+ // Create a new grid for the result of the transformation.
+ std::vector<Complex*> next_grid(width * height, nullptr);
+
+ // Number of times to run the Julia set iteration. This is not the same as the
+ // number of analyses performed by LeakDetectorImpl, which is determined by
+ // the total number of bytes allocated divided by
+ // |kAllocedSizeAnalysisInterval|.
+ const int kNumIterations = 20;
+ for (int n = 0; n < kNumIterations; ++n) {
+ for (int i = 0; i < kNumPoints; ++i) {
+ for (int j = 0; j < kNumPoints; ++j) {
+ if (!grid[i + j * width])
+ continue;
+
+ // NOTE: The below code is NOT an efficient way to compute a Julia set.
+ // This is only to test the leak detector with some nontrivial code.
+
+ // A simple polynomial function for generating Julia sets is:
+ // f(z) = z^n + c
+
+ // But in this algorithm, we need the inverse:
+ // fInv(z) = (z - c)^(1/n)
+
+ // Here, let's use n=5 and c=0.544.
+ const Complex c(0.544, 0);
+ const Complex& z = *grid[i + j * width];
+
+ // This is the principal root.
+ Complex root = pow(z - c, 0.2);
+
+ // Discard the result if it is too far out from the center of the plane.
+ if (abs(root) > kRadius)
+ continue;
+
+ // The below code only allocates Complex objects of the same size. The
+ // leak detector expects various sizes, so increase the allocation size
+ // by a different amount at each call site.
+
+ // Nth root produces N results.
+ // Place all root results on |next_grid|.
+
+ // First, place the principal root.
+ if (!next_grid[ComplexPlane::GetArrayIndex(root)]) {
+ next_grid[ComplexPlane::GetArrayIndex(root)] =
+ new (Alloc(sizeof(Complex) + 24, kStack1)) Complex(root);
+ }
+
+ double magnitude = abs(root);
+ double angle = arg(root);
+ // To generate other roots, rotate the principal root by increments of
+ // 1/N of a full circle.
+ const double kAngleIncrement = M_PI * 2 / 5;
+
+ // Second root.
+ root = std::polar(magnitude, angle + kAngleIncrement);
+ if (!next_grid[ComplexPlane::GetArrayIndex(root)]) {
+ next_grid[ComplexPlane::GetArrayIndex(root)] =
+ new (Alloc(sizeof(Complex) + 40, kStack2)) Complex(root);
+ }
+
+ // In some of the sections below, setting |enable_leaks| to true will
+ // trigger a memory leak by overwriting the old Complex pointer value
+ // without freeing it. Due to the nature of complex roots being confined
+ // to equal sections of the complex plane, each new pointer will
+ // displace an old pointer that was allocated from the same line of
+ // code.
+
+ // Third root.
+ root = std::polar(magnitude, angle + kAngleIncrement * 2);
+ // *** LEAK ***
+ if (enable_leaks || !next_grid[ComplexPlane::GetArrayIndex(root)]) {
+ next_grid[ComplexPlane::GetArrayIndex(root)] =
+ new (Alloc(sizeof(Complex) + 40, kStack3)) Complex(root);
+ }
+
+ // Fourth root.
+ root = std::polar(magnitude, angle + kAngleIncrement * 3);
+ // *** LEAK ***
+ if (enable_leaks || !next_grid[ComplexPlane::GetArrayIndex(root)]) {
+ next_grid[ComplexPlane::GetArrayIndex(root)] =
+ new (Alloc(sizeof(Complex) + 52, kStack4)) Complex(root);
+ }
+
+ // Fifth root.
+ root = std::polar(magnitude, angle + kAngleIncrement * 4);
+ if (!next_grid[ComplexPlane::GetArrayIndex(root)]) {
+ next_grid[ComplexPlane::GetArrayIndex(root)] =
+ new (Alloc(sizeof(Complex) + 52, kStack5)) Complex(root);
+ }
+ }
+ }
+
+ // Clear the previously allocated points.
+ for (Complex*& point : grid) {
+ if (point) {
+ Free(point);
+ point = nullptr;
+ }
+ }
+
+ // Now swap the two grids for the next iteration.
+ grid.swap(next_grid);
+ }
+
+ // Clear the previously allocated points.
+ for (Complex*& point : grid) {
+ if (point) {
+ Free(point);
+ point = nullptr;
+ }
+ }
+}
+
+TEST_F(LeakDetectorImplTest, CheckTestFramework) {
+ EXPECT_EQ(0U, total_num_allocs_);
+ EXPECT_EQ(0U, total_num_frees_);
+ EXPECT_EQ(0U, alloced_ptrs_.size());
+
+ // Allocate some memory.
+ void* ptr0 = Alloc(12, kStack0);
+ void* ptr1 = Alloc(16, kStack0);
+ void* ptr2 = Alloc(24, kStack0);
+ EXPECT_EQ(3U, total_num_allocs_);
+ EXPECT_EQ(0U, total_num_frees_);
+ EXPECT_EQ(3U, alloced_ptrs_.size());
+
+ // Free one of the pointers.
+ Free(ptr1);
+ EXPECT_EQ(3U, total_num_allocs_);
+ EXPECT_EQ(1U, total_num_frees_);
+ EXPECT_EQ(2U, alloced_ptrs_.size());
+
+ // Allocate some more memory.
+ void* ptr3 = Alloc(72, kStack1);
+ void* ptr4 = Alloc(104, kStack1);
+ void* ptr5 = Alloc(96, kStack1);
+ void* ptr6 = Alloc(24, kStack1);
+ EXPECT_EQ(7U, total_num_allocs_);
+ EXPECT_EQ(1U, total_num_frees_);
+ EXPECT_EQ(6U, alloced_ptrs_.size());
+
+ // Free more pointers.
+ Free(ptr2);
+ Free(ptr4);
+ Free(ptr6);
+ EXPECT_EQ(7U, total_num_allocs_);
+ EXPECT_EQ(4U, total_num_frees_);
+ EXPECT_EQ(3U, alloced_ptrs_.size());
+
+ // Free remaining memory.
+ Free(ptr0);
+ Free(ptr3);
+ Free(ptr5);
+ EXPECT_EQ(7U, total_num_allocs_);
+ EXPECT_EQ(7U, total_num_frees_);
+ EXPECT_EQ(0U, alloced_ptrs_.size());
+}
+
+TEST_F(LeakDetectorImplTest, SimpleLeakyFunctionNoLeak) {
+ SimpleLeakyFunction(false /* enable_leaks */);
+
+ // SimpleLeakyFunction() should have run cleanly without leaking.
+ EXPECT_EQ(total_num_allocs_, total_num_frees_);
+ EXPECT_EQ(0U, alloced_ptrs_.size());
+ ASSERT_EQ(0U, stored_reports_.size());
+}
+
+TEST_F(LeakDetectorImplTest, SimpleLeakyFunctionWithLeak) {
+ SimpleLeakyFunction(true /* enable_leaks */);
+
+ // SimpleLeakyFunction() should generated some leak reports.
+ EXPECT_GT(total_num_allocs_, total_num_frees_);
+ EXPECT_GT(alloced_ptrs_.size(), 0U);
+ ASSERT_EQ(2U, stored_reports_.size());
+
+ // The reports should be stored in order of size.
+
+ // |report1| comes from the call site marked with kStack1, with size=32.
+ const InternalLeakReport& report1 = *stored_reports_.begin();
+ EXPECT_EQ(32U, report1.alloc_size_bytes());
+ ASSERT_EQ(kStack1.depth, report1.call_stack().size());
+ for (size_t i = 0; i < kStack1.depth; ++i) {
+ EXPECT_EQ(GetOffsetInMapping(kStack1.stack[i]),
+ report1.call_stack()[i]) << i;
+ }
+
+ // |report2| comes from the call site marked with kStack2, with size=48.
+ const InternalLeakReport& report2 = *(++stored_reports_.begin());
+ EXPECT_EQ(48U, report2.alloc_size_bytes());
+ ASSERT_EQ(kStack2.depth, report2.call_stack().size());
+ for (size_t i = 0; i < kStack2.depth; ++i) {
+ EXPECT_EQ(GetOffsetInMapping(kStack2.stack[i]),
+ report2.call_stack()[i]) << i;
+ }
+
+ // Check historical data recorded in the reports.
+ // - Each inner loop iteration allocates a net of 1x 32 bytes and 1x 48 bytes.
+ // - Each outer loop iteration allocates a net of 32x 32 bytes and 32x 48
+ // bytes.
+ // - However, the leak analysis happens after the allocs but before the frees
+ // that come right after. So it should count the two extra allocs made at
+ // call sites |kStack3| and |kStack4|. The formula is |(i + 1) * 32 + 2|,
+ // where |i| is the iteration index.
+ // There should have been one leak analysis per outer loop iteration, for a
+ // total of 20 history records (|kNumOuterIterations|) per report.
+
+ const auto& report1_size_history = report1.size_breakdown_history();
+ EXPECT_EQ(20U, report1_size_history.size());
+
+ size_t index = 0;
+ for (const InternalVector<uint32_t>& entry : report1_size_history) {
+ ASSERT_GT(entry.size(), SizeToIndex(48));
+
+ // Check the two leaky sizes, 32 and 48.
+ EXPECT_EQ((index + 1) * 32 + 2, entry[SizeToIndex(32)]);
+ EXPECT_EQ((index + 1) * 32 + 2, entry[SizeToIndex(48)]);
+
+ // Not related to the leaks, but there should be a dangling 16-byte
+ // allocation during each leak analysis, because it hasn't yet been freed.
+ EXPECT_EQ(1U, entry[SizeToIndex(16)]);
+ ++index;
+ }
+
+ // |report2| should have the same size history as |report1|.
+ const auto& report2_size_history = report2.size_breakdown_history();
+ EXPECT_TRUE(std::equal(report1_size_history.begin(),
+ report1_size_history.end(),
+ report2_size_history.begin()));
+}
+
+TEST_F(LeakDetectorImplTest, JuliaSetNoLeak) {
+ JuliaSet(false /* enable_leaks */);
+
+ // JuliaSet() should have run cleanly without leaking.
+ EXPECT_EQ(total_num_allocs_, total_num_frees_);
+ EXPECT_EQ(0U, alloced_ptrs_.size());
+ ASSERT_EQ(0U, stored_reports_.size());
+}
+
+TEST_F(LeakDetectorImplTest, JuliaSetWithLeak) {
+ JuliaSet(true /* enable_leaks */);
+
+ // JuliaSet() should have leaked some memory from two call sites.
+ EXPECT_GT(total_num_allocs_, total_num_frees_);
+ EXPECT_GT(alloced_ptrs_.size(), 0U);
+
+ // There should be one unique leak report generated for each leaky call site.
+ ASSERT_EQ(2U, stored_reports_.size());
+
+ // The reports should be stored in order of size.
+
+ // |report1| comes from the call site in JuliaSet() corresponding to
+ // |kStack3|.
+ const InternalLeakReport& report1 = *stored_reports_.begin();
+ EXPECT_EQ(sizeof(Complex) + 40, report1.alloc_size_bytes());
+ ASSERT_EQ(kStack3.depth, report1.call_stack().size());
+ for (size_t i = 0; i < kStack3.depth; ++i) {
+ EXPECT_EQ(GetOffsetInMapping(kStack3.stack[i]),
+ report1.call_stack()[i]) << i;
+ }
+
+ // |report2| comes from the call site in JuliaSet() corresponding to
+ // |kStack4|.
+ const InternalLeakReport& report2 = *(++stored_reports_.begin());
+ EXPECT_EQ(sizeof(Complex) + 52, report2.alloc_size_bytes());
+ ASSERT_EQ(kStack4.depth, report2.call_stack().size());
+ for (size_t i = 0; i < kStack4.depth; ++i) {
+ EXPECT_EQ(GetOffsetInMapping(kStack4.stack[i]),
+ report2.call_stack()[i]) << i;
+ }
+
+ // Check |report1|'s historical data.
+ const auto& report1_size_history = report1.size_breakdown_history();
+ // Computing the exact number of leak analyses is not trivial, but we know it
+ // must be at least |kSizeSuspicionThreshold + kCallStackSuspicionThreshold|
+ // in order to have generated a report.
+ EXPECT_GT(report1_size_history.size(),
+ kSizeSuspicionThreshold + kCallStackSuspicionThreshold);
+
+ // Make sure that the final allocation counts for the leaky sizes are larger
+ // than that of the non-leaky size by at least an order of magnitude.
+ const InternalVector<uint32_t>& final_entry = *report1_size_history.rbegin();
+ uint32_t size_0_index = SizeToIndex(sizeof(Complex) + 24);
+ uint32_t size_1_index = SizeToIndex(sizeof(Complex) + 40);
+ uint32_t size_2_index = SizeToIndex(sizeof(Complex) + 52);
+ ASSERT_LT(size_0_index, final_entry.size());
+ ASSERT_LT(size_1_index, final_entry.size());
+ ASSERT_LT(size_2_index, final_entry.size());
+
+ EXPECT_GT(final_entry[size_1_index], final_entry[size_0_index] * 10);
+ EXPECT_GT(final_entry[size_2_index], final_entry[size_0_index] * 10);
+
+ // |report2| should have the same size history as |report1|.
+ const auto& report2_size_history = report2.size_breakdown_history();
+ EXPECT_TRUE(std::equal(report1_size_history.begin(),
+ report1_size_history.end(),
+ report2_size_history.begin()));
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/leak_detector_unittest.cc b/chromium/components/metrics/leak_detector/leak_detector_unittest.cc
new file mode 100644
index 00000000000..6f33c012bb6
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_detector_unittest.cc
@@ -0,0 +1,119 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/leak_detector.h"
+
+#include <set>
+
+#include "base/allocator/allocator_extension.h"
+#include "base/macros.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+// Default values for LeakDetector params. See header file for the meaning of
+// each parameter.
+const float kDefaultSamplingRate = 1.0f;
+const size_t kDefaultMaxCallStackUnwindDepth = 4;
+const uint64_t kDefaultAnalysisIntervalBytes = 32 * 1024 * 1024; // 32 MiB.
+const uint32_t kDefaultSizeSuspicionThreshold = 4;
+const uint32_t kDefaultCallStackSuspicionThreshold = 4;
+
+using LeakReport = LeakDetector::LeakReport;
+
+// Observer class that receives leak reports and stores them in |reports_|.
+// Only one copy of each unique report will be stored.
+class TestObserver : public LeakDetector::Observer {
+ public:
+ // Contains a comparator function used to compare LeakReports for uniqueness.
+ struct Comparator {
+ bool operator()(const LeakReport& a, const LeakReport& b) const {
+ if (a.alloc_size_bytes != b.alloc_size_bytes)
+ return a.alloc_size_bytes < b.alloc_size_bytes;
+
+ return a.call_stack < b.call_stack;
+ }
+ };
+
+ TestObserver() {}
+
+ void OnLeakFound(const LeakReport& report) override {
+ reports_.insert(report);
+ }
+
+ const std::set<LeakReport, Comparator>& reports() const { return reports_; }
+
+ private:
+ // Container for all leak reports received through OnLeakFound(). Stores only
+ // one copy of each unique report.
+ std::set<LeakReport, Comparator> reports_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestObserver);
+};
+
+} // namespace
+
+class LeakDetectorTest : public ::testing::Test {
+ public:
+ LeakDetectorTest() : detector_(LeakDetector::GetInstance()) {
+ detector_->Init(kDefaultSamplingRate, kDefaultMaxCallStackUnwindDepth,
+ kDefaultAnalysisIntervalBytes,
+ kDefaultSizeSuspicionThreshold,
+ kDefaultCallStackSuspicionThreshold);
+ }
+
+ protected:
+ // Points to the instance of LeakDetector returned by GetInstance().
+ LeakDetector* detector_;
+
+ private:
+ // For supporting content::BrowserThread operations.
+ content::TestBrowserThreadBundle thread_bundle_;
+
+ DISALLOW_COPY_AND_ASSIGN(LeakDetectorTest);
+};
+
+TEST_F(LeakDetectorTest, NotifyObservers) {
+ // Generate two sets of leak reports.
+ std::vector<LeakReport> reports1(3);
+ reports1[0].alloc_size_bytes = 8;
+ reports1[0].call_stack = {1, 2, 3, 4};
+ reports1[1].alloc_size_bytes = 16;
+ reports1[1].call_stack = {5, 6, 7, 8};
+ reports1[2].alloc_size_bytes = 24;
+ reports1[2].call_stack = {9, 10, 11, 12};
+
+ std::vector<LeakReport> reports2(3);
+ reports2[0].alloc_size_bytes = 32;
+ reports2[0].call_stack = {1, 2, 4, 8};
+ reports2[1].alloc_size_bytes = 40;
+ reports2[1].call_stack = {16, 32, 64, 128};
+ reports2[2].alloc_size_bytes = 48;
+ reports2[2].call_stack = {256, 512, 1024, 2048};
+
+ // Register three observers;
+ TestObserver obs1, obs2, obs3;
+ detector_->AddObserver(&obs1);
+ detector_->AddObserver(&obs2);
+ detector_->AddObserver(&obs3);
+
+ // Pass both sets of reports to the leak detector.
+ detector_->NotifyObservers(reports1);
+ detector_->NotifyObservers(reports2);
+
+ // Check that all three observers got both sets of reports, passed in
+ // separately.
+ for (const TestObserver* obs : {&obs1, &obs2, &obs3}) {
+ EXPECT_EQ(6U, obs->reports().size());
+ for (const auto& report : {reports1[0], reports1[1], reports1[2],
+ reports2[0], reports2[1], reports2[2]}) {
+ EXPECT_TRUE(obs->reports().find(report) != obs->reports().end());
+ }
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/leak_detector_value_type.cc b/chromium/components/metrics/leak_detector/leak_detector_value_type.cc
new file mode 100644
index 00000000000..4642f5150f8
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_detector_value_type.cc
@@ -0,0 +1,47 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/leak_detector_value_type.h"
+
+#include <stdio.h>
+
+namespace metrics {
+namespace leak_detector {
+
+bool LeakDetectorValueType::operator==(
+ const LeakDetectorValueType& other) const {
+ if (type_ != other.type_)
+ return false;
+
+ switch (type_) {
+ case TYPE_SIZE:
+ return size_ == other.size_;
+ case TYPE_CALL_STACK:
+ return call_stack_ == other.call_stack_;
+ case TYPE_NONE:
+ // "NONE" types are considered to be all identical.
+ return true;
+ }
+ return false;
+}
+
+bool LeakDetectorValueType::operator<(
+ const LeakDetectorValueType& other) const {
+ if (type_ != other.type_)
+ return type_ < other.type_;
+
+ switch (type_) {
+ case TYPE_SIZE:
+ return size_ < other.size_;
+ case TYPE_CALL_STACK:
+ return call_stack_ < other.call_stack_;
+ case TYPE_NONE:
+ // "NONE" types are considered to be all identical.
+ return false;
+ }
+ return false;
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/leak_detector_value_type.h b/chromium/components/metrics/leak_detector/leak_detector_value_type.h
new file mode 100644
index 00000000000..40f61081e9a
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_detector_value_type.h
@@ -0,0 +1,52 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_VALUE_TYPE_
+#define COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_VALUE_TYPE_
+
+#include <stddef.h>
+#include <stdint.h>
+
+namespace metrics {
+namespace leak_detector {
+
+// Used for tracking unique call stacks.
+// Not thread-safe.
+struct CallStack;
+
+class LeakDetectorValueType {
+ public:
+ // Supported types.
+ enum Type {
+ TYPE_NONE,
+ TYPE_SIZE,
+ TYPE_CALL_STACK,
+ };
+
+ LeakDetectorValueType() : type_(TYPE_NONE), size_(0), call_stack_(nullptr) {}
+ explicit LeakDetectorValueType(size_t size)
+ : type_(TYPE_SIZE), size_(size), call_stack_(nullptr) {}
+ explicit LeakDetectorValueType(const CallStack* call_stack)
+ : type_(TYPE_CALL_STACK), size_(0), call_stack_(call_stack) {}
+
+ // Accessors.
+ Type type() const { return type_; }
+ size_t size() const { return size_; }
+ const CallStack* call_stack() const { return call_stack_; }
+
+ // Comparators.
+ bool operator==(const LeakDetectorValueType& other) const;
+ bool operator<(const LeakDetectorValueType& other) const;
+
+ private:
+ Type type_;
+
+ size_t size_;
+ const CallStack* call_stack_;
+};
+
+} // namespace leak_detector
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_VALUE_TYPE_
diff --git a/chromium/components/metrics/leak_detector/ranked_set.cc b/chromium/components/metrics/leak_detector/ranked_set.cc
new file mode 100644
index 00000000000..5725fb5c01c
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/ranked_set.cc
@@ -0,0 +1,52 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/ranked_set.h"
+
+#include <algorithm>
+
+namespace metrics {
+namespace leak_detector {
+
+RankedSet::RankedSet(size_t max_size) : max_size_(max_size) {}
+
+RankedSet::~RankedSet() {}
+
+RankedSet::RankedSet(RankedSet&& other) : max_size_(other.max_size_) {
+ entries_ = std::move(other.entries_);
+}
+
+RankedSet& RankedSet::operator=(RankedSet&& other) {
+ max_size_ = other.max_size_;
+ entries_ = std::move(other.entries_);
+ return *this;
+}
+
+bool RankedSet::Entry::operator<(const RankedSet::Entry& other) const {
+ if (count == other.count)
+ return value < other.value;
+
+ return count > other.count;
+}
+
+void RankedSet::Add(const ValueType& value, int count) {
+ // If the container is full, do not add any entry with |count| if does not
+ // exceed the lowest count of the entries in the list.
+ if (size() == max_size_ && count < min_count())
+ return;
+
+ Entry new_entry;
+ new_entry.value = value;
+ new_entry.count = count;
+ entries_.insert(new_entry);
+
+ // Limit the container size if it exceeds the maximum allowed size, by
+ // deleting the last element. This should only iterate once because the size
+ // can only have increased by 1, but use a while loop just to be safe.
+ while (entries_.size() > max_size_)
+ entries_.erase(--entries_.end());
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/ranked_set.h b/chromium/components/metrics/leak_detector/ranked_set.h
new file mode 100644
index 00000000000..f91251593b0
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/ranked_set.h
@@ -0,0 +1,96 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_RANKED_SET_H_
+#define COMPONENTS_METRICS_LEAK_DETECTOR_RANKED_SET_H_
+
+#include <stddef.h>
+
+#include <functional> // for std::less
+#include <set>
+
+#include "base/macros.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "components/metrics/leak_detector/leak_detector_value_type.h"
+#include "components/metrics/leak_detector/stl_allocator.h"
+
+namespace metrics {
+namespace leak_detector {
+
+// RankedSet lets you add entries consisting of a value-count pair, and
+// automatically sorts them internally by count in descending order. This allows
+// for the user of this container to insert value-count pairs without having to
+// explicitly sort them by count.
+class RankedSet {
+ public:
+ using ValueType = LeakDetectorValueType;
+
+ // A single entry in the RankedSet. The RankedSet sorts entries by |count|
+ // in descending order.
+ struct Entry {
+ ValueType value;
+ int count;
+
+ // This less-than comparator is used for sorting Entries within a sorted
+ // container. It internally reverses the comparison so that higher-count
+ // entries are sorted ahead of lower-count entries.
+ bool operator<(const Entry& other) const;
+ };
+
+ // This class uses CustomAllocator to avoid recursive malloc hook invocation
+ // when analyzing allocs and frees.
+ using EntrySet =
+ std::set<Entry, std::less<Entry>, STLAllocator<Entry, CustomAllocator>>;
+ using const_iterator = EntrySet::const_iterator;
+
+ explicit RankedSet(size_t max_size);
+ ~RankedSet();
+
+ // For move semantics.
+ RankedSet(RankedSet&& other);
+ RankedSet& operator=(RankedSet&& other);
+
+ // Accessors for begin() and end() const iterators.
+ const_iterator begin() const { return entries_.begin(); }
+ const_iterator end() const { return entries_.end(); }
+
+ size_t size() const { return entries_.size(); }
+ size_t max_size() const { return max_size_; }
+
+ // Add a new value-count pair to the container. Will overwrite any existing
+ // entry with the same value and count. Will not overwrite an existing entry
+ // with the same value but a different count, or different values with the
+ // same count.
+ //
+ // Time complexity is O(log n).
+ void Add(const ValueType& value, int count);
+
+ // Helper functions to directly add a size or call stack to the RankedSet.
+ void AddSize(size_t size, int count) { Add(ValueType(size), count); }
+ void AddCallStack(const CallStack* call_stack, int count) {
+ Add(ValueType(call_stack), count);
+ }
+
+ private:
+ // Max and min counts. Returns 0 if the list is empty.
+ int max_count() const {
+ return entries_.empty() ? 0 : entries_.begin()->count;
+ }
+ int min_count() const {
+ return entries_.empty() ? 0 : entries_.rbegin()->count;
+ }
+
+ // Max number of items that can be stored in the list.
+ size_t max_size_;
+
+ // Actual storage container for entries.
+ EntrySet entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(RankedSet);
+};
+
+} // namespace leak_detector
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_LEAK_DETECTOR_RANKED_SET_H_
diff --git a/chromium/components/metrics/leak_detector/ranked_set_unittest.cc b/chromium/components/metrics/leak_detector/ranked_set_unittest.cc
new file mode 100644
index 00000000000..2e2de01c8b7
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/ranked_set_unittest.cc
@@ -0,0 +1,324 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/ranked_set.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+
+#include "base/macros.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "components/metrics/leak_detector/leak_detector_value_type.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+namespace leak_detector {
+
+namespace {
+
+// Makes it easier to instantiate LeakDetectorValueTypes.
+LeakDetectorValueType Value(uint32_t value) {
+ return LeakDetectorValueType(value);
+}
+
+} // namespace
+
+class RankedSetTest : public ::testing::Test {
+ public:
+ RankedSetTest() {}
+
+ void SetUp() override { CustomAllocator::Initialize(); }
+ void TearDown() override { EXPECT_TRUE(CustomAllocator::Shutdown()); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RankedSetTest);
+};
+
+TEST_F(RankedSetTest, Iterators) {
+ RankedSet set(10);
+ EXPECT_TRUE(set.begin() == set.end());
+
+ set.Add(Value(0x1234), 100);
+ EXPECT_FALSE(set.begin() == set.end());
+}
+
+TEST_F(RankedSetTest, SingleInsertion) {
+ RankedSet set(10);
+ EXPECT_EQ(0U, set.size());
+
+ set.Add(Value(0x1234), 100);
+ EXPECT_EQ(1U, set.size());
+
+ auto iter = set.begin();
+ EXPECT_EQ(0x1234U, iter->value.size());
+ EXPECT_EQ(100, iter->count);
+}
+
+TEST_F(RankedSetTest, InOrderInsertion) {
+ RankedSet set(10);
+ EXPECT_EQ(0U, set.size());
+
+ set.Add(Value(0x1234), 100);
+ EXPECT_EQ(1U, set.size());
+ set.Add(Value(0x2345), 95);
+ EXPECT_EQ(2U, set.size());
+ set.Add(Value(0x3456), 90);
+ EXPECT_EQ(3U, set.size());
+ set.Add(Value(0x4567), 85);
+ EXPECT_EQ(4U, set.size());
+ set.Add(Value(0x5678), 80);
+ EXPECT_EQ(5U, set.size());
+
+ // Iterate through the contents to make sure they match what went in.
+ const RankedSet::Entry kExpectedValues[] = {
+ {Value(0x1234), 100}, {Value(0x2345), 95}, {Value(0x3456), 90},
+ {Value(0x4567), 85}, {Value(0x5678), 80},
+ };
+
+ size_t index = 0;
+ for (const auto& entry : set) {
+ EXPECT_LT(index, arraysize(kExpectedValues));
+ EXPECT_EQ(kExpectedValues[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues[index].count, entry.count);
+ ++index;
+ }
+}
+
+TEST_F(RankedSetTest, ReverseOrderInsertion) {
+ RankedSet set(10);
+ EXPECT_EQ(0U, set.size());
+
+ set.Add(Value(0x1234), 0);
+ EXPECT_EQ(1U, set.size());
+ set.Add(Value(0x2345), 5);
+ EXPECT_EQ(2U, set.size());
+ set.Add(Value(0x3456), 10);
+ EXPECT_EQ(3U, set.size());
+ set.Add(Value(0x4567), 15);
+ EXPECT_EQ(4U, set.size());
+ set.Add(Value(0x5678), 20);
+ EXPECT_EQ(5U, set.size());
+
+ // Iterate through the contents to make sure they match what went in.
+ const RankedSet::Entry kExpectedValues[] = {
+ {Value(0x5678), 20}, {Value(0x4567), 15}, {Value(0x3456), 10},
+ {Value(0x2345), 5}, {Value(0x1234), 0},
+ };
+
+ size_t index = 0;
+ for (const auto& entry : set) {
+ EXPECT_LT(index, arraysize(kExpectedValues));
+ EXPECT_EQ(kExpectedValues[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues[index].count, entry.count);
+ ++index;
+ }
+}
+
+TEST_F(RankedSetTest, UnorderedInsertion) {
+ RankedSet set(10);
+ EXPECT_EQ(0U, set.size());
+
+ set.Add(Value(0x1234), 15);
+ set.Add(Value(0x2345), 20);
+ set.Add(Value(0x3456), 10);
+ set.Add(Value(0x4567), 30);
+ set.Add(Value(0x5678), 25);
+ EXPECT_EQ(5U, set.size());
+
+ // Iterate through the contents to make sure they match what went in.
+ const RankedSet::Entry kExpectedValues1[] = {
+ {Value(0x4567), 30}, {Value(0x5678), 25}, {Value(0x2345), 20},
+ {Value(0x1234), 15}, {Value(0x3456), 10},
+ };
+
+ size_t index = 0;
+ for (const auto& entry : set) {
+ EXPECT_LT(index, arraysize(kExpectedValues1));
+ EXPECT_EQ(kExpectedValues1[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues1[index].count, entry.count);
+ ++index;
+ }
+
+ // Add more items.
+ set.Add(Value(0x6789), 35);
+ set.Add(Value(0x789a), 40);
+ set.Add(Value(0x89ab), 50);
+ set.Add(Value(0x9abc), 5);
+ set.Add(Value(0xabcd), 0);
+ EXPECT_EQ(10U, set.size());
+
+ // Iterate through the contents to make sure they match what went in.
+ const RankedSet::Entry kExpectedValues2[] = {
+ {Value(0x89ab), 50}, {Value(0x789a), 40}, {Value(0x6789), 35},
+ {Value(0x4567), 30}, {Value(0x5678), 25}, {Value(0x2345), 20},
+ {Value(0x1234), 15}, {Value(0x3456), 10}, {Value(0x9abc), 5},
+ {Value(0xabcd), 0},
+ };
+
+ index = 0;
+ for (const auto& entry : set) {
+ EXPECT_LT(index, arraysize(kExpectedValues2));
+ EXPECT_EQ(kExpectedValues2[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues2[index].count, entry.count);
+ ++index;
+ }
+}
+
+TEST_F(RankedSetTest, UnorderedInsertionWithSameCounts) {
+ RankedSet set(10);
+ EXPECT_EQ(0U, set.size());
+
+ set.Add(Value(0x1234), 20);
+ set.Add(Value(0x2345), 20);
+ set.Add(Value(0x3456), 30);
+ set.Add(Value(0x4567), 30);
+ set.Add(Value(0x5678), 20);
+ EXPECT_EQ(5U, set.size());
+
+ // Iterate through the contents to make sure they match what went in. Entries
+ // with the same count should be ordered from lowest value to highest value.
+ const RankedSet::Entry kExpectedValues1[] = {
+ {Value(0x3456), 30}, {Value(0x4567), 30}, {Value(0x1234), 20},
+ {Value(0x2345), 20}, {Value(0x5678), 20},
+ };
+
+ size_t index = 0;
+ for (const auto& entry : set) {
+ EXPECT_LT(index, arraysize(kExpectedValues1));
+ EXPECT_EQ(kExpectedValues1[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues1[index].count, entry.count);
+ ++index;
+ }
+}
+
+TEST_F(RankedSetTest, RepeatedEntries) {
+ RankedSet set(10);
+ EXPECT_EQ(0U, set.size());
+
+ set.Add(Value(0x1234), 20);
+ set.Add(Value(0x3456), 30);
+ set.Add(Value(0x1234), 20);
+ set.Add(Value(0x3456), 30);
+ set.Add(Value(0x4567), 30);
+ set.Add(Value(0x4567), 30);
+ EXPECT_EQ(3U, set.size());
+
+ // Iterate through the contents to make sure they match what went in.
+ const RankedSet::Entry kExpectedValues1[] = {
+ {Value(0x3456), 30}, {Value(0x4567), 30}, {Value(0x1234), 20},
+ };
+
+ size_t index = 0;
+ for (const auto& entry : set) {
+ EXPECT_LT(index, arraysize(kExpectedValues1));
+ EXPECT_EQ(kExpectedValues1[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues1[index].count, entry.count);
+ ++index;
+ }
+}
+
+TEST_F(RankedSetTest, InsertionWithOverflow) {
+ RankedSet set(5);
+ EXPECT_EQ(0U, set.size());
+
+ set.Add(Value(0x1234), 15);
+ set.Add(Value(0x2345), 20);
+ set.Add(Value(0x3456), 10);
+ set.Add(Value(0x4567), 30);
+ set.Add(Value(0x5678), 25);
+ EXPECT_EQ(5U, set.size());
+
+ // These values will not make it into the set, which is now full.
+ set.Add(Value(0x6789), 0);
+ EXPECT_EQ(5U, set.size());
+ set.Add(Value(0x789a), 5);
+ EXPECT_EQ(5U, set.size());
+
+ // Iterate through the contents to make sure they match what went in.
+ const RankedSet::Entry kExpectedValues1[] = {
+ {Value(0x4567), 30}, {Value(0x5678), 25}, {Value(0x2345), 20},
+ {Value(0x1234), 15}, {Value(0x3456), 10},
+ };
+
+ size_t index = 0;
+ for (const auto& entry : set) {
+ EXPECT_LT(index, arraysize(kExpectedValues1));
+ EXPECT_EQ(kExpectedValues1[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues1[index].count, entry.count);
+ ++index;
+ }
+
+ // Insert some more values that go in the middle of the set.
+ set.Add(Value(0x89ab), 27);
+ EXPECT_EQ(5U, set.size());
+ set.Add(Value(0x9abc), 22);
+ EXPECT_EQ(5U, set.size());
+
+ // Iterate through the contents to make sure they match what went in.
+ const RankedSet::Entry kExpectedValues2[] = {
+ {Value(0x4567), 30}, {Value(0x89ab), 27}, {Value(0x5678), 25},
+ {Value(0x9abc), 22}, {Value(0x2345), 20},
+ };
+
+ index = 0;
+ for (const auto& entry : set) {
+ EXPECT_LT(index, arraysize(kExpectedValues2));
+ EXPECT_EQ(kExpectedValues2[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues2[index].count, entry.count);
+ ++index;
+ }
+
+ // Insert some more values at the front of the set.
+ set.Add(Value(0xabcd), 40);
+ EXPECT_EQ(5U, set.size());
+ set.Add(Value(0xbcde), 35);
+ EXPECT_EQ(5U, set.size());
+
+ // Iterate through the contents to make sure they match what went in.
+ const RankedSet::Entry kExpectedValues3[] = {
+ {Value(0xabcd), 40}, {Value(0xbcde), 35}, {Value(0x4567), 30},
+ {Value(0x89ab), 27}, {Value(0x5678), 25},
+ };
+
+ index = 0;
+ for (const auto& entry : set) {
+ EXPECT_LT(index, arraysize(kExpectedValues3));
+ EXPECT_EQ(kExpectedValues3[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues3[index].count, entry.count);
+ ++index;
+ }
+}
+
+TEST_F(RankedSetTest, MoveOperation) {
+ const RankedSet::Entry kExpectedValues[] = {
+ {Value(0x89ab), 50}, {Value(0x789a), 40}, {Value(0x6789), 35},
+ {Value(0x4567), 30}, {Value(0x5678), 25}, {Value(0x2345), 20},
+ {Value(0x1234), 15}, {Value(0x3456), 10}, {Value(0x9abc), 5},
+ {Value(0xabcd), 0},
+ };
+
+ RankedSet source(10);
+ for (const RankedSet::Entry& entry : kExpectedValues) {
+ source.Add(entry.value, entry.count);
+ }
+ EXPECT_EQ(10U, source.size());
+
+ RankedSet dest(25); // This should be changed by the move.
+ dest = std::move(source);
+ EXPECT_EQ(10U, dest.size());
+ EXPECT_EQ(10U, dest.max_size());
+
+ size_t index = 0;
+ for (const auto& entry : dest) {
+ EXPECT_LT(index, arraysize(kExpectedValues));
+ EXPECT_EQ(kExpectedValues[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues[index].count, entry.count);
+ ++index;
+ }
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/stl_allocator.h b/chromium/components/metrics/leak_detector/stl_allocator.h
new file mode 100644
index 00000000000..bfef0a38244
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/stl_allocator.h
@@ -0,0 +1,61 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_STL_ALLOCATOR_H_
+#define COMPONENTS_METRICS_LEAK_DETECTOR_STL_ALLOCATOR_H_
+
+#include <stddef.h>
+
+#include <limits>
+#include <memory>
+
+#include "base/logging.h"
+
+// Generic allocator class for STL objects.
+// deallocate() to use the template class Alloc's allocation.
+// that uses a given type-less allocator Alloc, which must provide:
+// static void* Alloc::Allocate(size_t size);
+// static void Alloc::Free(void* ptr, size_t size);
+//
+// Inherits from the default allocator, std::allocator. Overrides allocate() and
+// deallocate() and some other functions.
+//
+// STLAllocator<T, MyAlloc> provides the same thread-safety guarantees as
+// MyAlloc.
+//
+// Usage example:
+// set<T, less<T>, STLAllocator<T, MyAlloc> > my_set;
+
+template <typename T, class Alloc>
+class STLAllocator : public std::allocator<T> {
+ public:
+ typedef size_t size_type;
+ typedef T* pointer;
+
+ template <class T1>
+ struct rebind {
+ typedef STLAllocator<T1, Alloc> other;
+ };
+
+ STLAllocator() {}
+ explicit STLAllocator(const STLAllocator&) {}
+ template <class T1>
+ STLAllocator(const STLAllocator<T1, Alloc>&) {}
+ ~STLAllocator() {}
+
+ pointer allocate(size_type n, const void* = 0) {
+ // Make sure the computation of the total allocation size does not cause an
+ // integer overflow.
+ RAW_CHECK(n < max_size());
+ return static_cast<T*>(Alloc::Allocate(n * sizeof(T)));
+ }
+
+ void deallocate(pointer p, size_type n) { Alloc::Free(p, n * sizeof(T)); }
+
+ size_type max_size() const {
+ return std::numeric_limits<size_t>::max() / sizeof(T);
+ }
+};
+
+#endif // COMPONENTS_METRICS_LEAK_DETECTOR_STL_ALLOCATOR_H_
diff --git a/chromium/components/metrics/machine_id_provider.h b/chromium/components/metrics/machine_id_provider.h
new file mode 100644
index 00000000000..d7fcc447639
--- /dev/null
+++ b/chromium/components/metrics/machine_id_provider.h
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_MACHINE_ID_PROVIDER_H_
+#define COMPONENTS_METRICS_MACHINE_ID_PROVIDER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+
+namespace metrics {
+
+// Provides machine characteristics used as a machine id. The implementation is
+// platform specific with a default implementation that gives an empty id. The
+// class is ref-counted thread safe so it can be used to post to the FILE thread
+// and communicate back to the UI thread.
+// This raw machine id should not be stored or transmitted over the network.
+// TODO(jwd): Simplify implementation to get rid of the need for
+// RefCountedThreadSafe (crbug.com/354882).
+class MachineIdProvider : public base::RefCountedThreadSafe<MachineIdProvider> {
+ public:
+ // Get a string containing machine characteristics, to be used as a machine
+ // id. The implementation is platform specific, with a default implementation
+ // returning an empty string.
+ // The return value should not be stored to disk or transmitted.
+ std::string GetMachineId();
+
+ // Returns a pointer to a new MachineIdProvider or NULL if there is no
+ // provider implemented on a given platform. This is done to avoid posting a
+ // task to the FILE thread on platforms with no implementation.
+ static MachineIdProvider* CreateInstance();
+
+ private:
+ friend class base::RefCountedThreadSafe<MachineIdProvider>;
+
+ MachineIdProvider();
+ virtual ~MachineIdProvider();
+
+ DISALLOW_COPY_AND_ASSIGN(MachineIdProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_MACHINE_ID_PROVIDER_H_
diff --git a/chromium/components/metrics/machine_id_provider_stub.cc b/chromium/components/metrics/machine_id_provider_stub.cc
new file mode 100644
index 00000000000..626f2b7cdc8
--- /dev/null
+++ b/chromium/components/metrics/machine_id_provider_stub.cc
@@ -0,0 +1,24 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/machine_id_provider.h"
+
+namespace metrics {
+
+MachineIdProvider::MachineIdProvider() {
+}
+
+MachineIdProvider::~MachineIdProvider() {
+}
+
+// static
+MachineIdProvider* MachineIdProvider::CreateInstance() {
+ return NULL;
+}
+
+std::string MachineIdProvider::GetMachineId() {
+ return std::string();
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/machine_id_provider_win.cc b/chromium/components/metrics/machine_id_provider_win.cc
new file mode 100644
index 00000000000..41079a7dc2c
--- /dev/null
+++ b/chromium/components/metrics/machine_id_provider_win.cc
@@ -0,0 +1,118 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/machine_id_provider.h"
+
+#include <windows.h>
+#include <stdint.h>
+#include <winioctl.h>
+
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/win/scoped_handle.h"
+
+namespace metrics {
+
+MachineIdProvider::MachineIdProvider() {
+}
+
+MachineIdProvider::~MachineIdProvider() {
+}
+
+// On windows, the machine id is based on the serial number of the drive Chrome
+// is running from.
+std::string MachineIdProvider::GetMachineId() {
+ base::ThreadRestrictions::AssertIOAllowed();
+
+ // Use the program's path to get the drive used for the machine id. This means
+ // that whenever the underlying drive changes, it's considered a new machine.
+ // This is fine as we do not support migrating Chrome installs to new drives.
+ base::FilePath executable_path;
+
+ if (!PathService::Get(base::FILE_EXE, &executable_path)) {
+ NOTREACHED();
+ return std::string();
+ }
+
+ std::vector<base::FilePath::StringType> path_components;
+ executable_path.GetComponents(&path_components);
+ if (path_components.empty()) {
+ NOTREACHED();
+ return std::string();
+ }
+ base::FilePath::StringType drive_name = L"\\\\.\\" + path_components[0];
+
+ base::win::ScopedHandle drive_handle(
+ CreateFile(drive_name.c_str(),
+ 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL));
+
+ STORAGE_PROPERTY_QUERY query = {};
+ query.PropertyId = StorageDeviceProperty;
+ query.QueryType = PropertyStandardQuery;
+
+ // Perform an initial query to get the number of bytes being returned.
+ DWORD bytes_returned;
+ STORAGE_DESCRIPTOR_HEADER header = {};
+ BOOL status = DeviceIoControl(drive_handle.Get(),
+ IOCTL_STORAGE_QUERY_PROPERTY,
+ &query,
+ sizeof(STORAGE_PROPERTY_QUERY),
+ &header,
+ sizeof(STORAGE_DESCRIPTOR_HEADER),
+ &bytes_returned,
+ NULL);
+
+ if (!status)
+ return std::string();
+
+ // Query for the actual serial number.
+ std::vector<int8_t> output_buf(header.Size);
+ status = DeviceIoControl(drive_handle.Get(),
+ IOCTL_STORAGE_QUERY_PROPERTY,
+ &query,
+ sizeof(STORAGE_PROPERTY_QUERY),
+ &output_buf[0],
+ output_buf.size(),
+ &bytes_returned,
+ NULL);
+
+ if (!status)
+ return std::string();
+
+ const STORAGE_DEVICE_DESCRIPTOR* device_descriptor =
+ reinterpret_cast<STORAGE_DEVICE_DESCRIPTOR*>(&output_buf[0]);
+
+ // The serial number is stored in the |output_buf| as a null-terminated
+ // string starting at the specified offset.
+ const DWORD offset = device_descriptor->SerialNumberOffset;
+ if (offset >= output_buf.size())
+ return std::string();
+
+ // Make sure that the null-terminator exists.
+ const std::vector<int8_t>::iterator serial_number_begin =
+ output_buf.begin() + offset;
+ const std::vector<int8_t>::iterator null_location =
+ std::find(serial_number_begin, output_buf.end(), '\0');
+ if (null_location == output_buf.end())
+ return std::string();
+
+ const char* serial_number =
+ reinterpret_cast<const char*>(&output_buf[offset]);
+
+ return std::string(serial_number);
+}
+
+// static
+MachineIdProvider* MachineIdProvider::CreateInstance() {
+ return new MachineIdProvider();
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/machine_id_provider_win_unittest.cc b/chromium/components/metrics/machine_id_provider_win_unittest.cc
new file mode 100644
index 00000000000..11ffd8f73df
--- /dev/null
+++ b/chromium/components/metrics/machine_id_provider_win_unittest.cc
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/machine_id_provider.h"
+
+#include "base/memory/ref_counted.h"
+#include "base/win/windows_version.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+TEST(MachineIdProviderTest, GetId) {
+ scoped_refptr<MachineIdProvider> provider(
+ MachineIdProvider::CreateInstance());
+ std::string id1 = provider->GetMachineId();
+
+ // TODO(rpaquay): See crbug/458230
+ if (base::win::GetVersion() <= base::win::VERSION_XP)
+ return;
+
+ EXPECT_NE(std::string(), id1);
+
+ std::string id2 = provider->GetMachineId();
+ EXPECT_EQ(id1, id2);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_log.cc b/chromium/components/metrics/metrics_log.cc
new file mode 100644
index 00000000000..32db4184462
--- /dev/null
+++ b/chromium/components/metrics/metrics_log.cc
@@ -0,0 +1,423 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_log.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/build_time.h"
+#include "base/cpu.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/sys_info.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/metrics/histogram_encoder.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_provider.h"
+#include "components/metrics/metrics_service_client.h"
+#include "components/metrics/proto/histogram_event.pb.h"
+#include "components/metrics/proto/system_profile.pb.h"
+#include "components/metrics/proto/user_action_event.pb.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/active_field_trials.h"
+
+#if defined(OS_ANDROID)
+#include "base/android/build_info.h"
+#endif
+
+#if defined(OS_WIN)
+#include "base/win/current_module.h"
+#endif
+
+using base::SampleCountIterator;
+typedef variations::ActiveGroupId ActiveGroupId;
+
+namespace metrics {
+
+namespace {
+
+// Any id less than 16 bytes is considered to be a testing id.
+bool IsTestingID(const std::string& id) {
+ return id.size() < 16;
+}
+
+// Computes a SHA-1 hash of |data| and returns it as a hex string.
+std::string ComputeSHA1(const std::string& data) {
+ const std::string sha1 = base::SHA1HashString(data);
+ return base::HexEncode(sha1.data(), sha1.size());
+}
+
+void WriteFieldTrials(const std::vector<ActiveGroupId>& field_trial_ids,
+ SystemProfileProto* system_profile) {
+ for (std::vector<ActiveGroupId>::const_iterator it =
+ field_trial_ids.begin(); it != field_trial_ids.end(); ++it) {
+ SystemProfileProto::FieldTrial* field_trial =
+ system_profile->add_field_trial();
+ field_trial->set_name_id(it->name);
+ field_trial->set_group_id(it->group);
+ }
+}
+
+// Round a timestamp measured in seconds since epoch to one with a granularity
+// of an hour. This can be used before uploaded potentially sensitive
+// timestamps.
+int64_t RoundSecondsToHour(int64_t time_in_seconds) {
+ return 3600 * (time_in_seconds / 3600);
+}
+
+} // namespace
+
+MetricsLog::MetricsLog(const std::string& client_id,
+ int session_id,
+ LogType log_type,
+ MetricsServiceClient* client,
+ PrefService* local_state)
+ : closed_(false),
+ log_type_(log_type),
+ client_(client),
+ creation_time_(base::TimeTicks::Now()),
+ local_state_(local_state) {
+ if (IsTestingID(client_id))
+ uma_proto_.set_client_id(0);
+ else
+ uma_proto_.set_client_id(Hash(client_id));
+
+ uma_proto_.set_session_id(session_id);
+
+ const int32_t product = client_->GetProduct();
+ // Only set the product if it differs from the default value.
+ if (product != uma_proto_.product())
+ uma_proto_.set_product(product);
+
+ SystemProfileProto* system_profile = uma_proto_.mutable_system_profile();
+ system_profile->set_build_timestamp(GetBuildTime());
+ system_profile->set_app_version(client_->GetVersionString());
+ system_profile->set_channel(client_->GetChannel());
+#if defined(SYZYASAN)
+ system_profile->set_is_asan_build(true);
+#endif
+}
+
+MetricsLog::~MetricsLog() {
+}
+
+// static
+void MetricsLog::RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterIntegerPref(prefs::kStabilityLaunchCount, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityCrashCount, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityIncompleteSessionEndCount, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityBreakpadRegistrationFail, 0);
+ registry->RegisterIntegerPref(
+ prefs::kStabilityBreakpadRegistrationSuccess, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityDebuggerPresent, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityDebuggerNotPresent, 0);
+ registry->RegisterStringPref(prefs::kStabilitySavedSystemProfile,
+ std::string());
+ registry->RegisterStringPref(prefs::kStabilitySavedSystemProfileHash,
+ std::string());
+}
+
+// static
+uint64_t MetricsLog::Hash(const std::string& value) {
+ uint64_t hash = base::HashMetricName(value);
+
+ // The following log is VERY helpful when folks add some named histogram into
+ // the code, but forgot to update the descriptive list of histograms. When
+ // that happens, all we get to see (server side) is a hash of the histogram
+ // name. We can then use this logging to find out what histogram name was
+ // being hashed to a given MD5 value by just running the version of Chromium
+ // in question with --enable-logging.
+ DVLOG(1) << "Metrics: Hash numeric [" << value << "]=[" << hash << "]";
+
+ return hash;
+}
+
+// static
+int64_t MetricsLog::GetBuildTime() {
+ static int64_t integral_build_time = 0;
+ if (!integral_build_time)
+ integral_build_time = static_cast<int64_t>(base::GetBuildTime().ToTimeT());
+ return integral_build_time;
+}
+
+// static
+int64_t MetricsLog::GetCurrentTime() {
+ return (base::TimeTicks::Now() - base::TimeTicks()).InSeconds();
+}
+
+void MetricsLog::RecordUserAction(const std::string& key) {
+ DCHECK(!closed_);
+
+ UserActionEventProto* user_action = uma_proto_.add_user_action_event();
+ user_action->set_name_hash(Hash(key));
+ user_action->set_time(GetCurrentTime());
+}
+
+void MetricsLog::RecordHistogramDelta(const std::string& histogram_name,
+ const base::HistogramSamples& snapshot) {
+ DCHECK(!closed_);
+ EncodeHistogramDelta(histogram_name, snapshot, &uma_proto_);
+}
+
+void MetricsLog::RecordStabilityMetrics(
+ const std::vector<MetricsProvider*>& metrics_providers,
+ base::TimeDelta incremental_uptime,
+ base::TimeDelta uptime) {
+ DCHECK(!closed_);
+ DCHECK(HasEnvironment());
+ DCHECK(!HasStabilityMetrics());
+
+ PrefService* pref = local_state_;
+ DCHECK(pref);
+
+ // Get stability attributes out of Local State, zeroing out stored values.
+ // NOTE: This could lead to some data loss if this report isn't successfully
+ // sent, but that's true for all the metrics.
+
+ WriteRequiredStabilityAttributes(pref);
+
+ // Record recent delta for critical stability metrics. We can't wait for a
+ // restart to gather these, as that delay biases our observation away from
+ // users that run happily for a looooong time. We send increments with each
+ // uma log upload, just as we send histogram data.
+ WriteRealtimeStabilityAttributes(pref, incremental_uptime, uptime);
+
+ SystemProfileProto* system_profile = uma_proto()->mutable_system_profile();
+ for (size_t i = 0; i < metrics_providers.size(); ++i) {
+ if (log_type() == INITIAL_STABILITY_LOG)
+ metrics_providers[i]->ProvideInitialStabilityMetrics(system_profile);
+ metrics_providers[i]->ProvideStabilityMetrics(system_profile);
+ }
+
+ // Omit some stats unless this is the initial stability log.
+ if (log_type() != INITIAL_STABILITY_LOG)
+ return;
+
+ int incomplete_shutdown_count =
+ pref->GetInteger(prefs::kStabilityIncompleteSessionEndCount);
+ pref->SetInteger(prefs::kStabilityIncompleteSessionEndCount, 0);
+ int breakpad_registration_success_count =
+ pref->GetInteger(prefs::kStabilityBreakpadRegistrationSuccess);
+ pref->SetInteger(prefs::kStabilityBreakpadRegistrationSuccess, 0);
+ int breakpad_registration_failure_count =
+ pref->GetInteger(prefs::kStabilityBreakpadRegistrationFail);
+ pref->SetInteger(prefs::kStabilityBreakpadRegistrationFail, 0);
+ int debugger_present_count =
+ pref->GetInteger(prefs::kStabilityDebuggerPresent);
+ pref->SetInteger(prefs::kStabilityDebuggerPresent, 0);
+ int debugger_not_present_count =
+ pref->GetInteger(prefs::kStabilityDebuggerNotPresent);
+ pref->SetInteger(prefs::kStabilityDebuggerNotPresent, 0);
+
+ // TODO(jar): The following are all optional, so we *could* optimize them for
+ // values of zero (and not include them).
+ SystemProfileProto::Stability* stability =
+ system_profile->mutable_stability();
+ stability->set_incomplete_shutdown_count(incomplete_shutdown_count);
+ stability->set_breakpad_registration_success_count(
+ breakpad_registration_success_count);
+ stability->set_breakpad_registration_failure_count(
+ breakpad_registration_failure_count);
+ stability->set_debugger_present_count(debugger_present_count);
+ stability->set_debugger_not_present_count(debugger_not_present_count);
+}
+
+void MetricsLog::RecordGeneralMetrics(
+ const std::vector<MetricsProvider*>& metrics_providers) {
+ for (size_t i = 0; i < metrics_providers.size(); ++i)
+ metrics_providers[i]->ProvideGeneralMetrics(uma_proto());
+}
+
+void MetricsLog::GetFieldTrialIds(
+ std::vector<ActiveGroupId>* field_trial_ids) const {
+ variations::GetFieldTrialActiveGroupIds(field_trial_ids);
+}
+
+bool MetricsLog::HasEnvironment() const {
+ return uma_proto()->system_profile().has_uma_enabled_date();
+}
+
+void MetricsLog::WriteMetricsEnableDefault(
+ MetricsServiceClient::EnableMetricsDefault metrics_default,
+ SystemProfileProto* system_profile) {
+ if (client_->IsReportingPolicyManaged()) {
+ // If it's managed, then it must be reporting, otherwise we wouldn't be
+ // sending metrics.
+ system_profile->set_uma_default_state(
+ SystemProfileProto_UmaDefaultState_POLICY_FORCED_ENABLED);
+ return;
+ }
+
+ switch (metrics_default) {
+ case MetricsServiceClient::DEFAULT_UNKNOWN:
+ // Don't set the field if it's unknown.
+ break;
+ case MetricsServiceClient::OPT_IN:
+ system_profile->set_uma_default_state(
+ SystemProfileProto_UmaDefaultState_OPT_IN);
+ break;
+ case MetricsServiceClient::OPT_OUT:
+ system_profile->set_uma_default_state(
+ SystemProfileProto_UmaDefaultState_OPT_OUT);
+ }
+}
+
+bool MetricsLog::HasStabilityMetrics() const {
+ return uma_proto()->system_profile().stability().has_launch_count();
+}
+
+// The server refuses data that doesn't have certain values. crashcount and
+// launchcount are currently "required" in the "stability" group.
+// TODO(isherman): Stop writing these attributes specially once the migration to
+// protobufs is complete.
+void MetricsLog::WriteRequiredStabilityAttributes(PrefService* pref) {
+ int launch_count = pref->GetInteger(prefs::kStabilityLaunchCount);
+ pref->SetInteger(prefs::kStabilityLaunchCount, 0);
+ int crash_count = pref->GetInteger(prefs::kStabilityCrashCount);
+ pref->SetInteger(prefs::kStabilityCrashCount, 0);
+
+ SystemProfileProto::Stability* stability =
+ uma_proto()->mutable_system_profile()->mutable_stability();
+ stability->set_launch_count(launch_count);
+ stability->set_crash_count(crash_count);
+}
+
+void MetricsLog::WriteRealtimeStabilityAttributes(
+ PrefService* pref,
+ base::TimeDelta incremental_uptime,
+ base::TimeDelta uptime) {
+ // Update the stats which are critical for real-time stability monitoring.
+ // Since these are "optional," only list ones that are non-zero, as the counts
+ // are aggregated (summed) server side.
+
+ SystemProfileProto::Stability* stability =
+ uma_proto()->mutable_system_profile()->mutable_stability();
+
+ const uint64_t incremental_uptime_sec = incremental_uptime.InSeconds();
+ if (incremental_uptime_sec)
+ stability->set_incremental_uptime_sec(incremental_uptime_sec);
+ const uint64_t uptime_sec = uptime.InSeconds();
+ if (uptime_sec)
+ stability->set_uptime_sec(uptime_sec);
+}
+
+void MetricsLog::RecordEnvironment(
+ const std::vector<MetricsProvider*>& metrics_providers,
+ const std::vector<variations::ActiveGroupId>& synthetic_trials,
+ int64_t install_date,
+ int64_t metrics_reporting_enabled_date) {
+ DCHECK(!HasEnvironment());
+
+ SystemProfileProto* system_profile = uma_proto()->mutable_system_profile();
+
+ WriteMetricsEnableDefault(client_->GetDefaultOptIn(), system_profile);
+
+ std::string brand_code;
+ if (client_->GetBrand(&brand_code))
+ system_profile->set_brand_code(brand_code);
+
+ // Reduce granularity of the enabled_date field to nearest hour.
+ system_profile->set_uma_enabled_date(
+ RoundSecondsToHour(metrics_reporting_enabled_date));
+
+ // Reduce granularity of the install_date field to nearest hour.
+ system_profile->set_install_date(RoundSecondsToHour(install_date));
+
+ system_profile->set_application_locale(client_->GetApplicationLocale());
+
+ SystemProfileProto::Hardware* hardware = system_profile->mutable_hardware();
+
+ // HardwareModelName() will return an empty string on platforms where it's
+ // not implemented or if an error occured.
+ hardware->set_hardware_class(base::SysInfo::HardwareModelName());
+
+ hardware->set_cpu_architecture(base::SysInfo::OperatingSystemArchitecture());
+ hardware->set_system_ram_mb(base::SysInfo::AmountOfPhysicalMemoryMB());
+#if defined(OS_WIN)
+ hardware->set_dll_base(reinterpret_cast<uint64_t>(CURRENT_MODULE()));
+#endif
+
+#if defined(OVERRIDE_OS_NAME_TO_BLIMP)
+ os->set_name("Blimp");
+#else
+ SystemProfileProto::OS* os = system_profile->mutable_os();
+ std::string os_name = base::SysInfo::OperatingSystemName();
+ os->set_name(os_name);
+#endif
+
+ os->set_version(base::SysInfo::OperatingSystemVersion());
+#if defined(OS_ANDROID)
+ os->set_fingerprint(
+ base::android::BuildInfo::GetInstance()->android_build_fp());
+#endif
+
+ base::CPU cpu_info;
+ SystemProfileProto::Hardware::CPU* cpu = hardware->mutable_cpu();
+ cpu->set_vendor_name(cpu_info.vendor_name());
+ cpu->set_signature(cpu_info.signature());
+ cpu->set_num_cores(base::SysInfo::NumberOfProcessors());
+
+ std::vector<ActiveGroupId> field_trial_ids;
+ GetFieldTrialIds(&field_trial_ids);
+ WriteFieldTrials(field_trial_ids, system_profile);
+ WriteFieldTrials(synthetic_trials, system_profile);
+
+ for (size_t i = 0; i < metrics_providers.size(); ++i)
+ metrics_providers[i]->ProvideSystemProfileMetrics(system_profile);
+
+ std::string serialied_system_profile;
+ std::string base64_system_profile;
+ if (system_profile->SerializeToString(&serialied_system_profile)) {
+ base::Base64Encode(serialied_system_profile, &base64_system_profile);
+ PrefService* local_state = local_state_;
+ local_state->SetString(prefs::kStabilitySavedSystemProfile,
+ base64_system_profile);
+ local_state->SetString(prefs::kStabilitySavedSystemProfileHash,
+ ComputeSHA1(serialied_system_profile));
+ }
+}
+
+bool MetricsLog::LoadSavedEnvironmentFromPrefs() {
+ PrefService* local_state = local_state_;
+ const std::string base64_system_profile =
+ local_state->GetString(prefs::kStabilitySavedSystemProfile);
+ if (base64_system_profile.empty())
+ return false;
+
+ const std::string system_profile_hash =
+ local_state->GetString(prefs::kStabilitySavedSystemProfileHash);
+ local_state->ClearPref(prefs::kStabilitySavedSystemProfile);
+ local_state->ClearPref(prefs::kStabilitySavedSystemProfileHash);
+
+ SystemProfileProto* system_profile = uma_proto()->mutable_system_profile();
+ std::string serialied_system_profile;
+ return base::Base64Decode(base64_system_profile, &serialied_system_profile) &&
+ ComputeSHA1(serialied_system_profile) == system_profile_hash &&
+ system_profile->ParseFromString(serialied_system_profile);
+}
+
+void MetricsLog::CloseLog() {
+ DCHECK(!closed_);
+ closed_ = true;
+}
+
+void MetricsLog::GetEncodedLog(std::string* encoded_log) {
+ DCHECK(closed_);
+ uma_proto_.SerializeToString(encoded_log);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_log.h b/chromium/components/metrics/metrics_log.h
new file mode 100644
index 00000000000..e2324f8ac36
--- /dev/null
+++ b/chromium/components/metrics/metrics_log.h
@@ -0,0 +1,209 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines a set of user experience metrics data recorded by
+// the MetricsService. This is the unit of data that is sent to the server.
+
+#ifndef COMPONENTS_METRICS_METRICS_LOG_H_
+#define COMPONENTS_METRICS_METRICS_LOG_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "components/metrics/metrics_service_client.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace base {
+class DictionaryValue;
+class HistogramSamples;
+}
+
+namespace content {
+struct WebPluginInfo;
+}
+
+namespace variations {
+struct ActiveGroupId;
+}
+
+namespace metrics {
+
+class MetricsProvider;
+class MetricsServiceClient;
+
+class MetricsLog {
+ public:
+ enum LogType {
+ INITIAL_STABILITY_LOG, // The initial log containing stability stats.
+ ONGOING_LOG, // Subsequent logs in a session.
+ };
+
+ // Creates a new metrics log of the specified type.
+ // |client_id| is the identifier for this profile on this installation
+ // |session_id| is an integer that's incremented on each application launch
+ // |client| is used to interact with the embedder.
+ // |local_state| is the PrefService that this instance should use.
+ // Note: |this| instance does not take ownership of the |client|, but rather
+ // stores a weak pointer to it. The caller should ensure that the |client| is
+ // valid for the lifetime of this class.
+ MetricsLog(const std::string& client_id,
+ int session_id,
+ LogType log_type,
+ MetricsServiceClient* client,
+ PrefService* local_state);
+ virtual ~MetricsLog();
+
+ // Registers local state prefs used by this class.
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ // Computes the MD5 hash of the given string, and returns the first 8 bytes of
+ // the hash.
+ static uint64_t Hash(const std::string& value);
+
+ // Get the GMT buildtime for the current binary, expressed in seconds since
+ // January 1, 1970 GMT.
+ // The value is used to identify when a new build is run, so that previous
+ // reliability stats, from other builds, can be abandoned.
+ static int64_t GetBuildTime();
+
+ // Convenience function to return the current time at a resolution in seconds.
+ // This wraps base::TimeTicks, and hence provides an abstract time that is
+ // always incrementing for use in measuring time durations.
+ static int64_t GetCurrentTime();
+
+ // Records a user-initiated action.
+ void RecordUserAction(const std::string& key);
+
+ // Record any changes in a given histogram for transmission.
+ void RecordHistogramDelta(const std::string& histogram_name,
+ const base::HistogramSamples& snapshot);
+
+ // Records the current operating environment, including metrics provided by
+ // the specified set of |metrics_providers|. Takes the list of installed
+ // plugins, Google Update statistics, and synthetic trial IDs as parameters
+ // because those can't be obtained synchronously from the UI thread.
+ // A synthetic trial is one that is set up dynamically by code in Chrome. For
+ // example, a pref may be mapped to a synthetic trial such that the group
+ // is determined by the pref value.
+ void RecordEnvironment(
+ const std::vector<MetricsProvider*>& metrics_providers,
+ const std::vector<variations::ActiveGroupId>& synthetic_trials,
+ int64_t install_date,
+ int64_t metrics_reporting_enabled_date);
+
+ // Loads the environment proto that was saved by the last RecordEnvironment()
+ // call from prefs and clears the pref value. Returns true on success or false
+ // if there was no saved environment in prefs or it could not be decoded.
+ bool LoadSavedEnvironmentFromPrefs();
+
+ // Writes application stability metrics, including stability metrics provided
+ // by the specified set of |metrics_providers|. The system profile portion of
+ // the log must have already been filled in by a call to RecordEnvironment()
+ // or LoadSavedEnvironmentFromPrefs().
+ // NOTE: Has the side-effect of clearing the stability prefs..
+ //
+ // If this log is of type INITIAL_STABILITY_LOG, records additional info such
+ // as number of incomplete shutdowns as well as extra breakpad and debugger
+ // stats.
+ void RecordStabilityMetrics(
+ const std::vector<MetricsProvider*>& metrics_providers,
+ base::TimeDelta incremental_uptime,
+ base::TimeDelta uptime);
+
+ // Records general metrics based on the specified |metrics_providers|.
+ void RecordGeneralMetrics(
+ const std::vector<MetricsProvider*>& metrics_providers);
+
+ // Stop writing to this record and generate the encoded representation.
+ // None of the Record* methods can be called after this is called.
+ void CloseLog();
+
+ // Fills |encoded_log| with the serialized protobuf representation of the
+ // record. Must only be called after CloseLog() has been called.
+ void GetEncodedLog(std::string* encoded_log);
+
+ const base::TimeTicks& creation_time() const {
+ return creation_time_;
+ }
+
+ int num_events() const {
+ return uma_proto_.omnibox_event_size() +
+ uma_proto_.user_action_event_size();
+ }
+
+ LogType log_type() const { return log_type_; }
+
+ protected:
+ // Exposed for the sake of mocking/accessing in test code.
+
+ // Fills |field_trial_ids| with the list of initialized field trials name and
+ // group ids.
+ virtual void GetFieldTrialIds(
+ std::vector<variations::ActiveGroupId>* field_trial_ids) const;
+
+ ChromeUserMetricsExtension* uma_proto() { return &uma_proto_; }
+
+ // Exposed to allow subclass to access to export the uma_proto. Can be used
+ // by external components to export logs to Chrome.
+ const ChromeUserMetricsExtension* uma_proto() const {
+ return &uma_proto_;
+ }
+
+ private:
+ // Returns true if the environment has already been filled in by a call to
+ // RecordEnvironment() or LoadSavedEnvironmentFromPrefs().
+ bool HasEnvironment() const;
+
+ // Write the default state of the enable metrics checkbox.
+ void WriteMetricsEnableDefault(
+ MetricsServiceClient::EnableMetricsDefault metrics_default,
+ SystemProfileProto* system_profile);
+
+ // Returns true if the stability metrics have already been filled in by a
+ // call to RecordStabilityMetrics().
+ bool HasStabilityMetrics() const;
+
+ // Within the stability group, write required attributes.
+ void WriteRequiredStabilityAttributes(PrefService* pref);
+
+ // Within the stability group, write attributes that need to be updated asap
+ // and can't be delayed until the user decides to restart chromium.
+ // Delaying these stats would bias metrics away from happy long lived
+ // chromium processes (ones that don't crash, and keep on running).
+ void WriteRealtimeStabilityAttributes(PrefService* pref,
+ base::TimeDelta incremental_uptime,
+ base::TimeDelta uptime);
+
+ // closed_ is true when record has been packed up for sending, and should
+ // no longer be written to. It is only used for sanity checking.
+ bool closed_;
+
+ // The type of the log, i.e. initial or ongoing.
+ const LogType log_type_;
+
+ // Stores the protocol buffer representation for this log.
+ ChromeUserMetricsExtension uma_proto_;
+
+ // Used to interact with the embedder. Weak pointer; must outlive |this|
+ // instance.
+ MetricsServiceClient* const client_;
+
+ // The time when the current log was created.
+ const base::TimeTicks creation_time_;
+
+ PrefService* local_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsLog);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_LOG_H_
diff --git a/chromium/components/metrics/metrics_log_manager.cc b/chromium/components/metrics/metrics_log_manager.cc
new file mode 100644
index 00000000000..661dacfa55f
--- /dev/null
+++ b/chromium/components/metrics/metrics_log_manager.cc
@@ -0,0 +1,130 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_log_manager.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/strings/string_util.h"
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/metrics_pref_names.h"
+
+namespace metrics {
+
+namespace {
+
+// The number of "initial" logs to save, and hope to send during a future Chrome
+// session. Initial logs contain crash stats, and are pretty small.
+const size_t kInitialLogsPersistLimit = 20;
+
+// The number of ongoing logs to save persistently, and hope to
+// send during a this or future sessions. Note that each log may be pretty
+// large, as presumably the related "initial" log wasn't sent (probably nothing
+// was, as the user was probably off-line). As a result, the log probably kept
+// accumulating while the "initial" log was stalled, and couldn't be sent. As a
+// result, we don't want to save too many of these mega-logs.
+// A "standard shutdown" will create a small log, including just the data that
+// was not yet been transmitted, and that is normal (to have exactly one
+// ongoing_log_ at startup).
+const size_t kOngoingLogsPersistLimit = 8;
+
+// The number of bytes each of initial and ongoing logs that must be stored.
+// This ensures that a reasonable amount of history will be stored even if there
+// is a long series of very small logs.
+const size_t kStorageByteLimitPerLogType = 300000;
+
+} // namespace
+
+MetricsLogManager::MetricsLogManager(PrefService* local_state,
+ size_t max_ongoing_log_size)
+ : unsent_logs_loaded_(false),
+ initial_log_queue_(local_state,
+ prefs::kMetricsInitialLogs,
+ kInitialLogsPersistLimit,
+ kStorageByteLimitPerLogType,
+ 0),
+ ongoing_log_queue_(local_state,
+ prefs::kMetricsOngoingLogs,
+ kOngoingLogsPersistLimit,
+ kStorageByteLimitPerLogType,
+ max_ongoing_log_size) {}
+
+MetricsLogManager::~MetricsLogManager() {}
+
+void MetricsLogManager::BeginLoggingWithLog(scoped_ptr<MetricsLog> log) {
+ DCHECK(!current_log_);
+ current_log_ = std::move(log);
+}
+
+void MetricsLogManager::FinishCurrentLog() {
+ DCHECK(current_log_.get());
+ current_log_->CloseLog();
+ std::string log_data;
+ current_log_->GetEncodedLog(&log_data);
+ if (!log_data.empty())
+ StoreLog(log_data, current_log_->log_type());
+ current_log_.reset();
+}
+
+void MetricsLogManager::StageNextLogForUpload() {
+ DCHECK(!has_staged_log());
+ if (!initial_log_queue_.empty())
+ initial_log_queue_.StageLog();
+ else
+ ongoing_log_queue_.StageLog();
+}
+
+void MetricsLogManager::DiscardStagedLog() {
+ DCHECK(has_staged_log());
+ if (initial_log_queue_.has_staged_log())
+ initial_log_queue_.DiscardStagedLog();
+ else
+ ongoing_log_queue_.DiscardStagedLog();
+ DCHECK(!has_staged_log());
+}
+
+void MetricsLogManager::DiscardCurrentLog() {
+ current_log_->CloseLog();
+ current_log_.reset();
+}
+
+void MetricsLogManager::PauseCurrentLog() {
+ DCHECK(!paused_log_.get());
+ paused_log_.reset(current_log_.release());
+}
+
+void MetricsLogManager::ResumePausedLog() {
+ DCHECK(!current_log_.get());
+ current_log_.reset(paused_log_.release());
+}
+
+void MetricsLogManager::StoreLog(const std::string& log_data,
+ MetricsLog::LogType log_type) {
+ switch (log_type) {
+ case MetricsLog::INITIAL_STABILITY_LOG:
+ initial_log_queue_.StoreLog(log_data);
+ break;
+ case MetricsLog::ONGOING_LOG:
+ ongoing_log_queue_.StoreLog(log_data);
+ break;
+ }
+}
+
+void MetricsLogManager::PersistUnsentLogs() {
+ DCHECK(unsent_logs_loaded_);
+ if (!unsent_logs_loaded_)
+ return;
+
+ initial_log_queue_.SerializeLogs();
+ ongoing_log_queue_.SerializeLogs();
+}
+
+void MetricsLogManager::LoadPersistedUnsentLogs() {
+ initial_log_queue_.DeserializeLogs();
+ ongoing_log_queue_.DeserializeLogs();
+ unsent_logs_loaded_ = true;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_log_manager.h b/chromium/components/metrics/metrics_log_manager.h
new file mode 100644
index 00000000000..c4f2082bfdf
--- /dev/null
+++ b/chromium/components/metrics/metrics_log_manager.h
@@ -0,0 +1,118 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_LOG_MANAGER_H_
+#define COMPONENTS_METRICS_METRICS_LOG_MANAGER_H_
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/persisted_logs.h"
+
+namespace metrics {
+
+// Manages all the log objects used by a MetricsService implementation. Keeps
+// track of both an in progress log and a log that is staged for uploading as
+// text, as well as saving logs to, and loading logs from, persistent storage.
+class MetricsLogManager {
+ public:
+ // The metrics log manager will persist it's unsent logs by storing them in
+ // |local_state|, and will not persist ongoing logs over
+ // |max_ongoing_log_size|.
+ MetricsLogManager(PrefService* local_state, size_t max_ongoing_log_size);
+ ~MetricsLogManager();
+
+ // Makes |log| the current_log. This should only be called if there is not a
+ // current log.
+ void BeginLoggingWithLog(scoped_ptr<MetricsLog> log);
+
+ // Returns the in-progress log.
+ MetricsLog* current_log() { return current_log_.get(); }
+
+ // Closes current_log(), compresses it, and stores the compressed log for
+ // later, leaving current_log() NULL.
+ void FinishCurrentLog();
+
+ // Returns true if there are any logs waiting to be uploaded.
+ bool has_unsent_logs() const {
+ return initial_log_queue_.size() || ongoing_log_queue_.size();
+ }
+
+ // Populates staged_log_text() with the next stored log to send.
+ // Should only be called if has_unsent_logs() is true.
+ void StageNextLogForUpload();
+
+ // Returns true if there is a log that needs to be, or is being, uploaded.
+ bool has_staged_log() const {
+ return initial_log_queue_.has_staged_log() ||
+ ongoing_log_queue_.has_staged_log();
+ }
+
+ // The text of the staged log, as a serialized protobuf.
+ // Will trigger a DCHECK if there is no staged log.
+ const std::string& staged_log() const {
+ return initial_log_queue_.has_staged_log() ?
+ initial_log_queue_.staged_log() : ongoing_log_queue_.staged_log();
+ }
+
+ // The SHA1 hash of the staged log.
+ // Will trigger a DCHECK if there is no staged log.
+ const std::string& staged_log_hash() const {
+ return initial_log_queue_.has_staged_log() ?
+ initial_log_queue_.staged_log_hash() :
+ ongoing_log_queue_.staged_log_hash();
+ }
+
+ // Discards the staged log.
+ void DiscardStagedLog();
+
+ // Closes and discards |current_log|.
+ void DiscardCurrentLog();
+
+ // Sets current_log to NULL, but saves the current log for future use with
+ // ResumePausedLog(). Only one log may be paused at a time.
+ // TODO(stuartmorgan): Pause/resume support is really a workaround for a
+ // design issue in initial log writing; that should be fixed, and pause/resume
+ // removed.
+ void PauseCurrentLog();
+
+ // Restores the previously paused log (if any) to current_log().
+ // This should only be called if there is not a current log.
+ void ResumePausedLog();
+
+ // Saves any unsent logs to persistent storage.
+ void PersistUnsentLogs();
+
+ // Loads any unsent logs from persistent storage.
+ void LoadPersistedUnsentLogs();
+
+ // Saves |log_data| as the given type. Public to allow to push log created by
+ // external components.
+ void StoreLog(const std::string& log_data, MetricsLog::LogType log_type);
+
+ private:
+ // Tracks whether unsent logs (if any) have been loaded from the serializer.
+ bool unsent_logs_loaded_;
+
+ // The log that we are still appending to.
+ scoped_ptr<MetricsLog> current_log_;
+
+ // A paused, previously-current log.
+ scoped_ptr<MetricsLog> paused_log_;
+
+ // Logs that have not yet been sent.
+ PersistedLogs initial_log_queue_;
+ PersistedLogs ongoing_log_queue_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsLogManager);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_LOG_MANAGER_H_
diff --git a/chromium/components/metrics/metrics_log_manager_unittest.cc b/chromium/components/metrics/metrics_log_manager_unittest.cc
new file mode 100644
index 00000000000..c35d10043b7
--- /dev/null
+++ b/chromium/components/metrics/metrics_log_manager_unittest.cc
@@ -0,0 +1,306 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_log_manager.h"
+
+#include <stddef.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/test_metrics_service_client.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+// Dummy serializer that just stores logs in memory.
+class TestLogPrefService : public TestingPrefServiceSimple {
+ public:
+ TestLogPrefService() {
+ registry()->RegisterListPref(prefs::kMetricsInitialLogs);
+ registry()->RegisterListPref(prefs::kMetricsOngoingLogs);
+ }
+
+ // Returns the number of logs of the given type.
+ size_t TypeCount(MetricsLog::LogType log_type) {
+ int list_length = 0;
+ if (log_type == MetricsLog::INITIAL_STABILITY_LOG)
+ list_length = GetList(prefs::kMetricsInitialLogs)->GetSize();
+ else
+ list_length = GetList(prefs::kMetricsOngoingLogs)->GetSize();
+ return list_length / 2;
+ }
+};
+
+} // namespace
+
+TEST(MetricsLogManagerTest, StandardFlow) {
+ TestMetricsServiceClient client;
+ TestLogPrefService pref_service;
+ MetricsLogManager log_manager(&pref_service, 0);
+
+ // Make sure a new manager has a clean slate.
+ EXPECT_EQ(NULL, log_manager.current_log());
+ EXPECT_FALSE(log_manager.has_staged_log());
+ EXPECT_FALSE(log_manager.has_unsent_logs());
+
+ // Check that the normal flow works.
+ MetricsLog* initial_log = new MetricsLog(
+ "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service);
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(initial_log));
+ EXPECT_EQ(initial_log, log_manager.current_log());
+ EXPECT_FALSE(log_manager.has_staged_log());
+
+ log_manager.FinishCurrentLog();
+ EXPECT_EQ(NULL, log_manager.current_log());
+ EXPECT_TRUE(log_manager.has_unsent_logs());
+ EXPECT_FALSE(log_manager.has_staged_log());
+
+ MetricsLog* second_log =
+ new MetricsLog("id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service);
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(second_log));
+ EXPECT_EQ(second_log, log_manager.current_log());
+
+ log_manager.StageNextLogForUpload();
+ EXPECT_TRUE(log_manager.has_staged_log());
+ EXPECT_FALSE(log_manager.staged_log().empty());
+
+ log_manager.DiscardStagedLog();
+ EXPECT_EQ(second_log, log_manager.current_log());
+ EXPECT_FALSE(log_manager.has_staged_log());
+ EXPECT_FALSE(log_manager.has_unsent_logs());
+
+ EXPECT_FALSE(log_manager.has_unsent_logs());
+}
+
+TEST(MetricsLogManagerTest, AbandonedLog) {
+ TestMetricsServiceClient client;
+ TestLogPrefService pref_service;
+ MetricsLogManager log_manager(&pref_service, 0);
+
+ MetricsLog* dummy_log = new MetricsLog(
+ "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service);
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(dummy_log));
+ EXPECT_EQ(dummy_log, log_manager.current_log());
+
+ log_manager.DiscardCurrentLog();
+ EXPECT_EQ(NULL, log_manager.current_log());
+ EXPECT_FALSE(log_manager.has_staged_log());
+}
+
+TEST(MetricsLogManagerTest, InterjectedLog) {
+ TestMetricsServiceClient client;
+ TestLogPrefService pref_service;
+ MetricsLogManager log_manager(&pref_service, 0);
+
+ MetricsLog* ongoing_log =
+ new MetricsLog("id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service);
+ MetricsLog* temp_log = new MetricsLog(
+ "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service);
+
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(ongoing_log));
+ EXPECT_EQ(ongoing_log, log_manager.current_log());
+
+ log_manager.PauseCurrentLog();
+ EXPECT_EQ(NULL, log_manager.current_log());
+
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(temp_log));
+ EXPECT_EQ(temp_log, log_manager.current_log());
+ log_manager.FinishCurrentLog();
+ EXPECT_EQ(NULL, log_manager.current_log());
+
+ log_manager.ResumePausedLog();
+ EXPECT_EQ(ongoing_log, log_manager.current_log());
+
+ EXPECT_FALSE(log_manager.has_staged_log());
+ log_manager.StageNextLogForUpload();
+ log_manager.DiscardStagedLog();
+ EXPECT_FALSE(log_manager.has_unsent_logs());
+}
+
+TEST(MetricsLogManagerTest, InterjectedLogPreservesType) {
+ TestMetricsServiceClient client;
+ TestLogPrefService pref_service;
+ MetricsLogManager log_manager(&pref_service, 0);
+ log_manager.LoadPersistedUnsentLogs();
+
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service)));
+ log_manager.PauseCurrentLog();
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service)));
+ log_manager.FinishCurrentLog();
+ log_manager.ResumePausedLog();
+ log_manager.StageNextLogForUpload();
+ log_manager.DiscardStagedLog();
+
+ // Verify that the remaining log (which is the original ongoing log) still
+ // has the right type.
+ log_manager.FinishCurrentLog();
+ log_manager.PersistUnsentLogs();
+ EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+ EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+}
+
+TEST(MetricsLogManagerTest, StoreAndLoad) {
+ TestMetricsServiceClient client;
+ TestLogPrefService pref_service;
+ // Set up some in-progress logging in a scoped log manager simulating the
+ // leadup to quitting, then persist as would be done on quit.
+ {
+ MetricsLogManager log_manager(&pref_service, 0);
+ log_manager.LoadPersistedUnsentLogs();
+
+ // Simulate a log having already been unsent from a previous session.
+ {
+ std::string log("proto");
+ PersistedLogs ongoing_logs(&pref_service, prefs::kMetricsOngoingLogs, 1,
+ 1, 0);
+ ongoing_logs.StoreLog(log);
+ ongoing_logs.SerializeLogs();
+ }
+ EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+ EXPECT_FALSE(log_manager.has_unsent_logs());
+ log_manager.LoadPersistedUnsentLogs();
+ EXPECT_TRUE(log_manager.has_unsent_logs());
+
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service)));
+ log_manager.FinishCurrentLog();
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service)));
+ log_manager.StageNextLogForUpload();
+ log_manager.FinishCurrentLog();
+
+ // Nothing should be written out until PersistUnsentLogs is called.
+ EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+ EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+ log_manager.PersistUnsentLogs();
+ EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+ EXPECT_EQ(2U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+ }
+
+ // Now simulate the relaunch, ensure that the log manager restores
+ // everything correctly, and verify that once the are handled they are not
+ // re-persisted.
+ {
+ MetricsLogManager log_manager(&pref_service, 0);
+ log_manager.LoadPersistedUnsentLogs();
+ EXPECT_TRUE(log_manager.has_unsent_logs());
+
+ log_manager.StageNextLogForUpload();
+ log_manager.DiscardStagedLog();
+ // The initial log should be sent first; update the persisted storage to
+ // verify.
+ log_manager.PersistUnsentLogs();
+ EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+ EXPECT_EQ(2U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+
+ // Handle the first ongoing log.
+ log_manager.StageNextLogForUpload();
+ log_manager.DiscardStagedLog();
+ EXPECT_TRUE(log_manager.has_unsent_logs());
+
+ // Handle the last log.
+ log_manager.StageNextLogForUpload();
+ log_manager.DiscardStagedLog();
+ EXPECT_FALSE(log_manager.has_unsent_logs());
+
+ // Nothing should have changed "on disk" since PersistUnsentLogs hasn't been
+ // called again.
+ EXPECT_EQ(2U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+ // Persist, and make sure nothing is left.
+ log_manager.PersistUnsentLogs();
+ EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+ EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+ }
+}
+
+TEST(MetricsLogManagerTest, StoreStagedLogTypes) {
+ TestMetricsServiceClient client;
+
+ // Ensure that types are preserved when storing staged logs.
+ {
+ TestLogPrefService pref_service;
+ MetricsLogManager log_manager(&pref_service, 0);
+ log_manager.LoadPersistedUnsentLogs();
+
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service)));
+ log_manager.FinishCurrentLog();
+ log_manager.StageNextLogForUpload();
+ log_manager.PersistUnsentLogs();
+
+ EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+ EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+ }
+
+ {
+ TestLogPrefService pref_service;
+ MetricsLogManager log_manager(&pref_service, 0);
+ log_manager.LoadPersistedUnsentLogs();
+
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service)));
+ log_manager.FinishCurrentLog();
+ log_manager.StageNextLogForUpload();
+ log_manager.PersistUnsentLogs();
+
+ EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+ EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+ }
+}
+
+TEST(MetricsLogManagerTest, LargeLogDiscarding) {
+ TestMetricsServiceClient client;
+ TestLogPrefService pref_service;
+ // Set the size threshold very low, to verify that it's honored.
+ MetricsLogManager log_manager(&pref_service, 1);
+ log_manager.LoadPersistedUnsentLogs();
+
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service)));
+ log_manager.FinishCurrentLog();
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service)));
+ log_manager.FinishCurrentLog();
+
+ // Only the ongoing log should be written out, due to the threshold.
+ log_manager.PersistUnsentLogs();
+ EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+ EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+}
+
+TEST(MetricsLogManagerTest, DiscardOrder) {
+ // Ensure that the correct log is discarded if new logs are pushed while
+ // a log is staged.
+ TestMetricsServiceClient client;
+ {
+ TestLogPrefService pref_service;
+ MetricsLogManager log_manager(&pref_service, 0);
+ log_manager.LoadPersistedUnsentLogs();
+
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service)));
+ log_manager.FinishCurrentLog();
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service)));
+ log_manager.StageNextLogForUpload();
+ log_manager.FinishCurrentLog();
+ log_manager.DiscardStagedLog();
+
+ log_manager.PersistUnsentLogs();
+ EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+ EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_log_unittest.cc b/chromium/components/metrics/metrics_log_unittest.cc
new file mode 100644
index 00000000000..8eea886855a
--- /dev/null
+++ b/chromium/components/metrics/metrics_log_unittest.cc
@@ -0,0 +1,465 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_log.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+
+#include "base/base64.h"
+#include "base/macros.h"
+#include "base/memory/scoped_vector.h"
+#include "base/metrics/bucket_ranges.h"
+#include "base/metrics/sample_vector.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/time.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_state_manager.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+#include "components/metrics/test_metrics_provider.h"
+#include "components/metrics/test_metrics_service_client.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/variations/active_field_trials.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+const char kClientId[] = "bogus client ID";
+const int64_t kInstallDate = 1373051956;
+const int64_t kInstallDateExpected = 1373050800; // Computed from kInstallDate.
+const int64_t kEnabledDate = 1373001211;
+const int64_t kEnabledDateExpected = 1373000400; // Computed from kEnabledDate.
+const int kSessionId = 127;
+const variations::ActiveGroupId kFieldTrialIds[] = {
+ {37, 43},
+ {13, 47},
+ {23, 17}
+};
+const variations::ActiveGroupId kSyntheticTrials[] = {
+ {55, 15},
+ {66, 16}
+};
+
+class TestMetricsLog : public MetricsLog {
+ public:
+ TestMetricsLog(const std::string& client_id,
+ int session_id,
+ LogType log_type,
+ MetricsServiceClient* client,
+ TestingPrefServiceSimple* prefs)
+ : MetricsLog(client_id, session_id, log_type, client, prefs),
+ prefs_(prefs) {
+ InitPrefs();
+ }
+
+ ~TestMetricsLog() override {}
+
+ const ChromeUserMetricsExtension& uma_proto() const {
+ return *MetricsLog::uma_proto();
+ }
+
+ const SystemProfileProto& system_profile() const {
+ return uma_proto().system_profile();
+ }
+
+ private:
+ void InitPrefs() {
+ prefs_->SetString(prefs::kMetricsReportingEnabledTimestamp,
+ base::Int64ToString(kEnabledDate));
+ }
+
+ void GetFieldTrialIds(
+ std::vector<variations::ActiveGroupId>* field_trial_ids) const override {
+ ASSERT_TRUE(field_trial_ids->empty());
+
+ for (size_t i = 0; i < arraysize(kFieldTrialIds); ++i) {
+ field_trial_ids->push_back(kFieldTrialIds[i]);
+ }
+ }
+
+ // Weak pointer to the PrefsService used by this log.
+ TestingPrefServiceSimple* prefs_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestMetricsLog);
+};
+
+} // namespace
+
+class MetricsLogTest : public testing::Test {
+ public:
+ MetricsLogTest() {
+ MetricsLog::RegisterPrefs(prefs_.registry());
+ MetricsStateManager::RegisterPrefs(prefs_.registry());
+ }
+
+ ~MetricsLogTest() override {}
+
+ protected:
+ // Check that the values in |system_values| correspond to the test data
+ // defined at the top of this file.
+ void CheckSystemProfile(const SystemProfileProto& system_profile) {
+ EXPECT_EQ(kInstallDateExpected, system_profile.install_date());
+ EXPECT_EQ(kEnabledDateExpected, system_profile.uma_enabled_date());
+
+ ASSERT_EQ(arraysize(kFieldTrialIds) + arraysize(kSyntheticTrials),
+ static_cast<size_t>(system_profile.field_trial_size()));
+ for (size_t i = 0; i < arraysize(kFieldTrialIds); ++i) {
+ const SystemProfileProto::FieldTrial& field_trial =
+ system_profile.field_trial(i);
+ EXPECT_EQ(kFieldTrialIds[i].name, field_trial.name_id());
+ EXPECT_EQ(kFieldTrialIds[i].group, field_trial.group_id());
+ }
+ // Verify the right data is present for the synthetic trials.
+ for (size_t i = 0; i < arraysize(kSyntheticTrials); ++i) {
+ const SystemProfileProto::FieldTrial& field_trial =
+ system_profile.field_trial(i + arraysize(kFieldTrialIds));
+ EXPECT_EQ(kSyntheticTrials[i].name, field_trial.name_id());
+ EXPECT_EQ(kSyntheticTrials[i].group, field_trial.group_id());
+ }
+
+ EXPECT_EQ(TestMetricsServiceClient::kBrandForTesting,
+ system_profile.brand_code());
+
+ const SystemProfileProto::Hardware& hardware =
+ system_profile.hardware();
+
+ EXPECT_TRUE(hardware.has_cpu());
+ EXPECT_TRUE(hardware.cpu().has_vendor_name());
+ EXPECT_TRUE(hardware.cpu().has_signature());
+ EXPECT_TRUE(hardware.cpu().has_num_cores());
+
+ // TODO(isherman): Verify other data written into the protobuf as a result
+ // of this call.
+ }
+
+ protected:
+ TestingPrefServiceSimple prefs_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MetricsLogTest);
+};
+
+TEST_F(MetricsLogTest, LogType) {
+ TestMetricsServiceClient client;
+ TestingPrefServiceSimple prefs;
+
+ MetricsLog log1("id", 0, MetricsLog::ONGOING_LOG, &client, &prefs);
+ EXPECT_EQ(MetricsLog::ONGOING_LOG, log1.log_type());
+
+ MetricsLog log2("id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &prefs);
+ EXPECT_EQ(MetricsLog::INITIAL_STABILITY_LOG, log2.log_type());
+}
+
+TEST_F(MetricsLogTest, EmptyRecord) {
+ TestMetricsServiceClient client;
+ client.set_version_string("bogus version");
+ TestingPrefServiceSimple prefs;
+ MetricsLog log("totally bogus client ID", 137, MetricsLog::ONGOING_LOG,
+ &client, &prefs);
+ log.CloseLog();
+
+ std::string encoded;
+ log.GetEncodedLog(&encoded);
+
+ // A couple of fields are hard to mock, so these will be copied over directly
+ // for the expected output.
+ ChromeUserMetricsExtension parsed;
+ ASSERT_TRUE(parsed.ParseFromString(encoded));
+
+ ChromeUserMetricsExtension expected;
+ expected.set_client_id(5217101509553811875); // Hashed bogus client ID
+ expected.set_session_id(137);
+ expected.mutable_system_profile()->set_build_timestamp(
+ parsed.system_profile().build_timestamp());
+ expected.mutable_system_profile()->set_app_version("bogus version");
+ expected.mutable_system_profile()->set_channel(client.GetChannel());
+
+ EXPECT_EQ(expected.SerializeAsString(), encoded);
+}
+
+TEST_F(MetricsLogTest, HistogramBucketFields) {
+ // Create buckets: 1-5, 5-7, 7-8, 8-9, 9-10, 10-11, 11-12.
+ base::BucketRanges ranges(8);
+ ranges.set_range(0, 1);
+ ranges.set_range(1, 5);
+ ranges.set_range(2, 7);
+ ranges.set_range(3, 8);
+ ranges.set_range(4, 9);
+ ranges.set_range(5, 10);
+ ranges.set_range(6, 11);
+ ranges.set_range(7, 12);
+
+ base::SampleVector samples(1, &ranges);
+ samples.Accumulate(3, 1); // Bucket 1-5.
+ samples.Accumulate(6, 1); // Bucket 5-7.
+ samples.Accumulate(8, 1); // Bucket 8-9. (7-8 skipped)
+ samples.Accumulate(10, 1); // Bucket 10-11. (9-10 skipped)
+ samples.Accumulate(11, 1); // Bucket 11-12.
+
+ TestMetricsServiceClient client;
+ TestingPrefServiceSimple prefs;
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ log.RecordHistogramDelta("Test", samples);
+
+ const ChromeUserMetricsExtension& uma_proto = log.uma_proto();
+ const HistogramEventProto& histogram_proto =
+ uma_proto.histogram_event(uma_proto.histogram_event_size() - 1);
+
+ // Buckets with samples: 1-5, 5-7, 8-9, 10-11, 11-12.
+ // Should become: 1-/, 5-7, /-9, 10-/, /-12.
+ ASSERT_EQ(5, histogram_proto.bucket_size());
+
+ // 1-5 becomes 1-/ (max is same as next min).
+ EXPECT_TRUE(histogram_proto.bucket(0).has_min());
+ EXPECT_FALSE(histogram_proto.bucket(0).has_max());
+ EXPECT_EQ(1, histogram_proto.bucket(0).min());
+
+ // 5-7 stays 5-7 (no optimization possible).
+ EXPECT_TRUE(histogram_proto.bucket(1).has_min());
+ EXPECT_TRUE(histogram_proto.bucket(1).has_max());
+ EXPECT_EQ(5, histogram_proto.bucket(1).min());
+ EXPECT_EQ(7, histogram_proto.bucket(1).max());
+
+ // 8-9 becomes /-9 (min is same as max - 1).
+ EXPECT_FALSE(histogram_proto.bucket(2).has_min());
+ EXPECT_TRUE(histogram_proto.bucket(2).has_max());
+ EXPECT_EQ(9, histogram_proto.bucket(2).max());
+
+ // 10-11 becomes 10-/ (both optimizations apply, omit max is prioritized).
+ EXPECT_TRUE(histogram_proto.bucket(3).has_min());
+ EXPECT_FALSE(histogram_proto.bucket(3).has_max());
+ EXPECT_EQ(10, histogram_proto.bucket(3).min());
+
+ // 11-12 becomes /-12 (last record must keep max, min is same as max - 1).
+ EXPECT_FALSE(histogram_proto.bucket(4).has_min());
+ EXPECT_TRUE(histogram_proto.bucket(4).has_max());
+ EXPECT_EQ(12, histogram_proto.bucket(4).max());
+}
+
+TEST_F(MetricsLogTest, RecordEnvironment) {
+ TestMetricsServiceClient client;
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+
+ std::vector<variations::ActiveGroupId> synthetic_trials;
+ // Add two synthetic trials.
+ synthetic_trials.push_back(kSyntheticTrials[0]);
+ synthetic_trials.push_back(kSyntheticTrials[1]);
+
+ log.RecordEnvironment(std::vector<MetricsProvider*>(), synthetic_trials,
+ kInstallDate, kEnabledDate);
+ // Check that the system profile on the log has the correct values set.
+ CheckSystemProfile(log.system_profile());
+
+ // Check that the system profile has also been written to prefs.
+ const std::string base64_system_profile =
+ prefs_.GetString(prefs::kStabilitySavedSystemProfile);
+ EXPECT_FALSE(base64_system_profile.empty());
+ std::string serialied_system_profile;
+ EXPECT_TRUE(base::Base64Decode(base64_system_profile,
+ &serialied_system_profile));
+ SystemProfileProto decoded_system_profile;
+ EXPECT_TRUE(decoded_system_profile.ParseFromString(serialied_system_profile));
+ CheckSystemProfile(decoded_system_profile);
+}
+
+TEST_F(MetricsLogTest, LoadSavedEnvironmentFromPrefs) {
+ const char* kSystemProfilePref = prefs::kStabilitySavedSystemProfile;
+ const char* kSystemProfileHashPref =
+ prefs::kStabilitySavedSystemProfileHash;
+
+ TestMetricsServiceClient client;
+
+ // The pref value is empty, so loading it from prefs should fail.
+ {
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ EXPECT_FALSE(log.LoadSavedEnvironmentFromPrefs());
+ }
+
+ // Do a RecordEnvironment() call and check whether the pref is recorded.
+ {
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ log.RecordEnvironment(std::vector<MetricsProvider*>(),
+ std::vector<variations::ActiveGroupId>(),
+ kInstallDate, kEnabledDate);
+ EXPECT_FALSE(prefs_.GetString(kSystemProfilePref).empty());
+ EXPECT_FALSE(prefs_.GetString(kSystemProfileHashPref).empty());
+ }
+
+ {
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ EXPECT_TRUE(log.LoadSavedEnvironmentFromPrefs());
+ // Check some values in the system profile.
+ EXPECT_EQ(kInstallDateExpected, log.system_profile().install_date());
+ EXPECT_EQ(kEnabledDateExpected, log.system_profile().uma_enabled_date());
+ // Ensure that the call cleared the prefs.
+ EXPECT_TRUE(prefs_.GetString(kSystemProfilePref).empty());
+ EXPECT_TRUE(prefs_.GetString(kSystemProfileHashPref).empty());
+ }
+
+ // Ensure that a non-matching hash results in the pref being invalid.
+ {
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ // Call RecordEnvironment() to record the pref again.
+ log.RecordEnvironment(std::vector<MetricsProvider*>(),
+ std::vector<variations::ActiveGroupId>(),
+ kInstallDate, kEnabledDate);
+ }
+
+ {
+ // Set the hash to a bad value.
+ prefs_.SetString(kSystemProfileHashPref, "deadbeef");
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ EXPECT_FALSE(log.LoadSavedEnvironmentFromPrefs());
+ // Ensure that the prefs are cleared, even if the call failed.
+ EXPECT_TRUE(prefs_.GetString(kSystemProfilePref).empty());
+ EXPECT_TRUE(prefs_.GetString(kSystemProfileHashPref).empty());
+ }
+}
+
+TEST_F(MetricsLogTest, RecordEnvironmentEnableDefault) {
+ TestMetricsServiceClient client;
+ TestMetricsLog log_unknown(kClientId, kSessionId, MetricsLog::ONGOING_LOG,
+ &client, &prefs_);
+
+ std::vector<variations::ActiveGroupId> synthetic_trials;
+
+ log_unknown.RecordEnvironment(std::vector<MetricsProvider*>(),
+ synthetic_trials, kInstallDate, kEnabledDate);
+ EXPECT_FALSE(log_unknown.system_profile().has_uma_default_state());
+
+ client.set_enable_default(MetricsServiceClient::OPT_IN);
+ TestMetricsLog log_opt_in(kClientId, kSessionId, MetricsLog::ONGOING_LOG,
+ &client, &prefs_);
+ log_opt_in.RecordEnvironment(std::vector<MetricsProvider*>(),
+ synthetic_trials, kInstallDate, kEnabledDate);
+ EXPECT_TRUE(log_opt_in.system_profile().has_uma_default_state());
+ EXPECT_EQ(SystemProfileProto_UmaDefaultState_OPT_IN,
+ log_opt_in.system_profile().uma_default_state());
+
+ client.set_enable_default(MetricsServiceClient::OPT_OUT);
+ TestMetricsLog log_opt_out(kClientId, kSessionId, MetricsLog::ONGOING_LOG,
+ &client, &prefs_);
+ log_opt_out.RecordEnvironment(std::vector<MetricsProvider*>(),
+ synthetic_trials, kInstallDate, kEnabledDate);
+ EXPECT_TRUE(log_opt_out.system_profile().has_uma_default_state());
+ EXPECT_EQ(SystemProfileProto_UmaDefaultState_OPT_OUT,
+ log_opt_out.system_profile().uma_default_state());
+
+ client.set_reporting_is_managed(true);
+ TestMetricsLog log_managed(kClientId, kSessionId, MetricsLog::ONGOING_LOG,
+ &client, &prefs_);
+ log_managed.RecordEnvironment(std::vector<MetricsProvider*>(),
+ synthetic_trials, kInstallDate, kEnabledDate);
+ EXPECT_TRUE(log_managed.system_profile().has_uma_default_state());
+ EXPECT_EQ(SystemProfileProto_UmaDefaultState_POLICY_FORCED_ENABLED,
+ log_managed.system_profile().uma_default_state());
+}
+
+TEST_F(MetricsLogTest, InitialLogStabilityMetrics) {
+ TestMetricsServiceClient client;
+ TestMetricsLog log(kClientId,
+ kSessionId,
+ MetricsLog::INITIAL_STABILITY_LOG,
+ &client,
+ &prefs_);
+ TestMetricsProvider* test_provider = new TestMetricsProvider();
+ ScopedVector<MetricsProvider> metrics_providers;
+ metrics_providers.push_back(test_provider);
+ log.RecordEnvironment(metrics_providers.get(),
+ std::vector<variations::ActiveGroupId>(), kInstallDate,
+ kEnabledDate);
+ log.RecordStabilityMetrics(metrics_providers.get(), base::TimeDelta(),
+ base::TimeDelta());
+ const SystemProfileProto_Stability& stability =
+ log.system_profile().stability();
+ // Required metrics:
+ EXPECT_TRUE(stability.has_launch_count());
+ EXPECT_TRUE(stability.has_crash_count());
+ // Initial log metrics:
+ EXPECT_TRUE(stability.has_incomplete_shutdown_count());
+ EXPECT_TRUE(stability.has_breakpad_registration_success_count());
+ EXPECT_TRUE(stability.has_breakpad_registration_failure_count());
+ EXPECT_TRUE(stability.has_debugger_present_count());
+ EXPECT_TRUE(stability.has_debugger_not_present_count());
+
+ // The test provider should have been called upon to provide initial
+ // stability and regular stability metrics.
+ EXPECT_TRUE(test_provider->provide_initial_stability_metrics_called());
+ EXPECT_TRUE(test_provider->provide_stability_metrics_called());
+}
+
+TEST_F(MetricsLogTest, OngoingLogStabilityMetrics) {
+ TestMetricsServiceClient client;
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ TestMetricsProvider* test_provider = new TestMetricsProvider();
+ ScopedVector<MetricsProvider> metrics_providers;
+ metrics_providers.push_back(test_provider);
+ log.RecordEnvironment(metrics_providers.get(),
+ std::vector<variations::ActiveGroupId>(), kInstallDate,
+ kEnabledDate);
+ log.RecordStabilityMetrics(metrics_providers.get(), base::TimeDelta(),
+ base::TimeDelta());
+ const SystemProfileProto_Stability& stability =
+ log.system_profile().stability();
+ // Required metrics:
+ EXPECT_TRUE(stability.has_launch_count());
+ EXPECT_TRUE(stability.has_crash_count());
+ // Initial log metrics:
+ EXPECT_FALSE(stability.has_incomplete_shutdown_count());
+ EXPECT_FALSE(stability.has_breakpad_registration_success_count());
+ EXPECT_FALSE(stability.has_breakpad_registration_failure_count());
+ EXPECT_FALSE(stability.has_debugger_present_count());
+ EXPECT_FALSE(stability.has_debugger_not_present_count());
+
+ // The test provider should have been called upon to provide regular but not
+ // initial stability metrics.
+ EXPECT_FALSE(test_provider->provide_initial_stability_metrics_called());
+ EXPECT_TRUE(test_provider->provide_stability_metrics_called());
+}
+
+TEST_F(MetricsLogTest, ChromeChannelWrittenToProtobuf) {
+ TestMetricsServiceClient client;
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ EXPECT_TRUE(log.uma_proto().system_profile().has_channel());
+}
+
+TEST_F(MetricsLogTest, ProductNotSetIfDefault) {
+ TestMetricsServiceClient client;
+ EXPECT_EQ(ChromeUserMetricsExtension::CHROME, client.GetProduct());
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ // Check that the product isn't set, since it's default and also verify the
+ // default value is indeed equal to Chrome.
+ EXPECT_FALSE(log.uma_proto().has_product());
+ EXPECT_EQ(ChromeUserMetricsExtension::CHROME, log.uma_proto().product());
+}
+
+TEST_F(MetricsLogTest, ProductSetIfNotDefault) {
+ const int32_t kTestProduct = 100;
+ EXPECT_NE(ChromeUserMetricsExtension::CHROME, kTestProduct);
+
+ TestMetricsServiceClient client;
+ client.set_product(kTestProduct);
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ // Check that the product is set to |kTestProduct|.
+ EXPECT_TRUE(log.uma_proto().has_product());
+ EXPECT_EQ(kTestProduct, log.uma_proto().product());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_log_uploader.cc b/chromium/components/metrics/metrics_log_uploader.cc
new file mode 100644
index 00000000000..41b83ed9249
--- /dev/null
+++ b/chromium/components/metrics/metrics_log_uploader.cc
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_log_uploader.h"
+
+namespace metrics {
+
+MetricsLogUploader::MetricsLogUploader(
+ const std::string& server_url,
+ const std::string& mime_type,
+ const base::Callback<void(int)>& on_upload_complete)
+ : server_url_(server_url),
+ mime_type_(mime_type),
+ on_upload_complete_(on_upload_complete) {
+}
+
+MetricsLogUploader::~MetricsLogUploader() {
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_log_uploader.h b/chromium/components/metrics/metrics_log_uploader.h
new file mode 100644
index 00000000000..2ef0cf2577e
--- /dev/null
+++ b/chromium/components/metrics/metrics_log_uploader.h
@@ -0,0 +1,44 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_LOG_UPLOADER_H_
+#define COMPONENTS_METRICS_METRICS_LOG_UPLOADER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+
+namespace metrics {
+
+// MetricsLogUploader is an abstract base class for uploading UMA logs on behalf
+// of MetricsService.
+class MetricsLogUploader {
+ public:
+ // Constructs the uploader that will upload logs to the specified |server_url|
+ // with the given |mime_type|. The |on_upload_complete| callback will be
+ // called with the HTTP response code of the upload or with -1 on an error.
+ MetricsLogUploader(const std::string& server_url,
+ const std::string& mime_type,
+ const base::Callback<void(int)>& on_upload_complete);
+ virtual ~MetricsLogUploader();
+
+ // Uploads a log with the specified |compressed_log_data| and |log_hash|.
+ // |log_hash| is expected to be the hex-encoded SHA1 hash of the log data
+ // before compression.
+ virtual void UploadLog(const std::string& compressed_log_data,
+ const std::string& log_hash) = 0;
+
+ protected:
+ const std::string server_url_;
+ const std::string mime_type_;
+ const base::Callback<void(int)> on_upload_complete_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MetricsLogUploader);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_LOG_UPLOADER_H_
diff --git a/chromium/components/metrics/metrics_pref_names.cc b/chromium/components/metrics/metrics_pref_names.cc
new file mode 100644
index 00000000000..3872061fb3e
--- /dev/null
+++ b/chromium/components/metrics/metrics_pref_names.cc
@@ -0,0 +1,181 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_pref_names.h"
+
+namespace metrics {
+namespace prefs {
+
+// Set once, to the current epoch time, on the first run of chrome on this
+// machine. Attached to metrics reports forever thereafter.
+const char kInstallDate[] = "uninstall_metrics.installation_date2";
+
+// The metrics client GUID.
+// Note: The name client_id2 is a result of creating
+// new prefs to do a one-time reset of the previous values.
+const char kMetricsClientID[] = "user_experience_metrics.client_id2";
+
+// Array of strings that are each UMA logs that were supposed to be sent in the
+// first minute of a browser session. These logs include things like crash count
+// info, etc.
+const char kMetricsInitialLogs[] =
+ "user_experience_metrics.initial_logs_list";
+
+// The metrics entropy source.
+// Note: The name low_entropy_source2 is a result of creating
+// new prefs to do a one-time reset of the previous values.
+const char kMetricsLowEntropySource[] =
+ "user_experience_metrics.low_entropy_source2";
+
+// A machine ID used to detect when underlying hardware changes. It is only
+// stored locally and never transmitted in metrics reports.
+const char kMetricsMachineId[] = "user_experience_metrics.machine_id";
+
+// Array of strings that are each UMA logs that were not sent because the
+// browser terminated before these accumulated metrics could be sent. These
+// logs typically include histograms and memory reports, as well as ongoing
+// user activities.
+const char kMetricsOngoingLogs[] =
+ "user_experience_metrics.ongoing_logs_list";
+
+// Boolean that indicates a cloned install has been detected and the metrics
+// client id and low entropy source should be reset.
+const char kMetricsResetIds[] = "user_experience_metrics.reset_metrics_ids";
+
+// Boolean that specifies whether or not crash reporting and metrics reporting
+// are sent over the network for analysis.
+const char kMetricsReportingEnabled[] =
+ "user_experience_metrics.reporting_enabled";
+
+// Date/time when the user opted in to UMA and generated the client id for the
+// very first time (local machine time, stored as a 64-bit time_t value).
+const char kMetricsReportingEnabledTimestamp[] =
+ "user_experience_metrics.client_id_timestamp";
+
+// The metrics client session ID.
+const char kMetricsSessionID[] = "user_experience_metrics.session_id";
+
+// The prefix of the last-seen timestamp for persistent histogram files.
+// Values are named for the files themselves.
+const char kMetricsLastSeenPrefix[] =
+ "user_experience_metrics.last_seen.";
+
+// Number of times the browser has been able to register crash reporting.
+const char kStabilityBreakpadRegistrationSuccess[] =
+ "user_experience_metrics.stability.breakpad_registration_ok";
+
+// Number of times the browser has failed to register crash reporting.
+const char kStabilityBreakpadRegistrationFail[] =
+ "user_experience_metrics.stability.breakpad_registration_fail";
+
+// Total number of child process crashes (other than renderer / extension
+// renderer ones, and plugin children, which are counted separately) since the
+// last report.
+const char kStabilityChildProcessCrashCount[] =
+ "user_experience_metrics.stability.child_process_crash_count";
+
+// Number of times the application exited uncleanly since the last report.
+const char kStabilityCrashCount[] =
+ "user_experience_metrics.stability.crash_count";
+
+// Number of times the browser has been run under a debugger.
+const char kStabilityDebuggerPresent[] =
+ "user_experience_metrics.stability.debugger_present";
+
+// Number of times the browser has not been run under a debugger.
+const char kStabilityDebuggerNotPresent[] =
+ "user_experience_metrics.stability.debugger_not_present";
+
+// An enum value to indicate the execution phase the browser was in.
+const char kStabilityExecutionPhase[] =
+ "user_experience_metrics.stability.execution_phase";
+
+// True if the previous run of the program exited cleanly.
+const char kStabilityExitedCleanly[] =
+ "user_experience_metrics.stability.exited_cleanly";
+
+// Number of times an extension renderer process crashed since the last report.
+const char kStabilityExtensionRendererCrashCount[] =
+ "user_experience_metrics.stability.extension_renderer_crash_count";
+
+// Number of times an extension renderer process failed to launch since the last
+// report.
+const char kStabilityExtensionRendererFailedLaunchCount[] =
+ "user_experience_metrics.stability.extension_renderer_failed_launch_count";
+
+// Number of times the session end did not complete.
+const char kStabilityIncompleteSessionEndCount[] =
+ "user_experience_metrics.stability.incomplete_session_end_count";
+
+// Time when the app was last known to be running, in seconds since
+// the epoch.
+const char kStabilityLastTimestampSec[] =
+ "user_experience_metrics.stability.last_timestamp_sec";
+
+// Number of times the application was launched since last report.
+const char kStabilityLaunchCount[] =
+ "user_experience_metrics.stability.launch_count";
+
+// Time when the app was last launched, in seconds since the epoch.
+const char kStabilityLaunchTimeSec[] =
+ "user_experience_metrics.stability.launch_time_sec";
+
+// Number of times a page load event occurred since the last report.
+const char kStabilityPageLoadCount[] =
+ "user_experience_metrics.stability.page_load_count";
+
+// Number of times a renderer process crashed since the last report.
+const char kStabilityRendererCrashCount[] =
+ "user_experience_metrics.stability.renderer_crash_count";
+
+// Number of times a renderer process failed to launch since the last report.
+const char kStabilityRendererFailedLaunchCount[] =
+ "user_experience_metrics.stability.renderer_failed_launch_count";
+
+// Number of times the renderer has become non-responsive since the last
+// report.
+const char kStabilityRendererHangCount[] =
+ "user_experience_metrics.stability.renderer_hang_count";
+
+// Base64 encoded serialized UMA system profile proto from the previous session.
+const char kStabilitySavedSystemProfile[] =
+ "user_experience_metrics.stability.saved_system_profile";
+
+// SHA-1 hash of the serialized UMA system profile proto (hex encoded).
+const char kStabilitySavedSystemProfileHash[] =
+ "user_experience_metrics.stability.saved_system_profile_hash";
+
+// False if we received a session end and either we crashed during processing
+// the session end or ran out of time and windows terminated us.
+const char kStabilitySessionEndCompleted[] =
+ "user_experience_metrics.stability.session_end_completed";
+
+// Build time, in seconds since an epoch, which is used to assure that stability
+// metrics reported reflect stability of the same build.
+const char kStabilityStatsBuildTime[] =
+ "user_experience_metrics.stability.stats_buildtime";
+
+// Version string of previous run, which is used to assure that stability
+// metrics reported under current version reflect stability of the same version.
+const char kStabilityStatsVersion[] =
+ "user_experience_metrics.stability.stats_version";
+
+// The keys below are strictly increasing counters over the lifetime of
+// a chrome installation. They are (optionally) sent up to the uninstall
+// survey in the event of uninstallation.
+const char kUninstallLaunchCount[] = "uninstall_metrics.launch_count";
+const char kUninstallMetricsPageLoadCount[] =
+ "uninstall_metrics.page_load_count";
+const char kUninstallMetricsUptimeSec[] = "uninstall_metrics.uptime_sec";
+
+// Dictionary for measuring cellular data used by UMA service during last 7
+// days.
+const char kUmaCellDataUse[] = "user_experience_metrics.uma_cell_datause";
+
+// Dictionary for measuring cellular data used by user including chrome services
+// per day.
+const char kUserCellDataUse[] = "user_experience_metrics.user_call_datause";
+
+} // namespace prefs
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_pref_names.h b/chromium/components/metrics/metrics_pref_names.h
new file mode 100644
index 00000000000..52733611995
--- /dev/null
+++ b/chromium/components/metrics/metrics_pref_names.h
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_PREF_NAMES_H_
+#define COMPONENTS_METRICS_METRICS_PREF_NAMES_H_
+
+namespace metrics {
+namespace prefs {
+
+// Alphabetical list of preference names specific to the metrics
+// component. Document each in the .cc file.
+extern const char kInstallDate[];
+extern const char kMetricsClientID[];
+extern const char kMetricsInitialLogs[];
+extern const char kMetricsLowEntropySource[];
+extern const char kMetricsMachineId[];
+extern const char kMetricsOngoingLogs[];
+extern const char kMetricsResetIds[];
+
+// For finding out whether metrics and crash reporting is enabled use the
+// relevant embedder-specific subclass of MetricsServiceAccessor instead of
+// reading this pref directly; see the comments on metrics_service_accessor.h.
+// (NOTE: If within //chrome, use
+// ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled()).
+extern const char kMetricsReportingEnabled[];
+extern const char kMetricsReportingEnabledTimestamp[];
+extern const char kMetricsSessionID[];
+extern const char kMetricsLastSeenPrefix[];
+extern const char kStabilityBreakpadRegistrationSuccess[];
+extern const char kStabilityBreakpadRegistrationFail[];
+extern const char kStabilityChildProcessCrashCount[];
+extern const char kStabilityCrashCount[];
+extern const char kStabilityDebuggerPresent[];
+extern const char kStabilityDebuggerNotPresent[];
+extern const char kStabilityExecutionPhase[];
+extern const char kStabilityExtensionRendererCrashCount[];
+extern const char kStabilityExtensionRendererFailedLaunchCount[];
+extern const char kStabilityExitedCleanly[];
+extern const char kStabilityIncompleteSessionEndCount[];
+extern const char kStabilityLastTimestampSec[];
+extern const char kStabilityLaunchCount[];
+extern const char kStabilityLaunchTimeSec[];
+extern const char kStabilityPageLoadCount[];
+extern const char kStabilityRendererCrashCount[];
+extern const char kStabilityRendererFailedLaunchCount[];
+extern const char kStabilityRendererHangCount[];
+extern const char kStabilitySavedSystemProfile[];
+extern const char kStabilitySavedSystemProfileHash[];
+extern const char kStabilitySessionEndCompleted[];
+extern const char kStabilityStatsBuildTime[];
+extern const char kStabilityStatsVersion[];
+extern const char kUninstallLaunchCount[];
+extern const char kUninstallMetricsPageLoadCount[];
+extern const char kUninstallMetricsUptimeSec[];
+
+// For measuring data use for throttling UMA log uploads on cellular.
+extern const char kUmaCellDataUse[];
+extern const char kUserCellDataUse[];
+
+} // namespace prefs
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_PREF_NAMES_H_
diff --git a/chromium/components/metrics/metrics_provider.cc b/chromium/components/metrics/metrics_provider.cc
new file mode 100644
index 00000000000..5ba7382be5e
--- /dev/null
+++ b/chromium/components/metrics/metrics_provider.cc
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_provider.h"
+
+namespace metrics {
+
+MetricsProvider::MetricsProvider() {
+}
+
+MetricsProvider::~MetricsProvider() {
+}
+
+void MetricsProvider::Init() {
+}
+
+void MetricsProvider::OnDidCreateMetricsLog() {
+}
+
+void MetricsProvider::OnRecordingEnabled() {
+}
+
+void MetricsProvider::OnRecordingDisabled() {
+}
+
+void MetricsProvider::ProvideSystemProfileMetrics(
+ SystemProfileProto* system_profile_proto) {
+}
+
+bool MetricsProvider::HasInitialStabilityMetrics() {
+ return false;
+}
+
+void MetricsProvider::ProvideInitialStabilityMetrics(
+ SystemProfileProto* system_profile_proto) {
+}
+
+void MetricsProvider::ProvideStabilityMetrics(
+ SystemProfileProto* system_profile_proto) {
+}
+
+void MetricsProvider::ClearSavedStabilityMetrics() {
+}
+
+void MetricsProvider::ProvideGeneralMetrics(
+ ChromeUserMetricsExtension* uma_proto) {
+}
+
+void MetricsProvider::RecordHistogramSnapshots(
+ base::HistogramSnapshotManager* snapshot_manager) {
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_provider.h b/chromium/components/metrics/metrics_provider.h
new file mode 100644
index 00000000000..664e5dbc539
--- /dev/null
+++ b/chromium/components/metrics/metrics_provider.h
@@ -0,0 +1,86 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_METRICS_PROVIDER_H_
+
+#include "base/macros.h"
+
+namespace base {
+class HistogramSnapshotManager;
+} // namespace base
+
+namespace metrics {
+
+class ChromeUserMetricsExtension;
+class SystemProfileProto;
+class SystemProfileProto_Stability;
+
+// MetricsProvider is an interface allowing different parts of the UMA protos to
+// be filled out by different classes.
+class MetricsProvider {
+ public:
+ MetricsProvider();
+ virtual ~MetricsProvider();
+
+ // Called after initialiazation of MetricsService and field trials.
+ virtual void Init();
+
+ // Called when a new MetricsLog is created.
+ virtual void OnDidCreateMetricsLog();
+
+ // Called when metrics recording has been enabled.
+ virtual void OnRecordingEnabled();
+
+ // Called when metrics recording has been disabled.
+ virtual void OnRecordingDisabled();
+
+ // Provides additional metrics into the system profile.
+ virtual void ProvideSystemProfileMetrics(
+ SystemProfileProto* system_profile_proto);
+
+ // Called once at startup to see whether this provider has critical stability
+ // events to share in an initial stability log.
+ // Returning true can trigger ProvideInitialStabilityMetrics and
+ // ProvideStabilityMetrics on all other registered metrics providers.
+ // Default implementation always returns false.
+ virtual bool HasInitialStabilityMetrics();
+
+ // Called at most once at startup when an initial stability log is created.
+ // It provides critical statiblity metrics that need to be reported in an
+ // initial stability log.
+ // Default implementation is a no-op.
+ virtual void ProvideInitialStabilityMetrics(
+ SystemProfileProto* system_profile_proto);
+
+ // Provides additional stability metrics. Stability metrics can be provided
+ // directly into |stability_proto| fields or by logging stability histograms
+ // via the UMA_STABILITY_HISTOGRAM_ENUMERATION() macro.
+ virtual void ProvideStabilityMetrics(
+ SystemProfileProto* system_profile_proto);
+
+ // Called to indicate that saved stability prefs should be cleared, e.g.
+ // because they are from an old version and should not be kept.
+ virtual void ClearSavedStabilityMetrics();
+
+ // Provides general metrics that are neither system profile nor stability
+ // metrics. May also be used to add histograms when final metrics are
+ // collected right before upload.
+ virtual void ProvideGeneralMetrics(
+ ChromeUserMetricsExtension* uma_proto);
+
+ // Called during collection to explicitly load histogram snapshots using a
+ // snapshot manager. PrepareDeltas() will have already been called and
+ // FinishDeltas() will be called later; calls to only PrepareDelta(), not
+ // PrepareDeltas (plural), should be made.
+ virtual void RecordHistogramSnapshots(
+ base::HistogramSnapshotManager* snapshot_manager);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MetricsProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_PROVIDER_H_
diff --git a/chromium/components/metrics/metrics_reporting_scheduler.cc b/chromium/components/metrics/metrics_reporting_scheduler.cc
new file mode 100644
index 00000000000..ab117ad01e9
--- /dev/null
+++ b/chromium/components/metrics/metrics_reporting_scheduler.cc
@@ -0,0 +1,181 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_reporting_scheduler.h"
+
+#include <stdint.h>
+
+#include "base/compiler_specific.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "build/build_config.h"
+#include "components/variations/variations_associated_data.h"
+
+using base::TimeDelta;
+
+namespace metrics {
+
+namespace {
+
+// The delay, in seconds, after startup before sending the first log message.
+#if defined(OS_ANDROID) || defined(OS_IOS)
+// Sessions are more likely to be short on a mobile device, so handle the
+// initial log quickly.
+const int kInitialUploadIntervalSeconds = 15;
+#else
+const int kInitialUploadIntervalSeconds = 60;
+#endif
+
+// The delay, in seconds, between uploading when there are queued logs from
+// previous sessions to send.
+#if defined(OS_ANDROID) || defined(OS_IOS)
+// Sending in a burst is better on a mobile device, since keeping the radio on
+// is very expensive.
+const int kUnsentLogsIntervalSeconds = 3;
+#else
+const int kUnsentLogsIntervalSeconds = 15;
+#endif
+
+// When uploading metrics to the server fails, we progressively wait longer and
+// longer before sending the next log. This backoff process helps reduce load
+// on a server that is having issues.
+// The following is the multiplier we use to expand that inter-log duration.
+const double kBackoffMultiplier = 1.1;
+
+// The maximum backoff multiplier.
+const int kMaxBackoffMultiplier = 10;
+
+enum InitSequence {
+ TIMER_FIRED_FIRST,
+ INIT_TASK_COMPLETED_FIRST,
+ INIT_SEQUENCE_ENUM_SIZE,
+};
+
+void LogMetricsInitSequence(InitSequence sequence) {
+ UMA_HISTOGRAM_ENUMERATION("UMA.InitSequence", sequence,
+ INIT_SEQUENCE_ENUM_SIZE);
+}
+
+void LogActualUploadInterval(TimeDelta interval) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("UMA.ActualLogUploadInterval",
+ interval.InMinutes(),
+ 1,
+ base::TimeDelta::FromHours(12).InMinutes(),
+ 50);
+}
+
+} // anonymous namespace
+
+MetricsReportingScheduler::MetricsReportingScheduler(
+ const base::Closure& upload_callback,
+ const base::Callback<base::TimeDelta(void)>& upload_interval_callback)
+ : upload_callback_(upload_callback),
+ upload_interval_(TimeDelta::FromSeconds(kInitialUploadIntervalSeconds)),
+ running_(false),
+ callback_pending_(false),
+ init_task_complete_(false),
+ waiting_for_init_task_complete_(false),
+ upload_interval_callback_(upload_interval_callback) {
+}
+
+MetricsReportingScheduler::~MetricsReportingScheduler() {}
+
+void MetricsReportingScheduler::Start() {
+ running_ = true;
+ ScheduleNextUpload();
+}
+
+void MetricsReportingScheduler::Stop() {
+ running_ = false;
+ if (upload_timer_.IsRunning())
+ upload_timer_.Stop();
+}
+
+// Callback from MetricsService when the startup init task has completed.
+void MetricsReportingScheduler::InitTaskComplete() {
+ DCHECK(!init_task_complete_);
+ init_task_complete_ = true;
+ if (waiting_for_init_task_complete_) {
+ waiting_for_init_task_complete_ = false;
+ TriggerUpload();
+ } else {
+ LogMetricsInitSequence(INIT_TASK_COMPLETED_FIRST);
+ }
+}
+
+void MetricsReportingScheduler::UploadFinished(bool server_is_healthy,
+ bool more_logs_remaining) {
+ DCHECK(callback_pending_);
+ callback_pending_ = false;
+ // If the server is having issues, back off. Otherwise, reset to default
+ // (unless there are more logs to send, in which case the next upload should
+ // happen sooner).
+ if (!server_is_healthy) {
+ BackOffUploadInterval();
+ } else if (more_logs_remaining) {
+ upload_interval_ = TimeDelta::FromSeconds(kUnsentLogsIntervalSeconds);
+ } else {
+ upload_interval_ = GetStandardUploadInterval();
+ last_upload_finish_time_ = base::TimeTicks::Now();
+ }
+
+ if (running_)
+ ScheduleNextUpload();
+}
+
+void MetricsReportingScheduler::UploadCancelled() {
+ DCHECK(callback_pending_);
+ callback_pending_ = false;
+ if (running_)
+ ScheduleNextUpload();
+}
+
+void MetricsReportingScheduler::SetUploadIntervalForTesting(
+ base::TimeDelta interval) {
+ upload_interval_ = interval;
+}
+
+void MetricsReportingScheduler::TriggerUpload() {
+ // If the timer fired before the init task has completed, don't trigger the
+ // upload yet - wait for the init task to complete and do it then.
+ if (!init_task_complete_) {
+ LogMetricsInitSequence(TIMER_FIRED_FIRST);
+ waiting_for_init_task_complete_ = true;
+ return;
+ }
+
+ if (!last_upload_finish_time_.is_null()) {
+ LogActualUploadInterval(base::TimeTicks::Now() - last_upload_finish_time_);
+ last_upload_finish_time_ = base::TimeTicks();
+ }
+
+ callback_pending_ = true;
+ upload_callback_.Run();
+}
+
+void MetricsReportingScheduler::ScheduleNextUpload() {
+ DCHECK(running_);
+ if (upload_timer_.IsRunning() || callback_pending_)
+ return;
+
+ upload_timer_.Start(FROM_HERE, upload_interval_, this,
+ &MetricsReportingScheduler::TriggerUpload);
+}
+
+void MetricsReportingScheduler::BackOffUploadInterval() {
+ DCHECK_GT(kBackoffMultiplier, 1.0);
+ upload_interval_ = TimeDelta::FromMicroseconds(static_cast<int64_t>(
+ kBackoffMultiplier * upload_interval_.InMicroseconds()));
+
+ TimeDelta max_interval = kMaxBackoffMultiplier * GetStandardUploadInterval();
+ if (upload_interval_ > max_interval || upload_interval_.InSeconds() < 0) {
+ upload_interval_ = max_interval;
+ }
+}
+
+base::TimeDelta MetricsReportingScheduler::GetStandardUploadInterval() {
+ return upload_interval_callback_.Run();
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_reporting_scheduler.h b/chromium/components/metrics/metrics_reporting_scheduler.h
new file mode 100644
index 00000000000..f4e3dd11b3c
--- /dev/null
+++ b/chromium/components/metrics/metrics_reporting_scheduler.h
@@ -0,0 +1,99 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_REPORTING_SCHEDULER_H_
+#define COMPONENTS_METRICS_METRICS_REPORTING_SCHEDULER_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "components/metrics/net/network_metrics_provider.h"
+
+namespace metrics {
+
+// Scheduler task to drive a MetricsService object's uploading.
+class MetricsReportingScheduler {
+ public:
+ // Creates MetricsServiceScheduler object with the given |upload_callback|
+ // callback to call when uploading should happen and
+ // |upload_interval_callback| to determine the interval between uploads
+ // in steady state.
+ MetricsReportingScheduler(
+ const base::Closure& upload_callback,
+ const base::Callback<base::TimeDelta(void)>& upload_interval_callback);
+ ~MetricsReportingScheduler();
+
+ // Starts scheduling uploads. This in a no-op if the scheduler is already
+ // running, so it is safe to call more than once.
+ void Start();
+
+ // Stops scheduling uploads.
+ void Stop();
+
+ // Callback from MetricsService when the startup init task has completed.
+ void InitTaskComplete();
+
+ // Callback from MetricsService when a triggered upload finishes.
+ void UploadFinished(bool server_is_healthy, bool more_logs_remaining);
+
+ // Callback from MetricsService when a triggered upload is cancelled by the
+ // MetricsService.
+ void UploadCancelled();
+
+ // Sets the upload interval to a specific value, exposed for unit tests.
+ void SetUploadIntervalForTesting(base::TimeDelta interval);
+
+ private:
+ // Timer callback indicating it's time for the MetricsService to upload
+ // metrics.
+ void TriggerUpload();
+
+ // Schedules a future call to TriggerUpload if one isn't already pending.
+ void ScheduleNextUpload();
+
+ // Increases the upload interval each time it's called, to handle the case
+ // where the server is having issues.
+ void BackOffUploadInterval();
+
+ // Returns upload interval to use in steady state.
+ base::TimeDelta GetStandardUploadInterval();
+
+ // The MetricsService method to call when uploading should happen.
+ const base::Closure upload_callback_;
+
+ base::OneShotTimer upload_timer_;
+
+ // The interval between being told an upload is done and starting the next
+ // upload.
+ base::TimeDelta upload_interval_;
+
+ // The tick count of the last time log upload has been finished and null if no
+ // upload has been done yet.
+ base::TimeTicks last_upload_finish_time_;
+
+ // Indicates that the scheduler is running (i.e., that Start has been called
+ // more recently than Stop).
+ bool running_;
+
+ // Indicates that the last triggered upload hasn't resolved yet.
+ bool callback_pending_;
+
+ // Whether the InitTaskComplete() callback has been called.
+ bool init_task_complete_;
+
+ // Whether the initial scheduled upload timer has fired before the init task
+ // has been completed.
+ bool waiting_for_init_task_complete_;
+
+ // Callback function used to get the standard upload time.
+ base::Callback<base::TimeDelta(void)> upload_interval_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsReportingScheduler);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_REPORTING_SCHEDULER_H_
diff --git a/chromium/components/metrics/metrics_reporting_scheduler_unittest.cc b/chromium/components/metrics/metrics_reporting_scheduler_unittest.cc
new file mode 100644
index 00000000000..f5b9b5b0180
--- /dev/null
+++ b/chromium/components/metrics/metrics_reporting_scheduler_unittest.cc
@@ -0,0 +1,71 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_reporting_scheduler.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+class MetricsReportingSchedulerTest : public testing::Test {
+ public:
+ MetricsReportingSchedulerTest() : callback_call_count_(0) {}
+ ~MetricsReportingSchedulerTest() override {}
+
+ base::Closure GetCallback() {
+ return base::Bind(&MetricsReportingSchedulerTest::SchedulerCallback,
+ base::Unretained(this));
+ }
+
+ base::Callback<base::TimeDelta(void)> GetConnectionCallback() {
+ return base::Bind(&MetricsReportingSchedulerTest::GetStandardUploadInterval,
+ base::Unretained(this));
+ }
+
+ int callback_call_count() const { return callback_call_count_; }
+
+ private:
+ void SchedulerCallback() {
+ ++callback_call_count_;
+ }
+
+ base::TimeDelta GetStandardUploadInterval() {
+ return base::TimeDelta::FromMinutes(5);
+ }
+
+ int callback_call_count_;
+
+ base::MessageLoopForUI message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsReportingSchedulerTest);
+};
+
+
+TEST_F(MetricsReportingSchedulerTest, InitTaskCompleteBeforeTimer) {
+ MetricsReportingScheduler scheduler(GetCallback(), GetConnectionCallback());
+ scheduler.SetUploadIntervalForTesting(base::TimeDelta());
+ scheduler.InitTaskComplete();
+ scheduler.Start();
+ EXPECT_EQ(0, callback_call_count());
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, callback_call_count());
+}
+
+TEST_F(MetricsReportingSchedulerTest, InitTaskCompleteAfterTimer) {
+ MetricsReportingScheduler scheduler(GetCallback(), GetConnectionCallback());
+ scheduler.SetUploadIntervalForTesting(base::TimeDelta());
+ scheduler.Start();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, callback_call_count());
+
+ scheduler.InitTaskComplete();
+ EXPECT_EQ(1, callback_call_count());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_service.cc b/chromium/components/metrics/metrics_service.cc
new file mode 100644
index 00000000000..38ef6e45ab1
--- /dev/null
+++ b/chromium/components/metrics/metrics_service.cc
@@ -0,0 +1,1193 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//------------------------------------------------------------------------------
+// Description of the life cycle of a instance of MetricsService.
+//
+// OVERVIEW
+//
+// A MetricsService instance is typically created at application startup. It is
+// the central controller for the acquisition of log data, and the automatic
+// transmission of that log data to an external server. Its major job is to
+// manage logs, grouping them for transmission, and transmitting them. As part
+// of its grouping, MS finalizes logs by including some just-in-time gathered
+// memory statistics, snapshotting the current stats of numerous histograms,
+// closing the logs, translating to protocol buffer format, and compressing the
+// results for transmission. Transmission includes submitting a compressed log
+// as data in a URL-post, and retransmitting (or retaining at process
+// termination) if the attempted transmission failed. Retention across process
+// terminations is done using the the PrefServices facilities. The retained logs
+// (the ones that never got transmitted) are compressed and base64-encoded
+// before being persisted.
+//
+// Logs fall into one of two categories: "initial logs," and "ongoing logs."
+// There is at most one initial log sent for each complete run of Chrome (from
+// startup, to browser shutdown). An initial log is generally transmitted some
+// short time (1 minute?) after startup, and includes stats such as recent crash
+// info, the number and types of plugins, etc. The external server's response
+// to the initial log conceptually tells this MS if it should continue
+// transmitting logs (during this session). The server response can actually be
+// much more detailed, and always includes (at a minimum) how often additional
+// ongoing logs should be sent.
+//
+// After the above initial log, a series of ongoing logs will be transmitted.
+// The first ongoing log actually begins to accumulate information stating when
+// the MS was first constructed. Note that even though the initial log is
+// commonly sent a full minute after startup, the initial log does not include
+// much in the way of user stats. The most common interlog period (delay)
+// is 30 minutes. That time period starts when the first user action causes a
+// logging event. This means that if there is no user action, there may be long
+// periods without any (ongoing) log transmissions. Ongoing logs typically
+// contain very detailed records of user activities (ex: opened tab, closed
+// tab, fetched URL, maximized window, etc.) In addition, just before an
+// ongoing log is closed out, a call is made to gather memory statistics. Those
+// memory statistics are deposited into a histogram, and the log finalization
+// code is then called. In the finalization, a call to a Histogram server
+// acquires a list of all local histograms that have been flagged for upload
+// to the UMA server. The finalization also acquires the most recent number
+// of page loads, along with any counts of renderer or plugin crashes.
+//
+// When the browser shuts down, there will typically be a fragment of an ongoing
+// log that has not yet been transmitted. At shutdown time, that fragment is
+// closed (including snapshotting histograms), and persisted, for potential
+// transmission during a future run of the product.
+//
+// There are two slightly abnormal shutdown conditions. There is a
+// "disconnected scenario," and a "really fast startup and shutdown" scenario.
+// In the "never connected" situation, the user has (during the running of the
+// process) never established an internet connection. As a result, attempts to
+// transmit the initial log have failed, and a lot(?) of data has accumulated in
+// the ongoing log (which didn't yet get closed, because there was never even a
+// contemplation of sending it). There is also a kindred "lost connection"
+// situation, where a loss of connection prevented an ongoing log from being
+// transmitted, and a (still open) log was stuck accumulating a lot(?) of data,
+// while the earlier log retried its transmission. In both of these
+// disconnected situations, two logs need to be, and are, persistently stored
+// for future transmission.
+//
+// The other unusual shutdown condition, termed "really fast startup and
+// shutdown," involves the deliberate user termination of the process before
+// the initial log is even formed or transmitted. In that situation, no logging
+// is done, but the historical crash statistics remain (unlogged) for inclusion
+// in a future run's initial log. (i.e., we don't lose crash stats).
+//
+// With the above overview, we can now describe the state machine's various
+// states, based on the State enum specified in the state_ member. Those states
+// are:
+//
+// INITIALIZED, // Constructor was called.
+// INIT_TASK_SCHEDULED, // Waiting for deferred init tasks to finish.
+// INIT_TASK_DONE, // Waiting for timer to send initial log.
+// SENDING_LOGS, // Sending logs and creating new ones when we run out.
+//
+// In more detail, we have:
+//
+// INITIALIZED, // Constructor was called.
+// The MS has been constructed, but has taken no actions to compose the
+// initial log.
+//
+// INIT_TASK_SCHEDULED, // Waiting for deferred init tasks to finish.
+// Typically about 30 seconds after startup, a task is sent to a second thread
+// (the file thread) to perform deferred (lower priority and slower)
+// initialization steps such as getting the list of plugins. That task will
+// (when complete) make an async callback (via a Task) to indicate the
+// completion.
+//
+// INIT_TASK_DONE, // Waiting for timer to send initial log.
+// The callback has arrived, and it is now possible for an initial log to be
+// created. This callback typically arrives back less than one second after
+// the deferred init task is dispatched.
+//
+// SENDING_LOGS, // Sending logs an creating new ones when we run out.
+// Logs from previous sessions have been loaded, and initial logs have been
+// created (an optional stability log and the first metrics log). We will
+// send all of these logs, and when run out, we will start cutting new logs
+// to send. We will also cut a new log if we expect a shutdown.
+//
+// The progression through the above states is simple, and sequential.
+// States proceed from INITIAL to SENDING_LOGS, and remain in the latter until
+// shutdown.
+//
+// Also note that whenever we successfully send a log, we mirror the list
+// of logs into the PrefService. This ensures that IF we crash, we won't start
+// up and retransmit our old logs again.
+//
+// Due to race conditions, it is always possible that a log file could be sent
+// twice. For example, if a log file is sent, but not yet acknowledged by
+// the external server, and the user shuts down, then a copy of the log may be
+// saved for re-transmission. These duplicates could be filtered out server
+// side, but are not expected to be a significant problem.
+//
+//
+//------------------------------------------------------------------------------
+
+#include "components/metrics/metrics_service.h"
+
+#include <stddef.h>
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/persistent_histogram_allocator.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/rand_util.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "base/tracked_objects.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "components/metrics/data_use_tracker.h"
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/metrics_log_manager.h"
+#include "components/metrics/metrics_log_uploader.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_reporting_scheduler.h"
+#include "components/metrics/metrics_service_client.h"
+#include "components/metrics/metrics_state_manager.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/entropy_provider.h"
+#include "components/variations/variations_associated_data.h"
+
+namespace metrics {
+
+namespace {
+
+// Check to see that we're being called on only one thread.
+bool IsSingleThreaded() {
+ static base::PlatformThreadId thread_id = 0;
+ if (!thread_id)
+ thread_id = base::PlatformThread::CurrentId();
+ return base::PlatformThread::CurrentId() == thread_id;
+}
+
+// The delay, in seconds, after starting recording before doing expensive
+// initialization work.
+#if defined(OS_ANDROID) || defined(OS_IOS)
+// On mobile devices, a significant portion of sessions last less than a minute.
+// Use a shorter timer on these platforms to avoid losing data.
+// TODO(dfalcantara): To avoid delaying startup, tighten up initialization so
+// that it occurs after the user gets their initial page.
+const int kInitializationDelaySeconds = 5;
+#else
+const int kInitializationDelaySeconds = 30;
+#endif
+
+// The maximum number of events in a log uploaded to the UMA server.
+const int kEventLimit = 2400;
+
+// If an upload fails, and the transmission was over this byte count, then we
+// will discard the log, and not try to retransmit it. We also don't persist
+// the log to the prefs for transmission during the next chrome session if this
+// limit is exceeded.
+const size_t kUploadLogAvoidRetransmitSize = 100 * 1024;
+
+// Interval, in minutes, between state saves.
+const int kSaveStateIntervalMinutes = 5;
+
+enum ResponseStatus {
+ UNKNOWN_FAILURE,
+ SUCCESS,
+ BAD_REQUEST, // Invalid syntax or log too large.
+ NO_RESPONSE,
+ NUM_RESPONSE_STATUSES
+};
+
+ResponseStatus ResponseCodeToStatus(int response_code) {
+ switch (response_code) {
+ case -1:
+ return NO_RESPONSE;
+ case 200:
+ return SUCCESS;
+ case 400:
+ return BAD_REQUEST;
+ default:
+ return UNKNOWN_FAILURE;
+ }
+}
+
+#if defined(OS_ANDROID) || defined(OS_IOS)
+void MarkAppCleanShutdownAndCommit(CleanExitBeacon* clean_exit_beacon,
+ PrefService* local_state) {
+ clean_exit_beacon->WriteBeaconValue(true);
+ local_state->SetInteger(prefs::kStabilityExecutionPhase,
+ MetricsService::SHUTDOWN_COMPLETE);
+ // Start writing right away (write happens on a different thread).
+ local_state->CommitPendingWrite();
+}
+#endif // defined(OS_ANDROID) || defined(OS_IOS)
+
+// Determines if current log should be sent based on sampling rate. Returns true
+// if the sampling rate is not set.
+bool ShouldUploadLog() {
+ std::string probability_str = variations::GetVariationParamValue(
+ "UMA_EnableCellularLogUpload", "Sample_Probability");
+ if (probability_str.empty())
+ return true;
+
+ int probability;
+ // In case specified sampling rate is invalid.
+ if (!base::StringToInt(probability_str, &probability))
+ return true;
+ return base::RandInt(1, 100) <= probability;
+}
+
+} // namespace
+
+// static
+MetricsService::ShutdownCleanliness MetricsService::clean_shutdown_status_ =
+ MetricsService::CLEANLY_SHUTDOWN;
+
+MetricsService::ExecutionPhase MetricsService::execution_phase_ =
+ MetricsService::UNINITIALIZED_PHASE;
+
+// static
+void MetricsService::RegisterPrefs(PrefRegistrySimple* registry) {
+ DCHECK(IsSingleThreaded());
+ MetricsStateManager::RegisterPrefs(registry);
+ MetricsLog::RegisterPrefs(registry);
+ DataUseTracker::RegisterPrefs(registry);
+
+ registry->RegisterInt64Pref(prefs::kInstallDate, 0);
+
+ registry->RegisterInt64Pref(prefs::kStabilityLaunchTimeSec, 0);
+ registry->RegisterInt64Pref(prefs::kStabilityLastTimestampSec, 0);
+ registry->RegisterStringPref(prefs::kStabilityStatsVersion, std::string());
+ registry->RegisterInt64Pref(prefs::kStabilityStatsBuildTime, 0);
+ registry->RegisterBooleanPref(prefs::kStabilityExitedCleanly, true);
+ registry->RegisterIntegerPref(prefs::kStabilityExecutionPhase,
+ UNINITIALIZED_PHASE);
+ registry->RegisterBooleanPref(prefs::kStabilitySessionEndCompleted, true);
+ registry->RegisterIntegerPref(prefs::kMetricsSessionID, -1);
+
+ registry->RegisterListPref(prefs::kMetricsInitialLogs);
+ registry->RegisterListPref(prefs::kMetricsOngoingLogs);
+
+ registry->RegisterInt64Pref(prefs::kUninstallLaunchCount, 0);
+ registry->RegisterInt64Pref(prefs::kUninstallMetricsUptimeSec, 0);
+}
+
+MetricsService::MetricsService(MetricsStateManager* state_manager,
+ MetricsServiceClient* client,
+ PrefService* local_state)
+ : log_manager_(local_state, kUploadLogAvoidRetransmitSize),
+ histogram_snapshot_manager_(this),
+ state_manager_(state_manager),
+ client_(client),
+ local_state_(local_state),
+ clean_exit_beacon_(client->GetRegistryBackupKey(), local_state),
+ recording_state_(UNSET),
+ reporting_active_(false),
+ test_mode_active_(false),
+ state_(INITIALIZED),
+ log_upload_in_progress_(false),
+ idle_since_last_transmission_(false),
+ session_id_(-1),
+ data_use_tracker_(DataUseTracker::Create(local_state_)),
+ self_ptr_factory_(this),
+ state_saver_factory_(this) {
+ DCHECK(IsSingleThreaded());
+ DCHECK(state_manager_);
+ DCHECK(client_);
+ DCHECK(local_state_);
+
+ // Set the install date if this is our first run.
+ int64_t install_date = local_state_->GetInt64(prefs::kInstallDate);
+ if (install_date == 0)
+ local_state_->SetInt64(prefs::kInstallDate, base::Time::Now().ToTimeT());
+}
+
+MetricsService::~MetricsService() {
+ DisableRecording();
+}
+
+void MetricsService::InitializeMetricsRecordingState() {
+ InitializeMetricsState();
+
+ base::Closure upload_callback =
+ base::Bind(&MetricsService::StartScheduledUpload,
+ self_ptr_factory_.GetWeakPtr());
+ scheduler_.reset(
+ new MetricsReportingScheduler(
+ upload_callback,
+ // MetricsServiceClient outlives MetricsService, and
+ // MetricsReportingScheduler is tied to the lifetime of |this|.
+ base::Bind(&MetricsServiceClient::GetStandardUploadInterval,
+ base::Unretained(client_))));
+
+ for (MetricsProvider* provider : metrics_providers_)
+ provider->Init();
+}
+
+void MetricsService::Start() {
+ HandleIdleSinceLastTransmission(false);
+ EnableRecording();
+ EnableReporting();
+}
+
+void MetricsService::StartRecordingForTests() {
+ test_mode_active_ = true;
+ EnableRecording();
+ DisableReporting();
+}
+
+void MetricsService::Stop() {
+ HandleIdleSinceLastTransmission(false);
+ DisableReporting();
+ DisableRecording();
+}
+
+void MetricsService::EnableReporting() {
+ if (reporting_active_)
+ return;
+ reporting_active_ = true;
+ StartSchedulerIfNecessary();
+}
+
+void MetricsService::DisableReporting() {
+ reporting_active_ = false;
+}
+
+std::string MetricsService::GetClientId() {
+ return state_manager_->client_id();
+}
+
+int64_t MetricsService::GetInstallDate() {
+ return local_state_->GetInt64(prefs::kInstallDate);
+}
+
+int64_t MetricsService::GetMetricsReportingEnabledDate() {
+ return local_state_->GetInt64(prefs::kMetricsReportingEnabledTimestamp);
+}
+
+bool MetricsService::WasLastShutdownClean() const {
+ return clean_exit_beacon_.exited_cleanly();
+}
+
+scoped_ptr<const base::FieldTrial::EntropyProvider>
+MetricsService::CreateEntropyProvider() {
+ // TODO(asvitkine): Refactor the code so that MetricsService does not expose
+ // this method.
+ return state_manager_->CreateEntropyProvider();
+}
+
+void MetricsService::EnableRecording() {
+ DCHECK(IsSingleThreaded());
+
+ if (recording_state_ == ACTIVE)
+ return;
+ recording_state_ = ACTIVE;
+
+ state_manager_->ForceClientIdCreation();
+ client_->SetMetricsClientId(state_manager_->client_id());
+ if (!log_manager_.current_log())
+ OpenNewLog();
+
+ for (MetricsProvider* provider : metrics_providers_)
+ provider->OnRecordingEnabled();
+
+ base::RemoveActionCallback(action_callback_);
+ action_callback_ = base::Bind(&MetricsService::OnUserAction,
+ base::Unretained(this));
+ base::AddActionCallback(action_callback_);
+}
+
+void MetricsService::DisableRecording() {
+ DCHECK(IsSingleThreaded());
+
+ if (recording_state_ == INACTIVE)
+ return;
+ recording_state_ = INACTIVE;
+
+ client_->OnRecordingDisabled();
+
+ base::RemoveActionCallback(action_callback_);
+
+ for (MetricsProvider* provider : metrics_providers_)
+ provider->OnRecordingDisabled();
+
+ PushPendingLogsToPersistentStorage();
+}
+
+bool MetricsService::recording_active() const {
+ DCHECK(IsSingleThreaded());
+ return recording_state_ == ACTIVE;
+}
+
+bool MetricsService::reporting_active() const {
+ DCHECK(IsSingleThreaded());
+ return reporting_active_;
+}
+
+void MetricsService::RecordDelta(const base::HistogramBase& histogram,
+ const base::HistogramSamples& snapshot) {
+ log_manager_.current_log()->RecordHistogramDelta(histogram.histogram_name(),
+ snapshot);
+}
+
+void MetricsService::InconsistencyDetected(
+ base::HistogramBase::Inconsistency problem) {
+ UMA_HISTOGRAM_ENUMERATION("Histogram.InconsistenciesBrowser",
+ problem, base::HistogramBase::NEVER_EXCEEDED_VALUE);
+}
+
+void MetricsService::UniqueInconsistencyDetected(
+ base::HistogramBase::Inconsistency problem) {
+ UMA_HISTOGRAM_ENUMERATION("Histogram.InconsistenciesBrowserUnique",
+ problem, base::HistogramBase::NEVER_EXCEEDED_VALUE);
+}
+
+void MetricsService::InconsistencyDetectedInLoggedCount(int amount) {
+ UMA_HISTOGRAM_COUNTS("Histogram.InconsistentSnapshotBrowser",
+ std::abs(amount));
+}
+
+void MetricsService::HandleIdleSinceLastTransmission(bool in_idle) {
+ // If there wasn't a lot of action, maybe the computer was asleep, in which
+ // case, the log transmissions should have stopped. Here we start them up
+ // again.
+ if (!in_idle && idle_since_last_transmission_)
+ StartSchedulerIfNecessary();
+ idle_since_last_transmission_ = in_idle;
+}
+
+void MetricsService::OnApplicationNotIdle() {
+ if (recording_state_ == ACTIVE)
+ HandleIdleSinceLastTransmission(false);
+}
+
+void MetricsService::RecordStartOfSessionEnd() {
+ LogCleanShutdown();
+ RecordBooleanPrefValue(prefs::kStabilitySessionEndCompleted, false);
+}
+
+void MetricsService::RecordCompletedSessionEnd() {
+ LogCleanShutdown();
+ RecordBooleanPrefValue(prefs::kStabilitySessionEndCompleted, true);
+}
+
+#if defined(OS_ANDROID) || defined(OS_IOS)
+void MetricsService::OnAppEnterBackground() {
+ scheduler_->Stop();
+
+ MarkAppCleanShutdownAndCommit(&clean_exit_beacon_, local_state_);
+
+ // At this point, there's no way of knowing when the process will be
+ // killed, so this has to be treated similar to a shutdown, closing and
+ // persisting all logs. Unlinke a shutdown, the state is primed to be ready
+ // to continue logging and uploading if the process does return.
+ if (recording_active() && state_ >= SENDING_LOGS) {
+ PushPendingLogsToPersistentStorage();
+ // Persisting logs closes the current log, so start recording a new log
+ // immediately to capture any background work that might be done before the
+ // process is killed.
+ OpenNewLog();
+ }
+}
+
+void MetricsService::OnAppEnterForeground() {
+ clean_exit_beacon_.WriteBeaconValue(false);
+ StartSchedulerIfNecessary();
+}
+#else
+void MetricsService::LogNeedForCleanShutdown() {
+ clean_exit_beacon_.WriteBeaconValue(false);
+ // Redundant setting to be sure we call for a clean shutdown.
+ clean_shutdown_status_ = NEED_TO_SHUTDOWN;
+}
+#endif // defined(OS_ANDROID) || defined(OS_IOS)
+
+// static
+void MetricsService::SetExecutionPhase(ExecutionPhase execution_phase,
+ PrefService* local_state) {
+ execution_phase_ = execution_phase;
+ local_state->SetInteger(prefs::kStabilityExecutionPhase, execution_phase_);
+}
+
+void MetricsService::RecordBreakpadRegistration(bool success) {
+ if (!success)
+ IncrementPrefValue(prefs::kStabilityBreakpadRegistrationFail);
+ else
+ IncrementPrefValue(prefs::kStabilityBreakpadRegistrationSuccess);
+}
+
+void MetricsService::RecordBreakpadHasDebugger(bool has_debugger) {
+ if (!has_debugger)
+ IncrementPrefValue(prefs::kStabilityDebuggerNotPresent);
+ else
+ IncrementPrefValue(prefs::kStabilityDebuggerPresent);
+}
+
+void MetricsService::ClearSavedStabilityMetrics() {
+ for (MetricsProvider* provider : metrics_providers_)
+ provider->ClearSavedStabilityMetrics();
+
+ // Reset the prefs that are managed by MetricsService/MetricsLog directly.
+ local_state_->SetInteger(prefs::kStabilityCrashCount, 0);
+ local_state_->SetInteger(prefs::kStabilityExecutionPhase,
+ UNINITIALIZED_PHASE);
+ local_state_->SetInteger(prefs::kStabilityIncompleteSessionEndCount, 0);
+ local_state_->SetInteger(prefs::kStabilityLaunchCount, 0);
+ local_state_->SetBoolean(prefs::kStabilitySessionEndCompleted, true);
+}
+
+void MetricsService::PushExternalLog(const std::string& log) {
+ log_manager_.StoreLog(log, MetricsLog::ONGOING_LOG);
+}
+
+UpdateUsagePrefCallbackType MetricsService::GetDataUseForwardingCallback() {
+ DCHECK(IsSingleThreaded());
+
+ if (data_use_tracker_) {
+ return data_use_tracker_->GetDataUseForwardingCallback(
+ base::ThreadTaskRunnerHandle::Get());
+ }
+ return UpdateUsagePrefCallbackType();
+}
+
+//------------------------------------------------------------------------------
+// private methods
+//------------------------------------------------------------------------------
+
+
+//------------------------------------------------------------------------------
+// Initialization methods
+
+void MetricsService::InitializeMetricsState() {
+ const int64_t buildtime = MetricsLog::GetBuildTime();
+ const std::string version = client_->GetVersionString();
+ bool version_changed = false;
+ if (local_state_->GetInt64(prefs::kStabilityStatsBuildTime) != buildtime ||
+ local_state_->GetString(prefs::kStabilityStatsVersion) != version) {
+ local_state_->SetString(prefs::kStabilityStatsVersion, version);
+ local_state_->SetInt64(prefs::kStabilityStatsBuildTime, buildtime);
+ version_changed = true;
+ }
+
+ log_manager_.LoadPersistedUnsentLogs();
+
+ session_id_ = local_state_->GetInteger(prefs::kMetricsSessionID);
+
+ if (!clean_exit_beacon_.exited_cleanly()) {
+ IncrementPrefValue(prefs::kStabilityCrashCount);
+ // Reset flag, and wait until we call LogNeedForCleanShutdown() before
+ // monitoring.
+ clean_exit_beacon_.WriteBeaconValue(true);
+ }
+
+ bool has_initial_stability_log = false;
+ if (!clean_exit_beacon_.exited_cleanly() ||
+ ProvidersHaveInitialStabilityMetrics()) {
+ // TODO(rtenneti): On windows, consider saving/getting execution_phase from
+ // the registry.
+ int execution_phase =
+ local_state_->GetInteger(prefs::kStabilityExecutionPhase);
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Chrome.Browser.CrashedExecutionPhase",
+ execution_phase);
+
+ // If the previous session didn't exit cleanly, or if any provider
+ // explicitly requests it, prepare an initial stability log -
+ // provided UMA is enabled.
+ if (state_manager_->IsMetricsReportingEnabled())
+ has_initial_stability_log = PrepareInitialStabilityLog();
+ }
+
+ // If no initial stability log was generated and there was a version upgrade,
+ // clear the stability stats from the previous version (so that they don't get
+ // attributed to the current version). This could otherwise happen due to a
+ // number of different edge cases, such as if the last version crashed before
+ // it could save off a system profile or if UMA reporting is disabled (which
+ // normally results in stats being accumulated).
+ if (!has_initial_stability_log && version_changed)
+ ClearSavedStabilityMetrics();
+
+ // Update session ID.
+ ++session_id_;
+ local_state_->SetInteger(prefs::kMetricsSessionID, session_id_);
+
+ // Stability bookkeeping
+ IncrementPrefValue(prefs::kStabilityLaunchCount);
+
+ DCHECK_EQ(UNINITIALIZED_PHASE, execution_phase_);
+ SetExecutionPhase(START_METRICS_RECORDING, local_state_);
+
+ if (!local_state_->GetBoolean(prefs::kStabilitySessionEndCompleted)) {
+ IncrementPrefValue(prefs::kStabilityIncompleteSessionEndCount);
+ // This is marked false when we get a WM_ENDSESSION.
+ local_state_->SetBoolean(prefs::kStabilitySessionEndCompleted, true);
+ }
+
+ // Call GetUptimes() for the first time, thus allowing all later calls
+ // to record incremental uptimes accurately.
+ base::TimeDelta ignored_uptime_parameter;
+ base::TimeDelta startup_uptime;
+ GetUptimes(local_state_, &startup_uptime, &ignored_uptime_parameter);
+ DCHECK_EQ(0, startup_uptime.InMicroseconds());
+ // For backwards compatibility, leave this intact in case Omaha is checking
+ // them. prefs::kStabilityLastTimestampSec may also be useless now.
+ // TODO(jar): Delete these if they have no uses.
+ local_state_->SetInt64(prefs::kStabilityLaunchTimeSec,
+ base::Time::Now().ToTimeT());
+
+ // Bookkeeping for the uninstall metrics.
+ IncrementLongPrefsValue(prefs::kUninstallLaunchCount);
+
+ // Kick off the process of saving the state (so the uptime numbers keep
+ // getting updated) every n minutes.
+ ScheduleNextStateSave();
+}
+
+void MetricsService::OnUserAction(const std::string& action) {
+ if (!ShouldLogEvents())
+ return;
+
+ log_manager_.current_log()->RecordUserAction(action);
+ HandleIdleSinceLastTransmission(false);
+}
+
+void MetricsService::FinishedInitTask() {
+ DCHECK_EQ(INIT_TASK_SCHEDULED, state_);
+ state_ = INIT_TASK_DONE;
+
+ // Create the initial log.
+ if (!initial_metrics_log_.get()) {
+ initial_metrics_log_ = CreateLog(MetricsLog::ONGOING_LOG);
+ NotifyOnDidCreateMetricsLog();
+ }
+
+ scheduler_->InitTaskComplete();
+}
+
+void MetricsService::GetUptimes(PrefService* pref,
+ base::TimeDelta* incremental_uptime,
+ base::TimeDelta* uptime) {
+ base::TimeTicks now = base::TimeTicks::Now();
+ // If this is the first call, init |first_updated_time_| and
+ // |last_updated_time_|.
+ if (last_updated_time_.is_null()) {
+ first_updated_time_ = now;
+ last_updated_time_ = now;
+ }
+ *incremental_uptime = now - last_updated_time_;
+ *uptime = now - first_updated_time_;
+ last_updated_time_ = now;
+
+ const int64_t incremental_time_secs = incremental_uptime->InSeconds();
+ if (incremental_time_secs > 0) {
+ int64_t metrics_uptime = pref->GetInt64(prefs::kUninstallMetricsUptimeSec);
+ metrics_uptime += incremental_time_secs;
+ pref->SetInt64(prefs::kUninstallMetricsUptimeSec, metrics_uptime);
+ }
+}
+
+void MetricsService::NotifyOnDidCreateMetricsLog() {
+ DCHECK(IsSingleThreaded());
+ for (MetricsProvider* provider : metrics_providers_)
+ provider->OnDidCreateMetricsLog();
+}
+
+//------------------------------------------------------------------------------
+// State save methods
+
+void MetricsService::ScheduleNextStateSave() {
+ state_saver_factory_.InvalidateWeakPtrs();
+
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, base::Bind(&MetricsService::SaveLocalState,
+ state_saver_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMinutes(kSaveStateIntervalMinutes));
+}
+
+void MetricsService::SaveLocalState() {
+ RecordCurrentState(local_state_);
+
+ // TODO(jar):110021 Does this run down the batteries????
+ ScheduleNextStateSave();
+}
+
+
+//------------------------------------------------------------------------------
+// Recording control methods
+
+void MetricsService::OpenNewLog() {
+ DCHECK(!log_manager_.current_log());
+
+ log_manager_.BeginLoggingWithLog(CreateLog(MetricsLog::ONGOING_LOG));
+ NotifyOnDidCreateMetricsLog();
+ if (state_ == INITIALIZED) {
+ // We only need to schedule that run once.
+ state_ = INIT_TASK_SCHEDULED;
+
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, base::Bind(&MetricsService::StartInitTask,
+ self_ptr_factory_.GetWeakPtr()),
+ base::TimeDelta::FromSeconds(kInitializationDelaySeconds));
+ }
+}
+
+void MetricsService::StartInitTask() {
+ client_->InitializeSystemProfileMetrics(
+ base::Bind(&MetricsService::FinishedInitTask,
+ self_ptr_factory_.GetWeakPtr()));
+}
+
+void MetricsService::CloseCurrentLog() {
+ if (!log_manager_.current_log())
+ return;
+
+ // TODO(jar): Integrate bounds on log recording more consistently, so that we
+ // can stop recording logs that are too big much sooner.
+ if (log_manager_.current_log()->num_events() > kEventLimit) {
+ UMA_HISTOGRAM_COUNTS("UMA.Discarded Log Events",
+ log_manager_.current_log()->num_events());
+ log_manager_.DiscardCurrentLog();
+ OpenNewLog(); // Start trivial log to hold our histograms.
+ }
+
+ // If a persistent allocator is in use, update its internal histograms (such
+ // as how much memory is being used) before reporting.
+ base::PersistentHistogramAllocator* allocator =
+ base::GlobalHistogramAllocator::Get();
+ if (allocator)
+ allocator->UpdateTrackingHistograms();
+
+ // Put incremental data (histogram deltas, and realtime stats deltas) at the
+ // end of all log transmissions (initial log handles this separately).
+ // RecordIncrementalStabilityElements only exists on the derived
+ // MetricsLog class.
+ MetricsLog* current_log = log_manager_.current_log();
+ DCHECK(current_log);
+ RecordCurrentEnvironment(current_log);
+ base::TimeDelta incremental_uptime;
+ base::TimeDelta uptime;
+ GetUptimes(local_state_, &incremental_uptime, &uptime);
+ current_log->RecordStabilityMetrics(metrics_providers_.get(),
+ incremental_uptime, uptime);
+
+ current_log->RecordGeneralMetrics(metrics_providers_.get());
+ RecordCurrentHistograms();
+
+ log_manager_.FinishCurrentLog();
+}
+
+void MetricsService::PushPendingLogsToPersistentStorage() {
+ if (state_ < SENDING_LOGS)
+ return; // We didn't and still don't have time to get plugin list etc.
+
+ CloseCurrentLog();
+ log_manager_.PersistUnsentLogs();
+}
+
+//------------------------------------------------------------------------------
+// Transmission of logs methods
+
+void MetricsService::StartSchedulerIfNecessary() {
+ // Never schedule cutting or uploading of logs in test mode.
+ if (test_mode_active_)
+ return;
+
+ // Even if reporting is disabled, the scheduler is needed to trigger the
+ // creation of the initial log, which must be done in order for any logs to be
+ // persisted on shutdown or backgrounding.
+ if (recording_active() &&
+ (reporting_active() || state_ < SENDING_LOGS)) {
+ scheduler_->Start();
+ }
+}
+
+void MetricsService::StartScheduledUpload() {
+ DCHECK(state_ >= INIT_TASK_DONE);
+ // If we're getting no notifications, then the log won't have much in it, and
+ // it's possible the computer is about to go to sleep, so don't upload and
+ // stop the scheduler.
+ // If recording has been turned off, the scheduler doesn't need to run.
+ // If reporting is off, proceed if the initial log hasn't been created, since
+ // that has to happen in order for logs to be cut and stored when persisting.
+ // TODO(stuartmorgan): Call Stop() on the scheduler when reporting and/or
+ // recording are turned off instead of letting it fire and then aborting.
+ if (idle_since_last_transmission_ ||
+ !recording_active() ||
+ (!reporting_active() && state_ >= SENDING_LOGS)) {
+ scheduler_->Stop();
+ scheduler_->UploadCancelled();
+ return;
+ }
+
+ // If there are unsent logs, send the next one. If not, start the asynchronous
+ // process of finalizing the current log for upload.
+ if (state_ == SENDING_LOGS && log_manager_.has_unsent_logs()) {
+ SendNextLog();
+ } else {
+ // There are no logs left to send, so start creating a new one.
+ client_->CollectFinalMetricsForLog(
+ base::Bind(&MetricsService::OnFinalLogInfoCollectionDone,
+ self_ptr_factory_.GetWeakPtr()));
+ }
+}
+
+void MetricsService::OnFinalLogInfoCollectionDone() {
+ // If somehow there is a log upload in progress, we return and hope things
+ // work out. The scheduler isn't informed since if this happens, the scheduler
+ // will get a response from the upload.
+ DCHECK(!log_upload_in_progress_);
+ if (log_upload_in_progress_)
+ return;
+
+ // Abort if metrics were turned off during the final info gathering.
+ if (!recording_active()) {
+ scheduler_->Stop();
+ scheduler_->UploadCancelled();
+ return;
+ }
+
+ if (state_ == INIT_TASK_DONE) {
+ PrepareInitialMetricsLog();
+ } else {
+ DCHECK_EQ(SENDING_LOGS, state_);
+ CloseCurrentLog();
+ OpenNewLog();
+ }
+ SendNextLog();
+}
+
+void MetricsService::SendNextLog() {
+ DCHECK_EQ(SENDING_LOGS, state_);
+ if (!reporting_active()) {
+ scheduler_->Stop();
+ scheduler_->UploadCancelled();
+ return;
+ }
+ if (!log_manager_.has_unsent_logs()) {
+ // Should only get here if serializing the log failed somehow.
+ // Just tell the scheduler it was uploaded and wait for the next log
+ // interval.
+ scheduler_->UploadFinished(true, log_manager_.has_unsent_logs());
+ return;
+ }
+ if (!log_manager_.has_staged_log())
+ log_manager_.StageNextLogForUpload();
+
+ // Proceed to stage the log for upload if log size satisfies cellular log
+ // upload constrains.
+ bool is_cellular_logic = client_->IsUMACellularUploadLogicEnabled();
+ if (is_cellular_logic && data_use_tracker_ &&
+ !data_use_tracker_->ShouldUploadLogOnCellular(
+ log_manager_.staged_log_hash().size())) {
+ scheduler_->UploadCancelled();
+ } else {
+ SendStagedLog();
+ }
+}
+
+bool MetricsService::ProvidersHaveInitialStabilityMetrics() {
+ // Check whether any metrics provider has initial stability metrics.
+ for (MetricsProvider* provider : metrics_providers_) {
+ if (provider->HasInitialStabilityMetrics())
+ return true;
+ }
+
+ return false;
+}
+
+bool MetricsService::PrepareInitialStabilityLog() {
+ DCHECK_EQ(INITIALIZED, state_);
+
+ scoped_ptr<MetricsLog> initial_stability_log(
+ CreateLog(MetricsLog::INITIAL_STABILITY_LOG));
+
+ // Do not call NotifyOnDidCreateMetricsLog here because the stability
+ // log describes stats from the _previous_ session.
+
+ if (!initial_stability_log->LoadSavedEnvironmentFromPrefs())
+ return false;
+
+ log_manager_.PauseCurrentLog();
+ log_manager_.BeginLoggingWithLog(std::move(initial_stability_log));
+
+ // Note: Some stability providers may record stability stats via histograms,
+ // so this call has to be after BeginLoggingWithLog().
+ log_manager_.current_log()->RecordStabilityMetrics(
+ metrics_providers_.get(), base::TimeDelta(), base::TimeDelta());
+ RecordCurrentStabilityHistograms();
+
+ // Note: RecordGeneralMetrics() intentionally not called since this log is for
+ // stability stats from a previous session only.
+
+ log_manager_.FinishCurrentLog();
+ log_manager_.ResumePausedLog();
+
+ // Store unsent logs, including the stability log that was just saved, so
+ // that they're not lost in case of a crash before upload time.
+ log_manager_.PersistUnsentLogs();
+
+ return true;
+}
+
+void MetricsService::PrepareInitialMetricsLog() {
+ DCHECK_EQ(INIT_TASK_DONE, state_);
+
+ RecordCurrentEnvironment(initial_metrics_log_.get());
+ base::TimeDelta incremental_uptime;
+ base::TimeDelta uptime;
+ GetUptimes(local_state_, &incremental_uptime, &uptime);
+
+ // Histograms only get written to the current log, so make the new log current
+ // before writing them.
+ log_manager_.PauseCurrentLog();
+ log_manager_.BeginLoggingWithLog(std::move(initial_metrics_log_));
+
+ // Note: Some stability providers may record stability stats via histograms,
+ // so this call has to be after BeginLoggingWithLog().
+ MetricsLog* current_log = log_manager_.current_log();
+ current_log->RecordStabilityMetrics(metrics_providers_.get(),
+ base::TimeDelta(), base::TimeDelta());
+ current_log->RecordGeneralMetrics(metrics_providers_.get());
+ RecordCurrentHistograms();
+
+ log_manager_.FinishCurrentLog();
+ log_manager_.ResumePausedLog();
+
+ // Store unsent logs, including the initial log that was just saved, so
+ // that they're not lost in case of a crash before upload time.
+ log_manager_.PersistUnsentLogs();
+
+ state_ = SENDING_LOGS;
+}
+
+void MetricsService::SendStagedLog() {
+ DCHECK(log_manager_.has_staged_log());
+ if (!log_manager_.has_staged_log())
+ return;
+
+ DCHECK(!log_upload_in_progress_);
+ log_upload_in_progress_ = true;
+
+ if (!ShouldUploadLog()) {
+ SkipAndDiscardUpload();
+ return;
+ }
+
+ if (!log_uploader_) {
+ log_uploader_ = client_->CreateUploader(
+ base::Bind(&MetricsService::OnLogUploadComplete,
+ self_ptr_factory_.GetWeakPtr()));
+ }
+
+ const std::string hash =
+ base::HexEncode(log_manager_.staged_log_hash().data(),
+ log_manager_.staged_log_hash().size());
+ log_uploader_->UploadLog(log_manager_.staged_log(), hash);
+
+ HandleIdleSinceLastTransmission(true);
+}
+
+
+void MetricsService::OnLogUploadComplete(int response_code) {
+ DCHECK_EQ(SENDING_LOGS, state_);
+ DCHECK(log_upload_in_progress_);
+ log_upload_in_progress_ = false;
+
+ // Log a histogram to track response success vs. failure rates.
+ UMA_HISTOGRAM_ENUMERATION("UMA.UploadResponseStatus.Protobuf",
+ ResponseCodeToStatus(response_code),
+ NUM_RESPONSE_STATUSES);
+
+ bool upload_succeeded = response_code == 200;
+
+ // Provide boolean for error recovery (allow us to ignore response_code).
+ bool discard_log = false;
+ const size_t log_size = log_manager_.staged_log().length();
+ if (upload_succeeded) {
+ UMA_HISTOGRAM_COUNTS_10000("UMA.LogSize.OnSuccess", log_size / 1024);
+ } else if (log_size > kUploadLogAvoidRetransmitSize) {
+ UMA_HISTOGRAM_COUNTS("UMA.Large Rejected Log was Discarded",
+ static_cast<int>(log_size));
+ discard_log = true;
+ } else if (response_code == 400) {
+ // Bad syntax. Retransmission won't work.
+ discard_log = true;
+ }
+
+ if (upload_succeeded || discard_log) {
+ log_manager_.DiscardStagedLog();
+ // Store the updated list to disk now that the removed log is uploaded.
+ log_manager_.PersistUnsentLogs();
+ }
+
+ // Error 400 indicates a problem with the log, not with the server, so
+ // don't consider that a sign that the server is in trouble.
+ bool server_is_healthy = upload_succeeded || response_code == 400;
+ scheduler_->UploadFinished(server_is_healthy, log_manager_.has_unsent_logs());
+
+ if (server_is_healthy)
+ client_->OnLogUploadComplete();
+}
+
+void MetricsService::IncrementPrefValue(const char* path) {
+ int value = local_state_->GetInteger(path);
+ local_state_->SetInteger(path, value + 1);
+}
+
+void MetricsService::IncrementLongPrefsValue(const char* path) {
+ int64_t value = local_state_->GetInt64(path);
+ local_state_->SetInt64(path, value + 1);
+}
+
+bool MetricsService::UmaMetricsProperlyShutdown() {
+ CHECK(clean_shutdown_status_ == CLEANLY_SHUTDOWN ||
+ clean_shutdown_status_ == NEED_TO_SHUTDOWN);
+ return clean_shutdown_status_ == CLEANLY_SHUTDOWN;
+}
+
+void MetricsService::AddSyntheticTrialObserver(
+ variations::SyntheticTrialObserver* observer) {
+ synthetic_trial_observer_list_.AddObserver(observer);
+ if (!synthetic_trial_groups_.empty())
+ observer->OnSyntheticTrialsChanged(synthetic_trial_groups_);
+}
+
+void MetricsService::RemoveSyntheticTrialObserver(
+ variations::SyntheticTrialObserver* observer) {
+ synthetic_trial_observer_list_.RemoveObserver(observer);
+}
+
+void MetricsService::RegisterSyntheticFieldTrial(
+ const variations::SyntheticTrialGroup& trial) {
+ for (size_t i = 0; i < synthetic_trial_groups_.size(); ++i) {
+ if (synthetic_trial_groups_[i].id.name == trial.id.name) {
+ if (synthetic_trial_groups_[i].id.group != trial.id.group) {
+ synthetic_trial_groups_[i].id.group = trial.id.group;
+ synthetic_trial_groups_[i].start_time = base::TimeTicks::Now();
+ NotifySyntheticTrialObservers();
+ }
+ return;
+ }
+ }
+
+ variations::SyntheticTrialGroup trial_group = trial;
+ trial_group.start_time = base::TimeTicks::Now();
+ synthetic_trial_groups_.push_back(trial_group);
+ NotifySyntheticTrialObservers();
+}
+
+void MetricsService::GetCurrentSyntheticFieldTrialsForTesting(
+ std::vector<variations::ActiveGroupId>* synthetic_trials) {
+ GetSyntheticFieldTrialsOlderThan(base::TimeTicks::Now(), synthetic_trials);
+}
+
+void MetricsService::RegisterMetricsProvider(
+ scoped_ptr<MetricsProvider> provider) {
+ DCHECK_EQ(INITIALIZED, state_);
+ metrics_providers_.push_back(std::move(provider));
+}
+
+void MetricsService::CheckForClonedInstall(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+ state_manager_->CheckForClonedInstall(task_runner);
+}
+
+void MetricsService::NotifySyntheticTrialObservers() {
+ FOR_EACH_OBSERVER(variations::SyntheticTrialObserver,
+ synthetic_trial_observer_list_,
+ OnSyntheticTrialsChanged(synthetic_trial_groups_));
+}
+
+void MetricsService::GetSyntheticFieldTrialsOlderThan(
+ base::TimeTicks time,
+ std::vector<variations::ActiveGroupId>* synthetic_trials) {
+ DCHECK(synthetic_trials);
+ synthetic_trials->clear();
+ for (size_t i = 0; i < synthetic_trial_groups_.size(); ++i) {
+ if (synthetic_trial_groups_[i].start_time <= time)
+ synthetic_trials->push_back(synthetic_trial_groups_[i].id);
+ }
+}
+
+scoped_ptr<MetricsLog> MetricsService::CreateLog(MetricsLog::LogType log_type) {
+ return make_scoped_ptr(new MetricsLog(state_manager_->client_id(),
+ session_id_,
+ log_type,
+ client_,
+ local_state_));
+}
+
+void MetricsService::RecordCurrentEnvironment(MetricsLog* log) {
+ std::vector<variations::ActiveGroupId> synthetic_trials;
+ GetSyntheticFieldTrialsOlderThan(log->creation_time(), &synthetic_trials);
+ log->RecordEnvironment(metrics_providers_.get(), synthetic_trials,
+ GetInstallDate(), GetMetricsReportingEnabledDate());
+}
+
+void MetricsService::RecordCurrentHistograms() {
+ DCHECK(log_manager_.current_log());
+ histogram_snapshot_manager_.StartDeltas();
+ // "true" to the begin() call indicates that StatisticsRecorder should include
+ // histograms held in persistent storage.
+ auto end = base::StatisticsRecorder::end();
+ for (auto it = base::StatisticsRecorder::begin(true); it != end; ++it) {
+ if ((*it)->flags() & base::Histogram::kUmaTargetedHistogramFlag)
+ histogram_snapshot_manager_.PrepareDelta(*it);
+ }
+ for (MetricsProvider* provider : metrics_providers_)
+ provider->RecordHistogramSnapshots(&histogram_snapshot_manager_);
+ histogram_snapshot_manager_.FinishDeltas();
+}
+
+void MetricsService::RecordCurrentStabilityHistograms() {
+ DCHECK(log_manager_.current_log());
+ // "true" indicates that StatisticsRecorder should include histograms in
+ // persistent storage.
+ histogram_snapshot_manager_.PrepareDeltas(
+ base::StatisticsRecorder::begin(true), base::StatisticsRecorder::end(),
+ base::Histogram::kNoFlags, base::Histogram::kUmaStabilityHistogramFlag);
+}
+
+void MetricsService::LogCleanShutdown() {
+ // Redundant setting to assure that we always reset this value at shutdown
+ // (and that we don't use some alternate path, and not call LogCleanShutdown).
+ clean_shutdown_status_ = CLEANLY_SHUTDOWN;
+
+ clean_exit_beacon_.WriteBeaconValue(true);
+ RecordCurrentState(local_state_);
+ local_state_->SetInteger(prefs::kStabilityExecutionPhase,
+ MetricsService::SHUTDOWN_COMPLETE);
+}
+
+bool MetricsService::ShouldLogEvents() {
+ // We simply don't log events to UMA if there is a single incognito
+ // session visible. The problem is that we always notify using the original
+ // profile in order to simplify notification processing.
+ return !client_->IsOffTheRecordSessionActive();
+}
+
+void MetricsService::RecordBooleanPrefValue(const char* path, bool value) {
+ DCHECK(IsSingleThreaded());
+ local_state_->SetBoolean(path, value);
+ RecordCurrentState(local_state_);
+}
+
+void MetricsService::RecordCurrentState(PrefService* pref) {
+ pref->SetInt64(prefs::kStabilityLastTimestampSec,
+ base::Time::Now().ToTimeT());
+}
+
+void MetricsService::SkipAndDiscardUpload() {
+ log_manager_.DiscardStagedLog();
+ scheduler_->UploadCancelled();
+ log_upload_in_progress_ = false;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_service.h b/chromium/components/metrics/metrics_service.h
new file mode 100644
index 00000000000..f02df4bcabf
--- /dev/null
+++ b/chromium/components/metrics/metrics_service.h
@@ -0,0 +1,501 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines a service that collects information about the user
+// experience in order to help improve future versions of the app.
+
+#ifndef COMPONENTS_METRICS_METRICS_SERVICE_H_
+#define COMPONENTS_METRICS_METRICS_SERVICE_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram_flattener.h"
+#include "base/metrics/histogram_snapshot_manager.h"
+#include "base/metrics/user_metrics.h"
+#include "base/observer_list.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/metrics/clean_exit_beacon.h"
+#include "components/metrics/data_use_tracker.h"
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/metrics_log_manager.h"
+#include "components/metrics/metrics_provider.h"
+#include "components/metrics/net/network_metrics_provider.h"
+#include "components/variations/synthetic_trials.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace base {
+class DictionaryValue;
+class HistogramSamples;
+class PrefService;
+}
+
+namespace variations {
+struct ActiveGroupId;
+}
+
+namespace net {
+class URLFetcher;
+}
+
+namespace metrics {
+
+class MetricsLogUploader;
+class MetricsReportingScheduler;
+class MetricsServiceAccessor;
+class MetricsServiceClient;
+class MetricsStateManager;
+
+// See metrics_service.cc for a detailed description.
+class MetricsService : public base::HistogramFlattener {
+ public:
+ // The execution phase of the browser.
+ enum ExecutionPhase {
+ UNINITIALIZED_PHASE = 0,
+ START_METRICS_RECORDING = 100,
+ CREATE_PROFILE = 200,
+ STARTUP_TIMEBOMB_ARM = 300,
+ THREAD_WATCHER_START = 400,
+ MAIN_MESSAGE_LOOP_RUN = 500,
+ SHUTDOWN_TIMEBOMB_ARM = 600,
+ SHUTDOWN_COMPLETE = 700,
+ };
+
+ // Creates the MetricsService with the given |state_manager|, |client|, and
+ // |local_state|. Does not take ownership of the paramaters; instead stores
+ // a weak pointer to each. Caller should ensure that the parameters are valid
+ // for the lifetime of this class.
+ MetricsService(MetricsStateManager* state_manager,
+ MetricsServiceClient* client,
+ PrefService* local_state);
+ ~MetricsService() override;
+
+ // Initializes metrics recording state. Updates various bookkeeping values in
+ // prefs and sets up the scheduler. This is a separate function rather than
+ // being done by the constructor so that field trials could be created before
+ // this is run.
+ void InitializeMetricsRecordingState();
+
+ // Starts the metrics system, turning on recording and uploading of metrics.
+ // Should be called when starting up with metrics enabled, or when metrics
+ // are turned on.
+ void Start();
+
+ // Starts the metrics system in a special test-only mode. Metrics won't ever
+ // be uploaded or persisted in this mode, but metrics will be recorded in
+ // memory.
+ void StartRecordingForTests();
+
+ // Shuts down the metrics system. Should be called at shutdown, or if metrics
+ // are turned off.
+ void Stop();
+
+ // Enable/disable transmission of accumulated logs and crash reports (dumps).
+ // Calling Start() automatically enables reporting, but sending is
+ // asyncronous so this can be called immediately after Start() to prevent
+ // any uploading.
+ void EnableReporting();
+ void DisableReporting();
+
+ // Returns the client ID for this client, or the empty string if metrics
+ // recording is not currently running.
+ std::string GetClientId();
+
+ // Returns the install date of the application, in seconds since the epoch.
+ int64_t GetInstallDate();
+
+ // Returns the date at which the current metrics client ID was created as
+ // an int64_t containing seconds since the epoch.
+ int64_t GetMetricsReportingEnabledDate();
+
+ // Returns true if the last session exited cleanly.
+ bool WasLastShutdownClean() const;
+
+ // Returns the preferred entropy provider used to seed persistent activities
+ // based on whether or not metrics reporting will be permitted on this client.
+ //
+ // If metrics reporting is enabled, this method returns an entropy provider
+ // that has a high source of entropy, partially based on the client ID.
+ // Otherwise, it returns an entropy provider that is based on a low entropy
+ // source.
+ scoped_ptr<const base::FieldTrial::EntropyProvider> CreateEntropyProvider();
+
+ // At startup, prefs needs to be called with a list of all the pref names and
+ // types we'll be using.
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ // HistogramFlattener:
+ void RecordDelta(const base::HistogramBase& histogram,
+ const base::HistogramSamples& snapshot) override;
+ void InconsistencyDetected(
+ base::HistogramBase::Inconsistency problem) override;
+ void UniqueInconsistencyDetected(
+ base::HistogramBase::Inconsistency problem) override;
+ void InconsistencyDetectedInLoggedCount(int amount) override;
+
+ // This should be called when the application is not idle, i.e. the user seems
+ // to be interacting with the application.
+ void OnApplicationNotIdle();
+
+ // Invoked when we get a WM_SESSIONEND. This places a value in prefs that is
+ // reset when RecordCompletedSessionEnd is invoked.
+ void RecordStartOfSessionEnd();
+
+ // This should be called when the application is shutting down. It records
+ // that session end was successful.
+ void RecordCompletedSessionEnd();
+
+#if defined(OS_ANDROID) || defined(OS_IOS)
+ // Called when the application is going into background mode.
+ void OnAppEnterBackground();
+
+ // Called when the application is coming out of background mode.
+ void OnAppEnterForeground();
+#else
+ // Set the dirty flag, which will require a later call to LogCleanShutdown().
+ void LogNeedForCleanShutdown();
+#endif // defined(OS_ANDROID) || defined(OS_IOS)
+
+ static void SetExecutionPhase(ExecutionPhase execution_phase,
+ PrefService* local_state);
+
+ // Saves in the preferences if the crash report registration was successful.
+ // This count is eventually send via UMA logs.
+ void RecordBreakpadRegistration(bool success);
+
+ // Saves in the preferences if the browser is running under a debugger.
+ // This count is eventually send via UMA logs.
+ void RecordBreakpadHasDebugger(bool has_debugger);
+
+ bool recording_active() const;
+ bool reporting_active() const;
+
+ // Redundant test to ensure that we are notified of a clean exit.
+ // This value should be true when process has completed shutdown.
+ static bool UmaMetricsProperlyShutdown();
+
+ // Public accessor that returns the list of synthetic field trials. It must
+ // only be used for testing.
+ void GetCurrentSyntheticFieldTrialsForTesting(
+ std::vector<variations::ActiveGroupId>* synthetic_trials);
+
+ // Adds an observer to be notified when the synthetic trials list changes.
+ void AddSyntheticTrialObserver(variations::SyntheticTrialObserver* observer);
+
+ // Removes an existing observer of synthetic trials list changes.
+ void RemoveSyntheticTrialObserver(
+ variations::SyntheticTrialObserver* observer);
+
+ // Register the specified |provider| to provide additional metrics into the
+ // UMA log. Should be called during MetricsService initialization only.
+ void RegisterMetricsProvider(scoped_ptr<MetricsProvider> provider);
+
+ // Check if this install was cloned or imaged from another machine. If a
+ // clone is detected, reset the client id and low entropy source. This
+ // should not be called more than once.
+ void CheckForClonedInstall(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+
+ // Clears the stability metrics that are saved in local state.
+ void ClearSavedStabilityMetrics();
+
+ // Pushes a log that has been generated by an external component.
+ void PushExternalLog(const std::string& log);
+
+ // Returns a callback to data use pref updating function which can be called
+ // from any thread, but this function should be called on UI thread.
+ UpdateUsagePrefCallbackType GetDataUseForwardingCallback();
+
+ protected:
+ // Exposed for testing.
+ MetricsLogManager* log_manager() { return &log_manager_; }
+
+ private:
+ friend class MetricsServiceAccessor;
+
+ // The MetricsService has a lifecycle that is stored as a state.
+ // See metrics_service.cc for description of this lifecycle.
+ enum State {
+ INITIALIZED, // Constructor was called.
+ INIT_TASK_SCHEDULED, // Waiting for deferred init tasks to finish.
+ INIT_TASK_DONE, // Waiting for timer to send initial log.
+ SENDING_LOGS, // Sending logs an creating new ones when we run out.
+ };
+
+ enum ShutdownCleanliness {
+ CLEANLY_SHUTDOWN = 0xdeadbeef,
+ NEED_TO_SHUTDOWN = ~CLEANLY_SHUTDOWN
+ };
+
+ // The current state of recording for the MetricsService. The state is UNSET
+ // until set to something else, at which point it remains INACTIVE or ACTIVE
+ // for the lifetime of the object.
+ enum RecordingState {
+ INACTIVE,
+ ACTIVE,
+ UNSET
+ };
+
+ typedef std::vector<variations::SyntheticTrialGroup> SyntheticTrialGroups;
+
+ // Registers a field trial name and group to be used to annotate a UMA report
+ // with a particular Chrome configuration state. A UMA report will be
+ // annotated with this trial group if and only if all events in the report
+ // were created after the trial is registered. Only one group name may be
+ // registered at a time for a given trial_name. Only the last group name that
+ // is registered for a given trial name will be recorded. The values passed
+ // in must not correspond to any real field trial in the code.
+ void RegisterSyntheticFieldTrial(
+ const variations::SyntheticTrialGroup& trial_group);
+
+ // Calls into the client to initialize some system profile metrics.
+ void StartInitTask();
+
+ // Callback that moves the state to INIT_TASK_DONE. When this is called, the
+ // state should be INIT_TASK_SCHEDULED.
+ void FinishedInitTask();
+
+ void OnUserAction(const std::string& action);
+
+ // Get the amount of uptime since this process started and since the last
+ // call to this function. Also updates the cumulative uptime metric (stored
+ // as a pref) for uninstall. Uptimes are measured using TimeTicks, which
+ // guarantees that it is monotonic and does not jump if the user changes
+ // his/her clock. The TimeTicks implementation also makes the clock not
+ // count time the computer is suspended.
+ void GetUptimes(PrefService* pref,
+ base::TimeDelta* incremental_uptime,
+ base::TimeDelta* uptime);
+
+ // Turns recording on or off.
+ // DisableRecording() also forces a persistent save of logging state (if
+ // anything has been recorded, or transmitted).
+ void EnableRecording();
+ void DisableRecording();
+
+ // If in_idle is true, sets idle_since_last_transmission to true.
+ // If in_idle is false and idle_since_last_transmission_ is true, sets
+ // idle_since_last_transmission to false and starts the timer (provided
+ // starting the timer is permitted).
+ void HandleIdleSinceLastTransmission(bool in_idle);
+
+ // Set up client ID, session ID, etc.
+ void InitializeMetricsState();
+
+ // Notifies providers when a new metrics log is created.
+ void NotifyOnDidCreateMetricsLog();
+
+ // Schedule the next save of LocalState information. This is called
+ // automatically by the task that performs each save to schedule the next one.
+ void ScheduleNextStateSave();
+
+ // Save the LocalState information immediately. This should not be called by
+ // anybody other than the scheduler to avoid doing too many writes. When you
+ // make a change, call ScheduleNextStateSave() instead.
+ void SaveLocalState();
+
+ // Opens a new log for recording user experience metrics.
+ void OpenNewLog();
+
+ // Closes out the current log after adding any last information.
+ void CloseCurrentLog();
+
+ // Pushes the text of the current and staged logs into persistent storage.
+ // Called when Chrome shuts down.
+ void PushPendingLogsToPersistentStorage();
+
+ // Ensures that scheduler is running, assuming the current settings are such
+ // that metrics should be reported. If not, this is a no-op.
+ void StartSchedulerIfNecessary();
+
+ // Starts the process of uploading metrics data.
+ void StartScheduledUpload();
+
+ // Called by the client via a callback when final log info collection is
+ // complete.
+ void OnFinalLogInfoCollectionDone();
+
+ // If recording is enabled, begins uploading the next completed log from
+ // the log manager, staging it if necessary.
+ void SendNextLog();
+
+ // Returns true if any of the registered metrics providers have critical
+ // stability metrics to report in an initial stability log.
+ bool ProvidersHaveInitialStabilityMetrics();
+
+ // Prepares the initial stability log, which is only logged when the previous
+ // run of Chrome crashed. This log contains any stability metrics left over
+ // from that previous run, and only these stability metrics. It uses the
+ // system profile from the previous session. Returns true if a log was
+ // created.
+ bool PrepareInitialStabilityLog();
+
+ // Prepares the initial metrics log, which includes startup histograms and
+ // profiler data, as well as incremental stability-related metrics.
+ void PrepareInitialMetricsLog();
+
+ // Uploads the currently staged log (which must be non-null).
+ void SendStagedLog();
+
+ // Called after transmission completes (either successfully or with failure).
+ void OnLogUploadComplete(int response_code);
+
+ // Reads, increments and then sets the specified integer preference.
+ void IncrementPrefValue(const char* path);
+
+ // Reads, increments and then sets the specified long preference that is
+ // stored as a string.
+ void IncrementLongPrefsValue(const char* path);
+
+ // Records that the browser was shut down cleanly.
+ void LogCleanShutdown();
+
+ // Records state that should be periodically saved, like uptime and
+ // buffered plugin stability statistics.
+ void RecordCurrentState(PrefService* pref);
+
+ // Checks whether events should currently be logged.
+ bool ShouldLogEvents();
+
+ // Sets the value of the specified path in prefs and schedules a save.
+ void RecordBooleanPrefValue(const char* path, bool value);
+
+ // Notifies observers on a synthetic trial list change.
+ void NotifySyntheticTrialObservers();
+
+ // Returns a list of synthetic field trials that are older than |time|.
+ void GetSyntheticFieldTrialsOlderThan(
+ base::TimeTicks time,
+ std::vector<variations::ActiveGroupId>* synthetic_trials);
+
+ // Creates a new MetricsLog instance with the given |log_type|.
+ scoped_ptr<MetricsLog> CreateLog(MetricsLog::LogType log_type);
+
+ // Records the current environment (system profile) in |log|.
+ void RecordCurrentEnvironment(MetricsLog* log);
+
+ // Record complete list of histograms into the current log.
+ // Called when we close a log.
+ void RecordCurrentHistograms();
+
+ // Record complete list of stability histograms into the current log,
+ // i.e., histograms with the |kUmaStabilityHistogramFlag| flag set.
+ void RecordCurrentStabilityHistograms();
+
+ // Skips staged upload and discards the log. Used in case of unsuccessful
+ // upload or intentional sampling of logs.
+ void SkipAndDiscardUpload();
+
+ // Manager for the various in-flight logs.
+ MetricsLogManager log_manager_;
+
+ // |histogram_snapshot_manager_| prepares histogram deltas for transmission.
+ base::HistogramSnapshotManager histogram_snapshot_manager_;
+
+ // Used to manage various metrics reporting state prefs, such as client id,
+ // low entropy source and whether metrics reporting is enabled. Weak pointer.
+ MetricsStateManager* const state_manager_;
+
+ // Used to interact with the embedder. Weak pointer; must outlive |this|
+ // instance.
+ MetricsServiceClient* const client_;
+
+ // Registered metrics providers.
+ ScopedVector<MetricsProvider> metrics_providers_;
+
+ PrefService* local_state_;
+
+ CleanExitBeacon clean_exit_beacon_;
+
+ base::ActionCallback action_callback_;
+
+ // Indicate whether recording and reporting are currently happening.
+ // These should not be set directly, but by calling SetRecording and
+ // SetReporting.
+ RecordingState recording_state_;
+ bool reporting_active_;
+
+ // Indicate whether test mode is enabled, where the initial log should never
+ // be cut, and logs are neither persisted nor uploaded.
+ bool test_mode_active_;
+
+ // The progression of states made by the browser are recorded in the following
+ // state.
+ State state_;
+
+ // The initial metrics log, used to record startup metrics (histograms and
+ // profiler data). Note that if a crash occurred in the previous session, an
+ // initial stability log may be sent before this.
+ scoped_ptr<MetricsLog> initial_metrics_log_;
+
+ // Instance of the helper class for uploading logs.
+ scoped_ptr<MetricsLogUploader> log_uploader_;
+
+ // Whether there is a current log upload in progress.
+ bool log_upload_in_progress_;
+
+ // Whether the MetricsService object has received any notifications since
+ // the last time a transmission was sent.
+ bool idle_since_last_transmission_;
+
+ // A number that identifies the how many times the app has been launched.
+ int session_id_;
+
+ // The scheduler for determining when uploads should happen.
+ scoped_ptr<MetricsReportingScheduler> scheduler_;
+
+ // Stores the time of the first call to |GetUptimes()|.
+ base::TimeTicks first_updated_time_;
+
+ // Stores the time of the last call to |GetUptimes()|.
+ base::TimeTicks last_updated_time_;
+
+ // Field trial groups that map to Chrome configuration states.
+ SyntheticTrialGroups synthetic_trial_groups_;
+
+ // List of observers of |synthetic_trial_groups_| changes.
+ base::ObserverList<variations::SyntheticTrialObserver>
+ synthetic_trial_observer_list_;
+
+ // Execution phase the browser is in.
+ static ExecutionPhase execution_phase_;
+
+ // Redundant marker to check that we completed our shutdown, and set the
+ // exited-cleanly bit in the prefs.
+ static ShutdownCleanliness clean_shutdown_status_;
+
+ FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, IsPluginProcess);
+ FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest,
+ PermutedEntropyCacheClearedWhenLowEntropyReset);
+ FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, RegisterSyntheticTrial);
+
+ // Pointer used for obtaining data use pref updater callback on above layers.
+ scoped_ptr<DataUseTracker> data_use_tracker_;
+
+ // Weak pointers factory used to post task on different threads. All weak
+ // pointers managed by this factory have the same lifetime as MetricsService.
+ base::WeakPtrFactory<MetricsService> self_ptr_factory_;
+
+ // Weak pointers factory used for saving state. All weak pointers managed by
+ // this factory are invalidated in ScheduleNextStateSave.
+ base::WeakPtrFactory<MetricsService> state_saver_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsService);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_SERVICE_H_
diff --git a/chromium/components/metrics/metrics_service_accessor.cc b/chromium/components/metrics/metrics_service_accessor.cc
new file mode 100644
index 00000000000..ac04ba9884d
--- /dev/null
+++ b/chromium/components/metrics/metrics_service_accessor.cc
@@ -0,0 +1,70 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_service_accessor.h"
+
+#include "base/base_switches.h"
+#include "base/command_line.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_service.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/metrics_util.h"
+
+namespace metrics {
+
+// static
+bool MetricsServiceAccessor::IsMetricsReportingEnabled(
+ PrefService* pref_service) {
+ return IsMetricsReportingEnabledWithPrefValue(
+ pref_service->GetBoolean(prefs::kMetricsReportingEnabled));
+}
+
+// static
+bool MetricsServiceAccessor::IsMetricsReportingEnabledWithPrefValue(
+ bool enabled_in_prefs) {
+#if defined(GOOGLE_CHROME_BUILD)
+ // In official builds, disable metrics when reporting field trials are
+ // forced; otherwise, use the value of the user's preference to determine
+ // whether to enable metrics reporting.
+ return !base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kForceFieldTrials) &&
+ enabled_in_prefs;
+#else
+ // In non-official builds, disable metrics reporting completely.
+ return false;
+#endif // defined(GOOGLE_CHROME_BUILD)
+}
+
+// static
+bool MetricsServiceAccessor::RegisterSyntheticFieldTrial(
+ MetricsService* metrics_service,
+ const std::string& trial_name,
+ const std::string& group_name) {
+ return RegisterSyntheticFieldTrialWithNameAndGroupHash(
+ metrics_service, HashName(trial_name), HashName(group_name));
+}
+
+// static
+bool MetricsServiceAccessor::RegisterSyntheticFieldTrialWithNameHash(
+ MetricsService* metrics_service,
+ uint32_t trial_name_hash,
+ const std::string& group_name) {
+ return RegisterSyntheticFieldTrialWithNameAndGroupHash(
+ metrics_service, trial_name_hash, HashName(group_name));
+}
+
+// static
+bool MetricsServiceAccessor::RegisterSyntheticFieldTrialWithNameAndGroupHash(
+ MetricsService* metrics_service,
+ uint32_t trial_name_hash,
+ uint32_t group_name_hash) {
+ if (!metrics_service)
+ return false;
+
+ variations::SyntheticTrialGroup trial_group(trial_name_hash, group_name_hash);
+ metrics_service->RegisterSyntheticFieldTrial(trial_group);
+ return true;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_service_accessor.h b/chromium/components/metrics/metrics_service_accessor.h
new file mode 100644
index 00000000000..8d34675b5fe
--- /dev/null
+++ b/chromium/components/metrics/metrics_service_accessor.h
@@ -0,0 +1,77 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_SERVICE_ACCESSOR_H_
+#define COMPONENTS_METRICS_METRICS_SERVICE_ACCESSOR_H_
+
+#include <stdint.h>
+#include <string>
+
+#include "base/macros.h"
+
+class PrefService;
+
+namespace metrics {
+
+class MetricsService;
+
+// This class limits and documents access to metrics service helper methods.
+// These methods are protected so each user has to inherit own program-specific
+// specialization and enable access there by declaring friends.
+class MetricsServiceAccessor {
+ protected:
+ // Constructor declared as protected to enable inheritance. Descendants should
+ // disallow instantiation.
+ MetricsServiceAccessor() {}
+
+ // Returns whether metrics reporting is enabled, using the value of the
+ // kMetricsReportingEnabled pref in |pref_service| to determine whether user
+ // has enabled reporting.
+ // NOTE: This method currently does not return the correct value on ChromeOS
+ // and Android due to http://crbug.com/362192 and http://crbug.com/532084. See
+ // ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled().
+ static bool IsMetricsReportingEnabled(PrefService* pref_service);
+
+ // Returns whether metrics reporting is enabled, using the value of
+ // |enabled_in_prefs| to determine whether the user has enabled reporting.
+ // Exists because kMetricsReportingEnabled is currently not used on all
+ // platforms.
+ // TODO(gayane): Consolidate metric prefs on all platforms and eliminate this
+ // method. http://crbug.com/362192, http://crbug.com/532084
+ static bool IsMetricsReportingEnabledWithPrefValue(bool enabled_in_prefs);
+
+ // Registers a field trial name and group with |metrics_service| (if not
+ // null), to be used to annotate a UMA report with a particular configuration
+ // state. A UMA report will be annotated with this trial group if and only if
+ // all events in the report were created after the trial is registered. Only
+ // one group name may be registered at a time for a given trial name. Only the
+ // last group name that is registered for a given trial name will be recorded.
+ // The values passed in must not correspond to any real field trial in the
+ // code. Returns true on success.
+ // See the comment on MetricsService::RegisterSyntheticFieldTrial for details.
+ static bool RegisterSyntheticFieldTrial(MetricsService* metrics_service,
+ const std::string& trial_name,
+ const std::string& group_name);
+
+ // Same as RegisterSyntheticFieldTrial above, but takes in the trial name as a
+ // hash rather than computing the hash from the string.
+ static bool RegisterSyntheticFieldTrialWithNameHash(
+ MetricsService* metrics_service,
+ uint32_t trial_name_hash,
+ const std::string& group_name);
+
+ // Same as RegisterSyntheticFieldTrial above, but takes in the trial and group
+ // names as hashes rather than computing those hashes from the strings.
+ static bool RegisterSyntheticFieldTrialWithNameAndGroupHash(
+ MetricsService* metrics_service,
+ uint32_t trial_name_hash,
+ uint32_t group_name_hash);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MetricsServiceAccessor);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_SERVICE_ACCESSOR_H_
diff --git a/chromium/components/metrics/metrics_service_client.cc b/chromium/components/metrics/metrics_service_client.cc
new file mode 100644
index 00000000000..cd5b53ca802
--- /dev/null
+++ b/chromium/components/metrics/metrics_service_client.cc
@@ -0,0 +1,26 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_service_client.h"
+
+namespace metrics {
+
+base::string16 MetricsServiceClient::GetRegistryBackupKey() {
+ return base::string16();
+}
+
+bool MetricsServiceClient::IsReportingPolicyManaged() {
+ return false;
+}
+
+MetricsServiceClient::EnableMetricsDefault
+MetricsServiceClient::GetDefaultOptIn() {
+ return DEFAULT_UNKNOWN;
+}
+
+bool MetricsServiceClient::IsUMACellularUploadLogicEnabled() {
+ return false;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_service_client.h b/chromium/components/metrics/metrics_service_client.h
new file mode 100644
index 00000000000..43c5d136ca8
--- /dev/null
+++ b/chromium/components/metrics/metrics_service_client.h
@@ -0,0 +1,127 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_SERVICE_CLIENT_H_
+#define COMPONENTS_METRICS_METRICS_SERVICE_CLIENT_H_
+
+#include <stdint.h>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "components/metrics/proto/system_profile.pb.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace metrics {
+
+class MetricsLogUploader;
+class MetricsService;
+
+// An abstraction of operations that depend on the embedder's (e.g. Chrome)
+// environment.
+class MetricsServiceClient {
+ public:
+ // Default value of the enable metrics recording. This relates to the state of
+ // the enable checkbox shown on first-run. This enum is used to store values
+ // in a pref, and shouldn't be renumbered.
+ enum EnableMetricsDefault {
+ // We only record the value during first-run. The default of existing
+ // installs is considered unknown.
+ DEFAULT_UNKNOWN,
+ // The first-run checkbox was unchecked by default.
+ OPT_IN,
+ // The first-run checkbox was checked by default.
+ OPT_OUT,
+ };
+
+ virtual ~MetricsServiceClient() {}
+
+ // Returns the MetricsService instance that this client is associated with.
+ // With the exception of testing contexts, the returned instance must be valid
+ // for the lifetime of this object (typically, the embedder's client
+ // implementation will own the MetricsService instance being returned).
+ virtual MetricsService* GetMetricsService() = 0;
+
+ // Registers the client id with other services (e.g. crash reporting), called
+ // when metrics recording gets enabled.
+ virtual void SetMetricsClientId(const std::string& client_id) = 0;
+
+ // Notifies the client that recording is disabled, so that other services
+ // (such as crash reporting) can clear any association with metrics.
+ virtual void OnRecordingDisabled() = 0;
+
+ // Whether there's an "off the record" (aka "Incognito") session active.
+ virtual bool IsOffTheRecordSessionActive() = 0;
+
+ // Returns the product value to use in uploaded reports, which will be used to
+ // set the ChromeUserMetricsExtension.product field. See comments on that
+ // field on why it's an int32_t rather than an enum.
+ virtual int32_t GetProduct() = 0;
+
+ // Returns the current application locale (e.g. "en-US").
+ virtual std::string GetApplicationLocale() = 0;
+
+ // Retrieves the brand code string associated with the install, returning
+ // false if no brand code is available.
+ virtual bool GetBrand(std::string* brand_code) = 0;
+
+ // Returns the release channel (e.g. stable, beta, etc) of the application.
+ virtual SystemProfileProto::Channel GetChannel() = 0;
+
+ // Returns the version of the application as a string.
+ virtual std::string GetVersionString() = 0;
+
+ // Called by the metrics service when a log has been uploaded.
+ virtual void OnLogUploadComplete() = 0;
+
+ // Gathers metrics that will be filled into the system profile protobuf,
+ // calling |done_callback| when complete.
+ virtual void InitializeSystemProfileMetrics(
+ const base::Closure& done_callback) = 0;
+
+ // Called prior to a metrics log being closed, allowing the client to collect
+ // extra histograms that will go in that log. Asynchronous API - the client
+ // implementation should call |done_callback| when complete.
+ virtual void CollectFinalMetricsForLog(
+ const base::Closure& done_callback) = 0;
+
+ // Creates a MetricsLogUploader with the specified parameters (see comments on
+ // MetricsLogUploader for details).
+ virtual scoped_ptr<MetricsLogUploader> CreateUploader(
+ const base::Callback<void(int)>& on_upload_complete) = 0;
+
+ // Returns the standard interval between upload attempts.
+ virtual base::TimeDelta GetStandardUploadInterval() = 0;
+
+ // Returns the name of a key under HKEY_CURRENT_USER that can be used to store
+ // backups of metrics data. Unused except on Windows.
+ virtual base::string16 GetRegistryBackupKey();
+
+ // Called on plugin loading errors.
+ virtual void OnPluginLoadingError(const base::FilePath& plugin_path) {}
+
+ // Called on renderer crashes in some embedders (e.g., those that do not use
+ // //content and thus do not have //content's notification system available
+ // as a mechanism for observing renderer crashes).
+ virtual void OnRendererProcessCrash() {}
+
+ // Returns whether metrics reporting is managed by policy.
+ virtual bool IsReportingPolicyManaged();
+
+ // Gets information about the default value for the enable metrics reporting
+ // checkbox shown during first-run.
+ virtual EnableMetricsDefault GetDefaultOptIn();
+
+ // Returns whether cellular logic is enabled for metrics reporting.
+ virtual bool IsUMACellularUploadLogicEnabled();
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_SERVICE_CLIENT_H_
diff --git a/chromium/components/metrics/metrics_service_unittest.cc b/chromium/components/metrics/metrics_service_unittest.cc
new file mode 100644
index 00000000000..4a8aea770e9
--- /dev/null
+++ b/chromium/components/metrics/metrics_service_unittest.cc
@@ -0,0 +1,413 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_service.h"
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/threading/platform_thread.h"
+#include "components/metrics/client_info.h"
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_state_manager.h"
+#include "components/metrics/test_metrics_provider.h"
+#include "components/metrics/test_metrics_service_client.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/variations/metrics_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace metrics {
+
+namespace {
+
+void StoreNoClientInfoBackup(const ClientInfo& /* client_info */) {
+}
+
+scoped_ptr<ClientInfo> ReturnNoBackup() {
+ return scoped_ptr<ClientInfo>();
+}
+
+class TestMetricsService : public MetricsService {
+ public:
+ TestMetricsService(MetricsStateManager* state_manager,
+ MetricsServiceClient* client,
+ PrefService* local_state)
+ : MetricsService(state_manager, client, local_state) {}
+ ~TestMetricsService() override {}
+
+ using MetricsService::log_manager;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestMetricsService);
+};
+
+class TestMetricsLog : public MetricsLog {
+ public:
+ TestMetricsLog(const std::string& client_id,
+ int session_id,
+ MetricsServiceClient* client,
+ PrefService* local_state)
+ : MetricsLog(client_id,
+ session_id,
+ MetricsLog::ONGOING_LOG,
+ client,
+ local_state) {}
+
+ ~TestMetricsLog() override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestMetricsLog);
+};
+
+class MetricsServiceTest : public testing::Test {
+ public:
+ MetricsServiceTest() : is_metrics_reporting_enabled_(false) {
+ MetricsService::RegisterPrefs(testing_local_state_.registry());
+ metrics_state_manager_ = MetricsStateManager::Create(
+ GetLocalState(),
+ base::Bind(&MetricsServiceTest::is_metrics_reporting_enabled,
+ base::Unretained(this)),
+ base::Bind(&StoreNoClientInfoBackup),
+ base::Bind(&ReturnNoBackup));
+ }
+
+ ~MetricsServiceTest() override {
+ MetricsService::SetExecutionPhase(MetricsService::UNINITIALIZED_PHASE,
+ GetLocalState());
+ }
+
+ MetricsStateManager* GetMetricsStateManager() {
+ return metrics_state_manager_.get();
+ }
+
+ PrefService* GetLocalState() { return &testing_local_state_; }
+
+ // Sets metrics reporting as enabled for testing.
+ void EnableMetricsReporting() {
+ is_metrics_reporting_enabled_ = true;
+ }
+
+ // Waits until base::TimeTicks::Now() no longer equals |value|. This should
+ // take between 1-15ms per the documented resolution of base::TimeTicks.
+ void WaitUntilTimeChanges(const base::TimeTicks& value) {
+ while (base::TimeTicks::Now() == value) {
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1));
+ }
+ }
+
+ // Returns true if there is a synthetic trial in the given vector that matches
+ // the given trial name and trial group; returns false otherwise.
+ bool HasSyntheticTrial(
+ const std::vector<variations::ActiveGroupId>& synthetic_trials,
+ const std::string& trial_name,
+ const std::string& trial_group) {
+ uint32_t trial_name_hash = HashName(trial_name);
+ uint32_t trial_group_hash = HashName(trial_group);
+ for (const variations::ActiveGroupId& trial : synthetic_trials) {
+ if (trial.name == trial_name_hash && trial.group == trial_group_hash)
+ return true;
+ }
+ return false;
+ }
+
+ // Finds a histogram with the specified |name_hash| in |histograms|.
+ const base::HistogramBase* FindHistogram(
+ const base::StatisticsRecorder::Histograms& histograms,
+ uint64_t name_hash) {
+ for (const base::HistogramBase* histogram : histograms) {
+ if (name_hash == base::HashMetricName(histogram->histogram_name()))
+ return histogram;
+ }
+ return nullptr;
+ }
+
+ // Checks whether |uma_log| contains any histograms that are not flagged
+ // with kUmaStabilityHistogramFlag. Stability logs should only contain such
+ // histograms.
+ void CheckForNonStabilityHistograms(
+ const ChromeUserMetricsExtension& uma_log) {
+ const int kStabilityFlags = base::HistogramBase::kUmaStabilityHistogramFlag;
+ base::StatisticsRecorder::Histograms histograms;
+ base::StatisticsRecorder::GetHistograms(&histograms);
+ for (int i = 0; i < uma_log.histogram_event_size(); ++i) {
+ const uint64_t hash = uma_log.histogram_event(i).name_hash();
+
+ const base::HistogramBase* histogram = FindHistogram(histograms, hash);
+ EXPECT_TRUE(histogram) << hash;
+
+ EXPECT_EQ(kStabilityFlags, histogram->flags() & kStabilityFlags) << hash;
+ }
+ }
+
+ private:
+ bool is_metrics_reporting_enabled() const {
+ return is_metrics_reporting_enabled_;
+ }
+
+ bool is_metrics_reporting_enabled_;
+ TestingPrefServiceSimple testing_local_state_;
+ scoped_ptr<MetricsStateManager> metrics_state_manager_;
+ base::MessageLoop message_loop;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsServiceTest);
+};
+
+} // namespace
+
+TEST_F(MetricsServiceTest, InitialStabilityLogAfterCleanShutDown) {
+ EnableMetricsReporting();
+ GetLocalState()->SetBoolean(prefs::kStabilityExitedCleanly, true);
+
+ TestMetricsServiceClient client;
+ TestMetricsService service(
+ GetMetricsStateManager(), &client, GetLocalState());
+
+ TestMetricsProvider* test_provider = new TestMetricsProvider();
+ service.RegisterMetricsProvider(scoped_ptr<MetricsProvider>(test_provider));
+
+ service.InitializeMetricsRecordingState();
+ // No initial stability log should be generated.
+ EXPECT_FALSE(service.log_manager()->has_unsent_logs());
+ EXPECT_FALSE(service.log_manager()->has_staged_log());
+
+ // The test provider should not have been called upon to provide initial
+ // stability nor regular stability metrics.
+ EXPECT_FALSE(test_provider->provide_initial_stability_metrics_called());
+ EXPECT_FALSE(test_provider->provide_stability_metrics_called());
+}
+
+TEST_F(MetricsServiceTest, InitialStabilityLogAtProviderRequest) {
+ EnableMetricsReporting();
+
+ // Save an existing system profile to prefs, to correspond to what would be
+ // saved from a previous session.
+ TestMetricsServiceClient client;
+ TestMetricsLog log("client", 1, &client, GetLocalState());
+ log.RecordEnvironment(std::vector<MetricsProvider*>(),
+ std::vector<variations::ActiveGroupId>(), 0, 0);
+
+ // Record stability build time and version from previous session, so that
+ // stability metrics (including exited cleanly flag) won't be cleared.
+ GetLocalState()->SetInt64(prefs::kStabilityStatsBuildTime,
+ MetricsLog::GetBuildTime());
+ GetLocalState()->SetString(prefs::kStabilityStatsVersion,
+ client.GetVersionString());
+
+ // Set the clean exit flag, as that will otherwise cause a stabilty
+ // log to be produced, irrespective provider requests.
+ GetLocalState()->SetBoolean(prefs::kStabilityExitedCleanly, true);
+
+ TestMetricsService service(
+ GetMetricsStateManager(), &client, GetLocalState());
+ // Add a metrics provider that requests a stability log.
+ TestMetricsProvider* test_provider = new TestMetricsProvider();
+ test_provider->set_has_initial_stability_metrics(true);
+ service.RegisterMetricsProvider(
+ scoped_ptr<MetricsProvider>(test_provider));
+
+ service.InitializeMetricsRecordingState();
+
+ // The initial stability log should be generated and persisted in unsent logs.
+ MetricsLogManager* log_manager = service.log_manager();
+ EXPECT_TRUE(log_manager->has_unsent_logs());
+ EXPECT_FALSE(log_manager->has_staged_log());
+
+ // The test provider should have been called upon to provide initial
+ // stability and regular stability metrics.
+ EXPECT_TRUE(test_provider->provide_initial_stability_metrics_called());
+ EXPECT_TRUE(test_provider->provide_stability_metrics_called());
+
+ // Stage the log and retrieve it.
+ log_manager->StageNextLogForUpload();
+ EXPECT_TRUE(log_manager->has_staged_log());
+
+ std::string uncompressed_log;
+ EXPECT_TRUE(compression::GzipUncompress(log_manager->staged_log(),
+ &uncompressed_log));
+
+ ChromeUserMetricsExtension uma_log;
+ EXPECT_TRUE(uma_log.ParseFromString(uncompressed_log));
+
+ EXPECT_TRUE(uma_log.has_client_id());
+ EXPECT_TRUE(uma_log.has_session_id());
+ EXPECT_TRUE(uma_log.has_system_profile());
+ EXPECT_EQ(0, uma_log.user_action_event_size());
+ EXPECT_EQ(0, uma_log.omnibox_event_size());
+ EXPECT_EQ(0, uma_log.profiler_event_size());
+ EXPECT_EQ(0, uma_log.perf_data_size());
+ CheckForNonStabilityHistograms(uma_log);
+
+ // As there wasn't an unclean shutdown, this log has zero crash count.
+ EXPECT_EQ(0, uma_log.system_profile().stability().crash_count());
+}
+
+TEST_F(MetricsServiceTest, InitialStabilityLogAfterCrash) {
+ EnableMetricsReporting();
+ GetLocalState()->ClearPref(prefs::kStabilityExitedCleanly);
+
+ // Set up prefs to simulate restarting after a crash.
+
+ // Save an existing system profile to prefs, to correspond to what would be
+ // saved from a previous session.
+ TestMetricsServiceClient client;
+ TestMetricsLog log("client", 1, &client, GetLocalState());
+ log.RecordEnvironment(std::vector<MetricsProvider*>(),
+ std::vector<variations::ActiveGroupId>(), 0, 0);
+
+ // Record stability build time and version from previous session, so that
+ // stability metrics (including exited cleanly flag) won't be cleared.
+ GetLocalState()->SetInt64(prefs::kStabilityStatsBuildTime,
+ MetricsLog::GetBuildTime());
+ GetLocalState()->SetString(prefs::kStabilityStatsVersion,
+ client.GetVersionString());
+
+ GetLocalState()->SetBoolean(prefs::kStabilityExitedCleanly, false);
+
+ TestMetricsService service(
+ GetMetricsStateManager(), &client, GetLocalState());
+ // Add a provider.
+ TestMetricsProvider* test_provider = new TestMetricsProvider();
+ service.RegisterMetricsProvider(scoped_ptr<MetricsProvider>(test_provider));
+ service.InitializeMetricsRecordingState();
+
+ // The initial stability log should be generated and persisted in unsent logs.
+ MetricsLogManager* log_manager = service.log_manager();
+ EXPECT_TRUE(log_manager->has_unsent_logs());
+ EXPECT_FALSE(log_manager->has_staged_log());
+
+ // The test provider should have been called upon to provide initial
+ // stability and regular stability metrics.
+ EXPECT_TRUE(test_provider->provide_initial_stability_metrics_called());
+ EXPECT_TRUE(test_provider->provide_stability_metrics_called());
+
+ // Stage the log and retrieve it.
+ log_manager->StageNextLogForUpload();
+ EXPECT_TRUE(log_manager->has_staged_log());
+
+ std::string uncompressed_log;
+ EXPECT_TRUE(compression::GzipUncompress(log_manager->staged_log(),
+ &uncompressed_log));
+
+ ChromeUserMetricsExtension uma_log;
+ EXPECT_TRUE(uma_log.ParseFromString(uncompressed_log));
+
+ EXPECT_TRUE(uma_log.has_client_id());
+ EXPECT_TRUE(uma_log.has_session_id());
+ EXPECT_TRUE(uma_log.has_system_profile());
+ EXPECT_EQ(0, uma_log.user_action_event_size());
+ EXPECT_EQ(0, uma_log.omnibox_event_size());
+ EXPECT_EQ(0, uma_log.profiler_event_size());
+ EXPECT_EQ(0, uma_log.perf_data_size());
+ CheckForNonStabilityHistograms(uma_log);
+
+ EXPECT_EQ(1, uma_log.system_profile().stability().crash_count());
+}
+
+TEST_F(MetricsServiceTest, RegisterSyntheticTrial) {
+ TestMetricsServiceClient client;
+ MetricsService service(GetMetricsStateManager(), &client, GetLocalState());
+
+ // Add two synthetic trials and confirm that they show up in the list.
+ variations::SyntheticTrialGroup trial1(HashName("TestTrial1"),
+ HashName("Group1"));
+ service.RegisterSyntheticFieldTrial(trial1);
+
+ variations::SyntheticTrialGroup trial2(HashName("TestTrial2"),
+ HashName("Group2"));
+ service.RegisterSyntheticFieldTrial(trial2);
+ // Ensure that time has advanced by at least a tick before proceeding.
+ WaitUntilTimeChanges(base::TimeTicks::Now());
+
+ service.log_manager_.BeginLoggingWithLog(scoped_ptr<MetricsLog>(
+ new MetricsLog("clientID",
+ 1,
+ MetricsLog::INITIAL_STABILITY_LOG,
+ &client,
+ GetLocalState())));
+ // Save the time when the log was started (it's okay for this to be greater
+ // than the time recorded by the above call since it's used to ensure the
+ // value changes).
+ const base::TimeTicks begin_log_time = base::TimeTicks::Now();
+
+ std::vector<variations::ActiveGroupId> synthetic_trials;
+ service.GetSyntheticFieldTrialsOlderThan(base::TimeTicks::Now(),
+ &synthetic_trials);
+ EXPECT_EQ(2U, synthetic_trials.size());
+ EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial1", "Group1"));
+ EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial2", "Group2"));
+
+ // Ensure that time has advanced by at least a tick before proceeding.
+ WaitUntilTimeChanges(begin_log_time);
+
+ // Change the group for the first trial after the log started.
+ variations::SyntheticTrialGroup trial3(HashName("TestTrial1"),
+ HashName("Group2"));
+ service.RegisterSyntheticFieldTrial(trial3);
+ service.GetSyntheticFieldTrialsOlderThan(begin_log_time, &synthetic_trials);
+ EXPECT_EQ(1U, synthetic_trials.size());
+ EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial2", "Group2"));
+
+ // Add a new trial after the log started and confirm that it doesn't show up.
+ variations::SyntheticTrialGroup trial4(HashName("TestTrial3"),
+ HashName("Group3"));
+ service.RegisterSyntheticFieldTrial(trial4);
+ service.GetSyntheticFieldTrialsOlderThan(begin_log_time, &synthetic_trials);
+ EXPECT_EQ(1U, synthetic_trials.size());
+ EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial2", "Group2"));
+
+ // Ensure that time has advanced by at least a tick before proceeding.
+ WaitUntilTimeChanges(base::TimeTicks::Now());
+
+ // Start a new log and ensure all three trials appear in it.
+ service.log_manager_.FinishCurrentLog();
+ service.log_manager_.BeginLoggingWithLog(
+ scoped_ptr<MetricsLog>(new MetricsLog(
+ "clientID", 1, MetricsLog::ONGOING_LOG, &client, GetLocalState())));
+ service.GetSyntheticFieldTrialsOlderThan(
+ service.log_manager_.current_log()->creation_time(), &synthetic_trials);
+ EXPECT_EQ(3U, synthetic_trials.size());
+ EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial1", "Group2"));
+ EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial2", "Group2"));
+ EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial3", "Group3"));
+ service.log_manager_.FinishCurrentLog();
+}
+
+TEST_F(MetricsServiceTest,
+ MetricsProviderOnRecordingDisabledCalledOnInitialStop) {
+ TestMetricsServiceClient client;
+ TestMetricsService service(
+ GetMetricsStateManager(), &client, GetLocalState());
+
+ TestMetricsProvider* test_provider = new TestMetricsProvider();
+ service.RegisterMetricsProvider(scoped_ptr<MetricsProvider>(test_provider));
+
+ service.InitializeMetricsRecordingState();
+ service.Stop();
+
+ EXPECT_TRUE(test_provider->on_recording_disabled_called());
+}
+
+TEST_F(MetricsServiceTest, MetricsProvidersInitialized) {
+ TestMetricsServiceClient client;
+ TestMetricsService service(
+ GetMetricsStateManager(), &client, GetLocalState());
+
+ TestMetricsProvider* test_provider = new TestMetricsProvider();
+ service.RegisterMetricsProvider(scoped_ptr<MetricsProvider>(test_provider));
+
+ service.InitializeMetricsRecordingState();
+
+ EXPECT_TRUE(test_provider->init_called());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_state_manager.cc b/chromium/components/metrics/metrics_state_manager.cc
new file mode 100644
index 00000000000..89817aa5992
--- /dev/null
+++ b/chromium/components/metrics/metrics_state_manager.cc
@@ -0,0 +1,314 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_state_manager.h"
+
+#include <stddef.h>
+
+#include "base/command_line.h"
+#include "base/guid.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/metrics/cloned_install_detector.h"
+#include "components/metrics/machine_id_provider.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_switches.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/caching_permuted_entropy_provider.h"
+
+namespace metrics {
+
+namespace {
+
+// The argument used to generate a non-identifying entropy source. We want no
+// more than 13 bits of entropy, so use this max to return a number in the range
+// [0, 7999] as the entropy source (12.97 bits of entropy).
+const int kMaxLowEntropySize = 8000;
+
+// Default prefs value for prefs::kMetricsLowEntropySource to indicate that
+// the value has not yet been set.
+const int kLowEntropySourceNotSet = -1;
+
+// Generates a new non-identifying entropy source used to seed persistent
+// activities.
+int GenerateLowEntropySource() {
+ return base::RandInt(0, kMaxLowEntropySize - 1);
+}
+
+} // namespace
+
+// static
+bool MetricsStateManager::instance_exists_ = false;
+
+MetricsStateManager::MetricsStateManager(
+ PrefService* local_state,
+ const base::Callback<bool(void)>& is_reporting_enabled_callback,
+ const StoreClientInfoCallback& store_client_info,
+ const LoadClientInfoCallback& retrieve_client_info)
+ : local_state_(local_state),
+ is_reporting_enabled_callback_(is_reporting_enabled_callback),
+ store_client_info_(store_client_info),
+ load_client_info_(retrieve_client_info),
+ low_entropy_source_(kLowEntropySourceNotSet),
+ entropy_source_returned_(ENTROPY_SOURCE_NONE) {
+ ResetMetricsIDsIfNecessary();
+ if (IsMetricsReportingEnabled())
+ ForceClientIdCreation();
+
+ DCHECK(!instance_exists_);
+ instance_exists_ = true;
+}
+
+MetricsStateManager::~MetricsStateManager() {
+ DCHECK(instance_exists_);
+ instance_exists_ = false;
+}
+
+bool MetricsStateManager::IsMetricsReportingEnabled() {
+ return is_reporting_enabled_callback_.Run();
+}
+
+void MetricsStateManager::ForceClientIdCreation() {
+ if (!client_id_.empty())
+ return;
+
+ client_id_ = local_state_->GetString(prefs::kMetricsClientID);
+ if (!client_id_.empty()) {
+ // It is technically sufficient to only save a backup of the client id when
+ // it is initially generated below, but since the backup was only introduced
+ // in M38, seed it explicitly from here for some time.
+ BackUpCurrentClientInfo();
+ return;
+ }
+
+ const scoped_ptr<ClientInfo> client_info_backup =
+ LoadClientInfoAndMaybeMigrate();
+ if (client_info_backup) {
+ client_id_ = client_info_backup->client_id;
+
+ const base::Time now = base::Time::Now();
+
+ // Save the recovered client id and also try to reinstantiate the backup
+ // values for the dates corresponding with that client id in order to avoid
+ // weird scenarios where we could report an old client id with a recent
+ // install date.
+ local_state_->SetString(prefs::kMetricsClientID, client_id_);
+ local_state_->SetInt64(prefs::kInstallDate,
+ client_info_backup->installation_date != 0
+ ? client_info_backup->installation_date
+ : now.ToTimeT());
+ local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp,
+ client_info_backup->reporting_enabled_date != 0
+ ? client_info_backup->reporting_enabled_date
+ : now.ToTimeT());
+
+ base::TimeDelta recovered_installation_age;
+ if (client_info_backup->installation_date != 0) {
+ recovered_installation_age =
+ now - base::Time::FromTimeT(client_info_backup->installation_date);
+ }
+ UMA_HISTOGRAM_COUNTS_10000("UMA.ClientIdBackupRecoveredWithAge",
+ recovered_installation_age.InHours());
+
+ // Flush the backup back to persistent storage in case we re-generated
+ // missing data above.
+ BackUpCurrentClientInfo();
+ return;
+ }
+
+ // Failing attempts at getting an existing client ID, generate a new one.
+ client_id_ = base::GenerateGUID();
+ local_state_->SetString(prefs::kMetricsClientID, client_id_);
+
+ // Record the timestamp of when the user opted in to UMA.
+ local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp,
+ base::Time::Now().ToTimeT());
+
+ BackUpCurrentClientInfo();
+}
+
+void MetricsStateManager::CheckForClonedInstall(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+ DCHECK(!cloned_install_detector_);
+
+ MachineIdProvider* provider = MachineIdProvider::CreateInstance();
+ if (!provider)
+ return;
+
+ cloned_install_detector_.reset(new ClonedInstallDetector(provider));
+ cloned_install_detector_->CheckForClonedInstall(local_state_, task_runner);
+}
+
+scoped_ptr<const base::FieldTrial::EntropyProvider>
+MetricsStateManager::CreateEntropyProvider() {
+ // For metrics reporting-enabled users, we combine the client ID and low
+ // entropy source to get the final entropy source. Otherwise, only use the low
+ // entropy source.
+ // This has two useful properties:
+ // 1) It makes the entropy source less identifiable for parties that do not
+ // know the low entropy source.
+ // 2) It makes the final entropy source resettable.
+ const int low_entropy_source_value = GetLowEntropySource();
+ UMA_HISTOGRAM_SPARSE_SLOWLY("UMA.LowEntropySourceValue",
+ low_entropy_source_value);
+ if (IsMetricsReportingEnabled()) {
+ UpdateEntropySourceReturnedValue(ENTROPY_SOURCE_HIGH);
+ const std::string high_entropy_source =
+ client_id_ + base::IntToString(low_entropy_source_value);
+ return scoped_ptr<const base::FieldTrial::EntropyProvider>(
+ new SHA1EntropyProvider(high_entropy_source));
+ }
+
+ UpdateEntropySourceReturnedValue(ENTROPY_SOURCE_LOW);
+#if defined(OS_ANDROID) || defined(OS_IOS)
+ return scoped_ptr<const base::FieldTrial::EntropyProvider>(
+ new CachingPermutedEntropyProvider(local_state_,
+ low_entropy_source_value,
+ kMaxLowEntropySize));
+#else
+ return scoped_ptr<const base::FieldTrial::EntropyProvider>(
+ new PermutedEntropyProvider(low_entropy_source_value,
+ kMaxLowEntropySize));
+#endif
+}
+
+// static
+scoped_ptr<MetricsStateManager> MetricsStateManager::Create(
+ PrefService* local_state,
+ const base::Callback<bool(void)>& is_reporting_enabled_callback,
+ const StoreClientInfoCallback& store_client_info,
+ const LoadClientInfoCallback& retrieve_client_info) {
+ scoped_ptr<MetricsStateManager> result;
+ // Note: |instance_exists_| is updated in the constructor and destructor.
+ if (!instance_exists_) {
+ result.reset(new MetricsStateManager(local_state,
+ is_reporting_enabled_callback,
+ store_client_info,
+ retrieve_client_info));
+ }
+ return result;
+}
+
+// static
+void MetricsStateManager::RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterBooleanPref(prefs::kMetricsResetIds, false);
+ // registry->RegisterIntegerPref(prefs::kMetricsDefaultOptIn,
+ // DEFAULT_UNKNOWN);
+ registry->RegisterStringPref(prefs::kMetricsClientID, std::string());
+ registry->RegisterInt64Pref(prefs::kMetricsReportingEnabledTimestamp, 0);
+ registry->RegisterIntegerPref(prefs::kMetricsLowEntropySource,
+ kLowEntropySourceNotSet);
+
+ ClonedInstallDetector::RegisterPrefs(registry);
+ CachingPermutedEntropyProvider::RegisterPrefs(registry);
+}
+
+void MetricsStateManager::BackUpCurrentClientInfo() {
+ ClientInfo client_info;
+ client_info.client_id = client_id_;
+ client_info.installation_date = local_state_->GetInt64(prefs::kInstallDate);
+ client_info.reporting_enabled_date =
+ local_state_->GetInt64(prefs::kMetricsReportingEnabledTimestamp);
+ store_client_info_.Run(client_info);
+}
+
+scoped_ptr<ClientInfo> MetricsStateManager::LoadClientInfoAndMaybeMigrate() {
+ scoped_ptr<ClientInfo> client_info = load_client_info_.Run();
+
+ // Prior to 2014-07, the client ID was stripped of its dashes before being
+ // saved. Migrate back to a proper GUID if this is the case. This migration
+ // code can be removed in M41+.
+ const size_t kGUIDLengthWithoutDashes = 32U;
+ if (client_info &&
+ client_info->client_id.length() == kGUIDLengthWithoutDashes) {
+ DCHECK(client_info->client_id.find('-') == std::string::npos);
+
+ std::string client_id_with_dashes;
+ client_id_with_dashes.reserve(kGUIDLengthWithoutDashes + 4U);
+ std::string::const_iterator client_id_it = client_info->client_id.begin();
+ for (size_t i = 0; i < kGUIDLengthWithoutDashes + 4U; ++i) {
+ if (i == 8U || i == 13U || i == 18U || i == 23U) {
+ client_id_with_dashes.push_back('-');
+ } else {
+ client_id_with_dashes.push_back(*client_id_it);
+ ++client_id_it;
+ }
+ }
+ DCHECK(client_id_it == client_info->client_id.end());
+ client_info->client_id.assign(client_id_with_dashes);
+ }
+
+ // The GUID retrieved (and possibly fixed above) should be valid unless
+ // retrieval failed.
+ DCHECK(!client_info || base::IsValidGUID(client_info->client_id));
+
+ return client_info;
+}
+
+int MetricsStateManager::GetLowEntropySource() {
+ UpdateLowEntropySource();
+ return low_entropy_source_;
+}
+
+void MetricsStateManager::UpdateLowEntropySource() {
+ // Note that the default value for the low entropy source and the default pref
+ // value are both kLowEntropySourceNotSet, which is used to identify if the
+ // value has been set or not.
+ if (low_entropy_source_ != kLowEntropySourceNotSet)
+ return;
+
+ const base::CommandLine* command_line(base::CommandLine::ForCurrentProcess());
+ // Only try to load the value from prefs if the user did not request a
+ // reset.
+ // Otherwise, skip to generating a new value.
+ if (!command_line->HasSwitch(switches::kResetVariationState)) {
+ int value = local_state_->GetInteger(prefs::kMetricsLowEntropySource);
+ // If the value is outside the [0, kMaxLowEntropySize) range, re-generate
+ // it below.
+ if (value >= 0 && value < kMaxLowEntropySize) {
+ low_entropy_source_ = value;
+ return;
+ }
+ }
+
+ low_entropy_source_ = GenerateLowEntropySource();
+ local_state_->SetInteger(prefs::kMetricsLowEntropySource,
+ low_entropy_source_);
+ CachingPermutedEntropyProvider::ClearCache(local_state_);
+}
+
+void MetricsStateManager::UpdateEntropySourceReturnedValue(
+ EntropySourceType type) {
+ if (entropy_source_returned_ != ENTROPY_SOURCE_NONE)
+ return;
+
+ entropy_source_returned_ = type;
+ UMA_HISTOGRAM_ENUMERATION("UMA.EntropySourceType", type,
+ ENTROPY_SOURCE_ENUM_SIZE);
+}
+
+void MetricsStateManager::ResetMetricsIDsIfNecessary() {
+ if (!local_state_->GetBoolean(prefs::kMetricsResetIds))
+ return;
+
+ UMA_HISTOGRAM_BOOLEAN("UMA.MetricsIDsReset", true);
+
+ DCHECK(client_id_.empty());
+ DCHECK_EQ(kLowEntropySourceNotSet, low_entropy_source_);
+
+ local_state_->ClearPref(prefs::kMetricsClientID);
+ local_state_->ClearPref(prefs::kMetricsLowEntropySource);
+ local_state_->ClearPref(prefs::kMetricsResetIds);
+
+ // Also clear the backed up client info.
+ store_client_info_.Run(ClientInfo());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_state_manager.h b/chromium/components/metrics/metrics_state_manager.h
new file mode 100644
index 00000000000..cbf5bf71b6b
--- /dev/null
+++ b/chromium/components/metrics/metrics_state_manager.h
@@ -0,0 +1,173 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_STATE_MANAGER_H_
+#define COMPONENTS_METRICS_METRICS_STATE_MANAGER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/field_trial.h"
+#include "components/metrics/client_info.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace metrics {
+
+class ClonedInstallDetector;
+
+// Responsible for managing MetricsService state prefs, specifically the UMA
+// client id and low entropy source. Code outside the metrics directory should
+// not be instantiating or using this class directly.
+class MetricsStateManager {
+ public:
+ // A callback that can be invoked to store client info to persistent storage.
+ // Storing an empty client_id will resulted in the backup being voided.
+ typedef base::Callback<void(const ClientInfo& client_info)>
+ StoreClientInfoCallback;
+
+ // A callback that can be invoked to load client info stored through the
+ // StoreClientInfoCallback.
+ typedef base::Callback<scoped_ptr<ClientInfo>(void)> LoadClientInfoCallback;
+
+ virtual ~MetricsStateManager();
+
+ // Returns true if the user opted in to sending metric reports.
+ bool IsMetricsReportingEnabled();
+
+ // Returns the client ID for this client, or the empty string if the user is
+ // not opted in to metrics reporting.
+ const std::string& client_id() const { return client_id_; }
+
+ // Forces the client ID to be generated. This is useful in case it's needed
+ // before recording.
+ void ForceClientIdCreation();
+
+ // Checks if this install was cloned or imaged from another machine. If a
+ // clone is detected, resets the client id and low entropy source. This
+ // should not be called more than once.
+ void CheckForClonedInstall(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+
+ // Returns the preferred entropy provider used to seed persistent activities
+ // based on whether or not metrics reporting is permitted on this client.
+ //
+ // If metrics reporting is enabled, this method returns an entropy provider
+ // that has a high source of entropy, partially based on the client ID.
+ // Otherwise, it returns an entropy provider that is based on a low entropy
+ // source.
+ scoped_ptr<const base::FieldTrial::EntropyProvider> CreateEntropyProvider();
+
+ // Creates the MetricsStateManager, enforcing that only a single instance
+ // of the class exists at a time. Returns NULL if an instance exists already.
+ static scoped_ptr<MetricsStateManager> Create(
+ PrefService* local_state,
+ const base::Callback<bool(void)>& is_reporting_enabled_callback,
+ const StoreClientInfoCallback& store_client_info,
+ const LoadClientInfoCallback& load_client_info);
+
+ // Registers local state prefs used by this class.
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, EntropySourceUsed_Low);
+ FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, EntropySourceUsed_High);
+ FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, LowEntropySource0NotReset);
+ FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest,
+ PermutedEntropyCacheClearedWhenLowEntropyReset);
+ FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, ResetMetricsIDs);
+
+ // Designates which entropy source was returned from this class.
+ // This is used for testing to validate that we return the correct source
+ // depending on the state of the service.
+ enum EntropySourceType {
+ ENTROPY_SOURCE_NONE,
+ ENTROPY_SOURCE_LOW,
+ ENTROPY_SOURCE_HIGH,
+ ENTROPY_SOURCE_ENUM_SIZE,
+ };
+
+ // Creates the MetricsStateManager with the given |local_state|. Calls
+ // |is_reporting_enabled_callback| to query whether metrics reporting is
+ // enabled. Clients should instead use Create(), which enforces that a single
+ // instance of this class be alive at any given time.
+ // |store_client_info| should back up client info to persistent storage such
+ // that it is later retrievable by |load_client_info|.
+ MetricsStateManager(
+ PrefService* local_state,
+ const base::Callback<bool(void)>& is_reporting_enabled_callback,
+ const StoreClientInfoCallback& store_client_info,
+ const LoadClientInfoCallback& load_client_info);
+
+ // Backs up the current client info via |store_client_info_|.
+ void BackUpCurrentClientInfo();
+
+ // Loads the client info via |load_client_info_| and potentially migrates it
+ // before returning it if it comes back in its old form.
+ scoped_ptr<ClientInfo> LoadClientInfoAndMaybeMigrate();
+
+ // Returns the low entropy source for this client. This is a random value
+ // that is non-identifying amongst browser clients. This method will
+ // generate the entropy source value if it has not been called before.
+ int GetLowEntropySource();
+
+ // Generates the low entropy source value for this client if it is not
+ // already set.
+ void UpdateLowEntropySource();
+
+ // Updates |entropy_source_returned_| with |type| iff the current value is
+ // ENTROPY_SOURCE_NONE and logs the new value in a histogram.
+ void UpdateEntropySourceReturnedValue(EntropySourceType type);
+
+ // Returns the first entropy source that was returned by this service since
+ // start up, or NONE if neither was returned yet. This is exposed for testing
+ // only.
+ EntropySourceType entropy_source_returned() const {
+ return entropy_source_returned_;
+ }
+
+ // Reset the client id and low entropy source if the kMetricsResetMetricIDs
+ // pref is true.
+ void ResetMetricsIDsIfNecessary();
+
+ // Whether an instance of this class exists. Used to enforce that there aren't
+ // multiple instances of this class at a given time.
+ static bool instance_exists_;
+
+ // Weak pointer to the local state prefs store.
+ PrefService* const local_state_;
+
+ // A callback run by this MetricsStateManager to know whether reporting is
+ // enabled.
+ const base::Callback<bool(void)> is_reporting_enabled_callback_;
+
+ // A callback run during client id creation so this MetricsStateManager can
+ // store a backup of the newly generated ID.
+ const StoreClientInfoCallback store_client_info_;
+
+ // A callback run if this MetricsStateManager can't get the client id from
+ // its typical location and wants to attempt loading it from this backup.
+ const LoadClientInfoCallback load_client_info_;
+
+ // The identifier that's sent to the server with the log reports.
+ std::string client_id_;
+
+ // The non-identifying low entropy source value.
+ int low_entropy_source_;
+
+ // The last entropy source returned by this service, used for testing.
+ EntropySourceType entropy_source_returned_;
+
+ scoped_ptr<ClonedInstallDetector> cloned_install_detector_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsStateManager);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_STATE_MANAGER_H_
diff --git a/chromium/components/metrics/metrics_state_manager_unittest.cc b/chromium/components/metrics/metrics_state_manager_unittest.cc
new file mode 100644
index 00000000000..72280aea534
--- /dev/null
+++ b/chromium/components/metrics/metrics_state_manager_unittest.cc
@@ -0,0 +1,386 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_state_manager.h"
+
+#include <ctype.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "components/metrics/client_info.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_service.h"
+#include "components/metrics/metrics_switches.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/variations/caching_permuted_entropy_provider.h"
+#include "components/variations/pref_names.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+class MetricsStateManagerTest : public testing::Test {
+ public:
+ MetricsStateManagerTest() : is_metrics_reporting_enabled_(false) {
+ MetricsService::RegisterPrefs(prefs_.registry());
+ }
+
+ scoped_ptr<MetricsStateManager> CreateStateManager() {
+ return MetricsStateManager::Create(
+ &prefs_,
+ base::Bind(&MetricsStateManagerTest::is_metrics_reporting_enabled,
+ base::Unretained(this)),
+ base::Bind(&MetricsStateManagerTest::MockStoreClientInfoBackup,
+ base::Unretained(this)),
+ base::Bind(&MetricsStateManagerTest::LoadFakeClientInfoBackup,
+ base::Unretained(this)));
+ }
+
+ // Sets metrics reporting as enabled for testing.
+ void EnableMetricsReporting() {
+ is_metrics_reporting_enabled_ = true;
+ }
+
+ protected:
+ TestingPrefServiceSimple prefs_;
+
+ // Last ClientInfo stored by the MetricsStateManager via
+ // MockStoreClientInfoBackup.
+ scoped_ptr<ClientInfo> stored_client_info_backup_;
+
+ // If set, will be returned via LoadFakeClientInfoBackup if requested by the
+ // MetricsStateManager.
+ scoped_ptr<ClientInfo> fake_client_info_backup_;
+
+ private:
+ bool is_metrics_reporting_enabled() const {
+ return is_metrics_reporting_enabled_;
+ }
+
+ // Stores the |client_info| in |stored_client_info_backup_| for verification
+ // by the tests later.
+ void MockStoreClientInfoBackup(const ClientInfo& client_info) {
+ stored_client_info_backup_.reset(new ClientInfo);
+ stored_client_info_backup_->client_id = client_info.client_id;
+ stored_client_info_backup_->installation_date =
+ client_info.installation_date;
+ stored_client_info_backup_->reporting_enabled_date =
+ client_info.reporting_enabled_date;
+
+ // Respect the contract that storing an empty client_id voids the existing
+ // backup (required for the last section of the ForceClientIdCreation test
+ // below).
+ if (client_info.client_id.empty())
+ fake_client_info_backup_.reset();
+ }
+
+ // Hands out a copy of |fake_client_info_backup_| if it is set.
+ scoped_ptr<ClientInfo> LoadFakeClientInfoBackup() {
+ if (!fake_client_info_backup_)
+ return scoped_ptr<ClientInfo>();
+
+ scoped_ptr<ClientInfo> backup_copy(new ClientInfo);
+ backup_copy->client_id = fake_client_info_backup_->client_id;
+ backup_copy->installation_date =
+ fake_client_info_backup_->installation_date;
+ backup_copy->reporting_enabled_date =
+ fake_client_info_backup_->reporting_enabled_date;
+ return backup_copy;
+ }
+
+ bool is_metrics_reporting_enabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsStateManagerTest);
+};
+
+// Ensure the ClientId is formatted as expected.
+TEST_F(MetricsStateManagerTest, ClientIdCorrectlyFormatted) {
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ state_manager->ForceClientIdCreation();
+
+ const std::string client_id = state_manager->client_id();
+ EXPECT_EQ(36U, client_id.length());
+
+ for (size_t i = 0; i < client_id.length(); ++i) {
+ char current = client_id[i];
+ if (i == 8 || i == 13 || i == 18 || i == 23)
+ EXPECT_EQ('-', current);
+ else
+ EXPECT_TRUE(isxdigit(current));
+ }
+}
+
+TEST_F(MetricsStateManagerTest, EntropySourceUsed_Low) {
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ state_manager->CreateEntropyProvider();
+ EXPECT_EQ(MetricsStateManager::ENTROPY_SOURCE_LOW,
+ state_manager->entropy_source_returned());
+}
+
+TEST_F(MetricsStateManagerTest, EntropySourceUsed_High) {
+ EnableMetricsReporting();
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ state_manager->CreateEntropyProvider();
+ EXPECT_EQ(MetricsStateManager::ENTROPY_SOURCE_HIGH,
+ state_manager->entropy_source_returned());
+}
+
+TEST_F(MetricsStateManagerTest, LowEntropySource0NotReset) {
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+
+ // Get the low entropy source once, to initialize it.
+ state_manager->GetLowEntropySource();
+
+ // Now, set it to 0 and ensure it doesn't get reset.
+ state_manager->low_entropy_source_ = 0;
+ EXPECT_EQ(0, state_manager->GetLowEntropySource());
+ // Call it another time, just to make sure.
+ EXPECT_EQ(0, state_manager->GetLowEntropySource());
+}
+
+TEST_F(MetricsStateManagerTest,
+ PermutedEntropyCacheClearedWhenLowEntropyReset) {
+ const PrefService::Preference* low_entropy_pref =
+ prefs_.FindPreference(prefs::kMetricsLowEntropySource);
+ const char* kCachePrefName =
+ variations::prefs::kVariationsPermutedEntropyCache;
+ int low_entropy_value = -1;
+
+ // First, generate an initial low entropy source value.
+ {
+ EXPECT_TRUE(low_entropy_pref->IsDefaultValue());
+
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ state_manager->GetLowEntropySource();
+
+ EXPECT_FALSE(low_entropy_pref->IsDefaultValue());
+ EXPECT_TRUE(low_entropy_pref->GetValue()->GetAsInteger(&low_entropy_value));
+ }
+
+ // Now, set a dummy value in the permuted entropy cache pref and verify that
+ // another call to GetLowEntropySource() doesn't clobber it when
+ // --reset-variation-state wasn't specified.
+ {
+ prefs_.SetString(kCachePrefName, "test");
+
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ state_manager->GetLowEntropySource();
+
+ EXPECT_EQ("test", prefs_.GetString(kCachePrefName));
+ EXPECT_EQ(low_entropy_value,
+ prefs_.GetInteger(prefs::kMetricsLowEntropySource));
+ }
+
+ // Verify that the cache does get reset if --reset-variations-state is passed.
+ {
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kResetVariationState);
+
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ state_manager->GetLowEntropySource();
+
+ EXPECT_TRUE(prefs_.GetString(kCachePrefName).empty());
+ }
+}
+
+// Check that setting the kMetricsResetIds pref to true causes the client id to
+// be reset. We do not check that the low entropy source is reset because we
+// cannot ensure that metrics state manager won't generate the same id again.
+TEST_F(MetricsStateManagerTest, ResetMetricsIDs) {
+ // Set an initial client id in prefs. It should not be possible for the
+ // metrics state manager to generate this id randomly.
+ const std::string kInitialClientId = "initial client id";
+ prefs_.SetString(prefs::kMetricsClientID, kInitialClientId);
+
+ // Make sure the initial client id isn't reset by the metrics state manager.
+ {
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ state_manager->ForceClientIdCreation();
+ EXPECT_EQ(kInitialClientId, state_manager->client_id());
+ }
+
+ // Set the reset pref to cause the IDs to be reset.
+ prefs_.SetBoolean(prefs::kMetricsResetIds, true);
+
+ // Cause the actual reset to happen.
+ {
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ state_manager->ForceClientIdCreation();
+ EXPECT_NE(kInitialClientId, state_manager->client_id());
+
+ state_manager->GetLowEntropySource();
+
+ EXPECT_FALSE(prefs_.GetBoolean(prefs::kMetricsResetIds));
+ }
+
+ EXPECT_NE(kInitialClientId, prefs_.GetString(prefs::kMetricsClientID));
+}
+
+TEST_F(MetricsStateManagerTest, ForceClientIdCreation) {
+ const int64_t kFakeInstallationDate = 12345;
+ prefs_.SetInt64(prefs::kInstallDate, kFakeInstallationDate);
+
+ const int64_t test_begin_time = base::Time::Now().ToTimeT();
+
+ // Holds ClientInfo from previous scoped test for extra checks.
+ scoped_ptr<ClientInfo> previous_client_info;
+
+ {
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+
+ // client_id shouldn't be auto-generated if metrics reporting is not
+ // enabled.
+ EXPECT_EQ(std::string(), state_manager->client_id());
+ EXPECT_EQ(0, prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp));
+
+ // Confirm that the initial ForceClientIdCreation call creates the client id
+ // and backs it up via MockStoreClientInfoBackup.
+ EXPECT_FALSE(stored_client_info_backup_);
+ state_manager->ForceClientIdCreation();
+ EXPECT_NE(std::string(), state_manager->client_id());
+ EXPECT_GE(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp),
+ test_begin_time);
+
+ ASSERT_TRUE(stored_client_info_backup_);
+ EXPECT_EQ(state_manager->client_id(),
+ stored_client_info_backup_->client_id);
+ EXPECT_EQ(kFakeInstallationDate,
+ stored_client_info_backup_->installation_date);
+ EXPECT_EQ(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp),
+ stored_client_info_backup_->reporting_enabled_date);
+
+ previous_client_info = std::move(stored_client_info_backup_);
+ }
+
+ EnableMetricsReporting();
+
+ {
+ EXPECT_FALSE(stored_client_info_backup_);
+
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+
+ // client_id should be auto-obtained from the constructor when metrics
+ // reporting is enabled.
+ EXPECT_EQ(previous_client_info->client_id, state_manager->client_id());
+
+ // The backup should also be refreshed when the client id re-initialized.
+ ASSERT_TRUE(stored_client_info_backup_);
+ EXPECT_EQ(previous_client_info->client_id,
+ stored_client_info_backup_->client_id);
+ EXPECT_EQ(kFakeInstallationDate,
+ stored_client_info_backup_->installation_date);
+ EXPECT_EQ(previous_client_info->reporting_enabled_date,
+ stored_client_info_backup_->reporting_enabled_date);
+
+ // Re-forcing client id creation shouldn't cause another backup and
+ // shouldn't affect the existing client id.
+ stored_client_info_backup_.reset();
+ state_manager->ForceClientIdCreation();
+ EXPECT_FALSE(stored_client_info_backup_);
+ EXPECT_EQ(previous_client_info->client_id, state_manager->client_id());
+ }
+
+ const int64_t kBackupInstallationDate = 1111;
+ const int64_t kBackupReportingEnabledDate = 2222;
+ const char kBackupClientId[] = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE";
+ fake_client_info_backup_.reset(new ClientInfo);
+ fake_client_info_backup_->client_id = kBackupClientId;
+ fake_client_info_backup_->installation_date = kBackupInstallationDate;
+ fake_client_info_backup_->reporting_enabled_date =
+ kBackupReportingEnabledDate;
+
+ {
+ // The existence of a backup should result in the same behaviour as
+ // before if we already have a client id.
+
+ EXPECT_FALSE(stored_client_info_backup_);
+
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ EXPECT_EQ(previous_client_info->client_id, state_manager->client_id());
+
+ // The backup should also be refreshed when the client id re-initialized.
+ ASSERT_TRUE(stored_client_info_backup_);
+ EXPECT_EQ(previous_client_info->client_id,
+ stored_client_info_backup_->client_id);
+ EXPECT_EQ(kFakeInstallationDate,
+ stored_client_info_backup_->installation_date);
+ EXPECT_EQ(previous_client_info->reporting_enabled_date,
+ stored_client_info_backup_->reporting_enabled_date);
+ stored_client_info_backup_.reset();
+ }
+
+ prefs_.ClearPref(prefs::kMetricsClientID);
+ prefs_.ClearPref(prefs::kMetricsReportingEnabledTimestamp);
+
+ {
+ // The backup should kick in if the client id has gone missing. It should
+ // replace remaining and missing dates as well.
+
+ EXPECT_FALSE(stored_client_info_backup_);
+
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ EXPECT_EQ(kBackupClientId, state_manager->client_id());
+ EXPECT_EQ(kBackupInstallationDate, prefs_.GetInt64(prefs::kInstallDate));
+ EXPECT_EQ(kBackupReportingEnabledDate,
+ prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp));
+
+ EXPECT_TRUE(stored_client_info_backup_);
+ stored_client_info_backup_.reset();
+ }
+
+ const char kNoDashesBackupClientId[] = "AAAAAAAABBBBCCCCDDDDEEEEEEEEEEEE";
+ fake_client_info_backup_.reset(new ClientInfo);
+ fake_client_info_backup_->client_id = kNoDashesBackupClientId;
+
+ prefs_.ClearPref(prefs::kInstallDate);
+ prefs_.ClearPref(prefs::kMetricsClientID);
+ prefs_.ClearPref(prefs::kMetricsReportingEnabledTimestamp);
+
+ {
+ // When running the backup from old-style client ids, dashes should be
+ // re-added. And missing dates in backup should be replaced by Time::Now().
+
+ EXPECT_FALSE(stored_client_info_backup_);
+
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ EXPECT_EQ(kBackupClientId, state_manager->client_id());
+ EXPECT_GE(prefs_.GetInt64(prefs::kInstallDate), test_begin_time);
+ EXPECT_GE(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp),
+ test_begin_time);
+
+ EXPECT_TRUE(stored_client_info_backup_);
+ previous_client_info = std::move(stored_client_info_backup_);
+ }
+
+ prefs_.SetBoolean(prefs::kMetricsResetIds, true);
+
+ {
+ // Upon request to reset metrics ids, the existing backup should not be
+ // restored.
+
+ EXPECT_FALSE(stored_client_info_backup_);
+
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+
+ // A brand new client id should have been generated.
+ EXPECT_NE(std::string(), state_manager->client_id());
+ EXPECT_NE(previous_client_info->client_id, state_manager->client_id());
+
+ // The installation date should not have been affected.
+ EXPECT_EQ(previous_client_info->installation_date,
+ prefs_.GetInt64(prefs::kInstallDate));
+
+ // The metrics-reporting-enabled date will be reset to Now().
+ EXPECT_GE(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp),
+ previous_client_info->reporting_enabled_date);
+
+ stored_client_info_backup_.reset();
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_switches.cc b/chromium/components/metrics/metrics_switches.cc
new file mode 100644
index 00000000000..1aef619d6a4
--- /dev/null
+++ b/chromium/components/metrics/metrics_switches.cc
@@ -0,0 +1,15 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_switches.h"
+
+namespace metrics {
+namespace switches {
+
+// Forces a reset of the one-time-randomized FieldTrials on this client, also
+// known as the Chrome Variations state.
+const char kResetVariationState[] = "reset-variation-state";
+
+} // namespace switches
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_switches.h b/chromium/components/metrics/metrics_switches.h
new file mode 100644
index 00000000000..b3fe7fd9c03
--- /dev/null
+++ b/chromium/components/metrics/metrics_switches.h
@@ -0,0 +1,18 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_SWITCHES_H_
+#define COMPONENTS_METRICS_METRICS_SWITCHES_H_
+
+namespace metrics {
+namespace switches {
+
+// Alphabetical list of switches specific to the metrics component. Document
+// each in the .cc file.
+extern const char kResetVariationState[];
+
+} // namespace switches
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_SWITCHES_H_
diff --git a/chromium/components/metrics/net/DEPS b/chromium/components/metrics/net/DEPS
new file mode 100644
index 00000000000..c7c67e4830e
--- /dev/null
+++ b/chromium/components/metrics/net/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+chromeos/network",
+ "+components/data_use_measurement/core",
+ "+net",
+ "+third_party/cros_system_api",
+]
diff --git a/chromium/components/metrics/net/net_metrics_log_uploader.cc b/chromium/components/metrics/net/net_metrics_log_uploader.cc
new file mode 100644
index 00000000000..0ded667b787
--- /dev/null
+++ b/chromium/components/metrics/net/net_metrics_log_uploader.cc
@@ -0,0 +1,77 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/net/net_metrics_log_uploader.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "components/data_use_measurement/core/data_use_user_data.h"
+#include "net/base/load_flags.h"
+#include "net/base/network_change_notifier.h"
+#include "net/url_request/url_fetcher.h"
+#include "url/gurl.h"
+
+namespace {
+
+// Records the network connection type if upload was successful.
+void RecordConnectionType(int response_code) {
+ if (response_code == 200) {
+ UMA_HISTOGRAM_ENUMERATION("UMA.LogUpload.ConnetionType",
+ net::NetworkChangeNotifier::GetConnectionType(),
+ net::NetworkChangeNotifier::CONNECTION_LAST);
+ }
+}
+
+} // namespace
+
+namespace metrics {
+
+NetMetricsLogUploader::NetMetricsLogUploader(
+ net::URLRequestContextGetter* request_context_getter,
+ const std::string& server_url,
+ const std::string& mime_type,
+ const base::Callback<void(int)>& on_upload_complete)
+ : MetricsLogUploader(server_url, mime_type, on_upload_complete),
+ request_context_getter_(request_context_getter) {
+}
+
+NetMetricsLogUploader::~NetMetricsLogUploader() {
+}
+
+void NetMetricsLogUploader::UploadLog(const std::string& compressed_log_data,
+ const std::string& log_hash) {
+ current_fetch_ =
+ net::URLFetcher::Create(GURL(server_url_), net::URLFetcher::POST, this);
+ data_use_measurement::DataUseUserData::AttachToFetcher(
+ current_fetch_.get(), data_use_measurement::DataUseUserData::UMA);
+ current_fetch_->SetRequestContext(request_context_getter_);
+ current_fetch_->SetUploadData(mime_type_, compressed_log_data);
+
+ // Tell the server that we're uploading gzipped protobufs.
+ current_fetch_->SetExtraRequestHeaders("content-encoding: gzip");
+
+ DCHECK(!log_hash.empty());
+ current_fetch_->AddExtraRequestHeader("X-Chrome-UMA-Log-SHA1: " + log_hash);
+
+ // We already drop cookies server-side, but we might as well strip them out
+ // client-side as well.
+ current_fetch_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
+ net::LOAD_DO_NOT_SEND_COOKIES);
+ current_fetch_->Start();
+}
+
+void NetMetricsLogUploader::OnURLFetchComplete(const net::URLFetcher* source) {
+ // We're not allowed to re-use the existing |URLFetcher|s, so free them here.
+ // Note however that |source| is aliased to the fetcher, so we should be
+ // careful not to delete it too early.
+ DCHECK_EQ(current_fetch_.get(), source);
+
+ int response_code = source->GetResponseCode();
+ if (response_code == net::URLFetcher::RESPONSE_CODE_INVALID)
+ response_code = -1;
+ current_fetch_.reset();
+ RecordConnectionType(response_code);
+ on_upload_complete_.Run(response_code);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/net/net_metrics_log_uploader.h b/chromium/components/metrics/net/net_metrics_log_uploader.h
new file mode 100644
index 00000000000..a62b8988f3c
--- /dev/null
+++ b/chromium/components/metrics/net/net_metrics_log_uploader.h
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_NET_NET_METRICS_LOG_UPLOADER_H_
+#define COMPONENTS_METRICS_NET_NET_METRICS_LOG_UPLOADER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/metrics/metrics_log_uploader.h"
+#include "net/url_request/url_fetcher_delegate.h"
+
+namespace net {
+class URLFetcher;
+class URLRequestContextGetter;
+}
+
+namespace metrics {
+
+// Implementation of MetricsLogUploader using the Chrome network stack.
+class NetMetricsLogUploader : public MetricsLogUploader,
+ public net::URLFetcherDelegate {
+ public:
+ // Constructs a NetMetricsLogUploader with the specified request context and
+ // other params (see comments on MetricsLogUploader for details). The caller
+ // must ensure that |request_context_getter| remains valid for the lifetime
+ // of this class.
+ NetMetricsLogUploader(net::URLRequestContextGetter* request_context_getter,
+ const std::string& server_url,
+ const std::string& mime_type,
+ const base::Callback<void(int)>& on_upload_complete);
+ ~NetMetricsLogUploader() override;
+
+ // MetricsLogUploader:
+ void UploadLog(const std::string& compressed_log_data,
+ const std::string& log_hash) override;
+
+ private:
+ // net::URLFetcherDelegate:
+ void OnURLFetchComplete(const net::URLFetcher* source) override;
+
+ // The request context for fetches done using the network stack.
+ net::URLRequestContextGetter* const request_context_getter_;
+
+ // The outstanding transmission appears as a URL Fetch operation.
+ scoped_ptr<net::URLFetcher> current_fetch_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetMetricsLogUploader);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_NET_NET_METRICS_LOG_UPLOADER_H_
diff --git a/chromium/components/metrics/net/net_metrics_log_uploader_unittest.cc b/chromium/components/metrics/net/net_metrics_log_uploader_unittest.cc
new file mode 100644
index 00000000000..a2d0ecd166b
--- /dev/null
+++ b/chromium/components/metrics/net/net_metrics_log_uploader_unittest.cc
@@ -0,0 +1,59 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/net/net_metrics_log_uploader.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+class NetMetricsLogUploaderTest : public testing::Test {
+ public:
+ NetMetricsLogUploaderTest() : on_upload_complete_count_(0) {
+ }
+
+ void CreateAndOnUploadCompleteReuseUploader() {
+ uploader_.reset(new NetMetricsLogUploader(
+ NULL, "http://dummy_server", "dummy_mime",
+ base::Bind(&NetMetricsLogUploaderTest::OnUploadCompleteReuseUploader,
+ base::Unretained(this))));
+ uploader_->UploadLog("initial_dummy_data", "initial_dummy_hash");
+ }
+
+ void OnUploadCompleteReuseUploader(int response_code) {
+ ++on_upload_complete_count_;
+ if (on_upload_complete_count_ == 1)
+ uploader_->UploadLog("dummy_data", "dummy_hash");
+ }
+
+ int on_upload_complete_count() const {
+ return on_upload_complete_count_;
+ }
+
+ private:
+ scoped_ptr<NetMetricsLogUploader> uploader_;
+ int on_upload_complete_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetMetricsLogUploaderTest);
+};
+
+TEST_F(NetMetricsLogUploaderTest, OnUploadCompleteReuseUploader) {
+ net::TestURLFetcherFactory factory;
+ CreateAndOnUploadCompleteReuseUploader();
+
+ // Mimic the initial fetcher callback.
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+
+ // Mimic the second fetcher callback.
+ fetcher = factory.GetFetcherByID(0);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+
+ EXPECT_EQ(on_upload_complete_count(), 2);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/net/network_metrics_provider.cc b/chromium/components/metrics/net/network_metrics_provider.cc
new file mode 100644
index 00000000000..92540893984
--- /dev/null
+++ b/chromium/components/metrics/net/network_metrics_provider.cc
@@ -0,0 +1,265 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/net/network_metrics_provider.h"
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/task_runner_util.h"
+#include "build/build_config.h"
+#include "net/base/net_errors.h"
+
+#if defined(OS_CHROMEOS)
+#include "components/metrics/net/wifi_access_point_info_provider_chromeos.h"
+#endif // OS_CHROMEOS
+
+namespace metrics {
+
+NetworkMetricsProvider::NetworkMetricsProvider(base::TaskRunner* io_task_runner)
+ : io_task_runner_(io_task_runner),
+ connection_type_is_ambiguous_(false),
+ wifi_phy_layer_protocol_is_ambiguous_(false),
+ wifi_phy_layer_protocol_(net::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN),
+ total_aborts_(0),
+ total_codes_(0),
+ weak_ptr_factory_(this) {
+ net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
+ connection_type_ = net::NetworkChangeNotifier::GetConnectionType();
+ ProbeWifiPHYLayerProtocol();
+}
+
+NetworkMetricsProvider::~NetworkMetricsProvider() {
+ net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
+}
+
+void NetworkMetricsProvider::ProvideGeneralMetrics(
+ ChromeUserMetricsExtension*) {
+ // ProvideGeneralMetrics is called on the main thread, at the time a metrics
+ // record is being finalized.
+ net::NetworkChangeNotifier::FinalizingMetricsLogRecord();
+ LogAggregatedMetrics();
+}
+
+void NetworkMetricsProvider::ProvideSystemProfileMetrics(
+ SystemProfileProto* system_profile) {
+ SystemProfileProto::Network* network = system_profile->mutable_network();
+ network->set_connection_type_is_ambiguous(connection_type_is_ambiguous_);
+ network->set_connection_type(GetConnectionType());
+ network->set_wifi_phy_layer_protocol_is_ambiguous(
+ wifi_phy_layer_protocol_is_ambiguous_);
+ network->set_wifi_phy_layer_protocol(GetWifiPHYLayerProtocol());
+
+ // Update the connection type. Note that this is necessary to set the network
+ // type to "none" if there is no network connection for an entire UMA logging
+ // window, since OnConnectionTypeChanged() ignores transitions to the "none"
+ // state.
+ connection_type_ = net::NetworkChangeNotifier::GetConnectionType();
+ // Reset the "ambiguous" flags, since a new metrics log session has started.
+ connection_type_is_ambiguous_ = false;
+ wifi_phy_layer_protocol_is_ambiguous_ = false;
+
+ if (!wifi_access_point_info_provider_.get()) {
+#if defined(OS_CHROMEOS)
+ wifi_access_point_info_provider_.reset(
+ new WifiAccessPointInfoProviderChromeos());
+#else
+ wifi_access_point_info_provider_.reset(
+ new WifiAccessPointInfoProvider());
+#endif // OS_CHROMEOS
+ }
+
+ // Connected wifi access point information.
+ WifiAccessPointInfoProvider::WifiAccessPointInfo info;
+ if (wifi_access_point_info_provider_->GetInfo(&info))
+ WriteWifiAccessPointProto(info, network);
+}
+
+void NetworkMetricsProvider::OnConnectionTypeChanged(
+ net::NetworkChangeNotifier::ConnectionType type) {
+ // To avoid reporting an ambiguous connection type for users on flaky
+ // connections, ignore transitions to the "none" state. Note that the
+ // connection type is refreshed in ProvideSystemProfileMetrics() each time a
+ // new UMA logging window begins, so users who genuinely transition to offline
+ // mode for an extended duration will still be at least partially represented
+ // in the metrics logs.
+ if (type == net::NetworkChangeNotifier::CONNECTION_NONE)
+ return;
+
+ if (type != connection_type_ &&
+ connection_type_ != net::NetworkChangeNotifier::CONNECTION_NONE) {
+ connection_type_is_ambiguous_ = true;
+ }
+ connection_type_ = type;
+
+ ProbeWifiPHYLayerProtocol();
+}
+
+SystemProfileProto::Network::ConnectionType
+NetworkMetricsProvider::GetConnectionType() const {
+ switch (connection_type_) {
+ case net::NetworkChangeNotifier::CONNECTION_NONE:
+ return SystemProfileProto::Network::CONNECTION_NONE;
+ case net::NetworkChangeNotifier::CONNECTION_UNKNOWN:
+ return SystemProfileProto::Network::CONNECTION_UNKNOWN;
+ case net::NetworkChangeNotifier::CONNECTION_ETHERNET:
+ return SystemProfileProto::Network::CONNECTION_ETHERNET;
+ case net::NetworkChangeNotifier::CONNECTION_WIFI:
+ return SystemProfileProto::Network::CONNECTION_WIFI;
+ case net::NetworkChangeNotifier::CONNECTION_2G:
+ return SystemProfileProto::Network::CONNECTION_2G;
+ case net::NetworkChangeNotifier::CONNECTION_3G:
+ return SystemProfileProto::Network::CONNECTION_3G;
+ case net::NetworkChangeNotifier::CONNECTION_4G:
+ return SystemProfileProto::Network::CONNECTION_4G;
+ case net::NetworkChangeNotifier::CONNECTION_BLUETOOTH:
+ return SystemProfileProto::Network::CONNECTION_BLUETOOTH;
+ }
+ NOTREACHED();
+ return SystemProfileProto::Network::CONNECTION_UNKNOWN;
+}
+
+SystemProfileProto::Network::WifiPHYLayerProtocol
+NetworkMetricsProvider::GetWifiPHYLayerProtocol() const {
+ switch (wifi_phy_layer_protocol_) {
+ case net::WIFI_PHY_LAYER_PROTOCOL_NONE:
+ return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_NONE;
+ case net::WIFI_PHY_LAYER_PROTOCOL_ANCIENT:
+ return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_ANCIENT;
+ case net::WIFI_PHY_LAYER_PROTOCOL_A:
+ return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_A;
+ case net::WIFI_PHY_LAYER_PROTOCOL_B:
+ return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_B;
+ case net::WIFI_PHY_LAYER_PROTOCOL_G:
+ return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_G;
+ case net::WIFI_PHY_LAYER_PROTOCOL_N:
+ return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_N;
+ case net::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN:
+ return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN;
+ }
+ NOTREACHED();
+ return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN;
+}
+
+void NetworkMetricsProvider::ProbeWifiPHYLayerProtocol() {
+ PostTaskAndReplyWithResult(
+ io_task_runner_,
+ FROM_HERE,
+ base::Bind(&net::GetWifiPHYLayerProtocol),
+ base::Bind(&NetworkMetricsProvider::OnWifiPHYLayerProtocolResult,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void NetworkMetricsProvider::OnWifiPHYLayerProtocolResult(
+ net::WifiPHYLayerProtocol mode) {
+ if (wifi_phy_layer_protocol_ != net::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN &&
+ mode != wifi_phy_layer_protocol_) {
+ wifi_phy_layer_protocol_is_ambiguous_ = true;
+ }
+ wifi_phy_layer_protocol_ = mode;
+}
+
+void NetworkMetricsProvider::WriteWifiAccessPointProto(
+ const WifiAccessPointInfoProvider::WifiAccessPointInfo& info,
+ SystemProfileProto::Network* network_proto) {
+ SystemProfileProto::Network::WifiAccessPoint* access_point_info =
+ network_proto->mutable_access_point_info();
+ SystemProfileProto::Network::WifiAccessPoint::SecurityMode security =
+ SystemProfileProto::Network::WifiAccessPoint::SECURITY_UNKNOWN;
+ switch (info.security) {
+ case WifiAccessPointInfoProvider::WIFI_SECURITY_NONE:
+ security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_NONE;
+ break;
+ case WifiAccessPointInfoProvider::WIFI_SECURITY_WPA:
+ security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_WPA;
+ break;
+ case WifiAccessPointInfoProvider::WIFI_SECURITY_WEP:
+ security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_WEP;
+ break;
+ case WifiAccessPointInfoProvider::WIFI_SECURITY_RSN:
+ security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_RSN;
+ break;
+ case WifiAccessPointInfoProvider::WIFI_SECURITY_802_1X:
+ security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_802_1X;
+ break;
+ case WifiAccessPointInfoProvider::WIFI_SECURITY_PSK:
+ security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_PSK;
+ break;
+ case WifiAccessPointInfoProvider::WIFI_SECURITY_UNKNOWN:
+ security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_UNKNOWN;
+ break;
+ }
+ access_point_info->set_security_mode(security);
+
+ // |bssid| is xx:xx:xx:xx:xx:xx, extract the first three components and
+ // pack into a uint32_t.
+ std::string bssid = info.bssid;
+ if (bssid.size() == 17 && bssid[2] == ':' && bssid[5] == ':' &&
+ bssid[8] == ':' && bssid[11] == ':' && bssid[14] == ':') {
+ std::string vendor_prefix_str;
+ uint32_t vendor_prefix;
+
+ base::RemoveChars(bssid.substr(0, 9), ":", &vendor_prefix_str);
+ DCHECK_EQ(6U, vendor_prefix_str.size());
+ if (base::HexStringToUInt(vendor_prefix_str, &vendor_prefix))
+ access_point_info->set_vendor_prefix(vendor_prefix);
+ else
+ NOTREACHED();
+ }
+
+ // Return if vendor information is not provided.
+ if (info.model_number.empty() && info.model_name.empty() &&
+ info.device_name.empty() && info.oui_list.empty())
+ return;
+
+ SystemProfileProto::Network::WifiAccessPoint::VendorInformation* vendor =
+ access_point_info->mutable_vendor_info();
+ if (!info.model_number.empty())
+ vendor->set_model_number(info.model_number);
+ if (!info.model_name.empty())
+ vendor->set_model_name(info.model_name);
+ if (!info.device_name.empty())
+ vendor->set_device_name(info.device_name);
+
+ // Return if OUI list is not provided.
+ if (info.oui_list.empty())
+ return;
+
+ // Parse OUI list.
+ for (const base::StringPiece& oui_str : base::SplitStringPiece(
+ info.oui_list, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+ uint32_t oui;
+ if (base::HexStringToUInt(oui_str, &oui))
+ vendor->add_element_identifier(oui);
+ else
+ NOTREACHED();
+ }
+}
+
+void NetworkMetricsProvider::LogAggregatedMetrics() {
+ base::HistogramBase* error_codes = base::SparseHistogram::FactoryGet(
+ "Net.ErrorCodesForMainFrame3",
+ base::HistogramBase::kUmaTargetedHistogramFlag);
+ scoped_ptr<base::HistogramSamples> samples = error_codes->SnapshotSamples();
+ base::HistogramBase::Count new_aborts =
+ samples->GetCount(-net::ERR_ABORTED) - total_aborts_;
+ base::HistogramBase::Count new_codes = samples->TotalCount() - total_codes_;
+ if (new_codes > 0) {
+ UMA_HISTOGRAM_COUNTS("Net.ErrAborted.CountPerUpload", new_aborts);
+ UMA_HISTOGRAM_PERCENTAGE("Net.ErrAborted.ProportionPerUpload",
+ (100 * new_aborts) / new_codes);
+ total_codes_ += new_codes;
+ total_aborts_ += new_aborts;
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/net/network_metrics_provider.h b/chromium/components/metrics/net/network_metrics_provider.h
new file mode 100644
index 00000000000..5dbb5593b24
--- /dev/null
+++ b/chromium/components/metrics/net/network_metrics_provider.h
@@ -0,0 +1,88 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_NET_NETWORK_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_NET_NETWORK_METRICS_PROVIDER_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/metrics/histogram_base.h"
+#include "components/metrics/metrics_provider.h"
+#include "components/metrics/net/wifi_access_point_info_provider.h"
+#include "components/metrics/proto/system_profile.pb.h"
+#include "net/base/network_change_notifier.h"
+#include "net/base/network_interfaces.h"
+
+namespace metrics {
+
+// Registers as observer with net::NetworkChangeNotifier and keeps track of
+// the network environment.
+class NetworkMetricsProvider
+ : public MetricsProvider,
+ public net::NetworkChangeNotifier::ConnectionTypeObserver {
+ public:
+ // Creates a NetworkMetricsProvider, where |io_task_runner| is used to post
+ // network info collection tasks.
+ explicit NetworkMetricsProvider(base::TaskRunner* io_task_runner);
+ ~NetworkMetricsProvider() override;
+
+ private:
+ // MetricsProvider:
+ void ProvideGeneralMetrics(ChromeUserMetricsExtension* uma_proto) override;
+ void ProvideSystemProfileMetrics(SystemProfileProto* system_profile) override;
+
+ // ConnectionTypeObserver:
+ void OnConnectionTypeChanged(
+ net::NetworkChangeNotifier::ConnectionType type) override;
+
+ SystemProfileProto::Network::ConnectionType GetConnectionType() const;
+ SystemProfileProto::Network::WifiPHYLayerProtocol GetWifiPHYLayerProtocol()
+ const;
+
+ // Posts a call to net::GetWifiPHYLayerProtocol on the blocking pool.
+ void ProbeWifiPHYLayerProtocol();
+ // Callback from the blocking pool with the result of
+ // net::GetWifiPHYLayerProtocol.
+ void OnWifiPHYLayerProtocolResult(net::WifiPHYLayerProtocol mode);
+
+ // Writes info about the wireless access points that this system is
+ // connected to.
+ void WriteWifiAccessPointProto(
+ const WifiAccessPointInfoProvider::WifiAccessPointInfo& info,
+ SystemProfileProto::Network* network_proto);
+
+ // Logs metrics that are functions of other metrics being uploaded.
+ void LogAggregatedMetrics();
+
+ // Task runner used for blocking file I/O.
+ base::TaskRunner* io_task_runner_;
+
+ // True if |connection_type_| changed during the lifetime of the log.
+ bool connection_type_is_ambiguous_;
+ // The connection type according to net::NetworkChangeNotifier.
+ net::NetworkChangeNotifier::ConnectionType connection_type_;
+
+ // True if |wifi_phy_layer_protocol_| changed during the lifetime of the log.
+ bool wifi_phy_layer_protocol_is_ambiguous_;
+ // The PHY mode of the currently associated access point obtained via
+ // net::GetWifiPHYLayerProtocol.
+ net::WifiPHYLayerProtocol wifi_phy_layer_protocol_;
+
+ // Helper object for retrieving connected wifi access point information.
+ scoped_ptr<WifiAccessPointInfoProvider> wifi_access_point_info_provider_;
+
+ // These metrics track histogram totals for the Net.ErrorCodesForMainFrame3
+ // histogram. They are used to compute deltas at upload time.
+ base::HistogramBase::Count total_aborts_;
+ base::HistogramBase::Count total_codes_;
+
+ base::WeakPtrFactory<NetworkMetricsProvider> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkMetricsProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_NET_NETWORK_METRICS_PROVIDER_H_
diff --git a/chromium/components/metrics/net/version_utils.cc b/chromium/components/metrics/net/version_utils.cc
new file mode 100644
index 00000000000..37452330d9e
--- /dev/null
+++ b/chromium/components/metrics/net/version_utils.cc
@@ -0,0 +1,41 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/net/version_utils.h"
+
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "components/version_info/version_info.h"
+
+namespace metrics {
+
+std::string GetVersionString() {
+ std::string version = version_info::GetVersionNumber();
+#if defined(ARCH_CPU_64_BITS)
+ version += "-64";
+#endif // defined(ARCH_CPU_64_BITS)
+ if (!version_info::IsOfficialBuild())
+ version.append("-devel");
+ return version;
+}
+
+SystemProfileProto::Channel AsProtobufChannel(
+ version_info::Channel channel) {
+ switch (channel) {
+ case version_info::Channel::UNKNOWN:
+ return SystemProfileProto::CHANNEL_UNKNOWN;
+ case version_info::Channel::CANARY:
+ return SystemProfileProto::CHANNEL_CANARY;
+ case version_info::Channel::DEV:
+ return SystemProfileProto::CHANNEL_DEV;
+ case version_info::Channel::BETA:
+ return SystemProfileProto::CHANNEL_BETA;
+ case version_info::Channel::STABLE:
+ return SystemProfileProto::CHANNEL_STABLE;
+ }
+ NOTREACHED();
+ return SystemProfileProto::CHANNEL_UNKNOWN;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/net/version_utils.h b/chromium/components/metrics/net/version_utils.h
new file mode 100644
index 00000000000..853c846a875
--- /dev/null
+++ b/chromium/components/metrics/net/version_utils.h
@@ -0,0 +1,29 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_NET_VERSION_UTILS_H_
+#define COMPONENTS_METRICS_NET_VERSION_UTILS_H_
+
+#include <string>
+
+#include "components/metrics/proto/system_profile.pb.h"
+
+namespace version_info {
+enum class Channel;
+}
+
+namespace metrics {
+
+// Build a string including the Chrome app version, suffixed by "-64" on 64-bit
+// platforms, and "-devel" on developer builds.
+std::string GetVersionString();
+
+// Translates version_info::Channel to the equivalent
+// SystemProfileProto::Channel.
+SystemProfileProto::Channel AsProtobufChannel(
+ version_info::Channel channel);
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_NET_VERSION_UTILS_H_
diff --git a/chromium/components/metrics/net/wifi_access_point_info_provider.cc b/chromium/components/metrics/net/wifi_access_point_info_provider.cc
new file mode 100644
index 00000000000..21e04b181d2
--- /dev/null
+++ b/chromium/components/metrics/net/wifi_access_point_info_provider.cc
@@ -0,0 +1,25 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/net/wifi_access_point_info_provider.h"
+
+namespace metrics {
+
+WifiAccessPointInfoProvider::WifiAccessPointInfo::WifiAccessPointInfo() {
+}
+
+WifiAccessPointInfoProvider::WifiAccessPointInfo::~WifiAccessPointInfo() {
+}
+
+WifiAccessPointInfoProvider::WifiAccessPointInfoProvider() {
+}
+
+WifiAccessPointInfoProvider::~WifiAccessPointInfoProvider() {
+}
+
+bool WifiAccessPointInfoProvider::GetInfo(WifiAccessPointInfo *info) {
+ return false;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/net/wifi_access_point_info_provider.h b/chromium/components/metrics/net/wifi_access_point_info_provider.h
new file mode 100644
index 00000000000..3a761da1b12
--- /dev/null
+++ b/chromium/components/metrics/net/wifi_access_point_info_provider.h
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_H_
+#define COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_H_
+
+#include <string>
+
+#include "base/macros.h"
+
+namespace metrics {
+
+// Interface for accessing connected wireless access point information.
+class WifiAccessPointInfoProvider {
+ public:
+ // Wifi access point security mode definitions.
+ enum WifiSecurityMode {
+ WIFI_SECURITY_UNKNOWN = 0,
+ WIFI_SECURITY_WPA = 1,
+ WIFI_SECURITY_WEP = 2,
+ WIFI_SECURITY_RSN = 3,
+ WIFI_SECURITY_802_1X = 4,
+ WIFI_SECURITY_PSK = 5,
+ WIFI_SECURITY_NONE
+ };
+
+ // Information of the currently connected wifi access point.
+ struct WifiAccessPointInfo {
+ WifiAccessPointInfo();
+ ~WifiAccessPointInfo();
+ WifiSecurityMode security;
+ std::string bssid;
+ std::string model_number;
+ std::string model_name;
+ std::string device_name;
+ std::string oui_list;
+ };
+
+ WifiAccessPointInfoProvider();
+ virtual ~WifiAccessPointInfoProvider();
+
+ // Fill in the wifi access point info if device is currently connected to a
+ // wifi access point. Return true if device is connected to a wifi access
+ // point, false otherwise.
+ virtual bool GetInfo(WifiAccessPointInfo *info);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WifiAccessPointInfoProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_H_
diff --git a/chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.cc b/chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.cc
new file mode 100644
index 00000000000..3654e93ef4f
--- /dev/null
+++ b/chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.cc
@@ -0,0 +1,123 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/net/wifi_access_point_info_provider_chromeos.h"
+
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/strings/string_number_conversions.h"
+#include "chromeos/network/network_configuration_handler.h"
+#include "chromeos/network/network_handler.h"
+#include "chromeos/network/network_state.h"
+#include "chromeos/network/network_state_handler.h"
+#include "chromeos/network/shill_property_util.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+using chromeos::NetworkHandler;
+
+namespace metrics {
+
+WifiAccessPointInfoProviderChromeos::WifiAccessPointInfoProviderChromeos() {
+ NetworkHandler::Get()->network_state_handler()->AddObserver(this, FROM_HERE);
+
+ // Update initial connection state.
+ DefaultNetworkChanged(
+ NetworkHandler::Get()->network_state_handler()->DefaultNetwork());
+}
+
+WifiAccessPointInfoProviderChromeos::~WifiAccessPointInfoProviderChromeos() {
+ NetworkHandler::Get()->network_state_handler()->RemoveObserver(this,
+ FROM_HERE);
+}
+
+bool WifiAccessPointInfoProviderChromeos::GetInfo(WifiAccessPointInfo* info) {
+ // Wifi access point information is not provided if the BSSID is empty.
+ // This assumes the BSSID is never empty when access point information exists.
+ if (wifi_access_point_info_.bssid.empty())
+ return false;
+
+ *info = wifi_access_point_info_;
+ return true;
+}
+
+void WifiAccessPointInfoProviderChromeos::DefaultNetworkChanged(
+ const chromeos::NetworkState* default_network) {
+ // Reset access point info to prevent reporting of out-dated data.
+ wifi_access_point_info_ = WifiAccessPointInfo();
+
+ // Skip non-wifi connections
+ if (!default_network || default_network->type() != shill::kTypeWifi)
+ return;
+
+ // Retrieve access point info for wifi connection.
+ NetworkHandler::Get()->network_configuration_handler()->GetShillProperties(
+ default_network->path(),
+ base::Bind(&WifiAccessPointInfoProviderChromeos::ParseInfo, AsWeakPtr()),
+ chromeos::network_handler::ErrorCallback());
+}
+
+void WifiAccessPointInfoProviderChromeos::ParseInfo(
+ const std::string &service_path,
+ const base::DictionaryValue& properties) {
+ // Skip services that contain "_nomap" in the SSID.
+ std::string ssid = chromeos::shill_property_util::GetSSIDFromProperties(
+ properties, false /* verbose_logging */, nullptr);
+ if (ssid.find("_nomap", 0) != std::string::npos)
+ return;
+
+ std::string bssid;
+ if (!properties.GetStringWithoutPathExpansion(shill::kWifiBSsid, &bssid) ||
+ bssid.empty())
+ return;
+
+ // Filter out BSSID with local bit set in the first byte.
+ uint32_t first_octet;
+ if (!base::HexStringToUInt(bssid.substr(0, 2), &first_octet))
+ NOTREACHED();
+ if (first_octet & 0x2)
+ return;
+ wifi_access_point_info_.bssid = bssid;
+
+ // Parse security info.
+ std::string security;
+ properties.GetStringWithoutPathExpansion(
+ shill::kSecurityProperty, &security);
+ wifi_access_point_info_.security = WIFI_SECURITY_UNKNOWN;
+ if (security == shill::kSecurityWpa)
+ wifi_access_point_info_.security = WIFI_SECURITY_WPA;
+ else if (security == shill::kSecurityWep)
+ wifi_access_point_info_.security = WIFI_SECURITY_WEP;
+ else if (security == shill::kSecurityRsn)
+ wifi_access_point_info_.security = WIFI_SECURITY_RSN;
+ else if (security == shill::kSecurity8021x)
+ wifi_access_point_info_.security = WIFI_SECURITY_802_1X;
+ else if (security == shill::kSecurityPsk)
+ wifi_access_point_info_.security = WIFI_SECURITY_PSK;
+ else if (security == shill::kSecurityNone)
+ wifi_access_point_info_.security = WIFI_SECURITY_NONE;
+
+ properties.GetStringWithoutPathExpansion(
+ shill::kWifiBSsid, &wifi_access_point_info_.bssid);
+ const base::DictionaryValue* vendor_dict = NULL;
+ if (!properties.GetDictionaryWithoutPathExpansion(
+ shill::kWifiVendorInformationProperty,
+ &vendor_dict))
+ return;
+
+ vendor_dict->GetStringWithoutPathExpansion(
+ shill::kVendorWPSModelNumberProperty,
+ &wifi_access_point_info_.model_number);
+ vendor_dict->GetStringWithoutPathExpansion(
+ shill::kVendorWPSModelNameProperty,
+ &wifi_access_point_info_.model_name);
+ vendor_dict->GetStringWithoutPathExpansion(
+ shill::kVendorWPSDeviceNameProperty,
+ &wifi_access_point_info_.device_name);
+ vendor_dict->GetStringWithoutPathExpansion(shill::kVendorOUIListProperty,
+ &wifi_access_point_info_.oui_list);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.h b/chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.h
new file mode 100644
index 00000000000..d3102397741
--- /dev/null
+++ b/chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.h
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_CHROMEOS_H_
+#define COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_CHROMEOS_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "chromeos/network/network_state_handler_observer.h"
+#include "components/metrics/net/wifi_access_point_info_provider.h"
+
+namespace metrics {
+
+// WifiAccessPointInfoProviderChromeos provides the connected wifi
+// acccess point information for chromeos.
+class WifiAccessPointInfoProviderChromeos
+ : public WifiAccessPointInfoProvider,
+ public chromeos::NetworkStateHandlerObserver,
+ public base::SupportsWeakPtr<WifiAccessPointInfoProviderChromeos> {
+ public:
+ WifiAccessPointInfoProviderChromeos();
+ ~WifiAccessPointInfoProviderChromeos() override;
+
+ // WifiAccessPointInfoProvider:
+ bool GetInfo(WifiAccessPointInfo* info) override;
+
+ // NetworkStateHandlerObserver:
+ void DefaultNetworkChanged(
+ const chromeos::NetworkState* default_network) override;
+
+ private:
+ // Callback from Shill.Service.GetProperties. Parses |properties| to obtain
+ // the wifi access point information.
+ void ParseInfo(const std::string& service_path,
+ const base::DictionaryValue& properties);
+
+ WifiAccessPointInfo wifi_access_point_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(WifiAccessPointInfoProviderChromeos);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_CHROMEOS_H_
diff --git a/chromium/components/metrics/persisted_logs.cc b/chromium/components/metrics/persisted_logs.cc
new file mode 100644
index 00000000000..c78ecec2596
--- /dev/null
+++ b/chromium/components/metrics/persisted_logs.cc
@@ -0,0 +1,171 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/persisted_logs.h"
+
+#include <string>
+
+#include "base/base64.h"
+#include "base/md5.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/sha1.h"
+#include "base/timer/elapsed_timer.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace metrics {
+
+namespace {
+
+PersistedLogs::LogReadStatus MakeRecallStatusHistogram(
+ PersistedLogs::LogReadStatus status) {
+ UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecallProtobufs",
+ status, PersistedLogs::END_RECALL_STATUS);
+ return status;
+}
+
+// Reads the value at |index| from |list_value| as a string and Base64-decodes
+// it into |result|. Returns true on success.
+bool ReadBase64String(const base::ListValue& list_value,
+ size_t index,
+ std::string* result) {
+ std::string base64_result;
+ if (!list_value.GetString(index, &base64_result))
+ return false;
+ return base::Base64Decode(base64_result, result);
+}
+
+// Base64-encodes |str| and appends the result to |list_value|.
+void AppendBase64String(const std::string& str, base::ListValue* list_value) {
+ std::string base64_str;
+ base::Base64Encode(str, &base64_str);
+ list_value->AppendString(base64_str);
+}
+
+} // namespace
+
+void PersistedLogs::LogHashPair::Init(const std::string& log_data) {
+ DCHECK(!log_data.empty());
+
+ if (!compression::GzipCompress(log_data, &compressed_log_data)) {
+ NOTREACHED();
+ return;
+ }
+
+ UMA_HISTOGRAM_PERCENTAGE(
+ "UMA.ProtoCompressionRatio",
+ static_cast<int>(100 * compressed_log_data.size() / log_data.size()));
+
+ hash = base::SHA1HashString(log_data);
+}
+
+PersistedLogs::PersistedLogs(PrefService* local_state,
+ const char* pref_name,
+ size_t min_log_count,
+ size_t min_log_bytes,
+ size_t max_log_size)
+ : local_state_(local_state),
+ pref_name_(pref_name),
+ min_log_count_(min_log_count),
+ min_log_bytes_(min_log_bytes),
+ max_log_size_(max_log_size != 0 ? max_log_size : static_cast<size_t>(-1)),
+ staged_log_index_(-1) {
+ DCHECK(local_state_);
+ // One of the limit arguments must be non-zero.
+ DCHECK(min_log_count_ > 0 || min_log_bytes_ > 0);
+}
+
+PersistedLogs::~PersistedLogs() {}
+
+void PersistedLogs::SerializeLogs() const {
+ ListPrefUpdate update(local_state_, pref_name_);
+ WriteLogsToPrefList(update.Get());
+}
+
+PersistedLogs::LogReadStatus PersistedLogs::DeserializeLogs() {
+ return ReadLogsFromPrefList(*local_state_->GetList(pref_name_));
+}
+
+void PersistedLogs::StoreLog(const std::string& log_data) {
+ list_.push_back(LogHashPair());
+ list_.back().Init(log_data);
+}
+
+void PersistedLogs::StageLog() {
+ // CHECK, rather than DCHECK, because swap()ing with an empty list causes
+ // hard-to-identify crashes much later.
+ CHECK(!list_.empty());
+ DCHECK(!has_staged_log());
+ staged_log_index_ = list_.size() - 1;
+ DCHECK(has_staged_log());
+}
+
+void PersistedLogs::DiscardStagedLog() {
+ DCHECK(has_staged_log());
+ DCHECK_LT(static_cast<size_t>(staged_log_index_), list_.size());
+ list_.erase(list_.begin() + staged_log_index_);
+ staged_log_index_ = -1;
+}
+
+void PersistedLogs::WriteLogsToPrefList(base::ListValue* list_value) const {
+ list_value->Clear();
+
+ // Keep the most recent logs which are smaller than |max_log_size_|.
+ // We keep at least |min_log_bytes_| and |min_log_count_| of logs before
+ // discarding older logs.
+ size_t start = list_.size();
+ size_t saved_log_count = 0;
+ size_t bytes_used = 0;
+ for (; start > 0; --start) {
+ size_t log_size = list_[start - 1].compressed_log_data.length();
+ if (bytes_used >= min_log_bytes_ &&
+ saved_log_count >= min_log_count_) {
+ break;
+ }
+ // Oversized logs won't be persisted, so don't count them.
+ if (log_size > max_log_size_)
+ continue;
+ bytes_used += log_size;
+ ++saved_log_count;
+ }
+
+ for (size_t i = start; i < list_.size(); ++i) {
+ size_t log_size = list_[i].compressed_log_data.length();
+ if (log_size > max_log_size_) {
+ UMA_HISTOGRAM_COUNTS("UMA.Large Accumulated Log Not Persisted",
+ static_cast<int>(log_size));
+ continue;
+ }
+ AppendBase64String(list_[i].compressed_log_data, list_value);
+ AppendBase64String(list_[i].hash, list_value);
+ }
+}
+
+PersistedLogs::LogReadStatus PersistedLogs::ReadLogsFromPrefList(
+ const base::ListValue& list_value) {
+ if (list_value.empty())
+ return MakeRecallStatusHistogram(LIST_EMPTY);
+
+ // For each log, there's two entries in the list (the data and the hash).
+ DCHECK_EQ(0U, list_value.GetSize() % 2);
+ const size_t log_count = list_value.GetSize() / 2;
+
+ // Resize |list_| ahead of time, so that values can be decoded directly into
+ // the elements of the list.
+ DCHECK(list_.empty());
+ list_.resize(log_count);
+
+ for (size_t i = 0; i < log_count; ++i) {
+ if (!ReadBase64String(list_value, i * 2, &list_[i].compressed_log_data) ||
+ !ReadBase64String(list_value, i * 2 + 1, &list_[i].hash)) {
+ list_.clear();
+ return MakeRecallStatusHistogram(LOG_STRING_CORRUPTION);
+ }
+ }
+
+ return MakeRecallStatusHistogram(RECALL_SUCCESS);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/persisted_logs.h b/chromium/components/metrics/persisted_logs.h
new file mode 100644
index 00000000000..73ad79a5b06
--- /dev/null
+++ b/chromium/components/metrics/persisted_logs.h
@@ -0,0 +1,143 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_PERSISTED_LOGS_H_
+#define COMPONENTS_METRICS_PERSISTED_LOGS_H_
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/values.h"
+
+class PrefService;
+
+namespace metrics {
+
+// Maintains a list of unsent logs that are written and restored from disk.
+class PersistedLogs {
+ public:
+ // Used to produce a histogram that keeps track of the status of recalling
+ // persisted per logs.
+ enum LogReadStatus {
+ RECALL_SUCCESS, // We were able to correctly recall a persisted log.
+ LIST_EMPTY, // Attempting to recall from an empty list.
+ LIST_SIZE_MISSING, // Failed to recover list size using GetAsInteger().
+ LIST_SIZE_TOO_SMALL, // Too few elements in the list (less than 3).
+ LIST_SIZE_CORRUPTION, // List size is not as expected.
+ LOG_STRING_CORRUPTION, // Failed to recover log string using GetAsString().
+ CHECKSUM_CORRUPTION, // Failed to verify checksum.
+ CHECKSUM_STRING_CORRUPTION, // Failed to recover checksum string using
+ // GetAsString().
+ DECODE_FAIL, // Failed to decode log.
+ DEPRECATED_XML_PROTO_MISMATCH, // The XML and protobuf logs have
+ // inconsistent data.
+ END_RECALL_STATUS // Number of bins to use to create the histogram.
+ };
+
+ // Constructs a PersistedLogs that stores data in |local_state| under the
+ // preference |pref_name|.
+ // Calling code is responsible for ensuring that the lifetime of |local_state|
+ // is longer than the lifetime of PersistedLogs.
+ //
+ // When saving logs to disk, stores either the first |min_log_count| logs, or
+ // at least |min_log_bytes| bytes of logs, whichever is greater.
+ //
+ // If the optional |max_log_size| parameter is non-zero, all logs larger than
+ // that limit will be skipped when writing to disk.
+ PersistedLogs(PrefService* local_state,
+ const char* pref_name,
+ size_t min_log_count,
+ size_t min_log_bytes,
+ size_t max_log_size);
+ ~PersistedLogs();
+
+ // Write list to storage.
+ void SerializeLogs() const;
+
+ // Reads the list from the preference.
+ LogReadStatus DeserializeLogs();
+
+ // Adds a log to the list.
+ void StoreLog(const std::string& log_data);
+
+ // Stages the most recent log. The staged_log will remain the same even if
+ // additional logs are added.
+ void StageLog();
+
+ // Remove the staged log.
+ void DiscardStagedLog();
+
+ // True if a log has been staged.
+ bool has_staged_log() const { return staged_log_index_ != -1; }
+
+ // Returns the element in the front of the list.
+ const std::string& staged_log() const {
+ DCHECK(has_staged_log());
+ return list_[staged_log_index_].compressed_log_data;
+ }
+
+ // Returns the element in the front of the list.
+ const std::string& staged_log_hash() const {
+ DCHECK(has_staged_log());
+ return list_[staged_log_index_].hash;
+ }
+
+ // The number of elements currently stored.
+ size_t size() const { return list_.size(); }
+
+ // True if there are no stored logs.
+ bool empty() const { return list_.empty(); }
+
+ private:
+ // Writes the list to the ListValue.
+ void WriteLogsToPrefList(base::ListValue* list) const;
+
+ // Reads the list from the ListValue.
+ LogReadStatus ReadLogsFromPrefList(const base::ListValue& list);
+
+ // A weak pointer to the PrefService object to read and write the preference
+ // from. Calling code should ensure this object continues to exist for the
+ // lifetime of the PersistedLogs object.
+ PrefService* local_state_;
+
+ // The name of the preference to serialize logs to/from.
+ const char* pref_name_;
+
+ // We will keep at least this |min_log_count_| logs or |min_log_bytes_| bytes
+ // of logs, whichever is greater, when writing to disk. These apply after
+ // skipping logs greater than |max_log_size_|.
+ const size_t min_log_count_;
+ const size_t min_log_bytes_;
+
+ // Logs greater than this size will not be written to disk.
+ const size_t max_log_size_;
+
+ struct LogHashPair {
+ // Initializes the members based on uncompressed |log_data|.
+ void Init(const std::string& log_data);
+
+ // Compressed log data - a serialized protobuf that's been gzipped.
+ std::string compressed_log_data;
+
+ // The SHA1 hash of log, stored to catch errors from memory corruption.
+ std::string hash;
+ };
+ // A list of all of the stored logs, stored with SHA1 hashes to check for
+ // corruption while they are stored in memory.
+ std::vector<LogHashPair> list_;
+
+ // The index and type of the log staged for upload. If nothing has been
+ // staged, the index will be -1.
+ int staged_log_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(PersistedLogs);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_PERSISTED_LOGS_H_
diff --git a/chromium/components/metrics/persisted_logs_unittest.cc b/chromium/components/metrics/persisted_logs_unittest.cc
new file mode 100644
index 00000000000..b4a79a73ea6
--- /dev/null
+++ b/chromium/components/metrics/persisted_logs_unittest.cc
@@ -0,0 +1,289 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/persisted_logs.h"
+
+#include <stddef.h>
+
+#include "base/base64.h"
+#include "base/macros.h"
+#include "base/rand_util.h"
+#include "base/sha1.h"
+#include "base/values.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace metrics {
+
+namespace {
+
+const char kTestPrefName[] = "TestPref";
+const size_t kLogCountLimit = 3;
+const size_t kLogByteLimit = 1000;
+
+// Compresses |log_data| and returns the result.
+std::string Compress(const std::string& log_data) {
+ std::string compressed_log_data;
+ EXPECT_TRUE(compression::GzipCompress(log_data, &compressed_log_data));
+ return compressed_log_data;
+}
+
+// Generates and returns log data such that its size after compression is at
+// least |min_compressed_size|.
+std::string GenerateLogWithMinCompressedSize(size_t min_compressed_size) {
+ // Since the size check is done against a compressed log, generate enough
+ // data that compresses to larger than |log_size|.
+ std::string rand_bytes = base::RandBytesAsString(min_compressed_size);
+ while (Compress(rand_bytes).size() < min_compressed_size)
+ rand_bytes.append(base::RandBytesAsString(min_compressed_size));
+ std::string base64_data_for_logging;
+ base::Base64Encode(rand_bytes, &base64_data_for_logging);
+ SCOPED_TRACE(testing::Message() << "Using random data "
+ << base64_data_for_logging);
+ return rand_bytes;
+}
+
+class PersistedLogsTest : public testing::Test {
+ public:
+ PersistedLogsTest() {
+ prefs_.registry()->RegisterListPref(kTestPrefName);
+ }
+
+ protected:
+ TestingPrefServiceSimple prefs_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PersistedLogsTest);
+};
+
+class TestPersistedLogs : public PersistedLogs {
+ public:
+ TestPersistedLogs(PrefService* service, size_t min_log_bytes)
+ : PersistedLogs(service, kTestPrefName, kLogCountLimit, min_log_bytes,
+ 0) {
+ }
+
+ // Stages and removes the next log, while testing it's value.
+ void ExpectNextLog(const std::string& expected_log) {
+ StageLog();
+ EXPECT_EQ(staged_log(), Compress(expected_log));
+ DiscardStagedLog();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestPersistedLogs);
+};
+
+} // namespace
+
+// Store and retrieve empty list_value.
+TEST_F(PersistedLogsTest, EmptyLogList) {
+ TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+
+ persisted_logs.SerializeLogs();
+ const base::ListValue* list_value = prefs_.GetList(kTestPrefName);
+ EXPECT_EQ(0U, list_value->GetSize());
+
+ TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+ EXPECT_EQ(PersistedLogs::LIST_EMPTY, result_persisted_logs.DeserializeLogs());
+ EXPECT_EQ(0U, result_persisted_logs.size());
+}
+
+// Store and retrieve a single log value.
+TEST_F(PersistedLogsTest, SingleElementLogList) {
+ TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+
+ persisted_logs.StoreLog("Hello world!");
+ persisted_logs.SerializeLogs();
+
+ TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+ EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
+ result_persisted_logs.DeserializeLogs());
+ EXPECT_EQ(1U, result_persisted_logs.size());
+
+ // Verify that the result log matches the initial log.
+ persisted_logs.StageLog();
+ result_persisted_logs.StageLog();
+ EXPECT_EQ(persisted_logs.staged_log(), result_persisted_logs.staged_log());
+ EXPECT_EQ(persisted_logs.staged_log_hash(),
+ result_persisted_logs.staged_log_hash());
+}
+
+// Store a set of logs over the length limit, but smaller than the min number of
+// bytes.
+TEST_F(PersistedLogsTest, LongButTinyLogList) {
+ TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+
+ size_t log_count = kLogCountLimit * 5;
+ for (size_t i = 0; i < log_count; ++i)
+ persisted_logs.StoreLog("x");
+
+ persisted_logs.SerializeLogs();
+
+ TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+ EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
+ result_persisted_logs.DeserializeLogs());
+ EXPECT_EQ(persisted_logs.size(), result_persisted_logs.size());
+
+ result_persisted_logs.ExpectNextLog("x");
+}
+
+// Store a set of logs over the length limit, but that doesn't reach the minimum
+// number of bytes until after passing the length limit.
+TEST_F(PersistedLogsTest, LongButSmallLogList) {
+ size_t log_count = kLogCountLimit * 5;
+ size_t log_size = 50;
+
+ std::string first_kept = "First to keep";
+ first_kept.resize(log_size, ' ');
+
+ std::string blank_log = std::string(log_size, ' ');
+
+ std::string last_kept = "Last to keep";
+ last_kept.resize(log_size, ' ');
+
+ // Set the byte limit enough to keep everything but the first two logs.
+ const size_t min_log_bytes =
+ Compress(first_kept).length() + Compress(last_kept).length() +
+ (log_count - 4) * Compress(blank_log).length();
+ TestPersistedLogs persisted_logs(&prefs_, min_log_bytes);
+
+ persisted_logs.StoreLog("one");
+ persisted_logs.StoreLog("two");
+ persisted_logs.StoreLog(first_kept);
+ for (size_t i = persisted_logs.size(); i < log_count - 1; ++i) {
+ persisted_logs.StoreLog(blank_log);
+ }
+ persisted_logs.StoreLog(last_kept);
+ persisted_logs.SerializeLogs();
+
+ TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+ EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
+ result_persisted_logs.DeserializeLogs());
+ EXPECT_EQ(persisted_logs.size() - 2, result_persisted_logs.size());
+
+ result_persisted_logs.ExpectNextLog(last_kept);
+ while (result_persisted_logs.size() > 1) {
+ result_persisted_logs.ExpectNextLog(blank_log);
+ }
+ result_persisted_logs.ExpectNextLog(first_kept);
+}
+
+// Store a set of logs within the length limit, but well over the minimum
+// number of bytes.
+TEST_F(PersistedLogsTest, ShortButLargeLogList) {
+ // Make the total byte count about twice the minimum.
+ size_t log_count = kLogCountLimit;
+ size_t log_size = (kLogByteLimit / log_count) * 2;
+ std::string log_data = GenerateLogWithMinCompressedSize(log_size);
+
+ TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+ for (size_t i = 0; i < log_count; ++i) {
+ persisted_logs.StoreLog(log_data);
+ }
+ persisted_logs.SerializeLogs();
+
+ TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+ EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
+ result_persisted_logs.DeserializeLogs());
+ EXPECT_EQ(persisted_logs.size(), result_persisted_logs.size());
+}
+
+// Store a set of logs over the length limit, and over the minimum number of
+// bytes.
+TEST_F(PersistedLogsTest, LongAndLargeLogList) {
+ TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+
+ // Include twice the max number of logs.
+ size_t log_count = kLogCountLimit * 2;
+ // Make the total byte count about four times the minimum.
+ size_t log_size = (kLogByteLimit / log_count) * 4;
+
+ std::string target_log = "First to keep";
+ target_log += GenerateLogWithMinCompressedSize(log_size);
+
+ std::string log_data = GenerateLogWithMinCompressedSize(log_size);
+ for (size_t i = 0; i < log_count; ++i) {
+ if (i == log_count - kLogCountLimit)
+ persisted_logs.StoreLog(target_log);
+ else
+ persisted_logs.StoreLog(log_data);
+ }
+
+ persisted_logs.SerializeLogs();
+
+ TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+ EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
+ result_persisted_logs.DeserializeLogs());
+ EXPECT_EQ(kLogCountLimit, result_persisted_logs.size());
+
+ while (result_persisted_logs.size() > 1) {
+ result_persisted_logs.ExpectNextLog(log_data);
+ }
+ result_persisted_logs.ExpectNextLog(target_log);
+}
+
+// Check that the store/stage/discard functions work as expected.
+TEST_F(PersistedLogsTest, Staging) {
+ TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+ std::string tmp;
+
+ EXPECT_FALSE(persisted_logs.has_staged_log());
+ persisted_logs.StoreLog("one");
+ EXPECT_FALSE(persisted_logs.has_staged_log());
+ persisted_logs.StoreLog("two");
+ persisted_logs.StageLog();
+ EXPECT_TRUE(persisted_logs.has_staged_log());
+ EXPECT_EQ(persisted_logs.staged_log(), Compress("two"));
+ persisted_logs.StoreLog("three");
+ EXPECT_EQ(persisted_logs.staged_log(), Compress("two"));
+ EXPECT_EQ(persisted_logs.size(), 3U);
+ persisted_logs.DiscardStagedLog();
+ EXPECT_FALSE(persisted_logs.has_staged_log());
+ EXPECT_EQ(persisted_logs.size(), 2U);
+ persisted_logs.StageLog();
+ EXPECT_EQ(persisted_logs.staged_log(), Compress("three"));
+ persisted_logs.DiscardStagedLog();
+ persisted_logs.StageLog();
+ EXPECT_EQ(persisted_logs.staged_log(), Compress("one"));
+ persisted_logs.DiscardStagedLog();
+ EXPECT_FALSE(persisted_logs.has_staged_log());
+ EXPECT_EQ(persisted_logs.size(), 0U);
+}
+
+TEST_F(PersistedLogsTest, DiscardOrder) {
+ // Ensure that the correct log is discarded if new logs are pushed while
+ // a log is staged.
+ TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+
+ persisted_logs.StoreLog("one");
+ persisted_logs.StageLog();
+ persisted_logs.StoreLog("two");
+ persisted_logs.DiscardStagedLog();
+ persisted_logs.SerializeLogs();
+
+ TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+ EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
+ result_persisted_logs.DeserializeLogs());
+ EXPECT_EQ(1U, result_persisted_logs.size());
+ result_persisted_logs.ExpectNextLog("two");
+}
+
+
+TEST_F(PersistedLogsTest, Hashes) {
+ const char kFooText[] = "foo";
+ const std::string foo_hash = base::SHA1HashString(kFooText);
+
+ TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+ persisted_logs.StoreLog(kFooText);
+ persisted_logs.StageLog();
+
+ EXPECT_EQ(Compress(kFooText), persisted_logs.staged_log());
+ EXPECT_EQ(foo_hash, persisted_logs.staged_log_hash());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/profiler/content/DEPS b/chromium/components/metrics/profiler/content/DEPS
new file mode 100644
index 00000000000..c74cf95b862
--- /dev/null
+++ b/chromium/components/metrics/profiler/content/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+components/nacl/common",
+ "+content/public/browser",
+ "+content/public/common",
+ "+content/public/test",
+]
diff --git a/chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.cc b/chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.cc
new file mode 100644
index 00000000000..e1449e30572
--- /dev/null
+++ b/chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.cc
@@ -0,0 +1,97 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/profiler/content/content_tracking_synchronizer_delegate.h"
+
+#include "components/metrics/profiler/tracking_synchronizer.h"
+#include "components/nacl/common/nacl_process_type.h"
+#include "content/public/browser/profiler_controller.h"
+#include "content/public/common/process_type.h"
+
+namespace metrics {
+
+namespace {
+
+ProfilerEventProto::TrackedObject::ProcessType AsProtobufProcessType(
+ int process_type) {
+ switch (process_type) {
+ case content::PROCESS_TYPE_BROWSER:
+ return ProfilerEventProto::TrackedObject::BROWSER;
+ case content::PROCESS_TYPE_RENDERER:
+ return ProfilerEventProto::TrackedObject::RENDERER;
+ case content::PROCESS_TYPE_UTILITY:
+ return ProfilerEventProto::TrackedObject::UTILITY;
+ case content::PROCESS_TYPE_ZYGOTE:
+ return ProfilerEventProto::TrackedObject::ZYGOTE;
+ case content::PROCESS_TYPE_SANDBOX_HELPER:
+ return ProfilerEventProto::TrackedObject::SANDBOX_HELPER;
+ case content::PROCESS_TYPE_GPU:
+ return ProfilerEventProto::TrackedObject::GPU;
+ case content::PROCESS_TYPE_PPAPI_PLUGIN:
+ return ProfilerEventProto::TrackedObject::PPAPI_PLUGIN;
+ case content::PROCESS_TYPE_PPAPI_BROKER:
+ return ProfilerEventProto::TrackedObject::PPAPI_BROKER;
+ case PROCESS_TYPE_NACL_LOADER:
+ return ProfilerEventProto::TrackedObject::NACL_LOADER;
+ case PROCESS_TYPE_NACL_BROKER:
+ return ProfilerEventProto::TrackedObject::NACL_BROKER;
+ default:
+ NOTREACHED();
+ return ProfilerEventProto::TrackedObject::UNKNOWN;
+ }
+}
+
+} // namespace
+
+// static
+scoped_ptr<TrackingSynchronizerDelegate>
+ContentTrackingSynchronizerDelegate::Create(
+ TrackingSynchronizer* synchronizer) {
+ return make_scoped_ptr(new ContentTrackingSynchronizerDelegate(synchronizer));
+}
+
+ContentTrackingSynchronizerDelegate::ContentTrackingSynchronizerDelegate(
+ TrackingSynchronizer* synchronizer)
+ : synchronizer_(synchronizer) {
+ DCHECK(synchronizer_);
+ content::ProfilerController::GetInstance()->Register(this);
+}
+
+ContentTrackingSynchronizerDelegate::~ContentTrackingSynchronizerDelegate() {
+ content::ProfilerController::GetInstance()->Unregister(this);
+}
+
+void ContentTrackingSynchronizerDelegate::GetProfilerDataForChildProcesses(
+ int sequence_number,
+ int current_profiling_phase) {
+ // Get profiler data from renderer and browser child processes.
+ content::ProfilerController::GetInstance()->GetProfilerData(
+ sequence_number, current_profiling_phase);
+}
+
+void ContentTrackingSynchronizerDelegate::OnProfilingPhaseCompleted(
+ int profiling_phase) {
+ // Notify renderer and browser child processes.
+ content::ProfilerController::GetInstance()->OnProfilingPhaseCompleted(
+ profiling_phase);
+}
+
+void ContentTrackingSynchronizerDelegate::OnPendingProcesses(
+ int sequence_number,
+ int pending_processes,
+ bool end) {
+ // Notify |synchronizer_|.
+ synchronizer_->OnPendingProcesses(sequence_number, pending_processes, end);
+}
+
+void ContentTrackingSynchronizerDelegate::OnProfilerDataCollected(
+ int sequence_number,
+ const tracked_objects::ProcessDataSnapshot& profiler_data,
+ content::ProcessType process_type) {
+ // Notify |synchronizer_|.
+ synchronizer_->OnProfilerDataCollected(sequence_number, profiler_data,
+ AsProtobufProcessType(process_type));
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.h b/chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.h
new file mode 100644
index 00000000000..c648347c8cf
--- /dev/null
+++ b/chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.h
@@ -0,0 +1,52 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_PROFILER_CONTENT_CONTENT_TRACKING_SYNCHRONIZER_DELEGATE_H_
+#define COMPONENTS_METRICS_PROFILER_CONTENT_CONTENT_TRACKING_SYNCHRONIZER_DELEGATE_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/metrics/profiler/tracking_synchronizer_delegate.h"
+#include "content/public/browser/profiler_subscriber.h"
+
+namespace metrics {
+
+// Provides an implementation of TrackingSynchronizerDelegate for use on
+// //content-based platforms. Specifically, drives the tracking of profiler data
+// for child processes by interacting with content::ProfilerController.
+class ContentTrackingSynchronizerDelegate : public TrackingSynchronizerDelegate,
+ public content::ProfilerSubscriber {
+ public:
+ ~ContentTrackingSynchronizerDelegate() override;
+
+ // Creates a ContentTrackingSynchronizerDelegate that is associated with
+ // |synchronizer_|.
+ static scoped_ptr<TrackingSynchronizerDelegate> Create(
+ TrackingSynchronizer* synchronizer);
+
+ private:
+ ContentTrackingSynchronizerDelegate(TrackingSynchronizer* synchronizer);
+
+ // TrackingSynchronizerDelegate:
+ void GetProfilerDataForChildProcesses(int sequence_number,
+ int current_profiling_phase) override;
+ void OnProfilingPhaseCompleted(int profiling_phase) override;
+
+ // content::ProfilerSubscriber:
+ void OnPendingProcesses(int sequence_number,
+ int pending_processes,
+ bool end) override;
+ void OnProfilerDataCollected(
+ int sequence_number,
+ const tracked_objects::ProcessDataSnapshot& profiler_data,
+ content::ProcessType process_type) override;
+
+ TrackingSynchronizer* const synchronizer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentTrackingSynchronizerDelegate);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_PROFILER_CONTENT_CONTENT_TRACKING_SYNCHRONIZER_DELEGATE_H_
diff --git a/chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.cc b/chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.cc
new file mode 100644
index 00000000000..881d92f3e6f
--- /dev/null
+++ b/chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.cc
@@ -0,0 +1,35 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.h"
+#include "components/metrics/profiler/tracking_synchronizer.h"
+
+namespace metrics {
+
+// static
+scoped_ptr<TrackingSynchronizerDelegate>
+IOSTrackingSynchronizerDelegate::Create(TrackingSynchronizer* synchronizer) {
+ return make_scoped_ptr(new IOSTrackingSynchronizerDelegate(synchronizer));
+}
+
+IOSTrackingSynchronizerDelegate::IOSTrackingSynchronizerDelegate(
+ TrackingSynchronizer* synchronizer)
+ : synchronizer_(synchronizer) {
+ DCHECK(synchronizer_);
+}
+
+IOSTrackingSynchronizerDelegate::~IOSTrackingSynchronizerDelegate() {}
+
+void IOSTrackingSynchronizerDelegate::GetProfilerDataForChildProcesses(
+ int sequence_number,
+ int current_profiling_phase) {
+ // Notify |synchronizer_| that there are no processes pending (on iOS, there
+ // is only the browser process).
+ synchronizer_->OnPendingProcesses(sequence_number, 0, true);
+}
+
+void IOSTrackingSynchronizerDelegate::OnProfilingPhaseCompleted(
+ int profiling_phase) {}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.h b/chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.h
new file mode 100644
index 00000000000..d08476d839d
--- /dev/null
+++ b/chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.h
@@ -0,0 +1,40 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_PROFILER_IOS_IOS_TRACKING_SYNCHRONIZER_DELEGATE_H_
+#define COMPONENTS_METRICS_PROFILER_IOS_IOS_TRACKING_SYNCHRONIZER_DELEGATE_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/metrics/profiler/tracking_synchronizer_delegate.h"
+
+namespace metrics {
+
+// Provides an implementation of TrackingSynchronizerDelegate for usage on iOS.
+// This implementation is minimal, as on iOS there are no child processes.
+class IOSTrackingSynchronizerDelegate : public TrackingSynchronizerDelegate {
+ public:
+ ~IOSTrackingSynchronizerDelegate() override;
+
+ // Creates an IOSTrackingSynchronizerDelegate that is associated with
+ // |synchronizer_|.
+ static scoped_ptr<TrackingSynchronizerDelegate> Create(
+ TrackingSynchronizer* synchronizer);
+
+ private:
+ IOSTrackingSynchronizerDelegate(TrackingSynchronizer* synchronizer);
+
+ // TrackingSynchronizerDelegate:
+ void GetProfilerDataForChildProcesses(int sequence_number,
+ int current_profiling_phase) override;
+ void OnProfilingPhaseCompleted(int profiling_phase) override;
+
+ TrackingSynchronizer* const synchronizer_;
+
+ DISALLOW_COPY_AND_ASSIGN(IOSTrackingSynchronizerDelegate);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_PROFILER_IOS_IOS_TRACKING_SYNCHRONIZER_DELEGATE_H_
diff --git a/chromium/components/metrics/profiler/profiler_metrics_provider.cc b/chromium/components/metrics/profiler/profiler_metrics_provider.cc
new file mode 100644
index 00000000000..df64e293aee
--- /dev/null
+++ b/chromium/components/metrics/profiler/profiler_metrics_provider.cc
@@ -0,0 +1,135 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/profiler/profiler_metrics_provider.h"
+
+#include <ctype.h>
+#include <stddef.h>
+#include <string>
+#include <vector>
+
+#include "base/stl_util.h"
+#include "base/tracked_objects.h"
+#include "components/metrics/metrics_log.h"
+
+namespace metrics {
+namespace {
+
+// Maps a thread name by replacing trailing sequence of digits with "*".
+// Examples:
+// 1. "BrowserBlockingWorker1/23857" => "BrowserBlockingWorker1/*"
+// 2. "Chrome_IOThread" => "Chrome_IOThread"
+std::string MapThreadName(const std::string& thread_name) {
+ size_t i = thread_name.length();
+
+ while (i > 0 && isdigit(thread_name[i - 1])) {
+ --i;
+ }
+
+ if (i == thread_name.length())
+ return thread_name;
+
+ return thread_name.substr(0, i) + '*';
+}
+
+// Normalizes a source filename (which is platform- and build-method-dependent)
+// by extracting the last component of the full file name.
+// Example: "c:\b\build\slave\win\build\src\chrome\app\chrome_main.cc" =>
+// "chrome_main.cc".
+std::string NormalizeFileName(const std::string& file_name) {
+ const size_t offset = file_name.find_last_of("\\/");
+ return offset != std::string::npos ? file_name.substr(offset + 1) : file_name;
+}
+
+void WriteProfilerData(
+ const tracked_objects::ProcessDataPhaseSnapshot& process_data_phase,
+ base::ProcessId process_id,
+ ProfilerEventProto::TrackedObject::ProcessType process_type,
+ ProfilerEventProto* performance_profile) {
+ for (const auto& task : process_data_phase.tasks) {
+ const tracked_objects::DeathDataSnapshot& death_data = task.death_data;
+ ProfilerEventProto::TrackedObject* tracked_object =
+ performance_profile->add_tracked_object();
+ tracked_object->set_birth_thread_name_hash(
+ MetricsLog::Hash(MapThreadName(task.birth.thread_name)));
+ tracked_object->set_exec_thread_name_hash(
+ MetricsLog::Hash(MapThreadName(task.death_thread_name)));
+ tracked_object->set_source_file_name_hash(
+ MetricsLog::Hash(NormalizeFileName(task.birth.location.file_name)));
+ tracked_object->set_source_function_name_hash(
+ MetricsLog::Hash(task.birth.location.function_name));
+ tracked_object->set_source_line_number(task.birth.location.line_number);
+ tracked_object->set_exec_count(death_data.count);
+ tracked_object->set_exec_time_total(death_data.run_duration_sum);
+ tracked_object->set_exec_time_sampled(death_data.run_duration_sample);
+ tracked_object->set_queue_time_total(death_data.queue_duration_sum);
+ tracked_object->set_queue_time_sampled(death_data.queue_duration_sample);
+ tracked_object->set_process_type(process_type);
+ tracked_object->set_process_id(process_id);
+ }
+}
+
+} // namespace
+
+ProfilerMetricsProvider::ProfilerMetricsProvider() {
+}
+
+ProfilerMetricsProvider::ProfilerMetricsProvider(
+ const base::Callback<bool(void)>& cellular_callback)
+ : cellular_callback_(cellular_callback) {
+}
+
+ProfilerMetricsProvider::~ProfilerMetricsProvider() {
+}
+
+void ProfilerMetricsProvider::ProvideGeneralMetrics(
+ ChromeUserMetricsExtension* uma_proto) {
+ DCHECK_EQ(0, uma_proto->profiler_event_size());
+
+ for (auto& event : profiler_events_cache_) {
+ uma_proto->add_profiler_event()->Swap(&event.second);
+ }
+
+ profiler_events_cache_.clear();
+}
+
+void ProfilerMetricsProvider::RecordProfilerData(
+ const tracked_objects::ProcessDataPhaseSnapshot& process_data_phase,
+ base::ProcessId process_id,
+ ProfilerEventProto::TrackedObject::ProcessType process_type,
+ int profiling_phase,
+ base::TimeDelta phase_start,
+ base::TimeDelta phase_finish,
+ const ProfilerEvents& past_events) {
+ // Omit profiler data on connections where it's likely to cost the user money
+ // for us to upload it.
+ if (IsCellularLogicEnabled())
+ return;
+
+ const bool new_phase = !ContainsKey(profiler_events_cache_, profiling_phase);
+ ProfilerEventProto* profiler_event = &profiler_events_cache_[profiling_phase];
+
+ if (new_phase) {
+ profiler_event->set_profile_version(
+ ProfilerEventProto::VERSION_SPLIT_PROFILE);
+ profiler_event->set_time_source(ProfilerEventProto::WALL_CLOCK_TIME);
+ profiler_event->set_profiling_start_ms(phase_start.InMilliseconds());
+ profiler_event->set_profiling_finish_ms(phase_finish.InMilliseconds());
+ for (const auto& event : past_events) {
+ profiler_event->add_past_session_event(event);
+ }
+ }
+
+ WriteProfilerData(process_data_phase, process_id, process_type,
+ profiler_event);
+}
+
+bool ProfilerMetricsProvider::IsCellularLogicEnabled() {
+ if (cellular_callback_.is_null())
+ return false;
+
+ return cellular_callback_.Run();
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/profiler/profiler_metrics_provider.h b/chromium/components/metrics/profiler/profiler_metrics_provider.h
new file mode 100644
index 00000000000..8585fce5549
--- /dev/null
+++ b/chromium/components/metrics/profiler/profiler_metrics_provider.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_PROFILER_PROFILER_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_PROFILER_PROFILER_METRICS_PROVIDER_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "components/metrics/metrics_provider.h"
+#include "components/metrics/profiler/tracking_synchronizer_observer.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+
+namespace tracked_objects {
+struct ProcessDataPhaseSnapshot;
+}
+
+namespace metrics {
+
+// ProfilerMetricsProvider is responsible for filling out the |profiler_event|
+// section of the UMA proto.
+class ProfilerMetricsProvider : public MetricsProvider {
+ public:
+ explicit ProfilerMetricsProvider(
+ const base::Callback<bool(void)>& cellular_callback);
+ // Creates profiler metrics provider with a null callback.
+ ProfilerMetricsProvider();
+ ~ProfilerMetricsProvider() override;
+
+ // MetricsDataProvider:
+ void ProvideGeneralMetrics(ChromeUserMetricsExtension* uma_proto) override;
+
+ // Records the passed profiled data, which should be a snapshot of the
+ // browser's profiled performance during startup for a single process.
+ void RecordProfilerData(
+ const tracked_objects::ProcessDataPhaseSnapshot& process_data,
+ base::ProcessId process_id,
+ ProfilerEventProto::TrackedObject::ProcessType process_type,
+ int profiling_phase,
+ base::TimeDelta phase_start,
+ base::TimeDelta phase_finish,
+ const ProfilerEvents& past_events);
+
+ private:
+ // Returns true if current connection type is cellular and user is assigned to
+ // experimental group for enabled cellular uploads according to
+ // |cellular_callback_|.
+ bool IsCellularLogicEnabled();
+
+ // Saved cache of generated Profiler event protos, to be copied into the UMA
+ // proto when ProvideGeneralMetrics() is called. The map is from a profiling
+ // phase id to the profiler event proto that represents profiler data for the
+ // profiling phase.
+ std::map<int, ProfilerEventProto> profiler_events_cache_;
+
+ // Returns true if current connection type is cellular and user is assigned to
+ // experimental group for enabled cellular uploads.
+ base::Callback<bool(void)> cellular_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProfilerMetricsProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_PROFILER_PROFILER_METRICS_PROVIDER_H_
diff --git a/chromium/components/metrics/profiler/profiler_metrics_provider_unittest.cc b/chromium/components/metrics/profiler/profiler_metrics_provider_unittest.cc
new file mode 100644
index 00000000000..e2e31b922eb
--- /dev/null
+++ b/chromium/components/metrics/profiler/profiler_metrics_provider_unittest.cc
@@ -0,0 +1,277 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/profiler/profiler_metrics_provider.h"
+
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/tracked_objects.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using tracked_objects::ProcessDataPhaseSnapshot;
+using tracked_objects::TaskSnapshot;
+
+namespace metrics {
+
+TEST(ProfilerMetricsProviderTest, RecordData) {
+ // WARNING: If you broke the below check, you've modified how
+ // HashMetricName works. Please also modify all server-side code that
+ // relies on the existing way of hashing.
+ EXPECT_EQ(UINT64_C(1518842999910132863),
+ base::HashMetricName("birth_thread*"));
+
+ ProfilerMetricsProvider profiler_metrics_provider;
+
+ {
+ // Add data from the browser process.
+ ProcessDataPhaseSnapshot process_data_phase;
+ process_data_phase.tasks.push_back(TaskSnapshot());
+ process_data_phase.tasks.back().birth.location.file_name = "a/b/file.h";
+ process_data_phase.tasks.back().birth.location.function_name = "function";
+ process_data_phase.tasks.back().birth.location.line_number = 1337;
+ process_data_phase.tasks.back().birth.thread_name = "birth_thread";
+ process_data_phase.tasks.back().death_data.count = 37;
+ process_data_phase.tasks.back().death_data.run_duration_sum = 31;
+ process_data_phase.tasks.back().death_data.run_duration_max = 17;
+ process_data_phase.tasks.back().death_data.run_duration_sample = 13;
+ process_data_phase.tasks.back().death_data.queue_duration_sum = 8;
+ process_data_phase.tasks.back().death_data.queue_duration_max = 5;
+ process_data_phase.tasks.back().death_data.queue_duration_sample = 3;
+ process_data_phase.tasks.back().death_thread_name = "Still_Alive";
+ process_data_phase.tasks.push_back(TaskSnapshot());
+ process_data_phase.tasks.back().birth.location.file_name = "c\\d\\file2";
+ process_data_phase.tasks.back().birth.location.function_name = "function2";
+ process_data_phase.tasks.back().birth.location.line_number = 1773;
+ process_data_phase.tasks.back().birth.thread_name = "birth_thread2";
+ process_data_phase.tasks.back().death_data.count = 19;
+ process_data_phase.tasks.back().death_data.run_duration_sum = 23;
+ process_data_phase.tasks.back().death_data.run_duration_max = 11;
+ process_data_phase.tasks.back().death_data.run_duration_sample = 7;
+ process_data_phase.tasks.back().death_data.queue_duration_sum = 0;
+ process_data_phase.tasks.back().death_data.queue_duration_max = 0;
+ process_data_phase.tasks.back().death_data.queue_duration_sample = 0;
+ process_data_phase.tasks.back().death_thread_name = "death_thread";
+
+ profiler_metrics_provider.RecordProfilerData(
+ process_data_phase, 177, ProfilerEventProto::TrackedObject::BROWSER, 0,
+ base::TimeDelta::FromMinutes(1), base::TimeDelta::FromMinutes(2),
+ ProfilerEvents());
+ }
+
+ {
+ // Add second phase from the browser process.
+ ProcessDataPhaseSnapshot process_data_phase;
+ process_data_phase.tasks.push_back(TaskSnapshot());
+ process_data_phase.tasks.back().birth.location.file_name = "a/b/file10.h";
+ process_data_phase.tasks.back().birth.location.function_name = "function10";
+ process_data_phase.tasks.back().birth.location.line_number = 101337;
+ process_data_phase.tasks.back().birth.thread_name = "birth_thread_ten";
+ process_data_phase.tasks.back().death_data.count = 1037;
+ process_data_phase.tasks.back().death_data.run_duration_sum = 1031;
+ process_data_phase.tasks.back().death_data.run_duration_max = 1017;
+ process_data_phase.tasks.back().death_data.run_duration_sample = 1013;
+ process_data_phase.tasks.back().death_data.queue_duration_sum = 108;
+ process_data_phase.tasks.back().death_data.queue_duration_max = 105;
+ process_data_phase.tasks.back().death_data.queue_duration_sample = 103;
+ process_data_phase.tasks.back().death_thread_name = "Already_Dead";
+ process_data_phase.tasks.push_back(TaskSnapshot());
+ process_data_phase.tasks.back().birth.location.file_name = "c\\d\\file210";
+ process_data_phase.tasks.back().birth.location.function_name =
+ "function210";
+ process_data_phase.tasks.back().birth.location.line_number = 101773;
+ process_data_phase.tasks.back().birth.thread_name = "birth_thread_ten2";
+ process_data_phase.tasks.back().death_data.count = 1019;
+ process_data_phase.tasks.back().death_data.run_duration_sum = 1023;
+ process_data_phase.tasks.back().death_data.run_duration_max = 1011;
+ process_data_phase.tasks.back().death_data.run_duration_sample = 107;
+ process_data_phase.tasks.back().death_data.queue_duration_sum = 100;
+ process_data_phase.tasks.back().death_data.queue_duration_max = 100;
+ process_data_phase.tasks.back().death_data.queue_duration_sample = 100;
+ process_data_phase.tasks.back().death_thread_name = "death_thread_ten";
+
+ profiler_metrics_provider.RecordProfilerData(
+ process_data_phase, 177, ProfilerEventProto::TrackedObject::BROWSER, 1,
+ base::TimeDelta::FromMinutes(10), base::TimeDelta::FromMinutes(20),
+ ProfilerEvents(1, ProfilerEventProto::EVENT_FIRST_NONEMPTY_PAINT));
+ }
+
+ {
+ // Add data from a renderer process.
+ ProcessDataPhaseSnapshot process_data_phase;
+ process_data_phase.tasks.push_back(TaskSnapshot());
+ process_data_phase.tasks.back().birth.location.file_name = "file3";
+ process_data_phase.tasks.back().birth.location.function_name = "function3";
+ process_data_phase.tasks.back().birth.location.line_number = 7331;
+ process_data_phase.tasks.back().birth.thread_name = "birth_thread3";
+ process_data_phase.tasks.back().death_data.count = 137;
+ process_data_phase.tasks.back().death_data.run_duration_sum = 131;
+ process_data_phase.tasks.back().death_data.run_duration_max = 117;
+ process_data_phase.tasks.back().death_data.run_duration_sample = 113;
+ process_data_phase.tasks.back().death_data.queue_duration_sum = 108;
+ process_data_phase.tasks.back().death_data.queue_duration_max = 105;
+ process_data_phase.tasks.back().death_data.queue_duration_sample = 103;
+ process_data_phase.tasks.back().death_thread_name = "death_thread3";
+ process_data_phase.tasks.push_back(TaskSnapshot());
+ process_data_phase.tasks.back().birth.location.file_name = "";
+ process_data_phase.tasks.back().birth.location.function_name = "";
+ process_data_phase.tasks.back().birth.location.line_number = 7332;
+ process_data_phase.tasks.back().birth.thread_name = "";
+ process_data_phase.tasks.back().death_data.count = 138;
+ process_data_phase.tasks.back().death_data.run_duration_sum = 132;
+ process_data_phase.tasks.back().death_data.run_duration_max = 118;
+ process_data_phase.tasks.back().death_data.run_duration_sample = 114;
+ process_data_phase.tasks.back().death_data.queue_duration_sum = 109;
+ process_data_phase.tasks.back().death_data.queue_duration_max = 106;
+ process_data_phase.tasks.back().death_data.queue_duration_sample = 104;
+ process_data_phase.tasks.back().death_thread_name = "";
+
+ profiler_metrics_provider.RecordProfilerData(
+ process_data_phase, 1177, ProfilerEventProto::TrackedObject::RENDERER,
+ 0, base::TimeDelta::FromMinutes(1), base::TimeDelta::FromMinutes(2),
+ ProfilerEvents());
+ }
+
+ // Capture the data and verify that it is as expected.
+ ChromeUserMetricsExtension uma_proto;
+ profiler_metrics_provider.ProvideGeneralMetrics(&uma_proto);
+
+ // Phase 0
+ ASSERT_EQ(2, uma_proto.profiler_event_size());
+
+ EXPECT_EQ(ProfilerEventProto::VERSION_SPLIT_PROFILE,
+ uma_proto.profiler_event(0).profile_version());
+ EXPECT_EQ(ProfilerEventProto::WALL_CLOCK_TIME,
+ uma_proto.profiler_event(0).time_source());
+ ASSERT_EQ(0, uma_proto.profiler_event(0).past_session_event_size());
+ ASSERT_EQ(60000, uma_proto.profiler_event(0).profiling_start_ms());
+ ASSERT_EQ(120000, uma_proto.profiler_event(0).profiling_finish_ms());
+ ASSERT_EQ(4, uma_proto.profiler_event(0).tracked_object_size());
+
+ const ProfilerEventProto::TrackedObject* tracked_object =
+ &uma_proto.profiler_event(0).tracked_object(0);
+ EXPECT_EQ(base::HashMetricName("file.h"),
+ tracked_object->source_file_name_hash());
+ EXPECT_EQ(base::HashMetricName("function"),
+ tracked_object->source_function_name_hash());
+ EXPECT_EQ(1337, tracked_object->source_line_number());
+ EXPECT_EQ(base::HashMetricName("birth_thread"),
+ tracked_object->birth_thread_name_hash());
+ EXPECT_EQ(37, tracked_object->exec_count());
+ EXPECT_EQ(31, tracked_object->exec_time_total());
+ EXPECT_EQ(13, tracked_object->exec_time_sampled());
+ EXPECT_EQ(8, tracked_object->queue_time_total());
+ EXPECT_EQ(3, tracked_object->queue_time_sampled());
+ EXPECT_EQ(base::HashMetricName("Still_Alive"),
+ tracked_object->exec_thread_name_hash());
+ EXPECT_EQ(177U, tracked_object->process_id());
+ EXPECT_EQ(ProfilerEventProto::TrackedObject::BROWSER,
+ tracked_object->process_type());
+
+ tracked_object = &uma_proto.profiler_event(0).tracked_object(1);
+ EXPECT_EQ(base::HashMetricName("file2"),
+ tracked_object->source_file_name_hash());
+ EXPECT_EQ(base::HashMetricName("function2"),
+ tracked_object->source_function_name_hash());
+ EXPECT_EQ(1773, tracked_object->source_line_number());
+ EXPECT_EQ(base::HashMetricName("birth_thread*"),
+ tracked_object->birth_thread_name_hash());
+ EXPECT_EQ(19, tracked_object->exec_count());
+ EXPECT_EQ(23, tracked_object->exec_time_total());
+ EXPECT_EQ(7, tracked_object->exec_time_sampled());
+ EXPECT_EQ(0, tracked_object->queue_time_total());
+ EXPECT_EQ(0, tracked_object->queue_time_sampled());
+ EXPECT_EQ(base::HashMetricName("death_thread"),
+ tracked_object->exec_thread_name_hash());
+ EXPECT_EQ(177U, tracked_object->process_id());
+ EXPECT_EQ(ProfilerEventProto::TrackedObject::BROWSER,
+ tracked_object->process_type());
+
+ tracked_object = &uma_proto.profiler_event(0).tracked_object(2);
+ EXPECT_EQ(base::HashMetricName("file3"),
+ tracked_object->source_file_name_hash());
+ EXPECT_EQ(base::HashMetricName("function3"),
+ tracked_object->source_function_name_hash());
+ EXPECT_EQ(7331, tracked_object->source_line_number());
+ EXPECT_EQ(base::HashMetricName("birth_thread*"),
+ tracked_object->birth_thread_name_hash());
+ EXPECT_EQ(137, tracked_object->exec_count());
+ EXPECT_EQ(131, tracked_object->exec_time_total());
+ EXPECT_EQ(113, tracked_object->exec_time_sampled());
+ EXPECT_EQ(108, tracked_object->queue_time_total());
+ EXPECT_EQ(103, tracked_object->queue_time_sampled());
+ EXPECT_EQ(base::HashMetricName("death_thread*"),
+ tracked_object->exec_thread_name_hash());
+ EXPECT_EQ(1177U, tracked_object->process_id());
+ EXPECT_EQ(ProfilerEventProto::TrackedObject::RENDERER,
+ tracked_object->process_type());
+
+ tracked_object = &uma_proto.profiler_event(0).tracked_object(3);
+ EXPECT_EQ(base::HashMetricName(""), tracked_object->source_file_name_hash());
+ EXPECT_EQ(base::HashMetricName(""),
+ tracked_object->source_function_name_hash());
+ EXPECT_EQ(7332, tracked_object->source_line_number());
+ EXPECT_EQ(base::HashMetricName(""), tracked_object->birth_thread_name_hash());
+ EXPECT_EQ(138, tracked_object->exec_count());
+ EXPECT_EQ(132, tracked_object->exec_time_total());
+ EXPECT_EQ(114, tracked_object->exec_time_sampled());
+ EXPECT_EQ(109, tracked_object->queue_time_total());
+ EXPECT_EQ(104, tracked_object->queue_time_sampled());
+ EXPECT_EQ(base::HashMetricName(""), tracked_object->exec_thread_name_hash());
+ EXPECT_EQ(ProfilerEventProto::TrackedObject::RENDERER,
+ tracked_object->process_type());
+
+ // Phase 1
+ EXPECT_EQ(ProfilerEventProto::VERSION_SPLIT_PROFILE,
+ uma_proto.profiler_event(1).profile_version());
+ EXPECT_EQ(ProfilerEventProto::WALL_CLOCK_TIME,
+ uma_proto.profiler_event(1).time_source());
+ ASSERT_EQ(1, uma_proto.profiler_event(1).past_session_event_size());
+ ASSERT_EQ(ProfilerEventProto::EVENT_FIRST_NONEMPTY_PAINT,
+ uma_proto.profiler_event(1).past_session_event(0));
+ ASSERT_EQ(600000, uma_proto.profiler_event(1).profiling_start_ms());
+ ASSERT_EQ(1200000, uma_proto.profiler_event(1).profiling_finish_ms());
+ ASSERT_EQ(2, uma_proto.profiler_event(1).tracked_object_size());
+
+ tracked_object = &uma_proto.profiler_event(1).tracked_object(0);
+ EXPECT_EQ(base::HashMetricName("file10.h"),
+ tracked_object->source_file_name_hash());
+ EXPECT_EQ(base::HashMetricName("function10"),
+ tracked_object->source_function_name_hash());
+ EXPECT_EQ(101337, tracked_object->source_line_number());
+ EXPECT_EQ(base::HashMetricName("birth_thread_ten"),
+ tracked_object->birth_thread_name_hash());
+ EXPECT_EQ(1037, tracked_object->exec_count());
+ EXPECT_EQ(1031, tracked_object->exec_time_total());
+ EXPECT_EQ(1013, tracked_object->exec_time_sampled());
+ EXPECT_EQ(108, tracked_object->queue_time_total());
+ EXPECT_EQ(103, tracked_object->queue_time_sampled());
+ EXPECT_EQ(base::HashMetricName("Already_Dead"),
+ tracked_object->exec_thread_name_hash());
+ EXPECT_EQ(177U, tracked_object->process_id());
+ EXPECT_EQ(ProfilerEventProto::TrackedObject::BROWSER,
+ tracked_object->process_type());
+
+ tracked_object = &uma_proto.profiler_event(1).tracked_object(1);
+ EXPECT_EQ(base::HashMetricName("file210"),
+ tracked_object->source_file_name_hash());
+ EXPECT_EQ(base::HashMetricName("function210"),
+ tracked_object->source_function_name_hash());
+ EXPECT_EQ(101773, tracked_object->source_line_number());
+ EXPECT_EQ(base::HashMetricName("birth_thread_ten*"),
+ tracked_object->birth_thread_name_hash());
+ EXPECT_EQ(1019, tracked_object->exec_count());
+ EXPECT_EQ(1023, tracked_object->exec_time_total());
+ EXPECT_EQ(107, tracked_object->exec_time_sampled());
+ EXPECT_EQ(100, tracked_object->queue_time_total());
+ EXPECT_EQ(100, tracked_object->queue_time_sampled());
+ EXPECT_EQ(base::HashMetricName("death_thread_ten"),
+ tracked_object->exec_thread_name_hash());
+ EXPECT_EQ(177U, tracked_object->process_id());
+ EXPECT_EQ(ProfilerEventProto::TrackedObject::BROWSER,
+ tracked_object->process_type());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/profiler/tracking_synchronizer.cc b/chromium/components/metrics/profiler/tracking_synchronizer.cc
new file mode 100644
index 00000000000..67c8361cf72
--- /dev/null
+++ b/chromium/components/metrics/profiler/tracking_synchronizer.cc
@@ -0,0 +1,378 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/profiler/tracking_synchronizer.h"
+
+#include <stddef.h>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/metrics/histogram.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/threading/thread.h"
+#include "base/tracked_objects.h"
+#include "components/metrics/profiler/tracking_synchronizer_observer.h"
+#include "components/variations/variations_associated_data.h"
+
+using base::TimeTicks;
+
+namespace metrics {
+
+namespace {
+
+// Negative numbers are never used as sequence numbers. We explicitly pick a
+// negative number that is "so negative" that even when we add one (as is done
+// when we generated the next sequence number) that it will still be negative.
+// We have code that handles wrapping around on an overflow into negative
+// territory.
+const int kNeverUsableSequenceNumber = -2;
+
+// This singleton instance should be started during the single threaded
+// portion of main(). It initializes globals to provide support for all future
+// calls. This object is created on the UI thread, and it is destroyed after
+// all the other threads have gone away. As a result, it is ok to call it
+// from the UI thread, or for about:profiler.
+static TrackingSynchronizer* g_tracking_synchronizer = NULL;
+
+} // namespace
+
+// The "RequestContext" structure describes an individual request received
+// from the UI. All methods are accessible on UI thread.
+class TrackingSynchronizer::RequestContext {
+ public:
+ // A map from sequence_number_ to the actual RequestContexts.
+ typedef std::map<int, RequestContext*> RequestContextMap;
+
+ RequestContext(
+ const base::WeakPtr<TrackingSynchronizerObserver>& callback_object,
+ int sequence_number)
+ : callback_object_(callback_object),
+ sequence_number_(sequence_number),
+ received_process_group_count_(0),
+ processes_pending_(0) {
+ }
+ ~RequestContext() {}
+
+ void SetReceivedProcessGroupCount(bool done) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ received_process_group_count_ = done;
+ }
+
+ // Methods for book keeping of processes_pending_.
+ void IncrementProcessesPending() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ++processes_pending_;
+ }
+
+ void AddProcessesPending(int processes_pending) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ processes_pending_ += processes_pending;
+ }
+
+ void DecrementProcessesPending() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ --processes_pending_;
+ }
+
+ // Records that we are waiting for one less tracking data from a process for
+ // the given sequence number. If |received_process_group_count_| and
+ // |processes_pending_| are zero, then delete the current object by calling
+ // Unregister.
+ void DeleteIfAllDone() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (processes_pending_ <= 0 && received_process_group_count_)
+ RequestContext::Unregister(sequence_number_);
+ }
+
+ // Register |callback_object| in |outstanding_requests_| map for the given
+ // |sequence_number|.
+ static RequestContext* Register(
+ int sequence_number,
+ const base::WeakPtr<TrackingSynchronizerObserver>& callback_object) {
+ RequestContext* request = new RequestContext(
+ callback_object, sequence_number);
+ outstanding_requests_.Get()[sequence_number] = request;
+
+ return request;
+ }
+
+ // Find the |RequestContext| in |outstanding_requests_| map for the given
+ // |sequence_number|.
+ static RequestContext* GetRequestContext(int sequence_number) {
+ RequestContextMap::iterator it =
+ outstanding_requests_.Get().find(sequence_number);
+ if (it == outstanding_requests_.Get().end())
+ return NULL;
+
+ RequestContext* request = it->second;
+ DCHECK_EQ(sequence_number, request->sequence_number_);
+ return request;
+ }
+
+ // Delete the entry for the given |sequence_number| from
+ // |outstanding_requests_| map. This method is called when all changes have
+ // been acquired, or when the wait time expires (whichever is sooner).
+ static void Unregister(int sequence_number) {
+ RequestContextMap::iterator it =
+ outstanding_requests_.Get().find(sequence_number);
+ if (it == outstanding_requests_.Get().end())
+ return;
+
+ RequestContext* request = it->second;
+ DCHECK_EQ(sequence_number, request->sequence_number_);
+ bool received_process_group_count = request->received_process_group_count_;
+ int unresponsive_processes = request->processes_pending_;
+
+ if (request->callback_object_.get())
+ request->callback_object_->FinishedReceivingProfilerData();
+
+ delete request;
+ outstanding_requests_.Get().erase(it);
+
+ UMA_HISTOGRAM_BOOLEAN("Profiling.ReceivedProcessGroupCount",
+ received_process_group_count);
+ UMA_HISTOGRAM_COUNTS("Profiling.PendingProcessNotResponding",
+ unresponsive_processes);
+ }
+
+ // Delete all the entries in |outstanding_requests_| map.
+ static void OnShutdown() {
+ // Just in case we have any pending tasks, clear them out.
+ while (!outstanding_requests_.Get().empty()) {
+ RequestContextMap::iterator it = outstanding_requests_.Get().begin();
+ delete it->second;
+ outstanding_requests_.Get().erase(it);
+ }
+ }
+
+ // Used to verify that methods are called on the UI thread (the thread on
+ // which this object was created).
+ base::ThreadChecker thread_checker_;
+
+ // Requests are made to asynchronously send data to the |callback_object_|.
+ base::WeakPtr<TrackingSynchronizerObserver> callback_object_;
+
+ // The sequence number used by the most recent update request to contact all
+ // processes.
+ int sequence_number_;
+
+ // Indicates if we have received all pending processes count.
+ bool received_process_group_count_;
+
+ // The number of pending processes (browser, all renderer processes and
+ // browser child processes) that have not yet responded to requests.
+ int processes_pending_;
+
+ // Map of all outstanding RequestContexts, from sequence_number_ to
+ // RequestContext.
+ static base::LazyInstance<RequestContextMap>::Leaky outstanding_requests_;
+};
+
+// static
+base::LazyInstance
+ <TrackingSynchronizer::RequestContext::RequestContextMap>::Leaky
+ TrackingSynchronizer::RequestContext::outstanding_requests_ =
+ LAZY_INSTANCE_INITIALIZER;
+
+// TrackingSynchronizer methods and members.
+
+TrackingSynchronizer::TrackingSynchronizer(
+ scoped_ptr<base::TickClock> clock,
+ const TrackingSynchronizerDelegateFactory& delegate_factory)
+ : last_used_sequence_number_(kNeverUsableSequenceNumber),
+ clock_(std::move(clock)) {
+ DCHECK(!g_tracking_synchronizer);
+ g_tracking_synchronizer = this;
+ phase_start_times_.push_back(clock_->NowTicks());
+ delegate_ = delegate_factory.Run(this);
+}
+
+TrackingSynchronizer::~TrackingSynchronizer() {
+ // Just in case we have any pending tasks, clear them out.
+ RequestContext::OnShutdown();
+
+ g_tracking_synchronizer = NULL;
+}
+
+// static
+void TrackingSynchronizer::FetchProfilerDataAsynchronously(
+ const base::WeakPtr<TrackingSynchronizerObserver>& callback_object) {
+ if (!g_tracking_synchronizer) {
+ // System teardown is happening.
+ return;
+ }
+
+ int sequence_number = g_tracking_synchronizer->RegisterAndNotifyAllProcesses(
+ callback_object);
+
+ // Post a task that would be called after waiting for wait_time. This acts
+ // as a watchdog, to cancel the requests for non-responsive processes.
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&RequestContext::Unregister, sequence_number),
+ base::TimeDelta::FromMinutes(1));
+}
+
+// static
+void TrackingSynchronizer::OnProfilingPhaseCompleted(
+ ProfilerEventProto::ProfilerEvent profiling_event) {
+ if (!g_tracking_synchronizer) {
+ // System teardown is happening.
+ return;
+ }
+
+ g_tracking_synchronizer->NotifyAllProcessesOfProfilingPhaseCompletion(
+ profiling_event);
+}
+
+void TrackingSynchronizer::OnPendingProcesses(int sequence_number,
+ int pending_processes,
+ bool end) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ RequestContext* request = RequestContext::GetRequestContext(sequence_number);
+ if (!request)
+ return;
+ request->AddProcessesPending(pending_processes);
+ request->SetReceivedProcessGroupCount(end);
+ request->DeleteIfAllDone();
+}
+
+void TrackingSynchronizer::OnProfilerDataCollected(
+ int sequence_number,
+ const tracked_objects::ProcessDataSnapshot& profiler_data,
+ ProfilerEventProto::TrackedObject::ProcessType process_type) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DecrementPendingProcessesAndSendData(sequence_number, profiler_data,
+ process_type);
+}
+
+int TrackingSynchronizer::RegisterAndNotifyAllProcesses(
+ const base::WeakPtr<TrackingSynchronizerObserver>& callback_object) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ int sequence_number = GetNextAvailableSequenceNumber();
+
+ RequestContext* request =
+ RequestContext::Register(sequence_number, callback_object);
+
+ // Increment pending process count for sending browser's profiler data.
+ request->IncrementProcessesPending();
+
+ const int current_profiling_phase = phase_completion_events_sequence_.size();
+
+ delegate_->GetProfilerDataForChildProcesses(sequence_number,
+ current_profiling_phase);
+
+ // Send process data snapshot from browser process.
+ tracked_objects::ProcessDataSnapshot process_data_snapshot;
+ tracked_objects::ThreadData::Snapshot(current_profiling_phase,
+ &process_data_snapshot);
+
+ DecrementPendingProcessesAndSendData(
+ sequence_number, process_data_snapshot,
+ ProfilerEventProto::TrackedObject::BROWSER);
+
+ return sequence_number;
+}
+
+void TrackingSynchronizer::RegisterPhaseCompletion(
+ ProfilerEventProto::ProfilerEvent profiling_event) {
+ phase_completion_events_sequence_.push_back(profiling_event);
+ phase_start_times_.push_back(clock_->NowTicks());
+}
+
+void TrackingSynchronizer::NotifyAllProcessesOfProfilingPhaseCompletion(
+ ProfilerEventProto::ProfilerEvent profiling_event) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (variations::GetVariationParamValue("UMALogPhasedProfiling",
+ "send_split_profiles") == "false") {
+ return;
+ }
+
+ int profiling_phase = phase_completion_events_sequence_.size();
+
+ // If you hit this check, stop and think. You just added a new profiling
+ // phase. Each profiling phase takes additional memory in DeathData's list of
+ // snapshots. We cannot grow it indefinitely. Consider collapsing older phases
+ // after they were sent to UMA server, or other ways to save memory.
+ DCHECK_LT(profiling_phase, 1);
+
+ RegisterPhaseCompletion(profiling_event);
+
+ delegate_->OnProfilingPhaseCompleted(profiling_phase);
+
+ // Notify browser process.
+ tracked_objects::ThreadData::OnProfilingPhaseCompleted(profiling_phase);
+}
+
+void TrackingSynchronizer::SendData(
+ const tracked_objects::ProcessDataSnapshot& profiler_data,
+ ProfilerEventProto::TrackedObject::ProcessType process_type,
+ TrackingSynchronizerObserver* observer) const {
+ // We are going to loop though past profiling phases and notify the request
+ // about each phase that is contained in profiler_data. past_events
+ // will track the set of past profiling events as we go.
+ ProfilerEvents past_events;
+
+ // Go through all completed phases, and through the current one. The current
+ // one is not in phase_completion_events_sequence_, but note the <=
+ // comparison.
+ for (size_t phase = 0; phase <= phase_completion_events_sequence_.size();
+ ++phase) {
+ auto it = profiler_data.phased_snapshots.find(phase);
+
+ if (it != profiler_data.phased_snapshots.end()) {
+ // If the phase is contained in the received snapshot, notify the
+ // request.
+ const base::TimeTicks phase_start = phase_start_times_[phase];
+ const base::TimeTicks phase_finish = phase + 1 < phase_start_times_.size()
+ ? phase_start_times_[phase + 1]
+ : clock_->NowTicks();
+ observer->ReceivedProfilerData(
+ ProfilerDataAttributes(phase, profiler_data.process_id, process_type,
+ phase_start, phase_finish),
+ it->second, past_events);
+ }
+
+ if (phase < phase_completion_events_sequence_.size()) {
+ past_events.push_back(phase_completion_events_sequence_[phase]);
+ }
+ }
+}
+
+void TrackingSynchronizer::DecrementPendingProcessesAndSendData(
+ int sequence_number,
+ const tracked_objects::ProcessDataSnapshot& profiler_data,
+ ProfilerEventProto::TrackedObject::ProcessType process_type) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ RequestContext* request = RequestContext::GetRequestContext(sequence_number);
+ if (!request)
+ return;
+
+ TrackingSynchronizerObserver* observer = request->callback_object_.get();
+ if (observer)
+ SendData(profiler_data, process_type, observer);
+
+ // Delete request if we have heard back from all child processes.
+ request->DecrementProcessesPending();
+ request->DeleteIfAllDone();
+}
+
+int TrackingSynchronizer::GetNextAvailableSequenceNumber() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ ++last_used_sequence_number_;
+
+ // Watch out for wrapping to a negative number.
+ if (last_used_sequence_number_ < 0)
+ last_used_sequence_number_ = 1;
+ return last_used_sequence_number_;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/profiler/tracking_synchronizer.h b/chromium/components/metrics/profiler/tracking_synchronizer.h
new file mode 100644
index 00000000000..511cf2b5971
--- /dev/null
+++ b/chromium/components/metrics/profiler/tracking_synchronizer.h
@@ -0,0 +1,167 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_H_
+#define COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "base/tracked_objects.h"
+#include "components/metrics/profiler/tracking_synchronizer_delegate.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+
+// This class maintains state that is used to upload profiler data from the
+// various processes, into the browser process. Such transactions are usually
+// instigated by the browser. In general, a process will respond by gathering
+// profiler data, and transmitting the pickled profiler data. We collect the
+// data in asynchronous mode that doesn't block the UI thread.
+//
+// To assure that all the processes have responded, a counter is maintained
+// to indicate the number of pending (not yet responsive) processes. We tag
+// each group of requests with a sequence number. For each group of requests, we
+// create RequestContext object which stores the sequence number, pending
+// processes and the callback_object that needs to be notified when we receive
+// an update from processes. When an update arrives we find the RequestContext
+// associated with sequence number and send the unpickled profiler data to the
+// |callback_object_|.
+
+namespace metrics {
+
+class TrackingSynchronizerObserver;
+
+typedef base::Callback<scoped_ptr<TrackingSynchronizerDelegate>(
+ TrackingSynchronizer*)> TrackingSynchronizerDelegateFactory;
+
+class TrackingSynchronizer
+ : public base::RefCountedThreadSafe<TrackingSynchronizer> {
+ public:
+ // Construction also sets up the global singleton instance. This instance is
+ // used to communicate between the IO and UI thread, and is destroyed only as
+ // the main thread (browser_main) terminates, which means the IO thread has
+ // already completed, and will not need this instance any further.
+ // |clock| is a clock used for durations of profiling phases.
+ // |delegate| is used to abstract platform-specific profiling functionality.
+ TrackingSynchronizer(
+ scoped_ptr<base::TickClock> clock,
+ const TrackingSynchronizerDelegateFactory& delegate_factory);
+
+ // Contact all processes, and get them to upload to the browser any/all
+ // changes to profiler data. It calls |callback_object|'s SetData method with
+ // the data received from each sub-process.
+ // This method is accessible on the UI thread.
+ static void FetchProfilerDataAsynchronously(
+ const base::WeakPtr<TrackingSynchronizerObserver>& callback_object);
+
+ // Called when a profiling phase completes. |profiling_event| is the event
+ // that triggered the completion of the current phase, and begins a new phase.
+ // This method is accessible on the UI thread.
+ static void OnProfilingPhaseCompleted(
+ ProfilerEventProto::ProfilerEvent profiling_event);
+
+ // Send profiler_data back to |callback_object_| by calling
+ // DecrementPendingProcessesAndSendData which records that we are waiting
+ // for one less profiler data from renderer or browser child process for the
+ // given sequence number. This method is accessible on UI thread.
+ void OnProfilerDataCollected(
+ int sequence_number,
+ const tracked_objects::ProcessDataSnapshot& profiler_data,
+ ProfilerEventProto::TrackedObject::ProcessType process_type);
+
+ // Update the number of pending processes for the given |sequence_number|.
+ // This is called on UI thread.
+ void OnPendingProcesses(int sequence_number, int pending_processes, bool end);
+
+ protected:
+ virtual ~TrackingSynchronizer();
+
+ // Update the sequence of completed phases with a new phase completion info.
+ void RegisterPhaseCompletion(
+ ProfilerEventProto::ProfilerEvent profiling_event);
+
+ // Notify |observer| about |profiler_data| received from process of type
+ // |process_type|.
+ void SendData(const tracked_objects::ProcessDataSnapshot& profiler_data,
+ ProfilerEventProto::TrackedObject::ProcessType process_type,
+ TrackingSynchronizerObserver* observer) const;
+
+ private:
+ friend class base::RefCountedThreadSafe<TrackingSynchronizer>;
+
+ class RequestContext;
+
+ // Establish a new sequence_number_, and use it to notify all the processes of
+ // the need to supply, to the browser, their tracking data. It also registers
+ // |callback_object| in |outstanding_requests_| map. Return the
+ // sequence_number_ that was used. This method is accessible on UI thread.
+ int RegisterAndNotifyAllProcesses(
+ const base::WeakPtr<TrackingSynchronizerObserver>& callback_object);
+
+ // Notifies all processes of a completion of a profiling phase.
+ // |profiling_event| is the event associated with the phase change.
+ void NotifyAllProcessesOfProfilingPhaseCompletion(
+ ProfilerEventProto::ProfilerEvent profiling_event);
+
+ // It finds the RequestContext for the given |sequence_number| and notifies
+ // the RequestContext's |callback_object_| about the |value|. This is called
+ // whenever we receive profiler data from processes. It also records that we
+ // are waiting for one less profiler data from a process for the given
+ // sequence number. If we have received a response from all renderers and
+ // browser processes, then it calls RequestContext's DeleteIfAllDone to delete
+ // the entry for sequence_number. This method is accessible on UI thread.
+ void DecrementPendingProcessesAndSendData(
+ int sequence_number,
+ const tracked_objects::ProcessDataSnapshot& profiler_data,
+ ProfilerEventProto::TrackedObject::ProcessType process_type);
+
+ // Get a new sequence number to be sent to processes from browser process.
+ // This method is accessible on UI thread.
+ int GetNextAvailableSequenceNumber();
+
+ // Used to verify that certain methods are called on the UI thread (the thread
+ // on which this object was created).
+ base::ThreadChecker thread_checker_;
+
+ // We don't track the actual processes that are contacted for an update, only
+ // the count of the number of processes, and we can sometimes time-out and
+ // give up on a "slow to respond" process. We use a sequence_number to be
+ // sure a response from a process is associated with the current round of
+ // requests. All sequence numbers used are non-negative.
+ // last_used_sequence_number_ is the most recently used number (used to avoid
+ // reuse for a long time).
+ int last_used_sequence_number_;
+
+ // Sequence of events associated with already completed profiling phases. The
+ // index in the vector is the phase number. The current phase is not included.
+ std::vector<ProfilerEventProto::ProfilerEvent>
+ phase_completion_events_sequence_;
+
+ // Clock for profiling phase durations.
+ const scoped_ptr<base::TickClock> clock_;
+
+ // Times of starts of all profiling phases, including the current phase. The
+ // index in the vector is the phase number.
+ std::vector<base::TimeTicks> phase_start_times_;
+
+ // This object's delegate.
+ // NOTE: Leave this ivar last so that the delegate is torn down first at
+ // destruction, as it has a reference to this object.
+ scoped_ptr<TrackingSynchronizerDelegate> delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrackingSynchronizer);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_H_
diff --git a/chromium/components/metrics/profiler/tracking_synchronizer_delegate.h b/chromium/components/metrics/profiler/tracking_synchronizer_delegate.h
new file mode 100644
index 00000000000..23fc15fbfc0
--- /dev/null
+++ b/chromium/components/metrics/profiler/tracking_synchronizer_delegate.h
@@ -0,0 +1,31 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_DELEGATE_H_
+#define COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_DELEGATE_H_
+
+
+namespace metrics {
+
+class TrackingSynchronizer;
+
+// An abstraction of TrackingSynchronizer-related operations that depend on the
+// platform.
+class TrackingSynchronizerDelegate {
+ public:
+ virtual ~TrackingSynchronizerDelegate() {}
+
+ // Should perform the platform-specific action that is needed to start
+ // gathering profiler data for all relevant child processes.
+ virtual void GetProfilerDataForChildProcesses(
+ int sequence_number,
+ int current_profiling_phase) = 0;
+
+ // Called when |profiling_phase| has completed.
+ virtual void OnProfilingPhaseCompleted(int profiling_phase) = 0;
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_DELEGATE_H_
diff --git a/chromium/components/metrics/profiler/tracking_synchronizer_observer.cc b/chromium/components/metrics/profiler/tracking_synchronizer_observer.cc
new file mode 100644
index 00000000000..2a8eb3d2e4b
--- /dev/null
+++ b/chromium/components/metrics/profiler/tracking_synchronizer_observer.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/profiler/tracking_synchronizer_observer.h"
+
+namespace metrics {
+
+ProfilerDataAttributes::ProfilerDataAttributes(
+ int profiling_phase,
+ base::ProcessId process_id,
+ ProfilerEventProto::TrackedObject::ProcessType process_type,
+ base::TimeTicks phase_start,
+ base::TimeTicks phase_finish)
+ : profiling_phase(profiling_phase),
+ process_id(process_id),
+ process_type(process_type),
+ phase_start(phase_start),
+ phase_finish(phase_finish) {}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/profiler/tracking_synchronizer_observer.h b/chromium/components/metrics/profiler/tracking_synchronizer_observer.h
new file mode 100644
index 00000000000..c35de35cef2
--- /dev/null
+++ b/chromium/components/metrics/profiler/tracking_synchronizer_observer.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_OBSERVER_H_
+#define COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_OBSERVER_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/process/process_handle.h"
+#include "base/time/time.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+
+namespace base {
+class TimeDelta;
+}
+
+namespace tracked_objects {
+struct ProcessDataPhaseSnapshot;
+}
+
+namespace metrics {
+
+// Set of profiling events, in no guaranteed order. Implemented as a vector
+// because we don't need to have an efficient .find() on it, so vector<> is more
+// efficient.
+typedef std::vector<ProfilerEventProto::ProfilerEvent> ProfilerEvents;
+
+// Attributes of profiler data passed to
+// TrackingSynchronizerObserver::ReceivedProfilerData.
+struct ProfilerDataAttributes {
+ ProfilerDataAttributes(
+ int profiling_phase,
+ base::ProcessId process_id,
+ ProfilerEventProto::TrackedObject::ProcessType process_type,
+ base::TimeTicks phase_start,
+ base::TimeTicks phase_finish);
+
+ // 0-indexed profiling phase number.
+ const int profiling_phase;
+
+ // ID of the process that reported the data.
+ const base::ProcessId process_id;
+
+ // Type of the process that reported the data.
+ const ProfilerEventProto::TrackedObject::ProcessType process_type;
+
+ // Time of the profiling phase start.
+ const base::TimeTicks phase_start;
+
+ // Time of the profiling phase finish.
+ const base::TimeTicks phase_finish;
+};
+
+// Observer for notifications from the TrackingSynchronizer class.
+class TrackingSynchronizerObserver {
+ public:
+ // Received |process_data_phase| for profiling phase and process defined by
+ // |attributes|.
+ // Each completed phase is associated with an event that triggered the
+ // completion of the phase. |past_events| contains the set of events that
+ // completed prior to the reported phase. This data structure is useful for
+ // quickly computing the full set of profiled traces that occurred before or
+ // after a given event.
+ // The observer should assume there might be more data coming until
+ // FinishedReceivingData() is called.
+ virtual void ReceivedProfilerData(
+ const ProfilerDataAttributes& attributes,
+ const tracked_objects::ProcessDataPhaseSnapshot& process_data_phase,
+ const ProfilerEvents& past_events) = 0;
+
+ // The observer should not expect any more calls to |ReceivedProfilerData()|
+ // (without re-registering). This is sent either when data from all processes
+ // has been gathered, or when the request times out.
+ virtual void FinishedReceivingProfilerData() {}
+
+ protected:
+ TrackingSynchronizerObserver() {}
+ virtual ~TrackingSynchronizerObserver() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TrackingSynchronizerObserver);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_OBSERVER_H_
diff --git a/chromium/components/metrics/profiler/tracking_synchronizer_unittest.cc b/chromium/components/metrics/profiler/tracking_synchronizer_unittest.cc
new file mode 100644
index 00000000000..f1df9eb4181
--- /dev/null
+++ b/chromium/components/metrics/profiler/tracking_synchronizer_unittest.cc
@@ -0,0 +1,154 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/profiler/tracking_synchronizer.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/tracked_objects.h"
+#include "components/metrics/profiler/tracking_synchronizer_delegate.h"
+#include "components/metrics/profiler/tracking_synchronizer_observer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using tracked_objects::ProcessDataPhaseSnapshot;
+using tracked_objects::TaskSnapshot;
+
+namespace metrics {
+
+namespace {
+
+class TestDelegate : public TrackingSynchronizerDelegate {
+ public:
+ ~TestDelegate() override {}
+
+ static scoped_ptr<TrackingSynchronizerDelegate> Create(
+ TrackingSynchronizer* synchronizer) {
+ return make_scoped_ptr(new TestDelegate());
+ }
+
+ private:
+ TestDelegate() {}
+
+ // TrackingSynchronizerDelegate:
+ void GetProfilerDataForChildProcesses(int sequence_number,
+ int current_profiling_phase) override {}
+ void OnProfilingPhaseCompleted(int profiling_phase) override {}
+};
+
+class TestObserver : public TrackingSynchronizerObserver {
+ public:
+ TestObserver() {}
+
+ ~TestObserver() override {
+ EXPECT_TRUE(got_phase_0_);
+ EXPECT_TRUE(got_phase_1_);
+ }
+
+ void ReceivedProfilerData(
+ const ProfilerDataAttributes& attributes,
+ const tracked_objects::ProcessDataPhaseSnapshot& process_data_phase,
+ const ProfilerEvents& past_events) override {
+ EXPECT_EQ(static_cast<base::ProcessId>(239), attributes.process_id);
+ EXPECT_EQ(ProfilerEventProto::TrackedObject::PPAPI_PLUGIN,
+ attributes.process_type);
+ ASSERT_EQ(1u, process_data_phase.tasks.size());
+
+ switch (attributes.profiling_phase) {
+ case 0:
+ EXPECT_FALSE(got_phase_0_);
+ got_phase_0_ = true;
+
+ EXPECT_EQ(base::TimeTicks() + base::TimeDelta::FromMilliseconds(111),
+ attributes.phase_start);
+ EXPECT_EQ(base::TimeTicks() + base::TimeDelta::FromMilliseconds(333),
+ attributes.phase_finish);
+
+ EXPECT_EQ("death_thread0",
+ process_data_phase.tasks[0].death_thread_name);
+ EXPECT_EQ(0u, past_events.size());
+ break;
+
+ case 1:
+ EXPECT_FALSE(got_phase_1_);
+ got_phase_1_ = true;
+
+ EXPECT_EQ(base::TimeTicks() + base::TimeDelta::FromMilliseconds(333),
+ attributes.phase_start);
+ EXPECT_EQ(base::TimeTicks() + base::TimeDelta::FromMilliseconds(777),
+ attributes.phase_finish);
+
+ EXPECT_EQ("death_thread1",
+ process_data_phase.tasks[0].death_thread_name);
+ ASSERT_EQ(1u, past_events.size());
+ EXPECT_EQ(ProfilerEventProto::EVENT_FIRST_NONEMPTY_PAINT,
+ past_events[0]);
+ break;
+
+ default:
+ bool profiling_phase_is_neither_0_nor_1 = true;
+ EXPECT_FALSE(profiling_phase_is_neither_0_nor_1);
+ }
+ }
+
+ private:
+ bool got_phase_0_ = false;
+ bool got_phase_1_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(TestObserver);
+};
+
+class TestTrackingSynchronizer : public TrackingSynchronizer {
+ public:
+ explicit TestTrackingSynchronizer(scoped_ptr<base::TickClock> clock)
+ : TrackingSynchronizer(std::move(clock),
+ base::Bind(&TestDelegate::Create)) {}
+
+ using TrackingSynchronizer::RegisterPhaseCompletion;
+ using TrackingSynchronizer::SendData;
+
+ private:
+ ~TestTrackingSynchronizer() override {}
+};
+
+} // namespace
+
+TEST(TrackingSynchronizerTest, ProfilerData) {
+ // Testing how TrackingSynchronizer reports 2 phases of profiling.
+ auto clock = new base::SimpleTestTickClock(); // Will be owned by
+ // |tracking_synchronizer|.
+ clock->Advance(base::TimeDelta::FromMilliseconds(111));
+
+ scoped_refptr<TestTrackingSynchronizer> tracking_synchronizer =
+ new TestTrackingSynchronizer(make_scoped_ptr(clock));
+
+ clock->Advance(base::TimeDelta::FromMilliseconds(222));
+
+ tracking_synchronizer->RegisterPhaseCompletion(
+ ProfilerEventProto::EVENT_FIRST_NONEMPTY_PAINT);
+
+ tracked_objects::ProcessDataSnapshot profiler_data;
+ ProcessDataPhaseSnapshot snapshot0;
+ tracked_objects::TaskSnapshot task_snapshot0;
+ task_snapshot0.death_thread_name = "death_thread0";
+ snapshot0.tasks.push_back(task_snapshot0);
+ ProcessDataPhaseSnapshot snapshot1;
+ profiler_data.phased_snapshots[0] = snapshot0;
+ tracked_objects::TaskSnapshot task_snapshot1;
+ task_snapshot1.death_thread_name = "death_thread1";
+ snapshot1.tasks.push_back(task_snapshot1);
+ profiler_data.phased_snapshots[1] = snapshot1;
+ profiler_data.process_id = 239;
+
+ clock->Advance(base::TimeDelta::FromMilliseconds(444));
+ TestObserver test_observer;
+ tracking_synchronizer->SendData(
+ profiler_data, ProfilerEventProto::TrackedObject::PPAPI_PLUGIN,
+ &test_observer);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/proto/BUILD.gn b/chromium/components/metrics/proto/BUILD.gn
new file mode 100644
index 00000000000..3271f184cdf
--- /dev/null
+++ b/chromium/components/metrics/proto/BUILD.gn
@@ -0,0 +1,24 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/protobuf/proto_library.gni")
+
+# GYP version: components/
+proto_library("proto") {
+ sources = [
+ "call_stack_profile.proto",
+ "cast_logs.proto",
+ "chrome_user_metrics_extension.proto",
+ "histogram_event.proto",
+ "memory_leak_report.proto",
+ "omnibox_event.proto",
+ "omnibox_input_type.proto",
+ "perf_data.proto",
+ "perf_stat.proto",
+ "profiler_event.proto",
+ "sampled_profile.proto",
+ "system_profile.proto",
+ "user_action_event.proto",
+ ]
+}
diff --git a/chromium/components/metrics/proto/call_stack_profile.proto b/chromium/components/metrics/proto/call_stack_profile.proto
new file mode 100644
index 00000000000..e41b3390224
--- /dev/null
+++ b/chromium/components/metrics/proto/call_stack_profile.proto
@@ -0,0 +1,63 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Call stack sample data for a given profiling session.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "CallStackProfileProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 5
+message CallStackProfile {
+ // Describes an entry in the callstack.
+ message Entry {
+ // Instruction pointer subtracted by module base.
+ optional uint64 address = 1;
+
+ // Index to the module identifier in |module_ids| of CallStackProfile.
+ optional int32 module_id_index = 2;
+ }
+
+ // A sample consisting of one or more callstacks with the same stack frames
+ // and instruction pointers.
+ message Sample {
+ // The callstack. Sample.entries[0] represents the call on the top of the
+ // stack.
+ repeated Entry entry = 1;
+
+ // Number of times this stack signature occurs.
+ optional int64 count = 2;
+ }
+
+ // Uniquely identifies a module.
+ message ModuleIdentifier {
+ // A hash that uniquely identifies a particular program version with high
+ // probability. This is parsed from headers of the loaded module.
+ // For binaries generated by GNU tools:
+ // Contents of the .note.gnu.build-id field.
+ // On Windows:
+ // GUID + AGE in the debug image headers of a module.
+ optional string build_id = 1;
+
+ // MD5Sum Prefix of the module name. This is the same hashing scheme as used
+ // to hash UMA histogram names.
+ optional fixed64 name_md5_prefix = 2;
+ }
+
+ // The callstack and counts.
+ repeated Sample sample = 1;
+
+ // List of module ids found in this sample.
+ repeated ModuleIdentifier module_id = 2;
+
+ // Duration of this profile.
+ optional int32 profile_duration_ms = 3;
+
+ // Time between samples.
+ optional int32 sampling_period_ms = 4;
+}
diff --git a/chromium/components/metrics/proto/cast_logs.proto b/chromium/components/metrics/proto/cast_logs.proto
new file mode 100644
index 00000000000..0d01ef73b27
--- /dev/null
+++ b/chromium/components/metrics/proto/cast_logs.proto
@@ -0,0 +1,182 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Cast-enabled device specific log data included in ChromeUserMetricsExtension.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "CastLogsProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 7
+message CastLogsProto {
+ // Cast specific device information.
+ // Next tag: 5
+ message CastDeviceInfo {
+ // The product type of Cast device sent from Cast-enabled devices.
+ // Next tag: 5
+ enum CastProductType {
+ CAST_PRODUCT_TYPE_UNKNOWN = 0;
+ CAST_PRODUCT_TYPE_CHROMECAST = 1;
+ CAST_PRODUCT_TYPE_AUDIO = 3;
+ CAST_PRODUCT_TYPE_ANDROID_TV = 4;
+ }
+ optional CastProductType type = 1;
+
+ // The hardware revision of each product.
+ optional string hardware_revision = 2;
+
+ // The manufacturer of Cast device, this value is empty when the device
+ // is manufactured by Google.
+ optional string manufacturer = 3;
+
+ // The model of the Cast device.
+ optional string model = 4;
+ }
+ // The device sends this information at least once per day.
+ optional CastDeviceInfo cast_device_info = 1;
+
+ // Information about Cast connection between sender application and
+ // Cast-enabled device.
+ // Next tag: 4
+ message CastConnectionInfo {
+ optional fixed32 transport_connection_id = 1;
+
+ optional fixed32 virtual_connection_id = 2;
+
+ // This message describes a detail sender device and sdk. Those are
+ // parsed from the user agent string sent from sender sdk during connection.
+ // Next tag: 9
+ message SenderInfo {
+ // The identifier for the sender device, that is not tied any kind of
+ // device id outside of UMA, and this id is reset when user resets sender
+ // device.
+ optional fixed64 sender_device_id = 1;
+
+ // SDK type the sender application was using.
+ // Next tag: 3
+ enum SDKType {
+ SDK_UNKNOWN = 0;
+
+ // Native SDK type,
+ // E.G. Android sdk, iOS sdk.
+ SDK_NATIVE = 1;
+
+ // SDK via Chrome extension.
+ SDK_CHROME_EXTENSION = 2;
+ }
+ optional SDKType sdk_type = 2;
+
+ // Version of sender sdk/extension used to connection.
+ optional string version = 3;
+
+ // Chrome browser version where the Chrome extension running.
+ // Only Chrome extension sends this information.
+ optional string chrome_browser_version = 4;
+
+ // Platform of sender device.
+ // Next tag: 7
+ enum Platform {
+ // Any platform other then cases below.
+ PLATFORM_OTHER = 0;
+
+ PLATFORM_ANDROID = 1;
+ PLATFORM_IOS = 2;
+ PLATFORM_WINDOWS = 3;
+ PLATFORM_OSX = 4;
+ PLATFORM_CHROMEOS = 5;
+ PLATFORM_LINUX = 6;
+ }
+ optional Platform platform = 5;
+
+ // Sender device system version.
+ optional string system_version = 6;
+
+ // What type of connection type used to establish between sender and
+ // receiver.
+ enum ConnectionType {
+ CONNECTION_TYPE_UNKNOWN = 0;
+ CONNECTION_TYPE_LOCAL = 1;
+ CONNECTION_TYPE_RELAY = 2;
+ }
+ optional ConnectionType transport_connection_type = 7;
+
+ // Sender device model.
+ optional string model = 8;
+ }
+ optional SenderInfo sender_info = 3;
+ }
+
+ // Virtual connection established between sender application and Cast device.
+ repeated CastConnectionInfo cast_connection_info = 2;
+
+ // Stores Cast-enabled device specific events with a various context data.
+ // Next tag: 10
+ message CastEventProto {
+ // The name of the action, hashed by same logic used to hash user action
+ // event and histogram.
+ optional fixed64 name_hash = 1;
+
+ // The timestamp for the event, in milliseconds.
+ optional int64 time_msec = 2;
+
+ // The Cast receiver app ID related with this event.
+ optional fixed32 app_id = 3;
+
+ // The identifier for receiver application session.
+ optional fixed64 application_session_id = 4;
+
+ // Receiver side Cast SDK version.
+ optional fixed64 cast_receiver_version = 5;
+
+ // Cast MPL version.
+ optional fixed64 cast_mpl_version = 9;
+
+ // transport_connection_id related with this event.
+ optional fixed32 transport_connection_id = 6;
+
+ // virtual_connection_id related with this event.
+ optional fixed32 virtual_connection_id = 7;
+
+ // An optional value for the associated event
+ optional int64 value = 8;
+
+ // An optional value for the multi-room group uuid.
+ optional fixed64 group_uuid = 10;
+ }
+ repeated CastEventProto cast_event = 3;
+
+ // Virtual release track for device.
+ optional fixed32 virtual_release_track = 4;
+
+ // Cast specific device information which is expected to change over time.
+ // Next tag: 2
+ message CastDeviceMutableInfo {
+ // This is the last type of reboot the device encountered
+ // Next tag: 9
+ enum RebootType {
+ REBOOT_TYPE_UNKNOWN = 0;
+ REBOOT_TYPE_FORCED = 1;
+ REBOOT_TYPE_API = 2;
+ REBOOT_TYPE_NIGHTLY = 3;
+ REBOOT_TYPE_OTA = 4;
+ REBOOT_TYPE_WATCHDOG = 5;
+ REBOOT_TYPE_PROCESS_MANAGER = 6;
+ REBOOT_TYPE_CRASH_UPLOADER = 7;
+ REBOOT_TYPE_FDR = 8;
+ }
+ optional RebootType last_reboot_type = 1;
+ }
+ optional CastDeviceMutableInfo cast_device_mutable_info = 5;
+
+ // Unique identifier that is randomly generated on first setup, on re-setup
+ // (FDR), and when user opted-in from opted-out status. If user is opted-out
+ // then this field should not be set.
+ // This is used for joining logs from Cast sender SDK to evaluate Cast
+ // sender/receiver communication quality.
+ optional fixed64 receiver_metrics_id = 6;
+}
diff --git a/chromium/components/metrics/proto/chrome_user_metrics_extension.proto b/chromium/components/metrics/proto/chrome_user_metrics_extension.proto
new file mode 100644
index 00000000000..a80081a774d
--- /dev/null
+++ b/chromium/components/metrics/proto/chrome_user_metrics_extension.proto
@@ -0,0 +1,76 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Protocol buffer for Chrome UMA (User Metrics Analysis).
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "ChromeUserMetricsExtensionProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+import "cast_logs.proto";
+import "histogram_event.proto";
+import "omnibox_event.proto";
+import "profiler_event.proto";
+import "system_profile.proto";
+import "user_action_event.proto";
+import "perf_data.proto";
+import "sampled_profile.proto";
+
+// Next tag: 13
+message ChromeUserMetricsExtension {
+ // The product (i.e. end user application) for a given UMA log.
+ enum Product {
+ // Google Chrome product family.
+ CHROME = 0;
+
+ // UMA metrics from Android Webview.
+ ANDROID_WEBVIEW = 20;
+ }
+ // The product corresponding to this log. The field type is int32 instead of
+ // Product so that downstream users of the Chromium metrics component can
+ // introduce products without needing to make changes to the Chromium code
+ // (though they still need to add the new product to the server-side enum).
+ // Note: The default value is Chrome, so Chrome products will not transmit
+ // this field.
+ optional int32 product = 10 [default = 0];
+
+ // The id of the client install that generated these events.
+ //
+ // For Chrome clients, this id is unique to a top-level (one level above the
+ // "Default" directory) Chrome user data directory [1], and so is shared among
+ // all Chrome user profiles contained in this user data directory.
+ // An id of 0 is reserved for test data (monitoring and internal testing) and
+ // should normally be ignored in analysis of the data.
+ // [1] http://www.chromium.org/user-experience/user-data-directory
+ optional fixed64 client_id = 1;
+
+ // The session id for this user.
+ // Values such as tab ids are only meaningful within a particular session.
+ // The client keeps track of the session id and sends it with each event.
+ // The session id is simply an integer that is incremented each time the user
+ // relaunches Chrome.
+ optional int32 session_id = 2;
+
+ // Information about the user's browser and system configuration.
+ optional SystemProfileProto system_profile = 3;
+
+ // This message will log one or more of the following event types:
+ repeated UserActionEventProto user_action_event = 4;
+ repeated OmniboxEventProto omnibox_event = 5;
+ repeated HistogramEventProto histogram_event = 6;
+ repeated ProfilerEventProto profiler_event = 7;
+
+ // This field is no longer used. Use |sampled_profile| instead.
+ repeated PerfDataProto perf_data = 8 [deprecated=true];
+
+ // A list of all collected sample-based profiles since the last UMA upload.
+ repeated SampledProfile sampled_profile = 11;
+
+ // Additional data related with Cast-enabled devices.
+ optional CastLogsProto cast_logs = 12;
+}
diff --git a/chromium/components/metrics/proto/histogram_event.proto b/chromium/components/metrics/proto/histogram_event.proto
new file mode 100644
index 00000000000..8b054172eae
--- /dev/null
+++ b/chromium/components/metrics/proto/histogram_event.proto
@@ -0,0 +1,49 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Histogram-collected metrics.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "HistogramEventProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 4
+message HistogramEventProto {
+ // The name of the histogram, hashed.
+ optional fixed64 name_hash = 1;
+
+ // The sum of all the sample values.
+ // Together with the total count of the sample values, this allows us to
+ // compute the average value. The count of all sample values is just the sum
+ // of the counts of all the buckets. As of M51, when the value of this field
+ // would be 0, the field will be omitted instead.
+ optional int64 sum = 2;
+
+ // The per-bucket data.
+ message Bucket {
+ // Each bucket's range is bounded by min <= x < max.
+ // It is valid to omit one of these two fields in a bucket, but not both.
+ // If the min field is omitted, its value is assumed to be equal to max - 1.
+ // If the max field is omitted, its value is assumed to be equal to the next
+ // bucket's min value (possibly computed per above). The last bucket in a
+ // histogram should always include the max field.
+ optional int64 min = 1;
+ optional int64 max = 2;
+
+ // The bucket's index in the list of buckets, sorted in ascending order.
+ // This field was intended to provide extra redundancy to detect corrupted
+ // records, but was never used. As of M31, it is no longer sent by Chrome
+ // clients to reduce the UMA upload size.
+ optional int32 bucket_index = 3 [deprecated = true];
+
+ // The number of entries in this bucket. As of M51, when the value of this
+ // field would be 1, the field will be omitted instead.
+ optional int64 count = 4 [default = 1];
+ }
+ repeated Bucket bucket = 3;
+}
diff --git a/chromium/components/metrics/proto/memory_leak_report.proto b/chromium/components/metrics/proto/memory_leak_report.proto
new file mode 100644
index 00000000000..b465f16b4fe
--- /dev/null
+++ b/chromium/components/metrics/proto/memory_leak_report.proto
@@ -0,0 +1,81 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package metrics;
+
+// Next tag: 8
+message MemoryLeakReportProto {
+ // The call stack at which the leak was found. This is a list of offsets
+ // within the program binary. The first entry is the deepest level of the call
+ // stack.
+ //
+ // Some call stack entries may not be within the Chrome binary (e.g.
+ // JavaScript code). Those entries are given as the absolute offset in memory.
+ //
+ // The offsets within Chrome are determined by whether the original call stack
+ // address was within the executable region of the Chrome binary's mapping in
+ // memory. To symbolize these results, look up these values as offsets within
+ // the Chrome debug binary. If the value doesn't fit within the Chrome
+ // binary's offset range, then it is considered to be from another binary.
+ repeated uint64 call_stack = 1;
+
+ // Size of the memory allocation involved in the leak.
+ optional uint32 size_bytes = 2;
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ // The rate at which allocations are pseudorandomly sampled. Ranges from 0 to
+ // 1. A rate of 1 means all incoming allocations are sampled by the leak
+ // detector, which is the maximum possible.
+ optional float sampling_rate = 3;
+
+ // The max depth to which the call stacks were unwound by the leak detector.
+ // This may be greater than the size of |call_stack|.
+ optional uint32 max_stack_depth = 4;
+
+ // The leak analysis takes place every so often, with an interval based on the
+ // number of bytes allocated. This is independent of the sampling rate as it
+ // is computed from allocation sizes before sampling.
+ optional uint64 analysis_interval_bytes = 5;
+
+ // Suspicion thresholds used in leak analysis for size and call stacks,
+ // respectively. If an allocation size or call stack is suspected this many
+ // times in a row, the leak analysis escalates to the next level. For
+ // allocation sizes, the next level is to start analyzing by call stack. For
+ // call stacks, the next level is to generate a memory leak report.
+ optional uint32 size_suspicion_threshold = 6;
+ optional uint32 call_stack_suspicion_threshold = 7;
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ // Represents a single snapshot of the internal bookkeeping of the Runtime
+ // Memory Leak Detector, which tracks the number of extant allocations (a
+ // block of heap memory that has been allocated but not yet freed).
+ //
+ // Next tag: 3
+ message AllocationBreakdown {
+ // Table of number of extant allocations for each allocation size. The i-th
+ // entry in the vector is the net number of allocations for sizes in the
+ // range [i * 4, i * 4 + 3].
+ repeated uint32 counts_by_size = 1;
+
+ // The number of extant allocations with size = |size_bytes| and made from
+ // the call site given by |call_stack|. If it is not set, it means tracking
+ // of allocs per call site for allocation size = |size_bytes| has not yet
+ // begun at the time of this entry.
+ optional uint32 count_for_call_stack = 2;
+ }
+
+ // A record of past allocation data leading up to the circumstances that
+ // generated the current leak report.
+ //
+ // A new snapshot is taken every |analysis_interval_bytes| of memory
+ // allocation. The oldest record is at the beginning. The most recent record,
+ // taken at the time the report was generated, is at the end.
+ repeated AllocationBreakdown alloc_breakdown_history = 8;
+}
diff --git a/chromium/components/metrics/proto/omnibox_event.proto b/chromium/components/metrics/proto/omnibox_event.proto
new file mode 100644
index 00000000000..5d4f7199657
--- /dev/null
+++ b/chromium/components/metrics/proto/omnibox_event.proto
@@ -0,0 +1,286 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Stores information about an omnibox interaction.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "OmniboxEventProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+import "omnibox_input_type.proto";
+
+// Next tag: 17
+message OmniboxEventProto {
+ // The timestamp for the event, in seconds since the epoch.
+ optional int64 time = 1;
+
+ // The id of the originating tab for this omnibox interaction.
+ // This is the current tab *unless* the user opened the target in a new tab.
+ // In those cases, this is unset. Tab ids are unique for a given session_id
+ // (in the containing protocol buffer ChromeUserMetricsExtensionProto).
+ optional int32 tab_id = 2;
+
+ // The number of characters the user had typed before autocompleting.
+ optional int32 typed_length = 3;
+
+ // Whether the user deleted text immediately before selecting an omnibox
+ // suggestion. This is usually the result of pressing backspace or delete.
+ optional bool just_deleted_text = 11;
+
+ // The number of terms that the user typed in the omnibox.
+ optional int32 num_typed_terms = 4;
+
+ // The index of the item that the user selected in the omnibox popup list.
+ // This corresponds the index of the |suggestion| below.
+ optional int32 selected_index = 5;
+
+ // DEPRECATED. Whether or not the top match was hidden in the omnibox
+ // suggestions dropdown.
+ optional bool DEPRECATED_is_top_result_hidden_in_dropdown = 14
+ [deprecated = true];
+
+ // Whether the omnibox popup is open. It can be closed if, for instance,
+ // the user clicks in the omnibox and hits return to reload the same page.
+ // If the popup is closed, the suggestion list will contain only one item
+ // and selected_index will be 0 (pointing to that single item). Because
+ // paste-and-search/paste-and-go actions ignore the current content of the
+ // omnibox dropdown (if it is open) when they happen, we pretend the
+ // dropdown is closed when logging these.
+ optional bool is_popup_open = 15;
+
+ // True if this is a paste-and-search or paste-and-go action. (The codebase
+ // refers to both these types as paste-and-go.)
+ optional bool is_paste_and_go = 16;
+
+ // The length of the inline autocomplete text in the omnibox.
+ // The sum |typed_length| + |completed_length| gives the full length of the
+ // user-visible text in the omnibox.
+ // This field is only set for suggestions that are allowed to be the default
+ // match and omitted otherwise. The first suggestion is always allowed to
+ // be the default match. (This is an enforced constraint.) Hence, if
+ // |selected_index| == 0, then this field will always be set.
+ optional int32 completed_length = 6;
+
+ // The amount of time, in milliseconds, since the user first began modifying
+ // the text in the omnibox. If at some point after modifying the text, the
+ // user reverts the modifications (thus seeing the current web page's URL
+ // again), then writes in the omnibox again, this elapsed time should start
+ // from the time of the second series of modification.
+ optional int64 typing_duration_ms = 7;
+
+ // The amount of time, in milliseconds, since the last time the default
+ // (inline) match changed. This may be longer than the time since the
+ // last keystroke. (The last keystroke may not have changed the default
+ // match.) It may also be shorter than the time since the last keystroke
+ // because the default match might have come from an asynchronous
+ // provider. Regardless, it should always be less than or equal to
+ // the field |typing_duration_ms|.
+ optional int64 duration_since_last_default_match_update_ms = 13;
+
+ // The type of page currently displayed when the user used the omnibox.
+ enum PageClassification {
+ // An invalid URL; shouldn't happen.
+ INVALID_SPEC = 0;
+
+ // chrome://newtab/. This can be either the built-in version or a
+ // replacement new tab page from an extension. Note that when Instant
+ // Extended is enabled, the new tab page will be reported as either
+ // INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS or
+ // INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS below,
+ // unless an extension is replacing the new tab page, in which case
+ // it will still be reported as NTP.
+ NTP = 1;
+
+ // about:blank.
+ BLANK = 2;
+
+ // The user's home page. Note that if the home page is set to any
+ // of the new tab page versions or to about:blank, then we'll
+ // classify the page into those categories, not HOME_PAGE.
+ HOME_PAGE = 3;
+
+ // The catch-all entry of everything not included somewhere else
+ // on this list.
+ OTHER = 4;
+
+ // The instant new tab page enum value was deprecated on August 2, 2013.
+ OBSOLETE_INSTANT_NTP = 5;
+
+ // The user is on a search result page that's doing search term
+ // replacement, meaning the search terms should've appeared in the omnibox
+ // before the user started editing it, not the URL of the page.
+ SEARCH_RESULT_PAGE_DOING_SEARCH_TERM_REPLACEMENT = 6;
+
+ // The new tab page in which this omnibox interaction first started
+ // with the user having focus in the omnibox.
+ INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS = 7;
+
+ // The new tab page in which this omnibox interaction first started
+ // with the user having focus in the fakebox.
+ INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS = 8;
+
+ // The user is on a search result page that's not doing search term
+ // replacement, meaning the URL of the page should've appeared in the
+ // omnibox before the user started editing it, not the search terms.
+ SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT = 9;
+
+ // The user is on the home screen.
+ APP_HOME = 10;
+
+ // The user is in the search app.
+ APP_SEARCH = 11;
+
+ // The user is in the maps app.
+ APP_MAPS = 12;
+
+ // When adding new classifications, please consider adding them in
+ // chrome/browser/resources/omnibox/omnibox.html
+ // so that these new options are displayed on about:omnibox.
+ }
+ optional PageClassification current_page_classification = 10;
+
+ optional OmniboxInputType.Type input_type = 8;
+
+ // An enum used in multiple places below.
+ enum ProviderType {
+ UNKNOWN_PROVIDER = 0; // Unknown provider (should not reach here)
+ HISTORY_URL = 1; // URLs in history, or user-typed URLs
+ HISTORY_CONTENTS = 2; // Matches for page contents of pages in history
+ HISTORY_QUICK = 3; // Matches for recently or frequently visited pages
+ // in history
+ SEARCH = 4; // Search suggestions for the default search engine
+ KEYWORD = 5; // Keyword-triggered searches
+ BUILTIN = 6; // Built-in URLs, such as chrome://version
+ SHORTCUTS = 7; // Recently selected omnibox suggestions
+ EXTENSION_APPS = 8; // DEPRECATED. Suggestions from extensions or apps
+ CONTACT = 9; // DEPRECATED. The user's contacts
+ BOOKMARK = 10; // The user's bookmarks
+ ZERO_SUGGEST = 11; // Suggestions based on the current page
+ // This enum value is currently only used by Android GSA. It represents
+ // a suggestion from the phone.
+ ON_DEVICE = 12;
+ // This enum value is currently only used by Android GSA. It represents
+ // a suggestion powered by a Chrome content provider.
+ ON_DEVICE_CHROME = 13;
+ CLIPBOARD_URL = 14; // Suggestion coming from clipboard (iOS only).
+ }
+
+ // The result set displayed on the completion popup
+ // Next tag: 7
+ message Suggestion {
+ // Where does this result come from?
+ optional ProviderType provider = 1;
+
+ // What kind of result this is.
+ // This corresponds to the AutocompleteMatch::Type enumeration in
+ // components/omnibox/autocomplete_match.h (except for Android
+ // GSA result types).
+ enum ResultType {
+ UNKNOWN_RESULT_TYPE = 0; // Unknown type (should not reach here)
+ URL_WHAT_YOU_TYPED = 1; // The input as a URL
+ HISTORY_URL = 2; // A past page whose URL contains the input
+ HISTORY_TITLE = 3; // A past page whose title contains the input
+ HISTORY_BODY = 4; // DEPRECATED. A past page whose body
+ // contains the input
+ HISTORY_KEYWORD = 5; // A past page whose keyword contains the
+ // input
+ NAVSUGGEST = 6; // A suggested URL
+ SEARCH_WHAT_YOU_TYPED = 7; // The input as a search query (with the
+ // default engine)
+ SEARCH_HISTORY = 8; // A past search (with the default engine)
+ // containing the input
+ SEARCH_SUGGEST = 9; // A suggested search (with the default
+ // engine) for a query.
+ SEARCH_OTHER_ENGINE = 10; // A search with a non-default engine
+ EXTENSION_APP = 11; // DEPRECATED. An Extension App with a
+ // title/url that contains the input.
+ CONTACT = 12; // One of the user's contacts
+ BOOKMARK_TITLE = 13; // A bookmark whose title contains the input.
+ SEARCH_SUGGEST_ENTITY = 14; // A suggested search for an entity.
+ SEARCH_SUGGEST_TAIL = 15; // A suggested search to complete the tail
+ // of the query.
+ SEARCH_SUGGEST_PERSONALIZED = 16; // A personalized suggested search.
+ SEARCH_SUGGEST_PROFILE = 17; // A personalized suggested search for a
+ // Google+ profile.
+ APP_RESULT = 18; // Result from an installed app
+ // (eg: a gmail email).
+ // Used by Android GSA for on-device
+ // suggestion logging.
+ APP = 19; // An app result (eg: the gmail app).
+ // Used by Android GSA for on-device
+ // suggestion logging.
+ LEGACY_ON_DEVICE = 20; // An on-device result from a legacy
+ // provider. That is, this result is not
+ // from the on-device suggestion provider
+ // (go/icing). This field is
+ // used by Android GSA for on-device
+ // suggestion logging.
+ NAVSUGGEST_PERSONALIZED = 21; // A personalized url.
+ SEARCH_SUGGEST_ANSWER = 22; // DEPRECATED. Answers no longer have their
+ // own type but instead can be attached to
+ // suggestions of any type.
+ CALCULATOR = 23; // A calculator answer.
+ CLIPBOARD = 24; // An URL based on the clipboard.
+ }
+ optional ResultType result_type = 2;
+
+ // The relevance score for this suggestion.
+ optional int32 relevance = 3;
+
+ // How many times this result was typed in / selected from the omnibox.
+ // Only set for some providers and result_types. At the time of
+ // writing this comment, it is only set for HistoryURL and
+ // HistoryQuickProvider matches.
+ optional int32 typed_count = 5;
+
+ // Whether this item is starred (bookmarked) or not.
+ optional bool is_starred = 4 [deprecated=true];
+
+ // Whether this item is disabled in the UI (not clickable).
+ optional bool is_disabled = 6;
+ }
+ repeated Suggestion suggestion = 9;
+
+ // A data structure that holds per-provider information, general information
+ // not associated with a particular result.
+ // Next tag: 6
+ message ProviderInfo {
+ // Which provider generated this ProviderInfo entry.
+ optional ProviderType provider = 1;
+
+ // The provider's done() value, i.e., whether it's completed processing
+ // the query. Providers which don't do any asynchronous processing
+ // will always be done.
+ optional bool provider_done = 2;
+
+ // The set of field trials that have triggered in the most recent query,
+ // possibly affecting the shown suggestions. Each element is a hash
+ // of the corresponding field trial name.
+ // See chrome/browser/autocomplete/search_provider.cc for a specific usage
+ // example.
+ repeated fixed32 field_trial_triggered = 3;
+
+ // Same as above except that the set of field trials is a union of all field
+ // trials that have triggered within the current omnibox session including
+ // the most recent query.
+ // See AutocompleteController::ResetSession() for more details on the
+ // definition of a session.
+ // See chrome/browser/autocomplete/search_provider.cc for a specific usage
+ // example.
+ repeated fixed32 field_trial_triggered_in_session = 4;
+
+ // The number of times this provider returned a non-zero number of
+ // suggestions during this omnibox session.
+ // Note that each provider may define a session differently for its
+ // purposes.
+ optional int32 times_returned_results_in_session = 5;
+ }
+ // A list of diagnostic information about each provider. Providers
+ // will appear at most once in this list.
+ repeated ProviderInfo provider_info = 12;
+}
diff --git a/chromium/components/metrics/proto/omnibox_input_type.proto b/chromium/components/metrics/proto/omnibox_input_type.proto
new file mode 100644
index 00000000000..8d16bcb65d7
--- /dev/null
+++ b/chromium/components/metrics/proto/omnibox_input_type.proto
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Stores information about an omnibox interaction.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "OmniboxInputTypeProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics.OmniboxInputType;
+
+// What kind of input the user provided.
+// Note that the type below may be misleading. For example, "http:/" alone
+// cannot be opened as a URL, so it is marked as a QUERY; yet the user
+// probably intends to type more and have it eventually become a URL, so we
+// need to make sure we still run it through inline autocomplete.
+enum Type {
+ // Empty input (should not reach here)
+ INVALID = 0;
+
+ // Valid input whose type cannot be determined
+ UNKNOWN = 1;
+
+ // DEPRECATED. Input autodetected as UNKNOWN, which the user wants to treat
+ // as an URL by specifying a desired_tld.
+ DEPRECATED_REQUESTED_URL = 2;
+
+ // Input autodetected as a URL
+ URL = 3;
+
+ // Input autodetected as a query
+ QUERY = 4;
+
+ // Input forced to be a query by an initial '?'
+ FORCED_QUERY = 5;
+}
diff --git a/chromium/components/metrics/proto/perf_data.proto b/chromium/components/metrics/proto/perf_data.proto
new file mode 100644
index 00000000000..99f67f8021b
--- /dev/null
+++ b/chromium/components/metrics/proto/perf_data.proto
@@ -0,0 +1,412 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "PerfDataProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Stores information from a perf session generated via running:
+// "perf record"
+//
+// See $kernel/tools/perf/design.txt for more details.
+
+// Please do not modify this protobuf directly, except to mirror the upstream
+// version found here:
+// https://chromium.googlesource.com/chromiumos/platform/chromiumos-wide-profiling/+/master/perf_data.proto
+// with some fields omitted for privacy reasons. Because it is a read-only copy
+// of the upstream protobuf, "Next tag:" comments are also absent.
+
+message PerfDataProto {
+
+ // Perf event attribute. Stores the event description.
+ // This data structure is defined in the linux kernel:
+ // $kernel/tools/perf/util/event.h.
+ message PerfEventAttr {
+ // Type of the event. Type is an enumeration and can be:
+ // IP: an instruction-pointer was stored in the event.
+ // MMAP: a DLL was loaded.
+ // FORK: a process was forked.
+ // etc.
+ optional uint32 type = 1;
+
+ // Size of the event data in bytes.
+ optional uint32 size = 2;
+
+ // The config stores the CPU-specific counter information.
+ optional uint64 config = 3;
+
+ // Sample period of the event. Indicates how often the event is
+ // triggered in terms of # of events. After |sample_period| events, an event
+ // will be recorded and stored.
+ optional uint64 sample_period = 4;
+
+ // Sample frequency of the event. Indicates how often the event is
+ // triggered in terms of # per second. The kernel will try to record
+ // |sample_freq| events per second.
+ optional uint64 sample_freq = 5;
+
+ // Sample type is a bitfield that records attributes of the sample. Example,
+ // whether an entire callchain was recorded, etc.
+ optional uint64 sample_type = 6;
+
+ // Bitfield that indicates whether reads on the counter will return the
+ // total time enabled and total time running.
+ optional uint64 read_format = 7;
+
+ // Indicates whether the counter starts off disabled.
+ optional bool disabled = 8;
+
+ // Indicates whether child processes inherit the counter.
+ optional bool inherit = 9;
+
+ // Indicates whether the counter is pinned to a particular CPU.
+ optional bool pinned = 10;
+
+ // Indicates whether this counter's group has exclusive access to the CPU's
+ // counters.
+ optional bool exclusive = 11;
+
+ // The following bits restrict events to be counted when the CPU is in user,
+ // kernel, hypervisor or idle modes.
+ optional bool exclude_user = 12;
+ optional bool exclude_kernel = 13;
+ optional bool exclude_hv = 14;
+ optional bool exclude_idle = 15;
+
+ // Indicates whether mmap events should be recorded.
+ optional bool mmap = 16;
+
+ // Indicates whether process comm information should be recorded upon
+ // process creation.
+ optional bool comm = 17;
+
+ // Indicates that we are in frequency mode, not period mode.
+ optional bool freq = 18;
+
+ // Indicates whether we have per-task counts.
+ optional bool inherit_stat = 19;
+
+ // Indicates whether we enable perf events after an exec() function call.
+ optional bool enable_on_exec = 20;
+
+ // Indicates whether we trace fork/exit.
+ optional bool task = 21;
+
+ // Indicates whether we are using a watermark to wake up.
+ optional bool watermark = 22;
+
+ // CPUs often "skid" when recording events. That means the instruction
+ // pointer may not be the same as the one that caused the counter overflow.
+ // Indicates the capabilities of the CPU in terms of recording precise
+ // instruction pointer.
+ optional uint32 precise_ip = 23;
+
+ // Indicates whether we have non-exec mmap data.
+ optional bool mmap_data = 24;
+
+ // If set, all the event types will have the same sample_type.
+ optional bool sample_id_all = 25;
+
+ // Indicates whether we are counting events from the host (when running a
+ // VM).
+ optional bool exclude_host = 26;
+
+ // Exclude events that happen on a guest OS.
+ optional bool exclude_guest = 27;
+
+ // Contains the number of events after which we wake up.
+ optional uint32 wakeup_events = 28;
+
+ // Contains the number of bytes after which we wake up.
+ optional uint32 wakeup_watermark = 29;
+
+ // Information about the type of the breakpoint.
+ optional uint32 bp_type = 30;
+
+ // Contains the breakpoint address.
+ optional uint64 bp_addr = 31;
+
+ // This is an extension of config (see above).
+ optional uint64 config1 = 32;
+
+ // The length of the breakpoint data in bytes.
+ optional uint64 bp_len = 33;
+
+ // This is an extension of config (see above).
+ optional uint64 config2 = 34;
+
+ // Contains the type of branch, example: user, kernel, call, return, etc.
+ optional uint64 branch_sample_type = 35;
+ }
+
+ // Describes a perf.data file attribute.
+ message PerfFileAttr {
+ optional PerfEventAttr attr = 1;
+
+ // List of perf file attribute ids. Each id describes an event.
+ repeated uint64 ids = 2;
+ }
+
+ // This message contains information about a perf sample itself, as opposed to
+ // a perf event captured by a sample.
+ message SampleInfo {
+ // Process ID / thread ID from which this sample was taken.
+ optional uint32 pid = 1;
+ optional uint32 tid = 2;
+
+ // Time this sample was taken (NOT the same as an event time).
+ // It is the number of nanoseconds since bootup.
+ optional uint64 sample_time_ns = 3;
+
+ // The ID of the sample's event type (cycles, instructions, etc).
+ // The event type IDs are defined in PerfFileAttr.
+ optional uint64 id = 4;
+
+ // The CPU on which this sample was taken.
+ optional uint32 cpu = 5;
+ }
+
+ message CommEvent {
+ // Process id.
+ optional uint32 pid = 1;
+
+ // Thread id.
+ optional uint32 tid = 2;
+
+ // Comm string's md5 prefix.
+ // The comm string was field 3 and has been intentionally left out.
+ optional uint64 comm_md5_prefix = 4;
+
+ // Time the sample was taken.
+ // Deprecated, use |sample_info| instead.
+ optional uint64 sample_time = 5 [deprecated=true];
+
+ // Info about the perf sample containing this event.
+ optional SampleInfo sample_info = 6;
+ }
+
+ message MMapEvent {
+ // Process id.
+ optional uint32 pid = 1;
+
+ // Thread id.
+ optional uint32 tid = 2;
+
+ // Start address.
+ optional uint64 start = 3;
+
+ // Length.
+ optional uint64 len = 4;
+
+ // PG Offset.
+ optional uint64 pgoff = 5;
+
+ // Filename's md5 prefix.
+ // The filename was field 6 and has been intentionally left out.
+ optional uint64 filename_md5_prefix = 7;
+
+ // Info about the perf sample containing this event.
+ optional SampleInfo sample_info = 8;
+ }
+
+ message BranchStackEntry {
+ // Branch source address.
+ optional uint64 from_ip = 1;
+
+ // Branch destination address.
+ optional uint64 to_ip = 2;
+
+ // Indicates a mispredicted branch.
+ optional bool mispredicted = 3;
+ }
+
+ message SampleEvent {
+ // Instruction pointer.
+ optional uint64 ip = 1;
+
+ // Process id.
+ optional uint32 pid = 2;
+
+ // Thread id.
+ optional uint32 tid = 3;
+
+ // The time after boot when the sample was recorded, in nanoseconds.
+ optional uint64 sample_time_ns = 4;
+
+ // The address of the sample.
+ optional uint64 addr = 5;
+
+ // The id of the sample.
+ optional uint64 id = 6;
+
+ // The stream id of the sample.
+ optional uint64 stream_id = 7;
+
+ // The period of the sample.
+ optional uint64 period = 8;
+
+ // The CPU where the event was recorded.
+ optional uint32 cpu = 9;
+
+ // The raw size of the event in bytes.
+ optional uint32 raw_size = 10;
+
+ // Sample callchain info.
+ repeated uint64 callchain = 11;
+
+ // Branch stack info.
+ repeated BranchStackEntry branch_stack = 12;
+
+ // Not added from original: fields 13 and 14.
+
+ // Sample weight for special events.
+ optional uint64 weight = 15;
+
+ // Sample data source flags.
+ // Possible flag values:
+ // http://lxr.free-electrons.com/source/include/uapi/linux/perf_event.h#L849
+ optional uint64 data_src = 16;
+
+ // Sample transaction flags for special events.
+ // Flag fields:
+ // http://lxr.free-electrons.com/source/include/uapi/linux/perf_event.h#L209
+ optional uint64 transaction = 17;
+ }
+
+ // ForkEvent is used for both FORK and EXIT events, which have the same data
+ // format. We don't want to call this "ForkOrExitEvent", in case a separate
+ // exit event is introduced in the future.
+ message ForkEvent {
+ // Forked process ID.
+ optional uint32 pid = 1;
+
+ // Parent process ID.
+ optional uint32 ppid = 2;
+
+ // Forked process thread ID.
+ optional uint32 tid = 3;
+
+ // Parent process thread ID.
+ optional uint32 ptid = 4;
+
+ // Time of fork event in nanoseconds since bootup.
+ optional uint64 fork_time_ns = 5;
+
+ // Info about the perf sample containing this event.
+ optional SampleInfo sample_info = 11;
+ }
+
+ message EventHeader {
+ // Type of event.
+ optional uint32 type = 1;
+ optional uint32 misc = 2;
+ // Size of event.
+ optional uint32 size = 3;
+ }
+
+ message PerfEvent {
+ optional EventHeader header = 1;
+
+ optional MMapEvent mmap_event = 2;
+ optional SampleEvent sample_event = 3;
+ optional CommEvent comm_event = 4;
+ // FORK and EXIT events are structurally identical. They only differ by the
+ // event type. But using two distinct fields makes things easier.
+ optional ForkEvent fork_event = 5;
+ optional ForkEvent exit_event = 9;
+
+ // Not added from original: optional LostEvent lost_event = 6;
+ // Not added from original: optional ThrottleEvent throttle_event = 7;
+ // Not added from original: optional ReadEvent read_event = 8;
+ }
+
+ message PerfEventStats {
+ // Total number of events read from perf data.
+ optional uint32 num_events_read = 1;
+
+ // Total number of various types of events.
+ optional uint32 num_sample_events = 2;
+ optional uint32 num_mmap_events = 3;
+ optional uint32 num_fork_events = 4;
+ optional uint32 num_exit_events = 5;
+
+ // Number of sample events that were successfully mapped by the address
+ // mapper, a quipper module that is used to obscure addresses and convert
+ // them to DSO name + offset. Sometimes it fails to process sample events.
+ // This field allows us to track the success rate of the address mapper.
+ optional uint32 num_sample_events_mapped = 6;
+
+ // Whether address remapping was enabled.
+ optional bool did_remap = 7;
+ }
+
+ message PerfBuildID {
+ // Misc field in perf_event_header.
+ // Indicates whether the file is mapped in kernel mode or user mode.
+ optional uint32 misc = 1;
+
+ // Process ID.
+ optional uint32 pid = 2;
+
+ // Build id. Should always contain kBuildIDArraySize bytes of data.
+ // perf_reader.h in Chrome OS defines kBuildIDArraySize = 20.
+ optional bytes build_hash = 3;
+
+ // Filename Md5sum prefix.
+ // The filename was field 4 and has been intentionally left out.
+ optional uint64 filename_md5_prefix = 5;
+ }
+
+ repeated PerfFileAttr file_attrs = 1;
+ repeated PerfEvent events = 2;
+
+ // Time when quipper generated this perf data / protobuf, given as seconds
+ // since the epoch.
+ optional uint64 timestamp_sec = 3;
+
+ // Records some stats about the serialized perf events.
+ optional PerfEventStats stats = 4;
+
+ // Not added from original: repeated uint64 metadata_mask = 5;
+
+ // Build ID metadata.
+ repeated PerfBuildID build_ids = 7;
+
+ // Not added from original: repeated PerfUint32Metadata uint32_metadata = 8;
+ // Not added from original: repeated PerfUint64Metadata uint64_metadata = 9;
+ // Not added from original:
+ // optional PerfCPUTopologyMetadata cpu_topology = 11;
+ // Not added from original:
+ // repeated PerfNodeTopologyMetadata numa_topology = 12;
+
+ message StringMetadata {
+ message StringAndMd5sumPrefix {
+ // The string value was field 1 and has been intentionally left out.
+
+ // The string value's md5sum prefix.
+ optional uint64 value_md5_prefix = 2;
+ }
+
+ // Not added from original: optional StringAndMd5sumPrefix hostname = 1;
+ // Not added from original:
+ // optional StringAndMd5sumPrefix kernel_version =2;
+ // Not added from original: optional StringAndMd5sumPrefix perf_version = 3;
+ // Not added from original: optional StringAndMd5sumPrefix architecture = 4;
+ // Not added from original:
+ // optional StringAndMd5sumPrefix cpu_description = 5;
+ // Not added from original: optional StringAndMd5sumPrefix cpu_id = 6;
+ // Not added from original:
+ // repeated StringAndMd5sumPrefix perf_command_line_token = 7;
+
+ // The command line stored as a single string.
+ optional StringAndMd5sumPrefix perf_command_line_whole = 8;
+ }
+
+ // All the string metadata from the perf data file.
+ optional StringMetadata string_metadata = 13;
+}
diff --git a/chromium/components/metrics/proto/perf_stat.proto b/chromium/components/metrics/proto/perf_stat.proto
new file mode 100644
index 00000000000..bdfb3c458bf
--- /dev/null
+++ b/chromium/components/metrics/proto/perf_stat.proto
@@ -0,0 +1,52 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package metrics;
+
+// Stores output generated by the "perf stat" command.
+//
+// See https://perf.wiki.kernel.org/index.php/Tutorial#Counting_with_perf_stat
+// for more details.
+
+// Next tag: 3
+message PerfStatProto {
+ // All lines printed by "perf stat".
+ repeated PerfStatLine line = 1;
+
+ // The command line used to run "perf stat".
+ optional string command_line = 2;
+
+ // Represents one line of "perf stat" output.
+ // Next tag: 4
+ message PerfStatLine{
+ // Time since the start of the "perf stat" command, in milliseconds.
+ //
+ // When running "perf stat" and printing the counters at the end, this is
+ // the total time taken by the run.
+ //
+ // Alternatively, "perf stat" can print its stats at regular intervals until
+ // the end of the run. For example, if "perf stat" runs for one second and
+ // prints at 200-ms intervals, it will print counter values for each event
+ // a total of five times. According to "perf stat" usage instructions, the
+ // printing interval should be no less than 100 ms.
+ optional uint64 time_ms = 1;
+
+ // Current count value of the event being counted. May be different from the
+ // nominal counter value reported by "perf stat", depending on the event.
+ // For example, memory access counters are in units of 64 bytes. A counter
+ // value of 1024 would represent 65536 bytes, and we would set this field to
+ // 65536.
+ optional uint64 count = 2;
+
+ // Name of event whose counter is listed on this line.
+ // This string should also appear as part of |PerfStatProto::command_line|.
+ // "perf stat" will preserve the event name exactly as it is passed in via
+ // the command line.
+ optional string event = 3;
+ }
+}
diff --git a/chromium/components/metrics/proto/profiler_event.proto b/chromium/components/metrics/proto/profiler_event.proto
new file mode 100644
index 00000000000..0b9d4744579
--- /dev/null
+++ b/chromium/components/metrics/proto/profiler_event.proto
@@ -0,0 +1,127 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Performance metrics collected via Chrome's built-in profiler.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "ProfilerEventProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 7
+message ProfilerEventProto {
+ // The version of this profile.
+ enum ProfileVersion {
+ VERSION_UNKNOWN = 0; // Unknown version (should not reach here).
+ VERSION_STARTUP_PROFILE = 1; // Startup profile, logged approximately 60
+ // seconds after launch.
+ VERSION_SPLIT_PROFILE = 2; // Part of a profile logged in pieces, where
+ // we finish a piece when a ProfilerEvent or a
+ // special end-of-recording event gets
+ // triggered.
+ }
+ optional ProfileVersion profile_version = 1;
+
+ // The source based upon which "time" measurements are made.
+ // We currently only measure wall clock time; but we are exploring other
+ // measurement sources as well, such as CPU time or TCMalloc statistics.
+ enum TimeSource {
+ UNKNOWN_TIME_SOURCE = 0; // Unknown type (should not reach here).
+ WALL_CLOCK_TIME = 1; // Total time elapsed between the start and end of
+ // the task's execution.
+ }
+ optional TimeSource time_source = 2;
+
+ // An event in the browser life that causes the client-side profiler framework
+ // to finish recording of its current instance of ProfilerEventProto, and
+ // start recording a new one.
+ // It's not guaranteed that the events get triggered in the order they are
+ // defined.
+ enum ProfilerEvent {
+ // The first non-empty paint of the first web contents happened.
+ // Corresponds to the Startup.FirstWebContents.NonEmptyPaint2 histogram.
+ EVENT_FIRST_NONEMPTY_PAINT = 0;
+ }
+
+ // The set of events, in no particular order, that were triggered in the
+ // current Chrome session before the recording of this ProfilerEventProto
+ // started. It doesn't include the event that triggered the end of this
+ // ProfilerEventProto. A given event will not occur twice in this set.
+ // The field can be used to find all ProfilerEventProto instances recorded
+ // before or not before a given event.
+ repeated ProfilerEvent past_session_event = 4;
+
+ // Time when profiling started. This is recorded as a time delta relative to
+ // the start time of the profiler data recording in the current browser
+ // session.
+ optional int64 profiling_start_ms = 5;
+
+ // Time when profiling finished. This is recorded as a time delta relative to
+ // the start time of the profiler data recording in the current browser
+ // session.
+ optional int64 profiling_finish_ms = 6;
+
+ // Data for a single tracked object (typically, a Task).
+ message TrackedObject {
+ // The name of the thread from which this task was posted, hashed.
+ optional fixed64 birth_thread_name_hash = 1;
+
+ // The name of the thread on which this task was executed, hashed.
+ optional fixed64 exec_thread_name_hash = 2;
+
+ // The source file name from which this task was posted, hashed.
+ optional fixed64 source_file_name_hash = 3;
+
+ // Function name from which this task was posted, hashed.
+ optional fixed64 source_function_name_hash = 4;
+
+ // The line number within the source file from which this task was posted.
+ optional int32 source_line_number = 5;
+
+ // The number of times this task was executed.
+ optional int32 exec_count = 6;
+
+ // The total execution time for instances this task.
+ optional int32 exec_time_total = 7;
+
+ // The execution time for a uniformly randomly sampled instance of this
+ // task.
+ optional int32 exec_time_sampled = 8;
+
+ // The total time instances this task spent waiting (e.g. in a message loop)
+ // before they were run.
+ optional int32 queue_time_total = 9;
+
+ // The time that a uniformly randomly sampled instance of this task spent
+ // waiting (e.g. in a message loop) before it was run.
+ optional int32 queue_time_sampled = 10;
+
+ // The type of process within which this task was executed.
+ enum ProcessType {
+ UNKNOWN = 0; // Should not reach here
+ BROWSER = 1;
+ RENDERER = 2;
+ PLUGIN = 3; // Deprecated. Should not be sent as of M51 due to
+ // NPAPI removal.
+ WORKER = 4;
+ NACL_LOADER = 5;
+ UTILITY = 6;
+ PROFILE_IMPORT = 7;
+ ZYGOTE = 8;
+ SANDBOX_HELPER = 9;
+ NACL_BROKER = 10;
+ GPU = 11;
+ PPAPI_PLUGIN = 12;
+ PPAPI_BROKER = 13;
+ }
+ optional ProcessType process_type = 11;
+
+ // The local PID for the process within which this task was executed.
+ optional uint32 process_id = 12;
+ }
+ repeated TrackedObject tracked_object = 3;
+}
diff --git a/chromium/components/metrics/proto/sampled_profile.proto b/chromium/components/metrics/proto/sampled_profile.proto
new file mode 100644
index 00000000000..b8936e114c6
--- /dev/null
+++ b/chromium/components/metrics/proto/sampled_profile.proto
@@ -0,0 +1,83 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "SampledProfileProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+import "call_stack_profile.proto";
+import "perf_data.proto";
+import "perf_stat.proto";
+
+// Protocol buffer for collected sample-based profiling data.
+// Contains the parameters and data from a single profile collection event.
+
+// Next tag: 11
+message SampledProfile {
+ // Indicates the event that triggered this collection.
+ enum TriggerEvent {
+ UNKNOWN_TRIGGER_EVENT = 0;
+
+ // The profile was triggered by periodic sampling. Periodically sampled
+ // profiles are collected once per uniformly sized period interval. Within
+ // each interval, the sampled data is collected at a random time. For
+ // example, if the interval is 60 s, then data would be collected at a
+ // random point in each of the intervals [0, 60 s), [60 s, 120 s), etc.
+ PERIODIC_COLLECTION = 1;
+
+ // The profile was collected upon resume from suspend.
+ RESUME_FROM_SUSPEND = 2;
+
+ // The profile was collected upon restoring a previous session.
+ RESTORE_SESSION = 3;
+
+ // The profile was collected at process startup.
+ PROCESS_STARTUP = 4;
+
+ // The profile was collected after jank was detected while executing a task.
+ JANKY_TASK = 5;
+
+ // The profile was collected after a thread was determined to be hung.
+ THREAD_HUNG = 6;
+ }
+ optional TriggerEvent trigger_event = 1;
+
+ // Fields 2-3: Time durations are given in ticks, and represent system uptime
+ // rather than wall time.
+
+ // Time after system boot when the collection took place, in milliseconds.
+ optional int64 ms_after_boot = 2;
+
+ // Time after last login when the collection took place, in milliseconds.
+ optional int64 ms_after_login = 3;
+
+ // The duration for which the machine was suspended prior to collecting the
+ // sampled profile. Only set when |trigger_event| is RESUME_FROM_SUSPEND.
+ optional int64 suspend_duration_ms = 5;
+
+ // Number of milliseconds after a resume that profile was collected. Only set
+ // when |trigger_event| is RESUME_FROM_SUSPEND.
+ optional int64 ms_after_resume = 6;
+
+ // Number of tabs restored during a session restore. Only set when
+ // |trigger_event| is RESTORE_SESSION.
+ optional int32 num_tabs_restored = 7;
+
+ // Number of milliseconds after a session restore that a profile was
+ // collected. Only set when |trigger_event| is RESTORE_SESSION.
+ optional int64 ms_after_restore = 8;
+
+ // Sampled profile data collected from Linux perf tool.
+ optional PerfDataProto perf_data = 4;
+
+ // Sampled profile data collected by periodic sampling of call stacks.
+ optional CallStackProfile call_stack_profile = 9;
+
+ // Perf counter data collected using "perf stat".
+ optional PerfStatProto perf_stat = 10;
+}
diff --git a/chromium/components/metrics/proto/system_profile.proto b/chromium/components/metrics/proto/system_profile.proto
new file mode 100644
index 00000000000..d1fa799148f
--- /dev/null
+++ b/chromium/components/metrics/proto/system_profile.proto
@@ -0,0 +1,775 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Stores information about the user's brower and system configuration.
+// The system configuration fields are recorded once per client session.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "SystemProfileProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 23
+message SystemProfileProto {
+ // The time when the client was compiled/linked, in seconds since the epoch.
+ optional int64 build_timestamp = 1;
+
+ // A version number string for the application.
+ // Most commonly this is the browser version number found in a user agent
+ // string, and is typically a 4-tuple of numbers separated by periods. In
+ // cases where the user agent version might be ambiguous (example: Linux 64-
+ // bit build, rather than 32-bit build, or a Windows version used in some
+ // special context, such as ChromeFrame running in IE), then this may include
+ // some additional postfix to provide clarification not available in the UA
+ // string.
+ //
+ // An example of a browser version 4-tuple is "5.0.322.0". Currently used
+ // postfixes are:
+ //
+ // "-64": a 64-bit build
+ // "-F": Chrome is running under control of ChromeFrame
+ // "-devel": this is not an official build of Chrome
+ //
+ // A full version number string could look similar to:
+ // "5.0.322.0-F-devel".
+ //
+ // This value, when available, is more trustworthy than the UA string
+ // associated with the request; and including the postfix, may be more
+ // specific.
+ optional string app_version = 2;
+
+ // The brand code or distribution tag assigned to a partner, if available.
+ // Brand codes are only available on Windows. Not every Windows install
+ // though will have a brand code.
+ optional string brand_code = 12;
+
+ // The possible channels for an installation, from least to most stable.
+ enum Channel {
+ CHANNEL_UNKNOWN = 0; // Unknown channel -- perhaps an unofficial build?
+ CHANNEL_CANARY = 1;
+ CHANNEL_DEV = 2;
+ CHANNEL_BETA = 3;
+ CHANNEL_STABLE = 4;
+ }
+ optional Channel channel = 10;
+
+ // True if Chrome build is ASan-instrumented.
+ optional bool is_asan_build = 20 [default = false];
+
+ // The date the user enabled UMA, in seconds since the epoch.
+ // If the user has toggled the UMA enabled state multiple times, this will
+ // be the most recent date on which UMA was enabled.
+ // For privacy, this is rounded to the nearest hour.
+ optional int64 uma_enabled_date = 3;
+
+ // The time when the client was installed, in seconds since the epoch.
+ // For privacy, this is rounded to the nearest hour.
+ optional int64 install_date = 16;
+
+ // The user's selected application locale, i.e. the user interface language.
+ // The locale includes a language code and, possibly, also a country code,
+ // e.g. "en-US".
+ optional string application_locale = 4;
+
+ // Information on the user's operating system.
+ message OS {
+ // The user's operating system. This should be one of:
+ // - Android
+ // - Windows NT
+ // - Linux (includes ChromeOS)
+ // - iPhone OS
+ // - Mac OS X
+ optional string name = 1;
+
+ // The version of the OS. The meaning of this field is OS-dependent.
+ optional string version = 2;
+
+ // The fingerprint of the build. This field is used only on Android.
+ optional string fingerprint = 3;
+
+ // Whether the version of iOS appears to be "jailbroken". This field is
+ // used only on iOS. Chrome for iOS detects whether device contains a
+ // DynamicLibraries/ directory. It's a necessary but insufficient indicator
+ // of whether the operating system has been jailbroken.
+ optional bool is_jailbroken = 4;
+ }
+ optional OS os = 5;
+
+ // Next tag for Hardware: 18
+ // Information on the user's hardware.
+ message Hardware {
+ // The CPU architecture (x86, PowerPC, x86_64, ...)
+ optional string cpu_architecture = 1;
+
+ // The amount of RAM present on the system, in megabytes.
+ optional int64 system_ram_mb = 2;
+
+ // The base memory address that chrome.dll was loaded at.
+ // (Logged only on Windows.)
+ optional int64 dll_base = 3;
+
+ // The hardware_class describes the current machine model, e.g. "MacPro1,1"
+ // on Mac, or "Nexus 5" on Android. Only implemented on OS X, Android, and
+ // Chrome OS.
+ //
+ // For Chrome OS, the device hardware class ID is a unique string associated
+ // with each Chrome OS device product revision generally assigned at
+ // hardware qualification time. The hardware class effectively identifies
+ // the configured system components such as CPU, WiFi adapter, etc.
+ //
+ // An example of such a hardware class is "IEC MARIO PONY 6101". An
+ // internal database associates this hardware class with the qualified
+ // device specifications including OEM information, schematics, hardware
+ // qualification reports, test device tags, etc.
+ optional string hardware_class = 4;
+
+ // The number of physical screens.
+ optional int32 screen_count = 5;
+
+ // The screen dimensions of the primary screen, in pixels.
+ optional int32 primary_screen_width = 6;
+ optional int32 primary_screen_height = 7;
+
+ // The device scale factor of the primary screen.
+ optional float primary_screen_scale_factor = 12;
+
+ // Max DPI for any attached screen. (Windows only)
+ optional float max_dpi_x = 9;
+ optional float max_dpi_y = 10;
+
+ // Information on the CPU obtained by CPUID.
+ message CPU {
+ // A 12 character string naming the vendor, e.g. "GeniuneIntel".
+ optional string vendor_name = 1;
+
+ // The signature reported by CPUID (from EAX).
+ optional uint32 signature = 2;
+
+ // Number of logical processors/cores on the current machine.
+ optional uint32 num_cores = 3;
+ }
+ optional CPU cpu = 13;
+
+ // Information on the GPU
+ message Graphics {
+ // The GPU manufacturer's vendor id.
+ optional uint32 vendor_id = 1;
+
+ // The GPU manufacturer's device id for the chip set.
+ optional uint32 device_id = 2;
+
+ // The driver version on the GPU.
+ optional string driver_version = 3;
+
+ // The driver date on the GPU.
+ optional string driver_date = 4;
+
+ // The GL_VENDOR string. An example of a gl_vendor string is
+ // "Imagination Technologies". "" if we are not using OpenGL.
+ optional string gl_vendor = 6;
+
+ // The GL_RENDERER string. An example of a gl_renderer string is
+ // "PowerVR SGX 540". "" if we are not using OpenGL.
+ optional string gl_renderer = 7;
+ }
+ optional Graphics gpu = 8;
+
+ // Information about Bluetooth devices paired with the system.
+ message Bluetooth {
+ // Whether Bluetooth is present on this system.
+ optional bool is_present = 1;
+
+ // Whether Bluetooth is enabled on this system.
+ optional bool is_enabled = 2;
+
+ // Describes a paired device.
+ message PairedDevice {
+ // Assigned class of the device. This is a bitfield according to the
+ // Bluetooth specification available at the following URL:
+ // https://www.bluetooth.org/en-us/specification/assigned-numbers-overview/baseband
+ optional uint32 bluetooth_class = 1;
+
+ // Decoded device type.
+ enum Type {
+ DEVICE_UNKNOWN = 0;
+ DEVICE_COMPUTER = 1;
+ DEVICE_PHONE = 2;
+ DEVICE_MODEM = 3;
+ DEVICE_AUDIO = 4;
+ DEVICE_CAR_AUDIO = 5;
+ DEVICE_VIDEO = 6;
+ DEVICE_PERIPHERAL = 7;
+ DEVICE_JOYSTICK = 8;
+ DEVICE_GAMEPAD = 9;
+ DEVICE_KEYBOARD = 10;
+ DEVICE_MOUSE = 11;
+ DEVICE_TABLET = 12;
+ DEVICE_KEYBOARD_MOUSE_COMBO = 13;
+ }
+ optional Type type = 2;
+
+ // Vendor prefix of the Bluetooth address, these are OUI registered by
+ // the IEEE and are encoded with the first byte in bits 16-23, the
+ // second byte in bits 8-15 and the third byte in bits 0-7.
+ //
+ // ie. Google's OUI (00:1A:11) is encoded as 0x00001A11
+ optional uint32 vendor_prefix = 4;
+
+ // The Vendor ID of a device, returned in vendor_id below, can be
+ // either allocated by the Bluetooth SIG or USB IF, providing two
+ // completely overlapping namespaces for identifiers.
+ //
+ // This field should be read along with vendor_id to correctly
+ // identify the vendor. For example Google is identified by either
+ // vendor_id_source = VENDOR_ID_BLUETOOTH, vendor_id = 0x00E0 or
+ // vendor_id_source = VENDOR_ID_USB, vendor_id = 0x18D1.
+ //
+ // If the device does not support the Device ID specification the
+ // unknown value will be set.
+ enum VendorIDSource {
+ VENDOR_ID_UNKNOWN = 0;
+ VENDOR_ID_BLUETOOTH = 1;
+ VENDOR_ID_USB = 2;
+ }
+ optional VendorIDSource vendor_id_source = 8;
+
+ // Vendor ID of the device, where available.
+ optional uint32 vendor_id = 5;
+
+ // Product ID of the device, where available.
+ optional uint32 product_id = 6;
+
+ // Device ID of the device, generally the release or version number in
+ // BCD format, where available.
+ optional uint32 device_id = 7;
+ }
+ repeated PairedDevice paired_device = 3;
+ }
+ optional Bluetooth bluetooth = 11;
+
+ // Whether the internal display produces touch events. Omitted if unknown.
+ // Logged on ChromeOS only.
+ optional bool internal_display_supports_touch = 14;
+
+ // Vendor ids and product ids of external touchscreens.
+ message TouchScreen {
+ // Touch screen vendor id.
+ optional uint32 vendor_id = 1;
+ // Touch screen product id.
+ optional uint32 product_id = 2;
+ }
+ // Lists vendor and product ids of external touchscreens.
+ // Logged on ChromeOS only.
+ repeated TouchScreen external_touchscreen = 15;
+
+ // Drive messages are currently logged on Windows 7+, iOS, and Android.
+ message Drive {
+ // Whether this drive incurs a time penalty when randomly accessed. This
+ // should be true for spinning disks but false for SSDs or other
+ // flash-based drives.
+ optional bool has_seek_penalty = 1;
+ }
+ // The drive that the application executable was loaded from.
+ optional Drive app_drive = 16;
+ // The drive that the current user data directory was loaded from.
+ optional Drive user_data_drive = 17;
+ }
+ optional Hardware hardware = 6;
+
+ // Information about the network connection.
+ message Network {
+ // Set to true if connection_type changed during the lifetime of the log.
+ optional bool connection_type_is_ambiguous = 1;
+
+ // Derived from net::NetworkChangeNotifier::ConnectionType translated
+ // through NetworkMetricsProvider::GetConnectionType.
+ enum ConnectionType {
+ CONNECTION_UNKNOWN = 0;
+ CONNECTION_ETHERNET = 1;
+ CONNECTION_WIFI = 2;
+ CONNECTION_2G = 3;
+ CONNECTION_3G = 4;
+ CONNECTION_4G = 5;
+ CONNECTION_BLUETOOTH = 6;
+ CONNECTION_NONE = 7;
+ }
+ // The connection type according to NetworkChangeNotifier.
+ optional ConnectionType connection_type = 2;
+
+ // Set to true if wifi_phy_layer_protocol changed during the lifetime of the log.
+ optional bool wifi_phy_layer_protocol_is_ambiguous = 3;
+
+ // See net::WifiPHYLayerProtocol.
+ enum WifiPHYLayerProtocol {
+ WIFI_PHY_LAYER_PROTOCOL_NONE = 0;
+ WIFI_PHY_LAYER_PROTOCOL_ANCIENT = 1;
+ WIFI_PHY_LAYER_PROTOCOL_A = 2;
+ WIFI_PHY_LAYER_PROTOCOL_B = 3;
+ WIFI_PHY_LAYER_PROTOCOL_G = 4;
+ WIFI_PHY_LAYER_PROTOCOL_N = 5;
+ WIFI_PHY_LAYER_PROTOCOL_UNKNOWN = 6;
+ }
+ // The physical layer mode of the associated wifi access point, if any.
+ optional WifiPHYLayerProtocol wifi_phy_layer_protocol = 4;
+
+ // Describe wifi access point information.
+ message WifiAccessPoint {
+ // Vendor prefix of the access point's BSSID, these are OUIs
+ // (Organizationally Unique Identifiers) registered by
+ // the IEEE and are encoded with the first byte in bits 16-23, the
+ // second byte in bits 8-15 and the third byte in bits 0-7.
+ optional uint32 vendor_prefix = 1;
+
+ // Access point seurity mode definitions.
+ enum SecurityMode {
+ SECURITY_UNKNOWN = 0;
+ SECURITY_WPA = 1;
+ SECURITY_WEP = 2;
+ SECURITY_RSN = 3;
+ SECURITY_802_1X = 4;
+ SECURITY_PSK = 5;
+ SECURITY_NONE = 6;
+ }
+ // The security mode of the access point.
+ optional SecurityMode security_mode = 2;
+
+ // Vendor specific information.
+ message VendorInformation {
+ // The model number, for example "0".
+ optional string model_number = 1;
+
+ // The model name (sometimes the same as the model_number),
+ // for example "WZR-HP-AG300H".
+ optional string model_name = 2;
+
+ // The device name (sometimes the same as the model_number),
+ // for example "Dummynet"
+ optional string device_name = 3;
+
+ // The list of vendor-specific OUIs (Organziationally Unqiue
+ // Identifiers). These are provided by the vendor through WPS
+ // (Wireless Provisioning Service) information elements, which
+ // identifies the content of the element.
+ repeated uint32 element_identifier = 4;
+ }
+ // The wireless access point vendor information.
+ optional VendorInformation vendor_info = 3;
+ }
+ // Information of the wireless AP that device is connected to.
+ optional WifiAccessPoint access_point_info = 5;
+ }
+ optional Network network = 13;
+
+ // Information on the Google Update install that is managing this client.
+ message GoogleUpdate {
+ // Whether the Google Update install is system-level or user-level.
+ optional bool is_system_install = 1;
+
+ // The date at which Google Update last started performing an automatic
+ // update check, in seconds since the Unix epoch.
+ optional int64 last_automatic_start_timestamp = 2;
+
+ // The date at which Google Update last successfully sent an update check
+ // and recieved an intact response from the server, in seconds since the
+ // Unix epoch. (The updates don't need to be successfully installed.)
+ optional int64 last_update_check_timestamp = 3;
+
+ // Describes a product being managed by Google Update. (This can also
+ // describe Google Update itself.)
+ message ProductInfo {
+ // The current version of the product that is installed.
+ optional string version = 1;
+
+ // The date at which Google Update successfully updated this product,
+ // stored in seconds since the Unix epoch. This is updated when an update
+ // is successfully applied, or if the server reports that no update
+ // is available.
+ optional int64 last_update_success_timestamp = 2;
+
+ // The result reported by the product updater on its last run.
+ enum InstallResult {
+ INSTALL_RESULT_SUCCESS = 0;
+ INSTALL_RESULT_FAILED_CUSTOM_ERROR = 1;
+ INSTALL_RESULT_FAILED_MSI_ERROR = 2;
+ INSTALL_RESULT_FAILED_SYSTEM_ERROR = 3;
+ INSTALL_RESULT_EXIT_CODE = 4;
+ }
+ optional InstallResult last_result = 3;
+
+ // The error code reported by the product updater on its last run. This
+ // will typically be a error code specific to the product installer.
+ optional int32 last_error = 4;
+
+ // The extra error code reported by the product updater on its last run.
+ // This will typically be a Win32 error code.
+ optional int32 last_extra_error = 5;
+ }
+ optional ProductInfo google_update_status = 4;
+ optional ProductInfo client_status = 5;
+ }
+ optional GoogleUpdate google_update = 11;
+
+ // Information on all installed plugins.
+ message Plugin {
+ // The plugin's self-reported name and filename (without path).
+ optional string name = 1;
+ optional string filename = 2;
+
+ // The plugin's version.
+ optional string version = 3;
+
+ // True if the plugin is disabled.
+ // If a client has multiple local Chrome user accounts, this is logged based
+ // on the first user account launched during the current session.
+ optional bool is_disabled = 4;
+
+ // True if the plugin is PPAPI.
+ optional bool is_pepper = 5;
+ }
+ repeated Plugin plugin = 7;
+
+ // Figures that can be used to generate application stability metrics.
+ // All values are counts of events since the last time that these
+ // values were reported.
+ // Next tag: 26
+ message Stability {
+ // Total amount of time that the program was running, in seconds,
+ // since the last time a log was recorded, as measured using a client-side
+ // clock implemented via TimeTicks, which guarantees that it is monotonic
+ // and does not jump if the user changes his/her clock. The TimeTicks
+ // implementation also makes the clock not count time the computer is
+ // suspended.
+ optional int64 incremental_uptime_sec = 1;
+
+ // Total amount of time that the program was running, in seconds,
+ // since startup, as measured using a client-side clock implemented
+ // via TimeTicks, which guarantees that it is monotonic and does not
+ // jump if the user changes his/her clock. The TimeTicks implementation
+ // also makes the clock not count time the computer is suspended.
+ // This field was added for M-35.
+ optional int64 uptime_sec = 23;
+
+ // Page loads along with renderer crashes, hangs and failed launches, since
+ // page load count roughly corresponds to usage.
+ optional int32 page_load_count = 2;
+ optional int32 renderer_crash_count = 3;
+ optional int32 renderer_hang_count = 4;
+ optional int32 renderer_failed_launch_count = 24;
+
+ // Number of renderer crashes and failed launches that were for extensions.
+ // These are not counted in the renderer counts above.
+ optional int32 extension_renderer_crash_count = 5;
+ optional int32 extension_renderer_failed_launch_count = 25;
+
+ // Number of non-renderer child process crashes.
+ optional int32 child_process_crash_count = 6;
+
+ // Number of times the browser has crashed while logged in as the "other
+ // user" (guest) account.
+ // Logged on ChromeOS only.
+ optional int32 other_user_crash_count = 7;
+
+ // Number of times the kernel has crashed.
+ // Logged on ChromeOS only.
+ optional int32 kernel_crash_count = 8;
+
+ // Number of times the system has shut down uncleanly.
+ // Logged on ChromeOS only.
+ optional int32 unclean_system_shutdown_count = 9;
+
+ //
+ // All the remaining fields in the Stability are recorded at most once per
+ // client session.
+ //
+
+ // The number of times the program was launched.
+ // This will typically be equal to 1. However, it is possible that Chrome
+ // was unable to upload stability metrics for previous launches (e.g. due to
+ // crashing early during startup), and hence this value might be greater
+ // than 1.
+ optional int32 launch_count = 15;
+ // The number of times that it didn't exit cleanly (which we assume to be
+ // mostly crashes).
+ optional int32 crash_count = 16;
+
+ // The number of times the program began, but did not complete, the shutdown
+ // process. (For example, this may occur when Windows is shutting down, and
+ // it only gives the process a few seconds to clean up.)
+ optional int32 incomplete_shutdown_count = 17;
+
+ // The number of times the program was able register with breakpad crash
+ // services.
+ optional int32 breakpad_registration_success_count = 18;
+
+ // The number of times the program failed to register with breakpad crash
+ // services. If crash registration fails then when the program crashes no
+ // crash report will be generated.
+ optional int32 breakpad_registration_failure_count = 19;
+
+ // The number of times the program has run under a debugger. This should
+ // be an exceptional condition. Running under a debugger prevents crash
+ // dumps from being generated.
+ optional int32 debugger_present_count = 20;
+
+ // The number of times the program has run without a debugger attached.
+ // This should be most common scenario and should be very close to
+ // |launch_count|.
+ optional int32 debugger_not_present_count = 21;
+
+ // Stability information for all installed plugins.
+ message PluginStability {
+ // The relevant plugin's information (name, etc.)
+ optional Plugin plugin = 1;
+
+ // The number of times this plugin's process was launched.
+ optional int32 launch_count = 2;
+
+ // The number of times this plugin was instantiated on a web page.
+ // This will be >= |launch_count|.
+ // (A page load with multiple sections drawn by this plugin will
+ // increase this count multiple times.)
+ optional int32 instance_count = 3;
+
+ // The number of times this plugin process crashed.
+ // This value will be <= |launch_count|.
+ optional int32 crash_count = 4;
+
+ // The number of times this plugin could not be loaded.
+ optional int32 loading_error_count = 5;
+ }
+ repeated PluginStability plugin_stability = 22;
+ }
+ optional Stability stability = 8;
+
+ // Description of a field trial or experiment that the user is currently
+ // enrolled in.
+ // All metrics reported in this upload can potentially be influenced by the
+ // field trial.
+ message FieldTrial {
+ // The name of the field trial, as a 32-bit identifier.
+ // Currently, the identifier is a hash of the field trial's name.
+ optional fixed32 name_id = 1;
+
+ // The user's group within the field trial, as a 32-bit identifier.
+ // Currently, the identifier is a hash of the group's name.
+ optional fixed32 group_id = 2;
+ }
+ repeated FieldTrial field_trial = 9;
+
+ // Information about the A/V output device(s) (typically just a TV).
+ // However, a configuration may have one or more intermediate A/V devices
+ // between the source device and the TV (e.g. an A/V receiver, video
+ // processor, etc.).
+ message ExternalAudioVideoDevice {
+ // The manufacturer name (possibly encoded as a 3-letter code, e.g. "YMH"
+ // for Yamaha).
+ optional string manufacturer_name = 1;
+
+ // The model name (e.g. "RX-V1900"). Some devices may report generic names
+ // like "receiver" or use the full manufacturer name (e.g "PHILIPS").
+ optional string model_name = 2;
+
+ // The product code (e.g. "0218").
+ optional string product_code = 3;
+
+ // The device types. A single device can have multiple types (e.g. a set-top
+ // box could be both a tuner and a player). The same type may even be
+ // repeated (e.g a device that reports two tuners).
+ enum AVDeviceType {
+ AV_DEVICE_TYPE_UNKNOWN = 0;
+ AV_DEVICE_TYPE_TV = 1;
+ AV_DEVICE_TYPE_RECORDER = 2;
+ AV_DEVICE_TYPE_TUNER = 3;
+ AV_DEVICE_TYPE_PLAYER = 4;
+ AV_DEVICE_TYPE_AUDIO_SYSTEM = 5;
+ }
+ repeated AVDeviceType av_device_type = 4;
+
+ // The year of manufacture.
+ optional int32 manufacture_year = 5;
+
+ // The week of manufacture.
+ // Note: per the Wikipedia EDID article, numbering for this field may not
+ // be consistent between manufacturers.
+ optional int32 manufacture_week = 6;
+
+ // Max horizontal resolution in pixels.
+ optional int32 horizontal_resolution = 7;
+
+ // Max vertical resolution in pixels.
+ optional int32 vertical_resolution = 8;
+
+ // Audio capabilities of the device.
+ // Ref: http://en.wikipedia.org/wiki/Extended_display_identification_data
+ // Next tag: 7
+ message AudioDescription {
+ // Audio format
+ enum AudioFormat {
+ AUDIO_FORMAT_UNKNOWN = 0;
+ AUDIO_FORMAT_LPCM = 1;
+ AUDIO_FORMAT_AC_3 = 2;
+ AUDIO_FORMAT_MPEG1 = 3;
+ AUDIO_FORMAT_MP3 = 4;
+ AUDIO_FORMAT_MPEG2 = 5;
+ AUDIO_FORMAT_AAC = 6;
+ AUDIO_FORMAT_DTS = 7;
+ AUDIO_FORMAT_ATRAC = 8;
+ AUDIO_FORMAT_ONE_BIT = 9;
+ AUDIO_FORMAT_DD_PLUS = 10;
+ AUDIO_FORMAT_DTS_HD = 11;
+ AUDIO_FORMAT_MLP_DOLBY_TRUEHD = 12;
+ AUDIO_FORMAT_DST_AUDIO = 13;
+ AUDIO_FORMAT_MICROSOFT_WMA_PRO = 14;
+ }
+ optional AudioFormat audio_format = 1;
+
+ // Number of channels (e.g. 1, 2, 8, etc.).
+ optional int32 num_channels = 2;
+
+ // Supported sample frequencies in Hz (e.g. 32000, 44100, etc.).
+ // Multiple frequencies may be specified.
+ repeated int32 sample_frequency_hz = 3;
+
+ // Maximum bit rate in bits/s.
+ optional int32 max_bit_rate_per_second = 4;
+
+ // Bit depth (e.g. 16, 20, 24, etc.).
+ optional int32 bit_depth = 5;
+
+ // Output mode: analog vs digital.
+ enum OutputMode {
+ ANALOG = 0;
+ DIGITAL = 1;
+ }
+ optional OutputMode output_mode = 6;
+
+ }
+ repeated AudioDescription audio_description = 9;
+
+ // The position in AV setup.
+ // A value of 0 means this device is the TV.
+ // A value of 1 means this device is directly connected to one of
+ // the TV's inputs.
+ // Values > 1 indicate there are 1 or more devices between this device
+ // and the TV.
+ optional int32 position_in_setup = 10;
+
+ // Whether this device is in the path to the TV.
+ optional bool is_in_path_to_tv = 11;
+
+ // The CEC version the device supports.
+ // CEC stands for Consumer Electronics Control, a part of the HDMI
+ // specification. Not all HDMI devices support CEC.
+ // Only devices that support CEC will report a value here.
+ optional int32 cec_version = 12;
+
+ // This message reports CEC commands seen by a device.
+ // After each log is sent, this information is cleared and gathered again.
+ // By collecting CEC status information by opcode we can determine
+ // which CEC features can be supported.
+ message CECCommand {
+ // The CEC command opcode. CEC supports up to 256 opcodes.
+ // We add only one CECCommand message per unique opcode. Only opcodes
+ // seen by the device will be reported. The remainder of the message
+ // accumulates status for this opcode (and device).
+ optional int32 opcode = 1;
+
+ // The total number of commands received from the external device.
+ optional int32 num_received_direct = 2;
+
+ // The number of commands received from the external device as part of a
+ // broadcast message.
+ optional int32 num_received_broadcast = 3;
+
+ // The total number of commands sent to the external device.
+ optional int32 num_sent_direct = 4;
+
+ // The number of commands sent to the external device as part of a
+ // broadcast message.
+ optional int32 num_sent_broadcast = 5;
+
+ // The number of aborted commands for unknown reasons.
+ optional int32 num_aborted_unknown_reason = 6;
+
+ // The number of aborted commands because of an unrecognized opcode.
+ optional int32 num_aborted_unrecognized = 7;
+ }
+ repeated CECCommand cec_command = 13;
+ }
+ repeated ExternalAudioVideoDevice external_audio_video_device = 14;
+
+ // Information about the current wireless access point. Collected directly
+ // from the wireless access point via standard apis if the device is
+ // connected to the Internet wirelessly. Introduced for Chrome on TV devices
+ // but also can be collected by ChromeOS, Android or other clients.
+ message ExternalAccessPoint {
+ // The manufacturer name, for example "ASUSTeK Computer Inc.".
+ optional string manufacturer = 1;
+
+ // The model name, for example "Wi-Fi Protected Setup Router".
+ optional string model_name = 2;
+
+ // The model number, for example "RT-N16".
+ optional string model_number = 3;
+
+ // The device name (sometime same as model_number), for example "RT-N16".
+ optional string device_name = 4;
+ }
+ optional ExternalAccessPoint external_access_point = 15;
+
+ // Number of users currently signed into a multiprofile session.
+ // A zero value indicates that the user count changed while the log is open.
+ // Logged only on ChromeOS.
+ optional uint32 multi_profile_user_count = 17;
+
+ // Information about extensions that are installed, masked to provide better
+ // privacy. Only extensions from a single profile are reported; this will
+ // generally be the profile used when the browser is started. The profile
+ // reported on will remain consistent at least until the browser is
+ // relaunched (or the profile is deleted by the user).
+ //
+ // Each client first picks a value for client_key derived from its UMA
+ // client_id:
+ // client_key = client_id % 4096
+ // Then, each installed extension is mapped into a hash bucket according to
+ // bucket = CityHash64(StringPrintf("%d:%s",
+ // client_key, extension_id)) % 1024
+ // The client reports the set of hash buckets occupied by all installed
+ // extensions. If multiple extensions map to the same bucket, that bucket is
+ // still only reported once.
+ repeated int32 occupied_extension_bucket = 18;
+
+ // The state of loaded extensions for this system. The system can have either
+ // no applicable extensions, extensions only from the webstore and verified by
+ // the webstore, extensions only from the webstore but not verified, or
+ // extensions not from the store. If there is a single off-store extension,
+ // then HAS_OFFSTORE is reported. This should be kept in sync with the
+ // corresponding enum in chrome/browser/metrics/extensions_metrics_provider.cc
+ enum ExtensionsState {
+ NO_EXTENSIONS = 0;
+ NO_OFFSTORE_VERIFIED = 1;
+ NO_OFFSTORE_UNVERIFIED = 2;
+ HAS_OFFSTORE = 3;
+ }
+ optional ExtensionsState offstore_extensions_state = 19;
+
+ // The nature of the choice the user was given concerning metrics recording.
+ // Specifically, whether the enable metrics/crash reporting checkbox that was
+ // shown on first run was checked or unchecked by default.
+ // This state is recorded on first run, and uploaded in every UMA log.
+ // Consequently this should only be defined for clients that were installed
+ // after the recording code was implemented.
+ enum UmaDefaultState {
+ // The enable checkbox was unchecked by default.
+ OPT_IN = 0;
+ // The enable checkbox was checked by default.
+ OPT_OUT = 1;
+ // Policy mandated that UMA be enaled, the user had no choice.
+ POLICY_FORCED_ENABLED = 2;
+ }
+ optional UmaDefaultState uma_default_state = 22;
+}
diff --git a/chromium/components/metrics/proto/user_action_event.proto b/chromium/components/metrics/proto/user_action_event.proto
new file mode 100644
index 00000000000..30a93180d07
--- /dev/null
+++ b/chromium/components/metrics/proto/user_action_event.proto
@@ -0,0 +1,23 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Stores information about an event that occurs in response to a user action,
+// e.g. an interaction with a browser UI element.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "UserActionEventProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 3
+message UserActionEventProto {
+ // The name of the action, hashed.
+ optional fixed64 name_hash = 1;
+
+ // The timestamp for the event, in seconds since the epoch.
+ optional int64 time = 2;
+}
diff --git a/chromium/components/metrics/serialization/metric_sample.cc b/chromium/components/metrics/serialization/metric_sample.cc
new file mode 100644
index 00000000000..a0124744bb0
--- /dev/null
+++ b/chromium/components/metrics/serialization/metric_sample.cc
@@ -0,0 +1,197 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/serialization/metric_sample.h"
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+
+namespace metrics {
+
+MetricSample::MetricSample(MetricSample::SampleType sample_type,
+ const std::string& metric_name,
+ int sample,
+ int min,
+ int max,
+ int bucket_count)
+ : type_(sample_type),
+ name_(metric_name),
+ sample_(sample),
+ min_(min),
+ max_(max),
+ bucket_count_(bucket_count) {
+}
+
+MetricSample::~MetricSample() {
+}
+
+bool MetricSample::IsValid() const {
+ return name().find(' ') == std::string::npos &&
+ name().find('\0') == std::string::npos && !name().empty();
+}
+
+std::string MetricSample::ToString() const {
+ if (type_ == CRASH) {
+ return base::StringPrintf("crash%c%s%c",
+ '\0',
+ name().c_str(),
+ '\0');
+ } else if (type_ == SPARSE_HISTOGRAM) {
+ return base::StringPrintf("sparsehistogram%c%s %d%c",
+ '\0',
+ name().c_str(),
+ sample_,
+ '\0');
+ } else if (type_ == LINEAR_HISTOGRAM) {
+ return base::StringPrintf("linearhistogram%c%s %d %d%c",
+ '\0',
+ name().c_str(),
+ sample_,
+ max_,
+ '\0');
+ } else if (type_ == HISTOGRAM) {
+ return base::StringPrintf("histogram%c%s %d %d %d %d%c",
+ '\0',
+ name().c_str(),
+ sample_,
+ min_,
+ max_,
+ bucket_count_,
+ '\0');
+ } else {
+ // The type can only be USER_ACTION.
+ CHECK_EQ(type_, USER_ACTION);
+ return base::StringPrintf("useraction%c%s%c",
+ '\0',
+ name().c_str(),
+ '\0');
+ }
+}
+
+int MetricSample::sample() const {
+ CHECK_NE(type_, USER_ACTION);
+ CHECK_NE(type_, CRASH);
+ return sample_;
+}
+
+int MetricSample::min() const {
+ CHECK_EQ(type_, HISTOGRAM);
+ return min_;
+}
+
+int MetricSample::max() const {
+ CHECK_NE(type_, CRASH);
+ CHECK_NE(type_, USER_ACTION);
+ CHECK_NE(type_, SPARSE_HISTOGRAM);
+ return max_;
+}
+
+int MetricSample::bucket_count() const {
+ CHECK_EQ(type_, HISTOGRAM);
+ return bucket_count_;
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::CrashSample(
+ const std::string& crash_name) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(CRASH, crash_name, 0, 0, 0, 0));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::HistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int min,
+ int max,
+ int bucket_count) {
+ return scoped_ptr<MetricSample>(new MetricSample(
+ HISTOGRAM, histogram_name, sample, min, max, bucket_count));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::ParseHistogram(
+ const std::string& serialized_histogram) {
+ std::vector<base::StringPiece> parts = base::SplitStringPiece(
+ serialized_histogram, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+
+ if (parts.size() != 5)
+ return scoped_ptr<MetricSample>();
+ int sample, min, max, bucket_count;
+ if (parts[0].empty() || !base::StringToInt(parts[1], &sample) ||
+ !base::StringToInt(parts[2], &min) ||
+ !base::StringToInt(parts[3], &max) ||
+ !base::StringToInt(parts[4], &bucket_count)) {
+ return scoped_ptr<MetricSample>();
+ }
+
+ return HistogramSample(parts[0].as_string(), sample, min, max, bucket_count);
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::SparseHistogramSample(
+ const std::string& histogram_name,
+ int sample) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(SPARSE_HISTOGRAM, histogram_name, sample, 0, 0, 0));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::ParseSparseHistogram(
+ const std::string& serialized_histogram) {
+ std::vector<base::StringPiece> parts = base::SplitStringPiece(
+ serialized_histogram, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ if (parts.size() != 2)
+ return scoped_ptr<MetricSample>();
+ int sample;
+ if (parts[0].empty() || !base::StringToInt(parts[1], &sample))
+ return scoped_ptr<MetricSample>();
+
+ return SparseHistogramSample(parts[0].as_string(), sample);
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::LinearHistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int max) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(LINEAR_HISTOGRAM, histogram_name, sample, 0, max, 0));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::ParseLinearHistogram(
+ const std::string& serialized_histogram) {
+ std::vector<base::StringPiece> parts = base::SplitStringPiece(
+ serialized_histogram, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ int sample, max;
+ if (parts.size() != 3)
+ return scoped_ptr<MetricSample>();
+ if (parts[0].empty() || !base::StringToInt(parts[1], &sample) ||
+ !base::StringToInt(parts[2], &max)) {
+ return scoped_ptr<MetricSample>();
+ }
+
+ return LinearHistogramSample(parts[0].as_string(), sample, max);
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::UserActionSample(
+ const std::string& action_name) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(USER_ACTION, action_name, 0, 0, 0, 0));
+}
+
+bool MetricSample::IsEqual(const MetricSample& metric) {
+ return type_ == metric.type_ && name_ == metric.name_ &&
+ sample_ == metric.sample_ && min_ == metric.min_ &&
+ max_ == metric.max_ && bucket_count_ == metric.bucket_count_;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/serialization/metric_sample.h b/chromium/components/metrics/serialization/metric_sample.h
new file mode 100644
index 00000000000..247d2e99653
--- /dev/null
+++ b/chromium/components/metrics/serialization/metric_sample.h
@@ -0,0 +1,118 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_SERIALIZATION_METRIC_SAMPLE_H_
+#define COMPONENTS_METRICS_SERIALIZATION_METRIC_SAMPLE_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace metrics {
+
+// This class is used by libmetrics (ChromeOS) to serialize
+// and deserialize measurements to send them to a metrics sending service.
+// It is meant to be a simple container with serialization functions.
+class MetricSample {
+ public:
+ // Types of metric sample used.
+ enum SampleType {
+ CRASH,
+ HISTOGRAM,
+ LINEAR_HISTOGRAM,
+ SPARSE_HISTOGRAM,
+ USER_ACTION
+ };
+
+ ~MetricSample();
+
+ // Returns true if the sample is valid (can be serialized without ambiguity).
+ //
+ // This function should be used to filter bad samples before serializing them.
+ bool IsValid() const;
+
+ // Getters for type and name. All types of metrics have these so we do not
+ // need to check the type.
+ SampleType type() const { return type_; }
+ const std::string& name() const { return name_; }
+
+ // Getters for sample, min, max, bucket_count.
+ // Check the metric type to make sure the request make sense. (ex: a crash
+ // sample does not have a bucket_count so we crash if we call bucket_count()
+ // on it.)
+ int sample() const;
+ int min() const;
+ int max() const;
+ int bucket_count() const;
+
+ // Returns a serialized version of the sample.
+ //
+ // The serialized message for each type is:
+ // crash: crash\0|name_|\0
+ // user action: useraction\0|name_|\0
+ // histogram: histogram\0|name_| |sample_| |min_| |max_| |bucket_count_|\0
+ // sparsehistogram: sparsehistogram\0|name_| |sample_|\0
+ // linearhistogram: linearhistogram\0|name_| |sample_| |max_|\0
+ std::string ToString() const;
+
+ // Builds a crash sample.
+ static scoped_ptr<MetricSample> CrashSample(const std::string& crash_name);
+
+ // Builds a histogram sample.
+ static scoped_ptr<MetricSample> HistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int min,
+ int max,
+ int bucket_count);
+ // Deserializes a histogram sample.
+ static scoped_ptr<MetricSample> ParseHistogram(const std::string& serialized);
+
+ // Builds a sparse histogram sample.
+ static scoped_ptr<MetricSample> SparseHistogramSample(
+ const std::string& histogram_name,
+ int sample);
+ // Deserializes a sparse histogram sample.
+ static scoped_ptr<MetricSample> ParseSparseHistogram(
+ const std::string& serialized);
+
+ // Builds a linear histogram sample.
+ static scoped_ptr<MetricSample> LinearHistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int max);
+ // Deserializes a linear histogram sample.
+ static scoped_ptr<MetricSample> ParseLinearHistogram(
+ const std::string& serialized);
+
+ // Builds a user action sample.
+ static scoped_ptr<MetricSample> UserActionSample(
+ const std::string& action_name);
+
+ // Returns true if sample and this object represent the same sample (type,
+ // name, sample, min, max, bucket_count match).
+ bool IsEqual(const MetricSample& sample);
+
+ private:
+ MetricSample(SampleType sample_type,
+ const std::string& metric_name,
+ const int sample,
+ const int min,
+ const int max,
+ const int bucket_count);
+
+ const SampleType type_;
+ const std::string name_;
+ const int sample_;
+ const int min_;
+ const int max_;
+ const int bucket_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricSample);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_SERIALIZATION_METRIC_SAMPLE_H_
diff --git a/chromium/components/metrics/serialization/serialization_utils.cc b/chromium/components/metrics/serialization/serialization_utils.cc
new file mode 100644
index 00000000000..0038b4b2006
--- /dev/null
+++ b/chromium/components/metrics/serialization/serialization_utils.cc
@@ -0,0 +1,224 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/serialization/serialization_utils.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <sys/file.h>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "components/metrics/serialization/metric_sample.h"
+
+#define READ_WRITE_ALL_FILE_FLAGS \
+ (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
+
+namespace metrics {
+namespace {
+
+// Reads the next message from |file_descriptor| into |message|.
+//
+// |message| will be set to the empty string if no message could be read (EOF)
+// or the message was badly constructed.
+//
+// Returns false if no message can be read from this file anymore (EOF or
+// unrecoverable error).
+bool ReadMessage(int fd, std::string* message) {
+ CHECK(message);
+
+ int result;
+ int32_t message_size;
+ const int32_t message_header_size = sizeof(message_size);
+ // The file containing the metrics does not leave the device so the writer and
+ // the reader will always have the same endianness.
+ result = HANDLE_EINTR(read(fd, &message_size, message_header_size));
+ if (result < 0) {
+ DPLOG(ERROR) << "reading metrics message header";
+ return false;
+ }
+ if (result == 0) {
+ // This indicates a normal EOF.
+ return false;
+ }
+ if (result < message_header_size) {
+ DLOG(ERROR) << "bad read size " << result << ", expecting "
+ << sizeof(message_size);
+ return false;
+ }
+
+ // kMessageMaxLength applies to the entire message: the 4-byte
+ // length field and the content.
+ if (message_size > SerializationUtils::kMessageMaxLength) {
+ DLOG(ERROR) << "message too long : " << message_size;
+ if (HANDLE_EINTR(lseek(fd, message_size - 4, SEEK_CUR)) == -1) {
+ DLOG(ERROR) << "error while skipping message. abort";
+ return false;
+ }
+ // Badly formatted message was skipped. Treat the badly formatted sample as
+ // an empty sample.
+ message->clear();
+ return true;
+ }
+
+ if (message_size < message_header_size) {
+ DLOG(ERROR) << "message too short : " << message_size;
+ return false;
+ }
+
+ message_size -= message_header_size; // The message size includes itself.
+ char buffer[SerializationUtils::kMessageMaxLength];
+ if (!base::ReadFromFD(fd, buffer, message_size)) {
+ DPLOG(ERROR) << "reading metrics message body";
+ return false;
+ }
+ *message = std::string(buffer, message_size);
+ return true;
+}
+
+} // namespace
+
+scoped_ptr<MetricSample> SerializationUtils::ParseSample(
+ const std::string& sample) {
+ if (sample.empty())
+ return scoped_ptr<MetricSample>();
+
+ std::vector<std::string> parts = base::SplitString(
+ sample, std::string(1, '\0'),
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ // We should have two null terminated strings so split should produce
+ // three chunks.
+ if (parts.size() != 3) {
+ DLOG(ERROR) << "splitting message on \\0 produced " << parts.size()
+ << " parts (expected 3)";
+ return scoped_ptr<MetricSample>();
+ }
+ const std::string& name = parts[0];
+ const std::string& value = parts[1];
+
+ if (base::LowerCaseEqualsASCII(name, "crash")) {
+ return MetricSample::CrashSample(value);
+ } else if (base::LowerCaseEqualsASCII(name, "histogram")) {
+ return MetricSample::ParseHistogram(value);
+ } else if (base::LowerCaseEqualsASCII(name, "linearhistogram")) {
+ return MetricSample::ParseLinearHistogram(value);
+ } else if (base::LowerCaseEqualsASCII(name, "sparsehistogram")) {
+ return MetricSample::ParseSparseHistogram(value);
+ } else if (base::LowerCaseEqualsASCII(name, "useraction")) {
+ return MetricSample::UserActionSample(value);
+ } else {
+ DLOG(ERROR) << "invalid event type: " << name << ", value: " << value;
+ }
+ return scoped_ptr<MetricSample>();
+}
+
+void SerializationUtils::ReadAndTruncateMetricsFromFile(
+ const std::string& filename,
+ ScopedVector<MetricSample>* metrics) {
+ struct stat stat_buf;
+ int result;
+
+ result = stat(filename.c_str(), &stat_buf);
+ if (result < 0) {
+ if (errno != ENOENT)
+ DPLOG(ERROR) << "bad metrics file stat: " << filename;
+
+ // Nothing to collect---try later.
+ return;
+ }
+ if (stat_buf.st_size == 0) {
+ // Also nothing to collect.
+ return;
+ }
+ base::ScopedFD fd(open(filename.c_str(), O_RDWR));
+ if (fd.get() < 0) {
+ DPLOG(ERROR) << "cannot open: " << filename;
+ return;
+ }
+ result = flock(fd.get(), LOCK_EX);
+ if (result < 0) {
+ DPLOG(ERROR) << "cannot lock: " << filename;
+ return;
+ }
+
+ // This processes all messages in the log. When all messages are
+ // read and processed, or an error occurs, truncate the file to zero size.
+ for (;;) {
+ std::string message;
+
+ if (!ReadMessage(fd.get(), &message))
+ break;
+
+ scoped_ptr<MetricSample> sample = ParseSample(message);
+ if (sample)
+ metrics->push_back(std::move(sample));
+ }
+
+ result = ftruncate(fd.get(), 0);
+ if (result < 0)
+ DPLOG(ERROR) << "truncate metrics log: " << filename;
+
+ result = flock(fd.get(), LOCK_UN);
+ if (result < 0)
+ DPLOG(ERROR) << "unlock metrics log: " << filename;
+}
+
+bool SerializationUtils::WriteMetricToFile(const MetricSample& sample,
+ const std::string& filename) {
+ if (!sample.IsValid())
+ return false;
+
+ base::ScopedFD file_descriptor(open(filename.c_str(),
+ O_WRONLY | O_APPEND | O_CREAT,
+ READ_WRITE_ALL_FILE_FLAGS));
+
+ if (file_descriptor.get() < 0) {
+ DPLOG(ERROR) << "error openning the file: " << filename;
+ return false;
+ }
+
+ fchmod(file_descriptor.get(), READ_WRITE_ALL_FILE_FLAGS);
+ // Grab a lock to avoid chrome truncating the file
+ // underneath us. Keep the file locked as briefly as possible.
+ // Freeing file_descriptor will close the file and and remove the lock.
+ if (HANDLE_EINTR(flock(file_descriptor.get(), LOCK_EX)) < 0) {
+ DPLOG(ERROR) << "error locking: " << filename;
+ return false;
+ }
+
+ std::string msg = sample.ToString();
+ int32_t size = msg.length() + sizeof(int32_t);
+ if (size > kMessageMaxLength) {
+ DPLOG(ERROR) << "cannot write message: too long: " << filename;
+ return false;
+ }
+
+ // The file containing the metrics samples will only be read by programs on
+ // the same device so we do not check endianness.
+ if (!base::WriteFileDescriptor(file_descriptor.get(),
+ reinterpret_cast<char*>(&size),
+ sizeof(size))) {
+ DPLOG(ERROR) << "error writing message length: " << filename;
+ return false;
+ }
+
+ if (!base::WriteFileDescriptor(
+ file_descriptor.get(), msg.c_str(), msg.size())) {
+ DPLOG(ERROR) << "error writing message: " << filename;
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/serialization/serialization_utils.h b/chromium/components/metrics/serialization/serialization_utils.h
new file mode 100644
index 00000000000..17e5e4cebc5
--- /dev/null
+++ b/chromium/components/metrics/serialization/serialization_utils.h
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_SERIALIZATION_SERIALIZATION_UTILS_H_
+#define COMPONENTS_METRICS_SERIALIZATION_SERIALIZATION_UTILS_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+
+namespace metrics {
+
+class MetricSample;
+
+// Metrics helpers to serialize and deserialize metrics collected by
+// ChromeOS.
+namespace SerializationUtils {
+
+// Deserializes a sample passed as a string and return a sample.
+// The return value will either be a scoped_ptr to a Metric sample (if the
+// deserialization was successful) or a NULL scoped_ptr.
+scoped_ptr<MetricSample> ParseSample(const std::string& sample);
+
+// Reads all samples from a file and truncate the file when done.
+void ReadAndTruncateMetricsFromFile(const std::string& filename,
+ ScopedVector<MetricSample>* metrics);
+
+// Serializes a sample and write it to filename.
+// The format for the message is:
+// message_size, serialized_message
+// where
+// * message_size is the total length of the message (message_size +
+// serialized_message) on 4 bytes
+// * serialized_message is the serialized version of sample (using ToString)
+//
+// NB: the file will never leave the device so message_size will be written
+// with the architecture's endianness.
+bool WriteMetricToFile(const MetricSample& sample, const std::string& filename);
+
+// Maximum length of a serialized message
+static const int kMessageMaxLength = 1024;
+
+} // namespace SerializationUtils
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_SERIALIZATION_SERIALIZATION_UTILS_H_
diff --git a/chromium/components/metrics/serialization/serialization_utils_unittest.cc b/chromium/components/metrics/serialization/serialization_utils_unittest.cc
new file mode 100644
index 00000000000..fe8f2b2d081
--- /dev/null
+++ b/chromium/components/metrics/serialization/serialization_utils_unittest.cc
@@ -0,0 +1,171 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/serialization/serialization_utils.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "components/metrics/serialization/metric_sample.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+namespace {
+
+class SerializationUtilsTest : public testing::Test {
+ protected:
+ SerializationUtilsTest() {
+ bool success = temporary_dir.CreateUniqueTempDir();
+ if (success) {
+ base::FilePath dir_path = temporary_dir.path();
+ filename = dir_path.value() + "chromeossampletest";
+ filepath = base::FilePath(filename);
+ }
+ }
+
+ void SetUp() override { base::DeleteFile(filepath, false); }
+
+ void TestSerialization(MetricSample* sample) {
+ std::string serialized(sample->ToString());
+ ASSERT_EQ('\0', serialized.back());
+ scoped_ptr<MetricSample> deserialized =
+ SerializationUtils::ParseSample(serialized);
+ ASSERT_TRUE(deserialized);
+ EXPECT_TRUE(sample->IsEqual(*deserialized.get()));
+ }
+
+ std::string filename;
+ base::ScopedTempDir temporary_dir;
+ base::FilePath filepath;
+};
+
+TEST_F(SerializationUtilsTest, CrashSerializeTest) {
+ TestSerialization(MetricSample::CrashSample("test").get());
+}
+
+TEST_F(SerializationUtilsTest, HistogramSerializeTest) {
+ TestSerialization(
+ MetricSample::HistogramSample("myhist", 13, 1, 100, 10).get());
+}
+
+TEST_F(SerializationUtilsTest, LinearSerializeTest) {
+ TestSerialization(
+ MetricSample::LinearHistogramSample("linearhist", 12, 30).get());
+}
+
+TEST_F(SerializationUtilsTest, SparseSerializeTest) {
+ TestSerialization(MetricSample::SparseHistogramSample("mysparse", 30).get());
+}
+
+TEST_F(SerializationUtilsTest, UserActionSerializeTest) {
+ TestSerialization(MetricSample::UserActionSample("myaction").get());
+}
+
+TEST_F(SerializationUtilsTest, IllegalNameAreFilteredTest) {
+ scoped_ptr<MetricSample> sample1 =
+ MetricSample::SparseHistogramSample("no space", 10);
+ scoped_ptr<MetricSample> sample2 = MetricSample::LinearHistogramSample(
+ base::StringPrintf("here%cbhe", '\0'), 1, 3);
+
+ EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*sample1.get(), filename));
+ EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*sample2.get(), filename));
+ int64_t size = 0;
+
+ ASSERT_TRUE(!PathExists(filepath) || base::GetFileSize(filepath, &size));
+
+ EXPECT_EQ(0, size);
+}
+
+TEST_F(SerializationUtilsTest, BadInputIsCaughtTest) {
+ std::string input(
+ base::StringPrintf("sparsehistogram%cname foo%c", '\0', '\0'));
+ EXPECT_EQ(NULL, MetricSample::ParseSparseHistogram(input).get());
+}
+
+TEST_F(SerializationUtilsTest, MessageSeparatedByZero) {
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample("mycrash");
+
+ SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+ int64_t size = 0;
+ ASSERT_TRUE(base::GetFileSize(filepath, &size));
+ // 4 bytes for the size
+ // 5 bytes for crash
+ // 7 bytes for mycrash
+ // 2 bytes for the \0
+ // -> total of 18
+ EXPECT_EQ(size, 18);
+}
+
+TEST_F(SerializationUtilsTest, MessagesTooLongAreDiscardedTest) {
+ // Creates a message that is bigger than the maximum allowed size.
+ // As we are adding extra character (crash, \0s, etc), if the name is
+ // kMessageMaxLength long, it will be too long.
+ std::string name(SerializationUtils::kMessageMaxLength, 'c');
+
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample(name);
+ EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*crash.get(), filename));
+ int64_t size = 0;
+ ASSERT_TRUE(base::GetFileSize(filepath, &size));
+ EXPECT_EQ(0, size);
+}
+
+TEST_F(SerializationUtilsTest, ReadLongMessageTest) {
+ base::File test_file(filepath,
+ base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_APPEND);
+ std::string message(SerializationUtils::kMessageMaxLength + 1, 'c');
+
+ int32_t message_size = message.length() + sizeof(int32_t);
+ test_file.WriteAtCurrentPos(reinterpret_cast<const char*>(&message_size),
+ sizeof(message_size));
+ test_file.WriteAtCurrentPos(message.c_str(), message.length());
+ test_file.Close();
+
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample("test");
+ SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+
+ ScopedVector<MetricSample> samples;
+ SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &samples);
+ ASSERT_EQ(size_t(1), samples.size());
+ ASSERT_TRUE(samples[0] != NULL);
+ EXPECT_TRUE(crash->IsEqual(*samples[0]));
+}
+
+TEST_F(SerializationUtilsTest, WriteReadTest) {
+ scoped_ptr<MetricSample> hist =
+ MetricSample::HistogramSample("myhist", 1, 2, 3, 4);
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample("mycrash");
+ scoped_ptr<MetricSample> lhist =
+ MetricSample::LinearHistogramSample("linear", 1, 10);
+ scoped_ptr<MetricSample> shist =
+ MetricSample::SparseHistogramSample("mysparse", 30);
+ scoped_ptr<MetricSample> action = MetricSample::UserActionSample("myaction");
+
+ SerializationUtils::WriteMetricToFile(*hist.get(), filename);
+ SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+ SerializationUtils::WriteMetricToFile(*lhist.get(), filename);
+ SerializationUtils::WriteMetricToFile(*shist.get(), filename);
+ SerializationUtils::WriteMetricToFile(*action.get(), filename);
+ ScopedVector<MetricSample> vect;
+ SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &vect);
+ ASSERT_EQ(vect.size(), size_t(5));
+ for (int i = 0; i < 5; i++) {
+ ASSERT_TRUE(vect[0] != NULL);
+ }
+ EXPECT_TRUE(hist->IsEqual(*vect[0]));
+ EXPECT_TRUE(crash->IsEqual(*vect[1]));
+ EXPECT_TRUE(lhist->IsEqual(*vect[2]));
+ EXPECT_TRUE(shist->IsEqual(*vect[3]));
+ EXPECT_TRUE(action->IsEqual(*vect[4]));
+
+ int64_t size = 0;
+ ASSERT_TRUE(base::GetFileSize(filepath, &size));
+ ASSERT_EQ(0, size);
+}
+
+} // namespace
+} // namespace metrics
diff --git a/chromium/components/metrics/stability_metrics_helper.cc b/chromium/components/metrics/stability_metrics_helper.cc
new file mode 100644
index 00000000000..52b91f0f127
--- /dev/null
+++ b/chromium/components/metrics/stability_metrics_helper.cc
@@ -0,0 +1,221 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/stability_metrics_helper.h"
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "build/build_config.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/proto/system_profile.pb.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+#if defined(OS_WIN)
+#include <windows.h> // Needed for STATUS_* codes
+#endif
+
+#if defined(OS_CHROMEOS)
+#include "components/metrics/system_memory_stats_recorder.h"
+#endif
+
+namespace metrics {
+
+namespace {
+
+enum RendererType {
+ RENDERER_TYPE_RENDERER = 1,
+ RENDERER_TYPE_EXTENSION,
+ // NOTE: Add new action types only immediately above this line. Also,
+ // make sure the enum list in tools/metrics/histograms/histograms.xml is
+ // updated with any change in here.
+ RENDERER_TYPE_COUNT
+};
+
+// Converts an exit code into something that can be inserted into our
+// histograms (which expect non-negative numbers less than MAX_INT).
+int MapCrashExitCodeForHistogram(int exit_code) {
+#if defined(OS_WIN)
+ // Since |abs(STATUS_GUARD_PAGE_VIOLATION) == MAX_INT| it causes problems in
+ // histograms.cc. Solve this by remapping it to a smaller value, which
+ // hopefully doesn't conflict with other codes.
+ if (static_cast<DWORD>(exit_code) == STATUS_GUARD_PAGE_VIOLATION)
+ return 0x1FCF7EC3; // Randomly picked number.
+#endif
+
+ return std::abs(exit_code);
+}
+
+void RecordChildKills(int histogram_type) {
+ UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildKills",
+ histogram_type, RENDERER_TYPE_COUNT);
+}
+
+} // namespace
+
+StabilityMetricsHelper::StabilityMetricsHelper(PrefService* local_state)
+ : local_state_(local_state) {
+ DCHECK(local_state_);
+}
+
+StabilityMetricsHelper::~StabilityMetricsHelper() {}
+
+void StabilityMetricsHelper::ProvideStabilityMetrics(
+ SystemProfileProto* system_profile_proto) {
+ SystemProfileProto_Stability* stability_proto =
+ system_profile_proto->mutable_stability();
+
+ int count = local_state_->GetInteger(prefs::kStabilityPageLoadCount);
+ if (count) {
+ stability_proto->set_page_load_count(count);
+ local_state_->SetInteger(prefs::kStabilityPageLoadCount, 0);
+ }
+
+ count = local_state_->GetInteger(prefs::kStabilityChildProcessCrashCount);
+ if (count) {
+ stability_proto->set_child_process_crash_count(count);
+ local_state_->SetInteger(prefs::kStabilityChildProcessCrashCount, 0);
+ }
+
+ count = local_state_->GetInteger(prefs::kStabilityRendererCrashCount);
+ if (count) {
+ stability_proto->set_renderer_crash_count(count);
+ local_state_->SetInteger(prefs::kStabilityRendererCrashCount, 0);
+ }
+
+ count = local_state_->GetInteger(prefs::kStabilityRendererFailedLaunchCount);
+ if (count) {
+ stability_proto->set_renderer_failed_launch_count(count);
+ local_state_->SetInteger(prefs::kStabilityRendererFailedLaunchCount, 0);
+ }
+
+ count =
+ local_state_->GetInteger(prefs::kStabilityExtensionRendererCrashCount);
+ if (count) {
+ stability_proto->set_extension_renderer_crash_count(count);
+ local_state_->SetInteger(prefs::kStabilityExtensionRendererCrashCount, 0);
+ }
+
+ count = local_state_->GetInteger(
+ prefs::kStabilityExtensionRendererFailedLaunchCount);
+ if (count) {
+ stability_proto->set_extension_renderer_failed_launch_count(count);
+ local_state_->SetInteger(
+ prefs::kStabilityExtensionRendererFailedLaunchCount, 0);
+ }
+
+ count = local_state_->GetInteger(prefs::kStabilityRendererHangCount);
+ if (count) {
+ stability_proto->set_renderer_hang_count(count);
+ local_state_->SetInteger(prefs::kStabilityRendererHangCount, 0);
+ }
+}
+
+void StabilityMetricsHelper::ClearSavedStabilityMetrics() {
+ // Clear all the prefs used in this class in UMA reports (which doesn't
+ // include |kUninstallMetricsPageLoadCount| as it's not sent up by UMA).
+ local_state_->SetInteger(prefs::kStabilityChildProcessCrashCount, 0);
+ local_state_->SetInteger(prefs::kStabilityExtensionRendererCrashCount, 0);
+ local_state_->SetInteger(prefs::kStabilityExtensionRendererFailedLaunchCount,
+ 0);
+ local_state_->SetInteger(prefs::kStabilityPageLoadCount, 0);
+ local_state_->SetInteger(prefs::kStabilityRendererCrashCount, 0);
+ local_state_->SetInteger(prefs::kStabilityRendererFailedLaunchCount, 0);
+ local_state_->SetInteger(prefs::kStabilityRendererHangCount, 0);
+}
+
+// static
+void StabilityMetricsHelper::RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterIntegerPref(prefs::kStabilityChildProcessCrashCount, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityExtensionRendererCrashCount,
+ 0);
+ registry->RegisterIntegerPref(
+ prefs::kStabilityExtensionRendererFailedLaunchCount, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityPageLoadCount, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityRendererCrashCount, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityRendererFailedLaunchCount, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityRendererHangCount, 0);
+
+ registry->RegisterInt64Pref(prefs::kUninstallMetricsPageLoadCount, 0);
+}
+
+void StabilityMetricsHelper::BrowserChildProcessCrashed() {
+ IncrementPrefValue(prefs::kStabilityChildProcessCrashCount);
+}
+
+void StabilityMetricsHelper::LogLoadStarted() {
+ base::RecordAction(base::UserMetricsAction("PageLoad"));
+ // TODO(asvitkine): Check if this is used for anything and if not, remove.
+ LOCAL_HISTOGRAM_BOOLEAN("Chrome.UmaPageloadCounter", true);
+ IncrementPrefValue(prefs::kStabilityPageLoadCount);
+ IncrementLongPrefsValue(prefs::kUninstallMetricsPageLoadCount);
+ // We need to save the prefs, as page load count is a critical stat, and it
+ // might be lost due to a crash :-(.
+}
+
+void StabilityMetricsHelper::LogRendererCrash(bool was_extension_process,
+ base::TerminationStatus status,
+ int exit_code) {
+ int histogram_type =
+ was_extension_process ? RENDERER_TYPE_EXTENSION : RENDERER_TYPE_RENDERER;
+ if (status == base::TERMINATION_STATUS_PROCESS_CRASHED ||
+ status == base::TERMINATION_STATUS_ABNORMAL_TERMINATION) {
+ if (was_extension_process) {
+ IncrementPrefValue(prefs::kStabilityExtensionRendererCrashCount);
+
+ UMA_HISTOGRAM_SPARSE_SLOWLY("CrashExitCodes.Extension",
+ MapCrashExitCodeForHistogram(exit_code));
+ } else {
+ IncrementPrefValue(prefs::kStabilityRendererCrashCount);
+
+ UMA_HISTOGRAM_SPARSE_SLOWLY("CrashExitCodes.Renderer",
+ MapCrashExitCodeForHistogram(exit_code));
+ }
+
+ UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildCrashes",
+ histogram_type, RENDERER_TYPE_COUNT);
+ } else if (status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED) {
+ RecordChildKills(histogram_type);
+#if defined(OS_CHROMEOS)
+ } else if (status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM) {
+ RecordChildKills(histogram_type);
+ UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildKills.OOM",
+ was_extension_process ? 2 : 1, 3);
+ RecordMemoryStats(was_extension_process
+ ? RECORD_MEMORY_STATS_EXTENSIONS_OOM_KILLED
+ : RECORD_MEMORY_STATS_CONTENTS_OOM_KILLED);
+#endif
+ } else if (status == base::TERMINATION_STATUS_STILL_RUNNING) {
+ UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.DisconnectedAlive",
+ histogram_type, RENDERER_TYPE_COUNT);
+ } else if (status == base::TERMINATION_STATUS_LAUNCH_FAILED) {
+ UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildLaunchFailures",
+ histogram_type, RENDERER_TYPE_COUNT);
+ if (was_extension_process)
+ IncrementPrefValue(prefs::kStabilityExtensionRendererFailedLaunchCount);
+ else
+ IncrementPrefValue(prefs::kStabilityRendererFailedLaunchCount);
+ }
+}
+
+void StabilityMetricsHelper::IncrementPrefValue(const char* path) {
+ int value = local_state_->GetInteger(path);
+ local_state_->SetInteger(path, value + 1);
+}
+
+void StabilityMetricsHelper::IncrementLongPrefsValue(const char* path) {
+ int64_t value = local_state_->GetInt64(path);
+ local_state_->SetInt64(path, value + 1);
+}
+
+void StabilityMetricsHelper::LogRendererHang() {
+ IncrementPrefValue(prefs::kStabilityRendererHangCount);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/stability_metrics_helper.h b/chromium/components/metrics/stability_metrics_helper.h
new file mode 100644
index 00000000000..42a9ca20217
--- /dev/null
+++ b/chromium/components/metrics/stability_metrics_helper.h
@@ -0,0 +1,63 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_STABILITY_METRICS_HELPER_H_
+#define COMPONENTS_METRICS_STABILITY_METRICS_HELPER_H_
+
+#include "base/macros.h"
+#include "base/metrics/user_metrics.h"
+#include "base/process/kill.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace metrics {
+
+class SystemProfileProto;
+
+// StabilityMetricsHelper is a class that providers functionality common to
+// different embedders' stability metrics providers.
+class StabilityMetricsHelper {
+ public:
+ explicit StabilityMetricsHelper(PrefService* local_state);
+ ~StabilityMetricsHelper();
+
+ // Provides stability metrics.
+ void ProvideStabilityMetrics(SystemProfileProto* system_profile_proto);
+
+ // Clears the gathered stability metrics.
+ void ClearSavedStabilityMetrics();
+
+ // Records a browser child process crash.
+ void BrowserChildProcessCrashed();
+
+ // Logs the initiation of a page load.
+ void LogLoadStarted();
+
+ // Records a renderer process crash.
+ void LogRendererCrash(bool was_exception_process,
+ base::TerminationStatus status,
+ int exit_code);
+
+ // Records a renderer process hang.
+ void LogRendererHang();
+
+ // Registers local state prefs used by this class.
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ private:
+ // Increment an Integer pref value specified by |path|.
+ void IncrementPrefValue(const char* path);
+
+ // Increment a 64-bit Integer pref value specified by |path|.
+ void IncrementLongPrefsValue(const char* path);
+
+ PrefService* local_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(StabilityMetricsHelper);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_STABILITY_METRICS_HELPER_H_
diff --git a/chromium/components/metrics/stability_metrics_helper_unittest.cc b/chromium/components/metrics/stability_metrics_helper_unittest.cc
new file mode 100644
index 00000000000..6ca84df0f28
--- /dev/null
+++ b/chromium/components/metrics/stability_metrics_helper_unittest.cc
@@ -0,0 +1,96 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/stability_metrics_helper.h"
+
+#include "base/macros.h"
+#include "components/metrics/proto/system_profile.pb.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+class StabilityMetricsHelperTest : public testing::Test {
+ protected:
+ StabilityMetricsHelperTest() : prefs_(new TestingPrefServiceSimple) {
+ StabilityMetricsHelper::RegisterPrefs(prefs()->registry());
+ }
+
+ TestingPrefServiceSimple* prefs() { return prefs_.get(); }
+
+ private:
+ scoped_ptr<TestingPrefServiceSimple> prefs_;
+
+ DISALLOW_COPY_AND_ASSIGN(StabilityMetricsHelperTest);
+};
+
+} // namespace
+
+TEST_F(StabilityMetricsHelperTest, BrowserChildProcessCrashed) {
+ StabilityMetricsHelper helper(prefs());
+
+ helper.BrowserChildProcessCrashed();
+ helper.BrowserChildProcessCrashed();
+
+ // Call ProvideStabilityMetrics to check that it will force pending tasks to
+ // be executed immediately.
+ metrics::SystemProfileProto system_profile;
+
+ helper.ProvideStabilityMetrics(&system_profile);
+
+ // Check current number of instances created.
+ const metrics::SystemProfileProto_Stability& stability =
+ system_profile.stability();
+
+ EXPECT_EQ(2, stability.child_process_crash_count());
+}
+
+TEST_F(StabilityMetricsHelperTest, LogRendererCrash) {
+ StabilityMetricsHelper helper(prefs());
+
+ // Crash and abnormal termination should increment renderer crash count.
+ helper.LogRendererCrash(false, base::TERMINATION_STATUS_PROCESS_CRASHED, 1);
+
+ helper.LogRendererCrash(false, base::TERMINATION_STATUS_ABNORMAL_TERMINATION,
+ 1);
+
+ // Kill does not increment renderer crash count.
+ helper.LogRendererCrash(false, base::TERMINATION_STATUS_PROCESS_WAS_KILLED,
+ 1);
+
+ // Failed launch increments failed launch count.
+ helper.LogRendererCrash(false, base::TERMINATION_STATUS_LAUNCH_FAILED, 1);
+
+ metrics::SystemProfileProto system_profile;
+
+ // Call ProvideStabilityMetrics to check that it will force pending tasks to
+ // be executed immediately.
+ helper.ProvideStabilityMetrics(&system_profile);
+
+ EXPECT_EQ(2, system_profile.stability().renderer_crash_count());
+ EXPECT_EQ(1, system_profile.stability().renderer_failed_launch_count());
+ EXPECT_EQ(0, system_profile.stability().extension_renderer_crash_count());
+
+ helper.ClearSavedStabilityMetrics();
+
+ // Crash and abnormal termination should increment extension crash count.
+ helper.LogRendererCrash(true, base::TERMINATION_STATUS_PROCESS_CRASHED, 1);
+
+ // Failed launch increments extension failed launch count.
+ helper.LogRendererCrash(true, base::TERMINATION_STATUS_LAUNCH_FAILED, 1);
+
+ system_profile.Clear();
+ helper.ProvideStabilityMetrics(&system_profile);
+
+ EXPECT_EQ(0, system_profile.stability().renderer_crash_count());
+ EXPECT_EQ(1, system_profile.stability().extension_renderer_crash_count());
+ EXPECT_EQ(
+ 1, system_profile.stability().extension_renderer_failed_launch_count());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/system_memory_stats_recorder.h b/chromium/components/metrics/system_memory_stats_recorder.h
new file mode 100644
index 00000000000..bdd30f00f42
--- /dev/null
+++ b/chromium/components/metrics/system_memory_stats_recorder.h
@@ -0,0 +1,30 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_SYSTEM_MEMORY_STATS_RECORDER_H_
+#define COMPONENTS_METRICS_SYSTEM_MEMORY_STATS_RECORDER_H_
+
+namespace metrics {
+
+// Record a memory size in megabytes, over a potential interval up to 32 GB.
+#define UMA_HISTOGRAM_LARGE_MEMORY_MB(name, sample) \
+ UMA_HISTOGRAM_CUSTOM_COUNTS(name, sample, 1, 32768, 50)
+
+// The type of memory UMA stats to be recorded in RecordMemoryStats.
+enum RecordMemoryStatsType {
+ // When a tab was discarded.
+ RECORD_MEMORY_STATS_TAB_DISCARDED,
+
+ // Right after the renderer for contents was killed.
+ RECORD_MEMORY_STATS_CONTENTS_OOM_KILLED,
+
+ // Right after the renderer for extensions was killed.
+ RECORD_MEMORY_STATS_EXTENSIONS_OOM_KILLED,
+};
+
+void RecordMemoryStats(RecordMemoryStatsType type);
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_SYSTEM_MEMORY_STATS_RECORDER_H_
diff --git a/chromium/components/metrics/system_memory_stats_recorder_linux.cc b/chromium/components/metrics/system_memory_stats_recorder_linux.cc
new file mode 100644
index 00000000000..c69dbaa0460
--- /dev/null
+++ b/chromium/components/metrics/system_memory_stats_recorder_linux.cc
@@ -0,0 +1,98 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/system_memory_stats_recorder.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "base/process/process_metrics.h"
+#include "build/build_config.h"
+
+namespace metrics {
+
+// Record a size in megabytes, a potential interval from 250MB up to 32 GB.
+#define UMA_HISTOGRAM_ALLOCATED_MEGABYTES(name, sample) \
+ UMA_HISTOGRAM_CUSTOM_COUNTS(name, sample, 250, 32768, 50)
+
+// Records a statistics |sample| for UMA histogram |name|
+// using a linear distribution of buckets.
+#define UMA_HISTOGRAM_LINEAR(name, sample, max, buckets) \
+ STATIC_HISTOGRAM_POINTER_BLOCK( \
+ name, Add(sample), \
+ base::LinearHistogram::FactoryGet( \
+ name, \
+ 1, /* Minimum. The 0 bin for underflow is automatically added. */ \
+ max + 1, /* Ensure bucket size of |maximum| / |bucket_count|. */ \
+ buckets + 2, /* Account for the underflow and overflow bins. */ \
+ base::Histogram::kUmaTargetedHistogramFlag))
+
+#define UMA_HISTOGRAM_MEGABYTES_LINEAR(name, sample) \
+ UMA_HISTOGRAM_LINEAR(name, sample, 2500, 50)
+
+void RecordMemoryStats(RecordMemoryStatsType type) {
+ base::SystemMemoryInfoKB memory;
+ if (!base::GetSystemMemoryInfo(&memory))
+ return;
+#if defined(OS_CHROMEOS)
+ // Record graphics GEM object size in a histogram with 50 MB buckets.
+ int mem_graphics_gem_mb = 0;
+ if (memory.gem_size != -1)
+ mem_graphics_gem_mb = memory.gem_size / 1024 / 1024;
+
+ // Record shared memory (used by renderer/GPU buffers).
+ int mem_shmem_mb = memory.shmem / 1024;
+#endif
+
+ // On Intel, graphics objects are in anonymous pages, but on ARM they are
+ // not. For a total "allocated count" add in graphics pages on ARM.
+ int mem_allocated_mb = (memory.active_anon + memory.inactive_anon) / 1024;
+#if defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY)
+ mem_allocated_mb += mem_graphics_gem_mb;
+#endif
+
+ int mem_available_mb =
+ (memory.active_file + memory.inactive_file + memory.free) / 1024;
+
+ switch (type) {
+ case RECORD_MEMORY_STATS_TAB_DISCARDED: {
+#if defined(OS_CHROMEOS)
+ UMA_HISTOGRAM_MEGABYTES_LINEAR("Tabs.Discard.MemGraphicsMB",
+ mem_graphics_gem_mb);
+ UMA_HISTOGRAM_MEGABYTES_LINEAR("Tabs.Discard.MemShmemMB", mem_shmem_mb);
+#endif
+ UMA_HISTOGRAM_ALLOCATED_MEGABYTES("Tabs.Discard.MemAllocatedMB",
+ mem_allocated_mb);
+ UMA_HISTOGRAM_LARGE_MEMORY_MB("Tabs.Discard.MemAvailableMB",
+ mem_available_mb);
+ break;
+ }
+ case RECORD_MEMORY_STATS_CONTENTS_OOM_KILLED: {
+#if defined(OS_CHROMEOS)
+ UMA_HISTOGRAM_MEGABYTES_LINEAR("Memory.OOMKill.Contents.MemGraphicsMB",
+ mem_graphics_gem_mb);
+ UMA_HISTOGRAM_MEGABYTES_LINEAR("Memory.OOMKill.Contents.MemShmemMB",
+ mem_shmem_mb);
+#endif
+ UMA_HISTOGRAM_ALLOCATED_MEGABYTES(
+ "Memory.OOMKill.Contents.MemAllocatedMB", mem_allocated_mb);
+ UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.OOMKill.Contents.MemAvailableMB",
+ mem_available_mb);
+ break;
+ }
+ case RECORD_MEMORY_STATS_EXTENSIONS_OOM_KILLED: {
+#if defined(OS_CHROMEOS)
+ UMA_HISTOGRAM_MEGABYTES_LINEAR("Memory.OOMKill.Extensions.MemGraphicsMB",
+ mem_graphics_gem_mb);
+ UMA_HISTOGRAM_MEGABYTES_LINEAR("Memory.OOMKill.Extensions.MemShmemMB",
+ mem_shmem_mb);
+#endif
+ UMA_HISTOGRAM_ALLOCATED_MEGABYTES(
+ "Memory.OOMKill.Extensions.MemAllocatedMB", mem_allocated_mb);
+ UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.OOMKill.Extensions.MemAvailableMB",
+ mem_available_mb);
+ break;
+ }
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/system_memory_stats_recorder_win.cc b/chromium/components/metrics/system_memory_stats_recorder_win.cc
new file mode 100644
index 00000000000..cdd314989f3
--- /dev/null
+++ b/chromium/components/metrics/system_memory_stats_recorder_win.cc
@@ -0,0 +1,47 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/system_memory_stats_recorder.h"
+
+#include <windows.h>
+
+#include "base/metrics/histogram_macros.h"
+#include "base/process/process_metrics.h"
+
+namespace metrics {
+namespace {
+enum { kMBytes = 1024 * 1024 };
+}
+
+void RecordMemoryStats(RecordMemoryStatsType type) {
+ MEMORYSTATUSEX mem_status;
+ mem_status.dwLength = sizeof(mem_status);
+ if (!::GlobalMemoryStatusEx(&mem_status))
+ return;
+
+ switch (type) {
+ case RECORD_MEMORY_STATS_TAB_DISCARDED: {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Memory.Stats.Win.MemoryLoad",
+ mem_status.dwMemoryLoad, 0, 100, 101);
+ UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.TotalPhys2",
+ mem_status.ullTotalPhys / kMBytes);
+ UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.AvailPhys2",
+ mem_status.ullAvailPhys / kMBytes);
+ UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.TotalPageFile2",
+ mem_status.ullTotalPageFile / kMBytes);
+ UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.AvailPageFile2",
+ mem_status.ullAvailPageFile / kMBytes);
+ UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.TotalVirtual2",
+ mem_status.ullTotalVirtual / kMBytes);
+ UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.AvailVirtual2",
+ mem_status.ullAvailVirtual / kMBytes);
+ break;
+ }
+ default:
+ NOTREACHED() << L"Received unexpected notification";
+ break;
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/test_metrics_provider.cc b/chromium/components/metrics/test_metrics_provider.cc
new file mode 100644
index 00000000000..409d8d129d6
--- /dev/null
+++ b/chromium/components/metrics/test_metrics_provider.cc
@@ -0,0 +1,35 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/test_metrics_provider.h"
+
+#include "base/metrics/histogram_macros.h"
+
+namespace metrics {
+
+void TestMetricsProvider::Init() {
+ init_called_ = true;
+}
+
+void TestMetricsProvider::OnRecordingDisabled() {
+ on_recording_disabled_called_ = true;
+}
+
+bool TestMetricsProvider::HasInitialStabilityMetrics() {
+ return has_initial_stability_metrics_;
+}
+
+void TestMetricsProvider::ProvideInitialStabilityMetrics(
+ SystemProfileProto* system_profile_proto) {
+ UMA_STABILITY_HISTOGRAM_ENUMERATION("TestMetricsProvider.Initial", 1, 2);
+ provide_initial_stability_metrics_called_ = true;
+}
+
+void TestMetricsProvider::ProvideStabilityMetrics(
+ SystemProfileProto* system_profile_proto) {
+ UMA_STABILITY_HISTOGRAM_ENUMERATION("TestMetricsProvider.Regular", 1, 2);
+ provide_stability_metrics_called_ = true;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/test_metrics_provider.h b/chromium/components/metrics/test_metrics_provider.h
new file mode 100644
index 00000000000..6536ea23198
--- /dev/null
+++ b/chromium/components/metrics/test_metrics_provider.h
@@ -0,0 +1,57 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_TEST_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_TEST_METRICS_PROVIDER_H_
+
+#include "base/macros.h"
+#include "components/metrics/metrics_provider.h"
+
+namespace metrics {
+
+// A simple implementation of MetricsProvider that checks that its providing
+// functions are called, for use in tests.
+class TestMetricsProvider : public MetricsProvider {
+ public:
+ TestMetricsProvider()
+ : init_called_(false),
+ on_recording_disabled_called_(false),
+ has_initial_stability_metrics_(false),
+ provide_initial_stability_metrics_called_(false),
+ provide_stability_metrics_called_(false) {}
+
+ // MetricsProvider:
+ void Init() override;
+ void OnRecordingDisabled() override;
+ bool HasInitialStabilityMetrics() override;
+ void ProvideInitialStabilityMetrics(
+ SystemProfileProto* system_profile_proto) override;
+ void ProvideStabilityMetrics(
+ SystemProfileProto* system_profile_proto) override;
+
+ bool init_called() { return init_called_; }
+ bool on_recording_disabled_called() { return on_recording_disabled_called_; }
+ void set_has_initial_stability_metrics(bool has_initial_stability_metrics) {
+ has_initial_stability_metrics_ = has_initial_stability_metrics;
+ }
+ bool provide_initial_stability_metrics_called() const {
+ return provide_initial_stability_metrics_called_;
+ }
+ bool provide_stability_metrics_called() const {
+ return provide_stability_metrics_called_;
+ }
+
+ private:
+ bool init_called_;
+ bool on_recording_disabled_called_;
+ bool has_initial_stability_metrics_;
+ bool provide_initial_stability_metrics_called_;
+ bool provide_stability_metrics_called_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestMetricsProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_TEST_METRICS_PROVIDER_H_
diff --git a/chromium/components/metrics/test_metrics_service_client.cc b/chromium/components/metrics/test_metrics_service_client.cc
new file mode 100644
index 00000000000..7aeaddf9c69
--- /dev/null
+++ b/chromium/components/metrics/test_metrics_service_client.cc
@@ -0,0 +1,93 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/test_metrics_service_client.h"
+
+#include "base/callback.h"
+#include "components/metrics/metrics_log_uploader.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+
+namespace metrics {
+
+// static
+const char TestMetricsServiceClient::kBrandForTesting[] = "brand_for_testing";
+
+TestMetricsServiceClient::TestMetricsServiceClient()
+ : version_string_("5.0.322.0-64-devel"),
+ product_(ChromeUserMetricsExtension::CHROME),
+ reporting_is_managed_(false),
+ enable_default_(MetricsServiceClient::DEFAULT_UNKNOWN) {}
+
+TestMetricsServiceClient::~TestMetricsServiceClient() {
+}
+
+metrics::MetricsService* TestMetricsServiceClient::GetMetricsService() {
+ return nullptr;
+}
+
+void TestMetricsServiceClient::SetMetricsClientId(
+ const std::string& client_id) {
+ client_id_ = client_id;
+}
+
+void TestMetricsServiceClient::OnRecordingDisabled() {
+}
+
+bool TestMetricsServiceClient::IsOffTheRecordSessionActive() {
+ return false;
+}
+
+int32_t TestMetricsServiceClient::GetProduct() {
+ return product_;
+}
+
+std::string TestMetricsServiceClient::GetApplicationLocale() {
+ return "en-US";
+}
+
+bool TestMetricsServiceClient::GetBrand(std::string* brand_code) {
+ *brand_code = kBrandForTesting;
+ return true;
+}
+
+SystemProfileProto::Channel TestMetricsServiceClient::GetChannel() {
+ return SystemProfileProto::CHANNEL_BETA;
+}
+
+std::string TestMetricsServiceClient::GetVersionString() {
+ return version_string_;
+}
+
+void TestMetricsServiceClient::OnLogUploadComplete() {
+}
+
+void TestMetricsServiceClient::InitializeSystemProfileMetrics(
+ const base::Closure& done_callback) {
+ done_callback.Run();
+}
+
+void TestMetricsServiceClient::CollectFinalMetricsForLog(
+ const base::Closure& done_callback) {
+ done_callback.Run();
+}
+
+scoped_ptr<MetricsLogUploader> TestMetricsServiceClient::CreateUploader(
+ const base::Callback<void(int)>& on_upload_complete) {
+ return scoped_ptr<MetricsLogUploader>();
+}
+
+base::TimeDelta TestMetricsServiceClient::GetStandardUploadInterval() {
+ return base::TimeDelta::FromMinutes(5);
+}
+
+bool TestMetricsServiceClient::IsReportingPolicyManaged() {
+ return reporting_is_managed_;
+}
+
+MetricsServiceClient::EnableMetricsDefault
+TestMetricsServiceClient::GetDefaultOptIn() {
+ return enable_default_;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/test_metrics_service_client.h b/chromium/components/metrics/test_metrics_service_client.h
new file mode 100644
index 00000000000..8710355901c
--- /dev/null
+++ b/chromium/components/metrics/test_metrics_service_client.h
@@ -0,0 +1,69 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_TEST_METRICS_SERVICE_CLIENT_H_
+#define COMPONENTS_METRICS_TEST_METRICS_SERVICE_CLIENT_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "components/metrics/metrics_service_client.h"
+
+namespace metrics {
+
+// A simple concrete implementation of the MetricsServiceClient interface, for
+// use in tests.
+class TestMetricsServiceClient : public MetricsServiceClient {
+ public:
+ static const char kBrandForTesting[];
+
+ TestMetricsServiceClient();
+ ~TestMetricsServiceClient() override;
+
+ // MetricsServiceClient:
+ metrics::MetricsService* GetMetricsService() override;
+ void SetMetricsClientId(const std::string& client_id) override;
+ void OnRecordingDisabled() override;
+ bool IsOffTheRecordSessionActive() override;
+ int32_t GetProduct() override;
+ std::string GetApplicationLocale() override;
+ bool GetBrand(std::string* brand_code) override;
+ SystemProfileProto::Channel GetChannel() override;
+ std::string GetVersionString() override;
+ void OnLogUploadComplete() override;
+ void InitializeSystemProfileMetrics(
+ const base::Closure& done_callback) override;
+ void CollectFinalMetricsForLog(const base::Closure& done_callback) override;
+ scoped_ptr<MetricsLogUploader> CreateUploader(
+ const base::Callback<void(int)>& on_upload_complete) override;
+ base::TimeDelta GetStandardUploadInterval() override;
+ bool IsReportingPolicyManaged() override;
+ MetricsServiceClient::EnableMetricsDefault GetDefaultOptIn() override;
+
+ const std::string& get_client_id() const { return client_id_; }
+ void set_version_string(const std::string& str) { version_string_ = str; }
+ void set_product(int32_t product) { product_ = product; }
+ void set_reporting_is_managed(bool managed) {
+ reporting_is_managed_ = managed;
+ }
+ void set_enable_default(
+ MetricsServiceClient::EnableMetricsDefault enable_default) {
+ enable_default_ = enable_default;
+ }
+
+ private:
+ std::string client_id_;
+ std::string version_string_;
+ int32_t product_;
+ bool reporting_is_managed_;
+ MetricsServiceClient::EnableMetricsDefault enable_default_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestMetricsServiceClient);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_TEST_METRICS_SERVICE_CLIENT_H_
diff --git a/chromium/components/metrics/ui/DEPS b/chromium/components/metrics/ui/DEPS
new file mode 100644
index 00000000000..b273ae3319c
--- /dev/null
+++ b/chromium/components/metrics/ui/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+ui/gfx",
+]
diff --git a/chromium/components/metrics/ui/screen_info_metrics_provider.cc b/chromium/components/metrics/ui/screen_info_metrics_provider.cc
new file mode 100644
index 00000000000..3d31e988db9
--- /dev/null
+++ b/chromium/components/metrics/ui/screen_info_metrics_provider.cc
@@ -0,0 +1,92 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/ui/screen_info_metrics_provider.h"
+
+#include "build/build_config.h"
+#include "components/metrics/proto/system_profile.pb.h"
+#include "ui/gfx/screen.h"
+
+namespace metrics {
+
+#if defined(OS_WIN)
+
+#include <windows.h>
+
+namespace {
+
+struct ScreenDPIInformation {
+ double max_dpi_x;
+ double max_dpi_y;
+};
+
+// Called once for each connected monitor.
+BOOL CALLBACK GetMonitorDPICallback(HMONITOR, HDC hdc, LPRECT, LPARAM dwData) {
+ const double kMillimetersPerInch = 25.4;
+ ScreenDPIInformation* screen_info =
+ reinterpret_cast<ScreenDPIInformation*>(dwData);
+ // Size of screen, in mm.
+ DWORD size_x = GetDeviceCaps(hdc, HORZSIZE);
+ DWORD size_y = GetDeviceCaps(hdc, VERTSIZE);
+ double dpi_x = (size_x > 0) ?
+ GetDeviceCaps(hdc, HORZRES) / (size_x / kMillimetersPerInch) : 0;
+ double dpi_y = (size_y > 0) ?
+ GetDeviceCaps(hdc, VERTRES) / (size_y / kMillimetersPerInch) : 0;
+ screen_info->max_dpi_x = std::max(dpi_x, screen_info->max_dpi_x);
+ screen_info->max_dpi_y = std::max(dpi_y, screen_info->max_dpi_y);
+ return TRUE;
+}
+
+void WriteScreenDPIInformationProto(SystemProfileProto::Hardware* hardware) {
+ HDC desktop_dc = GetDC(NULL);
+ if (desktop_dc) {
+ ScreenDPIInformation si = {0, 0};
+ if (EnumDisplayMonitors(desktop_dc, NULL, GetMonitorDPICallback,
+ reinterpret_cast<LPARAM>(&si))) {
+ hardware->set_max_dpi_x(si.max_dpi_x);
+ hardware->set_max_dpi_y(si.max_dpi_y);
+ }
+ ReleaseDC(GetDesktopWindow(), desktop_dc);
+ }
+}
+
+} // namespace
+
+#endif // defined(OS_WIN)
+
+ScreenInfoMetricsProvider::ScreenInfoMetricsProvider() {
+}
+
+ScreenInfoMetricsProvider::~ScreenInfoMetricsProvider() {
+}
+
+void ScreenInfoMetricsProvider::ProvideSystemProfileMetrics(
+ SystemProfileProto* system_profile_proto) {
+ SystemProfileProto::Hardware* hardware =
+ system_profile_proto->mutable_hardware();
+
+ const gfx::Size display_size = GetScreenSize();
+ hardware->set_primary_screen_width(display_size.width());
+ hardware->set_primary_screen_height(display_size.height());
+ hardware->set_primary_screen_scale_factor(GetScreenDeviceScaleFactor());
+ hardware->set_screen_count(GetScreenCount());
+
+#if defined(OS_WIN)
+ WriteScreenDPIInformationProto(hardware);
+#endif
+}
+
+gfx::Size ScreenInfoMetricsProvider::GetScreenSize() const {
+ return gfx::Screen::GetScreen()->GetPrimaryDisplay().GetSizeInPixel();
+}
+
+float ScreenInfoMetricsProvider::GetScreenDeviceScaleFactor() const {
+ return gfx::Screen::GetScreen()->GetPrimaryDisplay().device_scale_factor();
+}
+
+int ScreenInfoMetricsProvider::GetScreenCount() const {
+ return gfx::Screen::GetScreen()->GetNumDisplays();
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/ui/screen_info_metrics_provider.h b/chromium/components/metrics/ui/screen_info_metrics_provider.h
new file mode 100644
index 00000000000..51ef2b77ab1
--- /dev/null
+++ b/chromium/components/metrics/ui/screen_info_metrics_provider.h
@@ -0,0 +1,42 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_UI_SCREEN_INFO_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_UI_SCREEN_INFO_METRICS_PROVIDER_H_
+
+#include "base/macros.h"
+#include "components/metrics/metrics_provider.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace metrics {
+
+// ScreenInfoMetricsProvider provides metrics related to screen info.
+class ScreenInfoMetricsProvider : public MetricsProvider {
+ public:
+ ScreenInfoMetricsProvider();
+ ~ScreenInfoMetricsProvider() override;
+
+ // MetricsProvider:
+ void ProvideSystemProfileMetrics(
+ SystemProfileProto* system_profile_proto) override;
+
+ protected:
+ // Exposed for the sake of mocking in test code.
+
+ // Returns the screen size for the primary monitor.
+ virtual gfx::Size GetScreenSize() const;
+
+ // Returns the device scale factor for the primary monitor.
+ virtual float GetScreenDeviceScaleFactor() const;
+
+ // Returns the number of monitors the user is using.
+ virtual int GetScreenCount() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScreenInfoMetricsProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_UI_SCREEN_INFO_METRICS_PROVIDER_H_
diff --git a/chromium/components/metrics/ui/screen_info_metrics_provider_unittest.cc b/chromium/components/metrics/ui/screen_info_metrics_provider_unittest.cc
new file mode 100644
index 00000000000..9e0046bd60e
--- /dev/null
+++ b/chromium/components/metrics/ui/screen_info_metrics_provider_unittest.cc
@@ -0,0 +1,66 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/ui/screen_info_metrics_provider.h"
+
+#include "base/macros.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace metrics {
+
+namespace {
+
+const int kScreenWidth = 1024;
+const int kScreenHeight = 768;
+const int kScreenCount = 3;
+const float kScreenScaleFactor = 2;
+
+class TestScreenInfoMetricsProvider : public ScreenInfoMetricsProvider {
+ public:
+ TestScreenInfoMetricsProvider() {}
+ ~TestScreenInfoMetricsProvider() override {}
+
+ private:
+ gfx::Size GetScreenSize() const override {
+ return gfx::Size(kScreenWidth, kScreenHeight);
+ }
+
+ float GetScreenDeviceScaleFactor() const override {
+ return kScreenScaleFactor;
+ }
+
+ int GetScreenCount() const override { return kScreenCount; }
+
+ DISALLOW_COPY_AND_ASSIGN(TestScreenInfoMetricsProvider);
+};
+
+} // namespace
+
+class ScreenInfoMetricsProviderTest : public testing::Test {
+ public:
+ ScreenInfoMetricsProviderTest() {}
+ ~ScreenInfoMetricsProviderTest() override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScreenInfoMetricsProviderTest);
+};
+
+TEST_F(ScreenInfoMetricsProviderTest, ProvideSystemProfileMetrics) {
+ TestScreenInfoMetricsProvider provider;
+ ChromeUserMetricsExtension uma_proto;
+
+ provider.ProvideSystemProfileMetrics(uma_proto.mutable_system_profile());
+
+ // Check that the system profile has the correct values set.
+ const SystemProfileProto::Hardware& hardware =
+ uma_proto.system_profile().hardware();
+ EXPECT_EQ(kScreenWidth, hardware.primary_screen_width());
+ EXPECT_EQ(kScreenHeight, hardware.primary_screen_height());
+ EXPECT_EQ(kScreenScaleFactor, hardware.primary_screen_scale_factor());
+ EXPECT_EQ(kScreenCount, hardware.screen_count());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/url_constants.cc b/chromium/components/metrics/url_constants.cc
new file mode 100644
index 00000000000..55d9e13d0e2
--- /dev/null
+++ b/chromium/components/metrics/url_constants.cc
@@ -0,0 +1,13 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/url_constants.h"
+
+namespace metrics {
+
+const char kDefaultMetricsServerUrl[] = "https://clients4.google.com/uma/v2";
+const char kDefaultMetricsMimeType[] = "application/vnd.chrome.uma";
+
+} // namespace metrics
+
diff --git a/chromium/components/metrics/url_constants.h b/chromium/components/metrics/url_constants.h
new file mode 100644
index 00000000000..b52ddef2ed9
--- /dev/null
+++ b/chromium/components/metrics/url_constants.h
@@ -0,0 +1,18 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_URL_CONSTANTS_H_
+#define COMPONENTS_METRICS_URL_CONSTANTS_H_
+
+namespace metrics {
+
+// The default metrics server's URL.
+extern const char kDefaultMetricsServerUrl[];
+
+// The default MIME type for the uploaded metrics data.
+extern const char kDefaultMetricsMimeType[];
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_URL_CONSTANTS_H_
diff --git a/chromium/components/variations/BUILD.gn b/chromium/components/variations/BUILD.gn
new file mode 100644
index 00000000000..32c71f2210f
--- /dev/null
+++ b/chromium/components/variations/BUILD.gn
@@ -0,0 +1,126 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+if (is_android) {
+ import("//build/config/android/rules.gni")
+}
+
+source_set("variations") {
+ sources = [
+ "active_field_trials.cc",
+ "active_field_trials.h",
+ "android/component_jni_registrar.cc",
+ "android/component_jni_registrar.h",
+ "android/variations_associated_data_android.cc",
+ "android/variations_associated_data_android.h",
+ "android/variations_seed_bridge.cc",
+ "android/variations_seed_bridge.h",
+ "caching_permuted_entropy_provider.cc",
+ "caching_permuted_entropy_provider.h",
+ "entropy_provider.cc",
+ "entropy_provider.h",
+ "experiment_labels.cc",
+ "experiment_labels.h",
+ "metrics_util.cc",
+ "metrics_util.h",
+ "pref_names.cc",
+ "pref_names.h",
+ "processed_study.cc",
+ "processed_study.h",
+ "proto/client_variations.proto",
+ "proto/permuted_entropy_cache.proto",
+ "proto/study.proto",
+ "proto/variations_seed.proto",
+ "study_filtering.cc",
+ "study_filtering.h",
+ "synthetic_trials.cc",
+ "synthetic_trials.h",
+ "variations_associated_data.cc",
+ "variations_associated_data.h",
+ "variations_experiment_util.cc",
+ "variations_experiment_util.h",
+ "variations_http_header_provider.cc",
+ "variations_http_header_provider.h",
+ "variations_request_scheduler.cc",
+ "variations_request_scheduler.h",
+ "variations_seed_processor.cc",
+ "variations_seed_processor.h",
+ "variations_seed_simulator.cc",
+ "variations_seed_simulator.h",
+ "variations_seed_store.cc",
+ "variations_seed_store.h",
+ "variations_switches.cc",
+ "variations_switches.h",
+ "variations_url_constants.cc",
+ "variations_url_constants.h",
+ "variations_util.cc",
+ "variations_util.h",
+ ]
+
+ if (is_android || is_ios) {
+ sources += [
+ "variations_request_scheduler_mobile.cc",
+ "variations_request_scheduler_mobile.h",
+ ]
+ }
+
+ deps = [
+ "proto",
+ "//base",
+ "//components/crash/core/common",
+ "//components/prefs",
+ "//crypto",
+ "//third_party/mt19937ar",
+ "//third_party/protobuf:protobuf_lite",
+ "//third_party/zlib:compression_utils",
+ ]
+
+ if (is_android) {
+ deps += [ ":jni" ]
+ }
+}
+
+if (is_android) {
+ # GYP: //components/variations.gypi:variations_jni_headers
+ generate_jni("jni") {
+ sources = [
+ "android/java/src/org/chromium/components/variations/VariationsAssociatedData.java",
+ "android/java/src/org/chromium/components/variations/firstrun/VariationsSeedBridge.java",
+ ]
+ jni_package = "variations"
+ }
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "active_field_trials_unittest.cc",
+ "caching_permuted_entropy_provider_unittest.cc",
+ "entropy_provider_unittest.cc",
+ "experiment_labels_unittest.cc",
+ "metrics_util_unittest.cc",
+ "net/variations_http_headers_unittest.cc",
+ "study_filtering_unittest.cc",
+ "variations_associated_data_unittest.cc",
+ "variations_http_header_provider_unittest.cc",
+ "variations_request_scheduler_unittest.cc",
+ "variations_seed_processor_unittest.cc",
+ "variations_seed_simulator_unittest.cc",
+ "variations_seed_store_unittest.cc",
+ ]
+
+ if (is_android || is_ios) {
+ sources += [ "variations_request_scheduler_mobile_unittest.cc" ]
+ }
+
+ deps = [
+ ":variations",
+ "net",
+ "proto",
+ "//base/test:test_support",
+ "//components/prefs:test_support",
+ "//testing/gtest",
+ "//third_party/zlib:compression_utils",
+ ]
+}
diff --git a/chromium/components/variations/DEPS b/chromium/components/variations/DEPS
new file mode 100644
index 00000000000..720e034fc7f
--- /dev/null
+++ b/chromium/components/variations/DEPS
@@ -0,0 +1,14 @@
+# This component is shared with the Chrome OS build, so it's important to limit
+# dependencies to a minimal set.
+include_rules = [
+ "-components",
+ "+components/compression",
+ "+components/crash/core/common",
+ "+components/prefs",
+ "+components/variations",
+ "+crypto",
+ "-net",
+ "+third_party/mt19937ar",
+ "+third_party/protobuf",
+ "+third_party/zlib/google",
+]
diff --git a/chromium/components/variations/OWNERS b/chromium/components/variations/OWNERS
new file mode 100644
index 00000000000..aadd23c5252
--- /dev/null
+++ b/chromium/components/variations/OWNERS
@@ -0,0 +1,3 @@
+asvitkine@chromium.org
+jwd@chromium.org
+stevet@chromium.org
diff --git a/chromium/components/variations/active_field_trials.cc b/chromium/components/variations/active_field_trials.cc
new file mode 100644
index 00000000000..07c8b6b599e
--- /dev/null
+++ b/chromium/components/variations/active_field_trials.cc
@@ -0,0 +1,74 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/active_field_trials.h"
+
+#include <stddef.h>
+
+#include <vector>
+
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/variations/metrics_util.h"
+
+namespace variations {
+
+namespace {
+
+// Populates |name_group_ids| based on |active_groups|.
+void GetFieldTrialActiveGroupIdsForActiveGroups(
+ const base::FieldTrial::ActiveGroups& active_groups,
+ std::vector<ActiveGroupId>* name_group_ids) {
+ DCHECK(name_group_ids->empty());
+ for (base::FieldTrial::ActiveGroups::const_iterator it =
+ active_groups.begin(); it != active_groups.end(); ++it) {
+ name_group_ids->push_back(MakeActiveGroupId(it->trial_name,
+ it->group_name));
+ }
+}
+
+} // namespace
+
+ActiveGroupId MakeActiveGroupId(const std::string& trial_name,
+ const std::string& group_name) {
+ ActiveGroupId id;
+ id.name = metrics::HashName(trial_name);
+ id.group = metrics::HashName(group_name);
+ return id;
+}
+
+void GetFieldTrialActiveGroupIds(
+ std::vector<ActiveGroupId>* name_group_ids) {
+ DCHECK(name_group_ids->empty());
+ // A note on thread safety: Since GetActiveFieldTrialGroups() is thread
+ // safe, and we operate on a separate list of that data, this function is
+ // technically thread safe as well, with respect to the FieldTrialList data.
+ base::FieldTrial::ActiveGroups active_groups;
+ base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
+ GetFieldTrialActiveGroupIdsForActiveGroups(active_groups,
+ name_group_ids);
+}
+
+void GetFieldTrialActiveGroupIdsAsStrings(std::vector<std::string>* output) {
+ DCHECK(output->empty());
+ std::vector<ActiveGroupId> name_group_ids;
+ GetFieldTrialActiveGroupIds(&name_group_ids);
+ for (size_t i = 0; i < name_group_ids.size(); ++i) {
+ output->push_back(base::StringPrintf(
+ "%x-%x", name_group_ids[i].name, name_group_ids[i].group));
+ }
+}
+
+namespace testing {
+
+void TestGetFieldTrialActiveGroupIds(
+ const base::FieldTrial::ActiveGroups& active_groups,
+ std::vector<ActiveGroupId>* name_group_ids) {
+ GetFieldTrialActiveGroupIdsForActiveGroups(active_groups,
+ name_group_ids);
+}
+
+} // namespace testing
+
+} // namespace variations
diff --git a/chromium/components/variations/active_field_trials.h b/chromium/components/variations/active_field_trials.h
new file mode 100644
index 00000000000..b7425510a10
--- /dev/null
+++ b/chromium/components/variations/active_field_trials.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_ACTIVE_FIELD_TRIALS_H_
+#define COMPONENTS_VARIATIONS_ACTIVE_FIELD_TRIALS_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/metrics/field_trial.h"
+
+namespace variations {
+
+// The Unique ID of a trial and its active group, where the name and group
+// identifiers are hashes of the trial and group name strings.
+struct ActiveGroupId {
+ uint32_t name;
+ uint32_t group;
+};
+
+// Returns an ActiveGroupId struct for the given trial and group names.
+ActiveGroupId MakeActiveGroupId(const std::string& trial_name,
+ const std::string& group_name);
+
+// We need to supply a Compare class for templates since ActiveGroupId is a
+// user-defined type.
+struct ActiveGroupIdCompare {
+ bool operator() (const ActiveGroupId& lhs, const ActiveGroupId& rhs) const {
+ // The group and name fields are just SHA-1 Hashes, so we just need to treat
+ // them as IDs and do a less-than comparison. We test group first, since
+ // name is more likely to collide.
+ if (lhs.group != rhs.group)
+ return lhs.group < rhs.group;
+ return lhs.name < rhs.name;
+ }
+};
+
+// Fills the supplied vector |name_group_ids| (which must be empty when called)
+// with unique ActiveGroupIds for each Field Trial that has a chosen group.
+// Field Trials for which a group has not been chosen yet are NOT returned in
+// this list.
+void GetFieldTrialActiveGroupIds(std::vector<ActiveGroupId>* name_group_ids);
+
+// Fills the supplied vector |output| (which must be empty when called) with
+// unique string representations of ActiveGroupIds for each Field Trial that
+// has a chosen group. The strings are formatted as "<TrialName>-<GroupName>",
+// with the names as hex strings. Field Trials for which a group has not been
+// chosen yet are NOT returned in this list.
+void GetFieldTrialActiveGroupIdsAsStrings(std::vector<std::string>* output);
+
+// Expose some functions for testing. These functions just wrap functionality
+// that is implemented above.
+namespace testing {
+
+void TestGetFieldTrialActiveGroupIds(
+ const base::FieldTrial::ActiveGroups& active_groups,
+ std::vector<ActiveGroupId>* name_group_ids);
+
+} // namespace testing
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_ACTIVE_FIELD_TRIALS_H_
diff --git a/chromium/components/variations/active_field_trials_unittest.cc b/chromium/components/variations/active_field_trials_unittest.cc
new file mode 100644
index 00000000000..548b09ce76f
--- /dev/null
+++ b/chromium/components/variations/active_field_trials_unittest.cc
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/active_field_trials.h"
+
+#include <stddef.h>
+
+#include "components/variations/metrics_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace variations {
+
+TEST(ActiveFieldTrialsTest, GetFieldTrialActiveGroups) {
+ typedef std::set<ActiveGroupId, ActiveGroupIdCompare> ActiveGroupIdSet;
+ std::string trial_one("trial one");
+ std::string group_one("group one");
+ std::string trial_two("trial two");
+ std::string group_two("group two");
+
+ base::FieldTrial::ActiveGroups active_groups;
+ base::FieldTrial::ActiveGroup active_group;
+ active_group.trial_name = trial_one;
+ active_group.group_name = group_one;
+ active_groups.push_back(active_group);
+
+ active_group.trial_name = trial_two;
+ active_group.group_name = group_two;
+ active_groups.push_back(active_group);
+
+ // Create our expected groups of IDs.
+ ActiveGroupIdSet expected_groups;
+ ActiveGroupId name_group_id;
+ name_group_id.name = metrics::HashName(trial_one);
+ name_group_id.group = metrics::HashName(group_one);
+ expected_groups.insert(name_group_id);
+ name_group_id.name = metrics::HashName(trial_two);
+ name_group_id.group = metrics::HashName(group_two);
+ expected_groups.insert(name_group_id);
+
+ std::vector<ActiveGroupId> active_group_ids;
+ testing::TestGetFieldTrialActiveGroupIds(active_groups, &active_group_ids);
+ EXPECT_EQ(2U, active_group_ids.size());
+ for (size_t i = 0; i < active_group_ids.size(); ++i) {
+ ActiveGroupIdSet::iterator expected_group =
+ expected_groups.find(active_group_ids[i]);
+ EXPECT_FALSE(expected_group == expected_groups.end());
+ expected_groups.erase(expected_group);
+ }
+ EXPECT_EQ(0U, expected_groups.size());
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/android/BUILD.gn b/chromium/components/variations/android/BUILD.gn
new file mode 100644
index 00000000000..0bcadfddab0
--- /dev/null
+++ b/chromium/components/variations/android/BUILD.gn
@@ -0,0 +1,18 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/android/rules.gni")
+
+# GYP: //components/variations.gypi:variations_java
+android_library("variations_java") {
+ deps = [
+ "//base:base_java",
+ ]
+ java_files = [
+ "java/src/org/chromium/components/variations/VariationsAssociatedData.java",
+ "java/src/org/chromium/components/variations/firstrun/VariationsSeedBridge.java",
+ "java/src/org/chromium/components/variations/firstrun/VariationsSeedService.java",
+ "java/src/org/chromium/components/variations/firstrun/VariationsSeedServiceLauncher.java",
+ ]
+}
diff --git a/chromium/components/variations/android/DEPS b/chromium/components/variations/android/DEPS
new file mode 100644
index 00000000000..c80012b5621
--- /dev/null
+++ b/chromium/components/variations/android/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+jni",
+]
diff --git a/chromium/components/variations/android/variations_associated_data_android.cc b/chromium/components/variations/android/variations_associated_data_android.cc
new file mode 100644
index 00000000000..42e5ae18875
--- /dev/null
+++ b/chromium/components/variations/android/variations_associated_data_android.cc
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/android/variations_associated_data_android.h"
+
+#include <string>
+
+#include "base/android/jni_string.h"
+#include "components/variations/variations_associated_data.h"
+#include "components/variations/variations_http_header_provider.h"
+#include "jni/VariationsAssociatedData_jni.h"
+
+using base::android::ConvertJavaStringToUTF8;
+using base::android::ConvertUTF8ToJavaString;
+
+namespace variations {
+
+namespace android {
+
+ScopedJavaLocalRef<jstring> GetVariationParamValue(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz,
+ const JavaParamRef<jstring>& jtrial_name,
+ const JavaParamRef<jstring>& jparam_name) {
+ std::string trial_name(ConvertJavaStringToUTF8(env, jtrial_name));
+ std::string param_name(ConvertJavaStringToUTF8(env, jparam_name));
+ std::string param_value =
+ variations::GetVariationParamValue(trial_name, param_name);
+ return ConvertUTF8ToJavaString(env, param_value);
+}
+
+ScopedJavaLocalRef<jstring> GetFeedbackVariations(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& clazz) {
+ const std::string values =
+ VariationsHttpHeaderProvider::GetInstance()->GetVariationsString();
+ return ConvertUTF8ToJavaString(env, values);
+}
+
+bool RegisterVariationsAssociatedData(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace android
+
+} // namespace variations
diff --git a/chromium/components/variations/android/variations_associated_data_android.h b/chromium/components/variations/android/variations_associated_data_android.h
new file mode 100644
index 00000000000..88122d339ad
--- /dev/null
+++ b/chromium/components/variations/android/variations_associated_data_android.h
@@ -0,0 +1,23 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_ANDROID_VARIATIONS_ASSOCIATED_DATA_ANDROID_H_
+#define COMPONENTS_VARIATIONS_ANDROID_VARIATIONS_ASSOCIATED_DATA_ANDROID_H_
+
+#include <jni.h>
+
+#include <string>
+
+namespace variations {
+
+namespace android {
+
+// Register JNI methods for VariationsAssociatedData.
+bool RegisterVariationsAssociatedData(JNIEnv* env);
+
+} // namespace android
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_ANDROID_VARIATIONS_ASSOCIATED_DATA_ANDROID_H_
diff --git a/chromium/components/variations/android/variations_seed_bridge.cc b/chromium/components/variations/android/variations_seed_bridge.cc
new file mode 100644
index 00000000000..5c2788bb7cd
--- /dev/null
+++ b/chromium/components/variations/android/variations_seed_bridge.cc
@@ -0,0 +1,104 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/android/variations_seed_bridge.h"
+
+#include <jni.h>
+#include <stdint.h>
+#include <vector>
+
+#include "base/android/context_utils.h"
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/android/jni_weak_ref.h"
+#include "jni/VariationsSeedBridge_jni.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ConvertJavaStringToUTF8;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::GetApplicationContext;
+using base::android::ScopedJavaLocalRef;
+
+namespace {
+
+std::string JavaByteArrayToString(JNIEnv* env, jbyteArray byte_array) {
+ if (!byte_array)
+ return std::string();
+ std::vector<uint8_t> array_data;
+ base::android::JavaByteArrayToByteVector(env, byte_array, &array_data);
+ return std::string(array_data.begin(), array_data.end());
+}
+
+ScopedJavaLocalRef<jbyteArray> StringToJavaByteArray(
+ JNIEnv* env,
+ const std::string& str_data) {
+ std::vector<uint8_t> array_data(str_data.begin(), str_data.end());
+ return base::android::ToJavaByteArray(env, array_data);
+}
+
+} // namespace
+
+namespace variations {
+namespace android {
+
+bool RegisterVariationsSeedBridge(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+void GetVariationsFirstRunSeed(std::string* seed_data,
+ std::string* seed_signature,
+ std::string* seed_country,
+ std::string* response_date,
+ bool* is_gzip_compressed) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jbyteArray> j_seed_data =
+ Java_VariationsSeedBridge_getVariationsFirstRunSeedData(
+ env, GetApplicationContext());
+ ScopedJavaLocalRef<jstring> j_seed_signature =
+ Java_VariationsSeedBridge_getVariationsFirstRunSeedSignature(
+ env, GetApplicationContext());
+ ScopedJavaLocalRef<jstring> j_seed_country =
+ Java_VariationsSeedBridge_getVariationsFirstRunSeedCountry(
+ env, GetApplicationContext());
+ ScopedJavaLocalRef<jstring> j_response_date =
+ Java_VariationsSeedBridge_getVariationsFirstRunSeedDate(
+ env, GetApplicationContext());
+ jboolean j_is_gzip_compressed =
+ Java_VariationsSeedBridge_getVariationsFirstRunSeedIsGzipCompressed(
+ env, GetApplicationContext());
+ *seed_data = JavaByteArrayToString(env, j_seed_data.obj());
+ *seed_signature = ConvertJavaStringToUTF8(j_seed_signature);
+ *seed_country = ConvertJavaStringToUTF8(j_seed_country);
+ *response_date = ConvertJavaStringToUTF8(j_response_date);
+ *is_gzip_compressed = static_cast<bool>(j_is_gzip_compressed);
+}
+
+void ClearJavaFirstRunPrefs() {
+ JNIEnv* env = AttachCurrentThread();
+ Java_VariationsSeedBridge_clearFirstRunPrefs(env, GetApplicationContext());
+}
+
+void MarkVariationsSeedAsStored() {
+ JNIEnv* env = AttachCurrentThread();
+ Java_VariationsSeedBridge_markVariationsSeedAsStored(env,
+ GetApplicationContext());
+}
+
+void SetJavaFirstRunPrefsForTesting(const std::string& seed_data,
+ const std::string& seed_signature,
+ const std::string& seed_country,
+ const std::string& response_date,
+ bool is_gzip_compressed) {
+ JNIEnv* env = AttachCurrentThread();
+ Java_VariationsSeedBridge_setVariationsFirstRunSeed(
+ env, GetApplicationContext(), StringToJavaByteArray(env, seed_data).obj(),
+ ConvertUTF8ToJavaString(env, seed_signature).obj(),
+ ConvertUTF8ToJavaString(env, seed_country).obj(),
+ ConvertUTF8ToJavaString(env, response_date).obj(),
+ static_cast<jboolean>(is_gzip_compressed));
+}
+
+} // namespace android
+} // namespace variations
diff --git a/chromium/components/variations/android/variations_seed_bridge.h b/chromium/components/variations/android/variations_seed_bridge.h
new file mode 100644
index 00000000000..85e633f7736
--- /dev/null
+++ b/chromium/components/variations/android/variations_seed_bridge.h
@@ -0,0 +1,43 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_ANDROID_VARIATIONS_SEED_BRIDGE_H_
+#define COMPONENTS_VARIATIONS_ANDROID_VARIATIONS_SEED_BRIDGE_H_
+
+#include <jni.h>
+#include <string>
+
+namespace variations {
+namespace android {
+
+bool RegisterVariationsSeedBridge(JNIEnv* env);
+
+// Return the first run seed data pulled from the Java side of application.
+void GetVariationsFirstRunSeed(std::string* seed_data,
+ std::string* seed_signature,
+ std::string* seed_country,
+ std::string* response_date,
+ bool* is_gzip_compressed);
+
+// Clears first run seed preferences stored on the Java side of Chrome for
+// Android.
+void ClearJavaFirstRunPrefs();
+
+// Marks variations seed as stored to avoid repeated fetches of the seed at
+// the Java side.
+void MarkVariationsSeedAsStored();
+
+// Sets test data on the Java side. The data is pulled during the unit tests to
+// C++ side and is being checked for consistency.
+// This method is used for unit testing purposes only.
+void SetJavaFirstRunPrefsForTesting(const std::string& seed_data,
+ const std::string& seed_signature,
+ const std::string& seed_country,
+ const std::string& response_date,
+ bool is_gzip_compressed);
+
+} // namespace android
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_ANDROID_FIRSTRUN_VARIATIONS_SEED_BRIDGE_H_
diff --git a/chromium/components/variations/caching_permuted_entropy_provider.cc b/chromium/components/variations/caching_permuted_entropy_provider.cc
new file mode 100644
index 00000000000..d1788c40a6c
--- /dev/null
+++ b/chromium/components/variations/caching_permuted_entropy_provider.cc
@@ -0,0 +1,108 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/caching_permuted_entropy_provider.h"
+
+#include <string>
+
+#include "base/base64.h"
+#include "base/logging.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/pref_names.h"
+
+namespace metrics {
+
+CachingPermutedEntropyProvider::CachingPermutedEntropyProvider(
+ PrefService* local_state,
+ uint16_t low_entropy_source,
+ size_t low_entropy_source_max)
+ : PermutedEntropyProvider(low_entropy_source, low_entropy_source_max),
+ local_state_(local_state) {
+ ReadFromLocalState();
+}
+
+CachingPermutedEntropyProvider::~CachingPermutedEntropyProvider() {
+}
+
+// static
+void CachingPermutedEntropyProvider::RegisterPrefs(
+ PrefRegistrySimple* registry) {
+ registry->RegisterStringPref(
+ variations::prefs::kVariationsPermutedEntropyCache, std::string());
+}
+
+// static
+void CachingPermutedEntropyProvider::ClearCache(PrefService* local_state) {
+ local_state->ClearPref(variations::prefs::kVariationsPermutedEntropyCache);
+}
+
+uint16_t CachingPermutedEntropyProvider::GetPermutedValue(
+ uint32_t randomization_seed) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ uint16_t value = 0;
+ if (!FindValue(randomization_seed, &value)) {
+ value = PermutedEntropyProvider::GetPermutedValue(randomization_seed);
+ AddToCache(randomization_seed, value);
+ }
+ return value;
+}
+
+void CachingPermutedEntropyProvider::ReadFromLocalState() const {
+ const std::string base64_cache_data = local_state_->GetString(
+ variations::prefs::kVariationsPermutedEntropyCache);
+ std::string cache_data;
+ if (!base::Base64Decode(base64_cache_data, &cache_data) ||
+ !cache_.ParseFromString(cache_data)) {
+ local_state_->ClearPref(variations::prefs::kVariationsPermutedEntropyCache);
+ NOTREACHED();
+ }
+}
+
+void CachingPermutedEntropyProvider::UpdateLocalState() const {
+ std::string serialized;
+ cache_.SerializeToString(&serialized);
+
+ std::string base64_encoded;
+ base::Base64Encode(serialized, &base64_encoded);
+ local_state_->SetString(variations::prefs::kVariationsPermutedEntropyCache,
+ base64_encoded);
+}
+
+void CachingPermutedEntropyProvider::AddToCache(uint32_t randomization_seed,
+ uint16_t value) const {
+ PermutedEntropyCache::Entry* entry;
+ const int kMaxSize = 25;
+ if (cache_.entry_size() >= kMaxSize) {
+ // If the cache is full, evict the first entry, swapping later entries in
+ // to take its place. This effectively creates a FIFO cache, which is good
+ // enough here because the expectation is that there shouldn't be more than
+ // |kMaxSize| field trials at any given time, so eviction should happen very
+ // rarely, only as new trials are introduced, evicting old expired trials.
+ for (int i = 1; i < kMaxSize; ++i)
+ cache_.mutable_entry()->SwapElements(i - 1, i);
+ entry = cache_.mutable_entry(kMaxSize - 1);
+ } else {
+ entry = cache_.add_entry();
+ }
+
+ entry->set_randomization_seed(randomization_seed);
+ entry->set_value(value);
+
+ UpdateLocalState();
+}
+
+bool CachingPermutedEntropyProvider::FindValue(uint32_t randomization_seed,
+ uint16_t* value) const {
+ for (int i = 0; i < cache_.entry_size(); ++i) {
+ if (cache_.entry(i).randomization_seed() == randomization_seed) {
+ *value = cache_.entry(i).value();
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace metrics
diff --git a/chromium/components/variations/caching_permuted_entropy_provider.h b/chromium/components/variations/caching_permuted_entropy_provider.h
new file mode 100644
index 00000000000..ce76449606d
--- /dev/null
+++ b/chromium/components/variations/caching_permuted_entropy_provider.h
@@ -0,0 +1,68 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_CACHING_PERMUTED_ENTROPY_PROVIDER_H_
+#define COMPONENTS_VARIATIONS_CACHING_PERMUTED_ENTROPY_PROVIDER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/threading/thread_checker.h"
+#include "components/variations/entropy_provider.h"
+#include "components/variations/proto/permuted_entropy_cache.pb.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace metrics {
+
+// CachingPermutedEntropyProvider is an entropy provider that uses the same
+// algorithm as the PermutedEntropyProvider, but caches the results in Local
+// State between runs.
+class CachingPermutedEntropyProvider : public PermutedEntropyProvider {
+ public:
+ // Creates a CachingPermutedEntropyProvider using the given |local_state|
+ // prefs service with the specified |low_entropy_source|, which should have a
+ // value in the range of [0, low_entropy_source_max).
+ CachingPermutedEntropyProvider(PrefService* local_state,
+ uint16_t low_entropy_source,
+ size_t low_entropy_source_max);
+ ~CachingPermutedEntropyProvider() override;
+
+ // Registers pref keys used by this class in the Local State pref registry.
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ // Clears the cache in local state. Should be called when the low entropy
+ // source value gets reset.
+ static void ClearCache(PrefService* local_state);
+
+ private:
+ // PermutedEntropyProvider overrides:
+ uint16_t GetPermutedValue(uint32_t randomization_seed) const override;
+
+ // Reads the cache from local state.
+ void ReadFromLocalState() const;
+
+ // Updates local state with the state of the cache.
+ void UpdateLocalState() const;
+
+ // Adds |randomization_seed| -> |value| to the cache.
+ void AddToCache(uint32_t randomization_seed, uint16_t value) const;
+
+ // Finds the value corresponding to |randomization_seed|, setting |value| and
+ // returning true if found.
+ bool FindValue(uint32_t randomization_seed, uint16_t* value) const;
+
+ base::ThreadChecker thread_checker_;
+ PrefService* local_state_;
+ mutable PermutedEntropyCache cache_;
+
+ DISALLOW_COPY_AND_ASSIGN(CachingPermutedEntropyProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_VARIATIONS_CACHING_PERMUTED_ENTROPY_PROVIDER_H_
diff --git a/chromium/components/variations/caching_permuted_entropy_provider_unittest.cc b/chromium/components/variations/caching_permuted_entropy_provider_unittest.cc
new file mode 100644
index 00000000000..c7139d8e1e2
--- /dev/null
+++ b/chromium/components/variations/caching_permuted_entropy_provider_unittest.cc
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/caching_permuted_entropy_provider.h"
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+// Size of the low entropy source to use for the permuted entropy provider
+// in tests.
+const size_t kMaxLowEntropySize = 8000;
+
+// Field trial names used in unit tests.
+const char* const kTestTrialNames[] = {"TestTrial", "AnotherTestTrial",
+ "NewTabButton"};
+
+TEST(CachingPermutedEntropyProviderTest, HasConsistentResults) {
+ TestingPrefServiceSimple prefs;
+ CachingPermutedEntropyProvider::RegisterPrefs(prefs.registry());
+ const int kEntropyValue = 1234;
+
+ // Check that the caching provider returns the same results as the non caching
+ // one. Loop over the trial names twice, to test that caching returns the
+ // expected results.
+ PermutedEntropyProvider provider(kEntropyValue, kMaxLowEntropySize);
+ for (size_t i = 0; i < 2 * arraysize(kTestTrialNames); ++i) {
+ CachingPermutedEntropyProvider cached_provider(
+ &prefs, kEntropyValue, kMaxLowEntropySize);
+ const std::string trial_name =
+ kTestTrialNames[i % arraysize(kTestTrialNames)];
+ EXPECT_DOUBLE_EQ(provider.GetEntropyForTrial(trial_name, 0),
+ cached_provider.GetEntropyForTrial(trial_name, 0));
+ }
+
+ // Now, do the same test re-using the same caching provider.
+ CachingPermutedEntropyProvider cached_provider(
+ &prefs, kEntropyValue, kMaxLowEntropySize);
+ for (size_t i = 0; i < 2 * arraysize(kTestTrialNames); ++i) {
+ const std::string trial_name =
+ kTestTrialNames[i % arraysize(kTestTrialNames)];
+ EXPECT_DOUBLE_EQ(provider.GetEntropyForTrial(trial_name, 0),
+ cached_provider.GetEntropyForTrial(trial_name, 0));
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/variations/entropy_provider.cc b/chromium/components/variations/entropy_provider.cc
new file mode 100644
index 00000000000..d07277f7add
--- /dev/null
+++ b/chromium/components/variations/entropy_provider.cc
@@ -0,0 +1,132 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/entropy_provider.h"
+
+#include <algorithm>
+#include <limits>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/sha1.h"
+#include "base/sys_byteorder.h"
+#include "components/variations/metrics_util.h"
+
+namespace metrics {
+
+namespace internal {
+
+SeededRandGenerator::SeededRandGenerator(uint32_t seed) {
+ mersenne_twister_.init_genrand(seed);
+}
+
+SeededRandGenerator::~SeededRandGenerator() {
+}
+
+uint32_t SeededRandGenerator::operator()(uint32_t range) {
+ // Based on base::RandGenerator().
+ DCHECK_GT(range, 0u);
+
+ // We must discard random results above this number, as they would
+ // make the random generator non-uniform (consider e.g. if
+ // MAX_UINT64 was 7 and |range| was 5, then a result of 1 would be twice
+ // as likely as a result of 3 or 4).
+ uint32_t max_acceptable_value =
+ (std::numeric_limits<uint32_t>::max() / range) * range - 1;
+
+ uint32_t value;
+ do {
+ value = mersenne_twister_.genrand_int32();
+ } while (value > max_acceptable_value);
+
+ return value % range;
+}
+
+void PermuteMappingUsingRandomizationSeed(uint32_t randomization_seed,
+ std::vector<uint16_t>* mapping) {
+ for (size_t i = 0; i < mapping->size(); ++i)
+ (*mapping)[i] = static_cast<uint16_t>(i);
+
+ SeededRandGenerator generator(randomization_seed);
+
+ // Do a deterministic random shuffle of the mapping using |generator|.
+ //
+ // Note: This logic is identical to the following call with libstdc++ and VS:
+ //
+ // std::random_shuffle(mapping->begin(), mapping->end(), generator);
+ //
+ // However, this is not guaranteed by the spec and some implementations (e.g.
+ // libc++) use a different algorithm. To ensure results are consistent
+ // regardless of the compiler toolchain used, use our own version.
+ for (size_t i = 1; i < mapping->size(); ++i) {
+ // Pick an element in mapping[:i+1] with which to exchange mapping[i].
+ size_t j = generator(i + 1);
+ std::swap((*mapping)[i], (*mapping)[j]);
+ }
+}
+
+} // namespace internal
+
+SHA1EntropyProvider::SHA1EntropyProvider(const std::string& entropy_source)
+ : entropy_source_(entropy_source) {
+}
+
+SHA1EntropyProvider::~SHA1EntropyProvider() {
+}
+
+double SHA1EntropyProvider::GetEntropyForTrial(
+ const std::string& trial_name,
+ uint32_t randomization_seed) const {
+ // Given enough input entropy, SHA-1 will produce a uniformly random spread
+ // in its output space. In this case, the input entropy that is used is the
+ // combination of the original |entropy_source_| and the |trial_name|.
+ //
+ // Note: If |entropy_source_| has very low entropy, such as 13 bits or less,
+ // it has been observed that this method does not result in a uniform
+ // distribution given the same |trial_name|. When using such a low entropy
+ // source, PermutedEntropyProvider should be used instead.
+ std::string input(entropy_source_ + trial_name);
+ unsigned char sha1_hash[base::kSHA1Length];
+ base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(input.c_str()),
+ input.size(),
+ sha1_hash);
+
+ uint64_t bits;
+ static_assert(sizeof(bits) < sizeof(sha1_hash), "more data required");
+ memcpy(&bits, sha1_hash, sizeof(bits));
+ bits = base::ByteSwapToLE64(bits);
+
+ return base::BitsToOpenEndedUnitInterval(bits);
+}
+
+PermutedEntropyProvider::PermutedEntropyProvider(uint16_t low_entropy_source,
+ size_t low_entropy_source_max)
+ : low_entropy_source_(low_entropy_source),
+ low_entropy_source_max_(low_entropy_source_max) {
+ DCHECK_LT(low_entropy_source, low_entropy_source_max);
+ DCHECK_LE(low_entropy_source_max, std::numeric_limits<uint16_t>::max());
+}
+
+PermutedEntropyProvider::~PermutedEntropyProvider() {
+}
+
+double PermutedEntropyProvider::GetEntropyForTrial(
+ const std::string& trial_name,
+ uint32_t randomization_seed) const {
+ if (randomization_seed == 0)
+ randomization_seed = HashName(trial_name);
+
+ return GetPermutedValue(randomization_seed) /
+ static_cast<double>(low_entropy_source_max_);
+}
+
+uint16_t PermutedEntropyProvider::GetPermutedValue(
+ uint32_t randomization_seed) const {
+ std::vector<uint16_t> mapping(low_entropy_source_max_);
+ internal::PermuteMappingUsingRandomizationSeed(randomization_seed, &mapping);
+ return mapping[low_entropy_source_];
+}
+
+} // namespace metrics
diff --git a/chromium/components/variations/entropy_provider.h b/chromium/components/variations/entropy_provider.h
new file mode 100644
index 00000000000..27e73eb3564
--- /dev/null
+++ b/chromium/components/variations/entropy_provider.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_ENTROPY_PROVIDER_H_
+#define COMPONENTS_VARIATIONS_ENTROPY_PROVIDER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/metrics/field_trial.h"
+#include "third_party/mt19937ar/mt19937ar.h"
+
+namespace metrics {
+
+// Internals of entropy_provider.cc exposed for testing.
+namespace internal {
+
+// A functor that generates random numbers based on a seed, using the Mersenne
+// Twister algorithm. Suitable for use with std::random_shuffle().
+struct SeededRandGenerator {
+ explicit SeededRandGenerator(uint32_t seed);
+ ~SeededRandGenerator();
+
+ // Returns a random number in range [0, range).
+ uint32_t operator()(uint32_t range);
+
+ MersenneTwister mersenne_twister_;
+};
+
+// Fills |mapping| to create a bijection of values in the range of
+// [0, |mapping.size()|), permuted based on |randomization_seed|.
+void PermuteMappingUsingRandomizationSeed(uint32_t randomization_seed,
+ std::vector<uint16_t>* mapping);
+
+} // namespace internal
+
+// SHA1EntropyProvider is an entropy provider suitable for high entropy
+// sources. It works by taking the first 64 bits of the SHA1 hash of the
+// entropy source concatenated with the trial name and using that for the
+// final entropy value.
+class SHA1EntropyProvider : public base::FieldTrial::EntropyProvider {
+ public:
+ // Creates a SHA1EntropyProvider with the given |entropy_source|, which
+ // should contain a large amount of entropy - for example, a textual
+ // representation of a persistent randomly-generated 128-bit value.
+ explicit SHA1EntropyProvider(const std::string& entropy_source);
+ ~SHA1EntropyProvider() override;
+
+ // base::FieldTrial::EntropyProvider implementation:
+ double GetEntropyForTrial(const std::string& trial_name,
+ uint32_t randomization_seed) const override;
+
+ private:
+ std::string entropy_source_;
+
+ DISALLOW_COPY_AND_ASSIGN(SHA1EntropyProvider);
+};
+
+// PermutedEntropyProvider is an entropy provider suitable for low entropy
+// sources (below 16 bits). It uses the field trial name to generate a
+// permutation of a mapping array from an initial entropy value to a new value.
+// Note: This provider's performance is O(2^n), where n is the number of bits
+// in the entropy source.
+class PermutedEntropyProvider : public base::FieldTrial::EntropyProvider {
+ public:
+ // Creates a PermutedEntropyProvider with the given |low_entropy_source|,
+ // which should have a value in the range of [0, low_entropy_source_max).
+ PermutedEntropyProvider(uint16_t low_entropy_source,
+ size_t low_entropy_source_max);
+ ~PermutedEntropyProvider() override;
+
+ // base::FieldTrial::EntropyProvider implementation:
+ double GetEntropyForTrial(const std::string& trial_name,
+ uint32_t randomization_seed) const override;
+
+ protected:
+ // Performs the permutation algorithm and returns the permuted value that
+ // corresponds to |low_entropy_source_|.
+ virtual uint16_t GetPermutedValue(uint32_t randomization_seed) const;
+
+ private:
+ uint16_t low_entropy_source_;
+ size_t low_entropy_source_max_;
+
+ DISALLOW_COPY_AND_ASSIGN(PermutedEntropyProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_VARIATIONS_ENTROPY_PROVIDER_H_
diff --git a/chromium/components/variations/entropy_provider_unittest.cc b/chromium/components/variations/entropy_provider_unittest.cc
new file mode 100644
index 00000000000..64c286222aa
--- /dev/null
+++ b/chromium/components/variations/entropy_provider_unittest.cc
@@ -0,0 +1,371 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/entropy_provider.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <cmath>
+#include <limits>
+#include <numeric>
+
+#include "base/guid.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/variations/metrics_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+// Size of the low entropy source to use for the permuted entropy provider
+// in tests.
+const size_t kMaxLowEntropySize = 8000;
+
+// Field trial names used in unit tests.
+const char* const kTestTrialNames[] = { "TestTrial", "AnotherTestTrial",
+ "NewTabButton" };
+
+// Computes the Chi-Square statistic for |values| assuming they follow a uniform
+// distribution, where each entry has expected value |expected_value|.
+//
+// The Chi-Square statistic is defined as Sum((O-E)^2/E) where O is the observed
+// value and E is the expected value.
+double ComputeChiSquare(const std::vector<int>& values,
+ double expected_value) {
+ double sum = 0;
+ for (size_t i = 0; i < values.size(); ++i) {
+ const double delta = values[i] - expected_value;
+ sum += (delta * delta) / expected_value;
+ }
+ return sum;
+}
+
+// Computes SHA1-based entropy for the given |trial_name| based on
+// |entropy_source|
+double GenerateSHA1Entropy(const std::string& entropy_source,
+ const std::string& trial_name) {
+ SHA1EntropyProvider sha1_provider(entropy_source);
+ return sha1_provider.GetEntropyForTrial(trial_name, 0);
+}
+
+// Generates permutation-based entropy for the given |trial_name| based on
+// |entropy_source| which must be in the range [0, entropy_max).
+double GeneratePermutedEntropy(uint16_t entropy_source,
+ size_t entropy_max,
+ const std::string& trial_name) {
+ PermutedEntropyProvider permuted_provider(entropy_source, entropy_max);
+ return permuted_provider.GetEntropyForTrial(trial_name, 0);
+}
+
+// Helper interface for testing used to generate entropy values for a given
+// field trial. Unlike EntropyProvider, which keeps the low/high entropy source
+// value constant and generates entropy for different trial names, instances
+// of TrialEntropyGenerator keep the trial name constant and generate low/high
+// entropy source values internally to produce each output entropy value.
+class TrialEntropyGenerator {
+ public:
+ virtual ~TrialEntropyGenerator() {}
+ virtual double GenerateEntropyValue() const = 0;
+};
+
+// An TrialEntropyGenerator that uses the SHA1EntropyProvider with the high
+// entropy source (random GUID with 128 bits of entropy + 13 additional bits of
+// entropy corresponding to a low entropy source).
+class SHA1EntropyGenerator : public TrialEntropyGenerator {
+ public:
+ explicit SHA1EntropyGenerator(const std::string& trial_name)
+ : trial_name_(trial_name) {
+ }
+
+ ~SHA1EntropyGenerator() override {}
+
+ double GenerateEntropyValue() const override {
+ // Use a random GUID + 13 additional bits of entropy to match how the
+ // SHA1EntropyProvider is used in metrics_service.cc.
+ const int low_entropy_source =
+ static_cast<uint16_t>(base::RandInt(0, kMaxLowEntropySize - 1));
+ const std::string high_entropy_source =
+ base::GenerateGUID() + base::IntToString(low_entropy_source);
+ return GenerateSHA1Entropy(high_entropy_source, trial_name_);
+ }
+
+ private:
+ std::string trial_name_;
+
+ DISALLOW_COPY_AND_ASSIGN(SHA1EntropyGenerator);
+};
+
+// An TrialEntropyGenerator that uses the permuted entropy provider algorithm,
+// using 13-bit low entropy source values.
+class PermutedEntropyGenerator : public TrialEntropyGenerator {
+ public:
+ explicit PermutedEntropyGenerator(const std::string& trial_name)
+ : mapping_(kMaxLowEntropySize) {
+ // Note: Given a trial name, the computed mapping will be the same.
+ // As a performance optimization, pre-compute the mapping once per trial
+ // name and index into it for each entropy value.
+ const uint32_t randomization_seed = HashName(trial_name);
+ internal::PermuteMappingUsingRandomizationSeed(randomization_seed,
+ &mapping_);
+ }
+
+ ~PermutedEntropyGenerator() override {}
+
+ double GenerateEntropyValue() const override {
+ const int low_entropy_source =
+ static_cast<uint16_t>(base::RandInt(0, kMaxLowEntropySize - 1));
+ return mapping_[low_entropy_source] /
+ static_cast<double>(kMaxLowEntropySize);
+ }
+
+ private:
+ std::vector<uint16_t> mapping_;
+
+ DISALLOW_COPY_AND_ASSIGN(PermutedEntropyGenerator);
+};
+
+// Tests uniformity of a given |entropy_generator| using the Chi-Square Goodness
+// of Fit Test.
+void PerformEntropyUniformityTest(
+ const std::string& trial_name,
+ const TrialEntropyGenerator& entropy_generator) {
+ // Number of buckets in the simulated field trials.
+ const size_t kBucketCount = 20;
+ // Max number of iterations to perform before giving up and failing.
+ const size_t kMaxIterationCount = 100000;
+ // The number of iterations to perform before each time the statistical
+ // significance of the results is checked.
+ const size_t kCheckIterationCount = 10000;
+ // This is the Chi-Square threshold from the Chi-Square statistic table for
+ // 19 degrees of freedom (based on |kBucketCount|) with a 99.9% confidence
+ // level. See: http://www.medcalc.org/manual/chi-square-table.php
+ const double kChiSquareThreshold = 43.82;
+
+ std::vector<int> distribution(kBucketCount);
+
+ for (size_t i = 1; i <= kMaxIterationCount; ++i) {
+ const double entropy_value = entropy_generator.GenerateEntropyValue();
+ const size_t bucket = static_cast<size_t>(kBucketCount * entropy_value);
+ ASSERT_LT(bucket, kBucketCount);
+ distribution[bucket] += 1;
+
+ // After |kCheckIterationCount| iterations, compute the Chi-Square
+ // statistic of the distribution. If the resulting statistic is greater
+ // than |kChiSquareThreshold|, we can conclude with 99.9% confidence
+ // that the observed samples do not follow a uniform distribution.
+ //
+ // However, since 99.9% would still result in a false negative every
+ // 1000 runs of the test, do not treat it as a failure (else the test
+ // will be flaky). Instead, perform additional iterations to determine
+ // if the distribution will converge, up to |kMaxIterationCount|.
+ if ((i % kCheckIterationCount) == 0) {
+ const double expected_value_per_bucket =
+ static_cast<double>(i) / kBucketCount;
+ const double chi_square =
+ ComputeChiSquare(distribution, expected_value_per_bucket);
+ if (chi_square < kChiSquareThreshold)
+ break;
+
+ // If |i == kMaxIterationCount|, the Chi-Square statistic did not
+ // converge after |kMaxIterationCount|.
+ EXPECT_NE(i, kMaxIterationCount) << "Failed for trial " <<
+ trial_name << " with chi_square = " << chi_square <<
+ " after " << kMaxIterationCount << " iterations.";
+ }
+ }
+}
+
+} // namespace
+
+TEST(EntropyProviderTest, UseOneTimeRandomizationSHA1) {
+ // Simply asserts that two trials using one-time randomization
+ // that have different names, normally generate different results.
+ //
+ // Note that depending on the one-time random initialization, they
+ // _might_ actually give the same result, but we know that given
+ // the particular client_id we use for unit tests they won't.
+ base::FieldTrialList field_trial_list(new SHA1EntropyProvider("client_id"));
+ const int kNoExpirationYear = base::FieldTrialList::kNoExpirationYear;
+ scoped_refptr<base::FieldTrial> trials[] = {
+ base::FieldTrialList::FactoryGetFieldTrial(
+ "one", 100, "default", kNoExpirationYear, 1, 1,
+ base::FieldTrial::ONE_TIME_RANDOMIZED, NULL),
+ base::FieldTrialList::FactoryGetFieldTrial(
+ "two", 100, "default", kNoExpirationYear, 1, 1,
+ base::FieldTrial::ONE_TIME_RANDOMIZED, NULL),
+ };
+
+ for (size_t i = 0; i < arraysize(trials); ++i) {
+ for (int j = 0; j < 100; ++j)
+ trials[i]->AppendGroup(std::string(), 1);
+ }
+
+ // The trials are most likely to give different results since they have
+ // different names.
+ EXPECT_NE(trials[0]->group(), trials[1]->group());
+ EXPECT_NE(trials[0]->group_name(), trials[1]->group_name());
+}
+
+TEST(EntropyProviderTest, UseOneTimeRandomizationPermuted) {
+ // Simply asserts that two trials using one-time randomization
+ // that have different names, normally generate different results.
+ //
+ // Note that depending on the one-time random initialization, they
+ // _might_ actually give the same result, but we know that given
+ // the particular client_id we use for unit tests they won't.
+ base::FieldTrialList field_trial_list(
+ new PermutedEntropyProvider(1234, kMaxLowEntropySize));
+ const int kNoExpirationYear = base::FieldTrialList::kNoExpirationYear;
+ scoped_refptr<base::FieldTrial> trials[] = {
+ base::FieldTrialList::FactoryGetFieldTrial(
+ "one", 100, "default", kNoExpirationYear, 1, 1,
+ base::FieldTrial::ONE_TIME_RANDOMIZED, NULL),
+ base::FieldTrialList::FactoryGetFieldTrial(
+ "two", 100, "default", kNoExpirationYear, 1, 1,
+ base::FieldTrial::ONE_TIME_RANDOMIZED, NULL),
+ };
+
+ for (size_t i = 0; i < arraysize(trials); ++i) {
+ for (int j = 0; j < 100; ++j)
+ trials[i]->AppendGroup(std::string(), 1);
+ }
+
+ // The trials are most likely to give different results since they have
+ // different names.
+ EXPECT_NE(trials[0]->group(), trials[1]->group());
+ EXPECT_NE(trials[0]->group_name(), trials[1]->group_name());
+}
+
+TEST(EntropyProviderTest, UseOneTimeRandomizationWithCustomSeedPermuted) {
+ // Ensures that two trials with different names but the same custom seed used
+ // for one time randomization produce the same group assignments.
+ base::FieldTrialList field_trial_list(
+ new PermutedEntropyProvider(1234, kMaxLowEntropySize));
+ const int kNoExpirationYear = base::FieldTrialList::kNoExpirationYear;
+ const uint32_t kCustomSeed = 9001;
+ scoped_refptr<base::FieldTrial> trials[] = {
+ base::FieldTrialList::FactoryGetFieldTrialWithRandomizationSeed(
+ "one", 100, "default", kNoExpirationYear, 1, 1,
+ base::FieldTrial::ONE_TIME_RANDOMIZED, kCustomSeed, NULL),
+ base::FieldTrialList::FactoryGetFieldTrialWithRandomizationSeed(
+ "two", 100, "default", kNoExpirationYear, 1, 1,
+ base::FieldTrial::ONE_TIME_RANDOMIZED, kCustomSeed, NULL),
+ };
+
+ for (size_t i = 0; i < arraysize(trials); ++i) {
+ for (int j = 0; j < 100; ++j)
+ trials[i]->AppendGroup(std::string(), 1);
+ }
+
+ // Normally, these trials should produce different groups, but if the same
+ // custom seed is used, they should produce the same group assignment.
+ EXPECT_EQ(trials[0]->group(), trials[1]->group());
+ EXPECT_EQ(trials[0]->group_name(), trials[1]->group_name());
+}
+
+TEST(EntropyProviderTest, SHA1Entropy) {
+ const double results[] = { GenerateSHA1Entropy("hi", "1"),
+ GenerateSHA1Entropy("there", "1") };
+
+ EXPECT_NE(results[0], results[1]);
+ for (size_t i = 0; i < arraysize(results); ++i) {
+ EXPECT_LE(0.0, results[i]);
+ EXPECT_GT(1.0, results[i]);
+ }
+
+ EXPECT_EQ(GenerateSHA1Entropy("yo", "1"),
+ GenerateSHA1Entropy("yo", "1"));
+ EXPECT_NE(GenerateSHA1Entropy("yo", "something"),
+ GenerateSHA1Entropy("yo", "else"));
+}
+
+TEST(EntropyProviderTest, PermutedEntropy) {
+ const double results[] = {
+ GeneratePermutedEntropy(1234, kMaxLowEntropySize, "1"),
+ GeneratePermutedEntropy(4321, kMaxLowEntropySize, "1") };
+
+ EXPECT_NE(results[0], results[1]);
+ for (size_t i = 0; i < arraysize(results); ++i) {
+ EXPECT_LE(0.0, results[i]);
+ EXPECT_GT(1.0, results[i]);
+ }
+
+ EXPECT_EQ(GeneratePermutedEntropy(1234, kMaxLowEntropySize, "1"),
+ GeneratePermutedEntropy(1234, kMaxLowEntropySize, "1"));
+ EXPECT_NE(GeneratePermutedEntropy(1234, kMaxLowEntropySize, "something"),
+ GeneratePermutedEntropy(1234, kMaxLowEntropySize, "else"));
+}
+
+TEST(EntropyProviderTest, PermutedEntropyProviderResults) {
+ // Verifies that PermutedEntropyProvider produces expected results. This
+ // ensures that the results are the same between platforms and ensures that
+ // changes to the implementation do not regress this accidentally.
+
+ EXPECT_DOUBLE_EQ(2194 / static_cast<double>(kMaxLowEntropySize),
+ GeneratePermutedEntropy(1234, kMaxLowEntropySize, "XYZ"));
+ EXPECT_DOUBLE_EQ(5676 / static_cast<double>(kMaxLowEntropySize),
+ GeneratePermutedEntropy(1, kMaxLowEntropySize, "Test"));
+ EXPECT_DOUBLE_EQ(1151 / static_cast<double>(kMaxLowEntropySize),
+ GeneratePermutedEntropy(5000, kMaxLowEntropySize, "Foo"));
+}
+
+TEST(EntropyProviderTest, SHA1EntropyIsUniform) {
+ for (size_t i = 0; i < arraysize(kTestTrialNames); ++i) {
+ SHA1EntropyGenerator entropy_generator(kTestTrialNames[i]);
+ PerformEntropyUniformityTest(kTestTrialNames[i], entropy_generator);
+ }
+}
+
+TEST(EntropyProviderTest, PermutedEntropyIsUniform) {
+ for (size_t i = 0; i < arraysize(kTestTrialNames); ++i) {
+ PermutedEntropyGenerator entropy_generator(kTestTrialNames[i]);
+ PerformEntropyUniformityTest(kTestTrialNames[i], entropy_generator);
+ }
+}
+
+TEST(EntropyProviderTest, SeededRandGeneratorIsUniform) {
+ // Verifies that SeededRandGenerator has a uniform distribution.
+ //
+ // Mirrors RandUtilTest.RandGeneratorIsUniform in base/rand_util_unittest.cc.
+
+ const uint32_t kTopOfRange =
+ (std::numeric_limits<uint32_t>::max() / 4ULL) * 3ULL;
+ const uint32_t kExpectedAverage = kTopOfRange / 2ULL;
+ const uint32_t kAllowedVariance = kExpectedAverage / 50ULL; // +/- 2%
+ const int kMinAttempts = 1000;
+ const int kMaxAttempts = 1000000;
+
+ for (size_t i = 0; i < arraysize(kTestTrialNames); ++i) {
+ const uint32_t seed = HashName(kTestTrialNames[i]);
+ internal::SeededRandGenerator rand_generator(seed);
+
+ double cumulative_average = 0.0;
+ int count = 0;
+ while (count < kMaxAttempts) {
+ uint32_t value = rand_generator(kTopOfRange);
+ cumulative_average = (count * cumulative_average + value) / (count + 1);
+
+ // Don't quit too quickly for things to start converging, or we may have
+ // a false positive.
+ if (count > kMinAttempts &&
+ kExpectedAverage - kAllowedVariance < cumulative_average &&
+ cumulative_average < kExpectedAverage + kAllowedVariance) {
+ break;
+ }
+
+ ++count;
+ }
+
+ ASSERT_LT(count, kMaxAttempts) << "Expected average was " <<
+ kExpectedAverage << ", average ended at " << cumulative_average <<
+ ", for trial " << kTestTrialNames[i];
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/variations/experiment_labels.cc b/chromium/components/variations/experiment_labels.cc
new file mode 100644
index 00000000000..ef9788055ad
--- /dev/null
+++ b/chromium/components/variations/experiment_labels.cc
@@ -0,0 +1,116 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/experiment_labels.h"
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/variations/variations_associated_data.h"
+#include "components/variations/variations_experiment_util.h"
+
+namespace variations {
+
+namespace {
+
+const char kVariationPrefix[] = "CrVar";
+
+// This method builds a single experiment label for a Chrome Variation,
+// including a timestamp that is a year in the future from |current_time|. Since
+// multiple headers can be transmitted, |count| is a number that is appended
+// after the label key to differentiate the labels.
+base::string16 CreateSingleExperimentLabel(int count,
+ variations::VariationID id,
+ const base::Time& current_time) {
+ // Build the parts separately so they can be validated.
+ const base::string16 key =
+ base::ASCIIToUTF16(kVariationPrefix) + base::IntToString16(count);
+ DCHECK_LE(key.size(), 8U);
+ const base::string16 value = base::IntToString16(id);
+ DCHECK_LE(value.size(), 8U);
+ base::string16 label(key);
+ label += base::ASCIIToUTF16("=");
+ label += value;
+ label += base::ASCIIToUTF16("|");
+ label += variations::BuildExperimentDateString(current_time);
+ return label;
+}
+
+} // namespace
+
+base::string16 BuildGoogleUpdateExperimentLabel(
+ const base::FieldTrial::ActiveGroups& active_groups) {
+ base::string16 experiment_labels;
+ int counter = 0;
+
+ const base::Time current_time(base::Time::Now());
+
+ // Find all currently active VariationIDs associated with Google Update.
+ for (base::FieldTrial::ActiveGroups::const_iterator it =
+ active_groups.begin(); it != active_groups.end(); ++it) {
+ const variations::VariationID id =
+ variations::GetGoogleVariationID(variations::GOOGLE_UPDATE_SERVICE,
+ it->trial_name, it->group_name);
+
+ if (id == variations::EMPTY_ID)
+ continue;
+
+ if (!experiment_labels.empty())
+ experiment_labels += variations::kExperimentLabelSeparator;
+ experiment_labels += CreateSingleExperimentLabel(++counter, id,
+ current_time);
+ }
+
+ return experiment_labels;
+}
+
+base::string16 ExtractNonVariationLabels(const base::string16& labels) {
+ // First, split everything by the label separator.
+ std::vector<base::StringPiece16> entries = base::SplitStringPiece(
+ labels, base::StringPiece16(&variations::kExperimentLabelSeparator, 1),
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+
+ // For each label, keep the ones that do not look like a Variations label.
+ base::string16 non_variation_labels;
+ for (const base::StringPiece16& entry : entries) {
+ if (entry.empty() ||
+ base::StartsWith(entry,
+ base::ASCIIToUTF16(kVariationPrefix),
+ base::CompareCase::INSENSITIVE_ASCII)) {
+ continue;
+ }
+
+ // Dump the whole thing, including the timestamp.
+ if (!non_variation_labels.empty())
+ non_variation_labels += variations::kExperimentLabelSeparator;
+ entry.AppendToString(&non_variation_labels);
+ }
+
+ return non_variation_labels;
+}
+
+base::string16 CombineExperimentLabels(const base::string16& variation_labels,
+ const base::string16& other_labels) {
+ base::StringPiece16 separator(&variations::kExperimentLabelSeparator, 1);
+ DCHECK(!base::StartsWith(variation_labels, separator,
+ base::CompareCase::SENSITIVE));
+ DCHECK(!base::EndsWith(variation_labels, separator,
+ base::CompareCase::SENSITIVE));
+ DCHECK(!base::StartsWith(other_labels, separator,
+ base::CompareCase::SENSITIVE));
+ DCHECK(!base::EndsWith(other_labels, separator,
+ base::CompareCase::SENSITIVE));
+ // Note that if either label is empty, a separator is not necessary.
+ base::string16 combined_labels = other_labels;
+ if (!other_labels.empty() && !variation_labels.empty())
+ combined_labels += variations::kExperimentLabelSeparator;
+ combined_labels += variation_labels;
+ return combined_labels;
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/experiment_labels.h b/chromium/components/variations/experiment_labels.h
new file mode 100644
index 00000000000..9168f70508c
--- /dev/null
+++ b/chromium/components/variations/experiment_labels.h
@@ -0,0 +1,33 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_EXPERIMENT_LABELS_H_
+#define COMPONENTS_VARIATIONS_EXPERIMENT_LABELS_H_
+
+#include "base/metrics/field_trial.h"
+#include "base/strings/string16.h"
+
+namespace variations {
+
+// Takes the list of active groups and builds the label for the ones that have
+// Google Update VariationID associated with them. This will return an empty
+// string if there are no such groups.
+base::string16 BuildGoogleUpdateExperimentLabel(
+ const base::FieldTrial::ActiveGroups& active_groups);
+
+// Creates a final combined experiment labels string with |variation_labels|
+// and |other_labels|, appropriately appending a separator based on their
+// contents. It is assumed that |variation_labels| and |other_labels| do not
+// have leading or trailing separators.
+base::string16 CombineExperimentLabels(const base::string16& variation_labels,
+ const base::string16& other_labels);
+
+// Takes the value of experiment_labels from the registry and returns a valid
+// experiment_labels string value containing only the labels that are not
+// associated with Chrome Variations.
+base::string16 ExtractNonVariationLabels(const base::string16& labels);
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_EXPERIMENT_LABELS_H_
diff --git a/chromium/components/variations/experiment_labels_unittest.cc b/chromium/components/variations/experiment_labels_unittest.cc
new file mode 100644
index 00000000000..8df28261a63
--- /dev/null
+++ b/chromium/components/variations/experiment_labels_unittest.cc
@@ -0,0 +1,196 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/experiment_labels.h"
+
+#include <stddef.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/metrics/field_trial.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/variations/variations_associated_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace variations {
+
+TEST(ExperimentLabelsTest, BuildGoogleUpdateExperimentLabel) {
+ const variations::VariationID TEST_VALUE_A = 3300200;
+ const variations::VariationID TEST_VALUE_B = 3300201;
+ const variations::VariationID TEST_VALUE_C = 3300202;
+ const variations::VariationID TEST_VALUE_D = 3300203;
+
+ struct {
+ const char* active_group_pairs;
+ const char* expected_ids;
+ } test_cases[] = {
+ // Empty group.
+ {"", ""},
+ // Group of 1.
+ {"FieldTrialA#Default", "3300200"},
+ // Group of 1, doesn't have an associated ID.
+ {"FieldTrialA#DoesNotExist", ""},
+ // Group of 3.
+ {"FieldTrialA#Default#FieldTrialB#Group1#FieldTrialC#Default",
+ "3300200#3300201#3300202"},
+ // Group of 3, one doesn't have an associated ID.
+ {"FieldTrialA#Default#FieldTrialB#DoesNotExist#FieldTrialC#Default",
+ "3300200#3300202"},
+ // Group of 3, all three don't have an associated ID.
+ {"FieldTrialX#Default#FieldTrialB#DoesNotExist#FieldTrialC#Default",
+ "3300202"},
+ };
+
+ // Register a few VariationIDs.
+ AssociateGoogleVariationID(variations::GOOGLE_UPDATE_SERVICE, "FieldTrialA",
+ "Default", TEST_VALUE_A);
+ AssociateGoogleVariationID(variations::GOOGLE_UPDATE_SERVICE, "FieldTrialB",
+ "Group1", TEST_VALUE_B);
+ AssociateGoogleVariationID(variations::GOOGLE_UPDATE_SERVICE, "FieldTrialC",
+ "Default", TEST_VALUE_C);
+ AssociateGoogleVariationID(variations::GOOGLE_UPDATE_SERVICE, "FieldTrialD",
+ "Default", TEST_VALUE_D); // Not actually used.
+
+ for (size_t i = 0; i < arraysize(test_cases); ++i) {
+ // Parse the input groups.
+ base::FieldTrial::ActiveGroups groups;
+ std::vector<std::string> group_data = base::SplitString(
+ test_cases[i].active_group_pairs, "#",
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ ASSERT_EQ(0U, group_data.size() % 2);
+ for (size_t j = 0; j < group_data.size(); j += 2) {
+ base::FieldTrial::ActiveGroup group;
+ group.trial_name = group_data[j];
+ group.group_name = group_data[j + 1];
+ groups.push_back(group);
+ }
+
+ // Parse the expected output.
+ std::vector<std::string> expected_ids_list = base::SplitString(
+ test_cases[i].expected_ids, "#",
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+
+ std::string experiment_labels_string = base::UTF16ToUTF8(
+ BuildGoogleUpdateExperimentLabel(groups));
+
+ // Split the VariationIDs from the labels for verification below.
+ std::set<std::string> parsed_ids;
+ for (const std::string& label : base::SplitString(
+ experiment_labels_string, ";",
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+ // The ID is precisely between the '=' and '|' characters in each label.
+ size_t index_of_equals = label.find('=');
+ size_t index_of_pipe = label.find('|');
+ ASSERT_NE(std::string::npos, index_of_equals);
+ ASSERT_NE(std::string::npos, index_of_pipe);
+ ASSERT_GT(index_of_pipe, index_of_equals);
+ parsed_ids.insert(label.substr(index_of_equals + 1,
+ index_of_pipe - index_of_equals - 1));
+ }
+
+ // Verify that the resulting string contains each of the expected labels,
+ // and nothing more. Note that the date is stripped out and ignored.
+ for (std::vector<std::string>::const_iterator it =
+ expected_ids_list.begin(); it != expected_ids_list.end(); ++it) {
+ std::set<std::string>::iterator it2 = parsed_ids.find(*it);
+ EXPECT_TRUE(parsed_ids.end() != it2);
+ parsed_ids.erase(it2);
+ }
+ EXPECT_TRUE(parsed_ids.empty());
+ } // for
+}
+
+TEST(ExperimentLabelsTest, CombineExperimentLabels) {
+ struct {
+ const char* variations_labels;
+ const char* other_labels;
+ const char* expected_label;
+ } test_cases[] = {
+ {"A=B|Tue, 21 Jan 2014 15:30:21 GMT",
+ "C=D|Tue, 21 Jan 2014 15:30:21 GMT",
+ "C=D|Tue, 21 Jan 2014 15:30:21 GMT;A=B|Tue, 21 Jan 2014 15:30:21 GMT"},
+ {"A=B|Tue, 21 Jan 2014 15:30:21 GMT",
+ "",
+ "A=B|Tue, 21 Jan 2014 15:30:21 GMT"},
+ {"",
+ "A=B|Tue, 21 Jan 2014 15:30:21 GMT",
+ "A=B|Tue, 21 Jan 2014 15:30:21 GMT"},
+ {"A=B|Tue, 21 Jan 2014 15:30:21 GMT;C=D|Tue, 21 Jan 2014 15:30:21 GMT",
+ "P=Q|Tue, 21 Jan 2014 15:30:21 GMT;X=Y|Tue, 21 Jan 2014 15:30:21 GMT",
+ "P=Q|Tue, 21 Jan 2014 15:30:21 GMT;X=Y|Tue, 21 Jan 2014 15:30:21 GMT;"
+ "A=B|Tue, 21 Jan 2014 15:30:21 GMT;C=D|Tue, 21 Jan 2014 15:30:21 GMT"},
+ {"",
+ "",
+ ""},
+ };
+
+ for (size_t i = 0; i < arraysize(test_cases); ++i) {
+ std::string result = base::UTF16ToUTF8(CombineExperimentLabels(
+ base::ASCIIToUTF16(test_cases[i].variations_labels),
+ base::ASCIIToUTF16(test_cases[i].other_labels)));
+ EXPECT_EQ(test_cases[i].expected_label, result);
+ }
+}
+
+TEST(ExperimentLabelsTest, ExtractNonVariationLabels) {
+ struct {
+ const char* input_label;
+ const char* expected_output;
+ } test_cases[] = {
+ // Empty
+ {"", ""},
+ // One
+ {"gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT",
+ "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"},
+ // Three
+ {"CrVar1=123|Tue, 21 Jan 2014 15:30:21 GMT;"
+ "experiment1=456|Tue, 21 Jan 2014 15:30:21 GMT;"
+ "experiment2=789|Tue, 21 Jan 2014 15:30:21 GMT;"
+ "CrVar1=123|Tue, 21 Jan 2014 15:30:21 GMT",
+ "experiment1=456|Tue, 21 Jan 2014 15:30:21 GMT;"
+ "experiment2=789|Tue, 21 Jan 2014 15:30:21 GMT"},
+ // One and one Variation
+ {"gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT;"
+ "CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT",
+ "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"},
+ // One and one Variation, flipped
+ {"CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT;"
+ "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT",
+ "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"},
+ // Sandwiched
+ {"CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT;"
+ "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT;"
+ "CrVar2=3310003|Tue, 21 Jan 2014 15:30:21 GMT;"
+ "CrVar3=3310004|Tue, 21 Jan 2014 15:30:21 GMT",
+ "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"},
+ // Only Variations
+ {"CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT;"
+ "CrVar2=3310003|Tue, 21 Jan 2014 15:30:21 GMT;"
+ "CrVar3=3310004|Tue, 21 Jan 2014 15:30:21 GMT",
+ ""},
+ // Empty values
+ {"gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT;"
+ "CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT",
+ "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"},
+ // Trailing semicolon
+ {"gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT;"
+ "CrVar1=3310002|Tue, 21 Jan 2014 15:30:21 GMT;", // Note the semi here.
+ "gcapi_brand=123|Tue, 21 Jan 2014 15:30:21 GMT"},
+ // Semis
+ {";;;;", ""},
+ };
+
+ for (size_t i = 0; i < arraysize(test_cases); ++i) {
+ std::string non_variation_labels = base::UTF16ToUTF8(
+ ExtractNonVariationLabels(
+ base::ASCIIToUTF16(test_cases[i].input_label)));
+ EXPECT_EQ(test_cases[i].expected_output, non_variation_labels);
+ }
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/metrics_util.cc b/chromium/components/variations/metrics_util.cc
new file mode 100644
index 00000000000..52fa0be09c6
--- /dev/null
+++ b/chromium/components/variations/metrics_util.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/metrics_util.h"
+
+#include <string.h>
+
+#include "base/sha1.h"
+#include "base/sys_byteorder.h"
+
+namespace metrics {
+
+uint32_t HashName(const std::string& name) {
+ // SHA-1 is designed to produce a uniformly random spread in its output space,
+ // even for nearly-identical inputs.
+ unsigned char sha1_hash[base::kSHA1Length];
+ base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(name.c_str()),
+ name.size(),
+ sha1_hash);
+
+ uint32_t bits;
+ static_assert(sizeof(bits) < sizeof(sha1_hash), "more data required");
+ memcpy(&bits, sha1_hash, sizeof(bits));
+
+ return base::ByteSwapToLE32(bits);
+}
+
+} // namespace metrics
diff --git a/chromium/components/variations/metrics_util.h b/chromium/components/variations/metrics_util.h
new file mode 100644
index 00000000000..823fcd7ba23
--- /dev/null
+++ b/chromium/components/variations/metrics_util.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_METRICS_UTIL_H_
+#define COMPONENTS_VARIATIONS_METRICS_UTIL_H_
+
+#include <stdint.h>
+
+#include <string>
+
+
+namespace metrics {
+
+// Computes a uint32_t hash of a given string based on its SHA1 hash. Suitable
+// for uniquely identifying field trial names and group names.
+uint32_t HashName(const std::string& name);
+
+} // namespace metrics
+
+#endif // COMPONENTS_VARIATIONS_METRICS_UTIL_H_
diff --git a/chromium/components/variations/metrics_util_unittest.cc b/chromium/components/variations/metrics_util_unittest.cc
new file mode 100644
index 00000000000..953e1390d0e
--- /dev/null
+++ b/chromium/components/variations/metrics_util_unittest.cc
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/metrics_util.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+TEST(MetricsUtilTest, HashName) {
+ // Checks that hashing is stable on all platforms.
+ struct {
+ const char* name;
+ uint32_t hash_value;
+ } known_hashes[] = {
+ {"a", 937752454u},
+ {"1", 723085877u},
+ {"Trial Name", 2713117220u},
+ {"Group Name", 3201815843u},
+ {"My Favorite Experiment", 3722155194u},
+ {"My Awesome Group Name", 4109503236u},
+ {"abcdefghijklmonpqrstuvwxyz", 787728696u},
+ {"0123456789ABCDEF", 348858318U}
+ };
+
+ for (size_t i = 0; i < arraysize(known_hashes); ++i)
+ EXPECT_EQ(known_hashes[i].hash_value, HashName(known_hashes[i].name));
+}
+
+} // namespace metrics
diff --git a/chromium/components/variations/net/BUILD.gn b/chromium/components/variations/net/BUILD.gn
new file mode 100644
index 00000000000..c037d46fec3
--- /dev/null
+++ b/chromium/components/variations/net/BUILD.gn
@@ -0,0 +1,22 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("net") {
+ sources = [
+ "variations_http_headers.cc",
+ "variations_http_headers.h",
+ ]
+
+ public_deps = [
+ "//components/variations",
+ "//net",
+ "//url",
+ ]
+ deps = [
+ "//base",
+ "//components/google/core/browser",
+ "//components/metrics",
+ "//components/variations/proto",
+ ]
+}
diff --git a/chromium/components/variations/net/DEPS b/chromium/components/variations/net/DEPS
new file mode 100644
index 00000000000..1abc71b7882
--- /dev/null
+++ b/chromium/components/variations/net/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+components/google",
+ "+components/metrics",
+ "+net",
+]
diff --git a/chromium/components/variations/net/variations_http_headers.cc b/chromium/components/variations/net/variations_http_headers.cc
new file mode 100644
index 00000000000..48e84eb190c
--- /dev/null
+++ b/chromium/components/variations/net/variations_http_headers.cc
@@ -0,0 +1,110 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/net/variations_http_headers.h"
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+#include "components/google/core/browser/google_util.h"
+#include "components/variations/variations_http_header_provider.h"
+#include "net/http/http_request_headers.h"
+#include "url/gurl.h"
+
+namespace variations {
+
+namespace {
+
+const char* kSuffixesToSetHeadersFor[] = {
+ ".android.com",
+ ".doubleclick.com",
+ ".doubleclick.net",
+ ".ggpht.com",
+ ".googleadservices.com",
+ ".googleapis.com",
+ ".googlesyndication.com",
+ ".googleusercontent.com",
+ ".googlevideo.com",
+ ".gstatic.com",
+ ".ytimg.com",
+};
+
+// Exact hostnames in lowercase to set headers for.
+const char* kHostsToSetHeadersFor[] = {
+ "googleweblight.com",
+};
+
+const char kChromeUMAEnabled[] = "X-Chrome-UMA-Enabled";
+const char kClientData[] = "X-Client-Data";
+
+} // namespace
+
+void AppendVariationHeaders(const GURL& url,
+ bool incognito,
+ bool uma_enabled,
+ net::HttpRequestHeaders* headers) {
+ // Note the criteria for attaching client experiment headers:
+ // 1. We only transmit to Google owned domains which can evaluate experiments.
+ // 1a. These include hosts which have a standard postfix such as:
+ // *.doubleclick.net or *.googlesyndication.com or
+ // exactly www.googleadservices.com or
+ // international TLD domains *.google.<TLD> or *.youtube.<TLD>.
+ // 2. Only transmit for non-Incognito profiles.
+ // 3. For the X-Chrome-UMA-Enabled bit, only set it if UMA is in fact enabled
+ // for this install of Chrome.
+ // 4. For the X-Client-Data header, only include non-empty variation IDs.
+ if (incognito || !internal::ShouldAppendVariationHeaders(url))
+ return;
+
+ if (uma_enabled)
+ headers->SetHeaderIfMissing(kChromeUMAEnabled, "1");
+
+ const std::string variation_ids_header =
+ VariationsHttpHeaderProvider::GetInstance()->GetClientDataHeader();
+ if (!variation_ids_header.empty()) {
+ // Note that prior to M33 this header was named X-Chrome-Variations.
+ headers->SetHeaderIfMissing(kClientData, variation_ids_header);
+ }
+}
+
+std::set<std::string> GetVariationHeaderNames() {
+ std::set<std::string> headers;
+ headers.insert(kChromeUMAEnabled);
+ headers.insert(kClientData);
+ return headers;
+}
+
+namespace internal {
+
+// static
+bool ShouldAppendVariationHeaders(const GURL& url) {
+ if (google_util::IsGoogleDomainUrl(url, google_util::ALLOW_SUBDOMAIN,
+ google_util::ALLOW_NON_STANDARD_PORTS)) {
+ return true;
+ }
+
+ if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS())
+ return false;
+
+ // Some domains don't have international TLD extensions, so testing for them
+ // is very straight forward.
+ const std::string host = url.host();
+ for (size_t i = 0; i < arraysize(kSuffixesToSetHeadersFor); ++i) {
+ if (base::EndsWith(host, kSuffixesToSetHeadersFor[i],
+ base::CompareCase::INSENSITIVE_ASCII))
+ return true;
+ }
+ for (size_t i = 0; i < arraysize(kHostsToSetHeadersFor); ++i) {
+ if (base::LowerCaseEqualsASCII(host, kHostsToSetHeadersFor[i]))
+ return true;
+ }
+
+ return google_util::IsYoutubeDomainUrl(url, google_util::ALLOW_SUBDOMAIN,
+ google_util::ALLOW_NON_STANDARD_PORTS);
+}
+
+} // namespace internal
+
+} // namespace variations
diff --git a/chromium/components/variations/net/variations_http_headers.h b/chromium/components/variations/net/variations_http_headers.h
new file mode 100644
index 00000000000..d6280e56e8e
--- /dev/null
+++ b/chromium/components/variations/net/variations_http_headers.h
@@ -0,0 +1,41 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_NET_VARIATIONS_HTTP_HEADERS_H_
+#define COMPONENTS_VARIATIONS_NET_VARIATIONS_HTTP_HEADERS_H_
+
+#include <set>
+#include <string>
+
+namespace net {
+class HttpRequestHeaders;
+}
+
+class GURL;
+
+namespace variations {
+
+// Adds Chrome experiment and metrics state as custom headers to |headers|.
+// Some headers may not be set given the |incognito| mode or whether
+// the user has |uma_enabled|. Also, we never transmit headers to non-Google
+// sites, which is checked based on the destination |url|.
+void AppendVariationHeaders(const GURL& url,
+ bool incognito,
+ bool uma_enabled,
+ net::HttpRequestHeaders* headers);
+
+// Returns the HTTP header names which are added by AppendVariationHeaders().
+std::set<std::string> GetVariationHeaderNames();
+
+namespace internal {
+
+// Checks whether variation headers should be appended to requests to the
+// specified |url|. Returns true for google.<TLD> and youtube.<TLD> URLs.
+bool ShouldAppendVariationHeaders(const GURL& url);
+
+} // namespace internal
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_NET_VARIATIONS_HTTP_HEADERS_H_
diff --git a/chromium/components/variations/net/variations_http_headers_unittest.cc b/chromium/components/variations/net/variations_http_headers_unittest.cc
new file mode 100644
index 00000000000..6421cc78868
--- /dev/null
+++ b/chromium/components/variations/net/variations_http_headers_unittest.cc
@@ -0,0 +1,118 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/net/variations_http_headers.h"
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace variations {
+
+TEST(VariationsHttpHeadersTest, ShouldAppendHeaders) {
+ struct {
+ const char* url;
+ bool should_append_headers;
+ } cases[] = {
+ {"http://google.com", true},
+ {"http://www.google.com", true},
+ {"http://m.google.com", true},
+ {"http://google.ca", true},
+ {"https://google.ca", true},
+ {"http://google.co.uk", true},
+ {"http://google.co.uk:8080/", true},
+ {"http://www.google.co.uk:8080/", true},
+ {"http://google", false},
+
+ {"http://youtube.com", true},
+ {"http://www.youtube.com", true},
+ {"http://www.youtube.ca", true},
+ {"http://www.youtube.co.uk:8080/", true},
+ {"https://www.youtube.com", true},
+ {"http://youtube", false},
+
+ {"http://www.yahoo.com", false},
+
+ {"http://ad.doubleclick.net", true},
+ {"https://a.b.c.doubleclick.net", true},
+ {"https://a.b.c.doubleclick.net:8081", true},
+ {"http://www.doubleclick.com", true},
+ {"http://www.doubleclick.org", false},
+ {"http://www.doubleclick.net.com", false},
+ {"https://www.doubleclick.net.com", false},
+
+ {"http://ad.googlesyndication.com", true},
+ {"https://a.b.c.googlesyndication.com", true},
+ {"https://a.b.c.googlesyndication.com:8080", true},
+ {"http://www.doubleclick.edu", false},
+ {"http://www.googlesyndication.com.edu", false},
+ {"https://www.googlesyndication.com.com", false},
+
+ {"http://www.googleadservices.com", true},
+ {"http://www.googleadservices.com:8080", true},
+ {"https://www.googleadservices.com", true},
+ {"https://www.internal.googleadservices.com", true},
+ {"https://www2.googleadservices.com", true},
+ {"https://www.googleadservices.org", false},
+ {"https://www.googleadservices.com.co.uk", false},
+
+ {"http://WWW.ANDROID.COM", true},
+ {"http://www.android.com", true},
+ {"http://www.doubleclick.com", true},
+ {"http://www.doubleclick.net", true},
+ {"http://www.ggpht.com", true},
+ {"http://www.googleadservices.com", true},
+ {"http://www.googleapis.com", true},
+ {"http://www.googlesyndication.com", true},
+ {"http://www.googleusercontent.com", true},
+ {"http://www.googlevideo.com", true},
+ {"http://ssl.gstatic.com", true},
+ {"http://www.gstatic.com", true},
+ {"http://www.ytimg.com", true},
+ {"http://wwwytimg.com", false},
+ {"http://ytimg.com", false},
+
+ {"http://www.android.org", false},
+ {"http://www.doubleclick.org", false},
+ {"http://www.doubleclick.net", true},
+ {"http://www.ggpht.org", false},
+ {"http://www.googleadservices.org", false},
+ {"http://www.googleapis.org", false},
+ {"http://www.googlesyndication.org", false},
+ {"http://www.googleusercontent.org", false},
+ {"http://www.googlevideo.org", false},
+ {"http://ssl.gstatic.org", false},
+ {"http://www.gstatic.org", false},
+ {"http://www.ytimg.org", false},
+
+ {"http://a.b.android.com", true},
+ {"http://a.b.doubleclick.com", true},
+ {"http://a.b.doubleclick.net", true},
+ {"http://a.b.ggpht.com", true},
+ {"http://a.b.googleadservices.com", true},
+ {"http://a.b.googleapis.com", true},
+ {"http://a.b.googlesyndication.com", true},
+ {"http://a.b.googleusercontent.com", true},
+ {"http://a.b.googlevideo.com", true},
+ {"http://ssl.gstatic.com", true},
+ {"http://a.b.gstatic.com", true},
+ {"http://a.b.ytimg.com", true},
+ {"http://googleweblight.com", true},
+ {"https://googleweblight.com", true},
+ {"http://wwwgoogleweblight.com", false},
+ {"http://www.googleweblight.com", false},
+ {"http://a.b.googleweblight.com", false},
+ };
+
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ const GURL url(cases[i].url);
+ EXPECT_EQ(cases[i].should_append_headers,
+ internal::ShouldAppendVariationHeaders(url))
+ << url;
+ }
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/pref_names.cc b/chromium/components/variations/pref_names.cc
new file mode 100644
index 00000000000..8bd52ce045a
--- /dev/null
+++ b/chromium/components/variations/pref_names.cc
@@ -0,0 +1,45 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/pref_names.h"
+
+namespace variations {
+namespace prefs {
+
+// Base64-encoded compressed serialized form of the variations seed protobuf.
+const char kVariationsCompressedSeed[] = "variations_compressed_seed";
+
+// 64-bit integer serialization of the base::Time from the last successful seed
+// fetch (i.e. when the Variations server responds with 200 or 304).
+const char kVariationsLastFetchTime[] = "variations_last_fetch_time";
+
+// The latest country code received by the VariationsService for evaluating
+// studies.
+const char kVariationsCountry[] = "variations_country";
+
+// Pair of <Chrome version string, country code string> representing the country
+// used for filtering permanent consistency studies until the next time Chrome
+// is updated.
+const char kVariationsPermanentConsistencyCountry[] =
+ "variations_permanent_consistency_country";
+
+// A serialized PermutedEntropyCache protobuf, used as a cache to avoid
+// recomputing permutations.
+const char kVariationsPermutedEntropyCache[] =
+ "user_experience_metrics.permuted_entropy_cache";
+
+// String for the restrict parameter to be appended to the variations URL.
+const char kVariationsRestrictParameter[] = "variations_restrict_parameter";
+
+// Base64-encoded serialized form of the variations seed protobuf.
+const char kVariationsSeed[] = "variations_seed";
+
+// 64-bit integer serialization of the base::Time from the last seed received.
+const char kVariationsSeedDate[] = "variations_seed_date";
+
+// Digital signature of the binary variations seed data, base64-encoded.
+const char kVariationsSeedSignature[] = "variations_seed_signature";
+
+} // namespace prefs
+} // namespace metrics
diff --git a/chromium/components/variations/pref_names.h b/chromium/components/variations/pref_names.h
new file mode 100644
index 00000000000..17cbd8858b0
--- /dev/null
+++ b/chromium/components/variations/pref_names.h
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_PREF_NAMES_H_
+#define COMPONENTS_VARIATIONS_PREF_NAMES_H_
+
+namespace variations {
+namespace prefs {
+
+// Alphabetical list of preference names specific to the variations component.
+// Keep alphabetized, and document each in the .cc file.
+
+extern const char kVariationsCompressedSeed[];
+extern const char kVariationsLastFetchTime[];
+extern const char kVariationsPermanentConsistencyCountry[];
+extern const char kVariationsPermutedEntropyCache[];
+extern const char kVariationsCountry[];
+extern const char kVariationsRestrictParameter[];
+extern const char kVariationsSeed[];
+extern const char kVariationsSeedDate[];
+extern const char kVariationsSeedSignature[];
+
+} // namespace prefs
+} // namespace metrics
+
+#endif // COMPONENTS_VARIATIONS_PREF_NAMES_H_
diff --git a/chromium/components/variations/processed_study.cc b/chromium/components/variations/processed_study.cc
new file mode 100644
index 00000000000..8bf91e3b3ff
--- /dev/null
+++ b/chromium/components/variations/processed_study.cc
@@ -0,0 +1,166 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/processed_study.h"
+
+#include <set>
+#include <string>
+
+#include "base/version.h"
+#include "components/variations/proto/study.pb.h"
+
+namespace variations {
+
+namespace {
+
+// Validates the sanity of |study| and computes the total probability and
+// whether all assignments are to a single group.
+bool ValidateStudyAndComputeTotalProbability(
+ const Study& study,
+ base::FieldTrial::Probability* total_probability,
+ bool* all_assignments_to_one_group,
+ std::string* single_feature_name) {
+ // At the moment, a missing default_experiment_name makes the study invalid.
+ if (study.default_experiment_name().empty()) {
+ DVLOG(1) << study.name() << " has no default experiment defined.";
+ return false;
+ }
+ if (study.filter().has_min_version() &&
+ !Version::IsValidWildcardString(study.filter().min_version())) {
+ DVLOG(1) << study.name() << " has invalid min version: "
+ << study.filter().min_version();
+ return false;
+ }
+ if (study.filter().has_max_version() &&
+ !Version::IsValidWildcardString(study.filter().max_version())) {
+ DVLOG(1) << study.name() << " has invalid max version: "
+ << study.filter().max_version();
+ return false;
+ }
+
+ const std::string& default_group_name = study.default_experiment_name();
+ base::FieldTrial::Probability divisor = 0;
+
+ bool multiple_assigned_groups = false;
+ bool found_default_group = false;
+ std::string single_feature_name_seen;
+ bool has_multiple_features = false;
+
+ std::set<std::string> experiment_names;
+ for (int i = 0; i < study.experiment_size(); ++i) {
+ const Study_Experiment& experiment = study.experiment(i);
+ if (experiment.name().empty()) {
+ DVLOG(1) << study.name() << " is missing experiment " << i << " name";
+ return false;
+ }
+ if (!experiment_names.insert(experiment.name()).second) {
+ DVLOG(1) << study.name() << " has a repeated experiment name "
+ << study.experiment(i).name();
+ return false;
+ }
+
+ if (!has_multiple_features) {
+ const auto& features = experiment.feature_association();
+ for (int i = 0; i < features.enable_feature_size(); ++i) {
+ const std::string& feature_name = features.enable_feature(i);
+ if (single_feature_name_seen.empty()) {
+ single_feature_name_seen = feature_name;
+ } else if (feature_name != single_feature_name_seen) {
+ has_multiple_features = true;
+ break;
+ }
+ }
+ for (int i = 0; i < features.disable_feature_size(); ++i) {
+ const std::string& feature_name = features.disable_feature(i);
+ if (single_feature_name_seen.empty()) {
+ single_feature_name_seen = feature_name;
+ } else if (feature_name != single_feature_name_seen) {
+ has_multiple_features = true;
+ break;
+ }
+ }
+ }
+
+ if (!experiment.has_forcing_flag() && experiment.probability_weight() > 0) {
+ // If |divisor| is not 0, there was at least one prior non-zero group.
+ if (divisor != 0)
+ multiple_assigned_groups = true;
+ divisor += experiment.probability_weight();
+ }
+ if (study.experiment(i).name() == default_group_name)
+ found_default_group = true;
+ }
+
+ if (!found_default_group) {
+ DVLOG(1) << study.name() << " is missing default experiment in its "
+ << "experiment list";
+ // The default group was not found in the list of groups. This study is not
+ // valid.
+ return false;
+ }
+
+ if (!has_multiple_features && !single_feature_name_seen.empty())
+ single_feature_name->swap(single_feature_name_seen);
+ else
+ single_feature_name->clear();
+
+ *total_probability = divisor;
+ *all_assignments_to_one_group = !multiple_assigned_groups;
+ return true;
+}
+
+
+} // namespace
+
+ProcessedStudy::ProcessedStudy()
+ : study_(NULL),
+ total_probability_(0),
+ all_assignments_to_one_group_(false),
+ is_expired_(false) {
+}
+
+ProcessedStudy::~ProcessedStudy() {
+}
+
+bool ProcessedStudy::Init(const Study* study, bool is_expired) {
+ base::FieldTrial::Probability total_probability = 0;
+ bool all_assignments_to_one_group = false;
+ std::string single_feature_name;
+ if (!ValidateStudyAndComputeTotalProbability(*study, &total_probability,
+ &all_assignments_to_one_group,
+ &single_feature_name)) {
+ return false;
+ }
+
+ study_ = study;
+ is_expired_ = is_expired;
+ total_probability_ = total_probability;
+ all_assignments_to_one_group_ = all_assignments_to_one_group;
+ single_feature_name_.swap(single_feature_name);
+ return true;
+}
+
+int ProcessedStudy::GetExperimentIndexByName(const std::string& name) const {
+ for (int i = 0; i < study_->experiment_size(); ++i) {
+ if (study_->experiment(i).name() == name)
+ return i;
+ }
+
+ return -1;
+}
+
+// static
+bool ProcessedStudy::ValidateAndAppendStudy(
+ const Study* study,
+ bool is_expired,
+ std::vector<ProcessedStudy>* processed_studies) {
+ ProcessedStudy processed_study;
+ if (processed_study.Init(study, is_expired)) {
+ processed_studies->push_back(processed_study);
+ return true;
+ }
+ return false;
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/processed_study.h b/chromium/components/variations/processed_study.h
new file mode 100644
index 00000000000..0607d064a9f
--- /dev/null
+++ b/chromium/components/variations/processed_study.h
@@ -0,0 +1,71 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_PROCESSED_STUDY_H_
+#define COMPONENTS_VARIATIONS_PROCESSED_STUDY_H_
+
+#include <string>
+#include <vector>
+
+#include "base/metrics/field_trial.h"
+
+namespace variations {
+
+class Study;
+
+// Wrapper over Study with extra information computed during pre-processing,
+// such as whether the study is expired and its total probability.
+class ProcessedStudy {
+ public:
+ ProcessedStudy();
+ ~ProcessedStudy();
+
+ bool Init(const Study* study, bool is_expired);
+
+ const Study* study() const { return study_; }
+
+ base::FieldTrial::Probability total_probability() const {
+ return total_probability_;
+ }
+
+ bool all_assignments_to_one_group() const {
+ return all_assignments_to_one_group_;
+ }
+
+ bool is_expired() const { return is_expired_; }
+
+ const std::string& single_feature_name() const {
+ return single_feature_name_;
+ }
+
+ // Gets the index of the experiment with the given |name|. Returns -1 if no
+ // experiment is found.
+ int GetExperimentIndexByName(const std::string& name) const;
+
+ static bool ValidateAndAppendStudy(
+ const Study* study,
+ bool is_expired,
+ std::vector<ProcessedStudy>* processed_studies);
+
+ private:
+ // Corresponding Study object. Weak reference.
+ const Study* study_;
+
+ // Computed total group probability for the study.
+ base::FieldTrial::Probability total_probability_;
+
+ // Whether all assignments are to a single group.
+ bool all_assignments_to_one_group_;
+
+ // Whether the study is expired.
+ bool is_expired_;
+
+ // If the study has groups that enable/disable a single feature, the name of
+ // that feature.
+ std::string single_feature_name_;
+};
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_PROCESSED_STUDY_H_
diff --git a/chromium/components/variations/proto/BUILD.gn b/chromium/components/variations/proto/BUILD.gn
new file mode 100644
index 00000000000..b22effe6438
--- /dev/null
+++ b/chromium/components/variations/proto/BUILD.gn
@@ -0,0 +1,14 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/protobuf/proto_library.gni")
+
+proto_library("proto") {
+ sources = [
+ "client_variations.proto",
+ "permuted_entropy_cache.proto",
+ "study.proto",
+ "variations_seed.proto",
+ ]
+}
diff --git a/chromium/components/variations/proto/client_variations.proto b/chromium/components/variations/proto/client_variations.proto
new file mode 100644
index 00000000000..fbb3367e0bf
--- /dev/null
+++ b/chromium/components/variations/proto/client_variations.proto
@@ -0,0 +1,20 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Summary of client variations from experiments.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package variations;
+
+message ClientVariations {
+ // A list of client experiment variation IDs that are active.
+ repeated int32 variation_id = 1;
+
+ // A list of client experiment variation IDs that trigger server side
+ // behavior.
+ repeated int32 trigger_variation_id = 3;
+}
diff --git a/chromium/components/variations/proto/permuted_entropy_cache.proto b/chromium/components/variations/proto/permuted_entropy_cache.proto
new file mode 100644
index 00000000000..fb78a06ef07
--- /dev/null
+++ b/chromium/components/variations/proto/permuted_entropy_cache.proto
@@ -0,0 +1,19 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package metrics;
+
+// Represents a cache of permuted entropy mappings, where each entry maps from
+// a |randomization_seed| to a |value|.
+message PermutedEntropyCache {
+ message Entry {
+ required uint32 randomization_seed = 1;
+ required uint32 value = 2;
+ }
+ repeated Entry entry = 1;
+}
diff --git a/chromium/components/variations/proto/study.proto b/chromium/components/variations/proto/study.proto
new file mode 100644
index 00000000000..f2bae5b9b9b
--- /dev/null
+++ b/chromium/components/variations/proto/study.proto
@@ -0,0 +1,298 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package variations;
+
+// This defines the Protocol Buffer representation of a Chrome Variations study
+// as sent to clients of the Variations server.
+//
+// Next tag: 13
+message Study {
+ // The name of the study. Should not contain spaces or special characters.
+ // Ex: "my_study"
+ required string name = 1;
+
+ // The expiry date of the study in Unix time format. (Seconds since midnight
+ // January 1, 1970 UTC). See: http://en.wikipedia.org/wiki/Unix_time
+ //
+ // A study that has expired will be disabled, and users will be assigned
+ // groups based on the default_experiment_name. This will take precedence over
+ // a corresponding hardcoded field trial in the client.
+ //
+ // Ex: 1330893974 (corresponds to 2012-03-04 20:46:14Z)
+ optional int64 expiry_date = 3;
+
+ // Consistency setting for a study.
+ enum Consistency {
+ SESSION = 0; // Can't change within a session.
+ PERMANENT = 1; // Can't change for a given user.
+ }
+
+ // Consistency setting for this study. Optional - defaults to SESSION.
+ // Ex: PERMANENT
+ optional Consistency consistency = 7 [default = SESSION];
+
+ // Name of the experiment that gets the default experience. This experiment
+ // must be included in the list below.
+ // Ex: "default"
+ optional string default_experiment_name = 8;
+
+ // An experiment within the study.
+ //
+ // Next tag: 13
+ message Experiment {
+ // A named parameter value for this experiment.
+ //
+ // Next tag: 3
+ message Param {
+ // The name of the parameter.
+ optional string name = 1;
+
+ // The value of the parameter.
+ optional string value = 2;
+ }
+
+ // The name of the experiment within the study.
+ // Ex: "bucketA"
+ required string name = 1;
+
+ // The cut of the total probability taken for this experiment (the x in
+ // x / N, where N is the sum of all x’s). Ex: "50"
+ required uint32 probability_weight = 2;
+
+ // Optional id used to uniquely identify this experiment for Google web
+ // properties.
+ optional uint64 google_web_experiment_id = 3;
+
+ // Optional id used to allow this experiment to trigger experimental
+ // behavior on Google web properties.
+ optional uint64 google_web_trigger_experiment_id = 8;
+
+ // Optional id used to uniquely identify this experiment for Google Update.
+ optional uint64 google_update_experiment_id = 4;
+
+ // Optional id used to uniquely identify this experiment for Chrome Sync.
+ optional uint64 chrome_sync_experiment_id = 10;
+
+ // Specifies the feature association parameters for this experiment group.
+ //
+ // Next tag: 5
+ message FeatureAssociation {
+ // Optional list of features to enable when this experiment is selected.
+ // Command-line overrides take precedence over this setting. No feature
+ // listed here should exist in the |disable_feature| list.
+ repeated string enable_feature = 1;
+
+ // Optional list of features to disable when this experiment is selected.
+ // Command-line overrides take precedence over this setting. No feature
+ // listed here should exist in the |enable_feature| list.
+ repeated string disable_feature = 2;
+
+ // Similar to |forcing_flag|, this is an optional name of a feature which
+ // will cause this experiment to be activated, if that feature is enabled
+ // from the command-line. Experiment with this set are not eligible for
+ // selection via a random dice roll.
+ // Mutually exclusive with |forcing_flag|, |forcing_feature_off| or
+ // having a non-zero |probability_weight|.
+ optional string forcing_feature_on = 3;
+
+ // Similar to |forcing_flag|, this is an optional name of a feature which
+ // will cause this experiment to be activated, if that feature is disabled
+ // from the command-line. Experiment with this set are not eligible for
+ // selection via a random dice roll.
+ // Mutually exclusive with |forcing_flag|, |forcing_feature_on| or having
+ // a non-zero |probability_weight|.
+ optional string forcing_feature_off = 4;
+ }
+ optional FeatureAssociation feature_association = 12;
+
+ // Optional name of a Chrome flag that, when present, causes this experiment
+ // to be forced. If the forcing_flag field is set, users will not be
+ // assigned to this experiment unless that flag is present in Chrome's
+ // command line.
+ // Mutually exclusive with |forcing_feature_on|, |forcing_feature_off| or
+ // having a non-zero |probability_weight|.
+ optional string forcing_flag = 5;
+
+ // Parameter values for this experiment.
+ repeated Param param = 6;
+
+ enum Type {
+ // Regular experiment group. This is the default value and can be omitted.
+ NORMAL = 0;
+
+ // Changes to this experiment group are ignored for the purposes of
+ // kill-switch triggering. Included to allow the flexibility to not
+ // trigger this logic for specific cases (e.g. a group rename without
+ // any functionality changes).
+ IGNORE_CHANGE = 1;
+
+ // This is a kill-switch group that should be killed at "best effort"
+ // priority, e.g. with a hot dog menu badge. The experiment must have a
+ // probability_weight of 0.
+ KILL_BEST_EFFORT = 2;
+
+ // This is a kill-switch group that should be killed with "critical"
+ // priority. Depending on platform this may result in showing a
+ // non-dismissible restart prompt with a timer. This should only be used
+ // in very serious emergency circumstances. The experiment must have a
+ // probability_weight of 0.
+ KILL_CRITICAL = 3;
+ }
+ optional Type type = 7 [default = NORMAL];
+
+ // A UI string to override, and the new value to use.
+ message OverrideUIString {
+ // The first 32 bits of the MD5 hash digest of the resource name to
+ // override.
+ // e.g. Hash("IDS_BOOKMARK_BAR_UNDO")
+ optional fixed32 name_hash = 1;
+
+ // The new value of the string being overridden.
+ // e.g. "Undo"
+ optional string value = 2;
+ }
+ repeated OverrideUIString override_ui_string = 9;
+ }
+
+ // List of experiments in this study. This list should include the default /
+ // control experiment.
+ //
+ // For example, to specify that 99% of users get the default behavior, while
+ // 0.5% of users get experience "A" and 0.5% of users get experience "B",
+ // specify the values below.
+ // Ex: { "default": 990, "A": 5, "B": 5 }
+ repeated Experiment experiment = 9;
+
+ // Possible Chrome release channels.
+ // See: http://dev.chromium.org/getting-involved/dev-channel
+ enum Channel {
+ // UNKNOWN value is defined here for the benefit of code using this enum
+ // type, but is not actually meant to be encoded in the protobuf.
+ UNKNOWN = -1;
+ CANARY = 0;
+ DEV = 1;
+ BETA = 2;
+ STABLE = 3;
+ }
+
+ // Possible Chrome operating system platforms.
+ enum Platform {
+ PLATFORM_WINDOWS = 0;
+ PLATFORM_MAC = 1;
+ PLATFORM_LINUX = 2;
+ PLATFORM_CHROMEOS = 3;
+ PLATFORM_ANDROID = 4;
+ PLATFORM_IOS = 5;
+ }
+
+ // Possible form factors Chrome is running on.
+ enum FormFactor {
+ DESKTOP = 0;
+ PHONE = 1;
+ TABLET = 2;
+ }
+
+ // Filtering criteria specifying whether this study is applicable to a given
+ // Chrome instance.
+ //
+ // Next tag: 12
+ message Filter {
+ // The start date of the study in Unix time format. (Seconds since midnight
+ // January 1, 1970 UTC). See: http://en.wikipedia.org/wiki/Unix_time
+ // Ex: 1330893974 (corresponds to 2012-03-04 20:46:14Z)
+ optional int64 start_date = 1;
+
+ // The minimum Chrome version for this study, allowing a trailing '*'
+ // character for pattern matching. Inclusive. (To check for a match, iterate
+ // over each component checking >= until a * or end of string is reached.)
+ // Optional - if not specified, there is no minimum version.
+ // Ex: "17.0.963.46", "17.0.963.*", "17.*"
+ optional string min_version = 2;
+
+ // The maximum Chrome version for this study; same formatting as
+ // |min_version| above. Inclusive. (To check for a match, iterate over each
+ // component checking <= until a * or end of string is reached.)
+ // Optional - if not specified, there is no maximum version.
+ // Ex: "19.*"
+ optional string max_version = 3;
+
+ // List of channels that will receive this study. If omitted, the study
+ // applies to all channels.
+ // Ex: [BETA, STABLE]
+ repeated Channel channel = 4;
+
+ // List of platforms that will receive this study. If omitted, the study
+ // applies to all platforms.
+ // Ex: [PLATFORM_WINDOWS, PLATFORM_MAC]
+ repeated Platform platform = 5;
+
+ // List of locales that will receive this study. If omitted, the study
+ // applies to all locales.
+ // Ex: ["en-US", "en-CA"]
+ repeated string locale = 6;
+
+ // List of form factors that will receive this study. If omitted, the study
+ // applies to all form factors.
+ // Ex: [PHONE, TABLET]
+ repeated FormFactor form_factor = 7;
+
+ // List of ChromeOS hardware classes that will receive this study. Each
+ // entry is treated as a substring of the actual device hardware_class,
+ // so "FOO" will match the client's hardware class "Device FOOBAR". If
+ // omitted, the study applies to all hardware classes unless
+ // |exclude_hardware_class| is specified. Mutually exclusive with
+ // |exclude_hardware_class|.
+ // An example might be "lumpy", "daisy", etc.
+ repeated string hardware_class = 8;
+
+ // List of ChromeOS hardware classes that will be excluded in this
+ // study. Each entry is treated as a substring of the actual device
+ // hardware_class, so "FOO" will match the client's hardware class
+ // "Device FOOBAR". If omitted, the study applies to all hardware classes
+ // unless |hardware_class| is specified. Mutually exclusive with
+ // |hardware_class|.
+ // An example might be "lumpy", "daisy", etc.
+ repeated string exclude_hardware_class = 9;
+
+ // List of lowercase ISO 3166-1 alpha-2 country codes that will receive this
+ // study. If omitted, the study applies to all countries unless
+ // |exclude_country| is specified. Mutually exclusive with
+ // |exclude_country|.
+ // Ex: ["in", "us"]
+ repeated string country = 10;
+
+ // List of lowercase ISO 3166-1 alpha-2 country codes that will be excluded
+ // in this study. If omitted, the study applies to all countries unless
+ // |country| is specified. Mutually exclusive with |country|.
+ // Ex: ["in", "us"]
+ repeated string exclude_country = 11;
+ }
+
+ // Filtering criteria for this study. A study that is filtered out for a given
+ // client is equivalent to that study not being sent at all.
+ optional Filter filter = 10;
+
+ // Randomization seed to be used when |consistency| is set to PERMANENT. If
+ // not specified, randomization will be done using the trial name.
+ optional uint32 randomization_seed = 11;
+
+ // Specifies whether the study starts as active initially, or whether it
+ // requires the client to query its state before it is marked as active.
+ enum ActivationType {
+ // The study will be activated when its state is queried by the client.
+ // This is recommended for most studies that include client code.
+ ACTIVATION_EXPLICIT = 0;
+ // The study will be automatically activated when it is created. This
+ // is recommended for studies that do not have any client logic.
+ ACTIVATION_AUTO = 1;
+ }
+
+ // Activation type for this study. Defaults to ACTIVATION_EXPLICIT if omitted.
+ optional ActivationType activation_type = 12;
+}
diff --git a/chromium/components/variations/proto/variations_seed.proto b/chromium/components/variations/proto/variations_seed.proto
new file mode 100644
index 00000000000..cfb58fed669
--- /dev/null
+++ b/chromium/components/variations/proto/variations_seed.proto
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package variations;
+
+import "study.proto";
+
+// The VariationsSeed is a protobuf response from the server that contains the
+// list of studies and a serial number to uniquely identify its contents. The
+// serial number allows the client to easily determine if the list of
+// experiments has changed from the previous VariationsSeed seen by the client.
+//
+// Next tag: 4
+message VariationsSeed {
+ optional string serial_number = 1;
+ repeated Study study = 2;
+
+ // Lowercase ISO 3166-1 alpha-2 country code of the client, according to IP
+ // address.
+ optional string country_code = 3;
+}
diff --git a/chromium/components/variations/service/BUILD.gn b/chromium/components/variations/service/BUILD.gn
new file mode 100644
index 00000000000..099be5c2884
--- /dev/null
+++ b/chromium/components/variations/service/BUILD.gn
@@ -0,0 +1,49 @@
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("service") {
+ sources = [
+ "ui_string_overrider.cc",
+ "ui_string_overrider.h",
+ "variations_service.cc",
+ "variations_service.h",
+ "variations_service_client.h",
+ ]
+
+ deps = [
+ "//base",
+ "//components/data_use_measurement/core",
+ "//components/metrics",
+ "//components/network_time",
+ "//components/pref_registry",
+ "//components/prefs",
+ "//components/variations",
+ "//components/variations/proto",
+ "//components/version_info",
+ "//components/web_resource",
+ "//net",
+ "//ui/base",
+ ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "ui_string_overrider_unittest.cc",
+ "variations_service_unittest.cc",
+ ]
+
+ deps = [
+ ":service",
+ "//base",
+ "//base/test:test_support",
+ "//components/prefs:test_support",
+ "//components/variations",
+ "//components/variations/proto",
+ "//components/web_resource:test_support",
+ "//net",
+ "//net:test_support",
+ "//testing/gtest",
+ ]
+}
diff --git a/chromium/components/variations/service/DEPS b/chromium/components/variations/service/DEPS
new file mode 100644
index 00000000000..54dcc1e8db8
--- /dev/null
+++ b/chromium/components/variations/service/DEPS
@@ -0,0 +1,10 @@
+include_rules = [
+ "+components/data_use_measurement/core",
+ "+components/metrics",
+ "+components/network_time",
+ "+components/pref_registry",
+ "+components/version_info",
+ "+components/web_resource",
+ "+net",
+ "+ui/base",
+]
diff --git a/chromium/components/variations/service/generate_ui_string_overrider.gni b/chromium/components/variations/service/generate_ui_string_overrider.gni
new file mode 100644
index 00000000000..14497f9ae44
--- /dev/null
+++ b/chromium/components/variations/service/generate_ui_string_overrider.gni
@@ -0,0 +1,71 @@
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Runs the resources map generation script other the given header files to
+# produce an output file and a source_set to build it.
+#
+# Parameters:
+# inputs:
+# List of file name to read. Each file should be an header file generated
+# by grit with line like "#define IDS_FOO 12345".
+#
+# namespace (optional):
+# Namespace in which the generated code should be scoped. If left empty,
+# the code will be in the global namespace.
+#
+# header_filename:
+# Name of the generated header file.
+#
+# source_filename:
+# Name of the generated source file.
+#
+# deps (optional):
+# List of targets to depend on.
+#
+template("generate_ui_string_overrider") {
+ # Copy "target_name" to allow restrict the visibility of the generation
+ # target to that target (as ":$target_name" will have a different meaning
+ # in the "action" block).
+ source_set_target_name = target_name
+ gen_action_target_name = target_name + "_gen_sources"
+
+ action(gen_action_target_name) {
+ header_filename = "$target_gen_dir/" + invoker.header_filename
+ source_filename = "$target_gen_dir/" + invoker.source_filename
+
+ visibility = [ ":$source_set_target_name" ]
+ script = "//components/variations/service/generate_ui_string_overrider.py"
+ outputs = [
+ header_filename,
+ source_filename,
+ ]
+
+ inputs = invoker.inputs
+ if (defined(invoker.deps)) {
+ deps = invoker.deps
+ }
+
+ args = []
+
+ if (defined(invoker.namespace) && invoker.namespace != "") {
+ args += [ "-N" + invoker.namespace ]
+ }
+
+ args += [
+ "-o" + rebase_path(root_gen_dir, root_build_dir),
+ "-H" + rebase_path(header_filename, root_gen_dir),
+ "-S" + rebase_path(source_filename, root_gen_dir),
+ ] + rebase_path(inputs, root_build_dir)
+ }
+
+ source_set(target_name) {
+ sources = get_target_outputs(":$gen_action_target_name")
+ deps = [
+ ":$gen_action_target_name",
+ "//components/variations/service",
+ ]
+
+ forward_variables_from(invoker, [ "visibility" ])
+ }
+}
diff --git a/chromium/components/variations/service/generate_ui_string_overrider.py b/chromium/components/variations/service/generate_ui_string_overrider.py
new file mode 100755
index 00000000000..b39a03e27c8
--- /dev/null
+++ b/chromium/components/variations/service/generate_ui_string_overrider.py
@@ -0,0 +1,319 @@
+#!/usr/bin/python
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import argparse
+import collections
+import hashlib
+import operator
+import os
+import re
+import sys
+
+SCRIPT_NAME = "generate_ui_string_overrider.py"
+
+
+# Regular expressions for parsing the #define macro format. Separate regular
+# expressions are used for parsing lines with pragma (for builds with
+# enable_resource_whitelist_generation flag) in windows and non-windows, and for
+# lines without pragma, For example,
+# Without generate whitelist flag:
+# #define IDS_FOO_MESSAGE 1234
+# With generate whitelist flag in non-windows:
+# #define IDS_FOO_MESSAGE _Pragma("whitelisted_resource_1234") 1234
+# With generate whitelist flag in windows:
+# #define IDS_FOO_MESSAGE __pragma(message("whitelisted_resource_1234")) 1234
+RESOURCE_EXTRACT_REGEX = re.compile('^#define (\S*) (\d*)$', re.MULTILINE)
+RESOURCE_EXTRACT_REGEX_PRAGMA = re.compile(
+ '^#define (\S*) _Pragma\("whitelisted_resource_\d*"\) (\d*)$',
+ re.MULTILINE)
+RESOURCE_EXTRACT_REGEX_PRAGMA_WINDOWS = re.compile(
+ '^#define (\S*) __pragma\(message\("whitelisted_resource_\d*"\)\) (\d*)$',
+ re.MULTILINE)
+
+class Error(Exception):
+ """Base error class for all exceptions in generated_resources_map."""
+
+
+class HashCollisionError(Error):
+ """Multiple resource names hash to the same value."""
+
+
+Resource = collections.namedtuple("Resource", ['hash', 'name', 'index'])
+
+
+def _HashName(name):
+ """Returns the hash id for a name.
+
+ Args:
+ name: The name to hash.
+
+ Returns:
+ An int that is at most 32 bits.
+ """
+ md5hash = hashlib.md5()
+ md5hash.update(name)
+ return int(md5hash.hexdigest()[:8], 16)
+
+
+def _GetNameIndexPairsIter(string_to_scan):
+ """Gets an iterator of the resource name and index pairs of the given string.
+
+ Scans the input string for lines of the form "#define NAME INDEX" and returns
+ an iterator over all matching (NAME, INDEX) pairs.
+
+ Args:
+ string_to_scan: The input string to scan.
+
+ Yields:
+ A tuple of name and index.
+ """
+ for match in RESOURCE_EXTRACT_REGEX.finditer(string_to_scan):
+ yield match.group(1, 2)
+ for match in RESOURCE_EXTRACT_REGEX_PRAGMA.finditer(string_to_scan):
+ yield match.group(1, 2)
+ for match in RESOURCE_EXTRACT_REGEX_PRAGMA_WINDOWS.finditer(string_to_scan):
+ yield match.group(1, 2)
+
+
+def _GetResourceListFromString(resources_content):
+ """Produces a list of |Resource| objects from a string.
+
+ The input string contains lines of the form "#define NAME INDEX". The returned
+ list is sorted primarily by hash, then name, and then index.
+
+ Args:
+ resources_content: The input string to process, contains lines of the form
+ "#define NAME INDEX".
+
+ Returns:
+ A sorted list of |Resource| objects.
+ """
+ resources = [Resource(_HashName(name), name, index) for name, index in
+ _GetNameIndexPairsIter(resources_content)]
+
+ # Deduplicate resources. Some name-index pairs appear in both chromium_ and
+ # google_chrome_ header files. Unless deduplicated here, collisions will be
+ # raised in _CheckForHashCollisions.
+ resources = list(set(resources))
+
+ # The default |Resource| order makes |resources| sorted by the hash, then
+ # name, then index.
+ resources.sort()
+
+ return resources
+
+
+def _CheckForHashCollisions(sorted_resource_list):
+ """Checks a sorted list of |Resource| objects for hash collisions.
+
+ Args:
+ sorted_resource_list: A sorted list of |Resource| objects.
+
+ Returns:
+ A set of all |Resource| objects with collisions.
+ """
+ collisions = set()
+ for i in xrange(len(sorted_resource_list) - 1):
+ resource = sorted_resource_list[i]
+ next_resource = sorted_resource_list[i+1]
+ if resource.hash == next_resource.hash:
+ collisions.add(resource)
+ collisions.add(next_resource)
+
+ return collisions
+
+
+def _GenDataArray(
+ resources, entry_pattern, array_name, array_type, data_getter):
+ """Generates a C++ statement defining a literal array containing the hashes.
+
+ Args:
+ resources: A sorted list of |Resource| objects.
+ entry_pattern: A pattern to be used to generate each entry in the array. The
+ pattern is expected to have a place for data and one for a comment, in
+ that order.
+ array_name: The name of the array being generated.
+ array_type: The type of the array being generated.
+ data_getter: A function that gets the array data from a |Resource| object.
+
+ Returns:
+ A string containing a C++ statement defining the an array.
+ """
+ lines = [entry_pattern % (data_getter(r), r.name) for r in resources]
+ pattern = """const %(type)s %(name)s[] = {
+%(content)s
+};
+"""
+ return pattern % {'type': array_type,
+ 'name': array_name,
+ 'content': '\n'.join(lines)}
+
+
+def _GenerateNamespacePrefixAndSuffix(namespace):
+ """Generates the namespace prefix and suffix for |namespace|.
+
+ Args:
+ namespace: A string corresponding to the namespace name. May be empty.
+
+ Returns:
+ A tuple of strings corresponding to the namespace prefix and suffix for
+ putting the code in the corresponding namespace in C++. If namespace is
+ the empty string, both returned strings are empty too.
+ """
+ if not namespace:
+ return "", ""
+ return "namespace %s {\n\n" % namespace, "\n} // namespace %s\n" % namespace
+
+
+def _GenerateSourceFileContent(resources_content, namespace, header_filename):
+ """Generates the .cc content from the given generated grit headers content.
+
+ Args:
+ resources_content: The input string to process, contains lines of the form
+ "#define NAME INDEX".
+
+ namespace: The namespace in which the generated code should be scoped. If
+ not defined, then the code will be in the global namespace.
+
+ header_filename: Path to the corresponding .h.
+
+ Returns:
+ .cc file content implementing the CreateUIStringOverrider() factory.
+ """
+ hashed_tuples = _GetResourceListFromString(resources_content)
+
+ collisions = _CheckForHashCollisions(hashed_tuples)
+ if collisions:
+ error_message = "\n".join(
+ ["hash: %i, name: %s" % (i.hash, i.name) for i in sorted(collisions)])
+ error_message = ("\nThe following names had hash collisions "
+ "(sorted by the hash value):\n%s\n" %(error_message))
+ raise HashCollisionError(error_message)
+
+ hashes_array = _GenDataArray(
+ hashed_tuples, " %iU, // %s", 'kResourceHashes', 'uint32_t',
+ operator.attrgetter('hash'))
+ indices_array = _GenDataArray(
+ hashed_tuples, " %s, // %s", 'kResourceIndices', 'int',
+ operator.attrgetter('index'))
+
+ namespace_prefix, namespace_suffix = _GenerateNamespacePrefixAndSuffix(
+ namespace)
+
+ return (
+ "// This file was generated by %(script_name)s. Do not edit.\n"
+ "\n"
+ "#include \"%(header_filename)s\"\n\n"
+ "%(namespace_prefix)s"
+ "namespace {\n\n"
+ "const size_t kNumResources = %(num_resources)i;\n\n"
+ "%(hashes_array)s"
+ "\n"
+ "%(indices_array)s"
+ "\n"
+ "} // namespace\n"
+ "\n"
+ "variations::UIStringOverrider CreateUIStringOverrider() {\n"
+ " return variations::UIStringOverrider(\n"
+ " kResourceHashes, kResourceIndices, kNumResources);\n"
+ "}\n"
+ "%(namespace_suffix)s") % {
+ 'script_name': SCRIPT_NAME,
+ 'header_filename': header_filename,
+ 'namespace_prefix': namespace_prefix,
+ 'num_resources': len(hashed_tuples),
+ 'hashes_array': hashes_array,
+ 'indices_array': indices_array,
+ 'namespace_suffix': namespace_suffix,
+ }
+
+
+def _GenerateHeaderFileContent(namespace, header_filename):
+ """Generates the .h for to the .cc generated by _GenerateSourceFileContent.
+
+ Args:
+ namespace: The namespace in which the generated code should be scoped. If
+ not defined, then the code will be in the global namespace.
+
+ header_filename: Path to the corresponding .h. Used to generate the include
+ guards.
+
+ Returns:
+ .cc file content implementing the CreateUIStringOverrider() factory.
+ """
+
+ include_guard = re.sub('[^A-Z]', '_', header_filename.upper()) + '_'
+ namespace_prefix, namespace_suffix = _GenerateNamespacePrefixAndSuffix(
+ namespace)
+
+ return (
+ "// This file was generated by %(script_name)s. Do not edit.\n"
+ "\n"
+ "#ifndef %(include_guard)s\n"
+ "#define %(include_guard)s\n"
+ "\n"
+ "#include \"components/variations/service/ui_string_overrider.h\"\n\n"
+ "%(namespace_prefix)s"
+ "// Returns an initialized UIStringOverrider.\n"
+ "variations::UIStringOverrider CreateUIStringOverrider();\n"
+ "%(namespace_suffix)s"
+ "\n"
+ "#endif // %(include_guard)s\n"
+ ) % {
+ 'script_name': SCRIPT_NAME,
+ 'include_guard': include_guard,
+ 'namespace_prefix': namespace_prefix,
+ 'namespace_suffix': namespace_suffix,
+ }
+
+
+def main():
+ arg_parser = argparse.ArgumentParser(
+ description="Generate UIStringOverrider factory from resources headers "
+ "generated by grit.")
+ arg_parser.add_argument(
+ "--output_dir", "-o", required=True,
+ help="Base directory to for generated files.")
+ arg_parser.add_argument(
+ "--source_filename", "-S", required=True,
+ help="File name of the generated source file.")
+ arg_parser.add_argument(
+ "--header_filename", "-H", required=True,
+ help="File name of the generated header file.")
+ arg_parser.add_argument(
+ "--namespace", "-N", default="",
+ help="Namespace of the generated factory function (code will be in "
+ "the global namespace if this is omitted).")
+ arg_parser.add_argument(
+ "--test_support", "-t", action="store_true", default=False,
+ help="Make internal variables accessible for testing.")
+ arg_parser.add_argument(
+ "inputs", metavar="FILENAME", nargs="+",
+ help="Path to resources header file generated by grit.")
+ arguments = arg_parser.parse_args()
+
+ generated_resources_h = ""
+ for resources_file in arguments.inputs:
+ with open(resources_file, "r") as resources:
+ generated_resources_h += resources.read()
+
+ if len(generated_resources_h) == 0:
+ raise Error("No content loaded for %s." % (resources_file))
+
+ source_file_content = _GenerateSourceFileContent(
+ generated_resources_h, arguments.namespace, arguments.header_filename)
+ header_file_content = _GenerateHeaderFileContent(
+ arguments.namespace, arguments.header_filename)
+
+ with open(os.path.join(
+ arguments.output_dir, arguments.source_filename), "w") as generated_file:
+ generated_file.write(source_file_content)
+ with open(os.path.join(
+ arguments.output_dir, arguments.header_filename), "w") as generated_file:
+ generated_file.write(header_file_content)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/chromium/components/variations/service/generate_ui_string_overrider_unittest.py b/chromium/components/variations/service/generate_ui_string_overrider_unittest.py
new file mode 100755
index 00000000000..55d55f33ca0
--- /dev/null
+++ b/chromium/components/variations/service/generate_ui_string_overrider_unittest.py
@@ -0,0 +1,132 @@
+#!/usr/bin/python
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Unittests for generate_ui_string_overrider.py"""
+
+import unittest
+
+import generate_ui_string_overrider
+
+
+class GenerateResourcesMapUnittest(unittest.TestCase):
+ NAMESPACE = "chrome_variations"
+ OUT_HEADER = "components/variations/service/ui_string_overrider_factory.h"
+ TEST_INPUT = """
+// This file is automatically generated by GRIT. Do not edit.
+
+#pragma once
+
+#define IDS_BOOKMARKS_NO_ITEMS 12500
+#define IDS_BOOKMARK_BAR_IMPORT_LINK 12501
+#define IDS_BOOKMARK_GROUP_FROM_IE 12502
+#define IDS_BOOKMARK_GROUP_FROM_FIREFOX 12503
+"""
+
+ def testGetResourceListFromString(self):
+ expected_tuples = [(301430091, "IDS_BOOKMARKS_NO_ITEMS", "12500"),
+ (2654138887, "IDS_BOOKMARK_BAR_IMPORT_LINK", "12501"),
+ (2894469061, "IDS_BOOKMARK_GROUP_FROM_IE", "12502"),
+ (3847176170, "IDS_BOOKMARK_GROUP_FROM_FIREFOX", "12503")]
+ expected = [
+ generate_ui_string_overrider.Resource(*t) for t in expected_tuples]
+
+ actual_tuples = generate_ui_string_overrider._GetResourceListFromString(
+ self.TEST_INPUT)
+
+ self.assertEqual(expected_tuples, actual_tuples)
+
+
+ def testCheckForHashCollisions(self):
+ collisions_tuples = [(123, "IDS_FOO", "12500"),
+ (456, "IDS_BAR", "12501"),
+ (456, "IDS_BAZ", "12502"),
+ (890, "IDS_QUX", "12503"),
+ (899, "IDS_NO", "12504"),
+ (899, "IDS_YES", "12505")]
+ list_with_collisions = [generate_ui_string_overrider.Resource(*t)
+ for t in collisions_tuples]
+
+ expected_collision_tuples = [(456, "IDS_BAR", "12501"),
+ (456, "IDS_BAZ", "12502"),
+ (899, "IDS_NO", "12504"),
+ (899, "IDS_YES", "12505")]
+ expected_collisions = [generate_ui_string_overrider.Resource(*t)
+ for t in expected_collision_tuples]
+
+ actual_collisions = sorted(
+ generate_ui_string_overrider._CheckForHashCollisions(
+ list_with_collisions))
+ actual_collisions
+
+ self.assertEqual(expected_collisions, actual_collisions)
+
+ def testGenerateSourceFileContent(self):
+ expected = (
+ """\
+// This file was generated by generate_ui_string_overrider.py. Do not edit.
+
+#include "components/variations/service/ui_string_overrider_factory.h"
+
+namespace chrome_variations {
+
+namespace {
+
+const size_t kNumResources = 4;
+
+const uint32_t kResourceHashes[] = {
+ 301430091U, // IDS_BOOKMARKS_NO_ITEMS
+ 2654138887U, // IDS_BOOKMARK_BAR_IMPORT_LINK
+ 2894469061U, // IDS_BOOKMARK_GROUP_FROM_IE
+ 3847176170U, // IDS_BOOKMARK_GROUP_FROM_FIREFOX
+};
+
+const int kResourceIndices[] = {
+ 12500, // IDS_BOOKMARKS_NO_ITEMS
+ 12501, // IDS_BOOKMARK_BAR_IMPORT_LINK
+ 12502, // IDS_BOOKMARK_GROUP_FROM_IE
+ 12503, // IDS_BOOKMARK_GROUP_FROM_FIREFOX
+};
+
+} // namespace
+
+variations::UIStringOverrider CreateUIStringOverrider() {
+ return variations::UIStringOverrider(
+ kResourceHashes, kResourceIndices, kNumResources);
+}
+
+} // namespace chrome_variations
+""")
+ actual = generate_ui_string_overrider._GenerateSourceFileContent(
+ self.TEST_INPUT, self.NAMESPACE, self.OUT_HEADER)
+
+ self.assertEqual(expected, actual)
+
+
+ def testGenerateHeaderFileContent(self):
+ expected = (
+ """\
+// This file was generated by generate_ui_string_overrider.py. Do not edit.
+
+#ifndef COMPONENTS_VARIATIONS_SERVICE_UI_STRING_OVERRIDER_FACTORY_H_
+#define COMPONENTS_VARIATIONS_SERVICE_UI_STRING_OVERRIDER_FACTORY_H_
+
+#include "components/variations/service/ui_string_overrider.h"
+
+namespace chrome_variations {
+
+// Returns an initialized UIStringOverrider.
+variations::UIStringOverrider CreateUIStringOverrider();
+
+} // namespace chrome_variations
+
+#endif // COMPONENTS_VARIATIONS_SERVICE_UI_STRING_OVERRIDER_FACTORY_H_
+""")
+ actual = generate_ui_string_overrider._GenerateHeaderFileContent(
+ self.NAMESPACE, self.OUT_HEADER)
+
+ self.assertEqual(expected, actual)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/components/variations/service/ui_string_overrider.cc b/chromium/components/variations/service/ui_string_overrider.cc
new file mode 100644
index 00000000000..ec573f521f8
--- /dev/null
+++ b/chromium/components/variations/service/ui_string_overrider.cc
@@ -0,0 +1,51 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/service/ui_string_overrider.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace variations {
+
+UIStringOverrider::UIStringOverrider()
+ : resource_hashes_(nullptr),
+ resource_indices_(nullptr),
+ num_resources_(0) {}
+
+UIStringOverrider::UIStringOverrider(const uint32_t* resource_hashes,
+ const int* resource_indices,
+ size_t num_resources)
+ : resource_hashes_(resource_hashes),
+ resource_indices_(resource_indices),
+ num_resources_(num_resources) {
+ DCHECK(!num_resources || resource_hashes_);
+ DCHECK(!num_resources || resource_indices_);
+}
+
+UIStringOverrider::~UIStringOverrider() {}
+
+int UIStringOverrider::GetResourceIndex(uint32_t hash) {
+ if (!num_resources_)
+ return -1;
+ const uint32_t* end = resource_hashes_ + num_resources_;
+ const uint32_t* element = std::lower_bound(resource_hashes_, end, hash);
+ if (element == end || *element != hash)
+ return -1;
+ return resource_indices_[element - resource_hashes_];
+}
+
+void UIStringOverrider::OverrideUIString(uint32_t hash,
+ const base::string16& string) {
+ int resource_id = GetResourceIndex(hash);
+ if (resource_id == -1)
+ return;
+
+ ui::ResourceBundle::GetSharedInstance().OverrideLocaleStringResource(
+ resource_id, string);
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/service/ui_string_overrider.h b/chromium/components/variations/service/ui_string_overrider.h
new file mode 100644
index 00000000000..c5baf8d9939
--- /dev/null
+++ b/chromium/components/variations/service/ui_string_overrider.h
@@ -0,0 +1,59 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_SERVICE_UI_STRING_OVERRIDER_H_
+#define COMPONENTS_VARIATIONS_SERVICE_UI_STRING_OVERRIDER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+
+namespace variations {
+
+// Provides a mapping from hashes of generated resource names to their IDs. The
+// mapping is provided by the embedder as two arrays |resource_hashes|, a sorted
+// array of resource name hashes, and |resource_indices| an array of resource
+// indices in the same order.
+//
+// The mapping is created by generate_ui_string_overrider.py based on generated
+// resources header files. The script ensure that if one header file contains
+// |#define IDS_FOO 12345| then for some index |i|, |resource_hashes[i]| will
+// be equal to |HASH("IDS_FOO")| and |resource_indices[i]| will be equal to
+// |12345|.
+//
+// Both array must have the same length |num_resources|. They are not owned by
+// the UIStringOverrider and the embedder is responsible for their lifetime
+// (usually by passing pointer to static data).
+//
+// This class is copy-constructible by design as it does not owns the array
+// and only have reference to globally allocated constants.
+class UIStringOverrider {
+ public:
+ UIStringOverrider();
+ UIStringOverrider(const uint32_t* resource_hashes,
+ const int* resource_indices,
+ size_t num_resources);
+ ~UIStringOverrider();
+
+ // Returns the resource index corresponding to the given hash or -1 if no
+ // resources is found. Visible for testing.
+ int GetResourceIndex(uint32_t hash);
+
+ // Overrides the string resource specified by |hash| with |string| in the
+ // resource bundle.
+ void OverrideUIString(uint32_t hash, const base::string16& string);
+
+ private:
+ const uint32_t* const resource_hashes_;
+ const int* const resource_indices_;
+ size_t const num_resources_;
+
+ DISALLOW_ASSIGN(UIStringOverrider);
+};
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_SERVICE_UI_STRING_OVERRIDER_H_
diff --git a/chromium/components/variations/service/ui_string_overrider_unittest.cc b/chromium/components/variations/service/ui_string_overrider_unittest.cc
new file mode 100644
index 00000000000..0f4ab527528
--- /dev/null
+++ b/chromium/components/variations/service/ui_string_overrider_unittest.cc
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/service/ui_string_overrider.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome_variations {
+
+namespace {
+
+const size_t kNumResources = 4;
+
+const uint32_t kResourceHashes[] = {
+ 301430091U, // IDS_BOOKMARKS_NO_ITEMS
+ 2654138887U, // IDS_BOOKMARK_BAR_IMPORT_LINK
+ 2894469061U, // IDS_BOOKMARK_GROUP_FROM_IE
+ 3847176170U, // IDS_BOOKMARK_GROUP_FROM_FIREFOX
+};
+
+const int kResourceIndices[] = {
+ 12500, // IDS_BOOKMARKS_NO_ITEMS
+ 12501, // IDS_BOOKMARK_BAR_IMPORT_LINK
+ 12502, // IDS_BOOKMARK_GROUP_FROM_IE
+ 12503, // IDS_BOOKMARK_GROUP_FROM_FIREFOX
+};
+
+} // namespace
+
+class UIStringOverriderTest : public ::testing::Test {
+ public:
+ UIStringOverriderTest()
+ : provider_(kResourceHashes, kResourceIndices, kNumResources) {}
+
+ int GetResourceIndex(uint32_t hash) {
+ return provider_.GetResourceIndex(hash);
+ }
+
+ private:
+ variations::UIStringOverrider provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(UIStringOverriderTest);
+};
+
+TEST_F(UIStringOverriderTest, LookupNotFound) {
+ EXPECT_EQ(-1, GetResourceIndex(0));
+ EXPECT_EQ(-1, GetResourceIndex(kResourceHashes[kNumResources - 1] + 1));
+
+ // Lookup a hash that shouldn't exist.
+ // 3847176171U is 1 + the hash for IDS_BOOKMARK_GROUP_FROM_FIREFOX.
+ EXPECT_EQ(-1, GetResourceIndex(3847176171U));
+}
+
+TEST_F(UIStringOverriderTest, LookupFound) {
+ for (size_t i = 0; i < kNumResources; ++i)
+ EXPECT_EQ(kResourceIndices[i], GetResourceIndex(kResourceHashes[i]));
+}
+
+} // namespace chrome_variations
diff --git a/chromium/components/variations/service/variations_service.cc b/chromium/components/variations/service/variations_service.cc
new file mode 100644
index 00000000000..9e0c4c75134
--- /dev/null
+++ b/chromium/components/variations/service/variations_service.cc
@@ -0,0 +1,865 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/service/variations_service.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <utility>
+
+#include "base/build_time.h"
+#include "base/command_line.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/strings/string_util.h"
+#include "base/sys_info.h"
+#include "base/task_runner_util.h"
+#include "base/timer/elapsed_timer.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "build/build_config.h"
+#include "components/data_use_measurement/core/data_use_user_data.h"
+#include "components/metrics/metrics_state_manager.h"
+#include "components/network_time/network_time_tracker.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/pref_names.h"
+#include "components/variations/proto/variations_seed.pb.h"
+#include "components/variations/variations_seed_processor.h"
+#include "components/variations/variations_seed_simulator.h"
+#include "components/variations/variations_switches.h"
+#include "components/variations/variations_url_constants.h"
+#include "components/version_info/version_info.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/network_change_notifier.h"
+#include "net/base/url_util.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_status_code.h"
+#include "net/http/http_util.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_status.h"
+#include "ui/base/device_form_factor.h"
+#include "url/gurl.h"
+
+namespace variations {
+
+namespace {
+
+// TODO(mad): To be removed when we stop updating the NetworkTimeTracker.
+// For the HTTP date headers, the resolution of the server time is 1 second.
+const int64_t kServerTimeResolutionMs = 1000;
+
+// Maximum age permitted for a variations seed, in days.
+const int kMaxVariationsSeedAgeDays = 30;
+
+// Wrapper around channel checking, used to enable channel mocking for
+// testing. If the current browser channel is not UNKNOWN, this will return
+// that channel value. Otherwise, if the fake channel flag is provided, this
+// will return the fake channel. Failing that, this will return the UNKNOWN
+// channel.
+variations::Study_Channel GetChannelForVariations(
+ version_info::Channel product_channel) {
+ switch (product_channel) {
+ case version_info::Channel::CANARY:
+ return variations::Study_Channel_CANARY;
+ case version_info::Channel::DEV:
+ return variations::Study_Channel_DEV;
+ case version_info::Channel::BETA:
+ return variations::Study_Channel_BETA;
+ case version_info::Channel::STABLE:
+ return variations::Study_Channel_STABLE;
+ case version_info::Channel::UNKNOWN:
+ break;
+ }
+ const std::string forced_channel =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kFakeVariationsChannel);
+ if (forced_channel == "stable")
+ return variations::Study_Channel_STABLE;
+ if (forced_channel == "beta")
+ return variations::Study_Channel_BETA;
+ if (forced_channel == "dev")
+ return variations::Study_Channel_DEV;
+ if (forced_channel == "canary")
+ return variations::Study_Channel_CANARY;
+ DVLOG(1) << "Invalid channel provided: " << forced_channel;
+ return variations::Study_Channel_UNKNOWN;
+}
+
+// Returns a string that will be used for the value of the 'osname' URL param
+// to the variations server.
+std::string GetPlatformString() {
+#if defined(OS_WIN)
+ return "win";
+#elif defined(OS_IOS)
+ return "ios";
+#elif defined(OS_MACOSX)
+ return "mac";
+#elif defined(OS_CHROMEOS)
+ return "chromeos";
+#elif defined(OS_ANDROID)
+ return "android";
+#elif defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
+ // Default BSD and SOLARIS to Linux to not break those builds, although these
+ // platforms are not officially supported by Chrome.
+ return "linux";
+#else
+#error Unknown platform
+#endif
+}
+
+// Gets the restrict parameter from either the client or |policy_pref_service|.
+std::string GetRestrictParameterPref(VariationsServiceClient* client,
+ PrefService* policy_pref_service) {
+ std::string parameter;
+ if (client->OverridesRestrictParameter(&parameter) || !policy_pref_service)
+ return parameter;
+
+ return policy_pref_service->GetString(prefs::kVariationsRestrictParameter);
+}
+
+enum ResourceRequestsAllowedState {
+ RESOURCE_REQUESTS_ALLOWED,
+ RESOURCE_REQUESTS_NOT_ALLOWED,
+ RESOURCE_REQUESTS_ALLOWED_NOTIFIED,
+ RESOURCE_REQUESTS_NOT_ALLOWED_EULA_NOT_ACCEPTED,
+ RESOURCE_REQUESTS_NOT_ALLOWED_NETWORK_DOWN,
+ RESOURCE_REQUESTS_NOT_ALLOWED_COMMAND_LINE_DISABLED,
+ RESOURCE_REQUESTS_ALLOWED_ENUM_SIZE,
+};
+
+// Records UMA histogram with the current resource requests allowed state.
+void RecordRequestsAllowedHistogram(ResourceRequestsAllowedState state) {
+ UMA_HISTOGRAM_ENUMERATION("Variations.ResourceRequestsAllowed", state,
+ RESOURCE_REQUESTS_ALLOWED_ENUM_SIZE);
+}
+
+enum VariationsSeedExpiry {
+ VARIATIONS_SEED_EXPIRY_NOT_EXPIRED,
+ VARIATIONS_SEED_EXPIRY_FETCH_TIME_MISSING,
+ VARIATIONS_SEED_EXPIRY_EXPIRED,
+ VARIATIONS_SEED_EXPIRY_ENUM_SIZE,
+};
+
+// Records UMA histogram with the result of the variations seed expiry check.
+void RecordCreateTrialsSeedExpiry(VariationsSeedExpiry expiry_check_result) {
+ UMA_HISTOGRAM_ENUMERATION("Variations.CreateTrials.SeedExpiry",
+ expiry_check_result,
+ VARIATIONS_SEED_EXPIRY_ENUM_SIZE);
+}
+
+// Converts ResourceRequestAllowedNotifier::State to the corresponding
+// ResourceRequestsAllowedState value.
+ResourceRequestsAllowedState ResourceRequestStateToHistogramValue(
+ web_resource::ResourceRequestAllowedNotifier::State state) {
+ using web_resource::ResourceRequestAllowedNotifier;
+ switch (state) {
+ case ResourceRequestAllowedNotifier::DISALLOWED_EULA_NOT_ACCEPTED:
+ return RESOURCE_REQUESTS_NOT_ALLOWED_EULA_NOT_ACCEPTED;
+ case ResourceRequestAllowedNotifier::DISALLOWED_NETWORK_DOWN:
+ return RESOURCE_REQUESTS_NOT_ALLOWED_NETWORK_DOWN;
+ case ResourceRequestAllowedNotifier::DISALLOWED_COMMAND_LINE_DISABLED:
+ return RESOURCE_REQUESTS_NOT_ALLOWED_COMMAND_LINE_DISABLED;
+ case ResourceRequestAllowedNotifier::ALLOWED:
+ return RESOURCE_REQUESTS_ALLOWED;
+ }
+ NOTREACHED();
+ return RESOURCE_REQUESTS_NOT_ALLOWED;
+}
+
+
+// Gets current form factor and converts it from enum DeviceFormFactor to enum
+// Study_FormFactor.
+variations::Study_FormFactor GetCurrentFormFactor() {
+ switch (ui::GetDeviceFormFactor()) {
+ case ui::DEVICE_FORM_FACTOR_PHONE:
+ return variations::Study_FormFactor_PHONE;
+ case ui::DEVICE_FORM_FACTOR_TABLET:
+ return variations::Study_FormFactor_TABLET;
+ case ui::DEVICE_FORM_FACTOR_DESKTOP:
+ return variations::Study_FormFactor_DESKTOP;
+ }
+ NOTREACHED();
+ return variations::Study_FormFactor_DESKTOP;
+}
+
+// Gets the hardware class and returns it as a string. This returns an empty
+// string if the client is not ChromeOS.
+std::string GetHardwareClass() {
+#if defined(OS_CHROMEOS)
+ return base::SysInfo::GetLsbReleaseBoard();
+#endif // OS_CHROMEOS
+ return std::string();
+}
+
+// Returns the date that should be used by the VariationsSeedProcessor to do
+// expiry and start date checks.
+base::Time GetReferenceDateForExpiryChecks(PrefService* local_state) {
+ const int64_t date_value = local_state->GetInt64(prefs::kVariationsSeedDate);
+ const base::Time seed_date = base::Time::FromInternalValue(date_value);
+ const base::Time build_time = base::GetBuildTime();
+ // Use the build time for date checks if either the seed date is invalid or
+ // the build time is newer than the seed date.
+ base::Time reference_date = seed_date;
+ if (seed_date.is_null() || seed_date < build_time)
+ reference_date = build_time;
+ return reference_date;
+}
+
+// Returns the header value for |name| from |headers| or an empty string if not
+// set.
+std::string GetHeaderValue(const net::HttpResponseHeaders* headers,
+ const base::StringPiece& name) {
+ std::string value;
+ headers->EnumerateHeader(nullptr, name, &value);
+ return value;
+}
+
+// Returns the list of values for |name| from |headers|. If the header in not
+// set, return an empty list.
+std::vector<std::string> GetHeaderValuesList(
+ const net::HttpResponseHeaders* headers,
+ const base::StringPiece& name) {
+ std::vector<std::string> values;
+ size_t iter = 0;
+ std::string value;
+ while (headers->EnumerateHeader(&iter, name, &value)) {
+ values.push_back(value);
+ }
+ return values;
+}
+
+// Looks for delta and gzip compression instance manipulation flags set by the
+// server in |headers|. Checks the order of flags and presence of unknown
+// instance manipulations. If successful, |is_delta_compressed| and
+// |is_gzip_compressed| contain compression flags and true is returned.
+bool GetInstanceManipulations(const net::HttpResponseHeaders* headers,
+ bool* is_delta_compressed,
+ bool* is_gzip_compressed) {
+ std::vector<std::string> ims = GetHeaderValuesList(headers, "IM");
+ const auto delta_im = std::find(ims.begin(), ims.end(), "x-bm");
+ const auto gzip_im = std::find(ims.begin(), ims.end(), "gzip");
+ *is_delta_compressed = delta_im != ims.end();
+ *is_gzip_compressed = gzip_im != ims.end();
+
+ // The IM field should not have anything but x-bm and gzip.
+ size_t im_count = (*is_delta_compressed ? 1 : 0) +
+ (*is_gzip_compressed ? 1 : 0);
+ if (im_count != ims.size()) {
+ DVLOG(1) << "Unrecognized instance manipulations in "
+ << base::JoinString(ims, ",")
+ << "; only x-bm and gzip are supported";
+ return false;
+ }
+
+ // The IM field defines order in which instance manipulations were applied.
+ // The client requests and supports gzip-compressed delta-compressed seeds,
+ // but not vice versa.
+ if (*is_delta_compressed && *is_gzip_compressed && delta_im > gzip_im) {
+ DVLOG(1) << "Unsupported instance manipulations order: "
+ << "requested x-bm,gzip but received gzip,x-bm";
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+VariationsService::VariationsService(
+ scoped_ptr<VariationsServiceClient> client,
+ scoped_ptr<web_resource::ResourceRequestAllowedNotifier> notifier,
+ PrefService* local_state,
+ metrics::MetricsStateManager* state_manager,
+ const UIStringOverrider& ui_string_overrider)
+ : client_(std::move(client)),
+ ui_string_overrider_(ui_string_overrider),
+ local_state_(local_state),
+ state_manager_(state_manager),
+ policy_pref_service_(local_state),
+ seed_store_(local_state),
+ create_trials_from_seed_called_(false),
+ initial_request_completed_(false),
+ disable_deltas_for_next_request_(false),
+ resource_request_allowed_notifier_(std::move(notifier)),
+ request_count_(0),
+ weak_ptr_factory_(this) {
+ DCHECK(client_.get());
+ DCHECK(resource_request_allowed_notifier_.get());
+
+ resource_request_allowed_notifier_->Init(this);
+}
+
+VariationsService::~VariationsService() {
+}
+
+bool VariationsService::CreateTrialsFromSeed(base::FeatureList* feature_list) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ CHECK(!create_trials_from_seed_called_);
+
+ create_trials_from_seed_called_ = true;
+
+ variations::VariationsSeed seed;
+ if (!LoadSeed(&seed))
+ return false;
+
+ const int64_t last_fetch_time_internal =
+ local_state_->GetInt64(prefs::kVariationsLastFetchTime);
+ const base::Time last_fetch_time =
+ base::Time::FromInternalValue(last_fetch_time_internal);
+ if (last_fetch_time.is_null()) {
+ // If the last fetch time is missing and we have a seed, then this must be
+ // the first run of Chrome. Store the current time as the last fetch time.
+ RecordLastFetchTime();
+ RecordCreateTrialsSeedExpiry(VARIATIONS_SEED_EXPIRY_FETCH_TIME_MISSING);
+ } else {
+ // Reject the seed if it is more than 30 days old.
+ const base::TimeDelta seed_age = base::Time::Now() - last_fetch_time;
+ if (seed_age.InDays() > kMaxVariationsSeedAgeDays) {
+ RecordCreateTrialsSeedExpiry(VARIATIONS_SEED_EXPIRY_EXPIRED);
+ return false;
+ }
+ RecordCreateTrialsSeedExpiry(VARIATIONS_SEED_EXPIRY_NOT_EXPIRED);
+ }
+
+ const base::Version current_version(version_info::GetVersionNumber());
+ if (!current_version.IsValid())
+ return false;
+
+ variations::Study_Channel channel =
+ GetChannelForVariations(client_->GetChannel());
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Variations.UserChannel", channel);
+
+ const std::string latest_country =
+ local_state_->GetString(prefs::kVariationsCountry);
+ // Note that passing |&ui_string_overrider_| via base::Unretained below is
+ // safe because the callback is executed synchronously. It is not possible
+ // to pass UIStringOverrider itself to VariationSeedProcesor as variations
+ // components should not depends on //ui/base.
+ variations::VariationsSeedProcessor().CreateTrialsFromSeed(
+ seed, client_->GetApplicationLocale(),
+ GetReferenceDateForExpiryChecks(local_state_), current_version, channel,
+ GetCurrentFormFactor(), GetHardwareClass(), latest_country,
+ LoadPermanentConsistencyCountry(current_version, latest_country),
+ base::Bind(&UIStringOverrider::OverrideUIString,
+ base::Unretained(&ui_string_overrider_)),
+ feature_list);
+
+ const base::Time now = base::Time::Now();
+
+ // Log the "freshness" of the seed that was just used. The freshness is the
+ // time between the last successful seed download and now.
+ if (last_fetch_time_internal) {
+ const base::TimeDelta delta =
+ now - base::Time::FromInternalValue(last_fetch_time_internal);
+ // Log the value in number of minutes.
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Variations.SeedFreshness", delta.InMinutes(),
+ 1, base::TimeDelta::FromDays(30).InMinutes(), 50);
+ }
+
+ return true;
+}
+
+void VariationsService::PerformPreMainMessageLoopStartup() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ StartRepeatedVariationsSeedFetch();
+ client_->OnInitialStartup();
+}
+
+void VariationsService::StartRepeatedVariationsSeedFetch() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Initialize the Variations server URL.
+ variations_server_url_ =
+ GetVariationsServerURL(policy_pref_service_, restrict_mode_);
+
+ // Check that |CreateTrialsFromSeed| was called, which is necessary to
+ // retrieve the serial number that will be sent to the server.
+ DCHECK(create_trials_from_seed_called_);
+
+ DCHECK(!request_scheduler_.get());
+ // Note that the act of instantiating the scheduler will start the fetch, if
+ // the scheduler deems appropriate.
+ request_scheduler_.reset(VariationsRequestScheduler::Create(
+ base::Bind(&VariationsService::FetchVariationsSeed,
+ weak_ptr_factory_.GetWeakPtr()),
+ local_state_));
+ request_scheduler_->Start();
+}
+
+void VariationsService::AddObserver(Observer* observer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ observer_list_.AddObserver(observer);
+}
+
+void VariationsService::RemoveObserver(Observer* observer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ observer_list_.RemoveObserver(observer);
+}
+
+void VariationsService::OnAppEnterForeground() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // On mobile platforms, initialize the fetch scheduler when we receive the
+ // first app foreground notification.
+ if (!request_scheduler_)
+ StartRepeatedVariationsSeedFetch();
+ request_scheduler_->OnAppEnterForeground();
+}
+
+void VariationsService::SetRestrictMode(const std::string& restrict_mode) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // This should be called before the server URL has been computed.
+ DCHECK(variations_server_url_.is_empty());
+ restrict_mode_ = restrict_mode;
+}
+
+void VariationsService::SetCreateTrialsFromSeedCalledForTesting(bool called) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ create_trials_from_seed_called_ = called;
+}
+
+GURL VariationsService::GetVariationsServerURL(
+ PrefService* policy_pref_service,
+ const std::string& restrict_mode_override) {
+ std::string server_url_string(
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kVariationsServerURL));
+ if (server_url_string.empty())
+ server_url_string = kDefaultServerUrl;
+ GURL server_url = GURL(server_url_string);
+
+ const std::string restrict_param =
+ !restrict_mode_override.empty()
+ ? restrict_mode_override
+ : GetRestrictParameterPref(client_.get(), policy_pref_service);
+ if (!restrict_param.empty()) {
+ server_url = net::AppendOrReplaceQueryParameter(server_url,
+ "restrict",
+ restrict_param);
+ }
+
+ server_url = net::AppendOrReplaceQueryParameter(server_url, "osname",
+ GetPlatformString());
+
+ DCHECK(server_url.is_valid());
+ return server_url;
+}
+
+// static
+std::string VariationsService::GetDefaultVariationsServerURLForTesting() {
+ return kDefaultServerUrl;
+}
+
+// static
+void VariationsService::RegisterPrefs(PrefRegistrySimple* registry) {
+ VariationsSeedStore::RegisterPrefs(registry);
+ registry->RegisterInt64Pref(prefs::kVariationsLastFetchTime, 0);
+ // This preference will only be written by the policy service, which will fill
+ // it according to a value stored in the User Policy.
+ registry->RegisterStringPref(prefs::kVariationsRestrictParameter,
+ std::string());
+ // This preference keeps track of the country code used to filter
+ // permanent-consistency studies.
+ registry->RegisterListPref(prefs::kVariationsPermanentConsistencyCountry);
+}
+
+// static
+void VariationsService::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) {
+ // This preference will only be written by the policy service, which will fill
+ // it according to a value stored in the User Policy.
+ registry->RegisterStringPref(prefs::kVariationsRestrictParameter,
+ std::string());
+}
+
+// static
+scoped_ptr<VariationsService> VariationsService::Create(
+ scoped_ptr<VariationsServiceClient> client,
+ PrefService* local_state,
+ metrics::MetricsStateManager* state_manager,
+ const char* disable_network_switch,
+ const UIStringOverrider& ui_string_overrider) {
+ scoped_ptr<VariationsService> result;
+#if !defined(GOOGLE_CHROME_BUILD)
+ // Unless the URL was provided, unsupported builds should return NULL to
+ // indicate that the service should not be used.
+ if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kVariationsServerURL)) {
+ DVLOG(1) << "Not creating VariationsService in unofficial build without --"
+ << switches::kVariationsServerURL << " specified.";
+ return result;
+ }
+#endif
+ result.reset(new VariationsService(
+ std::move(client),
+ make_scoped_ptr(new web_resource::ResourceRequestAllowedNotifier(
+ local_state, disable_network_switch)),
+ local_state, state_manager, ui_string_overrider));
+ return result;
+}
+
+// static
+scoped_ptr<VariationsService> VariationsService::CreateForTesting(
+ scoped_ptr<VariationsServiceClient> client,
+ PrefService* local_state) {
+ return make_scoped_ptr(new VariationsService(
+ std::move(client),
+ make_scoped_ptr(new web_resource::ResourceRequestAllowedNotifier(
+ local_state, nullptr)),
+ local_state, nullptr, UIStringOverrider()));
+}
+
+void VariationsService::DoActualFetch() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!pending_seed_request_);
+
+ pending_seed_request_ = net::URLFetcher::Create(0, variations_server_url_,
+ net::URLFetcher::GET, this);
+ data_use_measurement::DataUseUserData::AttachToFetcher(
+ pending_seed_request_.get(),
+ data_use_measurement::DataUseUserData::VARIATIONS);
+ pending_seed_request_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES);
+ pending_seed_request_->SetRequestContext(client_->GetURLRequestContext());
+ bool enable_deltas = false;
+ if (!seed_store_.variations_serial_number().empty() &&
+ !disable_deltas_for_next_request_) {
+ // If the current seed includes a country code, deltas are not supported (as
+ // the serial number doesn't take into account the country code). The server
+ // will update us with a seed that doesn't include a country code which will
+ // enable deltas to work.
+ // TODO(asvitkine): Remove the check in M50+ when the percentage of clients
+ // that have an old seed with a country code becomes miniscule.
+ if (!seed_store_.seed_has_country_code()) {
+ // Tell the server that delta-compressed seeds are supported.
+ enable_deltas = true;
+ }
+ // Get the seed only if its serial number doesn't match what we have.
+ pending_seed_request_->AddExtraRequestHeader(
+ "If-None-Match:" + seed_store_.variations_serial_number());
+ }
+ // Tell the server that delta-compressed and gzipped seeds are supported.
+ const char* supported_im = enable_deltas ? "A-IM:x-bm,gzip" : "A-IM:gzip";
+ pending_seed_request_->AddExtraRequestHeader(supported_im);
+
+ pending_seed_request_->Start();
+
+ const base::TimeTicks now = base::TimeTicks::Now();
+ base::TimeDelta time_since_last_fetch;
+ // Record a time delta of 0 (default value) if there was no previous fetch.
+ if (!last_request_started_time_.is_null())
+ time_since_last_fetch = now - last_request_started_time_;
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Variations.TimeSinceLastFetchAttempt",
+ time_since_last_fetch.InMinutes(), 0,
+ base::TimeDelta::FromDays(7).InMinutes(), 50);
+ UMA_HISTOGRAM_COUNTS_100("Variations.RequestCount", request_count_);
+ ++request_count_;
+ last_request_started_time_ = now;
+ disable_deltas_for_next_request_ = false;
+}
+
+bool VariationsService::StoreSeed(const std::string& seed_data,
+ const std::string& seed_signature,
+ const std::string& country_code,
+ const base::Time& date_fetched,
+ bool is_delta_compressed,
+ bool is_gzip_compressed) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ scoped_ptr<variations::VariationsSeed> seed(new variations::VariationsSeed);
+ if (!seed_store_.StoreSeedData(seed_data, seed_signature, country_code,
+ date_fetched, is_delta_compressed,
+ is_gzip_compressed, seed.get())) {
+ return false;
+ }
+ RecordLastFetchTime();
+
+ // Perform seed simulation only if |state_manager_| is not-NULL. The state
+ // manager may be NULL for some unit tests.
+ if (!state_manager_)
+ return true;
+
+ base::PostTaskAndReplyWithResult(
+ client_->GetBlockingPool(), FROM_HERE,
+ client_->GetVersionForSimulationCallback(),
+ base::Bind(&VariationsService::PerformSimulationWithVersion,
+ weak_ptr_factory_.GetWeakPtr(), base::Passed(&seed)));
+ return true;
+}
+
+bool VariationsService::LoadSeed(VariationsSeed* seed) {
+ return seed_store_.LoadSeed(seed);
+}
+
+void VariationsService::FetchVariationsSeed() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ const web_resource::ResourceRequestAllowedNotifier::State state =
+ resource_request_allowed_notifier_->GetResourceRequestsAllowedState();
+ RecordRequestsAllowedHistogram(ResourceRequestStateToHistogramValue(state));
+ if (state != web_resource::ResourceRequestAllowedNotifier::ALLOWED) {
+ DVLOG(1) << "Resource requests were not allowed. Waiting for notification.";
+ return;
+ }
+
+ DoActualFetch();
+}
+
+void VariationsService::NotifyObservers(
+ const variations::VariationsSeedSimulator::Result& result) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (result.kill_critical_group_change_count > 0) {
+ FOR_EACH_OBSERVER(Observer, observer_list_,
+ OnExperimentChangesDetected(Observer::CRITICAL));
+ } else if (result.kill_best_effort_group_change_count > 0) {
+ FOR_EACH_OBSERVER(Observer, observer_list_,
+ OnExperimentChangesDetected(Observer::BEST_EFFORT));
+ }
+}
+
+void VariationsService::OnURLFetchComplete(const net::URLFetcher* source) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(pending_seed_request_.get(), source);
+
+ const bool is_first_request = !initial_request_completed_;
+ initial_request_completed_ = true;
+
+ // The fetcher will be deleted when the request is handled.
+ scoped_ptr<const net::URLFetcher> request(pending_seed_request_.release());
+ const net::URLRequestStatus& request_status = request->GetStatus();
+ if (request_status.status() != net::URLRequestStatus::SUCCESS) {
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Variations.FailedRequestErrorCode",
+ -request_status.error());
+ DVLOG(1) << "Variations server request failed with error: "
+ << request_status.error() << ": "
+ << net::ErrorToString(request_status.error());
+ // It's common for the very first fetch attempt to fail (e.g. the network
+ // may not yet be available). In such a case, try again soon, rather than
+ // waiting the full time interval.
+ if (is_first_request)
+ request_scheduler_->ScheduleFetchShortly();
+ return;
+ }
+
+ // Log the response code.
+ const int response_code = request->GetResponseCode();
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Variations.SeedFetchResponseCode",
+ response_code);
+
+ const base::TimeDelta latency =
+ base::TimeTicks::Now() - last_request_started_time_;
+
+ base::Time response_date;
+ if (response_code == net::HTTP_OK ||
+ response_code == net::HTTP_NOT_MODIFIED) {
+ bool success = request->GetResponseHeaders()->GetDateValue(&response_date);
+ DCHECK(success || response_date.is_null());
+
+ if (!response_date.is_null()) {
+ client_->GetNetworkTimeTracker()->UpdateNetworkTime(
+ response_date,
+ base::TimeDelta::FromMilliseconds(kServerTimeResolutionMs), latency,
+ base::TimeTicks::Now());
+ }
+ }
+
+ if (response_code != net::HTTP_OK) {
+ DVLOG(1) << "Variations server request returned non-HTTP_OK response code: "
+ << response_code;
+ if (response_code == net::HTTP_NOT_MODIFIED) {
+ RecordLastFetchTime();
+ // Update the seed date value in local state (used for expiry check on
+ // next start up), since 304 is a successful response.
+ seed_store_.UpdateSeedDateAndLogDayChange(response_date);
+ }
+ return;
+ }
+
+ std::string seed_data;
+ bool success = request->GetResponseAsString(&seed_data);
+ DCHECK(success);
+
+ net::HttpResponseHeaders* headers = request->GetResponseHeaders();
+ bool is_delta_compressed;
+ bool is_gzip_compressed;
+ if (!GetInstanceManipulations(headers, &is_delta_compressed,
+ &is_gzip_compressed)) {
+ // The header does not specify supported instance manipulations, unable to
+ // process data. Details of errors were logged by GetInstanceManipulations.
+ seed_store_.ReportUnsupportedSeedFormatError();
+ return;
+ }
+
+ const std::string signature = GetHeaderValue(headers, "X-Seed-Signature");
+ const std::string country_code = GetHeaderValue(headers, "X-Country");
+ const bool store_success = StoreSeed(seed_data, signature, country_code,
+ response_date, is_delta_compressed,
+ is_gzip_compressed);
+ if (!store_success && is_delta_compressed) {
+ disable_deltas_for_next_request_ = true;
+ request_scheduler_->ScheduleFetchShortly();
+ }
+}
+
+void VariationsService::OnResourceRequestsAllowed() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Note that this only attempts to fetch the seed at most once per period
+ // (kSeedFetchPeriodHours). This works because
+ // |resource_request_allowed_notifier_| only calls this method if an
+ // attempt was made earlier that fails (which implies that the period had
+ // elapsed). After a successful attempt is made, the notifier will know not
+ // to call this method again until another failed attempt occurs.
+ RecordRequestsAllowedHistogram(RESOURCE_REQUESTS_ALLOWED_NOTIFIED);
+ DVLOG(1) << "Retrying fetch.";
+ DoActualFetch();
+
+ // This service must have created a scheduler in order for this to be called.
+ DCHECK(request_scheduler_.get());
+ request_scheduler_->Reset();
+}
+
+void VariationsService::PerformSimulationWithVersion(
+ scoped_ptr<variations::VariationsSeed> seed,
+ const base::Version& version) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!version.IsValid())
+ return;
+
+ const base::ElapsedTimer timer;
+
+ scoped_ptr<const base::FieldTrial::EntropyProvider> entropy_provider =
+ state_manager_->CreateEntropyProvider();
+ variations::VariationsSeedSimulator seed_simulator(*entropy_provider);
+
+ const std::string latest_country =
+ local_state_->GetString(prefs::kVariationsCountry);
+ const variations::VariationsSeedSimulator::Result result =
+ seed_simulator.SimulateSeedStudies(
+ *seed, client_->GetApplicationLocale(),
+ GetReferenceDateForExpiryChecks(local_state_), version,
+ GetChannelForVariations(client_->GetChannel()),
+ GetCurrentFormFactor(), GetHardwareClass(), latest_country,
+ LoadPermanentConsistencyCountry(version, latest_country));
+
+ UMA_HISTOGRAM_COUNTS_100("Variations.SimulateSeed.NormalChanges",
+ result.normal_group_change_count);
+ UMA_HISTOGRAM_COUNTS_100("Variations.SimulateSeed.KillBestEffortChanges",
+ result.kill_best_effort_group_change_count);
+ UMA_HISTOGRAM_COUNTS_100("Variations.SimulateSeed.KillCriticalChanges",
+ result.kill_critical_group_change_count);
+
+ UMA_HISTOGRAM_TIMES("Variations.SimulateSeed.Duration", timer.Elapsed());
+
+ NotifyObservers(result);
+}
+
+void VariationsService::RecordLastFetchTime() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // local_state_ is NULL in tests, so check it first.
+ if (local_state_) {
+ local_state_->SetInt64(prefs::kVariationsLastFetchTime,
+ base::Time::Now().ToInternalValue());
+ }
+}
+
+std::string VariationsService::GetInvalidVariationsSeedSignature() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return seed_store_.GetInvalidSignature();
+}
+
+std::string VariationsService::LoadPermanentConsistencyCountry(
+ const base::Version& version,
+ const std::string& latest_country) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(version.IsValid());
+
+ const base::ListValue* list_value =
+ local_state_->GetList(prefs::kVariationsPermanentConsistencyCountry);
+ std::string stored_version_string;
+ std::string stored_country;
+
+ // Determine if the saved pref value is present and valid.
+ const bool is_pref_empty = list_value->empty();
+ const bool is_pref_valid = list_value->GetSize() == 2 &&
+ list_value->GetString(0, &stored_version_string) &&
+ list_value->GetString(1, &stored_country) &&
+ base::Version(stored_version_string).IsValid();
+
+ // Determine if the version from the saved pref matches |version|.
+ const bool does_version_match =
+ is_pref_valid && version == base::Version(stored_version_string);
+
+ // Determine if the country in the saved pref matches the country in
+ // |latest_country|.
+ const bool does_country_match = is_pref_valid && !latest_country.empty() &&
+ stored_country == latest_country;
+
+ // Record a histogram for how the saved pref value compares to the current
+ // version and the country code in the variations seed.
+ LoadPermanentConsistencyCountryResult result;
+ if (is_pref_empty) {
+ result = !latest_country.empty() ? LOAD_COUNTRY_NO_PREF_HAS_SEED
+ : LOAD_COUNTRY_NO_PREF_NO_SEED;
+ } else if (!is_pref_valid) {
+ result = !latest_country.empty() ? LOAD_COUNTRY_INVALID_PREF_HAS_SEED
+ : LOAD_COUNTRY_INVALID_PREF_NO_SEED;
+ } else if (latest_country.empty()) {
+ result = does_version_match ? LOAD_COUNTRY_HAS_PREF_NO_SEED_VERSION_EQ
+ : LOAD_COUNTRY_HAS_PREF_NO_SEED_VERSION_NEQ;
+ } else if (does_version_match) {
+ result = does_country_match ? LOAD_COUNTRY_HAS_BOTH_VERSION_EQ_COUNTRY_EQ
+ : LOAD_COUNTRY_HAS_BOTH_VERSION_EQ_COUNTRY_NEQ;
+ } else {
+ result = does_country_match ? LOAD_COUNTRY_HAS_BOTH_VERSION_NEQ_COUNTRY_EQ
+ : LOAD_COUNTRY_HAS_BOTH_VERSION_NEQ_COUNTRY_NEQ;
+ }
+ UMA_HISTOGRAM_ENUMERATION("Variations.LoadPermanentConsistencyCountryResult",
+ result, LOAD_COUNTRY_MAX);
+
+ // Use the stored country if one is available and was fetched since the last
+ // time Chrome was updated.
+ if (does_version_match)
+ return stored_country;
+
+ if (latest_country.empty()) {
+ if (!is_pref_valid)
+ local_state_->ClearPref(prefs::kVariationsPermanentConsistencyCountry);
+ // If we've never received a country code from the server, use an empty
+ // country so that it won't pass any filters that specifically include
+ // countries, but so that it will pass any filters that specifically exclude
+ // countries.
+ return std::string();
+ }
+
+ // Otherwise, update the pref with the current Chrome version and country.
+ base::ListValue new_list_value;
+ new_list_value.AppendString(version.GetString());
+ new_list_value.AppendString(latest_country);
+ local_state_->Set(prefs::kVariationsPermanentConsistencyCountry,
+ new_list_value);
+ return latest_country;
+}
+
+std::string VariationsService::GetStoredPermanentCountry() {
+ const base::ListValue* list_value =
+ local_state_->GetList(prefs::kVariationsPermanentConsistencyCountry);
+ std::string stored_country;
+
+ if (list_value->GetSize() == 2) {
+ list_value->GetString(1, &stored_country);
+ }
+
+ return stored_country;
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/service/variations_service.h b/chromium/components/variations/service/variations_service.h
new file mode 100644
index 00000000000..61c61731ac0
--- /dev/null
+++ b/chromium/components/variations/service/variations_service.h
@@ -0,0 +1,322 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_SERVICE_VARIATIONS_SERVICE_H_
+#define COMPONENTS_VARIATIONS_SERVICE_VARIATIONS_SERVICE_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/metrics/field_trial.h"
+#include "base/observer_list.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "components/variations/service/ui_string_overrider.h"
+#include "components/variations/service/variations_service_client.h"
+#include "components/variations/variations_request_scheduler.h"
+#include "components/variations/variations_seed_simulator.h"
+#include "components/variations/variations_seed_store.h"
+#include "components/web_resource/resource_request_allowed_notifier.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "url/gurl.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace base {
+class FeatureList;
+class Version;
+}
+
+namespace metrics {
+class MetricsStateManager;
+}
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+namespace variations {
+class VariationsSeed;
+}
+
+namespace variations {
+
+// Used to setup field trials based on stored variations seed data, and fetch
+// new seed data from the variations server.
+class VariationsService
+ : public net::URLFetcherDelegate,
+ public web_resource::ResourceRequestAllowedNotifier::Observer {
+ public:
+ class Observer {
+ public:
+ // How critical a detected experiment change is. Whether it should be
+ // handled on a "best-effort" basis or, for a more critical change, if it
+ // should be given higher priority.
+ enum Severity {
+ BEST_EFFORT,
+ CRITICAL,
+ };
+
+ // Called when the VariationsService detects that there will be significant
+ // experiment changes on a restart. This notification can then be used to
+ // update UI (i.e. badging an icon).
+ virtual void OnExperimentChangesDetected(Severity severity) = 0;
+
+ protected:
+ virtual ~Observer() {}
+ };
+
+ ~VariationsService() override;
+
+ // Creates field trials based on the variations seed loaded from local state.
+ // If there is a problem loading the seed data, all trials specified by the
+ // seed may not be created. Some field trials are configured to override or
+ // associate with (for reporting) specific features. These associations are
+ // registered with |feature_list|.
+ bool CreateTrialsFromSeed(base::FeatureList* feature_list);
+
+ // Should be called before startup of the main message loop.
+ void PerformPreMainMessageLoopStartup();
+
+ // Calls FetchVariationsSeed once and repeats this periodically. See
+ // implementation for details on the period. Must be called after
+ // |CreateTrialsFromSeed|.
+ void StartRepeatedVariationsSeedFetch();
+
+ // Adds an observer to listen for detected experiment changes.
+ void AddObserver(Observer* observer);
+
+ // Removes a previously-added observer.
+ void RemoveObserver(Observer* observer);
+
+ // Called when the application enters foreground. This may trigger a
+ // FetchVariationsSeed call.
+ // TODO(rkaplow): Handle this and the similar event in metrics_service by
+ // observing an 'OnAppEnterForeground' event instead of requiring the frontend
+ // code to notify each service individually.
+ void OnAppEnterForeground();
+
+ // Sets the value of the "restrict" URL param to the variations service that
+ // should be used for variation seed requests. This takes precedence over any
+ // value coming from policy prefs. This should be called prior to any calls
+ // to |StartRepeatedVariationsSeedFetch|.
+ void SetRestrictMode(const std::string& restrict_mode);
+
+ // Exposed for testing.
+ void SetCreateTrialsFromSeedCalledForTesting(bool called);
+
+ // Returns the variations server URL, which can vary if a command-line flag is
+ // set and/or the variations restrict pref is set in |local_prefs|. Declared
+ // static for test purposes.
+ GURL GetVariationsServerURL(PrefService* local_prefs,
+ const std::string& restrict_mode_override);
+
+ // Returns the permanent country code stored for this client. Country code is
+ // in the format of lowercase ISO 3166-1 alpha-2. Example: us, br, in
+ std::string GetStoredPermanentCountry();
+
+ // Exposed for testing.
+ static std::string GetDefaultVariationsServerURLForTesting();
+
+ // Register Variations related prefs in Local State.
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ // Register Variations related prefs in the Profile prefs.
+ static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+ // Factory method for creating a VariationsService. Does not take ownership of
+ // |state_manager|. Caller should ensure that |state_manager| is valid for the
+ // lifetime of this class.
+ static scoped_ptr<VariationsService> Create(
+ scoped_ptr<VariationsServiceClient> client,
+ PrefService* local_state,
+ metrics::MetricsStateManager* state_manager,
+ const char* disable_network_switch,
+ const UIStringOverrider& ui_string_overrider);
+
+ // Factory method for creating a VariationsService in a testing context.
+ static scoped_ptr<VariationsService> CreateForTesting(
+ scoped_ptr<VariationsServiceClient> client,
+ PrefService* local_state);
+
+ // Set the PrefService responsible for getting policy-related preferences,
+ // such as the restrict parameter.
+ void set_policy_pref_service(PrefService* service) {
+ DCHECK(service);
+ policy_pref_service_ = service;
+ }
+
+ // Returns the invalid variations seed signature in base64 format, or an empty
+ // string if the signature was valid, missing, or if signature verification is
+ // disabled.
+ std::string GetInvalidVariationsSeedSignature() const;
+
+ protected:
+ // Starts the fetching process once, where |OnURLFetchComplete| is called with
+ // the response.
+ virtual void DoActualFetch();
+
+ // Stores the seed to prefs. Set as virtual and protected so that it can be
+ // overridden by tests.
+ virtual bool StoreSeed(const std::string& seed_data,
+ const std::string& seed_signature,
+ const std::string& country_code,
+ const base::Time& date_fetched,
+ bool is_delta_compressed,
+ bool is_gzip_compressed);
+
+ // Creates the VariationsService with the given |local_state| prefs service
+ // and |state_manager|. Does not take ownership of |state_manager|. Caller
+ // should ensure that |state_manager| is valid for the lifetime of this class.
+ // Use the |Create| factory method to create a VariationsService.
+ VariationsService(
+ scoped_ptr<VariationsServiceClient> client,
+ scoped_ptr<web_resource::ResourceRequestAllowedNotifier> notifier,
+ PrefService* local_state,
+ metrics::MetricsStateManager* state_manager,
+ const UIStringOverrider& ui_string_overrider);
+
+ // Sets the URL for querying the variations server. Used for testing.
+ void set_variations_server_url(const GURL& url) {
+ variations_server_url_ = url;
+ }
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(VariationsServiceTest, Observer);
+ FRIEND_TEST_ALL_PREFIXES(VariationsServiceTest, SeedStoredWhenOKStatus);
+ FRIEND_TEST_ALL_PREFIXES(VariationsServiceTest, SeedNotStoredWhenNonOKStatus);
+ FRIEND_TEST_ALL_PREFIXES(VariationsServiceTest, InstanceManipulations);
+ FRIEND_TEST_ALL_PREFIXES(VariationsServiceTest,
+ LoadPermanentConsistencyCountry);
+ FRIEND_TEST_ALL_PREFIXES(VariationsServiceTest, CountryHeader);
+ FRIEND_TEST_ALL_PREFIXES(VariationsServiceTest, GetVariationsServerURL);
+
+ // Set of different possible values to report for the
+ // Variations.LoadPermanentConsistencyCountryResult histogram. This enum must
+ // be kept consistent with its counterpart in histograms.xml.
+ enum LoadPermanentConsistencyCountryResult {
+ LOAD_COUNTRY_NO_PREF_NO_SEED = 0,
+ LOAD_COUNTRY_NO_PREF_HAS_SEED,
+ LOAD_COUNTRY_INVALID_PREF_NO_SEED,
+ LOAD_COUNTRY_INVALID_PREF_HAS_SEED,
+ LOAD_COUNTRY_HAS_PREF_NO_SEED_VERSION_EQ,
+ LOAD_COUNTRY_HAS_PREF_NO_SEED_VERSION_NEQ,
+ LOAD_COUNTRY_HAS_BOTH_VERSION_EQ_COUNTRY_EQ,
+ LOAD_COUNTRY_HAS_BOTH_VERSION_EQ_COUNTRY_NEQ,
+ LOAD_COUNTRY_HAS_BOTH_VERSION_NEQ_COUNTRY_EQ,
+ LOAD_COUNTRY_HAS_BOTH_VERSION_NEQ_COUNTRY_NEQ,
+ LOAD_COUNTRY_MAX,
+ };
+
+ // Loads the seed from the variations store into |seed|. If successfull,
+ // |seed| will contain the loaded data and true is returned. Set as virtual
+ // so that it can be overridden by tests.
+ virtual bool LoadSeed(VariationsSeed* seed);
+
+ // Checks if prerequisites for fetching the Variations seed are met, and if
+ // so, performs the actual fetch using |DoActualFetch|.
+ void FetchVariationsSeed();
+
+ // Notify any observers of this service based on the simulation |result|.
+ void NotifyObservers(
+ const variations::VariationsSeedSimulator::Result& result);
+
+ // net::URLFetcherDelegate implementation:
+ void OnURLFetchComplete(const net::URLFetcher* source) override;
+
+ // ResourceRequestAllowedNotifier::Observer implementation:
+ void OnResourceRequestsAllowed() override;
+
+ // Performs a variations seed simulation with the given |seed| and |version|
+ // and logs the simulation results as histograms.
+ void PerformSimulationWithVersion(scoped_ptr<variations::VariationsSeed> seed,
+ const base::Version& version);
+
+ // Record the time of the most recent successful fetch.
+ void RecordLastFetchTime();
+
+ // Loads the country code to use for filtering permanent consistency studies,
+ // updating the stored country code if the stored value was for a different
+ // Chrome version. The country used for permanent consistency studies is kept
+ // consistent between Chrome upgrades in order to avoid annoying the user due
+ // to experiment churn while traveling.
+ std::string LoadPermanentConsistencyCountry(
+ const base::Version& version,
+ const std::string& latest_country);
+
+ scoped_ptr<VariationsServiceClient> client_;
+ UIStringOverrider ui_string_overrider_;
+
+ // The pref service used to store persist the variations seed.
+ PrefService* local_state_;
+
+ // Used for instantiating entropy providers for variations seed simulation.
+ // Weak pointer.
+ metrics::MetricsStateManager* state_manager_;
+
+ // Used to obtain policy-related preferences. Depending on the platform, will
+ // either be Local State or Profile prefs.
+ PrefService* policy_pref_service_;
+
+ VariationsSeedStore seed_store_;
+
+ // Contains the scheduler instance that handles timing for requests to the
+ // server. Initially NULL and instantiated when the initial fetch is
+ // requested.
+ scoped_ptr<VariationsRequestScheduler> request_scheduler_;
+
+ // Contains the current seed request. Will only have a value while a request
+ // is pending, and will be reset by |OnURLFetchComplete|.
+ scoped_ptr<net::URLFetcher> pending_seed_request_;
+
+ // The value of the "restrict" URL param to the variations server that has
+ // been specified via |SetRestrictMode|. If empty, the URL param will be set
+ // based on policy prefs.
+ std::string restrict_mode_;
+
+ // The URL to use for querying the variations server.
+ GURL variations_server_url_;
+
+ // Tracks whether |CreateTrialsFromSeed| has been called, to ensure that
+ // it gets called prior to |StartRepeatedVariationsSeedFetch|.
+ bool create_trials_from_seed_called_;
+
+ // Tracks whether the initial request to the variations server had completed.
+ bool initial_request_completed_;
+
+ // Indicates that the next request to the variations service shouldn't specify
+ // that it supports delta compression. Set to true when a delta compressed
+ // response encountered an error.
+ bool disable_deltas_for_next_request_;
+
+ // Helper class used to tell this service if it's allowed to make network
+ // resource requests.
+ scoped_ptr<web_resource::ResourceRequestAllowedNotifier>
+ resource_request_allowed_notifier_;
+
+ // The start time of the last seed request. This is used to measure the
+ // latency of seed requests. Initially zero.
+ base::TimeTicks last_request_started_time_;
+
+ // The number of requests to the variations server that have been performed.
+ int request_count_;
+
+ // List of observers of the VariationsService.
+ base::ObserverList<Observer> observer_list_;
+
+ base::ThreadChecker thread_checker_;
+
+ base::WeakPtrFactory<VariationsService> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(VariationsService);
+};
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_SERVICE_VARIATIONS_SERVICE_H_
diff --git a/chromium/components/variations/service/variations_service_client.h b/chromium/components/variations/service/variations_service_client.h
new file mode 100644
index 00000000000..b3758a64b37
--- /dev/null
+++ b/chromium/components/variations/service/variations_service_client.h
@@ -0,0 +1,66 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_SERVICE_VARIATIONS_SERVICE_CLIENT_H_
+#define COMPONENTS_VARIATIONS_SERVICE_VARIATIONS_SERVICE_CLIENT_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/strings/string16.h"
+#include "base/version.h"
+#include "components/version_info/version_info.h"
+
+namespace base {
+class SequencedWorkerPool;
+}
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+namespace network_time {
+class NetworkTimeTracker;
+}
+
+namespace variations {
+
+// An abstraction of operations that depend on the embedder's (e.g. Chrome)
+// environment.
+class VariationsServiceClient {
+ public:
+ virtual ~VariationsServiceClient() {}
+
+ // Returns the current application locale (e.g. "en-US").
+ virtual std::string GetApplicationLocale() = 0;
+
+ // Returns the SequencedWorkerPool on which the VariationsService should run
+ // tasks that may block.
+ virtual base::SequencedWorkerPool* GetBlockingPool() = 0;
+
+ // Returns a callback that when run returns the base::Version to use for
+ // variations seed simulation. VariationsService guarantees that the callback
+ // will be run on the pool returned by
+ // VariationsServiceClient::GetBlockingPool().
+ virtual base::Callback<base::Version(void)>
+ GetVersionForSimulationCallback() = 0;
+
+ virtual net::URLRequestContextGetter* GetURLRequestContext() = 0;
+ virtual network_time::NetworkTimeTracker* GetNetworkTimeTracker() = 0;
+
+ // Gets the channel of the embedder.
+ virtual version_info::Channel GetChannel() = 0;
+
+ // Returns whether the embedder overrides the value of the restrict parameter.
+ // |parameter| is an out-param that will contain the value of the restrict
+ // parameter if true is returned.
+ virtual bool OverridesRestrictParameter(std::string* parameter) = 0;
+
+ // Called from VariationsService::PerformPreMainMessageLoopStartup().
+ virtual void OnInitialStartup() {}
+};
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_SERVICE_VARIATIONS_SERVICE_CLIENT_H_
diff --git a/chromium/components/variations/service/variations_service_unittest.cc b/chromium/components/variations/service/variations_service_unittest.cc
new file mode 100644
index 00000000000..aea08e39f8a
--- /dev/null
+++ b/chromium/components/variations/service/variations_service_unittest.cc
@@ -0,0 +1,731 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/service/variations_service.h"
+
+#include <stddef.h>
+#include <utility>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/feature_list.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/test/histogram_tester.h"
+#include "base/version.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/variations/pref_names.h"
+#include "components/variations/proto/study.pb.h"
+#include "components/variations/proto/variations_seed.pb.h"
+#include "components/web_resource/resource_request_allowed_notifier_test_util.h"
+#include "net/base/url_util.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace variations {
+
+namespace {
+
+class TestVariationsServiceClient : public VariationsServiceClient {
+ public:
+ TestVariationsServiceClient() {}
+ ~TestVariationsServiceClient() override {}
+
+ // variations::VariationsServiceClient:
+ std::string GetApplicationLocale() override { return std::string(); }
+ base::SequencedWorkerPool* GetBlockingPool() override { return nullptr; }
+ base::Callback<base::Version(void)> GetVersionForSimulationCallback()
+ override {
+ return base::Callback<base::Version(void)>();
+ }
+ net::URLRequestContextGetter* GetURLRequestContext() override {
+ return nullptr;
+ }
+ network_time::NetworkTimeTracker* GetNetworkTimeTracker() override {
+ return nullptr;
+ }
+ version_info::Channel GetChannel() override {
+ return version_info::Channel::UNKNOWN;
+ }
+ bool OverridesRestrictParameter(std::string* parameter) override {
+ if (restrict_parameter_.empty())
+ return false;
+ *parameter = restrict_parameter_;
+ return true;
+ }
+ void OnInitialStartup() override {}
+
+ void set_restrict_parameter(const std::string& value) {
+ restrict_parameter_ = value;
+ }
+
+ private:
+ std::string restrict_parameter_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestVariationsServiceClient);
+};
+
+// A test class used to validate expected functionality in VariationsService.
+class TestVariationsService : public VariationsService {
+ public:
+ TestVariationsService(
+ scoped_ptr<web_resource::TestRequestAllowedNotifier> test_notifier,
+ PrefService* local_state)
+ : VariationsService(make_scoped_ptr(new TestVariationsServiceClient()),
+ std::move(test_notifier),
+ local_state,
+ NULL,
+ UIStringOverrider()),
+ intercepts_fetch_(true),
+ fetch_attempted_(false),
+ seed_stored_(false),
+ delta_compressed_seed_(false),
+ gzip_compressed_seed_(false) {
+ // Set this so StartRepeatedVariationsSeedFetch can be called in tests.
+ SetCreateTrialsFromSeedCalledForTesting(true);
+ set_variations_server_url(
+ GetVariationsServerURL(local_state, std::string()));
+ }
+
+ ~TestVariationsService() override {}
+
+ void set_intercepts_fetch(bool value) {
+ intercepts_fetch_ = value;
+ }
+
+ bool fetch_attempted() const { return fetch_attempted_; }
+ bool seed_stored() const { return seed_stored_; }
+ const std::string& stored_country() const { return stored_country_; }
+ bool delta_compressed_seed() const { return delta_compressed_seed_; }
+ bool gzip_compressed_seed() const { return gzip_compressed_seed_; }
+
+ void DoActualFetch() override {
+ if (intercepts_fetch_) {
+ fetch_attempted_ = true;
+ return;
+ }
+
+ VariationsService::DoActualFetch();
+ }
+
+ bool StoreSeed(const std::string& seed_data,
+ const std::string& seed_signature,
+ const std::string& country_code,
+ const base::Time& date_fetched,
+ bool is_delta_compressed,
+ bool is_gzip_compressed) override {
+ seed_stored_ = true;
+ stored_seed_data_ = seed_data;
+ stored_country_ = country_code;
+ delta_compressed_seed_ = is_delta_compressed;
+ gzip_compressed_seed_ = is_gzip_compressed;
+ return true;
+ }
+
+ private:
+ bool LoadSeed(VariationsSeed* seed) override {
+ if (!seed_stored_)
+ return false;
+ return seed->ParseFromString(stored_seed_data_);
+ }
+
+ bool intercepts_fetch_;
+ bool fetch_attempted_;
+ bool seed_stored_;
+ std::string stored_seed_data_;
+ std::string stored_country_;
+ bool delta_compressed_seed_;
+ bool gzip_compressed_seed_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestVariationsService);
+};
+
+class TestVariationsServiceObserver : public VariationsService::Observer {
+ public:
+ TestVariationsServiceObserver()
+ : best_effort_changes_notified_(0),
+ crticial_changes_notified_(0) {
+ }
+ ~TestVariationsServiceObserver() override {}
+
+ void OnExperimentChangesDetected(Severity severity) override {
+ switch (severity) {
+ case BEST_EFFORT:
+ ++best_effort_changes_notified_;
+ break;
+ case CRITICAL:
+ ++crticial_changes_notified_;
+ break;
+ }
+ }
+
+ int best_effort_changes_notified() const {
+ return best_effort_changes_notified_;
+ }
+
+ int crticial_changes_notified() const {
+ return crticial_changes_notified_;
+ }
+
+ private:
+ // Number of notification received with BEST_EFFORT severity.
+ int best_effort_changes_notified_;
+
+ // Number of notification received with CRITICAL severity.
+ int crticial_changes_notified_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestVariationsServiceObserver);
+};
+
+// Constants used to create the test seed.
+const char kTestSeedStudyName[] = "test";
+const char kTestSeedExperimentName[] = "abc";
+const int kTestSeedExperimentProbability = 100;
+const char kTestSeedSerialNumber[] = "123";
+
+// Populates |seed| with simple test data. The resulting seed will contain one
+// study called "test", which contains one experiment called "abc" with
+// probability weight 100. |seed|'s study field will be cleared before adding
+// the new study.
+variations::VariationsSeed CreateTestSeed() {
+ variations::VariationsSeed seed;
+ variations::Study* study = seed.add_study();
+ study->set_name(kTestSeedStudyName);
+ study->set_default_experiment_name(kTestSeedExperimentName);
+ variations::Study_Experiment* experiment = study->add_experiment();
+ experiment->set_name(kTestSeedExperimentName);
+ experiment->set_probability_weight(kTestSeedExperimentProbability);
+ seed.set_serial_number(kTestSeedSerialNumber);
+ return seed;
+}
+
+// Serializes |seed| to protobuf binary format.
+std::string SerializeSeed(const variations::VariationsSeed& seed) {
+ std::string serialized_seed;
+ seed.SerializeToString(&serialized_seed);
+ return serialized_seed;
+}
+
+// Simulates a variations service response by setting a date header and the
+// specified HTTP |response_code| on |fetcher|. Sets additional header |header|
+// if it is not null.
+scoped_refptr<net::HttpResponseHeaders> SimulateServerResponseWithHeader(
+ int response_code,
+ net::TestURLFetcher* fetcher,
+ const std::string* header) {
+ EXPECT_TRUE(fetcher);
+ scoped_refptr<net::HttpResponseHeaders> headers(
+ new net::HttpResponseHeaders("date:Wed, 13 Feb 2013 00:25:24 GMT\0\0"));
+ if (header)
+ headers->AddHeader(*header);
+ fetcher->set_response_headers(headers);
+ fetcher->set_response_code(response_code);
+ return headers;
+}
+
+// Simulates a variations service response by setting a date header and the
+// specified HTTP |response_code| on |fetcher|.
+scoped_refptr<net::HttpResponseHeaders> SimulateServerResponse(
+ int response_code,
+ net::TestURLFetcher* fetcher) {
+ return SimulateServerResponseWithHeader(response_code, fetcher, nullptr);
+}
+
+// Converts |list_value| to a string, to make it easier for debugging.
+std::string ListValueToString(const base::ListValue& list_value) {
+ std::string json;
+ JSONStringValueSerializer serializer(&json);
+ serializer.set_pretty_print(true);
+ serializer.Serialize(list_value);
+ return json;
+}
+
+} // namespace
+
+class VariationsServiceTest : public ::testing::Test {
+ protected:
+ VariationsServiceTest() {}
+
+ private:
+ base::MessageLoop message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(VariationsServiceTest);
+};
+
+TEST_F(VariationsServiceTest, CreateTrialsFromSeed) {
+ TestingPrefServiceSimple prefs;
+ VariationsService::RegisterPrefs(prefs.registry());
+
+ // Setup base::FeatureList.
+ base::FeatureList::ClearInstanceForTesting();
+ base::FeatureList::SetInstance(make_scoped_ptr(new base::FeatureList()));
+
+ // Create a local base::FieldTrialList, to hold the field trials created in
+ // this test.
+ base::FieldTrialList field_trial_list(nullptr);
+
+ // Create a variations service.
+ TestVariationsService service(
+ make_scoped_ptr(new web_resource::TestRequestAllowedNotifier(&prefs)),
+ &prefs);
+ service.SetCreateTrialsFromSeedCalledForTesting(false);
+
+ // Store a seed.
+ service.StoreSeed(SerializeSeed(CreateTestSeed()), std::string(),
+ std::string(), base::Time::Now(), false, false);
+ prefs.SetInt64(prefs::kVariationsLastFetchTime,
+ base::Time::Now().ToInternalValue());
+
+ // Check that field trials are created from the seed. Since the test study has
+ // only 1 experiment with 100% probability weight, we must be part of it.
+ EXPECT_TRUE(service.CreateTrialsFromSeed(base::FeatureList::GetInstance()));
+ EXPECT_EQ(base::FieldTrialList::FindFullName(kTestSeedStudyName),
+ kTestSeedExperimentName);
+}
+
+TEST_F(VariationsServiceTest, CreateTrialsFromSeedNoLastFetchTime) {
+ TestingPrefServiceSimple prefs;
+ VariationsService::RegisterPrefs(prefs.registry());
+
+ // Setup base::FeatureList.
+ base::FeatureList::ClearInstanceForTesting();
+ base::FeatureList::SetInstance(make_scoped_ptr(new base::FeatureList()));
+
+ // Create a local base::FieldTrialList, to hold the field trials created in
+ // this test.
+ base::FieldTrialList field_trial_list(nullptr);
+
+ // Create a variations service
+ TestVariationsService service(
+ make_scoped_ptr(new web_resource::TestRequestAllowedNotifier(&prefs)),
+ &prefs);
+ service.SetCreateTrialsFromSeedCalledForTesting(false);
+
+ // Store a seed. To simulate a first run, |prefs::kVariationsLastFetchTime|
+ // is left empty.
+ service.StoreSeed(SerializeSeed(CreateTestSeed()), std::string(),
+ std::string(), base::Time::Now(), false, false);
+ EXPECT_EQ(0, prefs.GetInt64(prefs::kVariationsLastFetchTime));
+
+ // Check that field trials are created from the seed. Since the test study has
+ // only 1 experiment with 100% probability weight, we must be part of it.
+ EXPECT_TRUE(service.CreateTrialsFromSeed(base::FeatureList::GetInstance()));
+ EXPECT_EQ(base::FieldTrialList::FindFullName(kTestSeedStudyName),
+ kTestSeedExperimentName);
+}
+
+TEST_F(VariationsServiceTest, CreateTrialsFromOutdatedSeed) {
+ TestingPrefServiceSimple prefs;
+ VariationsService::RegisterPrefs(prefs.registry());
+
+ // Setup base::FeatureList.
+ base::FeatureList::ClearInstanceForTesting();
+ base::FeatureList::SetInstance(make_scoped_ptr(new base::FeatureList()));
+
+ // Create a local base::FieldTrialList, to hold the field trials created in
+ // this test.
+ base::FieldTrialList field_trial_list(nullptr);
+
+ // Create a variations service.
+ TestVariationsService service(
+ make_scoped_ptr(new web_resource::TestRequestAllowedNotifier(&prefs)),
+ &prefs);
+ service.SetCreateTrialsFromSeedCalledForTesting(false);
+
+ // Store a seed, with a fetch time 31 days in the past.
+ const base::Time seed_date =
+ base::Time::Now() - base::TimeDelta::FromDays(31);
+ service.StoreSeed(SerializeSeed(CreateTestSeed()), std::string(),
+ std::string(), seed_date, false, false);
+ prefs.SetInt64(prefs::kVariationsLastFetchTime, seed_date.ToInternalValue());
+
+ // Check that field trials are not created from the seed.
+ EXPECT_FALSE(service.CreateTrialsFromSeed(base::FeatureList::GetInstance()));
+ EXPECT_TRUE(base::FieldTrialList::FindFullName(kTestSeedStudyName).empty());
+}
+
+TEST_F(VariationsServiceTest, GetVariationsServerURL) {
+ TestingPrefServiceSimple prefs;
+ VariationsService::RegisterPrefs(prefs.registry());
+ const std::string default_variations_url =
+ VariationsService::GetDefaultVariationsServerURLForTesting();
+
+ std::string value;
+ scoped_ptr<TestVariationsServiceClient> client =
+ make_scoped_ptr(new TestVariationsServiceClient());
+ TestVariationsServiceClient* raw_client = client.get();
+ VariationsService service(
+ std::move(client),
+ make_scoped_ptr(new web_resource::TestRequestAllowedNotifier(&prefs)),
+ &prefs, NULL, UIStringOverrider());
+ GURL url = service.GetVariationsServerURL(&prefs, std::string());
+ EXPECT_TRUE(base::StartsWith(url.spec(), default_variations_url,
+ base::CompareCase::SENSITIVE));
+ EXPECT_FALSE(net::GetValueForKeyInQuery(url, "restrict", &value));
+
+ prefs.SetString(prefs::kVariationsRestrictParameter, "restricted");
+ url = service.GetVariationsServerURL(&prefs, std::string());
+ EXPECT_TRUE(base::StartsWith(url.spec(), default_variations_url,
+ base::CompareCase::SENSITIVE));
+ EXPECT_TRUE(net::GetValueForKeyInQuery(url, "restrict", &value));
+ EXPECT_EQ("restricted", value);
+
+ // A client override should take precedence over what's in prefs.
+ raw_client->set_restrict_parameter("client");
+ url = service.GetVariationsServerURL(&prefs, std::string());
+ EXPECT_TRUE(base::StartsWith(url.spec(), default_variations_url,
+ base::CompareCase::SENSITIVE));
+ EXPECT_TRUE(net::GetValueForKeyInQuery(url, "restrict", &value));
+ EXPECT_EQ("client", value);
+
+ // The override value passed to the method should take precedence over
+ // what's in prefs and a client override.
+ url = service.GetVariationsServerURL(&prefs, "override");
+ EXPECT_TRUE(base::StartsWith(url.spec(), default_variations_url,
+ base::CompareCase::SENSITIVE));
+ EXPECT_TRUE(net::GetValueForKeyInQuery(url, "restrict", &value));
+ EXPECT_EQ("override", value);
+}
+
+TEST_F(VariationsServiceTest, VariationsURLHasOSNameParam) {
+ TestingPrefServiceSimple prefs;
+ VariationsService::RegisterPrefs(prefs.registry());
+ TestVariationsService service(
+ make_scoped_ptr(new web_resource::TestRequestAllowedNotifier(&prefs)),
+ &prefs);
+ const GURL url = service.GetVariationsServerURL(&prefs, std::string());
+
+ std::string value;
+ EXPECT_TRUE(net::GetValueForKeyInQuery(url, "osname", &value));
+ EXPECT_FALSE(value.empty());
+}
+
+TEST_F(VariationsServiceTest, RequestsInitiallyNotAllowed) {
+ TestingPrefServiceSimple prefs;
+ VariationsService::RegisterPrefs(prefs.registry());
+
+ // Pass ownership to TestVariationsService, but keep a weak pointer to
+ // manipulate it for this test.
+ scoped_ptr<web_resource::TestRequestAllowedNotifier> test_notifier =
+ make_scoped_ptr(new web_resource::TestRequestAllowedNotifier(&prefs));
+ web_resource::TestRequestAllowedNotifier* raw_notifier = test_notifier.get();
+ TestVariationsService test_service(std::move(test_notifier), &prefs);
+
+ // Force the notifier to initially disallow requests.
+ raw_notifier->SetRequestsAllowedOverride(false);
+ test_service.StartRepeatedVariationsSeedFetch();
+ EXPECT_FALSE(test_service.fetch_attempted());
+
+ raw_notifier->NotifyObserver();
+ EXPECT_TRUE(test_service.fetch_attempted());
+}
+
+TEST_F(VariationsServiceTest, RequestsInitiallyAllowed) {
+ TestingPrefServiceSimple prefs;
+ VariationsService::RegisterPrefs(prefs.registry());
+
+ // Pass ownership to TestVariationsService, but keep a weak pointer to
+ // manipulate it for this test.
+ scoped_ptr<web_resource::TestRequestAllowedNotifier> test_notifier =
+ make_scoped_ptr(new web_resource::TestRequestAllowedNotifier(&prefs));
+ web_resource::TestRequestAllowedNotifier* raw_notifier = test_notifier.get();
+ TestVariationsService test_service(std::move(test_notifier), &prefs);
+
+ raw_notifier->SetRequestsAllowedOverride(true);
+ test_service.StartRepeatedVariationsSeedFetch();
+ EXPECT_TRUE(test_service.fetch_attempted());
+}
+
+TEST_F(VariationsServiceTest, SeedStoredWhenOKStatus) {
+ TestingPrefServiceSimple prefs;
+ VariationsService::RegisterPrefs(prefs.registry());
+
+ TestVariationsService service(
+ make_scoped_ptr(new web_resource::TestRequestAllowedNotifier(&prefs)),
+ &prefs);
+ service.set_intercepts_fetch(false);
+
+ net::TestURLFetcherFactory factory;
+ service.DoActualFetch();
+
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+ SimulateServerResponse(net::HTTP_OK, fetcher);
+ fetcher->SetResponseString(SerializeSeed(CreateTestSeed()));
+
+ EXPECT_FALSE(service.seed_stored());
+ service.OnURLFetchComplete(fetcher);
+ EXPECT_TRUE(service.seed_stored());
+}
+
+TEST_F(VariationsServiceTest, SeedNotStoredWhenNonOKStatus) {
+ const int non_ok_status_codes[] = {
+ net::HTTP_NO_CONTENT,
+ net::HTTP_NOT_MODIFIED,
+ net::HTTP_NOT_FOUND,
+ net::HTTP_INTERNAL_SERVER_ERROR,
+ net::HTTP_SERVICE_UNAVAILABLE,
+ };
+
+ TestingPrefServiceSimple prefs;
+ VariationsService::RegisterPrefs(prefs.registry());
+
+ TestVariationsService service(
+ make_scoped_ptr(new web_resource::TestRequestAllowedNotifier(&prefs)),
+ &prefs);
+ service.set_intercepts_fetch(false);
+ for (size_t i = 0; i < arraysize(non_ok_status_codes); ++i) {
+ net::TestURLFetcherFactory factory;
+ service.DoActualFetch();
+ EXPECT_TRUE(prefs.FindPreference(prefs::kVariationsSeed)->IsDefaultValue());
+
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+ SimulateServerResponse(non_ok_status_codes[i], fetcher);
+ service.OnURLFetchComplete(fetcher);
+
+ EXPECT_TRUE(prefs.FindPreference(prefs::kVariationsSeed)->IsDefaultValue());
+ }
+}
+
+TEST_F(VariationsServiceTest, RequestGzipCompressedSeed) {
+ TestingPrefServiceSimple prefs;
+ VariationsService::RegisterPrefs(prefs.registry());
+ net::TestURLFetcherFactory factory;
+
+ TestVariationsService service(
+ make_scoped_ptr(new web_resource::TestRequestAllowedNotifier(&prefs)),
+ &prefs);
+ service.set_intercepts_fetch(false);
+ service.DoActualFetch();
+
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+ net::HttpRequestHeaders headers;
+ fetcher->GetExtraRequestHeaders(&headers);
+ std::string field;
+ ASSERT_TRUE(headers.GetHeader("A-IM", &field));
+ EXPECT_EQ("gzip", field);
+}
+
+TEST_F(VariationsServiceTest, InstanceManipulations) {
+ struct {
+ std::string im;
+ bool delta_compressed;
+ bool gzip_compressed;
+ bool seed_stored;
+ } cases[] = {
+ {"", false, false, true},
+ {"IM:gzip", false, true, true},
+ {"IM:x-bm", true, false, true},
+ {"IM:x-bm,gzip", true, true, true},
+ {"IM: x-bm, gzip", true, true, true},
+ {"IM:gzip,x-bm", false, false, false},
+ {"IM:deflate,x-bm,gzip", false, false, false},
+ };
+
+ TestingPrefServiceSimple prefs;
+ VariationsService::RegisterPrefs(prefs.registry());
+ std::string serialized_seed = SerializeSeed(CreateTestSeed());
+ net::TestURLFetcherFactory factory;
+
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ TestVariationsService service(
+ make_scoped_ptr(new web_resource::TestRequestAllowedNotifier(&prefs)),
+ &prefs);
+ service.set_intercepts_fetch(false);
+ service.DoActualFetch();
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+
+ if (cases[i].im.empty())
+ SimulateServerResponse(net::HTTP_OK, fetcher);
+ else
+ SimulateServerResponseWithHeader(net::HTTP_OK, fetcher, &cases[i].im);
+ fetcher->SetResponseString(serialized_seed);
+ service.OnURLFetchComplete(fetcher);
+
+ EXPECT_EQ(cases[i].seed_stored, service.seed_stored());
+ EXPECT_EQ(cases[i].delta_compressed, service.delta_compressed_seed());
+ EXPECT_EQ(cases[i].gzip_compressed, service.gzip_compressed_seed());
+ }
+}
+
+TEST_F(VariationsServiceTest, CountryHeader) {
+ TestingPrefServiceSimple prefs;
+ VariationsService::RegisterPrefs(prefs.registry());
+
+ TestVariationsService service(
+ make_scoped_ptr(new web_resource::TestRequestAllowedNotifier(&prefs)),
+ &prefs);
+ service.set_intercepts_fetch(false);
+
+ net::TestURLFetcherFactory factory;
+ service.DoActualFetch();
+
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+ scoped_refptr<net::HttpResponseHeaders> headers =
+ SimulateServerResponse(net::HTTP_OK, fetcher);
+ headers->AddHeader("X-Country: test");
+ fetcher->SetResponseString(SerializeSeed(CreateTestSeed()));
+
+ EXPECT_FALSE(service.seed_stored());
+ service.OnURLFetchComplete(fetcher);
+ EXPECT_TRUE(service.seed_stored());
+ EXPECT_EQ("test", service.stored_country());
+}
+
+TEST_F(VariationsServiceTest, Observer) {
+ TestingPrefServiceSimple prefs;
+ VariationsService::RegisterPrefs(prefs.registry());
+ VariationsService service(
+ make_scoped_ptr(new TestVariationsServiceClient()),
+ make_scoped_ptr(new web_resource::TestRequestAllowedNotifier(&prefs)),
+ &prefs, NULL, UIStringOverrider());
+
+ struct {
+ int normal_count;
+ int best_effort_count;
+ int critical_count;
+ int expected_best_effort_notifications;
+ int expected_crtical_notifications;
+ } cases[] = {
+ {0, 0, 0, 0, 0},
+ {1, 0, 0, 0, 0},
+ {10, 0, 0, 0, 0},
+ {0, 1, 0, 1, 0},
+ {0, 10, 0, 1, 0},
+ {0, 0, 1, 0, 1},
+ {0, 0, 10, 0, 1},
+ {0, 1, 1, 0, 1},
+ {1, 1, 1, 0, 1},
+ {1, 1, 0, 1, 0},
+ {1, 0, 1, 0, 1},
+ };
+
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ TestVariationsServiceObserver observer;
+ service.AddObserver(&observer);
+
+ variations::VariationsSeedSimulator::Result result;
+ result.normal_group_change_count = cases[i].normal_count;
+ result.kill_best_effort_group_change_count = cases[i].best_effort_count;
+ result.kill_critical_group_change_count = cases[i].critical_count;
+ service.NotifyObservers(result);
+
+ EXPECT_EQ(cases[i].expected_best_effort_notifications,
+ observer.best_effort_changes_notified()) << i;
+ EXPECT_EQ(cases[i].expected_crtical_notifications,
+ observer.crticial_changes_notified()) << i;
+
+ service.RemoveObserver(&observer);
+ }
+}
+
+TEST_F(VariationsServiceTest, LoadPermanentConsistencyCountry) {
+ struct {
+ // Comma separated list, NULL if the pref isn't set initially.
+ const char* pref_value_before;
+ const char* version;
+ // NULL indicates that no latest country code is present.
+ const char* latest_country_code;
+ // Comma separated list.
+ const char* expected_pref_value_after;
+ std::string expected_country;
+ VariationsService::LoadPermanentConsistencyCountryResult expected_result;
+ } test_cases[] = {
+ // Existing pref value present for this version.
+ {"20.0.0.0,us", "20.0.0.0", "ca", "20.0.0.0,us", "us",
+ VariationsService::LOAD_COUNTRY_HAS_BOTH_VERSION_EQ_COUNTRY_NEQ},
+ {"20.0.0.0,us", "20.0.0.0", "us", "20.0.0.0,us", "us",
+ VariationsService::LOAD_COUNTRY_HAS_BOTH_VERSION_EQ_COUNTRY_EQ},
+ {"20.0.0.0,us", "20.0.0.0", nullptr, "20.0.0.0,us", "us",
+ VariationsService::LOAD_COUNTRY_HAS_PREF_NO_SEED_VERSION_EQ},
+
+ // Existing pref value present for a different version.
+ {"19.0.0.0,ca", "20.0.0.0", "us", "20.0.0.0,us", "us",
+ VariationsService::LOAD_COUNTRY_HAS_BOTH_VERSION_NEQ_COUNTRY_NEQ},
+ {"19.0.0.0,us", "20.0.0.0", "us", "20.0.0.0,us", "us",
+ VariationsService::LOAD_COUNTRY_HAS_BOTH_VERSION_NEQ_COUNTRY_EQ},
+ {"19.0.0.0,ca", "20.0.0.0", nullptr, "19.0.0.0,ca", "",
+ VariationsService::LOAD_COUNTRY_HAS_PREF_NO_SEED_VERSION_NEQ},
+
+ // No existing pref value present.
+ {nullptr, "20.0.0.0", "us", "20.0.0.0,us", "us",
+ VariationsService::LOAD_COUNTRY_NO_PREF_HAS_SEED},
+ {nullptr, "20.0.0.0", nullptr, "", "",
+ VariationsService::LOAD_COUNTRY_NO_PREF_NO_SEED},
+ {"", "20.0.0.0", "us", "20.0.0.0,us", "us",
+ VariationsService::LOAD_COUNTRY_NO_PREF_HAS_SEED},
+ {"", "20.0.0.0", nullptr, "", "",
+ VariationsService::LOAD_COUNTRY_NO_PREF_NO_SEED},
+
+ // Invalid existing pref value.
+ {"20.0.0.0", "20.0.0.0", "us", "20.0.0.0,us", "us",
+ VariationsService::LOAD_COUNTRY_INVALID_PREF_HAS_SEED},
+ {"20.0.0.0", "20.0.0.0", nullptr, "", "",
+ VariationsService::LOAD_COUNTRY_INVALID_PREF_NO_SEED},
+ {"20.0.0.0,us,element3", "20.0.0.0", "us", "20.0.0.0,us", "us",
+ VariationsService::LOAD_COUNTRY_INVALID_PREF_HAS_SEED},
+ {"20.0.0.0,us,element3", "20.0.0.0", nullptr, "", "",
+ VariationsService::LOAD_COUNTRY_INVALID_PREF_NO_SEED},
+ {"badversion,ca", "20.0.0.0", "us", "20.0.0.0,us", "us",
+ VariationsService::LOAD_COUNTRY_INVALID_PREF_HAS_SEED},
+ {"badversion,ca", "20.0.0.0", nullptr, "", "",
+ VariationsService::LOAD_COUNTRY_INVALID_PREF_NO_SEED},
+ };
+
+ for (const auto& test : test_cases) {
+ TestingPrefServiceSimple prefs;
+ VariationsService::RegisterPrefs(prefs.registry());
+ VariationsService service(
+ make_scoped_ptr(new TestVariationsServiceClient()),
+ make_scoped_ptr(new web_resource::TestRequestAllowedNotifier(&prefs)),
+ &prefs, NULL, UIStringOverrider());
+
+ if (test.pref_value_before) {
+ base::ListValue list_value;
+ for (const std::string& component :
+ base::SplitString(test.pref_value_before, ",", base::TRIM_WHITESPACE,
+ base::SPLIT_WANT_ALL)) {
+ list_value.AppendString(component);
+ }
+ prefs.Set(prefs::kVariationsPermanentConsistencyCountry, list_value);
+ }
+
+ variations::VariationsSeed seed(CreateTestSeed());
+ std::string latest_country;
+ if (test.latest_country_code)
+ latest_country = test.latest_country_code;
+
+ base::HistogramTester histogram_tester;
+ EXPECT_EQ(test.expected_country,
+ service.LoadPermanentConsistencyCountry(
+ base::Version(test.version), latest_country))
+ << test.pref_value_before << ", " << test.version << ", "
+ << test.latest_country_code;
+
+ base::ListValue expected_list_value;
+ for (const std::string& component :
+ base::SplitString(test.expected_pref_value_after, ",",
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+ expected_list_value.AppendString(component);
+ }
+ const base::ListValue* pref_value =
+ prefs.GetList(prefs::kVariationsPermanentConsistencyCountry);
+ EXPECT_EQ(ListValueToString(expected_list_value),
+ ListValueToString(*pref_value))
+ << test.pref_value_before << ", " << test.version << ", "
+ << test.latest_country_code;
+
+ histogram_tester.ExpectUniqueSample(
+ "Variations.LoadPermanentConsistencyCountryResult",
+ test.expected_result, 1);
+ }
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/study_filtering.cc b/chromium/components/variations/study_filtering.cc
new file mode 100644
index 00000000000..f5b3f26f9c5
--- /dev/null
+++ b/chromium/components/variations/study_filtering.cc
@@ -0,0 +1,305 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/study_filtering.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <set>
+
+#include "base/stl_util.h"
+#include "build/build_config.h"
+
+namespace variations {
+
+namespace {
+
+Study_Platform GetCurrentPlatform() {
+#if defined(OS_WIN)
+ return Study_Platform_PLATFORM_WINDOWS;
+#elif defined(OS_IOS)
+ return Study_Platform_PLATFORM_IOS;
+#elif defined(OS_MACOSX)
+ return Study_Platform_PLATFORM_MAC;
+#elif defined(OS_CHROMEOS)
+ return Study_Platform_PLATFORM_CHROMEOS;
+#elif defined(OS_ANDROID)
+ return Study_Platform_PLATFORM_ANDROID;
+#elif defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
+ // Default BSD and SOLARIS to Linux to not break those builds, although these
+ // platforms are not officially supported by Chrome.
+ return Study_Platform_PLATFORM_LINUX;
+#else
+#error Unknown platform
+#endif
+}
+
+// Converts |date_time| in Study date format to base::Time.
+base::Time ConvertStudyDateToBaseTime(int64_t date_time) {
+ return base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(date_time);
+}
+
+} // namespace
+
+namespace internal {
+
+bool CheckStudyChannel(const Study_Filter& filter, Study_Channel channel) {
+ // An empty channel list matches all channels.
+ if (filter.channel_size() == 0)
+ return true;
+
+ for (int i = 0; i < filter.channel_size(); ++i) {
+ if (filter.channel(i) == channel)
+ return true;
+ }
+ return false;
+}
+
+bool CheckStudyFormFactor(const Study_Filter& filter,
+ Study_FormFactor form_factor) {
+ // An empty form factor list matches all form factors.
+ if (filter.form_factor_size() == 0)
+ return true;
+
+ for (int i = 0; i < filter.form_factor_size(); ++i) {
+ if (filter.form_factor(i) == form_factor)
+ return true;
+ }
+ return false;
+}
+
+bool CheckStudyHardwareClass(const Study_Filter& filter,
+ const std::string& hardware_class) {
+ // Empty hardware_class and exclude_hardware_class matches all.
+ if (filter.hardware_class_size() == 0 &&
+ filter.exclude_hardware_class_size() == 0) {
+ return true;
+ }
+
+ // Checks if we are supposed to filter for a specified set of
+ // hardware_classes. Note that this means this overrides the
+ // exclude_hardware_class in case that ever occurs (which it shouldn't).
+ if (filter.hardware_class_size() > 0) {
+ for (int i = 0; i < filter.hardware_class_size(); ++i) {
+ // Check if the entry is a substring of |hardware_class|.
+ size_t position = hardware_class.find(filter.hardware_class(i));
+ if (position != std::string::npos)
+ return true;
+ }
+ // None of the requested hardware_classes match.
+ return false;
+ }
+
+ // Omit if matches any of the exclude entries.
+ for (int i = 0; i < filter.exclude_hardware_class_size(); ++i) {
+ // Check if the entry is a substring of |hardware_class|.
+ size_t position = hardware_class.find(
+ filter.exclude_hardware_class(i));
+ if (position != std::string::npos)
+ return false;
+ }
+
+ // None of the exclusions match, so this accepts.
+ return true;
+}
+
+bool CheckStudyLocale(const Study_Filter& filter, const std::string& locale) {
+ // An empty locale list matches all locales.
+ if (filter.locale_size() == 0)
+ return true;
+
+ for (int i = 0; i < filter.locale_size(); ++i) {
+ if (filter.locale(i) == locale)
+ return true;
+ }
+ return false;
+}
+
+bool CheckStudyPlatform(const Study_Filter& filter, Study_Platform platform) {
+ // An empty platform list matches all platforms.
+ if (filter.platform_size() == 0)
+ return true;
+
+ for (int i = 0; i < filter.platform_size(); ++i) {
+ if (filter.platform(i) == platform)
+ return true;
+ }
+ return false;
+}
+
+bool CheckStudyStartDate(const Study_Filter& filter,
+ const base::Time& date_time) {
+ if (filter.has_start_date()) {
+ const base::Time start_date =
+ ConvertStudyDateToBaseTime(filter.start_date());
+ return date_time >= start_date;
+ }
+
+ return true;
+}
+
+bool CheckStudyVersion(const Study_Filter& filter,
+ const base::Version& version) {
+ if (filter.has_min_version()) {
+ if (version.CompareToWildcardString(filter.min_version()) < 0)
+ return false;
+ }
+
+ if (filter.has_max_version()) {
+ if (version.CompareToWildcardString(filter.max_version()) > 0)
+ return false;
+ }
+
+ return true;
+}
+
+bool CheckStudyCountry(const Study_Filter& filter, const std::string& country) {
+ // Empty country and exclude_country matches all.
+ if (filter.country_size() == 0 && filter.exclude_country_size() == 0)
+ return true;
+
+ // Checks if we are supposed to filter for a specified set of countries. Note
+ // that this means this overrides the exclude_country in case that ever occurs
+ // (which it shouldn't).
+ if (filter.country_size() > 0)
+ return ContainsValue(filter.country(), country);
+
+ // Omit if matches any of the exclude entries.
+ return !ContainsValue(filter.exclude_country(), country);
+}
+
+bool IsStudyExpired(const Study& study, const base::Time& date_time) {
+ if (study.has_expiry_date()) {
+ const base::Time expiry_date =
+ ConvertStudyDateToBaseTime(study.expiry_date());
+ return date_time >= expiry_date;
+ }
+
+ return false;
+}
+
+bool ShouldAddStudy(
+ const Study& study,
+ const std::string& locale,
+ const base::Time& reference_date,
+ const base::Version& version,
+ Study_Channel channel,
+ Study_FormFactor form_factor,
+ const std::string& hardware_class,
+ const std::string& country) {
+ if (study.has_filter()) {
+ if (!CheckStudyChannel(study.filter(), channel)) {
+ DVLOG(1) << "Filtered out study " << study.name() << " due to channel.";
+ return false;
+ }
+
+ if (!CheckStudyFormFactor(study.filter(), form_factor)) {
+ DVLOG(1) << "Filtered out study " << study.name() <<
+ " due to form factor.";
+ return false;
+ }
+
+ if (!CheckStudyLocale(study.filter(), locale)) {
+ DVLOG(1) << "Filtered out study " << study.name() << " due to locale.";
+ return false;
+ }
+
+ if (!CheckStudyPlatform(study.filter(), GetCurrentPlatform())) {
+ DVLOG(1) << "Filtered out study " << study.name() << " due to platform.";
+ return false;
+ }
+
+ if (!CheckStudyVersion(study.filter(), version)) {
+ DVLOG(1) << "Filtered out study " << study.name() << " due to version.";
+ return false;
+ }
+
+ if (!CheckStudyStartDate(study.filter(), reference_date)) {
+ DVLOG(1) << "Filtered out study " << study.name() <<
+ " due to start date.";
+ return false;
+ }
+
+ if (!CheckStudyHardwareClass(study.filter(), hardware_class)) {
+ DVLOG(1) << "Filtered out study " << study.name() <<
+ " due to hardware_class.";
+ return false;
+ }
+
+ if (!CheckStudyCountry(study.filter(), country)) {
+ DVLOG(1) << "Filtered out study " << study.name() << " due to country.";
+ return false;
+ }
+ }
+
+ DVLOG(1) << "Kept study " << study.name() << ".";
+ return true;
+}
+
+} // namespace internal
+
+void FilterAndValidateStudies(const VariationsSeed& seed,
+ const std::string& locale,
+ const base::Time& reference_date,
+ const base::Version& version,
+ Study_Channel channel,
+ Study_FormFactor form_factor,
+ const std::string& hardware_class,
+ const std::string& session_consistency_country,
+ const std::string& permanent_consistency_country,
+ std::vector<ProcessedStudy>* filtered_studies) {
+ DCHECK(version.IsValid());
+
+ // Add expired studies (in a disabled state) only after all the non-expired
+ // studies have been added (and do not add an expired study if a corresponding
+ // non-expired study got added). This way, if there's both an expired and a
+ // non-expired study that applies, the non-expired study takes priority.
+ std::set<std::string> created_studies;
+ std::vector<const Study*> expired_studies;
+
+ for (int i = 0; i < seed.study_size(); ++i) {
+ const Study& study = seed.study(i);
+
+ // Unless otherwise specified, use an empty country that won't pass any
+ // filters that specifically include countries, but will pass any filters
+ // that specifically exclude countries.
+ std::string country;
+ switch (study.consistency()) {
+ case Study_Consistency_SESSION:
+ country = session_consistency_country;
+ break;
+ case Study_Consistency_PERMANENT:
+ // Use the saved |permanent_consistency_country| for permanent
+ // consistency studies. This allows Chrome to use the same country for
+ // filtering permanent consistency studies between Chrome upgrades.
+ // Since some studies have user-visible effects, this helps to avoid
+ // annoying users with experimental group churn while traveling.
+ country = permanent_consistency_country;
+ break;
+ }
+
+ if (!internal::ShouldAddStudy(study, locale, reference_date, version,
+ channel, form_factor, hardware_class,
+ country)) {
+ continue;
+ }
+
+ if (internal::IsStudyExpired(study, reference_date)) {
+ expired_studies.push_back(&study);
+ } else if (!ContainsKey(created_studies, study.name())) {
+ ProcessedStudy::ValidateAndAppendStudy(&study, false, filtered_studies);
+ created_studies.insert(study.name());
+ }
+ }
+
+ for (size_t i = 0; i < expired_studies.size(); ++i) {
+ if (!ContainsKey(created_studies, expired_studies[i]->name())) {
+ ProcessedStudy::ValidateAndAppendStudy(expired_studies[i], true,
+ filtered_studies);
+ }
+ }
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/study_filtering.h b/chromium/components/variations/study_filtering.h
new file mode 100644
index 00000000000..030d91918c3
--- /dev/null
+++ b/chromium/components/variations/study_filtering.h
@@ -0,0 +1,85 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_STUDY_FILTERING_H_
+#define COMPONENTS_VARIATIONS_STUDY_FILTERING_H_
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/time/time.h"
+#include "base/version.h"
+#include "components/variations/processed_study.h"
+#include "components/variations/proto/study.pb.h"
+#include "components/variations/proto/variations_seed.pb.h"
+
+namespace variations {
+
+// Internal functions exposed for testing purposes only.
+namespace internal {
+
+// Checks whether a study is applicable for the given |channel| per |filter|.
+bool CheckStudyChannel(const Study_Filter& filter, Study_Channel channel);
+
+// Checks whether a study is applicable for the given |form_factor| per
+// |filter|.
+bool CheckStudyFormFactor(const Study_Filter& filter,
+ Study_FormFactor form_factor);
+
+// Checks whether a study is applicable for the given |hardware_class| per
+// |filter|.
+bool CheckStudyHardwareClass(const Study_Filter& filter,
+ const std::string& hardware_class);
+
+// Checks whether a study is applicable for the given |locale| per |filter|.
+bool CheckStudyLocale(const Study_Filter& filter, const std::string& locale);
+
+// Checks whether a study is applicable for the given |platform| per |filter|.
+bool CheckStudyPlatform(const Study_Filter& filter, Study_Platform platform);
+
+// Checks whether a study is applicable for the given date/time per |filter|.
+bool CheckStudyStartDate(const Study_Filter& filter,
+ const base::Time& date_time);
+
+// Checks whether a study is applicable for the given version per |filter|.
+bool CheckStudyVersion(const Study_Filter& filter,
+ const base::Version& version);
+
+// Checks whether a study is applicable for the given |country| per |filter|.
+bool CheckStudyCountry(const Study_Filter& filter, const std::string& country);
+
+// Checks whether |study| is expired using the given date/time.
+bool IsStudyExpired(const Study& study, const base::Time& date_time);
+
+// Returns whether |study| should be disabled according to its restriction
+// parameters.
+bool ShouldAddStudy(const Study& study,
+ const std::string& locale,
+ const base::Time& reference_date,
+ const base::Version& version,
+ Study_Channel channel,
+ Study_FormFactor form_factor,
+ const std::string& hardware_class,
+ const std::string& country);
+
+} // namespace internal
+
+// Filters the list of studies in |seed| and validates and pre-processes them,
+// adding any kept studies to |filtered_studies| list. Ensures that the
+// resulting list will not have more than one study with the same name.
+void FilterAndValidateStudies(const VariationsSeed& seed,
+ const std::string& locale,
+ const base::Time& reference_date,
+ const base::Version& version,
+ Study_Channel channel,
+ Study_FormFactor form_factor,
+ const std::string& hardware_class,
+ const std::string& session_consistency_country,
+ const std::string& permanent_consistency_country,
+ std::vector<ProcessedStudy>* filtered_studies);
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_STUDY_FILTERING_H_
diff --git a/chromium/components/variations/study_filtering_unittest.cc b/chromium/components/variations/study_filtering_unittest.cc
new file mode 100644
index 00000000000..0715432a583
--- /dev/null
+++ b/chromium/components/variations/study_filtering_unittest.cc
@@ -0,0 +1,582 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/study_filtering.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/strings/string_split.h"
+#include "components/variations/processed_study.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace variations {
+
+namespace {
+
+// Converts |time| to Study proto format.
+int64_t TimeToProtoTime(const base::Time& time) {
+ return (time - base::Time::UnixEpoch()).InSeconds();
+}
+
+// Adds an experiment to |study| with the specified |name| and |probability|.
+Study_Experiment* AddExperiment(const std::string& name, int probability,
+ Study* study) {
+ Study_Experiment* experiment = study->add_experiment();
+ experiment->set_name(name);
+ experiment->set_probability_weight(probability);
+ return experiment;
+}
+
+} // namespace
+
+TEST(VariationsStudyFilteringTest, CheckStudyChannel) {
+ const Study_Channel channels[] = {
+ Study_Channel_CANARY,
+ Study_Channel_DEV,
+ Study_Channel_BETA,
+ Study_Channel_STABLE,
+ };
+ bool channel_added[arraysize(channels)] = { 0 };
+
+ Study_Filter filter;
+
+ // Check in the forwarded order. The loop cond is <= arraysize(channels)
+ // instead of < so that the result of adding the last channel gets checked.
+ for (size_t i = 0; i <= arraysize(channels); ++i) {
+ for (size_t j = 0; j < arraysize(channels); ++j) {
+ const bool expected = channel_added[j] || filter.channel_size() == 0;
+ const bool result = internal::CheckStudyChannel(filter, channels[j]);
+ EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
+ }
+
+ if (i < arraysize(channels)) {
+ filter.add_channel(channels[i]);
+ channel_added[i] = true;
+ }
+ }
+
+ // Do the same check in the reverse order.
+ filter.clear_channel();
+ memset(&channel_added, 0, sizeof(channel_added));
+ for (size_t i = 0; i <= arraysize(channels); ++i) {
+ for (size_t j = 0; j < arraysize(channels); ++j) {
+ const bool expected = channel_added[j] || filter.channel_size() == 0;
+ const bool result = internal::CheckStudyChannel(filter, channels[j]);
+ EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
+ }
+
+ if (i < arraysize(channels)) {
+ const int index = arraysize(channels) - i - 1;
+ filter.add_channel(channels[index]);
+ channel_added[index] = true;
+ }
+ }
+}
+
+TEST(VariationsStudyFilteringTest, CheckStudyFormFactor) {
+ const Study_FormFactor form_factors[] = {
+ Study_FormFactor_DESKTOP,
+ Study_FormFactor_PHONE,
+ Study_FormFactor_TABLET,
+ };
+
+ ASSERT_EQ(Study_FormFactor_FormFactor_ARRAYSIZE,
+ static_cast<int>(arraysize(form_factors)));
+
+ bool form_factor_added[arraysize(form_factors)] = { 0 };
+ Study_Filter filter;
+
+ for (size_t i = 0; i <= arraysize(form_factors); ++i) {
+ for (size_t j = 0; j < arraysize(form_factors); ++j) {
+ const bool expected = form_factor_added[j] ||
+ filter.form_factor_size() == 0;
+ const bool result = internal::CheckStudyFormFactor(filter,
+ form_factors[j]);
+ EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
+ }
+
+ if (i < arraysize(form_factors)) {
+ filter.add_form_factor(form_factors[i]);
+ form_factor_added[i] = true;
+ }
+ }
+
+ // Do the same check in the reverse order.
+ filter.clear_form_factor();
+ memset(&form_factor_added, 0, sizeof(form_factor_added));
+ for (size_t i = 0; i <= arraysize(form_factors); ++i) {
+ for (size_t j = 0; j < arraysize(form_factors); ++j) {
+ const bool expected = form_factor_added[j] ||
+ filter.form_factor_size() == 0;
+ const bool result = internal::CheckStudyFormFactor(filter,
+ form_factors[j]);
+ EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
+ }
+
+ if (i < arraysize(form_factors)) {
+ const int index = arraysize(form_factors) - i - 1;
+ filter.add_form_factor(form_factors[index]);
+ form_factor_added[index] = true;
+ }
+ }
+}
+
+TEST(VariationsStudyFilteringTest, CheckStudyLocale) {
+ struct {
+ const char* filter_locales;
+ bool en_us_result;
+ bool en_ca_result;
+ bool fr_result;
+ } test_cases[] = {
+ {"en-US", true, false, false},
+ {"en-US,en-CA,fr", true, true, true},
+ {"en-US,en-CA,en-GB", true, true, false},
+ {"en-GB,en-CA,en-US", true, true, false},
+ {"ja,kr,vi", false, false, false},
+ {"fr-CA", false, false, false},
+ {"", true, true, true},
+ };
+
+ for (size_t i = 0; i < arraysize(test_cases); ++i) {
+ Study_Filter filter;
+ for (const std::string& locale : base::SplitString(
+ test_cases[i].filter_locales, ",",
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL))
+ filter.add_locale(locale);
+ EXPECT_EQ(test_cases[i].en_us_result,
+ internal::CheckStudyLocale(filter, "en-US"));
+ EXPECT_EQ(test_cases[i].en_ca_result,
+ internal::CheckStudyLocale(filter, "en-CA"));
+ EXPECT_EQ(test_cases[i].fr_result,
+ internal::CheckStudyLocale(filter, "fr"));
+ }
+}
+
+TEST(VariationsStudyFilteringTest, CheckStudyPlatform) {
+ const Study_Platform platforms[] = {
+ Study_Platform_PLATFORM_WINDOWS,
+ Study_Platform_PLATFORM_MAC,
+ Study_Platform_PLATFORM_LINUX,
+ Study_Platform_PLATFORM_CHROMEOS,
+ Study_Platform_PLATFORM_ANDROID,
+ Study_Platform_PLATFORM_IOS,
+ };
+ ASSERT_EQ(Study_Platform_Platform_ARRAYSIZE,
+ static_cast<int>(arraysize(platforms)));
+ bool platform_added[arraysize(platforms)] = { 0 };
+
+ Study_Filter filter;
+
+ // Check in the forwarded order. The loop cond is <= arraysize(platforms)
+ // instead of < so that the result of adding the last channel gets checked.
+ for (size_t i = 0; i <= arraysize(platforms); ++i) {
+ for (size_t j = 0; j < arraysize(platforms); ++j) {
+ const bool expected = platform_added[j] || filter.platform_size() == 0;
+ const bool result = internal::CheckStudyPlatform(filter, platforms[j]);
+ EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
+ }
+
+ if (i < arraysize(platforms)) {
+ filter.add_platform(platforms[i]);
+ platform_added[i] = true;
+ }
+ }
+
+ // Do the same check in the reverse order.
+ filter.clear_platform();
+ memset(&platform_added, 0, sizeof(platform_added));
+ for (size_t i = 0; i <= arraysize(platforms); ++i) {
+ for (size_t j = 0; j < arraysize(platforms); ++j) {
+ const bool expected = platform_added[j] || filter.platform_size() == 0;
+ const bool result = internal::CheckStudyPlatform(filter, platforms[j]);
+ EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
+ }
+
+ if (i < arraysize(platforms)) {
+ const int index = arraysize(platforms) - i - 1;
+ filter.add_platform(platforms[index]);
+ platform_added[index] = true;
+ }
+ }
+}
+
+TEST(VariationsStudyFilteringTest, CheckStudyStartDate) {
+ const base::Time now = base::Time::Now();
+ const base::TimeDelta delta = base::TimeDelta::FromHours(1);
+ const struct {
+ const base::Time start_date;
+ bool expected_result;
+ } start_test_cases[] = {
+ { now - delta, true },
+ { now, true },
+ { now + delta, false },
+ };
+
+ Study_Filter filter;
+
+ // Start date not set should result in true.
+ EXPECT_TRUE(internal::CheckStudyStartDate(filter, now));
+
+ for (size_t i = 0; i < arraysize(start_test_cases); ++i) {
+ filter.set_start_date(TimeToProtoTime(start_test_cases[i].start_date));
+ const bool result = internal::CheckStudyStartDate(filter, now);
+ EXPECT_EQ(start_test_cases[i].expected_result, result)
+ << "Case " << i << " failed!";
+ }
+}
+
+TEST(VariationsStudyFilteringTest, CheckStudyVersion) {
+ const struct {
+ const char* min_version;
+ const char* version;
+ bool expected_result;
+ } min_test_cases[] = {
+ { "1.2.2", "1.2.3", true },
+ { "1.2.3", "1.2.3", true },
+ { "1.2.4", "1.2.3", false },
+ { "1.3.2", "1.2.3", false },
+ { "2.1.2", "1.2.3", false },
+ { "0.3.4", "1.2.3", true },
+ // Wildcards.
+ { "1.*", "1.2.3", true },
+ { "1.2.*", "1.2.3", true },
+ { "1.2.3.*", "1.2.3", true },
+ { "1.2.4.*", "1.2.3", false },
+ { "2.*", "1.2.3", false },
+ { "0.3.*", "1.2.3", true },
+ };
+
+ const struct {
+ const char* max_version;
+ const char* version;
+ bool expected_result;
+ } max_test_cases[] = {
+ { "1.2.2", "1.2.3", false },
+ { "1.2.3", "1.2.3", true },
+ { "1.2.4", "1.2.3", true },
+ { "2.1.1", "1.2.3", true },
+ { "2.1.1", "2.3.4", false },
+ // Wildcards
+ { "2.1.*", "2.3.4", false },
+ { "2.*", "2.3.4", true },
+ { "2.3.*", "2.3.4", true },
+ { "2.3.4.*", "2.3.4", true },
+ { "2.3.4.0.*", "2.3.4", true },
+ { "2.4.*", "2.3.4", true },
+ { "1.3.*", "2.3.4", false },
+ { "1.*", "2.3.4", false },
+ };
+
+ Study_Filter filter;
+
+ // Min/max version not set should result in true.
+ EXPECT_TRUE(internal::CheckStudyVersion(filter, base::Version("1.2.3")));
+
+ for (size_t i = 0; i < arraysize(min_test_cases); ++i) {
+ filter.set_min_version(min_test_cases[i].min_version);
+ const bool result =
+ internal::CheckStudyVersion(filter, Version(min_test_cases[i].version));
+ EXPECT_EQ(min_test_cases[i].expected_result, result) <<
+ "Min. version case " << i << " failed!";
+ }
+ filter.clear_min_version();
+
+ for (size_t i = 0; i < arraysize(max_test_cases); ++i) {
+ filter.set_max_version(max_test_cases[i].max_version);
+ const bool result =
+ internal::CheckStudyVersion(filter, Version(max_test_cases[i].version));
+ EXPECT_EQ(max_test_cases[i].expected_result, result) <<
+ "Max version case " << i << " failed!";
+ }
+
+ // Check intersection semantics.
+ for (size_t i = 0; i < arraysize(min_test_cases); ++i) {
+ for (size_t j = 0; j < arraysize(max_test_cases); ++j) {
+ filter.set_min_version(min_test_cases[i].min_version);
+ filter.set_max_version(max_test_cases[j].max_version);
+
+ if (!min_test_cases[i].expected_result) {
+ const bool result =
+ internal::CheckStudyVersion(
+ filter, Version(min_test_cases[i].version));
+ EXPECT_FALSE(result) << "Case " << i << "," << j << " failed!";
+ }
+
+ if (!max_test_cases[j].expected_result) {
+ const bool result =
+ internal::CheckStudyVersion(
+ filter, Version(max_test_cases[j].version));
+ EXPECT_FALSE(result) << "Case " << i << "," << j << " failed!";
+ }
+ }
+ }
+}
+
+TEST(VariationsStudyFilteringTest, CheckStudyHardwareClass) {
+ struct {
+ const char* hardware_class;
+ const char* exclude_hardware_class;
+ const char* actual_hardware_class;
+ bool expected_result;
+ } test_cases[] = {
+ // Neither filtered nor excluded set:
+ // True since empty is always a match.
+ {"", "", "fancy INTEL pear device", true},
+ {"", "", "", true},
+
+ // Filtered set:
+ {"apple,pear,orange", "", "apple", true},
+ {"apple,pear,orange", "", "fancy INTEL pear device", true},
+ {"apple,pear,orange", "", "fancy INTEL GRAPE device", false},
+ // Somehow tagged as both, but still valid.
+ {"apple,pear,orange", "", "fancy INTEL pear GRAPE device", true},
+ // Substring, so still valid.
+ {"apple,pear,orange", "", "fancy INTEL SNapple device", true},
+ // No issues with doubling.
+ {"apple,pear,orange", "", "fancy orange orange device", true},
+ // Empty, which is what would happen for non ChromeOS platforms.
+ {"apple,pear,orange", "", "", false},
+
+ // Excluded set:
+ {"", "apple,pear,orange", "apple", false},
+ {"", "apple,pear,orange", "fancy INTEL pear device", false},
+ {"", "apple,pear,orange", "fancy INTEL GRAPE device", true},
+ // Somehow tagged as both. Very excluded!
+ {"", "apple,pear,orange", "fancy INTEL pear GRAPE device", false},
+ // Substring, so still invalid.
+ {"", "apple,pear,orange", "fancy INTEL SNapple device", false},
+ // Empty.
+ {"", "apple,pear,orange", "", true},
+
+ // Not testing when both are set as it should never occur and should be
+ // considered undefined.
+ };
+
+ for (size_t i = 0; i < arraysize(test_cases); ++i) {
+ Study_Filter filter;
+ for (const std::string& cur : base::SplitString(
+ test_cases[i].hardware_class, ",",
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL))
+ filter.add_hardware_class(cur);
+
+ for (const std::string& cur : base::SplitString(
+ test_cases[i].exclude_hardware_class, ",",
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL))
+ filter.add_exclude_hardware_class(cur);
+
+ EXPECT_EQ(test_cases[i].expected_result,
+ internal::CheckStudyHardwareClass(
+ filter, test_cases[i].actual_hardware_class));
+ }
+}
+
+TEST(VariationsStudyFilteringTest, CheckStudyCountry) {
+ struct {
+ const char* country;
+ const char* exclude_country;
+ const char* actual_country;
+ bool expected_result;
+ } test_cases[] = {
+ // Neither filtered nor excluded set:
+ // True since empty is always a match.
+ {"", "", "us", true},
+ {"", "", "", true},
+
+ // Filtered set:
+ {"us", "", "us", true},
+ {"br,ca,us", "", "us", true},
+ {"br,ca,us", "", "in", false},
+ // Empty, which is what would happen if no country was returned from the
+ // server.
+ {"br,ca,us", "", "", false},
+
+ // Excluded set:
+ {"", "us", "us", false},
+ {"", "br,ca,us", "us", false},
+ {"", "br,ca,us", "in", true},
+ // Empty, which is what would happen if no country was returned from the
+ // server.
+ {"", "br,ca,us", "", true},
+
+ // Not testing when both are set as it should never occur and should be
+ // considered undefined.
+ };
+
+ for (const auto& test : test_cases) {
+ Study_Filter filter;
+ for (const std::string& country : base::SplitString(
+ test.country, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL))
+ filter.add_country(country);
+
+ for (const std::string& exclude_country : base::SplitString(
+ test.exclude_country, ",",
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL))
+ filter.add_exclude_country(exclude_country);
+
+ EXPECT_EQ(test.expected_result,
+ internal::CheckStudyCountry(filter, test.actual_country));
+ }
+}
+
+TEST(VariationsStudyFilteringTest, FilterAndValidateStudies) {
+ const std::string kTrial1Name = "A";
+ const std::string kGroup1Name = "Group1";
+ const std::string kTrial3Name = "B";
+
+ VariationsSeed seed;
+ Study* study1 = seed.add_study();
+ study1->set_name(kTrial1Name);
+ study1->set_default_experiment_name("Default");
+ AddExperiment(kGroup1Name, 100, study1);
+ AddExperiment("Default", 0, study1);
+
+ Study* study2 = seed.add_study();
+ *study2 = *study1;
+ study2->mutable_experiment(0)->set_name("Bam");
+ ASSERT_EQ(seed.study(0).name(), seed.study(1).name());
+
+ Study* study3 = seed.add_study();
+ study3->set_name(kTrial3Name);
+ study3->set_default_experiment_name("Default");
+ AddExperiment("A", 10, study3);
+ AddExperiment("Default", 25, study3);
+
+ std::vector<ProcessedStudy> processed_studies;
+ FilterAndValidateStudies(seed, "en-CA", base::Time::Now(),
+ base::Version("20.0.0.0"), Study_Channel_STABLE,
+ Study_FormFactor_DESKTOP, "", "", "",
+ &processed_studies);
+
+ // Check that only the first kTrial1Name study was kept.
+ ASSERT_EQ(2U, processed_studies.size());
+ EXPECT_EQ(kTrial1Name, processed_studies[0].study()->name());
+ EXPECT_EQ(kGroup1Name, processed_studies[0].study()->experiment(0).name());
+ EXPECT_EQ(kTrial3Name, processed_studies[1].study()->name());
+}
+
+TEST(VariationsStudyFilteringTest, FilterAndValidateStudiesWithCountry) {
+ const char kSessionCountry[] = "ca";
+ const char kPermanentCountry[] = "us";
+
+ struct {
+ Study_Consistency consistency;
+ const char* filter_country;
+ const char* filter_exclude_country;
+ bool expect_study_kept;
+ } test_cases[] = {
+ // Country-agnostic studies should be kept regardless of country.
+ {Study_Consistency_SESSION, nullptr, nullptr, true},
+ {Study_Consistency_PERMANENT, nullptr, nullptr, true},
+
+ // Session-consistency studies should obey the country code in the seed.
+ {Study_Consistency_SESSION, kSessionCountry, nullptr, true},
+ {Study_Consistency_SESSION, nullptr, kSessionCountry, false},
+ {Study_Consistency_SESSION, kPermanentCountry, nullptr, false},
+ {Study_Consistency_SESSION, nullptr, kPermanentCountry, true},
+
+ // Permanent-consistency studies should obey the permanent-consistency
+ // country code.
+ {Study_Consistency_PERMANENT, kPermanentCountry, nullptr, true},
+ {Study_Consistency_PERMANENT, nullptr, kPermanentCountry, false},
+ {Study_Consistency_PERMANENT, kSessionCountry, nullptr, false},
+ {Study_Consistency_PERMANENT, nullptr, kSessionCountry, true},
+ };
+
+ for (const auto& test : test_cases) {
+ VariationsSeed seed;
+ Study* study = seed.add_study();
+ study->set_name("study");
+ study->set_default_experiment_name("Default");
+ AddExperiment("Default", 100, study);
+ study->set_consistency(test.consistency);
+ if (test.filter_country)
+ study->mutable_filter()->add_country(test.filter_country);
+ if (test.filter_exclude_country)
+ study->mutable_filter()->add_exclude_country(test.filter_exclude_country);
+
+ std::vector<ProcessedStudy> processed_studies;
+ FilterAndValidateStudies(seed, "en-CA", base::Time::Now(),
+ base::Version("20.0.0.0"), Study_Channel_STABLE,
+ Study_FormFactor_DESKTOP, "", kSessionCountry,
+ kPermanentCountry, &processed_studies);
+
+ EXPECT_EQ(test.expect_study_kept, !processed_studies.empty());
+ }
+}
+
+TEST(VariationsStudyFilteringTest, IsStudyExpired) {
+ const base::Time now = base::Time::Now();
+ const base::TimeDelta delta = base::TimeDelta::FromHours(1);
+ const struct {
+ const base::Time expiry_date;
+ bool expected_result;
+ } expiry_test_cases[] = {
+ { now - delta, true },
+ { now, true },
+ { now + delta, false },
+ };
+
+ Study study;
+
+ // Expiry date not set should result in false.
+ EXPECT_FALSE(internal::IsStudyExpired(study, now));
+
+ for (size_t i = 0; i < arraysize(expiry_test_cases); ++i) {
+ study.set_expiry_date(TimeToProtoTime(expiry_test_cases[i].expiry_date));
+ const bool result = internal::IsStudyExpired(study, now);
+ EXPECT_EQ(expiry_test_cases[i].expected_result, result)
+ << "Case " << i << " failed!";
+ }
+}
+
+TEST(VariationsStudyFilteringTest, ValidateStudy) {
+ Study study;
+ study.set_default_experiment_name("def");
+ AddExperiment("abc", 100, &study);
+ Study_Experiment* default_group = AddExperiment("def", 200, &study);
+
+ ProcessedStudy processed_study;
+ EXPECT_TRUE(processed_study.Init(&study, false));
+ EXPECT_EQ(300, processed_study.total_probability());
+
+ // Min version checks.
+ study.mutable_filter()->set_min_version("1.2.3.*");
+ EXPECT_TRUE(processed_study.Init(&study, false));
+ study.mutable_filter()->set_min_version("1.*.3");
+ EXPECT_FALSE(processed_study.Init(&study, false));
+ study.mutable_filter()->set_min_version("1.2.3");
+ EXPECT_TRUE(processed_study.Init(&study, false));
+
+ // Max version checks.
+ study.mutable_filter()->set_max_version("2.3.4.*");
+ EXPECT_TRUE(processed_study.Init(&study, false));
+ study.mutable_filter()->set_max_version("*.3");
+ EXPECT_FALSE(processed_study.Init(&study, false));
+ study.mutable_filter()->set_max_version("2.3.4");
+ EXPECT_TRUE(processed_study.Init(&study, false));
+
+ study.clear_default_experiment_name();
+ EXPECT_FALSE(processed_study.Init(&study, false));
+
+ study.set_default_experiment_name("xyz");
+ EXPECT_FALSE(processed_study.Init(&study, false));
+
+ study.set_default_experiment_name("def");
+ default_group->clear_name();
+ EXPECT_FALSE(processed_study.Init(&study, false));
+
+ default_group->set_name("def");
+ EXPECT_TRUE(processed_study.Init(&study, false));
+ Study_Experiment* repeated_group = study.add_experiment();
+ repeated_group->set_name("abc");
+ repeated_group->set_probability_weight(1);
+ EXPECT_FALSE(processed_study.Init(&study, false));
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/synthetic_trials.cc b/chromium/components/variations/synthetic_trials.cc
new file mode 100644
index 00000000000..228ca44387a
--- /dev/null
+++ b/chromium/components/variations/synthetic_trials.cc
@@ -0,0 +1,16 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/synthetic_trials.h"
+
+namespace variations {
+
+SyntheticTrialGroup::SyntheticTrialGroup(uint32_t trial, uint32_t group) {
+ id.name = trial;
+ id.group = group;
+}
+
+SyntheticTrialGroup::~SyntheticTrialGroup() {}
+
+} // namespace variations
diff --git a/chromium/components/variations/synthetic_trials.h b/chromium/components/variations/synthetic_trials.h
new file mode 100644
index 00000000000..fa47e64ba76
--- /dev/null
+++ b/chromium/components/variations/synthetic_trials.h
@@ -0,0 +1,43 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_SYNTHETIC_TRIALS_H_
+#define COMPONENTS_VARIATIONS_SYNTHETIC_TRIALS_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/time/time.h"
+#include "components/variations/active_field_trials.h"
+
+namespace variations {
+
+// A Field Trial and its selected group, which represent a particular
+// Chrome configuration state. For example, the trial name could map to
+// a preference name, and the group name could map to a preference value.
+struct SyntheticTrialGroup {
+ public:
+ SyntheticTrialGroup(uint32_t trial, uint32_t group);
+ ~SyntheticTrialGroup();
+
+ ActiveGroupId id;
+ base::TimeTicks start_time;
+};
+
+// Interface class to observe changes to synthetic trials in MetricsService.
+class SyntheticTrialObserver {
+ public:
+ // Called when the list of synthetic field trial groups has changed.
+ virtual void OnSyntheticTrialsChanged(
+ const std::vector<SyntheticTrialGroup>& groups) = 0;
+
+ protected:
+ virtual ~SyntheticTrialObserver() {}
+};
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_SYNTHETIC_TRIALS_H_
diff --git a/chromium/components/variations/variations_associated_data.cc b/chromium/components/variations/variations_associated_data.cc
new file mode 100644
index 00000000000..4f0b471a5d0
--- /dev/null
+++ b/chromium/components/variations/variations_associated_data.cc
@@ -0,0 +1,238 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/variations_associated_data.h"
+
+#include <map>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+
+namespace variations {
+
+namespace {
+
+// The internal singleton accessor for the map, used to keep it thread-safe.
+class GroupMapAccessor {
+ public:
+ typedef std::map<ActiveGroupId, VariationID, ActiveGroupIdCompare>
+ GroupToIDMap;
+
+ // Retrieve the singleton.
+ static GroupMapAccessor* GetInstance() {
+ return base::Singleton<GroupMapAccessor>::get();
+ }
+
+ // Note that this normally only sets the ID for a group the first time, unless
+ // |force| is set to true, in which case it will always override it.
+ void AssociateID(IDCollectionKey key,
+ const ActiveGroupId& group_identifier,
+ const VariationID id,
+ const bool force) {
+#if !defined(NDEBUG)
+ DCHECK_EQ(4, ID_COLLECTION_COUNT);
+ // Ensure that at most one of the trigger/non-trigger web property IDs are
+ // set.
+ if (key == GOOGLE_WEB_PROPERTIES || key == GOOGLE_WEB_PROPERTIES_TRIGGER) {
+ IDCollectionKey other_key = key == GOOGLE_WEB_PROPERTIES ?
+ GOOGLE_WEB_PROPERTIES_TRIGGER : GOOGLE_WEB_PROPERTIES;
+ DCHECK_EQ(EMPTY_ID, GetID(other_key, group_identifier));
+ }
+
+ // Validate that all collections with this |group_identifier| have the same
+ // associated ID.
+ for (int i = 0; i < ID_COLLECTION_COUNT; ++i) {
+ IDCollectionKey other_key = static_cast<IDCollectionKey>(i);
+ if (other_key == key)
+ continue;
+ VariationID other_id = GetID(other_key, group_identifier);
+ DCHECK(other_id == EMPTY_ID || other_id == id);
+ }
+#endif
+
+ base::AutoLock scoped_lock(lock_);
+
+ GroupToIDMap* group_to_id_map = GetGroupToIDMap(key);
+ if (force ||
+ group_to_id_map->find(group_identifier) == group_to_id_map->end())
+ (*group_to_id_map)[group_identifier] = id;
+ }
+
+ VariationID GetID(IDCollectionKey key,
+ const ActiveGroupId& group_identifier) {
+ base::AutoLock scoped_lock(lock_);
+ GroupToIDMap* group_to_id_map = GetGroupToIDMap(key);
+ GroupToIDMap::const_iterator it = group_to_id_map->find(group_identifier);
+ if (it == group_to_id_map->end())
+ return EMPTY_ID;
+ return it->second;
+ }
+
+ void ClearAllMapsForTesting() {
+ base::AutoLock scoped_lock(lock_);
+
+ for (int i = 0; i < ID_COLLECTION_COUNT; ++i) {
+ GroupToIDMap* map = GetGroupToIDMap(static_cast<IDCollectionKey>(i));
+ DCHECK(map);
+ map->clear();
+ }
+ }
+
+ private:
+ friend struct base::DefaultSingletonTraits<GroupMapAccessor>;
+
+ // Retrieves the GroupToIDMap for |key|.
+ GroupToIDMap* GetGroupToIDMap(IDCollectionKey key) {
+ return &group_to_id_maps_[key];
+ }
+
+ GroupMapAccessor() {
+ group_to_id_maps_.resize(ID_COLLECTION_COUNT);
+ }
+ ~GroupMapAccessor() {}
+
+ base::Lock lock_;
+ std::vector<GroupToIDMap> group_to_id_maps_;
+
+ DISALLOW_COPY_AND_ASSIGN(GroupMapAccessor);
+};
+
+// Singleton helper class that keeps track of the parameters of all variations
+// and ensures access to these is thread-safe.
+class VariationsParamAssociator {
+ public:
+ typedef std::pair<std::string, std::string> VariationKey;
+ typedef std::map<std::string, std::string> VariationParams;
+
+ // Retrieve the singleton.
+ static VariationsParamAssociator* GetInstance() {
+ return base::Singleton<VariationsParamAssociator>::get();
+ }
+
+ bool AssociateVariationParams(const std::string& trial_name,
+ const std::string& group_name,
+ const VariationParams& params) {
+ base::AutoLock scoped_lock(lock_);
+
+ if (base::FieldTrialList::IsTrialActive(trial_name))
+ return false;
+
+ const VariationKey key(trial_name, group_name);
+ if (ContainsKey(variation_params_, key))
+ return false;
+
+ variation_params_[key] = params;
+ return true;
+ }
+
+ bool GetVariationParams(const std::string& trial_name,
+ VariationParams* params) {
+ base::AutoLock scoped_lock(lock_);
+
+ const std::string group_name =
+ base::FieldTrialList::FindFullName(trial_name);
+ const VariationKey key(trial_name, group_name);
+ if (!ContainsKey(variation_params_, key))
+ return false;
+
+ *params = variation_params_[key];
+ return true;
+ }
+
+ void ClearAllParamsForTesting() {
+ base::AutoLock scoped_lock(lock_);
+ variation_params_.clear();
+ }
+
+ private:
+ friend struct base::DefaultSingletonTraits<VariationsParamAssociator>;
+
+ VariationsParamAssociator() {}
+ ~VariationsParamAssociator() {}
+
+ base::Lock lock_;
+ std::map<VariationKey, VariationParams> variation_params_;
+
+ DISALLOW_COPY_AND_ASSIGN(VariationsParamAssociator);
+};
+
+} // namespace
+
+void AssociateGoogleVariationID(IDCollectionKey key,
+ const std::string& trial_name,
+ const std::string& group_name,
+ VariationID id) {
+ GroupMapAccessor::GetInstance()->AssociateID(
+ key, MakeActiveGroupId(trial_name, group_name), id, false);
+}
+
+void AssociateGoogleVariationIDForce(IDCollectionKey key,
+ const std::string& trial_name,
+ const std::string& group_name,
+ VariationID id) {
+ AssociateGoogleVariationIDForceHashes(
+ key, MakeActiveGroupId(trial_name, group_name), id);
+}
+
+void AssociateGoogleVariationIDForceHashes(IDCollectionKey key,
+ const ActiveGroupId& active_group,
+ VariationID id) {
+ GroupMapAccessor::GetInstance()->AssociateID(key, active_group, id, true);
+}
+
+VariationID GetGoogleVariationID(IDCollectionKey key,
+ const std::string& trial_name,
+ const std::string& group_name) {
+ return GetGoogleVariationIDFromHashes(
+ key, MakeActiveGroupId(trial_name, group_name));
+}
+
+VariationID GetGoogleVariationIDFromHashes(
+ IDCollectionKey key,
+ const ActiveGroupId& active_group) {
+ return GroupMapAccessor::GetInstance()->GetID(key, active_group);
+}
+
+bool AssociateVariationParams(
+ const std::string& trial_name,
+ const std::string& group_name,
+ const std::map<std::string, std::string>& params) {
+ return VariationsParamAssociator::GetInstance()->AssociateVariationParams(
+ trial_name, group_name, params);
+}
+
+bool GetVariationParams(const std::string& trial_name,
+ std::map<std::string, std::string>* params) {
+ return VariationsParamAssociator::GetInstance()->GetVariationParams(
+ trial_name, params);
+}
+
+std::string GetVariationParamValue(const std::string& trial_name,
+ const std::string& param_name) {
+ std::map<std::string, std::string> params;
+ if (GetVariationParams(trial_name, &params)) {
+ std::map<std::string, std::string>::iterator it = params.find(param_name);
+ if (it != params.end())
+ return it->second;
+ }
+ return std::string();
+}
+
+// Functions below are exposed for testing explicitly behind this namespace.
+// They simply wrap existing functions in this file.
+namespace testing {
+
+void ClearAllVariationIDs() {
+ GroupMapAccessor::GetInstance()->ClearAllMapsForTesting();
+}
+
+void ClearAllVariationParams() {
+ VariationsParamAssociator::GetInstance()->ClearAllParamsForTesting();
+}
+
+} // namespace testing
+
+} // namespace variations
diff --git a/chromium/components/variations/variations_associated_data.h b/chromium/components/variations/variations_associated_data.h
new file mode 100644
index 00000000000..79f31a31e05
--- /dev/null
+++ b/chromium/components/variations/variations_associated_data.h
@@ -0,0 +1,149 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_VARIATIONS_ASSOCIATED_DATA_H_
+#define COMPONENTS_VARIATIONS_VARIATIONS_ASSOCIATED_DATA_H_
+
+#include <map>
+#include <string>
+
+#include "base/metrics/field_trial.h"
+#include "components/variations/active_field_trials.h"
+
+// This file provides various helpers that extend the functionality around
+// base::FieldTrial.
+//
+// This includes several simple APIs to handle getting and setting additional
+// data related to Chrome variations, such as parameters and Google variation
+// IDs. These APIs are meant to extend the base::FieldTrial APIs to offer extra
+// functionality that is not offered by the simpler base::FieldTrial APIs.
+//
+// The AssociateGoogleVariationID and AssociateVariationParams functions are
+// generally meant to be called by the VariationsService based on server-side
+// variation configs, but may also be used for client-only field trials by
+// invoking them directly after appending all the groups to a FieldTrial.
+//
+// Experiment code can then use the getter APIs to retrieve variation parameters
+// or IDs:
+//
+// std::map<std::string, std::string> params;
+// if (GetVariationParams("trial", &params)) {
+// // use |params|
+// }
+//
+// std::string value = GetVariationParamValue("trial", "param_x");
+// // use |value|, which will be "" if it does not exist
+//
+// VariationID id = GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, "trial",
+// "group1");
+// if (id != variations::kEmptyID) {
+// // use |id|
+// }
+
+namespace variations {
+
+typedef int VariationID;
+
+const VariationID EMPTY_ID = 0;
+
+// A key into the Associate/Get methods for VariationIDs. This is used to create
+// separate ID associations for separate parties interested in VariationIDs.
+enum IDCollectionKey {
+ // This collection is used by Google web properties, transmitted through the
+ // X-Client-Data header.
+ GOOGLE_WEB_PROPERTIES,
+ // This collection is used by Google web properties for IDs that trigger
+ // server side experimental behavior, transmitted through the
+ // X-Client-Data header.
+ GOOGLE_WEB_PROPERTIES_TRIGGER,
+ // This collection is used by Google update services, transmitted through the
+ // Google Update experiment labels.
+ GOOGLE_UPDATE_SERVICE,
+ // This collection is used by Chrome Sync services, transmitted through the
+ // Chrome Sync experiment labels.
+ CHROME_SYNC_SERVICE,
+ // The total count of collections.
+ ID_COLLECTION_COUNT,
+};
+
+// Associate a variations::VariationID value with a FieldTrial group for
+// collection |key|. If an id was previously set for |trial_name| and
+// |group_name|, this does nothing. The group is denoted by |trial_name| and
+// |group_name|. This must be called whenever a FieldTrial is prepared (create
+// the trial and append groups) and needs to have a variations::VariationID
+// associated with it so Google servers can recognize the FieldTrial.
+// Thread safe.
+void AssociateGoogleVariationID(IDCollectionKey key,
+ const std::string& trial_name,
+ const std::string& group_name,
+ VariationID id);
+
+// As above, but overwrites any previously set id. Thread safe.
+void AssociateGoogleVariationIDForce(IDCollectionKey key,
+ const std::string& trial_name,
+ const std::string& group_name,
+ VariationID id);
+
+// As above, but takes an ActiveGroupId hash pair, rather than the string names.
+void AssociateGoogleVariationIDForceHashes(IDCollectionKey key,
+ const ActiveGroupId& active_group,
+ VariationID id);
+
+// Retrieve the variations::VariationID associated with a FieldTrial group for
+// collection |key|. The group is denoted by |trial_name| and |group_name|.
+// This will return variations::kEmptyID if there is currently no associated ID
+// for the named group. This API can be nicely combined with
+// FieldTrial::GetActiveFieldTrialGroups() to enumerate the variation IDs for
+// all active FieldTrial groups. Thread safe.
+VariationID GetGoogleVariationID(IDCollectionKey key,
+ const std::string& trial_name,
+ const std::string& group_name);
+
+// Same as GetGoogleVariationID(), but takes in a hashed |active_group| rather
+// than the string trial and group name.
+VariationID GetGoogleVariationIDFromHashes(IDCollectionKey key,
+ const ActiveGroupId& active_group);
+
+// Associates the specified set of key-value |params| with the variation
+// specified by |trial_name| and |group_name|. Fails and returns false if the
+// specified variation already has params associated with it or the field trial
+// is already active (group() has been called on it). Thread safe.
+bool AssociateVariationParams(const std::string& trial_name,
+ const std::string& group_name,
+ const std::map<std::string, std::string>& params);
+
+// Retrieves the set of key-value |params| for the variation associated with
+// the specified field trial, based on its selected group. If the field trial
+// does not exist or its selected group does not have any parameters associated
+// with it, returns false and does not modify |params|. Calling this function
+// will result in the field trial being marked as active if found (i.e. group()
+// will be called on it), if it wasn't already. Currently, this information is
+// only available from the browser process. Thread safe.
+bool GetVariationParams(const std::string& trial_name,
+ std::map<std::string, std::string>* params);
+
+// Retrieves a specific parameter value corresponding to |param_name| for the
+// variation associated with the specified field trial, based on its selected
+// group. If the field trial does not exist or the specified parameter does not
+// exist, returns an empty string. Calling this function will result in the
+// field trial being marked as active if found (i.e. group() will be called on
+// it), if it wasn't already. Currently, this information is only available from
+// the browser process. Thread safe.
+std::string GetVariationParamValue(const std::string& trial_name,
+ const std::string& param_name);
+
+// Expose some functions for testing.
+namespace testing {
+
+// Clears all of the mapped associations.
+void ClearAllVariationIDs();
+
+// Clears all of the associated params.
+void ClearAllVariationParams();
+
+} // namespace testing
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_VARIATIONS_ASSOCIATED_DATA_H_
diff --git a/chromium/components/variations/variations_associated_data_unittest.cc b/chromium/components/variations/variations_associated_data_unittest.cc
new file mode 100644
index 00000000000..470490dacdd
--- /dev/null
+++ b/chromium/components/variations/variations_associated_data_unittest.cc
@@ -0,0 +1,349 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/variations_associated_data.h"
+
+#include "base/macros.h"
+#include "base/metrics/field_trial.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace variations {
+
+namespace {
+
+const VariationID TEST_VALUE_A = 3300200;
+const VariationID TEST_VALUE_B = 3300201;
+
+// Convenience helper to retrieve the variations::VariationID for a FieldTrial.
+// Note that this will do the group assignment in |trial| if not already done.
+VariationID GetIDForTrial(IDCollectionKey key, base::FieldTrial* trial) {
+ return GetGoogleVariationID(key, trial->trial_name(), trial->group_name());
+}
+
+// Call FieldTrialList::FactoryGetFieldTrial() with a future expiry date.
+scoped_refptr<base::FieldTrial> CreateFieldTrial(
+ const std::string& trial_name,
+ int total_probability,
+ const std::string& default_group_name,
+ int* default_group_number) {
+ return base::FieldTrialList::FactoryGetFieldTrial(
+ trial_name, total_probability, default_group_name,
+ base::FieldTrialList::kNoExpirationYear, 1, 1,
+ base::FieldTrial::SESSION_RANDOMIZED, default_group_number);
+}
+
+} // namespace
+
+class VariationsAssociatedDataTest : public ::testing::Test {
+ public:
+ VariationsAssociatedDataTest() : field_trial_list_(NULL) {
+ }
+
+ ~VariationsAssociatedDataTest() override {
+ // Ensure that the maps are cleared between tests, since they are stored as
+ // process singletons.
+ testing::ClearAllVariationIDs();
+ testing::ClearAllVariationParams();
+ }
+
+ private:
+ base::FieldTrialList field_trial_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(VariationsAssociatedDataTest);
+};
+
+// Test that if the trial is immediately disabled, GetGoogleVariationID just
+// returns the empty ID.
+TEST_F(VariationsAssociatedDataTest, DisableImmediately) {
+ int default_group_number = -1;
+ scoped_refptr<base::FieldTrial> trial(
+ CreateFieldTrial("trial", 100, "default", &default_group_number));
+
+ ASSERT_EQ(default_group_number, trial->group());
+ ASSERT_EQ(EMPTY_ID, GetIDForTrial(GOOGLE_WEB_PROPERTIES, trial.get()));
+}
+
+// Test that successfully associating the FieldTrial with some ID, and then
+// disabling the FieldTrial actually makes GetGoogleVariationID correctly
+// return the empty ID.
+TEST_F(VariationsAssociatedDataTest, DisableAfterInitialization) {
+ const std::string default_name = "default";
+ const std::string non_default_name = "non_default";
+
+ scoped_refptr<base::FieldTrial> trial(
+ CreateFieldTrial("trial", 100, default_name, NULL));
+
+ trial->AppendGroup(non_default_name, 100);
+ AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial->trial_name(),
+ default_name, TEST_VALUE_A);
+ AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial->trial_name(),
+ non_default_name, TEST_VALUE_B);
+ trial->Disable();
+ ASSERT_EQ(default_name, trial->group_name());
+ ASSERT_EQ(TEST_VALUE_A, GetIDForTrial(GOOGLE_WEB_PROPERTIES, trial.get()));
+}
+
+// Test various successful association cases.
+TEST_F(VariationsAssociatedDataTest, AssociateGoogleVariationID) {
+ const std::string default_name1 = "default";
+ scoped_refptr<base::FieldTrial> trial_true(
+ CreateFieldTrial("d1", 10, default_name1, NULL));
+ const std::string winner = "TheWinner";
+ int winner_group = trial_true->AppendGroup(winner, 10);
+
+ // Set GoogleVariationIDs so we can verify that they were chosen correctly.
+ AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial_true->trial_name(),
+ default_name1, TEST_VALUE_A);
+ AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial_true->trial_name(),
+ winner, TEST_VALUE_B);
+
+ EXPECT_EQ(winner_group, trial_true->group());
+ EXPECT_EQ(winner, trial_true->group_name());
+ EXPECT_EQ(TEST_VALUE_B,
+ GetIDForTrial(GOOGLE_WEB_PROPERTIES, trial_true.get()));
+
+ const std::string default_name2 = "default2";
+ scoped_refptr<base::FieldTrial> trial_false(
+ CreateFieldTrial("d2", 10, default_name2, NULL));
+ const std::string loser = "ALoser";
+ const int loser_group = trial_false->AppendGroup(loser, 0);
+
+ AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial_false->trial_name(),
+ default_name2, TEST_VALUE_A);
+ AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial_false->trial_name(),
+ loser, TEST_VALUE_B);
+
+ EXPECT_NE(loser_group, trial_false->group());
+ EXPECT_EQ(TEST_VALUE_A,
+ GetIDForTrial(GOOGLE_WEB_PROPERTIES, trial_false.get()));
+}
+
+// Test that not associating a FieldTrial with any IDs ensure that the empty ID
+// will be returned.
+TEST_F(VariationsAssociatedDataTest, NoAssociation) {
+ const std::string default_name = "default";
+ scoped_refptr<base::FieldTrial> no_id_trial(
+ CreateFieldTrial("d3", 10, default_name, NULL));
+
+ const std::string winner = "TheWinner";
+ const int winner_group = no_id_trial->AppendGroup(winner, 10);
+
+ // Ensure that despite the fact that a normal winner is elected, it does not
+ // have a valid VariationID associated with it.
+ EXPECT_EQ(winner_group, no_id_trial->group());
+ EXPECT_EQ(winner, no_id_trial->group_name());
+ EXPECT_EQ(EMPTY_ID, GetIDForTrial(GOOGLE_WEB_PROPERTIES, no_id_trial.get()));
+}
+
+// Ensure that the AssociateGoogleVariationIDForce works as expected.
+TEST_F(VariationsAssociatedDataTest, ForceAssociation) {
+ EXPECT_EQ(EMPTY_ID,
+ GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, "trial", "group"));
+ AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES, "trial", "group",
+ TEST_VALUE_A);
+ EXPECT_EQ(TEST_VALUE_A,
+ GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, "trial", "group"));
+ AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES, "trial", "group",
+ TEST_VALUE_B);
+ EXPECT_EQ(TEST_VALUE_A,
+ GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, "trial", "group"));
+ AssociateGoogleVariationIDForce(GOOGLE_WEB_PROPERTIES, "trial", "group",
+ TEST_VALUE_B);
+ EXPECT_EQ(TEST_VALUE_B,
+ GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, "trial", "group"));
+}
+
+// Ensure that two collections can coexist without affecting each other.
+TEST_F(VariationsAssociatedDataTest, CollectionsCoexist) {
+ const std::string default_name = "default";
+ int default_group_number = -1;
+ scoped_refptr<base::FieldTrial> trial_true(
+ CreateFieldTrial("d1", 10, default_name, &default_group_number));
+ ASSERT_EQ(default_group_number, trial_true->group());
+ ASSERT_EQ(default_name, trial_true->group_name());
+
+ EXPECT_EQ(EMPTY_ID,
+ GetIDForTrial(GOOGLE_WEB_PROPERTIES, trial_true.get()));
+ EXPECT_EQ(EMPTY_ID,
+ GetIDForTrial(GOOGLE_WEB_PROPERTIES_TRIGGER, trial_true.get()));
+ EXPECT_EQ(EMPTY_ID,
+ GetIDForTrial(GOOGLE_UPDATE_SERVICE, trial_true.get()));
+ EXPECT_EQ(EMPTY_ID,
+ GetIDForTrial(CHROME_SYNC_SERVICE, trial_true.get()));
+
+ AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial_true->trial_name(),
+ default_name, TEST_VALUE_A);
+ EXPECT_EQ(TEST_VALUE_A,
+ GetIDForTrial(GOOGLE_WEB_PROPERTIES, trial_true.get()));
+ EXPECT_EQ(EMPTY_ID,
+ GetIDForTrial(GOOGLE_UPDATE_SERVICE, trial_true.get()));
+ EXPECT_EQ(EMPTY_ID,
+ GetIDForTrial(CHROME_SYNC_SERVICE, trial_true.get()));
+
+ AssociateGoogleVariationID(GOOGLE_UPDATE_SERVICE, trial_true->trial_name(),
+ default_name, TEST_VALUE_A);
+ EXPECT_EQ(TEST_VALUE_A,
+ GetIDForTrial(GOOGLE_WEB_PROPERTIES, trial_true.get()));
+ EXPECT_EQ(TEST_VALUE_A,
+ GetIDForTrial(GOOGLE_UPDATE_SERVICE, trial_true.get()));
+ EXPECT_EQ(EMPTY_ID,
+ GetIDForTrial(CHROME_SYNC_SERVICE, trial_true.get()));
+
+ AssociateGoogleVariationID(CHROME_SYNC_SERVICE, trial_true->trial_name(),
+ default_name, TEST_VALUE_A);
+ EXPECT_EQ(TEST_VALUE_A,
+ GetIDForTrial(GOOGLE_WEB_PROPERTIES, trial_true.get()));
+ EXPECT_EQ(TEST_VALUE_A,
+ GetIDForTrial(GOOGLE_UPDATE_SERVICE, trial_true.get()));
+ EXPECT_EQ(TEST_VALUE_A,
+ GetIDForTrial(CHROME_SYNC_SERVICE, trial_true.get()));
+
+ trial_true = CreateFieldTrial("d2", 10, default_name, &default_group_number);
+ ASSERT_EQ(default_group_number, trial_true->group());
+ ASSERT_EQ(default_name, trial_true->group_name());
+
+ AssociateGoogleVariationID(GOOGLE_WEB_PROPERTIES_TRIGGER,
+ trial_true->trial_name(), default_name,
+ TEST_VALUE_A);
+ EXPECT_EQ(TEST_VALUE_A,
+ GetIDForTrial(GOOGLE_WEB_PROPERTIES_TRIGGER, trial_true.get()));
+ EXPECT_EQ(EMPTY_ID,
+ GetIDForTrial(GOOGLE_UPDATE_SERVICE, trial_true.get()));
+ EXPECT_EQ(EMPTY_ID,
+ GetIDForTrial(CHROME_SYNC_SERVICE, trial_true.get()));
+
+ AssociateGoogleVariationID(GOOGLE_UPDATE_SERVICE, trial_true->trial_name(),
+ default_name, TEST_VALUE_A);
+ EXPECT_EQ(TEST_VALUE_A,
+ GetIDForTrial(GOOGLE_WEB_PROPERTIES_TRIGGER, trial_true.get()));
+ EXPECT_EQ(TEST_VALUE_A,
+ GetIDForTrial(GOOGLE_UPDATE_SERVICE, trial_true.get()));
+ EXPECT_EQ(EMPTY_ID,
+ GetIDForTrial(CHROME_SYNC_SERVICE, trial_true.get()));
+
+ AssociateGoogleVariationID(CHROME_SYNC_SERVICE, trial_true->trial_name(),
+ default_name, TEST_VALUE_A);
+ EXPECT_EQ(TEST_VALUE_A,
+ GetIDForTrial(GOOGLE_WEB_PROPERTIES_TRIGGER, trial_true.get()));
+ EXPECT_EQ(TEST_VALUE_A,
+ GetIDForTrial(GOOGLE_UPDATE_SERVICE, trial_true.get()));
+ EXPECT_EQ(TEST_VALUE_A,
+ GetIDForTrial(CHROME_SYNC_SERVICE, trial_true.get()));
+}
+
+TEST_F(VariationsAssociatedDataTest, AssociateVariationParams) {
+ const std::string kTrialName = "AssociateVariationParams";
+
+ {
+ std::map<std::string, std::string> params;
+ params["a"] = "10";
+ params["b"] = "test";
+ ASSERT_TRUE(AssociateVariationParams(kTrialName, "A", params));
+ }
+ {
+ std::map<std::string, std::string> params;
+ params["a"] = "5";
+ ASSERT_TRUE(AssociateVariationParams(kTrialName, "B", params));
+ }
+
+ base::FieldTrialList::CreateFieldTrial(kTrialName, "B");
+ EXPECT_EQ("5", GetVariationParamValue(kTrialName, "a"));
+ EXPECT_EQ(std::string(), GetVariationParamValue(kTrialName, "b"));
+ EXPECT_EQ(std::string(), GetVariationParamValue(kTrialName, "x"));
+
+ std::map<std::string, std::string> params;
+ EXPECT_TRUE(GetVariationParams(kTrialName, &params));
+ EXPECT_EQ(1U, params.size());
+ EXPECT_EQ("5", params["a"]);
+}
+
+TEST_F(VariationsAssociatedDataTest, AssociateVariationParams_Fail) {
+ const std::string kTrialName = "AssociateVariationParams_Fail";
+ const std::string kGroupName = "A";
+
+ std::map<std::string, std::string> params;
+ params["a"] = "10";
+ ASSERT_TRUE(AssociateVariationParams(kTrialName, kGroupName, params));
+ params["a"] = "1";
+ params["b"] = "2";
+ ASSERT_FALSE(AssociateVariationParams(kTrialName, kGroupName, params));
+
+ base::FieldTrialList::CreateFieldTrial(kTrialName, kGroupName);
+ EXPECT_EQ("10", GetVariationParamValue(kTrialName, "a"));
+ EXPECT_EQ(std::string(), GetVariationParamValue(kTrialName, "b"));
+}
+
+TEST_F(VariationsAssociatedDataTest, AssociateVariationParams_TrialActiveFail) {
+ const std::string kTrialName = "AssociateVariationParams_TrialActiveFail";
+ base::FieldTrialList::CreateFieldTrial(kTrialName, "A");
+ ASSERT_EQ("A", base::FieldTrialList::FindFullName(kTrialName));
+
+ std::map<std::string, std::string> params;
+ params["a"] = "10";
+ EXPECT_FALSE(AssociateVariationParams(kTrialName, "B", params));
+ EXPECT_FALSE(AssociateVariationParams(kTrialName, "A", params));
+}
+
+TEST_F(VariationsAssociatedDataTest,
+ AssociateVariationParams_DoesntActivateTrial) {
+ const std::string kTrialName = "AssociateVariationParams_DoesntActivateTrial";
+
+ ASSERT_FALSE(base::FieldTrialList::IsTrialActive(kTrialName));
+ scoped_refptr<base::FieldTrial> trial(
+ CreateFieldTrial(kTrialName, 100, "A", NULL));
+ ASSERT_FALSE(base::FieldTrialList::IsTrialActive(kTrialName));
+
+ std::map<std::string, std::string> params;
+ params["a"] = "10";
+ EXPECT_TRUE(AssociateVariationParams(kTrialName, "A", params));
+ ASSERT_FALSE(base::FieldTrialList::IsTrialActive(kTrialName));
+}
+
+TEST_F(VariationsAssociatedDataTest, GetVariationParams_NoTrial) {
+ const std::string kTrialName = "GetVariationParams_NoParams";
+
+ std::map<std::string, std::string> params;
+ EXPECT_FALSE(GetVariationParams(kTrialName, &params));
+ EXPECT_EQ(std::string(), GetVariationParamValue(kTrialName, "x"));
+ EXPECT_EQ(std::string(), GetVariationParamValue(kTrialName, "y"));
+}
+
+TEST_F(VariationsAssociatedDataTest, GetVariationParams_NoParams) {
+ const std::string kTrialName = "GetVariationParams_NoParams";
+
+ base::FieldTrialList::CreateFieldTrial(kTrialName, "A");
+
+ std::map<std::string, std::string> params;
+ EXPECT_FALSE(GetVariationParams(kTrialName, &params));
+ EXPECT_EQ(std::string(), GetVariationParamValue(kTrialName, "x"));
+ EXPECT_EQ(std::string(), GetVariationParamValue(kTrialName, "y"));
+}
+
+TEST_F(VariationsAssociatedDataTest, GetVariationParams_ActivatesTrial) {
+ const std::string kTrialName = "GetVariationParams_ActivatesTrial";
+
+ ASSERT_FALSE(base::FieldTrialList::IsTrialActive(kTrialName));
+ scoped_refptr<base::FieldTrial> trial(
+ CreateFieldTrial(kTrialName, 100, "A", NULL));
+ ASSERT_FALSE(base::FieldTrialList::IsTrialActive(kTrialName));
+
+ std::map<std::string, std::string> params;
+ EXPECT_FALSE(GetVariationParams(kTrialName, &params));
+ ASSERT_TRUE(base::FieldTrialList::IsTrialActive(kTrialName));
+}
+
+TEST_F(VariationsAssociatedDataTest, GetVariationParamValue_ActivatesTrial) {
+ const std::string kTrialName = "GetVariationParamValue_ActivatesTrial";
+
+ ASSERT_FALSE(base::FieldTrialList::IsTrialActive(kTrialName));
+ scoped_refptr<base::FieldTrial> trial(
+ CreateFieldTrial(kTrialName, 100, "A", NULL));
+ ASSERT_FALSE(base::FieldTrialList::IsTrialActive(kTrialName));
+
+ std::map<std::string, std::string> params;
+ EXPECT_EQ(std::string(), GetVariationParamValue(kTrialName, "x"));
+ ASSERT_TRUE(base::FieldTrialList::IsTrialActive(kTrialName));
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/variations_experiment_util.cc b/chromium/components/variations/variations_experiment_util.cc
new file mode 100644
index 00000000000..9cf78bbabc0
--- /dev/null
+++ b/chromium/components/variations/variations_experiment_util.cc
@@ -0,0 +1,54 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/variations_experiment_util.h"
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+
+namespace variations {
+
+const base::char16 kExperimentLabelSeparator = ';';
+
+namespace {
+
+const char* const kDays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+
+const char* const kMonths[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+
+} // namespace
+
+base::string16 BuildExperimentDateString(const base::Time& current_time) {
+ // The Google Update experiment_labels timestamp format is:
+ // "DAY, DD0 MON YYYY HH0:MI0:SE0 TZ"
+ // DAY = 3 character day of week,
+ // DD0 = 2 digit day of month,
+ // MON = 3 character month of year,
+ // YYYY = 4 digit year,
+ // HH0 = 2 digit hour,
+ // MI0 = 2 digit minute,
+ // SE0 = 2 digit second,
+ // TZ = 3 character timezone
+ base::Time::Exploded then = {};
+ current_time.UTCExplode(&then);
+ then.year += 1;
+ DCHECK(then.HasValidValues());
+
+ return base::UTF8ToUTF16(
+ base::StringPrintf("%s, %02d %s %d %02d:%02d:%02d GMT",
+ kDays[then.day_of_week],
+ then.day_of_month,
+ kMonths[then.month - 1],
+ then.year,
+ then.hour,
+ then.minute,
+ then.second));
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/variations_experiment_util.h b/chromium/components/variations/variations_experiment_util.h
new file mode 100644
index 00000000000..c9c23f4006b
--- /dev/null
+++ b/chromium/components/variations/variations_experiment_util.h
@@ -0,0 +1,25 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_VARIATIONS_EXPERIMENT_UTIL_H_
+#define COMPONENTS_VARIATIONS_VARIATIONS_EXPERIMENT_UTIL_H_
+
+#include "base/strings/string16.h"
+
+namespace base {
+class Time;
+}
+
+namespace variations {
+
+// The separator used to separate items in experiment labels.
+extern const base::char16 kExperimentLabelSeparator;
+
+// Constructs a date string in the format understood by Google Update for the
+// |current_time| plus one year.
+base::string16 BuildExperimentDateString(const base::Time& current_time);
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_VARIATIONS_EXPERIMENT_UTIL_H_
diff --git a/chromium/components/variations/variations_http_header_provider.cc b/chromium/components/variations/variations_http_header_provider.cc
new file mode 100644
index 00000000000..2739aef04f1
--- /dev/null
+++ b/chromium/components/variations/variations_http_header_provider.cc
@@ -0,0 +1,232 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/variations_http_header_provider.h"
+
+#include <stddef.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/memory/singleton.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "components/variations/proto/client_variations.pb.h"
+
+namespace variations {
+
+// static
+VariationsHttpHeaderProvider* VariationsHttpHeaderProvider::GetInstance() {
+ return base::Singleton<VariationsHttpHeaderProvider>::get();
+}
+
+std::string VariationsHttpHeaderProvider::GetClientDataHeader() {
+ // Lazily initialize the header, if not already done, before attempting to
+ // transmit it.
+ InitVariationIDsCacheIfNeeded();
+
+ std::string variation_ids_header_copy;
+ {
+ base::AutoLock scoped_lock(lock_);
+ variation_ids_header_copy = variation_ids_header_;
+ }
+ return variation_ids_header_copy;
+}
+
+std::string VariationsHttpHeaderProvider::GetVariationsString() {
+ InitVariationIDsCacheIfNeeded();
+
+ // Construct a space-separated string with leading and trailing spaces from
+ // the variations set. Note: The ids in it will be in sorted order per the
+ // std::set contract.
+ std::string ids_string = " ";
+ {
+ base::AutoLock scoped_lock(lock_);
+ for (VariationID id : GetAllVariationIds()) {
+ ids_string.append(base::IntToString(id));
+ ids_string.push_back(' ');
+ }
+ }
+ return ids_string;
+}
+
+bool VariationsHttpHeaderProvider::SetDefaultVariationIds(
+ const std::string& variation_ids) {
+ default_variation_ids_set_.clear();
+ default_trigger_id_set_.clear();
+ for (const base::StringPiece& entry : base::SplitStringPiece(
+ variation_ids, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+ if (entry.empty()) {
+ default_variation_ids_set_.clear();
+ default_trigger_id_set_.clear();
+ return false;
+ }
+ bool trigger_id =
+ base::StartsWith(entry, "t", base::CompareCase::SENSITIVE);
+ // Remove the "t" prefix if it's there.
+ base::StringPiece trimmed_entry = trigger_id ? entry.substr(1) : entry;
+
+ int variation_id = 0;
+ if (!base::StringToInt(trimmed_entry, &variation_id)) {
+ default_variation_ids_set_.clear();
+ default_trigger_id_set_.clear();
+ return false;
+ }
+ if (trigger_id)
+ default_trigger_id_set_.insert(variation_id);
+ else
+ default_variation_ids_set_.insert(variation_id);
+ }
+ return true;
+}
+
+void VariationsHttpHeaderProvider::ResetForTesting() {
+ base::AutoLock scoped_lock(lock_);
+
+ // Stop observing field trials so that it can be restarted when this is
+ // re-inited. Note: This is a no-op if this is not currently observing.
+ base::FieldTrialList::RemoveObserver(this);
+ variation_ids_cache_initialized_ = false;
+}
+
+VariationsHttpHeaderProvider::VariationsHttpHeaderProvider()
+ : variation_ids_cache_initialized_(false) {}
+
+VariationsHttpHeaderProvider::~VariationsHttpHeaderProvider() {}
+
+void VariationsHttpHeaderProvider::OnFieldTrialGroupFinalized(
+ const std::string& trial_name,
+ const std::string& group_name) {
+ VariationID new_id =
+ GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial_name, group_name);
+ VariationID new_trigger_id = GetGoogleVariationID(
+ GOOGLE_WEB_PROPERTIES_TRIGGER, trial_name, group_name);
+ if (new_id == EMPTY_ID && new_trigger_id == EMPTY_ID)
+ return;
+
+ base::AutoLock scoped_lock(lock_);
+ if (new_id != EMPTY_ID)
+ variation_ids_set_.insert(new_id);
+ if (new_trigger_id != EMPTY_ID)
+ variation_trigger_ids_set_.insert(new_trigger_id);
+
+ UpdateVariationIDsHeaderValue();
+}
+
+void VariationsHttpHeaderProvider::OnSyntheticTrialsChanged(
+ const std::vector<SyntheticTrialGroup>& groups) {
+ base::AutoLock scoped_lock(lock_);
+
+ synthetic_variation_ids_set_.clear();
+ for (const SyntheticTrialGroup& group : groups) {
+ const VariationID id =
+ GetGoogleVariationIDFromHashes(GOOGLE_WEB_PROPERTIES, group.id);
+ if (id != EMPTY_ID)
+ synthetic_variation_ids_set_.insert(id);
+ }
+ UpdateVariationIDsHeaderValue();
+}
+
+void VariationsHttpHeaderProvider::InitVariationIDsCacheIfNeeded() {
+ base::AutoLock scoped_lock(lock_);
+ if (variation_ids_cache_initialized_)
+ return;
+
+ // Register for additional cache updates. This is done first to avoid a race
+ // that could cause registered FieldTrials to be missed.
+ DCHECK(base::MessageLoop::current());
+ base::FieldTrialList::AddObserver(this);
+
+ base::TimeTicks before_time = base::TimeTicks::Now();
+
+ base::FieldTrial::ActiveGroups initial_groups;
+ base::FieldTrialList::GetActiveFieldTrialGroups(&initial_groups);
+
+ for (const auto& entry : initial_groups) {
+ const VariationID id =
+ GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, entry.trial_name,
+ entry.group_name);
+ if (id != EMPTY_ID)
+ variation_ids_set_.insert(id);
+
+ const VariationID trigger_id =
+ GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_TRIGGER, entry.trial_name,
+ entry.group_name);
+
+ if (trigger_id != EMPTY_ID)
+ variation_trigger_ids_set_.insert(trigger_id);
+ }
+ UpdateVariationIDsHeaderValue();
+
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Variations.HeaderConstructionTime",
+ (base::TimeTicks::Now() - before_time).InMicroseconds(), 0,
+ base::TimeDelta::FromSeconds(1).InMicroseconds(), 50);
+
+ variation_ids_cache_initialized_ = true;
+}
+
+void VariationsHttpHeaderProvider::UpdateVariationIDsHeaderValue() {
+ lock_.AssertAcquired();
+
+ // The header value is a serialized protobuffer of Variation IDs which is
+ // base64 encoded before transmitting as a string.
+ variation_ids_header_.clear();
+
+ if (variation_ids_set_.empty() && default_variation_ids_set_.empty() &&
+ variation_trigger_ids_set_.empty() && default_trigger_id_set_.empty() &&
+ synthetic_variation_ids_set_.empty()) {
+ return;
+ }
+
+ // This is the bottleneck for the creation of the header, so validate the size
+ // here. Force a hard maximum on the ID count in case the Variations server
+ // returns too many IDs and DOSs receiving servers with large requests.
+ const size_t total_id_count =
+ variation_ids_set_.size() + variation_trigger_ids_set_.size();
+ DCHECK_LE(total_id_count, 10U);
+ UMA_HISTOGRAM_COUNTS_100("Variations.Headers.ExperimentCount",
+ total_id_count);
+ if (total_id_count > 20)
+ return;
+
+ std::set<VariationID> all_variation_ids_set = GetAllVariationIds();
+ std::set<VariationID> all_trigger_ids_set = default_trigger_id_set_;
+ for (VariationID id : variation_trigger_ids_set_)
+ all_trigger_ids_set.insert(id);
+
+ ClientVariations proto;
+ for (VariationID id : all_variation_ids_set)
+ proto.add_variation_id(id);
+ for (VariationID id : all_trigger_ids_set)
+ proto.add_trigger_variation_id(id);
+
+ std::string serialized;
+ proto.SerializeToString(&serialized);
+
+ std::string hashed;
+ base::Base64Encode(serialized, &hashed);
+ // If successful, swap the header value with the new one.
+ // Note that the list of IDs and the header could be temporarily out of sync
+ // if IDs are added as the header is recreated. The receiving servers are OK
+ // with such discrepancies.
+ variation_ids_header_ = hashed;
+}
+
+std::set<VariationID> VariationsHttpHeaderProvider::GetAllVariationIds() {
+ lock_.AssertAcquired();
+
+ std::set<VariationID> all_variation_ids_set = default_variation_ids_set_;
+ for (VariationID id : variation_ids_set_)
+ all_variation_ids_set.insert(id);
+ for (VariationID id : synthetic_variation_ids_set_)
+ all_variation_ids_set.insert(id);
+ return all_variation_ids_set;
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/variations_http_header_provider.h b/chromium/components/variations/variations_http_header_provider.h
new file mode 100644
index 00000000000..b92daca3a54
--- /dev/null
+++ b/chromium/components/variations/variations_http_header_provider.h
@@ -0,0 +1,119 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_VARIATIONS_HTTP_HEADER_PROVIDER_H_
+#define COMPONENTS_VARIATIONS_VARIATIONS_HTTP_HEADER_PROVIDER_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/metrics/field_trial.h"
+#include "base/synchronization/lock.h"
+#include "components/variations/synthetic_trials.h"
+#include "components/variations/variations_associated_data.h"
+
+namespace base {
+template <typename T>
+struct DefaultSingletonTraits;
+}
+
+namespace variations {
+
+// A helper class for maintaining client experiments and metrics state
+// transmitted in custom HTTP request headers.
+// This class is a thread-safe singleton.
+class VariationsHttpHeaderProvider : public base::FieldTrialList::Observer,
+ public SyntheticTrialObserver {
+ public:
+ static VariationsHttpHeaderProvider* GetInstance();
+
+ // Returns the value of the client data header, computing and caching it if
+ // necessary.
+ std::string GetClientDataHeader();
+
+ // Returns a space-separated string containing the list of current active
+ // variations (as would be reported in the |variation_id| repeated field of
+ // the ClientVariations proto). The returned string is guaranteed to have a
+ // a leading and trailing space, e.g. " 123 234 345 ".
+ std::string GetVariationsString();
+
+ // Sets *additional* variation ids and trigger variation ids to be encoded in
+ // the X-Client-Data request header. This is intended for development use to
+ // force a server side experiment id. |variation_ids| should be a
+ // comma-separated string of numeric experiment ids. If an id is prefixed
+ // with "t" it will be treated as a trigger experiment id.
+ bool SetDefaultVariationIds(const std::string& variation_ids);
+
+ // Resets any cached state for tests.
+ void ResetForTesting();
+
+ private:
+ friend struct base::DefaultSingletonTraits<VariationsHttpHeaderProvider>;
+
+ FRIEND_TEST_ALL_PREFIXES(VariationsHttpHeaderProviderTest,
+ SetDefaultVariationIds_Valid);
+ FRIEND_TEST_ALL_PREFIXES(VariationsHttpHeaderProviderTest,
+ SetDefaultVariationIds_Invalid);
+ FRIEND_TEST_ALL_PREFIXES(VariationsHttpHeaderProviderTest,
+ OnFieldTrialGroupFinalized);
+ FRIEND_TEST_ALL_PREFIXES(VariationsHttpHeaderProviderTest,
+ GetVariationsString);
+
+ VariationsHttpHeaderProvider();
+ ~VariationsHttpHeaderProvider() override;
+
+ // base::FieldTrialList::Observer:
+ // This will add the variation ID associated with |trial_name| and
+ // |group_name| to the variation ID cache.
+ void OnFieldTrialGroupFinalized(const std::string& trial_name,
+ const std::string& group_name) override;
+
+ // metrics::SyntheticTrialObserver:
+ void OnSyntheticTrialsChanged(
+ const std::vector<SyntheticTrialGroup>& groups) override;
+
+ // Prepares the variation IDs cache with initial values if not already done.
+ // This method also registers the caller with the FieldTrialList to receive
+ // new variation IDs.
+ void InitVariationIDsCacheIfNeeded();
+
+ // Takes whatever is currently in |variation_ids_set_| and recreates
+ // |variation_ids_header_| with it. Assumes the the |lock_| is currently
+ // held.
+ void UpdateVariationIDsHeaderValue();
+
+ // Returns the currently active set of variation ids, which includes any
+ // default values, synthetic variations and actual field trial variations.
+ std::set<VariationID> GetAllVariationIds();
+
+ // Guards |variation_ids_cache_initialized_|, |variation_ids_set_| and
+ // |variation_ids_header_|.
+ base::Lock lock_;
+
+ // Whether or not we've initialized the cache.
+ bool variation_ids_cache_initialized_;
+
+ // Keep a cache of variation IDs that are transmitted in headers to Google.
+ // This consists of a list of valid IDs, and the actual transmitted header.
+ std::set<VariationID> variation_ids_set_;
+ std::set<VariationID> variation_trigger_ids_set_;
+
+ // Provides the google experiment ids forced from command line.
+ std::set<VariationID> default_variation_ids_set_;
+ std::set<VariationID> default_trigger_id_set_;
+
+ // Variations ids from synthetic field trials.
+ std::set<VariationID> synthetic_variation_ids_set_;
+
+ std::string variation_ids_header_;
+
+ DISALLOW_COPY_AND_ASSIGN(VariationsHttpHeaderProvider);
+};
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_VARIATIONS_HTTP_HEADER_PROVIDER_H_
diff --git a/chromium/components/variations/variations_http_header_provider_unittest.cc b/chromium/components/variations/variations_http_header_provider_unittest.cc
new file mode 100644
index 00000000000..90552c84476
--- /dev/null
+++ b/chromium/components/variations/variations_http_header_provider_unittest.cc
@@ -0,0 +1,138 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/variations_http_header_provider.h"
+
+#include <string>
+
+#include "base/base64.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/field_trial.h"
+#include "base/run_loop.h"
+#include "components/variations/entropy_provider.h"
+#include "components/variations/proto/client_variations.pb.h"
+#include "components/variations/variations_associated_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace variations {
+
+namespace {
+
+// Decodes the variations header and extracts the variation ids.
+bool ExtractVariationIds(const std::string& variations,
+ std::set<VariationID>* variation_ids,
+ std::set<VariationID>* trigger_ids) {
+ std::string serialized_proto;
+ if (!base::Base64Decode(variations, &serialized_proto))
+ return false;
+ ClientVariations proto;
+ if (!proto.ParseFromString(serialized_proto))
+ return false;
+ for (int i = 0; i < proto.variation_id_size(); ++i)
+ variation_ids->insert(proto.variation_id(i));
+ for (int i = 0; i < proto.trigger_variation_id_size(); ++i)
+ trigger_ids->insert(proto.trigger_variation_id(i));
+ return true;
+}
+
+scoped_refptr<base::FieldTrial> CreateTrialAndAssociateId(
+ const std::string& trial_name,
+ const std::string& default_group_name,
+ IDCollectionKey key,
+ VariationID id) {
+ scoped_refptr<base::FieldTrial> trial(
+ base::FieldTrialList::CreateFieldTrial(trial_name, default_group_name));
+
+ AssociateGoogleVariationID(key, trial->trial_name(), trial->group_name(), id);
+
+ return trial;
+}
+
+} // namespace
+
+class VariationsHttpHeaderProviderTest : public ::testing::Test {
+ public:
+ VariationsHttpHeaderProviderTest() {}
+
+ ~VariationsHttpHeaderProviderTest() override {}
+
+ void TearDown() override { testing::ClearAllVariationIDs(); }
+};
+
+TEST_F(VariationsHttpHeaderProviderTest, SetDefaultVariationIds_Valid) {
+ base::MessageLoop loop;
+ VariationsHttpHeaderProvider provider;
+
+ // Valid experiment ids.
+ EXPECT_TRUE(provider.SetDefaultVariationIds("12,456,t789"));
+ provider.InitVariationIDsCacheIfNeeded();
+ std::string variations = provider.GetClientDataHeader();
+ EXPECT_FALSE(variations.empty());
+ std::set<VariationID> variation_ids;
+ std::set<VariationID> trigger_ids;
+ ASSERT_TRUE(ExtractVariationIds(variations, &variation_ids, &trigger_ids));
+ EXPECT_TRUE(variation_ids.find(12) != variation_ids.end());
+ EXPECT_TRUE(variation_ids.find(456) != variation_ids.end());
+ EXPECT_TRUE(trigger_ids.find(789) != trigger_ids.end());
+ EXPECT_FALSE(variation_ids.find(789) != variation_ids.end());
+}
+
+TEST_F(VariationsHttpHeaderProviderTest, SetDefaultVariationIds_Invalid) {
+ base::MessageLoop loop;
+ VariationsHttpHeaderProvider provider;
+
+ // Invalid experiment ids.
+ EXPECT_FALSE(provider.SetDefaultVariationIds("abcd12,456"));
+ provider.InitVariationIDsCacheIfNeeded();
+ EXPECT_TRUE(provider.GetClientDataHeader().empty());
+
+ // Invalid trigger experiment id
+ EXPECT_FALSE(provider.SetDefaultVariationIds("12,tabc456"));
+ provider.InitVariationIDsCacheIfNeeded();
+ EXPECT_TRUE(provider.GetClientDataHeader().empty());
+}
+
+TEST_F(VariationsHttpHeaderProviderTest, OnFieldTrialGroupFinalized) {
+ base::MessageLoop loop;
+ base::FieldTrialList field_trial_list(nullptr);
+ VariationsHttpHeaderProvider provider;
+ provider.InitVariationIDsCacheIfNeeded();
+
+ const std::string default_name = "default";
+ scoped_refptr<base::FieldTrial> trial_1(CreateTrialAndAssociateId(
+ "t1", default_name, GOOGLE_WEB_PROPERTIES, 123));
+
+ ASSERT_EQ(default_name, trial_1->group_name());
+
+ scoped_refptr<base::FieldTrial> trial_2(CreateTrialAndAssociateId(
+ "t2", default_name, GOOGLE_WEB_PROPERTIES_TRIGGER, 456));
+
+ ASSERT_EQ(default_name, trial_2->group_name());
+
+ // Run the message loop to make sure OnFieldTrialGroupFinalized is called for
+ // the two field trials.
+ base::RunLoop().RunUntilIdle();
+
+ std::string variations = provider.GetClientDataHeader();
+
+ std::set<VariationID> variation_ids;
+ std::set<VariationID> trigger_ids;
+ ASSERT_TRUE(ExtractVariationIds(variations, &variation_ids, &trigger_ids));
+ EXPECT_TRUE(variation_ids.find(123) != variation_ids.end());
+ EXPECT_TRUE(trigger_ids.find(456) != trigger_ids.end());
+}
+
+TEST_F(VariationsHttpHeaderProviderTest, GetVariationsString) {
+ base::MessageLoop loop;
+ base::FieldTrialList field_trial_list(nullptr);
+
+ CreateTrialAndAssociateId("t1", "g1", GOOGLE_WEB_PROPERTIES, 123);
+ CreateTrialAndAssociateId("t2", "g2", GOOGLE_WEB_PROPERTIES, 124);
+
+ VariationsHttpHeaderProvider provider;
+ provider.SetDefaultVariationIds("100,200");
+ EXPECT_EQ(" 100 123 124 200 ", provider.GetVariationsString());
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/variations_request_scheduler.cc b/chromium/components/variations/variations_request_scheduler.cc
new file mode 100644
index 00000000000..08d39d10ce2
--- /dev/null
+++ b/chromium/components/variations/variations_request_scheduler.cc
@@ -0,0 +1,73 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/variations_request_scheduler.h"
+
+#include <stddef.h>
+
+#include "base/strings/string_number_conversions.h"
+#include "build/build_config.h"
+#include "components/variations/variations_associated_data.h"
+
+namespace variations {
+
+VariationsRequestScheduler::VariationsRequestScheduler(
+ const base::Closure& task) : task_(task) {
+}
+
+VariationsRequestScheduler::~VariationsRequestScheduler() {
+}
+
+void VariationsRequestScheduler::Start() {
+ task_.Run();
+ timer_.Start(FROM_HERE, GetFetchPeriod(), task_);
+}
+
+void VariationsRequestScheduler::Reset() {
+ if (timer_.IsRunning())
+ timer_.Reset();
+ one_shot_timer_.Stop();
+}
+
+void VariationsRequestScheduler::ScheduleFetchShortly() {
+ // Reset the regular timer to avoid it triggering soon after.
+ Reset();
+ // The delay before attempting a fetch shortly, in minutes.
+ const int kFetchShortlyDelayMinutes = 5;
+ one_shot_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMinutes(kFetchShortlyDelayMinutes),
+ task_);
+}
+
+void VariationsRequestScheduler::OnAppEnterForeground() {
+ NOTREACHED() << "Attempted to OnAppEnterForeground on non-mobile device";
+}
+
+base::TimeDelta VariationsRequestScheduler::GetFetchPeriod() const {
+ // The fetch interval can be overridden by a variation param.
+ std::string period_min_str =
+ variations::GetVariationParamValue("VarationsServiceControl",
+ "fetch_period_min");
+ size_t period_min;
+ if (base::StringToSizeT(period_min_str, &period_min))
+ return base::TimeDelta::FromMinutes(period_min);
+
+ // The default fetch interval is every 30 minutes.
+ return base::TimeDelta::FromMinutes(30);
+}
+
+base::Closure VariationsRequestScheduler::task() const {
+ return task_;
+}
+
+#if !defined(OS_ANDROID) && !defined(OS_IOS)
+// static
+VariationsRequestScheduler* VariationsRequestScheduler::Create(
+ const base::Closure& task,
+ PrefService* local_state) {
+ return new VariationsRequestScheduler(task);
+}
+#endif // !defined(OS_ANDROID) && !defined(OS_IOS)
+
+} // namespace variations
diff --git a/chromium/components/variations/variations_request_scheduler.h b/chromium/components/variations/variations_request_scheduler.h
new file mode 100644
index 00000000000..2b945010690
--- /dev/null
+++ b/chromium/components/variations/variations_request_scheduler.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_VARIATIONS_REQUEST_SCHEDULER_H_
+#define COMPONENTS_VARIATIONS_VARIATIONS_REQUEST_SCHEDULER_H_
+
+#include "base/bind.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+
+class PrefService;
+
+namespace variations {
+
+// A helper class that makes VariationsService requests at the correct times.
+class VariationsRequestScheduler {
+ public:
+ virtual ~VariationsRequestScheduler();
+
+ // Starts the task. This can be a repeated event or a one-off.
+ virtual void Start();
+
+ // Resets the scheduler if it is currently on a timer.
+ virtual void Reset();
+
+ // Schedules a fetch shortly, for example to re-try the initial request which
+ // may have failed.
+ void ScheduleFetchShortly();
+
+ // Called when the application has been foregrounded. This may fetch a new
+ // seed.
+ virtual void OnAppEnterForeground();
+
+ // Factory method for this class.
+ static VariationsRequestScheduler* Create(const base::Closure& task,
+ PrefService* local_state);
+
+ protected:
+ // |task| is the closure to call when the scheduler deems ready.
+ explicit VariationsRequestScheduler(const base::Closure& task);
+
+ // Returns the time interval between variations seed fetches.
+ base::TimeDelta GetFetchPeriod() const;
+
+ // Getter for derived classes.
+ base::Closure task() const;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(VariationsRequestSchedulerTest,
+ ScheduleFetchShortly);
+
+ // The task scheduled by this class.
+ base::Closure task_;
+
+ // The timer used to repeatedly ping the server. Keep this as an instance
+ // member so if VariationsRequestScheduler goes out of scope, the timer is
+ // automatically canceled.
+ base::RepeatingTimer timer_;
+
+ // A one-shot timer used for scheduling out-of-band fetches.
+ base::OneShotTimer one_shot_timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(VariationsRequestScheduler);
+};
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_VARIATIONS_REQUEST_SCHEDULER_H_
diff --git a/chromium/components/variations/variations_request_scheduler_mobile.cc b/chromium/components/variations/variations_request_scheduler_mobile.cc
new file mode 100644
index 00000000000..18a3ad1eb19
--- /dev/null
+++ b/chromium/components/variations/variations_request_scheduler_mobile.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/variations_request_scheduler_mobile.h"
+
+#include "components/prefs/pref_service.h"
+#include "components/variations/pref_names.h"
+
+namespace variations {
+
+namespace {
+
+// Time before attempting a seed fetch after a ScheduleFetch(), in seconds.
+const int kScheduleFetchDelaySeconds = 5;
+
+} // namespace
+
+VariationsRequestSchedulerMobile::VariationsRequestSchedulerMobile(
+ const base::Closure& task,
+ PrefService* local_state) :
+ VariationsRequestScheduler(task), local_state_(local_state) {
+}
+
+VariationsRequestSchedulerMobile::~VariationsRequestSchedulerMobile() {
+}
+
+void VariationsRequestSchedulerMobile::Start() {
+ // Check the time of the last request. If it has been longer than the fetch
+ // period, run the task. Otherwise, do nothing. Note that no future requests
+ // are scheduled since it is unlikely that the mobile process would live long
+ // enough for the timer to fire.
+ const base::Time last_fetch_time = base::Time::FromInternalValue(
+ local_state_->GetInt64(prefs::kVariationsLastFetchTime));
+ if (base::Time::Now() > last_fetch_time + GetFetchPeriod()) {
+ last_request_time_ = base::Time::Now();
+ task().Run();
+ }
+}
+
+void VariationsRequestSchedulerMobile::Reset() {
+}
+
+void VariationsRequestSchedulerMobile::OnAppEnterForeground() {
+ // Verify we haven't just attempted a fetch (which has not completed). This
+ // is mainly used to verify we don't trigger a second fetch for the
+ // OnAppEnterForeground right after startup.
+ if (base::Time::Now() < last_request_time_ + GetFetchPeriod())
+ return;
+
+ // Since Start() launches a one-off execution, we can reuse it here. Also
+ // note that since Start() verifies that the seed needs to be refreshed, we
+ // do not verify here.
+ schedule_fetch_timer_.Start(
+ FROM_HERE,
+ base::TimeDelta::FromSeconds(kScheduleFetchDelaySeconds),
+ this,
+ &VariationsRequestSchedulerMobile::Start);
+}
+
+// static
+VariationsRequestScheduler* VariationsRequestScheduler::Create(
+ const base::Closure& task,
+ PrefService* local_state) {
+ return new VariationsRequestSchedulerMobile(task, local_state);
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/variations_request_scheduler_mobile.h b/chromium/components/variations/variations_request_scheduler_mobile.h
new file mode 100644
index 00000000000..5b46f1f1286
--- /dev/null
+++ b/chromium/components/variations/variations_request_scheduler_mobile.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_VARIATIONS_REQUEST_SCHEDULER_MOBILE_H_
+#define COMPONENTS_VARIATIONS_VARIATIONS_REQUEST_SCHEDULER_MOBILE_H_
+
+#include "base/bind.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "components/variations/variations_request_scheduler.h"
+
+class PrefService;
+
+namespace variations {
+
+// A specialized VariationsRequestScheduler that manages request cycles for
+// VariationsService on mobile platforms.
+class VariationsRequestSchedulerMobile : public VariationsRequestScheduler {
+ public:
+ // |task} is the closure to call when the scheduler deems ready. |local_state|
+ // is the PrefService that contains the time of the last fetch.
+ explicit VariationsRequestSchedulerMobile(const base::Closure& task,
+ PrefService* local_state);
+ ~VariationsRequestSchedulerMobile() override;
+
+ // Base class overrides.
+ void Start() override;
+ void Reset() override;
+ void OnAppEnterForeground() override;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(VariationsRequestSchedulerMobileTest,
+ OnAppEnterForegroundNoRun);
+ FRIEND_TEST_ALL_PREFIXES(VariationsRequestSchedulerMobileTest,
+ OnAppEnterForegroundRun);
+ FRIEND_TEST_ALL_PREFIXES(VariationsRequestSchedulerMobileTest,
+ OnAppEnterForegroundOnStartup);
+
+ // The local state instance that provides the last fetch time.
+ PrefService* local_state_;
+
+ // Timer used for triggering a delayed fetch for ScheduleFetch().
+ base::OneShotTimer schedule_fetch_timer_;
+
+ // The time the last seed request was initiated.
+ base::Time last_request_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(VariationsRequestSchedulerMobile);
+};
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_VARIATIONS_REQUEST_SCHEDULER_MOBILE_H_
diff --git a/chromium/components/variations/variations_request_scheduler_mobile_unittest.cc b/chromium/components/variations/variations_request_scheduler_mobile_unittest.cc
new file mode 100644
index 00000000000..3f1e8e94985
--- /dev/null
+++ b/chromium/components/variations/variations_request_scheduler_mobile_unittest.cc
@@ -0,0 +1,145 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/variations_request_scheduler_mobile.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/variations/pref_names.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace variations {
+
+namespace {
+
+// Simple method used to verify a Callback has been triggered.
+void Increment(int *n) {
+ (*n)++;
+}
+
+} // namespace
+
+TEST(VariationsRequestSchedulerMobileTest, StartNoRun) {
+ TestingPrefServiceSimple prefs;
+ // Initialize to as if it was just fetched. This means it should not run.
+ prefs.registry()->RegisterInt64Pref(prefs::kVariationsLastFetchTime,
+ base::Time::Now().ToInternalValue());
+ int executed = 0;
+ const base::Closure task = base::Bind(&Increment, &executed);
+ VariationsRequestSchedulerMobile scheduler(task, &prefs);
+ scheduler.Start();
+ // We expect it the task to not have triggered.
+ EXPECT_EQ(0, executed);
+}
+
+TEST(VariationsRequestSchedulerMobileTest, StartRun) {
+ TestingPrefServiceSimple prefs;
+ // Verify it doesn't take more than a day.
+ base::Time old = base::Time::Now() - base::TimeDelta::FromHours(24);
+ prefs.registry()->RegisterInt64Pref(prefs::kVariationsLastFetchTime,
+ old.ToInternalValue());
+ int executed = 0;
+ const base::Closure task = base::Bind(&Increment, &executed);
+ VariationsRequestSchedulerMobile scheduler(task, &prefs);
+ scheduler.Start();
+ // We expect the task to have triggered.
+ EXPECT_EQ(1, executed);
+}
+
+TEST(VariationsRequestSchedulerMobileTest, OnAppEnterForegroundNoRun) {
+ base::MessageLoopForUI message_loop_;
+
+ TestingPrefServiceSimple prefs;
+
+ // Initialize to as if it was just fetched. This means it should not run.
+ prefs.registry()->RegisterInt64Pref(prefs::kVariationsLastFetchTime,
+ base::Time::Now().ToInternalValue());
+ int executed = 0;
+ const base::Closure task = base::Bind(&Increment, &executed);
+ VariationsRequestSchedulerMobile scheduler(task, &prefs);
+
+ // Verify timer not running.
+ EXPECT_FALSE(scheduler.schedule_fetch_timer_.IsRunning());
+ scheduler.OnAppEnterForeground();
+
+ // Timer now running.
+ EXPECT_TRUE(scheduler.schedule_fetch_timer_.IsRunning());
+
+ // Force execution of the task on this timer to verify that the correct task
+ // was added to the timer.
+ scheduler.schedule_fetch_timer_.user_task().Run();
+
+ // The task should not execute because the seed was fetched too recently.
+ EXPECT_EQ(0, executed);
+}
+
+TEST(VariationsRequestSchedulerMobileTest, OnAppEnterForegroundRun) {
+ base::MessageLoopForUI message_loop_;
+
+ TestingPrefServiceSimple prefs;
+
+ base::Time old = base::Time::Now() - base::TimeDelta::FromHours(24);
+ prefs.registry()->RegisterInt64Pref(prefs::kVariationsLastFetchTime,
+ old.ToInternalValue());
+ int executed = 0;
+ const base::Closure task = base::Bind(&Increment, &executed);
+ VariationsRequestSchedulerMobile scheduler(task, &prefs);
+
+ // Verify timer not running.
+ EXPECT_FALSE(scheduler.schedule_fetch_timer_.IsRunning());
+ scheduler.OnAppEnterForeground();
+
+ // Timer now running.
+ EXPECT_TRUE(scheduler.schedule_fetch_timer_.IsRunning());
+
+ // Force execution of the task on this timer to verify that the correct task
+ // was added to the timer - this will verify that the right task is running.
+ scheduler.schedule_fetch_timer_.user_task().Run();
+
+ // We expect the input task to have triggered.
+ EXPECT_EQ(1, executed);
+}
+
+TEST(VariationsRequestSchedulerMobileTest, OnAppEnterForegroundOnStartup) {
+ base::MessageLoopForUI message_loop_;
+
+ TestingPrefServiceSimple prefs;
+
+ base::Time old = base::Time::Now() - base::TimeDelta::FromHours(24);
+ prefs.registry()->RegisterInt64Pref(prefs::kVariationsLastFetchTime,
+ old.ToInternalValue());
+ int executed = 0;
+ const base::Closure task = base::Bind(&Increment, &executed);
+ VariationsRequestSchedulerMobile scheduler(task, &prefs);
+
+ scheduler.Start();
+
+ // We expect the task to have triggered.
+ EXPECT_EQ(1, executed);
+
+ scheduler.OnAppEnterForeground();
+
+ EXPECT_FALSE(scheduler.schedule_fetch_timer_.IsRunning());
+ // We expect the input task to not have triggered again, so executed stays
+ // at 1.
+ EXPECT_EQ(1, executed);
+
+ // Simulate letting time pass.
+ const base::Time last_fetch_time = base::Time::FromInternalValue(
+ prefs.GetInt64(prefs::kVariationsLastFetchTime));
+ prefs.SetInt64(
+ prefs::kVariationsLastFetchTime,
+ (last_fetch_time - base::TimeDelta::FromHours(24)).ToInternalValue());
+ scheduler.last_request_time_ -= base::TimeDelta::FromHours(24);
+
+ scheduler.OnAppEnterForeground();
+ EXPECT_TRUE(scheduler.schedule_fetch_timer_.IsRunning());
+ scheduler.schedule_fetch_timer_.user_task().Run();
+ // This time it should execute the task.
+ EXPECT_EQ(2, executed);
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/variations_request_scheduler_unittest.cc b/chromium/components/variations/variations_request_scheduler_unittest.cc
new file mode 100644
index 00000000000..822c17b2d43
--- /dev/null
+++ b/chromium/components/variations/variations_request_scheduler_unittest.cc
@@ -0,0 +1,25 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/variations_request_scheduler.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/message_loop/message_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace variations {
+
+TEST(VariationsRequestSchedulerTest, ScheduleFetchShortly) {
+ base::MessageLoopForUI message_loop_;
+
+ const base::Closure task = base::Bind(&base::DoNothing);
+ VariationsRequestScheduler scheduler(task);
+ EXPECT_FALSE(scheduler.one_shot_timer_.IsRunning());
+
+ scheduler.ScheduleFetchShortly();
+ EXPECT_TRUE(scheduler.one_shot_timer_.IsRunning());
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/variations_seed_processor.cc b/chromium/components/variations/variations_seed_processor.cc
new file mode 100644
index 00000000000..f22848afa57
--- /dev/null
+++ b/chromium/components/variations/variations_seed_processor.cc
@@ -0,0 +1,310 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/variations_seed_processor.h"
+
+#include <stddef.h>
+
+#include <map>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/metrics/field_trial.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/variations/processed_study.h"
+#include "components/variations/study_filtering.h"
+#include "components/variations/variations_associated_data.h"
+
+namespace variations {
+
+namespace {
+
+// Associates the variations params of |experiment|, if present.
+void RegisterExperimentParams(const Study& study,
+ const Study_Experiment& experiment) {
+ std::map<std::string, std::string> params;
+ for (int i = 0; i < experiment.param_size(); ++i) {
+ if (experiment.param(i).has_name() && experiment.param(i).has_value())
+ params[experiment.param(i).name()] = experiment.param(i).value();
+ }
+ if (!params.empty())
+ AssociateVariationParams(study.name(), experiment.name(), params);
+}
+
+// If there are variation ids associated with |experiment|, register the
+// variation ids.
+void RegisterVariationIds(const Study_Experiment& experiment,
+ const std::string& trial_name) {
+ if (experiment.has_google_web_experiment_id()) {
+ const VariationID variation_id =
+ static_cast<VariationID>(experiment.google_web_experiment_id());
+ AssociateGoogleVariationIDForce(GOOGLE_WEB_PROPERTIES,
+ trial_name,
+ experiment.name(),
+ variation_id);
+ }
+ if (experiment.has_google_web_trigger_experiment_id()) {
+ const VariationID variation_id =
+ static_cast<VariationID>(experiment.google_web_trigger_experiment_id());
+ AssociateGoogleVariationIDForce(GOOGLE_WEB_PROPERTIES_TRIGGER,
+ trial_name,
+ experiment.name(),
+ variation_id);
+ }
+ if (experiment.has_google_update_experiment_id()) {
+ const VariationID variation_id =
+ static_cast<VariationID>(experiment.google_update_experiment_id());
+ AssociateGoogleVariationIDForce(GOOGLE_UPDATE_SERVICE,
+ trial_name,
+ experiment.name(),
+ variation_id);
+ }
+ if (experiment.has_chrome_sync_experiment_id()) {
+ const VariationID variation_id =
+ static_cast<VariationID>(experiment.chrome_sync_experiment_id());
+ AssociateGoogleVariationIDForce(CHROME_SYNC_SERVICE,
+ trial_name,
+ experiment.name(),
+ variation_id);
+ }
+}
+
+// Executes |callback| on every override defined by |experiment|.
+void ApplyUIStringOverrides(
+ const Study_Experiment& experiment,
+ const VariationsSeedProcessor::UIStringOverrideCallback& callback) {
+ for (int i = 0; i < experiment.override_ui_string_size(); ++i) {
+ const Study_Experiment_OverrideUIString& override =
+ experiment.override_ui_string(i);
+ callback.Run(override.name_hash(), base::UTF8ToUTF16(override.value()));
+ }
+}
+
+// Forces the specified |experiment| to be enabled in |study|.
+void ForceExperimentState(
+ const Study& study,
+ const Study_Experiment& experiment,
+ const VariationsSeedProcessor::UIStringOverrideCallback& override_callback,
+ base::FieldTrial* trial) {
+ RegisterExperimentParams(study, experiment);
+ RegisterVariationIds(experiment, study.name());
+ if (study.activation_type() == Study_ActivationType_ACTIVATION_AUTO) {
+ trial->group();
+ // UI Strings can only be overridden from ACTIVATION_AUTO experiments.
+ ApplyUIStringOverrides(experiment, override_callback);
+ }
+}
+
+// Registers feature overrides for the chosen experiment in the specified study.
+void RegisterFeatureOverrides(const ProcessedStudy& processed_study,
+ base::FieldTrial* trial,
+ base::FeatureList* feature_list) {
+ const std::string& group_name = trial->GetGroupNameWithoutActivation();
+ int experiment_index = processed_study.GetExperimentIndexByName(group_name);
+ // The field trial was defined from |study|, so the active experiment's name
+ // must be in the |study|.
+ DCHECK_NE(-1, experiment_index);
+
+ const Study& study = *processed_study.study();
+ const Study_Experiment& experiment = study.experiment(experiment_index);
+
+ // Process all the features to enable.
+ int feature_count = experiment.feature_association().enable_feature_size();
+ for (int i = 0; i < feature_count; ++i) {
+ feature_list->RegisterFieldTrialOverride(
+ experiment.feature_association().enable_feature(i),
+ base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial);
+ }
+
+ // Process all the features to disable.
+ feature_count = experiment.feature_association().disable_feature_size();
+ for (int i = 0; i < feature_count; ++i) {
+ feature_list->RegisterFieldTrialOverride(
+ experiment.feature_association().disable_feature(i),
+ base::FeatureList::OVERRIDE_DISABLE_FEATURE, trial);
+ }
+
+ // Check if this study enables/disables a single feature and uses explicit
+ // activation (i.e. the trial should be activated when queried). If so, ensure
+ // that groups that don't explicitly enable/disable that feature are still
+ // associated with it (i.e. so "Default" group gets reported).
+ //
+ // Note: This checks for ACTIVATION_EXPLICIT, since there is no reason to
+ // have this association with ACTIVATION_AUTO (where the trial starts active),
+ // as well as allowing flexibility to disable this behavior in the future from
+ // the server by introducing a new activation type.
+ if (!processed_study.single_feature_name().empty() &&
+ study.activation_type() == Study_ActivationType_ACTIVATION_EXPLICIT &&
+ !experiment.has_feature_association()) {
+ feature_list->RegisterFieldTrialOverride(
+ processed_study.single_feature_name(),
+ base::FeatureList::OVERRIDE_USE_DEFAULT, trial);
+ }
+}
+
+// Checks if |experiment| is associated with a forcing flag or feature and if it
+// is, returns whether it should be forced enabled based on the |command_line|
+// or |feature_list| state.
+bool ShouldForceExperiment(const Study_Experiment& experiment,
+ const base::CommandLine& command_line,
+ const base::FeatureList& feature_list) {
+ if (experiment.feature_association().has_forcing_feature_on()) {
+ return feature_list.IsFeatureOverriddenFromCommandLine(
+ experiment.feature_association().forcing_feature_on(),
+ base::FeatureList::OVERRIDE_ENABLE_FEATURE);
+ }
+ if (experiment.feature_association().has_forcing_feature_off()) {
+ return feature_list.IsFeatureOverriddenFromCommandLine(
+ experiment.feature_association().forcing_feature_off(),
+ base::FeatureList::OVERRIDE_DISABLE_FEATURE);
+ }
+ if (experiment.has_forcing_flag())
+ return command_line.HasSwitch(experiment.forcing_flag());
+ return false;
+}
+
+} // namespace
+
+VariationsSeedProcessor::VariationsSeedProcessor() {
+}
+
+VariationsSeedProcessor::~VariationsSeedProcessor() {
+}
+
+void VariationsSeedProcessor::CreateTrialsFromSeed(
+ const VariationsSeed& seed,
+ const std::string& locale,
+ const base::Time& reference_date,
+ const base::Version& version,
+ Study_Channel channel,
+ Study_FormFactor form_factor,
+ const std::string& hardware_class,
+ const std::string& session_consistency_country,
+ const std::string& permanent_consistency_country,
+ const UIStringOverrideCallback& override_callback,
+ base::FeatureList* feature_list) {
+ std::vector<ProcessedStudy> filtered_studies;
+ FilterAndValidateStudies(seed, locale, reference_date, version, channel,
+ form_factor, hardware_class,
+ session_consistency_country,
+ permanent_consistency_country, &filtered_studies);
+
+ for (size_t i = 0; i < filtered_studies.size(); ++i)
+ CreateTrialFromStudy(filtered_studies[i], override_callback, feature_list);
+}
+
+void VariationsSeedProcessor::CreateTrialFromStudy(
+ const ProcessedStudy& processed_study,
+ const UIStringOverrideCallback& override_callback,
+ base::FeatureList* feature_list) {
+ const Study& study = *processed_study.study();
+
+ // Check if any experiments need to be forced due to a command line
+ // flag. Force the first experiment with an existing flag.
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ for (int i = 0; i < study.experiment_size(); ++i) {
+ const Study_Experiment& experiment = study.experiment(i);
+ if (ShouldForceExperiment(experiment, *command_line, *feature_list)) {
+ base::FieldTrial* trial = base::FieldTrialList::CreateFieldTrial(
+ study.name(), experiment.name());
+ // If |trial| is null, then there might already be a trial forced to a
+ // different group (e.g. via --force-fieldtrials). Break out of the loop,
+ // but don't return, so that variation ids and params for the selected
+ // group will still be picked up.
+ if (!trial)
+ break;
+
+ if (experiment.feature_association().has_forcing_feature_on()) {
+ feature_list->AssociateReportingFieldTrial(
+ experiment.feature_association().forcing_feature_on(),
+ base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial);
+ } else if (experiment.feature_association().has_forcing_feature_off()) {
+ feature_list->AssociateReportingFieldTrial(
+ experiment.feature_association().forcing_feature_off(),
+ base::FeatureList::OVERRIDE_DISABLE_FEATURE, trial);
+ }
+ ForceExperimentState(study, experiment, override_callback, trial);
+ return;
+ }
+ }
+
+ uint32_t randomization_seed = 0;
+ base::FieldTrial::RandomizationType randomization_type =
+ base::FieldTrial::SESSION_RANDOMIZED;
+ if (study.has_consistency() &&
+ study.consistency() == Study_Consistency_PERMANENT &&
+ // If all assignments are to a single group, no need to enable one time
+ // randomization (which is more expensive to compute), since the result
+ // will be the same.
+ !processed_study.all_assignments_to_one_group()) {
+ randomization_type = base::FieldTrial::ONE_TIME_RANDOMIZED;
+ if (study.has_randomization_seed())
+ randomization_seed = study.randomization_seed();
+ }
+
+ // The trial is created without specifying an expiration date because the
+ // expiration check in field_trial.cc is based on the build date. Instead,
+ // the expiration check using |reference_date| is done explicitly below.
+ scoped_refptr<base::FieldTrial> trial(
+ base::FieldTrialList::FactoryGetFieldTrialWithRandomizationSeed(
+ study.name(), processed_study.total_probability(),
+ study.default_experiment_name(),
+ base::FieldTrialList::kNoExpirationYear, 1, 1, randomization_type,
+ randomization_seed, NULL));
+
+ bool has_overrides = false;
+ bool enables_or_disables_features = false;
+ for (int i = 0; i < study.experiment_size(); ++i) {
+ const Study_Experiment& experiment = study.experiment(i);
+ RegisterExperimentParams(study, experiment);
+
+ // Groups with forcing flags have probability 0 and will never be selected.
+ // Therefore, there's no need to add them to the field trial.
+ if (experiment.has_forcing_flag() ||
+ experiment.feature_association().has_forcing_feature_on() ||
+ experiment.feature_association().has_forcing_feature_off()) {
+ continue;
+ }
+
+ if (experiment.name() != study.default_experiment_name())
+ trial->AppendGroup(experiment.name(), experiment.probability_weight());
+
+ RegisterVariationIds(experiment, study.name());
+
+ has_overrides = has_overrides || experiment.override_ui_string_size() > 0;
+ if (experiment.feature_association().enable_feature_size() != 0 ||
+ experiment.feature_association().disable_feature_size() != 0) {
+ enables_or_disables_features = true;
+ }
+ }
+
+ trial->SetForced();
+
+ if (enables_or_disables_features)
+ RegisterFeatureOverrides(processed_study, trial.get(), feature_list);
+
+ if (processed_study.is_expired()) {
+ trial->Disable();
+ } else if (study.activation_type() == Study_ActivationType_ACTIVATION_AUTO) {
+ const std::string& group_name = trial->group_name();
+
+ // Don't try to apply overrides if none of the experiments in this study had
+ // any.
+ if (!has_overrides)
+ return;
+
+ // UI Strings can only be overridden from ACTIVATION_AUTO experiments.
+ int experiment_index = processed_study.GetExperimentIndexByName(group_name);
+
+ // The field trial was defined from |study|, so the active experiment's name
+ // must be in the |study|.
+ DCHECK_NE(-1, experiment_index);
+
+ ApplyUIStringOverrides(study.experiment(experiment_index),
+ override_callback);
+ }
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/variations_seed_processor.h b/chromium/components/variations/variations_seed_processor.h
new file mode 100644
index 00000000000..a7ddd5c3118
--- /dev/null
+++ b/chromium/components/variations/variations_seed_processor.h
@@ -0,0 +1,90 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_VARIATIONS_SEED_PROCESSOR_H_
+#define COMPONENTS_VARIATIONS_VARIATIONS_SEED_PROCESSOR_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/metrics/field_trial.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "base/version.h"
+#include "components/variations/proto/study.pb.h"
+#include "components/variations/proto/variations_seed.pb.h"
+
+namespace base {
+class FeatureList;
+}
+
+namespace variations {
+
+class ProcessedStudy;
+
+// Helper class to instantiate field trials from a variations seed.
+class VariationsSeedProcessor {
+ public:
+ typedef base::Callback<void(uint32_t, const base::string16&)>
+ UIStringOverrideCallback;
+
+ VariationsSeedProcessor();
+ virtual ~VariationsSeedProcessor();
+
+ // Creates field trials from the specified variations |seed|, based on the
+ // specified configuration, as specified in the parameters.
+ void CreateTrialsFromSeed(const VariationsSeed& seed,
+ const std::string& locale,
+ const base::Time& reference_date,
+ const base::Version& version,
+ Study_Channel channel,
+ Study_FormFactor form_factor,
+ const std::string& hardware_class,
+ const std::string& session_consistency_country,
+ const std::string& permanent_consistency_country,
+ const UIStringOverrideCallback& override_callback,
+ base::FeatureList* feature_list);
+
+ private:
+ friend class VariationsSeedProcessorTest;
+ FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest,
+ AllowForceGroupAndVariationId);
+ FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest,
+ AllowVariationIdWithForcingFlag);
+ FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest,
+ ForbidForceGroupWithVariationId);
+ FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest, ForceGroupWithFlag1);
+ FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest, ForceGroupWithFlag2);
+ FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest,
+ ForceGroup_ChooseFirstGroupWithFlag);
+ FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest,
+ ForceGroup_DontChooseGroupWithFlag);
+ FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest, IsStudyExpired);
+ FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest, VariationParams);
+ FRIEND_TEST_ALL_PREFIXES(VariationsSeedProcessorTest,
+ VariationParamsWithForcingFlag);
+
+ // Check if the |study| is only associated with platform Android/iOS and
+ // channel dev/canary. If so, forcing flag and variation id can both be set.
+ // (Otherwise, forcing_flag and variation_id are mutually exclusive.)
+ bool AllowVariationIdWithForcingFlag(const Study& study);
+
+ // Creates and registers a field trial from the |processed_study| data.
+ // Disables the trial if |processed_study.is_expired| is true.
+ void CreateTrialFromStudy(const ProcessedStudy& processed_study,
+ const UIStringOverrideCallback& override_callback,
+ base::FeatureList* feature_list);
+
+ DISALLOW_COPY_AND_ASSIGN(VariationsSeedProcessor);
+};
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_VARIATIONS_SEED_PROCESSOR_H_
diff --git a/chromium/components/variations/variations_seed_processor_unittest.cc b/chromium/components/variations/variations_seed_processor_unittest.cc
new file mode 100644
index 00000000000..cac84dc64a3
--- /dev/null
+++ b/chromium/components/variations/variations_seed_processor_unittest.cc
@@ -0,0 +1,754 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/variations_seed_processor.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <map>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/format_macros.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/variations/processed_study.h"
+#include "components/variations/variations_associated_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace variations {
+
+namespace {
+
+// Converts |time| to Study proto format.
+int64_t TimeToProtoTime(const base::Time& time) {
+ return (time - base::Time::UnixEpoch()).InSeconds();
+}
+
+// Constants for testing associating command line flags with trial groups.
+const char kFlagStudyName[] = "flag_test_trial";
+const char kFlagGroup1Name[] = "flag_group1";
+const char kFlagGroup2Name[] = "flag_group2";
+const char kNonFlagGroupName[] = "non_flag_group";
+const char kForcingFlag1[] = "flag_test1";
+const char kForcingFlag2[] = "flag_test2";
+
+const VariationID kExperimentId = 123;
+
+// Adds an experiment to |study| with the specified |name| and |probability|.
+Study_Experiment* AddExperiment(const std::string& name, int probability,
+ Study* study) {
+ Study_Experiment* experiment = study->add_experiment();
+ experiment->set_name(name);
+ experiment->set_probability_weight(probability);
+ return experiment;
+}
+
+// Populates |study| with test data used for testing associating command line
+// flags with trials groups. The study will contain three groups, a default
+// group that isn't associated with a flag, and two other groups, both
+// associated with different flags.
+Study CreateStudyWithFlagGroups(int default_group_probability,
+ int flag_group1_probability,
+ int flag_group2_probability) {
+ DCHECK_GE(default_group_probability, 0);
+ DCHECK_GE(flag_group1_probability, 0);
+ DCHECK_GE(flag_group2_probability, 0);
+ Study study;
+ study.set_name(kFlagStudyName);
+ study.set_default_experiment_name(kNonFlagGroupName);
+
+ AddExperiment(kNonFlagGroupName, default_group_probability, &study);
+ AddExperiment(kFlagGroup1Name, flag_group1_probability, &study)
+ ->set_forcing_flag(kForcingFlag1);
+ AddExperiment(kFlagGroup2Name, flag_group2_probability, &study)
+ ->set_forcing_flag(kForcingFlag2);
+
+ return study;
+}
+
+class TestOverrideStringCallback {
+ public:
+ typedef std::map<uint32_t, base::string16> OverrideMap;
+
+ TestOverrideStringCallback()
+ : callback_(base::Bind(&TestOverrideStringCallback::Override,
+ base::Unretained(this))) {}
+
+ virtual ~TestOverrideStringCallback() {}
+
+ const VariationsSeedProcessor::UIStringOverrideCallback& callback() const {
+ return callback_;
+ }
+
+ const OverrideMap& overrides() const { return overrides_; }
+
+ private:
+ void Override(uint32_t hash, const base::string16& string) {
+ overrides_[hash] = string;
+ }
+
+ VariationsSeedProcessor::UIStringOverrideCallback callback_;
+ OverrideMap overrides_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestOverrideStringCallback);
+};
+
+} // namespace
+
+class VariationsSeedProcessorTest : public ::testing::Test {
+ public:
+ VariationsSeedProcessorTest() {
+ }
+
+ ~VariationsSeedProcessorTest() override {
+ // Ensure that the maps are cleared between tests, since they are stored as
+ // process singletons.
+ testing::ClearAllVariationIDs();
+ testing::ClearAllVariationParams();
+
+ base::FeatureList::ClearInstanceForTesting();
+ }
+
+ bool CreateTrialFromStudy(const Study* study) {
+ return CreateTrialFromStudyWithFeatureList(study, &feature_list_);
+ }
+
+ bool CreateTrialFromStudyWithFeatureList(const Study* study,
+ base::FeatureList* feature_list) {
+ ProcessedStudy processed_study;
+ if (processed_study.Init(study, false)) {
+ VariationsSeedProcessor().CreateTrialFromStudy(
+ processed_study, override_callback_.callback(), feature_list);
+ return true;
+ }
+ return false;
+ }
+
+ protected:
+ base::FeatureList feature_list_;
+ TestOverrideStringCallback override_callback_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(VariationsSeedProcessorTest);
+};
+
+TEST_F(VariationsSeedProcessorTest, AllowForceGroupAndVariationId) {
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);
+
+ base::FieldTrialList field_trial_list(NULL);
+
+ Study study = CreateStudyWithFlagGroups(100, 0, 0);
+ study.mutable_experiment(1)->set_google_web_experiment_id(kExperimentId);
+
+ EXPECT_TRUE(CreateTrialFromStudy(&study));
+ EXPECT_EQ(kFlagGroup1Name,
+ base::FieldTrialList::FindFullName(kFlagStudyName));
+
+ VariationID id = GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, kFlagStudyName,
+ kFlagGroup1Name);
+ EXPECT_EQ(kExperimentId, id);
+}
+
+// Test that the group for kForcingFlag1 is forced.
+TEST_F(VariationsSeedProcessorTest, ForceGroupWithFlag1) {
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);
+
+ base::FieldTrialList field_trial_list(NULL);
+
+ Study study = CreateStudyWithFlagGroups(100, 0, 0);
+ EXPECT_TRUE(CreateTrialFromStudy(&study));
+ EXPECT_EQ(kFlagGroup1Name,
+ base::FieldTrialList::FindFullName(kFlagStudyName));
+}
+
+// Test that the group for kForcingFlag2 is forced.
+TEST_F(VariationsSeedProcessorTest, ForceGroupWithFlag2) {
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag2);
+
+ base::FieldTrialList field_trial_list(NULL);
+
+ Study study = CreateStudyWithFlagGroups(100, 0, 0);
+ EXPECT_TRUE(CreateTrialFromStudy(&study));
+ EXPECT_EQ(kFlagGroup2Name,
+ base::FieldTrialList::FindFullName(kFlagStudyName));
+}
+
+TEST_F(VariationsSeedProcessorTest, ForceGroup_ChooseFirstGroupWithFlag) {
+ // Add the flag to the command line arguments so the flag group is forced.
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag2);
+
+ base::FieldTrialList field_trial_list(NULL);
+
+ Study study = CreateStudyWithFlagGroups(100, 0, 0);
+ EXPECT_TRUE(CreateTrialFromStudy(&study));
+ EXPECT_EQ(kFlagGroup1Name,
+ base::FieldTrialList::FindFullName(kFlagStudyName));
+}
+
+TEST_F(VariationsSeedProcessorTest, ForceGroup_DontChooseGroupWithFlag) {
+ base::FieldTrialList field_trial_list(NULL);
+
+ // The two flag groups are given high probability, which would normally make
+ // them very likely to be chosen. They won't be chosen since flag groups are
+ // never chosen when their flag isn't present.
+ Study study = CreateStudyWithFlagGroups(1, 999, 999);
+ EXPECT_TRUE(CreateTrialFromStudy(&study));
+ EXPECT_EQ(kNonFlagGroupName,
+ base::FieldTrialList::FindFullName(kFlagStudyName));
+}
+
+TEST_F(VariationsSeedProcessorTest,
+ NonExpiredStudyPrioritizedOverExpiredStudy) {
+ VariationsSeedProcessor seed_processor;
+
+ const std::string kTrialName = "A";
+ const std::string kGroup1Name = "Group1";
+
+ VariationsSeed seed;
+ Study* study1 = seed.add_study();
+ study1->set_name(kTrialName);
+ study1->set_default_experiment_name("Default");
+ AddExperiment(kGroup1Name, 100, study1);
+ AddExperiment("Default", 0, study1);
+ Study* study2 = seed.add_study();
+ *study2 = *study1;
+ ASSERT_EQ(seed.study(0).name(), seed.study(1).name());
+
+ const base::Time year_ago =
+ base::Time::Now() - base::TimeDelta::FromDays(365);
+
+ const base::Version version("20.0.0.0");
+
+ // Check that adding [expired, non-expired] activates the non-expired one.
+ ASSERT_EQ(std::string(), base::FieldTrialList::FindFullName(kTrialName));
+ {
+ base::FeatureList feature_list;
+ base::FieldTrialList field_trial_list(NULL);
+ study1->set_expiry_date(TimeToProtoTime(year_ago));
+ seed_processor.CreateTrialsFromSeed(
+ seed, "en-CA", base::Time::Now(), version, Study_Channel_STABLE,
+ Study_FormFactor_DESKTOP, "", "", "", override_callback_.callback(),
+ &feature_list);
+ EXPECT_EQ(kGroup1Name, base::FieldTrialList::FindFullName(kTrialName));
+ }
+
+ // Check that adding [non-expired, expired] activates the non-expired one.
+ ASSERT_EQ(std::string(), base::FieldTrialList::FindFullName(kTrialName));
+ {
+ base::FeatureList feature_list;
+ base::FieldTrialList field_trial_list(NULL);
+ study1->clear_expiry_date();
+ study2->set_expiry_date(TimeToProtoTime(year_ago));
+ seed_processor.CreateTrialsFromSeed(
+ seed, "en-CA", base::Time::Now(), version, Study_Channel_STABLE,
+ Study_FormFactor_DESKTOP, "", "", "", override_callback_.callback(),
+ &feature_list);
+ EXPECT_EQ(kGroup1Name, base::FieldTrialList::FindFullName(kTrialName));
+ }
+}
+
+TEST_F(VariationsSeedProcessorTest, OverrideUIStrings) {
+ base::FieldTrialList field_trial_list(NULL);
+
+ Study study;
+ study.set_name("Study1");
+ study.set_default_experiment_name("B");
+ study.set_activation_type(Study_ActivationType_ACTIVATION_AUTO);
+
+ Study_Experiment* experiment1 = AddExperiment("A", 0, &study);
+ Study_Experiment_OverrideUIString* override =
+ experiment1->add_override_ui_string();
+
+ override->set_name_hash(1234);
+ override->set_value("test");
+
+ Study_Experiment* experiment2 = AddExperiment("B", 1, &study);
+
+ EXPECT_TRUE(CreateTrialFromStudy(&study));
+
+ const TestOverrideStringCallback::OverrideMap& overrides =
+ override_callback_.overrides();
+
+ EXPECT_TRUE(overrides.empty());
+
+ study.set_name("Study2");
+ experiment1->set_probability_weight(1);
+ experiment2->set_probability_weight(0);
+
+ EXPECT_TRUE(CreateTrialFromStudy(&study));
+
+ EXPECT_EQ(1u, overrides.size());
+ TestOverrideStringCallback::OverrideMap::const_iterator it =
+ overrides.find(1234);
+ EXPECT_EQ(base::ASCIIToUTF16("test"), it->second);
+}
+
+TEST_F(VariationsSeedProcessorTest, OverrideUIStringsWithForcingFlag) {
+ Study study = CreateStudyWithFlagGroups(100, 0, 0);
+ ASSERT_EQ(kForcingFlag1, study.experiment(1).forcing_flag());
+
+ study.set_activation_type(Study_ActivationType_ACTIVATION_AUTO);
+ Study_Experiment_OverrideUIString* override =
+ study.mutable_experiment(1)->add_override_ui_string();
+ override->set_name_hash(1234);
+ override->set_value("test");
+
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);
+ base::FieldTrialList field_trial_list(NULL);
+ EXPECT_TRUE(CreateTrialFromStudy(&study));
+ EXPECT_EQ(kFlagGroup1Name, base::FieldTrialList::FindFullName(study.name()));
+
+ const TestOverrideStringCallback::OverrideMap& overrides =
+ override_callback_.overrides();
+ EXPECT_EQ(1u, overrides.size());
+ TestOverrideStringCallback::OverrideMap::const_iterator it =
+ overrides.find(1234);
+ EXPECT_EQ(base::ASCIIToUTF16("test"), it->second);
+}
+
+TEST_F(VariationsSeedProcessorTest, ValidateStudy) {
+ Study study;
+ study.set_default_experiment_name("def");
+ AddExperiment("abc", 100, &study);
+ Study_Experiment* default_group = AddExperiment("def", 200, &study);
+
+ ProcessedStudy processed_study;
+ EXPECT_TRUE(processed_study.Init(&study, false));
+ EXPECT_EQ(300, processed_study.total_probability());
+ EXPECT_FALSE(processed_study.all_assignments_to_one_group());
+
+ // Min version checks.
+ study.mutable_filter()->set_min_version("1.2.3.*");
+ EXPECT_TRUE(processed_study.Init(&study, false));
+ study.mutable_filter()->set_min_version("1.*.3");
+ EXPECT_FALSE(processed_study.Init(&study, false));
+ study.mutable_filter()->set_min_version("1.2.3");
+ EXPECT_TRUE(processed_study.Init(&study, false));
+
+ // Max version checks.
+ study.mutable_filter()->set_max_version("2.3.4.*");
+ EXPECT_TRUE(processed_study.Init(&study, false));
+ study.mutable_filter()->set_max_version("*.3");
+ EXPECT_FALSE(processed_study.Init(&study, false));
+ study.mutable_filter()->set_max_version("2.3.4");
+ EXPECT_TRUE(processed_study.Init(&study, false));
+
+ study.clear_default_experiment_name();
+ EXPECT_FALSE(processed_study.Init(&study, false));
+
+ study.set_default_experiment_name("xyz");
+ EXPECT_FALSE(processed_study.Init(&study, false));
+
+ study.set_default_experiment_name("def");
+ default_group->clear_name();
+ EXPECT_FALSE(processed_study.Init(&study, false));
+
+ default_group->set_name("def");
+ EXPECT_TRUE(processed_study.Init(&study, false));
+ Study_Experiment* repeated_group = study.add_experiment();
+ repeated_group->set_name("abc");
+ repeated_group->set_probability_weight(1);
+ EXPECT_FALSE(processed_study.Init(&study, false));
+}
+
+TEST_F(VariationsSeedProcessorTest, ValidateStudySingleFeature) {
+ Study study;
+ study.set_default_experiment_name("def");
+ Study_Experiment* exp1 = AddExperiment("exp1", 100, &study);
+ Study_Experiment* exp2 = AddExperiment("exp2", 100, &study);
+ Study_Experiment* exp3 = AddExperiment("exp3", 100, &study);
+ AddExperiment("def", 100, &study);
+
+ ProcessedStudy processed_study;
+ EXPECT_TRUE(processed_study.Init(&study, false));
+ EXPECT_EQ(400, processed_study.total_probability());
+
+ EXPECT_EQ(std::string(), processed_study.single_feature_name());
+
+ const char kFeature1Name[] = "Feature1";
+ const char kFeature2Name[] = "Feature2";
+
+ exp1->mutable_feature_association()->add_enable_feature(kFeature1Name);
+ EXPECT_TRUE(processed_study.Init(&study, false));
+ EXPECT_EQ(kFeature1Name, processed_study.single_feature_name());
+
+ exp1->clear_feature_association();
+ exp1->mutable_feature_association()->add_enable_feature(kFeature1Name);
+ exp1->mutable_feature_association()->add_enable_feature(kFeature2Name);
+ EXPECT_TRUE(processed_study.Init(&study, false));
+ // Since there's multiple different features, |single_feature_name| should be
+ // unset.
+ EXPECT_EQ(std::string(), processed_study.single_feature_name());
+
+ exp1->clear_feature_association();
+ exp1->mutable_feature_association()->add_enable_feature(kFeature1Name);
+ exp2->mutable_feature_association()->add_enable_feature(kFeature1Name);
+ exp3->mutable_feature_association()->add_disable_feature(kFeature1Name);
+ EXPECT_TRUE(processed_study.Init(&study, false));
+ EXPECT_EQ(kFeature1Name, processed_study.single_feature_name());
+
+ // Setting a different feature name on exp2 should cause |single_feature_name|
+ // to be not set.
+ exp2->mutable_feature_association()->set_enable_feature(0, kFeature2Name);
+ EXPECT_TRUE(processed_study.Init(&study, false));
+ EXPECT_EQ(std::string(), processed_study.single_feature_name());
+}
+
+TEST_F(VariationsSeedProcessorTest, ProcessedStudyAllAssignmentsToOneGroup) {
+ Study study;
+ study.set_default_experiment_name("def");
+ AddExperiment("def", 100, &study);
+
+ ProcessedStudy processed_study;
+ EXPECT_TRUE(processed_study.Init(&study, false));
+ EXPECT_TRUE(processed_study.all_assignments_to_one_group());
+
+ AddExperiment("abc", 0, &study);
+ AddExperiment("flag", 0, &study)->set_forcing_flag(kForcingFlag1);
+ EXPECT_TRUE(processed_study.Init(&study, false));
+ EXPECT_TRUE(processed_study.all_assignments_to_one_group());
+
+ AddExperiment("xyz", 1, &study);
+ EXPECT_TRUE(processed_study.Init(&study, false));
+ EXPECT_FALSE(processed_study.all_assignments_to_one_group());
+
+ // Try with default group and first group being at 0.
+ Study study2;
+ study2.set_default_experiment_name("def");
+ AddExperiment("def", 0, &study2);
+ AddExperiment("xyz", 34, &study2);
+ EXPECT_TRUE(processed_study.Init(&study2, false));
+ EXPECT_TRUE(processed_study.all_assignments_to_one_group());
+ AddExperiment("abc", 12, &study2);
+ EXPECT_TRUE(processed_study.Init(&study2, false));
+ EXPECT_FALSE(processed_study.all_assignments_to_one_group());
+}
+
+TEST_F(VariationsSeedProcessorTest, VariationParams) {
+ base::FieldTrialList field_trial_list(NULL);
+
+ Study study;
+ study.set_name("Study1");
+ study.set_default_experiment_name("B");
+
+ Study_Experiment* experiment1 = AddExperiment("A", 1, &study);
+ Study_Experiment_Param* param = experiment1->add_param();
+ param->set_name("x");
+ param->set_value("y");
+
+ Study_Experiment* experiment2 = AddExperiment("B", 0, &study);
+
+ EXPECT_TRUE(CreateTrialFromStudy(&study));
+ EXPECT_EQ("y", GetVariationParamValue("Study1", "x"));
+
+ study.set_name("Study2");
+ experiment1->set_probability_weight(0);
+ experiment2->set_probability_weight(1);
+ EXPECT_TRUE(CreateTrialFromStudy(&study));
+ EXPECT_EQ(std::string(), GetVariationParamValue("Study2", "x"));
+}
+
+TEST_F(VariationsSeedProcessorTest, VariationParamsWithForcingFlag) {
+ Study study = CreateStudyWithFlagGroups(100, 0, 0);
+ ASSERT_EQ(kForcingFlag1, study.experiment(1).forcing_flag());
+ Study_Experiment_Param* param = study.mutable_experiment(1)->add_param();
+ param->set_name("x");
+ param->set_value("y");
+
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);
+ base::FieldTrialList field_trial_list(NULL);
+ EXPECT_TRUE(CreateTrialFromStudy(&study));
+ EXPECT_EQ(kFlagGroup1Name, base::FieldTrialList::FindFullName(study.name()));
+ EXPECT_EQ("y", GetVariationParamValue(study.name(), "x"));
+}
+
+TEST_F(VariationsSeedProcessorTest, StartsActive) {
+ base::FieldTrialList field_trial_list(NULL);
+
+ VariationsSeed seed;
+ Study* study1 = seed.add_study();
+ study1->set_name("A");
+ study1->set_default_experiment_name("Default");
+ AddExperiment("AA", 100, study1);
+ AddExperiment("Default", 0, study1);
+
+ Study* study2 = seed.add_study();
+ study2->set_name("B");
+ study2->set_default_experiment_name("Default");
+ AddExperiment("BB", 100, study2);
+ AddExperiment("Default", 0, study2);
+ study2->set_activation_type(Study_ActivationType_ACTIVATION_AUTO);
+
+ Study* study3 = seed.add_study();
+ study3->set_name("C");
+ study3->set_default_experiment_name("Default");
+ AddExperiment("CC", 100, study3);
+ AddExperiment("Default", 0, study3);
+ study3->set_activation_type(Study_ActivationType_ACTIVATION_EXPLICIT);
+
+ VariationsSeedProcessor seed_processor;
+ seed_processor.CreateTrialsFromSeed(
+ seed, "en-CA", base::Time::Now(), base::Version("20.0.0.0"),
+ Study_Channel_STABLE, Study_FormFactor_DESKTOP, "", "", "",
+ override_callback_.callback(), &feature_list_);
+
+ // Non-specified and ACTIVATION_EXPLICIT should not start active, but
+ // ACTIVATION_AUTO should.
+ EXPECT_FALSE(base::FieldTrialList::IsTrialActive("A"));
+ EXPECT_TRUE(base::FieldTrialList::IsTrialActive("B"));
+ EXPECT_FALSE(base::FieldTrialList::IsTrialActive("C"));
+
+ EXPECT_EQ("AA", base::FieldTrialList::FindFullName("A"));
+ EXPECT_EQ("BB", base::FieldTrialList::FindFullName("B"));
+ EXPECT_EQ("CC", base::FieldTrialList::FindFullName("C"));
+
+ // Now, all studies should be active.
+ EXPECT_TRUE(base::FieldTrialList::IsTrialActive("A"));
+ EXPECT_TRUE(base::FieldTrialList::IsTrialActive("B"));
+ EXPECT_TRUE(base::FieldTrialList::IsTrialActive("C"));
+}
+
+TEST_F(VariationsSeedProcessorTest, StartsActiveWithFlag) {
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);
+
+ base::FieldTrialList field_trial_list(NULL);
+
+ Study study = CreateStudyWithFlagGroups(100, 0, 0);
+ study.set_activation_type(Study_ActivationType_ACTIVATION_AUTO);
+
+ EXPECT_TRUE(CreateTrialFromStudy(&study));
+ EXPECT_TRUE(base::FieldTrialList::IsTrialActive(kFlagStudyName));
+
+ EXPECT_EQ(kFlagGroup1Name,
+ base::FieldTrialList::FindFullName(kFlagStudyName));
+}
+
+TEST_F(VariationsSeedProcessorTest, ForcingFlagAlreadyForced) {
+ Study study = CreateStudyWithFlagGroups(100, 0, 0);
+ ASSERT_EQ(kNonFlagGroupName, study.experiment(0).name());
+ Study_Experiment_Param* param = study.mutable_experiment(0)->add_param();
+ param->set_name("x");
+ param->set_value("y");
+ study.mutable_experiment(0)->set_google_web_experiment_id(kExperimentId);
+
+ base::FieldTrialList field_trial_list(NULL);
+ base::FieldTrialList::CreateFieldTrial(kFlagStudyName, kNonFlagGroupName);
+
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(kForcingFlag1);
+ EXPECT_TRUE(CreateTrialFromStudy(&study));
+ // The previously forced experiment should still hold.
+ EXPECT_EQ(kNonFlagGroupName,
+ base::FieldTrialList::FindFullName(study.name()));
+
+ // Check that params and experiment ids correspond.
+ EXPECT_EQ("y", GetVariationParamValue(study.name(), "x"));
+ VariationID id = GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, kFlagStudyName,
+ kNonFlagGroupName);
+ EXPECT_EQ(kExperimentId, id);
+}
+
+TEST_F(VariationsSeedProcessorTest, FeatureEnabledOrDisableByTrial) {
+ struct base::Feature kFeatureOffByDefault {
+ "kOff", base::FEATURE_DISABLED_BY_DEFAULT
+ };
+ struct base::Feature kFeatureOnByDefault {
+ "kOn", base::FEATURE_ENABLED_BY_DEFAULT
+ };
+ struct base::Feature kUnrelatedFeature {
+ "kUnrelated", base::FEATURE_DISABLED_BY_DEFAULT
+ };
+
+ struct {
+ const char* enable_feature;
+ const char* disable_feature;
+ bool expected_feature_off_state;
+ bool expected_feature_on_state;
+ } test_cases[] = {
+ {nullptr, nullptr, false, true},
+ {kFeatureOnByDefault.name, nullptr, false, true},
+ {kFeatureOffByDefault.name, nullptr, true, true},
+ {nullptr, kFeatureOnByDefault.name, false, false},
+ {nullptr, kFeatureOffByDefault.name, false, true},
+ };
+
+ for (size_t i = 0; i < arraysize(test_cases); i++) {
+ const auto& test_case = test_cases[i];
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]", i));
+
+ base::FieldTrialList field_trial_list(NULL);
+ base::FeatureList::ClearInstanceForTesting();
+ scoped_ptr<base::FeatureList> feature_list(new base::FeatureList);
+
+ Study study;
+ study.set_name("Study1");
+ study.set_default_experiment_name("B");
+ AddExperiment("B", 0, &study);
+
+ Study_Experiment* experiment = AddExperiment("A", 1, &study);
+ Study_Experiment_FeatureAssociation* association =
+ experiment->mutable_feature_association();
+ if (test_case.enable_feature)
+ association->add_enable_feature(test_case.enable_feature);
+ else if (test_case.disable_feature)
+ association->add_disable_feature(test_case.disable_feature);
+
+ EXPECT_TRUE(
+ CreateTrialFromStudyWithFeatureList(&study, feature_list.get()));
+ base::FeatureList::SetInstance(std::move(feature_list));
+
+ // |kUnrelatedFeature| should not be affected.
+ EXPECT_FALSE(base::FeatureList::IsEnabled(kUnrelatedFeature));
+
+ // Before the associated feature is queried, the trial shouldn't be active.
+ EXPECT_FALSE(base::FieldTrialList::IsTrialActive(study.name()));
+
+ EXPECT_EQ(test_case.expected_feature_off_state,
+ base::FeatureList::IsEnabled(kFeatureOffByDefault));
+ EXPECT_EQ(test_case.expected_feature_on_state,
+ base::FeatureList::IsEnabled(kFeatureOnByDefault));
+
+ // The field trial should get activated if it had a feature association.
+ const bool expected_field_trial_active =
+ test_case.enable_feature || test_case.disable_feature;
+ EXPECT_EQ(expected_field_trial_active,
+ base::FieldTrialList::IsTrialActive(study.name()));
+ }
+}
+
+TEST_F(VariationsSeedProcessorTest, FeatureAssociationAndForcing) {
+ struct base::Feature kFeatureOffByDefault {
+ "kFeatureOffByDefault", base::FEATURE_DISABLED_BY_DEFAULT
+ };
+ struct base::Feature kFeatureOnByDefault {
+ "kFeatureOnByDefault", base::FEATURE_ENABLED_BY_DEFAULT
+ };
+
+ enum OneHundredPercentGroup {
+ DEFAULT_GROUP,
+ ENABLE_GROUP,
+ DISABLE_GROUP,
+ };
+
+ const char kDefaultGroup[] = "Default";
+ const char kEnabledGroup[] = "Enabled";
+ const char kDisabledGroup[] = "Disabled";
+ const char kForcedOnGroup[] = "ForcedOn";
+ const char kForcedOffGroup[] = "ForcedOff";
+
+ struct {
+ const base::Feature& feature;
+ const char* enable_features_command_line;
+ const char* disable_features_command_line;
+ OneHundredPercentGroup one_hundred_percent_group;
+
+ const char* expected_group;
+ bool expected_feature_state;
+ bool expected_trial_activated;
+ } test_cases[] = {
+ // Check what happens without and command-line forcing flags - that the
+ // |one_hundred_percent_group| gets correctly selected and does the right
+ // thing w.r.t. to affecting the feature / activating the trial.
+ {kFeatureOffByDefault, "", "", DEFAULT_GROUP, kDefaultGroup, false, true},
+ {kFeatureOffByDefault, "", "", ENABLE_GROUP, kEnabledGroup, true, true},
+ {kFeatureOffByDefault, "", "", DISABLE_GROUP, kDisabledGroup, false,
+ true},
+
+ // Do the same as above, but for kFeatureOnByDefault feature.
+ {kFeatureOnByDefault, "", "", DEFAULT_GROUP, kDefaultGroup, true, true},
+ {kFeatureOnByDefault, "", "", ENABLE_GROUP, kEnabledGroup, true, true},
+ {kFeatureOnByDefault, "", "", DISABLE_GROUP, kDisabledGroup, false, true},
+
+ // Test forcing each feature on and off through the command-line and that
+ // the correct associated experiment gets chosen.
+ {kFeatureOffByDefault, kFeatureOffByDefault.name, "", DEFAULT_GROUP,
+ kForcedOnGroup, true, true},
+ {kFeatureOffByDefault, "", kFeatureOffByDefault.name, DEFAULT_GROUP,
+ kForcedOffGroup, false, true},
+ {kFeatureOnByDefault, kFeatureOnByDefault.name, "", DEFAULT_GROUP,
+ kForcedOnGroup, true, true},
+ {kFeatureOnByDefault, "", kFeatureOnByDefault.name, DEFAULT_GROUP,
+ kForcedOffGroup, false, true},
+
+ // Check that even if a feature should be enabled or disabled based on the
+ // the experiment probability weights, the forcing flag association still
+ // takes precedence. This is 4 cases as above, but with different values
+ // for |one_hundred_percent_group|.
+ {kFeatureOffByDefault, kFeatureOffByDefault.name, "", ENABLE_GROUP,
+ kForcedOnGroup, true, true},
+ {kFeatureOffByDefault, "", kFeatureOffByDefault.name, ENABLE_GROUP,
+ kForcedOffGroup, false, true},
+ {kFeatureOnByDefault, kFeatureOnByDefault.name, "", ENABLE_GROUP,
+ kForcedOnGroup, true, true},
+ {kFeatureOnByDefault, "", kFeatureOnByDefault.name, ENABLE_GROUP,
+ kForcedOffGroup, false, true},
+ {kFeatureOffByDefault, kFeatureOffByDefault.name, "", DISABLE_GROUP,
+ kForcedOnGroup, true, true},
+ {kFeatureOffByDefault, "", kFeatureOffByDefault.name, DISABLE_GROUP,
+ kForcedOffGroup, false, true},
+ {kFeatureOnByDefault, kFeatureOnByDefault.name, "", DISABLE_GROUP,
+ kForcedOnGroup, true, true},
+ {kFeatureOnByDefault, "", kFeatureOnByDefault.name, DISABLE_GROUP,
+ kForcedOffGroup, false, true},
+ };
+
+ for (size_t i = 0; i < arraysize(test_cases); i++) {
+ const auto& test_case = test_cases[i];
+ const int group = test_case.one_hundred_percent_group;
+ SCOPED_TRACE(base::StringPrintf(
+ "Test[%" PRIuS "]: %s [%s] [%s] %d", i, test_case.feature.name,
+ test_case.enable_features_command_line,
+ test_case.disable_features_command_line, static_cast<int>(group)));
+
+ base::FieldTrialList field_trial_list(NULL);
+ base::FeatureList::ClearInstanceForTesting();
+ scoped_ptr<base::FeatureList> feature_list(new base::FeatureList);
+ feature_list->InitializeFromCommandLine(
+ test_case.enable_features_command_line,
+ test_case.disable_features_command_line);
+
+ Study study;
+ study.set_name("Study1");
+ study.set_default_experiment_name("Default");
+ AddExperiment(kDefaultGroup, group == DEFAULT_GROUP ? 1 : 0, &study);
+
+ Study_Experiment* feature_enable =
+ AddExperiment(kEnabledGroup, group == ENABLE_GROUP ? 1 : 0, &study);
+ feature_enable->mutable_feature_association()->add_enable_feature(
+ test_case.feature.name);
+
+ Study_Experiment* feature_disable =
+ AddExperiment(kDisabledGroup, group == DISABLE_GROUP ? 1 : 0, &study);
+ feature_disable->mutable_feature_association()->add_disable_feature(
+ test_case.feature.name);
+
+ AddExperiment(kForcedOnGroup, 0, &study)
+ ->mutable_feature_association()
+ ->set_forcing_feature_on(test_case.feature.name);
+ AddExperiment(kForcedOffGroup, 0, &study)
+ ->mutable_feature_association()
+ ->set_forcing_feature_off(test_case.feature.name);
+
+ EXPECT_TRUE(
+ CreateTrialFromStudyWithFeatureList(&study, feature_list.get()));
+ base::FeatureList::SetInstance(std::move(feature_list));
+
+ // Trial should not be activated initially, but later might get activated
+ // depending on the expected values.
+ EXPECT_FALSE(base::FieldTrialList::IsTrialActive(study.name()));
+ EXPECT_EQ(test_case.expected_feature_state,
+ base::FeatureList::IsEnabled(test_case.feature));
+ EXPECT_EQ(test_case.expected_trial_activated,
+ base::FieldTrialList::IsTrialActive(study.name()));
+ }
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/variations_seed_simulator.cc b/chromium/components/variations/variations_seed_simulator.cc
new file mode 100644
index 00000000000..a9a2f876692
--- /dev/null
+++ b/chromium/components/variations/variations_seed_simulator.cc
@@ -0,0 +1,258 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/variations_seed_simulator.h"
+
+#include <stddef.h>
+
+#include <map>
+
+#include "base/metrics/field_trial.h"
+#include "components/variations/processed_study.h"
+#include "components/variations/proto/study.pb.h"
+#include "components/variations/study_filtering.h"
+#include "components/variations/variations_associated_data.h"
+
+namespace variations {
+
+namespace {
+
+// Fills in |current_state| with the current process' active field trials, as a
+// map of trial names to group names.
+void GetCurrentTrialState(std::map<std::string, std::string>* current_state) {
+ base::FieldTrial::ActiveGroups trial_groups;
+ base::FieldTrialList::GetActiveFieldTrialGroups(&trial_groups);
+ for (size_t i = 0; i < trial_groups.size(); ++i)
+ (*current_state)[trial_groups[i].trial_name] = trial_groups[i].group_name;
+}
+
+// Simulate group assignment for the specified study with PERMANENT consistency.
+// Returns the experiment group that will be selected. Mirrors logic in
+// VariationsSeedProcessor::CreateTrialFromStudy().
+std::string SimulateGroupAssignment(
+ const base::FieldTrial::EntropyProvider& entropy_provider,
+ const ProcessedStudy& processed_study) {
+ const Study& study = *processed_study.study();
+ DCHECK_EQ(Study_Consistency_PERMANENT, study.consistency());
+
+ const double entropy_value =
+ entropy_provider.GetEntropyForTrial(study.name(),
+ study.randomization_seed());
+ scoped_refptr<base::FieldTrial> trial(
+ base::FieldTrial::CreateSimulatedFieldTrial(
+ study.name(), processed_study.total_probability(),
+ study.default_experiment_name(), entropy_value));
+
+ for (int i = 0; i < study.experiment_size(); ++i) {
+ const Study_Experiment& experiment = study.experiment(i);
+ // TODO(asvitkine): This needs to properly handle the case where a group was
+ // forced via forcing_flag in the current state, so that it is not treated
+ // as changed.
+ if (!experiment.has_forcing_flag() &&
+ experiment.name() != study.default_experiment_name()) {
+ trial->AppendGroup(experiment.name(), experiment.probability_weight());
+ }
+ }
+ if (processed_study.is_expired())
+ trial->Disable();
+ return trial->group_name();
+}
+
+// Finds an experiment in |study| with name |experiment_name| and returns it,
+// or NULL if it does not exist.
+const Study_Experiment* FindExperiment(const Study& study,
+ const std::string& experiment_name) {
+ for (int i = 0; i < study.experiment_size(); ++i) {
+ if (study.experiment(i).name() == experiment_name)
+ return &study.experiment(i);
+ }
+ return NULL;
+}
+
+// Checks whether experiment params set for |experiment| on |study| are exactly
+// equal to the params registered for the corresponding field trial in the
+// current process.
+bool VariationParamsAreEqual(const Study& study,
+ const Study_Experiment& experiment) {
+ std::map<std::string, std::string> params;
+ GetVariationParams(study.name(), &params);
+
+ if (static_cast<int>(params.size()) != experiment.param_size())
+ return false;
+
+ for (int i = 0; i < experiment.param_size(); ++i) {
+ std::map<std::string, std::string>::const_iterator it =
+ params.find(experiment.param(i).name());
+ if (it == params.end() || it->second != experiment.param(i).value())
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+VariationsSeedSimulator::Result::Result()
+ : normal_group_change_count(0),
+ kill_best_effort_group_change_count(0),
+ kill_critical_group_change_count(0) {
+}
+
+VariationsSeedSimulator::Result::~Result() {
+}
+
+VariationsSeedSimulator::VariationsSeedSimulator(
+ const base::FieldTrial::EntropyProvider& entropy_provider)
+ : entropy_provider_(entropy_provider) {
+}
+
+VariationsSeedSimulator::~VariationsSeedSimulator() {
+}
+
+VariationsSeedSimulator::Result VariationsSeedSimulator::SimulateSeedStudies(
+ const VariationsSeed& seed,
+ const std::string& locale,
+ const base::Time& reference_date,
+ const base::Version& version,
+ Study_Channel channel,
+ Study_FormFactor form_factor,
+ const std::string& hardware_class,
+ const std::string& session_consistency_country,
+ const std::string& permanent_consistency_country) {
+ std::vector<ProcessedStudy> filtered_studies;
+ FilterAndValidateStudies(seed, locale, reference_date, version, channel,
+ form_factor, hardware_class,
+ session_consistency_country,
+ permanent_consistency_country, &filtered_studies);
+
+ return ComputeDifferences(filtered_studies);
+}
+
+VariationsSeedSimulator::Result VariationsSeedSimulator::ComputeDifferences(
+ const std::vector<ProcessedStudy>& processed_studies) {
+ std::map<std::string, std::string> current_state;
+ GetCurrentTrialState(&current_state);
+
+ Result result;
+ for (size_t i = 0; i < processed_studies.size(); ++i) {
+ const Study& study = *processed_studies[i].study();
+ std::map<std::string, std::string>::const_iterator it =
+ current_state.find(study.name());
+
+ // Skip studies that aren't activated in the current state.
+ // TODO(asvitkine): This should be handled more intelligently. There are
+ // several cases that fall into this category:
+ // 1) There's an existing field trial with this name but it is not active.
+ // 2) There's an existing expired field trial with this name, which is
+ // also not considered as active.
+ // 3) This is a new study config that previously didn't exist.
+ // The above cases should be differentiated and handled explicitly.
+ if (it == current_state.end())
+ continue;
+
+ // Study exists in the current state, check whether its group will change.
+ // Note: The logic below does the right thing if study consistency changes,
+ // as it doesn't rely on the previous study consistency.
+ const std::string& selected_group = it->second;
+ ChangeType change_type = NO_CHANGE;
+ if (study.consistency() == Study_Consistency_PERMANENT) {
+ change_type = PermanentStudyGroupChanged(processed_studies[i],
+ selected_group);
+ } else if (study.consistency() == Study_Consistency_SESSION) {
+ change_type = SessionStudyGroupChanged(processed_studies[i],
+ selected_group);
+ }
+
+ switch (change_type) {
+ case NO_CHANGE:
+ break;
+ case CHANGED:
+ ++result.normal_group_change_count;
+ break;
+ case CHANGED_KILL_BEST_EFFORT:
+ ++result.kill_best_effort_group_change_count;
+ break;
+ case CHANGED_KILL_CRITICAL:
+ ++result.kill_critical_group_change_count;
+ break;
+ }
+ }
+
+ // TODO(asvitkine): Handle removed studies (i.e. studies that existed in the
+ // old seed, but were removed). This will require tracking the set of studies
+ // that were created from the original seed.
+
+ return result;
+}
+
+VariationsSeedSimulator::ChangeType
+VariationsSeedSimulator::ConvertExperimentTypeToChangeType(
+ Study_Experiment_Type type) {
+ switch (type) {
+ case Study_Experiment_Type_NORMAL:
+ return CHANGED;
+ case Study_Experiment_Type_IGNORE_CHANGE:
+ return NO_CHANGE;
+ case Study_Experiment_Type_KILL_BEST_EFFORT:
+ return CHANGED_KILL_BEST_EFFORT;
+ case Study_Experiment_Type_KILL_CRITICAL:
+ return CHANGED_KILL_CRITICAL;
+ }
+ return CHANGED;
+}
+
+VariationsSeedSimulator::ChangeType
+VariationsSeedSimulator::PermanentStudyGroupChanged(
+ const ProcessedStudy& processed_study,
+ const std::string& selected_group) {
+ const Study& study = *processed_study.study();
+ DCHECK_EQ(Study_Consistency_PERMANENT, study.consistency());
+
+ const std::string simulated_group = SimulateGroupAssignment(entropy_provider_,
+ processed_study);
+ const Study_Experiment* experiment = FindExperiment(study, selected_group);
+ if (simulated_group != selected_group) {
+ if (experiment)
+ return ConvertExperimentTypeToChangeType(experiment->type());
+ return CHANGED;
+ }
+
+ // Current group exists in the study - check whether its params changed.
+ DCHECK(experiment);
+ if (!VariationParamsAreEqual(study, *experiment))
+ return ConvertExperimentTypeToChangeType(experiment->type());
+ return NO_CHANGE;
+}
+
+VariationsSeedSimulator::ChangeType
+VariationsSeedSimulator::SessionStudyGroupChanged(
+ const ProcessedStudy& processed_study,
+ const std::string& selected_group) {
+ const Study& study = *processed_study.study();
+ DCHECK_EQ(Study_Consistency_SESSION, study.consistency());
+
+ const Study_Experiment* experiment = FindExperiment(study, selected_group);
+ if (processed_study.is_expired() &&
+ selected_group != study.default_experiment_name()) {
+ // An expired study will result in the default group being selected - mark
+ // it as changed if the current group differs from the default.
+ if (experiment)
+ return ConvertExperimentTypeToChangeType(experiment->type());
+ return CHANGED;
+ }
+
+ if (!experiment)
+ return CHANGED;
+ if (experiment->probability_weight() == 0 &&
+ !experiment->has_forcing_flag()) {
+ return ConvertExperimentTypeToChangeType(experiment->type());
+ }
+
+ // Current group exists in the study - check whether its params changed.
+ if (!VariationParamsAreEqual(study, *experiment))
+ return ConvertExperimentTypeToChangeType(experiment->type());
+ return NO_CHANGE;
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/variations_seed_simulator.h b/chromium/components/variations/variations_seed_simulator.h
new file mode 100644
index 00000000000..dafaf098afe
--- /dev/null
+++ b/chromium/components/variations/variations_seed_simulator.h
@@ -0,0 +1,107 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_VARIATIONS_SEED_SIMULATOR_H_
+#define COMPONENTS_VARIATIONS_VARIATIONS_SEED_SIMULATOR_H_
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/metrics/field_trial.h"
+#include "base/version.h"
+#include "components/variations/proto/study.pb.h"
+#include "components/variations/proto/variations_seed.pb.h"
+
+namespace variations {
+
+class ProcessedStudy;
+class VariationsSeed;
+
+// VariationsSeedSimulator simulates the result of creating a set of studies
+// and detecting which studies would result in group changes.
+class VariationsSeedSimulator {
+ public:
+ // The result of variations seed simulation, counting the number of experiment
+ // group changes of each type that are expected to occur on a restart with the
+ // seed.
+ struct Result {
+ // The number of expected group changes that do not fall into any special
+ // category. This is a lower bound due to session randomized studies.
+ int normal_group_change_count;
+
+ // The number of expected group changes that fall in the category of killed
+ // experiments that should trigger the "best effort" restart mechanism.
+ int kill_best_effort_group_change_count;
+
+ // The number of expected group changes that fall in the category of killed
+ // experiments that should trigger the "critical" restart mechanism.
+ int kill_critical_group_change_count;
+
+ Result();
+ ~Result();
+ };
+
+ // Creates the simulator with the given entropy |provider|.
+ explicit VariationsSeedSimulator(
+ const base::FieldTrial::EntropyProvider& provider);
+ virtual ~VariationsSeedSimulator();
+
+ // Computes differences between the current process' field trial state and
+ // the result of evaluating |seed| with the given parameters. Returns the
+ // results of the simulation as a set of expected group change counts of each
+ // type.
+ Result SimulateSeedStudies(const VariationsSeed& seed,
+ const std::string& locale,
+ const base::Time& reference_date,
+ const base::Version& version,
+ Study_Channel channel,
+ Study_FormFactor form_factor,
+ const std::string& hardware_class,
+ const std::string& session_consistency_country,
+ const std::string& permanent_consistency_country);
+
+ private:
+ friend class VariationsSeedSimulatorTest;
+
+ enum ChangeType {
+ NO_CHANGE,
+ CHANGED,
+ CHANGED_KILL_BEST_EFFORT,
+ CHANGED_KILL_CRITICAL,
+ };
+
+ // Computes differences between the current process' field trial state and
+ // the result of evaluating the |processed_studies| list. It is expected that
+ // |processed_studies| have already been filtered and only contain studies
+ // that apply to the configuration being simulated. Returns the results of the
+ // simulation as a set of expected group change counts of each type.
+ Result ComputeDifferences(
+ const std::vector<ProcessedStudy>& processed_studies);
+
+ // Maps proto enum |type| to a ChangeType.
+ ChangeType ConvertExperimentTypeToChangeType(Study_Experiment_Type type);
+
+ // For the given |processed_study| with PERMANENT consistency, simulates group
+ // assignment and returns a corresponding ChangeType if the result differs
+ // from that study's group in the current process.
+ ChangeType PermanentStudyGroupChanged(const ProcessedStudy& processed_study,
+ const std::string& selected_group);
+
+ // For the given |processed_study| with SESSION consistency, determines if
+ // there are enough changes in the study config that restarting will result
+ // in a guaranteed different group assignment (or different params) and
+ // returns the corresponding ChangeType.
+ ChangeType SessionStudyGroupChanged(const ProcessedStudy& filtered_study,
+ const std::string& selected_group);
+
+ const base::FieldTrial::EntropyProvider& entropy_provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(VariationsSeedSimulator);
+};
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_VARIATIONS_SEED_SIMULATOR_H_
diff --git a/chromium/components/variations/variations_seed_simulator_unittest.cc b/chromium/components/variations/variations_seed_simulator_unittest.cc
new file mode 100644
index 00000000000..1b656b6e11e
--- /dev/null
+++ b/chromium/components/variations/variations_seed_simulator_unittest.cc
@@ -0,0 +1,384 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/variations_seed_simulator.h"
+
+#include <stdint.h>
+
+#include <map>
+
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "components/variations/processed_study.h"
+#include "components/variations/proto/study.pb.h"
+#include "components/variations/variations_associated_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace variations {
+
+namespace {
+
+// An implementation of EntropyProvider that always returns a specific entropy
+// value, regardless of field trial.
+class TestEntropyProvider : public base::FieldTrial::EntropyProvider {
+ public:
+ explicit TestEntropyProvider(double entropy_value)
+ : entropy_value_(entropy_value) {}
+ ~TestEntropyProvider() override {}
+
+ // base::FieldTrial::EntropyProvider implementation:
+ double GetEntropyForTrial(const std::string& trial_name,
+ uint32_t randomization_seed) const override {
+ return entropy_value_;
+ }
+
+ private:
+ const double entropy_value_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestEntropyProvider);
+};
+
+// Creates and activates a single-group field trial with name |trial_name| and
+// group |group_name| and variations |params| (if not NULL).
+void CreateTrial(const std::string& trial_name,
+ const std::string& group_name,
+ const std::map<std::string, std::string>* params) {
+ base::FieldTrialList::CreateFieldTrial(trial_name, group_name);
+ if (params != NULL)
+ AssociateVariationParams(trial_name, group_name, *params);
+ base::FieldTrialList::FindFullName(trial_name);
+}
+
+// Creates a study with the given |study_name| and |consistency|.
+Study CreateStudy(const std::string& study_name,
+ Study_Consistency consistency) {
+ Study study;
+ study.set_name(study_name);
+ study.set_consistency(consistency);
+ return study;
+}
+
+// Adds an experiment to |study| with the specified |experiment_name| and
+// |probability| values and sets it as the study's default experiment.
+Study_Experiment* AddExperiment(const std::string& experiment_name,
+ int probability,
+ Study* study) {
+ Study_Experiment* experiment = study->add_experiment();
+ experiment->set_name(experiment_name);
+ experiment->set_probability_weight(probability);
+ study->set_default_experiment_name(experiment_name);
+ return experiment;
+}
+
+// Add an experiment param with |param_name| and |param_value| to |experiment|.
+Study_Experiment_Param* AddExperimentParam(const std::string& param_name,
+ const std::string& param_value,
+ Study_Experiment* experiment) {
+ Study_Experiment_Param* param = experiment->add_param();
+ param->set_name(param_name);
+ param->set_value(param_value);
+ return param;
+}
+
+} // namespace
+
+class VariationsSeedSimulatorTest : public ::testing::Test {
+ public:
+ VariationsSeedSimulatorTest() : field_trial_list_(NULL) {
+ }
+
+ ~VariationsSeedSimulatorTest() override {
+ // Ensure that the maps are cleared between tests, since they are stored as
+ // process singletons.
+ testing::ClearAllVariationIDs();
+ testing::ClearAllVariationParams();
+ }
+
+ // Uses a VariationsSeedSimulator to simulate the differences between
+ // |studies| and the current field trial state.
+ VariationsSeedSimulator::Result SimulateDifferences(
+ const std::vector<ProcessedStudy>& studies) {
+ TestEntropyProvider provider(0.5);
+ VariationsSeedSimulator seed_simulator(provider);
+ return seed_simulator.ComputeDifferences(studies);
+ }
+
+ // Simulates the differences between |study| and the current field trial
+ // state, returning a string like "1 2 3", where 1 is the number of regular
+ // group changes, 2 is the number of "kill best effort" group changes and 3
+ // is the number of "kill critical" group changes.
+ std::string SimulateStudyDifferences(const Study* study) {
+ std::vector<ProcessedStudy> studies;
+ if (!ProcessedStudy::ValidateAndAppendStudy(study, false, &studies))
+ return "invalid study";
+ return ConvertSimulationResultToString(SimulateDifferences(studies));
+ }
+
+ // Simulates the differences between expired |study| and the current field
+ // trial state, returning a string like "1 2 3", where 1 is the number of
+ // regular group changes, 2 is the number of "kill best effort" group changes
+ // and 3 is the number of "kill critical" group changes.
+ std::string SimulateStudyDifferencesExpired(const Study* study) {
+ std::vector<ProcessedStudy> studies;
+ if (!ProcessedStudy::ValidateAndAppendStudy(study, true, &studies))
+ return "invalid study";
+ if (!studies[0].is_expired())
+ return "not expired";
+ return ConvertSimulationResultToString(SimulateDifferences(studies));
+ }
+
+ // Formats |result| as a string with format "1 2 3", where 1 is the number of
+ // regular group changes, 2 is the number of "kill best effort" group changes
+ // and 3 is the number of "kill critical" group changes.
+ std::string ConvertSimulationResultToString(
+ const VariationsSeedSimulator::Result& result) {
+ return base::StringPrintf("%d %d %d",
+ result.normal_group_change_count,
+ result.kill_best_effort_group_change_count,
+ result.kill_critical_group_change_count);
+ }
+
+ private:
+ base::FieldTrialList field_trial_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(VariationsSeedSimulatorTest);
+};
+
+TEST_F(VariationsSeedSimulatorTest, PermanentNoChanges) {
+ CreateTrial("A", "B", NULL);
+
+ std::vector<ProcessedStudy> processed_studies;
+ Study study = CreateStudy("A", Study_Consistency_PERMANENT);
+ Study_Experiment* experiment = AddExperiment("B", 100, &study);
+
+ EXPECT_EQ("0 0 0", SimulateStudyDifferences(&study));
+
+ experiment->set_type(Study_Experiment_Type_NORMAL);
+ EXPECT_EQ("0 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_IGNORE_CHANGE);
+ EXPECT_EQ("0 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_BEST_EFFORT);
+ EXPECT_EQ("0 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_CRITICAL);
+ EXPECT_EQ("0 0 0", SimulateStudyDifferences(&study));
+}
+
+TEST_F(VariationsSeedSimulatorTest, PermanentGroupChange) {
+ CreateTrial("A", "B", NULL);
+
+ Study study = CreateStudy("A", Study_Consistency_PERMANENT);
+ Study_Experiment* experiment = AddExperiment("C", 100, &study);
+
+ EXPECT_EQ("1 0 0", SimulateStudyDifferences(&study));
+
+ // Changing "C" group type should not affect the type of change. (Since the
+ // type is evaluated for the "old" group.)
+ experiment->set_type(Study_Experiment_Type_NORMAL);
+ EXPECT_EQ("1 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_IGNORE_CHANGE);
+ EXPECT_EQ("1 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_BEST_EFFORT);
+ EXPECT_EQ("1 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_CRITICAL);
+ EXPECT_EQ("1 0 0", SimulateStudyDifferences(&study));
+}
+
+TEST_F(VariationsSeedSimulatorTest, PermanentExpired) {
+ CreateTrial("A", "B", NULL);
+
+ Study study = CreateStudy("A", Study_Consistency_PERMANENT);
+ Study_Experiment* experiment = AddExperiment("B", 1, &study);
+ AddExperiment("C", 0, &study);
+
+ // There should be a difference because the study is expired, which should
+ // result in the default group "C" being chosen.
+ EXPECT_EQ("1 0 0", SimulateStudyDifferencesExpired(&study));
+
+ experiment->set_type(Study_Experiment_Type_NORMAL);
+ EXPECT_EQ("1 0 0", SimulateStudyDifferencesExpired(&study));
+ experiment->set_type(Study_Experiment_Type_IGNORE_CHANGE);
+ EXPECT_EQ("0 0 0", SimulateStudyDifferencesExpired(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_BEST_EFFORT);
+ EXPECT_EQ("0 1 0", SimulateStudyDifferencesExpired(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_CRITICAL);
+ EXPECT_EQ("0 0 1", SimulateStudyDifferencesExpired(&study));
+}
+
+TEST_F(VariationsSeedSimulatorTest, SessionRandomized) {
+ CreateTrial("A", "B", NULL);
+
+ Study study = CreateStudy("A", Study_Consistency_SESSION);
+ Study_Experiment* experiment = AddExperiment("B", 1, &study);
+ AddExperiment("C", 1, &study);
+ AddExperiment("D", 1, &study);
+
+ // There should be no differences, since a session randomized study can result
+ // in any of the groups being chosen on startup.
+ EXPECT_EQ("0 0 0", SimulateStudyDifferences(&study));
+
+ experiment->set_type(Study_Experiment_Type_NORMAL);
+ EXPECT_EQ("0 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_IGNORE_CHANGE);
+ EXPECT_EQ("0 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_BEST_EFFORT);
+ EXPECT_EQ("0 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_CRITICAL);
+ EXPECT_EQ("0 0 0", SimulateStudyDifferences(&study));
+}
+
+TEST_F(VariationsSeedSimulatorTest, SessionRandomizedGroupRemoved) {
+ CreateTrial("A", "B", NULL);
+
+ Study study = CreateStudy("A", Study_Consistency_SESSION);
+ AddExperiment("C", 1, &study);
+ AddExperiment("D", 1, &study);
+
+ // There should be a difference since there is no group "B" in the new config.
+ EXPECT_EQ("1 0 0", SimulateStudyDifferences(&study));
+}
+
+TEST_F(VariationsSeedSimulatorTest, SessionRandomizedGroupProbabilityZero) {
+ CreateTrial("A", "B", NULL);
+
+ Study study = CreateStudy("A", Study_Consistency_SESSION);
+ Study_Experiment* experiment = AddExperiment("B", 0, &study);
+ AddExperiment("C", 1, &study);
+ AddExperiment("D", 1, &study);
+
+ // There should be a difference since group "B" has probability 0.
+ EXPECT_EQ("1 0 0", SimulateStudyDifferences(&study));
+
+ experiment->set_type(Study_Experiment_Type_NORMAL);
+ EXPECT_EQ("1 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_IGNORE_CHANGE);
+ EXPECT_EQ("0 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_BEST_EFFORT);
+ EXPECT_EQ("0 1 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_CRITICAL);
+ EXPECT_EQ("0 0 1", SimulateStudyDifferences(&study));
+}
+
+TEST_F(VariationsSeedSimulatorTest, SessionRandomizedExpired) {
+ CreateTrial("A", "B", NULL);
+
+ Study study = CreateStudy("A", Study_Consistency_SESSION);
+ Study_Experiment* experiment = AddExperiment("B", 1, &study);
+ AddExperiment("C", 1, &study);
+ AddExperiment("D", 1, &study);
+
+ // There should be a difference because the study is expired, which should
+ // result in the default group "D" being chosen.
+ EXPECT_EQ("1 0 0", SimulateStudyDifferencesExpired(&study));
+
+ experiment->set_type(Study_Experiment_Type_NORMAL);
+ EXPECT_EQ("1 0 0", SimulateStudyDifferencesExpired(&study));
+ experiment->set_type(Study_Experiment_Type_IGNORE_CHANGE);
+ EXPECT_EQ("0 0 0", SimulateStudyDifferencesExpired(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_BEST_EFFORT);
+ EXPECT_EQ("0 1 0", SimulateStudyDifferencesExpired(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_CRITICAL);
+ EXPECT_EQ("0 0 1", SimulateStudyDifferencesExpired(&study));
+}
+
+TEST_F(VariationsSeedSimulatorTest, ParamsUnchanged) {
+ std::map<std::string, std::string> params;
+ params["p1"] = "x";
+ params["p2"] = "y";
+ params["p3"] = "z";
+ CreateTrial("A", "B", &params);
+
+ std::vector<ProcessedStudy> processed_studies;
+ Study study = CreateStudy("A", Study_Consistency_PERMANENT);
+ Study_Experiment* experiment = AddExperiment("B", 100, &study);
+ AddExperimentParam("p2", "y", experiment);
+ AddExperimentParam("p1", "x", experiment);
+ AddExperimentParam("p3", "z", experiment);
+
+ EXPECT_EQ("0 0 0", SimulateStudyDifferences(&study));
+
+ experiment->set_type(Study_Experiment_Type_NORMAL);
+ EXPECT_EQ("0 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_IGNORE_CHANGE);
+ EXPECT_EQ("0 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_BEST_EFFORT);
+ EXPECT_EQ("0 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_CRITICAL);
+ EXPECT_EQ("0 0 0", SimulateStudyDifferences(&study));
+}
+
+TEST_F(VariationsSeedSimulatorTest, ParamsChanged) {
+ std::map<std::string, std::string> params;
+ params["p1"] = "x";
+ params["p2"] = "y";
+ params["p3"] = "z";
+ CreateTrial("A", "B", &params);
+
+ std::vector<ProcessedStudy> processed_studies;
+ Study study = CreateStudy("A", Study_Consistency_PERMANENT);
+ Study_Experiment* experiment = AddExperiment("B", 100, &study);
+ AddExperimentParam("p2", "test", experiment);
+ AddExperimentParam("p1", "x", experiment);
+ AddExperimentParam("p3", "z", experiment);
+
+ // The param lists differ.
+ EXPECT_EQ("1 0 0", SimulateStudyDifferences(&study));
+
+ experiment->set_type(Study_Experiment_Type_NORMAL);
+ EXPECT_EQ("1 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_IGNORE_CHANGE);
+ EXPECT_EQ("0 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_BEST_EFFORT);
+ EXPECT_EQ("0 1 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_CRITICAL);
+ EXPECT_EQ("0 0 1", SimulateStudyDifferences(&study));
+}
+
+TEST_F(VariationsSeedSimulatorTest, ParamsRemoved) {
+ std::map<std::string, std::string> params;
+ params["p1"] = "x";
+ params["p2"] = "y";
+ params["p3"] = "z";
+ CreateTrial("A", "B", &params);
+
+ std::vector<ProcessedStudy> processed_studies;
+ Study study = CreateStudy("A", Study_Consistency_PERMANENT);
+ Study_Experiment* experiment = AddExperiment("B", 100, &study);
+
+ // The current group has params, but the new config doesn't have any.
+ EXPECT_EQ("1 0 0", SimulateStudyDifferences(&study));
+
+ experiment->set_type(Study_Experiment_Type_NORMAL);
+ EXPECT_EQ("1 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_IGNORE_CHANGE);
+ EXPECT_EQ("0 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_BEST_EFFORT);
+ EXPECT_EQ("0 1 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_CRITICAL);
+ EXPECT_EQ("0 0 1", SimulateStudyDifferences(&study));
+}
+
+TEST_F(VariationsSeedSimulatorTest, ParamsAdded) {
+ CreateTrial("A", "B", NULL);
+
+ std::vector<ProcessedStudy> processed_studies;
+ Study study = CreateStudy("A", Study_Consistency_PERMANENT);
+ Study_Experiment* experiment = AddExperiment("B", 100, &study);
+ AddExperimentParam("p2", "y", experiment);
+ AddExperimentParam("p1", "x", experiment);
+ AddExperimentParam("p3", "z", experiment);
+
+ // The current group has no params, but the config has added some.
+ EXPECT_EQ("1 0 0", SimulateStudyDifferences(&study));
+
+ experiment->set_type(Study_Experiment_Type_NORMAL);
+ EXPECT_EQ("1 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_IGNORE_CHANGE);
+ EXPECT_EQ("0 0 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_BEST_EFFORT);
+ EXPECT_EQ("0 1 0", SimulateStudyDifferences(&study));
+ experiment->set_type(Study_Experiment_Type_KILL_CRITICAL);
+ EXPECT_EQ("0 0 1", SimulateStudyDifferences(&study));
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/variations_seed_store.cc b/chromium/components/variations/variations_seed_store.cc
new file mode 100644
index 00000000000..65b1efd1b5c
--- /dev/null
+++ b/chromium/components/variations/variations_seed_store.cc
@@ -0,0 +1,537 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/variations_seed_store.h"
+
+#include <stdint.h>
+
+#include "base/base64.h"
+#include "base/macros.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/numerics/safe_math.h"
+#include "base/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "build/build_config.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/pref_names.h"
+#include "components/variations/proto/variations_seed.pb.h"
+#include "crypto/signature_verifier.h"
+#include "third_party/protobuf/src/google/protobuf/io/coded_stream.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+#if defined(OS_ANDROID)
+#include "components/variations/android/variations_seed_bridge.h"
+#endif // OS_ANDROID
+
+namespace variations {
+
+namespace {
+
+// Signature verification is disabled on mobile platforms for now, since it
+// adds about ~15ms to the startup time on mobile (vs. a couple ms on desktop).
+bool SignatureVerificationEnabled() {
+#if defined(OS_IOS) || defined(OS_ANDROID)
+ return false;
+#else
+ return true;
+#endif
+}
+
+// The ECDSA public key of the variations server for verifying variations seed
+// signatures.
+const uint8_t kPublicKey[] = {
+ 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01,
+ 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00,
+ 0x04, 0x51, 0x7c, 0x31, 0x4b, 0x50, 0x42, 0xdd, 0x59, 0xda, 0x0b, 0xfa, 0x43,
+ 0x44, 0x33, 0x7c, 0x5f, 0xa1, 0x0b, 0xd5, 0x82, 0xf6, 0xac, 0x04, 0x19, 0x72,
+ 0x6c, 0x40, 0xd4, 0x3e, 0x56, 0xe2, 0xa0, 0x80, 0xa0, 0x41, 0xb3, 0x23, 0x7b,
+ 0x71, 0xc9, 0x80, 0x87, 0xde, 0x35, 0x0d, 0x25, 0x71, 0x09, 0x7f, 0xb4, 0x15,
+ 0x2b, 0xff, 0x82, 0x4d, 0xd3, 0xfe, 0xc5, 0xef, 0x20, 0xc6, 0xa3, 0x10, 0xbf,
+};
+
+// Note: UMA histogram enum - don't re-order or remove entries.
+enum VariationSeedEmptyState {
+ VARIATIONS_SEED_NOT_EMPTY,
+ VARIATIONS_SEED_EMPTY,
+ VARIATIONS_SEED_CORRUPT,
+ VARIATIONS_SEED_INVALID_SIGNATURE,
+ VARIATIONS_SEED_CORRUPT_BASE64,
+ VARIATIONS_SEED_CORRUPT_PROTOBUF,
+ VARIATIONS_SEED_CORRUPT_GZIP,
+ VARIATIONS_SEED_EMPTY_ENUM_SIZE,
+};
+
+void RecordVariationSeedEmptyHistogram(VariationSeedEmptyState state) {
+ UMA_HISTOGRAM_ENUMERATION("Variations.SeedEmpty", state,
+ VARIATIONS_SEED_EMPTY_ENUM_SIZE);
+}
+
+enum VariationsSeedStoreResult {
+ VARIATIONS_SEED_STORE_SUCCESS,
+ VARIATIONS_SEED_STORE_FAILED_EMPTY,
+ VARIATIONS_SEED_STORE_FAILED_PARSE,
+ VARIATIONS_SEED_STORE_FAILED_SIGNATURE,
+ VARIATIONS_SEED_STORE_FAILED_GZIP,
+ // DELTA_COUNT is not so much a result of the seed store, but rather counting
+ // the number of delta-compressed seeds the SeedStore() function saw. Kept in
+ // the same histogram for convenience of comparing against the other values.
+ VARIATIONS_SEED_STORE_DELTA_COUNT,
+ VARIATIONS_SEED_STORE_FAILED_DELTA_READ_SEED,
+ VARIATIONS_SEED_STORE_FAILED_DELTA_APPLY,
+ VARIATIONS_SEED_STORE_FAILED_DELTA_STORE,
+ VARIATIONS_SEED_STORE_FAILED_UNGZIP,
+ VARIATIONS_SEED_STORE_FAILED_EMPTY_GZIP_CONTENTS,
+ VARIATIONS_SEED_STORE_FAILED_UNSUPPORTED_SEED_FORMAT,
+ VARIATIONS_SEED_STORE_RESULT_ENUM_SIZE,
+};
+
+void RecordSeedStoreHistogram(VariationsSeedStoreResult result) {
+ UMA_HISTOGRAM_ENUMERATION("Variations.SeedStoreResult", result,
+ VARIATIONS_SEED_STORE_RESULT_ENUM_SIZE);
+}
+
+// Note: UMA histogram enum - don't re-order or remove entries.
+enum VariationsSeedDateChangeState {
+ SEED_DATE_NO_OLD_DATE,
+ SEED_DATE_NEW_DATE_OLDER,
+ SEED_DATE_SAME_DAY,
+ SEED_DATE_NEW_DAY,
+ SEED_DATE_ENUM_SIZE,
+};
+
+// Truncates a time to the start of the day in UTC. If given a time representing
+// 2014-03-11 10:18:03.1 UTC, it will return a time representing
+// 2014-03-11 00:00:00.0 UTC.
+base::Time TruncateToUTCDay(const base::Time& time) {
+ base::Time::Exploded exploded;
+ time.UTCExplode(&exploded);
+ exploded.hour = 0;
+ exploded.minute = 0;
+ exploded.second = 0;
+ exploded.millisecond = 0;
+
+ return base::Time::FromUTCExploded(exploded);
+}
+
+VariationsSeedDateChangeState GetSeedDateChangeState(
+ const base::Time& server_seed_date,
+ const base::Time& stored_seed_date) {
+ if (server_seed_date < stored_seed_date)
+ return SEED_DATE_NEW_DATE_OLDER;
+
+ if (TruncateToUTCDay(server_seed_date) !=
+ TruncateToUTCDay(stored_seed_date)) {
+ // The server date is earlier than the stored date, and they are from
+ // different UTC days, so |server_seed_date| is a valid new day.
+ return SEED_DATE_NEW_DAY;
+ }
+ return SEED_DATE_SAME_DAY;
+}
+
+#if defined(OS_ANDROID)
+enum FirstRunResult {
+ FIRST_RUN_SEED_IMPORT_SUCCESS,
+ FIRST_RUN_SEED_IMPORT_FAIL_NO_CALLBACK,
+ FIRST_RUN_SEED_IMPORT_FAIL_NO_FIRST_RUN_SEED,
+ FIRST_RUN_SEED_IMPORT_FAIL_STORE_FAILED,
+ FIRST_RUN_RESULT_ENUM_SIZE,
+};
+
+void RecordFirstRunResult(FirstRunResult result) {
+ UMA_HISTOGRAM_ENUMERATION("Variations.FirstRunResult", result,
+ FIRST_RUN_RESULT_ENUM_SIZE);
+}
+#endif // OS_ANDROID
+
+} // namespace
+
+VariationsSeedStore::VariationsSeedStore(PrefService* local_state)
+ : local_state_(local_state), seed_has_country_code_(false) {
+}
+
+VariationsSeedStore::~VariationsSeedStore() {
+}
+
+bool VariationsSeedStore::LoadSeed(variations::VariationsSeed* seed) {
+ invalid_base64_signature_.clear();
+
+#if defined(OS_ANDROID)
+ if (!local_state_->HasPrefPath(prefs::kVariationsSeedSignature))
+ ImportFirstRunJavaSeed();
+#endif // OS_ANDROID
+
+ std::string seed_data;
+ if (!ReadSeedData(&seed_data))
+ return false;
+
+ const std::string base64_seed_signature =
+ local_state_->GetString(prefs::kVariationsSeedSignature);
+ const VerifySignatureResult result =
+ VerifySeedSignature(seed_data, base64_seed_signature);
+ if (result != VARIATIONS_SEED_SIGNATURE_ENUM_SIZE) {
+ UMA_HISTOGRAM_ENUMERATION("Variations.LoadSeedSignature", result,
+ VARIATIONS_SEED_SIGNATURE_ENUM_SIZE);
+ if (result != VARIATIONS_SEED_SIGNATURE_VALID) {
+ ClearPrefs();
+ RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_INVALID_SIGNATURE);
+ // Record the invalid signature.
+ invalid_base64_signature_ = base64_seed_signature;
+ return false;
+ }
+ }
+
+ if (!seed->ParseFromString(seed_data)) {
+ ClearPrefs();
+ RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_CORRUPT_PROTOBUF);
+ return false;
+ }
+
+ // Migrate any existing country code from the seed to the pref, if the pref is
+ // empty. TODO(asvitkine): Clean up the code in M50+ when sufficient number
+ // of clients have migrated.
+ if (seed->has_country_code() &&
+ local_state_->GetString(prefs::kVariationsCountry).empty()) {
+ local_state_->SetString(prefs::kVariationsCountry, seed->country_code());
+ }
+ variations_serial_number_ = seed->serial_number();
+ seed_has_country_code_ = seed->has_country_code();
+ RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_NOT_EMPTY);
+ return true;
+}
+
+bool VariationsSeedStore::StoreSeedData(
+ const std::string& data,
+ const std::string& base64_seed_signature,
+ const std::string& country_code,
+ const base::Time& date_fetched,
+ bool is_delta_compressed,
+ bool is_gzip_compressed,
+ variations::VariationsSeed* parsed_seed) {
+ // If the data is gzip compressed, first uncompress it.
+ std::string ungzipped_data;
+ if (is_gzip_compressed) {
+ if (compression::GzipUncompress(data, &ungzipped_data)) {
+ if (ungzipped_data.empty()) {
+ RecordSeedStoreHistogram(
+ VARIATIONS_SEED_STORE_FAILED_EMPTY_GZIP_CONTENTS);
+ return false;
+ }
+
+ int size_reduction = ungzipped_data.length() - data.length();
+ UMA_HISTOGRAM_PERCENTAGE("Variations.StoreSeed.GzipSize.ReductionPercent",
+ 100 * size_reduction / ungzipped_data.length());
+ UMA_HISTOGRAM_COUNTS_1000("Variations.StoreSeed.GzipSize",
+ data.length() / 1024);
+ } else {
+ RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_UNGZIP);
+ return false;
+ }
+ } else {
+ ungzipped_data = data;
+ }
+
+ if (!is_delta_compressed) {
+ const bool result =
+ StoreSeedDataNoDelta(ungzipped_data, base64_seed_signature,
+ country_code, date_fetched, parsed_seed);
+ if (result) {
+ UMA_HISTOGRAM_COUNTS_1000("Variations.StoreSeed.Size",
+ ungzipped_data.length() / 1024);
+ }
+ return result;
+ }
+
+ // If the data is delta compressed, first decode it.
+ DCHECK(invalid_base64_signature_.empty());
+ RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_DELTA_COUNT);
+
+ std::string existing_seed_data;
+ std::string updated_seed_data;
+ if (!ReadSeedData(&existing_seed_data)) {
+ RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_DELTA_READ_SEED);
+ return false;
+ }
+ if (!ApplyDeltaPatch(existing_seed_data, ungzipped_data,
+ &updated_seed_data)) {
+ RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_DELTA_APPLY);
+ return false;
+ }
+
+ const bool result =
+ StoreSeedDataNoDelta(updated_seed_data, base64_seed_signature,
+ country_code, date_fetched, parsed_seed);
+ if (result) {
+ // Note: |updated_seed_data.length()| is guaranteed to be non-zero, else
+ // result would be false.
+ int size_reduction = updated_seed_data.length() - ungzipped_data.length();
+ UMA_HISTOGRAM_PERCENTAGE("Variations.StoreSeed.DeltaSize.ReductionPercent",
+ 100 * size_reduction / updated_seed_data.length());
+ UMA_HISTOGRAM_COUNTS_1000("Variations.StoreSeed.DeltaSize",
+ ungzipped_data.length() / 1024);
+ } else {
+ RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_DELTA_STORE);
+ }
+ return result;
+}
+
+void VariationsSeedStore::UpdateSeedDateAndLogDayChange(
+ const base::Time& server_date_fetched) {
+ VariationsSeedDateChangeState date_change = SEED_DATE_NO_OLD_DATE;
+
+ if (local_state_->HasPrefPath(prefs::kVariationsSeedDate)) {
+ const int64_t stored_date_value =
+ local_state_->GetInt64(prefs::kVariationsSeedDate);
+ const base::Time stored_date =
+ base::Time::FromInternalValue(stored_date_value);
+
+ date_change = GetSeedDateChangeState(server_date_fetched, stored_date);
+ }
+
+ UMA_HISTOGRAM_ENUMERATION("Variations.SeedDateChange", date_change,
+ SEED_DATE_ENUM_SIZE);
+
+ local_state_->SetInt64(prefs::kVariationsSeedDate,
+ server_date_fetched.ToInternalValue());
+}
+
+std::string VariationsSeedStore::GetInvalidSignature() const {
+ return invalid_base64_signature_;
+}
+
+// static
+void VariationsSeedStore::RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterStringPref(prefs::kVariationsCompressedSeed, std::string());
+ registry->RegisterStringPref(prefs::kVariationsSeed, std::string());
+ registry->RegisterInt64Pref(prefs::kVariationsSeedDate,
+ base::Time().ToInternalValue());
+ registry->RegisterStringPref(prefs::kVariationsSeedSignature, std::string());
+ registry->RegisterStringPref(prefs::kVariationsCountry, std::string());
+}
+
+VariationsSeedStore::VerifySignatureResult
+VariationsSeedStore::VerifySeedSignature(
+ const std::string& seed_bytes,
+ const std::string& base64_seed_signature) {
+ if (!SignatureVerificationEnabled())
+ return VARIATIONS_SEED_SIGNATURE_ENUM_SIZE;
+
+ if (base64_seed_signature.empty())
+ return VARIATIONS_SEED_SIGNATURE_MISSING;
+
+ std::string signature;
+ if (!base::Base64Decode(base64_seed_signature, &signature))
+ return VARIATIONS_SEED_SIGNATURE_DECODE_FAILED;
+
+ crypto::SignatureVerifier verifier;
+ if (!verifier.VerifyInit(crypto::SignatureVerifier::ECDSA_SHA256,
+ reinterpret_cast<const uint8_t*>(signature.data()),
+ signature.size(), kPublicKey,
+ arraysize(kPublicKey))) {
+ return VARIATIONS_SEED_SIGNATURE_INVALID_SIGNATURE;
+ }
+
+ verifier.VerifyUpdate(reinterpret_cast<const uint8_t*>(seed_bytes.data()),
+ seed_bytes.size());
+ if (verifier.VerifyFinal())
+ return VARIATIONS_SEED_SIGNATURE_VALID;
+ return VARIATIONS_SEED_SIGNATURE_INVALID_SEED;
+}
+
+void VariationsSeedStore::ClearPrefs() {
+ local_state_->ClearPref(prefs::kVariationsCompressedSeed);
+ local_state_->ClearPref(prefs::kVariationsSeed);
+ local_state_->ClearPref(prefs::kVariationsSeedDate);
+ local_state_->ClearPref(prefs::kVariationsSeedSignature);
+}
+
+#if defined(OS_ANDROID)
+void VariationsSeedStore::ImportFirstRunJavaSeed() {
+ DVLOG(1) << "Importing first run seed from Java preferences.";
+
+ std::string seed_data;
+ std::string seed_signature;
+ std::string seed_country;
+ std::string response_date;
+ bool is_gzip_compressed;
+
+ android::GetVariationsFirstRunSeed(&seed_data, &seed_signature, &seed_country,
+ &response_date, &is_gzip_compressed);
+ if (seed_data.empty()) {
+ RecordFirstRunResult(FIRST_RUN_SEED_IMPORT_FAIL_NO_FIRST_RUN_SEED);
+ return;
+ }
+
+ base::Time current_date;
+ base::Time::FromUTCString(response_date.c_str(), &current_date);
+
+ if (!StoreSeedData(seed_data, seed_signature, seed_country, current_date,
+ false, is_gzip_compressed, nullptr)) {
+ RecordFirstRunResult(FIRST_RUN_SEED_IMPORT_FAIL_STORE_FAILED);
+ LOG(WARNING) << "First run variations seed is invalid.";
+ return;
+ }
+ RecordFirstRunResult(FIRST_RUN_SEED_IMPORT_SUCCESS);
+}
+#endif // OS_ANDROID
+
+bool VariationsSeedStore::ReadSeedData(std::string* seed_data) {
+ std::string base64_seed_data =
+ local_state_->GetString(prefs::kVariationsCompressedSeed);
+ const bool is_compressed = !base64_seed_data.empty();
+ // If there's no compressed seed, fall back to the uncompressed one.
+ if (!is_compressed)
+ base64_seed_data = local_state_->GetString(prefs::kVariationsSeed);
+
+ if (base64_seed_data.empty()) {
+ RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_EMPTY);
+ return false;
+ }
+
+ // If the decode process fails, assume the pref value is corrupt and clear it.
+ std::string decoded_data;
+ if (!base::Base64Decode(base64_seed_data, &decoded_data)) {
+ ClearPrefs();
+ RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_CORRUPT_BASE64);
+ return false;
+ }
+
+ if (!is_compressed) {
+ seed_data->swap(decoded_data);
+ } else if (!compression::GzipUncompress(decoded_data, seed_data)) {
+ ClearPrefs();
+ RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_CORRUPT_GZIP);
+ return false;
+ }
+
+ return true;
+}
+
+bool VariationsSeedStore::StoreSeedDataNoDelta(
+ const std::string& seed_data,
+ const std::string& base64_seed_signature,
+ const std::string& country_code,
+ const base::Time& date_fetched,
+ variations::VariationsSeed* parsed_seed) {
+ if (seed_data.empty()) {
+ RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_EMPTY_GZIP_CONTENTS);
+ return false;
+ }
+
+ // Only store the seed data if it parses correctly.
+ variations::VariationsSeed seed;
+ if (!seed.ParseFromString(seed_data)) {
+ RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_PARSE);
+ return false;
+ }
+
+ const VerifySignatureResult result =
+ VerifySeedSignature(seed_data, base64_seed_signature);
+ if (result != VARIATIONS_SEED_SIGNATURE_ENUM_SIZE) {
+ UMA_HISTOGRAM_ENUMERATION("Variations.StoreSeedSignature", result,
+ VARIATIONS_SEED_SIGNATURE_ENUM_SIZE);
+ if (result != VARIATIONS_SEED_SIGNATURE_VALID) {
+ RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_SIGNATURE);
+ return false;
+ }
+ }
+
+ // Compress the seed before base64-encoding and storing.
+ std::string compressed_seed_data;
+ if (!compression::GzipCompress(seed_data, &compressed_seed_data)) {
+ RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_GZIP);
+ return false;
+ }
+
+ std::string base64_seed_data;
+ base::Base64Encode(compressed_seed_data, &base64_seed_data);
+
+ // TODO(asvitkine): This pref is no longer being used. Remove it completely
+ // in M45+.
+ local_state_->ClearPref(prefs::kVariationsSeed);
+
+#if defined(OS_ANDROID)
+ // If currently we do not have any stored pref then we mark seed storing as
+ // successful on the Java side of Chrome for Android to avoid repeated seed
+ // fetches and clear preferences on the Java side.
+ if (local_state_->GetString(prefs::kVariationsCompressedSeed).empty()) {
+ android::MarkVariationsSeedAsStored();
+ android::ClearJavaFirstRunPrefs();
+ }
+#endif
+
+ // Update the saved country code only if one was returned from the server.
+ // Prefer the country code that was transmitted in the header over the one in
+ // the seed (which is deprecated).
+ if (!country_code.empty())
+ local_state_->SetString(prefs::kVariationsCountry, country_code);
+ else if (seed.has_country_code())
+ local_state_->SetString(prefs::kVariationsCountry, seed.country_code());
+
+ local_state_->SetString(prefs::kVariationsCompressedSeed, base64_seed_data);
+ UpdateSeedDateAndLogDayChange(date_fetched);
+ local_state_->SetString(prefs::kVariationsSeedSignature,
+ base64_seed_signature);
+ variations_serial_number_ = seed.serial_number();
+ if (parsed_seed)
+ seed.Swap(parsed_seed);
+
+ RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_SUCCESS);
+ return true;
+}
+
+// static
+bool VariationsSeedStore::ApplyDeltaPatch(const std::string& existing_data,
+ const std::string& patch,
+ std::string* output) {
+ output->clear();
+
+ google::protobuf::io::CodedInputStream in(
+ reinterpret_cast<const uint8_t*>(patch.data()), patch.length());
+ // Temporary string declared outside the loop so it can be re-used between
+ // different iterations (rather than allocating new ones).
+ std::string temp;
+
+ const uint32_t existing_data_size =
+ static_cast<uint32_t>(existing_data.size());
+ while (in.CurrentPosition() != static_cast<int>(patch.length())) {
+ uint32_t value;
+ if (!in.ReadVarint32(&value))
+ return false;
+
+ if (value != 0) {
+ // A non-zero value indicates the number of bytes to copy from the patch
+ // stream to the output.
+
+ // No need to guard against bad data (i.e. very large |value|) because the
+ // call below will fail if |value| is greater than the size of the patch.
+ if (!in.ReadString(&temp, value))
+ return false;
+ output->append(temp);
+ } else {
+ // Otherwise, when it's zero, it indicates that it's followed by a pair of
+ // numbers - |offset| and |length| that specify a range of data to copy
+ // from |existing_data|.
+ uint32_t offset;
+ uint32_t length;
+ if (!in.ReadVarint32(&offset) || !in.ReadVarint32(&length))
+ return false;
+
+ // Check for |offset + length| being out of range and for overflow.
+ base::CheckedNumeric<uint32_t> end_offset(offset);
+ end_offset += length;
+ if (!end_offset.IsValid() || end_offset.ValueOrDie() > existing_data_size)
+ return false;
+ output->append(existing_data, offset, length);
+ }
+ }
+ return true;
+}
+
+void VariationsSeedStore::ReportUnsupportedSeedFormatError() {
+ RecordSeedStoreHistogram(
+ VARIATIONS_SEED_STORE_FAILED_UNSUPPORTED_SEED_FORMAT);
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/variations_seed_store.h b/chromium/components/variations/variations_seed_store.h
new file mode 100644
index 00000000000..2450e682ee5
--- /dev/null
+++ b/chromium/components/variations/variations_seed_store.h
@@ -0,0 +1,150 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_VARIATIONS_SEED_STORE_H_
+#define COMPONENTS_VARIATIONS_VARIATIONS_SEED_STORE_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace variations {
+class VariationsSeed;
+}
+
+namespace variations {
+
+// VariationsSeedStore is a helper class for reading and writing the variations
+// seed from Local State.
+class VariationsSeedStore {
+ public:
+ explicit VariationsSeedStore(PrefService* local_state);
+ virtual ~VariationsSeedStore();
+
+ // Loads the variations seed data from local state into |seed|. If there is a
+ // problem with loading, the pref value is cleared and false is returned. If
+ // successful, |seed| will contain the loaded data and true is returned.
+ bool LoadSeed(variations::VariationsSeed* seed);
+
+ // Stores the given seed |data| (serialized protobuf) to local state, along
+ // with a base64-encoded digital signature for seed and the date when it was
+ // fetched. If |is_gzip_compressed| is true, treats |data| as being gzip
+ // compressed and decompresses it before any other processing.
+ // If |is_delta_compressed| is true, treats |data| as being delta
+ // compressed and attempts to decode it first using the store's seed data.
+ // The actual seed data will be base64 encoded for storage. If the string
+ // is invalid, the existing prefs are untouched and false is returned.
+ // Additionally, stores the |country_code| that was received with the seed in
+ // a separate pref. On success and if |parsed_seed| is not NULL, |parsed_seed|
+ // will be filled with the de-serialized decoded protobuf.
+ bool StoreSeedData(const std::string& data,
+ const std::string& base64_seed_signature,
+ const std::string& country_code,
+ const base::Time& date_fetched,
+ bool is_delta_compressed,
+ bool is_gzip_compressed,
+ variations::VariationsSeed* parsed_seed);
+
+ // Updates |kVariationsSeedDate| and logs when previous date was from a
+ // different day.
+ void UpdateSeedDateAndLogDayChange(const base::Time& server_date_fetched);
+
+ // Reports to UMA that the seed format specified by the server is unsupported.
+ void ReportUnsupportedSeedFormatError();
+
+ // Returns the serial number of the last loaded or stored seed.
+ const std::string& variations_serial_number() const {
+ return variations_serial_number_;
+ }
+
+ // Returns whether the last loaded or stored seed has the country field set.
+ bool seed_has_country_code() const {
+ return seed_has_country_code_;
+ }
+
+ // Returns the invalid signature in base64 format, or an empty string if the
+ // signature was valid, missing, or if signature verification is disabled.
+ std::string GetInvalidSignature() const;
+
+ // Registers Local State prefs used by this class.
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ protected:
+ // Note: UMA histogram enum - don't re-order or remove entries.
+ enum VerifySignatureResult {
+ VARIATIONS_SEED_SIGNATURE_MISSING,
+ VARIATIONS_SEED_SIGNATURE_DECODE_FAILED,
+ VARIATIONS_SEED_SIGNATURE_INVALID_SIGNATURE,
+ VARIATIONS_SEED_SIGNATURE_INVALID_SEED,
+ VARIATIONS_SEED_SIGNATURE_VALID,
+ VARIATIONS_SEED_SIGNATURE_ENUM_SIZE,
+ };
+
+ // Verifies a variations seed (the serialized proto bytes) with the specified
+ // base-64 encoded signature that was received from the server and returns the
+ // result. The signature is assumed to be an "ECDSA with SHA-256" signature
+ // (see kECDSAWithSHA256AlgorithmID in the .cc file). Returns the result of
+ // signature verification or VARIATIONS_SEED_SIGNATURE_ENUM_SIZE if signature
+ // verification is not enabled.
+ virtual VariationsSeedStore::VerifySignatureResult VerifySeedSignature(
+ const std::string& seed_bytes,
+ const std::string& base64_seed_signature);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(VariationsSeedStoreTest, VerifySeedSignature);
+ FRIEND_TEST_ALL_PREFIXES(VariationsSeedStoreTest, ApplyDeltaPatch);
+
+ // Clears all prefs related to variations seed storage.
+ void ClearPrefs();
+
+#if defined(OS_ANDROID)
+ // Imports the variations seed data from Java side during the first
+ // Chrome for Android run.
+ void ImportFirstRunJavaSeed();
+#endif // OS_ANDROID
+
+ // Reads the variations seed data from prefs; returns true on success.
+ bool ReadSeedData(std::string* seed_data);
+
+ // Internal version of |StoreSeedData()| that assumes |seed_data| is not delta
+ // compressed.
+ bool StoreSeedDataNoDelta(
+ const std::string& seed_data,
+ const std::string& base64_seed_signature,
+ const std::string& country_code,
+ const base::Time& date_fetched,
+ variations::VariationsSeed* parsed_seed);
+
+ // Applies a delta-compressed |patch| to |existing_data|, producing the result
+ // in |output|. Returns whether the operation was successful.
+ static bool ApplyDeltaPatch(const std::string& existing_data,
+ const std::string& patch,
+ std::string* output);
+
+ // The pref service used to persist the variations seed.
+ PrefService* local_state_;
+
+ // Cached serial number from the most recently fetched variations seed.
+ std::string variations_serial_number_;
+
+ // Whether the most recently fetched variations seed has the country code
+ // field set.
+ bool seed_has_country_code_;
+
+ // Keeps track of an invalid signature.
+ std::string invalid_base64_signature_;
+
+ DISALLOW_COPY_AND_ASSIGN(VariationsSeedStore);
+};
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_VARIATIONS_SEED_STORE_H_
diff --git a/chromium/components/variations/variations_seed_store_unittest.cc b/chromium/components/variations/variations_seed_store_unittest.cc
new file mode 100644
index 00000000000..577e3f8c450
--- /dev/null
+++ b/chromium/components/variations/variations_seed_store_unittest.cc
@@ -0,0 +1,422 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/variations_seed_store.h"
+
+#include "base/base64.h"
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/variations/pref_names.h"
+#include "components/variations/proto/study.pb.h"
+#include "components/variations/proto/variations_seed.pb.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+#if defined(OS_ANDROID)
+#include "components/variations/android/variations_seed_bridge.h"
+#endif // OS_ANDROID
+
+namespace variations {
+
+namespace {
+
+class TestVariationsSeedStore : public VariationsSeedStore {
+ public:
+ explicit TestVariationsSeedStore(PrefService* local_state)
+ : VariationsSeedStore(local_state) {}
+ ~TestVariationsSeedStore() override {}
+
+ bool StoreSeedForTesting(const std::string& seed_data) {
+ return StoreSeedData(seed_data, std::string(), std::string(),
+ base::Time::Now(), false, false, nullptr);
+ }
+
+ VariationsSeedStore::VerifySignatureResult VerifySeedSignature(
+ const std::string& seed_bytes,
+ const std::string& base64_seed_signature) override {
+ return VariationsSeedStore::VARIATIONS_SEED_SIGNATURE_ENUM_SIZE;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestVariationsSeedStore);
+};
+
+
+// Populates |seed| with simple test data. The resulting seed will contain one
+// study called "test", which contains one experiment called "abc" with
+// probability weight 100. |seed|'s study field will be cleared before adding
+// the new study.
+variations::VariationsSeed CreateTestSeed() {
+ variations::VariationsSeed seed;
+ variations::Study* study = seed.add_study();
+ study->set_name("test");
+ study->set_default_experiment_name("abc");
+ variations::Study_Experiment* experiment = study->add_experiment();
+ experiment->set_name("abc");
+ experiment->set_probability_weight(100);
+ seed.set_serial_number("123");
+ return seed;
+}
+
+// Serializes |seed| to protobuf binary format.
+std::string SerializeSeed(const variations::VariationsSeed& seed) {
+ std::string serialized_seed;
+ seed.SerializeToString(&serialized_seed);
+ return serialized_seed;
+}
+
+// Compresses |data| using Gzip compression and returns the result.
+std::string Compress(const std::string& data) {
+ std::string compressed;
+ const bool result = compression::GzipCompress(data, &compressed);
+ EXPECT_TRUE(result);
+ return compressed;
+}
+
+// Serializes |seed| to compressed base64-encoded protobuf binary format.
+std::string SerializeSeedBase64(const variations::VariationsSeed& seed) {
+ std::string serialized_seed = SerializeSeed(seed);
+ std::string base64_serialized_seed;
+ base::Base64Encode(Compress(serialized_seed), &base64_serialized_seed);
+ return base64_serialized_seed;
+}
+
+// Checks whether the pref with name |pref_name| is at its default value in
+// |prefs|.
+bool PrefHasDefaultValue(const TestingPrefServiceSimple& prefs,
+ const char* pref_name) {
+ return prefs.FindPreference(pref_name)->IsDefaultValue();
+}
+
+} // namespace
+
+TEST(VariationsSeedStoreTest, LoadSeed) {
+ // Store good seed data to test if loading from prefs works.
+ const variations::VariationsSeed seed = CreateTestSeed();
+ const std::string base64_seed = SerializeSeedBase64(seed);
+
+ TestingPrefServiceSimple prefs;
+ VariationsSeedStore::RegisterPrefs(prefs.registry());
+ prefs.SetString(prefs::kVariationsCompressedSeed, base64_seed);
+
+ TestVariationsSeedStore seed_store(&prefs);
+
+ variations::VariationsSeed loaded_seed;
+ // Check that loading a seed works correctly.
+ EXPECT_TRUE(seed_store.LoadSeed(&loaded_seed));
+
+ // Check that the loaded data is the same as the original.
+ EXPECT_EQ(SerializeSeed(seed), SerializeSeed(loaded_seed));
+ // Make sure the pref hasn't been changed.
+ EXPECT_FALSE(PrefHasDefaultValue(prefs, prefs::kVariationsCompressedSeed));
+ EXPECT_EQ(base64_seed, prefs.GetString(prefs::kVariationsCompressedSeed));
+
+ // Check that loading a bad seed returns false and clears the pref.
+ prefs.SetString(prefs::kVariationsCompressedSeed, "this should fail");
+ EXPECT_FALSE(PrefHasDefaultValue(prefs, prefs::kVariationsCompressedSeed));
+ EXPECT_FALSE(seed_store.LoadSeed(&loaded_seed));
+ EXPECT_TRUE(PrefHasDefaultValue(prefs, prefs::kVariationsCompressedSeed));
+ EXPECT_TRUE(PrefHasDefaultValue(prefs, prefs::kVariationsSeedDate));
+ EXPECT_TRUE(PrefHasDefaultValue(prefs, prefs::kVariationsSeedSignature));
+
+ // Check that having no seed in prefs results in a return value of false.
+ prefs.ClearPref(prefs::kVariationsCompressedSeed);
+ EXPECT_FALSE(seed_store.LoadSeed(&loaded_seed));
+}
+
+TEST(VariationsSeedStoreTest, GetInvalidSignature) {
+ const variations::VariationsSeed seed = CreateTestSeed();
+ const std::string base64_seed = SerializeSeedBase64(seed);
+
+ TestingPrefServiceSimple prefs;
+ VariationsSeedStore::RegisterPrefs(prefs.registry());
+ prefs.SetString(prefs::kVariationsSeed, base64_seed);
+
+ // The below seed and signature pair were generated using the server's
+ // private key.
+ const std::string base64_seed_data =
+ "CigxZDI5NDY0ZmIzZDc4ZmYxNTU2ZTViNTUxYzY0NDdjYmM3NGU1ZmQwEr0BCh9VTUEtVW5p"
+ "Zm9ybWl0eS1UcmlhbC0xMC1QZXJjZW50GICckqUFOAFCB2RlZmF1bHRKCwoHZGVmYXVsdBAB"
+ "SgwKCGdyb3VwXzAxEAFKDAoIZ3JvdXBfMDIQAUoMCghncm91cF8wMxABSgwKCGdyb3VwXzA0"
+ "EAFKDAoIZ3JvdXBfMDUQAUoMCghncm91cF8wNhABSgwKCGdyb3VwXzA3EAFKDAoIZ3JvdXBf"
+ "MDgQAUoMCghncm91cF8wORAB";
+ const std::string base64_seed_signature =
+ "MEQCIDD1IVxjzWYncun+9IGzqYjZvqxxujQEayJULTlbTGA/AiAr0oVmEgVUQZBYq5VLOSvy"
+ "96JkMYgzTkHPwbv7K/CmgA==";
+ const std::string base64_seed_signature_invalid =
+ "AEQCIDD1IVxjzWYncun+9IGzqYjZvqxxujQEayJULTlbTGA/AiAr0oVmEgVUQZBYq5VLOSvy"
+ "96JkMYgzTkHPwbv7K/CmgA==";
+
+ // Set seed and valid signature in prefs.
+ prefs.SetString(prefs::kVariationsSeed, base64_seed_data);
+ prefs.SetString(prefs::kVariationsSeedSignature, base64_seed_signature);
+
+ VariationsSeedStore seed_store(&prefs);
+ variations::VariationsSeed loaded_seed;
+ seed_store.LoadSeed(&loaded_seed);
+ std::string invalid_signature = seed_store.GetInvalidSignature();
+ // Valid signature so we get an empty string.
+ EXPECT_EQ(std::string(), invalid_signature);
+
+ prefs.SetString(prefs::kVariationsSeedSignature,
+ base64_seed_signature_invalid);
+ seed_store.LoadSeed(&loaded_seed);
+ // Invalid signature, so we should get the signature itself, except on mobile
+ // where we should get an empty string because verification is not enabled.
+ invalid_signature = seed_store.GetInvalidSignature();
+#if defined(OS_IOS) || defined(OS_ANDROID)
+ EXPECT_EQ(std::string(), invalid_signature);
+#else
+ EXPECT_EQ(base64_seed_signature_invalid, invalid_signature);
+#endif
+
+ prefs.SetString(prefs::kVariationsSeedSignature, std::string());
+ seed_store.LoadSeed(&loaded_seed);
+ invalid_signature = seed_store.GetInvalidSignature();
+ // Empty signature, not considered invalid.
+ EXPECT_EQ(std::string(), invalid_signature);
+}
+
+TEST(VariationsSeedStoreTest, StoreSeedData) {
+ const variations::VariationsSeed seed = CreateTestSeed();
+ const std::string serialized_seed = SerializeSeed(seed);
+
+ TestingPrefServiceSimple prefs;
+ VariationsSeedStore::RegisterPrefs(prefs.registry());
+
+ TestVariationsSeedStore seed_store(&prefs);
+
+ EXPECT_TRUE(seed_store.StoreSeedForTesting(serialized_seed));
+ // Make sure the pref was actually set.
+ EXPECT_FALSE(PrefHasDefaultValue(prefs, prefs::kVariationsCompressedSeed));
+
+ std::string loaded_compressed_seed =
+ prefs.GetString(prefs::kVariationsCompressedSeed);
+ std::string decoded_compressed_seed;
+ ASSERT_TRUE(base::Base64Decode(loaded_compressed_seed,
+ &decoded_compressed_seed));
+ // Make sure the stored seed from pref is the same as the seed we created.
+ EXPECT_EQ(Compress(serialized_seed), decoded_compressed_seed);
+
+ // Check if trying to store a bad seed leaves the pref unchanged.
+ prefs.ClearPref(prefs::kVariationsCompressedSeed);
+ EXPECT_FALSE(seed_store.StoreSeedForTesting("should fail"));
+ EXPECT_TRUE(PrefHasDefaultValue(prefs, prefs::kVariationsCompressedSeed));
+}
+
+TEST(VariationsSeedStoreTest, StoreSeedData_ParsedSeed) {
+ const variations::VariationsSeed seed = CreateTestSeed();
+ const std::string serialized_seed = SerializeSeed(seed);
+
+ TestingPrefServiceSimple prefs;
+ VariationsSeedStore::RegisterPrefs(prefs.registry());
+ TestVariationsSeedStore seed_store(&prefs);
+
+ variations::VariationsSeed parsed_seed;
+ EXPECT_TRUE(seed_store.StoreSeedData(serialized_seed, std::string(),
+ std::string(), base::Time::Now(), false,
+ false, &parsed_seed));
+ EXPECT_EQ(serialized_seed, SerializeSeed(parsed_seed));
+}
+
+TEST(VariationsSeedStoreTest, StoreSeedData_CountryCode) {
+ TestingPrefServiceSimple prefs;
+ VariationsSeedStore::RegisterPrefs(prefs.registry());
+ TestVariationsSeedStore seed_store(&prefs);
+
+ // Test with a seed country code and no header value.
+ variations::VariationsSeed seed = CreateTestSeed();
+ seed.set_country_code("test_country");
+ EXPECT_TRUE(seed_store.StoreSeedData(SerializeSeed(seed), std::string(),
+ std::string(), base::Time::Now(), false,
+ false, nullptr));
+ EXPECT_EQ("test_country", prefs.GetString(prefs::kVariationsCountry));
+
+ // Test with a header value and no seed country.
+ prefs.ClearPref(prefs::kVariationsCountry);
+ seed.clear_country_code();
+ EXPECT_TRUE(seed_store.StoreSeedData(SerializeSeed(seed), std::string(),
+ "test_country2", base::Time::Now(),
+ false, false, nullptr));
+ EXPECT_EQ("test_country2", prefs.GetString(prefs::kVariationsCountry));
+
+ // Test with a seed country code and header value.
+ prefs.ClearPref(prefs::kVariationsCountry);
+ seed.set_country_code("test_country3");
+ EXPECT_TRUE(seed_store.StoreSeedData(SerializeSeed(seed), std::string(),
+ "test_country4", base::Time::Now(),
+ false, false, nullptr));
+ EXPECT_EQ("test_country4", prefs.GetString(prefs::kVariationsCountry));
+
+ // Test with no country code specified - which should preserve the old value.
+ seed.clear_country_code();
+ EXPECT_TRUE(seed_store.StoreSeedData(SerializeSeed(seed), std::string(),
+ std::string(), base::Time::Now(), false,
+ false, nullptr));
+ EXPECT_EQ("test_country4", prefs.GetString(prefs::kVariationsCountry));
+}
+
+TEST(VariationsSeedStoreTest, StoreSeedData_GzippedSeed) {
+ const variations::VariationsSeed seed = CreateTestSeed();
+ const std::string serialized_seed = SerializeSeed(seed);
+ std::string compressed_seed;
+ ASSERT_TRUE(compression::GzipCompress(serialized_seed, &compressed_seed));
+
+ TestingPrefServiceSimple prefs;
+ VariationsSeedStore::RegisterPrefs(prefs.registry());
+ TestVariationsSeedStore seed_store(&prefs);
+
+ variations::VariationsSeed parsed_seed;
+ EXPECT_TRUE(seed_store.StoreSeedData(compressed_seed, std::string(),
+ std::string(), base::Time::Now(), false,
+ true, &parsed_seed));
+ EXPECT_EQ(serialized_seed, SerializeSeed(parsed_seed));
+}
+
+TEST(VariationsSeedStoreTest, StoreSeedData_GzippedEmptySeed) {
+ std::string empty_seed;
+ std::string compressed_seed;
+ ASSERT_TRUE(compression::GzipCompress(empty_seed, &compressed_seed));
+
+ TestingPrefServiceSimple prefs;
+ VariationsSeedStore::RegisterPrefs(prefs.registry());
+ TestVariationsSeedStore seed_store(&prefs);
+
+ variations::VariationsSeed parsed_seed;
+ EXPECT_FALSE(seed_store.StoreSeedData(compressed_seed, std::string(),
+ std::string(), base::Time::Now(), false,
+ true, &parsed_seed));
+}
+
+TEST(VariationsSeedStoreTest, VerifySeedSignature) {
+ // The below seed and signature pair were generated using the server's
+ // private key.
+ const std::string base64_seed_data =
+ "CigxZDI5NDY0ZmIzZDc4ZmYxNTU2ZTViNTUxYzY0NDdjYmM3NGU1ZmQwEr0BCh9VTUEtVW5p"
+ "Zm9ybWl0eS1UcmlhbC0xMC1QZXJjZW50GICckqUFOAFCB2RlZmF1bHRKCwoHZGVmYXVsdBAB"
+ "SgwKCGdyb3VwXzAxEAFKDAoIZ3JvdXBfMDIQAUoMCghncm91cF8wMxABSgwKCGdyb3VwXzA0"
+ "EAFKDAoIZ3JvdXBfMDUQAUoMCghncm91cF8wNhABSgwKCGdyb3VwXzA3EAFKDAoIZ3JvdXBf"
+ "MDgQAUoMCghncm91cF8wORAB";
+ const std::string base64_seed_signature =
+ "MEQCIDD1IVxjzWYncun+9IGzqYjZvqxxujQEayJULTlbTGA/AiAr0oVmEgVUQZBYq5VLOSvy"
+ "96JkMYgzTkHPwbv7K/CmgA==";
+
+ std::string seed_data;
+ EXPECT_TRUE(base::Base64Decode(base64_seed_data, &seed_data));
+
+ VariationsSeedStore seed_store(NULL);
+
+#if defined(OS_IOS) || defined(OS_ANDROID)
+ // Signature verification is not enabled on mobile.
+ if (seed_store.VerifySeedSignature(seed_data, base64_seed_signature) ==
+ VariationsSeedStore::VARIATIONS_SEED_SIGNATURE_ENUM_SIZE) {
+ return;
+ }
+#endif
+
+ // The above inputs should be valid.
+ EXPECT_EQ(VariationsSeedStore::VARIATIONS_SEED_SIGNATURE_VALID,
+ seed_store.VerifySeedSignature(seed_data, base64_seed_signature));
+
+ // If there's no signature, the corresponding result should be returned.
+ EXPECT_EQ(VariationsSeedStore::VARIATIONS_SEED_SIGNATURE_MISSING,
+ seed_store.VerifySeedSignature(seed_data, std::string()));
+
+ // Using non-base64 encoded value as signature (e.g. seed data) should fail.
+ EXPECT_EQ(VariationsSeedStore::VARIATIONS_SEED_SIGNATURE_DECODE_FAILED,
+ seed_store.VerifySeedSignature(seed_data, seed_data));
+
+ // Using a different signature (e.g. the base64 seed data) should fail.
+#if defined(USE_OPENSSL)
+ // OpenSSL doesn't distinguish signature decode failure from the
+ // signature not matching.
+ EXPECT_EQ(VariationsSeedStore::VARIATIONS_SEED_SIGNATURE_INVALID_SEED,
+ seed_store.VerifySeedSignature(seed_data, base64_seed_data));
+#else
+ EXPECT_EQ(VariationsSeedStore::VARIATIONS_SEED_SIGNATURE_INVALID_SIGNATURE,
+ seed_store.VerifySeedSignature(seed_data, base64_seed_data));
+#endif
+
+ // Using a different seed should not match the signature.
+ seed_data[0] = 'x';
+ EXPECT_EQ(VariationsSeedStore::VARIATIONS_SEED_SIGNATURE_INVALID_SEED,
+ seed_store.VerifySeedSignature(seed_data, base64_seed_signature));
+}
+
+TEST(VariationsSeedStoreTest, ApplyDeltaPatch) {
+ // Sample seeds and the server produced delta between them to verify that the
+ // client code is able to decode the deltas produced by the server.
+ const std::string base64_before_seed_data =
+ "CigxN2E4ZGJiOTI4ODI0ZGU3ZDU2MGUyODRlODY1ZDllYzg2NzU1MTE0ElgKDFVNQVN0YWJp"
+ "bGl0eRjEyomgBTgBQgtTZXBhcmF0ZUxvZ0oLCgdEZWZhdWx0EABKDwoLU2VwYXJhdGVMb2cQ"
+ "ZFIVEgszNC4wLjE4MDEuMCAAIAEgAiADEkQKIFVNQS1Vbmlmb3JtaXR5LVRyaWFsLTEwMC1Q"
+ "ZXJjZW50GIDjhcAFOAFCCGdyb3VwXzAxSgwKCGdyb3VwXzAxEAFgARJPCh9VTUEtVW5pZm9y"
+ "bWl0eS1UcmlhbC01MC1QZXJjZW50GIDjhcAFOAFCB2RlZmF1bHRKDAoIZ3JvdXBfMDEQAUoL"
+ "CgdkZWZhdWx0EAFgAQ==";
+ const std::string base64_after_seed_data =
+ "CigyNGQzYTM3ZTAxYmViOWYwNWYzMjM4YjUzNWY3MDg1ZmZlZWI4NzQwElgKDFVNQVN0YWJp"
+ "bGl0eRjEyomgBTgBQgtTZXBhcmF0ZUxvZ0oLCgdEZWZhdWx0EABKDwoLU2VwYXJhdGVMb2cQ"
+ "ZFIVEgszNC4wLjE4MDEuMCAAIAEgAiADEpIBCh9VTUEtVW5pZm9ybWl0eS1UcmlhbC0yMC1Q"
+ "ZXJjZW50GIDjhcAFOAFCB2RlZmF1bHRKEQoIZ3JvdXBfMDEQARijtskBShEKCGdyb3VwXzAy"
+ "EAEYpLbJAUoRCghncm91cF8wMxABGKW2yQFKEQoIZ3JvdXBfMDQQARimtskBShAKB2RlZmF1"
+ "bHQQARiitskBYAESWAofVU1BLVVuaWZvcm1pdHktVHJpYWwtNTAtUGVyY2VudBiA44XABTgB"
+ "QgdkZWZhdWx0Sg8KC25vbl9kZWZhdWx0EAFKCwoHZGVmYXVsdBABUgQoACgBYAE=";
+ const std::string base64_delta_data =
+ "KgooMjRkM2EzN2UwMWJlYjlmMDVmMzIzOGI1MzVmNzA4NWZmZWViODc0MAAqW+4BkgEKH1VN"
+ "QS1Vbmlmb3JtaXR5LVRyaWFsLTIwLVBlcmNlbnQYgOOFwAU4AUIHZGVmYXVsdEoRCghncm91"
+ "cF8wMRABGKO2yQFKEQoIZ3JvdXBfMDIQARiktskBShEKCGdyb3VwXzAzEAEYpbbJAUoRCghn"
+ "cm91cF8wNBABGKa2yQFKEAoHZGVmYXVsdBABGKK2yQFgARJYCh9VTUEtVW5pZm9ybWl0eS1U"
+ "cmlhbC01MC1QZXJjZW50GIDjhcAFOAFCB2RlZmF1bHRKDwoLbm9uX2RlZmF1bHQQAUoLCgdk"
+ "ZWZhdWx0EAFSBCgAKAFgAQ==";
+
+ std::string before_seed_data;
+ std::string after_seed_data;
+ std::string delta_data;
+ EXPECT_TRUE(base::Base64Decode(base64_before_seed_data, &before_seed_data));
+ EXPECT_TRUE(base::Base64Decode(base64_after_seed_data, &after_seed_data));
+ EXPECT_TRUE(base::Base64Decode(base64_delta_data, &delta_data));
+
+ std::string output;
+ EXPECT_TRUE(VariationsSeedStore::ApplyDeltaPatch(before_seed_data, delta_data,
+ &output));
+ EXPECT_EQ(after_seed_data, output);
+}
+
+#if defined(OS_ANDROID)
+TEST(VariationsSeedStoreTest, ImportFirstRunJavaSeed) {
+ const std::string test_seed_data = "raw_seed_data_test";
+ const std::string test_seed_signature = "seed_signature_test";
+ const std::string test_seed_country = "seed_country_code_test";
+ const std::string test_response_date = "seed_response_date_test";
+ const bool test_is_gzip_compressed = true;
+ android::SetJavaFirstRunPrefsForTesting(test_seed_data, test_seed_signature,
+ test_seed_country, test_response_date,
+ test_is_gzip_compressed);
+
+ std::string seed_data;
+ std::string seed_signature;
+ std::string seed_country;
+ std::string response_date;
+ bool is_gzip_compressed;
+ android::GetVariationsFirstRunSeed(&seed_data, &seed_signature, &seed_country,
+ &response_date, &is_gzip_compressed);
+ EXPECT_EQ(test_seed_data, seed_data);
+ EXPECT_EQ(test_seed_signature, seed_signature);
+ EXPECT_EQ(test_seed_country, seed_country);
+ EXPECT_EQ(test_response_date, response_date);
+ EXPECT_EQ(test_is_gzip_compressed, is_gzip_compressed);
+
+ android::ClearJavaFirstRunPrefs();
+ android::GetVariationsFirstRunSeed(&seed_data, &seed_signature, &seed_country,
+ &response_date, &is_gzip_compressed);
+ EXPECT_EQ("", seed_data);
+ EXPECT_EQ("", seed_signature);
+ EXPECT_EQ("", seed_country);
+ EXPECT_EQ("", response_date);
+ EXPECT_FALSE(is_gzip_compressed);
+}
+#endif // OS_ANDROID
+
+} // namespace variations
diff --git a/chromium/components/variations/variations_switches.cc b/chromium/components/variations/variations_switches.cc
new file mode 100644
index 00000000000..e4b5b215444
--- /dev/null
+++ b/chromium/components/variations/variations_switches.cc
@@ -0,0 +1,22 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/variations_switches.h"
+
+namespace variations {
+namespace switches {
+
+// Fakes the channel of the browser for purposes of Variations filtering. This
+// is to be used for testing only. Possible values are "stable", "beta", "dev"
+// and "canary". Note that this only applies if the browser's reported channel
+// is UNKNOWN.
+const char kFakeVariationsChannel[] = "fake-variations-channel";
+
+// Specifies a custom URL for the server which reports variation data to the
+// client. Specifying this switch enables the Variations service on
+// unofficial builds. See variations_service.cc.
+const char kVariationsServerURL[] = "variations-server-url";
+
+} // namespace switches
+} // namespace variations
diff --git a/chromium/components/variations/variations_switches.h b/chromium/components/variations/variations_switches.h
new file mode 100644
index 00000000000..8e768290997
--- /dev/null
+++ b/chromium/components/variations/variations_switches.h
@@ -0,0 +1,20 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_VARIATIONS_SWITCHES_H_
+#define COMPONENTS_VARIATIONS_VARIATIONS_SWITCHES_H_
+
+namespace variations {
+namespace switches {
+
+// Alphabetical list of switches specific to the variations component. Document
+// each in the .cc file.
+
+extern const char kFakeVariationsChannel[];
+extern const char kVariationsServerURL[];
+
+} // namespace switches
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_VARIATIONS_SWITCHES_H_
diff --git a/chromium/components/variations/variations_url_constants.cc b/chromium/components/variations/variations_url_constants.cc
new file mode 100644
index 00000000000..dce446a641b
--- /dev/null
+++ b/chromium/components/variations/variations_url_constants.cc
@@ -0,0 +1,13 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/variations_url_constants.h"
+
+namespace variations {
+
+// Default server of Variations seed info.
+const char kDefaultServerUrl[] =
+ "https://clients4.google.com/chrome-variations/seed";
+
+} // namespace variations
diff --git a/chromium/components/variations/variations_url_constants.h b/chromium/components/variations/variations_url_constants.h
new file mode 100644
index 00000000000..ffff0dcaa74
--- /dev/null
+++ b/chromium/components/variations/variations_url_constants.h
@@ -0,0 +1,14 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_VARIATIONS_URL_CONSTANTS_H_
+#define COMPONENTS_VARIATIONS_VARIATIONS_URL_CONSTANTS_H_
+
+namespace variations {
+
+extern const char kDefaultServerUrl[];
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_VARIATIONS_URL_CONSTANTS_H_
diff --git a/chromium/components/variations/variations_util.cc b/chromium/components/variations/variations_util.cc
new file mode 100644
index 00000000000..b5116d101d8
--- /dev/null
+++ b/chromium/components/variations/variations_util.cc
@@ -0,0 +1,20 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/variations_util.h"
+
+#include <vector>
+
+#include "components/crash/core/common/crash_keys.h"
+#include "components/variations/active_field_trials.h"
+
+namespace variations {
+
+void SetVariationListCrashKeys() {
+ std::vector<std::string> experiment_strings;
+ GetFieldTrialActiveGroupIdsAsStrings(&experiment_strings);
+ crash_keys::SetVariationsList(experiment_strings);
+}
+
+} // namespace variations
diff --git a/chromium/components/variations/variations_util.h b/chromium/components/variations/variations_util.h
new file mode 100644
index 00000000000..c2d969da24d
--- /dev/null
+++ b/chromium/components/variations/variations_util.h
@@ -0,0 +1,18 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_VARIATIONS_UTIL_H_
+#define COMPONENTS_VARIATIONS_VARIATIONS_UTIL_H_
+
+#include <string>
+
+namespace variations {
+
+// Get the current set of chosen FieldTrial groups (aka variations) and send
+// them to the logging module so it can save it for crash dumps.
+void SetVariationListCrashKeys();
+
+} // namespace variations
+
+#endif // COMPONENTS_VARIATIONS_VARIATIONS_UTIL_H_
diff --git a/chromium/content/browser/appcache/appcache_storage_impl.cc b/chromium/content/browser/appcache/appcache_storage_impl.cc
index ceb52271fd8..d2548ebd572 100644
--- a/chromium/content/browser/appcache/appcache_storage_impl.cc
+++ b/chromium/content/browser/appcache/appcache_storage_impl.cc
@@ -1736,22 +1736,25 @@ AppCacheResponseReader* AppCacheStorageImpl::CreateResponseReader(
const GURL& manifest_url,
int64_t group_id,
int64_t response_id) {
- return new AppCacheResponseReader(response_id, group_id,
- disk_cache()->GetWeakPtr());
+ return new AppCacheResponseReader(
+ response_id, group_id,
+ is_disabled_ ? nullptr : disk_cache()->GetWeakPtr());
}
AppCacheResponseWriter* AppCacheStorageImpl::CreateResponseWriter(
const GURL& manifest_url,
int64_t group_id) {
- return new AppCacheResponseWriter(NewResponseId(), group_id,
- disk_cache()->GetWeakPtr());
+ return new AppCacheResponseWriter(
+ NewResponseId(), group_id,
+ is_disabled_ ? nullptr : disk_cache()->GetWeakPtr());
}
AppCacheResponseMetadataWriter*
AppCacheStorageImpl::CreateResponseMetadataWriter(int64_t group_id,
int64_t response_id) {
- return new AppCacheResponseMetadataWriter(response_id, group_id,
- disk_cache()->GetWeakPtr());
+ return new AppCacheResponseMetadataWriter(
+ response_id, group_id,
+ is_disabled_ ? nullptr : disk_cache()->GetWeakPtr());
}
void AppCacheStorageImpl::DoomResponses(
@@ -1816,8 +1819,7 @@ void AppCacheStorageImpl::DeleteOneResponse() {
DCHECK(is_response_deletion_scheduled_);
DCHECK(!deletable_response_ids_.empty());
- if (!disk_cache()) {
- DCHECK(is_disabled_);
+ if (is_disabled_) {
deletable_response_ids_.clear();
deleted_response_ids_.clear();
is_response_deletion_scheduled_ = false;
@@ -1826,7 +1828,7 @@ void AppCacheStorageImpl::DeleteOneResponse() {
// TODO(michaeln): add group_id to DoomEntry args
int64_t id = deletable_response_ids_.front();
- int rv = disk_cache_->DoomEntry(
+ int rv = disk_cache()->DoomEntry(
id, base::Bind(&AppCacheStorageImpl::OnDeletedOneResponse,
base::Unretained(this)));
if (rv != net::ERR_IO_PENDING)
@@ -1905,9 +1907,7 @@ void AppCacheStorageImpl::RunOnePendingSimpleTask() {
AppCacheDiskCache* AppCacheStorageImpl::disk_cache() {
DCHECK(IsInitTaskComplete());
-
- if (is_disabled_)
- return NULL;
+ DCHECK(!is_disabled_);
if (!disk_cache_) {
int rv = net::OK;
diff --git a/chromium/content/browser/appcache/appcache_storage_impl.h b/chromium/content/browser/appcache/appcache_storage_impl.h
index 664ba31f97e..b53c7122b31 100644
--- a/chromium/content/browser/appcache/appcache_storage_impl.h
+++ b/chromium/content/browser/appcache/appcache_storage_impl.h
@@ -147,6 +147,7 @@ class AppCacheStorageImpl : public AppCacheStorage {
int64_t group_id,
const GURL& manifest_url);
+ // Don't call this when |is_disabled_| is true.
CONTENT_EXPORT AppCacheDiskCache* disk_cache();
// The directory in which we place files in the file system.
diff --git a/chromium/content/browser/frame_host/navigation_controller_impl.cc b/chromium/content/browser/frame_host/navigation_controller_impl.cc
index 32975719bc7..8461e36106a 100644
--- a/chromium/content/browser/frame_host/navigation_controller_impl.cc
+++ b/chromium/content/browser/frame_host/navigation_controller_impl.cc
@@ -832,8 +832,8 @@ bool NavigationControllerImpl::RendererDidNavigate(
details->type = ClassifyNavigation(rfh, params);
// is_in_page must be computed before the entry gets committed.
- details->is_in_page = IsURLInPageNavigation(
- params.url, params.was_within_same_page, rfh);
+ details->is_in_page = IsURLInPageNavigation(params.url, params.origin,
+ params.was_within_same_page, rfh);
switch (details->type) {
case NAVIGATION_TYPE_NEW_PAGE:
@@ -1367,8 +1367,13 @@ int NavigationControllerImpl::GetIndexOfEntry(
// in-page. Therefore, trust the renderer if the URLs are on the same origin,
// and assume the renderer is malicious if a cross-origin navigation claims to
// be in-page.
+//
+// TODO(creis): Clean up and simplify the about:blank and origin checks below,
+// which are likely redundant with each other. Be careful about data URLs vs
+// about:blank, both of which are unique origins and thus not considered equal.
bool NavigationControllerImpl::IsURLInPageNavigation(
const GURL& url,
+ const url::Origin& origin,
bool renderer_says_in_page,
RenderFrameHost* rfh) const {
GURL last_committed_url;
@@ -1397,6 +1402,7 @@ bool NavigationControllerImpl::IsURLInPageNavigation(
// for now.
last_committed_url == GURL(url::kAboutBlankURL) ||
last_committed_url.GetOrigin() == url.GetOrigin() ||
+ committed_origin == origin ||
!prefs.web_security_enabled ||
(prefs.allow_universal_access_from_file_urls &&
committed_origin.scheme() == url::kFileScheme);
diff --git a/chromium/content/browser/frame_host/navigation_controller_impl.h b/chromium/content/browser/frame_host/navigation_controller_impl.h
index a0067f49051..a0bb12dc626 100644
--- a/chromium/content/browser/frame_host/navigation_controller_impl.h
+++ b/chromium/content/browser/frame_host/navigation_controller_impl.h
@@ -155,10 +155,10 @@ class CONTENT_EXPORT NavigationControllerImpl
// so that we know to load URLs that were pending as "lazy" loads.
void SetActive(bool is_active);
- // Returns true if the given URL would be an in-page navigation (i.e. only the
- // reference fragment is different) from the last committed URL in the
- // specified frame. If there is no last committed entry, then nothing will be
- // in-page.
+ // Returns true if the given URL would be an in-page navigation (e.g., if the
+ // reference fragment is different, or after a pushState) from the last
+ // committed URL in the specified frame. If there is no last committed entry,
+ // then nothing will be in-page.
//
// Special note: if the URLs are the same, it does NOT automatically count as
// an in-page navigation. Neither does an input URL that has no ref, even if
@@ -170,11 +170,12 @@ class CONTENT_EXPORT NavigationControllerImpl
// The situation is made murkier by history.replaceState(), which could
// provide the same URL as part of an in-page navigation, not a reload. So
// we need to let the (untrustworthy) renderer resolve the ambiguity, but
- // only when the URLs are on the same origin.
- bool IsURLInPageNavigation(
- const GURL& url,
- bool renderer_says_in_page,
- RenderFrameHost* rfh) const;
+ // only when the URLs are on the same origin. We rely on |origin|, which
+ // matters in cases like about:blank that otherwise look cross-origin.
+ bool IsURLInPageNavigation(const GURL& url,
+ const url::Origin& origin,
+ bool renderer_says_in_page,
+ RenderFrameHost* rfh) const;
// Sets the SessionStorageNamespace for the given |partition_id|. This is
// used during initialization of a new NavigationController to allow
diff --git a/chromium/content/browser/frame_host/navigation_controller_impl_browsertest.cc b/chromium/content/browser/frame_host/navigation_controller_impl_browsertest.cc
index 7d01f2a4173..743f2772b78 100644
--- a/chromium/content/browser/frame_host/navigation_controller_impl_browsertest.cc
+++ b/chromium/content/browser/frame_host/navigation_controller_impl_browsertest.cc
@@ -3539,6 +3539,144 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
#endif
}
+// Test for in-page navigation kills when going back to about:blank after a
+// document.write. See https://crbug.com/446959.
+IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
+ BackAfterIframeDocumentWrite) {
+ GURL links_url(embedded_test_server()->GetURL(
+ "/navigation_controller/page_with_links.html"));
+ NavigateToURL(shell(), links_url);
+ EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+
+ NavigationController& controller = shell()->web_contents()->GetController();
+ FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
+ ->GetFrameTree()
+ ->root();
+
+ EXPECT_EQ(1, controller.GetEntryCount());
+ EXPECT_EQ(1, RendererHistoryLength(shell()));
+
+ // Add an iframe with no 'src'.
+ GURL blank_url(url::kAboutBlankURL);
+ std::string script =
+ "var iframe = document.createElement('iframe');"
+ "iframe.id = 'frame';"
+ "document.body.appendChild(iframe);";
+ EXPECT_TRUE(ExecuteScript(root->current_frame_host(), script));
+ EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+ EXPECT_EQ(1, controller.GetEntryCount());
+ EXPECT_EQ(1, RendererHistoryLength(shell()));
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ ASSERT_EQ(1U, root->child_count());
+ FrameTreeNode* frame = root->child_at(0);
+ ASSERT_NE(nullptr, frame);
+ EXPECT_EQ(blank_url, frame->current_url());
+
+ // Do a document.write in the subframe to create a link to click.
+ std::string document_write_script =
+ "var iframe = document.getElementById('frame');"
+ "iframe.contentWindow.document.write("
+ " \"<a id='fraglink' href='#frag'>fragment link</a>\");"
+ "iframe.contentWindow.document.close();";
+ EXPECT_TRUE(ExecuteScript(root->current_frame_host(), document_write_script));
+
+ // Click the link to do an in-page navigation. Due to the document.write, the
+ // new URL matches the parent frame's URL.
+ GURL frame_url_2(embedded_test_server()->GetURL(
+ "/navigation_controller/page_with_links.html#frag"));
+ std::string link_script = "document.getElementById('fraglink').click()";
+ EXPECT_TRUE(ExecuteScript(frame->current_frame_host(), link_script));
+ EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+ EXPECT_EQ(2, controller.GetEntryCount());
+ EXPECT_EQ(2, RendererHistoryLength(shell()));
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(frame_url_2, frame->current_url());
+
+ // Go back.
+ {
+ TestNavigationObserver observer(shell()->web_contents(), 1);
+ controller.GoBack();
+ observer.Wait();
+ }
+
+ // Verify the process is still alive by running script. We can't just call
+ // IsRenderFrameLive after the navigation since it might not have disconnected
+ // yet.
+ EXPECT_TRUE(ExecuteScript(root->current_frame_host(), "true;"));
+ EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
+
+ EXPECT_EQ(blank_url, frame->current_url());
+}
+
+// Test for in-page navigation kills when going back to about:blank in an iframe
+// of a data URL, after a document.write. This differs from
+// BackAfterIframeDocumentWrite because both about:blank and the data URL are
+// considered unique origins. See https://crbug.com/446959.
+IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
+ BackAfterIframeDocumentWriteInDataURL) {
+ GURL data_url("data:text/html,Top level page");
+ NavigateToURL(shell(), data_url);
+ EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+
+ NavigationController& controller = shell()->web_contents()->GetController();
+ FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
+ ->GetFrameTree()
+ ->root();
+
+ EXPECT_EQ(1, controller.GetEntryCount());
+ EXPECT_EQ(1, RendererHistoryLength(shell()));
+
+ // Add an iframe with no 'src'.
+ GURL blank_url(url::kAboutBlankURL);
+ std::string script =
+ "var iframe = document.createElement('iframe');"
+ "iframe.id = 'frame';"
+ "document.body.appendChild(iframe);";
+ EXPECT_TRUE(ExecuteScript(root->current_frame_host(), script));
+ EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+ EXPECT_EQ(1, controller.GetEntryCount());
+ EXPECT_EQ(1, RendererHistoryLength(shell()));
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
+ ASSERT_EQ(1U, root->child_count());
+ FrameTreeNode* frame = root->child_at(0);
+ ASSERT_NE(nullptr, frame);
+ EXPECT_EQ(blank_url, frame->current_url());
+
+ // Do a document.write in the subframe to create a link to click.
+ std::string document_write_script =
+ "var iframe = document.getElementById('frame');"
+ "iframe.contentWindow.document.write("
+ " \"<a id='fraglink' href='#frag'>fragment link</a>\");"
+ "iframe.contentWindow.document.close();";
+ EXPECT_TRUE(ExecuteScript(root->current_frame_host(), document_write_script));
+
+ // Click the link to do an in-page navigation. Due to the document.write, the
+ // new URL matches the parent frame's URL.
+ GURL frame_url_2("data:text/html,Top level page#frag");
+ std::string link_script = "document.getElementById('fraglink').click()";
+ EXPECT_TRUE(ExecuteScript(frame->current_frame_host(), link_script));
+ EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+ EXPECT_EQ(2, controller.GetEntryCount());
+ EXPECT_EQ(2, RendererHistoryLength(shell()));
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
+ EXPECT_EQ(frame_url_2, frame->current_url());
+
+ // Go back.
+ {
+ TestNavigationObserver observer(shell()->web_contents(), 1);
+ controller.GoBack();
+ observer.Wait();
+ }
+
+ // Verify the process is still alive by running script. We can't just call
+ // IsRenderFrameLive after the navigation since it might not have disconnected
+ // yet.
+ EXPECT_TRUE(ExecuteScript(root->current_frame_host(), "true;"));
+ EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
+
+ EXPECT_EQ(blank_url, frame->current_url());
+}
+
// Ensure that we do not corrupt a NavigationEntry's PageState if a subframe
// forward navigation commits after we've already started another forward
// navigation in the main frame. See https://crbug.com/597322.
diff --git a/chromium/content/browser/frame_host/navigation_controller_impl_unittest.cc b/chromium/content/browser/frame_host/navigation_controller_impl_unittest.cc
index e86f1fdba6f..708f1190593 100644
--- a/chromium/content/browser/frame_host/navigation_controller_impl_unittest.cc
+++ b/chromium/content/browser/frame_host/navigation_controller_impl_unittest.cc
@@ -3672,59 +3672,65 @@ TEST_F(NavigationControllerTest, IsInPageNavigation) {
// TODO(japhet): We should only trust the renderer if the about:blank
// was the first document in the given frame, but we don't have enough
// information to identify that case currently.
+ // TODO(creis): Update this to verify that the origin of the about:blank page
+ // matches if the URL doesn't look same-origin.
const GURL blank_url(url::kAboutBlankURL);
+ const url::Origin blank_origin;
main_test_rfh()->NavigateAndCommitRendererInitiated(0, true, blank_url);
- EXPECT_TRUE(controller.IsURLInPageNavigation(url, true,
- main_test_rfh()));
+ EXPECT_TRUE(controller.IsURLInPageNavigation(url, url::Origin(url), true,
+ main_test_rfh()));
// Navigate to URL with no refs.
main_test_rfh()->NavigateAndCommitRendererInitiated(0, false, url);
// Reloading the page is not an in-page navigation.
- EXPECT_FALSE(controller.IsURLInPageNavigation(url, false, main_test_rfh()));
+ EXPECT_FALSE(controller.IsURLInPageNavigation(url, url::Origin(url), false,
+ main_test_rfh()));
const GURL other_url("http://www.google.com/add.html");
- EXPECT_FALSE(controller.IsURLInPageNavigation(other_url, false,
- main_test_rfh()));
+ EXPECT_FALSE(controller.IsURLInPageNavigation(
+ other_url, url::Origin(other_url), false, main_test_rfh()));
const GURL url_with_ref("http://www.google.com/home.html#my_ref");
- EXPECT_TRUE(controller.IsURLInPageNavigation(url_with_ref, true,
- main_test_rfh()));
+ EXPECT_TRUE(controller.IsURLInPageNavigation(
+ url_with_ref, url::Origin(url_with_ref), true, main_test_rfh()));
// Navigate to URL with refs.
main_test_rfh()->NavigateAndCommitRendererInitiated(1, true, url_with_ref);
// Reloading the page is not an in-page navigation.
- EXPECT_FALSE(controller.IsURLInPageNavigation(url_with_ref, false,
- main_test_rfh()));
- EXPECT_FALSE(controller.IsURLInPageNavigation(url, false,
- main_test_rfh()));
- EXPECT_FALSE(controller.IsURLInPageNavigation(other_url, false,
- main_test_rfh()));
+ EXPECT_FALSE(controller.IsURLInPageNavigation(
+ url_with_ref, url::Origin(url_with_ref), false, main_test_rfh()));
+ EXPECT_FALSE(controller.IsURLInPageNavigation(url, url::Origin(url), false,
+ main_test_rfh()));
+ EXPECT_FALSE(controller.IsURLInPageNavigation(
+ other_url, url::Origin(other_url), false, main_test_rfh()));
const GURL other_url_with_ref("http://www.google.com/home.html#my_other_ref");
- EXPECT_TRUE(controller.IsURLInPageNavigation(other_url_with_ref, true,
- main_test_rfh()));
+ EXPECT_TRUE(controller.IsURLInPageNavigation(other_url_with_ref,
+ url::Origin(other_url_with_ref),
+ true, main_test_rfh()));
// Going to the same url again will be considered in-page
// if the renderer says it is even if the navigation type isn't IN_PAGE.
- EXPECT_TRUE(controller.IsURLInPageNavigation(url_with_ref, true,
- main_test_rfh()));
+ EXPECT_TRUE(controller.IsURLInPageNavigation(
+ url_with_ref, url::Origin(url_with_ref), true, main_test_rfh()));
// Going back to the non ref url will be considered in-page if the navigation
// type is IN_PAGE.
- EXPECT_TRUE(controller.IsURLInPageNavigation(url, true,
- main_test_rfh()));
+ EXPECT_TRUE(controller.IsURLInPageNavigation(url, url::Origin(url), true,
+ main_test_rfh()));
// If the renderer says this is a same-origin in-page navigation, believe it.
// This is the pushState/replaceState case.
- EXPECT_TRUE(controller.IsURLInPageNavigation(other_url, true,
- main_test_rfh()));
+ EXPECT_TRUE(controller.IsURLInPageNavigation(
+ other_url, url::Origin(other_url), true, main_test_rfh()));
// Don't believe the renderer if it claims a cross-origin navigation is
// in-page.
const GURL different_origin_url("http://www.example.com");
MockRenderProcessHost* rph = main_test_rfh()->GetProcess();
EXPECT_EQ(0, rph->bad_msg_count());
- EXPECT_FALSE(controller.IsURLInPageNavigation(different_origin_url, true,
- main_test_rfh()));
+ EXPECT_FALSE(controller.IsURLInPageNavigation(
+ different_origin_url, url::Origin(different_origin_url), true,
+ main_test_rfh()));
EXPECT_EQ(1, rph->bad_msg_count());
}
@@ -3749,7 +3755,8 @@ TEST_F(NavigationControllerTest, IsInPageNavigationWithUniversalFileAccess) {
EXPECT_TRUE(file_origin.IsSameOriginWith(
main_test_rfh()->frame_tree_node()->current_origin()));
EXPECT_EQ(0, rph->bad_msg_count());
- EXPECT_TRUE(controller.IsURLInPageNavigation(different_origin_url, true,
+ EXPECT_TRUE(controller.IsURLInPageNavigation(
+ different_origin_url, url::Origin(different_origin_url), true,
main_test_rfh()));
EXPECT_EQ(0, rph->bad_msg_count());
@@ -3775,15 +3782,16 @@ TEST_F(NavigationControllerTest, IsInPageNavigationWithUniversalFileAccess) {
// so that a file URL would still be in-page. See https://crbug.com/553418.
EXPECT_TRUE(file_origin.IsSameOriginWith(
main_test_rfh()->frame_tree_node()->current_origin()));
- EXPECT_TRUE(
- controller.IsURLInPageNavigation(file_url, true, main_test_rfh()));
+ EXPECT_TRUE(controller.IsURLInPageNavigation(file_url, url::Origin(file_url),
+ true, main_test_rfh()));
EXPECT_EQ(0, rph->bad_msg_count());
// Don't honor allow_universal_access_from_file_urls if actual URL is
// not file scheme.
const GURL url("http://www.google.com/home.html");
main_test_rfh()->NavigateAndCommitRendererInitiated(2, true, url);
- EXPECT_FALSE(controller.IsURLInPageNavigation(different_origin_url, true,
+ EXPECT_FALSE(controller.IsURLInPageNavigation(
+ different_origin_url, url::Origin(different_origin_url), true,
main_test_rfh()));
EXPECT_EQ(1, rph->bad_msg_count());
}
diff --git a/chromium/content/browser/frame_host/navigator_impl.cc b/chromium/content/browser/frame_host/navigator_impl.cc
index 35ee34045fe..268f64c7864 100644
--- a/chromium/content/browser/frame_host/navigator_impl.cc
+++ b/chromium/content/browser/frame_host/navigator_impl.cc
@@ -462,7 +462,8 @@ void NavigatorImpl::DidNavigate(
has_embedded_credentials);
bool is_navigation_within_page = controller_->IsURLInPageNavigation(
- params.url, params.was_within_same_page, render_frame_host);
+ params.url, params.origin, params.was_within_same_page,
+ render_frame_host);
// If a frame claims it navigated within page, it must be the current frame,
// not a pending one.
diff --git a/chromium/content/browser/renderer_host/media/video_capture_manager.cc b/chromium/content/browser/renderer_host/media/video_capture_manager.cc
index cf31a65f6d9..579f04755e5 100644
--- a/chromium/content/browser/renderer_host/media/video_capture_manager.cc
+++ b/chromium/content/browser/renderer_host/media/video_capture_manager.cc
@@ -1047,9 +1047,20 @@ void VideoCaptureManager::ResumeDevices() {
entry->video_capture_device())
continue;
- // Session ID is only valid for Screen capture. So we can fake it to resume
- // video capture devices here.
- QueueStartDevice(kFakeSessionId, entry, entry->parameters);
+ // Check if the device is already in the start queue.
+ bool device_in_queue = false;
+ for (auto& request : device_start_queue_) {
+ if (request.serial_id() == entry->serial_id) {
+ device_in_queue = true;
+ break;
+ }
+ }
+
+ if (!device_in_queue) {
+ // Session ID is only valid for Screen capture. So we can fake it to
+ // resume video capture devices here.
+ QueueStartDevice(kFakeSessionId, entry, entry->parameters);
+ }
}
}
#endif // defined(OS_ANDROID)
diff --git a/chromium/content/browser/renderer_host/media/video_capture_manager.h b/chromium/content/browser/renderer_host/media/video_capture_manager.h
index 6e3e9561feb..585e8198c8d 100644
--- a/chromium/content/browser/renderer_host/media/video_capture_manager.h
+++ b/chromium/content/browser/renderer_host/media/video_capture_manager.h
@@ -156,6 +156,15 @@ class CONTENT_EXPORT VideoCaptureManager : public MediaStreamProvider {
scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner() {
return device_task_runner_;
}
+
+#if defined(OS_ANDROID)
+ // Some devices had troubles when stopped and restarted quickly, so the device
+ // is only stopped when Chrome is sent to background and not when, e.g., a tab
+ // is hidden, see http://crbug.com/582295.
+ void OnApplicationStateChange(base::android::ApplicationState state);
+#endif
+
+
private:
~VideoCaptureManager() override;
class DeviceEntry;
@@ -270,12 +279,6 @@ class CONTENT_EXPORT VideoCaptureManager : public MediaStreamProvider {
#endif
#if defined(OS_ANDROID)
- // On Android, we used to stop the video device when the host tab is hidden.
- // This caused problems on some devices when the device was stopped and
- // restarted quickly. See http://crbug/582295. Now instead, the device is
- // only stopped when Chrome goes to background. When a tab is hidden, it will
- // not receive video frames but the device is not stopped.
- void OnApplicationStateChange(base::android::ApplicationState state);
void ReleaseDevices();
void ResumeDevices();
diff --git a/chromium/content/browser/renderer_host/media/video_capture_manager_unittest.cc b/chromium/content/browser/renderer_host/media/video_capture_manager_unittest.cc
index 222dc5d0744..2bbeb886339 100644
--- a/chromium/content/browser/renderer_host/media/video_capture_manager_unittest.cc
+++ b/chromium/content/browser/renderer_host/media/video_capture_manager_unittest.cc
@@ -160,6 +160,12 @@ class VideoCaptureManagerTest : public testing::Test {
frame_observer_.get());
}
+#if defined(OS_ANDROID)
+ void ApplicationStateChange(base::android::ApplicationState state) {
+ vcm_->OnApplicationStateChange(state);
+ }
+#endif
+
int next_client_id_;
std::map<VideoCaptureControllerID, VideoCaptureController*> controllers_;
scoped_refptr<VideoCaptureManager> vcm_;
@@ -543,7 +549,7 @@ TEST_F(VideoCaptureManagerTest, CloseWithoutStop) {
}
// Try to open, start, pause and resume a device.
-TEST_F(VideoCaptureManagerTest, PauseAndResume) {
+TEST_F(VideoCaptureManagerTest, PauseAndResumeClient) {
StreamDeviceInfoArray devices;
InSequence s;
@@ -573,6 +579,47 @@ TEST_F(VideoCaptureManagerTest, PauseAndResume) {
vcm_->Unregister();
}
+#if defined(OS_ANDROID)
+// Try to open, start, pause and resume a device.
+TEST_F(VideoCaptureManagerTest, PauseAndResumeDevice) {
+ StreamDeviceInfoArray devices;
+
+ InSequence s;
+ EXPECT_CALL(*listener_, DevicesEnumerated(MEDIA_DEVICE_VIDEO_CAPTURE, _))
+ .WillOnce(SaveArg<1>(&devices));
+ EXPECT_CALL(*listener_, Opened(MEDIA_DEVICE_VIDEO_CAPTURE, _));
+ EXPECT_CALL(*listener_, Closed(MEDIA_DEVICE_VIDEO_CAPTURE, _));
+
+ vcm_->EnumerateDevices(MEDIA_DEVICE_VIDEO_CAPTURE);
+
+ // Wait to get device callback.
+ message_loop_->RunUntilIdle();
+
+ int video_session_id = vcm_->Open(devices.front());
+ VideoCaptureControllerID client_id = StartClient(video_session_id, true);
+
+ // Release/ResumeDevices according to ApplicationStatus. Should cause no
+ // problem in any order. Check https://crbug.com/615557 for more details.
+ ApplicationStateChange(
+ base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES);
+ ApplicationStateChange(
+ base::android::APPLICATION_STATE_HAS_STOPPED_ACTIVITIES);
+ ApplicationStateChange(
+ base::android::APPLICATION_STATE_HAS_STOPPED_ACTIVITIES);
+ ApplicationStateChange(
+ base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES);
+ ApplicationStateChange(
+ base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES);
+
+ StopClient(client_id);
+ vcm_->Close(video_session_id);
+
+ // Wait to check callbacks before removing the listener.
+ message_loop_->RunUntilIdle();
+ vcm_->Unregister();
+}
+#endif
+
// TODO(mcasas): Add a test to check consolidation of the supported formats
// provided by the device when http://crbug.com/323913 is closed.
diff --git a/chromium/content/renderer/media/webmediaplayer_ms_compositor.cc b/chromium/content/renderer/media/webmediaplayer_ms_compositor.cc
index 27e756246eb..e9ed6febdd6 100644
--- a/chromium/content/renderer/media/webmediaplayer_ms_compositor.cc
+++ b/chromium/content/renderer/media/webmediaplayer_ms_compositor.cc
@@ -36,8 +36,6 @@ scoped_refptr<media::VideoFrame> CopyFrame(
const scoped_refptr<media::VideoFrame>& frame,
media::SkCanvasVideoRenderer* video_renderer) {
scoped_refptr<media::VideoFrame> new_frame;
- const gfx::Size& size = frame->coded_size();
-
if (frame->HasTextures()) {
DCHECK(frame->format() == media::PIXEL_FORMAT_ARGB ||
frame->format() == media::PIXEL_FORMAT_XRGB ||
@@ -65,22 +63,23 @@ scoped_refptr<media::VideoFrame> CopyFrame(
}
libyuv::ARGBToI420(reinterpret_cast<uint8_t*>(bitmap.getPixels()),
bitmap.rowBytes(),
- new_frame->data(media::VideoFrame::kYPlane),
+ new_frame->visible_data(media::VideoFrame::kYPlane),
new_frame->stride(media::VideoFrame::kYPlane),
- new_frame->data(media::VideoFrame::kUPlane),
+ new_frame->visible_data(media::VideoFrame::kUPlane),
new_frame->stride(media::VideoFrame::kUPlane),
- new_frame->data(media::VideoFrame::kVPlane),
+ new_frame->visible_data(media::VideoFrame::kVPlane),
new_frame->stride(media::VideoFrame::kVPlane),
- size.width(), size.height());
+ bitmap.width(), bitmap.height());
} else {
DCHECK(frame->IsMappable());
DCHECK(frame->format() == media::PIXEL_FORMAT_YV12 ||
frame->format() == media::PIXEL_FORMAT_YV12A ||
frame->format() == media::PIXEL_FORMAT_I420);
+ const gfx::Size& coded_size = frame->coded_size();
new_frame = media::VideoFrame::CreateFrame(
media::IsOpaque(frame->format()) ? media::PIXEL_FORMAT_I420
: media::PIXEL_FORMAT_YV12A,
- frame->coded_size(), frame->visible_rect(), frame->natural_size(),
+ coded_size, frame->visible_rect(), frame->natural_size(),
frame->timestamp());
libyuv::I420Copy(frame->data(media::VideoFrame::kYPlane),
frame->stride(media::VideoFrame::kYPlane),
@@ -94,14 +93,13 @@ scoped_refptr<media::VideoFrame> CopyFrame(
new_frame->stride(media::VideoFrame::kUPlane),
new_frame->data(media::VideoFrame::kVPlane),
new_frame->stride(media::VideoFrame::kVPlane),
- size.width(), size.height());
+ coded_size.width(), coded_size.height());
if (frame->format() == media::PIXEL_FORMAT_YV12A) {
libyuv::CopyPlane(frame->data(media::VideoFrame::kAPlane),
frame->stride(media::VideoFrame::kAPlane),
new_frame->data(media::VideoFrame::kAPlane),
new_frame->stride(media::VideoFrame::kAPlane),
- size.width(),
- size.height());
+ coded_size.width(), coded_size.height());
}
}
diff --git a/chromium/content/renderer/media/webmediaplayer_ms_unittest.cc b/chromium/content/renderer/media/webmediaplayer_ms_unittest.cc
index 403389cdda8..85afce929e0 100644
--- a/chromium/content/renderer/media/webmediaplayer_ms_unittest.cc
+++ b/chromium/content/renderer/media/webmediaplayer_ms_unittest.cc
@@ -24,6 +24,8 @@ enum class FrameType {
using TestFrame = std::pair<FrameType, scoped_refptr<media::VideoFrame>>;
+static const int kOddSizeOffset = 3;
+
class FakeWebMediaPlayerDelegate
: public media::WebMediaPlayerDelegate,
public base::SupportsWeakPtr<FakeWebMediaPlayerDelegate> {
@@ -129,7 +131,8 @@ class MockVideoFrameProvider : public VideoFrameProvider {
// Methods for test use
void QueueFrames(const std::vector<int>& timestamps_or_frame_type,
- bool opaque_frame = true);
+ bool opaque_frame = true,
+ bool odd_size_frame = false);
bool Started() { return started_; }
bool Paused() { return paused_; }
@@ -186,7 +189,8 @@ void MockVideoFrameProvider::AddFrame(
void MockVideoFrameProvider::QueueFrames(
const std::vector<int>& timestamp_or_frame_type,
- bool opaque_frame) {
+ bool opaque_frame,
+ bool odd_size_frame) {
for (const int token : timestamp_or_frame_type) {
if (token < static_cast<int>(FrameType::MIN_TYPE)) {
CHECK(false) << "Unrecognized frame type: " << token;
@@ -200,6 +204,10 @@ void MockVideoFrameProvider::QueueFrames(
if (token >= 0) {
gfx::Size natural_size = media::TestVideoConfig::NormalCodedSize();
+ if (odd_size_frame) {
+ natural_size.SetSize(natural_size.width() - kOddSizeOffset,
+ natural_size.height() - kOddSizeOffset);
+ }
auto frame = media::VideoFrame::CreateZeroInitializedFrame(
opaque_frame ? media::PIXEL_FORMAT_YV12 : media::PIXEL_FORMAT_YV12A,
natural_size, gfx::Rect(natural_size), natural_size,
@@ -332,9 +340,10 @@ scoped_refptr<VideoFrameProvider> MockRenderFactory::GetVideoFrameProvider(
// WebMediaPlayerMSCompositor.
// 7. When WebMediaPlayerMS::play gets called, evething paused in step 6 should
// be resumed.
-class WebMediaPlayerMSTest : public testing::TestWithParam<bool>,
- public blink::WebMediaPlayerClient,
- public cc::VideoFrameProvider::Client {
+class WebMediaPlayerMSTest
+ : public testing::TestWithParam<testing::tuple<bool, bool>> ,
+ public blink::WebMediaPlayerClient,
+ public cc::VideoFrameProvider::Client {
public:
WebMediaPlayerMSTest()
: render_factory_(new MockRenderFactory(message_loop_.task_runner(),
@@ -572,7 +581,8 @@ TEST_F(WebMediaPlayerMSTest, Playing_ErrorFrame) {
}
TEST_P(WebMediaPlayerMSTest, PlayThenPause) {
- const bool opaque_frame = GetParam();
+ const bool opaque_frame = testing::get<0>(GetParam());
+ const bool odd_size_frame = testing::get<1>(GetParam());
// In the middle of this test, WebMediaPlayerMS::pause will be called, and we
// are going to verify that during the pause stage, a frame gets freezed, and
// cc::VideoFrameProviderClient should also be paused.
@@ -582,7 +592,7 @@ TEST_P(WebMediaPlayerMSTest, PlayThenPause) {
int tokens[] = {0, 33, 66, 100, 133, kTestBrake, 166, 200, 233, 266,
300, 333, 366, 400, 433, 466, 500, 533, 566, 600};
std::vector<int> timestamps(tokens, tokens + sizeof(tokens) / sizeof(int));
- provider->QueueFrames(timestamps, opaque_frame);
+ provider->QueueFrames(timestamps, opaque_frame, odd_size_frame);
EXPECT_CALL(*this, DoSetWebLayer(true));
EXPECT_CALL(*this, DoStartRendering());
@@ -608,7 +618,8 @@ TEST_P(WebMediaPlayerMSTest, PlayThenPause) {
}
TEST_P(WebMediaPlayerMSTest, PlayThenPauseThenPlay) {
- const bool opaque_frame = GetParam();
+ const bool opaque_frame = testing::get<0>(GetParam());
+ const bool odd_size_frame = testing::get<1>(GetParam());
// Similary to PlayAndPause test above, this one focuses on testing that
// WebMediaPlayerMS can be resumed after a period of paused status.
MockVideoFrameProvider* provider = LoadAndGetFrameProvider(false);
@@ -618,7 +629,7 @@ TEST_P(WebMediaPlayerMSTest, PlayThenPauseThenPlay) {
200, 233, 266, 300, 333, 366, 400,
433, kTestBrake, 466, 500, 533, 566, 600};
std::vector<int> timestamps(tokens, tokens + sizeof(tokens) / sizeof(int));
- provider->QueueFrames(timestamps, opaque_frame);
+ provider->QueueFrames(timestamps, opaque_frame, odd_size_frame);
EXPECT_CALL(*this, DoSetWebLayer(true));
EXPECT_CALL(*this, DoStartRendering());
@@ -654,9 +665,12 @@ TEST_P(WebMediaPlayerMSTest, PlayThenPauseThenPlay) {
EXPECT_CALL(*this, DoStopRendering());
}
-INSTANTIATE_TEST_CASE_P(, WebMediaPlayerMSTest, ::testing::Bool());
+INSTANTIATE_TEST_CASE_P(,
+ WebMediaPlayerMSTest,
+ ::testing::Combine(::testing::Bool(),
+ ::testing::Bool()));
-TEST_F(WebMediaPlayerMSTest, BackgroudRendering) {
+TEST_F(WebMediaPlayerMSTest, BackgroundRendering) {
// During this test, we will switch to background rendering mode, in which
// WebMediaPlayerMS::pause does not get called, but
// cc::VideoFrameProviderClient simply stops asking frames from
diff --git a/chromium/extensions/BUILD.gn b/chromium/extensions/BUILD.gn
new file mode 100644
index 00000000000..9e7e2fe397a
--- /dev/null
+++ b/chromium/extensions/BUILD.gn
@@ -0,0 +1,379 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# TODO(rockot) bug 505926: Don't include chrome files from here.
+# See chrome_browser_tests_extensions_sources below
+import("//chrome/chrome_tests.gni")
+import("//extensions/extensions.gni")
+import("//testing/test.gni")
+import("//tools/grit/grit_rule.gni")
+import("//tools/grit/repack.gni")
+
+assert(enable_extensions)
+
+# GYP version: extensions/extensions_resources.gyp:extensions_resources
+group("extensions_resources") {
+ public_deps = [
+ ":extensions_browser_resources",
+ ":extensions_renderer_resources",
+ ":extensions_resources_grd",
+ ]
+}
+
+# GYP version: extensions/extensions_resources.gyp:extensions_resources
+# (extensions_resources action)
+grit("extensions_resources_grd") {
+ source = "extensions_resources.grd"
+ outputs = [
+ "grit/extensions_resources.h",
+ "extensions_resources.pak",
+ ]
+}
+
+# GYP version: extensions/extensions_resources.gyp:extensions_resources
+# (extensions_browser_resources action)
+grit("extensions_browser_resources") {
+ source = "browser/resources/extensions_browser_resources.grd"
+ outputs = [
+ "grit/extensions_browser_resources.h",
+ "grit/extensions_browser_resources_map.cc",
+ "grit/extensions_browser_resources_map.h",
+ "extensions_browser_resources_100_percent.pak",
+ "extensions_browser_resources_200_percent.pak",
+ ]
+ grit_flags = [
+ "-E",
+ "mojom_root=" + rebase_path(root_gen_dir),
+ ]
+}
+
+# GYP version: extensions/extensions_resources.gyp:extensions_resources
+# (extensions_renderer_resources action)
+grit("extensions_renderer_resources") {
+ source = "renderer/resources/extensions_renderer_resources.grd"
+ outputs = [
+ "grit/extensions_renderer_resources.h",
+ "extensions_renderer_resources.pak",
+ ]
+ grit_flags = [
+ "-E",
+ "mojom_root=" + rebase_path(root_gen_dir),
+ ]
+
+ deps = [
+ "//chrome/browser/media/router:mojo_bindings__generator",
+ "//device/serial:serial_mojo__generator",
+ "//extensions/common:mojo__generator",
+ "//extensions/common/api:mojom__generator",
+ ]
+}
+
+source_set("test_support") {
+ testonly = true
+ sources = rebase_path(extensions_gypi_values.extensions_test_support_sources,
+ ".",
+ "//extensions")
+
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ configs += [ "//build/config/compiler:no_size_t_to_int_warning" ]
+
+ deps = [
+ ":extensions_resources",
+ "//base",
+ "//components/guest_view/browser:test_support",
+ "//components/pref_registry:test_support",
+ "//components/prefs:test_support",
+ "//content/public/common",
+ "//content/test:test_support",
+ "//extensions/browser",
+ "//extensions/common",
+ "//extensions/common/api",
+ "//extensions/common/api:api_registration",
+ "//net:test_support",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+
+ public_deps = [
+ "//content/public/browser",
+ "//extensions/common/api/cast_channel:cast_channel_proto",
+ ]
+
+ if (cld_version == 2) {
+ deps += [ "//third_party/cld_2:cld2_static" ]
+ }
+}
+
+# GYP version: //extensions/extensions.gyp:extensions_shell_and_test_pak
+repack("shell_and_test_pak") {
+ sources = [
+ "$root_gen_dir/blink/devtools_resources.pak",
+ "$root_gen_dir/blink/public/resources/blink_image_resources_100_percent.pak",
+ "$root_gen_dir/blink/public/resources/blink_resources.pak",
+ "$root_gen_dir/content/app/strings/content_strings_en-US.pak",
+ "$root_gen_dir/content/content_resources.pak",
+ "$root_gen_dir/content/shell/shell_resources.pak",
+ "$root_gen_dir/extensions/extensions_browser_resources_100_percent.pak",
+ "$root_gen_dir/extensions/extensions_renderer_resources.pak",
+ "$root_gen_dir/extensions/extensions_resources.pak",
+ "$root_gen_dir/extensions/shell/app_shell_resources.pak",
+ "$root_gen_dir/extensions/strings/extensions_strings_en-US.pak",
+ "$root_gen_dir/ui/resources/ui_resources_100_percent.pak",
+ "$root_gen_dir/ui/strings/app_locale_settings_en-US.pak",
+ "$root_gen_dir/ui/strings/ui_strings_en-US.pak",
+ ]
+
+ output = "$root_out_dir/extensions_shell_and_test.pak"
+
+ deps = [
+ ":extensions_resources",
+ "//content:resources",
+ "//content/app/strings",
+ "//content/browser/devtools:devtools_resources",
+ "//content/shell:resources",
+ "//extensions/shell:resources",
+ "//extensions/strings",
+ "//third_party/WebKit/public:image_resources",
+ "//third_party/WebKit/public:resources",
+ "//ui/resources",
+ "//ui/strings",
+ ]
+}
+
+test("extensions_unittests") {
+ sources =
+ rebase_path(extensions_tests_gypi_values.extensions_unittests_sources,
+ ".",
+ "//extensions")
+
+ sources += [
+ # TODO(rockot): DisplayInfoProvider::Create() is only implemented in Chrome
+ # and app_shell. This is wrong.
+ "shell/browser/shell_display_info_provider.cc",
+ ]
+
+ configs += [ "//build/config:precompiled_headers" ]
+
+ data = [
+ "test/data/",
+ "//chrome/test/data/extensions/",
+ "$root_out_dir/extensions_shell_and_test.pak",
+ ]
+
+ deps = [
+ ":extensions_resources",
+ ":shell_and_test_pak",
+ ":test_support",
+ "//base",
+ "//base/test:test_support",
+ "//components/keyed_service/content",
+ "//components/pref_registry:test_support",
+ "//components/prefs:test_support",
+ "//components/user_prefs",
+ "//content/test:test_support",
+ "//device/bluetooth:mocks",
+ "//device/core",
+ "//device/hid",
+ "//device/serial",
+ "//device/serial:test_support",
+ "//extensions/common",
+ "//extensions/common/api/cast_channel:cast_channel_proto",
+ "//extensions/renderer",
+ "//extensions/strings",
+ "//extensions/utility",
+ "//mojo/edk/js",
+ "//mojo/edk/system",
+ "//mojo/public/cpp/bindings",
+ "//mojo/shell/public/interfaces",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//third_party/leveldatabase",
+ ]
+
+ data_deps = [
+ "//third_party/mesa:osmesa",
+ ]
+
+ if (is_chromeos) {
+ sources += [
+ "browser/api/webcam_private/visca_webcam_unittest.cc",
+
+ # TODO(rockot): There are two implementations of VpnServiceFactory, a
+ # stub in app_shell and a real one in Chrome. This is wrong.
+ "shell/browser/api/vpn_provider/vpn_service_factory.cc",
+ ]
+
+ deps += [ "//chromeos:test_support" ]
+ }
+}
+
+test("extensions_browsertests") {
+ sources =
+ rebase_path(extensions_tests_gypi_values.extensions_browsertests_sources,
+ ".",
+ "//extensions")
+
+ data = [
+ "test/data/",
+ "//net/data/",
+ "//net/tools/testserver/",
+ "//third_party/pyftpdlib/",
+ "//third_party/pywebsocket/",
+ "//third_party/tlslite/",
+ "$root_out_dir/extensions_shell_and_test.pak",
+ ]
+
+ defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+
+ deps = [
+ ":test_support",
+ "shell:app_shell_lib",
+
+ # TODO(yoz): find the right deps
+ "//base",
+ "//base/test:test_support",
+ "//components/prefs:test_support",
+ "//components/storage_monitor:test_support",
+ "//content/test:browsertest_base",
+ "//content/test:test_support",
+ "//device/bluetooth:mocks",
+ "//device/core:mocks",
+ "//device/hid:mocks",
+ "//device/usb:mocks",
+ "//mojo/edk/js",
+ "//mojo/edk/system",
+ "//mojo/public/cpp/bindings",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+
+ data_deps = [
+ "//third_party/mesa:osmesa",
+ ]
+
+ if (is_win) {
+ if (target_cpu == "x86") {
+ data_deps += [
+ # "$root_out_dir/wow_helper.exe" # TODO(GYP)
+ ]
+ }
+ }
+
+ if (is_mac) {
+ deps += [ "shell:app_shell" ] # Needed for App Shell.app's Helper.
+ }
+}
+
+# TODO(rockot) bug 505926: These should be moved to extensions_browsertests but have
+# old dependencies on chrome files. The chrome dependencies should be removed
+# and these moved to the extensions_browsertests target. Currently, we solve
+# the problem by making this a source set and linking it into
+# //chrome/test:browser_tests.
+source_set("chrome_extensions_browsertests") {
+ testonly = true
+ sources = rebase_path(
+ chrome_tests_gypi_values.chrome_browser_tests_extensions_sources,
+ ".",
+ "//chrome")
+
+ defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+
+ # These are the deps from browser_tests minus some internal Chrome ones that
+ # aren't allowed to be included here and that aren't needed.
+ deps = [
+ "//base",
+ "//base:i18n",
+ "//base/test:test_support",
+ "//chrome/browser",
+ "//chrome/browser/resources:extension_resource_demo",
+ "//chrome/common/extensions/api",
+ "//chrome/renderer",
+ "//components/autofill/content/browser:risk_proto",
+ "//components/autofill/content/browser/wallet:test_support",
+ "//components/autofill/content/renderer:test_support",
+ "//components/captive_portal:test_support",
+ "//components/dom_distiller/content/browser",
+ "//components/dom_distiller/core:test_support",
+ "//components/guest_view/browser:test_support",
+ "//components/resources",
+ "//components/strings",
+ "//components/translate/core/common",
+ "//crypto:platform",
+ "//crypto:test_support",
+ "//device/bluetooth:mocks",
+ "//device/serial:test_support",
+ "//extensions/common/api",
+ "//google_apis:test_support",
+ "//media",
+ "//media/base:test_support",
+ "//media/cast:test_support",
+ "//net",
+ "//net:test_support",
+ "//sdch",
+ "//skia",
+ "//sync",
+ "//sync:test_support_sync_api",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//testing/perf",
+ "//third_party/WebKit/public:blink",
+ "//third_party/cacheinvalidation",
+ "//third_party/icu",
+ "//third_party/leveldatabase",
+ "//third_party/libaddressinput",
+ "//third_party/libjingle",
+ "//third_party/safe_browsing:test_support",
+ "//third_party/webrtc/modules/desktop_capture",
+ "//third_party/widevine/cdm:version_h",
+ "//ui/accessibility:test_support",
+ "//ui/base:test_support",
+ "//ui/compositor:test_support",
+ "//ui/resources",
+ "//ui/web_dialogs:test_support",
+ "//v8",
+ ]
+
+ if (is_chromeos) {
+ deps += [ "//components/user_manager:test_support" ]
+ }
+}
+
+# TODO(rockot) bug 505926: This should be deleted for the same reason as
+# chrome_extensions_browsertests.
+source_set("chrome_extensions_interactive_uitests") {
+ testonly = true
+ sources = rebase_path(
+ chrome_tests_gypi_values.chrome_interactive_ui_test_extensions_sources,
+ ".",
+ "//chrome")
+
+ defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+
+ # These are the deps from interactive_uitests minus some internal Chrome
+ # ones that aren't allowed to be included here and that aren't needed.
+ deps = [
+ "//chrome/browser",
+ "//chrome/browser/devtools",
+ "//chrome/renderer",
+ "//chrome/test:test_support",
+ "//content/app/resources",
+ "//crypto:platform",
+ "//crypto:test_support",
+ "//google_apis:test_support",
+ "//net",
+ "//net:net_resources",
+ "//net:test_support",
+ "//skia",
+ "//sync",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//third_party/hunspell",
+ "//third_party/icu",
+ "//third_party/libpng",
+ "//third_party/zlib",
+ "//ui/base:test_support",
+ "//ui/resources:ui_test_pak",
+ "//ui/web_dialogs:test_support",
+ ]
+}
diff --git a/chromium/extensions/DEPS b/chromium/extensions/DEPS
new file mode 100644
index 00000000000..52dbb3f76db
--- /dev/null
+++ b/chromium/extensions/DEPS
@@ -0,0 +1,59 @@
+include_rules = [
+ # Do not add Chrome dependencies. Much work went into removing them.
+ "+components/browsing_data",
+ "+components/crx_file",
+ "+components/guest_view",
+ "+components/prefs",
+ "+components/url_matcher",
+ "-content",
+ "+content/grit/content_resources.h",
+ "+content/public/common",
+ "+content/public/test",
+ "+crypto",
+ "-extensions/components",
+ "+extensions/test",
+ "+grit/extensions_renderer_resources.h",
+ "+grit/extensions_resources.h",
+ "+mojo/public",
+ "+testing",
+ "+third_party/skia/include",
+
+ # Minimal UI dependencies. There are two good rules for UI dependencies here:
+ #
+ # 1) UI components should only be added as they are needed, and
+ # 2) if //content doesn't allow it, //extensions probably won't allow it.
+ # (see for example ui/views)
+ "-ui",
+ "+ui/base",
+ "+ui/gfx",
+ "+ui/events",
+
+ # NOTE: Please do not add includes without talking to the app shell team;
+ # see OWNERS for this directory.
+]
+
+specific_include_rules = {
+ ".*(test|test_util)\.(cc|h)$": [
+ "+content/public/test",
+
+ # Temporarily allowed testing includes. See above.
+ # TODO(jamescook): Remove these. http://crbug.com/162530
+ "+chrome/browser/apps/app_browsertest_util.h",
+ "+chrome/browser/extensions/api/management/management_api.h",
+ "+chrome/browser/extensions/api/permissions/permissions_api.h",
+ "+chrome/browser/extensions/extension_apitest.h",
+ "+chrome/browser/extensions/extension_function_test_utils.h",
+ "+chrome/browser/extensions/extension_service.h",
+ "+chrome/browser/extensions/extension_service_test_base.h",
+ "+chrome/browser/extensions/extension_test_message_listener.h",
+ "+chrome/browser/extensions/test_extension_dir.h",
+ "+chrome/browser/extensions/test_extension_prefs.h",
+ "+chrome/browser/extensions/test_extension_system.h",
+ "+chrome/browser/ui/browser.h",
+ "+chrome/common/chrome_switches.h",
+ "+chrome/common/extensions/features/feature_channel.h",
+ "+chrome/test/base/chrome_render_view_test.h",
+ "+chrome/test/base/testing_profile.h",
+ "+chrome/test/base/ui_test_utils.h",
+ ],
+}
diff --git a/chromium/extensions/OWNERS b/chromium/extensions/OWNERS
new file mode 100644
index 00000000000..56b6fb20312
--- /dev/null
+++ b/chromium/extensions/OWNERS
@@ -0,0 +1,9 @@
+# Extension and apps team owners. Use file history to guide reviewer selection
+# as this list is randomized. If editing, see chrome/browser/extensions/OWNERS
+reillyg@chromium.org
+rockot@chromium.org
+asargent@chromium.org
+benwells@chromium.org
+finnur@chromium.org
+rdevlin.cronin@chromium.org
+mek@chromium.org
diff --git a/chromium/extensions/README b/chromium/extensions/README
new file mode 100644
index 00000000000..c994549997e
--- /dev/null
+++ b/chromium/extensions/README
@@ -0,0 +1,3 @@
+This will become a reusable extensions module. It implements the core parts of
+Chrome's extension system, and can be used with any host of the 'content'
+module.
diff --git a/chromium/extensions/browser/BUILD.gn b/chromium/extensions/browser/BUILD.gn
new file mode 100644
index 00000000000..e044a7a8912
--- /dev/null
+++ b/chromium/extensions/browser/BUILD.gn
@@ -0,0 +1,113 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/features.gni")
+import("//extensions/extensions.gni")
+
+# GYP version: extensions/extensions.gyp:extensions_browser
+source_set("browser") {
+ sources = []
+
+ deps = [
+ "//base:i18n",
+ "//components/guest_view/browser",
+ "//components/keyed_service/content",
+ "//components/keyed_service/core",
+ "//components/pref_registry",
+ "//components/sessions",
+ "//components/ui/zoom",
+ "//components/update_client",
+ "//components/version_info",
+ "//components/web_cache/browser",
+ "//components/web_modal",
+ "//content/public/browser",
+ "//crypto:platform",
+ "//extensions/common",
+ "//extensions/common/api",
+ "//extensions/common/api:api_registration",
+ "//extensions/strings",
+ "//google_apis",
+ "//skia",
+ "//third_party/leveldatabase",
+ "//third_party/re2",
+ ]
+
+ configs += [
+ "//build/config:precompiled_headers",
+
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ "//build/config/compiler:no_size_t_to_int_warning",
+ ]
+
+ if (enable_extensions) {
+ # Includes all API implementations and the ExtensionsApiClient
+ # interface. Moving an API from src/chrome to src/extensions implies
+ # it can be cleanly disabled with enable_extensions=false.
+ # TODO: Eventually the entire extensions module should not be built
+ # when enable_extensions=false.
+ sources = rebase_path(extensions_gypi_values.extensions_browser_sources,
+ ".",
+ "//extensions")
+
+ deps += [
+ "//components/browsing_data",
+ "//components/onc",
+ "//components/storage_monitor",
+ "//components/update_client",
+ "//components/variations",
+ "//crypto:platform",
+ "//device/bluetooth",
+ "//device/core",
+ "//device/hid",
+ "//device/serial",
+ "//device/usb",
+ "//extensions/common/api/cast_channel:cast_channel_proto",
+ ]
+
+ if (is_chromeos) {
+ deps += [ "//chromeos" ]
+ }
+
+ if (is_chromeos) {
+ chromeos_sources = rebase_path(
+ extensions_gypi_values.extensions_browser_sources_chromeos,
+ ".",
+ "//extensions")
+ sources += chromeos_sources
+ } else {
+ nonchromeos_sources = rebase_path(
+ extensions_gypi_values.extensions_browser_sources_nonchromeos,
+ ".",
+ "//extensions")
+ sources += nonchromeos_sources
+
+ if (is_linux) {
+ configs += [ "//build/config/linux:dbus" ]
+ deps += [ "//dbus" ]
+ linux_sources = rebase_path(
+ extensions_gypi_values.extensions_browser_sources_linux_nonchromeos,
+ ".",
+ "//extensions")
+ sources += linux_sources
+ } else {
+ if (is_win || is_mac) {
+ deps += [ "//components/wifi" ]
+
+ win_or_mac_sources = rebase_path(
+ extensions_gypi_values.extensions_browser_sources_win_or_mac,
+ ".",
+ "//extensions")
+ sources += win_or_mac_sources
+ }
+ }
+ if (enable_wifi_display) {
+ wifi_display_sources = rebase_path(
+ extensions_gypi_values.extensions_browser_sources_wifi_display,
+ ".",
+ "//extensions")
+ sources += wifi_display_sources
+ }
+ }
+ }
+}
diff --git a/chromium/extensions/browser/DEPS b/chromium/extensions/browser/DEPS
new file mode 100644
index 00000000000..abf85d9d797
--- /dev/null
+++ b/chromium/extensions/browser/DEPS
@@ -0,0 +1,34 @@
+include_rules = [
+ "+chromeos",
+ "+components/guest_view",
+ "+components/keyed_service",
+ "+components/pref_registry",
+ "+components/sessions",
+ "+components/storage_monitor",
+ "+components/ui/zoom",
+ "+components/update_client",
+ "+components/variations",
+ "+components/version_info",
+ "+components/web_cache",
+ "+components/web_modal",
+ "+content/public/browser",
+ "+device/bluetooth",
+ "+device/serial",
+ "+device/usb",
+ "+google_apis/gaia",
+ "+grit/extensions_strings.h",
+ "+net",
+ "+skia/ext/image_operations.h",
+ "+storage/browser/fileapi",
+ "+sync",
+ "+third_party/leveldatabase",
+ "+third_party/re2",
+ "+third_party/WebKit/public/web",
+ "+third_party/zlib/google",
+]
+
+specific_include_rules = {
+ ".*test\.cc$": [
+ "+components/user_prefs",
+ ]
+}
diff --git a/chromium/extensions/browser/OWNERS b/chromium/extensions/browser/OWNERS
new file mode 100644
index 00000000000..818eb497342
--- /dev/null
+++ b/chromium/extensions/browser/OWNERS
@@ -0,0 +1,16 @@
+# Please talk to the apps shell team before adding DEPS.
+per-file DEPS=set noparent
+per-file DEPS=benwells@chromium.org
+per-file DEPS=derat@chromium.org
+per-file DEPS=rockot@chromium.org
+
+per-file extension_event_histogram_value.h=set noparent
+per-file extension_event_histogram_value.h=asvitkine@chromium.org
+per-file extension_event_histogram_value.h=isherman@chromium.org
+per-file extension_event_histogram_value.h=jar@chromium.org
+per-file extension_event_histogram_value.h=mpearson@chromium.org
+per-file extension_function_histogram_value.h=set noparent
+per-file extension_function_histogram_value.h=asvitkine@chromium.org
+per-file extension_function_histogram_value.h=isherman@chromium.org
+per-file extension_function_histogram_value.h=jar@chromium.org
+per-file extension_function_histogram_value.h=mpearson@chromium.org
diff --git a/chromium/extensions/browser/PRESUBMIT.py b/chromium/extensions/browser/PRESUBMIT.py
new file mode 100644
index 00000000000..15f8d932fc5
--- /dev/null
+++ b/chromium/extensions/browser/PRESUBMIT.py
@@ -0,0 +1,43 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Chromium presubmit script for src/extensions/browser.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details on the presubmit API built into depot_tools.
+"""
+
+import sys
+
+def _CreateHistogramValueChecker(input_api, output_api, path):
+ original_sys_path = sys.path
+
+ try:
+ sys.path.append(input_api.os_path.join(
+ input_api.PresubmitLocalPath(), '..', '..', 'tools',
+ 'strict_enum_value_checker'))
+ from strict_enum_value_checker import StrictEnumValueChecker
+ finally:
+ sys.path = original_sys_path
+
+ return StrictEnumValueChecker(input_api, output_api,
+ start_marker='enum HistogramValue {', end_marker=' // Last entry:',
+ path=path)
+
+
+def _RunHistogramValueCheckers(input_api, output_api):
+ results = []
+ histogram_paths = ('extensions/browser/extension_event_histogram_value.h',
+ 'extensions/browser/extension_function_histogram_value.h')
+ for path in histogram_paths:
+ results += _CreateHistogramValueChecker(input_api, output_api, path).Run()
+ return results
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ results = []
+ results += _RunHistogramValueCheckers(input_api, output_api)
+ results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api)
+ return results
+
diff --git a/chromium/extensions/browser/api/DEPS b/chromium/extensions/browser/api/DEPS
new file mode 100644
index 00000000000..7dc85439079
--- /dev/null
+++ b/chromium/extensions/browser/api/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+device/core",
+ "+device/hid",
+]
diff --git a/chromium/extensions/browser/api/activity_log/web_request_constants.cc b/chromium/extensions/browser/api/activity_log/web_request_constants.cc
new file mode 100644
index 00000000000..b7f991dc1fb
--- /dev/null
+++ b/chromium/extensions/browser/api/activity_log/web_request_constants.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Constants used when describing request modifications via the WebRequest API
+// in the activity log.
+
+#include "extensions/browser/api/activity_log/web_request_constants.h"
+
+namespace activity_log_web_request_constants {
+
+// Keys used in the dictionary summarizing an EventResponseDelta for the
+// extension activity log.
+const char kCancelKey[] = "cancel";
+const char kNewUrlKey[] = "new_url";
+const char kModifiedRequestHeadersKey[] = "modified_request_headers";
+const char kDeletedRequestHeadersKey[] = "deleted_request_headers";
+const char kAddedRequestHeadersKey[] = "added_request_headers";
+const char kDeletedResponseHeadersKey[] = "deleted_response_headers";
+const char kAuthCredentialsKey[] = "auth_credentials";
+const char kResponseCookieModificationsKey[] = "response_cookie_modifications";
+
+// Keys and values used for describing cookie modifications.
+const char kCookieModificationTypeKey[] = "type";
+const char kCookieModificationAdd[] = "ADD";
+const char kCookieModificationEdit[] = "EDIT";
+const char kCookieModificationRemove[] = "REMOVE";
+const char kCookieFilterNameKey[] = "filter_name";
+const char kCookieFilterDomainKey[] = "filter_domain";
+const char kCookieModNameKey[] = "mod_name";
+const char kCookieModDomainKey[] = "mod_domain";
+
+} // namespace activity_log_web_request_constants
diff --git a/chromium/extensions/browser/api/activity_log/web_request_constants.h b/chromium/extensions/browser/api/activity_log/web_request_constants.h
new file mode 100644
index 00000000000..da16d57982e
--- /dev/null
+++ b/chromium/extensions/browser/api/activity_log/web_request_constants.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Constants used when describing request modifications via the WebRequest API
+// in the activity log.
+
+#ifndef EXTENSIONS_BROWSER_API_ACTIVITY_LOG_WEB_REQUEST_CONSTANTS_H_
+#define EXTENSIONS_BROWSER_API_ACTIVITY_LOG_WEB_REQUEST_CONSTANTS_H_
+
+namespace activity_log_web_request_constants {
+
+// Keys used in the dictionary summarizing an EventResponseDelta for the
+// extension activity log.
+extern const char kCancelKey[];
+extern const char kNewUrlKey[];
+extern const char kModifiedRequestHeadersKey[];
+extern const char kDeletedRequestHeadersKey[];
+extern const char kAddedRequestHeadersKey[];
+extern const char kDeletedResponseHeadersKey[];
+extern const char kAuthCredentialsKey[];
+extern const char kResponseCookieModificationsKey[];
+
+// Keys and values used for describing cookie modifications.
+extern const char kCookieModificationTypeKey[];
+extern const char kCookieModificationAdd[];
+extern const char kCookieModificationEdit[];
+extern const char kCookieModificationRemove[];
+extern const char kCookieFilterNameKey[];
+extern const char kCookieFilterDomainKey[];
+extern const char kCookieModNameKey[];
+extern const char kCookieModDomainKey[];
+
+} // namespace activity_log_web_request_constants
+
+#endif // EXTENSIONS_BROWSER_API_ACTIVITY_LOG_WEB_REQUEST_CONSTANTS_H_
diff --git a/chromium/extensions/browser/api/alarms/OWNERS b/chromium/extensions/browser/api/alarms/OWNERS
new file mode 100644
index 00000000000..39cb68cb187
--- /dev/null
+++ b/chromium/extensions/browser/api/alarms/OWNERS
@@ -0,0 +1 @@
+mpcomplete@chromium.org
diff --git a/chromium/extensions/browser/api/alarms/alarm_manager.cc b/chromium/extensions/browser/api/alarms/alarm_manager.cc
new file mode 100644
index 00000000000..0616481fc75
--- /dev/null
+++ b/chromium/extensions/browser/api/alarms/alarm_manager.cc
@@ -0,0 +1,473 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/alarms/alarm_manager.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/json/json_writer.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop/message_loop.h"
+#include "base/time/clock.h"
+#include "base/time/default_clock.h"
+#include "base/time/time.h"
+#include "base/value_conversions.h"
+#include "base/values.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/state_store.h"
+#include "extensions/common/api/alarms.h"
+
+namespace extensions {
+
+namespace alarms = api::alarms;
+
+namespace {
+
+// A list of alarms that this extension has set.
+const char kRegisteredAlarms[] = "alarms";
+const char kAlarmGranularity[] = "granularity";
+
+// The minimum period between polling for alarms to run.
+const base::TimeDelta kDefaultMinPollPeriod() {
+ return base::TimeDelta::FromDays(1);
+}
+
+class DefaultAlarmDelegate : public AlarmManager::Delegate {
+ public:
+ explicit DefaultAlarmDelegate(content::BrowserContext* context)
+ : browser_context_(context) {}
+ ~DefaultAlarmDelegate() override {}
+
+ void OnAlarm(const std::string& extension_id, const Alarm& alarm) override {
+ scoped_ptr<base::ListValue> args(new base::ListValue());
+ args->Append(alarm.js_alarm->ToValue().release());
+ scoped_ptr<Event> event(new Event(
+ events::ALARMS_ON_ALARM, alarms::OnAlarm::kEventName, std::move(args)));
+ EventRouter::Get(browser_context_)
+ ->DispatchEventToExtension(extension_id, std::move(event));
+ }
+
+ private:
+ content::BrowserContext* browser_context_;
+};
+
+// Creates a TimeDelta from a delay as specified in the API.
+base::TimeDelta TimeDeltaFromDelay(double delay_in_minutes) {
+ return base::TimeDelta::FromMicroseconds(delay_in_minutes *
+ base::Time::kMicrosecondsPerMinute);
+}
+
+std::vector<Alarm> AlarmsFromValue(const base::ListValue* list) {
+ std::vector<Alarm> alarms;
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ const base::DictionaryValue* alarm_dict = NULL;
+ Alarm alarm;
+ if (list->GetDictionary(i, &alarm_dict) &&
+ alarms::Alarm::Populate(*alarm_dict, alarm.js_alarm.get())) {
+ const base::Value* time_value = NULL;
+ if (alarm_dict->Get(kAlarmGranularity, &time_value))
+ base::GetValueAsTimeDelta(*time_value, &alarm.granularity);
+ alarms.push_back(alarm);
+ }
+ }
+ return alarms;
+}
+
+scoped_ptr<base::ListValue> AlarmsToValue(const std::vector<Alarm>& alarms) {
+ scoped_ptr<base::ListValue> list(new base::ListValue());
+ for (size_t i = 0; i < alarms.size(); ++i) {
+ scoped_ptr<base::DictionaryValue> alarm = alarms[i].js_alarm->ToValue();
+ alarm->Set(kAlarmGranularity,
+ base::CreateTimeDeltaValue(alarms[i].granularity));
+ list->Append(alarm.release());
+ }
+ return list;
+}
+
+} // namespace
+
+// AlarmManager
+
+AlarmManager::AlarmManager(content::BrowserContext* context)
+ : browser_context_(context),
+ clock_(new base::DefaultClock()),
+ delegate_(new DefaultAlarmDelegate(context)),
+ extension_registry_observer_(this) {
+ extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
+
+ StateStore* storage = ExtensionSystem::Get(browser_context_)->state_store();
+ if (storage)
+ storage->RegisterKey(kRegisteredAlarms);
+}
+
+AlarmManager::~AlarmManager() {
+}
+
+void AlarmManager::AddAlarm(const std::string& extension_id,
+ const Alarm& alarm,
+ const AddAlarmCallback& callback) {
+ RunWhenReady(extension_id, base::Bind(&AlarmManager::AddAlarmWhenReady,
+ AsWeakPtr(), alarm, callback));
+}
+
+void AlarmManager::GetAlarm(const std::string& extension_id,
+ const std::string& name,
+ const GetAlarmCallback& callback) {
+ RunWhenReady(extension_id, base::Bind(&AlarmManager::GetAlarmWhenReady,
+ AsWeakPtr(), name, callback));
+}
+
+void AlarmManager::GetAllAlarms(const std::string& extension_id,
+ const GetAllAlarmsCallback& callback) {
+ RunWhenReady(extension_id, base::Bind(&AlarmManager::GetAllAlarmsWhenReady,
+ AsWeakPtr(), callback));
+}
+
+void AlarmManager::RemoveAlarm(const std::string& extension_id,
+ const std::string& name,
+ const RemoveAlarmCallback& callback) {
+ RunWhenReady(extension_id, base::Bind(&AlarmManager::RemoveAlarmWhenReady,
+ AsWeakPtr(), name, callback));
+}
+
+void AlarmManager::RemoveAllAlarms(const std::string& extension_id,
+ const RemoveAllAlarmsCallback& callback) {
+ RunWhenReady(extension_id, base::Bind(&AlarmManager::RemoveAllAlarmsWhenReady,
+ AsWeakPtr(), callback));
+}
+
+void AlarmManager::AddAlarmWhenReady(const Alarm& alarm,
+ const AddAlarmCallback& callback,
+ const std::string& extension_id) {
+ AddAlarmImpl(extension_id, alarm);
+ WriteToStorage(extension_id);
+ callback.Run();
+}
+
+void AlarmManager::GetAlarmWhenReady(const std::string& name,
+ const GetAlarmCallback& callback,
+ const std::string& extension_id) {
+ AlarmIterator it = GetAlarmIterator(extension_id, name);
+ callback.Run(it.first != alarms_.end() ? &*it.second : NULL);
+}
+
+void AlarmManager::GetAllAlarmsWhenReady(const GetAllAlarmsCallback& callback,
+ const std::string& extension_id) {
+ AlarmMap::iterator list = alarms_.find(extension_id);
+ callback.Run(list != alarms_.end() ? &list->second : NULL);
+}
+
+void AlarmManager::RemoveAlarmWhenReady(const std::string& name,
+ const RemoveAlarmCallback& callback,
+ const std::string& extension_id) {
+ AlarmIterator it = GetAlarmIterator(extension_id, name);
+ if (it.first == alarms_.end()) {
+ callback.Run(false);
+ return;
+ }
+
+ RemoveAlarmIterator(it);
+ WriteToStorage(extension_id);
+ callback.Run(true);
+}
+
+void AlarmManager::RemoveAllAlarmsWhenReady(
+ const RemoveAllAlarmsCallback& callback,
+ const std::string& extension_id) {
+ AlarmMap::iterator list = alarms_.find(extension_id);
+ if (list != alarms_.end()) {
+ // Note: I'm using indices rather than iterators here because
+ // RemoveAlarmIterator will delete the list when it becomes empty.
+ for (size_t i = 0, size = list->second.size(); i < size; ++i)
+ RemoveAlarmIterator(AlarmIterator(list, list->second.begin()));
+
+ CHECK(alarms_.find(extension_id) == alarms_.end());
+ WriteToStorage(extension_id);
+ }
+ callback.Run();
+}
+
+AlarmManager::AlarmIterator AlarmManager::GetAlarmIterator(
+ const std::string& extension_id,
+ const std::string& name) {
+ AlarmMap::iterator list = alarms_.find(extension_id);
+ if (list == alarms_.end())
+ return make_pair(alarms_.end(), AlarmList::iterator());
+
+ for (AlarmList::iterator it = list->second.begin(); it != list->second.end();
+ ++it) {
+ if (it->js_alarm->name == name)
+ return make_pair(list, it);
+ }
+
+ return make_pair(alarms_.end(), AlarmList::iterator());
+}
+
+void AlarmManager::SetClockForTesting(base::Clock* clock) {
+ clock_.reset(clock);
+}
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<AlarmManager>>
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<AlarmManager>*
+AlarmManager::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+// static
+AlarmManager* AlarmManager::Get(content::BrowserContext* browser_context) {
+ return BrowserContextKeyedAPIFactory<AlarmManager>::Get(browser_context);
+}
+
+void AlarmManager::RemoveAlarmIterator(const AlarmIterator& iter) {
+ AlarmList& list = iter.first->second;
+ list.erase(iter.second);
+ if (list.empty())
+ alarms_.erase(iter.first);
+
+ // Cancel the timer if there are no more alarms.
+ // We don't need to reschedule the poll otherwise, because in
+ // the worst case we would just poll one extra time.
+ if (alarms_.empty()) {
+ timer_.Stop();
+ next_poll_time_ = base::Time();
+ }
+}
+
+void AlarmManager::OnAlarm(AlarmIterator it) {
+ CHECK(it.first != alarms_.end());
+ Alarm& alarm = *it.second;
+ std::string extension_id_copy(it.first->first);
+ delegate_->OnAlarm(extension_id_copy, alarm);
+
+ // Update our scheduled time for the next alarm.
+ if (double* period_in_minutes = alarm.js_alarm->period_in_minutes.get()) {
+ // Get the timer's delay in JS time (i.e., convert it from minutes to
+ // milliseconds).
+ double period_in_js_time = *period_in_minutes *
+ base::Time::kMicrosecondsPerMinute /
+ base::Time::kMicrosecondsPerMillisecond;
+ // Find out how many periods have transpired since the alarm last went off
+ // (it's possible that we missed some).
+ int transpired_periods =
+ (last_poll_time_.ToJsTime() - alarm.js_alarm->scheduled_time) /
+ period_in_js_time;
+ // Schedule the alarm for the next period that is in-line with the original
+ // scheduling.
+ alarm.js_alarm->scheduled_time +=
+ period_in_js_time * (transpired_periods + 1);
+ } else {
+ RemoveAlarmIterator(it);
+ }
+ WriteToStorage(extension_id_copy);
+}
+
+void AlarmManager::AddAlarmImpl(const std::string& extension_id,
+ const Alarm& alarm) {
+ // Override any old alarm with the same name.
+ AlarmIterator old_alarm =
+ GetAlarmIterator(extension_id, alarm.js_alarm->name);
+ if (old_alarm.first != alarms_.end())
+ RemoveAlarmIterator(old_alarm);
+
+ alarms_[extension_id].push_back(alarm);
+ base::Time alarm_time =
+ base::Time::FromJsTime(alarm.js_alarm->scheduled_time);
+ if (next_poll_time_.is_null() || alarm_time < next_poll_time_)
+ SetNextPollTime(alarm_time);
+}
+
+void AlarmManager::WriteToStorage(const std::string& extension_id) {
+ StateStore* storage = ExtensionSystem::Get(browser_context_)->state_store();
+ if (!storage)
+ return;
+
+ scoped_ptr<base::Value> alarms;
+ AlarmMap::iterator list = alarms_.find(extension_id);
+ if (list != alarms_.end())
+ alarms.reset(AlarmsToValue(list->second).release());
+ else
+ alarms.reset(AlarmsToValue(std::vector<Alarm>()).release());
+ storage->SetExtensionValue(extension_id, kRegisteredAlarms,
+ std::move(alarms));
+}
+
+void AlarmManager::ReadFromStorage(const std::string& extension_id,
+ scoped_ptr<base::Value> value) {
+ base::ListValue* list = NULL;
+ if (value.get() && value->GetAsList(&list)) {
+ std::vector<Alarm> alarm_states = AlarmsFromValue(list);
+ for (size_t i = 0; i < alarm_states.size(); ++i)
+ AddAlarmImpl(extension_id, alarm_states[i]);
+ }
+
+ ReadyQueue& extension_ready_queue = ready_actions_[extension_id];
+ while (!extension_ready_queue.empty()) {
+ extension_ready_queue.front().Run(extension_id);
+ extension_ready_queue.pop();
+ }
+ ready_actions_.erase(extension_id);
+}
+
+void AlarmManager::SetNextPollTime(const base::Time& time) {
+ next_poll_time_ = time;
+ timer_.Start(FROM_HERE,
+ std::max(base::TimeDelta::FromSeconds(0), time - clock_->Now()),
+ this, &AlarmManager::PollAlarms);
+}
+
+void AlarmManager::ScheduleNextPoll() {
+ // If there are no alarms, stop the timer.
+ if (alarms_.empty()) {
+ timer_.Stop();
+ next_poll_time_ = base::Time();
+ return;
+ }
+
+ // Find the soonest alarm that is scheduled to run and the smallest
+ // granularity of any alarm.
+ // alarms_ guarantees that none of its contained lists are empty.
+ base::Time soonest_alarm_time = base::Time::FromJsTime(
+ alarms_.begin()->second.begin()->js_alarm->scheduled_time);
+ base::TimeDelta min_granularity = kDefaultMinPollPeriod();
+ for (AlarmMap::const_iterator m_it = alarms_.begin(), m_end = alarms_.end();
+ m_it != m_end; ++m_it) {
+ for (AlarmList::const_iterator l_it = m_it->second.begin();
+ l_it != m_it->second.end(); ++l_it) {
+ base::Time cur_alarm_time =
+ base::Time::FromJsTime(l_it->js_alarm->scheduled_time);
+ if (cur_alarm_time < soonest_alarm_time)
+ soonest_alarm_time = cur_alarm_time;
+ if (l_it->granularity < min_granularity)
+ min_granularity = l_it->granularity;
+ base::TimeDelta cur_alarm_delta = cur_alarm_time - last_poll_time_;
+ if (cur_alarm_delta < l_it->minimum_granularity)
+ cur_alarm_delta = l_it->minimum_granularity;
+ if (cur_alarm_delta < min_granularity)
+ min_granularity = cur_alarm_delta;
+ }
+ }
+
+ base::Time next_poll(last_poll_time_ + min_granularity);
+ // If the next alarm is more than min_granularity in the future, wait for it.
+ // Otherwise, only poll as often as min_granularity.
+ // As a special case, if we've never checked for an alarm before
+ // (e.g. during startup), let alarms fire asap.
+ if (last_poll_time_.is_null() || next_poll < soonest_alarm_time)
+ next_poll = soonest_alarm_time;
+
+ // Schedule the poll.
+ SetNextPollTime(next_poll);
+}
+
+void AlarmManager::PollAlarms() {
+ last_poll_time_ = clock_->Now();
+
+ // Run any alarms scheduled in the past. OnAlarm uses vector::erase to remove
+ // elements from the AlarmList, and map::erase to remove AlarmLists from the
+ // AlarmMap.
+ for (AlarmMap::iterator m_it = alarms_.begin(), m_end = alarms_.end();
+ m_it != m_end;) {
+ AlarmMap::iterator cur_extension = m_it++;
+
+ // Iterate (a) backwards so that removing elements doesn't affect
+ // upcoming iterations, and (b) with indices so that if the last
+ // iteration destroys the AlarmList, I'm not about to use the end
+ // iterator that the destruction invalidates.
+ for (size_t i = cur_extension->second.size(); i > 0; --i) {
+ AlarmList::iterator cur_alarm = cur_extension->second.begin() + i - 1;
+ if (base::Time::FromJsTime(cur_alarm->js_alarm->scheduled_time) <=
+ last_poll_time_) {
+ OnAlarm(make_pair(cur_extension, cur_alarm));
+ }
+ }
+ }
+
+ ScheduleNextPoll();
+}
+
+static void RemoveAllOnUninstallCallback() {
+}
+
+void AlarmManager::RunWhenReady(const std::string& extension_id,
+ const ReadyAction& action) {
+ ReadyMap::iterator it = ready_actions_.find(extension_id);
+
+ if (it == ready_actions_.end())
+ action.Run(extension_id);
+ else
+ it->second.push(action);
+}
+
+void AlarmManager::OnExtensionLoaded(content::BrowserContext* browser_context,
+ const Extension* extension) {
+ StateStore* storage = ExtensionSystem::Get(browser_context_)->state_store();
+ if (storage) {
+ ready_actions_.insert(ReadyMap::value_type(extension->id(), ReadyQueue()));
+ storage->GetExtensionValue(extension->id(), kRegisteredAlarms,
+ base::Bind(&AlarmManager::ReadFromStorage,
+ AsWeakPtr(), extension->id()));
+ }
+}
+
+void AlarmManager::OnExtensionUninstalled(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ extensions::UninstallReason reason) {
+ RemoveAllAlarms(extension->id(), base::Bind(RemoveAllOnUninstallCallback));
+}
+
+// AlarmManager::Alarm
+
+Alarm::Alarm() : js_alarm(new alarms::Alarm()) {
+}
+
+Alarm::Alarm(const std::string& name,
+ const alarms::AlarmCreateInfo& create_info,
+ base::TimeDelta min_granularity,
+ base::Time now)
+ : js_alarm(new alarms::Alarm()) {
+ js_alarm->name = name;
+ minimum_granularity = min_granularity;
+
+ if (create_info.when.get()) {
+ // Absolute scheduling.
+ js_alarm->scheduled_time = *create_info.when;
+ granularity = base::Time::FromJsTime(js_alarm->scheduled_time) - now;
+ } else {
+ // Relative scheduling.
+ double* delay_in_minutes = create_info.delay_in_minutes.get();
+ if (delay_in_minutes == NULL)
+ delay_in_minutes = create_info.period_in_minutes.get();
+ CHECK(delay_in_minutes != NULL)
+ << "ValidateAlarmCreateInfo in alarms_api.cc should have "
+ << "prevented this call.";
+ base::TimeDelta delay = TimeDeltaFromDelay(*delay_in_minutes);
+ js_alarm->scheduled_time = (now + delay).ToJsTime();
+ granularity = delay;
+ }
+
+ if (granularity < min_granularity)
+ granularity = min_granularity;
+
+ // Check for repetition.
+ if (create_info.period_in_minutes.get()) {
+ js_alarm->period_in_minutes.reset(
+ new double(*create_info.period_in_minutes));
+ }
+}
+
+Alarm::Alarm(const Alarm& other) = default;
+
+Alarm::~Alarm() {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/alarms/alarm_manager.h b/chromium/extensions/browser/api/alarms/alarm_manager.h
new file mode 100644
index 00000000000..66e5fb105d5
--- /dev/null
+++ b/chromium/extensions/browser/api/alarms/alarm_manager.h
@@ -0,0 +1,246 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_ALARMS_ALARM_MANAGER_H_
+#define EXTENSIONS_BROWSER_API_ALARMS_ALARM_MANAGER_H_
+
+#include <map>
+#include <queue>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/scoped_observer.h"
+#include "base/timer/timer.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/common/api/alarms.h"
+
+namespace base {
+class Clock;
+} // namespace base
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+namespace extensions {
+class ExtensionAlarmsSchedulingTest;
+class ExtensionRegistry;
+
+struct Alarm {
+ Alarm();
+ Alarm(const std::string& name,
+ const api::alarms::AlarmCreateInfo& create_info,
+ base::TimeDelta min_granularity,
+ base::Time now);
+ Alarm(const Alarm& other);
+ ~Alarm();
+
+ linked_ptr<api::alarms::Alarm> js_alarm;
+ // The granularity isn't exposed to the extension's javascript, but we poll at
+ // least as often as the shortest alarm's granularity. It's initialized as
+ // the relative delay requested in creation, even if creation uses an absolute
+ // time. This will always be at least as large as the min_granularity
+ // constructor argument.
+ base::TimeDelta granularity;
+ // The minimum granularity is the minimum allowed polling rate. This stops
+ // alarms from polling too often.
+ base::TimeDelta minimum_granularity;
+};
+
+// Manages the currently pending alarms for every extension in a profile.
+// There is one manager per virtual Profile.
+class AlarmManager : public BrowserContextKeyedAPI,
+ public ExtensionRegistryObserver,
+ public base::SupportsWeakPtr<AlarmManager> {
+ public:
+ typedef std::vector<Alarm> AlarmList;
+
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+ // Called when an alarm fires.
+ virtual void OnAlarm(const std::string& extension_id,
+ const Alarm& alarm) = 0;
+ };
+
+ explicit AlarmManager(content::BrowserContext* context);
+ ~AlarmManager() override;
+
+ // Override the default delegate. Callee assumes onwership. Used for testing.
+ void set_delegate(Delegate* delegate) { delegate_.reset(delegate); }
+
+ typedef base::Callback<void()> AddAlarmCallback;
+ // Adds |alarm| for the given extension, and starts the timer. Invokes
+ // |callback| when done.
+ void AddAlarm(const std::string& extension_id,
+ const Alarm& alarm,
+ const AddAlarmCallback& callback);
+
+ typedef base::Callback<void(Alarm*)> GetAlarmCallback;
+ // Passes the alarm with the given name, or NULL if none exists, to
+ // |callback|.
+ void GetAlarm(const std::string& extension_id,
+ const std::string& name,
+ const GetAlarmCallback& callback);
+
+ typedef base::Callback<void(const AlarmList*)> GetAllAlarmsCallback;
+ // Passes the list of pending alarms for the given extension, or
+ // NULL if none exist, to |callback|.
+ void GetAllAlarms(const std::string& extension_id,
+ const GetAllAlarmsCallback& callback);
+
+ typedef base::Callback<void(bool)> RemoveAlarmCallback;
+ // Cancels and removes the alarm with the given name. Invokes |callback| when
+ // done.
+ void RemoveAlarm(const std::string& extension_id,
+ const std::string& name,
+ const RemoveAlarmCallback& callback);
+
+ typedef base::Callback<void()> RemoveAllAlarmsCallback;
+ // Cancels and removes all alarms for the given extension. Invokes |callback|
+ // when done.
+ void RemoveAllAlarms(const std::string& extension_id,
+ const RemoveAllAlarmsCallback& callback);
+
+ // Replaces AlarmManager's owned clock with |clock| and takes ownership of it.
+ void SetClockForTesting(base::Clock* clock);
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<AlarmManager>* GetFactoryInstance();
+
+ // Convenience method to get the AlarmManager for a content::BrowserContext.
+ static AlarmManager* Get(content::BrowserContext* browser_context);
+
+ private:
+ friend void RunScheduleNextPoll(AlarmManager*);
+ friend class ExtensionAlarmsSchedulingTest;
+ FRIEND_TEST_ALL_PREFIXES(ExtensionAlarmsSchedulingTest, PollScheduling);
+ FRIEND_TEST_ALL_PREFIXES(ExtensionAlarmsSchedulingTest,
+ ReleasedExtensionPollsInfrequently);
+ FRIEND_TEST_ALL_PREFIXES(ExtensionAlarmsSchedulingTest, TimerRunning);
+ FRIEND_TEST_ALL_PREFIXES(ExtensionAlarmsSchedulingTest, MinimumGranularity);
+ FRIEND_TEST_ALL_PREFIXES(ExtensionAlarmsSchedulingTest,
+ DifferentMinimumGranularities);
+ FRIEND_TEST_ALL_PREFIXES(ExtensionAlarmsSchedulingTest,
+ RepeatingAlarmsScheduledPredictably);
+ friend class BrowserContextKeyedAPIFactory<AlarmManager>;
+
+ typedef std::string ExtensionId;
+ typedef std::map<ExtensionId, AlarmList> AlarmMap;
+
+ typedef base::Callback<void(const std::string&)> ReadyAction;
+ typedef std::queue<ReadyAction> ReadyQueue;
+ typedef std::map<ExtensionId, ReadyQueue> ReadyMap;
+
+ // Iterator used to identify a particular alarm within the Map/List pair.
+ // "Not found" is represented by <alarms_.end(), invalid_iterator>.
+ typedef std::pair<AlarmMap::iterator, AlarmList::iterator> AlarmIterator;
+
+ // Part of AddAlarm that is executed after alarms are loaded.
+ void AddAlarmWhenReady(const Alarm& alarm,
+ const AddAlarmCallback& callback,
+ const std::string& extension_id);
+
+ // Part of GetAlarm that is executed after alarms are loaded.
+ void GetAlarmWhenReady(const std::string& name,
+ const GetAlarmCallback& callback,
+ const std::string& extension_id);
+
+ // Part of GetAllAlarms that is executed after alarms are loaded.
+ void GetAllAlarmsWhenReady(const GetAllAlarmsCallback& callback,
+ const std::string& extension_id);
+
+ // Part of RemoveAlarm that is executed after alarms are loaded.
+ void RemoveAlarmWhenReady(const std::string& name,
+ const RemoveAlarmCallback& callback,
+ const std::string& extension_id);
+
+ // Part of RemoveAllAlarms that is executed after alarms are loaded.
+ void RemoveAllAlarmsWhenReady(const RemoveAllAlarmsCallback& callback,
+ const std::string& extension_id);
+
+ // Helper to return the iterators within the AlarmMap and AlarmList for the
+ // matching alarm, or an iterator to the end of the AlarmMap if none were
+ // found.
+ AlarmIterator GetAlarmIterator(const std::string& extension_id,
+ const std::string& name);
+
+ // Helper to cancel and remove the alarm at the given iterator. The iterator
+ // must be valid.
+ void RemoveAlarmIterator(const AlarmIterator& iter);
+
+ // Callback for when an alarm fires.
+ void OnAlarm(AlarmIterator iter);
+
+ // Internal helper to add an alarm and start the timer with the given delay.
+ void AddAlarmImpl(const std::string& extension_id, const Alarm& alarm);
+
+ // Syncs our alarm data for the given extension to/from the state storage.
+ void WriteToStorage(const std::string& extension_id);
+ void ReadFromStorage(const std::string& extension_id,
+ scoped_ptr<base::Value> value);
+
+ // Set the timer to go off at the specified |time|, and set |next_poll_time|
+ // appropriately.
+ void SetNextPollTime(const base::Time& time);
+
+ // Schedules the next poll of alarms for when the next soonest alarm runs,
+ // but not more often than the minimum granularity of all alarms.
+ void ScheduleNextPoll();
+
+ // Polls the alarms, running any that have elapsed. After running them and
+ // rescheduling repeating alarms, schedule the next poll.
+ void PollAlarms();
+
+ // Executes |action| for given extension, making sure that the extension's
+ // alarm data has been synced from the storage.
+ void RunWhenReady(const std::string& extension_id, const ReadyAction& action);
+
+ // ExtensionRegistryObserver implementation.
+ void OnExtensionLoaded(content::BrowserContext* browser_context,
+ const Extension* extension) override;
+ void OnExtensionUninstalled(content::BrowserContext* browser_context,
+ const Extension* extension,
+ extensions::UninstallReason reason) override;
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "AlarmManager"; }
+ static const bool kServiceHasOwnInstanceInIncognito = true;
+
+ content::BrowserContext* const browser_context_;
+ scoped_ptr<base::Clock> clock_;
+ scoped_ptr<Delegate> delegate_;
+
+ // Listen to extension load notifications.
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+
+ // The timer for this alarm manager.
+ base::OneShotTimer timer_;
+
+ // A map of our pending alarms, per extension.
+ // Invariant: None of the AlarmLists are empty.
+ AlarmMap alarms_;
+
+ // A map of actions waiting for alarm data to be synced from storage, per
+ // extension.
+ ReadyMap ready_actions_;
+
+ // The previous time that alarms were run.
+ base::Time last_poll_time_;
+
+ // Next poll's time.
+ base::Time next_poll_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(AlarmManager);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_ALARMS_ALARM_MANAGER_H_
diff --git a/chromium/extensions/browser/api/alarms/alarms_api.cc b/chromium/extensions/browser/api/alarms/alarms_api.cc
new file mode 100644
index 00000000000..102bce4f863
--- /dev/null
+++ b/chromium/extensions/browser/api/alarms/alarms_api.cc
@@ -0,0 +1,204 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/alarms/alarms_api.h"
+
+#include <stddef.h>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/time/clock.h"
+#include "base/time/default_clock.h"
+#include "base/values.h"
+#include "extensions/browser/api/alarms/alarm_manager.h"
+#include "extensions/common/api/alarms.h"
+#include "extensions/common/error_utils.h"
+
+namespace extensions {
+
+namespace alarms = api::alarms;
+
+namespace {
+
+const char kDefaultAlarmName[] = "";
+const char kBothRelativeAndAbsoluteTime[] =
+ "Cannot set both when and delayInMinutes.";
+const char kNoScheduledTime[] =
+ "Must set at least one of when, delayInMinutes, or periodInMinutes.";
+const int kReleaseDelayMinimum = 1;
+const int kDevDelayMinimum = 0;
+
+bool ValidateAlarmCreateInfo(const std::string& alarm_name,
+ const alarms::AlarmCreateInfo& create_info,
+ const Extension* extension,
+ std::string* error,
+ std::vector<std::string>* warnings) {
+ if (create_info.delay_in_minutes.get() && create_info.when.get()) {
+ *error = kBothRelativeAndAbsoluteTime;
+ return false;
+ }
+ if (create_info.delay_in_minutes == NULL && create_info.when == NULL &&
+ create_info.period_in_minutes == NULL) {
+ *error = kNoScheduledTime;
+ return false;
+ }
+
+ // Users can always use an absolute timeout to request an arbitrarily-short or
+ // negative delay. We won't honor the short timeout, but we can't check it
+ // and warn the user because it would introduce race conditions (say they
+ // compute a long-enough timeout, but then the call into the alarms interface
+ // gets delayed past the boundary). However, it's still worth warning about
+ // relative delays that are shorter than we'll honor.
+ if (create_info.delay_in_minutes.get()) {
+ if (*create_info.delay_in_minutes < kReleaseDelayMinimum) {
+ static_assert(kReleaseDelayMinimum == 1,
+ "warning message must be updated");
+ if (Manifest::IsUnpackedLocation(extension->location()))
+ warnings->push_back(ErrorUtils::FormatErrorMessage(
+ "Alarm delay is less than minimum of 1 minutes."
+ " In released .crx, alarm \"*\" will fire in approximately"
+ " 1 minutes.",
+ alarm_name));
+ else
+ warnings->push_back(ErrorUtils::FormatErrorMessage(
+ "Alarm delay is less than minimum of 1 minutes."
+ " Alarm \"*\" will fire in approximately 1 minutes.",
+ alarm_name));
+ }
+ }
+ if (create_info.period_in_minutes.get()) {
+ if (*create_info.period_in_minutes < kReleaseDelayMinimum) {
+ static_assert(kReleaseDelayMinimum == 1,
+ "warning message must be updated");
+ if (Manifest::IsUnpackedLocation(extension->location()))
+ warnings->push_back(ErrorUtils::FormatErrorMessage(
+ "Alarm period is less than minimum of 1 minutes."
+ " In released .crx, alarm \"*\" will fire approximately"
+ " every 1 minutes.",
+ alarm_name));
+ else
+ warnings->push_back(ErrorUtils::FormatErrorMessage(
+ "Alarm period is less than minimum of 1 minutes."
+ " Alarm \"*\" will fire approximately every 1 minutes.",
+ alarm_name));
+ }
+ }
+
+ return true;
+}
+
+} // namespace
+
+AlarmsCreateFunction::AlarmsCreateFunction()
+ : clock_(new base::DefaultClock()), owns_clock_(true) {
+}
+
+AlarmsCreateFunction::AlarmsCreateFunction(base::Clock* clock)
+ : clock_(clock), owns_clock_(false) {
+}
+
+AlarmsCreateFunction::~AlarmsCreateFunction() {
+ if (owns_clock_)
+ delete clock_;
+}
+
+bool AlarmsCreateFunction::RunAsync() {
+ scoped_ptr<alarms::Create::Params> params(
+ alarms::Create::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ const std::string& alarm_name =
+ params->name.get() ? *params->name : kDefaultAlarmName;
+ std::vector<std::string> warnings;
+ if (!ValidateAlarmCreateInfo(alarm_name, params->alarm_info, extension(),
+ &error_, &warnings)) {
+ return false;
+ }
+ for (std::vector<std::string>::const_iterator it = warnings.begin();
+ it != warnings.end(); ++it)
+ WriteToConsole(content::CONSOLE_MESSAGE_LEVEL_WARNING, *it);
+
+ Alarm alarm(alarm_name, params->alarm_info,
+ base::TimeDelta::FromMinutes(
+ Manifest::IsUnpackedLocation(extension()->location())
+ ? kDevDelayMinimum
+ : kReleaseDelayMinimum),
+ clock_->Now());
+ AlarmManager::Get(browser_context())
+ ->AddAlarm(extension_id(), alarm,
+ base::Bind(&AlarmsCreateFunction::Callback, this));
+
+ return true;
+}
+
+void AlarmsCreateFunction::Callback() {
+ SendResponse(true);
+}
+
+bool AlarmsGetFunction::RunAsync() {
+ scoped_ptr<alarms::Get::Params> params(alarms::Get::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ std::string name = params->name.get() ? *params->name : kDefaultAlarmName;
+ AlarmManager::Get(browser_context())
+ ->GetAlarm(extension_id(), name,
+ base::Bind(&AlarmsGetFunction::Callback, this, name));
+
+ return true;
+}
+
+void AlarmsGetFunction::Callback(const std::string& name,
+ extensions::Alarm* alarm) {
+ if (alarm) {
+ results_ = alarms::Get::Results::Create(*alarm->js_alarm);
+ }
+ SendResponse(true);
+}
+
+bool AlarmsGetAllFunction::RunAsync() {
+ AlarmManager::Get(browser_context())
+ ->GetAllAlarms(extension_id(),
+ base::Bind(&AlarmsGetAllFunction::Callback, this));
+ return true;
+}
+
+void AlarmsGetAllFunction::Callback(const AlarmList* alarms) {
+ scoped_ptr<base::ListValue> alarms_value(new base::ListValue());
+ if (alarms) {
+ for (const Alarm& alarm : *alarms)
+ alarms_value->Append(alarm.js_alarm->ToValue());
+ }
+ SetResult(std::move(alarms_value));
+ SendResponse(true);
+}
+
+bool AlarmsClearFunction::RunAsync() {
+ scoped_ptr<alarms::Clear::Params> params(
+ alarms::Clear::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ std::string name = params->name.get() ? *params->name : kDefaultAlarmName;
+ AlarmManager::Get(browser_context())
+ ->RemoveAlarm(extension_id(), name,
+ base::Bind(&AlarmsClearFunction::Callback, this, name));
+
+ return true;
+}
+
+void AlarmsClearFunction::Callback(const std::string& name, bool success) {
+ SetResult(new base::FundamentalValue(success));
+ SendResponse(true);
+}
+
+bool AlarmsClearAllFunction::RunAsync() {
+ AlarmManager::Get(browser_context())
+ ->RemoveAllAlarms(extension_id(),
+ base::Bind(&AlarmsClearAllFunction::Callback, this));
+ return true;
+}
+
+void AlarmsClearAllFunction::Callback() {
+ SetResult(new base::FundamentalValue(true));
+ SendResponse(true);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/alarms/alarms_api.h b/chromium/extensions/browser/api/alarms/alarms_api.h
new file mode 100644
index 00000000000..837da909620
--- /dev/null
+++ b/chromium/extensions/browser/api/alarms/alarms_api.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_ALARMS_ALARMS_API_H_
+#define EXTENSIONS_BROWSER_API_ALARMS_ALARMS_API_H_
+
+#include <vector>
+
+#include "extensions/browser/extension_function.h"
+
+namespace base {
+class Clock;
+} // namespace base
+
+namespace extensions {
+struct Alarm;
+typedef std::vector<Alarm> AlarmList;
+
+class AlarmsCreateFunction : public AsyncExtensionFunction {
+ public:
+ AlarmsCreateFunction();
+ // Use |clock| instead of the default clock. Does not take ownership
+ // of |clock|. Used for testing.
+ explicit AlarmsCreateFunction(base::Clock* clock);
+
+ protected:
+ ~AlarmsCreateFunction() override;
+
+ // ExtensionFunction:
+ bool RunAsync() override;
+ DECLARE_EXTENSION_FUNCTION("alarms.create", ALARMS_CREATE)
+ private:
+ void Callback();
+
+ base::Clock* const clock_;
+ // Whether or not we own |clock_|. This is needed because we own it
+ // when we create it ourselves, but not when it's passed in for
+ // testing.
+ bool owns_clock_;
+};
+
+class AlarmsGetFunction : public AsyncExtensionFunction {
+ protected:
+ ~AlarmsGetFunction() override {}
+
+ // ExtensionFunction:
+ bool RunAsync() override;
+
+ private:
+ void Callback(const std::string& name, Alarm* alarm);
+ DECLARE_EXTENSION_FUNCTION("alarms.get", ALARMS_GET)
+};
+
+class AlarmsGetAllFunction : public AsyncExtensionFunction {
+ protected:
+ ~AlarmsGetAllFunction() override {}
+
+ // ExtensionFunction:
+ bool RunAsync() override;
+
+ private:
+ void Callback(const AlarmList* alarms);
+ DECLARE_EXTENSION_FUNCTION("alarms.getAll", ALARMS_GETALL)
+};
+
+class AlarmsClearFunction : public AsyncExtensionFunction {
+ protected:
+ ~AlarmsClearFunction() override {}
+
+ // ExtensionFunction:
+ bool RunAsync() override;
+
+ private:
+ void Callback(const std::string& name, bool success);
+ DECLARE_EXTENSION_FUNCTION("alarms.clear", ALARMS_CLEAR)
+};
+
+class AlarmsClearAllFunction : public AsyncExtensionFunction {
+ protected:
+ ~AlarmsClearAllFunction() override {}
+
+ // ExtensionFunction:
+ bool RunAsync() override;
+
+ private:
+ void Callback();
+ DECLARE_EXTENSION_FUNCTION("alarms.clearAll", ALARMS_CLEARALL)
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_ALARMS_ALARMS_API_H_
diff --git a/chromium/extensions/browser/api/alarms/alarms_api_unittest.cc b/chromium/extensions/browser/api/alarms/alarms_api_unittest.cc
new file mode 100644
index 00000000000..b0626f2641c
--- /dev/null
+++ b/chromium/extensions/browser/api/alarms/alarms_api_unittest.cc
@@ -0,0 +1,700 @@
+// Copyright (c) 2012 The Chromium 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 tests the chrome.alarms extension API.
+
+#include <stddef.h>
+
+#include "base/test/simple_test_clock.h"
+#include "base/values.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "extensions/browser/api/alarms/alarm_manager.h"
+#include "extensions/browser/api/alarms/alarms_api.h"
+#include "extensions/browser/api_test_utils.h"
+#include "extensions/browser/api_unittest.h"
+#include "extensions/common/extension_messages.h"
+#include "ipc/ipc_test_sink.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+typedef extensions::api::alarms::Alarm JsAlarm;
+
+namespace extensions {
+
+namespace utils = api_test_utils;
+
+namespace {
+
+// Test delegate which quits the message loop when an alarm fires.
+class AlarmDelegate : public AlarmManager::Delegate {
+ public:
+ ~AlarmDelegate() override {}
+ void OnAlarm(const std::string& extension_id, const Alarm& alarm) override {
+ alarms_seen.push_back(alarm.js_alarm->name);
+ if (base::MessageLoop::current()->is_running())
+ base::MessageLoop::current()->QuitWhenIdle();
+ }
+
+ std::vector<std::string> alarms_seen;
+};
+
+} // namespace
+
+void RunScheduleNextPoll(AlarmManager* alarm_manager) {
+ alarm_manager->ScheduleNextPoll();
+}
+
+class ExtensionAlarmsTest : public ApiUnitTest {
+ public:
+ using ApiUnitTest::RunFunction;
+
+ void SetUp() override {
+ ApiUnitTest::SetUp();
+
+ test_clock_ = new base::SimpleTestClock();
+ alarm_manager_ = AlarmManager::Get(browser_context());
+ alarm_manager_->SetClockForTesting(test_clock_);
+
+ alarm_delegate_ = new AlarmDelegate();
+ alarm_manager_->set_delegate(alarm_delegate_);
+
+ // Make sure there's a RenderViewHost for alarms to warn into.
+ CreateBackgroundPage();
+
+ test_clock_->SetNow(base::Time::FromDoubleT(10));
+ }
+
+ void CreateAlarm(const std::string& args) {
+ RunFunction(new AlarmsCreateFunction(test_clock_), args);
+ }
+
+ // Takes a JSON result from a function and converts it to a vector of
+ // JsAlarms.
+ std::vector<linked_ptr<JsAlarm>> ToAlarmList(base::ListValue* value) {
+ std::vector<linked_ptr<JsAlarm>> list;
+ for (size_t i = 0; i < value->GetSize(); ++i) {
+ linked_ptr<JsAlarm> alarm(new JsAlarm);
+ base::DictionaryValue* alarm_value;
+ if (!value->GetDictionary(i, &alarm_value)) {
+ ADD_FAILURE() << "Expected a list of Alarm objects.";
+ return list;
+ }
+ EXPECT_TRUE(JsAlarm::Populate(*alarm_value, alarm.get()));
+ list.push_back(alarm);
+ }
+ return list;
+ }
+
+ // Creates up to 3 alarms using the extension API.
+ void CreateAlarms(size_t num_alarms) {
+ CHECK_LE(num_alarms, 3U);
+
+ const char* const kCreateArgs[] = {
+ "[null, {\"periodInMinutes\": 0.001}]",
+ "[\"7\", {\"periodInMinutes\": 7}]",
+ "[\"0\", {\"delayInMinutes\": 0}]",
+ };
+ for (size_t i = 0; i < num_alarms; ++i) {
+ scoped_ptr<base::DictionaryValue> result(RunFunctionAndReturnDictionary(
+ new AlarmsCreateFunction(test_clock_), kCreateArgs[i]));
+ EXPECT_FALSE(result.get());
+ }
+ }
+
+ base::SimpleTestClock* test_clock_;
+ AlarmManager* alarm_manager_;
+ AlarmDelegate* alarm_delegate_;
+};
+
+void ExtensionAlarmsTestGetAllAlarmsCallback(
+ const AlarmManager::AlarmList* alarms) {
+ // Ensure the alarm is gone.
+ ASSERT_FALSE(alarms);
+}
+
+void ExtensionAlarmsTestGetAlarmCallback(ExtensionAlarmsTest* test,
+ Alarm* alarm) {
+ ASSERT_TRUE(alarm);
+ EXPECT_EQ("", alarm->js_alarm->name);
+ EXPECT_DOUBLE_EQ(10000, alarm->js_alarm->scheduled_time);
+ EXPECT_FALSE(alarm->js_alarm->period_in_minutes.get());
+
+ // Now wait for the alarm to fire. Our test delegate will quit the
+ // MessageLoop when that happens.
+ base::MessageLoop::current()->Run();
+
+ ASSERT_EQ(1u, test->alarm_delegate_->alarms_seen.size());
+ EXPECT_EQ("", test->alarm_delegate_->alarms_seen[0]);
+
+ // Ensure the alarm is gone.
+ test->alarm_manager_->GetAllAlarms(
+ test->extension()->id(),
+ base::Bind(ExtensionAlarmsTestGetAllAlarmsCallback));
+}
+
+TEST_F(ExtensionAlarmsTest, Create) {
+ test_clock_->SetNow(base::Time::FromDoubleT(10));
+ // Create 1 non-repeating alarm.
+ CreateAlarm("[null, {\"delayInMinutes\": 0}]");
+
+ alarm_manager_->GetAlarm(
+ extension()->id(), std::string(),
+ base::Bind(ExtensionAlarmsTestGetAlarmCallback, this));
+}
+
+void ExtensionAlarmsTestCreateRepeatingGetAlarmCallback(
+ ExtensionAlarmsTest* test,
+ Alarm* alarm) {
+ ASSERT_TRUE(alarm);
+ EXPECT_EQ("", alarm->js_alarm->name);
+ EXPECT_DOUBLE_EQ(10060, alarm->js_alarm->scheduled_time);
+ EXPECT_THAT(alarm->js_alarm->period_in_minutes,
+ testing::Pointee(testing::DoubleEq(0.001)));
+
+ test->test_clock_->Advance(base::TimeDelta::FromSeconds(1));
+ // Now wait for the alarm to fire. Our test delegate will quit the
+ // MessageLoop when that happens.
+ base::MessageLoop::current()->Run();
+
+ test->test_clock_->Advance(base::TimeDelta::FromSeconds(1));
+ // Wait again, and ensure the alarm fires again.
+ RunScheduleNextPoll(test->alarm_manager_);
+ base::MessageLoop::current()->Run();
+
+ ASSERT_EQ(2u, test->alarm_delegate_->alarms_seen.size());
+ EXPECT_EQ("", test->alarm_delegate_->alarms_seen[0]);
+}
+
+TEST_F(ExtensionAlarmsTest, CreateRepeating) {
+ test_clock_->SetNow(base::Time::FromDoubleT(10));
+
+ // Create 1 repeating alarm.
+ CreateAlarm("[null, {\"periodInMinutes\": 0.001}]");
+
+ alarm_manager_->GetAlarm(
+ extension()->id(), std::string(),
+ base::Bind(ExtensionAlarmsTestCreateRepeatingGetAlarmCallback, this));
+}
+
+void ExtensionAlarmsTestCreateAbsoluteGetAlarm2Callback(
+ ExtensionAlarmsTest* test,
+ Alarm* alarm) {
+ ASSERT_FALSE(alarm);
+
+ ASSERT_EQ(1u, test->alarm_delegate_->alarms_seen.size());
+ EXPECT_EQ("", test->alarm_delegate_->alarms_seen[0]);
+}
+
+void ExtensionAlarmsTestCreateAbsoluteGetAlarm1Callback(
+ ExtensionAlarmsTest* test,
+ Alarm* alarm) {
+ ASSERT_TRUE(alarm);
+ EXPECT_EQ("", alarm->js_alarm->name);
+ EXPECT_DOUBLE_EQ(10001, alarm->js_alarm->scheduled_time);
+ EXPECT_THAT(alarm->js_alarm->period_in_minutes, testing::IsNull());
+
+ test->test_clock_->SetNow(base::Time::FromDoubleT(10.1));
+ // Now wait for the alarm to fire. Our test delegate will quit the
+ // MessageLoop when that happens.
+ base::MessageLoop::current()->Run();
+
+ test->alarm_manager_->GetAlarm(
+ test->extension()->id(), std::string(),
+ base::Bind(ExtensionAlarmsTestCreateAbsoluteGetAlarm2Callback, test));
+}
+
+TEST_F(ExtensionAlarmsTest, CreateAbsolute) {
+ test_clock_->SetNow(base::Time::FromDoubleT(9.99));
+ CreateAlarm("[null, {\"when\": 10001}]");
+
+ alarm_manager_->GetAlarm(
+ extension()->id(), std::string(),
+ base::Bind(ExtensionAlarmsTestCreateAbsoluteGetAlarm1Callback, this));
+}
+
+void ExtensionAlarmsTestCreateRepeatingWithQuickFirstCallGetAlarm3Callback(
+ ExtensionAlarmsTest* test,
+ Alarm* alarm) {
+ ASSERT_TRUE(alarm);
+ EXPECT_THAT(test->alarm_delegate_->alarms_seen, testing::ElementsAre("", ""));
+}
+
+void ExtensionAlarmsTestCreateRepeatingWithQuickFirstCallGetAlarm2Callback(
+ ExtensionAlarmsTest* test,
+ Alarm* alarm) {
+ ASSERT_TRUE(alarm);
+ EXPECT_THAT(test->alarm_delegate_->alarms_seen, testing::ElementsAre(""));
+
+ test->test_clock_->SetNow(base::Time::FromDoubleT(10.7));
+ base::MessageLoop::current()->Run();
+
+ test->alarm_manager_->GetAlarm(
+ test->extension()->id(), std::string(),
+ base::Bind(
+ ExtensionAlarmsTestCreateRepeatingWithQuickFirstCallGetAlarm3Callback,
+ test));
+}
+
+void ExtensionAlarmsTestCreateRepeatingWithQuickFirstCallGetAlarm1Callback(
+ ExtensionAlarmsTest* test,
+ Alarm* alarm) {
+ ASSERT_TRUE(alarm);
+ EXPECT_EQ("", alarm->js_alarm->name);
+ EXPECT_DOUBLE_EQ(10001, alarm->js_alarm->scheduled_time);
+ EXPECT_THAT(alarm->js_alarm->period_in_minutes,
+ testing::Pointee(testing::DoubleEq(0.001)));
+
+ test->test_clock_->SetNow(base::Time::FromDoubleT(10.1));
+ // Now wait for the alarm to fire. Our test delegate will quit the
+ // MessageLoop when that happens.
+ base::MessageLoop::current()->Run();
+
+ test->alarm_manager_->GetAlarm(
+ test->extension()->id(), std::string(),
+ base::Bind(
+ ExtensionAlarmsTestCreateRepeatingWithQuickFirstCallGetAlarm2Callback,
+ test));
+}
+
+TEST_F(ExtensionAlarmsTest, CreateRepeatingWithQuickFirstCall) {
+ test_clock_->SetNow(base::Time::FromDoubleT(9.99));
+ CreateAlarm("[null, {\"when\": 10001, \"periodInMinutes\": 0.001}]");
+
+ alarm_manager_->GetAlarm(
+ extension()->id(), std::string(),
+ base::Bind(
+ ExtensionAlarmsTestCreateRepeatingWithQuickFirstCallGetAlarm1Callback,
+ this));
+}
+
+void ExtensionAlarmsTestCreateDupeGetAllAlarmsCallback(
+ const AlarmManager::AlarmList* alarms) {
+ ASSERT_TRUE(alarms);
+ EXPECT_EQ(1u, alarms->size());
+ EXPECT_DOUBLE_EQ(430000, (*alarms)[0].js_alarm->scheduled_time);
+}
+
+TEST_F(ExtensionAlarmsTest, CreateDupe) {
+ test_clock_->SetNow(base::Time::FromDoubleT(10));
+
+ // Create 2 duplicate alarms. The first should be overridden.
+ CreateAlarm("[\"dup\", {\"delayInMinutes\": 1}]");
+ CreateAlarm("[\"dup\", {\"delayInMinutes\": 7}]");
+
+ alarm_manager_->GetAllAlarms(
+ extension()->id(),
+ base::Bind(ExtensionAlarmsTestCreateDupeGetAllAlarmsCallback));
+}
+
+TEST_F(ExtensionAlarmsTest, CreateDelayBelowMinimum) {
+ // Create an alarm with delay below the minimum accepted value.
+ IPC::TestSink& sink =
+ static_cast<content::MockRenderProcessHost*>(
+ contents()->GetRenderViewHost()->GetProcess())->sink();
+ size_t initial_message_count = sink.message_count();
+ CreateAlarm("[\"negative\", {\"delayInMinutes\": -0.2}]");
+ // A new message should have been added.
+ ASSERT_GT(sink.message_count(), initial_message_count);
+
+ // All of this would be cleaner if we could read the message as a
+ // FrameMsg_AddMessageToConsole, but that would be a layering violation.
+ // Better yet would be an observer method for frames adding console messages,
+ // but it's not worth adding just for a test.
+ const IPC::Message* warning =
+ sink.GetMessageAt(initial_message_count /* 0-based */);
+ ASSERT_TRUE(warning);
+
+ int level = 0;
+ base::PickleIterator iter(*warning);
+ ASSERT_TRUE(iter.ReadInt(&level));
+ std::string message;
+ ASSERT_TRUE(iter.ReadString(&message));
+
+ EXPECT_EQ(content::CONSOLE_MESSAGE_LEVEL_WARNING,
+ static_cast<content::ConsoleMessageLevel>(level));
+ EXPECT_THAT(message, testing::HasSubstr("delay is less than minimum of 1"));
+}
+
+TEST_F(ExtensionAlarmsTest, Get) {
+ test_clock_->SetNow(base::Time::FromDoubleT(4));
+
+ // Create 2 alarms, and make sure we can query them.
+ CreateAlarms(2);
+
+ // Get the default one.
+ {
+ JsAlarm alarm;
+ scoped_ptr<base::DictionaryValue> result(
+ RunFunctionAndReturnDictionary(new AlarmsGetFunction(), "[null]"));
+ ASSERT_TRUE(result.get());
+ EXPECT_TRUE(JsAlarm::Populate(*result, &alarm));
+ EXPECT_EQ("", alarm.name);
+ EXPECT_DOUBLE_EQ(4060, alarm.scheduled_time);
+ EXPECT_THAT(alarm.period_in_minutes,
+ testing::Pointee(testing::DoubleEq(0.001)));
+ }
+
+ // Get "7".
+ {
+ JsAlarm alarm;
+ scoped_ptr<base::DictionaryValue> result(
+ RunFunctionAndReturnDictionary(new AlarmsGetFunction(), "[\"7\"]"));
+ ASSERT_TRUE(result.get());
+ EXPECT_TRUE(JsAlarm::Populate(*result, &alarm));
+ EXPECT_EQ("7", alarm.name);
+ EXPECT_EQ(424000, alarm.scheduled_time);
+ EXPECT_THAT(alarm.period_in_minutes, testing::Pointee(7));
+ }
+
+ // Get a non-existent one.
+ {
+ scoped_ptr<base::DictionaryValue> result(RunFunctionAndReturnDictionary(
+ new AlarmsGetFunction(), "[\"nobody\"]"));
+ ASSERT_FALSE(result.get());
+ }
+}
+
+TEST_F(ExtensionAlarmsTest, GetAll) {
+ // Test getAll with 0 alarms.
+ {
+ scoped_ptr<base::ListValue> result(
+ RunFunctionAndReturnList(new AlarmsGetAllFunction(), "[]"));
+ std::vector<linked_ptr<JsAlarm>> alarms = ToAlarmList(result.get());
+ EXPECT_EQ(0u, alarms.size());
+ }
+
+ // Create 2 alarms, and make sure we can query them.
+ CreateAlarms(2);
+
+ {
+ scoped_ptr<base::ListValue> result(
+ RunFunctionAndReturnList(new AlarmsGetAllFunction(), "[null]"));
+ std::vector<linked_ptr<JsAlarm>> alarms = ToAlarmList(result.get());
+ EXPECT_EQ(2u, alarms.size());
+
+ // Test the "7" alarm.
+ JsAlarm* alarm = alarms[0].get();
+ if (alarm->name != "7")
+ alarm = alarms[1].get();
+ EXPECT_EQ("7", alarm->name);
+ EXPECT_THAT(alarm->period_in_minutes, testing::Pointee(7));
+ }
+}
+
+void ExtensionAlarmsTestClearGetAllAlarms2Callback(
+ const AlarmManager::AlarmList* alarms) {
+ // Ensure the 0.001-minute alarm is still there, since it's repeating.
+ ASSERT_TRUE(alarms);
+ EXPECT_EQ(1u, alarms->size());
+ EXPECT_THAT((*alarms)[0].js_alarm->period_in_minutes,
+ testing::Pointee(0.001));
+}
+
+void ExtensionAlarmsTestClearGetAllAlarms1Callback(
+ ExtensionAlarmsTest* test,
+ const AlarmManager::AlarmList* alarms) {
+ ASSERT_TRUE(alarms);
+ EXPECT_EQ(1u, alarms->size());
+ EXPECT_THAT((*alarms)[0].js_alarm->period_in_minutes,
+ testing::Pointee(0.001));
+
+ // Now wait for the alarms to fire, and ensure the cancelled alarms don't
+ // fire.
+ test->test_clock_->Advance(base::TimeDelta::FromMilliseconds(60));
+ RunScheduleNextPoll(test->alarm_manager_);
+ base::MessageLoop::current()->Run();
+
+ ASSERT_EQ(1u, test->alarm_delegate_->alarms_seen.size());
+ EXPECT_EQ("", test->alarm_delegate_->alarms_seen[0]);
+
+ // Ensure the 0.001-minute alarm is still there, since it's repeating.
+ test->alarm_manager_->GetAllAlarms(
+ test->extension()->id(),
+ base::Bind(ExtensionAlarmsTestClearGetAllAlarms2Callback));
+}
+
+TEST_F(ExtensionAlarmsTest, Clear) {
+ // Clear a non-existent one.
+ {
+ scoped_ptr<base::Value> result(
+ RunFunctionAndReturnValue(new AlarmsClearFunction(), "[\"nobody\"]"));
+ bool copy_bool_result = false;
+ ASSERT_TRUE(result->GetAsBoolean(&copy_bool_result));
+ EXPECT_FALSE(copy_bool_result);
+ }
+
+ // Create 3 alarms.
+ CreateAlarms(3);
+
+ // Clear all but the 0.001-minute alarm.
+ {
+ scoped_ptr<base::Value> result(
+ RunFunctionAndReturnValue(new AlarmsClearFunction(), "[\"7\"]"));
+ bool copy_bool_result = false;
+ ASSERT_TRUE(result->GetAsBoolean(&copy_bool_result));
+ EXPECT_TRUE(copy_bool_result);
+ }
+ {
+ scoped_ptr<base::Value> result(
+ RunFunctionAndReturnValue(new AlarmsClearFunction(), "[\"0\"]"));
+ bool copy_bool_result = false;
+ ASSERT_TRUE(result->GetAsBoolean(&copy_bool_result));
+ EXPECT_TRUE(copy_bool_result);
+ }
+
+ alarm_manager_->GetAllAlarms(
+ extension()->id(),
+ base::Bind(ExtensionAlarmsTestClearGetAllAlarms1Callback, this));
+}
+
+void ExtensionAlarmsTestClearAllGetAllAlarms2Callback(
+ const AlarmManager::AlarmList* alarms) {
+ ASSERT_FALSE(alarms);
+}
+
+void ExtensionAlarmsTestClearAllGetAllAlarms1Callback(
+ ExtensionAlarmsTest* test,
+ const AlarmManager::AlarmList* alarms) {
+ ASSERT_TRUE(alarms);
+ EXPECT_EQ(3u, alarms->size());
+
+ // Clear them.
+ test->RunFunction(new AlarmsClearAllFunction(), "[]");
+ test->alarm_manager_->GetAllAlarms(
+ test->extension()->id(),
+ base::Bind(ExtensionAlarmsTestClearAllGetAllAlarms2Callback));
+}
+
+TEST_F(ExtensionAlarmsTest, ClearAll) {
+ // ClearAll with no alarms set.
+ {
+ scoped_ptr<base::Value> result(
+ RunFunctionAndReturnValue(new AlarmsClearAllFunction(), "[]"));
+ bool copy_bool_result = false;
+ ASSERT_TRUE(result->GetAsBoolean(&copy_bool_result));
+ EXPECT_TRUE(copy_bool_result);
+ }
+
+ // Create 3 alarms.
+ CreateAlarms(3);
+ alarm_manager_->GetAllAlarms(
+ extension()->id(),
+ base::Bind(ExtensionAlarmsTestClearAllGetAllAlarms1Callback, this));
+}
+
+class ExtensionAlarmsSchedulingTest : public ExtensionAlarmsTest {
+ void GetAlarmCallback(Alarm* alarm) {
+ CHECK(alarm);
+ const base::Time scheduled_time =
+ base::Time::FromJsTime(alarm->js_alarm->scheduled_time);
+ EXPECT_EQ(scheduled_time, alarm_manager_->next_poll_time_);
+ }
+
+ static void RemoveAlarmCallback(bool success) { EXPECT_TRUE(success); }
+ static void RemoveAllAlarmsCallback() {}
+
+ public:
+ // Get the time that the alarm named is scheduled to run.
+ void VerifyScheduledTime(const std::string& alarm_name) {
+ alarm_manager_->GetAlarm(
+ extension()->id(), alarm_name,
+ base::Bind(&ExtensionAlarmsSchedulingTest::GetAlarmCallback,
+ base::Unretained(this)));
+ }
+
+ void RemoveAlarm(const std::string& name) {
+ alarm_manager_->RemoveAlarm(
+ extension()->id(), name,
+ base::Bind(&ExtensionAlarmsSchedulingTest::RemoveAlarmCallback));
+ }
+
+ void RemoveAllAlarms() {
+ alarm_manager_->RemoveAllAlarms(
+ extension()->id(),
+ base::Bind(&ExtensionAlarmsSchedulingTest::RemoveAllAlarmsCallback));
+ }
+};
+
+TEST_F(ExtensionAlarmsSchedulingTest, PollScheduling) {
+ {
+ CreateAlarm("[\"a\", {\"periodInMinutes\": 6}]");
+ CreateAlarm("[\"bb\", {\"periodInMinutes\": 8}]");
+ VerifyScheduledTime("a");
+ RemoveAllAlarms();
+ }
+ {
+ CreateAlarm("[\"a\", {\"delayInMinutes\": 10}]");
+ CreateAlarm("[\"bb\", {\"delayInMinutes\": 21}]");
+ VerifyScheduledTime("a");
+ RemoveAllAlarms();
+ }
+ {
+ test_clock_->SetNow(base::Time::FromDoubleT(10));
+ CreateAlarm("[\"a\", {\"periodInMinutes\": 10}]");
+ Alarm alarm;
+ alarm.js_alarm->name = "bb";
+ alarm.js_alarm->scheduled_time = 30 * 60000;
+ alarm.js_alarm->period_in_minutes.reset(new double(30));
+ alarm_manager_->AddAlarmImpl(extension()->id(), alarm);
+ VerifyScheduledTime("a");
+ RemoveAllAlarms();
+ }
+ {
+ test_clock_->SetNow(base::Time::FromDoubleT(3 * 60 + 1));
+ Alarm alarm;
+ alarm.js_alarm->name = "bb";
+ alarm.js_alarm->scheduled_time = 3 * 60000;
+ alarm.js_alarm->period_in_minutes.reset(new double(3));
+ alarm_manager_->AddAlarmImpl(extension()->id(), alarm);
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(
+ base::Time::FromJsTime(3 * 60000) + base::TimeDelta::FromMinutes(3),
+ alarm_manager_->next_poll_time_);
+ RemoveAllAlarms();
+ }
+ {
+ test_clock_->SetNow(base::Time::FromDoubleT(4 * 60 + 1));
+ CreateAlarm("[\"a\", {\"periodInMinutes\": 2}]");
+ RemoveAlarm("a");
+ Alarm alarm2;
+ alarm2.js_alarm->name = "bb";
+ alarm2.js_alarm->scheduled_time = 4 * 60000;
+ alarm2.js_alarm->period_in_minutes.reset(new double(4));
+ alarm_manager_->AddAlarmImpl(extension()->id(), alarm2);
+ Alarm alarm3;
+ alarm3.js_alarm->name = "ccc";
+ alarm3.js_alarm->scheduled_time = 25 * 60000;
+ alarm3.js_alarm->period_in_minutes.reset(new double(25));
+ alarm_manager_->AddAlarmImpl(extension()->id(), alarm3);
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(
+ base::Time::FromJsTime(4 * 60000) + base::TimeDelta::FromMinutes(4),
+ alarm_manager_->next_poll_time_);
+ RemoveAllAlarms();
+ }
+}
+
+TEST_F(ExtensionAlarmsSchedulingTest, ReleasedExtensionPollsInfrequently) {
+ set_extension(
+ utils::CreateEmptyExtensionWithLocation(extensions::Manifest::INTERNAL));
+ test_clock_->SetNow(base::Time::FromJsTime(300000));
+ CreateAlarm("[\"a\", {\"when\": 300010}]");
+ CreateAlarm("[\"b\", {\"when\": 340000}]");
+
+ // On startup (when there's no "last poll"), we let alarms fire as
+ // soon as they're scheduled.
+ EXPECT_DOUBLE_EQ(300010, alarm_manager_->next_poll_time_.ToJsTime());
+
+ alarm_manager_->last_poll_time_ = base::Time::FromJsTime(290000);
+ // In released extensions, we set the granularity to at least 1
+ // minute, which makes AddAlarm schedule the next poll after the
+ // extension requested.
+ alarm_manager_->ScheduleNextPoll();
+ EXPECT_DOUBLE_EQ((alarm_manager_->last_poll_time_ +
+ base::TimeDelta::FromMinutes(1)).ToJsTime(),
+ alarm_manager_->next_poll_time_.ToJsTime());
+}
+
+TEST_F(ExtensionAlarmsSchedulingTest, TimerRunning) {
+ EXPECT_FALSE(alarm_manager_->timer_.IsRunning());
+ CreateAlarm("[\"a\", {\"delayInMinutes\": 0.001}]");
+ EXPECT_TRUE(alarm_manager_->timer_.IsRunning());
+ test_clock_->Advance(base::TimeDelta::FromMilliseconds(60));
+ base::MessageLoop::current()->Run();
+ EXPECT_FALSE(alarm_manager_->timer_.IsRunning());
+ CreateAlarm("[\"bb\", {\"delayInMinutes\": 10}]");
+ EXPECT_TRUE(alarm_manager_->timer_.IsRunning());
+ RemoveAllAlarms();
+ EXPECT_FALSE(alarm_manager_->timer_.IsRunning());
+}
+
+TEST_F(ExtensionAlarmsSchedulingTest, MinimumGranularity) {
+ set_extension(
+ utils::CreateEmptyExtensionWithLocation(extensions::Manifest::INTERNAL));
+ test_clock_->SetNow(base::Time::FromJsTime(0));
+ CreateAlarm("[\"a\", {\"periodInMinutes\": 2}]");
+ test_clock_->Advance(base::TimeDelta::FromSeconds(1));
+ CreateAlarm("[\"b\", {\"periodInMinutes\": 2}]");
+ test_clock_->Advance(base::TimeDelta::FromMinutes(2));
+
+ alarm_manager_->last_poll_time_ = base::Time::FromJsTime(2 * 60000);
+ // In released extensions, we set the granularity to at least 1
+ // minute, which makes scheduler set it to 1 minute, rather than
+ // 1 second later (when b is supposed to go off).
+ alarm_manager_->ScheduleNextPoll();
+ EXPECT_DOUBLE_EQ((alarm_manager_->last_poll_time_ +
+ base::TimeDelta::FromMinutes(1)).ToJsTime(),
+ alarm_manager_->next_poll_time_.ToJsTime());
+}
+
+TEST_F(ExtensionAlarmsSchedulingTest, DifferentMinimumGranularities) {
+ test_clock_->SetNow(base::Time::FromJsTime(0));
+ // Create an alarm to go off in 12 seconds. This uses the default, unpacked
+ // extension - so there is no minimum granularity.
+ CreateAlarm("[\"a\", {\"periodInMinutes\": 0.2}]"); // 12 seconds.
+
+ // Create a new extension, which is packed, and has a granularity of 1 minute.
+ // CreateAlarm() uses extension_, so keep a ref of the old one around, and
+ // repopulate extension_.
+ scoped_refptr<Extension> extension2(extension_ref());
+ set_extension(
+ utils::CreateEmptyExtensionWithLocation(extensions::Manifest::INTERNAL));
+
+ CreateAlarm("[\"b\", {\"periodInMinutes\": 2}]");
+
+ alarm_manager_->last_poll_time_ = base::Time::FromJsTime(0);
+ alarm_manager_->ScheduleNextPoll();
+
+ // The next poll time should be 12 seconds from now - the time at which the
+ // first alarm should go off.
+ EXPECT_DOUBLE_EQ((alarm_manager_->last_poll_time_ +
+ base::TimeDelta::FromSeconds(12)).ToJsTime(),
+ alarm_manager_->next_poll_time_.ToJsTime());
+}
+
+// Test that scheduled alarms go off at set intervals, even if their actual
+// trigger is off.
+TEST_F(ExtensionAlarmsSchedulingTest, RepeatingAlarmsScheduledPredictably) {
+ test_clock_->SetNow(base::Time::FromJsTime(0));
+ CreateAlarm("[\"a\", {\"periodInMinutes\": 2}]");
+
+ alarm_manager_->last_poll_time_ = base::Time::FromJsTime(0);
+ alarm_manager_->ScheduleNextPoll();
+
+ // We expect the first poll to happen two minutes from the start.
+ EXPECT_DOUBLE_EQ((alarm_manager_->last_poll_time_ +
+ base::TimeDelta::FromSeconds(120)).ToJsTime(),
+ alarm_manager_->next_poll_time_.ToJsTime());
+
+ // Poll more than two minutes later.
+ test_clock_->Advance(base::TimeDelta::FromSeconds(125));
+ alarm_manager_->PollAlarms();
+
+ // The alarm should have triggered once.
+ EXPECT_EQ(1u, alarm_delegate_->alarms_seen.size());
+
+ // The next poll should still be scheduled for four minutes from the start,
+ // even though this is less than two minutes since the last alarm.
+ // Last poll was at 125 seconds; next poll should be at 240 seconds.
+ EXPECT_DOUBLE_EQ((alarm_manager_->last_poll_time_ +
+ base::TimeDelta::FromSeconds(115)).ToJsTime(),
+ alarm_manager_->next_poll_time_.ToJsTime());
+
+ // Completely miss a scheduled trigger.
+ test_clock_->Advance(base::TimeDelta::FromSeconds(255)); // Total Time: 380s
+ alarm_manager_->PollAlarms();
+
+ // The alarm should have triggered again at this last poll.
+ EXPECT_EQ(2u, alarm_delegate_->alarms_seen.size());
+
+ // The next poll should be the first poll that hasn't happened and is in-line
+ // with the original scheduling.
+ // Last poll was at 380 seconds; next poll should be at 480 seconds.
+ EXPECT_DOUBLE_EQ((alarm_manager_->last_poll_time_ +
+ base::TimeDelta::FromSeconds(100)).ToJsTime(),
+ alarm_manager_->next_poll_time_.ToJsTime());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/api_resource.cc b/chromium/extensions/browser/api/api_resource.cc
new file mode 100644
index 00000000000..6e319186a2b
--- /dev/null
+++ b/chromium/extensions/browser/api/api_resource.cc
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/api_resource.h"
+
+namespace extensions {
+
+ApiResource::ApiResource(const std::string& owner_extension_id)
+ : owner_extension_id_(owner_extension_id) {
+
+ CHECK(!owner_extension_id_.empty());
+}
+
+ApiResource::~ApiResource() {}
+
+bool ApiResource::IsPersistent() const {
+ return true; // backward-compatible behavior.
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/api_resource.h b/chromium/extensions/browser/api/api_resource.h
new file mode 100644
index 00000000000..f068103415e
--- /dev/null
+++ b/chromium/extensions/browser/api/api_resource.h
@@ -0,0 +1,43 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_API_RESOURCE_H_
+#define EXTENSIONS_BROWSER_API_API_RESOURCE_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/common/extension.h"
+
+namespace extensions {
+
+// An ApiResource represents something that an extension API manages, such as a
+// socket or a serial-port connection. Typically, an ApiResourceManager will
+// control the lifetime of all ApiResources of a specific derived type.
+class ApiResource {
+ public:
+ virtual ~ApiResource();
+
+ const std::string& owner_extension_id() const { return owner_extension_id_; }
+
+ // If this method returns |true|, the resource remains open when the
+ // owning extension is suspended due to inactivity.
+ virtual bool IsPersistent() const;
+
+ static const content::BrowserThread::ID kThreadId =
+ content::BrowserThread::IO;
+
+ protected:
+ explicit ApiResource(const std::string& owner_extension_id);
+
+ private:
+ // The extension that owns this resource.
+ const std::string owner_extension_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(ApiResource);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_API_RESOURCE_H_
diff --git a/chromium/extensions/browser/api/api_resource_manager.h b/chromium/extensions/browser/api/api_resource_manager.h
new file mode 100644
index 00000000000..79ef86209dd
--- /dev/null
+++ b/chromium/extensions/browser/api/api_resource_manager.h
@@ -0,0 +1,438 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_API_RESOURCE_MANAGER_H_
+#define EXTENSIONS_BROWSER_API_API_RESOURCE_MANAGER_H_
+
+#include <map>
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/scoped_observer.h"
+#include "base/threading/non_thread_safe.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/browser/process_manager_observer.h"
+#include "extensions/common/extension.h"
+
+namespace extensions {
+class CastChannelAsyncApiFunction;
+
+namespace api {
+class BluetoothSocketApiFunction;
+class BluetoothSocketEventDispatcher;
+class SerialEventDispatcher;
+class TCPServerSocketEventDispatcher;
+class TCPSocketEventDispatcher;
+class UDPSocketEventDispatcher;
+}
+
+template <typename T>
+struct NamedThreadTraits {
+ static bool IsCalledOnValidThread() {
+ return content::BrowserThread::CurrentlyOn(T::kThreadId);
+ }
+
+ static bool IsMessageLoopValid() {
+ return content::BrowserThread::IsMessageLoopValid(T::kThreadId);
+ }
+
+ static scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() {
+ return content::BrowserThread::GetMessageLoopProxyForThread(T::kThreadId);
+ }
+};
+
+// An ApiResourceManager manages the lifetime of a set of resources that
+// that live on named threads (i.e. BrowserThread::IO) which ApiFunctions use.
+// Examples of such resources are sockets or USB connections.
+//
+// Users of this class should define kThreadId to be the thread that
+// ApiResourceManager to works on. The default is defined in ApiResource.
+// The user must also define a static const char* service_name() that returns
+// the name of the service, and in order for ApiResourceManager to use
+// service_name() friend this class.
+//
+// In the cc file the user must define a GetFactoryInstance() and manage their
+// own instances (typically using LazyInstance or Singleton).
+//
+// E.g.:
+//
+// class Resource {
+// public:
+// static const BrowserThread::ID kThreadId = BrowserThread::FILE;
+// private:
+// friend class ApiResourceManager<Resource>;
+// static const char* service_name() {
+// return "ResourceManager";
+// }
+// };
+//
+// In the cc file:
+//
+// static base::LazyInstance<BrowserContextKeyedAPIFactory<
+// ApiResourceManager<Resource> > >
+// g_factory = LAZY_INSTANCE_INITIALIZER;
+//
+//
+// template <>
+// BrowserContextKeyedAPIFactory<ApiResourceManager<Resource> >*
+// ApiResourceManager<Resource>::GetFactoryInstance() {
+// return g_factory.Pointer();
+// }
+template <class T, typename ThreadingTraits = NamedThreadTraits<T>>
+class ApiResourceManager : public BrowserContextKeyedAPI,
+ public base::NonThreadSafe,
+ public ExtensionRegistryObserver,
+ public ProcessManagerObserver {
+ public:
+ explicit ApiResourceManager(content::BrowserContext* context)
+ : data_(new ApiResourceData()),
+ extension_registry_observer_(this),
+ process_manager_observer_(this) {
+ extension_registry_observer_.Add(ExtensionRegistry::Get(context));
+ process_manager_observer_.Add(ProcessManager::Get(context));
+ }
+
+ virtual ~ApiResourceManager() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(ThreadingTraits::IsMessageLoopValid())
+ << "A unit test is using an ApiResourceManager but didn't provide "
+ "the thread message loop needed for that kind of resource. "
+ "Please ensure that the appropriate message loop is operational.";
+
+ data_->InititateCleanup();
+ }
+
+ // Takes ownership.
+ int Add(T* api_resource) { return data_->Add(api_resource); }
+
+ void Remove(const std::string& extension_id, int api_resource_id) {
+ data_->Remove(extension_id, api_resource_id);
+ }
+
+ T* Get(const std::string& extension_id, int api_resource_id) {
+ return data_->Get(extension_id, api_resource_id);
+ }
+
+ base::hash_set<int>* GetResourceIds(const std::string& extension_id) {
+ return data_->GetResourceIds(extension_id);
+ }
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<ApiResourceManager<T> >*
+ GetFactoryInstance();
+
+ // Convenience method to get the ApiResourceManager for a profile.
+ static ApiResourceManager<T>* Get(content::BrowserContext* context) {
+ return BrowserContextKeyedAPIFactory<ApiResourceManager<T> >::Get(context);
+ }
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return T::service_name(); }
+
+ // Change the resource mapped to this |extension_id| at this
+ // |api_resource_id| to |resource|. Returns true and succeeds unless
+ // |api_resource_id| does not already identify a resource held by
+ // |extension_id|.
+ bool Replace(const std::string& extension_id,
+ int api_resource_id,
+ T* resource) {
+ return data_->Replace(extension_id, api_resource_id, resource);
+ }
+
+ protected:
+ // ProcessManagerObserver:
+ void OnBackgroundHostClose(const std::string& extension_id) override {
+ data_->InitiateExtensionSuspendedCleanup(extension_id);
+ }
+
+ // ExtensionRegistryObserver:
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override {
+ data_->InitiateExtensionUnloadedCleanup(extension->id());
+ }
+
+ private:
+ // TODO(rockot): ApiResourceData could be moved out of ApiResourceManager and
+ // we could avoid maintaining a friends list here.
+ friend class BluetoothAPI;
+ friend class CastChannelAsyncApiFunction;
+ friend class api::BluetoothSocketApiFunction;
+ friend class api::BluetoothSocketEventDispatcher;
+ friend class api::SerialEventDispatcher;
+ friend class api::TCPServerSocketEventDispatcher;
+ friend class api::TCPSocketEventDispatcher;
+ friend class api::UDPSocketEventDispatcher;
+ friend class BrowserContextKeyedAPIFactory<ApiResourceManager<T> >;
+
+ static const bool kServiceHasOwnInstanceInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ // ApiResourceData class handles resource bookkeeping on a thread
+ // where resource lifetime is handled.
+ class ApiResourceData : public base::RefCountedThreadSafe<ApiResourceData> {
+ public:
+ typedef std::map<int, linked_ptr<T> > ApiResourceMap;
+ // Lookup map from extension id's to allocated resource id's.
+ typedef std::map<std::string, base::hash_set<int> > ExtensionToResourceMap;
+
+ ApiResourceData() : next_id_(1) {}
+
+ int Add(T* api_resource) {
+ DCHECK(ThreadingTraits::IsCalledOnValidThread());
+ int id = GenerateId();
+ if (id > 0) {
+ linked_ptr<T> resource_ptr(api_resource);
+ api_resource_map_[id] = resource_ptr;
+
+ const std::string& extension_id = api_resource->owner_extension_id();
+ ExtensionToResourceMap::iterator it =
+ extension_resource_map_.find(extension_id);
+ if (it == extension_resource_map_.end()) {
+ it = extension_resource_map_.insert(
+ std::make_pair(extension_id, base::hash_set<int>())).first;
+ }
+ it->second.insert(id);
+ return id;
+ }
+ return 0;
+ }
+
+ void Remove(const std::string& extension_id, int api_resource_id) {
+ DCHECK(ThreadingTraits::IsCalledOnValidThread());
+ if (GetOwnedResource(extension_id, api_resource_id)) {
+ ExtensionToResourceMap::iterator it =
+ extension_resource_map_.find(extension_id);
+ it->second.erase(api_resource_id);
+ api_resource_map_.erase(api_resource_id);
+ }
+ }
+
+ T* Get(const std::string& extension_id, int api_resource_id) {
+ DCHECK(ThreadingTraits::IsCalledOnValidThread());
+ return GetOwnedResource(extension_id, api_resource_id);
+ }
+
+ // Change the resource mapped to this |extension_id| at this
+ // |api_resource_id| to |resource|. Returns true and succeeds unless
+ // |api_resource_id| does not already identify a resource held by
+ // |extension_id|.
+ bool Replace(const std::string& extension_id,
+ int api_resource_id,
+ T* api_resource) {
+ DCHECK(ThreadingTraits::IsCalledOnValidThread());
+ T* old_resource = api_resource_map_[api_resource_id].get();
+ if (old_resource && extension_id == old_resource->owner_extension_id()) {
+ api_resource_map_[api_resource_id] = linked_ptr<T>(api_resource);
+ return true;
+ }
+ return false;
+ }
+
+ base::hash_set<int>* GetResourceIds(const std::string& extension_id) {
+ DCHECK(ThreadingTraits::IsCalledOnValidThread());
+ return GetOwnedResourceIds(extension_id);
+ }
+
+ void InitiateExtensionUnloadedCleanup(const std::string& extension_id) {
+ if (ThreadingTraits::IsCalledOnValidThread()) {
+ CleanupResourcesFromUnloadedExtension(extension_id);
+ } else {
+ ThreadingTraits::GetSequencedTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ApiResourceData::CleanupResourcesFromUnloadedExtension,
+ this,
+ extension_id));
+ }
+ }
+
+ void InitiateExtensionSuspendedCleanup(const std::string& extension_id) {
+ if (ThreadingTraits::IsCalledOnValidThread()) {
+ CleanupResourcesFromSuspendedExtension(extension_id);
+ } else {
+ ThreadingTraits::GetSequencedTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ApiResourceData::CleanupResourcesFromSuspendedExtension,
+ this,
+ extension_id));
+ }
+ }
+
+ void InititateCleanup() {
+ if (ThreadingTraits::IsCalledOnValidThread()) {
+ Cleanup();
+ } else {
+ ThreadingTraits::GetSequencedTaskRunner()->PostTask(
+ FROM_HERE, base::Bind(&ApiResourceData::Cleanup, this));
+ }
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<ApiResourceData>;
+
+ virtual ~ApiResourceData() {}
+
+ T* GetOwnedResource(const std::string& extension_id, int api_resource_id) {
+ linked_ptr<T> ptr = api_resource_map_[api_resource_id];
+ T* resource = ptr.get();
+ if (resource && extension_id == resource->owner_extension_id())
+ return resource;
+ return NULL;
+ }
+
+ base::hash_set<int>* GetOwnedResourceIds(const std::string& extension_id) {
+ DCHECK(ThreadingTraits::IsCalledOnValidThread());
+ ExtensionToResourceMap::iterator it =
+ extension_resource_map_.find(extension_id);
+ if (it == extension_resource_map_.end())
+ return NULL;
+ return &(it->second);
+ }
+
+ void CleanupResourcesFromUnloadedExtension(
+ const std::string& extension_id) {
+ CleanupResourcesFromExtension(extension_id, true);
+ }
+
+ void CleanupResourcesFromSuspendedExtension(
+ const std::string& extension_id) {
+ CleanupResourcesFromExtension(extension_id, false);
+ }
+
+ void CleanupResourcesFromExtension(const std::string& extension_id,
+ bool remove_all) {
+ DCHECK(ThreadingTraits::IsCalledOnValidThread());
+
+ ExtensionToResourceMap::iterator it =
+ extension_resource_map_.find(extension_id);
+ if (it == extension_resource_map_.end())
+ return;
+
+ // Remove all resources, or the non persistent ones only if |remove_all|
+ // is false.
+ base::hash_set<int>& resource_ids = it->second;
+ for (base::hash_set<int>::iterator it = resource_ids.begin();
+ it != resource_ids.end();) {
+ bool erase = false;
+ if (remove_all) {
+ erase = true;
+ } else {
+ linked_ptr<T> ptr = api_resource_map_[*it];
+ T* resource = ptr.get();
+ erase = (resource && !resource->IsPersistent());
+ }
+
+ if (erase) {
+ api_resource_map_.erase(*it);
+ resource_ids.erase(it++);
+ } else {
+ ++it;
+ }
+ } // end for
+
+ // Remove extension entry if we removed all its resources.
+ if (resource_ids.size() == 0) {
+ extension_resource_map_.erase(extension_id);
+ }
+ }
+
+ void Cleanup() {
+ DCHECK(ThreadingTraits::IsCalledOnValidThread());
+
+ api_resource_map_.clear();
+ extension_resource_map_.clear();
+ }
+
+ int GenerateId() { return next_id_++; }
+
+ int next_id_;
+ ApiResourceMap api_resource_map_;
+ ExtensionToResourceMap extension_resource_map_;
+ };
+
+ content::NotificationRegistrar registrar_;
+ scoped_refptr<ApiResourceData> data_;
+
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+ ScopedObserver<ProcessManager, ProcessManagerObserver>
+ process_manager_observer_;
+};
+
+// With WorkerPoolThreadTraits, ApiResourceManager can be used to manage the
+// lifetime of a set of resources that live on sequenced task runner threads
+// which ApiFunctions use. Examples of such resources are temporary file
+// resources produced by certain API calls.
+//
+// Instead of kThreadId. classes used for tracking such resources should define
+// kSequenceToken and kShutdownBehavior to identify sequence task runner for
+// ApiResourceManager to work on and how pending tasks should behave on
+// shutdown.
+// The user must also define a static const char* service_name() that returns
+// the name of the service, and in order for ApiWorkerPoolResourceManager to use
+// service_name() friend this class.
+//
+// In the cc file the user must define a GetFactoryInstance() and manage their
+// own instances (typically using LazyInstance or Singleton).
+//
+// E.g.:
+//
+// class PoolResource {
+// public:
+// static const char kSequenceToken[] = "temp_files";
+// static const base::SequencedWorkerPool::WorkerShutdown kShutdownBehavior =
+// base::SequencedWorkerPool::BLOCK_SHUTDOWN;
+// private:
+// friend class ApiResourceManager<WorkerPoolResource,
+// WorkerPoolThreadTraits>;
+// static const char* service_name() {
+// return "TempFilesResourceManager";
+// }
+// };
+//
+// In the cc file:
+//
+// static base::LazyInstance<BrowserContextKeyedAPIFactory<
+// ApiResourceManager<Resource, WorkerPoolThreadTraits> > >
+// g_factory = LAZY_INSTANCE_INITIALIZER;
+//
+//
+// template <>
+// BrowserContextKeyedAPIFactory<ApiResourceManager<WorkerPoolResource> >*
+// ApiResourceManager<WorkerPoolPoolResource,
+// WorkerPoolThreadTraits>::GetFactoryInstance() {
+// return g_factory.Pointer();
+// }
+template <typename T>
+struct WorkerPoolThreadTraits {
+ static bool IsCalledOnValidThread() {
+ return content::BrowserThread::GetBlockingPool()
+ ->IsRunningSequenceOnCurrentThread(
+ content::BrowserThread::GetBlockingPool()->GetNamedSequenceToken(
+ T::kSequenceToken));
+ }
+
+ static bool IsMessageLoopValid() {
+ return content::BrowserThread::GetBlockingPool() != NULL;
+ }
+
+ static scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() {
+ return content::BrowserThread::GetBlockingPool()
+ ->GetSequencedTaskRunnerWithShutdownBehavior(
+ content::BrowserThread::GetBlockingPool()->GetNamedSequenceToken(
+ T::kSequenceToken),
+ T::kShutdownBehavior);
+ }
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_API_RESOURCE_MANAGER_H_
diff --git a/chromium/extensions/browser/api/api_resource_manager_unittest.cc b/chromium/extensions/browser/api/api_resource_manager_unittest.cc
new file mode 100644
index 00000000000..d398b3075ec
--- /dev/null
+++ b/chromium/extensions/browser/api/api_resource_manager_unittest.cc
@@ -0,0 +1,58 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_path.h"
+#include "base/strings/string_util.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/api/api_resource.h"
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/browser/api_unittest.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+class ApiResourceManagerUnitTest : public ApiUnitTest {};
+
+class FakeApiResource : public ApiResource {
+ public:
+ explicit FakeApiResource(const std::string& owner_extension_id)
+ : ApiResource(owner_extension_id) {}
+ ~FakeApiResource() override {}
+ static const BrowserThread::ID kThreadId = BrowserThread::UI;
+};
+
+TEST_F(ApiResourceManagerUnitTest, TwoAppsCannotShareResources) {
+ scoped_ptr<ApiResourceManager<FakeApiResource>> manager(
+ new ApiResourceManager<FakeApiResource>(browser_context()));
+ scoped_refptr<extensions::Extension> extension_one =
+ test_util::CreateEmptyExtension("one");
+ scoped_refptr<extensions::Extension> extension_two =
+ test_util::CreateEmptyExtension("two");
+
+ const std::string extension_one_id(extension_one->id());
+ const std::string extension_two_id(extension_two->id());
+
+ int resource_one_id = manager->Add(new FakeApiResource(extension_one_id));
+ int resource_two_id = manager->Add(new FakeApiResource(extension_two_id));
+ CHECK(resource_one_id);
+ CHECK(resource_two_id);
+
+ // Confirm each extension can get its own resource.
+ ASSERT_TRUE(manager->Get(extension_one_id, resource_one_id) != NULL);
+ ASSERT_TRUE(manager->Get(extension_two_id, resource_two_id) != NULL);
+
+ // Confirm neither extension can get the other's resource.
+ ASSERT_TRUE(manager->Get(extension_one_id, resource_two_id) == NULL);
+ ASSERT_TRUE(manager->Get(extension_two_id, resource_one_id) == NULL);
+
+ // And make sure we're not susceptible to any Jedi mind tricks.
+ ASSERT_TRUE(manager->Get(std::string(), resource_one_id) == NULL);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/app_current_window_internal/OWNERS b/chromium/extensions/browser/api/app_current_window_internal/OWNERS
new file mode 100644
index 00000000000..5caf038be1a
--- /dev/null
+++ b/chromium/extensions/browser/api/app_current_window_internal/OWNERS
@@ -0,0 +1,3 @@
+benwells@chromium.org
+calamity@chromium.org
+tapted@chromium.org
diff --git a/chromium/extensions/browser/api/app_current_window_internal/app_current_window_internal_api.cc b/chromium/extensions/browser/api/app_current_window_internal/app_current_window_internal_api.cc
new file mode 100644
index 00000000000..661edcd8faa
--- /dev/null
+++ b/chromium/extensions/browser/api/app_current_window_internal/app_current_window_internal_api.cc
@@ -0,0 +1,379 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/app_current_window_internal/app_current_window_internal_api.h"
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/command_line.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/app_window_client.h"
+#include "extensions/browser/app_window/app_window_registry.h"
+#include "extensions/browser/app_window/native_app_window.h"
+#include "extensions/browser/app_window/size_constraints.h"
+#include "extensions/common/api/app_current_window_internal.h"
+#include "extensions/common/features/simple_feature.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/switches.h"
+#include "third_party/skia/include/core/SkRegion.h"
+
+namespace app_current_window_internal =
+ extensions::api::app_current_window_internal;
+
+namespace Show = app_current_window_internal::Show;
+namespace SetBounds = app_current_window_internal::SetBounds;
+namespace SetSizeConstraints = app_current_window_internal::SetSizeConstraints;
+namespace SetIcon = app_current_window_internal::SetIcon;
+namespace SetShape = app_current_window_internal::SetShape;
+namespace SetAlwaysOnTop = app_current_window_internal::SetAlwaysOnTop;
+namespace SetVisibleOnAllWorkspaces =
+ app_current_window_internal::SetVisibleOnAllWorkspaces;
+
+using app_current_window_internal::Bounds;
+using app_current_window_internal::Region;
+using app_current_window_internal::RegionRect;
+using app_current_window_internal::SizeConstraints;
+
+namespace extensions {
+
+namespace {
+
+const char kNoAssociatedAppWindow[] =
+ "The context from which the function was called did not have an "
+ "associated app window.";
+
+const char kDevChannelOnly[] =
+ "This function is currently only available in the Dev channel.";
+
+const char kRequiresFramelessWindow[] =
+ "This function requires a frameless window (frame:none).";
+
+const char kAlwaysOnTopPermission[] =
+ "The \"app.window.alwaysOnTop\" permission is required.";
+
+const char kInvalidParameters[] = "Invalid parameters.";
+
+const int kUnboundedSize = SizeConstraints::kUnboundedSize;
+
+void GetBoundsFields(const Bounds& bounds_spec, gfx::Rect* bounds) {
+ if (bounds_spec.left)
+ bounds->set_x(*bounds_spec.left);
+ if (bounds_spec.top)
+ bounds->set_y(*bounds_spec.top);
+ if (bounds_spec.width)
+ bounds->set_width(*bounds_spec.width);
+ if (bounds_spec.height)
+ bounds->set_height(*bounds_spec.height);
+}
+
+// Copy the constraint value from the API to our internal representation of
+// content size constraints. A value of zero resets the constraints. The insets
+// are used to transform window constraints to content constraints.
+void GetConstraintWidth(const scoped_ptr<int>& width,
+ const gfx::Insets& insets,
+ gfx::Size* size) {
+ if (!width.get())
+ return;
+
+ size->set_width(*width > 0 ? std::max(0, *width - insets.width())
+ : kUnboundedSize);
+}
+
+void GetConstraintHeight(const scoped_ptr<int>& height,
+ const gfx::Insets& insets,
+ gfx::Size* size) {
+ if (!height.get())
+ return;
+
+ size->set_height(*height > 0 ? std::max(0, *height - insets.height())
+ : kUnboundedSize);
+}
+
+} // namespace
+
+namespace bounds {
+
+enum BoundsType {
+ INNER_BOUNDS,
+ OUTER_BOUNDS,
+ DEPRECATED_BOUNDS,
+ INVALID_TYPE
+};
+
+const char kInnerBoundsType[] = "innerBounds";
+const char kOuterBoundsType[] = "outerBounds";
+const char kDeprecatedBoundsType[] = "bounds";
+
+BoundsType GetBoundsType(const std::string& type_as_string) {
+ if (type_as_string == kInnerBoundsType)
+ return INNER_BOUNDS;
+ else if (type_as_string == kOuterBoundsType)
+ return OUTER_BOUNDS;
+ else if (type_as_string == kDeprecatedBoundsType)
+ return DEPRECATED_BOUNDS;
+ else
+ return INVALID_TYPE;
+}
+
+} // namespace bounds
+
+bool AppCurrentWindowInternalExtensionFunction::RunSync() {
+ AppWindowRegistry* registry = AppWindowRegistry::Get(browser_context());
+ DCHECK(registry);
+ content::WebContents* web_contents = GetSenderWebContents();
+ if (!web_contents)
+ // No need to set an error, since we won't return to the caller anyway if
+ // there's no RVH.
+ return false;
+ AppWindow* window = registry->GetAppWindowForWebContents(web_contents);
+ if (!window) {
+ error_ = kNoAssociatedAppWindow;
+ return false;
+ }
+ return RunWithWindow(window);
+}
+
+bool AppCurrentWindowInternalFocusFunction::RunWithWindow(AppWindow* window) {
+ window->GetBaseWindow()->Activate();
+ return true;
+}
+
+bool AppCurrentWindowInternalFullscreenFunction::RunWithWindow(
+ AppWindow* window) {
+ window->Fullscreen();
+ return true;
+}
+
+bool AppCurrentWindowInternalMaximizeFunction::RunWithWindow(
+ AppWindow* window) {
+ window->Maximize();
+ return true;
+}
+
+bool AppCurrentWindowInternalMinimizeFunction::RunWithWindow(
+ AppWindow* window) {
+ window->Minimize();
+ return true;
+}
+
+bool AppCurrentWindowInternalRestoreFunction::RunWithWindow(AppWindow* window) {
+ window->Restore();
+ return true;
+}
+
+bool AppCurrentWindowInternalDrawAttentionFunction::RunWithWindow(
+ AppWindow* window) {
+ window->GetBaseWindow()->FlashFrame(true);
+ return true;
+}
+
+bool AppCurrentWindowInternalClearAttentionFunction::RunWithWindow(
+ AppWindow* window) {
+ window->GetBaseWindow()->FlashFrame(false);
+ return true;
+}
+
+bool AppCurrentWindowInternalShowFunction::RunWithWindow(AppWindow* window) {
+ scoped_ptr<Show::Params> params(Show::Params::Create(*args_));
+ CHECK(params.get());
+ if (params->focused && !*params->focused)
+ window->Show(AppWindow::SHOW_INACTIVE);
+ else
+ window->Show(AppWindow::SHOW_ACTIVE);
+ return true;
+}
+
+bool AppCurrentWindowInternalHideFunction::RunWithWindow(AppWindow* window) {
+ window->Hide();
+ return true;
+}
+
+bool AppCurrentWindowInternalSetBoundsFunction::RunWithWindow(
+ AppWindow* window) {
+ scoped_ptr<SetBounds::Params> params(SetBounds::Params::Create(*args_));
+ CHECK(params.get());
+
+ bounds::BoundsType bounds_type = bounds::GetBoundsType(params->bounds_type);
+ if (bounds_type == bounds::INVALID_TYPE) {
+ NOTREACHED();
+ error_ = kInvalidParameters;
+ return false;
+ }
+
+ // Start with the current bounds, and change any values that are specified in
+ // the incoming parameters.
+ gfx::Rect original_window_bounds = window->GetBaseWindow()->GetBounds();
+ gfx::Rect window_bounds = original_window_bounds;
+ gfx::Insets frame_insets = window->GetBaseWindow()->GetFrameInsets();
+ const Bounds& bounds_spec = params->bounds;
+
+ switch (bounds_type) {
+ case bounds::DEPRECATED_BOUNDS: {
+ // We need to maintain backcompatibility with a bug on Windows and
+ // ChromeOS, which sets the position of the window but the size of the
+ // content.
+ if (bounds_spec.left)
+ window_bounds.set_x(*bounds_spec.left);
+ if (bounds_spec.top)
+ window_bounds.set_y(*bounds_spec.top);
+ if (bounds_spec.width)
+ window_bounds.set_width(*bounds_spec.width + frame_insets.width());
+ if (bounds_spec.height)
+ window_bounds.set_height(*bounds_spec.height + frame_insets.height());
+ break;
+ }
+ case bounds::OUTER_BOUNDS: {
+ GetBoundsFields(bounds_spec, &window_bounds);
+ break;
+ }
+ case bounds::INNER_BOUNDS: {
+ window_bounds.Inset(frame_insets);
+ GetBoundsFields(bounds_spec, &window_bounds);
+ window_bounds.Inset(-frame_insets);
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+
+ if (original_window_bounds != window_bounds) {
+ if (original_window_bounds.size() != window_bounds.size()) {
+ SizeConstraints constraints(
+ SizeConstraints::AddFrameToConstraints(
+ window->GetBaseWindow()->GetContentMinimumSize(), frame_insets),
+ SizeConstraints::AddFrameToConstraints(
+ window->GetBaseWindow()->GetContentMaximumSize(), frame_insets));
+
+ window_bounds.set_size(constraints.ClampSize(window_bounds.size()));
+ }
+
+ window->GetBaseWindow()->SetBounds(window_bounds);
+ }
+
+ return true;
+}
+
+bool AppCurrentWindowInternalSetSizeConstraintsFunction::RunWithWindow(
+ AppWindow* window) {
+ scoped_ptr<SetSizeConstraints::Params> params(
+ SetSizeConstraints::Params::Create(*args_));
+ CHECK(params.get());
+
+ bounds::BoundsType bounds_type = bounds::GetBoundsType(params->bounds_type);
+ if (bounds_type != bounds::INNER_BOUNDS &&
+ bounds_type != bounds::OUTER_BOUNDS) {
+ NOTREACHED();
+ error_ = kInvalidParameters;
+ return false;
+ }
+
+ gfx::Size original_min_size =
+ window->GetBaseWindow()->GetContentMinimumSize();
+ gfx::Size original_max_size =
+ window->GetBaseWindow()->GetContentMaximumSize();
+ gfx::Size min_size = original_min_size;
+ gfx::Size max_size = original_max_size;
+ const app_current_window_internal::SizeConstraints& constraints =
+ params->constraints;
+
+ // Use the frame insets to convert window size constraints to content size
+ // constraints.
+ gfx::Insets insets;
+ if (bounds_type == bounds::OUTER_BOUNDS)
+ insets = window->GetBaseWindow()->GetFrameInsets();
+
+ GetConstraintWidth(constraints.min_width, insets, &min_size);
+ GetConstraintWidth(constraints.max_width, insets, &max_size);
+ GetConstraintHeight(constraints.min_height, insets, &min_size);
+ GetConstraintHeight(constraints.max_height, insets, &max_size);
+
+ if (min_size != original_min_size || max_size != original_max_size)
+ window->SetContentSizeConstraints(min_size, max_size);
+
+ return true;
+}
+
+bool AppCurrentWindowInternalSetIconFunction::RunWithWindow(AppWindow* window) {
+ if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev() &&
+ extension()->location() != extensions::Manifest::COMPONENT) {
+ error_ = kDevChannelOnly;
+ return false;
+ }
+
+ scoped_ptr<SetIcon::Params> params(SetIcon::Params::Create(*args_));
+ CHECK(params.get());
+ // The |icon_url| parameter may be a blob url (e.g. an image fetched with an
+ // XMLHttpRequest) or a resource url.
+ GURL url(params->icon_url);
+ if (!url.is_valid())
+ url = extension()->GetResourceURL(params->icon_url);
+
+ window->SetAppIconUrl(url);
+ return true;
+}
+
+bool AppCurrentWindowInternalSetShapeFunction::RunWithWindow(
+ AppWindow* window) {
+
+ if (!window->GetBaseWindow()->IsFrameless()) {
+ error_ = kRequiresFramelessWindow;
+ return false;
+ }
+
+ scoped_ptr<SetShape::Params> params(
+ SetShape::Params::Create(*args_));
+ const Region& shape = params->region;
+
+ // Build a region from the supplied list of rects.
+ // If |rects| is missing, then the input region is removed. This clears the
+ // input region so that the entire window accepts input events.
+ // To specify an empty input region (so the window ignores all input),
+ // |rects| should be an empty list.
+ scoped_ptr<SkRegion> region(new SkRegion);
+ if (shape.rects) {
+ for (const RegionRect& input_rect : *shape.rects) {
+ int32_t x = input_rect.left;
+ int32_t y = input_rect.top;
+ int32_t width = input_rect.width;
+ int32_t height = input_rect.height;
+
+ SkIRect rect = SkIRect::MakeXYWH(x, y, width, height);
+ region->op(rect, SkRegion::kUnion_Op);
+ }
+ } else {
+ region.reset(NULL);
+ }
+
+ window->UpdateShape(std::move(region));
+
+ return true;
+}
+
+bool AppCurrentWindowInternalSetAlwaysOnTopFunction::RunWithWindow(
+ AppWindow* window) {
+ if (!extension()->permissions_data()->HasAPIPermission(
+ extensions::APIPermission::kAlwaysOnTopWindows)) {
+ error_ = kAlwaysOnTopPermission;
+ return false;
+ }
+
+ scoped_ptr<SetAlwaysOnTop::Params> params(
+ SetAlwaysOnTop::Params::Create(*args_));
+ CHECK(params.get());
+ window->SetAlwaysOnTop(params->always_on_top);
+ return true;
+}
+
+bool AppCurrentWindowInternalSetVisibleOnAllWorkspacesFunction::RunWithWindow(
+ AppWindow* window) {
+ scoped_ptr<SetVisibleOnAllWorkspaces::Params> params(
+ SetVisibleOnAllWorkspaces::Params::Create(*args_));
+ CHECK(params.get());
+ window->GetBaseWindow()->SetVisibleOnAllWorkspaces(params->always_visible);
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/app_current_window_internal/app_current_window_internal_api.h b/chromium/extensions/browser/api/app_current_window_internal/app_current_window_internal_api.h
new file mode 100644
index 00000000000..7d9e784792e
--- /dev/null
+++ b/chromium/extensions/browser/api/app_current_window_internal/app_current_window_internal_api.h
@@ -0,0 +1,191 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_APP_CURRENT_WINDOW_INTERNAL_APP_CURRENT_WINDOW_INTERNAL_API_H_
+#define EXTENSIONS_BROWSER_API_APP_CURRENT_WINDOW_INTERNAL_APP_CURRENT_WINDOW_INTERNAL_API_H_
+
+#include "extensions/browser/extension_function.h"
+
+namespace extensions {
+
+class AppWindow;
+
+class AppCurrentWindowInternalExtensionFunction : public SyncExtensionFunction {
+ protected:
+ ~AppCurrentWindowInternalExtensionFunction() override {}
+
+ // Invoked with the current app window.
+ virtual bool RunWithWindow(AppWindow* window) = 0;
+
+ private:
+ bool RunSync() override;
+};
+
+class AppCurrentWindowInternalFocusFunction
+ : public AppCurrentWindowInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("app.currentWindowInternal.focus",
+ APP_CURRENTWINDOWINTERNAL_FOCUS)
+
+ protected:
+ ~AppCurrentWindowInternalFocusFunction() override {}
+ bool RunWithWindow(AppWindow* window) override;
+};
+
+class AppCurrentWindowInternalFullscreenFunction
+ : public AppCurrentWindowInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("app.currentWindowInternal.fullscreen",
+ APP_CURRENTWINDOWINTERNAL_FULLSCREEN)
+
+ protected:
+ ~AppCurrentWindowInternalFullscreenFunction() override {}
+ bool RunWithWindow(AppWindow* window) override;
+};
+
+class AppCurrentWindowInternalMaximizeFunction
+ : public AppCurrentWindowInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("app.currentWindowInternal.maximize",
+ APP_CURRENTWINDOWINTERNAL_MAXIMIZE)
+
+ protected:
+ ~AppCurrentWindowInternalMaximizeFunction() override {}
+ bool RunWithWindow(AppWindow* window) override;
+};
+
+class AppCurrentWindowInternalMinimizeFunction
+ : public AppCurrentWindowInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("app.currentWindowInternal.minimize",
+ APP_CURRENTWINDOWINTERNAL_MINIMIZE)
+
+ protected:
+ ~AppCurrentWindowInternalMinimizeFunction() override {}
+ bool RunWithWindow(AppWindow* window) override;
+};
+
+class AppCurrentWindowInternalRestoreFunction
+ : public AppCurrentWindowInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("app.currentWindowInternal.restore",
+ APP_CURRENTWINDOWINTERNAL_RESTORE)
+
+ protected:
+ ~AppCurrentWindowInternalRestoreFunction() override {}
+ bool RunWithWindow(AppWindow* window) override;
+};
+
+class AppCurrentWindowInternalDrawAttentionFunction
+ : public AppCurrentWindowInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("app.currentWindowInternal.drawAttention",
+ APP_CURRENTWINDOWINTERNAL_DRAWATTENTION)
+
+ protected:
+ ~AppCurrentWindowInternalDrawAttentionFunction() override {}
+ bool RunWithWindow(AppWindow* window) override;
+};
+
+class AppCurrentWindowInternalClearAttentionFunction
+ : public AppCurrentWindowInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("app.currentWindowInternal.clearAttention",
+ APP_CURRENTWINDOWINTERNAL_CLEARATTENTION)
+
+ protected:
+ ~AppCurrentWindowInternalClearAttentionFunction() override {}
+ bool RunWithWindow(AppWindow* window) override;
+};
+
+class AppCurrentWindowInternalShowFunction
+ : public AppCurrentWindowInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("app.currentWindowInternal.show",
+ APP_CURRENTWINDOWINTERNAL_SHOW)
+
+ protected:
+ ~AppCurrentWindowInternalShowFunction() override {}
+ bool RunWithWindow(AppWindow* window) override;
+};
+
+class AppCurrentWindowInternalHideFunction
+ : public AppCurrentWindowInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("app.currentWindowInternal.hide",
+ APP_CURRENTWINDOWINTERNAL_HIDE)
+
+ protected:
+ ~AppCurrentWindowInternalHideFunction() override {}
+ bool RunWithWindow(AppWindow* window) override;
+};
+
+class AppCurrentWindowInternalSetBoundsFunction
+ : public AppCurrentWindowInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("app.currentWindowInternal.setBounds",
+ APP_CURRENTWINDOWINTERNAL_SETBOUNDS)
+ protected:
+ ~AppCurrentWindowInternalSetBoundsFunction() override {}
+ bool RunWithWindow(AppWindow* window) override;
+};
+
+class AppCurrentWindowInternalSetSizeConstraintsFunction
+ : public AppCurrentWindowInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("app.currentWindowInternal.setSizeConstraints",
+ APP_CURRENTWINDOWINTERNAL_SETSIZECONSTRAINTS)
+ protected:
+ ~AppCurrentWindowInternalSetSizeConstraintsFunction() override {}
+ bool RunWithWindow(AppWindow* window) override;
+};
+
+class AppCurrentWindowInternalSetIconFunction
+ : public AppCurrentWindowInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("app.currentWindowInternal.setIcon",
+ APP_CURRENTWINDOWINTERNAL_SETICON)
+
+ protected:
+ ~AppCurrentWindowInternalSetIconFunction() override {}
+ bool RunWithWindow(AppWindow* window) override;
+};
+
+class AppCurrentWindowInternalSetShapeFunction
+ : public AppCurrentWindowInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("app.currentWindowInternal.setShape",
+ APP_CURRENTWINDOWINTERNAL_SETSHAPE)
+
+ protected:
+ ~AppCurrentWindowInternalSetShapeFunction() override {}
+ bool RunWithWindow(AppWindow* window) override;
+};
+
+class AppCurrentWindowInternalSetAlwaysOnTopFunction
+ : public AppCurrentWindowInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("app.currentWindowInternal.setAlwaysOnTop",
+ APP_CURRENTWINDOWINTERNAL_SETALWAYSONTOP)
+
+ protected:
+ ~AppCurrentWindowInternalSetAlwaysOnTopFunction() override {}
+ bool RunWithWindow(AppWindow* window) override;
+};
+
+class AppCurrentWindowInternalSetVisibleOnAllWorkspacesFunction
+ : public AppCurrentWindowInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION(
+ "app.currentWindowInternal.setVisibleOnAllWorkspaces",
+ APP_CURRENTWINDOWINTERNAL_SETVISIBLEONALLWORKSPACES)
+
+ protected:
+ ~AppCurrentWindowInternalSetVisibleOnAllWorkspacesFunction() override {}
+ bool RunWithWindow(AppWindow* window) override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_APP_CURRENT_WINDOW_INTERNAL_APP_CURRENT_WINDOW_INTERNAL_API_H_
diff --git a/chromium/extensions/browser/api/app_runtime/app_runtime_api.cc b/chromium/extensions/browser/api/app_runtime/app_runtime_api.cc
new file mode 100644
index 00000000000..fca709bd009
--- /dev/null
+++ b/chromium/extensions/browser/api/app_runtime/app_runtime_api.cc
@@ -0,0 +1,216 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/app_runtime/app_runtime_api.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/metrics/histogram.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "extensions/browser/entry_info.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/granted_file_entry.h"
+#include "extensions/common/api/app_runtime.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/feature_switch.h"
+#include "url/gurl.h"
+
+using content::BrowserContext;
+
+namespace extensions {
+
+namespace app_runtime = api::app_runtime;
+
+namespace {
+
+void DispatchOnEmbedRequestedEventImpl(
+ const std::string& extension_id,
+ scoped_ptr<base::DictionaryValue> app_embedding_request_data,
+ content::BrowserContext* context) {
+ scoped_ptr<base::ListValue> args(new base::ListValue());
+ args->Append(app_embedding_request_data.release());
+ scoped_ptr<Event> event(new Event(events::APP_RUNTIME_ON_EMBED_REQUESTED,
+ app_runtime::OnEmbedRequested::kEventName,
+ std::move(args)));
+ event->restrict_to_browser_context = context;
+ EventRouter::Get(context)
+ ->DispatchEventWithLazyListener(extension_id, std::move(event));
+
+ ExtensionPrefs::Get(context)
+ ->SetLastLaunchTime(extension_id, base::Time::Now());
+}
+
+void DispatchOnLaunchedEventImpl(const std::string& extension_id,
+ app_runtime::LaunchSource source,
+ scoped_ptr<base::DictionaryValue> launch_data,
+ BrowserContext* context) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "Extensions.AppLaunchSource", source, NUM_APP_LAUNCH_SOURCES);
+
+ // "Forced app mode" is true for Chrome OS kiosk mode.
+ launch_data->SetBoolean(
+ "isKioskSession",
+ ExtensionsBrowserClient::Get()->IsRunningInForcedAppMode());
+
+ launch_data->SetBoolean(
+ "isPublicSession",
+ ExtensionsBrowserClient::Get()->IsLoggedInAsPublicAccount());
+
+ scoped_ptr<base::ListValue> args(new base::ListValue());
+ args->Append(launch_data.release());
+ scoped_ptr<Event> event(new Event(events::APP_RUNTIME_ON_LAUNCHED,
+ app_runtime::OnLaunched::kEventName,
+ std::move(args)));
+ event->restrict_to_browser_context = context;
+ EventRouter::Get(context)
+ ->DispatchEventWithLazyListener(extension_id, std::move(event));
+ ExtensionPrefs::Get(context)
+ ->SetLastLaunchTime(extension_id, base::Time::Now());
+}
+
+app_runtime::LaunchSource getLaunchSourceEnum(
+ extensions::AppLaunchSource source) {
+ switch (source) {
+ case extensions::SOURCE_APP_LAUNCHER:
+ return app_runtime::LAUNCH_SOURCE_APP_LAUNCHER;
+ case extensions::SOURCE_NEW_TAB_PAGE:
+ return app_runtime::LAUNCH_SOURCE_NEW_TAB_PAGE;
+ case extensions::SOURCE_RELOAD:
+ return app_runtime::LAUNCH_SOURCE_RELOAD;
+ case extensions::SOURCE_RESTART:
+ return app_runtime::LAUNCH_SOURCE_RESTART;
+ case extensions::SOURCE_LOAD_AND_LAUNCH:
+ return app_runtime::LAUNCH_SOURCE_LOAD_AND_LAUNCH;
+ case extensions::SOURCE_COMMAND_LINE:
+ return app_runtime::LAUNCH_SOURCE_COMMAND_LINE;
+ case extensions::SOURCE_FILE_HANDLER:
+ return app_runtime::LAUNCH_SOURCE_FILE_HANDLER;
+ case extensions::SOURCE_URL_HANDLER:
+ return app_runtime::LAUNCH_SOURCE_URL_HANDLER;
+ case extensions::SOURCE_SYSTEM_TRAY:
+ return app_runtime::LAUNCH_SOURCE_SYSTEM_TRAY;
+ case extensions::SOURCE_ABOUT_PAGE:
+ return app_runtime::LAUNCH_SOURCE_ABOUT_PAGE;
+ case extensions::SOURCE_KEYBOARD:
+ return app_runtime::LAUNCH_SOURCE_KEYBOARD;
+ case extensions::SOURCE_EXTENSIONS_PAGE:
+ return app_runtime::LAUNCH_SOURCE_EXTENSIONS_PAGE;
+ case extensions::SOURCE_MANAGEMENT_API:
+ return app_runtime::LAUNCH_SOURCE_MANAGEMENT_API;
+ case extensions::SOURCE_EPHEMERAL_APP_DEPRECATED:
+ return app_runtime::LAUNCH_SOURCE_EPHEMERAL_APP;
+ case extensions::SOURCE_BACKGROUND:
+ return app_runtime::LAUNCH_SOURCE_BACKGROUND;
+ case extensions::SOURCE_KIOSK:
+ return app_runtime::LAUNCH_SOURCE_KIOSK;
+ case extensions::SOURCE_CHROME_INTERNAL:
+ return app_runtime::LAUNCH_SOURCE_CHROME_INTERNAL;
+ case extensions::SOURCE_TEST:
+ return app_runtime::LAUNCH_SOURCE_TEST;
+
+ default:
+ return app_runtime::LAUNCH_SOURCE_NONE;
+ }
+}
+
+} // namespace
+
+// static
+void AppRuntimeEventRouter::DispatchOnEmbedRequestedEvent(
+ content::BrowserContext* context,
+ scoped_ptr<base::DictionaryValue> embed_app_data,
+ const Extension* extension) {
+ DispatchOnEmbedRequestedEventImpl(extension->id(), std::move(embed_app_data),
+ context);
+}
+
+// static
+void AppRuntimeEventRouter::DispatchOnLaunchedEvent(
+ BrowserContext* context,
+ const Extension* extension,
+ extensions::AppLaunchSource source) {
+ app_runtime::LaunchData launch_data;
+
+ app_runtime::LaunchSource source_enum = getLaunchSourceEnum(source);
+ if (extensions::FeatureSwitch::trace_app_source()->IsEnabled()) {
+ launch_data.source = source_enum;
+ }
+ DispatchOnLaunchedEventImpl(extension->id(), source_enum,
+ launch_data.ToValue(), context);
+}
+
+// static
+void AppRuntimeEventRouter::DispatchOnRestartedEvent(
+ BrowserContext* context,
+ const Extension* extension) {
+ scoped_ptr<base::ListValue> arguments(new base::ListValue());
+ scoped_ptr<Event> event(new Event(events::APP_RUNTIME_ON_RESTARTED,
+ app_runtime::OnRestarted::kEventName,
+ std::move(arguments)));
+ event->restrict_to_browser_context = context;
+ EventRouter::Get(context)
+ ->DispatchEventToExtension(extension->id(), std::move(event));
+}
+
+// static
+void AppRuntimeEventRouter::DispatchOnLaunchedEventWithFileEntries(
+ BrowserContext* context,
+ const Extension* extension,
+ const std::string& handler_id,
+ const std::vector<EntryInfo>& entries,
+ const std::vector<GrantedFileEntry>& file_entries) {
+ // TODO(sergeygs): Use the same way of creating an event (using the generated
+ // boilerplate) as below in DispatchOnLaunchedEventWithUrl.
+ scoped_ptr<base::DictionaryValue> launch_data(new base::DictionaryValue);
+ launch_data->SetString("id", handler_id);
+
+ app_runtime::LaunchSource source_enum =
+ app_runtime::LAUNCH_SOURCE_FILE_HANDLER;
+ if (extensions::FeatureSwitch::trace_app_source()->IsEnabled()) {
+ launch_data->SetString("source", app_runtime::ToString(source_enum));
+ }
+
+ scoped_ptr<base::ListValue> items(new base::ListValue);
+ DCHECK(file_entries.size() == entries.size());
+ for (size_t i = 0; i < file_entries.size(); ++i) {
+ scoped_ptr<base::DictionaryValue> launch_item(new base::DictionaryValue);
+
+ launch_item->SetString("fileSystemId", file_entries[i].filesystem_id);
+ launch_item->SetString("baseName", file_entries[i].registered_name);
+ launch_item->SetString("mimeType", entries[i].mime_type);
+ launch_item->SetString("entryId", file_entries[i].id);
+ launch_item->SetBoolean("isDirectory", entries[i].is_directory);
+ items->Append(launch_item.release());
+ }
+ launch_data->Set("items", items.release());
+ DispatchOnLaunchedEventImpl(extension->id(), source_enum,
+ std::move(launch_data), context);
+}
+
+// static
+void AppRuntimeEventRouter::DispatchOnLaunchedEventWithUrl(
+ BrowserContext* context,
+ const Extension* extension,
+ const std::string& handler_id,
+ const GURL& url,
+ const GURL& referrer_url) {
+ app_runtime::LaunchData launch_data;
+ app_runtime::LaunchSource source_enum =
+ app_runtime::LAUNCH_SOURCE_URL_HANDLER;
+ launch_data.id.reset(new std::string(handler_id));
+ launch_data.url.reset(new std::string(url.spec()));
+ launch_data.referrer_url.reset(new std::string(referrer_url.spec()));
+ if (extensions::FeatureSwitch::trace_app_source()->IsEnabled()) {
+ launch_data.source = source_enum;
+ }
+ DispatchOnLaunchedEventImpl(extension->id(), source_enum,
+ launch_data.ToValue(), context);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/app_runtime/app_runtime_api.h b/chromium/extensions/browser/api/app_runtime/app_runtime_api.h
new file mode 100644
index 00000000000..35a28d22f60
--- /dev/null
+++ b/chromium/extensions/browser/api/app_runtime/app_runtime_api.h
@@ -0,0 +1,82 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_APP_RUNTIME_APP_RUNTIME_API_H_
+#define EXTENSIONS_BROWSER_API_APP_RUNTIME_APP_RUNTIME_API_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/constants.h"
+
+class GURL;
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace content {
+class BrowserContext;
+class WebContents;
+}
+
+namespace extensions {
+
+class Extension;
+struct EntryInfo;
+struct GrantedFileEntry;
+
+class AppRuntimeEventRouter {
+ public:
+ // Dispatches the onEmbedRequested event to the given app.
+ static void DispatchOnEmbedRequestedEvent(
+ content::BrowserContext* context,
+ scoped_ptr<base::DictionaryValue> app_embedding_request_data,
+ const extensions::Extension* extension);
+
+ // Dispatches the onLaunched event to the given app.
+ static void DispatchOnLaunchedEvent(content::BrowserContext* context,
+ const Extension* extension,
+ extensions::AppLaunchSource source);
+
+ // Dispatches the onRestarted event to the given app, providing a list of
+ // restored file entries from the previous run.
+ static void DispatchOnRestartedEvent(content::BrowserContext* context,
+ const Extension* extension);
+
+ // TODO(benwells): Update this comment, it is out of date.
+ // Dispatches the onLaunched event to the given app, providing launch data of
+ // the form:
+ // {
+ // "intent" : {
+ // "type" : "chrome-extension://fileentry",
+ // "data" : a FileEntry,
+ // "postResults" : a null function,
+ // "postFailure" : a null function
+ // }
+ // }
+
+ // The FileEntries are created from |file_system_id| and |base_name|.
+ // |handler_id| corresponds to the id of the file_handlers item in the
+ // manifest that resulted in a match which triggered this launch.
+ static void DispatchOnLaunchedEventWithFileEntries(
+ content::BrowserContext* context,
+ const Extension* extension,
+ const std::string& handler_id,
+ const std::vector<EntryInfo>& entries,
+ const std::vector<GrantedFileEntry>& file_entries);
+
+ // |handler_id| corresponds to the id of the url_handlers item
+ // in the manifest that resulted in a match which triggered this launch.
+ static void DispatchOnLaunchedEventWithUrl(content::BrowserContext* context,
+ const Extension* extension,
+ const std::string& handler_id,
+ const GURL& url,
+ const GURL& referrer_url);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_APP_RUNTIME_APP_RUNTIME_API_H_
diff --git a/chromium/extensions/browser/api/app_window/OWNERS b/chromium/extensions/browser/api/app_window/OWNERS
new file mode 100644
index 00000000000..5caf038be1a
--- /dev/null
+++ b/chromium/extensions/browser/api/app_window/OWNERS
@@ -0,0 +1,3 @@
+benwells@chromium.org
+calamity@chromium.org
+tapted@chromium.org
diff --git a/chromium/extensions/browser/api/app_window/app_window_api.cc b/chromium/extensions/browser/api/app_window/app_window_api.cc
new file mode 100644
index 00000000000..4f5ffa551fe
--- /dev/null
+++ b/chromium/extensions/browser/api/app_window/app_window_api.cc
@@ -0,0 +1,558 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/app_window/app_window_api.h"
+
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/browser_side_navigation_policy.h"
+#include "content/public/common/url_constants.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/app_window_client.h"
+#include "extensions/browser/app_window/app_window_contents.h"
+#include "extensions/browser/app_window/app_window_registry.h"
+#include "extensions/browser/app_window/native_app_window.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/common/api/app_window.h"
+#include "extensions/common/features/simple_feature.h"
+#include "extensions/common/image_util.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/switches.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/gfx/geometry/rect.h"
+#include "url/gurl.h"
+
+namespace app_window = extensions::api::app_window;
+namespace Create = app_window::Create;
+
+namespace extensions {
+
+namespace app_window_constants {
+const char kInvalidWindowId[] =
+ "The window id can not be more than 256 characters long.";
+const char kInvalidColorSpecification[] =
+ "The color specification could not be parsed.";
+const char kColorWithFrameNone[] = "Windows with no frame cannot have a color.";
+const char kInactiveColorWithoutColor[] =
+ "frame.inactiveColor must be used with frame.color.";
+const char kConflictingBoundsOptions[] =
+ "The $1 property cannot be specified for both inner and outer bounds.";
+const char kAlwaysOnTopPermission[] =
+ "The \"app.window.alwaysOnTop\" permission is required.";
+const char kInvalidUrlParameter[] =
+ "The URL used for window creation must be local for security reasons.";
+const char kAlphaEnabledWrongChannel[] =
+ "The alphaEnabled option requires dev channel or newer.";
+const char kAlphaEnabledMissingPermission[] =
+ "The alphaEnabled option requires app.window.alpha permission.";
+const char kAlphaEnabledNeedsFrameNone[] =
+ "The alphaEnabled option can only be used with \"frame: 'none'\".";
+const char kImeWindowMissingPermission[] =
+ "Extensions require the \"app.window.ime\" permission to create windows.";
+const char kImeOptionIsNotSupported[] =
+ "The \"ime\" option is not supported for platform app.";
+#if !defined(OS_CHROMEOS)
+const char kImeWindowUnsupportedPlatform[] =
+ "The \"ime\" option can only be used on ChromeOS.";
+#else
+const char kImeWindowMustBeImeWindowOrPanel[] =
+ "IME extensions must create ime window ( with \"ime: true\" and "
+ "\"frame: 'none'\") or panel window (with \"type: panel\").";
+#endif
+} // namespace app_window_constants
+
+const char kNoneFrameOption[] = "none";
+
+namespace {
+
+// If the same property is specified for the inner and outer bounds, raise an
+// error.
+bool CheckBoundsConflict(const scoped_ptr<int>& inner_property,
+ const scoped_ptr<int>& outer_property,
+ const std::string& property_name,
+ std::string* error) {
+ if (inner_property.get() && outer_property.get()) {
+ std::vector<std::string> subst;
+ subst.push_back(property_name);
+ *error = base::ReplaceStringPlaceholders(
+ app_window_constants::kConflictingBoundsOptions, subst, NULL);
+ return false;
+ }
+
+ return true;
+}
+
+// Copy over the bounds specification properties from the API to the
+// AppWindow::CreateParams.
+void CopyBoundsSpec(const app_window::BoundsSpecification* input_spec,
+ AppWindow::BoundsSpecification* create_spec) {
+ if (!input_spec)
+ return;
+
+ if (input_spec->left.get())
+ create_spec->bounds.set_x(*input_spec->left);
+ if (input_spec->top.get())
+ create_spec->bounds.set_y(*input_spec->top);
+ if (input_spec->width.get())
+ create_spec->bounds.set_width(*input_spec->width);
+ if (input_spec->height.get())
+ create_spec->bounds.set_height(*input_spec->height);
+ if (input_spec->min_width.get())
+ create_spec->minimum_size.set_width(*input_spec->min_width);
+ if (input_spec->min_height.get())
+ create_spec->minimum_size.set_height(*input_spec->min_height);
+ if (input_spec->max_width.get())
+ create_spec->maximum_size.set_width(*input_spec->max_width);
+ if (input_spec->max_height.get())
+ create_spec->maximum_size.set_height(*input_spec->max_height);
+}
+
+} // namespace
+
+AppWindowCreateFunction::AppWindowCreateFunction() {}
+
+bool AppWindowCreateFunction::RunAsync() {
+ // Don't create app window if the system is shutting down.
+ if (ExtensionsBrowserClient::Get()->IsShuttingDown())
+ return false;
+
+ scoped_ptr<Create::Params> params(Create::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ GURL url = extension()->GetResourceURL(params->url);
+ // Allow absolute URLs for component apps, otherwise prepend the extension
+ // path.
+ // TODO(devlin): Investigate if this is still used. If not, kill it dead!
+ GURL absolute = GURL(params->url);
+ if (absolute.has_scheme()) {
+ if (extension()->location() == Manifest::COMPONENT) {
+ url = absolute;
+ } else {
+ // Show error when url passed isn't local.
+ error_ = app_window_constants::kInvalidUrlParameter;
+ return false;
+ }
+ }
+
+ // TODO(jeremya): figure out a way to pass the opening WebContents through to
+ // AppWindow::Create so we can set the opener at create time rather than
+ // with a hack in AppWindowCustomBindings::GetView().
+ AppWindow::CreateParams create_params;
+ app_window::CreateWindowOptions* options = params->options.get();
+ if (options) {
+ if (options->id.get()) {
+ // TODO(mek): use URL if no id specified?
+ // Limit length of id to 256 characters.
+ if (options->id->length() > 256) {
+ error_ = app_window_constants::kInvalidWindowId;
+ return false;
+ }
+
+ create_params.window_key = *options->id;
+
+ if (options->singleton && *options->singleton == false) {
+ WriteToConsole(
+ content::CONSOLE_MESSAGE_LEVEL_WARNING,
+ "The 'singleton' option in chrome.apps.window.create() is deprecated!"
+ " Change your code to no longer rely on this.");
+ }
+
+ if (!options->singleton || *options->singleton) {
+ AppWindow* existing_window =
+ AppWindowRegistry::Get(browser_context())
+ ->GetAppWindowForAppAndKey(extension_id(),
+ create_params.window_key);
+ if (existing_window) {
+ content::RenderFrameHost* existing_frame =
+ existing_window->web_contents()->GetMainFrame();
+ int frame_id = MSG_ROUTING_NONE;
+ if (render_frame_host()->GetProcess()->GetID() ==
+ existing_frame->GetProcess()->GetID()) {
+ frame_id = existing_frame->GetRoutingID();
+ }
+
+ if (!options->hidden.get() || !*options->hidden.get()) {
+ if (options->focused.get() && !*options->focused.get())
+ existing_window->Show(AppWindow::SHOW_INACTIVE);
+ else
+ existing_window->Show(AppWindow::SHOW_ACTIVE);
+ }
+
+ base::DictionaryValue* result = new base::DictionaryValue;
+ result->Set("frameId", new base::FundamentalValue(frame_id));
+ existing_window->GetSerializedState(result);
+ result->SetBoolean("existingWindow", true);
+ SetResult(result);
+ SendResponse(true);
+ return true;
+ }
+ }
+ }
+
+ if (!GetBoundsSpec(*options, &create_params, &error_))
+ return false;
+
+ if (options->type == app_window::WINDOW_TYPE_PANEL) {
+#if defined(OS_CHROMEOS)
+ // Panels for v2 apps are only supported on Chrome OS.
+ create_params.window_type = AppWindow::WINDOW_TYPE_PANEL;
+#else
+ WriteToConsole(content::CONSOLE_MESSAGE_LEVEL_WARNING,
+ "Panels are not supported on this platform");
+#endif
+ }
+
+ if (!GetFrameOptions(*options, &create_params))
+ return false;
+
+ if (extension()->GetType() == Manifest::TYPE_EXTENSION) {
+ // Whitelisted IME extensions are allowed to use this API to create IME
+ // specific windows to show accented characters or suggestions.
+ if (!extension()->permissions_data()->HasAPIPermission(
+ APIPermission::kImeWindowEnabled)) {
+ error_ = app_window_constants::kImeWindowMissingPermission;
+ return false;
+ }
+
+#if !defined(OS_CHROMEOS)
+ // IME window is only supported on ChromeOS.
+ error_ = app_window_constants::kImeWindowUnsupportedPlatform;
+ return false;
+#else
+ // IME extensions must create ime window (with "ime: true" and
+ // "frame: none") or panel window (with "type: panel").
+ if (options->ime.get() && *options->ime.get() &&
+ create_params.frame == AppWindow::FRAME_NONE) {
+ create_params.is_ime_window = true;
+ } else if (options->type == app_window::WINDOW_TYPE_PANEL) {
+ create_params.window_type = AppWindow::WINDOW_TYPE_PANEL;
+ } else {
+ error_ = app_window_constants::kImeWindowMustBeImeWindowOrPanel;
+ return false;
+ }
+#endif // OS_CHROMEOS
+ } else {
+ if (options->ime.get()) {
+ error_ = app_window_constants::kImeOptionIsNotSupported;
+ return false;
+ }
+ }
+
+ if (options->alpha_enabled.get()) {
+ const char* const kWhitelist[] = {
+#if defined(OS_CHROMEOS)
+ "B58B99751225318C7EB8CF4688B5434661083E07", // http://crbug.com/410550
+ "06BE211D5F014BAB34BC22D9DDA09C63A81D828E", // http://crbug.com/425539
+ "F94EE6AB36D6C6588670B2B01EB65212D9C64E33",
+ "B9EF10DDFEA11EF77873CC5009809E5037FC4C7A", // http://crbug.com/435380
+#endif
+ "0F42756099D914A026DADFA182871C015735DD95", // http://crbug.com/323773
+ "2D22CDB6583FD0A13758AEBE8B15E45208B4E9A7",
+ "E7E2461CE072DF036CF9592740196159E2D7C089", // http://crbug.com/356200
+ "A74A4D44C7CFCD8844830E6140C8D763E12DD8F3",
+ "312745D9BF916161191143F6490085EEA0434997",
+ "53041A2FA309EECED01FFC751E7399186E860B2C",
+ "A07A5B743CD82A1C2579DB77D353C98A23201EEF", // http://crbug.com/413748
+ "F16F23C83C5F6DAD9B65A120448B34056DD80691",
+ "0F585FB1D0FDFBEBCE1FEB5E9DFFB6DA476B8C9B"
+ };
+ if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev() &&
+ !SimpleFeature::IsIdInArray(
+ extension_id(), kWhitelist, arraysize(kWhitelist))) {
+ error_ = app_window_constants::kAlphaEnabledWrongChannel;
+ return false;
+ }
+ if (!extension()->permissions_data()->HasAPIPermission(
+ APIPermission::kAlphaEnabled)) {
+ error_ = app_window_constants::kAlphaEnabledMissingPermission;
+ return false;
+ }
+ if (create_params.frame != AppWindow::FRAME_NONE) {
+ error_ = app_window_constants::kAlphaEnabledNeedsFrameNone;
+ return false;
+ }
+#if defined(USE_AURA)
+ create_params.alpha_enabled = *options->alpha_enabled;
+#else
+ // Transparency is only supported on Aura.
+ // Fallback to creating an opaque window (by ignoring alphaEnabled).
+#endif
+ }
+
+ if (options->hidden.get())
+ create_params.hidden = *options->hidden.get();
+
+ if (options->resizable.get())
+ create_params.resizable = *options->resizable.get();
+
+ if (options->always_on_top.get()) {
+ create_params.always_on_top = *options->always_on_top.get();
+
+ if (create_params.always_on_top &&
+ !extension()->permissions_data()->HasAPIPermission(
+ APIPermission::kAlwaysOnTopWindows)) {
+ error_ = app_window_constants::kAlwaysOnTopPermission;
+ return false;
+ }
+ }
+
+ if (options->focused.get())
+ create_params.focused = *options->focused.get();
+
+ if (options->visible_on_all_workspaces.get()) {
+ create_params.visible_on_all_workspaces =
+ *options->visible_on_all_workspaces.get();
+ }
+
+ if (options->type != app_window::WINDOW_TYPE_PANEL) {
+ switch (options->state) {
+ case app_window::STATE_NONE:
+ case app_window::STATE_NORMAL:
+ break;
+ case app_window::STATE_FULLSCREEN:
+ create_params.state = ui::SHOW_STATE_FULLSCREEN;
+ break;
+ case app_window::STATE_MAXIMIZED:
+ create_params.state = ui::SHOW_STATE_MAXIMIZED;
+ break;
+ case app_window::STATE_MINIMIZED:
+ create_params.state = ui::SHOW_STATE_MINIMIZED;
+ break;
+ }
+ }
+ }
+
+ create_params.creator_process_id =
+ render_frame_host()->GetProcess()->GetID();
+
+ AppWindow* app_window =
+ AppWindowClient::Get()->CreateAppWindow(browser_context(), extension());
+ app_window->Init(url, new AppWindowContentsImpl(app_window),
+ render_frame_host(), create_params);
+
+ if (ExtensionsBrowserClient::Get()->IsRunningInForcedAppMode() &&
+ !app_window->is_ime_window()) {
+ app_window->ForcedFullscreen();
+ }
+
+ content::RenderFrameHost* created_frame =
+ app_window->web_contents()->GetMainFrame();
+ int frame_id = MSG_ROUTING_NONE;
+ if (create_params.creator_process_id == created_frame->GetProcess()->GetID())
+ frame_id = created_frame->GetRoutingID();
+
+ base::DictionaryValue* result = new base::DictionaryValue;
+ result->Set("frameId", new base::FundamentalValue(frame_id));
+ result->Set("id", new base::StringValue(app_window->window_key()));
+ app_window->GetSerializedState(result);
+ SetResult(result);
+
+ if (AppWindowRegistry::Get(browser_context())
+ ->HadDevToolsAttached(app_window->web_contents())) {
+ AppWindowClient::Get()->OpenDevToolsWindow(
+ app_window->web_contents(),
+ base::Bind(&AppWindowCreateFunction::SendResponse, this, true));
+ return true;
+ }
+
+ // PlzNavigate: delay sending the response until the newly created window has
+ // been told to navigate, and blink has been correctly initialized in the
+ // renderer.
+ if (content::IsBrowserSideNavigationEnabled()) {
+ app_window->SetOnFirstCommitCallback(
+ base::Bind(&AppWindowCreateFunction::SendResponse, this, true));
+ return true;
+ }
+
+ SendResponse(true);
+ app_window->WindowEventsReady();
+
+ return true;
+}
+
+bool AppWindowCreateFunction::GetBoundsSpec(
+ const app_window::CreateWindowOptions& options,
+ AppWindow::CreateParams* params,
+ std::string* error) {
+ DCHECK(params);
+ DCHECK(error);
+
+ if (options.inner_bounds.get() || options.outer_bounds.get()) {
+ // Parse the inner and outer bounds specifications. If developers use the
+ // new API, the deprecated fields will be ignored - do not attempt to merge
+ // them.
+
+ const app_window::BoundsSpecification* inner_bounds =
+ options.inner_bounds.get();
+ const app_window::BoundsSpecification* outer_bounds =
+ options.outer_bounds.get();
+ if (inner_bounds && outer_bounds) {
+ if (!CheckBoundsConflict(
+ inner_bounds->left, outer_bounds->left, "left", error)) {
+ return false;
+ }
+ if (!CheckBoundsConflict(
+ inner_bounds->top, outer_bounds->top, "top", error)) {
+ return false;
+ }
+ if (!CheckBoundsConflict(
+ inner_bounds->width, outer_bounds->width, "width", error)) {
+ return false;
+ }
+ if (!CheckBoundsConflict(
+ inner_bounds->height, outer_bounds->height, "height", error)) {
+ return false;
+ }
+ if (!CheckBoundsConflict(inner_bounds->min_width,
+ outer_bounds->min_width,
+ "minWidth",
+ error)) {
+ return false;
+ }
+ if (!CheckBoundsConflict(inner_bounds->min_height,
+ outer_bounds->min_height,
+ "minHeight",
+ error)) {
+ return false;
+ }
+ if (!CheckBoundsConflict(inner_bounds->max_width,
+ outer_bounds->max_width,
+ "maxWidth",
+ error)) {
+ return false;
+ }
+ if (!CheckBoundsConflict(inner_bounds->max_height,
+ outer_bounds->max_height,
+ "maxHeight",
+ error)) {
+ return false;
+ }
+ }
+
+ CopyBoundsSpec(inner_bounds, &(params->content_spec));
+ CopyBoundsSpec(outer_bounds, &(params->window_spec));
+ } else {
+ // Parse deprecated fields.
+ // Due to a bug in NativeAppWindow::GetFrameInsets() on Windows and ChromeOS
+ // the bounds set the position of the window and the size of the content.
+ // This will be preserved as apps may be relying on this behavior.
+
+ if (options.default_width.get())
+ params->content_spec.bounds.set_width(*options.default_width.get());
+ if (options.default_height.get())
+ params->content_spec.bounds.set_height(*options.default_height.get());
+ if (options.default_left.get())
+ params->window_spec.bounds.set_x(*options.default_left.get());
+ if (options.default_top.get())
+ params->window_spec.bounds.set_y(*options.default_top.get());
+
+ if (options.width.get())
+ params->content_spec.bounds.set_width(*options.width.get());
+ if (options.height.get())
+ params->content_spec.bounds.set_height(*options.height.get());
+ if (options.left.get())
+ params->window_spec.bounds.set_x(*options.left.get());
+ if (options.top.get())
+ params->window_spec.bounds.set_y(*options.top.get());
+
+ if (options.bounds.get()) {
+ app_window::ContentBounds* bounds = options.bounds.get();
+ if (bounds->width.get())
+ params->content_spec.bounds.set_width(*bounds->width.get());
+ if (bounds->height.get())
+ params->content_spec.bounds.set_height(*bounds->height.get());
+ if (bounds->left.get())
+ params->window_spec.bounds.set_x(*bounds->left.get());
+ if (bounds->top.get())
+ params->window_spec.bounds.set_y(*bounds->top.get());
+ }
+
+ gfx::Size& minimum_size = params->content_spec.minimum_size;
+ if (options.min_width.get())
+ minimum_size.set_width(*options.min_width);
+ if (options.min_height.get())
+ minimum_size.set_height(*options.min_height);
+ gfx::Size& maximum_size = params->content_spec.maximum_size;
+ if (options.max_width.get())
+ maximum_size.set_width(*options.max_width);
+ if (options.max_height.get())
+ maximum_size.set_height(*options.max_height);
+ }
+
+ return true;
+}
+
+AppWindow::Frame AppWindowCreateFunction::GetFrameFromString(
+ const std::string& frame_string) {
+ if (frame_string == kNoneFrameOption)
+ return AppWindow::FRAME_NONE;
+
+ return AppWindow::FRAME_CHROME;
+}
+
+bool AppWindowCreateFunction::GetFrameOptions(
+ const app_window::CreateWindowOptions& options,
+ AppWindow::CreateParams* create_params) {
+ if (!options.frame)
+ return true;
+
+ DCHECK(options.frame->as_string || options.frame->as_frame_options);
+ if (options.frame->as_string) {
+ create_params->frame = GetFrameFromString(*options.frame->as_string);
+ return true;
+ }
+
+ if (options.frame->as_frame_options->type)
+ create_params->frame =
+ GetFrameFromString(*options.frame->as_frame_options->type);
+
+ if (options.frame->as_frame_options->color.get()) {
+ if (create_params->frame != AppWindow::FRAME_CHROME) {
+ error_ = app_window_constants::kColorWithFrameNone;
+ return false;
+ }
+
+ if (!image_util::ParseHexColorString(
+ *options.frame->as_frame_options->color,
+ &create_params->active_frame_color)) {
+ error_ = app_window_constants::kInvalidColorSpecification;
+ return false;
+ }
+
+ create_params->has_frame_color = true;
+ create_params->inactive_frame_color = create_params->active_frame_color;
+
+ if (options.frame->as_frame_options->inactive_color.get()) {
+ if (!image_util::ParseHexColorString(
+ *options.frame->as_frame_options->inactive_color,
+ &create_params->inactive_frame_color)) {
+ error_ = app_window_constants::kInvalidColorSpecification;
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ if (options.frame->as_frame_options->inactive_color.get()) {
+ error_ = app_window_constants::kInactiveColorWithoutColor;
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/app_window/app_window_api.h b/chromium/extensions/browser/api/app_window/app_window_api.h
new file mode 100644
index 00000000000..f10ec989b49
--- /dev/null
+++ b/chromium/extensions/browser/api/app_window/app_window_api.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_APP_WINDOW_APP_WINDOW_API_H_
+#define EXTENSIONS_BROWSER_API_APP_WINDOW_APP_WINDOW_API_H_
+
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/extension_function.h"
+
+namespace extensions {
+
+namespace api {
+namespace app_window {
+struct CreateWindowOptions;
+}
+}
+
+class AppWindowCreateFunction : public AsyncExtensionFunction {
+ public:
+ AppWindowCreateFunction();
+ DECLARE_EXTENSION_FUNCTION("app.window.create", APP_WINDOW_CREATE)
+
+ protected:
+ ~AppWindowCreateFunction() override {}
+ bool RunAsync() override;
+
+ private:
+ bool GetBoundsSpec(
+ const extensions::api::app_window::CreateWindowOptions& options,
+ AppWindow::CreateParams* params,
+ std::string* error);
+
+ AppWindow::Frame GetFrameFromString(const std::string& frame_string);
+ bool GetFrameOptions(
+ const extensions::api::app_window::CreateWindowOptions& options,
+ AppWindow::CreateParams* create_params);
+ void UpdateFrameOptionsForChannel(AppWindow::CreateParams* create_params);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_APP_WINDOW_APP_WINDOW_API_H_
diff --git a/chromium/extensions/browser/api/app_window/app_window_apitest.cc b/chromium/extensions/browser/api/app_window/app_window_apitest.cc
new file mode 100644
index 00000000000..b03fde448bf
--- /dev/null
+++ b/chromium/extensions/browser/api/app_window/app_window_apitest.cc
@@ -0,0 +1,219 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "build/build_config.h"
+#include "chrome/browser/apps/app_browsertest_util.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/features/feature_channel.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/version_info/version_info.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/app_window_registry.h"
+#include "extensions/browser/app_window/native_app_window.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "ui/base/base_window.h"
+#include "ui/gfx/geometry/rect.h"
+
+#if defined(OS_WIN)
+#include "ui/base/win/shell.h"
+#endif
+
+namespace extensions {
+
+namespace {
+
+class TestAppWindowRegistryObserver : public AppWindowRegistry::Observer {
+ public:
+ explicit TestAppWindowRegistryObserver(Profile* profile)
+ : profile_(profile), icon_updates_(0) {
+ AppWindowRegistry::Get(profile_)->AddObserver(this);
+ }
+ ~TestAppWindowRegistryObserver() override {
+ AppWindowRegistry::Get(profile_)->RemoveObserver(this);
+ }
+
+ // Overridden from AppWindowRegistry::Observer:
+ void OnAppWindowIconChanged(AppWindow* app_window) override {
+ ++icon_updates_;
+ }
+
+ int icon_updates() { return icon_updates_; }
+
+ private:
+ Profile* profile_;
+ int icon_updates_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestAppWindowRegistryObserver);
+};
+
+} // namespace
+
+// Tests chrome.app.window.setIcon.
+IN_PROC_BROWSER_TEST_F(ExperimentalPlatformAppBrowserTest, WindowsApiSetIcon) {
+ scoped_ptr<TestAppWindowRegistryObserver> test_observer(
+ new TestAppWindowRegistryObserver(browser()->profile()));
+ ExtensionTestMessageListener listener("ready", true);
+
+ // Launch the app and wait for it to be ready.
+ LoadAndLaunchPlatformApp("windows_api_set_icon", &listener);
+ EXPECT_EQ(0, test_observer->icon_updates());
+ listener.Reply("");
+
+ // Now wait until the WebContent has decoded the icon and chrome has
+ // processed it. This needs to be in a loop since the renderer runs in a
+ // different process.
+ while (test_observer->icon_updates() < 1) {
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ }
+ AppWindow* app_window = GetFirstAppWindow();
+ ASSERT_TRUE(app_window);
+ EXPECT_NE(std::string::npos,
+ app_window->app_icon_url().spec().find("icon.png"));
+ EXPECT_EQ(1, test_observer->icon_updates());
+}
+
+// TODO(asargent) - Figure out what to do about the fact that minimize events
+// don't work under ubuntu unity.
+// (crbug.com/162794 and https://bugs.launchpad.net/unity/+bug/998073).
+// TODO(linux_aura) http://crbug.com/163931
+// Flaky on Mac, http://crbug.com/232330
+#if defined(TOOLKIT_VIEWS) && !(defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(USE_AURA))
+
+IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, WindowsApiProperties) {
+ EXPECT_TRUE(
+ RunExtensionTest("platform_apps/windows_api_properties")) << message_;
+}
+
+#endif // defined(TOOLKIT_VIEWS)
+
+IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest,
+ WindowsApiAlwaysOnTopWithPermissions) {
+ EXPECT_TRUE(RunPlatformAppTest(
+ "platform_apps/windows_api_always_on_top/has_permissions")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest,
+ WindowsApiAlwaysOnTopWithOldPermissions) {
+ EXPECT_TRUE(RunPlatformAppTest(
+ "platform_apps/windows_api_always_on_top/has_old_permissions"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest,
+ WindowsApiAlwaysOnTopNoPermissions) {
+ EXPECT_TRUE(RunPlatformAppTest(
+ "platform_apps/windows_api_always_on_top/no_permissions")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, WindowsApiGet) {
+ EXPECT_TRUE(RunPlatformAppTest("platform_apps/windows_api_get"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, WindowsApiSetShapeHasPerm) {
+ EXPECT_TRUE(
+ RunPlatformAppTest("platform_apps/windows_api_shape/has_permission"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, WindowsApiSetShapeNoPerm) {
+ EXPECT_TRUE(
+ RunPlatformAppTest("platform_apps/windows_api_shape/no_permission"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest,
+ WindowsApiAlphaEnabledHasPermissions) {
+ const char* no_alpha_dir =
+ "platform_apps/windows_api_alpha_enabled/has_permissions_no_alpha";
+ const char* test_dir = no_alpha_dir;
+
+#if defined(USE_AURA) && (defined(OS_CHROMEOS) || !defined(OS_LINUX))
+ test_dir =
+ "platform_apps/windows_api_alpha_enabled/has_permissions_has_alpha";
+#if defined(OS_WIN)
+ if (!ui::win::IsAeroGlassEnabled()) {
+ test_dir = no_alpha_dir;
+ }
+#endif // OS_WIN
+#endif // USE_AURA && (OS_CHROMEOS || !OS_LINUX)
+
+ EXPECT_TRUE(RunPlatformAppTest(test_dir)) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest,
+ WindowsApiAlphaEnabledNoPermissions) {
+ EXPECT_TRUE(RunPlatformAppTest(
+ "platform_apps/windows_api_alpha_enabled/no_permissions"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, WindowsApiAlphaEnabledInStable) {
+ extensions::ScopedCurrentChannel channel(version_info::Channel::STABLE);
+ EXPECT_TRUE(RunPlatformAppTestWithFlags(
+ "platform_apps/windows_api_alpha_enabled/in_stable",
+ // Ignore manifest warnings because the extension will not load at all
+ // in stable.
+ kFlagIgnoreManifestWarnings))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest,
+ WindowsApiAlphaEnabledWrongFrameType) {
+ EXPECT_TRUE(RunPlatformAppTest(
+ "platform_apps/windows_api_alpha_enabled/wrong_frame_type"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest,
+ WindowsApiVisibleOnAllWorkspacesInStable) {
+ extensions::ScopedCurrentChannel channel(version_info::Channel::STABLE);
+ EXPECT_TRUE(RunPlatformAppTest(
+ "platform_apps/windows_api_visible_on_all_workspaces/in_stable"))
+ << message_;
+}
+
+#if defined(OS_CHROMEOS)
+IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest,
+ WindowsApiImeWindowHasPermissions) {
+ EXPECT_TRUE(RunComponentExtensionTest(
+ "platform_apps/windows_api_ime/has_permissions_whitelisted"))
+ << message_;
+
+ EXPECT_TRUE(RunPlatformAppTestWithFlags(
+ "platform_apps/windows_api_ime/has_permissions_platform_app",
+ kFlagIgnoreManifestWarnings))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest,
+ WindowsApiImeWindowNoPermissions) {
+ EXPECT_TRUE(RunComponentExtensionTest(
+ "platform_apps/windows_api_ime/no_permissions_whitelisted"))
+ << message_;
+
+ EXPECT_TRUE(RunPlatformAppTest(
+ "platform_apps/windows_api_ime/no_permissions_platform_app"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest,
+ WindowsApiImeWindowNotFullscreen) {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ command_line->AppendSwitch(switches::kForceAppMode);
+ command_line->AppendSwitchASCII(switches::kAppId,
+ "jkghodnilhceideoidjikpgommlajknk");
+
+ EXPECT_TRUE(RunComponentExtensionTest(
+ "platform_apps/windows_api_ime/forced_app_mode_not_fullscreen"))
+ << message_;
+}
+#endif // OS_CHROMEOS
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/async_api_function.cc b/chromium/extensions/browser/api/async_api_function.cc
new file mode 100644
index 00000000000..a4e40f6fe1b
--- /dev/null
+++ b/chromium/extensions/browser/api/async_api_function.cc
@@ -0,0 +1,68 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/async_api_function.h"
+
+#include "base/bind.h"
+#include "extensions/browser/extension_system.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+// AsyncApiFunction
+AsyncApiFunction::AsyncApiFunction() : work_thread_id_(BrowserThread::IO) {}
+
+AsyncApiFunction::~AsyncApiFunction() {}
+
+bool AsyncApiFunction::RunAsync() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (!PrePrepare() || !Prepare()) {
+ return false;
+ }
+ bool rv = BrowserThread::PostTask(
+ work_thread_id_,
+ FROM_HERE,
+ base::Bind(&AsyncApiFunction::WorkOnWorkThread, this));
+ DCHECK(rv);
+ return true;
+}
+
+bool AsyncApiFunction::PrePrepare() { return true; }
+
+void AsyncApiFunction::Work() {}
+
+void AsyncApiFunction::AsyncWorkStart() {
+ Work();
+ AsyncWorkCompleted();
+}
+
+void AsyncApiFunction::AsyncWorkCompleted() {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ bool rv = BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&AsyncApiFunction::RespondOnUIThread, this));
+ DCHECK(rv);
+ } else {
+ SendResponse(Respond());
+ }
+}
+
+void AsyncApiFunction::WorkOnWorkThread() {
+ DCHECK_CURRENTLY_ON(work_thread_id_);
+ DLOG_IF(ERROR, (work_thread_id_ == BrowserThread::UI))
+ << "You have specified that AsyncApiFunction::Work() should happen on "
+ "the UI thread. This nullifies the point of this class. Either "
+ "specify a different thread or derive from a different class.";
+ AsyncWorkStart();
+}
+
+void AsyncApiFunction::RespondOnUIThread() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ SendResponse(Respond());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/async_api_function.h b/chromium/extensions/browser/api/async_api_function.h
new file mode 100644
index 00000000000..f10950684a8
--- /dev/null
+++ b/chromium/extensions/browser/api/async_api_function.h
@@ -0,0 +1,61 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_ASYNC_API_FUCTION_H_
+#define EXTENSIONS_BROWSER_API_ASYNC_API_FUCTION_H_
+
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/extension_function.h"
+
+namespace extensions {
+
+// AsyncApiFunction provides convenient thread management for APIs that need to
+// do essentially all their work on a thread other than the UI thread.
+class AsyncApiFunction : public AsyncExtensionFunction {
+ protected:
+ AsyncApiFunction();
+ ~AsyncApiFunction() override;
+
+ // Like Prepare(). A useful place to put common work in an ApiFunction
+ // superclass that multiple API functions want to share.
+ virtual bool PrePrepare();
+
+ // Set up for work (e.g., validate arguments). Guaranteed to happen on UI
+ // thread.
+ virtual bool Prepare() = 0;
+
+ // Do actual work. Guaranteed to happen on the thread specified in
+ // work_thread_id_.
+ virtual void Work();
+
+ // Start the asynchronous work. Guraranteed to happen on requested thread.
+ virtual void AsyncWorkStart();
+
+ // Notify AsyncIOApiFunction that the work is completed
+ void AsyncWorkCompleted();
+
+ // Respond. Guaranteed to happen on UI thread.
+ virtual bool Respond() = 0;
+
+ // ExtensionFunction::RunAsync()
+ bool RunAsync() override;
+
+ protected:
+ content::BrowserThread::ID work_thread_id() const { return work_thread_id_; }
+ void set_work_thread_id(content::BrowserThread::ID work_thread_id) {
+ work_thread_id_ = work_thread_id;
+ }
+
+ private:
+ void WorkOnWorkThread();
+ void RespondOnUIThread();
+
+ // If you don't want your Work() method to happen on the IO thread, then set
+ // this to the thread that you do want, preferably in Prepare().
+ content::BrowserThread::ID work_thread_id_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_ASYNC_API_FUCTION_H_
diff --git a/chromium/extensions/browser/api/audio/OWNERS b/chromium/extensions/browser/api/audio/OWNERS
new file mode 100644
index 00000000000..6a2cb03fd3e
--- /dev/null
+++ b/chromium/extensions/browser/api/audio/OWNERS
@@ -0,0 +1 @@
+rkc@chromium.org
diff --git a/chromium/extensions/browser/api/audio/audio_api.cc b/chromium/extensions/browser/api/audio/audio_api.cc
new file mode 100644
index 00000000000..ad1f22d85e9
--- /dev/null
+++ b/chromium/extensions/browser/api/audio/audio_api.cc
@@ -0,0 +1,141 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/audio/audio_api.h"
+
+#include <utility>
+
+#include "base/lazy_instance.h"
+#include "base/values.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/common/api/audio.h"
+
+namespace extensions {
+
+namespace audio = api::audio;
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<AudioAPI> > g_factory =
+ LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<AudioAPI>* AudioAPI::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+AudioAPI::AudioAPI(content::BrowserContext* context)
+ : browser_context_(context), service_(AudioService::CreateInstance()) {
+ service_->AddObserver(this);
+}
+
+AudioAPI::~AudioAPI() {
+ service_->RemoveObserver(this);
+ delete service_;
+ service_ = NULL;
+}
+
+AudioService* AudioAPI::GetService() const {
+ return service_;
+}
+
+void AudioAPI::OnDeviceChanged() {
+ if (EventRouter::Get(browser_context_)) {
+ scoped_ptr<Event> event(new Event(
+ events::AUDIO_ON_DEVICE_CHANGED, audio::OnDeviceChanged::kEventName,
+ scoped_ptr<base::ListValue>(new base::ListValue())));
+ EventRouter::Get(browser_context_)->BroadcastEvent(std::move(event));
+ }
+}
+
+void AudioAPI::OnLevelChanged(const std::string& id, int level) {
+ if (EventRouter::Get(browser_context_)) {
+ scoped_ptr<base::ListValue> args = audio::OnLevelChanged::Create(id, level);
+ scoped_ptr<Event> event(new Event(events::AUDIO_ON_LEVEL_CHANGED,
+ audio::OnLevelChanged::kEventName,
+ std::move(args)));
+ EventRouter::Get(browser_context_)->BroadcastEvent(std::move(event));
+ }
+}
+
+void AudioAPI::OnMuteChanged(bool is_input, bool is_muted) {
+ if (EventRouter::Get(browser_context_)) {
+ scoped_ptr<base::ListValue> args =
+ audio::OnMuteChanged::Create(is_input, is_muted);
+ scoped_ptr<Event> event(new Event(events::AUDIO_ON_MUTE_CHANGED,
+ audio::OnMuteChanged::kEventName,
+ std::move(args)));
+ EventRouter::Get(browser_context_)->BroadcastEvent(std::move(event));
+ }
+}
+
+void AudioAPI::OnDevicesChanged(const DeviceInfoList& devices) {
+ if (EventRouter::Get(browser_context_)) {
+ scoped_ptr<base::ListValue> args = audio::OnDevicesChanged::Create(devices);
+ scoped_ptr<Event> event(new Event(events::AUDIO_ON_DEVICES_CHANGED,
+ audio::OnDevicesChanged::kEventName,
+ std::move(args)));
+ EventRouter::Get(browser_context_)->BroadcastEvent(std::move(event));
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool AudioGetInfoFunction::RunSync() {
+ AudioService* service =
+ AudioAPI::GetFactoryInstance()->Get(browser_context())->GetService();
+ DCHECK(service);
+ OutputInfo output_info;
+ InputInfo input_info;
+ if (!service->GetInfo(&output_info, &input_info)) {
+ SetError("Error occurred when querying audio device information.");
+ return false;
+ }
+
+ results_ = audio::GetInfo::Results::Create(output_info, input_info);
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool AudioSetActiveDevicesFunction::RunSync() {
+ scoped_ptr<audio::SetActiveDevices::Params> params(
+ audio::SetActiveDevices::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ AudioService* service =
+ AudioAPI::GetFactoryInstance()->Get(browser_context())->GetService();
+ DCHECK(service);
+
+ service->SetActiveDevices(params->ids);
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool AudioSetPropertiesFunction::RunSync() {
+ scoped_ptr<audio::SetProperties::Params> params(
+ audio::SetProperties::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ AudioService* service =
+ AudioAPI::GetFactoryInstance()->Get(browser_context())->GetService();
+ DCHECK(service);
+
+ int volume_value = params->properties.volume.get() ?
+ *params->properties.volume : -1;
+
+ int gain_value = params->properties.gain.get() ?
+ *params->properties.gain : -1;
+
+ if (!service->SetDeviceProperties(params->id,
+ params->properties.is_muted,
+ volume_value,
+ gain_value))
+ return false;
+ else
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/audio/audio_api.h b/chromium/extensions/browser/api/audio/audio_api.h
new file mode 100644
index 00000000000..e1a45347546
--- /dev/null
+++ b/chromium/extensions/browser/api/audio/audio_api.h
@@ -0,0 +1,73 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_AUDIO_AUDIO_API_H_
+#define EXTENSIONS_BROWSER_API_AUDIO_AUDIO_API_H_
+
+#include "extensions/browser/api/audio/audio_service.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_function.h"
+
+namespace extensions {
+
+class AudioService;
+
+class AudioAPI : public BrowserContextKeyedAPI, public AudioService::Observer {
+ public:
+ explicit AudioAPI(content::BrowserContext* context);
+ ~AudioAPI() override;
+
+ AudioService* GetService() const;
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<AudioAPI>* GetFactoryInstance();
+
+ // AudioService::Observer implementation.
+ void OnDeviceChanged() override;
+ void OnLevelChanged(const std::string& id, int level) override;
+ void OnMuteChanged(bool is_input, bool is_muted) override;
+ void OnDevicesChanged(const DeviceInfoList& devices) override;
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<AudioAPI>;
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() {
+ return "AudioAPI";
+ }
+
+ content::BrowserContext* const browser_context_;
+ AudioService* service_;
+};
+
+class AudioGetInfoFunction : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("audio.getInfo", AUDIO_GETINFO);
+
+ protected:
+ ~AudioGetInfoFunction() override {}
+ bool RunSync() override;
+};
+
+class AudioSetActiveDevicesFunction : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("audio.setActiveDevices", AUDIO_SETACTIVEDEVICES);
+
+ protected:
+ ~AudioSetActiveDevicesFunction() override {}
+ bool RunSync() override;
+};
+
+class AudioSetPropertiesFunction : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("audio.setProperties", AUDIO_SETPROPERTIES);
+
+ protected:
+ ~AudioSetPropertiesFunction() override {}
+ bool RunSync() override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_AUDIO_AUDIO_API_H_
diff --git a/chromium/extensions/browser/api/audio/audio_apitest.cc b/chromium/extensions/browser/api/audio/audio_apitest.cc
new file mode 100644
index 00000000000..de089e21dca
--- /dev/null
+++ b/chromium/extensions/browser/api/audio/audio_apitest.cc
@@ -0,0 +1,300 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/message_loop/message_loop.h"
+#include "build/build_config.h"
+#include "extensions/shell/test/shell_apitest.h"
+#if defined(OS_CHROMEOS)
+#include "chromeos/audio/audio_devices_pref_handler_stub.h"
+#include "chromeos/audio/cras_audio_handler.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/fake_cras_audio_client.h"
+#endif
+#include "extensions/test/extension_test_message_listener.h"
+
+namespace extensions {
+
+#if defined(OS_CHROMEOS)
+using chromeos::AudioDevice;
+using chromeos::AudioDeviceList;
+using chromeos::AudioNode;
+using chromeos::AudioNodeList;
+
+const uint64_t kJabraSpeaker1Id = 30001;
+const uint64_t kJabraSpeaker1StableDeviceId = 80001;
+const uint64_t kJabraSpeaker2Id = 30002;
+const uint64_t kJabraSpeaker2StableDeviceId = 80002;
+const uint64_t kHDMIOutputId = 30003;
+const uint64_t kHDMIOutputStabeDevicelId = 80003;
+const uint64_t kJabraMic1Id = 40001;
+const uint64_t kJabraMic1StableDeviceId = 90001;
+const uint64_t kJabraMic2Id = 40002;
+const uint64_t kJabraMic2StableDeviceId = 90002;
+const uint64_t kWebcamMicId = 40003;
+const uint64_t kWebcamMicStableDeviceId = 90003;
+
+const AudioNode kJabraSpeaker1(false,
+ kJabraSpeaker1Id,
+ kJabraSpeaker1StableDeviceId,
+ "Jabra Speaker",
+ "USB",
+ "Jabra Speaker 1",
+ false,
+ 0);
+
+const AudioNode kJabraSpeaker2(false,
+ kJabraSpeaker2Id,
+ kJabraSpeaker2StableDeviceId,
+ "Jabra Speaker",
+ "USB",
+ "Jabra Speaker 2",
+ false,
+ 0);
+
+const AudioNode kHDMIOutput(false,
+ kHDMIOutputId,
+ kHDMIOutputStabeDevicelId,
+ "HDMI output",
+ "HDMI",
+ "HDA Intel MID",
+ false,
+ 0);
+
+const AudioNode kJabraMic1(true,
+ kJabraMic1Id,
+ kJabraMic1StableDeviceId,
+ "Jabra Mic",
+ "USB",
+ "Jabra Mic 1",
+ false,
+ 0);
+
+const AudioNode kJabraMic2(true,
+ kJabraMic2Id,
+ kJabraMic2StableDeviceId,
+ "Jabra Mic",
+ "USB",
+ "Jabra Mic 2",
+ false,
+ 0);
+
+const AudioNode kUSBCameraMic(true,
+ kWebcamMicId,
+ kWebcamMicStableDeviceId,
+ "Webcam Mic",
+ "USB",
+ "Logitech Webcam",
+ false,
+ 0);
+
+class AudioApiTest : public ShellApiTest {
+ public:
+ AudioApiTest() : cras_audio_handler_(NULL), fake_cras_audio_client_(NULL) {}
+ ~AudioApiTest() override {}
+
+ void SetUpCrasAudioHandlerWithTestingNodes(const AudioNodeList& audio_nodes) {
+ chromeos::DBusThreadManager* dbus_manager =
+ chromeos::DBusThreadManager::Get();
+ DCHECK(dbus_manager);
+ fake_cras_audio_client_ = static_cast<chromeos::FakeCrasAudioClient*>(
+ dbus_manager->GetCrasAudioClient());
+ fake_cras_audio_client_->SetAudioNodesAndNotifyObserversForTesting(
+ audio_nodes);
+ cras_audio_handler_ = chromeos::CrasAudioHandler::Get();
+ DCHECK(cras_audio_handler_);
+ message_loop_.RunUntilIdle();
+ }
+
+ void ChangeAudioNodes(const AudioNodeList& audio_nodes) {
+ DCHECK(fake_cras_audio_client_);
+ fake_cras_audio_client_->SetAudioNodesAndNotifyObserversForTesting(
+ audio_nodes);
+ message_loop_.RunUntilIdle();
+ }
+
+ protected:
+ base::MessageLoopForUI message_loop_;
+ chromeos::CrasAudioHandler* cras_audio_handler_; // Not owned.
+ chromeos::FakeCrasAudioClient* fake_cras_audio_client_; // Not owned.
+};
+
+IN_PROC_BROWSER_TEST_F(AudioApiTest, Audio) {
+ // Set up the audio nodes for testing.
+ AudioNodeList audio_nodes;
+ audio_nodes.push_back(kJabraSpeaker1);
+ audio_nodes.push_back(kJabraSpeaker2);
+ audio_nodes.push_back(kHDMIOutput);
+ audio_nodes.push_back(kJabraMic1);
+ audio_nodes.push_back(kJabraMic2);
+ audio_nodes.push_back(kUSBCameraMic);
+ SetUpCrasAudioHandlerWithTestingNodes(audio_nodes);
+
+ EXPECT_TRUE(RunAppTest("api_test/audio")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(AudioApiTest, OnLevelChangedOutputDevice) {
+ AudioNodeList audio_nodes;
+ audio_nodes.push_back(kJabraSpeaker1);
+ audio_nodes.push_back(kHDMIOutput);
+ SetUpCrasAudioHandlerWithTestingNodes(audio_nodes);
+
+ // Verify the jabra speaker is the active output device.
+ AudioDevice device;
+ EXPECT_TRUE(cras_audio_handler_->GetPrimaryActiveOutputDevice(&device));
+ EXPECT_EQ(device.id, kJabraSpeaker1.id);
+
+ // Loads background app.
+ ExtensionTestMessageListener load_listener("loaded", false);
+ ExtensionTestMessageListener result_listener("success", false);
+ result_listener.set_failure_message("failure");
+ ASSERT_TRUE(LoadApp("api_test/audio/volume_change"));
+ ASSERT_TRUE(load_listener.WaitUntilSatisfied());
+
+ // Change output device volume.
+ const int kVolume = 60;
+ cras_audio_handler_->SetOutputVolumePercent(kVolume);
+
+ // Verify the output volume is changed to the designated value.
+ EXPECT_EQ(kVolume, cras_audio_handler_->GetOutputVolumePercent());
+ EXPECT_EQ(kVolume,
+ cras_audio_handler_->GetOutputVolumePercentForDevice(device.id));
+
+ // Verify the background app got the OnOutputNodeVolumeChanged event
+ // with the expected node id and volume value.
+ ASSERT_TRUE(result_listener.WaitUntilSatisfied());
+ EXPECT_EQ("success", result_listener.message());
+}
+
+IN_PROC_BROWSER_TEST_F(AudioApiTest, OnOutputMuteChanged) {
+ AudioNodeList audio_nodes;
+ audio_nodes.push_back(kJabraSpeaker1);
+ audio_nodes.push_back(kHDMIOutput);
+ SetUpCrasAudioHandlerWithTestingNodes(audio_nodes);
+
+ // Verify the jabra speaker is the active output device.
+ AudioDevice device;
+ EXPECT_TRUE(cras_audio_handler_->GetPrimaryActiveOutputDevice(&device));
+ EXPECT_EQ(device.id, kJabraSpeaker1.id);
+
+ // Mute the output.
+ cras_audio_handler_->SetOutputMute(true);
+ EXPECT_TRUE(cras_audio_handler_->IsOutputMuted());
+
+ // Loads background app.
+ ExtensionTestMessageListener load_listener("loaded", false);
+ ExtensionTestMessageListener result_listener("success", false);
+ result_listener.set_failure_message("failure");
+ ASSERT_TRUE(LoadApp("api_test/audio/output_mute_change"));
+ ASSERT_TRUE(load_listener.WaitUntilSatisfied());
+
+ // Un-mute the output.
+ cras_audio_handler_->SetOutputMute(false);
+ EXPECT_FALSE(cras_audio_handler_->IsOutputMuted());
+
+ // Verify the background app got the OnMuteChanged event
+ // with the expected output un-muted state.
+ ASSERT_TRUE(result_listener.WaitUntilSatisfied());
+ EXPECT_EQ("success", result_listener.message());
+}
+
+IN_PROC_BROWSER_TEST_F(AudioApiTest, OnInputMuteChanged) {
+ AudioNodeList audio_nodes;
+ audio_nodes.push_back(kJabraMic1);
+ audio_nodes.push_back(kUSBCameraMic);
+ SetUpCrasAudioHandlerWithTestingNodes(audio_nodes);
+
+ // Set the jabra mic to be the active input device.
+ AudioDevice jabra_mic(kJabraMic1);
+ cras_audio_handler_->SwitchToDevice(
+ jabra_mic, true, chromeos::CrasAudioHandler::ACTIVATE_BY_USER);
+ EXPECT_EQ(kJabraMic1.id, cras_audio_handler_->GetPrimaryActiveInputNode());
+
+ // Un-mute the input.
+ cras_audio_handler_->SetInputMute(false);
+ EXPECT_FALSE(cras_audio_handler_->IsInputMuted());
+
+ // Loads background app.
+ ExtensionTestMessageListener load_listener("loaded", false);
+ ExtensionTestMessageListener result_listener("success", false);
+ result_listener.set_failure_message("failure");
+ ASSERT_TRUE(LoadApp("api_test/audio/input_mute_change"));
+ ASSERT_TRUE(load_listener.WaitUntilSatisfied());
+
+ // Mute the input.
+ cras_audio_handler_->SetInputMute(true);
+ EXPECT_TRUE(cras_audio_handler_->IsInputMuted());
+
+ // Verify the background app got the OnMuteChanged event
+ // with the expected input muted state.
+ ASSERT_TRUE(result_listener.WaitUntilSatisfied());
+ EXPECT_EQ("success", result_listener.message());
+}
+
+IN_PROC_BROWSER_TEST_F(AudioApiTest, OnNodesChangedAddNodes) {
+ AudioNodeList audio_nodes;
+ audio_nodes.push_back(kJabraSpeaker1);
+ audio_nodes.push_back(kJabraSpeaker2);
+ SetUpCrasAudioHandlerWithTestingNodes(audio_nodes);
+ const size_t init_device_size = audio_nodes.size();
+
+ AudioDeviceList audio_devices;
+ cras_audio_handler_->GetAudioDevices(&audio_devices);
+ EXPECT_EQ(init_device_size, audio_devices.size());
+
+ // Load background app.
+ ExtensionTestMessageListener load_listener("loaded", false);
+ ExtensionTestMessageListener result_listener("success", false);
+ result_listener.set_failure_message("failure");
+ ASSERT_TRUE(LoadApp("api_test/audio/add_nodes"));
+ ASSERT_TRUE(load_listener.WaitUntilSatisfied());
+
+ // Plug in HDMI output.
+ audio_nodes.push_back(kHDMIOutput);
+ ChangeAudioNodes(audio_nodes);
+ cras_audio_handler_->GetAudioDevices(&audio_devices);
+ EXPECT_EQ(init_device_size + 1, audio_devices.size());
+
+ // Verify the background app got the OnNodesChanged event
+ // with the new node added.
+ ASSERT_TRUE(result_listener.WaitUntilSatisfied());
+ EXPECT_EQ("success", result_listener.message());
+}
+
+IN_PROC_BROWSER_TEST_F(AudioApiTest, OnNodesChangedRemoveNodes) {
+ AudioNodeList audio_nodes;
+ audio_nodes.push_back(kJabraMic1);
+ audio_nodes.push_back(kJabraMic2);
+ audio_nodes.push_back(kUSBCameraMic);
+ SetUpCrasAudioHandlerWithTestingNodes(audio_nodes);
+ const size_t init_device_size = audio_nodes.size();
+
+ AudioDeviceList audio_devices;
+ cras_audio_handler_->GetAudioDevices(&audio_devices);
+ EXPECT_EQ(init_device_size, audio_devices.size());
+
+ // Load background app.
+ ExtensionTestMessageListener load_listener("loaded", false);
+ ExtensionTestMessageListener result_listener("success", false);
+ result_listener.set_failure_message("failure");
+ ASSERT_TRUE(LoadApp("api_test/audio/remove_nodes"));
+ ASSERT_TRUE(load_listener.WaitUntilSatisfied());
+
+ // Remove camera mic.
+ audio_nodes.erase(audio_nodes.begin() + init_device_size - 1);
+ ChangeAudioNodes(audio_nodes);
+ cras_audio_handler_->GetAudioDevices(&audio_devices);
+ EXPECT_EQ(init_device_size - 1, audio_devices.size());
+
+ // Verify the background app got the onNodesChanged event
+ // with the last node removed.
+ ASSERT_TRUE(result_listener.WaitUntilSatisfied());
+ EXPECT_EQ("success", result_listener.message());
+}
+
+#endif // OS_CHROMEOS
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/audio/audio_service.cc b/chromium/extensions/browser/api/audio/audio_service.cc
new file mode 100644
index 00000000000..ec4c7c131da
--- /dev/null
+++ b/chromium/extensions/browser/api/audio/audio_service.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/audio/audio_service.h"
+
+namespace extensions {
+
+class AudioServiceImpl : public AudioService {
+ public:
+ AudioServiceImpl() {}
+ ~AudioServiceImpl() override {}
+
+ // Called by listeners to this service to add/remove themselves as observers.
+ void AddObserver(Observer* observer) override;
+ void RemoveObserver(Observer* observer) override;
+
+ // Start to query audio device information.
+ bool GetInfo(OutputInfo* output_info_out, InputInfo* input_info_out) override;
+ void SetActiveDevices(const DeviceIdList& device_list) override;
+ bool SetDeviceProperties(const std::string& device_id,
+ bool muted,
+ int volume,
+ int gain) override;
+};
+
+void AudioServiceImpl::AddObserver(Observer* observer) {
+ // TODO: implement this for platforms other than Chrome OS.
+}
+
+void AudioServiceImpl::RemoveObserver(Observer* observer) {
+ // TODO: implement this for platforms other than Chrome OS.
+}
+
+AudioService* AudioService::CreateInstance() {
+ return new AudioServiceImpl;
+}
+
+bool AudioServiceImpl::GetInfo(OutputInfo* output_info_out,
+ InputInfo* input_info_out) {
+ // TODO: implement this for platforms other than Chrome OS.
+ return false;
+}
+
+void AudioServiceImpl::SetActiveDevices(const DeviceIdList& device_list) {
+}
+
+bool AudioServiceImpl::SetDeviceProperties(const std::string& device_id,
+ bool muted,
+ int volume,
+ int gain) {
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/audio/audio_service.h b/chromium/extensions/browser/api/audio/audio_service.h
new file mode 100644
index 00000000000..e733f7afd39
--- /dev/null
+++ b/chromium/extensions/browser/api/audio/audio_service.h
@@ -0,0 +1,83 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_AUDIO_AUDIO_SERVICE_H_
+#define EXTENSIONS_BROWSER_API_AUDIO_AUDIO_SERVICE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "extensions/common/api/audio.h"
+
+namespace extensions {
+
+using OutputInfo = std::vector<api::audio::OutputDeviceInfo>;
+using InputInfo = std::vector<api::audio::InputDeviceInfo>;
+using DeviceIdList = std::vector<std::string>;
+using DeviceInfoList = std::vector<api::audio::AudioDeviceInfo>;
+
+class AudioService {
+ public:
+ class Observer {
+ public:
+ // Called when anything changes to the audio device configuration.
+ virtual void OnDeviceChanged() = 0;
+
+ // Called when the sound level of an active audio device changes.
+ virtual void OnLevelChanged(const std::string& id, int level) = 0;
+
+ // Called when the mute state of audio input/output changes.
+ virtual void OnMuteChanged(bool is_input, bool is_muted) = 0;
+
+ // Called when the audio devices change, either new devices being added, or
+ // existing devices being removed.
+ virtual void OnDevicesChanged(const DeviceInfoList&) = 0;
+
+ protected:
+ virtual ~Observer() {}
+ };
+
+ // Creates a platform-specific AudioService instance.
+ static AudioService* CreateInstance();
+
+ virtual ~AudioService() {}
+
+ // Called by listeners to this service to add/remove themselves as observers.
+ virtual void AddObserver(Observer* observer) = 0;
+ virtual void RemoveObserver(Observer* observer) = 0;
+
+ // Start to query audio device information. Should be called on UI thread.
+ // Populates |output_info_out| and |input_info_out| with the results.
+ // Returns true on success.
+ virtual bool GetInfo(OutputInfo* output_info_out,
+ InputInfo* input_info_out) = 0;
+
+ // Sets the active devices to the devices specified by |device_list|.
+ // It can pass in the "complete" active device list of either input
+ // devices, or output devices, or both. If only input devices are passed in,
+ // it will only change the input devices' active status, output devices will
+ // NOT be changed; similarly for the case if only output devices are passed.
+ // If the devices specified in |new_active_ids| are already active, they will
+ // remain active. Otherwise, the old active devices will be de-activated
+ // before we activate the new devices with the same type(input/output).
+ virtual void SetActiveDevices(const DeviceIdList& device_list) = 0;
+
+ // Set the muted and volume/gain properties of a device.
+ virtual bool SetDeviceProperties(const std::string& device_id,
+ bool muted,
+ int volume,
+ int gain) = 0;
+
+ protected:
+ AudioService() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AudioService);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_AUDIO_AUDIO_SERVICE_H_
diff --git a/chromium/extensions/browser/api/audio/audio_service_chromeos.cc b/chromium/extensions/browser/api/audio/audio_service_chromeos.cc
new file mode 100644
index 00000000000..5d3d37f3a0f
--- /dev/null
+++ b/chromium/extensions/browser/api/audio/audio_service_chromeos.cc
@@ -0,0 +1,288 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/audio/audio_service.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "chromeos/audio/audio_device.h"
+#include "chromeos/audio/cras_audio_handler.h"
+#include "content/public/browser/browser_thread.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+using api::audio::OutputDeviceInfo;
+using api::audio::InputDeviceInfo;
+using api::audio::AudioDeviceInfo;
+
+class AudioServiceImpl : public AudioService,
+ public chromeos::CrasAudioHandler::AudioObserver {
+ public:
+ AudioServiceImpl();
+ ~AudioServiceImpl() override;
+
+ // Called by listeners to this service to add/remove themselves as observers.
+ void AddObserver(AudioService::Observer* observer) override;
+ void RemoveObserver(AudioService::Observer* observer) override;
+
+ // Start to query audio device information.
+ bool GetInfo(OutputInfo* output_info_out, InputInfo* input_info_out) override;
+ void SetActiveDevices(const DeviceIdList& device_list) override;
+ bool SetDeviceProperties(const std::string& device_id,
+ bool muted,
+ int volume,
+ int gain) override;
+
+ protected:
+ // chromeos::CrasAudioHandler::AudioObserver overrides.
+ void OnOutputNodeVolumeChanged(uint64_t id, int volume) override;
+ void OnInputNodeGainChanged(uint64_t id, int gain) override;
+ void OnOutputMuteChanged(bool mute_on, bool system_adjust) override;
+ void OnInputMuteChanged(bool mute_on) override;
+ void OnAudioNodesChanged() override;
+ void OnActiveOutputNodeChanged() override;
+ void OnActiveInputNodeChanged() override;
+
+ private:
+ void NotifyDeviceChanged();
+ void NotifyLevelChanged(uint64_t id, int level);
+ void NotifyMuteChanged(bool is_input, bool is_muted);
+ void NotifyDevicesChanged();
+
+ bool FindDevice(uint64_t id, chromeos::AudioDevice* device);
+ uint64_t GetIdFromStr(const std::string& id_str);
+
+ // List of observers.
+ base::ObserverList<AudioService::Observer> observer_list_;
+
+ chromeos::CrasAudioHandler* cras_audio_handler_;
+
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate the weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<AudioServiceImpl> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioServiceImpl);
+};
+
+AudioServiceImpl::AudioServiceImpl()
+ : cras_audio_handler_(NULL),
+ weak_ptr_factory_(this) {
+ if (chromeos::CrasAudioHandler::IsInitialized()) {
+ cras_audio_handler_ = chromeos::CrasAudioHandler::Get();
+ cras_audio_handler_->AddAudioObserver(this);
+ }
+}
+
+AudioServiceImpl::~AudioServiceImpl() {
+ if (cras_audio_handler_ && chromeos::CrasAudioHandler::IsInitialized()) {
+ cras_audio_handler_->RemoveAudioObserver(this);
+ }
+}
+
+void AudioServiceImpl::AddObserver(AudioService::Observer* observer) {
+ observer_list_.AddObserver(observer);
+}
+
+void AudioServiceImpl::RemoveObserver(AudioService::Observer* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+bool AudioServiceImpl::GetInfo(OutputInfo* output_info_out,
+ InputInfo* input_info_out) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(cras_audio_handler_);
+ DCHECK(output_info_out);
+ DCHECK(input_info_out);
+
+ if (!cras_audio_handler_)
+ return false;
+
+ chromeos::AudioDeviceList devices;
+ cras_audio_handler_->GetAudioDevices(&devices);
+ for (size_t i = 0; i < devices.size(); ++i) {
+ if (!devices[i].is_input) {
+ OutputDeviceInfo info;
+ info.id = base::Uint64ToString(devices[i].id);
+ info.name = devices[i].device_name + ": " + devices[i].display_name;
+ info.is_active = devices[i].active;
+ info.volume =
+ cras_audio_handler_->GetOutputVolumePercentForDevice(devices[i].id);
+ info.is_muted =
+ cras_audio_handler_->IsOutputMutedForDevice(devices[i].id);
+ output_info_out->push_back(std::move(info));
+ } else {
+ InputDeviceInfo info;
+ info.id = base::Uint64ToString(devices[i].id);
+ info.name = devices[i].device_name + ": " + devices[i].display_name;
+ info.is_active = devices[i].active;
+ info.gain =
+ cras_audio_handler_->GetInputGainPercentForDevice(devices[i].id);
+ info.is_muted = cras_audio_handler_->IsInputMutedForDevice(devices[i].id);
+ input_info_out->push_back(std::move(info));
+ }
+ }
+ return true;
+}
+
+void AudioServiceImpl::SetActiveDevices(const DeviceIdList& device_list) {
+ DCHECK(cras_audio_handler_);
+ if (!cras_audio_handler_)
+ return;
+
+ chromeos::CrasAudioHandler::NodeIdList id_list;
+ for (size_t i = 0; i < device_list.size(); ++i) {
+ chromeos::AudioDevice device;
+ if (FindDevice(GetIdFromStr(device_list[i]), &device))
+ id_list.push_back(device.id);
+ }
+ cras_audio_handler_->ChangeActiveNodes(id_list);
+}
+
+bool AudioServiceImpl::SetDeviceProperties(const std::string& device_id,
+ bool muted,
+ int volume,
+ int gain) {
+ DCHECK(cras_audio_handler_);
+ if (!cras_audio_handler_)
+ return false;
+
+ chromeos::AudioDevice device;
+ bool found = FindDevice(GetIdFromStr(device_id), &device);
+ if (!found)
+ return false;
+
+ cras_audio_handler_->SetMuteForDevice(device.id, muted);
+
+ if (!device.is_input && volume != -1) {
+ cras_audio_handler_->SetVolumeGainPercentForDevice(device.id, volume);
+ return true;
+ } else if (device.is_input && gain != -1) {
+ cras_audio_handler_->SetVolumeGainPercentForDevice(device.id, gain);
+ return true;
+ }
+
+ return false;
+}
+
+bool AudioServiceImpl::FindDevice(uint64_t id, chromeos::AudioDevice* device) {
+ chromeos::AudioDeviceList devices;
+ cras_audio_handler_->GetAudioDevices(&devices);
+
+ for (size_t i = 0; i < devices.size(); ++i) {
+ if (devices[i].id == id) {
+ *device = devices[i];
+ return true;
+ }
+ }
+ return false;
+}
+
+uint64_t AudioServiceImpl::GetIdFromStr(const std::string& id_str) {
+ uint64_t device_id;
+ if (!base::StringToUint64(id_str, &device_id))
+ return 0;
+ else
+ return device_id;
+}
+
+void AudioServiceImpl::OnOutputNodeVolumeChanged(uint64_t id, int volume) {
+ NotifyLevelChanged(id, volume);
+}
+
+void AudioServiceImpl::OnOutputMuteChanged(bool mute_on, bool system_adjust) {
+ NotifyMuteChanged(false, mute_on);
+}
+
+void AudioServiceImpl::OnInputNodeGainChanged(uint64_t id, int gain) {
+ NotifyLevelChanged(id, gain);
+}
+
+void AudioServiceImpl::OnInputMuteChanged(bool mute_on) {
+ NotifyMuteChanged(true, mute_on);
+}
+
+void AudioServiceImpl::OnAudioNodesChanged() {
+ NotifyDevicesChanged();
+}
+
+void AudioServiceImpl::OnActiveOutputNodeChanged() {
+ NotifyDeviceChanged();
+}
+
+void AudioServiceImpl::OnActiveInputNodeChanged() {
+ NotifyDeviceChanged();
+}
+
+void AudioServiceImpl::NotifyDeviceChanged() {
+ FOR_EACH_OBSERVER(AudioService::Observer, observer_list_, OnDeviceChanged());
+}
+
+void AudioServiceImpl::NotifyLevelChanged(uint64_t id, int level) {
+ FOR_EACH_OBSERVER(AudioService::Observer, observer_list_,
+ OnLevelChanged(base::Uint64ToString(id), level));
+
+ // Notify DeviceChanged event for backward compatibility.
+ // TODO(jennyz): remove this code when the old version of hotrod retires.
+ NotifyDeviceChanged();
+}
+
+void AudioServiceImpl::NotifyMuteChanged(bool is_input, bool is_muted) {
+ FOR_EACH_OBSERVER(AudioService::Observer, observer_list_,
+ OnMuteChanged(is_input, is_muted));
+
+ // Notify DeviceChanged event for backward compatibility.
+ // TODO(jennyz): remove this code when the old version of hotrod retires.
+ NotifyDeviceChanged();
+}
+
+void AudioServiceImpl::NotifyDevicesChanged() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(cras_audio_handler_);
+
+ DeviceInfoList devices_info_list;
+ chromeos::AudioDeviceList devices;
+ cras_audio_handler_->GetAudioDevices(&devices);
+ for (size_t i = 0; i < devices.size(); ++i) {
+ AudioDeviceInfo info;
+ info.id = base::Uint64ToString(devices[i].id);
+ info.is_input = devices[i].is_input;
+ info.device_type = chromeos::AudioDevice::GetTypeString(devices[i].type);
+ info.display_name = devices[i].display_name;
+ info.device_name = devices[i].device_name;
+ info.is_active = devices[i].active;
+ info.is_muted =
+ devices[i].is_input
+ ? cras_audio_handler_->IsInputMutedForDevice(devices[i].id)
+ : cras_audio_handler_->IsOutputMutedForDevice(devices[i].id);
+ info.level =
+ devices[i].is_input
+ ? cras_audio_handler_->GetOutputVolumePercentForDevice(
+ devices[i].id)
+ : cras_audio_handler_->GetInputGainPercentForDevice(devices[i].id);
+ info.stable_device_id.reset(
+ new std::string(base::Uint64ToString(devices[i].stable_device_id)));
+
+ devices_info_list.push_back(std::move(info));
+ }
+
+ FOR_EACH_OBSERVER(AudioService::Observer, observer_list_,
+ OnDevicesChanged(devices_info_list));
+
+ // Notify DeviceChanged event for backward compatibility.
+ // TODO(jennyz): remove this code when the old version of hotrod retires.
+ NotifyDeviceChanged();
+}
+
+AudioService* AudioService::CreateInstance() {
+ return new AudioServiceImpl;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/bluetooth/OWNERS b/chromium/extensions/browser/api/bluetooth/OWNERS
new file mode 100644
index 00000000000..ecefbc4d9e5
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth/OWNERS
@@ -0,0 +1,4 @@
+armansito@chromium.org
+keybuk@chromium.org
+rpaquay@chromium.org
+stevenjb@chromium.org
diff --git a/chromium/extensions/browser/api/bluetooth/bluetooth_api.cc b/chromium/extensions/browser/api/bluetooth/bluetooth_api.cc
new file mode 100644
index 00000000000..ff9780bb6e0
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth/bluetooth_api.cc
@@ -0,0 +1,203 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth/bluetooth_api.h"
+
+#include <string>
+
+#include "base/bind_helpers.h"
+#include "base/lazy_instance.h"
+#include "base/memory/ref_counted.h"
+#include "content/public/browser/browser_thread.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "extensions/browser/api/bluetooth/bluetooth_api_utils.h"
+#include "extensions/browser/api/bluetooth/bluetooth_event_router.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/common/api/bluetooth.h"
+
+using content::BrowserContext;
+using content::BrowserThread;
+
+using device::BluetoothAdapter;
+using device::BluetoothDevice;
+
+namespace bluetooth = extensions::api::bluetooth;
+namespace GetDevice = extensions::api::bluetooth::GetDevice;
+namespace GetDevices = extensions::api::bluetooth::GetDevices;
+
+namespace {
+
+const char kInvalidDevice[] = "Invalid device";
+const char kStartDiscoveryFailed[] = "Starting discovery failed";
+const char kStopDiscoveryFailed[] = "Failed to stop discovery";
+
+extensions::BluetoothEventRouter* GetEventRouter(BrowserContext* context) {
+ // Note: |context| is valid on UI thread only.
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ return extensions::BluetoothAPI::Get(context)->event_router();
+}
+
+} // namespace
+
+namespace extensions {
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<BluetoothAPI> >
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<BluetoothAPI>*
+BluetoothAPI::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+// static
+BluetoothAPI* BluetoothAPI::Get(BrowserContext* context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ return GetFactoryInstance()->Get(context);
+}
+
+BluetoothAPI::BluetoothAPI(content::BrowserContext* context)
+ : browser_context_(context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ event_router->RegisterObserver(this,
+ bluetooth::OnAdapterStateChanged::kEventName);
+ event_router->RegisterObserver(this, bluetooth::OnDeviceAdded::kEventName);
+ event_router->RegisterObserver(this, bluetooth::OnDeviceChanged::kEventName);
+ event_router->RegisterObserver(this, bluetooth::OnDeviceRemoved::kEventName);
+}
+
+BluetoothAPI::~BluetoothAPI() {}
+
+BluetoothEventRouter* BluetoothAPI::event_router() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!event_router_) {
+ event_router_.reset(new BluetoothEventRouter(browser_context_));
+ }
+ return event_router_.get();
+}
+
+void BluetoothAPI::Shutdown() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ EventRouter::Get(browser_context_)->UnregisterObserver(this);
+}
+
+void BluetoothAPI::OnListenerAdded(const EventListenerInfo& details) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (event_router()->IsBluetoothSupported())
+ event_router()->OnListenerAdded();
+}
+
+void BluetoothAPI::OnListenerRemoved(const EventListenerInfo& details) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (event_router()->IsBluetoothSupported())
+ event_router()->OnListenerRemoved();
+}
+
+namespace api {
+
+BluetoothGetAdapterStateFunction::~BluetoothGetAdapterStateFunction() {}
+
+bool BluetoothGetAdapterStateFunction::DoWork(
+ scoped_refptr<BluetoothAdapter> adapter) {
+ bluetooth::AdapterState state;
+ PopulateAdapterState(*adapter.get(), &state);
+ results_ = bluetooth::GetAdapterState::Results::Create(state);
+ SendResponse(true);
+ return true;
+}
+
+BluetoothGetDevicesFunction::~BluetoothGetDevicesFunction() {}
+
+bool BluetoothGetDevicesFunction::DoWork(
+ scoped_refptr<BluetoothAdapter> adapter) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ base::ListValue* device_list = new base::ListValue;
+ SetResult(device_list);
+
+ BluetoothAdapter::DeviceList devices = adapter->GetDevices();
+ for (BluetoothAdapter::DeviceList::const_iterator iter = devices.begin();
+ iter != devices.end();
+ ++iter) {
+ const BluetoothDevice* device = *iter;
+ DCHECK(device);
+
+ bluetooth::Device extension_device;
+ bluetooth::BluetoothDeviceToApiDevice(*device, &extension_device);
+
+ device_list->Append(extension_device.ToValue().release());
+ }
+
+ SendResponse(true);
+
+ return true;
+}
+
+BluetoothGetDeviceFunction::~BluetoothGetDeviceFunction() {}
+
+bool BluetoothGetDeviceFunction::DoWork(
+ scoped_refptr<BluetoothAdapter> adapter) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ scoped_ptr<GetDevice::Params> params(GetDevice::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get() != NULL);
+
+ BluetoothDevice* device = adapter->GetDevice(params->device_address);
+ if (device) {
+ bluetooth::Device extension_device;
+ bluetooth::BluetoothDeviceToApiDevice(*device, &extension_device);
+ SetResult(extension_device.ToValue().release());
+ SendResponse(true);
+ } else {
+ SetError(kInvalidDevice);
+ SendResponse(false);
+ }
+
+ return false;
+}
+
+void BluetoothStartDiscoveryFunction::OnSuccessCallback() {
+ SendResponse(true);
+}
+
+void BluetoothStartDiscoveryFunction::OnErrorCallback() {
+ SetError(kStartDiscoveryFailed);
+ SendResponse(false);
+}
+
+bool BluetoothStartDiscoveryFunction::DoWork(
+ scoped_refptr<BluetoothAdapter> adapter) {
+ GetEventRouter(browser_context())
+ ->StartDiscoverySession(
+ adapter.get(), GetExtensionId(),
+ base::Bind(&BluetoothStartDiscoveryFunction::OnSuccessCallback, this),
+ base::Bind(&BluetoothStartDiscoveryFunction::OnErrorCallback, this));
+
+ return true;
+}
+
+void BluetoothStopDiscoveryFunction::OnSuccessCallback() {
+ SendResponse(true);
+}
+
+void BluetoothStopDiscoveryFunction::OnErrorCallback() {
+ SetError(kStopDiscoveryFailed);
+ SendResponse(false);
+}
+
+bool BluetoothStopDiscoveryFunction::DoWork(
+ scoped_refptr<BluetoothAdapter> adapter) {
+ GetEventRouter(browser_context())
+ ->StopDiscoverySession(
+ adapter.get(), GetExtensionId(),
+ base::Bind(&BluetoothStopDiscoveryFunction::OnSuccessCallback, this),
+ base::Bind(&BluetoothStopDiscoveryFunction::OnErrorCallback, this));
+
+ return true;
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/bluetooth/bluetooth_api.h b/chromium/extensions/browser/api/bluetooth/bluetooth_api.h
new file mode 100644
index 00000000000..d4f4f1fbb50
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth/bluetooth_api.h
@@ -0,0 +1,137 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_API_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_API_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "extensions/browser/api/bluetooth/bluetooth_extension_function.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/common/api/bluetooth.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace device {
+class BluetoothAdapter;
+}
+
+namespace extensions {
+
+class BluetoothEventRouter;
+
+// The profile-keyed service that manages the bluetooth extension API.
+// All methods of this class must be called on the UI thread.
+// TODO(rpaquay): Rename this and move to separate file.
+class BluetoothAPI : public BrowserContextKeyedAPI,
+ public EventRouter::Observer {
+ public:
+ // Convenience method to get the BluetoothAPI for a browser context.
+ static BluetoothAPI* Get(content::BrowserContext* context);
+
+ static BrowserContextKeyedAPIFactory<BluetoothAPI>* GetFactoryInstance();
+
+ explicit BluetoothAPI(content::BrowserContext* context);
+ ~BluetoothAPI() override;
+
+ BluetoothEventRouter* event_router();
+
+ // KeyedService implementation.
+ void Shutdown() override;
+
+ // EventRouter::Observer implementation.
+ void OnListenerAdded(const EventListenerInfo& details) override;
+ void OnListenerRemoved(const EventListenerInfo& details) override;
+
+ private:
+ // BrowserContextKeyedAPI implementation.
+ friend class BrowserContextKeyedAPIFactory<BluetoothAPI>;
+ static const char* service_name() { return "BluetoothAPI"; }
+ static const bool kServiceRedirectedInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ content::BrowserContext* browser_context_;
+
+ // Created lazily on first access.
+ scoped_ptr<BluetoothEventRouter> event_router_;
+};
+
+namespace api {
+
+class BluetoothGetAdapterStateFunction : public BluetoothExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetooth.getAdapterState",
+ BLUETOOTH_GETADAPTERSTATE)
+
+ protected:
+ ~BluetoothGetAdapterStateFunction() override;
+
+ // BluetoothExtensionFunction:
+ bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) override;
+};
+
+class BluetoothGetDevicesFunction : public BluetoothExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetooth.getDevices", BLUETOOTH_GETDEVICES)
+
+ protected:
+ ~BluetoothGetDevicesFunction() override;
+
+ // BluetoothExtensionFunction:
+ bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) override;
+};
+
+class BluetoothGetDeviceFunction : public BluetoothExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetooth.getDevice", BLUETOOTH_GETDEVICE)
+
+ // BluetoothExtensionFunction:
+ bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) override;
+
+ protected:
+ ~BluetoothGetDeviceFunction() override;
+};
+
+class BluetoothStartDiscoveryFunction : public BluetoothExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetooth.startDiscovery",
+ BLUETOOTH_STARTDISCOVERY)
+
+ protected:
+ ~BluetoothStartDiscoveryFunction() override {}
+
+ // BluetoothExtensionFunction:
+ bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) override;
+
+ private:
+ void OnSuccessCallback();
+ void OnErrorCallback();
+};
+
+class BluetoothStopDiscoveryFunction : public BluetoothExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetooth.stopDiscovery", BLUETOOTH_STOPDISCOVERY)
+
+ protected:
+ ~BluetoothStopDiscoveryFunction() override {}
+
+ // BluetoothExtensionFunction:
+ bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) override;
+
+ private:
+ void OnSuccessCallback();
+ void OnErrorCallback();
+};
+
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_API_H_
diff --git a/chromium/extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.cc b/chromium/extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.cc
new file mode 100644
index 00000000000..22cb6f74ffe
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.cc
@@ -0,0 +1,112 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.h"
+
+#include <utility>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "content/public/browser/browser_context.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "extensions/browser/api/bluetooth/bluetooth_api_utils.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/common/api/bluetooth_private.h"
+
+namespace extensions {
+
+namespace bt_private = api::bluetooth_private;
+
+namespace {
+
+void PopulatePairingEvent(const device::BluetoothDevice* device,
+ bt_private::PairingEventType type,
+ bt_private::PairingEvent* out) {
+ api::bluetooth::BluetoothDeviceToApiDevice(*device, &out->device);
+ out->pairing = type;
+}
+
+} // namespace
+
+BluetoothApiPairingDelegate::BluetoothApiPairingDelegate(
+ content::BrowserContext* browser_context)
+ : browser_context_(browser_context) {}
+
+BluetoothApiPairingDelegate::~BluetoothApiPairingDelegate() {}
+
+void BluetoothApiPairingDelegate::RequestPinCode(
+ device::BluetoothDevice* device) {
+ bt_private::PairingEvent event;
+ PopulatePairingEvent(
+ device, bt_private::PAIRING_EVENT_TYPE_REQUESTPINCODE, &event);
+ DispatchPairingEvent(event);
+}
+
+void BluetoothApiPairingDelegate::RequestPasskey(
+ device::BluetoothDevice* device) {
+ bt_private::PairingEvent event;
+ PopulatePairingEvent(
+ device, bt_private::PAIRING_EVENT_TYPE_REQUESTPASSKEY, &event);
+ DispatchPairingEvent(event);
+}
+
+void BluetoothApiPairingDelegate::DisplayPinCode(
+ device::BluetoothDevice* device,
+ const std::string& pincode) {
+ bt_private::PairingEvent event;
+ PopulatePairingEvent(
+ device, bt_private::PAIRING_EVENT_TYPE_DISPLAYPINCODE, &event);
+ event.pincode.reset(new std::string(pincode));
+ DispatchPairingEvent(event);
+}
+
+void BluetoothApiPairingDelegate::DisplayPasskey(
+ device::BluetoothDevice* device,
+ uint32_t passkey) {
+ bt_private::PairingEvent event;
+ PopulatePairingEvent(
+ device, bt_private::PAIRING_EVENT_TYPE_DISPLAYPASSKEY, &event);
+ event.passkey.reset(new int(passkey));
+ DispatchPairingEvent(event);
+}
+
+void BluetoothApiPairingDelegate::KeysEntered(device::BluetoothDevice* device,
+ uint32_t entered) {
+ bt_private::PairingEvent event;
+ PopulatePairingEvent(
+ device, bt_private::PAIRING_EVENT_TYPE_KEYSENTERED, &event);
+ event.entered_key.reset(new int(entered));
+ DispatchPairingEvent(event);
+}
+
+void BluetoothApiPairingDelegate::ConfirmPasskey(
+ device::BluetoothDevice* device,
+ uint32_t passkey) {
+ bt_private::PairingEvent event;
+ PopulatePairingEvent(
+ device, bt_private::PAIRING_EVENT_TYPE_CONFIRMPASSKEY, &event);
+ event.passkey.reset(new int(passkey));
+ DispatchPairingEvent(event);
+}
+
+void BluetoothApiPairingDelegate::AuthorizePairing(
+ device::BluetoothDevice* device) {
+ bt_private::PairingEvent event;
+ PopulatePairingEvent(
+ device, bt_private::PAIRING_EVENT_TYPE_REQUESTAUTHORIZATION, &event);
+ DispatchPairingEvent(event);
+}
+
+void BluetoothApiPairingDelegate::DispatchPairingEvent(
+ const bt_private::PairingEvent& pairing_event) {
+ scoped_ptr<base::ListValue> args =
+ bt_private::OnPairing::Create(pairing_event);
+ scoped_ptr<Event> event(new Event(events::BLUETOOTH_PRIVATE_ON_PAIRING,
+ bt_private::OnPairing::kEventName,
+ std::move(args)));
+ EventRouter::Get(browser_context_)->BroadcastEvent(std::move(event));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.h b/chromium/extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.h
new file mode 100644
index 00000000000..a09bcb6e06d
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.h
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_API_PAIRING_DELEGATE_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_API_PAIRING_DELEGATE_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "extensions/common/api/bluetooth_private.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+// A pairing delegate to dispatch incoming Bluetooth pairing events to the API
+// event router.
+class BluetoothApiPairingDelegate
+ : public device::BluetoothDevice::PairingDelegate {
+ public:
+ explicit BluetoothApiPairingDelegate(
+ content::BrowserContext* browser_context);
+ ~BluetoothApiPairingDelegate() override;
+
+ // device::PairingDelegate overrides:
+ void RequestPinCode(device::BluetoothDevice* device) override;
+ void RequestPasskey(device::BluetoothDevice* device) override;
+ void DisplayPinCode(device::BluetoothDevice* device,
+ const std::string& pincode) override;
+ void DisplayPasskey(device::BluetoothDevice* device,
+ uint32_t passkey) override;
+ void KeysEntered(device::BluetoothDevice* device, uint32_t entered) override;
+ void ConfirmPasskey(device::BluetoothDevice* device,
+ uint32_t passkey) override;
+ void AuthorizePairing(device::BluetoothDevice* device) override;
+
+ private:
+ // Dispatches a pairing event to the extension.
+ void DispatchPairingEvent(
+ const api::bluetooth_private::PairingEvent& pairing_event);
+
+ content::BrowserContext* browser_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothApiPairingDelegate);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_API_PAIRING_DELEGATE_H_
diff --git a/chromium/extensions/browser/api/bluetooth/bluetooth_api_utils.cc b/chromium/extensions/browser/api/bluetooth/bluetooth_api_utils.cc
new file mode 100644
index 00000000000..1b0c61e64e8
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth/bluetooth_api_utils.cc
@@ -0,0 +1,146 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth/bluetooth_api_utils.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "extensions/common/api/bluetooth.h"
+
+namespace bluetooth = extensions::api::bluetooth;
+
+using device::BluetoothDevice;
+using bluetooth::VendorIdSource;
+
+namespace {
+
+bool ConvertVendorIDSourceToApi(const BluetoothDevice::VendorIDSource& input,
+ bluetooth::VendorIdSource* output) {
+ switch (input) {
+ case BluetoothDevice::VENDOR_ID_UNKNOWN:
+ *output = bluetooth::VENDOR_ID_SOURCE_NONE;
+ return true;
+ case BluetoothDevice::VENDOR_ID_BLUETOOTH:
+ *output = bluetooth::VENDOR_ID_SOURCE_BLUETOOTH;
+ return true;
+ case BluetoothDevice::VENDOR_ID_USB:
+ *output = bluetooth::VENDOR_ID_SOURCE_USB;
+ return true;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+bool ConvertDeviceTypeToApi(const BluetoothDevice::DeviceType& input,
+ bluetooth::DeviceType* output) {
+ switch (input) {
+ case BluetoothDevice::DEVICE_UNKNOWN:
+ *output = bluetooth::DEVICE_TYPE_NONE;
+ return true;
+ case BluetoothDevice::DEVICE_COMPUTER:
+ *output = bluetooth::DEVICE_TYPE_COMPUTER;
+ return true;
+ case BluetoothDevice::DEVICE_PHONE:
+ *output = bluetooth::DEVICE_TYPE_PHONE;
+ return true;
+ case BluetoothDevice::DEVICE_MODEM:
+ *output = bluetooth::DEVICE_TYPE_MODEM;
+ return true;
+ case BluetoothDevice::DEVICE_AUDIO:
+ *output = bluetooth::DEVICE_TYPE_AUDIO;
+ return true;
+ case BluetoothDevice::DEVICE_CAR_AUDIO:
+ *output = bluetooth::DEVICE_TYPE_CARAUDIO;
+ return true;
+ case BluetoothDevice::DEVICE_VIDEO:
+ *output = bluetooth::DEVICE_TYPE_VIDEO;
+ return true;
+ case BluetoothDevice::DEVICE_PERIPHERAL:
+ *output = bluetooth::DEVICE_TYPE_PERIPHERAL;
+ return true;
+ case BluetoothDevice::DEVICE_JOYSTICK:
+ *output = bluetooth::DEVICE_TYPE_JOYSTICK;
+ return true;
+ case BluetoothDevice::DEVICE_GAMEPAD:
+ *output = bluetooth::DEVICE_TYPE_GAMEPAD;
+ return true;
+ case BluetoothDevice::DEVICE_KEYBOARD:
+ *output = bluetooth::DEVICE_TYPE_KEYBOARD;
+ return true;
+ case BluetoothDevice::DEVICE_MOUSE:
+ *output = bluetooth::DEVICE_TYPE_MOUSE;
+ return true;
+ case BluetoothDevice::DEVICE_TABLET:
+ *output = bluetooth::DEVICE_TYPE_TABLET;
+ return true;
+ case BluetoothDevice::DEVICE_KEYBOARD_MOUSE_COMBO:
+ *output = bluetooth::DEVICE_TYPE_KEYBOARDMOUSECOMBO;
+ return true;
+ default:
+ return false;
+ }
+}
+
+} // namespace
+
+namespace extensions {
+namespace api {
+namespace bluetooth {
+
+void BluetoothDeviceToApiDevice(const device::BluetoothDevice& device,
+ Device* out) {
+ out->address = device.GetAddress();
+ out->name.reset(new std::string(base::UTF16ToUTF8(device.GetName())));
+ out->device_class.reset(new int(device.GetBluetoothClass()));
+
+ // Only include the Device ID members when one exists for the device, and
+ // always include all or none.
+ if (ConvertVendorIDSourceToApi(device.GetVendorIDSource(),
+ &(out->vendor_id_source)) &&
+ out->vendor_id_source != VENDOR_ID_SOURCE_NONE) {
+ out->vendor_id.reset(new int(device.GetVendorID()));
+ out->product_id.reset(new int(device.GetProductID()));
+ out->device_id.reset(new int(device.GetDeviceID()));
+ }
+
+ ConvertDeviceTypeToApi(device.GetDeviceType(), &(out->type));
+
+ out->paired.reset(new bool(device.IsPaired()));
+ out->connected.reset(new bool(device.IsConnected()));
+ out->connecting.reset(new bool(device.IsConnecting()));
+ out->connectable.reset(new bool(device.IsConnectable()));
+
+ std::vector<std::string>* string_uuids = new std::vector<std::string>();
+ const device::BluetoothDevice::UUIDList& uuids = device.GetUUIDs();
+ for (device::BluetoothDevice::UUIDList::const_iterator iter = uuids.begin();
+ iter != uuids.end(); ++iter)
+ string_uuids->push_back(iter->canonical_value());
+ out->uuids.reset(string_uuids);
+
+ if (device.GetInquiryRSSI() != device::BluetoothDevice::kUnknownPower)
+ out->inquiry_rssi.reset(new int(device.GetInquiryRSSI()));
+ else
+ out->inquiry_rssi.reset();
+
+ if (device.GetInquiryTxPower() != device::BluetoothDevice::kUnknownPower)
+ out->inquiry_tx_power.reset(new int(device.GetInquiryTxPower()));
+ else
+ out->inquiry_tx_power.reset();
+}
+
+void PopulateAdapterState(const device::BluetoothAdapter& adapter,
+ AdapterState* out) {
+ out->discovering = adapter.IsDiscovering();
+ out->available = adapter.IsPresent();
+ out->powered = adapter.IsPowered();
+ out->name = adapter.GetName();
+ out->address = adapter.GetAddress();
+}
+
+} // namespace bluetooth
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/bluetooth/bluetooth_api_utils.h b/chromium/extensions/browser/api/bluetooth/bluetooth_api_utils.h
new file mode 100644
index 00000000000..a58eb42de23
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth/bluetooth_api_utils.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_API_UTILS_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_API_UTILS_H_
+
+#include "base/values.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "extensions/common/api/bluetooth.h"
+
+namespace extensions {
+namespace api {
+namespace bluetooth {
+
+// Fill in a Device object from a BluetoothDevice.
+void BluetoothDeviceToApiDevice(
+ const device::BluetoothDevice& device,
+ Device* out);
+
+// Fill in an AdapterState object from a BluetoothAdapter.
+void PopulateAdapterState(const device::BluetoothAdapter& adapter,
+ AdapterState* out);
+
+} // namespace bluetooth
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_API_UTILS_H_
diff --git a/chromium/extensions/browser/api/bluetooth/bluetooth_apitest.cc b/chromium/extensions/browser/api/bluetooth/bluetooth_apitest.cc
new file mode 100644
index 00000000000..184c4c8225c
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth/bluetooth_apitest.cc
@@ -0,0 +1,457 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string.h>
+
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/extensions/extension_function_test_utils.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_uuid.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "device/bluetooth/test/mock_bluetooth_device.h"
+#include "device/bluetooth/test/mock_bluetooth_discovery_session.h"
+#include "extensions/browser/api/bluetooth/bluetooth_api.h"
+#include "extensions/browser/api/bluetooth/bluetooth_event_router.h"
+#include "extensions/common/test_util.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "extensions/test/result_catcher.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using device::BluetoothAdapter;
+using device::BluetoothDevice;
+using device::BluetoothDiscoverySession;
+using device::BluetoothUUID;
+using device::MockBluetoothAdapter;
+using device::MockBluetoothDevice;
+using device::MockBluetoothDiscoverySession;
+using extensions::Extension;
+using extensions::ResultCatcher;
+
+namespace utils = extension_function_test_utils;
+namespace api = extensions::api;
+
+namespace {
+
+static const char* kAdapterAddress = "A1:A2:A3:A4:A5:A6";
+static const char* kName = "whatsinaname";
+
+class BluetoothApiTest : public ExtensionApiTest {
+ public:
+ BluetoothApiTest() {}
+
+ void SetUpOnMainThread() override {
+ ExtensionApiTest::SetUpOnMainThread();
+ empty_extension_ = extensions::test_util::CreateEmptyExtension();
+ SetUpMockAdapter();
+ }
+
+ void TearDownOnMainThread() override {
+ EXPECT_CALL(*mock_adapter_, RemoveObserver(testing::_));
+ }
+
+ void SetUpMockAdapter() {
+ // The browser will clean this up when it is torn down
+ mock_adapter_ = new testing::StrictMock<MockBluetoothAdapter>();
+ event_router()->SetAdapterForTest(mock_adapter_);
+
+ device1_.reset(new testing::NiceMock<MockBluetoothDevice>(
+ mock_adapter_, 0, "d1", "11:12:13:14:15:16",
+ true /* paired */, true /* connected */));
+ device2_.reset(new testing::NiceMock<MockBluetoothDevice>(
+ mock_adapter_, 0, "d2", "21:22:23:24:25:26",
+ false /* paired */, false /* connected */));
+ device3_.reset(new testing::NiceMock<MockBluetoothDevice>(
+ mock_adapter_, 0, "d3", "31:32:33:34:35:36",
+ false /* paired */, false /* connected */));
+ }
+
+ void DiscoverySessionCallback(
+ const BluetoothAdapter::DiscoverySessionCallback& callback,
+ const BluetoothAdapter::ErrorCallback& error_callback) {
+ if (mock_session_.get()) {
+ callback.Run(
+ scoped_ptr<BluetoothDiscoverySession>(mock_session_.release()));
+ return;
+ }
+ error_callback.Run();
+ }
+
+ template <class T>
+ T* setupFunction(T* function) {
+ function->set_extension(empty_extension_.get());
+ function->set_has_callback(true);
+ return function;
+ }
+
+ protected:
+ testing::StrictMock<MockBluetoothAdapter>* mock_adapter_;
+ scoped_ptr<testing::NiceMock<MockBluetoothDiscoverySession> > mock_session_;
+ scoped_ptr<testing::NiceMock<MockBluetoothDevice> > device1_;
+ scoped_ptr<testing::NiceMock<MockBluetoothDevice> > device2_;
+ scoped_ptr<testing::NiceMock<MockBluetoothDevice> > device3_;
+
+ extensions::BluetoothEventRouter* event_router() {
+ return bluetooth_api()->event_router();
+ }
+
+ extensions::BluetoothAPI* bluetooth_api() {
+ return extensions::BluetoothAPI::Get(browser()->profile());
+ }
+
+ private:
+ scoped_refptr<Extension> empty_extension_;
+};
+
+static void StopDiscoverySessionCallback(const base::Closure& callback,
+ const base::Closure& error_callback) {
+ callback.Run();
+}
+
+} // namespace
+
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, GetAdapterState) {
+ EXPECT_CALL(*mock_adapter_, GetAddress())
+ .WillOnce(testing::Return(kAdapterAddress));
+ EXPECT_CALL(*mock_adapter_, GetName())
+ .WillOnce(testing::Return(kName));
+ EXPECT_CALL(*mock_adapter_, IsPresent())
+ .WillOnce(testing::Return(false));
+ EXPECT_CALL(*mock_adapter_, IsPowered())
+ .WillOnce(testing::Return(true));
+ EXPECT_CALL(*mock_adapter_, IsDiscovering())
+ .WillOnce(testing::Return(false));
+
+ scoped_refptr<api::BluetoothGetAdapterStateFunction> get_adapter_state;
+ get_adapter_state = setupFunction(new api::BluetoothGetAdapterStateFunction);
+
+ scoped_ptr<base::Value> result(utils::RunFunctionAndReturnSingleResult(
+ get_adapter_state.get(), "[]", browser()));
+ ASSERT_TRUE(result.get() != NULL);
+ api::bluetooth::AdapterState state;
+ ASSERT_TRUE(api::bluetooth::AdapterState::Populate(*result, &state));
+
+ EXPECT_FALSE(state.available);
+ EXPECT_TRUE(state.powered);
+ EXPECT_FALSE(state.discovering);
+ EXPECT_EQ(kName, state.name);
+ EXPECT_EQ(kAdapterAddress, state.address);
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, DeviceEvents) {
+ ResultCatcher catcher;
+ catcher.RestrictToBrowserContext(browser()->profile());
+
+ ASSERT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("bluetooth/device_events")));
+
+ ExtensionTestMessageListener events_received("ready", true);
+ event_router()->DeviceAdded(mock_adapter_, device1_.get());
+ event_router()->DeviceAdded(mock_adapter_, device2_.get());
+
+ EXPECT_CALL(*device2_.get(), GetDeviceName())
+ .WillRepeatedly(testing::Return("the real d2"));
+ EXPECT_CALL(*device2_.get(), GetName())
+ .WillRepeatedly(testing::Return(base::UTF8ToUTF16("the real d2")));
+ event_router()->DeviceChanged(mock_adapter_, device2_.get());
+
+ event_router()->DeviceAdded(mock_adapter_, device3_.get());
+ event_router()->DeviceRemoved(mock_adapter_, device1_.get());
+ EXPECT_TRUE(events_received.WaitUntilSatisfied());
+ events_received.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, Discovery) {
+ // Try with a failure to start. This will return an error as we haven't
+ // initialied a session object.
+ EXPECT_CALL(*mock_adapter_, StartDiscoverySession(testing::_, testing::_))
+ .WillOnce(
+ testing::Invoke(this, &BluetoothApiTest::DiscoverySessionCallback));
+
+ // StartDiscovery failure will not reference the adapter.
+ scoped_refptr<api::BluetoothStartDiscoveryFunction> start_function;
+ start_function = setupFunction(new api::BluetoothStartDiscoveryFunction);
+ std::string error(
+ utils::RunFunctionAndReturnError(start_function.get(), "[]", browser()));
+ ASSERT_FALSE(error.empty());
+
+ // Reset the adapter and initiate a discovery session. The ownership of the
+ // mock session will be passed to the event router.
+ ASSERT_FALSE(mock_session_.get());
+ SetUpMockAdapter();
+
+ // Create a mock session to be returned as a result. Get a handle to it as
+ // its ownership will be passed and |mock_session_| will be reset.
+ mock_session_.reset(new testing::NiceMock<MockBluetoothDiscoverySession>());
+ MockBluetoothDiscoverySession* session = mock_session_.get();
+ EXPECT_CALL(*mock_adapter_, StartDiscoverySession(testing::_, testing::_))
+ .WillOnce(
+ testing::Invoke(this, &BluetoothApiTest::DiscoverySessionCallback));
+ start_function = setupFunction(new api::BluetoothStartDiscoveryFunction);
+ (void)
+ utils::RunFunctionAndReturnError(start_function.get(), "[]", browser());
+
+ // End the discovery session. The StopDiscovery function should succeed.
+ testing::Mock::VerifyAndClearExpectations(mock_adapter_);
+ EXPECT_CALL(*session, IsActive()).WillOnce(testing::Return(true));
+ EXPECT_CALL(*session, Stop(testing::_, testing::_))
+ .WillOnce(testing::Invoke(StopDiscoverySessionCallback));
+
+ // StopDiscovery success will remove the session object, unreferencing the
+ // adapter.
+ scoped_refptr<api::BluetoothStopDiscoveryFunction> stop_function;
+ stop_function = setupFunction(new api::BluetoothStopDiscoveryFunction);
+ (void) utils::RunFunctionAndReturnSingleResult(
+ stop_function.get(), "[]", browser());
+
+ // Reset the adapter. Simulate failure for stop discovery. The event router
+ // still owns the session. Make it appear inactive.
+ SetUpMockAdapter();
+ EXPECT_CALL(*session, IsActive()).WillOnce(testing::Return(false));
+ stop_function = setupFunction(new api::BluetoothStopDiscoveryFunction);
+ error =
+ utils::RunFunctionAndReturnError(stop_function.get(), "[]", browser());
+ ASSERT_FALSE(error.empty());
+ SetUpMockAdapter();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, DiscoveryCallback) {
+ mock_session_.reset(new testing::NiceMock<MockBluetoothDiscoverySession>());
+ MockBluetoothDiscoverySession* session = mock_session_.get();
+ EXPECT_CALL(*mock_adapter_, StartDiscoverySession(testing::_, testing::_))
+ .WillOnce(
+ testing::Invoke(this, &BluetoothApiTest::DiscoverySessionCallback));
+ EXPECT_CALL(*session, IsActive()).WillOnce(testing::Return(true));
+ EXPECT_CALL(*session, Stop(testing::_, testing::_))
+ .WillOnce(testing::Invoke(StopDiscoverySessionCallback));
+
+ ResultCatcher catcher;
+ catcher.RestrictToBrowserContext(browser()->profile());
+
+ ExtensionTestMessageListener discovery_started("ready", true);
+ ASSERT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("bluetooth/discovery_callback")));
+ EXPECT_TRUE(discovery_started.WaitUntilSatisfied());
+
+ event_router()->DeviceAdded(mock_adapter_, device1_.get());
+
+ discovery_started.Reply("go");
+ ExtensionTestMessageListener discovery_stopped("ready", true);
+ EXPECT_CALL(*mock_adapter_, RemoveObserver(testing::_));
+ EXPECT_TRUE(discovery_stopped.WaitUntilSatisfied());
+
+ SetUpMockAdapter();
+ event_router()->DeviceAdded(mock_adapter_, device2_.get());
+ discovery_stopped.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, DiscoveryInProgress) {
+ EXPECT_CALL(*mock_adapter_, GetAddress())
+ .WillOnce(testing::Return(kAdapterAddress));
+ EXPECT_CALL(*mock_adapter_, GetName())
+ .WillOnce(testing::Return(kName));
+ EXPECT_CALL(*mock_adapter_, IsPresent())
+ .WillOnce(testing::Return(true));
+ EXPECT_CALL(*mock_adapter_, IsPowered())
+ .WillOnce(testing::Return(true));
+
+ // Fake that the adapter is discovering
+ EXPECT_CALL(*mock_adapter_, IsDiscovering())
+ .WillOnce(testing::Return(true));
+ event_router()->AdapterDiscoveringChanged(mock_adapter_, true);
+
+ // Cache a device before the extension starts discovering
+ event_router()->DeviceAdded(mock_adapter_, device1_.get());
+
+ ResultCatcher catcher;
+ catcher.RestrictToBrowserContext(browser()->profile());
+
+ mock_session_.reset(new testing::NiceMock<MockBluetoothDiscoverySession>());
+ MockBluetoothDiscoverySession* session = mock_session_.get();
+ EXPECT_CALL(*mock_adapter_, StartDiscoverySession(testing::_, testing::_))
+ .WillOnce(
+ testing::Invoke(this, &BluetoothApiTest::DiscoverySessionCallback));
+ EXPECT_CALL(*session, IsActive()).WillOnce(testing::Return(true));
+ EXPECT_CALL(*session, Stop(testing::_, testing::_))
+ .WillOnce(testing::Invoke(StopDiscoverySessionCallback));
+
+ ExtensionTestMessageListener discovery_started("ready", true);
+ ASSERT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("bluetooth/discovery_in_progress")));
+ EXPECT_TRUE(discovery_started.WaitUntilSatisfied());
+
+ // Only this should be received. No additional notification should be sent for
+ // devices discovered before the discovery session started.
+ event_router()->DeviceAdded(mock_adapter_, device2_.get());
+
+ discovery_started.Reply("go");
+ ExtensionTestMessageListener discovery_stopped("ready", true);
+ EXPECT_CALL(*mock_adapter_, RemoveObserver(testing::_));
+ EXPECT_TRUE(discovery_stopped.WaitUntilSatisfied());
+
+ SetUpMockAdapter();
+ // This should never be received.
+ event_router()->DeviceAdded(mock_adapter_, device2_.get());
+ discovery_stopped.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, OnAdapterStateChanged) {
+ ResultCatcher catcher;
+ catcher.RestrictToBrowserContext(browser()->profile());
+
+ // Load and wait for setup
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(
+ LoadExtension(
+ test_data_dir_.AppendASCII("bluetooth/on_adapter_state_changed")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ EXPECT_CALL(*mock_adapter_, GetAddress())
+ .WillOnce(testing::Return(kAdapterAddress));
+ EXPECT_CALL(*mock_adapter_, GetName())
+ .WillOnce(testing::Return(kName));
+ EXPECT_CALL(*mock_adapter_, IsPresent())
+ .WillOnce(testing::Return(false));
+ EXPECT_CALL(*mock_adapter_, IsPowered())
+ .WillOnce(testing::Return(false));
+ EXPECT_CALL(*mock_adapter_, IsDiscovering())
+ .WillOnce(testing::Return(false));
+ event_router()->AdapterPoweredChanged(mock_adapter_, false);
+
+ EXPECT_CALL(*mock_adapter_, GetAddress())
+ .WillOnce(testing::Return(kAdapterAddress));
+ EXPECT_CALL(*mock_adapter_, GetName())
+ .WillOnce(testing::Return(kName));
+ EXPECT_CALL(*mock_adapter_, IsPresent())
+ .WillOnce(testing::Return(true));
+ EXPECT_CALL(*mock_adapter_, IsPowered())
+ .WillOnce(testing::Return(true));
+ EXPECT_CALL(*mock_adapter_, IsDiscovering())
+ .WillOnce(testing::Return(true));
+ event_router()->AdapterPresentChanged(mock_adapter_, true);
+
+ EXPECT_CALL(*mock_adapter_, GetAddress())
+ .WillOnce(testing::Return(kAdapterAddress));
+ EXPECT_CALL(*mock_adapter_, GetName())
+ .WillOnce(testing::Return(kName));
+ EXPECT_CALL(*mock_adapter_, IsPresent())
+ .WillOnce(testing::Return(true));
+ EXPECT_CALL(*mock_adapter_, IsPowered())
+ .WillOnce(testing::Return(true));
+ EXPECT_CALL(*mock_adapter_, IsDiscovering())
+ .WillOnce(testing::Return(true));
+ event_router()->AdapterDiscoveringChanged(mock_adapter_, true);
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, GetDevices) {
+ ResultCatcher catcher;
+ catcher.RestrictToBrowserContext(browser()->profile());
+
+ BluetoothAdapter::ConstDeviceList devices;
+ devices.push_back(device1_.get());
+ devices.push_back(device2_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevices())
+ .Times(1)
+ .WillRepeatedly(testing::Return(devices));
+
+ // Load and wait for setup
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(
+ LoadExtension(test_data_dir_.AppendASCII("bluetooth/get_devices")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, GetDevice) {
+ ResultCatcher catcher;
+ catcher.RestrictToBrowserContext(browser()->profile());
+
+ EXPECT_CALL(*mock_adapter_, GetDevice(device1_->GetAddress()))
+ .WillOnce(testing::Return(device1_.get()));
+ EXPECT_CALL(*mock_adapter_, GetDevice(device2_->GetAddress()))
+ .Times(1)
+ .WillRepeatedly(testing::Return(static_cast<BluetoothDevice*>(NULL)));
+
+ // Load and wait for setup
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(
+ LoadExtension(test_data_dir_.AppendASCII("bluetooth/get_device")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothApiTest, DeviceInfo) {
+ ResultCatcher catcher;
+ catcher.RestrictToBrowserContext(browser()->profile());
+
+ // Set up the first device object to reflect a real-world device.
+ BluetoothAdapter::ConstDeviceList devices;
+
+ EXPECT_CALL(*device1_.get(), GetAddress())
+ .WillRepeatedly(testing::Return("A4:17:31:00:00:00"));
+ EXPECT_CALL(*device1_.get(), GetDeviceName())
+ .WillRepeatedly(testing::Return("Chromebook Pixel"));
+ EXPECT_CALL(*device1_.get(), GetName())
+ .WillRepeatedly(testing::Return(base::UTF8ToUTF16("Chromebook Pixel")));
+ EXPECT_CALL(*device1_.get(), GetBluetoothClass())
+ .WillRepeatedly(testing::Return(0x080104));
+ EXPECT_CALL(*device1_.get(), GetDeviceType())
+ .WillRepeatedly(testing::Return(BluetoothDevice::DEVICE_COMPUTER));
+ EXPECT_CALL(*device1_.get(), GetVendorIDSource())
+ .WillRepeatedly(testing::Return(BluetoothDevice::VENDOR_ID_BLUETOOTH));
+ EXPECT_CALL(*device1_.get(), GetVendorID())
+ .WillRepeatedly(testing::Return(0x00E0));
+ EXPECT_CALL(*device1_.get(), GetProductID())
+ .WillRepeatedly(testing::Return(0x240A));
+ EXPECT_CALL(*device1_.get(), GetDeviceID())
+ .WillRepeatedly(testing::Return(0x0400));
+
+ BluetoothDevice::UUIDList uuids;
+ uuids.push_back(BluetoothUUID("1105"));
+ uuids.push_back(BluetoothUUID("1106"));
+
+ EXPECT_CALL(*device1_.get(), GetUUIDs())
+ .WillOnce(testing::Return(uuids));
+
+ devices.push_back(device1_.get());
+
+ // Leave the second largely empty so we can check a device without
+ // available information.
+ devices.push_back(device2_.get());
+
+ EXPECT_CALL(*mock_adapter_, GetDevices())
+ .Times(1)
+ .WillRepeatedly(testing::Return(devices));
+
+ // Load and wait for setup
+ ExtensionTestMessageListener listener("ready", true);
+ ASSERT_TRUE(
+ LoadExtension(test_data_dir_.AppendASCII("bluetooth/device_info")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
diff --git a/chromium/extensions/browser/api/bluetooth/bluetooth_event_router.cc b/chromium/extensions/browser/api/bluetooth/bluetooth_event_router.cc
new file mode 100644
index 00000000000..0f9345b4d0b
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth/bluetooth_event_router.cc
@@ -0,0 +1,476 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth/bluetooth_event_router.h"
+
+#include <map>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/stl_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_source.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "device/bluetooth/bluetooth_discovery_session.h"
+#include "extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.h"
+#include "extensions/browser/api/bluetooth/bluetooth_api_utils.h"
+#include "extensions/browser/api/bluetooth/bluetooth_private_api.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_host.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/notification_types.h"
+#include "extensions/common/api/bluetooth.h"
+#include "extensions/common/api/bluetooth_private.h"
+
+namespace extensions {
+
+namespace {
+
+void IgnoreAdapterResult(scoped_refptr<device::BluetoothAdapter> adapter) {}
+
+void IgnoreAdapterResultAndThen(
+ const base::Closure& callback,
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ callback.Run();
+}
+
+} // namespace
+
+namespace bluetooth = api::bluetooth;
+namespace bt_private = api::bluetooth_private;
+
+BluetoothEventRouter::BluetoothEventRouter(content::BrowserContext* context)
+ : browser_context_(context),
+ adapter_(nullptr),
+ num_event_listeners_(0),
+ extension_registry_observer_(this),
+ weak_ptr_factory_(this) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK(browser_context_);
+ registrar_.Add(this,
+ extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED,
+ content::Source<content::BrowserContext>(browser_context_));
+ extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
+}
+
+BluetoothEventRouter::~BluetoothEventRouter() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ if (adapter_.get()) {
+ adapter_->RemoveObserver(this);
+ adapter_ = nullptr;
+ }
+ CleanUpAllExtensions();
+}
+
+bool BluetoothEventRouter::IsBluetoothSupported() const {
+ return adapter_.get() ||
+ device::BluetoothAdapterFactory::IsBluetoothAdapterAvailable();
+}
+
+void BluetoothEventRouter::GetAdapter(
+ const device::BluetoothAdapterFactory::AdapterCallback& callback) {
+ if (adapter_.get()) {
+ callback.Run(scoped_refptr<device::BluetoothAdapter>(adapter_));
+ return;
+ }
+
+ device::BluetoothAdapterFactory::GetAdapter(
+ base::Bind(&BluetoothEventRouter::OnAdapterInitialized,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+}
+
+void BluetoothEventRouter::StartDiscoverySession(
+ device::BluetoothAdapter* adapter,
+ const std::string& extension_id,
+ const base::Closure& callback,
+ const base::Closure& error_callback) {
+ if (!adapter_.get() && IsBluetoothSupported()) {
+ // If |adapter_| isn't set yet, call GetAdapter() which will synchronously
+ // invoke the callback (StartDiscoverySessionImpl).
+ GetAdapter(base::Bind(
+ &IgnoreAdapterResultAndThen,
+ base::Bind(&BluetoothEventRouter::StartDiscoverySessionImpl,
+ weak_ptr_factory_.GetWeakPtr(), base::RetainedRef(adapter),
+ extension_id, callback, error_callback)));
+ return;
+ }
+ StartDiscoverySessionImpl(adapter, extension_id, callback, error_callback);
+}
+
+void BluetoothEventRouter::StartDiscoverySessionImpl(
+ device::BluetoothAdapter* adapter,
+ const std::string& extension_id,
+ const base::Closure& callback,
+ const base::Closure& error_callback) {
+ if (!adapter_.get()) {
+ LOG(ERROR) << "Unable to get Bluetooth adapter.";
+ error_callback.Run();
+ return;
+ }
+ if (adapter != adapter_.get()) {
+ LOG(ERROR) << "Bluetooth adapter mismatch.";
+ error_callback.Run();
+ return;
+ }
+ DiscoverySessionMap::iterator iter =
+ discovery_session_map_.find(extension_id);
+ if (iter != discovery_session_map_.end() && iter->second->IsActive()) {
+ DVLOG(1) << "An active discovery session exists for extension.";
+ error_callback.Run();
+ return;
+ }
+
+ // Check whether user pre set discovery filter by calling SetDiscoveryFilter
+ // before. If the user has set a discovery filter then start a filtered
+ // discovery session, otherwise start a regular session
+ PreSetFilterMap::iterator pre_set_iter =
+ pre_set_filter_map_.find(extension_id);
+ if (pre_set_iter != pre_set_filter_map_.end()) {
+ adapter->StartDiscoverySessionWithFilter(
+ scoped_ptr<device::BluetoothDiscoveryFilter>(pre_set_iter->second),
+ base::Bind(&BluetoothEventRouter::OnStartDiscoverySession,
+ weak_ptr_factory_.GetWeakPtr(), extension_id, callback),
+ error_callback);
+ pre_set_filter_map_.erase(pre_set_iter);
+ return;
+ }
+ adapter->StartDiscoverySession(
+ base::Bind(&BluetoothEventRouter::OnStartDiscoverySession,
+ weak_ptr_factory_.GetWeakPtr(), extension_id, callback),
+ error_callback);
+}
+
+void BluetoothEventRouter::StopDiscoverySession(
+ device::BluetoothAdapter* adapter,
+ const std::string& extension_id,
+ const base::Closure& callback,
+ const base::Closure& error_callback) {
+ if (adapter != adapter_.get()) {
+ error_callback.Run();
+ return;
+ }
+ DiscoverySessionMap::iterator iter =
+ discovery_session_map_.find(extension_id);
+ if (iter == discovery_session_map_.end() || !iter->second->IsActive()) {
+ DVLOG(1) << "No active discovery session exists for extension.";
+ error_callback.Run();
+ return;
+ }
+ device::BluetoothDiscoverySession* session = iter->second;
+ session->Stop(callback, error_callback);
+}
+
+void BluetoothEventRouter::SetDiscoveryFilter(
+ scoped_ptr<device::BluetoothDiscoveryFilter> discovery_filter,
+ device::BluetoothAdapter* adapter,
+ const std::string& extension_id,
+ const base::Closure& callback,
+ const base::Closure& error_callback) {
+ DVLOG(1) << "SetDiscoveryFilter";
+ if (adapter != adapter_.get()) {
+ error_callback.Run();
+ return;
+ }
+
+ DiscoverySessionMap::iterator iter =
+ discovery_session_map_.find(extension_id);
+ if (iter == discovery_session_map_.end() || !iter->second->IsActive()) {
+ DVLOG(1) << "No active discovery session exists for extension, so caching "
+ "filter for later use.";
+ pre_set_filter_map_[extension_id] = discovery_filter.release();
+ callback.Run();
+ return;
+ }
+
+ // extension is already running discovery, update it's discovery filter
+ iter->second->SetDiscoveryFilter(std::move(discovery_filter), callback,
+ error_callback);
+}
+
+BluetoothApiPairingDelegate* BluetoothEventRouter::GetPairingDelegate(
+ const std::string& extension_id) {
+ return ContainsKey(pairing_delegate_map_, extension_id)
+ ? pairing_delegate_map_[extension_id]
+ : nullptr;
+}
+
+void BluetoothEventRouter::OnAdapterInitialized(
+ const device::BluetoothAdapterFactory::AdapterCallback& callback,
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ if (!adapter_.get()) {
+ adapter_ = adapter;
+ adapter_->AddObserver(this);
+ }
+
+ callback.Run(adapter);
+}
+
+void BluetoothEventRouter::MaybeReleaseAdapter() {
+ if (adapter_.get() && num_event_listeners_ == 0 &&
+ pairing_delegate_map_.empty()) {
+ VLOG(1) << "Releasing Adapter.";
+ adapter_->RemoveObserver(this);
+ adapter_ = nullptr;
+ }
+}
+
+void BluetoothEventRouter::AddPairingDelegate(const std::string& extension_id) {
+ if (!adapter_.get() && IsBluetoothSupported()) {
+ GetAdapter(
+ base::Bind(&IgnoreAdapterResultAndThen,
+ base::Bind(&BluetoothEventRouter::AddPairingDelegateImpl,
+ weak_ptr_factory_.GetWeakPtr(), extension_id)));
+ return;
+ }
+ AddPairingDelegateImpl(extension_id);
+}
+
+void BluetoothEventRouter::AddPairingDelegateImpl(
+ const std::string& extension_id) {
+ if (!adapter_.get()) {
+ LOG(ERROR) << "Unable to get adapter for extension_id: " << extension_id;
+ return;
+ }
+ if (ContainsKey(pairing_delegate_map_, extension_id)) {
+ // For WebUI there may be more than one page open to the same url
+ // (e.g. chrome://settings). These will share the same pairing delegate.
+ VLOG(1) << "Pairing delegate already exists for extension_id: "
+ << extension_id;
+ return;
+ }
+ BluetoothApiPairingDelegate* delegate =
+ new BluetoothApiPairingDelegate(browser_context_);
+ DCHECK(adapter_.get());
+ adapter_->AddPairingDelegate(
+ delegate, device::BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_HIGH);
+ pairing_delegate_map_[extension_id] = delegate;
+}
+
+void BluetoothEventRouter::RemovePairingDelegate(
+ const std::string& extension_id) {
+ if (ContainsKey(pairing_delegate_map_, extension_id)) {
+ BluetoothApiPairingDelegate* delegate = pairing_delegate_map_[extension_id];
+ if (adapter_.get())
+ adapter_->RemovePairingDelegate(delegate);
+ pairing_delegate_map_.erase(extension_id);
+ delete delegate;
+ MaybeReleaseAdapter();
+ }
+}
+
+void BluetoothEventRouter::AdapterPresentChanged(
+ device::BluetoothAdapter* adapter,
+ bool present) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ if (adapter != adapter_.get()) {
+ DVLOG(1) << "Ignoring event for adapter " << adapter->GetAddress();
+ return;
+ }
+ DispatchAdapterStateEvent();
+}
+
+void BluetoothEventRouter::AdapterPoweredChanged(
+ device::BluetoothAdapter* adapter,
+ bool has_power) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ if (adapter != adapter_.get()) {
+ DVLOG(1) << "Ignoring event for adapter " << adapter->GetAddress();
+ return;
+ }
+ DispatchAdapterStateEvent();
+}
+
+void BluetoothEventRouter::AdapterDiscoveringChanged(
+ device::BluetoothAdapter* adapter,
+ bool discovering) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ if (adapter != adapter_.get()) {
+ DVLOG(1) << "Ignoring event for adapter " << adapter->GetAddress();
+ return;
+ }
+
+ if (!discovering) {
+ // If any discovery sessions are inactive, clean them up.
+ DiscoverySessionMap active_session_map;
+ for (DiscoverySessionMap::iterator iter = discovery_session_map_.begin();
+ iter != discovery_session_map_.end();
+ ++iter) {
+ device::BluetoothDiscoverySession* session = iter->second;
+ if (session->IsActive()) {
+ active_session_map[iter->first] = session;
+ continue;
+ }
+ delete session;
+ }
+ discovery_session_map_.swap(active_session_map);
+ }
+
+ DispatchAdapterStateEvent();
+
+ // Release the adapter after dispatching the event.
+ if (!discovering)
+ MaybeReleaseAdapter();
+}
+
+void BluetoothEventRouter::DeviceAdded(device::BluetoothAdapter* adapter,
+ device::BluetoothDevice* device) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ if (adapter != adapter_.get()) {
+ DVLOG(1) << "Ignoring event for adapter " << adapter->GetAddress();
+ return;
+ }
+
+ DispatchDeviceEvent(events::BLUETOOTH_ON_DEVICE_ADDED,
+ bluetooth::OnDeviceAdded::kEventName, device);
+}
+
+void BluetoothEventRouter::DeviceChanged(device::BluetoothAdapter* adapter,
+ device::BluetoothDevice* device) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ if (adapter != adapter_.get()) {
+ DVLOG(1) << "Ignoring event for adapter " << adapter->GetAddress();
+ return;
+ }
+
+ DispatchDeviceEvent(events::BLUETOOTH_ON_DEVICE_CHANGED,
+ bluetooth::OnDeviceChanged::kEventName, device);
+}
+
+void BluetoothEventRouter::DeviceRemoved(device::BluetoothAdapter* adapter,
+ device::BluetoothDevice* device) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ if (adapter != adapter_.get()) {
+ DVLOG(1) << "Ignoring event for adapter " << adapter->GetAddress();
+ return;
+ }
+
+ DispatchDeviceEvent(events::BLUETOOTH_ON_DEVICE_REMOVED,
+ bluetooth::OnDeviceRemoved::kEventName, device);
+}
+
+void BluetoothEventRouter::OnListenerAdded() {
+ num_event_listeners_++;
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ if (!adapter_.get())
+ GetAdapter(base::Bind(&IgnoreAdapterResult));
+}
+
+void BluetoothEventRouter::OnListenerRemoved() {
+ if (num_event_listeners_ > 0)
+ num_event_listeners_--;
+ MaybeReleaseAdapter();
+}
+
+void BluetoothEventRouter::DispatchAdapterStateEvent() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ api::bluetooth::AdapterState state;
+ CHECK(adapter_.get());
+ PopulateAdapterState(*adapter_.get(), &state);
+
+ scoped_ptr<base::ListValue> args =
+ bluetooth::OnAdapterStateChanged::Create(state);
+ scoped_ptr<Event> event(
+ new Event(events::BLUETOOTH_ON_ADAPTER_STATE_CHANGED,
+ bluetooth::OnAdapterStateChanged::kEventName, std::move(args)));
+ EventRouter::Get(browser_context_)->BroadcastEvent(std::move(event));
+}
+
+void BluetoothEventRouter::DispatchDeviceEvent(
+ events::HistogramValue histogram_value,
+ const std::string& event_name,
+ device::BluetoothDevice* device) {
+ bluetooth::Device extension_device;
+ CHECK(device);
+ bluetooth::BluetoothDeviceToApiDevice(*device, &extension_device);
+
+ scoped_ptr<base::ListValue> args =
+ bluetooth::OnDeviceAdded::Create(extension_device);
+ scoped_ptr<Event> event(
+ new Event(histogram_value, event_name, std::move(args)));
+ EventRouter::Get(browser_context_)->BroadcastEvent(std::move(event));
+}
+
+void BluetoothEventRouter::CleanUpForExtension(
+ const std::string& extension_id) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ RemovePairingDelegate(extension_id);
+
+ PreSetFilterMap::iterator pre_set_iter =
+ pre_set_filter_map_.find(extension_id);
+ if (pre_set_iter != pre_set_filter_map_.end()) {
+ delete pre_set_iter->second;
+ pre_set_filter_map_.erase(pre_set_iter);
+ }
+
+ // Remove any discovery session initiated by the extension.
+ DiscoverySessionMap::iterator session_iter =
+ discovery_session_map_.find(extension_id);
+ if (session_iter == discovery_session_map_.end())
+ return;
+ delete session_iter->second;
+ discovery_session_map_.erase(session_iter);
+}
+
+void BluetoothEventRouter::CleanUpAllExtensions() {
+ for (auto& it : pre_set_filter_map_)
+ delete it.second;
+
+ pre_set_filter_map_.clear();
+
+ for (auto& it : discovery_session_map_)
+ delete it.second;
+
+ discovery_session_map_.clear();
+
+ PairingDelegateMap::iterator pairing_iter = pairing_delegate_map_.begin();
+ while (pairing_iter != pairing_delegate_map_.end())
+ RemovePairingDelegate(pairing_iter++->first);
+}
+
+void BluetoothEventRouter::OnStartDiscoverySession(
+ const std::string& extension_id,
+ const base::Closure& callback,
+ scoped_ptr<device::BluetoothDiscoverySession> discovery_session) {
+ // Clean up any existing session instance for the extension.
+ DiscoverySessionMap::iterator iter =
+ discovery_session_map_.find(extension_id);
+ if (iter != discovery_session_map_.end())
+ delete iter->second;
+ discovery_session_map_[extension_id] = discovery_session.release();
+ callback.Run();
+}
+
+void BluetoothEventRouter::OnSetDiscoveryFilter(const std::string& extension_id,
+ const base::Closure& callback) {
+ DVLOG(1) << "Successfully set DiscoveryFilter.";
+ callback.Run();
+}
+
+void BluetoothEventRouter::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK_EQ(extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED, type);
+ ExtensionHost* host = content::Details<ExtensionHost>(details).ptr();
+ CleanUpForExtension(host->extension_id());
+}
+
+void BluetoothEventRouter::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ CleanUpForExtension(extension->id());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/bluetooth/bluetooth_event_router.h b/chromium/extensions/browser/api/bluetooth/bluetooth_event_router.h
new file mode 100644
index 00000000000..6afaac0d8d5
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth/bluetooth_event_router.h
@@ -0,0 +1,192 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_EVENT_ROUTER_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_EVENT_ROUTER_H_
+
+#include <map>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/scoped_observer.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "extensions/browser/extension_event_histogram_value.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/common/api/bluetooth.h"
+#include "extensions/common/api/bluetooth_private.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace device {
+
+class BluetoothDevice;
+class BluetoothDiscoverySession;
+
+} // namespace device
+
+namespace extensions {
+class BluetoothApiPairingDelegate;
+class ExtensionRegistry;
+
+class BluetoothEventRouter : public device::BluetoothAdapter::Observer,
+ public content::NotificationObserver,
+ public ExtensionRegistryObserver {
+ public:
+ explicit BluetoothEventRouter(content::BrowserContext* context);
+ ~BluetoothEventRouter() override;
+
+ // Returns true if adapter_ has been initialized for testing or bluetooth
+ // adapter is available for the current platform.
+ bool IsBluetoothSupported() const;
+
+ void GetAdapter(
+ const device::BluetoothAdapterFactory::AdapterCallback& callback);
+
+ // Requests that a new device discovery session be initiated for extension
+ // with id |extension_id|. |callback| is called, if a session has been
+ // initiated. |error_callback| is called, if the adapter failed to initiate
+ // the session or if an active session already exists for the extension.
+ void StartDiscoverySession(device::BluetoothAdapter* adapter,
+ const std::string& extension_id,
+ const base::Closure& callback,
+ const base::Closure& error_callback);
+
+ // Requests that the active discovery session that belongs to the extension
+ // with id |extension_id| be terminated. |callback| is called, if the session
+ // successfully ended. |error_callback| is called, if the adapter failed to
+ // terminate the session or if no active discovery session exists for the
+ // extension.
+ void StopDiscoverySession(device::BluetoothAdapter* adapter,
+ const std::string& extension_id,
+ const base::Closure& callback,
+ const base::Closure& error_callback);
+
+ // Requests that the filter associated with discovery session that belongs
+ // to the extension with id |extension_id| be set to |discovery_filter|.
+ // Callback is called, if the filter was successfully updated.
+ // |error_callback| is called, if filter update failed.
+ void SetDiscoveryFilter(
+ scoped_ptr<device::BluetoothDiscoveryFilter> discovery_filter,
+ device::BluetoothAdapter* adapter,
+ const std::string& extension_id,
+ const base::Closure& callback,
+ const base::Closure& error_callback);
+
+ // Called when a bluetooth event listener is added.
+ void OnListenerAdded();
+
+ // Called when a bluetooth event listener is removed.
+ void OnListenerRemoved();
+
+ // Adds a pairing delegate for an extension.
+ void AddPairingDelegate(const std::string& extension_id);
+
+ // Removes the pairing delegate for an extension.
+ void RemovePairingDelegate(const std::string& extension_id);
+
+ // Returns the pairing delegate for an extension or NULL if it doesn't have a
+ // pairing delegate.
+ BluetoothApiPairingDelegate* GetPairingDelegate(
+ const std::string& extension_id);
+
+ // Exposed for testing.
+ void SetAdapterForTest(device::BluetoothAdapter* adapter) {
+ adapter_ = adapter;
+ }
+
+ // Override from device::BluetoothAdapter::Observer.
+ void AdapterPresentChanged(device::BluetoothAdapter* adapter,
+ bool present) override;
+ void AdapterPoweredChanged(device::BluetoothAdapter* adapter,
+ bool has_power) override;
+ void AdapterDiscoveringChanged(device::BluetoothAdapter* adapter,
+ bool discovering) override;
+ void DeviceAdded(device::BluetoothAdapter* adapter,
+ device::BluetoothDevice* device) override;
+ void DeviceChanged(device::BluetoothAdapter* adapter,
+ device::BluetoothDevice* device) override;
+ void DeviceRemoved(device::BluetoothAdapter* adapter,
+ device::BluetoothDevice* device) override;
+
+ // Overridden from content::NotificationObserver.
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override;
+
+ // Overridden from ExtensionRegistryObserver.
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "BluetoothEventRouter"; }
+ static const bool kServiceRedirectedInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ private:
+ void StartDiscoverySessionImpl(device::BluetoothAdapter* adapter,
+ const std::string& extension_id,
+ const base::Closure& callback,
+ const base::Closure& error_callback);
+ void AddPairingDelegateImpl(const std::string& extension_id);
+
+ void OnAdapterInitialized(
+ const device::BluetoothAdapterFactory::AdapterCallback& callback,
+ scoped_refptr<device::BluetoothAdapter> adapter);
+ void MaybeReleaseAdapter();
+ void DispatchAdapterStateEvent();
+ void DispatchDeviceEvent(events::HistogramValue histogram_value,
+ const std::string& event_name,
+ device::BluetoothDevice* device);
+ void CleanUpForExtension(const std::string& extension_id);
+ void CleanUpAllExtensions();
+ void OnStartDiscoverySession(
+ const std::string& extension_id,
+ const base::Closure& callback,
+ scoped_ptr<device::BluetoothDiscoverySession> discovery_session);
+
+ void OnSetDiscoveryFilter(const std::string& extension_id,
+ const base::Closure& callback);
+
+ content::BrowserContext* browser_context_;
+ scoped_refptr<device::BluetoothAdapter> adapter_;
+
+ int num_event_listeners_;
+
+ // A map that maps extension ids to BluetoothDiscoverySession pointers.
+ typedef std::map<std::string, device::BluetoothDiscoverySession*>
+ DiscoverySessionMap;
+ DiscoverySessionMap discovery_session_map_;
+
+ typedef std::map<std::string, device::BluetoothDiscoveryFilter*>
+ PreSetFilterMap;
+
+ // Maps an extension id to it's pre-set discovery filter.
+ PreSetFilterMap pre_set_filter_map_;
+
+ // Maps an extension id to its pairing delegate.
+ typedef std::map<std::string, BluetoothApiPairingDelegate*>
+ PairingDelegateMap;
+ PairingDelegateMap pairing_delegate_map_;
+
+ content::NotificationRegistrar registrar_;
+
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+
+ base::WeakPtrFactory<BluetoothEventRouter> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothEventRouter);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_EVENT_ROUTER_H_
diff --git a/chromium/extensions/browser/api/bluetooth/bluetooth_event_router_unittest.cc b/chromium/extensions/browser/api/bluetooth/bluetooth_event_router_unittest.cc
new file mode 100644
index 00000000000..597d73838b7
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth/bluetooth_event_router_unittest.cc
@@ -0,0 +1,129 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth/bluetooth_event_router.h"
+
+#include <string>
+#include <utility>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread.h"
+#include "device/bluetooth/bluetooth_uuid.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extensions_test.h"
+#include "extensions/common/api/bluetooth.h"
+#include "extensions/common/extension_builder.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kTestExtensionId[] = "test extension id";
+const device::BluetoothUUID kAudioProfileUuid("1234");
+const device::BluetoothUUID kHealthProfileUuid("4321");
+
+MATCHER_P(IsFilterEqual, a, "") {
+ return arg.Equals(*a);
+}
+} // namespace
+
+namespace extensions {
+
+namespace bluetooth = api::bluetooth;
+
+class BluetoothEventRouterTest : public ExtensionsTest {
+ public:
+ BluetoothEventRouterTest()
+ : ui_thread_(content::BrowserThread::UI, &message_loop_),
+ mock_adapter_(new testing::StrictMock<device::MockBluetoothAdapter>()),
+ notification_service_(content::NotificationService::Create()),
+ router_(new BluetoothEventRouter(browser_context())) {
+ router_->SetAdapterForTest(mock_adapter_);
+ }
+
+ void TearDown() override {
+ // It's important to destroy the router before the browser context keyed
+ // services so it removes itself as an ExtensionRegistry observer.
+ router_.reset(NULL);
+ ExtensionsTest::TearDown();
+ }
+
+ protected:
+ base::MessageLoopForUI message_loop_;
+ // Note: |ui_thread_| must be declared before |router_|.
+ content::TestBrowserThread ui_thread_;
+ testing::StrictMock<device::MockBluetoothAdapter>* mock_adapter_;
+ scoped_ptr<content::NotificationService> notification_service_;
+ scoped_ptr<BluetoothEventRouter> router_;
+};
+
+TEST_F(BluetoothEventRouterTest, BluetoothEventListener) {
+ router_->OnListenerAdded();
+ EXPECT_CALL(*mock_adapter_, RemoveObserver(testing::_)).Times(1);
+ router_->OnListenerRemoved();
+}
+
+TEST_F(BluetoothEventRouterTest, MultipleBluetoothEventListeners) {
+ router_->OnListenerAdded();
+ router_->OnListenerAdded();
+ router_->OnListenerAdded();
+ router_->OnListenerRemoved();
+ router_->OnListenerRemoved();
+ EXPECT_CALL(*mock_adapter_, RemoveObserver(testing::_)).Times(1);
+ router_->OnListenerRemoved();
+}
+
+TEST_F(BluetoothEventRouterTest, UnloadExtension) {
+ scoped_refptr<const Extension> extension =
+ ExtensionBuilder()
+ .SetManifest(DictionaryBuilder()
+ .Set("name", "BT event router test")
+ .Set("version", "1.0")
+ .Set("manifest_version", 2)
+ .Build())
+ .SetID(kTestExtensionId)
+ .Build();
+
+ ExtensionRegistry::Get(browser_context())->TriggerOnUnloaded(
+ extension.get(), UnloadedExtensionInfo::REASON_DISABLE);
+
+ EXPECT_CALL(*mock_adapter_, RemoveObserver(testing::_)).Times(1);
+}
+
+// This test check that calling SetDiscoveryFilter before StartDiscoverySession
+// for given extension will start session with proper filter.
+TEST_F(BluetoothEventRouterTest, SetDiscoveryFilter) {
+ scoped_ptr<device::BluetoothDiscoveryFilter> discovery_filter(
+ new device::BluetoothDiscoveryFilter(
+ device::BluetoothDiscoveryFilter::Transport::TRANSPORT_LE));
+
+ discovery_filter->SetRSSI(-80);
+ discovery_filter->AddUUID(device::BluetoothUUID("1000"));
+
+ device::BluetoothDiscoveryFilter df(
+ device::BluetoothDiscoveryFilter::Transport::TRANSPORT_LE);
+ df.CopyFrom(*discovery_filter);
+
+ router_->SetDiscoveryFilter(std::move(discovery_filter), mock_adapter_,
+ kTestExtensionId, base::Bind(&base::DoNothing),
+ base::Bind(&base::DoNothing));
+
+ EXPECT_CALL(*mock_adapter_, StartDiscoverySessionWithFilterRaw(
+ testing::Pointee(IsFilterEqual(&df)),
+ testing::_, testing::_)).Times(1);
+
+ router_->StartDiscoverySession(mock_adapter_, kTestExtensionId,
+ base::Bind(&base::DoNothing),
+ base::Bind(&base::DoNothing));
+
+ EXPECT_CALL(*mock_adapter_, RemoveObserver(testing::_)).Times(1);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/bluetooth/bluetooth_extension_function.cc b/chromium/extensions/browser/api/bluetooth/bluetooth_extension_function.cc
new file mode 100644
index 00000000000..09ee87dbd07
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth/bluetooth_extension_function.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth/bluetooth_extension_function.h"
+
+#include "base/memory/ref_counted.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "extensions/browser/api/bluetooth/bluetooth_api.h"
+#include "extensions/browser/api/bluetooth/bluetooth_event_router.h"
+#include "url/gurl.h"
+
+using content::BrowserThread;
+
+namespace {
+
+const char kPlatformNotSupported[] =
+ "This operation is not supported on your platform";
+
+extensions::BluetoothEventRouter* GetEventRouter(
+ content::BrowserContext* context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ return extensions::BluetoothAPI::Get(context)->event_router();
+}
+
+bool IsBluetoothSupported(content::BrowserContext* context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ return GetEventRouter(context)->IsBluetoothSupported();
+}
+
+void GetAdapter(const device::BluetoothAdapterFactory::AdapterCallback callback,
+ content::BrowserContext* context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ GetEventRouter(context)->GetAdapter(callback);
+}
+
+} // namespace
+
+namespace extensions {
+namespace api {
+
+BluetoothExtensionFunction::BluetoothExtensionFunction() {
+}
+
+BluetoothExtensionFunction::~BluetoothExtensionFunction() {
+}
+
+bool BluetoothExtensionFunction::RunAsync() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (!IsBluetoothSupported(browser_context())) {
+ SetError(kPlatformNotSupported);
+ return false;
+ }
+ GetAdapter(base::Bind(&BluetoothExtensionFunction::RunOnAdapterReady, this),
+ browser_context());
+
+ return true;
+}
+
+std::string BluetoothExtensionFunction::GetExtensionId() {
+ if (extension())
+ return extension()->id();
+ return render_frame_host()->GetLastCommittedURL().host();
+}
+
+void BluetoothExtensionFunction::RunOnAdapterReady(
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DoWork(adapter);
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/bluetooth/bluetooth_extension_function.h b/chromium/extensions/browser/api/bluetooth/bluetooth_extension_function.h
new file mode 100644
index 00000000000..14ef94a8292
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth/bluetooth_extension_function.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_EXTENSION_FUNCTION_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_EXTENSION_FUNCTION_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "extensions/browser/extension_function.h"
+
+namespace device {
+
+class BluetoothAdapter;
+
+} // namespace device
+
+namespace extensions {
+namespace api {
+
+// Base class for bluetooth extension functions. This class initializes
+// bluetooth adapter and calls (on the UI thread) DoWork() implemented by
+// individual bluetooth extension functions.
+class BluetoothExtensionFunction : public AsyncExtensionFunction {
+ public:
+ BluetoothExtensionFunction();
+
+ protected:
+ ~BluetoothExtensionFunction() override;
+
+ // ExtensionFunction:
+ bool RunAsync() override;
+
+ // Use instead of extension_id() so that this can be run from WebUI.
+ std::string GetExtensionId();
+
+ private:
+ void RunOnAdapterReady(scoped_refptr<device::BluetoothAdapter> adapter);
+
+ // Implemented by individual bluetooth extension functions, called
+ // automatically on the UI thread once |adapter| has been initialized.
+ virtual bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothExtensionFunction);
+};
+
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_EXTENSION_FUNCTION_H_
diff --git a/chromium/extensions/browser/api/bluetooth/bluetooth_private_api.cc b/chromium/extensions/browser/api/bluetooth/bluetooth_private_api.cc
new file mode 100644
index 00000000000..79deb70543d
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth/bluetooth_private_api.cc
@@ -0,0 +1,625 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth/bluetooth_private_api.h"
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/callback.h"
+#include "base/lazy_instance.h"
+#include "base/strings/string_util.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/bluetooth_discovery_session.h"
+#include "extensions/browser/api/bluetooth/bluetooth_api.h"
+#include "extensions/browser/api/bluetooth/bluetooth_api_pairing_delegate.h"
+#include "extensions/browser/api/bluetooth/bluetooth_event_router.h"
+#include "extensions/common/api/bluetooth_private.h"
+
+namespace bt_private = extensions::api::bluetooth_private;
+namespace SetDiscoveryFilter = bt_private::SetDiscoveryFilter;
+
+namespace extensions {
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<BluetoothPrivateAPI>>
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+namespace {
+
+std::string GetListenerId(const EventListenerInfo& details) {
+ return !details.extension_id.empty() ? details.extension_id
+ : details.listener_url.host();
+}
+
+} // namespace
+
+// static
+BrowserContextKeyedAPIFactory<BluetoothPrivateAPI>*
+BluetoothPrivateAPI::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+BluetoothPrivateAPI::BluetoothPrivateAPI(content::BrowserContext* context)
+ : browser_context_(context) {
+ EventRouter::Get(browser_context_)
+ ->RegisterObserver(this, bt_private::OnPairing::kEventName);
+}
+
+BluetoothPrivateAPI::~BluetoothPrivateAPI() {}
+
+void BluetoothPrivateAPI::Shutdown() {
+ EventRouter::Get(browser_context_)->UnregisterObserver(this);
+}
+
+void BluetoothPrivateAPI::OnListenerAdded(const EventListenerInfo& details) {
+ // This function can be called multiple times for the same JS listener, for
+ // example, once for the addListener call and again if it is a lazy listener.
+ if (!details.browser_context)
+ return;
+
+ BluetoothAPI::Get(browser_context_)
+ ->event_router()
+ ->AddPairingDelegate(GetListenerId(details));
+}
+
+void BluetoothPrivateAPI::OnListenerRemoved(const EventListenerInfo& details) {
+ // This function can be called multiple times for the same JS listener, for
+ // example, once for the addListener call and again if it is a lazy listener.
+ if (!details.browser_context)
+ return;
+
+ BluetoothAPI::Get(browser_context_)
+ ->event_router()
+ ->RemovePairingDelegate(GetListenerId(details));
+}
+
+namespace api {
+
+namespace {
+
+const char kNameProperty[] = "name";
+const char kPoweredProperty[] = "powered";
+const char kDiscoverableProperty[] = "discoverable";
+const char kSetAdapterPropertyError[] = "Error setting adapter properties: $1";
+const char kDeviceNotFoundError[] = "Invalid Bluetooth device";
+const char kDeviceNotConnectedError[] = "Device not connected";
+const char kPairingNotEnabled[] = "Pairing not enabled";
+const char kInvalidPairingResponseOptions[] =
+ "Invalid pairing response options";
+const char kAdapterNotPresent[] = "Failed to find a Bluetooth adapter";
+const char kDisconnectError[] = "Failed to disconnect device";
+const char kForgetDeviceError[] = "Failed to forget device";
+const char kSetDiscoveryFilterFailed[] = "Failed to set discovery filter";
+const char kPairingFailed[] = "Pairing failed";
+
+// Returns true if the pairing response options passed into the
+// setPairingResponse function are valid.
+bool ValidatePairingResponseOptions(
+ const device::BluetoothDevice* device,
+ const bt_private::SetPairingResponseOptions& options) {
+ bool response = options.response != bt_private::PAIRING_RESPONSE_NONE;
+ bool pincode = options.pincode.get() != nullptr;
+ bool passkey = options.passkey.get() != nullptr;
+
+ if (!response && !pincode && !passkey)
+ return false;
+ if (pincode && passkey)
+ return false;
+ if (options.response != bt_private::PAIRING_RESPONSE_CONFIRM &&
+ (pincode || passkey))
+ return false;
+
+ if (options.response == bt_private::PAIRING_RESPONSE_CANCEL)
+ return true;
+
+ // Check the BluetoothDevice is in expecting the correct response.
+ if (!device->ExpectingConfirmation() && !device->ExpectingPinCode() &&
+ !device->ExpectingPasskey())
+ return false;
+ if (pincode && !device->ExpectingPinCode())
+ return false;
+ if (passkey && !device->ExpectingPasskey())
+ return false;
+ if (options.response == bt_private::PAIRING_RESPONSE_CONFIRM && !pincode &&
+ !passkey && !device->ExpectingConfirmation())
+ return false;
+
+ return true;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+BluetoothPrivateSetAdapterStateFunction::
+ BluetoothPrivateSetAdapterStateFunction() {}
+
+BluetoothPrivateSetAdapterStateFunction::
+ ~BluetoothPrivateSetAdapterStateFunction() {}
+
+bool BluetoothPrivateSetAdapterStateFunction::DoWork(
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ scoped_ptr<bt_private::SetAdapterState::Params> params(
+ bt_private::SetAdapterState::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ if (!adapter->IsPresent()) {
+ SetError(kAdapterNotPresent);
+ SendResponse(false);
+ return true;
+ }
+
+ const bt_private::NewAdapterState& new_state = params->adapter_state;
+
+ // These properties are not owned.
+ std::string* name = new_state.name.get();
+ bool* powered = new_state.powered.get();
+ bool* discoverable = new_state.discoverable.get();
+
+ if (name && adapter->GetName() != *name) {
+ pending_properties_.insert(kNameProperty);
+ adapter->SetName(*name, CreatePropertySetCallback(kNameProperty),
+ CreatePropertyErrorCallback(kNameProperty));
+ }
+
+ if (powered && adapter->IsPowered() != *powered) {
+ pending_properties_.insert(kPoweredProperty);
+ adapter->SetPowered(*powered, CreatePropertySetCallback(kPoweredProperty),
+ CreatePropertyErrorCallback(kPoweredProperty));
+ }
+
+ if (discoverable && adapter->IsDiscoverable() != *discoverable) {
+ pending_properties_.insert(kDiscoverableProperty);
+ adapter->SetDiscoverable(
+ *discoverable, CreatePropertySetCallback(kDiscoverableProperty),
+ CreatePropertyErrorCallback(kDiscoverableProperty));
+ }
+
+ if (pending_properties_.empty())
+ SendResponse(true);
+ return true;
+}
+
+base::Closure
+BluetoothPrivateSetAdapterStateFunction::CreatePropertySetCallback(
+ const std::string& property_name) {
+ return base::Bind(
+ &BluetoothPrivateSetAdapterStateFunction::OnAdapterPropertySet, this,
+ property_name);
+}
+
+base::Closure
+BluetoothPrivateSetAdapterStateFunction::CreatePropertyErrorCallback(
+ const std::string& property_name) {
+ return base::Bind(
+ &BluetoothPrivateSetAdapterStateFunction::OnAdapterPropertyError, this,
+ property_name);
+}
+
+void BluetoothPrivateSetAdapterStateFunction::OnAdapterPropertySet(
+ const std::string& property) {
+ DCHECK(pending_properties_.find(property) != pending_properties_.end());
+ DCHECK(failed_properties_.find(property) == failed_properties_.end());
+
+ pending_properties_.erase(property);
+ if (pending_properties_.empty()) {
+ if (failed_properties_.empty())
+ SendResponse(true);
+ else
+ SendError();
+ }
+}
+
+void BluetoothPrivateSetAdapterStateFunction::OnAdapterPropertyError(
+ const std::string& property) {
+ DCHECK(pending_properties_.find(property) != pending_properties_.end());
+ DCHECK(failed_properties_.find(property) == failed_properties_.end());
+
+ pending_properties_.erase(property);
+ failed_properties_.insert(property);
+ if (pending_properties_.empty())
+ SendError();
+}
+
+void BluetoothPrivateSetAdapterStateFunction::SendError() {
+ DCHECK(pending_properties_.empty());
+ DCHECK(!failed_properties_.empty());
+
+ std::vector<std::string> failed_vector;
+ std::copy(failed_properties_.begin(), failed_properties_.end(),
+ std::back_inserter(failed_vector));
+
+ std::vector<std::string> replacements(1);
+ replacements[0] = base::JoinString(failed_vector, ", ");
+ std::string error = base::ReplaceStringPlaceholders(kSetAdapterPropertyError,
+ replacements, nullptr);
+ SetError(error);
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+BluetoothPrivateSetPairingResponseFunction::
+ BluetoothPrivateSetPairingResponseFunction() {}
+
+BluetoothPrivateSetPairingResponseFunction::
+ ~BluetoothPrivateSetPairingResponseFunction() {}
+
+bool BluetoothPrivateSetPairingResponseFunction::DoWork(
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ scoped_ptr<bt_private::SetPairingResponse::Params> params(
+ bt_private::SetPairingResponse::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ const bt_private::SetPairingResponseOptions& options = params->options;
+
+ BluetoothEventRouter* router =
+ BluetoothAPI::Get(browser_context())->event_router();
+ if (!router->GetPairingDelegate(GetExtensionId())) {
+ SetError(kPairingNotEnabled);
+ SendResponse(false);
+ return true;
+ }
+
+ const std::string& device_address = options.device.address;
+ device::BluetoothDevice* device = adapter->GetDevice(device_address);
+ if (!device) {
+ SetError(kDeviceNotFoundError);
+ SendResponse(false);
+ return true;
+ }
+
+ if (!ValidatePairingResponseOptions(device, options)) {
+ SetError(kInvalidPairingResponseOptions);
+ SendResponse(false);
+ return true;
+ }
+
+ if (options.pincode.get()) {
+ device->SetPinCode(*options.pincode.get());
+ } else if (options.passkey.get()) {
+ device->SetPasskey(*options.passkey.get());
+ } else {
+ switch (options.response) {
+ case bt_private::PAIRING_RESPONSE_CONFIRM:
+ device->ConfirmPairing();
+ break;
+ case bt_private::PAIRING_RESPONSE_REJECT:
+ device->RejectPairing();
+ break;
+ case bt_private::PAIRING_RESPONSE_CANCEL:
+ device->CancelPairing();
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+
+ SendResponse(true);
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+BluetoothPrivateDisconnectAllFunction::BluetoothPrivateDisconnectAllFunction() {
+}
+
+BluetoothPrivateDisconnectAllFunction::
+ ~BluetoothPrivateDisconnectAllFunction() {}
+
+bool BluetoothPrivateDisconnectAllFunction::DoWork(
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ scoped_ptr<bt_private::DisconnectAll::Params> params(
+ bt_private::DisconnectAll::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ device::BluetoothDevice* device = adapter->GetDevice(params->device_address);
+ if (!device) {
+ SetError(kDeviceNotFoundError);
+ SendResponse(false);
+ return true;
+ }
+
+ if (!device->IsConnected()) {
+ SetError(kDeviceNotConnectedError);
+ SendResponse(false);
+ return true;
+ }
+
+ device->Disconnect(
+ base::Bind(&BluetoothPrivateDisconnectAllFunction::OnSuccessCallback,
+ this),
+ base::Bind(&BluetoothPrivateDisconnectAllFunction::OnErrorCallback, this,
+ adapter, params->device_address));
+
+ return true;
+}
+
+void BluetoothPrivateDisconnectAllFunction::OnSuccessCallback() {
+ SendResponse(true);
+}
+
+void BluetoothPrivateDisconnectAllFunction::OnErrorCallback(
+ scoped_refptr<device::BluetoothAdapter> adapter,
+ const std::string& device_address) {
+ // The call to Disconnect may report an error if the device was disconnected
+ // due to an external reason. In this case, report "Not Connected" as the
+ // error.
+ device::BluetoothDevice* device = adapter->GetDevice(device_address);
+ if (device && !device->IsConnected())
+ SetError(kDeviceNotConnectedError);
+ else
+ SetError(kDisconnectError);
+
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+BluetoothPrivateForgetDeviceFunction::BluetoothPrivateForgetDeviceFunction() {}
+
+BluetoothPrivateForgetDeviceFunction::~BluetoothPrivateForgetDeviceFunction() {}
+
+bool BluetoothPrivateForgetDeviceFunction::DoWork(
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ scoped_ptr<bt_private::ForgetDevice::Params> params(
+ bt_private::ForgetDevice::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ device::BluetoothDevice* device = adapter->GetDevice(params->device_address);
+ if (!device) {
+ SetError(kDeviceNotFoundError);
+ SendResponse(false);
+ return true;
+ }
+
+ device->Forget(
+ base::Bind(&BluetoothPrivateForgetDeviceFunction::OnSuccessCallback,
+ this),
+ base::Bind(&BluetoothPrivateForgetDeviceFunction::OnErrorCallback, this,
+ adapter, params->device_address));
+
+ return true;
+}
+
+void BluetoothPrivateForgetDeviceFunction::OnSuccessCallback() {
+ SendResponse(true);
+}
+
+void BluetoothPrivateForgetDeviceFunction::OnErrorCallback(
+ scoped_refptr<device::BluetoothAdapter> adapter,
+ const std::string& device_address) {
+ SetError(kForgetDeviceError);
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool BluetoothPrivateSetDiscoveryFilterFunction::DoWork(
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ scoped_ptr<SetDiscoveryFilter::Params> params(
+ SetDiscoveryFilter::Params::Create(*args_));
+ auto& df_param = params->discovery_filter;
+
+ scoped_ptr<device::BluetoothDiscoveryFilter> discovery_filter;
+
+ // If all filter fields are empty, we are clearing filter. If any field is
+ // set, then create proper filter.
+ if (df_param.uuids.get() || df_param.rssi.get() || df_param.pathloss.get() ||
+ df_param.transport != bt_private::TransportType::TRANSPORT_TYPE_NONE) {
+ uint8_t transport;
+
+ switch (df_param.transport) {
+ case bt_private::TransportType::TRANSPORT_TYPE_LE:
+ transport = device::BluetoothDiscoveryFilter::Transport::TRANSPORT_LE;
+ break;
+ case bt_private::TransportType::TRANSPORT_TYPE_BREDR:
+ transport =
+ device::BluetoothDiscoveryFilter::Transport::TRANSPORT_CLASSIC;
+ break;
+ default: // TRANSPORT_TYPE_NONE is included here
+ transport = device::BluetoothDiscoveryFilter::Transport::TRANSPORT_DUAL;
+ break;
+ }
+
+ discovery_filter.reset(new device::BluetoothDiscoveryFilter(transport));
+
+ if (df_param.uuids.get()) {
+ std::vector<device::BluetoothUUID> uuids;
+ if (df_param.uuids->as_string.get()) {
+ discovery_filter->AddUUID(
+ device::BluetoothUUID(*df_param.uuids->as_string));
+ } else if (df_param.uuids->as_strings.get()) {
+ for (const auto& iter : *df_param.uuids->as_strings) {
+ discovery_filter->AddUUID(device::BluetoothUUID(iter));
+ }
+ }
+ }
+
+ if (df_param.rssi.get())
+ discovery_filter->SetRSSI(*df_param.rssi);
+
+ if (df_param.pathloss.get())
+ discovery_filter->SetPathloss(*df_param.pathloss);
+ }
+
+ BluetoothAPI::Get(browser_context())
+ ->event_router()
+ ->SetDiscoveryFilter(
+ std::move(discovery_filter), adapter.get(), GetExtensionId(),
+ base::Bind(
+ &BluetoothPrivateSetDiscoveryFilterFunction::OnSuccessCallback,
+ this),
+ base::Bind(
+ &BluetoothPrivateSetDiscoveryFilterFunction::OnErrorCallback,
+ this));
+ return true;
+}
+
+void BluetoothPrivateSetDiscoveryFilterFunction::OnSuccessCallback() {
+ SendResponse(true);
+}
+
+void BluetoothPrivateSetDiscoveryFilterFunction::OnErrorCallback() {
+ SetError(kSetDiscoveryFilterFailed);
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+BluetoothPrivateConnectFunction::BluetoothPrivateConnectFunction() {}
+
+BluetoothPrivateConnectFunction::~BluetoothPrivateConnectFunction() {}
+
+bool BluetoothPrivateConnectFunction::DoWork(
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ scoped_ptr<bt_private::Connect::Params> params(
+ bt_private::Connect::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ device::BluetoothDevice* device = adapter->GetDevice(params->device_address);
+ if (!device) {
+ SetError(kDeviceNotFoundError);
+ SendResponse(false);
+ return true;
+ }
+
+ if (device->IsConnected()) {
+ results_ = bt_private::Connect::Results::Create(
+ bt_private::CONNECT_RESULT_TYPE_ALREADYCONNECTED);
+ SendResponse(true);
+ return true;
+ }
+
+ // pairing_delegate may be null for connect.
+ device::BluetoothDevice::PairingDelegate* pairing_delegate =
+ BluetoothAPI::Get(browser_context())
+ ->event_router()
+ ->GetPairingDelegate(GetExtensionId());
+ device->Connect(
+ pairing_delegate,
+ base::Bind(&BluetoothPrivateConnectFunction::OnSuccessCallback, this),
+ base::Bind(&BluetoothPrivateConnectFunction::OnErrorCallback, this));
+ return true;
+}
+
+void BluetoothPrivateConnectFunction::OnSuccessCallback() {
+ results_ = bt_private::Connect::Results::Create(
+ bt_private::CONNECT_RESULT_TYPE_SUCCESS);
+ SendResponse(true);
+}
+
+void BluetoothPrivateConnectFunction::OnErrorCallback(
+ device::BluetoothDevice::ConnectErrorCode error) {
+ bt_private::ConnectResultType result = bt_private::CONNECT_RESULT_TYPE_NONE;
+ switch (error) {
+ case device::BluetoothDevice::ERROR_ATTRIBUTE_LENGTH_INVALID:
+ result = bt_private::CONNECT_RESULT_TYPE_ATTRIBUTELENGTHINVALID;
+ break;
+ case device::BluetoothDevice::ERROR_AUTH_CANCELED:
+ result = bt_private::CONNECT_RESULT_TYPE_AUTHCANCELED;
+ break;
+ case device::BluetoothDevice::ERROR_AUTH_FAILED:
+ result = bt_private::CONNECT_RESULT_TYPE_AUTHFAILED;
+ break;
+ case device::BluetoothDevice::ERROR_AUTH_REJECTED:
+ result = bt_private::CONNECT_RESULT_TYPE_AUTHREJECTED;
+ break;
+ case device::BluetoothDevice::ERROR_AUTH_TIMEOUT:
+ result = bt_private::CONNECT_RESULT_TYPE_AUTHTIMEOUT;
+ break;
+ case device::BluetoothDevice::ERROR_CONNECTION_CONGESTED:
+ result = bt_private::CONNECT_RESULT_TYPE_CONNECTIONCONGESTED;
+ break;
+ case device::BluetoothDevice::ERROR_FAILED:
+ result = bt_private::CONNECT_RESULT_TYPE_FAILED;
+ break;
+ case device::BluetoothDevice::ERROR_INPROGRESS:
+ result = bt_private::CONNECT_RESULT_TYPE_INPROGRESS;
+ break;
+ case device::BluetoothDevice::ERROR_INSUFFICIENT_ENCRYPTION:
+ result = bt_private::CONNECT_RESULT_TYPE_INSUFFICIENTENCRYPTION;
+ break;
+ case device::BluetoothDevice::ERROR_OFFSET_INVALID:
+ result = bt_private::CONNECT_RESULT_TYPE_OFFSETINVALID;
+ break;
+ case device::BluetoothDevice::ERROR_READ_NOT_PERMITTED:
+ result = bt_private::CONNECT_RESULT_TYPE_READNOTPERMITTED;
+ break;
+ case device::BluetoothDevice::ERROR_REQUEST_NOT_SUPPORTED:
+ result = bt_private::CONNECT_RESULT_TYPE_REQUESTNOTSUPPORTED;
+ break;
+ case device::BluetoothDevice::ERROR_UNKNOWN:
+ result = bt_private::CONNECT_RESULT_TYPE_UNKNOWNERROR;
+ break;
+ case device::BluetoothDevice::ERROR_UNSUPPORTED_DEVICE:
+ result = bt_private::CONNECT_RESULT_TYPE_UNSUPPORTEDDEVICE;
+ break;
+ case device::BluetoothDevice::ERROR_WRITE_NOT_PERMITTED:
+ result = bt_private::CONNECT_RESULT_TYPE_WRITENOTPERMITTED;
+ break;
+ case device::BluetoothDevice::NUM_CONNECT_ERROR_CODES:
+ NOTREACHED();
+ break;
+ }
+ // Set the result type and respond with true (success).
+ results_ = bt_private::Connect::Results::Create(result);
+ SendResponse(true);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+BluetoothPrivatePairFunction::BluetoothPrivatePairFunction() {}
+
+BluetoothPrivatePairFunction::~BluetoothPrivatePairFunction() {}
+
+bool BluetoothPrivatePairFunction::DoWork(
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ scoped_ptr<bt_private::Pair::Params> params(
+ bt_private::Pair::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ device::BluetoothDevice* device = adapter->GetDevice(params->device_address);
+ if (!device) {
+ SetError(kDeviceNotFoundError);
+ SendResponse(false);
+ return true;
+ }
+
+ device::BluetoothDevice::PairingDelegate* pairing_delegate =
+ BluetoothAPI::Get(browser_context())
+ ->event_router()
+ ->GetPairingDelegate(GetExtensionId());
+
+ // pairing_delegate must be set (by adding an onPairing listener) before
+ // any calls to pair().
+ if (!pairing_delegate) {
+ SetError(kPairingNotEnabled);
+ SendResponse(false);
+ return true;
+ }
+
+ device->Pair(
+ pairing_delegate,
+ base::Bind(&BluetoothPrivatePairFunction::OnSuccessCallback, this),
+ base::Bind(&BluetoothPrivatePairFunction::OnErrorCallback, this));
+ return true;
+}
+
+void BluetoothPrivatePairFunction::OnSuccessCallback() {
+ SendResponse(true);
+}
+
+void BluetoothPrivatePairFunction::OnErrorCallback(
+ device::BluetoothDevice::ConnectErrorCode error) {
+ SetError(kPairingFailed);
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace api
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/bluetooth/bluetooth_private_api.h b/chromium/extensions/browser/api/bluetooth/bluetooth_private_api.h
new file mode 100644
index 00000000000..a35dc2ea54d
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth/bluetooth_private_api.h
@@ -0,0 +1,190 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_PRIVATE_API_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_PRIVATE_API_H_
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "extensions/browser/api/bluetooth/bluetooth_extension_function.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/event_router.h"
+
+namespace device {
+class BluetoothAdapter;
+}
+
+namespace extensions {
+
+class BluetoothApiPairingDelegate;
+
+// The profile-keyed service that manages the bluetoothPrivate extension API.
+class BluetoothPrivateAPI : public BrowserContextKeyedAPI,
+ public EventRouter::Observer {
+ public:
+ static BrowserContextKeyedAPIFactory<BluetoothPrivateAPI>*
+ GetFactoryInstance();
+
+ explicit BluetoothPrivateAPI(content::BrowserContext* context);
+ ~BluetoothPrivateAPI() override;
+
+ // KeyedService implementation.
+ void Shutdown() override;
+
+ // EventRouter::Observer implementation.
+ void OnListenerAdded(const EventListenerInfo& details) override;
+ void OnListenerRemoved(const EventListenerInfo& details) override;
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "BluetoothPrivateAPI"; }
+ static const bool kServiceRedirectedInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<BluetoothPrivateAPI>;
+
+ content::BrowserContext* browser_context_;
+};
+
+namespace api {
+
+class BluetoothPrivateSetAdapterStateFunction
+ : public BluetoothExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothPrivate.setAdapterState",
+ BLUETOOTHPRIVATE_SETADAPTERSTATE)
+ BluetoothPrivateSetAdapterStateFunction();
+
+ private:
+ ~BluetoothPrivateSetAdapterStateFunction() override;
+
+ base::Closure CreatePropertySetCallback(const std::string& property_name);
+ base::Closure CreatePropertyErrorCallback(const std::string& property_name);
+ void OnAdapterPropertySet(const std::string& property);
+ void OnAdapterPropertyError(const std::string& property);
+ void SendError();
+
+ // BluetoothExtensionFunction overrides:
+ bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) override;
+
+ // Set of expected adapter properties to be changed.
+ std::set<std::string> pending_properties_;
+
+ // Set of adapter properties that were not set successfully.
+ std::set<std::string> failed_properties_;
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothPrivateSetAdapterStateFunction);
+};
+
+class BluetoothPrivateSetPairingResponseFunction
+ : public BluetoothExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothPrivate.setPairingResponse",
+ BLUETOOTHPRIVATE_SETPAIRINGRESPONSE)
+ BluetoothPrivateSetPairingResponseFunction();
+ // BluetoothExtensionFunction overrides:
+ bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) override;
+
+ private:
+ ~BluetoothPrivateSetPairingResponseFunction() override;
+ DISALLOW_COPY_AND_ASSIGN(BluetoothPrivateSetPairingResponseFunction);
+};
+
+class BluetoothPrivateDisconnectAllFunction
+ : public BluetoothExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothPrivate.disconnectAll",
+ BLUETOOTHPRIVATE_DISCONNECTALL);
+ BluetoothPrivateDisconnectAllFunction();
+
+ // BluetoothExtensionFunction overrides:
+ bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) override;
+
+ private:
+ ~BluetoothPrivateDisconnectAllFunction() override;
+
+ void OnSuccessCallback();
+ void OnErrorCallback(scoped_refptr<device::BluetoothAdapter> adapter,
+ const std::string& device_address);
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothPrivateDisconnectAllFunction);
+};
+
+class BluetoothPrivateForgetDeviceFunction : public BluetoothExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothPrivate.forgetDevice",
+ BLUETOOTHPRIVATE_FORGETDEVICE);
+ BluetoothPrivateForgetDeviceFunction();
+
+ // BluetoothExtensionFunction overrides:
+ bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) override;
+
+ private:
+ ~BluetoothPrivateForgetDeviceFunction() override;
+
+ void OnSuccessCallback();
+ void OnErrorCallback(scoped_refptr<device::BluetoothAdapter> adapter,
+ const std::string& device_address);
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothPrivateForgetDeviceFunction);
+};
+
+class BluetoothPrivateSetDiscoveryFilterFunction
+ : public BluetoothExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothPrivate.setDiscoveryFilter",
+ BLUETOOTHPRIVATE_SETDISCOVERYFILTER)
+
+ protected:
+ ~BluetoothPrivateSetDiscoveryFilterFunction() override {}
+
+ // BluetoothExtensionFunction:
+ bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) override;
+
+ private:
+ void OnSuccessCallback();
+ void OnErrorCallback();
+};
+
+class BluetoothPrivateConnectFunction : public BluetoothExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothPrivate.connect",
+ BLUETOOTHPRIVATE_CONNECT)
+ BluetoothPrivateConnectFunction();
+
+ // BluetoothExtensionFunction:
+ bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) override;
+
+ private:
+ ~BluetoothPrivateConnectFunction() override;
+
+ void OnSuccessCallback();
+ void OnErrorCallback(device::BluetoothDevice::ConnectErrorCode error);
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothPrivateConnectFunction);
+};
+
+class BluetoothPrivatePairFunction : public BluetoothExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothPrivate.pair", BLUETOOTHPRIVATE_PAIR)
+ BluetoothPrivatePairFunction();
+
+ // BluetoothExtensionFunction:
+ bool DoWork(scoped_refptr<device::BluetoothAdapter> adapter) override;
+
+ private:
+ ~BluetoothPrivatePairFunction() override;
+
+ void OnSuccessCallback();
+ void OnErrorCallback(device::BluetoothDevice::ConnectErrorCode error);
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothPrivatePairFunction);
+};
+
+} // namespace api
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_BLUETOOTH_PRIVATE_API_H_
diff --git a/chromium/extensions/browser/api/bluetooth/bluetooth_private_apitest.cc b/chromium/extensions/browser/api/bluetooth/bluetooth_private_apitest.cc
new file mode 100644
index 00000000000..65adb8a379e
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth/bluetooth_private_apitest.cc
@@ -0,0 +1,303 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "build/build_config.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "device/bluetooth/test/mock_bluetooth_device.h"
+#include "device/bluetooth/test/mock_bluetooth_discovery_session.h"
+#include "extensions/browser/api/bluetooth/bluetooth_api.h"
+#include "extensions/browser/api/bluetooth/bluetooth_event_router.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/common/api/bluetooth_private.h"
+#include "extensions/common/switches.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using device::BluetoothDiscoveryFilter;
+using device::BluetoothUUID;
+using device::MockBluetoothAdapter;
+using device::MockBluetoothDevice;
+using device::MockBluetoothDiscoverySession;
+using testing::_;
+using testing::Eq;
+using testing::InSequence;
+using testing::NiceMock;
+using testing::Return;
+using testing::ReturnPointee;
+using testing::SaveArg;
+using testing::WithArgs;
+using testing::WithoutArgs;
+
+namespace bt = extensions::api::bluetooth;
+namespace bt_private = extensions::api::bluetooth_private;
+
+namespace extensions {
+
+namespace {
+const char kTestExtensionId[] = "jofgjdphhceggjecimellaapdjjadibj";
+const char kAdapterName[] = "Helix";
+const char kDeviceName[] = "Red";
+const char kDeviceAddress[] = "11:12:13:14:15:16";
+
+MATCHER_P(IsFilterEqual, a, "") {
+ return arg->Equals(*a);
+}
+}
+
+class BluetoothPrivateApiTest : public ExtensionApiTest {
+ public:
+ BluetoothPrivateApiTest()
+ : adapter_name_(kAdapterName),
+ adapter_powered_(false),
+ adapter_discoverable_(false) {}
+
+ ~BluetoothPrivateApiTest() override {}
+
+ void SetUpOnMainThread() override {
+ base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ switches::kWhitelistedExtensionID, kTestExtensionId);
+ mock_adapter_ = new NiceMock<MockBluetoothAdapter>();
+ event_router()->SetAdapterForTest(mock_adapter_.get());
+ mock_device_.reset(new NiceMock<MockBluetoothDevice>(
+ mock_adapter_.get(), 0, kDeviceName, kDeviceAddress, false, false));
+ ON_CALL(*mock_adapter_.get(), GetDevice(kDeviceAddress))
+ .WillByDefault(Return(mock_device_.get()));
+ ON_CALL(*mock_adapter_.get(), IsPresent()).WillByDefault(Return(true));
+ }
+
+ void TearDownOnMainThread() override {}
+
+ BluetoothEventRouter* event_router() {
+ return BluetoothAPI::Get(browser()->profile())->event_router();
+ }
+
+ void SetName(const std::string& name, const base::Closure& callback) {
+ adapter_name_ = name;
+ callback.Run();
+ }
+
+ void SetPowered(bool powered, const base::Closure& callback) {
+ adapter_powered_ = powered;
+ callback.Run();
+ }
+
+ void ForgetDevice(const base::Closure& callback) {
+ mock_device_.reset();
+ event_router()->SetAdapterForTest(nullptr);
+ callback.Run();
+ }
+
+ void SetDiscoverable(bool discoverable, const base::Closure& callback) {
+ adapter_discoverable_ = discoverable;
+ callback.Run();
+ }
+
+ void DispatchPairingEvent(bt_private::PairingEventType pairing_event_type) {
+ bt_private::PairingEvent pairing_event;
+ pairing_event.pairing = pairing_event_type;
+ pairing_event.device.name.reset(new std::string(kDeviceName));
+ pairing_event.device.address = mock_device_->GetAddress();
+ pairing_event.device.vendor_id_source = bt::VENDOR_ID_SOURCE_USB;
+ pairing_event.device.type = bt::DEVICE_TYPE_PHONE;
+
+ scoped_ptr<base::ListValue> args =
+ bt_private::OnPairing::Create(pairing_event);
+ scoped_ptr<Event> event(new Event(events::BLUETOOTH_PRIVATE_ON_PAIRING,
+ bt_private::OnPairing::kEventName,
+ std::move(args)));
+ EventRouter::Get(browser()->profile())
+ ->DispatchEventToExtension(kTestExtensionId, std::move(event));
+ }
+
+ void DispatchAuthorizePairingEvent() {
+ DispatchPairingEvent(bt_private::PAIRING_EVENT_TYPE_REQUESTAUTHORIZATION);
+ }
+
+ void DispatchPincodePairingEvent() {
+ DispatchPairingEvent(bt_private::PAIRING_EVENT_TYPE_REQUESTPINCODE);
+ }
+
+ void DispatchPasskeyPairingEvent() {
+ DispatchPairingEvent(bt_private::PAIRING_EVENT_TYPE_REQUESTPASSKEY);
+ }
+
+ void DispatchConfirmPasskeyPairingEvent() {
+ DispatchPairingEvent(bt_private::PAIRING_EVENT_TYPE_CONFIRMPASSKEY);
+ }
+
+ void CallSetDiscoveryFilterCallback(
+ device::BluetoothAdapter::DiscoverySessionCallback callback) {
+ auto session_ptr = scoped_ptr<NiceMock<MockBluetoothDiscoverySession>>(
+ mock_discovery_session_);
+
+ callback.Run(std::move(session_ptr));
+ }
+
+ protected:
+ std::string adapter_name_;
+ bool adapter_powered_;
+ bool adapter_discoverable_;
+
+ scoped_refptr<NiceMock<MockBluetoothAdapter> > mock_adapter_;
+ scoped_ptr<NiceMock<MockBluetoothDevice> > mock_device_;
+
+ // This discovery session will be owned by EventRouter, we'll only keep
+ // pointer to it.
+ NiceMock<MockBluetoothDiscoverySession>* mock_discovery_session_;
+};
+
+ACTION_TEMPLATE(InvokeCallbackArgument,
+ HAS_1_TEMPLATE_PARAMS(int, k),
+ AND_0_VALUE_PARAMS()) {
+ ::std::tr1::get<k>(args).Run();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothPrivateApiTest, SetAdapterState) {
+ ON_CALL(*mock_adapter_.get(), GetName())
+ .WillByDefault(ReturnPointee(&adapter_name_));
+ ON_CALL(*mock_adapter_.get(), IsPowered())
+ .WillByDefault(ReturnPointee(&adapter_powered_));
+ ON_CALL(*mock_adapter_.get(), IsDiscoverable())
+ .WillByDefault(ReturnPointee(&adapter_discoverable_));
+
+ EXPECT_CALL(*mock_adapter_.get(), SetName("Dome", _, _)).WillOnce(
+ WithArgs<0, 1>(Invoke(this, &BluetoothPrivateApiTest::SetName)));
+ EXPECT_CALL(*mock_adapter_.get(), SetPowered(true, _, _)).WillOnce(
+ WithArgs<0, 1>(Invoke(this, &BluetoothPrivateApiTest::SetPowered)));
+ EXPECT_CALL(*mock_adapter_.get(), SetDiscoverable(true, _, _)).WillOnce(
+ WithArgs<0, 1>(Invoke(this, &BluetoothPrivateApiTest::SetDiscoverable)));
+
+ ASSERT_TRUE(RunComponentExtensionTest("bluetooth_private/adapter_state"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothPrivateApiTest, NoBluetoothAdapter) {
+ ON_CALL(*mock_adapter_.get(), IsPresent()).WillByDefault(Return(false));
+ ASSERT_TRUE(RunComponentExtensionTest("bluetooth_private/no_adapter"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothPrivateApiTest, CancelPairing) {
+ InSequence s;
+ EXPECT_CALL(*mock_adapter_.get(),
+ AddPairingDelegate(
+ _, device::BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_HIGH))
+ .WillOnce(WithoutArgs(Invoke(
+ this, &BluetoothPrivateApiTest::DispatchAuthorizePairingEvent)));
+ EXPECT_CALL(*mock_device_, ExpectingConfirmation())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(*mock_device_, CancelPairing());
+ ASSERT_TRUE(RunComponentExtensionTest("bluetooth_private/cancel_pairing"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothPrivateApiTest, PincodePairing) {
+ EXPECT_CALL(*mock_adapter_.get(),
+ AddPairingDelegate(
+ _, device::BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_HIGH))
+ .WillOnce(WithoutArgs(
+ Invoke(this, &BluetoothPrivateApiTest::DispatchPincodePairingEvent)));
+ EXPECT_CALL(*mock_device_, ExpectingPinCode()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*mock_device_, SetPinCode("abbbbbbk"));
+ ASSERT_TRUE(RunComponentExtensionTest("bluetooth_private/pincode_pairing"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothPrivateApiTest, PasskeyPairing) {
+ EXPECT_CALL(*mock_adapter_.get(),
+ AddPairingDelegate(
+ _, device::BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_HIGH))
+ .WillOnce(WithoutArgs(
+ Invoke(this, &BluetoothPrivateApiTest::DispatchPasskeyPairingEvent)));
+ EXPECT_CALL(*mock_device_, ExpectingPasskey()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*mock_device_, SetPasskey(900531));
+ ASSERT_TRUE(RunComponentExtensionTest("bluetooth_private/passkey_pairing"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothPrivateApiTest, DisconnectAll) {
+ EXPECT_CALL(*mock_device_, IsConnected())
+ .Times(6)
+ .WillOnce(Return(false))
+ .WillOnce(Return(true))
+ .WillOnce(Return(false))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(*mock_device_, Disconnect(_, _))
+ .Times(3)
+ .WillOnce(InvokeCallbackArgument<1>())
+ .WillOnce(InvokeCallbackArgument<1>())
+ .WillOnce(InvokeCallbackArgument<0>());
+ ASSERT_TRUE(RunComponentExtensionTest("bluetooth_private/disconnect"))
+ << message_;
+}
+
+// Device::Forget not implemented on OSX.
+#if !defined(OS_MACOSX)
+IN_PROC_BROWSER_TEST_F(BluetoothPrivateApiTest, ForgetDevice) {
+ EXPECT_CALL(*mock_device_.get(), Forget(_, _))
+ .WillOnce(
+ WithArgs<0>(Invoke(this, &BluetoothPrivateApiTest::ForgetDevice)));
+ ASSERT_TRUE(RunComponentExtensionTest("bluetooth_private/forget_device"))
+ << message_;
+}
+#endif
+
+IN_PROC_BROWSER_TEST_F(BluetoothPrivateApiTest, DiscoveryFilter) {
+ mock_discovery_session_ = new NiceMock<MockBluetoothDiscoverySession>();
+
+ BluetoothDiscoveryFilter discovery_filter(
+ BluetoothDiscoveryFilter::Transport::TRANSPORT_LE);
+ discovery_filter.SetPathloss(50);
+ discovery_filter.AddUUID(BluetoothUUID("cafe"));
+ discovery_filter.AddUUID(
+ BluetoothUUID("0000bebe-0000-1000-8000-00805f9b34fb"));
+
+ EXPECT_CALL(*mock_adapter_.get(), StartDiscoverySessionWithFilterRaw(
+ IsFilterEqual(&discovery_filter), _, _))
+ .Times(1)
+ .WillOnce(WithArgs<1>(Invoke(
+ this, &BluetoothPrivateApiTest::CallSetDiscoveryFilterCallback)));
+ EXPECT_CALL(*mock_discovery_session_, IsActive())
+ .Times(1)
+ .WillOnce(Return(true));
+ EXPECT_CALL(*mock_discovery_session_,
+ SetDiscoveryFilterRaw(Eq(nullptr), _, _))
+ .Times(1)
+ .WillOnce(InvokeCallbackArgument<1>());
+ ASSERT_TRUE(RunComponentExtensionTest("bluetooth_private/discovery_filter"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothPrivateApiTest, Connect) {
+ EXPECT_CALL(*mock_device_.get(), IsConnected())
+ .Times(2)
+ .WillOnce(Return(false))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*mock_device_.get(), Connect(_, _, _))
+ .WillOnce(InvokeCallbackArgument<1>());
+ ASSERT_TRUE(RunComponentExtensionTest("bluetooth_private/connect"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothPrivateApiTest, Pair) {
+ EXPECT_CALL(*mock_adapter_.get(),
+ AddPairingDelegate(
+ _, device::BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_HIGH));
+ EXPECT_CALL(*mock_device_, ExpectingConfirmation())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(*mock_device_.get(), Pair(_, _, _))
+ .WillOnce(DoAll(
+ WithoutArgs(Invoke(
+ this,
+ &BluetoothPrivateApiTest::DispatchConfirmPasskeyPairingEvent)),
+ InvokeCallbackArgument<1>()));
+ ASSERT_TRUE(RunComponentExtensionTest("bluetooth_private/pair")) << message_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/bluetooth_socket/OWNERS b/chromium/extensions/browser/api/bluetooth_socket/OWNERS
new file mode 100644
index 00000000000..53dafd51aec
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth_socket/OWNERS
@@ -0,0 +1,2 @@
+rpaquay@chromium.org
+keybuk@chromium.org
diff --git a/chromium/extensions/browser/api/bluetooth_socket/bluetooth_api_socket.cc b/chromium/extensions/browser/api/bluetooth_socket/bluetooth_api_socket.cc
new file mode 100644
index 00000000000..c1a4004638b
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth_socket/bluetooth_api_socket.cc
@@ -0,0 +1,197 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth_socket/bluetooth_api_socket.h"
+
+#include "base/lazy_instance.h"
+#include "device/bluetooth/bluetooth_socket.h"
+#include "net/base/io_buffer.h"
+
+namespace {
+
+const char kSocketNotConnectedError[] = "Socket not connected";
+const char kSocketNotListeningError[] = "Socket not listening";
+
+} // namespace
+
+namespace extensions {
+
+// static
+static base::LazyInstance<
+ BrowserContextKeyedAPIFactory<ApiResourceManager<BluetoothApiSocket> > >
+ g_server_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+template <>
+BrowserContextKeyedAPIFactory<ApiResourceManager<BluetoothApiSocket> >*
+ApiResourceManager<BluetoothApiSocket>::GetFactoryInstance() {
+ return g_server_factory.Pointer();
+}
+
+BluetoothApiSocket::BluetoothApiSocket(const std::string& owner_extension_id)
+ : ApiResource(owner_extension_id),
+ persistent_(false),
+ buffer_size_(0),
+ paused_(false),
+ connected_(false) {
+ DCHECK_CURRENTLY_ON(kThreadId);
+}
+
+BluetoothApiSocket::BluetoothApiSocket(
+ const std::string& owner_extension_id,
+ scoped_refptr<device::BluetoothSocket> socket,
+ const std::string& device_address,
+ const device::BluetoothUUID& uuid)
+ : ApiResource(owner_extension_id),
+ socket_(socket),
+ device_address_(device_address),
+ uuid_(uuid),
+ persistent_(false),
+ buffer_size_(0),
+ paused_(true),
+ connected_(true) {
+ DCHECK_CURRENTLY_ON(kThreadId);
+}
+
+BluetoothApiSocket::~BluetoothApiSocket() {
+ DCHECK_CURRENTLY_ON(kThreadId);
+ if (socket_.get())
+ socket_->Close();
+}
+
+void BluetoothApiSocket::AdoptConnectedSocket(
+ scoped_refptr<device::BluetoothSocket> socket,
+ const std::string& device_address,
+ const device::BluetoothUUID& uuid) {
+ DCHECK_CURRENTLY_ON(kThreadId);
+
+ if (socket_.get())
+ socket_->Close();
+
+ socket_ = socket;
+ device_address_ = device_address;
+ uuid_ = uuid;
+ connected_ = true;
+}
+
+void BluetoothApiSocket::AdoptListeningSocket(
+ scoped_refptr<device::BluetoothSocket> socket,
+ const device::BluetoothUUID& uuid) {
+ DCHECK_CURRENTLY_ON(kThreadId);
+
+ if (socket_.get())
+ socket_->Close();
+
+ socket_ = socket;
+ device_address_ = "";
+ uuid_ = uuid;
+ connected_ = false;
+}
+
+void BluetoothApiSocket::Disconnect(const base::Closure& callback) {
+ DCHECK_CURRENTLY_ON(kThreadId);
+
+ if (!socket_.get()) {
+ callback.Run();
+ return;
+ }
+
+ connected_ = false;
+ socket_->Disconnect(callback);
+}
+
+bool BluetoothApiSocket::IsPersistent() const {
+ DCHECK_CURRENTLY_ON(kThreadId);
+ return persistent_;
+}
+
+void BluetoothApiSocket::Receive(
+ int count,
+ const ReceiveCompletionCallback& success_callback,
+ const ErrorCompletionCallback& error_callback) {
+ DCHECK_CURRENTLY_ON(kThreadId);
+
+ if (!socket_.get() || !IsConnected()) {
+ error_callback.Run(BluetoothApiSocket::kNotConnected,
+ kSocketNotConnectedError);
+ return;
+ }
+
+ socket_->Receive(count,
+ success_callback,
+ base::Bind(&OnSocketReceiveError, error_callback));
+}
+
+// static
+void BluetoothApiSocket::OnSocketReceiveError(
+ const ErrorCompletionCallback& error_callback,
+ device::BluetoothSocket::ErrorReason reason,
+ const std::string& message) {
+ DCHECK_CURRENTLY_ON(kThreadId);
+ BluetoothApiSocket::ErrorReason error_reason =
+ BluetoothApiSocket::kSystemError;
+ switch (reason) {
+ case device::BluetoothSocket::kIOPending:
+ error_reason = BluetoothApiSocket::kIOPending;
+ break;
+ case device::BluetoothSocket::kDisconnected:
+ error_reason = BluetoothApiSocket::kDisconnected;
+ break;
+ case device::BluetoothSocket::kSystemError:
+ error_reason = BluetoothApiSocket::kSystemError;
+ break;
+ }
+ error_callback.Run(error_reason, message);
+}
+
+void BluetoothApiSocket::Send(scoped_refptr<net::IOBuffer> buffer,
+ int buffer_size,
+ const SendCompletionCallback& success_callback,
+ const ErrorCompletionCallback& error_callback) {
+ DCHECK_CURRENTLY_ON(kThreadId);
+
+ if (!socket_.get() || !IsConnected()) {
+ error_callback.Run(BluetoothApiSocket::kNotConnected,
+ kSocketNotConnectedError);
+ return;
+ }
+
+ socket_->Send(buffer,
+ buffer_size,
+ success_callback,
+ base::Bind(&OnSocketSendError, error_callback));
+}
+
+// static
+void BluetoothApiSocket::OnSocketSendError(
+ const ErrorCompletionCallback& error_callback,
+ const std::string& message) {
+ DCHECK_CURRENTLY_ON(kThreadId);
+ error_callback.Run(BluetoothApiSocket::kSystemError, message);
+}
+
+void BluetoothApiSocket::Accept(
+ const AcceptCompletionCallback& success_callback,
+ const ErrorCompletionCallback& error_callback) {
+ DCHECK_CURRENTLY_ON(kThreadId);
+
+ if (!socket_.get() || IsConnected()) {
+ error_callback.Run(BluetoothApiSocket::kNotListening,
+ kSocketNotListeningError);
+ return;
+ }
+
+ socket_->Accept(success_callback,
+ base::Bind(&OnSocketAcceptError, error_callback));
+}
+
+// static
+void BluetoothApiSocket::OnSocketAcceptError(
+ const ErrorCompletionCallback& error_callback,
+ const std::string& message) {
+ DCHECK_CURRENTLY_ON(kThreadId);
+ error_callback.Run(BluetoothApiSocket::kSystemError, message);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/bluetooth_socket/bluetooth_api_socket.h b/chromium/extensions/browser/api/bluetooth_socket/bluetooth_api_socket.h
new file mode 100644
index 00000000000..40304fb0b0d
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth_socket/bluetooth_api_socket.h
@@ -0,0 +1,162 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_SOCKET_BLUETOOTH_API_SOCKET_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_SOCKET_BLUETOOTH_API_SOCKET_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "device/bluetooth/bluetooth_socket.h"
+#include "device/bluetooth/bluetooth_uuid.h"
+#include "extensions/browser/api/api_resource.h"
+#include "extensions/browser/api/api_resource_manager.h"
+
+namespace net {
+class IOBuffer;
+} // namespace net
+
+namespace extensions {
+
+// Representation of socket instances from the "bluetooth" namespace,
+// abstracting away platform differences from the underlying BluetoothSocketXxx
+// class. All methods must be called on the |kThreadId| thread.
+class BluetoothApiSocket : public ApiResource {
+ public:
+ enum ErrorReason { kSystemError, kNotConnected, kNotListening, kIOPending,
+ kDisconnected };
+
+ typedef base::Callback<void(int)> SendCompletionCallback;
+ typedef base::Callback<void(int, scoped_refptr<net::IOBuffer> io_buffer)>
+ ReceiveCompletionCallback;
+ typedef base::Callback<void(const device::BluetoothDevice* device,
+ scoped_refptr<device::BluetoothSocket>)>
+ AcceptCompletionCallback;
+ typedef base::Callback<void(ErrorReason, const std::string& error_message)>
+ ErrorCompletionCallback;
+
+ explicit BluetoothApiSocket(const std::string& owner_extension_id);
+ BluetoothApiSocket(const std::string& owner_extension_id,
+ scoped_refptr<device::BluetoothSocket> socket,
+ const std::string& device_address,
+ const device::BluetoothUUID& uuid);
+ ~BluetoothApiSocket() override;
+
+ // Adopts a socket |socket| connected to a device with address
+ // |device_address| using the service with UUID |uuid|.
+ virtual void AdoptConnectedSocket(
+ scoped_refptr<device::BluetoothSocket> socket,
+ const std::string& device_address,
+ const device::BluetoothUUID& uuid);
+
+ // Adopts a socket |socket| listening on a service advertised with UUID
+ // |uuid|.
+ virtual void AdoptListeningSocket(
+ scoped_refptr<device::BluetoothSocket> socket,
+ const device::BluetoothUUID& uuid);
+
+ // Closes the underlying connection. This is a best effort, and never fails.
+ virtual void Disconnect(const base::Closure& callback);
+
+ // Receives data from the socket and calls |success_callback| when data is
+ // available. |count| is maximum amount of bytes received. If an error occurs,
+ // calls |error_callback| with a reason and a message. In particular, if a
+ // |Receive| operation is still pending, |error_callback| will be called with
+ // |kIOPending| error.
+ virtual void Receive(int count,
+ const ReceiveCompletionCallback& success_callback,
+ const ErrorCompletionCallback& error_callback);
+
+ // Sends |buffer| to the socket and calls |success_callback| when data has
+ // been successfully sent. |buffer_size| is the numberof bytes contained in
+ // |buffer|. If an error occurs, calls |error_callback| with a reason and a
+ // message. Calling |Send| multiple times without waiting for the callbacks to
+ // be called is a valid usage, as |buffer| instances are buffered until the
+ // underlying communication channel is available for sending data.
+ virtual void Send(scoped_refptr<net::IOBuffer> buffer,
+ int buffer_size,
+ const SendCompletionCallback& success_callback,
+ const ErrorCompletionCallback& error_callback);
+
+ // Accepts a client connection from the socket and calls |success_callback|
+ // when one has connected. If an error occurs, calls |error_callback| with a
+ // reason and a message.
+ virtual void Accept(const AcceptCompletionCallback& success_callback,
+ const ErrorCompletionCallback& error_callback);
+
+ const std::string& device_address() const { return device_address_; }
+ const device::BluetoothUUID& uuid() const { return uuid_; }
+
+ // Overriden from extensions::ApiResource.
+ bool IsPersistent() const override;
+
+ const std::string* name() const { return name_.get(); }
+ void set_name(const std::string& name) { name_.reset(new std::string(name)); }
+
+ bool persistent() const { return persistent_; }
+ void set_persistent(bool persistent) { persistent_ = persistent; }
+
+ int buffer_size() const { return buffer_size_; }
+ void set_buffer_size(int buffer_size) { buffer_size_ = buffer_size; }
+
+ bool paused() const { return paused_; }
+ void set_paused(bool paused) { paused_ = paused; }
+
+ bool IsConnected() const { return connected_; }
+
+ // Platform specific implementations of |BluetoothSocket| require being called
+ // on the UI thread.
+ static const content::BrowserThread::ID kThreadId =
+ content::BrowserThread::UI;
+
+ private:
+ friend class ApiResourceManager<BluetoothApiSocket>;
+ static const char* service_name() { return "BluetoothApiSocketManager"; }
+
+ static void OnSocketReceiveError(
+ const ErrorCompletionCallback& error_callback,
+ device::BluetoothSocket::ErrorReason reason,
+ const std::string& message);
+
+ static void OnSocketSendError(
+ const ErrorCompletionCallback& error_callback,
+ const std::string& message);
+
+ static void OnSocketAcceptError(
+ const ErrorCompletionCallback& error_callback,
+ const std::string& message);
+
+ // The underlying device socket instance.
+ scoped_refptr<device::BluetoothSocket> socket_;
+
+ // The address of the device this socket is connected to.
+ std::string device_address_;
+
+ // The uuid of the service this socket is connected to.
+ device::BluetoothUUID uuid_;
+
+ // Application-defined string - see bluetooth.idl.
+ scoped_ptr<std::string> name_;
+
+ // Flag indicating whether the socket is left open when the application is
+ // suspended - see bluetooth.idl.
+ bool persistent_;
+
+ // The size of the buffer used to receive data - see bluetooth.idl.
+ int buffer_size_;
+
+ // Flag indicating whether a connected socket blocks its peer from sending
+ // more data - see bluetooth.idl.
+ bool paused_;
+
+ // Flag indicating whether a socket is connected.
+ bool connected_;
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothApiSocket);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_SOCKET_BLUETOOTH_API_SOCKET_H_
diff --git a/chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_api.cc b/chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_api.cc
new file mode 100644
index 00000000000..0357ed9b951
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_api.cc
@@ -0,0 +1,672 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth_socket/bluetooth_socket_api.h"
+
+#include <stdint.h>
+#include <utility>
+
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "device/bluetooth/bluetooth_socket.h"
+#include "extensions/browser/api/bluetooth_socket/bluetooth_api_socket.h"
+#include "extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.h"
+#include "extensions/common/api/bluetooth/bluetooth_manifest_data.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "net/base/io_buffer.h"
+
+using content::BrowserThread;
+using extensions::BluetoothApiSocket;
+using extensions::api::bluetooth_socket::ListenOptions;
+using extensions::api::bluetooth_socket::SocketInfo;
+using extensions::api::bluetooth_socket::SocketProperties;
+
+namespace extensions {
+namespace api {
+
+namespace {
+
+const char kDeviceNotFoundError[] = "Device not found";
+const char kInvalidPsmError[] = "Invalid PSM";
+const char kInvalidUuidError[] = "Invalid UUID";
+const char kPermissionDeniedError[] = "Permission denied";
+const char kSocketNotFoundError[] = "Socket not found";
+
+SocketInfo CreateSocketInfo(int socket_id, BluetoothApiSocket* socket) {
+ DCHECK_CURRENTLY_ON(BluetoothApiSocket::kThreadId);
+ SocketInfo socket_info;
+ // This represents what we know about the socket, and does not call through
+ // to the system.
+ socket_info.socket_id = socket_id;
+ if (socket->name()) {
+ socket_info.name.reset(new std::string(*socket->name()));
+ }
+ socket_info.persistent = socket->persistent();
+ if (socket->buffer_size() > 0) {
+ socket_info.buffer_size.reset(new int(socket->buffer_size()));
+ }
+ socket_info.paused = socket->paused();
+ socket_info.connected = socket->IsConnected();
+
+ if (socket->IsConnected())
+ socket_info.address.reset(new std::string(socket->device_address()));
+ socket_info.uuid.reset(new std::string(socket->uuid().canonical_value()));
+
+ return socket_info;
+}
+
+void SetSocketProperties(BluetoothApiSocket* socket,
+ SocketProperties* properties) {
+ if (properties->name.get()) {
+ socket->set_name(*properties->name.get());
+ }
+ if (properties->persistent.get()) {
+ socket->set_persistent(*properties->persistent.get());
+ }
+ if (properties->buffer_size.get()) {
+ // buffer size is validated when issuing the actual Recv operation
+ // on the socket.
+ socket->set_buffer_size(*properties->buffer_size.get());
+ }
+}
+
+BluetoothSocketEventDispatcher* GetSocketEventDispatcher(
+ content::BrowserContext* browser_context) {
+ BluetoothSocketEventDispatcher* socket_event_dispatcher =
+ BluetoothSocketEventDispatcher::Get(browser_context);
+ DCHECK(socket_event_dispatcher)
+ << "There is no socket event dispatcher. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "BluetoothSocketEventDispatcher.";
+ return socket_event_dispatcher;
+}
+
+// Returns |true| if |psm| is a valid PSM.
+// Per the Bluetooth specification, the PSM field must be at least two octets in
+// length, with least significant bit of the least significant octet equal to
+// '1' and the least significant bit of the most significant octet equal to '0'.
+bool IsValidPsm(int psm) {
+ if (psm <= 0)
+ return false;
+
+ std::vector<int16_t> octets;
+ while (psm > 0) {
+ octets.push_back(psm & 0xFF);
+ psm = psm >> 8;
+ }
+
+ if (octets.size() < 2U)
+ return false;
+
+ // The least significant bit of the least significant octet must be '1'.
+ if ((octets.front() & 0x01) != 1)
+ return false;
+
+ // The least significant bit of the most significant octet must be '0'.
+ if ((octets.back() & 0x01) != 0)
+ return false;
+
+ return true;
+}
+
+} // namespace
+
+BluetoothSocketAsyncApiFunction::BluetoothSocketAsyncApiFunction() {}
+
+BluetoothSocketAsyncApiFunction::~BluetoothSocketAsyncApiFunction() {}
+
+bool BluetoothSocketAsyncApiFunction::RunAsync() {
+ if (!PrePrepare() || !Prepare()) {
+ return false;
+ }
+ AsyncWorkStart();
+ return true;
+}
+
+bool BluetoothSocketAsyncApiFunction::PrePrepare() {
+ if (!BluetoothManifestData::CheckSocketPermitted(extension())) {
+ error_ = kPermissionDeniedError;
+ return false;
+ }
+
+ manager_ = ApiResourceManager<BluetoothApiSocket>::Get(browser_context());
+ DCHECK(manager_)
+ << "There is no socket manager. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "ApiResourceManager<BluetoothApiSocket>.";
+ return manager_ != NULL;
+}
+
+bool BluetoothSocketAsyncApiFunction::Respond() { return error_.empty(); }
+
+void BluetoothSocketAsyncApiFunction::AsyncWorkCompleted() {
+ SendResponse(Respond());
+}
+
+void BluetoothSocketAsyncApiFunction::Work() {}
+
+void BluetoothSocketAsyncApiFunction::AsyncWorkStart() {
+ Work();
+ AsyncWorkCompleted();
+}
+
+int BluetoothSocketAsyncApiFunction::AddSocket(BluetoothApiSocket* socket) {
+ return manager_->Add(socket);
+}
+
+content::BrowserThread::ID
+BluetoothSocketAsyncApiFunction::work_thread_id() const {
+ return BluetoothApiSocket::kThreadId;
+}
+
+BluetoothApiSocket* BluetoothSocketAsyncApiFunction::GetSocket(
+ int api_resource_id) {
+ return manager_->Get(extension_id(), api_resource_id);
+}
+
+void BluetoothSocketAsyncApiFunction::RemoveSocket(int api_resource_id) {
+ manager_->Remove(extension_id(), api_resource_id);
+}
+
+base::hash_set<int>* BluetoothSocketAsyncApiFunction::GetSocketIds() {
+ return manager_->GetResourceIds(extension_id());
+}
+
+BluetoothSocketCreateFunction::BluetoothSocketCreateFunction() {}
+
+BluetoothSocketCreateFunction::~BluetoothSocketCreateFunction() {}
+
+bool BluetoothSocketCreateFunction::Prepare() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ params_ = bluetooth_socket::Create::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void BluetoothSocketCreateFunction::Work() {
+ DCHECK_CURRENTLY_ON(work_thread_id());
+
+ BluetoothApiSocket* socket = new BluetoothApiSocket(extension_id());
+
+ bluetooth_socket::SocketProperties* properties =
+ params_.get()->properties.get();
+ if (properties) {
+ SetSocketProperties(socket, properties);
+ }
+
+ bluetooth_socket::CreateInfo create_info;
+ create_info.socket_id = AddSocket(socket);
+ results_ = bluetooth_socket::Create::Results::Create(create_info);
+ AsyncWorkCompleted();
+}
+
+BluetoothSocketUpdateFunction::BluetoothSocketUpdateFunction() {}
+
+BluetoothSocketUpdateFunction::~BluetoothSocketUpdateFunction() {}
+
+bool BluetoothSocketUpdateFunction::Prepare() {
+ params_ = bluetooth_socket::Update::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void BluetoothSocketUpdateFunction::Work() {
+ BluetoothApiSocket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ SetSocketProperties(socket, &params_.get()->properties);
+ results_ = bluetooth_socket::Update::Results::Create();
+}
+
+BluetoothSocketSetPausedFunction::BluetoothSocketSetPausedFunction()
+ : socket_event_dispatcher_(NULL) {}
+
+BluetoothSocketSetPausedFunction::~BluetoothSocketSetPausedFunction() {}
+
+bool BluetoothSocketSetPausedFunction::Prepare() {
+ params_ = bluetooth_socket::SetPaused::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ socket_event_dispatcher_ = GetSocketEventDispatcher(browser_context());
+ return socket_event_dispatcher_ != NULL;
+}
+
+void BluetoothSocketSetPausedFunction::Work() {
+ BluetoothApiSocket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ if (socket->paused() != params_->paused) {
+ socket->set_paused(params_->paused);
+ if (!params_->paused) {
+ socket_event_dispatcher_->OnSocketResume(extension_id(),
+ params_->socket_id);
+ }
+ }
+
+ results_ = bluetooth_socket::SetPaused::Results::Create();
+}
+
+BluetoothSocketListenFunction::BluetoothSocketListenFunction() {}
+
+BluetoothSocketListenFunction::~BluetoothSocketListenFunction() {}
+
+bool BluetoothSocketListenFunction::Prepare() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!CreateParams())
+ return false;
+ socket_event_dispatcher_ = GetSocketEventDispatcher(browser_context());
+ return socket_event_dispatcher_ != NULL;
+}
+
+void BluetoothSocketListenFunction::AsyncWorkStart() {
+ DCHECK_CURRENTLY_ON(work_thread_id());
+ device::BluetoothAdapterFactory::GetAdapter(
+ base::Bind(&BluetoothSocketListenFunction::OnGetAdapter, this));
+}
+
+void BluetoothSocketListenFunction::OnGetAdapter(
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ DCHECK_CURRENTLY_ON(work_thread_id());
+ BluetoothApiSocket* socket = GetSocket(socket_id());
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ device::BluetoothUUID bluetooth_uuid(uuid());
+ if (!bluetooth_uuid.IsValid()) {
+ error_ = kInvalidUuidError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ BluetoothPermissionRequest param(uuid());
+ if (!BluetoothManifestData::CheckRequest(extension(), param)) {
+ error_ = kPermissionDeniedError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ scoped_ptr<std::string> name;
+ if (socket->name())
+ name.reset(new std::string(*socket->name()));
+
+ CreateService(
+ adapter, bluetooth_uuid, std::move(name),
+ base::Bind(&BluetoothSocketListenFunction::OnCreateService, this),
+ base::Bind(&BluetoothSocketListenFunction::OnCreateServiceError, this));
+}
+
+
+void BluetoothSocketListenFunction::OnCreateService(
+ scoped_refptr<device::BluetoothSocket> socket) {
+ DCHECK_CURRENTLY_ON(work_thread_id());
+
+ // Fetch the socket again since this is not a reference-counted object, and
+ // it may have gone away in the meantime (we check earlier to avoid making
+ // a connection in the case of an obvious programming error).
+ BluetoothApiSocket* api_socket = GetSocket(socket_id());
+ if (!api_socket) {
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ api_socket->AdoptListeningSocket(socket,
+ device::BluetoothUUID(uuid()));
+ socket_event_dispatcher_->OnSocketListen(extension_id(), socket_id());
+
+ CreateResults();
+ AsyncWorkCompleted();
+}
+
+void BluetoothSocketListenFunction::OnCreateServiceError(
+ const std::string& message) {
+ DCHECK_CURRENTLY_ON(work_thread_id());
+ error_ = message;
+ AsyncWorkCompleted();
+}
+
+BluetoothSocketListenUsingRfcommFunction::
+ BluetoothSocketListenUsingRfcommFunction() {}
+
+BluetoothSocketListenUsingRfcommFunction::
+ ~BluetoothSocketListenUsingRfcommFunction() {}
+
+int BluetoothSocketListenUsingRfcommFunction::socket_id() const {
+ return params_->socket_id;
+}
+
+const std::string& BluetoothSocketListenUsingRfcommFunction::uuid() const {
+ return params_->uuid;
+}
+
+bool BluetoothSocketListenUsingRfcommFunction::CreateParams() {
+ params_ = bluetooth_socket::ListenUsingRfcomm::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void BluetoothSocketListenUsingRfcommFunction::CreateService(
+ scoped_refptr<device::BluetoothAdapter> adapter,
+ const device::BluetoothUUID& uuid,
+ scoped_ptr<std::string> name,
+ const device::BluetoothAdapter::CreateServiceCallback& callback,
+ const device::BluetoothAdapter::CreateServiceErrorCallback&
+ error_callback) {
+ device::BluetoothAdapter::ServiceOptions service_options;
+ service_options.name = std::move(name);
+
+ ListenOptions* options = params_->options.get();
+ if (options) {
+ if (options->channel.get())
+ service_options.channel.reset(new int(*(options->channel)));
+ }
+
+ adapter->CreateRfcommService(uuid, service_options, callback, error_callback);
+}
+
+void BluetoothSocketListenUsingRfcommFunction::CreateResults() {
+ results_ = bluetooth_socket::ListenUsingRfcomm::Results::Create();
+}
+
+BluetoothSocketListenUsingL2capFunction::
+ BluetoothSocketListenUsingL2capFunction() {}
+
+BluetoothSocketListenUsingL2capFunction::
+ ~BluetoothSocketListenUsingL2capFunction() {}
+
+int BluetoothSocketListenUsingL2capFunction::socket_id() const {
+ return params_->socket_id;
+}
+
+const std::string& BluetoothSocketListenUsingL2capFunction::uuid() const {
+ return params_->uuid;
+}
+
+bool BluetoothSocketListenUsingL2capFunction::CreateParams() {
+ params_ = bluetooth_socket::ListenUsingL2cap::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void BluetoothSocketListenUsingL2capFunction::CreateService(
+ scoped_refptr<device::BluetoothAdapter> adapter,
+ const device::BluetoothUUID& uuid,
+ scoped_ptr<std::string> name,
+ const device::BluetoothAdapter::CreateServiceCallback& callback,
+ const device::BluetoothAdapter::CreateServiceErrorCallback&
+ error_callback) {
+ device::BluetoothAdapter::ServiceOptions service_options;
+ service_options.name = std::move(name);
+
+ ListenOptions* options = params_->options.get();
+ if (options) {
+ if (options->psm) {
+ int psm = *options->psm;
+ if (!IsValidPsm(psm)) {
+ error_callback.Run(kInvalidPsmError);
+ return;
+ }
+
+ service_options.psm.reset(new int(psm));
+ }
+ }
+
+ adapter->CreateL2capService(uuid, service_options, callback, error_callback);
+}
+
+void BluetoothSocketListenUsingL2capFunction::CreateResults() {
+ results_ = bluetooth_socket::ListenUsingL2cap::Results::Create();
+}
+
+BluetoothSocketAbstractConnectFunction::
+ BluetoothSocketAbstractConnectFunction() {}
+
+BluetoothSocketAbstractConnectFunction::
+ ~BluetoothSocketAbstractConnectFunction() {}
+
+bool BluetoothSocketAbstractConnectFunction::Prepare() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ params_ = bluetooth_socket::Connect::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ socket_event_dispatcher_ = GetSocketEventDispatcher(browser_context());
+ return socket_event_dispatcher_ != NULL;
+}
+
+void BluetoothSocketAbstractConnectFunction::AsyncWorkStart() {
+ DCHECK_CURRENTLY_ON(work_thread_id());
+ device::BluetoothAdapterFactory::GetAdapter(
+ base::Bind(&BluetoothSocketAbstractConnectFunction::OnGetAdapter, this));
+}
+
+void BluetoothSocketAbstractConnectFunction::OnGetAdapter(
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ DCHECK_CURRENTLY_ON(work_thread_id());
+ BluetoothApiSocket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ device::BluetoothDevice* device = adapter->GetDevice(params_->address);
+ if (!device) {
+ error_ = kDeviceNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ device::BluetoothUUID uuid(params_->uuid);
+ if (!uuid.IsValid()) {
+ error_ = kInvalidUuidError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ BluetoothPermissionRequest param(params_->uuid);
+ if (!BluetoothManifestData::CheckRequest(extension(), param)) {
+ error_ = kPermissionDeniedError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ ConnectToService(device, uuid);
+}
+
+void BluetoothSocketAbstractConnectFunction::OnConnect(
+ scoped_refptr<device::BluetoothSocket> socket) {
+ DCHECK_CURRENTLY_ON(work_thread_id());
+
+ // Fetch the socket again since this is not a reference-counted object, and
+ // it may have gone away in the meantime (we check earlier to avoid making
+ // a connection in the case of an obvious programming error).
+ BluetoothApiSocket* api_socket = GetSocket(params_->socket_id);
+ if (!api_socket) {
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ api_socket->AdoptConnectedSocket(socket,
+ params_->address,
+ device::BluetoothUUID(params_->uuid));
+ socket_event_dispatcher_->OnSocketConnect(extension_id(),
+ params_->socket_id);
+
+ results_ = bluetooth_socket::Connect::Results::Create();
+ AsyncWorkCompleted();
+}
+
+void BluetoothSocketAbstractConnectFunction::OnConnectError(
+ const std::string& message) {
+ DCHECK_CURRENTLY_ON(work_thread_id());
+ error_ = message;
+ AsyncWorkCompleted();
+}
+
+BluetoothSocketConnectFunction::BluetoothSocketConnectFunction() {}
+
+BluetoothSocketConnectFunction::~BluetoothSocketConnectFunction() {}
+
+void BluetoothSocketConnectFunction::ConnectToService(
+ device::BluetoothDevice* device,
+ const device::BluetoothUUID& uuid) {
+ device->ConnectToService(
+ uuid,
+ base::Bind(&BluetoothSocketConnectFunction::OnConnect, this),
+ base::Bind(&BluetoothSocketConnectFunction::OnConnectError, this));
+}
+
+BluetoothSocketDisconnectFunction::BluetoothSocketDisconnectFunction() {}
+
+BluetoothSocketDisconnectFunction::~BluetoothSocketDisconnectFunction() {}
+
+bool BluetoothSocketDisconnectFunction::Prepare() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ params_ = bluetooth_socket::Disconnect::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void BluetoothSocketDisconnectFunction::AsyncWorkStart() {
+ DCHECK_CURRENTLY_ON(work_thread_id());
+ BluetoothApiSocket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ socket->Disconnect(base::Bind(&BluetoothSocketDisconnectFunction::OnSuccess,
+ this));
+}
+
+void BluetoothSocketDisconnectFunction::OnSuccess() {
+ DCHECK_CURRENTLY_ON(work_thread_id());
+ results_ = bluetooth_socket::Disconnect::Results::Create();
+ AsyncWorkCompleted();
+}
+
+BluetoothSocketCloseFunction::BluetoothSocketCloseFunction() {}
+
+BluetoothSocketCloseFunction::~BluetoothSocketCloseFunction() {}
+
+bool BluetoothSocketCloseFunction::Prepare() {
+ params_ = bluetooth_socket::Close::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void BluetoothSocketCloseFunction::Work() {
+ BluetoothApiSocket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ RemoveSocket(params_->socket_id);
+ results_ = bluetooth_socket::Close::Results::Create();
+}
+
+BluetoothSocketSendFunction::BluetoothSocketSendFunction()
+ : io_buffer_size_(0) {}
+
+BluetoothSocketSendFunction::~BluetoothSocketSendFunction() {}
+
+bool BluetoothSocketSendFunction::Prepare() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ params_ = bluetooth_socket::Send::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ io_buffer_size_ = params_->data.size();
+ io_buffer_ = new net::WrappedIOBuffer(params_->data.data());
+ return true;
+}
+
+void BluetoothSocketSendFunction::AsyncWorkStart() {
+ DCHECK_CURRENTLY_ON(work_thread_id());
+ BluetoothApiSocket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ socket->Send(io_buffer_,
+ io_buffer_size_,
+ base::Bind(&BluetoothSocketSendFunction::OnSuccess, this),
+ base::Bind(&BluetoothSocketSendFunction::OnError, this));
+}
+
+void BluetoothSocketSendFunction::OnSuccess(int bytes_sent) {
+ DCHECK_CURRENTLY_ON(work_thread_id());
+ results_ = bluetooth_socket::Send::Results::Create(bytes_sent);
+ AsyncWorkCompleted();
+}
+
+void BluetoothSocketSendFunction::OnError(
+ BluetoothApiSocket::ErrorReason reason,
+ const std::string& message) {
+ DCHECK_CURRENTLY_ON(work_thread_id());
+ error_ = message;
+ AsyncWorkCompleted();
+}
+
+BluetoothSocketGetInfoFunction::BluetoothSocketGetInfoFunction() {}
+
+BluetoothSocketGetInfoFunction::~BluetoothSocketGetInfoFunction() {}
+
+bool BluetoothSocketGetInfoFunction::Prepare() {
+ params_ = bluetooth_socket::GetInfo::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void BluetoothSocketGetInfoFunction::Work() {
+ BluetoothApiSocket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ results_ = bluetooth_socket::GetInfo::Results::Create(
+ CreateSocketInfo(params_->socket_id, socket));
+}
+
+BluetoothSocketGetSocketsFunction::BluetoothSocketGetSocketsFunction() {}
+
+BluetoothSocketGetSocketsFunction::~BluetoothSocketGetSocketsFunction() {}
+
+bool BluetoothSocketGetSocketsFunction::Prepare() { return true; }
+
+void BluetoothSocketGetSocketsFunction::Work() {
+ std::vector<bluetooth_socket::SocketInfo> socket_infos;
+ base::hash_set<int>* resource_ids = GetSocketIds();
+ if (resource_ids) {
+ for (int socket_id : *resource_ids) {
+ BluetoothApiSocket* socket = GetSocket(socket_id);
+ if (socket) {
+ socket_infos.push_back(CreateSocketInfo(socket_id, socket));
+ }
+ }
+ }
+ results_ = bluetooth_socket::GetSockets::Results::Create(socket_infos);
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_api.h b/chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_api.h
new file mode 100644
index 00000000000..31de3b77da6
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_api.h
@@ -0,0 +1,354 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_SOCKET_BLUETOOTH_SOCKET_API_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_SOCKET_BLUETOOTH_SOCKET_API_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/browser/api/async_api_function.h"
+#include "extensions/browser/api/bluetooth_socket/bluetooth_api_socket.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/browser/extension_function_histogram_value.h"
+#include "extensions/common/api/bluetooth_socket.h"
+
+namespace device {
+class BluetoothSocket;
+}
+
+namespace net {
+class IOBuffer;
+}
+
+namespace extensions {
+
+namespace api {
+
+class BluetoothSocketEventDispatcher;
+
+// Asynchronous API function that performs its work on the BluetoothApiSocket
+// thread while providing methods to manage resources of that class. This
+// follows the pattern of AsyncApiFunction, but does not derive from it,
+// because BluetoothApiSocket methods must be called on the UI Thread.
+class BluetoothSocketAsyncApiFunction : public AsyncExtensionFunction {
+ public:
+ BluetoothSocketAsyncApiFunction();
+
+ protected:
+ ~BluetoothSocketAsyncApiFunction() override;
+
+ // AsyncExtensionFunction:
+ bool RunAsync() override;
+
+ bool PrePrepare();
+ bool Respond();
+ void AsyncWorkCompleted();
+
+ virtual bool Prepare() = 0;
+ virtual void Work();
+ virtual void AsyncWorkStart();
+
+ content::BrowserThread::ID work_thread_id() const;
+
+ int AddSocket(BluetoothApiSocket* socket);
+ BluetoothApiSocket* GetSocket(int api_resource_id);
+ void RemoveSocket(int api_resource_id);
+ base::hash_set<int>* GetSocketIds();
+
+ private:
+ ApiResourceManager<BluetoothApiSocket>* manager_;
+};
+
+class BluetoothSocketCreateFunction : public BluetoothSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.create", BLUETOOTHSOCKET_CREATE);
+
+ BluetoothSocketCreateFunction();
+
+ protected:
+ ~BluetoothSocketCreateFunction() override;
+
+ // BluetoothSocketAsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<bluetooth_socket::Create::Params> params_;
+};
+
+class BluetoothSocketUpdateFunction : public BluetoothSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.update", BLUETOOTHSOCKET_UPDATE);
+
+ BluetoothSocketUpdateFunction();
+
+ protected:
+ ~BluetoothSocketUpdateFunction() override;
+
+ // BluetoothSocketAsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<bluetooth_socket::Update::Params> params_;
+};
+
+class BluetoothSocketSetPausedFunction
+ : public BluetoothSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.setPaused",
+ BLUETOOTHSOCKET_SETPAUSED);
+
+ BluetoothSocketSetPausedFunction();
+
+ protected:
+ ~BluetoothSocketSetPausedFunction() override;
+
+ // BluetoothSocketAsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<bluetooth_socket::SetPaused::Params> params_;
+ BluetoothSocketEventDispatcher* socket_event_dispatcher_;
+};
+
+class BluetoothSocketListenFunction : public BluetoothSocketAsyncApiFunction {
+ public:
+ BluetoothSocketListenFunction();
+
+ virtual bool CreateParams() = 0;
+ virtual void CreateService(
+ scoped_refptr<device::BluetoothAdapter> adapter,
+ const device::BluetoothUUID& uuid,
+ scoped_ptr<std::string> name,
+ const device::BluetoothAdapter::CreateServiceCallback& callback,
+ const device::BluetoothAdapter::CreateServiceErrorCallback&
+ error_callback) = 0;
+ virtual void CreateResults() = 0;
+
+ virtual int socket_id() const = 0;
+ virtual const std::string& uuid() const = 0;
+
+ // BluetoothSocketAsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ protected:
+ ~BluetoothSocketListenFunction() override;
+
+ virtual void OnGetAdapter(scoped_refptr<device::BluetoothAdapter> adapter);
+ virtual void OnCreateService(scoped_refptr<device::BluetoothSocket> socket);
+ virtual void OnCreateServiceError(const std::string& message);
+
+ BluetoothSocketEventDispatcher* socket_event_dispatcher_;
+};
+
+class BluetoothSocketListenUsingRfcommFunction
+ : public BluetoothSocketListenFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.listenUsingRfcomm",
+ BLUETOOTHSOCKET_LISTENUSINGRFCOMM);
+
+ BluetoothSocketListenUsingRfcommFunction();
+
+ // BluetoothSocketListenFunction:
+ int socket_id() const override;
+ const std::string& uuid() const override;
+
+ bool CreateParams() override;
+ void CreateService(
+ scoped_refptr<device::BluetoothAdapter> adapter,
+ const device::BluetoothUUID& uuid,
+ scoped_ptr<std::string> name,
+ const device::BluetoothAdapter::CreateServiceCallback& callback,
+ const device::BluetoothAdapter::CreateServiceErrorCallback&
+ error_callback) override;
+ void CreateResults() override;
+
+ protected:
+ ~BluetoothSocketListenUsingRfcommFunction() override;
+
+ private:
+ scoped_ptr<bluetooth_socket::ListenUsingRfcomm::Params> params_;
+};
+
+class BluetoothSocketListenUsingL2capFunction
+ : public BluetoothSocketListenFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.listenUsingL2cap",
+ BLUETOOTHSOCKET_LISTENUSINGL2CAP);
+
+ BluetoothSocketListenUsingL2capFunction();
+
+ // BluetoothSocketListenFunction:
+ int socket_id() const override;
+ const std::string& uuid() const override;
+
+ bool CreateParams() override;
+ void CreateService(
+ scoped_refptr<device::BluetoothAdapter> adapter,
+ const device::BluetoothUUID& uuid,
+ scoped_ptr<std::string> name,
+ const device::BluetoothAdapter::CreateServiceCallback& callback,
+ const device::BluetoothAdapter::CreateServiceErrorCallback&
+ error_callback) override;
+ void CreateResults() override;
+
+ protected:
+ ~BluetoothSocketListenUsingL2capFunction() override;
+
+ private:
+ scoped_ptr<bluetooth_socket::ListenUsingL2cap::Params> params_;
+};
+
+class BluetoothSocketAbstractConnectFunction :
+ public BluetoothSocketAsyncApiFunction {
+ public:
+ BluetoothSocketAbstractConnectFunction();
+
+ protected:
+ ~BluetoothSocketAbstractConnectFunction() override;
+
+ // BluetoothSocketAsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ // Subclasses should implement this method to connect to the service
+ // registered with |uuid| on the |device|.
+ virtual void ConnectToService(device::BluetoothDevice* device,
+ const device::BluetoothUUID& uuid) = 0;
+
+ virtual void OnConnect(scoped_refptr<device::BluetoothSocket> socket);
+ virtual void OnConnectError(const std::string& message);
+
+ private:
+ virtual void OnGetAdapter(scoped_refptr<device::BluetoothAdapter> adapter);
+
+ scoped_ptr<bluetooth_socket::Connect::Params> params_;
+ BluetoothSocketEventDispatcher* socket_event_dispatcher_;
+};
+
+class BluetoothSocketConnectFunction :
+ public BluetoothSocketAbstractConnectFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.connect",
+ BLUETOOTHSOCKET_CONNECT);
+
+ BluetoothSocketConnectFunction();
+
+ protected:
+ ~BluetoothSocketConnectFunction() override;
+
+ // BluetoothSocketAbstractConnectFunction:
+ void ConnectToService(device::BluetoothDevice* device,
+ const device::BluetoothUUID& uuid) override;
+};
+
+class BluetoothSocketDisconnectFunction
+ : public BluetoothSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.disconnect",
+ BLUETOOTHSOCKET_DISCONNECT);
+
+ BluetoothSocketDisconnectFunction();
+
+ protected:
+ ~BluetoothSocketDisconnectFunction() override;
+
+ // BluetoothSocketAsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ private:
+ virtual void OnSuccess();
+
+ scoped_ptr<bluetooth_socket::Disconnect::Params> params_;
+};
+
+class BluetoothSocketCloseFunction : public BluetoothSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.close", BLUETOOTHSOCKET_CLOSE);
+
+ BluetoothSocketCloseFunction();
+
+ protected:
+ ~BluetoothSocketCloseFunction() override;
+
+ // BluetoothSocketAsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<bluetooth_socket::Close::Params> params_;
+};
+
+class BluetoothSocketSendFunction : public BluetoothSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.send", BLUETOOTHSOCKET_SEND);
+
+ BluetoothSocketSendFunction();
+
+ protected:
+ ~BluetoothSocketSendFunction() override;
+
+ // BluetoothSocketAsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ private:
+ void OnSuccess(int bytes_sent);
+ void OnError(BluetoothApiSocket::ErrorReason reason,
+ const std::string& message);
+
+ scoped_ptr<bluetooth_socket::Send::Params> params_;
+ scoped_refptr<net::IOBuffer> io_buffer_;
+ size_t io_buffer_size_;
+};
+
+class BluetoothSocketGetInfoFunction : public BluetoothSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.getInfo",
+ BLUETOOTHSOCKET_GETINFO);
+
+ BluetoothSocketGetInfoFunction();
+
+ protected:
+ ~BluetoothSocketGetInfoFunction() override;
+
+ // BluetoothSocketAsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<bluetooth_socket::GetInfo::Params> params_;
+};
+
+class BluetoothSocketGetSocketsFunction
+ : public BluetoothSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("bluetoothSocket.getSockets",
+ BLUETOOTHSOCKET_GETSOCKETS);
+
+ BluetoothSocketGetSocketsFunction();
+
+ protected:
+ ~BluetoothSocketGetSocketsFunction() override;
+
+ // BluetoothSocketAsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+};
+
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_SOCKET_BLUETOOTH_SOCKET_API_H_
diff --git a/chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_apitest.cc b/chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_apitest.cc
new file mode 100644
index 00000000000..74c0842485a
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_apitest.cc
@@ -0,0 +1,217 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/run_loop.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/bluetooth_uuid.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "device/bluetooth/test/mock_bluetooth_device.h"
+#include "device/bluetooth/test/mock_bluetooth_socket.h"
+#include "extensions/browser/api/bluetooth_socket/bluetooth_socket_api.h"
+#include "extensions/common/test_util.h"
+#include "extensions/shell/test/shell_apitest.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "extensions/test/result_catcher.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using device::BluetoothAdapter;
+using device::BluetoothAdapterFactory;
+using device::BluetoothDevice;
+using device::BluetoothSocket;
+using device::BluetoothUUID;
+using device::MockBluetoothAdapter;
+using device::MockBluetoothDevice;
+using device::MockBluetoothSocket;
+using extensions::Extension;
+using extensions::ResultCatcher;
+
+namespace api = extensions::api;
+
+namespace {
+
+class BluetoothSocketApiTest : public extensions::ShellApiTest {
+ public:
+ BluetoothSocketApiTest() {}
+
+ void SetUpOnMainThread() override {
+ ShellApiTest::SetUpOnMainThread();
+ empty_extension_ = extensions::test_util::CreateEmptyExtension();
+ SetUpMockAdapter();
+ }
+
+ void SetUpMockAdapter() {
+ // The browser will clean this up when it is torn down.
+ mock_adapter_ = new testing::StrictMock<MockBluetoothAdapter>();
+ BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_);
+
+ mock_device1_.reset(
+ new testing::NiceMock<MockBluetoothDevice>(mock_adapter_.get(),
+ 0,
+ "d1",
+ "11:12:13:14:15:16",
+ true /* paired */,
+ false /* connected */));
+ mock_device2_.reset(
+ new testing::NiceMock<MockBluetoothDevice>(mock_adapter_.get(),
+ 0,
+ "d2",
+ "21:22:23:24:25:26",
+ true /* paired */,
+ false /* connected */));
+ }
+
+ protected:
+ scoped_refptr<testing::StrictMock<MockBluetoothAdapter> > mock_adapter_;
+ scoped_ptr<testing::NiceMock<MockBluetoothDevice> > mock_device1_;
+ scoped_ptr<testing::NiceMock<MockBluetoothDevice> > mock_device2_;
+
+ private:
+ scoped_refptr<Extension> empty_extension_;
+};
+
+// testing::InvokeArgument<N> does not work with base::Callback, fortunately
+// gmock makes it simple to create action templates that do for the various
+// possible numbers of arguments.
+ACTION_TEMPLATE(InvokeCallbackArgument,
+ HAS_1_TEMPLATE_PARAMS(int, k),
+ AND_0_VALUE_PARAMS()) {
+ ::std::tr1::get<k>(args).Run();
+}
+
+ACTION_TEMPLATE(InvokeCallbackArgument,
+ HAS_1_TEMPLATE_PARAMS(int, k),
+ AND_1_VALUE_PARAMS(p0)) {
+ ::std::tr1::get<k>(args).Run(p0);
+}
+
+ACTION_TEMPLATE(InvokeCallbackArgument,
+ HAS_1_TEMPLATE_PARAMS(int, k),
+ AND_2_VALUE_PARAMS(p0, p1)) {
+ ::std::tr1::get<k>(args).Run(p0, p1);
+}
+
+} // namespace
+
+IN_PROC_BROWSER_TEST_F(BluetoothSocketApiTest, Connect) {
+ ResultCatcher catcher;
+ catcher.RestrictToBrowserContext(browser_context());
+
+ // Return the right mock device object for the address used by the test,
+ // return NULL for the "Device not found" test.
+ EXPECT_CALL(*mock_adapter_.get(), GetDevice(mock_device1_->GetAddress()))
+ .WillRepeatedly(testing::Return(mock_device1_.get()));
+ EXPECT_CALL(*mock_adapter_.get(), GetDevice(std::string("aa:aa:aa:aa:aa:aa")))
+ .WillOnce(testing::Return(static_cast<BluetoothDevice*>(NULL)));
+
+ // Return a mock socket object as a successful result to the connect() call.
+ BluetoothUUID service_uuid("8e3ad063-db38-4289-aa8f-b30e4223cf40");
+ scoped_refptr<testing::StrictMock<MockBluetoothSocket> > mock_socket
+ = new testing::StrictMock<MockBluetoothSocket>();
+ EXPECT_CALL(*mock_device1_,
+ ConnectToService(service_uuid, testing::_, testing::_))
+ .WillOnce(InvokeCallbackArgument<1>(mock_socket));
+
+ // Since the socket is unpaused, expect a call to Receive() from the socket
+ // dispatcher. Since there is no data, this will not call its callback.
+ EXPECT_CALL(*mock_socket.get(), Receive(testing::_, testing::_, testing::_));
+
+ // The test also cleans up by calling Disconnect and Close.
+ EXPECT_CALL(*mock_socket.get(), Disconnect(testing::_))
+ .WillOnce(InvokeCallbackArgument<0>());
+ EXPECT_CALL(*mock_socket.get(), Close());
+
+ // Run the test.
+ ExtensionTestMessageListener listener("ready", true);
+ scoped_refptr<const Extension> extension(
+ LoadApp("api_test/bluetooth_socket/connect"));
+ ASSERT_TRUE(extension.get());
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ listener.Reply("go");
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+#if defined(_LIBCPP_VERSION)
+// This test fails in libc++ builds, see http://crbug.com/392205.
+#define MAYBE_Listen DISABLED_Listen
+#else
+#define MAYBE_Listen Listen
+#endif
+IN_PROC_BROWSER_TEST_F(BluetoothSocketApiTest, MAYBE_Listen) {
+ ResultCatcher catcher;
+ catcher.RestrictToBrowserContext(browser_context());
+
+ // Return a mock socket object as a successful result to the create service
+ // call.
+ BluetoothUUID service_uuid("2de497f9-ab28-49db-b6d2-066ea69f1737");
+ scoped_refptr<testing::StrictMock<MockBluetoothSocket> > mock_server_socket
+ = new testing::StrictMock<MockBluetoothSocket>();
+ BluetoothAdapter::ServiceOptions service_options;
+ service_options.name.reset(new std::string("MyServiceName"));
+ EXPECT_CALL(
+ *mock_adapter_.get(),
+ CreateRfcommService(
+ service_uuid,
+ testing::Field(&BluetoothAdapter::ServiceOptions::name,
+ testing::Pointee(testing::Eq("MyServiceName"))),
+ testing::_,
+ testing::_)).WillOnce(InvokeCallbackArgument<2>(mock_server_socket));
+
+ // Since the socket is unpaused, expect a call to Accept() from the socket
+ // dispatcher. We'll immediately send back another mock socket to represent
+ // the client API. Further calls will return no data and behave as if
+ // pending.
+ scoped_refptr<testing::StrictMock<MockBluetoothSocket> > mock_client_socket
+ = new testing::StrictMock<MockBluetoothSocket>();
+ EXPECT_CALL(*mock_server_socket.get(), Accept(testing::_, testing::_))
+ .Times(2)
+ .WillOnce(
+ InvokeCallbackArgument<0>(mock_device1_.get(), mock_client_socket))
+ .WillOnce(testing::Return());
+
+ // Run the test, it sends a ready signal once it's ready for us to dispatch
+ // a client connection to it.
+ ExtensionTestMessageListener socket_listening("ready", true);
+ scoped_refptr<const Extension> extension(
+ LoadApp("api_test/bluetooth_socket/listen"));
+ ASSERT_TRUE(extension.get());
+ EXPECT_TRUE(socket_listening.WaitUntilSatisfied());
+
+ // Connection events are dispatched using a couple of PostTask to the UI
+ // thread. Waiting until idle ensures the event is dispatched to the
+ // receiver(s).
+ base::RunLoop().RunUntilIdle();
+ ExtensionTestMessageListener listener("ready", true);
+ socket_listening.Reply("go");
+
+ // Second stage of tests checks for error conditions, and will clean up
+ // the existing server and client sockets.
+ EXPECT_CALL(*mock_server_socket.get(), Disconnect(testing::_))
+ .WillOnce(InvokeCallbackArgument<0>());
+ EXPECT_CALL(*mock_server_socket.get(), Close());
+
+ EXPECT_CALL(*mock_client_socket.get(), Disconnect(testing::_))
+ .WillOnce(InvokeCallbackArgument<0>());
+ EXPECT_CALL(*mock_client_socket.get(), Close());
+
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+ listener.Reply("go");
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(BluetoothSocketApiTest, PermissionDenied) {
+ ResultCatcher catcher;
+ catcher.RestrictToBrowserContext(browser_context());
+
+ // Run the test.
+ scoped_refptr<const Extension> extension(
+ LoadApp("api_test/bluetooth_socket/permission_denied"));
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
diff --git a/chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.cc b/chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.cc
new file mode 100644
index 00000000000..d6185dbba1f
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.cc
@@ -0,0 +1,377 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.h"
+
+#include <utility>
+
+#include "base/lazy_instance.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "device/bluetooth/bluetooth_socket.h"
+#include "extensions/browser/api/bluetooth_socket/bluetooth_api_socket.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/common/api/bluetooth_socket.h"
+#include "net/base/io_buffer.h"
+
+namespace {
+
+namespace bluetooth_socket = extensions::api::bluetooth_socket;
+using extensions::BluetoothApiSocket;
+
+int kDefaultBufferSize = 4096;
+
+bluetooth_socket::ReceiveError MapReceiveErrorReason(
+ BluetoothApiSocket::ErrorReason value) {
+ switch (value) {
+ case BluetoothApiSocket::kDisconnected:
+ return bluetooth_socket::RECEIVE_ERROR_DISCONNECTED;
+ case BluetoothApiSocket::kNotConnected:
+ // kNotConnected is impossible since a socket has to be connected to be
+ // able to call Receive() on it.
+ // fallthrough
+ case BluetoothApiSocket::kIOPending:
+ // kIOPending is not relevant to apps, as BluetoothSocketEventDispatcher
+ // handles this specific error.
+ // fallthrough
+ default:
+ return bluetooth_socket::RECEIVE_ERROR_SYSTEM_ERROR;
+ }
+}
+
+bluetooth_socket::AcceptError MapAcceptErrorReason(
+ BluetoothApiSocket::ErrorReason value) {
+ // TODO(keybuk): All values are system error, we may want to seperate these
+ // out to more discrete reasons.
+ switch (value) {
+ case BluetoothApiSocket::kNotListening:
+ // kNotListening is impossible since a socket has to be listening to be
+ // able to call Accept() on it.
+ // fallthrough
+ default:
+ return bluetooth_socket::ACCEPT_ERROR_SYSTEM_ERROR;
+ }
+}
+
+} // namespace
+
+namespace extensions {
+namespace api {
+
+using content::BrowserThread;
+
+static base::LazyInstance<
+ BrowserContextKeyedAPIFactory<BluetoothSocketEventDispatcher> > g_factory =
+ LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<BluetoothSocketEventDispatcher>*
+BluetoothSocketEventDispatcher::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+// static
+BluetoothSocketEventDispatcher* BluetoothSocketEventDispatcher::Get(
+ content::BrowserContext* context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ return BrowserContextKeyedAPIFactory<BluetoothSocketEventDispatcher>::Get(
+ context);
+}
+
+BluetoothSocketEventDispatcher::BluetoothSocketEventDispatcher(
+ content::BrowserContext* context)
+ : thread_id_(BluetoothApiSocket::kThreadId),
+ browser_context_(context) {
+ ApiResourceManager<BluetoothApiSocket>* manager =
+ ApiResourceManager<BluetoothApiSocket>::Get(browser_context_);
+ DCHECK(manager)
+ << "There is no socket manager. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "ApiResourceManager<BluetoothApiSocket>.";
+ sockets_ = manager->data_;
+}
+
+BluetoothSocketEventDispatcher::~BluetoothSocketEventDispatcher() {}
+
+BluetoothSocketEventDispatcher::SocketParams::SocketParams() {}
+
+BluetoothSocketEventDispatcher::SocketParams::SocketParams(
+ const SocketParams& other) = default;
+
+BluetoothSocketEventDispatcher::SocketParams::~SocketParams() {}
+
+void BluetoothSocketEventDispatcher::OnSocketConnect(
+ const std::string& extension_id,
+ int socket_id) {
+ DCHECK_CURRENTLY_ON(thread_id_);
+
+ SocketParams params;
+ params.thread_id = thread_id_;
+ params.browser_context_id = browser_context_;
+ params.extension_id = extension_id;
+ params.sockets = sockets_;
+ params.socket_id = socket_id;
+
+ StartReceive(params);
+}
+
+void BluetoothSocketEventDispatcher::OnSocketListen(
+ const std::string& extension_id,
+ int socket_id) {
+ DCHECK_CURRENTLY_ON(thread_id_);
+
+ SocketParams params;
+ params.thread_id = thread_id_;
+ params.browser_context_id = browser_context_;
+ params.extension_id = extension_id;
+ params.sockets = sockets_;
+ params.socket_id = socket_id;
+
+ StartAccept(params);
+}
+
+void BluetoothSocketEventDispatcher::OnSocketResume(
+ const std::string& extension_id,
+ int socket_id) {
+ DCHECK_CURRENTLY_ON(thread_id_);
+
+ SocketParams params;
+ params.thread_id = thread_id_;
+ params.browser_context_id = browser_context_;
+ params.extension_id = extension_id;
+ params.sockets = sockets_;
+ params.socket_id = socket_id;
+
+ BluetoothApiSocket* socket =
+ params.sockets->Get(params.extension_id, params.socket_id);
+ if (!socket) {
+ // This can happen if the socket is closed while our callback is active.
+ return;
+ }
+
+ if (socket->IsConnected()) {
+ StartReceive(params);
+ } else {
+ StartAccept(params);
+ }
+}
+
+// static
+void BluetoothSocketEventDispatcher::StartReceive(const SocketParams& params) {
+ DCHECK_CURRENTLY_ON(params.thread_id);
+
+ BluetoothApiSocket* socket =
+ params.sockets->Get(params.extension_id, params.socket_id);
+ if (!socket) {
+ // This can happen if the socket is closed while our callback is active.
+ return;
+ }
+ DCHECK(params.extension_id == socket->owner_extension_id())
+ << "Socket has wrong owner.";
+
+ // Don't start another read if the socket has been paused.
+ if (socket->paused())
+ return;
+
+ int buffer_size = socket->buffer_size();
+ if (buffer_size <= 0)
+ buffer_size = kDefaultBufferSize;
+ socket->Receive(
+ buffer_size,
+ base::Bind(
+ &BluetoothSocketEventDispatcher::ReceiveCallback, params),
+ base::Bind(
+ &BluetoothSocketEventDispatcher::ReceiveErrorCallback, params));
+}
+
+// static
+void BluetoothSocketEventDispatcher::ReceiveCallback(
+ const SocketParams& params,
+ int bytes_read,
+ scoped_refptr<net::IOBuffer> io_buffer) {
+ DCHECK_CURRENTLY_ON(params.thread_id);
+
+ // Dispatch "onReceive" event.
+ bluetooth_socket::ReceiveInfo receive_info;
+ receive_info.socket_id = params.socket_id;
+ receive_info.data.assign(io_buffer->data(), io_buffer->data() + bytes_read);
+ scoped_ptr<base::ListValue> args =
+ bluetooth_socket::OnReceive::Create(receive_info);
+ scoped_ptr<Event> event(new Event(events::BLUETOOTH_SOCKET_ON_RECEIVE,
+ bluetooth_socket::OnReceive::kEventName,
+ std::move(args)));
+ PostEvent(params, std::move(event));
+
+ // Post a task to delay the read until the socket is available, as
+ // calling StartReceive at this point would error with ERR_IO_PENDING.
+ BrowserThread::PostTask(
+ params.thread_id,
+ FROM_HERE,
+ base::Bind(&BluetoothSocketEventDispatcher::StartReceive, params));
+}
+
+// static
+void BluetoothSocketEventDispatcher::ReceiveErrorCallback(
+ const SocketParams& params,
+ BluetoothApiSocket::ErrorReason error_reason,
+ const std::string& error) {
+ DCHECK_CURRENTLY_ON(params.thread_id);
+
+ if (error_reason == BluetoothApiSocket::kIOPending) {
+ // This happens when resuming a socket which already had an active "read"
+ // callback. We can safely ignore this error, as the application should not
+ // care.
+ return;
+ }
+
+ // Dispatch "onReceiveError" event but don't start another read to avoid
+ // potential infinite reads if we have a persistent network error.
+ bluetooth_socket::ReceiveErrorInfo receive_error_info;
+ receive_error_info.socket_id = params.socket_id;
+ receive_error_info.error_message = error;
+ receive_error_info.error = MapReceiveErrorReason(error_reason);
+ scoped_ptr<base::ListValue> args =
+ bluetooth_socket::OnReceiveError::Create(receive_error_info);
+ scoped_ptr<Event> event(
+ new Event(events::BLUETOOTH_SOCKET_ON_RECEIVE_ERROR,
+ bluetooth_socket::OnReceiveError::kEventName, std::move(args)));
+ PostEvent(params, std::move(event));
+
+ // Since we got an error, the socket is now "paused" until the application
+ // "resumes" it.
+ BluetoothApiSocket* socket =
+ params.sockets->Get(params.extension_id, params.socket_id);
+ if (socket) {
+ socket->set_paused(true);
+ }
+}
+
+// static
+void BluetoothSocketEventDispatcher::StartAccept(const SocketParams& params) {
+ DCHECK_CURRENTLY_ON(params.thread_id);
+
+ BluetoothApiSocket* socket =
+ params.sockets->Get(params.extension_id, params.socket_id);
+ if (!socket) {
+ // This can happen if the socket is closed while our callback is active.
+ return;
+ }
+ DCHECK(params.extension_id == socket->owner_extension_id())
+ << "Socket has wrong owner.";
+
+ // Don't start another accept if the socket has been paused.
+ if (socket->paused())
+ return;
+
+ socket->Accept(
+ base::Bind(
+ &BluetoothSocketEventDispatcher::AcceptCallback, params),
+ base::Bind(
+ &BluetoothSocketEventDispatcher::AcceptErrorCallback, params));
+}
+
+// static
+void BluetoothSocketEventDispatcher::AcceptCallback(
+ const SocketParams& params,
+ const device::BluetoothDevice* device,
+ scoped_refptr<device::BluetoothSocket> socket) {
+ DCHECK_CURRENTLY_ON(params.thread_id);
+
+ BluetoothApiSocket* server_api_socket =
+ params.sockets->Get(params.extension_id, params.socket_id);
+ DCHECK(server_api_socket);
+
+ BluetoothApiSocket* client_api_socket = new BluetoothApiSocket(
+ params.extension_id,
+ socket,
+ device->GetAddress(),
+ server_api_socket->uuid());
+ int client_socket_id = params.sockets->Add(client_api_socket);
+
+ // Dispatch "onAccept" event.
+ bluetooth_socket::AcceptInfo accept_info;
+ accept_info.socket_id = params.socket_id;
+ accept_info.client_socket_id = client_socket_id;
+ scoped_ptr<base::ListValue> args =
+ bluetooth_socket::OnAccept::Create(accept_info);
+ scoped_ptr<Event> event(new Event(events::BLUETOOTH_SOCKET_ON_ACCEPT,
+ bluetooth_socket::OnAccept::kEventName,
+ std::move(args)));
+ PostEvent(params, std::move(event));
+
+ // Post a task to delay the accept until the socket is available, as
+ // calling StartAccept at this point would error with ERR_IO_PENDING.
+ BrowserThread::PostTask(
+ params.thread_id,
+ FROM_HERE,
+ base::Bind(&BluetoothSocketEventDispatcher::StartAccept, params));
+}
+
+// static
+void BluetoothSocketEventDispatcher::AcceptErrorCallback(
+ const SocketParams& params,
+ BluetoothApiSocket::ErrorReason error_reason,
+ const std::string& error) {
+ DCHECK_CURRENTLY_ON(params.thread_id);
+
+ if (error_reason == BluetoothApiSocket::kIOPending) {
+ // This happens when resuming a socket which already had an active "accept"
+ // callback. We can safely ignore this error, as the application should not
+ // care.
+ return;
+ }
+
+ // Dispatch "onAcceptError" event but don't start another accept to avoid
+ // potential infinite accepts if we have a persistent network error.
+ bluetooth_socket::AcceptErrorInfo accept_error_info;
+ accept_error_info.socket_id = params.socket_id;
+ accept_error_info.error_message = error;
+ accept_error_info.error = MapAcceptErrorReason(error_reason);
+ scoped_ptr<base::ListValue> args =
+ bluetooth_socket::OnAcceptError::Create(accept_error_info);
+ scoped_ptr<Event> event(new Event(events::BLUETOOTH_SOCKET_ON_ACCEPT_ERROR,
+ bluetooth_socket::OnAcceptError::kEventName,
+ std::move(args)));
+ PostEvent(params, std::move(event));
+
+ // Since we got an error, the socket is now "paused" until the application
+ // "resumes" it.
+ BluetoothApiSocket* socket =
+ params.sockets->Get(params.extension_id, params.socket_id);
+ if (socket) {
+ socket->set_paused(true);
+ }
+}
+
+// static
+void BluetoothSocketEventDispatcher::PostEvent(const SocketParams& params,
+ scoped_ptr<Event> event) {
+ DCHECK_CURRENTLY_ON(params.thread_id);
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&DispatchEvent, params.browser_context_id, params.extension_id,
+ base::Passed(std::move(event))));
+}
+
+// static
+void BluetoothSocketEventDispatcher::DispatchEvent(
+ void* browser_context_id,
+ const std::string& extension_id,
+ scoped_ptr<Event> event) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ content::BrowserContext* context =
+ reinterpret_cast<content::BrowserContext*>(browser_context_id);
+ if (!extensions::ExtensionsBrowserClient::Get()->IsValidContext(context))
+ return;
+
+ EventRouter* router = EventRouter::Get(context);
+ if (router)
+ router->DispatchEventToExtension(extension_id, std::move(event));
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.h b/chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.h
new file mode 100644
index 00000000000..5e45fc0c055
--- /dev/null
+++ b/chromium/extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.h
@@ -0,0 +1,120 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_BLUETOOTH_SOCKET_BLUETOOTH_SOCKET_EVENT_DISPATCHER_H_
+#define EXTENSIONS_BROWSER_API_BLUETOOTH_SOCKET_BLUETOOTH_SOCKET_EVENT_DISPATCHER_H_
+
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/browser/api/bluetooth_socket/bluetooth_api_socket.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace device {
+class BluetoothDevice;
+class BluetoothSocket;
+}
+
+namespace extensions {
+struct Event;
+class BluetoothApiSocket;
+}
+
+namespace extensions {
+namespace api {
+
+// Dispatch events related to "bluetooth" sockets from callback on native socket
+// instances. There is one instance per browser context.
+class BluetoothSocketEventDispatcher
+ : public BrowserContextKeyedAPI,
+ public base::SupportsWeakPtr<BluetoothSocketEventDispatcher> {
+ public:
+ explicit BluetoothSocketEventDispatcher(content::BrowserContext* context);
+ ~BluetoothSocketEventDispatcher() override;
+
+ // Socket is active, start receiving data from it.
+ void OnSocketConnect(const std::string& extension_id, int socket_id);
+
+ // Socket is active again, start accepting connections from it.
+ void OnSocketListen(const std::string& extension_id, int socket_id);
+
+ // Socket is active again, start receiving data from it.
+ void OnSocketResume(const std::string& extension_id, int socket_id);
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<BluetoothSocketEventDispatcher>*
+ GetFactoryInstance();
+
+ // Convenience method to get the SocketEventDispatcher for a profile.
+ static BluetoothSocketEventDispatcher* Get(content::BrowserContext* context);
+
+ private:
+ typedef ApiResourceManager<BluetoothApiSocket>::ApiResourceData SocketData;
+ friend class BrowserContextKeyedAPIFactory<BluetoothSocketEventDispatcher>;
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "BluetoothSocketEventDispatcher"; }
+ static const bool kServiceHasOwnInstanceInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ // base::Bind supports methods with up to 6 parameters. SocketParams is used
+ // as a workaround that limitation for invoking StartReceive() and
+ // StartAccept().
+ struct SocketParams {
+ SocketParams();
+ SocketParams(const SocketParams& other);
+ ~SocketParams();
+
+ content::BrowserThread::ID thread_id;
+ void* browser_context_id;
+ std::string extension_id;
+ scoped_refptr<SocketData> sockets;
+ int socket_id;
+ };
+
+ // Start a receive and register a callback.
+ static void StartReceive(const SocketParams& params);
+
+ // Called when socket receive data.
+ static void ReceiveCallback(const SocketParams& params,
+ int bytes_read,
+ scoped_refptr<net::IOBuffer> io_buffer);
+
+ // Called when socket receive data.
+ static void ReceiveErrorCallback(const SocketParams& params,
+ BluetoothApiSocket::ErrorReason error_reason,
+ const std::string& error);
+
+ // Start an accept and register a callback.
+ static void StartAccept(const SocketParams& params);
+
+ // Called when socket accepts a client connection.
+ static void AcceptCallback(const SocketParams& params,
+ const device::BluetoothDevice* device,
+ scoped_refptr<device::BluetoothSocket> socket);
+
+ // Called when socket encounters an error while accepting a client connection.
+ static void AcceptErrorCallback(const SocketParams& params,
+ BluetoothApiSocket::ErrorReason error_reason,
+ const std::string& error);
+
+ // Post an extension event from IO to UI thread
+ static void PostEvent(const SocketParams& params, scoped_ptr<Event> event);
+
+ // Dispatch an extension event on to EventRouter instance on UI thread.
+ static void DispatchEvent(void* browser_context_id,
+ const std::string& extension_id,
+ scoped_ptr<Event> event);
+
+ // Usually FILE thread (except for unit testing).
+ content::BrowserThread::ID thread_id_;
+ content::BrowserContext* const browser_context_;
+ scoped_refptr<SocketData> sockets_;
+};
+
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_BLUETOOTH_SOCKET_BLUETOOTH_SOCKET_EVENT_DISPATCHER_H_
diff --git a/chromium/extensions/browser/api/cast_channel/DEPS b/chromium/extensions/browser/api/cast_channel/DEPS
new file mode 100644
index 00000000000..c6e9550543b
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+third_party/zlib",
+]
diff --git a/chromium/extensions/browser/api/cast_channel/OWNERS b/chromium/extensions/browser/api/cast_channel/OWNERS
new file mode 100644
index 00000000000..94f54644c75
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/OWNERS
@@ -0,0 +1,3 @@
+mfoltz@chromium.org
+kmarshall@chromium.org
+wez@chromium.org
diff --git a/chromium/extensions/browser/api/cast_channel/cast_auth_util.cc b/chromium/extensions/browser/api/cast_channel/cast_auth_util.cc
new file mode 100644
index 00000000000..afc433ea71b
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_auth_util.cc
@@ -0,0 +1,185 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/cast_channel/cast_auth_util.h"
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "extensions/browser/api/cast_channel/cast_message_util.h"
+#include "extensions/common/api/cast_channel/cast_channel.pb.h"
+#include "extensions/common/cast/cast_cert_validator.h"
+#include "net/cert/x509_certificate.h"
+#include "net/der/parse_values.h"
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+namespace {
+
+const char* const kParseErrorPrefix = "Failed to parse auth message: ";
+
+// The maximum number of days a cert can live for.
+const int kMaxSelfSignedCertLifetimeInDays = 4;
+
+namespace cast_crypto = ::extensions::api::cast_crypto;
+
+// Extracts an embedded DeviceAuthMessage payload from an auth challenge reply
+// message.
+AuthResult ParseAuthMessage(const CastMessage& challenge_reply,
+ DeviceAuthMessage* auth_message) {
+ if (challenge_reply.payload_type() != CastMessage_PayloadType_BINARY) {
+ return AuthResult::CreateWithParseError(
+ "Wrong payload type in challenge reply",
+ AuthResult::ERROR_WRONG_PAYLOAD_TYPE);
+ }
+ if (!challenge_reply.has_payload_binary()) {
+ return AuthResult::CreateWithParseError(
+ "Payload type is binary but payload_binary field not set",
+ AuthResult::ERROR_NO_PAYLOAD);
+ }
+ if (!auth_message->ParseFromString(challenge_reply.payload_binary())) {
+ return AuthResult::CreateWithParseError(
+ "Cannot parse binary payload into DeviceAuthMessage",
+ AuthResult::ERROR_PAYLOAD_PARSING_FAILED);
+ }
+
+ VLOG(1) << "Auth message: " << AuthMessageToString(*auth_message);
+
+ if (auth_message->has_error()) {
+ return AuthResult::CreateWithParseError(
+ "Auth message error: " +
+ base::IntToString(auth_message->error().error_type()),
+ AuthResult::ERROR_MESSAGE_ERROR);
+ }
+ if (!auth_message->has_response()) {
+ return AuthResult::CreateWithParseError(
+ "Auth message has no response field", AuthResult::ERROR_NO_RESPONSE);
+ }
+ return AuthResult();
+}
+
+} // namespace
+
+AuthResult::AuthResult()
+ : error_type(ERROR_NONE), channel_policies(POLICY_NONE) {}
+
+AuthResult::AuthResult(const std::string& error_message, ErrorType error_type)
+ : error_message(error_message), error_type(error_type) {}
+
+AuthResult::~AuthResult() {
+}
+
+// static
+AuthResult AuthResult::CreateWithParseError(const std::string& error_message,
+ ErrorType error_type) {
+ return AuthResult(kParseErrorPrefix + error_message, error_type);
+}
+
+AuthResult AuthenticateChallengeReply(const CastMessage& challenge_reply,
+ const net::X509Certificate& peer_cert) {
+ DeviceAuthMessage auth_message;
+ AuthResult result = ParseAuthMessage(challenge_reply, &auth_message);
+ if (!result.success()) {
+ return result;
+ }
+
+ // Get the DER-encoded form of the certificate.
+ std::string peer_cert_der;
+ if (!net::X509Certificate::GetDEREncoded(peer_cert.os_cert_handle(),
+ &peer_cert_der) ||
+ peer_cert_der.empty()) {
+ return AuthResult::CreateWithParseError(
+ "Could not create DER-encoded peer cert.",
+ AuthResult::ERROR_CERT_PARSING_FAILED);
+ }
+
+ // Ensure the peer cert is valid and doesn't have an excessive remaining
+ // lifetime. Although it is not verified as an X.509 certificate, the entire
+ // structure is signed by the AuthResponse, so the validity field from X.509
+ // is repurposed as this signature's expiration.
+ base::Time expiry = peer_cert.valid_expiry();
+ base::Time lifetime_limit =
+ base::Time::Now() +
+ base::TimeDelta::FromDays(kMaxSelfSignedCertLifetimeInDays);
+ if (peer_cert.valid_start().is_null() ||
+ peer_cert.valid_start() > base::Time::Now()) {
+ return AuthResult::CreateWithParseError(
+ "Certificate's valid start date is in the future.",
+ AuthResult::ERROR_VALID_START_DATE_IN_FUTURE);
+ }
+ if (expiry.is_null() || peer_cert.HasExpired()) {
+ return AuthResult::CreateWithParseError("Certificate has expired.",
+ AuthResult::ERROR_CERT_EXPIRED);
+ }
+ if (expiry > lifetime_limit) {
+ return AuthResult::CreateWithParseError(
+ "Peer cert lifetime is too long.",
+ AuthResult::ERROR_VALIDITY_PERIOD_TOO_LONG);
+ }
+
+ const AuthResponse& response = auth_message.response();
+ return VerifyCredentials(response, peer_cert_der);
+}
+
+// This function does the following
+//
+// * Verifies that the certificate chain |response.client_auth_certificate| +
+// |response.intermediate_certificate| is valid and chains to a trusted
+// Cast root.
+//
+// * Verifies that |response.signature| matches the signature
+// of |signature_input| by |response.client_auth_certificate|'s public
+// key.
+AuthResult VerifyCredentials(const AuthResponse& response,
+ const std::string& signature_input) {
+ // Verify the certificate
+ scoped_ptr<cast_crypto::CertVerificationContext> verification_context;
+
+ // Build a single vector containing the certificate chain.
+ std::vector<std::string> cert_chain;
+ cert_chain.push_back(response.client_auth_certificate());
+ cert_chain.insert(cert_chain.end(),
+ response.intermediate_certificate().begin(),
+ response.intermediate_certificate().end());
+
+ // Use the current time when checking certificate validity.
+ base::Time::Exploded now;
+ base::Time::Now().UTCExplode(&now);
+
+ cast_crypto::CastDeviceCertPolicy device_policy;
+ if (!cast_crypto::VerifyDeviceCert(cert_chain, now, &verification_context,
+ &device_policy)) {
+ // TODO(eroman): The error information was lost; this error is ambiguous.
+ return AuthResult("Failed verifying cast device certificate",
+ AuthResult::ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA);
+ }
+
+ if (!verification_context->VerifySignatureOverData(response.signature(),
+ signature_input)) {
+ return AuthResult("Failed verifying signature over data",
+ AuthResult::ERROR_SIGNED_BLOBS_MISMATCH);
+ }
+
+ AuthResult success;
+
+ // Set the policy into the result.
+ switch (device_policy) {
+ case cast_crypto::CastDeviceCertPolicy::AUDIO_ONLY:
+ success.channel_policies = AuthResult::POLICY_AUDIO_ONLY;
+ break;
+ case cast_crypto::CastDeviceCertPolicy::NONE:
+ success.channel_policies = AuthResult::POLICY_NONE;
+ break;
+ }
+
+ return success;
+}
+
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/cast_channel/cast_auth_util.h b/chromium/extensions/browser/api/cast_channel/cast_auth_util.h
new file mode 100644
index 00000000000..61f10079228
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_auth_util.h
@@ -0,0 +1,80 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_AUTH_UTIL_H_
+#define EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_AUTH_UTIL_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+
+namespace net {
+class X509Certificate;
+} // namespace net
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+
+class AuthResponse;
+class CastMessage;
+class DeviceAuthMessage;
+
+struct AuthResult {
+ public:
+ enum ErrorType {
+ ERROR_NONE,
+ ERROR_PEER_CERT_EMPTY,
+ ERROR_WRONG_PAYLOAD_TYPE,
+ ERROR_NO_PAYLOAD,
+ ERROR_PAYLOAD_PARSING_FAILED,
+ ERROR_MESSAGE_ERROR,
+ ERROR_NO_RESPONSE,
+ ERROR_FINGERPRINT_NOT_FOUND,
+ ERROR_CERT_PARSING_FAILED,
+ ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA,
+ ERROR_CANNOT_EXTRACT_PUBLIC_KEY,
+ ERROR_SIGNED_BLOBS_MISMATCH,
+ ERROR_UNEXPECTED_AUTH_LIBRARY_RESULT,
+ ERROR_VALIDITY_PERIOD_TOO_LONG,
+ ERROR_VALID_START_DATE_IN_FUTURE,
+ ERROR_CERT_EXPIRED,
+ };
+
+ enum PolicyType { POLICY_NONE = 0, POLICY_AUDIO_ONLY = 1 << 0 };
+
+ // Constructs a AuthResult that corresponds to success.
+ AuthResult();
+
+ AuthResult(const std::string& error_message, ErrorType error_type);
+
+ ~AuthResult();
+
+ static AuthResult CreateWithParseError(const std::string& error_message,
+ ErrorType error_type);
+
+ bool success() const { return error_type == ERROR_NONE; }
+
+ std::string error_message;
+ ErrorType error_type;
+ unsigned int channel_policies;
+};
+
+// Authenticates the given |challenge_reply|:
+// 1. Signature contained in the reply is valid.
+// 2. Certficate used to sign is rooted to a trusted CA.
+AuthResult AuthenticateChallengeReply(const CastMessage& challenge_reply,
+ const net::X509Certificate& peer_cert);
+
+// Auth-library specific implementation of cryptographic signature
+// verification routines. Verifies that |response| contains a
+// valid signature of |signature_input|.
+AuthResult VerifyCredentials(const AuthResponse& response,
+ const std::string& signature_input);
+
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_AUTH_UTIL_H_
diff --git a/chromium/extensions/browser/api/cast_channel/cast_auth_util_unittest.cc b/chromium/extensions/browser/api/cast_channel/cast_auth_util_unittest.cc
new file mode 100644
index 00000000000..c474613c945
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_auth_util_unittest.cc
@@ -0,0 +1,191 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/cast_channel/cast_auth_util.h"
+
+#include <string>
+
+#include "base/files/file_util.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "extensions/common/api/cast_channel/cast_channel.pb.h"
+#include "extensions/common/extension_paths.h"
+#include "net/cert/pem_tokenizer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+namespace {
+
+// Creates an std::string given a uint8_t array.
+template <size_t N>
+std::string CreateString(const uint8_t (&data)[N]) {
+ return std::string(reinterpret_cast<const char*>(data), N);
+}
+
+// Reads a file from the cast certificates test data directory:
+// src/extensions/test/data/cast_certificates/
+std::string ReadTestFileToString(const std::string& file_name) {
+ base::FilePath filepath;
+ if (!PathService::Get(DIR_TEST_DATA, &filepath)) {
+ ADD_FAILURE() << "Couldn't retrieve test data root";
+ return std::string();
+ }
+ filepath = filepath.AppendASCII("cast_certificates/" + file_name);
+
+ // Read the full contents of the file.
+ std::string file_data;
+ if (!base::ReadFileToString(filepath, &file_data)) {
+ ADD_FAILURE() << "Couldn't read file: " << filepath.value();
+ return std::string();
+ }
+
+ return file_data;
+}
+
+// Reads a PEM file containing "CERTIFICATE" blocks to a vector of certificate
+// data.
+std::vector<std::string> ReadCertificateChainFromFile(
+ const std::string& file_name) {
+ std::string file_data = ReadTestFileToString(file_name);
+
+ // Read the certificate chain from the test file, which is comprised of PEM
+ // blocks titled "CERTIFICATE".
+ std::vector<std::string> pem_headers;
+ pem_headers.push_back("CERTIFICATE");
+
+ std::vector<std::string> certs;
+ net::PEMTokenizer pem_tokenizer(file_data, pem_headers);
+ while (pem_tokenizer.GetNext())
+ certs.push_back(pem_tokenizer.data());
+
+ return certs;
+}
+
+static const unsigned char kSignedData[] = {
+ 0x5f, 0x76, 0x0d, 0xc8, 0x4b, 0xe7, 0x6e, 0xcb, 0x31, 0x58, 0xca, 0xd3,
+ 0x7d, 0x23, 0x55, 0xbe, 0x8d, 0x52, 0x87, 0x83, 0x27, 0x52, 0x78, 0xfa,
+ 0xa6, 0xdd, 0xdf, 0x13, 0x00, 0x51, 0x57, 0x6a, 0x83, 0x15, 0xcc, 0xc5,
+ 0xb2, 0x5c, 0xdf, 0xe6, 0x81, 0xdc, 0x13, 0x58, 0x7b, 0x94, 0x0f, 0x69,
+ 0xcc, 0xdf, 0x68, 0x41, 0x8a, 0x95, 0xe2, 0xcd, 0xf8, 0xde, 0x0f, 0x2f,
+ 0x30, 0xcf, 0x73, 0xbf, 0x37, 0x52, 0x87, 0x23, 0xd7, 0xbe, 0xba, 0x7c,
+ 0xde, 0x50, 0xd3, 0x77, 0x9c, 0x06, 0x82, 0x28, 0x67, 0xc1, 0x1a, 0xf5,
+ 0x8a, 0xa0, 0xf2, 0x32, 0x09, 0x95, 0x41, 0x41, 0x93, 0x8e, 0x62, 0xaa,
+ 0xf3, 0xe3, 0x22, 0x17, 0x43, 0x94, 0x9b, 0x63, 0xfa, 0x68, 0x20, 0x69,
+ 0x38, 0xf6, 0x75, 0x6c, 0xe0, 0x3b, 0xe0, 0x8d, 0x63, 0xac, 0x7f, 0xe3,
+ 0x09, 0xd8, 0xde, 0x91, 0xc8, 0x1e, 0x07, 0x4a, 0xb2, 0x1e, 0xe1, 0xe3,
+ 0xf4, 0x4d, 0x3e, 0x8a, 0xf4, 0xf8, 0x83, 0x39, 0x2b, 0x50, 0x98, 0x61,
+ 0x91, 0x50, 0x00, 0x34, 0x57, 0xd2, 0x0d, 0xf7, 0xfa, 0xc9, 0xcc, 0xd9,
+ 0x7a, 0x3d, 0x39, 0x7a, 0x1a, 0xbd, 0xf8, 0xbe, 0x65, 0xb6, 0xea, 0x4e,
+ 0x86, 0x74, 0xdd, 0x51, 0x74, 0x6e, 0xa6, 0x7f, 0x14, 0x6c, 0x6a, 0x46,
+ 0xb8, 0xaf, 0xcd, 0x6c, 0x78, 0x43, 0x76, 0x47, 0x5b, 0xdc, 0xb6, 0xf6,
+ 0x4d, 0x1b, 0xe0, 0xb5, 0xf9, 0xa2, 0xb8, 0x26, 0x3f, 0x3f, 0xb8, 0x80,
+ 0xed, 0xce, 0xfd, 0x0e, 0xcb, 0x48, 0x7a, 0x3b, 0xdf, 0x92, 0x44, 0x04,
+ 0x81, 0xe4, 0xd3, 0x1e, 0x07, 0x9b, 0x02, 0xae, 0x05, 0x5a, 0x11, 0xf2,
+ 0xc2, 0x75, 0x85, 0xd5, 0xf1, 0x53, 0x4c, 0x09, 0xd0, 0x99, 0xf8, 0x3e,
+ 0xf6, 0x24, 0x46, 0xae, 0x83, 0x35, 0x3e, 0x6c, 0x8c, 0x2a, 0x9f, 0x1c,
+ 0x5b, 0xfb, 0x89, 0x56};
+
+static const unsigned char kSha1Signature[] = {
+ 0x52, 0x56, 0xcd, 0x53, 0xfa, 0xd9, 0x44, 0x31, 0x00, 0x2e, 0x85, 0x18,
+ 0x56, 0xae, 0xf9, 0xf2, 0x70, 0x16, 0xc9, 0x59, 0x53, 0xc0, 0x17, 0xd9,
+ 0x09, 0x65, 0x75, 0xee, 0xba, 0xc8, 0x0d, 0x06, 0x2e, 0xb7, 0x1b, 0xd0,
+ 0x6a, 0x4d, 0x58, 0xde, 0x8e, 0xbe, 0x92, 0x22, 0x53, 0x19, 0xbf, 0x74,
+ 0x8f, 0xb8, 0xfc, 0x3c, 0x9b, 0x42, 0x14, 0x7d, 0xe1, 0xfc, 0xa3, 0x71,
+ 0x91, 0x6c, 0x5d, 0x28, 0x69, 0x8d, 0xd2, 0xde, 0xd1, 0x8f, 0xac, 0x6d,
+ 0xf6, 0x48, 0xd8, 0x6f, 0x0e, 0xc9, 0x0a, 0xfa, 0xde, 0x20, 0xe0, 0x9d,
+ 0x7a, 0xf8, 0x30, 0xa8, 0xd4, 0x79, 0x15, 0x63, 0xfb, 0x97, 0xa9, 0xef,
+ 0x9f, 0x9c, 0xac, 0x16, 0xba, 0x1b, 0x2c, 0x14, 0xb4, 0xa4, 0x54, 0x5e,
+ 0xec, 0x04, 0x10, 0x84, 0xc2, 0xa0, 0xd9, 0x6f, 0x05, 0xd4, 0x09, 0x8c,
+ 0x85, 0xe9, 0x7a, 0xd1, 0x5a, 0xa3, 0x70, 0x00, 0x30, 0x9b, 0x19, 0x44,
+ 0x2a, 0x90, 0x7a, 0xcd, 0x91, 0x94, 0x90, 0x66, 0xf9, 0x2e, 0x5e, 0x43,
+ 0x27, 0x33, 0x2c, 0x45, 0xa7, 0xe2, 0x3a, 0x6d, 0xc9, 0x44, 0x58, 0x39,
+ 0x45, 0xcb, 0xbd, 0x2f, 0xc5, 0xb4, 0x08, 0x41, 0x4d, 0x45, 0x67, 0x55,
+ 0x0d, 0x43, 0x3c, 0xb6, 0x81, 0xbb, 0xb4, 0x34, 0x07, 0x10, 0x28, 0x17,
+ 0xc2, 0xad, 0x40, 0x3b, 0xaf, 0xcb, 0xc0, 0xf6, 0x9d, 0x0e, 0x9b, 0xca,
+ 0x2b, 0x20, 0xdf, 0xd0, 0xa3, 0xbe, 0xea, 0x3e, 0xe0, 0x82, 0x7b, 0x93,
+ 0xfd, 0x9c, 0xaf, 0x97, 0x00, 0x05, 0x44, 0x91, 0x73, 0x68, 0x92, 0x3a,
+ 0x8b, 0xbc, 0x0e, 0x96, 0x5e, 0x92, 0x98, 0x70, 0xab, 0xaa, 0x6e, 0x9a,
+ 0x8e, 0xb0, 0xf4, 0x92, 0xc5, 0xa0, 0xa0, 0x4b, 0xb3, 0xd5, 0x44, 0x99,
+ 0x8e, 0xa1, 0xd1, 0x8f, 0xe3, 0xac, 0x71, 0x1e, 0x3f, 0xc2, 0xfd, 0x0a,
+ 0x57, 0xed, 0xea, 0x04};
+
+class CastAuthUtilTest : public testing::Test {
+ public:
+ CastAuthUtilTest() {}
+ ~CastAuthUtilTest() override {}
+
+ void SetUp() override {}
+
+ protected:
+ static AuthResponse CreateAuthResponse(std::string* signed_data) {
+ auto chain = ReadCertificateChainFromFile("audio_ref_dev_test_chain_3.pem");
+ CHECK(!chain.empty());
+
+ AuthResponse response;
+
+ response.set_client_auth_certificate(chain[0]);
+ for (size_t i = 1; i < chain.size(); ++i)
+ response.add_intermediate_certificate(chain[i]);
+
+ response.set_signature(CreateString(kSha1Signature));
+ *signed_data = CreateString(kSignedData);
+
+ return response;
+ }
+
+ // Mangles a string by inverting the first byte.
+ static void MangleString(std::string* str) { (*str)[0] = ~(*str)[0]; }
+};
+
+TEST_F(CastAuthUtilTest, VerifySuccess) {
+ std::string signed_data;
+ AuthResponse auth_response = CreateAuthResponse(&signed_data);
+ AuthResult result = VerifyCredentials(auth_response, signed_data);
+ EXPECT_TRUE(result.success());
+ EXPECT_EQ(AuthResult::POLICY_AUDIO_ONLY, result.channel_policies);
+}
+
+TEST_F(CastAuthUtilTest, VerifyBadCA) {
+ std::string signed_data;
+ AuthResponse auth_response = CreateAuthResponse(&signed_data);
+ MangleString(auth_response.mutable_intermediate_certificate(0));
+ AuthResult result = VerifyCredentials(auth_response, signed_data);
+ EXPECT_FALSE(result.success());
+ EXPECT_EQ(AuthResult::ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA, result.error_type);
+}
+
+TEST_F(CastAuthUtilTest, VerifyBadClientAuthCert) {
+ std::string signed_data;
+ AuthResponse auth_response = CreateAuthResponse(&signed_data);
+ MangleString(auth_response.mutable_client_auth_certificate());
+ AuthResult result = VerifyCredentials(auth_response, signed_data);
+ EXPECT_FALSE(result.success());
+ // TODO(eroman): Not quite right of an error.
+ EXPECT_EQ(AuthResult::ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA, result.error_type);
+}
+
+TEST_F(CastAuthUtilTest, VerifyBadSignature) {
+ std::string signed_data;
+ AuthResponse auth_response = CreateAuthResponse(&signed_data);
+ MangleString(auth_response.mutable_signature());
+ AuthResult result = VerifyCredentials(auth_response, signed_data);
+ EXPECT_FALSE(result.success());
+ EXPECT_EQ(AuthResult::ERROR_SIGNED_BLOBS_MISMATCH, result.error_type);
+}
+
+TEST_F(CastAuthUtilTest, VerifyBadPeerCert) {
+ std::string signed_data;
+ AuthResponse auth_response = CreateAuthResponse(&signed_data);
+ MangleString(&signed_data);
+ AuthResult result = VerifyCredentials(auth_response, signed_data);
+ EXPECT_FALSE(result.success());
+ EXPECT_EQ(AuthResult::ERROR_SIGNED_BLOBS_MISMATCH, result.error_type);
+}
+
+} // namespace
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/cast_channel/cast_channel_api.cc b/chromium/extensions/browser/api/cast_channel/cast_channel_api.cc
new file mode 100644
index 00000000000..2c57b392cd2
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_channel_api.cc
@@ -0,0 +1,563 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/cast_channel/cast_channel_api.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+#include <string>
+#include <utility>
+
+#include "base/json/json_writer.h"
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/default_clock.h"
+#include "base/values.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/api/cast_channel/cast_message_util.h"
+#include "extensions/browser/api/cast_channel/cast_socket.h"
+#include "extensions/browser/api/cast_channel/keep_alive_delegate.h"
+#include "extensions/browser/api/cast_channel/logger.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/common/api/cast_channel/cast_channel.pb.h"
+#include "extensions/common/api/cast_channel/logging.pb.h"
+#include "net/base/ip_address.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+
+// Default timeout interval for connection setup.
+// Used if not otherwise specified at ConnectInfo::timeout.
+const int kDefaultConnectTimeoutMillis = 5000; // 5 seconds.
+
+namespace extensions {
+
+namespace Close = cast_channel::Close;
+namespace OnError = cast_channel::OnError;
+namespace OnMessage = cast_channel::OnMessage;
+namespace Open = cast_channel::Open;
+namespace Send = cast_channel::Send;
+using cast_channel::CastDeviceCapability;
+using cast_channel::CastMessage;
+using cast_channel::CastSocket;
+using cast_channel::ChannelAuthType;
+using cast_channel::ChannelError;
+using cast_channel::ChannelInfo;
+using cast_channel::ConnectInfo;
+using cast_channel::ErrorInfo;
+using cast_channel::LastErrors;
+using cast_channel::Logger;
+using cast_channel::MessageInfo;
+using cast_channel::ReadyState;
+using content::BrowserThread;
+
+namespace {
+
+// T is an extension dictionary (MessageInfo or ChannelInfo)
+template <class T>
+std::string ParamToString(const T& info) {
+ scoped_ptr<base::DictionaryValue> dict = info.ToValue();
+ std::string out;
+ base::JSONWriter::Write(*dict, &out);
+ return out;
+}
+
+// Fills |channel_info| from the destination and state of |socket|.
+void FillChannelInfo(const CastSocket& socket, ChannelInfo* channel_info) {
+ DCHECK(channel_info);
+ channel_info->channel_id = socket.id();
+ const net::IPEndPoint& ip_endpoint = socket.ip_endpoint();
+ channel_info->connect_info.ip_address = ip_endpoint.ToStringWithoutPort();
+ channel_info->connect_info.port = ip_endpoint.port();
+ channel_info->connect_info.auth = socket.channel_auth();
+ channel_info->ready_state = socket.ready_state();
+ channel_info->error_state = socket.error_state();
+ channel_info->keep_alive = socket.keep_alive();
+ channel_info->audio_only = socket.audio_only();
+}
+
+// Fills |error_info| from |error_state| and |last_errors|.
+void FillErrorInfo(ChannelError error_state,
+ const LastErrors& last_errors,
+ ErrorInfo* error_info) {
+ error_info->error_state = error_state;
+ if (last_errors.event_type != cast_channel::proto::EVENT_TYPE_UNKNOWN)
+ error_info->event_type.reset(new int(last_errors.event_type));
+ if (last_errors.challenge_reply_error_type !=
+ cast_channel::proto::CHALLENGE_REPLY_ERROR_NONE) {
+ error_info->challenge_reply_error_type.reset(
+ new int(last_errors.challenge_reply_error_type));
+ }
+ if (last_errors.net_return_value <= 0)
+ error_info->net_return_value.reset(new int(last_errors.net_return_value));
+}
+
+bool IsValidConnectInfoPort(const ConnectInfo& connect_info) {
+ return connect_info.port > 0 && connect_info.port <
+ std::numeric_limits<uint16_t>::max();
+}
+
+bool IsValidConnectInfoIpAddress(const ConnectInfo& connect_info) {
+ net::IPAddress ip_address;
+ return ip_address.AssignFromIPLiteral(connect_info.ip_address);
+}
+
+} // namespace
+
+CastChannelAPI::CastChannelAPI(content::BrowserContext* context)
+ : browser_context_(context),
+ logger_(new Logger(make_scoped_ptr<base::Clock>(new base::DefaultClock),
+ base::Time::UnixEpoch())) {
+ DCHECK(browser_context_);
+}
+
+// static
+CastChannelAPI* CastChannelAPI::Get(content::BrowserContext* context) {
+ return BrowserContextKeyedAPIFactory<CastChannelAPI>::Get(context);
+}
+
+scoped_refptr<Logger> CastChannelAPI::GetLogger() {
+ return logger_;
+}
+
+void CastChannelAPI::SendEvent(const std::string& extension_id,
+ scoped_ptr<Event> event) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ EventRouter* event_router = EventRouter::Get(GetBrowserContext());
+ if (event_router) {
+ event_router->DispatchEventToExtension(extension_id, std::move(event));
+ }
+}
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<CastChannelAPI> >
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<CastChannelAPI>*
+CastChannelAPI::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+void CastChannelAPI::SetSocketForTest(scoped_ptr<CastSocket> socket_for_test) {
+ socket_for_test_ = std::move(socket_for_test);
+}
+
+scoped_ptr<CastSocket> CastChannelAPI::GetSocketForTest() {
+ return std::move(socket_for_test_);
+}
+
+content::BrowserContext* CastChannelAPI::GetBrowserContext() const {
+ return browser_context_;
+}
+
+void CastChannelAPI::SetPingTimeoutTimerForTest(scoped_ptr<base::Timer> timer) {
+ injected_timeout_timer_ = std::move(timer);
+}
+
+scoped_ptr<base::Timer> CastChannelAPI::GetInjectedTimeoutTimerForTest() {
+ return std::move(injected_timeout_timer_);
+}
+
+CastChannelAPI::~CastChannelAPI() {}
+
+CastChannelAsyncApiFunction::CastChannelAsyncApiFunction() {
+}
+
+CastChannelAsyncApiFunction::~CastChannelAsyncApiFunction() { }
+
+bool CastChannelAsyncApiFunction::PrePrepare() {
+ DCHECK(ApiResourceManager<CastSocket>::Get(browser_context()));
+ sockets_ = ApiResourceManager<CastSocket>::Get(browser_context())->data_;
+ DCHECK(sockets_);
+ return true;
+}
+
+bool CastChannelAsyncApiFunction::Respond() {
+ return GetError().empty();
+}
+
+CastSocket* CastChannelAsyncApiFunction::GetSocketOrCompleteWithError(
+ int channel_id) {
+ CastSocket* socket = GetSocket(channel_id);
+ if (!socket) {
+ SetResultFromError(channel_id,
+ cast_channel::CHANNEL_ERROR_INVALID_CHANNEL_ID);
+ AsyncWorkCompleted();
+ }
+ return socket;
+}
+
+int CastChannelAsyncApiFunction::AddSocket(CastSocket* socket) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ DCHECK(socket);
+ const int id = sockets_->Add(socket);
+ socket->set_id(id);
+ return id;
+}
+
+void CastChannelAsyncApiFunction::RemoveSocket(int channel_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ sockets_->Remove(extension_->id(), channel_id);
+}
+
+void CastChannelAsyncApiFunction::SetResultFromSocket(
+ const CastSocket& socket) {
+ ChannelInfo channel_info;
+ FillChannelInfo(socket, &channel_info);
+ ChannelError error = socket.error_state();
+ if (error != cast_channel::CHANNEL_ERROR_NONE) {
+ SetError("Channel socket error = " + base::IntToString(error));
+ }
+ SetResultFromChannelInfo(channel_info);
+}
+
+void CastChannelAsyncApiFunction::SetResultFromError(int channel_id,
+ ChannelError error) {
+ ChannelInfo channel_info;
+ channel_info.channel_id = channel_id;
+ channel_info.ready_state = cast_channel::READY_STATE_CLOSED;
+ channel_info.error_state = error;
+ channel_info.connect_info.ip_address = "";
+ channel_info.connect_info.port = 0;
+ channel_info.connect_info.auth = cast_channel::CHANNEL_AUTH_TYPE_SSL;
+ SetResultFromChannelInfo(channel_info);
+ SetError("Channel error = " + base::IntToString(error));
+}
+
+CastSocket* CastChannelAsyncApiFunction::GetSocket(int channel_id) const {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ return sockets_->Get(extension_->id(), channel_id);
+}
+
+void CastChannelAsyncApiFunction::SetResultFromChannelInfo(
+ const ChannelInfo& channel_info) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ SetResult(channel_info.ToValue().release());
+}
+
+CastChannelOpenFunction::CastChannelOpenFunction()
+ : new_channel_id_(0) {
+}
+
+CastChannelOpenFunction::~CastChannelOpenFunction() { }
+
+net::IPEndPoint* CastChannelOpenFunction::ParseConnectInfo(
+ const ConnectInfo& connect_info) {
+ net::IPAddress ip_address;
+ CHECK(ip_address.AssignFromIPLiteral(connect_info.ip_address));
+ return new net::IPEndPoint(ip_address,
+ static_cast<uint16_t>(connect_info.port));
+}
+
+bool CastChannelOpenFunction::PrePrepare() {
+ api_ = CastChannelAPI::Get(browser_context());
+ return CastChannelAsyncApiFunction::PrePrepare();
+}
+
+bool CastChannelOpenFunction::Prepare() {
+ params_ = Open::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ const ConnectInfo& connect_info = params_->connect_info;
+ if (!IsValidConnectInfoPort(connect_info)) {
+ SetError("Invalid connect_info (invalid port)");
+ } else if (!IsValidConnectInfoIpAddress(connect_info)) {
+ SetError("Invalid connect_info (invalid IP address)");
+ } else {
+ // Parse timeout parameters if they are set.
+ if (connect_info.liveness_timeout.get()) {
+ liveness_timeout_ = base::TimeDelta::FromMilliseconds(
+ *connect_info.liveness_timeout.get());
+ }
+ if (connect_info.ping_interval.get()) {
+ ping_interval_ =
+ base::TimeDelta::FromMilliseconds(*connect_info.ping_interval.get());
+ }
+
+ // Validate timeout parameters.
+ if (liveness_timeout_ < base::TimeDelta() ||
+ ping_interval_ < base::TimeDelta()) {
+ SetError("livenessTimeout and pingInterval must be greater than 0.");
+ } else if ((liveness_timeout_ > base::TimeDelta()) !=
+ (ping_interval_ > base::TimeDelta())) {
+ SetError("livenessTimeout and pingInterval must be set together.");
+ } else if (liveness_timeout_ < ping_interval_) {
+ SetError("livenessTimeout must be longer than pingTimeout.");
+ }
+ }
+
+ if (!GetError().empty()) {
+ return false;
+ }
+
+ channel_auth_ = connect_info.auth;
+ ip_endpoint_.reset(ParseConnectInfo(connect_info));
+ return true;
+}
+
+void CastChannelOpenFunction::AsyncWorkStart() {
+ DCHECK(api_);
+ DCHECK(ip_endpoint_.get());
+ const ConnectInfo& connect_info = params_->connect_info;
+ CastSocket* socket;
+ scoped_ptr<CastSocket> test_socket = api_->GetSocketForTest();
+ if (test_socket.get()) {
+ socket = test_socket.release();
+ } else {
+ socket = new cast_channel::CastSocketImpl(
+ extension_->id(), *ip_endpoint_, channel_auth_,
+ ExtensionsBrowserClient::Get()->GetNetLog(),
+ base::TimeDelta::FromMilliseconds(connect_info.timeout.get()
+ ? *connect_info.timeout.get()
+ : kDefaultConnectTimeoutMillis),
+ liveness_timeout_ > base::TimeDelta(), api_->GetLogger(),
+ connect_info.capabilities.get() ? *connect_info.capabilities.get()
+ : CastDeviceCapability::NONE);
+ }
+ new_channel_id_ = AddSocket(socket);
+ api_->GetLogger()->LogNewSocketEvent(*socket);
+
+ // Construct read delegates.
+ scoped_ptr<api::cast_channel::CastTransport::Delegate> delegate(
+ make_scoped_ptr(new CastMessageHandler(
+ base::Bind(&CastChannelAPI::SendEvent, api_->AsWeakPtr()), socket,
+ api_->GetLogger())));
+ if (socket->keep_alive()) {
+ // Wrap read delegate in a KeepAliveDelegate for timeout handling.
+ api::cast_channel::KeepAliveDelegate* keep_alive =
+ new api::cast_channel::KeepAliveDelegate(
+ socket, api_->GetLogger(), std::move(delegate), ping_interval_,
+ liveness_timeout_);
+ scoped_ptr<base::Timer> injected_timer =
+ api_->GetInjectedTimeoutTimerForTest();
+ if (injected_timer) {
+ keep_alive->SetTimersForTest(
+ make_scoped_ptr(new base::Timer(false, false)),
+ std::move(injected_timer));
+ }
+ delegate.reset(keep_alive);
+ }
+
+ api_->GetLogger()->LogNewSocketEvent(*socket);
+ socket->Connect(std::move(delegate),
+ base::Bind(&CastChannelOpenFunction::OnOpen, this));
+}
+
+void CastChannelOpenFunction::OnOpen(cast_channel::ChannelError result) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ VLOG(1) << "Connect finished, OnOpen invoked.";
+ CastSocket* socket = GetSocket(new_channel_id_);
+ if (!socket) {
+ SetResultFromError(new_channel_id_, result);
+ } else {
+ SetResultFromSocket(*socket);
+ }
+ AsyncWorkCompleted();
+}
+
+CastChannelSendFunction::CastChannelSendFunction() { }
+
+CastChannelSendFunction::~CastChannelSendFunction() { }
+
+bool CastChannelSendFunction::Prepare() {
+ params_ = Send::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ if (params_->message.namespace_.empty()) {
+ SetError("message_info.namespace_ is required");
+ return false;
+ }
+ if (params_->message.source_id.empty()) {
+ SetError("message_info.source_id is required");
+ return false;
+ }
+ if (params_->message.destination_id.empty()) {
+ SetError("message_info.destination_id is required");
+ return false;
+ }
+ switch (params_->message.data->GetType()) {
+ case base::Value::TYPE_STRING:
+ case base::Value::TYPE_BINARY:
+ break;
+ default:
+ SetError("Invalid type of message_info.data");
+ return false;
+ }
+ return true;
+}
+
+void CastChannelSendFunction::AsyncWorkStart() {
+ CastSocket* socket = GetSocket(params_->channel.channel_id);
+ if (!socket) {
+ SetResultFromError(params_->channel.channel_id,
+ cast_channel::CHANNEL_ERROR_INVALID_CHANNEL_ID);
+ AsyncWorkCompleted();
+ return;
+ }
+ CastMessage message_to_send;
+ if (!MessageInfoToCastMessage(params_->message, &message_to_send)) {
+ SetResultFromError(params_->channel.channel_id,
+ cast_channel::CHANNEL_ERROR_INVALID_MESSAGE);
+ AsyncWorkCompleted();
+ return;
+ }
+ socket->transport()->SendMessage(
+ message_to_send, base::Bind(&CastChannelSendFunction::OnSend, this));
+}
+
+void CastChannelSendFunction::OnSend(int result) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ int channel_id = params_->channel.channel_id;
+ CastSocket* socket = GetSocket(channel_id);
+ if (result < 0 || !socket) {
+ SetResultFromError(channel_id,
+ cast_channel::CHANNEL_ERROR_SOCKET_ERROR);
+ } else {
+ SetResultFromSocket(*socket);
+ }
+ AsyncWorkCompleted();
+}
+
+CastChannelCloseFunction::CastChannelCloseFunction() { }
+
+CastChannelCloseFunction::~CastChannelCloseFunction() { }
+
+bool CastChannelCloseFunction::Prepare() {
+ params_ = Close::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void CastChannelCloseFunction::AsyncWorkStart() {
+ CastSocket* socket = GetSocket(params_->channel.channel_id);
+ if (!socket) {
+ SetResultFromError(params_->channel.channel_id,
+ cast_channel::CHANNEL_ERROR_INVALID_CHANNEL_ID);
+ AsyncWorkCompleted();
+ } else {
+ socket->Close(base::Bind(&CastChannelCloseFunction::OnClose, this));
+ }
+}
+
+void CastChannelCloseFunction::OnClose(int result) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ VLOG(1) << "CastChannelCloseFunction::OnClose result = " << result;
+ int channel_id = params_->channel.channel_id;
+ CastSocket* socket = GetSocket(channel_id);
+ if (result < 0 || !socket) {
+ SetResultFromError(channel_id,
+ cast_channel::CHANNEL_ERROR_SOCKET_ERROR);
+ } else {
+ SetResultFromSocket(*socket);
+ // This will delete |socket|.
+ RemoveSocket(channel_id);
+ }
+ AsyncWorkCompleted();
+}
+
+CastChannelGetLogsFunction::CastChannelGetLogsFunction() {
+}
+
+CastChannelGetLogsFunction::~CastChannelGetLogsFunction() {
+}
+
+bool CastChannelGetLogsFunction::PrePrepare() {
+ api_ = CastChannelAPI::Get(browser_context());
+ return CastChannelAsyncApiFunction::PrePrepare();
+}
+
+bool CastChannelGetLogsFunction::Prepare() {
+ return true;
+}
+
+void CastChannelGetLogsFunction::AsyncWorkStart() {
+ DCHECK(api_);
+
+ size_t length = 0;
+ scoped_ptr<char[]> out = api_->GetLogger()->GetLogs(&length);
+ if (out.get()) {
+ SetResult(new base::BinaryValue(std::move(out), length));
+ } else {
+ SetError("Unable to get logs.");
+ }
+
+ api_->GetLogger()->Reset();
+
+ AsyncWorkCompleted();
+}
+
+CastChannelOpenFunction::CastMessageHandler::CastMessageHandler(
+ const EventDispatchCallback& ui_dispatch_cb,
+ cast_channel::CastSocket* socket,
+ scoped_refptr<Logger> logger)
+ : ui_dispatch_cb_(ui_dispatch_cb), socket_(socket), logger_(logger) {
+ DCHECK(socket_);
+ DCHECK(logger_);
+}
+
+CastChannelOpenFunction::CastMessageHandler::~CastMessageHandler() {
+}
+
+void CastChannelOpenFunction::CastMessageHandler::OnError(
+ cast_channel::ChannelError error_state) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+ ChannelInfo channel_info;
+ FillChannelInfo(*socket_, &channel_info);
+ channel_info.error_state = error_state;
+ ErrorInfo error_info;
+ FillErrorInfo(error_state, logger_->GetLastErrors(socket_->id()),
+ &error_info);
+
+ scoped_ptr<base::ListValue> results =
+ OnError::Create(channel_info, error_info);
+ scoped_ptr<Event> event(new Event(events::CAST_CHANNEL_ON_ERROR,
+ OnError::kEventName, std::move(results)));
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(ui_dispatch_cb_, socket_->owner_extension_id(),
+ base::Passed(std::move(event))));
+}
+
+void CastChannelOpenFunction::CastMessageHandler::OnMessage(
+ const CastMessage& message) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+ MessageInfo message_info;
+ cast_channel::CastMessageToMessageInfo(message, &message_info);
+ ChannelInfo channel_info;
+ FillChannelInfo(*socket_, &channel_info);
+ VLOG(1) << "Received message " << ParamToString(message_info)
+ << " on channel " << ParamToString(channel_info);
+
+ scoped_ptr<base::ListValue> results =
+ OnMessage::Create(channel_info, message_info);
+ scoped_ptr<Event> event(new Event(events::CAST_CHANNEL_ON_MESSAGE,
+ OnMessage::kEventName, std::move(results)));
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(ui_dispatch_cb_, socket_->owner_extension_id(),
+ base::Passed(std::move(event))));
+}
+
+void CastChannelOpenFunction::CastMessageHandler::Start() {
+}
+
+CastChannelSetAuthorityKeysFunction::CastChannelSetAuthorityKeysFunction() {
+}
+
+CastChannelSetAuthorityKeysFunction::~CastChannelSetAuthorityKeysFunction() {
+}
+
+bool CastChannelSetAuthorityKeysFunction::Prepare() {
+ return true;
+}
+
+void CastChannelSetAuthorityKeysFunction::AsyncWorkStart() {
+ // TODO(eroman): crbug.com/601171: Delete this once the API is
+ // removed. It is currently a no-op.
+ AsyncWorkCompleted();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/cast_channel/cast_channel_api.h b/chromium/extensions/browser/api/cast_channel/cast_channel_api.h
new file mode 100644
index 00000000000..b4cbba602e1
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_channel_api.h
@@ -0,0 +1,298 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_CHANNEL_API_H_
+#define EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_CHANNEL_API_H_
+
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/browser/api/async_api_function.h"
+#include "extensions/browser/api/cast_channel/cast_socket.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/common/api/cast_channel.h"
+
+class CastChannelAPITest;
+
+namespace content {
+class BrowserContext;
+}
+
+namespace net {
+class IPEndPoint;
+}
+
+namespace extensions {
+
+struct Event;
+
+namespace api {
+namespace cast_channel {
+class Logger;
+} // namespace cast_channel
+} // namespace api
+
+namespace cast_channel = api::cast_channel;
+
+class CastChannelAPI : public BrowserContextKeyedAPI,
+ public base::SupportsWeakPtr<CastChannelAPI> {
+ public:
+ explicit CastChannelAPI(content::BrowserContext* context);
+
+ static CastChannelAPI* Get(content::BrowserContext* context);
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<CastChannelAPI>* GetFactoryInstance();
+
+ // Returns a pointer to the Logger member variable.
+ // TODO(imcheng): Consider whether it is possible for this class to own the
+ // CastSockets and make this class the sole owner of Logger.
+ // Alternatively,
+ // consider making Logger not ref-counted by passing a weak
+ // reference of Logger to the CastSockets instead.
+ scoped_refptr<cast_channel::Logger> GetLogger();
+
+ // Sets the CastSocket instance to be used for testing.
+ void SetSocketForTest(scoped_ptr<cast_channel::CastSocket> socket_for_test);
+
+ // Returns a test CastSocket instance, if it is defined.
+ // Otherwise returns a scoped_ptr with a nullptr value.
+ scoped_ptr<cast_channel::CastSocket> GetSocketForTest();
+
+ // Returns the API browser context.
+ content::BrowserContext* GetBrowserContext() const;
+
+ // Sets injected ping timeout timer for testing.
+ void SetPingTimeoutTimerForTest(scoped_ptr<base::Timer> timer);
+
+ // Gets the injected ping timeout timer, if set.
+ // Returns a null scoped ptr if there is no injected timer.
+ scoped_ptr<base::Timer> GetInjectedTimeoutTimerForTest();
+
+ // Sends an event to the extension's EventRouter, if it exists.
+ void SendEvent(const std::string& extension_id, scoped_ptr<Event> event);
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<CastChannelAPI>;
+ friend class ::CastChannelAPITest;
+ friend class CastTransportDelegate;
+
+ ~CastChannelAPI() override;
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "CastChannelAPI"; }
+
+ content::BrowserContext* const browser_context_;
+ scoped_refptr<cast_channel::Logger> logger_;
+ scoped_ptr<cast_channel::CastSocket> socket_for_test_;
+ scoped_ptr<base::Timer> injected_timeout_timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastChannelAPI);
+};
+
+class CastChannelAsyncApiFunction : public AsyncApiFunction {
+ public:
+ CastChannelAsyncApiFunction();
+
+ protected:
+ typedef ApiResourceManager<cast_channel::CastSocket>::ApiResourceData
+ SocketData;
+
+ ~CastChannelAsyncApiFunction() override;
+
+ // AsyncApiFunction:
+ bool PrePrepare() override;
+ bool Respond() override;
+
+ // Returns the socket corresponding to |channel_id| if one exists. Otherwise,
+ // sets the function result with CHANNEL_ERROR_INVALID_CHANNEL_ID, completes
+ // the function, and returns null.
+ cast_channel::CastSocket* GetSocketOrCompleteWithError(int channel_id);
+
+ // Adds |socket| to |manager_| and returns the new channel_id. |manager_|
+ // assumes ownership of |socket|.
+ int AddSocket(cast_channel::CastSocket* socket);
+
+ // Removes the CastSocket corresponding to |channel_id| from the resource
+ // manager.
+ void RemoveSocket(int channel_id);
+
+ // Sets the function result to a ChannelInfo obtained from the state of
+ // |socket|.
+ void SetResultFromSocket(const cast_channel::CastSocket& socket);
+
+ // Sets the function result to a ChannelInfo populated with |channel_id| and
+ // |error|.
+ void SetResultFromError(int channel_id, cast_channel::ChannelError error);
+
+ // Returns the socket corresponding to |channel_id| if one exists, or null
+ // otherwise.
+ cast_channel::CastSocket* GetSocket(int channel_id) const;
+
+ private:
+ // Sets the function result from |channel_info|.
+ void SetResultFromChannelInfo(const cast_channel::ChannelInfo& channel_info);
+
+ // The collection of CastSocket API resources.
+ scoped_refptr<SocketData> sockets_;
+};
+
+class CastChannelOpenFunction : public CastChannelAsyncApiFunction {
+ public:
+ CastChannelOpenFunction();
+
+ protected:
+ ~CastChannelOpenFunction() override;
+
+ // AsyncApiFunction:
+ bool PrePrepare() override;
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ private:
+ DECLARE_EXTENSION_FUNCTION("cast.channel.open", CAST_CHANNEL_OPEN)
+
+ // Defines a callback used to send events to the extension's
+ // EventRouter.
+ // Parameter #0 is the extension's ID.
+ // Parameter #1 is a scoped pointer to the event payload.
+ using EventDispatchCallback =
+ base::Callback<void(const std::string&, scoped_ptr<Event>)>;
+
+ // Receives incoming messages and errors and provides additional API and
+ // origin socket context.
+ class CastMessageHandler : public cast_channel::CastTransport::Delegate {
+ public:
+ CastMessageHandler(const EventDispatchCallback& ui_dispatch_cb,
+ cast_channel::CastSocket* socket,
+ scoped_refptr<api::cast_channel::Logger> logger);
+ ~CastMessageHandler() override;
+
+ // CastTransport::Delegate implementation.
+ void OnError(cast_channel::ChannelError error_state) override;
+ void OnMessage(const cast_channel::CastMessage& message) override;
+ void Start() override;
+
+ private:
+ // Callback for sending events to the extension.
+ // Should be bound to a weak pointer, to prevent any use-after-free
+ // conditions.
+ EventDispatchCallback const ui_dispatch_cb_;
+ cast_channel::CastSocket* const socket_;
+ // Logger object for reporting error details.
+ scoped_refptr<api::cast_channel::Logger> logger_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastMessageHandler);
+ };
+
+ // Validates that |connect_info| represents a valid IP end point and returns a
+ // new IPEndPoint if so. Otherwise returns nullptr.
+ static net::IPEndPoint* ParseConnectInfo(
+ const cast_channel::ConnectInfo& connect_info);
+
+ void OnOpen(cast_channel::ChannelError result);
+
+ scoped_ptr<cast_channel::Open::Params> params_;
+ // The id of the newly opened socket.
+ int new_channel_id_;
+ CastChannelAPI* api_;
+ scoped_ptr<net::IPEndPoint> ip_endpoint_;
+ cast_channel::ChannelAuthType channel_auth_;
+ base::TimeDelta liveness_timeout_;
+ base::TimeDelta ping_interval_;
+
+ FRIEND_TEST_ALL_PREFIXES(CastChannelOpenFunctionTest, TestParseConnectInfo);
+ DISALLOW_COPY_AND_ASSIGN(CastChannelOpenFunction);
+};
+
+class CastChannelSendFunction : public CastChannelAsyncApiFunction {
+ public:
+ CastChannelSendFunction();
+
+ protected:
+ ~CastChannelSendFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ private:
+ DECLARE_EXTENSION_FUNCTION("cast.channel.send", CAST_CHANNEL_SEND)
+
+ void OnSend(int result);
+
+ scoped_ptr<cast_channel::Send::Params> params_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastChannelSendFunction);
+};
+
+class CastChannelCloseFunction : public CastChannelAsyncApiFunction {
+ public:
+ CastChannelCloseFunction();
+
+ protected:
+ ~CastChannelCloseFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ private:
+ DECLARE_EXTENSION_FUNCTION("cast.channel.close", CAST_CHANNEL_CLOSE)
+
+ void OnClose(int result);
+
+ scoped_ptr<cast_channel::Close::Params> params_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastChannelCloseFunction);
+};
+
+class CastChannelGetLogsFunction : public CastChannelAsyncApiFunction {
+ public:
+ CastChannelGetLogsFunction();
+
+ protected:
+ ~CastChannelGetLogsFunction() override;
+
+ // AsyncApiFunction:
+ bool PrePrepare() override;
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ private:
+ DECLARE_EXTENSION_FUNCTION("cast.channel.getLogs", CAST_CHANNEL_GETLOGS)
+
+ CastChannelAPI* api_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastChannelGetLogsFunction);
+};
+
+// TODO(eroman): crbug.com/601171: Delete this entire extension API. It
+// is currently deprecated and calling the function has no effect.
+class CastChannelSetAuthorityKeysFunction : public CastChannelAsyncApiFunction {
+ public:
+ CastChannelSetAuthorityKeysFunction();
+
+ protected:
+ ~CastChannelSetAuthorityKeysFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ private:
+ DECLARE_EXTENSION_FUNCTION("cast.channel.setAuthorityKeys",
+ CAST_CHANNEL_SETAUTHORITYKEYS)
+
+ DISALLOW_COPY_AND_ASSIGN(CastChannelSetAuthorityKeysFunction);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_CHANNEL_API_H_
diff --git a/chromium/extensions/browser/api/cast_channel/cast_channel_api_unittest.cc b/chromium/extensions/browser/api/cast_channel/cast_channel_api_unittest.cc
new file mode 100644
index 00000000000..5587194d45e
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_channel_api_unittest.cc
@@ -0,0 +1,33 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/cast_channel/cast_channel_api.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/ip_endpoint.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+
+// Tests parsing of ConnectInfo.
+TEST(CastChannelOpenFunctionTest, TestParseConnectInfo) {
+ typedef CastChannelOpenFunction ccof;
+ scoped_ptr<net::IPEndPoint> ip_endpoint;
+
+ // Valid ConnectInfo
+ ConnectInfo connect_info;
+ connect_info.ip_address = "192.0.0.1";
+ connect_info.port = 8009;
+ connect_info.auth = CHANNEL_AUTH_TYPE_SSL;
+
+ ip_endpoint.reset(ccof::ParseConnectInfo(connect_info));
+ EXPECT_TRUE(ip_endpoint);
+ EXPECT_EQ(ip_endpoint->ToString(), "192.0.0.1:8009");
+}
+
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/cast_channel/cast_channel_apitest.cc b/chromium/extensions/browser/api/cast_channel/cast_channel_apitest.cc
new file mode 100644
index 00000000000..84958b263da
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_channel_apitest.cc
@@ -0,0 +1,583 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/timer/mock_timer.h"
+#include "build/build_config.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/extensions/extension_function_test_utils.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/ui/browser.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/api/cast_channel/cast_channel_api.h"
+#include "extensions/browser/api/cast_channel/cast_socket.h"
+#include "extensions/browser/api/cast_channel/cast_test_util.h"
+#include "extensions/browser/api/cast_channel/logger.h"
+#include "extensions/common/api/cast_channel.h"
+#include "extensions/common/api/cast_channel/cast_channel.pb.h"
+#include "extensions/common/switches.h"
+#include "extensions/common/test_util.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "extensions/test/result_catcher.h"
+#include "net/base/completion_callback.h"
+#include "net/base/ip_address.h"
+#include "net/base/net_errors.h"
+#include "net/log/test_net_log.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gmock_mutant.h"
+
+// TODO(mfoltz): Mock out the ApiResourceManager to resolve threading issues
+// (crbug.com/398242) and simulate unloading of the extension.
+
+namespace cast_channel = extensions::api::cast_channel;
+using cast_channel::CastMessage;
+using cast_channel::CastSocket;
+using cast_channel::CastTransport;
+using cast_channel::ChannelAuthType;
+using cast_channel::ChannelError;
+using cast_channel::CreateIPEndPointForTest;
+using cast_channel::ErrorInfo;
+using cast_channel::LastErrors;
+using cast_channel::Logger;
+using cast_channel::MessageInfo;
+using cast_channel::MockCastSocket;
+using cast_channel::MockCastTransport;
+using cast_channel::ReadyState;
+using extensions::Extension;
+
+namespace utils = extension_function_test_utils;
+
+using ::testing::_;
+using ::testing::A;
+using ::testing::DoAll;
+using ::testing::Invoke;
+using ::testing::InSequence;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::ReturnRef;
+using ::testing::ReturnPointee;
+using ::testing::SaveArg;
+
+namespace {
+
+static void FillCastMessage(const std::string& message,
+ CastMessage* cast_message) {
+ cast_message->set_namespace_("foo");
+ cast_message->set_source_id("src");
+ cast_message->set_destination_id("dest");
+ cast_message->set_payload_utf8(message);
+ cast_message->set_payload_type(CastMessage::STRING);
+}
+
+ACTION_TEMPLATE(InvokeCompletionCallback,
+ HAS_1_TEMPLATE_PARAMS(int, k),
+ AND_1_VALUE_PARAMS(result)) {
+ ::std::tr1::get<k>(args).Run(result);
+}
+
+} // namespace
+
+class CastChannelAPITest : public ExtensionApiTest {
+ public:
+ CastChannelAPITest() : ip_endpoint_(CreateIPEndPointForTest()) {}
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ ExtensionApiTest::SetUpCommandLine(command_line);
+ command_line->AppendSwitchASCII(
+ extensions::switches::kWhitelistedExtensionID,
+ cast_channel::kTestExtensionId);
+ }
+
+ void SetUpMockCastSocket() {
+ extensions::CastChannelAPI* api = GetApi();
+ timeout_timer_ = new base::MockTimer(true, false);
+ api->SetPingTimeoutTimerForTest(make_scoped_ptr(timeout_timer_));
+
+ net::IPEndPoint ip_endpoint(net::IPAddress(192, 168, 1, 1), 8009);
+ mock_cast_socket_ = new MockCastSocket;
+ // Transfers ownership of the socket.
+ api->SetSocketForTest(make_scoped_ptr<CastSocket>(mock_cast_socket_));
+ ON_CALL(*mock_cast_socket_, set_id(_))
+ .WillByDefault(SaveArg<0>(&channel_id_));
+ ON_CALL(*mock_cast_socket_, id())
+ .WillByDefault(ReturnPointee(&channel_id_));
+ ON_CALL(*mock_cast_socket_, ip_endpoint())
+ .WillByDefault(ReturnRef(ip_endpoint_));
+ ON_CALL(*mock_cast_socket_, channel_auth())
+ .WillByDefault(Return(cast_channel::CHANNEL_AUTH_TYPE_SSL));
+ ON_CALL(*mock_cast_socket_, keep_alive()).WillByDefault(Return(false));
+ }
+
+ void SetUpOpenSendClose() {
+ SetUpMockCastSocket();
+ EXPECT_CALL(*mock_cast_socket_, error_state())
+ .WillRepeatedly(Return(cast_channel::CHANNEL_ERROR_NONE));
+ {
+ InSequence sequence;
+ EXPECT_CALL(*mock_cast_socket_, ConnectRawPtr(_, _))
+ .WillOnce(
+ InvokeCompletionCallback<1>(cast_channel::CHANNEL_ERROR_NONE));
+ EXPECT_CALL(*mock_cast_socket_, ready_state())
+ .WillOnce(Return(cast_channel::READY_STATE_OPEN));
+ EXPECT_CALL(*mock_cast_socket_->mock_transport(),
+ SendMessage(A<const CastMessage&>(), _))
+ .WillOnce(InvokeCompletionCallback<1>(net::OK));
+ EXPECT_CALL(*mock_cast_socket_, ready_state())
+ .WillOnce(Return(cast_channel::READY_STATE_OPEN));
+ EXPECT_CALL(*mock_cast_socket_, Close(_))
+ .WillOnce(InvokeCompletionCallback<0>(net::OK));
+ EXPECT_CALL(*mock_cast_socket_, ready_state())
+ .WillOnce(Return(cast_channel::READY_STATE_CLOSED));
+ }
+ }
+
+ void SetUpOpenPingTimeout() {
+ SetUpMockCastSocket();
+ EXPECT_CALL(*mock_cast_socket_, error_state())
+ .WillRepeatedly(Return(cast_channel::CHANNEL_ERROR_NONE));
+ EXPECT_CALL(*mock_cast_socket_, keep_alive()).WillRepeatedly(Return(true));
+ {
+ InSequence sequence;
+ EXPECT_CALL(*mock_cast_socket_, ConnectRawPtr(_, _))
+ .WillOnce(DoAll(
+ SaveArg<0>(&message_delegate_),
+ InvokeCompletionCallback<1>(cast_channel::CHANNEL_ERROR_NONE)));
+ EXPECT_CALL(*mock_cast_socket_, ready_state())
+ .WillOnce(Return(cast_channel::READY_STATE_OPEN))
+ .RetiresOnSaturation();
+ EXPECT_CALL(*mock_cast_socket_, ready_state())
+ .WillOnce(Return(cast_channel::READY_STATE_CLOSED));
+ }
+ }
+
+ extensions::CastChannelAPI* GetApi() {
+ return extensions::CastChannelAPI::Get(profile());
+ }
+
+ // Logs some bogus error details and calls the OnError handler.
+ void DoCallOnError(extensions::CastChannelAPI* api) {
+ api->GetLogger()->LogSocketEventWithRv(mock_cast_socket_->id(),
+ cast_channel::proto::SOCKET_WRITE,
+ net::ERR_FAILED);
+ message_delegate_->OnError(cast_channel::CHANNEL_ERROR_CONNECT_ERROR);
+ }
+
+ protected:
+ void CallOnMessage(const std::string& message) {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&CastChannelAPITest::DoCallOnMessage, this,
+ GetApi(), mock_cast_socket_, message));
+ }
+
+ void DoCallOnMessage(extensions::CastChannelAPI* api,
+ MockCastSocket* cast_socket,
+ const std::string& message) {
+ CastMessage cast_message;
+ FillCastMessage(message, &cast_message);
+ message_delegate_->OnMessage(cast_message);
+ }
+
+ // Starts the read delegate on the IO thread.
+ void StartDelegate() {
+ CHECK(message_delegate_);
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO, FROM_HERE,
+ base::Bind(&cast_channel::CastTransport::Delegate::Start,
+ base::Unretained(message_delegate_)));
+ }
+
+ // Fires a timer on the IO thread.
+ void FireTimeout() {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO, FROM_HERE,
+ base::Bind(&base::MockTimer::Fire, base::Unretained(timeout_timer_)));
+ }
+
+ extensions::CastChannelOpenFunction* CreateOpenFunction(
+ scoped_refptr<Extension> extension) {
+ extensions::CastChannelOpenFunction* cast_channel_open_function =
+ new extensions::CastChannelOpenFunction;
+ cast_channel_open_function->set_extension(extension.get());
+ return cast_channel_open_function;
+ }
+
+ extensions::CastChannelSendFunction* CreateSendFunction(
+ scoped_refptr<Extension> extension) {
+ extensions::CastChannelSendFunction* cast_channel_send_function =
+ new extensions::CastChannelSendFunction;
+ cast_channel_send_function->set_extension(extension.get());
+ return cast_channel_send_function;
+ }
+
+ extensions::CastChannelSetAuthorityKeysFunction*
+ CreateSetAuthorityKeysFunction(scoped_refptr<Extension> extension) {
+ extensions::CastChannelSetAuthorityKeysFunction*
+ cast_channel_set_authority_keys_function =
+ new extensions::CastChannelSetAuthorityKeysFunction;
+ cast_channel_set_authority_keys_function->set_extension(extension.get());
+ return cast_channel_set_authority_keys_function;
+ }
+
+ MockCastSocket* mock_cast_socket_;
+ base::MockTimer* timeout_timer_;
+ net::IPEndPoint ip_endpoint_;
+ LastErrors last_errors_;
+ CastTransport::Delegate* message_delegate_;
+ net::TestNetLog capturing_net_log_;
+ int channel_id_;
+};
+
+ACTION_P2(InvokeDelegateOnError, api_test, api) {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO, FROM_HERE,
+ base::Bind(&CastChannelAPITest::DoCallOnError, base::Unretained(api_test),
+ base::Unretained(api)));
+}
+
+// TODO(kmarshall): Win Dbg has a workaround that makes RunExtensionSubtest
+// always return true without actually running the test. Remove when fixed.
+#if defined(OS_WIN) && !defined(NDEBUG)
+#define MAYBE_TestOpenSendClose DISABLED_TestOpenSendClose
+#else
+#define MAYBE_TestOpenSendClose TestOpenSendClose
+#endif
+// Test loading extension, opening a channel with ConnectInfo, adding a
+// listener, writing, reading, and closing.
+IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestOpenSendClose) {
+ SetUpOpenSendClose();
+
+ EXPECT_TRUE(RunExtensionSubtest("cast_channel/api",
+ "test_open_send_close.html"));
+}
+
+// TODO(kmarshall): Win Dbg has a workaround that makes RunExtensionSubtest
+// always return true without actually running the test. Remove when fixed.
+#if defined(OS_WIN) && !defined(NDEBUG)
+#define MAYBE_TestPingTimeout DISABLED_TestPingTimeout
+#else
+#define MAYBE_TestPingTimeout TestPingTimeout
+#endif
+// Verify that timeout events are propagated through the API layer.
+// (SSL, non-verified).
+IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestPingTimeout) {
+ SetUpOpenPingTimeout();
+
+ ExtensionTestMessageListener channel_opened("channel_opened_ssl", false);
+ ExtensionTestMessageListener timeout("timeout_ssl", false);
+ EXPECT_TRUE(
+ RunExtensionSubtest("cast_channel/api", "test_open_timeout.html"));
+ EXPECT_TRUE(channel_opened.WaitUntilSatisfied());
+ StartDelegate();
+ FireTimeout();
+ EXPECT_TRUE(timeout.WaitUntilSatisfied());
+}
+
+// TODO(kmarshall): Win Dbg has a workaround that makes RunExtensionSubtest
+// always return true without actually running the test. Remove when fixed.
+#if defined(OS_WIN) && !defined(NDEBUG)
+#define MAYBE_TestPingTimeoutSslVerified DISABLED_TestPingTimeoutSslVerified
+#else
+#define MAYBE_TestPingTimeoutSslVerified TestPingTimeoutSslVerified
+#endif
+// Verify that timeout events are propagated through the API layer.
+// (SSL, verified).
+IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestPingTimeoutSslVerified) {
+ SetUpOpenPingTimeout();
+
+ ExtensionTestMessageListener channel_opened("channel_opened_ssl_verified",
+ false);
+ ExtensionTestMessageListener timeout("timeout_ssl_verified", false);
+ EXPECT_TRUE(RunExtensionSubtest("cast_channel/api",
+ "test_open_timeout_verified.html"));
+ EXPECT_TRUE(channel_opened.WaitUntilSatisfied());
+ StartDelegate();
+ FireTimeout();
+ EXPECT_TRUE(timeout.WaitUntilSatisfied());
+}
+
+// TODO(kmarshall): Win Dbg has a workaround that makes RunExtensionSubtest
+// always return true without actually running the test. Remove when fixed.
+#if defined(OS_WIN) && !defined(NDEBUG)
+#define MAYBE_TestOpenReceiveClose DISABLED_TestOpenReceiveClose
+#else
+#define MAYBE_TestOpenReceiveClose TestOpenReceiveClose
+#endif
+// Test loading extension, opening a channel, adding a listener,
+// writing, reading, and closing.
+IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestOpenReceiveClose) {
+ SetUpMockCastSocket();
+ EXPECT_CALL(*mock_cast_socket_, error_state())
+ .WillRepeatedly(Return(cast_channel::CHANNEL_ERROR_NONE));
+
+ {
+ InSequence sequence;
+ EXPECT_CALL(*mock_cast_socket_, ConnectRawPtr(NotNull(), _))
+ .WillOnce(DoAll(
+ SaveArg<0>(&message_delegate_),
+ InvokeCompletionCallback<1>(cast_channel::CHANNEL_ERROR_NONE)));
+ EXPECT_CALL(*mock_cast_socket_, ready_state())
+ .Times(3)
+ .WillRepeatedly(Return(cast_channel::READY_STATE_OPEN));
+ EXPECT_CALL(*mock_cast_socket_, Close(_))
+ .WillOnce(InvokeCompletionCallback<0>(net::OK));
+ EXPECT_CALL(*mock_cast_socket_, ready_state())
+ .WillOnce(Return(cast_channel::READY_STATE_CLOSED));
+ }
+
+ EXPECT_TRUE(RunExtensionSubtest("cast_channel/api",
+ "test_open_receive_close.html"));
+
+ extensions::ResultCatcher catcher;
+ CallOnMessage("some-message");
+ CallOnMessage("some-message");
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+// TODO(imcheng): Win Dbg has a workaround that makes RunExtensionSubtest
+// always return true without actually running the test. Remove when fixed.
+#if defined(OS_WIN) && !defined(NDEBUG)
+#define MAYBE_TestGetLogs DISABLED_TestGetLogs
+#else
+#define MAYBE_TestGetLogs TestGetLogs
+#endif
+// Test loading extension, execute a open-send-close sequence, then get logs.
+IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestGetLogs) {
+ SetUpOpenSendClose();
+
+ EXPECT_TRUE(RunExtensionSubtest("cast_channel/api", "test_get_logs.html"));
+}
+
+// TODO(kmarshall): Win Dbg has a workaround that makes RunExtensionSubtest
+// always return true without actually running the test. Remove when fixed.
+#if defined(OS_WIN) && !defined(NDEBUG)
+#define MAYBE_TestOpenError DISABLED_TestOpenError
+#else
+#define MAYBE_TestOpenError TestOpenError
+#endif
+// Test the case when socket open results in an error.
+IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestOpenError) {
+ SetUpMockCastSocket();
+
+ EXPECT_CALL(*mock_cast_socket_, ConnectRawPtr(NotNull(), _))
+ .WillOnce(DoAll(SaveArg<0>(&message_delegate_),
+ InvokeDelegateOnError(this, GetApi()),
+ InvokeCompletionCallback<1>(
+ cast_channel::CHANNEL_ERROR_CONNECT_ERROR)));
+ EXPECT_CALL(*mock_cast_socket_, error_state())
+ .WillRepeatedly(Return(cast_channel::CHANNEL_ERROR_CONNECT_ERROR));
+ EXPECT_CALL(*mock_cast_socket_, ready_state())
+ .WillRepeatedly(Return(cast_channel::READY_STATE_CLOSED));
+ EXPECT_CALL(*mock_cast_socket_, Close(_))
+ .WillOnce(InvokeCompletionCallback<0>(net::OK));
+
+ EXPECT_TRUE(RunExtensionSubtest("cast_channel/api",
+ "test_open_error.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(CastChannelAPITest, TestOpenInvalidConnectInfo) {
+ scoped_refptr<Extension> empty_extension =
+ extensions::test_util::CreateEmptyExtension();
+ scoped_refptr<extensions::CastChannelOpenFunction> cast_channel_open_function;
+
+ // Invalid IP address
+ cast_channel_open_function = CreateOpenFunction(empty_extension);
+ std::string error = utils::RunFunctionAndReturnError(
+ cast_channel_open_function.get(),
+ "[{\"ipAddress\": \"invalid_ip\", \"port\": 8009, \"auth\": \"ssl\"}]",
+ browser());
+ EXPECT_EQ(error, "Invalid connect_info (invalid IP address)");
+
+ // Invalid port
+ cast_channel_open_function = CreateOpenFunction(empty_extension);
+ error = utils::RunFunctionAndReturnError(
+ cast_channel_open_function.get(),
+ "[{\"ipAddress\": \"127.0.0.1\", \"port\": -200, \"auth\": \"ssl\"}]",
+ browser());
+ EXPECT_EQ(error, "Invalid connect_info (invalid port)");
+}
+
+IN_PROC_BROWSER_TEST_F(CastChannelAPITest, TestSendInvalidMessageInfo) {
+ scoped_refptr<Extension> empty_extension(
+ extensions::test_util::CreateEmptyExtension());
+ scoped_refptr<extensions::CastChannelSendFunction> cast_channel_send_function;
+
+ // Numbers are not supported
+ cast_channel_send_function = CreateSendFunction(empty_extension);
+ std::string error(utils::RunFunctionAndReturnError(
+ cast_channel_send_function.get(),
+ "[{\"channelId\": 1, "
+ "\"keepAlive\": true, "
+ "\"audioOnly\": false, "
+ "\"connectInfo\": "
+ "{\"ipAddress\": \"127.0.0.1\", \"port\": 8009, "
+ "\"auth\": \"ssl\"}, \"readyState\": \"open\"}, "
+ "{\"namespace_\": \"foo\", \"sourceId\": \"src\", "
+ "\"destinationId\": \"dest\", \"data\": 1235}]",
+ browser()));
+ EXPECT_EQ(error, "Invalid type of message_info.data");
+
+ // Missing namespace_
+ cast_channel_send_function = CreateSendFunction(empty_extension);
+ error = utils::RunFunctionAndReturnError(
+ cast_channel_send_function.get(),
+ "[{\"channelId\": 1, "
+ "\"keepAlive\": true, "
+ "\"audioOnly\": false, "
+ "\"connectInfo\": "
+ "{\"ipAddress\": \"127.0.0.1\", \"port\": 8009, "
+ "\"auth\": \"ssl\"}, \"readyState\": \"open\"}, "
+ "{\"namespace_\": \"\", \"sourceId\": \"src\", "
+ "\"destinationId\": \"dest\", \"data\": \"data\"}]",
+ browser());
+ EXPECT_EQ(error, "message_info.namespace_ is required");
+
+ // Missing source_id
+ cast_channel_send_function = CreateSendFunction(empty_extension);
+ error = utils::RunFunctionAndReturnError(
+ cast_channel_send_function.get(),
+ "[{\"channelId\": 1, "
+ "\"keepAlive\": true, "
+ "\"audioOnly\": false, "
+ "\"connectInfo\": "
+ "{\"ipAddress\": \"127.0.0.1\", \"port\": 8009, "
+ "\"auth\": \"ssl\"}, \"readyState\": \"open\"}, "
+ "{\"namespace_\": \"foo\", \"sourceId\": \"\", "
+ "\"destinationId\": \"dest\", \"data\": \"data\"}]",
+ browser());
+ EXPECT_EQ(error, "message_info.source_id is required");
+
+ // Missing destination_id
+ cast_channel_send_function = CreateSendFunction(empty_extension);
+ error = utils::RunFunctionAndReturnError(
+ cast_channel_send_function.get(),
+ "[{\"channelId\": 1, "
+ "\"keepAlive\": true, "
+ "\"audioOnly\": false, "
+ "\"connectInfo\": "
+ "{\"ipAddress\": \"127.0.0.1\", \"port\": 8009, "
+ "\"auth\": \"ssl\"}, \"readyState\": \"open\"}, "
+ "{\"namespace_\": \"foo\", \"sourceId\": \"src\", "
+ "\"destinationId\": \"\", \"data\": \"data\"}]",
+ browser());
+ EXPECT_EQ(error, "message_info.destination_id is required");
+}
+
+IN_PROC_BROWSER_TEST_F(CastChannelAPITest, TestSetAuthorityKeysInvalid) {
+ scoped_refptr<Extension> empty_extension(
+ extensions::test_util::CreateEmptyExtension());
+ scoped_refptr<extensions::CastChannelSetAuthorityKeysFunction>
+ cast_channel_set_authority_keys_function;
+ // TODO(eroman): crbug.com/601171: Delete this test once the API has
+ // been removed. The API is deprecated and will trivially return
+ // success. So this is just testing that it succeeds for all inputs
+ // (even invalid ones).
+ std::string errorResult = "";
+
+ cast_channel_set_authority_keys_function =
+ CreateSetAuthorityKeysFunction(empty_extension);
+ std::string error = utils::RunFunctionAndReturnError(
+ cast_channel_set_authority_keys_function.get(),
+ "[\"\", \"signature\"]",
+ browser());
+ EXPECT_EQ(error, errorResult);
+
+ cast_channel_set_authority_keys_function =
+ CreateSetAuthorityKeysFunction(empty_extension);
+ error = utils::RunFunctionAndReturnError(
+ cast_channel_set_authority_keys_function.get(),
+ "[\"keys\", \"\"]",
+ browser());
+ EXPECT_EQ(error, errorResult);
+
+ std::string keys =
+ "CrMCCiBSnZzWf+XraY5w3SbX2PEmWfHm5SNIv2pc9xbhP0EOcxKOAjCCAQoCggEBALwigL"
+ "2A9johADuudl41fz3DZFxVlIY0LwWHKM33aYwXs1CnuIL638dDLdZ+q6BvtxNygKRHFcEg"
+ "mVDN7BRiCVukmM3SQbY2Tv/oLjIwSoGoQqNsmzNuyrL1U2bgJ1OGGoUepzk/SneO+1RmZv"
+ "tYVMBeOcf1UAYL4IrUzuFqVR+LFwDmaaMn5gglaTwSnY0FLNYuojHetFJQ1iBJ3nGg+a0g"
+ "QBLx3SXr1ea4NvTWj3/KQ9zXEFvmP1GKhbPz//YDLcsjT5ytGOeTBYysUpr3TOmZer5ufk"
+ "0K48YcqZP6OqWRXRy9ZuvMYNyGdMrP+JIcmH1X+mFHnquAt+RIgCqSxRsCAwEAAQ==";
+ std::string signature =
+ "chCUHZKkykcwU8HzU+hm027fUTBL0dqPMtrzppwExQwK9+"
+ "XlmCjJswfce2sUUfhR1OL1tyW4hWFwu4JnuQCJ+CvmSmAh2bzRpnuSKzBfgvIDjNOAGUs7"
+ "ADaNSSWPLxp+6ko++2Dn4S9HpOt8N1v6gMWqj3Ru5IqFSQPZSvGH2ois6uE50CFayPcjQE"
+ "OVZt41noQdFd15RmKTvocoCC5tHNlaikeQ52yi0IScOlad1B1lMhoplW3rWophQaqxMumr"
+ "OcHIZ+Y+p858x5f8Pny/kuqUClmFh9B/vF07NsUHwoSL9tA5t5jCY3L5iUc/v7o3oFcW/T"
+ "gojKkX2Kg7KQ86QA==";
+
+ cast_channel_set_authority_keys_function =
+ CreateSetAuthorityKeysFunction(empty_extension);
+ error = utils::RunFunctionAndReturnError(
+ cast_channel_set_authority_keys_function.get(),
+ "[\"" + keys + "\", \"signature\"]",
+ browser());
+ EXPECT_EQ(error, errorResult);
+
+ cast_channel_set_authority_keys_function =
+ CreateSetAuthorityKeysFunction(empty_extension);
+ error = utils::RunFunctionAndReturnError(
+ cast_channel_set_authority_keys_function.get(),
+ "[\"keys\", \"" + signature + "\"]",
+ browser());
+ EXPECT_EQ(error, errorResult);
+
+ cast_channel_set_authority_keys_function =
+ CreateSetAuthorityKeysFunction(empty_extension);
+ error = utils::RunFunctionAndReturnError(
+ cast_channel_set_authority_keys_function.get(),
+ "[\"" + keys + "\", \"" + signature + "\"]",
+ browser());
+ EXPECT_EQ(error, errorResult);
+}
+
+IN_PROC_BROWSER_TEST_F(CastChannelAPITest, TestSetAuthorityKeysValid) {
+ scoped_refptr<Extension> empty_extension(
+ extensions::test_util::CreateEmptyExtension());
+ scoped_refptr<extensions::CastChannelSetAuthorityKeysFunction>
+ cast_channel_set_authority_keys_function;
+
+ cast_channel_set_authority_keys_function =
+ CreateSetAuthorityKeysFunction(empty_extension);
+ std::string keys =
+ "CrMCCiBSnZzWf+XraY5w3SbX2PEmWfHm5SNIv2pc9xbhP0EOcxKOAjCCAQoCggEBALwigL"
+ "2A9johADuudl41fz3DZFxVlIY0LwWHKM33aYwXs1CnuIL638dDLdZ+q6BvtxNygKRHFcEg"
+ "mVDN7BRiCVukmM3SQbY2Tv/oLjIwSoGoQqNsmzNuyrL1U2bgJ1OGGoUepzk/SneO+1RmZv"
+ "tYVMBeOcf1UAYL4IrUzuFqVR+LFwDmaaMn5gglaTwSnY0FLNYuojHetFJQ1iBJ3nGg+a0g"
+ "QBLx3SXr1ea4NvTWj3/KQ9zXEFvmP1GKhbPz//YDLcsjT5ytGOeTBYysUpr3TOmZer5ufk"
+ "0K48YcqZP6OqWRXRy9ZuvMYNyGdMrP+JIcmH1X+mFHnquAt+RIgCqSxRsCAwEAAQqzAgog"
+ "okjC6FTmVqVt6CMfHuF1b9vkB/n+1GUNYMxay2URxyASjgIwggEKAoIBAQCwDl4HOt+kX2"
+ "j3Icdk27Z27+6Lk/j2G4jhk7cX8BUeflJVdzwCjXtKbNO91sGccsizFc8RwfVGxNUgR/sw"
+ "9ORhDGjwXqs3jpvhvIHDcIp41oM0MpwZYuvknO3jZGxBHZzSi0hMI5CVs+dS6gVXzGCzuh"
+ "TkugA55EZVdM5ajnpnI9poCvrEhB60xaGianMfbsguL5qeqLEO/Yemj009SwXVNVp0TbyO"
+ "gkSW9LWVYE6l3yc9QVwHo7Q1WrOe8gUkys0xWg0mTNTT/VDhNOlMgVgwssd63YGJptQ6OI"
+ "QDtzSedz//eAdbmcGyHzVWbjo8DCXhV/aKfknAzIMRNeeRbS5lAgMBAAE=";
+ std::string signature =
+ "o83oku3jP+xjTysNBalqp/ZfJRPLt8R+IUhZMepbARFSRVizLoeFW5XyUwe6lQaC+PFFQH"
+ "SZeGZyeeGRpwCJ/lef0xh6SWJlVMWNTk5+z0U84GQdizJP/CTCeHpIwMobN+kyDajgOyfD"
+ "DLhktc6LHmSlFGG6J7B8W67oziS8ZFEdrcT9WSXFrjLVyURHjvidZD5iFtuImI6k9R9OoX"
+ "LR6SyAwpjdrL+vlHMk3Gol6KQ98YpF0ghHnN3/FFW4ibvIwjmRbp+tUV3h8TRcCOjlXVGp"
+ "bzPtNRRlTqfv7Rxm5YXkZMLmJJMZiTs5+o8FMRMTQZT4hRR3DQ+A/jofViyTGA==";
+
+ std::string args = "[\"" + keys + "\", \"" + signature + "\"]";
+ std::string error = utils::RunFunctionAndReturnError(
+ cast_channel_set_authority_keys_function.get(), args, browser());
+ EXPECT_EQ(error, std::string());
+}
+
+// TODO(vadimgo): Win Dbg has a workaround that makes RunExtensionSubtest
+// always return true without actually running the test. Remove when fixed.
+#if defined(OS_WIN) && !defined(NDEBUG)
+#define MAYBE_TestSetAuthorityKeys DISABLED_TestSetAuthorityKeys
+#else
+#define MAYBE_TestSetAuthorityKeys TestSetAuthorityKeys
+#endif
+// Test loading extension, opening a channel with ConnectInfo, adding a
+// listener, writing, reading, and closing.
+IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestSetAuthorityKeys) {
+ EXPECT_TRUE(
+ RunExtensionSubtest("cast_channel/api", "test_authority_keys.html"));
+}
diff --git a/chromium/extensions/browser/api/cast_channel/cast_framer.cc b/chromium/extensions/browser/api/cast_channel/cast_framer.cc
new file mode 100644
index 00000000000..a5dcf3e816c
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_framer.cc
@@ -0,0 +1,182 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/cast_channel/cast_framer.h"
+
+#include <stdlib.h>
+
+#include <limits>
+
+#include "base/memory/free_deleter.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/sys_byteorder.h"
+#include "extensions/common/api/cast_channel/cast_channel.pb.h"
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+MessageFramer::MessageFramer(scoped_refptr<net::GrowableIOBuffer> input_buffer)
+ : input_buffer_(input_buffer), error_(false) {
+ Reset();
+}
+
+MessageFramer::~MessageFramer() {
+}
+
+MessageFramer::MessageHeader::MessageHeader() : message_size(0) {
+}
+
+void MessageFramer::MessageHeader::SetMessageSize(size_t size) {
+ DCHECK_LT(size, static_cast<size_t>(std::numeric_limits<uint32_t>::max()));
+ DCHECK_GT(size, 0U);
+ message_size = size;
+}
+
+// TODO(mfoltz): Investigate replacing header serialization with base::Pickle,
+// if bit-for-bit compatible.
+void MessageFramer::MessageHeader::PrependToString(std::string* str) {
+ MessageHeader output = *this;
+ output.message_size = base::HostToNet32(message_size);
+ size_t header_size = MessageHeader::header_size();
+ scoped_ptr<char, base::FreeDeleter> char_array(
+ static_cast<char*>(malloc(header_size)));
+ memcpy(char_array.get(), &output, header_size);
+ str->insert(0, char_array.get(), header_size);
+}
+
+// TODO(mfoltz): Investigate replacing header deserialization with base::Pickle,
+// if bit-for-bit compatible.
+void MessageFramer::MessageHeader::Deserialize(char* data,
+ MessageHeader* header) {
+ uint32_t message_size;
+ memcpy(&message_size, data, header_size());
+ header->message_size =
+ base::checked_cast<size_t>(base::NetToHost32(message_size));
+}
+
+// static
+size_t MessageFramer::MessageHeader::header_size() {
+ return sizeof(uint32_t);
+}
+
+// static
+size_t MessageFramer::MessageHeader::max_message_size() {
+ return 65535;
+}
+
+std::string MessageFramer::MessageHeader::ToString() {
+ return "{message_size: " +
+ base::UintToString(static_cast<uint32_t>(message_size)) + "}";
+}
+
+// static
+bool MessageFramer::Serialize(const CastMessage& message_proto,
+ std::string* message_data) {
+ DCHECK(message_data);
+ message_proto.SerializeToString(message_data);
+ size_t message_size = message_data->size();
+ if (message_size > MessageHeader::max_message_size()) {
+ message_data->clear();
+ return false;
+ }
+ MessageHeader header;
+ header.SetMessageSize(message_size);
+ header.PrependToString(message_data);
+ return true;
+}
+
+size_t MessageFramer::BytesRequested() {
+ size_t bytes_left;
+ if (error_) {
+ return 0;
+ }
+
+ switch (current_element_) {
+ case HEADER:
+ bytes_left = MessageHeader::header_size() - message_bytes_received_;
+ DCHECK_LE(bytes_left, MessageHeader::header_size());
+ VLOG(2) << "Bytes needed for header: " << bytes_left;
+ return bytes_left;
+ case BODY:
+ bytes_left =
+ (body_size_ + MessageHeader::header_size()) - message_bytes_received_;
+ DCHECK_LE(
+ bytes_left,
+ MessageHeader::max_message_size() - MessageHeader::header_size());
+ VLOG(2) << "Bytes needed for body: " << bytes_left;
+ return bytes_left;
+ default:
+ NOTREACHED() << "Unhandled packet element type.";
+ return 0;
+ }
+}
+
+scoped_ptr<CastMessage> MessageFramer::Ingest(size_t num_bytes,
+ size_t* message_length,
+ ChannelError* error) {
+ DCHECK(error);
+ DCHECK(message_length);
+ if (error_) {
+ *error = CHANNEL_ERROR_INVALID_MESSAGE;
+ return scoped_ptr<CastMessage>();
+ }
+
+ DCHECK_EQ(base::checked_cast<int32_t>(message_bytes_received_),
+ input_buffer_->offset());
+ CHECK_LE(num_bytes, BytesRequested());
+ message_bytes_received_ += num_bytes;
+ *error = CHANNEL_ERROR_NONE;
+ *message_length = 0;
+ switch (current_element_) {
+ case HEADER:
+ if (BytesRequested() == 0) {
+ MessageHeader header;
+ MessageHeader::Deserialize(input_buffer_.get()->StartOfBuffer(),
+ &header);
+ if (header.message_size > MessageHeader::max_message_size()) {
+ VLOG(1) << "Error parsing header (message size too large).";
+ *error = CHANNEL_ERROR_INVALID_MESSAGE;
+ error_ = true;
+ return scoped_ptr<CastMessage>();
+ }
+ current_element_ = BODY;
+ body_size_ = header.message_size;
+ }
+ break;
+ case BODY:
+ if (BytesRequested() == 0) {
+ scoped_ptr<CastMessage> parsed_message(new CastMessage);
+ if (!parsed_message->ParseFromArray(
+ input_buffer_->StartOfBuffer() + MessageHeader::header_size(),
+ body_size_)) {
+ VLOG(1) << "Error parsing packet body.";
+ *error = CHANNEL_ERROR_INVALID_MESSAGE;
+ error_ = true;
+ return scoped_ptr<CastMessage>();
+ }
+ *message_length = body_size_;
+ Reset();
+ return parsed_message;
+ }
+ break;
+ default:
+ NOTREACHED() << "Unhandled packet element type.";
+ return scoped_ptr<CastMessage>();
+ }
+
+ input_buffer_->set_offset(message_bytes_received_);
+ return scoped_ptr<CastMessage>();
+}
+
+void MessageFramer::Reset() {
+ current_element_ = HEADER;
+ message_bytes_received_ = 0;
+ body_size_ = 0;
+ input_buffer_->set_offset(0);
+}
+
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/cast_channel/cast_framer.h b/chromium/extensions/browser/api/cast_channel/cast_framer.h
new file mode 100644
index 00000000000..093d6e56526
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_framer.h
@@ -0,0 +1,102 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_FRAMER_H_
+#define EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_FRAMER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "extensions/common/api/cast_channel.h"
+#include "net/base/io_buffer.h"
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+class CastMessage;
+
+// Class for constructing and parsing CastMessage packet data.
+class MessageFramer {
+ public:
+ // |input_buffer|: The input buffer used by all socket read operations that
+ // feed data into the framer.
+ explicit MessageFramer(scoped_refptr<net::GrowableIOBuffer> input_buffer);
+ ~MessageFramer();
+
+ // The number of bytes required from |input_buffer| to complete the
+ // CastMessage being read.
+ // Returns zero if |error_| is true (framer is in an invalid state.)
+ size_t BytesRequested();
+
+ // Serializes |message_proto| into |message_data|.
+ // Returns true if the message was serialized successfully, false otherwise.
+ static bool Serialize(const CastMessage& message_proto,
+ std::string* message_data);
+
+ // Reads bytes from |input_buffer_| and returns a new CastMessage if one
+ // is fully read.
+ //
+ // |num_bytes| The number of bytes received by a read operation.
+ // Value must be <= BytesRequested().
+ // |message_length| Size of the deserialized message object, in bytes. For
+ // logging purposes. Set to zero if no message was parsed.
+ // |error| The result of the ingest operation. Set to CHANNEL_ERROR_NONE
+ // if no error occurred.
+ // Returns A pointer to a parsed CastMessage if a message was received
+ // in its entirety, nullptr otherwise.
+ scoped_ptr<CastMessage> Ingest(size_t num_bytes,
+ size_t* message_length,
+ ChannelError* error);
+
+ // Message header struct. If fields are added, be sure to update
+ // header_size(). Public to allow use of *_size() methods in unit tests.
+ struct MessageHeader {
+ MessageHeader();
+ // Sets the message size.
+ void SetMessageSize(size_t message_size);
+ // Prepends this header to |str|.
+ void PrependToString(std::string* str);
+ // Reads |header| from the bytes specified by |data|.
+ static void Deserialize(char* data, MessageHeader* header);
+ // Size (in bytes) of the message header.
+ static size_t header_size();
+ // Maximum size (in bytes) of a message payload on the wire (does not
+ // include header).
+ static size_t max_message_size();
+ std::string ToString();
+ // The size of the following protocol message in bytes, in host byte order.
+ size_t message_size;
+ };
+
+ private:
+ enum MessageElement { HEADER, BODY };
+
+ // Prepares the framer for ingesting a new message.
+ void Reset();
+
+ // The element of the message that will be read on the next call to Ingest().
+ MessageElement current_element_;
+
+ // Total size of the message, in bytes (head + body).
+ size_t message_bytes_received_;
+
+ // Size of the body alone, in bytes.
+ size_t body_size_;
+
+ // Input buffer which carries message data read from the socket.
+ // Caller is responsible for writing into this buffer.
+ scoped_refptr<net::GrowableIOBuffer> input_buffer_;
+
+ // Disables Ingest functionality is the parser receives invalid data.
+ bool error_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageFramer);
+};
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
+#endif // EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_FRAMER_H_
diff --git a/chromium/extensions/browser/api/cast_channel/cast_framer_unittest.cc b/chromium/extensions/browser/api/cast_channel/cast_framer_unittest.cc
new file mode 100644
index 00000000000..0ff96831973
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_framer_unittest.cc
@@ -0,0 +1,140 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/cast_channel/cast_framer.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <string>
+
+#include "extensions/common/api/cast_channel/cast_channel.pb.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+class CastFramerTest : public testing::Test {
+ public:
+ CastFramerTest() {}
+ ~CastFramerTest() override {}
+
+ void SetUp() override {
+ cast_message_.set_protocol_version(CastMessage::CASTV2_1_0);
+ cast_message_.set_source_id("source");
+ cast_message_.set_destination_id("destination");
+ cast_message_.set_namespace_("namespace");
+ cast_message_.set_payload_type(CastMessage::STRING);
+ cast_message_.set_payload_utf8("payload");
+ ASSERT_TRUE(MessageFramer::Serialize(cast_message_, &cast_message_str_));
+
+ buffer_ = new net::GrowableIOBuffer;
+ buffer_->SetCapacity(MessageFramer::MessageHeader::max_message_size());
+ framer_.reset(new MessageFramer(buffer_.get()));
+ }
+
+ void WriteToBuffer(const std::string& data) {
+ memcpy(buffer_->StartOfBuffer(), data.data(), data.size());
+ }
+
+ protected:
+ CastMessage cast_message_;
+ std::string cast_message_str_;
+ scoped_refptr<net::GrowableIOBuffer> buffer_;
+ scoped_ptr<MessageFramer> framer_;
+};
+
+TEST_F(CastFramerTest, TestMessageFramerCompleteMessage) {
+ ChannelError error;
+ size_t message_length;
+
+ WriteToBuffer(cast_message_str_);
+
+ // Receive 1 byte of the header, framer demands 3 more bytes.
+ EXPECT_EQ(4u, framer_->BytesRequested());
+ EXPECT_EQ(nullptr, framer_->Ingest(1, &message_length, &error).get());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, error);
+ EXPECT_EQ(3u, framer_->BytesRequested());
+
+ // Ingest remaining 3, expect that the framer has moved on to requesting the
+ // body contents.
+ EXPECT_EQ(nullptr, framer_->Ingest(3, &message_length, &error).get());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, error);
+ EXPECT_EQ(
+ cast_message_str_.size() - MessageFramer::MessageHeader::header_size(),
+ framer_->BytesRequested());
+
+ // Remainder of packet sent over the wire.
+ scoped_ptr<CastMessage> message;
+ message = framer_->Ingest(framer_->BytesRequested(), &message_length, &error);
+ EXPECT_NE(static_cast<CastMessage*>(nullptr), message.get());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, error);
+ EXPECT_EQ(message->SerializeAsString(), cast_message_.SerializeAsString());
+ EXPECT_EQ(4u, framer_->BytesRequested());
+ EXPECT_EQ(message->SerializeAsString().size(), message_length);
+}
+
+TEST_F(CastFramerTest, TestSerializeErrorMessageTooLarge) {
+ std::string serialized;
+ CastMessage big_message;
+ big_message.CopyFrom(cast_message_);
+ std::string payload;
+ payload.append(MessageFramer::MessageHeader::max_message_size() + 1, 'x');
+ big_message.set_payload_utf8(payload);
+ EXPECT_FALSE(MessageFramer::Serialize(big_message, &serialized));
+}
+
+TEST_F(CastFramerTest, TestIngestIllegalLargeMessage) {
+ std::string mangled_cast_message = cast_message_str_;
+ mangled_cast_message[0] = 88;
+ mangled_cast_message[1] = 88;
+ mangled_cast_message[2] = 88;
+ mangled_cast_message[3] = 88;
+ WriteToBuffer(mangled_cast_message);
+
+ size_t bytes_ingested;
+ ChannelError error;
+ EXPECT_EQ(4u, framer_->BytesRequested());
+ EXPECT_EQ(nullptr, framer_->Ingest(4, &bytes_ingested, &error).get());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_INVALID_MESSAGE, error);
+ EXPECT_EQ(0u, framer_->BytesRequested());
+
+ // Test that the parser enters a terminal error state.
+ WriteToBuffer(cast_message_str_);
+ EXPECT_EQ(0u, framer_->BytesRequested());
+ EXPECT_EQ(nullptr, framer_->Ingest(4, &bytes_ingested, &error).get());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_INVALID_MESSAGE, error);
+ EXPECT_EQ(0u, framer_->BytesRequested());
+}
+
+TEST_F(CastFramerTest, TestUnparsableBodyProto) {
+ // Message header is OK, but the body is replaced with "x"en.
+ std::string mangled_cast_message = cast_message_str_;
+ for (size_t i = MessageFramer::MessageHeader::header_size();
+ i < mangled_cast_message.size();
+ ++i) {
+ std::fill(mangled_cast_message.begin() +
+ MessageFramer::MessageHeader::header_size(),
+ mangled_cast_message.end(),
+ 'x');
+ }
+ WriteToBuffer(mangled_cast_message);
+
+ // Send header.
+ size_t message_length;
+ ChannelError error;
+ EXPECT_EQ(4u, framer_->BytesRequested());
+ EXPECT_EQ(nullptr, framer_->Ingest(4, &message_length, &error).get());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, error);
+ EXPECT_EQ(cast_message_str_.size() - 4, framer_->BytesRequested());
+
+ // Send body, expect an error.
+ scoped_ptr<CastMessage> message;
+ EXPECT_EQ(nullptr, framer_->Ingest(framer_->BytesRequested(), &message_length,
+ &error).get());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_INVALID_MESSAGE, error);
+}
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/cast_channel/cast_message_util.cc b/chromium/extensions/browser/api/cast_channel/cast_message_util.cc
new file mode 100644
index 00000000000..76fa2d0218a
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_message_util.cc
@@ -0,0 +1,161 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/cast_channel/cast_message_util.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "extensions/common/api/cast_channel.h"
+#include "extensions/common/api/cast_channel/cast_channel.pb.h"
+
+namespace {
+static const char kAuthNamespace[] =
+ "urn:x-cast:com.google.cast.tp.deviceauth";
+// Sender and receiver IDs to use for platform messages.
+static const char kPlatformSenderId[] = "sender-0";
+static const char kPlatformReceiverId[] = "receiver-0";
+} // namespace
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+
+bool MessageInfoToCastMessage(const MessageInfo& message,
+ CastMessage* message_proto) {
+ DCHECK(message_proto);
+ if (!message.data)
+ return false;
+
+ message_proto->set_protocol_version(CastMessage_ProtocolVersion_CASTV2_1_0);
+ message_proto->set_source_id(message.source_id);
+ message_proto->set_destination_id(message.destination_id);
+ message_proto->set_namespace_(message.namespace_);
+ // Determine the type of the base::Value and set the message payload
+ // appropriately.
+ std::string data;
+ base::BinaryValue* real_value;
+ switch (message.data->GetType()) {
+ // JS string
+ case base::Value::TYPE_STRING:
+ if (message.data->GetAsString(&data)) {
+ message_proto->set_payload_type(CastMessage_PayloadType_STRING);
+ message_proto->set_payload_utf8(data);
+ }
+ break;
+ // JS ArrayBuffer
+ case base::Value::TYPE_BINARY:
+ real_value = static_cast<base::BinaryValue*>(message.data.get());
+ if (real_value->GetBuffer()) {
+ message_proto->set_payload_type(CastMessage_PayloadType_BINARY);
+ message_proto->set_payload_binary(real_value->GetBuffer(),
+ real_value->GetSize());
+ }
+ break;
+ default:
+ // Unknown value type. message_proto will remain uninitialized because
+ // payload_type is unset.
+ break;
+ }
+ return message_proto->IsInitialized();
+}
+
+bool IsCastMessageValid(const CastMessage& message_proto) {
+ if (message_proto.namespace_().empty() || message_proto.source_id().empty() ||
+ message_proto.destination_id().empty()) {
+ return false;
+ }
+ return (message_proto.payload_type() == CastMessage_PayloadType_STRING &&
+ message_proto.has_payload_utf8()) ||
+ (message_proto.payload_type() == CastMessage_PayloadType_BINARY &&
+ message_proto.has_payload_binary());
+}
+
+bool CastMessageToMessageInfo(const CastMessage& message_proto,
+ MessageInfo* message) {
+ DCHECK(message);
+ message->source_id = message_proto.source_id();
+ message->destination_id = message_proto.destination_id();
+ message->namespace_ = message_proto.namespace_();
+ // Determine the type of the payload and fill base::Value appropriately.
+ scoped_ptr<base::Value> value;
+ switch (message_proto.payload_type()) {
+ case CastMessage_PayloadType_STRING:
+ if (message_proto.has_payload_utf8())
+ value.reset(new base::StringValue(message_proto.payload_utf8()));
+ break;
+ case CastMessage_PayloadType_BINARY:
+ if (message_proto.has_payload_binary())
+ value.reset(base::BinaryValue::CreateWithCopiedBuffer(
+ message_proto.payload_binary().data(),
+ message_proto.payload_binary().size()));
+ break;
+ default:
+ // Unknown payload type. value will remain unset.
+ break;
+ }
+ if (value.get()) {
+ DCHECK(!message->data.get());
+ message->data.reset(value.release());
+ return true;
+ } else {
+ return false;
+ }
+}
+
+std::string CastMessageToString(const CastMessage& message_proto) {
+ std::string out("{");
+ out += "namespace = " + message_proto.namespace_();
+ out += ", sourceId = " + message_proto.source_id();
+ out += ", destId = " + message_proto.destination_id();
+ out += ", type = " + base::IntToString(message_proto.payload_type());
+ out += ", str = \"" + message_proto.payload_utf8() + "\"}";
+ return out;
+}
+
+std::string AuthMessageToString(const DeviceAuthMessage& message) {
+ std::string out("{");
+ if (message.has_challenge()) {
+ out += "challenge: {}, ";
+ }
+ if (message.has_response()) {
+ out += "response: {signature: (";
+ out += base::SizeTToString(message.response().signature().length());
+ out += " bytes), certificate: (";
+ out += base::SizeTToString(
+ message.response().client_auth_certificate().length());
+ out += " bytes)}";
+ }
+ if (message.has_error()) {
+ out += ", error: {";
+ out += base::IntToString(message.error().error_type());
+ out += "}";
+ }
+ out += "}";
+ return out;
+}
+
+void CreateAuthChallengeMessage(CastMessage* message_proto) {
+ CHECK(message_proto);
+ DeviceAuthMessage auth_message;
+ auth_message.mutable_challenge();
+ std::string auth_message_string;
+ auth_message.SerializeToString(&auth_message_string);
+
+ message_proto->set_protocol_version(CastMessage_ProtocolVersion_CASTV2_1_0);
+ message_proto->set_source_id(kPlatformSenderId);
+ message_proto->set_destination_id(kPlatformReceiverId);
+ message_proto->set_namespace_(kAuthNamespace);
+ message_proto->set_payload_type(CastMessage_PayloadType_BINARY);
+ message_proto->set_payload_binary(auth_message_string);
+}
+
+bool IsAuthMessage(const CastMessage& message) {
+ return message.namespace_() == kAuthNamespace;
+}
+
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/cast_channel/cast_message_util.h b/chromium/extensions/browser/api/cast_channel/cast_message_util.h
new file mode 100644
index 00000000000..b14b737a5c9
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_message_util.h
@@ -0,0 +1,45 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_MESSAGE_UTIL_H_
+#define EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_MESSAGE_UTIL_H_
+
+#include <string>
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+
+class CastMessage;
+class DeviceAuthMessage;
+struct MessageInfo;
+
+// Fills |message_proto| from |message| and returns true on success.
+bool MessageInfoToCastMessage(const MessageInfo& message,
+ CastMessage* message_proto);
+
+// Checks if the contents of |message_proto| are valid.
+bool IsCastMessageValid(const CastMessage& message_proto);
+
+// Fills |message| from |message_proto| and returns true on success.
+bool CastMessageToMessageInfo(const CastMessage& message_proto,
+ MessageInfo* message);
+
+// Returns a human readable string for |message_proto|.
+std::string CastMessageToString(const CastMessage& message_proto);
+
+// Returns a human readable string for |message|.
+std::string AuthMessageToString(const DeviceAuthMessage& message);
+
+// Fills |message_proto| appropriately for an auth challenge request message.
+void CreateAuthChallengeMessage(CastMessage* message_proto);
+
+// Returns whether the given message is an auth handshake message.
+bool IsAuthMessage(const CastMessage& message);
+
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_MESSAGE_UTIL_H_
diff --git a/chromium/extensions/browser/api/cast_channel/cast_socket.cc b/chromium/extensions/browser/api/cast_channel/cast_socket.cc
new file mode 100644
index 00000000000..12df2bf382e
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_socket.cc
@@ -0,0 +1,622 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/cast_channel/cast_socket.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/format_macros.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop/message_loop.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_byteorder.h"
+#include "base/time/time.h"
+#include "extensions/browser/api/cast_channel/cast_auth_util.h"
+#include "extensions/browser/api/cast_channel/cast_framer.h"
+#include "extensions/browser/api/cast_channel/cast_message_util.h"
+#include "extensions/browser/api/cast_channel/cast_transport.h"
+#include "extensions/browser/api/cast_channel/logger.h"
+#include "extensions/browser/api/cast_channel/logger_util.h"
+#include "extensions/common/api/cast_channel/cast_channel.pb.h"
+#include "net/base/address_list.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_errors.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/x509_certificate.h"
+#include "net/http/transport_security_state.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/stream_socket.h"
+#include "net/socket/tcp_client_socket.h"
+#include "net/ssl/ssl_config_service.h"
+#include "net/ssl/ssl_info.h"
+
+// Helper for logging data with remote host IP and authentication state.
+// Assumes |ip_endpoint_| of type net::IPEndPoint and |channel_auth_| of enum
+// type ChannelAuthType are available in the current scope.
+#define CONNECTION_INFO() \
+ "[" << ip_endpoint_.ToString() << ", auth=" << channel_auth_ << "] "
+#define VLOG_WITH_CONNECTION(level) VLOG(level) << CONNECTION_INFO()
+#define LOG_WITH_CONNECTION(level) LOG(level) << CONNECTION_INFO()
+
+namespace extensions {
+static base::LazyInstance<BrowserContextKeyedAPIFactory<
+ ApiResourceManager<api::cast_channel::CastSocket>>> g_factory =
+ LAZY_INSTANCE_INITIALIZER;
+
+// static
+template <>
+BrowserContextKeyedAPIFactory<
+ ApiResourceManager<api::cast_channel::CastSocket>>*
+ApiResourceManager<api::cast_channel::CastSocket>::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+namespace api {
+namespace cast_channel {
+namespace {
+
+bool IsTerminalState(proto::ConnectionState state) {
+ return state == proto::CONN_STATE_FINISHED ||
+ state == proto::CONN_STATE_ERROR || state == proto::CONN_STATE_TIMEOUT;
+}
+
+// Cert verifier which blindly accepts all certificates, regardless of validity.
+class FakeCertVerifier : public net::CertVerifier {
+ public:
+ FakeCertVerifier() {}
+ ~FakeCertVerifier() override {}
+
+ int Verify(net::X509Certificate* cert,
+ const std::string&,
+ const std::string&,
+ int,
+ net::CRLSet*,
+ net::CertVerifyResult* verify_result,
+ const net::CompletionCallback&,
+ scoped_ptr<net::CertVerifier::Request>*,
+ const net::BoundNetLog&) override {
+ verify_result->Reset();
+ verify_result->verified_cert = cert;
+ return net::OK;
+ }
+};
+
+} // namespace
+
+CastSocket::CastSocket(const std::string& owner_extension_id)
+ : ApiResource(owner_extension_id) {
+}
+
+bool CastSocket::IsPersistent() const {
+ return true;
+}
+
+CastSocketImpl::CastSocketImpl(const std::string& owner_extension_id,
+ const net::IPEndPoint& ip_endpoint,
+ ChannelAuthType channel_auth,
+ net::NetLog* net_log,
+ const base::TimeDelta& timeout,
+ bool keep_alive,
+ const scoped_refptr<Logger>& logger,
+ uint64_t device_capabilities)
+ : CastSocket(owner_extension_id),
+ owner_extension_id_(owner_extension_id),
+ channel_id_(0),
+ ip_endpoint_(ip_endpoint),
+ channel_auth_(channel_auth),
+ net_log_(net_log),
+ keep_alive_(keep_alive),
+ logger_(logger),
+ connect_timeout_(timeout),
+ connect_timeout_timer_(new base::OneShotTimer),
+ is_canceled_(false),
+ device_capabilities_(device_capabilities),
+ audio_only_(false),
+ connect_state_(proto::CONN_STATE_START_CONNECT),
+ error_state_(CHANNEL_ERROR_NONE),
+ ready_state_(READY_STATE_NONE),
+ auth_delegate_(nullptr) {
+ DCHECK(net_log_);
+ DCHECK(channel_auth_ == CHANNEL_AUTH_TYPE_SSL ||
+ channel_auth_ == CHANNEL_AUTH_TYPE_SSL_VERIFIED);
+ net_log_source_.type = net::NetLog::SOURCE_SOCKET;
+ net_log_source_.id = net_log_->NextID();
+}
+
+CastSocketImpl::~CastSocketImpl() {
+ // Ensure that resources are freed but do not run pending callbacks to avoid
+ // any re-entrancy.
+ CloseInternal();
+}
+
+ReadyState CastSocketImpl::ready_state() const {
+ return ready_state_;
+}
+
+ChannelError CastSocketImpl::error_state() const {
+ return error_state_;
+}
+
+const net::IPEndPoint& CastSocketImpl::ip_endpoint() const {
+ return ip_endpoint_;
+}
+
+int CastSocketImpl::id() const {
+ return channel_id_;
+}
+
+void CastSocketImpl::set_id(int id) {
+ channel_id_ = id;
+}
+
+ChannelAuthType CastSocketImpl::channel_auth() const {
+ return channel_auth_;
+}
+
+bool CastSocketImpl::keep_alive() const {
+ return keep_alive_;
+}
+
+bool CastSocketImpl::audio_only() const {
+ return audio_only_;
+}
+
+scoped_ptr<net::TCPClientSocket> CastSocketImpl::CreateTcpSocket() {
+ net::AddressList addresses(ip_endpoint_);
+ return scoped_ptr<net::TCPClientSocket>(
+ new net::TCPClientSocket(addresses, net_log_, net_log_source_));
+ // Options cannot be set on the TCPClientSocket yet, because the
+ // underlying platform socket will not be created until Bind()
+ // or Connect() is called.
+}
+
+scoped_ptr<net::SSLClientSocket> CastSocketImpl::CreateSslSocket(
+ scoped_ptr<net::StreamSocket> socket) {
+ net::SSLConfig ssl_config;
+ cert_verifier_ = make_scoped_ptr(new FakeCertVerifier);
+ transport_security_state_.reset(new net::TransportSecurityState);
+ net::SSLClientSocketContext context;
+ // CertVerifier and TransportSecurityState are owned by us, not the
+ // context object.
+ context.cert_verifier = cert_verifier_.get();
+ context.transport_security_state = transport_security_state_.get();
+
+ scoped_ptr<net::ClientSocketHandle> connection(new net::ClientSocketHandle);
+ connection->SetSocket(std::move(socket));
+ net::HostPortPair host_and_port = net::HostPortPair::FromIPEndPoint(
+ ip_endpoint_);
+
+ return net::ClientSocketFactory::GetDefaultFactory()->CreateSSLClientSocket(
+ std::move(connection), host_and_port, ssl_config, context);
+}
+
+scoped_refptr<net::X509Certificate> CastSocketImpl::ExtractPeerCert() {
+ net::SSLInfo ssl_info;
+ if (!socket_->GetSSLInfo(&ssl_info) || !ssl_info.cert.get()) {
+ return nullptr;
+ }
+
+ logger_->LogSocketEvent(channel_id_, proto::SSL_INFO_OBTAINED);
+
+ return ssl_info.cert;
+}
+
+bool CastSocketImpl::VerifyChannelPolicy(const AuthResult& result) {
+ audio_only_ = (result.channel_policies & AuthResult::POLICY_AUDIO_ONLY) != 0;
+ if (audio_only_ &&
+ (device_capabilities_ & CastDeviceCapability::VIDEO_OUT) != 0) {
+ LOG_WITH_CONNECTION(ERROR)
+ << "Audio only channel policy enforced for video out capable device";
+ logger_->LogSocketEventWithDetails(
+ channel_id_, proto::CHANNEL_POLICY_ENFORCED, std::string());
+ return false;
+ }
+ return true;
+}
+
+bool CastSocketImpl::VerifyChallengeReply() {
+ DCHECK(peer_cert_);
+ AuthResult result =
+ AuthenticateChallengeReply(*challenge_reply_, *peer_cert_);
+ logger_->LogSocketChallengeReplyEvent(channel_id_, result);
+ if (result.success()) {
+ VLOG(1) << result.error_message;
+ if (!VerifyChannelPolicy(result)) {
+ return false;
+ }
+ }
+ return result.success();
+}
+
+void CastSocketImpl::SetTransportForTesting(
+ scoped_ptr<CastTransport> transport) {
+ transport_ = std::move(transport);
+}
+
+void CastSocketImpl::Connect(scoped_ptr<CastTransport::Delegate> delegate,
+ base::Callback<void(ChannelError)> callback) {
+ DCHECK(CalledOnValidThread());
+ VLOG_WITH_CONNECTION(1) << "Connect readyState = " << ready_state_;
+ DCHECK_EQ(proto::CONN_STATE_START_CONNECT, connect_state_);
+
+ delegate_ = std::move(delegate);
+
+ if (ready_state_ != READY_STATE_NONE) {
+ logger_->LogSocketEventWithDetails(
+ channel_id_, proto::CONNECT_FAILED, "ReadyState not NONE");
+ callback.Run(CHANNEL_ERROR_CONNECT_ERROR);
+ return;
+ }
+
+ connect_callback_ = callback;
+ SetReadyState(READY_STATE_CONNECTING);
+ SetConnectState(proto::CONN_STATE_TCP_CONNECT);
+
+ // Set up connection timeout.
+ if (connect_timeout_.InMicroseconds() > 0) {
+ DCHECK(connect_timeout_callback_.IsCancelled());
+ connect_timeout_callback_.Reset(
+ base::Bind(&CastSocketImpl::OnConnectTimeout, base::Unretained(this)));
+ GetTimer()->Start(FROM_HERE,
+ connect_timeout_,
+ connect_timeout_callback_.callback());
+ }
+
+ DoConnectLoop(net::OK);
+}
+
+CastTransport* CastSocketImpl::transport() const {
+ return transport_.get();
+}
+
+void CastSocketImpl::OnConnectTimeout() {
+ DCHECK(CalledOnValidThread());
+ // Stop all pending connection setup tasks and report back to the client.
+ is_canceled_ = true;
+ logger_->LogSocketEvent(channel_id_, proto::CONNECT_TIMED_OUT);
+ VLOG_WITH_CONNECTION(1) << "Timeout while establishing a connection.";
+ SetErrorState(CHANNEL_ERROR_CONNECT_TIMEOUT);
+ DoConnectCallback();
+}
+
+void CastSocketImpl::PostTaskToStartConnectLoop(int result) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(connect_loop_callback_.IsCancelled());
+ connect_loop_callback_.Reset(base::Bind(&CastSocketImpl::DoConnectLoop,
+ base::Unretained(this), result));
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ connect_loop_callback_.callback());
+}
+
+// This method performs the state machine transitions for connection flow.
+// There are two entry points to this method:
+// 1. Connect method: this starts the flow
+// 2. Callback from network operations that finish asynchronously.
+void CastSocketImpl::DoConnectLoop(int result) {
+ connect_loop_callback_.Cancel();
+ if (is_canceled_) {
+ LOG_WITH_CONNECTION(ERROR) << "CANCELLED - Aborting DoConnectLoop.";
+ return;
+ }
+
+ // Network operations can either finish synchronously or asynchronously.
+ // This method executes the state machine transitions in a loop so that
+ // correct state transitions happen even when network operations finish
+ // synchronously.
+ int rv = result;
+ do {
+ proto::ConnectionState state = connect_state_;
+ connect_state_ = proto::CONN_STATE_UNKNOWN;
+ switch (state) {
+ case proto::CONN_STATE_TCP_CONNECT:
+ rv = DoTcpConnect();
+ break;
+ case proto::CONN_STATE_TCP_CONNECT_COMPLETE:
+ rv = DoTcpConnectComplete(rv);
+ break;
+ case proto::CONN_STATE_SSL_CONNECT:
+ DCHECK_EQ(net::OK, rv);
+ rv = DoSslConnect();
+ break;
+ case proto::CONN_STATE_SSL_CONNECT_COMPLETE:
+ rv = DoSslConnectComplete(rv);
+ break;
+ case proto::CONN_STATE_AUTH_CHALLENGE_SEND:
+ rv = DoAuthChallengeSend();
+ break;
+ case proto::CONN_STATE_AUTH_CHALLENGE_SEND_COMPLETE:
+ rv = DoAuthChallengeSendComplete(rv);
+ break;
+ case proto::CONN_STATE_AUTH_CHALLENGE_REPLY_COMPLETE:
+ rv = DoAuthChallengeReplyComplete(rv);
+ DCHECK(IsTerminalState(connect_state_));
+ break;
+ default:
+ NOTREACHED() << "Unknown state in connect flow: " << state;
+ SetConnectState(proto::CONN_STATE_FINISHED);
+ SetErrorState(CHANNEL_ERROR_UNKNOWN);
+ DoConnectCallback();
+ return;
+ }
+ } while (rv != net::ERR_IO_PENDING && !IsTerminalState(connect_state_));
+ // Exit the state machine if an asynchronous network operation is pending
+ // or if the state machine is in the terminal "finished" state.
+
+ if (IsTerminalState(connect_state_)) {
+ DCHECK_NE(rv, net::ERR_IO_PENDING);
+ logger_->LogSocketConnectState(channel_id_, connect_state_);
+ GetTimer()->Stop();
+ DoConnectCallback();
+ } else {
+ DCHECK_EQ(rv, net::ERR_IO_PENDING);
+ }
+}
+
+int CastSocketImpl::DoTcpConnect() {
+ DCHECK(connect_loop_callback_.IsCancelled());
+ VLOG_WITH_CONNECTION(1) << "DoTcpConnect";
+ SetConnectState(proto::CONN_STATE_TCP_CONNECT_COMPLETE);
+ tcp_socket_ = CreateTcpSocket();
+
+ int rv = tcp_socket_->Connect(
+ base::Bind(&CastSocketImpl::DoConnectLoop, base::Unretained(this)));
+ logger_->LogSocketEventWithRv(channel_id_, proto::TCP_SOCKET_CONNECT, rv);
+ return rv;
+}
+
+int CastSocketImpl::DoTcpConnectComplete(int connect_result) {
+ VLOG_WITH_CONNECTION(1) << "DoTcpConnectComplete: " << connect_result;
+ logger_->LogSocketEventWithRv(channel_id_, proto::TCP_SOCKET_CONNECT_COMPLETE,
+ connect_result);
+ if (connect_result == net::OK) {
+ SetConnectState(proto::CONN_STATE_SSL_CONNECT);
+ } else if (connect_result == net::ERR_CONNECTION_TIMED_OUT) {
+ SetConnectState(proto::CONN_STATE_FINISHED);
+ SetErrorState(CHANNEL_ERROR_CONNECT_TIMEOUT);
+ } else {
+ SetConnectState(proto::CONN_STATE_FINISHED);
+ SetErrorState(CHANNEL_ERROR_CONNECT_ERROR);
+ }
+ return connect_result;
+}
+
+int CastSocketImpl::DoSslConnect() {
+ DCHECK(connect_loop_callback_.IsCancelled());
+ VLOG_WITH_CONNECTION(1) << "DoSslConnect";
+ SetConnectState(proto::CONN_STATE_SSL_CONNECT_COMPLETE);
+ socket_ = CreateSslSocket(std::move(tcp_socket_));
+
+ int rv = socket_->Connect(
+ base::Bind(&CastSocketImpl::DoConnectLoop, base::Unretained(this)));
+ logger_->LogSocketEventWithRv(channel_id_, proto::SSL_SOCKET_CONNECT, rv);
+ return rv;
+}
+
+int CastSocketImpl::DoSslConnectComplete(int result) {
+ logger_->LogSocketEventWithRv(channel_id_, proto::SSL_SOCKET_CONNECT_COMPLETE,
+ result);
+ VLOG_WITH_CONNECTION(1) << "DoSslConnectComplete: " << result;
+ if (result == net::OK) {
+ peer_cert_ = ExtractPeerCert();
+
+ if (!peer_cert_) {
+ LOG_WITH_CONNECTION(WARNING) << "Could not extract peer cert.";
+ SetConnectState(proto::CONN_STATE_FINISHED);
+ SetErrorState(CHANNEL_ERROR_AUTHENTICATION_ERROR);
+ return net::ERR_CERT_INVALID;
+ }
+
+ // SSL connection succeeded.
+ if (!transport_.get()) {
+ // Create a channel transport if one wasn't already set (e.g. by test
+ // code).
+ transport_.reset(new CastTransportImpl(this->socket_.get(), channel_id_,
+ ip_endpoint_, channel_auth_,
+ logger_));
+ }
+ auth_delegate_ = new AuthTransportDelegate(this);
+ transport_->SetReadDelegate(make_scoped_ptr(auth_delegate_));
+ if (channel_auth_ == CHANNEL_AUTH_TYPE_SSL_VERIFIED) {
+ // Additionally verify the connection with a handshake.
+ SetConnectState(proto::CONN_STATE_AUTH_CHALLENGE_SEND);
+ } else {
+ SetConnectState(proto::CONN_STATE_FINISHED);
+ transport_->Start();
+ }
+ } else if (result == net::ERR_CONNECTION_TIMED_OUT) {
+ SetConnectState(proto::CONN_STATE_FINISHED);
+ SetErrorState(CHANNEL_ERROR_CONNECT_TIMEOUT);
+ } else {
+ SetConnectState(proto::CONN_STATE_FINISHED);
+ SetErrorState(CHANNEL_ERROR_AUTHENTICATION_ERROR);
+ }
+ return result;
+}
+
+int CastSocketImpl::DoAuthChallengeSend() {
+ VLOG_WITH_CONNECTION(1) << "DoAuthChallengeSend";
+ SetConnectState(proto::CONN_STATE_AUTH_CHALLENGE_SEND_COMPLETE);
+
+ CastMessage challenge_message;
+ CreateAuthChallengeMessage(&challenge_message);
+ VLOG_WITH_CONNECTION(1) << "Sending challenge: "
+ << CastMessageToString(challenge_message);
+
+ transport_->SendMessage(
+ challenge_message,
+ base::Bind(&CastSocketImpl::DoConnectLoop, base::Unretained(this)));
+
+ // Always return IO_PENDING since the result is always asynchronous.
+ return net::ERR_IO_PENDING;
+}
+
+int CastSocketImpl::DoAuthChallengeSendComplete(int result) {
+ VLOG_WITH_CONNECTION(1) << "DoAuthChallengeSendComplete: " << result;
+ if (result < 0) {
+ SetConnectState(proto::CONN_STATE_ERROR);
+ SetErrorState(CHANNEL_ERROR_SOCKET_ERROR);
+ logger_->LogSocketEventWithRv(channel_id_,
+ proto::SEND_AUTH_CHALLENGE_FAILED, result);
+ return result;
+ }
+ transport_->Start();
+ SetConnectState(proto::CONN_STATE_AUTH_CHALLENGE_REPLY_COMPLETE);
+ return net::ERR_IO_PENDING;
+}
+
+CastSocketImpl::AuthTransportDelegate::AuthTransportDelegate(
+ CastSocketImpl* socket)
+ : socket_(socket), error_state_(CHANNEL_ERROR_NONE) {
+ DCHECK(socket);
+}
+
+ChannelError CastSocketImpl::AuthTransportDelegate::error_state() const {
+ return error_state_;
+}
+
+LastErrors CastSocketImpl::AuthTransportDelegate::last_errors() const {
+ return last_errors_;
+}
+
+void CastSocketImpl::AuthTransportDelegate::OnError(ChannelError error_state) {
+ error_state_ = error_state;
+ socket_->PostTaskToStartConnectLoop(net::ERR_CONNECTION_FAILED);
+}
+
+void CastSocketImpl::AuthTransportDelegate::OnMessage(
+ const CastMessage& message) {
+ if (!IsAuthMessage(message)) {
+ error_state_ = CHANNEL_ERROR_TRANSPORT_ERROR;
+ socket_->logger_->LogSocketEvent(socket_->channel_id_,
+ proto::AUTH_CHALLENGE_REPLY_INVALID);
+ socket_->PostTaskToStartConnectLoop(net::ERR_INVALID_RESPONSE);
+ } else {
+ socket_->challenge_reply_.reset(new CastMessage(message));
+ socket_->logger_->LogSocketEvent(socket_->channel_id_,
+ proto::RECEIVED_CHALLENGE_REPLY);
+ socket_->PostTaskToStartConnectLoop(net::OK);
+ }
+}
+
+void CastSocketImpl::AuthTransportDelegate::Start() {
+}
+
+int CastSocketImpl::DoAuthChallengeReplyComplete(int result) {
+ VLOG_WITH_CONNECTION(1) << "DoAuthChallengeReplyComplete: " << result;
+
+ if (auth_delegate_->error_state() != CHANNEL_ERROR_NONE) {
+ SetErrorState(auth_delegate_->error_state());
+ SetConnectState(proto::CONN_STATE_ERROR);
+ return net::ERR_CONNECTION_FAILED;
+ }
+ auth_delegate_ = nullptr;
+
+ if (result < 0) {
+ SetConnectState(proto::CONN_STATE_ERROR);
+ return result;
+ }
+
+ if (!VerifyChallengeReply()) {
+ SetErrorState(CHANNEL_ERROR_AUTHENTICATION_ERROR);
+ SetConnectState(proto::CONN_STATE_ERROR);
+ return net::ERR_CONNECTION_FAILED;
+ }
+ VLOG_WITH_CONNECTION(1) << "Auth challenge verification succeeded";
+
+ SetConnectState(proto::CONN_STATE_FINISHED);
+ return net::OK;
+}
+
+void CastSocketImpl::DoConnectCallback() {
+ VLOG(1) << "DoConnectCallback (error_state = " << error_state_ << ")";
+ if (connect_callback_.is_null()) {
+ DLOG(FATAL) << "Connection callback invoked multiple times.";
+ return;
+ }
+
+ if (error_state_ == CHANNEL_ERROR_NONE) {
+ SetReadyState(READY_STATE_OPEN);
+ transport_->SetReadDelegate(std::move(delegate_));
+ } else {
+ CloseInternal();
+ }
+
+ base::ResetAndReturn(&connect_callback_).Run(error_state_);
+}
+
+void CastSocketImpl::Close(const net::CompletionCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ CloseInternal();
+ // Run this callback last. It may delete the socket.
+ callback.Run(net::OK);
+}
+
+void CastSocketImpl::CloseInternal() {
+ // TODO(mfoltz): Enforce this when CastChannelAPITest is rewritten to create
+ // and free sockets on the same thread. crbug.com/398242
+ DCHECK(CalledOnValidThread());
+ if (ready_state_ == READY_STATE_CLOSED) {
+ return;
+ }
+
+ VLOG_WITH_CONNECTION(1) << "Close ReadyState = " << ready_state_;
+ transport_.reset();
+ tcp_socket_.reset();
+ socket_.reset();
+ transport_security_state_.reset();
+ if (GetTimer()) {
+ GetTimer()->Stop();
+ }
+
+ // Cancel callbacks that we queued ourselves to re-enter the connect or read
+ // loops.
+ connect_loop_callback_.Cancel();
+ send_auth_challenge_callback_.Cancel();
+ connect_timeout_callback_.Cancel();
+ SetReadyState(READY_STATE_CLOSED);
+ logger_->LogSocketEvent(channel_id_, proto::SOCKET_CLOSED);
+}
+
+bool CastSocketImpl::CalledOnValidThread() const {
+ return thread_checker_.CalledOnValidThread();
+}
+
+base::Timer* CastSocketImpl::GetTimer() {
+ return connect_timeout_timer_.get();
+}
+
+void CastSocketImpl::SetConnectState(proto::ConnectionState connect_state) {
+ if (connect_state_ != connect_state) {
+ connect_state_ = connect_state;
+ logger_->LogSocketConnectState(channel_id_, connect_state_);
+ }
+}
+
+void CastSocketImpl::SetReadyState(ReadyState ready_state) {
+ if (ready_state_ != ready_state) {
+ ready_state_ = ready_state;
+ logger_->LogSocketReadyState(channel_id_, ReadyStateToProto(ready_state_));
+ }
+}
+
+void CastSocketImpl::SetErrorState(ChannelError error_state) {
+ VLOG_WITH_CONNECTION(1) << "SetErrorState " << error_state;
+ DCHECK_EQ(CHANNEL_ERROR_NONE, error_state_);
+ error_state_ = error_state;
+ logger_->LogSocketErrorState(channel_id_, ErrorStateToProto(error_state_));
+ delegate_->OnError(error_state_);
+}
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
+#undef VLOG_WITH_CONNECTION
diff --git a/chromium/extensions/browser/api/cast_channel/cast_socket.h b/chromium/extensions/browser/api/cast_channel/cast_socket.h
new file mode 100644
index 00000000000..35edd4d84f6
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_socket.h
@@ -0,0 +1,385 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_SOCKET_H_
+#define EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_SOCKET_H_
+
+#include <stdint.h>
+
+#include <queue>
+#include <string>
+
+#include "base/cancelable_callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread_checker.h"
+#include "base/timer/timer.h"
+#include "extensions/browser/api/api_resource.h"
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/browser/api/cast_channel/cast_socket.h"
+#include "extensions/browser/api/cast_channel/cast_transport.h"
+#include "extensions/browser/api/cast_channel/logger_util.h"
+#include "extensions/common/api/cast_channel.h"
+#include "extensions/common/api/cast_channel/logging.pb.h"
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/log/net_log.h"
+
+namespace net {
+class AddressList;
+class CertVerifier;
+class SSLClientSocket;
+class StreamSocket;
+class TCPClientSocket;
+class TransportSecurityState;
+class X509Certificate;
+}
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+class CastMessage;
+class Logger;
+struct LastErrors;
+class MessageFramer;
+
+// Cast device capabilities.
+enum CastDeviceCapability {
+ NONE = 0,
+ VIDEO_OUT = 1 << 0,
+ VIDEO_IN = 1 << 1,
+ AUDIO_OUT = 1 << 2,
+ AUDIO_IN = 1 << 3,
+ DEV_MODE = 1 << 4
+};
+
+// Public interface of the CastSocket class.
+class CastSocket : public ApiResource {
+ public:
+ explicit CastSocket(const std::string& owner_extension_id);
+ ~CastSocket() override {}
+
+ // Used by BrowserContextKeyedAPIFactory.
+ static const char* service_name() { return "CastSocketImplManager"; }
+
+ // Connects the channel to the peer. If successful, the channel will be in
+ // READY_STATE_OPEN. DO NOT delete the CastSocket object in |callback|.
+ // Instead use Close().
+ // |callback| will be invoked with any ChannelError that occurred, or
+ // CHANNEL_ERROR_NONE if successful.
+ // |delegate| receives message receipt and error events.
+ // Ownership of |delegate| is transferred to this CastSocket.
+ virtual void Connect(scoped_ptr<CastTransport::Delegate> delegate,
+ base::Callback<void(ChannelError)> callback) = 0;
+
+ // Closes the channel if not already closed. On completion, the channel will
+ // be in READY_STATE_CLOSED.
+ //
+ // It is fine to delete this object in |callback|.
+ virtual void Close(const net::CompletionCallback& callback) = 0;
+
+ // The IP endpoint for the destination of the channel.
+ virtual const net::IPEndPoint& ip_endpoint() const = 0;
+
+ // Channel id generated by the ApiResourceManager.
+ virtual int id() const = 0;
+
+ // Sets the channel id generated by ApiResourceManager.
+ virtual void set_id(int id) = 0;
+
+ // The authentication level requested for the channel.
+ virtual ChannelAuthType channel_auth() const = 0;
+
+ // The ready state of the channel.
+ virtual ReadyState ready_state() const = 0;
+
+ // Returns the last error that occurred on this channel, or
+ // CHANNEL_ERROR_NONE if no error has occurred.
+ virtual ChannelError error_state() const = 0;
+
+ // True when keep-alive signaling is handled for this socket.
+ virtual bool keep_alive() const = 0;
+
+ // Whether the channel is audio only as identified by the device
+ // certificate during channel authentication.
+ virtual bool audio_only() const = 0;
+
+ // Marks a socket as invalid due to an error, and sends an OnError
+ // event to |delegate_|.
+ // The OnError event receipient is responsible for closing the socket in the
+ // event of an error.
+ // Setting the error state does not close the socket if it is open.
+ virtual void SetErrorState(ChannelError error_state) = 0;
+
+ // Returns a pointer to the socket's message transport layer. Can be used to
+ // send and receive CastMessages over the socket.
+ virtual CastTransport* transport() const = 0;
+
+ // Tells the ApiResourceManager to retain CastSocket objects even
+ // if their corresponding extension is suspended.
+ // (CastSockets are still deleted if the extension is removed entirely from
+ // the browser.)
+ bool IsPersistent() const override;
+};
+
+// This class implements a channel between Chrome and a Cast device using a TCP
+// socket with SSL. The channel may authenticate that the receiver is a genuine
+// Cast device. All CastSocketImpl objects must be used only on the IO thread.
+//
+// NOTE: Not called "CastChannel" to reduce confusion with the generated API
+// code.
+class CastSocketImpl : public CastSocket {
+ public:
+ // Creates a new CastSocket that connects to |ip_endpoint| with
+ // |channel_auth|. |owner_extension_id| is the id of the extension that opened
+ // the socket. |channel_auth| must not be CHANNEL_AUTH_NONE.
+ // Parameters:
+ // |owner_extension_id|: ID of the extension calling the API.
+ // |ip_endpoint|: IP address of the remote host.
+ // |channel_auth|: Authentication method used for connecting to a Cast
+ // receiver.
+ // |net_log|: Log of socket events.
+ // |connect_timeout|: Connection timeout interval.
+ // |logger|: Log of cast channel events.
+ CastSocketImpl(const std::string& owner_extension_id,
+ const net::IPEndPoint& ip_endpoint,
+ ChannelAuthType channel_auth,
+ net::NetLog* net_log,
+ const base::TimeDelta& connect_timeout,
+ bool keep_alive,
+ const scoped_refptr<Logger>& logger,
+ uint64_t device_capabilities);
+
+ // Ensures that the socket is closed.
+ ~CastSocketImpl() override;
+
+ // CastSocket interface.
+ void Connect(scoped_ptr<CastTransport::Delegate> delegate,
+ base::Callback<void(ChannelError)> callback) override;
+ CastTransport* transport() const override;
+ void Close(const net::CompletionCallback& callback) override;
+ const net::IPEndPoint& ip_endpoint() const override;
+ int id() const override;
+ void set_id(int channel_id) override;
+ ChannelAuthType channel_auth() const override;
+ ReadyState ready_state() const override;
+ ChannelError error_state() const override;
+ bool keep_alive() const override;
+ bool audio_only() const override;
+
+ // Required by ApiResourceManager.
+ static const char* service_name() { return "CastSocketManager"; }
+
+ protected:
+ // CastTransport::Delegate methods for receiving handshake messages.
+ class AuthTransportDelegate : public CastTransport::Delegate {
+ public:
+ explicit AuthTransportDelegate(CastSocketImpl* socket);
+
+ // Gets the error state of the channel.
+ // Returns CHANNEL_ERROR_NONE if no errors are present.
+ ChannelError error_state() const;
+
+ // Gets recorded error details.
+ LastErrors last_errors() const;
+
+ // CastTransport::Delegate interface.
+ void OnError(ChannelError error_state) override;
+ void OnMessage(const CastMessage& message) override;
+ void Start() override;
+
+ private:
+ CastSocketImpl* socket_;
+ ChannelError error_state_;
+ LastErrors last_errors_;
+ };
+
+ // Replaces the internally-constructed transport object with one provided
+ // by the caller (e.g. a mock).
+ void SetTransportForTesting(scoped_ptr<CastTransport> transport);
+
+ // Verifies whether the socket complies with cast channel policy.
+ // Audio only channel policy mandates that a device declaring a video out
+ // capability must not have a certificate with audio only policy.
+ bool VerifyChannelPolicy(const AuthResult& result);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(CastSocketTest, TestConnectAuthMessageCorrupted);
+ FRIEND_TEST_ALL_PREFIXES(CastSocketTest,
+ TestConnectChallengeReplyReceiveError);
+ FRIEND_TEST_ALL_PREFIXES(CastSocketTest,
+ TestConnectChallengeVerificationFails);
+ friend class AuthTransportDelegate;
+ friend class ApiResourceManager<CastSocketImpl>;
+ friend class CastSocketTest;
+ friend class TestCastSocket;
+
+ void SetErrorState(ChannelError error_state) override;
+
+ // Frees resources and cancels pending callbacks. |ready_state_| will be set
+ // READY_STATE_CLOSED on completion. A no-op if |ready_state_| is already
+ // READY_STATE_CLOSED.
+ void CloseInternal();
+
+ // Creates an instance of TCPClientSocket.
+ virtual scoped_ptr<net::TCPClientSocket> CreateTcpSocket();
+ // Creates an instance of SSLClientSocket with the given underlying |socket|.
+ virtual scoped_ptr<net::SSLClientSocket> CreateSslSocket(
+ scoped_ptr<net::StreamSocket> socket);
+ // Extracts peer certificate from SSLClientSocket instance when the socket
+ // is in cert error state.
+ // Returns null if the certificate could not be extracted.
+ // TODO(kmarshall): Use MockSSLClientSocket for tests instead of overriding
+ // this function.
+ virtual scoped_refptr<net::X509Certificate> ExtractPeerCert();
+ // Verifies whether the challenge reply received from the peer is valid:
+ // 1. Signature in the reply is valid.
+ // 2. Certificate is rooted to a trusted CA.
+ virtual bool VerifyChallengeReply();
+
+ // Invoked by a cancelable closure when connection setup time
+ // exceeds the interval specified at |connect_timeout|.
+ void OnConnectTimeout();
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Following methods work together to implement the following flow:
+ // 1. Create a new TCP socket and connect to it
+ // 2. Create a new SSL socket and try connecting to it
+ // 3. If connection fails due to invalid cert authority, then extract the
+ // peer certificate from the error.
+ // 4. Whitelist the peer certificate and try #1 and #2 again.
+ // 5. If SSL socket is connected successfully, and if protocol is casts://
+ // then issue an auth challenge request.
+ // 6. Validate the auth challenge response.
+ //
+ // Main method that performs connection state transitions.
+ void DoConnectLoop(int result);
+ // Each of the below Do* method is executed in the corresponding
+ // connection state. For example when connection state is TCP_CONNECT
+ // DoTcpConnect is called, and so on.
+ int DoTcpConnect();
+ int DoTcpConnectComplete(int result);
+ int DoSslConnect();
+ int DoSslConnectComplete(int result);
+ int DoAuthChallengeSend();
+ int DoAuthChallengeSendComplete(int result);
+ int DoAuthChallengeReplyComplete(int result);
+ /////////////////////////////////////////////////////////////////////////////
+
+ // Schedules asynchrous connection loop processing in the MessageLoop.
+ void PostTaskToStartConnectLoop(int result);
+
+ // Runs the external connection callback and resets it.
+ void DoConnectCallback();
+
+ virtual bool CalledOnValidThread() const;
+
+ virtual base::Timer* GetTimer();
+
+ void SetConnectState(proto::ConnectionState connect_state);
+ void SetReadyState(ReadyState ready_state);
+
+ base::ThreadChecker thread_checker_;
+
+ const std::string owner_extension_id_;
+ // The id of the channel.
+ int channel_id_;
+ // The IP endpoint that the the channel is connected to.
+ net::IPEndPoint ip_endpoint_;
+ // Receiver authentication requested for the channel.
+ ChannelAuthType channel_auth_;
+ // The NetLog for this service.
+ net::NetLog* net_log_;
+ // The NetLog source for this service.
+ net::NetLog::Source net_log_source_;
+ // True when keep-alive signaling should be handled for this socket.
+ bool keep_alive_;
+
+ // Shared logging object, used to log CastSocket events for diagnostics.
+ scoped_refptr<Logger> logger_;
+
+ // CertVerifier is owned by us but should be deleted AFTER SSLClientSocket
+ // since in some cases the destructor of SSLClientSocket may call a method
+ // to cancel a cert verification request.
+ scoped_ptr<net::CertVerifier> cert_verifier_;
+ scoped_ptr<net::TransportSecurityState> transport_security_state_;
+
+ // Owned ptr to the underlying TCP socket.
+ scoped_ptr<net::TCPClientSocket> tcp_socket_;
+
+ // Owned ptr to the underlying SSL socket.
+ scoped_ptr<net::SSLClientSocket> socket_;
+
+ // Certificate of the peer. This field may be empty if the peer
+ // certificate is not yet fetched.
+ scoped_refptr<net::X509Certificate> peer_cert_;
+
+ // Reply received from the receiver to a challenge request.
+ scoped_ptr<CastMessage> challenge_reply_;
+
+ // Callback invoked when the socket is connected or fails to connect.
+ base::Callback<void(ChannelError)> connect_callback_;
+
+ // Callback invoked by |connect_timeout_timer_| to cancel the connection.
+ base::CancelableClosure connect_timeout_callback_;
+
+ // Duration to wait before timing out.
+ base::TimeDelta connect_timeout_;
+
+ // Timer invoked when the connection has timed out.
+ scoped_ptr<base::Timer> connect_timeout_timer_;
+
+ // Set when a timeout is triggered and the connection process has
+ // canceled.
+ bool is_canceled_;
+
+ // Capabilities declared by the cast device.
+ uint64_t device_capabilities_;
+
+ // Whether the channel is audio only as identified by the device
+ // certificate during channel authentication.
+ bool audio_only_;
+
+ // Connection flow state machine state.
+ proto::ConnectionState connect_state_;
+
+ // Write flow state machine state.
+ proto::WriteState write_state_;
+
+ // Read flow state machine state.
+ proto::ReadState read_state_;
+
+ // The last error encountered by the channel.
+ ChannelError error_state_;
+
+ // The current status of the channel.
+ ReadyState ready_state_;
+
+ // Task invoked to (re)start the connect loop. Canceled on entry to the
+ // connect loop.
+ base::CancelableClosure connect_loop_callback_;
+
+ // Task invoked to send the auth challenge. Canceled when the auth challenge
+ // has been sent.
+ base::CancelableClosure send_auth_challenge_callback_;
+
+ // Cast message formatting and parsing layer.
+ scoped_ptr<CastTransport> transport_;
+
+ // Caller's message read and error handling delegate.
+ scoped_ptr<CastTransport::Delegate> delegate_;
+
+ // Raw pointer to the auth handshake delegate. Used to get detailed error
+ // information.
+ AuthTransportDelegate* auth_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastSocketImpl);
+};
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_SOCKET_H_
diff --git a/chromium/extensions/browser/api/cast_channel/cast_socket_unittest.cc b/chromium/extensions/browser/api/cast_channel/cast_socket_unittest.cc
new file mode 100644
index 00000000000..dbc46dcef32
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_socket_unittest.cc
@@ -0,0 +1,888 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/cast_channel/cast_socket.h"
+
+#include <stdint.h>
+
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/sys_byteorder.h"
+#include "base/test/simple_test_clock.h"
+#include "base/timer/mock_timer.h"
+#include "extensions/browser/api/cast_channel/cast_auth_util.h"
+#include "extensions/browser/api/cast_channel/cast_framer.h"
+#include "extensions/browser/api/cast_channel/cast_message_util.h"
+#include "extensions/browser/api/cast_channel/cast_test_util.h"
+#include "extensions/browser/api/cast_channel/cast_transport.h"
+#include "extensions/browser/api/cast_channel/logger.h"
+#include "extensions/common/api/cast_channel/cast_channel.pb.h"
+#include "net/base/address_list.h"
+#include "net/base/net_errors.h"
+#include "net/log/test_net_log.h"
+#include "net/socket/socket_test_util.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/tcp_client_socket.h"
+#include "net/ssl/ssl_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+const int64_t kDistantTimeoutMillis = 100000; // 100 seconds (never hit).
+
+using ::testing::_;
+using ::testing::A;
+using ::testing::DoAll;
+using ::testing::Invoke;
+using ::testing::InvokeArgument;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::SaveArg;
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+const char kAuthNamespace[] = "urn:x-cast:com.google.cast.tp.deviceauth";
+
+// Returns an auth challenge message inline.
+CastMessage CreateAuthChallenge() {
+ CastMessage output;
+ CreateAuthChallengeMessage(&output);
+ return output;
+}
+
+// Returns an auth challenge response message inline.
+CastMessage CreateAuthReply() {
+ CastMessage output;
+ output.set_protocol_version(CastMessage::CASTV2_1_0);
+ output.set_source_id("sender-0");
+ output.set_destination_id("receiver-0");
+ output.set_payload_type(CastMessage::BINARY);
+ output.set_payload_binary("abcd");
+ output.set_namespace_(kAuthNamespace);
+ return output;
+}
+
+CastMessage CreateTestMessage() {
+ CastMessage test_message;
+ test_message.set_protocol_version(CastMessage::CASTV2_1_0);
+ test_message.set_namespace_("ns");
+ test_message.set_source_id("source");
+ test_message.set_destination_id("dest");
+ test_message.set_payload_type(CastMessage::STRING);
+ test_message.set_payload_utf8("payload");
+ return test_message;
+}
+
+class MockTCPSocket : public net::TCPClientSocket {
+ public:
+ explicit MockTCPSocket(const net::MockConnect& connect_data)
+ : TCPClientSocket(net::AddressList(), nullptr, net::NetLog::Source()),
+ connect_data_(connect_data),
+ do_nothing_(false) {}
+
+ explicit MockTCPSocket(bool do_nothing)
+ : TCPClientSocket(net::AddressList(), nullptr, net::NetLog::Source()) {
+ CHECK(do_nothing);
+ do_nothing_ = do_nothing;
+ }
+
+ virtual int Connect(const net::CompletionCallback& callback) {
+ if (do_nothing_) {
+ // Stall the I/O event loop.
+ return net::ERR_IO_PENDING;
+ }
+
+ if (connect_data_.mode == net::ASYNC) {
+ CHECK_NE(connect_data_.result, net::ERR_IO_PENDING);
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, connect_data_.result));
+ return net::ERR_IO_PENDING;
+ } else {
+ return connect_data_.result;
+ }
+ }
+
+ virtual bool SetKeepAlive(bool enable, int delay) {
+ // Always return true in tests
+ return true;
+ }
+
+ virtual bool SetNoDelay(bool no_delay) {
+ // Always return true in tests
+ return true;
+ }
+
+ MOCK_METHOD3(Read,
+ int(net::IOBuffer*, int, const net::CompletionCallback&));
+ MOCK_METHOD3(Write,
+ int(net::IOBuffer*, int, const net::CompletionCallback&));
+
+ virtual void Disconnect() {
+ // Do nothing in tests
+ }
+
+ private:
+ net::MockConnect connect_data_;
+ bool do_nothing_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockTCPSocket);
+};
+
+class MockDelegate : public CastTransport::Delegate {
+ public:
+ MockDelegate() {}
+ virtual ~MockDelegate() {}
+ MOCK_METHOD1(OnError, void(ChannelError error_state));
+ MOCK_METHOD1(OnMessage, void(const CastMessage& message));
+ MOCK_METHOD0(Start, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockDelegate);
+};
+
+class CompleteHandler {
+ public:
+ CompleteHandler() {}
+ MOCK_METHOD1(OnCloseComplete, void(int result));
+ MOCK_METHOD1(OnConnectComplete, void(ChannelError error_state));
+ MOCK_METHOD1(OnWriteComplete, void(int result));
+ MOCK_METHOD1(OnReadComplete, void(int result));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CompleteHandler);
+};
+
+class TestCastSocket : public CastSocketImpl {
+ public:
+ static scoped_ptr<TestCastSocket> Create(
+ Logger* logger,
+ uint64_t device_capabilities = cast_channel::CastDeviceCapability::NONE) {
+ return scoped_ptr<TestCastSocket>(
+ new TestCastSocket(CreateIPEndPointForTest(), CHANNEL_AUTH_TYPE_SSL,
+ kDistantTimeoutMillis, logger, device_capabilities));
+ }
+
+ static scoped_ptr<TestCastSocket> CreateSecure(
+ Logger* logger,
+ uint64_t device_capabilities = cast_channel::CastDeviceCapability::NONE) {
+ return scoped_ptr<TestCastSocket>(new TestCastSocket(
+ CreateIPEndPointForTest(), CHANNEL_AUTH_TYPE_SSL_VERIFIED,
+ kDistantTimeoutMillis, logger, device_capabilities));
+ }
+
+ explicit TestCastSocket(const net::IPEndPoint& ip_endpoint,
+ ChannelAuthType channel_auth,
+ int64_t timeout_ms,
+ Logger* logger,
+ uint64_t device_capabilities)
+ : CastSocketImpl("some_extension_id",
+ ip_endpoint,
+ channel_auth,
+ &capturing_net_log_,
+ base::TimeDelta::FromMilliseconds(timeout_ms),
+ false,
+ logger,
+ device_capabilities),
+ ip_(ip_endpoint),
+ extract_cert_result_(true),
+ verify_challenge_result_(true),
+ verify_challenge_disallow_(false),
+ tcp_unresponsive_(false),
+ mock_timer_(new base::MockTimer(false, false)),
+ mock_transport_(nullptr) {}
+
+ ~TestCastSocket() override {}
+
+ void SetupMockTransport() {
+ mock_transport_ = new MockCastTransport;
+ SetTransportForTesting(make_scoped_ptr(mock_transport_));
+ }
+
+ // Socket connection helpers.
+ void SetupTcpConnect(net::IoMode mode, int result) {
+ tcp_connect_data_.reset(new net::MockConnect(mode, result));
+ }
+ void SetupSslConnect(net::IoMode mode, int result) {
+ ssl_connect_data_.reset(new net::MockConnect(mode, result));
+ }
+
+ // Socket I/O helpers.
+ void AddWriteResult(const net::MockWrite& write) {
+ writes_.push_back(write);
+ }
+ void AddWriteResult(net::IoMode mode, int result) {
+ AddWriteResult(net::MockWrite(mode, result));
+ }
+ void AddWriteResultForData(net::IoMode mode, const std::string& msg) {
+ AddWriteResult(mode, msg.size());
+ }
+ void AddReadResult(const net::MockRead& read) {
+ reads_.push_back(read);
+ }
+ void AddReadResult(net::IoMode mode, int result) {
+ AddReadResult(net::MockRead(mode, result));
+ }
+ void AddReadResultForData(net::IoMode mode, const std::string& data) {
+ AddReadResult(net::MockRead(mode, data.c_str(), data.size()));
+ }
+
+ // Helpers for modifying other connection-related behaviors.
+ void SetupTcpConnectUnresponsive() { tcp_unresponsive_ = true; }
+
+ void SetExtractCertResult(bool value) {
+ extract_cert_result_ = value;
+ }
+
+ void SetVerifyChallengeResult(bool value) {
+ verify_challenge_result_ = value;
+ }
+
+ void TriggerTimeout() {
+ mock_timer_->Fire();
+ }
+
+ bool TestVerifyChannelPolicyNone() {
+ AuthResult authResult;
+ return VerifyChannelPolicy(authResult);
+ }
+
+ bool TestVerifyChannelPolicyAudioOnly() {
+ AuthResult authResult;
+ authResult.channel_policies |= AuthResult::POLICY_AUDIO_ONLY;
+ return VerifyChannelPolicy(authResult);
+ }
+
+ void DisallowVerifyChallengeResult() { verify_challenge_disallow_ = true; }
+
+ MockCastTransport* GetMockTransport() {
+ CHECK(mock_transport_);
+ return mock_transport_;
+ }
+
+ private:
+ scoped_ptr<net::TCPClientSocket> CreateTcpSocket() override {
+ if (tcp_unresponsive_) {
+ return scoped_ptr<net::TCPClientSocket>(new MockTCPSocket(true));
+ } else {
+ net::MockConnect* connect_data = tcp_connect_data_.get();
+ connect_data->peer_addr = ip_;
+ return scoped_ptr<net::TCPClientSocket>(new MockTCPSocket(*connect_data));
+ }
+ }
+
+ scoped_ptr<net::SSLClientSocket> CreateSslSocket(
+ scoped_ptr<net::StreamSocket> socket) override {
+ net::MockConnect* connect_data = ssl_connect_data_.get();
+ connect_data->peer_addr = ip_;
+
+ ssl_data_.reset(new net::StaticSocketDataProvider(
+ reads_.data(), reads_.size(), writes_.data(), writes_.size()));
+ ssl_data_->set_connect_data(*connect_data);
+ // NOTE: net::MockTCPClientSocket inherits from net::SSLClientSocket !!
+ return scoped_ptr<net::SSLClientSocket>(
+ new net::MockTCPClientSocket(
+ net::AddressList(), &capturing_net_log_, ssl_data_.get()));
+ }
+
+ scoped_refptr<net::X509Certificate> ExtractPeerCert() override {
+ return extract_cert_result_ ? make_scoped_refptr<net::X509Certificate>(
+ new net::X509Certificate(
+ "", "", base::Time(), base::Time()))
+ : nullptr;
+ }
+
+ bool VerifyChallengeReply() override {
+ EXPECT_FALSE(verify_challenge_disallow_);
+ return verify_challenge_result_;
+ }
+
+ base::Timer* GetTimer() override { return mock_timer_.get(); }
+
+ net::TestNetLog capturing_net_log_;
+ net::IPEndPoint ip_;
+ // Simulated connect data
+ scoped_ptr<net::MockConnect> tcp_connect_data_;
+ scoped_ptr<net::MockConnect> ssl_connect_data_;
+ // Simulated read / write data
+ std::vector<net::MockWrite> writes_;
+ std::vector<net::MockRead> reads_;
+ scoped_ptr<net::SocketDataProvider> ssl_data_;
+ // Simulated result of peer cert extraction.
+ bool extract_cert_result_;
+ // Simulated result of verifying challenge reply.
+ bool verify_challenge_result_;
+ bool verify_challenge_disallow_;
+ // If true, makes TCP connection process stall. For timeout testing.
+ bool tcp_unresponsive_;
+ scoped_ptr<base::MockTimer> mock_timer_;
+ MockCastTransport* mock_transport_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestCastSocket);
+};
+
+class CastSocketTest : public testing::Test {
+ public:
+ CastSocketTest()
+ : logger_(
+ new Logger(make_scoped_ptr<base::Clock>(new base::SimpleTestClock),
+ base::Time())),
+ delegate_(new MockDelegate) {}
+ ~CastSocketTest() override {}
+
+ void SetUp() override { EXPECT_CALL(*delegate_, OnMessage(_)).Times(0); }
+
+ void TearDown() override {
+ if (socket_.get()) {
+ EXPECT_CALL(handler_, OnCloseComplete(net::OK));
+ socket_->Close(base::Bind(&CompleteHandler::OnCloseComplete,
+ base::Unretained(&handler_)));
+ }
+ }
+
+ void CreateCastSocket() { socket_ = TestCastSocket::Create(logger_); }
+
+ void CreateCastSocketSecure() {
+ socket_ = TestCastSocket::CreateSecure(logger_);
+ }
+
+ void HandleAuthHandshake() {
+ socket_->SetupMockTransport();
+ CastMessage challenge_proto = CreateAuthChallenge();
+ EXPECT_CALL(*socket_->GetMockTransport(),
+ SendMessage(EqualsProto(challenge_proto), _))
+ .WillOnce(PostCompletionCallbackTask<1>(net::OK));
+ EXPECT_CALL(*socket_->GetMockTransport(), Start());
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_NONE));
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+ socket_->GetMockTransport()->current_delegate()->OnMessage(
+ CreateAuthReply());
+ RunPendingTasks();
+ }
+
+ protected:
+ // Runs all pending tasks in the message loop.
+ void RunPendingTasks() {
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ }
+
+ base::MessageLoop message_loop_;
+ Logger* logger_;
+ scoped_ptr<TestCastSocket> socket_;
+ CompleteHandler handler_;
+ scoped_ptr<MockDelegate> delegate_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CastSocketTest);
+};
+
+// Tests connecting and closing the socket.
+TEST_F(CastSocketTest, TestConnectAndClose) {
+ CreateCastSocket();
+ socket_->SetupMockTransport();
+ socket_->SetupTcpConnect(net::SYNCHRONOUS, net::OK);
+ socket_->SetupSslConnect(net::SYNCHRONOUS, net::OK);
+
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_NONE));
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_OPEN, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, socket_->error_state());
+
+ EXPECT_CALL(handler_, OnCloseComplete(net::OK));
+ socket_->Close(base::Bind(&CompleteHandler::OnCloseComplete,
+ base::Unretained(&handler_)));
+ EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, socket_->error_state());
+}
+
+// Tests that the following connection flow works:
+// - TCP connection succeeds (async)
+// - SSL connection succeeds (async)
+TEST_F(CastSocketTest, TestConnect) {
+ CreateCastSocket();
+ socket_->SetupTcpConnect(net::ASYNC, net::OK);
+ socket_->SetupSslConnect(net::ASYNC, net::OK);
+ socket_->AddReadResult(net::ASYNC, net::ERR_IO_PENDING);
+
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_NONE));
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_OPEN, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, socket_->error_state());
+}
+
+// Tests that the following connection flow works:
+// - TCP connection fails (async)
+TEST_F(CastSocketTest, TestConnectFails) {
+ CreateCastSocket();
+ socket_->SetupTcpConnect(net::ASYNC, net::ERR_FAILED);
+
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_CONNECT_ERROR));
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_CONNECT_ERROR, socket_->error_state());
+ EXPECT_EQ(proto::TCP_SOCKET_CONNECT_COMPLETE,
+ logger_->GetLastErrors(socket_->id()).event_type);
+ EXPECT_EQ(net::ERR_FAILED,
+ logger_->GetLastErrors(socket_->id()).net_return_value);
+}
+
+// Tests that the following connection flow works:
+// - TCP connection succeeds (async)
+// - SSL connection succeeds (async)
+// - Cert is extracted successfully
+// - Challenge request is sent (async)
+// - Challenge response is received (async)
+// - Credentials are verified successfuly
+TEST_F(CastSocketTest, TestConnectFullSecureFlowAsync) {
+ CreateCastSocketSecure();
+ socket_->SetupTcpConnect(net::ASYNC, net::OK);
+ socket_->SetupSslConnect(net::ASYNC, net::OK);
+
+ HandleAuthHandshake();
+
+ EXPECT_EQ(cast_channel::READY_STATE_OPEN, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, socket_->error_state());
+}
+
+// Tests that the following connection flow works:
+// - TCP connection succeeds (sync)
+// - SSL connection succeeds (sync)
+// - Cert is extracted successfully
+// - Challenge request is sent (sync)
+// - Challenge response is received (sync)
+// - Credentials are verified successfuly
+TEST_F(CastSocketTest, TestConnectFullSecureFlowSync) {
+ CreateCastSocketSecure();
+ socket_->SetupTcpConnect(net::SYNCHRONOUS, net::OK);
+ socket_->SetupSslConnect(net::SYNCHRONOUS, net::OK);
+
+ HandleAuthHandshake();
+
+ EXPECT_EQ(cast_channel::READY_STATE_OPEN, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, socket_->error_state());
+}
+
+// Test that an AuthMessage with a mangled namespace triggers cancelation
+// of the connection event loop.
+TEST_F(CastSocketTest, TestConnectAuthMessageCorrupted) {
+ CreateCastSocketSecure();
+ socket_->SetupMockTransport();
+
+ socket_->SetupTcpConnect(net::ASYNC, net::OK);
+ socket_->SetupSslConnect(net::ASYNC, net::OK);
+
+ CastMessage challenge_proto = CreateAuthChallenge();
+ EXPECT_CALL(*socket_->GetMockTransport(),
+ SendMessage(EqualsProto(challenge_proto), _))
+ .WillOnce(PostCompletionCallbackTask<1>(net::OK));
+ EXPECT_CALL(*socket_->GetMockTransport(), Start());
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_TRANSPORT_ERROR));
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+ CastMessage mangled_auth_reply = CreateAuthReply();
+ mangled_auth_reply.set_namespace_("BOGUS_NAMESPACE");
+
+ socket_->GetMockTransport()->current_delegate()->OnMessage(
+ mangled_auth_reply);
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_TRANSPORT_ERROR,
+ socket_->error_state());
+
+ // Verifies that the CastSocket's resources were torn down during channel
+ // close. (see http://crbug.com/504078)
+ EXPECT_EQ(nullptr, socket_->transport());
+}
+
+// Test connection error - TCP connect fails (async)
+TEST_F(CastSocketTest, TestConnectTcpConnectErrorAsync) {
+ CreateCastSocketSecure();
+
+ socket_->SetupTcpConnect(net::ASYNC, net::ERR_FAILED);
+
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_CONNECT_ERROR));
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_CONNECT_ERROR, socket_->error_state());
+}
+
+// Test connection error - TCP connect fails (sync)
+TEST_F(CastSocketTest, TestConnectTcpConnectErrorSync) {
+ CreateCastSocketSecure();
+
+ socket_->SetupTcpConnect(net::SYNCHRONOUS, net::ERR_FAILED);
+
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_CONNECT_ERROR));
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_CONNECT_ERROR, socket_->error_state());
+}
+
+// Test connection error - timeout
+TEST_F(CastSocketTest, TestConnectTcpTimeoutError) {
+ CreateCastSocketSecure();
+ socket_->SetupTcpConnectUnresponsive();
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_CONNECT_TIMEOUT));
+ EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_CONNECT_TIMEOUT));
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_CONNECTING, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, socket_->error_state());
+ socket_->TriggerTimeout();
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_CONNECT_TIMEOUT,
+ socket_->error_state());
+}
+
+// Test connection error - TCP socket returns timeout
+TEST_F(CastSocketTest, TestConnectTcpSocketTimeoutError) {
+ CreateCastSocketSecure();
+ socket_->SetupTcpConnect(net::SYNCHRONOUS, net::ERR_CONNECTION_TIMED_OUT);
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_CONNECT_TIMEOUT));
+ EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_CONNECT_TIMEOUT));
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_CONNECT_TIMEOUT,
+ socket_->error_state());
+ EXPECT_EQ(net::ERR_CONNECTION_TIMED_OUT,
+ logger_->GetLastErrors(socket_->id()).net_return_value);
+}
+
+// Test connection error - SSL connect fails (async)
+TEST_F(CastSocketTest, TestConnectSslConnectErrorAsync) {
+ CreateCastSocketSecure();
+
+ socket_->SetupTcpConnect(net::SYNCHRONOUS, net::OK);
+ socket_->SetupSslConnect(net::SYNCHRONOUS, net::ERR_FAILED);
+
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_AUTHENTICATION_ERROR));
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_AUTHENTICATION_ERROR,
+ socket_->error_state());
+}
+
+// Test connection error - SSL connect fails (sync)
+TEST_F(CastSocketTest, TestConnectSslConnectErrorSync) {
+ CreateCastSocketSecure();
+
+ socket_->SetupTcpConnect(net::SYNCHRONOUS, net::OK);
+ socket_->SetupSslConnect(net::SYNCHRONOUS, net::ERR_FAILED);
+
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_AUTHENTICATION_ERROR));
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_AUTHENTICATION_ERROR,
+ socket_->error_state());
+ EXPECT_EQ(net::ERR_FAILED,
+ logger_->GetLastErrors(socket_->id()).net_return_value);
+}
+
+// Test connection error - SSL connect times out (sync)
+TEST_F(CastSocketTest, TestConnectSslConnectTimeoutSync) {
+ CreateCastSocketSecure();
+
+ socket_->SetupTcpConnect(net::SYNCHRONOUS, net::OK);
+ socket_->SetupSslConnect(net::SYNCHRONOUS, net::ERR_CONNECTION_TIMED_OUT);
+
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_CONNECT_TIMEOUT));
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_CONNECT_TIMEOUT,
+ socket_->error_state());
+ EXPECT_EQ(net::ERR_CONNECTION_TIMED_OUT,
+ logger_->GetLastErrors(socket_->id()).net_return_value);
+}
+
+// Test connection error - SSL connect times out (async)
+TEST_F(CastSocketTest, TestConnectSslConnectTimeoutAsync) {
+ CreateCastSocketSecure();
+
+ socket_->SetupTcpConnect(net::ASYNC, net::OK);
+ socket_->SetupSslConnect(net::ASYNC, net::ERR_CONNECTION_TIMED_OUT);
+
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_CONNECT_TIMEOUT));
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_CONNECT_TIMEOUT,
+ socket_->error_state());
+}
+
+// Test connection error - cert extraction error (async)
+TEST_F(CastSocketTest, TestConnectCertExtractionErrorAsync) {
+ CreateCastSocket();
+ socket_->SetupTcpConnect(net::ASYNC, net::OK);
+ socket_->SetupSslConnect(net::ASYNC, net::OK);
+ // Set cert extraction to fail
+ socket_->SetExtractCertResult(false);
+
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_AUTHENTICATION_ERROR));
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_AUTHENTICATION_ERROR,
+ socket_->error_state());
+}
+
+// Test connection error - cert extraction error (sync)
+TEST_F(CastSocketTest, TestConnectCertExtractionErrorSync) {
+ CreateCastSocket();
+ socket_->SetupTcpConnect(net::SYNCHRONOUS, net::OK);
+ socket_->SetupSslConnect(net::SYNCHRONOUS, net::OK);
+ // Set cert extraction to fail
+ socket_->SetExtractCertResult(false);
+
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_AUTHENTICATION_ERROR));
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_AUTHENTICATION_ERROR,
+ socket_->error_state());
+}
+
+// Test connection error - challenge send fails
+TEST_F(CastSocketTest, TestConnectChallengeSendError) {
+ CreateCastSocketSecure();
+ socket_->SetupMockTransport();
+
+ socket_->SetupTcpConnect(net::SYNCHRONOUS, net::OK);
+ socket_->SetupSslConnect(net::SYNCHRONOUS, net::OK);
+ EXPECT_CALL(*socket_->GetMockTransport(),
+ SendMessage(EqualsProto(CreateAuthChallenge()), _))
+ .WillOnce(PostCompletionCallbackTask<1>(net::ERR_CONNECTION_RESET));
+
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_SOCKET_ERROR));
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_SOCKET_ERROR, socket_->error_state());
+}
+
+// Test connection error - challenge reply receive fails
+TEST_F(CastSocketTest, TestConnectChallengeReplyReceiveError) {
+ CreateCastSocketSecure();
+ socket_->SetupMockTransport();
+
+ socket_->SetupTcpConnect(net::SYNCHRONOUS, net::OK);
+ socket_->SetupSslConnect(net::SYNCHRONOUS, net::OK);
+ EXPECT_CALL(*socket_->GetMockTransport(),
+ SendMessage(EqualsProto(CreateAuthChallenge()), _))
+ .WillOnce(PostCompletionCallbackTask<1>(net::OK));
+ socket_->AddReadResult(net::SYNCHRONOUS, net::ERR_FAILED);
+ EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR));
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_SOCKET_ERROR));
+ EXPECT_CALL(*socket_->GetMockTransport(), Start());
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+ socket_->GetMockTransport()->current_delegate()->OnError(
+ CHANNEL_ERROR_SOCKET_ERROR);
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_SOCKET_ERROR, socket_->error_state());
+}
+
+TEST_F(CastSocketTest, TestConnectChallengeVerificationFails) {
+ CreateCastSocketSecure();
+ socket_->SetupMockTransport();
+ socket_->SetupTcpConnect(net::ASYNC, net::OK);
+ socket_->SetupSslConnect(net::ASYNC, net::OK);
+ socket_->SetVerifyChallengeResult(false);
+
+ EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_AUTHENTICATION_ERROR));
+ CastMessage challenge_proto = CreateAuthChallenge();
+ EXPECT_CALL(*socket_->GetMockTransport(),
+ SendMessage(EqualsProto(challenge_proto), _))
+ .WillOnce(PostCompletionCallbackTask<1>(net::OK));
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_AUTHENTICATION_ERROR));
+ EXPECT_CALL(*socket_->GetMockTransport(), Start());
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+ socket_->GetMockTransport()->current_delegate()->OnMessage(CreateAuthReply());
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_AUTHENTICATION_ERROR,
+ socket_->error_state());
+}
+
+// Sends message data through an actual non-mocked CastTransport object,
+// testing the two components in integration.
+TEST_F(CastSocketTest, TestConnectEndToEndWithRealTransportAsync) {
+ CreateCastSocketSecure();
+ socket_->SetupTcpConnect(net::ASYNC, net::OK);
+ socket_->SetupSslConnect(net::ASYNC, net::OK);
+
+ // Set low-level auth challenge expectations.
+ CastMessage challenge = CreateAuthChallenge();
+ std::string challenge_str;
+ EXPECT_TRUE(MessageFramer::Serialize(challenge, &challenge_str));
+ socket_->AddWriteResultForData(net::ASYNC, challenge_str);
+
+ // Set low-level auth reply expectations.
+ CastMessage reply = CreateAuthReply();
+ std::string reply_str;
+ EXPECT_TRUE(MessageFramer::Serialize(reply, &reply_str));
+ socket_->AddReadResultForData(net::ASYNC, reply_str);
+ socket_->AddReadResult(net::ASYNC, net::ERR_IO_PENDING);
+
+ CastMessage test_message = CreateTestMessage();
+ std::string test_message_str;
+ EXPECT_TRUE(MessageFramer::Serialize(test_message, &test_message_str));
+ socket_->AddWriteResultForData(net::ASYNC, test_message_str);
+
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_NONE));
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+ EXPECT_EQ(cast_channel::READY_STATE_OPEN, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, socket_->error_state());
+
+ // Send the test message through a real transport object.
+ EXPECT_CALL(handler_, OnWriteComplete(net::OK));
+ socket_->transport()->SendMessage(
+ test_message, base::Bind(&CompleteHandler::OnWriteComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_OPEN, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, socket_->error_state());
+}
+
+// Same as TestConnectEndToEndWithRealTransportAsync, except synchronous.
+TEST_F(CastSocketTest, TestConnectEndToEndWithRealTransportSync) {
+ CreateCastSocketSecure();
+ socket_->SetupTcpConnect(net::SYNCHRONOUS, net::OK);
+ socket_->SetupSslConnect(net::SYNCHRONOUS, net::OK);
+
+ // Set low-level auth challenge expectations.
+ CastMessage challenge = CreateAuthChallenge();
+ std::string challenge_str;
+ EXPECT_TRUE(MessageFramer::Serialize(challenge, &challenge_str));
+ socket_->AddWriteResultForData(net::SYNCHRONOUS, challenge_str);
+
+ // Set low-level auth reply expectations.
+ CastMessage reply = CreateAuthReply();
+ std::string reply_str;
+ EXPECT_TRUE(MessageFramer::Serialize(reply, &reply_str));
+ socket_->AddReadResultForData(net::SYNCHRONOUS, reply_str);
+ socket_->AddReadResult(net::ASYNC, net::ERR_IO_PENDING);
+
+ CastMessage test_message = CreateTestMessage();
+ std::string test_message_str;
+ EXPECT_TRUE(MessageFramer::Serialize(test_message, &test_message_str));
+ socket_->AddWriteResultForData(net::SYNCHRONOUS, test_message_str);
+
+ EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_NONE));
+ socket_->Connect(std::move(delegate_),
+ base::Bind(&CompleteHandler::OnConnectComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+ EXPECT_EQ(cast_channel::READY_STATE_OPEN, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, socket_->error_state());
+
+ // Send the test message through a real transport object.
+ EXPECT_CALL(handler_, OnWriteComplete(net::OK));
+ socket_->transport()->SendMessage(
+ test_message, base::Bind(&CompleteHandler::OnWriteComplete,
+ base::Unretained(&handler_)));
+ RunPendingTasks();
+
+ EXPECT_EQ(cast_channel::READY_STATE_OPEN, socket_->ready_state());
+ EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, socket_->error_state());
+}
+
+// Tests channel policy verification for device with no capabilities.
+TEST_F(CastSocketTest, TestChannelPolicyVerificationCapabilitiesNone) {
+ socket_ =
+ TestCastSocket::Create(logger_, cast_channel::CastDeviceCapability::NONE);
+ EXPECT_TRUE(socket_->TestVerifyChannelPolicyNone());
+ EXPECT_TRUE(socket_->TestVerifyChannelPolicyAudioOnly());
+}
+
+// Tests channel policy verification for device with video out capability.
+TEST_F(CastSocketTest, TestChannelPolicyVerificationCapabilitiesVideoOut) {
+ socket_ = TestCastSocket::Create(
+ logger_, cast_channel::CastDeviceCapability::VIDEO_OUT);
+ EXPECT_FALSE(socket_->audio_only());
+ EXPECT_TRUE(socket_->TestVerifyChannelPolicyNone());
+ EXPECT_FALSE(socket_->audio_only());
+ EXPECT_FALSE(socket_->TestVerifyChannelPolicyAudioOnly());
+ EXPECT_TRUE(socket_->audio_only());
+}
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/cast_channel/cast_test_util.cc b/chromium/extensions/browser/api/cast_channel/cast_test_util.cc
new file mode 100644
index 00000000000..13a19641f2e
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_test_util.cc
@@ -0,0 +1,49 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/cast_channel/cast_test_util.h"
+
+#include <utility>
+
+#include "net/base/ip_address.h"
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+
+const char kTestExtensionId[] = "ddchlicdkolnonkihahngkmmmjnjlkkf";
+
+MockCastTransport::MockCastTransport() {
+}
+MockCastTransport::~MockCastTransport() {
+}
+
+CastTransport::Delegate* MockCastTransport::current_delegate() const {
+ CHECK(delegate_);
+ return delegate_.get();
+}
+
+void MockCastTransport::SetReadDelegate(
+ scoped_ptr<CastTransport::Delegate> delegate) {
+ delegate_ = std::move(delegate);
+}
+
+MockCastTransportDelegate::MockCastTransportDelegate() {
+}
+MockCastTransportDelegate::~MockCastTransportDelegate() {
+}
+
+MockCastSocket::MockCastSocket()
+ : CastSocket(kTestExtensionId), mock_transport_(new MockCastTransport) {
+}
+MockCastSocket::~MockCastSocket() {
+}
+
+net::IPEndPoint CreateIPEndPointForTest() {
+ return net::IPEndPoint(net::IPAddress(192, 168, 1, 1), 8009);
+}
+
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/cast_channel/cast_test_util.h b/chromium/extensions/browser/api/cast_channel/cast_test_util.h
new file mode 100644
index 00000000000..550a460a66a
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_test_util.h
@@ -0,0 +1,126 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_TEST_UTIL_H_
+#define EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_TEST_UTIL_H_
+
+#include <string>
+#include <utility>
+
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "extensions/browser/api/cast_channel/cast_socket.h"
+#include "extensions/browser/api/cast_channel/cast_transport.h"
+#include "extensions/common/api/cast_channel/cast_channel.pb.h"
+#include "net/base/ip_endpoint.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+
+extern const char kTestExtensionId[];
+
+class MockCastTransport : public extensions::api::cast_channel::CastTransport {
+ public:
+ MockCastTransport();
+ ~MockCastTransport() override;
+
+ void SetReadDelegate(scoped_ptr<CastTransport::Delegate> delegate) override;
+
+ MOCK_METHOD2(SendMessage,
+ void(const extensions::api::cast_channel::CastMessage& message,
+ const net::CompletionCallback& callback));
+
+ MOCK_METHOD0(Start, void(void));
+
+ // Gets the read delegate that is currently active for this transport.
+ CastTransport::Delegate* current_delegate() const;
+
+ private:
+ scoped_ptr<CastTransport::Delegate> delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockCastTransport);
+};
+
+class MockCastTransportDelegate : public CastTransport::Delegate {
+ public:
+ MockCastTransportDelegate();
+ ~MockCastTransportDelegate() override;
+
+ MOCK_METHOD1(OnError, void(ChannelError error));
+ MOCK_METHOD1(OnMessage, void(const CastMessage& message));
+ MOCK_METHOD0(Start, void(void));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCastTransportDelegate);
+};
+
+class MockCastSocket : public CastSocket {
+ public:
+ MockCastSocket();
+ ~MockCastSocket() override;
+
+ // Mockable version of Connect. Accepts a bare pointer to a mock object.
+ // (GMock won't compile with scoped_ptr method parameters.)
+ MOCK_METHOD2(ConnectRawPtr,
+ void(CastTransport::Delegate* delegate,
+ base::Callback<void(ChannelError)> callback));
+
+ // Proxy for ConnectRawPtr. Unpacks scoped_ptr into a GMock-friendly bare
+ // ptr.
+ void Connect(scoped_ptr<CastTransport::Delegate> delegate,
+ base::Callback<void(ChannelError)> callback) override {
+ delegate_ = std::move(delegate);
+ ConnectRawPtr(delegate_.get(), callback);
+ }
+
+ MOCK_METHOD1(Close, void(const net::CompletionCallback& callback));
+ MOCK_CONST_METHOD0(ip_endpoint, const net::IPEndPoint&());
+ MOCK_CONST_METHOD0(id, int());
+ MOCK_METHOD1(set_id, void(int id));
+ MOCK_CONST_METHOD0(channel_auth, ChannelAuthType());
+ MOCK_CONST_METHOD0(ready_state, ReadyState());
+ MOCK_CONST_METHOD0(error_state, ChannelError());
+ MOCK_CONST_METHOD0(keep_alive, bool(void));
+ MOCK_CONST_METHOD0(audio_only, bool(void));
+ MOCK_METHOD1(SetErrorState, void(ChannelError error_state));
+
+ CastTransport* transport() const override { return mock_transport_.get(); }
+
+ MockCastTransport* mock_transport() const { return mock_transport_.get(); }
+
+ private:
+ scoped_ptr<MockCastTransport> mock_transport_;
+ scoped_ptr<CastTransport::Delegate> delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockCastSocket);
+};
+
+// Creates the IPEndpoint 192.168.1.1.
+net::IPEndPoint CreateIPEndPointForTest();
+
+// Checks if two proto messages are the same.
+// From
+// third_party/cacheinvalidation/overrides/google/cacheinvalidation/deps/gmock.h
+// TODO(kmarshall): promote to a shared testing library.
+MATCHER_P(EqualsProto, message, "") {
+ std::string expected_serialized, actual_serialized;
+ message.SerializeToString(&expected_serialized);
+ arg.SerializeToString(&actual_serialized);
+ return expected_serialized == actual_serialized;
+}
+
+ACTION_TEMPLATE(PostCompletionCallbackTask,
+ HAS_1_TEMPLATE_PARAMS(int, cb_idx),
+ AND_1_VALUE_PARAMS(rv)) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(testing::get<cb_idx>(args), rv));
+}
+
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_TEST_UTIL_H_
diff --git a/chromium/extensions/browser/api/cast_channel/cast_transport.cc b/chromium/extensions/browser/api/cast_channel/cast_transport.cc
new file mode 100644
index 00000000000..8a22af65a2b
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_transport.cc
@@ -0,0 +1,477 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/cast_channel/cast_transport.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/format_macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "extensions/browser/api/cast_channel/cast_framer.h"
+#include "extensions/browser/api/cast_channel/cast_message_util.h"
+#include "extensions/browser/api/cast_channel/logger.h"
+#include "extensions/browser/api/cast_channel/logger_util.h"
+#include "extensions/common/api/cast_channel/cast_channel.pb.h"
+#include "net/base/net_errors.h"
+#include "net/socket/socket.h"
+
+#define VLOG_WITH_CONNECTION(level) \
+ VLOG(level) << "[" << ip_endpoint_.ToString() << ", auth=" << channel_auth_ \
+ << "] "
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+
+CastTransportImpl::CastTransportImpl(net::Socket* socket,
+ int channel_id,
+ const net::IPEndPoint& ip_endpoint,
+ ChannelAuthType channel_auth,
+ scoped_refptr<Logger> logger)
+ : started_(false),
+ socket_(socket),
+ write_state_(WRITE_STATE_IDLE),
+ read_state_(READ_STATE_READ),
+ error_state_(CHANNEL_ERROR_NONE),
+ channel_id_(channel_id),
+ ip_endpoint_(ip_endpoint),
+ channel_auth_(channel_auth),
+ logger_(logger) {
+ DCHECK(socket);
+
+ // Buffer is reused across messages to minimize unnecessary buffer
+ // [re]allocations.
+ read_buffer_ = new net::GrowableIOBuffer();
+ read_buffer_->SetCapacity(MessageFramer::MessageHeader::max_message_size());
+ framer_.reset(new MessageFramer(read_buffer_));
+}
+
+CastTransportImpl::~CastTransportImpl() {
+ DCHECK(CalledOnValidThread());
+ FlushWriteQueue();
+}
+
+bool CastTransportImpl::IsTerminalWriteState(
+ CastTransportImpl::WriteState write_state) {
+ return write_state == WRITE_STATE_ERROR || write_state == WRITE_STATE_IDLE;
+}
+
+bool CastTransportImpl::IsTerminalReadState(
+ CastTransportImpl::ReadState read_state) {
+ return read_state == READ_STATE_ERROR;
+}
+
+// static
+proto::ReadState CastTransportImpl::ReadStateToProto(
+ CastTransportImpl::ReadState state) {
+ switch (state) {
+ case CastTransportImpl::READ_STATE_UNKNOWN:
+ return proto::READ_STATE_UNKNOWN;
+ case CastTransportImpl::READ_STATE_READ:
+ return proto::READ_STATE_READ;
+ case CastTransportImpl::READ_STATE_READ_COMPLETE:
+ return proto::READ_STATE_READ_COMPLETE;
+ case CastTransportImpl::READ_STATE_DO_CALLBACK:
+ return proto::READ_STATE_DO_CALLBACK;
+ case CastTransportImpl::READ_STATE_HANDLE_ERROR:
+ return proto::READ_STATE_HANDLE_ERROR;
+ case CastTransportImpl::READ_STATE_ERROR:
+ return proto::READ_STATE_ERROR;
+ default:
+ NOTREACHED();
+ return proto::READ_STATE_UNKNOWN;
+ }
+}
+
+// static
+proto::WriteState CastTransportImpl::WriteStateToProto(
+ CastTransportImpl::WriteState state) {
+ switch (state) {
+ case CastTransportImpl::WRITE_STATE_IDLE:
+ return proto::WRITE_STATE_IDLE;
+ case CastTransportImpl::WRITE_STATE_UNKNOWN:
+ return proto::WRITE_STATE_UNKNOWN;
+ case CastTransportImpl::WRITE_STATE_WRITE:
+ return proto::WRITE_STATE_WRITE;
+ case CastTransportImpl::WRITE_STATE_WRITE_COMPLETE:
+ return proto::WRITE_STATE_WRITE_COMPLETE;
+ case CastTransportImpl::WRITE_STATE_DO_CALLBACK:
+ return proto::WRITE_STATE_DO_CALLBACK;
+ case CastTransportImpl::WRITE_STATE_HANDLE_ERROR:
+ return proto::WRITE_STATE_HANDLE_ERROR;
+ case CastTransportImpl::WRITE_STATE_ERROR:
+ return proto::WRITE_STATE_ERROR;
+ default:
+ NOTREACHED();
+ return proto::WRITE_STATE_UNKNOWN;
+ }
+}
+
+// static
+proto::ErrorState CastTransportImpl::ErrorStateToProto(ChannelError state) {
+ switch (state) {
+ case CHANNEL_ERROR_NONE:
+ return proto::CHANNEL_ERROR_NONE;
+ case CHANNEL_ERROR_CHANNEL_NOT_OPEN:
+ return proto::CHANNEL_ERROR_CHANNEL_NOT_OPEN;
+ case CHANNEL_ERROR_AUTHENTICATION_ERROR:
+ return proto::CHANNEL_ERROR_AUTHENTICATION_ERROR;
+ case CHANNEL_ERROR_CONNECT_ERROR:
+ return proto::CHANNEL_ERROR_CONNECT_ERROR;
+ case CHANNEL_ERROR_SOCKET_ERROR:
+ return proto::CHANNEL_ERROR_SOCKET_ERROR;
+ case CHANNEL_ERROR_TRANSPORT_ERROR:
+ return proto::CHANNEL_ERROR_TRANSPORT_ERROR;
+ case CHANNEL_ERROR_INVALID_MESSAGE:
+ return proto::CHANNEL_ERROR_INVALID_MESSAGE;
+ case CHANNEL_ERROR_INVALID_CHANNEL_ID:
+ return proto::CHANNEL_ERROR_INVALID_CHANNEL_ID;
+ case CHANNEL_ERROR_CONNECT_TIMEOUT:
+ return proto::CHANNEL_ERROR_CONNECT_TIMEOUT;
+ case CHANNEL_ERROR_UNKNOWN:
+ return proto::CHANNEL_ERROR_UNKNOWN;
+ default:
+ NOTREACHED();
+ return proto::CHANNEL_ERROR_NONE;
+ }
+}
+
+void CastTransportImpl::SetReadDelegate(scoped_ptr<Delegate> delegate) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(delegate);
+ delegate_ = std::move(delegate);
+ if (started_) {
+ delegate_->Start();
+ }
+}
+
+void CastTransportImpl::FlushWriteQueue() {
+ for (; !write_queue_.empty(); write_queue_.pop()) {
+ net::CompletionCallback& callback = write_queue_.front().callback;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(callback, net::ERR_FAILED));
+ callback.Reset();
+ }
+}
+
+void CastTransportImpl::SendMessage(const CastMessage& message,
+ const net::CompletionCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ std::string serialized_message;
+ if (!MessageFramer::Serialize(message, &serialized_message)) {
+ logger_->LogSocketEventForMessage(channel_id_, proto::SEND_MESSAGE_FAILED,
+ message.namespace_(),
+ "Error when serializing message.");
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(callback, net::ERR_FAILED));
+ return;
+ }
+ WriteRequest write_request(
+ message.namespace_(), serialized_message, callback);
+
+ write_queue_.push(write_request);
+ logger_->LogSocketEventForMessage(
+ channel_id_, proto::MESSAGE_ENQUEUED, message.namespace_(),
+ base::StringPrintf("Queue size: %" PRIuS, write_queue_.size()));
+ if (write_state_ == WRITE_STATE_IDLE) {
+ SetWriteState(WRITE_STATE_WRITE);
+ OnWriteResult(net::OK);
+ }
+}
+
+CastTransportImpl::WriteRequest::WriteRequest(
+ const std::string& namespace_,
+ const std::string& payload,
+ const net::CompletionCallback& callback)
+ : message_namespace(namespace_), callback(callback) {
+ VLOG(2) << "WriteRequest size: " << payload.size();
+ io_buffer = new net::DrainableIOBuffer(new net::StringIOBuffer(payload),
+ payload.size());
+}
+
+CastTransportImpl::WriteRequest::WriteRequest(const WriteRequest& other) =
+ default;
+
+CastTransportImpl::WriteRequest::~WriteRequest() {
+}
+
+void CastTransportImpl::SetReadState(ReadState read_state) {
+ if (read_state_ != read_state) {
+ read_state_ = read_state;
+ logger_->LogSocketReadState(channel_id_, ReadStateToProto(read_state_));
+ }
+}
+
+void CastTransportImpl::SetWriteState(WriteState write_state) {
+ if (write_state_ != write_state) {
+ write_state_ = write_state;
+ logger_->LogSocketWriteState(channel_id_, WriteStateToProto(write_state_));
+ }
+}
+
+void CastTransportImpl::SetErrorState(ChannelError error_state) {
+ VLOG_WITH_CONNECTION(2) << "SetErrorState: " << error_state;
+ error_state_ = error_state;
+}
+
+void CastTransportImpl::OnWriteResult(int result) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_NE(WRITE_STATE_IDLE, write_state_);
+ if (write_queue_.empty()) {
+ SetWriteState(WRITE_STATE_IDLE);
+ return;
+ }
+
+ // Network operations can either finish synchronously or asynchronously.
+ // This method executes the state machine transitions in a loop so that
+ // write state transitions happen even when network operations finish
+ // synchronously.
+ int rv = result;
+ do {
+ VLOG_WITH_CONNECTION(2) << "OnWriteResult (state=" << write_state_ << ", "
+ << "result=" << rv << ", "
+ << "queue size=" << write_queue_.size() << ")";
+
+ WriteState state = write_state_;
+ write_state_ = WRITE_STATE_UNKNOWN;
+ switch (state) {
+ case WRITE_STATE_WRITE:
+ rv = DoWrite();
+ break;
+ case WRITE_STATE_WRITE_COMPLETE:
+ rv = DoWriteComplete(rv);
+ break;
+ case WRITE_STATE_DO_CALLBACK:
+ rv = DoWriteCallback();
+ break;
+ case WRITE_STATE_HANDLE_ERROR:
+ rv = DoWriteHandleError(rv);
+ DCHECK_EQ(WRITE_STATE_ERROR, write_state_);
+ break;
+ default:
+ NOTREACHED() << "Unknown state in write state machine: " << state;
+ SetWriteState(WRITE_STATE_ERROR);
+ SetErrorState(CHANNEL_ERROR_UNKNOWN);
+ rv = net::ERR_FAILED;
+ break;
+ }
+ } while (rv != net::ERR_IO_PENDING && !IsTerminalWriteState(write_state_));
+
+ if (IsTerminalWriteState(write_state_)) {
+ logger_->LogSocketWriteState(channel_id_, WriteStateToProto(write_state_));
+
+ if (write_state_ == WRITE_STATE_ERROR) {
+ FlushWriteQueue();
+ DCHECK_NE(CHANNEL_ERROR_NONE, error_state_);
+ VLOG_WITH_CONNECTION(2) << "Sending OnError().";
+ delegate_->OnError(error_state_);
+ }
+ }
+}
+
+int CastTransportImpl::DoWrite() {
+ DCHECK(!write_queue_.empty());
+ WriteRequest& request = write_queue_.front();
+
+ VLOG_WITH_CONNECTION(2) << "WriteData byte_count = "
+ << request.io_buffer->size() << " bytes_written "
+ << request.io_buffer->BytesConsumed();
+
+ SetWriteState(WRITE_STATE_WRITE_COMPLETE);
+
+ int rv = socket_->Write(
+ request.io_buffer.get(), request.io_buffer->BytesRemaining(),
+ base::Bind(&CastTransportImpl::OnWriteResult, base::Unretained(this)));
+ return rv;
+}
+
+int CastTransportImpl::DoWriteComplete(int result) {
+ VLOG_WITH_CONNECTION(2) << "DoWriteComplete result=" << result;
+ DCHECK(!write_queue_.empty());
+ logger_->LogSocketEventWithRv(channel_id_, proto::SOCKET_WRITE, result);
+ if (result <= 0) { // NOTE that 0 also indicates an error
+ SetErrorState(CHANNEL_ERROR_SOCKET_ERROR);
+ SetWriteState(WRITE_STATE_HANDLE_ERROR);
+ return result == 0 ? net::ERR_FAILED : result;
+ }
+
+ // Some bytes were successfully written
+ WriteRequest& request = write_queue_.front();
+ scoped_refptr<net::DrainableIOBuffer> io_buffer = request.io_buffer;
+ io_buffer->DidConsume(result);
+ if (io_buffer->BytesRemaining() == 0) { // Message fully sent
+ SetWriteState(WRITE_STATE_DO_CALLBACK);
+ } else {
+ SetWriteState(WRITE_STATE_WRITE);
+ }
+
+ return net::OK;
+}
+
+int CastTransportImpl::DoWriteCallback() {
+ VLOG_WITH_CONNECTION(2) << "DoWriteCallback";
+ DCHECK(!write_queue_.empty());
+
+ WriteRequest& request = write_queue_.front();
+ int bytes_consumed = request.io_buffer->BytesConsumed();
+ logger_->LogSocketEventForMessage(
+ channel_id_, proto::MESSAGE_WRITTEN, request.message_namespace,
+ base::StringPrintf("Bytes: %d", bytes_consumed));
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(&base::DoNothing));
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(request.callback, net::OK));
+
+ write_queue_.pop();
+ if (write_queue_.empty()) {
+ SetWriteState(WRITE_STATE_IDLE);
+ } else {
+ SetWriteState(WRITE_STATE_WRITE);
+ }
+
+ return net::OK;
+}
+
+int CastTransportImpl::DoWriteHandleError(int result) {
+ VLOG_WITH_CONNECTION(2) << "DoWriteHandleError result=" << result;
+ DCHECK_NE(CHANNEL_ERROR_NONE, error_state_);
+ DCHECK_LT(result, 0);
+ SetWriteState(WRITE_STATE_ERROR);
+ return net::ERR_FAILED;
+}
+
+void CastTransportImpl::Start() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!started_);
+ DCHECK_EQ(READ_STATE_READ, read_state_);
+ DCHECK(delegate_) << "Read delegate must be set prior to calling Start()";
+ started_ = true;
+ delegate_->Start();
+ SetReadState(READ_STATE_READ);
+
+ // Start the read state machine.
+ OnReadResult(net::OK);
+}
+
+void CastTransportImpl::OnReadResult(int result) {
+ DCHECK(CalledOnValidThread());
+ // Network operations can either finish synchronously or asynchronously.
+ // This method executes the state machine transitions in a loop so that
+ // write state transitions happen even when network operations finish
+ // synchronously.
+ int rv = result;
+ do {
+ VLOG_WITH_CONNECTION(2) << "OnReadResult(state=" << read_state_
+ << ", result=" << rv << ")";
+ ReadState state = read_state_;
+ read_state_ = READ_STATE_UNKNOWN;
+
+ switch (state) {
+ case READ_STATE_READ:
+ rv = DoRead();
+ break;
+ case READ_STATE_READ_COMPLETE:
+ rv = DoReadComplete(rv);
+ break;
+ case READ_STATE_DO_CALLBACK:
+ rv = DoReadCallback();
+ break;
+ case READ_STATE_HANDLE_ERROR:
+ rv = DoReadHandleError(rv);
+ DCHECK_EQ(read_state_, READ_STATE_ERROR);
+ break;
+ default:
+ NOTREACHED() << "Unknown state in read state machine: " << state;
+ SetReadState(READ_STATE_ERROR);
+ SetErrorState(CHANNEL_ERROR_UNKNOWN);
+ rv = net::ERR_FAILED;
+ break;
+ }
+ } while (rv != net::ERR_IO_PENDING && !IsTerminalReadState(read_state_));
+
+ if (IsTerminalReadState(read_state_)) {
+ DCHECK_EQ(READ_STATE_ERROR, read_state_);
+ logger_->LogSocketReadState(channel_id_, ReadStateToProto(read_state_));
+ VLOG_WITH_CONNECTION(2) << "Sending OnError().";
+ delegate_->OnError(error_state_);
+ }
+}
+
+int CastTransportImpl::DoRead() {
+ VLOG_WITH_CONNECTION(2) << "DoRead";
+ SetReadState(READ_STATE_READ_COMPLETE);
+
+ // Determine how many bytes need to be read.
+ size_t num_bytes_to_read = framer_->BytesRequested();
+ DCHECK_GT(num_bytes_to_read, 0u);
+
+ // Read up to num_bytes_to_read into |current_read_buffer_|.
+ return socket_->Read(
+ read_buffer_.get(), base::checked_cast<uint32_t>(num_bytes_to_read),
+ base::Bind(&CastTransportImpl::OnReadResult, base::Unretained(this)));
+}
+
+int CastTransportImpl::DoReadComplete(int result) {
+ VLOG_WITH_CONNECTION(2) << "DoReadComplete result = " << result;
+ logger_->LogSocketEventWithRv(channel_id_, proto::SOCKET_READ, result);
+ if (result <= 0) {
+ VLOG_WITH_CONNECTION(1) << "Read error, peer closed the socket.";
+ SetErrorState(CHANNEL_ERROR_SOCKET_ERROR);
+ SetReadState(READ_STATE_HANDLE_ERROR);
+ return result == 0 ? net::ERR_FAILED : result;
+ }
+
+ size_t message_size;
+ DCHECK(!current_message_);
+ ChannelError framing_error;
+ current_message_ = framer_->Ingest(result, &message_size, &framing_error);
+ if (current_message_.get() && (framing_error == CHANNEL_ERROR_NONE)) {
+ DCHECK_GT(message_size, static_cast<size_t>(0));
+ logger_->LogSocketEventForMessage(
+ channel_id_, proto::MESSAGE_READ, current_message_->namespace_(),
+ base::StringPrintf("Message size: %u",
+ static_cast<uint32_t>(message_size)));
+ SetReadState(READ_STATE_DO_CALLBACK);
+ } else if (framing_error != CHANNEL_ERROR_NONE) {
+ DCHECK(!current_message_);
+ SetErrorState(CHANNEL_ERROR_INVALID_MESSAGE);
+ SetReadState(READ_STATE_HANDLE_ERROR);
+ } else {
+ DCHECK(!current_message_);
+ SetReadState(READ_STATE_READ);
+ }
+ return net::OK;
+}
+
+int CastTransportImpl::DoReadCallback() {
+ VLOG_WITH_CONNECTION(2) << "DoReadCallback";
+ if (!IsCastMessageValid(*current_message_)) {
+ SetReadState(READ_STATE_HANDLE_ERROR);
+ SetErrorState(CHANNEL_ERROR_INVALID_MESSAGE);
+ return net::ERR_INVALID_RESPONSE;
+ }
+ SetReadState(READ_STATE_READ);
+ delegate_->OnMessage(*current_message_);
+ current_message_.reset();
+ return net::OK;
+}
+
+int CastTransportImpl::DoReadHandleError(int result) {
+ VLOG_WITH_CONNECTION(2) << "DoReadHandleError";
+ DCHECK_NE(CHANNEL_ERROR_NONE, error_state_);
+ DCHECK_LE(result, 0);
+ SetReadState(READ_STATE_ERROR);
+ return net::ERR_FAILED;
+}
+
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/cast_channel/cast_transport.h b/chromium/extensions/browser/api/cast_channel/cast_transport.h
new file mode 100644
index 00000000000..740bd5f1d3b
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_transport.h
@@ -0,0 +1,227 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_TRANSPORT_H_
+#define EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_TRANSPORT_H_
+
+#include <queue>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/threading/thread_checker.h"
+#include "extensions/browser/api/cast_channel/logger.h"
+#include "extensions/common/api/cast_channel.h"
+#include "extensions/common/api/cast_channel/logging.pb.h"
+#include "net/base/completion_callback.h"
+
+namespace net {
+class DrainableIOBuffer;
+class IPEndPoint;
+class IOBuffer;
+class DrainableIOBuffer;
+class GrowableIOBuffer;
+class Socket;
+} // namespace net
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+class CastMessage;
+struct LastErrors;
+class Logger;
+class MessageFramer;
+
+class CastTransport {
+ public:
+ virtual ~CastTransport() {}
+
+ // Object to be informed of incoming messages and read errors.
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ // Called once Transport is successfully initialized and started.
+ // Owned read delegates are Start()ed automatically.
+ virtual void Start() = 0;
+
+ // An error occurred on the channel.
+ // The caller is responsible for closing |socket| if an error occurred.
+ virtual void OnError(ChannelError error_state) = 0;
+
+ // A message was received on the channel.
+ virtual void OnMessage(const CastMessage& message) = 0;
+ };
+
+ // Sends a CastMessage to |socket_|.
+ // |message|: The message to send.
+ // |callback|: Callback to be invoked when the write operation has finished.
+ // Virtual for testing.
+ virtual void SendMessage(const CastMessage& message,
+ const net::CompletionCallback& callback) = 0;
+
+ // Initializes the reading state machine and starts reading from the
+ // underlying socket.
+ // Virtual for testing.
+ virtual void Start() = 0;
+
+ // Changes the delegate for processing read events. Pending reads remain
+ // in-flight.
+ // Ownership of the pointee of |delegate| is assumed by the transport.
+ // Prior delegates are deleted automatically.
+ virtual void SetReadDelegate(scoped_ptr<Delegate> delegate) = 0;
+};
+
+// Manager class for reading and writing messages to/from a socket.
+class CastTransportImpl : public CastTransport, public base::NonThreadSafe {
+ public:
+ // Adds a CastMessage read/write layer to a socket.
+ // Message read events are propagated to the owner via |read_delegate|.
+ // |vlog_prefix| sets the prefix used for all VLOGged output.
+ // |socket| and |logger| must all out-live the
+ // CastTransportImpl instance.
+ // |read_delegate| is owned by this CastTransportImpl object.
+ CastTransportImpl(net::Socket* socket,
+ int channel_id,
+ const net::IPEndPoint& ip_endpoint_,
+ ChannelAuthType channel_auth_,
+ scoped_refptr<Logger> logger);
+
+ ~CastTransportImpl() override;
+
+ // CastTransport interface.
+ void SendMessage(const CastMessage& message,
+ const net::CompletionCallback& callback) override;
+ void Start() override;
+ void SetReadDelegate(scoped_ptr<Delegate> delegate) override;
+
+ private:
+ // Internal write states.
+ enum WriteState {
+ WRITE_STATE_UNKNOWN,
+ WRITE_STATE_WRITE,
+ WRITE_STATE_WRITE_COMPLETE,
+ WRITE_STATE_DO_CALLBACK,
+ WRITE_STATE_HANDLE_ERROR,
+ WRITE_STATE_ERROR,
+ WRITE_STATE_IDLE,
+ };
+
+ // Internal read states.
+ enum ReadState {
+ READ_STATE_UNKNOWN,
+ READ_STATE_READ,
+ READ_STATE_READ_COMPLETE,
+ READ_STATE_DO_CALLBACK,
+ READ_STATE_HANDLE_ERROR,
+ READ_STATE_ERROR,
+ };
+
+ // Holds a message to be written to the socket. |callback| is invoked when the
+ // message is fully written or an error occurrs.
+ struct WriteRequest {
+ explicit WriteRequest(const std::string& namespace_,
+ const std::string& payload,
+ const net::CompletionCallback& callback);
+ WriteRequest(const WriteRequest& other);
+ ~WriteRequest();
+
+ // Namespace of the serialized message.
+ std::string message_namespace;
+ // Write completion callback, invoked when the operation has completed or
+ // failed.
+ net::CompletionCallback callback;
+ // Buffer with outgoing data.
+ scoped_refptr<net::DrainableIOBuffer> io_buffer;
+ };
+
+ static proto::ReadState ReadStateToProto(CastTransportImpl::ReadState state);
+ static proto::WriteState WriteStateToProto(
+ CastTransportImpl::WriteState state);
+ static proto::ErrorState ErrorStateToProto(ChannelError state);
+ static bool IsTerminalReadState(ReadState read_state);
+ static bool IsTerminalWriteState(WriteState write_state);
+
+ void SetReadState(ReadState read_state);
+ void SetWriteState(WriteState write_state);
+ void SetErrorState(ChannelError error_state);
+
+ // Terminates all in-flight write callbacks with error code ERR_FAILED.
+ void FlushWriteQueue();
+
+ // Main method that performs write flow state transitions.
+ void OnWriteResult(int result);
+
+ // Each of the below Do* method is executed in the corresponding
+ // write state. For example when write state is WRITE_STATE_WRITE_COMPLETE
+ // DowriteComplete is called, and so on.
+ int DoWrite();
+ int DoWriteComplete(int result);
+ int DoWriteCallback();
+ int DoWriteHandleError(int result);
+
+ // Main method that performs write flow state transitions.
+ void OnReadResult(int result);
+
+ // Each of the below Do* method is executed in the corresponding
+ // write state. For example when read state is READ_STATE_READ_COMPLETE
+ // DoReadComplete is called, and so on.
+ int DoRead();
+ int DoReadComplete(int result);
+ int DoReadCallback();
+ int DoReadHandleError(int result);
+
+ // Indicates that the transport object is started and may receive and send
+ // messages.
+ bool started_;
+
+ // Queue of pending writes. The message at the front of the queue is the one
+ // being written.
+ std::queue<WriteRequest> write_queue_;
+
+ // Buffer used for read operations. Reused for every read.
+ scoped_refptr<net::GrowableIOBuffer> read_buffer_;
+
+ // Constructs and parses the wire representation of message frames.
+ scoped_ptr<MessageFramer> framer_;
+
+ // Last message received on the socket.
+ scoped_ptr<CastMessage> current_message_;
+
+ // Socket used for I/O operations.
+ net::Socket* const socket_;
+
+ // Methods for communicating message receipt and error status to client code.
+ scoped_ptr<Delegate> delegate_;
+
+ // Write flow state machine state.
+ WriteState write_state_;
+
+ // Read flow state machine state.
+ ReadState read_state_;
+
+ // The last error encountered by the channel.
+ ChannelError error_state_;
+
+ // Connection metadata for logging purposes.
+ // Socket ID assigned by ApiResourceManager.
+ int channel_id_;
+
+ // IP address of the remote end.
+ const net::IPEndPoint ip_endpoint_;
+
+ // Authentication level for the connection.
+ ChannelAuthType channel_auth_;
+
+ // Accumulates details of events and errors, for debugging purposes.
+ scoped_refptr<Logger> logger_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastTransportImpl);
+};
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_TRANSPORT_H_
diff --git a/chromium/extensions/browser/api/cast_channel/cast_transport_unittest.cc b/chromium/extensions/browser/api/cast_channel/cast_transport_unittest.cc
new file mode 100644
index 00000000000..d8cc6f42ba8
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/cast_transport_unittest.cc
@@ -0,0 +1,686 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/cast_channel/cast_transport.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <queue>
+
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/test/simple_test_clock.h"
+#include "extensions/browser/api/cast_channel/cast_framer.h"
+#include "extensions/browser/api/cast_channel/cast_socket.h"
+#include "extensions/browser/api/cast_channel/cast_test_util.h"
+#include "extensions/browser/api/cast_channel/logger.h"
+#include "extensions/browser/api/cast_channel/logger_util.h"
+#include "extensions/common/api/cast_channel/cast_channel.pb.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_errors.h"
+#include "net/log/test_net_log.h"
+#include "net/socket/socket.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::DoAll;
+using testing::InSequence;
+using testing::Invoke;
+using testing::NotNull;
+using testing::Return;
+using testing::WithArg;
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+namespace {
+
+const int kChannelId = 0;
+
+// Mockable placeholder for write completion events.
+class CompleteHandler {
+ public:
+ CompleteHandler() {}
+ MOCK_METHOD1(Complete, void(int result));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CompleteHandler);
+};
+
+// Creates a CastMessage proto with the bare minimum required fields set.
+CastMessage CreateCastMessage() {
+ CastMessage output;
+ output.set_protocol_version(CastMessage::CASTV2_1_0);
+ output.set_namespace_("x");
+ output.set_source_id("source");
+ output.set_destination_id("destination");
+ output.set_payload_type(CastMessage::STRING);
+ output.set_payload_utf8("payload");
+ return output;
+}
+
+// FIFO queue of completion callbacks. Outstanding write operations are
+// Push()ed into the queue. Callback completion is simulated by invoking
+// Pop() in the same order as Push().
+class CompletionQueue {
+ public:
+ CompletionQueue() {}
+ ~CompletionQueue() { CHECK_EQ(0u, cb_queue_.size()); }
+
+ // Enqueues a pending completion callback.
+ void Push(const net::CompletionCallback& cb) { cb_queue_.push(cb); }
+ // Runs the next callback and removes it from the queue.
+ void Pop(int rv) {
+ CHECK_GT(cb_queue_.size(), 0u);
+ cb_queue_.front().Run(rv);
+ cb_queue_.pop();
+ }
+
+ private:
+ std::queue<net::CompletionCallback> cb_queue_;
+ DISALLOW_COPY_AND_ASSIGN(CompletionQueue);
+};
+
+// GMock action that reads data from an IOBuffer and writes it to a string
+// variable.
+//
+// buf_idx (template parameter 0): 0-based index of the net::IOBuffer
+// in the function mock arg list.
+// size_idx (template parameter 1): 0-based index of the byte count arg.
+// str: pointer to the string which will receive data from the buffer.
+ACTION_TEMPLATE(ReadBufferToString,
+ HAS_2_TEMPLATE_PARAMS(int, buf_idx, int, size_idx),
+ AND_1_VALUE_PARAMS(str)) {
+ str->assign(testing::get<buf_idx>(args)->data(),
+ testing::get<size_idx>(args));
+}
+
+// GMock action that writes data from a string to an IOBuffer.
+//
+// buf_idx (template parameter 0): 0-based index of the IOBuffer arg.
+// str: the string containing data to be written to the IOBuffer.
+ACTION_TEMPLATE(FillBufferFromString,
+ HAS_1_TEMPLATE_PARAMS(int, buf_idx),
+ AND_1_VALUE_PARAMS(str)) {
+ memcpy(testing::get<buf_idx>(args)->data(), str.data(), str.size());
+}
+
+// GMock action that enqueues a write completion callback in a queue.
+//
+// buf_idx (template parameter 0): 0-based index of the CompletionCallback.
+// completion_queue: a pointer to the CompletionQueue.
+ACTION_TEMPLATE(EnqueueCallback,
+ HAS_1_TEMPLATE_PARAMS(int, cb_idx),
+ AND_1_VALUE_PARAMS(completion_queue)) {
+ completion_queue->Push(testing::get<cb_idx>(args));
+}
+
+} // namespace
+
+class MockSocket : public net::Socket {
+ public:
+ MOCK_METHOD3(Read,
+ int(net::IOBuffer* buf,
+ int buf_len,
+ const net::CompletionCallback& callback));
+
+ MOCK_METHOD3(Write,
+ int(net::IOBuffer* buf,
+ int buf_len,
+ const net::CompletionCallback& callback));
+
+ virtual int SetReceiveBufferSize(int32_t size) {
+ NOTREACHED();
+ return 0;
+ }
+
+ virtual int SetSendBufferSize(int32_t size) {
+ NOTREACHED();
+ return 0;
+ }
+};
+
+class CastTransportTest : public testing::Test {
+ public:
+ CastTransportTest()
+ : logger_(
+ new Logger(make_scoped_ptr<base::Clock>(new base::SimpleTestClock),
+ base::Time())) {
+ delegate_ = new MockCastTransportDelegate;
+ transport_.reset(new CastTransportImpl(&mock_socket_, kChannelId,
+ CreateIPEndPointForTest(),
+ auth_type_, logger_));
+ transport_->SetReadDelegate(make_scoped_ptr(delegate_));
+ }
+ ~CastTransportTest() override {}
+
+ protected:
+ // Runs all pending tasks in the message loop.
+ void RunPendingTasks() {
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ }
+
+ base::MessageLoop message_loop_;
+ MockCastTransportDelegate* delegate_;
+ MockSocket mock_socket_;
+ ChannelAuthType auth_type_;
+ Logger* logger_;
+ scoped_ptr<CastTransport> transport_;
+};
+
+// ----------------------------------------------------------------------------
+// Asynchronous write tests
+TEST_F(CastTransportTest, TestFullWriteAsync) {
+ CompletionQueue socket_cbs;
+ CompleteHandler write_handler;
+ std::string output;
+
+ CastMessage message = CreateCastMessage();
+ std::string serialized_message;
+ EXPECT_TRUE(MessageFramer::Serialize(message, &serialized_message));
+
+ EXPECT_CALL(mock_socket_, Write(NotNull(), serialized_message.size(), _))
+ .WillOnce(DoAll(ReadBufferToString<0, 1>(&output),
+ EnqueueCallback<2>(&socket_cbs),
+ Return(net::ERR_IO_PENDING)));
+ EXPECT_CALL(write_handler, Complete(net::OK));
+ transport_->SendMessage(
+ message,
+ base::Bind(&CompleteHandler::Complete, base::Unretained(&write_handler)));
+ RunPendingTasks();
+ socket_cbs.Pop(serialized_message.size());
+ RunPendingTasks();
+ EXPECT_EQ(serialized_message, output);
+}
+
+TEST_F(CastTransportTest, TestPartialWritesAsync) {
+ InSequence seq;
+ CompletionQueue socket_cbs;
+ CompleteHandler write_handler;
+ std::string output;
+
+ CastMessage message = CreateCastMessage();
+ std::string serialized_message;
+ EXPECT_TRUE(MessageFramer::Serialize(message, &serialized_message));
+
+ // Only one byte is written.
+ EXPECT_CALL(mock_socket_,
+ Write(NotNull(), static_cast<int>(serialized_message.size()), _))
+ .WillOnce(DoAll(ReadBufferToString<0, 1>(&output),
+ EnqueueCallback<2>(&socket_cbs),
+ Return(net::ERR_IO_PENDING)));
+ // Remainder of bytes are written.
+ EXPECT_CALL(
+ mock_socket_,
+ Write(NotNull(), static_cast<int>(serialized_message.size() - 1), _))
+ .WillOnce(DoAll(ReadBufferToString<0, 1>(&output),
+ EnqueueCallback<2>(&socket_cbs),
+ Return(net::ERR_IO_PENDING)));
+
+ transport_->SendMessage(
+ message,
+ base::Bind(&CompleteHandler::Complete, base::Unretained(&write_handler)));
+ RunPendingTasks();
+ EXPECT_EQ(serialized_message, output);
+ socket_cbs.Pop(1);
+ RunPendingTasks();
+
+ EXPECT_CALL(write_handler, Complete(net::OK));
+ socket_cbs.Pop(serialized_message.size() - 1);
+ RunPendingTasks();
+ EXPECT_EQ(serialized_message.substr(1, serialized_message.size() - 1),
+ output);
+}
+
+TEST_F(CastTransportTest, TestWriteFailureAsync) {
+ CompletionQueue socket_cbs;
+ CompleteHandler write_handler;
+ CastMessage message = CreateCastMessage();
+ EXPECT_CALL(mock_socket_, Write(NotNull(), _, _)).WillOnce(
+ DoAll(EnqueueCallback<2>(&socket_cbs), Return(net::ERR_IO_PENDING)));
+ EXPECT_CALL(write_handler, Complete(net::ERR_FAILED));
+ EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR));
+ transport_->SendMessage(
+ message,
+ base::Bind(&CompleteHandler::Complete, base::Unretained(&write_handler)));
+ RunPendingTasks();
+ socket_cbs.Pop(net::ERR_CONNECTION_RESET);
+ RunPendingTasks();
+ EXPECT_EQ(proto::SOCKET_WRITE, logger_->GetLastErrors(kChannelId).event_type);
+ EXPECT_EQ(net::ERR_CONNECTION_RESET,
+ logger_->GetLastErrors(kChannelId).net_return_value);
+}
+
+// ----------------------------------------------------------------------------
+// Synchronous write tests
+TEST_F(CastTransportTest, TestFullWriteSync) {
+ CompleteHandler write_handler;
+ std::string output;
+ CastMessage message = CreateCastMessage();
+ std::string serialized_message;
+ EXPECT_TRUE(MessageFramer::Serialize(message, &serialized_message));
+ EXPECT_CALL(mock_socket_, Write(NotNull(), serialized_message.size(), _))
+ .WillOnce(DoAll(ReadBufferToString<0, 1>(&output),
+ Return(serialized_message.size())));
+ EXPECT_CALL(write_handler, Complete(net::OK));
+ transport_->SendMessage(
+ message,
+ base::Bind(&CompleteHandler::Complete, base::Unretained(&write_handler)));
+ RunPendingTasks();
+ EXPECT_EQ(serialized_message, output);
+}
+
+TEST_F(CastTransportTest, TestPartialWritesSync) {
+ InSequence seq;
+ CompleteHandler write_handler;
+ std::string output;
+
+ CastMessage message = CreateCastMessage();
+ std::string serialized_message;
+ EXPECT_TRUE(MessageFramer::Serialize(message, &serialized_message));
+
+ // Only one byte is written.
+ EXPECT_CALL(mock_socket_, Write(NotNull(), serialized_message.size(), _))
+ .WillOnce(DoAll(ReadBufferToString<0, 1>(&output), Return(1)));
+ // Remainder of bytes are written.
+ EXPECT_CALL(mock_socket_, Write(NotNull(), serialized_message.size() - 1, _))
+ .WillOnce(DoAll(ReadBufferToString<0, 1>(&output),
+ Return(serialized_message.size() - 1)));
+
+ EXPECT_CALL(write_handler, Complete(net::OK));
+ transport_->SendMessage(
+ message,
+ base::Bind(&CompleteHandler::Complete, base::Unretained(&write_handler)));
+ RunPendingTasks();
+ EXPECT_EQ(serialized_message.substr(1, serialized_message.size() - 1),
+ output);
+}
+
+TEST_F(CastTransportTest, TestWriteFailureSync) {
+ CompleteHandler write_handler;
+ CastMessage message = CreateCastMessage();
+ EXPECT_CALL(mock_socket_, Write(NotNull(), _, _))
+ .WillOnce(Return(net::ERR_CONNECTION_RESET));
+ EXPECT_CALL(write_handler, Complete(net::ERR_FAILED));
+ transport_->SendMessage(
+ message,
+ base::Bind(&CompleteHandler::Complete, base::Unretained(&write_handler)));
+ RunPendingTasks();
+ EXPECT_EQ(proto::SOCKET_WRITE, logger_->GetLastErrors(kChannelId).event_type);
+ EXPECT_EQ(net::ERR_CONNECTION_RESET,
+ logger_->GetLastErrors(kChannelId).net_return_value);
+}
+
+// ----------------------------------------------------------------------------
+// Asynchronous read tests
+TEST_F(CastTransportTest, TestFullReadAsync) {
+ InSequence s;
+ CompletionQueue socket_cbs;
+
+ CastMessage message = CreateCastMessage();
+ std::string serialized_message;
+ EXPECT_TRUE(MessageFramer::Serialize(message, &serialized_message));
+ EXPECT_CALL(*delegate_, Start());
+
+ // Read bytes [0, 3].
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(), MessageFramer::MessageHeader::header_size(), _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message),
+ EnqueueCallback<2>(&socket_cbs),
+ Return(net::ERR_IO_PENDING)));
+
+ // Read bytes [4, n].
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(),
+ serialized_message.size() -
+ MessageFramer::MessageHeader::header_size(),
+ _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message.substr(
+ MessageFramer::MessageHeader::header_size(),
+ serialized_message.size() -
+ MessageFramer::MessageHeader::header_size())),
+ EnqueueCallback<2>(&socket_cbs),
+ Return(net::ERR_IO_PENDING)))
+ .RetiresOnSaturation();
+
+ EXPECT_CALL(*delegate_, OnMessage(EqualsProto(message)));
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(), MessageFramer::MessageHeader::header_size(), _))
+ .WillOnce(Return(net::ERR_IO_PENDING));
+ transport_->Start();
+ RunPendingTasks();
+ socket_cbs.Pop(MessageFramer::MessageHeader::header_size());
+ socket_cbs.Pop(serialized_message.size() -
+ MessageFramer::MessageHeader::header_size());
+ RunPendingTasks();
+}
+
+TEST_F(CastTransportTest, TestPartialReadAsync) {
+ InSequence s;
+ CompletionQueue socket_cbs;
+
+ CastMessage message = CreateCastMessage();
+ std::string serialized_message;
+ EXPECT_TRUE(MessageFramer::Serialize(message, &serialized_message));
+
+ EXPECT_CALL(*delegate_, Start());
+
+ // Read bytes [0, 3].
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(), MessageFramer::MessageHeader::header_size(), _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message),
+ EnqueueCallback<2>(&socket_cbs),
+ Return(net::ERR_IO_PENDING)))
+ .RetiresOnSaturation();
+ // Read bytes [4, n-1].
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(),
+ serialized_message.size() -
+ MessageFramer::MessageHeader::header_size(),
+ _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message.substr(
+ MessageFramer::MessageHeader::header_size(),
+ serialized_message.size() -
+ MessageFramer::MessageHeader::header_size() - 1)),
+ EnqueueCallback<2>(&socket_cbs),
+ Return(net::ERR_IO_PENDING)))
+ .RetiresOnSaturation();
+ // Read final byte.
+ EXPECT_CALL(mock_socket_, Read(NotNull(), 1, _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message.substr(
+ serialized_message.size() - 1, 1)),
+ EnqueueCallback<2>(&socket_cbs),
+ Return(net::ERR_IO_PENDING)))
+ .RetiresOnSaturation();
+ EXPECT_CALL(*delegate_, OnMessage(EqualsProto(message)));
+ transport_->Start();
+ socket_cbs.Pop(MessageFramer::MessageHeader::header_size());
+ socket_cbs.Pop(serialized_message.size() -
+ MessageFramer::MessageHeader::header_size() - 1);
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(), MessageFramer::MessageHeader::header_size(), _))
+ .WillOnce(Return(net::ERR_IO_PENDING));
+ socket_cbs.Pop(1);
+}
+
+TEST_F(CastTransportTest, TestReadErrorInHeaderAsync) {
+ CompletionQueue socket_cbs;
+
+ CastMessage message = CreateCastMessage();
+ std::string serialized_message;
+ EXPECT_TRUE(MessageFramer::Serialize(message, &serialized_message));
+
+ EXPECT_CALL(*delegate_, Start());
+
+ // Read bytes [0, 3].
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(), MessageFramer::MessageHeader::header_size(), _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message),
+ EnqueueCallback<2>(&socket_cbs),
+ Return(net::ERR_IO_PENDING)))
+ .RetiresOnSaturation();
+
+ EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR));
+ transport_->Start();
+ // Header read failure.
+ socket_cbs.Pop(net::ERR_CONNECTION_RESET);
+ EXPECT_EQ(proto::SOCKET_READ, logger_->GetLastErrors(kChannelId).event_type);
+ EXPECT_EQ(net::ERR_CONNECTION_RESET,
+ logger_->GetLastErrors(kChannelId).net_return_value);
+}
+
+TEST_F(CastTransportTest, TestReadErrorInBodyAsync) {
+ CompletionQueue socket_cbs;
+
+ CastMessage message = CreateCastMessage();
+ std::string serialized_message;
+ EXPECT_TRUE(MessageFramer::Serialize(message, &serialized_message));
+
+ EXPECT_CALL(*delegate_, Start());
+
+ // Read bytes [0, 3].
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(), MessageFramer::MessageHeader::header_size(), _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message),
+ EnqueueCallback<2>(&socket_cbs),
+ Return(net::ERR_IO_PENDING)))
+ .RetiresOnSaturation();
+ // Read bytes [4, n-1].
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(),
+ serialized_message.size() -
+ MessageFramer::MessageHeader::header_size(),
+ _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message.substr(
+ MessageFramer::MessageHeader::header_size(),
+ serialized_message.size() -
+ MessageFramer::MessageHeader::header_size() - 1)),
+ EnqueueCallback<2>(&socket_cbs),
+ Return(net::ERR_IO_PENDING)))
+ .RetiresOnSaturation();
+ EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR));
+ transport_->Start();
+ // Header read is OK.
+ socket_cbs.Pop(MessageFramer::MessageHeader::header_size());
+ // Body read fails.
+ socket_cbs.Pop(net::ERR_CONNECTION_RESET);
+ EXPECT_EQ(proto::SOCKET_READ, logger_->GetLastErrors(kChannelId).event_type);
+ EXPECT_EQ(net::ERR_CONNECTION_RESET,
+ logger_->GetLastErrors(kChannelId).net_return_value);
+}
+
+TEST_F(CastTransportTest, TestReadCorruptedMessageAsync) {
+ CompletionQueue socket_cbs;
+
+ CastMessage message = CreateCastMessage();
+ std::string serialized_message;
+ EXPECT_TRUE(MessageFramer::Serialize(message, &serialized_message));
+
+ // Corrupt the serialized message body(set it to X's).
+ for (size_t i = MessageFramer::MessageHeader::header_size();
+ i < serialized_message.size();
+ ++i) {
+ serialized_message[i] = 'x';
+ }
+
+ EXPECT_CALL(*delegate_, Start());
+ // Read bytes [0, 3].
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(), MessageFramer::MessageHeader::header_size(), _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message),
+ EnqueueCallback<2>(&socket_cbs),
+ Return(net::ERR_IO_PENDING)))
+ .RetiresOnSaturation();
+ // Read bytes [4, n].
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(),
+ serialized_message.size() -
+ MessageFramer::MessageHeader::header_size(),
+ _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message.substr(
+ MessageFramer::MessageHeader::header_size(),
+ serialized_message.size() -
+ MessageFramer::MessageHeader::header_size() - 1)),
+ EnqueueCallback<2>(&socket_cbs),
+ Return(net::ERR_IO_PENDING)))
+ .RetiresOnSaturation();
+
+ EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_INVALID_MESSAGE));
+ transport_->Start();
+ socket_cbs.Pop(MessageFramer::MessageHeader::header_size());
+ socket_cbs.Pop(serialized_message.size() -
+ MessageFramer::MessageHeader::header_size());
+}
+
+// ----------------------------------------------------------------------------
+// Synchronous read tests
+TEST_F(CastTransportTest, TestFullReadSync) {
+ InSequence s;
+ CastMessage message = CreateCastMessage();
+ std::string serialized_message;
+ EXPECT_TRUE(MessageFramer::Serialize(message, &serialized_message));
+
+ EXPECT_CALL(*delegate_, Start());
+
+ // Read bytes [0, 3].
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(), MessageFramer::MessageHeader::header_size(), _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message),
+ Return(MessageFramer::MessageHeader::header_size())))
+ .RetiresOnSaturation();
+ // Read bytes [4, n].
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(),
+ serialized_message.size() -
+ MessageFramer::MessageHeader::header_size(),
+ _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message.substr(
+ MessageFramer::MessageHeader::header_size(),
+ serialized_message.size() -
+ MessageFramer::MessageHeader::header_size())),
+ Return(serialized_message.size() -
+ MessageFramer::MessageHeader::header_size())))
+ .RetiresOnSaturation();
+ EXPECT_CALL(*delegate_, OnMessage(EqualsProto(message)));
+ // Async result in order to discontinue the read loop.
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(), MessageFramer::MessageHeader::header_size(), _))
+ .WillOnce(Return(net::ERR_IO_PENDING));
+ transport_->Start();
+}
+
+TEST_F(CastTransportTest, TestPartialReadSync) {
+ InSequence s;
+
+ CastMessage message = CreateCastMessage();
+ std::string serialized_message;
+ EXPECT_TRUE(MessageFramer::Serialize(message, &serialized_message));
+
+ EXPECT_CALL(*delegate_, Start());
+
+ // Read bytes [0, 3].
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(), MessageFramer::MessageHeader::header_size(), _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message),
+ Return(MessageFramer::MessageHeader::header_size())))
+ .RetiresOnSaturation();
+ // Read bytes [4, n-1].
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(),
+ serialized_message.size() -
+ MessageFramer::MessageHeader::header_size(),
+ _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message.substr(
+ MessageFramer::MessageHeader::header_size(),
+ serialized_message.size() -
+ MessageFramer::MessageHeader::header_size() - 1)),
+ Return(serialized_message.size() -
+ MessageFramer::MessageHeader::header_size() - 1)))
+ .RetiresOnSaturation();
+ // Read final byte.
+ EXPECT_CALL(mock_socket_, Read(NotNull(), 1, _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message.substr(
+ serialized_message.size() - 1, 1)),
+ Return(1)))
+ .RetiresOnSaturation();
+ EXPECT_CALL(*delegate_, OnMessage(EqualsProto(message)));
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(), MessageFramer::MessageHeader::header_size(), _))
+ .WillOnce(Return(net::ERR_IO_PENDING));
+ transport_->Start();
+}
+
+TEST_F(CastTransportTest, TestReadErrorInHeaderSync) {
+ InSequence s;
+ CastMessage message = CreateCastMessage();
+ std::string serialized_message;
+ EXPECT_TRUE(MessageFramer::Serialize(message, &serialized_message));
+
+ EXPECT_CALL(*delegate_, Start());
+
+ // Read bytes [0, 3].
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(), MessageFramer::MessageHeader::header_size(), _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message),
+ Return(net::ERR_CONNECTION_RESET)))
+ .RetiresOnSaturation();
+ EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR));
+ transport_->Start();
+}
+
+TEST_F(CastTransportTest, TestReadErrorInBodySync) {
+ CastMessage message = CreateCastMessage();
+ std::string serialized_message;
+ EXPECT_TRUE(MessageFramer::Serialize(message, &serialized_message));
+
+ EXPECT_CALL(*delegate_, Start());
+
+ // Read bytes [0, 3].
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(), MessageFramer::MessageHeader::header_size(), _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message),
+ Return(MessageFramer::MessageHeader::header_size())))
+ .RetiresOnSaturation();
+ // Read bytes [4, n-1].
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(),
+ serialized_message.size() -
+ MessageFramer::MessageHeader::header_size(),
+ _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message.substr(
+ MessageFramer::MessageHeader::header_size(),
+ serialized_message.size() -
+ MessageFramer::MessageHeader::header_size() - 1)),
+ Return(net::ERR_CONNECTION_RESET)))
+ .RetiresOnSaturation();
+ EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR));
+ transport_->Start();
+ EXPECT_EQ(proto::SOCKET_READ, logger_->GetLastErrors(kChannelId).event_type);
+ EXPECT_EQ(net::ERR_CONNECTION_RESET,
+ logger_->GetLastErrors(kChannelId).net_return_value);
+}
+
+TEST_F(CastTransportTest, TestReadCorruptedMessageSync) {
+ InSequence s;
+ CastMessage message = CreateCastMessage();
+ std::string serialized_message;
+ EXPECT_TRUE(MessageFramer::Serialize(message, &serialized_message));
+
+ // Corrupt the serialized message body(set it to X's).
+ for (size_t i = MessageFramer::MessageHeader::header_size();
+ i < serialized_message.size();
+ ++i) {
+ serialized_message[i] = 'x';
+ }
+
+ EXPECT_CALL(*delegate_, Start());
+
+ // Read bytes [0, 3].
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(), MessageFramer::MessageHeader::header_size(), _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message),
+ Return(MessageFramer::MessageHeader::header_size())))
+ .RetiresOnSaturation();
+ // Read bytes [4, n].
+ EXPECT_CALL(mock_socket_,
+ Read(NotNull(),
+ serialized_message.size() -
+ MessageFramer::MessageHeader::header_size(),
+ _))
+ .WillOnce(DoAll(FillBufferFromString<0>(serialized_message.substr(
+ MessageFramer::MessageHeader::header_size(),
+ serialized_message.size() -
+ MessageFramer::MessageHeader::header_size() - 1)),
+ Return(serialized_message.size() -
+ MessageFramer::MessageHeader::header_size())))
+ .RetiresOnSaturation();
+ EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_INVALID_MESSAGE));
+ transport_->Start();
+}
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/cast_channel/keep_alive_delegate.cc b/chromium/extensions/browser/api/cast_channel/keep_alive_delegate.cc
new file mode 100644
index 00000000000..fe1bd9167fc
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/keep_alive_delegate.cc
@@ -0,0 +1,206 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/cast_channel/keep_alive_delegate.h"
+
+#include <string>
+#include <utility>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "extensions/browser/api/cast_channel/cast_message_util.h"
+#include "extensions/browser/api/cast_channel/cast_socket.h"
+#include "extensions/browser/api/cast_channel/logger.h"
+#include "extensions/common/api/cast_channel/cast_channel.pb.h"
+#include "extensions/common/api/cast_channel/logging.pb.h"
+#include "net/base/net_errors.h"
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+namespace {
+
+const char kHeartbeatNamespace[] = "urn:x-cast:com.google.cast.tp.heartbeat";
+const char kPingSenderId[] = "chrome";
+const char kPingReceiverId[] = "receiver-0";
+const char kTypeNodeId[] = "type";
+
+// Determines if the JSON-encoded payload is equivalent to
+// { "type": |chk_type| }
+bool NestedPayloadTypeEquals(const std::string& chk_type,
+ const CastMessage& message) {
+ MessageInfo message_info;
+ CastMessageToMessageInfo(message, &message_info);
+ std::string type_json;
+ if (!message_info.data->GetAsString(&type_json)) {
+ return false;
+ }
+ scoped_ptr<base::Value> type_value(base::JSONReader::Read(type_json));
+ if (!type_value.get()) {
+ return false;
+ }
+
+ base::DictionaryValue* type_dict;
+ if (!type_value->GetAsDictionary(&type_dict)) {
+ return false;
+ }
+
+ std::string type_string;
+ return (type_dict->HasKey(kTypeNodeId) &&
+ type_dict->GetString(kTypeNodeId, &type_string) &&
+ type_string == chk_type);
+}
+
+} // namespace
+
+// static
+const char KeepAliveDelegate::kHeartbeatPingType[] = "PING";
+
+// static
+const char KeepAliveDelegate::kHeartbeatPongType[] = "PONG";
+
+// static
+CastMessage KeepAliveDelegate::CreateKeepAliveMessage(
+ const char* message_type) {
+ CastMessage output;
+ output.set_protocol_version(CastMessage::CASTV2_1_0);
+ output.set_source_id(kPingSenderId);
+ output.set_destination_id(kPingReceiverId);
+ output.set_namespace_(kHeartbeatNamespace);
+ base::DictionaryValue type_dict;
+ type_dict.SetString(kTypeNodeId, message_type);
+ if (!base::JSONWriter::Write(type_dict, output.mutable_payload_utf8())) {
+ LOG(ERROR) << "Failed to serialize dictionary.";
+ return output;
+ }
+ output.set_payload_type(
+ CastMessage::PayloadType::CastMessage_PayloadType_STRING);
+ return output;
+}
+
+KeepAliveDelegate::KeepAliveDelegate(
+ CastSocket* socket,
+ scoped_refptr<Logger> logger,
+ scoped_ptr<CastTransport::Delegate> inner_delegate,
+ base::TimeDelta ping_interval,
+ base::TimeDelta liveness_timeout)
+ : started_(false),
+ socket_(socket),
+ logger_(logger),
+ inner_delegate_(std::move(inner_delegate)),
+ liveness_timeout_(liveness_timeout),
+ ping_interval_(ping_interval) {
+ DCHECK(ping_interval_ < liveness_timeout_);
+ DCHECK(inner_delegate_);
+ DCHECK(socket_);
+ ping_message_ = CreateKeepAliveMessage(kHeartbeatPingType);
+ pong_message_ = CreateKeepAliveMessage(kHeartbeatPongType);
+}
+
+KeepAliveDelegate::~KeepAliveDelegate() {
+}
+
+void KeepAliveDelegate::SetTimersForTest(
+ scoped_ptr<base::Timer> injected_ping_timer,
+ scoped_ptr<base::Timer> injected_liveness_timer) {
+ ping_timer_ = std::move(injected_ping_timer);
+ liveness_timer_ = std::move(injected_liveness_timer);
+}
+
+void KeepAliveDelegate::Start() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!started_);
+
+ VLOG(1) << "Starting keep-alive timers.";
+ VLOG(1) << "Ping timeout: " << ping_interval_;
+ VLOG(1) << "Liveness timeout: " << liveness_timeout_;
+
+ // Use injected mock timers, if provided.
+ if (!ping_timer_) {
+ ping_timer_.reset(new base::Timer(true, false));
+ }
+ if (!liveness_timer_) {
+ liveness_timer_.reset(new base::Timer(true, false));
+ }
+
+ ping_timer_->Start(
+ FROM_HERE, ping_interval_,
+ base::Bind(&KeepAliveDelegate::SendKeepAliveMessage,
+ base::Unretained(this), ping_message_, kHeartbeatPingType));
+ liveness_timer_->Start(
+ FROM_HERE, liveness_timeout_,
+ base::Bind(&KeepAliveDelegate::LivenessTimeout, base::Unretained(this)));
+
+ started_ = true;
+ inner_delegate_->Start();
+}
+
+void KeepAliveDelegate::ResetTimers() {
+ DCHECK(started_);
+ ping_timer_->Reset();
+ liveness_timer_->Reset();
+}
+
+void KeepAliveDelegate::SendKeepAliveMessage(const CastMessage& message,
+ const char* message_type) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ VLOG(2) << "Sending " << message_type;
+ socket_->transport()->SendMessage(
+ message, base::Bind(&KeepAliveDelegate::SendKeepAliveMessageComplete,
+ base::Unretained(this), message_type));
+}
+
+void KeepAliveDelegate::SendKeepAliveMessageComplete(const char* message_type,
+ int rv) {
+ VLOG(2) << "Sending " << message_type << " complete, rv=" << rv;
+ if (rv != net::OK) {
+ // An error occurred while sending the ping response.
+ VLOG(1) << "Error sending " << message_type;
+ logger_->LogSocketEventWithRv(socket_->id(), proto::PING_WRITE_ERROR, rv);
+ OnError(cast_channel::CHANNEL_ERROR_SOCKET_ERROR);
+ }
+}
+
+void KeepAliveDelegate::LivenessTimeout() {
+ OnError(cast_channel::CHANNEL_ERROR_PING_TIMEOUT);
+ Stop();
+}
+
+// CastTransport::Delegate interface.
+void KeepAliveDelegate::OnError(ChannelError error_state) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ VLOG(1) << "KeepAlive::OnError: " << error_state;
+ inner_delegate_->OnError(error_state);
+ Stop();
+}
+
+void KeepAliveDelegate::OnMessage(const CastMessage& message) {
+ DCHECK(started_);
+ DCHECK(thread_checker_.CalledOnValidThread());
+ VLOG(2) << "KeepAlive::OnMessage : " << message.payload_utf8();
+
+ ResetTimers();
+
+ if (NestedPayloadTypeEquals(kHeartbeatPingType, message)) {
+ VLOG(2) << "Received PING.";
+ SendKeepAliveMessage(pong_message_, kHeartbeatPongType);
+ } else if (NestedPayloadTypeEquals(kHeartbeatPongType, message)) {
+ VLOG(2) << "Received PONG.";
+ } else {
+ // PING and PONG messages are intentionally suppressed from layers above.
+ inner_delegate_->OnMessage(message);
+ }
+}
+
+void KeepAliveDelegate::Stop() {
+ if (started_) {
+ started_ = false;
+ ping_timer_->Stop();
+ liveness_timer_->Stop();
+ }
+}
+
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/cast_channel/keep_alive_delegate.h b/chromium/extensions/browser/api/cast_channel/keep_alive_delegate.h
new file mode 100644
index 00000000000..27b3738eab3
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/keep_alive_delegate.h
@@ -0,0 +1,117 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_CAST_CHANNEL_KEEP_ALIVE_DELEGATE_H_
+#define EXTENSIONS_BROWSER_API_CAST_CHANNEL_KEEP_ALIVE_DELEGATE_H_
+
+#include "base/macros.h"
+#include "base/threading/thread_checker.h"
+#include "base/timer/timer.h"
+#include "extensions/browser/api/cast_channel/cast_transport.h"
+#include "extensions/common/api/cast_channel/cast_channel.pb.h"
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+
+class CastSocket;
+class Logger;
+
+// Decorator delegate which provides keep-alive functionality.
+// Keep-alive messages are handled by this object; all other messages and
+// errors are passed to |inner_delegate_|.
+class KeepAliveDelegate : public CastTransport::Delegate {
+ public:
+ // |socket|: The socket to be kept alive.
+ // |logger|: The logging object which collects protocol events and error
+ // details.
+ // |inner_delegate|: The delegate which processes all non-keep-alive
+ // messages. This object assumes ownership of
+ // |inner_delegate|.
+ // |ping_interval|: The amount of idle time to wait before sending a PING to
+ // the remote end.
+ // |liveness_timeout|: The amount of idle time to wait before terminating the
+ // connection.
+ KeepAliveDelegate(CastSocket* socket,
+ scoped_refptr<Logger> logger,
+ scoped_ptr<CastTransport::Delegate> inner_delegate,
+ base::TimeDelta ping_interval,
+ base::TimeDelta liveness_timeout);
+
+ ~KeepAliveDelegate() override;
+
+ // Creates a keep-alive message (e.g. PING or PONG).
+ static CastMessage CreateKeepAliveMessage(const char* message_type);
+
+ void SetTimersForTest(scoped_ptr<base::Timer> injected_ping_timer,
+ scoped_ptr<base::Timer> injected_liveness_timer);
+
+ // CastTransport::Delegate implementation.
+ void Start() override;
+ void OnError(ChannelError error_state) override;
+ void OnMessage(const CastMessage& message) override;
+
+ static const char kHeartbeatPingType[];
+ static const char kHeartbeatPongType[];
+
+ private:
+ // Restarts the ping/liveness timeout timers. Called when a message
+ // is received from the remote end.
+ void ResetTimers();
+
+ // Sends a formatted PING or PONG message to the remote side.
+ void SendKeepAliveMessage(const CastMessage& message,
+ const char* message_type);
+
+ // Callback for SendKeepAliveMessage.
+ void SendKeepAliveMessageComplete(const char* message_type, int rv);
+
+ // Called when the liveness timer expires, indicating that the remote
+ // end has not responded within the |liveness_timeout_| interval.
+ void LivenessTimeout();
+
+ // Stops the ping and liveness timers if they are started.
+ // To be called after an error.
+ void Stop();
+
+ // Indicates that Start() was called.
+ bool started_;
+
+ // Socket that is managed by the keep-alive object.
+ CastSocket* socket_;
+
+ // Logging object.
+ scoped_refptr<Logger> logger_;
+
+ // Delegate object which receives all non-keep alive messages.
+ scoped_ptr<CastTransport::Delegate> inner_delegate_;
+
+ // Amount of idle time to wait before disconnecting.
+ base::TimeDelta liveness_timeout_;
+
+ // Amount of idle time to wait before pinging the receiver.
+ base::TimeDelta ping_interval_;
+
+ // Fired when |ping_interval_| is exceeded or when triggered by test code.
+ scoped_ptr<base::Timer> ping_timer_;
+
+ // Fired when |liveness_timer_| is exceeded.
+ scoped_ptr<base::Timer> liveness_timer_;
+
+ // The PING message to send over the wire.
+ CastMessage ping_message_;
+
+ // The PONG message to send over the wire.
+ CastMessage pong_message_;
+
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeepAliveDelegate);
+};
+
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_CAST_CHANNEL_KEEP_ALIVE_DELEGATE_H_
diff --git a/chromium/extensions/browser/api/cast_channel/keep_alive_delegate_unittest.cc b/chromium/extensions/browser/api/cast_channel/keep_alive_delegate_unittest.cc
new file mode 100644
index 00000000000..645d1657f75
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/keep_alive_delegate_unittest.cc
@@ -0,0 +1,169 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/cast_channel/keep_alive_delegate.h"
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/test/simple_test_clock.h"
+#include "base/timer/mock_timer.h"
+#include "extensions/browser/api/cast_channel/cast_test_util.h"
+#include "net/base/net_errors.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+namespace {
+
+const int64_t kTestPingTimeoutMillis = 1000;
+const int64_t kTestLivenessTimeoutMillis = 10000;
+
+// Extends MockTimer with a mockable method ResetTriggered() which permits
+// test code to set GMock expectations for Timer::Reset().
+class MockTimerWithMonitoredReset : public base::MockTimer {
+ public:
+ MockTimerWithMonitoredReset(bool retain_user_task, bool is_repeating)
+ : base::MockTimer(retain_user_task, is_repeating) {}
+ ~MockTimerWithMonitoredReset() override {}
+
+ // Instrumentation point for determining how many times Reset() was called.
+ MOCK_METHOD0(ResetTriggered, void(void));
+ MOCK_METHOD0(Stop, void(void));
+
+ // Passes through the Reset call to the base MockTimer and visits the mock
+ // ResetTriggered method.
+ void Reset() override {
+ base::MockTimer::Reset();
+ ResetTriggered();
+ }
+};
+
+class KeepAliveDelegateTest : public testing::Test {
+ public:
+ KeepAliveDelegateTest() {}
+ ~KeepAliveDelegateTest() override {}
+
+ protected:
+ void SetUp() override {
+ inner_delegate_ = new MockCastTransportDelegate;
+ logger_ = new Logger(scoped_ptr<base::Clock>(new base::SimpleTestClock),
+ base::Time());
+ keep_alive_.reset(new KeepAliveDelegate(
+ &socket_, logger_, make_scoped_ptr(inner_delegate_),
+ base::TimeDelta::FromMilliseconds(kTestPingTimeoutMillis),
+ base::TimeDelta::FromMilliseconds(kTestLivenessTimeoutMillis)));
+ liveness_timer_ = new MockTimerWithMonitoredReset(true, false);
+ ping_timer_ = new MockTimerWithMonitoredReset(true, false);
+ EXPECT_CALL(*liveness_timer_, Stop()).Times(0);
+ EXPECT_CALL(*ping_timer_, Stop()).Times(0);
+ keep_alive_->SetTimersForTest(make_scoped_ptr(ping_timer_),
+ make_scoped_ptr(liveness_timer_));
+ }
+
+ // Runs all pending tasks in the message loop.
+ void RunPendingTasks() {
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ }
+
+ base::MessageLoop message_loop_;
+ MockCastSocket socket_;
+ scoped_ptr<KeepAliveDelegate> keep_alive_;
+ scoped_refptr<Logger> logger_;
+ MockCastTransportDelegate* inner_delegate_;
+ MockTimerWithMonitoredReset* liveness_timer_;
+ MockTimerWithMonitoredReset* ping_timer_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(KeepAliveDelegateTest);
+};
+
+TEST_F(KeepAliveDelegateTest, TestErrorHandledBeforeStarting) {
+ keep_alive_->OnError(CHANNEL_ERROR_CONNECT_ERROR);
+}
+
+TEST_F(KeepAliveDelegateTest, TestPing) {
+ EXPECT_CALL(*socket_.mock_transport(),
+ SendMessage(EqualsProto(KeepAliveDelegate::CreateKeepAliveMessage(
+ KeepAliveDelegate::kHeartbeatPingType)),
+ _))
+ .WillOnce(PostCompletionCallbackTask<1>(net::OK));
+ EXPECT_CALL(*inner_delegate_, Start());
+ EXPECT_CALL(*ping_timer_, ResetTriggered()).Times(2);
+ EXPECT_CALL(*liveness_timer_, ResetTriggered()).Times(2);
+ EXPECT_CALL(*ping_timer_, Stop());
+
+ keep_alive_->Start();
+ ping_timer_->Fire();
+ keep_alive_->OnMessage(KeepAliveDelegate::CreateKeepAliveMessage(
+ KeepAliveDelegate::kHeartbeatPongType));
+ RunPendingTasks();
+}
+
+TEST_F(KeepAliveDelegateTest, TestPingFailed) {
+ EXPECT_CALL(*socket_.mock_transport(),
+ SendMessage(EqualsProto(KeepAliveDelegate::CreateKeepAliveMessage(
+ KeepAliveDelegate::kHeartbeatPingType)),
+ _))
+ .WillOnce(PostCompletionCallbackTask<1>(net::ERR_CONNECTION_RESET));
+ EXPECT_CALL(*inner_delegate_, Start());
+ EXPECT_CALL(*inner_delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR));
+ EXPECT_CALL(*ping_timer_, ResetTriggered()).Times(1);
+ EXPECT_CALL(*liveness_timer_, ResetTriggered()).Times(1);
+ EXPECT_CALL(*liveness_timer_, Stop());
+ EXPECT_CALL(*ping_timer_, Stop()).Times(2);
+
+ keep_alive_->Start();
+ ping_timer_->Fire();
+ RunPendingTasks();
+ EXPECT_EQ(proto::PING_WRITE_ERROR,
+ logger_->GetLastErrors(socket_.id()).event_type);
+ EXPECT_EQ(net::ERR_CONNECTION_RESET,
+ logger_->GetLastErrors(socket_.id()).net_return_value);
+}
+
+TEST_F(KeepAliveDelegateTest, TestPingAndLivenessTimeout) {
+ EXPECT_CALL(*socket_.mock_transport(),
+ SendMessage(EqualsProto(KeepAliveDelegate::CreateKeepAliveMessage(
+ KeepAliveDelegate::kHeartbeatPingType)),
+ _))
+ .WillOnce(PostCompletionCallbackTask<1>(net::OK));
+ EXPECT_CALL(*inner_delegate_, OnError(CHANNEL_ERROR_PING_TIMEOUT));
+ EXPECT_CALL(*inner_delegate_, Start());
+ EXPECT_CALL(*ping_timer_, ResetTriggered()).Times(1);
+ EXPECT_CALL(*liveness_timer_, ResetTriggered()).Times(1);
+ EXPECT_CALL(*liveness_timer_, Stop()).Times(2);
+ EXPECT_CALL(*ping_timer_, Stop()).Times(2);
+
+ keep_alive_->Start();
+ ping_timer_->Fire();
+ liveness_timer_->Fire();
+ RunPendingTasks();
+}
+
+TEST_F(KeepAliveDelegateTest, TestResetTimersAndPassthroughAllOtherTraffic) {
+ CastMessage other_message =
+ KeepAliveDelegate::CreateKeepAliveMessage("NEITHER_PING_NOR_PONG");
+
+ EXPECT_CALL(*inner_delegate_, OnMessage(EqualsProto(other_message)));
+ EXPECT_CALL(*inner_delegate_, Start());
+ EXPECT_CALL(*ping_timer_, ResetTriggered()).Times(2);
+ EXPECT_CALL(*liveness_timer_, ResetTriggered()).Times(2);
+
+ keep_alive_->Start();
+ keep_alive_->OnMessage(other_message);
+ RunPendingTasks();
+}
+
+} // namespace
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/cast_channel/logger.cc b/chromium/extensions/browser/api/cast_channel/logger.cc
new file mode 100644
index 00000000000..1ea0e24f10c
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/logger.cc
@@ -0,0 +1,367 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/cast_channel/logger.h"
+
+#include <stdint.h>
+
+#include <string>
+#include <utility>
+
+#include "base/strings/string_util.h"
+#include "base/time/clock.h"
+#include "extensions/browser/api/cast_channel/cast_auth_util.h"
+#include "extensions/browser/api/cast_channel/cast_socket.h"
+#include "extensions/browser/api/cast_channel/logger_util.h"
+#include "net/base/net_errors.h"
+#include "third_party/zlib/zlib.h"
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+
+using net::IPEndPoint;
+using proto::AggregatedSocketEvent;
+using proto::EventType;
+using proto::Log;
+using proto::SocketEvent;
+
+namespace {
+
+const char* kInternalNamespacePrefix = "com.google.cast";
+
+proto::ChallengeReplyErrorType ChallegeReplyErrorToProto(
+ AuthResult::ErrorType error_type) {
+ switch (error_type) {
+ case AuthResult::ERROR_NONE:
+ return proto::CHALLENGE_REPLY_ERROR_NONE;
+ case AuthResult::ERROR_PEER_CERT_EMPTY:
+ return proto::CHALLENGE_REPLY_ERROR_PEER_CERT_EMPTY;
+ case AuthResult::ERROR_WRONG_PAYLOAD_TYPE:
+ return proto::CHALLENGE_REPLY_ERROR_WRONG_PAYLOAD_TYPE;
+ case AuthResult::ERROR_NO_PAYLOAD:
+ return proto::CHALLENGE_REPLY_ERROR_NO_PAYLOAD;
+ case AuthResult::ERROR_PAYLOAD_PARSING_FAILED:
+ return proto::CHALLENGE_REPLY_ERROR_PAYLOAD_PARSING_FAILED;
+ case AuthResult::ERROR_MESSAGE_ERROR:
+ return proto::CHALLENGE_REPLY_ERROR_MESSAGE_ERROR;
+ case AuthResult::ERROR_NO_RESPONSE:
+ return proto::CHALLENGE_REPLY_ERROR_NO_RESPONSE;
+ case AuthResult::ERROR_FINGERPRINT_NOT_FOUND:
+ return proto::CHALLENGE_REPLY_ERROR_FINGERPRINT_NOT_FOUND;
+ case AuthResult::ERROR_CERT_PARSING_FAILED:
+ return proto::CHALLENGE_REPLY_ERROR_CERT_PARSING_FAILED;
+ case AuthResult::ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA:
+ return proto::CHALLENGE_REPLY_ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA;
+ case AuthResult::ERROR_CANNOT_EXTRACT_PUBLIC_KEY:
+ return proto::CHALLENGE_REPLY_ERROR_CANNOT_EXTRACT_PUBLIC_KEY;
+ case AuthResult::ERROR_SIGNED_BLOBS_MISMATCH:
+ return proto::CHALLENGE_REPLY_ERROR_SIGNED_BLOBS_MISMATCH;
+ default:
+ NOTREACHED();
+ return proto::CHALLENGE_REPLY_ERROR_NONE;
+ }
+}
+
+scoped_ptr<char[]> Compress(const std::string& input, size_t* length) {
+ *length = 0;
+ z_stream stream = {0};
+ int result = deflateInit2(&stream,
+ Z_DEFAULT_COMPRESSION,
+ Z_DEFLATED,
+ // 16 is added to produce a gzip header + trailer.
+ MAX_WBITS + 16,
+ 8, // memLevel = 8 is default.
+ Z_DEFAULT_STRATEGY);
+ DCHECK_EQ(Z_OK, result);
+
+ size_t out_size = deflateBound(&stream, input.size());
+ scoped_ptr<char[]> out(new char[out_size]);
+
+ stream.next_in = reinterpret_cast<uint8_t*>(const_cast<char*>(input.data()));
+ stream.avail_in = input.size();
+ stream.next_out = reinterpret_cast<uint8_t*>(out.get());
+ stream.avail_out = out_size;
+
+ // Do a one-shot compression. This will return Z_STREAM_END only if |output|
+ // is large enough to hold all compressed data.
+ result = deflate(&stream, Z_FINISH);
+
+ bool success = (result == Z_STREAM_END);
+
+ if (!success)
+ VLOG(2) << "deflate() failed. Result: " << result;
+
+ result = deflateEnd(&stream);
+ DCHECK(result == Z_OK || result == Z_DATA_ERROR);
+
+ if (success)
+ *length = out_size - stream.avail_out;
+
+ return out;
+}
+
+// Propagate any error fields set in |event| to |last_errors|. If any error
+// field in |event| is set, then also set |last_errors->event_type|.
+void MaybeSetLastErrors(const SocketEvent& event, LastErrors* last_errors) {
+ if (event.has_net_return_value() &&
+ event.net_return_value() < net::ERR_IO_PENDING) {
+ last_errors->net_return_value = event.net_return_value();
+ last_errors->event_type = event.type();
+ }
+ if (event.has_challenge_reply_error_type()) {
+ last_errors->challenge_reply_error_type =
+ event.challenge_reply_error_type();
+ last_errors->event_type = event.type();
+ }
+}
+
+} // namespace
+
+Logger::AggregatedSocketEventLog::AggregatedSocketEventLog() {
+}
+
+Logger::AggregatedSocketEventLog::~AggregatedSocketEventLog() {
+}
+
+Logger::Logger(scoped_ptr<base::Clock> clock, base::Time unix_epoch_time)
+ : clock_(std::move(clock)), unix_epoch_time_(unix_epoch_time) {
+ DCHECK(clock_);
+
+ // Logger may not be necessarily be created on the IO thread, but logging
+ // happens exclusively there.
+ thread_checker_.DetachFromThread();
+}
+
+Logger::~Logger() {
+}
+
+void Logger::LogNewSocketEvent(const CastSocket& cast_socket) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ SocketEvent event = CreateEvent(proto::CAST_SOCKET_CREATED);
+ AggregatedSocketEvent& aggregated_socket_event =
+ LogSocketEvent(cast_socket.id(), event);
+
+ const net::IPAddress& ip = cast_socket.ip_endpoint().address();
+ DCHECK(ip.IsValid());
+ aggregated_socket_event.set_endpoint_id(ip.bytes().back());
+ aggregated_socket_event.set_channel_auth_type(cast_socket.channel_auth() ==
+ CHANNEL_AUTH_TYPE_SSL
+ ? proto::SSL
+ : proto::SSL_VERIFIED);
+}
+
+void Logger::LogSocketEvent(int channel_id, EventType event_type) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ LogSocketEventWithDetails(channel_id, event_type, std::string());
+}
+
+void Logger::LogSocketEventWithDetails(int channel_id,
+ EventType event_type,
+ const std::string& details) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ SocketEvent event = CreateEvent(event_type);
+ if (!details.empty())
+ event.set_details(details);
+
+ LogSocketEvent(channel_id, event);
+}
+
+void Logger::LogSocketEventWithRv(int channel_id,
+ EventType event_type,
+ int rv) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ SocketEvent event = CreateEvent(event_type);
+ event.set_net_return_value(rv);
+
+ AggregatedSocketEvent& aggregated_socket_event =
+ LogSocketEvent(channel_id, event);
+
+ if ((event_type == proto::SOCKET_READ || event_type == proto::SOCKET_WRITE) &&
+ rv > 0) {
+ if (event_type == proto::SOCKET_READ) {
+ aggregated_socket_event.set_bytes_read(
+ aggregated_socket_event.bytes_read() + rv);
+ } else {
+ aggregated_socket_event.set_bytes_written(
+ aggregated_socket_event.bytes_written() + rv);
+ }
+ }
+}
+
+void Logger::LogSocketReadyState(int channel_id, proto::ReadyState new_state) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ SocketEvent event = CreateEvent(proto::READY_STATE_CHANGED);
+ event.set_ready_state(new_state);
+
+ LogSocketEvent(channel_id, event);
+}
+
+void Logger::LogSocketConnectState(int channel_id,
+ proto::ConnectionState new_state) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ SocketEvent event = CreateEvent(proto::CONNECTION_STATE_CHANGED);
+ event.set_connection_state(new_state);
+
+ LogSocketEvent(channel_id, event);
+}
+
+void Logger::LogSocketReadState(int channel_id, proto::ReadState new_state) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ SocketEvent event = CreateEvent(proto::READ_STATE_CHANGED);
+ event.set_read_state(new_state);
+
+ LogSocketEvent(channel_id, event);
+}
+
+void Logger::LogSocketWriteState(int channel_id, proto::WriteState new_state) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ SocketEvent event = CreateEvent(proto::WRITE_STATE_CHANGED);
+ event.set_write_state(new_state);
+
+ LogSocketEvent(channel_id, event);
+}
+
+void Logger::LogSocketErrorState(int channel_id, proto::ErrorState new_state) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ SocketEvent event = CreateEvent(proto::ERROR_STATE_CHANGED);
+ event.set_error_state(new_state);
+
+ LogSocketEvent(channel_id, event);
+}
+
+void Logger::LogSocketEventForMessage(int channel_id,
+ EventType event_type,
+ const std::string& message_namespace,
+ const std::string& details) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ SocketEvent event = CreateEvent(event_type);
+ if (base::StartsWith(message_namespace, kInternalNamespacePrefix,
+ base::CompareCase::INSENSITIVE_ASCII))
+ event.set_message_namespace(message_namespace);
+ event.set_details(details);
+
+ LogSocketEvent(channel_id, event);
+}
+
+void Logger::LogSocketChallengeReplyEvent(int channel_id,
+ const AuthResult& auth_result) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ SocketEvent event = CreateEvent(proto::AUTH_CHALLENGE_REPLY);
+ event.set_challenge_reply_error_type(
+ ChallegeReplyErrorToProto(auth_result.error_type));
+
+ LogSocketEvent(channel_id, event);
+}
+
+SocketEvent Logger::CreateEvent(EventType event_type) {
+ SocketEvent event;
+ event.set_type(event_type);
+ event.set_timestamp_micros(
+ (clock_->Now() - unix_epoch_time_).InMicroseconds());
+ return event;
+}
+
+AggregatedSocketEvent& Logger::LogSocketEvent(int channel_id,
+ const SocketEvent& socket_event) {
+ AggregatedSocketEventLogMap::iterator it =
+ aggregated_socket_events_.find(channel_id);
+ if (it == aggregated_socket_events_.end()) {
+ if (aggregated_socket_events_.size() >= kMaxSocketsToLog) {
+ AggregatedSocketEventLogMap::iterator erase_it =
+ aggregated_socket_events_.begin();
+
+ log_.set_num_evicted_aggregated_socket_events(
+ log_.num_evicted_aggregated_socket_events() + 1);
+ log_.set_num_evicted_socket_events(
+ log_.num_evicted_socket_events() +
+ erase_it->second->socket_events.size());
+
+ aggregated_socket_events_.erase(erase_it);
+ }
+
+ it = aggregated_socket_events_
+ .insert(std::make_pair(
+ channel_id, make_linked_ptr(new AggregatedSocketEventLog)))
+ .first;
+ it->second->aggregated_socket_event.set_id(channel_id);
+ }
+
+ std::deque<proto::SocketEvent>& socket_events = it->second->socket_events;
+ if (socket_events.size() >= kMaxEventsPerSocket) {
+ socket_events.pop_front();
+ log_.set_num_evicted_socket_events(log_.num_evicted_socket_events() + 1);
+ }
+ socket_events.push_back(socket_event);
+
+ MaybeSetLastErrors(socket_event, &(it->second->last_errors));
+
+ return it->second->aggregated_socket_event;
+}
+
+scoped_ptr<char[]> Logger::GetLogs(size_t* length) const {
+ *length = 0;
+
+ Log log;
+ // Copy "global" values from |log_|. Don't use |log_| directly since this
+ // function is const.
+ log.CopyFrom(log_);
+
+ for (AggregatedSocketEventLogMap::const_iterator it =
+ aggregated_socket_events_.begin();
+ it != aggregated_socket_events_.end();
+ ++it) {
+ AggregatedSocketEvent* new_aggregated_socket_event =
+ log.add_aggregated_socket_event();
+ new_aggregated_socket_event->CopyFrom(it->second->aggregated_socket_event);
+
+ const std::deque<SocketEvent>& socket_events = it->second->socket_events;
+ for (std::deque<SocketEvent>::const_iterator socket_event_it =
+ socket_events.begin();
+ socket_event_it != socket_events.end();
+ ++socket_event_it) {
+ SocketEvent* socket_event =
+ new_aggregated_socket_event->add_socket_event();
+ socket_event->CopyFrom(*socket_event_it);
+ }
+ }
+
+ std::string serialized;
+ if (!log.SerializeToString(&serialized)) {
+ VLOG(2) << "Failed to serialized proto to string.";
+ return scoped_ptr<char[]>();
+ }
+
+ return Compress(serialized, length);
+}
+
+void Logger::Reset() {
+ aggregated_socket_events_.clear();
+ log_.Clear();
+}
+
+LastErrors Logger::GetLastErrors(int channel_id) const {
+ AggregatedSocketEventLogMap::const_iterator it =
+ aggregated_socket_events_.find(channel_id);
+ if (it != aggregated_socket_events_.end()) {
+ return it->second->last_errors;
+ } else {
+ return LastErrors();
+ }
+}
+
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/cast_channel/logger.h b/chromium/extensions/browser/api/cast_channel/logger.h
new file mode 100644
index 00000000000..90c32ebf955
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/logger.h
@@ -0,0 +1,147 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_CAST_CHANNEL_LOGGER_H_
+#define EXTENSIONS_BROWSER_API_CAST_CHANNEL_LOGGER_H_
+
+#include <stddef.h>
+
+#include <deque>
+#include <map>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "extensions/browser/api/cast_channel/logger_util.h"
+#include "extensions/common/api/cast_channel/logging.pb.h"
+#include "net/base/ip_endpoint.h"
+
+namespace base {
+class Clock;
+}
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+
+struct AuthResult;
+class CastSocket;
+
+static const int kMaxSocketsToLog = 50;
+static const int kMaxEventsPerSocket = 2000;
+
+// Logs information of each channel and sockets and exports the log as
+// a blob. Logger is done on the IO thread.
+class Logger : public base::RefCounted<Logger> {
+ public:
+ // |clock|: Clock used for generating timestamps for the events. Owned by
+ // this class.
+ // |unix_epoch_time|: The Time that corresponds to the Unix epoch.
+ // Can be set to other values (e.g. zero) for testing purposes.
+ //
+ // See crbug.com/518951 for information on why base::Clock
+ // is used instead of base::TickClock.
+ Logger(scoped_ptr<base::Clock> clock, base::Time unix_epoch_time);
+
+ // For newly created sockets. Will create an event and log a
+ // CAST_SOCKET_CREATED event.
+ void LogNewSocketEvent(const CastSocket& cast_socket);
+
+ void LogSocketEvent(int channel_id, proto::EventType event_type);
+ void LogSocketEventWithDetails(int channel_id,
+ proto::EventType event_type,
+ const std::string& details);
+
+ // For events that involves socket / crypto operations that returns a value.
+ void LogSocketEventWithRv(int channel_id,
+ proto::EventType event_type,
+ int rv);
+
+ // For *_STATE_CHANGED events.
+ void LogSocketReadyState(int channel_id, proto::ReadyState new_state);
+ void LogSocketConnectState(int channel_id, proto::ConnectionState new_state);
+ void LogSocketReadState(int channel_id, proto::ReadState new_state);
+ void LogSocketWriteState(int channel_id, proto::WriteState new_state);
+ void LogSocketErrorState(int channel_id, proto::ErrorState new_state);
+
+ // For AUTH_CHALLENGE_REPLY event.
+ void LogSocketChallengeReplyEvent(int channel_id,
+ const AuthResult& auth_result);
+
+ void LogSocketEventForMessage(int channel_id,
+ proto::EventType event_type,
+ const std::string& message_namespace,
+ const std::string& details);
+
+ // Assembles logs collected so far and return it as a serialized Log proto,
+ // compressed in gzip format.
+ // If serialization or compression failed, returns nullptr.
+ // |length|: If successful, assigned with size of compressed content.
+ scoped_ptr<char[]> GetLogs(size_t* length) const;
+
+ // Clears the internal map.
+ void Reset();
+
+ // Returns the last errors logged for |channel_id|. If the the logs for
+ // |channel_id| are evicted before this is called, returns a LastErrors with
+ // no errors. This may happen if errors are logged and retrieved in different
+ // tasks.
+ LastErrors GetLastErrors(int channel_id) const;
+
+ private:
+ friend class base::RefCounted<Logger>;
+ ~Logger();
+
+ struct AggregatedSocketEventLog {
+ public:
+ AggregatedSocketEventLog();
+ ~AggregatedSocketEventLog();
+
+ // Partially constructed AggregatedSocketEvent proto populated by Logger.
+ // Contains top level info such as channel ID, IP end point and channel
+ // auth type.
+ proto::AggregatedSocketEvent aggregated_socket_event;
+ // Events to be assigned to the AggregatedSocketEvent proto. Contains the
+ // most recent |kMaxEventsPerSocket| entries. The oldest events are
+ // evicted as new events are logged.
+ std::deque<proto::SocketEvent> socket_events;
+
+ // The most recent errors logged for the socket.
+ LastErrors last_errors;
+ };
+
+ typedef std::map<int, linked_ptr<AggregatedSocketEventLog> >
+ AggregatedSocketEventLogMap;
+
+ // Returns a SocketEvent proto with common fields (EventType, timestamp)
+ // populated.
+ proto::SocketEvent CreateEvent(proto::EventType event_type);
+
+ // Records |event| associated with |channel_id|.
+ // If the internal map is already logging maximum number of sockets and this
+ // is a new socket, the socket with the smallest channel id will be discarded.
+ // Returns a reference to the AggregatedSocketEvent proto created/modified.
+ proto::AggregatedSocketEvent& LogSocketEvent(
+ int channel_id,
+ const proto::SocketEvent& socket_event);
+
+ scoped_ptr<base::Clock> clock_;
+ AggregatedSocketEventLogMap aggregated_socket_events_;
+ base::Time unix_epoch_time_;
+
+ // Log proto holding global statistics.
+ proto::Log log_;
+
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(Logger);
+};
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_CAST_CHANNEL_LOGGER_H_
diff --git a/chromium/extensions/browser/api/cast_channel/logger_unittest.cc b/chromium/extensions/browser/api/cast_channel/logger_unittest.cc
new file mode 100644
index 00000000000..a9daf7bb33e
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/logger_unittest.cc
@@ -0,0 +1,326 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+
+#include "base/test/simple_test_clock.h"
+#include "extensions/browser/api/cast_channel/cast_auth_util.h"
+#include "extensions/browser/api/cast_channel/logger.h"
+#include "extensions/browser/api/cast_channel/logger_util.h"
+#include "net/base/net_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/zlib.h"
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+
+using proto::AggregatedSocketEvent;
+using proto::EventType;
+using proto::Log;
+using proto::SocketEvent;
+
+class CastChannelLoggerTest : public testing::Test {
+ public:
+ // |logger_| will take ownership of |clock_|.
+ CastChannelLoggerTest()
+ : clock_(new base::SimpleTestClock),
+ logger_(new Logger(scoped_ptr<base::Clock>(clock_), base::Time())) {}
+ ~CastChannelLoggerTest() override {}
+
+ bool Uncompress(const char* input, int length, std::string* output) {
+ z_stream stream = {0};
+
+ stream.next_in = reinterpret_cast<uint8_t*>(const_cast<char*>(input));
+ stream.avail_in = length;
+ stream.next_out = reinterpret_cast<uint8_t*>(&(*output)[0]);
+ stream.avail_out = output->size();
+
+ bool success = false;
+ while (stream.avail_in > 0 && stream.avail_out > 0) {
+ // 16 is added to read in gzip format.
+ int result = inflateInit2(&stream, MAX_WBITS + 16);
+ DCHECK_EQ(Z_OK, result);
+
+ result = inflate(&stream, Z_FINISH);
+ success = (result == Z_STREAM_END);
+ if (!success) {
+ DVLOG(2) << "inflate() failed. Result: " << result;
+ break;
+ }
+
+ result = inflateEnd(&stream);
+ DCHECK(result == Z_OK);
+ }
+
+ if (stream.avail_in == 0) {
+ success = true;
+ output->resize(output->size() - stream.avail_out);
+ }
+ return success;
+ }
+
+ scoped_ptr<Log> GetLog() {
+ size_t length = 0;
+ scoped_ptr<char[]> output = logger_->GetLogs(&length);
+ if (!output.get())
+ return scoped_ptr<Log>();
+
+ // 20kb should be enough for test purposes.
+ std::string uncompressed(20000, 0);
+ if (!Uncompress(output.get(), length, &uncompressed))
+ return scoped_ptr<Log>();
+
+ scoped_ptr<Log> log(new Log);
+ if (!log->ParseFromString(uncompressed))
+ return scoped_ptr<Log>();
+
+ return log;
+ }
+
+ protected:
+ base::SimpleTestClock* clock_;
+ scoped_refptr<Logger> logger_;
+};
+
+TEST_F(CastChannelLoggerTest, BasicLogging) {
+ logger_->LogSocketEvent(1, EventType::CAST_SOCKET_CREATED);
+ clock_->Advance(base::TimeDelta::FromMicroseconds(1));
+ logger_->LogSocketEventWithDetails(
+ 1, EventType::TCP_SOCKET_CONNECT, "TCP socket");
+ clock_->Advance(base::TimeDelta::FromMicroseconds(1));
+ logger_->LogSocketEvent(2, EventType::CAST_SOCKET_CREATED);
+ clock_->Advance(base::TimeDelta::FromMicroseconds(1));
+ logger_->LogSocketEventWithRv(1, EventType::SSL_SOCKET_CONNECT, -1);
+ clock_->Advance(base::TimeDelta::FromMicroseconds(1));
+ logger_->LogSocketEventForMessage(
+ 2, EventType::MESSAGE_ENQUEUED, "foo_namespace", "details");
+ clock_->Advance(base::TimeDelta::FromMicroseconds(1));
+
+ AuthResult auth_result = AuthResult::CreateWithParseError(
+ "No response", AuthResult::ERROR_NO_RESPONSE);
+
+ logger_->LogSocketChallengeReplyEvent(2, auth_result);
+ clock_->Advance(base::TimeDelta::FromMicroseconds(1));
+
+ auth_result =
+ AuthResult("Parsing failed", AuthResult::ERROR_CERT_PARSING_FAILED);
+ logger_->LogSocketChallengeReplyEvent(2, auth_result);
+
+ LastErrors last_errors = logger_->GetLastErrors(2);
+ EXPECT_EQ(last_errors.event_type, proto::AUTH_CHALLENGE_REPLY);
+ EXPECT_EQ(last_errors.net_return_value, net::OK);
+ EXPECT_EQ(last_errors.challenge_reply_error_type,
+ proto::CHALLENGE_REPLY_ERROR_CERT_PARSING_FAILED);
+
+ scoped_ptr<Log> log = GetLog();
+ ASSERT_TRUE(log);
+
+ ASSERT_EQ(2, log->aggregated_socket_event_size());
+ {
+ const AggregatedSocketEvent& aggregated_socket_event =
+ log->aggregated_socket_event(0);
+ EXPECT_EQ(1, aggregated_socket_event.id());
+ EXPECT_EQ(3, aggregated_socket_event.socket_event_size());
+ {
+ const SocketEvent& event = aggregated_socket_event.socket_event(0);
+ EXPECT_EQ(EventType::CAST_SOCKET_CREATED, event.type());
+ EXPECT_EQ(0, event.timestamp_micros());
+ }
+ {
+ const SocketEvent& event = aggregated_socket_event.socket_event(1);
+ EXPECT_EQ(EventType::TCP_SOCKET_CONNECT, event.type());
+ EXPECT_EQ(1, event.timestamp_micros());
+ EXPECT_EQ("TCP socket", event.details());
+ }
+ {
+ const SocketEvent& event = aggregated_socket_event.socket_event(2);
+ EXPECT_EQ(EventType::SSL_SOCKET_CONNECT, event.type());
+ EXPECT_EQ(3, event.timestamp_micros());
+ EXPECT_EQ(-1, event.net_return_value());
+ }
+ }
+ {
+ const AggregatedSocketEvent& aggregated_socket_event =
+ log->aggregated_socket_event(1);
+ EXPECT_EQ(2, aggregated_socket_event.id());
+ EXPECT_EQ(4, aggregated_socket_event.socket_event_size());
+ {
+ const SocketEvent& event = aggregated_socket_event.socket_event(0);
+ EXPECT_EQ(EventType::CAST_SOCKET_CREATED, event.type());
+ EXPECT_EQ(2, event.timestamp_micros());
+ }
+ {
+ const SocketEvent& event = aggregated_socket_event.socket_event(1);
+ EXPECT_EQ(EventType::MESSAGE_ENQUEUED, event.type());
+ EXPECT_EQ(4, event.timestamp_micros());
+ EXPECT_FALSE(event.has_message_namespace());
+ EXPECT_EQ("details", event.details());
+ }
+ {
+ const SocketEvent& event = aggregated_socket_event.socket_event(2);
+ EXPECT_EQ(EventType::AUTH_CHALLENGE_REPLY, event.type());
+ EXPECT_EQ(5, event.timestamp_micros());
+ EXPECT_EQ(proto::CHALLENGE_REPLY_ERROR_NO_RESPONSE,
+ event.challenge_reply_error_type());
+ EXPECT_FALSE(event.has_net_return_value());
+ EXPECT_FALSE(event.has_nss_error_code());
+ }
+ {
+ const SocketEvent& event = aggregated_socket_event.socket_event(3);
+ EXPECT_EQ(EventType::AUTH_CHALLENGE_REPLY, event.type());
+ EXPECT_EQ(6, event.timestamp_micros());
+ EXPECT_EQ(proto::CHALLENGE_REPLY_ERROR_CERT_PARSING_FAILED,
+ event.challenge_reply_error_type());
+ EXPECT_FALSE(event.has_net_return_value());
+ EXPECT_FALSE(event.has_nss_error_code());
+ }
+ }
+}
+
+TEST_F(CastChannelLoggerTest, LogLastErrorEvents) {
+ // Net return value is set to an error
+ logger_->LogSocketEventWithRv(
+ 1, EventType::TCP_SOCKET_CONNECT, net::ERR_CONNECTION_FAILED);
+
+ LastErrors last_errors = logger_->GetLastErrors(1);
+ EXPECT_EQ(last_errors.event_type, proto::TCP_SOCKET_CONNECT);
+ EXPECT_EQ(last_errors.net_return_value, net::ERR_CONNECTION_FAILED);
+
+ // Challenge reply error set
+ clock_->Advance(base::TimeDelta::FromMicroseconds(1));
+ AuthResult auth_result = AuthResult::CreateWithParseError(
+ "Some error", AuthResult::ErrorType::ERROR_PEER_CERT_EMPTY);
+
+ logger_->LogSocketChallengeReplyEvent(2, auth_result);
+ last_errors = logger_->GetLastErrors(2);
+ EXPECT_EQ(last_errors.event_type, proto::AUTH_CHALLENGE_REPLY);
+ EXPECT_EQ(last_errors.challenge_reply_error_type,
+ proto::CHALLENGE_REPLY_ERROR_PEER_CERT_EMPTY);
+
+ // Logging a non-error event does not set the LastErrors for the channel.
+ clock_->Advance(base::TimeDelta::FromMicroseconds(1));
+ logger_->LogSocketEventWithRv(3, EventType::TCP_SOCKET_CONNECT, net::OK);
+ last_errors = logger_->GetLastErrors(3);
+ EXPECT_EQ(last_errors.event_type, proto::EVENT_TYPE_UNKNOWN);
+ EXPECT_EQ(last_errors.net_return_value, net::OK);
+ EXPECT_EQ(last_errors.challenge_reply_error_type,
+ proto::CHALLENGE_REPLY_ERROR_NONE);
+
+ // Now log a challenge reply error. LastErrors will be set.
+ clock_->Advance(base::TimeDelta::FromMicroseconds(1));
+ auth_result =
+ AuthResult("Some error failed", AuthResult::ERROR_WRONG_PAYLOAD_TYPE);
+ logger_->LogSocketChallengeReplyEvent(3, auth_result);
+ last_errors = logger_->GetLastErrors(3);
+ EXPECT_EQ(last_errors.event_type, proto::AUTH_CHALLENGE_REPLY);
+ EXPECT_EQ(last_errors.challenge_reply_error_type,
+ proto::CHALLENGE_REPLY_ERROR_WRONG_PAYLOAD_TYPE);
+
+ // Logging a non-error event does not change the LastErrors for the channel.
+ clock_->Advance(base::TimeDelta::FromMicroseconds(1));
+ logger_->LogSocketEventWithRv(3, EventType::TCP_SOCKET_CONNECT, net::OK);
+ last_errors = logger_->GetLastErrors(3);
+ EXPECT_EQ(last_errors.event_type, proto::AUTH_CHALLENGE_REPLY);
+ EXPECT_EQ(last_errors.challenge_reply_error_type,
+ proto::CHALLENGE_REPLY_ERROR_WRONG_PAYLOAD_TYPE);
+}
+
+TEST_F(CastChannelLoggerTest, LogSocketReadWrite) {
+ logger_->LogSocketEventWithRv(1, EventType::SOCKET_READ, 50);
+ clock_->Advance(base::TimeDelta::FromMicroseconds(1));
+ logger_->LogSocketEventWithRv(1, EventType::SOCKET_READ, 30);
+ clock_->Advance(base::TimeDelta::FromMicroseconds(1));
+ logger_->LogSocketEventWithRv(1, EventType::SOCKET_READ, -1);
+ clock_->Advance(base::TimeDelta::FromMicroseconds(1));
+ logger_->LogSocketEventWithRv(1, EventType::SOCKET_WRITE, 20);
+ clock_->Advance(base::TimeDelta::FromMicroseconds(1));
+
+ logger_->LogSocketEventWithRv(2, EventType::SOCKET_READ, 100);
+ clock_->Advance(base::TimeDelta::FromMicroseconds(1));
+ logger_->LogSocketEventWithRv(2, EventType::SOCKET_WRITE, 100);
+ clock_->Advance(base::TimeDelta::FromMicroseconds(1));
+ logger_->LogSocketEventWithRv(2, EventType::SOCKET_WRITE, -5);
+ clock_->Advance(base::TimeDelta::FromMicroseconds(1));
+
+ scoped_ptr<Log> log = GetLog();
+ ASSERT_TRUE(log);
+
+ ASSERT_EQ(2, log->aggregated_socket_event_size());
+ {
+ const AggregatedSocketEvent& aggregated_socket_event =
+ log->aggregated_socket_event(0);
+ EXPECT_EQ(1, aggregated_socket_event.id());
+ EXPECT_EQ(4, aggregated_socket_event.socket_event_size());
+ EXPECT_EQ(80, aggregated_socket_event.bytes_read());
+ EXPECT_EQ(20, aggregated_socket_event.bytes_written());
+ }
+ {
+ const AggregatedSocketEvent& aggregated_socket_event =
+ log->aggregated_socket_event(1);
+ EXPECT_EQ(2, aggregated_socket_event.id());
+ EXPECT_EQ(3, aggregated_socket_event.socket_event_size());
+ EXPECT_EQ(100, aggregated_socket_event.bytes_read());
+ EXPECT_EQ(100, aggregated_socket_event.bytes_written());
+ }
+}
+
+TEST_F(CastChannelLoggerTest, TooManySockets) {
+ for (int i = 0; i < kMaxSocketsToLog + 5; i++) {
+ logger_->LogSocketEvent(i, EventType::CAST_SOCKET_CREATED);
+ }
+
+ scoped_ptr<Log> log = GetLog();
+ ASSERT_TRUE(log);
+
+ ASSERT_EQ(kMaxSocketsToLog, log->aggregated_socket_event_size());
+ EXPECT_EQ(5, log->num_evicted_aggregated_socket_events());
+ EXPECT_EQ(5, log->num_evicted_socket_events());
+
+ const AggregatedSocketEvent& aggregated_socket_event =
+ log->aggregated_socket_event(0);
+ EXPECT_EQ(5, aggregated_socket_event.id());
+}
+
+TEST_F(CastChannelLoggerTest, TooManyEvents) {
+ for (int i = 0; i < kMaxEventsPerSocket + 5; i++) {
+ logger_->LogSocketEvent(1, EventType::CAST_SOCKET_CREATED);
+ clock_->Advance(base::TimeDelta::FromMicroseconds(1));
+ }
+
+ scoped_ptr<Log> log = GetLog();
+ ASSERT_TRUE(log);
+
+ ASSERT_EQ(1, log->aggregated_socket_event_size());
+ EXPECT_EQ(0, log->num_evicted_aggregated_socket_events());
+ EXPECT_EQ(5, log->num_evicted_socket_events());
+
+ const AggregatedSocketEvent& aggregated_socket_event =
+ log->aggregated_socket_event(0);
+ ASSERT_EQ(kMaxEventsPerSocket, aggregated_socket_event.socket_event_size());
+ EXPECT_EQ(5, aggregated_socket_event.socket_event(0).timestamp_micros());
+}
+
+TEST_F(CastChannelLoggerTest, Reset) {
+ logger_->LogSocketEvent(1, EventType::CAST_SOCKET_CREATED);
+
+ scoped_ptr<Log> log = GetLog();
+ ASSERT_TRUE(log);
+
+ EXPECT_EQ(1, log->aggregated_socket_event_size());
+
+ logger_->Reset();
+
+ log = GetLog();
+ ASSERT_TRUE(log);
+
+ EXPECT_EQ(0, log->aggregated_socket_event_size());
+}
+
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/cast_channel/logger_util.cc b/chromium/extensions/browser/api/cast_channel/logger_util.cc
new file mode 100644
index 00000000000..35e2c107266
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/logger_util.cc
@@ -0,0 +1,67 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/cast_channel/logger_util.h"
+#include "extensions/common/api/cast_channel/logging.pb.h"
+#include "net/base/net_errors.h"
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+LastErrors::LastErrors()
+ : event_type(proto::EVENT_TYPE_UNKNOWN),
+ challenge_reply_error_type(proto::CHALLENGE_REPLY_ERROR_NONE),
+ net_return_value(net::OK) {}
+
+LastErrors::~LastErrors() {
+}
+
+proto::ErrorState ErrorStateToProto(ChannelError state) {
+ switch (state) {
+ case CHANNEL_ERROR_NONE:
+ return proto::CHANNEL_ERROR_NONE;
+ case CHANNEL_ERROR_CHANNEL_NOT_OPEN:
+ return proto::CHANNEL_ERROR_CHANNEL_NOT_OPEN;
+ case CHANNEL_ERROR_AUTHENTICATION_ERROR:
+ return proto::CHANNEL_ERROR_AUTHENTICATION_ERROR;
+ case CHANNEL_ERROR_CONNECT_ERROR:
+ return proto::CHANNEL_ERROR_CONNECT_ERROR;
+ case CHANNEL_ERROR_SOCKET_ERROR:
+ return proto::CHANNEL_ERROR_SOCKET_ERROR;
+ case CHANNEL_ERROR_TRANSPORT_ERROR:
+ return proto::CHANNEL_ERROR_TRANSPORT_ERROR;
+ case CHANNEL_ERROR_INVALID_MESSAGE:
+ return proto::CHANNEL_ERROR_INVALID_MESSAGE;
+ case CHANNEL_ERROR_INVALID_CHANNEL_ID:
+ return proto::CHANNEL_ERROR_INVALID_CHANNEL_ID;
+ case CHANNEL_ERROR_CONNECT_TIMEOUT:
+ return proto::CHANNEL_ERROR_CONNECT_TIMEOUT;
+ case CHANNEL_ERROR_UNKNOWN:
+ return proto::CHANNEL_ERROR_UNKNOWN;
+ default:
+ NOTREACHED();
+ return proto::CHANNEL_ERROR_NONE;
+ }
+}
+
+proto::ReadyState ReadyStateToProto(ReadyState state) {
+ switch (state) {
+ case READY_STATE_NONE:
+ return proto::READY_STATE_NONE;
+ case READY_STATE_CONNECTING:
+ return proto::READY_STATE_CONNECTING;
+ case READY_STATE_OPEN:
+ return proto::READY_STATE_OPEN;
+ case READY_STATE_CLOSING:
+ return proto::READY_STATE_CLOSING;
+ case READY_STATE_CLOSED:
+ return proto::READY_STATE_CLOSED;
+ default:
+ NOTREACHED();
+ return proto::READY_STATE_NONE;
+ }
+}
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/cast_channel/logger_util.h b/chromium/extensions/browser/api/cast_channel/logger_util.h
new file mode 100644
index 00000000000..2b5de5b41f4
--- /dev/null
+++ b/chromium/extensions/browser/api/cast_channel/logger_util.h
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_CAST_CHANNEL_LOGGER_UTIL_H_
+#define EXTENSIONS_BROWSER_API_CAST_CHANNEL_LOGGER_UTIL_H_
+
+#include "extensions/common/api/cast_channel.h"
+#include "extensions/common/api/cast_channel/logging.pb.h"
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+// Converts an IDL "ChannelError" to a proto enum "ErrorState".
+proto::ErrorState ErrorStateToProto(ChannelError state);
+
+// Converts an IDL "ReadyState" to a proto enum "ReadyState".
+proto::ReadyState ReadyStateToProto(ReadyState state);
+
+// Holds the most recent errors encountered by a CastSocket.
+struct LastErrors {
+ public:
+ LastErrors();
+ ~LastErrors();
+
+ // The most recent event that occurred at the time of the error.
+ proto::EventType event_type;
+
+ // The most recent ChallengeReplyErrorType logged for the socket.
+ proto::ChallengeReplyErrorType challenge_reply_error_type;
+
+ // The most recent net_return_value logged for the socket.
+ int net_return_value;
+};
+
+} // namespace cast_channel
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_CAST_CHANNEL_LOGGER_UTIL_H_
diff --git a/chromium/extensions/browser/api/declarative/declarative_api.cc b/chromium/extensions/browser/api/declarative/declarative_api.cc
new file mode 100644
index 00000000000..183ca3e9cd8
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative/declarative_api.cc
@@ -0,0 +1,226 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/declarative/declarative_api.h"
+
+#include <stddef.h>
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task_runner_util.h"
+#include "base/values.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/browser/api/declarative/rules_registry_service.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/guest_view/web_view/web_view_constants.h"
+#include "extensions/browser/guest_view/web_view/web_view_guest.h"
+#include "extensions/common/api/events.h"
+#include "extensions/common/extension_api.h"
+#include "extensions/common/permissions/permissions_data.h"
+
+using extensions::api::events::Rule;
+
+namespace AddRules = extensions::api::events::Event::AddRules;
+namespace GetRules = extensions::api::events::Event::GetRules;
+namespace RemoveRules = extensions::api::events::Event::RemoveRules;
+
+namespace extensions {
+
+namespace {
+
+const char kDeclarativeEventPrefix[] = "declarative";
+
+void ConvertBinaryDictionaryValuesToBase64(base::DictionaryValue* dict);
+
+// Encodes |binary| as base64 and returns a new StringValue populated with the
+// encoded string.
+scoped_ptr<base::StringValue> ConvertBinaryToBase64(base::BinaryValue* binary) {
+ std::string binary_data = std::string(binary->GetBuffer(), binary->GetSize());
+ std::string data64;
+ base::Base64Encode(binary_data, &data64);
+ return scoped_ptr<base::StringValue>(new base::StringValue(data64));
+}
+
+// Parses through |args| replacing any BinaryValues with base64 encoded
+// StringValues. Recurses over any nested ListValues, and calls
+// ConvertBinaryDictionaryValuesToBase64 for any nested DictionaryValues.
+void ConvertBinaryListElementsToBase64(base::ListValue* args) {
+ size_t index = 0;
+ for (base::ListValue::iterator iter = args->begin(); iter != args->end();
+ ++iter, ++index) {
+ if ((*iter)->IsType(base::Value::TYPE_BINARY)) {
+ base::BinaryValue* binary = NULL;
+ if (args->GetBinary(index, &binary))
+ args->Set(index, ConvertBinaryToBase64(binary).release());
+ } else if ((*iter)->IsType(base::Value::TYPE_LIST)) {
+ base::ListValue* list;
+ (*iter)->GetAsList(&list);
+ ConvertBinaryListElementsToBase64(list);
+ } else if ((*iter)->IsType(base::Value::TYPE_DICTIONARY)) {
+ base::DictionaryValue* dict;
+ (*iter)->GetAsDictionary(&dict);
+ ConvertBinaryDictionaryValuesToBase64(dict);
+ }
+ }
+}
+
+// Parses through |dict| replacing any BinaryValues with base64 encoded
+// StringValues. Recurses over any nested DictionaryValues, and calls
+// ConvertBinaryListElementsToBase64 for any nested ListValues.
+void ConvertBinaryDictionaryValuesToBase64(base::DictionaryValue* dict) {
+ for (base::DictionaryValue::Iterator iter(*dict); !iter.IsAtEnd();
+ iter.Advance()) {
+ if (iter.value().IsType(base::Value::TYPE_BINARY)) {
+ base::BinaryValue* binary = NULL;
+ if (dict->GetBinary(iter.key(), &binary))
+ dict->Set(iter.key(), ConvertBinaryToBase64(binary).release());
+ } else if (iter.value().IsType(base::Value::TYPE_LIST)) {
+ const base::ListValue* list;
+ iter.value().GetAsList(&list);
+ ConvertBinaryListElementsToBase64(const_cast<base::ListValue*>(list));
+ } else if (iter.value().IsType(base::Value::TYPE_DICTIONARY)) {
+ const base::DictionaryValue* dict;
+ iter.value().GetAsDictionary(&dict);
+ ConvertBinaryDictionaryValuesToBase64(
+ const_cast<base::DictionaryValue*>(dict));
+ }
+ }
+}
+
+} // namespace
+
+RulesFunction::RulesFunction()
+ : rules_registry_(NULL) {
+}
+
+RulesFunction::~RulesFunction() {}
+
+bool RulesFunction::HasPermission() {
+ std::string event_name;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &event_name));
+ int web_view_instance_id = 0;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(1, &web_view_instance_id));
+
+ // <webview> embedders use the declarativeWebRequest API via
+ // <webview>.onRequest.
+ if (web_view_instance_id != 0 &&
+ extension_->permissions_data()->HasAPIPermission(
+ extensions::APIPermission::kWebView))
+ return true;
+ return ExtensionFunction::HasPermission();
+}
+
+bool RulesFunction::RunAsync() {
+ std::string event_name;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &event_name));
+
+ int web_view_instance_id = 0;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(1, &web_view_instance_id));
+ int embedder_process_id = render_frame_host()->GetProcess()->GetID();
+
+ bool from_web_view = web_view_instance_id != 0;
+ // If we are not operating on a particular <webview>, then the key is 0.
+ int rules_registry_id = RulesRegistryService::kDefaultRulesRegistryID;
+ if (from_web_view) {
+ // Sample event names:
+ // webViewInternal.declarativeWebRequest.onRequest.
+ // webViewInternal.declarativeWebRequest.onMessage.
+ // The "webViewInternal." prefix is removed from the event name.
+ std::size_t found = event_name.find(kDeclarativeEventPrefix);
+ EXTENSION_FUNCTION_VALIDATE(found != std::string::npos);
+ event_name = event_name.substr(found);
+
+ rules_registry_id = WebViewGuest::GetOrGenerateRulesRegistryID(
+ embedder_process_id, web_view_instance_id);
+ }
+
+ // The following call will return a NULL pointer for apps_shell, but should
+ // never be called there anyways.
+ rules_registry_ = RulesRegistryService::Get(browser_context())->
+ GetRulesRegistry(rules_registry_id, event_name);
+ DCHECK(rules_registry_.get());
+ // Raw access to this function is not available to extensions, therefore
+ // there should never be a request for a nonexisting rules registry.
+ EXTENSION_FUNCTION_VALIDATE(rules_registry_.get());
+
+ if (content::BrowserThread::CurrentlyOn(rules_registry_->owner_thread())) {
+ bool success = RunAsyncOnCorrectThread();
+ SendResponse(success);
+ } else {
+ scoped_refptr<base::SingleThreadTaskRunner> thread_task_runner =
+ content::BrowserThread::GetMessageLoopProxyForThread(
+ rules_registry_->owner_thread());
+ base::PostTaskAndReplyWithResult(
+ thread_task_runner.get(), FROM_HERE,
+ base::Bind(&RulesFunction::RunAsyncOnCorrectThread, this),
+ base::Bind(&RulesFunction::SendResponse, this));
+ }
+
+ return true;
+}
+
+bool EventsEventAddRulesFunction::RunAsyncOnCorrectThread() {
+ ConvertBinaryListElementsToBase64(args_.get());
+ scoped_ptr<AddRules::Params> params(AddRules::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ // TODO(devlin): Remove the dependency on linked_ptr here.
+ std::vector<linked_ptr<api::events::Rule>> linked_rules;
+ for (api::events::Rule& rule : params->rules) {
+ linked_rules.push_back(
+ make_linked_ptr(new api::events::Rule(std::move(rule))));
+ }
+ error_ = rules_registry_->AddRules(extension_id(), linked_rules);
+
+ if (error_.empty()) {
+ scoped_ptr<base::ListValue> rules_value(new base::ListValue());
+ for (const auto& rule : linked_rules)
+ rules_value->Append(rule->ToValue());
+ SetResult(std::move(rules_value));
+ }
+
+ return error_.empty();
+}
+
+bool EventsEventRemoveRulesFunction::RunAsyncOnCorrectThread() {
+ scoped_ptr<RemoveRules::Params> params(RemoveRules::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ if (params->rule_identifiers.get()) {
+ error_ = rules_registry_->RemoveRules(extension_id(),
+ *params->rule_identifiers);
+ } else {
+ error_ = rules_registry_->RemoveAllRules(extension_id());
+ }
+
+ return error_.empty();
+}
+
+bool EventsEventGetRulesFunction::RunAsyncOnCorrectThread() {
+ scoped_ptr<GetRules::Params> params(GetRules::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ std::vector<linked_ptr<Rule> > rules;
+ if (params->rule_identifiers.get()) {
+ rules_registry_->GetRules(
+ extension_id(), *params->rule_identifiers, &rules);
+ } else {
+ rules_registry_->GetAllRules(extension_id(), &rules);
+ }
+
+ scoped_ptr<base::ListValue> rules_value(new base::ListValue());
+ for (const auto& rule : rules)
+ rules_value->Append(rule->ToValue());
+ SetResult(std::move(rules_value));
+
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/declarative/declarative_api.h b/chromium/extensions/browser/api/declarative/declarative_api.h
new file mode 100644
index 00000000000..7f3b1a2dfe9
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative/declarative_api.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DECLARATIVE_DECLARATIVE_API_H_
+#define EXTENSIONS_BROWSER_API_DECLARATIVE_DECLARATIVE_API_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "extensions/browser/api/declarative/rules_registry.h"
+#include "extensions/browser/extension_function.h"
+
+namespace extensions {
+
+class RulesFunction : public AsyncExtensionFunction {
+ public:
+ RulesFunction();
+
+ protected:
+ ~RulesFunction() override;
+
+ // ExtensionFunction:
+ bool HasPermission() override;
+ bool RunAsync() override;
+
+ // Concrete implementation of the RulesFunction that is being called
+ // on the thread on which the respective RulesRegistry lives.
+ // Returns false in case of errors.
+ virtual bool RunAsyncOnCorrectThread() = 0;
+
+ scoped_refptr<RulesRegistry> rules_registry_;
+};
+
+class EventsEventAddRulesFunction : public RulesFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("events.addRules", EVENTS_ADDRULES)
+
+ protected:
+ ~EventsEventAddRulesFunction() override {}
+
+ // RulesFunction:
+ bool RunAsyncOnCorrectThread() override;
+};
+
+class EventsEventRemoveRulesFunction : public RulesFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("events.removeRules", EVENTS_REMOVERULES)
+
+ protected:
+ ~EventsEventRemoveRulesFunction() override {}
+
+ // RulesFunction:
+ bool RunAsyncOnCorrectThread() override;
+};
+
+class EventsEventGetRulesFunction : public RulesFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("events.getRules", EVENTS_GETRULES)
+
+ protected:
+ ~EventsEventGetRulesFunction() override {}
+
+ // RulesFunction:
+ bool RunAsyncOnCorrectThread() override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DECLARATIVE_DECLARATIVE_API_H_
diff --git a/chromium/extensions/browser/api/declarative/declarative_rule.h b/chromium/extensions/browser/api/declarative/declarative_rule.h
new file mode 100644
index 00000000000..6e8e9098097
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative/declarative_rule.h
@@ -0,0 +1,506 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// DeclarativeRule<>, DeclarativeConditionSet<>, and DeclarativeActionSet<>
+// templates usable with multiple different declarativeFoo systems. These are
+// templated on the Condition and Action types that define the behavior of a
+// particular declarative event.
+
+#ifndef EXTENSIONS_BROWSER_API_DECLARATIVE_DECLARATIVE_RULE_H__
+#define EXTENSIONS_BROWSER_API_DECLARATIVE_DECLARATIVE_RULE_H__
+
+#include <limits>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/stl_util.h"
+#include "base/time/time.h"
+#include "components/url_matcher/url_matcher.h"
+#include "extensions/common/api/events.h"
+#include "extensions/common/extension.h"
+
+namespace base {
+class Time;
+class Value;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+// This class stores a set of conditions that may be part of a DeclarativeRule.
+// If any condition is fulfilled, the Actions of the DeclarativeRule can be
+// triggered.
+//
+// ConditionT should be immutable after creation. It must define the following
+// members:
+//
+// // Arguments passed through from DeclarativeConditionSet::Create.
+// static scoped_ptr<ConditionT> Create(
+// const Extension* extension,
+// URLMatcherConditionFactory* url_matcher_condition_factory,
+// // Except this argument gets elements of the Values array.
+// const base::Value& definition,
+// std::string* error);
+// // If the Condition needs to be filtered by some URLMatcherConditionSets,
+// // append them to |condition_sets|.
+// // DeclarativeConditionSet::GetURLMatcherConditionSets forwards here.
+// void GetURLMatcherConditionSets(
+// URLMatcherConditionSet::Vector* condition_sets);
+// // |match_data| passed through from DeclarativeConditionSet::IsFulfilled.
+// bool IsFulfilled(const ConditionT::MatchData& match_data);
+template<typename ConditionT>
+class DeclarativeConditionSet {
+ public:
+ typedef std::vector<scoped_ptr<base::Value>> Values;
+ typedef std::vector<linked_ptr<const ConditionT> > Conditions;
+ typedef typename Conditions::const_iterator const_iterator;
+
+ // Factory method that creates a DeclarativeConditionSet for |extension|
+ // according to the JSON array |conditions| passed by the extension API. Sets
+ // |error| and returns NULL in case of an error.
+ static scoped_ptr<DeclarativeConditionSet> Create(
+ const Extension* extension,
+ url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory,
+ const Values& condition_values,
+ std::string* error);
+
+ const Conditions& conditions() const {
+ return conditions_;
+ }
+
+ const_iterator begin() const { return conditions_.begin(); }
+ const_iterator end() const { return conditions_.end(); }
+
+ // If |url_match_trigger| is not -1, this function looks for a condition
+ // with this URLMatcherConditionSet, and forwards to that condition's
+ // IsFulfilled(|match_data|). If there is no such condition, then false is
+ // returned. If |url_match_trigger| is -1, this function returns whether any
+ // of the conditions without URL attributes is satisfied.
+ bool IsFulfilled(url_matcher::URLMatcherConditionSet::ID url_match_trigger,
+ const typename ConditionT::MatchData& match_data) const;
+
+ // Appends the URLMatcherConditionSet from all conditions to |condition_sets|.
+ void GetURLMatcherConditionSets(
+ url_matcher::URLMatcherConditionSet::Vector* condition_sets) const;
+
+ // Returns whether there are some conditions without UrlFilter attributes.
+ bool HasConditionsWithoutUrls() const {
+ return !conditions_without_urls_.empty();
+ }
+
+ private:
+ typedef std::map<url_matcher::URLMatcherConditionSet::ID, const ConditionT*>
+ URLMatcherIdToCondition;
+
+ DeclarativeConditionSet(
+ const Conditions& conditions,
+ const URLMatcherIdToCondition& match_id_to_condition,
+ const std::vector<const ConditionT*>& conditions_without_urls);
+
+ const URLMatcherIdToCondition match_id_to_condition_;
+ const Conditions conditions_;
+ const std::vector<const ConditionT*> conditions_without_urls_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeclarativeConditionSet);
+};
+
+// Immutable container for multiple actions.
+//
+// ActionT should be immutable after creation. It must define the following
+// members:
+//
+// // Arguments passed through from ActionSet::Create.
+// static scoped_ptr<ActionT> Create(
+// const Extension* extension,
+// // Except this argument gets elements of the Values array.
+// const base::Value& definition,
+// std::string* error, bool* bad_message);
+// void Apply(const std::string& extension_id,
+// const base::Time& extension_install_time,
+// // Contains action-type-specific in/out parameters.
+// typename ActionT::ApplyInfo* apply_info) const;
+// // Only needed if the RulesRegistry calls DeclarativeActionSet::Revert().
+// void Revert(const std::string& extension_id,
+// const base::Time& extension_install_time,
+// // Contains action-type-specific in/out parameters.
+// typename ActionT::ApplyInfo* apply_info) const;
+// // Return the minimum priority of rules that can be evaluated after this
+// // action runs. A suitable default value is MIN_INT.
+// int minimum_priority() const;
+//
+// TODO(battre): As DeclarativeActionSet can become the single owner of all
+// actions, we can optimize here by making some of them singletons (e.g. Cancel
+// actions).
+template<typename ActionT>
+class DeclarativeActionSet {
+ public:
+ typedef std::vector<scoped_ptr<base::Value>> Values;
+ typedef std::vector<scoped_refptr<const ActionT> > Actions;
+
+ explicit DeclarativeActionSet(const Actions& actions);
+
+ // Factory method that instantiates a DeclarativeActionSet for |extension|
+ // according to |actions| which represents the array of actions received from
+ // the extension API.
+ static scoped_ptr<DeclarativeActionSet> Create(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ const Values& action_values,
+ std::string* error,
+ bool* bad_message);
+
+ // Rules call this method when their conditions are fulfilled.
+ void Apply(const std::string& extension_id,
+ const base::Time& extension_install_time,
+ typename ActionT::ApplyInfo* apply_info) const;
+
+ // Rules call this method when their conditions are fulfilled, but Apply has
+ // already been called.
+ void Reapply(const std::string& extension_id,
+ const base::Time& extension_install_time,
+ typename ActionT::ApplyInfo* apply_info) const;
+
+ // Rules call this method when they have stateful conditions, and those
+ // conditions stop being fulfilled. Rules with event-based conditions (e.g. a
+ // network request happened) will never Revert() an action.
+ void Revert(const std::string& extension_id,
+ const base::Time& extension_install_time,
+ typename ActionT::ApplyInfo* apply_info) const;
+
+ // Returns the minimum priority of rules that may be evaluated after
+ // this rule. Defaults to MIN_INT.
+ int GetMinimumPriority() const;
+
+ const Actions& actions() const { return actions_; }
+
+ private:
+ const Actions actions_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeclarativeActionSet);
+};
+
+// Representation of a rule of a declarative API:
+// https://developer.chrome.com/beta/extensions/events.html#declarative.
+// Generally a RulesRegistry will hold a collection of Rules for a given
+// declarative API and contain the logic for matching and applying them.
+//
+// See DeclarativeConditionSet and DeclarativeActionSet for the requirements on
+// ConditionT and ActionT.
+template<typename ConditionT, typename ActionT>
+class DeclarativeRule {
+ public:
+ typedef std::string ExtensionId;
+ typedef std::string RuleId;
+ typedef std::pair<ExtensionId, RuleId> GlobalRuleId;
+ typedef int Priority;
+ typedef DeclarativeConditionSet<ConditionT> ConditionSet;
+ typedef DeclarativeActionSet<ActionT> ActionSet;
+ typedef extensions::api::events::Rule JsonRule;
+ typedef std::vector<std::string> Tags;
+
+ // Checks whether the set of |conditions| and |actions| are consistent.
+ // Returns true in case of consistency and MUST set |error| otherwise.
+ typedef base::Callback<bool(const ConditionSet* conditions,
+ const ActionSet* actions,
+ std::string* error)> ConsistencyChecker;
+
+ DeclarativeRule(const GlobalRuleId& id,
+ const Tags& tags,
+ base::Time extension_installation_time,
+ scoped_ptr<ConditionSet> conditions,
+ scoped_ptr<ActionSet> actions,
+ Priority priority);
+
+ // Creates a DeclarativeRule for |extension| given a json definition. The
+ // format of each condition and action's json is up to the specific ConditionT
+ // and ActionT. |extension| may be NULL in tests.
+ //
+ // Before constructing the final rule, calls check_consistency(conditions,
+ // actions, error) and returns NULL if it fails. Pass NULL if no consistency
+ // check is needed. If |error| is empty, the translation was successful and
+ // the returned rule is internally consistent.
+ static scoped_ptr<DeclarativeRule> Create(
+ url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory,
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ base::Time extension_installation_time,
+ linked_ptr<JsonRule> rule,
+ ConsistencyChecker check_consistency,
+ std::string* error);
+
+ const GlobalRuleId& id() const { return id_; }
+ const Tags& tags() const { return tags_; }
+ const std::string& extension_id() const { return id_.first; }
+ const ConditionSet& conditions() const { return *conditions_; }
+ const ActionSet& actions() const { return *actions_; }
+ Priority priority() const { return priority_; }
+
+ // Calls actions().Apply(extension_id(), extension_installation_time_,
+ // apply_info). This function should only be called when the conditions_ are
+ // fulfilled (from a semantic point of view; no harm is done if this function
+ // is called at other times for testing purposes).
+ void Apply(typename ActionT::ApplyInfo* apply_info) const;
+
+ // Returns the minimum priority of rules that may be evaluated after
+ // this rule. Defaults to MIN_INT. Only valid if the conditions of this rule
+ // are fulfilled.
+ Priority GetMinimumPriority() const;
+
+ private:
+ GlobalRuleId id_;
+ Tags tags_;
+ base::Time extension_installation_time_; // For precedences of rules.
+ scoped_ptr<ConditionSet> conditions_;
+ scoped_ptr<ActionSet> actions_;
+ Priority priority_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeclarativeRule);
+};
+
+// Implementation details below here.
+
+//
+// DeclarativeConditionSet
+//
+
+template<typename ConditionT>
+bool DeclarativeConditionSet<ConditionT>::IsFulfilled(
+ url_matcher::URLMatcherConditionSet::ID url_match_trigger,
+ const typename ConditionT::MatchData& match_data) const {
+ if (url_match_trigger == -1) {
+ // Invalid trigger -- indication that we should only check conditions
+ // without URL attributes.
+ for (const ConditionT* condition : conditions_without_urls_) {
+ if (condition->IsFulfilled(match_data))
+ return true;
+ }
+ return false;
+ }
+
+ typename URLMatcherIdToCondition::const_iterator triggered =
+ match_id_to_condition_.find(url_match_trigger);
+ return (triggered != match_id_to_condition_.end() &&
+ triggered->second->IsFulfilled(match_data));
+}
+
+template<typename ConditionT>
+void DeclarativeConditionSet<ConditionT>::GetURLMatcherConditionSets(
+ url_matcher::URLMatcherConditionSet::Vector* condition_sets) const {
+ for (const linked_ptr<const ConditionT>& condition : conditions_)
+ condition->GetURLMatcherConditionSets(condition_sets);
+}
+
+// static
+template <typename ConditionT>
+scoped_ptr<DeclarativeConditionSet<ConditionT>>
+DeclarativeConditionSet<ConditionT>::Create(
+ const Extension* extension,
+ url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory,
+ const Values& condition_values,
+ std::string* error) {
+ Conditions result;
+
+ for (const scoped_ptr<base::Value>& value : condition_values) {
+ CHECK(value.get());
+ scoped_ptr<ConditionT> condition = ConditionT::Create(
+ extension, url_matcher_condition_factory, *value, error);
+ if (!error->empty())
+ return scoped_ptr<DeclarativeConditionSet>();
+ result.push_back(make_linked_ptr(condition.release()));
+ }
+
+ URLMatcherIdToCondition match_id_to_condition;
+ std::vector<const ConditionT*> conditions_without_urls;
+ url_matcher::URLMatcherConditionSet::Vector condition_sets;
+
+ for (const linked_ptr<const ConditionT>& condition : result) {
+ condition_sets.clear();
+ condition->GetURLMatcherConditionSets(&condition_sets);
+ if (condition_sets.empty()) {
+ conditions_without_urls.push_back(condition.get());
+ } else {
+ for (const scoped_refptr<url_matcher::URLMatcherConditionSet>& match_set :
+ condition_sets)
+ match_id_to_condition[match_set->id()] = condition.get();
+ }
+ }
+
+ return make_scoped_ptr(new DeclarativeConditionSet(
+ result, match_id_to_condition, conditions_without_urls));
+}
+
+template<typename ConditionT>
+DeclarativeConditionSet<ConditionT>::DeclarativeConditionSet(
+ const Conditions& conditions,
+ const URLMatcherIdToCondition& match_id_to_condition,
+ const std::vector<const ConditionT*>& conditions_without_urls)
+ : match_id_to_condition_(match_id_to_condition),
+ conditions_(conditions),
+ conditions_without_urls_(conditions_without_urls) {}
+
+//
+// DeclarativeActionSet
+//
+
+template<typename ActionT>
+DeclarativeActionSet<ActionT>::DeclarativeActionSet(const Actions& actions)
+ : actions_(actions) {}
+
+// static
+template <typename ActionT>
+scoped_ptr<DeclarativeActionSet<ActionT>> DeclarativeActionSet<ActionT>::Create(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ const Values& action_values,
+ std::string* error,
+ bool* bad_message) {
+ *error = "";
+ *bad_message = false;
+ Actions result;
+
+ for (const scoped_ptr<base::Value>& value : action_values) {
+ CHECK(value.get());
+ scoped_refptr<const ActionT> action =
+ ActionT::Create(browser_context, extension, *value, error, bad_message);
+ if (!error->empty() || *bad_message)
+ return scoped_ptr<DeclarativeActionSet>();
+ result.push_back(action);
+ }
+
+ return scoped_ptr<DeclarativeActionSet>(new DeclarativeActionSet(result));
+}
+
+template<typename ActionT>
+void DeclarativeActionSet<ActionT>::Apply(
+ const std::string& extension_id,
+ const base::Time& extension_install_time,
+ typename ActionT::ApplyInfo* apply_info) const {
+ for (const scoped_refptr<const ActionT>& action : actions_)
+ action->Apply(extension_id, extension_install_time, apply_info);
+}
+
+template<typename ActionT>
+void DeclarativeActionSet<ActionT>::Reapply(
+ const std::string& extension_id,
+ const base::Time& extension_install_time,
+ typename ActionT::ApplyInfo* apply_info) const {
+ for (const scoped_refptr<const ActionT>& action : actions_)
+ action->Reapply(extension_id, extension_install_time, apply_info);
+}
+
+template<typename ActionT>
+void DeclarativeActionSet<ActionT>::Revert(
+ const std::string& extension_id,
+ const base::Time& extension_install_time,
+ typename ActionT::ApplyInfo* apply_info) const {
+ for (const scoped_refptr<const ActionT>& action : actions_)
+ action->Revert(extension_id, extension_install_time, apply_info);
+}
+
+template<typename ActionT>
+int DeclarativeActionSet<ActionT>::GetMinimumPriority() const {
+ int minimum_priority = std::numeric_limits<int>::min();
+ for (typename Actions::const_iterator i = actions_.begin();
+ i != actions_.end(); ++i) {
+ minimum_priority = std::max(minimum_priority, (*i)->minimum_priority());
+ }
+ return minimum_priority;
+}
+
+//
+// DeclarativeRule
+//
+
+template<typename ConditionT, typename ActionT>
+DeclarativeRule<ConditionT, ActionT>::DeclarativeRule(
+ const GlobalRuleId& id,
+ const Tags& tags,
+ base::Time extension_installation_time,
+ scoped_ptr<ConditionSet> conditions,
+ scoped_ptr<ActionSet> actions,
+ Priority priority)
+ : id_(id),
+ tags_(tags),
+ extension_installation_time_(extension_installation_time),
+ conditions_(conditions.release()),
+ actions_(actions.release()),
+ priority_(priority) {
+ CHECK(conditions_.get());
+ CHECK(actions_.get());
+}
+
+// static
+template<typename ConditionT, typename ActionT>
+scoped_ptr<DeclarativeRule<ConditionT, ActionT> >
+DeclarativeRule<ConditionT, ActionT>::Create(
+ url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory,
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ base::Time extension_installation_time,
+ linked_ptr<JsonRule> rule,
+ ConsistencyChecker check_consistency,
+ std::string* error) {
+ scoped_ptr<DeclarativeRule> error_result;
+
+ scoped_ptr<ConditionSet> conditions = ConditionSet::Create(
+ extension, url_matcher_condition_factory, rule->conditions, error);
+ if (!error->empty())
+ return std::move(error_result);
+ CHECK(conditions.get());
+
+ bool bad_message = false;
+ scoped_ptr<ActionSet> actions =
+ ActionSet::Create(
+ browser_context, extension, rule->actions, error, &bad_message);
+ if (bad_message) {
+ // TODO(battre) Export concept of bad_message to caller, the extension
+ // should be killed in case it is true.
+ *error = "An action of a rule set had an invalid "
+ "structure that should have been caught by the JSON validator.";
+ return std::move(error_result);
+ }
+ if (!error->empty() || bad_message)
+ return std::move(error_result);
+ CHECK(actions.get());
+
+ if (!check_consistency.is_null() &&
+ !check_consistency.Run(conditions.get(), actions.get(), error)) {
+ DCHECK(!error->empty());
+ return std::move(error_result);
+ }
+
+ CHECK(rule->priority.get());
+ int priority = *(rule->priority);
+
+ GlobalRuleId rule_id(extension->id(), *(rule->id));
+ Tags tags = rule->tags ? *rule->tags : Tags();
+ return scoped_ptr<DeclarativeRule>(
+ new DeclarativeRule(rule_id, tags, extension_installation_time,
+ std::move(conditions), std::move(actions), priority));
+}
+
+template<typename ConditionT, typename ActionT>
+void DeclarativeRule<ConditionT, ActionT>::Apply(
+ typename ActionT::ApplyInfo* apply_info) const {
+ return actions_->Apply(extension_id(),
+ extension_installation_time_,
+ apply_info);
+}
+
+template<typename ConditionT, typename ActionT>
+int DeclarativeRule<ConditionT, ActionT>::GetMinimumPriority() const {
+ return actions_->GetMinimumPriority();
+}
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DECLARATIVE_DECLARATIVE_RULE_H__
diff --git a/chromium/extensions/browser/api/declarative/declarative_rule_unittest.cc b/chromium/extensions/browser/api/declarative/declarative_rule_unittest.cc
new file mode 100644
index 00000000000..93ec58b4026
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative/declarative_rule_unittest.cc
@@ -0,0 +1,433 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/declarative/declarative_rule.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/test/values_test_util.h"
+#include "base/values.h"
+#include "components/url_matcher/url_matcher_constants.h"
+#include "extensions/common/extension_builder.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::test::ParseJson;
+using url_matcher::URLMatcher;
+using url_matcher::URLMatcherConditionFactory;
+using url_matcher::URLMatcherConditionSet;
+
+namespace extensions {
+
+namespace {
+
+scoped_ptr<base::DictionaryValue> SimpleManifest() {
+ return DictionaryBuilder()
+ .Set("name", "extension")
+ .Set("manifest_version", 2)
+ .Set("version", "1.0")
+ .Build();
+}
+
+} // namespace
+
+struct RecordingCondition {
+ typedef int MatchData;
+
+ URLMatcherConditionFactory* factory;
+ scoped_ptr<base::Value> value;
+
+ void GetURLMatcherConditionSets(
+ URLMatcherConditionSet::Vector* condition_sets) const {
+ // No condition sets.
+ }
+
+ static scoped_ptr<RecordingCondition> Create(
+ const Extension* extension,
+ URLMatcherConditionFactory* url_matcher_condition_factory,
+ const base::Value& condition,
+ std::string* error) {
+ const base::DictionaryValue* dict = NULL;
+ if (condition.GetAsDictionary(&dict) && dict->HasKey("bad_key")) {
+ *error = "Found error key";
+ return scoped_ptr<RecordingCondition>();
+ }
+
+ scoped_ptr<RecordingCondition> result(new RecordingCondition());
+ result->factory = url_matcher_condition_factory;
+ result->value.reset(condition.DeepCopy());
+ return result;
+ }
+};
+typedef DeclarativeConditionSet<RecordingCondition> RecordingConditionSet;
+
+TEST(DeclarativeConditionTest, ErrorConditionSet) {
+ URLMatcher matcher;
+ RecordingConditionSet::Values conditions;
+ conditions.push_back(ParseJson("{\"key\": 1}"));
+ conditions.push_back(ParseJson("{\"bad_key\": 2}"));
+
+ std::string error;
+ scoped_ptr<RecordingConditionSet> result = RecordingConditionSet::Create(
+ NULL, matcher.condition_factory(), conditions, &error);
+ EXPECT_EQ("Found error key", error);
+ ASSERT_FALSE(result);
+}
+
+TEST(DeclarativeConditionTest, CreateConditionSet) {
+ URLMatcher matcher;
+ RecordingConditionSet::Values conditions;
+ conditions.push_back(ParseJson("{\"key\": 1}"));
+ conditions.push_back(ParseJson("[\"val1\", 2]"));
+
+ // Test insertion
+ std::string error;
+ scoped_ptr<RecordingConditionSet> result = RecordingConditionSet::Create(
+ NULL, matcher.condition_factory(), conditions, &error);
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(result);
+ EXPECT_EQ(2u, result->conditions().size());
+
+ EXPECT_EQ(matcher.condition_factory(), result->conditions()[0]->factory);
+ EXPECT_TRUE(ParseJson("{\"key\": 1}")->Equals(
+ result->conditions()[0]->value.get()));
+}
+
+struct FulfillableCondition {
+ struct MatchData {
+ int value;
+ const std::set<URLMatcherConditionSet::ID>& url_matches;
+ };
+
+ scoped_refptr<URLMatcherConditionSet> condition_set;
+ int condition_set_id;
+ int max_value;
+
+ URLMatcherConditionSet::ID url_matcher_condition_set_id() const {
+ return condition_set_id;
+ }
+
+ scoped_refptr<URLMatcherConditionSet> url_matcher_condition_set() const {
+ return condition_set;
+ }
+
+ void GetURLMatcherConditionSets(
+ URLMatcherConditionSet::Vector* condition_sets) const {
+ if (condition_set.get())
+ condition_sets->push_back(condition_set);
+ }
+
+ bool IsFulfilled(const MatchData& match_data) const {
+ if (condition_set_id != -1 &&
+ !ContainsKey(match_data.url_matches, condition_set_id))
+ return false;
+ return match_data.value <= max_value;
+ }
+
+ static scoped_ptr<FulfillableCondition> Create(
+ const Extension* extension,
+ URLMatcherConditionFactory* url_matcher_condition_factory,
+ const base::Value& condition,
+ std::string* error) {
+ scoped_ptr<FulfillableCondition> result(new FulfillableCondition());
+ const base::DictionaryValue* dict;
+ if (!condition.GetAsDictionary(&dict)) {
+ *error = "Expected dict";
+ return result;
+ }
+ if (!dict->GetInteger("url_id", &result->condition_set_id))
+ result->condition_set_id = -1;
+ if (!dict->GetInteger("max", &result->max_value))
+ *error = "Expected integer at ['max']";
+ if (result->condition_set_id != -1) {
+ result->condition_set = new URLMatcherConditionSet(
+ result->condition_set_id,
+ URLMatcherConditionSet::Conditions());
+ }
+ return result;
+ }
+};
+
+TEST(DeclarativeConditionTest, FulfillConditionSet) {
+ typedef DeclarativeConditionSet<FulfillableCondition> FulfillableConditionSet;
+ FulfillableConditionSet::Values conditions;
+ conditions.push_back(ParseJson("{\"url_id\": 1, \"max\": 3}"));
+ conditions.push_back(ParseJson("{\"url_id\": 2, \"max\": 5}"));
+ conditions.push_back(ParseJson("{\"url_id\": 3, \"max\": 1}"));
+ conditions.push_back(ParseJson("{\"max\": -5}")); // No url.
+
+ // Test insertion
+ std::string error;
+ scoped_ptr<FulfillableConditionSet> result =
+ FulfillableConditionSet::Create(NULL, NULL, conditions, &error);
+ ASSERT_EQ("", error);
+ ASSERT_TRUE(result);
+ EXPECT_EQ(4u, result->conditions().size());
+
+ std::set<URLMatcherConditionSet::ID> url_matches;
+ FulfillableCondition::MatchData match_data = { 0, url_matches };
+ EXPECT_FALSE(result->IsFulfilled(1, match_data))
+ << "Testing an ID that's not in url_matches forwards to the Condition, "
+ << "which doesn't match.";
+ EXPECT_FALSE(result->IsFulfilled(-1, match_data))
+ << "Testing the 'no ID' value tries to match the 4th condition, but "
+ << "its max is too low.";
+ match_data.value = -5;
+ EXPECT_TRUE(result->IsFulfilled(-1, match_data))
+ << "Testing the 'no ID' value tries to match the 4th condition, and "
+ << "this value is low enough.";
+
+ url_matches.insert(1);
+ match_data.value = 3;
+ EXPECT_TRUE(result->IsFulfilled(1, match_data))
+ << "Tests a condition with a url matcher, for a matching value.";
+ match_data.value = 4;
+ EXPECT_FALSE(result->IsFulfilled(1, match_data))
+ << "Tests a condition with a url matcher, for a non-matching value "
+ << "that would match a different condition.";
+ url_matches.insert(2);
+ EXPECT_TRUE(result->IsFulfilled(2, match_data))
+ << "Tests with 2 elements in the match set.";
+
+ // Check the condition sets:
+ URLMatcherConditionSet::Vector condition_sets;
+ result->GetURLMatcherConditionSets(&condition_sets);
+ ASSERT_EQ(3U, condition_sets.size());
+ EXPECT_EQ(1, condition_sets[0]->id());
+ EXPECT_EQ(2, condition_sets[1]->id());
+ EXPECT_EQ(3, condition_sets[2]->id());
+}
+
+// DeclarativeAction
+
+class SummingAction : public base::RefCounted<SummingAction> {
+ public:
+ typedef int ApplyInfo;
+
+ SummingAction(int increment, int min_priority)
+ : increment_(increment), min_priority_(min_priority) {}
+
+ static scoped_refptr<const SummingAction> Create(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ const base::Value& action,
+ std::string* error,
+ bool* bad_message) {
+ int increment = 0;
+ int min_priority = 0;
+ const base::DictionaryValue* dict = NULL;
+ EXPECT_TRUE(action.GetAsDictionary(&dict));
+ if (dict->HasKey("error")) {
+ EXPECT_TRUE(dict->GetString("error", error));
+ return scoped_refptr<const SummingAction>(NULL);
+ }
+ if (dict->HasKey("bad")) {
+ *bad_message = true;
+ return scoped_refptr<const SummingAction>(NULL);
+ }
+
+ EXPECT_TRUE(dict->GetInteger("value", &increment));
+ dict->GetInteger("priority", &min_priority);
+ return scoped_refptr<const SummingAction>(
+ new SummingAction(increment, min_priority));
+ }
+
+ void Apply(const std::string& extension_id,
+ const base::Time& install_time,
+ int* sum) const {
+ *sum += increment_;
+ }
+
+ int increment() const { return increment_; }
+ int minimum_priority() const {
+ return min_priority_;
+ }
+
+ private:
+ friend class base::RefCounted<SummingAction>;
+ virtual ~SummingAction() {}
+
+ int increment_;
+ int min_priority_;
+};
+typedef DeclarativeActionSet<SummingAction> SummingActionSet;
+
+TEST(DeclarativeActionTest, ErrorActionSet) {
+ SummingActionSet::Values actions;
+ actions.push_back(ParseJson("{\"value\": 1}"));
+ actions.push_back(ParseJson("{\"error\": \"the error\"}"));
+
+ std::string error;
+ bool bad = false;
+ scoped_ptr<SummingActionSet> result =
+ SummingActionSet::Create(NULL, NULL, actions, &error, &bad);
+ EXPECT_EQ("the error", error);
+ EXPECT_FALSE(bad);
+ EXPECT_FALSE(result);
+
+ actions.clear();
+ actions.push_back(ParseJson("{\"value\": 1}"));
+ actions.push_back(ParseJson("{\"bad\": 3}"));
+ result = SummingActionSet::Create(NULL, NULL, actions, &error, &bad);
+ EXPECT_EQ("", error);
+ EXPECT_TRUE(bad);
+ EXPECT_FALSE(result);
+}
+
+TEST(DeclarativeActionTest, ApplyActionSet) {
+ SummingActionSet::Values actions;
+ actions.push_back(
+ ParseJson("{\"value\": 1,"
+ " \"priority\": 5}"));
+ actions.push_back(ParseJson("{\"value\": 2}"));
+
+ // Test insertion
+ std::string error;
+ bool bad = false;
+ scoped_ptr<SummingActionSet> result =
+ SummingActionSet::Create(NULL, NULL, actions, &error, &bad);
+ EXPECT_EQ("", error);
+ EXPECT_FALSE(bad);
+ ASSERT_TRUE(result);
+ EXPECT_EQ(2u, result->actions().size());
+
+ int sum = 0;
+ result->Apply("ext_id", base::Time(), &sum);
+ EXPECT_EQ(3, sum);
+ EXPECT_EQ(5, result->GetMinimumPriority());
+}
+
+TEST(DeclarativeRuleTest, Create) {
+ typedef DeclarativeRule<FulfillableCondition, SummingAction> Rule;
+ linked_ptr<Rule::JsonRule> json_rule(new Rule::JsonRule);
+ ASSERT_TRUE(Rule::JsonRule::Populate(
+ *ParseJson("{ \n"
+ " \"id\": \"rule1\", \n"
+ " \"conditions\": [ \n"
+ " {\"url_id\": 1, \"max\": 3}, \n"
+ " {\"url_id\": 2, \"max\": 5}, \n"
+ " ], \n"
+ " \"actions\": [ \n"
+ " { \n"
+ " \"value\": 2 \n"
+ " } \n"
+ " ], \n"
+ " \"priority\": 200 \n"
+ "}"),
+ json_rule.get()));
+
+ const char kExtensionId[] = "ext1";
+ scoped_refptr<Extension> extension = ExtensionBuilder()
+ .SetManifest(SimpleManifest())
+ .SetID(kExtensionId)
+ .Build();
+
+ base::Time install_time = base::Time::Now();
+
+ URLMatcher matcher;
+ std::string error;
+ scoped_ptr<Rule> rule(Rule::Create(matcher.condition_factory(),
+ NULL,
+ extension.get(),
+ install_time,
+ json_rule,
+ Rule::ConsistencyChecker(),
+ &error));
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(rule.get());
+
+ EXPECT_EQ(kExtensionId, rule->id().first);
+ EXPECT_EQ("rule1", rule->id().second);
+
+ EXPECT_EQ(200, rule->priority());
+
+ const Rule::ConditionSet& condition_set = rule->conditions();
+ const Rule::ConditionSet::Conditions& conditions =
+ condition_set.conditions();
+ ASSERT_EQ(2u, conditions.size());
+ EXPECT_EQ(3, conditions[0]->max_value);
+ EXPECT_EQ(5, conditions[1]->max_value);
+
+ const Rule::ActionSet& action_set = rule->actions();
+ const Rule::ActionSet::Actions& actions = action_set.actions();
+ ASSERT_EQ(1u, actions.size());
+ EXPECT_EQ(2, actions[0]->increment());
+
+ int sum = 0;
+ rule->Apply(&sum);
+ EXPECT_EQ(2, sum);
+}
+
+bool AtLeastOneCondition(
+ const DeclarativeConditionSet<FulfillableCondition>* conditions,
+ const DeclarativeActionSet<SummingAction>* actions,
+ std::string* error) {
+ if (conditions->conditions().empty()) {
+ *error = "No conditions";
+ return false;
+ }
+ return true;
+}
+
+TEST(DeclarativeRuleTest, CheckConsistency) {
+ typedef DeclarativeRule<FulfillableCondition, SummingAction> Rule;
+ URLMatcher matcher;
+ std::string error;
+ linked_ptr<Rule::JsonRule> json_rule(new Rule::JsonRule);
+ const char kExtensionId[] = "ext1";
+ scoped_refptr<Extension> extension = ExtensionBuilder()
+ .SetManifest(SimpleManifest())
+ .SetID(kExtensionId)
+ .Build();
+
+ ASSERT_TRUE(Rule::JsonRule::Populate(
+ *ParseJson("{ \n"
+ " \"id\": \"rule1\", \n"
+ " \"conditions\": [ \n"
+ " {\"url_id\": 1, \"max\": 3}, \n"
+ " {\"url_id\": 2, \"max\": 5}, \n"
+ " ], \n"
+ " \"actions\": [ \n"
+ " { \n"
+ " \"value\": 2 \n"
+ " } \n"
+ " ], \n"
+ " \"priority\": 200 \n"
+ "}"),
+ json_rule.get()));
+ scoped_ptr<Rule> rule(Rule::Create(matcher.condition_factory(),
+ NULL,
+ extension.get(),
+ base::Time(),
+ json_rule,
+ base::Bind(AtLeastOneCondition),
+ &error));
+ EXPECT_TRUE(rule);
+ EXPECT_EQ("", error);
+
+ ASSERT_TRUE(Rule::JsonRule::Populate(
+ *ParseJson("{ \n"
+ " \"id\": \"rule1\", \n"
+ " \"conditions\": [ \n"
+ " ], \n"
+ " \"actions\": [ \n"
+ " { \n"
+ " \"value\": 2 \n"
+ " } \n"
+ " ], \n"
+ " \"priority\": 200 \n"
+ "}"),
+ json_rule.get()));
+ rule = Rule::Create(matcher.condition_factory(),
+ NULL,
+ extension.get(),
+ base::Time(),
+ json_rule,
+ base::Bind(AtLeastOneCondition),
+ &error);
+ EXPECT_FALSE(rule);
+ EXPECT_EQ("No conditions", error);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/declarative/deduping_factory.h b/chromium/extensions/browser/api/declarative/deduping_factory.h
new file mode 100644
index 00000000000..1cc32318ec5
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative/deduping_factory.h
@@ -0,0 +1,182 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DECLARATIVE_DEDUPING_FACTORY_H__
+#define EXTENSIONS_BROWSER_API_DECLARATIVE_DEDUPING_FACTORY_H__
+
+#include <stddef.h>
+
+#include <list>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/containers/hash_tables.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/stl_util.h"
+
+namespace base {
+class Value;
+} // namespace base
+
+namespace extensions {
+
+// Factory class that stores a cache of the last |N| created objects of each
+// kind. These objects need to be immutable, refcounted objects that are derived
+// from BaseClassT. The objects do not need to be RefCountedThreadSafe. If a new
+// instance of an object is created that is identical to a pre-existing object,
+// it is discarded and the pre-existing object is recycled.
+//
+// BaseClassT needs to provide a comparison operations. Like the following:
+//
+// class BaseClassT {
+// virtual bool Equals(const BaseClassT* other) const;
+// };
+//
+// The unit test shows an example.
+template<typename BaseClassT>
+class DedupingFactory {
+ public:
+ // Factory methods for BaseClass instances. |value| contains e.g. the json
+ // dictionary that describes the object to be instantiated. |error| is used
+ // to return error messages in case the extension passed an action that was
+ // syntactically correct but semantically incorrect. |bad_message| is set to
+ // true in case |dict| does not confirm to the validated JSON specification.
+ typedef scoped_refptr<const BaseClassT>
+ (* FactoryMethod)(const std::string& instance_type,
+ const base::Value* /* value */ ,
+ std::string* /* error */,
+ bool* /* bad_message */);
+
+ enum Parameterized {
+ // Two instantiated objects may be different and we need to check for
+ // equality to see whether we can recycle one.
+ IS_PARAMETERIZED,
+ // The objects are not parameterized, i.e. all created instances are the
+ // same and it is sufficient to create a single one.
+ IS_NOT_PARAMETERIZED
+ };
+
+ // Creates a DedupingFactory with a MRU cache of size |max_number_prototypes|
+ // per instance_type. If we find a match within the cache, the factory reuses
+ // that instance instead of creating a new one. The cache size should not be
+ // too large because we probe linearly whether an element is in the cache.
+ explicit DedupingFactory(size_t max_number_prototypes);
+ ~DedupingFactory();
+
+ void RegisterFactoryMethod(const std::string& instance_type,
+ Parameterized parameterized,
+ FactoryMethod factory_method);
+
+ scoped_refptr<const BaseClassT> Instantiate(const std::string& instance_type,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message);
+
+ void ClearPrototypes();
+
+ private:
+ typedef std::string InstanceType;
+ // Cache of previous prototypes in most-recently-used order. Most recently
+ // used objects are at the end.
+ typedef std::list<scoped_refptr<const BaseClassT> > PrototypeList;
+ typedef base::hash_map<InstanceType, PrototypeList> ExistingPrototypes;
+ typedef base::hash_map<InstanceType, FactoryMethod> FactoryMethods;
+ typedef base::hash_set<InstanceType> ParameterizedTypes;
+
+ const size_t max_number_prototypes_;
+ ExistingPrototypes prototypes_;
+ FactoryMethods factory_methods_;
+ ParameterizedTypes parameterized_types_;
+
+ DISALLOW_COPY_AND_ASSIGN(DedupingFactory);
+};
+
+template<typename BaseClassT>
+DedupingFactory<BaseClassT>::DedupingFactory(size_t max_number_prototypes)
+ : max_number_prototypes_(max_number_prototypes) {}
+
+template<typename BaseClassT>
+DedupingFactory<BaseClassT>::~DedupingFactory() {}
+
+template<typename BaseClassT>
+void DedupingFactory<BaseClassT>::RegisterFactoryMethod(
+ const std::string& instance_type,
+ typename DedupingFactory<BaseClassT>::Parameterized parameterized,
+ FactoryMethod factory_method) {
+ DCHECK(!ContainsKey(factory_methods_, instance_type));
+ factory_methods_[instance_type] = factory_method;
+ if (parameterized == IS_PARAMETERIZED)
+ parameterized_types_.insert(instance_type);
+}
+
+template<typename BaseClassT>
+scoped_refptr<const BaseClassT> DedupingFactory<BaseClassT>::Instantiate(
+ const std::string& instance_type,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message) {
+ typename FactoryMethods::const_iterator factory_method_iter =
+ factory_methods_.find(instance_type);
+ if (factory_method_iter == factory_methods_.end()) {
+ *error = "Invalid instance type " + instance_type;
+ *bad_message = true;
+ return scoped_refptr<const BaseClassT>();
+ }
+
+ FactoryMethod factory_method = factory_method_iter->second;
+
+ PrototypeList& prototypes = prototypes_[instance_type];
+
+ // We can take a shortcut for objects that are not parameterized. For those
+ // only a single instance may ever exist so we can simplify the creation
+ // logic.
+ if (!ContainsKey(parameterized_types_, instance_type)) {
+ if (prototypes.empty()) {
+ scoped_refptr<const BaseClassT> new_object =
+ (*factory_method)(instance_type, value, error, bad_message);
+ if (!new_object.get() || !error->empty() || *bad_message)
+ return scoped_refptr<const BaseClassT>();
+ prototypes.push_back(new_object);
+ }
+ return prototypes.front();
+ }
+
+ // Handle parameterized objects.
+ scoped_refptr<const BaseClassT> new_object =
+ (*factory_method)(instance_type, value, error, bad_message);
+ if (!new_object.get() || !error->empty() || *bad_message)
+ return scoped_refptr<const BaseClassT>();
+
+ size_t length = 0;
+ for (typename PrototypeList::iterator i = prototypes.begin();
+ i != prototypes.end();
+ ++i) {
+ if ((*i)->Equals(new_object.get())) {
+ // Move the old object to the end of the queue so that it gets
+ // discarded later.
+ scoped_refptr<const BaseClassT> old_object = *i;
+ prototypes.erase(i);
+ prototypes.push_back(old_object);
+ return old_object;
+ }
+ ++length;
+ }
+
+ if (length >= max_number_prototypes_)
+ prototypes.pop_front();
+ prototypes.push_back(new_object);
+
+ return new_object;
+}
+
+template<typename BaseClassT>
+void DedupingFactory<BaseClassT>::ClearPrototypes() {
+ prototypes_.clear();
+}
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DECLARATIVE_DEDUPING_FACTORY_H__
diff --git a/chromium/extensions/browser/api/declarative/deduping_factory_unittest.cc b/chromium/extensions/browser/api/declarative/deduping_factory_unittest.cc
new file mode 100644
index 00000000000..f4c335b7040
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative/deduping_factory_unittest.cc
@@ -0,0 +1,201 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/declarative/deduping_factory.h"
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kTypeName[] = "Foo";
+const char kTypeName2[] = "Foo2";
+
+// This serves as an example how to use the DedupingFactory.
+class BaseClass : public base::RefCounted<BaseClass> {
+ public:
+ // The type is introduced so that we can compare derived classes even though
+ // Equals takes a parameter of type BaseClass. Each derived class gets an
+ // entry in Type.
+ enum Type { FOO };
+
+ explicit BaseClass(Type type) : type_(type) {}
+
+ Type type() const { return type_; }
+
+ // For BaseClassT template:
+ virtual bool Equals(const BaseClass* other) const = 0;
+
+ protected:
+ friend class base::RefCounted<BaseClass>;
+ virtual ~BaseClass() {}
+
+ private:
+ const Type type_;
+};
+
+class Foo : public BaseClass {
+ public:
+ explicit Foo(int parameter) : BaseClass(FOO), parameter_(parameter) {}
+ bool Equals(const BaseClass* other) const override {
+ return other->type() == type() &&
+ static_cast<const Foo*>(other)->parameter_ == parameter_;
+ }
+ int parameter() const {
+ return parameter_;
+ }
+
+ private:
+ friend class base::RefCounted<BaseClass>;
+ ~Foo() override {}
+
+ // Note that this class must be immutable.
+ const int parameter_;
+ DISALLOW_COPY_AND_ASSIGN(Foo);
+};
+
+scoped_refptr<const BaseClass> CreateFoo(const std::string& /*instance_type*/,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message) {
+ const base::DictionaryValue* dict = NULL;
+ CHECK(value->GetAsDictionary(&dict));
+ int parameter = 0;
+ if (!dict->GetInteger("parameter", &parameter)) {
+ *error = "No parameter";
+ *bad_message = true;
+ return scoped_refptr<const BaseClass>(NULL);
+ }
+ return scoped_refptr<const BaseClass>(new Foo(parameter));
+}
+
+scoped_ptr<base::DictionaryValue> CreateDictWithParameter(int parameter) {
+ scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+ dict->SetInteger("parameter", parameter);
+ return dict;
+}
+
+} // namespace
+
+namespace extensions {
+
+TEST(DedupingFactoryTest, InstantiationParameterized) {
+ DedupingFactory<BaseClass> factory(2);
+ factory.RegisterFactoryMethod(
+ kTypeName, DedupingFactory<BaseClass>::IS_PARAMETERIZED, &CreateFoo);
+
+ scoped_ptr<base::DictionaryValue> d1(CreateDictWithParameter(1));
+ scoped_ptr<base::DictionaryValue> d2(CreateDictWithParameter(2));
+ scoped_ptr<base::DictionaryValue> d3(CreateDictWithParameter(3));
+ scoped_ptr<base::DictionaryValue> d4(CreateDictWithParameter(4));
+
+ std::string error;
+ bool bad_message = false;
+
+ // Fill factory with 2 different types.
+ scoped_refptr<const BaseClass> c1(
+ factory.Instantiate(kTypeName, d1.get(), &error, &bad_message));
+ scoped_refptr<const BaseClass> c2(
+ factory.Instantiate(kTypeName, d2.get(), &error, &bad_message));
+ ASSERT_TRUE(c1.get());
+ ASSERT_TRUE(c2.get());
+ EXPECT_EQ(1, static_cast<const Foo*>(c1.get())->parameter());
+ EXPECT_EQ(2, static_cast<const Foo*>(c2.get())->parameter());
+
+ // This one produces an overflow, now the cache contains [2, 3]
+ scoped_refptr<const BaseClass> c3(
+ factory.Instantiate(kTypeName, d3.get(), &error, &bad_message));
+ ASSERT_TRUE(c3.get());
+ EXPECT_EQ(3, static_cast<const Foo*>(c3.get())->parameter());
+
+ // Reuse 2, this should give the same instance as c2.
+ scoped_refptr<const BaseClass> c2_b(
+ factory.Instantiate(kTypeName, d2.get(), &error, &bad_message));
+ EXPECT_EQ(2, static_cast<const Foo*>(c2_b.get())->parameter());
+ EXPECT_EQ(c2, c2_b);
+
+ // Also check that the reuse of 2 moved it to the end, so that the cache is
+ // now [3, 2] and 3 is discarded before 2.
+ // This discards 3, so the cache becomes [2, 1]
+ scoped_refptr<const BaseClass> c1_b(
+ factory.Instantiate(kTypeName, d1.get(), &error, &bad_message));
+
+ scoped_refptr<const BaseClass> c2_c(
+ factory.Instantiate(kTypeName, d2.get(), &error, &bad_message));
+ EXPECT_EQ(2, static_cast<const Foo*>(c2_c.get())->parameter());
+ EXPECT_EQ(c2, c2_c);
+}
+
+TEST(DedupingFactoryTest, InstantiationNonParameterized) {
+ DedupingFactory<BaseClass> factory(2);
+ factory.RegisterFactoryMethod(
+ kTypeName, DedupingFactory<BaseClass>::IS_NOT_PARAMETERIZED, &CreateFoo);
+
+ scoped_ptr<base::DictionaryValue> d1(CreateDictWithParameter(1));
+ scoped_ptr<base::DictionaryValue> d2(CreateDictWithParameter(2));
+
+ std::string error;
+ bool bad_message = false;
+
+ // We create two instances with different dictionaries but because the type is
+ // declared to be not parameterized, we should get the same instance.
+ scoped_refptr<const BaseClass> c1(
+ factory.Instantiate(kTypeName, d1.get(), &error, &bad_message));
+ scoped_refptr<const BaseClass> c2(
+ factory.Instantiate(kTypeName, d2.get(), &error, &bad_message));
+ ASSERT_TRUE(c1.get());
+ ASSERT_TRUE(c2.get());
+ EXPECT_EQ(1, static_cast<const Foo*>(c1.get())->parameter());
+ EXPECT_EQ(1, static_cast<const Foo*>(c2.get())->parameter());
+ EXPECT_EQ(c1, c2);
+}
+
+TEST(DedupingFactoryTest, TypeNames) {
+ DedupingFactory<BaseClass> factory(2);
+ factory.RegisterFactoryMethod(
+ kTypeName, DedupingFactory<BaseClass>::IS_PARAMETERIZED, &CreateFoo);
+ factory.RegisterFactoryMethod(
+ kTypeName2, DedupingFactory<BaseClass>::IS_PARAMETERIZED, &CreateFoo);
+
+ scoped_ptr<base::DictionaryValue> d1(CreateDictWithParameter(1));
+
+ std::string error;
+ bool bad_message = false;
+
+ scoped_refptr<const BaseClass> c1_a(
+ factory.Instantiate(kTypeName, d1.get(), &error, &bad_message));
+ scoped_refptr<const BaseClass> c1_b(
+ factory.Instantiate(kTypeName2, d1.get(), &error, &bad_message));
+
+ ASSERT_TRUE(c1_a.get());
+ ASSERT_TRUE(c1_b.get());
+ EXPECT_NE(c1_a, c1_b);
+}
+
+TEST(DedupingFactoryTest, Clear) {
+ DedupingFactory<BaseClass> factory(2);
+ factory.RegisterFactoryMethod(
+ kTypeName, DedupingFactory<BaseClass>::IS_PARAMETERIZED, &CreateFoo);
+
+ scoped_ptr<base::DictionaryValue> d1(CreateDictWithParameter(1));
+
+ std::string error;
+ bool bad_message = false;
+
+ scoped_refptr<const BaseClass> c1_a(
+ factory.Instantiate(kTypeName, d1.get(), &error, &bad_message));
+
+ factory.ClearPrototypes();
+
+ scoped_refptr<const BaseClass> c1_b(
+ factory.Instantiate(kTypeName, d1.get(), &error, &bad_message));
+
+ ASSERT_TRUE(c1_a.get());
+ ASSERT_TRUE(c1_b.get());
+ EXPECT_NE(c1_a, c1_b);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/declarative/rules_cache_delegate.cc b/chromium/extensions/browser/api/declarative/rules_cache_delegate.cc
new file mode 100644
index 00000000000..0603d6c4db7
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative/rules_cache_delegate.cc
@@ -0,0 +1,232 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/declarative/rules_cache_delegate.h"
+
+#include <utility>
+
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_source.h"
+#include "extensions/browser/api/declarative/rules_registry.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/info_map.h"
+#include "extensions/browser/state_store.h"
+#include "extensions/common/permissions/permissions_data.h"
+
+namespace {
+
+// Returns the key to use for storing declarative rules in the state store.
+std::string GetDeclarativeRuleStorageKey(const std::string& event_name,
+ bool incognito) {
+ if (incognito)
+ return "declarative_rules.incognito." + event_name;
+ else
+ return "declarative_rules." + event_name;
+}
+
+
+} // namespace
+
+namespace extensions {
+
+// RulesCacheDelegate
+
+const char RulesCacheDelegate::kRulesStoredKey[] =
+ "has_declarative_rules";
+
+RulesCacheDelegate::RulesCacheDelegate(bool log_storage_init_delay)
+ : browser_context_(NULL),
+ log_storage_init_delay_(log_storage_init_delay),
+ notified_registry_(false),
+ weak_ptr_factory_(this) {
+}
+
+RulesCacheDelegate::~RulesCacheDelegate() {}
+
+// Returns the key to use for storing whether the rules have been stored.
+// static
+std::string RulesCacheDelegate::GetRulesStoredKey(const std::string& event_name,
+ bool incognito) {
+ std::string result(kRulesStoredKey);
+ result += incognito ? ".incognito." : ".";
+ return result + event_name;
+}
+
+// This is called from the constructor of RulesRegistry, so it is
+// important that it both
+// 1. calls no (in particular virtual) methods of the rules registry, and
+// 2. does not create scoped_refptr holding the registry. (A short-lived
+// scoped_refptr might delete the rules registry before it is constructed.)
+void RulesCacheDelegate::Init(RulesRegistry* registry) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ // WARNING: The first use of |registry_| will bind it to the calling thread
+ // so don't use this here.
+ registry_ = registry->GetWeakPtr();
+
+ browser_context_ = registry->browser_context();
+ storage_key_ =
+ GetDeclarativeRuleStorageKey(registry->event_name(),
+ browser_context_->IsOffTheRecord());
+ rules_stored_key_ = GetRulesStoredKey(registry->event_name(),
+ browser_context_->IsOffTheRecord());
+ rules_registry_thread_ = registry->owner_thread();
+
+ ExtensionSystem& system = *ExtensionSystem::Get(browser_context_);
+ StateStore* store = system.rules_store();
+ if (store)
+ store->RegisterKey(storage_key_);
+
+ if (browser_context_->IsOffTheRecord())
+ log_storage_init_delay_ = false;
+
+ system.ready().Post(
+ FROM_HERE,
+ base::Bind(&RulesCacheDelegate::ReadRulesForInstalledExtensions,
+ weak_ptr_factory_.GetWeakPtr()));
+ system.ready().Post(FROM_HERE,
+ base::Bind(&RulesCacheDelegate::CheckIfReady,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void RulesCacheDelegate::WriteToStorage(const std::string& extension_id,
+ scoped_ptr<base::Value> value) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ if (!browser_context_)
+ return;
+
+ const base::ListValue* rules = NULL;
+ CHECK(value->GetAsList(&rules));
+ bool rules_stored_previously = GetDeclarativeRulesStored(extension_id);
+ bool store_rules = !rules->empty();
+ SetDeclarativeRulesStored(extension_id, store_rules);
+ if (!rules_stored_previously && !store_rules)
+ return;
+
+ StateStore* store = ExtensionSystem::Get(browser_context_)->rules_store();
+ if (store)
+ store->SetExtensionValue(extension_id, storage_key_, std::move(value));
+}
+
+void RulesCacheDelegate::CheckIfReady() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ if (notified_registry_ || !waiting_for_extensions_.empty())
+ return;
+
+ content::BrowserThread::PostTask(
+ rules_registry_thread_,
+ FROM_HERE,
+ base::Bind(
+ &RulesRegistry::MarkReady, registry_, storage_init_time_));
+ notified_registry_ = true;
+}
+
+void RulesCacheDelegate::ReadRulesForInstalledExtensions() {
+ bool is_ready = ExtensionSystem::Get(browser_context_)->ready().is_signaled();
+ // In an OTR context, we start on top of a normal context already, so the
+ // extension service should be ready.
+ DCHECK(!browser_context_->IsOffTheRecord() || is_ready);
+ if (is_ready) {
+ const ExtensionSet& extensions =
+ ExtensionRegistry::Get(browser_context_)->enabled_extensions();
+ const ExtensionPrefs* extension_prefs =
+ ExtensionPrefs::Get(browser_context_);
+ for (ExtensionSet::const_iterator i = extensions.begin();
+ i != extensions.end();
+ ++i) {
+ bool needs_apis_storing_rules =
+ (*i)->permissions_data()->HasAPIPermission(
+ APIPermission::kDeclarativeContent) ||
+ (*i)->permissions_data()->HasAPIPermission(
+ APIPermission::kDeclarativeWebRequest);
+ bool respects_off_the_record =
+ !(browser_context_->IsOffTheRecord()) ||
+ extension_prefs->IsIncognitoEnabled((*i)->id());
+ if (needs_apis_storing_rules && respects_off_the_record)
+ ReadFromStorage((*i)->id());
+ }
+ }
+}
+
+void RulesCacheDelegate::ReadFromStorage(const std::string& extension_id) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ if (!browser_context_)
+ return;
+
+ if (log_storage_init_delay_ && storage_init_time_.is_null())
+ storage_init_time_ = base::Time::Now();
+
+ if (!GetDeclarativeRulesStored(extension_id)) {
+ ExtensionSystem::Get(browser_context_)->ready().Post(
+ FROM_HERE, base::Bind(&RulesCacheDelegate::CheckIfReady,
+ weak_ptr_factory_.GetWeakPtr()));
+ return;
+ }
+
+ StateStore* store = ExtensionSystem::Get(browser_context_)->rules_store();
+ if (!store)
+ return;
+ waiting_for_extensions_.insert(extension_id);
+ store->GetExtensionValue(
+ extension_id,
+ storage_key_,
+ base::Bind(&RulesCacheDelegate::ReadFromStorageCallback,
+ weak_ptr_factory_.GetWeakPtr(),
+ extension_id));
+}
+
+void RulesCacheDelegate::ReadFromStorageCallback(
+ const std::string& extension_id,
+ scoped_ptr<base::Value> value) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ content::BrowserThread::PostTask(
+ rules_registry_thread_,
+ FROM_HERE,
+ base::Bind(&RulesRegistry::DeserializeAndAddRules,
+ registry_,
+ extension_id,
+ base::Passed(&value)));
+
+ waiting_for_extensions_.erase(extension_id);
+
+ if (waiting_for_extensions_.empty())
+ ExtensionSystem::Get(browser_context_)->ready().Post(
+ FROM_HERE, base::Bind(&RulesCacheDelegate::CheckIfReady,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+bool RulesCacheDelegate::GetDeclarativeRulesStored(
+ const std::string& extension_id) const {
+ CHECK(browser_context_);
+ const ExtensionScopedPrefs* extension_prefs =
+ ExtensionPrefs::Get(browser_context_);
+
+ bool rules_stored = true;
+ if (extension_prefs->ReadPrefAsBoolean(
+ extension_id, rules_stored_key_, &rules_stored))
+ return rules_stored;
+
+ // Safe default -- if we don't know that the rules are not stored, we force
+ // a read by returning true.
+ return true;
+}
+
+void RulesCacheDelegate::SetDeclarativeRulesStored(
+ const std::string& extension_id,
+ bool rules_stored) {
+ CHECK(browser_context_);
+ DCHECK(ExtensionRegistry::Get(browser_context_)
+ ->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING));
+
+ ExtensionScopedPrefs* extension_prefs = ExtensionPrefs::Get(browser_context_);
+ extension_prefs->UpdateExtensionPref(
+ extension_id,
+ rules_stored_key_,
+ new base::FundamentalValue(rules_stored));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/declarative/rules_cache_delegate.h b/chromium/extensions/browser/api/declarative/rules_cache_delegate.h
new file mode 100644
index 00000000000..15553dab75c
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative/rules_cache_delegate.h
@@ -0,0 +1,113 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DECLARATIVE_RULES_CACHE_DELEGATE_H__
+#define EXTENSIONS_BROWSER_API_DECLARATIVE_RULES_CACHE_DELEGATE_H__
+
+#include <set>
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class RulesRegistry;
+
+// RulesCacheDelegate implements the part of the RulesRegistry which works on
+// the UI thread. It should only be used on the UI thread.
+// If |log_storage_init_delay| is set, the delay caused by loading and
+// registering rules on initialization will be logged with UMA.
+class RulesCacheDelegate {
+ public:
+
+ explicit RulesCacheDelegate(bool log_storage_init_delay);
+
+ virtual ~RulesCacheDelegate();
+
+ // Returns a key for the state store. The associated preference is a boolean
+ // indicating whether there are some declarative rules stored in the rule
+ // store.
+ static std::string GetRulesStoredKey(const std::string& event_name,
+ bool incognito);
+
+ // Initialize the storage functionality.
+ void Init(RulesRegistry* registry);
+
+ void WriteToStorage(const std::string& extension_id,
+ scoped_ptr<base::Value> value);
+
+ base::WeakPtr<RulesCacheDelegate> GetWeakPtr() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ return weak_ptr_factory_.GetWeakPtr();
+ }
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(RulesRegistryWithCacheTest,
+ DeclarativeRulesStored);
+ FRIEND_TEST_ALL_PREFIXES(RulesRegistryWithCacheTest,
+ RulesStoredFlagMultipleRegistries);
+
+ static const char kRulesStoredKey[];
+
+ // Check if we are done reading all data from storage on startup, and notify
+ // the RulesRegistry on its thread if so. The notification is delivered
+ // exactly once.
+ void CheckIfReady();
+
+ // Schedules retrieving rules for already loaded extensions where
+ // appropriate.
+ void ReadRulesForInstalledExtensions();
+
+ // Read/write a list of rules serialized to Values.
+ void ReadFromStorage(const std::string& extension_id);
+ void ReadFromStorageCallback(const std::string& extension_id,
+ scoped_ptr<base::Value> value);
+
+ // Check the preferences whether the extension with |extension_id| has some
+ // rules stored on disk. If this information is not in the preferences, true
+ // is returned as a safe default value.
+ bool GetDeclarativeRulesStored(const std::string& extension_id) const;
+ // Modify the preference to |rules_stored|.
+ void SetDeclarativeRulesStored(const std::string& extension_id,
+ bool rules_stored);
+
+ content::BrowserContext* browser_context_;
+
+ // The key under which rules are stored.
+ std::string storage_key_;
+
+ // The key under which we store whether the rules have been stored.
+ std::string rules_stored_key_;
+
+ // A set of extension IDs that have rules we are reading from storage.
+ std::set<std::string> waiting_for_extensions_;
+
+ // We measure the time spent on loading rules on init. The result is logged
+ // with UMA once per each RulesCacheDelegate instance, unless in Incognito.
+ base::Time storage_init_time_;
+ bool log_storage_init_delay_;
+
+ // Weak pointer to post tasks to the owning rules registry.
+ base::WeakPtr<RulesRegistry> registry_;
+
+ // The thread |registry_| lives on.
+ content::BrowserThread::ID rules_registry_thread_;
+
+ // We notified the RulesRegistry that the rules are loaded.
+ bool notified_registry_;
+
+ // Use this factory to generate weak pointers bound to the UI thread.
+ base::WeakPtrFactory<RulesCacheDelegate> weak_ptr_factory_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DECLARATIVE_RULES_CACHE_DELEGATE_H__
diff --git a/chromium/extensions/browser/api/declarative/rules_registry.cc b/chromium/extensions/browser/api/declarative/rules_registry.cc
new file mode 100644
index 00000000000..ffbcb7f896b
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative/rules_registry.cc
@@ -0,0 +1,437 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/declarative/rules_registry.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_source.h"
+#include "extensions/browser/api/declarative/rules_cache_delegate.h"
+#include "extensions/browser/extension_error.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/state_store.h"
+#include "extensions/common/api/declarative/declarative_manifest_data.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+namespace {
+
+const char kSuccess[] = "";
+const char kDuplicateRuleId[] = "Duplicate rule ID: %s";
+const char kErrorCannotRemoveManifestRules[] =
+ "Rules declared in the 'event_rules' manifest field cannot be removed";
+
+scoped_ptr<base::Value> RulesToValue(
+ const std::vector<linked_ptr<api::events::Rule>>& rules) {
+ scoped_ptr<base::ListValue> list(new base::ListValue());
+ for (size_t i = 0; i < rules.size(); ++i)
+ list->Append(rules[i]->ToValue().release());
+ return std::move(list);
+}
+
+std::vector<linked_ptr<api::events::Rule>> RulesFromValue(
+ const base::Value* value) {
+ std::vector<linked_ptr<api::events::Rule>> rules;
+
+ const base::ListValue* list = NULL;
+ if (!value || !value->GetAsList(&list))
+ return rules;
+
+ rules.reserve(list->GetSize());
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ const base::DictionaryValue* dict = NULL;
+ if (!list->GetDictionary(i, &dict))
+ continue;
+ linked_ptr<api::events::Rule> rule(new api::events::Rule());
+ if (api::events::Rule::Populate(*dict, rule.get()))
+ rules.push_back(rule);
+ }
+
+ return rules;
+}
+
+std::string ToId(int identifier) {
+ return base::StringPrintf("_%d_", identifier);
+}
+
+} // namespace
+
+
+// RulesRegistry
+
+RulesRegistry::RulesRegistry(content::BrowserContext* browser_context,
+ const std::string& event_name,
+ content::BrowserThread::ID owner_thread,
+ RulesCacheDelegate* cache_delegate,
+ int id)
+ : browser_context_(browser_context),
+ owner_thread_(owner_thread),
+ event_name_(event_name),
+ id_(id),
+ ready_(/*signaled=*/!cache_delegate), // Immediately ready if no cache
+ // delegate to wait for.
+ last_generated_rule_identifier_id_(0),
+ weak_ptr_factory_(browser_context_ ? this : NULL) {
+ if (cache_delegate) {
+ cache_delegate_ = cache_delegate->GetWeakPtr();
+ cache_delegate->Init(this);
+ }
+}
+
+std::string RulesRegistry::AddRulesNoFill(
+ const std::string& extension_id,
+ const std::vector<linked_ptr<api::events::Rule>>& rules,
+ RulesDictionary* out) {
+ DCHECK_CURRENTLY_ON(owner_thread());
+
+ // Verify that all rule IDs are new.
+ for (std::vector<linked_ptr<api::events::Rule>>::const_iterator i =
+ rules.begin();
+ i != rules.end(); ++i) {
+ const RuleId& rule_id = *((*i)->id);
+ // Every rule should have a priority assigned.
+ DCHECK((*i)->priority);
+ RulesDictionaryKey key(extension_id, rule_id);
+ if (rules_.find(key) != rules_.end() ||
+ manifest_rules_.find(key) != manifest_rules_.end())
+ return base::StringPrintf(kDuplicateRuleId, rule_id.c_str());
+ }
+
+ std::string error = AddRulesImpl(extension_id, rules);
+
+ if (!error.empty())
+ return error;
+
+ // Commit all rules into |rules_| on success.
+ for (std::vector<linked_ptr<api::events::Rule>>::const_iterator i =
+ rules.begin();
+ i != rules.end(); ++i) {
+ const RuleId& rule_id = *((*i)->id);
+ RulesDictionaryKey key(extension_id, rule_id);
+ (*out)[key] = *i;
+ }
+
+ MaybeProcessChangedRules(extension_id);
+ return kSuccess;
+}
+
+std::string RulesRegistry::AddRules(
+ const std::string& extension_id,
+ const std::vector<linked_ptr<api::events::Rule>>& rules) {
+ return AddRulesInternal(extension_id, rules, &rules_);
+}
+
+std::string RulesRegistry::AddRulesInternal(
+ const std::string& extension_id,
+ const std::vector<linked_ptr<api::events::Rule>>& rules,
+ RulesDictionary* out) {
+ DCHECK_CURRENTLY_ON(owner_thread());
+
+ std::string error = CheckAndFillInOptionalRules(extension_id, rules);
+ if (!error.empty())
+ return error;
+ FillInOptionalPriorities(rules);
+
+ return AddRulesNoFill(extension_id, rules, out);
+}
+
+std::string RulesRegistry::RemoveRules(
+ const std::string& extension_id,
+ const std::vector<std::string>& rule_identifiers) {
+ DCHECK_CURRENTLY_ON(owner_thread());
+
+ // Check if any of the rules are non-removable.
+ for (RuleId rule_id : rule_identifiers) {
+ RulesDictionaryKey lookup_key(extension_id, rule_id);
+ RulesDictionary::iterator itr = manifest_rules_.find(lookup_key);
+ if (itr != manifest_rules_.end())
+ return kErrorCannotRemoveManifestRules;
+ }
+
+ std::string error = RemoveRulesImpl(extension_id, rule_identifiers);
+
+ if (!error.empty())
+ return error;
+
+ for (std::vector<std::string>::const_iterator i = rule_identifiers.begin();
+ i != rule_identifiers.end();
+ ++i) {
+ RulesDictionaryKey lookup_key(extension_id, *i);
+ rules_.erase(lookup_key);
+ }
+
+ MaybeProcessChangedRules(extension_id);
+ RemoveUsedRuleIdentifiers(extension_id, rule_identifiers);
+ return kSuccess;
+}
+
+std::string RulesRegistry::RemoveAllRules(const std::string& extension_id) {
+ std::string result =
+ RulesRegistry::RemoveAllRulesNoStoreUpdate(extension_id, false);
+ MaybeProcessChangedRules(extension_id); // Now update the prefs and store.
+ return result;
+}
+
+std::string RulesRegistry::RemoveAllRulesNoStoreUpdate(
+ const std::string& extension_id,
+ bool remove_manifest_rules) {
+ DCHECK_CURRENTLY_ON(owner_thread());
+
+ std::string error = RemoveAllRulesImpl(extension_id);
+
+ if (!error.empty())
+ return error;
+
+ auto remove_rules = [&extension_id](RulesDictionary& dictionary) {
+ for (auto it = dictionary.begin(); it != dictionary.end();) {
+ if (it->first.first == extension_id)
+ dictionary.erase(it++);
+ else
+ ++it;
+ }
+ };
+ remove_rules(rules_);
+ if (remove_manifest_rules)
+ remove_rules(manifest_rules_);
+
+ RemoveAllUsedRuleIdentifiers(extension_id);
+ return kSuccess;
+}
+
+void RulesRegistry::GetRules(const std::string& extension_id,
+ const std::vector<std::string>& rule_identifiers,
+ std::vector<linked_ptr<api::events::Rule>>* out) {
+ DCHECK_CURRENTLY_ON(owner_thread());
+
+ for (const auto& i : rule_identifiers) {
+ RulesDictionaryKey lookup_key(extension_id, i);
+ RulesDictionary::iterator entry = rules_.find(lookup_key);
+ if (entry != rules_.end())
+ out->push_back(entry->second);
+ entry = manifest_rules_.find(lookup_key);
+ if (entry != manifest_rules_.end())
+ out->push_back(entry->second);
+ }
+}
+
+void RulesRegistry::GetRules(const std::string& extension_id,
+ const RulesDictionary& rules,
+ std::vector<linked_ptr<api::events::Rule>>* out) {
+ for (const auto& i : rules) {
+ const RulesDictionaryKey& key = i.first;
+ if (key.first == extension_id)
+ out->push_back(i.second);
+ }
+}
+
+void RulesRegistry::GetAllRules(
+ const std::string& extension_id,
+ std::vector<linked_ptr<api::events::Rule>>* out) {
+ DCHECK_CURRENTLY_ON(owner_thread());
+ GetRules(extension_id, manifest_rules_, out);
+ GetRules(extension_id, rules_, out);
+}
+
+void RulesRegistry::OnExtensionUnloaded(const Extension* extension) {
+ DCHECK_CURRENTLY_ON(owner_thread());
+ std::string error = RemoveAllRulesImpl(extension->id());
+ if (!error.empty())
+ ReportInternalError(extension->id(), error);
+}
+
+void RulesRegistry::OnExtensionUninstalled(const Extension* extension) {
+ DCHECK_CURRENTLY_ON(owner_thread());
+ std::string error = RemoveAllRulesNoStoreUpdate(extension->id(), true);
+ if (!error.empty())
+ ReportInternalError(extension->id(), error);
+}
+
+void RulesRegistry::OnExtensionLoaded(const Extension* extension) {
+ DCHECK_CURRENTLY_ON(owner_thread());
+ std::vector<linked_ptr<api::events::Rule>> rules;
+ GetAllRules(extension->id(), &rules);
+ DeclarativeManifestData* declarative_data =
+ DeclarativeManifestData::Get(extension);
+ if (declarative_data) {
+ std::vector<linked_ptr<api::events::Rule>>& manifest_rules =
+ declarative_data->RulesForEvent(event_name_);
+ if (manifest_rules.size()) {
+ std::string error =
+ AddRulesInternal(extension->id(), manifest_rules, &manifest_rules_);
+ if (!error.empty())
+ ReportInternalError(extension->id(), error);
+ }
+ }
+ std::string error = AddRulesImpl(extension->id(), rules);
+ if (!error.empty())
+ ReportInternalError(extension->id(), error);
+}
+
+size_t RulesRegistry::GetNumberOfUsedRuleIdentifiersForTesting() const {
+ size_t entry_count = 0u;
+ for (RuleIdentifiersMap::const_iterator extension =
+ used_rule_identifiers_.begin();
+ extension != used_rule_identifiers_.end();
+ ++extension) {
+ // Each extension is counted as 1 just for being there. Otherwise we miss
+ // keys with empty values.
+ entry_count += 1u + extension->second.size();
+ }
+ return entry_count;
+}
+
+void RulesRegistry::DeserializeAndAddRules(
+ const std::string& extension_id,
+ scoped_ptr<base::Value> rules) {
+ DCHECK_CURRENTLY_ON(owner_thread());
+
+ std::string error =
+ AddRulesNoFill(extension_id, RulesFromValue(rules.get()), &rules_);
+ if (!error.empty())
+ ReportInternalError(extension_id, error);
+}
+
+void RulesRegistry::ReportInternalError(const std::string& extension_id,
+ const std::string& error) {
+ scoped_ptr<ExtensionError> error_instance(new InternalError(
+ extension_id, base::ASCIIToUTF16(error), logging::LOG_ERROR));
+ ExtensionsBrowserClient::Get()->ReportError(browser_context_,
+ std::move(error_instance));
+}
+
+RulesRegistry::~RulesRegistry() {
+}
+
+void RulesRegistry::MarkReady(base::Time storage_init_time) {
+ DCHECK_CURRENTLY_ON(owner_thread());
+
+ if (!storage_init_time.is_null()) {
+ UMA_HISTOGRAM_TIMES("Extensions.DeclarativeRulesStorageInitialization",
+ base::Time::Now() - storage_init_time);
+ }
+
+ ready_.Signal();
+}
+
+void RulesRegistry::ProcessChangedRules(const std::string& extension_id) {
+ DCHECK_CURRENTLY_ON(owner_thread());
+
+ DCHECK(ContainsKey(process_changed_rules_requested_, extension_id));
+ process_changed_rules_requested_[extension_id] = NOT_SCHEDULED_FOR_PROCESSING;
+
+ std::vector<linked_ptr<api::events::Rule>> new_rules;
+ GetRules(extension_id, rules_, &new_rules);
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI, FROM_HERE,
+ base::Bind(&RulesCacheDelegate::WriteToStorage, cache_delegate_,
+ extension_id, base::Passed(RulesToValue(new_rules))));
+}
+
+void RulesRegistry::MaybeProcessChangedRules(const std::string& extension_id) {
+ // Read and initialize |process_changed_rules_requested_[extension_id]| if
+ // necessary. (Note that the insertion below will not overwrite
+ // |process_changed_rules_requested_[extension_id]| if that already exists.
+ std::pair<ProcessStateMap::iterator, bool> insertion =
+ process_changed_rules_requested_.insert(std::make_pair(
+ extension_id,
+ browser_context_ ? NOT_SCHEDULED_FOR_PROCESSING : NEVER_PROCESS));
+ if (insertion.first->second != NOT_SCHEDULED_FOR_PROCESSING)
+ return;
+
+ process_changed_rules_requested_[extension_id] = SCHEDULED_FOR_PROCESSING;
+ ready_.Post(FROM_HERE,
+ base::Bind(&RulesRegistry::ProcessChangedRules,
+ weak_ptr_factory_.GetWeakPtr(),
+ extension_id));
+}
+
+bool RulesRegistry::IsUniqueId(const std::string& extension_id,
+ const std::string& rule_id) const {
+ RuleIdentifiersMap::const_iterator identifiers =
+ used_rule_identifiers_.find(extension_id);
+ if (identifiers == used_rule_identifiers_.end())
+ return true;
+ return identifiers->second.find(rule_id) == identifiers->second.end();
+}
+
+std::string RulesRegistry::GenerateUniqueId(const std::string& extension_id) {
+ while (!IsUniqueId(extension_id, ToId(last_generated_rule_identifier_id_)))
+ ++last_generated_rule_identifier_id_;
+ return ToId(last_generated_rule_identifier_id_);
+}
+
+std::string RulesRegistry::CheckAndFillInOptionalRules(
+ const std::string& extension_id,
+ const std::vector<linked_ptr<api::events::Rule>>& rules) {
+ // IDs we have inserted, in case we need to rollback this operation.
+ std::vector<std::string> rollback_log;
+
+ // First we insert all rules with existing identifier, so that generated
+ // identifiers cannot collide with identifiers passed by the caller.
+ for (std::vector<linked_ptr<api::events::Rule>>::const_iterator i =
+ rules.begin();
+ i != rules.end(); ++i) {
+ api::events::Rule* rule = i->get();
+ if (rule->id.get()) {
+ std::string id = *(rule->id);
+ if (!IsUniqueId(extension_id, id)) {
+ RemoveUsedRuleIdentifiers(extension_id, rollback_log);
+ return "Id " + id + " was used multiple times.";
+ }
+ used_rule_identifiers_[extension_id].insert(id);
+ }
+ }
+ // Now we generate IDs in case they were not specified in the rules. This
+ // cannot fail so we do not need to keep track of a rollback log.
+ for (std::vector<linked_ptr<api::events::Rule>>::const_iterator i =
+ rules.begin();
+ i != rules.end(); ++i) {
+ api::events::Rule* rule = i->get();
+ if (!rule->id.get()) {
+ rule->id.reset(new std::string(GenerateUniqueId(extension_id)));
+ used_rule_identifiers_[extension_id].insert(*(rule->id));
+ }
+ }
+ return std::string();
+}
+
+void RulesRegistry::FillInOptionalPriorities(
+ const std::vector<linked_ptr<api::events::Rule>>& rules) {
+ std::vector<linked_ptr<api::events::Rule>>::const_iterator i;
+ for (i = rules.begin(); i != rules.end(); ++i) {
+ if (!(*i)->priority.get())
+ (*i)->priority.reset(new int(DEFAULT_PRIORITY));
+ }
+}
+
+void RulesRegistry::RemoveUsedRuleIdentifiers(
+ const std::string& extension_id,
+ const std::vector<std::string>& identifiers) {
+ std::vector<std::string>::const_iterator i;
+ for (i = identifiers.begin(); i != identifiers.end(); ++i)
+ used_rule_identifiers_[extension_id].erase(*i);
+}
+
+void RulesRegistry::RemoveAllUsedRuleIdentifiers(
+ const std::string& extension_id) {
+ used_rule_identifiers_.erase(extension_id);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/declarative/rules_registry.h b/chromium/extensions/browser/api/declarative/rules_registry.h
new file mode 100644
index 00000000000..91f34f1cfd9
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative/rules_registry.h
@@ -0,0 +1,304 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DECLARATIVE_RULES_REGISTRY_H__
+#define EXTENSIONS_BROWSER_API_DECLARATIVE_RULES_REGISTRY_H__
+
+#include <stddef.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "extensions/common/api/events.h"
+#include "extensions/common/one_shot_event.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace base {
+class Value;
+} // namespace base
+
+namespace extensions {
+
+class Extension;
+class RulesCacheDelegate;
+
+// A base class for RulesRegistries that takes care of storing the
+// api::events::Rule objects. It contains all the methods that need to run
+// on the registry thread; methods that need to run on the UI thread are
+// separated in the RulesCacheDelegate object.
+class RulesRegistry : public base::RefCountedThreadSafe<RulesRegistry> {
+ public:
+ enum Defaults { DEFAULT_PRIORITY = 100 };
+ // After the RulesCacheDelegate object (the part of the registry which runs on
+ // the UI thread) is created, a pointer to it is passed to |*ui_part|.
+ // In tests, |browser_context| and |ui_part| can be NULL (at the same time).
+ // In that case the storage functionality disabled (no RulesCacheDelegate
+ // object created).
+ RulesRegistry(content::BrowserContext* browser_context,
+ const std::string& event_name,
+ content::BrowserThread::ID owner_thread,
+ RulesCacheDelegate* cache_delegate,
+ int id);
+
+ const OneShotEvent& ready() const {
+ return ready_;
+ }
+
+ // RulesRegistry implementation:
+
+ // Registers |rules|, owned by |extension_id| to this RulesRegistry.
+ // If a concrete RuleRegistry does not support some of the rules,
+ // it may ignore them.
+ //
+ // |rules| is a list of Rule instances following the definition of the
+ // declarative extension APIs. It is guaranteed that each rule in |rules| has
+ // a unique name within the scope of |extension_id| that has not been
+ // registered before, unless it has been removed again.
+ // The ownership of rules remains with the caller.
+ //
+ // Returns an empty string if the function is successful or an error
+ // message otherwise.
+ //
+ // IMPORTANT: This function is atomic. Either all rules that are deemed
+ // relevant are added or none.
+ std::string AddRules(const std::string& extension_id,
+ const std::vector<linked_ptr<api::events::Rule>>& rules);
+
+ // Unregisters all rules listed in |rule_identifiers| and owned by
+ // |extension_id| from this RulesRegistry.
+ // Some or all IDs in |rule_identifiers| may not be stored in this
+ // RulesRegistry and are ignored.
+ //
+ // Returns an empty string if the function is successful or an error
+ // message otherwise.
+ //
+ // IMPORTANT: This function is atomic. Either all rules that are deemed
+ // relevant are removed or none.
+ std::string RemoveRules(
+ const std::string& extension_id,
+ const std::vector<std::string>& rule_identifiers);
+
+ // Same as RemoveAllRules but acts on all rules owned by |extension_id|.
+ std::string RemoveAllRules(const std::string& extension_id);
+
+ // Returns all rules listed in |rule_identifiers| and owned by |extension_id|
+ // registered in this RuleRegistry. Entries in |rule_identifiers| that
+ // are unknown are ignored.
+ //
+ // The returned rules are stored in |out|. Ownership is passed to the caller.
+ void GetRules(const std::string& extension_id,
+ const std::vector<std::string>& rule_identifiers,
+ std::vector<linked_ptr<api::events::Rule>>* out);
+
+ // Same as GetRules but returns all rules owned by |extension_id|.
+ void GetAllRules(const std::string& extension_id,
+ std::vector<linked_ptr<api::events::Rule>>* out);
+
+ // Called to notify the RulesRegistry that the extension availability has
+ // changed, so that the registry can update which rules are active.
+ void OnExtensionUnloaded(const Extension* extension);
+ void OnExtensionUninstalled(const Extension* extension);
+ void OnExtensionLoaded(const Extension* extension);
+
+ // Returns the number of entries in used_rule_identifiers_ for leak detection.
+ // Every ExtensionId counts as one entry, even if it contains no rules.
+ size_t GetNumberOfUsedRuleIdentifiersForTesting() const;
+
+ // Returns the RulesCacheDelegate. This is used for testing.
+ RulesCacheDelegate* rules_cache_delegate_for_testing() const {
+ return cache_delegate_.get();
+ }
+
+ // Returns the context where the rules registry lives.
+ content::BrowserContext* browser_context() const { return browser_context_; }
+
+ // Returns the ID of the thread on which the rules registry lives.
+ // It is safe to call this function from any thread.
+ content::BrowserThread::ID owner_thread() const { return owner_thread_; }
+
+ // The name of the event with which rules are registered.
+ const std::string& event_name() const { return event_name_; }
+
+ // The unique identifier for this RulesRegistry object.
+ int id() const { return id_; }
+
+ protected:
+ virtual ~RulesRegistry();
+
+ // These functions need to apply the rules to the browser, while the base
+ // class will handle defaulting empty fields before calling *Impl, and will
+ // automatically cache the rules and re-call *Impl on browser startup.
+ virtual std::string AddRulesImpl(
+ const std::string& extension_id,
+ const std::vector<linked_ptr<api::events::Rule>>& rules) = 0;
+ virtual std::string RemoveRulesImpl(
+ const std::string& extension_id,
+ const std::vector<std::string>& rule_identifiers) = 0;
+ virtual std::string RemoveAllRulesImpl(
+ const std::string& extension_id) = 0;
+
+ private:
+ friend class base::RefCountedThreadSafe<RulesRegistry>;
+ friend class RulesCacheDelegate;
+
+ typedef std::string ExtensionId;
+ typedef std::string RuleId;
+ typedef std::pair<ExtensionId, RuleId> RulesDictionaryKey;
+ typedef std::map<RulesDictionaryKey, linked_ptr<api::events::Rule>>
+ RulesDictionary;
+ enum ProcessChangedRulesState {
+ // ProcessChangedRules can never be called, |cache_delegate_| is NULL.
+ NEVER_PROCESS,
+ // A task to call ProcessChangedRules is scheduled for future execution.
+ SCHEDULED_FOR_PROCESSING,
+ // No task to call ProcessChangedRules is scheduled yet, but it is possible
+ // to schedule one.
+ NOT_SCHEDULED_FOR_PROCESSING
+ };
+ typedef std::map<ExtensionId, ProcessChangedRulesState> ProcessStateMap;
+
+ base::WeakPtr<RulesRegistry> GetWeakPtr() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ return weak_ptr_factory_.GetWeakPtr();
+ }
+
+ // Internal implementation of the AddRules interface which adds
+ // |from_manifest| which is true when the source is the manifest.
+ std::string AddRulesInternal(
+ const std::string& extension_id,
+ const std::vector<linked_ptr<api::events::Rule>>& rules,
+ RulesDictionary* out);
+
+ // The precondition for calling this method is that all rules have unique IDs.
+ // AddRules establishes this precondition and calls into this method.
+ // Stored rules already meet this precondition and so they avoid calling
+ // CheckAndFillInOptionalRules for improved performance.
+ //
+ // Returns an empty string if the function is successful or an error
+ // message otherwise.
+ std::string AddRulesNoFill(
+ const std::string& extension_id,
+ const std::vector<linked_ptr<api::events::Rule>>& rules,
+ RulesDictionary* out);
+
+ // Same as GetRules but returns all rules owned by |extension_id| for a given
+ // |rules| dictionary.
+ void GetRules(const std::string& extension_id,
+ const RulesDictionary& rules,
+ std::vector<linked_ptr<api::events::Rule>>* out);
+
+ // Common processing after extension's rules have changed.
+ void ProcessChangedRules(const std::string& extension_id);
+
+ // Calls ProcessChangedRules if
+ // |process_changed_rules_requested_(extension_id)| ==
+ // NOT_SCHEDULED_FOR_PROCESSING.
+ void MaybeProcessChangedRules(const std::string& extension_id);
+
+ // This method implements the functionality of RemoveAllRules, except for not
+ // calling MaybeProcessChangedRules. That way updating the rules store and
+ // extension prefs is avoided. This method is called when an extension is
+ // uninstalled, that way there is no clash with the preferences being wiped.
+ // Set |remove_manifest_rules| to true if |manifest_rules_| should be cleared
+ // along with |rules_|.
+ std::string RemoveAllRulesNoStoreUpdate(const std::string& extension_id,
+ bool remove_manifest_rules);
+
+ void MarkReady(base::Time storage_init_time);
+
+ // Deserialize the rules from the given Value object and add them to the
+ // RulesRegistry.
+ void DeserializeAndAddRules(const std::string& extension_id,
+ scoped_ptr<base::Value> rules);
+
+ // Reports an internal error with the specified params to the extensions
+ // client.
+ void ReportInternalError(const std::string& extension_id,
+ const std::string& error);
+
+ // The context to which this rules registry belongs.
+ content::BrowserContext* browser_context_;
+
+ // The ID of the thread on which the rules registry lives.
+ const content::BrowserThread::ID owner_thread_;
+
+ // The name of the event with which rules are registered.
+ const std::string event_name_;
+
+ // The key that identifies the context in which these rules apply.
+ int id_;
+
+ RulesDictionary rules_;
+
+ RulesDictionary manifest_rules_;
+
+ // Signaled when we have finished reading from storage for all extensions that
+ // are loaded on startup.
+ OneShotEvent ready_;
+
+ ProcessStateMap process_changed_rules_requested_;
+
+ // Returns whether any existing rule is registered with identifier |rule_id|
+ // for extension |extension_id|.
+ bool IsUniqueId(const std::string& extension_id,
+ const std::string& rule_id) const;
+
+ // Creates an ID that is unique within the scope of|extension_id|.
+ std::string GenerateUniqueId(const std::string& extension_id);
+
+ // Verifies that all |rules| have unique IDs or initializes them with
+ // unique IDs if they don't have one. In case of duplicate IDs, this function
+ // returns a non-empty error message.
+ std::string CheckAndFillInOptionalRules(
+ const std::string& extension_id,
+ const std::vector<linked_ptr<api::events::Rule>>& rules);
+
+ // Initializes the priority fields in case they have not been set.
+ void FillInOptionalPriorities(
+ const std::vector<linked_ptr<api::events::Rule>>& rules);
+
+ // Removes all |identifiers| of |extension_id| from |used_rule_identifiers_|.
+ void RemoveUsedRuleIdentifiers(const std::string& extension_id,
+ const std::vector<std::string>& identifiers);
+
+ // Same as RemoveUsedRuleIdentifiers but operates on all rules of
+ // |extension_id|.
+ void RemoveAllUsedRuleIdentifiers(const std::string& extension_id);
+
+ typedef std::string RuleIdentifier;
+ typedef std::map<ExtensionId, std::set<RuleIdentifier> > RuleIdentifiersMap;
+ RuleIdentifiersMap used_rule_identifiers_;
+ int last_generated_rule_identifier_id_;
+
+ // |cache_delegate_| is owned by the registry service. If |cache_delegate_| is
+ // NULL, then the storage functionality is disabled (this is used in tests).
+ // This registry cannot own |cache_delegate_| because during the time after
+ // rules registry service shuts down on UI thread, and the registry is
+ // destroyed on its thread, the use of the |cache_delegate_| would not be
+ // safe. The registry only ever associates with one RulesCacheDelegate
+ // instance.
+ base::WeakPtr<RulesCacheDelegate> cache_delegate_;
+
+ base::WeakPtrFactory<RulesRegistry> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(RulesRegistry);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DECLARATIVE_RULES_REGISTRY_H__
diff --git a/chromium/extensions/browser/api/declarative/rules_registry_service.cc b/chromium/extensions/browser/api/declarative/rules_registry_service.cc
new file mode 100644
index 00000000000..d5c3b2f276b
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative/rules_registry_service.cc
@@ -0,0 +1,227 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/declarative/rules_registry_service.h"
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "extensions/browser/api/declarative/rules_cache_delegate.h"
+#include "extensions/browser/api/declarative_content/content_rules_registry.h"
+#include "extensions/browser/api/declarative_webrequest/webrequest_constants.h"
+#include "extensions/browser/api/declarative_webrequest/webrequest_rules_registry.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/api/web_request/web_request_api.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/extension.h"
+
+namespace extensions {
+
+namespace {
+
+// Registers |web_request_rules_registry| on the IO thread.
+void RegisterToExtensionWebRequestEventRouterOnIO(
+ content::BrowserContext* browser_context,
+ int rules_registry_id,
+ scoped_refptr<WebRequestRulesRegistry> web_request_rules_registry) {
+ ExtensionWebRequestEventRouter::GetInstance()->RegisterRulesRegistry(
+ browser_context, rules_registry_id, web_request_rules_registry);
+}
+
+void NotifyWithExtensionSafe(
+ scoped_refptr<const Extension> extension,
+ void (RulesRegistry::*notification_callback)(const Extension*),
+ scoped_refptr<RulesRegistry> registry) {
+ (registry.get()->*notification_callback)(extension.get());
+}
+
+} // namespace
+
+const int RulesRegistryService::kDefaultRulesRegistryID = 0;
+const int RulesRegistryService::kInvalidRulesRegistryID = -1;
+
+RulesRegistryService::RulesRegistryService(content::BrowserContext* context)
+ : current_rules_registry_id_(kDefaultRulesRegistryID),
+ content_rules_registry_(NULL),
+ extension_registry_observer_(this),
+ browser_context_(context) {
+ if (browser_context_) {
+ extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
+ EnsureDefaultRulesRegistriesRegistered(kDefaultRulesRegistryID);
+ }
+}
+
+RulesRegistryService::~RulesRegistryService() {}
+
+int RulesRegistryService::GetNextRulesRegistryID() {
+ return ++current_rules_registry_id_;
+}
+
+void RulesRegistryService::EnsureDefaultRulesRegistriesRegistered(
+ int rules_registry_id) {
+ if (!browser_context_)
+ return;
+ RulesRegistryKey key(declarative_webrequest_constants::kOnRequest,
+ rules_registry_id);
+ // If we can find the key in the |rule_registries_| then we have already
+ // installed the default registries.
+ if (ContainsKey(rule_registries_, key))
+ return;
+
+ // Only cache rules for regular pages.
+ RulesCacheDelegate* web_request_cache_delegate = NULL;
+ if (rules_registry_id == kDefaultRulesRegistryID) {
+ // Create a RulesCacheDelegate.
+ web_request_cache_delegate =
+ new RulesCacheDelegate(true /*log_storage_init_delay*/);
+ cache_delegates_.push_back(make_scoped_ptr(web_request_cache_delegate));
+ }
+ scoped_refptr<WebRequestRulesRegistry> web_request_rules_registry(
+ new WebRequestRulesRegistry(browser_context_, web_request_cache_delegate,
+ rules_registry_id));
+
+ RegisterRulesRegistry(web_request_rules_registry);
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO, FROM_HERE,
+ base::Bind(&RegisterToExtensionWebRequestEventRouterOnIO,
+ browser_context_, rules_registry_id,
+ web_request_rules_registry));
+
+ // Only create a ContentRulesRegistry for regular pages.
+ if (rules_registry_id == kDefaultRulesRegistryID) {
+ RulesCacheDelegate* content_rules_cache_delegate =
+ new RulesCacheDelegate(false /*log_storage_init_delay*/);
+ cache_delegates_.push_back(make_scoped_ptr(content_rules_cache_delegate));
+ scoped_refptr<ContentRulesRegistry> content_rules_registry =
+ ExtensionsAPIClient::Get()->CreateContentRulesRegistry(
+ browser_context_, content_rules_cache_delegate);
+ if (content_rules_registry.get() != nullptr) {
+ RegisterRulesRegistry(content_rules_registry);
+ content_rules_registry_ = content_rules_registry.get();
+ }
+ }
+}
+
+void RulesRegistryService::Shutdown() {
+ // Release the references to all registries. This would happen soon during
+ // destruction of |*this|, but we need the ExtensionWebRequestEventRouter to
+ // be the last to reference the WebRequestRulesRegistry objects, so that
+ // the posted task below causes their destruction on the IO thread, not on UI
+ // where the destruction of |*this| takes place.
+ // TODO(vabr): Remove once http://crbug.com/218451#c6 gets addressed.
+ rule_registries_.clear();
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO, FROM_HERE,
+ base::Bind(&RegisterToExtensionWebRequestEventRouterOnIO,
+ browser_context_,
+ RulesRegistryService::kDefaultRulesRegistryID,
+ scoped_refptr<WebRequestRulesRegistry>(NULL)));
+}
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<RulesRegistryService> >
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<RulesRegistryService>*
+RulesRegistryService::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+// static
+RulesRegistryService* RulesRegistryService::Get(
+ content::BrowserContext* context) {
+ return BrowserContextKeyedAPIFactory<RulesRegistryService>::Get(context);
+}
+
+// static
+RulesRegistryService* RulesRegistryService::GetIfExists(
+ content::BrowserContext* context) {
+ return BrowserContextKeyedAPIFactory<RulesRegistryService>::GetIfExists(
+ context);
+}
+
+void RulesRegistryService::RegisterRulesRegistry(
+ scoped_refptr<RulesRegistry> rule_registry) {
+ const std::string event_name(rule_registry->event_name());
+ RulesRegistryKey key(event_name, rule_registry->id());
+ DCHECK(rule_registries_.find(key) == rule_registries_.end());
+ rule_registries_[key] = rule_registry;
+}
+
+scoped_refptr<RulesRegistry> RulesRegistryService::GetRulesRegistry(
+ int rules_registry_id,
+ const std::string& event_name) {
+ EnsureDefaultRulesRegistriesRegistered(rules_registry_id);
+
+ RulesRegistryKey key(event_name, rules_registry_id);
+ RulesRegistryMap::const_iterator i = rule_registries_.find(key);
+ if (i == rule_registries_.end())
+ return scoped_refptr<RulesRegistry>();
+ return i->second;
+}
+
+void RulesRegistryService::RemoveRulesRegistriesByID(int rules_registry_id) {
+ std::set<RulesRegistryKey> registries_to_delete;
+ for (RulesRegistryMap::iterator it = rule_registries_.begin();
+ it != rule_registries_.end(); ++it) {
+ const RulesRegistryKey& key = it->first;
+ if (key.rules_registry_id != rules_registry_id)
+ continue;
+ // Modifying a container while iterating over it can lead to badness. So we
+ // save the keys in another container and delete them in another loop.
+ registries_to_delete.insert(key);
+ }
+
+ for (std::set<RulesRegistryKey>::iterator it = registries_to_delete.begin();
+ it != registries_to_delete.end(); ++it) {
+ rule_registries_.erase(*it);
+ }
+}
+
+void RulesRegistryService::SimulateExtensionUninstalled(
+ const Extension* extension) {
+ NotifyRegistriesHelper(&RulesRegistry::OnExtensionUninstalled, extension);
+}
+
+void RulesRegistryService::NotifyRegistriesHelper(
+ void (RulesRegistry::*notification_callback)(const Extension*),
+ const Extension* extension) {
+ RulesRegistryMap::iterator i;
+ for (i = rule_registries_.begin(); i != rule_registries_.end(); ++i) {
+ scoped_refptr<RulesRegistry> registry = i->second;
+ if (content::BrowserThread::CurrentlyOn(registry->owner_thread())) {
+ (registry.get()->*notification_callback)(extension);
+ } else {
+ content::BrowserThread::PostTask(
+ registry->owner_thread(), FROM_HERE,
+ base::Bind(&NotifyWithExtensionSafe, make_scoped_refptr(extension),
+ notification_callback, registry));
+ }
+ }
+}
+
+void RulesRegistryService::OnExtensionLoaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension) {
+ NotifyRegistriesHelper(&RulesRegistry::OnExtensionLoaded, extension);
+}
+
+void RulesRegistryService::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ NotifyRegistriesHelper(&RulesRegistry::OnExtensionUnloaded, extension);
+}
+
+void RulesRegistryService::OnExtensionUninstalled(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ extensions::UninstallReason reason) {
+ NotifyRegistriesHelper(&RulesRegistry::OnExtensionUninstalled, extension);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/declarative/rules_registry_service.h b/chromium/extensions/browser/api/declarative/rules_registry_service.h
new file mode 100644
index 00000000000..c79f1dfd31b
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative/rules_registry_service.h
@@ -0,0 +1,153 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DECLARATIVE_RULES_REGISTRY_SERVICE_H__
+#define EXTENSIONS_BROWSER_API_DECLARATIVE_RULES_REGISTRY_SERVICE_H__
+
+#include <map>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/scoped_observer.h"
+#include "extensions/browser/api/declarative/rules_registry.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_registry_observer.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+class ContentRulesRegistry;
+class ExtensionRegistry;
+class RulesRegistryStorageDelegate;
+}
+
+namespace extensions {
+
+// This class owns all RulesRegistries implementations of an ExtensionService.
+// This class lives on the UI thread.
+class RulesRegistryService : public BrowserContextKeyedAPI,
+ public ExtensionRegistryObserver {
+ public:
+ static const int kDefaultRulesRegistryID;
+ static const int kInvalidRulesRegistryID;
+
+ struct RulesRegistryKey {
+ std::string event_name;
+ int rules_registry_id;
+ RulesRegistryKey(const std::string& event_name, int rules_registry_id)
+ : event_name(event_name), rules_registry_id(rules_registry_id) {}
+ bool operator<(const RulesRegistryKey& other) const {
+ return std::tie(event_name, rules_registry_id) <
+ std::tie(other.event_name, other.rules_registry_id);
+ }
+ };
+
+ explicit RulesRegistryService(content::BrowserContext* context);
+ ~RulesRegistryService() override;
+
+ // Unregisters refptrs to concrete RulesRegistries at other objects that were
+ // created by us so that the RulesRegistries can be released.
+ void Shutdown() override;
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<RulesRegistryService>*
+ GetFactoryInstance();
+
+ // Convenience method to get the RulesRegistryService for a context. If a
+ // RulesRegistryService does not already exist for |context|, one will be
+ // created and returned.
+ static RulesRegistryService* Get(content::BrowserContext* context);
+
+ // The same as Get(), except that if a RulesRegistryService does not already
+ // exist for |context|, nullptr is returned.
+ static RulesRegistryService* GetIfExists(content::BrowserContext* context);
+
+ int GetNextRulesRegistryID();
+
+ // Registers the default RulesRegistries used in Chromium.
+ void EnsureDefaultRulesRegistriesRegistered(int rules_registry_id);
+
+ // Registers a RulesRegistry and wraps it in an InitializingRulesRegistry.
+ void RegisterRulesRegistry(scoped_refptr<RulesRegistry> rule_registry);
+
+ // Returns the RulesRegistry for |event_name| and |rules_registry_id| or
+ // NULL if no such registry has been registered. Default rules registries
+ // (such as the WebRequest rules registry) will be created on first access.
+ scoped_refptr<RulesRegistry> GetRulesRegistry(int rules_registry_id,
+ const std::string& event_name);
+
+ // Remove all rules registries of the given rules_registry_id.
+ void RemoveRulesRegistriesByID(int rules_registry_id);
+
+ // Accessors for each type of rules registry.
+ ContentRulesRegistry* content_rules_registry() const {
+ CHECK(content_rules_registry_);
+ return content_rules_registry_;
+ }
+
+ // For testing.
+ void SimulateExtensionUninstalled(const Extension* extension);
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<RulesRegistryService>;
+
+ // Maps <event name, rules registry ID> to RuleRegistries that handle these
+ // events.
+ typedef std::map<RulesRegistryKey, scoped_refptr<RulesRegistry> >
+ RulesRegistryMap;
+
+ // ExtensionRegistryObserver implementation.
+ void OnExtensionLoaded(content::BrowserContext* browser_context,
+ const Extension* extension) override;
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+ void OnExtensionUninstalled(content::BrowserContext* browser_context,
+ const Extension* extension,
+ extensions::UninstallReason reason) override;
+
+ // Iterates over all registries, and calls |notification_callback| on them
+ // with |extension| as the argument. If a registry lives on a different
+ // thread, the call is posted to that thread, so no guarantee of synchronous
+ // processing.
+ void NotifyRegistriesHelper(
+ void (RulesRegistry::*notification_callback)(const Extension*),
+ const Extension* extension);
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() {
+ return "RulesRegistryService";
+ }
+ static const bool kServiceHasOwnInstanceInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ int current_rules_registry_id_;
+
+ RulesRegistryMap rule_registries_;
+
+ // We own the parts of the registries which need to run on the UI thread.
+ std::vector<scoped_ptr<RulesCacheDelegate>> cache_delegates_;
+
+ // Weak pointer into rule_registries_ to make it easier to handle content rule
+ // conditions.
+ ContentRulesRegistry* content_rules_registry_;
+
+ // Listen to extension load, unloaded notification.
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+
+ content::BrowserContext* browser_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(RulesRegistryService);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DECLARATIVE_RULES_REGISTRY_SERVICE_H__
diff --git a/chromium/extensions/browser/api/declarative/rules_registry_unittest.cc b/chromium/extensions/browser/api/declarative/rules_registry_unittest.cc
new file mode 100644
index 00000000000..4e76b2b8559
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative/rules_registry_unittest.cc
@@ -0,0 +1,342 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/declarative/rules_registry.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/message_loop/message_loop.h"
+#include "base/values.h"
+#include "content/public/test/test_browser_thread.h"
+#include "extensions/browser/api/declarative/rules_registry_service.h"
+#include "extensions/browser/api/declarative/test_rules_registry.h"
+#include "extensions/browser/api_test_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+const char kExtensionId[] = "foobar";
+const char kRuleId[] = "foo";
+const int key = extensions::RulesRegistryService::kDefaultRulesRegistryID;
+} // namespace
+
+namespace extensions {
+
+using api_test_utils::ParseDictionary;
+
+TEST(RulesRegistryTest, FillOptionalIdentifiers) {
+ base::MessageLoopForUI message_loop;
+ content::TestBrowserThread thread(content::BrowserThread::UI, &message_loop);
+
+ std::string error;
+ scoped_refptr<RulesRegistry> registry =
+ new TestRulesRegistry(content::BrowserThread::UI, "" /*event_name*/, key);
+
+ // Add rules and check that their identifiers are filled and unique.
+
+ std::vector<linked_ptr<api::events::Rule>> add_rules;
+ add_rules.push_back(make_linked_ptr(new api::events::Rule));
+ add_rules.push_back(make_linked_ptr(new api::events::Rule));
+ error = registry->AddRules(kExtensionId, add_rules);
+ EXPECT_TRUE(error.empty()) << error;
+
+ std::vector<linked_ptr<api::events::Rule>> get_rules;
+ registry->GetAllRules(kExtensionId, &get_rules);
+
+ ASSERT_EQ(2u, get_rules.size());
+
+ ASSERT_TRUE(get_rules[0]->id.get());
+ EXPECT_NE("", *get_rules[0]->id);
+
+ ASSERT_TRUE(get_rules[1]->id.get());
+ EXPECT_NE("", *get_rules[1]->id);
+
+ EXPECT_NE(*get_rules[0]->id, *get_rules[1]->id);
+
+ EXPECT_EQ(1u /*extensions*/ + 2u /*rules*/,
+ registry->GetNumberOfUsedRuleIdentifiersForTesting());
+
+ // Check that we cannot add a new rule with the same ID.
+
+ std::vector<linked_ptr<api::events::Rule>> add_rules_2;
+ add_rules_2.push_back(make_linked_ptr(new api::events::Rule));
+ add_rules_2[0]->id.reset(new std::string(*get_rules[0]->id));
+ error = registry->AddRules(kExtensionId, add_rules_2);
+ EXPECT_FALSE(error.empty());
+
+ std::vector<linked_ptr<api::events::Rule>> get_rules_2;
+ registry->GetAllRules(kExtensionId, &get_rules_2);
+ ASSERT_EQ(2u, get_rules_2.size());
+ EXPECT_EQ(1u /*extensions*/ + 2u /*rules*/,
+ registry->GetNumberOfUsedRuleIdentifiersForTesting());
+
+ // Check that we can register the old rule IDs once they were unregistered.
+
+ std::vector<std::string> remove_rules_3;
+ remove_rules_3.push_back(*get_rules[0]->id);
+ error = registry->RemoveRules(kExtensionId, remove_rules_3);
+ EXPECT_TRUE(error.empty()) << error;
+
+ EXPECT_EQ(1u /*extensions*/ + 1u /*rules*/,
+ registry->GetNumberOfUsedRuleIdentifiersForTesting());
+
+ std::vector<linked_ptr<api::events::Rule>> get_rules_3a;
+ registry->GetAllRules(kExtensionId, &get_rules_3a);
+ ASSERT_EQ(1u, get_rules_3a.size());
+
+ std::vector<linked_ptr<api::events::Rule>> add_rules_3;
+ add_rules_3.push_back(make_linked_ptr(new api::events::Rule));
+ add_rules_3[0]->id.reset(new std::string(*get_rules[0]->id));
+ error = registry->AddRules(kExtensionId, add_rules_3);
+ EXPECT_TRUE(error.empty()) << error;
+ EXPECT_EQ(1u /*extensions*/ + 2u /*rules*/,
+ registry->GetNumberOfUsedRuleIdentifiersForTesting());
+
+ std::vector<linked_ptr<api::events::Rule>> get_rules_3b;
+ registry->GetAllRules(kExtensionId, &get_rules_3b);
+ ASSERT_EQ(2u, get_rules_3b.size());
+
+ // Check that we can register a rule with an ID that is not modified.
+
+ error = registry->RemoveAllRules(kExtensionId);
+ EXPECT_TRUE(error.empty()) << error;
+ EXPECT_EQ(0u /*extensions*/ + 0u /*rules*/,
+ registry->GetNumberOfUsedRuleIdentifiersForTesting());
+
+ std::vector<linked_ptr<api::events::Rule>> get_rules_4a;
+ registry->GetAllRules(kExtensionId, &get_rules_4a);
+ ASSERT_TRUE(get_rules_4a.empty());
+
+ std::vector<linked_ptr<api::events::Rule>> add_rules_4;
+ add_rules_4.push_back(make_linked_ptr(new api::events::Rule));
+ add_rules_4[0]->id.reset(new std::string(kRuleId));
+ error = registry->AddRules(kExtensionId, add_rules_4);
+ EXPECT_TRUE(error.empty()) << error;
+
+ EXPECT_EQ(1u /*extensions*/ + 1u /*rules*/,
+ registry->GetNumberOfUsedRuleIdentifiersForTesting());
+
+ std::vector<linked_ptr<api::events::Rule>> get_rules_4b;
+ registry->GetAllRules(kExtensionId, &get_rules_4b);
+
+ ASSERT_EQ(1u, get_rules_4b.size());
+
+ ASSERT_TRUE(get_rules_4b[0]->id.get());
+ EXPECT_EQ(kRuleId, *get_rules_4b[0]->id);
+
+ // Create extension
+ scoped_ptr<base::DictionaryValue> manifest = ParseDictionary(
+ "{"
+ " \"name\": \"Test\","
+ " \"version\": \"1\""
+ "}");
+ scoped_refptr<Extension> extension = ExtensionBuilder()
+ .SetManifest(std::move(manifest))
+ .SetID(kExtensionId)
+ .Build();
+ registry->OnExtensionUninstalled(extension.get());
+ EXPECT_EQ(0u /*extensions*/ + 0u /*rules*/,
+ registry->GetNumberOfUsedRuleIdentifiersForTesting());
+
+ // Make sure that deletion traits of registry are executed.
+ registry = NULL;
+ message_loop.RunUntilIdle();
+}
+
+TEST(RulesRegistryTest, FillOptionalPriority) {
+ base::MessageLoopForUI message_loop;
+ content::TestBrowserThread thread(content::BrowserThread::UI, &message_loop);
+
+ std::string error;
+ scoped_refptr<RulesRegistry> registry =
+ new TestRulesRegistry(content::BrowserThread::UI, "" /*event_name*/, key);
+
+ // Add rules and check that their priorities are filled if they are empty.
+
+ std::vector<linked_ptr<api::events::Rule>> add_rules;
+ add_rules.push_back(make_linked_ptr(new api::events::Rule));
+ add_rules[0]->priority.reset(new int(2));
+ add_rules.push_back(make_linked_ptr(new api::events::Rule));
+ error = registry->AddRules(kExtensionId, add_rules);
+ EXPECT_TRUE(error.empty()) << error;
+
+ std::vector<linked_ptr<api::events::Rule>> get_rules;
+ registry->GetAllRules(kExtensionId, &get_rules);
+
+ ASSERT_EQ(2u, get_rules.size());
+
+ ASSERT_TRUE(get_rules[0]->priority.get());
+ ASSERT_TRUE(get_rules[1]->priority.get());
+
+ // Verify the precondition so that the following EXPECT_EQ statements work.
+ EXPECT_GT(RulesRegistry::DEFAULT_PRIORITY, 2);
+ EXPECT_EQ(2, std::min(*get_rules[0]->priority, *get_rules[1]->priority));
+ EXPECT_EQ(RulesRegistry::DEFAULT_PRIORITY,
+ std::max(*get_rules[0]->priority, *get_rules[1]->priority));
+
+ // Make sure that deletion traits of registry are executed.
+ registry = NULL;
+ message_loop.RunUntilIdle();
+}
+
+// Test verifies 2 rules defined in the manifest appear in the registry.
+TEST(RulesRegistryTest, TwoRulesInManifest) {
+ base::MessageLoopForUI message_loop;
+ content::TestBrowserThread thread(content::BrowserThread::UI, &message_loop);
+
+ // Create extension
+ scoped_ptr<base::DictionaryValue> manifest = ParseDictionary(
+ "{"
+ " \"name\": \"Test\","
+ " \"version\": \"1\","
+ " \"event_rules\": ["
+ " {"
+ " \"id\": \"000\","
+ " \"priority\": 200,"
+ " \"tags\": [\"tagged\"],"
+ " \"event\": \"declarativeContent.onPageChanged\","
+ " \"actions\": [{"
+ " \"type\": \"declarativeContent.ShowPageAction\""
+ " }],"
+ " \"conditions\" : [{"
+ " \"css\": [\"video\"],"
+ " \"type\" : \"declarativeContent.PageStateMatcher\""
+ " }]"
+ " },"
+ " {"
+ " \"event\": \"declarativeContent.onPageChanged\","
+ " \"actions\": [{"
+ " \"type\": \"declarativeContent.ShowPageAction\""
+ " }],"
+ " \"conditions\" : [{"
+ " \"css\": [\"input[type='password']\"],"
+ " \"type\" : \"declarativeContent.PageStateMatcher\""
+ " }]"
+ " }"
+ " ]"
+ "}");
+ scoped_refptr<Extension> extension = ExtensionBuilder()
+ .SetManifest(std::move(manifest))
+ .SetID(kExtensionId)
+ .Build();
+
+ scoped_refptr<RulesRegistry> registry = new TestRulesRegistry(
+ content::BrowserThread::UI, "declarativeContent.onPageChanged", key);
+ // Simulate what RulesRegistryService would do on extension load.
+ registry->OnExtensionLoaded(extension.get());
+
+ std::vector<linked_ptr<api::events::Rule>> get_rules;
+ registry->GetAllRules(kExtensionId, &get_rules);
+
+ ASSERT_EQ(2u, get_rules.size());
+ scoped_ptr<base::DictionaryValue> expected_rule_0 = ParseDictionary(
+ "{"
+ " \"id\": \"000\","
+ " \"priority\": 200,"
+ " \"tags\": [\"tagged\"],"
+ " \"actions\": [{"
+ " \"instanceType\": \"declarativeContent.ShowPageAction\""
+ " }],"
+ " \"conditions\" : [{"
+ " \"css\": [\"video\"],"
+ " \"instanceType\" : \"declarativeContent.PageStateMatcher\""
+ " }]"
+ "}");
+ EXPECT_TRUE(expected_rule_0->Equals(get_rules[0]->ToValue().get()));
+
+ scoped_ptr<base::DictionaryValue> expected_rule_1 = ParseDictionary(
+ "{"
+ " \"id\": \"_0_\","
+ " \"priority\": 100,"
+ " \"actions\": [{"
+ " \"instanceType\": \"declarativeContent.ShowPageAction\""
+ " }],"
+ " \"conditions\" : [{"
+ " \"css\": [\"input[type='password']\"],"
+ " \"instanceType\" : \"declarativeContent.PageStateMatcher\""
+ " }]"
+ "}");
+ EXPECT_TRUE(expected_rule_1->Equals(get_rules[1]->ToValue().get()));
+}
+
+// Tests verifies that rules defined in the manifest cannot be deleted but
+// programmatically added rules still can be deleted.
+TEST(RulesRegistryTest, DeleteRuleInManifest) {
+ base::MessageLoopForUI message_loop;
+ content::TestBrowserThread thread(content::BrowserThread::UI, &message_loop);
+
+ // Create extension
+ scoped_ptr<base::DictionaryValue> manifest = ParseDictionary(
+ "{"
+ " \"name\": \"Test\","
+ " \"version\": \"1\","
+ " \"event_rules\": [{"
+ " \"id\": \"manifest_rule_0\","
+ " \"event\": \"declarativeContent.onPageChanged\","
+ " \"actions\": [{"
+ " \"type\": \"declarativeContent.ShowPageAction\""
+ " }],"
+ " \"conditions\" : [{"
+ " \"css\": [\"video\"],"
+ " \"type\" : \"declarativeContent.PageStateMatcher\""
+ " }]"
+ " }]"
+ "}");
+ scoped_refptr<Extension> extension = ExtensionBuilder()
+ .SetManifest(std::move(manifest))
+ .SetID(kExtensionId)
+ .Build();
+
+ scoped_refptr<RulesRegistry> registry = new TestRulesRegistry(
+ content::BrowserThread::UI, "declarativeContent.onPageChanged", key);
+ // Simulate what RulesRegistryService would do on extension load.
+ registry->OnExtensionLoaded(extension.get());
+ // Add some extra rules outside of the manifest.
+ std::vector<linked_ptr<api::events::Rule>> add_rules;
+ linked_ptr<api::events::Rule> rule_1 = make_linked_ptr(new api::events::Rule);
+ rule_1->id.reset(new std::string("rule_1"));
+ linked_ptr<api::events::Rule> rule_2 = make_linked_ptr(new api::events::Rule);
+ rule_2->id.reset(new std::string("rule_2"));
+ add_rules.push_back(rule_1);
+ add_rules.push_back(rule_2);
+ registry->AddRules(kExtensionId, add_rules);
+
+ std::vector<linked_ptr<api::events::Rule>> get_rules;
+ registry->GetAllRules(kExtensionId, &get_rules);
+ ASSERT_EQ(3u, get_rules.size());
+ EXPECT_EQ("manifest_rule_0", *(get_rules[0]->id));
+ EXPECT_EQ("rule_1", *(get_rules[1]->id));
+ EXPECT_EQ("rule_2", *(get_rules[2]->id));
+
+ // Remove a rule from outside the manifest.
+ std::vector<std::string> remove_ids;
+ remove_ids.push_back("rule_1");
+ EXPECT_TRUE(registry->RemoveRules(kExtensionId, remove_ids).empty());
+ get_rules.clear();
+ registry->GetAllRules(kExtensionId, &get_rules);
+ EXPECT_EQ(2u, get_rules.size());
+ EXPECT_EQ("manifest_rule_0", *(get_rules[0]->id));
+ EXPECT_EQ("rule_2", *(get_rules[1]->id));
+
+ // Attempt to remove rule in manifest.
+ remove_ids.clear();
+ remove_ids.push_back("manifest_rule_0");
+ EXPECT_FALSE(registry->RemoveRules(kExtensionId, remove_ids).empty());
+ get_rules.clear();
+ registry->GetAllRules(kExtensionId, &get_rules);
+ ASSERT_EQ(2u, get_rules.size());
+ EXPECT_EQ("manifest_rule_0", *(get_rules[0]->id));
+ EXPECT_EQ("rule_2", *(get_rules[1]->id));
+
+ // Remove all rules.
+ registry->RemoveAllRules(kExtensionId);
+ get_rules.clear();
+ registry->GetAllRules(kExtensionId, &get_rules);
+ ASSERT_EQ(1u, get_rules.size());
+ EXPECT_EQ("manifest_rule_0", *(get_rules[0]->id));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/declarative/test_rules_registry.cc b/chromium/extensions/browser/api/declarative/test_rules_registry.cc
new file mode 100644
index 00000000000..a8d359524ff
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative/test_rules_registry.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "extensions/browser/api/declarative/test_rules_registry.h"
+
+namespace extensions {
+
+TestRulesRegistry::TestRulesRegistry(content::BrowserThread::ID owner_thread,
+ const std::string& event_name,
+ int rules_registry_id)
+ : RulesRegistry(NULL /*profile*/,
+ event_name,
+ owner_thread,
+ NULL,
+ rules_registry_id) {
+}
+
+TestRulesRegistry::TestRulesRegistry(content::BrowserContext* browser_context,
+ const std::string& event_name,
+ content::BrowserThread::ID owner_thread,
+ RulesCacheDelegate* cache_delegate,
+ int rules_registry_id)
+ : RulesRegistry(browser_context,
+ event_name,
+ owner_thread,
+ cache_delegate,
+ rules_registry_id) {
+}
+
+std::string TestRulesRegistry::AddRulesImpl(
+ const std::string& extension_id,
+ const std::vector<linked_ptr<api::events::Rule>>& rules) {
+ return result_;
+}
+
+std::string TestRulesRegistry::RemoveRulesImpl(
+ const std::string& extension_id,
+ const std::vector<std::string>& rule_identifiers) {
+ return result_;
+}
+
+std::string TestRulesRegistry::RemoveAllRulesImpl(
+ const std::string& extension_id) {
+ return result_;
+}
+
+void TestRulesRegistry::SetResult(const std::string& result) {
+ result_ = result;
+}
+
+TestRulesRegistry::~TestRulesRegistry() {}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/declarative/test_rules_registry.h b/chromium/extensions/browser/api/declarative/test_rules_registry.h
new file mode 100644
index 00000000000..cd00d2cb4e6
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative/test_rules_registry.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DECLARATIVE_TEST_RULES_REGISTRY_H__
+#define EXTENSIONS_BROWSER_API_DECLARATIVE_TEST_RULES_REGISTRY_H__
+
+#include "base/macros.h"
+#include "extensions/browser/api/declarative/rules_registry.h"
+
+namespace extensions {
+
+// This is a trivial test RulesRegistry that can only store and retrieve rules.
+class TestRulesRegistry : public RulesRegistry {
+ public:
+ TestRulesRegistry(content::BrowserThread::ID owner_thread,
+ const std::string& event_name,
+ int rules_registry_id);
+ TestRulesRegistry(content::BrowserContext* browser_context,
+ const std::string& event_name,
+ content::BrowserThread::ID owner_thread,
+ RulesCacheDelegate* cache_delegate,
+ int rules_registry_id);
+
+ // RulesRegistry implementation:
+ std::string AddRulesImpl(
+ const std::string& extension_id,
+ const std::vector<linked_ptr<api::events::Rule>>& rules) override;
+ std::string RemoveRulesImpl(
+ const std::string& extension_id,
+ const std::vector<std::string>& rule_identifiers) override;
+ std::string RemoveAllRulesImpl(const std::string& extension_id) override;
+
+ // Sets the result message that will be returned by the next call of
+ // AddRulesImpl, RemoveRulesImpl and RemoveAllRulesImpl.
+ void SetResult(const std::string& result);
+
+ protected:
+ ~TestRulesRegistry() override;
+
+ private:
+ // The string that gets returned by the implementation functions of
+ // RulesRegistry. Defaults to "".
+ std::string result_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestRulesRegistry);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DECLARATIVE_TEST_RULES_REGISTRY_H__
diff --git a/chromium/extensions/browser/api/declarative_content/content_rules_registry.h b/chromium/extensions/browser/api/declarative_content/content_rules_registry.h
new file mode 100644
index 00000000000..239687fa7b7
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative_content/content_rules_registry.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DECLARATIVE_CONTENT_CONTENT_RULES_REGISTRY_H__
+#define EXTENSIONS_BROWSER_API_DECLARATIVE_CONTENT_CONTENT_RULES_REGISTRY_H__
+
+#include <string>
+
+#include "base/macros.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/api/declarative/rules_registry.h"
+
+namespace content {
+class BrowserContext;
+class WebContents;
+struct FrameNavigateParams;
+struct LoadCommittedDetails;
+}
+
+namespace extensions {
+
+// This class acts as an //extensions-side interface for ContentRulesRegistry
+// to allow RulesRegistryService to be moved to //extensions.
+// TODO(wjmaclean): Remove this once ContentRulesRegistry moves to
+// //extensions.
+//
+// Note: when dealing with WebContents associated with OffTheRecord contexts,
+// functions on this interface must be invoked for BOTH the Original and
+// OffTheRecord ContentRulesRegistry instances. This is necessary because the
+// Original ContentRulesRegistry instance handles spanning-mode incognito
+// extensions.
+class ContentRulesRegistry : public RulesRegistry {
+ public:
+ ContentRulesRegistry(content::BrowserContext* browser_context,
+ const std::string& event_name,
+ content::BrowserThread::ID owner_thread,
+ RulesCacheDelegate* cache_delegate,
+ int rules_registry_id)
+ : RulesRegistry(browser_context,
+ event_name,
+ owner_thread,
+ cache_delegate,
+ rules_registry_id) {}
+
+ // Notifies the registry that it should evaluate rules for |contents|.
+ virtual void MonitorWebContentsForRuleEvaluation(
+ content::WebContents* contents) = 0;
+
+ // Applies all content rules given that a tab was just navigated.
+ virtual void DidNavigateMainFrame(
+ content::WebContents* tab,
+ const content::LoadCommittedDetails& details,
+ const content::FrameNavigateParams& params) = 0;
+
+ protected:
+ ~ContentRulesRegistry() override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ContentRulesRegistry);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DECLARATIVE_CONTENT_CONTENT_RULES_REGISTRY_H__
diff --git a/chromium/extensions/browser/api/declarative_webrequest/request_stage.cc b/chromium/extensions/browser/api/declarative_webrequest/request_stage.cc
new file mode 100644
index 00000000000..3b033e331a0
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative_webrequest/request_stage.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/declarative_webrequest/request_stage.h"
+
+
+namespace extensions {
+
+const unsigned int kActiveStages = ON_BEFORE_REQUEST |
+ ON_BEFORE_SEND_HEADERS |
+ ON_HEADERS_RECEIVED |
+ ON_AUTH_REQUIRED;
+
+// HighestBit<n> computes the highest bit of |n| in compile time, provided that
+// |n| is a positive compile-time constant.
+template <long unsigned int n>
+struct HighestBit {
+ static_assert(n > 0, "argument is not a positive compile time constant");
+ enum { VALUE = HighestBit<(n >> 1)>::VALUE << 1 };
+};
+template <>
+struct HighestBit<1> {
+ enum { VALUE = 1 };
+};
+
+const unsigned int kLastActiveStage = HighestBit<kActiveStages>::VALUE;
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/declarative_webrequest/request_stage.h b/chromium/extensions/browser/api/declarative_webrequest/request_stage.h
new file mode 100644
index 00000000000..025ec6d3737
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative_webrequest/request_stage.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DECLARATIVE_WEBREQUEST_REQUEST_STAGE_H_
+#define EXTENSIONS_BROWSER_API_DECLARATIVE_WEBREQUEST_REQUEST_STAGE_H_
+
+namespace extensions {
+
+// The stages of the web request during which a condition could be tested and
+// an action could be applied. This is required because for example the response
+// headers cannot be tested before a request has been sent. Note that currently
+// not all stages are supported in declarative Web Request, only those marked
+// as "active" in |kActiveStages| below.
+enum RequestStage {
+ ON_BEFORE_REQUEST = 1 << 0,
+ ON_BEFORE_SEND_HEADERS = 1 << 1,
+ ON_SEND_HEADERS = 1 << 2,
+ ON_HEADERS_RECEIVED = 1 << 3,
+ ON_AUTH_REQUIRED = 1 << 4,
+ ON_BEFORE_REDIRECT = 1 << 5,
+ ON_RESPONSE_STARTED = 1 << 6,
+ ON_COMPLETED = 1 << 7,
+ ON_ERROR = 1 << 8
+};
+
+// The bitmap with active stages.
+extern const unsigned int kActiveStages;
+
+// The highest bit in |kActiveStages|. This allows to iterate over all active
+// stages in a "for" loop.
+extern const unsigned int kLastActiveStage;
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DECLARATIVE_WEBREQUEST_REQUEST_STAGE_H_
diff --git a/chromium/extensions/browser/api/declarative_webrequest/webrequest_action.cc b/chromium/extensions/browser/api/declarative_webrequest/webrequest_action.cc
new file mode 100644
index 00000000000..6cf0dbb7410
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative_webrequest/webrequest_action.cc
@@ -0,0 +1,1167 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/declarative_webrequest/webrequest_action.h"
+
+#include <limits>
+#include <utility>
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "content/public/browser/resource_request_info.h"
+#include "content/public/common/url_constants.h"
+#include "extensions/browser/api/declarative/deduping_factory.h"
+#include "extensions/browser/api/declarative_webrequest/request_stage.h"
+#include "extensions/browser/api/declarative_webrequest/webrequest_condition.h"
+#include "extensions/browser/api/declarative_webrequest/webrequest_constants.h"
+#include "extensions/browser/api/web_request/web_request_api_constants.h"
+#include "extensions/browser/api/web_request/web_request_api_helpers.h"
+#include "extensions/browser/api/web_request/web_request_permissions.h"
+#include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
+#include "extensions/browser/info_map.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "net/http/http_util.h"
+#include "net/url_request/url_request.h"
+#include "third_party/re2/src/re2/re2.h"
+
+using content::ResourceRequestInfo;
+
+namespace extensions {
+
+namespace helpers = extension_web_request_api_helpers;
+namespace keys = declarative_webrequest_constants;
+
+namespace {
+// Error messages.
+const char kIgnoreRulesRequiresParameterError[] =
+ "IgnoreRules requires at least one parameter.";
+
+const char kTransparentImageUrl[] = "data:image/png;base64,iVBORw0KGgoAAAANSUh"
+ "EUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==";
+const char kEmptyDocumentUrl[] = "data:text/html,";
+
+#define INPUT_FORMAT_VALIDATE(test) do { \
+ if (!(test)) { \
+ *bad_message = true; \
+ return scoped_refptr<const WebRequestAction>(NULL); \
+ } \
+ } while (0)
+
+scoped_ptr<helpers::RequestCookie> ParseRequestCookie(
+ const base::DictionaryValue* dict) {
+ scoped_ptr<helpers::RequestCookie> result(new helpers::RequestCookie);
+ std::string tmp;
+ if (dict->GetString(keys::kNameKey, &tmp))
+ result->name.reset(new std::string(tmp));
+ if (dict->GetString(keys::kValueKey, &tmp))
+ result->value.reset(new std::string(tmp));
+ return result;
+}
+
+void ParseResponseCookieImpl(const base::DictionaryValue* dict,
+ helpers::ResponseCookie* cookie) {
+ std::string string_tmp;
+ int int_tmp = 0;
+ bool bool_tmp = false;
+ if (dict->GetString(keys::kNameKey, &string_tmp))
+ cookie->name.reset(new std::string(string_tmp));
+ if (dict->GetString(keys::kValueKey, &string_tmp))
+ cookie->value.reset(new std::string(string_tmp));
+ if (dict->GetString(keys::kExpiresKey, &string_tmp))
+ cookie->expires.reset(new std::string(string_tmp));
+ if (dict->GetInteger(keys::kMaxAgeKey, &int_tmp))
+ cookie->max_age.reset(new int(int_tmp));
+ if (dict->GetString(keys::kDomainKey, &string_tmp))
+ cookie->domain.reset(new std::string(string_tmp));
+ if (dict->GetString(keys::kPathKey, &string_tmp))
+ cookie->path.reset(new std::string(string_tmp));
+ if (dict->GetBoolean(keys::kSecureKey, &bool_tmp))
+ cookie->secure.reset(new bool(bool_tmp));
+ if (dict->GetBoolean(keys::kHttpOnlyKey, &bool_tmp))
+ cookie->http_only.reset(new bool(bool_tmp));
+}
+
+scoped_ptr<helpers::ResponseCookie> ParseResponseCookie(
+ const base::DictionaryValue* dict) {
+ scoped_ptr<helpers::ResponseCookie> result(new helpers::ResponseCookie);
+ ParseResponseCookieImpl(dict, result.get());
+ return result;
+}
+
+scoped_ptr<helpers::FilterResponseCookie> ParseFilterResponseCookie(
+ const base::DictionaryValue* dict) {
+ scoped_ptr<helpers::FilterResponseCookie> result(
+ new helpers::FilterResponseCookie);
+ ParseResponseCookieImpl(dict, result.get());
+
+ int int_tmp = 0;
+ bool bool_tmp = false;
+ if (dict->GetInteger(keys::kAgeUpperBoundKey, &int_tmp))
+ result->age_upper_bound.reset(new int(int_tmp));
+ if (dict->GetInteger(keys::kAgeLowerBoundKey, &int_tmp))
+ result->age_lower_bound.reset(new int(int_tmp));
+ if (dict->GetBoolean(keys::kSessionCookieKey, &bool_tmp))
+ result->session_cookie.reset(new bool(bool_tmp));
+ return result;
+}
+
+// Helper function for WebRequestActions that can be instantiated by just
+// calling the constructor.
+template <class T>
+scoped_refptr<const WebRequestAction> CallConstructorFactoryMethod(
+ const std::string& instance_type,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message) {
+ return scoped_refptr<const WebRequestAction>(new T);
+}
+
+scoped_refptr<const WebRequestAction> CreateRedirectRequestAction(
+ const std::string& instance_type,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message) {
+ const base::DictionaryValue* dict = NULL;
+ CHECK(value->GetAsDictionary(&dict));
+ std::string redirect_url_string;
+ INPUT_FORMAT_VALIDATE(
+ dict->GetString(keys::kRedirectUrlKey, &redirect_url_string));
+ GURL redirect_url(redirect_url_string);
+ return scoped_refptr<const WebRequestAction>(
+ new WebRequestRedirectAction(redirect_url));
+}
+
+scoped_refptr<const WebRequestAction> CreateRedirectRequestByRegExAction(
+ const std::string& instance_type,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message) {
+ const base::DictionaryValue* dict = NULL;
+ CHECK(value->GetAsDictionary(&dict));
+ std::string from;
+ std::string to;
+ INPUT_FORMAT_VALIDATE(dict->GetString(keys::kFromKey, &from));
+ INPUT_FORMAT_VALIDATE(dict->GetString(keys::kToKey, &to));
+
+ to = WebRequestRedirectByRegExAction::PerlToRe2Style(to);
+
+ RE2::Options options;
+ options.set_case_sensitive(false);
+ scoped_ptr<RE2> from_pattern(new RE2(from, options));
+
+ if (!from_pattern->ok()) {
+ *error = "Invalid pattern '" + from + "' -> '" + to + "'";
+ return scoped_refptr<const WebRequestAction>(NULL);
+ }
+ return scoped_refptr<const WebRequestAction>(
+ new WebRequestRedirectByRegExAction(std::move(from_pattern), to));
+}
+
+scoped_refptr<const WebRequestAction> CreateSetRequestHeaderAction(
+ const std::string& instance_type,
+ const base::Value* json_value,
+ std::string* error,
+ bool* bad_message) {
+ const base::DictionaryValue* dict = NULL;
+ CHECK(json_value->GetAsDictionary(&dict));
+ std::string name;
+ std::string value;
+ INPUT_FORMAT_VALIDATE(dict->GetString(keys::kNameKey, &name));
+ INPUT_FORMAT_VALIDATE(dict->GetString(keys::kValueKey, &value));
+ if (!net::HttpUtil::IsValidHeaderName(name)) {
+ *error = extension_web_request_api_constants::kInvalidHeaderName;
+ return scoped_refptr<const WebRequestAction>(NULL);
+ }
+ if (!net::HttpUtil::IsValidHeaderValue(value)) {
+ *error = ErrorUtils::FormatErrorMessage(
+ extension_web_request_api_constants::kInvalidHeaderValue, name);
+ return scoped_refptr<const WebRequestAction>(NULL);
+ }
+ return scoped_refptr<const WebRequestAction>(
+ new WebRequestSetRequestHeaderAction(name, value));
+}
+
+scoped_refptr<const WebRequestAction> CreateRemoveRequestHeaderAction(
+ const std::string& instance_type,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message) {
+ const base::DictionaryValue* dict = NULL;
+ CHECK(value->GetAsDictionary(&dict));
+ std::string name;
+ INPUT_FORMAT_VALIDATE(dict->GetString(keys::kNameKey, &name));
+ if (!net::HttpUtil::IsValidHeaderName(name)) {
+ *error = extension_web_request_api_constants::kInvalidHeaderName;
+ return scoped_refptr<const WebRequestAction>(NULL);
+ }
+ return scoped_refptr<const WebRequestAction>(
+ new WebRequestRemoveRequestHeaderAction(name));
+}
+
+scoped_refptr<const WebRequestAction> CreateAddResponseHeaderAction(
+ const std::string& instance_type,
+ const base::Value* json_value,
+ std::string* error,
+ bool* bad_message) {
+ const base::DictionaryValue* dict = NULL;
+ CHECK(json_value->GetAsDictionary(&dict));
+ std::string name;
+ std::string value;
+ INPUT_FORMAT_VALIDATE(dict->GetString(keys::kNameKey, &name));
+ INPUT_FORMAT_VALIDATE(dict->GetString(keys::kValueKey, &value));
+ if (!net::HttpUtil::IsValidHeaderName(name)) {
+ *error = extension_web_request_api_constants::kInvalidHeaderName;
+ return scoped_refptr<const WebRequestAction>(NULL);
+ }
+ if (!net::HttpUtil::IsValidHeaderValue(value)) {
+ *error = ErrorUtils::FormatErrorMessage(
+ extension_web_request_api_constants::kInvalidHeaderValue, name);
+ return scoped_refptr<const WebRequestAction>(NULL);
+ }
+ return scoped_refptr<const WebRequestAction>(
+ new WebRequestAddResponseHeaderAction(name, value));
+}
+
+scoped_refptr<const WebRequestAction> CreateRemoveResponseHeaderAction(
+ const std::string& instance_type,
+ const base::Value* json_value,
+ std::string* error,
+ bool* bad_message) {
+ const base::DictionaryValue* dict = NULL;
+ CHECK(json_value->GetAsDictionary(&dict));
+ std::string name;
+ std::string value;
+ INPUT_FORMAT_VALIDATE(dict->GetString(keys::kNameKey, &name));
+ bool has_value = dict->GetString(keys::kValueKey, &value);
+ if (!net::HttpUtil::IsValidHeaderName(name)) {
+ *error = extension_web_request_api_constants::kInvalidHeaderName;
+ return scoped_refptr<const WebRequestAction>(NULL);
+ }
+ if (has_value && !net::HttpUtil::IsValidHeaderValue(value)) {
+ *error = ErrorUtils::FormatErrorMessage(
+ extension_web_request_api_constants::kInvalidHeaderValue, name);
+ return scoped_refptr<const WebRequestAction>(NULL);
+ }
+ return scoped_refptr<const WebRequestAction>(
+ new WebRequestRemoveResponseHeaderAction(name, value, has_value));
+}
+
+scoped_refptr<const WebRequestAction> CreateIgnoreRulesAction(
+ const std::string& instance_type,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message) {
+ const base::DictionaryValue* dict = NULL;
+ CHECK(value->GetAsDictionary(&dict));
+ bool has_parameter = false;
+ int minimum_priority = std::numeric_limits<int>::min();
+ std::string ignore_tag;
+ if (dict->HasKey(keys::kLowerPriorityThanKey)) {
+ INPUT_FORMAT_VALIDATE(
+ dict->GetInteger(keys::kLowerPriorityThanKey, &minimum_priority));
+ has_parameter = true;
+ }
+ if (dict->HasKey(keys::kHasTagKey)) {
+ INPUT_FORMAT_VALIDATE(dict->GetString(keys::kHasTagKey, &ignore_tag));
+ has_parameter = true;
+ }
+ if (!has_parameter) {
+ *error = kIgnoreRulesRequiresParameterError;
+ return scoped_refptr<const WebRequestAction>(NULL);
+ }
+ return scoped_refptr<const WebRequestAction>(
+ new WebRequestIgnoreRulesAction(minimum_priority, ignore_tag));
+}
+
+scoped_refptr<const WebRequestAction> CreateRequestCookieAction(
+ const std::string& instance_type,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message) {
+ using extension_web_request_api_helpers::RequestCookieModification;
+
+ const base::DictionaryValue* dict = NULL;
+ CHECK(value->GetAsDictionary(&dict));
+
+ linked_ptr<RequestCookieModification> modification(
+ new RequestCookieModification);
+
+ // Get modification type.
+ if (instance_type == keys::kAddRequestCookieType)
+ modification->type = helpers::ADD;
+ else if (instance_type == keys::kEditRequestCookieType)
+ modification->type = helpers::EDIT;
+ else if (instance_type == keys::kRemoveRequestCookieType)
+ modification->type = helpers::REMOVE;
+ else
+ INPUT_FORMAT_VALIDATE(false);
+
+ // Get filter.
+ if (modification->type == helpers::EDIT ||
+ modification->type == helpers::REMOVE) {
+ const base::DictionaryValue* filter = NULL;
+ INPUT_FORMAT_VALIDATE(dict->GetDictionary(keys::kFilterKey, &filter));
+ modification->filter = ParseRequestCookie(filter);
+ }
+
+ // Get new value.
+ if (modification->type == helpers::ADD) {
+ const base::DictionaryValue* value = NULL;
+ INPUT_FORMAT_VALIDATE(dict->GetDictionary(keys::kCookieKey, &value));
+ modification->modification = ParseRequestCookie(value);
+ } else if (modification->type == helpers::EDIT) {
+ const base::DictionaryValue* value = NULL;
+ INPUT_FORMAT_VALIDATE(dict->GetDictionary(keys::kModificationKey, &value));
+ modification->modification = ParseRequestCookie(value);
+ }
+
+ return scoped_refptr<const WebRequestAction>(
+ new WebRequestRequestCookieAction(modification));
+}
+
+scoped_refptr<const WebRequestAction> CreateResponseCookieAction(
+ const std::string& instance_type,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message) {
+ using extension_web_request_api_helpers::ResponseCookieModification;
+
+ const base::DictionaryValue* dict = NULL;
+ CHECK(value->GetAsDictionary(&dict));
+
+ linked_ptr<ResponseCookieModification> modification(
+ new ResponseCookieModification);
+
+ // Get modification type.
+ if (instance_type == keys::kAddResponseCookieType)
+ modification->type = helpers::ADD;
+ else if (instance_type == keys::kEditResponseCookieType)
+ modification->type = helpers::EDIT;
+ else if (instance_type == keys::kRemoveResponseCookieType)
+ modification->type = helpers::REMOVE;
+ else
+ INPUT_FORMAT_VALIDATE(false);
+
+ // Get filter.
+ if (modification->type == helpers::EDIT ||
+ modification->type == helpers::REMOVE) {
+ const base::DictionaryValue* filter = NULL;
+ INPUT_FORMAT_VALIDATE(dict->GetDictionary(keys::kFilterKey, &filter));
+ modification->filter = ParseFilterResponseCookie(filter);
+ }
+
+ // Get new value.
+ if (modification->type == helpers::ADD) {
+ const base::DictionaryValue* value = NULL;
+ INPUT_FORMAT_VALIDATE(dict->GetDictionary(keys::kCookieKey, &value));
+ modification->modification = ParseResponseCookie(value);
+ } else if (modification->type == helpers::EDIT) {
+ const base::DictionaryValue* value = NULL;
+ INPUT_FORMAT_VALIDATE(dict->GetDictionary(keys::kModificationKey, &value));
+ modification->modification = ParseResponseCookie(value);
+ }
+
+ return scoped_refptr<const WebRequestAction>(
+ new WebRequestResponseCookieAction(modification));
+}
+
+scoped_refptr<const WebRequestAction> CreateSendMessageToExtensionAction(
+ const std::string& name,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message) {
+ const base::DictionaryValue* dict = NULL;
+ CHECK(value->GetAsDictionary(&dict));
+ std::string message;
+ INPUT_FORMAT_VALIDATE(dict->GetString(keys::kMessageKey, &message));
+ return scoped_refptr<const WebRequestAction>(
+ new WebRequestSendMessageToExtensionAction(message));
+}
+
+struct WebRequestActionFactory {
+ DedupingFactory<WebRequestAction> factory;
+
+ WebRequestActionFactory() : factory(5) {
+ factory.RegisterFactoryMethod(
+ keys::kAddRequestCookieType,
+ DedupingFactory<WebRequestAction>::IS_PARAMETERIZED,
+ &CreateRequestCookieAction);
+ factory.RegisterFactoryMethod(
+ keys::kAddResponseCookieType,
+ DedupingFactory<WebRequestAction>::IS_PARAMETERIZED,
+ &CreateResponseCookieAction);
+ factory.RegisterFactoryMethod(
+ keys::kAddResponseHeaderType,
+ DedupingFactory<WebRequestAction>::IS_PARAMETERIZED,
+ &CreateAddResponseHeaderAction);
+ factory.RegisterFactoryMethod(
+ keys::kCancelRequestType,
+ DedupingFactory<WebRequestAction>::IS_NOT_PARAMETERIZED,
+ &CallConstructorFactoryMethod<WebRequestCancelAction>);
+ factory.RegisterFactoryMethod(
+ keys::kEditRequestCookieType,
+ DedupingFactory<WebRequestAction>::IS_PARAMETERIZED,
+ &CreateRequestCookieAction);
+ factory.RegisterFactoryMethod(
+ keys::kEditResponseCookieType,
+ DedupingFactory<WebRequestAction>::IS_PARAMETERIZED,
+ &CreateResponseCookieAction);
+ factory.RegisterFactoryMethod(
+ keys::kRedirectByRegExType,
+ DedupingFactory<WebRequestAction>::IS_PARAMETERIZED,
+ &CreateRedirectRequestByRegExAction);
+ factory.RegisterFactoryMethod(
+ keys::kRedirectRequestType,
+ DedupingFactory<WebRequestAction>::IS_PARAMETERIZED,
+ &CreateRedirectRequestAction);
+ factory.RegisterFactoryMethod(
+ keys::kRedirectToTransparentImageType,
+ DedupingFactory<WebRequestAction>::IS_NOT_PARAMETERIZED,
+ &CallConstructorFactoryMethod<
+ WebRequestRedirectToTransparentImageAction>);
+ factory.RegisterFactoryMethod(
+ keys::kRedirectToEmptyDocumentType,
+ DedupingFactory<WebRequestAction>::IS_NOT_PARAMETERIZED,
+ &CallConstructorFactoryMethod<WebRequestRedirectToEmptyDocumentAction>);
+ factory.RegisterFactoryMethod(
+ keys::kRemoveRequestCookieType,
+ DedupingFactory<WebRequestAction>::IS_PARAMETERIZED,
+ &CreateRequestCookieAction);
+ factory.RegisterFactoryMethod(
+ keys::kRemoveResponseCookieType,
+ DedupingFactory<WebRequestAction>::IS_PARAMETERIZED,
+ &CreateResponseCookieAction);
+ factory.RegisterFactoryMethod(
+ keys::kSetRequestHeaderType,
+ DedupingFactory<WebRequestAction>::IS_PARAMETERIZED,
+ &CreateSetRequestHeaderAction);
+ factory.RegisterFactoryMethod(
+ keys::kRemoveRequestHeaderType,
+ DedupingFactory<WebRequestAction>::IS_PARAMETERIZED,
+ &CreateRemoveRequestHeaderAction);
+ factory.RegisterFactoryMethod(
+ keys::kRemoveResponseHeaderType,
+ DedupingFactory<WebRequestAction>::IS_PARAMETERIZED,
+ &CreateRemoveResponseHeaderAction);
+ factory.RegisterFactoryMethod(
+ keys::kIgnoreRulesType,
+ DedupingFactory<WebRequestAction>::IS_PARAMETERIZED,
+ &CreateIgnoreRulesAction);
+ factory.RegisterFactoryMethod(
+ keys::kSendMessageToExtensionType,
+ DedupingFactory<WebRequestAction>::IS_PARAMETERIZED,
+ &CreateSendMessageToExtensionAction);
+ }
+};
+
+base::LazyInstance<WebRequestActionFactory>::Leaky
+ g_web_request_action_factory = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+//
+// WebRequestAction
+//
+
+WebRequestAction::~WebRequestAction() {}
+
+bool WebRequestAction::Equals(const WebRequestAction* other) const {
+ return type() == other->type();
+}
+
+bool WebRequestAction::HasPermission(const InfoMap* extension_info_map,
+ const std::string& extension_id,
+ const net::URLRequest* request,
+ bool crosses_incognito) const {
+ if (WebRequestPermissions::HideRequest(extension_info_map, request))
+ return false;
+
+ // In unit tests we don't have an extension_info_map object here and skip host
+ // permission checks.
+ if (!extension_info_map)
+ return true;
+
+ const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request);
+ int process_id = info ? info->GetChildID() : 0;
+
+ // The embedder can always access all hosts from within a <webview>.
+ // The same is not true of extensions.
+ if (WebViewRendererState::GetInstance()->IsGuest(process_id))
+ return true;
+
+ WebRequestPermissions::HostPermissionsCheck permission_check =
+ WebRequestPermissions::REQUIRE_ALL_URLS;
+ switch (host_permissions_strategy()) {
+ case STRATEGY_DEFAULT: // Default value is already set.
+ break;
+ case STRATEGY_NONE:
+ permission_check = WebRequestPermissions::DO_NOT_CHECK_HOST;
+ break;
+ case STRATEGY_HOST:
+ permission_check = WebRequestPermissions::REQUIRE_HOST_PERMISSION;
+ break;
+ }
+ // TODO(devlin): Pass in the real tab id here.
+ return WebRequestPermissions::CanExtensionAccessURL(
+ extension_info_map, extension_id, request->url(), -1,
+ crosses_incognito,
+ permission_check) == PermissionsData::ACCESS_ALLOWED;
+}
+
+// static
+scoped_refptr<const WebRequestAction> WebRequestAction::Create(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ const base::Value& json_action,
+ std::string* error,
+ bool* bad_message) {
+ *error = "";
+ *bad_message = false;
+
+ const base::DictionaryValue* action_dict = NULL;
+ INPUT_FORMAT_VALIDATE(json_action.GetAsDictionary(&action_dict));
+
+ std::string instance_type;
+ INPUT_FORMAT_VALIDATE(
+ action_dict->GetString(keys::kInstanceTypeKey, &instance_type));
+
+ WebRequestActionFactory& factory = g_web_request_action_factory.Get();
+ return factory.factory.Instantiate(
+ instance_type, action_dict, error, bad_message);
+}
+
+void WebRequestAction::Apply(const std::string& extension_id,
+ base::Time extension_install_time,
+ ApplyInfo* apply_info) const {
+ if (!HasPermission(apply_info->extension_info_map, extension_id,
+ apply_info->request_data.request,
+ apply_info->crosses_incognito))
+ return;
+ if (stages() & apply_info->request_data.stage) {
+ LinkedPtrEventResponseDelta delta = CreateDelta(
+ apply_info->request_data, extension_id, extension_install_time);
+ if (delta.get())
+ apply_info->deltas->push_back(delta);
+ if (type() == WebRequestAction::ACTION_IGNORE_RULES) {
+ const WebRequestIgnoreRulesAction* ignore_action =
+ static_cast<const WebRequestIgnoreRulesAction*>(this);
+ if (!ignore_action->ignore_tag().empty())
+ apply_info->ignored_tags->insert(ignore_action->ignore_tag());
+ }
+ }
+}
+
+WebRequestAction::WebRequestAction(int stages,
+ Type type,
+ int minimum_priority,
+ HostPermissionsStrategy strategy)
+ : stages_(stages),
+ type_(type),
+ minimum_priority_(minimum_priority),
+ host_permissions_strategy_(strategy) {}
+
+//
+// WebRequestCancelAction
+//
+
+WebRequestCancelAction::WebRequestCancelAction()
+ : WebRequestAction(ON_BEFORE_REQUEST | ON_BEFORE_SEND_HEADERS |
+ ON_HEADERS_RECEIVED | ON_AUTH_REQUIRED,
+ ACTION_CANCEL_REQUEST,
+ std::numeric_limits<int>::min(),
+ STRATEGY_NONE) {}
+
+WebRequestCancelAction::~WebRequestCancelAction() {}
+
+std::string WebRequestCancelAction::GetName() const {
+ return keys::kCancelRequestType;
+}
+
+LinkedPtrEventResponseDelta WebRequestCancelAction::CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const {
+ CHECK(request_data.stage & stages());
+ LinkedPtrEventResponseDelta result(
+ new helpers::EventResponseDelta(extension_id, extension_install_time));
+ result->cancel = true;
+ return result;
+}
+
+//
+// WebRequestRedirectAction
+//
+
+WebRequestRedirectAction::WebRequestRedirectAction(const GURL& redirect_url)
+ : WebRequestAction(ON_BEFORE_REQUEST | ON_HEADERS_RECEIVED,
+ ACTION_REDIRECT_REQUEST,
+ std::numeric_limits<int>::min(),
+ STRATEGY_DEFAULT),
+ redirect_url_(redirect_url) {}
+
+WebRequestRedirectAction::~WebRequestRedirectAction() {}
+
+bool WebRequestRedirectAction::Equals(const WebRequestAction* other) const {
+ return WebRequestAction::Equals(other) &&
+ redirect_url_ ==
+ static_cast<const WebRequestRedirectAction*>(other)->redirect_url_;
+}
+
+std::string WebRequestRedirectAction::GetName() const {
+ return keys::kRedirectRequestType;
+}
+
+LinkedPtrEventResponseDelta WebRequestRedirectAction::CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const {
+ CHECK(request_data.stage & stages());
+ if (request_data.request->url() == redirect_url_)
+ return LinkedPtrEventResponseDelta(NULL);
+ LinkedPtrEventResponseDelta result(
+ new helpers::EventResponseDelta(extension_id, extension_install_time));
+ result->new_url = redirect_url_;
+ return result;
+}
+
+//
+// WebRequestRedirectToTransparentImageAction
+//
+
+WebRequestRedirectToTransparentImageAction::
+ WebRequestRedirectToTransparentImageAction()
+ : WebRequestAction(ON_BEFORE_REQUEST | ON_HEADERS_RECEIVED,
+ ACTION_REDIRECT_TO_TRANSPARENT_IMAGE,
+ std::numeric_limits<int>::min(),
+ STRATEGY_NONE) {}
+
+WebRequestRedirectToTransparentImageAction::
+~WebRequestRedirectToTransparentImageAction() {}
+
+std::string WebRequestRedirectToTransparentImageAction::GetName() const {
+ return keys::kRedirectToTransparentImageType;
+}
+
+LinkedPtrEventResponseDelta
+WebRequestRedirectToTransparentImageAction::CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const {
+ CHECK(request_data.stage & stages());
+ LinkedPtrEventResponseDelta result(
+ new helpers::EventResponseDelta(extension_id, extension_install_time));
+ result->new_url = GURL(kTransparentImageUrl);
+ return result;
+}
+
+//
+// WebRequestRedirectToEmptyDocumentAction
+//
+
+WebRequestRedirectToEmptyDocumentAction::
+ WebRequestRedirectToEmptyDocumentAction()
+ : WebRequestAction(ON_BEFORE_REQUEST | ON_HEADERS_RECEIVED,
+ ACTION_REDIRECT_TO_EMPTY_DOCUMENT,
+ std::numeric_limits<int>::min(),
+ STRATEGY_NONE) {}
+
+WebRequestRedirectToEmptyDocumentAction::
+~WebRequestRedirectToEmptyDocumentAction() {}
+
+std::string WebRequestRedirectToEmptyDocumentAction::GetName() const {
+ return keys::kRedirectToEmptyDocumentType;
+}
+
+LinkedPtrEventResponseDelta
+WebRequestRedirectToEmptyDocumentAction::CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const {
+ CHECK(request_data.stage & stages());
+ LinkedPtrEventResponseDelta result(
+ new helpers::EventResponseDelta(extension_id, extension_install_time));
+ result->new_url = GURL(kEmptyDocumentUrl);
+ return result;
+}
+
+//
+// WebRequestRedirectByRegExAction
+//
+
+WebRequestRedirectByRegExAction::WebRequestRedirectByRegExAction(
+ scoped_ptr<RE2> from_pattern,
+ const std::string& to_pattern)
+ : WebRequestAction(ON_BEFORE_REQUEST | ON_HEADERS_RECEIVED,
+ ACTION_REDIRECT_BY_REGEX_DOCUMENT,
+ std::numeric_limits<int>::min(),
+ STRATEGY_DEFAULT),
+ from_pattern_(std::move(from_pattern)),
+ to_pattern_(to_pattern.data(), to_pattern.size()) {}
+
+WebRequestRedirectByRegExAction::~WebRequestRedirectByRegExAction() {}
+
+// About the syntax of the two languages:
+//
+// ICU (Perl) states:
+// $n The text of capture group n will be substituted for $n. n must be >= 0
+// and not greater than the number of capture groups. A $ not followed by a
+// digit has no special meaning, and will appear in the substitution text
+// as itself, a $.
+// \ Treat the following character as a literal, suppressing any special
+// meaning. Backslash escaping in substitution text is only required for
+// '$' and '\', but may be used on any other character without bad effects.
+//
+// RE2, derived from RE2::Rewrite()
+// \ May only be followed by a digit or another \. If followed by a single
+// digit, both characters represent the respective capture group. If followed
+// by another \, it is used as an escape sequence.
+
+// static
+std::string WebRequestRedirectByRegExAction::PerlToRe2Style(
+ const std::string& perl) {
+ std::string::const_iterator i = perl.begin();
+ std::string result;
+ while (i != perl.end()) {
+ if (*i == '$') {
+ ++i;
+ if (i == perl.end()) {
+ result += '$';
+ return result;
+ } else if (isdigit(*i)) {
+ result += '\\';
+ result += *i;
+ } else {
+ result += '$';
+ result += *i;
+ }
+ } else if (*i == '\\') {
+ ++i;
+ if (i == perl.end()) {
+ result += '\\';
+ } else if (*i == '$') {
+ result += '$';
+ } else if (*i == '\\') {
+ result += "\\\\";
+ } else {
+ result += *i;
+ }
+ } else {
+ result += *i;
+ }
+ ++i;
+ }
+ return result;
+}
+
+bool WebRequestRedirectByRegExAction::Equals(
+ const WebRequestAction* other) const {
+ if (!WebRequestAction::Equals(other))
+ return false;
+ const WebRequestRedirectByRegExAction* casted_other =
+ static_cast<const WebRequestRedirectByRegExAction*>(other);
+ return from_pattern_->pattern() == casted_other->from_pattern_->pattern() &&
+ to_pattern_ == casted_other->to_pattern_;
+}
+
+std::string WebRequestRedirectByRegExAction::GetName() const {
+ return keys::kRedirectByRegExType;
+}
+
+LinkedPtrEventResponseDelta WebRequestRedirectByRegExAction::CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const {
+ CHECK(request_data.stage & stages());
+ CHECK(from_pattern_.get());
+
+ const std::string& old_url = request_data.request->url().spec();
+ std::string new_url = old_url;
+ if (!RE2::Replace(&new_url, *from_pattern_, to_pattern_) ||
+ new_url == old_url) {
+ return LinkedPtrEventResponseDelta(NULL);
+ }
+
+ LinkedPtrEventResponseDelta result(
+ new extension_web_request_api_helpers::EventResponseDelta(
+ extension_id, extension_install_time));
+ result->new_url = GURL(new_url);
+ return result;
+}
+
+//
+// WebRequestSetRequestHeaderAction
+//
+
+WebRequestSetRequestHeaderAction::WebRequestSetRequestHeaderAction(
+ const std::string& name,
+ const std::string& value)
+ : WebRequestAction(ON_BEFORE_SEND_HEADERS,
+ ACTION_SET_REQUEST_HEADER,
+ std::numeric_limits<int>::min(),
+ STRATEGY_DEFAULT),
+ name_(name),
+ value_(value) {}
+
+WebRequestSetRequestHeaderAction::~WebRequestSetRequestHeaderAction() {}
+
+bool WebRequestSetRequestHeaderAction::Equals(
+ const WebRequestAction* other) const {
+ if (!WebRequestAction::Equals(other))
+ return false;
+ const WebRequestSetRequestHeaderAction* casted_other =
+ static_cast<const WebRequestSetRequestHeaderAction*>(other);
+ return name_ == casted_other->name_ && value_ == casted_other->value_;
+}
+
+std::string WebRequestSetRequestHeaderAction::GetName() const {
+ return keys::kSetRequestHeaderType;
+}
+
+
+LinkedPtrEventResponseDelta
+WebRequestSetRequestHeaderAction::CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const {
+ CHECK(request_data.stage & stages());
+ LinkedPtrEventResponseDelta result(
+ new helpers::EventResponseDelta(extension_id, extension_install_time));
+ result->modified_request_headers.SetHeader(name_, value_);
+ return result;
+}
+
+//
+// WebRequestRemoveRequestHeaderAction
+//
+
+WebRequestRemoveRequestHeaderAction::WebRequestRemoveRequestHeaderAction(
+ const std::string& name)
+ : WebRequestAction(ON_BEFORE_SEND_HEADERS,
+ ACTION_REMOVE_REQUEST_HEADER,
+ std::numeric_limits<int>::min(),
+ STRATEGY_DEFAULT),
+ name_(name) {}
+
+WebRequestRemoveRequestHeaderAction::~WebRequestRemoveRequestHeaderAction() {}
+
+bool WebRequestRemoveRequestHeaderAction::Equals(
+ const WebRequestAction* other) const {
+ if (!WebRequestAction::Equals(other))
+ return false;
+ const WebRequestRemoveRequestHeaderAction* casted_other =
+ static_cast<const WebRequestRemoveRequestHeaderAction*>(other);
+ return name_ == casted_other->name_;
+}
+
+std::string WebRequestRemoveRequestHeaderAction::GetName() const {
+ return keys::kRemoveRequestHeaderType;
+}
+
+LinkedPtrEventResponseDelta
+WebRequestRemoveRequestHeaderAction::CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const {
+ CHECK(request_data.stage & stages());
+ LinkedPtrEventResponseDelta result(
+ new helpers::EventResponseDelta(extension_id, extension_install_time));
+ result->deleted_request_headers.push_back(name_);
+ return result;
+}
+
+//
+// WebRequestAddResponseHeaderAction
+//
+
+WebRequestAddResponseHeaderAction::WebRequestAddResponseHeaderAction(
+ const std::string& name,
+ const std::string& value)
+ : WebRequestAction(ON_HEADERS_RECEIVED,
+ ACTION_ADD_RESPONSE_HEADER,
+ std::numeric_limits<int>::min(),
+ STRATEGY_DEFAULT),
+ name_(name),
+ value_(value) {}
+
+WebRequestAddResponseHeaderAction::~WebRequestAddResponseHeaderAction() {}
+
+bool WebRequestAddResponseHeaderAction::Equals(
+ const WebRequestAction* other) const {
+ if (!WebRequestAction::Equals(other))
+ return false;
+ const WebRequestAddResponseHeaderAction* casted_other =
+ static_cast<const WebRequestAddResponseHeaderAction*>(other);
+ return name_ == casted_other->name_ && value_ == casted_other->value_;
+}
+
+std::string WebRequestAddResponseHeaderAction::GetName() const {
+ return keys::kAddResponseHeaderType;
+}
+
+LinkedPtrEventResponseDelta
+WebRequestAddResponseHeaderAction::CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const {
+ CHECK(request_data.stage & stages());
+ const net::HttpResponseHeaders* headers =
+ request_data.original_response_headers;
+ if (!headers)
+ return LinkedPtrEventResponseDelta(NULL);
+
+ // Don't generate the header if it exists already.
+ if (headers->HasHeaderValue(name_, value_))
+ return LinkedPtrEventResponseDelta(NULL);
+
+ LinkedPtrEventResponseDelta result(
+ new helpers::EventResponseDelta(extension_id, extension_install_time));
+ result->added_response_headers.push_back(make_pair(name_, value_));
+ return result;
+}
+
+//
+// WebRequestRemoveResponseHeaderAction
+//
+
+WebRequestRemoveResponseHeaderAction::WebRequestRemoveResponseHeaderAction(
+ const std::string& name,
+ const std::string& value,
+ bool has_value)
+ : WebRequestAction(ON_HEADERS_RECEIVED,
+ ACTION_REMOVE_RESPONSE_HEADER,
+ std::numeric_limits<int>::min(),
+ STRATEGY_DEFAULT),
+ name_(name),
+ value_(value),
+ has_value_(has_value) {}
+
+WebRequestRemoveResponseHeaderAction::~WebRequestRemoveResponseHeaderAction() {}
+
+bool WebRequestRemoveResponseHeaderAction::Equals(
+ const WebRequestAction* other) const {
+ if (!WebRequestAction::Equals(other))
+ return false;
+ const WebRequestRemoveResponseHeaderAction* casted_other =
+ static_cast<const WebRequestRemoveResponseHeaderAction*>(other);
+ return name_ == casted_other->name_ && value_ == casted_other->value_ &&
+ has_value_ == casted_other->has_value_;
+}
+
+std::string WebRequestRemoveResponseHeaderAction::GetName() const {
+ return keys::kRemoveResponseHeaderType;
+}
+
+LinkedPtrEventResponseDelta
+WebRequestRemoveResponseHeaderAction::CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const {
+ CHECK(request_data.stage & stages());
+ const net::HttpResponseHeaders* headers =
+ request_data.original_response_headers;
+ if (!headers)
+ return LinkedPtrEventResponseDelta(NULL);
+
+ LinkedPtrEventResponseDelta result(
+ new helpers::EventResponseDelta(extension_id, extension_install_time));
+ size_t iter = 0;
+ std::string current_value;
+ while (headers->EnumerateHeader(&iter, name_, &current_value)) {
+ if (has_value_ && !base::EqualsCaseInsensitiveASCII(current_value, value_))
+ continue;
+ result->deleted_response_headers.push_back(make_pair(name_, current_value));
+ }
+ return result;
+}
+
+//
+// WebRequestIgnoreRulesAction
+//
+
+WebRequestIgnoreRulesAction::WebRequestIgnoreRulesAction(
+ int minimum_priority,
+ const std::string& ignore_tag)
+ : WebRequestAction(ON_BEFORE_REQUEST | ON_BEFORE_SEND_HEADERS |
+ ON_HEADERS_RECEIVED | ON_AUTH_REQUIRED,
+ ACTION_IGNORE_RULES,
+ minimum_priority,
+ STRATEGY_NONE),
+ ignore_tag_(ignore_tag) {}
+
+WebRequestIgnoreRulesAction::~WebRequestIgnoreRulesAction() {}
+
+bool WebRequestIgnoreRulesAction::Equals(const WebRequestAction* other) const {
+ if (!WebRequestAction::Equals(other))
+ return false;
+ const WebRequestIgnoreRulesAction* casted_other =
+ static_cast<const WebRequestIgnoreRulesAction*>(other);
+ return minimum_priority() == casted_other->minimum_priority() &&
+ ignore_tag_ == casted_other->ignore_tag_;
+}
+
+std::string WebRequestIgnoreRulesAction::GetName() const {
+ return keys::kIgnoreRulesType;
+}
+
+LinkedPtrEventResponseDelta WebRequestIgnoreRulesAction::CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const {
+ CHECK(request_data.stage & stages());
+ return LinkedPtrEventResponseDelta(NULL);
+}
+
+//
+// WebRequestRequestCookieAction
+//
+
+WebRequestRequestCookieAction::WebRequestRequestCookieAction(
+ linked_ptr<RequestCookieModification> request_cookie_modification)
+ : WebRequestAction(ON_BEFORE_SEND_HEADERS,
+ ACTION_MODIFY_REQUEST_COOKIE,
+ std::numeric_limits<int>::min(),
+ STRATEGY_DEFAULT),
+ request_cookie_modification_(request_cookie_modification) {
+ CHECK(request_cookie_modification_.get());
+}
+
+WebRequestRequestCookieAction::~WebRequestRequestCookieAction() {}
+
+bool WebRequestRequestCookieAction::Equals(
+ const WebRequestAction* other) const {
+ if (!WebRequestAction::Equals(other))
+ return false;
+ const WebRequestRequestCookieAction* casted_other =
+ static_cast<const WebRequestRequestCookieAction*>(other);
+ return helpers::NullableEquals(
+ request_cookie_modification_.get(),
+ casted_other->request_cookie_modification_.get());
+}
+
+std::string WebRequestRequestCookieAction::GetName() const {
+ switch (request_cookie_modification_->type) {
+ case helpers::ADD:
+ return keys::kAddRequestCookieType;
+ case helpers::EDIT:
+ return keys::kEditRequestCookieType;
+ case helpers::REMOVE:
+ return keys::kRemoveRequestCookieType;
+ }
+ NOTREACHED();
+ return "";
+}
+
+LinkedPtrEventResponseDelta WebRequestRequestCookieAction::CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const {
+ CHECK(request_data.stage & stages());
+ LinkedPtrEventResponseDelta result(
+ new extension_web_request_api_helpers::EventResponseDelta(
+ extension_id, extension_install_time));
+ result->request_cookie_modifications.push_back(
+ request_cookie_modification_);
+ return result;
+}
+
+//
+// WebRequestResponseCookieAction
+//
+
+WebRequestResponseCookieAction::WebRequestResponseCookieAction(
+ linked_ptr<ResponseCookieModification> response_cookie_modification)
+ : WebRequestAction(ON_HEADERS_RECEIVED,
+ ACTION_MODIFY_RESPONSE_COOKIE,
+ std::numeric_limits<int>::min(),
+ STRATEGY_DEFAULT),
+ response_cookie_modification_(response_cookie_modification) {
+ CHECK(response_cookie_modification_.get());
+}
+
+WebRequestResponseCookieAction::~WebRequestResponseCookieAction() {}
+
+bool WebRequestResponseCookieAction::Equals(
+ const WebRequestAction* other) const {
+ if (!WebRequestAction::Equals(other))
+ return false;
+ const WebRequestResponseCookieAction* casted_other =
+ static_cast<const WebRequestResponseCookieAction*>(other);
+ return helpers::NullableEquals(
+ response_cookie_modification_.get(),
+ casted_other->response_cookie_modification_.get());
+}
+
+std::string WebRequestResponseCookieAction::GetName() const {
+ switch (response_cookie_modification_->type) {
+ case helpers::ADD:
+ return keys::kAddResponseCookieType;
+ case helpers::EDIT:
+ return keys::kEditResponseCookieType;
+ case helpers::REMOVE:
+ return keys::kRemoveResponseCookieType;
+ }
+ NOTREACHED();
+ return "";
+}
+
+LinkedPtrEventResponseDelta WebRequestResponseCookieAction::CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const {
+ CHECK(request_data.stage & stages());
+ LinkedPtrEventResponseDelta result(
+ new extension_web_request_api_helpers::EventResponseDelta(
+ extension_id, extension_install_time));
+ result->response_cookie_modifications.push_back(
+ response_cookie_modification_);
+ return result;
+}
+
+//
+// WebRequestSendMessageToExtensionAction
+//
+
+WebRequestSendMessageToExtensionAction::WebRequestSendMessageToExtensionAction(
+ const std::string& message)
+ : WebRequestAction(ON_BEFORE_REQUEST | ON_BEFORE_SEND_HEADERS |
+ ON_HEADERS_RECEIVED | ON_AUTH_REQUIRED,
+ ACTION_SEND_MESSAGE_TO_EXTENSION,
+ std::numeric_limits<int>::min(),
+ STRATEGY_HOST),
+ message_(message) {}
+
+WebRequestSendMessageToExtensionAction::
+~WebRequestSendMessageToExtensionAction() {}
+
+bool WebRequestSendMessageToExtensionAction::Equals(
+ const WebRequestAction* other) const {
+ if (!WebRequestAction::Equals(other))
+ return false;
+ const WebRequestSendMessageToExtensionAction* casted_other =
+ static_cast<const WebRequestSendMessageToExtensionAction*>(other);
+ return message_ == casted_other->message_;
+}
+
+std::string WebRequestSendMessageToExtensionAction::GetName() const {
+ return keys::kSendMessageToExtensionType;
+}
+
+LinkedPtrEventResponseDelta WebRequestSendMessageToExtensionAction::CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const {
+ CHECK(request_data.stage & stages());
+ LinkedPtrEventResponseDelta result(
+ new extension_web_request_api_helpers::EventResponseDelta(
+ extension_id, extension_install_time));
+ result->messages_to_extension.insert(message_);
+ return result;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/declarative_webrequest/webrequest_action.h b/chromium/extensions/browser/api/declarative_webrequest/webrequest_action.h
new file mode 100644
index 00000000000..be7ae9d7944
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative_webrequest/webrequest_action.h
@@ -0,0 +1,471 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DECLARATIVE_WEBREQUEST_WEBREQUEST_ACTION_H_
+#define EXTENSIONS_BROWSER_API_DECLARATIVE_WEBREQUEST_WEBREQUEST_ACTION_H_
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "extensions/browser/api/declarative/declarative_rule.h"
+#include "extensions/browser/api/declarative_webrequest/request_stage.h"
+#include "extensions/browser/api/web_request/web_request_api_helpers.h"
+#include "extensions/common/api/events.h"
+#include "url/gurl.h"
+
+class WebRequestPermission;
+
+namespace base {
+class DictionaryValue;
+class Time;
+class Value;
+}
+
+namespace extension_web_request_api_helpers {
+struct EventResponseDelta;
+}
+
+namespace extensions {
+class Extension;
+class InfoMap;
+struct WebRequestData;
+}
+
+namespace net {
+class URLRequest;
+}
+
+namespace re2 {
+class RE2;
+}
+
+namespace extensions {
+
+typedef linked_ptr<extension_web_request_api_helpers::EventResponseDelta>
+ LinkedPtrEventResponseDelta;
+
+// Base class for all WebRequestActions of the declarative Web Request API.
+class WebRequestAction : public base::RefCounted<WebRequestAction> {
+ public:
+ // Type identifiers for concrete WebRequestActions. If you add a new type,
+ // also update the unittest WebRequestActionTest.GetName, and add a
+ // WebRequestActionWithThreadsTest.Permission* unittest.
+ enum Type {
+ ACTION_CANCEL_REQUEST,
+ ACTION_REDIRECT_REQUEST,
+ ACTION_REDIRECT_TO_TRANSPARENT_IMAGE,
+ ACTION_REDIRECT_TO_EMPTY_DOCUMENT,
+ ACTION_REDIRECT_BY_REGEX_DOCUMENT,
+ ACTION_SET_REQUEST_HEADER,
+ ACTION_REMOVE_REQUEST_HEADER,
+ ACTION_ADD_RESPONSE_HEADER,
+ ACTION_REMOVE_RESPONSE_HEADER,
+ ACTION_IGNORE_RULES,
+ ACTION_MODIFY_REQUEST_COOKIE,
+ ACTION_MODIFY_RESPONSE_COOKIE,
+ ACTION_SEND_MESSAGE_TO_EXTENSION,
+ };
+
+ // Strategies for checking host permissions.
+ enum HostPermissionsStrategy {
+ STRATEGY_NONE, // Do not check host permissions.
+ STRATEGY_DEFAULT, // Check for host permissions for all URLs
+ // before creating the delta.
+ STRATEGY_HOST, // Check that host permissions match the URL
+ // of the request.
+ };
+
+ // Information necessary to decide how to apply a WebRequestAction
+ // inside a matching rule.
+ struct ApplyInfo {
+ const InfoMap* extension_info_map;
+ const WebRequestData& request_data;
+ bool crosses_incognito;
+ // Modified by each applied action:
+ std::list<LinkedPtrEventResponseDelta>* deltas;
+ std::set<std::string>* ignored_tags;
+ };
+
+ int stages() const {
+ return stages_;
+ }
+
+ Type type() const {
+ return type_;
+ }
+
+ // Compares the Type of two WebRequestActions, needs to be overridden for
+ // parameterized types.
+ virtual bool Equals(const WebRequestAction* other) const;
+
+ // Return the JavaScript type name corresponding to type(). If there are
+ // more names, they are returned separated by a colon.
+ virtual std::string GetName() const = 0;
+
+ int minimum_priority() const {
+ return minimum_priority_;
+ }
+
+ HostPermissionsStrategy host_permissions_strategy() const {
+ return host_permissions_strategy_;
+ }
+
+ // Returns whether the specified extension has permission to execute this
+ // action on |request|. Checks the host permission if the host permissions
+ // strategy is STRATEGY_DEFAULT.
+ // |extension_info_map| may only be NULL for during testing, in which case
+ // host permissions are ignored. |crosses_incognito| specifies
+ // whether the request comes from a different profile than |extension_id|
+ // but was processed because the extension is in spanning mode.
+ virtual bool HasPermission(const InfoMap* extension_info_map,
+ const std::string& extension_id,
+ const net::URLRequest* request,
+ bool crosses_incognito) const;
+
+ // Factory method that instantiates a concrete WebRequestAction
+ // implementation according to |json_action|, the representation of the
+ // WebRequestAction as received from the extension API.
+ // Sets |error| and returns NULL in case of a semantic error that cannot
+ // be caught by schema validation. Sets |bad_message| and returns NULL
+ // in case the input is syntactically unexpected.
+ static scoped_refptr<const WebRequestAction> Create(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ const base::Value& json_action,
+ std::string* error,
+ bool* bad_message);
+
+ // Returns a description of the modification to the request caused by
+ // this action.
+ virtual LinkedPtrEventResponseDelta CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const = 0;
+
+ // Applies this action to a request, recording the results into
+ // apply_info.deltas.
+ void Apply(const std::string& extension_id,
+ base::Time extension_install_time,
+ ApplyInfo* apply_info) const;
+
+ protected:
+ friend class base::RefCounted<WebRequestAction>;
+ virtual ~WebRequestAction();
+ WebRequestAction(int stages,
+ Type type,
+ int minimum_priority,
+ HostPermissionsStrategy strategy);
+
+ private:
+ // A bit vector representing a set of extensions::RequestStage during which
+ // the condition can be tested.
+ const int stages_;
+
+ const Type type_;
+
+ // The minimum priority of rules that may be evaluated after the rule
+ // containing this action.
+ const int minimum_priority_;
+
+ // Defaults to STRATEGY_DEFAULT.
+ const HostPermissionsStrategy host_permissions_strategy_;
+};
+
+typedef DeclarativeActionSet<WebRequestAction> WebRequestActionSet;
+
+//
+// The following are concrete actions.
+//
+
+// Action that instructs to cancel a network request.
+class WebRequestCancelAction : public WebRequestAction {
+ public:
+ WebRequestCancelAction();
+
+ // Implementation of WebRequestAction:
+ std::string GetName() const override;
+ LinkedPtrEventResponseDelta CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const override;
+
+ private:
+ ~WebRequestCancelAction() override;
+ DISALLOW_COPY_AND_ASSIGN(WebRequestCancelAction);
+};
+
+// Action that instructs to redirect a network request.
+class WebRequestRedirectAction : public WebRequestAction {
+ public:
+ explicit WebRequestRedirectAction(const GURL& redirect_url);
+
+ // Implementation of WebRequestAction:
+ bool Equals(const WebRequestAction* other) const override;
+ std::string GetName() const override;
+ LinkedPtrEventResponseDelta CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const override;
+
+ private:
+ ~WebRequestRedirectAction() override;
+
+ GURL redirect_url_; // Target to which the request shall be redirected.
+
+ DISALLOW_COPY_AND_ASSIGN(WebRequestRedirectAction);
+};
+
+// Action that instructs to redirect a network request to a transparent image.
+class WebRequestRedirectToTransparentImageAction : public WebRequestAction {
+ public:
+ WebRequestRedirectToTransparentImageAction();
+
+ // Implementation of WebRequestAction:
+ std::string GetName() const override;
+ LinkedPtrEventResponseDelta CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const override;
+
+ private:
+ ~WebRequestRedirectToTransparentImageAction() override;
+ DISALLOW_COPY_AND_ASSIGN(WebRequestRedirectToTransparentImageAction);
+};
+
+
+// Action that instructs to redirect a network request to an empty document.
+class WebRequestRedirectToEmptyDocumentAction : public WebRequestAction {
+ public:
+ WebRequestRedirectToEmptyDocumentAction();
+
+ // Implementation of WebRequestAction:
+ std::string GetName() const override;
+ LinkedPtrEventResponseDelta CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const override;
+
+ private:
+ ~WebRequestRedirectToEmptyDocumentAction() override;
+ DISALLOW_COPY_AND_ASSIGN(WebRequestRedirectToEmptyDocumentAction);
+};
+
+// Action that instructs to redirect a network request.
+class WebRequestRedirectByRegExAction : public WebRequestAction {
+ public:
+ // The |to_pattern| has to be passed in RE2 syntax with the exception that
+ // capture groups are referenced in Perl style ($1, $2, ...).
+ explicit WebRequestRedirectByRegExAction(scoped_ptr<re2::RE2> from_pattern,
+ const std::string& to_pattern);
+
+ // Conversion of capture group styles between Perl style ($1, $2, ...) and
+ // RE2 (\1, \2, ...).
+ static std::string PerlToRe2Style(const std::string& perl);
+
+ // Implementation of WebRequestAction:
+ bool Equals(const WebRequestAction* other) const override;
+ std::string GetName() const override;
+ LinkedPtrEventResponseDelta CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const override;
+
+ private:
+ ~WebRequestRedirectByRegExAction() override;
+
+ scoped_ptr<re2::RE2> from_pattern_;
+ std::string to_pattern_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRequestRedirectByRegExAction);
+};
+
+// Action that instructs to set a request header.
+class WebRequestSetRequestHeaderAction : public WebRequestAction {
+ public:
+ WebRequestSetRequestHeaderAction(const std::string& name,
+ const std::string& value);
+
+ // Implementation of WebRequestAction:
+ bool Equals(const WebRequestAction* other) const override;
+ std::string GetName() const override;
+ LinkedPtrEventResponseDelta CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const override;
+
+ private:
+ ~WebRequestSetRequestHeaderAction() override;
+
+ std::string name_;
+ std::string value_;
+ DISALLOW_COPY_AND_ASSIGN(WebRequestSetRequestHeaderAction);
+};
+
+// Action that instructs to remove a request header.
+class WebRequestRemoveRequestHeaderAction : public WebRequestAction {
+ public:
+ explicit WebRequestRemoveRequestHeaderAction(const std::string& name);
+
+ // Implementation of WebRequestAction:
+ bool Equals(const WebRequestAction* other) const override;
+ std::string GetName() const override;
+ LinkedPtrEventResponseDelta CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const override;
+
+ private:
+ ~WebRequestRemoveRequestHeaderAction() override;
+
+ std::string name_;
+ DISALLOW_COPY_AND_ASSIGN(WebRequestRemoveRequestHeaderAction);
+};
+
+// Action that instructs to add a response header.
+class WebRequestAddResponseHeaderAction : public WebRequestAction {
+ public:
+ WebRequestAddResponseHeaderAction(const std::string& name,
+ const std::string& value);
+
+ // Implementation of WebRequestAction:
+ bool Equals(const WebRequestAction* other) const override;
+ std::string GetName() const override;
+ LinkedPtrEventResponseDelta CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const override;
+
+ private:
+ ~WebRequestAddResponseHeaderAction() override;
+
+ std::string name_;
+ std::string value_;
+ DISALLOW_COPY_AND_ASSIGN(WebRequestAddResponseHeaderAction);
+};
+
+// Action that instructs to remove a response header.
+class WebRequestRemoveResponseHeaderAction : public WebRequestAction {
+ public:
+ explicit WebRequestRemoveResponseHeaderAction(const std::string& name,
+ const std::string& value,
+ bool has_value);
+
+ // Implementation of WebRequestAction:
+ bool Equals(const WebRequestAction* other) const override;
+ std::string GetName() const override;
+ LinkedPtrEventResponseDelta CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const override;
+
+ private:
+ ~WebRequestRemoveResponseHeaderAction() override;
+
+ std::string name_;
+ std::string value_;
+ bool has_value_;
+ DISALLOW_COPY_AND_ASSIGN(WebRequestRemoveResponseHeaderAction);
+};
+
+// Action that instructs to ignore rules below a certain priority.
+class WebRequestIgnoreRulesAction : public WebRequestAction {
+ public:
+ explicit WebRequestIgnoreRulesAction(int minimum_priority,
+ const std::string& ignore_tag);
+
+ // Implementation of WebRequestAction:
+ bool Equals(const WebRequestAction* other) const override;
+ std::string GetName() const override;
+ LinkedPtrEventResponseDelta CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const override;
+ const std::string& ignore_tag() const { return ignore_tag_; }
+
+ private:
+ ~WebRequestIgnoreRulesAction() override;
+
+ // Rules are ignored if they have a tag matching |ignore_tag_| and
+ // |ignore_tag_| is non-empty.
+ std::string ignore_tag_;
+ DISALLOW_COPY_AND_ASSIGN(WebRequestIgnoreRulesAction);
+};
+
+// Action that instructs to modify (add, edit, remove) a request cookie.
+class WebRequestRequestCookieAction : public WebRequestAction {
+ public:
+ typedef extension_web_request_api_helpers::RequestCookieModification
+ RequestCookieModification;
+
+ explicit WebRequestRequestCookieAction(
+ linked_ptr<RequestCookieModification> request_cookie_modification);
+
+ // Implementation of WebRequestAction:
+ bool Equals(const WebRequestAction* other) const override;
+ std::string GetName() const override;
+ LinkedPtrEventResponseDelta CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const override;
+
+ private:
+ ~WebRequestRequestCookieAction() override;
+
+ linked_ptr<RequestCookieModification> request_cookie_modification_;
+ DISALLOW_COPY_AND_ASSIGN(WebRequestRequestCookieAction);
+};
+
+// Action that instructs to modify (add, edit, remove) a response cookie.
+class WebRequestResponseCookieAction : public WebRequestAction {
+ public:
+ typedef extension_web_request_api_helpers::ResponseCookieModification
+ ResponseCookieModification;
+
+ explicit WebRequestResponseCookieAction(
+ linked_ptr<ResponseCookieModification> response_cookie_modification);
+
+ // Implementation of WebRequestAction:
+ bool Equals(const WebRequestAction* other) const override;
+ std::string GetName() const override;
+ LinkedPtrEventResponseDelta CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const override;
+
+ private:
+ ~WebRequestResponseCookieAction() override;
+
+ linked_ptr<ResponseCookieModification> response_cookie_modification_;
+ DISALLOW_COPY_AND_ASSIGN(WebRequestResponseCookieAction);
+};
+
+// Action that triggers the chrome.declarativeWebRequest.onMessage event in
+// the background/event/... pages of the extension.
+class WebRequestSendMessageToExtensionAction : public WebRequestAction {
+ public:
+ explicit WebRequestSendMessageToExtensionAction(const std::string& message);
+
+ // Implementation of WebRequestAction:
+ bool Equals(const WebRequestAction* other) const override;
+ std::string GetName() const override;
+ LinkedPtrEventResponseDelta CreateDelta(
+ const WebRequestData& request_data,
+ const std::string& extension_id,
+ const base::Time& extension_install_time) const override;
+
+ private:
+ ~WebRequestSendMessageToExtensionAction() override;
+
+ std::string message_;
+ DISALLOW_COPY_AND_ASSIGN(WebRequestSendMessageToExtensionAction);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DECLARATIVE_WEBREQUEST_WEBREQUEST_ACTION_H_
diff --git a/chromium/extensions/browser/api/declarative_webrequest/webrequest_condition.cc b/chromium/extensions/browser/api/declarative_webrequest/webrequest_condition.cc
new file mode 100644
index 00000000000..ef72ea4496c
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative_webrequest/webrequest_condition.cc
@@ -0,0 +1,204 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/declarative_webrequest/webrequest_condition.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "components/url_matcher/url_matcher_factory.h"
+#include "extensions/browser/api/declarative_webrequest/request_stage.h"
+#include "extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.h"
+#include "extensions/browser/api/declarative_webrequest/webrequest_constants.h"
+#include "net/url_request/url_request.h"
+
+using url_matcher::URLMatcherConditionFactory;
+using url_matcher::URLMatcherConditionSet;
+using url_matcher::URLMatcherFactory;
+
+namespace keys = extensions::declarative_webrequest_constants;
+
+namespace {
+static URLMatcherConditionSet::ID g_next_id = 0;
+
+// TODO(battre): improve error messaging to give more meaningful messages
+// to the extension developer.
+// Error messages:
+const char kExpectedDictionary[] = "A condition has to be a dictionary.";
+const char kConditionWithoutInstanceType[] = "A condition had no instanceType";
+const char kExpectedOtherConditionType[] = "Expected a condition of type "
+ "declarativeWebRequest.RequestMatcher";
+const char kInvalidTypeOfParamter[] = "Attribute '%s' has an invalid type";
+const char kConditionCannotBeFulfilled[] = "A condition can never be "
+ "fulfilled because its attributes cannot all be tested at the "
+ "same time in the request life-cycle.";
+} // namespace
+
+namespace extensions {
+
+namespace keys = declarative_webrequest_constants;
+
+//
+// WebRequestData
+//
+
+WebRequestData::WebRequestData(net::URLRequest* request, RequestStage stage)
+ : request(request),
+ stage(stage),
+ original_response_headers(NULL) {}
+
+WebRequestData::WebRequestData(
+ net::URLRequest* request,
+ RequestStage stage,
+ const net::HttpResponseHeaders* original_response_headers)
+ : request(request),
+ stage(stage),
+ original_response_headers(original_response_headers) {}
+
+WebRequestData::~WebRequestData() {}
+
+//
+// WebRequestDataWithMatchIds
+//
+
+WebRequestDataWithMatchIds::WebRequestDataWithMatchIds(
+ const WebRequestData* request_data)
+ : data(request_data) {}
+
+WebRequestDataWithMatchIds::~WebRequestDataWithMatchIds() {}
+
+//
+// WebRequestCondition
+//
+
+WebRequestCondition::WebRequestCondition(
+ scoped_refptr<URLMatcherConditionSet> url_matcher_conditions,
+ scoped_refptr<URLMatcherConditionSet> first_party_url_matcher_conditions,
+ const WebRequestConditionAttributes& condition_attributes)
+ : url_matcher_conditions_(url_matcher_conditions),
+ first_party_url_matcher_conditions_(first_party_url_matcher_conditions),
+ condition_attributes_(condition_attributes),
+ applicable_request_stages_(~0) {
+ for (WebRequestConditionAttributes::const_iterator i =
+ condition_attributes_.begin(); i != condition_attributes_.end(); ++i) {
+ applicable_request_stages_ &= (*i)->GetStages();
+ }
+}
+
+WebRequestCondition::~WebRequestCondition() {}
+
+bool WebRequestCondition::IsFulfilled(
+ const MatchData& request_data) const {
+ if (!(request_data.data->stage & applicable_request_stages_)) {
+ // A condition that cannot be evaluated is considered as violated.
+ return false;
+ }
+
+ // Check URL attributes if present.
+ if (url_matcher_conditions_.get() &&
+ !ContainsKey(request_data.url_match_ids, url_matcher_conditions_->id()))
+ return false;
+ if (first_party_url_matcher_conditions_.get() &&
+ !ContainsKey(request_data.first_party_url_match_ids,
+ first_party_url_matcher_conditions_->id()))
+ return false;
+
+ // All condition attributes must be fulfilled for a fulfilled condition.
+ for (WebRequestConditionAttributes::const_iterator i =
+ condition_attributes_.begin();
+ i != condition_attributes_.end(); ++i) {
+ if (!(*i)->IsFulfilled(*(request_data.data)))
+ return false;
+ }
+ return true;
+}
+
+void WebRequestCondition::GetURLMatcherConditionSets(
+ URLMatcherConditionSet::Vector* condition_sets) const {
+ if (url_matcher_conditions_.get())
+ condition_sets->push_back(url_matcher_conditions_);
+ if (first_party_url_matcher_conditions_.get())
+ condition_sets->push_back(first_party_url_matcher_conditions_);
+}
+
+// static
+scoped_ptr<WebRequestCondition> WebRequestCondition::Create(
+ const Extension* extension,
+ URLMatcherConditionFactory* url_matcher_condition_factory,
+ const base::Value& condition,
+ std::string* error) {
+ const base::DictionaryValue* condition_dict = NULL;
+ if (!condition.GetAsDictionary(&condition_dict)) {
+ *error = kExpectedDictionary;
+ return scoped_ptr<WebRequestCondition>();
+ }
+
+ // Verify that we are dealing with a Condition whose type we understand.
+ std::string instance_type;
+ if (!condition_dict->GetString(keys::kInstanceTypeKey, &instance_type)) {
+ *error = kConditionWithoutInstanceType;
+ return scoped_ptr<WebRequestCondition>();
+ }
+ if (instance_type != keys::kRequestMatcherType) {
+ *error = kExpectedOtherConditionType;
+ return scoped_ptr<WebRequestCondition>();
+ }
+
+ WebRequestConditionAttributes attributes;
+ scoped_refptr<URLMatcherConditionSet> url_matcher_condition_set;
+ scoped_refptr<URLMatcherConditionSet> first_party_url_matcher_condition_set;
+
+ for (base::DictionaryValue::Iterator iter(*condition_dict);
+ !iter.IsAtEnd(); iter.Advance()) {
+ const std::string& condition_attribute_name = iter.key();
+ const base::Value& condition_attribute_value = iter.value();
+ const bool name_is_url = condition_attribute_name == keys::kUrlKey;
+ if (condition_attribute_name == keys::kInstanceTypeKey) {
+ // Skip this.
+ } else if (name_is_url ||
+ condition_attribute_name == keys::kFirstPartyForCookiesUrlKey) {
+ const base::DictionaryValue* dict = NULL;
+ if (!condition_attribute_value.GetAsDictionary(&dict)) {
+ *error = base::StringPrintf(kInvalidTypeOfParamter,
+ condition_attribute_name.c_str());
+ } else {
+ if (name_is_url) {
+ url_matcher_condition_set =
+ URLMatcherFactory::CreateFromURLFilterDictionary(
+ url_matcher_condition_factory, dict, ++g_next_id, error);
+ } else {
+ first_party_url_matcher_condition_set =
+ URLMatcherFactory::CreateFromURLFilterDictionary(
+ url_matcher_condition_factory, dict, ++g_next_id, error);
+ }
+ }
+ } else {
+ scoped_refptr<const WebRequestConditionAttribute> attribute =
+ WebRequestConditionAttribute::Create(
+ condition_attribute_name,
+ &condition_attribute_value,
+ error);
+ if (attribute.get())
+ attributes.push_back(attribute);
+ }
+ if (!error->empty())
+ return scoped_ptr<WebRequestCondition>();
+ }
+
+ scoped_ptr<WebRequestCondition> result(
+ new WebRequestCondition(url_matcher_condition_set,
+ first_party_url_matcher_condition_set,
+ attributes));
+
+ if (!result->stages()) {
+ *error = kConditionCannotBeFulfilled;
+ return scoped_ptr<WebRequestCondition>();
+ }
+
+ return result;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/declarative_webrequest/webrequest_condition.h b/chromium/extensions/browser/api/declarative_webrequest/webrequest_condition.h
new file mode 100644
index 00000000000..0d43f46be78
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative_webrequest/webrequest_condition.h
@@ -0,0 +1,119 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DECLARATIVE_WEBREQUEST_WEBREQUEST_CONDITION_H_
+#define EXTENSIONS_BROWSER_API_DECLARATIVE_WEBREQUEST_WEBREQUEST_CONDITION_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "components/url_matcher/url_matcher.h"
+#include "extensions/browser/api/declarative/declarative_rule.h"
+#include "extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.h"
+#include "net/http/http_response_headers.h"
+
+namespace extensions {
+
+// Container for information about a URLRequest to determine which
+// rules apply to the request.
+struct WebRequestData {
+ WebRequestData(net::URLRequest* request, RequestStage stage);
+ WebRequestData(
+ net::URLRequest* request,
+ RequestStage stage,
+ const net::HttpResponseHeaders* original_response_headers);
+ ~WebRequestData();
+
+ // The network request that is currently being processed.
+ net::URLRequest* request;
+ // The stage (progress) of the network request.
+ RequestStage stage;
+ // Additional information about requests that is not
+ // available in all request stages.
+ const net::HttpResponseHeaders* original_response_headers;
+};
+
+// Adds information about URL matches to WebRequestData.
+struct WebRequestDataWithMatchIds {
+ explicit WebRequestDataWithMatchIds(const WebRequestData* request_data);
+ ~WebRequestDataWithMatchIds();
+
+ const WebRequestData* data;
+ std::set<url_matcher::URLMatcherConditionSet::ID> url_match_ids;
+ std::set<url_matcher::URLMatcherConditionSet::ID> first_party_url_match_ids;
+};
+
+// Representation of a condition in the Declarative WebRequest API. A condition
+// consists of several attributes. Each of these attributes needs to be
+// fulfilled in order for the condition to be fulfilled.
+//
+// We distinguish between two types of conditions:
+// - URL Matcher conditions are conditions that test the URL of a request.
+// These are treated separately because we use a URLMatcher to efficiently
+// test many of these conditions in parallel by using some advanced
+// data structures. The URLMatcher tells us if all URL Matcher conditions
+// are fulfilled for a WebRequestCondition.
+// - All other conditions are represented as WebRequestConditionAttributes.
+// These conditions are probed linearly (only if the URL Matcher found a hit).
+//
+// TODO(battre) Consider making the URLMatcher an owner of the
+// URLMatcherConditionSet and only pass a pointer to URLMatcherConditionSet
+// in url_matcher_condition_set(). This saves some copying in
+// WebRequestConditionSet::GetURLMatcherConditionSets.
+class WebRequestCondition {
+ public:
+ typedef WebRequestDataWithMatchIds MatchData;
+
+ WebRequestCondition(
+ scoped_refptr<url_matcher::URLMatcherConditionSet> url_matcher_conditions,
+ scoped_refptr<url_matcher::URLMatcherConditionSet>
+ first_party_url_matcher_conditions,
+ const WebRequestConditionAttributes& condition_attributes);
+ ~WebRequestCondition();
+
+ // Factory method that instantiates a WebRequestCondition according to
+ // the description |condition| passed by the extension API.
+ static scoped_ptr<WebRequestCondition> Create(
+ const Extension* extension,
+ url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory,
+ const base::Value& condition,
+ std::string* error);
+
+ // Returns whether the request matches this condition.
+ bool IsFulfilled(const MatchData& request_data) const;
+
+ // If this condition has url attributes, appends them to |condition_sets|.
+ void GetURLMatcherConditionSets(
+ url_matcher::URLMatcherConditionSet::Vector* condition_sets) const;
+
+ // Returns a bit vector representing extensions::RequestStage. The bit vector
+ // contains a 1 for each request stage during which the condition can be
+ // tested.
+ int stages() const { return applicable_request_stages_; }
+
+ private:
+ // URL attributes of this condition.
+ scoped_refptr<url_matcher::URLMatcherConditionSet> url_matcher_conditions_;
+ scoped_refptr<url_matcher::URLMatcherConditionSet>
+ first_party_url_matcher_conditions_;
+
+ // All non-UrlFilter attributes of this condition.
+ WebRequestConditionAttributes condition_attributes_;
+
+ // Bit vector indicating all RequestStage during which all
+ // |condition_attributes_| can be evaluated.
+ int applicable_request_stages_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRequestCondition);
+};
+
+typedef DeclarativeConditionSet<WebRequestCondition> WebRequestConditionSet;
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DECLARATIVE_WEBREQUEST_WEBREQUEST_CONDITION_H_
diff --git a/chromium/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.cc b/chromium/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.cc
new file mode 100644
index 00000000000..c9c51f17acd
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.cc
@@ -0,0 +1,883 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "content/public/browser/resource_request_info.h"
+#include "extensions/browser/api/declarative/deduping_factory.h"
+#include "extensions/browser/api/declarative_webrequest/request_stage.h"
+#include "extensions/browser/api/declarative_webrequest/webrequest_condition.h"
+#include "extensions/browser/api/declarative_webrequest/webrequest_constants.h"
+#include "extensions/browser/api/web_request/web_request_api_helpers.h"
+#include "extensions/common/error_utils.h"
+#include "net/base/net_errors.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "net/base/static_cookie_policy.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_util.h"
+#include "net/url_request/url_request.h"
+
+using base::CaseInsensitiveCompareASCII;
+using base::DictionaryValue;
+using base::ListValue;
+using base::StringValue;
+using base::Value;
+using content::ResourceType;
+
+namespace helpers = extension_web_request_api_helpers;
+namespace keys = extensions::declarative_webrequest_constants;
+
+namespace extensions {
+
+namespace {
+// Error messages.
+const char kInvalidValue[] = "Condition '*' has an invalid value";
+
+struct WebRequestConditionAttributeFactory {
+ DedupingFactory<WebRequestConditionAttribute> factory;
+
+ WebRequestConditionAttributeFactory() : factory(5) {
+ factory.RegisterFactoryMethod(
+ keys::kResourceTypeKey,
+ DedupingFactory<WebRequestConditionAttribute>::IS_PARAMETERIZED,
+ &WebRequestConditionAttributeResourceType::Create);
+
+ factory.RegisterFactoryMethod(
+ keys::kContentTypeKey,
+ DedupingFactory<WebRequestConditionAttribute>::IS_PARAMETERIZED,
+ &WebRequestConditionAttributeContentType::Create);
+ factory.RegisterFactoryMethod(
+ keys::kExcludeContentTypeKey,
+ DedupingFactory<WebRequestConditionAttribute>::IS_PARAMETERIZED,
+ &WebRequestConditionAttributeContentType::Create);
+
+ factory.RegisterFactoryMethod(
+ keys::kRequestHeadersKey,
+ DedupingFactory<WebRequestConditionAttribute>::IS_PARAMETERIZED,
+ &WebRequestConditionAttributeRequestHeaders::Create);
+ factory.RegisterFactoryMethod(
+ keys::kExcludeRequestHeadersKey,
+ DedupingFactory<WebRequestConditionAttribute>::IS_PARAMETERIZED,
+ &WebRequestConditionAttributeRequestHeaders::Create);
+
+ factory.RegisterFactoryMethod(
+ keys::kResponseHeadersKey,
+ DedupingFactory<WebRequestConditionAttribute>::IS_PARAMETERIZED,
+ &WebRequestConditionAttributeResponseHeaders::Create);
+ factory.RegisterFactoryMethod(
+ keys::kExcludeResponseHeadersKey,
+ DedupingFactory<WebRequestConditionAttribute>::IS_PARAMETERIZED,
+ &WebRequestConditionAttributeResponseHeaders::Create);
+
+ factory.RegisterFactoryMethod(
+ keys::kThirdPartyKey,
+ DedupingFactory<WebRequestConditionAttribute>::IS_PARAMETERIZED,
+ &WebRequestConditionAttributeThirdParty::Create);
+
+ factory.RegisterFactoryMethod(
+ keys::kStagesKey,
+ DedupingFactory<WebRequestConditionAttribute>::IS_PARAMETERIZED,
+ &WebRequestConditionAttributeStages::Create);
+ }
+};
+
+base::LazyInstance<WebRequestConditionAttributeFactory>::Leaky
+ g_web_request_condition_attribute_factory = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+//
+// WebRequestConditionAttribute
+//
+
+WebRequestConditionAttribute::WebRequestConditionAttribute() {}
+
+WebRequestConditionAttribute::~WebRequestConditionAttribute() {}
+
+bool WebRequestConditionAttribute::Equals(
+ const WebRequestConditionAttribute* other) const {
+ return GetType() == other->GetType();
+}
+
+// static
+scoped_refptr<const WebRequestConditionAttribute>
+WebRequestConditionAttribute::Create(
+ const std::string& name,
+ const base::Value* value,
+ std::string* error) {
+ CHECK(value != NULL && error != NULL);
+ bool bad_message = false;
+ return g_web_request_condition_attribute_factory.Get().factory.Instantiate(
+ name, value, error, &bad_message);
+}
+
+//
+// WebRequestConditionAttributeResourceType
+//
+
+WebRequestConditionAttributeResourceType::
+WebRequestConditionAttributeResourceType(
+ const std::vector<ResourceType>& types)
+ : types_(types) {}
+
+WebRequestConditionAttributeResourceType::
+~WebRequestConditionAttributeResourceType() {}
+
+// static
+scoped_refptr<const WebRequestConditionAttribute>
+WebRequestConditionAttributeResourceType::Create(
+ const std::string& instance_type,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message) {
+ DCHECK(instance_type == keys::kResourceTypeKey);
+ const base::ListValue* value_as_list = NULL;
+ if (!value->GetAsList(&value_as_list)) {
+ *error = ErrorUtils::FormatErrorMessage(kInvalidValue,
+ keys::kResourceTypeKey);
+ return scoped_refptr<const WebRequestConditionAttribute>(NULL);
+ }
+
+ size_t number_types = value_as_list->GetSize();
+
+ std::vector<ResourceType> passed_types;
+ passed_types.reserve(number_types);
+ for (size_t i = 0; i < number_types; ++i) {
+ std::string resource_type_string;
+ if (!value_as_list->GetString(i, &resource_type_string) ||
+ !helpers::ParseResourceType(resource_type_string, &passed_types)) {
+ *error = ErrorUtils::FormatErrorMessage(kInvalidValue,
+ keys::kResourceTypeKey);
+ return scoped_refptr<const WebRequestConditionAttribute>(NULL);
+ }
+ }
+
+ return scoped_refptr<const WebRequestConditionAttribute>(
+ new WebRequestConditionAttributeResourceType(passed_types));
+}
+
+int WebRequestConditionAttributeResourceType::GetStages() const {
+ return ON_BEFORE_REQUEST | ON_BEFORE_SEND_HEADERS | ON_SEND_HEADERS |
+ ON_HEADERS_RECEIVED | ON_AUTH_REQUIRED | ON_BEFORE_REDIRECT |
+ ON_RESPONSE_STARTED | ON_COMPLETED | ON_ERROR;
+}
+
+bool WebRequestConditionAttributeResourceType::IsFulfilled(
+ const WebRequestData& request_data) const {
+ if (!(request_data.stage & GetStages()))
+ return false;
+ const content::ResourceRequestInfo* info =
+ content::ResourceRequestInfo::ForRequest(request_data.request);
+ if (!info)
+ return false;
+ return std::find(types_.begin(), types_.end(), info->GetResourceType()) !=
+ types_.end();
+}
+
+WebRequestConditionAttribute::Type
+WebRequestConditionAttributeResourceType::GetType() const {
+ return CONDITION_RESOURCE_TYPE;
+}
+
+std::string WebRequestConditionAttributeResourceType::GetName() const {
+ return keys::kResourceTypeKey;
+}
+
+bool WebRequestConditionAttributeResourceType::Equals(
+ const WebRequestConditionAttribute* other) const {
+ if (!WebRequestConditionAttribute::Equals(other))
+ return false;
+ const WebRequestConditionAttributeResourceType* casted_other =
+ static_cast<const WebRequestConditionAttributeResourceType*>(other);
+ return types_ == casted_other->types_;
+}
+
+//
+// WebRequestConditionAttributeContentType
+//
+
+WebRequestConditionAttributeContentType::
+WebRequestConditionAttributeContentType(
+ const std::vector<std::string>& content_types,
+ bool inclusive)
+ : content_types_(content_types),
+ inclusive_(inclusive) {}
+
+WebRequestConditionAttributeContentType::
+~WebRequestConditionAttributeContentType() {}
+
+// static
+scoped_refptr<const WebRequestConditionAttribute>
+WebRequestConditionAttributeContentType::Create(
+ const std::string& name,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message) {
+ DCHECK(name == keys::kContentTypeKey || name == keys::kExcludeContentTypeKey);
+
+ const base::ListValue* value_as_list = NULL;
+ if (!value->GetAsList(&value_as_list)) {
+ *error = ErrorUtils::FormatErrorMessage(kInvalidValue, name);
+ return scoped_refptr<const WebRequestConditionAttribute>(NULL);
+ }
+ std::vector<std::string> content_types;
+ for (base::ListValue::const_iterator it = value_as_list->begin();
+ it != value_as_list->end(); ++it) {
+ std::string content_type;
+ if (!(*it)->GetAsString(&content_type)) {
+ *error = ErrorUtils::FormatErrorMessage(kInvalidValue, name);
+ return scoped_refptr<const WebRequestConditionAttribute>(NULL);
+ }
+ content_types.push_back(content_type);
+ }
+
+ return scoped_refptr<const WebRequestConditionAttribute>(
+ new WebRequestConditionAttributeContentType(
+ content_types, name == keys::kContentTypeKey));
+}
+
+int WebRequestConditionAttributeContentType::GetStages() const {
+ return ON_HEADERS_RECEIVED;
+}
+
+bool WebRequestConditionAttributeContentType::IsFulfilled(
+ const WebRequestData& request_data) const {
+ if (!(request_data.stage & GetStages()))
+ return false;
+ std::string content_type;
+ request_data.original_response_headers->GetNormalizedHeader(
+ net::HttpRequestHeaders::kContentType, &content_type);
+ std::string mime_type;
+ std::string charset;
+ bool had_charset = false;
+ net::HttpUtil::ParseContentType(
+ content_type, &mime_type, &charset, &had_charset, NULL);
+
+ if (inclusive_) {
+ return std::find(content_types_.begin(), content_types_.end(),
+ mime_type) != content_types_.end();
+ } else {
+ return std::find(content_types_.begin(), content_types_.end(),
+ mime_type) == content_types_.end();
+ }
+}
+
+WebRequestConditionAttribute::Type
+WebRequestConditionAttributeContentType::GetType() const {
+ return CONDITION_CONTENT_TYPE;
+}
+
+std::string WebRequestConditionAttributeContentType::GetName() const {
+ return (inclusive_ ? keys::kContentTypeKey : keys::kExcludeContentTypeKey);
+}
+
+bool WebRequestConditionAttributeContentType::Equals(
+ const WebRequestConditionAttribute* other) const {
+ if (!WebRequestConditionAttribute::Equals(other))
+ return false;
+ const WebRequestConditionAttributeContentType* casted_other =
+ static_cast<const WebRequestConditionAttributeContentType*>(other);
+ return content_types_ == casted_other->content_types_ &&
+ inclusive_ == casted_other->inclusive_;
+}
+
+// Manages a set of tests to be applied to name-value pairs representing
+// headers. This is a helper class to header-related condition attributes.
+// It contains a set of test groups. A name-value pair satisfies the whole
+// set of test groups iff it passes at least one test group.
+class HeaderMatcher {
+ public:
+ ~HeaderMatcher();
+
+ // Creates an instance based on a list |tests| of test groups, encoded as
+ // dictionaries of the type declarativeWebRequest.HeaderFilter (see
+ // declarative_web_request.json).
+ static scoped_ptr<const HeaderMatcher> Create(const base::ListValue* tests);
+
+ // Does |this| match the header "|name|: |value|"?
+ bool TestNameValue(const std::string& name, const std::string& value) const;
+
+ private:
+ // Represents a single string-matching test.
+ class StringMatchTest {
+ public:
+ enum MatchType { kPrefix, kSuffix, kEquals, kContains };
+
+ // |data| is the pattern to be matched in the position given by |type|.
+ // Note that |data| must point to a StringValue object.
+ static scoped_ptr<StringMatchTest> Create(const base::Value* data,
+ MatchType type,
+ bool case_sensitive);
+ ~StringMatchTest();
+
+ // Does |str| pass |this| StringMatchTest?
+ bool Matches(const std::string& str) const;
+
+ private:
+ StringMatchTest(const std::string& data,
+ MatchType type,
+ bool case_sensitive);
+
+ const std::string data_;
+ const MatchType type_;
+ const base::CompareCase case_sensitive_;
+ DISALLOW_COPY_AND_ASSIGN(StringMatchTest);
+ };
+
+ // Represents a test group -- a set of string matching tests to be applied to
+ // both the header name and value.
+ class HeaderMatchTest {
+ public:
+ ~HeaderMatchTest();
+
+ // Gets the test group description in |tests| and creates the corresponding
+ // HeaderMatchTest. On failure returns NULL.
+ static scoped_ptr<const HeaderMatchTest> Create(
+ const base::DictionaryValue* tests);
+
+ // Does the header "|name|: |value|" match all tests in |this|?
+ bool Matches(const std::string& name, const std::string& value) const;
+
+ private:
+ // Takes ownership of the content of both |name_match| and |value_match|.
+ HeaderMatchTest(std::vector<scoped_ptr<const StringMatchTest>> name_match,
+ std::vector<scoped_ptr<const StringMatchTest>> value_match);
+
+ // Tests to be passed by a header's name.
+ const std::vector<scoped_ptr<const StringMatchTest>> name_match_;
+ // Tests to be passed by a header's value.
+ const std::vector<scoped_ptr<const StringMatchTest>> value_match_;
+
+ DISALLOW_COPY_AND_ASSIGN(HeaderMatchTest);
+ };
+
+ explicit HeaderMatcher(std::vector<scoped_ptr<const HeaderMatchTest>> tests);
+
+ const std::vector<scoped_ptr<const HeaderMatchTest>> tests_;
+
+ DISALLOW_COPY_AND_ASSIGN(HeaderMatcher);
+};
+
+// HeaderMatcher implementation.
+
+HeaderMatcher::~HeaderMatcher() {}
+
+// static
+scoped_ptr<const HeaderMatcher> HeaderMatcher::Create(
+ const base::ListValue* tests) {
+ std::vector<scoped_ptr<const HeaderMatchTest>> header_tests;
+ for (base::ListValue::const_iterator it = tests->begin();
+ it != tests->end(); ++it) {
+ const base::DictionaryValue* tests = NULL;
+ if (!(*it)->GetAsDictionary(&tests))
+ return scoped_ptr<const HeaderMatcher>();
+
+ scoped_ptr<const HeaderMatchTest> header_test(
+ HeaderMatchTest::Create(tests));
+ if (header_test.get() == NULL)
+ return scoped_ptr<const HeaderMatcher>();
+ header_tests.push_back(std::move(header_test));
+ }
+
+ return scoped_ptr<const HeaderMatcher>(
+ new HeaderMatcher(std::move(header_tests)));
+}
+
+bool HeaderMatcher::TestNameValue(const std::string& name,
+ const std::string& value) const {
+ for (size_t i = 0; i < tests_.size(); ++i) {
+ if (tests_[i]->Matches(name, value))
+ return true;
+ }
+ return false;
+}
+
+HeaderMatcher::HeaderMatcher(
+ std::vector<scoped_ptr<const HeaderMatchTest>> tests)
+ : tests_(std::move(tests)) {}
+
+// HeaderMatcher::StringMatchTest implementation.
+
+// static
+scoped_ptr<HeaderMatcher::StringMatchTest>
+HeaderMatcher::StringMatchTest::Create(const base::Value* data,
+ MatchType type,
+ bool case_sensitive) {
+ std::string str;
+ CHECK(data->GetAsString(&str));
+ return scoped_ptr<StringMatchTest>(
+ new StringMatchTest(str, type, case_sensitive));
+}
+
+HeaderMatcher::StringMatchTest::~StringMatchTest() {}
+
+bool HeaderMatcher::StringMatchTest::Matches(
+ const std::string& str) const {
+ switch (type_) {
+ case kPrefix:
+ return base::StartsWith(str, data_, case_sensitive_);
+ case kSuffix:
+ return base::EndsWith(str, data_, case_sensitive_);
+ case kEquals:
+ return str.size() == data_.size() &&
+ base::StartsWith(str, data_, case_sensitive_);
+ case kContains:
+ if (case_sensitive_ == base::CompareCase::INSENSITIVE_ASCII) {
+ return std::search(str.begin(), str.end(), data_.begin(), data_.end(),
+ CaseInsensitiveCompareASCII<char>()) != str.end();
+ } else {
+ return str.find(data_) != std::string::npos;
+ }
+ }
+ // We never get past the "switch", but the compiler worries about no return.
+ NOTREACHED();
+ return false;
+}
+
+HeaderMatcher::StringMatchTest::StringMatchTest(const std::string& data,
+ MatchType type,
+ bool case_sensitive)
+ : data_(data),
+ type_(type),
+ case_sensitive_(case_sensitive ? base::CompareCase::SENSITIVE
+ : base::CompareCase::INSENSITIVE_ASCII) {}
+
+// HeaderMatcher::HeaderMatchTest implementation.
+
+HeaderMatcher::HeaderMatchTest::HeaderMatchTest(
+ std::vector<scoped_ptr<const StringMatchTest>> name_match,
+ std::vector<scoped_ptr<const StringMatchTest>> value_match)
+ : name_match_(std::move(name_match)),
+ value_match_(std::move(value_match)) {}
+
+HeaderMatcher::HeaderMatchTest::~HeaderMatchTest() {}
+
+// static
+scoped_ptr<const HeaderMatcher::HeaderMatchTest>
+HeaderMatcher::HeaderMatchTest::Create(const base::DictionaryValue* tests) {
+ std::vector<scoped_ptr<const StringMatchTest>> name_match;
+ std::vector<scoped_ptr<const StringMatchTest>> value_match;
+
+ for (base::DictionaryValue::Iterator it(*tests);
+ !it.IsAtEnd(); it.Advance()) {
+ bool is_name = false; // Is this test for header name?
+ StringMatchTest::MatchType match_type;
+ if (it.key() == keys::kNamePrefixKey) {
+ is_name = true;
+ match_type = StringMatchTest::kPrefix;
+ } else if (it.key() == keys::kNameSuffixKey) {
+ is_name = true;
+ match_type = StringMatchTest::kSuffix;
+ } else if (it.key() == keys::kNameContainsKey) {
+ is_name = true;
+ match_type = StringMatchTest::kContains;
+ } else if (it.key() == keys::kNameEqualsKey) {
+ is_name = true;
+ match_type = StringMatchTest::kEquals;
+ } else if (it.key() == keys::kValuePrefixKey) {
+ match_type = StringMatchTest::kPrefix;
+ } else if (it.key() == keys::kValueSuffixKey) {
+ match_type = StringMatchTest::kSuffix;
+ } else if (it.key() == keys::kValueContainsKey) {
+ match_type = StringMatchTest::kContains;
+ } else if (it.key() == keys::kValueEqualsKey) {
+ match_type = StringMatchTest::kEquals;
+ } else {
+ NOTREACHED(); // JSON schema type checking should prevent this.
+ return scoped_ptr<const HeaderMatchTest>();
+ }
+ const base::Value* content = &it.value();
+
+ std::vector<scoped_ptr<const StringMatchTest>>* tests =
+ is_name ? &name_match : &value_match;
+ switch (content->GetType()) {
+ case base::Value::TYPE_LIST: {
+ const base::ListValue* list = NULL;
+ CHECK(content->GetAsList(&list));
+ for (base::ListValue::const_iterator it = list->begin();
+ it != list->end(); ++it) {
+ tests->push_back(make_scoped_ptr(
+ StringMatchTest::Create(*it, match_type, !is_name).release()));
+ }
+ break;
+ }
+ case base::Value::TYPE_STRING: {
+ tests->push_back(make_scoped_ptr(
+ StringMatchTest::Create(content, match_type, !is_name).release()));
+ break;
+ }
+ default: {
+ NOTREACHED(); // JSON schema type checking should prevent this.
+ return scoped_ptr<const HeaderMatchTest>();
+ }
+ }
+ }
+
+ return scoped_ptr<const HeaderMatchTest>(
+ new HeaderMatchTest(std::move(name_match), std::move(value_match)));
+}
+
+bool HeaderMatcher::HeaderMatchTest::Matches(const std::string& name,
+ const std::string& value) const {
+ for (size_t i = 0; i < name_match_.size(); ++i) {
+ if (!name_match_[i]->Matches(name))
+ return false;
+ }
+
+ for (size_t i = 0; i < value_match_.size(); ++i) {
+ if (!value_match_[i]->Matches(value))
+ return false;
+ }
+
+ return true;
+}
+
+//
+// WebRequestConditionAttributeRequestHeaders
+//
+
+WebRequestConditionAttributeRequestHeaders::
+ WebRequestConditionAttributeRequestHeaders(
+ scoped_ptr<const HeaderMatcher> header_matcher,
+ bool positive)
+ : header_matcher_(std::move(header_matcher)), positive_(positive) {}
+
+WebRequestConditionAttributeRequestHeaders::
+~WebRequestConditionAttributeRequestHeaders() {}
+
+namespace {
+
+scoped_ptr<const HeaderMatcher> PrepareHeaderMatcher(
+ const std::string& name,
+ const base::Value* value,
+ std::string* error) {
+ const base::ListValue* value_as_list = NULL;
+ if (!value->GetAsList(&value_as_list)) {
+ *error = ErrorUtils::FormatErrorMessage(kInvalidValue, name);
+ return scoped_ptr<const HeaderMatcher>();
+ }
+
+ scoped_ptr<const HeaderMatcher> header_matcher(
+ HeaderMatcher::Create(value_as_list));
+ if (header_matcher.get() == NULL)
+ *error = ErrorUtils::FormatErrorMessage(kInvalidValue, name);
+ return header_matcher;
+}
+
+} // namespace
+
+// static
+scoped_refptr<const WebRequestConditionAttribute>
+WebRequestConditionAttributeRequestHeaders::Create(
+ const std::string& name,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message) {
+ DCHECK(name == keys::kRequestHeadersKey ||
+ name == keys::kExcludeRequestHeadersKey);
+
+ scoped_ptr<const HeaderMatcher> header_matcher(
+ PrepareHeaderMatcher(name, value, error));
+ if (header_matcher.get() == NULL)
+ return scoped_refptr<const WebRequestConditionAttribute>(NULL);
+
+ return scoped_refptr<const WebRequestConditionAttribute>(
+ new WebRequestConditionAttributeRequestHeaders(
+ std::move(header_matcher), name == keys::kRequestHeadersKey));
+}
+
+int WebRequestConditionAttributeRequestHeaders::GetStages() const {
+ // Currently we only allow matching against headers in the before-send-headers
+ // stage. The headers are accessible in other stages as well, but before
+ // allowing to match against them in further stages, we should consider
+ // caching the match result.
+ return ON_BEFORE_SEND_HEADERS;
+}
+
+bool WebRequestConditionAttributeRequestHeaders::IsFulfilled(
+ const WebRequestData& request_data) const {
+ if (!(request_data.stage & GetStages()))
+ return false;
+
+ const net::HttpRequestHeaders& headers =
+ request_data.request->extra_request_headers();
+
+ bool passed = false; // Did some header pass TestNameValue?
+ net::HttpRequestHeaders::Iterator it(headers);
+ while (!passed && it.GetNext())
+ passed |= header_matcher_->TestNameValue(it.name(), it.value());
+
+ return (positive_ ? passed : !passed);
+}
+
+WebRequestConditionAttribute::Type
+WebRequestConditionAttributeRequestHeaders::GetType() const {
+ return CONDITION_REQUEST_HEADERS;
+}
+
+std::string WebRequestConditionAttributeRequestHeaders::GetName() const {
+ return (positive_ ? keys::kRequestHeadersKey
+ : keys::kExcludeRequestHeadersKey);
+}
+
+bool WebRequestConditionAttributeRequestHeaders::Equals(
+ const WebRequestConditionAttribute* other) const {
+ // Comparing headers is too heavy, so we skip it entirely.
+ return false;
+}
+
+//
+// WebRequestConditionAttributeResponseHeaders
+//
+
+WebRequestConditionAttributeResponseHeaders::
+ WebRequestConditionAttributeResponseHeaders(
+ scoped_ptr<const HeaderMatcher> header_matcher,
+ bool positive)
+ : header_matcher_(std::move(header_matcher)), positive_(positive) {}
+
+WebRequestConditionAttributeResponseHeaders::
+~WebRequestConditionAttributeResponseHeaders() {}
+
+// static
+scoped_refptr<const WebRequestConditionAttribute>
+WebRequestConditionAttributeResponseHeaders::Create(
+ const std::string& name,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message) {
+ DCHECK(name == keys::kResponseHeadersKey ||
+ name == keys::kExcludeResponseHeadersKey);
+
+ scoped_ptr<const HeaderMatcher> header_matcher(
+ PrepareHeaderMatcher(name, value, error));
+ if (header_matcher.get() == NULL)
+ return scoped_refptr<const WebRequestConditionAttribute>(NULL);
+
+ return scoped_refptr<const WebRequestConditionAttribute>(
+ new WebRequestConditionAttributeResponseHeaders(
+ std::move(header_matcher), name == keys::kResponseHeadersKey));
+}
+
+int WebRequestConditionAttributeResponseHeaders::GetStages() const {
+ return ON_HEADERS_RECEIVED;
+}
+
+bool WebRequestConditionAttributeResponseHeaders::IsFulfilled(
+ const WebRequestData& request_data) const {
+ if (!(request_data.stage & GetStages()))
+ return false;
+
+ const net::HttpResponseHeaders* headers =
+ request_data.original_response_headers;
+ if (headers == NULL) {
+ // Each header of an empty set satisfies (the negation of) everything;
+ // OTOH, there is no header to satisfy even the most permissive test.
+ return !positive_;
+ }
+
+ bool passed = false; // Did some header pass TestNameValue?
+ std::string name;
+ std::string value;
+ size_t iter = 0;
+ while (!passed && headers->EnumerateHeaderLines(&iter, &name, &value)) {
+ passed |= header_matcher_->TestNameValue(name, value);
+ }
+
+ return (positive_ ? passed : !passed);
+}
+
+WebRequestConditionAttribute::Type
+WebRequestConditionAttributeResponseHeaders::GetType() const {
+ return CONDITION_RESPONSE_HEADERS;
+}
+
+std::string WebRequestConditionAttributeResponseHeaders::GetName() const {
+ return (positive_ ? keys::kResponseHeadersKey
+ : keys::kExcludeResponseHeadersKey);
+}
+
+bool WebRequestConditionAttributeResponseHeaders::Equals(
+ const WebRequestConditionAttribute* other) const {
+ return false;
+}
+
+//
+// WebRequestConditionAttributeThirdParty
+//
+
+WebRequestConditionAttributeThirdParty::
+WebRequestConditionAttributeThirdParty(bool match_third_party)
+ : match_third_party_(match_third_party) {}
+
+WebRequestConditionAttributeThirdParty::
+~WebRequestConditionAttributeThirdParty() {}
+
+// static
+scoped_refptr<const WebRequestConditionAttribute>
+WebRequestConditionAttributeThirdParty::Create(
+ const std::string& name,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message) {
+ DCHECK(name == keys::kThirdPartyKey);
+
+ bool third_party = false; // Dummy value, gets overwritten.
+ if (!value->GetAsBoolean(&third_party)) {
+ *error = ErrorUtils::FormatErrorMessage(kInvalidValue,
+ keys::kThirdPartyKey);
+ return scoped_refptr<const WebRequestConditionAttribute>(NULL);
+ }
+
+ return scoped_refptr<const WebRequestConditionAttribute>(
+ new WebRequestConditionAttributeThirdParty(third_party));
+}
+
+int WebRequestConditionAttributeThirdParty::GetStages() const {
+ return ON_BEFORE_REQUEST | ON_BEFORE_SEND_HEADERS | ON_SEND_HEADERS |
+ ON_HEADERS_RECEIVED | ON_AUTH_REQUIRED | ON_BEFORE_REDIRECT |
+ ON_RESPONSE_STARTED | ON_COMPLETED | ON_ERROR;
+}
+
+bool WebRequestConditionAttributeThirdParty::IsFulfilled(
+ const WebRequestData& request_data) const {
+ if (!(request_data.stage & GetStages()))
+ return false;
+
+ // Request is "1st party" if it gets cookies under 3rd party-blocking policy.
+ const net::StaticCookiePolicy block_third_party_policy(
+ net::StaticCookiePolicy::BLOCK_ALL_THIRD_PARTY_COOKIES);
+ const int can_get_cookies = block_third_party_policy.CanGetCookies(
+ request_data.request->url(),
+ request_data.request->first_party_for_cookies());
+ const bool is_first_party = (can_get_cookies == net::OK);
+
+ return match_third_party_ ? !is_first_party : is_first_party;
+}
+
+WebRequestConditionAttribute::Type
+WebRequestConditionAttributeThirdParty::GetType() const {
+ return CONDITION_THIRD_PARTY;
+}
+
+std::string WebRequestConditionAttributeThirdParty::GetName() const {
+ return keys::kThirdPartyKey;
+}
+
+bool WebRequestConditionAttributeThirdParty::Equals(
+ const WebRequestConditionAttribute* other) const {
+ if (!WebRequestConditionAttribute::Equals(other))
+ return false;
+ const WebRequestConditionAttributeThirdParty* casted_other =
+ static_cast<const WebRequestConditionAttributeThirdParty*>(other);
+ return match_third_party_ == casted_other->match_third_party_;
+}
+
+//
+// WebRequestConditionAttributeStages
+//
+
+WebRequestConditionAttributeStages::
+WebRequestConditionAttributeStages(int allowed_stages)
+ : allowed_stages_(allowed_stages) {}
+
+WebRequestConditionAttributeStages::
+~WebRequestConditionAttributeStages() {}
+
+namespace {
+
+// Reads strings stored in |value|, which is expected to be a ListValue, and
+// sets corresponding bits (see RequestStage) in |out_stages|. Returns true on
+// success, false otherwise.
+bool ParseListOfStages(const base::Value& value, int* out_stages) {
+ const base::ListValue* list = NULL;
+ if (!value.GetAsList(&list))
+ return false;
+
+ int stages = 0;
+ std::string stage_name;
+ for (base::ListValue::const_iterator it = list->begin();
+ it != list->end(); ++it) {
+ if (!((*it)->GetAsString(&stage_name)))
+ return false;
+ if (stage_name == keys::kOnBeforeRequestEnum) {
+ stages |= ON_BEFORE_REQUEST;
+ } else if (stage_name == keys::kOnBeforeSendHeadersEnum) {
+ stages |= ON_BEFORE_SEND_HEADERS;
+ } else if (stage_name == keys::kOnHeadersReceivedEnum) {
+ stages |= ON_HEADERS_RECEIVED;
+ } else if (stage_name == keys::kOnAuthRequiredEnum) {
+ stages |= ON_AUTH_REQUIRED;
+ } else {
+ NOTREACHED(); // JSON schema checks prevent getting here.
+ return false;
+ }
+ }
+
+ *out_stages = stages;
+ return true;
+}
+
+} // namespace
+
+// static
+scoped_refptr<const WebRequestConditionAttribute>
+WebRequestConditionAttributeStages::Create(const std::string& name,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message) {
+ DCHECK(name == keys::kStagesKey);
+
+ int allowed_stages = 0;
+ if (!ParseListOfStages(*value, &allowed_stages)) {
+ *error = ErrorUtils::FormatErrorMessage(kInvalidValue,
+ keys::kStagesKey);
+ return scoped_refptr<const WebRequestConditionAttribute>(NULL);
+ }
+
+ return scoped_refptr<const WebRequestConditionAttribute>(
+ new WebRequestConditionAttributeStages(allowed_stages));
+}
+
+int WebRequestConditionAttributeStages::GetStages() const {
+ return allowed_stages_;
+}
+
+bool WebRequestConditionAttributeStages::IsFulfilled(
+ const WebRequestData& request_data) const {
+ // Note: removing '!=' triggers warning C4800 on the VS compiler.
+ return (request_data.stage & GetStages()) != 0;
+}
+
+WebRequestConditionAttribute::Type
+WebRequestConditionAttributeStages::GetType() const {
+ return CONDITION_STAGES;
+}
+
+std::string WebRequestConditionAttributeStages::GetName() const {
+ return keys::kStagesKey;
+}
+
+bool WebRequestConditionAttributeStages::Equals(
+ const WebRequestConditionAttribute* other) const {
+ if (!WebRequestConditionAttribute::Equals(other))
+ return false;
+ const WebRequestConditionAttributeStages* casted_other =
+ static_cast<const WebRequestConditionAttributeStages*>(other);
+ return allowed_stages_ == casted_other->allowed_stages_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.h b/chromium/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.h
new file mode 100644
index 00000000000..b39a68db61d
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.h
@@ -0,0 +1,269 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DECLARATIVE_WEBREQUEST_WEBREQUEST_CONDITION_ATTRIBUTE_H_
+#define EXTENSIONS_BROWSER_API_DECLARATIVE_WEBREQUEST_WEBREQUEST_CONDITION_ATTRIBUTE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/public/common/resource_type.h"
+#include "extensions/browser/api/declarative_webrequest/request_stage.h"
+#include "extensions/common/api/events.h"
+
+namespace base {
+class Value;
+}
+
+namespace net {
+class URLRequest;
+}
+
+namespace extensions {
+
+class HeaderMatcher;
+struct WebRequestData;
+
+// Base class for all condition attributes of the declarative Web Request API
+// except for condition attribute to test URLPatterns.
+class WebRequestConditionAttribute
+ : public base::RefCounted<WebRequestConditionAttribute> {
+ public:
+ enum Type {
+ CONDITION_RESOURCE_TYPE,
+ CONDITION_CONTENT_TYPE,
+ CONDITION_RESPONSE_HEADERS,
+ CONDITION_THIRD_PARTY,
+ CONDITION_REQUEST_HEADERS,
+ CONDITION_STAGES
+ };
+
+ WebRequestConditionAttribute();
+
+ // Factory method that creates a WebRequestConditionAttribute for the JSON
+ // dictionary {|name|: |value|} passed by the extension API. Sets |error| and
+ // returns NULL if something fails.
+ // The ownership of |value| remains at the caller.
+ static scoped_refptr<const WebRequestConditionAttribute> Create(
+ const std::string& name,
+ const base::Value* value,
+ std::string* error);
+
+ // Returns a bit vector representing extensions::RequestStage. The bit vector
+ // contains a 1 for each request stage during which the condition attribute
+ // can be tested.
+ virtual int GetStages() const = 0;
+
+ // Returns whether the condition is fulfilled for this request.
+ virtual bool IsFulfilled(
+ const WebRequestData& request_data) const = 0;
+
+ virtual Type GetType() const = 0;
+ virtual std::string GetName() const = 0;
+
+ // Compares the Type of two WebRequestConditionAttributes, needs to be
+ // overridden for parameterized types.
+ virtual bool Equals(const WebRequestConditionAttribute* other) const;
+
+ protected:
+ friend class base::RefCounted<WebRequestConditionAttribute>;
+ virtual ~WebRequestConditionAttribute();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebRequestConditionAttribute);
+};
+
+typedef std::vector<scoped_refptr<const WebRequestConditionAttribute> >
+ WebRequestConditionAttributes;
+
+//
+// The following are concrete condition attributes.
+//
+
+// Condition that checks whether a request is for a specific resource type.
+class WebRequestConditionAttributeResourceType
+ : public WebRequestConditionAttribute {
+ public:
+ // Factory method, see WebRequestConditionAttribute::Create.
+ static scoped_refptr<const WebRequestConditionAttribute> Create(
+ const std::string& instance_type,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message);
+
+ // Implementation of WebRequestConditionAttribute:
+ int GetStages() const override;
+ bool IsFulfilled(const WebRequestData& request_data) const override;
+ Type GetType() const override;
+ std::string GetName() const override;
+ bool Equals(const WebRequestConditionAttribute* other) const override;
+
+ private:
+ explicit WebRequestConditionAttributeResourceType(
+ const std::vector<content::ResourceType>& types);
+ ~WebRequestConditionAttributeResourceType() override;
+
+ const std::vector<content::ResourceType> types_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRequestConditionAttributeResourceType);
+};
+
+// Condition that checks whether a response's Content-Type header has a
+// certain MIME media type.
+class WebRequestConditionAttributeContentType
+ : public WebRequestConditionAttribute {
+ public:
+ // Factory method, see WebRequestConditionAttribute::Create.
+ static scoped_refptr<const WebRequestConditionAttribute> Create(
+ const std::string& name,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message);
+
+ // Implementation of WebRequestConditionAttribute:
+ int GetStages() const override;
+ bool IsFulfilled(const WebRequestData& request_data) const override;
+ Type GetType() const override;
+ std::string GetName() const override;
+ bool Equals(const WebRequestConditionAttribute* other) const override;
+
+ private:
+ explicit WebRequestConditionAttributeContentType(
+ const std::vector<std::string>& include_content_types,
+ bool inclusive);
+ ~WebRequestConditionAttributeContentType() override;
+
+ const std::vector<std::string> content_types_;
+ const bool inclusive_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRequestConditionAttributeContentType);
+};
+
+// Condition attribute for matching against request headers. Uses HeaderMatcher
+// to handle the actual tests, in connection with a boolean positiveness
+// flag. If that flag is set to true, then IsFulfilled() returns true iff
+// |header_matcher_| matches at least one header. Otherwise IsFulfilled()
+// returns true iff the |header_matcher_| matches no header.
+class WebRequestConditionAttributeRequestHeaders
+ : public WebRequestConditionAttribute {
+ public:
+ // Factory method, see WebRequestConditionAttribute::Create.
+ static scoped_refptr<const WebRequestConditionAttribute> Create(
+ const std::string& name,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message);
+
+ // Implementation of WebRequestConditionAttribute:
+ int GetStages() const override;
+ bool IsFulfilled(const WebRequestData& request_data) const override;
+ Type GetType() const override;
+ std::string GetName() const override;
+ bool Equals(const WebRequestConditionAttribute* other) const override;
+
+ private:
+ WebRequestConditionAttributeRequestHeaders(
+ scoped_ptr<const HeaderMatcher> header_matcher, bool positive);
+ ~WebRequestConditionAttributeRequestHeaders() override;
+
+ const scoped_ptr<const HeaderMatcher> header_matcher_;
+ const bool positive_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRequestConditionAttributeRequestHeaders);
+};
+
+// Condition attribute for matching against response headers. Uses HeaderMatcher
+// to handle the actual tests, in connection with a boolean positiveness
+// flag. If that flag is set to true, then IsFulfilled() returns true iff
+// |header_matcher_| matches at least one header. Otherwise IsFulfilled()
+// returns true iff the |header_matcher_| matches no header.
+class WebRequestConditionAttributeResponseHeaders
+ : public WebRequestConditionAttribute {
+ public:
+ // Factory method, see WebRequestConditionAttribute::Create.
+ static scoped_refptr<const WebRequestConditionAttribute> Create(
+ const std::string& name,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message);
+
+ // Implementation of WebRequestConditionAttribute:
+ int GetStages() const override;
+ bool IsFulfilled(const WebRequestData& request_data) const override;
+ Type GetType() const override;
+ std::string GetName() const override;
+ bool Equals(const WebRequestConditionAttribute* other) const override;
+
+ private:
+ WebRequestConditionAttributeResponseHeaders(
+ scoped_ptr<const HeaderMatcher> header_matcher, bool positive);
+ ~WebRequestConditionAttributeResponseHeaders() override;
+
+ const scoped_ptr<const HeaderMatcher> header_matcher_;
+ const bool positive_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRequestConditionAttributeResponseHeaders);
+};
+
+// This condition tests whether the request origin is third-party.
+class WebRequestConditionAttributeThirdParty
+ : public WebRequestConditionAttribute {
+ public:
+ // Factory method, see WebRequestConditionAttribute::Create.
+ static scoped_refptr<const WebRequestConditionAttribute> Create(
+ const std::string& name,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message);
+
+ // Implementation of WebRequestConditionAttribute:
+ int GetStages() const override;
+ bool IsFulfilled(const WebRequestData& request_data) const override;
+ Type GetType() const override;
+ std::string GetName() const override;
+ bool Equals(const WebRequestConditionAttribute* other) const override;
+
+ private:
+ explicit WebRequestConditionAttributeThirdParty(bool match_third_party);
+ ~WebRequestConditionAttributeThirdParty() override;
+
+ const bool match_third_party_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRequestConditionAttributeThirdParty);
+};
+
+// This condition is used as a filter for request stages. It is true exactly in
+// stages specified on construction.
+class WebRequestConditionAttributeStages
+ : public WebRequestConditionAttribute {
+ public:
+ // Factory method, see WebRequestConditionAttribute::Create.
+ static scoped_refptr<const WebRequestConditionAttribute> Create(
+ const std::string& name,
+ const base::Value* value,
+ std::string* error,
+ bool* bad_message);
+
+ // Implementation of WebRequestConditionAttribute:
+ int GetStages() const override;
+ bool IsFulfilled(const WebRequestData& request_data) const override;
+ Type GetType() const override;
+ std::string GetName() const override;
+ bool Equals(const WebRequestConditionAttribute* other) const override;
+
+ private:
+ explicit WebRequestConditionAttributeStages(int allowed_stages);
+ ~WebRequestConditionAttributeStages() override;
+
+ const int allowed_stages_; // Composition of RequestStage values.
+
+ DISALLOW_COPY_AND_ASSIGN(WebRequestConditionAttributeStages);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DECLARATIVE_WEBREQUEST_WEBREQUEST_CONDITION_ATTRIBUTE_H_
diff --git a/chromium/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute_unittest.cc b/chromium/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute_unittest.cc
new file mode 100644
index 00000000000..7960af4b860
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute_unittest.cc
@@ -0,0 +1,719 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.h"
+
+#include <stddef.h>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/values.h"
+#include "content/public/browser/resource_request_info.h"
+#include "extensions/browser/api/declarative_webrequest/webrequest_condition.h"
+#include "extensions/browser/api/declarative_webrequest/webrequest_constants.h"
+#include "net/base/request_priority.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::DictionaryValue;
+using base::FundamentalValue;
+using base::ListValue;
+using base::StringValue;
+using base::Value;
+using content::ResourceType;
+
+namespace extensions {
+
+namespace keys = declarative_webrequest_constants;
+
+namespace {
+const char kUnknownConditionName[] = "unknownType";
+
+base::FilePath TestDataPath(base::StringPiece relative_to_src) {
+ base::FilePath src_dir;
+ CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &src_dir));
+ return src_dir.AppendASCII(relative_to_src);
+}
+
+TEST(WebRequestConditionAttributeTest, CreateConditionAttribute) {
+ // Necessary for TestURLRequest.
+ base::MessageLoopForIO message_loop;
+
+ std::string error;
+ scoped_refptr<const WebRequestConditionAttribute> result;
+ base::StringValue string_value("main_frame");
+ base::ListValue resource_types;
+ resource_types.Append(new base::StringValue("main_frame"));
+
+ // Test wrong condition name passed.
+ error.clear();
+ result = WebRequestConditionAttribute::Create(
+ kUnknownConditionName, &resource_types, &error);
+ EXPECT_FALSE(error.empty());
+ EXPECT_FALSE(result.get());
+
+ // Test wrong data type passed
+ error.clear();
+ result = WebRequestConditionAttribute::Create(
+ keys::kResourceTypeKey, &string_value, &error);
+ EXPECT_FALSE(error.empty());
+ EXPECT_FALSE(result.get());
+
+ error.clear();
+ result = WebRequestConditionAttribute::Create(
+ keys::kContentTypeKey, &string_value, &error);
+ EXPECT_FALSE(error.empty());
+ EXPECT_FALSE(result.get());
+
+ // Test success
+ error.clear();
+ result = WebRequestConditionAttribute::Create(
+ keys::kResourceTypeKey, &resource_types, &error);
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(result.get());
+ EXPECT_EQ(WebRequestConditionAttribute::CONDITION_RESOURCE_TYPE,
+ result->GetType());
+ EXPECT_EQ(std::string(keys::kResourceTypeKey), result->GetName());
+}
+
+TEST(WebRequestConditionAttributeTest, ResourceType) {
+ // Necessary for TestURLRequest.
+ base::MessageLoopForIO message_loop;
+
+ std::string error;
+ base::ListValue resource_types;
+ // The 'sub_frame' value is chosen arbitrarily, so as the corresponding
+ // content::ResourceType is not 0, the default value.
+ resource_types.Append(new base::StringValue("sub_frame"));
+
+ scoped_refptr<const WebRequestConditionAttribute> attribute =
+ WebRequestConditionAttribute::Create(
+ keys::kResourceTypeKey, &resource_types, &error);
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(attribute.get());
+ EXPECT_EQ(std::string(keys::kResourceTypeKey), attribute->GetName());
+
+ net::TestURLRequestContext context;
+ scoped_ptr<net::URLRequest> url_request_ok(context.CreateRequest(
+ GURL("http://www.example.com"), net::DEFAULT_PRIORITY, NULL));
+ content::ResourceRequestInfo::AllocateForTesting(
+ url_request_ok.get(), content::RESOURCE_TYPE_SUB_FRAME,
+ NULL, // context
+ -1, // render_process_id
+ -1, // render_view_id
+ -1, // render_frame_id
+ false, // is_main_frame
+ false, // parent_is_main_frame
+ true, // allow_download
+ false, // is_async
+ false); // is_using_lofi
+ EXPECT_TRUE(attribute->IsFulfilled(WebRequestData(url_request_ok.get(),
+ ON_BEFORE_REQUEST)));
+
+ scoped_ptr<net::URLRequest> url_request_fail(context.CreateRequest(
+ GURL("http://www.example.com"), net::DEFAULT_PRIORITY, NULL));
+ content::ResourceRequestInfo::AllocateForTesting(
+ url_request_fail.get(), content::RESOURCE_TYPE_MAIN_FRAME,
+ NULL, // context
+ -1, // render_process_id
+ -1, // render_view_id
+ -1, // render_frame_id
+ true, // is_main_frame
+ false, // parent_is_main_frame
+ true, // allow_download
+ false, // is_async
+ false); // is_using_lofi
+ EXPECT_FALSE(attribute->IsFulfilled(WebRequestData(url_request_fail.get(),
+ ON_BEFORE_REQUEST)));
+}
+
+TEST(WebRequestConditionAttributeTest, ContentType) {
+ // Necessary for TestURLRequest.
+ base::MessageLoopForIO message_loop;
+
+ std::string error;
+ scoped_refptr<const WebRequestConditionAttribute> result;
+
+ net::EmbeddedTestServer test_server;
+ test_server.ServeFilesFromDirectory(TestDataPath(
+ "chrome/test/data/extensions/api_test/webrequest/declarative"));
+ ASSERT_TRUE(test_server.Start());
+
+ net::TestURLRequestContext context;
+ net::TestDelegate delegate;
+ scoped_ptr<net::URLRequest> url_request(context.CreateRequest(
+ test_server.GetURL("/headers.html"), net::DEFAULT_PRIORITY, &delegate));
+ url_request->Start();
+ base::MessageLoop::current()->Run();
+
+ base::ListValue content_types;
+ content_types.Append(new base::StringValue("text/plain"));
+ scoped_refptr<const WebRequestConditionAttribute> attribute_include =
+ WebRequestConditionAttribute::Create(
+ keys::kContentTypeKey, &content_types, &error);
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(attribute_include.get());
+ EXPECT_FALSE(attribute_include->IsFulfilled(
+ WebRequestData(url_request.get(), ON_BEFORE_REQUEST,
+ url_request->response_headers())));
+ EXPECT_TRUE(attribute_include->IsFulfilled(
+ WebRequestData(url_request.get(), ON_HEADERS_RECEIVED,
+ url_request->response_headers())));
+ EXPECT_EQ(std::string(keys::kContentTypeKey), attribute_include->GetName());
+
+ scoped_refptr<const WebRequestConditionAttribute> attribute_exclude =
+ WebRequestConditionAttribute::Create(
+ keys::kExcludeContentTypeKey, &content_types, &error);
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(attribute_exclude.get());
+ EXPECT_FALSE(attribute_exclude->IsFulfilled(
+ WebRequestData(url_request.get(), ON_HEADERS_RECEIVED,
+ url_request->response_headers())));
+
+ content_types.Clear();
+ content_types.Append(new base::StringValue("something/invalid"));
+ scoped_refptr<const WebRequestConditionAttribute> attribute_unincluded =
+ WebRequestConditionAttribute::Create(
+ keys::kContentTypeKey, &content_types, &error);
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(attribute_unincluded.get());
+ EXPECT_FALSE(attribute_unincluded->IsFulfilled(
+ WebRequestData(url_request.get(), ON_HEADERS_RECEIVED,
+ url_request->response_headers())));
+
+ scoped_refptr<const WebRequestConditionAttribute> attribute_unexcluded =
+ WebRequestConditionAttribute::Create(
+ keys::kExcludeContentTypeKey, &content_types, &error);
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(attribute_unexcluded.get());
+ EXPECT_TRUE(attribute_unexcluded->IsFulfilled(
+ WebRequestData(url_request.get(), ON_HEADERS_RECEIVED,
+ url_request->response_headers())));
+ EXPECT_EQ(std::string(keys::kExcludeContentTypeKey),
+ attribute_unexcluded->GetName());
+}
+
+// Testing WebRequestConditionAttributeThirdParty.
+TEST(WebRequestConditionAttributeTest, ThirdParty) {
+ // Necessary for TestURLRequest.
+ base::MessageLoopForIO message_loop;
+
+ std::string error;
+ const FundamentalValue value_true(true);
+ // This attribute matches only third party requests.
+ scoped_refptr<const WebRequestConditionAttribute> third_party_attribute =
+ WebRequestConditionAttribute::Create(keys::kThirdPartyKey,
+ &value_true,
+ &error);
+ ASSERT_EQ("", error);
+ ASSERT_TRUE(third_party_attribute.get());
+ EXPECT_EQ(std::string(keys::kThirdPartyKey),
+ third_party_attribute->GetName());
+ const FundamentalValue value_false(false);
+ // This attribute matches only first party requests.
+ scoped_refptr<const WebRequestConditionAttribute> first_party_attribute =
+ WebRequestConditionAttribute::Create(keys::kThirdPartyKey,
+ &value_false,
+ &error);
+ ASSERT_EQ("", error);
+ ASSERT_TRUE(first_party_attribute.get());
+ EXPECT_EQ(std::string(keys::kThirdPartyKey),
+ first_party_attribute->GetName());
+
+ const GURL url_empty;
+ const GURL url_a("http://a.com");
+ const GURL url_b("http://b.com");
+ net::TestURLRequestContext context;
+ net::TestDelegate delegate;
+ scoped_ptr<net::URLRequest> url_request(
+ context.CreateRequest(url_a, net::DEFAULT_PRIORITY, &delegate));
+
+ for (unsigned int i = 1; i <= kLastActiveStage; i <<= 1) {
+ if (!(kActiveStages & i))
+ continue;
+ const RequestStage stage = static_cast<RequestStage>(i);
+ url_request->set_first_party_for_cookies(url_empty);
+ EXPECT_FALSE(third_party_attribute->IsFulfilled(
+ WebRequestData(url_request.get(), stage)));
+ EXPECT_TRUE(first_party_attribute->IsFulfilled(
+ WebRequestData(url_request.get(), stage)));
+
+ url_request->set_first_party_for_cookies(url_b);
+ EXPECT_TRUE(third_party_attribute->IsFulfilled(
+ WebRequestData(url_request.get(), stage)));
+ EXPECT_FALSE(first_party_attribute->IsFulfilled(
+ WebRequestData(url_request.get(), stage)));
+
+ url_request->set_first_party_for_cookies(url_a);
+ EXPECT_FALSE(third_party_attribute->IsFulfilled(
+ WebRequestData(url_request.get(), stage)));
+ EXPECT_TRUE(first_party_attribute->IsFulfilled(
+ WebRequestData(url_request.get(), stage)));
+ }
+}
+
+// Testing WebRequestConditionAttributeStages. This iterates over all stages,
+// and tests a couple of "stage" attributes -- one created with an empty set of
+// applicable stages, one for each stage applicable for that stage, and one
+// applicable in all stages.
+TEST(WebRequestConditionAttributeTest, Stages) {
+ // Necessary for TestURLRequest.
+ base::MessageLoopForIO message_loop;
+
+ typedef std::pair<RequestStage, const char*> StageNamePair;
+ static const StageNamePair active_stages[] = {
+ StageNamePair(ON_BEFORE_REQUEST, keys::kOnBeforeRequestEnum),
+ StageNamePair(ON_BEFORE_SEND_HEADERS, keys::kOnBeforeSendHeadersEnum),
+ StageNamePair(ON_HEADERS_RECEIVED, keys::kOnHeadersReceivedEnum),
+ StageNamePair(ON_AUTH_REQUIRED, keys::kOnAuthRequiredEnum)
+ };
+
+ // Check that exactly all active stages are considered in this test.
+ unsigned int covered_stages = 0;
+ for (size_t i = 0; i < arraysize(active_stages); ++i)
+ covered_stages |= active_stages[i].first;
+ EXPECT_EQ(kActiveStages, covered_stages);
+
+ std::string error;
+
+ // Create an attribute with an empty set of applicable stages.
+ base::ListValue empty_list;
+ scoped_refptr<const WebRequestConditionAttribute> empty_attribute =
+ WebRequestConditionAttribute::Create(keys::kStagesKey,
+ &empty_list,
+ &error);
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(empty_attribute.get());
+ EXPECT_EQ(std::string(keys::kStagesKey), empty_attribute->GetName());
+
+ // Create an attribute with all possible applicable stages.
+ base::ListValue all_stages;
+ for (size_t i = 0; i < arraysize(active_stages); ++i)
+ all_stages.AppendString(active_stages[i].second);
+ scoped_refptr<const WebRequestConditionAttribute> attribute_with_all =
+ WebRequestConditionAttribute::Create(keys::kStagesKey,
+ &all_stages,
+ &error);
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(attribute_with_all.get());
+ EXPECT_EQ(std::string(keys::kStagesKey), attribute_with_all->GetName());
+
+ // Create one attribute for each single stage, to be applicable in that stage.
+ std::vector<scoped_refptr<const WebRequestConditionAttribute> >
+ one_stage_attributes;
+
+ for (size_t i = 0; i < arraysize(active_stages); ++i) {
+ base::ListValue single_stage_list;
+ single_stage_list.AppendString(active_stages[i].second);
+ one_stage_attributes.push_back(
+ WebRequestConditionAttribute::Create(keys::kStagesKey,
+ &single_stage_list,
+ &error));
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(one_stage_attributes.back().get() != NULL);
+ }
+
+ const GURL url_empty;
+ net::TestURLRequestContext context;
+ net::TestDelegate delegate;
+ scoped_ptr<net::URLRequest> url_request(
+ context.CreateRequest(url_empty, net::DEFAULT_PRIORITY, &delegate));
+
+ for (size_t i = 0; i < arraysize(active_stages); ++i) {
+ EXPECT_FALSE(empty_attribute->IsFulfilled(
+ WebRequestData(url_request.get(), active_stages[i].first)));
+
+ for (size_t j = 0; j < one_stage_attributes.size(); ++j) {
+ EXPECT_EQ(i == j,
+ one_stage_attributes[j]->IsFulfilled(
+ WebRequestData(url_request.get(), active_stages[i].first)));
+ }
+
+ EXPECT_TRUE(attribute_with_all->IsFulfilled(
+ WebRequestData(url_request.get(), active_stages[i].first)));
+ }
+}
+
+namespace {
+
+// Builds a vector of vectors of string pointers from an array of strings.
+// |array| is in fact a sequence of arrays. The array |sizes| captures the sizes
+// of all parts of |array|, and |size| is the length of |sizes| itself.
+// Example (this is pseudo-code, not C++):
+// array = { "a", "b", "c", "d", "e", "f" }
+// sizes = { 2, 0, 4 }
+// size = 3
+// results in out == { {&"a", &"b"}, {}, {&"c", &"d", &"e", &"f"} }
+void GetArrayAsVector(const std::string array[],
+ const size_t sizes[],
+ const size_t size,
+ std::vector< std::vector<const std::string*> >* out) {
+ out->clear();
+ size_t next = 0;
+ for (size_t i = 0; i < size; ++i) {
+ out->push_back(std::vector<const std::string*>());
+ for (size_t j = next; j < next + sizes[i]; ++j) {
+ out->back().push_back(&(array[j]));
+ }
+ next += sizes[i];
+ }
+}
+
+// Builds a DictionaryValue from an array of the form {name1, value1, name2,
+// value2, ...}. Values for the same key are grouped in a ListValue.
+scoped_ptr<base::DictionaryValue> GetDictionaryFromArray(
+ const std::vector<const std::string*>& array) {
+ const size_t length = array.size();
+ CHECK(length % 2 == 0);
+
+ scoped_ptr<base::DictionaryValue> dictionary(new base::DictionaryValue);
+ for (size_t i = 0; i < length; i += 2) {
+ const std::string* name = array[i];
+ const std::string* value = array[i+1];
+ if (dictionary->HasKey(*name)) {
+ base::Value* entry = NULL;
+ scoped_ptr<base::Value> entry_owned;
+ base::ListValue* list = NULL;
+ if (!dictionary->GetWithoutPathExpansion(*name, &entry))
+ return scoped_ptr<base::DictionaryValue>();
+ switch (entry->GetType()) {
+ case base::Value::TYPE_STRING:
+ // Replace the present string with a list.
+ list = new base::ListValue;
+ // Ignoring return value, we already verified the entry is there.
+ dictionary->RemoveWithoutPathExpansion(*name, &entry_owned);
+ list->Append(entry_owned.release());
+ list->Append(new base::StringValue(*value));
+ dictionary->SetWithoutPathExpansion(*name, list);
+ break;
+ case base::Value::TYPE_LIST: // Just append to the list.
+ CHECK(entry->GetAsList(&list));
+ list->Append(new base::StringValue(*value));
+ break;
+ default:
+ NOTREACHED(); // We never put other Values here.
+ return scoped_ptr<base::DictionaryValue>();
+ }
+ } else {
+ dictionary->SetString(*name, *value);
+ }
+ }
+ return dictionary;
+}
+
+// Returns whether the response headers from |url_request| satisfy the match
+// criteria given in |tests|. For at least one |i| all tests from |tests[i]|
+// must pass. If |positive_test| is true, the dictionary is interpreted as the
+// containsHeaders property of a RequestMatcher, otherwise as
+// doesNotContainHeaders.
+void MatchAndCheck(const std::vector< std::vector<const std::string*> >& tests,
+ const std::string& key,
+ RequestStage stage,
+ net::URLRequest* url_request,
+ bool* result) {
+ base::ListValue contains_headers;
+ for (size_t i = 0; i < tests.size(); ++i) {
+ scoped_ptr<base::DictionaryValue> temp(GetDictionaryFromArray(tests[i]));
+ ASSERT_TRUE(temp.get());
+ contains_headers.Append(temp.release());
+ }
+
+ std::string error;
+ scoped_refptr<const WebRequestConditionAttribute> attribute =
+ WebRequestConditionAttribute::Create(key, &contains_headers, &error);
+ ASSERT_EQ("", error);
+ ASSERT_TRUE(attribute.get());
+ EXPECT_EQ(key, attribute->GetName());
+
+ *result = attribute->IsFulfilled(WebRequestData(
+ url_request, stage, url_request->response_headers()));
+}
+
+} // namespace
+
+// Here we test WebRequestConditionAttributeRequestHeaders for matching
+// correctly against request headers. This test is not as extensive as
+// "ResponseHeaders" (below), because the header-matching code is shared
+// by both types of condition attributes, so it is enough to test it once.
+TEST(WebRequestConditionAttributeTest, RequestHeaders) {
+ // Necessary for TestURLRequest.
+ base::MessageLoopForIO message_loop;
+
+ net::TestURLRequestContext context;
+ net::TestDelegate delegate;
+ scoped_ptr<net::URLRequest> url_request(
+ context.CreateRequest(GURL("http://example.com"), // Dummy URL.
+ net::DEFAULT_PRIORITY, &delegate));
+ url_request->SetExtraRequestHeaderByName(
+ "Custom-header", "custom/value", true /* overwrite */);
+ url_request->Start();
+ base::MessageLoop::current()->Run();
+
+ std::vector<std::vector<const std::string*> > tests;
+ bool result = false;
+
+ const RequestStage stage = ON_BEFORE_SEND_HEADERS;
+
+ // First set of test data -- passing conjunction.
+ const std::string kPassingCondition[] = {
+ keys::kNameContainsKey, "CuStOm", // Header names are case insensitive.
+ keys::kNameEqualsKey, "custom-header",
+ keys::kValueSuffixKey, "alue",
+ keys::kValuePrefixKey, "custom/value"
+ };
+ const size_t kPassingConditionSizes[] = { arraysize(kPassingCondition) };
+ GetArrayAsVector(kPassingCondition, kPassingConditionSizes, 1u, &tests);
+ // Positive filter, passing (conjunction of tests).
+ MatchAndCheck(
+ tests, keys::kRequestHeadersKey, stage, url_request.get(), &result);
+ EXPECT_TRUE(result);
+ // Negative filter, failing (conjunction of tests).
+ MatchAndCheck(tests, keys::kExcludeRequestHeadersKey, stage,
+ url_request.get(), &result);
+ EXPECT_FALSE(result);
+
+ // Second set of test data -- failing disjunction.
+ const std::string kFailCondition[] = {
+ keys::kNameSuffixKey, "Custom", // Test 1.
+ keys::kNameEqualsKey, "ustom-valu", // Test 2.
+ keys::kValuePrefixKey, "custom ", // Test 3.
+ keys::kValueContainsKey, " value" // Test 4.
+ };
+ const size_t kFailConditionSizes[] = { 2u, 2u, 2u, 2u };
+ GetArrayAsVector(kFailCondition, kFailConditionSizes, 4u, &tests);
+ // Positive filter, failing (disjunction of tests).
+ MatchAndCheck(tests, keys::kRequestHeadersKey, stage, url_request.get(),
+ &result);
+ EXPECT_FALSE(result);
+ // Negative filter, passing (disjunction of tests).
+ MatchAndCheck(tests, keys::kExcludeRequestHeadersKey, stage,
+ url_request.get(), &result);
+ EXPECT_TRUE(result);
+
+ // Third set of test data, corner case -- empty disjunction.
+ GetArrayAsVector(NULL, NULL, 0u, &tests);
+ // Positive filter, failing (no test to pass).
+ MatchAndCheck(tests, keys::kRequestHeadersKey, stage, url_request.get(),
+ &result);
+ EXPECT_FALSE(result);
+ // Negative filter, passing (no test to fail).
+ MatchAndCheck(tests, keys::kExcludeRequestHeadersKey, stage,
+ url_request.get(), &result);
+ EXPECT_TRUE(result);
+
+ // Fourth set of test data, corner case -- empty conjunction.
+ const size_t kEmptyConjunctionSizes[] = { 0u };
+ GetArrayAsVector(NULL, kEmptyConjunctionSizes, 1u, &tests);
+ // Positive filter, passing (trivial test).
+ MatchAndCheck(tests, keys::kRequestHeadersKey, stage, url_request.get(),
+ &result);
+ EXPECT_TRUE(result);
+ // Negative filter, failing.
+ MatchAndCheck(tests, keys::kExcludeRequestHeadersKey, stage,
+ url_request.get(), &result);
+ EXPECT_FALSE(result);
+}
+
+// Here we test WebRequestConditionAttributeResponseHeaders for:
+// 1. Correct implementation of prefix/suffix/contains/equals matching.
+// 2. Performing logical disjunction (||) between multiple specifications.
+// 3. Negating the match in case of 'doesNotContainHeaders'.
+TEST(WebRequestConditionAttributeTest, ResponseHeaders) {
+ // Necessary for TestURLRequest.
+ base::MessageLoopForIO message_loop;
+
+ net::EmbeddedTestServer test_server;
+ test_server.ServeFilesFromDirectory(TestDataPath(
+ "chrome/test/data/extensions/api_test/webrequest/declarative"));
+ ASSERT_TRUE(test_server.Start());
+
+ net::TestURLRequestContext context;
+ net::TestDelegate delegate;
+ scoped_ptr<net::URLRequest> url_request(context.CreateRequest(
+ test_server.GetURL("/headers.html"), net::DEFAULT_PRIORITY, &delegate));
+ url_request->Start();
+ base::MessageLoop::current()->Run();
+
+ // In all the tests below we assume that the server includes the headers
+ // Custom-Header: custom/value
+ // Custom-Header-B: valueA
+ // Custom-Header-B: valueB
+ // Custom-Header-C: valueC, valueD
+ // Custom-Header-D:
+ // in the response, but does not include "Non-existing: void".
+
+ std::vector< std::vector<const std::string*> > tests;
+ bool result;
+
+ const RequestStage stage = ON_HEADERS_RECEIVED;
+
+ // 1.a. -- All these tests should pass.
+ const std::string kPassingCondition[] = {
+ keys::kNamePrefixKey, "Custom",
+ keys::kNameSuffixKey, "m-header", // Header names are case insensitive.
+ keys::kValueContainsKey, "alu",
+ keys::kValueEqualsKey, "custom/value"
+ };
+ const size_t kPassingConditionSizes[] = { arraysize(kPassingCondition) };
+ GetArrayAsVector(kPassingCondition, kPassingConditionSizes, 1u, &tests);
+ MatchAndCheck(tests, keys::kResponseHeadersKey, stage, url_request.get(),
+ &result);
+ EXPECT_TRUE(result);
+
+ // 1.b. -- None of the following tests in the discjunction should pass.
+ const std::string kFailCondition[] = {
+ keys::kNamePrefixKey, " Custom", // Test 1.
+ keys::kNameContainsKey, " -", // Test 2.
+ keys::kValueSuffixKey, "alu", // Test 3.
+ keys::kValueEqualsKey, "custom" // Test 4.
+ };
+ const size_t kFailConditionSizes[] = { 2u, 2u, 2u, 2u };
+ GetArrayAsVector(kFailCondition, kFailConditionSizes, 4u, &tests);
+ MatchAndCheck(tests, keys::kResponseHeadersKey, stage, url_request.get(),
+ &result);
+ EXPECT_FALSE(result);
+
+ // 1.c. -- This should fail (mixing name and value from different headers)
+ const std::string kMixingCondition[] = {
+ keys::kNameSuffixKey, "Header-B",
+ keys::kValueEqualsKey, "custom/value"
+ };
+ const size_t kMixingConditionSizes[] = { arraysize(kMixingCondition) };
+ GetArrayAsVector(kMixingCondition, kMixingConditionSizes, 1u, &tests);
+ MatchAndCheck(tests, keys::kResponseHeadersKey, stage, url_request.get(),
+ &result);
+ EXPECT_FALSE(result);
+
+ // 1.d. -- Test handling multiple values for one header (both should pass).
+ const std::string kMoreValues1[] = {
+ keys::kNameEqualsKey, "Custom-header-b",
+ keys::kValueEqualsKey, "valueA"
+ };
+ const size_t kMoreValues1Sizes[] = { arraysize(kMoreValues1) };
+ GetArrayAsVector(kMoreValues1, kMoreValues1Sizes, 1u, &tests);
+ MatchAndCheck(tests, keys::kResponseHeadersKey, stage, url_request.get(),
+ &result);
+ EXPECT_TRUE(result);
+ const std::string kMoreValues2[] = {
+ keys::kNameEqualsKey, "Custom-header-b",
+ keys::kValueEqualsKey, "valueB"
+ };
+ const size_t kMoreValues2Sizes[] = { arraysize(kMoreValues2) };
+ GetArrayAsVector(kMoreValues2, kMoreValues2Sizes, 1u, &tests);
+ MatchAndCheck(tests, keys::kResponseHeadersKey, stage, url_request.get(),
+ &result);
+ EXPECT_TRUE(result);
+
+ // 1.e. -- This should fail as conjunction but pass as disjunction.
+ const std::string kConflict[] = {
+ keys::kNameSuffixKey, "Header", // True for some header.
+ keys::kNameContainsKey, "Header-B" // True for a different header.
+ };
+ // First disjunction, no conflict.
+ const size_t kNoConflictSizes[] = { 2u, 2u };
+ GetArrayAsVector(kConflict, kNoConflictSizes, 2u, &tests);
+ MatchAndCheck(tests, keys::kResponseHeadersKey, stage, url_request.get(),
+ &result);
+ EXPECT_TRUE(result);
+ // Then conjunction, conflict.
+ const size_t kConflictSizes[] = { arraysize(kConflict) };
+ GetArrayAsVector(kConflict, kConflictSizes, 1u, &tests);
+ MatchAndCheck(tests, keys::kResponseHeadersKey, stage, url_request.get(),
+ &result);
+ EXPECT_FALSE(result);
+
+ // 1.f. -- This should pass, checking for correct treatment of ',' in values.
+ const std::string kComma[] = {
+ keys::kNameSuffixKey, "Header-C",
+ keys::kValueEqualsKey, "valueC, valueD"
+ };
+ const size_t kCommaSizes[] = { arraysize(kComma) };
+ GetArrayAsVector(kComma, kCommaSizes, 1u, &tests);
+ MatchAndCheck(tests, keys::kResponseHeadersKey, stage, url_request.get(),
+ &result);
+ EXPECT_TRUE(result);
+
+ // 1.g. -- This should pass, empty values are values as well.
+ const std::string kEmpty[] = {
+ keys::kNameEqualsKey, "custom-header-d",
+ keys::kValueEqualsKey, ""
+ };
+ const size_t kEmptySizes[] = { arraysize(kEmpty) };
+ GetArrayAsVector(kEmpty, kEmptySizes, 1u, &tests);
+ MatchAndCheck(tests, keys::kResponseHeadersKey, stage, url_request.get(),
+ &result);
+ EXPECT_TRUE(result);
+
+ // 1.h. -- Values are case-sensitive, this should fail.
+ const std::string kLowercase[] = {
+ keys::kNameEqualsKey, "Custom-header-b",
+ keys::kValuePrefixKey, "valueb", // valueb != valueB
+ keys::kNameEqualsKey, "Custom-header-b",
+ keys::kValueSuffixKey, "valueb",
+ keys::kNameEqualsKey, "Custom-header-b",
+ keys::kValueContainsKey, "valueb",
+ keys::kNameEqualsKey, "Custom-header-b",
+ keys::kValueEqualsKey, "valueb"
+ };
+ const size_t kLowercaseSizes[] = { 4u, 4u, 4u, 4u }; // As disjunction.
+ GetArrayAsVector(kLowercase, kLowercaseSizes, 4u, &tests);
+ MatchAndCheck(tests, keys::kResponseHeadersKey, stage, url_request.get(),
+ &result);
+ EXPECT_FALSE(result);
+
+ // 1.i. -- Names are case-insensitive, this should pass.
+ const std::string kUppercase[] = {
+ keys::kNamePrefixKey, "CUSTOM-HEADER-B",
+ keys::kNameSuffixKey, "CUSTOM-HEADER-B",
+ keys::kNameEqualsKey, "CUSTOM-HEADER-B",
+ keys::kNameContainsKey, "CUSTOM-HEADER-B"
+ };
+ const size_t kUppercaseSizes[] = { arraysize(kUppercase) }; // Conjunction.
+ GetArrayAsVector(kUppercase, kUppercaseSizes, 1u, &tests);
+ MatchAndCheck(tests, keys::kResponseHeadersKey, stage, url_request.get(),
+ &result);
+ EXPECT_TRUE(result);
+
+ // 2.a. -- This should pass as disjunction, because one of the tests passes.
+ const std::string kDisjunction[] = {
+ keys::kNamePrefixKey, "Non-existing", // This one fails.
+ keys::kNameSuffixKey, "Non-existing", // This one fails.
+ keys::kValueEqualsKey, "void", // This one fails.
+ keys::kValueContainsKey, "alu" // This passes.
+ };
+ const size_t kDisjunctionSizes[] = { 2u, 2u, 2u, 2u };
+ GetArrayAsVector(kDisjunction, kDisjunctionSizes, 4u, &tests);
+ MatchAndCheck(tests, keys::kResponseHeadersKey, stage, url_request.get(),
+ &result);
+ EXPECT_TRUE(result);
+
+ // 3.a. -- This should pass.
+ const std::string kNonExistent[] = {
+ keys::kNameEqualsKey, "Non-existing",
+ keys::kValueEqualsKey, "void"
+ };
+ const size_t kNonExistentSizes[] = { arraysize(kNonExistent) };
+ GetArrayAsVector(kNonExistent, kNonExistentSizes, 1u, &tests);
+ MatchAndCheck(tests, keys::kExcludeResponseHeadersKey, stage,
+ url_request.get(), &result);
+ EXPECT_TRUE(result);
+
+ // 3.b. -- This should fail.
+ const std::string kExisting[] = {
+ keys::kNameEqualsKey, "custom-header-b",
+ keys::kValueEqualsKey, "valueB"
+ };
+ const size_t kExistingSize[] = { arraysize(kExisting) };
+ GetArrayAsVector(kExisting, kExistingSize, 1u, &tests);
+ MatchAndCheck(tests, keys::kExcludeResponseHeadersKey, stage,
+ url_request.get(), &result);
+ EXPECT_FALSE(result);
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/declarative_webrequest/webrequest_condition_unittest.cc b/chromium/extensions/browser/api/declarative_webrequest/webrequest_condition_unittest.cc
new file mode 100644
index 00000000000..0de50310766
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative_webrequest/webrequest_condition_unittest.cc
@@ -0,0 +1,406 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/declarative_webrequest/webrequest_condition.h"
+
+#include <set>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/test/values_test_util.h"
+#include "base/values.h"
+#include "components/url_matcher/url_matcher_constants.h"
+#include "content/public/browser/resource_request_info.h"
+#include "extensions/browser/api/declarative_webrequest/webrequest_constants.h"
+#include "net/base/request_priority.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::ResourceType;
+using url_matcher::URLMatcher;
+using url_matcher::URLMatcherConditionSet;
+
+namespace extensions {
+
+TEST(WebRequestConditionTest, CreateCondition) {
+ // Necessary for TestURLRequest.
+ base::MessageLoopForIO message_loop;
+ URLMatcher matcher;
+
+ std::string error;
+ scoped_ptr<WebRequestCondition> result;
+
+ // Test wrong condition name passed.
+ error.clear();
+ result = WebRequestCondition::Create(
+ NULL,
+ matcher.condition_factory(),
+ *base::test::ParseJson(
+ "{ \"invalid\": \"foobar\", \n"
+ " \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
+ "}"),
+ &error);
+ EXPECT_FALSE(error.empty());
+ EXPECT_FALSE(result.get());
+
+ // Test wrong datatype in host_suffix.
+ error.clear();
+ result = WebRequestCondition::Create(
+ NULL,
+ matcher.condition_factory(),
+ *base::test::ParseJson(
+ "{ \n"
+ " \"url\": [], \n"
+ " \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
+ "}"),
+ &error);
+ EXPECT_FALSE(error.empty());
+ EXPECT_FALSE(result.get());
+
+ // Test success (can we support multiple criteria?)
+ error.clear();
+ result = WebRequestCondition::Create(
+ NULL,
+ matcher.condition_factory(),
+ *base::test::ParseJson(
+ "{ \n"
+ " \"resourceType\": [\"main_frame\"], \n"
+ " \"url\": { \"hostSuffix\": \"example.com\" }, \n"
+ " \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
+ "}"),
+ &error);
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(result.get());
+
+ URLMatcherConditionSet::Vector url_matcher_condition_set;
+ result->GetURLMatcherConditionSets(&url_matcher_condition_set);
+ matcher.AddConditionSets(url_matcher_condition_set);
+
+ net::TestURLRequestContext context;
+ const GURL http_url("http://www.example.com");
+ scoped_ptr<net::URLRequest> match_request(
+ context.CreateRequest(http_url, net::DEFAULT_PRIORITY, NULL));
+ WebRequestData data(match_request.get(), ON_BEFORE_REQUEST);
+ WebRequestDataWithMatchIds request_data(&data);
+ request_data.url_match_ids = matcher.MatchURL(http_url);
+ EXPECT_EQ(1u, request_data.url_match_ids.size());
+ content::ResourceRequestInfo::AllocateForTesting(
+ match_request.get(), content::RESOURCE_TYPE_MAIN_FRAME,
+ NULL, // context
+ -1, // render_process_id
+ -1, // render_view_id
+ -1, // render_frame_id
+ true, // is_main_frame
+ false, // parent_is_main_frame
+ true, // allow_download
+ false, // is_async
+ false); // is_using_lofi
+ EXPECT_TRUE(result->IsFulfilled(request_data));
+
+ const GURL https_url("https://www.example.com");
+ scoped_ptr<net::URLRequest> wrong_resource_type(
+ context.CreateRequest(https_url, net::DEFAULT_PRIORITY, NULL));
+ data.request = wrong_resource_type.get();
+ request_data.url_match_ids = matcher.MatchURL(http_url);
+ // Make sure IsFulfilled does not fail because of URL matching.
+ EXPECT_EQ(1u, request_data.url_match_ids.size());
+ content::ResourceRequestInfo::AllocateForTesting(
+ wrong_resource_type.get(), content::RESOURCE_TYPE_SUB_FRAME,
+ NULL, // context
+ -1, // render_process_id
+ -1, // render_view_id
+ -1, // render_frame_id
+ false, // is_main_frame
+ false, // parent_is_main_frame
+ true, // allow_download
+ false, // is_async
+ false); // is_using_lofi
+ EXPECT_FALSE(result->IsFulfilled(request_data));
+}
+
+TEST(WebRequestConditionTest, CreateConditionFirstPartyForCookies) {
+ // Necessary for TestURLRequest.
+ base::MessageLoopForIO message_loop;
+ URLMatcher matcher;
+
+ std::string error;
+ scoped_ptr<WebRequestCondition> result;
+
+ result = WebRequestCondition::Create(
+ NULL,
+ matcher.condition_factory(),
+ *base::test::ParseJson(
+ "{ \n"
+ " \"firstPartyForCookiesUrl\": { \"hostPrefix\": \"fpfc\"}, \n"
+ " \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
+ "}"),
+ &error);
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(result.get());
+
+ URLMatcherConditionSet::Vector url_matcher_condition_set;
+ result->GetURLMatcherConditionSets(&url_matcher_condition_set);
+ matcher.AddConditionSets(url_matcher_condition_set);
+
+ net::TestURLRequestContext context;
+ const GURL http_url("http://www.example.com");
+ const GURL first_party_url("http://fpfc.example.com");
+ scoped_ptr<net::URLRequest> match_request(
+ context.CreateRequest(http_url, net::DEFAULT_PRIORITY, NULL));
+ WebRequestData data(match_request.get(), ON_BEFORE_REQUEST);
+ WebRequestDataWithMatchIds request_data(&data);
+ request_data.url_match_ids = matcher.MatchURL(http_url);
+ EXPECT_EQ(0u, request_data.url_match_ids.size());
+ request_data.first_party_url_match_ids = matcher.MatchURL(first_party_url);
+ EXPECT_EQ(1u, request_data.first_party_url_match_ids.size());
+ content::ResourceRequestInfo::AllocateForTesting(
+ match_request.get(), content::RESOURCE_TYPE_MAIN_FRAME,
+ NULL, // context
+ -1, // render_process_id
+ -1, // render_view_id
+ -1, // render_frame_id
+ true, // is_main_frame
+ false, // parent_is_main_frame
+ true, // allow_download
+ false, // is_async
+ false); // is_using_lofi
+ EXPECT_TRUE(result->IsFulfilled(request_data));
+}
+
+// Conditions without UrlFilter attributes need to be independent of URL
+// matching results. We test here that:
+// 1. A non-empty condition without UrlFilter attributes is fulfilled iff its
+// attributes are fulfilled.
+// 2. An empty condition (in particular, without UrlFilter attributes) is
+// always fulfilled.
+TEST(WebRequestConditionTest, NoUrlAttributes) {
+ // Necessary for TestURLRequest.
+ base::MessageLoopForIO message_loop;
+ URLMatcher matcher;
+ std::string error;
+
+ // The empty condition.
+ error.clear();
+ scoped_ptr<WebRequestCondition> condition_empty = WebRequestCondition::Create(
+ NULL,
+ matcher.condition_factory(),
+ *base::test::ParseJson(
+ "{ \n"
+ " \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
+ "}"),
+ &error);
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(condition_empty.get());
+
+ // A condition without a UrlFilter attribute, which is always true.
+ error.clear();
+ scoped_ptr<WebRequestCondition> condition_no_url_true =
+ WebRequestCondition::Create(
+ NULL,
+ matcher.condition_factory(),
+ *base::test::ParseJson(
+ "{ \n"
+ " \"instanceType\": \"declarativeWebRequest.RequestMatcher\", "
+ "\n"
+ // There is no "1st party for cookies" URL in the requests below,
+ // therefore all requests are considered first party for cookies.
+ " \"thirdPartyForCookies\": false, \n"
+ "}"),
+ &error);
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(condition_no_url_true.get());
+
+ // A condition without a UrlFilter attribute, which is always false.
+ error.clear();
+ scoped_ptr<WebRequestCondition> condition_no_url_false =
+ WebRequestCondition::Create(
+ NULL,
+ matcher.condition_factory(),
+ *base::test::ParseJson(
+ "{ \n"
+ " \"instanceType\": \"declarativeWebRequest.RequestMatcher\", "
+ "\n"
+ " \"thirdPartyForCookies\": true, \n"
+ "}"),
+ &error);
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(condition_no_url_false.get());
+
+ net::TestURLRequestContext context;
+ scoped_ptr<net::URLRequest> https_request(context.CreateRequest(
+ GURL("https://www.example.com"), net::DEFAULT_PRIORITY, NULL));
+
+ // 1. A non-empty condition without UrlFilter attributes is fulfilled iff its
+ // attributes are fulfilled.
+ WebRequestData data(https_request.get(), ON_BEFORE_REQUEST);
+ EXPECT_FALSE(
+ condition_no_url_false->IsFulfilled(WebRequestDataWithMatchIds(&data)));
+
+ data = WebRequestData(https_request.get(), ON_BEFORE_REQUEST);
+ EXPECT_TRUE(
+ condition_no_url_true->IsFulfilled(WebRequestDataWithMatchIds(&data)));
+
+ // 2. An empty condition (in particular, without UrlFilter attributes) is
+ // always fulfilled.
+ data = WebRequestData(https_request.get(), ON_BEFORE_REQUEST);
+ EXPECT_TRUE(condition_empty->IsFulfilled(WebRequestDataWithMatchIds(&data)));
+}
+
+TEST(WebRequestConditionTest, CreateConditionSet) {
+ // Necessary for TestURLRequest.
+ base::MessageLoopForIO message_loop;
+ URLMatcher matcher;
+
+ WebRequestConditionSet::Values conditions;
+ conditions.push_back(base::test::ParseJson(
+ "{ \n"
+ " \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
+ " \"url\": { \n"
+ " \"hostSuffix\": \"example.com\", \n"
+ " \"schemes\": [\"http\"], \n"
+ " }, \n"
+ "}"));
+ conditions.push_back(base::test::ParseJson(
+ "{ \n"
+ " \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
+ " \"url\": { \n"
+ " \"hostSuffix\": \"example.com\", \n"
+ " \"hostPrefix\": \"www\", \n"
+ " \"schemes\": [\"https\"], \n"
+ " }, \n"
+ "}"));
+
+ // Test insertion
+ std::string error;
+ scoped_ptr<WebRequestConditionSet> result = WebRequestConditionSet::Create(
+ NULL, matcher.condition_factory(), conditions, &error);
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(result.get());
+ EXPECT_EQ(2u, result->conditions().size());
+
+ // Tell the URLMatcher about our shiny new patterns.
+ URLMatcherConditionSet::Vector url_matcher_condition_set;
+ result->GetURLMatcherConditionSets(&url_matcher_condition_set);
+ matcher.AddConditionSets(url_matcher_condition_set);
+
+ // Test that the result is correct and matches http://www.example.com and
+ // https://www.example.com
+ GURL http_url("http://www.example.com");
+ net::TestURLRequestContext context;
+ scoped_ptr<net::URLRequest> http_request(
+ context.CreateRequest(http_url, net::DEFAULT_PRIORITY, NULL));
+ WebRequestData data(http_request.get(), ON_BEFORE_REQUEST);
+ WebRequestDataWithMatchIds request_data(&data);
+ request_data.url_match_ids = matcher.MatchURL(http_url);
+ EXPECT_EQ(1u, request_data.url_match_ids.size());
+ EXPECT_TRUE(result->IsFulfilled(*(request_data.url_match_ids.begin()),
+ request_data));
+
+ GURL https_url("https://www.example.com");
+ request_data.url_match_ids = matcher.MatchURL(https_url);
+ EXPECT_EQ(1u, request_data.url_match_ids.size());
+ scoped_ptr<net::URLRequest> https_request(
+ context.CreateRequest(https_url, net::DEFAULT_PRIORITY, NULL));
+ data.request = https_request.get();
+ EXPECT_TRUE(result->IsFulfilled(*(request_data.url_match_ids.begin()),
+ request_data));
+
+ // Check that both, hostPrefix and hostSuffix are evaluated.
+ GURL https_foo_url("https://foo.example.com");
+ request_data.url_match_ids = matcher.MatchURL(https_foo_url);
+ EXPECT_EQ(0u, request_data.url_match_ids.size());
+ scoped_ptr<net::URLRequest> https_foo_request(
+ context.CreateRequest(https_foo_url, net::DEFAULT_PRIORITY, NULL));
+ data.request = https_foo_request.get();
+ EXPECT_FALSE(result->IsFulfilled(-1, request_data));
+}
+
+TEST(WebRequestConditionTest, TestPortFilter) {
+ // Necessary for TestURLRequest.
+ base::MessageLoopForIO message_loop;
+ URLMatcher matcher;
+
+ WebRequestConditionSet::Values conditions;
+ conditions.push_back(base::test::ParseJson(
+ "{ \n"
+ " \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
+ " \"url\": { \n"
+ " \"ports\": [80, [1000, 1010]], \n" // Allow 80;1000-1010.
+ " \"hostSuffix\": \"example.com\", \n"
+ " }, \n"
+ "}"));
+
+ // Test insertion
+ std::string error;
+ scoped_ptr<WebRequestConditionSet> result = WebRequestConditionSet::Create(
+ NULL, matcher.condition_factory(), conditions, &error);
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(result.get());
+ EXPECT_EQ(1u, result->conditions().size());
+
+ // Tell the URLMatcher about our shiny new patterns.
+ URLMatcherConditionSet::Vector url_matcher_condition_set;
+ result->GetURLMatcherConditionSets(&url_matcher_condition_set);
+ matcher.AddConditionSets(url_matcher_condition_set);
+
+ std::set<URLMatcherConditionSet::ID> url_match_ids;
+
+ // Test various URLs.
+ GURL http_url("http://www.example.com");
+ net::TestURLRequestContext context;
+ scoped_ptr<net::URLRequest> http_request(
+ context.CreateRequest(http_url, net::DEFAULT_PRIORITY, NULL));
+ url_match_ids = matcher.MatchURL(http_url);
+ ASSERT_EQ(1u, url_match_ids.size());
+
+ GURL http_url_80("http://www.example.com:80");
+ scoped_ptr<net::URLRequest> http_request_80(
+ context.CreateRequest(http_url_80, net::DEFAULT_PRIORITY, NULL));
+ url_match_ids = matcher.MatchURL(http_url_80);
+ ASSERT_EQ(1u, url_match_ids.size());
+
+ GURL http_url_1000("http://www.example.com:1000");
+ scoped_ptr<net::URLRequest> http_request_1000(
+ context.CreateRequest(http_url_1000, net::DEFAULT_PRIORITY, NULL));
+ url_match_ids = matcher.MatchURL(http_url_1000);
+ ASSERT_EQ(1u, url_match_ids.size());
+
+ GURL http_url_2000("http://www.example.com:2000");
+ scoped_ptr<net::URLRequest> http_request_2000(
+ context.CreateRequest(http_url_2000, net::DEFAULT_PRIORITY, NULL));
+ url_match_ids = matcher.MatchURL(http_url_2000);
+ ASSERT_EQ(0u, url_match_ids.size());
+}
+
+// Create a condition with two attributes: one on the request header and one on
+// the response header. The Create() method should fail and complain that it is
+// impossible that both conditions are fulfilled at the same time.
+TEST(WebRequestConditionTest, ConditionsWithConflictingStages) {
+ // Necessary for TestURLRequest.
+ base::MessageLoopForIO message_loop;
+ URLMatcher matcher;
+
+ std::string error;
+ scoped_ptr<WebRequestCondition> result;
+
+ // Test error on incompatible application stages for involved attributes.
+ error.clear();
+ result = WebRequestCondition::Create(
+ NULL,
+ matcher.condition_factory(),
+ *base::test::ParseJson(
+ "{ \n"
+ " \"instanceType\": \"declarativeWebRequest.RequestMatcher\", \n"
+ // Pass a JS array with one empty object to each of the header
+ // filters.
+ " \"requestHeaders\": [{}], \n"
+ " \"responseHeaders\": [{}], \n"
+ "}"),
+ &error);
+ EXPECT_FALSE(error.empty());
+ EXPECT_FALSE(result.get());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/declarative_webrequest/webrequest_constants.cc b/chromium/extensions/browser/api/declarative_webrequest/webrequest_constants.cc
new file mode 100644
index 00000000000..48e35e7decd
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative_webrequest/webrequest_constants.cc
@@ -0,0 +1,92 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/declarative_webrequest/webrequest_constants.h"
+
+namespace extensions {
+namespace declarative_webrequest_constants {
+
+// Signals to which WebRequestRulesRegistries are registered.
+const char kOnRequest[] = "declarativeWebRequest.onRequest";
+const char kOnMessage[] = "declarativeWebRequest.onMessage";
+
+// Keys of dictionaries.
+const char kAgeLowerBoundKey[] = "ageLowerBound";
+const char kAgeUpperBoundKey[] = "ageUpperBound";
+const char kCookieKey[] = "cookie";
+const char kContentTypeKey[] = "contentType";
+const char kDomainKey[] = "domain";
+const char kExcludeContentTypeKey[] = "excludeContentType";
+const char kExcludeRequestHeadersKey[] = "excludeRequestHeaders";
+const char kExcludeResponseHeadersKey[] = "excludeResponseHeaders";
+const char kExpiresKey[] = "expires";
+const char kFilterKey[] ="filter";
+const char kFirstPartyForCookiesUrlKey[] = "firstPartyForCookiesUrl";
+const char kFromKey[] = "from";
+const char kHttpOnlyKey[] = "httpOnly";
+const char kHasTagKey[] = "hasTag";
+const char kInstanceTypeKey[] = "instanceType";
+const char kLowerPriorityThanKey[] = "lowerPriorityThan";
+const char kMaxAgeKey[] = "maxAge";
+const char kMessageKey[] = "message";
+const char kModificationKey[] = "modification";
+const char kNameContainsKey[] = "nameContains";
+const char kNameEqualsKey[] = "nameEquals";
+const char kNameKey[] = "name";
+const char kNamePrefixKey[] = "namePrefix";
+const char kNameSuffixKey[] = "nameSuffix";
+const char kPathKey[] = "path";
+const char kRedirectUrlKey[] = "redirectUrl";
+const char kRequestHeadersKey[] = "requestHeaders";
+const char kResourceTypeKey[] = "resourceType";
+const char kResponseHeadersKey[] = "responseHeaders";
+const char kSecureKey[] = "secure";
+const char kSessionCookieKey[] = "sessionCookie";
+const char kStagesKey[] = "stages";
+const char kThirdPartyKey[] = "thirdPartyForCookies";
+const char kToKey[] = "to";
+const char kUrlKey[] = "url";
+const char kValueContainsKey[] = "valueContains";
+const char kValueEqualsKey[] = "valueEquals";
+const char kValueKey[] = "value";
+const char kValuePrefixKey[] = "valuePrefix";
+const char kValueSuffixKey[] = "valueSuffix";
+
+// Enum string values
+const char kOnBeforeRequestEnum[] = "onBeforeRequest";
+const char kOnBeforeSendHeadersEnum[] = "onBeforeSendHeaders";
+const char kOnHeadersReceivedEnum[] = "onHeadersReceived";
+const char kOnAuthRequiredEnum[] = "onAuthRequired";
+
+// Values of dictionaries, in particular instance types
+const char kAddRequestCookieType[] = "declarativeWebRequest.AddRequestCookie";
+const char kAddResponseCookieType[] = "declarativeWebRequest.AddResponseCookie";
+const char kAddResponseHeaderType[] = "declarativeWebRequest.AddResponseHeader";
+const char kCancelRequestType[] = "declarativeWebRequest.CancelRequest";
+const char kEditRequestCookieType[] = "declarativeWebRequest.EditRequestCookie";
+const char kEditResponseCookieType[] =
+ "declarativeWebRequest.EditResponseCookie";
+const char kIgnoreRulesType[] = "declarativeWebRequest.IgnoreRules";
+const char kRedirectRequestType[] = "declarativeWebRequest.RedirectRequest";
+const char kRedirectByRegExType[] =
+ "declarativeWebRequest.RedirectByRegEx";
+const char kRedirectToEmptyDocumentType[] =
+ "declarativeWebRequest.RedirectToEmptyDocument";
+const char kRedirectToTransparentImageType[] =
+ "declarativeWebRequest.RedirectToTransparentImage";
+const char kRemoveRequestCookieType[] =
+ "declarativeWebRequest.RemoveRequestCookie";
+const char kRemoveRequestHeaderType[] =
+ "declarativeWebRequest.RemoveRequestHeader";
+const char kRemoveResponseCookieType[] =
+ "declarativeWebRequest.RemoveResponseCookie";
+const char kRemoveResponseHeaderType[] =
+ "declarativeWebRequest.RemoveResponseHeader";
+const char kRequestMatcherType[] = "declarativeWebRequest.RequestMatcher";
+const char kSendMessageToExtensionType[] =
+ "declarativeWebRequest.SendMessageToExtension";
+const char kSetRequestHeaderType[] = "declarativeWebRequest.SetRequestHeader";
+
+} // namespace declarative_webrequest_constants
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/declarative_webrequest/webrequest_constants.h b/chromium/extensions/browser/api/declarative_webrequest/webrequest_constants.h
new file mode 100644
index 00000000000..322ccc639d1
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative_webrequest/webrequest_constants.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Constants used for the WebRequest API.
+
+#ifndef EXTENSIONS_BROWSER_API_DECLARATIVE_WEBREQUEST_WEBREQUEST_CONSTANTS_H_
+#define EXTENSIONS_BROWSER_API_DECLARATIVE_WEBREQUEST_WEBREQUEST_CONSTANTS_H_
+
+namespace extensions {
+namespace declarative_webrequest_constants {
+
+// Signals to which WebRequestRulesRegistries are registered or listeners can
+// be registered.
+extern const char kOnRequest[];
+extern const char kOnMessage[];
+
+// Keys of dictionaries.
+extern const char kAgeLowerBoundKey[];
+extern const char kAgeUpperBoundKey[];
+extern const char kCookieKey[];
+extern const char kContentTypeKey[];
+extern const char kDomainKey[];
+extern const char kExcludeContentTypeKey[];
+extern const char kExcludeRequestHeadersKey[];
+extern const char kExcludeResponseHeadersKey[];
+extern const char kExpiresKey[];
+extern const char kFilterKey[];
+extern const char kFirstPartyForCookiesUrlKey[];
+extern const char kFromKey[];
+extern const char kHttpOnlyKey[];
+extern const char kHasTagKey[];
+extern const char kInstanceTypeKey[];
+extern const char kLowerPriorityThanKey[];
+extern const char kMaxAgeKey[];
+extern const char kMessageKey[];
+extern const char kModificationKey[];
+extern const char kNameContainsKey[];
+extern const char kNameEqualsKey[];
+extern const char kNameKey[];
+extern const char kNamePrefixKey[];
+extern const char kNameSuffixKey[];
+extern const char kPathKey[];
+extern const char kRedirectUrlKey[];
+extern const char kRequestHeadersKey[];
+extern const char kResourceTypeKey[];
+extern const char kResponseHeadersKey[];
+extern const char kSecureKey[];
+extern const char kSessionCookieKey[];
+extern const char kStagesKey[];
+extern const char kThirdPartyKey[];
+extern const char kToKey[];
+extern const char kUrlKey[];
+extern const char kValueContainsKey[];
+extern const char kValueEqualsKey[];
+extern const char kValueKey[];
+extern const char kValuePrefixKey[];
+extern const char kValueSuffixKey[];
+
+// Enum string values
+extern const char kOnBeforeRequestEnum[];
+extern const char kOnBeforeSendHeadersEnum[];
+extern const char kOnHeadersReceivedEnum[];
+extern const char kOnAuthRequiredEnum[];
+
+// Values of dictionaries, in particular instance types
+extern const char kAddRequestCookieType[];
+extern const char kAddResponseCookieType[];
+extern const char kAddResponseHeaderType[];
+extern const char kCancelRequestType[];
+extern const char kEditRequestCookieType[];
+extern const char kEditResponseCookieType[];
+extern const char kIgnoreRulesType[];
+extern const char kRedirectByRegExType[];
+extern const char kRedirectRequestType[];
+extern const char kRedirectToEmptyDocumentType[];
+extern const char kRedirectToTransparentImageType[];
+extern const char kRemoveRequestCookieType[];
+extern const char kRemoveRequestHeaderType[];
+extern const char kRemoveResponseCookieType[];
+extern const char kRemoveResponseHeaderType[];
+extern const char kRequestMatcherType[];
+extern const char kSendMessageToExtensionType[];
+extern const char kSetRequestHeaderType[];
+
+} // namespace declarative_webrequest_constants
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DECLARATIVE_WEBREQUEST_WEBREQUEST_CONSTANTS_H_
diff --git a/chromium/extensions/browser/api/declarative_webrequest/webrequest_rules_registry.cc b/chromium/extensions/browser/api/declarative_webrequest/webrequest_rules_registry.cc
new file mode 100644
index 00000000000..ef709b28aa2
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative_webrequest/webrequest_rules_registry.cc
@@ -0,0 +1,390 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/declarative_webrequest/webrequest_rules_registry.h"
+
+#include <algorithm>
+#include <limits>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/stl_util.h"
+#include "extensions/browser/api/declarative_webrequest/webrequest_condition.h"
+#include "extensions/browser/api/declarative_webrequest/webrequest_constants.h"
+#include "extensions/browser/api/web_request/web_request_api_helpers.h"
+#include "extensions/browser/api/web_request/web_request_permissions.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "net/url_request/url_request.h"
+
+using url_matcher::URLMatcherConditionSet;
+
+namespace {
+
+const char kActionCannotBeExecuted[] = "The action '*' can never be executed "
+ "because there are is no time in the request life-cycle during which the "
+ "conditions can be checked and the action can possibly be executed.";
+
+const char kAllURLsPermissionNeeded[] =
+ "To execute the action '*', you need to request host permission for all "
+ "hosts.";
+
+} // namespace
+
+namespace extensions {
+
+WebRequestRulesRegistry::WebRequestRulesRegistry(
+ content::BrowserContext* browser_context,
+ RulesCacheDelegate* cache_delegate,
+ int rules_registry_id)
+ : RulesRegistry(browser_context,
+ declarative_webrequest_constants::kOnRequest,
+ content::BrowserThread::IO,
+ cache_delegate,
+ rules_registry_id),
+ browser_context_(browser_context) {
+ if (browser_context_)
+ extension_info_map_ = ExtensionSystem::Get(browser_context_)->info_map();
+}
+
+std::set<const WebRequestRule*> WebRequestRulesRegistry::GetMatches(
+ const WebRequestData& request_data_without_ids) const {
+ RuleSet result;
+
+ WebRequestDataWithMatchIds request_data(&request_data_without_ids);
+ request_data.url_match_ids = url_matcher_.MatchURL(
+ request_data.data->request->url());
+ request_data.first_party_url_match_ids = url_matcher_.MatchURL(
+ request_data.data->request->first_party_for_cookies());
+
+ // 1st phase -- add all rules with some conditions without UrlFilter
+ // attributes.
+ for (const WebRequestRule* rule : rules_with_untriggered_conditions_) {
+ if (rule->conditions().IsFulfilled(-1, request_data))
+ result.insert(rule);
+ }
+
+ // 2nd phase -- add all rules with some conditions triggered by URL matches.
+ AddTriggeredRules(request_data.url_match_ids, request_data, &result);
+ AddTriggeredRules(request_data.first_party_url_match_ids,
+ request_data, &result);
+
+ return result;
+}
+
+std::list<LinkedPtrEventResponseDelta> WebRequestRulesRegistry::CreateDeltas(
+ const InfoMap* extension_info_map,
+ const WebRequestData& request_data,
+ bool crosses_incognito) {
+ if (webrequest_rules_.empty())
+ return std::list<LinkedPtrEventResponseDelta>();
+
+ std::set<const WebRequestRule*> matches = GetMatches(request_data);
+
+ // Sort all matching rules by their priority so that they can be processed
+ // in decreasing order.
+ typedef std::pair<WebRequestRule::Priority, WebRequestRule::GlobalRuleId>
+ PriorityRuleIdPair;
+ std::vector<PriorityRuleIdPair> ordered_matches;
+ ordered_matches.reserve(matches.size());
+ for (const WebRequestRule* rule : matches)
+ ordered_matches.push_back(make_pair(rule->priority(), rule->id()));
+ // Sort from rbegin to rend in order to get descending priority order.
+ std::sort(ordered_matches.rbegin(), ordered_matches.rend());
+
+ // Build a map that maps each extension id to the minimum required priority
+ // for rules of that extension. Initially, this priority is -infinite and
+ // will be increased when the rules are processed and raise the bar via
+ // WebRequestIgnoreRulesActions.
+ typedef std::string ExtensionId;
+ typedef std::map<ExtensionId, WebRequestRule::Priority> MinPriorities;
+ typedef std::map<ExtensionId, std::set<std::string> > IgnoreTags;
+ MinPriorities min_priorities;
+ IgnoreTags ignore_tags;
+ for (const PriorityRuleIdPair& priority_rule_id_pair : ordered_matches) {
+ const WebRequestRule::GlobalRuleId& rule_id = priority_rule_id_pair.second;
+ const ExtensionId& extension_id = rule_id.first;
+ min_priorities[extension_id] = std::numeric_limits<int>::min();
+ }
+
+ // Create deltas until we have passed the minimum priority.
+ std::list<LinkedPtrEventResponseDelta> result;
+ for (const PriorityRuleIdPair& priority_rule_id_pair : ordered_matches) {
+ const WebRequestRule::Priority priority_of_rule =
+ priority_rule_id_pair.first;
+ const WebRequestRule::GlobalRuleId& rule_id = priority_rule_id_pair.second;
+ const ExtensionId& extension_id = rule_id.first;
+ const WebRequestRule* rule =
+ webrequest_rules_[rule_id.first][rule_id.second].get();
+ CHECK(rule);
+
+ // Skip rule if a previous rule of this extension instructed to ignore
+ // all rules with a lower priority than min_priorities[extension_id].
+ int current_min_priority = min_priorities[extension_id];
+ if (priority_of_rule < current_min_priority)
+ continue;
+
+ if (!rule->tags().empty() && !ignore_tags[extension_id].empty()) {
+ bool ignore_rule = false;
+ for (const std::string& tag : rule->tags())
+ ignore_rule |= ContainsKey(ignore_tags[extension_id], tag);
+ if (ignore_rule)
+ continue;
+ }
+
+ std::list<LinkedPtrEventResponseDelta> rule_result;
+ WebRequestAction::ApplyInfo apply_info = {
+ extension_info_map, request_data, crosses_incognito, &rule_result,
+ &ignore_tags[extension_id]
+ };
+ rule->Apply(&apply_info);
+ result.splice(result.begin(), rule_result);
+
+ min_priorities[extension_id] = std::max(current_min_priority,
+ rule->GetMinimumPriority());
+ }
+ return result;
+}
+
+std::string WebRequestRulesRegistry::AddRulesImpl(
+ const std::string& extension_id,
+ const std::vector<linked_ptr<api::events::Rule>>& rules) {
+ typedef std::pair<WebRequestRule::RuleId, linked_ptr<const WebRequestRule>>
+ IdRulePair;
+ typedef std::vector<IdRulePair> RulesVector;
+
+ base::Time extension_installation_time =
+ GetExtensionInstallationTime(extension_id);
+
+ std::string error;
+ RulesVector new_webrequest_rules;
+ new_webrequest_rules.reserve(rules.size());
+ const Extension* extension =
+ extension_info_map_->extensions().GetByID(extension_id);
+ RulesMap& registered_rules = webrequest_rules_[extension_id];
+
+ for (const linked_ptr<api::events::Rule>& rule : rules) {
+ const WebRequestRule::RuleId& rule_id(*rule->id);
+ DCHECK(registered_rules.find(rule_id) == registered_rules.end());
+
+ scoped_ptr<WebRequestRule> webrequest_rule(WebRequestRule::Create(
+ url_matcher_.condition_factory(), browser_context(), extension,
+ extension_installation_time, rule,
+ base::Bind(&Checker, base::Unretained(extension)), &error));
+ if (!error.empty()) {
+ // We don't return here, because we want to clear temporary
+ // condition sets in the url_matcher_.
+ break;
+ }
+
+ new_webrequest_rules.push_back(
+ IdRulePair(rule_id, make_linked_ptr(webrequest_rule.release())));
+ }
+
+ if (!error.empty()) {
+ // Clean up temporary condition sets created during rule creation.
+ url_matcher_.ClearUnusedConditionSets();
+ return error;
+ }
+
+ // Wohoo, everything worked fine.
+ registered_rules.insert(new_webrequest_rules.begin(),
+ new_webrequest_rules.end());
+
+ // Create the triggers.
+ for (const IdRulePair& id_rule_pair : new_webrequest_rules) {
+ URLMatcherConditionSet::Vector url_condition_sets;
+ const linked_ptr<const WebRequestRule>& rule = id_rule_pair.second;
+ rule->conditions().GetURLMatcherConditionSets(&url_condition_sets);
+ for (const scoped_refptr<URLMatcherConditionSet>& condition_set :
+ url_condition_sets) {
+ rule_triggers_[condition_set->id()] = rule.get();
+ }
+ }
+
+ // Register url patterns in |url_matcher_| and
+ // |rules_with_untriggered_conditions_|.
+ URLMatcherConditionSet::Vector all_new_condition_sets;
+ for (const IdRulePair& id_rule_pair : new_webrequest_rules) {
+ const linked_ptr<const WebRequestRule>& rule = id_rule_pair.second;
+ rule->conditions().GetURLMatcherConditionSets(&all_new_condition_sets);
+ if (rule->conditions().HasConditionsWithoutUrls())
+ rules_with_untriggered_conditions_.insert(rule.get());
+ }
+ url_matcher_.AddConditionSets(all_new_condition_sets);
+
+ ClearCacheOnNavigation();
+
+ if (browser_context_ && !registered_rules.empty()) {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI, FROM_HERE,
+ base::Bind(&extension_web_request_api_helpers::NotifyWebRequestAPIUsed,
+ browser_context_, extension->id()));
+ }
+
+ return std::string();
+}
+
+std::string WebRequestRulesRegistry::RemoveRulesImpl(
+ const std::string& extension_id,
+ const std::vector<std::string>& rule_identifiers) {
+ // URLMatcherConditionSet IDs that can be removed from URLMatcher.
+ std::vector<URLMatcherConditionSet::ID> remove_from_url_matcher;
+ RulesMap& registered_rules = webrequest_rules_[extension_id];
+
+ for (const std::string& identifier : rule_identifiers) {
+ // Skip unknown rules.
+ RulesMap::iterator webrequest_rules_entry =
+ registered_rules.find(identifier);
+ if (webrequest_rules_entry == registered_rules.end())
+ continue;
+
+ // Remove all triggers but collect their IDs.
+ CleanUpAfterRule(webrequest_rules_entry->second.get(),
+ &remove_from_url_matcher);
+
+ // Removes the owning references to (and thus deletes) the rule.
+ registered_rules.erase(webrequest_rules_entry);
+ }
+ if (registered_rules.empty())
+ webrequest_rules_.erase(extension_id);
+
+ // Clear URLMatcher based on condition_set_ids that are not needed any more.
+ url_matcher_.RemoveConditionSets(remove_from_url_matcher);
+
+ ClearCacheOnNavigation();
+
+ return std::string();
+}
+
+std::string WebRequestRulesRegistry::RemoveAllRulesImpl(
+ const std::string& extension_id) {
+ // First we get out all URLMatcherConditionSets and remove the rule references
+ // from |rules_with_untriggered_conditions_|.
+ std::vector<URLMatcherConditionSet::ID> remove_from_url_matcher;
+ for (const std::pair<WebRequestRule::RuleId,
+ linked_ptr<const WebRequestRule>>& rule_id_rule_pair :
+ webrequest_rules_[extension_id]) {
+ CleanUpAfterRule(rule_id_rule_pair.second.get(), &remove_from_url_matcher);
+ }
+ url_matcher_.RemoveConditionSets(remove_from_url_matcher);
+
+ webrequest_rules_.erase(extension_id);
+ ClearCacheOnNavigation();
+ return std::string();
+}
+
+void WebRequestRulesRegistry::CleanUpAfterRule(
+ const WebRequestRule* rule,
+ std::vector<URLMatcherConditionSet::ID>* remove_from_url_matcher) {
+ URLMatcherConditionSet::Vector condition_sets;
+ rule->conditions().GetURLMatcherConditionSets(&condition_sets);
+ for (const scoped_refptr<URLMatcherConditionSet>& condition_set :
+ condition_sets) {
+ remove_from_url_matcher->push_back(condition_set->id());
+ rule_triggers_.erase(condition_set->id());
+ }
+ rules_with_untriggered_conditions_.erase(rule);
+}
+
+bool WebRequestRulesRegistry::IsEmpty() const {
+ // Easy first.
+ if (!rule_triggers_.empty() && url_matcher_.IsEmpty())
+ return false;
+
+ // Now all the registered rules for each extensions.
+ for (const std::pair<WebRequestRule::ExtensionId, RulesMap>&
+ extension_id_rules_map_pair : webrequest_rules_) {
+ if (!extension_id_rules_map_pair.second.empty())
+ return false;
+ }
+ return true;
+}
+
+WebRequestRulesRegistry::~WebRequestRulesRegistry() {}
+
+base::Time WebRequestRulesRegistry::GetExtensionInstallationTime(
+ const std::string& extension_id) const {
+ return extension_info_map_->GetInstallTime(extension_id);
+}
+
+void WebRequestRulesRegistry::ClearCacheOnNavigation() {
+ extension_web_request_api_helpers::ClearCacheOnNavigation();
+}
+
+// static
+bool WebRequestRulesRegistry::Checker(const Extension* extension,
+ const WebRequestConditionSet* conditions,
+ const WebRequestActionSet* actions,
+ std::string* error) {
+ return (StageChecker(conditions, actions, error) &&
+ HostPermissionsChecker(extension, actions, error));
+}
+
+// static
+bool WebRequestRulesRegistry::HostPermissionsChecker(
+ const Extension* extension,
+ const WebRequestActionSet* actions,
+ std::string* error) {
+ if (extension->permissions_data()->HasEffectiveAccessToAllHosts())
+ return true;
+
+ // Without the permission for all URLs, actions with the STRATEGY_DEFAULT
+ // should not be registered, they would never be able to execute.
+ for (const scoped_refptr<const WebRequestAction>& action :
+ actions->actions()) {
+ if (action->host_permissions_strategy() ==
+ WebRequestAction::STRATEGY_DEFAULT) {
+ *error = ErrorUtils::FormatErrorMessage(kAllURLsPermissionNeeded,
+ action->GetName());
+ return false;
+ }
+ }
+ return true;
+}
+
+// static
+bool WebRequestRulesRegistry::StageChecker(
+ const WebRequestConditionSet* conditions,
+ const WebRequestActionSet* actions,
+ std::string* error) {
+ // Actions and conditions can be checked and executed in specific stages
+ // of each web request. A rule is inconsistent if there is an action that
+ // can only be triggered in stages in which no condition can be evaluated.
+
+ // In which stages there are conditions to evaluate.
+ int condition_stages = 0;
+ for (const linked_ptr<const WebRequestCondition>& condition :
+ conditions->conditions()) {
+ condition_stages |= condition->stages();
+ }
+
+ for (const scoped_refptr<const WebRequestAction>& action :
+ actions->actions()) {
+ // Test the intersection of bit masks, this is intentionally & and not &&.
+ if (action->stages() & condition_stages)
+ continue;
+ // We only get here if no matching condition was found.
+ *error = ErrorUtils::FormatErrorMessage(kActionCannotBeExecuted,
+ action->GetName());
+ return false;
+ }
+ return true;
+}
+void WebRequestRulesRegistry::AddTriggeredRules(
+ const URLMatches& url_matches,
+ const WebRequestCondition::MatchData& request_data,
+ RuleSet* result) const {
+ for (url_matcher::URLMatcherConditionSet::ID url_match : url_matches) {
+ RuleTriggers::const_iterator rule_trigger = rule_triggers_.find(url_match);
+ CHECK(rule_trigger != rule_triggers_.end());
+ if (!ContainsKey(*result, rule_trigger->second) &&
+ rule_trigger->second->conditions().IsFulfilled(url_match, request_data))
+ result->insert(rule_trigger->second);
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/declarative_webrequest/webrequest_rules_registry.h b/chromium/extensions/browser/api/declarative_webrequest/webrequest_rules_registry.h
new file mode 100644
index 00000000000..1bdad0cc64e
--- /dev/null
+++ b/chromium/extensions/browser/api/declarative_webrequest/webrequest_rules_registry.h
@@ -0,0 +1,197 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DECLARATIVE_WEBREQUEST_WEBREQUEST_RULES_REGISTRY_H_
+#define EXTENSIONS_BROWSER_API_DECLARATIVE_WEBREQUEST_WEBREQUEST_RULES_REGISTRY_H_
+
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "components/url_matcher/url_matcher.h"
+#include "extensions/browser/api/declarative/declarative_rule.h"
+#include "extensions/browser/api/declarative/rules_registry.h"
+#include "extensions/browser/api/declarative_webrequest/request_stage.h"
+#include "extensions/browser/api/declarative_webrequest/webrequest_action.h"
+#include "extensions/browser/api/declarative_webrequest/webrequest_condition.h"
+#include "extensions/browser/info_map.h"
+
+class WebRequestPermissions;
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extension_web_request_api_helpers {
+struct EventResponseDelta;
+}
+
+namespace net {
+class URLRequest;
+}
+
+namespace extensions {
+
+class RulesRegistryService;
+
+typedef linked_ptr<extension_web_request_api_helpers::EventResponseDelta>
+ LinkedPtrEventResponseDelta;
+typedef DeclarativeRule<WebRequestCondition, WebRequestAction> WebRequestRule;
+
+// The WebRequestRulesRegistry is responsible for managing
+// the internal representation of rules for the Declarative Web Request API.
+//
+// Here is the high level overview of this functionality:
+//
+// api::events::Rule consists of Conditions and Actions, these are
+// represented as a WebRequestRule with WebRequestConditions and
+// WebRequestRuleActions.
+//
+// WebRequestConditions represent JSON dictionaries as the following:
+// {
+// 'instanceType': 'URLMatcher',
+// 'host_suffix': 'example.com',
+// 'path_prefix': '/query',
+// 'scheme': 'http'
+// }
+//
+// The evaluation of URL related condition attributes (host_suffix, path_prefix)
+// is delegated to a URLMatcher, because this is capable of evaluating many
+// of such URL related condition attributes in parallel.
+//
+// For this, the URLRequestCondition has a URLMatcherConditionSet, which
+// represents the {'host_suffix': 'example.com', 'path_prefix': '/query'} part.
+// We will then ask the URLMatcher, whether a given URL
+// "http://www.example.com/query/" has any matches, and the URLMatcher
+// will respond with the URLMatcherConditionSet::ID. We can map this
+// to the WebRequestRule and check whether also the other conditions (in this
+// example 'scheme': 'http') are fulfilled.
+class WebRequestRulesRegistry : public RulesRegistry {
+ public:
+ // |cache_delegate| can be NULL. In that case it constructs the registry with
+ // storage functionality suspended.
+ WebRequestRulesRegistry(content::BrowserContext* browser_context,
+ RulesCacheDelegate* cache_delegate,
+ int rules_registry_id);
+
+ // TODO(battre): This will become an implementation detail, because we need
+ // a way to also execute the actions of the rules.
+ std::set<const WebRequestRule*> GetMatches(
+ const WebRequestData& request_data_without_ids) const;
+
+ // Returns which modifications should be executed on the network request
+ // according to the rules registered in this registry.
+ std::list<LinkedPtrEventResponseDelta> CreateDeltas(
+ const InfoMap* extension_info_map,
+ const WebRequestData& request_data,
+ bool crosses_incognito);
+
+ // Implementation of RulesRegistry:
+ std::string AddRulesImpl(
+ const std::string& extension_id,
+ const std::vector<linked_ptr<api::events::Rule>>& rules) override;
+ std::string RemoveRulesImpl(
+ const std::string& extension_id,
+ const std::vector<std::string>& rule_identifiers) override;
+ std::string RemoveAllRulesImpl(const std::string& extension_id) override;
+
+ // Returns true if this object retains no allocated data. Only for debugging.
+ bool IsEmpty() const;
+
+ protected:
+ ~WebRequestRulesRegistry() override;
+
+ // Virtual for testing:
+ virtual base::Time GetExtensionInstallationTime(
+ const std::string& extension_id) const;
+ virtual void ClearCacheOnNavigation();
+
+ void SetExtensionInfoMapForTesting(
+ scoped_refptr<InfoMap> extension_info_map) {
+ extension_info_map_ = extension_info_map;
+ }
+
+ const std::set<const WebRequestRule*>&
+ rules_with_untriggered_conditions_for_test() const {
+ return rules_with_untriggered_conditions_;
+ }
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(WebRequestRulesRegistrySimpleTest, StageChecker);
+ FRIEND_TEST_ALL_PREFIXES(WebRequestRulesRegistrySimpleTest,
+ HostPermissionsChecker);
+
+ typedef std::map<url_matcher::URLMatcherConditionSet::ID,
+ const WebRequestRule*> RuleTriggers;
+ typedef std::map<WebRequestRule::RuleId, linked_ptr<const WebRequestRule>>
+ RulesMap;
+ typedef std::set<url_matcher::URLMatcherConditionSet::ID> URLMatches;
+ typedef std::set<const WebRequestRule*> RuleSet;
+
+ // This bundles all consistency checkers. Returns true in case of consistency
+ // and MUST set |error| otherwise.
+ static bool Checker(const Extension* extension,
+ const WebRequestConditionSet* conditions,
+ const WebRequestActionSet* actions,
+ std::string* error);
+
+ // Check that the |extension| has host permissions for all URLs if actions
+ // requiring them are present.
+ static bool HostPermissionsChecker(const Extension* extension,
+ const WebRequestActionSet* actions,
+ std::string* error);
+
+ // Check that every action is applicable in the same request stage as at
+ // least one condition.
+ static bool StageChecker(const WebRequestConditionSet* conditions,
+ const WebRequestActionSet* actions,
+ std::string* error);
+
+ // Helper for RemoveRulesImpl and RemoveAllRulesImpl. Call this before
+ // deleting |rule| from one of the maps in |webrequest_rules_|. It will erase
+ // the rule from |rule_triggers_| and |rules_with_untriggered_conditions_|,
+ // and add every of the rule's URLMatcherConditionSet to
+ // |remove_from_url_matcher|, so that the caller can remove them from the
+ // matcher later.
+ void CleanUpAfterRule(const WebRequestRule* rule,
+ std::vector<url_matcher::URLMatcherConditionSet::ID>*
+ remove_from_url_matcher);
+
+ // This is a helper function to GetMatches. Rules triggered by |url_matches|
+ // get added to |result| if one of their conditions is fulfilled.
+ // |request_data| gets passed to IsFulfilled of the rules' condition sets.
+ void AddTriggeredRules(const URLMatches& url_matches,
+ const WebRequestCondition::MatchData& request_data,
+ RuleSet* result) const;
+
+ // Map that tells us which WebRequestRule may match under the condition that
+ // the URLMatcherConditionSet::ID was returned by the |url_matcher_|.
+ RuleTriggers rule_triggers_;
+
+ // These rules contain condition sets with conditions without URL attributes.
+ // Such conditions are not triggered by URL matcher, so we need to test them
+ // separately.
+ std::set<const WebRequestRule*> rules_with_untriggered_conditions_;
+
+ std::map<WebRequestRule::ExtensionId, RulesMap> webrequest_rules_;
+
+ url_matcher::URLMatcher url_matcher_;
+
+ content::BrowserContext* browser_context_;
+ scoped_refptr<InfoMap> extension_info_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRequestRulesRegistry);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DECLARATIVE_WEBREQUEST_WEBREQUEST_RULES_REGISTRY_H_
diff --git a/chromium/extensions/browser/api/device_permissions_manager.cc b/chromium/extensions/browser/api/device_permissions_manager.cc
new file mode 100644
index 00000000000..0bf8db4f696
--- /dev/null
+++ b/chromium/extensions/browser/api/device_permissions_manager.cc
@@ -0,0 +1,733 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/device_permissions_manager.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/memory/singleton.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_thread.h"
+#include "device/core/device_client.h"
+#include "device/hid/hid_device_info.h"
+#include "device/hid/hid_service.h"
+#include "device/usb/usb_device.h"
+#include "device/usb/usb_ids.h"
+#include "extensions/browser/extension_host.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/common/value_builder.h"
+#include "extensions/strings/grit/extensions_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+using content::BrowserContext;
+using content::BrowserThread;
+using device::HidDeviceInfo;
+using device::HidService;
+using device::UsbDevice;
+using device::UsbService;
+using extensions::APIPermission;
+using extensions::Extension;
+using extensions::ExtensionHost;
+using extensions::ExtensionPrefs;
+
+namespace {
+
+// Preference keys
+
+// The device that the app has permission to access.
+const char kDevices[] = "devices";
+
+// The type of device saved.
+const char kDeviceType[] = "type";
+
+// Type identifier for USB devices.
+const char kDeviceTypeUsb[] = "usb";
+
+// Type identifier for HID devices.
+const char kDeviceTypeHid[] = "hid";
+
+// The vendor ID of the device that the app had permission to access.
+const char kDeviceVendorId[] = "vendor_id";
+
+// The product ID of the device that the app had permission to access.
+const char kDeviceProductId[] = "product_id";
+
+// The serial number of the device that the app has permission to access.
+const char kDeviceSerialNumber[] = "serial_number";
+
+// The manufacturer string read from the device that the app has permission to
+// access.
+const char kDeviceManufacturerString[] = "manufacturer_string";
+
+// The product string read from the device that the app has permission to
+// access.
+const char kDeviceProductString[] = "product_string";
+
+// Serialized timestamp of the last time when the device was opened by the app.
+const char kDeviceLastUsed[] = "last_used_time";
+
+// Converts a DevicePermissionEntry::Type to a string for the prefs file.
+const char* TypeToString(DevicePermissionEntry::Type type) {
+ switch (type) {
+ case DevicePermissionEntry::Type::USB:
+ return kDeviceTypeUsb;
+ case DevicePermissionEntry::Type::HID:
+ return kDeviceTypeHid;
+ }
+ NOTREACHED();
+ return "";
+}
+
+// Persists a DevicePermissionEntry in ExtensionPrefs.
+void SaveDevicePermissionEntry(BrowserContext* context,
+ const std::string& extension_id,
+ scoped_refptr<DevicePermissionEntry> entry) {
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(context);
+ ExtensionPrefs::ScopedListUpdate update(prefs, extension_id, kDevices);
+ base::ListValue* devices = update.Get();
+ if (!devices) {
+ devices = update.Create();
+ }
+
+ scoped_ptr<base::Value> device_entry(entry->ToValue());
+ DCHECK(devices->Find(*device_entry.get()) == devices->end());
+ devices->Append(device_entry.release());
+}
+
+bool MatchesDevicePermissionEntry(const base::DictionaryValue* value,
+ scoped_refptr<DevicePermissionEntry> entry) {
+ std::string type;
+ if (!value->GetStringWithoutPathExpansion(kDeviceType, &type) ||
+ type != TypeToString(entry->type())) {
+ return false;
+ }
+ int vendor_id;
+ if (!value->GetIntegerWithoutPathExpansion(kDeviceVendorId, &vendor_id) ||
+ vendor_id != entry->vendor_id()) {
+ return false;
+ }
+ int product_id;
+ if (!value->GetIntegerWithoutPathExpansion(kDeviceProductId, &product_id) ||
+ product_id != entry->product_id()) {
+ return false;
+ }
+ base::string16 serial_number;
+ if (!value->GetStringWithoutPathExpansion(kDeviceSerialNumber,
+ &serial_number) ||
+ serial_number != entry->serial_number()) {
+ return false;
+ }
+ return true;
+}
+
+// Updates the timestamp stored in ExtensionPrefs for the given
+// DevicePermissionEntry.
+void UpdateDevicePermissionEntry(BrowserContext* context,
+ const std::string& extension_id,
+ scoped_refptr<DevicePermissionEntry> entry) {
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(context);
+ ExtensionPrefs::ScopedListUpdate update(prefs, extension_id, kDevices);
+ base::ListValue* devices = update.Get();
+ if (!devices) {
+ return;
+ }
+
+ for (size_t i = 0; i < devices->GetSize(); ++i) {
+ base::DictionaryValue* dict_value;
+ if (!devices->GetDictionary(i, &dict_value)) {
+ continue;
+ }
+ if (!MatchesDevicePermissionEntry(dict_value, entry)) {
+ continue;
+ }
+ devices->Set(i, entry->ToValue().release());
+ break;
+ }
+}
+
+// Removes the given DevicePermissionEntry from ExtensionPrefs.
+void RemoveDevicePermissionEntry(BrowserContext* context,
+ const std::string& extension_id,
+ scoped_refptr<DevicePermissionEntry> entry) {
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(context);
+ ExtensionPrefs::ScopedListUpdate update(prefs, extension_id, kDevices);
+ base::ListValue* devices = update.Get();
+ if (!devices) {
+ return;
+ }
+
+ for (size_t i = 0; i < devices->GetSize(); ++i) {
+ base::DictionaryValue* dict_value;
+ if (!devices->GetDictionary(i, &dict_value)) {
+ continue;
+ }
+ if (!MatchesDevicePermissionEntry(dict_value, entry)) {
+ continue;
+ }
+ devices->Remove(i, nullptr);
+ break;
+ }
+}
+
+// Clears all DevicePermissionEntries for the app from ExtensionPrefs.
+void ClearDevicePermissionEntries(ExtensionPrefs* prefs,
+ const std::string& extension_id) {
+ prefs->UpdateExtensionPref(extension_id, kDevices, NULL);
+}
+
+scoped_refptr<DevicePermissionEntry> ReadDevicePermissionEntry(
+ const base::DictionaryValue* entry) {
+ int vendor_id;
+ if (!entry->GetIntegerWithoutPathExpansion(kDeviceVendorId, &vendor_id) ||
+ vendor_id < 0 || vendor_id > UINT16_MAX) {
+ return nullptr;
+ }
+
+ int product_id;
+ if (!entry->GetIntegerWithoutPathExpansion(kDeviceProductId, &product_id) ||
+ product_id < 0 || product_id > UINT16_MAX) {
+ return nullptr;
+ }
+
+ base::string16 serial_number;
+ if (!entry->GetStringWithoutPathExpansion(kDeviceSerialNumber,
+ &serial_number)) {
+ return nullptr;
+ }
+
+ base::string16 manufacturer_string;
+ // Ignore failure as this string is optional.
+ entry->GetStringWithoutPathExpansion(kDeviceManufacturerString,
+ &manufacturer_string);
+
+ base::string16 product_string;
+ // Ignore failure as this string is optional.
+ entry->GetStringWithoutPathExpansion(kDeviceProductString, &product_string);
+
+ // If a last used time is not stored in ExtensionPrefs last_used.is_null()
+ // will be true.
+ std::string last_used_str;
+ int64_t last_used_i64 = 0;
+ base::Time last_used;
+ if (entry->GetStringWithoutPathExpansion(kDeviceLastUsed, &last_used_str) &&
+ base::StringToInt64(last_used_str, &last_used_i64)) {
+ last_used = base::Time::FromInternalValue(last_used_i64);
+ }
+
+ std::string type;
+ if (!entry->GetStringWithoutPathExpansion(kDeviceType, &type)) {
+ return nullptr;
+ }
+
+ if (type == kDeviceTypeUsb) {
+ return new DevicePermissionEntry(
+ DevicePermissionEntry::Type::USB, vendor_id, product_id, serial_number,
+ manufacturer_string, product_string, last_used);
+ } else if (type == kDeviceTypeHid) {
+ return new DevicePermissionEntry(
+ DevicePermissionEntry::Type::HID, vendor_id, product_id, serial_number,
+ base::string16(), product_string, last_used);
+ }
+ return nullptr;
+}
+
+// Returns all DevicePermissionEntries for the app.
+std::set<scoped_refptr<DevicePermissionEntry>> GetDevicePermissionEntries(
+ ExtensionPrefs* prefs,
+ const std::string& extension_id) {
+ std::set<scoped_refptr<DevicePermissionEntry>> result;
+ const base::ListValue* devices = NULL;
+ if (!prefs->ReadPrefAsList(extension_id, kDevices, &devices)) {
+ return result;
+ }
+
+ for (const base::Value* entry : *devices) {
+ const base::DictionaryValue* entry_dict;
+ if (entry->GetAsDictionary(&entry_dict)) {
+ scoped_refptr<DevicePermissionEntry> device_entry =
+ ReadDevicePermissionEntry(entry_dict);
+ if (entry_dict) {
+ result.insert(device_entry);
+ }
+ }
+ }
+ return result;
+}
+
+} // namespace
+
+DevicePermissionEntry::DevicePermissionEntry(scoped_refptr<UsbDevice> device)
+ : usb_device_(device),
+ type_(Type::USB),
+ vendor_id_(device->vendor_id()),
+ product_id_(device->product_id()),
+ serial_number_(device->serial_number()),
+ manufacturer_string_(device->manufacturer_string()),
+ product_string_(device->product_string()) {
+}
+
+DevicePermissionEntry::DevicePermissionEntry(
+ scoped_refptr<HidDeviceInfo> device)
+ : hid_device_(device),
+ type_(Type::HID),
+ vendor_id_(device->vendor_id()),
+ product_id_(device->product_id()),
+ serial_number_(base::UTF8ToUTF16(device->serial_number())),
+ product_string_(base::UTF8ToUTF16(device->product_name())) {
+}
+
+DevicePermissionEntry::DevicePermissionEntry(
+ Type type,
+ uint16_t vendor_id,
+ uint16_t product_id,
+ const base::string16& serial_number,
+ const base::string16& manufacturer_string,
+ const base::string16& product_string,
+ const base::Time& last_used)
+ : type_(type),
+ vendor_id_(vendor_id),
+ product_id_(product_id),
+ serial_number_(serial_number),
+ manufacturer_string_(manufacturer_string),
+ product_string_(product_string),
+ last_used_(last_used) {
+}
+
+DevicePermissionEntry::~DevicePermissionEntry() {
+}
+
+bool DevicePermissionEntry::IsPersistent() const {
+ return !serial_number_.empty();
+}
+
+scoped_ptr<base::Value> DevicePermissionEntry::ToValue() const {
+ if (!IsPersistent()) {
+ return nullptr;
+ }
+
+ DCHECK(!serial_number_.empty());
+ scoped_ptr<base::DictionaryValue> entry_dict(
+ DictionaryBuilder()
+ .Set(kDeviceType, TypeToString(type_))
+ .Set(kDeviceVendorId, vendor_id_)
+ .Set(kDeviceProductId, product_id_)
+ .Set(kDeviceSerialNumber, serial_number_)
+ .Build());
+
+ if (!manufacturer_string_.empty()) {
+ entry_dict->SetStringWithoutPathExpansion(kDeviceManufacturerString,
+ manufacturer_string_);
+ }
+ if (!product_string_.empty()) {
+ entry_dict->SetStringWithoutPathExpansion(kDeviceProductString,
+ product_string_);
+ }
+ if (!last_used_.is_null()) {
+ entry_dict->SetStringWithoutPathExpansion(
+ kDeviceLastUsed, base::Int64ToString(last_used_.ToInternalValue()));
+ }
+
+ return std::move(entry_dict);
+}
+
+base::string16 DevicePermissionEntry::GetPermissionMessageString() const {
+ return DevicePermissionsManager::GetPermissionMessage(
+ vendor_id_, product_id_, manufacturer_string_, product_string_,
+ serial_number_, type_ == Type::USB);
+}
+
+DevicePermissions::~DevicePermissions() {
+}
+
+scoped_refptr<DevicePermissionEntry> DevicePermissions::FindUsbDeviceEntry(
+ scoped_refptr<UsbDevice> device) const {
+ const auto& ephemeral_device_entry =
+ ephemeral_usb_devices_.find(device.get());
+ if (ephemeral_device_entry != ephemeral_usb_devices_.end()) {
+ return ephemeral_device_entry->second;
+ }
+
+ if (device->serial_number().empty()) {
+ return nullptr;
+ }
+
+ for (const auto& entry : entries_) {
+ if (entry->IsPersistent() && entry->vendor_id() == device->vendor_id() &&
+ entry->product_id() == device->product_id() &&
+ entry->serial_number() == device->serial_number()) {
+ return entry;
+ }
+ }
+ return nullptr;
+}
+
+scoped_refptr<DevicePermissionEntry> DevicePermissions::FindHidDeviceEntry(
+ scoped_refptr<HidDeviceInfo> device) const {
+ const auto& ephemeral_device_entry =
+ ephemeral_hid_devices_.find(device.get());
+ if (ephemeral_device_entry != ephemeral_hid_devices_.end()) {
+ return ephemeral_device_entry->second;
+ }
+
+ if (device->serial_number().empty()) {
+ return nullptr;
+ }
+
+ base::string16 serial_number = base::UTF8ToUTF16(device->serial_number());
+ for (const auto& entry : entries_) {
+ if (entry->IsPersistent() && entry->vendor_id() == device->vendor_id() &&
+ entry->product_id() == device->product_id() &&
+ entry->serial_number() == serial_number) {
+ return entry;
+ }
+ }
+ return nullptr;
+}
+
+DevicePermissions::DevicePermissions(BrowserContext* context,
+ const std::string& extension_id) {
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(context);
+ entries_ = GetDevicePermissionEntries(prefs, extension_id);
+}
+
+// static
+DevicePermissionsManager* DevicePermissionsManager::Get(
+ BrowserContext* context) {
+ return DevicePermissionsManagerFactory::GetForBrowserContext(context);
+}
+
+// static
+base::string16 DevicePermissionsManager::GetPermissionMessage(
+ uint16_t vendor_id,
+ uint16_t product_id,
+ const base::string16& manufacturer_string,
+ const base::string16& product_string,
+ const base::string16& serial_number,
+ bool always_include_manufacturer) {
+ base::string16 product = product_string;
+ if (product.empty()) {
+ const char* product_name =
+ device::UsbIds::GetProductName(vendor_id, product_id);
+ if (product_name) {
+ product = base::UTF8ToUTF16(product_name);
+ }
+ }
+
+ base::string16 manufacturer = manufacturer_string;
+ if (manufacturer_string.empty()) {
+ const char* vendor_name = device::UsbIds::GetVendorName(vendor_id);
+ if (vendor_name) {
+ manufacturer = base::UTF8ToUTF16(vendor_name);
+ }
+ }
+
+ if (serial_number.empty()) {
+ if (product.empty()) {
+ product = base::ASCIIToUTF16(base::StringPrintf("%04x", product_id));
+ if (manufacturer.empty()) {
+ manufacturer =
+ base::ASCIIToUTF16(base::StringPrintf("%04x", vendor_id));
+ return l10n_util::GetStringFUTF16(
+ IDS_DEVICE_NAME_WITH_UNKNOWN_PRODUCT_UNKNOWN_VENDOR, product,
+ manufacturer);
+ } else {
+ return l10n_util::GetStringFUTF16(
+ IDS_DEVICE_NAME_WITH_UNKNOWN_PRODUCT_VENDOR, product, manufacturer);
+ }
+ } else {
+ if (always_include_manufacturer) {
+ if (manufacturer.empty()) {
+ manufacturer =
+ base::ASCIIToUTF16(base::StringPrintf("%04x", vendor_id));
+ return l10n_util::GetStringFUTF16(
+ IDS_DEVICE_NAME_WITH_PRODUCT_UNKNOWN_VENDOR, product,
+ manufacturer);
+ } else {
+ return l10n_util::GetStringFUTF16(IDS_DEVICE_NAME_WITH_PRODUCT_VENDOR,
+ product, manufacturer);
+ }
+ } else {
+ return product;
+ }
+ }
+ } else {
+ if (product.empty()) {
+ product = base::ASCIIToUTF16(base::StringPrintf("%04x", product_id));
+ if (manufacturer.empty()) {
+ manufacturer =
+ base::ASCIIToUTF16(base::StringPrintf("%04x", vendor_id));
+ return l10n_util::GetStringFUTF16(
+ IDS_DEVICE_NAME_WITH_UNKNOWN_PRODUCT_UNKNOWN_VENDOR_SERIAL, product,
+ manufacturer, serial_number);
+ } else {
+ return l10n_util::GetStringFUTF16(
+ IDS_DEVICE_NAME_WITH_UNKNOWN_PRODUCT_VENDOR_SERIAL, product,
+ manufacturer, serial_number);
+ }
+ } else {
+ if (always_include_manufacturer) {
+ if (manufacturer.empty()) {
+ manufacturer =
+ base::ASCIIToUTF16(base::StringPrintf("%04x", vendor_id));
+ return l10n_util::GetStringFUTF16(
+ IDS_DEVICE_NAME_WITH_PRODUCT_UNKNOWN_VENDOR_SERIAL, product,
+ manufacturer, serial_number);
+ } else {
+ return l10n_util::GetStringFUTF16(
+ IDS_DEVICE_NAME_WITH_PRODUCT_VENDOR_SERIAL, product, manufacturer,
+ serial_number);
+ }
+ } else {
+ return l10n_util::GetStringFUTF16(IDS_DEVICE_NAME_WITH_PRODUCT_SERIAL,
+ product, serial_number);
+ }
+ }
+ }
+}
+
+DevicePermissions* DevicePermissionsManager::GetForExtension(
+ const std::string& extension_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DevicePermissions* device_permissions = GetInternal(extension_id);
+ if (!device_permissions) {
+ device_permissions = new DevicePermissions(context_, extension_id);
+ extension_id_to_device_permissions_[extension_id] = device_permissions;
+ }
+
+ return device_permissions;
+}
+
+std::vector<base::string16>
+DevicePermissionsManager::GetPermissionMessageStrings(
+ const std::string& extension_id) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ std::vector<base::string16> messages;
+ const DevicePermissions* device_permissions = GetInternal(extension_id);
+ if (device_permissions) {
+ for (const scoped_refptr<DevicePermissionEntry>& entry :
+ device_permissions->entries()) {
+ messages.push_back(entry->GetPermissionMessageString());
+ }
+ }
+ return messages;
+}
+
+void DevicePermissionsManager::AllowUsbDevice(const std::string& extension_id,
+ scoped_refptr<UsbDevice> device) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DevicePermissions* device_permissions = GetForExtension(extension_id);
+
+ scoped_refptr<DevicePermissionEntry> device_entry(
+ new DevicePermissionEntry(device));
+
+ if (device_entry->IsPersistent()) {
+ for (const auto& entry : device_permissions->entries()) {
+ if (entry->vendor_id() == device_entry->vendor_id() &&
+ entry->product_id() == device_entry->product_id() &&
+ entry->serial_number() == device_entry->serial_number()) {
+ return;
+ }
+ }
+
+ device_permissions->entries_.insert(device_entry);
+ SaveDevicePermissionEntry(context_, extension_id, device_entry);
+ } else if (!ContainsKey(device_permissions->ephemeral_usb_devices_,
+ device.get())) {
+ // Non-persistent devices cannot be reliably identified when they are
+ // reconnected so such devices are only remembered until disconnect.
+ // Register an observer here so that this set doesn't grow undefinitely.
+ device_permissions->entries_.insert(device_entry);
+ device_permissions->ephemeral_usb_devices_[device.get()] = device_entry;
+
+ // Only start observing when an ephemeral device has been added so that
+ // UsbService is not automatically initialized on profile creation (which it
+ // would be if this call were in the constructor).
+ UsbService* usb_service = device::DeviceClient::Get()->GetUsbService();
+ if (!usb_service_observer_.IsObserving(usb_service)) {
+ usb_service_observer_.Add(usb_service);
+ }
+ }
+}
+
+void DevicePermissionsManager::AllowHidDevice(
+ const std::string& extension_id,
+ scoped_refptr<HidDeviceInfo> device) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DevicePermissions* device_permissions = GetForExtension(extension_id);
+
+ scoped_refptr<DevicePermissionEntry> device_entry(
+ new DevicePermissionEntry(device));
+
+ if (device_entry->IsPersistent()) {
+ for (const auto& entry : device_permissions->entries()) {
+ if (entry->vendor_id() == device_entry->vendor_id() &&
+ entry->product_id() == device_entry->product_id() &&
+ entry->serial_number() == device_entry->serial_number()) {
+ return;
+ }
+ }
+
+ device_permissions->entries_.insert(device_entry);
+ SaveDevicePermissionEntry(context_, extension_id, device_entry);
+ } else if (!ContainsKey(device_permissions->ephemeral_hid_devices_,
+ device.get())) {
+ // Non-persistent devices cannot be reliably identified when they are
+ // reconnected so such devices are only remembered until disconnect.
+ // Register an observer here so that this set doesn't grow undefinitely.
+ device_permissions->entries_.insert(device_entry);
+ device_permissions->ephemeral_hid_devices_[device.get()] = device_entry;
+
+ // Only start observing when an ephemeral device has been added so that
+ // HidService is not automatically initialized on profile creation (which it
+ // would be if this call were in the constructor).
+ HidService* hid_service = device::DeviceClient::Get()->GetHidService();
+ if (!hid_service_observer_.IsObserving(hid_service)) {
+ hid_service_observer_.Add(hid_service);
+ }
+ }
+}
+
+void DevicePermissionsManager::UpdateLastUsed(
+ const std::string& extension_id,
+ scoped_refptr<DevicePermissionEntry> entry) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ entry->set_last_used(base::Time::Now());
+ if (entry->IsPersistent()) {
+ UpdateDevicePermissionEntry(context_, extension_id, entry);
+ }
+}
+
+void DevicePermissionsManager::RemoveEntry(
+ const std::string& extension_id,
+ scoped_refptr<DevicePermissionEntry> entry) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DevicePermissions* device_permissions = GetInternal(extension_id);
+ DCHECK(device_permissions);
+ DCHECK(ContainsKey(device_permissions->entries_, entry));
+ device_permissions->entries_.erase(entry);
+ if (entry->IsPersistent()) {
+ RemoveDevicePermissionEntry(context_, extension_id, entry);
+ } else if (entry->type_ == DevicePermissionEntry::Type::USB) {
+ device_permissions->ephemeral_usb_devices_.erase(entry->usb_device_.get());
+ } else if (entry->type_ == DevicePermissionEntry::Type::HID) {
+ device_permissions->ephemeral_hid_devices_.erase(entry->hid_device_.get());
+ } else {
+ NOTREACHED();
+ }
+}
+
+void DevicePermissionsManager::Clear(const std::string& extension_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ ClearDevicePermissionEntries(ExtensionPrefs::Get(context_), extension_id);
+ DevicePermissions* device_permissions = GetInternal(extension_id);
+ if (device_permissions) {
+ extension_id_to_device_permissions_.erase(extension_id);
+ delete device_permissions;
+ }
+}
+
+DevicePermissionsManager::DevicePermissionsManager(
+ content::BrowserContext* context)
+ : context_(context),
+ usb_service_observer_(this),
+ hid_service_observer_(this) {
+}
+
+DevicePermissionsManager::~DevicePermissionsManager() {
+ for (const auto& map_entry : extension_id_to_device_permissions_) {
+ DevicePermissions* device_permissions = map_entry.second;
+ delete device_permissions;
+ }
+}
+
+DevicePermissions* DevicePermissionsManager::GetInternal(
+ const std::string& extension_id) const {
+ std::map<std::string, DevicePermissions*>::const_iterator it =
+ extension_id_to_device_permissions_.find(extension_id);
+ if (it != extension_id_to_device_permissions_.end()) {
+ return it->second;
+ }
+
+ return NULL;
+}
+
+void DevicePermissionsManager::OnDeviceRemovedCleanup(
+ scoped_refptr<UsbDevice> device) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ for (const auto& map_entry : extension_id_to_device_permissions_) {
+ // An ephemeral device cannot be identified if it is reconnected and so
+ // permission to access it is cleared on disconnect.
+ DevicePermissions* device_permissions = map_entry.second;
+ const auto& device_entry =
+ device_permissions->ephemeral_usb_devices_.find(device.get());
+ if (device_entry != device_permissions->ephemeral_usb_devices_.end()) {
+ device_permissions->entries_.erase(device_entry->second);
+ device_permissions->ephemeral_usb_devices_.erase(device_entry);
+ }
+ }
+}
+
+void DevicePermissionsManager::OnDeviceRemovedCleanup(
+ scoped_refptr<device::HidDeviceInfo> device) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ for (const auto& map_entry : extension_id_to_device_permissions_) {
+ // An ephemeral device cannot be identified if it is reconnected and so
+ // permission to access it is cleared on disconnect.
+ DevicePermissions* device_permissions = map_entry.second;
+ const auto& device_entry =
+ device_permissions->ephemeral_hid_devices_.find(device.get());
+ if (device_entry != device_permissions->ephemeral_hid_devices_.end()) {
+ device_permissions->entries_.erase(device_entry->second);
+ device_permissions->ephemeral_hid_devices_.erase(device_entry);
+ }
+ }
+}
+
+// static
+DevicePermissionsManager* DevicePermissionsManagerFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<DevicePermissionsManager*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+DevicePermissionsManagerFactory*
+DevicePermissionsManagerFactory::GetInstance() {
+ return base::Singleton<DevicePermissionsManagerFactory>::get();
+}
+
+DevicePermissionsManagerFactory::DevicePermissionsManagerFactory()
+ : BrowserContextKeyedServiceFactory(
+ "DevicePermissionsManager",
+ BrowserContextDependencyManager::GetInstance()) {
+}
+
+DevicePermissionsManagerFactory::~DevicePermissionsManagerFactory() {
+}
+
+KeyedService* DevicePermissionsManagerFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ return new DevicePermissionsManager(context);
+}
+
+BrowserContext* DevicePermissionsManagerFactory::GetBrowserContextToUse(
+ BrowserContext* context) const {
+ // Return the original (possibly off-the-record) browser context so that a
+ // separate instance of the DevicePermissionsManager is used in incognito
+ // mode. The parent class's implemenation returns NULL.
+ return context;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/device_permissions_manager.h b/chromium/extensions/browser/api/device_permissions_manager.h
new file mode 100644
index 00000000000..534abca2b4a
--- /dev/null
+++ b/chromium/extensions/browser/api/device_permissions_manager.h
@@ -0,0 +1,230 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_DEVICE_PERMISSION_MANAGER_H_
+#define EXTENSIONS_DEVICE_PERMISSION_MANAGER_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/scoped_observer.h"
+#include "base/strings/string16.h"
+#include "base/threading/thread_checker.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "device/hid/hid_service.h"
+#include "device/usb/usb_service.h"
+
+namespace base {
+template <typename T>
+struct DefaultSingletonTraits;
+class Value;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+// Stores information about a device saved with access granted.
+class DevicePermissionEntry : public base::RefCounted<DevicePermissionEntry> {
+ public:
+ enum class Type {
+ USB,
+ HID,
+ };
+
+ DevicePermissionEntry(scoped_refptr<device::UsbDevice> device);
+ DevicePermissionEntry(scoped_refptr<device::HidDeviceInfo> device);
+ DevicePermissionEntry(Type type,
+ uint16_t vendor_id,
+ uint16_t product_id,
+ const base::string16& serial_number,
+ const base::string16& manufacturer_string,
+ const base::string16& product_string,
+ const base::Time& last_used);
+
+ // A persistent device is one that can be recognized when it is reconnected
+ // and can therefore be remembered persistently by writing information about
+ // it to ExtensionPrefs. Currently this means it has a serial number string.
+ bool IsPersistent() const;
+
+ // Convert the device to a serializable value, returns a null pointer if the
+ // entry is not persistent.
+ scoped_ptr<base::Value> ToValue() const;
+
+ base::string16 GetPermissionMessageString() const;
+
+ Type type() const { return type_; }
+ uint16_t vendor_id() const { return vendor_id_; }
+ uint16_t product_id() const { return product_id_; }
+ const base::string16& serial_number() const { return serial_number_; }
+ const base::Time& last_used() const { return last_used_; }
+
+ base::string16 GetManufacturer() const;
+ base::string16 GetProduct() const;
+
+ private:
+ friend class base::RefCounted<DevicePermissionEntry>;
+ friend class DevicePermissionsManager;
+
+ ~DevicePermissionEntry();
+
+ void set_last_used(const base::Time& last_used) { last_used_ = last_used; }
+
+ // The USB device tracked by this entry. Will be nullptr if this entry was
+ // restored from ExtensionPrefs or type_ is not Type::USB.
+ scoped_refptr<device::UsbDevice> usb_device_;
+ // The HID device tracked by this entry. Will be nullptr if this entry was
+ // restored from ExtensionPrefs or type_ is not Type::HID.
+ scoped_refptr<device::HidDeviceInfo> hid_device_;
+
+ // The type of device this entry represents.
+ Type type_;
+ // The vendor ID of this device.
+ uint16_t vendor_id_;
+ // The product ID of this device.
+ uint16_t product_id_;
+ // The serial number (possibly alphanumeric) of this device.
+ base::string16 serial_number_;
+ // The manufacturer string read from the device (optional).
+ base::string16 manufacturer_string_;
+ // The product string read from the device (optional).
+ base::string16 product_string_;
+ // The last time this device was used by the extension.
+ base::Time last_used_;
+};
+
+// Stores device permissions associated with a particular extension.
+class DevicePermissions {
+ public:
+ virtual ~DevicePermissions();
+
+ // Attempts to find a permission entry matching the given device.
+ scoped_refptr<DevicePermissionEntry> FindUsbDeviceEntry(
+ scoped_refptr<device::UsbDevice> device) const;
+ scoped_refptr<DevicePermissionEntry> FindHidDeviceEntry(
+ scoped_refptr<device::HidDeviceInfo> device) const;
+
+ const std::set<scoped_refptr<DevicePermissionEntry>>& entries() const {
+ return entries_;
+ }
+
+ private:
+ friend class DevicePermissionsManager;
+
+ // Reads permissions out of ExtensionPrefs.
+ DevicePermissions(content::BrowserContext* context,
+ const std::string& extension_id);
+
+ std::set<scoped_refptr<DevicePermissionEntry>> entries_;
+ std::map<device::UsbDevice*, scoped_refptr<DevicePermissionEntry>>
+ ephemeral_usb_devices_;
+ std::map<device::HidDeviceInfo*, scoped_refptr<DevicePermissionEntry>>
+ ephemeral_hid_devices_;
+
+ DISALLOW_COPY_AND_ASSIGN(DevicePermissions);
+};
+
+// Manages saved device permissions for all extensions.
+class DevicePermissionsManager : public KeyedService,
+ public device::UsbService::Observer,
+ public device::HidService::Observer {
+ public:
+ static DevicePermissionsManager* Get(content::BrowserContext* context);
+
+ static base::string16 GetPermissionMessage(
+ uint16_t vendor_id,
+ uint16_t product_id,
+ const base::string16& manufacturer_string,
+ const base::string16& product_string,
+ const base::string16& serial_number,
+ bool always_include_manufacturer);
+
+ // The DevicePermissions object for a given extension.
+ DevicePermissions* GetForExtension(const std::string& extension_id);
+
+ // Equivalent to calling GetForExtension and extracting the permission string
+ // for each entry.
+ std::vector<base::string16> GetPermissionMessageStrings(
+ const std::string& extension_id) const;
+
+ void AllowUsbDevice(const std::string& extension_id,
+ scoped_refptr<device::UsbDevice> device);
+ void AllowHidDevice(const std::string& extension_id,
+ scoped_refptr<device::HidDeviceInfo> device);
+
+ // Updates the "last used" timestamp on the given device entry and writes it
+ // out to ExtensionPrefs.
+ void UpdateLastUsed(const std::string& extension_id,
+ scoped_refptr<DevicePermissionEntry> entry);
+
+ // Revokes permission for the extension to access the given device.
+ void RemoveEntry(const std::string& extension_id,
+ scoped_refptr<DevicePermissionEntry> entry);
+
+ // Revokes permission for the extension to access all allowed devices.
+ void Clear(const std::string& extension_id);
+
+ private:
+
+ friend class DevicePermissionsManagerFactory;
+ FRIEND_TEST_ALL_PREFIXES(DevicePermissionsManagerTest, SuspendExtension);
+
+ DevicePermissionsManager(content::BrowserContext* context);
+ ~DevicePermissionsManager() override;
+
+ DevicePermissions* GetInternal(const std::string& extension_id) const;
+
+ // UsbService::Observer implementation
+ void OnDeviceRemovedCleanup(scoped_refptr<device::UsbDevice> device) override;
+
+ // HidService::Observer implementation
+ void OnDeviceRemovedCleanup(
+ scoped_refptr<device::HidDeviceInfo> device) override;
+
+ base::ThreadChecker thread_checker_;
+ content::BrowserContext* context_;
+ std::map<std::string, DevicePermissions*> extension_id_to_device_permissions_;
+ ScopedObserver<device::UsbService, device::UsbService::Observer>
+ usb_service_observer_;
+ ScopedObserver<device::HidService, device::HidService::Observer>
+ hid_service_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(DevicePermissionsManager);
+};
+
+class DevicePermissionsManagerFactory
+ : public BrowserContextKeyedServiceFactory {
+ public:
+ static DevicePermissionsManager* GetForBrowserContext(
+ content::BrowserContext* context);
+ static DevicePermissionsManagerFactory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<DevicePermissionsManagerFactory>;
+
+ DevicePermissionsManagerFactory();
+ ~DevicePermissionsManagerFactory() override;
+
+ // BrowserContextKeyedServiceFactory implementation
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(DevicePermissionsManagerFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_DEVICE_PERMISSION_MANAGER_H_
diff --git a/chromium/extensions/browser/api/device_permissions_prompt.cc b/chromium/extensions/browser/api/device_permissions_prompt.cc
new file mode 100644
index 00000000000..e29ae85d97e
--- /dev/null
+++ b/chromium/extensions/browser/api/device_permissions_prompt.cc
@@ -0,0 +1,404 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/device_permissions_prompt.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/i18n/message_formatter.h"
+#include "base/scoped_observer.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "device/core/device_client.h"
+#include "device/hid/hid_device_filter.h"
+#include "device/hid/hid_device_info.h"
+#include "device/hid/hid_service.h"
+#include "device/usb/usb_device.h"
+#include "device/usb/usb_device_filter.h"
+#include "device/usb/usb_ids.h"
+#include "device/usb/usb_service.h"
+#include "extensions/browser/api/device_permissions_manager.h"
+#include "extensions/common/extension.h"
+#include "extensions/strings/grit/extensions_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/permission_broker_client.h"
+#include "device/hid/hid_device_info_linux.h"
+#endif // defined(OS_CHROMEOS)
+
+using device::HidDeviceFilter;
+using device::HidService;
+using device::UsbDevice;
+using device::UsbDeviceFilter;
+using device::UsbService;
+
+namespace extensions {
+
+namespace {
+
+void NoopHidCallback(const std::vector<scoped_refptr<device::HidDeviceInfo>>&) {
+}
+
+void NoopUsbCallback(const std::vector<scoped_refptr<device::UsbDevice>>&) {}
+
+class UsbDeviceInfo : public DevicePermissionsPrompt::Prompt::DeviceInfo {
+ public:
+ UsbDeviceInfo(scoped_refptr<UsbDevice> device) : device_(device) {
+ name_ = DevicePermissionsManager::GetPermissionMessage(
+ device->vendor_id(), device->product_id(),
+ device->manufacturer_string(), device->product_string(),
+ base::string16(), // Serial number is displayed separately.
+ true);
+ serial_number_ = device->serial_number();
+ }
+
+ ~UsbDeviceInfo() override {}
+
+ const scoped_refptr<UsbDevice>& device() const { return device_; }
+
+ private:
+ // TODO(reillyg): Convert this to a weak reference when UsbDevice has a
+ // connected flag.
+ scoped_refptr<UsbDevice> device_;
+};
+
+class UsbDevicePermissionsPrompt : public DevicePermissionsPrompt::Prompt,
+ public device::UsbService::Observer {
+ public:
+ UsbDevicePermissionsPrompt(
+ const Extension* extension,
+ content::BrowserContext* context,
+ bool multiple,
+ const std::vector<UsbDeviceFilter>& filters,
+ const DevicePermissionsPrompt::UsbDevicesCallback& callback)
+ : Prompt(extension, context, multiple),
+ filters_(filters),
+ callback_(callback),
+ service_observer_(this) {}
+
+ private:
+ ~UsbDevicePermissionsPrompt() override {}
+
+ // DevicePermissionsPrompt::Prompt implementation:
+ void SetObserver(
+ DevicePermissionsPrompt::Prompt::Observer* observer) override {
+ DevicePermissionsPrompt::Prompt::SetObserver(observer);
+
+ if (observer) {
+ UsbService* service = device::DeviceClient::Get()->GetUsbService();
+ if (service && !service_observer_.IsObserving(service)) {
+ service->GetDevices(
+ base::Bind(&UsbDevicePermissionsPrompt::OnDevicesEnumerated, this));
+ service_observer_.Add(service);
+ }
+ }
+ }
+
+ base::string16 GetHeading() const override {
+ return l10n_util::GetSingleOrMultipleStringUTF16(
+ IDS_USB_DEVICE_PERMISSIONS_PROMPT_TITLE, multiple());
+ }
+
+ void Dismissed() override {
+ DevicePermissionsManager* permissions_manager =
+ DevicePermissionsManager::Get(browser_context());
+ std::vector<scoped_refptr<UsbDevice>> devices;
+ for (const auto& device : devices_) {
+ if (device->granted()) {
+ const UsbDeviceInfo* usb_device =
+ static_cast<const UsbDeviceInfo*>(device.get());
+ devices.push_back(usb_device->device());
+ if (permissions_manager) {
+ permissions_manager->AllowUsbDevice(extension()->id(),
+ usb_device->device());
+ }
+ }
+ }
+ DCHECK(multiple() || devices.size() <= 1);
+ callback_.Run(devices);
+ callback_.Reset();
+ }
+
+ // device::UsbService::Observer implementation:
+ void OnDeviceAdded(scoped_refptr<UsbDevice> device) override {
+ if (!(filters_.empty() || UsbDeviceFilter::MatchesAny(device, filters_))) {
+ return;
+ }
+
+ scoped_ptr<DeviceInfo> device_info(new UsbDeviceInfo(device));
+ device->CheckUsbAccess(
+ base::Bind(&UsbDevicePermissionsPrompt::AddCheckedDevice, this,
+ base::Passed(&device_info)));
+ }
+
+ void OnDeviceRemoved(scoped_refptr<UsbDevice> device) override {
+ for (auto it = devices_.begin(); it != devices_.end(); ++it) {
+ const UsbDeviceInfo* entry =
+ static_cast<const UsbDeviceInfo*>((*it).get());
+ if (entry->device() == device) {
+ devices_.erase(it);
+ if (observer()) {
+ observer()->OnDevicesChanged();
+ }
+ return;
+ }
+ }
+ }
+
+ void OnDevicesEnumerated(
+ const std::vector<scoped_refptr<UsbDevice>>& devices) {
+ for (const auto& device : devices) {
+ OnDeviceAdded(device);
+ }
+ }
+
+ std::vector<UsbDeviceFilter> filters_;
+ DevicePermissionsPrompt::UsbDevicesCallback callback_;
+ ScopedObserver<UsbService, UsbService::Observer> service_observer_;
+};
+
+class HidDeviceInfo : public DevicePermissionsPrompt::Prompt::DeviceInfo {
+ public:
+ HidDeviceInfo(scoped_refptr<device::HidDeviceInfo> device) : device_(device) {
+ name_ = DevicePermissionsManager::GetPermissionMessage(
+ device->vendor_id(), device->product_id(),
+ base::string16(), // HID devices include manufacturer in product name.
+ base::UTF8ToUTF16(device->product_name()),
+ base::string16(), // Serial number is displayed separately.
+ false);
+ serial_number_ = base::UTF8ToUTF16(device->serial_number());
+ }
+
+ ~HidDeviceInfo() override {}
+
+ const scoped_refptr<device::HidDeviceInfo>& device() const { return device_; }
+
+ private:
+ scoped_refptr<device::HidDeviceInfo> device_;
+};
+
+class HidDevicePermissionsPrompt : public DevicePermissionsPrompt::Prompt,
+ public device::HidService::Observer {
+ public:
+ HidDevicePermissionsPrompt(
+ const Extension* extension,
+ content::BrowserContext* context,
+ bool multiple,
+ const std::vector<HidDeviceFilter>& filters,
+ const DevicePermissionsPrompt::HidDevicesCallback& callback)
+ : Prompt(extension, context, multiple),
+ filters_(filters),
+ callback_(callback),
+ service_observer_(this) {}
+
+ private:
+ ~HidDevicePermissionsPrompt() override {}
+
+ // DevicePermissionsPrompt::Prompt implementation:
+ void SetObserver(
+ DevicePermissionsPrompt::Prompt::Observer* observer) override {
+ DevicePermissionsPrompt::Prompt::SetObserver(observer);
+
+ if (observer) {
+ HidService* service = device::DeviceClient::Get()->GetHidService();
+ if (service && !service_observer_.IsObserving(service)) {
+ service->GetDevices(
+ base::Bind(&HidDevicePermissionsPrompt::OnDevicesEnumerated, this));
+ service_observer_.Add(service);
+ }
+ }
+ }
+
+ base::string16 GetHeading() const override {
+ return l10n_util::GetSingleOrMultipleStringUTF16(
+ IDS_HID_DEVICE_PERMISSIONS_PROMPT_TITLE, multiple());
+ }
+
+ void Dismissed() override {
+ DevicePermissionsManager* permissions_manager =
+ DevicePermissionsManager::Get(browser_context());
+ std::vector<scoped_refptr<device::HidDeviceInfo>> devices;
+ for (const auto& device : devices_) {
+ if (device->granted()) {
+ const HidDeviceInfo* hid_device =
+ static_cast<const HidDeviceInfo*>(device.get());
+ devices.push_back(hid_device->device());
+ if (permissions_manager) {
+ permissions_manager->AllowHidDevice(extension()->id(),
+ hid_device->device());
+ }
+ }
+ }
+ DCHECK(multiple() || devices.size() <= 1);
+ callback_.Run(devices);
+ callback_.Reset();
+ }
+
+ // device::HidService::Observer implementation:
+ void OnDeviceAdded(scoped_refptr<device::HidDeviceInfo> device) override {
+ if (HasUnprotectedCollections(device) &&
+ (filters_.empty() || HidDeviceFilter::MatchesAny(device, filters_))) {
+ scoped_ptr<DeviceInfo> device_info(new HidDeviceInfo(device));
+#if defined(OS_CHROMEOS)
+ chromeos::PermissionBrokerClient* client =
+ chromeos::DBusThreadManager::Get()->GetPermissionBrokerClient();
+ DCHECK(client) << "Could not get permission broker client.";
+ device::HidDeviceInfoLinux* linux_device_info =
+ static_cast<device::HidDeviceInfoLinux*>(device.get());
+ client->CheckPathAccess(
+ linux_device_info->device_node(),
+ base::Bind(&HidDevicePermissionsPrompt::AddCheckedDevice, this,
+ base::Passed(&device_info)));
+#else
+ AddCheckedDevice(std::move(device_info), true);
+#endif // defined(OS_CHROMEOS)
+ }
+ }
+
+ void OnDeviceRemoved(scoped_refptr<device::HidDeviceInfo> device) override {
+ for (auto it = devices_.begin(); it != devices_.end(); ++it) {
+ const HidDeviceInfo* entry =
+ static_cast<const HidDeviceInfo*>((*it).get());
+ if (entry->device() == device) {
+ devices_.erase(it);
+ if (observer()) {
+ observer()->OnDevicesChanged();
+ }
+ return;
+ }
+ }
+ }
+
+ void OnDevicesEnumerated(
+ const std::vector<scoped_refptr<device::HidDeviceInfo>>& devices) {
+ for (const auto& device : devices) {
+ OnDeviceAdded(device);
+ }
+ }
+
+ bool HasUnprotectedCollections(scoped_refptr<device::HidDeviceInfo> device) {
+ for (const auto& collection : device->collections()) {
+ if (!collection.usage.IsProtected()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ std::vector<HidDeviceFilter> filters_;
+ DevicePermissionsPrompt::HidDevicesCallback callback_;
+ ScopedObserver<HidService, HidService::Observer> service_observer_;
+};
+
+} // namespace
+
+DevicePermissionsPrompt::Prompt::DeviceInfo::DeviceInfo() {
+}
+
+DevicePermissionsPrompt::Prompt::DeviceInfo::~DeviceInfo() {
+}
+
+DevicePermissionsPrompt::Prompt::Observer::~Observer() {
+}
+
+DevicePermissionsPrompt::Prompt::Prompt(const Extension* extension,
+ content::BrowserContext* context,
+ bool multiple)
+ : extension_(extension), browser_context_(context), multiple_(multiple) {
+}
+
+void DevicePermissionsPrompt::Prompt::SetObserver(Observer* observer) {
+ observer_ = observer;
+}
+
+base::string16 DevicePermissionsPrompt::Prompt::GetPromptMessage() const {
+ return base::i18n::MessageFormatter::FormatWithNumberedArgs(
+ l10n_util::GetStringUTF16(IDS_DEVICE_PERMISSIONS_PROMPT),
+ multiple_ ? "multiple" : "single", extension_->name());
+}
+
+base::string16 DevicePermissionsPrompt::Prompt::GetDeviceName(
+ size_t index) const {
+ DCHECK_LT(index, devices_.size());
+ return devices_[index]->name();
+}
+
+base::string16 DevicePermissionsPrompt::Prompt::GetDeviceSerialNumber(
+ size_t index) const {
+ DCHECK_LT(index, devices_.size());
+ return devices_[index]->serial_number();
+}
+
+void DevicePermissionsPrompt::Prompt::GrantDevicePermission(size_t index) {
+ DCHECK_LT(index, devices_.size());
+ devices_[index]->set_granted();
+}
+
+DevicePermissionsPrompt::Prompt::~Prompt() {
+}
+
+void DevicePermissionsPrompt::Prompt::AddCheckedDevice(
+ scoped_ptr<DeviceInfo> device,
+ bool allowed) {
+ if (allowed) {
+ devices_.push_back(std::move(device));
+ if (observer_) {
+ observer_->OnDevicesChanged();
+ }
+ }
+}
+
+DevicePermissionsPrompt::DevicePermissionsPrompt(
+ content::WebContents* web_contents)
+ : web_contents_(web_contents) {
+}
+
+DevicePermissionsPrompt::~DevicePermissionsPrompt() {
+}
+
+void DevicePermissionsPrompt::AskForUsbDevices(
+ const Extension* extension,
+ content::BrowserContext* context,
+ bool multiple,
+ const std::vector<UsbDeviceFilter>& filters,
+ const UsbDevicesCallback& callback) {
+ prompt_ = new UsbDevicePermissionsPrompt(extension, context, multiple,
+ filters, callback);
+ ShowDialog();
+}
+
+void DevicePermissionsPrompt::AskForHidDevices(
+ const Extension* extension,
+ content::BrowserContext* context,
+ bool multiple,
+ const std::vector<HidDeviceFilter>& filters,
+ const HidDevicesCallback& callback) {
+ prompt_ = new HidDevicePermissionsPrompt(extension, context, multiple,
+ filters, callback);
+ ShowDialog();
+}
+
+// static
+scoped_refptr<DevicePermissionsPrompt::Prompt>
+DevicePermissionsPrompt::CreateHidPromptForTest(const Extension* extension,
+ bool multiple) {
+ return make_scoped_refptr(new HidDevicePermissionsPrompt(
+ extension, nullptr, multiple, std::vector<HidDeviceFilter>(),
+ base::Bind(&NoopHidCallback)));
+}
+
+// static
+scoped_refptr<DevicePermissionsPrompt::Prompt>
+DevicePermissionsPrompt::CreateUsbPromptForTest(const Extension* extension,
+ bool multiple) {
+ return make_scoped_refptr(new UsbDevicePermissionsPrompt(
+ extension, nullptr, multiple, std::vector<UsbDeviceFilter>(),
+ base::Bind(&NoopUsbCallback)));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/device_permissions_prompt.h b/chromium/extensions/browser/api/device_permissions_prompt.h
new file mode 100644
index 00000000000..469ff594d71
--- /dev/null
+++ b/chromium/extensions/browser/api/device_permissions_prompt.h
@@ -0,0 +1,165 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_DEVICE_PERMISSIONS_PROMPT_H_
+#define EXTENSIONS_BROWSER_DEVICE_PERMISSIONS_PROMPT_H_
+
+#include <stddef.h>
+
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+
+namespace content {
+class BrowserContext;
+class WebContents;
+}
+
+namespace device {
+class HidDeviceFilter;
+class HidDeviceInfo;
+class UsbDevice;
+class UsbDeviceFilter;
+}
+
+namespace extensions {
+
+class Extension;
+
+// Platform-independent interface for displaing a UI for choosing devices
+// (similar to choosing files).
+class DevicePermissionsPrompt {
+ public:
+ using UsbDevicesCallback = base::Callback<void(
+ const std::vector<scoped_refptr<device::UsbDevice>>&)>;
+ using HidDevicesCallback = base::Callback<void(
+ const std::vector<scoped_refptr<device::HidDeviceInfo>>&)>;
+
+ // Context information available to the UI implementation.
+ class Prompt : public base::RefCounted<Prompt> {
+ public:
+ // This class stores the device information displayed in the UI. It should
+ // be extended to support particular device types.
+ class DeviceInfo {
+ public:
+ DeviceInfo();
+ virtual ~DeviceInfo();
+
+ const base::string16& name() const { return name_; }
+ const base::string16& serial_number() const { return serial_number_; }
+ bool granted() const { return granted_; }
+ void set_granted() { granted_ = true; }
+
+ protected:
+ base::string16 name_;
+ base::string16 serial_number_;
+
+ private:
+ bool granted_ = false;
+ };
+
+ // Since the set of devices can change while the UI is visible an
+ // implementation should register an observer.
+ class Observer {
+ public:
+ virtual void OnDevicesChanged() = 0;
+
+ protected:
+ virtual ~Observer();
+ };
+
+ Prompt(const Extension* extension,
+ content::BrowserContext* context,
+ bool multiple);
+
+ // Only one observer may be registered at a time.
+ virtual void SetObserver(Observer* observer);
+
+ virtual base::string16 GetHeading() const = 0;
+ base::string16 GetPromptMessage() const;
+ size_t GetDeviceCount() const { return devices_.size(); }
+ base::string16 GetDeviceName(size_t index) const;
+ base::string16 GetDeviceSerialNumber(size_t index) const;
+
+ // Notifies the DevicePermissionsManager for the current extension that
+ // access to the device at the given index is now granted.
+ void GrantDevicePermission(size_t index);
+
+ virtual void Dismissed() = 0;
+
+ // Allow the user to select multiple devices.
+ bool multiple() const { return multiple_; }
+
+ protected:
+ virtual ~Prompt();
+
+ void AddCheckedDevice(scoped_ptr<DeviceInfo> device, bool allowed);
+
+ const Extension* extension() const { return extension_; }
+ Observer* observer() const { return observer_; }
+ content::BrowserContext* browser_context() const {
+ return browser_context_;
+ }
+
+ // Subclasses may fill this with a particular subclass of DeviceInfo and may
+ // assume that only that instances of that type are stored here.
+ std::vector<scoped_ptr<DeviceInfo>> devices_;
+
+ private:
+ friend class base::RefCounted<Prompt>;
+
+ const extensions::Extension* extension_ = nullptr;
+ Observer* observer_ = nullptr;
+ content::BrowserContext* browser_context_ = nullptr;
+ bool multiple_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(Prompt);
+ };
+
+ DevicePermissionsPrompt(content::WebContents* web_contents);
+ virtual ~DevicePermissionsPrompt();
+
+ void AskForUsbDevices(const Extension* extension,
+ content::BrowserContext* context,
+ bool multiple,
+ const std::vector<device::UsbDeviceFilter>& filters,
+ const UsbDevicesCallback& callback);
+
+ void AskForHidDevices(const Extension* extension,
+ content::BrowserContext* context,
+ bool multiple,
+ const std::vector<device::HidDeviceFilter>& filters,
+ const HidDevicesCallback& callback);
+
+ static scoped_refptr<Prompt> CreateHidPromptForTest(
+ const Extension* extension,
+ bool multiple);
+ static scoped_refptr<Prompt> CreateUsbPromptForTest(
+ const Extension* extension,
+ bool multiple);
+
+ protected:
+ virtual void ShowDialog() = 0;
+
+ content::WebContents* web_contents() { return web_contents_; }
+ scoped_refptr<Prompt> prompt() { return prompt_; }
+
+ private:
+ // Parent web contents of the device permissions UI dialog.
+ content::WebContents* web_contents_;
+
+ // Parameters available to the UI implementation.
+ scoped_refptr<Prompt> prompt_;
+
+ DISALLOW_COPY_AND_ASSIGN(DevicePermissionsPrompt);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DEVICE_PERMISSIONS_PROMPT_H_
diff --git a/chromium/extensions/browser/api/device_permissions_prompt_unittest.cc b/chromium/extensions/browser/api/device_permissions_prompt_unittest.cc
new file mode 100644
index 00000000000..ba6a07eaae4
--- /dev/null
+++ b/chromium/extensions/browser/api/device_permissions_prompt_unittest.cc
@@ -0,0 +1,79 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/ref_counted.h"
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/browser/api/device_permissions_prompt.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/value_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+class DevicePermissionsPromptTest : public testing::Test {};
+
+TEST_F(DevicePermissionsPromptTest, HidPromptMessages) {
+ scoped_refptr<Extension> extension =
+ ExtensionBuilder()
+ .SetManifest(DictionaryBuilder()
+ .Set("name", "Test Application")
+ .Set("manifest_version", 2)
+ .Set("version", "1.0")
+ .Build())
+ .Build();
+
+ scoped_refptr<DevicePermissionsPrompt::Prompt> prompt =
+ DevicePermissionsPrompt::CreateHidPromptForTest(extension.get(), false);
+ EXPECT_EQ(base::ASCIIToUTF16("Select a HID device"), prompt->GetHeading());
+ EXPECT_EQ(
+ base::ASCIIToUTF16(
+ "The application \"Test Application\" is requesting access to one of "
+ "your devices."),
+ prompt->GetPromptMessage());
+
+ prompt =
+ DevicePermissionsPrompt::CreateHidPromptForTest(extension.get(), true);
+ EXPECT_EQ(base::ASCIIToUTF16("Select HID devices"), prompt->GetHeading());
+ EXPECT_EQ(
+ base::ASCIIToUTF16(
+ "The application \"Test Application\" is requesting access to one or "
+ "more of your devices."),
+ prompt->GetPromptMessage());
+}
+
+TEST_F(DevicePermissionsPromptTest, UsbPromptMessages) {
+ scoped_refptr<Extension> extension =
+ ExtensionBuilder()
+ .SetManifest(DictionaryBuilder()
+ .Set("name", "Test Application")
+ .Set("manifest_version", 2)
+ .Set("version", "1.0")
+ .Build())
+ .Build();
+
+ scoped_refptr<DevicePermissionsPrompt::Prompt> prompt =
+ DevicePermissionsPrompt::CreateUsbPromptForTest(extension.get(), false);
+ EXPECT_EQ(base::ASCIIToUTF16("Select a USB device"), prompt->GetHeading());
+ EXPECT_EQ(
+ base::ASCIIToUTF16(
+ "The application \"Test Application\" is requesting access to one of "
+ "your devices."),
+ prompt->GetPromptMessage());
+
+ prompt =
+ DevicePermissionsPrompt::CreateUsbPromptForTest(extension.get(), true);
+ EXPECT_EQ(base::ASCIIToUTF16("Select USB devices"), prompt->GetHeading());
+ EXPECT_EQ(
+ base::ASCIIToUTF16(
+ "The application \"Test Application\" is requesting access to one or "
+ "more of your devices."),
+ prompt->GetPromptMessage());
+}
+
+} // namespace
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/diagnostics/diagnostics_api.cc b/chromium/extensions/browser/api/diagnostics/diagnostics_api.cc
new file mode 100644
index 00000000000..668daa8416a
--- /dev/null
+++ b/chromium/extensions/browser/api/diagnostics/diagnostics_api.cc
@@ -0,0 +1,55 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/diagnostics/diagnostics_api.h"
+
+namespace {
+
+const char kErrorPingNotImplemented[] = "Not implemented";
+const char kErrorPingFailed[] = "Failed to send ping packet";
+}
+
+namespace extensions {
+
+namespace SendPacket = api::diagnostics::SendPacket;
+
+DiagnosticsSendPacketFunction::DiagnosticsSendPacketFunction() {
+}
+
+DiagnosticsSendPacketFunction::~DiagnosticsSendPacketFunction() {
+}
+
+bool DiagnosticsSendPacketFunction::Prepare() {
+ parameters_ = SendPacket::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters_.get());
+ return true;
+}
+
+bool DiagnosticsSendPacketFunction::Respond() {
+ return error_.empty();
+}
+
+void DiagnosticsSendPacketFunction::OnCompleted(
+ SendPacketResultCode result_code,
+ const std::string& ip,
+ double latency) {
+ switch (result_code) {
+ case SEND_PACKET_OK: {
+ api::diagnostics::SendPacketResult result;
+ result.ip = ip;
+ result.latency = latency;
+ results_ = SendPacket::Results::Create(result);
+ break;
+ }
+ case SEND_PACKET_NOT_IMPLEMENTED:
+ SetError(kErrorPingNotImplemented);
+ break;
+ case SEND_PACKET_FAILED:
+ SetError(kErrorPingFailed);
+ break;
+ }
+ AsyncWorkCompleted();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/diagnostics/diagnostics_api.h b/chromium/extensions/browser/api/diagnostics/diagnostics_api.h
new file mode 100644
index 00000000000..cf7ce6c13c2
--- /dev/null
+++ b/chromium/extensions/browser/api/diagnostics/diagnostics_api.h
@@ -0,0 +1,55 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DIAGNOSTICS_DIAGNOSTICS_API_H_
+#define EXTENSIONS_BROWSER_API_DIAGNOSTICS_DIAGNOSTICS_API_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "extensions/browser/api/async_api_function.h"
+#include "extensions/common/api/diagnostics.h"
+
+namespace extensions {
+
+class DiagnosticsSendPacketFunction : public AsyncApiFunction {
+ public:
+ // Result code for sending packet. Platform specific AsyncWorkStart() will
+ // finish with this ResultCode so we can maximize shared code.
+ enum SendPacketResultCode {
+ // Ping packed is sent and ICMP reply is received before time out.
+ SEND_PACKET_OK,
+
+ // Not implemented on the platform.
+ SEND_PACKET_NOT_IMPLEMENTED,
+
+ // The ping operation failed because of timeout or network unreachable.
+ SEND_PACKET_FAILED,
+ };
+
+ DECLARE_EXTENSION_FUNCTION("diagnostics.sendPacket", DIAGNOSTICS_SENDPACKET);
+
+ DiagnosticsSendPacketFunction();
+
+ protected:
+ ~DiagnosticsSendPacketFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ // This methods will be implemented differently on different platforms.
+ void AsyncWorkStart() override;
+ bool Respond() override;
+
+ private:
+ void SendPingPacket();
+ void OnCompleted(SendPacketResultCode result_code,
+ const std::string& ip,
+ double latency);
+
+ scoped_ptr<api::diagnostics::SendPacket::Params> parameters_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DIAGNOSTICS_DIAGNOSTICS_API_H_
diff --git a/chromium/extensions/browser/api/diagnostics/diagnostics_api_chromeos.cc b/chromium/extensions/browser/api/diagnostics/diagnostics_api_chromeos.cc
new file mode 100644
index 00000000000..f14b8fb0cbe
--- /dev/null
+++ b/chromium/extensions/browser/api/diagnostics/diagnostics_api_chromeos.cc
@@ -0,0 +1,87 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/diagnostics/diagnostics_api.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/json/json_reader.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/debug_daemon_client.h"
+
+namespace extensions {
+
+namespace {
+
+const char kCount[] = "count";
+const char kDefaultCount[] = "1";
+const char kTTL[] = "ttl";
+const char kTimeout[] = "timeout";
+const char kSize[] = "size";
+
+typedef base::Callback<void(
+ DiagnosticsSendPacketFunction::SendPacketResultCode result_code,
+ const std::string& ip,
+ double latency)> SendPacketCallback;
+
+bool ParseResult(const std::string& status, std::string* ip, double* latency) {
+ // Parses the result and returns IP and latency.
+ scoped_ptr<base::Value> parsed_value(base::JSONReader::Read(status));
+ if (!parsed_value)
+ return false;
+
+ base::DictionaryValue* result = NULL;
+ if (!parsed_value->GetAsDictionary(&result) || result->size() != 1)
+ return false;
+
+ // Returns the first item.
+ base::DictionaryValue::Iterator iterator(*result);
+
+ const base::DictionaryValue* info;
+ if (!iterator.value().GetAsDictionary(&info))
+ return false;
+
+ if (!info->GetDouble("avg", latency))
+ return false;
+
+ *ip = iterator.key();
+ return true;
+}
+
+void OnTestICMPCompleted(const SendPacketCallback& callback,
+ bool succeeded,
+ const std::string& status) {
+ std::string ip;
+ double latency;
+ if (!succeeded || !ParseResult(status, &ip, &latency)) {
+ callback.Run(DiagnosticsSendPacketFunction::SEND_PACKET_FAILED, "", 0.0);
+ } else {
+ callback.Run(DiagnosticsSendPacketFunction::SEND_PACKET_OK, ip, latency);
+ }
+}
+
+} // namespace
+
+void DiagnosticsSendPacketFunction::AsyncWorkStart() {
+ std::map<std::string, std::string> config;
+ config[kCount] = kDefaultCount;
+ if (parameters_->options.ttl)
+ config[kTTL] = base::IntToString(*parameters_->options.ttl);
+ if (parameters_->options.timeout)
+ config[kTimeout] = base::IntToString(*parameters_->options.timeout);
+ if (parameters_->options.size)
+ config[kSize] = base::IntToString(*parameters_->options.size);
+
+ chromeos::DBusThreadManager::Get()
+ ->GetDebugDaemonClient()
+ ->TestICMPWithOptions(
+ parameters_->options.ip, config,
+ base::Bind(
+ OnTestICMPCompleted,
+ base::Bind(&DiagnosticsSendPacketFunction::OnCompleted, this)));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/display_source/OWNERS b/chromium/extensions/browser/api/display_source/OWNERS
new file mode 100644
index 00000000000..f7e4f6f8751
--- /dev/null
+++ b/chromium/extensions/browser/api/display_source/OWNERS
@@ -0,0 +1,2 @@
+alexander.shalamov@intel.com
+mikhail.pozdnyakov@intel.com
diff --git a/chromium/extensions/browser/api/display_source/display_source_api.cc b/chromium/extensions/browser/api/display_source/display_source_api.cc
new file mode 100644
index 00000000000..9b57e6b6e3b
--- /dev/null
+++ b/chromium/extensions/browser/api/display_source/display_source_api.cc
@@ -0,0 +1,100 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/display_source/display_source_api.h"
+
+#include <utility>
+
+#include "extensions/browser/api/display_source/display_source_connection_delegate_factory.h"
+#include "extensions/common/api/display_source.h"
+
+namespace extensions {
+
+namespace {
+
+const char kErrorNotSupported[] = "Not supported";
+const char kErrorInvalidArguments[] = "Invalid arguments";
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// DisplaySourceGetAvailableSinksFunction
+
+DisplaySourceGetAvailableSinksFunction::
+ ~DisplaySourceGetAvailableSinksFunction() {}
+
+ExtensionFunction::ResponseAction
+DisplaySourceGetAvailableSinksFunction::Run() {
+ DisplaySourceConnectionDelegate* delegate =
+ DisplaySourceConnectionDelegateFactory::GetForBrowserContext(
+ browser_context());
+ if (!delegate) {
+ return RespondNow(Error(kErrorNotSupported));
+ }
+
+ auto success_callback = base::Bind(
+ &DisplaySourceGetAvailableSinksFunction::OnGetSinksCompleted, this);
+ auto failure_callback = base::Bind(
+ &DisplaySourceGetAvailableSinksFunction::OnGetSinksFailed, this);
+ delegate->GetAvailableSinks(success_callback, failure_callback);
+
+ return RespondLater();
+}
+
+void DisplaySourceGetAvailableSinksFunction::OnGetSinksCompleted(
+ const DisplaySourceSinkInfoList& sinks) {
+ scoped_ptr<base::ListValue> result =
+ api::display_source::GetAvailableSinks::Results::Create(sinks);
+ Respond(ArgumentList(std::move(result)));
+}
+
+void DisplaySourceGetAvailableSinksFunction::OnGetSinksFailed(
+ const std::string& reason) {
+ Respond(Error(reason));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DisplaySourceRequestAuthenticationFunction
+
+DisplaySourceRequestAuthenticationFunction::
+ ~DisplaySourceRequestAuthenticationFunction() {}
+
+ExtensionFunction::ResponseAction
+DisplaySourceRequestAuthenticationFunction::Run() {
+ scoped_ptr<api::display_source::RequestAuthentication::Params> params(
+ api::display_source::RequestAuthentication::Params::Create(*args_));
+ if (!params) {
+ return RespondNow(Error(kErrorInvalidArguments));
+ }
+
+ DisplaySourceConnectionDelegate* delegate =
+ DisplaySourceConnectionDelegateFactory::GetForBrowserContext(
+ browser_context());
+ if (!delegate) {
+ return RespondNow(Error(kErrorNotSupported));
+ }
+
+ auto success_callback = base::Bind(
+ &DisplaySourceRequestAuthenticationFunction::OnRequestAuthCompleted,
+ this);
+ auto failure_callback = base::Bind(
+ &DisplaySourceRequestAuthenticationFunction::OnRequestAuthFailed, this);
+ delegate->RequestAuthentication(params->sink_id, success_callback,
+ failure_callback);
+ return RespondLater();
+}
+
+void DisplaySourceRequestAuthenticationFunction::OnRequestAuthCompleted(
+ const DisplaySourceAuthInfo& auth_info) {
+ scoped_ptr<base::ListValue> result =
+ api::display_source::RequestAuthentication::Results::Create(auth_info);
+ Respond(ArgumentList(std::move(result)));
+}
+
+void DisplaySourceRequestAuthenticationFunction::OnRequestAuthFailed(
+ const std::string& reason) {
+ Respond(Error(reason));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/display_source/display_source_api.h b/chromium/extensions/browser/api/display_source/display_source_api.h
new file mode 100644
index 00000000000..f1e3ad44c4d
--- /dev/null
+++ b/chromium/extensions/browser/api/display_source/display_source_api.h
@@ -0,0 +1,52 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_API_H_
+#define EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_API_H_
+
+#include "base/macros.h"
+#include "extensions/browser/api/display_source/display_source_connection_delegate.h"
+#include "extensions/browser/extension_function.h"
+
+namespace extensions {
+
+class DisplaySourceGetAvailableSinksFunction
+ : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("displaySource.getAvailableSinks",
+ DISPLAYSOURCE_GETAVAILABLESINKS);
+ DisplaySourceGetAvailableSinksFunction() = default;
+
+ protected:
+ ~DisplaySourceGetAvailableSinksFunction() override;
+ ResponseAction Run() final;
+
+ private:
+ void OnGetSinksCompleted(const DisplaySourceSinkInfoList& sinks);
+ void OnGetSinksFailed(const std::string& reason);
+
+ DISALLOW_COPY_AND_ASSIGN(DisplaySourceGetAvailableSinksFunction);
+};
+
+class DisplaySourceRequestAuthenticationFunction
+ : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("displaySource.requestAuthentication",
+ DISPLAYSOURCE_REQUESTAUTHENTICATION);
+ DisplaySourceRequestAuthenticationFunction() = default;
+
+ protected:
+ ~DisplaySourceRequestAuthenticationFunction() override;
+ ResponseAction Run() final;
+
+ private:
+ void OnRequestAuthCompleted(const DisplaySourceAuthInfo& auth_info);
+ void OnRequestAuthFailed(const std::string& reason);
+
+ DISALLOW_COPY_AND_ASSIGN(DisplaySourceRequestAuthenticationFunction);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_API_H_
diff --git a/chromium/extensions/browser/api/display_source/display_source_apitest.cc b/chromium/extensions/browser/api/display_source/display_source_apitest.cc
new file mode 100644
index 00000000000..01419f3dec4
--- /dev/null
+++ b/chromium/extensions/browser/api/display_source/display_source_apitest.cc
@@ -0,0 +1,99 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/scoped_ptr.h"
+#include "content/public/test/test_utils.h"
+#include "extensions/browser/api/display_source/display_source_connection_delegate.h"
+#include "extensions/browser/api/display_source/display_source_connection_delegate_factory.h"
+#include "extensions/shell/test/shell_apitest.h"
+
+namespace extensions {
+
+using api::display_source::SinkInfo;
+using api::display_source::SinkState;
+using api::display_source::SINK_STATE_DISCONNECTED;
+using api::display_source::AUTHENTICATION_METHOD_PBC;
+
+namespace {
+
+DisplaySourceSinkInfo CreateSinkInfoPtr(int id,
+ const std::string& name,
+ SinkState state) {
+ DisplaySourceSinkInfo info;
+ info.id = id;
+ info.name = name;
+ info.state = state;
+
+ return info;
+}
+
+} // namespace
+
+class MockDisplaySourceConnectionDelegate
+ : public DisplaySourceConnectionDelegate {
+ public:
+ const DisplaySourceSinkInfoList& last_found_sinks() const override {
+ return sinks_;
+ }
+ const Connection* connection() const override { return nullptr; }
+ void GetAvailableSinks(const SinkInfoListCallback& sinks_callback,
+ const StringCallback& failure_callback) override {
+ AddSink(CreateSinkInfoPtr(1, "sink 1", SINK_STATE_DISCONNECTED));
+ sinks_callback.Run(sinks_);
+ }
+
+ void RequestAuthentication(int sink_id,
+ const AuthInfoCallback& auth_info_callback,
+ const StringCallback& failure_callback) override {
+ DisplaySourceAuthInfo info;
+ info.method = AUTHENTICATION_METHOD_PBC;
+ auth_info_callback.Run(info);
+ }
+
+ void Connect(int sink_id,
+ const DisplaySourceAuthInfo& auth_info,
+ const StringCallback& failure_callback) override {}
+
+ void Disconnect(const StringCallback& failure_callback) override {}
+
+ void StartWatchingAvailableSinks() override {
+ AddSink(CreateSinkInfoPtr(2, "sink 2", SINK_STATE_DISCONNECTED));
+ }
+
+ void StopWatchingAvailableSinks() override {}
+
+ private:
+ void AddSink(DisplaySourceSinkInfo sink) {
+ sinks_.push_back(std::move(sink));
+ FOR_EACH_OBSERVER(DisplaySourceConnectionDelegate::Observer, observers_,
+ OnSinksUpdated(sinks_));
+ }
+
+ DisplaySourceSinkInfoList sinks_;
+};
+
+class DisplaySourceApiTest : public ShellApiTest {
+ public:
+ DisplaySourceApiTest() = default;
+
+ private:
+ static scoped_ptr<KeyedService> CreateMockDelegate(
+ content::BrowserContext* profile) {
+ return make_scoped_ptr<KeyedService>(
+ new MockDisplaySourceConnectionDelegate());
+ }
+
+ void SetUpOnMainThread() override {
+ ShellApiTest::SetUpOnMainThread();
+ DisplaySourceConnectionDelegateFactory::GetInstance()->SetTestingFactory(
+ browser_context(), &CreateMockDelegate);
+ content::RunAllPendingInMessageLoop();
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(DisplaySourceApiTest, DisplaySourceExtension) {
+ ASSERT_TRUE(RunAppTest("api_test/display_source/api")) << message_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/display_source/display_source_connection_delegate.cc b/chromium/extensions/browser/api/display_source/display_source_connection_delegate.cc
new file mode 100644
index 00000000000..1fd09901153
--- /dev/null
+++ b/chromium/extensions/browser/api/display_source/display_source_connection_delegate.cc
@@ -0,0 +1,25 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/display_source/display_source_connection_delegate.h"
+
+namespace extensions {
+
+DisplaySourceConnectionDelegate::Connection::Connection() {}
+
+DisplaySourceConnectionDelegate::Connection::~Connection() {}
+
+DisplaySourceConnectionDelegate::DisplaySourceConnectionDelegate() {}
+
+DisplaySourceConnectionDelegate::~DisplaySourceConnectionDelegate() {}
+
+void DisplaySourceConnectionDelegate::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void DisplaySourceConnectionDelegate::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/display_source/display_source_connection_delegate.h b/chromium/extensions/browser/api/display_source/display_source_connection_delegate.h
new file mode 100644
index 00000000000..a1a3106beb8
--- /dev/null
+++ b/chromium/extensions/browser/api/display_source/display_source_connection_delegate.h
@@ -0,0 +1,130 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_CONNECTION_DELEGATE_H_
+#define EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_CONNECTION_DELEGATE_H_
+
+#include "base/callback.h"
+#include "base/observer_list.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/common/api/display_source.h"
+
+namespace extensions {
+
+using DisplaySourceSinkInfo = api::display_source::SinkInfo;
+using DisplaySourceSinkInfoList = std::vector<DisplaySourceSinkInfo>;
+using DisplaySourceAuthInfo = api::display_source::AuthenticationInfo;
+using DisplaySourceErrorType = api::display_source::ErrorType;
+// The DisplaySourceConnectionDelegate interface should be implemented
+// to provide sinks search and connection functionality for
+// 'chrome.displaySource' API.
+class DisplaySourceConnectionDelegate : public KeyedService {
+ public:
+ using AuthInfoCallback = base::Callback<void(const DisplaySourceAuthInfo&)>;
+ using StringCallback = base::Callback<void(const std::string&)>;
+ using SinkInfoListCallback =
+ base::Callback<void(const DisplaySourceSinkInfoList&)>;
+
+ const static int kInvalidSinkId = -1;
+
+ class Connection {
+ public:
+ // Returns the connected sink object.
+ virtual DisplaySourceSinkInfo GetConnectedSink() const = 0;
+
+ // Returns the local address of the source.
+ virtual std::string GetLocalAddress() const = 0;
+
+ // Returns the address of the connected sink.
+ virtual std::string GetSinkAddress() const = 0;
+
+ // Sends a control message to the connected sink.
+ // If an error occurs 'Observer::OnConnectionError' is invoked.
+ virtual void SendMessage(const std::string& message) const = 0;
+
+ // Sets a callback to receive control messages from the connected sink.
+ // This method should only be called once in the lifetime of each
+ // Connection object.
+ // If an error occurs 'Observer::OnConnectionError' is invoked.
+ virtual void SetMessageReceivedCallback(
+ const StringCallback& callback) const = 0;
+
+ protected:
+ Connection();
+ virtual ~Connection();
+ };
+
+ class Observer {
+ public:
+ // This method is called each time the list of available
+ // sinks is updated whether after 'GetAvailableSinks' call
+ // or while the implementation is constantly watching the
+ // available sinks (after 'StartWatchingAvailableSinks' was called).
+ // Also this method is called to reflect current connection updates.
+ virtual void OnSinksUpdated(const DisplaySourceSinkInfoList& sinks) = 0;
+
+ // This method is called during the established connection to report
+ // a transport layer fatal error (which implies that the connection
+ // becomes broken/disconnected).
+ virtual void OnConnectionError(int sink_id,
+ DisplaySourceErrorType type,
+ const std::string& description) = 0;
+
+ protected:
+ virtual ~Observer() {}
+ };
+
+ DisplaySourceConnectionDelegate();
+ ~DisplaySourceConnectionDelegate() override;
+
+ virtual void AddObserver(Observer* observer);
+ virtual void RemoveObserver(Observer* observer);
+
+ // Returns the list of last found available sinks
+ // this list may contain outdated data if the delegate
+ // is not watching the sinks (via 'StartWatchingSinks'
+ // method). The list is refreshed after 'GetAvailableSinks'
+ // call.
+ virtual const DisplaySourceSinkInfoList& last_found_sinks() const = 0;
+
+ // Returns the Connection object representing the current
+ // connection to the sink or NULL if there is no current connection.
+ virtual const Connection* connection() const = 0;
+
+ // Queries the list of currently available sinks.
+ virtual void GetAvailableSinks(const SinkInfoListCallback& sinks_callback,
+ const StringCallback& failure_callback) = 0;
+
+ // Queries the authentication method required by the sink for connection.
+ // If the used authentication method requires authentication data to be
+ // visible on the sink's display (e.g. PIN) the implementation should
+ // request the sink to show it.
+ virtual void RequestAuthentication(
+ int sink_id,
+ const AuthInfoCallback& auth_info_callback,
+ const StringCallback& failure_callback) = 0;
+
+ // Connects to a sink by given id and auth info.
+ virtual void Connect(int sink_id,
+ const DisplaySourceAuthInfo& auth_info,
+ const StringCallback& failure_callback) = 0;
+
+ // Disconnects the current connection to sink, the 'failure_callback'
+ // is called if an error has occurred or if there is no established
+ // connection.
+ virtual void Disconnect(const StringCallback& failure_callback) = 0;
+
+ // Implementation should start watching the available sinks updates.
+ virtual void StartWatchingAvailableSinks() = 0;
+
+ // Implementation should stop watching the available sinks updates.
+ virtual void StopWatchingAvailableSinks() = 0;
+
+ protected:
+ base::ObserverList<Observer> observers_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_CONNECTION_DELEGATE_H_
diff --git a/chromium/extensions/browser/api/display_source/display_source_connection_delegate_factory.cc b/chromium/extensions/browser/api/display_source/display_source_connection_delegate_factory.cc
new file mode 100644
index 00000000000..5495661ae0d
--- /dev/null
+++ b/chromium/extensions/browser/api/display_source/display_source_connection_delegate_factory.cc
@@ -0,0 +1,60 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/display_source/display_source_connection_delegate_factory.h"
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/extensions_browser_client.h"
+
+namespace extensions {
+
+using content::BrowserContext;
+
+// static
+DisplaySourceConnectionDelegate*
+DisplaySourceConnectionDelegateFactory::GetForBrowserContext(
+ BrowserContext* browser_context) {
+ return static_cast<DisplaySourceConnectionDelegate*>(
+ GetInstance()->GetServiceForBrowserContext(browser_context, true));
+}
+
+// static
+DisplaySourceConnectionDelegateFactory*
+DisplaySourceConnectionDelegateFactory::GetInstance() {
+ return base::Singleton<DisplaySourceConnectionDelegateFactory>::get();
+}
+
+DisplaySourceConnectionDelegateFactory::DisplaySourceConnectionDelegateFactory()
+ : BrowserContextKeyedServiceFactory(
+ "DisplaySourceConnectionDelegate",
+ BrowserContextDependencyManager::GetInstance()) {}
+
+DisplaySourceConnectionDelegateFactory::
+ ~DisplaySourceConnectionDelegateFactory() {}
+
+KeyedService* DisplaySourceConnectionDelegateFactory::BuildServiceInstanceFor(
+ BrowserContext* browser_context) const {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ DisplaySourceConnectionDelegate* delegate = nullptr;
+ // TODO(mikhail): Add implementation.
+ return delegate;
+}
+
+BrowserContext* DisplaySourceConnectionDelegateFactory::GetBrowserContextToUse(
+ BrowserContext* context) const {
+ return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+}
+
+bool DisplaySourceConnectionDelegateFactory::
+ ServiceIsCreatedWithBrowserContext() const {
+ return false;
+}
+
+bool DisplaySourceConnectionDelegateFactory::ServiceIsNULLWhileTesting() const {
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/display_source/display_source_connection_delegate_factory.h b/chromium/extensions/browser/api/display_source/display_source_connection_delegate_factory.h
new file mode 100644
index 00000000000..ec412a9884d
--- /dev/null
+++ b/chromium/extensions/browser/api/display_source/display_source_connection_delegate_factory.h
@@ -0,0 +1,47 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_CONNECTION_DELEGATE_FACTORY_H_
+#define EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_CONNECTION_DELEGATE_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "extensions/browser/api/display_source/display_source_connection_delegate.h"
+
+namespace context {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class DisplaySourceConnectionDelegateFactory
+ : public BrowserContextKeyedServiceFactory {
+ public:
+ static DisplaySourceConnectionDelegate* GetForBrowserContext(
+ content::BrowserContext* browser_context);
+ static DisplaySourceConnectionDelegateFactory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<
+ DisplaySourceConnectionDelegateFactory>;
+
+ DisplaySourceConnectionDelegateFactory();
+ ~DisplaySourceConnectionDelegateFactory() override;
+
+ // BrowserContextKeyedServiceFactory:
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* browser_context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+ bool ServiceIsCreatedWithBrowserContext() const override;
+ bool ServiceIsNULLWhileTesting() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplaySourceConnectionDelegateFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_CONNECTION_DELEGATE_FACTORY_H_
diff --git a/chromium/extensions/browser/api/display_source/display_source_event_router.cc b/chromium/extensions/browser/api/display_source/display_source_event_router.cc
new file mode 100644
index 00000000000..d621e94cd37
--- /dev/null
+++ b/chromium/extensions/browser/api/display_source/display_source_event_router.cc
@@ -0,0 +1,102 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/display_source/display_source_event_router.h"
+
+#include <utility>
+
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/api/display_source/display_source_api.h"
+#include "extensions/browser/api/display_source/display_source_connection_delegate_factory.h"
+#include "extensions/common/api/display_source.h"
+
+namespace extensions {
+
+DisplaySourceEventRouter::DisplaySourceEventRouter(
+ content::BrowserContext* browser_context)
+ : browser_context_(browser_context), listening_(false) {
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ if (!event_router)
+ return;
+ event_router->RegisterObserver(
+ this, api::display_source::OnSinksUpdated::kEventName);
+}
+
+DisplaySourceEventRouter::~DisplaySourceEventRouter() {
+ DCHECK(!listening_);
+}
+
+void DisplaySourceEventRouter::Shutdown() {
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ if (event_router)
+ event_router->UnregisterObserver(this);
+
+ if (!listening_)
+ return;
+ listening_ = false;
+ DisplaySourceConnectionDelegate* delegate =
+ DisplaySourceConnectionDelegateFactory::GetForBrowserContext(
+ browser_context_);
+ if (delegate)
+ delegate->RemoveObserver(this);
+}
+
+void DisplaySourceEventRouter::OnListenerAdded(
+ const EventListenerInfo& details) {
+ StartOrStopListeningForSinksChanges();
+}
+
+void DisplaySourceEventRouter::OnListenerRemoved(
+ const EventListenerInfo& details) {
+ StartOrStopListeningForSinksChanges();
+}
+
+void DisplaySourceEventRouter::StartOrStopListeningForSinksChanges() {
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ if (!event_router)
+ return;
+
+ bool should_listen = event_router->HasEventListener(
+ api::display_source::OnSinksUpdated::kEventName);
+ if (should_listen && !listening_) {
+ DisplaySourceConnectionDelegate* delegate =
+ DisplaySourceConnectionDelegateFactory::GetForBrowserContext(
+ browser_context_);
+ if (delegate) {
+ delegate->AddObserver(this);
+ delegate->StartWatchingAvailableSinks();
+ }
+ }
+ if (!should_listen && listening_) {
+ DisplaySourceConnectionDelegate* delegate =
+ DisplaySourceConnectionDelegateFactory::GetForBrowserContext(
+ browser_context_);
+ if (delegate) {
+ delegate->RemoveObserver(this);
+ delegate->StopWatchingAvailableSinks();
+ }
+ }
+
+ listening_ = should_listen;
+}
+
+void DisplaySourceEventRouter::OnSinksUpdated(
+ const DisplaySourceSinkInfoList& sinks) {
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ if (!event_router)
+ return;
+ scoped_ptr<base::ListValue> args(
+ api::display_source::OnSinksUpdated::Create(sinks));
+ scoped_ptr<Event> sinks_updated_event(new Event(
+ events::DISPLAY_SOURCE_ON_SINKS_UPDATED,
+ api::display_source::OnSinksUpdated::kEventName, std::move(args)));
+ event_router->BroadcastEvent(std::move(sinks_updated_event));
+}
+
+DisplaySourceEventRouter* DisplaySourceEventRouter::Create(
+ content::BrowserContext* browser_context) {
+ return new DisplaySourceEventRouter(browser_context);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/display_source/display_source_event_router.h b/chromium/extensions/browser/api/display_source/display_source_event_router.h
new file mode 100644
index 00000000000..97e2e6f20b5
--- /dev/null
+++ b/chromium/extensions/browser/api/display_source/display_source_event_router.h
@@ -0,0 +1,60 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_EVENT_ROUTER_H_
+#define EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_EVENT_ROUTER_H_
+
+#include "base/macros.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/browser/api/display_source/display_source_connection_delegate.h"
+#include "extensions/browser/event_router.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+// Observe listeners to 'onSinksUpdated' events.
+// This class initiates/stops listening for the available sinks
+// by the DisplaySourceConnectionDelegate, depending on whether
+// there are existing listeners to 'onSinksUpdated' event.
+class DisplaySourceEventRouter
+ : public KeyedService,
+ public EventRouter::Observer,
+ public DisplaySourceConnectionDelegate::Observer {
+ public:
+ static DisplaySourceEventRouter* Create(
+ content::BrowserContext* browser_context);
+
+ ~DisplaySourceEventRouter() override;
+
+ private:
+ explicit DisplaySourceEventRouter(content::BrowserContext* browser_context);
+
+ // KeyedService overrides:
+ void Shutdown() override;
+
+ // EventRouter::Observer overrides:
+ void OnListenerAdded(const EventListenerInfo& details) override;
+ void OnListenerRemoved(const EventListenerInfo& details) override;
+
+ // DisplaySourceConnectionDelegate::Observer overrides:
+ void OnSinksUpdated(const DisplaySourceSinkInfoList& sinks) override;
+ void OnConnectionError(int sink_id,
+ DisplaySourceErrorType type,
+ const std::string& description) override {}
+
+ private:
+ void StartOrStopListeningForSinksChanges();
+
+ content::BrowserContext* browser_context_;
+ bool listening_;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplaySourceEventRouter);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_EVENT_ROUTER_H_
diff --git a/chromium/extensions/browser/api/display_source/display_source_event_router_factory.cc b/chromium/extensions/browser/api/display_source/display_source_event_router_factory.cc
new file mode 100644
index 00000000000..050159f19f9
--- /dev/null
+++ b/chromium/extensions/browser/api/display_source/display_source_event_router_factory.cc
@@ -0,0 +1,59 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/display_source/display_source_event_router_factory.h"
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/api/display_source/display_source_connection_delegate_factory.h"
+#include "extensions/browser/api/display_source/display_source_event_router.h"
+#include "extensions/browser/extension_system_provider.h"
+#include "extensions/browser/extensions_browser_client.h"
+
+namespace extensions {
+
+// static
+DisplaySourceEventRouter* DisplaySourceEventRouterFactory::GetForProfile(
+ content::BrowserContext* context) {
+ return static_cast<DisplaySourceEventRouter*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+DisplaySourceEventRouterFactory*
+DisplaySourceEventRouterFactory::GetInstance() {
+ return base::Singleton<DisplaySourceEventRouterFactory>::get();
+}
+
+DisplaySourceEventRouterFactory::DisplaySourceEventRouterFactory()
+ : BrowserContextKeyedServiceFactory(
+ "DisplaySourceEventRouter",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
+ DependsOn(DisplaySourceConnectionDelegateFactory::GetInstance());
+}
+
+DisplaySourceEventRouterFactory::~DisplaySourceEventRouterFactory() {}
+
+KeyedService* DisplaySourceEventRouterFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ return DisplaySourceEventRouter::Create(context);
+}
+
+content::BrowserContext*
+DisplaySourceEventRouterFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+}
+
+bool DisplaySourceEventRouterFactory::ServiceIsCreatedWithBrowserContext()
+ const {
+ return true;
+}
+
+bool DisplaySourceEventRouterFactory::ServiceIsNULLWhileTesting() const {
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/display_source/display_source_event_router_factory.h b/chromium/extensions/browser/api/display_source/display_source_event_router_factory.h
new file mode 100644
index 00000000000..7fd777f2cb2
--- /dev/null
+++ b/chromium/extensions/browser/api/display_source/display_source_event_router_factory.h
@@ -0,0 +1,48 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_EVENT_ROUTER_FACTORY_H_
+#define EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_EVENT_ROUTER_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace extensions {
+
+class DisplaySourceEventRouter;
+
+class DisplaySourceEventRouterFactory
+ : public BrowserContextKeyedServiceFactory {
+ public:
+ // Returns the DisplaySourceEventRouter for |profile|, creating it if
+ // it is not yet created.
+ static DisplaySourceEventRouter* GetForProfile(
+ content::BrowserContext* context);
+
+ static DisplaySourceEventRouterFactory* GetInstance();
+
+ protected:
+ // BrowserContextKeyedBaseFactory overrides:
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+ bool ServiceIsCreatedWithBrowserContext() const override;
+ bool ServiceIsNULLWhileTesting() const override;
+
+ private:
+ friend struct base::DefaultSingletonTraits<DisplaySourceEventRouterFactory>;
+
+ DisplaySourceEventRouterFactory();
+ ~DisplaySourceEventRouterFactory() override;
+
+ // BrowserContextKeyedServiceFactory:
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* profile) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplaySourceEventRouterFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_EVENT_ROUTER_FACTORY_H_
diff --git a/chromium/extensions/browser/api/display_source/wifi_display/wifi_display_session_service_impl.cc b/chromium/extensions/browser/api/display_source/wifi_display/wifi_display_session_service_impl.cc
new file mode 100644
index 00000000000..8143512f325
--- /dev/null
+++ b/chromium/extensions/browser/api/display_source/wifi_display/wifi_display_session_service_impl.cc
@@ -0,0 +1,205 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/display_source/wifi_display/wifi_display_session_service_impl.h"
+
+#include "base/bind.h"
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/api/display_source/display_source_connection_delegate_factory.h"
+
+namespace {
+const char kErrorCannotHaveMultipleSessions[] =
+ "Multiple Wi-Fi Display sessions are not supported";
+const char kErrorSinkNotAvailable[] = "The sink is not available";
+} // namespace
+
+namespace extensions {
+
+using namespace api::display_source;
+
+WiFiDisplaySessionServiceImpl::WiFiDisplaySessionServiceImpl(
+ DisplaySourceConnectionDelegate* delegate,
+ mojo::InterfaceRequest<WiFiDisplaySessionService> request)
+ : binding_(this, std::move(request)),
+ delegate_(delegate),
+ sink_state_(SINK_STATE_NONE),
+ sink_id_(DisplaySourceConnectionDelegate::kInvalidSinkId),
+ weak_factory_(this) {
+ delegate_->AddObserver(this);
+}
+
+WiFiDisplaySessionServiceImpl::~WiFiDisplaySessionServiceImpl() {
+ delegate_->RemoveObserver(this);
+ Disconnect();
+}
+
+// static
+void WiFiDisplaySessionServiceImpl::BindToRequest(
+ content::BrowserContext* browser_context,
+ mojo::InterfaceRequest<WiFiDisplaySessionService> request) {
+ DisplaySourceConnectionDelegate* delegate =
+ DisplaySourceConnectionDelegateFactory::GetForBrowserContext(
+ browser_context);
+ CHECK(delegate);
+
+ new WiFiDisplaySessionServiceImpl(delegate, std::move(request));
+}
+
+void WiFiDisplaySessionServiceImpl::SetClient(
+ WiFiDisplaySessionServiceClientPtr client) {
+ DCHECK(client);
+ DCHECK(!client_);
+ client_ = std::move(client);
+ client_.set_connection_error_handler(
+ base::Bind(&WiFiDisplaySessionServiceImpl::OnClientConnectionError,
+ weak_factory_.GetWeakPtr()));
+}
+
+void WiFiDisplaySessionServiceImpl::Connect(int32_t sink_id,
+ int32_t auth_method,
+ const mojo::String& auth_data) {
+ DCHECK(client_);
+ // We support only one Wi-Fi Display session at a time.
+ if (delegate_->connection()) {
+ client_->OnConnectRequestHandled(false, kErrorCannotHaveMultipleSessions);
+ return;
+ }
+
+ const DisplaySourceSinkInfoList& sinks = delegate_->last_found_sinks();
+ auto found = std::find_if(sinks.begin(), sinks.end(),
+ [sink_id](const DisplaySourceSinkInfo& sink) {
+ return sink.id == sink_id;
+ });
+ if (found == sinks.end() || found->state != SINK_STATE_DISCONNECTED) {
+ client_->OnConnectRequestHandled(false, kErrorSinkNotAvailable);
+ return;
+ }
+ AuthenticationInfo auth_info;
+ if (auth_method != AUTHENTICATION_METHOD_NONE) {
+ DCHECK(auth_method <= AUTHENTICATION_METHOD_LAST);
+ auth_info.method = static_cast<AuthenticationMethod>(auth_method);
+ auth_info.data = scoped_ptr<std::string>(new std::string(auth_data));
+ }
+ auto on_error = base::Bind(&WiFiDisplaySessionServiceImpl::OnConnectFailed,
+ weak_factory_.GetWeakPtr(), sink_id);
+ delegate_->Connect(sink_id, auth_info, on_error);
+ sink_id_ = sink_id;
+ sink_state_ = found->state;
+ DCHECK(sink_state_ == SINK_STATE_CONNECTING);
+ client_->OnConnectRequestHandled(true, "");
+}
+
+void WiFiDisplaySessionServiceImpl::Disconnect() {
+ if (sink_id_ == DisplaySourceConnectionDelegate::kInvalidSinkId) {
+ // The connection might drop before this call has arrived.
+ // Renderer should have been notified already.
+ return;
+ }
+
+ const DisplaySourceSinkInfoList& sinks = delegate_->last_found_sinks();
+ auto found = std::find_if(sinks.begin(), sinks.end(),
+ [this](const DisplaySourceSinkInfo& sink) {
+ return sink.id == sink_id_;
+ });
+ DCHECK(found != sinks.end());
+ DCHECK(found->state == SINK_STATE_CONNECTED ||
+ found->state == SINK_STATE_CONNECTING);
+
+ auto on_error = base::Bind(&WiFiDisplaySessionServiceImpl::OnDisconnectFailed,
+ weak_factory_.GetWeakPtr(), sink_id_);
+ delegate_->Disconnect(on_error);
+}
+
+void WiFiDisplaySessionServiceImpl::SendMessage(const mojo::String& message) {
+ if (sink_id_ == DisplaySourceConnectionDelegate::kInvalidSinkId) {
+ // The connection might drop before this call has arrived.
+ return;
+ }
+ auto connection = delegate_->connection();
+ DCHECK(connection);
+ DCHECK_EQ(sink_id_, connection->GetConnectedSink().id);
+ connection->SendMessage(message);
+}
+
+void WiFiDisplaySessionServiceImpl::OnSinkMessage(const std::string& message) {
+ DCHECK(delegate_->connection());
+ DCHECK_NE(sink_id_, DisplaySourceConnectionDelegate::kInvalidSinkId);
+ DCHECK(client_);
+ client_->OnMessage(message);
+}
+
+void WiFiDisplaySessionServiceImpl::OnSinksUpdated(
+ const DisplaySourceSinkInfoList& sinks) {
+ if (sink_id_ == DisplaySourceConnectionDelegate::kInvalidSinkId)
+ return;
+ // The initialized sink id means that the client should have
+ // been initialized as well.
+ DCHECK(client_);
+ auto found = std::find_if(sinks.begin(), sinks.end(),
+ [this](const DisplaySourceSinkInfo& sink) {
+ return sink.id == sink_id_;
+ });
+ if (found == sinks.end()) {
+ client_->OnError(ERROR_TYPE_CONNECTION_ERROR, "The sink has disappeared");
+ client_->OnTerminated();
+ sink_id_ = DisplaySourceConnectionDelegate::kInvalidSinkId;
+ }
+
+ SinkState actual_state = found->state;
+
+ if (actual_state == sink_state_)
+ return;
+
+ if (actual_state == SINK_STATE_CONNECTED) {
+ auto connection = delegate_->connection();
+ DCHECK(connection);
+ auto on_message = base::Bind(&WiFiDisplaySessionServiceImpl::OnSinkMessage,
+ weak_factory_.GetWeakPtr());
+ connection->SetMessageReceivedCallback(on_message);
+ client_->OnConnected(connection->GetLocalAddress());
+ }
+
+ if (actual_state == SINK_STATE_DISCONNECTED) {
+ client_->OnDisconnectRequestHandled(true, "");
+ client_->OnTerminated();
+ sink_id_ = DisplaySourceConnectionDelegate::kInvalidSinkId;
+ }
+
+ sink_state_ = actual_state;
+}
+
+void WiFiDisplaySessionServiceImpl::OnConnectionError(
+ int sink_id,
+ DisplaySourceErrorType type,
+ const std::string& description) {
+ if (sink_id != sink_id_)
+ return;
+ DCHECK(client_);
+ client_->OnError(type, description);
+}
+
+void WiFiDisplaySessionServiceImpl::OnConnectFailed(
+ int sink_id,
+ const std::string& message) {
+ if (sink_id != sink_id_)
+ return;
+ DCHECK(client_);
+ client_->OnError(ERROR_TYPE_CONNECTION_ERROR, message);
+}
+
+void WiFiDisplaySessionServiceImpl::OnDisconnectFailed(
+ int sink_id,
+ const std::string& message) {
+ if (sink_id != sink_id_)
+ return;
+ DCHECK(client_);
+ client_->OnDisconnectRequestHandled(false, message);
+}
+
+void WiFiDisplaySessionServiceImpl::OnClientConnectionError() {
+ DLOG(ERROR) << "IPC connection error";
+ delete this;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/display_source/wifi_display/wifi_display_session_service_impl.h b/chromium/extensions/browser/api/display_source/wifi_display/wifi_display_session_service_impl.h
new file mode 100644
index 00000000000..6639871b116
--- /dev/null
+++ b/chromium/extensions/browser/api/display_source/wifi_display/wifi_display_session_service_impl.h
@@ -0,0 +1,75 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_SESSION_SERVICE_IMPL_H_
+#define EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_SESSION_SERVICE_IMPL_H_
+
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "extensions/browser/api/display_source/display_source_connection_delegate.h"
+#include "extensions/common/mojo/wifi_display_session_service.mojom.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+namespace extensions {
+
+// This class provides access to the network transport for the Wi-Fi Display
+// session (which is itself hosted in the sandboxed renderer process).
+class WiFiDisplaySessionServiceImpl
+ : public WiFiDisplaySessionService,
+ public DisplaySourceConnectionDelegate::Observer {
+ public:
+ ~WiFiDisplaySessionServiceImpl() override;
+ static void BindToRequest(
+ content::BrowserContext* context,
+ mojo::InterfaceRequest<WiFiDisplaySessionService> request);
+
+ private:
+ // WiFiDisplaySessionService overrides.
+ void SetClient(WiFiDisplaySessionServiceClientPtr client) override;
+ void Connect(int32_t sink_id,
+ int32_t auth_method,
+ const mojo::String& auth_data) override;
+ void Disconnect() override;
+ void SendMessage(const mojo::String& message) override;
+
+ // DisplaySourceConnectionDelegate::Observer overrides.
+ void OnSinksUpdated(const DisplaySourceSinkInfoList& sinks) override;
+ void OnConnectionError(int sink_id,
+ DisplaySourceErrorType type,
+ const std::string& description) override;
+
+ explicit WiFiDisplaySessionServiceImpl(
+ DisplaySourceConnectionDelegate* delegate,
+ mojo::InterfaceRequest<WiFiDisplaySessionService> request);
+
+ // Called if a message is received from the connected sink.
+ void OnSinkMessage(const std::string& message);
+
+ // Failure callbacks for Connect and Disconnect methods.
+ void OnConnectFailed(int sink_id, const std::string& reason);
+ void OnDisconnectFailed(int sink_id, const std::string& reason);
+
+ // Mojo error callback.
+ void OnClientConnectionError();
+
+ mojo::StrongBinding<WiFiDisplaySessionService> binding_;
+ WiFiDisplaySessionServiceClientPtr client_;
+ DisplaySourceConnectionDelegate* delegate_;
+
+ api::display_source::SinkState sink_state_;
+ // Id of the sink of the session this object is associated with.
+ int sink_id_;
+
+ base::WeakPtrFactory<WiFiDisplaySessionServiceImpl> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WiFiDisplaySessionServiceImpl);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_SESSION_SERVICE_IMPL_H_
diff --git a/chromium/extensions/browser/api/dns/dns_api.cc b/chromium/extensions/browser/api/dns/dns_api.cc
new file mode 100644
index 00000000000..5513e3a081e
--- /dev/null
+++ b/chromium/extensions/browser/api/dns/dns_api.cc
@@ -0,0 +1,103 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/dns/dns_api.h"
+
+#include "base/bind.h"
+#include "base/values.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/resource_context.h"
+#include "extensions/browser/api/dns/host_resolver_wrapper.h"
+#include "extensions/common/api/dns.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_errors.h"
+#include "net/log/net_log.h"
+
+using content::BrowserThread;
+using extensions::api::dns::ResolveCallbackResolveInfo;
+
+namespace Resolve = extensions::api::dns::Resolve;
+
+namespace extensions {
+
+DnsResolveFunction::DnsResolveFunction()
+ : resource_context_(NULL),
+ response_(false),
+ request_handle_(new net::HostResolver::RequestHandle()),
+ addresses_(new net::AddressList) {}
+
+DnsResolveFunction::~DnsResolveFunction() {}
+
+bool DnsResolveFunction::RunAsync() {
+ scoped_ptr<Resolve::Params> params(Resolve::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ hostname_ = params->hostname;
+ resource_context_ = browser_context()->GetResourceContext();
+
+ bool result = BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&DnsResolveFunction::WorkOnIOThread, this));
+ DCHECK(result);
+ return true;
+}
+
+void DnsResolveFunction::WorkOnIOThread() {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+ net::HostResolver* host_resolver =
+ HostResolverWrapper::GetInstance()->GetHostResolver(resource_context_);
+ DCHECK(host_resolver);
+
+ // Yes, we are passing zero as the port. There are some interesting but not
+ // presently relevant reasons why HostResolver asks for the port of the
+ // hostname you'd like to resolve, even though it doesn't use that value in
+ // determining its answer.
+ net::HostPortPair host_port_pair(hostname_, 0);
+
+ net::HostResolver::RequestInfo request_info(host_port_pair);
+ int resolve_result = host_resolver->Resolve(
+ request_info,
+ net::DEFAULT_PRIORITY,
+ addresses_.get(),
+ base::Bind(&DnsResolveFunction::OnLookupFinished, this),
+ request_handle_.get(),
+ net::BoundNetLog());
+
+ // Balanced in OnLookupFinished.
+ AddRef();
+
+ if (resolve_result != net::ERR_IO_PENDING)
+ OnLookupFinished(resolve_result);
+}
+
+void DnsResolveFunction::RespondOnUIThread() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ SendResponse(response_);
+}
+
+void DnsResolveFunction::OnLookupFinished(int resolve_result) {
+ scoped_ptr<ResolveCallbackResolveInfo> resolve_info(
+ new ResolveCallbackResolveInfo());
+ resolve_info->result_code = resolve_result;
+ if (resolve_result == net::OK) {
+ DCHECK(!addresses_->empty());
+ resolve_info->address.reset(
+ new std::string(addresses_->front().ToStringWithoutPort()));
+ }
+ results_ = Resolve::Results::Create(*resolve_info);
+ response_ = true;
+
+ bool post_task_result = BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&DnsResolveFunction::RespondOnUIThread, this));
+ DCHECK(post_task_result);
+
+ Release(); // Added in WorkOnIOThread().
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/dns/dns_api.h b/chromium/extensions/browser/api/dns/dns_api.h
new file mode 100644
index 00000000000..a8947ead2dd
--- /dev/null
+++ b/chromium/extensions/browser/api/dns/dns_api.h
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DNS_DNS_API_H_
+#define EXTENSIONS_BROWSER_API_DNS_DNS_API_H_
+
+#include <string>
+
+#include "extensions/browser/extension_function.h"
+#include "net/base/address_list.h"
+#include "net/base/completion_callback.h"
+#include "net/dns/host_resolver.h"
+
+namespace content {
+class ResourceContext;
+}
+
+namespace extensions {
+
+class DnsResolveFunction : public AsyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("dns.resolve", DNS_RESOLVE)
+
+ DnsResolveFunction();
+
+ protected:
+ ~DnsResolveFunction() override;
+
+ // ExtensionFunction:
+ bool RunAsync() override;
+
+ void WorkOnIOThread();
+ void RespondOnUIThread();
+
+ private:
+ void OnLookupFinished(int result);
+
+ std::string hostname_;
+
+ // Not owned.
+ content::ResourceContext* resource_context_;
+
+ bool response_; // The value sent in SendResponse().
+
+ scoped_ptr<net::HostResolver::RequestHandle> request_handle_;
+ scoped_ptr<net::AddressList> addresses_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DNS_DNS_API_H_
diff --git a/chromium/extensions/browser/api/dns/dns_apitest.cc b/chromium/extensions/browser/api/dns/dns_apitest.cc
new file mode 100644
index 00000000000..24586f9dafb
--- /dev/null
+++ b/chromium/extensions/browser/api/dns/dns_apitest.cc
@@ -0,0 +1,96 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/test/test_utils.h"
+#include "extensions/browser/api/dns/dns_api.h"
+#include "extensions/browser/api/dns/host_resolver_wrapper.h"
+#include "extensions/browser/api/dns/mock_host_resolver_creator.h"
+#include "extensions/browser/api_test_utils.h"
+#include "extensions/browser/notification_types.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/test_util.h"
+#include "extensions/shell/test/shell_apitest.h"
+#include "net/base/net_errors.h"
+
+using extensions::api_test_utils::RunFunctionAndReturnSingleResult;
+
+namespace extensions {
+
+class DnsApiTest : public ShellApiTest {
+ public:
+ DnsApiTest() : resolver_creator_(new MockHostResolverCreator()) {}
+
+ private:
+ void SetUpOnMainThread() override {
+ ShellApiTest::SetUpOnMainThread();
+ HostResolverWrapper::GetInstance()->SetHostResolverForTesting(
+ resolver_creator_->CreateMockHostResolver());
+ }
+
+ void TearDownOnMainThread() override {
+ HostResolverWrapper::GetInstance()->SetHostResolverForTesting(NULL);
+ resolver_creator_->DeleteMockHostResolver();
+ ShellApiTest::TearDownOnMainThread();
+ }
+
+ // The MockHostResolver asserts that it's used on the same thread on which
+ // it's created, which is actually a stronger rule than its real counterpart.
+ // But that's fine; it's good practice.
+ scoped_refptr<MockHostResolverCreator> resolver_creator_;
+};
+
+IN_PROC_BROWSER_TEST_F(DnsApiTest, DnsResolveIPLiteral) {
+ scoped_refptr<DnsResolveFunction> resolve_function(new DnsResolveFunction());
+ scoped_refptr<Extension> empty_extension = test_util::CreateEmptyExtension();
+
+ resolve_function->set_extension(empty_extension.get());
+ resolve_function->set_has_callback(true);
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnSingleResult(
+ resolve_function.get(), "[\"127.0.0.1\"]", browser_context()));
+ base::DictionaryValue* dict = NULL;
+ ASSERT_TRUE(result->GetAsDictionary(&dict));
+
+ int result_code = 0;
+ EXPECT_TRUE(dict->GetInteger("resultCode", &result_code));
+ EXPECT_EQ(net::OK, result_code);
+
+ std::string address;
+ EXPECT_TRUE(dict->GetString("address", &address));
+ EXPECT_EQ("127.0.0.1", address);
+}
+
+IN_PROC_BROWSER_TEST_F(DnsApiTest, DnsResolveHostname) {
+ scoped_refptr<DnsResolveFunction> resolve_function(new DnsResolveFunction());
+ scoped_refptr<Extension> empty_extension = test_util::CreateEmptyExtension();
+
+ resolve_function->set_extension(empty_extension.get());
+ resolve_function->set_has_callback(true);
+
+ std::string function_arguments("[\"");
+ function_arguments += MockHostResolverCreator::kHostname;
+ function_arguments += "\"]";
+ scoped_ptr<base::Value> result(RunFunctionAndReturnSingleResult(
+ resolve_function.get(), function_arguments, browser_context()));
+ base::DictionaryValue* dict = NULL;
+ ASSERT_TRUE(result->GetAsDictionary(&dict));
+
+ int result_code = 0;
+ EXPECT_TRUE(dict->GetInteger("resultCode", &result_code));
+ EXPECT_EQ(net::OK, result_code);
+
+ std::string address;
+ EXPECT_TRUE(dict->GetString("address", &address));
+ EXPECT_EQ(MockHostResolverCreator::kAddress, address);
+}
+
+IN_PROC_BROWSER_TEST_F(DnsApiTest, DnsExtension) {
+ ASSERT_TRUE(RunAppTest("api_test/dns/api")) << message_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/dns/host_resolver_wrapper.cc b/chromium/extensions/browser/api/dns/host_resolver_wrapper.cc
new file mode 100644
index 00000000000..1b3e6a35414
--- /dev/null
+++ b/chromium/extensions/browser/api/dns/host_resolver_wrapper.cc
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/dns/host_resolver_wrapper.h"
+
+#include "content/public/browser/resource_context.h"
+#include "net/dns/host_resolver.h"
+
+namespace extensions {
+
+HostResolverWrapper::HostResolverWrapper() : resolver_(NULL) {}
+
+// static
+HostResolverWrapper* HostResolverWrapper::GetInstance() {
+ return base::Singleton<extensions::HostResolverWrapper>::get();
+}
+
+net::HostResolver* HostResolverWrapper::GetHostResolver(
+ content::ResourceContext* context) {
+ return resolver_ ? resolver_ : context->GetHostResolver();
+}
+
+void HostResolverWrapper::SetHostResolverForTesting(
+ net::HostResolver* mock_resolver) {
+ resolver_ = mock_resolver;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/dns/host_resolver_wrapper.h b/chromium/extensions/browser/api/dns/host_resolver_wrapper.h
new file mode 100644
index 00000000000..7b7d8d53211
--- /dev/null
+++ b/chromium/extensions/browser/api/dns/host_resolver_wrapper.h
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DNS_HOST_RESOLVER_WRAPPER_H_
+#define EXTENSIONS_BROWSER_API_DNS_HOST_RESOLVER_WRAPPER_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+
+namespace content {
+class ResourceContext;
+}
+
+namespace net {
+class HostResolver;
+}
+
+namespace extensions {
+
+// Used for testing. In production code, this class does nothing interesting.
+// This class is a singleton that holds a pointer to a mock HostResolver, or
+// else to NULL. API classes that need to resolve hostnames ask this class for
+// the correct HostResolver to use, passing in the one that they want to use,
+// thereby avoiding most lifetime issues, and it will reply with either that
+// same one, or else the test version to use instead.
+//
+// This is a pretty complicated way to replace a single pointer with another.
+// TODO(miket): make the previous statement obsolete.
+class HostResolverWrapper {
+ public:
+ static HostResolverWrapper* GetInstance();
+
+ // Given a pointer to a ResourceContext, returns its HostResolver if
+ // SetHostResolverForTesting() hasn't been called, or else a
+ // a substitute MockHostResolver to use instead.
+ net::HostResolver* GetHostResolver(content::ResourceContext* context);
+
+ // Sets the MockHostResolver to return in GetHostResolver().
+ void SetHostResolverForTesting(net::HostResolver* mock_resolver);
+
+ private:
+ HostResolverWrapper();
+ friend struct base::DefaultSingletonTraits<HostResolverWrapper>;
+
+ net::HostResolver* resolver_;
+
+ DISALLOW_COPY_AND_ASSIGN(HostResolverWrapper);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DNS_HOST_RESOLVER_WRAPPER_H_
diff --git a/chromium/extensions/browser/api/dns/mock_host_resolver_creator.cc b/chromium/extensions/browser/api/dns/mock_host_resolver_creator.cc
new file mode 100644
index 00000000000..e2841e8f2cf
--- /dev/null
+++ b/chromium/extensions/browser/api/dns/mock_host_resolver_creator.cc
@@ -0,0 +1,71 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/dns/mock_host_resolver_creator.h"
+
+#include "base/logging.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/dns/mock_host_resolver.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+const std::string MockHostResolverCreator::kHostname = "www.sowbug.com";
+const std::string MockHostResolverCreator::kAddress = "9.8.7.6";
+
+MockHostResolverCreator::MockHostResolverCreator()
+ : resolver_event_(true, false), mock_host_resolver_(NULL) {
+}
+
+MockHostResolverCreator::~MockHostResolverCreator() {
+}
+
+net::HostResolver* MockHostResolverCreator::CreateMockHostResolver() {
+ DCHECK(!mock_host_resolver_);
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ bool result = BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&MockHostResolverCreator::CreateMockHostResolverOnIOThread,
+ this));
+ DCHECK(result);
+
+ base::TimeDelta max_time = base::TimeDelta::FromSeconds(5);
+ resolver_event_.TimedWait(max_time);
+
+ return mock_host_resolver_;
+}
+
+void MockHostResolverCreator::CreateMockHostResolverOnIOThread() {
+ mock_host_resolver_ = new net::MockHostResolver();
+ mock_host_resolver_->rules()->AddRule(kHostname, kAddress);
+ mock_host_resolver_->rules()->AddSimulatedFailure("this.hostname.is.bogus");
+ resolver_event_.Signal();
+}
+
+void MockHostResolverCreator::DeleteMockHostResolver() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!mock_host_resolver_)
+ return;
+ resolver_event_.Reset();
+ bool result = BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&MockHostResolverCreator::DeleteMockHostResolverOnIOThread,
+ this));
+ DCHECK(result);
+
+ base::TimeDelta max_time = base::TimeDelta::FromSeconds(5);
+ CHECK(resolver_event_.TimedWait(max_time));
+}
+
+void MockHostResolverCreator::DeleteMockHostResolverOnIOThread() {
+ delete (mock_host_resolver_);
+ mock_host_resolver_ = NULL;
+ resolver_event_.Signal();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/dns/mock_host_resolver_creator.h b/chromium/extensions/browser/api/dns/mock_host_resolver_creator.h
new file mode 100644
index 00000000000..f57fdb8a4b3
--- /dev/null
+++ b/chromium/extensions/browser/api/dns/mock_host_resolver_creator.h
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DNS_MOCK_HOST_RESOLVER_CREATOR_H_
+#define EXTENSIONS_BROWSER_API_DNS_MOCK_HOST_RESOLVER_CREATOR_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/waitable_event.h"
+
+namespace net {
+class HostResolver;
+class MockHostResolver;
+}
+
+namespace extensions {
+
+// Used only for testing. Creates a MockHostResolver, respecting threading
+// constraints.
+class MockHostResolverCreator
+ : public base::RefCountedThreadSafe<MockHostResolverCreator> {
+ public:
+ static const std::string kHostname;
+ static const std::string kAddress;
+
+ MockHostResolverCreator();
+
+ net::HostResolver* CreateMockHostResolver();
+ void DeleteMockHostResolver();
+
+ private:
+ friend class base::RefCountedThreadSafe<MockHostResolverCreator>;
+ virtual ~MockHostResolverCreator();
+
+ void CreateMockHostResolverOnIOThread();
+ void DeleteMockHostResolverOnIOThread();
+
+ base::WaitableEvent resolver_event_;
+
+ // The MockHostResolver asserts that it's used on the same thread on which
+ // it's created, which is actually a stronger rule than its real counterpart.
+ // But that's fine; it's good practice.
+ //
+ // Plain pointer because we have to manage lifetime manually.
+ net::MockHostResolver* mock_host_resolver_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DNS_MOCK_HOST_RESOLVER_CREATOR_H_
diff --git a/chromium/extensions/browser/api/document_scan/DEPS b/chromium/extensions/browser/api/document_scan/DEPS
new file mode 100644
index 00000000000..a0e2dba0b9e
--- /dev/null
+++ b/chromium/extensions/browser/api/document_scan/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+third_party/cros_system_api",
+]
diff --git a/chromium/extensions/browser/api/document_scan/document_scan_api.cc b/chromium/extensions/browser/api/document_scan/document_scan_api.cc
new file mode 100644
index 00000000000..6cdaf1dd74d
--- /dev/null
+++ b/chromium/extensions/browser/api/document_scan/document_scan_api.cc
@@ -0,0 +1,124 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/document_scan/document_scan_api.h"
+
+#include <algorithm>
+
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/extension_system.h"
+
+using content::BrowserThread;
+
+namespace {
+
+const char kScannerNotAvailable[] = "Scanner not available";
+const char kUserGestureRequiredError[] =
+ "User gesture required to perform scan";
+
+} // namespace
+
+namespace extensions {
+
+namespace api {
+
+DocumentScanScanFunction::DocumentScanScanFunction()
+ : document_scan_interface_(DocumentScanInterface::CreateInstance()) {
+}
+
+DocumentScanScanFunction::~DocumentScanScanFunction() {
+}
+
+bool DocumentScanScanFunction::Prepare() {
+ set_work_thread_id(BrowserThread::FILE);
+ params_ = document_scan::Scan::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void DocumentScanScanFunction::AsyncWorkStart() {
+ if (!user_gesture()) {
+ error_ = kUserGestureRequiredError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ // Add a reference, which is balanced in OnScannerListReceived to keep the
+ // object around and allow the callback to be invoked.
+ AddRef();
+
+ document_scan_interface_->ListScanners(
+ base::Bind(&DocumentScanScanFunction::OnScannerListReceived,
+ base::Unretained(this)));
+}
+
+void DocumentScanScanFunction::OnScannerListReceived(
+ const std::vector<DocumentScanInterface::ScannerDescription>&
+ scanner_descriptions,
+ const std::string& error) {
+ std::vector<DocumentScanInterface::ScannerDescription>::const_iterator
+ scanner_i = scanner_descriptions.begin();
+
+ // If no |scanner_descriptions| is empty, this is an error. If no
+ // MIME types are specified, the first scanner is chosen. If MIME
+ // types are specified, the first scanner that supports one of these
+ // MIME types is selected.
+ if (params_->options.mime_types) {
+ std::vector<std::string>& mime_types = *params_->options.mime_types.get();
+ for (; scanner_i != scanner_descriptions.end(); ++scanner_i) {
+ if (std::find(mime_types.begin(), mime_types.end(),
+ scanner_i->image_mime_type) != mime_types.end()) {
+ break;
+ }
+ }
+ }
+
+ if (scanner_i == scanner_descriptions.end()) {
+ error_ = kScannerNotAvailable;
+ AsyncWorkCompleted();
+
+ // Balance the AddRef in AsyncWorkStart().
+ Release();
+ return;
+ }
+
+ // TODO(pstew): Call a delegate method here to select a scanner and options.
+
+ document_scan_interface_->Scan(
+ scanner_i->name, DocumentScanInterface::kScanModeColor, 0,
+ base::Bind(&DocumentScanScanFunction::OnResultsReceived,
+ base::Unretained(this)));
+}
+
+void DocumentScanScanFunction::OnResultsReceived(
+ const std::string& scanned_image,
+ const std::string& mime_type,
+ const std::string& error) {
+ // TODO(pstew): Enlist a delegate to display received scan in the UI
+ // and confirm that this scan should be sent to the caller. If this
+ // is a multi-page scan, provide a means for adding additional scanned
+ // images up to the requested limit.
+
+ if (error.empty()) {
+ document_scan::ScanResults scan_results;
+ if (!scanned_image.empty()) {
+ scan_results.data_urls.push_back(scanned_image);
+ }
+ scan_results.mime_type = mime_type;
+ results_ = document_scan::Scan::Results::Create(scan_results);
+ }
+ error_ = error;
+ AsyncWorkCompleted();
+
+ // Balance the AddRef in AsyncWorkStart().
+ Release();
+}
+
+bool DocumentScanScanFunction::Respond() {
+ return error_.empty();
+}
+
+} // namespace api
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/document_scan/document_scan_api.h b/chromium/extensions/browser/api/document_scan/document_scan_api.h
new file mode 100644
index 00000000000..efd0443ceff
--- /dev/null
+++ b/chromium/extensions/browser/api/document_scan/document_scan_api.h
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DOCUMENT_SCAN_DOCUMENT_SCAN_API_H_
+#define EXTENSIONS_BROWSER_API_DOCUMENT_SCAN_DOCUMENT_SCAN_API_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/browser/api/async_api_function.h"
+#include "extensions/browser/api/document_scan/document_scan_interface.h"
+#include "extensions/common/api/document_scan.h"
+
+namespace extensions {
+
+namespace api {
+
+class DocumentScanScanFunction : public AsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("documentScan.scan", DOCUMENT_SCAN_SCAN)
+ DocumentScanScanFunction();
+
+ protected:
+ ~DocumentScanScanFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+ bool Respond() override;
+
+ private:
+ friend class DocumentScanScanFunctionTest;
+
+ void OnScannerListReceived(
+ const std::vector<DocumentScanInterface::ScannerDescription>&
+ scanner_descriptions,
+ const std::string& error);
+ void OnResultsReceived(const std::string& scanned_image,
+ const std::string& mime_type,
+ const std::string& error);
+
+ scoped_ptr<document_scan::Scan::Params> params_;
+ scoped_ptr<DocumentScanInterface> document_scan_interface_;
+
+ DISALLOW_COPY_AND_ASSIGN(DocumentScanScanFunction);
+};
+
+} // namespace api
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DOCUMENT_SCAN_DOCUMENT_SCAN_API_H_
diff --git a/chromium/extensions/browser/api/document_scan/document_scan_api_unittest.cc b/chromium/extensions/browser/api/document_scan/document_scan_api_unittest.cc
new file mode 100644
index 00000000000..ddc4e4dd53f
--- /dev/null
+++ b/chromium/extensions/browser/api/document_scan/document_scan_api_unittest.cc
@@ -0,0 +1,120 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/document_scan/document_scan_api.h"
+
+#include <string>
+#include <vector>
+
+#include "extensions/browser/api/document_scan/mock_document_scan_interface.h"
+#include "extensions/browser/api_test_utils.h"
+#include "extensions/browser/api_unittest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+
+namespace extensions {
+
+namespace api {
+
+// Tests of networking_private_crypto support for Networking Private API.
+class DocumentScanScanFunctionTest : public ApiUnitTest {
+ public:
+ DocumentScanScanFunctionTest()
+ : function_(new DocumentScanScanFunction()),
+ document_scan_interface_(new MockDocumentScanInterface()) {}
+ ~DocumentScanScanFunctionTest() override {}
+
+ void SetUp() override {
+ ApiUnitTest::SetUp();
+ // Passes ownership.
+ function_->document_scan_interface_.reset(document_scan_interface_);
+ }
+
+ protected:
+ std::string RunFunctionAndReturnError(const std::string& args) {
+ function_->set_extension(extension());
+ std::string error = api_test_utils::RunFunctionAndReturnError(
+ function_, args, browser_context(), api_test_utils::NONE);
+ return error;
+ }
+
+ DocumentScanScanFunction* function_;
+ MockDocumentScanInterface* document_scan_interface_; // Owned by function_.
+};
+
+ACTION_P2(InvokeListScannersCallback, scanner_list, error) {
+ ::std::tr1::get<0>(args).Run(scanner_list, error);
+}
+
+ACTION_P3(InvokeScanCallback, data, mime_type, error) {
+ ::std::tr1::get<3>(args).Run(data, mime_type, error);
+}
+
+TEST_F(DocumentScanScanFunctionTest, GestureRequired) {
+ EXPECT_EQ("User gesture required to perform scan",
+ RunFunctionAndReturnError("[{}]"));
+}
+
+TEST_F(DocumentScanScanFunctionTest, NoScanners) {
+ function_->set_user_gesture(true);
+ EXPECT_CALL(*document_scan_interface_, ListScanners(_))
+ .WillOnce(InvokeListScannersCallback(
+ std::vector<DocumentScanInterface::ScannerDescription>(), ""));
+ EXPECT_EQ("Scanner not available", RunFunctionAndReturnError("[{}]"));
+}
+
+TEST_F(DocumentScanScanFunctionTest, NoMatchingScanners) {
+ function_->set_user_gesture(true);
+ std::vector<DocumentScanInterface::ScannerDescription> scanner_list;
+ DocumentScanInterface::ScannerDescription scanner;
+ scanner.image_mime_type = "img/fresco";
+ scanner_list.push_back(scanner);
+ EXPECT_CALL(*document_scan_interface_, ListScanners(_))
+ .WillOnce(InvokeListScannersCallback(scanner_list, ""));
+ EXPECT_EQ(
+ "Scanner not available",
+ RunFunctionAndReturnError("[{\"mimeTypes\": [\"img/silverpoint\"]}]"));
+}
+
+TEST_F(DocumentScanScanFunctionTest, ScanFailure) {
+ function_->set_user_gesture(true);
+ std::vector<DocumentScanInterface::ScannerDescription> scanner_list;
+ DocumentScanInterface::ScannerDescription scanner;
+ const char kMimeType[] = "img/tempera";
+ const char kScannerName[] = "Michelangelo";
+ scanner.name = kScannerName;
+ scanner.image_mime_type = kMimeType;
+ scanner_list.push_back(scanner);
+ EXPECT_CALL(*document_scan_interface_, ListScanners(_))
+ .WillOnce(InvokeListScannersCallback(scanner_list, ""));
+ const char kScanError[] = "Someone ate all the eggs";
+ EXPECT_CALL(*document_scan_interface_, Scan(kScannerName, _, _, _))
+ .WillOnce(InvokeScanCallback("", "", kScanError));
+ EXPECT_EQ(kScanError,
+ RunFunctionAndReturnError("[{\"mimeTypes\": [\"img/tempera\"]}]"));
+}
+
+TEST_F(DocumentScanScanFunctionTest, Success) {
+ std::vector<DocumentScanInterface::ScannerDescription> scanner_list;
+ scanner_list.push_back(DocumentScanInterface::ScannerDescription());
+ EXPECT_CALL(*document_scan_interface_, ListScanners(_))
+ .WillOnce(InvokeListScannersCallback(scanner_list, ""));
+ const char kScanData[] = "A beautiful picture";
+ const char kMimeType[] = "img/encaustic";
+ EXPECT_CALL(*document_scan_interface_, Scan(_, _, _, _))
+ .WillOnce(InvokeScanCallback(kScanData, kMimeType, ""));
+ function_->set_user_gesture(true);
+ scoped_ptr<base::DictionaryValue> result(
+ RunFunctionAndReturnDictionary(function_, "[{}]"));
+ ASSERT_NE(nullptr, result.get());
+ document_scan::ScanResults scan_results;
+ EXPECT_TRUE(document_scan::ScanResults::Populate(*result, &scan_results));
+ EXPECT_THAT(scan_results.data_urls, testing::ElementsAre(kScanData));
+ EXPECT_EQ(kMimeType, scan_results.mime_type);
+}
+
+} // namespace api
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/document_scan/document_scan_interface.cc b/chromium/extensions/browser/api/document_scan/document_scan_interface.cc
new file mode 100644
index 00000000000..976e1a9720d
--- /dev/null
+++ b/chromium/extensions/browser/api/document_scan/document_scan_interface.cc
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/document_scan/document_scan_interface.h"
+
+namespace extensions {
+
+namespace api {
+
+DocumentScanInterface::DocumentScanInterface() {
+}
+
+DocumentScanInterface::~DocumentScanInterface() {
+}
+
+DocumentScanInterface::ScannerDescription::ScannerDescription() {
+}
+
+DocumentScanInterface::ScannerDescription::ScannerDescription(
+ const ScannerDescription& other) = default;
+
+DocumentScanInterface::ScannerDescription::~ScannerDescription() {
+}
+
+} // namespace api
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/document_scan/document_scan_interface.h b/chromium/extensions/browser/api/document_scan/document_scan_interface.h
new file mode 100644
index 00000000000..dbcee196b23
--- /dev/null
+++ b/chromium/extensions/browser/api/document_scan/document_scan_interface.h
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DOCUMENT_SCAN_DOCUMENT_SCAN_INTERFACE_H_
+#define EXTENSIONS_BROWSER_API_DOCUMENT_SCAN_DOCUMENT_SCAN_INTERFACE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace extensions {
+
+namespace api {
+
+class DocumentScanInterface {
+ public:
+ struct ScannerDescription {
+ ScannerDescription();
+ ScannerDescription(const ScannerDescription& other);
+ ~ScannerDescription();
+ std::string name;
+ std::string manufacturer;
+ std::string model;
+ std::string scanner_type;
+ std::string image_mime_type;
+ };
+
+ enum ScanMode { kScanModeColor, kScanModeGray, kScanModeLineart };
+
+ typedef base::Callback<void(
+ const std::vector<ScannerDescription>& scanner_descriptions,
+ const std::string& error)> ListScannersResultsCallback;
+
+ typedef base::Callback<void(const std::string& scanned_image,
+ const std::string& mime_type,
+ const std::string& error)> ScanResultsCallback;
+
+ virtual ~DocumentScanInterface();
+
+ virtual void Scan(const std::string& scanner_name,
+ ScanMode mode,
+ int resolution_dpi,
+ const ScanResultsCallback& callback) = 0;
+ virtual void ListScanners(const ListScannersResultsCallback& callback) = 0;
+
+ // Creates a platform-specific DocumentScanInterface instance.
+ static DocumentScanInterface* CreateInstance();
+
+ protected:
+ DocumentScanInterface();
+};
+
+} // namespace api
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DOCUMENT_SCAN_DOCUMENT_SCAN_INTERFACE_H_
diff --git a/chromium/extensions/browser/api/document_scan/document_scan_interface_chromeos.cc b/chromium/extensions/browser/api/document_scan/document_scan_interface_chromeos.cc
new file mode 100644
index 00000000000..981688f4716
--- /dev/null
+++ b/chromium/extensions/browser/api/document_scan/document_scan_interface_chromeos.cc
@@ -0,0 +1,131 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/document_scan/document_scan_interface_chromeos.h"
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/lorgnette_manager_client.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+namespace {
+
+const char kImageScanFailedError[] = "Image scan failed";
+const char kScannerImageMimeTypePng[] = "image/png";
+const char kPngImageDataUrlPrefix[] = "data:image/png;base64,";
+
+} // namespace
+
+namespace extensions {
+
+namespace api {
+
+DocumentScanInterfaceChromeos::DocumentScanInterfaceChromeos()
+ : lorgnette_manager_client_(nullptr) {
+}
+
+DocumentScanInterfaceChromeos::~DocumentScanInterfaceChromeos() {
+}
+
+void DocumentScanInterfaceChromeos::ListScanners(
+ const ListScannersResultsCallback& callback) {
+ GetLorgnetteManagerClient()->ListScanners(
+ base::Bind(&DocumentScanInterfaceChromeos::OnScannerListReceived,
+ base::Unretained(this), callback));
+}
+
+void DocumentScanInterfaceChromeos::OnScannerListReceived(
+ const ListScannersResultsCallback& callback,
+ bool succeeded,
+ const chromeos::LorgnetteManagerClient::ScannerTable& scanners) {
+ std::vector<ScannerDescription> scanner_descriptions;
+ for (const auto& scanner : scanners) {
+ ScannerDescription description;
+ description.name = scanner.first;
+ const auto& entry = scanner.second;
+ auto info_it = entry.find(lorgnette::kScannerPropertyManufacturer);
+ if (info_it != entry.end()) {
+ description.manufacturer = info_it->second;
+ }
+ info_it = entry.find(lorgnette::kScannerPropertyModel);
+ if (info_it != entry.end()) {
+ description.model = info_it->second;
+ }
+ info_it = entry.find(lorgnette::kScannerPropertyType);
+ if (info_it != entry.end()) {
+ description.scanner_type = info_it->second;
+ }
+ description.image_mime_type = kScannerImageMimeTypePng;
+ scanner_descriptions.push_back(description);
+ }
+ const std::string kNoError;
+ callback.Run(scanner_descriptions, kNoError);
+}
+
+void DocumentScanInterfaceChromeos::Scan(const std::string& scanner_name,
+ ScanMode mode,
+ int resolution_dpi,
+ const ScanResultsCallback& callback) {
+ VLOG(1) << "Choosing scanner " << scanner_name;
+ chromeos::LorgnetteManagerClient::ScanProperties properties;
+ switch (mode) {
+ case kScanModeColor:
+ properties.mode = lorgnette::kScanPropertyModeColor;
+ break;
+
+ case kScanModeGray:
+ properties.mode = lorgnette::kScanPropertyModeGray;
+ break;
+
+ case kScanModeLineart:
+ properties.mode = lorgnette::kScanPropertyModeLineart;
+ break;
+ }
+
+ if (resolution_dpi != 0) {
+ properties.resolution_dpi = resolution_dpi;
+ }
+
+ GetLorgnetteManagerClient()->ScanImageToString(
+ scanner_name, properties,
+ base::Bind(&DocumentScanInterfaceChromeos::OnScanCompleted,
+ base::Unretained(this), callback));
+}
+
+void DocumentScanInterfaceChromeos::OnScanCompleted(
+ const ScanResultsCallback& callback,
+ bool succeeded,
+ const std::string& image_data) {
+ VLOG(1) << "ScanImage returns " << succeeded;
+ std::string error_string;
+ if (!succeeded) {
+ error_string = kImageScanFailedError;
+ }
+
+ std::string image_base64;
+ base::Base64Encode(image_data, &image_base64);
+ std::string image_url(std::string(kPngImageDataUrlPrefix) + image_base64);
+
+ callback.Run(image_url, kScannerImageMimeTypePng, error_string);
+}
+
+chromeos::LorgnetteManagerClient*
+DocumentScanInterfaceChromeos::GetLorgnetteManagerClient() {
+ if (!lorgnette_manager_client_) {
+ lorgnette_manager_client_ =
+ chromeos::DBusThreadManager::Get()->GetLorgnetteManagerClient();
+ CHECK(lorgnette_manager_client_);
+ }
+ return lorgnette_manager_client_;
+}
+
+// static
+DocumentScanInterface* DocumentScanInterface::CreateInstance() {
+ return new DocumentScanInterfaceChromeos();
+}
+
+} // namespace api
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/document_scan/document_scan_interface_chromeos.h b/chromium/extensions/browser/api/document_scan/document_scan_interface_chromeos.h
new file mode 100644
index 00000000000..b0651dd615e
--- /dev/null
+++ b/chromium/extensions/browser/api/document_scan/document_scan_interface_chromeos.h
@@ -0,0 +1,49 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DOCUMENT_SCAN_DOCUMENT_SCAN_INTERFACE_CHROMEOS_H_
+#define EXTENSIONS_BROWSER_API_DOCUMENT_SCAN_DOCUMENT_SCAN_INTERFACE_CHROMEOS_H_
+
+#include "base/macros.h"
+#include "chromeos/dbus/lorgnette_manager_client.h"
+#include "extensions/browser/api/document_scan/document_scan_interface.h"
+
+namespace extensions {
+
+namespace api {
+
+class DocumentScanInterfaceChromeos : public DocumentScanInterface {
+ public:
+ DocumentScanInterfaceChromeos();
+ ~DocumentScanInterfaceChromeos() override;
+
+ void ListScanners(const ListScannersResultsCallback& callback) override;
+ void Scan(const std::string& scanner_name,
+ ScanMode mode,
+ int resolution_dpi,
+ const ScanResultsCallback& callback) override;
+
+ private:
+ friend class DocumentScanInterfaceChromeosTest;
+
+ void OnScannerListReceived(
+ const ListScannersResultsCallback& callback,
+ bool succeeded,
+ const chromeos::LorgnetteManagerClient::ScannerTable& scanners);
+ void OnScanCompleted(const ScanResultsCallback& callback,
+ bool succeeded,
+ const std::string& image_data);
+ chromeos::LorgnetteManagerClient* GetLorgnetteManagerClient();
+
+ // Guaranteed to outlive |this|.
+ chromeos::LorgnetteManagerClient* lorgnette_manager_client_;
+
+ DISALLOW_COPY_AND_ASSIGN(DocumentScanInterfaceChromeos);
+};
+
+} // namespace api
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DOCUMENT_SCAN_DOCUMENT_SCAN_INTERFACE_CHROMEOS_H_
diff --git a/chromium/extensions/browser/api/document_scan/document_scan_interface_chromeos_unittest.cc b/chromium/extensions/browser/api/document_scan/document_scan_interface_chromeos_unittest.cc
new file mode 100644
index 00000000000..4de0597ddc8
--- /dev/null
+++ b/chromium/extensions/browser/api/document_scan/document_scan_interface_chromeos_unittest.cc
@@ -0,0 +1,127 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/document_scan/document_scan_interface_chromeos.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "chromeos/dbus/mock_lorgnette_manager_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+using testing::_;
+
+namespace extensions {
+
+namespace api {
+
+// Tests of networking_private_crypto support for Networking Private API.
+class DocumentScanInterfaceChromeosTest : public testing::Test {
+ public:
+ DocumentScanInterfaceChromeosTest()
+ : client_(new chromeos::MockLorgnetteManagerClient()) {}
+ ~DocumentScanInterfaceChromeosTest() override {}
+
+ void SetUp() override {
+ scan_interface_.lorgnette_manager_client_ = client_.get();
+ }
+
+ MOCK_METHOD2(OnListScannersResultReceived,
+ void(const std::vector<
+ DocumentScanInterface::ScannerDescription>& scanners,
+ const std::string& error));
+
+ MOCK_METHOD3(OnScanCompleted,
+ void(const std::string& scanned_image,
+ const std::string& mime_type,
+ const std::string& error));
+
+ protected:
+ DocumentScanInterfaceChromeos scan_interface_;
+ scoped_ptr<chromeos::MockLorgnetteManagerClient> client_;
+};
+
+ACTION_P2(InvokeListScannersCallback, scanner_list, error) {
+ ::std::tr1::get<0>(args).Run(scanner_list, error);
+}
+
+ACTION_P2(InvokeScanCallback, succeeded, image_data) {
+ ::std::tr1::get<2>(args).Run(succeeded, image_data);
+}
+
+MATCHER_P5(IsScannerDescription, name, manufacturer, model, type, mime, "") {
+ return arg.name == name && arg.manufacturer == manufacturer &&
+ arg.model == model && arg.scanner_type == type &&
+ arg.image_mime_type == mime;
+}
+
+MATCHER_P2(IsScannerProperties, mode, resolution, "") {
+ return arg.mode == mode && arg.resolution_dpi == resolution;
+}
+
+TEST_F(DocumentScanInterfaceChromeosTest, ListScanners) {
+ chromeos::LorgnetteManagerClient::ScannerTable scanners;
+ const char kScannerName[] = "Monet";
+ chromeos::LorgnetteManagerClient::ScannerTableEntry entry;
+ const char kScannerManufacturer[] = "Jacques-Louis David";
+ entry[lorgnette::kScannerPropertyManufacturer] = kScannerManufacturer;
+ const char kScannerModel[] = "Le Havre";
+ entry[lorgnette::kScannerPropertyModel] = kScannerModel;
+ const char kScannerType[] = "Impressionism";
+ entry[lorgnette::kScannerPropertyType] = kScannerType;
+ scanners[kScannerName] = entry;
+ EXPECT_CALL(*client_, ListScanners(_))
+ .WillOnce(InvokeListScannersCallback(true, scanners));
+ EXPECT_CALL(*this, OnListScannersResultReceived(
+ testing::ElementsAre(IsScannerDescription(
+ kScannerName, kScannerManufacturer, kScannerModel,
+ kScannerType, "image/png")),
+ ""));
+ scan_interface_.ListScanners(base::Bind(
+ &DocumentScanInterfaceChromeosTest::OnListScannersResultReceived,
+ base::Unretained(this)));
+}
+
+TEST_F(DocumentScanInterfaceChromeosTest, ScanFailure) {
+ const char kScannerName[] = "Monet";
+ const int kResolution = 4096;
+ EXPECT_CALL(*client_, ScanImageToString(
+ kScannerName,
+ IsScannerProperties(
+ lorgnette::kScanPropertyModeColor, kResolution),
+ _)).WillOnce(InvokeScanCallback(false, ""));
+ EXPECT_CALL(*this, OnScanCompleted("data:image/png;base64,", "image/png",
+ "Image scan failed"));
+ scan_interface_.Scan(
+ kScannerName, DocumentScanInterface::kScanModeColor, kResolution,
+ base::Bind(&DocumentScanInterfaceChromeosTest::OnScanCompleted,
+ base::Unretained(this)));
+}
+
+TEST_F(DocumentScanInterfaceChromeosTest, ScanSuccess) {
+ const char kScannerName[] = "Monet";
+ const int kResolution = 4096;
+ EXPECT_CALL(
+ *client_,
+ ScanImageToString(
+ kScannerName,
+ IsScannerProperties(lorgnette::kScanPropertyModeColor, kResolution),
+ _)).WillOnce(InvokeScanCallback(true, std::string("PrettyPicture")));
+
+ // Data URL plus base64 representation of "PrettyPicture".
+ const char kExpectedImageData[] =
+ "data:image/png;base64,UHJldHR5UGljdHVyZQ==";
+
+ EXPECT_CALL(*this, OnScanCompleted(kExpectedImageData, "image/png", ""));
+ scan_interface_.Scan(
+ kScannerName, DocumentScanInterface::kScanModeColor, kResolution,
+ base::Bind(&DocumentScanInterfaceChromeosTest::OnScanCompleted,
+ base::Unretained(this)));
+}
+
+} // namespace api
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/document_scan/document_scan_interface_nonchromeos.cc b/chromium/extensions/browser/api/document_scan/document_scan_interface_nonchromeos.cc
new file mode 100644
index 00000000000..2f8a41f6345
--- /dev/null
+++ b/chromium/extensions/browser/api/document_scan/document_scan_interface_nonchromeos.cc
@@ -0,0 +1,44 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/macros.h"
+#include "extensions/browser/api/document_scan/document_scan_interface.h"
+
+namespace {
+
+const char kScanFunctionNotImplementedError[] = "Scan function not implemented";
+
+} // namespace
+
+namespace extensions {
+
+namespace api {
+
+class DocumentScanInterfaceImpl : public DocumentScanInterface {
+ public:
+ DocumentScanInterfaceImpl() {}
+ ~DocumentScanInterfaceImpl() override {}
+
+ void ListScanners(const ListScannersResultsCallback& callback) override {
+ callback.Run(std::vector<ScannerDescription>(), "");
+ }
+ void Scan(const std::string& scanner_name,
+ ScanMode mode,
+ int resolution_dpi,
+ const ScanResultsCallback& callback) override {
+ callback.Run("", "", kScanFunctionNotImplementedError);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DocumentScanInterfaceImpl);
+};
+
+// static
+DocumentScanInterface* DocumentScanInterface::CreateInstance() {
+ return new DocumentScanInterfaceImpl();
+}
+
+} // namespace api
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/document_scan/mock_document_scan_interface.cc b/chromium/extensions/browser/api/document_scan/mock_document_scan_interface.cc
new file mode 100644
index 00000000000..7f31dfe7591
--- /dev/null
+++ b/chromium/extensions/browser/api/document_scan/mock_document_scan_interface.cc
@@ -0,0 +1,19 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/document_scan/mock_document_scan_interface.h"
+
+namespace extensions {
+
+namespace api {
+
+MockDocumentScanInterface::MockDocumentScanInterface() {
+}
+
+MockDocumentScanInterface::~MockDocumentScanInterface() {
+}
+
+} // namespace api
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/document_scan/mock_document_scan_interface.h b/chromium/extensions/browser/api/document_scan/mock_document_scan_interface.h
new file mode 100644
index 00000000000..51c66683caf
--- /dev/null
+++ b/chromium/extensions/browser/api/document_scan/mock_document_scan_interface.h
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DOCUMENT_SCAN_MOCK_DOCUMENT_SCAN_INTERFACE_H_
+#define EXTENSIONS_BROWSER_API_DOCUMENT_SCAN_MOCK_DOCUMENT_SCAN_INTERFACE_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "extensions/browser/api/document_scan/document_scan_interface.h"
+
+namespace extensions {
+
+namespace api {
+
+class MockDocumentScanInterface : public DocumentScanInterface {
+ public:
+ MockDocumentScanInterface();
+ ~MockDocumentScanInterface() override;
+
+ MOCK_METHOD4(Scan,
+ void(const std::string& scanner_name,
+ ScanMode mode,
+ int resolution_dpi,
+ const ScanResultsCallback& callback));
+ MOCK_METHOD1(ListScanners, void(const ListScannersResultsCallback& callback));
+ MOCK_CONST_METHOD0(GetImageMimeType, std::string());
+};
+
+} // namespace api
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DOCUMENT_SCAN_MOCK_DOCUMENT_SCAN_INTERFACE_H_
diff --git a/chromium/extensions/browser/api/execute_code_function.cc b/chromium/extensions/browser/api/execute_code_function.cc
new file mode 100644
index 00000000000..53fcb44a47e
--- /dev/null
+++ b/chromium/extensions/browser/api/execute_code_function.cc
@@ -0,0 +1,249 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_EXECUTE_CODE_FUNCTION_IMPL_H_
+#define EXTENSIONS_BROWSER_API_EXECUTE_CODE_FUNCTION_IMPL_H_
+
+#include "extensions/browser/api/execute_code_function.h"
+
+#include "extensions/browser/component_extension_resource_manager.h"
+#include "extensions/browser/extension_api_frame_id_map.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/file_reader.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/file_util.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/message_bundle.h"
+#include "net/base/filename_util.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace {
+
+// Error messages
+const char kNoCodeOrFileToExecuteError[] = "No source code or file specified.";
+const char kMoreThanOneValuesError[] =
+ "Code and file should not be specified "
+ "at the same time in the second argument.";
+const char kBadFileEncodingError[] =
+ "Could not load file '*' for content script. It isn't UTF-8 encoded.";
+const char kLoadFileError[] = "Failed to load file: \"*\". ";
+
+}
+
+namespace extensions {
+
+using api::extension_types::InjectDetails;
+
+ExecuteCodeFunction::ExecuteCodeFunction() {
+}
+
+ExecuteCodeFunction::~ExecuteCodeFunction() {
+}
+
+void ExecuteCodeFunction::DidLoadFile(bool success, const std::string& data) {
+ if (!success || !details_->file) {
+ DidLoadAndLocalizeFile(
+ resource_.relative_path().AsUTF8Unsafe(), success, data);
+ return;
+ }
+
+ ScriptExecutor::ScriptType script_type =
+ ShouldInsertCSS() ? ScriptExecutor::CSS : ScriptExecutor::JAVASCRIPT;
+
+ std::string extension_id;
+ base::FilePath extension_path;
+ std::string extension_default_locale;
+ if (extension()) {
+ extension_id = extension()->id();
+ extension_path = extension()->path();
+ extension()->manifest()->GetString(manifest_keys::kDefaultLocale,
+ &extension_default_locale);
+ }
+
+ content::BrowserThread::PostTask(
+ content::BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&ExecuteCodeFunction::GetFileURLAndLocalizeCSS,
+ this,
+ script_type,
+ data,
+ extension_id,
+ extension_path,
+ extension_default_locale));
+}
+
+void ExecuteCodeFunction::GetFileURLAndLocalizeCSS(
+ ScriptExecutor::ScriptType script_type,
+ const std::string& data,
+ const std::string& extension_id,
+ const base::FilePath& extension_path,
+ const std::string& extension_default_locale) {
+ std::string localized_data = data;
+ // Check if the file is CSS and needs localization.
+ if ((script_type == ScriptExecutor::CSS) && !extension_id.empty() &&
+ (data.find(MessageBundle::kMessageBegin) != std::string::npos)) {
+ scoped_ptr<SubstitutionMap> localization_messages(
+ file_util::LoadMessageBundleSubstitutionMap(
+ extension_path, extension_id, extension_default_locale));
+
+ // We need to do message replacement on the data, so it has to be mutable.
+ std::string error;
+ MessageBundle::ReplaceMessagesWithExternalDictionary(
+ *localization_messages, &localized_data, &error);
+ }
+
+ file_url_ = net::FilePathToFileURL(resource_.GetFilePath());
+
+ // Call back DidLoadAndLocalizeFile on the UI thread. The success parameter
+ // is always true, because if loading had failed, we wouldn't have had
+ // anything to localize.
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&ExecuteCodeFunction::DidLoadAndLocalizeFile,
+ this,
+ resource_.relative_path().AsUTF8Unsafe(),
+ true,
+ localized_data));
+}
+
+void ExecuteCodeFunction::DidLoadAndLocalizeFile(const std::string& file,
+ bool success,
+ const std::string& data) {
+ if (success) {
+ if (!base::IsStringUTF8(data)) {
+ error_ = ErrorUtils::FormatErrorMessage(kBadFileEncodingError, file);
+ SendResponse(false);
+ } else if (!Execute(data))
+ SendResponse(false);
+ } else {
+ // TODO(viettrungluu): bug: there's no particular reason the path should be
+ // UTF-8, in which case this may fail.
+ error_ = ErrorUtils::FormatErrorMessage(kLoadFileError, file);
+ SendResponse(false);
+ }
+}
+
+bool ExecuteCodeFunction::Execute(const std::string& code_string) {
+ ScriptExecutor* executor = GetScriptExecutor();
+ if (!executor)
+ return false;
+
+ if (!extension() && !IsWebView())
+ return false;
+
+ ScriptExecutor::ScriptType script_type = ScriptExecutor::JAVASCRIPT;
+ if (ShouldInsertCSS())
+ script_type = ScriptExecutor::CSS;
+
+ ScriptExecutor::FrameScope frame_scope =
+ details_->all_frames.get() && *details_->all_frames
+ ? ScriptExecutor::INCLUDE_SUB_FRAMES
+ : ScriptExecutor::SINGLE_FRAME;
+
+ int frame_id = details_->frame_id.get() ? *details_->frame_id
+ : ExtensionApiFrameIdMap::kTopFrameId;
+
+ ScriptExecutor::MatchAboutBlank match_about_blank =
+ details_->match_about_blank.get() && *details_->match_about_blank
+ ? ScriptExecutor::MATCH_ABOUT_BLANK
+ : ScriptExecutor::DONT_MATCH_ABOUT_BLANK;
+
+ UserScript::RunLocation run_at = UserScript::UNDEFINED;
+ switch (details_->run_at) {
+ case api::extension_types::RUN_AT_NONE:
+ case api::extension_types::RUN_AT_DOCUMENT_IDLE:
+ run_at = UserScript::DOCUMENT_IDLE;
+ break;
+ case api::extension_types::RUN_AT_DOCUMENT_START:
+ run_at = UserScript::DOCUMENT_START;
+ break;
+ case api::extension_types::RUN_AT_DOCUMENT_END:
+ run_at = UserScript::DOCUMENT_END;
+ break;
+ }
+ CHECK_NE(UserScript::UNDEFINED, run_at);
+
+ executor->ExecuteScript(
+ host_id_, script_type, code_string, frame_scope, frame_id,
+ match_about_blank, run_at, ScriptExecutor::ISOLATED_WORLD,
+ IsWebView() ? ScriptExecutor::WEB_VIEW_PROCESS
+ : ScriptExecutor::DEFAULT_PROCESS,
+ GetWebViewSrc(), file_url_, user_gesture_,
+ has_callback() ? ScriptExecutor::JSON_SERIALIZED_RESULT
+ : ScriptExecutor::NO_RESULT,
+ base::Bind(&ExecuteCodeFunction::OnExecuteCodeFinished, this));
+ return true;
+}
+
+bool ExecuteCodeFunction::HasPermission() {
+ return true;
+}
+
+bool ExecuteCodeFunction::RunAsync() {
+ EXTENSION_FUNCTION_VALIDATE(Init());
+
+ if (!details_->code.get() && !details_->file.get()) {
+ error_ = kNoCodeOrFileToExecuteError;
+ return false;
+ }
+ if (details_->code.get() && details_->file.get()) {
+ error_ = kMoreThanOneValuesError;
+ return false;
+ }
+
+ if (!CanExecuteScriptOnPage())
+ return false;
+
+ if (details_->code.get())
+ return Execute(*details_->code);
+
+ if (!details_->file.get())
+ return false;
+
+ return LoadFile(*details_->file);
+}
+
+bool ExecuteCodeFunction::LoadFile(const std::string& file) {
+ resource_ = extension()->GetResource(file);
+
+ if (resource_.extension_root().empty() || resource_.relative_path().empty()) {
+ error_ = kNoCodeOrFileToExecuteError;
+ return false;
+ }
+
+ int resource_id;
+ const ComponentExtensionResourceManager*
+ component_extension_resource_manager =
+ ExtensionsBrowserClient::Get()
+ ->GetComponentExtensionResourceManager();
+ if (component_extension_resource_manager &&
+ component_extension_resource_manager->IsComponentExtensionResource(
+ resource_.extension_root(),
+ resource_.relative_path(),
+ &resource_id)) {
+ const ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ DidLoadFile(true, rb.GetRawDataResource(resource_id).as_string());
+ } else {
+ scoped_refptr<FileReader> file_reader(new FileReader(
+ resource_, base::Bind(&ExecuteCodeFunction::DidLoadFile, this)));
+ file_reader->Start();
+ }
+
+ return true;
+}
+
+void ExecuteCodeFunction::OnExecuteCodeFinished(const std::string& error,
+ const GURL& on_url,
+ const base::ListValue& result) {
+ if (!error.empty())
+ SetError(error);
+
+ SendResponse(error.empty());
+}
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_EXECUTE_CODE_FUNCTION_IMPL_H_
diff --git a/chromium/extensions/browser/api/execute_code_function.h b/chromium/extensions/browser/api/execute_code_function.h
new file mode 100644
index 00000000000..bf984f48dd8
--- /dev/null
+++ b/chromium/extensions/browser/api/execute_code_function.h
@@ -0,0 +1,85 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_EXECUTE_CODE_FUNCTION_H_
+#define EXTENSIONS_BROWSER_API_EXECUTE_CODE_FUNCTION_H_
+
+#include "extensions/browser/extension_function.h"
+#include "extensions/browser/script_executor.h"
+#include "extensions/common/api/extension_types.h"
+#include "extensions/common/host_id.h"
+
+namespace extensions {
+
+// Base class for javascript code injection.
+// This is used by both chrome.webview.executeScript and
+// chrome.tabs.executeScript.
+class ExecuteCodeFunction : public AsyncExtensionFunction {
+ public:
+ ExecuteCodeFunction();
+
+ protected:
+ ~ExecuteCodeFunction() override;
+
+ // ExtensionFunction implementation.
+ bool HasPermission() override;
+ bool RunAsync() override;
+
+ // Initialize |details_| if it hasn't already been.
+ virtual bool Init() = 0;
+ virtual bool ShouldInsertCSS() const = 0;
+ virtual bool CanExecuteScriptOnPage() = 0;
+ virtual ScriptExecutor* GetScriptExecutor() = 0;
+ virtual bool IsWebView() const = 0;
+ virtual const GURL& GetWebViewSrc() const = 0;
+ virtual void OnExecuteCodeFinished(const std::string& error,
+ const GURL& on_url,
+ const base::ListValue& result);
+
+ virtual bool LoadFile(const std::string& file);
+
+ // Called when contents from the loaded file have been localized.
+ void DidLoadAndLocalizeFile(const std::string& file,
+ bool success,
+ const std::string& data);
+
+ const HostID& host_id() const { return host_id_; }
+ void set_host_id(HostID host_id) {
+ host_id_ = host_id;
+ }
+
+ // The injection details.
+ scoped_ptr<api::extension_types::InjectDetails> details_;
+
+ private:
+ // Called when contents from the file whose path is specified in JSON
+ // arguments has been loaded.
+ void DidLoadFile(bool success, const std::string& data);
+
+ // Runs on FILE thread. Loads message bundles for the extension and
+ // localizes the CSS data. Calls back DidLoadAndLocalizeFile on the UI thread.
+ void GetFileURLAndLocalizeCSS(ScriptExecutor::ScriptType script_type,
+ const std::string& data,
+ const std::string& extension_id,
+ const base::FilePath& extension_path,
+ const std::string& extension_default_locale);
+
+ // Run in UI thread. Code string contains the code to be executed. Returns
+ // true on success. If true is returned, this does an AddRef.
+ bool Execute(const std::string& code_string);
+
+ // Contains extension resource built from path of file which is
+ // specified in JSON arguments.
+ ExtensionResource resource_;
+
+ // The URL of the file being injected into the page.
+ GURL file_url_;
+
+ // The ID of the injection host.
+ HostID host_id_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_EXECUTE_CODE_FUNCTION_H_
diff --git a/chromium/extensions/browser/api/extensions_api_client.cc b/chromium/extensions/browser/api/extensions_api_client.cc
new file mode 100644
index 00000000000..bfc08d3dae3
--- /dev/null
+++ b/chromium/extensions/browser/api/extensions_api_client.cc
@@ -0,0 +1,101 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/extensions_api_client.h"
+
+#include "base/logging.h"
+#include "extensions/browser/api/device_permissions_prompt.h"
+#include "extensions/browser/api/virtual_keyboard_private/virtual_keyboard_delegate.h"
+#include "extensions/browser/api/web_request/web_request_event_router_delegate.h"
+#include "extensions/browser/guest_view/extensions_guest_view_manager_delegate.h"
+#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest_delegate.h"
+#include "extensions/browser/guest_view/web_view/web_view_permission_helper_delegate.h"
+
+namespace extensions {
+class AppViewGuestDelegate;
+
+namespace {
+ExtensionsAPIClient* g_instance = NULL;
+} // namespace
+
+ExtensionsAPIClient::ExtensionsAPIClient() { g_instance = this; }
+
+ExtensionsAPIClient::~ExtensionsAPIClient() { g_instance = NULL; }
+
+// static
+ExtensionsAPIClient* ExtensionsAPIClient::Get() { return g_instance; }
+
+void ExtensionsAPIClient::AddAdditionalValueStoreCaches(
+ content::BrowserContext* context,
+ const scoped_refptr<ValueStoreFactory>& factory,
+ const scoped_refptr<base::ObserverListThreadSafe<SettingsObserver>>&
+ observers,
+ std::map<settings_namespace::Namespace, ValueStoreCache*>* caches) {}
+
+void ExtensionsAPIClient::AttachWebContentsHelpers(
+ content::WebContents* web_contents) const {
+}
+
+AppViewGuestDelegate* ExtensionsAPIClient::CreateAppViewGuestDelegate() const {
+ return NULL;
+}
+
+ExtensionOptionsGuestDelegate*
+ExtensionsAPIClient::CreateExtensionOptionsGuestDelegate(
+ ExtensionOptionsGuest* guest) const {
+ return NULL;
+}
+
+scoped_ptr<guest_view::GuestViewManagerDelegate>
+ExtensionsAPIClient::CreateGuestViewManagerDelegate(
+ content::BrowserContext* context) const {
+ return make_scoped_ptr(new ExtensionsGuestViewManagerDelegate(context));
+}
+
+scoped_ptr<MimeHandlerViewGuestDelegate>
+ExtensionsAPIClient::CreateMimeHandlerViewGuestDelegate(
+ MimeHandlerViewGuest* guest) const {
+ return scoped_ptr<MimeHandlerViewGuestDelegate>();
+}
+
+WebViewGuestDelegate* ExtensionsAPIClient::CreateWebViewGuestDelegate(
+ WebViewGuest* web_view_guest) const {
+ return NULL;
+}
+
+WebViewPermissionHelperDelegate* ExtensionsAPIClient::
+ CreateWebViewPermissionHelperDelegate(
+ WebViewPermissionHelper* web_view_permission_helper) const {
+ return new WebViewPermissionHelperDelegate(web_view_permission_helper);
+}
+
+WebRequestEventRouterDelegate*
+ExtensionsAPIClient::CreateWebRequestEventRouterDelegate() const {
+ return new WebRequestEventRouterDelegate();
+}
+
+scoped_refptr<ContentRulesRegistry>
+ExtensionsAPIClient::CreateContentRulesRegistry(
+ content::BrowserContext* browser_context,
+ RulesCacheDelegate* cache_delegate) const {
+ return scoped_refptr<ContentRulesRegistry>();
+}
+
+scoped_ptr<DevicePermissionsPrompt>
+ExtensionsAPIClient::CreateDevicePermissionsPrompt(
+ content::WebContents* web_contents) const {
+ return nullptr;
+}
+
+scoped_ptr<VirtualKeyboardDelegate>
+ExtensionsAPIClient::CreateVirtualKeyboardDelegate() const {
+ return nullptr;
+}
+
+ManagementAPIDelegate* ExtensionsAPIClient::CreateManagementAPIDelegate()
+ const {
+ return nullptr;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/extensions_api_client.h b/chromium/extensions/browser/api/extensions_api_client.h
new file mode 100644
index 00000000000..d68174d935d
--- /dev/null
+++ b/chromium/extensions/browser/api/extensions_api_client.h
@@ -0,0 +1,136 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_EXTENSIONS_API_CLIENT_H_
+#define EXTENSIONS_BROWSER_API_EXTENSIONS_API_CLIENT_H_
+
+#include <map>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/browser/api/declarative_content/content_rules_registry.h"
+#include "extensions/browser/api/storage/settings_namespace.h"
+
+class GURL;
+
+namespace base {
+template <class T>
+class ObserverListThreadSafe;
+}
+
+namespace content {
+class BrowserContext;
+class WebContents;
+}
+
+namespace guest_view {
+class GuestViewManagerDelegate;
+} // namespace guest_view
+
+namespace extensions {
+
+class AppViewGuestDelegate;
+class ContentRulesRegistry;
+class DevicePermissionsPrompt;
+class ExtensionOptionsGuest;
+class ExtensionOptionsGuestDelegate;
+class ManagementAPIDelegate;
+class MimeHandlerViewGuest;
+class MimeHandlerViewGuestDelegate;
+class RulesCacheDelegate;
+class SettingsObserver;
+class ValueStoreCache;
+class ValueStoreFactory;
+class VirtualKeyboardDelegate;
+class WebRequestEventRouterDelegate;
+class WebViewGuest;
+class WebViewGuestDelegate;
+class WebViewPermissionHelper;
+class WebViewPermissionHelperDelegate;
+
+// Allows the embedder of the extensions module to customize its support for
+// API features. The embedder must create a single instance in the browser
+// process. Provides a default implementation that does nothing.
+class ExtensionsAPIClient {
+ public:
+ // Construction sets the single instance.
+ ExtensionsAPIClient();
+
+ // Destruction clears the single instance.
+ virtual ~ExtensionsAPIClient();
+
+ // Returns the single instance of |this|.
+ static ExtensionsAPIClient* Get();
+
+ // Storage API support.
+
+ // Add any additional value store caches (e.g. for chrome.storage.managed)
+ // to |caches|. By default adds nothing.
+ virtual void AddAdditionalValueStoreCaches(
+ content::BrowserContext* context,
+ const scoped_refptr<ValueStoreFactory>& factory,
+ const scoped_refptr<base::ObserverListThreadSafe<SettingsObserver>>&
+ observers,
+ std::map<settings_namespace::Namespace, ValueStoreCache*>* caches);
+
+ // Attaches any extra web contents helpers (like ExtensionWebContentsObserver)
+ // to |web_contents|.
+ virtual void AttachWebContentsHelpers(content::WebContents* web_contents)
+ const;
+
+ // Creates the AppViewGuestDelegate.
+ virtual AppViewGuestDelegate* CreateAppViewGuestDelegate() const;
+
+ // Returns a delegate for ExtensionOptionsGuest. The caller owns the returned
+ // ExtensionOptionsGuestDelegate.
+ virtual ExtensionOptionsGuestDelegate* CreateExtensionOptionsGuestDelegate(
+ ExtensionOptionsGuest* guest) const;
+
+ // Returns a delegate for GuestViewManagerDelegate.
+ virtual scoped_ptr<guest_view::GuestViewManagerDelegate>
+ CreateGuestViewManagerDelegate(content::BrowserContext* context) const;
+
+ // Creates a delegate for MimeHandlerViewGuest.
+ virtual scoped_ptr<MimeHandlerViewGuestDelegate>
+ CreateMimeHandlerViewGuestDelegate(MimeHandlerViewGuest* guest) const;
+
+ // Returns a delegate for some of WebViewGuest's behavior. The caller owns the
+ // returned WebViewGuestDelegate.
+ virtual WebViewGuestDelegate* CreateWebViewGuestDelegate(
+ WebViewGuest* web_view_guest) const;
+
+ // Returns a delegate for some of WebViewPermissionHelper's behavior. The
+ // caller owns the returned WebViewPermissionHelperDelegate.
+ virtual WebViewPermissionHelperDelegate*
+ CreateWebViewPermissionHelperDelegate(
+ WebViewPermissionHelper* web_view_permission_helper) const;
+
+ // Creates a delegate for WebRequestEventRouter.
+ virtual WebRequestEventRouterDelegate* CreateWebRequestEventRouterDelegate()
+ const;
+
+ // TODO(wjmaclean): Remove this when (if) ContentRulesRegistry code moves
+ // to extensions/browser/api.
+ virtual scoped_refptr<ContentRulesRegistry> CreateContentRulesRegistry(
+ content::BrowserContext* browser_context,
+ RulesCacheDelegate* cache_delegate) const;
+
+ // Creates a DevicePermissionsPrompt appropriate for the embedder.
+ virtual scoped_ptr<DevicePermissionsPrompt> CreateDevicePermissionsPrompt(
+ content::WebContents* web_contents) const;
+
+ // Returns a delegate for some of VirtualKeyboardAPI's behavior.
+ virtual scoped_ptr<VirtualKeyboardDelegate> CreateVirtualKeyboardDelegate()
+ const;
+
+ // Creates a delegate for handling the management extension api.
+ virtual ManagementAPIDelegate* CreateManagementAPIDelegate() const;
+
+ // NOTE: If this interface gains too many methods (perhaps more than 20) it
+ // should be split into one interface per API.
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_EXTENSIONS_API_CLIENT_H_
diff --git a/chromium/extensions/browser/api/guest_view/OWNERS b/chromium/extensions/browser/api/guest_view/OWNERS
new file mode 100644
index 00000000000..ccd93487931
--- /dev/null
+++ b/chromium/extensions/browser/api/guest_view/OWNERS
@@ -0,0 +1,5 @@
+fsamuel@chromium.org
+lazyboy@chromium.org
+hanxi@chromium.org
+wjmaclean@chromium.org
+lfg@chromium.org
diff --git a/chromium/extensions/browser/api/guest_view/app_view/app_view_guest_internal_api.cc b/chromium/extensions/browser/api/guest_view/app_view/app_view_guest_internal_api.cc
new file mode 100644
index 00000000000..bacf7cd1c8f
--- /dev/null
+++ b/chromium/extensions/browser/api/guest_view/app_view/app_view_guest_internal_api.cc
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/guest_view/app_view/app_view_guest_internal_api.h"
+
+#include "content/public/browser/render_frame_host.h"
+#include "extensions/browser/guest_view/app_view/app_view_guest.h"
+#include "extensions/common/api/app_view_guest_internal.h"
+
+namespace extensions {
+
+namespace appview = api::app_view_guest_internal;
+
+AppViewGuestInternalAttachFrameFunction::
+ AppViewGuestInternalAttachFrameFunction() {
+}
+
+bool AppViewGuestInternalAttachFrameFunction::RunAsync() {
+ scoped_ptr<appview::AttachFrame::Params> params(
+ appview::AttachFrame::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ GURL url = extension()->GetResourceURL(params->url);
+ EXTENSION_FUNCTION_VALIDATE(url.is_valid());
+
+ return AppViewGuest::CompletePendingRequest(
+ browser_context(), url, params->guest_instance_id, extension_id(),
+ render_frame_host()->GetProcess());
+}
+
+AppViewGuestInternalDenyRequestFunction::
+ AppViewGuestInternalDenyRequestFunction() {
+}
+
+bool AppViewGuestInternalDenyRequestFunction::RunAsync() {
+ scoped_ptr<appview::DenyRequest::Params> params(
+ appview::DenyRequest::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ // Since the URL passed into AppViewGuest:::CompletePendingRequest is invalid,
+ // a new <appview> WebContents will not be created.
+ return AppViewGuest::CompletePendingRequest(
+ browser_context(), GURL(), params->guest_instance_id, extension_id(),
+ render_frame_host()->GetProcess());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/guest_view/app_view/app_view_guest_internal_api.h b/chromium/extensions/browser/api/guest_view/app_view/app_view_guest_internal_api.h
new file mode 100644
index 00000000000..e9e252bb2d9
--- /dev/null
+++ b/chromium/extensions/browser/api/guest_view/app_view/app_view_guest_internal_api.h
@@ -0,0 +1,43 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_APP_VIEW_APP_VIEW_GUEST_INTERNAL_API_H_
+#define EXTENSIONS_BROWSER_API_APP_VIEW_APP_VIEW_GUEST_INTERNAL_API_H_
+
+#include "base/macros.h"
+#include "extensions/browser/extension_function.h"
+
+namespace extensions {
+
+class AppViewGuestInternalAttachFrameFunction : public AsyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("appViewGuestInternal.attachFrame",
+ APPVIEWINTERNAL_ATTACHFRAME);
+ AppViewGuestInternalAttachFrameFunction();
+
+ protected:
+ ~AppViewGuestInternalAttachFrameFunction() override {}
+ bool RunAsync() final;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AppViewGuestInternalAttachFrameFunction);
+};
+
+class AppViewGuestInternalDenyRequestFunction : public AsyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("appViewGuestInternal.denyRequest",
+ APPVIEWINTERNAL_DENYREQUEST);
+ AppViewGuestInternalDenyRequestFunction();
+
+ protected:
+ ~AppViewGuestInternalDenyRequestFunction() override {}
+ bool RunAsync() final;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AppViewGuestInternalDenyRequestFunction);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_APP_VIEW_APP_VIEW_GUEST_INTERNAL_API_H_
diff --git a/chromium/extensions/browser/api/guest_view/extension_view/extension_view_internal_api.cc b/chromium/extensions/browser/api/guest_view/extension_view/extension_view_internal_api.cc
new file mode 100644
index 00000000000..878c4b583b1
--- /dev/null
+++ b/chromium/extensions/browser/api/guest_view/extension_view/extension_view_internal_api.cc
@@ -0,0 +1,88 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/guest_view/extension_view/extension_view_internal_api.h"
+
+#include <utility>
+
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/crx_file/id_util.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/stop_find_action.h"
+#include "extensions/browser/guest_view/extension_view/whitelist/extension_view_whitelist.h"
+#include "extensions/common/api/extension_view_internal.h"
+#include "extensions/common/constants.h"
+
+namespace extensionview = extensions::api::extension_view_internal;
+
+namespace extensions {
+
+bool ExtensionViewInternalExtensionFunction::RunAsync() {
+ int instance_id = 0;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &instance_id));
+ ExtensionViewGuest* guest = ExtensionViewGuest::From(
+ render_frame_host()->GetProcess()->GetID(), instance_id);
+ if (!guest)
+ return false;
+ return RunAsyncSafe(guest);
+}
+
+// Checks the validity of |src|, including that it follows the chrome extension
+// scheme and that its extension ID is valid.
+// Returns true if |src| is valid.
+bool IsSrcValid(GURL src) {
+ // Check if src is valid and matches the extension scheme.
+ if (!src.is_valid() || !src.SchemeIs(kExtensionScheme))
+ return false;
+
+ // Get the extension id and check if it is valid.
+ std::string extension_id = src.host();
+ if (!crx_file::id_util::IdIsValid(extension_id) ||
+ !IsExtensionIdWhitelisted(extension_id))
+ return false;
+
+ return true;
+}
+
+bool ExtensionViewInternalLoadSrcFunction::RunAsyncSafe(
+ ExtensionViewGuest* guest) {
+ scoped_ptr<extensionview::LoadSrc::Params> params(
+ extensionview::LoadSrc::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ std::string src = params->src;
+ GURL url(src);
+ bool has_load_succeeded = false;
+ bool is_src_valid = IsSrcValid(url);
+
+ if (is_src_valid)
+ has_load_succeeded = guest->NavigateGuest(src, true /* force_navigation */);
+
+ // Return whether load is successful.
+ SetResult(new base::FundamentalValue(has_load_succeeded));
+ SendResponse(true);
+ return true;
+}
+
+bool ExtensionViewInternalParseSrcFunction::RunAsync() {
+ scoped_ptr<extensionview::ParseSrc::Params> params(
+ extensionview::ParseSrc::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ GURL url(params->src);
+ bool is_src_valid = IsSrcValid(url);
+
+ // Return whether the src is valid and the current extension ID to
+ // the callback.
+ scoped_ptr<base::ListValue> result_list(new base::ListValue());
+ result_list->AppendBoolean(is_src_valid);
+ result_list->AppendString(url.host());
+ SetResultList(std::move(result_list));
+ SendResponse(true);
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/guest_view/extension_view/extension_view_internal_api.h b/chromium/extensions/browser/api/guest_view/extension_view/extension_view_internal_api.h
new file mode 100644
index 00000000000..1899d42ae19
--- /dev/null
+++ b/chromium/extensions/browser/api/guest_view/extension_view/extension_view_internal_api.h
@@ -0,0 +1,69 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_EXTENSION_VIEW_EXTENSION_VIEW_INTERNAL_API_H_
+#define EXTENSIONS_BROWSER_API_EXTENSION_VIEW_EXTENSION_VIEW_INTERNAL_API_H_
+
+#include "base/macros.h"
+#include "extensions/browser/api/execute_code_function.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/browser/guest_view/extension_view/extension_view_guest.h"
+
+namespace extensions {
+
+// An abstract base class for async extensionview APIs. It does a process ID
+// check in RunAsync, and then calls RunAsyncSafe which must be overriden by
+// all subclasses.
+class ExtensionViewInternalExtensionFunction : public AsyncExtensionFunction {
+ public:
+ ExtensionViewInternalExtensionFunction() {}
+
+ protected:
+ ~ExtensionViewInternalExtensionFunction() override {}
+
+ // ExtensionFunction implementation.
+ bool RunAsync() final;
+
+ private:
+ virtual bool RunAsyncSafe(ExtensionViewGuest* guest) = 0;
+};
+
+// Attempts to load a src into the extensionview.
+class ExtensionViewInternalLoadSrcFunction
+ : public ExtensionViewInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("extensionViewInternal.loadSrc",
+ EXTENSIONVIEWINTERNAL_LOADSRC);
+ ExtensionViewInternalLoadSrcFunction() {}
+
+ protected:
+ ~ExtensionViewInternalLoadSrcFunction() override {}
+
+ private:
+ // ExtensionViewInternalLoadSrcFunction implementation.
+ bool RunAsyncSafe(ExtensionViewGuest* guest) override;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionViewInternalLoadSrcFunction);
+};
+
+// Parses a src and determines whether or not it is valid.
+class ExtensionViewInternalParseSrcFunction : public AsyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("extensionViewInternal.parseSrc",
+ EXTENSIONVIEWINTERNAL_PARSESRC);
+ ExtensionViewInternalParseSrcFunction() {}
+
+ protected:
+ ~ExtensionViewInternalParseSrcFunction() override {}
+
+ private:
+ // ExtensionViewInternalParseSrcFunction implementation.
+ bool RunAsync() final;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionViewInternalParseSrcFunction);
+};
+
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_API_EXTENSION_VIEW_EXTENSION_VIEW_INTERNAL_API_H_
diff --git a/chromium/extensions/browser/api/guest_view/guest_view_internal_api.cc b/chromium/extensions/browser/api/guest_view/guest_view_internal_api.cc
new file mode 100644
index 00000000000..6a60f566b7d
--- /dev/null
+++ b/chromium/extensions/browser/api/guest_view/guest_view_internal_api.cc
@@ -0,0 +1,142 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/guest_view/guest_view_internal_api.h"
+
+#include "components/guest_view/browser/guest_view_base.h"
+#include "components/guest_view/browser/guest_view_manager.h"
+#include "components/guest_view/browser/guest_view_manager_delegate.h"
+#include "components/guest_view/common/guest_view_constants.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/common/api/guest_view_internal.h"
+#include "extensions/common/permissions/permissions_data.h"
+
+using guest_view::GuestViewBase;
+using guest_view::GuestViewManager;
+using guest_view::GuestViewManagerDelegate;
+
+namespace guest_view_internal = extensions::api::guest_view_internal;
+
+namespace extensions {
+
+GuestViewInternalCreateGuestFunction::
+ GuestViewInternalCreateGuestFunction() {
+}
+
+bool GuestViewInternalCreateGuestFunction::RunAsync() {
+ std::string view_type;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &view_type));
+
+ base::DictionaryValue* create_params;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &create_params));
+
+ // Since we are creating a new guest, we will create a GuestViewManager
+ // if we don't already have one.
+ GuestViewManager* guest_view_manager =
+ GuestViewManager::FromBrowserContext(browser_context());
+ if (!guest_view_manager) {
+ guest_view_manager = GuestViewManager::CreateWithDelegate(
+ browser_context(),
+ ExtensionsAPIClient::Get()->CreateGuestViewManagerDelegate(context_));
+ }
+
+ GuestViewManager::WebContentsCreatedCallback callback =
+ base::Bind(&GuestViewInternalCreateGuestFunction::CreateGuestCallback,
+ this);
+
+ content::WebContents* sender_web_contents = GetSenderWebContents();
+ if (!sender_web_contents) {
+ error_ = "Guest views can only be embedded in web content";
+ return false;
+ }
+
+ // Add flag to |create_params| to indicate that the element size is specified
+ // in logical units.
+ create_params->SetBoolean(guest_view::kElementSizeIsLogical, true);
+
+ guest_view_manager->CreateGuest(view_type,
+ sender_web_contents,
+ *create_params,
+ callback);
+ return true;
+}
+
+void GuestViewInternalCreateGuestFunction::CreateGuestCallback(
+ content::WebContents* guest_web_contents) {
+ int guest_instance_id = 0;
+ int content_window_id = MSG_ROUTING_NONE;
+ if (guest_web_contents) {
+ GuestViewBase* guest = GuestViewBase::FromWebContents(guest_web_contents);
+ guest_instance_id = guest->guest_instance_id();
+ content_window_id = guest->proxy_routing_id();
+ }
+ scoped_ptr<base::DictionaryValue> return_params(new base::DictionaryValue());
+ return_params->SetInteger(guest_view::kID, guest_instance_id);
+ return_params->SetInteger(guest_view::kContentWindowID, content_window_id);
+ SetResult(return_params.release());
+ SendResponse(true);
+}
+
+GuestViewInternalDestroyGuestFunction::
+ GuestViewInternalDestroyGuestFunction() {
+}
+
+GuestViewInternalDestroyGuestFunction::
+ ~GuestViewInternalDestroyGuestFunction() {
+}
+
+bool GuestViewInternalDestroyGuestFunction::RunAsync() {
+ scoped_ptr<guest_view_internal::DestroyGuest::Params> params(
+ guest_view_internal::DestroyGuest::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ GuestViewBase* guest = GuestViewBase::From(
+ render_frame_host()->GetProcess()->GetID(), params->instance_id);
+ if (!guest)
+ return false;
+ guest->Destroy();
+ SendResponse(true);
+ return true;
+}
+
+GuestViewInternalSetSizeFunction::GuestViewInternalSetSizeFunction() {
+}
+
+GuestViewInternalSetSizeFunction::~GuestViewInternalSetSizeFunction() {
+}
+
+bool GuestViewInternalSetSizeFunction::RunAsync() {
+ scoped_ptr<guest_view_internal::SetSize::Params> params(
+ guest_view_internal::SetSize::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ GuestViewBase* guest = GuestViewBase::From(
+ render_frame_host()->GetProcess()->GetID(), params->instance_id);
+ if (!guest)
+ return false;
+
+ guest_view::SetSizeParams set_size_params;
+ if (params->params.enable_auto_size) {
+ set_size_params.enable_auto_size.reset(
+ params->params.enable_auto_size.release());
+ }
+ if (params->params.min) {
+ set_size_params.min_size.reset(
+ new gfx::Size(params->params.min->width, params->params.min->height));
+ }
+ if (params->params.max) {
+ set_size_params.max_size.reset(
+ new gfx::Size(params->params.max->width, params->params.max->height));
+ }
+ if (params->params.normal) {
+ set_size_params.normal_size.reset(new gfx::Size(
+ params->params.normal->width, params->params.normal->height));
+ }
+
+ guest->SetSize(set_size_params);
+ SendResponse(true);
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/guest_view/guest_view_internal_api.h b/chromium/extensions/browser/api/guest_view/guest_view_internal_api.h
new file mode 100644
index 00000000000..970cdc349b8
--- /dev/null
+++ b/chromium/extensions/browser/api/guest_view/guest_view_internal_api.h
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_GUEST_VIEW_GUEST_VIEW_INTERNAL_API_H_
+#define EXTENSIONS_BROWSER_API_GUEST_VIEW_GUEST_VIEW_INTERNAL_API_H_
+
+#include "base/macros.h"
+#include "extensions/browser/extension_function.h"
+
+namespace extensions {
+
+class GuestViewInternalCreateGuestFunction : public AsyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("guestViewInternal.createGuest",
+ GUESTVIEWINTERNAL_CREATEGUEST);
+ GuestViewInternalCreateGuestFunction();
+
+ protected:
+ ~GuestViewInternalCreateGuestFunction() override {}
+ bool RunAsync() final;
+
+ private:
+ void CreateGuestCallback(content::WebContents* guest_web_contents);
+ DISALLOW_COPY_AND_ASSIGN(GuestViewInternalCreateGuestFunction);
+};
+
+class GuestViewInternalDestroyGuestFunction : public AsyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("guestViewInternal.destroyGuest",
+ GUESTVIEWINTERNAL_DESTROYGUEST);
+ GuestViewInternalDestroyGuestFunction();
+
+ protected:
+ ~GuestViewInternalDestroyGuestFunction() override;
+ bool RunAsync() final;
+
+ private:
+ void DestroyGuestCallback(content::WebContents* guest_web_contents);
+ DISALLOW_COPY_AND_ASSIGN(GuestViewInternalDestroyGuestFunction);
+};
+
+class GuestViewInternalSetSizeFunction : public AsyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("guestViewInternal.setSize",
+ GUESTVIEWINTERNAL_SETAUTOSIZE);
+
+ GuestViewInternalSetSizeFunction();
+
+ protected:
+ ~GuestViewInternalSetSizeFunction() override;
+ bool RunAsync() final;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(GuestViewInternalSetSizeFunction);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_GUEST_VIEW_GUEST_VIEW_INTERNAL_API_H_
diff --git a/chromium/extensions/browser/api/guest_view/web_view/web_view_internal_api.cc b/chromium/extensions/browser/api/guest_view/web_view/web_view_internal_api.cc
new file mode 100644
index 00000000000..e33962dbe2e
--- /dev/null
+++ b/chromium/extensions/browser/api/guest_view/web_view/web_view_internal_api.cc
@@ -0,0 +1,969 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/guest_view/web_view/web_view_internal_api.h"
+
+#include <utility>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/stop_find_action.h"
+#include "content/public/common/url_fetcher.h"
+#include "extensions/browser/guest_view/web_view/web_view_constants.h"
+#include "extensions/browser/guest_view/web_view/web_view_content_script_manager.h"
+#include "extensions/common/api/web_view_internal.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/user_script.h"
+#include "third_party/WebKit/public/web/WebFindOptions.h"
+
+using content::WebContents;
+using extensions::ExtensionResource;
+using extensions::api::web_view_internal::ContentScriptDetails;
+using extensions::api::web_view_internal::InjectionItems;
+using extensions::api::web_view_internal::SetPermission::Params;
+using extensions::api::extension_types::InjectDetails;
+using extensions::UserScript;
+using ui_zoom::ZoomController;
+// error messages for content scripts:
+namespace errors = extensions::manifest_errors;
+namespace web_view_internal = extensions::api::web_view_internal;
+
+namespace {
+
+const char kAppCacheKey[] = "appcache";
+const char kCacheKey[] = "cache";
+const char kCookiesKey[] = "cookies";
+const char kFileSystemsKey[] = "fileSystems";
+const char kIndexedDBKey[] = "indexedDB";
+const char kLocalStorageKey[] = "localStorage";
+const char kWebSQLKey[] = "webSQL";
+const char kSinceKey[] = "since";
+const char kLoadFileError[] = "Failed to load file: \"*\". ";
+const char kViewInstanceIdError[] = "view_instance_id is missing.";
+const char kDuplicatedContentScriptNamesError[] =
+ "The given content script name already exists.";
+
+uint32_t MaskForKey(const char* key) {
+ if (strcmp(key, kAppCacheKey) == 0)
+ return webview::WEB_VIEW_REMOVE_DATA_MASK_APPCACHE;
+ if (strcmp(key, kCacheKey) == 0)
+ return webview::WEB_VIEW_REMOVE_DATA_MASK_CACHE;
+ if (strcmp(key, kCookiesKey) == 0)
+ return webview::WEB_VIEW_REMOVE_DATA_MASK_COOKIES;
+ if (strcmp(key, kFileSystemsKey) == 0)
+ return webview::WEB_VIEW_REMOVE_DATA_MASK_FILE_SYSTEMS;
+ if (strcmp(key, kIndexedDBKey) == 0)
+ return webview::WEB_VIEW_REMOVE_DATA_MASK_INDEXEDDB;
+ if (strcmp(key, kLocalStorageKey) == 0)
+ return webview::WEB_VIEW_REMOVE_DATA_MASK_LOCAL_STORAGE;
+ if (strcmp(key, kWebSQLKey) == 0)
+ return webview::WEB_VIEW_REMOVE_DATA_MASK_WEBSQL;
+ return 0;
+}
+
+HostID GenerateHostIDFromEmbedder(const extensions::Extension* extension,
+ const content::WebContents* web_contents) {
+ if (extension)
+ return HostID(HostID::EXTENSIONS, extension->id());
+
+ if (web_contents && web_contents->GetWebUI()) {
+ const GURL& url = web_contents->GetSiteInstance()->GetSiteURL();
+ return HostID(HostID::WEBUI, url.spec());
+ }
+ NOTREACHED();
+ return HostID();
+}
+
+// Creates content script files when parsing InjectionItems of "js" or "css"
+// proterties, and stores them in the |result|.
+void AddScriptFiles(const GURL& owner_base_url,
+ const extensions::Extension* extension,
+ const InjectionItems& items,
+ UserScript::FileList* result) {
+ // files:
+ if (items.files) {
+ for (const std::string& relative : *items.files) {
+ GURL url = owner_base_url.Resolve(relative);
+ if (extension) {
+ ExtensionResource resource = extension->GetResource(relative);
+ result->push_back(UserScript::File(resource.extension_root(),
+ resource.relative_path(), url));
+ } else {
+ result->push_back(extensions::UserScript::File(base::FilePath(),
+ base::FilePath(), url));
+ }
+ }
+ }
+ // code:
+ if (items.code) {
+ extensions::UserScript::File file((base::FilePath()), (base::FilePath()),
+ GURL());
+ file.set_content(*items.code);
+ result->push_back(file);
+ }
+}
+
+// Parses the values stored in ContentScriptDetails, and constructs a
+// UserScript.
+bool ParseContentScript(const ContentScriptDetails& script_value,
+ const extensions::Extension* extension,
+ const GURL& owner_base_url,
+ UserScript* script,
+ std::string* error) {
+ // matches (required):
+ if (script_value.matches.empty())
+ return false;
+
+ // The default for WebUI is not having special access, but we can change that
+ // if needed.
+ bool allowed_everywhere = false;
+ if (extension &&
+ extensions::PermissionsData::CanExecuteScriptEverywhere(extension))
+ allowed_everywhere = true;
+
+ for (const std::string& match : script_value.matches) {
+ URLPattern pattern(UserScript::ValidUserScriptSchemes(allowed_everywhere));
+ if (pattern.Parse(match) != URLPattern::PARSE_SUCCESS) {
+ *error = errors::kInvalidMatches;
+ return false;
+ }
+ script->add_url_pattern(pattern);
+ }
+
+ // exclude_matches:
+ if (script_value.exclude_matches) {
+ const std::vector<std::string>& exclude_matches =
+ *(script_value.exclude_matches.get());
+ for (const std::string& exclude_match : exclude_matches) {
+ URLPattern pattern(
+ UserScript::ValidUserScriptSchemes(allowed_everywhere));
+
+ if (pattern.Parse(exclude_match) != URLPattern::PARSE_SUCCESS) {
+ *error = errors::kInvalidExcludeMatches;
+ return false;
+ }
+ script->add_exclude_url_pattern(pattern);
+ }
+ }
+ // run_at:
+ if (script_value.run_at) {
+ UserScript::RunLocation run_at = UserScript::UNDEFINED;
+ switch (script_value.run_at) {
+ case extensions::api::extension_types::RUN_AT_NONE:
+ case extensions::api::extension_types::RUN_AT_DOCUMENT_IDLE:
+ run_at = UserScript::DOCUMENT_IDLE;
+ break;
+ case extensions::api::extension_types::RUN_AT_DOCUMENT_START:
+ run_at = UserScript::DOCUMENT_START;
+ break;
+ case extensions::api::extension_types::RUN_AT_DOCUMENT_END:
+ run_at = UserScript::DOCUMENT_END;
+ break;
+ }
+ // The default for run_at is RUN_AT_DOCUMENT_IDLE.
+ script->set_run_location(run_at);
+ }
+
+ // match_about_blank:
+ if (script_value.match_about_blank)
+ script->set_match_about_blank(*script_value.match_about_blank);
+
+ // css:
+ if (script_value.css) {
+ AddScriptFiles(owner_base_url, extension, *script_value.css,
+ &script->css_scripts());
+ }
+
+ // js:
+ if (script_value.js) {
+ AddScriptFiles(owner_base_url, extension, *script_value.js,
+ &script->js_scripts());
+ }
+
+ // all_frames:
+ if (script_value.all_frames)
+ script->set_match_all_frames(*script_value.all_frames);
+
+ // include_globs:
+ if (script_value.include_globs) {
+ for (const std::string& glob : *script_value.include_globs)
+ script->add_glob(glob);
+ }
+
+ // exclude_globs:
+ if (script_value.exclude_globs) {
+ for (const std::string& glob : *script_value.exclude_globs)
+ script->add_exclude_glob(glob);
+ }
+
+ return true;
+}
+
+bool ParseContentScripts(
+ const std::vector<ContentScriptDetails>& content_script_list,
+ const extensions::Extension* extension,
+ const HostID& host_id,
+ bool incognito_enabled,
+ const GURL& owner_base_url,
+ std::set<UserScript>* result,
+ std::string* error) {
+ if (content_script_list.empty())
+ return false;
+
+ std::set<std::string> names;
+ for (const ContentScriptDetails& script_value : content_script_list) {
+ const std::string& name = script_value.name;
+ if (!names.insert(name).second) {
+ // The name was already in the list.
+ *error = kDuplicatedContentScriptNamesError;
+ return false;
+ }
+
+ UserScript script;
+ if (!ParseContentScript(script_value, extension, owner_base_url, &script,
+ error))
+ return false;
+
+ script.set_id(UserScript::GenerateUserScriptID());
+ script.set_name(name);
+ script.set_incognito_enabled(incognito_enabled);
+ script.set_host_id(host_id);
+ script.set_consumer_instance_type(UserScript::WEBVIEW);
+ result->insert(script);
+ }
+ return true;
+}
+
+} // namespace
+
+namespace extensions {
+
+bool WebViewInternalExtensionFunction::RunAsync() {
+ int instance_id = 0;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &instance_id));
+ WebViewGuest* guest = WebViewGuest::From(
+ render_frame_host()->GetProcess()->GetID(), instance_id);
+ if (!guest)
+ return false;
+
+ return RunAsyncSafe(guest);
+}
+
+WebViewInternalCaptureVisibleRegionFunction::
+ WebViewInternalCaptureVisibleRegionFunction()
+ : is_guest_transparent_(false) {}
+
+bool WebViewInternalCaptureVisibleRegionFunction::RunAsyncSafe(
+ WebViewGuest* guest) {
+ using api::extension_types::ImageDetails;
+
+ scoped_ptr<web_view_internal::CaptureVisibleRegion::Params> params(
+ web_view_internal::CaptureVisibleRegion::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ scoped_ptr<ImageDetails> image_details;
+ if (args_->GetSize() > 1) {
+ base::Value* spec = NULL;
+ EXTENSION_FUNCTION_VALIDATE(args_->Get(1, &spec) && spec);
+ image_details = ImageDetails::FromValue(*spec);
+ }
+
+ is_guest_transparent_ = guest->allow_transparency();
+ return CaptureAsync(guest->web_contents(), image_details.get(),
+ base::Bind(&WebViewInternalCaptureVisibleRegionFunction::
+ CopyFromBackingStoreComplete,
+ this));
+}
+bool WebViewInternalCaptureVisibleRegionFunction::IsScreenshotEnabled() {
+ // TODO(wjmaclean): Is it ok to always return true here?
+ return true;
+}
+
+bool WebViewInternalCaptureVisibleRegionFunction::ClientAllowsTransparency() {
+ return is_guest_transparent_;
+}
+
+void WebViewInternalCaptureVisibleRegionFunction::OnCaptureSuccess(
+ const SkBitmap& bitmap) {
+ std::string base64_result;
+ if (!EncodeBitmap(bitmap, &base64_result)) {
+ OnCaptureFailure(FAILURE_REASON_ENCODING_FAILED);
+ return;
+ }
+
+ SetResult(new base::StringValue(base64_result));
+ SendResponse(true);
+}
+
+void WebViewInternalCaptureVisibleRegionFunction::OnCaptureFailure(
+ FailureReason reason) {
+ const char* reason_description = "internal error";
+ switch (reason) {
+ case FAILURE_REASON_UNKNOWN:
+ reason_description = "unknown error";
+ break;
+ case FAILURE_REASON_ENCODING_FAILED:
+ reason_description = "encoding failed";
+ break;
+ case FAILURE_REASON_VIEW_INVISIBLE:
+ reason_description = "view is invisible";
+ break;
+ }
+ error_ = ErrorUtils::FormatErrorMessage("Failed to capture webview: *",
+ reason_description);
+ SendResponse(false);
+}
+
+bool WebViewInternalNavigateFunction::RunAsyncSafe(WebViewGuest* guest) {
+ scoped_ptr<web_view_internal::Navigate::Params> params(
+ web_view_internal::Navigate::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ std::string src = params->src;
+ guest->NavigateGuest(src, true /* force_navigation */);
+ return true;
+}
+
+WebViewInternalExecuteCodeFunction::WebViewInternalExecuteCodeFunction()
+ : guest_instance_id_(0), guest_src_(GURL::EmptyGURL()) {
+}
+
+WebViewInternalExecuteCodeFunction::~WebViewInternalExecuteCodeFunction() {
+}
+
+bool WebViewInternalExecuteCodeFunction::Init() {
+ if (details_.get())
+ return true;
+
+ if (!args_->GetInteger(0, &guest_instance_id_))
+ return false;
+
+ if (!guest_instance_id_)
+ return false;
+
+ std::string src;
+ if (!args_->GetString(1, &src))
+ return false;
+
+ // Set |guest_src_| here, but do not return false if it is invalid.
+ // Instead, let it continue with the normal page load sequence,
+ // which will result in the usual LOAD_ABORT event in the case where
+ // the URL is invalid.
+ guest_src_ = GURL(src);
+
+ base::DictionaryValue* details_value = NULL;
+ if (!args_->GetDictionary(2, &details_value))
+ return false;
+ scoped_ptr<InjectDetails> details(new InjectDetails());
+ if (!InjectDetails::Populate(*details_value, details.get()))
+ return false;
+
+ details_ = std::move(details);
+
+ if (extension()) {
+ set_host_id(HostID(HostID::EXTENSIONS, extension()->id()));
+ return true;
+ }
+
+ WebContents* web_contents = GetSenderWebContents();
+ if (web_contents && web_contents->GetWebUI()) {
+ const GURL& url = render_frame_host()->GetSiteInstance()->GetSiteURL();
+ set_host_id(HostID(HostID::WEBUI, url.spec()));
+ return true;
+ }
+ return false;
+}
+
+bool WebViewInternalExecuteCodeFunction::ShouldInsertCSS() const {
+ return false;
+}
+
+bool WebViewInternalExecuteCodeFunction::CanExecuteScriptOnPage() {
+ return true;
+}
+
+extensions::ScriptExecutor*
+WebViewInternalExecuteCodeFunction::GetScriptExecutor() {
+ if (!render_frame_host() || !render_frame_host()->GetProcess())
+ return NULL;
+ WebViewGuest* guest = WebViewGuest::From(
+ render_frame_host()->GetProcess()->GetID(), guest_instance_id_);
+ if (!guest)
+ return NULL;
+
+ return guest->script_executor();
+}
+
+bool WebViewInternalExecuteCodeFunction::IsWebView() const {
+ return true;
+}
+
+const GURL& WebViewInternalExecuteCodeFunction::GetWebViewSrc() const {
+ return guest_src_;
+}
+
+bool WebViewInternalExecuteCodeFunction::LoadFileForWebUI(
+ const std::string& file_src,
+ const WebUIURLFetcher::WebUILoadFileCallback& callback) {
+ if (!render_frame_host() || !render_frame_host()->GetProcess())
+ return false;
+ WebViewGuest* guest = WebViewGuest::From(
+ render_frame_host()->GetProcess()->GetID(), guest_instance_id_);
+ if (!guest || host_id().type() != HostID::WEBUI)
+ return false;
+
+ GURL owner_base_url(guest->GetOwnerSiteURL().GetWithEmptyPath());
+ GURL file_url(owner_base_url.Resolve(file_src));
+
+ url_fetcher_.reset(new WebUIURLFetcher(
+ this->browser_context(), render_frame_host()->GetProcess()->GetID(),
+ render_view_host_do_not_use()->GetRoutingID(), file_url, callback));
+ url_fetcher_->Start();
+ return true;
+}
+
+bool WebViewInternalExecuteCodeFunction::LoadFile(const std::string& file) {
+ if (!extension()) {
+ if (LoadFileForWebUI(
+ *details_->file,
+ base::Bind(
+ &WebViewInternalExecuteCodeFunction::DidLoadAndLocalizeFile,
+ this, file)))
+ return true;
+
+ SendResponse(false);
+ error_ = ErrorUtils::FormatErrorMessage(kLoadFileError, file);
+ return false;
+ }
+ return ExecuteCodeFunction::LoadFile(file);
+}
+
+WebViewInternalExecuteScriptFunction::WebViewInternalExecuteScriptFunction() {
+}
+
+void WebViewInternalExecuteScriptFunction::OnExecuteCodeFinished(
+ const std::string& error,
+ const GURL& on_url,
+ const base::ListValue& result) {
+ if (error.empty())
+ SetResult(result.DeepCopy());
+ WebViewInternalExecuteCodeFunction::OnExecuteCodeFinished(
+ error, on_url, result);
+}
+
+WebViewInternalInsertCSSFunction::WebViewInternalInsertCSSFunction() {
+}
+
+bool WebViewInternalInsertCSSFunction::ShouldInsertCSS() const {
+ return true;
+}
+
+WebViewInternalAddContentScriptsFunction::
+ WebViewInternalAddContentScriptsFunction() {
+}
+
+WebViewInternalAddContentScriptsFunction::
+ ~WebViewInternalAddContentScriptsFunction() {
+}
+
+ExecuteCodeFunction::ResponseAction
+WebViewInternalAddContentScriptsFunction::Run() {
+ scoped_ptr<web_view_internal::AddContentScripts::Params> params(
+ web_view_internal::AddContentScripts::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ if (!params->instance_id)
+ return RespondNow(Error(kViewInstanceIdError));
+
+ GURL owner_base_url(
+ render_frame_host()->GetSiteInstance()->GetSiteURL().GetWithEmptyPath());
+ std::set<UserScript> result;
+
+ content::WebContents* sender_web_contents = GetSenderWebContents();
+ HostID host_id = GenerateHostIDFromEmbedder(extension(), sender_web_contents);
+ bool incognito_enabled = browser_context()->IsOffTheRecord();
+
+ if (!ParseContentScripts(params->content_script_list, extension(), host_id,
+ incognito_enabled, owner_base_url, &result, &error_))
+ return RespondNow(Error(error_));
+
+ WebViewContentScriptManager* manager =
+ WebViewContentScriptManager::Get(browser_context());
+ DCHECK(manager);
+
+ manager->AddContentScripts(
+ sender_web_contents->GetRenderProcessHost()->GetID(),
+ render_view_host_do_not_use(), params->instance_id, host_id, result);
+
+ return RespondNow(NoArguments());
+}
+
+WebViewInternalRemoveContentScriptsFunction::
+ WebViewInternalRemoveContentScriptsFunction() {
+}
+
+WebViewInternalRemoveContentScriptsFunction::
+ ~WebViewInternalRemoveContentScriptsFunction() {
+}
+
+ExecuteCodeFunction::ResponseAction
+WebViewInternalRemoveContentScriptsFunction::Run() {
+ scoped_ptr<web_view_internal::RemoveContentScripts::Params> params(
+ web_view_internal::RemoveContentScripts::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ if (!params->instance_id)
+ return RespondNow(Error(kViewInstanceIdError));
+
+ WebViewContentScriptManager* manager =
+ WebViewContentScriptManager::Get(browser_context());
+ DCHECK(manager);
+
+ content::WebContents* sender_web_contents = GetSenderWebContents();
+ HostID host_id = GenerateHostIDFromEmbedder(extension(), sender_web_contents);
+
+ std::vector<std::string> script_name_list;
+ if (params->script_name_list)
+ script_name_list.swap(*params->script_name_list);
+ manager->RemoveContentScripts(
+ sender_web_contents->GetRenderProcessHost()->GetID(),
+ params->instance_id, host_id, script_name_list);
+ return RespondNow(NoArguments());
+}
+
+WebViewInternalSetNameFunction::WebViewInternalSetNameFunction() {
+}
+
+WebViewInternalSetNameFunction::~WebViewInternalSetNameFunction() {
+}
+
+bool WebViewInternalSetNameFunction::RunAsyncSafe(WebViewGuest* guest) {
+ scoped_ptr<web_view_internal::SetName::Params> params(
+ web_view_internal::SetName::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ guest->SetName(params->frame_name);
+ SendResponse(true);
+ return true;
+}
+
+WebViewInternalSetAllowTransparencyFunction::
+WebViewInternalSetAllowTransparencyFunction() {
+}
+
+WebViewInternalSetAllowTransparencyFunction::
+~WebViewInternalSetAllowTransparencyFunction() {
+}
+
+bool WebViewInternalSetAllowTransparencyFunction::RunAsyncSafe(
+ WebViewGuest* guest) {
+ scoped_ptr<web_view_internal::SetAllowTransparency::Params> params(
+ web_view_internal::SetAllowTransparency::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ guest->SetAllowTransparency(params->allow);
+ SendResponse(true);
+ return true;
+}
+
+WebViewInternalSetAllowScalingFunction::
+ WebViewInternalSetAllowScalingFunction() {
+}
+
+WebViewInternalSetAllowScalingFunction::
+ ~WebViewInternalSetAllowScalingFunction() {
+}
+
+bool WebViewInternalSetAllowScalingFunction::RunAsyncSafe(WebViewGuest* guest) {
+ scoped_ptr<web_view_internal::SetAllowScaling::Params> params(
+ web_view_internal::SetAllowScaling::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ guest->SetAllowScaling(params->allow);
+ SendResponse(true);
+ return true;
+}
+
+WebViewInternalSetZoomFunction::WebViewInternalSetZoomFunction() {
+}
+
+WebViewInternalSetZoomFunction::~WebViewInternalSetZoomFunction() {
+}
+
+bool WebViewInternalSetZoomFunction::RunAsyncSafe(WebViewGuest* guest) {
+ scoped_ptr<web_view_internal::SetZoom::Params> params(
+ web_view_internal::SetZoom::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ guest->SetZoom(params->zoom_factor);
+
+ SendResponse(true);
+ return true;
+}
+
+WebViewInternalGetZoomFunction::WebViewInternalGetZoomFunction() {
+}
+
+WebViewInternalGetZoomFunction::~WebViewInternalGetZoomFunction() {
+}
+
+bool WebViewInternalGetZoomFunction::RunAsyncSafe(WebViewGuest* guest) {
+ scoped_ptr<web_view_internal::GetZoom::Params> params(
+ web_view_internal::GetZoom::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ double zoom_factor = guest->GetZoom();
+ SetResult(new base::FundamentalValue(zoom_factor));
+ SendResponse(true);
+ return true;
+}
+
+WebViewInternalSetZoomModeFunction::WebViewInternalSetZoomModeFunction() {
+}
+
+WebViewInternalSetZoomModeFunction::~WebViewInternalSetZoomModeFunction() {
+}
+
+bool WebViewInternalSetZoomModeFunction::RunAsyncSafe(WebViewGuest* guest) {
+ scoped_ptr<web_view_internal::SetZoomMode::Params> params(
+ web_view_internal::SetZoomMode::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ ZoomController::ZoomMode zoom_mode = ZoomController::ZOOM_MODE_DEFAULT;
+ switch (params->zoom_mode) {
+ case web_view_internal::ZOOM_MODE_PER_ORIGIN:
+ zoom_mode = ZoomController::ZOOM_MODE_DEFAULT;
+ break;
+ case web_view_internal::ZOOM_MODE_PER_VIEW:
+ zoom_mode = ZoomController::ZOOM_MODE_ISOLATED;
+ break;
+ case web_view_internal::ZOOM_MODE_DISABLED:
+ zoom_mode = ZoomController::ZOOM_MODE_DISABLED;
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ guest->SetZoomMode(zoom_mode);
+
+ SendResponse(true);
+ return true;
+}
+
+WebViewInternalGetZoomModeFunction::WebViewInternalGetZoomModeFunction() {
+}
+
+WebViewInternalGetZoomModeFunction::~WebViewInternalGetZoomModeFunction() {
+}
+
+bool WebViewInternalGetZoomModeFunction::RunAsyncSafe(WebViewGuest* guest) {
+ scoped_ptr<web_view_internal::GetZoomMode::Params> params(
+ web_view_internal::GetZoomMode::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ web_view_internal::ZoomMode zoom_mode = web_view_internal::ZOOM_MODE_NONE;
+ switch (guest->GetZoomMode()) {
+ case ZoomController::ZOOM_MODE_DEFAULT:
+ zoom_mode = web_view_internal::ZOOM_MODE_PER_ORIGIN;
+ break;
+ case ZoomController::ZOOM_MODE_ISOLATED:
+ zoom_mode = web_view_internal::ZOOM_MODE_PER_VIEW;
+ break;
+ case ZoomController::ZOOM_MODE_DISABLED:
+ zoom_mode = web_view_internal::ZOOM_MODE_DISABLED;
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ SetResult(new base::StringValue(web_view_internal::ToString(zoom_mode)));
+ SendResponse(true);
+ return true;
+}
+
+WebViewInternalFindFunction::WebViewInternalFindFunction() {
+}
+
+WebViewInternalFindFunction::~WebViewInternalFindFunction() {
+}
+
+bool WebViewInternalFindFunction::RunAsyncSafe(WebViewGuest* guest) {
+ scoped_ptr<web_view_internal::Find::Params> params(
+ web_view_internal::Find::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ // Convert the std::string search_text to string16.
+ base::string16 search_text;
+ base::UTF8ToUTF16(
+ params->search_text.c_str(), params->search_text.length(), &search_text);
+
+ // Set the find options to their default values.
+ blink::WebFindOptions options;
+ if (params->options) {
+ options.forward =
+ params->options->backward ? !*params->options->backward : true;
+ options.matchCase =
+ params->options->match_case ? *params->options->match_case : false;
+ }
+
+ guest->StartFind(search_text, options, this);
+ return true;
+}
+
+WebViewInternalStopFindingFunction::WebViewInternalStopFindingFunction() {
+}
+
+WebViewInternalStopFindingFunction::~WebViewInternalStopFindingFunction() {
+}
+
+bool WebViewInternalStopFindingFunction::RunAsyncSafe(WebViewGuest* guest) {
+ scoped_ptr<web_view_internal::StopFinding::Params> params(
+ web_view_internal::StopFinding::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ // Set the StopFindAction.
+ content::StopFindAction action;
+ switch (params->action) {
+ case web_view_internal::STOP_FINDING_ACTION_CLEAR:
+ action = content::STOP_FIND_ACTION_CLEAR_SELECTION;
+ break;
+ case web_view_internal::STOP_FINDING_ACTION_KEEP:
+ action = content::STOP_FIND_ACTION_KEEP_SELECTION;
+ break;
+ case web_view_internal::STOP_FINDING_ACTION_ACTIVATE:
+ action = content::STOP_FIND_ACTION_ACTIVATE_SELECTION;
+ break;
+ default:
+ action = content::STOP_FIND_ACTION_KEEP_SELECTION;
+ }
+
+ guest->StopFinding(action);
+ return true;
+}
+
+WebViewInternalLoadDataWithBaseUrlFunction::
+ WebViewInternalLoadDataWithBaseUrlFunction() {
+}
+
+WebViewInternalLoadDataWithBaseUrlFunction::
+ ~WebViewInternalLoadDataWithBaseUrlFunction() {
+}
+
+bool WebViewInternalLoadDataWithBaseUrlFunction::RunAsyncSafe(
+ WebViewGuest* guest) {
+ scoped_ptr<web_view_internal::LoadDataWithBaseUrl::Params> params(
+ web_view_internal::LoadDataWithBaseUrl::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ // If a virtual URL was provided, use it. Otherwise, the user will be shown
+ // the data URL.
+ std::string virtual_url =
+ params->virtual_url ? *params->virtual_url : params->data_url;
+
+ bool successful = guest->LoadDataWithBaseURL(
+ params->data_url, params->base_url, virtual_url, &error_);
+ SendResponse(successful);
+ return successful;
+}
+
+WebViewInternalGoFunction::WebViewInternalGoFunction() {
+}
+
+WebViewInternalGoFunction::~WebViewInternalGoFunction() {
+}
+
+bool WebViewInternalGoFunction::RunAsyncSafe(WebViewGuest* guest) {
+ scoped_ptr<web_view_internal::Go::Params> params(
+ web_view_internal::Go::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ bool successful = guest->Go(params->relative_index);
+ SetResult(new base::FundamentalValue(successful));
+ SendResponse(true);
+ return true;
+}
+
+WebViewInternalReloadFunction::WebViewInternalReloadFunction() {
+}
+
+WebViewInternalReloadFunction::~WebViewInternalReloadFunction() {
+}
+
+bool WebViewInternalReloadFunction::RunAsyncSafe(WebViewGuest* guest) {
+ guest->Reload();
+ return true;
+}
+
+WebViewInternalSetPermissionFunction::WebViewInternalSetPermissionFunction() {
+}
+
+WebViewInternalSetPermissionFunction::~WebViewInternalSetPermissionFunction() {
+}
+
+bool WebViewInternalSetPermissionFunction::RunAsyncSafe(WebViewGuest* guest) {
+ scoped_ptr<web_view_internal::SetPermission::Params> params(
+ web_view_internal::SetPermission::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ WebViewPermissionHelper::PermissionResponseAction action =
+ WebViewPermissionHelper::DEFAULT;
+ switch (params->action) {
+ case api::web_view_internal::SET_PERMISSION_ACTION_ALLOW:
+ action = WebViewPermissionHelper::ALLOW;
+ break;
+ case api::web_view_internal::SET_PERMISSION_ACTION_DENY:
+ action = WebViewPermissionHelper::DENY;
+ break;
+ case api::web_view_internal::SET_PERMISSION_ACTION_DEFAULT:
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ std::string user_input;
+ if (params->user_input)
+ user_input = *params->user_input;
+
+ WebViewPermissionHelper* web_view_permission_helper =
+ WebViewPermissionHelper::FromWebContents(guest->web_contents());
+
+ WebViewPermissionHelper::SetPermissionResult result =
+ web_view_permission_helper->SetPermission(
+ params->request_id, action, user_input);
+
+ EXTENSION_FUNCTION_VALIDATE(result !=
+ WebViewPermissionHelper::SET_PERMISSION_INVALID);
+
+ SetResult(new base::FundamentalValue(
+ result == WebViewPermissionHelper::SET_PERMISSION_ALLOWED));
+ SendResponse(true);
+ return true;
+}
+
+WebViewInternalOverrideUserAgentFunction::
+ WebViewInternalOverrideUserAgentFunction() {
+}
+
+WebViewInternalOverrideUserAgentFunction::
+ ~WebViewInternalOverrideUserAgentFunction() {
+}
+
+bool WebViewInternalOverrideUserAgentFunction::RunAsyncSafe(
+ WebViewGuest* guest) {
+ scoped_ptr<web_view_internal::OverrideUserAgent::Params> params(
+ web_view_internal::OverrideUserAgent::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ guest->SetUserAgentOverride(params->user_agent_override);
+ return true;
+}
+
+WebViewInternalStopFunction::WebViewInternalStopFunction() {
+}
+
+WebViewInternalStopFunction::~WebViewInternalStopFunction() {
+}
+
+bool WebViewInternalStopFunction::RunAsyncSafe(WebViewGuest* guest) {
+ guest->Stop();
+ return true;
+}
+
+WebViewInternalTerminateFunction::WebViewInternalTerminateFunction() {
+}
+
+WebViewInternalTerminateFunction::~WebViewInternalTerminateFunction() {
+}
+
+bool WebViewInternalTerminateFunction::RunAsyncSafe(WebViewGuest* guest) {
+ guest->Terminate();
+ return true;
+}
+
+WebViewInternalClearDataFunction::WebViewInternalClearDataFunction()
+ : remove_mask_(0), bad_message_(false) {
+}
+
+WebViewInternalClearDataFunction::~WebViewInternalClearDataFunction() {
+}
+
+// Parses the |dataToRemove| argument to generate the remove mask. Sets
+// |bad_message_| (like EXTENSION_FUNCTION_VALIDATE would if this were a bool
+// method) if 'dataToRemove' is not present.
+uint32_t WebViewInternalClearDataFunction::GetRemovalMask() {
+ base::DictionaryValue* data_to_remove;
+ if (!args_->GetDictionary(2, &data_to_remove)) {
+ bad_message_ = true;
+ return 0;
+ }
+
+ uint32_t remove_mask = 0;
+ for (base::DictionaryValue::Iterator i(*data_to_remove); !i.IsAtEnd();
+ i.Advance()) {
+ bool selected = false;
+ if (!i.value().GetAsBoolean(&selected)) {
+ bad_message_ = true;
+ return 0;
+ }
+ if (selected)
+ remove_mask |= MaskForKey(i.key().c_str());
+ }
+
+ return remove_mask;
+}
+
+// TODO(lazyboy): Parameters in this extension function are similar (or a
+// sub-set) to BrowsingDataRemoverFunction. How can we share this code?
+bool WebViewInternalClearDataFunction::RunAsyncSafe(WebViewGuest* guest) {
+ // Grab the initial |options| parameter, and parse out the arguments.
+ base::DictionaryValue* options;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &options));
+ DCHECK(options);
+
+ // If |ms_since_epoch| isn't set, default it to 0.
+ double ms_since_epoch;
+ if (!options->GetDouble(kSinceKey, &ms_since_epoch)) {
+ ms_since_epoch = 0;
+ }
+
+ // base::Time takes a double that represents seconds since epoch. JavaScript
+ // gives developers milliseconds, so do a quick conversion before populating
+ // the object. Also, Time::FromDoubleT converts double time 0 to empty Time
+ // object. So we need to do special handling here.
+ remove_since_ = (ms_since_epoch == 0)
+ ? base::Time::UnixEpoch()
+ : base::Time::FromDoubleT(ms_since_epoch / 1000.0);
+
+ remove_mask_ = GetRemovalMask();
+ if (bad_message_)
+ return false;
+
+ AddRef(); // Balanced below or in WebViewInternalClearDataFunction::Done().
+
+ bool scheduled = false;
+ if (remove_mask_) {
+ scheduled = guest->ClearData(
+ remove_since_,
+ remove_mask_,
+ base::Bind(&WebViewInternalClearDataFunction::ClearDataDone, this));
+ }
+ if (!remove_mask_ || !scheduled) {
+ SendResponse(false);
+ Release(); // Balanced above.
+ return false;
+ }
+
+ // Will finish asynchronously.
+ return true;
+}
+
+void WebViewInternalClearDataFunction::ClearDataDone() {
+ Release(); // Balanced in RunAsync().
+ SendResponse(true);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/guest_view/web_view/web_view_internal_api.h b/chromium/extensions/browser/api/guest_view/web_view/web_view_internal_api.h
new file mode 100644
index 00000000000..9f646e69721
--- /dev/null
+++ b/chromium/extensions/browser/api/guest_view/web_view/web_view_internal_api.h
@@ -0,0 +1,491 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_WEB_VIEW_WEB_VIEW_INTERNAL_API_H_
+#define EXTENSIONS_BROWSER_API_WEB_VIEW_WEB_VIEW_INTERNAL_API_H_
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "extensions/browser/api/execute_code_function.h"
+#include "extensions/browser/api/web_contents_capture_client.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/browser/guest_view/web_view/web_ui/web_ui_url_fetcher.h"
+#include "extensions/browser/guest_view/web_view/web_view_guest.h"
+
+// WARNING: WebViewInternal could be loaded in an unblessed context, thus any
+// new APIs must extend WebViewInternalExtensionFunction or
+// WebViewInternalExecuteCodeFunction which do a process ID check to prevent
+// abuse by normal renderer processes.
+namespace extensions {
+
+// An abstract base class for async webview APIs. It does a process ID check
+// in RunAsync, and then calls RunAsyncSafe which must be overriden by all
+// subclasses.
+class WebViewInternalExtensionFunction : public AsyncExtensionFunction {
+ public:
+ WebViewInternalExtensionFunction() {}
+
+ protected:
+ ~WebViewInternalExtensionFunction() override {}
+
+ // ExtensionFunction implementation.
+ bool RunAsync() final;
+
+ private:
+ virtual bool RunAsyncSafe(WebViewGuest* guest) = 0;
+};
+
+class WebViewInternalCaptureVisibleRegionFunction
+ : public WebViewInternalExtensionFunction,
+ public WebContentsCaptureClient {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.captureVisibleRegion",
+ WEBVIEWINTERNAL_CAPTUREVISIBLEREGION);
+ WebViewInternalCaptureVisibleRegionFunction();
+
+ protected:
+ ~WebViewInternalCaptureVisibleRegionFunction() override {}
+
+ private:
+ // WebViewInternalExtensionFunction implementation.
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ // extensions::WebContentsCaptureClient:
+ bool IsScreenshotEnabled() override;
+ bool ClientAllowsTransparency() override;
+ void OnCaptureSuccess(const SkBitmap& bitmap) override;
+ void OnCaptureFailure(FailureReason reason) override;
+
+ bool is_guest_transparent_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalCaptureVisibleRegionFunction);
+};
+
+class WebViewInternalNavigateFunction
+ : public WebViewInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.navigate",
+ WEBVIEWINTERNAL_NAVIGATE);
+ WebViewInternalNavigateFunction() {}
+
+ protected:
+ ~WebViewInternalNavigateFunction() override {}
+
+ private:
+ // WebViewInternalExtensionFunction implementation.
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalNavigateFunction);
+};
+
+class WebViewInternalExecuteCodeFunction
+ : public extensions::ExecuteCodeFunction {
+ public:
+ WebViewInternalExecuteCodeFunction();
+
+ protected:
+ ~WebViewInternalExecuteCodeFunction() override;
+
+ // Initialize |details_| if it hasn't already been.
+ bool Init() override;
+ bool ShouldInsertCSS() const override;
+ bool CanExecuteScriptOnPage() override;
+ // Guarded by a process ID check.
+ extensions::ScriptExecutor* GetScriptExecutor() final;
+ bool IsWebView() const override;
+ const GURL& GetWebViewSrc() const override;
+ bool LoadFile(const std::string& file) override;
+
+ private:
+ // Loads a file url on WebUI.
+ bool LoadFileForWebUI(const std::string& file_src,
+ const WebUIURLFetcher::WebUILoadFileCallback& callback);
+
+ // Contains extension resource built from path of file which is
+ // specified in JSON arguments.
+ extensions::ExtensionResource resource_;
+
+ int guest_instance_id_;
+
+ GURL guest_src_;
+
+ scoped_ptr<WebUIURLFetcher> url_fetcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalExecuteCodeFunction);
+};
+
+class WebViewInternalExecuteScriptFunction
+ : public WebViewInternalExecuteCodeFunction {
+ public:
+ WebViewInternalExecuteScriptFunction();
+
+ protected:
+ ~WebViewInternalExecuteScriptFunction() override {}
+
+ void OnExecuteCodeFinished(const std::string& error,
+ const GURL& on_url,
+ const base::ListValue& result) override;
+
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.executeScript",
+ WEBVIEWINTERNAL_EXECUTESCRIPT)
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalExecuteScriptFunction);
+};
+
+class WebViewInternalInsertCSSFunction
+ : public WebViewInternalExecuteCodeFunction {
+ public:
+ WebViewInternalInsertCSSFunction();
+
+ protected:
+ ~WebViewInternalInsertCSSFunction() override {}
+
+ bool ShouldInsertCSS() const override;
+
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.insertCSS",
+ WEBVIEWINTERNAL_INSERTCSS)
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalInsertCSSFunction);
+};
+
+class WebViewInternalAddContentScriptsFunction
+ : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.addContentScripts",
+ WEBVIEWINTERNAL_ADDCONTENTSCRIPTS);
+
+ WebViewInternalAddContentScriptsFunction();
+
+ protected:
+ ~WebViewInternalAddContentScriptsFunction() override;
+
+ private:
+ ExecuteCodeFunction::ResponseAction Run() override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalAddContentScriptsFunction);
+};
+
+class WebViewInternalRemoveContentScriptsFunction
+ : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.removeContentScripts",
+ WEBVIEWINTERNAL_REMOVECONTENTSCRIPTS);
+
+ WebViewInternalRemoveContentScriptsFunction();
+
+ protected:
+ ~WebViewInternalRemoveContentScriptsFunction() override;
+
+ private:
+ ExecuteCodeFunction::ResponseAction Run() override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalRemoveContentScriptsFunction);
+};
+
+class WebViewInternalSetNameFunction : public WebViewInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.setName",
+ WEBVIEWINTERNAL_SETNAME);
+
+ WebViewInternalSetNameFunction();
+
+ protected:
+ ~WebViewInternalSetNameFunction() override;
+
+ private:
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalSetNameFunction);
+};
+
+class WebViewInternalSetAllowTransparencyFunction :
+ public WebViewInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.setAllowTransparency",
+ WEBVIEWINTERNAL_SETALLOWTRANSPARENCY);
+
+ WebViewInternalSetAllowTransparencyFunction();
+
+ protected:
+ ~WebViewInternalSetAllowTransparencyFunction() override;
+
+ private:
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalSetAllowTransparencyFunction);
+};
+
+class WebViewInternalSetAllowScalingFunction
+ : public WebViewInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.setAllowScaling",
+ WEBVIEWINTERNAL_SETALLOWSCALING);
+
+ WebViewInternalSetAllowScalingFunction();
+
+ protected:
+ ~WebViewInternalSetAllowScalingFunction() override;
+
+ private:
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalSetAllowScalingFunction);
+};
+
+class WebViewInternalSetZoomFunction : public WebViewInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.setZoom",
+ WEBVIEWINTERNAL_SETZOOM);
+
+ WebViewInternalSetZoomFunction();
+
+ protected:
+ ~WebViewInternalSetZoomFunction() override;
+
+ private:
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalSetZoomFunction);
+};
+
+class WebViewInternalGetZoomFunction : public WebViewInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.getZoom",
+ WEBVIEWINTERNAL_GETZOOM);
+
+ WebViewInternalGetZoomFunction();
+
+ protected:
+ ~WebViewInternalGetZoomFunction() override;
+
+ private:
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalGetZoomFunction);
+};
+
+class WebViewInternalSetZoomModeFunction
+ : public WebViewInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.setZoomMode",
+ WEBVIEWINTERNAL_SETZOOMMODE);
+
+ WebViewInternalSetZoomModeFunction();
+
+ protected:
+ ~WebViewInternalSetZoomModeFunction() override;
+
+ private:
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalSetZoomModeFunction);
+};
+
+class WebViewInternalGetZoomModeFunction
+ : public WebViewInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.getZoomMode",
+ WEBVIEWINTERNAL_GETZOOMMODE);
+
+ WebViewInternalGetZoomModeFunction();
+
+ protected:
+ ~WebViewInternalGetZoomModeFunction() override;
+
+ private:
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalGetZoomModeFunction);
+};
+
+class WebViewInternalFindFunction : public WebViewInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.find", WEBVIEWINTERNAL_FIND);
+
+ WebViewInternalFindFunction();
+
+ // Exposes SendResponse() for use by WebViewInternalFindHelper.
+ using WebViewInternalExtensionFunction::SendResponse;
+
+ protected:
+ ~WebViewInternalFindFunction() override;
+
+ private:
+ // WebViewInternalExtensionFunction implementation.
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalFindFunction);
+};
+
+class WebViewInternalStopFindingFunction
+ : public WebViewInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.stopFinding",
+ WEBVIEWINTERNAL_STOPFINDING);
+
+ WebViewInternalStopFindingFunction();
+
+ protected:
+ ~WebViewInternalStopFindingFunction() override;
+
+ private:
+ // WebViewInternalExtensionFunction implementation.
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalStopFindingFunction);
+};
+
+class WebViewInternalLoadDataWithBaseUrlFunction
+ : public WebViewInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.loadDataWithBaseUrl",
+ WEBVIEWINTERNAL_LOADDATAWITHBASEURL);
+
+ WebViewInternalLoadDataWithBaseUrlFunction();
+
+ protected:
+ ~WebViewInternalLoadDataWithBaseUrlFunction() override;
+
+ private:
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalLoadDataWithBaseUrlFunction);
+};
+
+class WebViewInternalGoFunction : public WebViewInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.go", WEBVIEWINTERNAL_GO);
+
+ WebViewInternalGoFunction();
+
+ protected:
+ ~WebViewInternalGoFunction() override;
+
+ private:
+ // WebViewInternalExtensionFunction implementation.
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalGoFunction);
+};
+
+class WebViewInternalReloadFunction : public WebViewInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.reload", WEBVIEWINTERNAL_RELOAD);
+
+ WebViewInternalReloadFunction();
+
+ protected:
+ ~WebViewInternalReloadFunction() override;
+
+ private:
+ // WebViewInternalExtensionFunction implementation.
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalReloadFunction);
+};
+
+class WebViewInternalSetPermissionFunction
+ : public WebViewInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.setPermission",
+ WEBVIEWINTERNAL_SETPERMISSION);
+
+ WebViewInternalSetPermissionFunction();
+
+ protected:
+ ~WebViewInternalSetPermissionFunction() override;
+
+ private:
+ // WebViewInternalExtensionFunction implementation.
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalSetPermissionFunction);
+};
+
+class WebViewInternalOverrideUserAgentFunction
+ : public WebViewInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.overrideUserAgent",
+ WEBVIEWINTERNAL_OVERRIDEUSERAGENT);
+
+ WebViewInternalOverrideUserAgentFunction();
+
+ protected:
+ ~WebViewInternalOverrideUserAgentFunction() override;
+
+ private:
+ // WebViewInternalExtensionFunction implementation.
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalOverrideUserAgentFunction);
+};
+
+class WebViewInternalStopFunction : public WebViewInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.stop", WEBVIEWINTERNAL_STOP);
+
+ WebViewInternalStopFunction();
+
+ protected:
+ ~WebViewInternalStopFunction() override;
+
+ private:
+ // WebViewInternalExtensionFunction implementation.
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalStopFunction);
+};
+
+class WebViewInternalTerminateFunction
+ : public WebViewInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.terminate",
+ WEBVIEWINTERNAL_TERMINATE);
+
+ WebViewInternalTerminateFunction();
+
+ protected:
+ ~WebViewInternalTerminateFunction() override;
+
+ private:
+ // WebViewInternalExtensionFunction implementation.
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalTerminateFunction);
+};
+
+class WebViewInternalClearDataFunction
+ : public WebViewInternalExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webViewInternal.clearData",
+ WEBVIEWINTERNAL_CLEARDATA);
+
+ WebViewInternalClearDataFunction();
+
+ protected:
+ ~WebViewInternalClearDataFunction() override;
+
+ private:
+ // WebViewInternalExtensionFunction implementation.
+ bool RunAsyncSafe(WebViewGuest* guest) override;
+
+ uint32_t GetRemovalMask();
+ void ClearDataDone();
+
+ // Removal start time.
+ base::Time remove_since_;
+ // Removal mask, corresponds to StoragePartition::RemoveDataMask enum.
+ uint32_t remove_mask_;
+ // Tracks any data related or parse errors.
+ bool bad_message_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewInternalClearDataFunction);
+};
+
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_API_WEB_VIEW_WEB_VIEW_INTERNAL_API_H_
diff --git a/chromium/extensions/browser/api/hid/OWNERS b/chromium/extensions/browser/api/hid/OWNERS
new file mode 100644
index 00000000000..bcb39225427
--- /dev/null
+++ b/chromium/extensions/browser/api/hid/OWNERS
@@ -0,0 +1,3 @@
+reillyg@chromium.org
+rockot@chromium.org
+rpaquay@chromium.org
diff --git a/chromium/extensions/browser/api/hid/hid_api.cc b/chromium/extensions/browser/api/hid/hid_api.cc
new file mode 100644
index 00000000000..841795db2c4
--- /dev/null
+++ b/chromium/extensions/browser/api/hid/hid_api.cc
@@ -0,0 +1,386 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/hid/hid_api.h"
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "device/core/device_client.h"
+#include "device/hid/hid_connection.h"
+#include "device/hid/hid_device_filter.h"
+#include "device/hid/hid_device_info.h"
+#include "device/hid/hid_service.h"
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/browser/api/device_permissions_prompt.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/common/api/hid.h"
+#include "net/base/io_buffer.h"
+
+// The normal EXTENSION_FUNCTION_VALIDATE macro doesn't work well here. It's
+// used in functions that returns a bool. However, EXTENSION_FUNCTION_VALIDATE
+// returns a smart pointer on failure.
+//
+// With C++11, this is problematic since a smart pointer that uses explicit
+// operator bool won't allow this conversion, since it's not in a context (such
+// as a conditional) where a contextual conversion to bool would be allowed.
+// TODO(rdevlin.cronin): restructure this code to remove the need for the
+// additional macro.
+#ifdef NDEBUG
+#define EXTENSION_FUNCTION_VALIDATE_RETURN_FALSE_ON_ERROR(test) \
+ do { \
+ if (!(test)) { \
+ this->bad_message_ = true; \
+ return false; \
+ } \
+ } while (0)
+#else // NDEBUG
+#define EXTENSION_FUNCTION_VALIDATE_RETURN_FALSE_ON_ERROR(test) CHECK(test)
+#endif // NDEBUG
+
+namespace hid = extensions::api::hid;
+
+using device::HidConnection;
+using device::HidDeviceFilter;
+using device::HidDeviceInfo;
+using device::HidService;
+
+namespace {
+
+const char kErrorPermissionDenied[] = "Permission to access device was denied.";
+const char kErrorInvalidDeviceId[] = "Invalid HID device ID.";
+const char kErrorFailedToOpenDevice[] = "Failed to open HID device.";
+const char kErrorConnectionNotFound[] = "Connection not established.";
+const char kErrorTransfer[] = "Transfer failed.";
+
+base::Value* PopulateHidConnection(int connection_id,
+ scoped_refptr<HidConnection> connection) {
+ hid::HidConnectInfo connection_value;
+ connection_value.connection_id = connection_id;
+ return connection_value.ToValue().release();
+}
+
+void ConvertHidDeviceFilter(const hid::DeviceFilter& input,
+ HidDeviceFilter* output) {
+ if (input.vendor_id) {
+ output->SetVendorId(*input.vendor_id);
+ }
+ if (input.product_id) {
+ output->SetProductId(*input.product_id);
+ }
+ if (input.usage_page) {
+ output->SetUsagePage(*input.usage_page);
+ }
+ if (input.usage) {
+ output->SetUsage(*input.usage);
+ }
+}
+
+} // namespace
+
+namespace extensions {
+
+HidGetDevicesFunction::HidGetDevicesFunction() {}
+
+HidGetDevicesFunction::~HidGetDevicesFunction() {}
+
+ExtensionFunction::ResponseAction HidGetDevicesFunction::Run() {
+ scoped_ptr<api::hid::GetDevices::Params> parameters =
+ hid::GetDevices::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters);
+
+ HidDeviceManager* device_manager = HidDeviceManager::Get(browser_context());
+ CHECK(device_manager);
+
+ std::vector<HidDeviceFilter> filters;
+ if (parameters->options.filters) {
+ filters.resize(parameters->options.filters->size());
+ for (size_t i = 0; i < parameters->options.filters->size(); ++i) {
+ ConvertHidDeviceFilter(parameters->options.filters->at(i), &filters[i]);
+ }
+ }
+ if (parameters->options.vendor_id) {
+ HidDeviceFilter legacy_filter;
+ legacy_filter.SetVendorId(*parameters->options.vendor_id);
+ if (parameters->options.product_id) {
+ legacy_filter.SetProductId(*parameters->options.product_id);
+ }
+ filters.push_back(legacy_filter);
+ }
+
+ device_manager->GetApiDevices(
+ extension(), filters,
+ base::Bind(&HidGetDevicesFunction::OnEnumerationComplete, this));
+ return RespondLater();
+}
+
+void HidGetDevicesFunction::OnEnumerationComplete(
+ scoped_ptr<base::ListValue> devices) {
+ Respond(OneArgument(devices.release()));
+}
+
+HidGetUserSelectedDevicesFunction::HidGetUserSelectedDevicesFunction() {
+}
+
+HidGetUserSelectedDevicesFunction::~HidGetUserSelectedDevicesFunction() {
+}
+
+ExtensionFunction::ResponseAction HidGetUserSelectedDevicesFunction::Run() {
+ scoped_ptr<api::hid::GetUserSelectedDevices::Params> parameters =
+ hid::GetUserSelectedDevices::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters);
+
+ content::WebContents* web_contents = GetSenderWebContents();
+ if (!web_contents || !user_gesture()) {
+ return RespondNow(OneArgument(new base::ListValue()));
+ }
+
+ bool multiple = false;
+ std::vector<HidDeviceFilter> filters;
+ if (parameters->options) {
+ multiple = parameters->options->multiple && *parameters->options->multiple;
+ if (parameters->options->filters) {
+ const auto& api_filters = *parameters->options->filters;
+ filters.resize(api_filters.size());
+ for (size_t i = 0; i < api_filters.size(); ++i) {
+ ConvertHidDeviceFilter(api_filters[i], &filters[i]);
+ }
+ }
+ }
+
+ prompt_ =
+ ExtensionsAPIClient::Get()->CreateDevicePermissionsPrompt(web_contents);
+ CHECK(prompt_);
+ prompt_->AskForHidDevices(
+ extension(), browser_context(), multiple, filters,
+ base::Bind(&HidGetUserSelectedDevicesFunction::OnDevicesChosen, this));
+ return RespondLater();
+}
+
+void HidGetUserSelectedDevicesFunction::OnDevicesChosen(
+ const std::vector<scoped_refptr<HidDeviceInfo>>& devices) {
+ HidDeviceManager* device_manager = HidDeviceManager::Get(browser_context());
+ CHECK(device_manager);
+ Respond(OneArgument(device_manager->GetApiDevicesFromList(devices)));
+}
+
+HidConnectFunction::HidConnectFunction() : connection_manager_(nullptr) {
+}
+
+HidConnectFunction::~HidConnectFunction() {}
+
+ExtensionFunction::ResponseAction HidConnectFunction::Run() {
+ scoped_ptr<api::hid::Connect::Params> parameters =
+ hid::Connect::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters);
+
+ HidDeviceManager* device_manager = HidDeviceManager::Get(browser_context());
+ CHECK(device_manager);
+
+ connection_manager_ =
+ ApiResourceManager<HidConnectionResource>::Get(browser_context());
+ CHECK(connection_manager_);
+
+ scoped_refptr<HidDeviceInfo> device_info =
+ device_manager->GetDeviceInfo(parameters->device_id);
+ if (!device_info) {
+ return RespondNow(Error(kErrorInvalidDeviceId));
+ }
+
+ if (!device_manager->HasPermission(extension(), device_info, true)) {
+ return RespondNow(Error(kErrorPermissionDenied));
+ }
+
+ HidService* hid_service = device::DeviceClient::Get()->GetHidService();
+ CHECK(hid_service);
+
+ hid_service->Connect(
+ device_info->device_id(),
+ base::Bind(&HidConnectFunction::OnConnectComplete, this));
+ return RespondLater();
+}
+
+void HidConnectFunction::OnConnectComplete(
+ scoped_refptr<HidConnection> connection) {
+ if (!connection) {
+ Respond(Error(kErrorFailedToOpenDevice));
+ return;
+ }
+
+ DCHECK(connection_manager_);
+ int connection_id = connection_manager_->Add(
+ new HidConnectionResource(extension_id(), connection));
+ Respond(OneArgument(PopulateHidConnection(connection_id, connection)));
+}
+
+HidDisconnectFunction::HidDisconnectFunction() {}
+
+HidDisconnectFunction::~HidDisconnectFunction() {}
+
+ExtensionFunction::ResponseAction HidDisconnectFunction::Run() {
+ scoped_ptr<api::hid::Disconnect::Params> parameters =
+ hid::Disconnect::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters);
+
+ ApiResourceManager<HidConnectionResource>* connection_manager =
+ ApiResourceManager<HidConnectionResource>::Get(browser_context());
+ CHECK(connection_manager);
+
+ int connection_id = parameters->connection_id;
+ HidConnectionResource* resource =
+ connection_manager->Get(extension_id(), connection_id);
+ if (!resource) {
+ return RespondNow(Error(kErrorConnectionNotFound));
+ }
+
+ connection_manager->Remove(extension_id(), connection_id);
+ return RespondNow(NoArguments());
+}
+
+HidConnectionIoFunction::HidConnectionIoFunction() {
+}
+
+HidConnectionIoFunction::~HidConnectionIoFunction() {
+}
+
+ExtensionFunction::ResponseAction HidConnectionIoFunction::Run() {
+ if (!ValidateParameters()) {
+ return RespondNow(Error(error_));
+ }
+
+ ApiResourceManager<HidConnectionResource>* connection_manager =
+ ApiResourceManager<HidConnectionResource>::Get(browser_context());
+ CHECK(connection_manager);
+
+ HidConnectionResource* resource =
+ connection_manager->Get(extension_id(), connection_id_);
+ if (!resource) {
+ return RespondNow(Error(kErrorConnectionNotFound));
+ }
+
+ StartWork(resource->connection().get());
+ return RespondLater();
+}
+
+HidReceiveFunction::HidReceiveFunction() {}
+
+HidReceiveFunction::~HidReceiveFunction() {}
+
+bool HidReceiveFunction::ValidateParameters() {
+ parameters_ = hid::Receive::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE_RETURN_FALSE_ON_ERROR(parameters_);
+ set_connection_id(parameters_->connection_id);
+ return true;
+}
+
+void HidReceiveFunction::StartWork(HidConnection* connection) {
+ connection->Read(base::Bind(&HidReceiveFunction::OnFinished, this));
+}
+
+void HidReceiveFunction::OnFinished(bool success,
+ scoped_refptr<net::IOBuffer> buffer,
+ size_t size) {
+ if (success) {
+ DCHECK_GE(size, 1u);
+ int report_id = reinterpret_cast<uint8_t*>(buffer->data())[0];
+
+ Respond(TwoArguments(new base::FundamentalValue(report_id),
+ base::BinaryValue::CreateWithCopiedBuffer(
+ buffer->data() + 1, size - 1)));
+ } else {
+ Respond(Error(kErrorTransfer));
+ }
+}
+
+HidSendFunction::HidSendFunction() {}
+
+HidSendFunction::~HidSendFunction() {}
+
+bool HidSendFunction::ValidateParameters() {
+ parameters_ = hid::Send::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE_RETURN_FALSE_ON_ERROR(parameters_);
+ set_connection_id(parameters_->connection_id);
+ return true;
+}
+
+void HidSendFunction::StartWork(HidConnection* connection) {
+ scoped_refptr<net::IOBufferWithSize> buffer(
+ new net::IOBufferWithSize(parameters_->data.size() + 1));
+ buffer->data()[0] = static_cast<uint8_t>(parameters_->report_id);
+ memcpy(buffer->data() + 1, parameters_->data.data(),
+ parameters_->data.size());
+ connection->Write(buffer, buffer->size(),
+ base::Bind(&HidSendFunction::OnFinished, this));
+}
+
+void HidSendFunction::OnFinished(bool success) {
+ if (success) {
+ Respond(NoArguments());
+ } else {
+ Respond(Error(kErrorTransfer));
+ }
+}
+
+HidReceiveFeatureReportFunction::HidReceiveFeatureReportFunction() {}
+
+HidReceiveFeatureReportFunction::~HidReceiveFeatureReportFunction() {}
+
+bool HidReceiveFeatureReportFunction::ValidateParameters() {
+ parameters_ = hid::ReceiveFeatureReport::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE_RETURN_FALSE_ON_ERROR(parameters_);
+ set_connection_id(parameters_->connection_id);
+ return true;
+}
+
+void HidReceiveFeatureReportFunction::StartWork(HidConnection* connection) {
+ connection->GetFeatureReport(
+ static_cast<uint8_t>(parameters_->report_id),
+ base::Bind(&HidReceiveFeatureReportFunction::OnFinished, this));
+}
+
+void HidReceiveFeatureReportFunction::OnFinished(
+ bool success,
+ scoped_refptr<net::IOBuffer> buffer,
+ size_t size) {
+ if (success) {
+ Respond(OneArgument(
+ base::BinaryValue::CreateWithCopiedBuffer(buffer->data(), size)));
+ } else {
+ Respond(Error(kErrorTransfer));
+ }
+}
+
+HidSendFeatureReportFunction::HidSendFeatureReportFunction() {}
+
+HidSendFeatureReportFunction::~HidSendFeatureReportFunction() {}
+
+bool HidSendFeatureReportFunction::ValidateParameters() {
+ parameters_ = hid::SendFeatureReport::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE_RETURN_FALSE_ON_ERROR(parameters_);
+ set_connection_id(parameters_->connection_id);
+ return true;
+}
+
+void HidSendFeatureReportFunction::StartWork(HidConnection* connection) {
+ scoped_refptr<net::IOBufferWithSize> buffer(
+ new net::IOBufferWithSize(parameters_->data.size() + 1));
+ buffer->data()[0] = static_cast<uint8_t>(parameters_->report_id);
+ memcpy(buffer->data() + 1, parameters_->data.data(),
+ parameters_->data.size());
+ connection->SendFeatureReport(
+ buffer, buffer->size(),
+ base::Bind(&HidSendFeatureReportFunction::OnFinished, this));
+}
+
+void HidSendFeatureReportFunction::OnFinished(bool success) {
+ if (success) {
+ Respond(NoArguments());
+ } else {
+ Respond(Error(kErrorTransfer));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/hid/hid_api.h b/chromium/extensions/browser/api/hid/hid_api.h
new file mode 100644
index 00000000000..99824696f9b
--- /dev/null
+++ b/chromium/extensions/browser/api/hid/hid_api.h
@@ -0,0 +1,216 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_HID_HID_API_H_
+#define EXTENSIONS_BROWSER_API_HID_HID_API_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/browser/api/hid/hid_connection_resource.h"
+#include "extensions/browser/api/hid/hid_device_manager.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/common/api/hid.h"
+
+namespace device {
+class HidConnection;
+class HidDeviceInfo;
+class HidService;
+} // namespace device
+
+namespace net {
+class IOBuffer;
+} // namespace net
+
+namespace extensions {
+
+class DevicePermissionsPrompt;
+
+class HidGetDevicesFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("hid.getDevices", HID_GETDEVICES);
+
+ HidGetDevicesFunction();
+
+ private:
+ ~HidGetDevicesFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ void OnEnumerationComplete(scoped_ptr<base::ListValue> devices);
+
+ DISALLOW_COPY_AND_ASSIGN(HidGetDevicesFunction);
+};
+
+class HidGetUserSelectedDevicesFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("hid.getUserSelectedDevices",
+ HID_GETUSERSELECTEDDEVICES)
+
+ HidGetUserSelectedDevicesFunction();
+
+ private:
+ ~HidGetUserSelectedDevicesFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ void OnDevicesChosen(
+ const std::vector<scoped_refptr<device::HidDeviceInfo>>& devices);
+
+ HidDeviceManager* device_manager_;
+ scoped_ptr<DevicePermissionsPrompt> prompt_;
+
+ DISALLOW_COPY_AND_ASSIGN(HidGetUserSelectedDevicesFunction);
+};
+
+class HidConnectFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("hid.connect", HID_CONNECT);
+
+ HidConnectFunction();
+
+ private:
+ ~HidConnectFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ void OnConnectComplete(scoped_refptr<device::HidConnection> connection);
+
+ ApiResourceManager<HidConnectionResource>* connection_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(HidConnectFunction);
+};
+
+class HidDisconnectFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("hid.disconnect", HID_DISCONNECT);
+
+ HidDisconnectFunction();
+
+ private:
+ ~HidDisconnectFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ DISALLOW_COPY_AND_ASSIGN(HidDisconnectFunction);
+};
+
+// Base class for extension functions that start some asynchronous work after
+// looking up a HidConnection.
+class HidConnectionIoFunction : public UIThreadExtensionFunction {
+ public:
+ HidConnectionIoFunction();
+
+ protected:
+ ~HidConnectionIoFunction() override;
+
+ virtual bool ValidateParameters() = 0;
+ virtual void StartWork(device::HidConnection* connection) = 0;
+
+ void set_connection_id(int connection_id) { connection_id_ = connection_id; }
+
+ private:
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ int connection_id_;
+};
+
+class HidReceiveFunction : public HidConnectionIoFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("hid.receive", HID_RECEIVE);
+
+ HidReceiveFunction();
+
+ private:
+ ~HidReceiveFunction() override;
+
+ // HidConnectionIoFunction:
+ bool ValidateParameters() override;
+ void StartWork(device::HidConnection* connection) override;
+
+ void OnFinished(bool success,
+ scoped_refptr<net::IOBuffer> buffer,
+ size_t size);
+
+ scoped_ptr<api::hid::Receive::Params> parameters_;
+
+ DISALLOW_COPY_AND_ASSIGN(HidReceiveFunction);
+};
+
+class HidSendFunction : public HidConnectionIoFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("hid.send", HID_SEND);
+
+ HidSendFunction();
+
+ private:
+ ~HidSendFunction() override;
+
+ // HidConnectionIoFunction:
+ bool ValidateParameters() override;
+ void StartWork(device::HidConnection* connection) override;
+
+ void OnFinished(bool success);
+
+ scoped_ptr<api::hid::Send::Params> parameters_;
+
+ DISALLOW_COPY_AND_ASSIGN(HidSendFunction);
+};
+
+class HidReceiveFeatureReportFunction : public HidConnectionIoFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("hid.receiveFeatureReport",
+ HID_RECEIVEFEATUREREPORT);
+
+ HidReceiveFeatureReportFunction();
+
+ private:
+ ~HidReceiveFeatureReportFunction() override;
+
+ // HidConnectionIoFunction:
+ bool ValidateParameters() override;
+ void StartWork(device::HidConnection* connection) override;
+
+ void OnFinished(bool success,
+ scoped_refptr<net::IOBuffer> buffer,
+ size_t size);
+
+ scoped_ptr<api::hid::ReceiveFeatureReport::Params> parameters_;
+
+ DISALLOW_COPY_AND_ASSIGN(HidReceiveFeatureReportFunction);
+};
+
+class HidSendFeatureReportFunction : public HidConnectionIoFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("hid.sendFeatureReport", HID_SENDFEATUREREPORT);
+
+ HidSendFeatureReportFunction();
+
+ private:
+ ~HidSendFeatureReportFunction() override;
+
+ // HidConnectionIoFunction:
+ bool ValidateParameters() override;
+ void StartWork(device::HidConnection* connection) override;
+
+ void OnFinished(bool success);
+
+ scoped_ptr<api::hid::SendFeatureReport::Params> parameters_;
+
+ DISALLOW_COPY_AND_ASSIGN(HidSendFeatureReportFunction);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_HID_HID_API_H_
diff --git a/chromium/extensions/browser/api/hid/hid_apitest.cc b/chromium/extensions/browser/api/hid/hid_apitest.cc
new file mode 100644
index 00000000000..706419dd393
--- /dev/null
+++ b/chromium/extensions/browser/api/hid/hid_apitest.cc
@@ -0,0 +1,266 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "device/core/mock_device_client.h"
+#include "device/hid/hid_collection_info.h"
+#include "device/hid/hid_connection.h"
+#include "device/hid/hid_device_info.h"
+#include "device/hid/hid_usage_and_page.h"
+#include "device/hid/mock_hid_service.h"
+#include "extensions/browser/api/device_permissions_prompt.h"
+#include "extensions/shell/browser/shell_extensions_api_client.h"
+#include "extensions/shell/test/shell_apitest.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "net/base/io_buffer.h"
+
+using base::ThreadTaskRunnerHandle;
+using device::HidCollectionInfo;
+using device::HidDeviceId;
+using device::HidDeviceInfo;
+using device::HidUsageAndPage;
+using device::MockDeviceClient;
+using net::IOBuffer;
+using testing::_;
+
+#if defined(OS_MACOSX)
+const uint64_t kTestDeviceIds[] = {1, 2, 3, 4, 5};
+#else
+const char* kTestDeviceIds[] = {"A", "B", "C", "D", "E"};
+#endif
+
+// These report descriptors define two devices with 8-byte input, output and
+// feature reports. The first implements usage page 0xFF00 and has a single
+// report without and ID. The second implements usage page 0xFF01 and has a
+// single report with ID 1.
+const uint8_t kReportDescriptor[] = {0x06, 0x00, 0xFF, 0x08, 0xA1, 0x01, 0x15,
+ 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95,
+ 0x08, 0x08, 0x81, 0x02, 0x08, 0x91, 0x02,
+ 0x08, 0xB1, 0x02, 0xC0};
+const uint8_t kReportDescriptorWithIDs[] = {
+ 0x06, 0x01, 0xFF, 0x08, 0xA1, 0x01, 0x15, 0x00, 0x26,
+ 0xFF, 0x00, 0x85, 0x01, 0x75, 0x08, 0x95, 0x08, 0x08,
+ 0x81, 0x02, 0x08, 0x91, 0x02, 0x08, 0xB1, 0x02, 0xC0};
+
+namespace extensions {
+
+class MockHidConnection : public device::HidConnection {
+ public:
+ explicit MockHidConnection(scoped_refptr<HidDeviceInfo> device_info)
+ : HidConnection(device_info) {}
+
+ void PlatformClose() override {}
+
+ void PlatformRead(const ReadCallback& callback) override {
+ const char kResult[] = "This is a HID input report.";
+ uint8_t report_id = device_info()->has_report_id() ? 1 : 0;
+ scoped_refptr<IOBuffer> buffer(new IOBuffer(sizeof(kResult)));
+ buffer->data()[0] = report_id;
+ memcpy(buffer->data() + 1, kResult, sizeof(kResult) - 1);
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, true, buffer, sizeof(kResult)));
+ }
+
+ void PlatformWrite(scoped_refptr<net::IOBuffer> buffer,
+ size_t size,
+ const WriteCallback& callback) override {
+ const char kExpected[] = "o-report"; // 8 bytes
+ bool result = false;
+ if (size == sizeof(kExpected)) {
+ uint8_t report_id = buffer->data()[0];
+ uint8_t expected_report_id = device_info()->has_report_id() ? 1 : 0;
+ if (report_id == expected_report_id) {
+ if (memcmp(buffer->data() + 1, kExpected, sizeof(kExpected) - 1) == 0) {
+ result = true;
+ }
+ }
+ }
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::Bind(callback, result));
+ }
+
+ void PlatformGetFeatureReport(uint8_t report_id,
+ const ReadCallback& callback) override {
+ const char kResult[] = "This is a HID feature report.";
+ scoped_refptr<IOBuffer> buffer(new IOBuffer(sizeof(kResult)));
+ size_t offset = 0;
+ if (device_info()->has_report_id()) {
+ buffer->data()[offset++] = report_id;
+ }
+ memcpy(buffer->data() + offset, kResult, sizeof(kResult) - 1);
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, true, buffer, sizeof(kResult) - 1 + offset));
+ }
+
+ void PlatformSendFeatureReport(scoped_refptr<net::IOBuffer> buffer,
+ size_t size,
+ const WriteCallback& callback) override {
+ const char kExpected[] = "The app is setting this HID feature report.";
+ bool result = false;
+ if (size == sizeof(kExpected)) {
+ uint8_t report_id = buffer->data()[0];
+ uint8_t expected_report_id = device_info()->has_report_id() ? 1 : 0;
+ if (report_id == expected_report_id &&
+ memcmp(buffer->data() + 1, kExpected, sizeof(kExpected) - 1) == 0) {
+ result = true;
+ }
+ }
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::Bind(callback, result));
+ }
+
+ private:
+ ~MockHidConnection() override {}
+};
+
+class TestDevicePermissionsPrompt
+ : public DevicePermissionsPrompt,
+ public DevicePermissionsPrompt::Prompt::Observer {
+ public:
+ explicit TestDevicePermissionsPrompt(content::WebContents* web_contents)
+ : DevicePermissionsPrompt(web_contents) {}
+
+ ~TestDevicePermissionsPrompt() override { prompt()->SetObserver(nullptr); }
+
+ void ShowDialog() override { prompt()->SetObserver(this); }
+
+ void OnDevicesChanged() override {
+ for (size_t i = 0; i < prompt()->GetDeviceCount(); ++i) {
+ prompt()->GrantDevicePermission(i);
+ if (!prompt()->multiple()) {
+ break;
+ }
+ }
+ prompt()->Dismissed();
+ }
+};
+
+class TestExtensionsAPIClient : public ShellExtensionsAPIClient {
+ public:
+ TestExtensionsAPIClient() : ShellExtensionsAPIClient() {}
+
+ scoped_ptr<DevicePermissionsPrompt> CreateDevicePermissionsPrompt(
+ content::WebContents* web_contents) const override {
+ return make_scoped_ptr(new TestDevicePermissionsPrompt(web_contents));
+ }
+};
+
+class HidApiTest : public ShellApiTest {
+ public:
+ void SetUpOnMainThread() override {
+ ShellApiTest::SetUpOnMainThread();
+
+ // MockDeviceClient replaces ShellDeviceClient.
+ device_client_.reset(new MockDeviceClient());
+
+ ON_CALL(*device_client_->hid_service(), Connect(_, _))
+ .WillByDefault(Invoke(this, &HidApiTest::Connect));
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&HidApiTest::LazyFirstEnumeration, base::Unretained(this)));
+ }
+
+ void Connect(const HidDeviceId& device_id,
+ const device::HidService::ConnectCallback& callback) {
+ const auto& devices = device_client_->hid_service()->devices();
+ const auto& device_entry = devices.find(device_id);
+ scoped_refptr<MockHidConnection> connection;
+ if (device_entry != devices.end()) {
+ connection = new MockHidConnection(device_entry->second);
+ }
+
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::Bind(callback, connection));
+ }
+
+ void LazyFirstEnumeration() {
+ AddDevice(kTestDeviceIds[0], 0x18D1, 0x58F0, false);
+ AddDevice(kTestDeviceIds[1], 0x18D1, 0x58F0, true);
+ AddDevice(kTestDeviceIds[2], 0x18D1, 0x58F1, false);
+ device_client_->hid_service()->FirstEnumerationComplete();
+ }
+
+ void AddDevice(const HidDeviceId& device_id,
+ int vendor_id,
+ int product_id,
+ bool report_id) {
+ std::vector<uint8_t> report_descriptor;
+ if (report_id) {
+ report_descriptor.insert(
+ report_descriptor.begin(), kReportDescriptorWithIDs,
+ kReportDescriptorWithIDs + sizeof(kReportDescriptorWithIDs));
+ } else {
+ report_descriptor.insert(report_descriptor.begin(), kReportDescriptor,
+ kReportDescriptor + sizeof(kReportDescriptor));
+ }
+ device_client_->hid_service()->AddDevice(
+ new HidDeviceInfo(device_id, vendor_id, product_id, "Test Device", "A",
+ device::kHIDBusTypeUSB, report_descriptor));
+ }
+
+ protected:
+ scoped_ptr<MockDeviceClient> device_client_;
+};
+
+IN_PROC_BROWSER_TEST_F(HidApiTest, HidApp) {
+ ASSERT_TRUE(RunAppTest("api_test/hid/api")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(HidApiTest, OnDeviceAdded) {
+ ExtensionTestMessageListener load_listener("loaded", false);
+ ExtensionTestMessageListener result_listener("success", false);
+ result_listener.set_failure_message("failure");
+
+ ASSERT_TRUE(LoadApp("api_test/hid/add_event"));
+ ASSERT_TRUE(load_listener.WaitUntilSatisfied());
+
+ // Add a blocked device first so that the test will fail if a notification is
+ // received.
+ AddDevice(kTestDeviceIds[3], 0x18D1, 0x58F1, false);
+ AddDevice(kTestDeviceIds[4], 0x18D1, 0x58F0, false);
+ ASSERT_TRUE(result_listener.WaitUntilSatisfied());
+ EXPECT_EQ("success", result_listener.message());
+}
+
+IN_PROC_BROWSER_TEST_F(HidApiTest, OnDeviceRemoved) {
+ ExtensionTestMessageListener load_listener("loaded", false);
+ ExtensionTestMessageListener result_listener("success", false);
+ result_listener.set_failure_message("failure");
+
+ ASSERT_TRUE(LoadApp("api_test/hid/remove_event"));
+ ASSERT_TRUE(load_listener.WaitUntilSatisfied());
+
+ // Device C was not returned by chrome.hid.getDevices, the app will not get
+ // a notification.
+ device_client_->hid_service()->RemoveDevice(kTestDeviceIds[2]);
+ // Device A was returned, the app will get a notification.
+ device_client_->hid_service()->RemoveDevice(kTestDeviceIds[0]);
+ ASSERT_TRUE(result_listener.WaitUntilSatisfied());
+ EXPECT_EQ("success", result_listener.message());
+}
+
+IN_PROC_BROWSER_TEST_F(HidApiTest, GetUserSelectedDevices) {
+ ExtensionTestMessageListener open_listener("opened_device", false);
+
+ TestExtensionsAPIClient test_api_client;
+ ASSERT_TRUE(LoadApp("api_test/hid/get_user_selected_devices"));
+ ASSERT_TRUE(open_listener.WaitUntilSatisfied());
+
+ ExtensionTestMessageListener remove_listener("removed", false);
+ device_client_->hid_service()->RemoveDevice(kTestDeviceIds[0]);
+ ASSERT_TRUE(remove_listener.WaitUntilSatisfied());
+
+ ExtensionTestMessageListener add_listener("added", false);
+ AddDevice(kTestDeviceIds[0], 0x18D1, 0x58F0, true);
+ ASSERT_TRUE(add_listener.WaitUntilSatisfied());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/hid/hid_connection_resource.cc b/chromium/extensions/browser/api/hid/hid_connection_resource.cc
new file mode 100644
index 00000000000..d77073c355d
--- /dev/null
+++ b/chromium/extensions/browser/api/hid/hid_connection_resource.cc
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/hid/hid_connection_resource.h"
+
+#include <string>
+
+#include "base/lazy_instance.h"
+#include "base/memory/ref_counted.h"
+#include "device/hid/hid_connection.h"
+#include "extensions/browser/api/api_resource_manager.h"
+
+namespace extensions {
+
+static base::LazyInstance<
+ BrowserContextKeyedAPIFactory<ApiResourceManager<HidConnectionResource> > >
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+template <>
+BrowserContextKeyedAPIFactory<ApiResourceManager<HidConnectionResource> >*
+ApiResourceManager<HidConnectionResource>::GetFactoryInstance() {
+ return &g_factory.Get();
+}
+
+HidConnectionResource::HidConnectionResource(
+ const std::string& owner_extension_id,
+ scoped_refptr<device::HidConnection> connection)
+ : ApiResource(owner_extension_id), connection_(connection) {}
+
+HidConnectionResource::~HidConnectionResource() {
+ connection_->Close();
+}
+
+bool HidConnectionResource::IsPersistent() const {
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/hid/hid_connection_resource.h b/chromium/extensions/browser/api/hid/hid_connection_resource.h
new file mode 100644
index 00000000000..674b05ce618
--- /dev/null
+++ b/chromium/extensions/browser/api/hid/hid_connection_resource.h
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_HID_HID_CONNECTION_RESOURCE_H_
+#define EXTENSIONS_BROWSER_API_HID_HID_CONNECTION_RESOURCE_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "content/public/browser/browser_thread.h"
+#include "device/hid/hid_connection.h"
+#include "extensions/browser/api/api_resource.h"
+
+namespace device {
+class HidConnection;
+}
+
+namespace extensions {
+
+class HidConnectionResource : public ApiResource {
+ public:
+ static const content::BrowserThread::ID kThreadId =
+ content::BrowserThread::UI;
+
+ HidConnectionResource(const std::string& owner_extension_id,
+ scoped_refptr<device::HidConnection> connection);
+ ~HidConnectionResource() override;
+
+ scoped_refptr<device::HidConnection> connection() const {
+ return connection_;
+ }
+
+ bool IsPersistent() const override;
+
+ static const char* service_name() { return "HidConnectionResourceManager"; }
+
+ private:
+ scoped_refptr<device::HidConnection> connection_;
+
+ DISALLOW_COPY_AND_ASSIGN(HidConnectionResource);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_HID_HID_CONNECTION_RESOURCE_H_
diff --git a/chromium/extensions/browser/api/hid/hid_device_manager.cc b/chromium/extensions/browser/api/hid/hid_device_manager.cc
new file mode 100644
index 00000000000..11e1569ffe9
--- /dev/null
+++ b/chromium/extensions/browser/api/hid/hid_device_manager.cc
@@ -0,0 +1,338 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/hid/hid_device_manager.h"
+
+#include <stdint.h>
+
+#include <limits>
+#include <utility>
+#include <vector>
+
+#include "base/lazy_instance.h"
+#include "device/core/device_client.h"
+#include "device/hid/hid_device_filter.h"
+#include "device/hid/hid_service.h"
+#include "extensions/browser/api/device_permissions_manager.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/permissions/usb_device_permission.h"
+
+namespace hid = extensions::api::hid;
+
+using device::HidDeviceFilter;
+using device::HidDeviceId;
+using device::HidDeviceInfo;
+using device::HidService;
+
+namespace extensions {
+
+namespace {
+
+void PopulateHidDeviceInfo(hid::HidDeviceInfo* output,
+ scoped_refptr<const HidDeviceInfo> input) {
+ output->vendor_id = input->vendor_id();
+ output->product_id = input->product_id();
+ output->product_name = input->product_name();
+ output->serial_number = input->serial_number();
+ output->max_input_report_size = input->max_input_report_size();
+ output->max_output_report_size = input->max_output_report_size();
+ output->max_feature_report_size = input->max_feature_report_size();
+
+ for (const device::HidCollectionInfo& collection : input->collections()) {
+ // Don't expose sensitive data.
+ if (collection.usage.IsProtected()) {
+ continue;
+ }
+
+ hid::HidCollectionInfo api_collection;
+ api_collection.usage_page = collection.usage.usage_page;
+ api_collection.usage = collection.usage.usage;
+
+ api_collection.report_ids.resize(collection.report_ids.size());
+ std::copy(collection.report_ids.begin(), collection.report_ids.end(),
+ api_collection.report_ids.begin());
+
+ output->collections.push_back(std::move(api_collection));
+ }
+
+ const std::vector<uint8_t>& report_descriptor = input->report_descriptor();
+ if (report_descriptor.size() > 0) {
+ output->report_descriptor.assign(report_descriptor.begin(),
+ report_descriptor.end());
+ }
+}
+
+bool WillDispatchDeviceEvent(base::WeakPtr<HidDeviceManager> device_manager,
+ scoped_refptr<device::HidDeviceInfo> device_info,
+ content::BrowserContext* context,
+ const Extension* extension,
+ Event* event,
+ const base::DictionaryValue* listener_filter) {
+ if (device_manager && extension) {
+ return device_manager->HasPermission(extension, device_info, false);
+ }
+ return false;
+}
+
+} // namespace
+
+struct HidDeviceManager::GetApiDevicesParams {
+ public:
+ GetApiDevicesParams(const Extension* extension,
+ const std::vector<HidDeviceFilter>& filters,
+ const GetApiDevicesCallback& callback)
+ : extension(extension), filters(filters), callback(callback) {}
+ ~GetApiDevicesParams() {}
+
+ const Extension* extension;
+ std::vector<HidDeviceFilter> filters;
+ GetApiDevicesCallback callback;
+};
+
+HidDeviceManager::HidDeviceManager(content::BrowserContext* context)
+ : browser_context_(context),
+ hid_service_observer_(this),
+ weak_factory_(this) {
+ event_router_ = EventRouter::Get(context);
+ if (event_router_) {
+ event_router_->RegisterObserver(this, hid::OnDeviceAdded::kEventName);
+ event_router_->RegisterObserver(this, hid::OnDeviceRemoved::kEventName);
+ }
+}
+
+HidDeviceManager::~HidDeviceManager() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+// static
+BrowserContextKeyedAPIFactory<HidDeviceManager>*
+HidDeviceManager::GetFactoryInstance() {
+ static base::LazyInstance<BrowserContextKeyedAPIFactory<HidDeviceManager> >
+ factory = LAZY_INSTANCE_INITIALIZER;
+ return &factory.Get();
+}
+
+void HidDeviceManager::GetApiDevices(
+ const Extension* extension,
+ const std::vector<HidDeviceFilter>& filters,
+ const GetApiDevicesCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ LazyInitialize();
+
+ if (enumeration_ready_) {
+ scoped_ptr<base::ListValue> devices =
+ CreateApiDeviceList(extension, filters);
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(callback, base::Passed(&devices)));
+ } else {
+ pending_enumerations_.push_back(
+ make_scoped_ptr(new GetApiDevicesParams(extension, filters, callback)));
+ }
+}
+
+scoped_ptr<base::ListValue> HidDeviceManager::GetApiDevicesFromList(
+ const std::vector<scoped_refptr<HidDeviceInfo>>& devices) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ scoped_ptr<base::ListValue> device_list(new base::ListValue());
+ for (const auto& device : devices) {
+ const auto device_entry = resource_ids_.find(device->device_id());
+ DCHECK(device_entry != resource_ids_.end());
+
+ hid::HidDeviceInfo device_info;
+ device_info.device_id = device_entry->second;
+ PopulateHidDeviceInfo(&device_info, device);
+ device_list->Append(device_info.ToValue().release());
+ }
+ return device_list;
+}
+
+scoped_refptr<HidDeviceInfo> HidDeviceManager::GetDeviceInfo(int resource_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ HidService* hid_service = device::DeviceClient::Get()->GetHidService();
+ DCHECK(hid_service);
+
+ ResourceIdToDeviceIdMap::const_iterator device_iter =
+ device_ids_.find(resource_id);
+ if (device_iter == device_ids_.end()) {
+ return nullptr;
+ }
+
+ return hid_service->GetDeviceInfo(device_iter->second);
+}
+
+bool HidDeviceManager::HasPermission(const Extension* extension,
+ scoped_refptr<HidDeviceInfo> device_info,
+ bool update_last_used) {
+ DevicePermissionsManager* permissions_manager =
+ DevicePermissionsManager::Get(browser_context_);
+ CHECK(permissions_manager);
+ DevicePermissions* device_permissions =
+ permissions_manager->GetForExtension(extension->id());
+ DCHECK(device_permissions);
+ scoped_refptr<DevicePermissionEntry> permission_entry =
+ device_permissions->FindHidDeviceEntry(device_info);
+ if (permission_entry) {
+ if (update_last_used) {
+ permissions_manager->UpdateLastUsed(extension->id(), permission_entry);
+ }
+ return true;
+ }
+
+ UsbDevicePermission::CheckParam usbParam(
+ device_info->vendor_id(), device_info->product_id(),
+ UsbDevicePermissionData::UNSPECIFIED_INTERFACE);
+ if (extension->permissions_data()->CheckAPIPermissionWithParam(
+ APIPermission::kUsbDevice, &usbParam)) {
+ return true;
+ }
+
+ if (extension->permissions_data()->HasAPIPermission(
+ APIPermission::kU2fDevices)) {
+ HidDeviceFilter u2f_filter;
+ u2f_filter.SetUsagePage(0xF1D0);
+ if (u2f_filter.Matches(device_info)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void HidDeviceManager::Shutdown() {
+ if (event_router_) {
+ event_router_->UnregisterObserver(this);
+ }
+}
+
+void HidDeviceManager::OnListenerAdded(const EventListenerInfo& details) {
+ LazyInitialize();
+}
+
+void HidDeviceManager::OnDeviceAdded(scoped_refptr<HidDeviceInfo> device_info) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_LT(next_resource_id_, std::numeric_limits<int>::max());
+ int new_id = next_resource_id_++;
+ DCHECK(!ContainsKey(resource_ids_, device_info->device_id()));
+ resource_ids_[device_info->device_id()] = new_id;
+ device_ids_[new_id] = device_info->device_id();
+
+ // Don't generate events during the initial enumeration.
+ if (enumeration_ready_ && event_router_) {
+ api::hid::HidDeviceInfo api_device_info;
+ api_device_info.device_id = new_id;
+ PopulateHidDeviceInfo(&api_device_info, device_info);
+
+ if (api_device_info.collections.size() > 0) {
+ scoped_ptr<base::ListValue> args(
+ hid::OnDeviceAdded::Create(api_device_info));
+ DispatchEvent(events::HID_ON_DEVICE_ADDED, hid::OnDeviceAdded::kEventName,
+ std::move(args), device_info);
+ }
+ }
+}
+
+void HidDeviceManager::OnDeviceRemoved(
+ scoped_refptr<HidDeviceInfo> device_info) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ const auto& resource_entry = resource_ids_.find(device_info->device_id());
+ DCHECK(resource_entry != resource_ids_.end());
+ int resource_id = resource_entry->second;
+ const auto& device_entry = device_ids_.find(resource_id);
+ DCHECK(device_entry != device_ids_.end());
+ resource_ids_.erase(resource_entry);
+ device_ids_.erase(device_entry);
+
+ if (event_router_) {
+ DCHECK(enumeration_ready_);
+ scoped_ptr<base::ListValue> args(hid::OnDeviceRemoved::Create(resource_id));
+ DispatchEvent(events::HID_ON_DEVICE_REMOVED,
+ hid::OnDeviceRemoved::kEventName, std::move(args),
+ device_info);
+ }
+}
+
+void HidDeviceManager::LazyInitialize() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (initialized_) {
+ return;
+ }
+
+ HidService* hid_service = device::DeviceClient::Get()->GetHidService();
+ DCHECK(hid_service);
+ hid_service->GetDevices(base::Bind(&HidDeviceManager::OnEnumerationComplete,
+ weak_factory_.GetWeakPtr()));
+
+ hid_service_observer_.Add(hid_service);
+ initialized_ = true;
+}
+
+scoped_ptr<base::ListValue> HidDeviceManager::CreateApiDeviceList(
+ const Extension* extension,
+ const std::vector<HidDeviceFilter>& filters) {
+ HidService* hid_service = device::DeviceClient::Get()->GetHidService();
+ DCHECK(hid_service);
+
+ scoped_ptr<base::ListValue> api_devices(new base::ListValue());
+ for (const ResourceIdToDeviceIdMap::value_type& map_entry : device_ids_) {
+ int resource_id = map_entry.first;
+ const HidDeviceId& device_id = map_entry.second;
+
+ scoped_refptr<HidDeviceInfo> device_info =
+ hid_service->GetDeviceInfo(device_id);
+ if (!device_info) {
+ continue;
+ }
+
+ if (!filters.empty() &&
+ !HidDeviceFilter::MatchesAny(device_info, filters)) {
+ continue;
+ }
+
+ if (!HasPermission(extension, device_info, false)) {
+ continue;
+ }
+
+ hid::HidDeviceInfo api_device_info;
+ api_device_info.device_id = resource_id;
+ PopulateHidDeviceInfo(&api_device_info, device_info);
+
+ // Expose devices with which user can communicate.
+ if (api_device_info.collections.size() > 0) {
+ api_devices->Append(api_device_info.ToValue());
+ }
+ }
+
+ return api_devices;
+}
+
+void HidDeviceManager::OnEnumerationComplete(
+ const std::vector<scoped_refptr<HidDeviceInfo>>& devices) {
+ DCHECK(resource_ids_.empty());
+ DCHECK(device_ids_.empty());
+ for (const scoped_refptr<HidDeviceInfo>& device_info : devices) {
+ OnDeviceAdded(device_info);
+ }
+ enumeration_ready_ = true;
+
+ for (const auto& params : pending_enumerations_) {
+ scoped_ptr<base::ListValue> devices =
+ CreateApiDeviceList(params->extension, params->filters);
+ params->callback.Run(std::move(devices));
+ }
+ pending_enumerations_.clear();
+}
+
+void HidDeviceManager::DispatchEvent(events::HistogramValue histogram_value,
+ const std::string& event_name,
+ scoped_ptr<base::ListValue> event_args,
+ scoped_refptr<HidDeviceInfo> device_info) {
+ scoped_ptr<Event> event(
+ new Event(histogram_value, event_name, std::move(event_args)));
+ event->will_dispatch_callback = base::Bind(
+ &WillDispatchDeviceEvent, weak_factory_.GetWeakPtr(), device_info);
+ event_router_->BroadcastEvent(std::move(event));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/hid/hid_device_manager.h b/chromium/extensions/browser/api/hid/hid_device_manager.h
new file mode 100644
index 00000000000..0e57e6f68f1
--- /dev/null
+++ b/chromium/extensions/browser/api/hid/hid_device_manager.h
@@ -0,0 +1,133 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_HID_HID_DEVICE_MANAGER_H_
+#define EXTENSIONS_BROWSER_API_HID_HID_DEVICE_MANAGER_H_
+
+#include <map>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/scoped_observer.h"
+#include "base/threading/thread_checker.h"
+#include "device/hid/hid_service.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_event_histogram_value.h"
+#include "extensions/common/api/hid.h"
+
+namespace device {
+class HidDeviceFilter;
+class HidDeviceInfo;
+}
+
+namespace extensions {
+
+class Extension;
+
+// This service maps devices enumerated by device::HidService to resource IDs
+// returned by the chrome.hid API.
+class HidDeviceManager : public BrowserContextKeyedAPI,
+ public device::HidService::Observer,
+ public EventRouter::Observer {
+ public:
+ typedef base::Callback<void(scoped_ptr<base::ListValue>)>
+ GetApiDevicesCallback;
+
+ explicit HidDeviceManager(content::BrowserContext* context);
+ ~HidDeviceManager() override;
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<HidDeviceManager>* GetFactoryInstance();
+
+ // Convenience method to get the HidDeviceManager for a profile.
+ static HidDeviceManager* Get(content::BrowserContext* context) {
+ return BrowserContextKeyedAPIFactory<HidDeviceManager>::Get(context);
+ }
+
+ // Enumerates available devices, taking into account the permissions held by
+ // the given extension and the filters provided. The provided callback will
+ // be posted to the calling thread's task runner with a list of device info
+ // objects.
+ void GetApiDevices(const Extension* extension,
+ const std::vector<device::HidDeviceFilter>& filters,
+ const GetApiDevicesCallback& callback);
+
+ // Converts a list of HidDeviceInfo objects into a value that can be returned
+ // through the API.
+ scoped_ptr<base::ListValue> GetApiDevicesFromList(
+ const std::vector<scoped_refptr<device::HidDeviceInfo>>& devices);
+
+ scoped_refptr<device::HidDeviceInfo> GetDeviceInfo(int resource_id);
+
+ // Checks if |extension| has permission to open |device_info|. Set
+ // |update_last_used| to update the timestamp in the DevicePermissionsManager.
+ bool HasPermission(const Extension* extension,
+ scoped_refptr<device::HidDeviceInfo> device_info,
+ bool update_last_used);
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<HidDeviceManager>;
+
+ typedef std::map<int, device::HidDeviceId> ResourceIdToDeviceIdMap;
+ typedef std::map<device::HidDeviceId, int> DeviceIdToResourceIdMap;
+
+ struct GetApiDevicesParams;
+
+ // KeyedService:
+ void Shutdown() override;
+
+ // BrowserContextKeyedAPI:
+ static const char* service_name() { return "HidDeviceManager"; }
+ static const bool kServiceHasOwnInstanceInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ // EventRouter::Observer:
+ void OnListenerAdded(const EventListenerInfo& details) override;
+
+ // HidService::Observer:
+ void OnDeviceAdded(scoped_refptr<device::HidDeviceInfo> device_info) override;
+ void OnDeviceRemoved(
+ scoped_refptr<device::HidDeviceInfo> device_info) override;
+
+ // Wait to perform an initial enumeration and register a HidService::Observer
+ // until the first API customer makes a request or registers an event
+ // listener.
+ void LazyInitialize();
+
+ // Builds a list of device info objects representing the currently enumerated
+ // devices, taking into account the permissions held by the given extension
+ // and the filters provided.
+ scoped_ptr<base::ListValue> CreateApiDeviceList(
+ const Extension* extension,
+ const std::vector<device::HidDeviceFilter>& filters);
+ void OnEnumerationComplete(
+ const std::vector<scoped_refptr<device::HidDeviceInfo>>& devices);
+
+ void DispatchEvent(events::HistogramValue histogram_value,
+ const std::string& event_name,
+ scoped_ptr<base::ListValue> event_args,
+ scoped_refptr<device::HidDeviceInfo> device_info);
+
+ base::ThreadChecker thread_checker_;
+ content::BrowserContext* browser_context_ = nullptr;
+ EventRouter* event_router_ = nullptr;
+ bool initialized_ = false;
+ ScopedObserver<device::HidService, device::HidService::Observer>
+ hid_service_observer_;
+ bool enumeration_ready_ = false;
+ std::vector<scoped_ptr<GetApiDevicesParams>> pending_enumerations_;
+ int next_resource_id_ = 0;
+ ResourceIdToDeviceIdMap device_ids_;
+ DeviceIdToResourceIdMap resource_ids_;
+ base::WeakPtrFactory<HidDeviceManager> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(HidDeviceManager);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_HID_HID_DEVICE_MANAGER_H_
diff --git a/chromium/extensions/browser/api/idle/OWNERS b/chromium/extensions/browser/api/idle/OWNERS
new file mode 100644
index 00000000000..859c2c608c3
--- /dev/null
+++ b/chromium/extensions/browser/api/idle/OWNERS
@@ -0,0 +1 @@
+dcheng@chromium.org
diff --git a/chromium/extensions/browser/api/idle/idle_api.cc b/chromium/extensions/browser/api/idle/idle_api.cc
new file mode 100644
index 00000000000..17ae5a82e2d
--- /dev/null
+++ b/chromium/extensions/browser/api/idle/idle_api.cc
@@ -0,0 +1,62 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/idle/idle_api.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/values.h"
+#include "extensions/browser/api/idle/idle_api_constants.h"
+#include "extensions/browser/api/idle/idle_manager.h"
+#include "extensions/browser/api/idle/idle_manager_factory.h"
+
+namespace extensions {
+
+namespace {
+
+// In seconds. Set >1 sec for security concerns.
+const int kMinThreshold = 15;
+
+// Four hours, in seconds. Not set arbitrarily high for security concerns.
+const int kMaxThreshold = 4 * 60 * 60;
+
+int ClampThreshold(int threshold) {
+ if (threshold < kMinThreshold) {
+ threshold = kMinThreshold;
+ } else if (threshold > kMaxThreshold) {
+ threshold = kMaxThreshold;
+ }
+
+ return threshold;
+}
+
+} // namespace
+
+ExtensionFunction::ResponseAction IdleQueryStateFunction::Run() {
+ int threshold = 0;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &threshold));
+ threshold = ClampThreshold(threshold);
+
+ IdleManagerFactory::GetForBrowserContext(context_)->QueryState(
+ threshold, base::Bind(&IdleQueryStateFunction::IdleStateCallback, this));
+
+ return RespondLater();
+}
+
+void IdleQueryStateFunction::IdleStateCallback(ui::IdleState state) {
+ Respond(OneArgument(IdleManager::CreateIdleValue(state)));
+}
+
+ExtensionFunction::ResponseAction IdleSetDetectionIntervalFunction::Run() {
+ int threshold = 0;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &threshold));
+ threshold = ClampThreshold(threshold);
+
+ IdleManagerFactory::GetForBrowserContext(context_)
+ ->SetThreshold(extension_id(), threshold);
+
+ return RespondNow(NoArguments());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/idle/idle_api.h b/chromium/extensions/browser/api/idle/idle_api.h
new file mode 100644
index 00000000000..60f13ade5ac
--- /dev/null
+++ b/chromium/extensions/browser/api/idle/idle_api.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_IDLE_IDLE_API_H_
+#define EXTENSIONS_BROWSER_API_IDLE_IDLE_API_H_
+
+#include "extensions/browser/extension_function.h"
+#include "ui/base/idle/idle.h"
+
+namespace extensions {
+
+// Implementation of the chrome.idle.queryState API.
+class IdleQueryStateFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("idle.queryState", IDLE_QUERYSTATE)
+
+ protected:
+ ~IdleQueryStateFunction() override {}
+
+ // UIThreadExtensionFunction:
+ ResponseAction Run() override;
+
+ private:
+ void IdleStateCallback(ui::IdleState state);
+};
+
+// Implementation of the chrome.idle.setDetectionInterval API.
+class IdleSetDetectionIntervalFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("idle.setDetectionInterval",
+ IDLE_SETDETECTIONINTERVAL)
+
+ protected:
+ ~IdleSetDetectionIntervalFunction() override {}
+
+ // UIThreadExtensionFunction:
+ ResponseAction Run() override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_IDLE_IDLE_API_H_
diff --git a/chromium/extensions/browser/api/idle/idle_api_constants.cc b/chromium/extensions/browser/api/idle/idle_api_constants.cc
new file mode 100644
index 00000000000..49ef0950365
--- /dev/null
+++ b/chromium/extensions/browser/api/idle/idle_api_constants.cc
@@ -0,0 +1,15 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/idle/idle_api_constants.h"
+
+namespace extensions {
+namespace idle_api_constants {
+
+const char kStateActive[] = "active";
+const char kStateIdle[] = "idle";
+const char kStateLocked[] = "locked";
+
+} // namespace idle_api_constants
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/idle/idle_api_constants.h b/chromium/extensions/browser/api/idle/idle_api_constants.h
new file mode 100644
index 00000000000..93da12e9b37
--- /dev/null
+++ b/chromium/extensions/browser/api/idle/idle_api_constants.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_IDLE_IDLE_API_CONSTANTS_H_
+#define EXTENSIONS_BROWSER_API_IDLE_IDLE_API_CONSTANTS_H_
+
+namespace extensions {
+namespace idle_api_constants {
+
+// Events.
+extern const char kOnStateChanged[];
+
+// States.
+extern const char kStateActive[];
+extern const char kStateIdle[];
+extern const char kStateLocked[];
+
+} // namespace idle_api_constants
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_IDLE_IDLE_API_CONSTANTS_H_
diff --git a/chromium/extensions/browser/api/idle/idle_api_unittest.cc b/chromium/extensions/browser/api/idle/idle_api_unittest.cc
new file mode 100644
index 00000000000..7e3b5a82356
--- /dev/null
+++ b/chromium/extensions/browser/api/idle/idle_api_unittest.cc
@@ -0,0 +1,556 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/idle/idle_api.h"
+
+#include <limits.h>
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "extensions/browser/api/idle/idle_api_constants.h"
+#include "extensions/browser/api/idle/idle_manager_factory.h"
+#include "extensions/browser/api/idle/idle_manager.h"
+#include "extensions/browser/api_unittest.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/api/idle.h"
+#include "extensions/common/extension.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+
+namespace idle = extensions::api::idle;
+
+namespace extensions {
+
+namespace {
+
+class MockEventDelegate : public IdleManager::EventDelegate {
+ public:
+ MockEventDelegate() {}
+ virtual ~MockEventDelegate() {}
+ MOCK_METHOD2(OnStateChanged, void(const std::string&, ui::IdleState));
+ virtual void RegisterObserver(EventRouter::Observer* observer) {}
+ virtual void UnregisterObserver(EventRouter::Observer* observer) {}
+};
+
+class TestIdleProvider : public IdleManager::IdleTimeProvider {
+ public:
+ TestIdleProvider();
+ ~TestIdleProvider() override;
+ void CalculateIdleState(int idle_threshold, ui::IdleCallback notify) override;
+ void CalculateIdleTime(ui::IdleTimeCallback notify) override;
+ bool CheckIdleStateIsLocked() override;
+
+ void set_idle_time(int idle_time);
+ void set_locked(bool locked);
+
+ private:
+ int idle_time_;
+ bool locked_;
+};
+
+TestIdleProvider::TestIdleProvider() : idle_time_(0), locked_(false) {
+}
+
+TestIdleProvider::~TestIdleProvider() {
+}
+
+void TestIdleProvider::CalculateIdleState(int idle_threshold,
+ ui::IdleCallback notify) {
+ if (locked_) {
+ notify.Run(ui::IDLE_STATE_LOCKED);
+ } else {
+ if (idle_time_ >= idle_threshold) {
+ notify.Run(ui::IDLE_STATE_IDLE);
+ } else {
+ notify.Run(ui::IDLE_STATE_ACTIVE);
+ }
+ }
+}
+
+void TestIdleProvider::CalculateIdleTime(ui::IdleTimeCallback notify) {
+ notify.Run(idle_time_);
+}
+
+bool TestIdleProvider::CheckIdleStateIsLocked() {
+ return locked_;
+}
+
+void TestIdleProvider::set_idle_time(int idle_time) {
+ idle_time_ = idle_time;
+}
+
+void TestIdleProvider::set_locked(bool locked) {
+ locked_ = locked;
+}
+
+class ScopedListen {
+ public:
+ ScopedListen(IdleManager* idle_manager, const std::string& extension_id);
+ ~ScopedListen();
+
+ private:
+ IdleManager* idle_manager_;
+ const std::string extension_id_;
+};
+
+ScopedListen::ScopedListen(IdleManager* idle_manager,
+ const std::string& extension_id)
+ : idle_manager_(idle_manager), extension_id_(extension_id) {
+ const EventListenerInfo details(idle::OnStateChanged::kEventName,
+ extension_id_, GURL(), NULL);
+ idle_manager_->OnListenerAdded(details);
+}
+
+ScopedListen::~ScopedListen() {
+ const EventListenerInfo details(idle::OnStateChanged::kEventName,
+ extension_id_, GURL(), NULL);
+ idle_manager_->OnListenerRemoved(details);
+}
+
+scoped_ptr<KeyedService> IdleManagerTestFactory(
+ content::BrowserContext* context) {
+ return make_scoped_ptr(new IdleManager(context));
+}
+
+} // namespace
+
+class IdleTest : public ApiUnitTest {
+ public:
+ void SetUp() override;
+
+ protected:
+ IdleManager* idle_manager_;
+ TestIdleProvider* idle_provider_;
+ testing::StrictMock<MockEventDelegate>* event_delegate_;
+};
+
+void IdleTest::SetUp() {
+ ApiUnitTest::SetUp();
+
+ IdleManagerFactory::GetInstance()->SetTestingFactory(browser_context(),
+ &IdleManagerTestFactory);
+ idle_manager_ = IdleManagerFactory::GetForBrowserContext(browser_context());
+
+ idle_provider_ = new TestIdleProvider();
+ idle_manager_->SetIdleTimeProviderForTest(
+ scoped_ptr<IdleManager::IdleTimeProvider>(idle_provider_));
+ event_delegate_ = new testing::StrictMock<MockEventDelegate>();
+ idle_manager_->SetEventDelegateForTest(
+ scoped_ptr<IdleManager::EventDelegate>(event_delegate_));
+ idle_manager_->Init();
+}
+
+// Verifies that "locked" takes priority over "active".
+TEST_F(IdleTest, QueryLockedActive) {
+ idle_provider_->set_locked(true);
+ idle_provider_->set_idle_time(0);
+
+ scoped_ptr<base::Value> result(
+ RunFunctionAndReturnValue(new IdleQueryStateFunction(), "[60]"));
+
+ std::string idle_state;
+ ASSERT_TRUE(result->GetAsString(&idle_state));
+ EXPECT_EQ("locked", idle_state);
+}
+
+// Verifies that "locked" takes priority over "idle".
+TEST_F(IdleTest, QueryLockedIdle) {
+ idle_provider_->set_locked(true);
+ idle_provider_->set_idle_time(INT_MAX);
+
+ scoped_ptr<base::Value> result(
+ RunFunctionAndReturnValue(new IdleQueryStateFunction(), "[60]"));
+
+ std::string idle_state;
+ ASSERT_TRUE(result->GetAsString(&idle_state));
+ EXPECT_EQ("locked", idle_state);
+}
+
+// Verifies that any amount of idle time less than the detection interval
+// translates to a state of "active".
+TEST_F(IdleTest, QueryActive) {
+ idle_provider_->set_locked(false);
+
+ for (int time = 0; time < 60; ++time) {
+ SCOPED_TRACE(time);
+ idle_provider_->set_idle_time(time);
+
+ scoped_ptr<base::Value> result(
+ RunFunctionAndReturnValue(new IdleQueryStateFunction(), "[60]"));
+
+ std::string idle_state;
+ ASSERT_TRUE(result->GetAsString(&idle_state));
+ EXPECT_EQ("active", idle_state);
+ }
+}
+
+// Verifies that an idle time >= the detection interval returns the "idle"
+// state.
+TEST_F(IdleTest, QueryIdle) {
+ idle_provider_->set_locked(false);
+
+ for (int time = 80; time >= 60; --time) {
+ SCOPED_TRACE(time);
+ idle_provider_->set_idle_time(time);
+
+ scoped_ptr<base::Value> result(
+ RunFunctionAndReturnValue(new IdleQueryStateFunction(), "[60]"));
+
+ std::string idle_state;
+ ASSERT_TRUE(result->GetAsString(&idle_state));
+ EXPECT_EQ("idle", idle_state);
+ }
+}
+
+// Verifies that requesting a detection interval < 15 has the same effect as
+// passing in 15.
+TEST_F(IdleTest, QueryMinThreshold) {
+ idle_provider_->set_locked(false);
+
+ for (int threshold = 0; threshold < 20; ++threshold) {
+ for (int time = 10; time < 60; ++time) {
+ SCOPED_TRACE(threshold);
+ SCOPED_TRACE(time);
+ idle_provider_->set_idle_time(time);
+
+ std::string args = "[" + base::IntToString(threshold) + "]";
+ scoped_ptr<base::Value> result(
+ RunFunctionAndReturnValue(new IdleQueryStateFunction(), args));
+
+ std::string idle_state;
+ ASSERT_TRUE(result->GetAsString(&idle_state));
+
+ int real_threshold = (threshold < 15) ? 15 : threshold;
+ const char* expected = (time < real_threshold) ? "active" : "idle";
+ EXPECT_EQ(expected, idle_state);
+ }
+ }
+}
+
+// Verifies that passing in a detection interval > 4 hours has the same effect
+// as passing in 4 hours.
+TEST_F(IdleTest, QueryMaxThreshold) {
+ idle_provider_->set_locked(false);
+
+ const int kFourHoursInSeconds = 4 * 60 * 60;
+
+ for (int threshold = kFourHoursInSeconds - 20;
+ threshold < (kFourHoursInSeconds + 20); ++threshold) {
+ for (int time = kFourHoursInSeconds - 30; time < kFourHoursInSeconds + 30;
+ ++time) {
+ SCOPED_TRACE(threshold);
+ SCOPED_TRACE(time);
+ idle_provider_->set_idle_time(time);
+
+ std::string args = "[" + base::IntToString(threshold) + "]";
+ scoped_ptr<base::Value> result(
+ RunFunctionAndReturnValue(new IdleQueryStateFunction(), args));
+
+ std::string idle_state;
+ ASSERT_TRUE(result->GetAsString(&idle_state));
+
+ int real_threshold =
+ (threshold > kFourHoursInSeconds) ? kFourHoursInSeconds : threshold;
+ const char* expected = (time < real_threshold) ? "active" : "idle";
+ EXPECT_EQ(expected, idle_state);
+ }
+ }
+}
+
+// Verifies that transitioning from an active to idle state fires an "idle"
+// OnStateChanged event.
+TEST_F(IdleTest, ActiveToIdle) {
+ ScopedListen listen_test(idle_manager_, "test");
+
+ idle_provider_->set_locked(false);
+
+ for (int time = 0; time < 60; ++time) {
+ SCOPED_TRACE(time);
+ idle_provider_->set_idle_time(time);
+
+ idle_manager_->UpdateIdleState();
+ }
+
+ idle_provider_->set_idle_time(60);
+
+ EXPECT_CALL(*event_delegate_, OnStateChanged("test", ui::IDLE_STATE_IDLE));
+ idle_manager_->UpdateIdleState();
+ testing::Mock::VerifyAndClearExpectations(event_delegate_);
+
+ for (int time = 61; time < 75; ++time) {
+ SCOPED_TRACE(time);
+ idle_provider_->set_idle_time(time);
+ idle_manager_->UpdateIdleState();
+ }
+}
+
+// Verifies that locking an active system generates a "locked" event.
+TEST_F(IdleTest, ActiveToLocked) {
+ ScopedListen listen_test(idle_manager_, "test");
+
+ idle_provider_->set_locked(true);
+ idle_provider_->set_idle_time(5);
+
+ EXPECT_CALL(*event_delegate_, OnStateChanged("test", ui::IDLE_STATE_LOCKED));
+ idle_manager_->UpdateIdleState();
+}
+
+// Verifies that transitioning from an idle to active state generates an
+// "active" event.
+TEST_F(IdleTest, IdleToActive) {
+ ScopedListen listen_test(idle_manager_, "test");
+
+ idle_provider_->set_locked(false);
+ idle_provider_->set_idle_time(75);
+ EXPECT_CALL(*event_delegate_, OnStateChanged("test", ui::IDLE_STATE_IDLE));
+ idle_manager_->UpdateIdleState();
+ testing::Mock::VerifyAndClearExpectations(event_delegate_);
+
+ idle_provider_->set_idle_time(0);
+ EXPECT_CALL(*event_delegate_, OnStateChanged("test", ui::IDLE_STATE_ACTIVE));
+ idle_manager_->UpdateIdleState();
+}
+
+// Verifies that locking an idle system generates a "locked" event.
+TEST_F(IdleTest, IdleToLocked) {
+ ScopedListen listen_test(idle_manager_, "test");
+
+ idle_provider_->set_locked(false);
+ idle_provider_->set_idle_time(75);
+
+ EXPECT_CALL(*event_delegate_, OnStateChanged("test", ui::IDLE_STATE_IDLE));
+ idle_manager_->UpdateIdleState();
+ testing::Mock::VerifyAndClearExpectations(event_delegate_);
+
+ idle_provider_->set_locked(true);
+ EXPECT_CALL(*event_delegate_, OnStateChanged("test", ui::IDLE_STATE_LOCKED));
+ idle_manager_->UpdateIdleState();
+}
+
+// Verifies that unlocking an active system generates an "active" event.
+TEST_F(IdleTest, LockedToActive) {
+ ScopedListen listen_test(idle_manager_, "test");
+
+ idle_provider_->set_locked(true);
+ idle_provider_->set_idle_time(0);
+
+ EXPECT_CALL(*event_delegate_, OnStateChanged("test", ui::IDLE_STATE_LOCKED));
+ idle_manager_->UpdateIdleState();
+
+ idle_provider_->set_locked(false);
+ idle_provider_->set_idle_time(5);
+ EXPECT_CALL(*event_delegate_, OnStateChanged("test", ui::IDLE_STATE_ACTIVE));
+ idle_manager_->UpdateIdleState();
+}
+
+// Verifies that unlocking an inactive system generates an "idle" event.
+TEST_F(IdleTest, LockedToIdle) {
+ ScopedListen listen_test(idle_manager_, "test");
+
+ idle_provider_->set_locked(true);
+ idle_provider_->set_idle_time(75);
+ EXPECT_CALL(*event_delegate_, OnStateChanged("test", ui::IDLE_STATE_LOCKED));
+ idle_manager_->UpdateIdleState();
+ testing::Mock::VerifyAndClearExpectations(event_delegate_);
+
+ idle_provider_->set_locked(false);
+ EXPECT_CALL(*event_delegate_, OnStateChanged("test", ui::IDLE_STATE_IDLE));
+ idle_manager_->UpdateIdleState();
+}
+
+// Verifies that events are routed to extensions that have one or more listeners
+// in scope.
+TEST_F(IdleTest, MultipleExtensions) {
+ ScopedListen listen_1(idle_manager_, "1");
+ ScopedListen listen_2(idle_manager_, "2");
+
+ idle_provider_->set_locked(true);
+ EXPECT_CALL(*event_delegate_, OnStateChanged("1", ui::IDLE_STATE_LOCKED));
+ EXPECT_CALL(*event_delegate_, OnStateChanged("2", ui::IDLE_STATE_LOCKED));
+ idle_manager_->UpdateIdleState();
+ testing::Mock::VerifyAndClearExpectations(event_delegate_);
+
+ {
+ ScopedListen listen_2prime(idle_manager_, "2");
+ ScopedListen listen_3(idle_manager_, "3");
+ idle_provider_->set_locked(false);
+ EXPECT_CALL(*event_delegate_, OnStateChanged("1", ui::IDLE_STATE_ACTIVE));
+ EXPECT_CALL(*event_delegate_, OnStateChanged("2", ui::IDLE_STATE_ACTIVE));
+ EXPECT_CALL(*event_delegate_, OnStateChanged("3", ui::IDLE_STATE_ACTIVE));
+ idle_manager_->UpdateIdleState();
+ testing::Mock::VerifyAndClearExpectations(event_delegate_);
+ }
+
+ idle_provider_->set_locked(true);
+ EXPECT_CALL(*event_delegate_, OnStateChanged("1", ui::IDLE_STATE_LOCKED));
+ EXPECT_CALL(*event_delegate_, OnStateChanged("2", ui::IDLE_STATE_LOCKED));
+ idle_manager_->UpdateIdleState();
+}
+
+// Verifies that setDetectionInterval changes the detection interval from the
+// default of 60 seconds, and that the call only affects a single extension's
+// IdleMonitor.
+TEST_F(IdleTest, SetDetectionInterval) {
+ ScopedListen listen_default(idle_manager_, "default");
+ ScopedListen listen_extension(idle_manager_, extension()->id());
+
+ scoped_ptr<base::Value> result45(RunFunctionAndReturnValue(
+ new IdleSetDetectionIntervalFunction(), "[45]"));
+
+ idle_provider_->set_locked(false);
+ idle_provider_->set_idle_time(44);
+ idle_manager_->UpdateIdleState();
+
+ idle_provider_->set_idle_time(45);
+ EXPECT_CALL(*event_delegate_,
+ OnStateChanged(extension()->id(), ui::IDLE_STATE_IDLE));
+ idle_manager_->UpdateIdleState();
+ // Verify that the expectation has been fulfilled before incrementing the
+ // time again.
+ testing::Mock::VerifyAndClearExpectations(event_delegate_);
+
+ idle_provider_->set_idle_time(60);
+ EXPECT_CALL(*event_delegate_, OnStateChanged("default", ui::IDLE_STATE_IDLE));
+ idle_manager_->UpdateIdleState();
+}
+
+// Verifies that setting the detection interval before creating the listener
+// works correctly.
+TEST_F(IdleTest, SetDetectionIntervalBeforeListener) {
+ scoped_ptr<base::Value> result45(RunFunctionAndReturnValue(
+ new IdleSetDetectionIntervalFunction(), "[45]"));
+
+ ScopedListen listen_extension(idle_manager_, extension()->id());
+
+ idle_provider_->set_locked(false);
+ idle_provider_->set_idle_time(44);
+ idle_manager_->UpdateIdleState();
+
+ idle_provider_->set_idle_time(45);
+ EXPECT_CALL(*event_delegate_,
+ OnStateChanged(extension()->id(), ui::IDLE_STATE_IDLE));
+ idle_manager_->UpdateIdleState();
+}
+
+// Verifies that setting a detection interval above the maximum value results
+// in an interval of 4 hours.
+TEST_F(IdleTest, SetDetectionIntervalMaximum) {
+ ScopedListen listen_extension(idle_manager_, extension()->id());
+
+ scoped_ptr<base::Value> result(
+ RunFunctionAndReturnValue(new IdleSetDetectionIntervalFunction(),
+ "[18000]")); // five hours in seconds
+
+ idle_provider_->set_locked(false);
+ idle_provider_->set_idle_time(4 * 60 * 60 - 1);
+ idle_manager_->UpdateIdleState();
+
+ idle_provider_->set_idle_time(4 * 60 * 60);
+ EXPECT_CALL(*event_delegate_,
+ OnStateChanged(extension()->id(), ui::IDLE_STATE_IDLE));
+ idle_manager_->UpdateIdleState();
+}
+
+// Verifies that setting a detection interval below the minimum value results
+// in an interval of 15 seconds.
+TEST_F(IdleTest, SetDetectionIntervalMinimum) {
+ ScopedListen listen_extension(idle_manager_, extension()->id());
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnValue(
+ new IdleSetDetectionIntervalFunction(), "[10]"));
+
+ idle_provider_->set_locked(false);
+ idle_provider_->set_idle_time(14);
+ idle_manager_->UpdateIdleState();
+
+ idle_provider_->set_idle_time(15);
+ EXPECT_CALL(*event_delegate_,
+ OnStateChanged(extension()->id(), ui::IDLE_STATE_IDLE));
+ idle_manager_->UpdateIdleState();
+}
+
+// Verifies that an extension's detection interval is discarded when it unloads.
+TEST_F(IdleTest, UnloadCleanup) {
+ {
+ ScopedListen listen(idle_manager_, extension()->id());
+
+ scoped_ptr<base::Value> result45(RunFunctionAndReturnValue(
+ new IdleSetDetectionIntervalFunction(), "[15]"));
+ }
+
+ // Listener count dropping to zero does not reset threshold.
+
+ {
+ ScopedListen listen(idle_manager_, extension()->id());
+ idle_provider_->set_idle_time(16);
+ EXPECT_CALL(*event_delegate_,
+ OnStateChanged(extension()->id(), ui::IDLE_STATE_IDLE));
+ idle_manager_->UpdateIdleState();
+ testing::Mock::VerifyAndClearExpectations(event_delegate_);
+ }
+
+ // Threshold will reset after unload (and listen count == 0)
+ ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
+ registry->TriggerOnUnloaded(extension(),
+ UnloadedExtensionInfo::REASON_UNINSTALL);
+
+ {
+ ScopedListen listen(idle_manager_, extension()->id());
+ idle_manager_->UpdateIdleState();
+ testing::Mock::VerifyAndClearExpectations(event_delegate_);
+
+ idle_provider_->set_idle_time(61);
+ EXPECT_CALL(*event_delegate_,
+ OnStateChanged(extension()->id(), ui::IDLE_STATE_IDLE));
+ idle_manager_->UpdateIdleState();
+ }
+}
+
+// Verifies that unloading an extension with no listeners or threshold works.
+TEST_F(IdleTest, UnloadOnly) {
+ ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
+ registry->TriggerOnUnloaded(extension(),
+ UnloadedExtensionInfo::REASON_UNINSTALL);
+}
+
+// Verifies that its ok for the unload notification to happen before all the
+// listener removals.
+TEST_F(IdleTest, UnloadWhileListening) {
+ ScopedListen listen(idle_manager_, extension()->id());
+ ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
+ registry->TriggerOnUnloaded(extension(),
+ UnloadedExtensionInfo::REASON_UNINSTALL);
+}
+
+// Verifies that re-adding a listener after a state change doesn't immediately
+// fire a change event. Regression test for http://crbug.com/366580.
+TEST_F(IdleTest, ReAddListener) {
+ idle_provider_->set_locked(false);
+
+ {
+ // Fire idle event.
+ ScopedListen listen(idle_manager_, "test");
+ idle_provider_->set_idle_time(60);
+ EXPECT_CALL(*event_delegate_, OnStateChanged("test", ui::IDLE_STATE_IDLE));
+ idle_manager_->UpdateIdleState();
+ testing::Mock::VerifyAndClearExpectations(event_delegate_);
+ }
+
+ // Trigger active.
+ idle_provider_->set_idle_time(0);
+ idle_manager_->UpdateIdleState();
+
+ {
+ // Nothing should have fired, the listener wasn't added until afterward.
+ ScopedListen listen(idle_manager_, "test");
+ idle_manager_->UpdateIdleState();
+ testing::Mock::VerifyAndClearExpectations(event_delegate_);
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/idle/idle_manager.cc b/chromium/extensions/browser/api/idle/idle_manager.cc
new file mode 100644
index 00000000000..695e52f2766
--- /dev/null
+++ b/chromium/extensions/browser/api/idle/idle_manager.cc
@@ -0,0 +1,263 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/idle/idle_manager.h"
+
+#include <utility>
+
+#include "base/stl_util.h"
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/api/idle/idle_api_constants.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/api/idle.h"
+#include "extensions/common/extension.h"
+
+namespace keys = extensions::idle_api_constants;
+namespace idle = extensions::api::idle;
+
+namespace extensions {
+
+namespace {
+
+const int kDefaultIdleThreshold = 60;
+const int kPollInterval = 1;
+
+class DefaultEventDelegate : public IdleManager::EventDelegate {
+ public:
+ explicit DefaultEventDelegate(content::BrowserContext* context);
+ ~DefaultEventDelegate() override;
+
+ void OnStateChanged(const std::string& extension_id,
+ ui::IdleState new_state) override;
+ void RegisterObserver(EventRouter::Observer* observer) override;
+ void UnregisterObserver(EventRouter::Observer* observer) override;
+
+ private:
+ content::BrowserContext* const context_;
+};
+
+DefaultEventDelegate::DefaultEventDelegate(content::BrowserContext* context)
+ : context_(context) {
+}
+
+DefaultEventDelegate::~DefaultEventDelegate() {
+}
+
+void DefaultEventDelegate::OnStateChanged(const std::string& extension_id,
+ ui::IdleState new_state) {
+ scoped_ptr<base::ListValue> args(new base::ListValue());
+ args->Append(IdleManager::CreateIdleValue(new_state));
+ scoped_ptr<Event> event(new Event(events::IDLE_ON_STATE_CHANGED,
+ idle::OnStateChanged::kEventName,
+ std::move(args)));
+ event->restrict_to_browser_context = context_;
+ EventRouter::Get(context_)
+ ->DispatchEventToExtension(extension_id, std::move(event));
+}
+
+void DefaultEventDelegate::RegisterObserver(EventRouter::Observer* observer) {
+ EventRouter::Get(context_)
+ ->RegisterObserver(observer, idle::OnStateChanged::kEventName);
+}
+
+void DefaultEventDelegate::UnregisterObserver(EventRouter::Observer* observer) {
+ EventRouter::Get(context_)->UnregisterObserver(observer);
+}
+
+class DefaultIdleProvider : public IdleManager::IdleTimeProvider {
+ public:
+ DefaultIdleProvider();
+ ~DefaultIdleProvider() override;
+
+ void CalculateIdleState(int idle_threshold, ui::IdleCallback notify) override;
+ void CalculateIdleTime(ui::IdleTimeCallback notify) override;
+ bool CheckIdleStateIsLocked() override;
+};
+
+DefaultIdleProvider::DefaultIdleProvider() {
+}
+
+DefaultIdleProvider::~DefaultIdleProvider() {
+}
+
+void DefaultIdleProvider::CalculateIdleState(int idle_threshold,
+ ui::IdleCallback notify) {
+ ui::CalculateIdleState(idle_threshold, notify);
+}
+
+void DefaultIdleProvider::CalculateIdleTime(ui::IdleTimeCallback notify) {
+ ui::CalculateIdleTime(notify);
+}
+
+bool DefaultIdleProvider::CheckIdleStateIsLocked() {
+ return ui::CheckIdleStateIsLocked();
+}
+
+ui::IdleState IdleTimeToIdleState(bool locked,
+ int idle_time,
+ int idle_threshold) {
+ ui::IdleState state;
+
+ if (locked) {
+ state = ui::IDLE_STATE_LOCKED;
+ } else if (idle_time >= idle_threshold) {
+ state = ui::IDLE_STATE_IDLE;
+ } else {
+ state = ui::IDLE_STATE_ACTIVE;
+ }
+ return state;
+}
+
+} // namespace
+
+IdleMonitor::IdleMonitor(ui::IdleState initial_state)
+ : last_state(initial_state),
+ listeners(0),
+ threshold(kDefaultIdleThreshold) {
+}
+
+IdleManager::IdleManager(content::BrowserContext* context)
+ : context_(context),
+ last_state_(ui::IDLE_STATE_ACTIVE),
+ idle_time_provider_(new DefaultIdleProvider()),
+ event_delegate_(new DefaultEventDelegate(context)),
+ extension_registry_observer_(this),
+ weak_factory_(this) {
+}
+
+IdleManager::~IdleManager() {
+}
+
+void IdleManager::Init() {
+ extension_registry_observer_.Add(ExtensionRegistry::Get(context_));
+ event_delegate_->RegisterObserver(this);
+}
+
+void IdleManager::Shutdown() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ event_delegate_->UnregisterObserver(this);
+}
+
+void IdleManager::OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ monitors_.erase(extension->id());
+}
+
+void IdleManager::OnListenerAdded(const EventListenerInfo& details) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ ++GetMonitor(details.extension_id)->listeners;
+ StartPolling();
+}
+
+void IdleManager::OnListenerRemoved(const EventListenerInfo& details) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // During unload the monitor could already have been deleted. No need to do
+ // anything in that case.
+ MonitorMap::iterator it = monitors_.find(details.extension_id);
+ if (it != monitors_.end()) {
+ DCHECK_GT(it->second.listeners, 0);
+ // Note: Deliberately leave the listener count as 0 rather than erase()ing
+ // this record so that the threshold doesn't get reset when all listeners
+ // are removed.
+ --it->second.listeners;
+ }
+}
+
+void IdleManager::QueryState(int threshold, QueryStateCallback notify) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ idle_time_provider_->CalculateIdleState(threshold, notify);
+}
+
+void IdleManager::SetThreshold(const std::string& extension_id, int threshold) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ GetMonitor(extension_id)->threshold = threshold;
+}
+
+// static
+base::StringValue* IdleManager::CreateIdleValue(ui::IdleState idle_state) {
+ const char* description;
+
+ if (idle_state == ui::IDLE_STATE_ACTIVE) {
+ description = keys::kStateActive;
+ } else if (idle_state == ui::IDLE_STATE_IDLE) {
+ description = keys::kStateIdle;
+ } else {
+ description = keys::kStateLocked;
+ }
+
+ return new base::StringValue(description);
+}
+
+void IdleManager::SetEventDelegateForTest(
+ scoped_ptr<EventDelegate> event_delegate) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ event_delegate_ = std::move(event_delegate);
+}
+
+void IdleManager::SetIdleTimeProviderForTest(
+ scoped_ptr<IdleTimeProvider> idle_time_provider) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ idle_time_provider_ = std::move(idle_time_provider);
+}
+
+IdleMonitor* IdleManager::GetMonitor(const std::string& extension_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ MonitorMap::iterator it = monitors_.find(extension_id);
+
+ if (it == monitors_.end()) {
+ it = monitors_.insert(std::make_pair(extension_id,
+ IdleMonitor(last_state_))).first;
+ }
+ return &it->second;
+}
+
+void IdleManager::StartPolling() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!poll_timer_.IsRunning()) {
+ poll_timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(kPollInterval),
+ this, &IdleManager::UpdateIdleState);
+ }
+}
+
+void IdleManager::StopPolling() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ poll_timer_.Stop();
+}
+
+void IdleManager::UpdateIdleState() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ idle_time_provider_->CalculateIdleTime(base::Bind(
+ &IdleManager::UpdateIdleStateCallback, weak_factory_.GetWeakPtr()));
+}
+
+void IdleManager::UpdateIdleStateCallback(int idle_time) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ bool locked = idle_time_provider_->CheckIdleStateIsLocked();
+ int listener_count = 0;
+
+ // Remember this state for initializing new event listeners.
+ last_state_ = IdleTimeToIdleState(locked, idle_time, kDefaultIdleThreshold);
+
+ for (MonitorMap::iterator it = monitors_.begin(); it != monitors_.end();
+ ++it) {
+ IdleMonitor& monitor = it->second;
+ ui::IdleState new_state =
+ IdleTimeToIdleState(locked, idle_time, monitor.threshold);
+ // TODO(kalman): Use EventRouter::HasListeners for these sorts of checks.
+ if (monitor.listeners > 0 && monitor.last_state != new_state)
+ event_delegate_->OnStateChanged(it->first, new_state);
+ monitor.last_state = new_state;
+ listener_count += monitor.listeners;
+ }
+
+ if (listener_count == 0)
+ StopPolling();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/idle/idle_manager.h b/chromium/extensions/browser/api/idle/idle_manager.h
new file mode 100644
index 00000000000..9394c7a753a
--- /dev/null
+++ b/chromium/extensions/browser/api/idle/idle_manager.h
@@ -0,0 +1,148 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_IDLE_IDLE_MANAGER_H_
+#define EXTENSIONS_BROWSER_API_IDLE_IDLE_MANAGER_H_
+
+#include <map>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/scoped_observer.h"
+#include "base/threading/thread_checker.h"
+#include "base/timer/timer.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "ui/base/idle/idle.h"
+
+namespace base {
+class StringValue;
+} // namespace base
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+namespace extensions {
+class ExtensionRegistry;
+
+typedef base::Callback<void(ui::IdleState)> QueryStateCallback;
+
+struct IdleMonitor {
+ explicit IdleMonitor(ui::IdleState initial_state);
+
+ ui::IdleState last_state;
+ int listeners;
+ int threshold;
+};
+
+class IdleManager : public ExtensionRegistryObserver,
+ public EventRouter::Observer,
+ public KeyedService {
+ public:
+ class IdleTimeProvider {
+ public:
+ IdleTimeProvider() {}
+ virtual ~IdleTimeProvider() {}
+ virtual void CalculateIdleState(int idle_threshold,
+ ui::IdleCallback notify) = 0;
+ virtual void CalculateIdleTime(ui::IdleTimeCallback notify) = 0;
+ virtual bool CheckIdleStateIsLocked() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(IdleTimeProvider);
+ };
+
+ class EventDelegate {
+ public:
+ EventDelegate() {}
+ virtual ~EventDelegate() {}
+ virtual void OnStateChanged(const std::string& extension_id,
+ ui::IdleState new_state) = 0;
+ virtual void RegisterObserver(EventRouter::Observer* observer) = 0;
+ virtual void UnregisterObserver(EventRouter::Observer* observer) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EventDelegate);
+ };
+
+ explicit IdleManager(content::BrowserContext* context);
+ ~IdleManager() override;
+
+ void Init();
+
+ // KeyedService implementation.
+ void Shutdown() override;
+
+ // ExtensionRegistryObserver implementation.
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+
+ // EventRouter::Observer implementation.
+ void OnListenerAdded(const EventListenerInfo& details) override;
+ void OnListenerRemoved(const EventListenerInfo& details) override;
+
+ void QueryState(int threshold, QueryStateCallback notify);
+ void SetThreshold(const std::string& extension_id, int threshold);
+ static base::StringValue* CreateIdleValue(ui::IdleState idle_state);
+
+ // Override default event class. Callee assumes ownership. Used for testing.
+ void SetEventDelegateForTest(scoped_ptr<EventDelegate> event_delegate);
+
+ // Override default idle time calculations. Callee assumes ownership. Used
+ // for testing.
+ void SetIdleTimeProviderForTest(scoped_ptr<IdleTimeProvider> idle_provider);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(IdleTest, ActiveToIdle);
+ FRIEND_TEST_ALL_PREFIXES(IdleTest, ActiveToLocked);
+ FRIEND_TEST_ALL_PREFIXES(IdleTest, IdleToActive);
+ FRIEND_TEST_ALL_PREFIXES(IdleTest, IdleToLocked);
+ FRIEND_TEST_ALL_PREFIXES(IdleTest, LockedToActive);
+ FRIEND_TEST_ALL_PREFIXES(IdleTest, LockedToIdle);
+ FRIEND_TEST_ALL_PREFIXES(IdleTest, MultipleExtensions);
+ FRIEND_TEST_ALL_PREFIXES(IdleTest, ReAddListener);
+ FRIEND_TEST_ALL_PREFIXES(IdleTest, SetDetectionInterval);
+ FRIEND_TEST_ALL_PREFIXES(IdleTest, SetDetectionIntervalBeforeListener);
+ FRIEND_TEST_ALL_PREFIXES(IdleTest, SetDetectionIntervalMaximum);
+ FRIEND_TEST_ALL_PREFIXES(IdleTest, SetDetectionIntervalMinimum);
+ FRIEND_TEST_ALL_PREFIXES(IdleTest, UnloadCleanup);
+
+ typedef std::map<const std::string, IdleMonitor> MonitorMap;
+
+ IdleMonitor* GetMonitor(const std::string& extension_id);
+ void StartPolling();
+ void StopPolling();
+ void UpdateIdleState();
+ void UpdateIdleStateCallback(int idle_time);
+
+ content::BrowserContext* const context_;
+
+ ui::IdleState last_state_;
+ MonitorMap monitors_;
+
+ base::RepeatingTimer poll_timer_;
+
+ scoped_ptr<IdleTimeProvider> idle_time_provider_;
+ scoped_ptr<EventDelegate> event_delegate_;
+
+ base::ThreadChecker thread_checker_;
+
+ // Listen to extension unloaded notification.
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+
+ base::WeakPtrFactory<IdleManager> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(IdleManager);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_IDLE_IDLE_MANAGER_H_
diff --git a/chromium/extensions/browser/api/idle/idle_manager_factory.cc b/chromium/extensions/browser/api/idle/idle_manager_factory.cc
new file mode 100644
index 00000000000..71f6214f0fe
--- /dev/null
+++ b/chromium/extensions/browser/api/idle/idle_manager_factory.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/idle/idle_manager_factory.h"
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "extensions/browser/api/idle/idle_manager.h"
+#include "extensions/browser/extension_system_provider.h"
+#include "extensions/browser/extensions_browser_client.h"
+
+namespace extensions {
+
+// static
+IdleManager* IdleManagerFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<IdleManager*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+IdleManagerFactory* IdleManagerFactory::GetInstance() {
+ return base::Singleton<IdleManagerFactory>::get();
+}
+
+IdleManagerFactory::IdleManagerFactory()
+ : BrowserContextKeyedServiceFactory(
+ "IdleManager",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
+}
+
+IdleManagerFactory::~IdleManagerFactory() {
+}
+
+KeyedService* IdleManagerFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ IdleManager* idle_manager = new IdleManager(context);
+ idle_manager->Init();
+ return idle_manager;
+}
+
+content::BrowserContext* IdleManagerFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+}
+
+bool IdleManagerFactory::ServiceIsCreatedWithBrowserContext() const {
+ return true;
+}
+
+bool IdleManagerFactory::ServiceIsNULLWhileTesting() const {
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/idle/idle_manager_factory.h b/chromium/extensions/browser/api/idle/idle_manager_factory.h
new file mode 100644
index 00000000000..c5f8a9bf7af
--- /dev/null
+++ b/chromium/extensions/browser/api/idle/idle_manager_factory.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_IDLE_IDLE_MANAGER_FACTORY_H__
+#define EXTENSIONS_BROWSER_API_IDLE_IDLE_MANAGER_FACTORY_H__
+
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+namespace extensions {
+class IdleManager;
+
+class IdleManagerFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static IdleManager* GetForBrowserContext(content::BrowserContext* context);
+
+ static IdleManagerFactory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<IdleManagerFactory>;
+
+ IdleManagerFactory();
+ ~IdleManagerFactory() override;
+
+ // BrowserContextKeyedBaseFactory implementation.
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+ bool ServiceIsCreatedWithBrowserContext() const override;
+ bool ServiceIsNULLWhileTesting() const override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_IDLE_IDLE_MANAGER_FACTORY_H__
diff --git a/chromium/extensions/browser/api/management/management_api.cc b/chromium/extensions/browser/api/management/management_api.cc
new file mode 100644
index 00000000000..984f41c7380
--- /dev/null
+++ b/chromium/extensions/browser/api/management/management_api.cc
@@ -0,0 +1,908 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/management/management_api.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/json/json_writer.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/api/management/management_api_constants.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/management_policy.h"
+#include "extensions/browser/requirements_checker.h"
+#include "extensions/browser/uninstall_reason.h"
+#include "extensions/common/api/management.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_icon_set.h"
+#include "extensions/common/manifest_handlers/icons_handler.h"
+#include "extensions/common/manifest_handlers/offline_enabled_info.h"
+#include "extensions/common/manifest_handlers/options_page_info.h"
+#include "extensions/common/manifest_url_handlers.h"
+#include "extensions/common/permissions/permission_message.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/url_pattern.h"
+
+using base::IntToString;
+using content::BrowserThread;
+
+namespace keys = extension_management_api_constants;
+
+namespace extensions {
+
+namespace management = api::management;
+
+namespace {
+
+typedef std::vector<management::ExtensionInfo> ExtensionInfoList;
+typedef std::vector<management::IconInfo> IconInfoList;
+
+enum AutoConfirmForTest { DO_NOT_SKIP = 0, PROCEED, ABORT };
+
+AutoConfirmForTest auto_confirm_for_test = DO_NOT_SKIP;
+
+std::vector<std::string> CreateWarningsList(const Extension* extension) {
+ std::vector<std::string> warnings_list;
+ for (const PermissionMessage& msg :
+ extension->permissions_data()->GetPermissionMessages()) {
+ warnings_list.push_back(base::UTF16ToUTF8(msg.message()));
+ }
+
+ return warnings_list;
+}
+
+std::vector<management::LaunchType> GetAvailableLaunchTypes(
+ const Extension& extension,
+ const ManagementAPIDelegate* delegate) {
+ std::vector<management::LaunchType> launch_type_list;
+ if (extension.is_platform_app()) {
+ launch_type_list.push_back(management::LAUNCH_TYPE_OPEN_AS_WINDOW);
+ return launch_type_list;
+ }
+
+ launch_type_list.push_back(management::LAUNCH_TYPE_OPEN_AS_REGULAR_TAB);
+
+ // TODO(dominickn): remove check when hosted apps can open in windows on Mac.
+ if (delegate->CanHostedAppsOpenInWindows())
+ launch_type_list.push_back(management::LAUNCH_TYPE_OPEN_AS_WINDOW);
+
+ if (!delegate->IsNewBookmarkAppsEnabled()) {
+ launch_type_list.push_back(management::LAUNCH_TYPE_OPEN_AS_PINNED_TAB);
+ launch_type_list.push_back(management::LAUNCH_TYPE_OPEN_FULL_SCREEN);
+ }
+ return launch_type_list;
+}
+
+management::ExtensionInfo CreateExtensionInfo(
+ const Extension& extension,
+ content::BrowserContext* context) {
+ ExtensionSystem* system = ExtensionSystem::Get(context);
+ ExtensionRegistry* registry = ExtensionRegistry::Get(context);
+ const ManagementAPIDelegate* delegate =
+ ManagementAPI::GetFactoryInstance()->Get(context)->GetDelegate();
+ management::ExtensionInfo info;
+
+ info.id = extension.id();
+ info.name = extension.name();
+ info.short_name = extension.short_name();
+ info.enabled = registry->enabled_extensions().Contains(info.id);
+ info.offline_enabled = OfflineEnabledInfo::IsOfflineEnabled(&extension);
+ info.version = extension.VersionString();
+ if (!extension.version_name().empty())
+ info.version_name.reset(new std::string(extension.version_name()));
+ info.description = extension.description();
+ info.options_url = OptionsPageInfo::GetOptionsPage(&extension).spec();
+ info.homepage_url.reset(
+ new std::string(ManifestURL::GetHomepageURL(&extension).spec()));
+ info.may_disable =
+ system->management_policy()->UserMayModifySettings(&extension, NULL);
+ info.is_app = extension.is_app();
+ if (info.is_app) {
+ if (extension.is_legacy_packaged_app())
+ info.type = management::EXTENSION_TYPE_LEGACY_PACKAGED_APP;
+ else if (extension.is_hosted_app())
+ info.type = management::EXTENSION_TYPE_HOSTED_APP;
+ else
+ info.type = management::EXTENSION_TYPE_PACKAGED_APP;
+ } else if (extension.is_theme()) {
+ info.type = management::EXTENSION_TYPE_THEME;
+ } else {
+ info.type = management::EXTENSION_TYPE_EXTENSION;
+ }
+
+ if (info.enabled) {
+ info.disabled_reason = management::EXTENSION_DISABLED_REASON_NONE;
+ } else {
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(context);
+ if (prefs->DidExtensionEscalatePermissions(extension.id())) {
+ info.disabled_reason =
+ management::EXTENSION_DISABLED_REASON_PERMISSIONS_INCREASE;
+ } else {
+ info.disabled_reason = management::EXTENSION_DISABLED_REASON_UNKNOWN;
+ }
+ }
+
+ if (!ManifestURL::GetUpdateURL(&extension).is_empty()) {
+ info.update_url.reset(
+ new std::string(ManifestURL::GetUpdateURL(&extension).spec()));
+ }
+
+ if (extension.is_app()) {
+ info.app_launch_url.reset(
+ new std::string(delegate->GetFullLaunchURL(&extension).spec()));
+ }
+
+ const ExtensionIconSet::IconMap& icons =
+ IconsInfo::GetIcons(&extension).map();
+ if (!icons.empty()) {
+ info.icons.reset(new IconInfoList());
+ ExtensionIconSet::IconMap::const_iterator icon_iter;
+ for (icon_iter = icons.begin(); icon_iter != icons.end(); ++icon_iter) {
+ management::IconInfo icon_info;
+ icon_info.size = icon_iter->first;
+ GURL url =
+ delegate->GetIconURL(&extension, icon_info.size,
+ ExtensionIconSet::MATCH_EXACTLY, false, nullptr);
+ icon_info.url = url.spec();
+ info.icons->push_back(std::move(icon_info));
+ }
+ }
+
+ const std::set<std::string> perms =
+ extension.permissions_data()->active_permissions().GetAPIsAsStrings();
+ if (!perms.empty()) {
+ std::set<std::string>::const_iterator perms_iter;
+ for (perms_iter = perms.begin(); perms_iter != perms.end(); ++perms_iter)
+ info.permissions.push_back(*perms_iter);
+ }
+
+ if (!extension.is_hosted_app()) {
+ // Skip host permissions for hosted apps.
+ const URLPatternSet host_perms =
+ extension.permissions_data()->active_permissions().explicit_hosts();
+ if (!host_perms.is_empty()) {
+ for (URLPatternSet::const_iterator iter = host_perms.begin();
+ iter != host_perms.end(); ++iter) {
+ info.host_permissions.push_back(iter->GetAsString());
+ }
+ }
+ }
+
+ switch (extension.location()) {
+ case Manifest::INTERNAL:
+ info.install_type = management::EXTENSION_INSTALL_TYPE_NORMAL;
+ break;
+ case Manifest::UNPACKED:
+ case Manifest::COMMAND_LINE:
+ info.install_type = management::EXTENSION_INSTALL_TYPE_DEVELOPMENT;
+ break;
+ case Manifest::EXTERNAL_PREF:
+ case Manifest::EXTERNAL_REGISTRY:
+ case Manifest::EXTERNAL_PREF_DOWNLOAD:
+ info.install_type = management::EXTENSION_INSTALL_TYPE_SIDELOAD;
+ break;
+ case Manifest::EXTERNAL_POLICY:
+ case Manifest::EXTERNAL_POLICY_DOWNLOAD:
+ info.install_type = management::EXTENSION_INSTALL_TYPE_ADMIN;
+ break;
+ case Manifest::NUM_LOCATIONS:
+ NOTREACHED();
+ case Manifest::INVALID_LOCATION:
+ case Manifest::COMPONENT:
+ case Manifest::EXTERNAL_COMPONENT:
+ info.install_type = management::EXTENSION_INSTALL_TYPE_OTHER;
+ break;
+ }
+
+ info.launch_type = management::LAUNCH_TYPE_NONE;
+ if (extension.is_app()) {
+ LaunchType launch_type;
+ if (extension.is_platform_app()) {
+ launch_type = LAUNCH_TYPE_WINDOW;
+ } else {
+ launch_type =
+ delegate->GetLaunchType(ExtensionPrefs::Get(context), &extension);
+ }
+
+ switch (launch_type) {
+ case LAUNCH_TYPE_PINNED:
+ info.launch_type = management::LAUNCH_TYPE_OPEN_AS_PINNED_TAB;
+ break;
+ case LAUNCH_TYPE_REGULAR:
+ info.launch_type = management::LAUNCH_TYPE_OPEN_AS_REGULAR_TAB;
+ break;
+ case LAUNCH_TYPE_FULLSCREEN:
+ info.launch_type = management::LAUNCH_TYPE_OPEN_FULL_SCREEN;
+ break;
+ case LAUNCH_TYPE_WINDOW:
+ info.launch_type = management::LAUNCH_TYPE_OPEN_AS_WINDOW;
+ break;
+ case LAUNCH_TYPE_INVALID:
+ case NUM_LAUNCH_TYPES:
+ NOTREACHED();
+ }
+
+ info.available_launch_types.reset(new std::vector<management::LaunchType>(
+ GetAvailableLaunchTypes(extension, delegate)));
+ }
+
+ return info;
+}
+
+void AddExtensionInfo(const ExtensionSet& extensions,
+ ExtensionInfoList* extension_list,
+ content::BrowserContext* context) {
+ for (ExtensionSet::const_iterator iter = extensions.begin();
+ iter != extensions.end(); ++iter) {
+ const Extension& extension = *iter->get();
+
+ if (extension.ShouldNotBeVisible())
+ continue; // Skip built-in extensions/apps.
+
+ extension_list->push_back(CreateExtensionInfo(extension, context));
+ }
+}
+
+} // namespace
+
+bool ManagementGetAllFunction::RunSync() {
+ ExtensionInfoList extensions;
+ ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
+
+ AddExtensionInfo(registry->enabled_extensions(), &extensions,
+ browser_context());
+ AddExtensionInfo(registry->disabled_extensions(), &extensions,
+ browser_context());
+ AddExtensionInfo(registry->terminated_extensions(), &extensions,
+ browser_context());
+
+ results_ = management::GetAll::Results::Create(extensions);
+ return true;
+}
+
+bool ManagementGetFunction::RunSync() {
+ scoped_ptr<management::Get::Params> params(
+ management::Get::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
+
+ const Extension* extension =
+ registry->GetExtensionById(params->id, ExtensionRegistry::EVERYTHING);
+ if (!extension) {
+ error_ =
+ ErrorUtils::FormatErrorMessage(keys::kNoExtensionError, params->id);
+ return false;
+ }
+
+ results_ = management::Get::Results::Create(
+ CreateExtensionInfo(*extension, browser_context()));
+
+ return true;
+}
+
+bool ManagementGetSelfFunction::RunSync() {
+ results_ = management::Get::Results::Create(
+ CreateExtensionInfo(*extension_, browser_context()));
+
+ return true;
+}
+
+bool ManagementGetPermissionWarningsByIdFunction::RunSync() {
+ scoped_ptr<management::GetPermissionWarningsById::Params> params(
+ management::GetPermissionWarningsById::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ const Extension* extension =
+ ExtensionRegistry::Get(browser_context())
+ ->GetExtensionById(params->id, ExtensionRegistry::EVERYTHING);
+ if (!extension) {
+ error_ =
+ ErrorUtils::FormatErrorMessage(keys::kNoExtensionError, params->id);
+ return false;
+ }
+
+ std::vector<std::string> warnings = CreateWarningsList(extension);
+ results_ = management::GetPermissionWarningsById::Results::Create(warnings);
+ return true;
+}
+
+bool ManagementGetPermissionWarningsByManifestFunction::RunAsync() {
+ scoped_ptr<management::GetPermissionWarningsByManifest::Params> params(
+ management::GetPermissionWarningsByManifest::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ const ManagementAPIDelegate* delegate = ManagementAPI::GetFactoryInstance()
+ ->Get(browser_context())
+ ->GetDelegate();
+
+ if (delegate) {
+ delegate->GetPermissionWarningsByManifestFunctionDelegate(
+ this, params->manifest_str);
+
+ // Matched with a Release() in OnParseSuccess/Failure().
+ AddRef();
+
+ // Response is sent async in OnParseSuccess/Failure().
+ return true;
+ } else {
+ // TODO(lfg) add error string
+ OnParseFailure("");
+ return false;
+ }
+}
+
+void ManagementGetPermissionWarningsByManifestFunction::OnParseSuccess(
+ scoped_ptr<base::Value> value) {
+ if (!value->IsType(base::Value::TYPE_DICTIONARY)) {
+ OnParseFailure(keys::kManifestParseError);
+ return;
+ }
+ const base::DictionaryValue* parsed_manifest =
+ static_cast<const base::DictionaryValue*>(value.get());
+
+ scoped_refptr<Extension> extension =
+ Extension::Create(base::FilePath(), Manifest::INVALID_LOCATION,
+ *parsed_manifest, Extension::NO_FLAGS, &error_);
+ if (!extension) {
+ OnParseFailure(keys::kExtensionCreateError);
+ return;
+ }
+
+ std::vector<std::string> warnings = CreateWarningsList(extension.get());
+ results_ =
+ management::GetPermissionWarningsByManifest::Results::Create(warnings);
+ SendResponse(true);
+
+ // Matched with AddRef() in RunAsync().
+ Release();
+}
+
+void ManagementGetPermissionWarningsByManifestFunction::OnParseFailure(
+ const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+
+ // Matched with AddRef() in RunAsync().
+ Release();
+}
+
+bool ManagementLaunchAppFunction::RunSync() {
+ scoped_ptr<management::LaunchApp::Params> params(
+ management::LaunchApp::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ const Extension* extension =
+ ExtensionRegistry::Get(browser_context())
+ ->GetExtensionById(params->id, ExtensionRegistry::EVERYTHING);
+ if (!extension) {
+ error_ =
+ ErrorUtils::FormatErrorMessage(keys::kNoExtensionError, params->id);
+ return false;
+ }
+ if (!extension->is_app()) {
+ error_ = ErrorUtils::FormatErrorMessage(keys::kNotAnAppError, params->id);
+ return false;
+ }
+
+ const ManagementAPIDelegate* delegate = ManagementAPI::GetFactoryInstance()
+ ->Get(browser_context())
+ ->GetDelegate();
+ return delegate->LaunchAppFunctionDelegate(extension, browser_context());
+}
+
+ManagementSetEnabledFunction::ManagementSetEnabledFunction() {
+}
+
+ManagementSetEnabledFunction::~ManagementSetEnabledFunction() {
+}
+
+ExtensionFunction::ResponseAction ManagementSetEnabledFunction::Run() {
+ scoped_ptr<management::SetEnabled::Params> params(
+ management::SetEnabled::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
+ const ManagementAPIDelegate* delegate = ManagementAPI::GetFactoryInstance()
+ ->Get(browser_context())
+ ->GetDelegate();
+
+ extension_id_ = params->id;
+
+ const Extension* extension =
+ registry->GetExtensionById(extension_id_, ExtensionRegistry::EVERYTHING);
+ if (!extension || extension->ShouldNotBeVisible())
+ return RespondNow(Error(keys::kNoExtensionError, extension_id_));
+
+ bool enabled = params->enabled;
+ const ManagementPolicy* policy =
+ ExtensionSystem::Get(browser_context())->management_policy();
+ if (!policy->UserMayModifySettings(extension, nullptr) ||
+ (!enabled && policy->MustRemainEnabled(extension, nullptr)) ||
+ (enabled && policy->MustRemainDisabled(extension, nullptr, nullptr))) {
+ return RespondNow(Error(keys::kUserCantModifyError, extension_id_));
+ }
+
+ bool currently_enabled =
+ registry->enabled_extensions().Contains(extension_id_) ||
+ registry->terminated_extensions().Contains(extension_id_);
+
+ if (!currently_enabled && enabled) {
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context());
+ if (prefs->DidExtensionEscalatePermissions(extension_id_)) {
+ if (!user_gesture())
+ return RespondNow(Error(keys::kGestureNeededForEscalationError));
+
+ AddRef(); // Matched in OnInstallPromptDone().
+ install_prompt_ = delegate->SetEnabledFunctionDelegate(
+ GetSenderWebContents(), browser_context(), extension,
+ base::Bind(&ManagementSetEnabledFunction::OnInstallPromptDone, this));
+ return RespondLater();
+ }
+ if (prefs->GetDisableReasons(extension_id_) &
+ Extension::DISABLE_UNSUPPORTED_REQUIREMENT) {
+ // Recheck the requirements.
+ requirements_checker_ = delegate->CreateRequirementsChecker();
+ requirements_checker_->Check(
+ extension,
+ base::Bind(&ManagementSetEnabledFunction::OnRequirementsChecked,
+ this)); // This bind creates a reference.
+ return RespondLater();
+ }
+ delegate->EnableExtension(browser_context(), extension_id_);
+ } else if (currently_enabled && !params->enabled) {
+ delegate->DisableExtension(browser_context(), extension_id_,
+ Extension::DISABLE_USER_ACTION);
+ }
+
+ return RespondNow(NoArguments());
+}
+
+void ManagementSetEnabledFunction::OnInstallPromptDone(bool did_accept) {
+ if (did_accept) {
+ ManagementAPI::GetFactoryInstance()
+ ->Get(browser_context())
+ ->GetDelegate()
+ ->EnableExtension(browser_context(), extension_id_);
+ Respond(OneArgument(new base::FundamentalValue(true)));
+ } else {
+ Respond(Error(keys::kUserDidNotReEnableError));
+ }
+
+ Release(); // Balanced in Run().
+}
+
+void ManagementSetEnabledFunction::OnRequirementsChecked(
+ const std::vector<std::string>& requirements_errors) {
+ if (requirements_errors.empty()) {
+ ManagementAPI::GetFactoryInstance()->Get(browser_context())->GetDelegate()->
+ EnableExtension(browser_context(), extension_id_);
+ Respond(NoArguments());
+ } else {
+ // TODO(devlin): Should we really be noisy here all the time?
+ Respond(Error(keys::kMissingRequirementsError,
+ base::JoinString(requirements_errors, " ")));
+ }
+}
+
+ManagementUninstallFunctionBase::ManagementUninstallFunctionBase() {
+}
+
+ManagementUninstallFunctionBase::~ManagementUninstallFunctionBase() {
+}
+
+ExtensionFunction::ResponseAction ManagementUninstallFunctionBase::Uninstall(
+ const std::string& target_extension_id,
+ bool show_confirm_dialog) {
+ const ManagementAPIDelegate* delegate = ManagementAPI::GetFactoryInstance()
+ ->Get(browser_context())
+ ->GetDelegate();
+ target_extension_id_ = target_extension_id;
+ const Extension* target_extension =
+ extensions::ExtensionRegistry::Get(browser_context())
+ ->GetExtensionById(target_extension_id_,
+ ExtensionRegistry::EVERYTHING);
+ if (!target_extension || target_extension->ShouldNotBeVisible()) {
+ return RespondNow(Error(keys::kNoExtensionError, target_extension_id_));
+ }
+
+ ManagementPolicy* policy =
+ ExtensionSystem::Get(browser_context())->management_policy();
+ if (!policy->UserMayModifySettings(target_extension, nullptr) ||
+ policy->MustRemainInstalled(target_extension, nullptr)) {
+ return RespondNow(Error(keys::kUserCantModifyError, target_extension_id_));
+ }
+
+ // Note: null extension() means it's WebUI.
+ bool self_uninstall = extension() && extension_id() == target_extension_id_;
+ // We need to show a dialog for any extension uninstalling another extension.
+ show_confirm_dialog |= !self_uninstall;
+
+ if (show_confirm_dialog && !user_gesture())
+ return RespondNow(Error(keys::kGestureNeededForUninstallError));
+
+ if (show_confirm_dialog) {
+ // We show the programmatic uninstall ui for extensions uninstalling
+ // other extensions.
+ bool show_programmatic_uninstall_ui = !self_uninstall && extension();
+ AddRef(); // Balanced in OnExtensionUninstallDialogClosed.
+ // TODO(devlin): A method called "UninstallFunctionDelegate" does not in
+ // any way imply that this actually creates a dialog and runs it.
+ uninstall_dialog_ = delegate->UninstallFunctionDelegate(
+ this, target_extension, show_programmatic_uninstall_ui);
+ } else { // No confirm dialog.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ManagementUninstallFunctionBase::UninstallExtension, this));
+ }
+
+ return RespondLater();
+}
+
+void ManagementUninstallFunctionBase::Finish(bool did_start_uninstall,
+ const std::string& error) {
+ Respond(did_start_uninstall ? NoArguments() : Error(error));
+}
+
+void ManagementUninstallFunctionBase::OnExtensionUninstallDialogClosed(
+ bool did_start_uninstall,
+ const base::string16& error) {
+ Finish(did_start_uninstall,
+ ErrorUtils::FormatErrorMessage(keys::kUninstallCanceledError,
+ target_extension_id_));
+ Release(); // Balanced in Uninstall().
+}
+
+void ManagementUninstallFunctionBase::UninstallExtension() {
+ // The extension can be uninstalled in another window while the UI was
+ // showing. Do nothing in that case.
+ const Extension* target_extension =
+ extensions::ExtensionRegistry::Get(browser_context())
+ ->GetExtensionById(target_extension_id_,
+ ExtensionRegistry::EVERYTHING);
+ std::string error;
+ bool success = false;
+ if (target_extension) {
+ const ManagementAPIDelegate* delegate = ManagementAPI::GetFactoryInstance()
+ ->Get(browser_context())
+ ->GetDelegate();
+ base::string16 utf16_error;
+ success = delegate->UninstallExtension(
+ browser_context(), target_extension_id_,
+ extensions::UNINSTALL_REASON_MANAGEMENT_API,
+ base::Bind(&base::DoNothing), &utf16_error);
+ error = base::UTF16ToUTF8(utf16_error);
+ } else {
+ error = ErrorUtils::FormatErrorMessage(keys::kNoExtensionError,
+ target_extension_id_);
+ }
+ Finish(success, error);
+}
+
+ManagementUninstallFunction::ManagementUninstallFunction() {
+}
+
+ManagementUninstallFunction::~ManagementUninstallFunction() {
+}
+
+ExtensionFunction::ResponseAction ManagementUninstallFunction::Run() {
+ scoped_ptr<management::Uninstall::Params> params(
+ management::Uninstall::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ bool show_confirm_dialog = params->options.get() &&
+ params->options->show_confirm_dialog.get() &&
+ *params->options->show_confirm_dialog;
+ return Uninstall(params->id, show_confirm_dialog);
+}
+
+ManagementUninstallSelfFunction::ManagementUninstallSelfFunction() {
+}
+
+ManagementUninstallSelfFunction::~ManagementUninstallSelfFunction() {
+}
+
+ExtensionFunction::ResponseAction ManagementUninstallSelfFunction::Run() {
+ scoped_ptr<management::UninstallSelf::Params> params(
+ management::UninstallSelf::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ EXTENSION_FUNCTION_VALIDATE(extension_.get());
+
+ bool show_confirm_dialog = params->options.get() &&
+ params->options->show_confirm_dialog.get() &&
+ *params->options->show_confirm_dialog;
+ return Uninstall(extension_->id(), show_confirm_dialog);
+}
+
+ManagementCreateAppShortcutFunction::ManagementCreateAppShortcutFunction() {
+}
+
+ManagementCreateAppShortcutFunction::~ManagementCreateAppShortcutFunction() {
+}
+
+// static
+void ManagementCreateAppShortcutFunction::SetAutoConfirmForTest(
+ bool should_proceed) {
+ auto_confirm_for_test = should_proceed ? PROCEED : ABORT;
+}
+
+void ManagementCreateAppShortcutFunction::OnCloseShortcutPrompt(bool created) {
+ if (!created)
+ error_ = keys::kCreateShortcutCanceledError;
+ SendResponse(created);
+ Release();
+}
+
+bool ManagementCreateAppShortcutFunction::RunAsync() {
+ if (!user_gesture()) {
+ error_ = keys::kGestureNeededForCreateAppShortcutError;
+ return false;
+ }
+
+ scoped_ptr<management::CreateAppShortcut::Params> params(
+ management::CreateAppShortcut::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ const Extension* extension =
+ ExtensionRegistry::Get(browser_context())
+ ->GetExtensionById(params->id, ExtensionRegistry::EVERYTHING);
+ if (!extension) {
+ error_ =
+ ErrorUtils::FormatErrorMessage(keys::kNoExtensionError, params->id);
+ return false;
+ }
+
+ if (!extension->is_app()) {
+ error_ = ErrorUtils::FormatErrorMessage(keys::kNotAnAppError, params->id);
+ return false;
+ }
+
+#if defined(OS_MACOSX)
+ if (!extension->is_platform_app()) {
+ error_ = keys::kCreateOnlyPackagedAppShortcutMac;
+ return false;
+ }
+#endif
+
+ if (auto_confirm_for_test != DO_NOT_SKIP) {
+ // Matched with a Release() in OnCloseShortcutPrompt().
+ AddRef();
+
+ OnCloseShortcutPrompt(auto_confirm_for_test == PROCEED);
+
+ return true;
+ }
+
+ if (ManagementAPI::GetFactoryInstance()
+ ->Get(browser_context())
+ ->GetDelegate()
+ ->CreateAppShortcutFunctionDelegate(this, extension)) {
+ // Matched with a Release() in OnCloseShortcutPrompt().
+ AddRef();
+ }
+
+ // Response is sent async in OnCloseShortcutPrompt().
+ return true;
+}
+
+bool ManagementSetLaunchTypeFunction::RunSync() {
+ if (!user_gesture()) {
+ error_ = keys::kGestureNeededForSetLaunchTypeError;
+ return false;
+ }
+
+ scoped_ptr<management::SetLaunchType::Params> params(
+ management::SetLaunchType::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ const Extension* extension =
+ ExtensionRegistry::Get(browser_context())
+ ->GetExtensionById(params->id, ExtensionRegistry::EVERYTHING);
+ const ManagementAPIDelegate* delegate = ManagementAPI::GetFactoryInstance()
+ ->Get(browser_context())
+ ->GetDelegate();
+ if (!extension) {
+ error_ =
+ ErrorUtils::FormatErrorMessage(keys::kNoExtensionError, params->id);
+ return false;
+ }
+
+ if (!extension->is_app()) {
+ error_ = ErrorUtils::FormatErrorMessage(keys::kNotAnAppError, params->id);
+ return false;
+ }
+
+ std::vector<management::LaunchType> available_launch_types =
+ GetAvailableLaunchTypes(*extension, delegate);
+
+ management::LaunchType app_launch_type = params->launch_type;
+ if (std::find(available_launch_types.begin(), available_launch_types.end(),
+ app_launch_type) == available_launch_types.end()) {
+ error_ = keys::kLaunchTypeNotAvailableError;
+ return false;
+ }
+
+ LaunchType launch_type = LAUNCH_TYPE_DEFAULT;
+ switch (app_launch_type) {
+ case management::LAUNCH_TYPE_OPEN_AS_PINNED_TAB:
+ launch_type = LAUNCH_TYPE_PINNED;
+ break;
+ case management::LAUNCH_TYPE_OPEN_AS_REGULAR_TAB:
+ launch_type = LAUNCH_TYPE_REGULAR;
+ break;
+ case management::LAUNCH_TYPE_OPEN_FULL_SCREEN:
+ launch_type = LAUNCH_TYPE_FULLSCREEN;
+ break;
+ case management::LAUNCH_TYPE_OPEN_AS_WINDOW:
+ launch_type = LAUNCH_TYPE_WINDOW;
+ break;
+ case management::LAUNCH_TYPE_NONE:
+ NOTREACHED();
+ }
+
+ delegate->SetLaunchType(browser_context(), params->id, launch_type);
+
+ return true;
+}
+
+ManagementGenerateAppForLinkFunction::ManagementGenerateAppForLinkFunction() {
+}
+
+ManagementGenerateAppForLinkFunction::~ManagementGenerateAppForLinkFunction() {
+}
+
+void ManagementGenerateAppForLinkFunction::FinishCreateBookmarkApp(
+ const Extension* extension,
+ const WebApplicationInfo& web_app_info) {
+ if (extension) {
+ results_ = management::GenerateAppForLink::Results::Create(
+ CreateExtensionInfo(*extension, browser_context()));
+
+ SendResponse(true);
+ Release();
+ } else {
+ error_ = keys::kGenerateAppForLinkInstallError;
+ SendResponse(false);
+ Release();
+ }
+}
+
+bool ManagementGenerateAppForLinkFunction::RunAsync() {
+ if (!user_gesture()) {
+ error_ = keys::kGestureNeededForGenerateAppForLinkError;
+ return false;
+ }
+
+ scoped_ptr<management::GenerateAppForLink::Params> params(
+ management::GenerateAppForLink::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ GURL launch_url(params->url);
+ if (!launch_url.is_valid() || !launch_url.SchemeIsHTTPOrHTTPS()) {
+ error_ =
+ ErrorUtils::FormatErrorMessage(keys::kInvalidURLError, params->url);
+ return false;
+ }
+
+ if (params->title.empty()) {
+ error_ = keys::kEmptyTitleError;
+ return false;
+ }
+
+ app_for_link_delegate_ =
+ ManagementAPI::GetFactoryInstance()
+ ->Get(browser_context())
+ ->GetDelegate()
+ ->GenerateAppForLinkFunctionDelegate(this, browser_context(),
+ params->title, launch_url);
+
+ // Matched with a Release() in FinishCreateBookmarkApp().
+ AddRef();
+
+ // Response is sent async in FinishCreateBookmarkApp().
+ return true;
+}
+
+ManagementEventRouter::ManagementEventRouter(content::BrowserContext* context)
+ : browser_context_(context), extension_registry_observer_(this) {
+ extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
+}
+
+ManagementEventRouter::~ManagementEventRouter() {
+}
+
+void ManagementEventRouter::OnExtensionLoaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension) {
+ BroadcastEvent(extension, events::MANAGEMENT_ON_ENABLED,
+ management::OnEnabled::kEventName);
+}
+
+void ManagementEventRouter::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ BroadcastEvent(extension, events::MANAGEMENT_ON_DISABLED,
+ management::OnDisabled::kEventName);
+}
+
+void ManagementEventRouter::OnExtensionInstalled(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ bool is_update) {
+ BroadcastEvent(extension, events::MANAGEMENT_ON_INSTALLED,
+ management::OnInstalled::kEventName);
+}
+
+void ManagementEventRouter::OnExtensionUninstalled(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ extensions::UninstallReason reason) {
+ BroadcastEvent(extension, events::MANAGEMENT_ON_UNINSTALLED,
+ management::OnUninstalled::kEventName);
+}
+
+void ManagementEventRouter::BroadcastEvent(
+ const Extension* extension,
+ events::HistogramValue histogram_value,
+ const char* event_name) {
+ if (extension->ShouldNotBeVisible())
+ return; // Don't dispatch events for built-in extenions.
+ scoped_ptr<base::ListValue> args(new base::ListValue());
+ if (event_name == management::OnUninstalled::kEventName) {
+ args->Append(new base::StringValue(extension->id()));
+ } else {
+ args->Append(CreateExtensionInfo(*extension, browser_context_).ToValue());
+ }
+
+ EventRouter::Get(browser_context_)
+ ->BroadcastEvent(scoped_ptr<Event>(
+ new Event(histogram_value, event_name, std::move(args))));
+}
+
+ManagementAPI::ManagementAPI(content::BrowserContext* context)
+ : browser_context_(context),
+ delegate_(ExtensionsAPIClient::Get()->CreateManagementAPIDelegate()) {
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ event_router->RegisterObserver(this, management::OnInstalled::kEventName);
+ event_router->RegisterObserver(this, management::OnUninstalled::kEventName);
+ event_router->RegisterObserver(this, management::OnEnabled::kEventName);
+ event_router->RegisterObserver(this, management::OnDisabled::kEventName);
+}
+
+ManagementAPI::~ManagementAPI() {
+}
+
+void ManagementAPI::Shutdown() {
+ EventRouter::Get(browser_context_)->UnregisterObserver(this);
+}
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<ManagementAPI>>
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<ManagementAPI>*
+ManagementAPI::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+void ManagementAPI::OnListenerAdded(const EventListenerInfo& details) {
+ management_event_router_.reset(new ManagementEventRouter(browser_context_));
+ EventRouter::Get(browser_context_)->UnregisterObserver(this);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/management/management_api.h b/chromium/extensions/browser/api/management/management_api.h
new file mode 100644
index 00000000000..fc9b563bd1a
--- /dev/null
+++ b/chromium/extensions/browser/api/management/management_api.h
@@ -0,0 +1,297 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_MANAGEMENT_MANAGEMENT_API_H_
+#define EXTENSIONS_BROWSER_API_MANAGEMENT_MANAGEMENT_API_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/scoped_observer.h"
+#include "base/strings/string16.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/browser/api/management/management_api_delegate.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_event_histogram_value.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/browser/extension_registry_observer.h"
+
+class ExtensionRegistry;
+class ExtensionUninstallDialog;
+struct WebApplicationInfo;
+
+namespace extensions {
+class ExtensionRegistry;
+class RequirementsChecker;
+
+class ManagementFunction : public SyncExtensionFunction {
+ protected:
+ ~ManagementFunction() override {}
+};
+
+class AsyncManagementFunction : public AsyncExtensionFunction {
+ protected:
+ ~AsyncManagementFunction() override {}
+};
+
+class ManagementGetAllFunction : public ManagementFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("management.getAll", MANAGEMENT_GETALL)
+
+ protected:
+ ~ManagementGetAllFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+class ManagementGetFunction : public ManagementFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("management.get", MANAGEMENT_GET)
+
+ protected:
+ ~ManagementGetFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+class ManagementGetSelfFunction : public ManagementFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("management.getSelf", MANAGEMENT_GETSELF)
+
+ protected:
+ ~ManagementGetSelfFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+class ManagementGetPermissionWarningsByIdFunction : public ManagementFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("management.getPermissionWarningsById",
+ MANAGEMENT_GETPERMISSIONWARNINGSBYID)
+
+ protected:
+ ~ManagementGetPermissionWarningsByIdFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+class ManagementGetPermissionWarningsByManifestFunction
+ : public AsyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("management.getPermissionWarningsByManifest",
+ MANAGEMENT_GETPERMISSIONWARNINGSBYMANIFEST);
+
+ // Called when utility process finishes.
+ void OnParseSuccess(scoped_ptr<base::Value> value);
+ void OnParseFailure(const std::string& error);
+
+ protected:
+ ~ManagementGetPermissionWarningsByManifestFunction() override {}
+
+ // ExtensionFunction:
+ bool RunAsync() override;
+};
+
+class ManagementLaunchAppFunction : public ManagementFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("management.launchApp", MANAGEMENT_LAUNCHAPP)
+
+ protected:
+ ~ManagementLaunchAppFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+class ManagementSetEnabledFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("management.setEnabled", MANAGEMENT_SETENABLED)
+
+ ManagementSetEnabledFunction();
+
+ protected:
+ ~ManagementSetEnabledFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ private:
+ void OnInstallPromptDone(bool did_accept);
+
+ void OnRequirementsChecked(const std::vector<std::string>& requirements);
+
+ std::string extension_id_;
+
+ scoped_ptr<InstallPromptDelegate> install_prompt_;
+
+ scoped_ptr<RequirementsChecker> requirements_checker_;
+};
+
+class ManagementUninstallFunctionBase : public UIThreadExtensionFunction {
+ public:
+ ManagementUninstallFunctionBase();
+
+ void OnExtensionUninstallDialogClosed(bool did_start_uninstall,
+ const base::string16& error);
+
+ protected:
+ ~ManagementUninstallFunctionBase() override;
+ ResponseAction Uninstall(const std::string& extension_id,
+ bool show_confirm_dialog);
+
+ private:
+ // Uninstalls the extension without showing the dialog.
+ void UninstallExtension();
+
+ // Finishes and responds to the extension.
+ void Finish(bool did_start_uninstall, const std::string& error);
+
+ std::string target_extension_id_;
+
+ scoped_ptr<UninstallDialogDelegate> uninstall_dialog_;
+};
+
+class ManagementUninstallFunction : public ManagementUninstallFunctionBase {
+ public:
+ DECLARE_EXTENSION_FUNCTION("management.uninstall", MANAGEMENT_UNINSTALL)
+ ManagementUninstallFunction();
+
+ private:
+ ~ManagementUninstallFunction() override;
+ ResponseAction Run() override;
+};
+
+class ManagementUninstallSelfFunction : public ManagementUninstallFunctionBase {
+ public:
+ DECLARE_EXTENSION_FUNCTION("management.uninstallSelf",
+ MANAGEMENT_UNINSTALLSELF);
+ ManagementUninstallSelfFunction();
+
+ private:
+ ~ManagementUninstallSelfFunction() override;
+ ResponseAction Run() override;
+};
+
+class ManagementCreateAppShortcutFunction : public AsyncManagementFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("management.createAppShortcut",
+ MANAGEMENT_CREATEAPPSHORTCUT);
+
+ ManagementCreateAppShortcutFunction();
+
+ void OnCloseShortcutPrompt(bool created);
+
+ static void SetAutoConfirmForTest(bool should_proceed);
+
+ protected:
+ ~ManagementCreateAppShortcutFunction() override;
+
+ bool RunAsync() override;
+};
+
+class ManagementSetLaunchTypeFunction : public ManagementFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("management.setLaunchType",
+ MANAGEMENT_SETLAUNCHTYPE);
+
+ protected:
+ ~ManagementSetLaunchTypeFunction() override {}
+
+ bool RunSync() override;
+};
+
+class ManagementGenerateAppForLinkFunction : public AsyncManagementFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("management.generateAppForLink",
+ MANAGEMENT_GENERATEAPPFORLINK);
+
+ ManagementGenerateAppForLinkFunction();
+
+ void FinishCreateBookmarkApp(const Extension* extension,
+ const WebApplicationInfo& web_app_info);
+
+ protected:
+ ~ManagementGenerateAppForLinkFunction() override;
+
+ bool RunAsync() override;
+
+ private:
+ scoped_ptr<AppForLinkDelegate> app_for_link_delegate_;
+};
+
+class ManagementEventRouter : public ExtensionRegistryObserver {
+ public:
+ explicit ManagementEventRouter(content::BrowserContext* context);
+ ~ManagementEventRouter() override;
+
+ private:
+ // ExtensionRegistryObserver implementation.
+ void OnExtensionLoaded(content::BrowserContext* browser_context,
+ const Extension* extension) override;
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+ void OnExtensionInstalled(content::BrowserContext* browser_context,
+ const Extension* extension,
+ bool is_update) override;
+ void OnExtensionUninstalled(content::BrowserContext* browser_context,
+ const Extension* extension,
+ extensions::UninstallReason reason) override;
+
+ // Dispatches management api events to listening extensions.
+ void BroadcastEvent(const Extension* extension,
+ events::HistogramValue histogram_value,
+ const char* event_name);
+
+ content::BrowserContext* browser_context_;
+
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ManagementEventRouter);
+};
+
+class ManagementAPI : public BrowserContextKeyedAPI,
+ public EventRouter::Observer {
+ public:
+ explicit ManagementAPI(content::BrowserContext* context);
+ ~ManagementAPI() override;
+
+ // KeyedService implementation.
+ void Shutdown() override;
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<ManagementAPI>* GetFactoryInstance();
+
+ // EventRouter::Observer implementation.
+ void OnListenerAdded(const EventListenerInfo& details) override;
+
+ // Returns the ManagementAPI delegate.
+ const ManagementAPIDelegate* GetDelegate() const { return delegate_.get(); }
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<ManagementAPI>;
+
+ content::BrowserContext* browser_context_;
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "ManagementAPI"; }
+ static const bool kServiceIsNULLWhileTesting = true;
+ static const bool kServiceRedirectedInIncognito = true;
+
+ // Created lazily upon OnListenerAdded.
+ scoped_ptr<ManagementEventRouter> management_event_router_;
+
+ scoped_ptr<ManagementAPIDelegate> delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(ManagementAPI);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_MANAGEMENT_MANAGEMENT_API_H_
diff --git a/chromium/extensions/browser/api/management/management_api_constants.cc b/chromium/extensions/browser/api/management/management_api_constants.cc
new file mode 100644
index 00000000000..924b35767b7
--- /dev/null
+++ b/chromium/extensions/browser/api/management/management_api_constants.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/management/management_api_constants.h"
+
+namespace extension_management_api_constants {
+
+const char kDisabledReasonKey[] = "disabledReason";
+
+const char kDisabledReasonPermissionsIncrease[] = "permissions_increase";
+
+const char kExtensionCreateError[] =
+ "Failed to create extension from manifest.";
+const char kGestureNeededForEscalationError[] =
+ "Re-enabling an extension disabled due to permissions increase "
+ "requires a user gesture.";
+const char kGestureNeededForUninstallError[] =
+ "chrome.management.uninstall requires a user gesture.";
+const char kManifestParseError[] = "Failed to parse manifest.";
+const char kNoExtensionError[] = "Failed to find extension with id *.";
+const char kNotAnAppError[] = "Extension * is not an App.";
+const char kUserCantModifyError[] = "Extension * cannot be modified by user.";
+const char kUninstallCanceledError[] =
+ "Extension * uninstall canceled by user.";
+const char kUserDidNotReEnableError[] =
+ "The user did not accept the re-enable dialog.";
+const char kMissingRequirementsError[] = "There were missing requirements: *.";
+const char kGestureNeededForCreateAppShortcutError[] =
+ "chrome.management.createAppShortcut requires a user gesture.";
+const char kNoBrowserToCreateShortcut[] =
+ "There is no browser window to create shortcut.";
+const char kCreateOnlyPackagedAppShortcutMac[] =
+ "Shortcuts can only be created for new-style packaged apps on Mac.";
+const char kCreateShortcutCanceledError[] =
+ "App shortcuts creation canceled by user.";
+const char kGestureNeededForSetLaunchTypeError[] =
+ "chrome.management.setLaunchType requires a user gesture.";
+const char kLaunchTypeNotAvailableError[] =
+ "The launch type is not available for this app.";
+const char kGestureNeededForGenerateAppForLinkError[] =
+ "chrome.management.generateAppForLink requires a user gesture.";
+const char kInvalidURLError[] = "The URL \"*\" is invalid.";
+const char kEmptyTitleError[] = "The title can not be empty.";
+const char kGenerateAppForLinkInstallError[] =
+ "Failed to install the generated app.";
+
+} // namespace extension_management_api_constants
diff --git a/chromium/extensions/browser/api/management/management_api_constants.h b/chromium/extensions/browser/api/management/management_api_constants.h
new file mode 100644
index 00000000000..bdbeffbf0c2
--- /dev/null
+++ b/chromium/extensions/browser/api/management/management_api_constants.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_MANAGEMENT_MANAGEMENT_API_CONSTANTS_H_
+#define EXTENSIONS_BROWSER_API_MANAGEMENT_MANAGEMENT_API_CONSTANTS_H_
+
+namespace extension_management_api_constants {
+
+// Keys used for incoming arguments and outgoing JSON data.
+extern const char kDisabledReasonKey[];
+
+// Values for outgoing JSON data.
+extern const char kDisabledReasonPermissionsIncrease[];
+
+// Error messages.
+extern const char kExtensionCreateError[];
+extern const char kGestureNeededForEscalationError[];
+extern const char kGestureNeededForUninstallError[];
+extern const char kManifestParseError[];
+extern const char kNoExtensionError[];
+extern const char kNotAnAppError[];
+extern const char kUserCantModifyError[];
+extern const char kUninstallCanceledError[];
+extern const char kUserDidNotReEnableError[];
+extern const char kMissingRequirementsError[];
+extern const char kGestureNeededForCreateAppShortcutError[];
+extern const char kNoBrowserToCreateShortcut[];
+extern const char kCreateOnlyPackagedAppShortcutMac[];
+extern const char kCreateShortcutCanceledError[];
+extern const char kGestureNeededForSetLaunchTypeError[];
+extern const char kLaunchTypeNotAvailableError[];
+extern const char kGestureNeededForGenerateAppForLinkError[];
+extern const char kInvalidURLError[];
+extern const char kEmptyTitleError[];
+extern const char kGenerateAppForLinkInstallError[];
+
+} // namespace extension_management_api_constants
+
+#endif // EXTENSIONS_BROWSER_API_MANAGEMENT_MANAGEMENT_API_CONSTANTS_H_
diff --git a/chromium/extensions/browser/api/management/management_api_delegate.h b/chromium/extensions/browser/api/management/management_api_delegate.h
new file mode 100644
index 00000000000..9e127eeb7f1
--- /dev/null
+++ b/chromium/extensions/browser/api/management/management_api_delegate.h
@@ -0,0 +1,140 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_MANAGEMENT_MANAGEMENT_API_DELEGATER_H_
+#define EXTENSIONS_BROWSER_API_MANAGEMENT_MANAGEMENT_API_DELEGATER_H_
+
+#include "base/callback.h"
+#include "extensions/browser/uninstall_reason.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_icon_set.h"
+#include "url/gurl.h"
+
+namespace content {
+class BrowserContext;
+class WebContents;
+} // namespace content
+
+namespace extensions {
+
+class Extension;
+class ExtensionPrefs;
+class ManagementCreateAppShortcutFunction;
+class ManagementGenerateAppForLinkFunction;
+class ManagementGetPermissionWarningsByManifestFunction;
+class ManagementSetEnabledFunction;
+class ManagementUninstallFunctionBase;
+class RequirementsChecker;
+
+// Manages the lifetime of the install prompt.
+class InstallPromptDelegate {
+ public:
+ virtual ~InstallPromptDelegate() {}
+};
+
+// Manages the lifetime of the uninstall prompt.
+class UninstallDialogDelegate {
+ public:
+ virtual ~UninstallDialogDelegate() {}
+};
+
+// Manages the lifetime of the bookmark app creation.
+class AppForLinkDelegate {
+ public:
+ virtual ~AppForLinkDelegate() {}
+};
+
+class ManagementAPIDelegate {
+ public:
+ virtual ~ManagementAPIDelegate() {}
+
+ // Launches the app |extension|. Returns true on success.
+ virtual bool LaunchAppFunctionDelegate(
+ const Extension* extension,
+ content::BrowserContext* context) const = 0;
+
+ // Forwards the call to extensions::util::IsNewBookmarkAppsEnabled in
+ // chrome.
+ virtual bool IsNewBookmarkAppsEnabled() const = 0;
+
+ // Forwards the call to extensions::util::CanHostedAppsOpenInWindows in
+ // chrome.
+ virtual bool CanHostedAppsOpenInWindows() const = 0;
+
+ // Forwards the call to AppLaunchInfo::GetFullLaunchURL in chrome.
+ virtual GURL GetFullLaunchURL(const Extension* extension) const = 0;
+
+ // Forwards the call to launch_util::GetLaunchType in chrome.
+ virtual LaunchType GetLaunchType(const ExtensionPrefs* prefs,
+ const Extension* extension) const = 0;
+
+ // Parses the manifest and calls back the
+ // ManagementGetPermissionWarningsByManifestFunction.
+ virtual void GetPermissionWarningsByManifestFunctionDelegate(
+ ManagementGetPermissionWarningsByManifestFunction* function,
+ const std::string& manifest_str) const = 0;
+
+ // Used to show a dialog prompt in chrome when management.setEnabled extension
+ // function is called.
+ virtual scoped_ptr<InstallPromptDelegate> SetEnabledFunctionDelegate(
+ content::WebContents* web_contents,
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ const base::Callback<void(bool)>& callback) const = 0;
+
+ // Returns a new RequirementsChecker.
+ virtual scoped_ptr<RequirementsChecker> CreateRequirementsChecker() const = 0;
+
+ // Enables the extension identified by |extension_id|.
+ virtual void EnableExtension(content::BrowserContext* context,
+ const std::string& extension_id) const = 0;
+
+ // Disables the extension identified by |extension_id|.
+ virtual void DisableExtension(
+ content::BrowserContext* context,
+ const std::string& extension_id,
+ Extension::DisableReason disable_reason) const = 0;
+
+ // Used to show a confirmation dialog when uninstalling |target_extension|.
+ virtual scoped_ptr<UninstallDialogDelegate> UninstallFunctionDelegate(
+ ManagementUninstallFunctionBase* function,
+ const Extension* target_extension,
+ bool show_programmatic_uninstall_ui) const = 0;
+
+ // Uninstalls the extension.
+ virtual bool UninstallExtension(content::BrowserContext* context,
+ const std::string& transient_extension_id,
+ UninstallReason reason,
+ const base::Closure& deletion_done_callback,
+ base::string16* error) const = 0;
+
+ // Creates an app shortcut.
+ virtual bool CreateAppShortcutFunctionDelegate(
+ ManagementCreateAppShortcutFunction* function,
+ const Extension* extension) const = 0;
+
+ // Forwards the call to launch_util::SetLaunchType in chrome.
+ virtual void SetLaunchType(content::BrowserContext* context,
+ const std::string& extension_id,
+ LaunchType launch_type) const = 0;
+
+ // Creates a bookmark app for |launch_url|.
+ virtual scoped_ptr<AppForLinkDelegate> GenerateAppForLinkFunctionDelegate(
+ ManagementGenerateAppForLinkFunction* function,
+ content::BrowserContext* context,
+ const std::string& title,
+ const GURL& launch_url) const = 0;
+
+ // Forwards the call to ExtensionIconSource::GetIconURL in chrome.
+ virtual GURL GetIconURL(const Extension* extension,
+ int icon_size,
+ ExtensionIconSet::MatchType match,
+ bool grayscale,
+ bool* exists) const = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_MANAGEMENT_MANAGEMENT_API_DELEGATER_H_
diff --git a/chromium/extensions/browser/api/messaging/OWNERS b/chromium/extensions/browser/api/messaging/OWNERS
new file mode 100644
index 00000000000..03bad0d6651
--- /dev/null
+++ b/chromium/extensions/browser/api/messaging/OWNERS
@@ -0,0 +1,2 @@
+kelvinp@chromium.org
+sergeyu@chromium.org
diff --git a/chromium/extensions/browser/api/messaging/native_message_host.cc b/chromium/extensions/browser/api/messaging/native_message_host.cc
new file mode 100644
index 00000000000..f3bb082f12c
--- /dev/null
+++ b/chromium/extensions/browser/api/messaging/native_message_host.cc
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/messaging/native_message_host.h"
+
+namespace extensions {
+
+const char NativeMessageHost::kFailedToStartError[] =
+ "Failed to start native messaging host.";
+const char NativeMessageHost::kInvalidNameError[] =
+ "Invalid native messaging host name specified.";
+const char NativeMessageHost::kNativeHostExited[] = "Native host has exited.";
+const char NativeMessageHost::kNotFoundError[] =
+ "Specified native messaging host not found.";
+const char NativeMessageHost::kForbiddenError[] =
+ "Access to the specified native messaging host is forbidden.";
+const char NativeMessageHost::kHostInputOuputError[] =
+ "Error when communicating with the native messaging host.";
+
+} // extensions
diff --git a/chromium/extensions/browser/api/messaging/native_message_host.h b/chromium/extensions/browser/api/messaging/native_message_host.h
new file mode 100644
index 00000000000..da241e06f83
--- /dev/null
+++ b/chromium/extensions/browser/api/messaging/native_message_host.h
@@ -0,0 +1,62 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_MESSAGING_NATIVE_MESSAGE_HOST_H_
+#define EXTENSIONS_BROWSER_API_MESSAGING_NATIVE_MESSAGE_HOST_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace extensions {
+
+// An interface for receiving messages from MessageService (Chrome) using the
+// Native Messaging API. A NativeMessageHost object hosts a native component,
+// which can run in the browser-process or in a separate process (See
+// NativeMessageProcessHost).
+class NativeMessageHost {
+ public:
+ static const char kFailedToStartError[];
+ static const char kInvalidNameError[];
+ static const char kNativeHostExited[];
+ static const char kNotFoundError[];
+ static const char kForbiddenError[];
+ static const char kHostInputOuputError[];
+
+ // Callback interface for receiving messages from the native host.
+ class Client {
+ public:
+ virtual ~Client() {}
+
+ // Called on the UI thread.
+ virtual void PostMessageFromNativeHost(const std::string& message) = 0;
+ virtual void CloseChannel(const std::string& error_message) = 0;
+ };
+
+ // Creates the NativeMessageHost based on the |native_host_name|.
+ static scoped_ptr<NativeMessageHost> Create(
+ gfx::NativeView native_view,
+ const std::string& source_extension_id,
+ const std::string& native_host_name,
+ bool allow_user_level,
+ std::string* error);
+
+ virtual ~NativeMessageHost() {}
+
+ // Called when a message is received from MessageService (Chrome).
+ virtual void OnMessage(const std::string& message) = 0;
+
+ // Sets the client to start receiving messages from the native host.
+ virtual void Start(Client* client) = 0;
+
+ // Returns the task runner that the host runs on. The Client should only
+ // invoke OnMessage() on this task runner.
+ virtual scoped_refptr<base::SingleThreadTaskRunner> task_runner() const = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_MESSAGING_NATIVE_MESSAGE_HOST_H_
diff --git a/chromium/extensions/browser/api/messaging/native_messaging_channel.h b/chromium/extensions/browser/api/messaging/native_messaging_channel.h
new file mode 100644
index 00000000000..b368706dad9
--- /dev/null
+++ b/chromium/extensions/browser/api/messaging/native_messaging_channel.h
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_MESSAGING_NATIVE_MESSAGING_CHANNEL_H_
+#define EXTENSIONS_BROWSER_API_MESSAGING_NATIVE_MESSAGING_CHANNEL_H_
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace base {
+class Value;
+} // namespace base
+
+namespace extensions {
+
+// An interface to receive and send messages between a native component and
+// chrome.
+class NativeMessagingChannel {
+ public:
+ // Callback interface for the channel. EventHandler must outlive
+ // NativeMessagingChannel.
+ class EventHandler {
+ public:
+ // Called when a message is received from the other endpoint.
+ virtual void OnMessage(scoped_ptr<base::Value> message) = 0;
+
+ // Called when the channel is disconnected.
+ // EventHandler is guaranteed not to be called after OnDisconnect().
+ virtual void OnDisconnect() = 0;
+
+ virtual ~EventHandler() {}
+ };
+
+ virtual ~NativeMessagingChannel() {}
+
+ // Starts reading and processing messages.
+ virtual void Start(EventHandler* event_handler) = 0;
+
+ // Sends a message to the other endpoint.
+ virtual void SendMessage(scoped_ptr<base::Value> message) = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_MESSAGING_NATIVE_MESSAGING_CHANNEL_H_
diff --git a/chromium/extensions/browser/api/mime_handler_private/OWNERS b/chromium/extensions/browser/api/mime_handler_private/OWNERS
new file mode 100644
index 00000000000..ae34e834807
--- /dev/null
+++ b/chromium/extensions/browser/api/mime_handler_private/OWNERS
@@ -0,0 +1 @@
+sammc@chromium.org
diff --git a/chromium/extensions/browser/api/mime_handler_private/mime_handler_private.cc b/chromium/extensions/browser/api/mime_handler_private/mime_handler_private.cc
new file mode 100644
index 00000000000..120363c8c47
--- /dev/null
+++ b/chromium/extensions/browser/api/mime_handler_private/mime_handler_private.cc
@@ -0,0 +1,125 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/mime_handler_private/mime_handler_private.h"
+
+#include <utility>
+
+#include "base/strings/string_util.h"
+#include "content/public/browser/stream_handle.h"
+#include "content/public/browser/stream_info.h"
+#include "content/public/common/content_constants.h"
+#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
+#include "extensions/common/constants.h"
+#include "mojo/public/cpp/bindings/map.h"
+#include "net/http/http_response_headers.h"
+
+namespace extensions {
+namespace {
+
+mojo::Map<mojo::String, mojo::String> CreateResponseHeadersMap(
+ const net::HttpResponseHeaders* headers) {
+ std::map<std::string, std::string> result;
+ if (!headers)
+ return mojo::Map<mojo::String, mojo::String>::From(result);
+
+ size_t iter = 0;
+ std::string header_name;
+ std::string header_value;
+ while (headers->EnumerateHeaderLines(&iter, &header_name, &header_value)) {
+ // mojo strings must be UTF-8 and headers might not be, so drop any headers
+ // that aren't ASCII. The PDF plugin does not use any headers with non-ASCII
+ // names and non-ASCII values are never useful for the headers the plugin
+ // does use.
+ //
+ // TODO(sammc): Send as bytes instead of a string and let the client decide
+ // how to decode.
+ if (!base::IsStringASCII(header_name) || !base::IsStringASCII(header_value))
+ continue;
+ auto& current_value = result[header_name];
+ if (!current_value.empty())
+ current_value += ", ";
+ current_value += header_value;
+ }
+ return mojo::Map<mojo::String, mojo::String>::From(result);
+}
+
+} // namespace
+
+// static
+void MimeHandlerServiceImpl::Create(
+ base::WeakPtr<StreamContainer> stream_container,
+ mojo::InterfaceRequest<mime_handler::MimeHandlerService> request) {
+ new MimeHandlerServiceImpl(stream_container, std::move(request));
+}
+
+MimeHandlerServiceImpl::MimeHandlerServiceImpl(
+ base::WeakPtr<StreamContainer> stream_container,
+ mojo::InterfaceRequest<mime_handler::MimeHandlerService> request)
+ : stream_(stream_container),
+ binding_(this, std::move(request)),
+ weak_factory_(this) {}
+
+MimeHandlerServiceImpl::~MimeHandlerServiceImpl() {
+}
+
+void MimeHandlerServiceImpl::GetStreamInfo(
+ const mojo::Callback<void(mime_handler::StreamInfoPtr)>& callback) {
+ if (!stream_) {
+ callback.Run(mime_handler::StreamInfoPtr());
+ return;
+ }
+ callback.Run(mojo::ConvertTo<mime_handler::StreamInfoPtr>(*stream_));
+}
+
+void MimeHandlerServiceImpl::AbortStream(
+ const mojo::Callback<void()>& callback) {
+ if (!stream_) {
+ callback.Run();
+ return;
+ }
+ stream_->Abort(base::Bind(&MimeHandlerServiceImpl::OnStreamClosed,
+ weak_factory_.GetWeakPtr(), callback));
+}
+
+void MimeHandlerServiceImpl::OnStreamClosed(
+ const mojo::Callback<void()>& callback) {
+ callback.Run();
+}
+
+} // namespace extensions
+
+namespace mojo {
+
+extensions::mime_handler::StreamInfoPtr TypeConverter<
+ extensions::mime_handler::StreamInfoPtr,
+ extensions::StreamContainer>::Convert(const extensions::StreamContainer&
+ stream) {
+ if (!stream.stream_info()->handle)
+ return extensions::mime_handler::StreamInfoPtr();
+
+ auto result = extensions::mime_handler::StreamInfo::New();
+ result->embedded = stream.embedded();
+ result->tab_id = stream.tab_id();
+ const content::StreamInfo* info = stream.stream_info();
+ result->mime_type = info->mime_type;
+
+ // If the URL is too long, mojo will give up on sending the URL. In these
+ // cases truncate it. Only data: URLs should ever really suffer this problem
+ // so only worry about those for now.
+ // TODO(raymes): This appears to be a bug in mojo somewhere. crbug.com/480099.
+ if (info->original_url.SchemeIs(url::kDataScheme) &&
+ info->original_url.spec().size() > content::kMaxURLDisplayChars) {
+ result->original_url = info->original_url.scheme() + ":";
+ } else {
+ result->original_url = info->original_url.spec();
+ }
+
+ result->stream_url = info->handle->GetURL().spec();
+ result->response_headers =
+ extensions::CreateResponseHeadersMap(info->response_headers.get());
+ return result;
+}
+
+} // namespace mojo
diff --git a/chromium/extensions/browser/api/mime_handler_private/mime_handler_private.h b/chromium/extensions/browser/api/mime_handler_private/mime_handler_private.h
new file mode 100644
index 00000000000..15e34e8117a
--- /dev/null
+++ b/chromium/extensions/browser/api/mime_handler_private/mime_handler_private.h
@@ -0,0 +1,59 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_MIME_HANDLER_PRIVATE_MIME_HANDLER_PRIVATE_H_
+#define EXTENSIONS_BROWSER_API_MIME_HANDLER_PRIVATE_MIME_HANDLER_PRIVATE_H_
+
+#include "base/memory/weak_ptr.h"
+#include "extensions/common/api/mime_handler.mojom.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+
+namespace extensions {
+class StreamContainer;
+class MimeHandlerServiceImplTest;
+
+class MimeHandlerServiceImpl : public mime_handler::MimeHandlerService {
+ public:
+ static void Create(
+ base::WeakPtr<StreamContainer> stream_container,
+ mojo::InterfaceRequest<mime_handler::MimeHandlerService> request);
+
+ private:
+ friend class MimeHandlerServiceImplTest;
+
+ MimeHandlerServiceImpl(
+ base::WeakPtr<StreamContainer> stream_container,
+ mojo::InterfaceRequest<mime_handler::MimeHandlerService> request);
+ ~MimeHandlerServiceImpl() override;
+
+ // mime_handler::MimeHandlerService overrides.
+ void GetStreamInfo(const mojo::Callback<void(mime_handler::StreamInfoPtr)>&
+ callback) override;
+ void AbortStream(const mojo::Callback<void()>& callback) override;
+
+ // Invoked by the callback used to abort |stream_|.
+ void OnStreamClosed(const mojo::Callback<void()>& callback);
+
+ // A handle to the stream being handled by the MimeHandlerViewGuest.
+ base::WeakPtr<StreamContainer> stream_;
+
+ mojo::StrongBinding<mime_handler::MimeHandlerService> binding_;
+
+ base::WeakPtrFactory<MimeHandlerServiceImpl> weak_factory_;
+};
+
+} // namespace extensions
+
+namespace mojo {
+
+template <>
+struct TypeConverter<extensions::mime_handler::StreamInfoPtr,
+ extensions::StreamContainer> {
+ static extensions::mime_handler::StreamInfoPtr Convert(
+ const extensions::StreamContainer& stream);
+};
+
+} // namespace mojo
+
+#endif // EXTENSIONS_BROWSER_API_MIME_HANDLER_PRIVATE_MIME_HANDLER_PRIVATE_H_
diff --git a/chromium/extensions/browser/api/mime_handler_private/mime_handler_private_unittest.cc b/chromium/extensions/browser/api/mime_handler_private/mime_handler_private_unittest.cc
new file mode 100644
index 00000000000..d88335890b7
--- /dev/null
+++ b/chromium/extensions/browser/api/mime_handler_private/mime_handler_private_unittest.cc
@@ -0,0 +1,113 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/mime_handler_private/mime_handler_private.h"
+
+#include <utility>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "content/public/browser/stream_handle.h"
+#include "content/public/browser/stream_info.h"
+#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+class TestStreamHandle : public content::StreamHandle {
+ public:
+ TestStreamHandle() : url_("stream://url") {}
+
+ ~TestStreamHandle() override {
+ while (!close_callbacks_.empty()) {
+ close_callbacks_.front().Run();
+ close_callbacks_.pop();
+ }
+ }
+
+ const GURL& GetURL() override { return url_; }
+
+ void AddCloseListener(const base::Closure& callback) override {
+ close_callbacks_.push(callback);
+ }
+
+ private:
+ GURL url_;
+ std::queue<base::Closure> close_callbacks_;
+};
+
+class MimeHandlerServiceImplTest : public testing::Test {
+ public:
+ void SetUp() override {
+ scoped_ptr<content::StreamInfo> stream_info(new content::StreamInfo);
+ stream_info->handle = make_scoped_ptr(new TestStreamHandle);
+ stream_info->mime_type = "test/unit";
+ stream_info->original_url = GURL("test://extensions_unittests");
+ stream_container_.reset(
+ new StreamContainer(std::move(stream_info), 1, true, GURL(), ""));
+ service_.reset(new MimeHandlerServiceImpl(stream_container_->GetWeakPtr(),
+ mojo::GetProxy(&service_ptr_)));
+ }
+ void TearDown() override {
+ service_.reset();
+ stream_container_.reset();
+ }
+
+ void AbortCallback() { abort_called_ = true; }
+ void GetStreamInfoCallback(mime_handler::StreamInfoPtr stream_info) {
+ stream_info_ = std::move(stream_info);
+ }
+
+ base::MessageLoop message_loop_;
+ scoped_ptr<StreamContainer> stream_container_;
+ mime_handler::MimeHandlerServicePtr service_ptr_;
+ scoped_ptr<mime_handler::MimeHandlerService> service_;
+ bool abort_called_ = false;
+ mime_handler::StreamInfoPtr stream_info_;
+};
+
+TEST_F(MimeHandlerServiceImplTest, Abort) {
+ service_->AbortStream(base::Bind(&MimeHandlerServiceImplTest::AbortCallback,
+ base::Unretained(this)));
+ EXPECT_TRUE(abort_called_);
+
+ abort_called_ = false;
+ service_->AbortStream(base::Bind(&MimeHandlerServiceImplTest::AbortCallback,
+ base::Unretained(this)));
+ EXPECT_TRUE(abort_called_);
+
+ stream_container_.reset();
+ abort_called_ = false;
+ service_->AbortStream(base::Bind(&MimeHandlerServiceImplTest::AbortCallback,
+ base::Unretained(this)));
+ EXPECT_TRUE(abort_called_);
+}
+
+TEST_F(MimeHandlerServiceImplTest, GetStreamInfo) {
+ service_->GetStreamInfo(
+ base::Bind(&MimeHandlerServiceImplTest::GetStreamInfoCallback,
+ base::Unretained(this)));
+ ASSERT_TRUE(stream_info_);
+ EXPECT_TRUE(stream_info_->embedded);
+ EXPECT_EQ(1, stream_info_->tab_id);
+ EXPECT_EQ("test/unit", stream_info_->mime_type);
+ EXPECT_EQ("test://extensions_unittests", stream_info_->original_url);
+ EXPECT_EQ("stream://url", stream_info_->stream_url);
+
+ service_->AbortStream(base::Bind(&MimeHandlerServiceImplTest::AbortCallback,
+ base::Unretained(this)));
+ EXPECT_TRUE(abort_called_);
+ service_->GetStreamInfo(
+ base::Bind(&MimeHandlerServiceImplTest::GetStreamInfoCallback,
+ base::Unretained(this)));
+ ASSERT_FALSE(stream_info_);
+
+ stream_container_.reset();
+ service_->GetStreamInfo(
+ base::Bind(&MimeHandlerServiceImplTest::GetStreamInfoCallback,
+ base::Unretained(this)));
+ ASSERT_FALSE(stream_info_);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/networking_config/OWNERS b/chromium/extensions/browser/api/networking_config/OWNERS
new file mode 100644
index 00000000000..17c27e6b3a6
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_config/OWNERS
@@ -0,0 +1,2 @@
+cschuet@chromium.org
+stevenjb@chromium.org
diff --git a/chromium/extensions/browser/api/networking_config/networking_config_api.cc b/chromium/extensions/browser/api/networking_config/networking_config_api.cc
new file mode 100644
index 00000000000..581e0d8b59a
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_config/networking_config_api.cc
@@ -0,0 +1,127 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/browser/api/networking_config/networking_config_api.h"
+#include "extensions/browser/api/networking_config/networking_config_service.h"
+#include "extensions/browser/api/networking_config/networking_config_service_factory.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+namespace {
+
+const char kAuthenticationResultFailed[] =
+ "Failed to set AuthenticationResult.";
+const char kMalformedFilterDescription[] = "Malformed filter description.";
+const char kMalformedFilterDescriptionWithSSID[] =
+ "Malformed filter description. Failed to register network with SSID "
+ "(hex): *";
+const char kUnsupportedNetworkType[] = "Unsupported network type.";
+
+} // namespace
+
+NetworkingConfigSetNetworkFilterFunction::
+ NetworkingConfigSetNetworkFilterFunction() {
+}
+
+ExtensionFunction::ResponseAction
+NetworkingConfigSetNetworkFilterFunction::Run() {
+ parameters_ =
+ api::networking_config::SetNetworkFilter::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters_.get());
+
+ NetworkingConfigService* service =
+ NetworkingConfigServiceFactory::GetForBrowserContext(browser_context());
+ DCHECK(service);
+
+ // Remove previously registered networks.
+ service->UnregisterExtension(extension_id());
+
+ for (const api::networking_config::NetworkInfo& ni : parameters_->networks) {
+ // |Type| field must be set to |WiFi|
+ if (ni.type != api::networking_config::NETWORK_TYPE_WIFI)
+ return RespondNow(Error(kUnsupportedNetworkType));
+
+ // Either |ssid| or |hex_ssid| must be set.
+ if (!ni.ssid.get() && !ni.hex_ssid.get())
+ return RespondNow(Error(kMalformedFilterDescription));
+
+ std::string hex_ssid;
+ if (ni.ssid.get()) {
+ auto ssid_field = ni.ssid.get();
+ hex_ssid = base::HexEncode(ssid_field->c_str(), ssid_field->size());
+ }
+ if (ni.hex_ssid.get())
+ hex_ssid = *ni.hex_ssid.get();
+
+ if (!service->RegisterHexSsid(hex_ssid, extension_id()))
+ return RespondNow(Error(kMalformedFilterDescriptionWithSSID, hex_ssid));
+ }
+
+ return RespondNow(NoArguments());
+}
+
+NetworkingConfigSetNetworkFilterFunction::
+ ~NetworkingConfigSetNetworkFilterFunction() {
+}
+
+NetworkingConfigFinishAuthenticationFunction::
+ NetworkingConfigFinishAuthenticationFunction() {
+}
+
+ExtensionFunction::ResponseAction
+NetworkingConfigFinishAuthenticationFunction::Run() {
+ parameters_ =
+ api::networking_config::FinishAuthentication::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters_.get());
+
+ NetworkingConfigService* service =
+ NetworkingConfigServiceFactory::GetForBrowserContext(browser_context());
+ DCHECK(service);
+
+ const NetworkingConfigService::AuthenticationResult& last_result =
+ service->GetAuthenticationResult();
+ if (last_result.authentication_state != NetworkingConfigService::NOTRY ||
+ last_result.guid != parameters_->guid) {
+ return RespondNow(Error(kAuthenticationResultFailed));
+ }
+
+ // Populate NetworkingCaptivePortalAPI::AuthenticationResult.
+ NetworkingConfigService::AuthenticationResult authentication_result = {
+ extension_id(), parameters_->guid, NetworkingConfigService::FAILED,
+ };
+ switch (parameters_->result) {
+ case api::networking_config::AUTHENTICATION_RESULT_NONE:
+ NOTREACHED();
+ break;
+ case api::networking_config::AUTHENTICATION_RESULT_UNHANDLED:
+ authentication_result.authentication_state =
+ NetworkingConfigService::FAILED;
+ break;
+ case api::networking_config::AUTHENTICATION_RESULT_REJECTED:
+ authentication_result.authentication_state =
+ NetworkingConfigService::REJECTED;
+ break;
+ case api::networking_config::AUTHENTICATION_RESULT_FAILED:
+ authentication_result.authentication_state =
+ NetworkingConfigService::FAILED;
+ break;
+ case api::networking_config::AUTHENTICATION_RESULT_SUCCEEDED:
+ authentication_result.authentication_state =
+ NetworkingConfigService::SUCCESS;
+ break;
+ }
+ service->SetAuthenticationResult(authentication_result);
+ return RespondNow(NoArguments());
+}
+
+NetworkingConfigFinishAuthenticationFunction::
+ ~NetworkingConfigFinishAuthenticationFunction() {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/networking_config/networking_config_api.h b/chromium/extensions/browser/api/networking_config/networking_config_api.h
new file mode 100644
index 00000000000..a5c8a0fa6bb
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_config/networking_config_api.h
@@ -0,0 +1,54 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_NETWORKING_CONFIG_NETWORKING_CONFIG_API_H_
+#define EXTENSIONS_BROWSER_API_NETWORKING_CONFIG_NETWORKING_CONFIG_API_H_
+
+#include "base/macros.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/common/api/networking_config.h"
+
+namespace extensions {
+
+class NetworkingConfigSetNetworkFilterFunction
+ : public UIThreadExtensionFunction {
+ public:
+ NetworkingConfigSetNetworkFilterFunction();
+
+ ResponseAction Run() override;
+
+ DECLARE_EXTENSION_FUNCTION("networking.config.setNetworkFilter",
+ NETWORKING_CONFIG_SETNETWORKFILTER);
+
+ protected:
+ ~NetworkingConfigSetNetworkFilterFunction() override;
+
+ scoped_ptr<api::networking_config::SetNetworkFilter::Params> parameters_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetworkingConfigSetNetworkFilterFunction);
+};
+
+class NetworkingConfigFinishAuthenticationFunction
+ : public UIThreadExtensionFunction {
+ public:
+ NetworkingConfigFinishAuthenticationFunction();
+
+ ResponseAction Run() override;
+
+ DECLARE_EXTENSION_FUNCTION("networking.config.finishAuthentication",
+ NETWORKING_CONFIG_FINISHAUTHENTICATION);
+
+ protected:
+ ~NetworkingConfigFinishAuthenticationFunction() override;
+
+ scoped_ptr<api::networking_config::FinishAuthentication::Params> parameters_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetworkingConfigFinishAuthenticationFunction);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_NETWORKING_CONFIG_NETWORKING_CONFIG_API_H_
diff --git a/chromium/extensions/browser/api/networking_config/networking_config_chromeos_apitest.cc b/chromium/extensions/browser/api/networking_config/networking_config_chromeos_apitest.cc
new file mode 100644
index 00000000000..a87ed7b0cff
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_config/networking_config_chromeos_apitest.cc
@@ -0,0 +1,27 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "chrome/browser/extensions/extension_apitest.h"
+
+namespace {
+
+class NetworkingConfigTest : public ExtensionApiTest {
+ public:
+ NetworkingConfigTest() {}
+};
+
+} // namespace
+
+IN_PROC_BROWSER_TEST_F(NetworkingConfigTest, ApiAvailability) {
+ ASSERT_TRUE(RunExtensionSubtest("networking_config", "api_availability.html"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(NetworkingConfigTest, RegisterNetworks) {
+ ASSERT_TRUE(
+ RunExtensionSubtest("networking_config", "register_networks.html"))
+ << message_;
+}
diff --git a/chromium/extensions/browser/api/networking_config/networking_config_service.cc b/chromium/extensions/browser/api/networking_config/networking_config_service.cc
new file mode 100644
index 00000000000..0f14345bbf9
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_config/networking_config_service.cc
@@ -0,0 +1,234 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/networking_config/networking_config_service.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "chromeos/network/managed_network_configuration_handler.h"
+#include "chromeos/network/network_handler.h"
+#include "chromeos/network/network_state.h"
+#include "chromeos/network/network_state_handler.h"
+#include "extensions/common/api/networking_config.h"
+
+namespace extensions {
+
+namespace {
+
+bool IsValidNonEmptyHexString(const std::string& input) {
+ size_t count = input.size();
+ if (count == 0 || (count % 2) != 0)
+ return false;
+ for (const char& c : input)
+ if (!base::IsHexDigit<char>(c))
+ return false;
+ return true;
+}
+
+} // namespace
+
+NetworkingConfigService::AuthenticationResult::AuthenticationResult()
+ : authentication_state(NetworkingConfigService::NOTRY) {
+}
+
+NetworkingConfigService::AuthenticationResult::AuthenticationResult(
+ ExtensionId extension_id,
+ std::string guid,
+ AuthenticationState authentication_state)
+ : extension_id(extension_id),
+ guid(guid),
+ authentication_state(authentication_state) {
+}
+
+NetworkingConfigService::NetworkingConfigService(
+ content::BrowserContext* browser_context,
+ scoped_ptr<EventDelegate> event_delegate,
+ ExtensionRegistry* extension_registry)
+ : browser_context_(browser_context),
+ registry_observer_(this),
+ event_delegate_(std::move(event_delegate)),
+ weak_factory_(this) {
+ registry_observer_.Add(extension_registry);
+}
+
+NetworkingConfigService::~NetworkingConfigService() {
+}
+
+void NetworkingConfigService::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ UnregisterExtension(extension->id());
+}
+
+std::string NetworkingConfigService::LookupExtensionIdForHexSsid(
+ std::string hex_ssid) const {
+ // Transform hex_ssid to uppercase.
+ transform(hex_ssid.begin(), hex_ssid.end(), hex_ssid.begin(), toupper);
+
+ const auto it = hex_ssid_to_extension_id_.find(hex_ssid);
+ if (it == hex_ssid_to_extension_id_.end())
+ return std::string();
+ return it->second;
+}
+
+bool NetworkingConfigService::IsRegisteredForCaptivePortalEvent(
+ const std::string& extension_id) const {
+ return event_delegate_->HasExtensionRegisteredForEvent(extension_id);
+}
+
+bool NetworkingConfigService::RegisterHexSsid(std::string hex_ssid,
+ const std::string& extension_id) {
+ if (!IsValidNonEmptyHexString(hex_ssid)) {
+ LOG(ERROR) << "\'" << hex_ssid << "\' is not a valid hex encoded string.";
+ return false;
+ }
+
+ // Transform hex_ssid to uppercase.
+ transform(hex_ssid.begin(), hex_ssid.end(), hex_ssid.begin(), toupper);
+
+ return hex_ssid_to_extension_id_.insert(make_pair(hex_ssid, extension_id))
+ .second;
+}
+
+void NetworkingConfigService::UnregisterExtension(
+ const std::string& extension_id) {
+ for (auto it = hex_ssid_to_extension_id_.begin();
+ it != hex_ssid_to_extension_id_.end();) {
+ if (it->second == extension_id)
+ hex_ssid_to_extension_id_.erase(it++);
+ else
+ ++it;
+ }
+}
+
+const NetworkingConfigService::AuthenticationResult&
+NetworkingConfigService::GetAuthenticationResult() const {
+ return authentication_result_;
+}
+
+void NetworkingConfigService::ResetAuthenticationResult() {
+ authentication_result_ = AuthenticationResult();
+ authentication_callback_.Reset();
+}
+
+void NetworkingConfigService::SetAuthenticationResult(
+ const AuthenticationResult& authentication_result) {
+ authentication_result_ = authentication_result;
+ if (authentication_result.authentication_state != SUCCESS) {
+ LOG(WARNING) << "Captive Portal Authentication failed.";
+ return;
+ }
+
+ if (!authentication_callback_.is_null()) {
+ authentication_callback_.Run();
+ authentication_callback_.Reset();
+ }
+}
+
+void NetworkingConfigService::OnGotProperties(
+ const std::string& extension_id,
+ const std::string& guid,
+ const base::Closure& authentication_callback,
+ const std::string& service_path,
+ const base::DictionaryValue& onc_network_config) {
+ authentication_result_ = NetworkingConfigService::AuthenticationResult(
+ std::string(), guid, NetworkingConfigService::NOTRY);
+ authentication_callback_ = authentication_callback;
+
+ // Try to extract |bssid| field.
+ const base::DictionaryValue* wifi_with_state = nullptr;
+ std::string bssid;
+ scoped_ptr<Event> event;
+ if (onc_network_config.GetDictionaryWithoutPathExpansion(
+ ::onc::network_config::kWiFi, &wifi_with_state) &&
+ wifi_with_state->GetStringWithoutPathExpansion(::onc::wifi::kBSSID,
+ &bssid)) {
+ event = CreatePortalDetectedEventAndDispatch(extension_id, guid, &bssid);
+ } else {
+ event = CreatePortalDetectedEventAndDispatch(extension_id, guid, nullptr);
+ }
+
+ EventRouter::Get(browser_context_)
+ ->DispatchEventToExtension(extension_id, std::move(event));
+}
+
+void NetworkingConfigService::OnGetPropertiesFailed(
+ const std::string& extension_id,
+ const std::string& guid,
+ const std::string& error_name,
+ scoped_ptr<base::DictionaryValue> error_data) {
+ LOG(WARNING) << "Failed to determine BSSID for network with guid " << guid
+ << ": " << error_name;
+ scoped_ptr<Event> event =
+ CreatePortalDetectedEventAndDispatch(extension_id, guid, nullptr);
+ EventRouter::Get(browser_context_)
+ ->DispatchEventToExtension(extension_id, std::move(event));
+}
+
+scoped_ptr<Event> NetworkingConfigService::CreatePortalDetectedEventAndDispatch(
+ const std::string& extension_id,
+ const std::string& guid,
+ const std::string* bssid) {
+ const chromeos::NetworkState* network = chromeos::NetworkHandler::Get()
+ ->network_state_handler()
+ ->GetNetworkStateFromGuid(guid);
+ if (!network)
+ return nullptr;
+
+ // Populate the NetworkInfo object.
+ api::networking_config::NetworkInfo network_info;
+ network_info.type = api::networking_config::NETWORK_TYPE_WIFI;
+ const std::vector<uint8_t>& raw_ssid = network->raw_ssid();
+ std::string hex_ssid = base::HexEncode(raw_ssid.data(), raw_ssid.size());
+ network_info.hex_ssid = make_scoped_ptr(new std::string(hex_ssid));
+ network_info.ssid = make_scoped_ptr(new std::string(network->name()));
+ network_info.guid = make_scoped_ptr(new std::string(network->guid()));
+ if (bssid)
+ network_info.bssid.reset(new std::string(*bssid));
+ scoped_ptr<base::ListValue> results =
+ api::networking_config::OnCaptivePortalDetected::Create(network_info);
+ scoped_ptr<Event> event(
+ new Event(events::NETWORKING_CONFIG_ON_CAPTIVE_PORTAL_DETECTED,
+ api::networking_config::OnCaptivePortalDetected::kEventName,
+ std::move(results)));
+ return event;
+}
+
+void NetworkingConfigService::DispatchPortalDetectedEvent(
+ const std::string& extension_id,
+ const std::string& guid,
+ const base::Closure& authentication_callback) {
+ // This dispatch starts a new cycle of portal detection and authentication.
+ // Ensure that any old |authentication_callback_| is dropped.
+ authentication_callback_.Reset();
+
+ // Determine |service_path| of network identified by |guid|.
+ chromeos::NetworkHandler* network_handler = chromeos::NetworkHandler::Get();
+ const chromeos::NetworkState* network =
+ network_handler->network_state_handler()->GetNetworkStateFromGuid(guid);
+ if (!network)
+ return;
+ const std::string service_path = network->path();
+
+ // We do not provide |userhash| here because we only care about properties
+ // that are not affected by policy, i.e BSSID.
+ network_handler->managed_network_configuration_handler()->GetProperties(
+ "" /* empty userhash */, service_path,
+ base::Bind(&NetworkingConfigService::OnGotProperties,
+ weak_factory_.GetWeakPtr(), extension_id, guid,
+ authentication_callback),
+ base::Bind(&NetworkingConfigService::OnGetPropertiesFailed,
+ weak_factory_.GetWeakPtr(), extension_id, guid));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/networking_config/networking_config_service.h b/chromium/extensions/browser/api/networking_config/networking_config_service.h
new file mode 100644
index 00000000000..fae6c68fe7e
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_config/networking_config_service.h
@@ -0,0 +1,148 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_NETWORKING_CONFIG_NETWORKING_CONFIG_SERVICE_H_
+#define EXTENSIONS_BROWSER_API_NETWORKING_CONFIG_NETWORKING_CONFIG_SERVICE_H_
+
+#include <map>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/scoped_observer.h"
+#include "base/values.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_registry_observer.h"
+
+namespace extensions {
+
+// This class provides the session-scoped storage backing the networking config
+// extension API. Currently only the parts relevant for captive portal handling
+// are implemented.
+class NetworkingConfigService : public ExtensionRegistryObserver,
+ public KeyedService {
+ public:
+ class EventDelegate {
+ public:
+ EventDelegate() {}
+ virtual ~EventDelegate() {}
+ virtual bool HasExtensionRegisteredForEvent(
+ const std::string& extension_id) const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EventDelegate);
+ };
+
+ // Indicates the authentication state of the portal.
+ enum AuthenticationState { NOTRY, TRYING, SUCCESS, REJECTED, FAILED };
+
+ // Provides information about the current authentication state of the portal.
+ struct AuthenticationResult {
+ AuthenticationResult();
+ AuthenticationResult(ExtensionId extension_id,
+ std::string guid,
+ AuthenticationState authentication_state);
+ ExtensionId extension_id;
+ std::string guid;
+ AuthenticationState authentication_state;
+ };
+
+ // Note: |extension_registry| must outlive this class.
+ NetworkingConfigService(content::BrowserContext* browser_context,
+ scoped_ptr<EventDelegate> event_delegate,
+ ExtensionRegistry* extension_registry);
+ ~NetworkingConfigService() override;
+
+ // ExtensionRegistryObserver
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+
+ // Returns the extension id registered for |hex_ssid|. If no extension is
+ // registered for this |hex_ssid|, the function returns an empty string.
+ // |hex_ssid|: SSID in hex encoding.
+ std::string LookupExtensionIdForHexSsid(std::string hex_ssid) const;
+
+ // Returns true if the extension with id |extension_id| registered for
+ // |onCaptivePortalDetected| events, otherwise false.
+ bool IsRegisteredForCaptivePortalEvent(const std::string& extension_id) const;
+
+ // Registers |hex_ssid| as being handled by the extension with extension ID
+ // |extension_id|. Returns true on success and false if another extension
+ // already registered for |hex_ssid|.
+ // |hex_ssid|: SSID in hex encoding of the network to be registered.
+ // |extension_id|: Extension ID of the extension handling the network
+ // configuration for this network.
+ bool RegisterHexSsid(std::string hex_ssid, const std::string& extension_id);
+
+ // Unregisters extension with the ID |extension_id| removing all associated
+ // HexSSIDs from the map.
+ // |extension_id|: ID identifying the extenion to be removed
+ void UnregisterExtension(const std::string& extensionId);
+
+ // Returns the current AuthenticationResult.
+ const AuthenticationResult& GetAuthenticationResult() const;
+
+ // Sets the authentication_state to NOTRY and clears all other fields.
+ void ResetAuthenticationResult();
+
+ // Sets the current AuthenticationResult.
+ void SetAuthenticationResult(
+ const AuthenticationResult& authentication_result);
+
+ // Sends a PortalDetected event for the network with |guid| to the extension
+ // with |extension_id|.
+ // |authentication_callback| is stored and called if the extension finishes
+ // authentication succesfully. This Service handles at most one portal
+ // detection at a time, i.e. another call to this function or a not successful
+ // authentication will drop a previously provided |authentication_callback|.
+ void DispatchPortalDetectedEvent(
+ const std::string& extension_id,
+ const std::string& guid,
+ const base::Closure& authentication_callback);
+
+ private:
+ void OnGotProperties(const std::string& extension_id,
+ const std::string& guid,
+ const base::Closure& authentication_callback,
+ const std::string& service_path,
+ const base::DictionaryValue& onc_network_config);
+
+ void OnGetPropertiesFailed(const std::string& extension_id,
+ const std::string& guid,
+ const std::string& error_name,
+ scoped_ptr<base::DictionaryValue> error_data);
+
+ // Creates the captive portal event about the network with guid |guid| that is
+ // to be dispatched to the extension identified by |extension_id|. |bssid|
+ // contains a human readable, hex-encoded version of the BSSID with bytes
+ // separated by colons, e.g. 45:67:89:ab:cd:ef.
+ scoped_ptr<Event> CreatePortalDetectedEventAndDispatch(
+ const std::string& extension_id,
+ const std::string& guid,
+ const std::string* bssid);
+
+ content::BrowserContext* const browser_context_;
+
+ AuthenticationResult authentication_result_;
+ base::Closure authentication_callback_;
+
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ registry_observer_;
+
+ scoped_ptr<EventDelegate> event_delegate_;
+
+ // This map associates a given hex encoded SSID to an extension entry.
+ std::map<std::string, std::string> hex_ssid_to_extension_id_;
+
+ base::WeakPtrFactory<NetworkingConfigService> weak_factory_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_NETWORKING_CONFIG_NETWORKING_CONFIG_SERVICE_H_
diff --git a/chromium/extensions/browser/api/networking_config/networking_config_service_chromeos_unittest.cc b/chromium/extensions/browser/api/networking_config/networking_config_service_chromeos_unittest.cc
new file mode 100644
index 00000000000..93bab3a429a
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_config/networking_config_service_chromeos_unittest.cc
@@ -0,0 +1,75 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/networking_config/networking_config_service.h"
+
+#include <utility>
+
+#include "extensions/browser/api_unittest.h"
+#include "extensions/browser/extension_registry.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+const char kExtensionId[] = "necdpnkfgondfageiompbacibhgmfebg";
+const char kHexSsid[] = "54657374535349445F5A5A5A5A";
+const char kHexSsidLower[] = "54657374535349445f5a5a5a5a";
+
+class MockEventDelegate : public NetworkingConfigService::EventDelegate {
+ public:
+ MockEventDelegate() : extension_registered_(false) {}
+ ~MockEventDelegate() override {}
+
+ bool HasExtensionRegisteredForEvent(
+ const std::string& extension_id) const override {
+ return extension_registered_;
+ }
+
+ void SetExtensionRegisteredForEvent(bool extension_registered) {
+ extension_registered_ = extension_registered;
+ }
+
+ private:
+ bool extension_registered_;
+};
+
+} // namespace
+
+class NetworkingConfigServiceTest : public ApiUnitTest {
+ public:
+ NetworkingConfigServiceTest() {}
+ ~NetworkingConfigServiceTest() override {}
+
+ void SetUp() override {
+ ApiUnitTest::SetUp();
+ extension_registry_ = scoped_ptr<ExtensionRegistry>(
+ new ExtensionRegistry(browser_context()));
+ scoped_ptr<MockEventDelegate> mock_event_delegate =
+ scoped_ptr<MockEventDelegate>(new MockEventDelegate());
+ service_ = scoped_ptr<NetworkingConfigService>(new NetworkingConfigService(
+ browser_context(), std::move(mock_event_delegate),
+ extension_registry_.get()));
+ DCHECK(service_);
+ }
+
+ protected:
+ scoped_ptr<ExtensionRegistry> extension_registry_;
+ scoped_ptr<NetworkingConfigService> service_;
+};
+
+TEST_F(NetworkingConfigServiceTest, BasicRegisterHexSsid) {
+ EXPECT_TRUE(service_->RegisterHexSsid(kHexSsid, kExtensionId));
+ EXPECT_EQ(kExtensionId, service_->LookupExtensionIdForHexSsid(kHexSsid));
+ EXPECT_EQ(kExtensionId, service_->LookupExtensionIdForHexSsid(kHexSsidLower));
+}
+
+TEST_F(NetworkingConfigServiceTest, BasicRegisterHexSsidLower) {
+ EXPECT_TRUE(service_->RegisterHexSsid(kHexSsidLower, kExtensionId));
+ EXPECT_EQ(kExtensionId, service_->LookupExtensionIdForHexSsid(kHexSsid));
+ EXPECT_EQ(kExtensionId, service_->LookupExtensionIdForHexSsid(kHexSsidLower));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/networking_config/networking_config_service_factory.cc b/chromium/extensions/browser/api/networking_config/networking_config_service_factory.cc
new file mode 100644
index 00000000000..38b9f902a24
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_config/networking_config_service_factory.cc
@@ -0,0 +1,83 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/networking_config/networking_config_service_factory.h"
+
+#include <string>
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "extensions/browser/api/networking_config/networking_config_service.h"
+#include "extensions/browser/extension_registry_factory.h"
+#include "extensions/browser/extension_system_provider.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/common/api/networking_config.h"
+
+namespace extensions {
+
+namespace {
+
+class DefaultEventDelegate : public NetworkingConfigService::EventDelegate {
+ public:
+ explicit DefaultEventDelegate(content::BrowserContext* context);
+ ~DefaultEventDelegate() override;
+
+ bool HasExtensionRegisteredForEvent(
+ const std::string& extension_id) const override;
+
+ private:
+ content::BrowserContext* const context_;
+};
+
+DefaultEventDelegate::DefaultEventDelegate(content::BrowserContext* context)
+ : context_(context) {
+}
+
+DefaultEventDelegate::~DefaultEventDelegate() {
+}
+
+bool DefaultEventDelegate::HasExtensionRegisteredForEvent(
+ const std::string& extension_id) const {
+ return EventRouter::Get(context_)->ExtensionHasEventListener(
+ extension_id,
+ api::networking_config::OnCaptivePortalDetected::kEventName);
+}
+
+} // namespace
+
+// static
+NetworkingConfigService* NetworkingConfigServiceFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<NetworkingConfigService*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+NetworkingConfigServiceFactory* NetworkingConfigServiceFactory::GetInstance() {
+ return base::Singleton<NetworkingConfigServiceFactory>::get();
+}
+
+NetworkingConfigServiceFactory::NetworkingConfigServiceFactory()
+ : BrowserContextKeyedServiceFactory(
+ "NetworkingConfigService",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
+ DependsOn(extensions::ExtensionRegistryFactory::GetInstance());
+}
+
+NetworkingConfigServiceFactory::~NetworkingConfigServiceFactory() {
+}
+
+KeyedService* NetworkingConfigServiceFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ return new NetworkingConfigService(
+ context, make_scoped_ptr(new DefaultEventDelegate(context)),
+ ExtensionRegistry::Get(context));
+}
+
+content::BrowserContext* NetworkingConfigServiceFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/networking_config/networking_config_service_factory.h b/chromium/extensions/browser/api/networking_config/networking_config_service_factory.h
new file mode 100644
index 00000000000..b5939015b85
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_config/networking_config_service_factory.h
@@ -0,0 +1,41 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_NETWORKING_CONFIG_NETWORKING_CONFIG_SERVICE_FACTORY_H_
+#define EXTENSIONS_BROWSER_API_NETWORKING_CONFIG_NETWORKING_CONFIG_SERVICE_FACTORY_H_
+
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+namespace extensions {
+class NetworkingConfigService;
+
+class NetworkingConfigServiceFactory
+ : public BrowserContextKeyedServiceFactory {
+ public:
+ static NetworkingConfigService* GetForBrowserContext(
+ content::BrowserContext* context);
+
+ static NetworkingConfigServiceFactory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<NetworkingConfigServiceFactory>;
+
+ NetworkingConfigServiceFactory();
+ ~NetworkingConfigServiceFactory() override;
+
+ // BrowserContextKeyedBaseFactory
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_NETWORKING_CONFIG_NETWORKING_CONFIG_SERVICE_FACTORY_H_
diff --git a/chromium/extensions/browser/api/networking_private/DEPS b/chromium/extensions/browser/api/networking_private/DEPS
new file mode 100644
index 00000000000..a64ac148c4f
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/DEPS
@@ -0,0 +1,7 @@
+include_rules = [
+ "+components/onc",
+ "+components/wifi",
+ "+chromeos",
+ "+dbus",
+ "+third_party/cros_system_api"
+]
diff --git a/chromium/extensions/browser/api/networking_private/OWNERS b/chromium/extensions/browser/api/networking_private/OWNERS
new file mode 100644
index 00000000000..aa215c7d505
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/OWNERS
@@ -0,0 +1 @@
+stevenjb@chromium.org
diff --git a/chromium/extensions/browser/api/networking_private/network_config_dbus_constants_linux.cc b/chromium/extensions/browser/api/networking_private/network_config_dbus_constants_linux.cc
new file mode 100644
index 00000000000..4acb2067903
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/network_config_dbus_constants_linux.cc
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/networking_private/network_config_dbus_constants_linux.h"
+
+namespace extensions {
+
+namespace networking_private {
+
+// Network manager API strings.
+const char kNetworkManagerPath[] = "/org/freedesktop/NetworkManager";
+const char kNetworkManagerNamespace[] = "org.freedesktop.NetworkManager";
+const char kNetworkManagerAccessPointNamespace[] =
+ "org.freedesktop.NetworkManager.AccessPoint";
+const char kNetworkManagerActiveConnectionNamespace[] =
+ "org.freedesktop.NetworkManager.Connection.Active";
+const char kNetworkManagerDeviceNamespace[] =
+ "org.freedesktop.NetworkManager.Device";
+const char kNetworkManagerWirelessDeviceNamespace[] =
+ "org.freedesktop.NetworkManager.Device.Wireless";
+const char kNetworkManagerActiveConnections[] = "ActiveConnections";
+const char kNetworkManagerSpecificObject[] = "SpecificObject";
+const char kNetworkManagerDeviceType[] = "DeviceType";
+const char kNetworkManagerGetDevicesMethod[] = "GetDevices";
+const char kNetworkManagerGetAccessPointsMethod[] = "GetAccessPoints";
+const char kNetworkManagerDisconnectMethod[] = "Disconnect";
+const char kNetworkManagerAddAndActivateConnectionMethod[] =
+ "AddAndActivateConnection";
+const char kNetworkManagerGetMethod[] = "Get";
+const char kNetworkManagerSsidProperty[] = "Ssid";
+const char kNetworkManagerStrengthProperty[] = "Strength";
+const char kNetworkManagerRsnFlagsProperty[] = "RsnFlags";
+const char kNetworkManagerWpaFlagsProperty[] = "WpaFlags";
+
+// Network manager connection configuration strings.
+const char kNetworkManagerConnectionConfig80211Wireless[] = "802-11-wireless";
+const char kNetworkManagerConnectionConfigSsid[] = "ssid";
+
+} // namespace networking_private
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/networking_private/network_config_dbus_constants_linux.h b/chromium/extensions/browser/api/networking_private/network_config_dbus_constants_linux.h
new file mode 100644
index 00000000000..91a864ce790
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/network_config_dbus_constants_linux.h
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORK_CONFIG_DBUS_CONSTANTS_LINUX_H_
+#define EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORK_CONFIG_DBUS_CONSTANTS_LINUX_H_
+
+namespace extensions {
+
+namespace networking_private {
+
+// Network manager API strings.
+extern const char kNetworkManagerPath[];
+extern const char kNetworkManagerNamespace[];
+extern const char kNetworkManagerAccessPointNamespace[];
+extern const char kNetworkManagerActiveConnectionNamespace[];
+extern const char kNetworkManagerDeviceNamespace[];
+extern const char kNetworkManagerWirelessDeviceNamespace[];
+extern const char kNetworkManagerActiveConnections[];
+extern const char kNetworkManagerSpecificObject[];
+extern const char kNetworkManagerDeviceType[];
+extern const char kNetworkManagerGetDevicesMethod[];
+extern const char kNetworkManagerGetAccessPointsMethod[];
+extern const char kNetworkManagerDisconnectMethod[];
+extern const char kNetworkManagerAddAndActivateConnectionMethod[];
+extern const char kNetworkManagerGetMethod[];
+extern const char kNetworkManagerSsidProperty[];
+extern const char kNetworkManagerStrengthProperty[];
+extern const char kNetworkManagerRsnFlagsProperty[];
+extern const char kNetworkManagerWpaFlagsProperty[];
+
+// Network manager connection configuration strings.
+extern const char kNetworkManagerConnectionConfig80211Wireless[];
+extern const char kNetworkManagerConnectionConfigSsid[];
+
+} // namespace networking_private
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORK_CONFIG_DBUS_CONSTANTS_LINUX_H_
diff --git a/chromium/extensions/browser/api/networking_private/networking_private_api.cc b/chromium/extensions/browser/api/networking_private/networking_private_api.cc
new file mode 100644
index 00000000000..4e6368fc054
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/networking_private_api.cc
@@ -0,0 +1,773 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/networking_private/networking_private_api.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "components/onc/onc_constants.h"
+#include "extensions/browser/api/networking_private/networking_private_delegate.h"
+#include "extensions/browser/api/networking_private/networking_private_delegate_factory.h"
+#include "extensions/browser/extension_function_registry.h"
+#include "extensions/common/api/networking_private.h"
+
+namespace {
+
+const int kDefaultNetworkListLimit = 1000;
+
+extensions::NetworkingPrivateDelegate* GetDelegate(
+ content::BrowserContext* browser_context) {
+ return extensions::NetworkingPrivateDelegateFactory::GetForBrowserContext(
+ browser_context);
+}
+
+} // namespace
+
+namespace extensions {
+
+namespace private_api = api::networking_private;
+
+namespace networking_private {
+
+// static
+const char kErrorInvalidNetworkGuid[] = "Error.InvalidNetworkGuid";
+const char kErrorInvalidNetworkOperation[] = "Error.InvalidNetworkOperation";
+const char kErrorNetworkUnavailable[] = "Error.NetworkUnavailable";
+const char kErrorEncryptionError[] = "Error.EncryptionError";
+const char kErrorNotReady[] = "Error.NotReady";
+const char kErrorNotSupported[] = "Error.NotSupported";
+const char kErrorSimLocked[] = "Error.SimLocked";
+
+} // namespace networking_private
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateGetPropertiesFunction
+
+NetworkingPrivateGetPropertiesFunction::
+ ~NetworkingPrivateGetPropertiesFunction() {
+}
+
+bool NetworkingPrivateGetPropertiesFunction::RunAsync() {
+ scoped_ptr<private_api::GetProperties::Params> params =
+ private_api::GetProperties::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ GetDelegate(browser_context())
+ ->GetProperties(
+ params->network_guid,
+ base::Bind(&NetworkingPrivateGetPropertiesFunction::Success, this),
+ base::Bind(&NetworkingPrivateGetPropertiesFunction::Failure, this));
+ return true;
+}
+
+void NetworkingPrivateGetPropertiesFunction::Success(
+ scoped_ptr<base::DictionaryValue> result) {
+ SetResult(result.release());
+ SendResponse(true);
+}
+
+void NetworkingPrivateGetPropertiesFunction::Failure(const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateGetManagedPropertiesFunction
+
+NetworkingPrivateGetManagedPropertiesFunction::
+ ~NetworkingPrivateGetManagedPropertiesFunction() {
+}
+
+bool NetworkingPrivateGetManagedPropertiesFunction::RunAsync() {
+ scoped_ptr<private_api::GetManagedProperties::Params> params =
+ private_api::GetManagedProperties::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ GetDelegate(browser_context())
+ ->GetManagedProperties(
+ params->network_guid,
+ base::Bind(&NetworkingPrivateGetManagedPropertiesFunction::Success,
+ this),
+ base::Bind(&NetworkingPrivateGetManagedPropertiesFunction::Failure,
+ this));
+ return true;
+}
+
+void NetworkingPrivateGetManagedPropertiesFunction::Success(
+ scoped_ptr<base::DictionaryValue> result) {
+ SetResult(result.release());
+ SendResponse(true);
+}
+
+void NetworkingPrivateGetManagedPropertiesFunction::Failure(
+ const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateGetStateFunction
+
+NetworkingPrivateGetStateFunction::~NetworkingPrivateGetStateFunction() {
+}
+
+bool NetworkingPrivateGetStateFunction::RunAsync() {
+ scoped_ptr<private_api::GetState::Params> params =
+ private_api::GetState::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ GetDelegate(browser_context())
+ ->GetState(params->network_guid,
+ base::Bind(&NetworkingPrivateGetStateFunction::Success, this),
+ base::Bind(&NetworkingPrivateGetStateFunction::Failure, this));
+ return true;
+}
+
+void NetworkingPrivateGetStateFunction::Success(
+ scoped_ptr<base::DictionaryValue> result) {
+ SetResult(result.release());
+ SendResponse(true);
+}
+
+void NetworkingPrivateGetStateFunction::Failure(const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateSetPropertiesFunction
+
+NetworkingPrivateSetPropertiesFunction::
+ ~NetworkingPrivateSetPropertiesFunction() {
+}
+
+bool NetworkingPrivateSetPropertiesFunction::RunAsync() {
+ scoped_ptr<private_api::SetProperties::Params> params =
+ private_api::SetProperties::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ scoped_ptr<base::DictionaryValue> properties_dict(
+ params->properties.ToValue());
+
+ GetDelegate(browser_context())
+ ->SetProperties(
+ params->network_guid, std::move(properties_dict),
+ base::Bind(&NetworkingPrivateSetPropertiesFunction::Success, this),
+ base::Bind(&NetworkingPrivateSetPropertiesFunction::Failure, this));
+ return true;
+}
+
+void NetworkingPrivateSetPropertiesFunction::Success() {
+ SendResponse(true);
+}
+
+void NetworkingPrivateSetPropertiesFunction::Failure(const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateCreateNetworkFunction
+
+NetworkingPrivateCreateNetworkFunction::
+ ~NetworkingPrivateCreateNetworkFunction() {
+}
+
+bool NetworkingPrivateCreateNetworkFunction::RunAsync() {
+ scoped_ptr<private_api::CreateNetwork::Params> params =
+ private_api::CreateNetwork::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ scoped_ptr<base::DictionaryValue> properties_dict(
+ params->properties.ToValue());
+
+ GetDelegate(browser_context())
+ ->CreateNetwork(
+ params->shared, std::move(properties_dict),
+ base::Bind(&NetworkingPrivateCreateNetworkFunction::Success, this),
+ base::Bind(&NetworkingPrivateCreateNetworkFunction::Failure, this));
+ return true;
+}
+
+void NetworkingPrivateCreateNetworkFunction::Success(const std::string& guid) {
+ results_ = private_api::CreateNetwork::Results::Create(guid);
+ SendResponse(true);
+}
+
+void NetworkingPrivateCreateNetworkFunction::Failure(const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateForgetNetworkFunction
+
+NetworkingPrivateForgetNetworkFunction::
+ ~NetworkingPrivateForgetNetworkFunction() {
+}
+
+bool NetworkingPrivateForgetNetworkFunction::RunAsync() {
+ scoped_ptr<private_api::ForgetNetwork::Params> params =
+ private_api::ForgetNetwork::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ GetDelegate(browser_context())
+ ->ForgetNetwork(
+ params->network_guid,
+ base::Bind(&NetworkingPrivateForgetNetworkFunction::Success, this),
+ base::Bind(&NetworkingPrivateForgetNetworkFunction::Failure, this));
+ return true;
+}
+
+void NetworkingPrivateForgetNetworkFunction::Success() {
+ SendResponse(true);
+}
+
+void NetworkingPrivateForgetNetworkFunction::Failure(const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateGetNetworksFunction
+
+NetworkingPrivateGetNetworksFunction::~NetworkingPrivateGetNetworksFunction() {
+}
+
+bool NetworkingPrivateGetNetworksFunction::RunAsync() {
+ scoped_ptr<private_api::GetNetworks::Params> params =
+ private_api::GetNetworks::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ std::string network_type = private_api::ToString(params->filter.network_type);
+ const bool configured_only =
+ params->filter.configured ? *params->filter.configured : false;
+ const bool visible_only =
+ params->filter.visible ? *params->filter.visible : false;
+ const int limit =
+ params->filter.limit ? *params->filter.limit : kDefaultNetworkListLimit;
+
+ GetDelegate(browser_context())
+ ->GetNetworks(
+ network_type, configured_only, visible_only, limit,
+ base::Bind(&NetworkingPrivateGetNetworksFunction::Success, this),
+ base::Bind(&NetworkingPrivateGetNetworksFunction::Failure, this));
+ return true;
+}
+
+void NetworkingPrivateGetNetworksFunction::Success(
+ scoped_ptr<base::ListValue> network_list) {
+ SetResult(network_list.release());
+ SendResponse(true);
+}
+
+void NetworkingPrivateGetNetworksFunction::Failure(const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateGetVisibleNetworksFunction
+
+NetworkingPrivateGetVisibleNetworksFunction::
+ ~NetworkingPrivateGetVisibleNetworksFunction() {
+}
+
+bool NetworkingPrivateGetVisibleNetworksFunction::RunAsync() {
+ scoped_ptr<private_api::GetVisibleNetworks::Params> params =
+ private_api::GetVisibleNetworks::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ std::string network_type = private_api::ToString(params->network_type);
+ const bool configured_only = false;
+ const bool visible_only = true;
+
+ GetDelegate(browser_context())
+ ->GetNetworks(
+ network_type, configured_only, visible_only, kDefaultNetworkListLimit,
+ base::Bind(&NetworkingPrivateGetVisibleNetworksFunction::Success,
+ this),
+ base::Bind(&NetworkingPrivateGetVisibleNetworksFunction::Failure,
+ this));
+ return true;
+}
+
+void NetworkingPrivateGetVisibleNetworksFunction::Success(
+ scoped_ptr<base::ListValue> network_properties_list) {
+ SetResult(network_properties_list.release());
+ SendResponse(true);
+}
+
+void NetworkingPrivateGetVisibleNetworksFunction::Failure(
+ const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateGetEnabledNetworkTypesFunction
+
+NetworkingPrivateGetEnabledNetworkTypesFunction::
+ ~NetworkingPrivateGetEnabledNetworkTypesFunction() {
+}
+
+bool NetworkingPrivateGetEnabledNetworkTypesFunction::RunSync() {
+ scoped_ptr<base::ListValue> enabled_networks_onc_types(
+ GetDelegate(browser_context())->GetEnabledNetworkTypes());
+ if (!enabled_networks_onc_types) {
+ error_ = networking_private::kErrorNotSupported;
+ return false;
+ }
+ scoped_ptr<base::ListValue> enabled_networks_list(new base::ListValue);
+ for (base::ListValue::iterator iter = enabled_networks_onc_types->begin();
+ iter != enabled_networks_onc_types->end(); ++iter) {
+ std::string type;
+ if (!(*iter)->GetAsString(&type))
+ NOTREACHED();
+ if (type == ::onc::network_type::kEthernet) {
+ enabled_networks_list->AppendString(
+ private_api::ToString(private_api::NETWORK_TYPE_ETHERNET));
+ } else if (type == ::onc::network_type::kWiFi) {
+ enabled_networks_list->AppendString(
+ private_api::ToString(private_api::NETWORK_TYPE_WIFI));
+ } else if (type == ::onc::network_type::kWimax) {
+ enabled_networks_list->AppendString(
+ private_api::ToString(private_api::NETWORK_TYPE_WIMAX));
+ } else if (type == ::onc::network_type::kCellular) {
+ enabled_networks_list->AppendString(
+ private_api::ToString(private_api::NETWORK_TYPE_CELLULAR));
+ } else {
+ LOG(ERROR) << "networkingPrivate: Unexpected type: " << type;
+ }
+ }
+ SetResult(enabled_networks_list.release());
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateGetDeviceStatesFunction
+
+NetworkingPrivateGetDeviceStatesFunction::
+ ~NetworkingPrivateGetDeviceStatesFunction() {
+}
+
+bool NetworkingPrivateGetDeviceStatesFunction::RunSync() {
+ scoped_ptr<NetworkingPrivateDelegate::DeviceStateList> device_states(
+ GetDelegate(browser_context())->GetDeviceStateList());
+ if (!device_states) {
+ error_ = networking_private::kErrorNotSupported;
+ return false;
+ }
+
+ scoped_ptr<base::ListValue> device_state_list(new base::ListValue);
+ for (const auto& properties : *device_states)
+ device_state_list->Append(properties->ToValue().release());
+ SetResult(device_state_list.release());
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateEnableNetworkTypeFunction
+
+NetworkingPrivateEnableNetworkTypeFunction::
+ ~NetworkingPrivateEnableNetworkTypeFunction() {
+}
+
+bool NetworkingPrivateEnableNetworkTypeFunction::RunSync() {
+ scoped_ptr<private_api::EnableNetworkType::Params> params =
+ private_api::EnableNetworkType::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ return GetDelegate(browser_context())
+ ->EnableNetworkType(private_api::ToString(params->network_type));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateDisableNetworkTypeFunction
+
+NetworkingPrivateDisableNetworkTypeFunction::
+ ~NetworkingPrivateDisableNetworkTypeFunction() {
+}
+
+bool NetworkingPrivateDisableNetworkTypeFunction::RunSync() {
+ scoped_ptr<private_api::DisableNetworkType::Params> params =
+ private_api::DisableNetworkType::Params::Create(*args_);
+
+ return GetDelegate(browser_context())
+ ->DisableNetworkType(private_api::ToString(params->network_type));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateRequestNetworkScanFunction
+
+NetworkingPrivateRequestNetworkScanFunction::
+ ~NetworkingPrivateRequestNetworkScanFunction() {
+}
+
+bool NetworkingPrivateRequestNetworkScanFunction::RunSync() {
+ return GetDelegate(browser_context())->RequestScan();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateStartConnectFunction
+
+NetworkingPrivateStartConnectFunction::
+ ~NetworkingPrivateStartConnectFunction() {
+}
+
+bool NetworkingPrivateStartConnectFunction::RunAsync() {
+ scoped_ptr<private_api::StartConnect::Params> params =
+ private_api::StartConnect::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ GetDelegate(browser_context())
+ ->StartConnect(
+ params->network_guid,
+ base::Bind(&NetworkingPrivateStartConnectFunction::Success, this),
+ base::Bind(&NetworkingPrivateStartConnectFunction::Failure, this));
+ return true;
+}
+
+void NetworkingPrivateStartConnectFunction::Success() {
+ SendResponse(true);
+}
+
+void NetworkingPrivateStartConnectFunction::Failure(const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateStartDisconnectFunction
+
+NetworkingPrivateStartDisconnectFunction::
+ ~NetworkingPrivateStartDisconnectFunction() {
+}
+
+bool NetworkingPrivateStartDisconnectFunction::RunAsync() {
+ scoped_ptr<private_api::StartDisconnect::Params> params =
+ private_api::StartDisconnect::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ GetDelegate(browser_context())
+ ->StartDisconnect(
+ params->network_guid,
+ base::Bind(&NetworkingPrivateStartDisconnectFunction::Success, this),
+ base::Bind(&NetworkingPrivateStartDisconnectFunction::Failure, this));
+ return true;
+}
+
+void NetworkingPrivateStartDisconnectFunction::Success() {
+ SendResponse(true);
+}
+
+void NetworkingPrivateStartDisconnectFunction::Failure(
+ const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateStartActivateFunction
+
+NetworkingPrivateStartActivateFunction::
+ ~NetworkingPrivateStartActivateFunction() {
+}
+
+bool NetworkingPrivateStartActivateFunction::RunAsync() {
+ scoped_ptr<private_api::StartActivate::Params> params =
+ private_api::StartActivate::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ GetDelegate(browser_context())
+ ->StartActivate(
+ params->network_guid, params->carrier ? *params->carrier : "",
+ base::Bind(&NetworkingPrivateStartActivateFunction::Success, this),
+ base::Bind(&NetworkingPrivateStartActivateFunction::Failure, this));
+ return true;
+}
+
+void NetworkingPrivateStartActivateFunction::Success() {
+ SendResponse(true);
+}
+
+void NetworkingPrivateStartActivateFunction::Failure(const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateVerifyDestinationFunction
+
+NetworkingPrivateVerifyDestinationFunction::
+ ~NetworkingPrivateVerifyDestinationFunction() {
+}
+
+bool NetworkingPrivateVerifyDestinationFunction::RunAsync() {
+ scoped_ptr<private_api::VerifyDestination::Params> params =
+ private_api::VerifyDestination::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ GetDelegate(browser_context())
+ ->VerifyDestination(
+ params->properties,
+ base::Bind(&NetworkingPrivateVerifyDestinationFunction::Success,
+ this),
+ base::Bind(&NetworkingPrivateVerifyDestinationFunction::Failure,
+ this));
+ return true;
+}
+
+void NetworkingPrivateVerifyDestinationFunction::Success(bool result) {
+ results_ = private_api::VerifyDestination::Results::Create(result);
+ SendResponse(true);
+}
+
+void NetworkingPrivateVerifyDestinationFunction::Failure(
+ const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateVerifyAndEncryptCredentialsFunction
+
+NetworkingPrivateVerifyAndEncryptCredentialsFunction::
+ ~NetworkingPrivateVerifyAndEncryptCredentialsFunction() {
+}
+
+bool NetworkingPrivateVerifyAndEncryptCredentialsFunction::RunAsync() {
+ scoped_ptr<private_api::VerifyAndEncryptCredentials::Params> params =
+ private_api::VerifyAndEncryptCredentials::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ GetDelegate(browser_context())
+ ->VerifyAndEncryptCredentials(
+ params->network_guid, params->properties,
+ base::Bind(
+ &NetworkingPrivateVerifyAndEncryptCredentialsFunction::Success,
+ this),
+ base::Bind(
+ &NetworkingPrivateVerifyAndEncryptCredentialsFunction::Failure,
+ this));
+ return true;
+}
+
+void NetworkingPrivateVerifyAndEncryptCredentialsFunction::Success(
+ const std::string& result) {
+ results_ = private_api::VerifyAndEncryptCredentials::Results::Create(result);
+ SendResponse(true);
+}
+
+void NetworkingPrivateVerifyAndEncryptCredentialsFunction::Failure(
+ const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateVerifyAndEncryptDataFunction
+
+NetworkingPrivateVerifyAndEncryptDataFunction::
+ ~NetworkingPrivateVerifyAndEncryptDataFunction() {
+}
+
+bool NetworkingPrivateVerifyAndEncryptDataFunction::RunAsync() {
+ scoped_ptr<private_api::VerifyAndEncryptData::Params> params =
+ private_api::VerifyAndEncryptData::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ GetDelegate(browser_context())
+ ->VerifyAndEncryptData(
+ params->properties, params->data,
+ base::Bind(&NetworkingPrivateVerifyAndEncryptDataFunction::Success,
+ this),
+ base::Bind(&NetworkingPrivateVerifyAndEncryptDataFunction::Failure,
+ this));
+ return true;
+}
+
+void NetworkingPrivateVerifyAndEncryptDataFunction::Success(
+ const std::string& result) {
+ results_ = private_api::VerifyAndEncryptData::Results::Create(result);
+ SendResponse(true);
+}
+
+void NetworkingPrivateVerifyAndEncryptDataFunction::Failure(
+ const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateSetWifiTDLSEnabledStateFunction
+
+NetworkingPrivateSetWifiTDLSEnabledStateFunction::
+ ~NetworkingPrivateSetWifiTDLSEnabledStateFunction() {
+}
+
+bool NetworkingPrivateSetWifiTDLSEnabledStateFunction::RunAsync() {
+ scoped_ptr<private_api::SetWifiTDLSEnabledState::Params> params =
+ private_api::SetWifiTDLSEnabledState::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ GetDelegate(browser_context())
+ ->SetWifiTDLSEnabledState(
+ params->ip_or_mac_address, params->enabled,
+ base::Bind(&NetworkingPrivateSetWifiTDLSEnabledStateFunction::Success,
+ this),
+ base::Bind(&NetworkingPrivateSetWifiTDLSEnabledStateFunction::Failure,
+ this));
+
+ return true;
+}
+
+void NetworkingPrivateSetWifiTDLSEnabledStateFunction::Success(
+ const std::string& result) {
+ results_ = private_api::SetWifiTDLSEnabledState::Results::Create(result);
+ SendResponse(true);
+}
+
+void NetworkingPrivateSetWifiTDLSEnabledStateFunction::Failure(
+ const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateGetWifiTDLSStatusFunction
+
+NetworkingPrivateGetWifiTDLSStatusFunction::
+ ~NetworkingPrivateGetWifiTDLSStatusFunction() {
+}
+
+bool NetworkingPrivateGetWifiTDLSStatusFunction::RunAsync() {
+ scoped_ptr<private_api::GetWifiTDLSStatus::Params> params =
+ private_api::GetWifiTDLSStatus::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ GetDelegate(browser_context())
+ ->GetWifiTDLSStatus(
+ params->ip_or_mac_address,
+ base::Bind(&NetworkingPrivateGetWifiTDLSStatusFunction::Success,
+ this),
+ base::Bind(&NetworkingPrivateGetWifiTDLSStatusFunction::Failure,
+ this));
+
+ return true;
+}
+
+void NetworkingPrivateGetWifiTDLSStatusFunction::Success(
+ const std::string& result) {
+ results_ = private_api::GetWifiTDLSStatus::Results::Create(result);
+ SendResponse(true);
+}
+
+void NetworkingPrivateGetWifiTDLSStatusFunction::Failure(
+ const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateGetCaptivePortalStatusFunction
+
+NetworkingPrivateGetCaptivePortalStatusFunction::
+ ~NetworkingPrivateGetCaptivePortalStatusFunction() {
+}
+
+bool NetworkingPrivateGetCaptivePortalStatusFunction::RunAsync() {
+ scoped_ptr<private_api::GetCaptivePortalStatus::Params> params =
+ private_api::GetCaptivePortalStatus::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ GetDelegate(browser_context())
+ ->GetCaptivePortalStatus(
+ params->network_guid,
+ base::Bind(&NetworkingPrivateGetCaptivePortalStatusFunction::Success,
+ this),
+ base::Bind(&NetworkingPrivateGetCaptivePortalStatusFunction::Failure,
+ this));
+ return true;
+}
+
+void NetworkingPrivateGetCaptivePortalStatusFunction::Success(
+ const std::string& result) {
+ results_ = private_api::GetCaptivePortalStatus::Results::Create(
+ private_api::ParseCaptivePortalStatus(result));
+ SendResponse(true);
+}
+
+void NetworkingPrivateGetCaptivePortalStatusFunction::Failure(
+ const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateUnlockCellularSimFunction
+
+NetworkingPrivateUnlockCellularSimFunction::
+ ~NetworkingPrivateUnlockCellularSimFunction() {}
+
+bool NetworkingPrivateUnlockCellularSimFunction::RunAsync() {
+ scoped_ptr<private_api::UnlockCellularSim::Params> params =
+ private_api::UnlockCellularSim::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ GetDelegate(browser_context())
+ ->UnlockCellularSim(
+ params->network_guid, params->pin, params->puk ? *params->puk : "",
+ base::Bind(&NetworkingPrivateUnlockCellularSimFunction::Success,
+ this),
+ base::Bind(&NetworkingPrivateUnlockCellularSimFunction::Failure,
+ this));
+ return true;
+}
+
+void NetworkingPrivateUnlockCellularSimFunction::Success() {
+ SendResponse(true);
+}
+
+void NetworkingPrivateUnlockCellularSimFunction::Failure(
+ const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NetworkingPrivateSetCellularSimStateFunction
+
+NetworkingPrivateSetCellularSimStateFunction::
+ ~NetworkingPrivateSetCellularSimStateFunction() {}
+
+bool NetworkingPrivateSetCellularSimStateFunction::RunAsync() {
+ scoped_ptr<private_api::SetCellularSimState::Params> params =
+ private_api::SetCellularSimState::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ GetDelegate(browser_context())
+ ->SetCellularSimState(
+ params->network_guid, params->sim_state.require_pin,
+ params->sim_state.current_pin,
+ params->sim_state.new_pin ? *params->sim_state.new_pin : "",
+ base::Bind(&NetworkingPrivateSetCellularSimStateFunction::Success,
+ this),
+ base::Bind(&NetworkingPrivateSetCellularSimStateFunction::Failure,
+ this));
+ return true;
+}
+
+void NetworkingPrivateSetCellularSimStateFunction::Success() {
+ SendResponse(true);
+}
+
+void NetworkingPrivateSetCellularSimStateFunction::Failure(
+ const std::string& error) {
+ error_ = error;
+ SendResponse(false);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/networking_private/networking_private_api.h b/chromium/extensions/browser/api/networking_private/networking_private_api.h
new file mode 100644
index 00000000000..6283839f547
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/networking_private_api.h
@@ -0,0 +1,508 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_API_H_
+#define EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_API_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "extensions/browser/extension_function.h"
+
+namespace extensions {
+
+namespace networking_private {
+
+extern const char kErrorInvalidNetworkGuid[];
+extern const char kErrorInvalidNetworkOperation[];
+extern const char kErrorNetworkUnavailable[];
+extern const char kErrorEncryptionError[];
+extern const char kErrorNotReady[];
+extern const char kErrorNotSupported[];
+extern const char kErrorSimLocked[];
+
+} // namespace networking_private
+
+// Implements the chrome.networkingPrivate.getProperties method.
+class NetworkingPrivateGetPropertiesFunction : public AsyncExtensionFunction {
+ public:
+ NetworkingPrivateGetPropertiesFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.getProperties",
+ NETWORKINGPRIVATE_GETPROPERTIES);
+
+ protected:
+ ~NetworkingPrivateGetPropertiesFunction() override;
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ private:
+ void Success(scoped_ptr<base::DictionaryValue> result);
+ void Failure(const std::string& error_name);
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateGetPropertiesFunction);
+};
+
+// Implements the chrome.networkingPrivate.getManagedProperties method.
+class NetworkingPrivateGetManagedPropertiesFunction
+ : public AsyncExtensionFunction {
+ public:
+ NetworkingPrivateGetManagedPropertiesFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.getManagedProperties",
+ NETWORKINGPRIVATE_GETMANAGEDPROPERTIES);
+
+ protected:
+ ~NetworkingPrivateGetManagedPropertiesFunction() override;
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ private:
+ void Success(scoped_ptr<base::DictionaryValue> result);
+ void Failure(const std::string& error);
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateGetManagedPropertiesFunction);
+};
+
+// Implements the chrome.networkingPrivate.getState method.
+class NetworkingPrivateGetStateFunction : public AsyncExtensionFunction {
+ public:
+ NetworkingPrivateGetStateFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.getState",
+ NETWORKINGPRIVATE_GETSTATE);
+
+ protected:
+ ~NetworkingPrivateGetStateFunction() override;
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ private:
+ void Success(scoped_ptr<base::DictionaryValue> result);
+ void Failure(const std::string& error);
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateGetStateFunction);
+};
+
+// Implements the chrome.networkingPrivate.setProperties method.
+class NetworkingPrivateSetPropertiesFunction : public AsyncExtensionFunction {
+ public:
+ NetworkingPrivateSetPropertiesFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.setProperties",
+ NETWORKINGPRIVATE_SETPROPERTIES);
+
+ protected:
+ ~NetworkingPrivateSetPropertiesFunction() override;
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ private:
+ void Success();
+ void Failure(const std::string& error);
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateSetPropertiesFunction);
+};
+
+// Implements the chrome.networkingPrivate.createNetwork method.
+class NetworkingPrivateCreateNetworkFunction : public AsyncExtensionFunction {
+ public:
+ NetworkingPrivateCreateNetworkFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.createNetwork",
+ NETWORKINGPRIVATE_CREATENETWORK);
+
+ protected:
+ ~NetworkingPrivateCreateNetworkFunction() override;
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ private:
+ void Success(const std::string& guid);
+ void Failure(const std::string& error);
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateCreateNetworkFunction);
+};
+
+// Implements the chrome.networkingPrivate.createNetwork method.
+class NetworkingPrivateForgetNetworkFunction : public AsyncExtensionFunction {
+ public:
+ NetworkingPrivateForgetNetworkFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.forgetNetwork",
+ NETWORKINGPRIVATE_FORGETNETWORK);
+
+ protected:
+ ~NetworkingPrivateForgetNetworkFunction() override;
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ private:
+ void Success();
+ void Failure(const std::string& error);
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateForgetNetworkFunction);
+};
+
+// Implements the chrome.networkingPrivate.getNetworks method.
+class NetworkingPrivateGetNetworksFunction : public AsyncExtensionFunction {
+ public:
+ NetworkingPrivateGetNetworksFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.getNetworks",
+ NETWORKINGPRIVATE_GETNETWORKS);
+
+ protected:
+ ~NetworkingPrivateGetNetworksFunction() override;
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ private:
+ void Success(scoped_ptr<base::ListValue> network_list);
+ void Failure(const std::string& error);
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateGetNetworksFunction);
+};
+
+// Implements the chrome.networkingPrivate.getVisibleNetworks method.
+class NetworkingPrivateGetVisibleNetworksFunction
+ : public AsyncExtensionFunction {
+ public:
+ NetworkingPrivateGetVisibleNetworksFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.getVisibleNetworks",
+ NETWORKINGPRIVATE_GETVISIBLENETWORKS);
+
+ protected:
+ ~NetworkingPrivateGetVisibleNetworksFunction() override;
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ private:
+ void Success(scoped_ptr<base::ListValue> network_list);
+ void Failure(const std::string& error);
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateGetVisibleNetworksFunction);
+};
+
+// Implements the chrome.networkingPrivate.getEnabledNetworkTypes method.
+class NetworkingPrivateGetEnabledNetworkTypesFunction
+ : public SyncExtensionFunction {
+ public:
+ NetworkingPrivateGetEnabledNetworkTypesFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.getEnabledNetworkTypes",
+ NETWORKINGPRIVATE_GETENABLEDNETWORKTYPES);
+
+ protected:
+ ~NetworkingPrivateGetEnabledNetworkTypesFunction() override;
+
+ // SyncExtensionFunction overrides.
+ bool RunSync() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateGetEnabledNetworkTypesFunction);
+};
+
+// Implements the chrome.networkingPrivate.getDeviceStates method.
+class NetworkingPrivateGetDeviceStatesFunction : public SyncExtensionFunction {
+ public:
+ NetworkingPrivateGetDeviceStatesFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.getDeviceStates",
+ NETWORKINGPRIVATE_GETDEVICESTATES);
+
+ protected:
+ ~NetworkingPrivateGetDeviceStatesFunction() override;
+
+ // SyncExtensionFunction overrides.
+ bool RunSync() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateGetDeviceStatesFunction);
+};
+
+// Implements the chrome.networkingPrivate.enableNetworkType method.
+class NetworkingPrivateEnableNetworkTypeFunction
+ : public SyncExtensionFunction {
+ public:
+ NetworkingPrivateEnableNetworkTypeFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.enableNetworkType",
+ NETWORKINGPRIVATE_ENABLENETWORKTYPE);
+
+ protected:
+ ~NetworkingPrivateEnableNetworkTypeFunction() override;
+
+ // SyncExtensionFunction overrides.
+ bool RunSync() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateEnableNetworkTypeFunction);
+};
+
+// Implements the chrome.networkingPrivate.disableNetworkType method.
+class NetworkingPrivateDisableNetworkTypeFunction
+ : public SyncExtensionFunction {
+ public:
+ NetworkingPrivateDisableNetworkTypeFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.disableNetworkType",
+ NETWORKINGPRIVATE_DISABLENETWORKTYPE);
+
+ protected:
+ ~NetworkingPrivateDisableNetworkTypeFunction() override;
+
+ // SyncExtensionFunction overrides.
+ bool RunSync() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateDisableNetworkTypeFunction);
+};
+
+// Implements the chrome.networkingPrivate.requestNetworkScan method.
+class NetworkingPrivateRequestNetworkScanFunction
+ : public SyncExtensionFunction {
+ public:
+ NetworkingPrivateRequestNetworkScanFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.requestNetworkScan",
+ NETWORKINGPRIVATE_REQUESTNETWORKSCAN);
+
+ protected:
+ ~NetworkingPrivateRequestNetworkScanFunction() override;
+
+ // SyncExtensionFunction overrides.
+ bool RunSync() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateRequestNetworkScanFunction);
+};
+
+// Implements the chrome.networkingPrivate.startConnect method.
+class NetworkingPrivateStartConnectFunction : public AsyncExtensionFunction {
+ public:
+ NetworkingPrivateStartConnectFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.startConnect",
+ NETWORKINGPRIVATE_STARTCONNECT);
+
+ protected:
+ ~NetworkingPrivateStartConnectFunction() override;
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ private:
+ void Success();
+ void Failure(const std::string& error);
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateStartConnectFunction);
+};
+
+// Implements the chrome.networkingPrivate.startDisconnect method.
+class NetworkingPrivateStartDisconnectFunction : public AsyncExtensionFunction {
+ public:
+ NetworkingPrivateStartDisconnectFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.startDisconnect",
+ NETWORKINGPRIVATE_STARTDISCONNECT);
+
+ protected:
+ ~NetworkingPrivateStartDisconnectFunction() override;
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ private:
+ void Success();
+ void Failure(const std::string& error);
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateStartDisconnectFunction);
+};
+
+// Implements the chrome.networkingPrivate.startActivate method.
+class NetworkingPrivateStartActivateFunction : public AsyncExtensionFunction {
+ public:
+ NetworkingPrivateStartActivateFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.startActivate",
+ NETWORKINGPRIVATE_STARTACTIVATE);
+
+ protected:
+ ~NetworkingPrivateStartActivateFunction() override;
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ private:
+ void Success();
+ void Failure(const std::string& error);
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateStartActivateFunction);
+};
+
+// Implements the chrome.networkingPrivate.verifyDestination method.
+class NetworkingPrivateVerifyDestinationFunction
+ : public AsyncExtensionFunction {
+ public:
+ NetworkingPrivateVerifyDestinationFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.verifyDestination",
+ NETWORKINGPRIVATE_VERIFYDESTINATION);
+
+ protected:
+ ~NetworkingPrivateVerifyDestinationFunction() override;
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ void Success(bool result);
+ void Failure(const std::string& error);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateVerifyDestinationFunction);
+};
+
+// Implements the chrome.networkingPrivate.verifyAndEncryptCredentials method.
+class NetworkingPrivateVerifyAndEncryptCredentialsFunction
+ : public AsyncExtensionFunction {
+ public:
+ NetworkingPrivateVerifyAndEncryptCredentialsFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.verifyAndEncryptCredentials",
+ NETWORKINGPRIVATE_VERIFYANDENCRYPTCREDENTIALS);
+
+ protected:
+ ~NetworkingPrivateVerifyAndEncryptCredentialsFunction() override;
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ void Success(const std::string& result);
+ void Failure(const std::string& error);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(
+ NetworkingPrivateVerifyAndEncryptCredentialsFunction);
+};
+
+// Implements the chrome.networkingPrivate.verifyAndEncryptData method.
+class NetworkingPrivateVerifyAndEncryptDataFunction
+ : public AsyncExtensionFunction {
+ public:
+ NetworkingPrivateVerifyAndEncryptDataFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.verifyAndEncryptData",
+ NETWORKINGPRIVATE_VERIFYANDENCRYPTDATA);
+
+ protected:
+ ~NetworkingPrivateVerifyAndEncryptDataFunction() override;
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ void Success(const std::string& result);
+ void Failure(const std::string& error);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateVerifyAndEncryptDataFunction);
+};
+
+// Implements the chrome.networkingPrivate.setWifiTDLSEnabledState method.
+class NetworkingPrivateSetWifiTDLSEnabledStateFunction
+ : public AsyncExtensionFunction {
+ public:
+ NetworkingPrivateSetWifiTDLSEnabledStateFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.setWifiTDLSEnabledState",
+ NETWORKINGPRIVATE_SETWIFITDLSENABLEDSTATE);
+
+ protected:
+ ~NetworkingPrivateSetWifiTDLSEnabledStateFunction() override;
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ void Success(const std::string& result);
+ void Failure(const std::string& error);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateSetWifiTDLSEnabledStateFunction);
+};
+
+// Implements the chrome.networkingPrivate.getWifiTDLSStatus method.
+class NetworkingPrivateGetWifiTDLSStatusFunction
+ : public AsyncExtensionFunction {
+ public:
+ NetworkingPrivateGetWifiTDLSStatusFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.getWifiTDLSStatus",
+ NETWORKINGPRIVATE_GETWIFITDLSSTATUS);
+
+ protected:
+ ~NetworkingPrivateGetWifiTDLSStatusFunction() override;
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ void Success(const std::string& result);
+ void Failure(const std::string& error);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateGetWifiTDLSStatusFunction);
+};
+
+class NetworkingPrivateGetCaptivePortalStatusFunction
+ : public AsyncExtensionFunction {
+ public:
+ NetworkingPrivateGetCaptivePortalStatusFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.getCaptivePortalStatus",
+ NETWORKINGPRIVATE_GETCAPTIVEPORTALSTATUS);
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ protected:
+ ~NetworkingPrivateGetCaptivePortalStatusFunction() override;
+
+ private:
+ void Success(const std::string& result);
+ void Failure(const std::string& error);
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateGetCaptivePortalStatusFunction);
+};
+
+class NetworkingPrivateUnlockCellularSimFunction
+ : public AsyncExtensionFunction {
+ public:
+ NetworkingPrivateUnlockCellularSimFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.unlockCellularSim",
+ NETWORKINGPRIVATE_UNLOCKCELLULARSIM);
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ protected:
+ ~NetworkingPrivateUnlockCellularSimFunction() override;
+
+ private:
+ void Success();
+ void Failure(const std::string& error);
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateUnlockCellularSimFunction);
+};
+
+class NetworkingPrivateSetCellularSimStateFunction
+ : public AsyncExtensionFunction {
+ public:
+ NetworkingPrivateSetCellularSimStateFunction() {}
+ DECLARE_EXTENSION_FUNCTION("networkingPrivate.setCellularSimState",
+ NETWORKINGPRIVATE_SETCELLULARSIMSTATE);
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ protected:
+ ~NetworkingPrivateSetCellularSimStateFunction() override;
+
+ private:
+ void Success();
+ void Failure(const std::string& error);
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateSetCellularSimStateFunction);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_API_H_
diff --git a/chromium/extensions/browser/api/networking_private/networking_private_chromeos.cc b/chromium/extensions/browser/api/networking_private/networking_private_chromeos.cc
new file mode 100644
index 00000000000..a7108488a2a
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/networking_private_chromeos.cc
@@ -0,0 +1,674 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/networking_private/networking_private_chromeos.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/shill_manager_client.h"
+#include "chromeos/login/login_state.h"
+#include "chromeos/network/device_state.h"
+#include "chromeos/network/managed_network_configuration_handler.h"
+#include "chromeos/network/network_activation_handler.h"
+#include "chromeos/network/network_connection_handler.h"
+#include "chromeos/network/network_device_handler.h"
+#include "chromeos/network/network_event_log.h"
+#include "chromeos/network/network_state.h"
+#include "chromeos/network/network_state_handler.h"
+#include "chromeos/network/network_util.h"
+#include "chromeos/network/onc/onc_signature.h"
+#include "chromeos/network/onc/onc_translator.h"
+#include "chromeos/network/onc/onc_utils.h"
+#include "chromeos/network/portal_detector/network_portal_detector.h"
+#include "components/onc/onc_constants.h"
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/api/networking_private/networking_private_api.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+using chromeos::DeviceState;
+using chromeos::NetworkHandler;
+using chromeos::NetworkStateHandler;
+using chromeos::NetworkTypePattern;
+using chromeos::ShillManagerClient;
+using extensions::NetworkingPrivateDelegate;
+
+namespace private_api = extensions::api::networking_private;
+
+namespace {
+
+chromeos::NetworkStateHandler* GetStateHandler() {
+ return NetworkHandler::Get()->network_state_handler();
+}
+
+chromeos::ManagedNetworkConfigurationHandler* GetManagedConfigurationHandler() {
+ return NetworkHandler::Get()->managed_network_configuration_handler();
+}
+
+bool GetServicePathFromGuid(const std::string& guid,
+ std::string* service_path,
+ std::string* error) {
+ const chromeos::NetworkState* network =
+ GetStateHandler()->GetNetworkStateFromGuid(guid);
+ if (!network) {
+ *error = extensions::networking_private::kErrorInvalidNetworkGuid;
+ return false;
+ }
+ *service_path = network->path();
+ return true;
+}
+
+bool GetPrimaryUserIdHash(content::BrowserContext* browser_context,
+ std::string* user_hash,
+ std::string* error) {
+ std::string context_user_hash =
+ extensions::ExtensionsBrowserClient::Get()->GetUserIdHashFromContext(
+ browser_context);
+
+ // Currently Chrome OS only configures networks for the primary user.
+ // Configuration attempts from other browser contexts should fail.
+ if (context_user_hash != chromeos::LoginState::Get()->primary_user_hash()) {
+ // Disallow class requiring a user id hash from a non-primary user context
+ // to avoid complexities with the policy code.
+ LOG(ERROR) << "networkingPrivate API call from non primary user: "
+ << context_user_hash;
+ if (error)
+ *error = "Error.NonPrimaryUser";
+ return false;
+ }
+ if (user_hash)
+ *user_hash = context_user_hash;
+ return true;
+}
+
+void AppendDeviceState(
+ const std::string& type,
+ const DeviceState* device,
+ NetworkingPrivateDelegate::DeviceStateList* device_state_list) {
+ DCHECK(!type.empty());
+ NetworkTypePattern pattern =
+ chromeos::onc::NetworkTypePatternFromOncType(type);
+ NetworkStateHandler::TechnologyState technology_state =
+ GetStateHandler()->GetTechnologyState(pattern);
+ private_api::DeviceStateType state = private_api::DEVICE_STATE_TYPE_NONE;
+ switch (technology_state) {
+ case NetworkStateHandler::TECHNOLOGY_UNAVAILABLE:
+ if (!device)
+ return;
+ // If we have a DeviceState entry but the technology is not available,
+ // assume the technology is not initialized.
+ state = private_api::DEVICE_STATE_TYPE_UNINITIALIZED;
+ break;
+ case NetworkStateHandler::TECHNOLOGY_AVAILABLE:
+ state = private_api::DEVICE_STATE_TYPE_DISABLED;
+ break;
+ case NetworkStateHandler::TECHNOLOGY_UNINITIALIZED:
+ state = private_api::DEVICE_STATE_TYPE_UNINITIALIZED;
+ break;
+ case NetworkStateHandler::TECHNOLOGY_ENABLING:
+ state = private_api::DEVICE_STATE_TYPE_ENABLING;
+ break;
+ case NetworkStateHandler::TECHNOLOGY_ENABLED:
+ state = private_api::DEVICE_STATE_TYPE_ENABLED;
+ break;
+ case NetworkStateHandler::TECHNOLOGY_PROHIBITED:
+ state = private_api::DEVICE_STATE_TYPE_PROHIBITED;
+ break;
+ }
+ DCHECK_NE(private_api::DEVICE_STATE_TYPE_NONE, state);
+ scoped_ptr<private_api::DeviceStateProperties> properties(
+ new private_api::DeviceStateProperties);
+ properties->type = private_api::ParseNetworkType(type);
+ properties->state = state;
+ if (device && state == private_api::DEVICE_STATE_TYPE_ENABLED)
+ properties->scanning.reset(new bool(device->scanning()));
+ if (device && type == ::onc::network_config::kCellular) {
+ properties->sim_present.reset(new bool(!device->IsSimAbsent()));
+ if (!device->sim_lock_type().empty())
+ properties->sim_lock_type.reset(new std::string(device->sim_lock_type()));
+ }
+ device_state_list->push_back(std::move(properties));
+}
+
+void NetworkHandlerFailureCallback(
+ const NetworkingPrivateDelegate::FailureCallback& callback,
+ const std::string& error_name,
+ scoped_ptr<base::DictionaryValue> error_data) {
+ callback.Run(error_name);
+}
+
+void RequirePinSuccess(
+ const std::string& device_path,
+ const std::string& current_pin,
+ const std::string& new_pin,
+ const extensions::NetworkingPrivateChromeOS::VoidCallback& success_callback,
+ const extensions::NetworkingPrivateChromeOS::FailureCallback&
+ failure_callback) {
+ // After RequirePin succeeds, call ChangePIN iff a different new_pin is
+ // provided.
+ if (new_pin.empty() || new_pin == current_pin) {
+ success_callback.Run();
+ return;
+ }
+ NetworkHandler::Get()->network_device_handler()->ChangePin(
+ device_path, current_pin, new_pin, success_callback,
+ base::Bind(&NetworkHandlerFailureCallback, failure_callback));
+}
+
+// Returns the string corresponding to |key|. If the property is a managed
+// dictionary, returns the active value. If the property does not exist or
+// has no active value, returns an empty string.
+std::string GetStringFromDictionary(const base::DictionaryValue& dictionary,
+ const std::string& key) {
+ std::string result;
+ if (!dictionary.GetStringWithoutPathExpansion(key, &result)) {
+ const base::DictionaryValue* managed = nullptr;
+ if (dictionary.GetDictionaryWithoutPathExpansion(key, &managed)) {
+ managed->GetStringWithoutPathExpansion(::onc::kAugmentationActiveSetting,
+ &result);
+ }
+ }
+ return result;
+}
+
+base::DictionaryValue* GetThirdPartyVPNDictionary(
+ base::DictionaryValue* dictionary) {
+ const std::string type =
+ GetStringFromDictionary(*dictionary, ::onc::network_config::kType);
+ if (type != ::onc::network_config::kVPN)
+ return nullptr;
+ base::DictionaryValue* vpn_dict = nullptr;
+ if (!dictionary->GetDictionary(::onc::network_config::kVPN, &vpn_dict))
+ return nullptr;
+ if (GetStringFromDictionary(*vpn_dict, ::onc::vpn::kType) !=
+ ::onc::vpn::kThirdPartyVpn) {
+ return nullptr;
+ }
+ base::DictionaryValue* third_party_vpn = nullptr;
+ vpn_dict->GetDictionary(::onc::vpn::kThirdPartyVpn, &third_party_vpn);
+ return third_party_vpn;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace extensions {
+
+NetworkingPrivateChromeOS::NetworkingPrivateChromeOS(
+ content::BrowserContext* browser_context,
+ scoped_ptr<VerifyDelegate> verify_delegate)
+ : NetworkingPrivateDelegate(std::move(verify_delegate)),
+ browser_context_(browser_context),
+ weak_ptr_factory_(this) {}
+
+NetworkingPrivateChromeOS::~NetworkingPrivateChromeOS() {
+}
+
+void NetworkingPrivateChromeOS::GetProperties(
+ const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ std::string service_path, error;
+ if (!GetServicePathFromGuid(guid, &service_path, &error)) {
+ failure_callback.Run(error);
+ return;
+ }
+
+ std::string user_id_hash;
+ if (!GetPrimaryUserIdHash(browser_context_, &user_id_hash, &error)) {
+ failure_callback.Run(error);
+ return;
+ }
+
+ GetManagedConfigurationHandler()->GetProperties(
+ user_id_hash, service_path,
+ base::Bind(&NetworkingPrivateChromeOS::GetPropertiesCallback,
+ weak_ptr_factory_.GetWeakPtr(), success_callback),
+ base::Bind(&NetworkHandlerFailureCallback, failure_callback));
+}
+
+void NetworkingPrivateChromeOS::GetManagedProperties(
+ const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ std::string service_path, error;
+ if (!GetServicePathFromGuid(guid, &service_path, &error)) {
+ failure_callback.Run(error);
+ return;
+ }
+
+ std::string user_id_hash;
+ if (!GetPrimaryUserIdHash(browser_context_, &user_id_hash, &error)) {
+ failure_callback.Run(error);
+ return;
+ }
+
+ GetManagedConfigurationHandler()->GetManagedProperties(
+ user_id_hash, service_path,
+ base::Bind(&NetworkingPrivateChromeOS::GetPropertiesCallback,
+ weak_ptr_factory_.GetWeakPtr(), success_callback),
+ base::Bind(&NetworkHandlerFailureCallback, failure_callback));
+}
+
+void NetworkingPrivateChromeOS::GetState(
+ const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ std::string service_path, error;
+ if (!GetServicePathFromGuid(guid, &service_path, &error)) {
+ failure_callback.Run(error);
+ return;
+ }
+
+ const chromeos::NetworkState* network_state =
+ GetStateHandler()->GetNetworkStateFromServicePath(
+ service_path, false /* configured_only */);
+ if (!network_state) {
+ failure_callback.Run(networking_private::kErrorNetworkUnavailable);
+ return;
+ }
+
+ scoped_ptr<base::DictionaryValue> network_properties =
+ chromeos::network_util::TranslateNetworkStateToONC(network_state);
+ AppendThirdPartyProviderName(network_properties.get());
+
+ success_callback.Run(std::move(network_properties));
+}
+
+void NetworkingPrivateChromeOS::SetProperties(
+ const std::string& guid,
+ scoped_ptr<base::DictionaryValue> properties,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ std::string service_path, error;
+ if (!GetServicePathFromGuid(guid, &service_path, &error)) {
+ failure_callback.Run(error);
+ return;
+ }
+
+ GetManagedConfigurationHandler()->SetProperties(
+ service_path, *properties, success_callback,
+ base::Bind(&NetworkHandlerFailureCallback, failure_callback));
+}
+
+void NetworkHandlerCreateCallback(
+ const NetworkingPrivateDelegate::StringCallback& callback,
+ const std::string& service_path,
+ const std::string& guid) {
+ callback.Run(guid);
+}
+
+void NetworkingPrivateChromeOS::CreateNetwork(
+ bool shared,
+ scoped_ptr<base::DictionaryValue> properties,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ std::string user_id_hash, error;
+ // Do not allow configuring a non-shared network from a non-primary user.
+ if (!shared &&
+ !GetPrimaryUserIdHash(browser_context_, &user_id_hash, &error)) {
+ failure_callback.Run(error);
+ return;
+ }
+
+ GetManagedConfigurationHandler()->CreateConfiguration(
+ user_id_hash, *properties,
+ base::Bind(&NetworkHandlerCreateCallback, success_callback),
+ base::Bind(&NetworkHandlerFailureCallback, failure_callback));
+}
+
+void NetworkingPrivateChromeOS::ForgetNetwork(
+ const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ std::string service_path, error;
+ if (!GetServicePathFromGuid(guid, &service_path, &error)) {
+ failure_callback.Run(error);
+ return;
+ }
+
+ GetManagedConfigurationHandler()->RemoveConfiguration(
+ service_path, success_callback,
+ base::Bind(&NetworkHandlerFailureCallback, failure_callback));
+}
+
+void NetworkingPrivateChromeOS::GetNetworks(
+ const std::string& network_type,
+ bool configured_only,
+ bool visible_only,
+ int limit,
+ const NetworkListCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ NetworkTypePattern pattern =
+ chromeos::onc::NetworkTypePatternFromOncType(network_type);
+ scoped_ptr<base::ListValue> network_properties_list =
+ chromeos::network_util::TranslateNetworkListToONC(
+ pattern, configured_only, visible_only, limit);
+
+ for (base::Value* value : *network_properties_list) {
+ base::DictionaryValue* network_dict = nullptr;
+ value->GetAsDictionary(&network_dict);
+ DCHECK(network_dict);
+ if (GetThirdPartyVPNDictionary(network_dict))
+ AppendThirdPartyProviderName(network_dict);
+ }
+
+ success_callback.Run(std::move(network_properties_list));
+}
+
+void NetworkingPrivateChromeOS::StartConnect(
+ const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ std::string service_path, error;
+ if (!GetServicePathFromGuid(guid, &service_path, &error)) {
+ failure_callback.Run(error);
+ return;
+ }
+
+ const bool check_error_state = false;
+ NetworkHandler::Get()->network_connection_handler()->ConnectToNetwork(
+ service_path, success_callback,
+ base::Bind(&NetworkingPrivateChromeOS::ConnectFailureCallback,
+ weak_ptr_factory_.GetWeakPtr(), guid, success_callback,
+ failure_callback),
+ check_error_state);
+}
+
+void NetworkingPrivateChromeOS::StartDisconnect(
+ const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ std::string service_path, error;
+ if (!GetServicePathFromGuid(guid, &service_path, &error)) {
+ failure_callback.Run(error);
+ return;
+ }
+
+ NetworkHandler::Get()->network_connection_handler()->DisconnectNetwork(
+ service_path, success_callback,
+ base::Bind(&NetworkHandlerFailureCallback, failure_callback));
+}
+
+void NetworkingPrivateChromeOS::StartActivate(
+ const std::string& guid,
+ const std::string& specified_carrier,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ const chromeos::NetworkState* network =
+ GetStateHandler()->GetNetworkStateFromGuid(guid);
+ if (!network) {
+ failure_callback.Run(
+ extensions::networking_private::kErrorInvalidNetworkGuid);
+ return;
+ }
+
+ std::string carrier(specified_carrier);
+ if (carrier.empty()) {
+ const chromeos::DeviceState* device =
+ GetStateHandler()->GetDeviceState(network->device_path());
+ if (device)
+ carrier = device->carrier();
+ }
+ if (carrier != shill::kCarrierSprint) {
+ // Only Sprint is directly activated. For other carriers, show the
+ // account details page.
+ if (ui_delegate())
+ ui_delegate()->ShowAccountDetails(guid);
+ success_callback.Run();
+ return;
+ }
+
+ if (!network->RequiresActivation()) {
+ // If no activation is required, show the account details page.
+ if (ui_delegate())
+ ui_delegate()->ShowAccountDetails(guid);
+ success_callback.Run();
+ return;
+ }
+
+ NetworkHandler::Get()->network_activation_handler()->Activate(
+ network->path(), carrier, success_callback,
+ base::Bind(&NetworkHandlerFailureCallback, failure_callback));
+}
+
+void NetworkingPrivateChromeOS::SetWifiTDLSEnabledState(
+ const std::string& ip_or_mac_address,
+ bool enabled,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ NetworkHandler::Get()->network_device_handler()->SetWifiTDLSEnabled(
+ ip_or_mac_address, enabled, success_callback,
+ base::Bind(&NetworkHandlerFailureCallback, failure_callback));
+}
+
+void NetworkingPrivateChromeOS::GetWifiTDLSStatus(
+ const std::string& ip_or_mac_address,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ NetworkHandler::Get()->network_device_handler()->GetWifiTDLSStatus(
+ ip_or_mac_address, success_callback,
+ base::Bind(&NetworkHandlerFailureCallback, failure_callback));
+}
+
+void NetworkingPrivateChromeOS::GetCaptivePortalStatus(
+ const std::string& guid,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ if (!chromeos::network_portal_detector::IsInitialized()) {
+ failure_callback.Run(networking_private::kErrorNotReady);
+ return;
+ }
+
+ success_callback.Run(
+ chromeos::NetworkPortalDetector::CaptivePortalStatusString(
+ chromeos::network_portal_detector::GetInstance()
+ ->GetCaptivePortalState(guid)
+ .status));
+}
+
+void NetworkingPrivateChromeOS::UnlockCellularSim(
+ const std::string& guid,
+ const std::string& pin,
+ const std::string& puk,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ const chromeos::NetworkState* network_state =
+ GetStateHandler()->GetNetworkStateFromGuid(guid);
+ if (!network_state) {
+ failure_callback.Run(networking_private::kErrorNetworkUnavailable);
+ return;
+ }
+ const chromeos::DeviceState* device_state =
+ GetStateHandler()->GetDeviceState(network_state->device_path());
+ if (!device_state) {
+ failure_callback.Run(networking_private::kErrorNetworkUnavailable);
+ return;
+ }
+ std::string lock_type = device_state->sim_lock_type();
+ if (lock_type.empty()) {
+ // Sim is already unlocked.
+ failure_callback.Run(networking_private::kErrorInvalidNetworkOperation);
+ return;
+ }
+
+ // Unblock or unlock the SIM.
+ if (lock_type == shill::kSIMLockPuk) {
+ NetworkHandler::Get()->network_device_handler()->UnblockPin(
+ device_state->path(), puk, pin, success_callback,
+ base::Bind(&NetworkHandlerFailureCallback, failure_callback));
+ } else {
+ NetworkHandler::Get()->network_device_handler()->EnterPin(
+ device_state->path(), pin, success_callback,
+ base::Bind(&NetworkHandlerFailureCallback, failure_callback));
+ }
+}
+
+void NetworkingPrivateChromeOS::SetCellularSimState(
+ const std::string& guid,
+ bool require_pin,
+ const std::string& current_pin,
+ const std::string& new_pin,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ const chromeos::NetworkState* network_state =
+ GetStateHandler()->GetNetworkStateFromGuid(guid);
+ if (!network_state) {
+ failure_callback.Run(networking_private::kErrorNetworkUnavailable);
+ return;
+ }
+ const chromeos::DeviceState* device_state =
+ GetStateHandler()->GetDeviceState(network_state->device_path());
+ if (!device_state) {
+ failure_callback.Run(networking_private::kErrorNetworkUnavailable);
+ return;
+ }
+ if (!device_state->sim_lock_type().empty()) {
+ // The SIM needs to be unlocked before the state can be changed.
+ failure_callback.Run(networking_private::kErrorSimLocked);
+ return;
+ }
+
+ // Only set a new pin if require_pin is true.
+ std::string set_new_pin = require_pin ? new_pin : "";
+ NetworkHandler::Get()->network_device_handler()->RequirePin(
+ device_state->path(), require_pin, current_pin,
+ base::Bind(&RequirePinSuccess, device_state->path(), current_pin,
+ set_new_pin, success_callback, failure_callback),
+ base::Bind(&NetworkHandlerFailureCallback, failure_callback));
+}
+
+scoped_ptr<base::ListValue>
+NetworkingPrivateChromeOS::GetEnabledNetworkTypes() {
+ chromeos::NetworkStateHandler* state_handler = GetStateHandler();
+
+ scoped_ptr<base::ListValue> network_list(new base::ListValue);
+
+ if (state_handler->IsTechnologyEnabled(NetworkTypePattern::Ethernet()))
+ network_list->AppendString(::onc::network_type::kEthernet);
+ if (state_handler->IsTechnologyEnabled(NetworkTypePattern::WiFi()))
+ network_list->AppendString(::onc::network_type::kWiFi);
+ if (state_handler->IsTechnologyEnabled(NetworkTypePattern::Wimax()))
+ network_list->AppendString(::onc::network_type::kWimax);
+ if (state_handler->IsTechnologyEnabled(NetworkTypePattern::Cellular()))
+ network_list->AppendString(::onc::network_type::kCellular);
+
+ return network_list;
+}
+
+scoped_ptr<NetworkingPrivateDelegate::DeviceStateList>
+NetworkingPrivateChromeOS::GetDeviceStateList() {
+ std::set<std::string> technologies_found;
+ NetworkStateHandler::DeviceStateList devices;
+ NetworkHandler::Get()->network_state_handler()->GetDeviceList(&devices);
+
+ scoped_ptr<DeviceStateList> device_state_list(new DeviceStateList);
+ for (const DeviceState* device : devices) {
+ std::string onc_type =
+ chromeos::network_util::TranslateShillTypeToONC(device->type());
+ AppendDeviceState(onc_type, device, device_state_list.get());
+ technologies_found.insert(onc_type);
+ }
+
+ // For any technologies that we do not have a DeviceState entry for, append
+ // an entry if the technolog is available.
+ const char* technology_types[] = {::onc::network_type::kEthernet,
+ ::onc::network_type::kWiFi,
+ ::onc::network_type::kWimax,
+ ::onc::network_type::kCellular};
+ for (const char* technology : technology_types) {
+ if (ContainsValue(technologies_found, technology))
+ continue;
+ AppendDeviceState(technology, nullptr /* device */,
+ device_state_list.get());
+ }
+ return device_state_list;
+}
+
+bool NetworkingPrivateChromeOS::EnableNetworkType(const std::string& type) {
+ NetworkTypePattern pattern =
+ chromeos::onc::NetworkTypePatternFromOncType(type);
+
+ GetStateHandler()->SetTechnologyEnabled(
+ pattern, true, chromeos::network_handler::ErrorCallback());
+
+ return true;
+}
+
+bool NetworkingPrivateChromeOS::DisableNetworkType(const std::string& type) {
+ NetworkTypePattern pattern =
+ chromeos::onc::NetworkTypePatternFromOncType(type);
+
+ GetStateHandler()->SetTechnologyEnabled(
+ pattern, false, chromeos::network_handler::ErrorCallback());
+
+ return true;
+}
+
+bool NetworkingPrivateChromeOS::RequestScan() {
+ GetStateHandler()->RequestScan();
+ return true;
+}
+
+// Private methods
+
+void NetworkingPrivateChromeOS::GetPropertiesCallback(
+ const DictionaryCallback& callback,
+ const std::string& service_path,
+ const base::DictionaryValue& dictionary) {
+ scoped_ptr<base::DictionaryValue> dictionary_copy(dictionary.DeepCopy());
+ AppendThirdPartyProviderName(dictionary_copy.get());
+ callback.Run(std::move(dictionary_copy));
+}
+
+// Populate ThirdPartyVPN.kProviderName for third-party VPNs.
+void NetworkingPrivateChromeOS::AppendThirdPartyProviderName(
+ base::DictionaryValue* dictionary) {
+ base::DictionaryValue* third_party_vpn =
+ GetThirdPartyVPNDictionary(dictionary);
+ if (!third_party_vpn)
+ return;
+
+ const std::string extension_id = GetStringFromDictionary(
+ *third_party_vpn, ::onc::third_party_vpn::kExtensionID);
+ const ExtensionSet& extensions =
+ ExtensionRegistry::Get(browser_context_)->enabled_extensions();
+ for (const auto& extension : extensions) {
+ if (extension->permissions_data()->HasAPIPermission(
+ APIPermission::kVpnProvider) &&
+ extension->id() == extension_id) {
+ third_party_vpn->SetStringWithoutPathExpansion(
+ ::onc::third_party_vpn::kProviderName, extension->name());
+ break;
+ }
+ }
+}
+
+void NetworkingPrivateChromeOS::ConnectFailureCallback(
+ const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback,
+ const std::string& error_name,
+ scoped_ptr<base::DictionaryValue> error_data) {
+ // TODO(stevenjb): Temporary workaround to show the configuration UI.
+ // Eventually the caller (e.g. Settings) should handle any failures and
+ // show its own configuration UI. crbug.com/380937.
+ if (ui_delegate()->HandleConnectFailed(guid, error_name)) {
+ success_callback.Run();
+ return;
+ }
+ failure_callback.Run(error_name);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/networking_private/networking_private_chromeos.h b/chromium/extensions/browser/api/networking_private/networking_private_chromeos.h
new file mode 100644
index 00000000000..f393fcb6137
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/networking_private_chromeos.h
@@ -0,0 +1,128 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_CHROMEOS_H_
+#define EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_CHROMEOS_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "extensions/browser/api/networking_private/networking_private_delegate.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+// Chrome OS NetworkingPrivateDelegate implementation.
+
+class NetworkingPrivateChromeOS : public NetworkingPrivateDelegate {
+ public:
+ // |verify_delegate| is passed to NetworkingPrivateDelegate and may be NULL.
+ NetworkingPrivateChromeOS(content::BrowserContext* browser_context,
+ scoped_ptr<VerifyDelegate> verify_delegate);
+ ~NetworkingPrivateChromeOS() override;
+
+ // NetworkingPrivateApi
+ void GetProperties(const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void GetManagedProperties(const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void GetState(const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void SetProperties(const std::string& guid,
+ scoped_ptr<base::DictionaryValue> properties,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void CreateNetwork(bool shared,
+ scoped_ptr<base::DictionaryValue> properties,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void ForgetNetwork(const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void GetNetworks(const std::string& network_type,
+ bool configured_only,
+ bool visible_only,
+ int limit,
+ const NetworkListCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void StartConnect(const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void StartDisconnect(const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void StartActivate(const std::string& guid,
+ const std::string& carrier,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void SetWifiTDLSEnabledState(
+ const std::string& ip_or_mac_address,
+ bool enabled,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void GetWifiTDLSStatus(const std::string& ip_or_mac_address,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void GetCaptivePortalStatus(const std::string& guid,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void UnlockCellularSim(const std::string& guid,
+ const std::string& pin,
+ const std::string& puk,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void SetCellularSimState(const std::string& guid,
+ bool require_pin,
+ const std::string& current_pin,
+ const std::string& new_pin,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ scoped_ptr<base::ListValue> GetEnabledNetworkTypes() override;
+ scoped_ptr<DeviceStateList> GetDeviceStateList() override;
+ bool EnableNetworkType(const std::string& type) override;
+ bool DisableNetworkType(const std::string& type) override;
+ bool RequestScan() override;
+
+ private:
+ // Callback for both GetProperties and GetManagedProperties. Copies
+ // |dictionary| and appends any networkingPrivate API specific properties,
+ // then calls |callback| with the result.
+ void GetPropertiesCallback(const DictionaryCallback& callback,
+ const std::string& service_path,
+ const base::DictionaryValue& dictionary);
+
+ // Populate ThirdPartyVPN.ProviderName with the provider name for third-party
+ // VPNs. The provider name needs to be looked up from the list of extensions
+ // which is not available to the chromeos/network module.
+ void AppendThirdPartyProviderName(base::DictionaryValue* dictionary);
+
+ // Handles connection failures, possibly showing UI for configuration
+ // failures, then calls the appropriate callback.
+ void ConnectFailureCallback(const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback,
+ const std::string& error_name,
+ scoped_ptr<base::DictionaryValue> error_data);
+
+ content::BrowserContext* browser_context_;
+ base::WeakPtrFactory<NetworkingPrivateChromeOS> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateChromeOS);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_CHROMEOS_H_
diff --git a/chromium/extensions/browser/api/networking_private/networking_private_delegate.cc b/chromium/extensions/browser/api/networking_private/networking_private_delegate.cc
new file mode 100644
index 00000000000..b9c722e5bf6
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/networking_private_delegate.cc
@@ -0,0 +1,84 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/networking_private/networking_private_delegate.h"
+
+#include "extensions/browser/api/networking_private/networking_private_api.h"
+
+namespace extensions {
+
+NetworkingPrivateDelegate::VerifyDelegate::VerifyDelegate() {
+}
+
+NetworkingPrivateDelegate::VerifyDelegate::~VerifyDelegate() {
+}
+
+NetworkingPrivateDelegate::UIDelegate::UIDelegate() {}
+
+NetworkingPrivateDelegate::UIDelegate::~UIDelegate() {}
+
+NetworkingPrivateDelegate::NetworkingPrivateDelegate(
+ scoped_ptr<VerifyDelegate> verify_delegate)
+ : verify_delegate_(std::move(verify_delegate)) {}
+
+NetworkingPrivateDelegate::~NetworkingPrivateDelegate() {
+}
+
+void NetworkingPrivateDelegate::AddObserver(
+ NetworkingPrivateDelegateObserver* observer) {
+ NOTREACHED() << "Class does not use NetworkingPrivateDelegateObserver";
+}
+
+void NetworkingPrivateDelegate::RemoveObserver(
+ NetworkingPrivateDelegateObserver* observer) {
+ NOTREACHED() << "Class does not use NetworkingPrivateDelegateObserver";
+}
+
+void NetworkingPrivateDelegate::StartActivate(
+ const std::string& guid,
+ const std::string& carrier,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ failure_callback.Run(networking_private::kErrorNotSupported);
+}
+
+void NetworkingPrivateDelegate::VerifyDestination(
+ const VerificationProperties& verification_properties,
+ const BoolCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ if (!verify_delegate_) {
+ failure_callback.Run(networking_private::kErrorNotSupported);
+ return;
+ }
+ verify_delegate_->VerifyDestination(verification_properties, success_callback,
+ failure_callback);
+}
+
+void NetworkingPrivateDelegate::VerifyAndEncryptCredentials(
+ const std::string& guid,
+ const VerificationProperties& verification_properties,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ if (!verify_delegate_) {
+ failure_callback.Run(networking_private::kErrorNotSupported);
+ return;
+ }
+ verify_delegate_->VerifyAndEncryptCredentials(
+ guid, verification_properties, success_callback, failure_callback);
+}
+
+void NetworkingPrivateDelegate::VerifyAndEncryptData(
+ const VerificationProperties& verification_properties,
+ const std::string& data,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ if (!verify_delegate_) {
+ failure_callback.Run(networking_private::kErrorNotSupported);
+ return;
+ }
+ verify_delegate_->VerifyAndEncryptData(verification_properties, data,
+ success_callback, failure_callback);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/networking_private/networking_private_delegate.h b/chromium/extensions/browser/api/networking_private/networking_private_delegate.h
new file mode 100644
index 00000000000..410d4858d84
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/networking_private_delegate.h
@@ -0,0 +1,224 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_DELEGATE_H_
+#define EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_DELEGATE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/values.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/common/api/networking_private.h"
+
+namespace extensions {
+
+class NetworkingPrivateDelegateObserver;
+
+namespace api {
+namespace networking_private {
+struct DeviceStateProperties;
+struct VerificationProperties;
+} // networking_private
+} // api
+
+// Base class for platform dependent networkingPrivate API implementations.
+// All inputs and results for this class use ONC values. See
+// networking_private.idl for descriptions of the expected inputs and results.
+class NetworkingPrivateDelegate : public KeyedService {
+ public:
+ using DictionaryCallback =
+ base::Callback<void(scoped_ptr<base::DictionaryValue>)>;
+ using VoidCallback = base::Callback<void()>;
+ using BoolCallback = base::Callback<void(bool)>;
+ using StringCallback = base::Callback<void(const std::string&)>;
+ using NetworkListCallback = base::Callback<void(scoped_ptr<base::ListValue>)>;
+ using FailureCallback = base::Callback<void(const std::string&)>;
+ using DeviceStateList =
+ std::vector<scoped_ptr<api::networking_private::DeviceStateProperties>>;
+ using VerificationProperties =
+ api::networking_private::VerificationProperties;
+
+ // The Verify* methods will be forwarded to a delegate implementation if
+ // provided, otherwise they will fail. A separate delegate it used so that the
+ // current Verify* implementations are not exposed outside of src/chrome.
+ class VerifyDelegate {
+ public:
+ typedef NetworkingPrivateDelegate::VerificationProperties
+ VerificationProperties;
+ typedef NetworkingPrivateDelegate::BoolCallback BoolCallback;
+ typedef NetworkingPrivateDelegate::StringCallback StringCallback;
+ typedef NetworkingPrivateDelegate::FailureCallback FailureCallback;
+
+ VerifyDelegate();
+ virtual ~VerifyDelegate();
+
+ virtual void VerifyDestination(
+ const VerificationProperties& verification_properties,
+ const BoolCallback& success_callback,
+ const FailureCallback& failure_callback) = 0;
+ virtual void VerifyAndEncryptCredentials(
+ const std::string& guid,
+ const VerificationProperties& verification_properties,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) = 0;
+ virtual void VerifyAndEncryptData(
+ const VerificationProperties& verification_properties,
+ const std::string& data,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(VerifyDelegate);
+ };
+
+ // Delegate for forwarding UI requests, e.g. for showing the account UI.
+ class UIDelegate {
+ public:
+ UIDelegate();
+ virtual ~UIDelegate();
+
+ // Navigate to the acoount details page for the cellular network associated
+ // with |guid|.
+ virtual void ShowAccountDetails(const std::string& guid) const = 0;
+
+ // Possibly handle a connection failure, e.g. by showing the configuration
+ // UI. Returns true if the error was handled, i.e. the UI was shown.
+ virtual bool HandleConnectFailed(const std::string& guid,
+ const std::string error) const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UIDelegate);
+ };
+
+ // If |verify_delegate| is not NULL, the Verify* methods will be forwarded
+ // to the delegate. Otherwise they will fail with a NotSupported error.
+ explicit NetworkingPrivateDelegate(
+ scoped_ptr<VerifyDelegate> verify_delegate);
+ ~NetworkingPrivateDelegate() override;
+
+ void set_ui_delegate(scoped_ptr<UIDelegate> ui_delegate) {
+ ui_delegate_.reset(ui_delegate.release());
+ }
+
+ const UIDelegate* ui_delegate() { return ui_delegate_.get(); }
+
+ // Asynchronous methods
+ virtual void GetProperties(const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) = 0;
+ virtual void GetManagedProperties(
+ const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) = 0;
+ virtual void GetState(const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) = 0;
+ virtual void SetProperties(const std::string& guid,
+ scoped_ptr<base::DictionaryValue> properties,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) = 0;
+ virtual void CreateNetwork(bool shared,
+ scoped_ptr<base::DictionaryValue> properties,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) = 0;
+ virtual void ForgetNetwork(const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) = 0;
+ virtual void GetNetworks(const std::string& network_type,
+ bool configured_only,
+ bool visible_only,
+ int limit,
+ const NetworkListCallback& success_callback,
+ const FailureCallback& failure_callback) = 0;
+ virtual void StartConnect(const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) = 0;
+ virtual void StartDisconnect(const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) = 0;
+ virtual void StartActivate(const std::string& guid,
+ const std::string& carrier,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback);
+ virtual void SetWifiTDLSEnabledState(
+ const std::string& ip_or_mac_address,
+ bool enabled,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) = 0;
+ virtual void GetWifiTDLSStatus(const std::string& ip_or_mac_address,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) = 0;
+ virtual void GetCaptivePortalStatus(
+ const std::string& guid,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) = 0;
+ virtual void UnlockCellularSim(const std::string& guid,
+ const std::string& pin,
+ const std::string& puk,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) = 0;
+
+ virtual void SetCellularSimState(const std::string& guid,
+ bool require_pin,
+ const std::string& current_pin,
+ const std::string& new_pin,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) = 0;
+
+ // Synchronous methods
+
+ // Returns a list of ONC type strings.
+ virtual scoped_ptr<base::ListValue> GetEnabledNetworkTypes() = 0;
+
+ // Returns a list of DeviceStateProperties.
+ virtual scoped_ptr<DeviceStateList> GetDeviceStateList() = 0;
+
+ // Returns true if the ONC network type |type| is enabled.
+ virtual bool EnableNetworkType(const std::string& type) = 0;
+
+ // Returns true if the ONC network type |type| is disabled.
+ virtual bool DisableNetworkType(const std::string& type) = 0;
+
+ // Returns true if a scan was requested. It may take many seconds for a scan
+ // to complete. The scan may or may not trigger API events when complete.
+ virtual bool RequestScan() = 0;
+
+ // Optional methods for adding a NetworkingPrivateDelegateObserver for
+ // implementations that require it (non-chromeos).
+ virtual void AddObserver(NetworkingPrivateDelegateObserver* observer);
+ virtual void RemoveObserver(NetworkingPrivateDelegateObserver* observer);
+
+ // Verify* methods are forwarded to |verify_delegate_| if not NULL.
+ void VerifyDestination(const VerificationProperties& verification_properties,
+ const BoolCallback& success_callback,
+ const FailureCallback& failure_callback);
+ void VerifyAndEncryptCredentials(
+ const std::string& guid,
+ const VerificationProperties& verification_properties,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback);
+ void VerifyAndEncryptData(
+ const VerificationProperties& verification_properties,
+ const std::string& data,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback);
+
+ private:
+ // Interface for Verify* methods. May be null.
+ scoped_ptr<VerifyDelegate> verify_delegate_;
+
+ // Interface for UI methods. May be null.
+ scoped_ptr<UIDelegate> ui_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateDelegate);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_DELEGATE_H_
diff --git a/chromium/extensions/browser/api/networking_private/networking_private_delegate_factory.cc b/chromium/extensions/browser/api/networking_private/networking_private_delegate_factory.cc
new file mode 100644
index 00000000000..972cda00ba1
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/networking_private_delegate_factory.cc
@@ -0,0 +1,113 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/networking_private/networking_private_delegate_factory.h"
+
+#include "build/build_config.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/extensions_browser_client.h"
+
+#if defined(OS_CHROMEOS)
+#include "extensions/browser/api/networking_private/networking_private_chromeos.h"
+#elif defined(OS_LINUX)
+#include "extensions/browser/api/networking_private/networking_private_linux.h"
+#elif defined(OS_WIN) || defined(OS_MACOSX)
+#include "components/wifi/wifi_service.h"
+#include "extensions/browser/api/networking_private/networking_private_service_client.h"
+#endif
+
+namespace extensions {
+
+using content::BrowserContext;
+
+NetworkingPrivateDelegateFactory::VerifyDelegateFactory::
+ VerifyDelegateFactory() {
+}
+
+NetworkingPrivateDelegateFactory::VerifyDelegateFactory::
+ ~VerifyDelegateFactory() {
+}
+
+NetworkingPrivateDelegateFactory::UIDelegateFactory::UIDelegateFactory() {}
+
+NetworkingPrivateDelegateFactory::UIDelegateFactory::~UIDelegateFactory() {}
+
+// static
+NetworkingPrivateDelegate*
+NetworkingPrivateDelegateFactory::GetForBrowserContext(
+ BrowserContext* browser_context) {
+ return static_cast<NetworkingPrivateDelegate*>(
+ GetInstance()->GetServiceForBrowserContext(browser_context, true));
+}
+
+// static
+NetworkingPrivateDelegateFactory*
+NetworkingPrivateDelegateFactory::GetInstance() {
+ return base::Singleton<NetworkingPrivateDelegateFactory>::get();
+}
+
+NetworkingPrivateDelegateFactory::NetworkingPrivateDelegateFactory()
+ : BrowserContextKeyedServiceFactory(
+ "NetworkingPrivateDelegate",
+ BrowserContextDependencyManager::GetInstance()) {
+}
+
+NetworkingPrivateDelegateFactory::~NetworkingPrivateDelegateFactory() {
+}
+
+void NetworkingPrivateDelegateFactory::SetVerifyDelegateFactory(
+ scoped_ptr<VerifyDelegateFactory> factory) {
+ verify_factory_.reset(factory.release());
+}
+
+void NetworkingPrivateDelegateFactory::SetUIDelegateFactory(
+ scoped_ptr<UIDelegateFactory> factory) {
+ ui_factory_.reset(factory.release());
+}
+
+KeyedService* NetworkingPrivateDelegateFactory::BuildServiceInstanceFor(
+ BrowserContext* browser_context) const {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ scoped_ptr<NetworkingPrivateDelegate::VerifyDelegate> verify_delegate;
+ if (verify_factory_)
+ verify_delegate = verify_factory_->CreateDelegate();
+
+ NetworkingPrivateDelegate* delegate;
+#if defined(OS_CHROMEOS)
+ delegate = new NetworkingPrivateChromeOS(browser_context,
+ std::move(verify_delegate));
+#elif defined(OS_LINUX)
+ delegate =
+ new NetworkingPrivateLinux(browser_context, std::move(verify_delegate));
+#elif defined(OS_WIN) || defined(OS_MACOSX)
+ scoped_ptr<wifi::WiFiService> wifi_service(wifi::WiFiService::Create());
+ delegate = new NetworkingPrivateServiceClient(std::move(wifi_service),
+ std::move(verify_delegate));
+#else
+ NOTREACHED();
+ delegate = nullptr;
+#endif
+
+ if (ui_factory_)
+ delegate->set_ui_delegate(ui_factory_->CreateDelegate());
+
+ return delegate;
+}
+
+BrowserContext* NetworkingPrivateDelegateFactory::GetBrowserContextToUse(
+ BrowserContext* context) const {
+ return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+}
+
+bool NetworkingPrivateDelegateFactory::ServiceIsCreatedWithBrowserContext()
+ const {
+ return false;
+}
+
+bool NetworkingPrivateDelegateFactory::ServiceIsNULLWhileTesting() const {
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/networking_private/networking_private_delegate_factory.h b/chromium/extensions/browser/api/networking_private/networking_private_delegate_factory.h
new file mode 100644
index 00000000000..e048d72b3e8
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/networking_private_delegate_factory.h
@@ -0,0 +1,88 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_DELEGATE_FACTORY_H_
+#define EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_DELEGATE_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/singleton.h"
+#include "build/build_config.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "extensions/browser/api/networking_private/networking_private_delegate.h"
+
+namespace context {
+class BrowserContext;
+}
+
+namespace extensions {
+
+#if defined(OS_WIN) || defined(OS_MACOSX)
+class NetworkingPrivateServiceClient;
+#endif
+
+// Factory for creating NetworkingPrivateDelegate instances as a keyed service.
+// NetworkingPrivateDelegate supports the networkingPrivate API.
+class NetworkingPrivateDelegateFactory
+ : public BrowserContextKeyedServiceFactory {
+ public:
+ // There needs to be a way to allow the application (e.g. Chrome) to provide
+ // additional delegates to the API (in src/extensions). Since this factory is
+ // already a singleton, it provides a good place to hold these delegate
+ // factories. See NetworkingPrivateDelegate for the delegate declarations.
+
+ class VerifyDelegateFactory {
+ public:
+ VerifyDelegateFactory();
+ virtual ~VerifyDelegateFactory();
+
+ virtual scoped_ptr<NetworkingPrivateDelegate::VerifyDelegate>
+ CreateDelegate() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(VerifyDelegateFactory);
+ };
+
+ class UIDelegateFactory {
+ public:
+ UIDelegateFactory();
+ virtual ~UIDelegateFactory();
+
+ virtual scoped_ptr<NetworkingPrivateDelegate::UIDelegate>
+ CreateDelegate() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UIDelegateFactory);
+ };
+
+ // Provide optional factories for creating delegate instances.
+ void SetVerifyDelegateFactory(scoped_ptr<VerifyDelegateFactory> factory);
+ void SetUIDelegateFactory(scoped_ptr<UIDelegateFactory> factory);
+
+ static NetworkingPrivateDelegate* GetForBrowserContext(
+ content::BrowserContext* browser_context);
+ static NetworkingPrivateDelegateFactory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<NetworkingPrivateDelegateFactory>;
+
+ NetworkingPrivateDelegateFactory();
+ ~NetworkingPrivateDelegateFactory() override;
+
+ // BrowserContextKeyedServiceFactory:
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* browser_context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+ bool ServiceIsCreatedWithBrowserContext() const override;
+ bool ServiceIsNULLWhileTesting() const override;
+
+ scoped_ptr<VerifyDelegateFactory> verify_factory_;
+ scoped_ptr<UIDelegateFactory> ui_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateDelegateFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_DELEGATE_FACTORY_H_
diff --git a/chromium/extensions/browser/api/networking_private/networking_private_delegate_observer.h b/chromium/extensions/browser/api/networking_private/networking_private_delegate_observer.h
new file mode 100644
index 00000000000..2a294f33c25
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/networking_private_delegate_observer.h
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_DELEGATE_OBSERVER_H_
+#define EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_DELEGATE_OBSERVER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+
+namespace extensions {
+
+// Implemented by event handlers so they are notified when a change event
+// occurs. Triggered by NetworkingPrivateServiceClient or
+// NetworkingPrivateLinux. Not used on Chrome OS.
+class NetworkingPrivateDelegateObserver {
+ public:
+ // Notifes observers when properties may have changed for the networks listed
+ // in |network_guids|.
+ virtual void OnNetworksChangedEvent(
+ const std::vector<std::string>& network_guids) = 0;
+
+ // Notifies observers that the list of networks changed. |network_guids|
+ // contains the complete list of network guids.
+ virtual void OnNetworkListChangedEvent(
+ const std::vector<std::string>& network_guids) = 0;
+
+ protected:
+ virtual ~NetworkingPrivateDelegateObserver() {}
+
+ private:
+ DISALLOW_ASSIGN(NetworkingPrivateDelegateObserver);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_DELEGATE_OBSERVER_H_
diff --git a/chromium/extensions/browser/api/networking_private/networking_private_event_router.h b/chromium/extensions/browser/api/networking_private/networking_private_event_router.h
new file mode 100644
index 00000000000..2cdc476d75a
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/networking_private_event_router.h
@@ -0,0 +1,38 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_EVENT_ROUTER_H_
+#define EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_EVENT_ROUTER_H_
+
+#include "base/macros.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/browser/event_router.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class NetworkingPrivateDelegate;
+
+// This is an event router that will observe listeners to |NetworksChanged| and
+// |NetworkListChanged| events. On ChromeOS it will forward these events
+// from the NetworkStateHandler to the JavaScript Networking API.
+class NetworkingPrivateEventRouter : public KeyedService,
+ public EventRouter::Observer {
+ public:
+ static NetworkingPrivateEventRouter* Create(
+ content::BrowserContext* browser_context);
+
+ protected:
+ NetworkingPrivateEventRouter() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateEventRouter);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_EVENT_ROUTER_H_
diff --git a/chromium/extensions/browser/api/networking_private/networking_private_event_router_chromeos.cc b/chromium/extensions/browser/api/networking_private/networking_private_event_router_chromeos.cc
new file mode 100644
index 00000000000..259fcd3671a
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/networking_private_event_router_chromeos.cc
@@ -0,0 +1,280 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/networking_private/networking_private_event_router.h"
+
+#include "base/json/json_writer.h"
+#include "base/macros.h"
+#include "chromeos/network/device_state.h"
+#include "chromeos/network/network_event_log.h"
+#include "chromeos/network/network_state.h"
+#include "chromeos/network/network_state_handler.h"
+#include "chromeos/network/network_state_handler_observer.h"
+#include "chromeos/network/onc/onc_signature.h"
+#include "chromeos/network/onc/onc_translator.h"
+#include "chromeos/network/portal_detector/network_portal_detector.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/onc/onc_constants.h"
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/api/networking_private/networking_private_api.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/common/api/networking_private.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+using chromeos::DeviceState;
+using chromeos::NetworkHandler;
+using chromeos::NetworkPortalDetector;
+using chromeos::NetworkState;
+using chromeos::NetworkStateHandler;
+
+namespace extensions {
+
+class NetworkingPrivateEventRouterImpl
+ : public NetworkingPrivateEventRouter,
+ public chromeos::NetworkStateHandlerObserver,
+ public NetworkPortalDetector::Observer {
+ public:
+ explicit NetworkingPrivateEventRouterImpl(content::BrowserContext* context);
+ ~NetworkingPrivateEventRouterImpl() override;
+
+ protected:
+ // KeyedService overrides:
+ void Shutdown() override;
+
+ // EventRouter::Observer overrides:
+ void OnListenerAdded(const EventListenerInfo& details) override;
+ void OnListenerRemoved(const EventListenerInfo& details) override;
+
+ // NetworkStateHandlerObserver overrides:
+ void NetworkListChanged() override;
+ void DeviceListChanged() override;
+ void NetworkPropertiesUpdated(const NetworkState* network) override;
+ void DevicePropertiesUpdated(const DeviceState* device) override;
+
+ // NetworkPortalDetector::Observer overrides:
+ void OnPortalDetectionCompleted(
+ const NetworkState* network,
+ const NetworkPortalDetector::CaptivePortalState& state) override;
+
+ private:
+ // Decide if we should listen for network changes or not. If there are any
+ // JavaScript listeners registered for the onNetworkChanged event, then we
+ // want to register for change notification from the network state handler.
+ // Otherwise, we want to unregister and not be listening to network changes.
+ void StartOrStopListeningForNetworkChanges();
+
+ content::BrowserContext* context_;
+ bool listening_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateEventRouterImpl);
+};
+
+NetworkingPrivateEventRouterImpl::NetworkingPrivateEventRouterImpl(
+ content::BrowserContext* context)
+ : context_(context), listening_(false) {
+ // Register with the event router so we know when renderers are listening to
+ // our events. We first check and see if there *is* an event router, because
+ // some unit tests try to create all context services, but don't initialize
+ // the event router first.
+ EventRouter* event_router = EventRouter::Get(context_);
+ if (event_router) {
+ event_router->RegisterObserver(
+ this, api::networking_private::OnNetworksChanged::kEventName);
+ event_router->RegisterObserver(
+ this, api::networking_private::OnNetworkListChanged::kEventName);
+ event_router->RegisterObserver(
+ this, api::networking_private::OnDeviceStateListChanged::kEventName);
+ event_router->RegisterObserver(
+ this, api::networking_private::OnPortalDetectionCompleted::kEventName);
+ StartOrStopListeningForNetworkChanges();
+ }
+}
+
+NetworkingPrivateEventRouterImpl::~NetworkingPrivateEventRouterImpl() {
+ DCHECK(!listening_);
+}
+
+void NetworkingPrivateEventRouterImpl::Shutdown() {
+ // Unregister with the event router. We first check and see if there *is* an
+ // event router, because some unit tests try to shutdown all context services,
+ // but didn't initialize the event router first.
+ EventRouter* event_router = EventRouter::Get(context_);
+ if (event_router)
+ event_router->UnregisterObserver(this);
+
+ if (listening_) {
+ NetworkHandler::Get()->network_state_handler()->RemoveObserver(this,
+ FROM_HERE);
+ }
+ listening_ = false;
+}
+
+void NetworkingPrivateEventRouterImpl::OnListenerAdded(
+ const EventListenerInfo& details) {
+ // Start listening to events from the network state handler.
+ StartOrStopListeningForNetworkChanges();
+}
+
+void NetworkingPrivateEventRouterImpl::OnListenerRemoved(
+ const EventListenerInfo& details) {
+ // Stop listening to events from the network state handler if there are no
+ // more listeners.
+ StartOrStopListeningForNetworkChanges();
+}
+
+void NetworkingPrivateEventRouterImpl::StartOrStopListeningForNetworkChanges() {
+ EventRouter* event_router = EventRouter::Get(context_);
+ bool should_listen =
+ event_router->HasEventListener(
+ api::networking_private::OnNetworksChanged::kEventName) ||
+ event_router->HasEventListener(
+ api::networking_private::OnNetworkListChanged::kEventName) ||
+ event_router->HasEventListener(
+ api::networking_private::OnDeviceStateListChanged::kEventName) ||
+ event_router->HasEventListener(
+ api::networking_private::OnPortalDetectionCompleted::kEventName);
+
+ if (should_listen && !listening_) {
+ NetworkHandler::Get()->network_state_handler()->AddObserver(this,
+ FROM_HERE);
+ if (chromeos::network_portal_detector::IsInitialized())
+ chromeos::network_portal_detector::GetInstance()->AddObserver(this);
+ } else if (!should_listen && listening_) {
+ NetworkHandler::Get()->network_state_handler()->RemoveObserver(this,
+ FROM_HERE);
+ if (chromeos::network_portal_detector::IsInitialized())
+ chromeos::network_portal_detector::GetInstance()->RemoveObserver(this);
+ }
+ listening_ = should_listen;
+}
+
+void NetworkingPrivateEventRouterImpl::NetworkListChanged() {
+ EventRouter* event_router = EventRouter::Get(context_);
+ if (!event_router->HasEventListener(
+ api::networking_private::OnNetworkListChanged::kEventName)) {
+ return;
+ }
+
+ NetworkStateHandler::NetworkStateList networks;
+ NetworkHandler::Get()->network_state_handler()->GetVisibleNetworkList(
+ &networks);
+ std::vector<std::string> changes;
+ for (NetworkStateHandler::NetworkStateList::const_iterator iter =
+ networks.begin();
+ iter != networks.end(); ++iter) {
+ changes.push_back((*iter)->guid());
+ }
+
+ scoped_ptr<base::ListValue> args(
+ api::networking_private::OnNetworkListChanged::Create(changes));
+ scoped_ptr<Event> extension_event(
+ new Event(events::NETWORKING_PRIVATE_ON_NETWORK_LIST_CHANGED,
+ api::networking_private::OnNetworkListChanged::kEventName,
+ std::move(args)));
+ event_router->BroadcastEvent(std::move(extension_event));
+}
+
+void NetworkingPrivateEventRouterImpl::DeviceListChanged() {
+ EventRouter* event_router = EventRouter::Get(context_);
+ if (!event_router->HasEventListener(
+ api::networking_private::OnDeviceStateListChanged::kEventName)) {
+ return;
+ }
+
+ scoped_ptr<base::ListValue> args(
+ api::networking_private::OnDeviceStateListChanged::Create());
+ scoped_ptr<Event> extension_event(
+ new Event(events::NETWORKING_PRIVATE_ON_DEVICE_STATE_LIST_CHANGED,
+ api::networking_private::OnDeviceStateListChanged::kEventName,
+ std::move(args)));
+ event_router->BroadcastEvent(std::move(extension_event));
+}
+
+void NetworkingPrivateEventRouterImpl::NetworkPropertiesUpdated(
+ const NetworkState* network) {
+ EventRouter* event_router = EventRouter::Get(context_);
+ if (!event_router->HasEventListener(
+ api::networking_private::OnNetworksChanged::kEventName)) {
+ NET_LOG_EVENT("NetworkingPrivate.NetworkPropertiesUpdated: No Listeners",
+ network->path());
+ return;
+ }
+ NET_LOG_EVENT("NetworkingPrivate.NetworkPropertiesUpdated", network->path());
+ scoped_ptr<base::ListValue> args(
+ api::networking_private::OnNetworksChanged::Create(
+ std::vector<std::string>(1, network->guid())));
+ scoped_ptr<Event> extension_event(new Event(
+ events::NETWORKING_PRIVATE_ON_NETWORKS_CHANGED,
+ api::networking_private::OnNetworksChanged::kEventName, std::move(args)));
+ event_router->BroadcastEvent(std::move(extension_event));
+}
+
+void NetworkingPrivateEventRouterImpl::DevicePropertiesUpdated(
+ const DeviceState* device) {
+ // DeviceState changes may affect Cellular networks.
+ if (device->type() != shill::kTypeCellular)
+ return;
+
+ NetworkStateHandler::NetworkStateList cellular_networks;
+ NetworkHandler::Get()->network_state_handler()->GetNetworkListByType(
+ chromeos::NetworkTypePattern::Cellular(), false /* configured_only */,
+ true /* visible_only */, -1 /* default limit */, &cellular_networks);
+ for (const NetworkState* network : cellular_networks) {
+ NetworkPropertiesUpdated(network);
+ }
+}
+
+void NetworkingPrivateEventRouterImpl::OnPortalDetectionCompleted(
+ const NetworkState* network,
+ const NetworkPortalDetector::CaptivePortalState& state) {
+ const std::string path = network ? network->guid() : std::string();
+
+ EventRouter* event_router = EventRouter::Get(context_);
+ if (!event_router->HasEventListener(
+ api::networking_private::OnPortalDetectionCompleted::kEventName)) {
+ NET_LOG_EVENT("NetworkingPrivate.OnPortalDetectionCompleted: No Listeners",
+ path);
+ return;
+ }
+ NET_LOG_EVENT("NetworkingPrivate.OnPortalDetectionCompleted", path);
+
+ api::networking_private::CaptivePortalStatus status =
+ api::networking_private::CAPTIVE_PORTAL_STATUS_UNKNOWN;
+ switch (state.status) {
+ case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN:
+ status = api::networking_private::CAPTIVE_PORTAL_STATUS_UNKNOWN;
+ break;
+ case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE:
+ status = api::networking_private::CAPTIVE_PORTAL_STATUS_OFFLINE;
+ break;
+ case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE:
+ status = api::networking_private::CAPTIVE_PORTAL_STATUS_ONLINE;
+ break;
+ case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL:
+ status = api::networking_private::CAPTIVE_PORTAL_STATUS_PORTAL;
+ break;
+ case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED:
+ status = api::networking_private::CAPTIVE_PORTAL_STATUS_PROXYAUTHREQUIRED;
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+
+ scoped_ptr<base::ListValue> args(
+ api::networking_private::OnPortalDetectionCompleted::Create(path,
+ status));
+ scoped_ptr<Event> extension_event(
+ new Event(events::NETWORKING_PRIVATE_ON_PORTAL_DETECTION_COMPLETED,
+ api::networking_private::OnPortalDetectionCompleted::kEventName,
+ std::move(args)));
+ event_router->BroadcastEvent(std::move(extension_event));
+}
+
+NetworkingPrivateEventRouter* NetworkingPrivateEventRouter::Create(
+ content::BrowserContext* context) {
+ return new NetworkingPrivateEventRouterImpl(context);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/networking_private/networking_private_event_router_factory.cc b/chromium/extensions/browser/api/networking_private/networking_private_event_router_factory.cc
new file mode 100644
index 00000000000..0ca9caeec7d
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/networking_private_event_router_factory.cc
@@ -0,0 +1,61 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/networking_private/networking_private_event_router_factory.h"
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/api/networking_private/networking_private_delegate_factory.h"
+#include "extensions/browser/api/networking_private/networking_private_event_router.h"
+#include "extensions/browser/extension_system_provider.h"
+#include "extensions/browser/extensions_browser_client.h"
+
+namespace extensions {
+
+// static
+NetworkingPrivateEventRouter*
+NetworkingPrivateEventRouterFactory::GetForProfile(
+ content::BrowserContext* context) {
+ return static_cast<NetworkingPrivateEventRouter*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+NetworkingPrivateEventRouterFactory*
+NetworkingPrivateEventRouterFactory::GetInstance() {
+ return base::Singleton<NetworkingPrivateEventRouterFactory>::get();
+}
+
+NetworkingPrivateEventRouterFactory::NetworkingPrivateEventRouterFactory()
+ : BrowserContextKeyedServiceFactory(
+ "NetworkingPrivateEventRouter",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
+ DependsOn(NetworkingPrivateDelegateFactory::GetInstance());
+}
+
+NetworkingPrivateEventRouterFactory::~NetworkingPrivateEventRouterFactory() {
+}
+
+KeyedService* NetworkingPrivateEventRouterFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ return NetworkingPrivateEventRouter::Create(context);
+}
+
+content::BrowserContext*
+NetworkingPrivateEventRouterFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+}
+
+bool NetworkingPrivateEventRouterFactory::ServiceIsCreatedWithBrowserContext()
+ const {
+ return true;
+}
+
+bool NetworkingPrivateEventRouterFactory::ServiceIsNULLWhileTesting() const {
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/networking_private/networking_private_event_router_factory.h b/chromium/extensions/browser/api/networking_private/networking_private_event_router_factory.h
new file mode 100644
index 00000000000..79edb632d4f
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/networking_private_event_router_factory.h
@@ -0,0 +1,53 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_EVENT_ROUTER_FACTORY_H_
+#define EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_EVENT_ROUTER_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace extensions {
+
+class NetworkingPrivateEventRouter;
+
+// This is a factory class used by the BrowserContextDependencyManager
+// to instantiate the networking event router per profile (since the extension
+// event router is per profile).
+class NetworkingPrivateEventRouterFactory
+ : public BrowserContextKeyedServiceFactory {
+ public:
+ // Returns the NetworkingPrivateEventRouter for |profile|, creating it if
+ // it is not yet created.
+ static NetworkingPrivateEventRouter* GetForProfile(
+ content::BrowserContext* context);
+
+ // Returns the NetworkingPrivateEventRouterFactory instance.
+ static NetworkingPrivateEventRouterFactory* GetInstance();
+
+ protected:
+ // BrowserContextKeyedBaseFactory overrides:
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+ bool ServiceIsCreatedWithBrowserContext() const override;
+ bool ServiceIsNULLWhileTesting() const override;
+
+ private:
+ friend struct base::DefaultSingletonTraits<
+ NetworkingPrivateEventRouterFactory>;
+
+ NetworkingPrivateEventRouterFactory();
+ ~NetworkingPrivateEventRouterFactory() override;
+
+ // BrowserContextKeyedServiceFactory:
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* profile) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateEventRouterFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_EVENT_ROUTER_FACTORY_H_
diff --git a/chromium/extensions/browser/api/networking_private/networking_private_event_router_nonchromeos.cc b/chromium/extensions/browser/api/networking_private/networking_private_event_router_nonchromeos.cc
new file mode 100644
index 00000000000..99db0a132c9
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/networking_private_event_router_nonchromeos.cc
@@ -0,0 +1,165 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/networking_private/networking_private_event_router.h"
+
+#include "base/macros.h"
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/api/networking_private/networking_private_api.h"
+#include "extensions/browser/api/networking_private/networking_private_delegate_factory.h"
+#include "extensions/browser/api/networking_private/networking_private_delegate_observer.h"
+#include "extensions/common/api/networking_private.h"
+
+namespace extensions {
+
+// This is an event router that will observe listeners to |NetworksChanged| and
+// |NetworkListChanged| events.
+class NetworkingPrivateEventRouterImpl
+ : public NetworkingPrivateEventRouter,
+ public NetworkingPrivateDelegateObserver {
+ public:
+ explicit NetworkingPrivateEventRouterImpl(
+ content::BrowserContext* browser_context);
+ ~NetworkingPrivateEventRouterImpl() override;
+
+ protected:
+ // KeyedService overrides:
+ void Shutdown() override;
+
+ // EventRouter::Observer overrides:
+ void OnListenerAdded(const EventListenerInfo& details) override;
+ void OnListenerRemoved(const EventListenerInfo& details) override;
+
+ // NetworkingPrivateDelegateObserver overrides:
+ void OnNetworksChangedEvent(
+ const std::vector<std::string>& network_guids) override;
+ void OnNetworkListChangedEvent(
+ const std::vector<std::string>& network_guids) override;
+
+ private:
+ // Decide if we should listen for network changes or not. If there are any
+ // JavaScript listeners registered for the onNetworkChanged event, then we
+ // want to register for change notification from the network state handler.
+ // Otherwise, we want to unregister and not be listening to network changes.
+ void StartOrStopListeningForNetworkChanges();
+
+ content::BrowserContext* browser_context_;
+ bool listening_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateEventRouterImpl);
+};
+
+NetworkingPrivateEventRouterImpl::NetworkingPrivateEventRouterImpl(
+ content::BrowserContext* browser_context)
+ : browser_context_(browser_context), listening_(false) {
+ // Register with the event router so we know when renderers are listening to
+ // our events. We first check and see if there *is* an event router, because
+ // some unit tests try to create all profile services, but don't initialize
+ // the event router first.
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ if (!event_router)
+ return;
+ event_router->RegisterObserver(
+ this, api::networking_private::OnNetworksChanged::kEventName);
+ event_router->RegisterObserver(
+ this, api::networking_private::OnNetworkListChanged::kEventName);
+ StartOrStopListeningForNetworkChanges();
+}
+
+NetworkingPrivateEventRouterImpl::~NetworkingPrivateEventRouterImpl() {
+ DCHECK(!listening_);
+}
+
+void NetworkingPrivateEventRouterImpl::Shutdown() {
+ // Unregister with the event router. We first check and see if there *is* an
+ // event router, because some unit tests try to shutdown all profile services,
+ // but didn't initialize the event router first.
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ if (event_router)
+ event_router->UnregisterObserver(this);
+
+ if (!listening_)
+ return;
+ listening_ = false;
+ NetworkingPrivateDelegate* delegate =
+ NetworkingPrivateDelegateFactory::GetForBrowserContext(browser_context_);
+ if (delegate)
+ delegate->RemoveObserver(this);
+}
+
+void NetworkingPrivateEventRouterImpl::OnListenerAdded(
+ const EventListenerInfo& details) {
+ // Start listening to events from the network state handler.
+ StartOrStopListeningForNetworkChanges();
+}
+
+void NetworkingPrivateEventRouterImpl::OnListenerRemoved(
+ const EventListenerInfo& details) {
+ // Stop listening to events from the network state handler if there are no
+ // more listeners.
+ StartOrStopListeningForNetworkChanges();
+}
+
+void NetworkingPrivateEventRouterImpl::StartOrStopListeningForNetworkChanges() {
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ if (!event_router)
+ return;
+
+ bool should_listen =
+ event_router->HasEventListener(
+ api::networking_private::OnNetworksChanged::kEventName) ||
+ event_router->HasEventListener(
+ api::networking_private::OnNetworkListChanged::kEventName);
+
+ if (should_listen && !listening_) {
+ NetworkingPrivateDelegate* delegate =
+ NetworkingPrivateDelegateFactory::GetForBrowserContext(
+ browser_context_);
+ if (delegate)
+ delegate->AddObserver(this);
+ }
+ if (!should_listen && listening_) {
+ NetworkingPrivateDelegate* delegate =
+ NetworkingPrivateDelegateFactory::GetForBrowserContext(
+ browser_context_);
+ if (delegate)
+ delegate->RemoveObserver(this);
+ }
+
+ listening_ = should_listen;
+}
+
+void NetworkingPrivateEventRouterImpl::OnNetworksChangedEvent(
+ const std::vector<std::string>& network_guids) {
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ if (!event_router)
+ return;
+ scoped_ptr<base::ListValue> args(
+ api::networking_private::OnNetworksChanged::Create(network_guids));
+ scoped_ptr<Event> netchanged_event(new Event(
+ events::NETWORKING_PRIVATE_ON_NETWORKS_CHANGED,
+ api::networking_private::OnNetworksChanged::kEventName, std::move(args)));
+ event_router->BroadcastEvent(std::move(netchanged_event));
+}
+
+void NetworkingPrivateEventRouterImpl::OnNetworkListChangedEvent(
+ const std::vector<std::string>& network_guids) {
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ if (!event_router)
+ return;
+ scoped_ptr<base::ListValue> args(
+ api::networking_private::OnNetworkListChanged::Create(network_guids));
+ scoped_ptr<Event> netlistchanged_event(
+ new Event(events::NETWORKING_PRIVATE_ON_NETWORK_LIST_CHANGED,
+ api::networking_private::OnNetworkListChanged::kEventName,
+ std::move(args)));
+ event_router->BroadcastEvent(std::move(netlistchanged_event));
+}
+
+NetworkingPrivateEventRouter* NetworkingPrivateEventRouter::Create(
+ content::BrowserContext* browser_context) {
+ return new NetworkingPrivateEventRouterImpl(browser_context);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/networking_private/networking_private_linux.cc b/chromium/extensions/browser/api/networking_private/networking_private_linux.cc
new file mode 100644
index 00000000000..dd489034c13
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/networking_private_linux.cc
@@ -0,0 +1,1207 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/networking_private/networking_private_linux.h"
+
+#include <stddef.h>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "components/onc/onc_constants.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "dbus/bus.h"
+#include "dbus/message.h"
+#include "dbus/object_path.h"
+#include "dbus/object_proxy.h"
+#include "extensions/browser/api/networking_private/network_config_dbus_constants_linux.h"
+#include "extensions/browser/api/networking_private/networking_private_api.h"
+#include "extensions/browser/api/networking_private/networking_private_delegate_observer.h"
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace extensions {
+
+namespace {
+// Access Point info strings.
+const char kAccessPointInfoName[] = "Name";
+const char kAccessPointInfoGuid[] = "GUID";
+const char kAccessPointInfoConnectable[] = "Connectable";
+const char kAccessPointInfoConnectionState[] = "ConnectionState";
+const char kAccessPointInfoType[] = "Type";
+const char kAccessPointInfoTypeWifi[] = "WiFi";
+const char kAccessPointInfoWifiSignalStrengthDotted[] = "WiFi.SignalStrength";
+const char kAccessPointInfoWifiSecurityDotted[] = "WiFi.Security";
+
+// Access point security type strings.
+const char kAccessPointSecurityNone[] = "None";
+const char kAccessPointSecurityUnknown[] = "Unknown";
+const char kAccessPointSecurityWpaPsk[] = "WPA-PSK";
+const char kAccessPointSecurity9021X[] = "WEP-8021X";
+
+// Parses the GUID which contains 3 pieces of relevant information. The
+// object path to the network device, the object path of the access point,
+// and the ssid.
+bool ParseNetworkGuid(const std::string& guid,
+ std::string* device_path,
+ std::string* access_point_path,
+ std::string* ssid) {
+ std::vector<std::string> guid_parts =
+ base::SplitString(guid, "|", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ if (guid_parts.size() != 3) {
+ return false;
+ }
+
+ *device_path = guid_parts[0];
+ *access_point_path = guid_parts[1];
+ *ssid = guid_parts[2];
+
+ if (device_path->empty() || access_point_path->empty() || ssid->empty()) {
+ return false;
+ }
+
+ return true;
+}
+
+// Simplified helper to parse the SSID from the GUID.
+bool GuidToSsid(const std::string& guid, std::string* ssid) {
+ std::string unused_1;
+ std::string unused_2;
+ return ParseNetworkGuid(guid, &unused_1, &unused_2, ssid);
+}
+
+// Iterates over the map cloning the contained networks to a
+// list then returns the list.
+scoped_ptr<base::ListValue> CopyNetworkMapToList(
+ const NetworkingPrivateLinux::NetworkMap& network_map) {
+ scoped_ptr<base::ListValue> network_list(new base::ListValue);
+
+ for (const auto& network : network_map) {
+ network_list->Append(network.second->DeepCopy());
+ }
+
+ return network_list;
+}
+
+// Constructs a network guid from its constituent parts.
+std::string ConstructNetworkGuid(const dbus::ObjectPath& device_path,
+ const dbus::ObjectPath& access_point_path,
+ const std::string& ssid) {
+ return device_path.value() + "|" + access_point_path.value() + "|" + ssid;
+}
+
+// Logs that the method is not implemented and reports |kErrorNotSupported|
+// to the failure callback.
+void ReportNotSupported(
+ const std::string& method_name,
+ const NetworkingPrivateDelegate::FailureCallback& failure_callback) {
+ LOG(WARNING) << method_name << " is not supported";
+ failure_callback.Run(extensions::networking_private::kErrorNotSupported);
+}
+
+// Fires the appropriate callback when the network connect operation succeeds
+// or fails.
+void OnNetworkConnectOperationCompleted(
+ scoped_ptr<std::string> error,
+ const NetworkingPrivateDelegate::VoidCallback& success_callback,
+ const NetworkingPrivateDelegate::FailureCallback& failure_callback) {
+ if (!error->empty()) {
+ failure_callback.Run(*error);
+ return;
+ }
+ success_callback.Run();
+}
+
+// Fires the appropriate callback when the network properties are returned
+// from the |dbus_thread_|.
+void GetCachedNetworkPropertiesCallback(
+ scoped_ptr<std::string> error,
+ scoped_ptr<base::DictionaryValue> properties,
+ const NetworkingPrivateDelegate::DictionaryCallback& success_callback,
+ const NetworkingPrivateDelegate::FailureCallback& failure_callback) {
+ if (!error->empty()) {
+ failure_callback.Run(*error);
+ return;
+ }
+ success_callback.Run(std::move(properties));
+}
+
+} // namespace
+
+NetworkingPrivateLinux::NetworkingPrivateLinux(
+ content::BrowserContext* browser_context,
+ scoped_ptr<VerifyDelegate> verify_delegate)
+ : NetworkingPrivateDelegate(std::move(verify_delegate)),
+ browser_context_(browser_context),
+ dbus_thread_("Networking Private DBus"),
+ network_manager_proxy_(NULL) {
+ base::Thread::Options thread_options(base::MessageLoop::Type::TYPE_IO, 0);
+
+ dbus_thread_.StartWithOptions(thread_options);
+ dbus_thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&NetworkingPrivateLinux::Initialize, base::Unretained(this)));
+}
+
+NetworkingPrivateLinux::~NetworkingPrivateLinux() {
+ dbus_thread_.Stop();
+}
+
+void NetworkingPrivateLinux::AssertOnDBusThread() {
+ DCHECK(dbus_task_runner_->RunsTasksOnCurrentThread());
+}
+
+void NetworkingPrivateLinux::Initialize() {
+ dbus_task_runner_ = dbus_thread_.task_runner();
+ // This has to be called after the task runner is initialized.
+ AssertOnDBusThread();
+
+ dbus::Bus::Options dbus_options;
+ dbus_options.bus_type = dbus::Bus::SYSTEM;
+ dbus_options.connection_type = dbus::Bus::PRIVATE;
+ dbus_options.dbus_task_runner = dbus_task_runner_;
+
+ dbus_ = new dbus::Bus(dbus_options);
+ network_manager_proxy_ = dbus_->GetObjectProxy(
+ networking_private::kNetworkManagerNamespace,
+ dbus::ObjectPath(networking_private::kNetworkManagerPath));
+
+ if (!network_manager_proxy_) {
+ LOG(ERROR) << "Platform does not support NetworkManager over DBUS";
+ }
+
+ network_map_.reset(new NetworkMap());
+}
+
+bool NetworkingPrivateLinux::CheckNetworkManagerSupported(
+ const FailureCallback& failure_callback) {
+ if (!network_manager_proxy_) {
+ ReportNotSupported("NetworkManager over DBus", failure_callback);
+ return false;
+ }
+
+ return true;
+}
+
+void NetworkingPrivateLinux::GetProperties(
+ const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ GetState(guid, success_callback, failure_callback);
+}
+
+void NetworkingPrivateLinux::GetManagedProperties(
+ const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ ReportNotSupported("GetManagedProperties", failure_callback);
+}
+
+void NetworkingPrivateLinux::GetState(
+ const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ if (!CheckNetworkManagerSupported(failure_callback))
+ return;
+
+ scoped_ptr<std::string> error(new std::string);
+ scoped_ptr<base::DictionaryValue> network_properties(
+ new base::DictionaryValue);
+
+ // Runs GetCachedNetworkProperties on |dbus_thread|.
+ dbus_thread_.task_runner()->PostTaskAndReply(
+ FROM_HERE, base::Bind(&NetworkingPrivateLinux::GetCachedNetworkProperties,
+ base::Unretained(this), guid,
+ base::Unretained(network_properties.get()),
+ base::Unretained(error.get())),
+ base::Bind(&GetCachedNetworkPropertiesCallback, base::Passed(&error),
+ base::Passed(&network_properties), success_callback,
+ failure_callback));
+}
+
+void NetworkingPrivateLinux::GetCachedNetworkProperties(
+ const std::string& guid,
+ base::DictionaryValue* properties,
+ std::string* error) {
+ AssertOnDBusThread();
+ std::string ssid;
+
+ if (!GuidToSsid(guid, &ssid)) {
+ *error = "Invalid Network GUID format";
+ return;
+ }
+
+ NetworkMap::const_iterator network_iter =
+ network_map_->find(base::UTF8ToUTF16(ssid));
+ if (network_iter == network_map_->end()) {
+ *error = "Unknown network GUID";
+ return;
+ }
+
+ // Make a copy of the properties out of the cached map.
+ scoped_ptr<base::DictionaryValue> temp_properties(
+ network_iter->second->DeepCopy());
+
+ // Swap the new copy into the dictionary that is shared with the reply.
+ properties->Swap(temp_properties.get());
+}
+
+void NetworkingPrivateLinux::SetProperties(
+ const std::string& guid,
+ scoped_ptr<base::DictionaryValue> properties,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ ReportNotSupported("SetProperties", failure_callback);
+}
+
+void NetworkingPrivateLinux::CreateNetwork(
+ bool shared,
+ scoped_ptr<base::DictionaryValue> properties,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ ReportNotSupported("CreateNetwork", failure_callback);
+}
+
+void NetworkingPrivateLinux::ForgetNetwork(
+ const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ // TODO(zentaro): Implement for Linux.
+ ReportNotSupported("ForgetNetwork", failure_callback);
+}
+
+void NetworkingPrivateLinux::GetNetworks(
+ const std::string& network_type,
+ bool configured_only,
+ bool visible_only,
+ int limit,
+ const NetworkListCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ if (!CheckNetworkManagerSupported(failure_callback)) {
+ return;
+ }
+
+ scoped_ptr<NetworkMap> network_map(new NetworkMap);
+
+ if (!(network_type == ::onc::network_type::kWiFi ||
+ network_type == ::onc::network_type::kWireless ||
+ network_type == ::onc::network_type::kAllTypes)) {
+ // Only enumerating WiFi networks is supported on linux.
+ ReportNotSupported("GetNetworks with network_type=" + network_type,
+ failure_callback);
+ return;
+ }
+
+ // Runs GetAllWiFiAccessPoints on the dbus_thread and returns the
+ // results back to OnAccessPointsFound where the callback is fired.
+ dbus_thread_.task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&NetworkingPrivateLinux::GetAllWiFiAccessPoints,
+ base::Unretained(this), configured_only, visible_only, limit,
+ base::Unretained(network_map.get())),
+ base::Bind(&NetworkingPrivateLinux::OnAccessPointsFound,
+ base::Unretained(this), base::Passed(&network_map),
+ success_callback, failure_callback));
+}
+
+bool NetworkingPrivateLinux::GetNetworksForScanRequest() {
+ if (!network_manager_proxy_) {
+ return false;
+ }
+
+ scoped_ptr<NetworkMap> network_map(new NetworkMap);
+
+ // Runs GetAllWiFiAccessPoints on the dbus_thread and returns the
+ // results back to SendNetworkListChangedEvent to fire the event. No
+ // callbacks are used in this case.
+ dbus_thread_.task_runner()->PostTaskAndReply(
+ FROM_HERE, base::Bind(&NetworkingPrivateLinux::GetAllWiFiAccessPoints,
+ base::Unretained(this), false /* configured_only */,
+ false /* visible_only */, 0 /* limit */,
+ base::Unretained(network_map.get())),
+ base::Bind(&NetworkingPrivateLinux::OnAccessPointsFoundViaScan,
+ base::Unretained(this), base::Passed(&network_map)));
+
+ return true;
+}
+
+// Constructs the network configuration message and connects to the network.
+// The message is of the form:
+// {
+// '802-11-wireless': {
+// 'ssid': 'FooNetwork'
+// }
+// }
+void NetworkingPrivateLinux::ConnectToNetwork(const std::string& guid,
+ std::string* error) {
+ AssertOnDBusThread();
+ std::string device_path_str;
+ std::string access_point_path_str;
+ std::string ssid;
+ DVLOG(1) << "Connecting to network GUID " << guid;
+
+ if (!ParseNetworkGuid(guid, &device_path_str, &access_point_path_str,
+ &ssid)) {
+ *error = "Invalid Network GUID format";
+ return;
+ }
+
+ // Set the connection state to connecting in the map.
+ if (!SetConnectionStateAndPostEvent(guid, ssid,
+ ::onc::connection_state::kConnecting)) {
+ *error = "Unknown network GUID";
+ return;
+ }
+
+ dbus::ObjectPath device_path(device_path_str);
+ dbus::ObjectPath access_point_path(access_point_path_str);
+
+ dbus::MethodCall method_call(
+ networking_private::kNetworkManagerNamespace,
+ networking_private::kNetworkManagerAddAndActivateConnectionMethod);
+ dbus::MessageWriter builder(&method_call);
+
+ // Build up the settings nested dictionary.
+ dbus::MessageWriter array_writer(&method_call);
+ builder.OpenArray("{sa{sv}}", &array_writer);
+
+ dbus::MessageWriter dict_writer(&method_call);
+ array_writer.OpenDictEntry(&dict_writer);
+ // TODO(zentaro): Support other network types. Currently only WiFi is
+ // supported.
+ dict_writer.AppendString(
+ networking_private::kNetworkManagerConnectionConfig80211Wireless);
+
+ dbus::MessageWriter wifi_array(&method_call);
+ dict_writer.OpenArray("{sv}", &wifi_array);
+
+ dbus::MessageWriter wifi_dict_writer(&method_call);
+ wifi_array.OpenDictEntry(&wifi_dict_writer);
+ wifi_dict_writer.AppendString(
+ networking_private::kNetworkManagerConnectionConfigSsid);
+
+ dbus::MessageWriter variant_writer(&method_call);
+ wifi_dict_writer.OpenVariant("ay", &variant_writer);
+ variant_writer.AppendArrayOfBytes(
+ reinterpret_cast<const uint8_t*>(ssid.c_str()), ssid.size());
+
+ // Close all the arrays and dicts.
+ wifi_dict_writer.CloseContainer(&variant_writer);
+ wifi_array.CloseContainer(&wifi_dict_writer);
+ dict_writer.CloseContainer(&wifi_array);
+ array_writer.CloseContainer(&dict_writer);
+ builder.CloseContainer(&array_writer);
+
+ builder.AppendObjectPath(device_path);
+ builder.AppendObjectPath(access_point_path);
+
+ scoped_ptr<dbus::Response> response(
+ network_manager_proxy_->CallMethodAndBlock(
+ &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
+ if (!response) {
+ LOG(ERROR) << "Failed to add a new connection";
+ *error = "Failed to connect.";
+
+ // Set the connection state to NotConnected in the map.
+ SetConnectionStateAndPostEvent(guid, ssid,
+ ::onc::connection_state::kNotConnected);
+ return;
+ }
+
+ dbus::MessageReader reader(response.get());
+ dbus::ObjectPath connection_settings_path;
+ dbus::ObjectPath active_connection_path;
+
+ if (!reader.PopObjectPath(&connection_settings_path)) {
+ LOG(ERROR) << "Unexpected response for add connection path "
+ << ": " << response->ToString();
+ *error = "Failed to connect.";
+
+ // Set the connection state to NotConnected in the map.
+ SetConnectionStateAndPostEvent(guid, ssid,
+ ::onc::connection_state::kNotConnected);
+ return;
+ }
+
+ if (!reader.PopObjectPath(&active_connection_path)) {
+ LOG(ERROR) << "Unexpected response for connection path "
+ << ": " << response->ToString();
+ *error = "Failed to connect.";
+
+ // Set the connection state to NotConnected in the map.
+ SetConnectionStateAndPostEvent(guid, ssid,
+ ::onc::connection_state::kNotConnected);
+ return;
+ }
+
+ // Set the connection state to Connected in the map.
+ SetConnectionStateAndPostEvent(guid, ssid,
+ ::onc::connection_state::kConnected);
+ return;
+}
+
+void NetworkingPrivateLinux::DisconnectFromNetwork(const std::string& guid,
+ std::string* error) {
+ AssertOnDBusThread();
+ std::string device_path_str;
+ std::string access_point_path_str;
+ std::string ssid;
+ DVLOG(1) << "Disconnecting from network GUID " << guid;
+
+ if (!ParseNetworkGuid(guid, &device_path_str, &access_point_path_str,
+ &ssid)) {
+ *error = "Invalid Network GUID format";
+ return;
+ }
+
+ scoped_ptr<NetworkMap> network_map(new NetworkMap);
+ GetAllWiFiAccessPoints(false /* configured_only */, false /* visible_only */,
+ 0 /* limit */, network_map.get());
+
+ NetworkMap::const_iterator network_iter =
+ network_map->find(base::UTF8ToUTF16(ssid));
+ if (network_iter == network_map->end()) {
+ // This network doesn't exist so there's nothing to do.
+ return;
+ }
+
+ std::string connection_state;
+ network_iter->second->GetString(kAccessPointInfoConnectionState,
+ &connection_state);
+ if (connection_state == ::onc::connection_state::kNotConnected) {
+ // Already disconnected so nothing to do.
+ return;
+ }
+
+ // It's not disconnected so disconnect it.
+ dbus::ObjectProxy* device_proxy =
+ dbus_->GetObjectProxy(networking_private::kNetworkManagerNamespace,
+ dbus::ObjectPath(device_path_str));
+ dbus::MethodCall method_call(
+ networking_private::kNetworkManagerDeviceNamespace,
+ networking_private::kNetworkManagerDisconnectMethod);
+ scoped_ptr<dbus::Response> response(device_proxy->CallMethodAndBlock(
+ &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
+
+ if (!response) {
+ LOG(WARNING) << "Failed to disconnect network on device "
+ << device_path_str;
+ *error = "Failed to disconnect network";
+ }
+}
+
+void NetworkingPrivateLinux::StartConnect(
+ const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ if (!CheckNetworkManagerSupported(failure_callback))
+ return;
+
+ scoped_ptr<std::string> error(new std::string);
+
+ // Runs ConnectToNetwork on |dbus_thread|.
+ dbus_thread_.task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&NetworkingPrivateLinux::ConnectToNetwork,
+ base::Unretained(this), guid, base::Unretained(error.get())),
+ base::Bind(&OnNetworkConnectOperationCompleted, base::Passed(&error),
+ success_callback, failure_callback));
+}
+
+void NetworkingPrivateLinux::StartDisconnect(
+ const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ if (!CheckNetworkManagerSupported(failure_callback))
+ return;
+
+ scoped_ptr<std::string> error(new std::string);
+
+ // Runs DisconnectFromNetwork on |dbus_thread|.
+ dbus_thread_.task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&NetworkingPrivateLinux::DisconnectFromNetwork,
+ base::Unretained(this), guid, base::Unretained(error.get())),
+ base::Bind(&OnNetworkConnectOperationCompleted, base::Passed(&error),
+ success_callback, failure_callback));
+}
+
+void NetworkingPrivateLinux::SetWifiTDLSEnabledState(
+ const std::string& ip_or_mac_address,
+ bool enabled,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ ReportNotSupported("SetWifiTDLSEnabledState", failure_callback);
+}
+
+void NetworkingPrivateLinux::GetWifiTDLSStatus(
+ const std::string& ip_or_mac_address,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ ReportNotSupported("GetWifiTDLSStatus", failure_callback);
+}
+
+void NetworkingPrivateLinux::GetCaptivePortalStatus(
+ const std::string& guid,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ ReportNotSupported("GetCaptivePortalStatus", failure_callback);
+}
+
+void NetworkingPrivateLinux::UnlockCellularSim(
+ const std::string& guid,
+ const std::string& pin,
+ const std::string& puk,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ ReportNotSupported("UnlockCellularSim", failure_callback);
+}
+
+void NetworkingPrivateLinux::SetCellularSimState(
+ const std::string& guid,
+ bool require_pin,
+ const std::string& current_pin,
+ const std::string& new_pin,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ ReportNotSupported("SetCellularSimState", failure_callback);
+}
+
+scoped_ptr<base::ListValue> NetworkingPrivateLinux::GetEnabledNetworkTypes() {
+ scoped_ptr<base::ListValue> network_list(new base::ListValue);
+ network_list->AppendString(::onc::network_type::kWiFi);
+ return network_list;
+}
+
+scoped_ptr<NetworkingPrivateDelegate::DeviceStateList>
+NetworkingPrivateLinux::GetDeviceStateList() {
+ scoped_ptr<DeviceStateList> device_state_list(new DeviceStateList);
+ scoped_ptr<api::networking_private::DeviceStateProperties> properties(
+ new api::networking_private::DeviceStateProperties);
+ properties->type = api::networking_private::NETWORK_TYPE_WIFI;
+ properties->state = api::networking_private::DEVICE_STATE_TYPE_ENABLED;
+ device_state_list->push_back(std::move(properties));
+ return device_state_list;
+}
+
+bool NetworkingPrivateLinux::EnableNetworkType(const std::string& type) {
+ return false;
+}
+
+bool NetworkingPrivateLinux::DisableNetworkType(const std::string& type) {
+ return false;
+}
+
+bool NetworkingPrivateLinux::RequestScan() {
+ return GetNetworksForScanRequest();
+}
+
+void NetworkingPrivateLinux::AddObserver(
+ NetworkingPrivateDelegateObserver* observer) {
+ network_events_observers_.AddObserver(observer);
+}
+
+void NetworkingPrivateLinux::RemoveObserver(
+ NetworkingPrivateDelegateObserver* observer) {
+ network_events_observers_.RemoveObserver(observer);
+}
+
+void NetworkingPrivateLinux::OnAccessPointsFound(
+ scoped_ptr<NetworkMap> network_map,
+ const NetworkListCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ scoped_ptr<base::ListValue> network_list = CopyNetworkMapToList(*network_map);
+ // Give ownership to the member variable.
+ network_map_.swap(network_map);
+ SendNetworkListChangedEvent(*network_list);
+ success_callback.Run(std::move(network_list));
+}
+
+void NetworkingPrivateLinux::OnAccessPointsFoundViaScan(
+ scoped_ptr<NetworkMap> network_map) {
+ scoped_ptr<base::ListValue> network_list = CopyNetworkMapToList(*network_map);
+ // Give ownership to the member variable.
+ network_map_.swap(network_map);
+ SendNetworkListChangedEvent(*network_list);
+}
+
+void NetworkingPrivateLinux::SendNetworkListChangedEvent(
+ const base::ListValue& network_list) {
+ GuidList guidsForEventCallback;
+
+ for (const auto& network : network_list) {
+ std::string guid;
+ base::DictionaryValue* dict;
+ if (network->GetAsDictionary(&dict)) {
+ if (dict->GetString(kAccessPointInfoGuid, &guid)) {
+ guidsForEventCallback.push_back(guid);
+ }
+ }
+ }
+
+ OnNetworkListChangedEventOnUIThread(guidsForEventCallback);
+}
+
+bool NetworkingPrivateLinux::GetNetworkDevices(
+ std::vector<dbus::ObjectPath>* device_paths) {
+ AssertOnDBusThread();
+ dbus::MethodCall method_call(
+ networking_private::kNetworkManagerNamespace,
+ networking_private::kNetworkManagerGetDevicesMethod);
+
+ scoped_ptr<dbus::Response> device_response(
+ network_manager_proxy_->CallMethodAndBlock(
+ &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
+
+ if (!device_response) {
+ return false;
+ }
+
+ dbus::MessageReader reader(device_response.get());
+ if (!reader.PopArrayOfObjectPaths(device_paths)) {
+ LOG(WARNING) << "Unexpected response: " << device_response->ToString();
+ return false;
+ }
+
+ return true;
+}
+
+NetworkingPrivateLinux::DeviceType NetworkingPrivateLinux::GetDeviceType(
+ const dbus::ObjectPath& device_path) {
+ AssertOnDBusThread();
+ dbus::ObjectProxy* device_proxy = dbus_->GetObjectProxy(
+ networking_private::kNetworkManagerNamespace, device_path);
+ dbus::MethodCall method_call(DBUS_INTERFACE_PROPERTIES,
+ networking_private::kNetworkManagerGetMethod);
+ dbus::MessageWriter builder(&method_call);
+ builder.AppendString(networking_private::kNetworkManagerDeviceNamespace);
+ builder.AppendString(networking_private::kNetworkManagerDeviceType);
+
+ scoped_ptr<dbus::Response> response(device_proxy->CallMethodAndBlock(
+ &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
+
+ if (!response) {
+ LOG(ERROR) << "Failed to get the device type for device "
+ << device_path.value();
+ return NetworkingPrivateLinux::NM_DEVICE_TYPE_UNKNOWN;
+ }
+
+ dbus::MessageReader reader(response.get());
+ uint32_t device_type = 0;
+ if (!reader.PopVariantOfUint32(&device_type)) {
+ LOG(ERROR) << "Unexpected response for device " << device_type << ": "
+ << response->ToString();
+ return NM_DEVICE_TYPE_UNKNOWN;
+ }
+
+ return static_cast<NetworkingPrivateLinux::DeviceType>(device_type);
+}
+
+void NetworkingPrivateLinux::GetAllWiFiAccessPoints(bool configured_only,
+ bool visible_only,
+ int limit,
+ NetworkMap* network_map) {
+ AssertOnDBusThread();
+ // TODO(zentaro): The filters are not implemented and are ignored.
+ std::vector<dbus::ObjectPath> device_paths;
+ if (!GetNetworkDevices(&device_paths)) {
+ LOG(ERROR) << "Failed to enumerate network devices";
+ return;
+ }
+
+ for (const auto& device_path : device_paths) {
+ NetworkingPrivateLinux::DeviceType device_type = GetDeviceType(device_path);
+
+ // Get the access points for each WiFi adapter. Other network types are
+ // ignored.
+ if (device_type != NetworkingPrivateLinux::NM_DEVICE_TYPE_WIFI)
+ continue;
+
+ // Found a wlan adapter
+ if (!AddAccessPointsFromDevice(device_path, network_map)) {
+ // Ignore devices we can't enumerate.
+ LOG(WARNING) << "Failed to add access points from device "
+ << device_path.value();
+ }
+ }
+}
+
+scoped_ptr<dbus::Response> NetworkingPrivateLinux::GetAccessPointProperty(
+ dbus::ObjectProxy* access_point_proxy,
+ const std::string& property_name) {
+ AssertOnDBusThread();
+ dbus::MethodCall method_call(DBUS_INTERFACE_PROPERTIES,
+ networking_private::kNetworkManagerGetMethod);
+ dbus::MessageWriter builder(&method_call);
+ builder.AppendString(networking_private::kNetworkManagerAccessPointNamespace);
+ builder.AppendString(property_name);
+ scoped_ptr<dbus::Response> response = access_point_proxy->CallMethodAndBlock(
+ &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
+ if (!response) {
+ LOG(ERROR) << "Failed to get property for " << property_name;
+ }
+ return response;
+}
+
+bool NetworkingPrivateLinux::GetAccessPointInfo(
+ const dbus::ObjectPath& access_point_path,
+ const scoped_ptr<base::DictionaryValue>& access_point_info) {
+ AssertOnDBusThread();
+ dbus::ObjectProxy* access_point_proxy = dbus_->GetObjectProxy(
+ networking_private::kNetworkManagerNamespace, access_point_path);
+
+ // Read the SSID. The GUID is derived from the Ssid.
+ {
+ scoped_ptr<dbus::Response> response(GetAccessPointProperty(
+ access_point_proxy, networking_private::kNetworkManagerSsidProperty));
+
+ if (!response) {
+ return false;
+ }
+
+ // The response should contain a variant that contains an array of bytes.
+ dbus::MessageReader reader(response.get());
+ dbus::MessageReader variant_reader(response.get());
+ if (!reader.PopVariant(&variant_reader)) {
+ LOG(ERROR) << "Unexpected response for " << access_point_path.value()
+ << ": " << response->ToString();
+ return false;
+ }
+
+ const uint8_t* ssid_bytes = NULL;
+ size_t ssid_length = 0;
+ if (!variant_reader.PopArrayOfBytes(&ssid_bytes, &ssid_length)) {
+ LOG(ERROR) << "Unexpected response for " << access_point_path.value()
+ << ": " << response->ToString();
+ return false;
+ }
+
+ std::string ssidUTF8(ssid_bytes, ssid_bytes + ssid_length);
+ base::string16 ssid = base::UTF8ToUTF16(ssidUTF8);
+
+ access_point_info->SetString(kAccessPointInfoName, ssid);
+ }
+
+ // Read signal strength.
+ {
+ scoped_ptr<dbus::Response> response(GetAccessPointProperty(
+ access_point_proxy,
+ networking_private::kNetworkManagerStrengthProperty));
+ if (!response) {
+ return false;
+ }
+
+ dbus::MessageReader reader(response.get());
+ uint8_t strength = 0;
+ if (!reader.PopVariantOfByte(&strength)) {
+ LOG(ERROR) << "Unexpected response for " << access_point_path.value()
+ << ": " << response->ToString();
+ return false;
+ }
+
+ access_point_info->SetInteger(kAccessPointInfoWifiSignalStrengthDotted,
+ strength);
+ }
+
+ // Read the security type. This is from the WpaFlags and RsnFlags property
+ // which are of the same type and can be OR'd together to find all supported
+ // security modes.
+
+ uint32_t wpa_security_flags = 0;
+ {
+ scoped_ptr<dbus::Response> response(GetAccessPointProperty(
+ access_point_proxy,
+ networking_private::kNetworkManagerWpaFlagsProperty));
+ if (!response) {
+ return false;
+ }
+
+ dbus::MessageReader reader(response.get());
+
+ if (!reader.PopVariantOfUint32(&wpa_security_flags)) {
+ LOG(ERROR) << "Unexpected response for " << access_point_path.value()
+ << ": " << response->ToString();
+ return false;
+ }
+ }
+
+ uint32_t rsn_security_flags = 0;
+ {
+ scoped_ptr<dbus::Response> response(GetAccessPointProperty(
+ access_point_proxy,
+ networking_private::kNetworkManagerRsnFlagsProperty));
+ if (!response) {
+ return false;
+ }
+
+ dbus::MessageReader reader(response.get());
+
+ if (!reader.PopVariantOfUint32(&rsn_security_flags)) {
+ LOG(ERROR) << "Unexpected response for " << access_point_path.value()
+ << ": " << response->ToString();
+ return false;
+ }
+ }
+
+ std::string security;
+ MapSecurityFlagsToString(rsn_security_flags | wpa_security_flags, &security);
+ access_point_info->SetString(kAccessPointInfoWifiSecurityDotted, security);
+ access_point_info->SetString(kAccessPointInfoType, kAccessPointInfoTypeWifi);
+ access_point_info->SetBoolean(kAccessPointInfoConnectable, true);
+ return true;
+}
+
+bool NetworkingPrivateLinux::AddAccessPointsFromDevice(
+ const dbus::ObjectPath& device_path,
+ NetworkMap* network_map) {
+ AssertOnDBusThread();
+ dbus::ObjectPath connected_access_point;
+ if (!GetConnectedAccessPoint(device_path, &connected_access_point)) {
+ return false;
+ }
+
+ dbus::ObjectProxy* device_proxy = dbus_->GetObjectProxy(
+ networking_private::kNetworkManagerNamespace, device_path);
+ dbus::MethodCall method_call(
+ networking_private::kNetworkManagerWirelessDeviceNamespace,
+ networking_private::kNetworkManagerGetAccessPointsMethod);
+ scoped_ptr<dbus::Response> response(device_proxy->CallMethodAndBlock(
+ &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
+
+ if (!response) {
+ LOG(WARNING) << "Failed to get access points data for "
+ << device_path.value();
+ return false;
+ }
+
+ dbus::MessageReader reader(response.get());
+ std::vector<dbus::ObjectPath> access_point_paths;
+ if (!reader.PopArrayOfObjectPaths(&access_point_paths)) {
+ LOG(ERROR) << "Unexpected response for " << device_path.value() << ": "
+ << response->ToString();
+ return false;
+ }
+
+ for (const auto& access_point_path : access_point_paths) {
+ scoped_ptr<base::DictionaryValue> access_point(new base::DictionaryValue);
+
+ if (GetAccessPointInfo(access_point_path, access_point)) {
+ std::string connection_state =
+ (access_point_path == connected_access_point)
+ ? ::onc::connection_state::kConnected
+ : ::onc::connection_state::kNotConnected;
+
+ access_point->SetString(kAccessPointInfoConnectionState,
+ connection_state);
+ std::string ssid;
+ access_point->GetString(kAccessPointInfoName, &ssid);
+
+ std::string network_guid =
+ ConstructNetworkGuid(device_path, access_point_path, ssid);
+
+ // Adds the network to the map. Since each SSID can actually have multiple
+ // access point paths, this consolidates them. If it is already
+ // in the map it updates the signal strength and GUID paths if this
+ // network is stronger or the one that is connected.
+ AddOrUpdateAccessPoint(network_map, network_guid, access_point);
+ }
+ }
+
+ return true;
+}
+
+void NetworkingPrivateLinux::AddOrUpdateAccessPoint(
+ NetworkMap* network_map,
+ const std::string& network_guid,
+ scoped_ptr<base::DictionaryValue>& access_point) {
+ base::string16 ssid;
+ std::string connection_state;
+ int signal_strength;
+
+ access_point->GetString(kAccessPointInfoConnectionState, &connection_state);
+ access_point->GetInteger(kAccessPointInfoWifiSignalStrengthDotted,
+ &signal_strength);
+ access_point->GetString(kAccessPointInfoName, &ssid);
+ access_point->SetString(kAccessPointInfoGuid, network_guid);
+
+ NetworkMap::iterator existing_access_point_iter = network_map->find(ssid);
+
+ if (existing_access_point_iter == network_map->end()) {
+ // Unseen access point. Add it to the map.
+ network_map->insert(NetworkMap::value_type(
+ ssid, linked_ptr<base::DictionaryValue>(access_point.release())));
+ } else {
+ // Already seen access point. Update the record if this is the connected
+ // record or if the signal strength is higher. But don't override a weaker
+ // access point if that is the one that is connected.
+ int existing_signal_strength;
+ linked_ptr<base::DictionaryValue>& existing_access_point =
+ existing_access_point_iter->second;
+ existing_access_point->GetInteger(kAccessPointInfoWifiSignalStrengthDotted,
+ &existing_signal_strength);
+
+ std::string existing_connection_state;
+ existing_access_point->GetString(kAccessPointInfoConnectionState,
+ &existing_connection_state);
+
+ if ((connection_state == ::onc::connection_state::kConnected) ||
+ (!(existing_connection_state == ::onc::connection_state::kConnected) &&
+ signal_strength > existing_signal_strength)) {
+ existing_access_point->SetString(kAccessPointInfoConnectionState,
+ connection_state);
+ existing_access_point->SetInteger(
+ kAccessPointInfoWifiSignalStrengthDotted, signal_strength);
+ existing_access_point->SetString(kAccessPointInfoGuid, network_guid);
+ }
+ }
+}
+
+void NetworkingPrivateLinux::MapSecurityFlagsToString(uint32_t security_flags,
+ std::string* security) {
+ // Valid values are None, WEP-PSK, WEP-8021X, WPA-PSK, WPA-EAP
+ if (security_flags == NetworkingPrivateLinux::NM_802_11_AP_SEC_NONE) {
+ *security = kAccessPointSecurityNone;
+ } else if (security_flags &
+ NetworkingPrivateLinux::NM_802_11_AP_SEC_KEY_MGMT_PSK) {
+ *security = kAccessPointSecurityWpaPsk;
+ } else if (security_flags &
+ NetworkingPrivateLinux::NM_802_11_AP_SEC_KEY_MGMT_802_1X) {
+ *security = kAccessPointSecurity9021X;
+ } else {
+ DVLOG(1) << "Security flag mapping is missing. Found " << security_flags;
+ *security = kAccessPointSecurityUnknown;
+ }
+
+ DVLOG(1) << "Network security setting " << *security;
+}
+
+bool NetworkingPrivateLinux::GetConnectedAccessPoint(
+ dbus::ObjectPath device_path,
+ dbus::ObjectPath* access_point_path) {
+ AssertOnDBusThread();
+ dbus::MethodCall method_call(DBUS_INTERFACE_PROPERTIES,
+ networking_private::kNetworkManagerGetMethod);
+ dbus::MessageWriter builder(&method_call);
+ builder.AppendString(networking_private::kNetworkManagerNamespace);
+ builder.AppendString(networking_private::kNetworkManagerActiveConnections);
+
+ scoped_ptr<dbus::Response> response(
+ network_manager_proxy_->CallMethodAndBlock(
+ &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
+
+ if (!response) {
+ LOG(WARNING) << "Failed to get a list of active connections";
+ return false;
+ }
+
+ dbus::MessageReader reader(response.get());
+ dbus::MessageReader variant_reader(response.get());
+ if (!reader.PopVariant(&variant_reader)) {
+ LOG(ERROR) << "Unexpected response: " << response->ToString();
+ return false;
+ }
+
+ std::vector<dbus::ObjectPath> connection_paths;
+ if (!variant_reader.PopArrayOfObjectPaths(&connection_paths)) {
+ LOG(ERROR) << "Unexpected response: " << response->ToString();
+ return false;
+ }
+
+ for (const auto& connection_path : connection_paths) {
+ dbus::ObjectPath connections_device_path;
+ if (!GetDeviceOfConnection(connection_path, &connections_device_path)) {
+ return false;
+ }
+
+ if (connections_device_path == device_path) {
+ if (!GetAccessPointForConnection(connection_path, access_point_path)) {
+ return false;
+ }
+
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool NetworkingPrivateLinux::GetDeviceOfConnection(
+ dbus::ObjectPath connection_path,
+ dbus::ObjectPath* device_path) {
+ AssertOnDBusThread();
+ dbus::ObjectProxy* connection_proxy = dbus_->GetObjectProxy(
+ networking_private::kNetworkManagerNamespace, connection_path);
+
+ if (!connection_proxy) {
+ return false;
+ }
+
+ dbus::MethodCall method_call(DBUS_INTERFACE_PROPERTIES,
+ networking_private::kNetworkManagerGetMethod);
+ dbus::MessageWriter builder(&method_call);
+ builder.AppendString(
+ networking_private::kNetworkManagerActiveConnectionNamespace);
+ builder.AppendString("Devices");
+
+ scoped_ptr<dbus::Response> response(connection_proxy->CallMethodAndBlock(
+ &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
+
+ if (!response) {
+ LOG(ERROR) << "Failed to get devices";
+ return false;
+ }
+
+ dbus::MessageReader reader(response.get());
+ dbus::MessageReader variant_reader(response.get());
+ if (!reader.PopVariant(&variant_reader)) {
+ LOG(ERROR) << "Unexpected response: " << response->ToString();
+ return false;
+ }
+
+ std::vector<dbus::ObjectPath> device_paths;
+ if (!variant_reader.PopArrayOfObjectPaths(&device_paths)) {
+ LOG(ERROR) << "Unexpected response: " << response->ToString();
+ return false;
+ }
+
+ if (device_paths.size() == 1) {
+ *device_path = device_paths[0];
+
+ return true;
+ }
+
+ return false;
+}
+
+bool NetworkingPrivateLinux::GetAccessPointForConnection(
+ dbus::ObjectPath connection_path,
+ dbus::ObjectPath* access_point_path) {
+ AssertOnDBusThread();
+ dbus::ObjectProxy* connection_proxy = dbus_->GetObjectProxy(
+ networking_private::kNetworkManagerNamespace, connection_path);
+
+ if (!connection_proxy) {
+ return false;
+ }
+
+ dbus::MethodCall method_call(DBUS_INTERFACE_PROPERTIES,
+ networking_private::kNetworkManagerGetMethod);
+ dbus::MessageWriter builder(&method_call);
+ builder.AppendString(
+ networking_private::kNetworkManagerActiveConnectionNamespace);
+ builder.AppendString(networking_private::kNetworkManagerSpecificObject);
+
+ scoped_ptr<dbus::Response> response(connection_proxy->CallMethodAndBlock(
+ &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
+
+ if (!response) {
+ LOG(WARNING) << "Failed to get access point from active connection";
+ return false;
+ }
+
+ dbus::MessageReader reader(response.get());
+ dbus::MessageReader variant_reader(response.get());
+ if (!reader.PopVariant(&variant_reader)) {
+ LOG(ERROR) << "Unexpected response: " << response->ToString();
+ return false;
+ }
+
+ if (!variant_reader.PopObjectPath(access_point_path)) {
+ LOG(ERROR) << "Unexpected response: " << response->ToString();
+ return false;
+ }
+
+ return true;
+}
+
+bool NetworkingPrivateLinux::SetConnectionStateAndPostEvent(
+ const std::string& guid,
+ const std::string& ssid,
+ const std::string& connection_state) {
+ AssertOnDBusThread();
+
+ NetworkMap::iterator network_iter =
+ network_map_->find(base::UTF8ToUTF16(ssid));
+ if (network_iter == network_map_->end()) {
+ return false;
+ }
+
+ DVLOG(1) << "Setting connection state of " << ssid << " to "
+ << connection_state;
+
+ // If setting this network to connected, find the previously connected network
+ // and disconnect that one. Also retain the guid of that network to fire a
+ // changed event.
+ std::string connected_network_guid;
+ if (connection_state == ::onc::connection_state::kConnected) {
+ for (auto& network : *network_map_) {
+ std::string other_connection_state;
+ if (network.second->GetString(kAccessPointInfoConnectionState,
+ &other_connection_state)) {
+ if (other_connection_state == ::onc::connection_state::kConnected) {
+ network.second->GetString(kAccessPointInfoGuid,
+ &connected_network_guid);
+ network.second->SetString(kAccessPointInfoConnectionState,
+ ::onc::connection_state::kNotConnected);
+ }
+ }
+ }
+ }
+
+ // Set the status.
+ network_iter->second->SetString(kAccessPointInfoConnectionState,
+ connection_state);
+
+ scoped_ptr<GuidList> changed_networks(new GuidList());
+ changed_networks->push_back(guid);
+
+ // Only add a second network if it exists and it is not the same as the
+ // network already being added to the list.
+ if (!connected_network_guid.empty() && connected_network_guid != guid) {
+ changed_networks->push_back(connected_network_guid);
+ }
+
+ PostOnNetworksChangedToUIThread(std::move(changed_networks));
+ return true;
+}
+
+void NetworkingPrivateLinux::OnNetworksChangedEventOnUIThread(
+ const GuidList& network_guids) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ FOR_EACH_OBSERVER(NetworkingPrivateDelegateObserver,
+ network_events_observers_,
+ OnNetworksChangedEvent(network_guids));
+}
+
+void NetworkingPrivateLinux::OnNetworkListChangedEventOnUIThread(
+ const GuidList& network_guids) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ FOR_EACH_OBSERVER(NetworkingPrivateDelegateObserver,
+ network_events_observers_,
+ OnNetworkListChangedEvent(network_guids));
+}
+
+void NetworkingPrivateLinux::PostOnNetworksChangedToUIThread(
+ scoped_ptr<GuidList> guid_list) {
+ AssertOnDBusThread();
+
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI, FROM_HERE,
+ base::Bind(&NetworkingPrivateLinux::OnNetworksChangedEventTask,
+ base::Unretained(this), base::Passed(&guid_list)));
+}
+
+void NetworkingPrivateLinux::OnNetworksChangedEventTask(
+ scoped_ptr<GuidList> guid_list) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ OnNetworksChangedEventOnUIThread(*guid_list);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/networking_private/networking_private_linux.h b/chromium/extensions/browser/api/networking_private/networking_private_linux.h
new file mode 100644
index 00000000000..e33c7b889cd
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/networking_private_linux.h
@@ -0,0 +1,283 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_LINUX_H_
+#define EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_LINUX_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/thread.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/browser/api/networking_private/networking_private_delegate.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace dbus {
+class Bus;
+class ObjectPath;
+class ObjectProxy;
+class Response;
+};
+
+namespace extensions {
+
+// Linux NetworkingPrivateDelegate implementation.
+class NetworkingPrivateLinux : public NetworkingPrivateDelegate {
+ public:
+ typedef std::map<base::string16, linked_ptr<base::DictionaryValue>>
+ NetworkMap;
+
+ typedef std::vector<std::string> GuidList;
+
+ NetworkingPrivateLinux(content::BrowserContext* browser_context,
+ scoped_ptr<VerifyDelegate> verify_delegate);
+
+ // NetworkingPrivateDelegate
+ void GetProperties(const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void GetManagedProperties(const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void GetState(const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void SetProperties(const std::string& guid,
+ scoped_ptr<base::DictionaryValue> properties,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void CreateNetwork(bool shared,
+ scoped_ptr<base::DictionaryValue> properties,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void ForgetNetwork(const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void GetNetworks(const std::string& network_type,
+ bool configured_only,
+ bool visible_only,
+ int limit,
+ const NetworkListCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void StartConnect(const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void StartDisconnect(const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void SetWifiTDLSEnabledState(
+ const std::string& ip_or_mac_address,
+ bool enabled,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void GetWifiTDLSStatus(const std::string& ip_or_mac_address,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void GetCaptivePortalStatus(const std::string& guid,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void UnlockCellularSim(const std::string& guid,
+ const std::string& pin,
+ const std::string& puk,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void SetCellularSimState(const std::string& guid,
+ bool require_pin,
+ const std::string& current_pin,
+ const std::string& new_pin,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+
+ scoped_ptr<base::ListValue> GetEnabledNetworkTypes() override;
+ scoped_ptr<DeviceStateList> GetDeviceStateList() override;
+ bool EnableNetworkType(const std::string& type) override;
+ bool DisableNetworkType(const std::string& type) override;
+ bool RequestScan() override;
+ void AddObserver(NetworkingPrivateDelegateObserver* observer) override;
+ void RemoveObserver(NetworkingPrivateDelegateObserver* observer) override;
+
+ private:
+ ~NetworkingPrivateLinux() override;
+
+ // https://developer.gnome.org/NetworkManager/unstable/spec.html#type-NM_DEVICE_TYPE
+ enum DeviceType {
+ NM_DEVICE_TYPE_UNKNOWN = 0,
+ NM_DEVICE_TYPE_ETHERNET = 1,
+ NM_DEVICE_TYPE_WIFI = 2
+ };
+
+ // https://developer.gnome.org/NetworkManager/unstable/spec.html#type-NM_802_11_AP_SEC
+ enum WirelessSecurityFlags {
+ NM_802_11_AP_SEC_NONE = 0x0,
+ NM_802_11_AP_SEC_PAIR_WEP40 = 0x1,
+ NM_802_11_AP_SEC_PAIR_WEP104 = 0x2,
+ NM_802_11_AP_SEC_PAIR_TKIP = 0x4,
+ NM_802_11_AP_SEC_PAIR_CCMP = 0x8,
+ NM_802_11_AP_SEC_GROUP_WEP40 = 0x10,
+ NM_802_11_AP_SEC_GROUP_WEP104 = 0x20,
+ NM_802_11_AP_SEC_GROUP_TKIP = 0x40,
+ NM_802_11_AP_SEC_GROUP_CCMP = 0x80,
+ NM_802_11_AP_SEC_KEY_MGMT_PSK = 0x100,
+ NM_802_11_AP_SEC_KEY_MGMT_802_1X = 0x200
+ };
+
+ // Initializes the DBus instance and the proxy object to the network manager.
+ // Must be called on |dbus_thread_|.
+ void Initialize();
+
+ // Enumerates all WiFi adapters and scans for access points on each.
+ // Results are appended into the provided |network_map|.
+ // Must be called on |dbus_thread_|.
+ void GetAllWiFiAccessPoints(bool configured_only,
+ bool visible_only,
+ int limit,
+ NetworkMap* network_map);
+
+ // Helper function for handling a scan request. This function acts similarly
+ // to the public GetNetworks to get visible networks and fire the
+ // OnNetworkListChanged event, however no callbacks are called.
+ bool GetNetworksForScanRequest();
+
+ // Initiates the connection to the network.
+ // Must be called on |dbus_thread_|.
+ void ConnectToNetwork(const std::string& guid, std::string* error);
+
+ // Initiates disconnection from the specified network.
+ // Must be called on |dbus_thread_|
+ void DisconnectFromNetwork(const std::string& guid, std::string* error);
+
+ // Checks whether the current thread is the DBus thread. If not, DCHECK will
+ // fail.
+ void AssertOnDBusThread();
+
+ // Verifies that NetworkManager interfaces are initialized.
+ // Returns true if NetworkManager is initialized, otherwise returns false
+ // and the API call will fail with |kErrorNotSupported|.
+ bool CheckNetworkManagerSupported(const FailureCallback& failure_callback);
+
+ // Gets all network devices on the system.
+ // Returns false if there is an error getting the device paths.
+ bool GetNetworkDevices(std::vector<dbus::ObjectPath>* device_paths);
+
+ // Returns the DeviceType (eg. WiFi, ethernet). corresponding to the
+ // |device_path|.
+ DeviceType GetDeviceType(const dbus::ObjectPath& device_path);
+
+ // Helper function to enumerate WiFi networks. Takes a path to a Wireless
+ // device, scans that device and appends networks to network_list.
+ // Returns false if there is an error getting the access points visible
+ // to the |device_path|.
+ bool AddAccessPointsFromDevice(const dbus::ObjectPath& device_path,
+ NetworkMap* network_map);
+
+ // Reply callback accepts the map of networks and fires the
+ // OnNetworkListChanged event and user callbacks.
+ void OnAccessPointsFound(scoped_ptr<NetworkMap> network_map,
+ const NetworkListCallback& success_callback,
+ const FailureCallback& failure_callback);
+
+ // Reply callback accepts the map of networks and fires the
+ // OnNetworkListChanged event.
+ void OnAccessPointsFoundViaScan(scoped_ptr<NetworkMap> network_map);
+
+ // Helper function for OnAccessPointsFound and OnAccessPointsFoundViaScan to
+ // fire the OnNetworkListChangedEvent.
+ void SendNetworkListChangedEvent(const base::ListValue& network_list);
+
+ // Gets a dictionary of information about the access point.
+ // Returns false if there is an error getting information about the
+ // supplied |access_point_path|.
+ bool GetAccessPointInfo(
+ const dbus::ObjectPath& access_point_path,
+ const scoped_ptr<base::DictionaryValue>& access_point_info);
+
+ // Helper function to extract a property from a device.
+ // Returns the dbus::Response object from calling Get on the supplied
+ // |property_name|.
+ scoped_ptr<dbus::Response> GetAccessPointProperty(
+ dbus::ObjectProxy* access_point_proxy,
+ const std::string& property_name);
+
+ // If the access_point is not already in the map it is added. Otherwise
+ // the access point is updated (eg. with the max of the signal
+ // strength).
+ void AddOrUpdateAccessPoint(NetworkMap* network_map,
+ const std::string& network_guid,
+ scoped_ptr<base::DictionaryValue>& access_point);
+
+ // Maps the WPA security flags to a human readable string.
+ void MapSecurityFlagsToString(uint32_t securityFlags, std::string* security);
+
+ // Gets the connected access point path on the given device. Internally gets
+ // all active connections then checks if the device matches the requested
+ // device, then gets the access point associated with the connection.
+ // Returns false if there is an error getting the connected access point.
+ bool GetConnectedAccessPoint(dbus::ObjectPath device_path,
+ dbus::ObjectPath* access_point_path);
+
+ // Given a path to an active connection gets the path to the device
+ // that the connection belongs to. Returns false if there is an error getting
+ // the device corresponding to the supplied |connection_path|.
+ bool GetDeviceOfConnection(dbus::ObjectPath connection_path,
+ dbus::ObjectPath* device_path);
+
+ // Given a path to an active wireless connection gets the path to the
+ // access point associated with that connection.
+ // Returns false if there is an error getting the |access_point_path|
+ // corresponding to the supplied |connection_path|.
+ bool GetAccessPointForConnection(dbus::ObjectPath connection_path,
+ dbus::ObjectPath* access_point_path);
+
+ // Helper method to set the connection state in the |network_map_| and post
+ // a change event.
+ bool SetConnectionStateAndPostEvent(const std::string& guid,
+ const std::string& ssid,
+ const std::string& connection_state);
+
+ // Helper method to post an OnNetworkChanged event to the UI thread from the
+ // dbus thread. Used for connection status progress during |StartConnect|.
+ void PostOnNetworksChangedToUIThread(scoped_ptr<GuidList> guid_list);
+
+ // Helper method to be called from the UI thread and manage ownership of the
+ // passed vector from the |dbus_thread_|.
+ void OnNetworksChangedEventTask(scoped_ptr<GuidList> guid_list);
+
+ void GetCachedNetworkProperties(const std::string& guid,
+ base::DictionaryValue* properties,
+ std::string* error);
+
+ void OnNetworksChangedEventOnUIThread(const GuidList& network_guids);
+
+ void OnNetworkListChangedEventOnUIThread(const GuidList& network_guids);
+
+ // Browser context.
+ content::BrowserContext* browser_context_;
+ // Thread used for DBus actions.
+ base::Thread dbus_thread_;
+ // DBus instance. Only access on |dbus_thread_|.
+ scoped_refptr<dbus::Bus> dbus_;
+ // Task runner used by the |dbus_| object.
+ scoped_refptr<base::SequencedTaskRunner> dbus_task_runner_;
+ // This is owned by |dbus_| object. Only access on |dbus_thread_|.
+ dbus::ObjectProxy* network_manager_proxy_;
+ // Holds the current mapping of known networks. Only access on |dbus_thread_|.
+ scoped_ptr<NetworkMap> network_map_;
+ // Observers to Network Events.
+ base::ObserverList<NetworkingPrivateDelegateObserver>
+ network_events_observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateLinux);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_LINUX_H_
diff --git a/chromium/extensions/browser/api/networking_private/networking_private_service_client.cc b/chromium/extensions/browser/api/networking_private/networking_private_service_client.cc
new file mode 100644
index 00000000000..d9c5cf0f64d
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/networking_private_service_client.cc
@@ -0,0 +1,474 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/networking_private/networking_private_service_client.h"
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/sequenced_task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/threading/worker_pool.h"
+#include "components/onc/onc_constants.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/api/networking_private/networking_private_api.h"
+#include "extensions/browser/api/networking_private/networking_private_delegate_observer.h"
+
+using content::BrowserThread;
+using wifi::WiFiService;
+
+namespace extensions {
+
+namespace {
+
+const char kNetworkingPrivateSequenceTokenName[] = "NetworkingPrivate";
+
+// Deletes WiFiService object on the worker thread.
+void ShutdownWifiServiceOnWorkerThread(scoped_ptr<WiFiService> wifi_service) {
+ DCHECK(wifi_service.get());
+}
+
+} // namespace
+
+NetworkingPrivateServiceClient::ServiceCallbacks::ServiceCallbacks() {
+}
+
+NetworkingPrivateServiceClient::ServiceCallbacks::~ServiceCallbacks() {
+}
+
+NetworkingPrivateServiceClient::NetworkingPrivateServiceClient(
+ scoped_ptr<WiFiService> wifi_service,
+ scoped_ptr<VerifyDelegate> verify_delegate)
+ : NetworkingPrivateDelegate(std::move(verify_delegate)),
+ wifi_service_(std::move(wifi_service)),
+ weak_factory_(this) {
+ sequence_token_ = BrowserThread::GetBlockingPool()->GetNamedSequenceToken(
+ kNetworkingPrivateSequenceTokenName);
+ task_runner_ =
+ BrowserThread::GetBlockingPool()
+ ->GetSequencedTaskRunnerWithShutdownBehavior(
+ sequence_token_, base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&WiFiService::Initialize,
+ base::Unretained(wifi_service_.get()), task_runner_));
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &WiFiService::SetEventObservers,
+ base::Unretained(wifi_service_.get()),
+ base::ThreadTaskRunnerHandle::Get(),
+ base::Bind(
+ &NetworkingPrivateServiceClient::OnNetworksChangedEventOnUIThread,
+ weak_factory_.GetWeakPtr()),
+ base::Bind(&NetworkingPrivateServiceClient::
+ OnNetworkListChangedEventOnUIThread,
+ weak_factory_.GetWeakPtr())));
+ net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
+}
+
+NetworkingPrivateServiceClient::~NetworkingPrivateServiceClient() {
+ // Verify that wifi_service was passed to ShutdownWifiServiceOnWorkerThread to
+ // be deleted after completion of all posted tasks.
+ DCHECK(!wifi_service_.get());
+}
+
+void NetworkingPrivateServiceClient::Shutdown() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
+ // Clear callbacks map to release callbacks from UI thread.
+ callbacks_map_.Clear();
+ // Post ShutdownWifiServiceOnWorkerThread task to delete services when all
+ // posted tasks are done.
+ task_runner_->PostTask(FROM_HERE,
+ base::Bind(&ShutdownWifiServiceOnWorkerThread,
+ base::Passed(&wifi_service_)));
+}
+
+void NetworkingPrivateServiceClient::AddObserver(
+ NetworkingPrivateDelegateObserver* observer) {
+ network_events_observers_.AddObserver(observer);
+}
+
+void NetworkingPrivateServiceClient::RemoveObserver(
+ NetworkingPrivateDelegateObserver* observer) {
+ network_events_observers_.RemoveObserver(observer);
+}
+
+void NetworkingPrivateServiceClient::OnNetworkChanged(
+ net::NetworkChangeNotifier::ConnectionType type) {
+ task_runner_->PostTask(FROM_HERE,
+ base::Bind(&WiFiService::RequestConnectedNetworkUpdate,
+ base::Unretained(wifi_service_.get())));
+}
+
+NetworkingPrivateServiceClient::ServiceCallbacks*
+NetworkingPrivateServiceClient::AddServiceCallbacks() {
+ ServiceCallbacks* service_callbacks = new ServiceCallbacks();
+ service_callbacks->id = callbacks_map_.Add(service_callbacks);
+ return service_callbacks;
+}
+
+void NetworkingPrivateServiceClient::RemoveServiceCallbacks(
+ ServiceCallbacksID callback_id) {
+ callbacks_map_.Remove(callback_id);
+}
+
+// NetworkingPrivateServiceClient implementation
+
+void NetworkingPrivateServiceClient::GetProperties(
+ const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ ServiceCallbacks* service_callbacks = AddServiceCallbacks();
+ service_callbacks->failure_callback = failure_callback;
+ service_callbacks->get_properties_callback = success_callback;
+
+ scoped_ptr<base::DictionaryValue> properties(new base::DictionaryValue);
+ std::string* error = new std::string;
+
+ base::DictionaryValue* properties_ptr = properties.get();
+ task_runner_->PostTaskAndReply(
+ FROM_HERE, base::Bind(&WiFiService::GetProperties,
+ base::Unretained(wifi_service_.get()), guid,
+ properties_ptr, error),
+ base::Bind(&NetworkingPrivateServiceClient::AfterGetProperties,
+ weak_factory_.GetWeakPtr(), service_callbacks->id, guid,
+ base::Passed(&properties), base::Owned(error)));
+}
+
+void NetworkingPrivateServiceClient::GetManagedProperties(
+ const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ ServiceCallbacks* service_callbacks = AddServiceCallbacks();
+ service_callbacks->failure_callback = failure_callback;
+ service_callbacks->get_properties_callback = success_callback;
+
+ scoped_ptr<base::DictionaryValue> properties(new base::DictionaryValue);
+ std::string* error = new std::string;
+
+ base::DictionaryValue* properties_ptr = properties.get();
+ task_runner_->PostTaskAndReply(
+ FROM_HERE, base::Bind(&WiFiService::GetManagedProperties,
+ base::Unretained(wifi_service_.get()), guid,
+ properties_ptr, error),
+ base::Bind(&NetworkingPrivateServiceClient::AfterGetProperties,
+ weak_factory_.GetWeakPtr(), service_callbacks->id, guid,
+ base::Passed(&properties), base::Owned(error)));
+}
+
+void NetworkingPrivateServiceClient::GetState(
+ const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ ServiceCallbacks* service_callbacks = AddServiceCallbacks();
+ service_callbacks->failure_callback = failure_callback;
+ service_callbacks->get_properties_callback = success_callback;
+
+ scoped_ptr<base::DictionaryValue> properties(new base::DictionaryValue);
+ std::string* error = new std::string;
+
+ base::DictionaryValue* properties_ptr = properties.get();
+ task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&WiFiService::GetState, base::Unretained(wifi_service_.get()),
+ guid, properties_ptr, error),
+ base::Bind(&NetworkingPrivateServiceClient::AfterGetProperties,
+ weak_factory_.GetWeakPtr(), service_callbacks->id, guid,
+ base::Passed(&properties), base::Owned(error)));
+}
+
+void NetworkingPrivateServiceClient::SetProperties(
+ const std::string& guid,
+ scoped_ptr<base::DictionaryValue> properties,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ ServiceCallbacks* service_callbacks = AddServiceCallbacks();
+ service_callbacks->failure_callback = failure_callback;
+ service_callbacks->set_properties_callback = success_callback;
+
+ std::string* error = new std::string;
+
+ task_runner_->PostTaskAndReply(
+ FROM_HERE, base::Bind(&WiFiService::SetProperties,
+ base::Unretained(wifi_service_.get()), guid,
+ base::Passed(&properties), error),
+ base::Bind(&NetworkingPrivateServiceClient::AfterSetProperties,
+ weak_factory_.GetWeakPtr(), service_callbacks->id,
+ base::Owned(error)));
+}
+
+void NetworkingPrivateServiceClient::CreateNetwork(
+ bool shared,
+ scoped_ptr<base::DictionaryValue> properties,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ ServiceCallbacks* service_callbacks = AddServiceCallbacks();
+ service_callbacks->failure_callback = failure_callback;
+ service_callbacks->create_network_callback = success_callback;
+
+ std::string* network_guid = new std::string;
+ std::string* error = new std::string;
+
+ task_runner_->PostTaskAndReply(
+ FROM_HERE, base::Bind(&WiFiService::CreateNetwork,
+ base::Unretained(wifi_service_.get()), shared,
+ base::Passed(&properties), network_guid, error),
+ base::Bind(&NetworkingPrivateServiceClient::AfterCreateNetwork,
+ weak_factory_.GetWeakPtr(), service_callbacks->id,
+ base::Owned(network_guid), base::Owned(error)));
+}
+
+void NetworkingPrivateServiceClient::ForgetNetwork(
+ const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ // TODO(mef): Implement for Win/Mac
+ failure_callback.Run(networking_private::kErrorNotSupported);
+}
+
+void NetworkingPrivateServiceClient::GetNetworks(
+ const std::string& network_type,
+ bool configured_only,
+ bool visible_only,
+ int limit,
+ const NetworkListCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ ServiceCallbacks* service_callbacks = AddServiceCallbacks();
+ service_callbacks->failure_callback = failure_callback;
+ service_callbacks->get_visible_networks_callback = success_callback;
+
+ scoped_ptr<base::ListValue> networks(new base::ListValue);
+
+ // TODO(stevenjb/mef): Apply filters (configured, visible, limit).
+
+ base::ListValue* networks_ptr = networks.get();
+ task_runner_->PostTaskAndReply(
+ FROM_HERE, base::Bind(&WiFiService::GetVisibleNetworks,
+ base::Unretained(wifi_service_.get()), network_type,
+ networks_ptr, false),
+ base::Bind(&NetworkingPrivateServiceClient::AfterGetVisibleNetworks,
+ weak_factory_.GetWeakPtr(), service_callbacks->id,
+ base::Passed(&networks)));
+}
+
+void NetworkingPrivateServiceClient::StartConnect(
+ const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ ServiceCallbacks* service_callbacks = AddServiceCallbacks();
+ service_callbacks->failure_callback = failure_callback;
+ service_callbacks->start_connect_callback = success_callback;
+
+ std::string* error = new std::string;
+
+ task_runner_->PostTaskAndReply(
+ FROM_HERE, base::Bind(&WiFiService::StartConnect,
+ base::Unretained(wifi_service_.get()), guid, error),
+ base::Bind(&NetworkingPrivateServiceClient::AfterStartConnect,
+ weak_factory_.GetWeakPtr(), service_callbacks->id,
+ base::Owned(error)));
+}
+
+void NetworkingPrivateServiceClient::StartDisconnect(
+ const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ ServiceCallbacks* service_callbacks = AddServiceCallbacks();
+ service_callbacks->failure_callback = failure_callback;
+ service_callbacks->start_disconnect_callback = success_callback;
+
+ std::string* error = new std::string;
+
+ task_runner_->PostTaskAndReply(
+ FROM_HERE, base::Bind(&WiFiService::StartDisconnect,
+ base::Unretained(wifi_service_.get()), guid, error),
+ base::Bind(&NetworkingPrivateServiceClient::AfterStartDisconnect,
+ weak_factory_.GetWeakPtr(), service_callbacks->id,
+ base::Owned(error)));
+}
+
+void NetworkingPrivateServiceClient::SetWifiTDLSEnabledState(
+ const std::string& ip_or_mac_address,
+ bool enabled,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ failure_callback.Run(networking_private::kErrorNotSupported);
+}
+
+void NetworkingPrivateServiceClient::GetWifiTDLSStatus(
+ const std::string& ip_or_mac_address,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ failure_callback.Run(networking_private::kErrorNotSupported);
+}
+
+void NetworkingPrivateServiceClient::GetCaptivePortalStatus(
+ const std::string& guid,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ failure_callback.Run(networking_private::kErrorNotSupported);
+}
+
+void NetworkingPrivateServiceClient::UnlockCellularSim(
+ const std::string& guid,
+ const std::string& pin,
+ const std::string& puk,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ failure_callback.Run(networking_private::kErrorNotSupported);
+}
+
+void NetworkingPrivateServiceClient::SetCellularSimState(
+ const std::string& guid,
+ bool require_pin,
+ const std::string& current_pin,
+ const std::string& new_pin,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) {
+ failure_callback.Run(networking_private::kErrorNotSupported);
+}
+
+scoped_ptr<base::ListValue>
+NetworkingPrivateServiceClient::GetEnabledNetworkTypes() {
+ scoped_ptr<base::ListValue> network_list;
+ network_list->AppendString(::onc::network_type::kWiFi);
+ return network_list;
+}
+
+scoped_ptr<NetworkingPrivateDelegate::DeviceStateList>
+NetworkingPrivateServiceClient::GetDeviceStateList() {
+ scoped_ptr<DeviceStateList> device_state_list(new DeviceStateList);
+ scoped_ptr<api::networking_private::DeviceStateProperties> properties(
+ new api::networking_private::DeviceStateProperties);
+ properties->type = api::networking_private::NETWORK_TYPE_WIFI;
+ properties->state = api::networking_private::DEVICE_STATE_TYPE_ENABLED;
+ device_state_list->push_back(std::move(properties));
+ return device_state_list;
+}
+
+bool NetworkingPrivateServiceClient::EnableNetworkType(
+ const std::string& type) {
+ return false;
+}
+
+bool NetworkingPrivateServiceClient::DisableNetworkType(
+ const std::string& type) {
+ return false;
+}
+
+bool NetworkingPrivateServiceClient::RequestScan() {
+ task_runner_->PostTask(FROM_HERE,
+ base::Bind(&WiFiService::RequestNetworkScan,
+ base::Unretained(wifi_service_.get())));
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void NetworkingPrivateServiceClient::AfterGetProperties(
+ ServiceCallbacksID callback_id,
+ const std::string& network_guid,
+ scoped_ptr<base::DictionaryValue> properties,
+ const std::string* error) {
+ ServiceCallbacks* service_callbacks = callbacks_map_.Lookup(callback_id);
+ DCHECK(service_callbacks);
+ if (!error->empty()) {
+ DCHECK(!service_callbacks->failure_callback.is_null());
+ service_callbacks->failure_callback.Run(*error);
+ } else {
+ DCHECK(!service_callbacks->get_properties_callback.is_null());
+ service_callbacks->get_properties_callback.Run(std::move(properties));
+ }
+ RemoveServiceCallbacks(callback_id);
+}
+
+void NetworkingPrivateServiceClient::AfterGetVisibleNetworks(
+ ServiceCallbacksID callback_id,
+ scoped_ptr<base::ListValue> networks) {
+ ServiceCallbacks* service_callbacks = callbacks_map_.Lookup(callback_id);
+ DCHECK(service_callbacks);
+ DCHECK(!service_callbacks->get_visible_networks_callback.is_null());
+ service_callbacks->get_visible_networks_callback.Run(std::move(networks));
+ RemoveServiceCallbacks(callback_id);
+}
+
+void NetworkingPrivateServiceClient::AfterSetProperties(
+ ServiceCallbacksID callback_id,
+ const std::string* error) {
+ ServiceCallbacks* service_callbacks = callbacks_map_.Lookup(callback_id);
+ DCHECK(service_callbacks);
+ if (!error->empty()) {
+ DCHECK(!service_callbacks->failure_callback.is_null());
+ service_callbacks->failure_callback.Run(*error);
+ } else {
+ DCHECK(!service_callbacks->set_properties_callback.is_null());
+ service_callbacks->set_properties_callback.Run();
+ }
+ RemoveServiceCallbacks(callback_id);
+}
+
+void NetworkingPrivateServiceClient::AfterCreateNetwork(
+ ServiceCallbacksID callback_id,
+ const std::string* network_guid,
+ const std::string* error) {
+ ServiceCallbacks* service_callbacks = callbacks_map_.Lookup(callback_id);
+ DCHECK(service_callbacks);
+ if (!error->empty()) {
+ DCHECK(!service_callbacks->failure_callback.is_null());
+ service_callbacks->failure_callback.Run(*error);
+ } else {
+ DCHECK(!service_callbacks->create_network_callback.is_null());
+ service_callbacks->create_network_callback.Run(*network_guid);
+ }
+ RemoveServiceCallbacks(callback_id);
+}
+
+void NetworkingPrivateServiceClient::AfterStartConnect(
+ ServiceCallbacksID callback_id,
+ const std::string* error) {
+ ServiceCallbacks* service_callbacks = callbacks_map_.Lookup(callback_id);
+ DCHECK(service_callbacks);
+ if (!error->empty()) {
+ DCHECK(!service_callbacks->failure_callback.is_null());
+ service_callbacks->failure_callback.Run(*error);
+ } else {
+ DCHECK(!service_callbacks->start_connect_callback.is_null());
+ service_callbacks->start_connect_callback.Run();
+ }
+ RemoveServiceCallbacks(callback_id);
+}
+
+void NetworkingPrivateServiceClient::AfterStartDisconnect(
+ ServiceCallbacksID callback_id,
+ const std::string* error) {
+ ServiceCallbacks* service_callbacks = callbacks_map_.Lookup(callback_id);
+ DCHECK(service_callbacks);
+ if (!error->empty()) {
+ DCHECK(!service_callbacks->failure_callback.is_null());
+ service_callbacks->failure_callback.Run(*error);
+ } else {
+ DCHECK(!service_callbacks->start_disconnect_callback.is_null());
+ service_callbacks->start_disconnect_callback.Run();
+ }
+ RemoveServiceCallbacks(callback_id);
+}
+
+void NetworkingPrivateServiceClient::OnNetworksChangedEventOnUIThread(
+ const std::vector<std::string>& network_guids) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ FOR_EACH_OBSERVER(NetworkingPrivateDelegateObserver,
+ network_events_observers_,
+ OnNetworksChangedEvent(network_guids));
+}
+
+void NetworkingPrivateServiceClient::OnNetworkListChangedEventOnUIThread(
+ const std::vector<std::string>& network_guids) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ FOR_EACH_OBSERVER(NetworkingPrivateDelegateObserver,
+ network_events_observers_,
+ OnNetworkListChangedEvent(network_guids));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/networking_private/networking_private_service_client.h b/chromium/extensions/browser/api/networking_private/networking_private_service_client.h
new file mode 100644
index 00000000000..63805db98a2
--- /dev/null
+++ b/chromium/extensions/browser/api/networking_private/networking_private_service_client.h
@@ -0,0 +1,188 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_SERVICE_CLIENT_H_
+#define EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_SERVICE_CLIENT_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/id_map.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/strings/string16.h"
+#include "base/supports_user_data.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/values.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "components/wifi/wifi_service.h"
+#include "content/public/browser/utility_process_host.h"
+#include "content/public/browser/utility_process_host_client.h"
+#include "extensions/browser/api/networking_private/networking_private_delegate.h"
+#include "net/base/network_change_notifier.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace extensions {
+
+// Windows / Mac NetworkingPrivateApi implementation. This implements
+// NetworkingPrivateDelegate, making WiFiService calls on the worker thead.
+// It also observes |OnNetworkChanged| notifications and posts them to
+// WiFiService on the worker thread. Created and called from the UI thread.
+class NetworkingPrivateServiceClient
+ : public NetworkingPrivateDelegate,
+ net::NetworkChangeNotifier::NetworkChangeObserver {
+ public:
+ // Takes ownership of |wifi_service| which is accessed and deleted on the
+ // worker thread. The deletion task is posted from the destructor.
+ // |verify_delegate| is passed to NetworkingPrivateDelegate and may be NULL.
+ NetworkingPrivateServiceClient(scoped_ptr<wifi::WiFiService> wifi_service,
+ scoped_ptr<VerifyDelegate> verify_delegate);
+
+ // KeyedService
+ void Shutdown() override;
+
+ // NetworkingPrivateDelegate
+ void GetProperties(const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void GetManagedProperties(const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void GetState(const std::string& guid,
+ const DictionaryCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void SetProperties(const std::string& guid,
+ scoped_ptr<base::DictionaryValue> properties_dict,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void CreateNetwork(bool shared,
+ scoped_ptr<base::DictionaryValue> properties_dict,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void ForgetNetwork(const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void GetNetworks(const std::string& network_type,
+ bool configured_only,
+ bool visible_only,
+ int limit,
+ const NetworkListCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void StartConnect(const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void StartDisconnect(const std::string& guid,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void SetWifiTDLSEnabledState(
+ const std::string& ip_or_mac_address,
+ bool enabled,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void GetWifiTDLSStatus(const std::string& ip_or_mac_address,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void GetCaptivePortalStatus(const std::string& guid,
+ const StringCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void UnlockCellularSim(const std::string& guid,
+ const std::string& pin,
+ const std::string& puk,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ void SetCellularSimState(const std::string& guid,
+ bool require_pin,
+ const std::string& current_pin,
+ const std::string& new_pin,
+ const VoidCallback& success_callback,
+ const FailureCallback& failure_callback) override;
+ scoped_ptr<base::ListValue> GetEnabledNetworkTypes() override;
+ scoped_ptr<DeviceStateList> GetDeviceStateList() override;
+ bool EnableNetworkType(const std::string& type) override;
+ bool DisableNetworkType(const std::string& type) override;
+ bool RequestScan() override;
+ void AddObserver(NetworkingPrivateDelegateObserver* observer) override;
+ void RemoveObserver(NetworkingPrivateDelegateObserver* observer) override;
+
+ // NetworkChangeNotifier::NetworkChangeObserver implementation.
+ void OnNetworkChanged(
+ net::NetworkChangeNotifier::ConnectionType type) override;
+
+ private:
+ // Callbacks to extension api function objects. Keep reference to API object
+ // and are released in ShutdownOnUIThread. Run when WiFiService calls back
+ // into NetworkingPrivateServiceClient's callback wrappers.
+ typedef int32_t ServiceCallbacksID;
+ struct ServiceCallbacks {
+ ServiceCallbacks();
+ ~ServiceCallbacks();
+
+ DictionaryCallback get_properties_callback;
+ VoidCallback start_connect_callback;
+ VoidCallback start_disconnect_callback;
+ VoidCallback set_properties_callback;
+ StringCallback create_network_callback;
+ NetworkListCallback get_visible_networks_callback;
+ FailureCallback failure_callback;
+
+ ServiceCallbacksID id;
+ };
+ typedef IDMap<ServiceCallbacks, IDMapOwnPointer> ServiceCallbacksMap;
+
+ ~NetworkingPrivateServiceClient() override;
+
+ // Callback wrappers.
+ void AfterGetProperties(ServiceCallbacksID callback_id,
+ const std::string& network_guid,
+ scoped_ptr<base::DictionaryValue> properties,
+ const std::string* error);
+ void AfterSetProperties(ServiceCallbacksID callback_id,
+ const std::string* error);
+ void AfterCreateNetwork(ServiceCallbacksID callback_id,
+ const std::string* network_guid,
+ const std::string* error);
+ void AfterGetVisibleNetworks(ServiceCallbacksID callback_id,
+ scoped_ptr<base::ListValue> networks);
+ void AfterStartConnect(ServiceCallbacksID callback_id,
+ const std::string* error);
+ void AfterStartDisconnect(ServiceCallbacksID callback_id,
+ const std::string* error);
+
+ void OnNetworksChangedEventOnUIThread(
+ const wifi::WiFiService::NetworkGuidList& network_guid_list);
+ void OnNetworkListChangedEventOnUIThread(
+ const wifi::WiFiService::NetworkGuidList& network_guid_list);
+
+ // Add new |ServiceCallbacks| to |callbacks_map_|.
+ ServiceCallbacks* AddServiceCallbacks();
+ // Removes ServiceCallbacks for |callback_id| from |callbacks_map_|.
+ void RemoveServiceCallbacks(ServiceCallbacksID callback_id);
+
+ // Callbacks to run when callback is called from WiFiService.
+ ServiceCallbacksMap callbacks_map_;
+ // Observers to Network Events.
+ base::ObserverList<NetworkingPrivateDelegateObserver>
+ network_events_observers_;
+ // Interface to WiFiService. Used and deleted on the worker thread.
+ scoped_ptr<wifi::WiFiService> wifi_service_;
+ // Sequence token associated with wifi tasks.
+ base::SequencedWorkerPool::SequenceToken sequence_token_;
+ // Task runner for worker tasks.
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+ // Use WeakPtrs for callbacks from |wifi_service_|.
+ base::WeakPtrFactory<NetworkingPrivateServiceClient> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateServiceClient);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_SERVICE_CLIENT_H_
diff --git a/chromium/extensions/browser/api/power/OWNERS b/chromium/extensions/browser/api/power/OWNERS
new file mode 100644
index 00000000000..3c97e54df02
--- /dev/null
+++ b/chromium/extensions/browser/api/power/OWNERS
@@ -0,0 +1 @@
+derat@chromium.org
diff --git a/chromium/extensions/browser/api/power/power_api.cc b/chromium/extensions/browser/api/power/power_api.cc
new file mode 100644
index 00000000000..4c9ed0dcd98
--- /dev/null
+++ b/chromium/extensions/browser/api/power/power_api.cc
@@ -0,0 +1,131 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/power/power_api.h"
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/api/power.h"
+#include "extensions/common/extension.h"
+
+namespace extensions {
+
+namespace {
+
+const char kPowerSaveBlockerDescription[] = "extension";
+
+content::PowerSaveBlocker::PowerSaveBlockerType LevelToPowerSaveBlockerType(
+ api::power::Level level) {
+ switch (level) {
+ case api::power::LEVEL_SYSTEM:
+ return content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension;
+ case api::power::LEVEL_DISPLAY: // fallthrough
+ case api::power::LEVEL_NONE:
+ return content::PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep;
+ }
+ NOTREACHED() << "Unhandled level " << level;
+ return content::PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep;
+}
+
+base::LazyInstance<BrowserContextKeyedAPIFactory<PowerAPI>> g_factory =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+bool PowerRequestKeepAwakeFunction::RunSync() {
+ scoped_ptr<api::power::RequestKeepAwake::Params> params(
+ api::power::RequestKeepAwake::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params);
+ EXTENSION_FUNCTION_VALIDATE(params->level != api::power::LEVEL_NONE);
+ PowerAPI::Get(browser_context())->AddRequest(extension_id(), params->level);
+ return true;
+}
+
+bool PowerReleaseKeepAwakeFunction::RunSync() {
+ PowerAPI::Get(browser_context())->RemoveRequest(extension_id());
+ return true;
+}
+
+// static
+PowerAPI* PowerAPI::Get(content::BrowserContext* context) {
+ return BrowserContextKeyedAPIFactory<PowerAPI>::Get(context);
+}
+
+// static
+BrowserContextKeyedAPIFactory<PowerAPI>* PowerAPI::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+void PowerAPI::AddRequest(const std::string& extension_id,
+ api::power::Level level) {
+ extension_levels_[extension_id] = level;
+ UpdatePowerSaveBlocker();
+}
+
+void PowerAPI::RemoveRequest(const std::string& extension_id) {
+ extension_levels_.erase(extension_id);
+ UpdatePowerSaveBlocker();
+}
+
+void PowerAPI::SetCreateBlockerFunctionForTesting(
+ CreateBlockerFunction function) {
+ create_blocker_function_ =
+ !function.is_null() ? function
+ : base::Bind(&content::PowerSaveBlocker::Create);
+}
+
+void PowerAPI::OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ RemoveRequest(extension->id());
+ UpdatePowerSaveBlocker();
+}
+
+PowerAPI::PowerAPI(content::BrowserContext* context)
+ : browser_context_(context),
+ create_blocker_function_(base::Bind(&content::PowerSaveBlocker::Create)),
+ current_level_(api::power::LEVEL_SYSTEM) {
+ ExtensionRegistry::Get(browser_context_)->AddObserver(this);
+}
+
+PowerAPI::~PowerAPI() {
+}
+
+void PowerAPI::UpdatePowerSaveBlocker() {
+ if (extension_levels_.empty()) {
+ power_save_blocker_.reset();
+ return;
+ }
+
+ api::power::Level new_level = api::power::LEVEL_SYSTEM;
+ for (ExtensionLevelMap::const_iterator it = extension_levels_.begin();
+ it != extension_levels_.end(); ++it) {
+ if (it->second == api::power::LEVEL_DISPLAY)
+ new_level = it->second;
+ }
+
+ // If the level changed and we need to create a new blocker, do a swap
+ // to ensure that there isn't a brief period where power management is
+ // unblocked.
+ if (!power_save_blocker_ || new_level != current_level_) {
+ content::PowerSaveBlocker::PowerSaveBlockerType type =
+ LevelToPowerSaveBlockerType(new_level);
+ scoped_ptr<content::PowerSaveBlocker> new_blocker(
+ create_blocker_function_.Run(type,
+ content::PowerSaveBlocker::kReasonOther,
+ kPowerSaveBlockerDescription));
+ power_save_blocker_.swap(new_blocker);
+ current_level_ = new_level;
+ }
+}
+
+void PowerAPI::Shutdown() {
+ // Unregister here rather than in the d'tor; otherwise this call will recreate
+ // the already-deleted ExtensionRegistry.
+ ExtensionRegistry::Get(browser_context_)->RemoveObserver(this);
+ power_save_blocker_.reset();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/power/power_api.h b/chromium/extensions/browser/api/power/power_api.h
new file mode 100644
index 00000000000..313151d7757
--- /dev/null
+++ b/chromium/extensions/browser/api/power/power_api.h
@@ -0,0 +1,122 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_POWER_POWER_API_H_
+#define EXTENSIONS_BROWSER_API_POWER_POWER_API_H_
+
+#include <map>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/public/browser/power_save_blocker.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/common/api/power.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+// Implementation of the chrome.power.requestKeepAwake API.
+class PowerRequestKeepAwakeFunction : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("power.requestKeepAwake", POWER_REQUESTKEEPAWAKE)
+
+ protected:
+ ~PowerRequestKeepAwakeFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+// Implementation of the chrome.power.releaseKeepAwake API.
+class PowerReleaseKeepAwakeFunction : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("power.releaseKeepAwake", POWER_RELEASEKEEPAWAKE)
+
+ protected:
+ ~PowerReleaseKeepAwakeFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+// Handles calls made via the chrome.power API. There is a separate instance of
+// this class for each profile, as requests are tracked by extension ID, but a
+// regular and incognito profile will share the same instance.
+class PowerAPI : public BrowserContextKeyedAPI,
+ public extensions::ExtensionRegistryObserver {
+ public:
+ typedef base::Callback<scoped_ptr<content::PowerSaveBlocker>(
+ content::PowerSaveBlocker::PowerSaveBlockerType,
+ content::PowerSaveBlocker::Reason,
+ const std::string&)> CreateBlockerFunction;
+
+ static PowerAPI* Get(content::BrowserContext* context);
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<PowerAPI>* GetFactoryInstance();
+
+ // Adds an extension lock at |level| for |extension_id|, replacing the
+ // extension's existing lock, if any.
+ void AddRequest(const std::string& extension_id, api::power::Level level);
+
+ // Removes an extension lock for an extension. Calling this for an
+ // extension id without a lock will do nothing.
+ void RemoveRequest(const std::string& extension_id);
+
+ // Replaces the function that will be called to create PowerSaveBlocker
+ // objects. Passing an empty callback will revert to the default.
+ void SetCreateBlockerFunctionForTesting(CreateBlockerFunction function);
+
+ // Overridden from extensions::ExtensionRegistryObserver.
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<PowerAPI>;
+
+ explicit PowerAPI(content::BrowserContext* context);
+ ~PowerAPI() override;
+
+ // Updates |power_save_blocker_| and |current_level_| after iterating
+ // over |extension_levels_|.
+ void UpdatePowerSaveBlocker();
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "PowerAPI"; }
+ static const bool kServiceRedirectedInIncognito = true;
+ static const bool kServiceIsCreatedWithBrowserContext = false;
+ void Shutdown() override;
+
+ content::BrowserContext* browser_context_;
+
+ // Function that should be called to create PowerSaveBlocker objects.
+ // Tests can change this to record what would've been done instead of
+ // actually changing the system power-saving settings.
+ CreateBlockerFunction create_blocker_function_;
+
+ scoped_ptr<content::PowerSaveBlocker> power_save_blocker_;
+
+ // Current level used by |power_save_blocker_|. Meaningless if
+ // |power_save_blocker_| is NULL.
+ api::power::Level current_level_;
+
+ // Map from extension ID to the corresponding level for each extension
+ // that has an outstanding request.
+ typedef std::map<std::string, api::power::Level> ExtensionLevelMap;
+ ExtensionLevelMap extension_levels_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerAPI);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_POWER_POWER_API_H_
diff --git a/chromium/extensions/browser/api/power/power_api_unittest.cc b/chromium/extensions/browser/api/power/power_api_unittest.cc
new file mode 100644
index 00000000000..693abd10b04
--- /dev/null
+++ b/chromium/extensions/browser/api/power/power_api_unittest.cc
@@ -0,0 +1,278 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/power/power_api.h"
+
+#include <deque>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/public/browser/power_save_blocker.h"
+#include "extensions/browser/api_test_utils.h"
+#include "extensions/browser/api_unittest.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/test_util.h"
+
+namespace extensions {
+
+namespace {
+
+// Args commonly passed to PowerSaveBlockerStubManager::CallFunction().
+const char kDisplayArgs[] = "[\"display\"]";
+const char kSystemArgs[] = "[\"system\"]";
+const char kEmptyArgs[] = "[]";
+
+// Different actions that can be performed as a result of a
+// PowerSaveBlocker being created or destroyed.
+enum Request {
+ BLOCK_APP_SUSPENSION,
+ UNBLOCK_APP_SUSPENSION,
+ BLOCK_DISPLAY_SLEEP,
+ UNBLOCK_DISPLAY_SLEEP,
+ // Returned by PowerSaveBlockerStubManager::PopFirstRequest() when no
+ // requests are present.
+ NONE,
+};
+
+// Stub implementation of content::PowerSaveBlocker that just runs a
+// callback on destruction.
+class PowerSaveBlockerStub : public content::PowerSaveBlocker {
+ public:
+ explicit PowerSaveBlockerStub(base::Closure unblock_callback)
+ : unblock_callback_(unblock_callback) {
+ }
+
+ ~PowerSaveBlockerStub() override { unblock_callback_.Run(); }
+
+ private:
+ base::Closure unblock_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerSaveBlockerStub);
+};
+
+// Manages PowerSaveBlockerStub objects. Tests can instantiate this class
+// to make PowerAPI's calls to create PowerSaveBlockers record the
+// actions that would've been performed instead of actually blocking and
+// unblocking power management.
+class PowerSaveBlockerStubManager {
+ public:
+ explicit PowerSaveBlockerStubManager(content::BrowserContext* context)
+ : browser_context_(context),
+ weak_ptr_factory_(this) {
+ // Use base::Unretained since callbacks with return values can't use
+ // weak pointers.
+ PowerAPI::Get(browser_context_)
+ ->SetCreateBlockerFunctionForTesting(base::Bind(
+ &PowerSaveBlockerStubManager::CreateStub, base::Unretained(this)));
+ }
+
+ ~PowerSaveBlockerStubManager() {
+ PowerAPI::Get(browser_context_)
+ ->SetCreateBlockerFunctionForTesting(PowerAPI::CreateBlockerFunction());
+ }
+
+ // Removes and returns the first item from |requests_|. Returns NONE if
+ // |requests_| is empty.
+ Request PopFirstRequest() {
+ if (requests_.empty())
+ return NONE;
+
+ Request request = requests_.front();
+ requests_.pop_front();
+ return request;
+ }
+
+ private:
+ // Creates a new PowerSaveBlockerStub of type |type|.
+ scoped_ptr<content::PowerSaveBlocker> CreateStub(
+ content::PowerSaveBlocker::PowerSaveBlockerType type,
+ content::PowerSaveBlocker::Reason reason,
+ const std::string& description) {
+ Request unblock_request = NONE;
+ switch (type) {
+ case content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension:
+ requests_.push_back(BLOCK_APP_SUSPENSION);
+ unblock_request = UNBLOCK_APP_SUSPENSION;
+ break;
+ case content::PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep:
+ requests_.push_back(BLOCK_DISPLAY_SLEEP);
+ unblock_request = UNBLOCK_DISPLAY_SLEEP;
+ break;
+ }
+ return scoped_ptr<content::PowerSaveBlocker>(
+ new PowerSaveBlockerStub(
+ base::Bind(&PowerSaveBlockerStubManager::AppendRequest,
+ weak_ptr_factory_.GetWeakPtr(),
+ unblock_request)));
+ }
+
+ void AppendRequest(Request request) {
+ requests_.push_back(request);
+ }
+
+ content::BrowserContext* browser_context_;
+
+ // Requests in chronological order.
+ std::deque<Request> requests_;
+
+ base::WeakPtrFactory<PowerSaveBlockerStubManager> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerSaveBlockerStubManager);
+};
+
+} // namespace
+
+class PowerAPITest : public ApiUnitTest {
+ public:
+ void SetUp() override {
+ ApiUnitTest::SetUp();
+ manager_.reset(new PowerSaveBlockerStubManager(browser_context()));
+ }
+
+ void TearDown() override {
+ manager_.reset();
+ ApiUnitTest::TearDown();
+ }
+
+ protected:
+ // Shorthand for PowerRequestKeepAwakeFunction and
+ // PowerReleaseKeepAwakeFunction.
+ enum FunctionType {
+ REQUEST,
+ RELEASE,
+ };
+
+ // Calls the function described by |type| with |args|, a JSON list of
+ // arguments, on behalf of |extension|.
+ bool CallFunction(FunctionType type,
+ const std::string& args,
+ const extensions::Extension* extension) {
+ scoped_refptr<UIThreadExtensionFunction> function(
+ type == REQUEST ?
+ static_cast<UIThreadExtensionFunction*>(
+ new PowerRequestKeepAwakeFunction) :
+ static_cast<UIThreadExtensionFunction*>(
+ new PowerReleaseKeepAwakeFunction));
+ function->set_extension(extension);
+ return api_test_utils::RunFunction(function.get(), args, browser_context());
+ }
+
+ // Send a notification to PowerAPI saying that |extension| has
+ // been unloaded.
+ void UnloadExtension(const extensions::Extension* extension) {
+ PowerAPI::Get(browser_context())
+ ->OnExtensionUnloaded(browser_context(), extension,
+ UnloadedExtensionInfo::REASON_UNINSTALL);
+ }
+
+ scoped_ptr<PowerSaveBlockerStubManager> manager_;
+};
+
+TEST_F(PowerAPITest, RequestAndRelease) {
+ // Simulate an extension making and releasing a "display" request and a
+ // "system" request.
+ ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension()));
+ EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+ ASSERT_TRUE(CallFunction(RELEASE, kEmptyArgs, extension()));
+ EXPECT_EQ(UNBLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ ASSERT_TRUE(CallFunction(REQUEST, kSystemArgs, extension()));
+ EXPECT_EQ(BLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+ ASSERT_TRUE(CallFunction(RELEASE, kEmptyArgs, extension()));
+ EXPECT_EQ(UNBLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+}
+
+TEST_F(PowerAPITest, RequestWithoutRelease) {
+ // Simulate an extension calling requestKeepAwake() without calling
+ // releaseKeepAwake(). The override should be automatically removed when
+ // the extension is unloaded.
+ ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension()));
+ EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ UnloadExtension(extension());
+ EXPECT_EQ(UNBLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+}
+
+TEST_F(PowerAPITest, ReleaseWithoutRequest) {
+ // Simulate an extension calling releaseKeepAwake() without having
+ // calling requestKeepAwake() earlier. The call should be ignored.
+ ASSERT_TRUE(CallFunction(RELEASE, kEmptyArgs, extension()));
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+}
+
+TEST_F(PowerAPITest, UpgradeRequest) {
+ // Simulate an extension calling requestKeepAwake("system") and then
+ // requestKeepAwake("display"). When the second call is made, a
+ // display-sleep-blocking request should be made before the initial
+ // app-suspension-blocking request is released.
+ ASSERT_TRUE(CallFunction(REQUEST, kSystemArgs, extension()));
+ EXPECT_EQ(BLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension()));
+ EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(UNBLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ ASSERT_TRUE(CallFunction(RELEASE, kEmptyArgs, extension()));
+ EXPECT_EQ(UNBLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+}
+
+TEST_F(PowerAPITest, DowngradeRequest) {
+ // Simulate an extension calling requestKeepAwake("display") and then
+ // requestKeepAwake("system"). When the second call is made, an
+ // app-suspension-blocking request should be made before the initial
+ // display-sleep-blocking request is released.
+ ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension()));
+ EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ ASSERT_TRUE(CallFunction(REQUEST, kSystemArgs, extension()));
+ EXPECT_EQ(BLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
+ EXPECT_EQ(UNBLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ ASSERT_TRUE(CallFunction(RELEASE, kEmptyArgs, extension()));
+ EXPECT_EQ(UNBLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+}
+
+TEST_F(PowerAPITest, MultipleExtensions) {
+ // Simulate an extension blocking the display from sleeping.
+ ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension()));
+ EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ // Create a second extension that blocks system suspend. No additional
+ // PowerSaveBlocker is needed; the blocker from the first extension
+ // already covers the behavior requested by the second extension.
+ scoped_refptr<Extension> extension2(test_util::CreateEmptyExtension("id2"));
+ ASSERT_TRUE(CallFunction(REQUEST, kSystemArgs, extension2.get()));
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ // When the first extension is unloaded, a new app-suspension blocker
+ // should be created before the display-sleep blocker is destroyed.
+ UnloadExtension(extension());
+ EXPECT_EQ(BLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
+ EXPECT_EQ(UNBLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ // Make the first extension block display-sleep again.
+ ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension()));
+ EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(UNBLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/printer_provider/OWNERS b/chromium/extensions/browser/api/printer_provider/OWNERS
new file mode 100644
index 00000000000..0fa42c6a45c
--- /dev/null
+++ b/chromium/extensions/browser/api/printer_provider/OWNERS
@@ -0,0 +1,2 @@
+tbarzic@chromium.org
+vitalybuka@chromium.org
diff --git a/chromium/extensions/browser/api/printer_provider/printer_provider_api.cc b/chromium/extensions/browser/api/printer_provider/printer_provider_api.cc
new file mode 100644
index 00000000000..ed776e5b4df
--- /dev/null
+++ b/chromium/extensions/browser/api/printer_provider/printer_provider_api.cc
@@ -0,0 +1,772 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/printer_provider/printer_provider_api.h"
+
+#include <stddef.h>
+
+#include <map>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/i18n/rtl.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/macros.h"
+#include "base/scoped_observer.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "device/usb/usb_device.h"
+#include "extensions/browser/api/printer_provider/printer_provider_print_job.h"
+#include "extensions/browser/api/printer_provider_internal/printer_provider_internal_api.h"
+#include "extensions/browser/api/printer_provider_internal/printer_provider_internal_api_observer.h"
+#include "extensions/browser/api/usb/usb_guid_map.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/common/api/printer_provider.h"
+#include "extensions/common/api/printer_provider_internal.h"
+#include "extensions/common/api/usb.h"
+#include "extensions/common/extension.h"
+
+using device::UsbDevice;
+
+namespace extensions {
+
+namespace {
+
+// The separator between extension id and the extension's internal printer id
+// used when generating a printer id unique across extensions.
+const char kPrinterIdSeparator = ':';
+
+// Given an extension ID and an ID of a printer reported by the extension, it
+// generates a ID for the printer unique across extensions (assuming that the
+// printer id is unique in the extension's space).
+std::string GeneratePrinterId(const std::string& extension_id,
+ const std::string& internal_printer_id) {
+ std::string result = extension_id;
+ result.append(1, kPrinterIdSeparator);
+ result.append(internal_printer_id);
+ return result;
+}
+
+// Parses an ID created using |GeneratePrinterId| to it's components:
+// the extension ID and the printer ID internal to the extension.
+// Returns whenter the ID was succesfully parsed.
+bool ParsePrinterId(const std::string& printer_id,
+ std::string* extension_id,
+ std::string* internal_printer_id) {
+ size_t separator = printer_id.find_first_of(kPrinterIdSeparator);
+ if (separator == std::string::npos)
+ return false;
+ *extension_id = printer_id.substr(0, separator);
+ *internal_printer_id = printer_id.substr(separator + 1);
+ return true;
+}
+
+void UpdatePrinterWithExtensionInfo(base::DictionaryValue* printer,
+ const Extension* extension) {
+ std::string internal_printer_id;
+ CHECK(printer->GetString("id", &internal_printer_id));
+ printer->SetString("id",
+ GeneratePrinterId(extension->id(), internal_printer_id));
+ printer->SetString("extensionId", extension->id());
+ printer->SetString("extensionName", extension->name());
+
+ base::string16 printer_name;
+ if (printer->GetString("name", &printer_name) &&
+ base::i18n::AdjustStringForLocaleDirection(&printer_name)) {
+ printer->SetString("name", printer_name);
+ }
+
+ base::string16 printer_description;
+ if (printer->GetString("description", &printer_description) &&
+ base::i18n::AdjustStringForLocaleDirection(&printer_description)) {
+ printer->SetString("description", printer_description);
+ }
+}
+
+// Holds information about a pending onGetPrintersRequested request;
+// in particular, the list of extensions to which the event was dispatched but
+// which haven't yet responded, and the |GetPrinters| callback associated with
+// the event.
+class GetPrintersRequest {
+ public:
+ explicit GetPrintersRequest(
+ const PrinterProviderAPI::GetPrintersCallback& callback);
+ ~GetPrintersRequest();
+
+ // Adds an extension id to the list of the extensions that need to respond
+ // to the event.
+ void AddSource(const std::string& extension_id);
+
+ // Whether all extensions have responded to the event.
+ bool IsDone() const;
+
+ // Runs the callback for an extension and removes the extension from the
+ // list of extensions that still have to respond to the event.
+ void ReportForExtension(const std::string& extension_id,
+ const base::ListValue& printers);
+
+ private:
+ // Callback reporting event result for an extension. Called once for each
+ // extension.
+ PrinterProviderAPI::GetPrintersCallback callback_;
+
+ // The list of extensions that still have to respond to the event.
+ std::set<std::string> extensions_;
+};
+
+// Keeps track of pending chrome.printerProvider.onGetPrintersRequested
+// requests.
+class PendingGetPrintersRequests {
+ public:
+ PendingGetPrintersRequests();
+ ~PendingGetPrintersRequests();
+
+ // Adds a new request to the set of pending requests. Returns the id
+ // assigned to the request.
+ int Add(const PrinterProviderAPI::GetPrintersCallback& callback);
+
+ // Completes a request for an extension. It runs the request callback with
+ // values reported by the extension.
+ bool CompleteForExtension(const std::string& extension_id,
+ int request_id,
+ const base::ListValue& result);
+
+ // Runs callbacks for the extension for all requests that are waiting for a
+ // response from the extension with the provided extension id. Callbacks are
+ // called as if the extension reported empty set of printers.
+ void FailAllForExtension(const std::string& extension_id);
+
+ // Adds an extension id to the list of the extensions that need to respond to
+ // the event.
+ bool AddSource(int request_id, const std::string& extension_id);
+
+ private:
+ int last_request_id_;
+ std::map<int, GetPrintersRequest> pending_requests_;
+
+ DISALLOW_COPY_AND_ASSIGN(PendingGetPrintersRequests);
+};
+
+// Keeps track of pending chrome.printerProvider.onGetCapabilityRequested
+// requests for an extension.
+class PendingGetCapabilityRequests {
+ public:
+ PendingGetCapabilityRequests();
+ ~PendingGetCapabilityRequests();
+
+ // Adds a new request to the set. Only information needed is the callback
+ // associated with the request. Returns the id assigned to the request.
+ int Add(const PrinterProviderAPI::GetCapabilityCallback& callback);
+
+ // Completes the request with the provided request id. It runs the request
+ // callback and removes the request from the set.
+ bool Complete(int request_id, const base::DictionaryValue& result);
+
+ // Runs all pending callbacks with empty capability value and clears the
+ // set of pending requests.
+ void FailAll();
+
+ private:
+ int last_request_id_;
+ std::map<int, PrinterProviderAPI::GetCapabilityCallback> pending_requests_;
+};
+
+// Keeps track of pending chrome.printerProvider.onPrintRequested requests
+// for an extension.
+class PendingPrintRequests {
+ public:
+ PendingPrintRequests();
+ ~PendingPrintRequests();
+
+ // Adds a new request to the set. Only information needed is the callback
+ // associated with the request. Returns the id assigned to the request.
+ int Add(const PrinterProviderPrintJob& job,
+ const PrinterProviderAPI::PrintCallback& callback);
+
+ // Gets print job associated with a request.
+ const PrinterProviderPrintJob* GetPrintJob(int request_id) const;
+
+ // Completes the request with the provided request id. It runs the request
+ // callback and removes the request from the set.
+ bool Complete(int request_id, bool success, const std::string& result);
+
+ // Runs all pending callbacks with ERROR_FAILED and clears the set of
+ // pending requests.
+ void FailAll();
+
+ private:
+ struct PrintRequest {
+ PrinterProviderAPI::PrintCallback callback;
+ PrinterProviderPrintJob job;
+ };
+
+ int last_request_id_;
+ std::map<int, PrintRequest> pending_requests_;
+};
+
+// Keeps track of pending chrome.printerProvider.onGetUsbPrinterInfoRequested
+// requests for an extension.
+class PendingUsbPrinterInfoRequests {
+ public:
+ PendingUsbPrinterInfoRequests();
+ ~PendingUsbPrinterInfoRequests();
+
+ // Adds a new request to the set. Only information needed is the callback
+ // associated with the request. Returns the id assigned to the request.
+ int Add(const PrinterProviderAPI::GetPrinterInfoCallback& callback);
+
+ // Completes the request with the provided request id. It runs the request
+ // callback and removes the request from the set.
+ void Complete(int request_id, const base::DictionaryValue& printer_info);
+
+ // Runs all pending callbacks with empty capability value and clears the
+ // set of pending requests.
+ void FailAll();
+
+ private:
+ int last_request_id_ = 0;
+ std::map<int, PrinterProviderAPI::GetPrinterInfoCallback> pending_requests_;
+};
+
+// Implements chrome.printerProvider API events.
+class PrinterProviderAPIImpl : public PrinterProviderAPI,
+ public PrinterProviderInternalAPIObserver,
+ public ExtensionRegistryObserver {
+ public:
+ explicit PrinterProviderAPIImpl(content::BrowserContext* browser_context);
+ ~PrinterProviderAPIImpl() override;
+
+ private:
+ // PrinterProviderAPI implementation:
+ void DispatchGetPrintersRequested(
+ const PrinterProviderAPI::GetPrintersCallback& callback) override;
+ void DispatchGetCapabilityRequested(
+ const std::string& printer_id,
+ const PrinterProviderAPI::GetCapabilityCallback& callback) override;
+ void DispatchPrintRequested(
+ const PrinterProviderPrintJob& job,
+ const PrinterProviderAPI::PrintCallback& callback) override;
+ const PrinterProviderPrintJob* GetPrintJob(const Extension* extension,
+ int request_id) const override;
+ void DispatchGetUsbPrinterInfoRequested(
+ const std::string& extension_id,
+ scoped_refptr<UsbDevice> device,
+ const PrinterProviderAPI::GetPrinterInfoCallback& callback) override;
+
+ // PrinterProviderInternalAPIObserver implementation:
+ void OnGetPrintersResult(
+ const Extension* extension,
+ int request_id,
+ const PrinterProviderInternalAPIObserver::PrinterInfoVector& result)
+ override;
+ void OnGetCapabilityResult(const Extension* extension,
+ int request_id,
+ const base::DictionaryValue& result) override;
+ void OnPrintResult(const Extension* extension,
+ int request_id,
+ api::printer_provider_internal::PrintError error) override;
+ void OnGetUsbPrinterInfoResult(
+ const Extension* extension,
+ int request_id,
+ const api::printer_provider::PrinterInfo* printer_info) override;
+
+ // ExtensionRegistryObserver implementation:
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+
+ // Called before chrome.printerProvider.onGetPrintersRequested event is
+ // dispatched to an extension. It returns whether the extension is interested
+ // in the event. If the extension listens to the event, it's added to the set
+ // of |request| sources. |request| is |GetPrintersRequest| object associated
+ // with the event.
+ bool WillRequestPrinters(int request_id,
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ Event* event,
+ const base::DictionaryValue* listener_filter);
+
+ content::BrowserContext* browser_context_;
+
+ PendingGetPrintersRequests pending_get_printers_requests_;
+
+ std::map<std::string, PendingPrintRequests> pending_print_requests_;
+
+ std::map<std::string, PendingGetCapabilityRequests>
+ pending_capability_requests_;
+
+ std::map<std::string, PendingUsbPrinterInfoRequests>
+ pending_usb_printer_info_requests_;
+
+ ScopedObserver<PrinterProviderInternalAPI, PrinterProviderInternalAPIObserver>
+ internal_api_observer_;
+
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrinterProviderAPIImpl);
+};
+
+GetPrintersRequest::GetPrintersRequest(
+ const PrinterProviderAPI::GetPrintersCallback& callback)
+ : callback_(callback) {
+}
+
+GetPrintersRequest::~GetPrintersRequest() {
+}
+
+void GetPrintersRequest::AddSource(const std::string& extension_id) {
+ extensions_.insert(extension_id);
+}
+
+bool GetPrintersRequest::IsDone() const {
+ return extensions_.empty();
+}
+
+void GetPrintersRequest::ReportForExtension(const std::string& extension_id,
+ const base::ListValue& printers) {
+ if (extensions_.erase(extension_id) > 0)
+ callback_.Run(printers, IsDone());
+}
+
+PendingGetPrintersRequests::PendingGetPrintersRequests() : last_request_id_(0) {
+}
+
+PendingGetPrintersRequests::~PendingGetPrintersRequests() {
+}
+
+int PendingGetPrintersRequests::Add(
+ const PrinterProviderAPI::GetPrintersCallback& callback) {
+ pending_requests_.insert(
+ std::make_pair(++last_request_id_, GetPrintersRequest(callback)));
+ return last_request_id_;
+}
+
+bool PendingGetPrintersRequests::CompleteForExtension(
+ const std::string& extension_id,
+ int request_id,
+ const base::ListValue& result) {
+ auto it = pending_requests_.find(request_id);
+ if (it == pending_requests_.end())
+ return false;
+
+ it->second.ReportForExtension(extension_id, result);
+ if (it->second.IsDone()) {
+ pending_requests_.erase(it);
+ }
+ return true;
+}
+
+void PendingGetPrintersRequests::FailAllForExtension(
+ const std::string& extension_id) {
+ auto it = pending_requests_.begin();
+ while (it != pending_requests_.end()) {
+ int request_id = it->first;
+ // |it| may get deleted during |CompleteForExtension|, so progress it to the
+ // next item before calling the method.
+ ++it;
+ CompleteForExtension(extension_id, request_id, base::ListValue());
+ }
+}
+
+bool PendingGetPrintersRequests::AddSource(int request_id,
+ const std::string& extension_id) {
+ auto it = pending_requests_.find(request_id);
+ if (it == pending_requests_.end())
+ return false;
+
+ it->second.AddSource(extension_id);
+ return true;
+}
+
+PendingGetCapabilityRequests::PendingGetCapabilityRequests()
+ : last_request_id_(0) {
+}
+
+PendingGetCapabilityRequests::~PendingGetCapabilityRequests() {
+}
+
+int PendingGetCapabilityRequests::Add(
+ const PrinterProviderAPI::GetCapabilityCallback& callback) {
+ pending_requests_[++last_request_id_] = callback;
+ return last_request_id_;
+}
+
+bool PendingGetCapabilityRequests::Complete(
+ int request_id,
+ const base::DictionaryValue& response) {
+ auto it = pending_requests_.find(request_id);
+ if (it == pending_requests_.end())
+ return false;
+
+ PrinterProviderAPI::GetCapabilityCallback callback = it->second;
+ pending_requests_.erase(it);
+
+ callback.Run(response);
+ return true;
+}
+
+void PendingGetCapabilityRequests::FailAll() {
+ for (auto& request : pending_requests_)
+ request.second.Run(base::DictionaryValue());
+ pending_requests_.clear();
+}
+
+PendingPrintRequests::PendingPrintRequests() : last_request_id_(0) {
+}
+
+PendingPrintRequests::~PendingPrintRequests() {
+}
+
+int PendingPrintRequests::Add(
+ const PrinterProviderPrintJob& job,
+ const PrinterProviderAPI::PrintCallback& callback) {
+ PrintRequest request;
+ request.callback = callback;
+ request.job = job;
+ pending_requests_[++last_request_id_] = request;
+ return last_request_id_;
+}
+
+bool PendingPrintRequests::Complete(int request_id,
+ bool success,
+ const std::string& response) {
+ auto it = pending_requests_.find(request_id);
+ if (it == pending_requests_.end())
+ return false;
+
+ PrinterProviderAPI::PrintCallback callback = it->second.callback;
+ pending_requests_.erase(it);
+
+ callback.Run(success, response);
+ return true;
+}
+
+const PrinterProviderPrintJob* PendingPrintRequests::GetPrintJob(
+ int request_id) const {
+ auto it = pending_requests_.find(request_id);
+ if (it == pending_requests_.end())
+ return nullptr;
+
+ return &it->second.job;
+}
+
+void PendingPrintRequests::FailAll() {
+ for (auto& request : pending_requests_)
+ request.second.callback.Run(false,
+ PrinterProviderAPI::GetDefaultPrintError());
+ pending_requests_.clear();
+}
+
+PendingUsbPrinterInfoRequests::PendingUsbPrinterInfoRequests() {
+}
+
+PendingUsbPrinterInfoRequests::~PendingUsbPrinterInfoRequests() {
+}
+
+int PendingUsbPrinterInfoRequests::Add(
+ const PrinterProviderAPI::GetPrinterInfoCallback& callback) {
+ pending_requests_[++last_request_id_] = callback;
+ return last_request_id_;
+}
+
+void PendingUsbPrinterInfoRequests::Complete(
+ int request_id,
+ const base::DictionaryValue& printer_info) {
+ auto it = pending_requests_.find(request_id);
+ if (it == pending_requests_.end())
+ return;
+
+ PrinterProviderAPI::GetPrinterInfoCallback callback = it->second;
+ pending_requests_.erase(it);
+
+ callback.Run(printer_info);
+}
+
+void PendingUsbPrinterInfoRequests::FailAll() {
+ for (auto& request : pending_requests_) {
+ request.second.Run(base::DictionaryValue());
+ }
+ pending_requests_.clear();
+}
+
+PrinterProviderAPIImpl::PrinterProviderAPIImpl(
+ content::BrowserContext* browser_context)
+ : browser_context_(browser_context),
+ internal_api_observer_(this),
+ extension_registry_observer_(this) {
+ internal_api_observer_.Add(
+ PrinterProviderInternalAPI::GetFactoryInstance()->Get(browser_context));
+ extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context));
+}
+
+PrinterProviderAPIImpl::~PrinterProviderAPIImpl() {
+}
+
+void PrinterProviderAPIImpl::DispatchGetPrintersRequested(
+ const GetPrintersCallback& callback) {
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ if (!event_router->HasEventListener(
+ api::printer_provider::OnGetPrintersRequested::kEventName)) {
+ callback.Run(base::ListValue(), true /* done */);
+ return;
+ }
+
+ // |pending_get_printers_requests_| take ownership of |request| which gets
+ // NULLed out. Save the pointer before passing it to the requests, as it will
+ // be needed later on.
+ int request_id = pending_get_printers_requests_.Add(callback);
+
+ scoped_ptr<base::ListValue> internal_args(new base::ListValue);
+ // Request id is not part of the public API, but it will be massaged out in
+ // custom bindings.
+ internal_args->AppendInteger(request_id);
+
+ scoped_ptr<Event> event(
+ new Event(events::PRINTER_PROVIDER_ON_GET_PRINTERS_REQUESTED,
+ api::printer_provider::OnGetPrintersRequested::kEventName,
+ std::move(internal_args)));
+ // This callback is called synchronously during |BroadcastEvent|, so
+ // Unretained is safe.
+ event->will_dispatch_callback =
+ base::Bind(&PrinterProviderAPIImpl::WillRequestPrinters,
+ base::Unretained(this), request_id);
+
+ event_router->BroadcastEvent(std::move(event));
+}
+
+void PrinterProviderAPIImpl::DispatchGetCapabilityRequested(
+ const std::string& printer_id,
+ const PrinterProviderAPI::GetCapabilityCallback& callback) {
+ std::string extension_id;
+ std::string internal_printer_id;
+ if (!ParsePrinterId(printer_id, &extension_id, &internal_printer_id)) {
+ callback.Run(base::DictionaryValue());
+ return;
+ }
+
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ if (!event_router->ExtensionHasEventListener(
+ extension_id,
+ api::printer_provider::OnGetCapabilityRequested::kEventName)) {
+ callback.Run(base::DictionaryValue());
+ return;
+ }
+
+ int request_id = pending_capability_requests_[extension_id].Add(callback);
+
+ scoped_ptr<base::ListValue> internal_args(new base::ListValue);
+ // Request id is not part of the public API, but it will be massaged out in
+ // custom bindings.
+ internal_args->AppendInteger(request_id);
+ internal_args->AppendString(internal_printer_id);
+
+ scoped_ptr<Event> event(
+ new Event(events::PRINTER_PROVIDER_ON_GET_CAPABILITY_REQUESTED,
+ api::printer_provider::OnGetCapabilityRequested::kEventName,
+ std::move(internal_args)));
+
+ event_router->DispatchEventToExtension(extension_id, std::move(event));
+}
+
+void PrinterProviderAPIImpl::DispatchPrintRequested(
+ const PrinterProviderPrintJob& job,
+ const PrinterProviderAPI::PrintCallback& callback) {
+ std::string extension_id;
+ std::string internal_printer_id;
+ if (!ParsePrinterId(job.printer_id, &extension_id, &internal_printer_id)) {
+ callback.Run(false, PrinterProviderAPI::GetDefaultPrintError());
+ return;
+ }
+
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ if (!event_router->ExtensionHasEventListener(
+ extension_id, api::printer_provider::OnPrintRequested::kEventName)) {
+ callback.Run(false, PrinterProviderAPI::GetDefaultPrintError());
+ return;
+ }
+
+ api::printer_provider::PrintJob print_job;
+ print_job.printer_id = internal_printer_id;
+
+ JSONStringValueDeserializer deserializer(job.ticket_json);
+ scoped_ptr<base::Value> ticket_value = deserializer.Deserialize(NULL, NULL);
+ if (!ticket_value ||
+ !api::printer_provider::PrintJob::Ticket::Populate(*ticket_value,
+ &print_job.ticket)) {
+ callback.Run(false, api::printer_provider::ToString(
+ api::printer_provider::PRINT_ERROR_INVALID_TICKET));
+ return;
+ }
+
+ print_job.content_type = job.content_type;
+ print_job.title = base::UTF16ToUTF8(job.job_title);
+ int request_id = pending_print_requests_[extension_id].Add(job, callback);
+
+ scoped_ptr<base::ListValue> internal_args(new base::ListValue);
+ // Request id is not part of the public API and it will be massaged out in
+ // custom bindings.
+ internal_args->AppendInteger(request_id);
+ internal_args->Append(print_job.ToValue().release());
+ scoped_ptr<Event> event(
+ new Event(events::PRINTER_PROVIDER_ON_PRINT_REQUESTED,
+ api::printer_provider::OnPrintRequested::kEventName,
+ std::move(internal_args)));
+ event_router->DispatchEventToExtension(extension_id, std::move(event));
+}
+
+const PrinterProviderPrintJob* PrinterProviderAPIImpl::GetPrintJob(
+ const Extension* extension,
+ int request_id) const {
+ auto it = pending_print_requests_.find(extension->id());
+ if (it == pending_print_requests_.end())
+ return nullptr;
+ return it->second.GetPrintJob(request_id);
+}
+
+void PrinterProviderAPIImpl::DispatchGetUsbPrinterInfoRequested(
+ const std::string& extension_id,
+ scoped_refptr<UsbDevice> device,
+ const PrinterProviderAPI::GetPrinterInfoCallback& callback) {
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ if (!event_router->ExtensionHasEventListener(
+ extension_id,
+ api::printer_provider::OnGetUsbPrinterInfoRequested::kEventName)) {
+ callback.Run(base::DictionaryValue());
+ return;
+ }
+
+ int request_id =
+ pending_usb_printer_info_requests_[extension_id].Add(callback);
+ api::usb::Device api_device;
+ UsbGuidMap::Get(browser_context_)->GetApiDevice(device, &api_device);
+
+ scoped_ptr<base::ListValue> internal_args(new base::ListValue());
+ // Request id is not part of the public API and it will be massaged out in
+ // custom bindings.
+ internal_args->AppendInteger(request_id);
+ internal_args->Append(api_device.ToValue());
+ scoped_ptr<Event> event(
+ new Event(events::PRINTER_PROVIDER_ON_GET_USB_PRINTER_INFO_REQUESTED,
+ api::printer_provider::OnGetUsbPrinterInfoRequested::kEventName,
+ std::move(internal_args)));
+ event_router->DispatchEventToExtension(extension_id, std::move(event));
+}
+
+void PrinterProviderAPIImpl::OnGetPrintersResult(
+ const Extension* extension,
+ int request_id,
+ const PrinterProviderInternalAPIObserver::PrinterInfoVector& result) {
+ base::ListValue printer_list;
+
+ // Update some printer description properties to better identify the extension
+ // managing the printer.
+ for (const api::printer_provider::PrinterInfo& p : result) {
+ scoped_ptr<base::DictionaryValue> printer(p.ToValue());
+ UpdatePrinterWithExtensionInfo(printer.get(), extension);
+ printer_list.Append(std::move(printer));
+ }
+
+ pending_get_printers_requests_.CompleteForExtension(extension->id(),
+ request_id, printer_list);
+}
+
+void PrinterProviderAPIImpl::OnGetCapabilityResult(
+ const Extension* extension,
+ int request_id,
+ const base::DictionaryValue& result) {
+ pending_capability_requests_[extension->id()].Complete(request_id, result);
+}
+
+void PrinterProviderAPIImpl::OnPrintResult(
+ const Extension* extension,
+ int request_id,
+ api::printer_provider_internal::PrintError error) {
+ const std::string error_str =
+ error == api::printer_provider_internal::PRINT_ERROR_NONE
+ ? PrinterProviderAPI::GetDefaultPrintError()
+ : api::printer_provider_internal::ToString(error);
+ pending_print_requests_[extension->id()].Complete(
+ request_id, error == api::printer_provider_internal::PRINT_ERROR_OK,
+ error_str);
+}
+
+void PrinterProviderAPIImpl::OnGetUsbPrinterInfoResult(
+ const Extension* extension,
+ int request_id,
+ const api::printer_provider::PrinterInfo* result) {
+ if (result) {
+ scoped_ptr<base::DictionaryValue> printer(result->ToValue());
+ UpdatePrinterWithExtensionInfo(printer.get(), extension);
+ pending_usb_printer_info_requests_[extension->id()].Complete(request_id,
+ *printer);
+ } else {
+ pending_usb_printer_info_requests_[extension->id()].Complete(
+ request_id, base::DictionaryValue());
+ }
+}
+
+void PrinterProviderAPIImpl::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ pending_get_printers_requests_.FailAllForExtension(extension->id());
+
+ auto print_it = pending_print_requests_.find(extension->id());
+ if (print_it != pending_print_requests_.end()) {
+ print_it->second.FailAll();
+ pending_print_requests_.erase(print_it);
+ }
+
+ auto capability_it = pending_capability_requests_.find(extension->id());
+ if (capability_it != pending_capability_requests_.end()) {
+ capability_it->second.FailAll();
+ pending_capability_requests_.erase(capability_it);
+ }
+
+ auto usb_it = pending_usb_printer_info_requests_.find(extension->id());
+ if (usb_it != pending_usb_printer_info_requests_.end()) {
+ usb_it->second.FailAll();
+ pending_usb_printer_info_requests_.erase(usb_it);
+ }
+}
+
+bool PrinterProviderAPIImpl::WillRequestPrinters(
+ int request_id,
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ Event* event,
+ const base::DictionaryValue* listener_filter) {
+ if (!extension)
+ return false;
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ if (!event_router->ExtensionHasEventListener(
+ extension->id(),
+ api::printer_provider::OnGetPrintersRequested::kEventName)) {
+ return false;
+ }
+
+ return pending_get_printers_requests_.AddSource(request_id, extension->id());
+}
+
+} // namespace
+
+// static
+PrinterProviderAPI* PrinterProviderAPI::Create(
+ content::BrowserContext* context) {
+ return new PrinterProviderAPIImpl(context);
+}
+
+// static
+std::string PrinterProviderAPI::GetDefaultPrintError() {
+ return api::printer_provider_internal::ToString(
+ api::printer_provider_internal::PRINT_ERROR_FAILED);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/printer_provider/printer_provider_api.h b/chromium/extensions/browser/api/printer_provider/printer_provider_api.h
new file mode 100644
index 00000000000..2112a76be27
--- /dev/null
+++ b/chromium/extensions/browser/api/printer_provider/printer_provider_api.h
@@ -0,0 +1,104 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_PRINTER_PROVIDER_PRINTER_PROVIDER_API_H_
+#define EXTENSIONS_BROWSER_API_PRINTER_PROVIDER_PRINTER_PROVIDER_API_H_
+
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace device {
+class UsbDevice;
+}
+
+namespace extensions {
+class Extension;
+struct PrinterProviderPrintJob;
+}
+
+namespace extensions {
+
+// Implements chrome.printerProvider API events.
+class PrinterProviderAPI : public KeyedService {
+ public:
+ using GetPrintersCallback =
+ base::Callback<void(const base::ListValue& printers, bool done)>;
+ using GetCapabilityCallback =
+ base::Callback<void(const base::DictionaryValue& capability)>;
+ using PrintCallback =
+ base::Callback<void(bool success, const std::string& error)>;
+ using GetPrinterInfoCallback =
+ base::Callback<void(const base::DictionaryValue& printer_info)>;
+
+ static PrinterProviderAPI* Create(content::BrowserContext* context);
+
+ // Returns generic error string for print request.
+ static std::string GetDefaultPrintError();
+
+ ~PrinterProviderAPI() override {}
+
+ // Requests list of supported printers from extensions implementing
+ // chrome.printerProvider API. It dispatches
+ // chrome.printerProvider.onGetPrintersRequested event. The callback is
+ // called once for every extension handling the event with a list of its
+ // supported printers. The printer values reported by an extension are
+ // added "extensionId" property that is set to the ID of the extension
+ // returning the list and "extensionName" property set to the extension's
+ // name.
+ // Note that the "id" property of printer values reported by an extension are
+ // rewriten as "<extension_id>:<id>" to ensure they are unique across
+ // different extensions.
+ virtual void DispatchGetPrintersRequested(
+ const GetPrintersCallback& callback) = 0;
+
+ // Requests printer capability for a printer with id |printer_id|.
+ // |printer_id| should be one of the printer ids reported by |GetPrinters|
+ // callback.
+ // It dispatches chrome.printerProvider.onGetCapabilityRequested event
+ // to the extension that manages the printer (which can be determined from
+ // |printer_id| value).
+ // |callback| is passed a dictionary value containing printer capabilities as
+ // reported by the extension.
+ virtual void DispatchGetCapabilityRequested(
+ const std::string& printer_id,
+ const GetCapabilityCallback& callback) = 0;
+
+ // It dispatches chrome.printerProvider.onPrintRequested event with the
+ // provided print job. The event is dispatched only to the extension that
+ // manages printer with id |job.printer_id|.
+ // |callback| is passed the print status returned by the extension, and it
+ // must not be null.
+ virtual void DispatchPrintRequested(const PrinterProviderPrintJob& job,
+ const PrintCallback& callback) = 0;
+
+ // Returns print job associated with the print request with id |request_id|
+ // for extension |extension|.
+ // It should return NULL if the job for the request does not exist.
+ virtual const PrinterProviderPrintJob* GetPrintJob(const Extension* extension,
+ int request_id) const = 0;
+
+ // Dispatches a chrome.printerProvider.getUsbPrinterInfo event requesting
+ // information about |device_id|. The event is only dispatched to the
+ // extension identified by |extension_id|.
+ virtual void DispatchGetUsbPrinterInfoRequested(
+ const std::string& extension_id,
+ scoped_refptr<device::UsbDevice> device,
+ const PrinterProviderAPI::GetPrinterInfoCallback& callback) = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_PRINTER_PROVIDER_PRINTER_PROVIDER_API_H_
diff --git a/chromium/extensions/browser/api/printer_provider/printer_provider_api_factory.cc b/chromium/extensions/browser/api/printer_provider/printer_provider_api_factory.cc
new file mode 100644
index 00000000000..05d67361b37
--- /dev/null
+++ b/chromium/extensions/browser/api/printer_provider/printer_provider_api_factory.cc
@@ -0,0 +1,55 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/printer_provider/printer_provider_api_factory.h"
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "extensions/browser/api/printer_provider/printer_provider_api.h"
+#include "extensions/browser/api/printer_provider_internal/printer_provider_internal_api.h"
+#include "extensions/browser/extension_registry_factory.h"
+#include "extensions/browser/extensions_browser_client.h"
+
+namespace {
+
+static base::LazyInstance<extensions::PrinterProviderAPIFactory> g_api_factory =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+namespace extensions {
+
+// static
+PrinterProviderAPIFactory* PrinterProviderAPIFactory::GetInstance() {
+ return g_api_factory.Pointer();
+}
+
+PrinterProviderAPI* PrinterProviderAPIFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<PrinterProviderAPI*>(
+ GetServiceForBrowserContext(context, true));
+}
+
+PrinterProviderAPIFactory::PrinterProviderAPIFactory()
+ : BrowserContextKeyedServiceFactory(
+ "PrinterProviderAPI",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
+ DependsOn(PrinterProviderInternalAPI::GetFactoryInstance());
+ DependsOn(ExtensionRegistryFactory::GetInstance());
+}
+
+PrinterProviderAPIFactory::~PrinterProviderAPIFactory() {
+}
+
+KeyedService* PrinterProviderAPIFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ return PrinterProviderAPI::Create(context);
+}
+
+content::BrowserContext* PrinterProviderAPIFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/printer_provider/printer_provider_api_factory.h b/chromium/extensions/browser/api/printer_provider/printer_provider_api_factory.h
new file mode 100644
index 00000000000..b238322dce4
--- /dev/null
+++ b/chromium/extensions/browser/api/printer_provider/printer_provider_api_factory.h
@@ -0,0 +1,48 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_PRINTER_PROVIDER_PRINTER_PROVIDER_API_FACTORY_H_
+#define EXTENSIONS_BROWSER_API_PRINTER_PROVIDER_PRINTER_PROVIDER_API_FACTORY_H_
+
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+class KeyedService;
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+class PrinterProviderAPI;
+}
+
+namespace extensions {
+
+// Factory for PrinterProviderAPI.
+class PrinterProviderAPIFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static PrinterProviderAPIFactory* GetInstance();
+
+ PrinterProviderAPI* GetForBrowserContext(content::BrowserContext* context);
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<PrinterProviderAPIFactory>;
+
+ PrinterProviderAPIFactory();
+ ~PrinterProviderAPIFactory() override;
+
+ // BrowserContextKeyedServiceFactory implementation:
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(PrinterProviderAPIFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_PRINTER_PROVIDER_PRINTER_PROVIDER_API_FACTORY_H_
diff --git a/chromium/extensions/browser/api/printer_provider/printer_provider_apitest.cc b/chromium/extensions/browser/api/printer_provider/printer_provider_apitest.cc
new file mode 100644
index 00000000000..b49663c8691
--- /dev/null
+++ b/chromium/extensions/browser/api/printer_provider/printer_provider_apitest.cc
@@ -0,0 +1,859 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "device/usb/mock_usb_device.h"
+#include "device/usb/mock_usb_service.h"
+#include "extensions/browser/api/printer_provider/printer_provider_api.h"
+#include "extensions/browser/api/printer_provider/printer_provider_api_factory.h"
+#include "extensions/browser/api/printer_provider/printer_provider_print_job.h"
+#include "extensions/browser/api/usb/usb_guid_map.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/value_builder.h"
+#include "extensions/shell/test/shell_apitest.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "extensions/test/result_catcher.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+// Callback for PrinterProviderAPI::DispatchGetPrintersRequested calls.
+// It appends items in |printers| to |*printers_out|. If |done| is set, it runs
+// |callback|.
+void AppendPrintersAndRunCallbackIfDone(base::ListValue* printers_out,
+ const base::Closure& callback,
+ const base::ListValue& printers,
+ bool done) {
+ for (size_t i = 0; i < printers.GetSize(); ++i) {
+ const base::DictionaryValue* printer = NULL;
+ EXPECT_TRUE(printers.GetDictionary(i, &printer))
+ << "Found invalid printer value at index " << i << ": " << printers;
+ if (printer)
+ printers_out->Append(printer->DeepCopy());
+ }
+ if (done && !callback.is_null())
+ callback.Run();
+}
+
+// Callback for PrinterProviderAPI::DispatchPrintRequested calls.
+// It copies |value| to |*result| and runs |callback|.
+void RecordPrintResultAndRunCallback(bool* result_success,
+ std::string* result_status,
+ const base::Closure& callback,
+ bool success,
+ const std::string& status) {
+ *result_success = success;
+ *result_status = status;
+ if (!callback.is_null())
+ callback.Run();
+}
+
+// Callback for PrinterProviderAPI::DispatchGetCapabilityRequested calls.
+// It saves reported |value| as JSON string to |*result| and runs |callback|.
+void RecordDictAndRunCallback(std::string* result,
+ const base::Closure& callback,
+ const base::DictionaryValue& value) {
+ JSONStringValueSerializer serializer(result);
+ EXPECT_TRUE(serializer.Serialize(value));
+ if (!callback.is_null())
+ callback.Run();
+}
+
+// Callback for PrinterProvider::DispatchGrantUsbPrinterAccess calls.
+// It expects |value| to equal |expected_value| and runs |callback|.
+void ExpectValueAndRunCallback(const base::Value* expected_value,
+ const base::Closure& callback,
+ const base::DictionaryValue& value) {
+ EXPECT_TRUE(value.Equals(expected_value));
+ if (!callback.is_null())
+ callback.Run();
+}
+
+// Tests for chrome.printerProvider API.
+class PrinterProviderApiTest : public ShellApiTest {
+ public:
+ enum PrintRequestDataType {
+ PRINT_REQUEST_DATA_TYPE_NOT_SET,
+ PRINT_REQUEST_DATA_TYPE_FILE,
+ PRINT_REQUEST_DATA_TYPE_FILE_DELETED,
+ PRINT_REQUEST_DATA_TYPE_BYTES
+ };
+
+ PrinterProviderApiTest() {}
+ ~PrinterProviderApiTest() override {}
+
+ void StartGetPrintersRequest(
+ const PrinterProviderAPI::GetPrintersCallback& callback) {
+ PrinterProviderAPIFactory::GetInstance()
+ ->GetForBrowserContext(browser_context())
+ ->DispatchGetPrintersRequested(callback);
+ }
+
+ void StartGetUsbPrinterInfoRequest(
+ const std::string& extension_id,
+ scoped_refptr<device::UsbDevice> device,
+ const PrinterProviderAPI::GetPrinterInfoCallback& callback) {
+ PrinterProviderAPIFactory::GetInstance()
+ ->GetForBrowserContext(browser_context())
+ ->DispatchGetUsbPrinterInfoRequested(extension_id, device, callback);
+ }
+
+ void StartPrintRequestWithNoData(
+ const std::string& extension_id,
+ const PrinterProviderAPI::PrintCallback& callback) {
+ PrinterProviderPrintJob job;
+ job.printer_id = extension_id + ":printer_id";
+ job.ticket_json = "{}";
+ job.content_type = "application/pdf";
+
+ PrinterProviderAPIFactory::GetInstance()
+ ->GetForBrowserContext(browser_context())
+ ->DispatchPrintRequested(job, callback);
+ }
+
+ void StartPrintRequestUsingDocumentBytes(
+ const std::string& extension_id,
+ const PrinterProviderAPI::PrintCallback& callback) {
+ PrinterProviderPrintJob job;
+ job.printer_id = extension_id + ":printer_id";
+ job.job_title = base::ASCIIToUTF16("Print job");
+ job.ticket_json = "{}";
+ job.content_type = "application/pdf";
+ const unsigned char kDocumentBytes[] = {'b', 'y', 't', 'e', 's'};
+ job.document_bytes =
+ new base::RefCountedBytes(kDocumentBytes, arraysize(kDocumentBytes));
+
+ PrinterProviderAPIFactory::GetInstance()
+ ->GetForBrowserContext(browser_context())
+ ->DispatchPrintRequested(job, callback);
+ }
+
+ bool StartPrintRequestUsingFileInfo(
+ const std::string& extension_id,
+ const PrinterProviderAPI::PrintCallback& callback) {
+ PrinterProviderPrintJob job;
+
+ const char kBytes[] = {'b', 'y', 't', 'e', 's'};
+ if (!CreateTempFileWithContents(kBytes, static_cast<int>(arraysize(kBytes)),
+ &job.document_path, &job.file_info)) {
+ ADD_FAILURE() << "Failed to create test file.";
+ return false;
+ }
+
+ job.printer_id = extension_id + ":printer_id";
+ job.job_title = base::ASCIIToUTF16("Print job");
+ job.ticket_json = "{}";
+ job.content_type = "image/pwg-raster";
+
+ PrinterProviderAPIFactory::GetInstance()
+ ->GetForBrowserContext(browser_context())
+ ->DispatchPrintRequested(job, callback);
+ return true;
+ }
+
+ void StartCapabilityRequest(
+ const std::string& extension_id,
+ const PrinterProviderAPI::GetCapabilityCallback& callback) {
+ PrinterProviderAPIFactory::GetInstance()
+ ->GetForBrowserContext(browser_context())
+ ->DispatchGetCapabilityRequested(extension_id + ":printer_id",
+ callback);
+ }
+
+ // Loads chrome.printerProvider test app and initializes is for test
+ // |test_param|.
+ // When the app's background page is loaded, the app will send 'loaded'
+ // message. As a response to the message it will expect string message
+ // specifying the test that should be run. When the app initializes its state
+ // (e.g. registers listener for a chrome.printerProvider event) it will send
+ // message 'ready', at which point the test may be started.
+ // If the app is successfully initialized, |*extension_id_out| will be set to
+ // the loaded extension's id, otherwise it will remain unchanged.
+ void InitializePrinterProviderTestApp(const std::string& app_path,
+ const std::string& test_param,
+ std::string* extension_id_out) {
+ ExtensionTestMessageListener loaded_listener("loaded", true);
+ ExtensionTestMessageListener ready_listener("ready", false);
+
+ const Extension* extension = LoadApp(app_path);
+ ASSERT_TRUE(extension);
+ const std::string extension_id = extension->id();
+
+ loaded_listener.set_extension_id(extension_id);
+ ready_listener.set_extension_id(extension_id);
+
+ ASSERT_TRUE(loaded_listener.WaitUntilSatisfied());
+
+ loaded_listener.Reply(test_param);
+
+ ASSERT_TRUE(ready_listener.WaitUntilSatisfied());
+
+ *extension_id_out = extension_id;
+ }
+
+ // Runs a test for chrome.printerProvider.onPrintRequested event.
+ // |test_param|: The test that should be run.
+ // |expected_result|: The print result the app is expected to report.
+ void RunPrintRequestTestApp(const std::string& test_param,
+ PrintRequestDataType data_type,
+ const std::string& expected_result) {
+ ResultCatcher catcher;
+
+ std::string extension_id;
+ InitializePrinterProviderTestApp("api_test/printer_provider/request_print",
+ test_param, &extension_id);
+ if (extension_id.empty())
+ return;
+
+ base::RunLoop run_loop;
+ bool success;
+ std::string print_status;
+ PrinterProviderAPI::PrintCallback callback =
+ base::Bind(&RecordPrintResultAndRunCallback, &success, &print_status,
+ run_loop.QuitClosure());
+
+ switch (data_type) {
+ case PRINT_REQUEST_DATA_TYPE_NOT_SET:
+ StartPrintRequestWithNoData(extension_id, callback);
+ break;
+ case PRINT_REQUEST_DATA_TYPE_FILE:
+ ASSERT_TRUE(StartPrintRequestUsingFileInfo(extension_id, callback));
+ break;
+ case PRINT_REQUEST_DATA_TYPE_FILE_DELETED:
+ ASSERT_TRUE(StartPrintRequestUsingFileInfo(extension_id, callback));
+ ASSERT_TRUE(data_dir_.Delete());
+ break;
+ case PRINT_REQUEST_DATA_TYPE_BYTES:
+ StartPrintRequestUsingDocumentBytes(extension_id, callback);
+ break;
+ }
+
+ if (data_type != PRINT_REQUEST_DATA_TYPE_NOT_SET)
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ run_loop.Run();
+ EXPECT_EQ(expected_result, print_status);
+ EXPECT_EQ(expected_result == "OK", success);
+ }
+
+ // Runs a test for chrome.printerProvider.onGetCapabilityRequested
+ // event.
+ // |test_param|: The test that should be run.
+ // |expected_result|: The printer capability the app is expected to report.
+ void RunPrinterCapabilitiesRequestTest(const std::string& test_param,
+ const std::string& expected_result) {
+ ResultCatcher catcher;
+
+ std::string extension_id;
+ InitializePrinterProviderTestApp(
+ "api_test/printer_provider/request_capability", test_param,
+ &extension_id);
+ if (extension_id.empty())
+ return;
+
+ base::RunLoop run_loop;
+ std::string result;
+ StartCapabilityRequest(
+ extension_id,
+ base::Bind(&RecordDictAndRunCallback, &result, run_loop.QuitClosure()));
+
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ run_loop.Run();
+ EXPECT_EQ(expected_result, result);
+ }
+
+ // Run a test for the chrome.printerProvider.onGetUsbPrinterInfoRequested
+ // event.
+ // |test_param|: The test that should be run.
+ // |expected_result|: The printer info that the app is expected to report.
+ void RunUsbPrinterInfoRequestTest(const std::string& test_param) {
+ ResultCatcher catcher;
+ scoped_refptr<device::UsbDevice> device =
+ new device::MockUsbDevice(0, 0, "Google", "USB Printer", "");
+ usb_service_.AddDevice(device);
+
+ std::string extension_id;
+ InitializePrinterProviderTestApp("api_test/printer_provider/usb_printers",
+ test_param, &extension_id);
+ ASSERT_FALSE(extension_id.empty());
+
+ scoped_ptr<base::Value> expected_printer_info(new base::DictionaryValue());
+ base::RunLoop run_loop;
+ StartGetUsbPrinterInfoRequest(
+ extension_id, device,
+ base::Bind(&ExpectValueAndRunCallback, expected_printer_info.get(),
+ run_loop.QuitClosure()));
+ run_loop.Run();
+
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+ }
+
+ bool SimulateExtensionUnload(const std::string& extension_id) {
+ ExtensionRegistry* extension_registry =
+ ExtensionRegistry::Get(browser_context());
+
+ const Extension* extension = extension_registry->GetExtensionById(
+ extension_id, ExtensionRegistry::ENABLED);
+ if (!extension)
+ return false;
+
+ extension_registry->RemoveEnabled(extension_id);
+ extension_registry->TriggerOnUnloaded(
+ extension, UnloadedExtensionInfo::REASON_TERMINATE);
+ return true;
+ }
+
+ // Validates that set of printers reported by test apps via
+ // chrome.printerProvider.onGetPritersRequested is the same as the set of
+ // printers in |expected_printers|. |expected_printers| contains list of
+ // printer objects formatted as a JSON string. It is assumed that the values
+ // in |expoected_printers| are unique.
+ void ValidatePrinterListValue(
+ const base::ListValue& printers,
+ const std::vector<scoped_ptr<base::Value>> expected_printers) {
+ ASSERT_EQ(expected_printers.size(), printers.GetSize());
+ for (const auto& printer_value : expected_printers) {
+ EXPECT_TRUE(printers.Find(*printer_value.get()) != printers.end())
+ << "Unable to find " << *printer_value.get() << " in " << printers;
+ }
+ }
+
+ protected:
+ device::MockUsbService usb_service_;
+
+ private:
+ // Initializes |data_dir_| if needed and creates a file in it containing
+ // provided data.
+ bool CreateTempFileWithContents(const char* data,
+ int size,
+ base::FilePath* path,
+ base::File::Info* file_info) {
+ if (!data_dir_.IsValid() && !data_dir_.CreateUniqueTempDir())
+ return false;
+
+ *path = data_dir_.path().AppendASCII("data.pwg");
+ int written = base::WriteFile(*path, data, size);
+ if (written != size)
+ return false;
+ if (!base::GetFileInfo(*path, file_info))
+ return false;
+ return true;
+ }
+
+ base::ScopedTempDir data_dir_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrinterProviderApiTest);
+};
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, PrintJobSuccess) {
+ RunPrintRequestTestApp("OK", PRINT_REQUEST_DATA_TYPE_BYTES, "OK");
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, PrintJobWithFileSuccess) {
+ RunPrintRequestTestApp("OK", PRINT_REQUEST_DATA_TYPE_FILE, "OK");
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest,
+ PrintJobWithFile_FileDeletedBeforeDispatch) {
+ RunPrintRequestTestApp("OK", PRINT_REQUEST_DATA_TYPE_FILE_DELETED,
+ "INVALID_DATA");
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, PrintJobAsyncSuccess) {
+ RunPrintRequestTestApp("ASYNC_RESPONSE", PRINT_REQUEST_DATA_TYPE_BYTES, "OK");
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, PrintJobFailed) {
+ RunPrintRequestTestApp("INVALID_TICKET", PRINT_REQUEST_DATA_TYPE_BYTES,
+ "INVALID_TICKET");
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, NoPrintEventListener) {
+ RunPrintRequestTestApp("NO_LISTENER", PRINT_REQUEST_DATA_TYPE_BYTES,
+ "FAILED");
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest,
+ PrintRequestInvalidCallbackParam) {
+ RunPrintRequestTestApp("INVALID_VALUE", PRINT_REQUEST_DATA_TYPE_BYTES,
+ "FAILED");
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, PrintRequestDataNotSet) {
+ RunPrintRequestTestApp("IGNORE_CALLBACK", PRINT_REQUEST_DATA_TYPE_NOT_SET,
+ "FAILED");
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, PrintRequestAppUnloaded) {
+ ResultCatcher catcher;
+
+ std::string extension_id;
+ InitializePrinterProviderTestApp("api_test/printer_provider/request_print",
+ "IGNORE_CALLBACK", &extension_id);
+ ASSERT_FALSE(extension_id.empty());
+
+ base::RunLoop run_loop;
+ bool success = false;
+ std::string status;
+ StartPrintRequestUsingDocumentBytes(
+ extension_id, base::Bind(&RecordPrintResultAndRunCallback, &success,
+ &status, run_loop.QuitClosure()));
+
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ ASSERT_TRUE(SimulateExtensionUnload(extension_id));
+
+ run_loop.Run();
+ EXPECT_FALSE(success);
+ EXPECT_EQ("FAILED", status);
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetCapabilitySuccess) {
+ RunPrinterCapabilitiesRequestTest("OK", "{\"capability\":\"value\"}");
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetCapabilityAsyncSuccess) {
+ RunPrinterCapabilitiesRequestTest("ASYNC_RESPONSE",
+ "{\"capability\":\"value\"}");
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, EmptyCapability) {
+ RunPrinterCapabilitiesRequestTest("EMPTY", "{}");
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, NoCapabilityEventListener) {
+ RunPrinterCapabilitiesRequestTest("NO_LISTENER", "{}");
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, CapabilityInvalidValue) {
+ RunPrinterCapabilitiesRequestTest("INVALID_VALUE", "{}");
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetCapabilityAppUnloaded) {
+ ResultCatcher catcher;
+
+ std::string extension_id;
+ InitializePrinterProviderTestApp(
+ "api_test/printer_provider/request_capability", "IGNORE_CALLBACK",
+ &extension_id);
+ ASSERT_FALSE(extension_id.empty());
+
+ base::RunLoop run_loop;
+ std::string result;
+ StartCapabilityRequest(
+ extension_id,
+ base::Bind(&RecordDictAndRunCallback, &result, run_loop.QuitClosure()));
+
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ ASSERT_TRUE(SimulateExtensionUnload(extension_id));
+ run_loop.Run();
+ EXPECT_EQ("{}", result);
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetPrintersSuccess) {
+ ResultCatcher catcher;
+
+ std::string extension_id;
+ InitializePrinterProviderTestApp("api_test/printer_provider/request_printers",
+ "OK", &extension_id);
+ ASSERT_FALSE(extension_id.empty());
+
+ base::RunLoop run_loop;
+ base::ListValue printers;
+
+ StartGetPrintersRequest(base::Bind(&AppendPrintersAndRunCallbackIfDone,
+ &printers, run_loop.QuitClosure()));
+
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ run_loop.Run();
+
+ std::vector<scoped_ptr<base::Value>> expected_printers;
+ expected_printers.push_back(
+ DictionaryBuilder()
+ .Set("description", "Test printer")
+ .Set("extensionId", extension_id)
+ .Set("extensionName", "Test printer provider")
+ .Set("id", base::StringPrintf("%s:printer1", extension_id.c_str()))
+ .Set("name", "Printer 1")
+ .Build());
+ expected_printers.push_back(
+ DictionaryBuilder()
+ .Set("extensionId", extension_id)
+ .Set("extensionName", "Test printer provider")
+ .Set("id",
+ base::StringPrintf("%s:printerNoDesc", extension_id.c_str()))
+ .Set("name", "Printer 2")
+ .Build());
+
+ ValidatePrinterListValue(printers, std::move(expected_printers));
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetPrintersAsyncSuccess) {
+ ResultCatcher catcher;
+
+ std::string extension_id;
+ InitializePrinterProviderTestApp("api_test/printer_provider/request_printers",
+ "ASYNC_RESPONSE", &extension_id);
+ ASSERT_FALSE(extension_id.empty());
+
+ base::RunLoop run_loop;
+ base::ListValue printers;
+
+ StartGetPrintersRequest(base::Bind(&AppendPrintersAndRunCallbackIfDone,
+ &printers, run_loop.QuitClosure()));
+
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ run_loop.Run();
+
+ std::vector<scoped_ptr<base::Value>> expected_printers;
+ expected_printers.push_back(
+ DictionaryBuilder()
+ .Set("description", "Test printer")
+ .Set("extensionId", extension_id)
+ .Set("extensionName", "Test printer provider")
+ .Set("id", base::StringPrintf("%s:printer1", extension_id.c_str()))
+ .Set("name", "Printer 1")
+ .Build());
+
+ ValidatePrinterListValue(printers, std::move(expected_printers));
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetPrintersTwoExtensions) {
+ ResultCatcher catcher;
+
+ std::string extension_id_1;
+ InitializePrinterProviderTestApp("api_test/printer_provider/request_printers",
+ "OK", &extension_id_1);
+ ASSERT_FALSE(extension_id_1.empty());
+
+ std::string extension_id_2;
+ InitializePrinterProviderTestApp(
+ "api_test/printer_provider/request_printers_second", "OK",
+ &extension_id_2);
+ ASSERT_FALSE(extension_id_2.empty());
+
+ base::RunLoop run_loop;
+ base::ListValue printers;
+
+ StartGetPrintersRequest(base::Bind(&AppendPrintersAndRunCallbackIfDone,
+ &printers, run_loop.QuitClosure()));
+
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ run_loop.Run();
+
+ std::vector<scoped_ptr<base::Value>> expected_printers;
+ expected_printers.push_back(
+ DictionaryBuilder()
+ .Set("description", "Test printer")
+ .Set("extensionId", extension_id_1)
+ .Set("extensionName", "Test printer provider")
+ .Set("id", base::StringPrintf("%s:printer1", extension_id_1.c_str()))
+ .Set("name", "Printer 1")
+ .Build());
+ expected_printers.push_back(
+ DictionaryBuilder()
+ .Set("extensionId", extension_id_1)
+ .Set("extensionName", "Test printer provider")
+ .Set("id",
+ base::StringPrintf("%s:printerNoDesc", extension_id_1.c_str()))
+ .Set("name", "Printer 2")
+ .Build());
+ expected_printers.push_back(
+ DictionaryBuilder()
+ .Set("description", "Test printer")
+ .Set("extensionId", extension_id_2)
+ .Set("extensionName", "Test printer provider")
+ .Set("id", base::StringPrintf("%s:printer1", extension_id_2.c_str()))
+ .Set("name", "Printer 1")
+ .Build());
+ expected_printers.push_back(
+ DictionaryBuilder()
+ .Set("extensionId", extension_id_2)
+ .Set("extensionName", "Test printer provider")
+ .Set("id",
+ base::StringPrintf("%s:printerNoDesc", extension_id_2.c_str()))
+ .Set("name", "Printer 2")
+ .Build());
+
+ ValidatePrinterListValue(printers, std::move(expected_printers));
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest,
+ GetPrintersTwoExtensionsBothUnloaded) {
+ ResultCatcher catcher;
+
+ std::string extension_id_1;
+ InitializePrinterProviderTestApp("api_test/printer_provider/request_printers",
+ "IGNORE_CALLBACK", &extension_id_1);
+ ASSERT_FALSE(extension_id_1.empty());
+
+ std::string extension_id_2;
+ InitializePrinterProviderTestApp(
+ "api_test/printer_provider/request_printers_second", "IGNORE_CALLBACK",
+ &extension_id_2);
+ ASSERT_FALSE(extension_id_2.empty());
+
+ base::RunLoop run_loop;
+ base::ListValue printers;
+
+ StartGetPrintersRequest(base::Bind(&AppendPrintersAndRunCallbackIfDone,
+ &printers, run_loop.QuitClosure()));
+
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ ASSERT_TRUE(SimulateExtensionUnload(extension_id_1));
+ ASSERT_TRUE(SimulateExtensionUnload(extension_id_2));
+
+ run_loop.Run();
+
+ EXPECT_TRUE(printers.empty());
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest,
+ GetPrintersTwoExtensionsOneFails) {
+ ResultCatcher catcher;
+
+ std::string extension_id_1;
+ InitializePrinterProviderTestApp("api_test/printer_provider/request_printers",
+ "NOT_ARRAY", &extension_id_1);
+ ASSERT_FALSE(extension_id_1.empty());
+
+ std::string extension_id_2;
+ InitializePrinterProviderTestApp(
+ "api_test/printer_provider/request_printers_second", "OK",
+ &extension_id_2);
+ ASSERT_FALSE(extension_id_2.empty());
+
+ base::RunLoop run_loop;
+ base::ListValue printers;
+
+ StartGetPrintersRequest(base::Bind(&AppendPrintersAndRunCallbackIfDone,
+ &printers, run_loop.QuitClosure()));
+
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ run_loop.Run();
+
+ std::vector<scoped_ptr<base::Value>> expected_printers;
+ expected_printers.push_back(
+ DictionaryBuilder()
+ .Set("description", "Test printer")
+ .Set("extensionId", extension_id_2)
+ .Set("extensionName", "Test printer provider")
+ .Set("id", base::StringPrintf("%s:printer1", extension_id_2.c_str()))
+ .Set("name", "Printer 1")
+ .Build());
+ expected_printers.push_back(
+ DictionaryBuilder()
+ .Set("extensionId", extension_id_2)
+ .Set("extensionName", "Test printer provider")
+ .Set("id",
+ base::StringPrintf("%s:printerNoDesc", extension_id_2.c_str()))
+ .Set("name", "Printer 2")
+ .Build());
+
+ ValidatePrinterListValue(printers, std::move(expected_printers));
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest,
+ GetPrintersTwoExtensionsOneWithNoListener) {
+ ResultCatcher catcher;
+
+ std::string extension_id_1;
+ InitializePrinterProviderTestApp("api_test/printer_provider/request_printers",
+ "NO_LISTENER", &extension_id_1);
+ ASSERT_FALSE(extension_id_1.empty());
+
+ std::string extension_id_2;
+ InitializePrinterProviderTestApp(
+ "api_test/printer_provider/request_printers_second", "OK",
+ &extension_id_2);
+ ASSERT_FALSE(extension_id_2.empty());
+
+ base::RunLoop run_loop;
+ base::ListValue printers;
+
+ StartGetPrintersRequest(base::Bind(&AppendPrintersAndRunCallbackIfDone,
+ &printers, run_loop.QuitClosure()));
+
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ run_loop.Run();
+
+ std::vector<scoped_ptr<base::Value>> expected_printers;
+ expected_printers.push_back(
+ DictionaryBuilder()
+ .Set("description", "Test printer")
+ .Set("extensionId", extension_id_2)
+ .Set("extensionName", "Test printer provider")
+ .Set("id", base::StringPrintf("%s:printer1", extension_id_2.c_str()))
+ .Set("name", "Printer 1")
+ .Build());
+ expected_printers.push_back(
+ DictionaryBuilder()
+ .Set("extensionId", extension_id_2)
+ .Set("extensionName", "Test printer provider")
+ .Set("id",
+ base::StringPrintf("%s:printerNoDesc", extension_id_2.c_str()))
+ .Set("name", "Printer 2")
+ .Build());
+
+ ValidatePrinterListValue(printers, std::move(expected_printers));
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetPrintersNoListener) {
+ ResultCatcher catcher;
+
+ std::string extension_id;
+ InitializePrinterProviderTestApp("api_test/printer_provider/request_printers",
+ "NO_LISTENER", &extension_id);
+ ASSERT_FALSE(extension_id.empty());
+
+ base::RunLoop run_loop;
+ base::ListValue printers;
+
+ StartGetPrintersRequest(base::Bind(&AppendPrintersAndRunCallbackIfDone,
+ &printers, run_loop.QuitClosure()));
+
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ run_loop.Run();
+
+ EXPECT_TRUE(printers.empty());
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetPrintersNotArray) {
+ ResultCatcher catcher;
+
+ std::string extension_id;
+ InitializePrinterProviderTestApp("api_test/printer_provider/request_printers",
+ "NOT_ARRAY", &extension_id);
+ ASSERT_FALSE(extension_id.empty());
+
+ base::RunLoop run_loop;
+ base::ListValue printers;
+
+ StartGetPrintersRequest(base::Bind(&AppendPrintersAndRunCallbackIfDone,
+ &printers, run_loop.QuitClosure()));
+
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ run_loop.Run();
+
+ EXPECT_TRUE(printers.empty());
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest,
+ GetPrintersInvalidPrinterValueType) {
+ ResultCatcher catcher;
+
+ std::string extension_id;
+ InitializePrinterProviderTestApp("api_test/printer_provider/request_printers",
+ "INVALID_PRINTER_TYPE", &extension_id);
+ ASSERT_FALSE(extension_id.empty());
+
+ base::RunLoop run_loop;
+ base::ListValue printers;
+
+ StartGetPrintersRequest(base::Bind(&AppendPrintersAndRunCallbackIfDone,
+ &printers, run_loop.QuitClosure()));
+
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ run_loop.Run();
+
+ EXPECT_TRUE(printers.empty());
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetPrintersInvalidPrinterValue) {
+ ResultCatcher catcher;
+
+ std::string extension_id;
+ InitializePrinterProviderTestApp("api_test/printer_provider/request_printers",
+ "INVALID_PRINTER", &extension_id);
+ ASSERT_FALSE(extension_id.empty());
+
+ base::RunLoop run_loop;
+ base::ListValue printers;
+
+ StartGetPrintersRequest(base::Bind(&AppendPrintersAndRunCallbackIfDone,
+ &printers, run_loop.QuitClosure()));
+
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ run_loop.Run();
+
+ EXPECT_TRUE(printers.empty());
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetUsbPrinterInfo) {
+ ResultCatcher catcher;
+ scoped_refptr<device::UsbDevice> device =
+ new device::MockUsbDevice(0, 0, "Google", "USB Printer", "");
+ usb_service_.AddDevice(device);
+
+ std::string extension_id;
+ InitializePrinterProviderTestApp("api_test/printer_provider/usb_printers",
+ "OK", &extension_id);
+ ASSERT_FALSE(extension_id.empty());
+
+ UsbGuidMap* guid_map = UsbGuidMap::Get(browser_context());
+ scoped_ptr<base::Value> expected_printer_info(
+ DictionaryBuilder()
+ .Set("description", "This printer is a USB device.")
+ .Set("extensionId", extension_id)
+ .Set("extensionName", "Test USB printer provider")
+ .Set("id",
+ base::StringPrintf("%s:usbDevice-%u", extension_id.c_str(),
+ guid_map->GetIdFromGuid(device->guid())))
+ .Set("name", "Test Printer")
+ .Build());
+ base::RunLoop run_loop;
+ StartGetUsbPrinterInfoRequest(
+ extension_id, device,
+ base::Bind(&ExpectValueAndRunCallback, expected_printer_info.get(),
+ run_loop.QuitClosure()));
+ run_loop.Run();
+
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetUsbPrinterInfoEmptyResponse) {
+ RunUsbPrinterInfoRequestTest("EMPTY_RESPONSE");
+}
+
+IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetUsbPrinterInfoNoListener) {
+ RunUsbPrinterInfoRequestTest("NO_LISTENER");
+}
+
+} // namespace
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/printer_provider/printer_provider_print_job.cc b/chromium/extensions/browser/api/printer_provider/printer_provider_print_job.cc
new file mode 100644
index 00000000000..a2838be137b
--- /dev/null
+++ b/chromium/extensions/browser/api/printer_provider/printer_provider_print_job.cc
@@ -0,0 +1,18 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/printer_provider/printer_provider_print_job.h"
+
+namespace extensions {
+
+PrinterProviderPrintJob::PrinterProviderPrintJob() {
+}
+
+PrinterProviderPrintJob::PrinterProviderPrintJob(
+ const PrinterProviderPrintJob& other) = default;
+
+PrinterProviderPrintJob::~PrinterProviderPrintJob() {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/printer_provider/printer_provider_print_job.h b/chromium/extensions/browser/api/printer_provider/printer_provider_print_job.h
new file mode 100644
index 00000000000..a9f4b4b5555
--- /dev/null
+++ b/chromium/extensions/browser/api/printer_provider/printer_provider_print_job.h
@@ -0,0 +1,59 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_PRINTER_PROVIDER_PRINTER_PROVIDER_PRINT_JOB_H_
+#define EXTENSIONS_BROWSER_API_PRINTER_PROVIDER_PRINTER_PROVIDER_PRINT_JOB_H_
+
+#include <string>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/strings/string16.h"
+
+namespace extensions {
+
+// Struct describing print job that should be forwarded to an extension via
+// chrome.printerProvider.onPrintRequested event.
+// TODO(tbarzic): This should probably be a class and have some methods, e.g.
+// whether the job is initialized and whether the data is described using a file
+// or bytes.
+struct PrinterProviderPrintJob {
+ PrinterProviderPrintJob();
+ PrinterProviderPrintJob(const PrinterProviderPrintJob& other);
+ ~PrinterProviderPrintJob();
+
+ // The id of the printer that should handle the print job. The id is
+ // formatted as <extension_id>:<printer_id>, where <extension_id> is the
+ // id of the extension that manages the printer, and <printer_id> is
+ // the the printer's id within the extension (as reported via
+ // chrome.printerProvider.onGetPrintersRequested event callback).
+ std::string printer_id;
+
+ // The print job title.
+ base::string16 job_title;
+
+ // The print job ticket.
+ std::string ticket_json;
+
+ // Content type of the document that should be printed.
+ std::string content_type;
+
+ // The document data that should be printed. Should be NULL if document data
+ // is kept in a file.
+ scoped_refptr<base::RefCountedMemory> document_bytes;
+
+ // Path of the file which contains data to be printed. Should be set only if
+ // |document_bytes| are NULL.
+ base::FilePath document_path;
+
+ // Information about the file which contains data to be printed. Should be
+ // set only if |document_path| is set.
+ base::File::Info file_info;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_PRINTER_PROVIDER_PRINTER_PROVIDER_PRINT_JOB_H_
diff --git a/chromium/extensions/browser/api/printer_provider_internal/OWNERS b/chromium/extensions/browser/api/printer_provider_internal/OWNERS
new file mode 100644
index 00000000000..0fa42c6a45c
--- /dev/null
+++ b/chromium/extensions/browser/api/printer_provider_internal/OWNERS
@@ -0,0 +1,2 @@
+tbarzic@chromium.org
+vitalybuka@chromium.org
diff --git a/chromium/extensions/browser/api/printer_provider_internal/printer_provider_internal_api.cc b/chromium/extensions/browser/api/printer_provider_internal/printer_provider_internal_api.cc
new file mode 100644
index 00000000000..e032703dfeb
--- /dev/null
+++ b/chromium/extensions/browser/api/printer_provider_internal/printer_provider_internal_api.cc
@@ -0,0 +1,270 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/printer_provider_internal/printer_provider_internal_api.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/guid.h"
+#include "base/lazy_instance.h"
+#include "base/location.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "content/public/browser/blob_handle.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/browser/api/printer_provider/printer_provider_api.h"
+#include "extensions/browser/api/printer_provider/printer_provider_api_factory.h"
+#include "extensions/browser/api/printer_provider/printer_provider_print_job.h"
+#include "extensions/browser/blob_holder.h"
+#include "extensions/common/api/printer_provider.h"
+#include "extensions/common/api/printer_provider_internal.h"
+
+namespace internal_api = extensions::api::printer_provider_internal;
+
+namespace extensions {
+
+namespace {
+
+static base::LazyInstance<
+ BrowserContextKeyedAPIFactory<PrinterProviderInternalAPI>> g_api_factory =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// static
+BrowserContextKeyedAPIFactory<PrinterProviderInternalAPI>*
+PrinterProviderInternalAPI::GetFactoryInstance() {
+ return g_api_factory.Pointer();
+}
+
+PrinterProviderInternalAPI::PrinterProviderInternalAPI(
+ content::BrowserContext* browser_context) {
+}
+
+PrinterProviderInternalAPI::~PrinterProviderInternalAPI() {
+}
+
+void PrinterProviderInternalAPI::AddObserver(
+ PrinterProviderInternalAPIObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void PrinterProviderInternalAPI::RemoveObserver(
+ PrinterProviderInternalAPIObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void PrinterProviderInternalAPI::NotifyGetPrintersResult(
+ const Extension* extension,
+ int request_id,
+ const PrinterProviderInternalAPIObserver::PrinterInfoVector& printers) {
+ FOR_EACH_OBSERVER(PrinterProviderInternalAPIObserver, observers_,
+ OnGetPrintersResult(extension, request_id, printers));
+}
+
+void PrinterProviderInternalAPI::NotifyGetCapabilityResult(
+ const Extension* extension,
+ int request_id,
+ const base::DictionaryValue& capability) {
+ FOR_EACH_OBSERVER(PrinterProviderInternalAPIObserver, observers_,
+ OnGetCapabilityResult(extension, request_id, capability));
+}
+
+void PrinterProviderInternalAPI::NotifyPrintResult(
+ const Extension* extension,
+ int request_id,
+ api::printer_provider_internal::PrintError error) {
+ FOR_EACH_OBSERVER(PrinterProviderInternalAPIObserver, observers_,
+ OnPrintResult(extension, request_id, error));
+}
+
+void PrinterProviderInternalAPI::NotifyGetUsbPrinterInfoResult(
+ const Extension* extension,
+ int request_id,
+ const api::printer_provider::PrinterInfo* printer_info) {
+ FOR_EACH_OBSERVER(
+ PrinterProviderInternalAPIObserver, observers_,
+ OnGetUsbPrinterInfoResult(extension, request_id, printer_info));
+}
+
+PrinterProviderInternalReportPrintResultFunction::
+ PrinterProviderInternalReportPrintResultFunction() {
+}
+
+PrinterProviderInternalReportPrintResultFunction::
+ ~PrinterProviderInternalReportPrintResultFunction() {
+}
+
+ExtensionFunction::ResponseAction
+PrinterProviderInternalReportPrintResultFunction::Run() {
+ scoped_ptr<internal_api::ReportPrintResult::Params> params(
+ internal_api::ReportPrintResult::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ PrinterProviderInternalAPI::GetFactoryInstance()
+ ->Get(browser_context())
+ ->NotifyPrintResult(extension(), params->request_id, params->error);
+ return RespondNow(NoArguments());
+}
+
+PrinterProviderInternalReportPrinterCapabilityFunction::
+ PrinterProviderInternalReportPrinterCapabilityFunction() {
+}
+
+PrinterProviderInternalReportPrinterCapabilityFunction::
+ ~PrinterProviderInternalReportPrinterCapabilityFunction() {
+}
+
+ExtensionFunction::ResponseAction
+PrinterProviderInternalReportPrinterCapabilityFunction::Run() {
+ scoped_ptr<internal_api::ReportPrinterCapability::Params> params(
+ internal_api::ReportPrinterCapability::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ if (params->capability) {
+ PrinterProviderInternalAPI::GetFactoryInstance()
+ ->Get(browser_context())
+ ->NotifyGetCapabilityResult(extension(), params->request_id,
+ params->capability->additional_properties);
+ } else {
+ PrinterProviderInternalAPI::GetFactoryInstance()
+ ->Get(browser_context())
+ ->NotifyGetCapabilityResult(extension(), params->request_id,
+ base::DictionaryValue());
+ }
+ return RespondNow(NoArguments());
+}
+
+PrinterProviderInternalReportPrintersFunction::
+ PrinterProviderInternalReportPrintersFunction() {
+}
+
+PrinterProviderInternalReportPrintersFunction::
+ ~PrinterProviderInternalReportPrintersFunction() {
+}
+
+ExtensionFunction::ResponseAction
+PrinterProviderInternalReportPrintersFunction::Run() {
+ scoped_ptr<internal_api::ReportPrinters::Params> params(
+ internal_api::ReportPrinters::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ base::ListValue printers;
+ if (params->printers) {
+ PrinterProviderInternalAPI::GetFactoryInstance()
+ ->Get(browser_context())
+ ->NotifyGetPrintersResult(extension(), params->request_id,
+ *params->printers);
+ } else {
+ PrinterProviderInternalAPI::GetFactoryInstance()
+ ->Get(browser_context())
+ ->NotifyGetPrintersResult(
+ extension(), params->request_id,
+ PrinterProviderInternalAPIObserver::PrinterInfoVector());
+ }
+ return RespondNow(NoArguments());
+}
+
+PrinterProviderInternalGetPrintDataFunction::
+ PrinterProviderInternalGetPrintDataFunction() {
+}
+
+PrinterProviderInternalGetPrintDataFunction::
+ ~PrinterProviderInternalGetPrintDataFunction() {
+}
+
+ExtensionFunction::ResponseAction
+PrinterProviderInternalGetPrintDataFunction::Run() {
+ scoped_ptr<internal_api::GetPrintData::Params> params(
+ internal_api::GetPrintData::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ const PrinterProviderPrintJob* job =
+ PrinterProviderAPIFactory::GetInstance()
+ ->GetForBrowserContext(browser_context())
+ ->GetPrintJob(extension(), params->request_id);
+ if (!job)
+ return RespondNow(Error("Print request not found."));
+
+ if (job->document_bytes.get()) {
+ // |job->document_bytes| are passed to the callback to make sure the ref
+ // counted memory does not go away before the memory backed blob is created.
+ content::BrowserContext::CreateMemoryBackedBlob(
+ browser_context(), job->document_bytes->front_as<char>(),
+ job->document_bytes->size(),
+ base::Bind(&PrinterProviderInternalGetPrintDataFunction::OnBlob, this,
+ job->content_type, job->document_bytes->size(),
+ job->document_bytes));
+ } else if (!job->document_path.empty()) {
+ content::BrowserContext::CreateFileBackedBlob(
+ browser_context(), job->document_path, 0 /* offset */,
+ job->file_info.size, job->file_info.last_modified,
+ base::Bind(&PrinterProviderInternalGetPrintDataFunction::OnBlob, this,
+ job->content_type, job->file_info.size,
+ scoped_refptr<base::RefCountedMemory>()));
+ } else {
+ return RespondNow(Error("Job data not set"));
+ }
+ return RespondLater();
+}
+
+void PrinterProviderInternalGetPrintDataFunction::OnBlob(
+ const std::string& type,
+ int size,
+ const scoped_refptr<base::RefCountedMemory>& data,
+ scoped_ptr<content::BlobHandle> blob) {
+ if (!blob) {
+ SetError("Unable to create the blob.");
+ SendResponse(false);
+ return;
+ }
+
+ internal_api::BlobInfo info;
+ info.blob_uuid = blob->GetUUID();
+ info.type = type;
+ info.size = size;
+
+ std::vector<std::string> uuids;
+ uuids.push_back(blob->GetUUID());
+
+ extensions::BlobHolder* holder =
+ extensions::BlobHolder::FromRenderProcessHost(
+ render_frame_host()->GetProcess());
+ holder->HoldBlobReference(std::move(blob));
+
+ results_ = internal_api::GetPrintData::Results::Create(info);
+ SetTransferredBlobUUIDs(uuids);
+ SendResponse(true);
+}
+
+PrinterProviderInternalReportUsbPrinterInfoFunction::
+ PrinterProviderInternalReportUsbPrinterInfoFunction() {
+}
+
+PrinterProviderInternalReportUsbPrinterInfoFunction::
+ ~PrinterProviderInternalReportUsbPrinterInfoFunction() {
+}
+
+ExtensionFunction::ResponseAction
+PrinterProviderInternalReportUsbPrinterInfoFunction::Run() {
+ scoped_ptr<internal_api::ReportUsbPrinterInfo::Params> params(
+ internal_api::ReportUsbPrinterInfo::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ PrinterProviderInternalAPI::GetFactoryInstance()
+ ->Get(browser_context())
+ ->NotifyGetUsbPrinterInfoResult(extension(), params->request_id,
+ params->printer_info.get());
+ return RespondNow(NoArguments());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/printer_provider_internal/printer_provider_internal_api.h b/chromium/extensions/browser/api/printer_provider_internal/printer_provider_internal_api.h
new file mode 100644
index 00000000000..472cf193f67
--- /dev/null
+++ b/chromium/extensions/browser/api/printer_provider_internal/printer_provider_internal_api.h
@@ -0,0 +1,182 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_PRINTER_PROVIDER_INTERNAL_PRINTER_PROVIDER_INTERNAL_API_H_
+#define EXTENSIONS_BROWSER_API_PRINTER_PROVIDER_INTERNAL_PRINTER_PROVIDER_INTERNAL_API_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "extensions/browser/api/printer_provider_internal/printer_provider_internal_api_observer.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/common/api/printer_provider_internal.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+class RefCountedMemory;
+}
+
+namespace content {
+class BlobHandle;
+class BrowserContext;
+}
+
+namespace extensions {
+class Extension;
+}
+
+namespace extensions {
+
+// Internal API instance. Primarily used to enable observers to watch for when
+// printerProviderInternal API functions are called.
+class PrinterProviderInternalAPI : public BrowserContextKeyedAPI {
+ public:
+ static BrowserContextKeyedAPIFactory<PrinterProviderInternalAPI>*
+ GetFactoryInstance();
+
+ explicit PrinterProviderInternalAPI(content::BrowserContext* browser_context);
+ ~PrinterProviderInternalAPI() override;
+
+ void AddObserver(PrinterProviderInternalAPIObserver* observer);
+ void RemoveObserver(PrinterProviderInternalAPIObserver* observer);
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<PrinterProviderInternalAPI>;
+ friend class PrinterProviderInternalReportPrintersFunction;
+ friend class PrinterProviderInternalReportPrinterCapabilityFunction;
+ friend class PrinterProviderInternalReportPrintResultFunction;
+ friend class PrinterProviderInternalReportUsbPrinterInfoFunction;
+
+ // BrowserContextKeyedAPI implementation.
+ static const bool kServiceRedirectedInIncognito = true;
+ static const char* service_name() { return "PrinterProviderInternal"; }
+
+ // Notifies observers that a printerProvider.onGetPrintersRequested callback
+ // has been called. Called from
+ // |PrinterProviderInternalReportPrintersFunction|.
+ void NotifyGetPrintersResult(
+ const Extension* extension,
+ int request_id,
+ const PrinterProviderInternalAPIObserver::PrinterInfoVector& printers);
+
+ // Notifies observers that a printerProvider.onGetCapabilityRequested callback
+ // has been called. Called from
+ // |PrinterProviderInternalReportPrinterCapabilityFunction|.
+ void NotifyGetCapabilityResult(const Extension* extension,
+ int request_id,
+ const base::DictionaryValue& capability);
+
+ // Notifies observers that a printerProvider.onPrintRequested callback has
+ // been called. Called from
+ // |PrinterProviderInternalReportPrintResultFunction|.
+ void NotifyPrintResult(const Extension* extension,
+ int request_id,
+ api::printer_provider_internal::PrintError error);
+
+ // Notifies observers that a printerProvider.onGetUsbPrinterInfoRequested
+ // callback has been called. Called from
+ // |PrinterProviderInternalReportUsbPrinterInfoFunction|.
+ void NotifyGetUsbPrinterInfoResult(
+ const Extension* extension,
+ int request_id,
+ const api::printer_provider::PrinterInfo* printer_info);
+
+ base::ObserverList<PrinterProviderInternalAPIObserver> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrinterProviderInternalAPI);
+};
+
+class PrinterProviderInternalReportPrintResultFunction
+ : public UIThreadExtensionFunction {
+ public:
+ PrinterProviderInternalReportPrintResultFunction();
+
+ protected:
+ ~PrinterProviderInternalReportPrintResultFunction() override;
+
+ ExtensionFunction::ResponseAction Run() override;
+
+ private:
+ DECLARE_EXTENSION_FUNCTION("printerProviderInternal.reportPrintResult",
+ PRINTERPROVIDERINTERNAL_REPORTPRINTRESULT)
+
+ DISALLOW_COPY_AND_ASSIGN(PrinterProviderInternalReportPrintResultFunction);
+};
+
+class PrinterProviderInternalReportPrinterCapabilityFunction
+ : public UIThreadExtensionFunction {
+ public:
+ PrinterProviderInternalReportPrinterCapabilityFunction();
+
+ protected:
+ ~PrinterProviderInternalReportPrinterCapabilityFunction() override;
+
+ ExtensionFunction::ResponseAction Run() override;
+
+ private:
+ DECLARE_EXTENSION_FUNCTION("printerProviderInternal.reportPrinterCapability",
+ PRINTERPROVIDERINTERNAL_REPORTPRINTERCAPABILITY)
+
+ DISALLOW_COPY_AND_ASSIGN(
+ PrinterProviderInternalReportPrinterCapabilityFunction);
+};
+
+class PrinterProviderInternalReportPrintersFunction
+ : public UIThreadExtensionFunction {
+ public:
+ PrinterProviderInternalReportPrintersFunction();
+
+ protected:
+ ~PrinterProviderInternalReportPrintersFunction() override;
+ ExtensionFunction::ResponseAction Run() override;
+
+ private:
+ DECLARE_EXTENSION_FUNCTION("printerProviderInternal.reportPrinters",
+ PRINTERPROVIDERINTERNAL_REPORTPRINTERS)
+
+ DISALLOW_COPY_AND_ASSIGN(PrinterProviderInternalReportPrintersFunction);
+};
+
+class PrinterProviderInternalGetPrintDataFunction
+ : public UIThreadExtensionFunction {
+ public:
+ PrinterProviderInternalGetPrintDataFunction();
+
+ protected:
+ ~PrinterProviderInternalGetPrintDataFunction() override;
+ ExtensionFunction::ResponseAction Run() override;
+
+ private:
+ void OnBlob(const std::string& type,
+ int size,
+ const scoped_refptr<base::RefCountedMemory>& data,
+ scoped_ptr<content::BlobHandle> blob);
+ DECLARE_EXTENSION_FUNCTION("printerProviderInternal.getPrintData",
+ PRINTERPROVIDERINTERNAL_GETPRINTDATA)
+
+ DISALLOW_COPY_AND_ASSIGN(PrinterProviderInternalGetPrintDataFunction);
+};
+
+class PrinterProviderInternalReportUsbPrinterInfoFunction
+ : public UIThreadExtensionFunction {
+ public:
+ PrinterProviderInternalReportUsbPrinterInfoFunction();
+
+ protected:
+ ~PrinterProviderInternalReportUsbPrinterInfoFunction() override;
+ ExtensionFunction::ResponseAction Run() override;
+
+ private:
+ DECLARE_EXTENSION_FUNCTION("printerProviderInternal.reportUsbPrinterInfo",
+ PRINTERPROVIDERINTERNAL_REPORTUSBPRINTERINFO)
+
+ DISALLOW_COPY_AND_ASSIGN(PrinterProviderInternalReportUsbPrinterInfoFunction);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_PRINTER_PROVIDER_INTERNAL_PRINTER_PROVIDER_INTERNAL_API_H_
diff --git a/chromium/extensions/browser/api/printer_provider_internal/printer_provider_internal_api_observer.h b/chromium/extensions/browser/api/printer_provider_internal/printer_provider_internal_api_observer.h
new file mode 100644
index 00000000000..260b7647fb2
--- /dev/null
+++ b/chromium/extensions/browser/api/printer_provider_internal/printer_provider_internal_api_observer.h
@@ -0,0 +1,71 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_PRINTER_PROVIDER_INTERNAL_PRINTER_PROVIDER_INTERNAL_API_OBSERVER_H_
+#define EXTENSIONS_BROWSER_API_PRINTER_PROVIDER_INTERNAL_PRINTER_PROVIDER_INTERNAL_API_OBSERVER_H_
+
+#include <vector>
+
+#include "extensions/common/api/printer_provider.h"
+#include "extensions/common/api/printer_provider_internal.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+}
+
+namespace extensions {
+
+class Extension;
+
+// Interface for observing chrome.printerProviderInternal API function calls.
+class PrinterProviderInternalAPIObserver {
+ public:
+ using PrinterInfoVector = std::vector<api::printer_provider::PrinterInfo>;
+
+ // Used by chrome.printerProviderInternal API to report
+ // chrome.printerProvider.onGetPrintersRequested result returned by the
+ // extension |extension|.
+ // |request_id| is the request id passed to the original
+ // chrome.printerProvider.onGetPrintersRequested event.
+ virtual void OnGetPrintersResult(const Extension* extension,
+ int request_id,
+ const PrinterInfoVector& result) = 0;
+
+ // Used by chrome.printerProviderInternal API to report
+ // chrome.printerProvider.onGetCapabilityRequested result returned by the
+ // extension |extensiod|.
+ // |request_id| is the request id passed to the original
+ // chrome.printerProvider.onGetCapabilityRequested event.
+ virtual void OnGetCapabilityResult(const Extension* extension,
+ int request_id,
+ const base::DictionaryValue& result) = 0;
+
+ // Used by chrome.printerProviderInternal API to report
+ // chrome.printerProvider.onPrintRequested result returned by the extension
+ // |extension|.
+ // |request_id| is the request id passed to the original
+ // chrome.printerProvider.onPrintRequested event.
+ virtual void OnPrintResult(
+ const Extension* extension,
+ int request_id,
+ api::printer_provider_internal::PrintError error) = 0;
+
+ // Used by chrome.printerProviderInternal API to report
+ // chrome.printerProvider.onGetUsbPrinterInfoRequested result returned by the
+ // extension |extension|.
+ // |request_id| is the request id passed to the original
+ // chrome.printerProvider.onGetUsbPrinterInfoRequested event.
+ virtual void OnGetUsbPrinterInfoResult(
+ const Extension* extension,
+ int request_id,
+ const api::printer_provider::PrinterInfo* printer_info) = 0;
+
+ protected:
+ virtual ~PrinterProviderInternalAPIObserver() {}
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_PRINTER_PROVIDER_INTERNAL_PRINTER_PROVIDER_INTERNAL_API_OBSERVER_H_
diff --git a/chromium/extensions/browser/api/runtime/runtime_api.cc b/chromium/extensions/browser/api/runtime/runtime_api.cc
new file mode 100644
index 00000000000..a42e4662ba7
--- /dev/null
+++ b/chromium/extensions/browser/api/runtime/runtime_api.cc
@@ -0,0 +1,586 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/runtime/runtime_api.h"
+
+#include <utility>
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/child_process_security_policy.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "extensions/browser/api/runtime/runtime_api_delegate.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_host.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/extension_util.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/lazy_background_task_queue.h"
+#include "extensions/browser/notification_types.h"
+#include "extensions/browser/process_manager_factory.h"
+#include "extensions/common/api/runtime.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handlers/background_info.h"
+#include "extensions/common/manifest_handlers/shared_module_info.h"
+#include "storage/browser/fileapi/isolated_context.h"
+#include "url/gurl.h"
+
+using content::BrowserContext;
+
+namespace extensions {
+
+namespace runtime = api::runtime;
+
+namespace {
+
+const char kNoBackgroundPageError[] = "You do not have a background page.";
+const char kPageLoadError[] = "Background page failed to load.";
+const char kFailedToCreateOptionsPage[] = "Could not create an options page.";
+const char kInstallId[] = "id";
+const char kInstallReason[] = "reason";
+const char kInstallReasonChromeUpdate[] = "chrome_update";
+const char kInstallReasonUpdate[] = "update";
+const char kInstallReasonInstall[] = "install";
+const char kInstallReasonSharedModuleUpdate[] = "shared_module_update";
+const char kInstallPreviousVersion[] = "previousVersion";
+const char kInvalidUrlError[] = "Invalid URL: \"*\".";
+const char kPlatformInfoUnavailable[] = "Platform information unavailable.";
+
+const char kUpdatesDisabledError[] = "Autoupdate is not enabled.";
+
+// A preference key storing the url loaded when an extension is uninstalled.
+const char kUninstallUrl[] = "uninstall_url";
+
+// A preference key storing the information about an extension that was
+// installed but not loaded. We keep the pending info here so that we can send
+// chrome.runtime.onInstalled event during the extension load.
+const char kPrefPendingOnInstalledEventDispatchInfo[] =
+ "pending_on_installed_event_dispatch_info";
+
+// Previously installed version number.
+const char kPrefPreviousVersion[] = "previous_version";
+
+// The name of the directory to be returned by getPackageDirectoryEntry. This
+// particular value does not matter to user code, but is chosen for consistency
+// with the equivalent Pepper API.
+const char kPackageDirectoryPath[] = "crxfs";
+
+void DispatchOnStartupEventImpl(BrowserContext* browser_context,
+ const std::string& extension_id,
+ bool first_call,
+ ExtensionHost* host) {
+ // A NULL host from the LazyBackgroundTaskQueue means the page failed to
+ // load. Give up.
+ if (!host && !first_call)
+ return;
+
+ // Don't send onStartup events to incognito browser contexts.
+ if (browser_context->IsOffTheRecord())
+ return;
+
+ if (ExtensionsBrowserClient::Get()->IsShuttingDown() ||
+ !ExtensionsBrowserClient::Get()->IsValidContext(browser_context))
+ return;
+ ExtensionSystem* system = ExtensionSystem::Get(browser_context);
+ if (!system)
+ return;
+
+ // If this is a persistent background page, we want to wait for it to load
+ // (it might not be ready, since this is startup). But only enqueue once.
+ // If it fails to load the first time, don't bother trying again.
+ const Extension* extension =
+ ExtensionRegistry::Get(browser_context)->enabled_extensions().GetByID(
+ extension_id);
+ if (extension && BackgroundInfo::HasPersistentBackgroundPage(extension) &&
+ first_call &&
+ LazyBackgroundTaskQueue::Get(browser_context)
+ ->ShouldEnqueueTask(browser_context, extension)) {
+ LazyBackgroundTaskQueue::Get(browser_context)
+ ->AddPendingTask(browser_context, extension_id,
+ base::Bind(&DispatchOnStartupEventImpl,
+ browser_context, extension_id, false));
+ return;
+ }
+
+ scoped_ptr<base::ListValue> event_args(new base::ListValue());
+ scoped_ptr<Event> event(new Event(events::RUNTIME_ON_STARTUP,
+ runtime::OnStartup::kEventName,
+ std::move(event_args)));
+ EventRouter::Get(browser_context)
+ ->DispatchEventToExtension(extension_id, std::move(event));
+}
+
+void SetUninstallURL(ExtensionPrefs* prefs,
+ const std::string& extension_id,
+ const std::string& url_string) {
+ prefs->UpdateExtensionPref(
+ extension_id, kUninstallUrl, new base::StringValue(url_string));
+}
+
+std::string GetUninstallURL(ExtensionPrefs* prefs,
+ const std::string& extension_id) {
+ std::string url_string;
+ prefs->ReadPrefAsString(extension_id, kUninstallUrl, &url_string);
+ return url_string;
+}
+
+} // namespace
+
+///////////////////////////////////////////////////////////////////////////////
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<RuntimeAPI> >
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<RuntimeAPI>* RuntimeAPI::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+template <>
+void BrowserContextKeyedAPIFactory<RuntimeAPI>::DeclareFactoryDependencies() {
+ DependsOn(ProcessManagerFactory::GetInstance());
+}
+
+RuntimeAPI::RuntimeAPI(content::BrowserContext* context)
+ : browser_context_(context),
+ dispatch_chrome_updated_event_(false),
+ extension_registry_observer_(this),
+ process_manager_observer_(this) {
+ // RuntimeAPI is redirected in incognito, so |browser_context_| is never
+ // incognito.
+ DCHECK(!browser_context_->IsOffTheRecord());
+
+ registrar_.Add(this,
+ extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
+ content::Source<BrowserContext>(context));
+ extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
+ process_manager_observer_.Add(ProcessManager::Get(browser_context_));
+
+ delegate_ = ExtensionsBrowserClient::Get()->CreateRuntimeAPIDelegate(
+ browser_context_);
+
+ // Check if registered events are up-to-date. We can only do this once
+ // per browser context, since it updates internal state when called.
+ dispatch_chrome_updated_event_ =
+ ExtensionsBrowserClient::Get()->DidVersionUpdate(browser_context_);
+}
+
+RuntimeAPI::~RuntimeAPI() {
+}
+
+void RuntimeAPI::Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ DCHECK_EQ(extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED, type);
+ // We're done restarting Chrome after an update.
+ dispatch_chrome_updated_event_ = false;
+
+ delegate_->AddUpdateObserver(this);
+}
+
+void RuntimeAPI::OnExtensionLoaded(content::BrowserContext* browser_context,
+ const Extension* extension) {
+ base::Version previous_version;
+ if (ReadPendingOnInstallInfoFromPref(extension->id(), &previous_version)) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&RuntimeEventRouter::DispatchOnInstalledEvent,
+ browser_context_, extension->id(), previous_version, false));
+ RemovePendingOnInstallInfoFromPref(extension->id());
+ }
+
+ if (!dispatch_chrome_updated_event_)
+ return;
+
+ // Dispatch the onInstalled event with reason "chrome_update".
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&RuntimeEventRouter::DispatchOnInstalledEvent,
+ browser_context_,
+ extension->id(),
+ Version(),
+ true));
+}
+
+void RuntimeAPI::OnExtensionWillBeInstalled(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ bool is_update,
+ const std::string& old_name) {
+ // This extension might be disabled before it has a chance to load, e.g. if
+ // the extension increased its permissions. So instead of trying to send the
+ // onInstalled event here, we remember the fact in prefs and fire the event
+ // when the extension is actually loaded.
+ StorePendingOnInstallInfoToPref(extension);
+}
+
+void RuntimeAPI::OnExtensionUninstalled(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UninstallReason reason) {
+ RemovePendingOnInstallInfoFromPref(extension->id());
+
+ RuntimeEventRouter::OnExtensionUninstalled(
+ browser_context_, extension->id(), reason);
+}
+
+void RuntimeAPI::Shutdown() {
+ delegate_->RemoveUpdateObserver(this);
+}
+
+void RuntimeAPI::OnAppUpdateAvailable(const Extension* extension) {
+ RuntimeEventRouter::DispatchOnUpdateAvailableEvent(
+ browser_context_, extension->id(), extension->manifest()->value());
+}
+
+void RuntimeAPI::OnChromeUpdateAvailable() {
+ RuntimeEventRouter::DispatchOnBrowserUpdateAvailableEvent(browser_context_);
+}
+
+void RuntimeAPI::OnBackgroundHostStartup(const Extension* extension) {
+ RuntimeEventRouter::DispatchOnStartupEvent(browser_context_, extension->id());
+}
+
+bool RuntimeAPI::ReadPendingOnInstallInfoFromPref(
+ const ExtensionId& extension_id,
+ base::Version* previous_version) {
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context_);
+ DCHECK(prefs);
+
+ const base::DictionaryValue* info = nullptr;
+ if (!prefs->ReadPrefAsDictionary(
+ extension_id, kPrefPendingOnInstalledEventDispatchInfo, &info)) {
+ return false;
+ }
+
+ std::string previous_version_string;
+ info->GetString(kPrefPreviousVersion, &previous_version_string);
+ // |previous_version_string| can be empty.
+ *previous_version = base::Version(previous_version_string);
+ return true;
+}
+
+void RuntimeAPI::RemovePendingOnInstallInfoFromPref(
+ const ExtensionId& extension_id) {
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context_);
+ DCHECK(prefs);
+
+ prefs->UpdateExtensionPref(extension_id,
+ kPrefPendingOnInstalledEventDispatchInfo, nullptr);
+}
+
+void RuntimeAPI::StorePendingOnInstallInfoToPref(const Extension* extension) {
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context_);
+ DCHECK(prefs);
+
+ // |pending_on_install_info| currently only contains a version string. Instead
+ // of making the pref hold a plain string, we store it as a dictionary value
+ // so that we can add more stuff to it in the future if necessary.
+ scoped_ptr<base::DictionaryValue> pending_on_install_info(
+ new base::DictionaryValue());
+ base::Version previous_version =
+ delegate_->GetPreviousExtensionVersion(extension);
+ pending_on_install_info->SetString(
+ kPrefPreviousVersion,
+ previous_version.IsValid() ? previous_version.GetString() : "");
+ prefs->UpdateExtensionPref(extension->id(),
+ kPrefPendingOnInstalledEventDispatchInfo,
+ pending_on_install_info.release());
+}
+
+void RuntimeAPI::ReloadExtension(const std::string& extension_id) {
+ delegate_->ReloadExtension(extension_id);
+}
+
+bool RuntimeAPI::CheckForUpdates(
+ const std::string& extension_id,
+ const RuntimeAPIDelegate::UpdateCheckCallback& callback) {
+ return delegate_->CheckForUpdates(extension_id, callback);
+}
+
+void RuntimeAPI::OpenURL(const GURL& update_url) {
+ delegate_->OpenURL(update_url);
+}
+
+bool RuntimeAPI::GetPlatformInfo(runtime::PlatformInfo* info) {
+ return delegate_->GetPlatformInfo(info);
+}
+
+bool RuntimeAPI::RestartDevice(std::string* error_message) {
+ return delegate_->RestartDevice(error_message);
+}
+
+bool RuntimeAPI::OpenOptionsPage(const Extension* extension) {
+ return delegate_->OpenOptionsPage(extension);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// static
+void RuntimeEventRouter::DispatchOnStartupEvent(
+ content::BrowserContext* context,
+ const std::string& extension_id) {
+ DispatchOnStartupEventImpl(context, extension_id, true, NULL);
+}
+
+// static
+void RuntimeEventRouter::DispatchOnInstalledEvent(
+ content::BrowserContext* context,
+ const std::string& extension_id,
+ const Version& old_version,
+ bool chrome_updated) {
+ if (!ExtensionsBrowserClient::Get()->IsValidContext(context))
+ return;
+ ExtensionSystem* system = ExtensionSystem::Get(context);
+ if (!system)
+ return;
+
+ scoped_ptr<base::ListValue> event_args(new base::ListValue());
+ base::DictionaryValue* info = new base::DictionaryValue();
+ event_args->Append(info);
+ if (old_version.IsValid()) {
+ info->SetString(kInstallReason, kInstallReasonUpdate);
+ info->SetString(kInstallPreviousVersion, old_version.GetString());
+ } else if (chrome_updated) {
+ info->SetString(kInstallReason, kInstallReasonChromeUpdate);
+ } else {
+ info->SetString(kInstallReason, kInstallReasonInstall);
+ }
+ EventRouter* event_router = EventRouter::Get(context);
+ DCHECK(event_router);
+ scoped_ptr<Event> event(new Event(events::RUNTIME_ON_INSTALLED,
+ runtime::OnInstalled::kEventName,
+ std::move(event_args)));
+ event_router->DispatchEventWithLazyListener(extension_id, std::move(event));
+
+ if (old_version.IsValid()) {
+ const Extension* extension =
+ ExtensionRegistry::Get(context)->enabled_extensions().GetByID(
+ extension_id);
+ if (extension && SharedModuleInfo::IsSharedModule(extension)) {
+ scoped_ptr<ExtensionSet> dependents =
+ system->GetDependentExtensions(extension);
+ for (ExtensionSet::const_iterator i = dependents->begin();
+ i != dependents->end();
+ i++) {
+ scoped_ptr<base::ListValue> sm_event_args(new base::ListValue());
+ base::DictionaryValue* sm_info = new base::DictionaryValue();
+ sm_event_args->Append(sm_info);
+ sm_info->SetString(kInstallReason, kInstallReasonSharedModuleUpdate);
+ sm_info->SetString(kInstallPreviousVersion, old_version.GetString());
+ sm_info->SetString(kInstallId, extension_id);
+ scoped_ptr<Event> sm_event(new Event(events::RUNTIME_ON_INSTALLED,
+ runtime::OnInstalled::kEventName,
+ std::move(sm_event_args)));
+ event_router->DispatchEventWithLazyListener((*i)->id(),
+ std::move(sm_event));
+ }
+ }
+ }
+}
+
+// static
+void RuntimeEventRouter::DispatchOnUpdateAvailableEvent(
+ content::BrowserContext* context,
+ const std::string& extension_id,
+ const base::DictionaryValue* manifest) {
+ ExtensionSystem* system = ExtensionSystem::Get(context);
+ if (!system)
+ return;
+
+ scoped_ptr<base::ListValue> args(new base::ListValue);
+ args->Append(manifest->DeepCopy());
+ EventRouter* event_router = EventRouter::Get(context);
+ DCHECK(event_router);
+ scoped_ptr<Event> event(new Event(events::RUNTIME_ON_UPDATE_AVAILABLE,
+ runtime::OnUpdateAvailable::kEventName,
+ std::move(args)));
+ event_router->DispatchEventToExtension(extension_id, std::move(event));
+}
+
+// static
+void RuntimeEventRouter::DispatchOnBrowserUpdateAvailableEvent(
+ content::BrowserContext* context) {
+ ExtensionSystem* system = ExtensionSystem::Get(context);
+ if (!system)
+ return;
+
+ scoped_ptr<base::ListValue> args(new base::ListValue);
+ EventRouter* event_router = EventRouter::Get(context);
+ DCHECK(event_router);
+ scoped_ptr<Event> event(new Event(
+ events::RUNTIME_ON_BROWSER_UPDATE_AVAILABLE,
+ runtime::OnBrowserUpdateAvailable::kEventName, std::move(args)));
+ event_router->BroadcastEvent(std::move(event));
+}
+
+// static
+void RuntimeEventRouter::DispatchOnRestartRequiredEvent(
+ content::BrowserContext* context,
+ const std::string& app_id,
+ api::runtime::OnRestartRequiredReason reason) {
+ ExtensionSystem* system = ExtensionSystem::Get(context);
+ if (!system)
+ return;
+
+ scoped_ptr<Event> event(
+ new Event(events::RUNTIME_ON_RESTART_REQUIRED,
+ runtime::OnRestartRequired::kEventName,
+ api::runtime::OnRestartRequired::Create(reason)));
+ EventRouter* event_router = EventRouter::Get(context);
+ DCHECK(event_router);
+ event_router->DispatchEventToExtension(app_id, std::move(event));
+}
+
+// static
+void RuntimeEventRouter::OnExtensionUninstalled(
+ content::BrowserContext* context,
+ const std::string& extension_id,
+ UninstallReason reason) {
+ if (!(reason == UNINSTALL_REASON_USER_INITIATED ||
+ reason == UNINSTALL_REASON_MANAGEMENT_API)) {
+ return;
+ }
+
+ GURL uninstall_url(
+ GetUninstallURL(ExtensionPrefs::Get(context), extension_id));
+
+ if (!uninstall_url.SchemeIsHTTPOrHTTPS()) {
+ // Previous versions of Chrome allowed non-http(s) URLs to be stored in the
+ // prefs. Now they're disallowed, but the old data may still exist.
+ return;
+ }
+
+ RuntimeAPI::GetFactoryInstance()->Get(context)->OpenURL(uninstall_url);
+}
+
+ExtensionFunction::ResponseAction RuntimeGetBackgroundPageFunction::Run() {
+ ExtensionHost* host = ProcessManager::Get(browser_context())
+ ->GetBackgroundHostForExtension(extension_id());
+ if (LazyBackgroundTaskQueue::Get(browser_context())
+ ->ShouldEnqueueTask(browser_context(), extension())) {
+ LazyBackgroundTaskQueue::Get(browser_context())
+ ->AddPendingTask(
+ browser_context(), extension_id(),
+ base::Bind(&RuntimeGetBackgroundPageFunction::OnPageLoaded, this));
+ } else if (host) {
+ OnPageLoaded(host);
+ } else {
+ return RespondNow(Error(kNoBackgroundPageError));
+ }
+
+ return RespondLater();
+}
+
+void RuntimeGetBackgroundPageFunction::OnPageLoaded(ExtensionHost* host) {
+ if (host) {
+ Respond(NoArguments());
+ } else {
+ Respond(Error(kPageLoadError));
+ }
+}
+
+ExtensionFunction::ResponseAction RuntimeOpenOptionsPageFunction::Run() {
+ RuntimeAPI* api = RuntimeAPI::GetFactoryInstance()->Get(browser_context());
+ return RespondNow(api->OpenOptionsPage(extension())
+ ? NoArguments()
+ : Error(kFailedToCreateOptionsPage));
+}
+
+ExtensionFunction::ResponseAction RuntimeSetUninstallURLFunction::Run() {
+ std::string url_string;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &url_string));
+
+ if (!url_string.empty() && !GURL(url_string).SchemeIsHTTPOrHTTPS()) {
+ return RespondNow(Error(kInvalidUrlError, url_string));
+ }
+ SetUninstallURL(
+ ExtensionPrefs::Get(browser_context()), extension_id(), url_string);
+ return RespondNow(NoArguments());
+}
+
+ExtensionFunction::ResponseAction RuntimeReloadFunction::Run() {
+ RuntimeAPI::GetFactoryInstance()->Get(browser_context())->ReloadExtension(
+ extension_id());
+ return RespondNow(NoArguments());
+}
+
+ExtensionFunction::ResponseAction RuntimeRequestUpdateCheckFunction::Run() {
+ if (!RuntimeAPI::GetFactoryInstance()
+ ->Get(browser_context())
+ ->CheckForUpdates(
+ extension_id(),
+ base::Bind(&RuntimeRequestUpdateCheckFunction::CheckComplete,
+ this))) {
+ return RespondNow(Error(kUpdatesDisabledError));
+ }
+ return RespondLater();
+}
+
+void RuntimeRequestUpdateCheckFunction::CheckComplete(
+ const RuntimeAPIDelegate::UpdateCheckResult& result) {
+ if (result.success) {
+ base::DictionaryValue* details = new base::DictionaryValue;
+ details->SetString("version", result.version);
+ Respond(TwoArguments(new base::StringValue(result.response), details));
+ } else {
+ // HMM(kalman): Why does !success not imply Error()?
+ Respond(OneArgument(new base::StringValue(result.response)));
+ }
+}
+
+ExtensionFunction::ResponseAction RuntimeRestartFunction::Run() {
+ std::string message;
+ bool result =
+ RuntimeAPI::GetFactoryInstance()->Get(browser_context())->RestartDevice(
+ &message);
+ if (!result) {
+ return RespondNow(Error(message));
+ }
+ return RespondNow(NoArguments());
+}
+
+ExtensionFunction::ResponseAction RuntimeGetPlatformInfoFunction::Run() {
+ runtime::PlatformInfo info;
+ if (!RuntimeAPI::GetFactoryInstance()
+ ->Get(browser_context())
+ ->GetPlatformInfo(&info)) {
+ return RespondNow(Error(kPlatformInfoUnavailable));
+ }
+ return RespondNow(
+ ArgumentList(runtime::GetPlatformInfo::Results::Create(info)));
+}
+
+ExtensionFunction::ResponseAction
+RuntimeGetPackageDirectoryEntryFunction::Run() {
+ storage::IsolatedContext* isolated_context =
+ storage::IsolatedContext::GetInstance();
+ DCHECK(isolated_context);
+
+ std::string relative_path = kPackageDirectoryPath;
+ base::FilePath path = extension_->path();
+ std::string filesystem_id = isolated_context->RegisterFileSystemForPath(
+ storage::kFileSystemTypeNativeLocal, std::string(), path, &relative_path);
+
+ int renderer_id = render_frame_host()->GetProcess()->GetID();
+ content::ChildProcessSecurityPolicy* policy =
+ content::ChildProcessSecurityPolicy::GetInstance();
+ policy->GrantReadFileSystem(renderer_id, filesystem_id);
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("fileSystemId", filesystem_id);
+ dict->SetString("baseName", relative_path);
+ return RespondNow(OneArgument(dict));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/runtime/runtime_api.h b/chromium/extensions/browser/api/runtime/runtime_api.h
new file mode 100644
index 00000000000..5f6fbe5a2d3
--- /dev/null
+++ b/chromium/extensions/browser/api/runtime/runtime_api.h
@@ -0,0 +1,246 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_RUNTIME_RUNTIME_API_H_
+#define EXTENSIONS_BROWSER_API_RUNTIME_RUNTIME_API_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/scoped_observer.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "extensions/browser/api/runtime/runtime_api_delegate.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/browser/process_manager_observer.h"
+#include "extensions/browser/update_observer.h"
+#include "extensions/common/api/runtime.h"
+
+namespace base {
+class Version;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+namespace api {
+namespace runtime {
+struct PlatformInfo;
+}
+}
+
+class Extension;
+class ExtensionHost;
+class ExtensionRegistry;
+
+// Runtime API dispatches onStartup, onInstalled, and similar events to
+// extensions. There is one instance shared between a browser context and
+// its related incognito instance.
+class RuntimeAPI : public BrowserContextKeyedAPI,
+ public content::NotificationObserver,
+ public ExtensionRegistryObserver,
+ public UpdateObserver,
+ public ProcessManagerObserver {
+ public:
+ static BrowserContextKeyedAPIFactory<RuntimeAPI>* GetFactoryInstance();
+
+ explicit RuntimeAPI(content::BrowserContext* context);
+ ~RuntimeAPI() override;
+
+ // content::NotificationObserver overrides:
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override;
+
+ void ReloadExtension(const std::string& extension_id);
+ bool CheckForUpdates(const std::string& extension_id,
+ const RuntimeAPIDelegate::UpdateCheckCallback& callback);
+ void OpenURL(const GURL& uninstall_url);
+ bool GetPlatformInfo(api::runtime::PlatformInfo* info);
+ bool RestartDevice(std::string* error_message);
+ bool OpenOptionsPage(const Extension* extension);
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<RuntimeAPI>;
+
+ // ExtensionRegistryObserver implementation.
+ void OnExtensionLoaded(content::BrowserContext* browser_context,
+ const Extension* extension) override;
+ void OnExtensionWillBeInstalled(content::BrowserContext* browser_context,
+ const Extension* extension,
+ bool is_update,
+ const std::string& old_name) override;
+ void OnExtensionUninstalled(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UninstallReason reason) override;
+
+ // BrowserContextKeyedAPI implementation:
+ static const char* service_name() { return "RuntimeAPI"; }
+ static const bool kServiceRedirectedInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+ void Shutdown() override;
+
+ // extensions::UpdateObserver overrides:
+ void OnAppUpdateAvailable(const Extension* extension) override;
+ void OnChromeUpdateAvailable() override;
+
+ // ProcessManagerObserver implementation:
+ void OnBackgroundHostStartup(const Extension* extension) override;
+
+ // Pref related functions that deals with info about installed extensions that
+ // has not been loaded yet.
+ // Used to send chrome.runtime.onInstalled event upon loading the extensions.
+ bool ReadPendingOnInstallInfoFromPref(const ExtensionId& extension_id,
+ base::Version* previous_version);
+ void RemovePendingOnInstallInfoFromPref(const ExtensionId& extension_id);
+ void StorePendingOnInstallInfoToPref(const Extension* extension);
+
+ scoped_ptr<RuntimeAPIDelegate> delegate_;
+
+ content::BrowserContext* browser_context_;
+
+ // True if we should dispatch the chrome.runtime.onInstalled event with
+ // reason "chrome_update" upon loading each extension.
+ bool dispatch_chrome_updated_event_;
+
+ content::NotificationRegistrar registrar_;
+
+ // Listen to extension notifications.
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+ ScopedObserver<ProcessManager, ProcessManagerObserver>
+ process_manager_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(RuntimeAPI);
+};
+
+template <>
+void BrowserContextKeyedAPIFactory<RuntimeAPI>::DeclareFactoryDependencies();
+
+class RuntimeEventRouter {
+ public:
+ // Dispatches the onStartup event to all currently-loaded extensions.
+ static void DispatchOnStartupEvent(content::BrowserContext* context,
+ const std::string& extension_id);
+
+ // Dispatches the onInstalled event to the given extension.
+ static void DispatchOnInstalledEvent(content::BrowserContext* context,
+ const std::string& extension_id,
+ const base::Version& old_version,
+ bool chrome_updated);
+
+ // Dispatches the onUpdateAvailable event to the given extension.
+ static void DispatchOnUpdateAvailableEvent(
+ content::BrowserContext* context,
+ const std::string& extension_id,
+ const base::DictionaryValue* manifest);
+
+ // Dispatches the onBrowserUpdateAvailable event to all extensions.
+ static void DispatchOnBrowserUpdateAvailableEvent(
+ content::BrowserContext* context);
+
+ // Dispatches the onRestartRequired event to the given app.
+ static void DispatchOnRestartRequiredEvent(
+ content::BrowserContext* context,
+ const std::string& app_id,
+ api::runtime::OnRestartRequiredReason reason);
+
+ // Does any work needed at extension uninstall (e.g. load uninstall url).
+ static void OnExtensionUninstalled(content::BrowserContext* context,
+ const std::string& extension_id,
+ UninstallReason reason);
+};
+
+class RuntimeGetBackgroundPageFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("runtime.getBackgroundPage",
+ RUNTIME_GETBACKGROUNDPAGE)
+
+ protected:
+ ~RuntimeGetBackgroundPageFunction() override {}
+ ResponseAction Run() override;
+
+ private:
+ void OnPageLoaded(ExtensionHost*);
+};
+
+class RuntimeOpenOptionsPageFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("runtime.openOptionsPage", RUNTIME_OPENOPTIONSPAGE)
+
+ protected:
+ ~RuntimeOpenOptionsPageFunction() override {}
+ ResponseAction Run() override;
+};
+
+class RuntimeSetUninstallURLFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("runtime.setUninstallURL", RUNTIME_SETUNINSTALLURL)
+
+ protected:
+ ~RuntimeSetUninstallURLFunction() override {}
+ ResponseAction Run() override;
+};
+
+class RuntimeReloadFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("runtime.reload", RUNTIME_RELOAD)
+
+ protected:
+ ~RuntimeReloadFunction() override {}
+ ResponseAction Run() override;
+};
+
+class RuntimeRequestUpdateCheckFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("runtime.requestUpdateCheck",
+ RUNTIME_REQUESTUPDATECHECK)
+
+ protected:
+ ~RuntimeRequestUpdateCheckFunction() override {}
+ ResponseAction Run() override;
+
+ private:
+ void CheckComplete(const RuntimeAPIDelegate::UpdateCheckResult& result);
+};
+
+class RuntimeRestartFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("runtime.restart", RUNTIME_RESTART)
+
+ protected:
+ ~RuntimeRestartFunction() override {}
+ ResponseAction Run() override;
+};
+
+class RuntimeGetPlatformInfoFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("runtime.getPlatformInfo",
+ RUNTIME_GETPLATFORMINFO);
+
+ protected:
+ ~RuntimeGetPlatformInfoFunction() override {}
+ ResponseAction Run() override;
+};
+
+class RuntimeGetPackageDirectoryEntryFunction
+ : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("runtime.getPackageDirectoryEntry",
+ RUNTIME_GETPACKAGEDIRECTORYENTRY)
+
+ protected:
+ ~RuntimeGetPackageDirectoryEntryFunction() override {}
+ ResponseAction Run() override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_RUNTIME_RUNTIME_API_H_
diff --git a/chromium/extensions/browser/api/runtime/runtime_api_delegate.cc b/chromium/extensions/browser/api/runtime/runtime_api_delegate.cc
new file mode 100644
index 00000000000..b012c74b5b0
--- /dev/null
+++ b/chromium/extensions/browser/api/runtime/runtime_api_delegate.cc
@@ -0,0 +1,20 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/runtime/runtime_api_delegate.h"
+
+namespace extensions {
+
+RuntimeAPIDelegate::UpdateCheckResult::UpdateCheckResult(
+ bool success,
+ const std::string& response,
+ const std::string& version)
+ : success(success), response(response), version(version) {
+}
+
+bool RuntimeAPIDelegate::OpenOptionsPage(const Extension* extension) {
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/runtime/runtime_api_delegate.h b/chromium/extensions/browser/api/runtime/runtime_api_delegate.h
new file mode 100644
index 00000000000..21e9a6e271c
--- /dev/null
+++ b/chromium/extensions/browser/api/runtime/runtime_api_delegate.h
@@ -0,0 +1,82 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_RUNTIME_RUNTIME_API_DELEGATE_H
+#define EXTENSIONS_BROWSER_API_RUNTIME_RUNTIME_API_DELEGATE_H
+
+#include "base/callback.h"
+#include "base/version.h"
+
+class GURL;
+
+namespace extensions {
+
+namespace api {
+namespace runtime {
+struct PlatformInfo;
+}
+}
+
+class Extension;
+class UpdateObserver;
+
+// This is a delegate interface for chrome.runtime API behavior. Clients must
+// vend some implementation of this interface through
+// ExtensionsBrowserClient::CreateRuntimeAPIDelegate.
+class RuntimeAPIDelegate {
+ public:
+ struct UpdateCheckResult {
+ bool success;
+ std::string response;
+ std::string version;
+
+ UpdateCheckResult(bool success,
+ const std::string& response,
+ const std::string& version);
+ };
+
+ virtual ~RuntimeAPIDelegate() {}
+
+ // The callback given to RequestUpdateCheck.
+ typedef base::Callback<void(const UpdateCheckResult&)> UpdateCheckCallback;
+
+ // Registers an UpdateObserver on behalf of the runtime API.
+ virtual void AddUpdateObserver(UpdateObserver* observer) = 0;
+
+ // Unregisters an UpdateObserver on behalf of the runtime API.
+ virtual void RemoveUpdateObserver(UpdateObserver* observer) = 0;
+
+ // Determines an extension's previously installed version if applicable.
+ virtual base::Version GetPreviousExtensionVersion(
+ const Extension* extension) = 0;
+
+ // Reloads an extension.
+ virtual void ReloadExtension(const std::string& extension_id) = 0;
+
+ // Requests an extensions update update check. Returns |false| if updates
+ // are disabled. Otherwise |callback| is called with the result of the
+ // update check.
+ virtual bool CheckForUpdates(const std::string& extension_id,
+ const UpdateCheckCallback& callback) = 0;
+
+ // Navigates the browser to a URL on behalf of the runtime API.
+ virtual void OpenURL(const GURL& uninstall_url) = 0;
+
+ // Populates platform info to be provided by the getPlatformInfo function.
+ // Returns false iff no info is provided.
+ virtual bool GetPlatformInfo(api::runtime::PlatformInfo* info) = 0;
+
+ // Request a restart of the host device. Returns false iff the device
+ // will not be restarted.
+ virtual bool RestartDevice(std::string* error_message) = 0;
+
+ // Open |extension|'s options page, if it has one. Returns true if an
+ // options page was opened, false otherwise. See the docs of the
+ // chrome.runtime.openOptionsPage function for the gritty details.
+ virtual bool OpenOptionsPage(const Extension* extension);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_RUNTIME_RUNTIME_API_DELEGATE_H
diff --git a/chromium/extensions/browser/api/runtime/runtime_apitest.cc b/chromium/extensions/browser/api/runtime/runtime_apitest.cc
new file mode 100644
index 00000000000..cc7226687a0
--- /dev/null
+++ b/chromium/extensions/browser/api/runtime/runtime_apitest.cc
@@ -0,0 +1,131 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/apps/app_browsertest_util.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/extensions/extension_function_test_utils.h"
+#include "chrome/browser/extensions/test_extension_dir.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/notification_service.h"
+#include "extensions/browser/api/runtime/runtime_api.h"
+#include "extensions/browser/extension_dialog_auto_confirm.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/test/result_catcher.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+// Tests the privileged components of chrome.runtime.
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ChromeRuntimePrivileged) {
+ ASSERT_TRUE(RunExtensionTest("runtime/privileged")) << message_;
+}
+
+// Tests the unprivileged components of chrome.runtime.
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ChromeRuntimeUnprivileged) {
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(
+ LoadExtension(test_data_dir_.AppendASCII("runtime/content_script")));
+
+ // The content script runs on webpage.html.
+ extensions::ResultCatcher catcher;
+ ui_test_utils::NavigateToURL(browser(),
+ embedded_test_server()->GetURL("/webpage.html"));
+ EXPECT_TRUE(catcher.GetNextResult()) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ChromeRuntimeUninstallURL) {
+ // Auto-confirm the uninstall dialog.
+ extensions::ScopedTestDialogAutoConfirm auto_confirm(
+ extensions::ScopedTestDialogAutoConfirm::ACCEPT);
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("runtime")
+ .AppendASCII("uninstall_url")
+ .AppendASCII("sets_uninstall_url")));
+ ASSERT_TRUE(RunExtensionTest("runtime/uninstall_url")) << message_;
+}
+
+namespace extensions {
+
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ChromeRuntimeOpenOptionsPage) {
+ ASSERT_TRUE(RunExtensionTest("runtime/open_options_page"));
+}
+
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ChromeRuntimeOpenOptionsPageError) {
+ ASSERT_TRUE(RunExtensionTest("runtime/open_options_page_error"));
+}
+
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ChromeRuntimeGetPlatformInfo) {
+ scoped_ptr<base::Value> result(
+ extension_function_test_utils::RunFunctionAndReturnSingleResult(
+ new RuntimeGetPlatformInfoFunction(), "[]", browser()));
+ ASSERT_TRUE(result.get() != NULL);
+ base::DictionaryValue* dict =
+ extension_function_test_utils::ToDictionary(result.get());
+ ASSERT_TRUE(dict != NULL);
+ EXPECT_TRUE(dict->HasKey("os"));
+ EXPECT_TRUE(dict->HasKey("arch"));
+ EXPECT_TRUE(dict->HasKey("nacl_arch"));
+}
+
+// Tests chrome.runtime.getPackageDirectory with an app.
+IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest,
+ ChromeRuntimeGetPackageDirectoryEntryApp) {
+ ASSERT_TRUE(RunPlatformAppTest("api_test/runtime/get_package_directory/app"))
+ << message_;
+}
+
+// Tests chrome.runtime.getPackageDirectory with an extension.
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest,
+ ChromeRuntimeGetPackageDirectoryEntryExtension) {
+ ASSERT_TRUE(RunExtensionTest("runtime/get_package_directory/extension"))
+ << message_;
+}
+
+// Tests chrome.runtime.reload
+// This test is flaky: crbug.com/366181
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, DISABLED_ChromeRuntimeReload) {
+ ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
+ const char kManifest[] =
+ "{"
+ " \"name\": \"reload\","
+ " \"version\": \"1.0\","
+ " \"background\": {"
+ " \"scripts\": [\"background.js\"]"
+ " },"
+ " \"manifest_version\": 2"
+ "}";
+
+ TestExtensionDir dir;
+ dir.WriteManifest(kManifest);
+ dir.WriteFile(FILE_PATH_LITERAL("background.js"), "console.log('loaded');");
+
+ const Extension* extension = LoadExtension(dir.unpacked_path());
+ ASSERT_TRUE(extension);
+ const std::string extension_id = extension->id();
+
+ // Somewhat arbitrary upper limit of 30 iterations. If the extension manages
+ // to reload itself that often without being terminated, the test fails
+ // anyway.
+ for (int i = 0; i < 30; i++) {
+ content::WindowedNotificationObserver unload_observer(
+ extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
+ content::NotificationService::AllSources());
+ content::WindowedNotificationObserver load_observer(
+ extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
+ content::NotificationService::AllSources());
+
+ ASSERT_TRUE(ExecuteScriptInBackgroundPageNoWait(
+ extension_id, "chrome.runtime.reload();"));
+ unload_observer.Wait();
+
+ if (registry->GetExtensionById(extension_id,
+ ExtensionRegistry::TERMINATED)) {
+ break;
+ } else {
+ load_observer.Wait();
+ WaitForExtensionViewsToLoad();
+ }
+ }
+ ASSERT_TRUE(
+ registry->GetExtensionById(extension_id, ExtensionRegistry::TERMINATED));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/serial/DEPS b/chromium/extensions/browser/api/serial/DEPS
new file mode 100644
index 00000000000..e273c393ba2
--- /dev/null
+++ b/chromium/extensions/browser/api/serial/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+device/serial",
+]
diff --git a/chromium/extensions/browser/api/serial/OWNERS b/chromium/extensions/browser/api/serial/OWNERS
new file mode 100644
index 00000000000..bcb39225427
--- /dev/null
+++ b/chromium/extensions/browser/api/serial/OWNERS
@@ -0,0 +1,3 @@
+reillyg@chromium.org
+rockot@chromium.org
+rpaquay@chromium.org
diff --git a/chromium/extensions/browser/api/serial/serial_api.cc b/chromium/extensions/browser/api/serial/serial_api.cc
new file mode 100644
index 00000000000..74346baa2f5
--- /dev/null
+++ b/chromium/extensions/browser/api/serial/serial_api.cc
@@ -0,0 +1,495 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/serial/serial_api.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/values.h"
+#include "build/build_config.h"
+#include "content/public/browser/browser_thread.h"
+#include "device/serial/serial_device_enumerator.h"
+#include "extensions/browser/api/serial/serial_connection.h"
+#include "extensions/browser/api/serial/serial_event_dispatcher.h"
+#include "extensions/common/api/serial.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+namespace api {
+
+namespace {
+
+// It's a fool's errand to come up with a default bitrate, because we don't get
+// to control both sides of the communication. Unless the other side has
+// implemented auto-bitrate detection (rare), if we pick the wrong rate, then
+// you're gonna have a bad time. Close doesn't count.
+//
+// But we'd like to pick something that has a chance of working, and 9600 is a
+// good balance between popularity and speed. So 9600 it is.
+const int kDefaultBufferSize = 4096;
+const int kDefaultBitrate = 9600;
+const serial::DataBits kDefaultDataBits = serial::DATA_BITS_EIGHT;
+const serial::ParityBit kDefaultParityBit = serial::PARITY_BIT_NO;
+const serial::StopBits kDefaultStopBits = serial::STOP_BITS_ONE;
+const int kDefaultReceiveTimeout = 0;
+const int kDefaultSendTimeout = 0;
+
+const char kErrorConnectFailed[] = "Failed to connect to the port.";
+const char kErrorSerialConnectionNotFound[] = "Serial connection not found.";
+const char kErrorGetControlSignalsFailed[] = "Failed to get control signals.";
+
+template <class T>
+void SetDefaultScopedPtrValue(scoped_ptr<T>& ptr, const T& value) {
+ if (!ptr.get())
+ ptr.reset(new T(value));
+}
+
+} // namespace
+
+SerialAsyncApiFunction::SerialAsyncApiFunction() : manager_(NULL) {
+}
+
+SerialAsyncApiFunction::~SerialAsyncApiFunction() {
+}
+
+bool SerialAsyncApiFunction::PrePrepare() {
+ manager_ = ApiResourceManager<SerialConnection>::Get(browser_context());
+ DCHECK(manager_);
+ return true;
+}
+
+bool SerialAsyncApiFunction::Respond() {
+ return error_.empty();
+}
+
+SerialConnection* SerialAsyncApiFunction::GetSerialConnection(
+ int api_resource_id) {
+ return manager_->Get(extension_->id(), api_resource_id);
+}
+
+void SerialAsyncApiFunction::RemoveSerialConnection(int api_resource_id) {
+ manager_->Remove(extension_->id(), api_resource_id);
+}
+
+SerialGetDevicesFunction::SerialGetDevicesFunction() {
+}
+
+bool SerialGetDevicesFunction::Prepare() {
+ set_work_thread_id(BrowserThread::FILE);
+ return true;
+}
+
+void SerialGetDevicesFunction::Work() {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+
+ scoped_ptr<device::SerialDeviceEnumerator> enumerator =
+ device::SerialDeviceEnumerator::Create();
+ mojo::Array<device::serial::DeviceInfoPtr> devices = enumerator->GetDevices();
+ results_ = serial::GetDevices::Results::Create(
+ devices.To<std::vector<serial::DeviceInfo>>());
+}
+
+SerialConnectFunction::SerialConnectFunction() {
+}
+
+SerialConnectFunction::~SerialConnectFunction() {
+}
+
+bool SerialConnectFunction::Prepare() {
+ params_ = serial::Connect::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ // Fill in any omitted options to ensure a known initial configuration.
+ if (!params_->options.get())
+ params_->options.reset(new serial::ConnectionOptions());
+ serial::ConnectionOptions* options = params_->options.get();
+
+ SetDefaultScopedPtrValue(options->persistent, false);
+ SetDefaultScopedPtrValue(options->buffer_size, kDefaultBufferSize);
+ SetDefaultScopedPtrValue(options->bitrate, kDefaultBitrate);
+ SetDefaultScopedPtrValue(options->cts_flow_control, false);
+ SetDefaultScopedPtrValue(options->receive_timeout, kDefaultReceiveTimeout);
+ SetDefaultScopedPtrValue(options->send_timeout, kDefaultSendTimeout);
+
+ if (options->data_bits == serial::DATA_BITS_NONE)
+ options->data_bits = kDefaultDataBits;
+ if (options->parity_bit == serial::PARITY_BIT_NONE)
+ options->parity_bit = kDefaultParityBit;
+ if (options->stop_bits == serial::STOP_BITS_NONE)
+ options->stop_bits = kDefaultStopBits;
+
+ serial_event_dispatcher_ = SerialEventDispatcher::Get(browser_context());
+ DCHECK(serial_event_dispatcher_);
+
+ return true;
+}
+
+void SerialConnectFunction::AsyncWorkStart() {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ connection_ = CreateSerialConnection(params_->path, extension_->id());
+ connection_->Open(*params_->options.get(),
+ base::Bind(&SerialConnectFunction::OnConnected, this));
+}
+
+void SerialConnectFunction::OnConnected(bool success) {
+ DCHECK(connection_);
+
+ if (!success) {
+ delete connection_;
+ connection_ = NULL;
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&SerialConnectFunction::FinishConnect, this));
+}
+
+void SerialConnectFunction::FinishConnect() {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ if (!connection_) {
+ error_ = kErrorConnectFailed;
+ } else {
+ int id = manager_->Add(connection_);
+ serial::ConnectionInfo info;
+ info.connection_id = id;
+ if (connection_->GetInfo(&info)) {
+ serial_event_dispatcher_->PollConnection(extension_->id(), id);
+ results_ = serial::Connect::Results::Create(info);
+ } else {
+ RemoveSerialConnection(id);
+ error_ = kErrorConnectFailed;
+ }
+ }
+ AsyncWorkCompleted();
+}
+
+SerialConnection* SerialConnectFunction::CreateSerialConnection(
+ const std::string& port,
+ const std::string& extension_id) const {
+ return new SerialConnection(port, extension_id);
+}
+
+SerialUpdateFunction::SerialUpdateFunction() {
+}
+
+SerialUpdateFunction::~SerialUpdateFunction() {
+}
+
+bool SerialUpdateFunction::Prepare() {
+ params_ = serial::Update::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ return true;
+}
+
+void SerialUpdateFunction::Work() {
+ SerialConnection* connection = GetSerialConnection(params_->connection_id);
+ if (!connection) {
+ error_ = kErrorSerialConnectionNotFound;
+ return;
+ }
+ bool success = connection->Configure(params_->options);
+ results_ = serial::Update::Results::Create(success);
+}
+
+SerialDisconnectFunction::SerialDisconnectFunction() {
+}
+
+SerialDisconnectFunction::~SerialDisconnectFunction() {
+}
+
+bool SerialDisconnectFunction::Prepare() {
+ params_ = serial::Disconnect::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ return true;
+}
+
+void SerialDisconnectFunction::Work() {
+ SerialConnection* connection = GetSerialConnection(params_->connection_id);
+ if (!connection) {
+ error_ = kErrorSerialConnectionNotFound;
+ return;
+ }
+ RemoveSerialConnection(params_->connection_id);
+ results_ = serial::Disconnect::Results::Create(true);
+}
+
+SerialSendFunction::SerialSendFunction() {
+}
+
+SerialSendFunction::~SerialSendFunction() {
+}
+
+bool SerialSendFunction::Prepare() {
+ params_ = serial::Send::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ return true;
+}
+
+void SerialSendFunction::AsyncWorkStart() {
+ SerialConnection* connection = GetSerialConnection(params_->connection_id);
+ if (!connection) {
+ error_ = kErrorSerialConnectionNotFound;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ if (!connection->Send(
+ params_->data,
+ base::Bind(&SerialSendFunction::OnSendComplete, this))) {
+ OnSendComplete(0, serial::SEND_ERROR_PENDING);
+ }
+}
+
+void SerialSendFunction::OnSendComplete(int bytes_sent,
+ serial::SendError error) {
+ serial::SendInfo send_info;
+ send_info.bytes_sent = bytes_sent;
+ send_info.error = error;
+ results_ = serial::Send::Results::Create(send_info);
+ AsyncWorkCompleted();
+}
+
+SerialFlushFunction::SerialFlushFunction() {
+}
+
+SerialFlushFunction::~SerialFlushFunction() {
+}
+
+bool SerialFlushFunction::Prepare() {
+ params_ = serial::Flush::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SerialFlushFunction::Work() {
+ SerialConnection* connection = GetSerialConnection(params_->connection_id);
+ if (!connection) {
+ error_ = kErrorSerialConnectionNotFound;
+ return;
+ }
+
+ bool success = connection->Flush();
+ results_ = serial::Flush::Results::Create(success);
+}
+
+SerialSetPausedFunction::SerialSetPausedFunction() {
+}
+
+SerialSetPausedFunction::~SerialSetPausedFunction() {
+}
+
+bool SerialSetPausedFunction::Prepare() {
+ params_ = serial::SetPaused::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ serial_event_dispatcher_ = SerialEventDispatcher::Get(browser_context());
+ DCHECK(serial_event_dispatcher_);
+ return true;
+}
+
+void SerialSetPausedFunction::Work() {
+ SerialConnection* connection = GetSerialConnection(params_->connection_id);
+ if (!connection) {
+ error_ = kErrorSerialConnectionNotFound;
+ return;
+ }
+
+ if (params_->paused != connection->paused()) {
+ connection->set_paused(params_->paused);
+ if (!params_->paused) {
+ serial_event_dispatcher_->PollConnection(extension_->id(),
+ params_->connection_id);
+ }
+ }
+
+ results_ = serial::SetPaused::Results::Create();
+}
+
+SerialGetInfoFunction::SerialGetInfoFunction() {
+}
+
+SerialGetInfoFunction::~SerialGetInfoFunction() {
+}
+
+bool SerialGetInfoFunction::Prepare() {
+ params_ = serial::GetInfo::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ return true;
+}
+
+void SerialGetInfoFunction::Work() {
+ SerialConnection* connection = GetSerialConnection(params_->connection_id);
+ if (!connection) {
+ error_ = kErrorSerialConnectionNotFound;
+ return;
+ }
+
+ serial::ConnectionInfo info;
+ info.connection_id = params_->connection_id;
+ connection->GetInfo(&info);
+ results_ = serial::GetInfo::Results::Create(info);
+}
+
+SerialGetConnectionsFunction::SerialGetConnectionsFunction() {
+}
+
+SerialGetConnectionsFunction::~SerialGetConnectionsFunction() {
+}
+
+bool SerialGetConnectionsFunction::Prepare() {
+ return true;
+}
+
+void SerialGetConnectionsFunction::Work() {
+ std::vector<serial::ConnectionInfo> infos;
+ const base::hash_set<int>* connection_ids =
+ manager_->GetResourceIds(extension_->id());
+ if (connection_ids) {
+ for (base::hash_set<int>::const_iterator it = connection_ids->begin();
+ it != connection_ids->end();
+ ++it) {
+ int connection_id = *it;
+ SerialConnection* connection = GetSerialConnection(connection_id);
+ if (connection) {
+ serial::ConnectionInfo info;
+ info.connection_id = connection_id;
+ connection->GetInfo(&info);
+ infos.push_back(std::move(info));
+ }
+ }
+ }
+ results_ = serial::GetConnections::Results::Create(infos);
+}
+
+SerialGetControlSignalsFunction::SerialGetControlSignalsFunction() {
+}
+
+SerialGetControlSignalsFunction::~SerialGetControlSignalsFunction() {
+}
+
+bool SerialGetControlSignalsFunction::Prepare() {
+ params_ = serial::GetControlSignals::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ return true;
+}
+
+void SerialGetControlSignalsFunction::Work() {
+ SerialConnection* connection = GetSerialConnection(params_->connection_id);
+ if (!connection) {
+ error_ = kErrorSerialConnectionNotFound;
+ return;
+ }
+
+ serial::DeviceControlSignals signals;
+ if (!connection->GetControlSignals(&signals)) {
+ error_ = kErrorGetControlSignalsFailed;
+ return;
+ }
+
+ results_ = serial::GetControlSignals::Results::Create(signals);
+}
+
+SerialSetControlSignalsFunction::SerialSetControlSignalsFunction() {
+}
+
+SerialSetControlSignalsFunction::~SerialSetControlSignalsFunction() {
+}
+
+bool SerialSetControlSignalsFunction::Prepare() {
+ params_ = serial::SetControlSignals::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ return true;
+}
+
+void SerialSetControlSignalsFunction::Work() {
+ SerialConnection* connection = GetSerialConnection(params_->connection_id);
+ if (!connection) {
+ error_ = kErrorSerialConnectionNotFound;
+ return;
+ }
+
+ bool success = connection->SetControlSignals(params_->signals);
+ results_ = serial::SetControlSignals::Results::Create(success);
+}
+
+SerialSetBreakFunction::SerialSetBreakFunction() {
+}
+
+SerialSetBreakFunction::~SerialSetBreakFunction() {
+}
+
+bool SerialSetBreakFunction::Prepare() {
+ params_ = serial::SetBreak::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ return true;
+}
+
+void SerialSetBreakFunction::Work() {
+ SerialConnection* connection = GetSerialConnection(params_->connection_id);
+ if (!connection) {
+ error_ = kErrorSerialConnectionNotFound;
+ return;
+ }
+ bool success = connection->SetBreak();
+ results_ = serial::SetBreak::Results::Create(success);
+}
+
+SerialClearBreakFunction::SerialClearBreakFunction() {
+}
+
+SerialClearBreakFunction::~SerialClearBreakFunction() {
+}
+
+bool SerialClearBreakFunction::Prepare() {
+ params_ = serial::ClearBreak::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ return true;
+}
+
+void SerialClearBreakFunction::Work() {
+ SerialConnection* connection = GetSerialConnection(params_->connection_id);
+ if (!connection) {
+ error_ = kErrorSerialConnectionNotFound;
+ return;
+ }
+
+ bool success = connection->ClearBreak();
+ results_ = serial::ClearBreak::Results::Create(success);
+}
+
+} // namespace api
+
+} // namespace extensions
+
+namespace mojo {
+
+// static
+extensions::api::serial::DeviceInfo TypeConverter<
+ extensions::api::serial::DeviceInfo,
+ device::serial::DeviceInfoPtr>::Convert(const device::serial::DeviceInfoPtr&
+ device) {
+ extensions::api::serial::DeviceInfo info;
+ info.path = device->path;
+ if (device->has_vendor_id)
+ info.vendor_id.reset(new int(static_cast<int>(device->vendor_id)));
+ if (device->has_product_id)
+ info.product_id.reset(new int(static_cast<int>(device->product_id)));
+ if (device->display_name)
+ info.display_name.reset(new std::string(device->display_name));
+ return info;
+}
+
+} // namespace mojo
diff --git a/chromium/extensions/browser/api/serial/serial_api.h b/chromium/extensions/browser/api/serial/serial_api.h
new file mode 100644
index 00000000000..c6831fb6730
--- /dev/null
+++ b/chromium/extensions/browser/api/serial/serial_api.h
@@ -0,0 +1,289 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SERIAL_SERIAL_API_H_
+#define EXTENSIONS_BROWSER_API_SERIAL_SERIAL_API_H_
+
+#include <string>
+
+#include "device/serial/serial.mojom.h"
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/browser/api/async_api_function.h"
+#include "extensions/common/api/serial.h"
+
+namespace extensions {
+
+class SerialConnection;
+
+namespace api {
+
+class SerialEventDispatcher;
+
+class SerialAsyncApiFunction : public AsyncApiFunction {
+ public:
+ SerialAsyncApiFunction();
+
+ protected:
+ ~SerialAsyncApiFunction() override;
+
+ // AsyncApiFunction:
+ bool PrePrepare() override;
+ bool Respond() override;
+
+ SerialConnection* GetSerialConnection(int api_resource_id);
+ void RemoveSerialConnection(int api_resource_id);
+
+ ApiResourceManager<SerialConnection>* manager_;
+};
+
+class SerialGetDevicesFunction : public SerialAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("serial.getDevices", SERIAL_GETDEVICES)
+
+ SerialGetDevicesFunction();
+
+ protected:
+ ~SerialGetDevicesFunction() override {}
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+};
+
+class SerialConnectFunction : public SerialAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("serial.connect", SERIAL_CONNECT)
+
+ SerialConnectFunction();
+
+ protected:
+ ~SerialConnectFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ virtual SerialConnection* CreateSerialConnection(
+ const std::string& port,
+ const std::string& extension_id) const;
+
+ private:
+ void OnConnected(bool success);
+ void FinishConnect();
+
+ scoped_ptr<serial::Connect::Params> params_;
+
+ // SerialEventDispatcher is owned by a BrowserContext.
+ SerialEventDispatcher* serial_event_dispatcher_;
+
+ // This connection is created within SerialConnectFunction.
+ // From there it is either destroyed in OnConnected (upon failure)
+ // or its ownership is transferred to the
+ // ApiResourceManager<SerialConnection>.
+ SerialConnection* connection_;
+};
+
+class SerialUpdateFunction : public SerialAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("serial.update", SERIAL_UPDATE);
+
+ SerialUpdateFunction();
+
+ protected:
+ ~SerialUpdateFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<serial::Update::Params> params_;
+};
+
+class SerialDisconnectFunction : public SerialAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("serial.disconnect", SERIAL_DISCONNECT)
+
+ SerialDisconnectFunction();
+
+ protected:
+ ~SerialDisconnectFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<serial::Disconnect::Params> params_;
+};
+
+class SerialSetPausedFunction : public SerialAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("serial.setPaused", SERIAL_SETPAUSED)
+
+ SerialSetPausedFunction();
+
+ protected:
+ ~SerialSetPausedFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<serial::SetPaused::Params> params_;
+ SerialEventDispatcher* serial_event_dispatcher_;
+};
+
+class SerialGetInfoFunction : public SerialAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("serial.getInfo", SERIAL_GETINFO)
+
+ SerialGetInfoFunction();
+
+ protected:
+ ~SerialGetInfoFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<serial::GetInfo::Params> params_;
+};
+
+class SerialGetConnectionsFunction : public SerialAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("serial.getConnections", SERIAL_GETCONNECTIONS);
+
+ SerialGetConnectionsFunction();
+
+ protected:
+ ~SerialGetConnectionsFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+};
+
+class SerialSendFunction : public SerialAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("serial.send", SERIAL_SEND)
+
+ SerialSendFunction();
+
+ protected:
+ ~SerialSendFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ private:
+ void OnSendComplete(int bytes_sent, serial::SendError error);
+
+ scoped_ptr<serial::Send::Params> params_;
+};
+
+class SerialFlushFunction : public SerialAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("serial.flush", SERIAL_FLUSH)
+
+ SerialFlushFunction();
+
+ protected:
+ ~SerialFlushFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<serial::Flush::Params> params_;
+};
+
+class SerialGetControlSignalsFunction : public SerialAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("serial.getControlSignals",
+ SERIAL_GETCONTROLSIGNALS)
+
+ SerialGetControlSignalsFunction();
+
+ protected:
+ ~SerialGetControlSignalsFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<serial::GetControlSignals::Params> params_;
+};
+
+class SerialSetControlSignalsFunction : public SerialAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("serial.setControlSignals",
+ SERIAL_SETCONTROLSIGNALS)
+
+ SerialSetControlSignalsFunction();
+
+ protected:
+ ~SerialSetControlSignalsFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<serial::SetControlSignals::Params> params_;
+};
+
+class SerialSetBreakFunction : public SerialAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("serial.setBreak", SERIAL_SETBREAK)
+ SerialSetBreakFunction();
+
+ protected:
+ ~SerialSetBreakFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<serial::SetBreak::Params> params_;
+};
+
+class SerialClearBreakFunction : public SerialAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("serial.clearBreak", SERIAL_CLEARBREAK)
+ SerialClearBreakFunction();
+
+ protected:
+ ~SerialClearBreakFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<serial::ClearBreak::Params> params_;
+};
+
+} // namespace api
+
+} // namespace extensions
+
+namespace mojo {
+
+template <>
+struct TypeConverter<extensions::api::serial::DeviceInfo,
+ device::serial::DeviceInfoPtr> {
+ static extensions::api::serial::DeviceInfo Convert(
+ const device::serial::DeviceInfoPtr& input);
+};
+
+} // namespace mojo
+
+#endif // EXTENSIONS_BROWSER_API_SERIAL_SERIAL_API_H_
diff --git a/chromium/extensions/browser/api/serial/serial_apitest.cc b/chromium/extensions/browser/api/serial/serial_apitest.cc
new file mode 100644
index 00000000000..f56ff94345c
--- /dev/null
+++ b/chromium/extensions/browser/api/serial/serial_apitest.cc
@@ -0,0 +1,205 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+#include <utility>
+
+#include "base/macros.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "content/public/browser/browser_thread.h"
+#include "device/serial/serial_device_enumerator.h"
+#include "device/serial/serial_service_impl.h"
+#include "device/serial/test_serial_io_handler.h"
+#include "extensions/browser/api/serial/serial_api.h"
+#include "extensions/browser/api/serial/serial_connection.h"
+#include "extensions/browser/api/serial/serial_service_factory.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/common/api/serial.h"
+#include "extensions/common/switches.h"
+#include "extensions/test/result_catcher.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using testing::_;
+using testing::Return;
+
+namespace extensions {
+namespace {
+
+class FakeSerialGetDevicesFunction : public AsyncExtensionFunction {
+ public:
+ bool RunAsync() override {
+ base::ListValue* devices = new base::ListValue();
+ base::DictionaryValue* device0 = new base::DictionaryValue();
+ device0->SetString("path", "/dev/fakeserial");
+ base::DictionaryValue* device1 = new base::DictionaryValue();
+ device1->SetString("path", "\\\\COM800\\");
+ devices->Append(device0);
+ devices->Append(device1);
+ SetResult(devices);
+ SendResponse(true);
+ return true;
+ }
+
+ protected:
+ ~FakeSerialGetDevicesFunction() override {}
+};
+
+class FakeSerialDeviceEnumerator : public device::SerialDeviceEnumerator {
+ public:
+ ~FakeSerialDeviceEnumerator() override {}
+
+ mojo::Array<device::serial::DeviceInfoPtr> GetDevices() override {
+ mojo::Array<device::serial::DeviceInfoPtr> devices;
+ device::serial::DeviceInfoPtr device0(device::serial::DeviceInfo::New());
+ device0->path = "/dev/fakeserialmojo";
+ device::serial::DeviceInfoPtr device1(device::serial::DeviceInfo::New());
+ device1->path = "\\\\COM800\\";
+ devices.push_back(std::move(device0));
+ devices.push_back(std::move(device1));
+ return devices;
+ }
+};
+
+class FakeEchoSerialIoHandler : public device::TestSerialIoHandler {
+ public:
+ FakeEchoSerialIoHandler() {
+ device_control_signals()->dcd = true;
+ device_control_signals()->cts = true;
+ device_control_signals()->ri = true;
+ device_control_signals()->dsr = true;
+ EXPECT_CALL(*this, SetControlSignals(_)).Times(1).WillOnce(Return(true));
+ }
+
+ static scoped_refptr<device::SerialIoHandler> Create() {
+ return new FakeEchoSerialIoHandler();
+ }
+
+ MOCK_METHOD1(SetControlSignals,
+ bool(const device::serial::HostControlSignals&));
+
+ protected:
+ ~FakeEchoSerialIoHandler() override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FakeEchoSerialIoHandler);
+};
+
+class FakeSerialConnectFunction : public api::SerialConnectFunction {
+ protected:
+ SerialConnection* CreateSerialConnection(
+ const std::string& port,
+ const std::string& owner_extension_id) const override {
+ scoped_refptr<FakeEchoSerialIoHandler> io_handler =
+ new FakeEchoSerialIoHandler;
+ SerialConnection* serial_connection =
+ new SerialConnection(port, owner_extension_id);
+ serial_connection->SetIoHandlerForTest(io_handler);
+ return serial_connection;
+ }
+
+ protected:
+ ~FakeSerialConnectFunction() override {}
+};
+
+class SerialApiTest : public ExtensionApiTest,
+ public testing::WithParamInterface<bool> {
+ public:
+ SerialApiTest() {}
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ ExtensionApiTest::SetUpCommandLine(command_line);
+ if (GetParam())
+ command_line->AppendSwitch(switches::kEnableMojoSerialService);
+ }
+
+ void TearDownOnMainThread() override {
+ SetSerialServiceFactoryForTest(nullptr);
+ ExtensionApiTest::TearDownOnMainThread();
+ }
+
+ protected:
+ base::Callback<void(mojo::InterfaceRequest<device::serial::SerialService>)>
+ serial_service_factory_;
+};
+
+ExtensionFunction* FakeSerialGetDevicesFunctionFactory() {
+ return new FakeSerialGetDevicesFunction();
+}
+
+ExtensionFunction* FakeSerialConnectFunctionFactory() {
+ return new FakeSerialConnectFunction();
+}
+
+void CreateTestSerialServiceOnFileThread(
+ mojo::InterfaceRequest<device::serial::SerialService> request) {
+ auto io_handler_factory = base::Bind(&FakeEchoSerialIoHandler::Create);
+ auto connection_factory = new device::SerialConnectionFactory(
+ io_handler_factory,
+ content::BrowserThread::GetMessageLoopProxyForThread(
+ content::BrowserThread::IO));
+ scoped_ptr<device::SerialDeviceEnumerator> device_enumerator(
+ new FakeSerialDeviceEnumerator);
+ new device::SerialServiceImpl(
+ connection_factory, std::move(device_enumerator), std::move(request));
+}
+
+void CreateTestSerialService(
+ mojo::InterfaceRequest<device::serial::SerialService> request) {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&CreateTestSerialServiceOnFileThread, base::Passed(&request)));
+}
+
+} // namespace
+
+// Disable SIMULATE_SERIAL_PORTS only if all the following are true:
+//
+// 1. You have an Arduino or compatible board attached to your machine and
+// properly appearing as the first virtual serial port ("first" is very loosely
+// defined as whichever port shows up in serial.getPorts). We've tested only
+// the Atmega32u4 Breakout Board and Arduino Leonardo; note that both these
+// boards are based on the Atmel ATmega32u4, rather than the more common
+// Arduino '328p with either FTDI or '8/16u2 USB interfaces. TODO: test more
+// widely.
+//
+// 2. Your user has permission to read/write the port. For example, this might
+// mean that your user is in the "tty" or "uucp" group on Ubuntu flavors of
+// Linux, or else that the port's path (e.g., /dev/ttyACM0) has global
+// read/write permissions.
+//
+// 3. You have uploaded a program to the board that does a byte-for-byte echo
+// on the virtual serial port at 57600 bps. An example is at
+// chrome/test/data/extensions/api_test/serial/api/serial_arduino_test.ino.
+//
+#define SIMULATE_SERIAL_PORTS (1)
+IN_PROC_BROWSER_TEST_P(SerialApiTest, SerialFakeHardware) {
+ ResultCatcher catcher;
+ catcher.RestrictToBrowserContext(browser()->profile());
+
+#if SIMULATE_SERIAL_PORTS
+ if (GetParam()) {
+ serial_service_factory_ = base::Bind(&CreateTestSerialService);
+ SetSerialServiceFactoryForTest(&serial_service_factory_);
+ } else {
+ ASSERT_TRUE(ExtensionFunctionDispatcher::OverrideFunction(
+ "serial.getDevices", FakeSerialGetDevicesFunctionFactory));
+ ASSERT_TRUE(ExtensionFunctionDispatcher::OverrideFunction(
+ "serial.connect", FakeSerialConnectFunctionFactory));
+ }
+#endif
+
+ ASSERT_TRUE(RunExtensionTest("serial/api")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_P(SerialApiTest, SerialRealHardware) {
+ ResultCatcher catcher;
+ catcher.RestrictToBrowserContext(browser()->profile());
+
+ ASSERT_TRUE(RunExtensionTest("serial/real_hardware")) << message_;
+}
+
+INSTANTIATE_TEST_CASE_P(SerialApiTest, SerialApiTest, testing::Bool());
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/serial/serial_connection.cc b/chromium/extensions/browser/api/serial/serial_connection.cc
new file mode 100644
index 00000000000..6e4cde90309
--- /dev/null
+++ b/chromium/extensions/browser/api/serial/serial_connection.cc
@@ -0,0 +1,420 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/serial/serial_connection.h"
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop/message_loop.h"
+#include "device/serial/buffer.h"
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/common/api/serial.h"
+
+namespace extensions {
+
+namespace {
+
+const int kDefaultBufferSize = 4096;
+
+api::serial::SendError ConvertSendErrorFromMojo(
+ device::serial::SendError input) {
+ switch (input) {
+ case device::serial::SendError::NONE:
+ return api::serial::SEND_ERROR_NONE;
+ case device::serial::SendError::DISCONNECTED:
+ return api::serial::SEND_ERROR_DISCONNECTED;
+ case device::serial::SendError::PENDING:
+ return api::serial::SEND_ERROR_PENDING;
+ case device::serial::SendError::TIMEOUT:
+ return api::serial::SEND_ERROR_TIMEOUT;
+ case device::serial::SendError::SYSTEM_ERROR:
+ return api::serial::SEND_ERROR_SYSTEM_ERROR;
+ }
+ return api::serial::SEND_ERROR_NONE;
+}
+
+api::serial::ReceiveError ConvertReceiveErrorFromMojo(
+ device::serial::ReceiveError input) {
+ switch (input) {
+ case device::serial::ReceiveError::NONE:
+ return api::serial::RECEIVE_ERROR_NONE;
+ case device::serial::ReceiveError::DISCONNECTED:
+ return api::serial::RECEIVE_ERROR_DISCONNECTED;
+ case device::serial::ReceiveError::TIMEOUT:
+ return api::serial::RECEIVE_ERROR_TIMEOUT;
+ case device::serial::ReceiveError::DEVICE_LOST:
+ return api::serial::RECEIVE_ERROR_DEVICE_LOST;
+ case device::serial::ReceiveError::BREAK:
+ return api::serial::RECEIVE_ERROR_BREAK;
+ case device::serial::ReceiveError::FRAME_ERROR:
+ return api::serial::RECEIVE_ERROR_FRAME_ERROR;
+ case device::serial::ReceiveError::OVERRUN:
+ return api::serial::RECEIVE_ERROR_OVERRUN;
+ case device::serial::ReceiveError::BUFFER_OVERFLOW:
+ return api::serial::RECEIVE_ERROR_BUFFER_OVERFLOW;
+ case device::serial::ReceiveError::PARITY_ERROR:
+ return api::serial::RECEIVE_ERROR_PARITY_ERROR;
+ case device::serial::ReceiveError::SYSTEM_ERROR:
+ return api::serial::RECEIVE_ERROR_SYSTEM_ERROR;
+ }
+ return api::serial::RECEIVE_ERROR_NONE;
+}
+
+api::serial::DataBits ConvertDataBitsFromMojo(device::serial::DataBits input) {
+ switch (input) {
+ case device::serial::DataBits::NONE:
+ return api::serial::DATA_BITS_NONE;
+ case device::serial::DataBits::SEVEN:
+ return api::serial::DATA_BITS_SEVEN;
+ case device::serial::DataBits::EIGHT:
+ return api::serial::DATA_BITS_EIGHT;
+ }
+ return api::serial::DATA_BITS_NONE;
+}
+
+device::serial::DataBits ConvertDataBitsToMojo(api::serial::DataBits input) {
+ switch (input) {
+ case api::serial::DATA_BITS_NONE:
+ return device::serial::DataBits::NONE;
+ case api::serial::DATA_BITS_SEVEN:
+ return device::serial::DataBits::SEVEN;
+ case api::serial::DATA_BITS_EIGHT:
+ return device::serial::DataBits::EIGHT;
+ }
+ return device::serial::DataBits::NONE;
+}
+
+api::serial::ParityBit ConvertParityBitFromMojo(
+ device::serial::ParityBit input) {
+ switch (input) {
+ case device::serial::ParityBit::NONE:
+ return api::serial::PARITY_BIT_NONE;
+ case device::serial::ParityBit::ODD:
+ return api::serial::PARITY_BIT_ODD;
+ case device::serial::ParityBit::NO:
+ return api::serial::PARITY_BIT_NO;
+ case device::serial::ParityBit::EVEN:
+ return api::serial::PARITY_BIT_EVEN;
+ }
+ return api::serial::PARITY_BIT_NONE;
+}
+
+device::serial::ParityBit ConvertParityBitToMojo(api::serial::ParityBit input) {
+ switch (input) {
+ case api::serial::PARITY_BIT_NONE:
+ return device::serial::ParityBit::NONE;
+ case api::serial::PARITY_BIT_NO:
+ return device::serial::ParityBit::NO;
+ case api::serial::PARITY_BIT_ODD:
+ return device::serial::ParityBit::ODD;
+ case api::serial::PARITY_BIT_EVEN:
+ return device::serial::ParityBit::EVEN;
+ }
+ return device::serial::ParityBit::NONE;
+}
+
+api::serial::StopBits ConvertStopBitsFromMojo(device::serial::StopBits input) {
+ switch (input) {
+ case device::serial::StopBits::NONE:
+ return api::serial::STOP_BITS_NONE;
+ case device::serial::StopBits::ONE:
+ return api::serial::STOP_BITS_ONE;
+ case device::serial::StopBits::TWO:
+ return api::serial::STOP_BITS_TWO;
+ }
+ return api::serial::STOP_BITS_NONE;
+}
+
+device::serial::StopBits ConvertStopBitsToMojo(api::serial::StopBits input) {
+ switch (input) {
+ case api::serial::STOP_BITS_NONE:
+ return device::serial::StopBits::NONE;
+ case api::serial::STOP_BITS_ONE:
+ return device::serial::StopBits::ONE;
+ case api::serial::STOP_BITS_TWO:
+ return device::serial::StopBits::TWO;
+ }
+ return device::serial::StopBits::NONE;
+}
+
+} // namespace
+
+static base::LazyInstance<
+ BrowserContextKeyedAPIFactory<ApiResourceManager<SerialConnection> > >
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+template <>
+BrowserContextKeyedAPIFactory<ApiResourceManager<SerialConnection> >*
+ApiResourceManager<SerialConnection>::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+SerialConnection::SerialConnection(const std::string& port,
+ const std::string& owner_extension_id)
+ : ApiResource(owner_extension_id),
+ port_(port),
+ persistent_(false),
+ buffer_size_(kDefaultBufferSize),
+ receive_timeout_(0),
+ send_timeout_(0),
+ paused_(false),
+ io_handler_(device::SerialIoHandler::Create(
+ content::BrowserThread::GetMessageLoopProxyForThread(
+ content::BrowserThread::FILE),
+ content::BrowserThread::GetMessageLoopProxyForThread(
+ content::BrowserThread::UI))) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+}
+
+SerialConnection::~SerialConnection() {
+ io_handler_->CancelRead(device::serial::ReceiveError::DISCONNECTED);
+ io_handler_->CancelWrite(device::serial::SendError::DISCONNECTED);
+}
+
+bool SerialConnection::IsPersistent() const {
+ return persistent();
+}
+
+void SerialConnection::set_buffer_size(int buffer_size) {
+ buffer_size_ = buffer_size;
+}
+
+void SerialConnection::set_receive_timeout(int receive_timeout) {
+ receive_timeout_ = receive_timeout;
+}
+
+void SerialConnection::set_send_timeout(int send_timeout) {
+ send_timeout_ = send_timeout;
+}
+
+void SerialConnection::set_paused(bool paused) {
+ paused_ = paused;
+ if (paused) {
+ io_handler_->CancelRead(device::serial::ReceiveError::NONE);
+ }
+}
+
+void SerialConnection::Open(const api::serial::ConnectionOptions& options,
+ const OpenCompleteCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ if (options.persistent.get())
+ set_persistent(*options.persistent);
+ if (options.name.get())
+ set_name(*options.name);
+ if (options.buffer_size.get())
+ set_buffer_size(*options.buffer_size);
+ if (options.receive_timeout.get())
+ set_receive_timeout(*options.receive_timeout);
+ if (options.send_timeout.get())
+ set_send_timeout(*options.send_timeout);
+ io_handler_->Open(port_, *device::serial::ConnectionOptions::From(options),
+ callback);
+}
+
+bool SerialConnection::Receive(const ReceiveCompleteCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ if (!receive_complete_.is_null())
+ return false;
+ receive_complete_ = callback;
+ receive_buffer_ = new net::IOBuffer(buffer_size_);
+ io_handler_->Read(make_scoped_ptr(new device::ReceiveBuffer(
+ receive_buffer_, buffer_size_,
+ base::Bind(&SerialConnection::OnAsyncReadComplete, AsWeakPtr()))));
+ receive_timeout_task_.reset();
+ if (receive_timeout_ > 0) {
+ receive_timeout_task_.reset(new TimeoutTask(
+ base::Bind(&SerialConnection::OnReceiveTimeout, AsWeakPtr()),
+ base::TimeDelta::FromMilliseconds(receive_timeout_)));
+ }
+ return true;
+}
+
+bool SerialConnection::Send(const std::vector<char>& data,
+ const SendCompleteCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ if (!send_complete_.is_null())
+ return false;
+ send_complete_ = callback;
+ io_handler_->Write(make_scoped_ptr(new device::SendBuffer(
+ data, base::Bind(&SerialConnection::OnAsyncWriteComplete, AsWeakPtr()))));
+ send_timeout_task_.reset();
+ if (send_timeout_ > 0) {
+ send_timeout_task_.reset(new TimeoutTask(
+ base::Bind(&SerialConnection::OnSendTimeout, AsWeakPtr()),
+ base::TimeDelta::FromMilliseconds(send_timeout_)));
+ }
+ return true;
+}
+
+bool SerialConnection::Configure(
+ const api::serial::ConnectionOptions& options) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ if (options.persistent.get())
+ set_persistent(*options.persistent);
+ if (options.name.get())
+ set_name(*options.name);
+ if (options.buffer_size.get())
+ set_buffer_size(*options.buffer_size);
+ if (options.receive_timeout.get())
+ set_receive_timeout(*options.receive_timeout);
+ if (options.send_timeout.get())
+ set_send_timeout(*options.send_timeout);
+ bool success = io_handler_->ConfigurePort(
+ *device::serial::ConnectionOptions::From(options));
+ io_handler_->CancelRead(device::serial::ReceiveError::NONE);
+ return success;
+}
+
+void SerialConnection::SetIoHandlerForTest(
+ scoped_refptr<device::SerialIoHandler> handler) {
+ io_handler_ = handler;
+}
+
+bool SerialConnection::GetInfo(api::serial::ConnectionInfo* info) const {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ info->paused = paused_;
+ info->persistent = persistent_;
+ info->name = name_;
+ info->buffer_size = buffer_size_;
+ info->receive_timeout = receive_timeout_;
+ info->send_timeout = send_timeout_;
+ device::serial::ConnectionInfoPtr port_info = io_handler_->GetPortInfo();
+ if (!port_info)
+ return false;
+
+ info->bitrate.reset(new int(port_info->bitrate));
+ info->data_bits = ConvertDataBitsFromMojo(port_info->data_bits);
+ info->parity_bit = ConvertParityBitFromMojo(port_info->parity_bit);
+ info->stop_bits = ConvertStopBitsFromMojo(port_info->stop_bits);
+ info->cts_flow_control.reset(new bool(port_info->cts_flow_control));
+ return true;
+}
+
+bool SerialConnection::Flush() const {
+ return io_handler_->Flush();
+}
+
+bool SerialConnection::GetControlSignals(
+ api::serial::DeviceControlSignals* control_signals) const {
+ device::serial::DeviceControlSignalsPtr signals =
+ io_handler_->GetControlSignals();
+ if (!signals)
+ return false;
+
+ control_signals->dcd = signals->dcd;
+ control_signals->cts = signals->cts;
+ control_signals->ri = signals->ri;
+ control_signals->dsr = signals->dsr;
+ return true;
+}
+
+bool SerialConnection::SetControlSignals(
+ const api::serial::HostControlSignals& control_signals) {
+ return io_handler_->SetControlSignals(
+ *device::serial::HostControlSignals::From(control_signals));
+}
+
+bool SerialConnection::SetBreak() {
+ return io_handler_->SetBreak();
+}
+
+bool SerialConnection::ClearBreak() {
+ return io_handler_->ClearBreak();
+}
+
+void SerialConnection::OnReceiveTimeout() {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ io_handler_->CancelRead(device::serial::ReceiveError::TIMEOUT);
+}
+
+void SerialConnection::OnSendTimeout() {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ io_handler_->CancelWrite(device::serial::SendError::TIMEOUT);
+}
+
+void SerialConnection::OnAsyncReadComplete(int bytes_read,
+ device::serial::ReceiveError error) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ DCHECK(!receive_complete_.is_null());
+ ReceiveCompleteCallback callback = receive_complete_;
+ receive_complete_.Reset();
+ receive_timeout_task_.reset();
+ callback.Run(std::vector<char>(receive_buffer_->data(),
+ receive_buffer_->data() + bytes_read),
+ ConvertReceiveErrorFromMojo(error));
+ receive_buffer_ = NULL;
+}
+
+void SerialConnection::OnAsyncWriteComplete(int bytes_sent,
+ device::serial::SendError error) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ DCHECK(!send_complete_.is_null());
+ SendCompleteCallback callback = send_complete_;
+ send_complete_.Reset();
+ send_timeout_task_.reset();
+ callback.Run(bytes_sent, ConvertSendErrorFromMojo(error));
+}
+
+SerialConnection::TimeoutTask::TimeoutTask(const base::Closure& closure,
+ const base::TimeDelta& delay)
+ : closure_(closure), delay_(delay), weak_factory_(this) {
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&TimeoutTask::Run, weak_factory_.GetWeakPtr()),
+ delay_);
+}
+
+SerialConnection::TimeoutTask::~TimeoutTask() {
+}
+
+void SerialConnection::TimeoutTask::Run() const {
+ closure_.Run();
+}
+
+} // namespace extensions
+
+namespace mojo {
+
+// static
+device::serial::HostControlSignalsPtr
+TypeConverter<device::serial::HostControlSignalsPtr,
+ extensions::api::serial::HostControlSignals>::
+ Convert(const extensions::api::serial::HostControlSignals& input) {
+ device::serial::HostControlSignalsPtr output(
+ device::serial::HostControlSignals::New());
+ if (input.dtr.get()) {
+ output->has_dtr = true;
+ output->dtr = *input.dtr;
+ }
+ if (input.rts.get()) {
+ output->has_rts = true;
+ output->rts = *input.rts;
+ }
+ return output;
+}
+
+// static
+device::serial::ConnectionOptionsPtr
+TypeConverter<device::serial::ConnectionOptionsPtr,
+ extensions::api::serial::ConnectionOptions>::
+ Convert(const extensions::api::serial::ConnectionOptions& input) {
+ device::serial::ConnectionOptionsPtr output(
+ device::serial::ConnectionOptions::New());
+ if (input.bitrate.get() && *input.bitrate > 0)
+ output->bitrate = *input.bitrate;
+ output->data_bits = extensions::ConvertDataBitsToMojo(input.data_bits);
+ output->parity_bit = extensions::ConvertParityBitToMojo(input.parity_bit);
+ output->stop_bits = extensions::ConvertStopBitsToMojo(input.stop_bits);
+ if (input.cts_flow_control.get()) {
+ output->has_cts_flow_control = true;
+ output->cts_flow_control = *input.cts_flow_control;
+ }
+ return output;
+}
+
+} // namespace mojo
diff --git a/chromium/extensions/browser/api/serial/serial_connection.h b/chromium/extensions/browser/api/serial/serial_connection.h
new file mode 100644
index 00000000000..428e2d66ea4
--- /dev/null
+++ b/chromium/extensions/browser/api/serial/serial_connection.h
@@ -0,0 +1,222 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SERIAL_SERIAL_CONNECTION_H_
+#define EXTENSIONS_BROWSER_API_SERIAL_SERIAL_CONNECTION_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "content/public/browser/browser_thread.h"
+#include "device/serial/serial_io_handler.h"
+#include "extensions/browser/api/api_resource.h"
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/common/api/serial.h"
+#include "net/base/io_buffer.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+// Encapsulates an open serial port.
+// NOTE: Instances of this object should only be constructed on the IO thread,
+// and all methods should only be called on the IO thread unless otherwise
+// noted.
+class SerialConnection : public ApiResource,
+ public base::SupportsWeakPtr<SerialConnection> {
+ public:
+ typedef device::SerialIoHandler::OpenCompleteCallback OpenCompleteCallback;
+
+ // This is the callback type expected by Receive. Note that an error result
+ // does not necessarily imply an empty |data| string, since a receive may
+ // complete partially before being interrupted by an error condition.
+ using ReceiveCompleteCallback =
+ base::Callback<void(const std::vector<char>& data,
+ api::serial::ReceiveError error)>;
+
+ // This is the callback type expected by Send. Note that an error result
+ // does not necessarily imply 0 bytes sent, since a send may complete
+ // partially before being interrupted by an error condition.
+ using SendCompleteCallback =
+ base::Callback<void(int bytes_sent, api::serial::SendError error)>;
+
+ SerialConnection(const std::string& port,
+ const std::string& owner_extension_id);
+ ~SerialConnection() override;
+
+ // ApiResource override.
+ bool IsPersistent() const override;
+
+ void set_persistent(bool persistent) { persistent_ = persistent; }
+ bool persistent() const { return persistent_; }
+
+ void set_name(const std::string& name) { name_ = name; }
+ const std::string& name() const { return name_; }
+
+ void set_buffer_size(int buffer_size);
+ int buffer_size() const { return buffer_size_; }
+
+ void set_receive_timeout(int receive_timeout);
+ int receive_timeout() const { return receive_timeout_; }
+
+ void set_send_timeout(int send_timeout);
+ int send_timeout() const { return send_timeout_; }
+
+ void set_paused(bool paused);
+ bool paused() const { return paused_; }
+
+ // Initiates an asynchronous Open of the device. It is the caller's
+ // responsibility to ensure that this SerialConnection stays alive
+ // until |callback| is run.
+ virtual void Open(const api::serial::ConnectionOptions& options,
+ const OpenCompleteCallback& callback);
+
+ // Begins an asynchronous receive operation. Calling this while a Receive
+ // is already pending is a no-op and returns |false| without calling
+ // |callback|.
+ virtual bool Receive(const ReceiveCompleteCallback& callback);
+
+ // Begins an asynchronous send operation. Calling this while a Send
+ // is already pending is a no-op and returns |false| without calling
+ // |callback|.
+ virtual bool Send(const std::vector<char>& data,
+ const SendCompleteCallback& callback);
+
+ // Flushes input and output buffers.
+ bool Flush() const;
+
+ // Configures some subset of port options for this connection.
+ // Omitted options are unchanged. Returns |true| iff the configuration
+ // changes were successful.
+ bool Configure(const api::serial::ConnectionOptions& options);
+
+ // Connection configuration query. Fills values in an existing
+ // ConnectionInfo. Returns |true| iff the connection's information
+ // was successfully retrieved.
+ bool GetInfo(api::serial::ConnectionInfo* info) const;
+
+ // Reads current control signals (DCD, CTS, etc.) into an existing
+ // DeviceControlSignals structure. Returns |true| iff the signals were
+ // successfully read.
+ bool GetControlSignals(
+ api::serial::DeviceControlSignals* control_signals) const;
+
+ // Sets one or more control signals (DTR and/or RTS). Returns |true| iff
+ // the signals were successfully set. Unininitialized flags in the
+ // HostControlSignals structure are left unchanged.
+ bool SetControlSignals(
+ const api::serial::HostControlSignals& control_signals);
+
+ // Suspend character transmission. Known as setting/sending 'Break' signal.
+ bool SetBreak();
+
+ // Restore character transmission. Known as clear/stop sending 'Break' signal.
+ bool ClearBreak();
+
+ // Overrides |io_handler_| for testing.
+ void SetIoHandlerForTest(scoped_refptr<device::SerialIoHandler> handler);
+
+ static const BrowserThread::ID kThreadId = BrowserThread::IO;
+
+ private:
+ friend class ApiResourceManager<SerialConnection>;
+ static const char* service_name() { return "SerialConnectionManager"; }
+
+ // Encapsulates a cancelable, delayed timeout task. Posts a delayed
+ // task upon construction and implicitly cancels the task upon
+ // destruction if it hasn't run yet.
+ class TimeoutTask {
+ public:
+ TimeoutTask(const base::Closure& closure, const base::TimeDelta& delay);
+ ~TimeoutTask();
+
+ private:
+ void Run() const;
+
+ base::Closure closure_;
+ base::TimeDelta delay_;
+ base::WeakPtrFactory<TimeoutTask> weak_factory_;
+ };
+
+ // Handles a receive timeout.
+ void OnReceiveTimeout();
+
+ // Handles a send timeout.
+ void OnSendTimeout();
+
+ // Receives read completion notification from the |io_handler_|.
+ void OnAsyncReadComplete(int bytes_read, device::serial::ReceiveError error);
+
+ // Receives write completion notification from the |io_handler_|.
+ void OnAsyncWriteComplete(int bytes_sent, device::serial::SendError error);
+
+ // The pathname of the serial device.
+ std::string port_;
+
+ // Flag indicating whether or not the connection should persist when
+ // its host app is suspended.
+ bool persistent_;
+
+ // User-specified connection name.
+ std::string name_;
+
+ // Size of the receive buffer.
+ int buffer_size_;
+
+ // Amount of time (in ms) to wait for a Read to succeed before triggering a
+ // timeout response via onReceiveError.
+ int receive_timeout_;
+
+ // Amount of time (in ms) to wait for a Write to succeed before triggering
+ // a timeout response.
+ int send_timeout_;
+
+ // Flag indicating that the connection is paused. A paused connection will not
+ // raise new onReceive events.
+ bool paused_;
+
+ // Callback to handle the completion of a pending Receive() request.
+ ReceiveCompleteCallback receive_complete_;
+
+ // Callback to handle the completion of a pending Send() request.
+ SendCompleteCallback send_complete_;
+
+ // Closure which will trigger a receive timeout unless cancelled. Reset on
+ // initialization and after every successful Receive().
+ scoped_ptr<TimeoutTask> receive_timeout_task_;
+
+ // Write timeout closure. Reset on initialization and after every successful
+ // Send().
+ scoped_ptr<TimeoutTask> send_timeout_task_;
+
+ scoped_refptr<net::IOBuffer> receive_buffer_;
+
+ // Asynchronous I/O handler.
+ scoped_refptr<device::SerialIoHandler> io_handler_;
+};
+
+} // namespace extensions
+
+namespace mojo {
+
+template <>
+struct TypeConverter<device::serial::HostControlSignalsPtr,
+ extensions::api::serial::HostControlSignals> {
+ static device::serial::HostControlSignalsPtr Convert(
+ const extensions::api::serial::HostControlSignals& input);
+};
+
+template <>
+struct TypeConverter<device::serial::ConnectionOptionsPtr,
+ extensions::api::serial::ConnectionOptions> {
+ static device::serial::ConnectionOptionsPtr Convert(
+ const extensions::api::serial::ConnectionOptions& input);
+};
+
+} // namespace mojo
+
+#endif // EXTENSIONS_BROWSER_API_SERIAL_SERIAL_CONNECTION_H_
diff --git a/chromium/extensions/browser/api/serial/serial_event_dispatcher.cc b/chromium/extensions/browser/api/serial/serial_event_dispatcher.cc
new file mode 100644
index 00000000000..31e44b041b0
--- /dev/null
+++ b/chromium/extensions/browser/api/serial/serial_event_dispatcher.cc
@@ -0,0 +1,169 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/serial/serial_event_dispatcher.h"
+
+#include <utility>
+
+#include "base/lazy_instance.h"
+#include "extensions/browser/api/serial/serial_connection.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extensions_browser_client.h"
+
+namespace extensions {
+
+namespace api {
+
+namespace {
+
+bool ShouldPauseOnReceiveError(serial::ReceiveError error) {
+ return error == serial::RECEIVE_ERROR_DEVICE_LOST ||
+ error == serial::RECEIVE_ERROR_SYSTEM_ERROR ||
+ error == serial::RECEIVE_ERROR_DISCONNECTED ||
+ error == serial::RECEIVE_ERROR_BREAK ||
+ error == serial::RECEIVE_ERROR_FRAME_ERROR ||
+ error == serial::RECEIVE_ERROR_OVERRUN ||
+ error == serial::RECEIVE_ERROR_BUFFER_OVERFLOW ||
+ error == serial::RECEIVE_ERROR_PARITY_ERROR;
+}
+
+} // namespace
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<SerialEventDispatcher> >
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<SerialEventDispatcher>*
+SerialEventDispatcher::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+// static
+SerialEventDispatcher* SerialEventDispatcher::Get(
+ content::BrowserContext* context) {
+ return BrowserContextKeyedAPIFactory<SerialEventDispatcher>::Get(context);
+}
+
+SerialEventDispatcher::SerialEventDispatcher(content::BrowserContext* context)
+ : thread_id_(SerialConnection::kThreadId), context_(context) {
+ ApiResourceManager<SerialConnection>* manager =
+ ApiResourceManager<SerialConnection>::Get(context_);
+ DCHECK(manager) << "No serial connection manager.";
+ connections_ = manager->data_;
+}
+
+SerialEventDispatcher::~SerialEventDispatcher() {
+}
+
+SerialEventDispatcher::ReceiveParams::ReceiveParams() {
+}
+
+SerialEventDispatcher::ReceiveParams::ReceiveParams(
+ const ReceiveParams& other) = default;
+
+SerialEventDispatcher::ReceiveParams::~ReceiveParams() {
+}
+
+void SerialEventDispatcher::PollConnection(const std::string& extension_id,
+ int connection_id) {
+ DCHECK_CURRENTLY_ON(thread_id_);
+
+ ReceiveParams params;
+ params.thread_id = thread_id_;
+ params.browser_context_id = context_;
+ params.extension_id = extension_id;
+ params.connections = connections_;
+ params.connection_id = connection_id;
+
+ StartReceive(params);
+}
+
+// static
+void SerialEventDispatcher::StartReceive(const ReceiveParams& params) {
+ DCHECK_CURRENTLY_ON(params.thread_id);
+
+ SerialConnection* connection =
+ params.connections->Get(params.extension_id, params.connection_id);
+ if (!connection)
+ return;
+ DCHECK(params.extension_id == connection->owner_extension_id());
+
+ if (connection->paused())
+ return;
+
+ connection->Receive(base::Bind(&ReceiveCallback, params));
+}
+
+// static
+void SerialEventDispatcher::ReceiveCallback(const ReceiveParams& params,
+ const std::vector<char>& data,
+ serial::ReceiveError error) {
+ DCHECK_CURRENTLY_ON(params.thread_id);
+
+ // Note that an error (e.g. timeout) does not necessarily mean that no data
+ // was read, so we may fire an onReceive regardless of any error code.
+ if (data.size() > 0) {
+ serial::ReceiveInfo receive_info;
+ receive_info.connection_id = params.connection_id;
+ receive_info.data = data;
+ scoped_ptr<base::ListValue> args = serial::OnReceive::Create(receive_info);
+ scoped_ptr<extensions::Event> event(
+ new extensions::Event(extensions::events::SERIAL_ON_RECEIVE,
+ serial::OnReceive::kEventName, std::move(args)));
+ PostEvent(params, std::move(event));
+ }
+
+ if (error != serial::RECEIVE_ERROR_NONE) {
+ serial::ReceiveErrorInfo error_info;
+ error_info.connection_id = params.connection_id;
+ error_info.error = error;
+ scoped_ptr<base::ListValue> args =
+ serial::OnReceiveError::Create(error_info);
+ scoped_ptr<extensions::Event> event(new extensions::Event(
+ extensions::events::SERIAL_ON_RECEIVE_ERROR,
+ serial::OnReceiveError::kEventName, std::move(args)));
+ PostEvent(params, std::move(event));
+ if (ShouldPauseOnReceiveError(error)) {
+ SerialConnection* connection =
+ params.connections->Get(params.extension_id, params.connection_id);
+ if (connection)
+ connection->set_paused(true);
+ }
+ }
+
+ // Queue up the next read operation.
+ BrowserThread::PostTask(
+ params.thread_id, FROM_HERE, base::Bind(&StartReceive, params));
+}
+
+// static
+void SerialEventDispatcher::PostEvent(const ReceiveParams& params,
+ scoped_ptr<extensions::Event> event) {
+ DCHECK_CURRENTLY_ON(params.thread_id);
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&DispatchEvent, params.browser_context_id, params.extension_id,
+ base::Passed(std::move(event))));
+}
+
+// static
+void SerialEventDispatcher::DispatchEvent(void* browser_context_id,
+ const std::string& extension_id,
+ scoped_ptr<extensions::Event> event) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ content::BrowserContext* context =
+ reinterpret_cast<content::BrowserContext*>(browser_context_id);
+ if (!extensions::ExtensionsBrowserClient::Get()->IsValidContext(context))
+ return;
+
+ EventRouter* router = EventRouter::Get(context);
+ if (router)
+ router->DispatchEventToExtension(extension_id, std::move(event));
+}
+
+} // namespace api
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/serial/serial_event_dispatcher.h b/chromium/extensions/browser/api/serial/serial_event_dispatcher.h
new file mode 100644
index 00000000000..10188af493a
--- /dev/null
+++ b/chromium/extensions/browser/api/serial/serial_event_dispatcher.h
@@ -0,0 +1,83 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SERIAL_SERIAL_EVENT_DISPATCHER_H_
+#define EXTENSIONS_BROWSER_API_SERIAL_SERIAL_EVENT_DISPATCHER_H_
+
+#include <string>
+#include <vector>
+
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/common/api/serial.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+struct Event;
+class SerialConnection;
+
+namespace api {
+
+// Per-browser-context dispatcher for events on serial connections.
+class SerialEventDispatcher : public BrowserContextKeyedAPI {
+ public:
+ explicit SerialEventDispatcher(content::BrowserContext* context);
+ ~SerialEventDispatcher() override;
+
+ // Start receiving data and firing events for a connection.
+ void PollConnection(const std::string& extension_id, int connection_id);
+
+ static SerialEventDispatcher* Get(content::BrowserContext* context);
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<SerialEventDispatcher>*
+ GetFactoryInstance();
+
+ private:
+ typedef ApiResourceManager<SerialConnection>::ApiResourceData ConnectionData;
+ friend class BrowserContextKeyedAPIFactory<SerialEventDispatcher>;
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "SerialEventDispatcher"; }
+ static const bool kServiceHasOwnInstanceInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ struct ReceiveParams {
+ ReceiveParams();
+ ReceiveParams(const ReceiveParams& other);
+ ~ReceiveParams();
+
+ content::BrowserThread::ID thread_id;
+ void* browser_context_id;
+ std::string extension_id;
+ scoped_refptr<ConnectionData> connections;
+ int connection_id;
+ };
+
+ static void StartReceive(const ReceiveParams& params);
+
+ static void ReceiveCallback(const ReceiveParams& params,
+ const std::vector<char>& data,
+ serial::ReceiveError error);
+
+ static void PostEvent(const ReceiveParams& params,
+ scoped_ptr<extensions::Event> event);
+
+ static void DispatchEvent(void* browser_context_id,
+ const std::string& extension_id,
+ scoped_ptr<extensions::Event> event);
+
+ content::BrowserThread::ID thread_id_;
+ content::BrowserContext* const context_;
+ scoped_refptr<ConnectionData> connections_;
+};
+
+} // namespace api
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SERIAL_SERIAL_EVENT_DISPATCHER_H_
diff --git a/chromium/extensions/browser/api/serial/serial_service_factory.cc b/chromium/extensions/browser/api/serial/serial_service_factory.cc
new file mode 100644
index 00000000000..10abe15f531
--- /dev/null
+++ b/chromium/extensions/browser/api/serial/serial_service_factory.cc
@@ -0,0 +1,40 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/serial/serial_service_factory.h"
+
+#include <utility>
+
+#include "content/public/browser/browser_thread.h"
+#include "device/serial/serial_service_impl.h"
+
+namespace extensions {
+namespace {
+const base::Callback<void(
+ mojo::InterfaceRequest<device::serial::SerialService>)>*
+ g_serial_service_test_factory = nullptr;
+}
+
+void BindToSerialServiceRequest(
+ mojo::InterfaceRequest<device::serial::SerialService> request) {
+ if (g_serial_service_test_factory) {
+ g_serial_service_test_factory->Run(std::move(request));
+ return;
+ }
+ device::SerialServiceImpl::CreateOnMessageLoop(
+ content::BrowserThread::GetMessageLoopProxyForThread(
+ content::BrowserThread::FILE),
+ content::BrowserThread::GetMessageLoopProxyForThread(
+ content::BrowserThread::IO),
+ content::BrowserThread::GetMessageLoopProxyForThread(
+ content::BrowserThread::UI),
+ std::move(request));
+}
+
+void SetSerialServiceFactoryForTest(const base::Callback<
+ void(mojo::InterfaceRequest<device::serial::SerialService>)>* callback) {
+ g_serial_service_test_factory = callback;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/serial/serial_service_factory.h b/chromium/extensions/browser/api/serial/serial_service_factory.h
new file mode 100644
index 00000000000..df74c9ba531
--- /dev/null
+++ b/chromium/extensions/browser/api/serial/serial_service_factory.h
@@ -0,0 +1,22 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SERIAL_SERIAL_SERVICE_FACTORY_H_
+#define EXTENSIONS_BROWSER_API_SERIAL_SERIAL_SERVICE_FACTORY_H_
+
+#include "base/callback.h"
+#include "device/serial/serial.mojom.h"
+#include "extensions/browser/mojo/service_registration.h"
+
+namespace extensions {
+
+void BindToSerialServiceRequest(
+ mojo::InterfaceRequest<device::serial::SerialService> request);
+
+void SetSerialServiceFactoryForTest(const base::Callback<void(
+ mojo::InterfaceRequest<device::serial::SerialService> request)>* callback);
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SERIAL_SERIAL_SERVICE_FACTORY_H_
diff --git a/chromium/extensions/browser/api/socket/OWNERS b/chromium/extensions/browser/api/socket/OWNERS
new file mode 100644
index 00000000000..a6b22ec93ad
--- /dev/null
+++ b/chromium/extensions/browser/api/socket/OWNERS
@@ -0,0 +1,4 @@
+# Username must start with r
+reillyg@chromium.org
+rockot@chromium.org
+rpaquay@chromium.org
diff --git a/chromium/extensions/browser/api/socket/app_firewall_hole_manager.cc b/chromium/extensions/browser/api/socket/app_firewall_hole_manager.cc
new file mode 100644
index 00000000000..f5973734081
--- /dev/null
+++ b/chromium/extensions/browser/api/socket/app_firewall_hole_manager.cc
@@ -0,0 +1,166 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/socket/app_firewall_hole_manager.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/stl_util.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/app_window/app_window.h"
+
+using chromeos::FirewallHole;
+using content::BrowserContext;
+
+namespace extensions {
+
+namespace {
+
+class AppFirewallHoleManagerFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static AppFirewallHoleManager* GetForBrowserContext(BrowserContext* context,
+ bool create) {
+ return static_cast<AppFirewallHoleManager*>(
+ GetInstance()->GetServiceForBrowserContext(context, create));
+ }
+
+ static AppFirewallHoleManagerFactory* GetInstance() {
+ return base::Singleton<AppFirewallHoleManagerFactory>::get();
+ }
+
+ AppFirewallHoleManagerFactory()
+ : BrowserContextKeyedServiceFactory(
+ "AppFirewallHoleManager",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(AppWindowRegistry::Factory::GetInstance());
+ }
+
+ ~AppFirewallHoleManagerFactory() override {}
+
+ private:
+ // BrowserContextKeyedServiceFactory
+ KeyedService* BuildServiceInstanceFor(
+ BrowserContext* context) const override {
+ return new AppFirewallHoleManager(context);
+ }
+
+ BrowserContext* GetBrowserContextToUse(
+ BrowserContext* context) const override {
+ return context;
+ }
+};
+
+bool HasVisibleAppWindows(BrowserContext* context,
+ const std::string& extension_id) {
+ AppWindowRegistry* registry = AppWindowRegistry::Get(context);
+
+ for (const AppWindow* window : registry->GetAppWindowsForApp(extension_id)) {
+ if (!window->is_hidden()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace
+
+AppFirewallHole::~AppFirewallHole() {
+ manager_->Close(this);
+}
+
+AppFirewallHole::AppFirewallHole(AppFirewallHoleManager* manager,
+ PortType type,
+ uint16_t port,
+ const std::string& extension_id)
+ : type_(type),
+ port_(port),
+ extension_id_(extension_id),
+ manager_(manager),
+ weak_factory_(this) {
+}
+
+void AppFirewallHole::SetVisible(bool app_visible) {
+ app_visible_ = app_visible;
+ if (app_visible_) {
+ if (!firewall_hole_) {
+ FirewallHole::Open(type_, port_, "" /* all interfaces */,
+ base::Bind(&AppFirewallHole::OnFirewallHoleOpened,
+ weak_factory_.GetWeakPtr()));
+ }
+ } else {
+ firewall_hole_.reset(nullptr);
+ }
+}
+
+void AppFirewallHole::OnFirewallHoleOpened(
+ scoped_ptr<FirewallHole> firewall_hole) {
+ if (app_visible_) {
+ DCHECK(!firewall_hole_);
+ firewall_hole_ = std::move(firewall_hole);
+ }
+}
+
+AppFirewallHoleManager::AppFirewallHoleManager(BrowserContext* context)
+ : context_(context), observer_(this) {
+ observer_.Add(AppWindowRegistry::Get(context));
+}
+
+AppFirewallHoleManager::~AppFirewallHoleManager() {
+ STLDeleteValues(&tracked_holes_);
+}
+
+AppFirewallHoleManager* AppFirewallHoleManager::Get(BrowserContext* context) {
+ return AppFirewallHoleManagerFactory::GetForBrowserContext(context, true);
+}
+
+scoped_ptr<AppFirewallHole> AppFirewallHoleManager::Open(
+ AppFirewallHole::PortType type,
+ uint16_t port,
+ const std::string& extension_id) {
+ scoped_ptr<AppFirewallHole> hole(
+ new AppFirewallHole(this, type, port, extension_id));
+ tracked_holes_.insert(std::make_pair(extension_id, hole.get()));
+ if (HasVisibleAppWindows(context_, extension_id)) {
+ hole->SetVisible(true);
+ }
+ return hole;
+}
+
+void AppFirewallHoleManager::Close(AppFirewallHole* hole) {
+ auto range = tracked_holes_.equal_range(hole->extension_id());
+ for (auto iter = range.first; iter != range.second; ++iter) {
+ if (iter->second == hole) {
+ tracked_holes_.erase(iter);
+ return;
+ }
+ }
+ NOTREACHED();
+}
+
+void AppFirewallHoleManager::OnAppWindowRemoved(AppWindow* app_window) {
+ OnAppWindowHidden(app_window);
+}
+
+void AppFirewallHoleManager::OnAppWindowHidden(AppWindow* app_window) {
+ DCHECK(context_ == app_window->browser_context());
+ if (!HasVisibleAppWindows(context_, app_window->extension_id())) {
+ const auto& range = tracked_holes_.equal_range(app_window->extension_id());
+ for (auto iter = range.first; iter != range.second; ++iter) {
+ iter->second->SetVisible(false);
+ }
+ }
+}
+
+void AppFirewallHoleManager::OnAppWindowShown(AppWindow* app_window,
+ bool was_hidden) {
+ const auto& range = tracked_holes_.equal_range(app_window->extension_id());
+ for (auto iter = range.first; iter != range.second; ++iter) {
+ iter->second->SetVisible(true);
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/socket/app_firewall_hole_manager.h b/chromium/extensions/browser/api/socket/app_firewall_hole_manager.h
new file mode 100644
index 00000000000..0c125201938
--- /dev/null
+++ b/chromium/extensions/browser/api/socket/app_firewall_hole_manager.h
@@ -0,0 +1,98 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SOCKET_APP_FIREWALL_HOLE_MANAGER_H_
+#define EXTENSIONS_BROWSER_API_SOCKET_APP_FIREWALL_HOLE_MANAGER_H_
+
+#include <stdint.h>
+
+#include <map>
+
+#include "base/scoped_observer.h"
+#include "chromeos/network/firewall_hole.h"
+#include "extensions/browser/app_window/app_window_registry.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class AppFirewallHoleManager;
+
+// Represents an open port in the system firewall that will be opened and closed
+// automatically when the application has a visible window or not. The hole is
+// closed on destruction.
+class AppFirewallHole {
+ public:
+ typedef chromeos::FirewallHole::PortType PortType;
+
+ ~AppFirewallHole();
+
+ PortType type() const { return type_; }
+ uint16_t port() const { return port_; }
+ const std::string& extension_id() const { return extension_id_; }
+
+ private:
+ friend class AppFirewallHoleManager;
+
+ AppFirewallHole(AppFirewallHoleManager* manager,
+ PortType type,
+ uint16_t port,
+ const std::string& extension_id);
+
+ void SetVisible(bool app_visible);
+ void OnFirewallHoleOpened(scoped_ptr<chromeos::FirewallHole> firewall_hole);
+
+ PortType type_;
+ uint16_t port_;
+ std::string extension_id_;
+ bool app_visible_ = false;
+
+ // This object is destroyed when the AppFirewallHoleManager that owns it is
+ // destroyed and so a raw pointer is okay here.
+ AppFirewallHoleManager* manager_;
+
+ // This will hold the FirewallHole object if one is opened.
+ scoped_ptr<chromeos::FirewallHole> firewall_hole_;
+
+ base::WeakPtrFactory<AppFirewallHole> weak_factory_;
+};
+
+// Tracks ports in the system firewall opened by an application so that they
+// may be automatically opened and closed only when the application has a
+// visible window.
+class AppFirewallHoleManager : public KeyedService,
+ public AppWindowRegistry::Observer {
+ public:
+ explicit AppFirewallHoleManager(content::BrowserContext* context);
+ ~AppFirewallHoleManager() override;
+
+ // Returns the instance for a given browser context, or NULL if none.
+ static AppFirewallHoleManager* Get(content::BrowserContext* context);
+
+ // Takes ownership of the AppFirewallHole and will open a port on the system
+ // firewall if the associated application is currently visible.
+ scoped_ptr<AppFirewallHole> Open(AppFirewallHole::PortType type,
+ uint16_t port,
+ const std::string& extension_id);
+
+ private:
+ friend class AppFirewallHole;
+
+ void Close(AppFirewallHole* hole);
+
+ // AppWindowRegistry::Observer
+ void OnAppWindowRemoved(AppWindow* app_window) override;
+ void OnAppWindowHidden(AppWindow* app_window) override;
+ void OnAppWindowShown(AppWindow* app_window, bool was_hidden) override;
+
+ content::BrowserContext* context_;
+ ScopedObserver<AppWindowRegistry, AppWindowRegistry::Observer> observer_;
+ std::multimap<std::string, AppFirewallHole*> tracked_holes_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SOCKET_APP_FIREWALL_HOLE_MANAGER_H_
diff --git a/chromium/extensions/browser/api/socket/socket.cc b/chromium/extensions/browser/api/socket/socket.cc
new file mode 100644
index 00000000000..77de30190a0
--- /dev/null
+++ b/chromium/extensions/browser/api/socket/socket.cc
@@ -0,0 +1,143 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/socket/socket.h"
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "extensions/browser/api/api_resource_manager.h"
+#include "net/base/address_list.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_address.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/socket/socket.h"
+
+namespace extensions {
+
+const char kSocketTypeNotSupported[] = "Socket type does not support this API";
+
+static base::LazyInstance<
+ BrowserContextKeyedAPIFactory<ApiResourceManager<Socket> > > g_factory =
+ LAZY_INSTANCE_INITIALIZER;
+
+// static
+template <>
+BrowserContextKeyedAPIFactory<ApiResourceManager<Socket> >*
+ApiResourceManager<Socket>::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+Socket::Socket(const std::string& owner_extension_id)
+ : ApiResource(owner_extension_id), is_connected_(false) {}
+
+Socket::~Socket() {
+ // Derived destructors should make sure the socket has been closed.
+ DCHECK(!is_connected_);
+}
+
+void Socket::Write(scoped_refptr<net::IOBuffer> io_buffer,
+ int byte_count,
+ const CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ write_queue_.push(WriteRequest(io_buffer, byte_count, callback));
+ WriteData();
+}
+
+void Socket::WriteData() {
+ // IO is pending.
+ if (io_buffer_write_.get())
+ return;
+
+ WriteRequest& request = write_queue_.front();
+
+ DCHECK(request.byte_count >= request.bytes_written);
+ io_buffer_write_ = new net::WrappedIOBuffer(request.io_buffer->data() +
+ request.bytes_written);
+ int result =
+ WriteImpl(io_buffer_write_.get(),
+ request.byte_count - request.bytes_written,
+ base::Bind(&Socket::OnWriteComplete, base::Unretained(this)));
+
+ if (result != net::ERR_IO_PENDING)
+ OnWriteComplete(result);
+}
+
+void Socket::OnWriteComplete(int result) {
+ io_buffer_write_ = NULL;
+
+ WriteRequest& request = write_queue_.front();
+
+ if (result >= 0) {
+ request.bytes_written += result;
+ if (request.bytes_written < request.byte_count) {
+ WriteData();
+ return;
+ }
+ DCHECK(request.bytes_written == request.byte_count);
+ result = request.bytes_written;
+ }
+
+ request.callback.Run(result);
+ write_queue_.pop();
+
+ if (!write_queue_.empty())
+ WriteData();
+}
+
+bool Socket::SetKeepAlive(bool enable, int delay) { return false; }
+
+bool Socket::SetNoDelay(bool no_delay) { return false; }
+
+int Socket::Listen(const std::string& address,
+ uint16_t port,
+ int backlog,
+ std::string* error_msg) {
+ *error_msg = kSocketTypeNotSupported;
+ return net::ERR_FAILED;
+}
+
+void Socket::Accept(const AcceptCompletionCallback& callback) {
+ callback.Run(net::ERR_FAILED, NULL);
+}
+
+// static
+bool Socket::StringAndPortToIPEndPoint(const std::string& ip_address_str,
+ uint16_t port,
+ net::IPEndPoint* ip_end_point) {
+ DCHECK(ip_end_point);
+ net::IPAddress ip_address;
+ if (!ip_address.AssignFromIPLiteral(ip_address_str))
+ return false;
+
+ *ip_end_point = net::IPEndPoint(ip_address, port);
+ return true;
+}
+
+void Socket::IPEndPointToStringAndPort(const net::IPEndPoint& address,
+ std::string* ip_address_str,
+ uint16_t* port) {
+ DCHECK(ip_address_str);
+ DCHECK(port);
+ *ip_address_str = address.ToStringWithoutPort();
+ if (ip_address_str->empty()) {
+ *port = 0;
+ } else {
+ *port = address.port();
+ }
+}
+
+Socket::WriteRequest::WriteRequest(scoped_refptr<net::IOBuffer> io_buffer,
+ int byte_count,
+ const CompletionCallback& callback)
+ : io_buffer(io_buffer),
+ byte_count(byte_count),
+ callback(callback),
+ bytes_written(0) {}
+
+Socket::WriteRequest::WriteRequest(const WriteRequest& other) = default;
+
+Socket::WriteRequest::~WriteRequest() {}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/socket/socket.h b/chromium/extensions/browser/api/socket/socket.h
new file mode 100644
index 00000000000..fefe6bc0f20
--- /dev/null
+++ b/chromium/extensions/browser/api/socket/socket.h
@@ -0,0 +1,159 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SOCKET_SOCKET_H_
+#define EXTENSIONS_BROWSER_API_SOCKET_SOCKET_H_
+
+#include <stdint.h>
+
+#include <queue>
+#include <string>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "build/build_config.h"
+#include "extensions/browser/api/api_resource.h"
+#include "extensions/browser/api/api_resource_manager.h"
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/socket/tcp_client_socket.h"
+
+#if defined(OS_CHROMEOS)
+#include "extensions/browser/api/socket/app_firewall_hole_manager.h"
+#endif // OS_CHROMEOS
+
+namespace net {
+class AddressList;
+class IPEndPoint;
+class Socket;
+}
+
+namespace extensions {
+
+typedef base::Callback<void(int)> CompletionCallback;
+typedef base::Callback<void(int, scoped_refptr<net::IOBuffer> io_buffer)>
+ ReadCompletionCallback;
+typedef base::Callback<void(int,
+ scoped_refptr<net::IOBuffer> io_buffer,
+ const std::string&,
+ uint16_t)> RecvFromCompletionCallback;
+typedef base::Callback<void(int, scoped_ptr<net::TCPClientSocket>)>
+ AcceptCompletionCallback;
+
+// A Socket wraps a low-level socket and includes housekeeping information that
+// we need to manage it in the context of an extension.
+class Socket : public ApiResource {
+ public:
+ enum SocketType { TYPE_TCP, TYPE_UDP, TYPE_TLS };
+
+ ~Socket() override;
+
+ // The hostname of the remote host that this socket is connected to. This
+ // may be the empty string if the client does not intend to ever upgrade the
+ // socket to TLS, and thusly has not invoked set_hostname().
+ const std::string& hostname() const { return hostname_; }
+
+ // Set the hostname of the remote host that this socket is connected to.
+ // Note: This may be an IP literal. In the case of IDNs, this should be a
+ // series of U-LABELs (UTF-8), not A-LABELs. IP literals for IPv6 will be
+ // unbracketed.
+ void set_hostname(const std::string& hostname) { hostname_ = hostname; }
+
+#if defined(OS_CHROMEOS)
+ void set_firewall_hole(
+ scoped_ptr<AppFirewallHole, content::BrowserThread::DeleteOnUIThread>
+ firewall_hole) {
+ firewall_hole_ = std::move(firewall_hole);
+ }
+#endif // OS_CHROMEOS
+
+ // Note: |address| contains the resolved IP address, not the hostname of
+ // the remote endpoint. In order to upgrade this socket to TLS, callers
+ // must also supply the hostname of the endpoint via set_hostname().
+ virtual void Connect(const net::AddressList& address,
+ const CompletionCallback& callback) = 0;
+ virtual void Disconnect() = 0;
+ virtual int Bind(const std::string& address, uint16_t port) = 0;
+
+ // The |callback| will be called with the number of bytes read into the
+ // buffer, or a negative number if an error occurred.
+ virtual void Read(int count, const ReadCompletionCallback& callback) = 0;
+
+ // The |callback| will be called with |byte_count| or a negative number if an
+ // error occurred.
+ void Write(scoped_refptr<net::IOBuffer> io_buffer,
+ int byte_count,
+ const CompletionCallback& callback);
+
+ virtual void RecvFrom(int count,
+ const RecvFromCompletionCallback& callback) = 0;
+ virtual void SendTo(scoped_refptr<net::IOBuffer> io_buffer,
+ int byte_count,
+ const net::IPEndPoint& address,
+ const CompletionCallback& callback) = 0;
+
+ virtual bool SetKeepAlive(bool enable, int delay);
+ virtual bool SetNoDelay(bool no_delay);
+ virtual int Listen(const std::string& address,
+ uint16_t port,
+ int backlog,
+ std::string* error_msg);
+ virtual void Accept(const AcceptCompletionCallback& callback);
+
+ virtual bool IsConnected() = 0;
+
+ virtual bool GetPeerAddress(net::IPEndPoint* address) = 0;
+ virtual bool GetLocalAddress(net::IPEndPoint* address) = 0;
+
+ virtual SocketType GetSocketType() const = 0;
+
+ static bool StringAndPortToIPEndPoint(const std::string& ip_address_str,
+ uint16_t port,
+ net::IPEndPoint* ip_end_point);
+ static void IPEndPointToStringAndPort(const net::IPEndPoint& address,
+ std::string* ip_address_str,
+ uint16_t* port);
+
+ protected:
+ explicit Socket(const std::string& owner_extension_id_);
+
+ void WriteData();
+ virtual int WriteImpl(net::IOBuffer* io_buffer,
+ int io_buffer_size,
+ const net::CompletionCallback& callback) = 0;
+ virtual void OnWriteComplete(int result);
+
+ std::string hostname_;
+ bool is_connected_;
+
+ private:
+ friend class ApiResourceManager<Socket>;
+ static const char* service_name() { return "SocketManager"; }
+
+ struct WriteRequest {
+ WriteRequest(scoped_refptr<net::IOBuffer> io_buffer,
+ int byte_count,
+ const CompletionCallback& callback);
+ WriteRequest(const WriteRequest& other);
+ ~WriteRequest();
+ scoped_refptr<net::IOBuffer> io_buffer;
+ int byte_count;
+ CompletionCallback callback;
+ int bytes_written;
+ };
+ std::queue<WriteRequest> write_queue_;
+ scoped_refptr<net::IOBuffer> io_buffer_write_;
+
+#if defined(OS_CHROMEOS)
+ // Represents a hole punched in the system firewall for this socket.
+ scoped_ptr<AppFirewallHole, content::BrowserThread::DeleteOnUIThread>
+ firewall_hole_;
+#endif // OS_CHROMEOS
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SOCKET_SOCKET_H_
diff --git a/chromium/extensions/browser/api/socket/socket_api.cc b/chromium/extensions/browser/api/socket/socket_api.cc
new file mode 100644
index 00000000000..4b1074a0085
--- /dev/null
+++ b/chromium/extensions/browser/api/socket/socket_api.cc
@@ -0,0 +1,1075 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/socket/socket_api.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/containers/hash_tables.h"
+#include "build/build_config.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/resource_context.h"
+#include "extensions/browser/api/dns/host_resolver_wrapper.h"
+#include "extensions/browser/api/socket/socket.h"
+#include "extensions/browser/api/socket/tcp_socket.h"
+#include "extensions/browser/api/socket/tls_socket.h"
+#include "extensions/browser/api/socket/udp_socket.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/permissions/socket_permission.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/network_interfaces.h"
+#include "net/base/url_util.h"
+#include "net/log/net_log.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+
+#if defined(OS_CHROMEOS)
+#include "content/public/browser/browser_thread.h"
+#endif // OS_CHROMEOS
+
+namespace extensions {
+
+using content::BrowserThread;
+using content::SocketPermissionRequest;
+
+const char kAddressKey[] = "address";
+const char kPortKey[] = "port";
+const char kBytesWrittenKey[] = "bytesWritten";
+const char kDataKey[] = "data";
+const char kResultCodeKey[] = "resultCode";
+const char kSocketIdKey[] = "socketId";
+
+const char kSocketNotFoundError[] = "Socket not found";
+const char kDnsLookupFailedError[] = "DNS resolution failed";
+const char kPermissionError[] = "App does not have permission";
+const char kNetworkListError[] = "Network lookup failed or unsupported";
+const char kTCPSocketBindError[] =
+ "TCP socket does not support bind. For TCP server please use listen.";
+const char kMulticastSocketTypeError[] = "Only UDP socket supports multicast.";
+const char kSecureSocketTypeError[] = "Only TCP sockets are supported for TLS.";
+const char kSocketNotConnectedError[] = "Socket not connected";
+const char kWildcardAddress[] = "*";
+const uint16_t kWildcardPort = 0;
+
+#if defined(OS_CHROMEOS)
+const char kFirewallFailure[] = "Failed to open firewall port";
+#endif // OS_CHROMEOS
+
+SocketAsyncApiFunction::SocketAsyncApiFunction() {}
+
+SocketAsyncApiFunction::~SocketAsyncApiFunction() {}
+
+bool SocketAsyncApiFunction::PrePrepare() {
+ manager_ = CreateSocketResourceManager();
+ return manager_->SetBrowserContext(browser_context());
+}
+
+bool SocketAsyncApiFunction::Respond() { return error_.empty(); }
+
+scoped_ptr<SocketResourceManagerInterface>
+SocketAsyncApiFunction::CreateSocketResourceManager() {
+ return scoped_ptr<SocketResourceManagerInterface>(
+ new SocketResourceManager<Socket>());
+}
+
+int SocketAsyncApiFunction::AddSocket(Socket* socket) {
+ return manager_->Add(socket);
+}
+
+Socket* SocketAsyncApiFunction::GetSocket(int api_resource_id) {
+ return manager_->Get(extension_->id(), api_resource_id);
+}
+
+void SocketAsyncApiFunction::ReplaceSocket(int api_resource_id,
+ Socket* socket) {
+ manager_->Replace(extension_->id(), api_resource_id, socket);
+}
+
+base::hash_set<int>* SocketAsyncApiFunction::GetSocketIds() {
+ return manager_->GetResourceIds(extension_->id());
+}
+
+void SocketAsyncApiFunction::RemoveSocket(int api_resource_id) {
+ manager_->Remove(extension_->id(), api_resource_id);
+}
+
+void SocketAsyncApiFunction::OpenFirewallHole(const std::string& address,
+ int socket_id,
+ Socket* socket) {
+#if defined(OS_CHROMEOS)
+ if (!net::IsLocalhost(address)) {
+ net::IPEndPoint local_address;
+ if (!socket->GetLocalAddress(&local_address)) {
+ NOTREACHED() << "Cannot get address of recently bound socket.";
+ error_ = kFirewallFailure;
+ SetResult(new base::FundamentalValue(-1));
+ AsyncWorkCompleted();
+ return;
+ }
+
+ AppFirewallHole::PortType type = socket->GetSocketType() == Socket::TYPE_TCP
+ ? AppFirewallHole::PortType::TCP
+ : AppFirewallHole::PortType::UDP;
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&SocketAsyncApiFunction::OpenFirewallHoleOnUIThread, this,
+ type, local_address.port(), socket_id));
+ return;
+ }
+#endif
+ AsyncWorkCompleted();
+}
+
+#if defined(OS_CHROMEOS)
+
+void SocketAsyncApiFunction::OpenFirewallHoleOnUIThread(
+ AppFirewallHole::PortType type,
+ uint16_t port,
+ int socket_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ AppFirewallHoleManager* manager =
+ AppFirewallHoleManager::Get(browser_context());
+ scoped_ptr<AppFirewallHole, BrowserThread::DeleteOnUIThread> hole(
+ manager->Open(type, port, extension_id()).release());
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&SocketAsyncApiFunction::OnFirewallHoleOpened, this, socket_id,
+ base::Passed(&hole)));
+}
+
+void SocketAsyncApiFunction::OnFirewallHoleOpened(
+ int socket_id,
+ scoped_ptr<AppFirewallHole, BrowserThread::DeleteOnUIThread> hole) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ if (!hole) {
+ error_ = kFirewallFailure;
+ SetResult(new base::FundamentalValue(-1));
+ AsyncWorkCompleted();
+ return;
+ }
+
+ Socket* socket = GetSocket(socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ SetResult(new base::FundamentalValue(-1));
+ AsyncWorkCompleted();
+ return;
+ }
+
+ socket->set_firewall_hole(std::move(hole));
+ AsyncWorkCompleted();
+}
+
+#endif // OS_CHROMEOS
+
+SocketExtensionWithDnsLookupFunction::SocketExtensionWithDnsLookupFunction()
+ : resource_context_(NULL) {
+}
+
+SocketExtensionWithDnsLookupFunction::~SocketExtensionWithDnsLookupFunction() {
+}
+
+bool SocketExtensionWithDnsLookupFunction::PrePrepare() {
+ if (!SocketAsyncApiFunction::PrePrepare())
+ return false;
+ resource_context_ = browser_context()->GetResourceContext();
+ return resource_context_ != NULL;
+}
+
+void SocketExtensionWithDnsLookupFunction::StartDnsLookup(
+ const net::HostPortPair& host_port_pair) {
+ net::HostResolver* host_resolver =
+ HostResolverWrapper::GetInstance()->GetHostResolver(resource_context_);
+ DCHECK(host_resolver);
+
+ // RequestHandle is not needed because we never need to cancel requests.
+ net::HostResolver::RequestHandle request_handle;
+
+ net::HostResolver::RequestInfo request_info(host_port_pair);
+ int resolve_result = host_resolver->Resolve(
+ request_info, net::DEFAULT_PRIORITY, &addresses_,
+ base::Bind(&SocketExtensionWithDnsLookupFunction::OnDnsLookup, this),
+ &request_handle, net::BoundNetLog());
+
+ if (resolve_result != net::ERR_IO_PENDING)
+ OnDnsLookup(resolve_result);
+}
+
+void SocketExtensionWithDnsLookupFunction::OnDnsLookup(int resolve_result) {
+ if (resolve_result == net::OK) {
+ DCHECK(!addresses_.empty());
+ } else {
+ error_ = kDnsLookupFailedError;
+ }
+ AfterDnsLookup(resolve_result);
+}
+
+SocketCreateFunction::SocketCreateFunction()
+ : socket_type_(kSocketTypeInvalid) {}
+
+SocketCreateFunction::~SocketCreateFunction() {}
+
+bool SocketCreateFunction::Prepare() {
+ params_ = api::socket::Create::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ switch (params_->type) {
+ case extensions::api::socket::SOCKET_TYPE_TCP:
+ socket_type_ = kSocketTypeTCP;
+ break;
+ case extensions::api::socket::SOCKET_TYPE_UDP:
+ socket_type_ = kSocketTypeUDP;
+ break;
+ case extensions::api::socket::SOCKET_TYPE_NONE:
+ NOTREACHED();
+ break;
+ }
+
+ return true;
+}
+
+void SocketCreateFunction::Work() {
+ Socket* socket = NULL;
+ if (socket_type_ == kSocketTypeTCP) {
+ socket = new TCPSocket(extension_->id());
+ } else if (socket_type_ == kSocketTypeUDP) {
+ socket = new UDPSocket(extension_->id());
+ }
+ DCHECK(socket);
+
+ base::DictionaryValue* result = new base::DictionaryValue();
+ result->SetInteger(kSocketIdKey, AddSocket(socket));
+ SetResult(result);
+}
+
+bool SocketDestroyFunction::Prepare() {
+ EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &socket_id_));
+ return true;
+}
+
+void SocketDestroyFunction::Work() { RemoveSocket(socket_id_); }
+
+SocketConnectFunction::SocketConnectFunction()
+ : socket_id_(0), hostname_(), port_(0) {
+}
+
+SocketConnectFunction::~SocketConnectFunction() {}
+
+bool SocketConnectFunction::Prepare() {
+ EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &socket_id_));
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &hostname_));
+ int port;
+ EXTENSION_FUNCTION_VALIDATE(
+ args_->GetInteger(2, &port) && port >= 0 && port <= 65535);
+ port_ = static_cast<uint16_t>(port);
+ return true;
+}
+
+void SocketConnectFunction::AsyncWorkStart() {
+ Socket* socket = GetSocket(socket_id_);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ SetResult(new base::FundamentalValue(-1));
+ AsyncWorkCompleted();
+ return;
+ }
+
+ socket->set_hostname(hostname_);
+
+ SocketPermissionRequest::OperationType operation_type;
+ switch (socket->GetSocketType()) {
+ case Socket::TYPE_TCP:
+ operation_type = SocketPermissionRequest::TCP_CONNECT;
+ break;
+ case Socket::TYPE_UDP:
+ operation_type = SocketPermissionRequest::UDP_SEND_TO;
+ break;
+ default:
+ NOTREACHED() << "Unknown socket type.";
+ operation_type = SocketPermissionRequest::NONE;
+ break;
+ }
+
+ SocketPermission::CheckParam param(operation_type, hostname_, port_);
+ if (!extension()->permissions_data()->CheckAPIPermissionWithParam(
+ APIPermission::kSocket, &param)) {
+ error_ = kPermissionError;
+ SetResult(new base::FundamentalValue(-1));
+ AsyncWorkCompleted();
+ return;
+ }
+
+ StartDnsLookup(net::HostPortPair(hostname_, port_));
+}
+
+void SocketConnectFunction::AfterDnsLookup(int lookup_result) {
+ if (lookup_result == net::OK) {
+ StartConnect();
+ } else {
+ SetResult(new base::FundamentalValue(lookup_result));
+ AsyncWorkCompleted();
+ }
+}
+
+void SocketConnectFunction::StartConnect() {
+ Socket* socket = GetSocket(socket_id_);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ SetResult(new base::FundamentalValue(-1));
+ AsyncWorkCompleted();
+ return;
+ }
+
+ socket->Connect(addresses_,
+ base::Bind(&SocketConnectFunction::OnConnect, this));
+}
+
+void SocketConnectFunction::OnConnect(int result) {
+ SetResult(new base::FundamentalValue(result));
+ AsyncWorkCompleted();
+}
+
+bool SocketDisconnectFunction::Prepare() {
+ EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &socket_id_));
+ return true;
+}
+
+void SocketDisconnectFunction::Work() {
+ Socket* socket = GetSocket(socket_id_);
+ if (socket)
+ socket->Disconnect();
+ else
+ error_ = kSocketNotFoundError;
+ SetResult(base::Value::CreateNullValue());
+}
+
+bool SocketBindFunction::Prepare() {
+ EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &socket_id_));
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &address_));
+ int port;
+ EXTENSION_FUNCTION_VALIDATE(
+ args_->GetInteger(2, &port) && port >= 0 && port <= 65535);
+ port_ = static_cast<uint16_t>(port);
+ return true;
+}
+
+void SocketBindFunction::AsyncWorkStart() {
+ Socket* socket = GetSocket(socket_id_);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ SetResult(new base::FundamentalValue(-1));
+ AsyncWorkCompleted();
+ return;
+ }
+
+ if (socket->GetSocketType() == Socket::TYPE_TCP) {
+ error_ = kTCPSocketBindError;
+ SetResult(new base::FundamentalValue(-1));
+ AsyncWorkCompleted();
+ return;
+ }
+
+ CHECK(socket->GetSocketType() == Socket::TYPE_UDP);
+ SocketPermission::CheckParam param(SocketPermissionRequest::UDP_BIND,
+ address_, port_);
+ if (!extension()->permissions_data()->CheckAPIPermissionWithParam(
+ APIPermission::kSocket, &param)) {
+ error_ = kPermissionError;
+ SetResult(new base::FundamentalValue(-1));
+ AsyncWorkCompleted();
+ return;
+ }
+
+ int result = socket->Bind(address_, port_);
+ SetResult(new base::FundamentalValue(result));
+ if (result != net::OK) {
+ AsyncWorkCompleted();
+ return;
+ }
+
+ OpenFirewallHole(address_, socket_id_, socket);
+}
+
+SocketListenFunction::SocketListenFunction() {}
+
+SocketListenFunction::~SocketListenFunction() {}
+
+bool SocketListenFunction::Prepare() {
+ params_ = api::socket::Listen::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketListenFunction::AsyncWorkStart() {
+ Socket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ SetResult(new base::FundamentalValue(-1));
+ AsyncWorkCompleted();
+ return;
+ }
+
+ SocketPermission::CheckParam param(SocketPermissionRequest::TCP_LISTEN,
+ params_->address, params_->port);
+ if (!extension()->permissions_data()->CheckAPIPermissionWithParam(
+ APIPermission::kSocket, &param)) {
+ error_ = kPermissionError;
+ SetResult(new base::FundamentalValue(-1));
+ AsyncWorkCompleted();
+ return;
+ }
+
+ int result = socket->Listen(
+ params_->address, params_->port,
+ params_->backlog.get() ? *params_->backlog.get() : 5, &error_);
+ SetResult(new base::FundamentalValue(result));
+ if (result != net::OK) {
+ AsyncWorkCompleted();
+ return;
+ }
+
+ OpenFirewallHole(params_->address, params_->socket_id, socket);
+}
+
+SocketAcceptFunction::SocketAcceptFunction() {}
+
+SocketAcceptFunction::~SocketAcceptFunction() {}
+
+bool SocketAcceptFunction::Prepare() {
+ params_ = api::socket::Accept::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketAcceptFunction::AsyncWorkStart() {
+ Socket* socket = GetSocket(params_->socket_id);
+ if (socket) {
+ socket->Accept(base::Bind(&SocketAcceptFunction::OnAccept, this));
+ } else {
+ error_ = kSocketNotFoundError;
+ OnAccept(-1, NULL);
+ }
+}
+
+void SocketAcceptFunction::OnAccept(int result_code,
+ scoped_ptr<net::TCPClientSocket> socket) {
+ base::DictionaryValue* result = new base::DictionaryValue();
+ result->SetInteger(kResultCodeKey, result_code);
+ if (socket) {
+ Socket* client_socket =
+ new TCPSocket(std::move(socket), extension_id(), true);
+ result->SetInteger(kSocketIdKey, AddSocket(client_socket));
+ }
+ SetResult(result);
+
+ AsyncWorkCompleted();
+}
+
+SocketReadFunction::SocketReadFunction() {}
+
+SocketReadFunction::~SocketReadFunction() {}
+
+bool SocketReadFunction::Prepare() {
+ params_ = api::socket::Read::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketReadFunction::AsyncWorkStart() {
+ Socket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ OnCompleted(-1, NULL);
+ return;
+ }
+
+ socket->Read(params_->buffer_size.get() ? *params_->buffer_size.get() : 4096,
+ base::Bind(&SocketReadFunction::OnCompleted, this));
+}
+
+void SocketReadFunction::OnCompleted(int bytes_read,
+ scoped_refptr<net::IOBuffer> io_buffer) {
+ base::DictionaryValue* result = new base::DictionaryValue();
+ result->SetInteger(kResultCodeKey, bytes_read);
+ if (bytes_read > 0) {
+ result->Set(kDataKey,
+ base::BinaryValue::CreateWithCopiedBuffer(io_buffer->data(),
+ bytes_read));
+ } else {
+ result->Set(kDataKey, new base::BinaryValue());
+ }
+ SetResult(result);
+
+ AsyncWorkCompleted();
+}
+
+SocketWriteFunction::SocketWriteFunction()
+ : socket_id_(0), io_buffer_(NULL), io_buffer_size_(0) {}
+
+SocketWriteFunction::~SocketWriteFunction() {}
+
+bool SocketWriteFunction::Prepare() {
+ EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &socket_id_));
+ base::BinaryValue* data = NULL;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetBinary(1, &data));
+
+ io_buffer_size_ = data->GetSize();
+ io_buffer_ = new net::WrappedIOBuffer(data->GetBuffer());
+ return true;
+}
+
+void SocketWriteFunction::AsyncWorkStart() {
+ Socket* socket = GetSocket(socket_id_);
+
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ OnCompleted(-1);
+ return;
+ }
+
+ socket->Write(io_buffer_,
+ io_buffer_size_,
+ base::Bind(&SocketWriteFunction::OnCompleted, this));
+}
+
+void SocketWriteFunction::OnCompleted(int bytes_written) {
+ base::DictionaryValue* result = new base::DictionaryValue();
+ result->SetInteger(kBytesWrittenKey, bytes_written);
+ SetResult(result);
+
+ AsyncWorkCompleted();
+}
+
+SocketRecvFromFunction::SocketRecvFromFunction() {}
+
+SocketRecvFromFunction::~SocketRecvFromFunction() {}
+
+bool SocketRecvFromFunction::Prepare() {
+ params_ = api::socket::RecvFrom::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketRecvFromFunction::AsyncWorkStart() {
+ Socket* socket = GetSocket(params_->socket_id);
+ if (!socket || socket->GetSocketType() != Socket::TYPE_UDP) {
+ error_ = kSocketNotFoundError;
+ OnCompleted(-1, NULL, std::string(), 0);
+ return;
+ }
+
+ socket->RecvFrom(params_->buffer_size.get() ? *params_->buffer_size : 4096,
+ base::Bind(&SocketRecvFromFunction::OnCompleted, this));
+}
+
+void SocketRecvFromFunction::OnCompleted(int bytes_read,
+ scoped_refptr<net::IOBuffer> io_buffer,
+ const std::string& address,
+ uint16_t port) {
+ base::DictionaryValue* result = new base::DictionaryValue();
+ result->SetInteger(kResultCodeKey, bytes_read);
+ if (bytes_read > 0) {
+ result->Set(kDataKey,
+ base::BinaryValue::CreateWithCopiedBuffer(io_buffer->data(),
+ bytes_read));
+ } else {
+ result->Set(kDataKey, new base::BinaryValue());
+ }
+ result->SetString(kAddressKey, address);
+ result->SetInteger(kPortKey, port);
+ SetResult(result);
+
+ AsyncWorkCompleted();
+}
+
+SocketSendToFunction::SocketSendToFunction()
+ : socket_id_(0), io_buffer_(NULL), io_buffer_size_(0), port_(0) {
+}
+
+SocketSendToFunction::~SocketSendToFunction() {}
+
+bool SocketSendToFunction::Prepare() {
+ EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &socket_id_));
+ base::BinaryValue* data = NULL;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetBinary(1, &data));
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &hostname_));
+ int port;
+ EXTENSION_FUNCTION_VALIDATE(
+ args_->GetInteger(3, &port) && port >= 0 && port <= 65535);
+ port_ = static_cast<uint16_t>(port);
+
+ io_buffer_size_ = data->GetSize();
+ io_buffer_ = new net::WrappedIOBuffer(data->GetBuffer());
+ return true;
+}
+
+void SocketSendToFunction::AsyncWorkStart() {
+ Socket* socket = GetSocket(socket_id_);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ SetResult(new base::FundamentalValue(-1));
+ AsyncWorkCompleted();
+ return;
+ }
+
+ if (socket->GetSocketType() == Socket::TYPE_UDP) {
+ SocketPermission::CheckParam param(
+ SocketPermissionRequest::UDP_SEND_TO, hostname_, port_);
+ if (!extension()->permissions_data()->CheckAPIPermissionWithParam(
+ APIPermission::kSocket, &param)) {
+ error_ = kPermissionError;
+ SetResult(new base::FundamentalValue(-1));
+ AsyncWorkCompleted();
+ return;
+ }
+ }
+
+ StartDnsLookup(net::HostPortPair(hostname_, port_));
+}
+
+void SocketSendToFunction::AfterDnsLookup(int lookup_result) {
+ if (lookup_result == net::OK) {
+ StartSendTo();
+ } else {
+ SetResult(new base::FundamentalValue(lookup_result));
+ AsyncWorkCompleted();
+ }
+}
+
+void SocketSendToFunction::StartSendTo() {
+ Socket* socket = GetSocket(socket_id_);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ SetResult(new base::FundamentalValue(-1));
+ AsyncWorkCompleted();
+ return;
+ }
+
+ socket->SendTo(io_buffer_, io_buffer_size_, addresses_.front(),
+ base::Bind(&SocketSendToFunction::OnCompleted, this));
+}
+
+void SocketSendToFunction::OnCompleted(int bytes_written) {
+ base::DictionaryValue* result = new base::DictionaryValue();
+ result->SetInteger(kBytesWrittenKey, bytes_written);
+ SetResult(result);
+
+ AsyncWorkCompleted();
+}
+
+SocketSetKeepAliveFunction::SocketSetKeepAliveFunction() {}
+
+SocketSetKeepAliveFunction::~SocketSetKeepAliveFunction() {}
+
+bool SocketSetKeepAliveFunction::Prepare() {
+ params_ = api::socket::SetKeepAlive::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketSetKeepAliveFunction::Work() {
+ bool result = false;
+ Socket* socket = GetSocket(params_->socket_id);
+ if (socket) {
+ int delay = 0;
+ if (params_->delay.get())
+ delay = *params_->delay;
+ result = socket->SetKeepAlive(params_->enable, delay);
+ } else {
+ error_ = kSocketNotFoundError;
+ }
+ SetResult(new base::FundamentalValue(result));
+}
+
+SocketSetNoDelayFunction::SocketSetNoDelayFunction() {}
+
+SocketSetNoDelayFunction::~SocketSetNoDelayFunction() {}
+
+bool SocketSetNoDelayFunction::Prepare() {
+ params_ = api::socket::SetNoDelay::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketSetNoDelayFunction::Work() {
+ bool result = false;
+ Socket* socket = GetSocket(params_->socket_id);
+ if (socket)
+ result = socket->SetNoDelay(params_->no_delay);
+ else
+ error_ = kSocketNotFoundError;
+ SetResult(new base::FundamentalValue(result));
+}
+
+SocketGetInfoFunction::SocketGetInfoFunction() {}
+
+SocketGetInfoFunction::~SocketGetInfoFunction() {}
+
+bool SocketGetInfoFunction::Prepare() {
+ params_ = api::socket::GetInfo::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketGetInfoFunction::Work() {
+ Socket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ api::socket::SocketInfo info;
+ // This represents what we know about the socket, and does not call through
+ // to the system.
+ if (socket->GetSocketType() == Socket::TYPE_TCP)
+ info.socket_type = extensions::api::socket::SOCKET_TYPE_TCP;
+ else
+ info.socket_type = extensions::api::socket::SOCKET_TYPE_UDP;
+ info.connected = socket->IsConnected();
+
+ // Grab the peer address as known by the OS. This and the call below will
+ // always succeed while the socket is connected, even if the socket has
+ // been remotely closed by the peer; only reading the socket will reveal
+ // that it should be closed locally.
+ net::IPEndPoint peerAddress;
+ if (socket->GetPeerAddress(&peerAddress)) {
+ info.peer_address.reset(new std::string(peerAddress.ToStringWithoutPort()));
+ info.peer_port.reset(new int(peerAddress.port()));
+ }
+
+ // Grab the local address as known by the OS.
+ net::IPEndPoint localAddress;
+ if (socket->GetLocalAddress(&localAddress)) {
+ info.local_address.reset(
+ new std::string(localAddress.ToStringWithoutPort()));
+ info.local_port.reset(new int(localAddress.port()));
+ }
+
+ SetResult(info.ToValue().release());
+}
+
+bool SocketGetNetworkListFunction::RunAsync() {
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&SocketGetNetworkListFunction::GetNetworkListOnFileThread,
+ this));
+ return true;
+}
+
+void SocketGetNetworkListFunction::GetNetworkListOnFileThread() {
+ net::NetworkInterfaceList interface_list;
+ if (GetNetworkList(&interface_list,
+ net::INCLUDE_HOST_SCOPE_VIRTUAL_INTERFACES)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&SocketGetNetworkListFunction::SendResponseOnUIThread, this,
+ interface_list));
+ return;
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&SocketGetNetworkListFunction::HandleGetNetworkListError,
+ this));
+}
+
+void SocketGetNetworkListFunction::HandleGetNetworkListError() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ error_ = kNetworkListError;
+ SendResponse(false);
+}
+
+void SocketGetNetworkListFunction::SendResponseOnUIThread(
+ const net::NetworkInterfaceList& interface_list) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ std::vector<api::socket::NetworkInterface> create_arg;
+ create_arg.reserve(interface_list.size());
+ for (const net::NetworkInterface& interface : interface_list) {
+ api::socket::NetworkInterface info;
+ info.name = interface.name;
+ info.address = interface.address.ToString();
+ info.prefix_length = interface.prefix_length;
+ create_arg.push_back(std::move(info));
+ }
+
+ results_ = api::socket::GetNetworkList::Results::Create(create_arg);
+ SendResponse(true);
+}
+
+SocketJoinGroupFunction::SocketJoinGroupFunction() {}
+
+SocketJoinGroupFunction::~SocketJoinGroupFunction() {}
+
+bool SocketJoinGroupFunction::Prepare() {
+ params_ = api::socket::JoinGroup::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketJoinGroupFunction::Work() {
+ int result = -1;
+ Socket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ SetResult(new base::FundamentalValue(result));
+ return;
+ }
+
+ if (socket->GetSocketType() != Socket::TYPE_UDP) {
+ error_ = kMulticastSocketTypeError;
+ SetResult(new base::FundamentalValue(result));
+ return;
+ }
+
+ SocketPermission::CheckParam param(
+ SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP,
+ kWildcardAddress,
+ kWildcardPort);
+
+ if (!extension()->permissions_data()->CheckAPIPermissionWithParam(
+ APIPermission::kSocket, &param)) {
+ error_ = kPermissionError;
+ SetResult(new base::FundamentalValue(result));
+ return;
+ }
+
+ result = static_cast<UDPSocket*>(socket)->JoinGroup(params_->address);
+ if (result != 0) {
+ error_ = net::ErrorToString(result);
+ }
+ SetResult(new base::FundamentalValue(result));
+}
+
+SocketLeaveGroupFunction::SocketLeaveGroupFunction() {}
+
+SocketLeaveGroupFunction::~SocketLeaveGroupFunction() {}
+
+bool SocketLeaveGroupFunction::Prepare() {
+ params_ = api::socket::LeaveGroup::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketLeaveGroupFunction::Work() {
+ int result = -1;
+ Socket* socket = GetSocket(params_->socket_id);
+
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ SetResult(new base::FundamentalValue(result));
+ return;
+ }
+
+ if (socket->GetSocketType() != Socket::TYPE_UDP) {
+ error_ = kMulticastSocketTypeError;
+ SetResult(new base::FundamentalValue(result));
+ return;
+ }
+
+ SocketPermission::CheckParam param(
+ SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP,
+ kWildcardAddress,
+ kWildcardPort);
+ if (!extension()->permissions_data()->CheckAPIPermissionWithParam(
+ APIPermission::kSocket, &param)) {
+ error_ = kPermissionError;
+ SetResult(new base::FundamentalValue(result));
+ return;
+ }
+
+ result = static_cast<UDPSocket*>(socket)->LeaveGroup(params_->address);
+ if (result != 0)
+ error_ = net::ErrorToString(result);
+ SetResult(new base::FundamentalValue(result));
+}
+
+SocketSetMulticastTimeToLiveFunction::SocketSetMulticastTimeToLiveFunction() {}
+
+SocketSetMulticastTimeToLiveFunction::~SocketSetMulticastTimeToLiveFunction() {}
+
+bool SocketSetMulticastTimeToLiveFunction::Prepare() {
+ params_ = api::socket::SetMulticastTimeToLive::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+void SocketSetMulticastTimeToLiveFunction::Work() {
+ int result = -1;
+ Socket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ SetResult(new base::FundamentalValue(result));
+ return;
+ }
+
+ if (socket->GetSocketType() != Socket::TYPE_UDP) {
+ error_ = kMulticastSocketTypeError;
+ SetResult(new base::FundamentalValue(result));
+ return;
+ }
+
+ result =
+ static_cast<UDPSocket*>(socket)->SetMulticastTimeToLive(params_->ttl);
+ if (result != 0)
+ error_ = net::ErrorToString(result);
+ SetResult(new base::FundamentalValue(result));
+}
+
+SocketSetMulticastLoopbackModeFunction::
+ SocketSetMulticastLoopbackModeFunction() {}
+
+SocketSetMulticastLoopbackModeFunction::
+ ~SocketSetMulticastLoopbackModeFunction() {}
+
+bool SocketSetMulticastLoopbackModeFunction::Prepare() {
+ params_ = api::socket::SetMulticastLoopbackMode::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketSetMulticastLoopbackModeFunction::Work() {
+ int result = -1;
+ Socket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ SetResult(new base::FundamentalValue(result));
+ return;
+ }
+
+ if (socket->GetSocketType() != Socket::TYPE_UDP) {
+ error_ = kMulticastSocketTypeError;
+ SetResult(new base::FundamentalValue(result));
+ return;
+ }
+
+ result = static_cast<UDPSocket*>(socket)
+ ->SetMulticastLoopbackMode(params_->enabled);
+ if (result != 0)
+ error_ = net::ErrorToString(result);
+ SetResult(new base::FundamentalValue(result));
+}
+
+SocketGetJoinedGroupsFunction::SocketGetJoinedGroupsFunction() {}
+
+SocketGetJoinedGroupsFunction::~SocketGetJoinedGroupsFunction() {}
+
+bool SocketGetJoinedGroupsFunction::Prepare() {
+ params_ = api::socket::GetJoinedGroups::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketGetJoinedGroupsFunction::Work() {
+ int result = -1;
+ Socket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ SetResult(new base::FundamentalValue(result));
+ return;
+ }
+
+ if (socket->GetSocketType() != Socket::TYPE_UDP) {
+ error_ = kMulticastSocketTypeError;
+ SetResult(new base::FundamentalValue(result));
+ return;
+ }
+
+ SocketPermission::CheckParam param(
+ SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP,
+ kWildcardAddress,
+ kWildcardPort);
+ if (!extension()->permissions_data()->CheckAPIPermissionWithParam(
+ APIPermission::kSocket, &param)) {
+ error_ = kPermissionError;
+ SetResult(new base::FundamentalValue(result));
+ return;
+ }
+
+ base::ListValue* values = new base::ListValue();
+ values->AppendStrings((std::vector<std::string>&)static_cast<UDPSocket*>(
+ socket)->GetJoinedGroups());
+ SetResult(values);
+}
+
+SocketSecureFunction::SocketSecureFunction() {
+}
+
+SocketSecureFunction::~SocketSecureFunction() {
+}
+
+bool SocketSecureFunction::Prepare() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ params_ = api::socket::Secure::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ url_request_getter_ = browser_context()->GetRequestContext();
+ return true;
+}
+
+// Override the regular implementation, which would call AsyncWorkCompleted
+// immediately after Work().
+void SocketSecureFunction::AsyncWorkStart() {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+ Socket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ SetResult(new base::FundamentalValue(net::ERR_INVALID_ARGUMENT));
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ // Make sure that the socket is a TCP client socket.
+ if (socket->GetSocketType() != Socket::TYPE_TCP ||
+ static_cast<TCPSocket*>(socket)->ClientStream() == NULL) {
+ SetResult(new base::FundamentalValue(net::ERR_INVALID_ARGUMENT));
+ error_ = kSecureSocketTypeError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ if (!socket->IsConnected()) {
+ SetResult(new base::FundamentalValue(net::ERR_INVALID_ARGUMENT));
+ error_ = kSocketNotConnectedError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ net::URLRequestContext* url_request_context =
+ url_request_getter_->GetURLRequestContext();
+
+ TLSSocket::UpgradeSocketToTLS(
+ socket,
+ url_request_context->ssl_config_service(),
+ url_request_context->cert_verifier(),
+ url_request_context->transport_security_state(),
+ extension_id(),
+ params_->options.get(),
+ base::Bind(&SocketSecureFunction::TlsConnectDone, this));
+}
+
+void SocketSecureFunction::TlsConnectDone(scoped_ptr<TLSSocket> socket,
+ int result) {
+ // if an error occurred, socket MUST be NULL.
+ DCHECK(result == net::OK || socket == NULL);
+
+ if (socket && result == net::OK) {
+ ReplaceSocket(params_->socket_id, socket.release());
+ } else {
+ RemoveSocket(params_->socket_id);
+ error_ = net::ErrorToString(result);
+ }
+
+ results_ = api::socket::Secure::Results::Create(result);
+ AsyncWorkCompleted();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/socket/socket_api.h b/chromium/extensions/browser/api/socket/socket_api.h
new file mode 100644
index 00000000000..9094f6097d6
--- /dev/null
+++ b/chromium/extensions/browser/api/socket/socket_api.h
@@ -0,0 +1,560 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SOCKET_SOCKET_API_H_
+#define EXTENSIONS_BROWSER_API_SOCKET_SOCKET_API_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "build/build_config.h"
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/browser/api/async_api_function.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/common/api/socket.h"
+#include "net/base/address_list.h"
+#include "net/base/network_change_notifier.h"
+#include "net/dns/host_resolver.h"
+#include "net/socket/tcp_client_socket.h"
+
+#if defined(OS_CHROMEOS)
+#include "extensions/browser/api/socket/app_firewall_hole_manager.h"
+#endif // OS_CHROMEOS
+
+namespace content {
+class BrowserContext;
+class ResourceContext;
+}
+
+namespace net {
+class IOBuffer;
+class URLRequestContextGetter;
+class SSLClientSocket;
+}
+
+namespace extensions {
+class Socket;
+class TLSSocket;
+
+// A simple interface to ApiResourceManager<Socket> or derived class. The goal
+// of this interface is to allow Socket API functions to use distinct instances
+// of ApiResourceManager<> depending on the type of socket (old version in
+// "socket" namespace vs new version in "socket.xxx" namespaces).
+class SocketResourceManagerInterface {
+ public:
+ virtual ~SocketResourceManagerInterface() {}
+
+ virtual bool SetBrowserContext(content::BrowserContext* context) = 0;
+ virtual int Add(Socket* socket) = 0;
+ virtual Socket* Get(const std::string& extension_id, int api_resource_id) = 0;
+ virtual void Remove(const std::string& extension_id, int api_resource_id) = 0;
+ virtual void Replace(const std::string& extension_id,
+ int api_resource_id,
+ Socket* socket) = 0;
+ virtual base::hash_set<int>* GetResourceIds(
+ const std::string& extension_id) = 0;
+};
+
+// Implementation of SocketResourceManagerInterface using an
+// ApiResourceManager<T> instance (where T derives from Socket).
+template <typename T>
+class SocketResourceManager : public SocketResourceManagerInterface {
+ public:
+ SocketResourceManager() : manager_(NULL) {}
+
+ bool SetBrowserContext(content::BrowserContext* context) override {
+ manager_ = ApiResourceManager<T>::Get(context);
+ DCHECK(manager_)
+ << "There is no socket manager. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "ApiResourceManager<Socket>.";
+ return manager_ != NULL;
+ }
+
+ int Add(Socket* socket) override {
+ // Note: Cast needed here, because "T" may be a subclass of "Socket".
+ return manager_->Add(static_cast<T*>(socket));
+ }
+
+ Socket* Get(const std::string& extension_id, int api_resource_id) override {
+ return manager_->Get(extension_id, api_resource_id);
+ }
+
+ void Replace(const std::string& extension_id,
+ int api_resource_id,
+ Socket* socket) override {
+ manager_->Replace(extension_id, api_resource_id, static_cast<T*>(socket));
+ }
+
+ void Remove(const std::string& extension_id, int api_resource_id) override {
+ manager_->Remove(extension_id, api_resource_id);
+ }
+
+ base::hash_set<int>* GetResourceIds(
+ const std::string& extension_id) override {
+ return manager_->GetResourceIds(extension_id);
+ }
+
+ private:
+ ApiResourceManager<T>* manager_;
+};
+
+class SocketAsyncApiFunction : public AsyncApiFunction {
+ public:
+ SocketAsyncApiFunction();
+
+ protected:
+ ~SocketAsyncApiFunction() override;
+
+ // AsyncApiFunction:
+ bool PrePrepare() override;
+ bool Respond() override;
+
+ virtual scoped_ptr<SocketResourceManagerInterface>
+ CreateSocketResourceManager();
+
+ int AddSocket(Socket* socket);
+ Socket* GetSocket(int api_resource_id);
+ void ReplaceSocket(int api_resource_id, Socket* socket);
+ void RemoveSocket(int api_resource_id);
+ base::hash_set<int>* GetSocketIds();
+
+ // A no-op outside of Chrome OS.
+ void OpenFirewallHole(const std::string& address,
+ int socket_id,
+ Socket* socket);
+
+ private:
+#if defined(OS_CHROMEOS)
+ void OpenFirewallHoleOnUIThread(AppFirewallHole::PortType type,
+ uint16_t port,
+ int socket_id);
+ void OnFirewallHoleOpened(
+ int socket_id,
+ scoped_ptr<AppFirewallHole, content::BrowserThread::DeleteOnUIThread>
+ hole);
+#endif // OS_CHROMEOS
+
+ scoped_ptr<SocketResourceManagerInterface> manager_;
+};
+
+class SocketExtensionWithDnsLookupFunction : public SocketAsyncApiFunction {
+ protected:
+ SocketExtensionWithDnsLookupFunction();
+ ~SocketExtensionWithDnsLookupFunction() override;
+
+ // AsyncApiFunction:
+ bool PrePrepare() override;
+
+ void StartDnsLookup(const net::HostPortPair& host_port_pair);
+ virtual void AfterDnsLookup(int lookup_result) = 0;
+
+ net::AddressList addresses_;
+
+ private:
+ void OnDnsLookup(int resolve_result);
+
+ // Weak pointer to the resource context.
+ content::ResourceContext* resource_context_;
+};
+
+class SocketCreateFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.create", SOCKET_CREATE)
+
+ SocketCreateFunction();
+
+ protected:
+ ~SocketCreateFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(SocketUnitTest, Create);
+ enum SocketType { kSocketTypeInvalid = -1, kSocketTypeTCP, kSocketTypeUDP };
+
+ scoped_ptr<api::socket::Create::Params> params_;
+ SocketType socket_type_;
+};
+
+class SocketDestroyFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.destroy", SOCKET_DESTROY)
+
+ protected:
+ ~SocketDestroyFunction() override {}
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ int socket_id_;
+};
+
+class SocketConnectFunction : public SocketExtensionWithDnsLookupFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.connect", SOCKET_CONNECT)
+
+ SocketConnectFunction();
+
+ protected:
+ ~SocketConnectFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ // SocketExtensionWithDnsLookupFunction:
+ void AfterDnsLookup(int lookup_result) override;
+
+ private:
+ void StartConnect();
+ void OnConnect(int result);
+
+ int socket_id_;
+ std::string hostname_;
+ uint16_t port_;
+};
+
+class SocketDisconnectFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.disconnect", SOCKET_DISCONNECT)
+
+ protected:
+ ~SocketDisconnectFunction() override {}
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ int socket_id_;
+};
+
+class SocketBindFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.bind", SOCKET_BIND)
+
+ protected:
+ ~SocketBindFunction() override {}
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ private:
+ int socket_id_;
+ std::string address_;
+ uint16_t port_;
+};
+
+class SocketListenFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.listen", SOCKET_LISTEN)
+
+ SocketListenFunction();
+
+ protected:
+ ~SocketListenFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ private:
+ scoped_ptr<api::socket::Listen::Params> params_;
+};
+
+class SocketAcceptFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.accept", SOCKET_ACCEPT)
+
+ SocketAcceptFunction();
+
+ protected:
+ ~SocketAcceptFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ private:
+ void OnAccept(int result_code, scoped_ptr<net::TCPClientSocket> socket);
+
+ scoped_ptr<api::socket::Accept::Params> params_;
+};
+
+class SocketReadFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.read", SOCKET_READ)
+
+ SocketReadFunction();
+
+ protected:
+ ~SocketReadFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+ void OnCompleted(int result, scoped_refptr<net::IOBuffer> io_buffer);
+
+ private:
+ scoped_ptr<api::socket::Read::Params> params_;
+};
+
+class SocketWriteFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.write", SOCKET_WRITE)
+
+ SocketWriteFunction();
+
+ protected:
+ ~SocketWriteFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+ void OnCompleted(int result);
+
+ private:
+ int socket_id_;
+ scoped_refptr<net::IOBuffer> io_buffer_;
+ size_t io_buffer_size_;
+};
+
+class SocketRecvFromFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.recvFrom", SOCKET_RECVFROM)
+
+ SocketRecvFromFunction();
+
+ protected:
+ ~SocketRecvFromFunction() override;
+
+ // AsyncApiFunction
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+ void OnCompleted(int result,
+ scoped_refptr<net::IOBuffer> io_buffer,
+ const std::string& address,
+ uint16_t port);
+
+ private:
+ scoped_ptr<api::socket::RecvFrom::Params> params_;
+};
+
+class SocketSendToFunction : public SocketExtensionWithDnsLookupFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.sendTo", SOCKET_SENDTO)
+
+ SocketSendToFunction();
+
+ protected:
+ ~SocketSendToFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+ void OnCompleted(int result);
+
+ // SocketExtensionWithDnsLookupFunction:
+ void AfterDnsLookup(int lookup_result) override;
+
+ private:
+ void StartSendTo();
+
+ int socket_id_;
+ scoped_refptr<net::IOBuffer> io_buffer_;
+ size_t io_buffer_size_;
+ std::string hostname_;
+ uint16_t port_;
+};
+
+class SocketSetKeepAliveFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.setKeepAlive", SOCKET_SETKEEPALIVE)
+
+ SocketSetKeepAliveFunction();
+
+ protected:
+ ~SocketSetKeepAliveFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<api::socket::SetKeepAlive::Params> params_;
+};
+
+class SocketSetNoDelayFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.setNoDelay", SOCKET_SETNODELAY)
+
+ SocketSetNoDelayFunction();
+
+ protected:
+ ~SocketSetNoDelayFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<api::socket::SetNoDelay::Params> params_;
+};
+
+class SocketGetInfoFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.getInfo", SOCKET_GETINFO)
+
+ SocketGetInfoFunction();
+
+ protected:
+ ~SocketGetInfoFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<api::socket::GetInfo::Params> params_;
+};
+
+class SocketGetNetworkListFunction : public AsyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.getNetworkList", SOCKET_GETNETWORKLIST)
+
+ protected:
+ ~SocketGetNetworkListFunction() override {}
+ bool RunAsync() override;
+
+ private:
+ void GetNetworkListOnFileThread();
+ void HandleGetNetworkListError();
+ void SendResponseOnUIThread(const net::NetworkInterfaceList& interface_list);
+};
+
+class SocketJoinGroupFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.joinGroup", SOCKET_MULTICAST_JOIN_GROUP)
+
+ SocketJoinGroupFunction();
+
+ protected:
+ ~SocketJoinGroupFunction() override;
+
+ // AsyncApiFunction
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<api::socket::JoinGroup::Params> params_;
+};
+
+class SocketLeaveGroupFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.leaveGroup", SOCKET_MULTICAST_LEAVE_GROUP)
+
+ SocketLeaveGroupFunction();
+
+ protected:
+ ~SocketLeaveGroupFunction() override;
+
+ // AsyncApiFunction
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<api::socket::LeaveGroup::Params> params_;
+};
+
+class SocketSetMulticastTimeToLiveFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.setMulticastTimeToLive",
+ SOCKET_MULTICAST_SET_TIME_TO_LIVE)
+
+ SocketSetMulticastTimeToLiveFunction();
+
+ protected:
+ ~SocketSetMulticastTimeToLiveFunction() override;
+
+ // AsyncApiFunction
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<api::socket::SetMulticastTimeToLive::Params> params_;
+};
+
+class SocketSetMulticastLoopbackModeFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.setMulticastLoopbackMode",
+ SOCKET_MULTICAST_SET_LOOPBACK_MODE)
+
+ SocketSetMulticastLoopbackModeFunction();
+
+ protected:
+ ~SocketSetMulticastLoopbackModeFunction() override;
+
+ // AsyncApiFunction
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<api::socket::SetMulticastLoopbackMode::Params> params_;
+};
+
+class SocketGetJoinedGroupsFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.getJoinedGroups",
+ SOCKET_MULTICAST_GET_JOINED_GROUPS)
+
+ SocketGetJoinedGroupsFunction();
+
+ protected:
+ ~SocketGetJoinedGroupsFunction() override;
+
+ // AsyncApiFunction
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<api::socket::GetJoinedGroups::Params> params_;
+};
+
+class SocketSecureFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.secure", SOCKET_SECURE);
+ SocketSecureFunction();
+
+ protected:
+ ~SocketSecureFunction() override;
+
+ // AsyncApiFunction
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ private:
+ // Callback from TLSSocket::UpgradeSocketToTLS().
+ void TlsConnectDone(scoped_ptr<TLSSocket> socket, int result);
+
+ scoped_ptr<api::socket::Secure::Params> params_;
+ scoped_refptr<net::URLRequestContextGetter> url_request_getter_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketSecureFunction);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SOCKET_SOCKET_API_H_
diff --git a/chromium/extensions/browser/api/socket/socket_apitest.cc b/chromium/extensions/browser/api/socket/socket_apitest.cc
new file mode 100644
index 00000000000..edc5a84541c
--- /dev/null
+++ b/chromium/extensions/browser/api/socket/socket_apitest.cc
@@ -0,0 +1,71 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/dns/host_resolver_wrapper.h"
+#include "extensions/browser/api/dns/mock_host_resolver_creator.h"
+#include "extensions/browser/api/socket/socket_api.h"
+#include "extensions/browser/api_test_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/test_util.h"
+#include "extensions/shell/test/shell_test.h"
+
+using extensions::api_test_utils::RunFunctionAndReturnSingleResult;
+
+namespace extensions {
+
+class SocketApiTest : public AppShellTest {};
+
+IN_PROC_BROWSER_TEST_F(SocketApiTest, SocketUDPCreateGood) {
+ scoped_refptr<extensions::SocketCreateFunction> socket_create_function(
+ new extensions::SocketCreateFunction());
+ scoped_refptr<Extension> empty_extension = test_util::CreateEmptyExtension();
+
+ socket_create_function->set_extension(empty_extension.get());
+ socket_create_function->set_has_callback(true);
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnSingleResult(
+ socket_create_function.get(), "[\"udp\"]", browser_context()));
+ base::DictionaryValue* value = NULL;
+ ASSERT_TRUE(result->GetAsDictionary(&value));
+ int socket_id = -1;
+ EXPECT_TRUE(value->GetInteger("socketId", &socket_id));
+ EXPECT_GT(socket_id, 0);
+}
+
+IN_PROC_BROWSER_TEST_F(SocketApiTest, SocketTCPCreateGood) {
+ scoped_refptr<extensions::SocketCreateFunction> socket_create_function(
+ new extensions::SocketCreateFunction());
+ scoped_refptr<Extension> empty_extension = test_util::CreateEmptyExtension();
+
+ socket_create_function->set_extension(empty_extension.get());
+ socket_create_function->set_has_callback(true);
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnSingleResult(
+ socket_create_function.get(), "[\"tcp\"]", browser_context()));
+ base::DictionaryValue* value = NULL;
+ ASSERT_TRUE(result->GetAsDictionary(&value));
+ int socket_id = -1;
+ EXPECT_TRUE(value->GetInteger("socketId", &socket_id));
+ ASSERT_GT(socket_id, 0);
+}
+
+IN_PROC_BROWSER_TEST_F(SocketApiTest, GetNetworkList) {
+ scoped_refptr<extensions::SocketGetNetworkListFunction> socket_function(
+ new extensions::SocketGetNetworkListFunction());
+ scoped_refptr<Extension> empty_extension = test_util::CreateEmptyExtension();
+
+ socket_function->set_extension(empty_extension.get());
+ socket_function->set_has_callback(true);
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnSingleResult(
+ socket_function.get(), "[]", browser_context()));
+
+ // If we're invoking socket tests, all we can confirm is that we have at
+ // least one address, but not what it is.
+ base::ListValue* value = NULL;
+ ASSERT_TRUE(result->GetAsList(&value));
+ ASSERT_GT(value->GetSize(), 0U);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/socket/tcp_socket.cc b/chromium/extensions/browser/api/socket/tcp_socket.cc
new file mode 100644
index 00000000000..d4aaafbd6c2
--- /dev/null
+++ b/chromium/extensions/browser/api/socket/tcp_socket.cc
@@ -0,0 +1,363 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/socket/tcp_socket.h"
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "extensions/browser/api/api_resource.h"
+#include "net/base/address_list.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/rand_callback.h"
+#include "net/socket/tcp_client_socket.h"
+
+namespace extensions {
+
+const char kTCPSocketTypeInvalidError[] =
+ "Cannot call both connect and listen on the same socket.";
+const char kSocketListenError[] = "Could not listen on the specified port.";
+
+static base::LazyInstance<
+ BrowserContextKeyedAPIFactory<ApiResourceManager<ResumableTCPSocket> > >
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+template <>
+BrowserContextKeyedAPIFactory<ApiResourceManager<ResumableTCPSocket> >*
+ApiResourceManager<ResumableTCPSocket>::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<
+ ApiResourceManager<ResumableTCPServerSocket> > > g_server_factory =
+ LAZY_INSTANCE_INITIALIZER;
+
+// static
+template <>
+BrowserContextKeyedAPIFactory<ApiResourceManager<ResumableTCPServerSocket> >*
+ApiResourceManager<ResumableTCPServerSocket>::GetFactoryInstance() {
+ return g_server_factory.Pointer();
+}
+
+TCPSocket::TCPSocket(const std::string& owner_extension_id)
+ : Socket(owner_extension_id), socket_mode_(UNKNOWN) {}
+
+TCPSocket::TCPSocket(scoped_ptr<net::TCPClientSocket> tcp_client_socket,
+ const std::string& owner_extension_id,
+ bool is_connected)
+ : Socket(owner_extension_id),
+ socket_(std::move(tcp_client_socket)),
+ socket_mode_(CLIENT) {
+ this->is_connected_ = is_connected;
+}
+
+TCPSocket::TCPSocket(scoped_ptr<net::TCPServerSocket> tcp_server_socket,
+ const std::string& owner_extension_id)
+ : Socket(owner_extension_id),
+ server_socket_(std::move(tcp_server_socket)),
+ socket_mode_(SERVER) {}
+
+// static
+TCPSocket* TCPSocket::CreateSocketForTesting(
+ scoped_ptr<net::TCPClientSocket> tcp_client_socket,
+ const std::string& owner_extension_id,
+ bool is_connected) {
+ return new TCPSocket(std::move(tcp_client_socket), owner_extension_id,
+ is_connected);
+}
+
+// static
+TCPSocket* TCPSocket::CreateServerSocketForTesting(
+ scoped_ptr<net::TCPServerSocket> tcp_server_socket,
+ const std::string& owner_extension_id) {
+ return new TCPSocket(std::move(tcp_server_socket), owner_extension_id);
+}
+
+TCPSocket::~TCPSocket() { Disconnect(); }
+
+void TCPSocket::Connect(const net::AddressList& address,
+ const CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+
+ if (socket_mode_ == SERVER || !connect_callback_.is_null()) {
+ callback.Run(net::ERR_CONNECTION_FAILED);
+ return;
+ }
+
+ if (is_connected_) {
+ callback.Run(net::ERR_SOCKET_IS_CONNECTED);
+ return;
+ }
+
+ DCHECK(!server_socket_.get());
+ socket_mode_ = CLIENT;
+ connect_callback_ = callback;
+
+ int result = net::ERR_CONNECTION_FAILED;
+ if (!is_connected_) {
+ socket_.reset(
+ new net::TCPClientSocket(address, NULL, net::NetLog::Source()));
+ result = socket_->Connect(
+ base::Bind(&TCPSocket::OnConnectComplete, base::Unretained(this)));
+ }
+
+ if (result != net::ERR_IO_PENDING)
+ OnConnectComplete(result);
+}
+
+void TCPSocket::Disconnect() {
+ is_connected_ = false;
+ if (socket_.get())
+ socket_->Disconnect();
+ server_socket_.reset(NULL);
+ connect_callback_.Reset();
+ read_callback_.Reset();
+ accept_callback_.Reset();
+ accept_socket_.reset(NULL);
+}
+
+int TCPSocket::Bind(const std::string& address, uint16_t port) {
+ return net::ERR_FAILED;
+}
+
+void TCPSocket::Read(int count, const ReadCompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+
+ if (socket_mode_ != CLIENT) {
+ callback.Run(net::ERR_FAILED, NULL);
+ return;
+ }
+
+ if (!read_callback_.is_null() || !connect_callback_.is_null()) {
+ // It's illegal to read a net::TCPSocket while a pending Connect or Read is
+ // already in progress.
+ callback.Run(net::ERR_IO_PENDING, NULL);
+ return;
+ }
+
+ if (count < 0) {
+ callback.Run(net::ERR_INVALID_ARGUMENT, NULL);
+ return;
+ }
+
+ if (!socket_.get() || !is_connected_) {
+ callback.Run(net::ERR_SOCKET_NOT_CONNECTED, NULL);
+ return;
+ }
+
+ read_callback_ = callback;
+ scoped_refptr<net::IOBuffer> io_buffer = new net::IOBuffer(count);
+ int result = socket_->Read(
+ io_buffer.get(),
+ count,
+ base::Bind(
+ &TCPSocket::OnReadComplete, base::Unretained(this), io_buffer));
+
+ if (result != net::ERR_IO_PENDING)
+ OnReadComplete(io_buffer, result);
+}
+
+void TCPSocket::RecvFrom(int count,
+ const RecvFromCompletionCallback& callback) {
+ callback.Run(net::ERR_FAILED, NULL, NULL, 0);
+}
+
+void TCPSocket::SendTo(scoped_refptr<net::IOBuffer> io_buffer,
+ int byte_count,
+ const net::IPEndPoint& address,
+ const CompletionCallback& callback) {
+ callback.Run(net::ERR_FAILED);
+}
+
+bool TCPSocket::SetKeepAlive(bool enable, int delay) {
+ if (!socket_.get())
+ return false;
+ return socket_->SetKeepAlive(enable, delay);
+}
+
+bool TCPSocket::SetNoDelay(bool no_delay) {
+ if (!socket_.get())
+ return false;
+ return socket_->SetNoDelay(no_delay);
+}
+
+int TCPSocket::Listen(const std::string& address,
+ uint16_t port,
+ int backlog,
+ std::string* error_msg) {
+ if (socket_mode_ == CLIENT) {
+ *error_msg = kTCPSocketTypeInvalidError;
+ return net::ERR_NOT_IMPLEMENTED;
+ }
+ DCHECK(!socket_.get());
+ socket_mode_ = SERVER;
+
+ if (!server_socket_.get()) {
+ server_socket_.reset(new net::TCPServerSocket(NULL, net::NetLog::Source()));
+ }
+
+ int result = server_socket_->ListenWithAddressAndPort(address, port, backlog);
+ if (result) {
+ server_socket_.reset();
+ *error_msg = kSocketListenError;
+ }
+ return result;
+}
+
+void TCPSocket::Accept(const AcceptCompletionCallback& callback) {
+ if (socket_mode_ != SERVER || !server_socket_.get()) {
+ callback.Run(net::ERR_FAILED, NULL);
+ return;
+ }
+
+ // Limits to only 1 blocked accept call.
+ if (!accept_callback_.is_null()) {
+ callback.Run(net::ERR_FAILED, NULL);
+ return;
+ }
+
+ int result = server_socket_->Accept(
+ &accept_socket_,
+ base::Bind(&TCPSocket::OnAccept, base::Unretained(this)));
+ if (result == net::ERR_IO_PENDING) {
+ accept_callback_ = callback;
+ } else if (result == net::OK) {
+ accept_callback_ = callback;
+ this->OnAccept(result);
+ } else {
+ callback.Run(result, NULL);
+ }
+}
+
+bool TCPSocket::IsConnected() {
+ RefreshConnectionStatus();
+ return is_connected_;
+}
+
+bool TCPSocket::GetPeerAddress(net::IPEndPoint* address) {
+ if (!socket_.get())
+ return false;
+ return !socket_->GetPeerAddress(address);
+}
+
+bool TCPSocket::GetLocalAddress(net::IPEndPoint* address) {
+ if (socket_.get()) {
+ return !socket_->GetLocalAddress(address);
+ } else if (server_socket_.get()) {
+ return !server_socket_->GetLocalAddress(address);
+ } else {
+ return false;
+ }
+}
+
+Socket::SocketType TCPSocket::GetSocketType() const { return Socket::TYPE_TCP; }
+
+int TCPSocket::WriteImpl(net::IOBuffer* io_buffer,
+ int io_buffer_size,
+ const net::CompletionCallback& callback) {
+ if (socket_mode_ != CLIENT)
+ return net::ERR_FAILED;
+ else if (!socket_.get() || !IsConnected())
+ return net::ERR_SOCKET_NOT_CONNECTED;
+ else
+ return socket_->Write(io_buffer, io_buffer_size, callback);
+}
+
+void TCPSocket::RefreshConnectionStatus() {
+ if (!is_connected_)
+ return;
+ if (server_socket_)
+ return;
+ if (!socket_->IsConnected()) {
+ Disconnect();
+ }
+}
+
+void TCPSocket::OnConnectComplete(int result) {
+ DCHECK(!connect_callback_.is_null());
+ DCHECK(!is_connected_);
+ is_connected_ = result == net::OK;
+
+ // The completion callback may re-enter TCPSocket, e.g. to Read(); therefore
+ // we reset |connect_callback_| before calling it.
+ CompletionCallback connect_callback = connect_callback_;
+ connect_callback_.Reset();
+ connect_callback.Run(result);
+}
+
+void TCPSocket::OnReadComplete(scoped_refptr<net::IOBuffer> io_buffer,
+ int result) {
+ DCHECK(!read_callback_.is_null());
+ read_callback_.Run(result, io_buffer);
+ read_callback_.Reset();
+}
+
+void TCPSocket::OnAccept(int result) {
+ DCHECK(!accept_callback_.is_null());
+ if (result == net::OK && accept_socket_.get()) {
+ accept_callback_.Run(result,
+ make_scoped_ptr(static_cast<net::TCPClientSocket*>(
+ accept_socket_.release())));
+ } else {
+ accept_callback_.Run(result, NULL);
+ }
+ accept_callback_.Reset();
+}
+
+void TCPSocket::Release() {
+ // Release() is only invoked when the underlying sockets are taken (via
+ // ClientStream()) by TLSSocket. TLSSocket only supports CLIENT-mode
+ // sockets.
+ DCHECK(!server_socket_.release() && !accept_socket_.release() &&
+ socket_mode_ == CLIENT)
+ << "Called in server mode.";
+
+ // Release() doesn't disconnect the underlying sockets, but it does
+ // disconnect them from this TCPSocket.
+ is_connected_ = false;
+
+ connect_callback_.Reset();
+ read_callback_.Reset();
+ accept_callback_.Reset();
+
+ DCHECK(socket_.get()) << "Called on null client socket.";
+ ignore_result(socket_.release());
+}
+
+net::TCPClientSocket* TCPSocket::ClientStream() {
+ if (socket_mode_ != CLIENT || GetSocketType() != TYPE_TCP)
+ return NULL;
+ return socket_.get();
+}
+
+bool TCPSocket::HasPendingRead() const {
+ return !read_callback_.is_null();
+}
+
+ResumableTCPSocket::ResumableTCPSocket(const std::string& owner_extension_id)
+ : TCPSocket(owner_extension_id),
+ persistent_(false),
+ buffer_size_(0),
+ paused_(false) {}
+
+ResumableTCPSocket::ResumableTCPSocket(
+ scoped_ptr<net::TCPClientSocket> tcp_client_socket,
+ const std::string& owner_extension_id,
+ bool is_connected)
+ : TCPSocket(std::move(tcp_client_socket), owner_extension_id, is_connected),
+ persistent_(false),
+ buffer_size_(0),
+ paused_(false) {}
+
+bool ResumableTCPSocket::IsPersistent() const { return persistent(); }
+
+ResumableTCPServerSocket::ResumableTCPServerSocket(
+ const std::string& owner_extension_id)
+ : TCPSocket(owner_extension_id), persistent_(false), paused_(false) {}
+
+bool ResumableTCPServerSocket::IsPersistent() const { return persistent(); }
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/socket/tcp_socket.h b/chromium/extensions/browser/api/socket/tcp_socket.h
new file mode 100644
index 00000000000..b6f3281a9da
--- /dev/null
+++ b/chromium/extensions/browser/api/socket/tcp_socket.h
@@ -0,0 +1,186 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SOCKET_TCP_SOCKET_H_
+#define EXTENSIONS_BROWSER_API_SOCKET_TCP_SOCKET_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "extensions/browser/api/socket/socket.h"
+
+// This looks like it should be forward-declarable, but it does some tricky
+// moves that make it easier to just include it.
+#include "net/socket/tcp_client_socket.h"
+#include "net/socket/tcp_server_socket.h"
+
+namespace net {
+class Socket;
+}
+
+namespace extensions {
+
+class TCPSocket : public Socket {
+ public:
+ explicit TCPSocket(const std::string& owner_extension_id);
+ TCPSocket(scoped_ptr<net::TCPClientSocket> tcp_client_socket,
+ const std::string& owner_extension_id,
+ bool is_connected = false);
+
+ ~TCPSocket() override;
+
+ void Connect(const net::AddressList& address,
+ const CompletionCallback& callback) override;
+ void Disconnect() override;
+ int Bind(const std::string& address, uint16_t port) override;
+ void Read(int count, const ReadCompletionCallback& callback) override;
+ void RecvFrom(int count, const RecvFromCompletionCallback& callback) override;
+ void SendTo(scoped_refptr<net::IOBuffer> io_buffer,
+ int byte_count,
+ const net::IPEndPoint& address,
+ const CompletionCallback& callback) override;
+ bool SetKeepAlive(bool enable, int delay) override;
+ bool SetNoDelay(bool no_delay) override;
+ int Listen(const std::string& address,
+ uint16_t port,
+ int backlog,
+ std::string* error_msg) override;
+ void Accept(const AcceptCompletionCallback& callback) override;
+
+ bool IsConnected() override;
+
+ bool GetPeerAddress(net::IPEndPoint* address) override;
+ bool GetLocalAddress(net::IPEndPoint* address) override;
+
+ // Like Disconnect(), only Release() doesn't delete the underlying stream
+ // or attempt to close it. Useful when giving away ownership with
+ // ClientStream().
+ virtual void Release();
+
+ Socket::SocketType GetSocketType() const override;
+
+ static TCPSocket* CreateSocketForTesting(
+ scoped_ptr<net::TCPClientSocket> tcp_client_socket,
+ const std::string& owner_extension_id,
+ bool is_connected = false);
+ static TCPSocket* CreateServerSocketForTesting(
+ scoped_ptr<net::TCPServerSocket> tcp_server_socket,
+ const std::string& owner_extension_id);
+
+ // Returns NULL if GetSocketType() isn't TYPE_TCP or if the connection
+ // wasn't set up via Connect() (vs Listen()/Accept()).
+ net::TCPClientSocket* ClientStream();
+
+ // Whether a Read() has been issued, that hasn't come back yet.
+ bool HasPendingRead() const;
+
+ protected:
+ int WriteImpl(net::IOBuffer* io_buffer,
+ int io_buffer_size,
+ const net::CompletionCallback& callback) override;
+
+ private:
+ void RefreshConnectionStatus();
+ void OnConnectComplete(int result);
+ void OnReadComplete(scoped_refptr<net::IOBuffer> io_buffer, int result);
+ void OnAccept(int result);
+
+ TCPSocket(scoped_ptr<net::TCPServerSocket> tcp_server_socket,
+ const std::string& owner_extension_id);
+
+ scoped_ptr<net::TCPClientSocket> socket_;
+ scoped_ptr<net::TCPServerSocket> server_socket_;
+
+ enum SocketMode { UNKNOWN = 0, CLIENT, SERVER, };
+ SocketMode socket_mode_;
+
+ CompletionCallback connect_callback_;
+
+ ReadCompletionCallback read_callback_;
+
+ scoped_ptr<net::StreamSocket> accept_socket_;
+ AcceptCompletionCallback accept_callback_;
+};
+
+// TCP Socket instances from the "sockets.tcp" namespace. These are regular
+// socket objects with additional properties related to the behavior defined in
+// the "sockets.tcp" namespace.
+class ResumableTCPSocket : public TCPSocket {
+ public:
+ explicit ResumableTCPSocket(const std::string& owner_extension_id);
+ explicit ResumableTCPSocket(
+ scoped_ptr<net::TCPClientSocket> tcp_client_socket,
+ const std::string& owner_extension_id,
+ bool is_connected);
+
+ // Overriden from ApiResource
+ bool IsPersistent() const override;
+
+ const std::string& name() const { return name_; }
+ void set_name(const std::string& name) { name_ = name; }
+
+ bool persistent() const { return persistent_; }
+ void set_persistent(bool persistent) { persistent_ = persistent; }
+
+ int buffer_size() const { return buffer_size_; }
+ void set_buffer_size(int buffer_size) { buffer_size_ = buffer_size; }
+
+ bool paused() const { return paused_; }
+ void set_paused(bool paused) { paused_ = paused; }
+
+ private:
+ friend class ApiResourceManager<ResumableTCPSocket>;
+ static const char* service_name() { return "ResumableTCPSocketManager"; }
+
+ // Application-defined string - see sockets_tcp.idl.
+ std::string name_;
+ // Flag indicating whether the socket is left open when the application is
+ // suspended - see sockets_tcp.idl.
+ bool persistent_;
+ // The size of the buffer used to receive data - see sockets_tcp.idl.
+ int buffer_size_;
+ // Flag indicating whether a connected socket blocks its peer from sending
+ // more data - see sockets_tcp.idl.
+ bool paused_;
+};
+
+// TCP Socket instances from the "sockets.tcpServer" namespace. These are
+// regular socket objects with additional properties related to the behavior
+// defined in the "sockets.tcpServer" namespace.
+class ResumableTCPServerSocket : public TCPSocket {
+ public:
+ explicit ResumableTCPServerSocket(const std::string& owner_extension_id);
+
+ // Overriden from ApiResource
+ bool IsPersistent() const override;
+
+ const std::string& name() const { return name_; }
+ void set_name(const std::string& name) { name_ = name; }
+
+ bool persistent() const { return persistent_; }
+ void set_persistent(bool persistent) { persistent_ = persistent; }
+
+ bool paused() const { return paused_; }
+ void set_paused(bool paused) { paused_ = paused; }
+
+ private:
+ friend class ApiResourceManager<ResumableTCPServerSocket>;
+ static const char* service_name() {
+ return "ResumableTCPServerSocketManager";
+ }
+
+ // Application-defined string - see sockets_tcp_server.idl.
+ std::string name_;
+ // Flag indicating whether the socket is left open when the application is
+ // suspended - see sockets_tcp_server.idl.
+ bool persistent_;
+ // Flag indicating whether a connected socket blocks its peer from sending
+ // more data - see sockets_tcp_server.idl.
+ bool paused_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SOCKET_TCP_SOCKET_H_
diff --git a/chromium/extensions/browser/api/socket/tls_socket.cc b/chromium/extensions/browser/api/socket/tls_socket.cc
new file mode 100644
index 00000000000..1dc86345eb3
--- /dev/null
+++ b/chromium/extensions/browser/api/socket/tls_socket.cc
@@ -0,0 +1,308 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/socket/tls_socket.h"
+
+#include <utility>
+
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "extensions/browser/api/api_resource.h"
+#include "net/base/address_list.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/rand_callback.h"
+#include "net/base/url_util.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/tcp_client_socket.h"
+#include "url/url_canon.h"
+
+namespace {
+
+// Returns the SSL protocol version (as a uint16_t) represented by a string.
+// Returns 0 if the string is invalid.
+uint16_t SSLProtocolVersionFromString(const std::string& version_str) {
+ uint16_t version = 0; // Invalid.
+ if (version_str == "tls1") {
+ version = net::SSL_PROTOCOL_VERSION_TLS1;
+ } else if (version_str == "tls1.1") {
+ version = net::SSL_PROTOCOL_VERSION_TLS1_1;
+ } else if (version_str == "tls1.2") {
+ version = net::SSL_PROTOCOL_VERSION_TLS1_2;
+ }
+ return version;
+}
+
+void TlsConnectDone(scoped_ptr<net::SSLClientSocket> ssl_socket,
+ const std::string& extension_id,
+ const extensions::TLSSocket::SecureCallback& callback,
+ int result) {
+ DVLOG(1) << "Got back result " << result << " " << net::ErrorToString(result);
+
+ // No matter how the TLS connection attempt went, the underlying socket's
+ // no longer bound to the original TCPSocket. It belongs to |ssl_socket|,
+ // which is promoted here to a new API-accessible socket (via a TLSSocket
+ // wrapper), or deleted.
+ if (result != net::OK) {
+ callback.Run(scoped_ptr<extensions::TLSSocket>(), result);
+ return;
+ };
+
+ // Wrap the StreamSocket in a TLSSocket, which matches the extension socket
+ // API. Set the handle of the socket to the new value, so that it can be
+ // used for read/write/close/etc.
+ scoped_ptr<extensions::TLSSocket> wrapper(
+ new extensions::TLSSocket(std::move(ssl_socket), extension_id));
+
+ // Caller will end up deleting the prior TCPSocket, once it calls
+ // SetSocket(..,wrapper).
+ callback.Run(std::move(wrapper), result);
+}
+
+} // namespace
+
+namespace extensions {
+
+const char kTLSSocketTypeInvalidError[] =
+ "Cannot listen on a socket that is already connected.";
+
+TLSSocket::TLSSocket(scoped_ptr<net::StreamSocket> tls_socket,
+ const std::string& owner_extension_id)
+ : ResumableTCPSocket(owner_extension_id),
+ tls_socket_(std::move(tls_socket)) {}
+
+TLSSocket::~TLSSocket() {
+ Disconnect();
+}
+
+void TLSSocket::Connect(const net::AddressList& address,
+ const CompletionCallback& callback) {
+ callback.Run(net::ERR_CONNECTION_FAILED);
+}
+
+void TLSSocket::Disconnect() {
+ if (tls_socket_) {
+ tls_socket_->Disconnect();
+ tls_socket_.reset();
+ }
+}
+
+void TLSSocket::Read(int count, const ReadCompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+
+ if (!read_callback_.is_null()) {
+ callback.Run(net::ERR_IO_PENDING, NULL);
+ return;
+ }
+
+ if (count <= 0) {
+ callback.Run(net::ERR_INVALID_ARGUMENT, NULL);
+ return;
+ }
+
+ if (!tls_socket_.get()) {
+ callback.Run(net::ERR_SOCKET_NOT_CONNECTED, NULL);
+ return;
+ }
+
+ read_callback_ = callback;
+ scoped_refptr<net::IOBuffer> io_buffer(new net::IOBuffer(count));
+ // |tls_socket_| is owned by this class and the callback won't be run once
+ // |tls_socket_| is gone (as in an a call to Disconnect()). Therefore, it is
+ // safe to use base::Unretained() here.
+ int result = tls_socket_->Read(
+ io_buffer.get(),
+ count,
+ base::Bind(
+ &TLSSocket::OnReadComplete, base::Unretained(this), io_buffer));
+
+ if (result != net::ERR_IO_PENDING) {
+ OnReadComplete(io_buffer, result);
+ }
+}
+
+void TLSSocket::OnReadComplete(const scoped_refptr<net::IOBuffer>& io_buffer,
+ int result) {
+ DCHECK(!read_callback_.is_null());
+ base::ResetAndReturn(&read_callback_).Run(result, io_buffer);
+}
+
+int TLSSocket::WriteImpl(net::IOBuffer* io_buffer,
+ int io_buffer_size,
+ const net::CompletionCallback& callback) {
+ if (!IsConnected()) {
+ return net::ERR_SOCKET_NOT_CONNECTED;
+ }
+ return tls_socket_->Write(io_buffer, io_buffer_size, callback);
+}
+
+bool TLSSocket::SetKeepAlive(bool enable, int delay) {
+ return false;
+}
+
+bool TLSSocket::SetNoDelay(bool no_delay) {
+ return false;
+}
+
+int TLSSocket::Listen(const std::string& address,
+ uint16_t port,
+ int backlog,
+ std::string* error_msg) {
+ *error_msg = kTLSSocketTypeInvalidError;
+ return net::ERR_NOT_IMPLEMENTED;
+}
+
+void TLSSocket::Accept(const AcceptCompletionCallback& callback) {
+ callback.Run(net::ERR_FAILED, NULL);
+}
+
+bool TLSSocket::IsConnected() {
+ return tls_socket_.get() && tls_socket_->IsConnected();
+}
+
+bool TLSSocket::GetPeerAddress(net::IPEndPoint* address) {
+ return IsConnected() && tls_socket_->GetPeerAddress(address);
+}
+
+bool TLSSocket::GetLocalAddress(net::IPEndPoint* address) {
+ return IsConnected() && tls_socket_->GetLocalAddress(address);
+}
+
+Socket::SocketType TLSSocket::GetSocketType() const {
+ return Socket::TYPE_TLS;
+}
+
+// static
+void TLSSocket::UpgradeSocketToTLS(
+ Socket* socket,
+ scoped_refptr<net::SSLConfigService> ssl_config_service,
+ net::CertVerifier* cert_verifier,
+ net::TransportSecurityState* transport_security_state,
+ const std::string& extension_id,
+ api::socket::SecureOptions* options,
+ const TLSSocket::SecureCallback& callback) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+ TCPSocket* tcp_socket = static_cast<TCPSocket*>(socket);
+ scoped_ptr<net::SSLClientSocket> null_sock;
+
+ if (!tcp_socket || tcp_socket->GetSocketType() != Socket::TYPE_TCP ||
+ !tcp_socket->ClientStream() || !tcp_socket->IsConnected() ||
+ tcp_socket->HasPendingRead()) {
+ DVLOG(1) << "Failing before trying. socket is " << tcp_socket;
+ if (tcp_socket) {
+ DVLOG(1) << "type: " << tcp_socket->GetSocketType()
+ << ", ClientStream is " << tcp_socket->ClientStream()
+ << ", IsConnected: " << tcp_socket->IsConnected()
+ << ", HasPendingRead: " << tcp_socket->HasPendingRead();
+ }
+ TlsConnectDone(std::move(null_sock), extension_id, callback,
+ net::ERR_INVALID_ARGUMENT);
+ return;
+ }
+
+ net::IPEndPoint dest_host_port_pair;
+ if (!tcp_socket->GetPeerAddress(&dest_host_port_pair)) {
+ DVLOG(1) << "Could not get peer address.";
+ TlsConnectDone(std::move(null_sock), extension_id, callback,
+ net::ERR_INVALID_ARGUMENT);
+ return;
+ }
+
+ // Convert any U-LABELs to A-LABELs.
+ url::CanonHostInfo host_info;
+ std::string canon_host =
+ net::CanonicalizeHost(tcp_socket->hostname(), &host_info);
+
+ // Canonicalization shouldn't fail: the socket is already connected with a
+ // host, using this hostname.
+ if (host_info.family == url::CanonHostInfo::BROKEN) {
+ DVLOG(1) << "Could not canonicalize hostname";
+ TlsConnectDone(std::move(null_sock), extension_id, callback,
+ net::ERR_INVALID_ARGUMENT);
+ return;
+ }
+
+ net::HostPortPair host_and_port(canon_host, dest_host_port_pair.port());
+
+ scoped_ptr<net::ClientSocketHandle> socket_handle(
+ new net::ClientSocketHandle());
+
+ // Set the socket handle to the socket's client stream (that should be the
+ // only one active here). Then have the old socket release ownership on
+ // that client stream.
+ socket_handle->SetSocket(
+ scoped_ptr<net::StreamSocket>(tcp_socket->ClientStream()));
+ tcp_socket->Release();
+
+ DCHECK(transport_security_state);
+ net::SSLClientSocketContext context;
+ context.cert_verifier = cert_verifier;
+ context.transport_security_state = transport_security_state;
+
+ // Fill in the SSL socket params.
+ net::SSLConfig ssl_config;
+ ssl_config_service->GetSSLConfig(&ssl_config);
+ if (options && options->tls_version.get()) {
+ uint16_t version_min = 0, version_max = 0;
+ api::socket::TLSVersionConstraints* versions = options->tls_version.get();
+ if (versions->min.get()) {
+ version_min = SSLProtocolVersionFromString(*versions->min.get());
+ }
+ if (versions->max.get()) {
+ version_max = SSLProtocolVersionFromString(*versions->max.get());
+ }
+ if (version_min) {
+ ssl_config.version_min = version_min;
+ }
+ if (version_max) {
+ ssl_config.version_max = version_max;
+ }
+ }
+
+ net::ClientSocketFactory* socket_factory =
+ net::ClientSocketFactory::GetDefaultFactory();
+
+ // Create the socket.
+ scoped_ptr<net::SSLClientSocket> ssl_socket(
+ socket_factory->CreateSSLClientSocket(
+ std::move(socket_handle), host_and_port, ssl_config, context));
+
+ DVLOG(1) << "Attempting to secure a connection to " << tcp_socket->hostname()
+ << ":" << dest_host_port_pair.port();
+
+ // We need the contents of |ssl_socket| in order to invoke its Connect()
+ // method. It belongs to |ssl_socket|, and we own that until our internal
+ // callback (|connect_cb|, below) is invoked.
+ net::SSLClientSocket* saved_ssl_socket = ssl_socket.get();
+
+ // Try establish a TLS connection. Pass ownership of |ssl_socket| to
+ // TlsConnectDone, which will pass it on to |callback|. |connect_cb| below
+ // is only for UpgradeSocketToTLS use, and not be confused with the
+ // argument |callback|, which gets invoked by TlsConnectDone() after
+ // Connect() below returns.
+ base::Callback<void(int)> connect_cb(base::Bind(
+ &TlsConnectDone, base::Passed(&ssl_socket), extension_id, callback));
+ int status = saved_ssl_socket->Connect(connect_cb);
+ saved_ssl_socket = NULL;
+
+ // Connect completed synchronously, or failed.
+ if (status != net::ERR_IO_PENDING) {
+ // Note: this can't recurse -- if |socket| is already a connected
+ // TLSSocket, it will return TYPE_TLS instead of TYPE_TCP, causing
+ // UpgradeSocketToTLS() to fail with an error above. If
+ // UpgradeSocketToTLS() is called on |socket| twice, the call to
+ // Release() on |socket| above causes the additional call to
+ // fail with an error above.
+ if (status != net::OK) {
+ DVLOG(1) << "Status is not OK or IO-pending: "
+ << net::ErrorToString(status);
+ }
+ connect_cb.Run(status);
+ }
+}
+
+} // namespace extensions
+
diff --git a/chromium/extensions/browser/api/socket/tls_socket.h b/chromium/extensions/browser/api/socket/tls_socket.h
new file mode 100644
index 00000000000..2bd5ea7c392
--- /dev/null
+++ b/chromium/extensions/browser/api/socket/tls_socket.h
@@ -0,0 +1,120 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_API_SOCKET_TLS_SOCKET_H_
+#define CHROME_BROWSER_EXTENSIONS_API_SOCKET_TLS_SOCKET_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "extensions/browser/api/socket/socket.h"
+#include "extensions/browser/api/socket/socket_api.h"
+#include "extensions/browser/api/socket/tcp_socket.h"
+#include "net/ssl/ssl_config_service.h"
+
+namespace net {
+class Socket;
+class CertVerifier;
+class TransportSecurityState;
+}
+
+namespace extensions {
+
+class TLSSocket;
+
+// TLS Sockets from the chrome.socket and chrome.sockets.tcp APIs. A regular
+// TCPSocket is converted to a TLSSocket via chrome.socket.secure() or
+// chrome.sockets.tcp.secure(). The inheritance here is for interface API
+// compatibility, not for the implementation that comes with it. TLSSocket
+// does not use its superclass's socket state, so all methods are overridden
+// here to prevent any access of ResumableTCPSocket's socket state. Except
+// for the implementation of a write queue in Socket::Write() (a super-super
+// class of ResumableTCPSocket). That implementation only queues and
+// serializes invocations to WriteImpl(), implemented here, and does not
+// touch any socket state.
+class TLSSocket : public ResumableTCPSocket {
+ public:
+ typedef base::Callback<void(scoped_ptr<TLSSocket>, int)> SecureCallback;
+
+ TLSSocket(scoped_ptr<net::StreamSocket> tls_socket,
+ const std::string& owner_extension_id);
+
+ ~TLSSocket() override;
+
+ // Most of these methods either fail or forward the method call on to the
+ // inner net::StreamSocket. The remaining few do actual TLS work.
+
+ // Fails.
+ void Connect(const net::AddressList& address,
+ const CompletionCallback& callback) override;
+ // Forwards.
+ void Disconnect() override;
+
+ // Attempts to read |count| bytes of decrypted data from the TLS socket,
+ // invoking |callback| with the actual number of bytes read, or a network
+ // error code if an error occurred.
+ void Read(int count, const ReadCompletionCallback& callback) override;
+
+ // Fails. This should have been called on the TCP socket before secure() was
+ // invoked.
+ bool SetKeepAlive(bool enable, int delay) override;
+
+ // Fails. This should have been called on the TCP socket before secure() was
+ // invoked.
+ bool SetNoDelay(bool no_delay) override;
+
+ // Fails. TLSSocket is only a client.
+ int Listen(const std::string& address,
+ uint16_t port,
+ int backlog,
+ std::string* error_msg) override;
+
+ // Fails. TLSSocket is only a client.
+ void Accept(const AcceptCompletionCallback& callback) override;
+
+ // Forwards.
+ bool IsConnected() override;
+
+ // Forwards.
+ bool GetPeerAddress(net::IPEndPoint* address) override;
+ // Forwards.
+ bool GetLocalAddress(net::IPEndPoint* address) override;
+
+ // Returns TYPE_TLS.
+ SocketType GetSocketType() const override;
+
+ // Convert |socket| to a TLS socket. |socket| must be an open TCP client
+ // socket. |socket| must not have a pending read. UpgradeSocketToTLS() must
+ // be invoked in the IO thread. |callback| will always be invoked. |options|
+ // may be NULL.
+ // Note: |callback| may be synchronously invoked before
+ // UpgradeSocketToTLS() returns. Currently using the older chrome.socket
+ // version of SecureOptions, to avoid having the older API implementation
+ // depend on the newer one.
+ static void UpgradeSocketToTLS(
+ Socket* socket,
+ scoped_refptr<net::SSLConfigService> config_service,
+ net::CertVerifier* cert_verifier,
+ net::TransportSecurityState* transport_security_state,
+ const std::string& extension_id,
+ api::socket::SecureOptions* options,
+ const SecureCallback& callback);
+
+ private:
+ int WriteImpl(net::IOBuffer* io_buffer,
+ int io_buffer_size,
+ const net::CompletionCallback& callback) override;
+
+ void OnReadComplete(const scoped_refptr<net::IOBuffer>& io_buffer,
+ int result);
+
+ scoped_ptr<net::StreamSocket> tls_socket_;
+ ReadCompletionCallback read_callback_;
+};
+
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_API_SOCKET_TLS_SOCKET_H_
+
diff --git a/chromium/extensions/browser/api/socket/udp_socket.cc b/chromium/extensions/browser/api/socket/udp_socket.cc
new file mode 100644
index 00000000000..6e58ac3a2ea
--- /dev/null
+++ b/chromium/extensions/browser/api/socket/udp_socket.cc
@@ -0,0 +1,307 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/socket/udp_socket.h"
+
+#include <algorithm>
+
+#include "base/lazy_instance.h"
+#include "extensions/browser/api/api_resource.h"
+#include "net/base/ip_address.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/udp/datagram_socket.h"
+#include "net/udp/udp_client_socket.h"
+
+namespace extensions {
+
+static base::LazyInstance<
+ BrowserContextKeyedAPIFactory<ApiResourceManager<ResumableUDPSocket> > >
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+template <>
+BrowserContextKeyedAPIFactory<ApiResourceManager<ResumableUDPSocket> >*
+ApiResourceManager<ResumableUDPSocket>::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+UDPSocket::UDPSocket(const std::string& owner_extension_id)
+ : Socket(owner_extension_id),
+ socket_(net::DatagramSocket::DEFAULT_BIND,
+ net::RandIntCallback(),
+ NULL,
+ net::NetLog::Source()) {}
+
+UDPSocket::~UDPSocket() { Disconnect(); }
+
+void UDPSocket::Connect(const net::AddressList& address,
+ const CompletionCallback& callback) {
+ int result = net::ERR_CONNECTION_FAILED;
+ do {
+ if (is_connected_)
+ break;
+
+ // UDP API only connects to the first address received from DNS so
+ // connection may not work even if other addresses are reachable.
+ net::IPEndPoint ip_end_point = address.front();
+ result = socket_.Open(ip_end_point.GetFamily());
+ if (result != net::OK)
+ break;
+
+ result = socket_.Connect(ip_end_point);
+ if (result != net::OK) {
+ socket_.Close();
+ break;
+ }
+ is_connected_ = true;
+ } while (false);
+
+ callback.Run(result);
+}
+
+int UDPSocket::Bind(const std::string& address, uint16_t port) {
+ if (IsBound())
+ return net::ERR_CONNECTION_FAILED;
+
+ net::IPEndPoint ip_end_point;
+ if (!StringAndPortToIPEndPoint(address, port, &ip_end_point))
+ return net::ERR_INVALID_ARGUMENT;
+
+ int result = socket_.Open(ip_end_point.GetFamily());
+ if (result != net::OK)
+ return result;
+
+ result = socket_.Bind(ip_end_point);
+ if (result != net::OK)
+ socket_.Close();
+ return result;
+}
+
+void UDPSocket::Disconnect() {
+ is_connected_ = false;
+ socket_.Close();
+ read_callback_.Reset();
+ recv_from_callback_.Reset();
+ send_to_callback_.Reset();
+ multicast_groups_.clear();
+}
+
+void UDPSocket::Read(int count, const ReadCompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+
+ if (!read_callback_.is_null()) {
+ callback.Run(net::ERR_IO_PENDING, NULL);
+ return;
+ } else {
+ read_callback_ = callback;
+ }
+
+ int result = net::ERR_FAILED;
+ scoped_refptr<net::IOBuffer> io_buffer;
+ do {
+ if (count < 0) {
+ result = net::ERR_INVALID_ARGUMENT;
+ break;
+ }
+
+ if (!socket_.is_connected()) {
+ result = net::ERR_SOCKET_NOT_CONNECTED;
+ break;
+ }
+
+ io_buffer = new net::IOBuffer(count);
+ result = socket_.Read(
+ io_buffer.get(),
+ count,
+ base::Bind(
+ &UDPSocket::OnReadComplete, base::Unretained(this), io_buffer));
+ } while (false);
+
+ if (result != net::ERR_IO_PENDING)
+ OnReadComplete(io_buffer, result);
+}
+
+int UDPSocket::WriteImpl(net::IOBuffer* io_buffer,
+ int io_buffer_size,
+ const net::CompletionCallback& callback) {
+ if (!socket_.is_connected())
+ return net::ERR_SOCKET_NOT_CONNECTED;
+ else
+ return socket_.Write(io_buffer, io_buffer_size, callback);
+}
+
+void UDPSocket::RecvFrom(int count,
+ const RecvFromCompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+
+ if (!recv_from_callback_.is_null()) {
+ callback.Run(net::ERR_IO_PENDING, NULL, std::string(), 0);
+ return;
+ } else {
+ recv_from_callback_ = callback;
+ }
+
+ int result = net::ERR_FAILED;
+ scoped_refptr<net::IOBuffer> io_buffer;
+ scoped_refptr<IPEndPoint> address;
+ do {
+ if (count < 0) {
+ result = net::ERR_INVALID_ARGUMENT;
+ break;
+ }
+
+ if (!socket_.is_connected()) {
+ result = net::ERR_SOCKET_NOT_CONNECTED;
+ break;
+ }
+
+ io_buffer = new net::IOBuffer(count);
+ address = new IPEndPoint();
+ result = socket_.RecvFrom(io_buffer.get(),
+ count,
+ &address->data,
+ base::Bind(&UDPSocket::OnRecvFromComplete,
+ base::Unretained(this),
+ io_buffer,
+ address));
+ } while (false);
+
+ if (result != net::ERR_IO_PENDING)
+ OnRecvFromComplete(io_buffer, address, result);
+}
+
+void UDPSocket::SendTo(scoped_refptr<net::IOBuffer> io_buffer,
+ int byte_count,
+ const net::IPEndPoint& address,
+ const CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+
+ if (!send_to_callback_.is_null()) {
+ // TODO(penghuang): Put requests in a pending queue to support multiple
+ // sendTo calls.
+ callback.Run(net::ERR_IO_PENDING);
+ return;
+ } else {
+ send_to_callback_ = callback;
+ }
+
+ int result = net::ERR_FAILED;
+ do {
+ if (!socket_.is_connected()) {
+ result = net::ERR_SOCKET_NOT_CONNECTED;
+ break;
+ }
+
+ result = socket_.SendTo(
+ io_buffer.get(), byte_count, address,
+ base::Bind(&UDPSocket::OnSendToComplete, base::Unretained(this)));
+ } while (false);
+
+ if (result != net::ERR_IO_PENDING)
+ OnSendToComplete(result);
+}
+
+bool UDPSocket::IsConnected() { return is_connected_; }
+
+bool UDPSocket::GetPeerAddress(net::IPEndPoint* address) {
+ return !socket_.GetPeerAddress(address);
+}
+
+bool UDPSocket::GetLocalAddress(net::IPEndPoint* address) {
+ return !socket_.GetLocalAddress(address);
+}
+
+Socket::SocketType UDPSocket::GetSocketType() const { return Socket::TYPE_UDP; }
+
+void UDPSocket::OnReadComplete(scoped_refptr<net::IOBuffer> io_buffer,
+ int result) {
+ DCHECK(!read_callback_.is_null());
+ read_callback_.Run(result, io_buffer);
+ read_callback_.Reset();
+}
+
+void UDPSocket::OnRecvFromComplete(scoped_refptr<net::IOBuffer> io_buffer,
+ scoped_refptr<IPEndPoint> address,
+ int result) {
+ DCHECK(!recv_from_callback_.is_null());
+ std::string ip;
+ uint16_t port = 0;
+ if (result > 0 && address.get()) {
+ IPEndPointToStringAndPort(address->data, &ip, &port);
+ }
+ recv_from_callback_.Run(result, io_buffer, ip, port);
+ recv_from_callback_.Reset();
+}
+
+void UDPSocket::OnSendToComplete(int result) {
+ DCHECK(!send_to_callback_.is_null());
+ send_to_callback_.Run(result);
+ send_to_callback_.Reset();
+}
+
+bool UDPSocket::IsBound() { return socket_.is_connected(); }
+
+int UDPSocket::JoinGroup(const std::string& address) {
+ net::IPAddress ip;
+ if (!ip.AssignFromIPLiteral(address))
+ return net::ERR_ADDRESS_INVALID;
+
+ std::string normalized_address = ip.ToString();
+ std::vector<std::string>::iterator find_result = std::find(
+ multicast_groups_.begin(), multicast_groups_.end(), normalized_address);
+ if (find_result != multicast_groups_.end())
+ return net::ERR_ADDRESS_INVALID;
+
+ int rv = socket_.JoinGroup(ip);
+ if (rv == 0)
+ multicast_groups_.push_back(normalized_address);
+ return rv;
+}
+
+int UDPSocket::LeaveGroup(const std::string& address) {
+ net::IPAddress ip;
+ if (!ip.AssignFromIPLiteral(address))
+ return net::ERR_ADDRESS_INVALID;
+
+ std::string normalized_address = ip.ToString();
+ std::vector<std::string>::iterator find_result = std::find(
+ multicast_groups_.begin(), multicast_groups_.end(), normalized_address);
+ if (find_result == multicast_groups_.end())
+ return net::ERR_ADDRESS_INVALID;
+
+ int rv = socket_.LeaveGroup(ip);
+ if (rv == 0)
+ multicast_groups_.erase(find_result);
+ return rv;
+}
+
+int UDPSocket::SetMulticastTimeToLive(int ttl) {
+ return socket_.SetMulticastTimeToLive(ttl);
+}
+
+int UDPSocket::SetMulticastLoopbackMode(bool loopback) {
+ return socket_.SetMulticastLoopbackMode(loopback);
+}
+
+int UDPSocket::SetBroadcast(bool enabled) {
+ if (!socket_.is_connected()) {
+ return net::ERR_SOCKET_NOT_CONNECTED;
+ }
+ return socket_.SetBroadcast(enabled);
+}
+
+const std::vector<std::string>& UDPSocket::GetJoinedGroups() const {
+ return multicast_groups_;
+}
+
+ResumableUDPSocket::ResumableUDPSocket(const std::string& owner_extension_id)
+ : UDPSocket(owner_extension_id),
+ persistent_(false),
+ buffer_size_(0),
+ paused_(false) {}
+
+bool ResumableUDPSocket::IsPersistent() const { return persistent(); }
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/socket/udp_socket.h b/chromium/extensions/browser/api/socket/udp_socket.h
new file mode 100644
index 00000000000..17a4117441c
--- /dev/null
+++ b/chromium/extensions/browser/api/socket/udp_socket.h
@@ -0,0 +1,118 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SOCKET_UDP_SOCKET_H_
+#define EXTENSIONS_BROWSER_API_SOCKET_UDP_SOCKET_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "extensions/browser/api/socket/socket.h"
+#include "net/udp/udp_socket.h"
+
+namespace extensions {
+
+class UDPSocket : public Socket {
+ public:
+ explicit UDPSocket(const std::string& owner_extension_id);
+ ~UDPSocket() override;
+
+ void Connect(const net::AddressList& address,
+ const CompletionCallback& callback) override;
+ void Disconnect() override;
+ int Bind(const std::string& address, uint16_t port) override;
+ void Read(int count, const ReadCompletionCallback& callback) override;
+ void RecvFrom(int count, const RecvFromCompletionCallback& callback) override;
+ void SendTo(scoped_refptr<net::IOBuffer> io_buffer,
+ int byte_count,
+ const net::IPEndPoint& address,
+ const CompletionCallback& callback) override;
+
+ bool IsConnected() override;
+
+ bool GetPeerAddress(net::IPEndPoint* address) override;
+ bool GetLocalAddress(net::IPEndPoint* address) override;
+ Socket::SocketType GetSocketType() const override;
+
+ bool IsBound();
+
+ int JoinGroup(const std::string& address);
+ int LeaveGroup(const std::string& address);
+
+ int SetMulticastTimeToLive(int ttl);
+ int SetMulticastLoopbackMode(bool loopback);
+
+ int SetBroadcast(bool enabled);
+
+ const std::vector<std::string>& GetJoinedGroups() const;
+
+ protected:
+ int WriteImpl(net::IOBuffer* io_buffer,
+ int io_buffer_size,
+ const net::CompletionCallback& callback) override;
+
+ private:
+ // Make net::IPEndPoint can be refcounted
+ typedef base::RefCountedData<net::IPEndPoint> IPEndPoint;
+
+ void OnReadComplete(scoped_refptr<net::IOBuffer> io_buffer, int result);
+ void OnRecvFromComplete(scoped_refptr<net::IOBuffer> io_buffer,
+ scoped_refptr<IPEndPoint> address,
+ int result);
+ void OnSendToComplete(int result);
+
+ net::UDPSocket socket_;
+
+ ReadCompletionCallback read_callback_;
+
+ RecvFromCompletionCallback recv_from_callback_;
+
+ CompletionCallback send_to_callback_;
+
+ std::vector<std::string> multicast_groups_;
+};
+
+// UDP Socket instances from the "sockets.udp" namespace. These are regular
+// socket objects with additional properties related to the behavior defined in
+// the "sockets.udp" namespace.
+class ResumableUDPSocket : public UDPSocket {
+ public:
+ explicit ResumableUDPSocket(const std::string& owner_extension_id);
+
+ // Overriden from ApiResource
+ bool IsPersistent() const override;
+
+ const std::string& name() const { return name_; }
+ void set_name(const std::string& name) { name_ = name; }
+
+ bool persistent() const { return persistent_; }
+ void set_persistent(bool persistent) { persistent_ = persistent; }
+
+ int buffer_size() const { return buffer_size_; }
+ void set_buffer_size(int buffer_size) { buffer_size_ = buffer_size; }
+
+ bool paused() const { return paused_; }
+ void set_paused(bool paused) { paused_ = paused; }
+
+ private:
+ friend class ApiResourceManager<ResumableUDPSocket>;
+ static const char* service_name() { return "ResumableUDPSocketManager"; }
+
+ // Application-defined string - see sockets_udp.idl.
+ std::string name_;
+ // Flag indicating whether the socket is left open when the application is
+ // suspended - see sockets_udp.idl.
+ bool persistent_;
+ // The size of the buffer used to receive data - see sockets_udp.idl.
+ int buffer_size_;
+ // Flag indicating whether a connected socket blocks its peer from sending
+ // more data - see sockets_udp.idl.
+ bool paused_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SOCKET_UDP_SOCKET_H_
diff --git a/chromium/extensions/browser/api/sockets_tcp/OWNERS b/chromium/extensions/browser/api/sockets_tcp/OWNERS
new file mode 100644
index 00000000000..3e30c826edc
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_tcp/OWNERS
@@ -0,0 +1 @@
+rpaquay@chromium.org
diff --git a/chromium/extensions/browser/api/sockets_tcp/sockets_tcp_api.cc b/chromium/extensions/browser/api/sockets_tcp/sockets_tcp_api.cc
new file mode 100644
index 00000000000..be36153cbbc
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_tcp/sockets_tcp_api.cc
@@ -0,0 +1,542 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/sockets_tcp/sockets_tcp_api.h"
+
+#include "content/public/browser/browser_context.h"
+#include "content/public/common/socket_permission_request.h"
+#include "extensions/browser/api/socket/tcp_socket.h"
+#include "extensions/browser/api/socket/tls_socket.h"
+#include "extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.h"
+#include "extensions/common/api/sockets/sockets_manifest_data.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+
+using extensions::ResumableTCPSocket;
+using extensions::api::sockets_tcp::SocketInfo;
+using extensions::api::sockets_tcp::SocketProperties;
+
+namespace {
+
+const char kSocketNotFoundError[] = "Socket not found";
+const char kPermissionError[] = "Does not have permission";
+const char kInvalidSocketStateError[] =
+ "Socket must be a connected client TCP socket.";
+const char kSocketNotConnectedError[] = "Socket not connected";
+
+SocketInfo CreateSocketInfo(int socket_id, ResumableTCPSocket* socket) {
+ SocketInfo socket_info;
+ // This represents what we know about the socket, and does not call through
+ // to the system.
+ socket_info.socket_id = socket_id;
+ if (!socket->name().empty()) {
+ socket_info.name.reset(new std::string(socket->name()));
+ }
+ socket_info.persistent = socket->persistent();
+ if (socket->buffer_size() > 0) {
+ socket_info.buffer_size.reset(new int(socket->buffer_size()));
+ }
+ socket_info.paused = socket->paused();
+ socket_info.connected = socket->IsConnected();
+
+ // Grab the local address as known by the OS.
+ net::IPEndPoint localAddress;
+ if (socket->GetLocalAddress(&localAddress)) {
+ socket_info.local_address.reset(
+ new std::string(localAddress.ToStringWithoutPort()));
+ socket_info.local_port.reset(new int(localAddress.port()));
+ }
+
+ // Grab the peer address as known by the OS. This and the call below will
+ // always succeed while the socket is connected, even if the socket has
+ // been remotely closed by the peer; only reading the socket will reveal
+ // that it should be closed locally.
+ net::IPEndPoint peerAddress;
+ if (socket->GetPeerAddress(&peerAddress)) {
+ socket_info.peer_address.reset(
+ new std::string(peerAddress.ToStringWithoutPort()));
+ socket_info.peer_port.reset(new int(peerAddress.port()));
+ }
+
+ return socket_info;
+}
+
+void SetSocketProperties(ResumableTCPSocket* socket,
+ SocketProperties* properties) {
+ if (properties->name.get()) {
+ socket->set_name(*properties->name.get());
+ }
+ if (properties->persistent.get()) {
+ socket->set_persistent(*properties->persistent.get());
+ }
+ if (properties->buffer_size.get()) {
+ // buffer size is validated when issuing the actual Recv operation
+ // on the socket.
+ socket->set_buffer_size(*properties->buffer_size.get());
+ }
+}
+
+} // namespace
+
+namespace extensions {
+namespace api {
+
+using content::SocketPermissionRequest;
+
+TCPSocketAsyncApiFunction::~TCPSocketAsyncApiFunction() {}
+
+scoped_ptr<SocketResourceManagerInterface>
+TCPSocketAsyncApiFunction::CreateSocketResourceManager() {
+ return scoped_ptr<SocketResourceManagerInterface>(
+ new SocketResourceManager<ResumableTCPSocket>());
+}
+
+ResumableTCPSocket* TCPSocketAsyncApiFunction::GetTcpSocket(int socket_id) {
+ return static_cast<ResumableTCPSocket*>(GetSocket(socket_id));
+}
+
+TCPSocketExtensionWithDnsLookupFunction::
+ ~TCPSocketExtensionWithDnsLookupFunction() {}
+
+scoped_ptr<SocketResourceManagerInterface>
+TCPSocketExtensionWithDnsLookupFunction::CreateSocketResourceManager() {
+ return scoped_ptr<SocketResourceManagerInterface>(
+ new SocketResourceManager<ResumableTCPSocket>());
+}
+
+ResumableTCPSocket* TCPSocketExtensionWithDnsLookupFunction::GetTcpSocket(
+ int socket_id) {
+ return static_cast<ResumableTCPSocket*>(GetSocket(socket_id));
+}
+
+SocketsTcpCreateFunction::SocketsTcpCreateFunction() {}
+
+SocketsTcpCreateFunction::~SocketsTcpCreateFunction() {}
+
+bool SocketsTcpCreateFunction::Prepare() {
+ params_ = sockets_tcp::Create::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsTcpCreateFunction::Work() {
+ ResumableTCPSocket* socket = new ResumableTCPSocket(extension_->id());
+
+ sockets_tcp::SocketProperties* properties = params_.get()->properties.get();
+ if (properties) {
+ SetSocketProperties(socket, properties);
+ }
+
+ sockets_tcp::CreateInfo create_info;
+ create_info.socket_id = AddSocket(socket);
+ results_ = sockets_tcp::Create::Results::Create(create_info);
+}
+
+SocketsTcpUpdateFunction::SocketsTcpUpdateFunction() {}
+
+SocketsTcpUpdateFunction::~SocketsTcpUpdateFunction() {}
+
+bool SocketsTcpUpdateFunction::Prepare() {
+ params_ = sockets_tcp::Update::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsTcpUpdateFunction::Work() {
+ ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ SetSocketProperties(socket, &params_.get()->properties);
+ results_ = sockets_tcp::Update::Results::Create();
+}
+
+SocketsTcpSetPausedFunction::SocketsTcpSetPausedFunction()
+ : socket_event_dispatcher_(NULL) {}
+
+SocketsTcpSetPausedFunction::~SocketsTcpSetPausedFunction() {}
+
+bool SocketsTcpSetPausedFunction::Prepare() {
+ params_ = api::sockets_tcp::SetPaused::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ socket_event_dispatcher_ = TCPSocketEventDispatcher::Get(browser_context());
+ DCHECK(socket_event_dispatcher_)
+ << "There is no socket event dispatcher. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "TCPSocketEventDispatcher.";
+ return socket_event_dispatcher_ != NULL;
+}
+
+void SocketsTcpSetPausedFunction::Work() {
+ ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ if (socket->paused() != params_->paused) {
+ socket->set_paused(params_->paused);
+ if (socket->IsConnected() && !params_->paused) {
+ socket_event_dispatcher_->OnSocketResume(extension_->id(),
+ params_->socket_id);
+ }
+ }
+
+ results_ = sockets_tcp::SetPaused::Results::Create();
+}
+
+SocketsTcpSetKeepAliveFunction::SocketsTcpSetKeepAliveFunction() {}
+
+SocketsTcpSetKeepAliveFunction::~SocketsTcpSetKeepAliveFunction() {}
+
+bool SocketsTcpSetKeepAliveFunction::Prepare() {
+ params_ = api::sockets_tcp::SetKeepAlive::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsTcpSetKeepAliveFunction::Work() {
+ ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ int delay = params_->delay ? *params_->delay.get() : 0;
+
+ bool success = socket->SetKeepAlive(params_->enable, delay);
+ int net_result = (success ? net::OK : net::ERR_FAILED);
+ if (net_result != net::OK)
+ error_ = net::ErrorToString(net_result);
+ results_ = sockets_tcp::SetKeepAlive::Results::Create(net_result);
+}
+
+SocketsTcpSetNoDelayFunction::SocketsTcpSetNoDelayFunction() {}
+
+SocketsTcpSetNoDelayFunction::~SocketsTcpSetNoDelayFunction() {}
+
+bool SocketsTcpSetNoDelayFunction::Prepare() {
+ params_ = api::sockets_tcp::SetNoDelay::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsTcpSetNoDelayFunction::Work() {
+ ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ bool success = socket->SetNoDelay(params_->no_delay);
+ int net_result = (success ? net::OK : net::ERR_FAILED);
+ if (net_result != net::OK)
+ error_ = net::ErrorToString(net_result);
+ results_ = sockets_tcp::SetNoDelay::Results::Create(net_result);
+}
+
+SocketsTcpConnectFunction::SocketsTcpConnectFunction()
+ : socket_event_dispatcher_(NULL) {}
+
+SocketsTcpConnectFunction::~SocketsTcpConnectFunction() {}
+
+bool SocketsTcpConnectFunction::Prepare() {
+ params_ = sockets_tcp::Connect::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ socket_event_dispatcher_ = TCPSocketEventDispatcher::Get(browser_context());
+ DCHECK(socket_event_dispatcher_)
+ << "There is no socket event dispatcher. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "TCPSocketEventDispatcher.";
+ return socket_event_dispatcher_ != NULL;
+}
+
+void SocketsTcpConnectFunction::AsyncWorkStart() {
+ ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ socket->set_hostname(params_->peer_address);
+
+ content::SocketPermissionRequest param(SocketPermissionRequest::TCP_CONNECT,
+ params_->peer_address,
+ params_->peer_port);
+ if (!SocketsManifestData::CheckRequest(extension(), param)) {
+ error_ = kPermissionError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ StartDnsLookup(net::HostPortPair(params_->peer_address, params_->peer_port));
+}
+
+void SocketsTcpConnectFunction::AfterDnsLookup(int lookup_result) {
+ if (lookup_result == net::OK) {
+ StartConnect();
+ } else {
+ OnCompleted(lookup_result);
+ }
+}
+
+void SocketsTcpConnectFunction::StartConnect() {
+ ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ socket->Connect(addresses_,
+ base::Bind(&SocketsTcpConnectFunction::OnCompleted, this));
+}
+
+void SocketsTcpConnectFunction::OnCompleted(int net_result) {
+ if (net_result == net::OK) {
+ socket_event_dispatcher_->OnSocketConnect(extension_->id(),
+ params_->socket_id);
+ }
+
+ if (net_result != net::OK)
+ error_ = net::ErrorToString(net_result);
+ results_ = sockets_tcp::Connect::Results::Create(net_result);
+ AsyncWorkCompleted();
+}
+
+SocketsTcpDisconnectFunction::SocketsTcpDisconnectFunction() {}
+
+SocketsTcpDisconnectFunction::~SocketsTcpDisconnectFunction() {}
+
+bool SocketsTcpDisconnectFunction::Prepare() {
+ params_ = sockets_tcp::Disconnect::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsTcpDisconnectFunction::Work() {
+ ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ socket->Disconnect();
+ results_ = sockets_tcp::Disconnect::Results::Create();
+}
+
+SocketsTcpSendFunction::SocketsTcpSendFunction() : io_buffer_size_(0) {}
+
+SocketsTcpSendFunction::~SocketsTcpSendFunction() {}
+
+bool SocketsTcpSendFunction::Prepare() {
+ params_ = sockets_tcp::Send::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ io_buffer_size_ = params_->data.size();
+ io_buffer_ = new net::WrappedIOBuffer(params_->data.data());
+ return true;
+}
+
+void SocketsTcpSendFunction::AsyncWorkStart() {
+ ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ socket->Write(io_buffer_,
+ io_buffer_size_,
+ base::Bind(&SocketsTcpSendFunction::OnCompleted, this));
+}
+
+void SocketsTcpSendFunction::OnCompleted(int net_result) {
+ if (net_result >= net::OK) {
+ SetSendResult(net::OK, net_result);
+ } else {
+ SetSendResult(net_result, -1);
+ }
+}
+
+void SocketsTcpSendFunction::SetSendResult(int net_result, int bytes_sent) {
+ CHECK(net_result <= net::OK) << "Network status code must be <= net::OK";
+
+ sockets_tcp::SendInfo send_info;
+ send_info.result_code = net_result;
+ if (net_result == net::OK) {
+ send_info.bytes_sent.reset(new int(bytes_sent));
+ }
+
+ if (net_result != net::OK)
+ error_ = net::ErrorToString(net_result);
+ results_ = sockets_tcp::Send::Results::Create(send_info);
+ AsyncWorkCompleted();
+}
+
+SocketsTcpCloseFunction::SocketsTcpCloseFunction() {}
+
+SocketsTcpCloseFunction::~SocketsTcpCloseFunction() {}
+
+bool SocketsTcpCloseFunction::Prepare() {
+ params_ = sockets_tcp::Close::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsTcpCloseFunction::Work() {
+ ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ RemoveSocket(params_->socket_id);
+ results_ = sockets_tcp::Close::Results::Create();
+}
+
+SocketsTcpGetInfoFunction::SocketsTcpGetInfoFunction() {}
+
+SocketsTcpGetInfoFunction::~SocketsTcpGetInfoFunction() {}
+
+bool SocketsTcpGetInfoFunction::Prepare() {
+ params_ = sockets_tcp::GetInfo::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsTcpGetInfoFunction::Work() {
+ ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ sockets_tcp::SocketInfo socket_info =
+ CreateSocketInfo(params_->socket_id, socket);
+ results_ = sockets_tcp::GetInfo::Results::Create(socket_info);
+}
+
+SocketsTcpGetSocketsFunction::SocketsTcpGetSocketsFunction() {}
+
+SocketsTcpGetSocketsFunction::~SocketsTcpGetSocketsFunction() {}
+
+bool SocketsTcpGetSocketsFunction::Prepare() { return true; }
+
+void SocketsTcpGetSocketsFunction::Work() {
+ std::vector<sockets_tcp::SocketInfo> socket_infos;
+ base::hash_set<int>* resource_ids = GetSocketIds();
+ if (resource_ids != NULL) {
+ for (int socket_id : *resource_ids) {
+ ResumableTCPSocket* socket = GetTcpSocket(socket_id);
+ if (socket) {
+ socket_infos.push_back(CreateSocketInfo(socket_id, socket));
+ }
+ }
+ }
+ results_ = sockets_tcp::GetSockets::Results::Create(socket_infos);
+}
+
+SocketsTcpSecureFunction::SocketsTcpSecureFunction() {
+}
+
+SocketsTcpSecureFunction::~SocketsTcpSecureFunction() {
+}
+
+bool SocketsTcpSecureFunction::Prepare() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ params_ = api::sockets_tcp::Secure::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ url_request_getter_ = browser_context()->GetRequestContext();
+ return true;
+}
+
+// Override the regular implementation, which would call AsyncWorkCompleted
+// immediately after Work().
+void SocketsTcpSecureFunction::AsyncWorkStart() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+
+ ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ SetResult(new base::FundamentalValue(net::ERR_INVALID_ARGUMENT));
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ paused_ = socket->paused();
+ persistent_ = socket->persistent();
+
+ // Make sure it's a connected TCP client socket. Error out if it's already
+ // secure()'d.
+ if (socket->GetSocketType() != Socket::TYPE_TCP ||
+ socket->ClientStream() == NULL) {
+ SetResult(new base::FundamentalValue(net::ERR_INVALID_ARGUMENT));
+ error_ = kInvalidSocketStateError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ if (!socket->IsConnected()) {
+ SetResult(new base::FundamentalValue(net::ERR_INVALID_ARGUMENT));
+ error_ = kSocketNotConnectedError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ net::URLRequestContext* url_request_context =
+ url_request_getter_->GetURLRequestContext();
+
+ // UpgradeSocketToTLS() uses the older API's SecureOptions. Copy over the
+ // only values inside -- TLSVersionConstraints's |min| and |max|,
+ api::socket::SecureOptions legacy_params;
+ if (params_->options.get() && params_->options->tls_version.get()) {
+ legacy_params.tls_version.reset(new api::socket::TLSVersionConstraints);
+ if (params_->options->tls_version->min.get()) {
+ legacy_params.tls_version->min.reset(
+ new std::string(*params_->options->tls_version->min.get()));
+ }
+ if (params_->options->tls_version->max.get()) {
+ legacy_params.tls_version->max.reset(
+ new std::string(*params_->options->tls_version->max.get()));
+ }
+ }
+
+ TLSSocket::UpgradeSocketToTLS(
+ socket,
+ url_request_context->ssl_config_service(),
+ url_request_context->cert_verifier(),
+ url_request_context->transport_security_state(),
+ extension_id(),
+ &legacy_params,
+ base::Bind(&SocketsTcpSecureFunction::TlsConnectDone, this));
+}
+
+void SocketsTcpSecureFunction::TlsConnectDone(scoped_ptr<TLSSocket> socket,
+ int result) {
+ // If an error occurred, socket MUST be NULL
+ DCHECK(result == net::OK || socket == NULL);
+
+ if (socket && result == net::OK) {
+ socket->set_persistent(persistent_);
+ socket->set_paused(paused_);
+ ReplaceSocket(params_->socket_id, socket.release());
+ } else {
+ RemoveSocket(params_->socket_id);
+ error_ = net::ErrorToString(result);
+ }
+
+ results_ = api::sockets_tcp::Secure::Results::Create(result);
+ AsyncWorkCompleted();
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/sockets_tcp/sockets_tcp_api.h b/chromium/extensions/browser/api/sockets_tcp/sockets_tcp_api.h
new file mode 100644
index 00000000000..bbea8f4c3dc
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_tcp/sockets_tcp_api.h
@@ -0,0 +1,272 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SOCKETS_TCP_SOCKETS_TCP_API_H_
+#define EXTENSIONS_BROWSER_API_SOCKETS_TCP_SOCKETS_TCP_API_H_
+
+#include <stddef.h>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "extensions/browser/api/socket/socket_api.h"
+#include "extensions/common/api/sockets_tcp.h"
+
+namespace extensions {
+class ResumableTCPSocket;
+class TLSSocket;
+}
+
+namespace extensions {
+namespace api {
+
+class TCPSocketEventDispatcher;
+
+class TCPSocketAsyncApiFunction : public SocketAsyncApiFunction {
+ protected:
+ ~TCPSocketAsyncApiFunction() override;
+
+ scoped_ptr<SocketResourceManagerInterface> CreateSocketResourceManager()
+ override;
+
+ ResumableTCPSocket* GetTcpSocket(int socket_id);
+};
+
+class TCPSocketExtensionWithDnsLookupFunction
+ : public SocketExtensionWithDnsLookupFunction {
+ protected:
+ ~TCPSocketExtensionWithDnsLookupFunction() override;
+
+ scoped_ptr<SocketResourceManagerInterface> CreateSocketResourceManager()
+ override;
+
+ ResumableTCPSocket* GetTcpSocket(int socket_id);
+};
+
+class SocketsTcpCreateFunction : public TCPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcp.create", SOCKETS_TCP_CREATE)
+
+ SocketsTcpCreateFunction();
+
+ protected:
+ ~SocketsTcpCreateFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(SocketsTcpUnitTest, Create);
+ scoped_ptr<sockets_tcp::Create::Params> params_;
+};
+
+class SocketsTcpUpdateFunction : public TCPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcp.update", SOCKETS_TCP_UPDATE)
+
+ SocketsTcpUpdateFunction();
+
+ protected:
+ ~SocketsTcpUpdateFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_tcp::Update::Params> params_;
+};
+
+class SocketsTcpSetPausedFunction : public TCPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcp.setPaused", SOCKETS_TCP_SETPAUSED)
+
+ SocketsTcpSetPausedFunction();
+
+ protected:
+ ~SocketsTcpSetPausedFunction() override;
+
+ // AsyncApiFunction
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_tcp::SetPaused::Params> params_;
+ TCPSocketEventDispatcher* socket_event_dispatcher_;
+};
+
+class SocketsTcpSetKeepAliveFunction : public TCPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcp.setKeepAlive",
+ SOCKETS_TCP_SETKEEPALIVE)
+
+ SocketsTcpSetKeepAliveFunction();
+
+ protected:
+ ~SocketsTcpSetKeepAliveFunction() override;
+
+ // AsyncApiFunction
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_tcp::SetKeepAlive::Params> params_;
+};
+
+class SocketsTcpSetNoDelayFunction : public TCPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcp.setNoDelay", SOCKETS_TCP_SETNODELAY)
+
+ SocketsTcpSetNoDelayFunction();
+
+ protected:
+ ~SocketsTcpSetNoDelayFunction() override;
+
+ // AsyncApiFunction
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_tcp::SetNoDelay::Params> params_;
+};
+
+class SocketsTcpConnectFunction
+ : public TCPSocketExtensionWithDnsLookupFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcp.connect", SOCKETS_TCP_CONNECT)
+
+ SocketsTcpConnectFunction();
+
+ protected:
+ ~SocketsTcpConnectFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ // SocketExtensionWithDnsLookupFunction:
+ void AfterDnsLookup(int lookup_result) override;
+
+ private:
+ void StartConnect();
+ void OnCompleted(int net_result);
+
+ scoped_ptr<sockets_tcp::Connect::Params> params_;
+ TCPSocketEventDispatcher* socket_event_dispatcher_;
+};
+
+class SocketsTcpDisconnectFunction : public TCPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcp.disconnect", SOCKETS_TCP_DISCONNECT)
+
+ SocketsTcpDisconnectFunction();
+
+ protected:
+ ~SocketsTcpDisconnectFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_tcp::Disconnect::Params> params_;
+};
+
+class SocketsTcpSendFunction : public TCPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcp.send", SOCKETS_TCP_SEND)
+
+ SocketsTcpSendFunction();
+
+ protected:
+ ~SocketsTcpSendFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ private:
+ void OnCompleted(int net_result);
+ void SetSendResult(int net_result, int bytes_sent);
+
+ scoped_ptr<sockets_tcp::Send::Params> params_;
+ scoped_refptr<net::IOBuffer> io_buffer_;
+ size_t io_buffer_size_;
+};
+
+class SocketsTcpCloseFunction : public TCPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcp.close", SOCKETS_TCP_CLOSE)
+
+ SocketsTcpCloseFunction();
+
+ protected:
+ ~SocketsTcpCloseFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_tcp::Close::Params> params_;
+};
+
+class SocketsTcpGetInfoFunction : public TCPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcp.getInfo", SOCKETS_TCP_GETINFO)
+
+ SocketsTcpGetInfoFunction();
+
+ protected:
+ ~SocketsTcpGetInfoFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_tcp::GetInfo::Params> params_;
+};
+
+class SocketsTcpGetSocketsFunction : public TCPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcp.getSockets", SOCKETS_TCP_GETSOCKETS)
+
+ SocketsTcpGetSocketsFunction();
+
+ protected:
+ ~SocketsTcpGetSocketsFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+};
+
+class SocketsTcpSecureFunction : public TCPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcp.secure", SOCKETS_TCP_SECURE);
+
+ SocketsTcpSecureFunction();
+
+ protected:
+ ~SocketsTcpSecureFunction() override;
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ private:
+ virtual void TlsConnectDone(scoped_ptr<extensions::TLSSocket> sock,
+ int result);
+
+ bool paused_;
+ bool persistent_;
+ scoped_ptr<sockets_tcp::Secure::Params> params_;
+ scoped_refptr<net::URLRequestContextGetter> url_request_getter_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketsTcpSecureFunction);
+};
+
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SOCKETS_TCP_SOCKETS_TCP_API_H_
diff --git a/chromium/extensions/browser/api/sockets_tcp/sockets_tcp_api_unittest.cc b/chromium/extensions/browser/api/sockets_tcp/sockets_tcp_api_unittest.cc
new file mode 100644
index 00000000000..83c9faeaa39
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_tcp/sockets_tcp_api_unittest.cc
@@ -0,0 +1,51 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "content/public/test/test_browser_context.h"
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/browser/api/socket/socket.h"
+#include "extensions/browser/api/socket/tcp_socket.h"
+#include "extensions/browser/api/sockets_tcp/sockets_tcp_api.h"
+#include "extensions/browser/api_unittest.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+namespace api {
+
+static scoped_ptr<KeyedService> ApiResourceManagerTestFactory(
+ content::BrowserContext* context) {
+ return make_scoped_ptr(new ApiResourceManager<ResumableTCPSocket>(context));
+}
+
+class SocketsTcpUnitTest : public ApiUnitTest {
+ public:
+ void SetUp() override {
+ ApiUnitTest::SetUp();
+
+ ApiResourceManager<ResumableTCPSocket>::GetFactoryInstance()
+ ->SetTestingFactoryAndUse(browser_context(),
+ ApiResourceManagerTestFactory);
+ }
+};
+
+TEST_F(SocketsTcpUnitTest, Create) {
+ // Get BrowserThread
+ content::BrowserThread::ID id;
+ CHECK(content::BrowserThread::GetCurrentThreadIdentifier(&id));
+
+ // Create SocketCreateFunction and put it on BrowserThread
+ SocketsTcpCreateFunction* function = new SocketsTcpCreateFunction();
+ function->set_work_thread_id(id);
+
+ // Run tests
+ scoped_ptr<base::DictionaryValue> result(RunFunctionAndReturnDictionary(
+ function, "[{\"persistent\": true, \"name\": \"foo\"}]"));
+ ASSERT_TRUE(result.get());
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/sockets_tcp/sockets_tcp_apitest.cc b/chromium/extensions/browser/api/sockets_tcp/sockets_tcp_apitest.cc
new file mode 100644
index 00000000000..f11908d4979
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_tcp/sockets_tcp_apitest.cc
@@ -0,0 +1,125 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <utility>
+
+#include "base/memory/ref_counted.h"
+#include "base/strings/stringprintf.h"
+#include "extensions/browser/api/dns/host_resolver_wrapper.h"
+#include "extensions/browser/api/dns/mock_host_resolver_creator.h"
+#include "extensions/browser/api/sockets_tcp/sockets_tcp_api.h"
+#include "extensions/browser/api_test_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/test_util.h"
+#include "extensions/shell/test/shell_apitest.h"
+#include "extensions/shell/test/shell_test.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "extensions/test/result_catcher.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+
+namespace extensions {
+
+const std::string kHostname = "127.0.0.1";
+
+class SocketsTcpApiTest : public ShellApiTest {
+ public:
+ SocketsTcpApiTest()
+ : resolver_event_(true, false),
+ resolver_creator_(new MockHostResolverCreator()) {}
+
+ void SetUpOnMainThread() override {
+ ShellApiTest::SetUpOnMainThread();
+
+ HostResolverWrapper::GetInstance()->SetHostResolverForTesting(
+ resolver_creator_->CreateMockHostResolver());
+ }
+
+ void TearDownOnMainThread() override {
+ HostResolverWrapper::GetInstance()->SetHostResolverForTesting(NULL);
+ resolver_creator_->DeleteMockHostResolver();
+
+ ShellApiTest::TearDownOnMainThread();
+ }
+
+ private:
+ base::WaitableEvent resolver_event_;
+
+ // The MockHostResolver asserts that it's used on the same thread on which
+ // it's created, which is actually a stronger rule than its real counterpart.
+ // But that's fine; it's good practice.
+ scoped_refptr<MockHostResolverCreator> resolver_creator_;
+};
+
+IN_PROC_BROWSER_TEST_F(SocketsTcpApiTest, SocketsTcpCreateGood) {
+ scoped_refptr<api::SocketsTcpCreateFunction> socket_create_function(
+ new api::SocketsTcpCreateFunction());
+ scoped_refptr<Extension> empty_extension = test_util::CreateEmptyExtension();
+
+ socket_create_function->set_extension(empty_extension.get());
+ socket_create_function->set_has_callback(true);
+
+ scoped_ptr<base::Value> result(
+ api_test_utils::RunFunctionAndReturnSingleResult(
+ socket_create_function.get(), "[]", browser_context()));
+
+ ASSERT_EQ(base::Value::TYPE_DICTIONARY, result->GetType());
+ scoped_ptr<base::DictionaryValue> value =
+ base::DictionaryValue::From(std::move(result));
+ int socketId = -1;
+ EXPECT_TRUE(value->GetInteger("socketId", &socketId));
+ ASSERT_TRUE(socketId > 0);
+}
+
+IN_PROC_BROWSER_TEST_F(SocketsTcpApiTest, SocketTcpExtension) {
+ scoped_ptr<net::SpawnedTestServer> test_server(new net::SpawnedTestServer(
+ net::SpawnedTestServer::TYPE_TCP_ECHO, net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("net/data"))));
+ EXPECT_TRUE(test_server->Start());
+
+ net::HostPortPair host_port_pair = test_server->host_port_pair();
+ int port = host_port_pair.port();
+ ASSERT_TRUE(port > 0);
+
+ // Test that connect() is properly resolving hostnames.
+ host_port_pair.set_host("lOcAlHoSt");
+
+ ResultCatcher catcher;
+ catcher.RestrictToBrowserContext(browser_context());
+
+ ExtensionTestMessageListener listener("info_please", true);
+
+ ASSERT_TRUE(LoadApp("sockets_tcp/api"));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+ listener.Reply(
+ base::StringPrintf("tcp:%s:%d", host_port_pair.host().c_str(), port));
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(SocketsTcpApiTest, SocketTcpExtensionTLS) {
+ scoped_ptr<net::SpawnedTestServer> test_https_server(
+ new net::SpawnedTestServer(
+ net::SpawnedTestServer::TYPE_HTTPS, net::BaseTestServer::SSLOptions(),
+ base::FilePath(FILE_PATH_LITERAL("net/data"))));
+ EXPECT_TRUE(test_https_server->Start());
+
+ net::HostPortPair https_host_port_pair = test_https_server->host_port_pair();
+ int https_port = https_host_port_pair.port();
+ ASSERT_GT(https_port, 0);
+
+ ResultCatcher catcher;
+ catcher.RestrictToBrowserContext(browser_context());
+
+ ExtensionTestMessageListener listener("info_please", true);
+
+ ASSERT_TRUE(LoadApp("sockets_tcp/api"));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+ listener.Reply(base::StringPrintf(
+ "https:%s:%d", https_host_port_pair.host().c_str(), https_port));
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.cc b/chromium/extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.cc
new file mode 100644
index 00000000000..7d2f2ca46cf
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.cc
@@ -0,0 +1,204 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.h"
+
+#include <utility>
+
+#include "base/lazy_instance.h"
+#include "extensions/browser/api/socket/tcp_socket.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "net/base/net_errors.h"
+
+namespace {
+int kDefaultBufferSize = 4096;
+}
+
+namespace extensions {
+namespace api {
+
+using content::BrowserThread;
+
+static base::LazyInstance<
+ BrowserContextKeyedAPIFactory<TCPSocketEventDispatcher> > g_factory =
+ LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<TCPSocketEventDispatcher>*
+TCPSocketEventDispatcher::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+// static
+TCPSocketEventDispatcher* TCPSocketEventDispatcher::Get(
+ content::BrowserContext* context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ return BrowserContextKeyedAPIFactory<TCPSocketEventDispatcher>::Get(context);
+}
+
+TCPSocketEventDispatcher::TCPSocketEventDispatcher(
+ content::BrowserContext* context)
+ : thread_id_(Socket::kThreadId), browser_context_(context) {
+ ApiResourceManager<ResumableTCPSocket>* manager =
+ ApiResourceManager<ResumableTCPSocket>::Get(browser_context_);
+ DCHECK(manager)
+ << "There is no socket manager. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "ApiResourceManager<ResumableTCPSocket>.";
+ sockets_ = manager->data_;
+}
+
+TCPSocketEventDispatcher::~TCPSocketEventDispatcher() {}
+
+TCPSocketEventDispatcher::ReadParams::ReadParams() {}
+
+TCPSocketEventDispatcher::ReadParams::ReadParams(const ReadParams& other) =
+ default;
+
+TCPSocketEventDispatcher::ReadParams::~ReadParams() {}
+
+void TCPSocketEventDispatcher::OnSocketConnect(const std::string& extension_id,
+ int socket_id) {
+ DCHECK_CURRENTLY_ON(thread_id_);
+
+ StartSocketRead(extension_id, socket_id);
+}
+
+void TCPSocketEventDispatcher::OnSocketResume(const std::string& extension_id,
+ int socket_id) {
+ DCHECK_CURRENTLY_ON(thread_id_);
+
+ StartSocketRead(extension_id, socket_id);
+}
+
+void TCPSocketEventDispatcher::StartSocketRead(const std::string& extension_id,
+ int socket_id) {
+ DCHECK_CURRENTLY_ON(thread_id_);
+
+ ReadParams params;
+ params.thread_id = thread_id_;
+ params.browser_context_id = browser_context_;
+ params.extension_id = extension_id;
+ params.sockets = sockets_;
+ params.socket_id = socket_id;
+
+ StartRead(params);
+}
+
+// static
+void TCPSocketEventDispatcher::StartRead(const ReadParams& params) {
+ DCHECK_CURRENTLY_ON(params.thread_id);
+
+ ResumableTCPSocket* socket =
+ params.sockets->Get(params.extension_id, params.socket_id);
+ if (!socket) {
+ // This can happen if the socket is closed while our callback is active.
+ return;
+ }
+ DCHECK(params.extension_id == socket->owner_extension_id())
+ << "Socket has wrong owner.";
+
+ // Don't start another read if the socket has been paused.
+ if (socket->paused())
+ return;
+
+ int buffer_size = socket->buffer_size();
+ if (buffer_size <= 0)
+ buffer_size = kDefaultBufferSize;
+ socket->Read(buffer_size,
+ base::Bind(&TCPSocketEventDispatcher::ReadCallback, params));
+}
+
+// static
+void TCPSocketEventDispatcher::ReadCallback(
+ const ReadParams& params,
+ int bytes_read,
+ scoped_refptr<net::IOBuffer> io_buffer) {
+ DCHECK_CURRENTLY_ON(params.thread_id);
+
+ // If |bytes_read| == 0, the connection has been closed by the peer.
+ // If |bytes_read| < 0, there was a network error, and |bytes_read| is a value
+ // from "net::ERR_".
+
+ if (bytes_read == 0) {
+ bytes_read = net::ERR_CONNECTION_CLOSED;
+ }
+
+ if (bytes_read > 0) {
+ // Dispatch "onReceive" event.
+ sockets_tcp::ReceiveInfo receive_info;
+ receive_info.socket_id = params.socket_id;
+ receive_info.data.assign(io_buffer->data(), io_buffer->data() + bytes_read);
+ scoped_ptr<base::ListValue> args =
+ sockets_tcp::OnReceive::Create(receive_info);
+ scoped_ptr<Event> event(new Event(events::SOCKETS_TCP_ON_RECEIVE,
+ sockets_tcp::OnReceive::kEventName,
+ std::move(args)));
+ PostEvent(params, std::move(event));
+
+ // Post a task to delay the read until the socket is available, as
+ // calling StartReceive at this point would error with ERR_IO_PENDING.
+ BrowserThread::PostTask(
+ params.thread_id,
+ FROM_HERE,
+ base::Bind(&TCPSocketEventDispatcher::StartRead, params));
+ } else if (bytes_read == net::ERR_IO_PENDING) {
+ // This happens when resuming a socket which already had an
+ // active "read" callback.
+ } else {
+ // Dispatch "onReceiveError" event but don't start another read to avoid
+ // potential infinite reads if we have a persistent network error.
+ sockets_tcp::ReceiveErrorInfo receive_error_info;
+ receive_error_info.socket_id = params.socket_id;
+ receive_error_info.result_code = bytes_read;
+ scoped_ptr<base::ListValue> args =
+ sockets_tcp::OnReceiveError::Create(receive_error_info);
+ scoped_ptr<Event> event(new Event(events::SOCKETS_TCP_ON_RECEIVE_ERROR,
+ sockets_tcp::OnReceiveError::kEventName,
+ std::move(args)));
+ PostEvent(params, std::move(event));
+
+ // Since we got an error, the socket is now "paused" until the application
+ // "resumes" it.
+ ResumableTCPSocket* socket =
+ params.sockets->Get(params.extension_id, params.socket_id);
+ if (socket) {
+ socket->set_paused(true);
+ }
+ }
+}
+
+// static
+void TCPSocketEventDispatcher::PostEvent(const ReadParams& params,
+ scoped_ptr<Event> event) {
+ DCHECK_CURRENTLY_ON(params.thread_id);
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&DispatchEvent, params.browser_context_id, params.extension_id,
+ base::Passed(std::move(event))));
+}
+
+// static
+void TCPSocketEventDispatcher::DispatchEvent(void* browser_context_id,
+ const std::string& extension_id,
+ scoped_ptr<Event> event) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ content::BrowserContext* context =
+ reinterpret_cast<content::BrowserContext*>(browser_context_id);
+ if (!extensions::ExtensionsBrowserClient::Get()->IsValidContext(context))
+ return;
+
+ EventRouter* event_router = EventRouter::Get(context);
+ if (event_router)
+ event_router->DispatchEventToExtension(extension_id, std::move(event));
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.h b/chromium/extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.h
new file mode 100644
index 00000000000..0c90980fb5e
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.h
@@ -0,0 +1,97 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SOCKETS_TCP_TCP_SOCKET_EVENT_DISPATCHER_H_
+#define EXTENSIONS_BROWSER_API_SOCKETS_TCP_TCP_SOCKET_EVENT_DISPATCHER_H_
+
+#include <string>
+
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/browser/api/sockets_tcp/sockets_tcp_api.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+struct Event;
+class ResumableTCPSocket;
+}
+
+namespace extensions {
+namespace api {
+
+// Dispatch events related to "sockets.tcp" sockets from callback on native
+// socket instances. There is one instance per profile.
+class TCPSocketEventDispatcher
+ : public BrowserContextKeyedAPI,
+ public base::SupportsWeakPtr<TCPSocketEventDispatcher> {
+ public:
+ explicit TCPSocketEventDispatcher(content::BrowserContext* context);
+ ~TCPSocketEventDispatcher() override;
+
+ // Socket is active, start receving from it.
+ void OnSocketConnect(const std::string& extension_id, int socket_id);
+
+ // Socket is active again, start receiving data from it.
+ void OnSocketResume(const std::string& extension_id, int socket_id);
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<TCPSocketEventDispatcher>*
+ GetFactoryInstance();
+
+ // Convenience method to get the SocketEventDispatcher for a profile.
+ static TCPSocketEventDispatcher* Get(content::BrowserContext* context);
+
+ private:
+ typedef ApiResourceManager<ResumableTCPSocket>::ApiResourceData SocketData;
+ friend class BrowserContextKeyedAPIFactory<TCPSocketEventDispatcher>;
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "TCPSocketEventDispatcher"; }
+ static const bool kServiceHasOwnInstanceInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ // base::Bind supports methods with up to 6 parameters. ReadParams is used
+ // as a workaround that limitation for invoking StartReceive.
+ struct ReadParams {
+ ReadParams();
+ ReadParams(const ReadParams& other);
+ ~ReadParams();
+
+ content::BrowserThread::ID thread_id;
+ void* browser_context_id;
+ std::string extension_id;
+ scoped_refptr<SocketData> sockets;
+ int socket_id;
+ };
+
+ // Start a receive and register a callback.
+ void StartSocketRead(const std::string& extension_id, int socket_id);
+
+ // Start a receive and register a callback.
+ static void StartRead(const ReadParams& params);
+
+ // Called when socket receive data.
+ static void ReadCallback(const ReadParams& params,
+ int bytes_read,
+ scoped_refptr<net::IOBuffer> io_buffer);
+
+ // Post an extension event from IO to UI thread
+ static void PostEvent(const ReadParams& params, scoped_ptr<Event> event);
+
+ // Dispatch an extension event on to EventRouter instance on UI thread.
+ static void DispatchEvent(void* browser_context_id,
+ const std::string& extension_id,
+ scoped_ptr<Event> event);
+
+ // Usually IO thread (except for unit testing).
+ content::BrowserThread::ID thread_id_;
+ content::BrowserContext* const browser_context_;
+ scoped_refptr<SocketData> sockets_;
+};
+
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SOCKETS_TCP_TCP_SOCKET_EVENT_DISPATCHER_H_
diff --git a/chromium/extensions/browser/api/sockets_tcp_server/OWNERS b/chromium/extensions/browser/api/sockets_tcp_server/OWNERS
new file mode 100644
index 00000000000..3e30c826edc
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_tcp_server/OWNERS
@@ -0,0 +1 @@
+rpaquay@chromium.org
diff --git a/chromium/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.cc b/chromium/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.cc
new file mode 100644
index 00000000000..bc39c83fe88
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.cc
@@ -0,0 +1,297 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.h"
+
+#include "content/public/common/socket_permission_request.h"
+#include "extensions/browser/api/socket/tcp_socket.h"
+#include "extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h"
+#include "extensions/common/api/sockets/sockets_manifest_data.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/permissions/socket_permission.h"
+#include "net/base/net_errors.h"
+
+using content::SocketPermissionRequest;
+using extensions::ResumableTCPServerSocket;
+using extensions::api::sockets_tcp_server::SocketInfo;
+using extensions::api::sockets_tcp_server::SocketProperties;
+
+namespace {
+
+const char kSocketNotFoundError[] = "Socket not found";
+const char kPermissionError[] = "Does not have permission";
+const int kDefaultListenBacklog = SOMAXCONN;
+
+SocketInfo CreateSocketInfo(int socket_id, ResumableTCPServerSocket* socket) {
+ SocketInfo socket_info;
+ // This represents what we know about the socket, and does not call through
+ // to the system.
+ socket_info.socket_id = socket_id;
+ if (!socket->name().empty()) {
+ socket_info.name.reset(new std::string(socket->name()));
+ }
+ socket_info.persistent = socket->persistent();
+ socket_info.paused = socket->paused();
+
+ // Grab the local address as known by the OS.
+ net::IPEndPoint localAddress;
+ if (socket->GetLocalAddress(&localAddress)) {
+ socket_info.local_address.reset(
+ new std::string(localAddress.ToStringWithoutPort()));
+ socket_info.local_port.reset(new int(localAddress.port()));
+ }
+
+ return socket_info;
+}
+
+void SetSocketProperties(ResumableTCPServerSocket* socket,
+ SocketProperties* properties) {
+ if (properties->name.get()) {
+ socket->set_name(*properties->name.get());
+ }
+ if (properties->persistent.get()) {
+ socket->set_persistent(*properties->persistent.get());
+ }
+}
+
+} // namespace
+
+namespace extensions {
+namespace api {
+
+TCPServerSocketAsyncApiFunction::~TCPServerSocketAsyncApiFunction() {}
+
+scoped_ptr<SocketResourceManagerInterface>
+TCPServerSocketAsyncApiFunction::CreateSocketResourceManager() {
+ return scoped_ptr<SocketResourceManagerInterface>(
+ new SocketResourceManager<ResumableTCPServerSocket>());
+}
+
+ResumableTCPServerSocket* TCPServerSocketAsyncApiFunction::GetTcpSocket(
+ int socket_id) {
+ return static_cast<ResumableTCPServerSocket*>(GetSocket(socket_id));
+}
+
+SocketsTcpServerCreateFunction::SocketsTcpServerCreateFunction() {}
+
+SocketsTcpServerCreateFunction::~SocketsTcpServerCreateFunction() {}
+
+bool SocketsTcpServerCreateFunction::Prepare() {
+ params_ = sockets_tcp_server::Create::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsTcpServerCreateFunction::Work() {
+ ResumableTCPServerSocket* socket =
+ new ResumableTCPServerSocket(extension_->id());
+
+ sockets_tcp_server::SocketProperties* properties =
+ params_.get()->properties.get();
+ if (properties) {
+ SetSocketProperties(socket, properties);
+ }
+
+ sockets_tcp_server::CreateInfo create_info;
+ create_info.socket_id = AddSocket(socket);
+ results_ = sockets_tcp_server::Create::Results::Create(create_info);
+}
+
+SocketsTcpServerUpdateFunction::SocketsTcpServerUpdateFunction() {}
+
+SocketsTcpServerUpdateFunction::~SocketsTcpServerUpdateFunction() {}
+
+bool SocketsTcpServerUpdateFunction::Prepare() {
+ params_ = sockets_tcp_server::Update::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsTcpServerUpdateFunction::Work() {
+ ResumableTCPServerSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ SetSocketProperties(socket, &params_.get()->properties);
+ results_ = sockets_tcp_server::Update::Results::Create();
+}
+
+SocketsTcpServerSetPausedFunction::SocketsTcpServerSetPausedFunction()
+ : socket_event_dispatcher_(NULL) {}
+
+SocketsTcpServerSetPausedFunction::~SocketsTcpServerSetPausedFunction() {}
+
+bool SocketsTcpServerSetPausedFunction::Prepare() {
+ params_ = api::sockets_tcp_server::SetPaused::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ socket_event_dispatcher_ =
+ TCPServerSocketEventDispatcher::Get(browser_context());
+ DCHECK(socket_event_dispatcher_)
+ << "There is no socket event dispatcher. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "TCPServerSocketEventDispatcher.";
+ return socket_event_dispatcher_ != NULL;
+}
+
+void SocketsTcpServerSetPausedFunction::Work() {
+ ResumableTCPServerSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ if (socket->paused() != params_->paused) {
+ socket->set_paused(params_->paused);
+ if (socket->IsConnected() && !params_->paused) {
+ socket_event_dispatcher_->OnServerSocketResume(extension_->id(),
+ params_->socket_id);
+ }
+ }
+
+ results_ = sockets_tcp_server::SetPaused::Results::Create();
+}
+
+SocketsTcpServerListenFunction::SocketsTcpServerListenFunction()
+ : socket_event_dispatcher_(NULL) {}
+
+SocketsTcpServerListenFunction::~SocketsTcpServerListenFunction() {}
+
+bool SocketsTcpServerListenFunction::Prepare() {
+ params_ = api::sockets_tcp_server::Listen::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ socket_event_dispatcher_ =
+ TCPServerSocketEventDispatcher::Get(browser_context());
+ DCHECK(socket_event_dispatcher_)
+ << "There is no socket event dispatcher. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "TCPServerSocketEventDispatcher.";
+ return socket_event_dispatcher_ != NULL;
+}
+
+void SocketsTcpServerListenFunction::AsyncWorkStart() {
+ ResumableTCPServerSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ SocketPermissionRequest param(
+ SocketPermissionRequest::TCP_LISTEN, params_->address, params_->port);
+ if (!SocketsManifestData::CheckRequest(extension(), param)) {
+ error_ = kPermissionError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ int net_result = socket->Listen(
+ params_->address,
+ params_->port,
+ params_->backlog.get() ? *params_->backlog.get() : kDefaultListenBacklog,
+ &error_);
+ results_ = sockets_tcp_server::Listen::Results::Create(net_result);
+ if (net_result == net::OK) {
+ socket_event_dispatcher_->OnServerSocketListen(extension_->id(),
+ params_->socket_id);
+ } else {
+ error_ = net::ErrorToString(net_result);
+ AsyncWorkCompleted();
+ return;
+ }
+
+ OpenFirewallHole(params_->address, params_->socket_id, socket);
+}
+
+SocketsTcpServerDisconnectFunction::SocketsTcpServerDisconnectFunction() {}
+
+SocketsTcpServerDisconnectFunction::~SocketsTcpServerDisconnectFunction() {}
+
+bool SocketsTcpServerDisconnectFunction::Prepare() {
+ params_ = sockets_tcp_server::Disconnect::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsTcpServerDisconnectFunction::Work() {
+ ResumableTCPServerSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ socket->Disconnect();
+ results_ = sockets_tcp_server::Disconnect::Results::Create();
+}
+
+SocketsTcpServerCloseFunction::SocketsTcpServerCloseFunction() {}
+
+SocketsTcpServerCloseFunction::~SocketsTcpServerCloseFunction() {}
+
+bool SocketsTcpServerCloseFunction::Prepare() {
+ params_ = sockets_tcp_server::Close::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsTcpServerCloseFunction::Work() {
+ ResumableTCPServerSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ RemoveSocket(params_->socket_id);
+ results_ = sockets_tcp_server::Close::Results::Create();
+}
+
+SocketsTcpServerGetInfoFunction::SocketsTcpServerGetInfoFunction() {}
+
+SocketsTcpServerGetInfoFunction::~SocketsTcpServerGetInfoFunction() {}
+
+bool SocketsTcpServerGetInfoFunction::Prepare() {
+ params_ = sockets_tcp_server::GetInfo::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsTcpServerGetInfoFunction::Work() {
+ ResumableTCPServerSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ sockets_tcp_server::SocketInfo socket_info =
+ CreateSocketInfo(params_->socket_id, socket);
+ results_ = sockets_tcp_server::GetInfo::Results::Create(socket_info);
+}
+
+SocketsTcpServerGetSocketsFunction::SocketsTcpServerGetSocketsFunction() {}
+
+SocketsTcpServerGetSocketsFunction::~SocketsTcpServerGetSocketsFunction() {}
+
+bool SocketsTcpServerGetSocketsFunction::Prepare() { return true; }
+
+void SocketsTcpServerGetSocketsFunction::Work() {
+ std::vector<sockets_tcp_server::SocketInfo> socket_infos;
+ base::hash_set<int>* resource_ids = GetSocketIds();
+ if (resource_ids != NULL) {
+ for (int socket_id : *resource_ids) {
+ ResumableTCPServerSocket* socket = GetTcpSocket(socket_id);
+ if (socket) {
+ socket_infos.push_back(CreateSocketInfo(socket_id, socket));
+ }
+ }
+ }
+ results_ = sockets_tcp_server::GetSockets::Results::Create(socket_infos);
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.h b/chromium/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.h
new file mode 100644
index 00000000000..a125b283ebd
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.h
@@ -0,0 +1,179 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SOCKETS_TCP_SERVER_SOCKETS_TCP_SERVER_API_H_
+#define EXTENSIONS_BROWSER_API_SOCKETS_TCP_SERVER_SOCKETS_TCP_SERVER_API_H_
+
+#include "base/gtest_prod_util.h"
+#include "extensions/browser/api/socket/socket_api.h"
+#include "extensions/common/api/sockets_tcp_server.h"
+
+namespace extensions {
+class ResumableTCPServerSocket;
+}
+
+namespace extensions {
+namespace api {
+
+class TCPServerSocketAsyncApiFunction : public SocketAsyncApiFunction {
+ protected:
+ ~TCPServerSocketAsyncApiFunction() override;
+
+ scoped_ptr<SocketResourceManagerInterface> CreateSocketResourceManager()
+ override;
+
+ ResumableTCPServerSocket* GetTcpSocket(int socket_id);
+};
+
+class SocketsTcpServerCreateFunction : public TCPServerSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.create",
+ SOCKETS_TCP_SERVER_CREATE)
+
+ SocketsTcpServerCreateFunction();
+
+ protected:
+ ~SocketsTcpServerCreateFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(SocketsTcpServerUnitTest, Create);
+ scoped_ptr<sockets_tcp_server::Create::Params> params_;
+};
+
+class SocketsTcpServerUpdateFunction : public TCPServerSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.update",
+ SOCKETS_TCP_SERVER_UPDATE)
+
+ SocketsTcpServerUpdateFunction();
+
+ protected:
+ ~SocketsTcpServerUpdateFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_tcp_server::Update::Params> params_;
+};
+
+class SocketsTcpServerSetPausedFunction
+ : public TCPServerSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.setPaused",
+ SOCKETS_TCP_SERVER_SETPAUSED)
+
+ SocketsTcpServerSetPausedFunction();
+
+ protected:
+ ~SocketsTcpServerSetPausedFunction() override;
+
+ // AsyncApiFunction
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_tcp_server::SetPaused::Params> params_;
+ TCPServerSocketEventDispatcher* socket_event_dispatcher_;
+};
+
+class SocketsTcpServerListenFunction : public TCPServerSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.listen",
+ SOCKETS_TCP_SERVER_LISTEN)
+
+ SocketsTcpServerListenFunction();
+
+ protected:
+ ~SocketsTcpServerListenFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ private:
+ scoped_ptr<sockets_tcp_server::Listen::Params> params_;
+ TCPServerSocketEventDispatcher* socket_event_dispatcher_;
+};
+
+class SocketsTcpServerDisconnectFunction
+ : public TCPServerSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.disconnect",
+ SOCKETS_TCP_SERVER_DISCONNECT)
+
+ SocketsTcpServerDisconnectFunction();
+
+ protected:
+ ~SocketsTcpServerDisconnectFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_tcp_server::Disconnect::Params> params_;
+};
+
+class SocketsTcpServerCloseFunction : public TCPServerSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.close",
+ SOCKETS_TCP_SERVER_CLOSE)
+
+ SocketsTcpServerCloseFunction();
+
+ protected:
+ ~SocketsTcpServerCloseFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_tcp_server::Close::Params> params_;
+};
+
+class SocketsTcpServerGetInfoFunction : public TCPServerSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.getInfo",
+ SOCKETS_TCP_SERVER_GETINFO)
+
+ SocketsTcpServerGetInfoFunction();
+
+ protected:
+ ~SocketsTcpServerGetInfoFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_tcp_server::GetInfo::Params> params_;
+};
+
+class SocketsTcpServerGetSocketsFunction
+ : public TCPServerSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.getSockets",
+ SOCKETS_TCP_SERVER_GETSOCKETS)
+
+ SocketsTcpServerGetSocketsFunction();
+
+ protected:
+ ~SocketsTcpServerGetSocketsFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+};
+
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SOCKETS_TCP_SERVER_SOCKETS_TCP_SERVER_API_H_
diff --git a/chromium/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_apitest.cc b/chromium/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_apitest.cc
new file mode 100644
index 00000000000..c54724ba7c5
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_apitest.cc
@@ -0,0 +1,100 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <utility>
+
+#include "base/memory/ref_counted.h"
+#include "base/strings/stringprintf.h"
+#include "extensions/browser/api/dns/host_resolver_wrapper.h"
+#include "extensions/browser/api/dns/mock_host_resolver_creator.h"
+#include "extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.h"
+#include "extensions/browser/api_test_utils.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/test_util.h"
+#include "extensions/shell/test/shell_apitest.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "extensions/test/result_catcher.h"
+#include "net/dns/mock_host_resolver.h"
+
+namespace extensions {
+
+const std::string kHostname = "127.0.0.1";
+const int kPort = 8888;
+
+class SocketsTcpServerApiTest : public ShellApiTest {
+ public:
+ SocketsTcpServerApiTest()
+ : resolver_event_(true, false),
+ resolver_creator_(new MockHostResolverCreator()) {}
+
+ void SetUpOnMainThread() override {
+ ShellApiTest::SetUpOnMainThread();
+
+ HostResolverWrapper::GetInstance()->SetHostResolverForTesting(
+ resolver_creator_->CreateMockHostResolver());
+ }
+
+ void TearDownOnMainThread() override {
+ HostResolverWrapper::GetInstance()->SetHostResolverForTesting(NULL);
+ resolver_creator_->DeleteMockHostResolver();
+
+ ShellApiTest::TearDownOnMainThread();
+ }
+
+ private:
+ base::WaitableEvent resolver_event_;
+
+ // The MockHostResolver asserts that it's used on the same thread on which
+ // it's created, which is actually a stronger rule than its real counterpart.
+ // But that's fine; it's good practice.
+ scoped_refptr<MockHostResolverCreator> resolver_creator_;
+};
+
+IN_PROC_BROWSER_TEST_F(SocketsTcpServerApiTest, SocketTCPCreateGood) {
+ scoped_refptr<api::SocketsTcpServerCreateFunction> socket_create_function(
+ new api::SocketsTcpServerCreateFunction());
+ scoped_refptr<Extension> empty_extension(test_util::CreateEmptyExtension());
+
+ socket_create_function->set_extension(empty_extension.get());
+ socket_create_function->set_has_callback(true);
+
+ scoped_ptr<base::Value> result(
+ api_test_utils::RunFunctionAndReturnSingleResult(
+ socket_create_function.get(), "[]", browser_context()));
+ ASSERT_EQ(base::Value::TYPE_DICTIONARY, result->GetType());
+ scoped_ptr<base::DictionaryValue> value =
+ base::DictionaryValue::From(std::move(result));
+ int socketId = -1;
+ EXPECT_TRUE(value->GetInteger("socketId", &socketId));
+ ASSERT_TRUE(socketId > 0);
+}
+
+IN_PROC_BROWSER_TEST_F(SocketsTcpServerApiTest, SocketTCPServerExtension) {
+ ResultCatcher catcher;
+ catcher.RestrictToBrowserContext(browser_context());
+ ExtensionTestMessageListener listener("info_please", true);
+ ASSERT_TRUE(LoadApp("sockets_tcp_server/api"));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+ listener.Reply(
+ base::StringPrintf("tcp_server:%s:%d", kHostname.c_str(), kPort));
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+// Flaky. http://crbug.com/561474
+IN_PROC_BROWSER_TEST_F(SocketsTcpServerApiTest,
+ DISABLED_SocketTCPServerUnbindOnUnload) {
+ std::string path("sockets_tcp_server/unload");
+ ResultCatcher catcher;
+ const Extension* extension = LoadApp(path);
+ ASSERT_TRUE(extension);
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ UnloadApp(extension);
+
+ ASSERT_TRUE(LoadApp(path)) << message_;
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc b/chromium/extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc
new file mode 100644
index 00000000000..a43d6be185f
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc
@@ -0,0 +1,204 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h"
+
+#include <utility>
+
+#include "base/lazy_instance.h"
+#include "extensions/browser/api/socket/tcp_socket.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "net/base/net_errors.h"
+
+namespace extensions {
+namespace api {
+
+using content::BrowserThread;
+
+static base::LazyInstance<
+ BrowserContextKeyedAPIFactory<TCPServerSocketEventDispatcher> > g_factory =
+ LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<TCPServerSocketEventDispatcher>*
+TCPServerSocketEventDispatcher::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+// static
+TCPServerSocketEventDispatcher* TCPServerSocketEventDispatcher::Get(
+ content::BrowserContext* context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ return BrowserContextKeyedAPIFactory<TCPServerSocketEventDispatcher>::Get(
+ context);
+}
+
+TCPServerSocketEventDispatcher::TCPServerSocketEventDispatcher(
+ content::BrowserContext* context)
+ : thread_id_(Socket::kThreadId), browser_context_(context) {
+ ApiResourceManager<ResumableTCPServerSocket>* server_manager =
+ ApiResourceManager<ResumableTCPServerSocket>::Get(browser_context_);
+ DCHECK(server_manager)
+ << "There is no server socket manager. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "ApiResourceManager<ResumableTCPServerSocket>.";
+ server_sockets_ = server_manager->data_;
+
+ ApiResourceManager<ResumableTCPSocket>* client_manager =
+ ApiResourceManager<ResumableTCPSocket>::Get(browser_context_);
+ DCHECK(client_manager)
+ << "There is no client socket manager. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "ApiResourceManager<ResumableTCPSocket>.";
+ client_sockets_ = client_manager->data_;
+}
+
+TCPServerSocketEventDispatcher::~TCPServerSocketEventDispatcher() {}
+
+TCPServerSocketEventDispatcher::AcceptParams::AcceptParams() {}
+
+TCPServerSocketEventDispatcher::AcceptParams::AcceptParams(
+ const AcceptParams& other) = default;
+
+TCPServerSocketEventDispatcher::AcceptParams::~AcceptParams() {}
+
+void TCPServerSocketEventDispatcher::OnServerSocketListen(
+ const std::string& extension_id,
+ int socket_id) {
+ DCHECK_CURRENTLY_ON(thread_id_);
+
+ StartSocketAccept(extension_id, socket_id);
+}
+
+void TCPServerSocketEventDispatcher::OnServerSocketResume(
+ const std::string& extension_id,
+ int socket_id) {
+ DCHECK_CURRENTLY_ON(thread_id_);
+
+ StartSocketAccept(extension_id, socket_id);
+}
+
+void TCPServerSocketEventDispatcher::StartSocketAccept(
+ const std::string& extension_id,
+ int socket_id) {
+ DCHECK_CURRENTLY_ON(thread_id_);
+
+ AcceptParams params;
+ params.thread_id = thread_id_;
+ params.browser_context_id = browser_context_;
+ params.extension_id = extension_id;
+ params.server_sockets = server_sockets_;
+ params.client_sockets = client_sockets_;
+ params.socket_id = socket_id;
+
+ StartAccept(params);
+}
+
+// static
+void TCPServerSocketEventDispatcher::StartAccept(const AcceptParams& params) {
+ DCHECK_CURRENTLY_ON(params.thread_id);
+
+ ResumableTCPServerSocket* socket =
+ params.server_sockets->Get(params.extension_id, params.socket_id);
+ if (!socket) {
+ // This can happen if the socket is closed while our callback is active.
+ return;
+ }
+ DCHECK(params.extension_id == socket->owner_extension_id())
+ << "Socket has wrong owner.";
+
+ // Don't start another accept if the socket has been paused.
+ if (socket->paused())
+ return;
+
+ socket->Accept(
+ base::Bind(&TCPServerSocketEventDispatcher::AcceptCallback, params));
+}
+
+// static
+void TCPServerSocketEventDispatcher::AcceptCallback(
+ const AcceptParams& params,
+ int result_code,
+ scoped_ptr<net::TCPClientSocket> socket) {
+ DCHECK_CURRENTLY_ON(params.thread_id);
+
+ if (result_code >= 0) {
+ ResumableTCPSocket* client_socket =
+ new ResumableTCPSocket(std::move(socket), params.extension_id, true);
+ client_socket->set_paused(true);
+ int client_socket_id = params.client_sockets->Add(client_socket);
+
+ // Dispatch "onAccept" event.
+ sockets_tcp_server::AcceptInfo accept_info;
+ accept_info.socket_id = params.socket_id;
+ accept_info.client_socket_id = client_socket_id;
+ scoped_ptr<base::ListValue> args =
+ sockets_tcp_server::OnAccept::Create(accept_info);
+ scoped_ptr<Event> event(new Event(events::SOCKETS_TCP_SERVER_ON_ACCEPT,
+ sockets_tcp_server::OnAccept::kEventName,
+ std::move(args)));
+ PostEvent(params, std::move(event));
+
+ // Post a task to delay the "accept" until the socket is available, as
+ // calling StartAccept at this point would error with ERR_IO_PENDING.
+ BrowserThread::PostTask(
+ params.thread_id,
+ FROM_HERE,
+ base::Bind(&TCPServerSocketEventDispatcher::StartAccept, params));
+ } else {
+ // Dispatch "onAcceptError" event but don't start another accept to avoid
+ // potential infinite "accepts" if we have a persistent network error.
+ sockets_tcp_server::AcceptErrorInfo accept_error_info;
+ accept_error_info.socket_id = params.socket_id;
+ accept_error_info.result_code = result_code;
+ scoped_ptr<base::ListValue> args =
+ sockets_tcp_server::OnAcceptError::Create(accept_error_info);
+ scoped_ptr<Event> event(new Event(
+ events::SOCKETS_TCP_SERVER_ON_ACCEPT_ERROR,
+ sockets_tcp_server::OnAcceptError::kEventName, std::move(args)));
+ PostEvent(params, std::move(event));
+
+ // Since we got an error, the socket is now "paused" until the application
+ // "resumes" it.
+ ResumableTCPServerSocket* socket =
+ params.server_sockets->Get(params.extension_id, params.socket_id);
+ if (socket) {
+ socket->set_paused(true);
+ }
+ }
+}
+
+// static
+void TCPServerSocketEventDispatcher::PostEvent(const AcceptParams& params,
+ scoped_ptr<Event> event) {
+ DCHECK_CURRENTLY_ON(params.thread_id);
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&DispatchEvent, params.browser_context_id, params.extension_id,
+ base::Passed(std::move(event))));
+}
+
+// static
+void TCPServerSocketEventDispatcher::DispatchEvent(
+ void* browser_context_id,
+ const std::string& extension_id,
+ scoped_ptr<Event> event) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ content::BrowserContext* context =
+ reinterpret_cast<content::BrowserContext*>(browser_context_id);
+ if (!extensions::ExtensionsBrowserClient::Get()->IsValidContext(context))
+ return;
+ EventRouter* router = EventRouter::Get(context);
+ if (router)
+ router->DispatchEventToExtension(extension_id, std::move(event));
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h b/chromium/extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h
new file mode 100644
index 00000000000..e792b829cde
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h
@@ -0,0 +1,101 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SOCKETS_TCP_SERVER_TCP_SERVER_SOCKET_EVENT_DISPATCHER_H_
+#define EXTENSIONS_BROWSER_API_SOCKETS_TCP_SERVER_TCP_SERVER_SOCKET_EVENT_DISPATCHER_H_
+
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/browser/api/sockets_tcp/sockets_tcp_api.h"
+#include "extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+struct Event;
+class ResumableTCPSocket;
+}
+
+namespace extensions {
+namespace api {
+
+// Dispatch events related to "sockets.tcp" sockets from callback on native
+// socket instances. There is one instance per profile.
+class TCPServerSocketEventDispatcher
+ : public BrowserContextKeyedAPI,
+ public base::SupportsWeakPtr<TCPServerSocketEventDispatcher> {
+ public:
+ explicit TCPServerSocketEventDispatcher(content::BrowserContext* context);
+ ~TCPServerSocketEventDispatcher() override;
+
+ // Server socket is active, start accepting connections from it.
+ void OnServerSocketListen(const std::string& extension_id, int socket_id);
+
+ // Server socket is active again, start accepting connections from it.
+ void OnServerSocketResume(const std::string& extension_id, int socket_id);
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<TCPServerSocketEventDispatcher>*
+ GetFactoryInstance();
+
+ // Convenience method to get the SocketEventDispatcher for a profile.
+ static TCPServerSocketEventDispatcher* Get(content::BrowserContext* context);
+
+ private:
+ typedef ApiResourceManager<ResumableTCPServerSocket>::ApiResourceData
+ ServerSocketData;
+ typedef ApiResourceManager<ResumableTCPSocket>::ApiResourceData
+ ClientSocketData;
+ friend class BrowserContextKeyedAPIFactory<TCPServerSocketEventDispatcher>;
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "TCPServerSocketEventDispatcher"; }
+ static const bool kServiceHasOwnInstanceInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ // base::Bind supports methods with up to 6 parameters. AcceptParams is used
+ // as a workaround that limitation for invoking StartAccept.
+ struct AcceptParams {
+ AcceptParams();
+ AcceptParams(const AcceptParams& other);
+ ~AcceptParams();
+
+ content::BrowserThread::ID thread_id;
+ void* browser_context_id;
+ std::string extension_id;
+ scoped_refptr<ServerSocketData> server_sockets;
+ scoped_refptr<ClientSocketData> client_sockets;
+ int socket_id;
+ };
+
+ // Start an accept and register a callback.
+ void StartSocketAccept(const std::string& extension_id, int socket_id);
+
+ // Start an accept and register a callback.
+ static void StartAccept(const AcceptParams& params);
+
+ // Called when socket accepts a new connection.
+ static void AcceptCallback(const AcceptParams& params,
+ int result_code,
+ scoped_ptr<net::TCPClientSocket> socket);
+
+ // Post an extension event from |thread_id| to UI thread
+ static void PostEvent(const AcceptParams& params, scoped_ptr<Event> event);
+
+ // Dispatch an extension event on to EventRouter instance on UI thread.
+ static void DispatchEvent(void* browser_context_id,
+ const std::string& extension_id,
+ scoped_ptr<Event> event);
+
+ // Usually IO thread (except for unit testing).
+ content::BrowserThread::ID thread_id_;
+ content::BrowserContext* const browser_context_;
+ scoped_refptr<ServerSocketData> server_sockets_;
+ scoped_refptr<ClientSocketData> client_sockets_;
+};
+
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SOCKETS_TCP_SERVER_TCP_SERVER_SOCKET_EVENT_DISPATCHER_H_
diff --git a/chromium/extensions/browser/api/sockets_udp/OWNERS b/chromium/extensions/browser/api/sockets_udp/OWNERS
new file mode 100644
index 00000000000..3e30c826edc
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_udp/OWNERS
@@ -0,0 +1 @@
+rpaquay@chromium.org
diff --git a/chromium/extensions/browser/api/sockets_udp/sockets_udp_api.cc b/chromium/extensions/browser/api/sockets_udp/sockets_udp_api.cc
new file mode 100644
index 00000000000..dfefb738ce8
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_udp/sockets_udp_api.cc
@@ -0,0 +1,526 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/sockets_udp/sockets_udp_api.h"
+
+#include "content/public/common/socket_permission_request.h"
+#include "extensions/browser/api/socket/udp_socket.h"
+#include "extensions/browser/api/sockets_udp/udp_socket_event_dispatcher.h"
+#include "extensions/common/api/sockets/sockets_manifest_data.h"
+#include "net/base/net_errors.h"
+
+namespace extensions {
+namespace api {
+
+using content::SocketPermissionRequest;
+
+const char kSocketNotFoundError[] = "Socket not found";
+const char kPermissionError[] = "App does not have permission";
+const char kWildcardAddress[] = "*";
+const int kWildcardPort = 0;
+
+UDPSocketAsyncApiFunction::~UDPSocketAsyncApiFunction() {}
+
+scoped_ptr<SocketResourceManagerInterface>
+UDPSocketAsyncApiFunction::CreateSocketResourceManager() {
+ return scoped_ptr<SocketResourceManagerInterface>(
+ new SocketResourceManager<ResumableUDPSocket>());
+}
+
+ResumableUDPSocket* UDPSocketAsyncApiFunction::GetUdpSocket(int socket_id) {
+ return static_cast<ResumableUDPSocket*>(GetSocket(socket_id));
+}
+
+UDPSocketExtensionWithDnsLookupFunction::
+ ~UDPSocketExtensionWithDnsLookupFunction() {}
+
+scoped_ptr<SocketResourceManagerInterface>
+UDPSocketExtensionWithDnsLookupFunction::CreateSocketResourceManager() {
+ return scoped_ptr<SocketResourceManagerInterface>(
+ new SocketResourceManager<ResumableUDPSocket>());
+}
+
+ResumableUDPSocket* UDPSocketExtensionWithDnsLookupFunction::GetUdpSocket(
+ int socket_id) {
+ return static_cast<ResumableUDPSocket*>(GetSocket(socket_id));
+}
+
+sockets_udp::SocketInfo CreateSocketInfo(int socket_id,
+ ResumableUDPSocket* socket) {
+ sockets_udp::SocketInfo socket_info;
+ // This represents what we know about the socket, and does not call through
+ // to the system.
+ socket_info.socket_id = socket_id;
+ if (!socket->name().empty()) {
+ socket_info.name.reset(new std::string(socket->name()));
+ }
+ socket_info.persistent = socket->persistent();
+ if (socket->buffer_size() > 0) {
+ socket_info.buffer_size.reset(new int(socket->buffer_size()));
+ }
+ socket_info.paused = socket->paused();
+
+ // Grab the local address as known by the OS.
+ net::IPEndPoint localAddress;
+ if (socket->GetLocalAddress(&localAddress)) {
+ socket_info.local_address.reset(
+ new std::string(localAddress.ToStringWithoutPort()));
+ socket_info.local_port.reset(new int(localAddress.port()));
+ }
+
+ return socket_info;
+}
+
+void SetSocketProperties(ResumableUDPSocket* socket,
+ sockets_udp::SocketProperties* properties) {
+ if (properties->name.get()) {
+ socket->set_name(*properties->name.get());
+ }
+ if (properties->persistent.get()) {
+ socket->set_persistent(*properties->persistent.get());
+ }
+ if (properties->buffer_size.get()) {
+ socket->set_buffer_size(*properties->buffer_size.get());
+ }
+}
+
+SocketsUdpCreateFunction::SocketsUdpCreateFunction() {}
+
+SocketsUdpCreateFunction::~SocketsUdpCreateFunction() {}
+
+bool SocketsUdpCreateFunction::Prepare() {
+ params_ = sockets_udp::Create::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpCreateFunction::Work() {
+ ResumableUDPSocket* socket = new ResumableUDPSocket(extension_->id());
+
+ sockets_udp::SocketProperties* properties = params_.get()->properties.get();
+ if (properties) {
+ SetSocketProperties(socket, properties);
+ }
+
+ sockets_udp::CreateInfo create_info;
+ create_info.socket_id = AddSocket(socket);
+ results_ = sockets_udp::Create::Results::Create(create_info);
+}
+
+SocketsUdpUpdateFunction::SocketsUdpUpdateFunction() {}
+
+SocketsUdpUpdateFunction::~SocketsUdpUpdateFunction() {}
+
+bool SocketsUdpUpdateFunction::Prepare() {
+ params_ = sockets_udp::Update::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpUpdateFunction::Work() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ SetSocketProperties(socket, &params_.get()->properties);
+ results_ = sockets_udp::Update::Results::Create();
+}
+
+SocketsUdpSetPausedFunction::SocketsUdpSetPausedFunction()
+ : socket_event_dispatcher_(NULL) {}
+
+SocketsUdpSetPausedFunction::~SocketsUdpSetPausedFunction() {}
+
+bool SocketsUdpSetPausedFunction::Prepare() {
+ params_ = api::sockets_udp::SetPaused::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ socket_event_dispatcher_ = UDPSocketEventDispatcher::Get(browser_context());
+ DCHECK(socket_event_dispatcher_)
+ << "There is no socket event dispatcher. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "UDPSocketEventDispatcher.";
+ return socket_event_dispatcher_ != NULL;
+}
+
+void SocketsUdpSetPausedFunction::Work() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ if (socket->paused() != params_->paused) {
+ socket->set_paused(params_->paused);
+ if (socket->IsBound() && !params_->paused) {
+ socket_event_dispatcher_->OnSocketResume(extension_->id(),
+ params_->socket_id);
+ }
+ }
+
+ results_ = sockets_udp::SetPaused::Results::Create();
+}
+
+SocketsUdpBindFunction::SocketsUdpBindFunction()
+ : socket_event_dispatcher_(NULL) {}
+
+SocketsUdpBindFunction::~SocketsUdpBindFunction() {}
+
+bool SocketsUdpBindFunction::Prepare() {
+ params_ = sockets_udp::Bind::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ socket_event_dispatcher_ = UDPSocketEventDispatcher::Get(browser_context());
+ DCHECK(socket_event_dispatcher_)
+ << "There is no socket event dispatcher. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "UDPSocketEventDispatcher.";
+ return socket_event_dispatcher_ != NULL;
+}
+
+void SocketsUdpBindFunction::AsyncWorkStart() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ content::SocketPermissionRequest param(
+ SocketPermissionRequest::UDP_BIND, params_->address, params_->port);
+ if (!SocketsManifestData::CheckRequest(extension(), param)) {
+ error_ = kPermissionError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ int net_result = socket->Bind(params_->address, params_->port);
+ results_ = sockets_udp::Bind::Results::Create(net_result);
+ if (net_result == net::OK) {
+ socket_event_dispatcher_->OnSocketBind(extension_->id(),
+ params_->socket_id);
+ } else {
+ error_ = net::ErrorToString(net_result);
+ AsyncWorkCompleted();
+ return;
+ }
+
+ OpenFirewallHole(params_->address, params_->socket_id, socket);
+}
+
+SocketsUdpSendFunction::SocketsUdpSendFunction() : io_buffer_size_(0) {}
+
+SocketsUdpSendFunction::~SocketsUdpSendFunction() {}
+
+bool SocketsUdpSendFunction::Prepare() {
+ params_ = sockets_udp::Send::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ io_buffer_size_ = params_->data.size();
+ io_buffer_ = new net::WrappedIOBuffer(params_->data.data());
+
+ return true;
+}
+
+void SocketsUdpSendFunction::AsyncWorkStart() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ content::SocketPermissionRequest param(
+ SocketPermissionRequest::UDP_SEND_TO, params_->address, params_->port);
+ if (!SocketsManifestData::CheckRequest(extension(), param)) {
+ error_ = kPermissionError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ StartDnsLookup(net::HostPortPair(params_->address, params_->port));
+}
+
+void SocketsUdpSendFunction::AfterDnsLookup(int lookup_result) {
+ if (lookup_result == net::OK) {
+ StartSendTo();
+ } else {
+ SetSendResult(lookup_result, -1);
+ }
+}
+
+void SocketsUdpSendFunction::StartSendTo() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ socket->SendTo(io_buffer_, io_buffer_size_, addresses_.front(),
+ base::Bind(&SocketsUdpSendFunction::OnCompleted, this));
+}
+
+void SocketsUdpSendFunction::OnCompleted(int net_result) {
+ if (net_result >= net::OK) {
+ SetSendResult(net::OK, net_result);
+ } else {
+ SetSendResult(net_result, -1);
+ }
+}
+
+void SocketsUdpSendFunction::SetSendResult(int net_result, int bytes_sent) {
+ CHECK(net_result <= net::OK) << "Network status code must be < 0";
+
+ sockets_udp::SendInfo send_info;
+ send_info.result_code = net_result;
+ if (net_result == net::OK) {
+ send_info.bytes_sent.reset(new int(bytes_sent));
+ }
+
+ if (net_result != net::OK)
+ error_ = net::ErrorToString(net_result);
+ results_ = sockets_udp::Send::Results::Create(send_info);
+ AsyncWorkCompleted();
+}
+
+SocketsUdpCloseFunction::SocketsUdpCloseFunction() {}
+
+SocketsUdpCloseFunction::~SocketsUdpCloseFunction() {}
+
+bool SocketsUdpCloseFunction::Prepare() {
+ params_ = sockets_udp::Close::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpCloseFunction::Work() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ socket->Disconnect();
+ RemoveSocket(params_->socket_id);
+ results_ = sockets_udp::Close::Results::Create();
+}
+
+SocketsUdpGetInfoFunction::SocketsUdpGetInfoFunction() {}
+
+SocketsUdpGetInfoFunction::~SocketsUdpGetInfoFunction() {}
+
+bool SocketsUdpGetInfoFunction::Prepare() {
+ params_ = sockets_udp::GetInfo::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpGetInfoFunction::Work() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ sockets_udp::SocketInfo socket_info =
+ CreateSocketInfo(params_->socket_id, socket);
+ results_ = sockets_udp::GetInfo::Results::Create(socket_info);
+}
+
+SocketsUdpGetSocketsFunction::SocketsUdpGetSocketsFunction() {}
+
+SocketsUdpGetSocketsFunction::~SocketsUdpGetSocketsFunction() {}
+
+bool SocketsUdpGetSocketsFunction::Prepare() { return true; }
+
+void SocketsUdpGetSocketsFunction::Work() {
+ std::vector<sockets_udp::SocketInfo> socket_infos;
+ base::hash_set<int>* resource_ids = GetSocketIds();
+ if (resource_ids != NULL) {
+ for (int socket_id : *resource_ids) {
+ ResumableUDPSocket* socket = GetUdpSocket(socket_id);
+ if (socket) {
+ socket_infos.push_back(CreateSocketInfo(socket_id, socket));
+ }
+ }
+ }
+ results_ = sockets_udp::GetSockets::Results::Create(socket_infos);
+}
+
+SocketsUdpJoinGroupFunction::SocketsUdpJoinGroupFunction() {}
+
+SocketsUdpJoinGroupFunction::~SocketsUdpJoinGroupFunction() {}
+
+bool SocketsUdpJoinGroupFunction::Prepare() {
+ params_ = sockets_udp::JoinGroup::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpJoinGroupFunction::Work() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ content::SocketPermissionRequest param(
+ SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP,
+ kWildcardAddress,
+ kWildcardPort);
+ if (!SocketsManifestData::CheckRequest(extension(), param)) {
+ error_ = kPermissionError;
+ return;
+ }
+
+ int net_result = socket->JoinGroup(params_->address);
+ if (net_result != net::OK)
+ error_ = net::ErrorToString(net_result);
+ results_ = sockets_udp::JoinGroup::Results::Create(net_result);
+}
+
+SocketsUdpLeaveGroupFunction::SocketsUdpLeaveGroupFunction() {}
+
+SocketsUdpLeaveGroupFunction::~SocketsUdpLeaveGroupFunction() {}
+
+bool SocketsUdpLeaveGroupFunction::Prepare() {
+ params_ = api::sockets_udp::LeaveGroup::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpLeaveGroupFunction::Work() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ content::SocketPermissionRequest param(
+ SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP,
+ kWildcardAddress,
+ kWildcardPort);
+ if (!SocketsManifestData::CheckRequest(extension(), param)) {
+ error_ = kPermissionError;
+ return;
+ }
+
+ int net_result = socket->LeaveGroup(params_->address);
+ if (net_result != net::OK)
+ error_ = net::ErrorToString(net_result);
+ results_ = sockets_udp::LeaveGroup::Results::Create(net_result);
+}
+
+SocketsUdpSetMulticastTimeToLiveFunction::
+ SocketsUdpSetMulticastTimeToLiveFunction() {}
+
+SocketsUdpSetMulticastTimeToLiveFunction::
+ ~SocketsUdpSetMulticastTimeToLiveFunction() {}
+
+bool SocketsUdpSetMulticastTimeToLiveFunction::Prepare() {
+ params_ = api::sockets_udp::SetMulticastTimeToLive::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpSetMulticastTimeToLiveFunction::Work() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ int net_result = socket->SetMulticastTimeToLive(params_->ttl);
+ if (net_result != net::OK)
+ error_ = net::ErrorToString(net_result);
+ results_ = sockets_udp::SetMulticastTimeToLive::Results::Create(net_result);
+}
+
+SocketsUdpSetMulticastLoopbackModeFunction::
+ SocketsUdpSetMulticastLoopbackModeFunction() {}
+
+SocketsUdpSetMulticastLoopbackModeFunction::
+ ~SocketsUdpSetMulticastLoopbackModeFunction() {}
+
+bool SocketsUdpSetMulticastLoopbackModeFunction::Prepare() {
+ params_ = api::sockets_udp::SetMulticastLoopbackMode::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpSetMulticastLoopbackModeFunction::Work() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ int net_result = socket->SetMulticastLoopbackMode(params_->enabled);
+ if (net_result != net::OK)
+ error_ = net::ErrorToString(net_result);
+ results_ = sockets_udp::SetMulticastLoopbackMode::Results::Create(net_result);
+}
+
+SocketsUdpGetJoinedGroupsFunction::SocketsUdpGetJoinedGroupsFunction() {}
+
+SocketsUdpGetJoinedGroupsFunction::~SocketsUdpGetJoinedGroupsFunction() {}
+
+bool SocketsUdpGetJoinedGroupsFunction::Prepare() {
+ params_ = api::sockets_udp::GetJoinedGroups::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpGetJoinedGroupsFunction::Work() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ content::SocketPermissionRequest param(
+ SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP,
+ kWildcardAddress,
+ kWildcardPort);
+ if (!SocketsManifestData::CheckRequest(extension(), param)) {
+ error_ = kPermissionError;
+ return;
+ }
+
+ const std::vector<std::string>& groups = socket->GetJoinedGroups();
+ results_ = sockets_udp::GetJoinedGroups::Results::Create(groups);
+}
+
+SocketsUdpSetBroadcastFunction::SocketsUdpSetBroadcastFunction() {
+}
+
+SocketsUdpSetBroadcastFunction::~SocketsUdpSetBroadcastFunction() {
+}
+
+bool SocketsUdpSetBroadcastFunction::Prepare() {
+ params_ = api::sockets_udp::SetBroadcast::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpSetBroadcastFunction::Work() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ int net_result = socket->SetBroadcast(params_->enabled);
+ if (net_result != net::OK) {
+ error_ = net::ErrorToString(net_result);
+ }
+ results_ = sockets_udp::SetBroadcast::Results::Create(net_result);
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/sockets_udp/sockets_udp_api.h b/chromium/extensions/browser/api/sockets_udp/sockets_udp_api.h
new file mode 100644
index 00000000000..3ade564eb91
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_udp/sockets_udp_api.h
@@ -0,0 +1,300 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SOCKETS_UDP_SOCKETS_UDP_API_H_
+#define EXTENSIONS_BROWSER_API_SOCKETS_UDP_SOCKETS_UDP_API_H_
+
+#include <stddef.h>
+
+#include "base/gtest_prod_util.h"
+#include "extensions/browser/api/socket/socket_api.h"
+#include "extensions/common/api/sockets_udp.h"
+
+namespace extensions {
+class ResumableUDPSocket;
+}
+
+namespace extensions {
+namespace api {
+
+class UDPSocketEventDispatcher;
+
+class UDPSocketAsyncApiFunction : public SocketAsyncApiFunction {
+ protected:
+ ~UDPSocketAsyncApiFunction() override;
+
+ scoped_ptr<SocketResourceManagerInterface> CreateSocketResourceManager()
+ override;
+
+ ResumableUDPSocket* GetUdpSocket(int socket_id);
+};
+
+class UDPSocketExtensionWithDnsLookupFunction
+ : public SocketExtensionWithDnsLookupFunction {
+ protected:
+ ~UDPSocketExtensionWithDnsLookupFunction() override;
+
+ scoped_ptr<SocketResourceManagerInterface> CreateSocketResourceManager()
+ override;
+
+ ResumableUDPSocket* GetUdpSocket(int socket_id);
+};
+
+class SocketsUdpCreateFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.create", SOCKETS_UDP_CREATE)
+
+ SocketsUdpCreateFunction();
+
+ protected:
+ ~SocketsUdpCreateFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(SocketsUdpUnitTest, Create);
+ scoped_ptr<sockets_udp::Create::Params> params_;
+};
+
+class SocketsUdpUpdateFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.update", SOCKETS_UDP_UPDATE)
+
+ SocketsUdpUpdateFunction();
+
+ protected:
+ ~SocketsUdpUpdateFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_udp::Update::Params> params_;
+};
+
+class SocketsUdpSetPausedFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.setPaused", SOCKETS_UDP_SETPAUSED)
+
+ SocketsUdpSetPausedFunction();
+
+ protected:
+ ~SocketsUdpSetPausedFunction() override;
+
+ // AsyncApiFunction
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_udp::SetPaused::Params> params_;
+ UDPSocketEventDispatcher* socket_event_dispatcher_;
+};
+
+class SocketsUdpBindFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.bind", SOCKETS_UDP_BIND)
+
+ SocketsUdpBindFunction();
+
+ protected:
+ ~SocketsUdpBindFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+
+ private:
+ scoped_ptr<sockets_udp::Bind::Params> params_;
+ UDPSocketEventDispatcher* socket_event_dispatcher_;
+};
+
+class SocketsUdpSendFunction : public UDPSocketExtensionWithDnsLookupFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.send", SOCKETS_UDP_SEND)
+
+ SocketsUdpSendFunction();
+
+ protected:
+ ~SocketsUdpSendFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void AsyncWorkStart() override;
+ void OnCompleted(int net_result);
+ void SetSendResult(int net_result, int bytes_sent);
+
+ // SocketExtensionWithDnsLookupFunction:
+ void AfterDnsLookup(int lookup_result) override;
+
+ private:
+ void StartSendTo();
+
+ scoped_ptr<sockets_udp::Send::Params> params_;
+ scoped_refptr<net::IOBuffer> io_buffer_;
+ size_t io_buffer_size_;
+};
+
+class SocketsUdpCloseFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.close", SOCKETS_UDP_CLOSE)
+
+ SocketsUdpCloseFunction();
+
+ protected:
+ ~SocketsUdpCloseFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_udp::Close::Params> params_;
+};
+
+class SocketsUdpGetInfoFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.getInfo", SOCKETS_UDP_GETINFO)
+
+ SocketsUdpGetInfoFunction();
+
+ protected:
+ ~SocketsUdpGetInfoFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_udp::GetInfo::Params> params_;
+};
+
+class SocketsUdpGetSocketsFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.getSockets", SOCKETS_UDP_GETSOCKETS)
+
+ SocketsUdpGetSocketsFunction();
+
+ protected:
+ ~SocketsUdpGetSocketsFunction() override;
+
+ // AsyncApiFunction:
+ bool Prepare() override;
+ void Work() override;
+};
+
+class SocketsUdpJoinGroupFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.joinGroup", SOCKETS_UDP_JOINGROUP)
+
+ SocketsUdpJoinGroupFunction();
+
+ protected:
+ ~SocketsUdpJoinGroupFunction() override;
+
+ // AsyncApiFunction
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_udp::JoinGroup::Params> params_;
+};
+
+class SocketsUdpLeaveGroupFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.leaveGroup", SOCKETS_UDP_LEAVEGROUP)
+
+ SocketsUdpLeaveGroupFunction();
+
+ protected:
+ ~SocketsUdpLeaveGroupFunction() override;
+
+ // AsyncApiFunction
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_udp::LeaveGroup::Params> params_;
+};
+
+class SocketsUdpSetMulticastTimeToLiveFunction
+ : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.setMulticastTimeToLive",
+ SOCKETS_UDP_SETMULTICASTTIMETOLIVE)
+
+ SocketsUdpSetMulticastTimeToLiveFunction();
+
+ protected:
+ ~SocketsUdpSetMulticastTimeToLiveFunction() override;
+
+ // AsyncApiFunction
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_udp::SetMulticastTimeToLive::Params> params_;
+};
+
+class SocketsUdpSetMulticastLoopbackModeFunction
+ : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.setMulticastLoopbackMode",
+ SOCKETS_UDP_SETMULTICASTLOOPBACKMODE)
+
+ SocketsUdpSetMulticastLoopbackModeFunction();
+
+ protected:
+ ~SocketsUdpSetMulticastLoopbackModeFunction() override;
+
+ // AsyncApiFunction
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_udp::SetMulticastLoopbackMode::Params> params_;
+};
+
+class SocketsUdpGetJoinedGroupsFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.getJoinedGroups",
+ SOCKETS_UDP_GETJOINEDGROUPS)
+
+ SocketsUdpGetJoinedGroupsFunction();
+
+ protected:
+ ~SocketsUdpGetJoinedGroupsFunction() override;
+
+ // AsyncApiFunction
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_udp::GetJoinedGroups::Params> params_;
+};
+
+class SocketsUdpSetBroadcastFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.setBroadcast",
+ SOCKETS_UDP_SETBROADCAST)
+
+ SocketsUdpSetBroadcastFunction();
+
+ protected:
+ ~SocketsUdpSetBroadcastFunction() override;
+
+ // AsyncApiFunction
+ bool Prepare() override;
+ void Work() override;
+
+ private:
+ scoped_ptr<sockets_udp::SetBroadcast::Params> params_;
+};
+
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SOCKETS_UDP_SOCKETS_UDP_API_H_
diff --git a/chromium/extensions/browser/api/sockets_udp/sockets_udp_api_unittest.cc b/chromium/extensions/browser/api/sockets_udp/sockets_udp_api_unittest.cc
new file mode 100644
index 00000000000..ebba7bcc1a9
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_udp/sockets_udp_api_unittest.cc
@@ -0,0 +1,50 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/browser/api/socket/socket.h"
+#include "extensions/browser/api/socket/udp_socket.h"
+#include "extensions/browser/api/sockets_udp/sockets_udp_api.h"
+#include "extensions/browser/api_unittest.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+namespace api {
+
+static scoped_ptr<KeyedService> ApiResourceManagerTestFactory(
+ content::BrowserContext* context) {
+ return make_scoped_ptr(new ApiResourceManager<ResumableUDPSocket>(context));
+}
+
+class SocketsUdpUnitTest : public ApiUnitTest {
+ public:
+ void SetUp() override {
+ ApiUnitTest::SetUp();
+
+ ApiResourceManager<ResumableUDPSocket>::GetFactoryInstance()
+ ->SetTestingFactoryAndUse(browser_context(),
+ ApiResourceManagerTestFactory);
+ }
+};
+
+TEST_F(SocketsUdpUnitTest, Create) {
+ // Get BrowserThread
+ content::BrowserThread::ID id;
+ CHECK(content::BrowserThread::GetCurrentThreadIdentifier(&id));
+
+ // Create SocketCreateFunction and put it on BrowserThread
+ SocketsUdpCreateFunction* function = new SocketsUdpCreateFunction();
+ function->set_work_thread_id(id);
+
+ // Run tests
+ scoped_ptr<base::DictionaryValue> result(RunFunctionAndReturnDictionary(
+ function, "[{\"persistent\": true, \"name\": \"foo\"}]"));
+ ASSERT_TRUE(result.get());
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/sockets_udp/sockets_udp_apitest.cc b/chromium/extensions/browser/api/sockets_udp/sockets_udp_apitest.cc
new file mode 100644
index 00000000000..85cbfffdb89
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_udp/sockets_udp_apitest.cc
@@ -0,0 +1,112 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/ref_counted.h"
+#include "base/strings/stringprintf.h"
+#include "extensions/browser/api/dns/host_resolver_wrapper.h"
+#include "extensions/browser/api/dns/mock_host_resolver_creator.h"
+#include "extensions/browser/api/sockets_udp/sockets_udp_api.h"
+#include "extensions/browser/api_test_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/test_util.h"
+#include "extensions/shell/test/shell_apitest.h"
+#include "extensions/shell/test/shell_test.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "extensions/test/result_catcher.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+
+namespace extensions {
+
+const std::string kHostname = "127.0.0.1";
+const int kPort = 8888;
+
+class SocketsUdpApiTest : public ShellApiTest {
+ public:
+ SocketsUdpApiTest()
+ : resolver_event_(true, false),
+ resolver_creator_(new MockHostResolverCreator()) {}
+
+ void SetUpOnMainThread() override {
+ ShellApiTest::SetUpOnMainThread();
+
+ HostResolverWrapper::GetInstance()->SetHostResolverForTesting(
+ resolver_creator_->CreateMockHostResolver());
+ }
+
+ void TearDownOnMainThread() override {
+ HostResolverWrapper::GetInstance()->SetHostResolverForTesting(NULL);
+ resolver_creator_->DeleteMockHostResolver();
+
+ ShellApiTest::TearDownOnMainThread();
+ }
+
+ private:
+ base::WaitableEvent resolver_event_;
+
+ // The MockHostResolver asserts that it's used on the same thread on which
+ // it's created, which is actually a stronger rule than its real counterpart.
+ // But that's fine; it's good practice.
+ scoped_refptr<MockHostResolverCreator> resolver_creator_;
+};
+
+IN_PROC_BROWSER_TEST_F(SocketsUdpApiTest, SocketsUdpCreateGood) {
+ scoped_refptr<api::SocketsUdpCreateFunction> socket_create_function(
+ new api::SocketsUdpCreateFunction());
+ scoped_refptr<Extension> empty_extension = test_util::CreateEmptyExtension();
+
+ socket_create_function->set_extension(empty_extension.get());
+ socket_create_function->set_has_callback(true);
+
+ scoped_ptr<base::Value> result(
+ api_test_utils::RunFunctionAndReturnSingleResult(
+ socket_create_function.get(), "[]", browser_context()));
+
+ base::DictionaryValue* value = NULL;
+ ASSERT_TRUE(result->GetAsDictionary(&value));
+ int socketId = -1;
+ EXPECT_TRUE(value->GetInteger("socketId", &socketId));
+ ASSERT_TRUE(socketId > 0);
+}
+
+IN_PROC_BROWSER_TEST_F(SocketsUdpApiTest, SocketsUdpExtension) {
+ scoped_ptr<net::SpawnedTestServer> test_server(new net::SpawnedTestServer(
+ net::SpawnedTestServer::TYPE_UDP_ECHO, net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("net/data"))));
+ EXPECT_TRUE(test_server->Start());
+
+ net::HostPortPair host_port_pair = test_server->host_port_pair();
+ int port = host_port_pair.port();
+ ASSERT_TRUE(port > 0);
+
+ // Test that sendTo() is properly resolving hostnames.
+ host_port_pair.set_host("LOCALhost");
+
+ ResultCatcher catcher;
+ catcher.RestrictToBrowserContext(browser_context());
+
+ ExtensionTestMessageListener listener("info_please", true);
+
+ ASSERT_TRUE(LoadApp("sockets_udp/api"));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+ listener.Reply(
+ base::StringPrintf("udp:%s:%d", host_port_pair.host().c_str(), port));
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+// See crbug.com/321451.
+IN_PROC_BROWSER_TEST_F(SocketsUdpApiTest, DISABLED_SocketsUdpMulticast) {
+ ResultCatcher catcher;
+ catcher.RestrictToBrowserContext(browser_context());
+ ExtensionTestMessageListener listener("info_please", true);
+ ASSERT_TRUE(LoadApp("sockets_udp/api"));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+ listener.Reply(
+ base::StringPrintf("multicast:%s:%d", kHostname.c_str(), kPort));
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/sockets_udp/udp_socket_event_dispatcher.cc b/chromium/extensions/browser/api/sockets_udp/udp_socket_event_dispatcher.cc
new file mode 100644
index 00000000000..07e2a22f929
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_udp/udp_socket_event_dispatcher.cc
@@ -0,0 +1,188 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/sockets_udp/udp_socket_event_dispatcher.h"
+
+#include <utility>
+
+#include "base/lazy_instance.h"
+#include "extensions/browser/api/socket/udp_socket.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "net/base/net_errors.h"
+
+namespace extensions {
+namespace api {
+
+using content::BrowserThread;
+
+static base::LazyInstance<
+ BrowserContextKeyedAPIFactory<UDPSocketEventDispatcher> > g_factory =
+ LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<UDPSocketEventDispatcher>*
+UDPSocketEventDispatcher::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+// static
+UDPSocketEventDispatcher* UDPSocketEventDispatcher::Get(
+ content::BrowserContext* context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ return BrowserContextKeyedAPIFactory<UDPSocketEventDispatcher>::Get(context);
+}
+
+UDPSocketEventDispatcher::UDPSocketEventDispatcher(
+ content::BrowserContext* context)
+ : thread_id_(Socket::kThreadId), browser_context_(context) {
+ ApiResourceManager<ResumableUDPSocket>* manager =
+ ApiResourceManager<ResumableUDPSocket>::Get(browser_context_);
+ DCHECK(manager)
+ << "There is no socket manager. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "ApiResourceManager<ResumableUDPSocket>.";
+ sockets_ = manager->data_;
+}
+
+UDPSocketEventDispatcher::~UDPSocketEventDispatcher() {}
+
+UDPSocketEventDispatcher::ReceiveParams::ReceiveParams() {}
+
+UDPSocketEventDispatcher::ReceiveParams::ReceiveParams(
+ const ReceiveParams& other) = default;
+
+UDPSocketEventDispatcher::ReceiveParams::~ReceiveParams() {}
+
+void UDPSocketEventDispatcher::OnSocketBind(const std::string& extension_id,
+ int socket_id) {
+ OnSocketResume(extension_id, socket_id);
+}
+
+void UDPSocketEventDispatcher::OnSocketResume(const std::string& extension_id,
+ int socket_id) {
+ DCHECK_CURRENTLY_ON(thread_id_);
+
+ ReceiveParams params;
+ params.thread_id = thread_id_;
+ params.browser_context_id = browser_context_;
+ params.extension_id = extension_id;
+ params.sockets = sockets_;
+ params.socket_id = socket_id;
+
+ StartReceive(params);
+}
+
+/* static */
+void UDPSocketEventDispatcher::StartReceive(const ReceiveParams& params) {
+ DCHECK_CURRENTLY_ON(params.thread_id);
+
+ ResumableUDPSocket* socket =
+ params.sockets->Get(params.extension_id, params.socket_id);
+ if (socket == NULL) {
+ // This can happen if the socket is closed while our callback is active.
+ return;
+ }
+ DCHECK(params.extension_id == socket->owner_extension_id())
+ << "Socket has wrong owner.";
+
+ // Don't start another read if the socket has been paused.
+ if (socket->paused())
+ return;
+
+ int buffer_size = (socket->buffer_size() <= 0 ? 4096 : socket->buffer_size());
+ socket->RecvFrom(
+ buffer_size,
+ base::Bind(&UDPSocketEventDispatcher::ReceiveCallback, params));
+}
+
+/* static */
+void UDPSocketEventDispatcher::ReceiveCallback(
+ const ReceiveParams& params,
+ int bytes_read,
+ scoped_refptr<net::IOBuffer> io_buffer,
+ const std::string& address,
+ uint16_t port) {
+ DCHECK_CURRENTLY_ON(params.thread_id);
+
+ // If |bytes_read| == 0, the message contained no data.
+ // If |bytes_read| < 0, there was a network error, and |bytes_read| is a value
+ // from "net::ERR_".
+
+ if (bytes_read >= 0) {
+ // Dispatch "onReceive" event.
+ sockets_udp::ReceiveInfo receive_info;
+ receive_info.socket_id = params.socket_id;
+ receive_info.data.assign(io_buffer->data(), io_buffer->data() + bytes_read);
+ receive_info.remote_address = address;
+ receive_info.remote_port = port;
+ scoped_ptr<base::ListValue> args =
+ sockets_udp::OnReceive::Create(receive_info);
+ scoped_ptr<Event> event(new Event(events::SOCKETS_UDP_ON_RECEIVE,
+ sockets_udp::OnReceive::kEventName,
+ std::move(args)));
+ PostEvent(params, std::move(event));
+
+ // Post a task to delay the read until the socket is available, as
+ // calling StartReceive at this point would error with ERR_IO_PENDING.
+ BrowserThread::PostTask(
+ params.thread_id,
+ FROM_HERE,
+ base::Bind(&UDPSocketEventDispatcher::StartReceive, params));
+ } else if (bytes_read == net::ERR_IO_PENDING) {
+ // This happens when resuming a socket which already had an
+ // active "recv" callback.
+ } else {
+ // Dispatch "onReceiveError" event but don't start another read to avoid
+ // potential infinite reads if we have a persistent network error.
+ sockets_udp::ReceiveErrorInfo receive_error_info;
+ receive_error_info.socket_id = params.socket_id;
+ receive_error_info.result_code = bytes_read;
+ scoped_ptr<base::ListValue> args =
+ sockets_udp::OnReceiveError::Create(receive_error_info);
+ scoped_ptr<Event> event(new Event(events::SOCKETS_UDP_ON_RECEIVE_ERROR,
+ sockets_udp::OnReceiveError::kEventName,
+ std::move(args)));
+ PostEvent(params, std::move(event));
+
+ // Since we got an error, the socket is now "paused" until the application
+ // "resumes" it.
+ ResumableUDPSocket* socket =
+ params.sockets->Get(params.extension_id, params.socket_id);
+ if (socket) {
+ socket->set_paused(true);
+ }
+ }
+}
+
+/* static */
+void UDPSocketEventDispatcher::PostEvent(const ReceiveParams& params,
+ scoped_ptr<Event> event) {
+ DCHECK_CURRENTLY_ON(params.thread_id);
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&DispatchEvent, params.browser_context_id, params.extension_id,
+ base::Passed(std::move(event))));
+}
+
+/*static*/
+void UDPSocketEventDispatcher::DispatchEvent(void* browser_context_id,
+ const std::string& extension_id,
+ scoped_ptr<Event> event) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ content::BrowserContext* context =
+ reinterpret_cast<content::BrowserContext*>(browser_context_id);
+ if (!extensions::ExtensionsBrowserClient::Get()->IsValidContext(context))
+ return;
+ EventRouter* router = EventRouter::Get(context);
+ if (router)
+ router->DispatchEventToExtension(extension_id, std::move(event));
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/sockets_udp/udp_socket_event_dispatcher.h b/chromium/extensions/browser/api/sockets_udp/udp_socket_event_dispatcher.h
new file mode 100644
index 00000000000..ff8f111c295
--- /dev/null
+++ b/chromium/extensions/browser/api/sockets_udp/udp_socket_event_dispatcher.h
@@ -0,0 +1,98 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SOCKETS_UDP_UDP_SOCKET_EVENT_DISPATCHER_H_
+#define EXTENSIONS_BROWSER_API_SOCKETS_UDP_UDP_SOCKET_EVENT_DISPATCHER_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/browser/api/sockets_udp/sockets_udp_api.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+struct Event;
+class ResumableUDPSocket;
+}
+
+namespace extensions {
+namespace api {
+
+// Dispatch events related to "sockets.udp" sockets from callback on native
+// socket instances. There is one instance per profile.
+class UDPSocketEventDispatcher
+ : public BrowserContextKeyedAPI,
+ public base::SupportsWeakPtr<UDPSocketEventDispatcher> {
+ public:
+ explicit UDPSocketEventDispatcher(content::BrowserContext* context);
+ ~UDPSocketEventDispatcher() override;
+
+ // Socket is active, start receving from it.
+ void OnSocketBind(const std::string& extension_id, int socket_id);
+
+ // Socket is active again, start receiving data from it.
+ void OnSocketResume(const std::string& extension_id, int socket_id);
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<UDPSocketEventDispatcher>*
+ GetFactoryInstance();
+
+ // Convenience method to get the SocketEventDispatcher for a profile.
+ static UDPSocketEventDispatcher* Get(content::BrowserContext* context);
+
+ private:
+ typedef ApiResourceManager<ResumableUDPSocket>::ApiResourceData SocketData;
+ friend class BrowserContextKeyedAPIFactory<UDPSocketEventDispatcher>;
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "UDPSocketEventDispatcher"; }
+ static const bool kServiceHasOwnInstanceInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ // base::Bind supports methods with up to 6 parameters. ReceiveParams is used
+ // as a workaround that limitation for invoking StartReceive.
+ struct ReceiveParams {
+ ReceiveParams();
+ ReceiveParams(const ReceiveParams& other);
+ ~ReceiveParams();
+
+ content::BrowserThread::ID thread_id;
+ void* browser_context_id;
+ std::string extension_id;
+ scoped_refptr<SocketData> sockets;
+ int socket_id;
+ };
+
+ // Start a receive and register a callback.
+ static void StartReceive(const ReceiveParams& params);
+
+ // Called when socket receive data.
+ static void ReceiveCallback(const ReceiveParams& params,
+ int bytes_read,
+ scoped_refptr<net::IOBuffer> io_buffer,
+ const std::string& address,
+ uint16_t port);
+
+ // Post an extension event from IO to UI thread
+ static void PostEvent(const ReceiveParams& params, scoped_ptr<Event> event);
+
+ // Dispatch an extension event on to EventRouter instance on UI thread.
+ static void DispatchEvent(void* browser_context_id,
+ const std::string& extension_id,
+ scoped_ptr<Event> event);
+
+ // Usually IO thread (except for unit testing).
+ content::BrowserThread::ID thread_id_;
+ content::BrowserContext* const browser_context_;
+ scoped_refptr<SocketData> sockets_;
+};
+
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SOCKETS_UDP_UDP_SOCKET_EVENT_DISPATCHER_H_
diff --git a/chromium/extensions/browser/api/storage/OWNERS b/chromium/extensions/browser/api/storage/OWNERS
new file mode 100644
index 00000000000..aad0b9203df
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/OWNERS
@@ -0,0 +1 @@
+rdevlin.cronin@chromium.org
diff --git a/chromium/extensions/browser/api/storage/local_value_store_cache.cc b/chromium/extensions/browser/api/storage/local_value_store_cache.cc
new file mode 100644
index 00000000000..920d1305db3
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/local_value_store_cache.cc
@@ -0,0 +1,91 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/storage/local_value_store_cache.h"
+
+#include <stddef.h>
+
+#include <limits>
+
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/api/storage/weak_unlimited_settings_storage.h"
+#include "extensions/browser/value_store/value_store_factory.h"
+#include "extensions/common/api/storage.h"
+#include "extensions/common/permissions/permissions_data.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+namespace {
+
+// Returns the quota limit for local storage, taken from the schema in
+// extensions/common/api/storage.json.
+SettingsStorageQuotaEnforcer::Limits GetLocalQuotaLimits() {
+ SettingsStorageQuotaEnforcer::Limits limits = {
+ static_cast<size_t>(api::storage::local::QUOTA_BYTES),
+ std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()};
+ return limits;
+}
+
+} // namespace
+
+LocalValueStoreCache::LocalValueStoreCache(
+ const scoped_refptr<ValueStoreFactory>& factory)
+ : storage_factory_(factory), quota_(GetLocalQuotaLimits()) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+LocalValueStoreCache::~LocalValueStoreCache() {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+}
+
+void LocalValueStoreCache::RunWithValueStoreForExtension(
+ const StorageCallback& callback,
+ scoped_refptr<const Extension> extension) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+
+ ValueStore* storage = GetStorage(extension.get());
+
+ // A neat way to implement unlimited storage; if the extension has the
+ // unlimited storage permission, force through all calls to Set().
+ if (extension->permissions_data()->HasAPIPermission(
+ APIPermission::kUnlimitedStorage)) {
+ WeakUnlimitedSettingsStorage unlimited_storage(storage);
+ callback.Run(&unlimited_storage);
+ } else {
+ callback.Run(storage);
+ }
+}
+
+void LocalValueStoreCache::DeleteStorageSoon(const std::string& extension_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ storage_map_.erase(extension_id);
+ storage_factory_->DeleteSettings(settings_namespace::LOCAL,
+ ValueStoreFactory::ModelType::APP,
+ extension_id);
+ storage_factory_->DeleteSettings(settings_namespace::LOCAL,
+ ValueStoreFactory::ModelType::EXTENSION,
+ extension_id);
+}
+
+ValueStore* LocalValueStoreCache::GetStorage(const Extension* extension) {
+ StorageMap::iterator iter = storage_map_.find(extension->id());
+ if (iter != storage_map_.end())
+ return iter->second.get();
+
+ ValueStoreFactory::ModelType model_type =
+ extension->is_app() ? ValueStoreFactory::ModelType::APP
+ : ValueStoreFactory::ModelType::EXTENSION;
+ scoped_ptr<ValueStore> store = storage_factory_->CreateSettingsStore(
+ settings_namespace::LOCAL, model_type, extension->id());
+ linked_ptr<SettingsStorageQuotaEnforcer> storage(
+ new SettingsStorageQuotaEnforcer(quota_, std::move(store)));
+ DCHECK(storage.get());
+
+ storage_map_[extension->id()] = storage;
+ return storage.get();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/storage/local_value_store_cache.h b/chromium/extensions/browser/api/storage/local_value_store_cache.h
new file mode 100644
index 00000000000..c3303a5ef66
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/local_value_store_cache.h
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_STORAGE_LOCAL_VALUE_STORE_CACHE_H_
+#define EXTENSIONS_BROWSER_API_STORAGE_LOCAL_VALUE_STORE_CACHE_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/browser/api/storage/settings_storage_quota_enforcer.h"
+#include "extensions/browser/api/storage/value_store_cache.h"
+
+namespace extensions {
+
+class ValueStoreFactory;
+
+// ValueStoreCache for the LOCAL namespace. It owns a backend for apps and
+// another for extensions. Each backend takes care of persistence.
+class LocalValueStoreCache : public ValueStoreCache {
+ public:
+ explicit LocalValueStoreCache(
+ const scoped_refptr<ValueStoreFactory>& factory);
+ ~LocalValueStoreCache() override;
+
+ // ValueStoreCache implementation:
+ void RunWithValueStoreForExtension(
+ const StorageCallback& callback,
+ scoped_refptr<const Extension> extension) override;
+ void DeleteStorageSoon(const std::string& extension_id) override;
+
+ private:
+ typedef std::map<std::string, linked_ptr<ValueStore> > StorageMap;
+
+ ValueStore* GetStorage(const Extension* extension);
+
+ // The Factory to use for creating new ValueStores.
+ const scoped_refptr<ValueStoreFactory> storage_factory_;
+
+ // Quota limits (see SettingsStorageQuotaEnforcer).
+ const SettingsStorageQuotaEnforcer::Limits quota_;
+
+ // The collection of ValueStores for local storage.
+ StorageMap storage_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(LocalValueStoreCache);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_STORAGE_LOCAL_VALUE_STORE_CACHE_H_
diff --git a/chromium/extensions/browser/api/storage/settings_namespace.cc b/chromium/extensions/browser/api/storage/settings_namespace.cc
new file mode 100644
index 00000000000..a38d060013e
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/settings_namespace.cc
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/storage/settings_namespace.h"
+
+#include "base/logging.h"
+
+namespace extensions {
+
+namespace settings_namespace {
+
+namespace {
+const char kLocalNamespace[] = "local";
+const char kSyncNamespace[] = "sync";
+const char kManagedNamespace[] = "managed";
+} // namespace
+
+std::string ToString(Namespace settings_namespace) {
+ switch (settings_namespace) {
+ case LOCAL:
+ return kLocalNamespace;
+ case SYNC:
+ return kSyncNamespace;
+ case MANAGED:
+ return kManagedNamespace;
+ case INVALID:
+ break;
+ }
+ NOTREACHED();
+ return std::string();
+}
+
+Namespace FromString(const std::string& namespace_string) {
+ if (namespace_string == kLocalNamespace)
+ return LOCAL;
+ if (namespace_string == kSyncNamespace)
+ return SYNC;
+ if (namespace_string == kManagedNamespace)
+ return MANAGED;
+ return INVALID;
+}
+
+} // namespace settings_namespace
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/storage/settings_namespace.h b/chromium/extensions/browser/api/storage/settings_namespace.h
new file mode 100644
index 00000000000..e32612b96c0
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/settings_namespace.h
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_STORAGE_SETTINGS_NAMESPACE_H_
+#define EXTENSIONS_BROWSER_API_STORAGE_SETTINGS_NAMESPACE_H_
+
+#include <string>
+
+namespace extensions {
+
+namespace settings_namespace {
+
+// The namespaces of the storage areas.
+enum Namespace {
+ LOCAL, // "local" i.e. chrome.storage.local
+ SYNC, // "sync" i.e. chrome.storage.sync
+ MANAGED, // "managed" i.e. chrome.storage.managed
+ INVALID
+};
+
+// Converts a namespace to its string representation.
+// Namespace must not be INVALID.
+std::string ToString(Namespace settings_namespace);
+
+// Converts a string representation of a namespace to its namespace, or INVALID
+// if the string doesn't map to one.
+Namespace FromString(const std::string& ns_string);
+
+} // namespace settings_namespace
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_STORAGE_SETTINGS_NAMESPACE_H_
diff --git a/chromium/extensions/browser/api/storage/settings_observer.h b/chromium/extensions/browser/api/storage/settings_observer.h
new file mode 100644
index 00000000000..daa7e6932c0
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/settings_observer.h
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_STORAGE_SETTINGS_OBSERVER_H_
+#define EXTENSIONS_BROWSER_API_STORAGE_SETTINGS_OBSERVER_H_
+
+#include "base/observer_list_threadsafe.h"
+#include "extensions/browser/api/storage/settings_namespace.h"
+
+namespace extensions {
+
+// Interface for classes that listen to changes to extension settings.
+class SettingsObserver {
+ public:
+ // Called when a list of settings have changed for an extension.
+ virtual void OnSettingsChanged(
+ const std::string& extension_id,
+ settings_namespace::Namespace settings_namespace,
+ const std::string& changes_json) = 0;
+
+ virtual ~SettingsObserver() {}
+};
+
+typedef base::ObserverListThreadSafe<SettingsObserver> SettingsObserverList;
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_STORAGE_SETTINGS_OBSERVER_H_
diff --git a/chromium/extensions/browser/api/storage/settings_quota_unittest.cc b/chromium/extensions/browser/api/storage/settings_quota_unittest.cc
new file mode 100644
index 00000000000..3681401a49c
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/settings_quota_unittest.cc
@@ -0,0 +1,597 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/json/json_writer.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "extensions/browser/api/storage/settings_storage_quota_enforcer.h"
+#include "extensions/browser/value_store/testing_value_store.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::DictionaryValue;
+using base::ListValue;
+using base::Value;
+
+namespace extensions {
+
+// To save typing ValueStore::DEFAULTS/IGNORE_QUOTA everywhere.
+const ValueStore::WriteOptions DEFAULTS = ValueStore::DEFAULTS;
+const ValueStore::WriteOptions IGNORE_QUOTA =
+ ValueStore::IGNORE_QUOTA;
+
+class ExtensionSettingsQuotaTest : public testing::Test {
+ public:
+ ExtensionSettingsQuotaTest()
+ : byte_value_1_(1),
+ byte_value_16_("sixteen bytes."),
+ delegate_(new TestingValueStore()) {
+ for (int i = 1; i < 89; ++i) {
+ byte_value_256_.AppendInteger(i);
+ }
+ ValidateByteValues();
+ }
+
+ void ValidateByteValues() {
+ std::string validate_sizes;
+ base::JSONWriter::Write(byte_value_1_, &validate_sizes);
+ ASSERT_EQ(1u, validate_sizes.size());
+ base::JSONWriter::Write(byte_value_16_, &validate_sizes);
+ ASSERT_EQ(16u, validate_sizes.size());
+ base::JSONWriter::Write(byte_value_256_, &validate_sizes);
+ ASSERT_EQ(256u, validate_sizes.size());
+ }
+
+ void TearDown() override { ASSERT_TRUE(storage_.get() != NULL); }
+
+ protected:
+ // Creates |storage_|. Must only be called once.
+ void CreateStorage(
+ size_t quota_bytes, size_t quota_bytes_per_item, size_t max_items) {
+ ASSERT_TRUE(storage_.get() == NULL);
+ SettingsStorageQuotaEnforcer::Limits limits =
+ { quota_bytes, quota_bytes_per_item, max_items };
+ storage_.reset(
+ new SettingsStorageQuotaEnforcer(limits, make_scoped_ptr(delegate_)));
+ }
+
+ // Returns whether the settings in |storage_| and |delegate_| are the same as
+ // |settings|.
+ bool SettingsEqual(const base::DictionaryValue& settings) {
+ return settings.Equals(&storage_->Get()->settings()) &&
+ settings.Equals(&delegate_->Get()->settings());
+ }
+
+ // Values with different serialized sizes.
+ base::FundamentalValue byte_value_1_;
+ base::StringValue byte_value_16_;
+ base::ListValue byte_value_256_;
+
+ // Quota enforcing storage area being tested.
+ scoped_ptr<SettingsStorageQuotaEnforcer> storage_;
+
+ // In-memory storage area being delegated to. Always owned by |storage_|.
+ TestingValueStore* delegate_;
+};
+
+TEST_F(ExtensionSettingsQuotaTest, ZeroQuotaBytes) {
+ base::DictionaryValue empty;
+ CreateStorage(0, UINT_MAX, UINT_MAX);
+
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "a", byte_value_1_)->status().ok());
+ EXPECT_TRUE(storage_->Remove("a")->status().ok());
+ EXPECT_TRUE(storage_->Remove("b")->status().ok());
+ EXPECT_TRUE(SettingsEqual(empty));
+}
+
+TEST_F(ExtensionSettingsQuotaTest, KeySizeTakenIntoAccount) {
+ base::DictionaryValue empty;
+ CreateStorage(8u, UINT_MAX, UINT_MAX);
+ EXPECT_FALSE(
+ storage_->Set(DEFAULTS, "Really long key", byte_value_1_)->status().ok());
+ EXPECT_TRUE(SettingsEqual(empty));
+}
+
+TEST_F(ExtensionSettingsQuotaTest, SmallByteQuota) {
+ base::DictionaryValue settings;
+ CreateStorage(8u, UINT_MAX, UINT_MAX);
+
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "a", byte_value_1_)->status().ok());
+ settings.Set("a", byte_value_1_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "b", byte_value_16_)->status().ok());
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "c", byte_value_256_)->status().ok());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaTest, MediumByteQuota) {
+ base::DictionaryValue settings;
+ CreateStorage(40, UINT_MAX, UINT_MAX);
+
+ base::DictionaryValue to_set;
+ to_set.Set("a", byte_value_1_.CreateDeepCopy());
+ to_set.Set("b", byte_value_16_.CreateDeepCopy());
+ EXPECT_TRUE(storage_->Set(DEFAULTS, to_set)->status().ok());
+ settings.Set("a", byte_value_1_.CreateDeepCopy());
+ settings.Set("b", byte_value_16_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Should be able to set value to other under-quota value.
+ to_set.Set("a", byte_value_16_.CreateDeepCopy());
+ EXPECT_TRUE(storage_->Set(DEFAULTS, to_set)->status().ok());
+ settings.Set("a", byte_value_16_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "c", byte_value_256_)->status().ok());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaTest, ZeroMaxKeys) {
+ base::DictionaryValue empty;
+ CreateStorage(UINT_MAX, UINT_MAX, 0);
+
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "a", byte_value_1_)->status().ok());
+ EXPECT_TRUE(storage_->Remove("a")->status().ok());
+ EXPECT_TRUE(storage_->Remove("b")->status().ok());
+ EXPECT_TRUE(SettingsEqual(empty));
+}
+
+TEST_F(ExtensionSettingsQuotaTest, SmallMaxKeys) {
+ base::DictionaryValue settings;
+ CreateStorage(UINT_MAX, UINT_MAX, 1);
+
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "a", byte_value_1_)->status().ok());
+ settings.Set("a", byte_value_1_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Should be able to set existing key to other value without going over quota.
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "a", byte_value_16_)->status().ok());
+ settings.Set("a", byte_value_16_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "b", byte_value_16_)->status().ok());
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "c", byte_value_256_)->status().ok());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaTest, MediumMaxKeys) {
+ base::DictionaryValue settings;
+ CreateStorage(UINT_MAX, UINT_MAX, 2);
+
+ base::DictionaryValue to_set;
+ to_set.Set("a", byte_value_1_.CreateDeepCopy());
+ to_set.Set("b", byte_value_16_.CreateDeepCopy());
+ EXPECT_TRUE(storage_->Set(DEFAULTS, to_set)->status().ok());
+ settings.Set("a", byte_value_1_.CreateDeepCopy());
+ settings.Set("b", byte_value_16_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Should be able to set existing keys to other values without going over
+ // quota.
+ to_set.Set("a", byte_value_16_.CreateDeepCopy());
+ EXPECT_TRUE(storage_->Set(DEFAULTS, to_set)->status().ok());
+ settings.Set("a", byte_value_16_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "c", byte_value_256_)->status().ok());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaTest, RemovingExistingSettings) {
+ base::DictionaryValue settings;
+ CreateStorage(266, UINT_MAX, 2);
+
+ storage_->Set(DEFAULTS, "b", byte_value_16_);
+ settings.Set("b", byte_value_16_.CreateDeepCopy());
+ // Not enough quota.
+ storage_->Set(DEFAULTS, "c", byte_value_256_);
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Try again with "b" removed, enough quota.
+ EXPECT_TRUE(storage_->Remove("b")->status().ok());
+ settings.Remove("b", NULL);
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "c", byte_value_256_)->status().ok());
+ settings.Set("c", byte_value_256_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Enough byte quota but max keys not high enough.
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "a", byte_value_1_)->status().ok());
+ settings.Set("a", byte_value_1_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "b", byte_value_1_)->status().ok());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Back under max keys.
+ EXPECT_TRUE(storage_->Remove("a")->status().ok());
+ settings.Remove("a", NULL);
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "b", byte_value_1_)->status().ok());
+ settings.Set("b", byte_value_1_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaTest, RemovingNonexistentSettings) {
+ base::DictionaryValue settings;
+ CreateStorage(36, UINT_MAX, 3);
+
+ // Max out bytes.
+ base::DictionaryValue to_set;
+ to_set.Set("b1", byte_value_16_.CreateDeepCopy());
+ to_set.Set("b2", byte_value_16_.CreateDeepCopy());
+ storage_->Set(DEFAULTS, to_set);
+ settings.Set("b1", byte_value_16_.CreateDeepCopy());
+ settings.Set("b2", byte_value_16_.CreateDeepCopy());
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "a", byte_value_1_)->status().ok());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Remove some settings that don't exist.
+ std::vector<std::string> to_remove;
+ to_remove.push_back("a1");
+ to_remove.push_back("a2");
+ EXPECT_TRUE(storage_->Remove(to_remove)->status().ok());
+ EXPECT_TRUE(storage_->Remove("b")->status().ok());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Still no quota.
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "a", byte_value_1_)->status().ok());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Max out key count.
+ to_set.Clear();
+ to_set.Set("b1", byte_value_1_.CreateDeepCopy());
+ to_set.Set("b2", byte_value_1_.CreateDeepCopy());
+ storage_->Set(DEFAULTS, to_set);
+ settings.Set("b1", byte_value_1_.CreateDeepCopy());
+ settings.Set("b2", byte_value_1_.CreateDeepCopy());
+ storage_->Set(DEFAULTS, "b3", byte_value_1_);
+ settings.Set("b3", byte_value_1_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Remove some settings that don't exist.
+ to_remove.clear();
+ to_remove.push_back("a1");
+ to_remove.push_back("a2");
+ EXPECT_TRUE(storage_->Remove(to_remove)->status().ok());
+ EXPECT_TRUE(storage_->Remove("b")->status().ok());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Still no quota.
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "a", byte_value_1_)->status().ok());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaTest, Clear) {
+ base::DictionaryValue settings;
+ CreateStorage(40, UINT_MAX, 5);
+
+ // Test running out of byte quota.
+ {
+ base::DictionaryValue to_set;
+ to_set.Set("a", byte_value_16_.CreateDeepCopy());
+ to_set.Set("b", byte_value_16_.CreateDeepCopy());
+ EXPECT_TRUE(storage_->Set(DEFAULTS, to_set)->status().ok());
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "c", byte_value_16_)->status().ok());
+
+ EXPECT_TRUE(storage_->Clear()->status().ok());
+
+ // (repeat)
+ EXPECT_TRUE(storage_->Set(DEFAULTS, to_set)->status().ok());
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "c", byte_value_16_)->status().ok());
+ }
+
+ // Test reaching max keys.
+ storage_->Clear();
+ {
+ base::DictionaryValue to_set;
+ to_set.Set("a", byte_value_1_.CreateDeepCopy());
+ to_set.Set("b", byte_value_1_.CreateDeepCopy());
+ to_set.Set("c", byte_value_1_.CreateDeepCopy());
+ to_set.Set("d", byte_value_1_.CreateDeepCopy());
+ to_set.Set("e", byte_value_1_.CreateDeepCopy());
+ EXPECT_TRUE(storage_->Set(DEFAULTS, to_set)->status().ok());
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "f", byte_value_1_)->status().ok());
+
+ storage_->Clear();
+
+ // (repeat)
+ EXPECT_TRUE(storage_->Set(DEFAULTS, to_set)->status().ok());
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "f", byte_value_1_)->status().ok());
+ }
+}
+
+TEST_F(ExtensionSettingsQuotaTest, ChangingUsedBytesWithSet) {
+ base::DictionaryValue settings;
+ CreateStorage(20, UINT_MAX, UINT_MAX);
+
+ // Change a setting to make it go over quota.
+ storage_->Set(DEFAULTS, "a", byte_value_16_);
+ settings.Set("a", byte_value_16_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "a", byte_value_256_)->status().ok());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Change a setting to reduce usage and room for another setting.
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "foobar", byte_value_1_)->status().ok());
+ storage_->Set(DEFAULTS, "a", byte_value_1_);
+ settings.Set("a", byte_value_1_.CreateDeepCopy());
+
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "foobar", byte_value_1_)->status().ok());
+ settings.Set("foobar", byte_value_1_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaTest, SetsOnlyEntirelyCompletedWithByteQuota) {
+ base::DictionaryValue settings;
+ CreateStorage(40, UINT_MAX, UINT_MAX);
+
+ storage_->Set(DEFAULTS, "a", byte_value_16_);
+ settings.Set("a", byte_value_16_.CreateDeepCopy());
+
+ // The entire change is over quota.
+ base::DictionaryValue to_set;
+ to_set.Set("b", byte_value_16_.CreateDeepCopy());
+ to_set.Set("c", byte_value_16_.CreateDeepCopy());
+ EXPECT_FALSE(storage_->Set(DEFAULTS, to_set)->status().ok());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // The entire change is over quota, but quota reduced in existing key.
+ to_set.Set("a", byte_value_1_.CreateDeepCopy());
+ EXPECT_TRUE(storage_->Set(DEFAULTS, to_set)->status().ok());
+ settings.Set("a", byte_value_1_.CreateDeepCopy());
+ settings.Set("b", byte_value_16_.CreateDeepCopy());
+ settings.Set("c", byte_value_16_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaTest, SetsOnlyEntireCompletedWithMaxKeys) {
+ base::DictionaryValue settings;
+ CreateStorage(UINT_MAX, UINT_MAX, 2);
+
+ storage_->Set(DEFAULTS, "a", byte_value_1_);
+ settings.Set("a", byte_value_1_.CreateDeepCopy());
+
+ base::DictionaryValue to_set;
+ to_set.Set("b", byte_value_16_.CreateDeepCopy());
+ to_set.Set("c", byte_value_16_.CreateDeepCopy());
+ EXPECT_FALSE(storage_->Set(DEFAULTS, to_set)->status().ok());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaTest, WithInitialDataAndByteQuota) {
+ base::DictionaryValue settings;
+ delegate_->Set(DEFAULTS, "a", byte_value_256_);
+ settings.Set("a", byte_value_256_.CreateDeepCopy());
+
+ CreateStorage(280, UINT_MAX, UINT_MAX);
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Add some data.
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "b", byte_value_16_)->status().ok());
+ settings.Set("b", byte_value_16_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Not enough quota.
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "c", byte_value_16_)->status().ok());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Reduce usage of original setting so that "c" can fit.
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "a", byte_value_16_)->status().ok());
+ settings.Set("a", byte_value_16_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "c", byte_value_16_)->status().ok());
+ settings.Set("c", byte_value_16_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Remove to free up some more data.
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "d", byte_value_256_)->status().ok());
+
+ std::vector<std::string> to_remove;
+ to_remove.push_back("a");
+ to_remove.push_back("b");
+ storage_->Remove(to_remove);
+ settings.Remove("a", NULL);
+ settings.Remove("b", NULL);
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "d", byte_value_256_)->status().ok());
+ settings.Set("d", byte_value_256_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaTest, WithInitialDataAndMaxKeys) {
+ base::DictionaryValue settings;
+ delegate_->Set(DEFAULTS, "a", byte_value_1_);
+ settings.Set("a", byte_value_1_.CreateDeepCopy());
+ CreateStorage(UINT_MAX, UINT_MAX, 2);
+
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "b", byte_value_1_)->status().ok());
+ settings.Set("b", byte_value_1_.CreateDeepCopy());
+
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "c", byte_value_1_)->status().ok());
+
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaTest, InitiallyOverByteQuota) {
+ base::DictionaryValue settings;
+ settings.Set("a", byte_value_16_.CreateDeepCopy());
+ settings.Set("b", byte_value_16_.CreateDeepCopy());
+ settings.Set("c", byte_value_16_.CreateDeepCopy());
+ delegate_->Set(DEFAULTS, settings);
+
+ CreateStorage(40, UINT_MAX, UINT_MAX);
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "d", byte_value_16_)->status().ok());
+
+ // Take under quota by reducing size of an existing setting
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "a", byte_value_1_)->status().ok());
+ settings.Set("a", byte_value_1_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Should be able set another small setting.
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "d", byte_value_1_)->status().ok());
+ settings.Set("d", byte_value_1_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaTest, InitiallyOverMaxKeys) {
+ base::DictionaryValue settings;
+ settings.Set("a", byte_value_16_.CreateDeepCopy());
+ settings.Set("b", byte_value_16_.CreateDeepCopy());
+ settings.Set("c", byte_value_16_.CreateDeepCopy());
+ delegate_->Set(DEFAULTS, settings);
+
+ CreateStorage(UINT_MAX, UINT_MAX, 2);
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Can't set either an existing or new setting.
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "d", byte_value_16_)->status().ok());
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "a", byte_value_1_)->status().ok());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Should be able after removing 2.
+ storage_->Remove("a");
+ settings.Remove("a", NULL);
+ storage_->Remove("b");
+ settings.Remove("b", NULL);
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "e", byte_value_1_)->status().ok());
+ settings.Set("e", byte_value_1_.CreateDeepCopy());
+ EXPECT_TRUE(SettingsEqual(settings));
+
+ // Still can't set any.
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "d", byte_value_16_)->status().ok());
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "a", byte_value_1_)->status().ok());
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaTest, ZeroQuotaBytesPerSetting) {
+ base::DictionaryValue empty;
+ CreateStorage(UINT_MAX, 0, UINT_MAX);
+
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "a", byte_value_1_)->status().ok());
+ EXPECT_TRUE(storage_->Remove("a")->status().ok());
+ EXPECT_TRUE(storage_->Remove("b")->status().ok());
+ EXPECT_TRUE(SettingsEqual(empty));
+}
+
+TEST_F(ExtensionSettingsQuotaTest, QuotaBytesPerSetting) {
+ base::DictionaryValue settings;
+
+ CreateStorage(UINT_MAX, 20, UINT_MAX);
+
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "a", byte_value_1_)->status().ok());
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "a", byte_value_16_)->status().ok());
+ settings.Set("a", byte_value_16_.CreateDeepCopy());
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "a", byte_value_256_)->status().ok());
+
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "b", byte_value_1_)->status().ok());
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "b", byte_value_16_)->status().ok());
+ settings.Set("b", byte_value_16_.CreateDeepCopy());
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "b", byte_value_256_)->status().ok());
+
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaTest, QuotaBytesPerSettingWithInitialSettings) {
+ base::DictionaryValue settings;
+
+ delegate_->Set(DEFAULTS, "a", byte_value_1_);
+ delegate_->Set(DEFAULTS, "b", byte_value_16_);
+ delegate_->Set(DEFAULTS, "c", byte_value_256_);
+ CreateStorage(UINT_MAX, 20, UINT_MAX);
+
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "a", byte_value_1_)->status().ok());
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "a", byte_value_16_)->status().ok());
+ settings.Set("a", byte_value_16_.CreateDeepCopy());
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "a", byte_value_256_)->status().ok());
+
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "b", byte_value_1_)->status().ok());
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "b", byte_value_16_)->status().ok());
+ settings.Set("b", byte_value_16_.CreateDeepCopy());
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "b", byte_value_256_)->status().ok());
+
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "c", byte_value_1_)->status().ok());
+ EXPECT_TRUE(storage_->Set(DEFAULTS, "c", byte_value_16_)->status().ok());
+ settings.Set("c", byte_value_16_.CreateDeepCopy());
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "c", byte_value_256_)->status().ok());
+
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaTest,
+ QuotaBytesPerSettingWithInitialSettingsForced) {
+ // This is a lazy test to make sure IGNORE_QUOTA lets through changes: the
+ // test above copied, but using IGNORE_QUOTA and asserting nothing is ever
+ // rejected...
+ base::DictionaryValue settings;
+
+ delegate_->Set(DEFAULTS, "a", byte_value_1_);
+ delegate_->Set(DEFAULTS, "b", byte_value_16_);
+ delegate_->Set(DEFAULTS, "c", byte_value_256_);
+ CreateStorage(UINT_MAX, 20, UINT_MAX);
+
+ EXPECT_TRUE(storage_->Set(IGNORE_QUOTA, "a", byte_value_1_)->status().ok());
+ EXPECT_TRUE(storage_->Set(IGNORE_QUOTA, "a", byte_value_16_)->status().ok());
+ EXPECT_TRUE(storage_->Set(IGNORE_QUOTA, "a", byte_value_256_)->status().ok());
+ settings.Set("a", byte_value_256_.CreateDeepCopy());
+
+ EXPECT_TRUE(storage_->Set(IGNORE_QUOTA, "b", byte_value_1_)->status().ok());
+ EXPECT_TRUE(storage_->Set(IGNORE_QUOTA, "b", byte_value_16_)->status().ok());
+ EXPECT_TRUE(storage_->Set(IGNORE_QUOTA, "b", byte_value_256_)->status().ok());
+ settings.Set("b", byte_value_256_.CreateDeepCopy());
+
+ EXPECT_TRUE(storage_->Set(IGNORE_QUOTA, "c", byte_value_1_)->status().ok());
+ EXPECT_TRUE(storage_->Set(IGNORE_QUOTA, "c", byte_value_16_)->status().ok());
+ settings.Set("c", byte_value_16_.CreateDeepCopy());
+
+ // ... except the last. Make sure it can still fail.
+ EXPECT_FALSE(storage_->Set(DEFAULTS, "c", byte_value_256_)->status().ok());
+
+ EXPECT_TRUE(SettingsEqual(settings));
+}
+
+TEST_F(ExtensionSettingsQuotaTest, GetBytesInUse) {
+ // Just testing GetBytesInUse, no need for a quota.
+ CreateStorage(UINT_MAX, UINT_MAX, UINT_MAX);
+
+ std::vector<std::string> ab;
+ ab.push_back("a");
+ ab.push_back("b");
+
+ EXPECT_EQ(0u, storage_->GetBytesInUse());
+ EXPECT_EQ(0u, storage_->GetBytesInUse("a"));
+ EXPECT_EQ(0u, storage_->GetBytesInUse("b"));
+ EXPECT_EQ(0u, storage_->GetBytesInUse(ab));
+
+ storage_->Set(DEFAULTS, "a", byte_value_1_);
+
+ EXPECT_EQ(2u, storage_->GetBytesInUse());
+ EXPECT_EQ(2u, storage_->GetBytesInUse("a"));
+ EXPECT_EQ(0u, storage_->GetBytesInUse("b"));
+ EXPECT_EQ(2u, storage_->GetBytesInUse(ab));
+
+ storage_->Set(DEFAULTS, "b", byte_value_1_);
+
+ EXPECT_EQ(4u, storage_->GetBytesInUse());
+ EXPECT_EQ(2u, storage_->GetBytesInUse("a"));
+ EXPECT_EQ(2u, storage_->GetBytesInUse("b"));
+ EXPECT_EQ(4u, storage_->GetBytesInUse(ab));
+
+ storage_->Set(DEFAULTS, "c", byte_value_1_);
+
+ EXPECT_EQ(6u, storage_->GetBytesInUse());
+ EXPECT_EQ(2u, storage_->GetBytesInUse("a"));
+ EXPECT_EQ(2u, storage_->GetBytesInUse("b"));
+ EXPECT_EQ(4u, storage_->GetBytesInUse(ab));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/storage/settings_storage_quota_enforcer.cc b/chromium/extensions/browser/api/storage/settings_storage_quota_enforcer.cc
new file mode 100644
index 00000000000..7d3703cd4db
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/settings_storage_quota_enforcer.cc
@@ -0,0 +1,262 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/storage/settings_storage_quota_enforcer.h"
+
+#include "base/bind.h"
+#include "base/json/json_writer.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "extensions/common/extension_api.h"
+
+namespace extensions {
+
+namespace {
+
+// Resources there are a quota for.
+enum Resource {
+ QUOTA_BYTES,
+ QUOTA_BYTES_PER_ITEM,
+ MAX_ITEMS
+};
+
+// Allocates a setting in a record of total and per-setting usage.
+void Allocate(
+ const std::string& key,
+ const base::Value& value,
+ size_t* used_total,
+ std::map<std::string, size_t>* used_per_setting) {
+ // Calculate the setting size based on its JSON serialization size.
+ // TODO(kalman): Does this work with different encodings?
+ // TODO(kalman): This is duplicating work that the leveldb delegate
+ // implementation is about to do, and it would be nice to avoid this.
+ std::string value_as_json;
+ base::JSONWriter::Write(value, &value_as_json);
+ size_t new_size = key.size() + value_as_json.size();
+ size_t existing_size = (*used_per_setting)[key];
+
+ *used_total += (new_size - existing_size);
+ (*used_per_setting)[key] = new_size;
+}
+
+ValueStore::Status QuotaExceededError(Resource resource) {
+ const char* name = NULL;
+ // TODO(kalman): These hisograms are both silly and untracked. Fix.
+ switch (resource) {
+ case QUOTA_BYTES:
+ name = "QUOTA_BYTES";
+ UMA_HISTOGRAM_COUNTS_100(
+ "Extensions.SettingsQuotaExceeded.TotalBytes", 1);
+ break;
+ case QUOTA_BYTES_PER_ITEM:
+ name = "QUOTA_BYTES_PER_ITEM";
+ UMA_HISTOGRAM_COUNTS_100(
+ "Extensions.SettingsQuotaExceeded.BytesPerSetting", 1);
+ break;
+ case MAX_ITEMS:
+ name = "MAX_ITEMS";
+ UMA_HISTOGRAM_COUNTS_100(
+ "Extensions.SettingsQuotaExceeded.KeyCount", 1);
+ break;
+ }
+ CHECK(name);
+ return ValueStore::Status(ValueStore::QUOTA_EXCEEDED,
+ base::StringPrintf("%s quota exceeded", name));
+}
+
+} // namespace
+
+SettingsStorageQuotaEnforcer::SettingsStorageQuotaEnforcer(
+ const Limits& limits,
+ scoped_ptr<ValueStore> delegate)
+ : limits_(limits),
+ delegate_(std::move(delegate)),
+ used_total_(0),
+ usage_calculated_(false) {}
+
+SettingsStorageQuotaEnforcer::~SettingsStorageQuotaEnforcer() {}
+
+size_t SettingsStorageQuotaEnforcer::GetBytesInUse(const std::string& key) {
+ LazyCalculateUsage();
+ std::map<std::string, size_t>::iterator maybe_used =
+ used_per_setting_.find(key);
+ return maybe_used == used_per_setting_.end() ? 0u : maybe_used->second;
+}
+
+size_t SettingsStorageQuotaEnforcer::GetBytesInUse(
+ const std::vector<std::string>& keys) {
+ size_t used = 0;
+ for (const std::string& key : keys)
+ used += GetBytesInUse(key);
+ return used;
+}
+
+size_t SettingsStorageQuotaEnforcer::GetBytesInUse() {
+ // All ValueStore implementations rely on GetBytesInUse being
+ // implemented here.
+ LazyCalculateUsage();
+ return used_total_;
+}
+
+ValueStore::ReadResult SettingsStorageQuotaEnforcer::Get(
+ const std::string& key) {
+ return HandleResult(delegate_->Get(key));
+}
+
+ValueStore::ReadResult SettingsStorageQuotaEnforcer::Get(
+ const std::vector<std::string>& keys) {
+ return HandleResult(delegate_->Get(keys));
+}
+
+ValueStore::ReadResult SettingsStorageQuotaEnforcer::Get() {
+ return HandleResult(delegate_->Get());
+}
+
+ValueStore::WriteResult SettingsStorageQuotaEnforcer::Set(
+ WriteOptions options, const std::string& key, const base::Value& value) {
+ LazyCalculateUsage();
+ size_t new_used_total = used_total_;
+ std::map<std::string, size_t> new_used_per_setting = used_per_setting_;
+ Allocate(key, value, &new_used_total, &new_used_per_setting);
+
+ if (!(options & IGNORE_QUOTA)) {
+ if (new_used_total > limits_.quota_bytes)
+ return MakeWriteResult(QuotaExceededError(QUOTA_BYTES));
+ if (new_used_per_setting[key] > limits_.quota_bytes_per_item)
+ return MakeWriteResult(QuotaExceededError(QUOTA_BYTES_PER_ITEM));
+ if (new_used_per_setting.size() > limits_.max_items)
+ return MakeWriteResult(QuotaExceededError(MAX_ITEMS));
+ }
+
+ WriteResult result = HandleResult(delegate_->Set(options, key, value));
+ if (!result->status().ok())
+ return result;
+
+ if (usage_calculated_) {
+ used_total_ = new_used_total;
+ used_per_setting_.swap(new_used_per_setting);
+ }
+ return result;
+}
+
+ValueStore::WriteResult SettingsStorageQuotaEnforcer::Set(
+ WriteOptions options, const base::DictionaryValue& values) {
+ LazyCalculateUsage();
+ size_t new_used_total = used_total_;
+ std::map<std::string, size_t> new_used_per_setting = used_per_setting_;
+ for (base::DictionaryValue::Iterator it(values); !it.IsAtEnd();
+ it.Advance()) {
+ Allocate(it.key(), it.value(), &new_used_total, &new_used_per_setting);
+
+ if (!(options & IGNORE_QUOTA) &&
+ new_used_per_setting[it.key()] > limits_.quota_bytes_per_item) {
+ return MakeWriteResult(QuotaExceededError(QUOTA_BYTES_PER_ITEM));
+ }
+ }
+
+ if (!(options & IGNORE_QUOTA)) {
+ if (new_used_total > limits_.quota_bytes)
+ return MakeWriteResult(QuotaExceededError(QUOTA_BYTES));
+ if (new_used_per_setting.size() > limits_.max_items)
+ return MakeWriteResult(QuotaExceededError(MAX_ITEMS));
+ }
+
+ WriteResult result = HandleResult(delegate_->Set(options, values));
+ if (!result->status().ok())
+ return result;
+
+ if (usage_calculated_) {
+ used_total_ = new_used_total;
+ used_per_setting_ = new_used_per_setting;
+ }
+
+ return result;
+}
+
+ValueStore::WriteResult SettingsStorageQuotaEnforcer::Remove(
+ const std::string& key) {
+ LazyCalculateUsage();
+ WriteResult result = HandleResult(delegate_->Remove(key));
+ if (!result->status().ok())
+ return result;
+ Free(key);
+
+ return result;
+}
+
+ValueStore::WriteResult SettingsStorageQuotaEnforcer::Remove(
+ const std::vector<std::string>& keys) {
+ WriteResult result = HandleResult(delegate_->Remove(keys));
+ if (!result->status().ok())
+ return result;
+
+ for (const std::string& key : keys)
+ Free(key);
+
+ return result;
+}
+
+ValueStore::WriteResult SettingsStorageQuotaEnforcer::Clear() {
+ LazyCalculateUsage();
+ WriteResult result = HandleResult(delegate_->Clear());
+ if (!result->status().ok())
+ return result;
+
+ used_per_setting_.clear();
+ used_total_ = 0u;
+
+ return result;
+}
+
+template <class T>
+T SettingsStorageQuotaEnforcer::HandleResult(T result) {
+ if (result->status().restore_status != RESTORE_NONE) {
+ // Restoration means that an unknown amount, possibly all, of the data was
+ // lost from the database. Reset our counters - they will be lazily
+ // recalculated if/when needed.
+ used_per_setting_.clear();
+ used_total_ = 0u;
+ usage_calculated_ = false;
+ }
+ return result;
+}
+
+void SettingsStorageQuotaEnforcer::LazyCalculateUsage() {
+ if (usage_calculated_)
+ return;
+
+ DCHECK_EQ(0u, used_total_);
+ DCHECK(used_per_setting_.empty());
+
+ ReadResult maybe_settings = HandleResult(delegate_->Get());
+ if (!maybe_settings->status().ok()) {
+ LOG(WARNING) << "Failed to get settings for quota:"
+ << maybe_settings->status().message;
+ return;
+ }
+
+ for (base::DictionaryValue::Iterator it(maybe_settings->settings());
+ !it.IsAtEnd();
+ it.Advance()) {
+ Allocate(it.key(), it.value(), &used_total_, &used_per_setting_);
+ }
+
+ usage_calculated_ = true;
+}
+
+void SettingsStorageQuotaEnforcer::Free(const std::string& key) {
+ if (!usage_calculated_)
+ return;
+ auto it = used_per_setting_.find(key);
+ if (it == used_per_setting_.end())
+ return;
+ DCHECK_GE(used_total_, it->second);
+ used_total_ -= it->second;
+ used_per_setting_.erase(it);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/storage/settings_storage_quota_enforcer.h b/chromium/extensions/browser/api/storage/settings_storage_quota_enforcer.h
new file mode 100644
index 00000000000..33de5b7fa13
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/settings_storage_quota_enforcer.h
@@ -0,0 +1,92 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_STORAGE_SETTINGS_STORAGE_QUOTA_ENFORCER_H_
+#define EXTENSIONS_BROWSER_API_STORAGE_SETTINGS_STORAGE_QUOTA_ENFORCER_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "extensions/browser/value_store/value_store.h"
+
+namespace extensions {
+
+// Enforces total quota and a per-setting quota in bytes, and a maximum number
+// of setting keys, for a delegate storage area.
+class SettingsStorageQuotaEnforcer : public ValueStore {
+ public:
+ struct Limits {
+ // The total quota in bytes.
+ size_t quota_bytes;
+
+ // The quota for each individual item in bytes.
+ size_t quota_bytes_per_item;
+
+ // The maximum number of items allowed.
+ size_t max_items;
+ };
+
+ SettingsStorageQuotaEnforcer(const Limits& limits,
+ scoped_ptr<ValueStore> delegate);
+
+ ~SettingsStorageQuotaEnforcer() override;
+
+ // ValueStore implementation.
+ size_t GetBytesInUse(const std::string& key) override;
+ size_t GetBytesInUse(const std::vector<std::string>& keys) override;
+ size_t GetBytesInUse() override;
+ ReadResult Get(const std::string& key) override;
+ ReadResult Get(const std::vector<std::string>& keys) override;
+ ReadResult Get() override;
+ WriteResult Set(WriteOptions options,
+ const std::string& key,
+ const base::Value& value) override;
+ WriteResult Set(WriteOptions options,
+ const base::DictionaryValue& values) override;
+ WriteResult Remove(const std::string& key) override;
+ WriteResult Remove(const std::vector<std::string>& keys) override;
+ WriteResult Clear() override;
+
+ ValueStore* get_delegate_for_test() { return delegate_.get(); }
+
+ private:
+ template <class T>
+ T HandleResult(T result);
+
+ // Calculate the current usage for the database if not already calculated.
+ void LazyCalculateUsage();
+
+ // Frees the allocation of a setting in a record of total and per-setting
+ // usage.
+ void Free(const std::string& key);
+
+ // Limits configuration.
+ const Limits limits_;
+
+ // The delegate storage area.
+ scoped_ptr<ValueStore> const delegate_;
+
+ // Total bytes in used by |delegate_|. Includes both key lengths and
+ // JSON-encoded values.
+ size_t used_total_;
+
+ // Have the total bytes used been calculated?
+ bool usage_calculated_;
+
+ // Map of item key to its size, including the key itself.
+ std::map<std::string, size_t> used_per_setting_;
+
+ DISALLOW_COPY_AND_ASSIGN(SettingsStorageQuotaEnforcer);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_STORAGE_SETTINGS_STORAGE_QUOTA_ENFORCER_H_
diff --git a/chromium/extensions/browser/api/storage/settings_test_util.cc b/chromium/extensions/browser/api/storage/settings_test_util.cc
new file mode 100644
index 00000000000..44425412439
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/settings_test_util.cc
@@ -0,0 +1,123 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/storage/settings_test_util.h"
+
+#include "base/files/file_path.h"
+#include "extensions/browser/api/storage/storage_frontend.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system_provider.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/permissions/permissions_data.h"
+
+namespace extensions {
+
+namespace settings_test_util {
+
+// Creates a kilobyte of data.
+scoped_ptr<base::Value> CreateKilobyte() {
+ std::string kilobyte_string;
+ for (int i = 0; i < 1024; ++i) {
+ kilobyte_string += "a";
+ }
+ return scoped_ptr<base::Value>(new base::StringValue(kilobyte_string));
+}
+
+// Creates a megabyte of data.
+scoped_ptr<base::Value> CreateMegabyte() {
+ base::ListValue* megabyte = new base::ListValue();
+ for (int i = 0; i < 1000; ++i) {
+ megabyte->Append(CreateKilobyte().release());
+ }
+ return scoped_ptr<base::Value>(megabyte);
+}
+
+// Intended as a StorageCallback from GetStorage.
+static void AssignStorage(ValueStore** dst, ValueStore* src) {
+ *dst = src;
+}
+
+ValueStore* GetStorage(scoped_refptr<const Extension> extension,
+ settings_namespace::Namespace settings_namespace,
+ StorageFrontend* frontend) {
+ ValueStore* storage = NULL;
+ frontend->RunWithStorage(
+ extension, settings_namespace, base::Bind(&AssignStorage, &storage));
+ base::MessageLoop::current()->RunUntilIdle();
+ return storage;
+}
+
+ValueStore* GetStorage(scoped_refptr<const Extension> extension,
+ StorageFrontend* frontend) {
+ return GetStorage(extension, settings_namespace::SYNC, frontend);
+}
+
+scoped_refptr<const Extension> AddExtensionWithId(
+ content::BrowserContext* context,
+ const std::string& id,
+ Manifest::Type type) {
+ return AddExtensionWithIdAndPermissions(
+ context, id, type, std::set<std::string>());
+}
+
+scoped_refptr<const Extension> AddExtensionWithIdAndPermissions(
+ content::BrowserContext* context,
+ const std::string& id,
+ Manifest::Type type,
+ const std::set<std::string>& permissions_set) {
+ base::DictionaryValue manifest;
+ manifest.SetString("name", std::string("Test extension ") + id);
+ manifest.SetString("version", "1.0");
+
+ scoped_ptr<base::ListValue> permissions(new base::ListValue());
+ for (std::set<std::string>::const_iterator it = permissions_set.begin();
+ it != permissions_set.end(); ++it) {
+ permissions->Append(new base::StringValue(*it));
+ }
+ manifest.Set("permissions", permissions.release());
+
+ switch (type) {
+ case Manifest::TYPE_EXTENSION:
+ break;
+
+ case Manifest::TYPE_LEGACY_PACKAGED_APP: {
+ base::DictionaryValue* app = new base::DictionaryValue();
+ base::DictionaryValue* app_launch = new base::DictionaryValue();
+ app_launch->SetString("local_path", "fake.html");
+ app->Set("launch", app_launch);
+ manifest.Set("app", app);
+ break;
+ }
+
+ default:
+ NOTREACHED();
+ }
+
+ std::string error;
+ scoped_refptr<const Extension> extension(
+ Extension::Create(base::FilePath(),
+ Manifest::INTERNAL,
+ manifest,
+ Extension::NO_FLAGS,
+ id,
+ &error));
+ DCHECK(extension.get());
+ DCHECK(error.empty());
+
+ // Ensure lookups via ExtensionRegistry (and ExtensionService) work even if
+ // the test discards the referenced to the returned extension.
+ ExtensionRegistry::Get(context)->AddEnabled(extension);
+
+ for (std::set<std::string>::const_iterator it = permissions_set.begin();
+ it != permissions_set.end(); ++it) {
+ DCHECK(extension->permissions_data()->HasAPIPermission(*it));
+ }
+
+ return extension;
+}
+
+} // namespace settings_test_util
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/storage/settings_test_util.h b/chromium/extensions/browser/api/storage/settings_test_util.h
new file mode 100644
index 00000000000..07a1d5242e1
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/settings_test_util.h
@@ -0,0 +1,63 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_STORAGE_SETTINGS_TEST_UTIL_H_
+#define EXTENSIONS_BROWSER_API_STORAGE_SETTINGS_TEST_UTIL_H_
+
+#include <set>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/test/base/testing_profile.h"
+#include "extensions/browser/api/storage/settings_namespace.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/mock_extension_system.h"
+#include "extensions/browser/value_store/value_store_factory.h"
+#include "extensions/common/extension.h"
+
+class ValueStore;
+
+namespace extensions {
+
+class StorageFrontend;
+// Utilities for extension settings API tests.
+namespace settings_test_util {
+
+// Creates a kilobyte of data.
+scoped_ptr<base::Value> CreateKilobyte();
+
+// Creates a megabyte of data.
+scoped_ptr<base::Value> CreateMegabyte();
+
+// Synchronously gets the storage area for an extension from |frontend|.
+ValueStore* GetStorage(scoped_refptr<const Extension> extension,
+ settings_namespace::Namespace setting_namespace,
+ StorageFrontend* frontend);
+
+// Synchronously gets the SYNC storage for an extension from |frontend|.
+ValueStore* GetStorage(scoped_refptr<const Extension> extension,
+ StorageFrontend* frontend);
+
+// Creates an extension with |id| and adds it to the registry for |context|.
+scoped_refptr<const Extension> AddExtensionWithId(
+ content::BrowserContext* context,
+ const std::string& id,
+ Manifest::Type type);
+
+// Creates an extension with |id| with a set of |permissions| and adds it to
+// the registry for |context|.
+scoped_refptr<const Extension> AddExtensionWithIdAndPermissions(
+ content::BrowserContext* context,
+ const std::string& id,
+ Manifest::Type type,
+ const std::set<std::string>& permissions);
+
+} // namespace settings_test_util
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_STORAGE_SETTINGS_TEST_UTIL_H_
diff --git a/chromium/extensions/browser/api/storage/storage_api.cc b/chromium/extensions/browser/api/storage/storage_api.cc
new file mode 100644
index 00000000000..16a46b0c37d
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/storage_api.cc
@@ -0,0 +1,275 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/storage/storage_api.h"
+
+#include <stddef.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/api/storage/storage_frontend.h"
+#include "extensions/browser/quota_service.h"
+#include "extensions/common/api/storage.h"
+
+namespace extensions {
+
+using content::BrowserThread;
+
+// SettingsFunction
+
+SettingsFunction::SettingsFunction()
+ : settings_namespace_(settings_namespace::INVALID) {}
+
+SettingsFunction::~SettingsFunction() {}
+
+bool SettingsFunction::ShouldSkipQuotaLimiting() const {
+ // Only apply quota if this is for sync storage.
+ std::string settings_namespace_string;
+ if (!args_->GetString(0, &settings_namespace_string)) {
+ // This should be EXTENSION_FUNCTION_VALIDATE(false) but there is no way
+ // to signify that from this function. It will be caught in Run().
+ return false;
+ }
+ return settings_namespace_string != "sync";
+}
+
+ExtensionFunction::ResponseAction SettingsFunction::Run() {
+ std::string settings_namespace_string;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &settings_namespace_string));
+ args_->Remove(0, NULL);
+ settings_namespace_ =
+ settings_namespace::FromString(settings_namespace_string);
+ EXTENSION_FUNCTION_VALIDATE(settings_namespace_ !=
+ settings_namespace::INVALID);
+
+ StorageFrontend* frontend = StorageFrontend::Get(browser_context());
+ if (!frontend->IsStorageEnabled(settings_namespace_)) {
+ return RespondNow(Error(
+ base::StringPrintf("\"%s\" is not available in this instance of Chrome",
+ settings_namespace_string.c_str())));
+ }
+
+ observers_ = frontend->GetObservers();
+ frontend->RunWithStorage(
+ extension(),
+ settings_namespace_,
+ base::Bind(&SettingsFunction::AsyncRunWithStorage, this));
+ return RespondLater();
+}
+
+void SettingsFunction::AsyncRunWithStorage(ValueStore* storage) {
+ ResponseValue response = RunWithStorage(storage);
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&SettingsFunction::Respond, this, base::Passed(&response)));
+}
+
+ExtensionFunction::ResponseValue SettingsFunction::UseReadResult(
+ ValueStore::ReadResult result) {
+ if (!result->status().ok())
+ return Error(result->status().message);
+
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->Swap(&result->settings());
+ return OneArgument(dict);
+}
+
+ExtensionFunction::ResponseValue SettingsFunction::UseWriteResult(
+ ValueStore::WriteResult result) {
+ if (!result->status().ok())
+ return Error(result->status().message);
+
+ if (!result->changes().empty()) {
+ observers_->Notify(FROM_HERE, &SettingsObserver::OnSettingsChanged,
+ extension_id(), settings_namespace_,
+ ValueStoreChange::ToJson(result->changes()));
+ }
+
+ return NoArguments();
+}
+
+// Concrete settings functions
+
+namespace {
+
+// Adds all StringValues from a ListValue to a vector of strings.
+void AddAllStringValues(const base::ListValue& from,
+ std::vector<std::string>* to) {
+ DCHECK(to->empty());
+ std::string as_string;
+ for (base::ListValue::const_iterator it = from.begin();
+ it != from.end(); ++it) {
+ if ((*it)->GetAsString(&as_string)) {
+ to->push_back(as_string);
+ }
+ }
+}
+
+// Gets the keys of a DictionaryValue.
+std::vector<std::string> GetKeys(const base::DictionaryValue& dict) {
+ std::vector<std::string> keys;
+ for (base::DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) {
+ keys.push_back(it.key());
+ }
+ return keys;
+}
+
+// Creates quota heuristics for settings modification.
+void GetModificationQuotaLimitHeuristics(QuotaLimitHeuristics* heuristics) {
+ // See storage.json for the current value of these limits.
+ QuotaLimitHeuristic::Config short_limit_config = {
+ api::storage::sync::MAX_WRITE_OPERATIONS_PER_MINUTE,
+ base::TimeDelta::FromMinutes(1)};
+ QuotaLimitHeuristic::Config long_limit_config = {
+ api::storage::sync::MAX_WRITE_OPERATIONS_PER_HOUR,
+ base::TimeDelta::FromHours(1)};
+ heuristics->push_back(new QuotaService::TimedLimit(
+ short_limit_config, new QuotaLimitHeuristic::SingletonBucketMapper(),
+ "MAX_WRITE_OPERATIONS_PER_MINUTE"));
+ heuristics->push_back(new QuotaService::TimedLimit(
+ long_limit_config, new QuotaLimitHeuristic::SingletonBucketMapper(),
+ "MAX_WRITE_OPERATIONS_PER_HOUR"));
+}
+
+} // namespace
+
+ExtensionFunction::ResponseValue StorageStorageAreaGetFunction::RunWithStorage(
+ ValueStore* storage) {
+ base::Value* input = NULL;
+ if (!args_->Get(0, &input))
+ return BadMessage();
+
+ switch (input->GetType()) {
+ case base::Value::TYPE_NULL:
+ return UseReadResult(storage->Get());
+
+ case base::Value::TYPE_STRING: {
+ std::string as_string;
+ input->GetAsString(&as_string);
+ return UseReadResult(storage->Get(as_string));
+ }
+
+ case base::Value::TYPE_LIST: {
+ std::vector<std::string> as_string_list;
+ AddAllStringValues(*static_cast<base::ListValue*>(input),
+ &as_string_list);
+ return UseReadResult(storage->Get(as_string_list));
+ }
+
+ case base::Value::TYPE_DICTIONARY: {
+ base::DictionaryValue* as_dict =
+ static_cast<base::DictionaryValue*>(input);
+ ValueStore::ReadResult result = storage->Get(GetKeys(*as_dict));
+ if (!result->status().ok()) {
+ return UseReadResult(std::move(result));
+ }
+
+ base::DictionaryValue* with_default_values = as_dict->DeepCopy();
+ with_default_values->MergeDictionary(&result->settings());
+ return UseReadResult(ValueStore::MakeReadResult(
+ make_scoped_ptr(with_default_values), result->status()));
+ }
+
+ default:
+ return BadMessage();
+ }
+}
+
+ExtensionFunction::ResponseValue
+StorageStorageAreaGetBytesInUseFunction::RunWithStorage(ValueStore* storage) {
+ base::Value* input = NULL;
+ if (!args_->Get(0, &input))
+ return BadMessage();
+
+ size_t bytes_in_use = 0;
+
+ switch (input->GetType()) {
+ case base::Value::TYPE_NULL:
+ bytes_in_use = storage->GetBytesInUse();
+ break;
+
+ case base::Value::TYPE_STRING: {
+ std::string as_string;
+ input->GetAsString(&as_string);
+ bytes_in_use = storage->GetBytesInUse(as_string);
+ break;
+ }
+
+ case base::Value::TYPE_LIST: {
+ std::vector<std::string> as_string_list;
+ AddAllStringValues(*static_cast<base::ListValue*>(input),
+ &as_string_list);
+ bytes_in_use = storage->GetBytesInUse(as_string_list);
+ break;
+ }
+
+ default:
+ return BadMessage();
+ }
+
+ return OneArgument(
+ new base::FundamentalValue(static_cast<int>(bytes_in_use)));
+}
+
+ExtensionFunction::ResponseValue StorageStorageAreaSetFunction::RunWithStorage(
+ ValueStore* storage) {
+ base::DictionaryValue* input = NULL;
+ if (!args_->GetDictionary(0, &input))
+ return BadMessage();
+ return UseWriteResult(storage->Set(ValueStore::DEFAULTS, *input));
+}
+
+void StorageStorageAreaSetFunction::GetQuotaLimitHeuristics(
+ QuotaLimitHeuristics* heuristics) const {
+ GetModificationQuotaLimitHeuristics(heuristics);
+}
+
+ExtensionFunction::ResponseValue
+StorageStorageAreaRemoveFunction::RunWithStorage(ValueStore* storage) {
+ base::Value* input = NULL;
+ if (!args_->Get(0, &input))
+ return BadMessage();
+
+ switch (input->GetType()) {
+ case base::Value::TYPE_STRING: {
+ std::string as_string;
+ input->GetAsString(&as_string);
+ return UseWriteResult(storage->Remove(as_string));
+ }
+
+ case base::Value::TYPE_LIST: {
+ std::vector<std::string> as_string_list;
+ AddAllStringValues(*static_cast<base::ListValue*>(input),
+ &as_string_list);
+ return UseWriteResult(storage->Remove(as_string_list));
+ }
+
+ default:
+ return BadMessage();
+ }
+}
+
+void StorageStorageAreaRemoveFunction::GetQuotaLimitHeuristics(
+ QuotaLimitHeuristics* heuristics) const {
+ GetModificationQuotaLimitHeuristics(heuristics);
+}
+
+ExtensionFunction::ResponseValue
+StorageStorageAreaClearFunction::RunWithStorage(ValueStore* storage) {
+ return UseWriteResult(storage->Clear());
+}
+
+void StorageStorageAreaClearFunction::GetQuotaLimitHeuristics(
+ QuotaLimitHeuristics* heuristics) const {
+ GetModificationQuotaLimitHeuristics(heuristics);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/storage/storage_api.h b/chromium/extensions/browser/api/storage/storage_api.h
new file mode 100644
index 00000000000..e6b532c135b
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/storage_api.h
@@ -0,0 +1,123 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_STORAGE_STORAGE_API_H_
+#define EXTENSIONS_BROWSER_API_STORAGE_STORAGE_API_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "extensions/browser/api/storage/settings_namespace.h"
+#include "extensions/browser/api/storage/settings_observer.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/browser/value_store/value_store.h"
+
+namespace extensions {
+
+// Superclass of all settings functions.
+class SettingsFunction : public UIThreadExtensionFunction {
+ protected:
+ SettingsFunction();
+ ~SettingsFunction() override;
+
+ // ExtensionFunction:
+ bool ShouldSkipQuotaLimiting() const override;
+ ResponseAction Run() override;
+
+ // Extension settings function implementations should do their work here.
+ // The StorageFrontend makes sure this is posted to the appropriate thread.
+ virtual ResponseValue RunWithStorage(ValueStore* storage) = 0;
+
+ // Convert the |result| of a read function to the appropriate response value.
+ // - If the |result| succeeded this will return a response object argument.
+ // - If the |result| failed will return an error object.
+ ResponseValue UseReadResult(ValueStore::ReadResult result);
+
+ // Handles the |result| of a write function.
+ // - If the |result| succeeded this will send out change notification(s), if
+ // appropriate, and return no arguments.
+ // - If the |result| failed will return an error object.
+ ResponseValue UseWriteResult(ValueStore::WriteResult result);
+
+ private:
+ // Called via PostTask from Run. Calls RunWithStorage and then
+ // SendResponse with its success value.
+ void AsyncRunWithStorage(ValueStore* storage);
+
+ // The settings namespace the call was for. For example, SYNC if the API
+ // call was chrome.settings.experimental.sync..., LOCAL if .local, etc.
+ settings_namespace::Namespace settings_namespace_;
+
+ // Observers, cached so that it's only grabbed from the UI thread.
+ scoped_refptr<SettingsObserverList> observers_;
+};
+
+class StorageStorageAreaGetFunction : public SettingsFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("storage.get", STORAGE_GET)
+
+ protected:
+ ~StorageStorageAreaGetFunction() override {}
+
+ // SettingsFunction:
+ ResponseValue RunWithStorage(ValueStore* storage) override;
+};
+
+class StorageStorageAreaSetFunction : public SettingsFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("storage.set", STORAGE_SET)
+
+ protected:
+ ~StorageStorageAreaSetFunction() override {}
+
+ // SettingsFunction:
+ ResponseValue RunWithStorage(ValueStore* storage) override;
+
+ // ExtensionFunction:
+ void GetQuotaLimitHeuristics(QuotaLimitHeuristics* heuristics) const override;
+};
+
+class StorageStorageAreaRemoveFunction : public SettingsFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("storage.remove", STORAGE_REMOVE)
+
+ protected:
+ ~StorageStorageAreaRemoveFunction() override {}
+
+ // SettingsFunction:
+ ResponseValue RunWithStorage(ValueStore* storage) override;
+
+ // ExtensionFunction:
+ void GetQuotaLimitHeuristics(QuotaLimitHeuristics* heuristics) const override;
+};
+
+class StorageStorageAreaClearFunction : public SettingsFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("storage.clear", STORAGE_CLEAR)
+
+ protected:
+ ~StorageStorageAreaClearFunction() override {}
+
+ // SettingsFunction:
+ ResponseValue RunWithStorage(ValueStore* storage) override;
+
+ // ExtensionFunction:
+ void GetQuotaLimitHeuristics(QuotaLimitHeuristics* heuristics) const override;
+};
+
+class StorageStorageAreaGetBytesInUseFunction : public SettingsFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("storage.getBytesInUse", STORAGE_GETBYTESINUSE)
+
+ protected:
+ ~StorageStorageAreaGetBytesInUseFunction() override {}
+
+ // SettingsFunction:
+ ResponseValue RunWithStorage(ValueStore* storage) override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_STORAGE_STORAGE_API_H_
diff --git a/chromium/extensions/browser/api/storage/storage_api_unittest.cc b/chromium/extensions/browser/api/storage/storage_api_unittest.cc
new file mode 100644
index 00000000000..4a013db42a0
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/storage_api_unittest.cc
@@ -0,0 +1,127 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/stringprintf.h"
+#include "content/public/test/test_browser_context.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/api/storage/settings_storage_quota_enforcer.h"
+#include "extensions/browser/api/storage/settings_test_util.h"
+#include "extensions/browser/api/storage/storage_api.h"
+#include "extensions/browser/api/storage/storage_frontend.h"
+#include "extensions/browser/api_unittest.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/event_router_factory.h"
+#include "extensions/browser/test_extensions_browser_client.h"
+#include "extensions/browser/value_store/leveldb_value_store.h"
+#include "extensions/browser/value_store/value_store.h"
+#include "extensions/browser/value_store/value_store_factory_impl.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/test_util.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+
+namespace extensions {
+
+namespace {
+
+// Caller owns the returned object.
+scoped_ptr<KeyedService> CreateStorageFrontendForTesting(
+ content::BrowserContext* context) {
+ scoped_refptr<ValueStoreFactory> factory =
+ new ValueStoreFactoryImpl(context->GetPath());
+ return StorageFrontend::CreateForTesting(factory, context);
+}
+
+scoped_ptr<KeyedService> BuildEventRouter(content::BrowserContext* context) {
+ return make_scoped_ptr(new extensions::EventRouter(context, nullptr));
+}
+
+} // namespace
+
+class StorageApiUnittest : public ApiUnitTest {
+ public:
+ StorageApiUnittest() {}
+ ~StorageApiUnittest() override {}
+
+ protected:
+ // Runs the storage.set() API function with local storage.
+ void RunSetFunction(const std::string& key, const std::string& value) {
+ RunFunction(
+ new StorageStorageAreaSetFunction(),
+ base::StringPrintf(
+ "[\"local\", {\"%s\": \"%s\"}]", key.c_str(), value.c_str()));
+ }
+
+ // Runs the storage.get() API function with the local storage, and populates
+ // |value| with the string result.
+ testing::AssertionResult RunGetFunction(const std::string& key,
+ std::string* value) {
+ scoped_ptr<base::Value> result = RunFunctionAndReturnValue(
+ new StorageStorageAreaGetFunction(),
+ base::StringPrintf("[\"local\", \"%s\"]", key.c_str()));
+ if (!result.get())
+ return testing::AssertionFailure() << "No result";
+ base::DictionaryValue* dict = NULL;
+ if (!result->GetAsDictionary(&dict))
+ return testing::AssertionFailure() << result.get()
+ << " was not a dictionary.";
+ if (!dict->GetString(key, value)) {
+ return testing::AssertionFailure() << " could not retrieve a string from"
+ << dict << " at " << key;
+ }
+ return testing::AssertionSuccess();
+ }
+
+ ExtensionsAPIClient extensions_api_client_;
+};
+
+TEST_F(StorageApiUnittest, RestoreCorruptedStorage) {
+ EventRouterFactory::GetInstance()->SetTestingFactory(browser_context(),
+ &BuildEventRouter);
+
+ // Ensure a StorageFrontend can be created on demand. The StorageFrontend
+ // will be owned by the KeyedService system.
+ StorageFrontend::GetFactoryInstance()->SetTestingFactory(
+ browser_context(), &CreateStorageFrontendForTesting);
+
+ const char kKey[] = "key";
+ const char kValue[] = "value";
+ std::string result;
+
+ // Do a simple set/get combo to make sure the API works.
+ RunSetFunction(kKey, kValue);
+ EXPECT_TRUE(RunGetFunction(kKey, &result));
+ EXPECT_EQ(kValue, result);
+
+ // Corrupt the store. This is not as pretty as ideal, because we use knowledge
+ // of the underlying structure, but there's no real good way to corrupt a
+ // store other than directly modifying the files.
+ ValueStore* store =
+ settings_test_util::GetStorage(extension_ref(),
+ settings_namespace::LOCAL,
+ StorageFrontend::Get(browser_context()));
+ ASSERT_TRUE(store);
+ // TODO(cmumford): Modify test as this requires that the factory always
+ // creates instances of LeveldbValueStore.
+ SettingsStorageQuotaEnforcer* quota_store =
+ static_cast<SettingsStorageQuotaEnforcer*>(store);
+ LeveldbValueStore* leveldb_store =
+ static_cast<LeveldbValueStore*>(quota_store->get_delegate_for_test());
+ leveldb::WriteBatch batch;
+ batch.Put(kKey, "[{(.*+\"\'\\");
+ EXPECT_TRUE(leveldb_store->WriteToDbForTest(&batch));
+ EXPECT_TRUE(leveldb_store->Get(kKey)->status().IsCorrupted());
+
+ // Running another set should end up working (even though it will restore the
+ // store behind the scenes).
+ RunSetFunction(kKey, kValue);
+ EXPECT_TRUE(RunGetFunction(kKey, &result));
+ EXPECT_EQ(kValue, result);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/storage/storage_frontend.cc b/chromium/extensions/browser/api/storage/storage_frontend.cc
new file mode 100644
index 00000000000..911a49290bf
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/storage_frontend.cc
@@ -0,0 +1,186 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/storage/storage_frontend.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/file_path.h"
+#include "base/json/json_reader.h"
+#include "base/lazy_instance.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/trace_event/trace_event.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/api/storage/local_value_store_cache.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/value_store/value_store_factory.h"
+#include "extensions/common/api/storage.h"
+
+using content::BrowserContext;
+using content::BrowserThread;
+
+namespace extensions {
+
+namespace {
+
+base::LazyInstance<BrowserContextKeyedAPIFactory<StorageFrontend> > g_factory =
+ LAZY_INSTANCE_INITIALIZER;
+
+// Settings change Observer which forwards changes on to the extension
+// processes for |context| and its incognito partner if it exists.
+class DefaultObserver : public SettingsObserver {
+ public:
+ explicit DefaultObserver(BrowserContext* context)
+ : browser_context_(context) {}
+
+ // SettingsObserver implementation.
+ void OnSettingsChanged(const std::string& extension_id,
+ settings_namespace::Namespace settings_namespace,
+ const std::string& change_json) override {
+ // TODO(gdk): This is a temporary hack while the refactoring for
+ // string-based event payloads is removed. http://crbug.com/136045
+ scoped_ptr<base::ListValue> args(new base::ListValue());
+ args->Append(base::JSONReader::Read(change_json));
+ args->Append(new base::StringValue(settings_namespace::ToString(
+ settings_namespace)));
+ scoped_ptr<Event> event(new Event(events::STORAGE_ON_CHANGED,
+ api::storage::OnChanged::kEventName,
+ std::move(args)));
+ EventRouter::Get(browser_context_)
+ ->DispatchEventToExtension(extension_id, std::move(event));
+ }
+
+ private:
+ BrowserContext* const browser_context_;
+};
+
+} // namespace
+
+// static
+StorageFrontend* StorageFrontend::Get(BrowserContext* context) {
+ return BrowserContextKeyedAPIFactory<StorageFrontend>::Get(context);
+}
+
+// static
+scoped_ptr<StorageFrontend> StorageFrontend::CreateForTesting(
+ const scoped_refptr<ValueStoreFactory>& storage_factory,
+ BrowserContext* context) {
+ return make_scoped_ptr(new StorageFrontend(storage_factory, context));
+}
+
+StorageFrontend::StorageFrontend(BrowserContext* context)
+ : StorageFrontend(ExtensionSystem::Get(context)->store_factory(), context) {
+}
+
+StorageFrontend::StorageFrontend(
+ const scoped_refptr<ValueStoreFactory>& factory,
+ BrowserContext* context)
+ : browser_context_(context) {
+ Init(factory);
+}
+
+void StorageFrontend::Init(const scoped_refptr<ValueStoreFactory>& factory) {
+ TRACE_EVENT0("browser,startup", "StorageFrontend::Init")
+ SCOPED_UMA_HISTOGRAM_TIMER("Extensions.StorageFrontendInitTime");
+
+ observers_ = new SettingsObserverList();
+ browser_context_observer_.reset(new DefaultObserver(browser_context_));
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(!browser_context_->IsOffTheRecord());
+
+ observers_->AddObserver(browser_context_observer_.get());
+
+ caches_[settings_namespace::LOCAL] = new LocalValueStoreCache(factory);
+
+ // Add any additional caches the embedder supports (for example, caches
+ // for chrome.storage.managed and chrome.storage.sync).
+ ExtensionsAPIClient::Get()->AddAdditionalValueStoreCaches(
+ browser_context_, factory, observers_, &caches_);
+}
+
+StorageFrontend::~StorageFrontend() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ observers_->RemoveObserver(browser_context_observer_.get());
+ for (CacheMap::iterator it = caches_.begin(); it != caches_.end(); ++it) {
+ ValueStoreCache* cache = it->second;
+ cache->ShutdownOnUI();
+ BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, cache);
+ }
+}
+
+ValueStoreCache* StorageFrontend::GetValueStoreCache(
+ settings_namespace::Namespace settings_namespace) const {
+ CacheMap::const_iterator it = caches_.find(settings_namespace);
+ if (it != caches_.end())
+ return it->second;
+ return NULL;
+}
+
+bool StorageFrontend::IsStorageEnabled(
+ settings_namespace::Namespace settings_namespace) const {
+ return caches_.find(settings_namespace) != caches_.end();
+}
+
+void StorageFrontend::RunWithStorage(
+ scoped_refptr<const Extension> extension,
+ settings_namespace::Namespace settings_namespace,
+ const ValueStoreCache::StorageCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ CHECK(extension.get());
+
+ ValueStoreCache* cache = caches_[settings_namespace];
+ CHECK(cache);
+
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&ValueStoreCache::RunWithValueStoreForExtension,
+ base::Unretained(cache), callback, extension));
+}
+
+void StorageFrontend::DeleteStorageSoon(const std::string& extension_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ for (CacheMap::iterator it = caches_.begin(); it != caches_.end(); ++it) {
+ ValueStoreCache* cache = it->second;
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&ValueStoreCache::DeleteStorageSoon,
+ base::Unretained(cache),
+ extension_id));
+ }
+}
+
+scoped_refptr<SettingsObserverList> StorageFrontend::GetObservers() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ return observers_;
+}
+
+void StorageFrontend::DisableStorageForTesting(
+ settings_namespace::Namespace settings_namespace) {
+ CacheMap::iterator it = caches_.find(settings_namespace);
+ if (it != caches_.end()) {
+ ValueStoreCache* cache = it->second;
+ cache->ShutdownOnUI();
+ BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, cache);
+ caches_.erase(it);
+ }
+}
+
+// BrowserContextKeyedAPI implementation.
+
+// static
+BrowserContextKeyedAPIFactory<StorageFrontend>*
+StorageFrontend::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+// static
+const char* StorageFrontend::service_name() { return "StorageFrontend"; }
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/storage/storage_frontend.h b/chromium/extensions/browser/api/storage/storage_frontend.h
new file mode 100644
index 00000000000..6d3427fd546
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/storage_frontend.h
@@ -0,0 +1,101 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_STORAGE_STORAGE_FRONTEND_H_
+#define EXTENSIONS_BROWSER_API_STORAGE_STORAGE_FRONTEND_H_
+
+#include <map>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/browser/api/storage/settings_namespace.h"
+#include "extensions/browser/api/storage/settings_observer.h"
+#include "extensions/browser/api/storage/value_store_cache.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class ValueStoreFactory;
+
+// The component of the Storage API which runs on the UI thread.
+class StorageFrontend : public BrowserContextKeyedAPI {
+ public:
+ // Returns the current instance for |context|.
+ static StorageFrontend* Get(content::BrowserContext* context);
+
+ // Creates with a specific |storage_factory|.
+ static scoped_ptr<StorageFrontend> CreateForTesting(
+ const scoped_refptr<ValueStoreFactory>& storage_factory,
+ content::BrowserContext* context);
+
+ // Public so tests can create and delete their own instances.
+ ~StorageFrontend() override;
+
+ // Returns the value store cache for |settings_namespace|.
+ ValueStoreCache* GetValueStoreCache(
+ settings_namespace::Namespace settings_namespace) const;
+
+ // Returns true if |settings_namespace| is a valid namespace.
+ bool IsStorageEnabled(settings_namespace::Namespace settings_namespace) const;
+
+ // Runs |callback| with the storage area of the given |settings_namespace|
+ // for the |extension|.
+ void RunWithStorage(scoped_refptr<const Extension> extension,
+ settings_namespace::Namespace settings_namespace,
+ const ValueStoreCache::StorageCallback& callback);
+
+ // Deletes the settings for the given |extension_id|.
+ void DeleteStorageSoon(const std::string& extension_id);
+
+ // Gets the thread-safe observer list.
+ scoped_refptr<SettingsObserverList> GetObservers();
+
+ void DisableStorageForTesting(
+ settings_namespace::Namespace settings_namespace);
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<StorageFrontend>* GetFactoryInstance();
+ static const char* service_name();
+ static const bool kServiceRedirectedInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<StorageFrontend>;
+
+ typedef std::map<settings_namespace::Namespace, ValueStoreCache*> CacheMap;
+
+ // Constructor for normal BrowserContextKeyedAPI usage.
+ explicit StorageFrontend(content::BrowserContext* context);
+
+ // Constructor for tests.
+ StorageFrontend(const scoped_refptr<ValueStoreFactory>& storage_factory,
+ content::BrowserContext* context);
+
+ void Init(const scoped_refptr<ValueStoreFactory>& storage_factory);
+
+ // The (non-incognito) browser context this Frontend belongs to.
+ content::BrowserContext* const browser_context_;
+
+ // List of observers to settings changes.
+ scoped_refptr<SettingsObserverList> observers_;
+
+ // Observer for |browser_context_|.
+ scoped_ptr<SettingsObserver> browser_context_observer_;
+
+ // Maps a known namespace to its corresponding ValueStoreCache. The caches
+ // are owned by this object.
+ CacheMap caches_;
+
+ DISALLOW_COPY_AND_ASSIGN(StorageFrontend);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_STORAGE_STORAGE_FRONTEND_H_
diff --git a/chromium/extensions/browser/api/storage/storage_frontend_unittest.cc b/chromium/extensions/browser/api/storage/storage_frontend_unittest.cc
new file mode 100644
index 00000000000..5995ceb2dbe
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/storage_frontend_unittest.cc
@@ -0,0 +1,221 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/api/storage/settings_namespace.h"
+#include "extensions/browser/api/storage/settings_test_util.h"
+#include "extensions/browser/api/storage/storage_frontend.h"
+#include "extensions/browser/extensions_test.h"
+#include "extensions/browser/value_store/value_store.h"
+#include "extensions/browser/value_store/value_store_factory_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+namespace settings = settings_namespace;
+namespace util = settings_test_util;
+
+namespace {
+
+// To save typing ValueStore::DEFAULTS everywhere.
+const ValueStore::WriteOptions DEFAULTS = ValueStore::DEFAULTS;
+
+} // namespace
+
+// A better name for this would be StorageFrontendTest, but the historical name
+// has been ExtensionSettingsFrontendTest. In order to preserve crash/failure
+// history, the test names are unchanged.
+class ExtensionSettingsFrontendTest : public ExtensionsTest {
+ public:
+ ExtensionSettingsFrontendTest()
+ : ui_thread_(BrowserThread::UI, base::MessageLoop::current()),
+ file_thread_(BrowserThread::FILE, base::MessageLoop::current()) {}
+
+ void SetUp() override {
+ ExtensionsTest::SetUp();
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ storage_factory_ = new ValueStoreFactoryImpl(temp_dir_.path());
+ ResetFrontend();
+ }
+
+ void TearDown() override {
+ frontend_.reset();
+ // Execute any pending deletion tasks.
+ message_loop_.RunUntilIdle();
+ ExtensionsTest::TearDown();
+ }
+
+ protected:
+ void ResetFrontend() {
+ frontend_ =
+ StorageFrontend::CreateForTesting(storage_factory_, browser_context());
+ }
+
+ base::ScopedTempDir temp_dir_;
+ scoped_ptr<StorageFrontend> frontend_;
+ scoped_refptr<ValueStoreFactoryImpl> storage_factory_;
+
+ private:
+ base::MessageLoop message_loop_;
+ content::TestBrowserThread ui_thread_;
+ content::TestBrowserThread file_thread_;
+ ExtensionsAPIClient extensions_api_client_;
+};
+
+// Get a semblance of coverage for both extension and app settings by
+// alternating in each test.
+// TODO(kalman): explicitly test the two interact correctly.
+
+// Tests that the frontend is set up correctly.
+TEST_F(ExtensionSettingsFrontendTest, Basics) {
+ // Local storage is always enabled.
+ EXPECT_TRUE(frontend_->IsStorageEnabled(settings::LOCAL));
+ EXPECT_TRUE(frontend_->GetValueStoreCache(settings::LOCAL));
+
+ // Invalid storage areas are not available.
+ EXPECT_FALSE(frontend_->IsStorageEnabled(settings::INVALID));
+ EXPECT_FALSE(frontend_->GetValueStoreCache(settings::INVALID));
+}
+
+TEST_F(ExtensionSettingsFrontendTest, SettingsPreservedAcrossReconstruction) {
+ const std::string id = "ext";
+ scoped_refptr<const Extension> extension =
+ util::AddExtensionWithId(browser_context(), id, Manifest::TYPE_EXTENSION);
+
+ ValueStore* storage =
+ util::GetStorage(extension, settings::LOCAL, frontend_.get());
+
+ // The correctness of Get/Set/Remove/Clear is tested elsewhere so no need to
+ // be too rigorous.
+ {
+ base::StringValue bar("bar");
+ ValueStore::WriteResult result = storage->Set(DEFAULTS, "foo", bar);
+ ASSERT_TRUE(result->status().ok());
+ }
+
+ {
+ ValueStore::ReadResult result = storage->Get();
+ ASSERT_TRUE(result->status().ok());
+ EXPECT_FALSE(result->settings().empty());
+ }
+
+ ResetFrontend();
+ storage = util::GetStorage(extension, settings::LOCAL, frontend_.get());
+
+ {
+ ValueStore::ReadResult result = storage->Get();
+ ASSERT_TRUE(result->status().ok());
+ EXPECT_FALSE(result->settings().empty());
+ }
+}
+
+TEST_F(ExtensionSettingsFrontendTest, SettingsClearedOnUninstall) {
+ const std::string id = "ext";
+ scoped_refptr<const Extension> extension = util::AddExtensionWithId(
+ browser_context(), id, Manifest::TYPE_LEGACY_PACKAGED_APP);
+
+ ValueStore* storage =
+ util::GetStorage(extension, settings::LOCAL, frontend_.get());
+
+ {
+ base::StringValue bar("bar");
+ ValueStore::WriteResult result = storage->Set(DEFAULTS, "foo", bar);
+ ASSERT_TRUE(result->status().ok());
+ }
+
+ // This would be triggered by extension uninstall via a DataDeleter.
+ frontend_->DeleteStorageSoon(id);
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // The storage area may no longer be valid post-uninstall, so re-request.
+ storage = util::GetStorage(extension, settings::LOCAL, frontend_.get());
+ {
+ ValueStore::ReadResult result = storage->Get();
+ ASSERT_TRUE(result->status().ok());
+ EXPECT_TRUE(result->settings().empty());
+ }
+}
+
+TEST_F(ExtensionSettingsFrontendTest, LeveldbDatabaseDeletedFromDiskOnClear) {
+ const std::string id = "ext";
+ scoped_refptr<const Extension> extension =
+ util::AddExtensionWithId(browser_context(), id, Manifest::TYPE_EXTENSION);
+
+ ValueStore* storage =
+ util::GetStorage(extension, settings::LOCAL, frontend_.get());
+
+ {
+ base::StringValue bar("bar");
+ ValueStore::WriteResult result = storage->Set(DEFAULTS, "foo", bar);
+ ASSERT_TRUE(result->status().ok());
+ EXPECT_TRUE(base::PathExists(temp_dir_.path()));
+ }
+
+ // Should need to both clear the database and delete the frontend for the
+ // leveldb database to be deleted from disk.
+ {
+ ValueStore::WriteResult result = storage->Clear();
+ ASSERT_TRUE(result->status().ok());
+ EXPECT_TRUE(base::PathExists(temp_dir_.path()));
+ }
+
+ frontend_.reset();
+ base::MessageLoop::current()->RunUntilIdle();
+ // TODO(kalman): Figure out why this fails, despite appearing to work.
+ // Leaving this commented out rather than disabling the whole test so that the
+ // deletion code paths are at least exercised.
+ //EXPECT_FALSE(base::PathExists(temp_dir_.path()));
+}
+
+// Disabled (slow), http://crbug.com/322751 .
+TEST_F(ExtensionSettingsFrontendTest,
+ DISABLED_QuotaLimitsEnforcedCorrectlyForSyncAndLocal) {
+ const std::string id = "ext";
+ scoped_refptr<const Extension> extension =
+ util::AddExtensionWithId(browser_context(), id, Manifest::TYPE_EXTENSION);
+
+ ValueStore* sync_storage =
+ util::GetStorage(extension, settings::SYNC, frontend_.get());
+ ValueStore* local_storage =
+ util::GetStorage(extension, settings::LOCAL, frontend_.get());
+
+ // Sync storage should run out after ~100K.
+ scoped_ptr<base::Value> kilobyte = util::CreateKilobyte();
+ for (int i = 0; i < 100; ++i) {
+ sync_storage->Set(DEFAULTS, base::IntToString(i), *kilobyte);
+ }
+
+ EXPECT_FALSE(
+ sync_storage->Set(DEFAULTS, "WillError", *kilobyte)->status().ok());
+
+ // Local storage shouldn't run out after ~100K.
+ for (int i = 0; i < 100; ++i) {
+ local_storage->Set(DEFAULTS, base::IntToString(i), *kilobyte);
+ }
+
+ EXPECT_TRUE(
+ local_storage->Set(DEFAULTS, "WontError", *kilobyte)->status().ok());
+
+ // Local storage should run out after ~5MB.
+ scoped_ptr<base::Value> megabyte = util::CreateMegabyte();
+ for (int i = 0; i < 5; ++i) {
+ local_storage->Set(DEFAULTS, base::IntToString(i), *megabyte);
+ }
+
+ EXPECT_FALSE(
+ local_storage->Set(DEFAULTS, "WillError", *megabyte)->status().ok());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/storage/value_store_cache.cc b/chromium/extensions/browser/api/storage/value_store_cache.cc
new file mode 100644
index 00000000000..04ea418113c
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/value_store_cache.cc
@@ -0,0 +1,13 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/storage/value_store_cache.h"
+
+namespace extensions {
+
+ValueStoreCache::~ValueStoreCache() {}
+
+void ValueStoreCache::ShutdownOnUI() {}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/storage/value_store_cache.h b/chromium/extensions/browser/api/storage/value_store_cache.h
new file mode 100644
index 00000000000..99934424ffb
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/value_store_cache.h
@@ -0,0 +1,57 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_STORAGE_VALUE_STORE_CACHE_H_
+#define EXTENSIONS_BROWSER_API_STORAGE_VALUE_STORE_CACHE_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+
+class ValueStore;
+
+namespace extensions {
+
+class Extension;
+
+// Each namespace of the storage API implements this interface.
+// Instances are created on the UI thread, but from then on live on the FILE
+// thread. At shutdown, ShutdownOnUI() is first invoked on the UI thread, and
+// the destructor is invoked soon after on the FILE thread. This gives
+// implementations the chance to work with ValueStores on FILE but observe
+// events on UI.
+// It also means that any methods invoked on UI *before ShutdownOnUI()* can
+// safely post other methods to the FILE thread, since the deletion task is only
+// posted to FILE after ShutdownOnUI().
+class ValueStoreCache {
+ public:
+ typedef base::Callback<void(ValueStore*)> StorageCallback;
+
+ // Invoked on FILE.
+ virtual ~ValueStoreCache();
+
+ // This is invoked from the UI thread during destruction of the Profile that
+ // ultimately owns this object. Any Profile-related cleanups should be
+ // performed in this method, since the destructor will execute later, after
+ // the Profile is already gone.
+ virtual void ShutdownOnUI();
+
+ // Requests the cache to invoke |callback| with the appropriate ValueStore
+ // for the given |extension|. |callback| should be invoked with a NULL
+ // ValueStore in case of errors.
+ // |extension| is passed in a scoped_refptr<> because this method is
+ // asynchronously posted as a task to the loop returned by GetMessageLoop(),
+ // and this guarantees the Extension is still valid when the method executes.
+ virtual void RunWithValueStoreForExtension(
+ const StorageCallback& callback,
+ scoped_refptr<const Extension> extension) = 0;
+
+ // Requests the cache to delete any storage used by |extension_id|.
+ virtual void DeleteStorageSoon(const std::string& extension_id) = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_STORAGE_VALUE_STORE_CACHE_H_
diff --git a/chromium/extensions/browser/api/storage/weak_unlimited_settings_storage.cc b/chromium/extensions/browser/api/storage/weak_unlimited_settings_storage.cc
new file mode 100644
index 00000000000..3de934be26a
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/weak_unlimited_settings_storage.cc
@@ -0,0 +1,67 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/storage/weak_unlimited_settings_storage.h"
+
+namespace extensions {
+
+WeakUnlimitedSettingsStorage::WeakUnlimitedSettingsStorage(
+ ValueStore* delegate)
+ : delegate_(delegate) {}
+
+WeakUnlimitedSettingsStorage::~WeakUnlimitedSettingsStorage() {}
+
+size_t WeakUnlimitedSettingsStorage::GetBytesInUse(const std::string& key) {
+ return delegate_->GetBytesInUse(key);
+}
+
+size_t WeakUnlimitedSettingsStorage::GetBytesInUse(
+ const std::vector<std::string>& keys) {
+ return delegate_->GetBytesInUse(keys);
+}
+
+
+size_t WeakUnlimitedSettingsStorage::GetBytesInUse() {
+ return delegate_->GetBytesInUse();
+}
+
+ValueStore::ReadResult WeakUnlimitedSettingsStorage::Get(
+ const std::string& key) {
+ return delegate_->Get(key);
+}
+
+ValueStore::ReadResult WeakUnlimitedSettingsStorage::Get(
+ const std::vector<std::string>& keys) {
+ return delegate_->Get(keys);
+}
+
+ValueStore::ReadResult WeakUnlimitedSettingsStorage::Get() {
+ return delegate_->Get();
+}
+
+ValueStore::WriteResult WeakUnlimitedSettingsStorage::Set(
+ WriteOptions options, const std::string& key, const base::Value& value) {
+ return delegate_->Set(IGNORE_QUOTA, key, value);
+}
+
+ValueStore::WriteResult WeakUnlimitedSettingsStorage::Set(
+ WriteOptions options, const base::DictionaryValue& values) {
+ return delegate_->Set(IGNORE_QUOTA, values);
+}
+
+ValueStore::WriteResult WeakUnlimitedSettingsStorage::Remove(
+ const std::string& key) {
+ return delegate_->Remove(key);
+}
+
+ValueStore::WriteResult WeakUnlimitedSettingsStorage::Remove(
+ const std::vector<std::string>& keys) {
+ return delegate_->Remove(keys);
+}
+
+ValueStore::WriteResult WeakUnlimitedSettingsStorage::Clear() {
+ return delegate_->Clear();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/storage/weak_unlimited_settings_storage.h b/chromium/extensions/browser/api/storage/weak_unlimited_settings_storage.h
new file mode 100644
index 00000000000..96e2419f774
--- /dev/null
+++ b/chromium/extensions/browser/api/storage/weak_unlimited_settings_storage.h
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_STORAGE_WEAK_UNLIMITED_SETTINGS_STORAGE_H_
+#define EXTENSIONS_BROWSER_API_STORAGE_WEAK_UNLIMITED_SETTINGS_STORAGE_H_
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "extensions/browser/value_store/value_store.h"
+
+namespace extensions {
+
+// A ValueStore decorator which makes calls through |Set| ignore quota.
+// "Weak" because ownership of the delegate isn't taken; this is designed to be
+// temporarily attached to storage areas.
+class WeakUnlimitedSettingsStorage : public ValueStore {
+ public:
+ // Ownership of |delegate| NOT taken.
+ explicit WeakUnlimitedSettingsStorage(ValueStore* delegate);
+
+ ~WeakUnlimitedSettingsStorage() override;
+
+ // ValueStore implementation.
+ size_t GetBytesInUse(const std::string& key) override;
+ size_t GetBytesInUse(const std::vector<std::string>& keys) override;
+ size_t GetBytesInUse() override;
+ ReadResult Get(const std::string& key) override;
+ ReadResult Get(const std::vector<std::string>& keys) override;
+ ReadResult Get() override;
+ WriteResult Set(WriteOptions options,
+ const std::string& key,
+ const base::Value& value) override;
+ WriteResult Set(WriteOptions options,
+ const base::DictionaryValue& values) override;
+ WriteResult Remove(const std::string& key) override;
+ WriteResult Remove(const std::vector<std::string>& keys) override;
+ WriteResult Clear() override;
+
+ private:
+ // The delegate storage area, NOT OWNED.
+ ValueStore* const delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(WeakUnlimitedSettingsStorage);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_STORAGE_WEAK_UNLIMITED_SETTINGS_STORAGE_H_
diff --git a/chromium/extensions/browser/api/system_cpu/OWNERS b/chromium/extensions/browser/api/system_cpu/OWNERS
new file mode 100644
index 00000000000..20685fc6fc3
--- /dev/null
+++ b/chromium/extensions/browser/api/system_cpu/OWNERS
@@ -0,0 +1 @@
+hongbo.min@intel.com
diff --git a/chromium/extensions/browser/api/system_cpu/cpu_info_provider.cc b/chromium/extensions/browser/api/system_cpu/cpu_info_provider.cc
new file mode 100644
index 00000000000..ac85834c2bb
--- /dev/null
+++ b/chromium/extensions/browser/api/system_cpu/cpu_info_provider.cc
@@ -0,0 +1,74 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/system_cpu/cpu_info_provider.h"
+
+#include "base/sys_info.h"
+
+namespace extensions {
+
+using api::system_cpu::CpuInfo;
+
+// Static member intialization.
+base::LazyInstance<scoped_refptr<CpuInfoProvider> > CpuInfoProvider::provider_ =
+ LAZY_INSTANCE_INITIALIZER;
+
+CpuInfoProvider::CpuInfoProvider() {
+}
+
+CpuInfoProvider::~CpuInfoProvider() {
+}
+
+void CpuInfoProvider::InitializeForTesting(
+ scoped_refptr<CpuInfoProvider> provider) {
+ DCHECK(provider.get() != NULL);
+ provider_.Get() = provider;
+}
+
+bool CpuInfoProvider::QueryInfo() {
+ info_.num_of_processors = base::SysInfo::NumberOfProcessors();
+ info_.arch_name = base::SysInfo::OperatingSystemArchitecture();
+ info_.model_name = base::SysInfo::CPUModelName();
+ info_.features = GetFeatures();
+
+ info_.processors.clear();
+ // Fill in the correct number of uninitialized ProcessorInfos.
+ for (int i = 0; i < info_.num_of_processors; ++i)
+ info_.processors.push_back(api::system_cpu::ProcessorInfo());
+ // Initialize the ProcessorInfos, or return an empty array if that fails.
+ if (!QueryCpuTimePerProcessor(&info_.processors))
+ info_.processors.clear();
+ return true;
+}
+
+std::vector<std::string> CpuInfoProvider::GetFeatures() const {
+ std::vector<std::string> features;
+ // These are the feature codes used by /proc/cpuinfo on Linux.
+ if (cpu_.has_mmx())
+ features.push_back("mmx");
+ if (cpu_.has_sse())
+ features.push_back("sse");
+ if (cpu_.has_sse2())
+ features.push_back("sse2");
+ if (cpu_.has_sse3())
+ features.push_back("sse3");
+ if (cpu_.has_ssse3())
+ features.push_back("ssse3");
+ if (cpu_.has_sse41())
+ features.push_back("sse4_1");
+ if (cpu_.has_sse42())
+ features.push_back("sse4_2");
+ if (cpu_.has_avx())
+ features.push_back("avx");
+ return features;
+}
+
+// static
+CpuInfoProvider* CpuInfoProvider::Get() {
+ if (provider_.Get().get() == NULL)
+ provider_.Get() = new CpuInfoProvider();
+ return provider_.Get().get();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/system_cpu/cpu_info_provider.h b/chromium/extensions/browser/api/system_cpu/cpu_info_provider.h
new file mode 100644
index 00000000000..617d26e7694
--- /dev/null
+++ b/chromium/extensions/browser/api/system_cpu/cpu_info_provider.h
@@ -0,0 +1,61 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SYSTEM_CPU_CPU_INFO_PROVIDER_H_
+#define EXTENSIONS_BROWSER_API_SYSTEM_CPU_CPU_INFO_PROVIDER_H_
+
+#include <vector>
+
+#include "base/cpu.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "extensions/browser/api/system_info/system_info_provider.h"
+#include "extensions/common/api/system_cpu.h"
+
+namespace extensions {
+
+class CpuInfoProvider : public SystemInfoProvider {
+ public:
+ // Return the single shared instance of CpuInfoProvider.
+ static CpuInfoProvider* Get();
+
+ const api::system_cpu::CpuInfo& cpu_info() const { return info_; }
+
+ static void InitializeForTesting(scoped_refptr<CpuInfoProvider> provider);
+
+ private:
+ friend class MockCpuInfoProviderImpl;
+
+ CpuInfoProvider();
+ ~CpuInfoProvider() override;
+
+ // Platform specific implementation for querying the CPU time information
+ // for each processor.
+ virtual bool QueryCpuTimePerProcessor(
+ std::vector<api::system_cpu::ProcessorInfo>* infos);
+
+ // Overriden from SystemInfoProvider.
+ bool QueryInfo() override;
+
+ // Creates a list of codenames for currently active features.
+ std::vector<std::string> GetFeatures() const;
+
+ // The last information filled up by QueryInfo and is accessed on multiple
+ // threads, but the whole class is being guarded by SystemInfoProvider base
+ // class.
+ //
+ // |info_| is accessed on the UI thread while |is_waiting_for_completion_| is
+ // false and on the sequenced worker pool while |is_waiting_for_completion_|
+ // is true.
+ api::system_cpu::CpuInfo info_;
+
+ static base::LazyInstance<scoped_refptr<CpuInfoProvider> > provider_;
+ base::CPU cpu_;
+
+ DISALLOW_COPY_AND_ASSIGN(CpuInfoProvider);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SYSTEM_CPU_CPU_INFO_PROVIDER_H_
diff --git a/chromium/extensions/browser/api/system_cpu/cpu_info_provider_linux.cc b/chromium/extensions/browser/api/system_cpu/cpu_info_provider_linux.cc
new file mode 100644
index 00000000000..2adbdb15f0f
--- /dev/null
+++ b/chromium/extensions/browser/api/system_cpu/cpu_info_provider_linux.cc
@@ -0,0 +1,77 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/system_cpu/cpu_info_provider.h"
+
+#include <stdint.h>
+
+#include <cstdio>
+#include <sstream>
+
+#include "base/files/file_util.h"
+#include "base/format_macros.h"
+
+namespace extensions {
+
+namespace {
+
+const char kProcStat[] = "/proc/stat";
+
+} // namespace
+
+bool CpuInfoProvider::QueryCpuTimePerProcessor(
+ std::vector<api::system_cpu::ProcessorInfo>* infos) {
+ DCHECK(infos);
+
+ // WARNING: this method may return incomplete data because some processors may
+ // be brought offline at runtime. /proc/stat does not report statistics of
+ // offline processors. CPU usages of offline processors will be filled with
+ // zeros.
+ //
+ // An example of output of /proc/stat when processor 0 and 3 are online, but
+ // processor 1 and 2 are offline:
+ //
+ // cpu 145292 20018 83444 1485410 995 44 3578 0 0 0
+ // cpu0 138060 19947 78350 1479514 570 44 3576 0 0 0
+ // cpu3 2033 32 1075 1400 52 0 1 0 0 0
+ std::string contents;
+ if (!base::ReadFileToString(base::FilePath(kProcStat), &contents))
+ return false;
+
+ std::istringstream iss(contents);
+ std::string line;
+
+ // Skip the first line because it is just an aggregated number of
+ // all cpuN lines.
+ std::getline(iss, line);
+ while (std::getline(iss, line)) {
+ if (line.compare(0, 3, "cpu") != 0)
+ continue;
+
+ uint64_t user = 0, nice = 0, sys = 0, idle = 0;
+ uint32_t pindex = 0;
+ int vals =
+ sscanf(line.c_str(),
+ "cpu%" PRIu32 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64,
+ &pindex,
+ &user,
+ &nice,
+ &sys,
+ &idle);
+ if (vals != 5 || pindex >= infos->size()) {
+ NOTREACHED();
+ return false;
+ }
+
+ infos->at(pindex).usage.kernel = static_cast<double>(sys);
+ infos->at(pindex).usage.user = static_cast<double>(user + nice);
+ infos->at(pindex).usage.idle = static_cast<double>(idle);
+ infos->at(pindex).usage.total =
+ static_cast<double>(sys + user + nice + idle);
+ }
+
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/system_cpu/cpu_info_provider_mac.cc b/chromium/extensions/browser/api/system_cpu/cpu_info_provider_mac.cc
new file mode 100644
index 00000000000..ccc2e4d0e99
--- /dev/null
+++ b/chromium/extensions/browser/api/system_cpu/cpu_info_provider_mac.cc
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/system_cpu/cpu_info_provider.h"
+
+#include <mach/mach_host.h>
+
+#include "base/mac/scoped_mach_port.h"
+#include "base/sys_info.h"
+
+namespace extensions {
+
+bool CpuInfoProvider::QueryCpuTimePerProcessor(
+ std::vector<api::system_cpu::ProcessorInfo>* infos) {
+ DCHECK(infos);
+
+ natural_t num_of_processors;
+ base::mac::ScopedMachSendRight host(mach_host_self());
+ mach_msg_type_number_t type;
+ processor_cpu_load_info_data_t* cpu_infos;
+
+ if (host_processor_info(host.get(),
+ PROCESSOR_CPU_LOAD_INFO,
+ &num_of_processors,
+ reinterpret_cast<processor_info_array_t*>(&cpu_infos),
+ &type) == KERN_SUCCESS) {
+ DCHECK_EQ(num_of_processors,
+ static_cast<natural_t>(base::SysInfo::NumberOfProcessors()));
+ DCHECK_EQ(num_of_processors, static_cast<natural_t>(infos->size()));
+
+ for (natural_t i = 0; i < num_of_processors; ++i) {
+ double user = static_cast<double>(cpu_infos[i].cpu_ticks[CPU_STATE_USER]),
+ sys =
+ static_cast<double>(cpu_infos[i].cpu_ticks[CPU_STATE_SYSTEM]),
+ nice = static_cast<double>(cpu_infos[i].cpu_ticks[CPU_STATE_NICE]),
+ idle = static_cast<double>(cpu_infos[i].cpu_ticks[CPU_STATE_IDLE]);
+
+ infos->at(i).usage.kernel = sys;
+ infos->at(i).usage.user = user + nice;
+ infos->at(i).usage.idle = idle;
+ infos->at(i).usage.total = sys + user + nice + idle;
+ }
+
+ vm_deallocate(mach_task_self(),
+ reinterpret_cast<vm_address_t>(cpu_infos),
+ num_of_processors * sizeof(processor_cpu_load_info));
+
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/system_cpu/cpu_info_provider_win.cc b/chromium/extensions/browser/api/system_cpu/cpu_info_provider_win.cc
new file mode 100644
index 00000000000..eba4891b625
--- /dev/null
+++ b/chromium/extensions/browser/api/system_cpu/cpu_info_provider_win.cc
@@ -0,0 +1,77 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/system_cpu/cpu_info_provider.h"
+
+#include <windows.h>
+#include <winternl.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/sys_info.h"
+
+namespace extensions {
+
+namespace {
+
+const wchar_t kNtdll[] = L"ntdll.dll";
+const char kNtQuerySystemInformationName[] = "NtQuerySystemInformation";
+
+// See MSDN about NtQuerySystemInformation definition.
+typedef DWORD(WINAPI* NtQuerySystemInformationPF)(DWORD system_info_class,
+ PVOID system_info,
+ ULONG system_info_length,
+ PULONG return_length);
+
+} // namespace
+
+bool CpuInfoProvider::QueryCpuTimePerProcessor(
+ std::vector<api::system_cpu::ProcessorInfo>* infos) {
+ DCHECK(infos);
+
+ HMODULE ntdll = GetModuleHandle(kNtdll);
+ CHECK(ntdll != NULL);
+ NtQuerySystemInformationPF NtQuerySystemInformation =
+ reinterpret_cast<NtQuerySystemInformationPF>(
+ ::GetProcAddress(ntdll, kNtQuerySystemInformationName));
+
+ CHECK(NtQuerySystemInformation != NULL);
+
+ int num_of_processors = base::SysInfo::NumberOfProcessors();
+ scoped_ptr<SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[]> processor_info(
+ new SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[num_of_processors]);
+
+ ULONG returned_bytes = 0,
+ bytes = sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION) *
+ num_of_processors;
+ if (!NT_SUCCESS(
+ NtQuerySystemInformation(SystemProcessorPerformanceInformation,
+ processor_info.get(),
+ bytes,
+ &returned_bytes)))
+ return false;
+
+ int returned_num_of_processors =
+ returned_bytes / sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION);
+
+ if (returned_num_of_processors != num_of_processors)
+ return false;
+
+ DCHECK_EQ(num_of_processors, static_cast<int>(infos->size()));
+ for (int i = 0; i < returned_num_of_processors; ++i) {
+ double kernel = static_cast<double>(processor_info[i].KernelTime.QuadPart),
+ user = static_cast<double>(processor_info[i].UserTime.QuadPart),
+ idle = static_cast<double>(processor_info[i].IdleTime.QuadPart);
+
+ // KernelTime needs to be fixed-up, because it includes both idle time and
+ // real kernel time.
+ infos->at(i).usage.kernel = kernel - idle;
+ infos->at(i).usage.user = user;
+ infos->at(i).usage.idle = idle;
+ infos->at(i).usage.total = kernel + user;
+ }
+
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/system_cpu/system_cpu_api.cc b/chromium/extensions/browser/api/system_cpu/system_cpu_api.cc
new file mode 100644
index 00000000000..5cfe69b0ff4
--- /dev/null
+++ b/chromium/extensions/browser/api/system_cpu/system_cpu_api.cc
@@ -0,0 +1,34 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "extensions/browser/api/system_cpu/cpu_info_provider.h"
+#include "extensions/browser/api/system_cpu/system_cpu_api.h"
+#include "extensions/common/features/base_feature_provider.h"
+
+namespace extensions {
+
+using api::system_cpu::CpuInfo;
+
+SystemCpuGetInfoFunction::SystemCpuGetInfoFunction() {
+}
+
+SystemCpuGetInfoFunction::~SystemCpuGetInfoFunction() {
+}
+
+bool SystemCpuGetInfoFunction::RunAsync() {
+ CpuInfoProvider::Get()->StartQueryInfo(
+ base::Bind(&SystemCpuGetInfoFunction::OnGetCpuInfoCompleted, this));
+ return true;
+}
+
+void SystemCpuGetInfoFunction::OnGetCpuInfoCompleted(bool success) {
+ if (success)
+ SetResult(CpuInfoProvider::Get()->cpu_info().ToValue().release());
+ else
+ SetError("Error occurred when querying cpu information.");
+ SendResponse(success);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/system_cpu/system_cpu_api.h b/chromium/extensions/browser/api/system_cpu/system_cpu_api.h
new file mode 100644
index 00000000000..544ef801110
--- /dev/null
+++ b/chromium/extensions/browser/api/system_cpu/system_cpu_api.h
@@ -0,0 +1,25 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef EXTENSIONS_BROWSER_API_SYSTEM_CPU_SYSTEM_CPU_API_H_
+#define EXTENSIONS_BROWSER_API_SYSTEM_CPU_SYSTEM_CPU_API_H_
+
+#include "extensions/common/api/system_cpu.h"
+#include "extensions/browser/extension_function.h"
+
+namespace extensions {
+
+class SystemCpuGetInfoFunction : public AsyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("system.cpu.getInfo", SYSTEM_CPU_GETINFO)
+ SystemCpuGetInfoFunction();
+
+ private:
+ ~SystemCpuGetInfoFunction() override;
+ bool RunAsync() override;
+ void OnGetCpuInfoCompleted(bool success);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SYSTEM_CPU_SYSTEM_CPU_API_H_
diff --git a/chromium/extensions/browser/api/system_cpu/system_cpu_apitest.cc b/chromium/extensions/browser/api/system_cpu/system_cpu_apitest.cc
new file mode 100644
index 00000000000..eabacff9393
--- /dev/null
+++ b/chromium/extensions/browser/api/system_cpu/system_cpu_apitest.cc
@@ -0,0 +1,55 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/system_cpu/cpu_info_provider.h"
+#include "extensions/shell/test/shell_apitest.h"
+
+namespace extensions {
+
+using api::system_cpu::CpuInfo;
+
+class MockCpuInfoProviderImpl : public CpuInfoProvider {
+ public:
+ MockCpuInfoProviderImpl() {}
+
+ bool QueryInfo() override {
+ info_.num_of_processors = 4;
+ info_.arch_name = "x86";
+ info_.model_name = "unknown";
+
+ info_.features.clear();
+ info_.features.push_back("mmx");
+ info_.features.push_back("avx");
+
+ info_.processors.clear();
+ info_.processors.push_back(api::system_cpu::ProcessorInfo());
+ info_.processors[0].usage.kernel = 1;
+ info_.processors[0].usage.user = 2;
+ info_.processors[0].usage.idle = 3;
+ info_.processors[0].usage.total = 6;
+ return true;
+ }
+
+ private:
+ ~MockCpuInfoProviderImpl() override {}
+};
+
+class SystemCpuApiTest : public ShellApiTest {
+ public:
+ SystemCpuApiTest() {}
+ ~SystemCpuApiTest() override {}
+
+ void SetUpInProcessBrowserTestFixture() override {
+ ShellApiTest::SetUpInProcessBrowserTestFixture();
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(SystemCpuApiTest, Cpu) {
+ CpuInfoProvider* provider = new MockCpuInfoProviderImpl();
+ // The provider is owned by the single CpuInfoProvider instance.
+ CpuInfoProvider::InitializeForTesting(provider);
+ ASSERT_TRUE(RunAppTest("system/cpu")) << message_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/system_display/OWNERS b/chromium/extensions/browser/api/system_display/OWNERS
new file mode 100644
index 00000000000..20685fc6fc3
--- /dev/null
+++ b/chromium/extensions/browser/api/system_display/OWNERS
@@ -0,0 +1 @@
+hongbo.min@intel.com
diff --git a/chromium/extensions/browser/api/system_display/display_info_provider.cc b/chromium/extensions/browser/api/system_display/display_info_provider.cc
new file mode 100644
index 00000000000..54c247af3fc
--- /dev/null
+++ b/chromium/extensions/browser/api/system_display/display_info_provider.cc
@@ -0,0 +1,95 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/system_display/display_info_provider.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "extensions/common/api/system_display.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+
+namespace extensions {
+
+namespace {
+
+// Created on demand and will leak when the process exits.
+DisplayInfoProvider* g_display_info_provider = NULL;
+
+// Converts Rotation enum to integer.
+int RotationToDegrees(gfx::Display::Rotation rotation) {
+ switch (rotation) {
+ case gfx::Display::ROTATE_0:
+ return 0;
+ case gfx::Display::ROTATE_90:
+ return 90;
+ case gfx::Display::ROTATE_180:
+ return 180;
+ case gfx::Display::ROTATE_270:
+ return 270;
+ }
+ return 0;
+}
+
+} // namespace
+
+DisplayInfoProvider::~DisplayInfoProvider() {}
+
+// static
+DisplayInfoProvider* DisplayInfoProvider::Get() {
+ if (g_display_info_provider == NULL)
+ g_display_info_provider = DisplayInfoProvider::Create();
+ return g_display_info_provider;
+}
+
+// static
+void DisplayInfoProvider::InitializeForTesting(
+ DisplayInfoProvider* display_info_provider) {
+ DCHECK(display_info_provider);
+ g_display_info_provider = display_info_provider;
+}
+
+// static
+// Creates new DisplayUnitInfo struct for |display|.
+api::system_display::DisplayUnitInfo DisplayInfoProvider::CreateDisplayUnitInfo(
+ const gfx::Display& display,
+ int64_t primary_display_id) {
+ api::system_display::DisplayUnitInfo unit;
+ const gfx::Rect& bounds = display.bounds();
+ const gfx::Rect& work_area = display.work_area();
+ unit.id = base::Int64ToString(display.id());
+ unit.is_primary = (display.id() == primary_display_id);
+ unit.is_internal = display.IsInternal();
+ unit.is_enabled = true;
+ unit.rotation = RotationToDegrees(display.rotation());
+ unit.bounds.left = bounds.x();
+ unit.bounds.top = bounds.y();
+ unit.bounds.width = bounds.width();
+ unit.bounds.height = bounds.height();
+ unit.work_area.left = work_area.x();
+ unit.work_area.top = work_area.y();
+ unit.work_area.width = work_area.width();
+ unit.work_area.height = work_area.height();
+ return unit;
+}
+
+void DisplayInfoProvider::EnableUnifiedDesktop(bool enable) {}
+
+DisplayInfo DisplayInfoProvider::GetAllDisplaysInfo() {
+ gfx::Screen* screen = gfx::Screen::GetScreen();
+ int64_t primary_id = screen->GetPrimaryDisplay().id();
+ std::vector<gfx::Display> displays = screen->GetAllDisplays();
+ DisplayInfo all_displays;
+ for (const gfx::Display& display : displays) {
+ api::system_display::DisplayUnitInfo unit =
+ CreateDisplayUnitInfo(display, primary_id);
+ UpdateDisplayUnitInfoForPlatform(display, &unit);
+ all_displays.push_back(std::move(unit));
+ }
+ return all_displays;
+}
+
+DisplayInfoProvider::DisplayInfoProvider() {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/system_display/display_info_provider.h b/chromium/extensions/browser/api/system_display/display_info_provider.h
new file mode 100644
index 00000000000..48e3d9e537f
--- /dev/null
+++ b/chromium/extensions/browser/api/system_display/display_info_provider.h
@@ -0,0 +1,78 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SYSTEM_DISPLAY_DISPLAY_INFO_PROVIDER_H_
+#define EXTENSIONS_BROWSER_API_SYSTEM_DISPLAY_DISPLAY_INFO_PROVIDER_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+
+namespace gfx {
+class Display;
+}
+
+namespace extensions {
+
+namespace api {
+namespace system_display {
+struct DisplayProperties;
+struct DisplayUnitInfo;
+}
+}
+
+typedef std::vector<api::system_display::DisplayUnitInfo> DisplayInfo;
+
+class DisplayInfoProvider {
+ public:
+ virtual ~DisplayInfoProvider();
+
+ // Returns a pointer to DisplayInfoProvider or NULL if Create()
+ // or InitializeForTesting() or not called yet.
+ static DisplayInfoProvider* Get();
+
+ // This is for tests that run in its own process (e.g. browser_tests).
+ // Using this in other tests (e.g. unit_tests) will result in DCHECK failure.
+ static void InitializeForTesting(DisplayInfoProvider* display_info_provider);
+
+ // Updates the display with |display_id| according to |info|. Returns whether
+ // the display was successfully updated. On failure, no display parameters
+ // should be changed, and |error| should be set to the error string.
+ virtual bool SetInfo(const std::string& display_id,
+ const api::system_display::DisplayProperties& info,
+ std::string* error) = 0;
+
+ // Enable the unified desktop feature.
+ virtual void EnableUnifiedDesktop(bool enable);
+
+ // Get display information.
+ virtual DisplayInfo GetAllDisplaysInfo();
+
+ protected:
+ DisplayInfoProvider();
+
+ // Create a DisplayUnitInfo from a gfx::Display for implementations of
+ // GetAllDisplaysInfo()
+ static api::system_display::DisplayUnitInfo CreateDisplayUnitInfo(
+ const gfx::Display& display,
+ int64_t primary_display_id);
+
+ private:
+ static DisplayInfoProvider* Create();
+
+ // Update the content of the |unit| obtained for |display| using
+ // platform specific method.
+ virtual void UpdateDisplayUnitInfoForPlatform(
+ const gfx::Display& display,
+ api::system_display::DisplayUnitInfo* unit) = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplayInfoProvider);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SYSTEM_DISPLAY_DISPLAY_INFO_PROVIDER_H_
diff --git a/chromium/extensions/browser/api/system_display/system_display_api.cc b/chromium/extensions/browser/api/system_display/system_display_api.cc
new file mode 100644
index 00000000000..423109d2ab3
--- /dev/null
+++ b/chromium/extensions/browser/api/system_display/system_display_api.cc
@@ -0,0 +1,64 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/system_display/system_display_api.h"
+
+#include <string>
+
+#include "build/build_config.h"
+#include "extensions/browser/api/system_display/display_info_provider.h"
+#include "extensions/common/api/system_display.h"
+
+#if defined(OS_CHROMEOS)
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/manifest_handlers/kiosk_mode_info.h"
+#include "ui/gfx/screen.h"
+#endif
+
+namespace extensions {
+
+using api::system_display::DisplayUnitInfo;
+
+namespace SetDisplayProperties = api::system_display::SetDisplayProperties;
+
+bool SystemDisplayGetInfoFunction::RunSync() {
+ DisplayInfo all_displays_info =
+ DisplayInfoProvider::Get()->GetAllDisplaysInfo();
+ results_ = api::system_display::GetInfo::Results::Create(all_displays_info);
+ return true;
+}
+
+bool SystemDisplaySetDisplayPropertiesFunction::RunSync() {
+#if !defined(OS_CHROMEOS)
+ SetError("Function available only on ChromeOS.");
+ return false;
+#else
+ if (!KioskModeInfo::IsKioskEnabled(extension())) {
+ SetError("The extension needs to be kiosk enabled to use the function.");
+ return false;
+ }
+ std::string error;
+ scoped_ptr<SetDisplayProperties::Params> params(
+ SetDisplayProperties::Params::Create(*args_));
+ bool success =
+ DisplayInfoProvider::Get()->SetInfo(params->id, params->info, &error);
+ if (!success)
+ SetError(error);
+ return true;
+#endif
+}
+
+bool SystemDisplayEnableUnifiedDesktopFunction::RunSync() {
+#if !defined(OS_CHROMEOS)
+ SetError("Function available only on ChromeOS.");
+ return false;
+#else
+ scoped_ptr<api::system_display::EnableUnifiedDesktop::Params> params(
+ api::system_display::EnableUnifiedDesktop::Params::Create(*args_));
+ DisplayInfoProvider::Get()->EnableUnifiedDesktop(params->enabled);
+ return true;
+#endif
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/system_display/system_display_api.h b/chromium/extensions/browser/api/system_display/system_display_api.h
new file mode 100644
index 00000000000..3c35e64d7ea
--- /dev/null
+++ b/chromium/extensions/browser/api/system_display/system_display_api.h
@@ -0,0 +1,45 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SYSTEM_DISPLAY_SYSTEM_DISPLAY_API_H_
+#define EXTENSIONS_BROWSER_API_SYSTEM_DISPLAY_SYSTEM_DISPLAY_API_H_
+
+#include <string>
+
+#include "extensions/browser/extension_function.h"
+
+namespace extensions {
+
+class SystemDisplayGetInfoFunction : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("system.display.getInfo", SYSTEM_DISPLAY_GETINFO);
+
+ protected:
+ ~SystemDisplayGetInfoFunction() override {}
+ bool RunSync() override;
+};
+
+class SystemDisplaySetDisplayPropertiesFunction : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("system.display.setDisplayProperties",
+ SYSTEM_DISPLAY_SETDISPLAYPROPERTIES);
+
+ protected:
+ ~SystemDisplaySetDisplayPropertiesFunction() override {}
+ bool RunSync() override;
+};
+
+class SystemDisplayEnableUnifiedDesktopFunction : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("system.display.enableUnifiedDesktop",
+ SYSTEM_DISPLAY_ENABLEUNIFIEDDESKTOP);
+
+ protected:
+ ~SystemDisplayEnableUnifiedDesktopFunction() override {}
+ bool RunSync() override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SYSTEM_DISPLAY_SYSTEM_DISPLAY_API_H_
diff --git a/chromium/extensions/browser/api/system_display/system_display_apitest.cc b/chromium/extensions/browser/api/system_display/system_display_apitest.cc
new file mode 100644
index 00000000000..2c0548ec7ce
--- /dev/null
+++ b/chromium/extensions/browser/api/system_display/system_display_apitest.cc
@@ -0,0 +1,302 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/debug/leak_annotations.h"
+#include "base/macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "build/build_config.h"
+#include "extensions/browser/api/system_display/display_info_provider.h"
+#include "extensions/browser/api/system_display/system_display_api.h"
+#include "extensions/browser/api_test_utils.h"
+#include "extensions/common/api/system_display.h"
+#include "extensions/shell/test/shell_apitest.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/display_observer.h"
+#include "ui/gfx/screen.h"
+
+namespace extensions {
+
+using api::system_display::Bounds;
+using api::system_display::DisplayUnitInfo;
+using gfx::Screen;
+
+class MockScreen : public Screen {
+ public:
+ MockScreen() {
+ for (int i = 0; i < 4; i++) {
+ gfx::Rect bounds(0, 0, 1280, 720);
+ gfx::Rect work_area(0, 0, 960, 720);
+ gfx::Display display(i, bounds);
+ display.set_work_area(work_area);
+ displays_.push_back(display);
+ }
+ }
+ ~MockScreen() override {}
+
+ protected:
+ // Overridden from gfx::Screen:
+ gfx::Point GetCursorScreenPoint() override { return gfx::Point(); }
+ gfx::NativeWindow GetWindowUnderCursor() override {
+ return gfx::NativeWindow();
+ }
+ gfx::NativeWindow GetWindowAtScreenPoint(const gfx::Point& point) override {
+ return gfx::NativeWindow();
+ }
+ int GetNumDisplays() const override {
+ return static_cast<int>(displays_.size());
+ }
+ std::vector<gfx::Display> GetAllDisplays() const override {
+ return displays_;
+ }
+ gfx::Display GetDisplayNearestWindow(gfx::NativeView window) const override {
+ return gfx::Display(0);
+ }
+ gfx::Display GetDisplayNearestPoint(const gfx::Point& point) const override {
+ return gfx::Display(0);
+ }
+ gfx::Display GetDisplayMatching(const gfx::Rect& match_rect) const override {
+ return gfx::Display(0);
+ }
+ gfx::Display GetPrimaryDisplay() const override { return displays_[0]; }
+ void AddObserver(gfx::DisplayObserver* observer) override {}
+ void RemoveObserver(gfx::DisplayObserver* observer) override {}
+
+ private:
+ std::vector<gfx::Display> displays_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockScreen);
+};
+
+class MockDisplayInfoProvider : public DisplayInfoProvider {
+ public:
+ MockDisplayInfoProvider() {}
+
+ ~MockDisplayInfoProvider() override {}
+
+ bool SetInfo(const std::string& display_id,
+ const api::system_display::DisplayProperties& params,
+ std::string* error) override {
+ // Should get called only once per test case.
+ EXPECT_FALSE(set_info_value_);
+ set_info_value_ = params.ToValue();
+ set_info_display_id_ = display_id;
+ return true;
+ }
+
+ void EnableUnifiedDesktop(bool enable) override {
+ unified_desktop_enabled_ = enable;
+ }
+
+ scoped_ptr<base::DictionaryValue> GetSetInfoValue() {
+ return std::move(set_info_value_);
+ }
+
+ std::string GetSetInfoDisplayId() const { return set_info_display_id_; }
+
+ bool unified_desktop_enabled() const { return unified_desktop_enabled_; }
+
+ private:
+ // Update the content of the |unit| obtained for |display| using
+ // platform specific method.
+ void UpdateDisplayUnitInfoForPlatform(
+ const gfx::Display& display,
+ extensions::api::system_display::DisplayUnitInfo* unit) override {
+ int64_t id = display.id();
+ unit->name = "DISPLAY NAME FOR " + base::Int64ToString(id);
+ if (id == 1)
+ unit->mirroring_source_id = "0";
+ unit->is_primary = id == 0 ? true : false;
+ unit->is_internal = id == 0 ? true : false;
+ unit->is_enabled = true;
+ unit->rotation = (90 * id) % 360;
+ unit->dpi_x = 96.0;
+ unit->dpi_y = 96.0;
+ if (id == 0) {
+ unit->overscan.left = 20;
+ unit->overscan.top = 40;
+ unit->overscan.right = 60;
+ unit->overscan.bottom = 80;
+ }
+ }
+
+ scoped_ptr<base::DictionaryValue> set_info_value_;
+ std::string set_info_display_id_;
+ bool unified_desktop_enabled_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(MockDisplayInfoProvider);
+};
+
+class SystemDisplayApiTest : public ShellApiTest {
+ public:
+ SystemDisplayApiTest()
+ : provider_(new MockDisplayInfoProvider), screen_(new MockScreen) {}
+
+ ~SystemDisplayApiTest() override {}
+
+ void SetUpOnMainThread() override {
+ ShellApiTest::SetUpOnMainThread();
+ ANNOTATE_LEAKING_OBJECT_PTR(gfx::Screen::GetScreen());
+ gfx::Screen::SetScreenInstance(screen_.get());
+ DisplayInfoProvider::InitializeForTesting(provider_.get());
+ }
+
+ protected:
+ scoped_ptr<MockDisplayInfoProvider> provider_;
+ scoped_ptr<gfx::Screen> screen_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SystemDisplayApiTest);
+};
+
+IN_PROC_BROWSER_TEST_F(SystemDisplayApiTest, GetDisplay) {
+ ASSERT_TRUE(RunAppTest("system/display")) << message_;
+}
+
+#if !defined(OS_CHROMEOS)
+IN_PROC_BROWSER_TEST_F(SystemDisplayApiTest, SetDisplay) {
+ scoped_refptr<SystemDisplaySetDisplayPropertiesFunction> set_info_function(
+ new SystemDisplaySetDisplayPropertiesFunction());
+
+ set_info_function->set_has_callback(true);
+
+ EXPECT_EQ(
+ "Function available only on ChromeOS.",
+ api_test_utils::RunFunctionAndReturnError(
+ set_info_function.get(), "[\"display_id\", {}]", browser_context()));
+
+ scoped_ptr<base::DictionaryValue> set_info = provider_->GetSetInfoValue();
+ EXPECT_FALSE(set_info);
+}
+#endif // !defined(OS_CHROMEOS)
+
+#if defined(OS_CHROMEOS)
+IN_PROC_BROWSER_TEST_F(SystemDisplayApiTest, SetDisplayNotKioskEnabled) {
+ scoped_ptr<base::DictionaryValue> test_extension_value(
+ api_test_utils::ParseDictionary(
+ "{\n"
+ " \"name\": \"Test\",\n"
+ " \"version\": \"1.0\",\n"
+ " \"app\": {\n"
+ " \"background\": {\n"
+ " \"scripts\": [\"background.js\"]\n"
+ " }\n"
+ " }\n"
+ "}"));
+ scoped_refptr<Extension> test_extension(
+ api_test_utils::CreateExtension(test_extension_value.get()));
+
+ scoped_refptr<SystemDisplaySetDisplayPropertiesFunction> set_info_function(
+ new SystemDisplaySetDisplayPropertiesFunction());
+
+ set_info_function->set_extension(test_extension.get());
+ set_info_function->set_has_callback(true);
+
+ EXPECT_EQ(
+ "The extension needs to be kiosk enabled to use the function.",
+ api_test_utils::RunFunctionAndReturnError(
+ set_info_function.get(), "[\"display_id\", {}]", browser_context()));
+
+ scoped_ptr<base::DictionaryValue> set_info = provider_->GetSetInfoValue();
+ EXPECT_FALSE(set_info);
+}
+
+IN_PROC_BROWSER_TEST_F(SystemDisplayApiTest, SetDisplayKioskEnabled) {
+ scoped_ptr<base::DictionaryValue> test_extension_value(
+ api_test_utils::ParseDictionary(
+ "{\n"
+ " \"name\": \"Test\",\n"
+ " \"version\": \"1.0\",\n"
+ " \"app\": {\n"
+ " \"background\": {\n"
+ " \"scripts\": [\"background.js\"]\n"
+ " }\n"
+ " },\n"
+ " \"kiosk_enabled\": true\n"
+ "}"));
+ scoped_refptr<Extension> test_extension(
+ api_test_utils::CreateExtension(test_extension_value.get()));
+
+ scoped_refptr<SystemDisplaySetDisplayPropertiesFunction> set_info_function(
+ new SystemDisplaySetDisplayPropertiesFunction());
+
+ set_info_function->set_has_callback(true);
+ set_info_function->set_extension(test_extension.get());
+
+ ASSERT_TRUE(api_test_utils::RunFunction(
+ set_info_function.get(),
+ "[\"display_id\", {\n"
+ " \"isPrimary\": true,\n"
+ " \"mirroringSourceId\": \"mirroringId\",\n"
+ " \"boundsOriginX\": 100,\n"
+ " \"boundsOriginY\": 200,\n"
+ " \"rotation\": 90,\n"
+ " \"overscan\": {\"left\": 1, \"top\": 2, \"right\": 3, \"bottom\": 4}\n"
+ "}]",
+ browser_context()));
+
+ scoped_ptr<base::DictionaryValue> set_info = provider_->GetSetInfoValue();
+ ASSERT_TRUE(set_info);
+ EXPECT_TRUE(api_test_utils::GetBoolean(set_info.get(), "isPrimary"));
+ EXPECT_EQ("mirroringId",
+ api_test_utils::GetString(set_info.get(), "mirroringSourceId"));
+ EXPECT_EQ(100, api_test_utils::GetInteger(set_info.get(), "boundsOriginX"));
+ EXPECT_EQ(200, api_test_utils::GetInteger(set_info.get(), "boundsOriginY"));
+ EXPECT_EQ(90, api_test_utils::GetInteger(set_info.get(), "rotation"));
+ base::DictionaryValue* overscan;
+ ASSERT_TRUE(set_info->GetDictionary("overscan", &overscan));
+ EXPECT_EQ(1, api_test_utils::GetInteger(overscan, "left"));
+ EXPECT_EQ(2, api_test_utils::GetInteger(overscan, "top"));
+ EXPECT_EQ(3, api_test_utils::GetInteger(overscan, "right"));
+ EXPECT_EQ(4, api_test_utils::GetInteger(overscan, "bottom"));
+
+ EXPECT_EQ("display_id", provider_->GetSetInfoDisplayId());
+}
+
+IN_PROC_BROWSER_TEST_F(SystemDisplayApiTest, EnableUnifiedDesktop) {
+ scoped_ptr<base::DictionaryValue> test_extension_value(
+ api_test_utils::ParseDictionary("{\n"
+ " \"name\": \"Test\",\n"
+ " \"version\": \"1.0\",\n"
+ " \"app\": {\n"
+ " \"background\": {\n"
+ " \"scripts\": [\"background.js\"]\n"
+ " }\n"
+ " }\n"
+ "}"));
+ scoped_refptr<Extension> test_extension(
+ api_test_utils::CreateExtension(test_extension_value.get()));
+ {
+ scoped_refptr<SystemDisplayEnableUnifiedDesktopFunction>
+ enable_unified_function(
+ new SystemDisplayEnableUnifiedDesktopFunction());
+
+ enable_unified_function->set_has_callback(true);
+ enable_unified_function->set_extension(test_extension.get());
+
+ EXPECT_FALSE(provider_->unified_desktop_enabled());
+
+ ASSERT_TRUE(api_test_utils::RunFunction(enable_unified_function.get(),
+ "[true]", browser_context()));
+ EXPECT_TRUE(provider_->unified_desktop_enabled());
+ }
+ {
+ scoped_refptr<SystemDisplayEnableUnifiedDesktopFunction>
+ enable_unified_function(
+ new SystemDisplayEnableUnifiedDesktopFunction());
+
+ enable_unified_function->set_has_callback(true);
+ enable_unified_function->set_extension(test_extension.get());
+ ASSERT_TRUE(api_test_utils::RunFunction(enable_unified_function.get(),
+ "[false]", browser_context()));
+ EXPECT_FALSE(provider_->unified_desktop_enabled());
+ }
+}
+
+#endif // defined(OS_CHROMEOS)
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/system_info/OWNERS b/chromium/extensions/browser/api/system_info/OWNERS
new file mode 100644
index 00000000000..20685fc6fc3
--- /dev/null
+++ b/chromium/extensions/browser/api/system_info/OWNERS
@@ -0,0 +1 @@
+hongbo.min@intel.com
diff --git a/chromium/extensions/browser/api/system_info/system_info_api.cc b/chromium/extensions/browser/api/system_info/system_info_api.cc
new file mode 100644
index 00000000000..42679d66afd
--- /dev/null
+++ b/chromium/extensions/browser/api/system_info/system_info_api.cc
@@ -0,0 +1,266 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/system_info/system_info_api.h"
+
+#include <stdint.h>
+
+#include <set>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/singleton.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "components/storage_monitor/removable_storage_observer.h"
+#include "components/storage_monitor/storage_info.h"
+#include "components/storage_monitor/storage_monitor.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/api/system_storage/storage_info_provider.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/common/api/system_display.h"
+#include "extensions/common/api/system_storage.h"
+#include "ui/gfx/display_observer.h"
+#include "ui/gfx/screen.h"
+
+namespace extensions {
+
+using api::system_storage::StorageUnitInfo;
+using content::BrowserThread;
+using storage_monitor::StorageMonitor;
+
+namespace system_display = api::system_display;
+namespace system_storage = api::system_storage;
+
+namespace {
+
+bool IsDisplayChangedEvent(const std::string& event_name) {
+ return event_name == system_display::OnDisplayChanged::kEventName;
+}
+
+bool IsSystemStorageEvent(const std::string& event_name) {
+ return (event_name == system_storage::OnAttached::kEventName ||
+ event_name == system_storage::OnDetached::kEventName);
+}
+
+// Event router for systemInfo API. It is a singleton instance shared by
+// multiple profiles.
+class SystemInfoEventRouter : public gfx::DisplayObserver,
+ public storage_monitor::RemovableStorageObserver {
+ public:
+ static SystemInfoEventRouter* GetInstance();
+
+ SystemInfoEventRouter();
+ ~SystemInfoEventRouter() override;
+
+ // Add/remove event listener for the |event_name| event.
+ void AddEventListener(const std::string& event_name);
+ void RemoveEventListener(const std::string& event_name);
+
+ private:
+ // gfx::DisplayObserver:
+ void OnDisplayAdded(const gfx::Display& new_display) override;
+ void OnDisplayRemoved(const gfx::Display& old_display) override;
+ void OnDisplayMetricsChanged(const gfx::Display& display,
+ uint32_t metrics) override;
+
+ // RemovableStorageObserver implementation.
+ void OnRemovableStorageAttached(
+ const storage_monitor::StorageInfo& info) override;
+ void OnRemovableStorageDetached(
+ const storage_monitor::StorageInfo& info) override;
+
+ // Called from any thread to dispatch the systemInfo event to all extension
+ // processes cross multiple profiles.
+ void DispatchEvent(events::HistogramValue histogram_value,
+ const std::string& event_name,
+ scoped_ptr<base::ListValue> args);
+
+ // Called to dispatch the systemInfo.display.onDisplayChanged event.
+ void OnDisplayChanged();
+
+ // Used to record the event names being watched.
+ std::multiset<std::string> watching_event_set_;
+
+ bool has_storage_monitor_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(SystemInfoEventRouter);
+};
+
+static base::LazyInstance<SystemInfoEventRouter>::Leaky
+ g_system_info_event_router = LAZY_INSTANCE_INITIALIZER;
+
+// static
+SystemInfoEventRouter* SystemInfoEventRouter::GetInstance() {
+ return g_system_info_event_router.Pointer();
+}
+
+SystemInfoEventRouter::SystemInfoEventRouter()
+ : has_storage_monitor_observer_(false) {
+}
+
+SystemInfoEventRouter::~SystemInfoEventRouter() {
+ if (has_storage_monitor_observer_) {
+ StorageMonitor* storage_monitor = StorageMonitor::GetInstance();
+ if (storage_monitor)
+ storage_monitor->RemoveObserver(this);
+ }
+}
+
+void SystemInfoEventRouter::AddEventListener(const std::string& event_name) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ watching_event_set_.insert(event_name);
+ if (watching_event_set_.count(event_name) > 1)
+ return;
+
+ if (IsDisplayChangedEvent(event_name)) {
+ gfx::Screen* screen = gfx::Screen::GetScreen();
+ if (screen)
+ screen->AddObserver(this);
+ }
+
+ if (IsSystemStorageEvent(event_name)) {
+ if (!has_storage_monitor_observer_) {
+ has_storage_monitor_observer_ = true;
+ DCHECK(StorageMonitor::GetInstance()->IsInitialized());
+ StorageMonitor::GetInstance()->AddObserver(this);
+ }
+ }
+}
+
+void SystemInfoEventRouter::RemoveEventListener(const std::string& event_name) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ std::multiset<std::string>::iterator it =
+ watching_event_set_.find(event_name);
+ if (it != watching_event_set_.end()) {
+ watching_event_set_.erase(it);
+ if (watching_event_set_.count(event_name) > 0)
+ return;
+ }
+
+ if (IsDisplayChangedEvent(event_name)) {
+ gfx::Screen* screen = gfx::Screen::GetScreen();
+ if (screen)
+ screen->RemoveObserver(this);
+ }
+
+ if (IsSystemStorageEvent(event_name)) {
+ const std::string& other_event_name =
+ (event_name == system_storage::OnDetached::kEventName)
+ ? system_storage::OnAttached::kEventName
+ : system_storage::OnDetached::kEventName;
+ if (watching_event_set_.count(other_event_name) == 0) {
+ StorageMonitor::GetInstance()->RemoveObserver(this);
+ has_storage_monitor_observer_ = false;
+ }
+ }
+}
+
+void SystemInfoEventRouter::OnRemovableStorageAttached(
+ const storage_monitor::StorageInfo& info) {
+ StorageUnitInfo unit;
+ systeminfo::BuildStorageUnitInfo(info, &unit);
+ scoped_ptr<base::ListValue> args(new base::ListValue);
+ args->Append(unit.ToValue().release());
+ DispatchEvent(events::SYSTEM_STORAGE_ON_ATTACHED,
+ system_storage::OnAttached::kEventName, std::move(args));
+}
+
+void SystemInfoEventRouter::OnRemovableStorageDetached(
+ const storage_monitor::StorageInfo& info) {
+ scoped_ptr<base::ListValue> args(new base::ListValue);
+ std::string transient_id =
+ StorageMonitor::GetInstance()->GetTransientIdForDeviceId(
+ info.device_id());
+ args->AppendString(transient_id);
+
+ DispatchEvent(events::SYSTEM_STORAGE_ON_DETACHED,
+ system_storage::OnDetached::kEventName, std::move(args));
+}
+
+void SystemInfoEventRouter::OnDisplayAdded(const gfx::Display& new_display) {
+ OnDisplayChanged();
+}
+
+void SystemInfoEventRouter::OnDisplayRemoved(const gfx::Display& old_display) {
+ OnDisplayChanged();
+}
+
+void SystemInfoEventRouter::OnDisplayMetricsChanged(const gfx::Display& display,
+ uint32_t metrics) {
+ OnDisplayChanged();
+}
+
+void SystemInfoEventRouter::OnDisplayChanged() {
+ scoped_ptr<base::ListValue> args(new base::ListValue());
+ DispatchEvent(events::SYSTEM_DISPLAY_ON_DISPLAY_CHANGED,
+ system_display::OnDisplayChanged::kEventName, std::move(args));
+}
+
+void SystemInfoEventRouter::DispatchEvent(
+ events::HistogramValue histogram_value,
+ const std::string& event_name,
+ scoped_ptr<base::ListValue> args) {
+ ExtensionsBrowserClient::Get()->BroadcastEventToRenderers(
+ histogram_value, event_name, std::move(args));
+}
+
+void AddEventListener(const std::string& event_name) {
+ SystemInfoEventRouter::GetInstance()->AddEventListener(event_name);
+}
+
+void RemoveEventListener(const std::string& event_name) {
+ SystemInfoEventRouter::GetInstance()->RemoveEventListener(event_name);
+}
+
+} // namespace
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<SystemInfoAPI> >
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<SystemInfoAPI>*
+SystemInfoAPI::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+SystemInfoAPI::SystemInfoAPI(content::BrowserContext* context)
+ : browser_context_(context) {
+ EventRouter* router = EventRouter::Get(browser_context_);
+ router->RegisterObserver(this, system_storage::OnAttached::kEventName);
+ router->RegisterObserver(this, system_storage::OnDetached::kEventName);
+ router->RegisterObserver(this, system_display::OnDisplayChanged::kEventName);
+}
+
+SystemInfoAPI::~SystemInfoAPI() {
+}
+
+void SystemInfoAPI::Shutdown() {
+ EventRouter::Get(browser_context_)->UnregisterObserver(this);
+}
+
+void SystemInfoAPI::OnListenerAdded(const EventListenerInfo& details) {
+ if (IsSystemStorageEvent(details.event_name)) {
+ StorageMonitor::GetInstance()->EnsureInitialized(
+ base::Bind(&AddEventListener, details.event_name));
+ } else {
+ AddEventListener(details.event_name);
+ }
+}
+
+void SystemInfoAPI::OnListenerRemoved(const EventListenerInfo& details) {
+ if (IsSystemStorageEvent(details.event_name)) {
+ StorageMonitor::GetInstance()->EnsureInitialized(
+ base::Bind(&RemoveEventListener, details.event_name));
+ } else {
+ RemoveEventListener(details.event_name);
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/system_info/system_info_api.h b/chromium/extensions/browser/api/system_info/system_info_api.h
new file mode 100644
index 00000000000..a89022751fc
--- /dev/null
+++ b/chromium/extensions/browser/api/system_info/system_info_api.h
@@ -0,0 +1,46 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SYSTEM_INFO_SYSTEM_INFO_API_H_
+#define EXTENSIONS_BROWSER_API_SYSTEM_INFO_SYSTEM_INFO_API_H_
+
+#include "base/macros.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/event_router.h"
+
+namespace extensions {
+
+// A Profile-scoped object which is registered as an observer of EventRouter
+// to observe the systemInfo event listener arrival/removal.
+class SystemInfoAPI : public BrowserContextKeyedAPI,
+ public EventRouter::Observer {
+ public:
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<SystemInfoAPI>* GetFactoryInstance();
+
+ explicit SystemInfoAPI(content::BrowserContext* context);
+ ~SystemInfoAPI() override;
+
+ // KeyedService implementation.
+ void Shutdown() override;
+
+ // EventRouter::Observer implementation.
+ void OnListenerAdded(const EventListenerInfo& details) override;
+ void OnListenerRemoved(const EventListenerInfo& details) override;
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<SystemInfoAPI>;
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "SystemInfoAPI"; }
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ content::BrowserContext* browser_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(SystemInfoAPI);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SYSTEM_INFO_SYSTEM_INFO_API_H_
diff --git a/chromium/extensions/browser/api/system_info/system_info_provider.cc b/chromium/extensions/browser/api/system_info/system_info_provider.cc
new file mode 100644
index 00000000000..3c2cf5fd38c
--- /dev/null
+++ b/chromium/extensions/browser/api/system_info/system_info_provider.cc
@@ -0,0 +1,69 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/system_info/system_info_provider.h"
+
+#include "base/bind.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace extensions {
+
+SystemInfoProvider::SystemInfoProvider() : is_waiting_for_completion_(false) {
+ base::SequencedWorkerPool* pool = content::BrowserThread::GetBlockingPool();
+ worker_pool_ = pool->GetSequencedTaskRunnerWithShutdownBehavior(
+ pool->GetSequenceToken(),
+ base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
+}
+
+SystemInfoProvider::~SystemInfoProvider() {
+}
+
+void SystemInfoProvider::PrepareQueryOnUIThread() {
+}
+
+void SystemInfoProvider::InitializeProvider(
+ const base::Closure& do_query_info_callback) {
+ do_query_info_callback.Run();
+}
+
+void SystemInfoProvider::StartQueryInfo(
+ const QueryInfoCompletionCallback& callback) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK(!callback.is_null());
+
+ callbacks_.push(callback);
+
+ if (is_waiting_for_completion_)
+ return;
+
+ is_waiting_for_completion_ = true;
+
+ InitializeProvider(
+ base::Bind(&SystemInfoProvider::StartQueryInfoPostInitialization, this));
+}
+
+void SystemInfoProvider::OnQueryCompleted(bool success) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ while (!callbacks_.empty()) {
+ QueryInfoCompletionCallback callback = callbacks_.front();
+ callback.Run(success);
+ callbacks_.pop();
+ }
+
+ is_waiting_for_completion_ = false;
+}
+
+void SystemInfoProvider::StartQueryInfoPostInitialization() {
+ PrepareQueryOnUIThread();
+ // Post the custom query info task to blocking pool for information querying
+ // and reply with OnQueryCompleted.
+ base::PostTaskAndReplyWithResult(
+ worker_pool_.get(),
+ FROM_HERE,
+ base::Bind(&SystemInfoProvider::QueryInfo, this),
+ base::Bind(&SystemInfoProvider::OnQueryCompleted, this));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/system_info/system_info_provider.h b/chromium/extensions/browser/api/system_info/system_info_provider.h
new file mode 100644
index 00000000000..242692835b4
--- /dev/null
+++ b/chromium/extensions/browser/api/system_info/system_info_provider.h
@@ -0,0 +1,99 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SYSTEM_INFO_SYSTEM_INFO_PROVIDER_H_
+#define EXTENSIONS_BROWSER_API_SYSTEM_INFO_SYSTEM_INFO_PROVIDER_H_
+
+#include <queue>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/sequenced_worker_pool.h"
+
+namespace extensions {
+
+// An abstract base class for all kinds of system information providers. Each
+// kind of SystemInfoProvider is a single shared instance. It is created if
+// needed, and destroyed at exit time. This is done via LazyInstance and
+// scoped_refptr.
+//
+// The SystemInfoProvider is designed to query system information on the worker
+// pool. It also maintains a queue of callbacks on the UI thread which are
+// waiting for the completion of querying operation. Once the query operation
+// is completed, all pending callbacks in the queue get called on the UI
+// thread. In this way, it avoids frequent querying operation in case of lots
+// of query requests, e.g. calling systemInfo.cpu.get repeatedly in an
+// extension process.
+//
+// Each kind of SystemInfoProvider should satisfy an API query in a subclass on
+// the blocking pool.
+class SystemInfoProvider
+ : public base::RefCountedThreadSafe<SystemInfoProvider> {
+ public:
+ // Callback type for completing to get information. The argument indicates
+ // whether its contents are valid, for example, no error occurs in querying
+ // the information.
+ typedef base::Callback<void(bool)> QueryInfoCompletionCallback;
+ typedef std::queue<QueryInfoCompletionCallback> CallbackQueue;
+
+ SystemInfoProvider();
+
+ // Override to do any prepare work on UI thread before |QueryInfo()| gets
+ // called.
+ virtual void PrepareQueryOnUIThread();
+
+ // The parameter |do_query_info_callback| is query info task which is posted
+ // to SystemInfoProvider sequenced worker pool.
+ //
+ // You can do any initial things of *InfoProvider before start to query info.
+ // While overriding this method, |do_query_info_callback| *must* be called
+ // directly or indirectly.
+ //
+ // Sample usage please refer to StorageInfoProvider.
+ virtual void InitializeProvider(const base::Closure& do_query_info_callback);
+
+ // Start to query the system information. Should be called on UI thread.
+ // The |callback| will get called once the query is completed.
+ //
+ // If the parameter |callback| itself calls StartQueryInfo(callback2),
+ // callback2 will be called immediately rather than triggering another call to
+ // the system.
+ void StartQueryInfo(const QueryInfoCompletionCallback& callback);
+
+ protected:
+ virtual ~SystemInfoProvider();
+
+ private:
+ friend class base::RefCountedThreadSafe<SystemInfoProvider>;
+
+ // Interface to query the system information synchronously.
+ // Return true if no error occurs.
+ // Should be called in the blocking pool.
+ virtual bool QueryInfo() = 0;
+
+ // Called on UI thread. The |success| parameter means whether it succeeds
+ // to get the information.
+ void OnQueryCompleted(bool success);
+
+ void StartQueryInfoPostInitialization();
+
+ // The queue of callbacks waiting for the info querying completion. It is
+ // maintained on the UI thread.
+ CallbackQueue callbacks_;
+
+ // Indicates if it is waiting for the querying completion.
+ bool is_waiting_for_completion_;
+
+ // Sequenced worker pool to make the operation of querying information get
+ // executed in order.
+ scoped_refptr<base::SequencedTaskRunner> worker_pool_;
+
+ DISALLOW_COPY_AND_ASSIGN(SystemInfoProvider);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SYSTEM_INFO_SYSTEM_INFO_PROVIDER_H_
diff --git a/chromium/extensions/browser/api/system_memory/OWNERS b/chromium/extensions/browser/api/system_memory/OWNERS
new file mode 100644
index 00000000000..20685fc6fc3
--- /dev/null
+++ b/chromium/extensions/browser/api/system_memory/OWNERS
@@ -0,0 +1 @@
+hongbo.min@intel.com
diff --git a/chromium/extensions/browser/api/system_memory/memory_info_provider.cc b/chromium/extensions/browser/api/system_memory/memory_info_provider.cc
new file mode 100644
index 00000000000..7d97dde3907
--- /dev/null
+++ b/chromium/extensions/browser/api/system_memory/memory_info_provider.cc
@@ -0,0 +1,43 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/system_memory/memory_info_provider.h"
+
+#include "base/sys_info.h"
+
+namespace extensions {
+
+using api::system_memory::MemoryInfo;
+
+// Static member intialization.
+base::LazyInstance<scoped_refptr<MemoryInfoProvider> >
+ MemoryInfoProvider::provider_ = LAZY_INSTANCE_INITIALIZER;
+
+MemoryInfoProvider::MemoryInfoProvider() {
+}
+
+MemoryInfoProvider::~MemoryInfoProvider() {
+}
+
+void MemoryInfoProvider::InitializeForTesting(
+ scoped_refptr<MemoryInfoProvider> provider) {
+ DCHECK(provider.get() != NULL);
+ provider_.Get() = provider;
+}
+
+bool MemoryInfoProvider::QueryInfo() {
+ info_.capacity = static_cast<double>(base::SysInfo::AmountOfPhysicalMemory());
+ info_.available_capacity =
+ static_cast<double>(base::SysInfo::AmountOfAvailablePhysicalMemory());
+ return true;
+}
+
+// static
+MemoryInfoProvider* MemoryInfoProvider::Get() {
+ if (provider_.Get().get() == NULL)
+ provider_.Get() = new MemoryInfoProvider();
+ return provider_.Get().get();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/system_memory/memory_info_provider.h b/chromium/extensions/browser/api/system_memory/memory_info_provider.h
new file mode 100644
index 00000000000..a461b84c358
--- /dev/null
+++ b/chromium/extensions/browser/api/system_memory/memory_info_provider.h
@@ -0,0 +1,48 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SYSTEM_MEMORY_MEMORY_INFO_PROVIDER_H_
+#define EXTENSIONS_BROWSER_API_SYSTEM_MEMORY_MEMORY_INFO_PROVIDER_H_
+
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "extensions/browser/api/system_info/system_info_provider.h"
+#include "extensions/common/api/system_memory.h"
+
+namespace extensions {
+
+class MemoryInfoProvider : public SystemInfoProvider {
+ public:
+ static MemoryInfoProvider* Get();
+
+ const api::system_memory::MemoryInfo& memory_info() const { return info_; }
+
+ static void InitializeForTesting(scoped_refptr<MemoryInfoProvider> provider);
+
+ private:
+ friend class MockMemoryInfoProviderImpl;
+
+ MemoryInfoProvider();
+ ~MemoryInfoProvider() override;
+
+ // Overriden from SystemInfoProvider.
+ bool QueryInfo() override;
+
+ // The last information filled up by QueryInfo and is accessed on multiple
+ // threads, but the whole class is being guarded by SystemInfoProvider base
+ // class.
+ //
+ // |info_| is accessed on the UI thread while |is_waiting_for_completion_| is
+ // false and on the sequenced worker pool while |is_waiting_for_completion_|
+ // is true.
+ api::system_memory::MemoryInfo info_;
+
+ static base::LazyInstance<scoped_refptr<MemoryInfoProvider> > provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(MemoryInfoProvider);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SYSTEM_MEMORY_MEMORY_INFO_PROVIDER_H_
diff --git a/chromium/extensions/browser/api/system_memory/system_memory_api.cc b/chromium/extensions/browser/api/system_memory/system_memory_api.cc
new file mode 100644
index 00000000000..82c081a1d0b
--- /dev/null
+++ b/chromium/extensions/browser/api/system_memory/system_memory_api.cc
@@ -0,0 +1,33 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/system_memory/system_memory_api.h"
+
+#include "extensions/browser/api/system_memory/memory_info_provider.h"
+
+namespace extensions {
+
+using api::system_memory::MemoryInfo;
+
+SystemMemoryGetInfoFunction::SystemMemoryGetInfoFunction() {
+}
+
+SystemMemoryGetInfoFunction::~SystemMemoryGetInfoFunction() {
+}
+
+bool SystemMemoryGetInfoFunction::RunAsync() {
+ MemoryInfoProvider::Get()->StartQueryInfo(
+ base::Bind(&SystemMemoryGetInfoFunction::OnGetMemoryInfoCompleted, this));
+ return true;
+}
+
+void SystemMemoryGetInfoFunction::OnGetMemoryInfoCompleted(bool success) {
+ if (success)
+ SetResult(MemoryInfoProvider::Get()->memory_info().ToValue().release());
+ else
+ SetError("Error occurred when querying memory information.");
+ SendResponse(success);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/system_memory/system_memory_api.h b/chromium/extensions/browser/api/system_memory/system_memory_api.h
new file mode 100644
index 00000000000..b592d8056ed
--- /dev/null
+++ b/chromium/extensions/browser/api/system_memory/system_memory_api.h
@@ -0,0 +1,26 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SYSTEM_MEMORY_SYSTEM_MEMORY_API_H_
+#define EXTENSIONS_BROWSER_API_SYSTEM_MEMORY_SYSTEM_MEMORY_API_H_
+
+#include "extensions/browser/extension_function.h"
+#include "extensions/common/api/system_memory.h"
+
+namespace extensions {
+
+class SystemMemoryGetInfoFunction : public AsyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("system.memory.getInfo", SYSTEM_MEMORY_GETINFO)
+ SystemMemoryGetInfoFunction();
+
+ private:
+ ~SystemMemoryGetInfoFunction() override;
+ bool RunAsync() override;
+ void OnGetMemoryInfoCompleted(bool success);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SYSTEM_MEMORY_SYSTEM_MEMORY_API_H_
diff --git a/chromium/extensions/browser/api/system_memory/system_memory_apitest.cc b/chromium/extensions/browser/api/system_memory/system_memory_apitest.cc
new file mode 100644
index 00000000000..64be93c4f56
--- /dev/null
+++ b/chromium/extensions/browser/api/system_memory/system_memory_apitest.cc
@@ -0,0 +1,48 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/message_loop/message_loop.h"
+#include "extensions/browser/api/system_memory/memory_info_provider.h"
+#include "extensions/shell/test/shell_apitest.h"
+
+namespace extensions {
+
+using api::system_memory::MemoryInfo;
+
+class MockMemoryInfoProviderImpl : public MemoryInfoProvider {
+ public:
+ MockMemoryInfoProviderImpl() {}
+
+ bool QueryInfo() override {
+ info_.capacity = 4096;
+ info_.available_capacity = 1024;
+ return true;
+ }
+
+ private:
+ ~MockMemoryInfoProviderImpl() override {}
+};
+
+class SystemMemoryApiTest : public ShellApiTest {
+ public:
+ SystemMemoryApiTest() {}
+ ~SystemMemoryApiTest() override {}
+
+ void SetUpInProcessBrowserTestFixture() override {
+ ShellApiTest::SetUpInProcessBrowserTestFixture();
+ message_loop_.reset(new base::MessageLoopForUI);
+ }
+
+ private:
+ scoped_ptr<base::MessageLoop> message_loop_;
+};
+
+IN_PROC_BROWSER_TEST_F(SystemMemoryApiTest, Memory) {
+ scoped_refptr<MemoryInfoProvider> provider = new MockMemoryInfoProviderImpl();
+ // The provider is owned by the single MemoryInfoProvider instance.
+ MemoryInfoProvider::InitializeForTesting(provider);
+ ASSERT_TRUE(RunAppTest("system/memory")) << message_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/system_network/OWNERS b/chromium/extensions/browser/api/system_network/OWNERS
new file mode 100644
index 00000000000..3e30c826edc
--- /dev/null
+++ b/chromium/extensions/browser/api/system_network/OWNERS
@@ -0,0 +1 @@
+rpaquay@chromium.org
diff --git a/chromium/extensions/browser/api/system_network/system_network_api.cc b/chromium/extensions/browser/api/system_network/system_network_api.cc
new file mode 100644
index 00000000000..0b196bd617e
--- /dev/null
+++ b/chromium/extensions/browser/api/system_network/system_network_api.cc
@@ -0,0 +1,81 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/system_network/system_network_api.h"
+
+#include "net/base/ip_address_number.h"
+
+namespace {
+const char kNetworkListError[] = "Network lookup failed or unsupported";
+} // namespace
+
+namespace extensions {
+namespace api {
+
+SystemNetworkGetNetworkInterfacesFunction::
+ SystemNetworkGetNetworkInterfacesFunction() {
+}
+
+SystemNetworkGetNetworkInterfacesFunction::
+ ~SystemNetworkGetNetworkInterfacesFunction() {
+}
+
+bool SystemNetworkGetNetworkInterfacesFunction::RunAsync() {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(
+ &SystemNetworkGetNetworkInterfacesFunction::GetListOnFileThread,
+ this));
+ return true;
+}
+
+void SystemNetworkGetNetworkInterfacesFunction::GetListOnFileThread() {
+ net::NetworkInterfaceList interface_list;
+ if (net::GetNetworkList(&interface_list,
+ net::INCLUDE_HOST_SCOPE_VIRTUAL_INTERFACES)) {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(
+ &SystemNetworkGetNetworkInterfacesFunction::SendResponseOnUIThread,
+ this,
+ interface_list));
+ return;
+ }
+
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&SystemNetworkGetNetworkInterfacesFunction::HandleGetListError,
+ this));
+}
+
+void SystemNetworkGetNetworkInterfacesFunction::HandleGetListError() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ error_ = kNetworkListError;
+ SendResponse(false);
+}
+
+void SystemNetworkGetNetworkInterfacesFunction::SendResponseOnUIThread(
+ const net::NetworkInterfaceList& interface_list) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ std::vector<api::system_network::NetworkInterface> create_arg;
+ create_arg.reserve(interface_list.size());
+ for (const net::NetworkInterface interface : interface_list) {
+ api::system_network::NetworkInterface info;
+ info.name = interface.name;
+ info.address = interface.address.ToString();
+ info.prefix_length = interface.prefix_length;
+ create_arg.push_back(std::move(info));
+ }
+
+ results_ =
+ api::system_network::GetNetworkInterfaces::Results::Create(create_arg);
+ SendResponse(true);
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/system_network/system_network_api.h b/chromium/extensions/browser/api/system_network/system_network_api.h
new file mode 100644
index 00000000000..29d20bdeb2e
--- /dev/null
+++ b/chromium/extensions/browser/api/system_network/system_network_api.h
@@ -0,0 +1,38 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SYSTEM_NETWORK_SYSTEM_NETWORK_API_H_
+#define EXTENSIONS_BROWSER_API_SYSTEM_NETWORK_SYSTEM_NETWORK_API_H_
+
+#include "extensions/browser/extension_function.h"
+#include "extensions/common/api/system_network.h"
+#include "net/base/network_interfaces.h"
+
+namespace extensions {
+namespace api {
+
+class SystemNetworkGetNetworkInterfacesFunction
+ : public AsyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("system.network.getNetworkInterfaces",
+ SYSTEM_NETWORK_GETNETWORKINTERFACES)
+
+ SystemNetworkGetNetworkInterfacesFunction();
+
+ protected:
+ ~SystemNetworkGetNetworkInterfacesFunction() override;
+
+ // AsyncApiFunction:
+ bool RunAsync() override;
+
+ private:
+ void GetListOnFileThread();
+ void HandleGetListError();
+ void SendResponseOnUIThread(const net::NetworkInterfaceList& interface_list);
+};
+
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SYSTEM_NETWORK_SYSTEM_NETWORK_API_H_
diff --git a/chromium/extensions/browser/api/system_network/system_network_apitest.cc b/chromium/extensions/browser/api/system_network/system_network_apitest.cc
new file mode 100644
index 00000000000..f7c1964345b
--- /dev/null
+++ b/chromium/extensions/browser/api/system_network/system_network_apitest.cc
@@ -0,0 +1,61 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/ref_counted.h"
+#include "base/strings/stringprintf.h"
+#include "extensions/browser/api/system_network/system_network_api.h"
+#include "extensions/browser/api_test_utils.h"
+#include "extensions/common/test_util.h"
+#include "extensions/shell/test/shell_apitest.h"
+#include "extensions/test/extension_test_message_listener.h"
+
+using extensions::Extension;
+using extensions::api_test_utils::RunFunctionAndReturnSingleResult;
+using extensions::api::SystemNetworkGetNetworkInterfacesFunction;
+using extensions::api::system_network::NetworkInterface;
+
+namespace {
+
+class SystemNetworkApiTest : public extensions::ShellApiTest {};
+
+} // namespace
+
+IN_PROC_BROWSER_TEST_F(SystemNetworkApiTest, SystemNetworkExtension) {
+ ASSERT_TRUE(RunAppTest("system/network")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(SystemNetworkApiTest, GetNetworkInterfaces) {
+ scoped_refptr<SystemNetworkGetNetworkInterfacesFunction> socket_function(
+ new SystemNetworkGetNetworkInterfacesFunction());
+ scoped_refptr<Extension> empty_extension(
+ extensions::test_util::CreateEmptyExtension());
+
+ socket_function->set_extension(empty_extension.get());
+ socket_function->set_has_callback(true);
+
+ scoped_ptr<base::Value> result(RunFunctionAndReturnSingleResult(
+ socket_function.get(), "[]", browser_context()));
+ ASSERT_EQ(base::Value::TYPE_LIST, result->GetType());
+
+ // All we can confirm is that we have at least one address, but not what it
+ // is.
+ base::ListValue* value = static_cast<base::ListValue*>(result.get());
+ ASSERT_TRUE(value->GetSize() > 0);
+
+ for (base::ListValue::const_iterator it = value->begin(); it != value->end();
+ ++it) {
+ base::Value* network_interface_value = *it;
+
+ NetworkInterface network_interface;
+ ASSERT_TRUE(NetworkInterface::Populate(*network_interface_value,
+ &network_interface));
+
+ LOG(INFO) << "Network interface: address=" << network_interface.address
+ << ", name=" << network_interface.name
+ << ", prefix length=" << network_interface.prefix_length;
+ ASSERT_NE(std::string(), network_interface.address);
+ ASSERT_NE(std::string(), network_interface.name);
+ ASSERT_LE(0, network_interface.prefix_length);
+ }
+}
diff --git a/chromium/extensions/browser/api/system_storage/OWNERS b/chromium/extensions/browser/api/system_storage/OWNERS
new file mode 100644
index 00000000000..71174fe39c7
--- /dev/null
+++ b/chromium/extensions/browser/api/system_storage/OWNERS
@@ -0,0 +1,3 @@
+hongbo.min@intel.com
+ningxin.hu@intel.com
+joshua.lock@intel.com
diff --git a/chromium/extensions/browser/api/system_storage/storage_api_test_util.cc b/chromium/extensions/browser/api/system_storage/storage_api_test_util.cc
new file mode 100644
index 00000000000..f06c3570d4b
--- /dev/null
+++ b/chromium/extensions/browser/api/system_storage/storage_api_test_util.cc
@@ -0,0 +1,28 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/system_storage/storage_api_test_util.h"
+
+#include "base/strings/utf_string_conversions.h"
+
+namespace extensions {
+namespace test {
+
+const struct TestStorageUnitInfo kRemovableStorageData = {"dcim:device:001",
+ "/media/usb1",
+ 4098,
+ 1000};
+
+storage_monitor::StorageInfo BuildStorageInfoFromTestStorageUnitInfo(
+ const TestStorageUnitInfo& unit) {
+ return storage_monitor::StorageInfo(
+ unit.device_id, base::FilePath::StringType(), /* no location */
+ base::UTF8ToUTF16(unit.name), /* storage label */
+ base::string16(), /* no storage vendor */
+ base::string16(), /* no storage model */
+ unit.capacity);
+}
+
+} // namespace test
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/system_storage/storage_api_test_util.h b/chromium/extensions/browser/api/system_storage/storage_api_test_util.h
new file mode 100644
index 00000000000..a035906416c
--- /dev/null
+++ b/chromium/extensions/browser/api/system_storage/storage_api_test_util.h
@@ -0,0 +1,33 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SYSTEM_STORAGE_STORAGE_API_TEST_UTIL_H_
+#define EXTENSIONS_BROWSER_API_SYSTEM_STORAGE_STORAGE_API_TEST_UTIL_H_
+
+#include <vector>
+
+#include "components/storage_monitor/storage_info.h"
+#include "extensions/browser/api/system_storage/storage_info_provider.h"
+
+namespace extensions {
+namespace test {
+
+struct TestStorageUnitInfo {
+ const char* device_id;
+ const char* name;
+ // Total amount of the storage device space, in bytes.
+ double capacity;
+ // The available amount of the storage space, in bytes.
+ double available_capacity;
+};
+
+extern const struct TestStorageUnitInfo kRemovableStorageData;
+
+storage_monitor::StorageInfo BuildStorageInfoFromTestStorageUnitInfo(
+ const TestStorageUnitInfo& unit);
+
+} // namespace test
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SYSTEM_STORAGE_STORAGE_API_TEST_UTIL_H_
diff --git a/chromium/extensions/browser/api/system_storage/storage_info_provider.cc b/chromium/extensions/browser/api/system_storage/storage_info_provider.cc
new file mode 100644
index 00000000000..7943b892f31
--- /dev/null
+++ b/chromium/extensions/browser/api/system_storage/storage_info_provider.cc
@@ -0,0 +1,119 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/system_storage/storage_info_provider.h"
+
+#include "base/stl_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/sys_info.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "components/storage_monitor/storage_info.h"
+#include "components/storage_monitor/storage_monitor.h"
+#include "content/public/browser/browser_thread.h"
+
+using storage_monitor::StorageInfo;
+using storage_monitor::StorageMonitor;
+
+namespace extensions {
+
+using content::BrowserThread;
+using api::system_storage::StorageUnitInfo;
+using api::system_storage::STORAGE_UNIT_TYPE_FIXED;
+using api::system_storage::STORAGE_UNIT_TYPE_REMOVABLE;
+
+namespace systeminfo {
+
+void BuildStorageUnitInfo(const StorageInfo& info, StorageUnitInfo* unit) {
+ unit->id = StorageMonitor::GetInstance()->GetTransientIdForDeviceId(
+ info.device_id());
+ unit->name = base::UTF16ToUTF8(info.GetDisplayName(false));
+ // TODO(hmin): Might need to take MTP device into consideration.
+ unit->type = StorageInfo::IsRemovableDevice(info.device_id())
+ ? STORAGE_UNIT_TYPE_REMOVABLE
+ : STORAGE_UNIT_TYPE_FIXED;
+ unit->capacity = static_cast<double>(info.total_size_in_bytes());
+}
+
+} // namespace systeminfo
+
+// Static member intialization.
+base::LazyInstance<scoped_refptr<StorageInfoProvider> >
+ StorageInfoProvider::provider_ = LAZY_INSTANCE_INITIALIZER;
+
+StorageInfoProvider::StorageInfoProvider() {
+}
+
+StorageInfoProvider::~StorageInfoProvider() {
+}
+
+void StorageInfoProvider::InitializeForTesting(
+ scoped_refptr<StorageInfoProvider> provider) {
+ DCHECK(provider.get() != NULL);
+ provider_.Get() = provider;
+}
+
+void StorageInfoProvider::PrepareQueryOnUIThread() {
+ // Get all available storage devices before invoking |QueryInfo()|.
+ GetAllStoragesIntoInfoList();
+}
+
+void StorageInfoProvider::InitializeProvider(
+ const base::Closure& do_query_info_callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ // Register the |do_query_info_callback| callback to StorageMonitor.
+ // See the comments of StorageMonitor::EnsureInitialized about when the
+ // callback gets run.
+ StorageMonitor::GetInstance()->EnsureInitialized(do_query_info_callback);
+}
+
+bool StorageInfoProvider::QueryInfo() {
+ DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
+ // No info to query since we get all available storage devices' info in
+ // |PrepareQueryOnUIThread()|.
+ return true;
+}
+
+void StorageInfoProvider::GetAllStoragesIntoInfoList() {
+ info_.clear();
+ std::vector<StorageInfo> storage_list =
+ StorageMonitor::GetInstance()->GetAllAvailableStorages();
+
+ for (std::vector<StorageInfo>::const_iterator it = storage_list.begin();
+ it != storage_list.end();
+ ++it) {
+ StorageUnitInfo unit;
+ systeminfo::BuildStorageUnitInfo(*it, &unit);
+ info_.push_back(std::move(unit));
+ }
+}
+
+double StorageInfoProvider::GetStorageFreeSpaceFromTransientIdOnFileThread(
+ const std::string& transient_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ std::vector<StorageInfo> storage_list =
+ StorageMonitor::GetInstance()->GetAllAvailableStorages();
+
+ std::string device_id =
+ StorageMonitor::GetInstance()->GetDeviceIdForTransientId(transient_id);
+
+ // Lookup the matched storage info by |device_id|.
+ for (std::vector<StorageInfo>::const_iterator it = storage_list.begin();
+ it != storage_list.end();
+ ++it) {
+ if (device_id == it->device_id())
+ return static_cast<double>(
+ base::SysInfo::AmountOfFreeDiskSpace(base::FilePath(it->location())));
+ }
+
+ return -1;
+}
+
+// static
+StorageInfoProvider* StorageInfoProvider::Get() {
+ if (provider_.Get().get() == NULL)
+ provider_.Get() = new StorageInfoProvider();
+ return provider_.Get().get();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/system_storage/storage_info_provider.h b/chromium/extensions/browser/api/system_storage/storage_info_provider.h
new file mode 100644
index 00000000000..9a6a47bbb7f
--- /dev/null
+++ b/chromium/extensions/browser/api/system_storage/storage_info_provider.h
@@ -0,0 +1,83 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SYSTEM_STORAGE_STORAGE_INFO_PROVIDER_H_
+#define EXTENSIONS_BROWSER_API_SYSTEM_STORAGE_STORAGE_INFO_PROVIDER_H_
+
+#include <set>
+
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list_threadsafe.h"
+#include "extensions/browser/api/system_info/system_info_provider.h"
+#include "extensions/common/api/system_storage.h"
+
+namespace storage_monitor {
+class StorageInfo;
+}
+
+namespace extensions {
+
+namespace systeminfo {
+
+// Build StorageUnitInfo struct from StorageInfo instance. The |unit|
+// parameter is the output value.
+void BuildStorageUnitInfo(const storage_monitor::StorageInfo& info,
+ api::system_storage::StorageUnitInfo* unit);
+
+} // namespace systeminfo
+
+typedef std::vector<api::system_storage::StorageUnitInfo> StorageUnitInfoList;
+
+class StorageInfoProvider : public SystemInfoProvider {
+ public:
+ typedef base::Callback<void(const std::string&, double)>
+ GetStorageFreeSpaceCallback;
+
+ // Get the single shared instance of StorageInfoProvider.
+ static StorageInfoProvider* Get();
+
+ // SystemInfoProvider implementations
+ void PrepareQueryOnUIThread() override;
+ void InitializeProvider(const base::Closure& do_query_info_callback) override;
+
+ virtual double GetStorageFreeSpaceFromTransientIdOnFileThread(
+ const std::string& transient_id);
+
+ const StorageUnitInfoList& storage_unit_info_list() const { return info_; }
+
+ static void InitializeForTesting(scoped_refptr<StorageInfoProvider> provider);
+
+ protected:
+ StorageInfoProvider();
+
+ ~StorageInfoProvider() override;
+
+ // Put all available storages' information into |info_|.
+ void GetAllStoragesIntoInfoList();
+
+ // The last information filled up by QueryInfo and is accessed on multiple
+ // threads, but the whole class is being guarded by SystemInfoProvider base
+ // class.
+ //
+ // |info_| is accessed on the UI thread while |is_waiting_for_completion_| is
+ // false and on the sequenced worker pool while |is_waiting_for_completion_|
+ // is true.
+ StorageUnitInfoList info_;
+
+ private:
+ // SystemInfoProvider implementations.
+ // Override to query the available capacity of all known storage devices on
+ // the blocking pool, including fixed and removable devices.
+ bool QueryInfo() override;
+
+ static base::LazyInstance<scoped_refptr<StorageInfoProvider> > provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(StorageInfoProvider);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SYSTEM_STORAGE_STORAGE_INFO_PROVIDER_H_
diff --git a/chromium/extensions/browser/api/system_storage/system_storage_api.cc b/chromium/extensions/browser/api/system_storage/system_storage_api.cc
new file mode 100644
index 00000000000..de460183ea3
--- /dev/null
+++ b/chromium/extensions/browser/api/system_storage/system_storage_api.cc
@@ -0,0 +1,145 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/system_storage/system_storage_api.h"
+
+using storage_monitor::StorageMonitor;
+
+namespace extensions {
+
+using api::system_storage::StorageUnitInfo;
+namespace EjectDevice = api::system_storage::EjectDevice;
+namespace GetAvailableCapacity = api::system_storage::GetAvailableCapacity;
+
+SystemStorageGetInfoFunction::SystemStorageGetInfoFunction() {
+}
+
+SystemStorageGetInfoFunction::~SystemStorageGetInfoFunction() {
+}
+
+bool SystemStorageGetInfoFunction::RunAsync() {
+ StorageInfoProvider::Get()->StartQueryInfo(base::Bind(
+ &SystemStorageGetInfoFunction::OnGetStorageInfoCompleted, this));
+ return true;
+}
+
+void SystemStorageGetInfoFunction::OnGetStorageInfoCompleted(bool success) {
+ if (success) {
+ results_ = api::system_storage::GetInfo::Results::Create(
+ StorageInfoProvider::Get()->storage_unit_info_list());
+ } else {
+ SetError("Error occurred when querying storage information.");
+ }
+
+ SendResponse(success);
+}
+
+SystemStorageEjectDeviceFunction::~SystemStorageEjectDeviceFunction() {
+}
+
+bool SystemStorageEjectDeviceFunction::RunAsync() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ scoped_ptr<EjectDevice::Params> params(EjectDevice::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ StorageMonitor::GetInstance()->EnsureInitialized(
+ base::Bind(&SystemStorageEjectDeviceFunction::OnStorageMonitorInit,
+ this,
+ params->id));
+ return true;
+}
+
+void SystemStorageEjectDeviceFunction::OnStorageMonitorInit(
+ const std::string& transient_device_id) {
+ DCHECK(StorageMonitor::GetInstance()->IsInitialized());
+ StorageMonitor* monitor = StorageMonitor::GetInstance();
+ std::string device_id_str =
+ StorageMonitor::GetInstance()->GetDeviceIdForTransientId(
+ transient_device_id);
+
+ if (device_id_str.empty()) {
+ HandleResponse(StorageMonitor::EJECT_NO_SUCH_DEVICE);
+ return;
+ }
+
+ monitor->EjectDevice(
+ device_id_str,
+ base::Bind(&SystemStorageEjectDeviceFunction::HandleResponse, this));
+}
+
+void SystemStorageEjectDeviceFunction::HandleResponse(
+ StorageMonitor::EjectStatus status) {
+ api::system_storage::EjectDeviceResultCode result =
+ api::system_storage::EJECT_DEVICE_RESULT_CODE_FAILURE;
+ switch (status) {
+ case StorageMonitor::EJECT_OK:
+ result = api::system_storage::EJECT_DEVICE_RESULT_CODE_SUCCESS;
+ break;
+ case StorageMonitor::EJECT_IN_USE:
+ result = api::system_storage::EJECT_DEVICE_RESULT_CODE_IN_USE;
+ break;
+ case StorageMonitor::EJECT_NO_SUCH_DEVICE:
+ result = api::system_storage::EJECT_DEVICE_RESULT_CODE_NO_SUCH_DEVICE;
+ break;
+ case StorageMonitor::EJECT_FAILURE:
+ result = api::system_storage::EJECT_DEVICE_RESULT_CODE_FAILURE;
+ }
+
+ SetResult(new base::StringValue(api::system_storage::ToString(result)));
+ SendResponse(true);
+}
+
+SystemStorageGetAvailableCapacityFunction::
+ SystemStorageGetAvailableCapacityFunction() {
+}
+
+SystemStorageGetAvailableCapacityFunction::
+ ~SystemStorageGetAvailableCapacityFunction() {
+}
+
+bool SystemStorageGetAvailableCapacityFunction::RunAsync() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ scoped_ptr<GetAvailableCapacity::Params> params(
+ GetAvailableCapacity::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ StorageMonitor::GetInstance()->EnsureInitialized(base::Bind(
+ &SystemStorageGetAvailableCapacityFunction::OnStorageMonitorInit,
+ this,
+ params->id));
+ return true;
+}
+
+void SystemStorageGetAvailableCapacityFunction::OnStorageMonitorInit(
+ const std::string& transient_id) {
+ content::BrowserThread::PostTaskAndReplyWithResult(
+ content::BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(
+ &StorageInfoProvider::GetStorageFreeSpaceFromTransientIdOnFileThread,
+ StorageInfoProvider::Get(),
+ transient_id),
+ base::Bind(&SystemStorageGetAvailableCapacityFunction::OnQueryCompleted,
+ this,
+ transient_id));
+}
+
+void SystemStorageGetAvailableCapacityFunction::OnQueryCompleted(
+ const std::string& transient_id,
+ double available_capacity) {
+ bool success = available_capacity >= 0;
+ if (success) {
+ api::system_storage::StorageAvailableCapacityInfo result;
+ result.id = transient_id;
+ result.available_capacity = available_capacity;
+ SetResult(result.ToValue().release());
+ } else {
+ SetError("Error occurred when querying available capacity.");
+ }
+ SendResponse(success);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/system_storage/system_storage_api.h b/chromium/extensions/browser/api/system_storage/system_storage_api.h
new file mode 100644
index 00000000000..914e8a228ab
--- /dev/null
+++ b/chromium/extensions/browser/api/system_storage/system_storage_api.h
@@ -0,0 +1,63 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_SYSTEM_STORAGE_SYSTEM_STORAGE_API_H_
+#define EXTENSIONS_BROWSER_API_SYSTEM_STORAGE_SYSTEM_STORAGE_API_H_
+
+#include "components/storage_monitor/storage_monitor.h"
+#include "extensions/browser/api/system_storage/storage_info_provider.h"
+#include "extensions/browser/extension_function.h"
+
+namespace extensions {
+
+// Implementation of the systeminfo.storage.get API. It is an asynchronous
+// call relative to browser UI thread.
+class SystemStorageGetInfoFunction : public AsyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("system.storage.getInfo", SYSTEM_STORAGE_GETINFO);
+ SystemStorageGetInfoFunction();
+
+ private:
+ ~SystemStorageGetInfoFunction() override;
+ bool RunAsync() override;
+
+ void OnGetStorageInfoCompleted(bool success);
+};
+
+class SystemStorageEjectDeviceFunction : public AsyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("system.storage.ejectDevice",
+ SYSTEM_STORAGE_EJECTDEVICE);
+
+ protected:
+ ~SystemStorageEjectDeviceFunction() override;
+
+ // AsyncExtensionFunction overrides.
+ bool RunAsync() override;
+
+ private:
+ void OnStorageMonitorInit(const std::string& transient_device_id);
+
+ // Eject device request handler.
+ void HandleResponse(storage_monitor::StorageMonitor::EjectStatus status);
+};
+
+class SystemStorageGetAvailableCapacityFunction
+ : public AsyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("system.storage.getAvailableCapacity",
+ SYSTEM_STORAGE_GETAVAILABLECAPACITY);
+ SystemStorageGetAvailableCapacityFunction();
+
+ private:
+ void OnStorageMonitorInit(const std::string& transient_id);
+ void OnQueryCompleted(const std::string& transient_id,
+ double available_capacity);
+ ~SystemStorageGetAvailableCapacityFunction() override;
+ bool RunAsync() override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_SYSTEM_STORAGE_SYSTEM_STORAGE_API_H_
diff --git a/chromium/extensions/browser/api/system_storage/system_storage_apitest.cc b/chromium/extensions/browser/api/system_storage/system_storage_apitest.cc
new file mode 100644
index 00000000000..c57b311ec1d
--- /dev/null
+++ b/chromium/extensions/browser/api/system_storage/system_storage_apitest.cc
@@ -0,0 +1,151 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/storage_monitor/storage_monitor.h"
+#include "components/storage_monitor/test_storage_monitor.h"
+#include "extensions/browser/api/system_storage/storage_api_test_util.h"
+#include "extensions/browser/api/system_storage/storage_info_provider.h"
+#include "extensions/shell/test/shell_apitest.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "extensions/test/result_catcher.h"
+
+namespace {
+
+using extensions::StorageUnitInfoList;
+using extensions::test::TestStorageUnitInfo;
+using extensions::test::kRemovableStorageData;
+using storage_monitor::StorageMonitor;
+using storage_monitor::TestStorageMonitor;
+
+const struct TestStorageUnitInfo kTestingData[] = {
+ {"dcim:device:001", "0xbeaf", 4098, 1},
+ {"path:device:002", "/home", 4098, 2},
+ {"path:device:003", "/data", 10000, 3}};
+
+} // namespace
+
+class TestStorageInfoProvider : public extensions::StorageInfoProvider {
+ public:
+ TestStorageInfoProvider(const struct TestStorageUnitInfo* testing_data,
+ size_t n);
+
+ private:
+ ~TestStorageInfoProvider() override;
+
+ // StorageInfoProvider implementations.
+ double GetStorageFreeSpaceFromTransientIdOnFileThread(
+ const std::string& transient_id) override;
+
+ std::vector<struct TestStorageUnitInfo> testing_data_;
+};
+
+TestStorageInfoProvider::TestStorageInfoProvider(
+ const struct TestStorageUnitInfo* testing_data,
+ size_t n)
+ : testing_data_(testing_data, testing_data + n) {
+}
+
+TestStorageInfoProvider::~TestStorageInfoProvider() {
+}
+
+double TestStorageInfoProvider::GetStorageFreeSpaceFromTransientIdOnFileThread(
+ const std::string& transient_id) {
+ std::string device_id =
+ StorageMonitor::GetInstance()->GetDeviceIdForTransientId(transient_id);
+ for (size_t i = 0; i < testing_data_.size(); ++i) {
+ if (testing_data_[i].device_id == device_id) {
+ return static_cast<double>(testing_data_[i].available_capacity);
+ }
+ }
+ return -1;
+}
+
+class SystemStorageApiTest : public extensions::ShellApiTest {
+ public:
+ SystemStorageApiTest() {}
+ ~SystemStorageApiTest() override {}
+
+ void SetUpOnMainThread() override {
+ ShellApiTest::SetUpOnMainThread();
+ TestStorageMonitor::CreateForBrowserTests();
+ }
+
+ void SetUpInProcessBrowserTestFixture() override {
+ ShellApiTest::SetUpInProcessBrowserTestFixture();
+ message_loop_.reset(new base::MessageLoopForUI);
+ }
+
+ void SetUpAllMockStorageDevices() {
+ for (size_t i = 0; i < arraysize(kTestingData); ++i) {
+ AttachRemovableStorage(kTestingData[i]);
+ }
+ }
+
+ void AttachRemovableStorage(
+ const struct TestStorageUnitInfo& removable_storage_info) {
+ StorageMonitor::GetInstance()->receiver()->ProcessAttach(
+ extensions::test::BuildStorageInfoFromTestStorageUnitInfo(
+ removable_storage_info));
+ }
+
+ void DetachRemovableStorage(const std::string& id) {
+ StorageMonitor::GetInstance()->receiver()->ProcessDetach(id);
+ }
+
+ private:
+ scoped_ptr<base::MessageLoop> message_loop_;
+};
+
+IN_PROC_BROWSER_TEST_F(SystemStorageApiTest, Storage) {
+ SetUpAllMockStorageDevices();
+ TestStorageInfoProvider* provider =
+ new TestStorageInfoProvider(kTestingData, arraysize(kTestingData));
+ extensions::StorageInfoProvider::InitializeForTesting(provider);
+ std::vector<linked_ptr<ExtensionTestMessageListener>> device_ids_listeners;
+ for (size_t i = 0; i < arraysize(kTestingData); ++i) {
+ linked_ptr<ExtensionTestMessageListener> listener(
+ new ExtensionTestMessageListener(
+ StorageMonitor::GetInstance()->GetTransientIdForDeviceId(
+ kTestingData[i].device_id),
+ false));
+ device_ids_listeners.push_back(listener);
+ }
+ ASSERT_TRUE(RunAppTest("system/storage")) << message_;
+ for (size_t i = 0; i < device_ids_listeners.size(); ++i)
+ EXPECT_TRUE(device_ids_listeners[i]->WaitUntilSatisfied());
+}
+
+IN_PROC_BROWSER_TEST_F(SystemStorageApiTest, StorageAttachment) {
+ extensions::ResultCatcher catcher;
+ ExtensionTestMessageListener attach_listener("attach", false);
+ ExtensionTestMessageListener detach_listener("detach", false);
+
+ EXPECT_TRUE(LoadApp("system/storage_attachment"));
+ // Simulate triggering onAttached event.
+ ASSERT_TRUE(attach_listener.WaitUntilSatisfied());
+
+ AttachRemovableStorage(kRemovableStorageData);
+
+ std::string removable_storage_transient_id =
+ StorageMonitor::GetInstance()->GetTransientIdForDeviceId(
+ kRemovableStorageData.device_id);
+ ExtensionTestMessageListener detach_device_id_listener(
+ removable_storage_transient_id, false);
+
+ // Simulate triggering onDetached event.
+ ASSERT_TRUE(detach_listener.WaitUntilSatisfied());
+ DetachRemovableStorage(kRemovableStorageData.device_id);
+
+ ASSERT_TRUE(detach_device_id_listener.WaitUntilSatisfied());
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
diff --git a/chromium/extensions/browser/api/system_storage/system_storage_eject_apitest.cc b/chromium/extensions/browser/api/system_storage/system_storage_eject_apitest.cc
new file mode 100644
index 00000000000..f96cf3d4d06
--- /dev/null
+++ b/chromium/extensions/browser/api/system_storage/system_storage_eject_apitest.cc
@@ -0,0 +1,111 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// SystemStorage eject API browser tests.
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/storage_monitor/storage_info.h"
+#include "components/storage_monitor/storage_monitor.h"
+#include "components/storage_monitor/test_storage_monitor.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/test/test_utils.h"
+#include "extensions/browser/api/system_storage/storage_api_test_util.h"
+#include "extensions/browser/api/system_storage/storage_info_provider.h"
+#include "extensions/browser/extension_host.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/common/extension.h"
+#include "extensions/shell/test/shell_apitest.h"
+#include "extensions/test/extension_test_message_listener.h"
+
+namespace {
+
+using extensions::test::TestStorageUnitInfo;
+using extensions::test::kRemovableStorageData;
+using storage_monitor::StorageMonitor;
+using storage_monitor::TestStorageMonitor;
+
+} // namespace
+
+class SystemStorageEjectApiTest : public extensions::ShellApiTest {
+ public:
+ SystemStorageEjectApiTest() : monitor_(NULL) {}
+ ~SystemStorageEjectApiTest() override {}
+
+ protected:
+ void SetUpOnMainThread() override {
+ monitor_ = TestStorageMonitor::CreateForBrowserTests();
+ ShellApiTest::SetUpOnMainThread();
+ }
+
+ content::RenderViewHost* GetHost() {
+ ExtensionTestMessageListener listener("loaded", false);
+ const extensions::Extension* extension = LoadApp("system/storage_eject");
+
+ // Wait for the extension to load completely so we can execute
+ // the JavaScript function from test case.
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ return extensions::ProcessManager::Get(browser_context())
+ ->GetBackgroundHostForExtension(extension->id())
+ ->render_view_host();
+ }
+
+ void ExecuteCmdAndCheckReply(content::RenderViewHost* host,
+ const std::string& js_command,
+ const std::string& ok_message) {
+ ExtensionTestMessageListener listener(ok_message, false);
+ host->GetMainFrame()->ExecuteJavaScriptForTests(
+ base::ASCIIToUTF16(js_command));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+ }
+
+ void Attach() {
+ DCHECK(StorageMonitor::GetInstance()->IsInitialized());
+ StorageMonitor::GetInstance()->receiver()->ProcessAttach(
+ extensions::test::BuildStorageInfoFromTestStorageUnitInfo(
+ kRemovableStorageData));
+ content::RunAllPendingInMessageLoop();
+ }
+
+ void Detach() {
+ DCHECK(StorageMonitor::GetInstance()->IsInitialized());
+ StorageMonitor::GetInstance()->receiver()->ProcessDetach(
+ kRemovableStorageData.device_id);
+ content::RunAllPendingInMessageLoop();
+ }
+
+ protected:
+ TestStorageMonitor* monitor_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SystemStorageEjectApiTest);
+};
+
+IN_PROC_BROWSER_TEST_F(SystemStorageEjectApiTest, EjectTest) {
+ content::RenderViewHost* host = GetHost();
+ ExecuteCmdAndCheckReply(host, "addAttachListener()", "add_attach_ok");
+
+ // Attach / detach
+ const std::string expect_attach_msg =
+ base::StringPrintf("%s,%s", "attach_test_ok", kRemovableStorageData.name);
+ ExtensionTestMessageListener attach_finished_listener(expect_attach_msg,
+ false /* no reply */);
+ Attach();
+ EXPECT_TRUE(attach_finished_listener.WaitUntilSatisfied());
+
+ ExecuteCmdAndCheckReply(host, "ejectTest()", "eject_ok");
+ EXPECT_EQ(kRemovableStorageData.device_id, monitor_->ejected_device());
+
+ Detach();
+}
+
+IN_PROC_BROWSER_TEST_F(SystemStorageEjectApiTest, EjectBadDeviceTest) {
+ ExecuteCmdAndCheckReply(GetHost(), "ejectFailTest()", "eject_no_such_device");
+
+ EXPECT_EQ("", monitor_->ejected_device());
+}
diff --git a/chromium/extensions/browser/api/usb/OWNERS b/chromium/extensions/browser/api/usb/OWNERS
new file mode 100644
index 00000000000..0286c074134
--- /dev/null
+++ b/chromium/extensions/browser/api/usb/OWNERS
@@ -0,0 +1,5 @@
+# USB API members. For core API change, check with owners in
+# chrome/browser/extensions/OWNERS
+reillyg@chromium.org
+rockot@chromium.org
+rpaquay@chromium.org
diff --git a/chromium/extensions/browser/api/usb/usb_api.cc b/chromium/extensions/browser/api/usb/usb_api.cc
new file mode 100644
index 00000000000..49ad2c2ebcd
--- /dev/null
+++ b/chromium/extensions/browser/api/usb/usb_api.cc
@@ -0,0 +1,1263 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/usb/usb_api.h"
+
+#include <algorithm>
+#include <numeric>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/barrier_closure.h"
+#include "base/memory/scoped_ptr.h"
+#include "device/core/device_client.h"
+#include "device/usb/usb_descriptors.h"
+#include "device/usb/usb_device_handle.h"
+#include "device/usb/usb_service.h"
+#include "extensions/browser/api/device_permissions_manager.h"
+#include "extensions/browser/api/device_permissions_prompt.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/api/usb/usb_device_resource.h"
+#include "extensions/browser/api/usb/usb_guid_map.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/common/api/usb.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/permissions/usb_device_permission.h"
+
+namespace usb = extensions::api::usb;
+namespace BulkTransfer = usb::BulkTransfer;
+namespace ClaimInterface = usb::ClaimInterface;
+namespace CloseDevice = usb::CloseDevice;
+namespace ControlTransfer = usb::ControlTransfer;
+namespace FindDevices = usb::FindDevices;
+namespace GetConfigurations = usb::GetConfigurations;
+namespace GetDevices = usb::GetDevices;
+namespace GetUserSelectedDevices = usb::GetUserSelectedDevices;
+namespace InterruptTransfer = usb::InterruptTransfer;
+namespace IsochronousTransfer = usb::IsochronousTransfer;
+namespace SetConfiguration = usb::SetConfiguration;
+namespace GetConfiguration = usb::GetConfiguration;
+namespace ListInterfaces = usb::ListInterfaces;
+namespace OpenDevice = usb::OpenDevice;
+namespace ReleaseInterface = usb::ReleaseInterface;
+namespace RequestAccess = usb::RequestAccess;
+namespace ResetDevice = usb::ResetDevice;
+namespace SetInterfaceAlternateSetting = usb::SetInterfaceAlternateSetting;
+
+using content::BrowserThread;
+using device::UsbConfigDescriptor;
+using device::UsbDevice;
+using device::UsbDeviceFilter;
+using device::UsbDeviceHandle;
+using device::UsbEndpointDescriptor;
+using device::UsbEndpointDirection;
+using device::UsbInterfaceDescriptor;
+using device::UsbService;
+using device::UsbSynchronizationType;
+using device::UsbTransferStatus;
+using device::UsbTransferType;
+using device::UsbUsageType;
+using std::string;
+using std::vector;
+using usb::ConfigDescriptor;
+using usb::ControlTransferInfo;
+using usb::ConnectionHandle;
+using usb::Device;
+using usb::Direction;
+using usb::EndpointDescriptor;
+using usb::GenericTransferInfo;
+using usb::InterfaceDescriptor;
+using usb::IsochronousTransferInfo;
+using usb::Recipient;
+using usb::RequestType;
+using usb::SynchronizationType;
+using usb::TransferType;
+using usb::UsageType;
+
+namespace extensions {
+
+namespace {
+
+const char kDataKey[] = "data";
+const char kResultCodeKey[] = "resultCode";
+
+const char kErrorInitService[] = "Failed to initialize USB service.";
+
+const char kErrorOpen[] = "Failed to open device.";
+const char kErrorCancelled[] = "Transfer was cancelled.";
+const char kErrorDisconnect[] = "Device disconnected.";
+const char kErrorGeneric[] = "Transfer failed.";
+const char kErrorNotSupported[] = "Not supported on this platform.";
+const char kErrorNotConfigured[] = "The device is not in a configured state.";
+const char kErrorOverflow[] = "Inbound transfer overflow.";
+const char kErrorStalled[] = "Transfer stalled.";
+const char kErrorTimeout[] = "Transfer timed out.";
+const char kErrorTransferLength[] = "Transfer length is insufficient.";
+const char kErrorCannotSetConfiguration[] =
+ "Error setting device configuration.";
+const char kErrorCannotClaimInterface[] = "Error claiming interface.";
+const char kErrorCannotReleaseInterface[] = "Error releasing interface.";
+const char kErrorCannotSetInterfaceAlternateSetting[] =
+ "Error setting alternate interface setting.";
+const char kErrorConvertDirection[] = "Invalid transfer direction.";
+const char kErrorConvertRecipient[] = "Invalid transfer recipient.";
+const char kErrorConvertRequestType[] = "Invalid request type.";
+const char kErrorMalformedParameters[] = "Error parsing parameters.";
+const char kErrorNoConnection[] = "No such connection.";
+const char kErrorNoDevice[] = "No such device.";
+const char kErrorPermissionDenied[] = "Permission to access device was denied";
+const char kErrorInvalidTransferLength[] =
+ "Transfer length must be a positive number less than 104,857,600.";
+const char kErrorInvalidNumberOfPackets[] =
+ "Number of packets must be a positive number less than 4,194,304.";
+const char kErrorInvalidPacketLength[] =
+ "Packet length must be a positive number less than 65,536.";
+const char kErrorInvalidTimeout[] =
+ "Transfer timeout must be greater than or equal to 0.";
+const char kErrorResetDevice[] =
+ "Error resetting the device. The device has been closed.";
+
+const size_t kMaxTransferLength = 100 * 1024 * 1024;
+const int kMaxPackets = 4 * 1024 * 1024;
+const int kMaxPacketLength = 64 * 1024;
+
+bool ConvertDirectionFromApi(const Direction& input,
+ UsbEndpointDirection* output) {
+ switch (input) {
+ case usb::DIRECTION_IN:
+ *output = device::USB_DIRECTION_INBOUND;
+ return true;
+ case usb::DIRECTION_OUT:
+ *output = device::USB_DIRECTION_OUTBOUND;
+ return true;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+bool ConvertRequestTypeFromApi(const RequestType& input,
+ UsbDeviceHandle::TransferRequestType* output) {
+ switch (input) {
+ case usb::REQUEST_TYPE_STANDARD:
+ *output = UsbDeviceHandle::STANDARD;
+ return true;
+ case usb::REQUEST_TYPE_CLASS:
+ *output = UsbDeviceHandle::CLASS;
+ return true;
+ case usb::REQUEST_TYPE_VENDOR:
+ *output = UsbDeviceHandle::VENDOR;
+ return true;
+ case usb::REQUEST_TYPE_RESERVED:
+ *output = UsbDeviceHandle::RESERVED;
+ return true;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+bool ConvertRecipientFromApi(const Recipient& input,
+ UsbDeviceHandle::TransferRecipient* output) {
+ switch (input) {
+ case usb::RECIPIENT_DEVICE:
+ *output = UsbDeviceHandle::DEVICE;
+ return true;
+ case usb::RECIPIENT_INTERFACE:
+ *output = UsbDeviceHandle::INTERFACE;
+ return true;
+ case usb::RECIPIENT_ENDPOINT:
+ *output = UsbDeviceHandle::ENDPOINT;
+ return true;
+ case usb::RECIPIENT_OTHER:
+ *output = UsbDeviceHandle::OTHER;
+ return true;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+template <class T>
+bool GetTransferSize(const T& input, size_t* output) {
+ if (input.direction == usb::DIRECTION_IN) {
+ const int* length = input.length.get();
+ if (length && *length >= 0 &&
+ static_cast<size_t>(*length) < kMaxTransferLength) {
+ *output = *length;
+ return true;
+ }
+ } else if (input.direction == usb::DIRECTION_OUT) {
+ if (input.data.get()) {
+ *output = input.data->size();
+ return true;
+ }
+ }
+ return false;
+}
+
+template <class T>
+scoped_refptr<net::IOBuffer> CreateBufferForTransfer(
+ const T& input,
+ UsbEndpointDirection direction,
+ size_t size) {
+ if (size >= kMaxTransferLength)
+ return NULL;
+
+ // Allocate a |size|-bytes buffer, or a one-byte buffer if |size| is 0. This
+ // is due to an impedance mismatch between IOBuffer and URBs. An IOBuffer
+ // cannot represent a zero-length buffer, while an URB can.
+ scoped_refptr<net::IOBuffer> buffer =
+ new net::IOBuffer(std::max(static_cast<size_t>(1), size));
+
+ if (direction == device::USB_DIRECTION_INBOUND) {
+ return buffer;
+ } else if (direction == device::USB_DIRECTION_OUTBOUND) {
+ if (input.data.get() && size <= input.data->size()) {
+ memcpy(buffer->data(), input.data->data(), size);
+ return buffer;
+ }
+ }
+ NOTREACHED();
+ return NULL;
+}
+
+const char* ConvertTransferStatusToApi(const UsbTransferStatus status) {
+ switch (status) {
+ case device::USB_TRANSFER_COMPLETED:
+ return "";
+ case device::USB_TRANSFER_ERROR:
+ return kErrorGeneric;
+ case device::USB_TRANSFER_TIMEOUT:
+ return kErrorTimeout;
+ case device::USB_TRANSFER_CANCELLED:
+ return kErrorCancelled;
+ case device::USB_TRANSFER_STALLED:
+ return kErrorStalled;
+ case device::USB_TRANSFER_DISCONNECT:
+ return kErrorDisconnect;
+ case device::USB_TRANSFER_OVERFLOW:
+ return kErrorOverflow;
+ case device::USB_TRANSFER_LENGTH_SHORT:
+ return kErrorTransferLength;
+ default:
+ NOTREACHED();
+ return "";
+ }
+}
+
+base::Value* PopulateConnectionHandle(int handle,
+ int vendor_id,
+ int product_id) {
+ ConnectionHandle result;
+ result.handle = handle;
+ result.vendor_id = vendor_id;
+ result.product_id = product_id;
+ return result.ToValue().release();
+}
+
+TransferType ConvertTransferTypeToApi(const UsbTransferType& input) {
+ switch (input) {
+ case device::USB_TRANSFER_CONTROL:
+ return usb::TRANSFER_TYPE_CONTROL;
+ case device::USB_TRANSFER_INTERRUPT:
+ return usb::TRANSFER_TYPE_INTERRUPT;
+ case device::USB_TRANSFER_ISOCHRONOUS:
+ return usb::TRANSFER_TYPE_ISOCHRONOUS;
+ case device::USB_TRANSFER_BULK:
+ return usb::TRANSFER_TYPE_BULK;
+ default:
+ NOTREACHED();
+ return usb::TRANSFER_TYPE_NONE;
+ }
+}
+
+Direction ConvertDirectionToApi(const UsbEndpointDirection& input) {
+ switch (input) {
+ case device::USB_DIRECTION_INBOUND:
+ return usb::DIRECTION_IN;
+ case device::USB_DIRECTION_OUTBOUND:
+ return usb::DIRECTION_OUT;
+ default:
+ NOTREACHED();
+ return usb::DIRECTION_NONE;
+ }
+}
+
+SynchronizationType ConvertSynchronizationTypeToApi(
+ const UsbSynchronizationType& input) {
+ switch (input) {
+ case device::USB_SYNCHRONIZATION_NONE:
+ return usb::SYNCHRONIZATION_TYPE_NONE;
+ case device::USB_SYNCHRONIZATION_ASYNCHRONOUS:
+ return usb::SYNCHRONIZATION_TYPE_ASYNCHRONOUS;
+ case device::USB_SYNCHRONIZATION_ADAPTIVE:
+ return usb::SYNCHRONIZATION_TYPE_ADAPTIVE;
+ case device::USB_SYNCHRONIZATION_SYNCHRONOUS:
+ return usb::SYNCHRONIZATION_TYPE_SYNCHRONOUS;
+ default:
+ NOTREACHED();
+ return usb::SYNCHRONIZATION_TYPE_NONE;
+ }
+}
+
+UsageType ConvertUsageTypeToApi(const UsbUsageType& input) {
+ switch (input) {
+ case device::USB_USAGE_DATA:
+ return usb::USAGE_TYPE_DATA;
+ case device::USB_USAGE_FEEDBACK:
+ return usb::USAGE_TYPE_FEEDBACK;
+ case device::USB_USAGE_EXPLICIT_FEEDBACK:
+ return usb::USAGE_TYPE_EXPLICITFEEDBACK;
+ default:
+ NOTREACHED();
+ return usb::USAGE_TYPE_NONE;
+ }
+}
+
+EndpointDescriptor ConvertEndpointDescriptor(
+ const UsbEndpointDescriptor& input) {
+ EndpointDescriptor output;
+ output.address = input.address;
+ output.type = ConvertTransferTypeToApi(input.transfer_type);
+ output.direction = ConvertDirectionToApi(input.direction);
+ output.maximum_packet_size = input.maximum_packet_size;
+ output.synchronization =
+ ConvertSynchronizationTypeToApi(input.synchronization_type);
+ output.usage = ConvertUsageTypeToApi(input.usage_type);
+ output.polling_interval.reset(new int(input.polling_interval));
+ output.extra_data.assign(input.extra_data.begin(), input.extra_data.end());
+ return output;
+}
+
+InterfaceDescriptor ConvertInterfaceDescriptor(
+ const UsbInterfaceDescriptor& input) {
+ InterfaceDescriptor output;
+ output.interface_number = input.interface_number;
+ output.alternate_setting = input.alternate_setting;
+ output.interface_class = input.interface_class;
+ output.interface_subclass = input.interface_subclass;
+ output.interface_protocol = input.interface_protocol;
+ for (const UsbEndpointDescriptor& input_endpoint : input.endpoints)
+ output.endpoints.push_back(ConvertEndpointDescriptor(input_endpoint));
+ output.extra_data.assign(input.extra_data.begin(), input.extra_data.end());
+ return output;
+}
+
+ConfigDescriptor ConvertConfigDescriptor(const UsbConfigDescriptor& input) {
+ ConfigDescriptor output;
+ output.configuration_value = input.configuration_value;
+ output.self_powered = input.self_powered;
+ output.remote_wakeup = input.remote_wakeup;
+ output.max_power = input.maximum_power;
+ for (const UsbInterfaceDescriptor& input_interface : input.interfaces)
+ output.interfaces.push_back(ConvertInterfaceDescriptor(input_interface));
+ output.extra_data.assign(input.extra_data.begin(), input.extra_data.end());
+ return output;
+}
+
+void ConvertDeviceFilter(const usb::DeviceFilter& input,
+ UsbDeviceFilter* output) {
+ if (input.vendor_id) {
+ output->SetVendorId(*input.vendor_id);
+ }
+ if (input.product_id) {
+ output->SetProductId(*input.product_id);
+ }
+ if (input.interface_class) {
+ output->SetInterfaceClass(*input.interface_class);
+ }
+ if (input.interface_subclass) {
+ output->SetInterfaceSubclass(*input.interface_subclass);
+ }
+ if (input.interface_protocol) {
+ output->SetInterfaceProtocol(*input.interface_protocol);
+ }
+}
+
+} // namespace
+
+UsbPermissionCheckingFunction::UsbPermissionCheckingFunction()
+ : device_permissions_manager_(nullptr) {
+}
+
+UsbPermissionCheckingFunction::~UsbPermissionCheckingFunction() {
+}
+
+bool UsbPermissionCheckingFunction::HasDevicePermission(
+ scoped_refptr<UsbDevice> device) {
+ if (!device_permissions_manager_) {
+ device_permissions_manager_ =
+ DevicePermissionsManager::Get(browser_context());
+ }
+
+ DevicePermissions* device_permissions =
+ device_permissions_manager_->GetForExtension(extension_id());
+ DCHECK(device_permissions);
+
+ permission_entry_ = device_permissions->FindUsbDeviceEntry(device);
+ if (permission_entry_.get()) {
+ return true;
+ }
+
+ UsbDevicePermission::CheckParam param(
+ device->vendor_id(),
+ device->product_id(),
+ UsbDevicePermissionData::UNSPECIFIED_INTERFACE);
+ if (extension()->permissions_data()->CheckAPIPermissionWithParam(
+ APIPermission::kUsbDevice, &param)) {
+ return true;
+ }
+
+ return false;
+}
+
+void UsbPermissionCheckingFunction::RecordDeviceLastUsed() {
+ if (permission_entry_.get()) {
+ device_permissions_manager_->UpdateLastUsed(extension_id(),
+ permission_entry_);
+ }
+}
+
+UsbConnectionFunction::UsbConnectionFunction() {
+}
+
+UsbConnectionFunction::~UsbConnectionFunction() {
+}
+
+scoped_refptr<device::UsbDeviceHandle> UsbConnectionFunction::GetDeviceHandle(
+ const extensions::api::usb::ConnectionHandle& handle) {
+ ApiResourceManager<UsbDeviceResource>* manager =
+ ApiResourceManager<UsbDeviceResource>::Get(browser_context());
+ if (!manager) {
+ return nullptr;
+ }
+
+ UsbDeviceResource* resource = manager->Get(extension_id(), handle.handle);
+ if (!resource) {
+ return nullptr;
+ }
+
+ return resource->device();
+}
+
+void UsbConnectionFunction::ReleaseDeviceHandle(
+ const extensions::api::usb::ConnectionHandle& handle) {
+ ApiResourceManager<UsbDeviceResource>* manager =
+ ApiResourceManager<UsbDeviceResource>::Get(browser_context());
+ manager->Remove(extension_id(), handle.handle);
+}
+
+UsbTransferFunction::UsbTransferFunction() {
+}
+
+UsbTransferFunction::~UsbTransferFunction() {
+}
+
+void UsbTransferFunction::OnCompleted(UsbTransferStatus status,
+ scoped_refptr<net::IOBuffer> data,
+ size_t length) {
+ scoped_ptr<base::DictionaryValue> transfer_info(new base::DictionaryValue());
+ transfer_info->SetInteger(kResultCodeKey, status);
+ transfer_info->Set(kDataKey, base::BinaryValue::CreateWithCopiedBuffer(
+ data->data(), length));
+
+ if (status == device::USB_TRANSFER_COMPLETED) {
+ Respond(OneArgument(std::move(transfer_info)));
+ } else {
+ scoped_ptr<base::ListValue> error_args(new base::ListValue());
+ error_args->Append(std::move(transfer_info));
+ // Using ErrorWithArguments is discouraged but required to provide the
+ // detailed transfer info as the transfer may have partially succeeded.
+ Respond(ErrorWithArguments(std::move(error_args),
+ ConvertTransferStatusToApi(status)));
+ }
+}
+
+UsbFindDevicesFunction::UsbFindDevicesFunction() {
+}
+
+UsbFindDevicesFunction::~UsbFindDevicesFunction() {
+}
+
+ExtensionFunction::ResponseAction UsbFindDevicesFunction::Run() {
+ scoped_ptr<extensions::api::usb::FindDevices::Params> parameters =
+ FindDevices::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters.get());
+
+ vendor_id_ = parameters->options.vendor_id;
+ product_id_ = parameters->options.product_id;
+ int interface_id = parameters->options.interface_id.get()
+ ? *parameters->options.interface_id.get()
+ : UsbDevicePermissionData::ANY_INTERFACE;
+ UsbDevicePermission::CheckParam param(vendor_id_, product_id_, interface_id);
+ if (!extension()->permissions_data()->CheckAPIPermissionWithParam(
+ APIPermission::kUsbDevice, &param)) {
+ return RespondNow(Error(kErrorPermissionDenied));
+ }
+
+ UsbService* service = device::DeviceClient::Get()->GetUsbService();
+ if (!service) {
+ return RespondNow(Error(kErrorInitService));
+ }
+
+ service->GetDevices(
+ base::Bind(&UsbFindDevicesFunction::OnGetDevicesComplete, this));
+ return RespondLater();
+}
+
+void UsbFindDevicesFunction::OnGetDevicesComplete(
+ const std::vector<scoped_refptr<UsbDevice>>& devices) {
+ result_.reset(new base::ListValue());
+ barrier_ = base::BarrierClosure(
+ devices.size(), base::Bind(&UsbFindDevicesFunction::OpenComplete, this));
+
+ for (const scoped_refptr<UsbDevice>& device : devices) {
+ if (device->vendor_id() != vendor_id_ ||
+ device->product_id() != product_id_) {
+ barrier_.Run();
+ } else {
+ device->Open(base::Bind(&UsbFindDevicesFunction::OnDeviceOpened, this));
+ }
+ }
+}
+
+void UsbFindDevicesFunction::OnDeviceOpened(
+ scoped_refptr<UsbDeviceHandle> device_handle) {
+ if (device_handle.get()) {
+ ApiResourceManager<UsbDeviceResource>* manager =
+ ApiResourceManager<UsbDeviceResource>::Get(browser_context());
+ UsbDeviceResource* resource =
+ new UsbDeviceResource(extension_id(), device_handle);
+ scoped_refptr<UsbDevice> device = device_handle->GetDevice();
+ result_->Append(PopulateConnectionHandle(
+ manager->Add(resource), device->vendor_id(), device->product_id()));
+ }
+ barrier_.Run();
+}
+
+void UsbFindDevicesFunction::OpenComplete() {
+ Respond(OneArgument(std::move(result_)));
+}
+
+UsbGetDevicesFunction::UsbGetDevicesFunction() {
+}
+
+UsbGetDevicesFunction::~UsbGetDevicesFunction() {
+}
+
+ExtensionFunction::ResponseAction UsbGetDevicesFunction::Run() {
+ scoped_ptr<extensions::api::usb::GetDevices::Params> parameters =
+ GetDevices::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters.get());
+
+ if (parameters->options.filters) {
+ filters_.resize(parameters->options.filters->size());
+ for (size_t i = 0; i < parameters->options.filters->size(); ++i) {
+ ConvertDeviceFilter(parameters->options.filters->at(i), &filters_[i]);
+ }
+ }
+ if (parameters->options.vendor_id) {
+ filters_.resize(filters_.size() + 1);
+ filters_.back().SetVendorId(*parameters->options.vendor_id);
+ if (parameters->options.product_id) {
+ filters_.back().SetProductId(*parameters->options.product_id);
+ }
+ }
+
+ UsbService* service = device::DeviceClient::Get()->GetUsbService();
+ if (!service) {
+ return RespondNow(Error(kErrorInitService));
+ }
+
+ service->GetDevices(
+ base::Bind(&UsbGetDevicesFunction::OnGetDevicesComplete, this));
+ return RespondLater();
+}
+
+void UsbGetDevicesFunction::OnGetDevicesComplete(
+ const std::vector<scoped_refptr<UsbDevice>>& devices) {
+ scoped_ptr<base::ListValue> result(new base::ListValue());
+ UsbGuidMap* guid_map = UsbGuidMap::Get(browser_context());
+ for (const scoped_refptr<UsbDevice>& device : devices) {
+ if ((filters_.empty() || UsbDeviceFilter::MatchesAny(device, filters_)) &&
+ HasDevicePermission(device)) {
+ Device api_device;
+ guid_map->GetApiDevice(device, &api_device);
+ result->Append(api_device.ToValue());
+ }
+ }
+
+ Respond(OneArgument(std::move(result)));
+}
+
+UsbGetUserSelectedDevicesFunction::UsbGetUserSelectedDevicesFunction() {
+}
+
+UsbGetUserSelectedDevicesFunction::~UsbGetUserSelectedDevicesFunction() {
+}
+
+ExtensionFunction::ResponseAction UsbGetUserSelectedDevicesFunction::Run() {
+ scoped_ptr<extensions::api::usb::GetUserSelectedDevices::Params> parameters =
+ GetUserSelectedDevices::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters.get());
+
+ if (!user_gesture()) {
+ return RespondNow(OneArgument(new base::ListValue()));
+ }
+
+ bool multiple = false;
+ if (parameters->options.multiple) {
+ multiple = *parameters->options.multiple;
+ }
+
+ std::vector<UsbDeviceFilter> filters;
+ if (parameters->options.filters) {
+ filters.resize(parameters->options.filters->size());
+ for (size_t i = 0; i < parameters->options.filters->size(); ++i) {
+ ConvertDeviceFilter(parameters->options.filters->at(i), &filters[i]);
+ }
+ }
+
+ prompt_ = ExtensionsAPIClient::Get()->CreateDevicePermissionsPrompt(
+ GetAssociatedWebContents());
+ if (!prompt_) {
+ return RespondNow(Error(kErrorNotSupported));
+ }
+
+ prompt_->AskForUsbDevices(
+ extension(), browser_context(), multiple, filters,
+ base::Bind(&UsbGetUserSelectedDevicesFunction::OnDevicesChosen, this));
+ return RespondLater();
+}
+
+void UsbGetUserSelectedDevicesFunction::OnDevicesChosen(
+ const std::vector<scoped_refptr<UsbDevice>>& devices) {
+ scoped_ptr<base::ListValue> result(new base::ListValue());
+ UsbGuidMap* guid_map = UsbGuidMap::Get(browser_context());
+ for (const auto& device : devices) {
+ Device api_device;
+ guid_map->GetApiDevice(device, &api_device);
+ result->Append(api_device.ToValue());
+ }
+
+ Respond(OneArgument(std::move(result)));
+}
+
+UsbGetConfigurationsFunction::UsbGetConfigurationsFunction() {}
+
+UsbGetConfigurationsFunction::~UsbGetConfigurationsFunction() {}
+
+ExtensionFunction::ResponseAction UsbGetConfigurationsFunction::Run() {
+ scoped_ptr<extensions::api::usb::GetConfigurations::Params> parameters =
+ GetConfigurations::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters.get());
+
+ UsbService* service = device::DeviceClient::Get()->GetUsbService();
+ if (!service) {
+ return RespondNow(Error(kErrorInitService));
+ }
+
+ std::string guid;
+ if (!UsbGuidMap::Get(browser_context())
+ ->GetGuidFromId(parameters->device.device, &guid)) {
+ return RespondNow(Error(kErrorNoDevice));
+ }
+
+ scoped_refptr<UsbDevice> device = service->GetDevice(guid);
+ if (!device.get()) {
+ return RespondNow(Error(kErrorNoDevice));
+ }
+
+ if (!HasDevicePermission(device)) {
+ // This function must act as if there is no such device. Otherwise it can be
+ // used to fingerprint unauthorized devices.
+ return RespondNow(Error(kErrorNoDevice));
+ }
+
+ scoped_ptr<base::ListValue> configs(new base::ListValue());
+ const UsbConfigDescriptor* active_config = device->GetActiveConfiguration();
+ for (const UsbConfigDescriptor& config : device->configurations()) {
+ ConfigDescriptor api_config = ConvertConfigDescriptor(config);
+ if (active_config &&
+ config.configuration_value == active_config->configuration_value) {
+ api_config.active = true;
+ }
+ configs->Append(api_config.ToValue());
+ }
+ return RespondNow(OneArgument(std::move(configs)));
+}
+
+UsbRequestAccessFunction::UsbRequestAccessFunction() {
+}
+
+UsbRequestAccessFunction::~UsbRequestAccessFunction() {
+}
+
+ExtensionFunction::ResponseAction UsbRequestAccessFunction::Run() {
+ scoped_ptr<extensions::api::usb::RequestAccess::Params> parameters =
+ RequestAccess::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters.get());
+ return RespondNow(OneArgument(new base::FundamentalValue(true)));
+}
+
+UsbOpenDeviceFunction::UsbOpenDeviceFunction() {
+}
+
+UsbOpenDeviceFunction::~UsbOpenDeviceFunction() {
+}
+
+ExtensionFunction::ResponseAction UsbOpenDeviceFunction::Run() {
+ scoped_ptr<extensions::api::usb::OpenDevice::Params> parameters =
+ OpenDevice::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters.get());
+
+ UsbService* service = device::DeviceClient::Get()->GetUsbService();
+ if (!service) {
+ return RespondNow(Error(kErrorInitService));
+ }
+
+ std::string guid;
+ if (!UsbGuidMap::Get(browser_context())
+ ->GetGuidFromId(parameters->device.device, &guid)) {
+ return RespondNow(Error(kErrorNoDevice));
+ }
+
+ scoped_refptr<UsbDevice> device = service->GetDevice(guid);
+ if (!device.get()) {
+ return RespondNow(Error(kErrorNoDevice));
+ }
+
+ if (!HasDevicePermission(device)) {
+ // This function must act as if there is no such device. Otherwise it can be
+ // used to fingerprint unauthorized devices.
+ return RespondNow(Error(kErrorNoDevice));
+ }
+
+ device->Open(base::Bind(&UsbOpenDeviceFunction::OnDeviceOpened, this));
+ return RespondLater();
+}
+
+void UsbOpenDeviceFunction::OnDeviceOpened(
+ scoped_refptr<UsbDeviceHandle> device_handle) {
+ if (!device_handle.get()) {
+ Respond(Error(kErrorOpen));
+ return;
+ }
+
+ RecordDeviceLastUsed();
+
+ ApiResourceManager<UsbDeviceResource>* manager =
+ ApiResourceManager<UsbDeviceResource>::Get(browser_context());
+ scoped_refptr<UsbDevice> device = device_handle->GetDevice();
+ Respond(OneArgument(PopulateConnectionHandle(
+ manager->Add(new UsbDeviceResource(extension_id(), device_handle)),
+ device->vendor_id(), device->product_id())));
+}
+
+UsbSetConfigurationFunction::UsbSetConfigurationFunction() {
+}
+
+UsbSetConfigurationFunction::~UsbSetConfigurationFunction() {
+}
+
+ExtensionFunction::ResponseAction UsbSetConfigurationFunction::Run() {
+ scoped_ptr<extensions::api::usb::SetConfiguration::Params> parameters =
+ SetConfiguration::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters.get());
+
+ scoped_refptr<UsbDeviceHandle> device_handle =
+ GetDeviceHandle(parameters->handle);
+ if (!device_handle.get()) {
+ return RespondNow(Error(kErrorNoConnection));
+ }
+
+ device_handle->SetConfiguration(
+ parameters->configuration_value,
+ base::Bind(&UsbSetConfigurationFunction::OnComplete, this));
+ return RespondLater();
+}
+
+void UsbSetConfigurationFunction::OnComplete(bool success) {
+ if (success) {
+ Respond(NoArguments());
+ } else {
+ Respond(Error(kErrorCannotSetConfiguration));
+ }
+}
+
+UsbGetConfigurationFunction::UsbGetConfigurationFunction() {
+}
+
+UsbGetConfigurationFunction::~UsbGetConfigurationFunction() {
+}
+
+ExtensionFunction::ResponseAction UsbGetConfigurationFunction::Run() {
+ scoped_ptr<extensions::api::usb::GetConfiguration::Params> parameters =
+ GetConfiguration::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters.get());
+
+ scoped_refptr<UsbDeviceHandle> device_handle =
+ GetDeviceHandle(parameters->handle);
+ if (!device_handle.get()) {
+ return RespondNow(Error(kErrorNoConnection));
+ }
+
+ const UsbConfigDescriptor* config_descriptor =
+ device_handle->GetDevice()->GetActiveConfiguration();
+ if (config_descriptor) {
+ ConfigDescriptor config = ConvertConfigDescriptor(*config_descriptor);
+ config.active = true;
+ return RespondNow(OneArgument(config.ToValue()));
+ } else {
+ return RespondNow(Error(kErrorNotConfigured));
+ }
+}
+
+UsbListInterfacesFunction::UsbListInterfacesFunction() {
+}
+
+UsbListInterfacesFunction::~UsbListInterfacesFunction() {
+}
+
+ExtensionFunction::ResponseAction UsbListInterfacesFunction::Run() {
+ scoped_ptr<extensions::api::usb::ListInterfaces::Params> parameters =
+ ListInterfaces::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters.get());
+
+ scoped_refptr<UsbDeviceHandle> device_handle =
+ GetDeviceHandle(parameters->handle);
+ if (!device_handle.get()) {
+ return RespondNow(Error(kErrorNoConnection));
+ }
+
+ const UsbConfigDescriptor* config_descriptor =
+ device_handle->GetDevice()->GetActiveConfiguration();
+ if (config_descriptor) {
+ ConfigDescriptor config = ConvertConfigDescriptor(*config_descriptor);
+
+ scoped_ptr<base::ListValue> result(new base::ListValue);
+ for (size_t i = 0; i < config.interfaces.size(); ++i) {
+ result->Append(config.interfaces[i].ToValue());
+ }
+
+ return RespondNow(OneArgument(std::move(result)));
+ } else {
+ return RespondNow(Error(kErrorNotConfigured));
+ }
+}
+
+UsbCloseDeviceFunction::UsbCloseDeviceFunction() {
+}
+
+UsbCloseDeviceFunction::~UsbCloseDeviceFunction() {
+}
+
+ExtensionFunction::ResponseAction UsbCloseDeviceFunction::Run() {
+ scoped_ptr<extensions::api::usb::CloseDevice::Params> parameters =
+ CloseDevice::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters.get());
+
+ scoped_refptr<UsbDeviceHandle> device_handle =
+ GetDeviceHandle(parameters->handle);
+ if (!device_handle.get()) {
+ return RespondNow(Error(kErrorNoConnection));
+ }
+
+ // The device handle is closed when the resource is destroyed.
+ ReleaseDeviceHandle(parameters->handle);
+ return RespondNow(NoArguments());
+}
+
+UsbClaimInterfaceFunction::UsbClaimInterfaceFunction() {
+}
+
+UsbClaimInterfaceFunction::~UsbClaimInterfaceFunction() {
+}
+
+ExtensionFunction::ResponseAction UsbClaimInterfaceFunction::Run() {
+ scoped_ptr<extensions::api::usb::ClaimInterface::Params> parameters =
+ ClaimInterface::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters.get());
+
+ scoped_refptr<UsbDeviceHandle> device_handle =
+ GetDeviceHandle(parameters->handle);
+ if (!device_handle.get()) {
+ return RespondNow(Error(kErrorNoConnection));
+ }
+
+ device_handle->ClaimInterface(
+ parameters->interface_number,
+ base::Bind(&UsbClaimInterfaceFunction::OnComplete, this));
+ return RespondLater();
+}
+
+void UsbClaimInterfaceFunction::OnComplete(bool success) {
+ if (success) {
+ Respond(NoArguments());
+ } else {
+ Respond(Error(kErrorCannotClaimInterface));
+ }
+}
+
+UsbReleaseInterfaceFunction::UsbReleaseInterfaceFunction() {
+}
+
+UsbReleaseInterfaceFunction::~UsbReleaseInterfaceFunction() {
+}
+
+ExtensionFunction::ResponseAction UsbReleaseInterfaceFunction::Run() {
+ scoped_ptr<extensions::api::usb::ReleaseInterface::Params> parameters =
+ ReleaseInterface::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters.get());
+
+ scoped_refptr<UsbDeviceHandle> device_handle =
+ GetDeviceHandle(parameters->handle);
+ if (!device_handle.get()) {
+ return RespondNow(Error(kErrorNoConnection));
+ }
+
+ device_handle->ReleaseInterface(
+ parameters->interface_number,
+ base::Bind(&UsbReleaseInterfaceFunction::OnComplete, this));
+ return RespondLater();
+}
+
+void UsbReleaseInterfaceFunction::OnComplete(bool success) {
+ if (success)
+ Respond(NoArguments());
+ else
+ Respond(Error(kErrorCannotReleaseInterface));
+}
+
+UsbSetInterfaceAlternateSettingFunction::
+ UsbSetInterfaceAlternateSettingFunction() {
+}
+
+UsbSetInterfaceAlternateSettingFunction::
+ ~UsbSetInterfaceAlternateSettingFunction() {
+}
+
+ExtensionFunction::ResponseAction
+UsbSetInterfaceAlternateSettingFunction::Run() {
+ scoped_ptr<extensions::api::usb::SetInterfaceAlternateSetting::Params>
+ parameters = SetInterfaceAlternateSetting::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters.get());
+
+ scoped_refptr<UsbDeviceHandle> device_handle =
+ GetDeviceHandle(parameters->handle);
+ if (!device_handle.get()) {
+ return RespondNow(Error(kErrorNoConnection));
+ }
+
+ device_handle->SetInterfaceAlternateSetting(
+ parameters->interface_number, parameters->alternate_setting,
+ base::Bind(&UsbSetInterfaceAlternateSettingFunction::OnComplete, this));
+ return RespondLater();
+}
+
+void UsbSetInterfaceAlternateSettingFunction::OnComplete(bool success) {
+ if (success) {
+ Respond(NoArguments());
+ } else {
+ Respond(Error(kErrorCannotSetInterfaceAlternateSetting));
+ }
+}
+
+UsbControlTransferFunction::UsbControlTransferFunction() {
+}
+
+UsbControlTransferFunction::~UsbControlTransferFunction() {
+}
+
+ExtensionFunction::ResponseAction UsbControlTransferFunction::Run() {
+ scoped_ptr<extensions::api::usb::ControlTransfer::Params> parameters =
+ ControlTransfer::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters.get());
+
+ scoped_refptr<UsbDeviceHandle> device_handle =
+ GetDeviceHandle(parameters->handle);
+ if (!device_handle.get()) {
+ return RespondNow(Error(kErrorNoConnection));
+ }
+
+ const ControlTransferInfo& transfer = parameters->transfer_info;
+ UsbEndpointDirection direction = device::USB_DIRECTION_INBOUND;
+ UsbDeviceHandle::TransferRequestType request_type;
+ UsbDeviceHandle::TransferRecipient recipient;
+ size_t size = 0;
+
+ if (!ConvertDirectionFromApi(transfer.direction, &direction)) {
+ return RespondNow(Error(kErrorConvertDirection));
+ }
+
+ if (!ConvertRequestTypeFromApi(transfer.request_type, &request_type)) {
+ return RespondNow(Error(kErrorConvertRequestType));
+ }
+
+ if (!ConvertRecipientFromApi(transfer.recipient, &recipient)) {
+ return RespondNow(Error(kErrorConvertRecipient));
+ }
+
+ if (!GetTransferSize(transfer, &size)) {
+ return RespondNow(Error(kErrorInvalidTransferLength));
+ }
+
+ scoped_refptr<net::IOBuffer> buffer =
+ CreateBufferForTransfer(transfer, direction, size);
+ if (!buffer.get()) {
+ return RespondNow(Error(kErrorMalformedParameters));
+ }
+
+ int timeout = transfer.timeout ? *transfer.timeout : 0;
+ if (timeout < 0) {
+ return RespondNow(Error(kErrorInvalidTimeout));
+ }
+
+ device_handle->ControlTransfer(
+ direction, request_type, recipient, transfer.request, transfer.value,
+ transfer.index, buffer.get(), size, timeout,
+ base::Bind(&UsbControlTransferFunction::OnCompleted, this));
+ return RespondLater();
+}
+
+UsbBulkTransferFunction::UsbBulkTransferFunction() {
+}
+
+UsbBulkTransferFunction::~UsbBulkTransferFunction() {
+}
+
+ExtensionFunction::ResponseAction UsbBulkTransferFunction::Run() {
+ scoped_ptr<extensions::api::usb::BulkTransfer::Params> parameters =
+ BulkTransfer::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters.get());
+
+ scoped_refptr<UsbDeviceHandle> device_handle =
+ GetDeviceHandle(parameters->handle);
+ if (!device_handle.get()) {
+ return RespondNow(Error(kErrorNoConnection));
+ }
+
+ const GenericTransferInfo& transfer = parameters->transfer_info;
+ UsbEndpointDirection direction = device::USB_DIRECTION_INBOUND;
+ size_t size = 0;
+
+ if (!ConvertDirectionFromApi(transfer.direction, &direction)) {
+ return RespondNow(Error(kErrorConvertDirection));
+ }
+
+ if (!GetTransferSize(transfer, &size)) {
+ return RespondNow(Error(kErrorInvalidTransferLength));
+ }
+
+ scoped_refptr<net::IOBuffer> buffer =
+ CreateBufferForTransfer(transfer, direction, size);
+ if (!buffer.get()) {
+ return RespondNow(Error(kErrorMalformedParameters));
+ }
+
+ int timeout = transfer.timeout ? *transfer.timeout : 0;
+ if (timeout < 0) {
+ return RespondNow(Error(kErrorInvalidTimeout));
+ }
+
+ device_handle->GenericTransfer(
+ direction, transfer.endpoint, buffer.get(), size, timeout,
+ base::Bind(&UsbBulkTransferFunction::OnCompleted, this));
+ return RespondLater();
+}
+
+UsbInterruptTransferFunction::UsbInterruptTransferFunction() {
+}
+
+UsbInterruptTransferFunction::~UsbInterruptTransferFunction() {
+}
+
+ExtensionFunction::ResponseAction UsbInterruptTransferFunction::Run() {
+ scoped_ptr<extensions::api::usb::InterruptTransfer::Params> parameters =
+ InterruptTransfer::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters.get());
+
+ scoped_refptr<UsbDeviceHandle> device_handle =
+ GetDeviceHandle(parameters->handle);
+ if (!device_handle.get()) {
+ return RespondNow(Error(kErrorNoConnection));
+ }
+
+ const GenericTransferInfo& transfer = parameters->transfer_info;
+ UsbEndpointDirection direction = device::USB_DIRECTION_INBOUND;
+ size_t size = 0;
+
+ if (!ConvertDirectionFromApi(transfer.direction, &direction)) {
+ return RespondNow(Error(kErrorConvertDirection));
+ }
+
+ if (!GetTransferSize(transfer, &size)) {
+ return RespondNow(Error(kErrorInvalidTransferLength));
+ }
+
+ scoped_refptr<net::IOBuffer> buffer =
+ CreateBufferForTransfer(transfer, direction, size);
+ if (!buffer.get()) {
+ return RespondNow(Error(kErrorMalformedParameters));
+ }
+
+ int timeout = transfer.timeout ? *transfer.timeout : 0;
+ if (timeout < 0) {
+ return RespondNow(Error(kErrorInvalidTimeout));
+ }
+
+ device_handle->GenericTransfer(
+ direction, transfer.endpoint, buffer.get(), size, timeout,
+ base::Bind(&UsbInterruptTransferFunction::OnCompleted, this));
+ return RespondLater();
+}
+
+UsbIsochronousTransferFunction::UsbIsochronousTransferFunction() {
+}
+
+UsbIsochronousTransferFunction::~UsbIsochronousTransferFunction() {
+}
+
+ExtensionFunction::ResponseAction UsbIsochronousTransferFunction::Run() {
+ scoped_ptr<extensions::api::usb::IsochronousTransfer::Params> parameters =
+ IsochronousTransfer::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters.get());
+
+ scoped_refptr<UsbDeviceHandle> device_handle =
+ GetDeviceHandle(parameters->handle);
+ if (!device_handle.get()) {
+ return RespondNow(Error(kErrorNoConnection));
+ }
+
+ const IsochronousTransferInfo& transfer = parameters->transfer_info;
+ const GenericTransferInfo& generic_transfer = transfer.transfer_info;
+ size_t size = 0;
+ UsbEndpointDirection direction = device::USB_DIRECTION_INBOUND;
+
+ if (!ConvertDirectionFromApi(generic_transfer.direction, &direction))
+ return RespondNow(Error(kErrorConvertDirection));
+
+ if (!GetTransferSize(generic_transfer, &size))
+ return RespondNow(Error(kErrorInvalidTransferLength));
+
+ if (transfer.packets < 0 || transfer.packets >= kMaxPackets)
+ return RespondNow(Error(kErrorInvalidNumberOfPackets));
+ size_t packets = transfer.packets;
+
+ if (transfer.packet_length < 0 ||
+ transfer.packet_length >= kMaxPacketLength) {
+ return RespondNow(Error(kErrorInvalidPacketLength));
+ }
+
+ size_t total_length = packets * transfer.packet_length;
+ if (packets > size || total_length > size)
+ return RespondNow(Error(kErrorTransferLength));
+ std::vector<uint32_t> packet_lengths(packets, transfer.packet_length);
+
+ int timeout = generic_transfer.timeout ? *generic_transfer.timeout : 0;
+ if (timeout < 0)
+ return RespondNow(Error(kErrorInvalidTimeout));
+
+ if (direction == device::USB_DIRECTION_INBOUND) {
+ device_handle->IsochronousTransferIn(
+ generic_transfer.endpoint, packet_lengths, timeout,
+ base::Bind(&UsbIsochronousTransferFunction::OnCompleted, this));
+ } else {
+ scoped_refptr<net::IOBuffer> buffer = CreateBufferForTransfer(
+ generic_transfer, direction, transfer.packets * transfer.packet_length);
+ if (!buffer.get())
+ return RespondNow(Error(kErrorMalformedParameters));
+
+ device_handle->IsochronousTransferOut(
+ generic_transfer.endpoint, buffer.get(), packet_lengths, timeout,
+ base::Bind(&UsbIsochronousTransferFunction::OnCompleted, this));
+ }
+ return RespondLater();
+}
+
+void UsbIsochronousTransferFunction::OnCompleted(
+ scoped_refptr<net::IOBuffer> data,
+ const std::vector<UsbDeviceHandle::IsochronousPacket>& packets) {
+ size_t length = std::accumulate(
+ packets.begin(), packets.end(), 0,
+ [](const size_t& a, const UsbDeviceHandle::IsochronousPacket& packet) {
+ return a + packet.transferred_length;
+ });
+ scoped_ptr<char[]> buffer(new char[length]);
+
+ UsbTransferStatus status = device::USB_TRANSFER_COMPLETED;
+ size_t buffer_offset = 0;
+ size_t data_offset = 0;
+ for (const auto& packet : packets) {
+ // Capture the error status of the first unsuccessful packet.
+ if (status == device::USB_TRANSFER_COMPLETED &&
+ packet.status != device::USB_TRANSFER_COMPLETED) {
+ status = packet.status;
+ }
+
+ memcpy(&buffer[buffer_offset], data->data() + data_offset,
+ packet.transferred_length);
+ buffer_offset += packet.transferred_length;
+ data_offset += packet.length;
+ }
+
+ scoped_ptr<base::DictionaryValue> transfer_info(new base::DictionaryValue());
+ transfer_info->SetInteger(kResultCodeKey, status);
+ transfer_info->Set(kDataKey,
+ new base::BinaryValue(std::move(buffer), length));
+ if (status == device::USB_TRANSFER_COMPLETED) {
+ Respond(OneArgument(std::move(transfer_info)));
+ } else {
+ scoped_ptr<base::ListValue> error_args(new base::ListValue());
+ error_args->Append(std::move(transfer_info));
+ // Using ErrorWithArguments is discouraged but required to provide the
+ // detailed transfer info as the transfer may have partially succeeded.
+ Respond(ErrorWithArguments(std::move(error_args),
+ ConvertTransferStatusToApi(status)));
+ }
+}
+
+UsbResetDeviceFunction::UsbResetDeviceFunction() {
+}
+
+UsbResetDeviceFunction::~UsbResetDeviceFunction() {
+}
+
+ExtensionFunction::ResponseAction UsbResetDeviceFunction::Run() {
+ parameters_ = ResetDevice::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(parameters_.get());
+
+ scoped_refptr<UsbDeviceHandle> device_handle =
+ GetDeviceHandle(parameters_->handle);
+ if (!device_handle.get()) {
+ return RespondNow(Error(kErrorNoConnection));
+ }
+
+ device_handle->ResetDevice(
+ base::Bind(&UsbResetDeviceFunction::OnComplete, this));
+ return RespondLater();
+}
+
+void UsbResetDeviceFunction::OnComplete(bool success) {
+ if (success) {
+ Respond(OneArgument(new base::FundamentalValue(true)));
+ } else {
+ scoped_refptr<UsbDeviceHandle> device_handle =
+ GetDeviceHandle(parameters_->handle);
+ if (device_handle.get()) {
+ device_handle->Close();
+ }
+ ReleaseDeviceHandle(parameters_->handle);
+
+ scoped_ptr<base::ListValue> error_args(new base::ListValue());
+ error_args->AppendBoolean(false);
+ // Using ErrorWithArguments is discouraged but required to maintain
+ // compatibility with existing applications.
+ Respond(ErrorWithArguments(std::move(error_args), kErrorResetDevice));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/usb/usb_api.h b/chromium/extensions/browser/api/usb/usb_api.h
new file mode 100644
index 00000000000..397b1ad78d0
--- /dev/null
+++ b/chromium/extensions/browser/api/usb/usb_api.h
@@ -0,0 +1,378 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_USB_USB_API_H_
+#define EXTENSIONS_BROWSER_API_USB_USB_API_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "device/usb/usb_device.h"
+#include "device/usb/usb_device_filter.h"
+#include "device/usb/usb_device_handle.h"
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/common/api/usb.h"
+#include "net/base/io_buffer.h"
+
+namespace extensions {
+
+class DevicePermissionEntry;
+class DevicePermissions;
+class DevicePermissionsPrompt;
+class DevicePermissionsManager;
+class UsbDeviceResource;
+
+class UsbPermissionCheckingFunction : public UIThreadExtensionFunction {
+ protected:
+ UsbPermissionCheckingFunction();
+ ~UsbPermissionCheckingFunction() override;
+
+ bool HasDevicePermission(scoped_refptr<device::UsbDevice> device);
+ void RecordDeviceLastUsed();
+
+ private:
+ DevicePermissionsManager* device_permissions_manager_;
+ scoped_refptr<DevicePermissionEntry> permission_entry_;
+};
+
+class UsbConnectionFunction : public UIThreadExtensionFunction {
+ protected:
+ UsbConnectionFunction();
+ ~UsbConnectionFunction() override;
+
+ scoped_refptr<device::UsbDeviceHandle> GetDeviceHandle(
+ const extensions::api::usb::ConnectionHandle& handle);
+ void ReleaseDeviceHandle(
+ const extensions::api::usb::ConnectionHandle& handle);
+};
+
+class UsbTransferFunction : public UsbConnectionFunction {
+ protected:
+ UsbTransferFunction();
+ ~UsbTransferFunction() override;
+
+ void OnCompleted(device::UsbTransferStatus status,
+ scoped_refptr<net::IOBuffer> data,
+ size_t length);
+};
+
+class UsbFindDevicesFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("usb.findDevices", USB_FINDDEVICES)
+
+ UsbFindDevicesFunction();
+
+ private:
+ ~UsbFindDevicesFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ void OnGetDevicesComplete(
+ const std::vector<scoped_refptr<device::UsbDevice>>& devices);
+ void OnDeviceOpened(scoped_refptr<device::UsbDeviceHandle> device_handle);
+ void OpenComplete();
+
+ uint16_t vendor_id_;
+ uint16_t product_id_;
+ scoped_ptr<base::ListValue> result_;
+ base::Closure barrier_;
+
+ DISALLOW_COPY_AND_ASSIGN(UsbFindDevicesFunction);
+};
+
+class UsbGetDevicesFunction : public UsbPermissionCheckingFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("usb.getDevices", USB_GETDEVICES)
+
+ UsbGetDevicesFunction();
+
+ private:
+ ~UsbGetDevicesFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ void OnGetDevicesComplete(
+ const std::vector<scoped_refptr<device::UsbDevice>>& devices);
+
+ std::vector<device::UsbDeviceFilter> filters_;
+
+ DISALLOW_COPY_AND_ASSIGN(UsbGetDevicesFunction);
+};
+
+class UsbGetUserSelectedDevicesFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("usb.getUserSelectedDevices",
+ USB_GETUSERSELECTEDDEVICES)
+
+ UsbGetUserSelectedDevicesFunction();
+
+ private:
+ ~UsbGetUserSelectedDevicesFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ void OnDevicesChosen(
+ const std::vector<scoped_refptr<device::UsbDevice>>& devices);
+
+ scoped_ptr<DevicePermissionsPrompt> prompt_;
+
+ DISALLOW_COPY_AND_ASSIGN(UsbGetUserSelectedDevicesFunction);
+};
+
+class UsbGetConfigurationsFunction : public UsbPermissionCheckingFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("usb.getConfigurations", USB_GETCONFIGURATIONS);
+
+ UsbGetConfigurationsFunction();
+
+ private:
+ ~UsbGetConfigurationsFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ DISALLOW_COPY_AND_ASSIGN(UsbGetConfigurationsFunction);
+};
+
+class UsbRequestAccessFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("usb.requestAccess", USB_REQUESTACCESS)
+
+ UsbRequestAccessFunction();
+
+ private:
+ ~UsbRequestAccessFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ DISALLOW_COPY_AND_ASSIGN(UsbRequestAccessFunction);
+};
+
+class UsbOpenDeviceFunction : public UsbPermissionCheckingFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("usb.openDevice", USB_OPENDEVICE)
+
+ UsbOpenDeviceFunction();
+
+ private:
+ ~UsbOpenDeviceFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ void OnDeviceOpened(scoped_refptr<device::UsbDeviceHandle> device_handle);
+
+ DISALLOW_COPY_AND_ASSIGN(UsbOpenDeviceFunction);
+};
+
+class UsbSetConfigurationFunction : public UsbConnectionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("usb.setConfiguration", USB_SETCONFIGURATION)
+
+ UsbSetConfigurationFunction();
+
+ private:
+ ~UsbSetConfigurationFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ void OnComplete(bool success);
+
+ DISALLOW_COPY_AND_ASSIGN(UsbSetConfigurationFunction);
+};
+
+class UsbGetConfigurationFunction : public UsbConnectionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("usb.getConfiguration", USB_GETCONFIGURATION)
+
+ UsbGetConfigurationFunction();
+
+ private:
+ ~UsbGetConfigurationFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ DISALLOW_COPY_AND_ASSIGN(UsbGetConfigurationFunction);
+};
+
+class UsbListInterfacesFunction : public UsbConnectionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("usb.listInterfaces", USB_LISTINTERFACES)
+
+ UsbListInterfacesFunction();
+
+ private:
+ ~UsbListInterfacesFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ DISALLOW_COPY_AND_ASSIGN(UsbListInterfacesFunction);
+};
+
+class UsbCloseDeviceFunction : public UsbConnectionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("usb.closeDevice", USB_CLOSEDEVICE)
+
+ UsbCloseDeviceFunction();
+
+ private:
+ ~UsbCloseDeviceFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ DISALLOW_COPY_AND_ASSIGN(UsbCloseDeviceFunction);
+};
+
+class UsbClaimInterfaceFunction : public UsbConnectionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("usb.claimInterface", USB_CLAIMINTERFACE)
+
+ UsbClaimInterfaceFunction();
+
+ private:
+ ~UsbClaimInterfaceFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ void OnComplete(bool success);
+
+ DISALLOW_COPY_AND_ASSIGN(UsbClaimInterfaceFunction);
+};
+
+class UsbReleaseInterfaceFunction : public UsbConnectionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("usb.releaseInterface", USB_RELEASEINTERFACE)
+
+ UsbReleaseInterfaceFunction();
+
+ private:
+ ~UsbReleaseInterfaceFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ void OnComplete(bool success);
+
+ DISALLOW_COPY_AND_ASSIGN(UsbReleaseInterfaceFunction);
+};
+
+class UsbSetInterfaceAlternateSettingFunction : public UsbConnectionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("usb.setInterfaceAlternateSetting",
+ USB_SETINTERFACEALTERNATESETTING)
+
+ UsbSetInterfaceAlternateSettingFunction();
+
+ private:
+ ~UsbSetInterfaceAlternateSettingFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ void OnComplete(bool success);
+
+ DISALLOW_COPY_AND_ASSIGN(UsbSetInterfaceAlternateSettingFunction);
+};
+
+class UsbControlTransferFunction : public UsbTransferFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("usb.controlTransfer", USB_CONTROLTRANSFER)
+
+ UsbControlTransferFunction();
+
+ private:
+ ~UsbControlTransferFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ DISALLOW_COPY_AND_ASSIGN(UsbControlTransferFunction);
+};
+
+class UsbBulkTransferFunction : public UsbTransferFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("usb.bulkTransfer", USB_BULKTRANSFER)
+
+ UsbBulkTransferFunction();
+
+ private:
+ ~UsbBulkTransferFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ DISALLOW_COPY_AND_ASSIGN(UsbBulkTransferFunction);
+};
+
+class UsbInterruptTransferFunction : public UsbTransferFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("usb.interruptTransfer", USB_INTERRUPTTRANSFER)
+
+ UsbInterruptTransferFunction();
+
+ private:
+ ~UsbInterruptTransferFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ DISALLOW_COPY_AND_ASSIGN(UsbInterruptTransferFunction);
+};
+
+class UsbIsochronousTransferFunction : public UsbConnectionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("usb.isochronousTransfer", USB_ISOCHRONOUSTRANSFER)
+
+ UsbIsochronousTransferFunction();
+
+ private:
+ ~UsbIsochronousTransferFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ void OnCompleted(
+ scoped_refptr<net::IOBuffer> data,
+ const std::vector<device::UsbDeviceHandle::IsochronousPacket>& packets);
+
+ DISALLOW_COPY_AND_ASSIGN(UsbIsochronousTransferFunction);
+};
+
+class UsbResetDeviceFunction : public UsbConnectionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("usb.resetDevice", USB_RESETDEVICE)
+
+ UsbResetDeviceFunction();
+
+ private:
+ ~UsbResetDeviceFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ void OnComplete(bool success);
+
+ scoped_ptr<extensions::api::usb::ResetDevice::Params> parameters_;
+
+ DISALLOW_COPY_AND_ASSIGN(UsbResetDeviceFunction);
+};
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_USB_USB_API_H_
diff --git a/chromium/extensions/browser/api/usb/usb_apitest.cc b/chromium/extensions/browser/api/usb/usb_apitest.cc
new file mode 100644
index 00000000000..887ae2170fb
--- /dev/null
+++ b/chromium/extensions/browser/api/usb/usb_apitest.cc
@@ -0,0 +1,285 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <numeric>
+
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/test_utils.h"
+#include "device/core/mock_device_client.h"
+#include "device/usb/mock_usb_device.h"
+#include "device/usb/mock_usb_device_handle.h"
+#include "device/usb/mock_usb_service.h"
+#include "extensions/browser/api/device_permissions_prompt.h"
+#include "extensions/browser/api/usb/usb_api.h"
+#include "extensions/shell/browser/shell_extensions_api_client.h"
+#include "extensions/shell/test/shell_apitest.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "net/base/io_buffer.h"
+
+using testing::_;
+using testing::AnyNumber;
+using testing::Invoke;
+using testing::Return;
+using device::MockDeviceClient;
+using device::MockUsbDevice;
+using device::MockUsbDeviceHandle;
+using device::UsbConfigDescriptor;
+using device::UsbDeviceHandle;
+using device::UsbEndpointDirection;
+using device::UsbInterfaceDescriptor;
+
+namespace extensions {
+
+namespace {
+
+ACTION_TEMPLATE(InvokeCallback,
+ HAS_1_TEMPLATE_PARAMS(int, k),
+ AND_1_VALUE_PARAMS(p1)) {
+ ::std::tr1::get<k>(args).Run(p1);
+}
+
+ACTION_TEMPLATE(InvokeUsbTransferCallback,
+ HAS_1_TEMPLATE_PARAMS(int, k),
+ AND_1_VALUE_PARAMS(p1)) {
+ net::IOBuffer* io_buffer = new net::IOBuffer(1);
+ memset(io_buffer->data(), 0, 1); // Avoid uninitialized reads.
+ ::std::tr1::get<k>(args).Run(p1, io_buffer, 1);
+}
+
+ACTION_P2(InvokeUsbIsochronousTransferOutCallback,
+ transferred_length,
+ success_packets) {
+ std::vector<UsbDeviceHandle::IsochronousPacket> packets(arg2.size());
+ for (size_t i = 0; i < packets.size(); ++i) {
+ packets[i].length = arg2[i];
+ if (i < success_packets) {
+ packets[i].transferred_length = transferred_length;
+ packets[i].status = device::USB_TRANSFER_COMPLETED;
+ } else {
+ packets[i].transferred_length = 0;
+ packets[i].status = device::USB_TRANSFER_ERROR;
+ }
+ }
+ arg4.Run(arg1, packets);
+}
+
+ACTION_P2(InvokeUsbIsochronousTransferInCallback,
+ transferred_length,
+ success_packets) {
+ size_t total_length = std::accumulate(arg1.begin(), arg1.end(), 0u);
+ net::IOBuffer* io_buffer = new net::IOBuffer(total_length);
+ memset(io_buffer->data(), 0, total_length); // Avoid uninitialized reads.
+ std::vector<UsbDeviceHandle::IsochronousPacket> packets(arg1.size());
+ for (size_t i = 0; i < packets.size(); ++i) {
+ packets[i].length = arg1[i];
+ packets[i].transferred_length = transferred_length;
+ if (i < success_packets) {
+ packets[i].transferred_length = transferred_length;
+ packets[i].status = device::USB_TRANSFER_COMPLETED;
+ } else {
+ packets[i].transferred_length = 0;
+ packets[i].status = device::USB_TRANSFER_ERROR;
+ }
+ }
+ arg3.Run(io_buffer, packets);
+}
+
+class TestDevicePermissionsPrompt
+ : public DevicePermissionsPrompt,
+ public DevicePermissionsPrompt::Prompt::Observer {
+ public:
+ explicit TestDevicePermissionsPrompt(content::WebContents* web_contents)
+ : DevicePermissionsPrompt(web_contents) {}
+
+ void ShowDialog() override { prompt()->SetObserver(this); }
+
+ void OnDevicesChanged() override {
+ for (size_t i = 0; i < prompt()->GetDeviceCount(); ++i) {
+ prompt()->GrantDevicePermission(i);
+ if (!prompt()->multiple()) {
+ break;
+ }
+ }
+ prompt()->Dismissed();
+ }
+};
+
+class TestExtensionsAPIClient : public ShellExtensionsAPIClient {
+ public:
+ TestExtensionsAPIClient() : ShellExtensionsAPIClient() {}
+
+ scoped_ptr<DevicePermissionsPrompt> CreateDevicePermissionsPrompt(
+ content::WebContents* web_contents) const override {
+ return make_scoped_ptr(new TestDevicePermissionsPrompt(web_contents));
+ }
+};
+
+class UsbApiTest : public ShellApiTest {
+ public:
+ void SetUpOnMainThread() override {
+ ShellApiTest::SetUpOnMainThread();
+
+ // MockDeviceClient replaces ShellDeviceClient.
+ device_client_.reset(new MockDeviceClient());
+
+ std::vector<UsbConfigDescriptor> configs;
+ configs.emplace_back(1, false, false, 0);
+ configs.emplace_back(2, false, false, 0);
+
+ mock_device_ = new MockUsbDevice(0, 0, "Test Manufacturer", "Test Device",
+ "ABC123", configs);
+ mock_device_handle_ = new MockUsbDeviceHandle(mock_device_.get());
+ EXPECT_CALL(*mock_device_.get(), Open(_))
+ .WillRepeatedly(InvokeCallback<0>(mock_device_handle_));
+ device_client_->usb_service()->AddDevice(mock_device_);
+ }
+
+ protected:
+ scoped_refptr<MockUsbDeviceHandle> mock_device_handle_;
+ scoped_refptr<MockUsbDevice> mock_device_;
+ scoped_ptr<MockDeviceClient> device_client_;
+};
+
+} // namespace
+
+IN_PROC_BROWSER_TEST_F(UsbApiTest, DeviceHandling) {
+ EXPECT_CALL(*mock_device_.get(), GetActiveConfiguration())
+ .WillOnce(Return(&mock_device_->configurations()[0]));
+ EXPECT_CALL(*mock_device_handle_.get(), Close()).Times(2);
+ ASSERT_TRUE(RunAppTest("api_test/usb/device_handling"));
+}
+
+IN_PROC_BROWSER_TEST_F(UsbApiTest, ResetDevice) {
+ EXPECT_CALL(*mock_device_handle_.get(), Close()).Times(2);
+ EXPECT_CALL(*mock_device_handle_.get(), ResetDevice(_))
+ .WillOnce(InvokeCallback<0>(true))
+ .WillOnce(InvokeCallback<0>(false));
+ EXPECT_CALL(*mock_device_handle_.get(),
+ GenericTransfer(device::USB_DIRECTION_OUTBOUND, 2, _, 1, _, _))
+ .WillOnce(InvokeUsbTransferCallback<5>(device::USB_TRANSFER_COMPLETED));
+ ASSERT_TRUE(RunAppTest("api_test/usb/reset_device"));
+}
+
+IN_PROC_BROWSER_TEST_F(UsbApiTest, SetConfiguration) {
+ UsbConfigDescriptor config_descriptor(1, false, false, 0);
+ EXPECT_CALL(*mock_device_handle_.get(), SetConfiguration(1, _))
+ .WillOnce(InvokeCallback<1>(true));
+ EXPECT_CALL(*mock_device_handle_.get(), Close()).Times(1);
+ EXPECT_CALL(*mock_device_.get(), GetActiveConfiguration())
+ .WillOnce(Return(nullptr))
+ .WillOnce(Return(&config_descriptor));
+ ASSERT_TRUE(RunAppTest("api_test/usb/set_configuration"));
+}
+
+IN_PROC_BROWSER_TEST_F(UsbApiTest, ListInterfaces) {
+ UsbConfigDescriptor config_descriptor(1, false, false, 0);
+ EXPECT_CALL(*mock_device_handle_.get(), Close()).Times(1);
+ EXPECT_CALL(*mock_device_.get(), GetActiveConfiguration())
+ .WillOnce(Return(&config_descriptor));
+ ASSERT_TRUE(RunAppTest("api_test/usb/list_interfaces"));
+}
+
+IN_PROC_BROWSER_TEST_F(UsbApiTest, TransferEvent) {
+ EXPECT_CALL(*mock_device_handle_.get(),
+ ControlTransfer(device::USB_DIRECTION_OUTBOUND,
+ UsbDeviceHandle::STANDARD,
+ UsbDeviceHandle::DEVICE,
+ 1,
+ 2,
+ 3,
+ _,
+ 1,
+ _,
+ _))
+ .WillOnce(InvokeUsbTransferCallback<9>(device::USB_TRANSFER_COMPLETED));
+ EXPECT_CALL(*mock_device_handle_.get(),
+ GenericTransfer(device::USB_DIRECTION_OUTBOUND, 1, _, 1, _, _))
+ .WillOnce(InvokeUsbTransferCallback<5>(device::USB_TRANSFER_COMPLETED));
+ EXPECT_CALL(*mock_device_handle_.get(),
+ GenericTransfer(device::USB_DIRECTION_OUTBOUND, 2, _, 1, _, _))
+ .WillOnce(InvokeUsbTransferCallback<5>(device::USB_TRANSFER_COMPLETED));
+ EXPECT_CALL(*mock_device_handle_.get(), IsochronousTransferOut(3, _, _, _, _))
+ .WillOnce(InvokeUsbIsochronousTransferOutCallback(1, 1u));
+ EXPECT_CALL(*mock_device_handle_.get(), Close()).Times(AnyNumber());
+ ASSERT_TRUE(RunAppTest("api_test/usb/transfer_event"));
+}
+
+IN_PROC_BROWSER_TEST_F(UsbApiTest, ZeroLengthTransfer) {
+ EXPECT_CALL(*mock_device_handle_.get(), GenericTransfer(_, _, _, 0, _, _))
+ .WillOnce(InvokeUsbTransferCallback<5>(device::USB_TRANSFER_COMPLETED));
+ EXPECT_CALL(*mock_device_handle_.get(), Close()).Times(AnyNumber());
+ ASSERT_TRUE(RunAppTest("api_test/usb/zero_length_transfer"));
+}
+
+IN_PROC_BROWSER_TEST_F(UsbApiTest, TransferFailure) {
+ EXPECT_CALL(*mock_device_handle_.get(),
+ GenericTransfer(device::USB_DIRECTION_OUTBOUND, 1, _, _, _, _))
+ .WillOnce(InvokeUsbTransferCallback<5>(device::USB_TRANSFER_COMPLETED))
+ .WillOnce(InvokeUsbTransferCallback<5>(device::USB_TRANSFER_ERROR))
+ .WillOnce(InvokeUsbTransferCallback<5>(device::USB_TRANSFER_TIMEOUT));
+ EXPECT_CALL(*mock_device_handle_.get(), IsochronousTransferIn(2, _, _, _))
+ .WillOnce(InvokeUsbIsochronousTransferInCallback(8, 10u))
+ .WillOnce(InvokeUsbIsochronousTransferInCallback(8, 5u));
+ EXPECT_CALL(*mock_device_handle_.get(), Close()).Times(AnyNumber());
+ ASSERT_TRUE(RunAppTest("api_test/usb/transfer_failure"));
+}
+
+IN_PROC_BROWSER_TEST_F(UsbApiTest, InvalidLengthTransfer) {
+ EXPECT_CALL(*mock_device_handle_.get(), Close()).Times(AnyNumber());
+ ASSERT_TRUE(RunAppTest("api_test/usb/invalid_length_transfer"));
+}
+
+IN_PROC_BROWSER_TEST_F(UsbApiTest, InvalidTimeout) {
+ EXPECT_CALL(*mock_device_handle_.get(), Close()).Times(AnyNumber());
+ ASSERT_TRUE(RunAppTest("api_test/usb/invalid_timeout"));
+}
+
+IN_PROC_BROWSER_TEST_F(UsbApiTest, OnDeviceAdded) {
+ ExtensionTestMessageListener load_listener("loaded", false);
+ ExtensionTestMessageListener result_listener("success", false);
+ result_listener.set_failure_message("failure");
+
+ ASSERT_TRUE(LoadApp("api_test/usb/add_event"));
+ ASSERT_TRUE(load_listener.WaitUntilSatisfied());
+
+ scoped_refptr<MockUsbDevice> device(new MockUsbDevice(0x18D1, 0x58F0));
+ device_client_->usb_service()->AddDevice(device);
+
+ device = new MockUsbDevice(0x18D1, 0x58F1);
+ device_client_->usb_service()->AddDevice(device);
+
+ ASSERT_TRUE(result_listener.WaitUntilSatisfied());
+}
+
+IN_PROC_BROWSER_TEST_F(UsbApiTest, OnDeviceRemoved) {
+ ExtensionTestMessageListener load_listener("loaded", false);
+ ExtensionTestMessageListener result_listener("success", false);
+ result_listener.set_failure_message("failure");
+
+ ASSERT_TRUE(LoadApp("api_test/usb/remove_event"));
+ ASSERT_TRUE(load_listener.WaitUntilSatisfied());
+
+ device_client_->usb_service()->RemoveDevice(mock_device_);
+ ASSERT_TRUE(result_listener.WaitUntilSatisfied());
+}
+
+IN_PROC_BROWSER_TEST_F(UsbApiTest, GetUserSelectedDevices) {
+ ExtensionTestMessageListener ready_listener("opened_device", false);
+ ExtensionTestMessageListener result_listener("success", false);
+ result_listener.set_failure_message("failure");
+
+ EXPECT_CALL(*mock_device_handle_.get(), Close()).Times(1);
+
+ TestExtensionsAPIClient test_api_client;
+ ASSERT_TRUE(LoadApp("api_test/usb/get_user_selected_devices"));
+ ASSERT_TRUE(ready_listener.WaitUntilSatisfied());
+
+ device_client_->usb_service()->RemoveDevice(mock_device_);
+ ASSERT_TRUE(result_listener.WaitUntilSatisfied());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/usb/usb_device_resource.cc b/chromium/extensions/browser/api/usb/usb_device_resource.cc
new file mode 100644
index 00000000000..d984867aa70
--- /dev/null
+++ b/chromium/extensions/browser/api/usb/usb_device_resource.cc
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/usb/usb_device_resource.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/lazy_instance.h"
+#include "base/synchronization/lock.h"
+#include "content/public/browser/browser_thread.h"
+#include "device/usb/usb_device_handle.h"
+#include "extensions/browser/api/api_resource.h"
+#include "extensions/common/api/usb.h"
+
+using content::BrowserThread;
+using device::UsbDeviceHandle;
+
+namespace extensions {
+
+static base::LazyInstance<
+ BrowserContextKeyedAPIFactory<ApiResourceManager<UsbDeviceResource> > >
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+template <>
+BrowserContextKeyedAPIFactory<ApiResourceManager<UsbDeviceResource> >*
+ApiResourceManager<UsbDeviceResource>::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+UsbDeviceResource::UsbDeviceResource(const std::string& owner_extension_id,
+ scoped_refptr<UsbDeviceHandle> device)
+ : ApiResource(owner_extension_id), device_(device) {
+}
+
+UsbDeviceResource::~UsbDeviceResource() {
+ device_->Close();
+}
+
+bool UsbDeviceResource::IsPersistent() const {
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/usb/usb_device_resource.h b/chromium/extensions/browser/api/usb/usb_device_resource.h
new file mode 100644
index 00000000000..e1acf47d42a
--- /dev/null
+++ b/chromium/extensions/browser/api/usb/usb_device_resource.h
@@ -0,0 +1,49 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_USB_USB_DEVICE_RESOURCE_H_
+#define EXTENSIONS_BROWSER_API_USB_USB_DEVICE_RESOURCE_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "content/public/browser/browser_thread.h"
+#include "device/usb/usb_device_handle.h"
+#include "extensions/browser/api/api_resource.h"
+#include "extensions/browser/api/api_resource_manager.h"
+
+namespace usb_service {
+class UsbDeviceHandle;
+}
+
+namespace extensions {
+
+// A UsbDeviceResource is an ApiResource wrapper for a UsbDevice.
+class UsbDeviceResource : public ApiResource {
+ public:
+ static const content::BrowserThread::ID kThreadId =
+ content::BrowserThread::UI;
+
+ UsbDeviceResource(const std::string& owner_extension_id,
+ scoped_refptr<device::UsbDeviceHandle> device);
+ ~UsbDeviceResource() override;
+
+ scoped_refptr<device::UsbDeviceHandle> device() { return device_; }
+
+ bool IsPersistent() const override;
+
+ private:
+ friend class ApiResourceManager<UsbDeviceResource>;
+ static const char* service_name() { return "UsbDeviceResourceManager"; }
+
+ scoped_refptr<device::UsbDeviceHandle> device_;
+
+ DISALLOW_COPY_AND_ASSIGN(UsbDeviceResource);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_USB_USB_DEVICE_RESOURCE_H_
diff --git a/chromium/extensions/browser/api/usb/usb_event_router.cc b/chromium/extensions/browser/api/usb/usb_event_router.cc
new file mode 100644
index 00000000000..8c8acdd4a3d
--- /dev/null
+++ b/chromium/extensions/browser/api/usb/usb_event_router.cc
@@ -0,0 +1,134 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/usb/usb_event_router.h"
+
+#include <utility>
+
+#include "device/core/device_client.h"
+#include "device/usb/usb_device.h"
+#include "extensions/browser/api/device_permissions_manager.h"
+#include "extensions/browser/api/usb/usb_guid_map.h"
+#include "extensions/browser/event_router_factory.h"
+#include "extensions/common/api/usb.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/permissions/usb_device_permission.h"
+
+namespace usb = extensions::api::usb;
+
+using content::BrowserThread;
+using device::UsbDevice;
+using device::UsbService;
+
+namespace extensions {
+
+namespace {
+
+// Returns true iff the given extension has permission to receive events
+// regarding this device.
+bool WillDispatchDeviceEvent(scoped_refptr<UsbDevice> device,
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ Event* event,
+ const base::DictionaryValue* listener_filter) {
+ // Check install-time and optional permissions.
+ UsbDevicePermission::CheckParam param(
+ device->vendor_id(), device->product_id(),
+ UsbDevicePermissionData::UNSPECIFIED_INTERFACE);
+ if (extension->permissions_data()->CheckAPIPermissionWithParam(
+ APIPermission::kUsbDevice, &param)) {
+ return true;
+ }
+
+ // Check permissions granted through chrome.usb.getUserSelectedDevices.
+ DevicePermissions* device_permissions =
+ DevicePermissionsManager::Get(browser_context)
+ ->GetForExtension(extension->id());
+ if (device_permissions->FindUsbDeviceEntry(device).get()) {
+ return true;
+ }
+
+ return false;
+}
+
+base::LazyInstance<BrowserContextKeyedAPIFactory<UsbEventRouter>>::Leaky
+ g_event_router_factory = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// static
+BrowserContextKeyedAPIFactory<UsbEventRouter>*
+UsbEventRouter::GetFactoryInstance() {
+ return g_event_router_factory.Pointer();
+}
+
+UsbEventRouter::UsbEventRouter(content::BrowserContext* browser_context)
+ : browser_context_(browser_context), observer_(this) {
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ if (event_router) {
+ event_router->RegisterObserver(this, usb::OnDeviceAdded::kEventName);
+ event_router->RegisterObserver(this, usb::OnDeviceRemoved::kEventName);
+ }
+}
+
+UsbEventRouter::~UsbEventRouter() {
+}
+
+void UsbEventRouter::Shutdown() {
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ if (event_router) {
+ event_router->UnregisterObserver(this);
+ }
+}
+
+void UsbEventRouter::OnListenerAdded(const EventListenerInfo& details) {
+ UsbService* service = device::DeviceClient::Get()->GetUsbService();
+ if (!observer_.IsObserving(service)) {
+ observer_.Add(service);
+ }
+}
+
+void UsbEventRouter::OnDeviceAdded(scoped_refptr<device::UsbDevice> device) {
+ DispatchEvent(usb::OnDeviceAdded::kEventName, device);
+}
+
+void UsbEventRouter::OnDeviceRemoved(scoped_refptr<device::UsbDevice> device) {
+ DispatchEvent(usb::OnDeviceRemoved::kEventName, device);
+}
+
+void UsbEventRouter::DispatchEvent(const std::string& event_name,
+ scoped_refptr<UsbDevice> device) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ if (event_router) {
+ usb::Device device_obj;
+ UsbGuidMap::Get(browser_context_)->GetApiDevice(device, &device_obj);
+
+ scoped_ptr<Event> event;
+ if (event_name == usb::OnDeviceAdded::kEventName) {
+ event.reset(new Event(events::USB_ON_DEVICE_ADDED,
+ usb::OnDeviceAdded::kEventName,
+ usb::OnDeviceAdded::Create(device_obj)));
+ } else {
+ DCHECK(event_name == usb::OnDeviceRemoved::kEventName);
+ event.reset(new Event(events::USB_ON_DEVICE_REMOVED,
+ usb::OnDeviceRemoved::kEventName,
+ usb::OnDeviceRemoved::Create(device_obj)));
+ }
+
+ event->will_dispatch_callback =
+ base::Bind(&WillDispatchDeviceEvent, device);
+ event_router->BroadcastEvent(std::move(event));
+ }
+}
+
+template <>
+void BrowserContextKeyedAPIFactory<
+ UsbEventRouter>::DeclareFactoryDependencies() {
+ DependsOn(DevicePermissionsManagerFactory::GetInstance());
+ DependsOn(EventRouterFactory::GetInstance());
+ DependsOn(UsbGuidMap::GetFactoryInstance());
+}
+
+} // extensions
diff --git a/chromium/extensions/browser/api/usb/usb_event_router.h b/chromium/extensions/browser/api/usb/usb_event_router.h
new file mode 100644
index 00000000000..bebdad54b76
--- /dev/null
+++ b/chromium/extensions/browser/api/usb/usb_event_router.h
@@ -0,0 +1,66 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_USB_USB_EVENT_ROUTER_H_
+#define EXTENSIONS_BROWSER_API_USB_USB_EVENT_ROUTER_H_
+
+#include "base/macros.h"
+#include "content/public/browser/browser_thread.h"
+#include "device/usb/usb_service.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/event_router.h"
+
+namespace device {
+class UsbDevice;
+}
+
+namespace extensions {
+
+// A BrowserContext-scoped object which is registered as an observer of the
+// EventRouter and UsbService in order to generate device add/remove events.
+class UsbEventRouter : public BrowserContextKeyedAPI,
+ public EventRouter::Observer,
+ public device::UsbService::Observer {
+ public:
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<UsbEventRouter>* GetFactoryInstance();
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<UsbEventRouter>;
+
+ explicit UsbEventRouter(content::BrowserContext* context);
+ ~UsbEventRouter() override;
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "UsbEventRouter"; }
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ // KeyedService implementation.
+ void Shutdown() override;
+
+ // EventRouter::Observer implementation.
+ void OnListenerAdded(const EventListenerInfo& details) override;
+
+ // UsbService::Observer implementation.
+ void OnDeviceAdded(scoped_refptr<device::UsbDevice> device) override;
+ void OnDeviceRemoved(scoped_refptr<device::UsbDevice> device) override;
+
+ // Broadcasts a device add or remove event for the given device.
+ void DispatchEvent(const std::string& event_name,
+ scoped_refptr<device::UsbDevice> device);
+
+ content::BrowserContext* const browser_context_;
+
+ ScopedObserver<device::UsbService, device::UsbService::Observer> observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(UsbEventRouter);
+};
+
+template <>
+void BrowserContextKeyedAPIFactory<
+ UsbEventRouter>::DeclareFactoryDependencies();
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_USB_USB_EVENT_ROUTER_H_
diff --git a/chromium/extensions/browser/api/usb/usb_guid_map.cc b/chromium/extensions/browser/api/usb/usb_guid_map.cc
new file mode 100644
index 00000000000..8d65d07aa4c
--- /dev/null
+++ b/chromium/extensions/browser/api/usb/usb_guid_map.cc
@@ -0,0 +1,86 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/usb/usb_guid_map.h"
+
+#include <utility>
+
+#include "base/lazy_instance.h"
+#include "base/strings/utf_string_conversions.h"
+#include "device/core/device_client.h"
+#include "device/usb/usb_device.h"
+#include "device/usb/usb_service.h"
+#include "extensions/common/api/usb.h"
+
+namespace extensions {
+
+namespace {
+
+base::LazyInstance<BrowserContextKeyedAPIFactory<UsbGuidMap>>::Leaky
+ g_usb_guid_map_factory = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// static
+UsbGuidMap* UsbGuidMap::Get(content::BrowserContext* browser_context) {
+ return BrowserContextKeyedAPIFactory<UsbGuidMap>::Get(browser_context);
+}
+
+// static
+BrowserContextKeyedAPIFactory<UsbGuidMap>* UsbGuidMap::GetFactoryInstance() {
+ return g_usb_guid_map_factory.Pointer();
+}
+
+int UsbGuidMap::GetIdFromGuid(const std::string& guid) {
+ auto iter = guid_to_id_map_.find(guid);
+ if (iter == guid_to_id_map_.end()) {
+ auto result = guid_to_id_map_.insert(std::make_pair(guid, next_id_++));
+ DCHECK(result.second);
+ iter = result.first;
+ id_to_guid_map_.insert(std::make_pair(iter->second, guid));
+ }
+ return iter->second;
+}
+
+bool UsbGuidMap::GetGuidFromId(int id, std::string* guid) {
+ auto iter = id_to_guid_map_.find(id);
+ if (iter == id_to_guid_map_.end())
+ return false;
+ *guid = iter->second;
+ return true;
+}
+
+void UsbGuidMap::GetApiDevice(scoped_refptr<const device::UsbDevice> device_in,
+ extensions::api::usb::Device* device_out) {
+ device_out->device = GetIdFromGuid(device_in->guid());
+ device_out->vendor_id = device_in->vendor_id();
+ device_out->product_id = device_in->product_id();
+ device_out->version = device_in->device_version();
+ device_out->product_name = base::UTF16ToUTF8(device_in->product_string());
+ device_out->manufacturer_name =
+ base::UTF16ToUTF8(device_in->manufacturer_string());
+ device_out->serial_number = base::UTF16ToUTF8(device_in->serial_number());
+}
+
+UsbGuidMap::UsbGuidMap(content::BrowserContext* browser_context)
+ : browser_context_(browser_context), observer_(this) {
+ device::UsbService* service = device::DeviceClient::Get()->GetUsbService();
+ DCHECK(service);
+ observer_.Add(service);
+}
+
+UsbGuidMap::~UsbGuidMap() {
+}
+
+void UsbGuidMap::OnDeviceRemovedCleanup(
+ scoped_refptr<device::UsbDevice> device) {
+ auto iter = guid_to_id_map_.find(device->guid());
+ if (iter != guid_to_id_map_.end()) {
+ int id = iter->second;
+ guid_to_id_map_.erase(iter);
+ id_to_guid_map_.erase(id);
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/usb/usb_guid_map.h b/chromium/extensions/browser/api/usb/usb_guid_map.h
new file mode 100644
index 00000000000..78afa6d65fd
--- /dev/null
+++ b/chromium/extensions/browser/api/usb/usb_guid_map.h
@@ -0,0 +1,75 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_USB_USB_GUID_MAP_H_
+#define EXTENSIONS_BROWSER_API_USB_USB_GUID_MAP_H_
+
+#include <map>
+#include <string>
+
+#include "base/macros.h"
+#include "base/scoped_observer.h"
+#include "device/usb/usb_service.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/common/api/usb.h"
+
+namespace device {
+class UsbDevice;
+}
+
+namespace extensions {
+
+// A BrowserContext-scoped object which maps USB device GUIDs to legacy integer
+// IDs for use with the extensions API. This observes device removal to keep
+// the mapping from growing indefinitely.
+class UsbGuidMap : public BrowserContextKeyedAPI,
+ public device::UsbService::Observer {
+ public:
+ static UsbGuidMap* Get(content::BrowserContext* browser_context);
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<UsbGuidMap>* GetFactoryInstance();
+
+ // Returns an ID for this device GUID. If the GUID is unknown to the
+ // UsbGuidMap a new ID is generated for it.
+ int GetIdFromGuid(const std::string& guid);
+
+ // Looks up a device GUID for a given extensions USB device ID. If the ID is
+ // unknown (e.g., the corresponding device was unplugged), this returns
+ // |false|; otherwise it returns |true|.
+ bool GetGuidFromId(int id, std::string* guid);
+
+ // Populates an instance of the chrome.usb.Device object from the given
+ // device.
+ void GetApiDevice(scoped_refptr<const device::UsbDevice> device_in,
+ extensions::api::usb::Device* device_out);
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<UsbGuidMap>;
+
+ explicit UsbGuidMap(content::BrowserContext* context);
+ ~UsbGuidMap() override;
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "UsbGuidMap"; }
+ static const bool kServiceIsCreatedWithBrowserContext = false;
+ static const bool kServiceRedirectedInIncognito = true;
+
+ // UsbService::Observer implementation.
+ void OnDeviceRemovedCleanup(scoped_refptr<device::UsbDevice> device) override;
+
+ content::BrowserContext* const browser_context_;
+
+ int next_id_ = 0;
+ std::map<std::string, int> guid_to_id_map_;
+ std::map<int, std::string> id_to_guid_map_;
+
+ ScopedObserver<device::UsbService, device::UsbService::Observer> observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(UsbGuidMap);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_USB_USB_GUID_MAP_H_
diff --git a/chromium/extensions/browser/api/usb/usb_manual_apitest.cc b/chromium/extensions/browser/api/usb/usb_manual_apitest.cc
new file mode 100644
index 00000000000..d28be9d390a
--- /dev/null
+++ b/chromium/extensions/browser/api/usb/usb_manual_apitest.cc
@@ -0,0 +1,18 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/api/permissions/permissions_api.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+
+namespace {
+
+class UsbManualApiTest : public ExtensionApiTest {};
+
+} // namespace
+
+IN_PROC_BROWSER_TEST_F(UsbManualApiTest, MANUAL_ListInterfaces) {
+ extensions::PermissionsRequestFunction::SetIgnoreUserGestureForTests(true);
+ extensions::PermissionsRequestFunction::SetAutoConfirmForTests(true);
+ ASSERT_TRUE(RunExtensionTest("usb_manual/list_interfaces"));
+}
diff --git a/chromium/extensions/browser/api/virtual_keyboard_private/OWNERS b/chromium/extensions/browser/api/virtual_keyboard_private/OWNERS
new file mode 100644
index 00000000000..dcaae5e44b5
--- /dev/null
+++ b/chromium/extensions/browser/api/virtual_keyboard_private/OWNERS
@@ -0,0 +1,2 @@
+bshe@chromium.org
+kevers@chromium.org \ No newline at end of file
diff --git a/chromium/extensions/browser/api/virtual_keyboard_private/virtual_keyboard_delegate.h b/chromium/extensions/browser/api/virtual_keyboard_private/virtual_keyboard_delegate.h
new file mode 100644
index 00000000000..4d1452e3ca7
--- /dev/null
+++ b/chromium/extensions/browser/api/virtual_keyboard_private/virtual_keyboard_delegate.h
@@ -0,0 +1,71 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_VIRTUAL_KEYBOARD_VIRTUAL_KEYBOARD_DELEGATE_H_
+#define EXTENSIONS_BROWSER_API_VIRTUAL_KEYBOARD_VIRTUAL_KEYBOARD_DELEGATE_H_
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "base/values.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace extensions {
+
+class VirtualKeyboardDelegate {
+ public:
+ virtual ~VirtualKeyboardDelegate() {}
+
+ // Fetch information about the preferred configuration of the keyboard. On
+ // exit, |settings| is populated with the keyboard configuration. Returns true
+ // if successful.
+ virtual bool GetKeyboardConfig(base::DictionaryValue* settings) = 0;
+
+ // Dismiss the virtual keyboard without changing input focus. Returns true if
+ // successful.
+ virtual bool HideKeyboard() = 0;
+
+ // Insert |text| verbatim into a text area. Returns true if successful.
+ virtual bool InsertText(const base::string16& text) = 0;
+
+ // Notifiy system that keyboard loading is complete. Used in UMA stats to
+ // track loading performance. Returns true if the notification was handled.
+ virtual bool OnKeyboardLoaded() = 0;
+
+ // Indicate if settings are accessible and enabled based on current state.
+ // For example, settings should be blocked when the session is locked.
+ virtual bool IsLanguageSettingsEnabled() = 0;
+
+ // Sets the state of the hotrod virtual keyboad.
+ virtual void SetHotrodKeyboard(bool enable) = 0;
+
+ // Activate and lock the virtual keyboad on screen or dismiss the keyboard
+ // regardless of the state of text focus. Used in a11y mode to allow typing
+ // hotkeys without the need for text focus. Returns true if successful.
+ virtual bool LockKeyboard(bool state) = 0;
+
+ // Dispatches a virtual key event. |type| indicates if the event is a keydown
+ // or keyup event. |char_value| is the unicode value for the key. |key_code|
+ // is the code assigned to the key, which is independent of the state of
+ // modifier keys. |key_name| is the standardized w3c name for the key.
+ // |modifiers| indicates which modifier keys are active. Returns true if
+ // successful.
+ virtual bool SendKeyEvent(const std::string& type,
+ int char_value,
+ int key_code,
+ const std::string& key_name,
+ int modifiers) = 0;
+
+ // Launches the settings app. Returns true if successful.
+ virtual bool ShowLanguageSettings() = 0;
+
+ // Sets virtual keyboard window mode.
+ virtual bool SetVirtualKeyboardMode(int mode_enum) = 0;
+
+ // Sets requested virtual keyboard state.
+ virtual bool SetRequestedKeyboardState(int state_enum) = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_VIRTUAL_KEYBOARD_VIRTUAL_KEYBOARD_DELEGATE_H_
diff --git a/chromium/extensions/browser/api/virtual_keyboard_private/virtual_keyboard_private_api.cc b/chromium/extensions/browser/api/virtual_keyboard_private/virtual_keyboard_private_api.cc
new file mode 100644
index 00000000000..cb85a9f98f5
--- /dev/null
+++ b/chromium/extensions/browser/api/virtual_keyboard_private/virtual_keyboard_private_api.cc
@@ -0,0 +1,186 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/virtual_keyboard_private/virtual_keyboard_private_api.h"
+
+#include "base/lazy_instance.h"
+#include "base/strings/string16.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/api/virtual_keyboard_private/virtual_keyboard_delegate.h"
+#include "extensions/browser/extension_function_registry.h"
+#include "extensions/common/api/virtual_keyboard_private.h"
+#include "ui/events/event.h"
+
+namespace SetMode = extensions::api::virtual_keyboard_private::SetMode;
+namespace SetRequestedKeyboardState =
+ extensions::api::virtual_keyboard_private::SetKeyboardState;
+
+namespace extensions {
+
+namespace {
+
+const char kNotYetImplementedError[] =
+ "API is not implemented on this platform.";
+const char kVirtualKeyboardNotEnabled[] =
+ "The virtual keyboard is not enabled.";
+
+typedef BrowserContextKeyedAPIFactory<VirtualKeyboardAPI> factory;
+
+VirtualKeyboardDelegate* GetDelegate(SyncExtensionFunction* f) {
+ VirtualKeyboardAPI* api = factory::Get(f->browser_context());
+ DCHECK(api);
+ return api ? api->delegate() : nullptr;
+}
+
+} // namespace
+
+bool VirtualKeyboardPrivateInsertTextFunction::RunSync() {
+ VirtualKeyboardDelegate* delegate = GetDelegate(this);
+ if (delegate) {
+ base::string16 text;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &text));
+ return delegate->InsertText(text);
+ }
+ error_ = kNotYetImplementedError;
+ return false;
+}
+
+bool VirtualKeyboardPrivateSendKeyEventFunction::RunSync() {
+ VirtualKeyboardDelegate* delegate = GetDelegate(this);
+ if (delegate) {
+ base::Value* options_value = nullptr;
+ base::DictionaryValue* params = nullptr;
+ std::string type;
+ int char_value;
+ int key_code;
+ std::string key_name;
+ int modifiers;
+
+ EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &options_value));
+ EXTENSION_FUNCTION_VALIDATE(options_value->GetAsDictionary(&params));
+ EXTENSION_FUNCTION_VALIDATE(params->GetString("type", &type));
+ EXTENSION_FUNCTION_VALIDATE(params->GetInteger("charValue", &char_value));
+ EXTENSION_FUNCTION_VALIDATE(params->GetInteger("keyCode", &key_code));
+ EXTENSION_FUNCTION_VALIDATE(params->GetString("keyName", &key_name));
+ EXTENSION_FUNCTION_VALIDATE(params->GetInteger("modifiers", &modifiers));
+ return delegate->SendKeyEvent(
+ type, char_value, key_code, key_name, modifiers);
+ }
+ error_ = kNotYetImplementedError;
+ return false;
+}
+
+bool VirtualKeyboardPrivateHideKeyboardFunction::RunSync() {
+ VirtualKeyboardDelegate* delegate = GetDelegate(this);
+ if (delegate)
+ return delegate->HideKeyboard();
+ error_ = kNotYetImplementedError;
+ return false;
+}
+
+bool VirtualKeyboardPrivateSetHotrodKeyboardFunction::RunSync() {
+ VirtualKeyboardDelegate* delegate = GetDelegate(this);
+ if (delegate) {
+ bool enable;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(0, &enable));
+ delegate->SetHotrodKeyboard(enable);
+ return true;
+ }
+ error_ = kNotYetImplementedError;
+ return false;
+}
+
+bool VirtualKeyboardPrivateLockKeyboardFunction::RunSync() {
+ VirtualKeyboardDelegate* delegate = GetDelegate(this);
+ if (delegate) {
+ bool lock;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(0, &lock));
+ return delegate->LockKeyboard(lock);
+ }
+ error_ = kNotYetImplementedError;
+ return false;
+}
+
+bool VirtualKeyboardPrivateKeyboardLoadedFunction::RunSync() {
+ VirtualKeyboardDelegate* delegate = GetDelegate(this);
+ if (delegate)
+ return delegate->OnKeyboardLoaded();
+ error_ = kNotYetImplementedError;
+ return false;
+}
+
+bool VirtualKeyboardPrivateGetKeyboardConfigFunction::RunSync() {
+ VirtualKeyboardDelegate* delegate = GetDelegate(this);
+ if (delegate) {
+ base::DictionaryValue* results = new base::DictionaryValue();
+ if (delegate->GetKeyboardConfig(results)) {
+ SetResult(results);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool VirtualKeyboardPrivateOpenSettingsFunction::RunSync() {
+ VirtualKeyboardDelegate* delegate = GetDelegate(this);
+ if (delegate) {
+ if (delegate->IsLanguageSettingsEnabled())
+ return delegate->ShowLanguageSettings();
+ return false;
+ }
+ error_ = kNotYetImplementedError;
+ return false;
+}
+
+bool VirtualKeyboardPrivateSetModeFunction::RunSync() {
+ VirtualKeyboardDelegate* delegate = GetDelegate(this);
+ if (delegate) {
+ scoped_ptr<SetMode::Params> params = SetMode::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+ if (!delegate->SetVirtualKeyboardMode(params->mode)) {
+ error_ = kVirtualKeyboardNotEnabled;
+ return false;
+ } else {
+ return true;
+ }
+ }
+ error_ = kNotYetImplementedError;
+ return false;
+}
+
+bool VirtualKeyboardPrivateSetKeyboardStateFunction::RunSync() {
+ VirtualKeyboardDelegate* delegate = GetDelegate(this);
+ if (delegate) {
+ scoped_ptr<SetRequestedKeyboardState::Params> params =
+ SetRequestedKeyboardState::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+ if (!delegate->SetRequestedKeyboardState(params->state)) {
+ error_ = kVirtualKeyboardNotEnabled;
+ return false;
+ } else {
+ return true;
+ }
+ }
+ error_ = kNotYetImplementedError;
+ return false;
+}
+
+VirtualKeyboardAPI::VirtualKeyboardAPI(content::BrowserContext* context) {
+ delegate_ =
+ extensions::ExtensionsAPIClient::Get()->CreateVirtualKeyboardDelegate();
+}
+
+VirtualKeyboardAPI::~VirtualKeyboardAPI() {
+}
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<VirtualKeyboardAPI>>
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<VirtualKeyboardAPI>*
+VirtualKeyboardAPI::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/virtual_keyboard_private/virtual_keyboard_private_api.h b/chromium/extensions/browser/api/virtual_keyboard_private/virtual_keyboard_private_api.h
new file mode 100644
index 00000000000..ebf0720feaf
--- /dev/null
+++ b/chromium/extensions/browser/api/virtual_keyboard_private/virtual_keyboard_private_api.h
@@ -0,0 +1,173 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_VIRTUAL_KEYBOARD_PRIVATE_VIRTUAL_KEYBOARD_PRIVATE_API_H_
+#define EXTENSIONS_BROWSER_API_VIRTUAL_KEYBOARD_PRIVATE_VIRTUAL_KEYBOARD_PRIVATE_API_H_
+
+#include "base/compiler_specific.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_function.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class VirtualKeyboardPrivateInsertTextFunction : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("virtualKeyboardPrivate.insertText",
+ VIRTUALKEYBOARDPRIVATE_INSERTTEXT);
+
+ protected:
+ ~VirtualKeyboardPrivateInsertTextFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+class VirtualKeyboardPrivateSendKeyEventFunction
+ : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("virtualKeyboardPrivate.sendKeyEvent",
+ VIRTUALKEYBOARDPRIVATE_SENDKEYEVENT);
+
+ protected:
+ ~VirtualKeyboardPrivateSendKeyEventFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+class VirtualKeyboardPrivateHideKeyboardFunction
+ : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("virtualKeyboardPrivate.hideKeyboard",
+ VIRTUALKEYBOARDPRIVATE_HIDEKEYBOARD);
+
+ protected:
+ ~VirtualKeyboardPrivateHideKeyboardFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+class VirtualKeyboardPrivateSetHotrodKeyboardFunction
+ : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("virtualKeyboardPrivate.setHotrodKeyboard",
+ VIRTUALKEYBOARDPRIVATE_SETHOTRODKEYBOARD);
+
+ protected:
+ ~VirtualKeyboardPrivateSetHotrodKeyboardFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+class VirtualKeyboardPrivateLockKeyboardFunction
+ : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("virtualKeyboardPrivate.lockKeyboard",
+ VIRTUALKEYBOARDPRIVATE_LOCKKEYBOARD);
+
+ protected:
+ ~VirtualKeyboardPrivateLockKeyboardFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+class VirtualKeyboardPrivateKeyboardLoadedFunction
+ : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("virtualKeyboardPrivate.keyboardLoaded",
+ VIRTUALKEYBOARDPRIVATE_KEYBOARDLOADED);
+
+ protected:
+ ~VirtualKeyboardPrivateKeyboardLoadedFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+class VirtualKeyboardPrivateGetKeyboardConfigFunction
+ : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("virtualKeyboardPrivate.getKeyboardConfig",
+ VIRTUALKEYBOARDPRIVATE_GETKEYBOARDCONFIG);
+
+ protected:
+ ~VirtualKeyboardPrivateGetKeyboardConfigFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+class VirtualKeyboardPrivateOpenSettingsFunction
+ : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("virtualKeyboardPrivate.openSettings",
+ VIRTUALKEYBOARDPRIVATE_OPENSETTINGS);
+
+ protected:
+ ~VirtualKeyboardPrivateOpenSettingsFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+class VirtualKeyboardPrivateSetModeFunction : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("virtualKeyboardPrivate.setMode",
+ VIRTUALKEYBOARDPRIVATE_SETMODE);
+
+ protected:
+ ~VirtualKeyboardPrivateSetModeFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+class VirtualKeyboardPrivateSetKeyboardStateFunction
+ : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("virtualKeyboardPrivate.setKeyboardState",
+ VIRTUALKEYBOARDPRIVATE_SETKEYBOARDSTATE);
+
+ protected:
+ ~VirtualKeyboardPrivateSetKeyboardStateFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+class VirtualKeyboardDelegate;
+
+class VirtualKeyboardAPI : public BrowserContextKeyedAPI {
+ public:
+ explicit VirtualKeyboardAPI(content::BrowserContext* context);
+ ~VirtualKeyboardAPI() override;
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<VirtualKeyboardAPI>*
+ GetFactoryInstance();
+
+ VirtualKeyboardDelegate* delegate() { return delegate_.get(); }
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<VirtualKeyboardAPI>;
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "VirtualKeyboardAPI"; }
+
+ // Require accces to delegate while incognito or during login.
+ static const bool kServiceHasOwnInstanceInIncognito = true;
+
+ scoped_ptr<VirtualKeyboardDelegate> delegate_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_VIRTUAL_KEYBOARD_PRIVATE_VIRTUAL_KEYBOARD_PRIVATE_API_H_
diff --git a/chromium/extensions/browser/api/vpn_provider/DEPS b/chromium/extensions/browser/api/vpn_provider/DEPS
new file mode 100644
index 00000000000..902385e4533
--- /dev/null
+++ b/chromium/extensions/browser/api/vpn_provider/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+chromeos",
+
+ "+third_party/cros_system_api",
+]
diff --git a/chromium/extensions/browser/api/vpn_provider/OWNERS b/chromium/extensions/browser/api/vpn_provider/OWNERS
new file mode 100644
index 00000000000..c83fa2113aa
--- /dev/null
+++ b/chromium/extensions/browser/api/vpn_provider/OWNERS
@@ -0,0 +1,3 @@
+kaliamoorthi@chromium.org
+bartfab@chromium.org
+emaxx@chromium.org
diff --git a/chromium/extensions/browser/api/vpn_provider/vpn_provider_api.cc b/chromium/extensions/browser/api/vpn_provider/vpn_provider_api.cc
new file mode 100644
index 00000000000..601835db076
--- /dev/null
+++ b/chromium/extensions/browser/api/vpn_provider/vpn_provider_api.cc
@@ -0,0 +1,337 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/vpn_provider/vpn_provider_api.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "extensions/browser/api/vpn_provider/vpn_service.h"
+#include "extensions/browser/api/vpn_provider/vpn_service_factory.h"
+#include "extensions/common/api/vpn_provider.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+namespace extensions {
+
+namespace {
+
+namespace api_vpn = extensions::api::vpn_provider;
+
+const char kCIDRSeperator[] = "/";
+
+bool CheckIPCIDRSanity(const std::string& value, bool cidr, bool ipv6) {
+ int dots = ipv6 ? 0 : 3;
+ int sep = cidr ? 1 : 0;
+ int colon = ipv6 ? 7 : 0;
+ bool hex_allowed = ipv6;
+ int counter = 0;
+
+ for (const auto& elem : value) {
+ if (base::IsAsciiDigit(elem)) {
+ counter++;
+ continue;
+ }
+ if (elem == '.') {
+ if (!dots)
+ return false;
+ dots--;
+ } else if (elem == kCIDRSeperator[0]) {
+ if (!sep || dots || colon == 7 || !counter)
+ return false;
+ // Separator observed, no more dots and colons, only digits are allowed
+ // after observing separator. So setting hex_allowed to false.
+ sep--;
+ counter = 0;
+ colon = 0;
+ hex_allowed = false;
+ } else if (elem == ':') {
+ if (!colon)
+ return false;
+ colon--;
+ } else if (!hex_allowed || !base::IsHexDigit(elem)) {
+ return false;
+ } else {
+ counter++;
+ }
+ }
+ return !sep && !dots && (colon < 7) && counter;
+}
+
+bool CheckIPCIDRSanityList(const std::vector<std::string>& list,
+ bool cidr,
+ bool ipv6) {
+ for (const auto& address : list) {
+ if (!CheckIPCIDRSanity(address, cidr, ipv6)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void ConvertParameters(const api_vpn::Parameters& parameters,
+ base::DictionaryValue* parameter_value,
+ std::string* error) {
+ if (!CheckIPCIDRSanity(parameters.address, true /* CIDR */,
+ false /*IPV4 */)) {
+ *error = "Address CIDR sanity check failed.";
+ return;
+ }
+
+ if (!CheckIPCIDRSanityList(parameters.exclusion_list, true /* CIDR */,
+ false /*IPV4 */)) {
+ *error = "Exclusion list CIDR sanity check failed.";
+ return;
+ }
+
+ if (!CheckIPCIDRSanityList(parameters.inclusion_list, true /* CIDR */,
+ false /*IPV4 */)) {
+ *error = "Inclusion list CIDR sanity check failed.";
+ return;
+ }
+
+ if (!CheckIPCIDRSanityList(parameters.dns_servers, false /* Not CIDR */,
+ false /*IPV4 */)) {
+ *error = "DNS server IP sanity check failed.";
+ return;
+ }
+
+ std::vector<std::string> cidr_parts = base::SplitString(
+ parameters.address, kCIDRSeperator, base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY);
+ CHECK_EQ(2u, cidr_parts.size());
+
+ parameter_value->SetStringWithoutPathExpansion(
+ shill::kAddressParameterThirdPartyVpn, cidr_parts[0]);
+
+ parameter_value->SetStringWithoutPathExpansion(
+ shill::kSubnetPrefixParameterThirdPartyVpn, cidr_parts[1]);
+
+ std::string ip_delimiter(1, shill::kIPDelimiter);
+ parameter_value->SetStringWithoutPathExpansion(
+ shill::kExclusionListParameterThirdPartyVpn,
+ base::JoinString(parameters.exclusion_list, ip_delimiter));
+
+ parameter_value->SetStringWithoutPathExpansion(
+ shill::kInclusionListParameterThirdPartyVpn,
+ base::JoinString(parameters.inclusion_list, ip_delimiter));
+
+ if (parameters.mtu) {
+ parameter_value->SetStringWithoutPathExpansion(
+ shill::kMtuParameterThirdPartyVpn, *parameters.mtu);
+ }
+
+ if (parameters.broadcast_address) {
+ parameter_value->SetStringWithoutPathExpansion(
+ shill::kBroadcastAddressParameterThirdPartyVpn,
+ *parameters.broadcast_address);
+ }
+
+ std::string non_ip_delimiter(1, shill::kNonIPDelimiter);
+ if (parameters.domain_search) {
+ parameter_value->SetStringWithoutPathExpansion(
+ shill::kDomainSearchParameterThirdPartyVpn,
+ base::JoinString(*parameters.domain_search, non_ip_delimiter));
+ }
+
+ parameter_value->SetStringWithoutPathExpansion(
+ shill::kDnsServersParameterThirdPartyVpn,
+ base::JoinString(parameters.dns_servers, ip_delimiter));
+
+ if (parameters.reconnect) {
+ parameter_value->SetStringWithoutPathExpansion(
+ shill::kReconnectParameterThirdPartyVpn, *parameters.reconnect);
+ }
+
+ return;
+}
+
+} // namespace
+
+VpnThreadExtensionFunction::~VpnThreadExtensionFunction() {
+}
+
+void VpnThreadExtensionFunction::SignalCallCompletionSuccess() {
+ Respond(NoArguments());
+}
+
+void VpnThreadExtensionFunction::SignalCallCompletionSuccessWithId(
+ const std::string& configuration_id) {
+ Respond(OneArgument(new base::StringValue(configuration_id)));
+}
+
+void VpnThreadExtensionFunction::SignalCallCompletionSuccessWithWarning(
+ const std::string& warning) {
+ if (!warning.empty()) {
+ WriteToConsole(content::CONSOLE_MESSAGE_LEVEL_WARNING, warning);
+ }
+ Respond(NoArguments());
+}
+
+void VpnThreadExtensionFunction::SignalCallCompletionFailure(
+ const std::string& error_name,
+ const std::string& error_message) {
+ if (!error_name.empty() && !error_message.empty()) {
+ Respond(Error(error_name + ": " + error_message));
+ } else if (!error_name.empty()) {
+ Respond(Error(error_name));
+ } else {
+ Respond(Error(error_message));
+ }
+}
+
+VpnProviderCreateConfigFunction::~VpnProviderCreateConfigFunction() {
+}
+
+ExtensionFunction::ResponseAction VpnProviderCreateConfigFunction::Run() {
+ scoped_ptr<api_vpn::CreateConfig::Params> params(
+ api_vpn::CreateConfig::Params::Create(*args_));
+ if (!params) {
+ return RespondNow(Error("Invalid arguments."));
+ }
+
+ chromeos::VpnService* service =
+ chromeos::VpnServiceFactory::GetForBrowserContext(browser_context());
+ if (!service) {
+ return RespondNow(Error("Invalid profile."));
+ }
+
+ // Use the configuration name as ID. In the future, a different ID scheme may
+ // be used, requiring a mapping between the two.
+ service->CreateConfiguration(
+ extension_id(), extension()->name(), params->name,
+ base::Bind(
+ &VpnProviderCreateConfigFunction::SignalCallCompletionSuccessWithId,
+ this, params->name),
+ base::Bind(&VpnProviderNotifyConnectionStateChangedFunction::
+ SignalCallCompletionFailure,
+ this));
+
+ return RespondLater();
+}
+
+VpnProviderDestroyConfigFunction::~VpnProviderDestroyConfigFunction() {
+}
+
+ExtensionFunction::ResponseAction VpnProviderDestroyConfigFunction::Run() {
+ scoped_ptr<api_vpn::DestroyConfig::Params> params(
+ api_vpn::DestroyConfig::Params::Create(*args_));
+ if (!params) {
+ return RespondNow(Error("Invalid arguments."));
+ }
+
+ chromeos::VpnService* service =
+ chromeos::VpnServiceFactory::GetForBrowserContext(browser_context());
+ if (!service) {
+ return RespondNow(Error("Invalid profile."));
+ }
+
+ service->DestroyConfiguration(
+ extension_id(), params->id,
+ base::Bind(&VpnProviderDestroyConfigFunction::SignalCallCompletionSuccess,
+ this),
+ base::Bind(&VpnProviderNotifyConnectionStateChangedFunction::
+ SignalCallCompletionFailure,
+ this));
+
+ return RespondLater();
+}
+
+VpnProviderSetParametersFunction::~VpnProviderSetParametersFunction() {
+}
+
+ExtensionFunction::ResponseAction VpnProviderSetParametersFunction::Run() {
+ scoped_ptr<api_vpn::SetParameters::Params> params(
+ api_vpn::SetParameters::Params::Create(*args_));
+ if (!params) {
+ return RespondNow(Error("Invalid arguments."));
+ }
+
+ chromeos::VpnService* service =
+ chromeos::VpnServiceFactory::GetForBrowserContext(browser_context());
+ if (!service) {
+ return RespondNow(Error("Invalid profile."));
+ }
+
+ base::DictionaryValue parameter_value;
+ std::string error;
+ ConvertParameters(params->parameters, &parameter_value, &error);
+ if (!error.empty()) {
+ return RespondNow(Error(error));
+ }
+
+ service->SetParameters(
+ extension_id(), parameter_value,
+ base::Bind(&VpnProviderSetParametersFunction::
+ SignalCallCompletionSuccessWithWarning,
+ this),
+ base::Bind(&VpnProviderNotifyConnectionStateChangedFunction::
+ SignalCallCompletionFailure,
+ this));
+
+ return RespondLater();
+}
+
+VpnProviderSendPacketFunction::~VpnProviderSendPacketFunction() {
+}
+
+ExtensionFunction::ResponseAction VpnProviderSendPacketFunction::Run() {
+ scoped_ptr<api_vpn::SendPacket::Params> params(
+ api_vpn::SendPacket::Params::Create(*args_));
+ if (!params) {
+ return RespondNow(Error("Invalid arguments."));
+ }
+
+ chromeos::VpnService* service =
+ chromeos::VpnServiceFactory::GetForBrowserContext(browser_context());
+ if (!service) {
+ return RespondNow(Error("Invalid profile."));
+ }
+
+ service->SendPacket(
+ extension_id(), params->data,
+ base::Bind(&VpnProviderSendPacketFunction::SignalCallCompletionSuccess,
+ this),
+ base::Bind(&VpnProviderNotifyConnectionStateChangedFunction::
+ SignalCallCompletionFailure,
+ this));
+
+ return RespondLater();
+}
+
+VpnProviderNotifyConnectionStateChangedFunction::
+ ~VpnProviderNotifyConnectionStateChangedFunction() {
+}
+
+ExtensionFunction::ResponseAction
+VpnProviderNotifyConnectionStateChangedFunction::Run() {
+ scoped_ptr<api_vpn::NotifyConnectionStateChanged::Params> params(
+ api_vpn::NotifyConnectionStateChanged::Params::Create(*args_));
+ if (!params) {
+ return RespondNow(Error("Invalid arguments."));
+ }
+
+ chromeos::VpnService* service =
+ chromeos::VpnServiceFactory::GetForBrowserContext(browser_context());
+ if (!service) {
+ return RespondNow(Error("Invalid profile."));
+ }
+
+ service->NotifyConnectionStateChanged(
+ extension_id(), params->state,
+ base::Bind(&VpnProviderNotifyConnectionStateChangedFunction::
+ SignalCallCompletionSuccess,
+ this),
+ base::Bind(&VpnProviderNotifyConnectionStateChangedFunction::
+ SignalCallCompletionFailure,
+ this));
+
+ return RespondLater();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/vpn_provider/vpn_provider_api.h b/chromium/extensions/browser/api/vpn_provider/vpn_provider_api.h
new file mode 100644
index 00000000000..3d3bb1deca1
--- /dev/null
+++ b/chromium/extensions/browser/api/vpn_provider/vpn_provider_api.h
@@ -0,0 +1,84 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_API_VPN_PROVIDER_VPN_PROVIDER_API_H_
+#define CHROME_BROWSER_EXTENSIONS_API_VPN_PROVIDER_VPN_PROVIDER_API_H_
+
+#include <string>
+
+#include "extensions/browser/extension_function.h"
+
+namespace extensions {
+
+class VpnThreadExtensionFunction : public UIThreadExtensionFunction {
+ public:
+ void SignalCallCompletionSuccess();
+ void SignalCallCompletionSuccessWithId(const std::string& configuration_id);
+ void SignalCallCompletionSuccessWithWarning(const std::string& warning);
+
+ void SignalCallCompletionFailure(const std::string& error_name,
+ const std::string& error_message);
+
+ protected:
+ ~VpnThreadExtensionFunction() override;
+};
+
+class VpnProviderCreateConfigFunction : public VpnThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("vpnProvider.createConfig",
+ VPNPROVIDER_CREATECONFIG);
+
+ protected:
+ ~VpnProviderCreateConfigFunction() override;
+
+ ExtensionFunction::ResponseAction Run() override;
+};
+
+class VpnProviderDestroyConfigFunction : public VpnThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("vpnProvider.destroyConfig",
+ VPNPROVIDER_DESTROYCONFIG);
+
+ protected:
+ ~VpnProviderDestroyConfigFunction() override;
+
+ ExtensionFunction::ResponseAction Run() override;
+};
+
+class VpnProviderSetParametersFunction : public VpnThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("vpnProvider.setParameters",
+ VPNPROVIDER_SETPARAMETERS);
+
+ protected:
+ ~VpnProviderSetParametersFunction() override;
+
+ ExtensionFunction::ResponseAction Run() override;
+};
+
+class VpnProviderSendPacketFunction : public VpnThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("vpnProvider.sendPacket", VPNPROVIDER_SENDPACKET);
+
+ protected:
+ ~VpnProviderSendPacketFunction() override;
+
+ ExtensionFunction::ResponseAction Run() override;
+};
+
+class VpnProviderNotifyConnectionStateChangedFunction
+ : public VpnThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("vpnProvider.notifyConnectionStateChanged",
+ VPNPROVIDER_NOTIFYCONNECTIONSTATECHANGED);
+
+ protected:
+ ~VpnProviderNotifyConnectionStateChangedFunction() override;
+
+ ExtensionFunction::ResponseAction Run() override;
+};
+
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_API_VPN_PROVIDER_VPN_PROVIDER_API_H_
diff --git a/chromium/extensions/browser/api/vpn_provider/vpn_service.cc b/chromium/extensions/browser/api/vpn_provider/vpn_service.cc
new file mode 100644
index 00000000000..09452d5290f
--- /dev/null
+++ b/chromium/extensions/browser/api/vpn_provider/vpn_service.cc
@@ -0,0 +1,578 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/vpn_provider/vpn_service.h"
+
+#include <stdint.h>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/guid.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "chromeos/dbus/shill_third_party_vpn_driver_client.h"
+#include "chromeos/dbus/shill_third_party_vpn_observer.h"
+#include "chromeos/network/network_configuration_handler.h"
+#include "chromeos/network/network_profile.h"
+#include "chromeos/network/network_profile_handler.h"
+#include "chromeos/network/network_state.h"
+#include "chromeos/network/network_state_handler.h"
+#include "chromeos/network/network_type_pattern.h"
+#include "crypto/sha2.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_registry.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+namespace chromeos {
+
+namespace {
+
+namespace api_vpn = extensions::api::vpn_provider;
+
+void DoNothingFailureCallback(const std::string& error_name,
+ const std::string& error_message) {
+ LOG(ERROR) << error_name << ": " << error_message;
+}
+
+} // namespace
+
+class VpnService::VpnConfiguration : public ShillThirdPartyVpnObserver {
+ public:
+ VpnConfiguration(const std::string& extension_id,
+ const std::string& configuration_name,
+ const std::string& key,
+ base::WeakPtr<VpnService> vpn_service);
+ ~VpnConfiguration() override;
+
+ const std::string& extension_id() const { return extension_id_; }
+ const std::string& configuration_name() const { return configuration_name_; }
+ const std::string& key() const { return key_; }
+ const std::string& service_path() const { return service_path_; }
+ void set_service_path(const std::string& service_path) {
+ service_path_ = service_path;
+ }
+ const std::string& object_path() const { return object_path_; }
+
+ // ShillThirdPartyVpnObserver:
+ void OnPacketReceived(const std::vector<char>& data) override;
+ void OnPlatformMessage(uint32_t message) override;
+
+ private:
+ const std::string extension_id_;
+ const std::string configuration_name_;
+ const std::string key_;
+ const std::string object_path_;
+
+ std::string service_path_;
+
+ base::WeakPtr<VpnService> vpn_service_;
+
+ DISALLOW_COPY_AND_ASSIGN(VpnConfiguration);
+};
+
+VpnService::VpnConfiguration::VpnConfiguration(
+ const std::string& extension_id,
+ const std::string& configuration_name,
+ const std::string& key,
+ base::WeakPtr<VpnService> vpn_service)
+ : extension_id_(extension_id),
+ configuration_name_(configuration_name),
+ key_(key),
+ object_path_(shill::kObjectPathBase + key_),
+ vpn_service_(vpn_service) {
+}
+
+VpnService::VpnConfiguration::~VpnConfiguration() {
+}
+
+void VpnService::VpnConfiguration::OnPacketReceived(
+ const std::vector<char>& data) {
+ if (!vpn_service_) {
+ return;
+ }
+ scoped_ptr<base::ListValue> event_args =
+ api_vpn::OnPacketReceived::Create(data);
+ vpn_service_->SendSignalToExtension(
+ extension_id_, extensions::events::VPN_PROVIDER_ON_PACKET_RECEIVED,
+ api_vpn::OnPacketReceived::kEventName, std::move(event_args));
+}
+
+void VpnService::VpnConfiguration::OnPlatformMessage(uint32_t message) {
+ if (!vpn_service_) {
+ return;
+ }
+ DCHECK_GE(api_vpn::PLATFORM_MESSAGE_LAST, message);
+
+ api_vpn::PlatformMessage platform_message =
+ static_cast<api_vpn::PlatformMessage>(message);
+
+ if (platform_message == api_vpn::PLATFORM_MESSAGE_CONNECTED) {
+ vpn_service_->SetActiveConfiguration(this);
+ } else if (platform_message == api_vpn::PLATFORM_MESSAGE_DISCONNECTED ||
+ platform_message == api_vpn::PLATFORM_MESSAGE_ERROR) {
+ vpn_service_->SetActiveConfiguration(nullptr);
+ }
+
+ // TODO(kaliamoorthi): Update the lower layers to get the error message and
+ // pass in the error instead of std::string().
+ scoped_ptr<base::ListValue> event_args = api_vpn::OnPlatformMessage::Create(
+ configuration_name_, platform_message, std::string());
+
+ vpn_service_->SendSignalToExtension(
+ extension_id_, extensions::events::VPN_PROVIDER_ON_PLATFORM_MESSAGE,
+ api_vpn::OnPlatformMessage::kEventName, std::move(event_args));
+}
+
+VpnService::VpnService(
+ content::BrowserContext* browser_context,
+ const std::string& userid_hash,
+ extensions::ExtensionRegistry* extension_registry,
+ extensions::EventRouter* event_router,
+ ShillThirdPartyVpnDriverClient* shill_client,
+ NetworkConfigurationHandler* network_configuration_handler,
+ NetworkProfileHandler* network_profile_handler,
+ NetworkStateHandler* network_state_handler)
+ : browser_context_(browser_context),
+ userid_hash_(userid_hash),
+ extension_registry_(extension_registry),
+ event_router_(event_router),
+ shill_client_(shill_client),
+ network_configuration_handler_(network_configuration_handler),
+ network_profile_handler_(network_profile_handler),
+ network_state_handler_(network_state_handler),
+ active_configuration_(nullptr),
+ weak_factory_(this) {
+ extension_registry_->AddObserver(this);
+ network_state_handler_->AddObserver(this, FROM_HERE);
+ network_configuration_handler_->AddObserver(this);
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&VpnService::NetworkListChanged, weak_factory_.GetWeakPtr()));
+}
+
+VpnService::~VpnService() {
+ network_configuration_handler_->RemoveObserver(this);
+ network_state_handler_->RemoveObserver(this, FROM_HERE);
+ extension_registry_->RemoveObserver(this);
+ STLDeleteContainerPairSecondPointers(key_to_configuration_map_.begin(),
+ key_to_configuration_map_.end());
+}
+
+void VpnService::SendShowAddDialogToExtension(const std::string& extension_id) {
+ SendSignalToExtension(extension_id,
+ extensions::events::VPN_PROVIDER_ON_UI_EVENT,
+ api_vpn::OnUIEvent::kEventName,
+ api_vpn::OnUIEvent::Create(
+ api_vpn::UI_EVENT_SHOWADDDIALOG, std::string()));
+}
+
+void VpnService::SendShowConfigureDialogToExtension(
+ const std::string& extension_id,
+ const std::string& configuration_id) {
+ SendSignalToExtension(
+ extension_id, extensions::events::VPN_PROVIDER_ON_UI_EVENT,
+ api_vpn::OnUIEvent::kEventName,
+ api_vpn::OnUIEvent::Create(api_vpn::UI_EVENT_SHOWCONFIGUREDIALOG,
+ configuration_id));
+}
+
+void VpnService::SendPlatformError(const std::string& extension_id,
+ const std::string& configuration_id,
+ const std::string& error_message) {
+ SendSignalToExtension(
+ extension_id, extensions::events::VPN_PROVIDER_ON_PLATFORM_MESSAGE,
+ api_vpn::OnPlatformMessage::kEventName,
+ api_vpn::OnPlatformMessage::Create(
+ configuration_id, api_vpn::PLATFORM_MESSAGE_ERROR, error_message));
+}
+
+std::string VpnService::GetKey(const std::string& extension_id,
+ const std::string& name) {
+ const std::string key = crypto::SHA256HashString(extension_id + name);
+ return base::HexEncode(key.data(), key.size());
+}
+
+void VpnService::OnConfigurationCreated(const std::string& service_path,
+ const std::string& profile_path,
+ const base::DictionaryValue& properties,
+ Source source) {
+}
+
+void VpnService::OnConfigurationRemoved(const std::string& service_path,
+ const std::string& guid,
+ Source source) {
+ if (source == SOURCE_EXTENSION_INSTALL) {
+ // No need to process if the configuration was removed using an extension
+ // API since the API would have already done the cleanup.
+ return;
+ }
+
+ if (service_path_to_configuration_map_.find(service_path) ==
+ service_path_to_configuration_map_.end()) {
+ // Ignore removal of a configuration unknown to VPN service, which means the
+ // configuration was created internally by the platform.
+ return;
+ }
+
+ VpnConfiguration* configuration =
+ service_path_to_configuration_map_[service_path];
+ scoped_ptr<base::ListValue> event_args =
+ api_vpn::OnConfigRemoved::Create(configuration->configuration_name());
+ SendSignalToExtension(configuration->extension_id(),
+ extensions::events::VPN_PROVIDER_ON_CONFIG_REMOVED,
+ api_vpn::OnConfigRemoved::kEventName,
+ std::move(event_args));
+
+ DestroyConfigurationInternal(configuration);
+}
+
+void VpnService::OnPropertiesSet(const std::string& service_path,
+ const std::string& guid,
+ const base::DictionaryValue& set_properties,
+ Source source) {
+}
+
+void VpnService::OnConfigurationProfileChanged(const std::string& service_path,
+ const std::string& profile_path,
+ Source source) {
+}
+
+void VpnService::OnGetPropertiesSuccess(
+ const std::string& service_path,
+ const base::DictionaryValue& dictionary) {
+ if (service_path_to_configuration_map_.find(service_path) !=
+ service_path_to_configuration_map_.end()) {
+ return;
+ }
+ std::string vpn_type;
+ std::string extension_id;
+ std::string type;
+ std::string configuration_name;
+ if (!dictionary.GetString(shill::kProviderTypeProperty, &vpn_type) ||
+ !dictionary.GetString(shill::kProviderHostProperty, &extension_id) ||
+ !dictionary.GetString(shill::kTypeProperty, &type) ||
+ !dictionary.GetString(shill::kNameProperty, &configuration_name) ||
+ vpn_type != shill::kProviderThirdPartyVpn || type != shill::kTypeVPN) {
+ return;
+ }
+
+ if (!extension_registry_->GetExtensionById(
+ extension_id, extensions::ExtensionRegistry::ENABLED)) {
+ // Does not belong to this instance of VpnService.
+ return;
+ }
+
+ const std::string key = GetKey(extension_id, configuration_name);
+ VpnConfiguration* configuration =
+ CreateConfigurationInternal(extension_id, configuration_name, key);
+ configuration->set_service_path(service_path);
+ service_path_to_configuration_map_[service_path] = configuration;
+ shill_client_->AddShillThirdPartyVpnObserver(configuration->object_path(),
+ configuration);
+}
+
+void VpnService::OnGetPropertiesFailure(
+ const std::string& error_name,
+ scoped_ptr<base::DictionaryValue> error_data) {
+}
+
+void VpnService::NetworkListChanged() {
+ NetworkStateHandler::NetworkStateList network_list;
+ network_state_handler_->GetVisibleNetworkListByType(NetworkTypePattern::VPN(),
+ &network_list);
+ for (auto& iter : network_list) {
+ if (service_path_to_configuration_map_.find(iter->path()) !=
+ service_path_to_configuration_map_.end()) {
+ continue;
+ }
+
+ network_configuration_handler_->GetShillProperties(
+ iter->path(), base::Bind(&VpnService::OnGetPropertiesSuccess,
+ weak_factory_.GetWeakPtr()),
+ base::Bind(&VpnService::OnGetPropertiesFailure,
+ weak_factory_.GetWeakPtr()));
+ }
+}
+
+void VpnService::CreateConfiguration(const std::string& extension_id,
+ const std::string& extension_name,
+ const std::string& configuration_name,
+ const SuccessCallback& success,
+ const FailureCallback& failure) {
+ if (configuration_name.empty()) {
+ failure.Run(std::string(), std::string("Empty name not supported."));
+ return;
+ }
+
+ const std::string key = GetKey(extension_id, configuration_name);
+ if (ContainsKey(key_to_configuration_map_, key)) {
+ failure.Run(std::string(), std::string("Name not unique."));
+ return;
+ }
+
+ const NetworkProfile* profile =
+ network_profile_handler_->GetProfileForUserhash(userid_hash_);
+ if (!profile) {
+ failure.Run(
+ std::string(),
+ std::string("No user profile for unshared network configuration."));
+ return;
+ }
+
+ VpnConfiguration* configuration =
+ CreateConfigurationInternal(extension_id, configuration_name, key);
+
+ base::DictionaryValue properties;
+ properties.SetStringWithoutPathExpansion(shill::kTypeProperty,
+ shill::kTypeVPN);
+ properties.SetStringWithoutPathExpansion(shill::kNameProperty,
+ configuration_name);
+ properties.SetStringWithoutPathExpansion(shill::kProviderHostProperty,
+ extension_id);
+ properties.SetStringWithoutPathExpansion(shill::kObjectPathSuffixProperty,
+ configuration->key());
+ properties.SetStringWithoutPathExpansion(shill::kProviderTypeProperty,
+ shill::kProviderThirdPartyVpn);
+ properties.SetStringWithoutPathExpansion(shill::kProfileProperty,
+ profile->path);
+
+ // Note: This will not create an entry in |policy_util|. TODO(pneubeck):
+ // Determine the correct thing to do here, crbug.com/459278.
+ std::string guid = base::GenerateGUID();
+ properties.SetStringWithoutPathExpansion(shill::kGuidProperty, guid);
+
+ network_configuration_handler_->CreateShillConfiguration(
+ properties, NetworkConfigurationObserver::SOURCE_EXTENSION_INSTALL,
+ base::Bind(&VpnService::OnCreateConfigurationSuccess,
+ weak_factory_.GetWeakPtr(), success, configuration),
+ base::Bind(&VpnService::OnCreateConfigurationFailure,
+ weak_factory_.GetWeakPtr(), failure, configuration));
+}
+
+void VpnService::DestroyConfiguration(const std::string& extension_id,
+ const std::string& configuration_id,
+ const SuccessCallback& success,
+ const FailureCallback& failure) {
+ // The ID is the configuration name for now. This may change in the future.
+ const std::string key = GetKey(extension_id, configuration_id);
+ if (!ContainsKey(key_to_configuration_map_, key)) {
+ failure.Run(std::string(), std::string("Unauthorized access."));
+ return;
+ }
+
+ VpnConfiguration* configuration = key_to_configuration_map_[key];
+ const std::string service_path = configuration->service_path();
+ if (service_path.empty()) {
+ failure.Run(std::string(), std::string("Pending create."));
+ return;
+ }
+ if (active_configuration_ == configuration) {
+ configuration->OnPlatformMessage(api_vpn::PLATFORM_MESSAGE_DISCONNECTED);
+ }
+ DestroyConfigurationInternal(configuration);
+
+ network_configuration_handler_->RemoveConfiguration(
+ service_path, NetworkConfigurationObserver::SOURCE_EXTENSION_INSTALL,
+ base::Bind(&VpnService::OnRemoveConfigurationSuccess,
+ weak_factory_.GetWeakPtr(), success),
+ base::Bind(&VpnService::OnRemoveConfigurationFailure,
+ weak_factory_.GetWeakPtr(), failure));
+}
+
+void VpnService::SetParameters(const std::string& extension_id,
+ const base::DictionaryValue& parameters,
+ const StringCallback& success,
+ const FailureCallback& failure) {
+ if (!DoesActiveConfigurationExistAndIsAccessAuthorized(extension_id)) {
+ failure.Run(std::string(), std::string("Unauthorized access."));
+ return;
+ }
+
+ shill_client_->SetParameters(active_configuration_->object_path(), parameters,
+ success, failure);
+}
+
+void VpnService::SendPacket(const std::string& extension_id,
+ const std::vector<char>& data,
+ const SuccessCallback& success,
+ const FailureCallback& failure) {
+ if (!DoesActiveConfigurationExistAndIsAccessAuthorized(extension_id)) {
+ failure.Run(std::string(), std::string("Unauthorized access."));
+ return;
+ }
+
+ if (data.empty()) {
+ failure.Run(std::string(), std::string("Can't send an empty packet."));
+ return;
+ }
+
+ shill_client_->SendPacket(active_configuration_->object_path(), data, success,
+ failure);
+}
+
+void VpnService::NotifyConnectionStateChanged(const std::string& extension_id,
+ api_vpn::VpnConnectionState state,
+ const SuccessCallback& success,
+ const FailureCallback& failure) {
+ if (!DoesActiveConfigurationExistAndIsAccessAuthorized(extension_id)) {
+ failure.Run(std::string(), std::string("Unauthorized access."));
+ return;
+ }
+
+ shill_client_->UpdateConnectionState(active_configuration_->object_path(),
+ static_cast<uint32_t>(state), success,
+ failure);
+}
+
+bool VpnService::VerifyConfigExistsForTesting(
+ const std::string& extension_id,
+ const std::string& configuration_name) {
+ const std::string key = GetKey(extension_id, configuration_name);
+ return ContainsKey(key_to_configuration_map_, key);
+}
+
+bool VpnService::VerifyConfigIsConnectedForTesting(
+ const std::string& extension_id) {
+ return DoesActiveConfigurationExistAndIsAccessAuthorized(extension_id);
+}
+
+void VpnService::DestroyConfigurationsForExtension(
+ const extensions::Extension* extension) {
+ std::vector<VpnConfiguration*> to_be_destroyed;
+ for (const auto& iter : key_to_configuration_map_) {
+ if (iter.second->extension_id() == extension->id()) {
+ to_be_destroyed.push_back(iter.second);
+ }
+ }
+
+ for (auto& iter : to_be_destroyed) {
+ DestroyConfiguration(extension->id(), // Extension ID
+ iter->configuration_name(), // Configuration name
+ base::Bind(base::DoNothing),
+ base::Bind(DoNothingFailureCallback));
+ }
+}
+
+void VpnService::OnExtensionUninstalled(
+ content::BrowserContext* browser_context,
+ const extensions::Extension* extension,
+ extensions::UninstallReason reason) {
+ if (browser_context != browser_context_) {
+ NOTREACHED();
+ return;
+ }
+
+ DestroyConfigurationsForExtension(extension);
+}
+
+void VpnService::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const extensions::Extension* extension,
+ extensions::UnloadedExtensionInfo::Reason reason) {
+ if (browser_context != browser_context_) {
+ NOTREACHED();
+ return;
+ }
+
+ if (active_configuration_ &&
+ active_configuration_->extension_id() == extension->id()) {
+ shill_client_->UpdateConnectionState(
+ active_configuration_->object_path(),
+ static_cast<uint32_t>(api_vpn::VPN_CONNECTION_STATE_FAILURE),
+ base::Bind(base::DoNothing), base::Bind(DoNothingFailureCallback));
+ }
+ if (reason == extensions::UnloadedExtensionInfo::REASON_DISABLE ||
+ reason == extensions::UnloadedExtensionInfo::REASON_BLACKLIST) {
+ DestroyConfigurationsForExtension(extension);
+ }
+}
+
+void VpnService::OnCreateConfigurationSuccess(
+ const VpnService::SuccessCallback& callback,
+ VpnConfiguration* configuration,
+ const std::string& service_path,
+ const std::string& guid) {
+ configuration->set_service_path(service_path);
+ service_path_to_configuration_map_[service_path] = configuration;
+ shill_client_->AddShillThirdPartyVpnObserver(configuration->object_path(),
+ configuration);
+ callback.Run();
+}
+
+void VpnService::OnCreateConfigurationFailure(
+ const VpnService::FailureCallback& callback,
+ VpnConfiguration* configuration,
+ const std::string& error_name,
+ scoped_ptr<base::DictionaryValue> error_data) {
+ DestroyConfigurationInternal(configuration);
+ callback.Run(error_name, std::string());
+}
+
+void VpnService::OnRemoveConfigurationSuccess(
+ const VpnService::SuccessCallback& callback) {
+ callback.Run();
+}
+
+void VpnService::OnRemoveConfigurationFailure(
+ const VpnService::FailureCallback& callback,
+ const std::string& error_name,
+ scoped_ptr<base::DictionaryValue> error_data) {
+ callback.Run(error_name, std::string());
+}
+
+void VpnService::SendSignalToExtension(
+ const std::string& extension_id,
+ extensions::events::HistogramValue histogram_value,
+ const std::string& event_name,
+ scoped_ptr<base::ListValue> event_args) {
+ scoped_ptr<extensions::Event> event(new extensions::Event(
+ histogram_value, event_name, std::move(event_args), browser_context_));
+
+ event_router_->DispatchEventToExtension(extension_id, std::move(event));
+}
+
+void VpnService::SetActiveConfiguration(
+ VpnService::VpnConfiguration* configuration) {
+ active_configuration_ = configuration;
+}
+
+VpnService::VpnConfiguration* VpnService::CreateConfigurationInternal(
+ const std::string& extension_id,
+ const std::string& configuration_name,
+ const std::string& key) {
+ VpnConfiguration* configuration = new VpnConfiguration(
+ extension_id, configuration_name, key, weak_factory_.GetWeakPtr());
+ // The object is owned by key_to_configuration_map_ henceforth.
+ key_to_configuration_map_[key] = configuration;
+ return configuration;
+}
+
+void VpnService::DestroyConfigurationInternal(VpnConfiguration* configuration) {
+ key_to_configuration_map_.erase(configuration->key());
+ if (active_configuration_ == configuration) {
+ active_configuration_ = nullptr;
+ }
+ if (!configuration->service_path().empty()) {
+ shill_client_->RemoveShillThirdPartyVpnObserver(
+ configuration->object_path());
+ service_path_to_configuration_map_.erase(configuration->service_path());
+ }
+ delete configuration;
+}
+
+bool VpnService::DoesActiveConfigurationExistAndIsAccessAuthorized(
+ const std::string& extension_id) {
+ return active_configuration_ &&
+ active_configuration_->extension_id() == extension_id;
+}
+
+} // namespace chromeos
diff --git a/chromium/extensions/browser/api/vpn_provider/vpn_service.h b/chromium/extensions/browser/api/vpn_provider/vpn_service.h
new file mode 100644
index 00000000000..6e515c9b41f
--- /dev/null
+++ b/chromium/extensions/browser/api/vpn_provider/vpn_service.h
@@ -0,0 +1,253 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_VPN_PROVIDER_VPN_SERVICE_H_
+#define EXTENSIONS_BROWSER_API_VPN_PROVIDER_VPN_SERVICE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "chromeos/network/network_configuration_observer.h"
+#include "chromeos/network/network_state_handler_observer.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/browser/extension_event_histogram_value.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/common/api/vpn_provider.h"
+
+namespace base {
+
+class DictionaryValue;
+class ListValue;
+
+} // namespace base
+
+namespace content {
+
+class BrowserContext;
+
+} // namespace content
+
+namespace extensions {
+
+class EventRouter;
+class ExtensionRegistry;
+
+} // namespace extensions
+
+namespace chromeos {
+
+class NetworkConfigurationHandler;
+class NetworkProfileHandler;
+class NetworkStateHandler;
+class ShillThirdPartyVpnDriverClient;
+
+// The class manages the VPN configurations.
+class VpnService : public KeyedService,
+ public NetworkConfigurationObserver,
+ public NetworkStateHandlerObserver,
+ public extensions::ExtensionRegistryObserver {
+ public:
+ using SuccessCallback = base::Closure;
+ using StringCallback = base::Callback<void(const std::string& result)>;
+ using FailureCallback =
+ base::Callback<void(const std::string& error_name,
+ const std::string& error_message)>;
+
+ VpnService(content::BrowserContext* browser_context,
+ const std::string& userid_hash,
+ extensions::ExtensionRegistry* extension_registry,
+ extensions::EventRouter* event_router,
+ ShillThirdPartyVpnDriverClient* shill_client,
+ NetworkConfigurationHandler* network_configuration_handler,
+ NetworkProfileHandler* network_profile_handler,
+ NetworkStateHandler* network_state_handler);
+ ~VpnService() override;
+
+ void SendShowAddDialogToExtension(const std::string& extension_id);
+
+ void SendShowConfigureDialogToExtension(const std::string& extension_id,
+ const std::string& configuration_id);
+
+ void SendPlatformError(const std::string& extension_id,
+ const std::string& configuration_id,
+ const std::string& error_message);
+
+ // NetworkConfigurationObserver:
+ void OnConfigurationCreated(const std::string& service_path,
+ const std::string& profile_path,
+ const base::DictionaryValue& properties,
+ Source source) override;
+ void OnConfigurationRemoved(const std::string& service_path,
+ const std::string& guid,
+ Source source) override;
+ void OnPropertiesSet(const std::string& service_path,
+ const std::string& guid,
+ const base::DictionaryValue& set_properties,
+ Source source) override;
+ void OnConfigurationProfileChanged(const std::string& service_path,
+ const std::string& profile_path,
+ Source source) override;
+
+ // NetworkStateHandlerObserver:
+ void NetworkListChanged() override;
+
+ // ExtensionRegistryObserver:
+ void OnExtensionUninstalled(content::BrowserContext* browser_context,
+ const extensions::Extension* extension,
+ extensions::UninstallReason reason) override;
+ void OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const extensions::Extension* extension,
+ extensions::UnloadedExtensionInfo::Reason reason) override;
+
+ // Creates a new VPN configuration with |configuration_name| as the name and
+ // attaches it to the extension with id |extension_id|.
+ // Calls |success| or |failure| based on the outcome.
+ void CreateConfiguration(const std::string& extension_id,
+ const std::string& extension_name,
+ const std::string& configuration_name,
+ const SuccessCallback& success,
+ const FailureCallback& failure);
+
+ // Destroys the VPN configuration with |configuration_id| after verifying that
+ // it belongs to the extension with id |extension_id|.
+ // Calls |success| or |failure| based on the outcome.
+ void DestroyConfiguration(const std::string& extension_id,
+ const std::string& configuration_id,
+ const SuccessCallback& success,
+ const FailureCallback& failure);
+
+ // Set |parameters| for the active VPN configuration after verifying that it
+ // belongs to the extension with id |extension_id|.
+ // Calls |success| or |failure| based on the outcome.
+ void SetParameters(const std::string& extension_id,
+ const base::DictionaryValue& parameters,
+ const StringCallback& success,
+ const FailureCallback& failure);
+
+ // Sends an IP packet contained in |data| to the active VPN configuration
+ // after verifying that it belongs to the extension with id |extension_id|.
+ // Calls |success| or |failure| based on the outcome.
+ void SendPacket(const std::string& extension_id,
+ const std::vector<char>& data,
+ const SuccessCallback& success,
+ const FailureCallback& failure);
+
+ // Notifies connection state |state| to the active VPN configuration after
+ // verifying that it belongs to the extension with id |extension_id|.
+ // Calls |success| or |failure| based on the outcome.
+ void NotifyConnectionStateChanged(
+ const std::string& extension_id,
+ extensions::api::vpn_provider::VpnConnectionState state,
+ const SuccessCallback& success,
+ const FailureCallback& failure);
+
+ // Verifies if a configuration with name |configuration_name| exists for the
+ // extension with id |extension_id|.
+ bool VerifyConfigExistsForTesting(const std::string& extension_id,
+ const std::string& configuration_name);
+
+ // Verifies if the extension has a configuration that is connected.
+ bool VerifyConfigIsConnectedForTesting(const std::string& extension_id);
+
+ // Gets the unique key for the configuration |configuration_name| created by
+ // the extension with id |extension_id|.
+ // This method is made public for testing.
+ static std::string GetKey(const std::string& extension_id,
+ const std::string& configuration_name);
+
+ private:
+ class VpnConfiguration;
+
+ using StringToConfigurationMap = std::map<std::string, VpnConfiguration*>;
+
+ // Callback used to indicate that configuration was successfully created.
+ void OnCreateConfigurationSuccess(const SuccessCallback& callback,
+ VpnConfiguration* configuration,
+ const std::string& service_path,
+ const std::string& guid);
+
+ // Callback used to indicate that configuration creation failed.
+ void OnCreateConfigurationFailure(
+ const FailureCallback& callback,
+ VpnConfiguration* configuration,
+ const std::string& error_name,
+ scoped_ptr<base::DictionaryValue> error_data);
+
+ // Callback used to indicate that removing a configuration succeeded.
+ void OnRemoveConfigurationSuccess(const SuccessCallback& callback);
+
+ // Callback used to indicate that removing a configuration failed.
+ void OnRemoveConfigurationFailure(
+ const FailureCallback& callback,
+ const std::string& error_name,
+ scoped_ptr<base::DictionaryValue> error_data);
+
+ // Callback used to indicate that GetProperties was successful.
+ void OnGetPropertiesSuccess(const std::string& service_path,
+ const base::DictionaryValue& dictionary);
+
+ // Callback used to indicate that GetProperties failed.
+ void OnGetPropertiesFailure(const std::string& error_name,
+ scoped_ptr<base::DictionaryValue> error_data);
+
+ // Creates and adds the configuration to the internal store.
+ VpnConfiguration* CreateConfigurationInternal(
+ const std::string& extension_id,
+ const std::string& configuration_name,
+ const std::string& key);
+
+ // Removes configuration from the internal store and destroys it.
+ void DestroyConfigurationInternal(VpnConfiguration* configuration);
+
+ // Verifies if |active_configuration_| exists and if the extension with id
+ // |extension_id| is authorized to access it.
+ bool DoesActiveConfigurationExistAndIsAccessAuthorized(
+ const std::string& extension_id);
+
+ // Send an event with name |event_name| and arguments |event_args| to the
+ // extension with id |extension_id|.
+ void SendSignalToExtension(const std::string& extension_id,
+ extensions::events::HistogramValue histogram_value,
+ const std::string& event_name,
+ scoped_ptr<base::ListValue> event_args);
+
+ // Destroy configurations belonging to the extension.
+ void DestroyConfigurationsForExtension(
+ const extensions::Extension* extension);
+
+ // Set the active configuration.
+ void SetActiveConfiguration(VpnConfiguration* configuration);
+
+ content::BrowserContext* browser_context_;
+ std::string userid_hash_;
+
+ extensions::ExtensionRegistry* extension_registry_;
+ extensions::EventRouter* event_router_;
+ ShillThirdPartyVpnDriverClient* shill_client_;
+ NetworkConfigurationHandler* network_configuration_handler_;
+ NetworkProfileHandler* network_profile_handler_;
+ NetworkStateHandler* network_state_handler_;
+
+ VpnConfiguration* active_configuration_;
+
+ // Key map owns the VpnConfigurations.
+ StringToConfigurationMap key_to_configuration_map_;
+
+ // Service path does not own the VpnConfigurations.
+ StringToConfigurationMap service_path_to_configuration_map_;
+
+ base::WeakPtrFactory<VpnService> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(VpnService);
+};
+
+} // namespace chromeos
+
+#endif // EXTENSIONS_BROWSER_API_VPN_PROVIDER_VPN_SERVICE_H_
diff --git a/chromium/extensions/browser/api/vpn_provider/vpn_service_factory.h b/chromium/extensions/browser/api/vpn_provider/vpn_service_factory.h
new file mode 100644
index 00000000000..a343602ad5c
--- /dev/null
+++ b/chromium/extensions/browser/api/vpn_provider/vpn_service_factory.h
@@ -0,0 +1,49 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_VPN_PROVIDER_VPN_SERVICE_FACTORY_H_
+#define EXTENSIONS_BROWSER_API_VPN_PROVIDER_VPN_SERVICE_FACTORY_H_
+
+#include "base/macros.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace content {
+
+class BrowserContext;
+
+} // namespace content
+
+namespace base {
+template <typename T>
+struct DefaultSingletonTraits;
+}
+
+namespace chromeos {
+
+class VpnService;
+
+// Factory to create VpnService.
+class VpnServiceFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static VpnService* GetForBrowserContext(content::BrowserContext* context);
+ static VpnServiceFactory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<VpnServiceFactory>;
+
+ VpnServiceFactory();
+ ~VpnServiceFactory() override;
+
+ // BrowserContextKeyedServiceFactory:
+ bool ServiceIsCreatedWithBrowserContext() const override;
+ bool ServiceIsNULLWhileTesting() const override;
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(VpnServiceFactory);
+};
+
+} // namespace chromeos
+
+#endif // EXTENSIONS_BROWSER_API_VPN_PROVIDER_VPN_SERVICE_FACTORY_H_
diff --git a/chromium/extensions/browser/api/web_contents_capture_client.cc b/chromium/extensions/browser/api/web_contents_capture_client.cc
new file mode 100644
index 00000000000..6dff09e7d56
--- /dev/null
+++ b/chromium/extensions/browser/api/web_contents_capture_client.cc
@@ -0,0 +1,143 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/web_contents_capture_client.h"
+
+#include "base/base64.h"
+#include "base/strings/stringprintf.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/common/constants.h"
+#include "ui/gfx/codec/jpeg_codec.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/geometry/size_conversions.h"
+#include "ui/gfx/screen.h"
+
+using content::RenderWidgetHost;
+using content::RenderWidgetHostView;
+using content::WebContents;
+
+namespace extensions {
+
+using api::extension_types::ImageDetails;
+
+bool WebContentsCaptureClient::CaptureAsync(
+ WebContents* web_contents,
+ const ImageDetails* image_details,
+ const content::ReadbackRequestCallback callback) {
+ if (!web_contents)
+ return false;
+
+ if (!IsScreenshotEnabled())
+ return false;
+
+ // The default format and quality setting used when encoding jpegs.
+ const api::extension_types::ImageFormat kDefaultFormat =
+ api::extension_types::IMAGE_FORMAT_JPEG;
+ const int kDefaultQuality = 90;
+
+ image_format_ = kDefaultFormat;
+ image_quality_ = kDefaultQuality;
+
+ if (image_details) {
+ if (image_details->format != api::extension_types::IMAGE_FORMAT_NONE)
+ image_format_ = image_details->format;
+ if (image_details->quality.get())
+ image_quality_ = *image_details->quality;
+ }
+
+ // TODO(miu): Account for fullscreen render widget? http://crbug.com/419878
+ RenderWidgetHostView* const view = web_contents->GetRenderWidgetHostView();
+ RenderWidgetHost* const host = view ? view->GetRenderWidgetHost() : nullptr;
+ if (!view || !host) {
+ OnCaptureFailure(FAILURE_REASON_VIEW_INVISIBLE);
+ return false;
+ }
+
+ // By default, the requested bitmap size is the view size in screen
+ // coordinates. However, if there's more pixel detail available on the
+ // current system, increase the requested bitmap size to capture it all.
+ const gfx::Size view_size = view->GetViewBounds().size();
+ gfx::Size bitmap_size = view_size;
+ const gfx::NativeView native_view = view->GetNativeView();
+ gfx::Screen* const screen = gfx::Screen::GetScreen();
+ const float scale =
+ screen->GetDisplayNearestWindow(native_view).device_scale_factor();
+ if (scale > 1.0f)
+ bitmap_size = gfx::ScaleToCeiledSize(view_size, scale);
+
+ host->CopyFromBackingStore(gfx::Rect(view_size), bitmap_size, callback,
+ kN32_SkColorType);
+ return true;
+}
+
+void WebContentsCaptureClient::CopyFromBackingStoreComplete(
+ const SkBitmap& bitmap,
+ content::ReadbackResponse response) {
+ if (response == content::READBACK_SUCCESS) {
+ OnCaptureSuccess(bitmap);
+ return;
+ }
+ // TODO(wjmaclean): Improve error reporting. Why aren't we passing more
+ // information here?
+ std::string reason;
+ switch (response) {
+ case content::READBACK_FAILED:
+ reason = "READBACK_FAILED";
+ break;
+ case content::READBACK_SURFACE_UNAVAILABLE:
+ reason = "READBACK_SURFACE_UNAVAILABLE";
+ break;
+ case content::READBACK_BITMAP_ALLOCATION_FAILURE:
+ reason = "READBACK_BITMAP_ALLOCATION_FAILURE";
+ break;
+ default:
+ reason = "<unknown>";
+ }
+ OnCaptureFailure(FAILURE_REASON_UNKNOWN);
+}
+
+// TODO(wjmaclean) can this be static?
+bool WebContentsCaptureClient::EncodeBitmap(const SkBitmap& bitmap,
+ std::string* base64_result) {
+ DCHECK(base64_result);
+ std::vector<unsigned char> data;
+ SkAutoLockPixels screen_capture_lock(bitmap);
+ const bool should_discard_alpha = !ClientAllowsTransparency();
+ bool encoded = false;
+ std::string mime_type;
+ switch (image_format_) {
+ case api::extension_types::IMAGE_FORMAT_JPEG:
+ encoded = gfx::JPEGCodec::Encode(
+ reinterpret_cast<unsigned char*>(bitmap.getAddr32(0, 0)),
+ gfx::JPEGCodec::FORMAT_SkBitmap, bitmap.width(), bitmap.height(),
+ static_cast<int>(bitmap.rowBytes()), image_quality_, &data);
+ mime_type = kMimeTypeJpeg;
+ break;
+ case api::extension_types::IMAGE_FORMAT_PNG:
+ encoded = gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, should_discard_alpha,
+ &data);
+ mime_type = kMimeTypePng;
+ break;
+ default:
+ NOTREACHED() << "Invalid image format.";
+ }
+
+ if (!encoded)
+ return false;
+
+ base::StringPiece stream_as_string(reinterpret_cast<const char*>(data.data()),
+ data.size());
+
+ base::Base64Encode(stream_as_string, base64_result);
+ base64_result->insert(
+ 0, base::StringPrintf("data:%s;base64,", mime_type.c_str()));
+
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/web_contents_capture_client.h b/chromium/extensions/browser/api/web_contents_capture_client.h
new file mode 100644
index 00000000000..653535448fd
--- /dev/null
+++ b/chromium/extensions/browser/api/web_contents_capture_client.h
@@ -0,0 +1,59 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_WEB_CONTENTS_CAPTURE_CLIENT_H_
+#define EXTENSIONS_BROWSER_API_WEB_CONTENTS_CAPTURE_CLIENT_H_
+
+#include "base/macros.h"
+#include "content/public/browser/readback_types.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/common/api/extension_types.h"
+
+class SkBitmap;
+
+namespace content {
+class WebContents;
+}
+
+namespace extensions {
+
+// Base class for capturing visible area of a WebContents.
+// This is used by both webview.captureVisibleRegion and tabs.captureVisibleTab.
+class WebContentsCaptureClient {
+ public:
+ WebContentsCaptureClient() {}
+
+ protected:
+ virtual ~WebContentsCaptureClient() {}
+
+ virtual bool IsScreenshotEnabled() = 0;
+ virtual bool ClientAllowsTransparency() = 0;
+
+ enum FailureReason {
+ FAILURE_REASON_UNKNOWN,
+ FAILURE_REASON_ENCODING_FAILED,
+ FAILURE_REASON_VIEW_INVISIBLE
+ };
+ bool CaptureAsync(content::WebContents* web_contents,
+ const api::extension_types::ImageDetails* image_detail,
+ const content::ReadbackRequestCallback callback);
+ bool EncodeBitmap(const SkBitmap& bitmap, std::string* base64_result);
+ virtual void OnCaptureFailure(FailureReason reason) = 0;
+ virtual void OnCaptureSuccess(const SkBitmap& bitmap) = 0;
+ void CopyFromBackingStoreComplete(const SkBitmap& bitmap,
+ content::ReadbackResponse response);
+
+ private:
+ // The format (JPEG vs PNG) of the resulting image. Set in RunAsync().
+ api::extension_types::ImageFormat image_format_;
+
+ // Quality setting to use when encoding jpegs. Set in RunAsync().
+ int image_quality_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsCaptureClient);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_WEB_CONTENTS_CAPTURE_CLIENT_H_
diff --git a/chromium/extensions/browser/api/web_request/form_data_parser.cc b/chromium/extensions/browser/api/web_request/form_data_parser.cc
new file mode 100644
index 00000000000..f81c1099564
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/form_data_parser.cc
@@ -0,0 +1,601 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/web_request/form_data_parser.h"
+
+#include <stddef.h>
+
+#include <vector>
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "net/base/escape.h"
+#include "net/url_request/url_request.h"
+#include "third_party/re2/src/re2/re2.h"
+
+using base::DictionaryValue;
+using base::ListValue;
+using base::StringPiece;
+using re2::RE2;
+
+namespace extensions {
+
+namespace {
+
+const char kContentDisposition[] = "content-disposition:";
+const size_t kContentDispositionLength = arraysize(kContentDisposition) - 1;
+// kCharacterPattern is an allowed character in a URL encoding. Definition is
+// from RFC 1738, end of section 2.2.
+const char kCharacterPattern[] =
+ "(?:[a-zA-Z0-9$_.+!*'(),]|-|(?:%[a-fA-F0-9]{2}))";
+const char kEscapeClosingQuote[] = "\\\\E";
+
+// A wrapper struct for static RE2 objects to be held as LazyInstance.
+struct Patterns {
+ Patterns();
+ ~Patterns();
+ const RE2 transfer_padding_pattern;
+ const RE2 crlf_pattern;
+ const RE2 closing_pattern;
+ const RE2 epilogue_pattern;
+ const RE2 crlf_free_pattern;
+ const RE2 preamble_pattern;
+ const RE2 header_pattern;
+ const RE2 content_disposition_pattern;
+ const RE2 name_pattern;
+ const RE2 value_pattern;
+ const RE2 unquote_pattern;
+ const RE2 url_encoded_pattern;
+};
+
+Patterns::Patterns()
+ : transfer_padding_pattern("[ \\t]*\\r\\n"),
+ crlf_pattern("\\r\\n"),
+ closing_pattern("--[ \\t]*"),
+ epilogue_pattern("|\\r\\n(?s:.)*"),
+ crlf_free_pattern("(?:[^\\r]|\\r+[^\\r\\n])*"),
+ preamble_pattern(".+?"),
+ header_pattern("[!-9;-~]+:(.|\\r\\n[\\t ])*\\r\\n"),
+ content_disposition_pattern(std::string("(?i:") + kContentDisposition +
+ ")"),
+ name_pattern("\\bname=\"([^\"]*)\""),
+ value_pattern("\\bfilename=\"([^\"]*)\""),
+ unquote_pattern(kEscapeClosingQuote),
+ url_encoded_pattern(std::string("(") + kCharacterPattern + "*)=(" +
+ kCharacterPattern +
+ "*)") {
+}
+
+Patterns::~Patterns() {}
+
+base::LazyInstance<Patterns>::Leaky g_patterns = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// Parses URLencoded forms, see
+// http://www.w3.org/TR/REC-html40-971218/interact/forms.html#h-17.13.4.1 .
+class FormDataParserUrlEncoded : public FormDataParser {
+ public:
+ FormDataParserUrlEncoded();
+ ~FormDataParserUrlEncoded() override;
+
+ // Implementation of FormDataParser.
+ bool AllDataReadOK() override;
+ bool GetNextNameValue(Result* result) override;
+ bool SetSource(base::StringPiece source) override;
+
+ private:
+ // Returns the pattern to match a single name-value pair. This could be even
+ // static, but then we would have to spend more code on initializing the
+ // cached pointer to g_patterns.Get().
+ const RE2& pattern() const {
+ return patterns_->url_encoded_pattern;
+ }
+
+ // Auxiliary constant for using RE2. Number of arguments for parsing
+ // name-value pairs (one for name, one for value).
+ static const size_t args_size_ = 2u;
+ static const net::UnescapeRule::Type unescape_rules_;
+
+ re2::StringPiece source_;
+ bool source_set_;
+ bool source_malformed_;
+
+ // Auxiliary store for using RE2.
+ std::string name_;
+ std::string value_;
+ const RE2::Arg arg_name_;
+ const RE2::Arg arg_value_;
+ const RE2::Arg* args_[args_size_];
+
+ // Caching the pointer to g_patterns.Get().
+ const Patterns* patterns_;
+
+ DISALLOW_COPY_AND_ASSIGN(FormDataParserUrlEncoded);
+};
+
+// The following class, FormDataParserMultipart, parses forms encoded as
+// multipart, defined in RFCs 2388 (specific to forms), 2046 (multipart
+// encoding) and 5322 (MIME-headers).
+//
+// Implementation details
+//
+// The original grammar from RFC 2046 is this, "multipart-body" being the root
+// non-terminal:
+//
+// boundary := 0*69<bchars> bcharsnospace
+// bchars := bcharsnospace / " "
+// bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / "+" / "_" / ","
+// / "-" / "." / "/" / ":" / "=" / "?"
+// dash-boundary := "--" boundary
+// multipart-body := [preamble CRLF]
+// dash-boundary transport-padding CRLF
+// body-part *encapsulation
+// close-delimiter transport-padding
+// [CRLF epilogue]
+// transport-padding := *LWSP-char
+// encapsulation := delimiter transport-padding CRLF body-part
+// delimiter := CRLF dash-boundary
+// close-delimiter := delimiter "--"
+// preamble := discard-text
+// epilogue := discard-text
+// discard-text := *(*text CRLF) *text
+// body-part := MIME-part-headers [CRLF *OCTET]
+// OCTET := <any 0-255 octet value>
+//
+// Uppercase non-terminals are defined in RFC 5234, Appendix B.1; i.e. CRLF,
+// DIGIT, and ALPHA stand for "\r\n", '0'-'9' and the set of letters of the
+// English alphabet, respectively.
+// The non-terminal "text" is presumably just any text, excluding line breaks.
+// The non-terminal "LWSP-char" is not directly defined in the original grammar
+// but it means "linear whitespace", which is a space or a horizontal tab.
+// The non-terminal "MIME-part-headers" is not discussed in RFC 2046, so we use
+// the syntax for "optional fields" from Section 3.6.8 of RFC 5322:
+//
+// MIME-part-headers := field-name ":" unstructured CRLF
+// field-name := 1*ftext
+// ftext := %d33-57 / ; Printable US-ASCII
+// %d59-126 ; characters not including ":".
+// Based on Section 2.2.1 of RFC 5322, "unstructured" matches any string which
+// does not contain a CRLF sub-string, except for substrings "CRLF<space>" and
+// "CRLF<horizontal tab>", which serve for "folding".
+//
+// The FormDataParseMultipart class reads the input source and tries to parse it
+// according to the grammar above, rooted at the "multipart-body" non-terminal.
+// This happens in stages:
+//
+// 1. The optional preamble and the initial dash-boundary with transport padding
+// and a CRLF are read and ignored.
+//
+// 2. Repeatedly each body part is read. The body parts can either serve to
+// upload a file, or just a string of bytes.
+// 2.a. The headers of that part are searched for the "content-disposition"
+// header, which contains the name of the value represented by that body
+// part. If the body-part is for file upload, that header also contains a
+// filename.
+// 2.b. The "*OCTET" part of the body part is then read and passed as the value
+// of the name-value pair for body parts representing a string of bytes.
+// For body parts for uploading a file the "*OCTET" part is just ignored
+// and the filename is used for value instead.
+//
+// 3. The final close-delimiter and epilogue are read and ignored.
+//
+// IMPORTANT NOTE
+// This parser supports sources split into multiple chunks. Therefore SetSource
+// can be called multiple times if the source is spread over several chunks.
+// However, the split may only occur inside a body part, right after the
+// trailing CRLF of headers.
+class FormDataParserMultipart : public FormDataParser {
+ public:
+ explicit FormDataParserMultipart(const std::string& boundary_separator);
+ ~FormDataParserMultipart() override;
+
+ // Implementation of FormDataParser.
+ bool AllDataReadOK() override;
+ bool GetNextNameValue(Result* result) override;
+ bool SetSource(base::StringPiece source) override;
+
+ private:
+ enum State {
+ STATE_INIT, // No input read yet.
+ STATE_READY, // Ready to call GetNextNameValue.
+ STATE_FINISHED, // Read the input until the end.
+ STATE_SUSPEND, // Waiting until a new |source_| is set.
+ STATE_ERROR
+ };
+
+ // Produces a regexp to match the string "--" + |literal|. The idea is to
+ // represent "--" + |literal| as a "quoted pattern", a verbatim copy enclosed
+ // in "\\Q" and "\\E". The only catch is to watch out for occurences of "\\E"
+ // inside |literal|. Those must be excluded from the quote and the backslash
+ // doubly escaped. For example, for literal == "abc\\Edef" the result is
+ // "\\Q--abc\\E\\\\E\\Qdef\\E".
+ static std::string CreateBoundaryPatternFromLiteral(
+ const std::string& literal);
+
+ // Tests whether |input| has a prefix matching |pattern|.
+ static bool StartsWithPattern(const re2::StringPiece& input,
+ const RE2& pattern);
+
+ // If |source_| starts with a header, seeks |source_| beyond the header. If
+ // the header is Content-Disposition, extracts |name| from "name=" and
+ // possibly |value| from "filename=" fields of that header. Only if the
+ // "name" or "filename" fields are found, then |name| or |value| are touched.
+ // Returns true iff |source_| is seeked forward. Sets |value_assigned|
+ // to true iff |value| has been assigned to.
+ bool TryReadHeader(base::StringPiece* name,
+ base::StringPiece* value,
+ bool* value_assigned);
+
+ // Helper to GetNextNameValue. Expects that the input starts with a data
+ // portion of a body part. An attempt is made to read the input until the end
+ // of that body part. If |data| is not NULL, it is set to contain the data
+ // portion. Returns true iff the reading was successful.
+ bool FinishReadingPart(base::StringPiece* data);
+
+ // These methods could be even static, but then we would have to spend more
+ // code on initializing the cached pointer to g_patterns.Get().
+ const RE2& transfer_padding_pattern() const {
+ return patterns_->transfer_padding_pattern;
+ }
+ const RE2& crlf_pattern() const {
+ return patterns_->crlf_pattern;
+ }
+ const RE2& closing_pattern() const {
+ return patterns_->closing_pattern;
+ }
+ const RE2& epilogue_pattern() const {
+ return patterns_->epilogue_pattern;
+ }
+ const RE2& crlf_free_pattern() const {
+ return patterns_->crlf_free_pattern;
+ }
+ const RE2& preamble_pattern() const {
+ return patterns_->preamble_pattern;
+ }
+ const RE2& header_pattern() const {
+ return patterns_->header_pattern;
+ }
+ const RE2& content_disposition_pattern() const {
+ return patterns_->content_disposition_pattern;
+ }
+ const RE2& name_pattern() const {
+ return patterns_->name_pattern;
+ }
+ const RE2& value_pattern() const {
+ return patterns_->value_pattern;
+ }
+ // However, this is used in a static method so it needs to be static.
+ static const RE2& unquote_pattern() {
+ return g_patterns.Get().unquote_pattern; // No caching g_patterns here.
+ }
+
+ const RE2 dash_boundary_pattern_;
+
+ // Because of initialisation dependency, |state_| needs to be declared after
+ // |dash_boundary_pattern_|.
+ State state_;
+
+ // The parsed message can be split into multiple sources which we read
+ // sequentially.
+ re2::StringPiece source_;
+
+ // Caching the pointer to g_patterns.Get().
+ const Patterns* patterns_;
+
+ DISALLOW_COPY_AND_ASSIGN(FormDataParserMultipart);
+};
+
+FormDataParser::Result::Result() {}
+FormDataParser::Result::~Result() {}
+
+FormDataParser::~FormDataParser() {}
+
+// static
+scoped_ptr<FormDataParser> FormDataParser::Create(
+ const net::URLRequest& request) {
+ std::string value;
+ const bool found = request.extra_request_headers().GetHeader(
+ net::HttpRequestHeaders::kContentType, &value);
+ return CreateFromContentTypeHeader(found ? &value : NULL);
+}
+
+// static
+scoped_ptr<FormDataParser> FormDataParser::CreateFromContentTypeHeader(
+ const std::string* content_type_header) {
+ enum ParserChoice {URL_ENCODED, MULTIPART, ERROR_CHOICE};
+ ParserChoice choice = ERROR_CHOICE;
+ std::string boundary;
+
+ if (content_type_header == NULL) {
+ choice = URL_ENCODED;
+ } else {
+ const std::string content_type(
+ content_type_header->substr(0, content_type_header->find(';')));
+
+ if (base::EqualsCaseInsensitiveASCII(content_type,
+ "application/x-www-form-urlencoded")) {
+ choice = URL_ENCODED;
+ } else if (base::EqualsCaseInsensitiveASCII(content_type,
+ "multipart/form-data")) {
+ static const char kBoundaryString[] = "boundary=";
+ size_t offset = content_type_header->find(kBoundaryString);
+ if (offset == std::string::npos) {
+ // Malformed header.
+ return scoped_ptr<FormDataParser>();
+ }
+ offset += sizeof(kBoundaryString) - 1;
+ boundary = content_type_header->substr(
+ offset, content_type_header->find(';', offset));
+ if (!boundary.empty())
+ choice = MULTIPART;
+ }
+ }
+ // Other cases are unparseable, including when |content_type| is "text/plain".
+
+ switch (choice) {
+ case URL_ENCODED:
+ return scoped_ptr<FormDataParser>(new FormDataParserUrlEncoded());
+ case MULTIPART:
+ return scoped_ptr<FormDataParser>(new FormDataParserMultipart(boundary));
+ case ERROR_CHOICE:
+ return scoped_ptr<FormDataParser>();
+ }
+ NOTREACHED(); // Some compilers do not believe this is unreachable.
+ return scoped_ptr<FormDataParser>();
+}
+
+FormDataParser::FormDataParser() {}
+
+const net::UnescapeRule::Type FormDataParserUrlEncoded::unescape_rules_ =
+ net::UnescapeRule::PATH_SEPARATORS |
+ net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS |
+ net::UnescapeRule::SPOOFING_AND_CONTROL_CHARS | net::UnescapeRule::SPACES |
+ net::UnescapeRule::REPLACE_PLUS_WITH_SPACE;
+
+FormDataParserUrlEncoded::FormDataParserUrlEncoded()
+ : source_(NULL),
+ source_set_(false),
+ source_malformed_(false),
+ arg_name_(&name_),
+ arg_value_(&value_),
+ patterns_(g_patterns.Pointer()) {
+ args_[0] = &arg_name_;
+ args_[1] = &arg_value_;
+}
+
+FormDataParserUrlEncoded::~FormDataParserUrlEncoded() {}
+
+bool FormDataParserUrlEncoded::AllDataReadOK() {
+ // All OK means we read the whole source.
+ return source_set_ && source_.empty() && !source_malformed_;
+}
+
+bool FormDataParserUrlEncoded::GetNextNameValue(Result* result) {
+ if (!source_set_ || source_malformed_)
+ return false;
+
+ bool success = RE2::ConsumeN(&source_, pattern(), args_, args_size_);
+ if (success) {
+ result->set_name(net::UnescapeURLComponent(name_, unescape_rules_));
+ result->set_value(net::UnescapeURLComponent(value_, unescape_rules_));
+ }
+ if (source_.length() > 0) {
+ if (source_[0] == '&')
+ source_.remove_prefix(1); // Remove the leading '&'.
+ else
+ source_malformed_ = true; // '&' missing between two name-value pairs.
+ }
+ return success && !source_malformed_;
+}
+
+bool FormDataParserUrlEncoded::SetSource(base::StringPiece source) {
+ if (source_set_)
+ return false; // We do not allow multiple sources for this parser.
+ source_.set(source.data(), source.size());
+ source_set_ = true;
+ source_malformed_ = false;
+ return true;
+}
+
+// static
+std::string FormDataParserMultipart::CreateBoundaryPatternFromLiteral(
+ const std::string& literal) {
+ static const char quote[] = "\\Q";
+ static const char unquote[] = "\\E";
+
+ // The result always starts with opening the qoute and then "--".
+ std::string result("\\Q--");
+
+ // This StringPiece is used below to record the next occurrence of "\\E" in
+ // |literal|.
+ re2::StringPiece seek_unquote(literal);
+ const char* copy_start = literal.data();
+ size_t copy_length = literal.size();
+
+ // Find all "\\E" in |literal| and exclude them from the \Q...\E quote.
+ while (RE2::FindAndConsume(&seek_unquote, unquote_pattern())) {
+ copy_length = seek_unquote.data() - copy_start;
+ result.append(copy_start, copy_length);
+ result.append(kEscapeClosingQuote);
+ result.append(quote);
+ copy_start = seek_unquote.data();
+ }
+
+ // Finish the last \Q...\E quote.
+ copy_length = (literal.data() + literal.size()) - copy_start;
+ result.append(copy_start, copy_length);
+ result.append(unquote);
+ return result;
+}
+
+// static
+bool FormDataParserMultipart::StartsWithPattern(const re2::StringPiece& input,
+ const RE2& pattern) {
+ return pattern.Match(input, 0, input.size(), RE2::ANCHOR_START, NULL, 0);
+}
+
+FormDataParserMultipart::FormDataParserMultipart(
+ const std::string& boundary_separator)
+ : dash_boundary_pattern_(
+ CreateBoundaryPatternFromLiteral(boundary_separator)),
+ state_(dash_boundary_pattern_.ok() ? STATE_INIT : STATE_ERROR),
+ patterns_(g_patterns.Pointer()) {}
+
+FormDataParserMultipart::~FormDataParserMultipart() {}
+
+bool FormDataParserMultipart::AllDataReadOK() {
+ return state_ == STATE_FINISHED;
+}
+
+bool FormDataParserMultipart::FinishReadingPart(base::StringPiece* data) {
+ const char* data_start = source_.data();
+ while (!StartsWithPattern(source_, dash_boundary_pattern_)) {
+ if (!RE2::Consume(&source_, crlf_free_pattern()) ||
+ !RE2::Consume(&source_, crlf_pattern())) {
+ state_ = STATE_ERROR;
+ return false;
+ }
+ }
+ if (data != NULL) {
+ if (source_.data() == data_start) {
+ // No data in this body part.
+ state_ = STATE_ERROR;
+ return false;
+ }
+ // Subtract 2 for the trailing "\r\n".
+ data->set(data_start, source_.data() - data_start - 2);
+ }
+
+ // Finally, read the dash-boundary and either skip to the next body part, or
+ // finish reading the source.
+ CHECK(RE2::Consume(&source_, dash_boundary_pattern_));
+ if (StartsWithPattern(source_, closing_pattern())) {
+ CHECK(RE2::Consume(&source_, closing_pattern()));
+ if (RE2::Consume(&source_, epilogue_pattern()))
+ state_ = STATE_FINISHED;
+ else
+ state_ = STATE_ERROR;
+ } else { // Next body part ahead.
+ if (!RE2::Consume(&source_, transfer_padding_pattern()))
+ state_ = STATE_ERROR;
+ }
+ return state_ != STATE_ERROR;
+}
+
+bool FormDataParserMultipart::GetNextNameValue(Result* result) {
+ if (source_.empty() || state_ != STATE_READY)
+ return false;
+
+ // 1. Read body-part headers.
+ base::StringPiece name;
+ base::StringPiece value;
+ bool value_assigned = false;
+ bool value_assigned_temp;
+ while (TryReadHeader(&name, &value, &value_assigned_temp))
+ value_assigned |= value_assigned_temp;
+ if (name.empty() || state_ == STATE_ERROR) {
+ state_ = STATE_ERROR;
+ return false;
+ }
+
+ // 2. Read the trailing CRLF after headers.
+ if (!RE2::Consume(&source_, crlf_pattern())) {
+ state_ = STATE_ERROR;
+ return false;
+ }
+
+ // 3. Read the data of this body part, i.e., everything until the first
+ // dash-boundary.
+ bool return_value;
+ if (value_assigned && source_.empty()) { // Wait for a new source?
+ return_value = true;
+ state_ = STATE_SUSPEND;
+ } else {
+ return_value = FinishReadingPart(value_assigned ? NULL : &value);
+ }
+
+ std::string unescaped_name = net::UnescapeURLComponent(
+ name.as_string(),
+ net::UnescapeRule::PATH_SEPARATORS |
+ net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS |
+ net::UnescapeRule::SPOOFING_AND_CONTROL_CHARS);
+ result->set_name(unescaped_name);
+ result->set_value(value);
+
+ return return_value;
+}
+
+bool FormDataParserMultipart::SetSource(base::StringPiece source) {
+ if (source.data() == NULL || !source_.empty())
+ return false;
+ source_.set(source.data(), source.size());
+
+ switch (state_) {
+ case STATE_INIT:
+ // Seek behind the preamble.
+ while (!StartsWithPattern(source_, dash_boundary_pattern_)) {
+ if (!RE2::Consume(&source_, preamble_pattern())) {
+ state_ = STATE_ERROR;
+ break;
+ }
+ }
+ // Read dash-boundary, transfer padding, and CRLF.
+ if (state_ != STATE_ERROR) {
+ if (!RE2::Consume(&source_, dash_boundary_pattern_) ||
+ !RE2::Consume(&source_, transfer_padding_pattern()))
+ state_ = STATE_ERROR;
+ else
+ state_ = STATE_READY;
+ }
+ break;
+ case STATE_READY: // Nothing to do.
+ break;
+ case STATE_SUSPEND:
+ state_ = FinishReadingPart(NULL) ? STATE_READY : STATE_ERROR;
+ break;
+ default:
+ state_ = STATE_ERROR;
+ }
+ return state_ != STATE_ERROR;
+}
+
+bool FormDataParserMultipart::TryReadHeader(base::StringPiece* name,
+ base::StringPiece* value,
+ bool* value_assigned) {
+ *value_assigned = false;
+ const char* header_start = source_.data();
+ if (!RE2::Consume(&source_, header_pattern()))
+ return false;
+ // (*) After this point we must return true, because we consumed one header.
+
+ // Subtract 2 for the trailing "\r\n".
+ re2::StringPiece header(header_start, source_.data() - header_start - 2);
+
+ if (!StartsWithPattern(header, content_disposition_pattern()))
+ return true; // Skip headers that don't describe the content-disposition.
+
+ re2::StringPiece groups[2];
+
+ if (!name_pattern().Match(header,
+ kContentDispositionLength, header.size(),
+ RE2::UNANCHORED, groups, 2)) {
+ state_ = STATE_ERROR;
+ return true; // See (*) for why true.
+ }
+ name->set(groups[1].data(), groups[1].size());
+
+ if (value_pattern().Match(header,
+ kContentDispositionLength, header.size(),
+ RE2::UNANCHORED, groups, 2)) {
+ value->set(groups[1].data(), groups[1].size());
+ *value_assigned = true;
+ }
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/web_request/form_data_parser.h b/chromium/extensions/browser/api/web_request/form_data_parser.h
new file mode 100644
index 00000000000..0c49caa0138
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/form_data_parser.h
@@ -0,0 +1,82 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_WEB_REQUEST_FORM_DATA_PARSER_H_
+#define EXTENSIONS_BROWSER_API_WEB_REQUEST_FORM_DATA_PARSER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+// Cannot forward declare StringPiece because it is a typedef.
+#include "base/strings/string_piece.h"
+
+namespace net {
+class URLRequest;
+}
+
+namespace extensions {
+
+// Interface for the form data parsers.
+class FormDataParser {
+ public:
+ // Result encapsulates name-value pairs returned by GetNextNameValue.
+ class Result {
+ public:
+ Result();
+ ~Result();
+
+ const std::string& name() const { return name_; }
+ const std::string& value() const { return value_; }
+
+ void set_name(base::StringPiece str) { str.CopyToString(&name_); }
+ void set_value(base::StringPiece str) { str.CopyToString(&value_); }
+
+ private:
+ std::string name_;
+ std::string value_;
+
+ DISALLOW_COPY_AND_ASSIGN(Result);
+ };
+
+ virtual ~FormDataParser();
+
+ // Creates a correct parser instance based on the |request|. Returns NULL
+ // on failure.
+ static scoped_ptr<FormDataParser> Create(const net::URLRequest& request);
+
+ // Creates a correct parser instance based on |content_type_header|, the
+ // "Content-Type" request header value. If |content_type_header| is NULL, it
+ // defaults to "application/x-www-form-urlencoded". Returns NULL on failure.
+ static scoped_ptr<FormDataParser> CreateFromContentTypeHeader(
+ const std::string* content_type_header);
+
+ // Returns true if there was some data, it was well formed and all was read.
+ virtual bool AllDataReadOK() = 0;
+
+ // Gets the next name-value pair from the source data and stores it in
+ // |result|. Returns true if a pair was found. Callers must have previously
+ // succesfully called the SetSource method.
+ virtual bool GetNextNameValue(Result* result) = 0;
+
+ // Sets the |source| of the data to be parsed. One form data parser is only
+ // expected to be associated with one source, so generally, SetSource should
+ // be only called once. However, for technical reasons, the source might only
+ // be available in chunks for multipart encoded forms, in which case it is OK
+ // to call SetSource multiple times to add all chunks of a single source. The
+ // ownership of |source| is left with the caller and the source should live
+ // until |this| dies or |this->SetSource()| is called again, whichever comes
+ // sooner. Returns true on success.
+ virtual bool SetSource(base::StringPiece source) = 0;
+
+ protected:
+ FormDataParser();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FormDataParser);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_WEB_REQUEST_FORM_DATA_PARSER_H_
diff --git a/chromium/extensions/browser/api/web_request/form_data_parser_unittest.cc b/chromium/extensions/browser/api/web_request/form_data_parser_unittest.cc
new file mode 100644
index 00000000000..6f700c7ae6d
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/form_data_parser_unittest.cc
@@ -0,0 +1,252 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+#include "extensions/browser/api/web_request/form_data_parser.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+// Attempts to create a parser corresponding to the |content_type_header|.
+// On success, returns the parser.
+scoped_ptr<FormDataParser> InitParser(const std::string& content_type_header) {
+ scoped_ptr<FormDataParser> parser(
+ FormDataParser::CreateFromContentTypeHeader(&content_type_header));
+ if (parser.get() == NULL)
+ return scoped_ptr<FormDataParser>();
+ return parser;
+}
+
+// Attempts to run the parser corresponding to the |content_type_header|
+// on the source represented by the concatenation of blocks from |bytes|.
+// On success, returns true and the parsed |output|, else false.
+// Parsed |output| has names on even positions (0, 2, ...), values on odd ones.
+bool RunParser(const std::string& content_type_header,
+ const std::vector<const base::StringPiece*>& bytes,
+ std::vector<std::string>* output) {
+ DCHECK(output);
+ output->clear();
+ scoped_ptr<FormDataParser> parser(InitParser(content_type_header));
+ if (!parser.get())
+ return false;
+ FormDataParser::Result result;
+ for (size_t block = 0; block < bytes.size(); ++block) {
+ if (!parser->SetSource(*(bytes[block])))
+ return false;
+ while (parser->GetNextNameValue(&result)) {
+ output->push_back(result.name());
+ output->push_back(result.value());
+ }
+ }
+ return parser->AllDataReadOK();
+}
+
+// Attempts to run the parser corresponding to the |content_type_header|
+// on the source represented by the concatenation of blocks from |bytes|.
+// Checks that the parser fails parsing.
+bool CheckParserFails(const std::string& content_type_header,
+ const std::vector<const base::StringPiece*>& bytes) {
+ std::vector<std::string> output;
+ scoped_ptr<FormDataParser> parser(InitParser(content_type_header));
+ if (!parser.get())
+ return false;
+ FormDataParser::Result result;
+ for (size_t block = 0; block < bytes.size(); ++block) {
+ if (!parser->SetSource(*(bytes[block])))
+ break;
+ while (parser->GetNextNameValue(&result)) {
+ output.push_back(result.name());
+ output.push_back(result.value());
+ }
+ }
+ return !parser->AllDataReadOK();
+}
+
+} // namespace
+
+TEST(WebRequestFormDataParserTest, Parsing) {
+ // We verify that POST data parsers cope with various formats of POST data.
+ // Construct the test data.
+ const std::string kBoundary = "THIS_IS_A_BOUNDARY";
+ const std::string kBlockStr1 =
+ std::string("--") + kBoundary +
+ "\r\n"
+ "Content-Disposition: form-data; name=\"text\"\r\n"
+ "\r\n"
+ "test\rtext\nwith non-CRLF line breaks\r\n"
+ "--" +
+ kBoundary +
+ "\r\n"
+ "Content-Disposition: form-data; name=\"file\"; filename=\"test\"\r\n"
+ "Content-Type: application/octet-stream\r\n"
+ "\r\n";
+ const std::string kBlockStr2 =
+ std::string("\r\n--") + kBoundary +
+ "\r\n"
+ "Content-Disposition: form-data; name=\"password\"\r\n"
+ "\r\n"
+ "test password\r\n"
+ "--" +
+ kBoundary +
+ "\r\n"
+ "Content-Disposition: form-data; name=\"radio\"\r\n"
+ "\r\n"
+ "Yes\r\n"
+ "--" +
+ kBoundary +
+ "\r\n"
+ "Content-Disposition: form-data; name=\"check\"\r\n"
+ "\r\n"
+ "option A\r\n"
+ "--" +
+ kBoundary +
+ "\r\n"
+ "Content-Disposition: form-data; name=\"check\"\r\n"
+ "\r\n"
+ "option B\r\n"
+ "--" +
+ kBoundary +
+ "\r\n"
+ "Content-Disposition: form-data; name=\"txtarea\"\r\n"
+ "\r\n"
+ "Some text.\r\n"
+ "Other.\r\n"
+ "\r\n"
+ "--" +
+ kBoundary +
+ "\r\n"
+ "Content-Disposition: form-data; name=\"select\"\r\n"
+ "\r\n"
+ "one\r\n"
+ "--" +
+ kBoundary + "--";
+ // POST data input.
+ const std::string kBigBlock = kBlockStr1 + kBlockStr2;
+ const std::string kUrlEncodedBlock =
+ "text=test%0Dtext%0Awith+non-CRLF+line+breaks"
+ "&file=test&password=test+password&radio=Yes&check=option+A"
+ "&check=option+B&txtarea=Some+text.%0D%0AOther.%0D%0A&select=one";
+ const base::StringPiece kMultipartBytes(kBigBlock);
+ const base::StringPiece kMultipartBytesSplit1(kBlockStr1);
+ const base::StringPiece kMultipartBytesSplit2(kBlockStr2);
+ const base::StringPiece kUrlEncodedBytes(kUrlEncodedBlock);
+ const std::string kPlainBlock = "abc";
+ const base::StringPiece kTextPlainBytes(kPlainBlock);
+ // Headers.
+ const std::string kUrlEncoded = "application/x-www-form-urlencoded";
+ const std::string kTextPlain = "text/plain";
+ const std::string kMultipart =
+ std::string("multipart/form-data; boundary=") + kBoundary;
+ // Expected output.
+ const char* kPairs[] = {
+ "text", "test\rtext\nwith non-CRLF line breaks",
+ "file", "test",
+ "password", "test password",
+ "radio", "Yes",
+ "check", "option A",
+ "check", "option B",
+ "txtarea", "Some text.\r\nOther.\r\n",
+ "select", "one"
+ };
+ const std::vector<std::string> kExpected(kPairs, kPairs + arraysize(kPairs));
+
+ std::vector<const base::StringPiece*> input;
+ std::vector<std::string> output;
+
+ // First test: multipart POST data in one lump.
+ input.push_back(&kMultipartBytes);
+ EXPECT_TRUE(RunParser(kMultipart, input, &output));
+ EXPECT_EQ(kExpected, output);
+
+ // Second test: multipart POST data in several lumps.
+ input.clear();
+ input.push_back(&kMultipartBytesSplit1);
+ input.push_back(&kMultipartBytesSplit2);
+ EXPECT_TRUE(RunParser(kMultipart, input, &output));
+ EXPECT_EQ(kExpected, output);
+
+ // Third test: URL-encoded POST data.
+ input.clear();
+ input.push_back(&kUrlEncodedBytes);
+ EXPECT_TRUE(RunParser(kUrlEncoded, input, &output));
+ EXPECT_EQ(kExpected, output);
+
+ // Fourth test: text/plain POST data in one lump.
+ input.clear();
+ input.push_back(&kTextPlainBytes);
+ // This should fail, text/plain is ambiguous and thus unparseable.
+ EXPECT_FALSE(RunParser(kTextPlain, input, &output));
+}
+
+TEST(WebRequestFormDataParserTest, MalformedPayload) {
+ // We verify that POST data parsers reject malformed data.
+ // Construct the test data.
+ const std::string kBoundary = "THIS_IS_A_BOUNDARY";
+ const std::string kBlockStr =
+ std::string("--") + kBoundary +
+ "\r\n"
+ "Content-Disposition: form-data; name=\"text\"\r\n"
+ "\r\n"
+ "test\rtext\nwith non-CRLF line breaks\r\n"
+ "-" +
+ kBoundary +
+ "\r\n" /* Missing '-'. */
+ "Content-Disposition: form-data; name=\"file\"; filename=\"test\"\r\n"
+ "Content-Type: application/octet-stream\r\n"
+ /* Two CRLF missing. */
+ "--" +
+ kBoundary +
+ "\r\n"
+ "Content-Disposition: form-data; name=\"select\"\r\n"
+ "\r\n"
+ "one\r\n"
+ "--" +
+ kBoundary + "-" /* Missing '-' at the end. */;
+ // POST data input.
+ // The following block is corrupted -- contains a "==" substring.
+ const std::string kUrlEncodedBlock =
+ "text=test%0Dtext%0Awith+non-CRLF+line+breaks"
+ "&file==test&password=test+password&radio=Yes&check=option+A"
+ "&check=option+B&txtarea=Some+text.%0D%0AOther.%0D%0A&select=one";
+ const base::StringPiece kMultipartBytes(kBlockStr);
+ const base::StringPiece kMultipartBytesEmpty("");
+ const base::StringPiece kUrlEncodedBytes(kUrlEncodedBlock);
+ const base::StringPiece kUrlEncodedBytesEmpty("");
+ // Headers.
+ const std::string kUrlEncoded = "application/x-www-form-urlencoded";
+ const std::string kMultipart =
+ std::string("multipart/form-data; boundary=") + kBoundary;
+
+ std::vector<const base::StringPiece*> input;
+
+ // First test: malformed multipart POST data.
+ input.push_back(&kMultipartBytes);
+ EXPECT_TRUE(CheckParserFails(kMultipart, input));
+
+ // Second test: empty multipart POST data.
+ input.clear();
+ input.push_back(&kMultipartBytesEmpty);
+ EXPECT_TRUE(CheckParserFails(kMultipart, input));
+
+ // Third test: malformed URL-encoded POST data.
+ input.clear();
+ input.push_back(&kUrlEncodedBytes);
+ EXPECT_TRUE(CheckParserFails(kUrlEncoded, input));
+
+ // Fourth test: empty URL-encoded POST data. Note that an empty string is a
+ // valid url-encoded value, so this should parse correctly.
+ std::vector<std::string> output;
+ input.clear();
+ input.push_back(&kUrlEncodedBytesEmpty);
+ EXPECT_TRUE(RunParser(kUrlEncoded, input, &output));
+ EXPECT_EQ(0u, output.size());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/web_request/upload_data_presenter.cc b/chromium/extensions/browser/api/web_request/upload_data_presenter.cc
new file mode 100644
index 00000000000..bdd80d69081
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/upload_data_presenter.cc
@@ -0,0 +1,164 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/web_request/upload_data_presenter.h"
+
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "extensions/browser/api/web_request/form_data_parser.h"
+#include "extensions/browser/api/web_request/web_request_api_constants.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_file_element_reader.h"
+#include "net/url_request/url_request.h"
+
+using base::BinaryValue;
+using base::DictionaryValue;
+using base::ListValue;
+using base::StringValue;
+using base::Value;
+
+namespace keys = extension_web_request_api_constants;
+
+namespace {
+
+// Takes |dictionary| of <string, list of strings> pairs, and gets the list
+// for |key|, creating it if necessary.
+base::ListValue* GetOrCreateList(base::DictionaryValue* dictionary,
+ const std::string& key) {
+ base::ListValue* list = NULL;
+ if (!dictionary->GetList(key, &list)) {
+ list = new base::ListValue();
+ dictionary->SetWithoutPathExpansion(key, list);
+ }
+ return list;
+}
+
+} // namespace
+
+namespace extensions {
+
+namespace subtle {
+
+void AppendKeyValuePair(const char* key,
+ base::Value* value,
+ base::ListValue* list) {
+ base::DictionaryValue* dictionary = new base::DictionaryValue;
+ dictionary->SetWithoutPathExpansion(key, value);
+ list->Append(dictionary);
+}
+
+} // namespace subtle
+
+UploadDataPresenter::~UploadDataPresenter() {}
+
+RawDataPresenter::RawDataPresenter()
+ : success_(true),
+ list_(new base::ListValue) {
+}
+RawDataPresenter::~RawDataPresenter() {}
+
+void RawDataPresenter::FeedNext(const net::UploadElementReader& reader) {
+ if (!success_)
+ return;
+
+ if (reader.AsBytesReader()) {
+ const net::UploadBytesElementReader* bytes_reader = reader.AsBytesReader();
+ FeedNextBytes(bytes_reader->bytes(), bytes_reader->length());
+ } else if (reader.AsFileReader()) {
+ // Insert the file path instead of the contents, which may be too large.
+ const net::UploadFileElementReader* file_reader = reader.AsFileReader();
+ FeedNextFile(file_reader->path().AsUTF8Unsafe());
+ } else {
+ NOTIMPLEMENTED();
+ }
+}
+
+bool RawDataPresenter::Succeeded() {
+ return success_;
+}
+
+scoped_ptr<base::Value> RawDataPresenter::Result() {
+ if (!success_)
+ return nullptr;
+
+ return std::move(list_);
+}
+
+void RawDataPresenter::FeedNextBytes(const char* bytes, size_t size) {
+ subtle::AppendKeyValuePair(keys::kRequestBodyRawBytesKey,
+ BinaryValue::CreateWithCopiedBuffer(bytes, size),
+ list_.get());
+}
+
+void RawDataPresenter::FeedNextFile(const std::string& filename) {
+ // Insert the file path instead of the contents, which may be too large.
+ subtle::AppendKeyValuePair(keys::kRequestBodyRawFileKey,
+ new base::StringValue(filename),
+ list_.get());
+}
+
+ParsedDataPresenter::ParsedDataPresenter(const net::URLRequest& request)
+ : parser_(FormDataParser::Create(request)),
+ success_(parser_.get() != NULL),
+ dictionary_(success_ ? new base::DictionaryValue() : NULL) {
+}
+
+ParsedDataPresenter::~ParsedDataPresenter() {}
+
+void ParsedDataPresenter::FeedNext(const net::UploadElementReader& reader) {
+ if (!success_)
+ return;
+
+ const net::UploadBytesElementReader* bytes_reader = reader.AsBytesReader();
+ if (!bytes_reader) {
+ return;
+ }
+ if (!parser_->SetSource(base::StringPiece(bytes_reader->bytes(),
+ bytes_reader->length()))) {
+ Abort();
+ return;
+ }
+
+ FormDataParser::Result result;
+ while (parser_->GetNextNameValue(&result)) {
+ GetOrCreateList(dictionary_.get(), result.name())->Append(
+ new base::StringValue(result.value()));
+ }
+}
+
+bool ParsedDataPresenter::Succeeded() {
+ if (success_ && !parser_->AllDataReadOK())
+ Abort();
+ return success_;
+}
+
+scoped_ptr<base::Value> ParsedDataPresenter::Result() {
+ if (!success_)
+ return nullptr;
+
+ return std::move(dictionary_);
+}
+
+// static
+scoped_ptr<ParsedDataPresenter> ParsedDataPresenter::CreateForTests() {
+ const std::string form_type("application/x-www-form-urlencoded");
+ return scoped_ptr<ParsedDataPresenter>(new ParsedDataPresenter(form_type));
+}
+
+ParsedDataPresenter::ParsedDataPresenter(const std::string& form_type)
+ : parser_(FormDataParser::CreateFromContentTypeHeader(&form_type)),
+ success_(parser_.get() != NULL),
+ dictionary_(success_ ? new base::DictionaryValue() : NULL) {
+}
+
+void ParsedDataPresenter::Abort() {
+ success_ = false;
+ dictionary_.reset();
+ parser_.reset();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/web_request/upload_data_presenter.h b/chromium/extensions/browser/api/web_request/upload_data_presenter.h
new file mode 100644
index 00000000000..90e3d44796b
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/upload_data_presenter.h
@@ -0,0 +1,132 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_WEB_REQUEST_UPLOAD_DATA_PRESENTER_H_
+#define EXTENSIONS_BROWSER_API_WEB_REQUEST_UPLOAD_DATA_PRESENTER_H_
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+class Value;
+}
+
+namespace extensions {
+class FormDataParser;
+}
+
+namespace net {
+class URLRequest;
+class UploadElementReader;
+}
+
+namespace extensions {
+
+namespace subtle {
+
+// Helpers shared with unit-tests.
+
+// Appends a dictionary {'key': 'value'} to |list|. |list| becomes the owner of
+// |value|.
+void AppendKeyValuePair(const char* key,
+ base::Value* value,
+ base::ListValue* list);
+
+} // namespace subtle
+
+FORWARD_DECLARE_TEST(WebRequestUploadDataPresenterTest, RawData);
+
+// UploadDataPresenter is an interface for objects capable to consume a series
+// of UploadElementReader and represent this data as a base:Value.
+//
+// Workflow for objects implementing this interface:
+// 1. Call object->FeedNext(reader) for each element from the request's body.
+// 2. Check if object->Succeeded().
+// 3. If that check passed then retrieve object->Result().
+class UploadDataPresenter {
+ public:
+ virtual ~UploadDataPresenter();
+ virtual void FeedNext(const net::UploadElementReader& reader) = 0;
+ virtual bool Succeeded() = 0;
+ virtual scoped_ptr<base::Value> Result() = 0;
+
+ protected:
+ UploadDataPresenter() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UploadDataPresenter);
+};
+
+// This class passes all the bytes from bytes elements as a BinaryValue for each
+// such element. File elements are presented as StringValue containing the path
+// for that file.
+class RawDataPresenter : public UploadDataPresenter {
+ public:
+ RawDataPresenter();
+ ~RawDataPresenter() override;
+
+ // Implementation of UploadDataPresenter.
+ void FeedNext(const net::UploadElementReader& reader) override;
+ bool Succeeded() override;
+ scoped_ptr<base::Value> Result() override;
+
+ private:
+ void FeedNextBytes(const char* bytes, size_t size);
+ void FeedNextFile(const std::string& filename);
+ FRIEND_TEST_ALL_PREFIXES(WebRequestUploadDataPresenterTest, RawData);
+
+ bool success_;
+ scoped_ptr<base::ListValue> list_;
+
+ DISALLOW_COPY_AND_ASSIGN(RawDataPresenter);
+};
+
+// This class inspects the contents of bytes elements. It uses the
+// parser classes inheriting from FormDataParser to parse the concatenated
+// content of such elements. If the parsing is successful, the parsed form is
+// returned as a DictionaryValue. For example, a form consisting of
+// <input name="check" type="checkbox" value="A" checked />
+// <input name="check" type="checkbox" value="B" checked />
+// <input name="text" type="text" value="abc" />
+// would be represented as {"check": ["A", "B"], "text": ["abc"]} (although as a
+// DictionaryValue, not as a JSON string).
+class ParsedDataPresenter : public UploadDataPresenter {
+ public:
+ explicit ParsedDataPresenter(const net::URLRequest& request);
+ ~ParsedDataPresenter() override;
+
+ // Implementation of UploadDataPresenter.
+ void FeedNext(const net::UploadElementReader& reader) override;
+ bool Succeeded() override;
+ scoped_ptr<base::Value> Result() override;
+
+ // Allows to create ParsedDataPresenter without the URLRequest. Uses the
+ // parser for "application/x-www-form-urlencoded" form encoding. Only use this
+ // in tests.
+ static scoped_ptr<ParsedDataPresenter> CreateForTests();
+
+ private:
+ // This constructor is used in CreateForTests.
+ explicit ParsedDataPresenter(const std::string& form_type);
+
+ // Clears resources and the success flag.
+ void Abort();
+ scoped_ptr<FormDataParser> parser_;
+ bool success_;
+ scoped_ptr<base::DictionaryValue> dictionary_;
+
+ DISALLOW_COPY_AND_ASSIGN(ParsedDataPresenter);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_WEB_REQUEST_UPLOAD_DATA_PRESENTER_H_
diff --git a/chromium/extensions/browser/api/web_request/upload_data_presenter_unittest.cc b/chromium/extensions/browser/api/web_request/upload_data_presenter_unittest.cc
new file mode 100644
index 00000000000..f28619dfbd3
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/upload_data_presenter_unittest.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/values.h"
+#include "extensions/browser/api/web_request/upload_data_presenter.h"
+#include "extensions/browser/api/web_request/web_request_api_constants.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace keys = extension_web_request_api_constants;
+
+namespace extensions {
+
+// This only tests the handling of dots in keys. Other functionality is covered
+// by ExtensionWebRequestTest.AccessRequestBodyData and
+// WebRequestFormDataParserTest.
+TEST(WebRequestUploadDataPresenterTest, ParsedData) {
+ // Input.
+ const char block[] = "key.with.dots=value";
+ net::UploadBytesElementReader element(block, sizeof(block) - 1);
+
+ // Expected output.
+ scoped_ptr<base::ListValue> values(new base::ListValue);
+ values->Append(new base::StringValue("value"));
+ base::DictionaryValue expected_form;
+ expected_form.SetWithoutPathExpansion("key.with.dots", values.release());
+
+ // Real output.
+ scoped_ptr<ParsedDataPresenter> parsed_data_presenter(
+ ParsedDataPresenter::CreateForTests());
+ ASSERT_TRUE(parsed_data_presenter.get() != NULL);
+ parsed_data_presenter->FeedNext(element);
+ EXPECT_TRUE(parsed_data_presenter->Succeeded());
+ scoped_ptr<base::Value> result = parsed_data_presenter->Result();
+ ASSERT_TRUE(result.get() != NULL);
+
+ EXPECT_TRUE(result->Equals(&expected_form));
+}
+
+TEST(WebRequestUploadDataPresenterTest, RawData) {
+ // Input.
+ const char block1[] = "test";
+ const size_t block1_size = sizeof(block1) - 1;
+ const char kFilename[] = "path/test_filename.ext";
+ const char block2[] = "another test";
+ const size_t block2_size = sizeof(block2) - 1;
+
+ // Expected output.
+ scoped_ptr<base::BinaryValue> expected_a(
+ base::BinaryValue::CreateWithCopiedBuffer(block1, block1_size));
+ ASSERT_TRUE(expected_a.get() != NULL);
+ scoped_ptr<base::StringValue> expected_b(
+ new base::StringValue(kFilename));
+ ASSERT_TRUE(expected_b.get() != NULL);
+ scoped_ptr<base::BinaryValue> expected_c(
+ base::BinaryValue::CreateWithCopiedBuffer(block2, block2_size));
+ ASSERT_TRUE(expected_c.get() != NULL);
+
+ base::ListValue expected_list;
+ subtle::AppendKeyValuePair(
+ keys::kRequestBodyRawBytesKey, expected_a.release(), &expected_list);
+ subtle::AppendKeyValuePair(
+ keys::kRequestBodyRawFileKey, expected_b.release(), &expected_list);
+ subtle::AppendKeyValuePair(
+ keys::kRequestBodyRawBytesKey, expected_c.release(), &expected_list);
+
+ // Real output.
+ RawDataPresenter raw_presenter;
+ raw_presenter.FeedNextBytes(block1, block1_size);
+ raw_presenter.FeedNextFile(kFilename);
+ raw_presenter.FeedNextBytes(block2, block2_size);
+ EXPECT_TRUE(raw_presenter.Succeeded());
+ scoped_ptr<base::Value> result = raw_presenter.Result();
+ ASSERT_TRUE(result.get() != NULL);
+
+ EXPECT_TRUE(result->Equals(&expected_list));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/web_request/web_request_api.cc b/chromium/extensions/browser/api/web_request/web_request_api.cc
new file mode 100644
index 00000000000..d8da8ecdc05
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/web_request_api.cc
@@ -0,0 +1,2304 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/web_request/web_request_api.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/json/json_writer.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/resource_request_info.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/common/child_process_host.h"
+#include "extensions/browser/api/activity_log/web_request_constants.h"
+#include "extensions/browser/api/declarative/rules_registry_service.h"
+#include "extensions/browser/api/declarative_webrequest/request_stage.h"
+#include "extensions/browser/api/declarative_webrequest/webrequest_constants.h"
+#include "extensions/browser/api/declarative_webrequest/webrequest_rules_registry.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/api/web_request/web_request_api_constants.h"
+#include "extensions/browser/api/web_request/web_request_api_helpers.h"
+#include "extensions/browser/api/web_request/web_request_event_details.h"
+#include "extensions/browser/api/web_request/web_request_event_router_delegate.h"
+#include "extensions/browser/api/web_request/web_request_time_tracker.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/guest_view/guest_view_events.h"
+#include "extensions/browser/guest_view/web_view/web_view_constants.h"
+#include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
+#include "extensions/browser/info_map.h"
+#include "extensions/browser/io_thread_extension_message_filter.h"
+#include "extensions/browser/runtime_data.h"
+#include "extensions/browser/warning_service.h"
+#include "extensions/browser/warning_set.h"
+#include "extensions/common/api/web_request.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/event_filtering_info.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/url_pattern.h"
+#include "extensions/strings/grit/extensions_strings.h"
+#include "net/base/auth.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_util.h"
+#include "net/url_request/url_request.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
+
+using content::BrowserThread;
+using content::ResourceRequestInfo;
+using extension_web_request_api_helpers::ExtraInfoSpec;
+
+namespace activity_log = activity_log_web_request_constants;
+namespace helpers = extension_web_request_api_helpers;
+namespace keys = extension_web_request_api_constants;
+
+namespace extensions {
+
+namespace declarative_keys = declarative_webrequest_constants;
+namespace web_request = api::web_request;
+
+namespace {
+
+const char kWebRequestEventPrefix[] = "webRequest.";
+
+// List of all the webRequest events.
+const char* const kWebRequestEvents[] = {
+ keys::kOnBeforeRedirectEvent,
+ web_request::OnBeforeRequest::kEventName,
+ keys::kOnBeforeSendHeadersEvent,
+ keys::kOnCompletedEvent,
+ web_request::OnErrorOccurred::kEventName,
+ keys::kOnSendHeadersEvent,
+ keys::kOnAuthRequiredEvent,
+ keys::kOnResponseStartedEvent,
+ keys::kOnHeadersReceivedEvent,
+};
+
+const char* GetRequestStageAsString(
+ ExtensionWebRequestEventRouter::EventTypes type) {
+ switch (type) {
+ case ExtensionWebRequestEventRouter::kInvalidEvent:
+ return "Invalid";
+ case ExtensionWebRequestEventRouter::kOnBeforeRequest:
+ return keys::kOnBeforeRequest;
+ case ExtensionWebRequestEventRouter::kOnBeforeSendHeaders:
+ return keys::kOnBeforeSendHeaders;
+ case ExtensionWebRequestEventRouter::kOnSendHeaders:
+ return keys::kOnSendHeaders;
+ case ExtensionWebRequestEventRouter::kOnHeadersReceived:
+ return keys::kOnHeadersReceived;
+ case ExtensionWebRequestEventRouter::kOnBeforeRedirect:
+ return keys::kOnBeforeRedirect;
+ case ExtensionWebRequestEventRouter::kOnAuthRequired:
+ return keys::kOnAuthRequired;
+ case ExtensionWebRequestEventRouter::kOnResponseStarted:
+ return keys::kOnResponseStarted;
+ case ExtensionWebRequestEventRouter::kOnErrorOccurred:
+ return keys::kOnErrorOccurred;
+ case ExtensionWebRequestEventRouter::kOnCompleted:
+ return keys::kOnCompleted;
+ }
+ NOTREACHED();
+ return "Not reached";
+}
+
+bool IsWebRequestEvent(const std::string& event_name) {
+ std::string web_request_event_name(event_name);
+ if (base::StartsWith(web_request_event_name,
+ webview::kWebViewEventPrefix,
+ base::CompareCase::SENSITIVE)) {
+ web_request_event_name.replace(
+ 0, strlen(webview::kWebViewEventPrefix), kWebRequestEventPrefix);
+ }
+ const auto web_request_events_end =
+ kWebRequestEvents + arraysize(kWebRequestEvents);
+ return std::find(kWebRequestEvents, web_request_events_end,
+ web_request_event_name) != web_request_events_end;
+}
+
+// Returns whether |request| has been triggered by an extension in
+// |extension_info_map|.
+bool IsRequestFromExtension(const net::URLRequest* request,
+ const InfoMap* extension_info_map) {
+ // |extension_info_map| is NULL for system-level requests.
+ if (!extension_info_map)
+ return false;
+
+ const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request);
+
+ // If this request was not created by the ResourceDispatcher, |info| is NULL.
+ // All requests from extensions are created by the ResourceDispatcher.
+ if (!info)
+ return false;
+
+ const std::set<std::string> extension_ids =
+ extension_info_map->process_map().GetExtensionsInProcess(
+ info->GetChildID());
+ if (extension_ids.empty())
+ return false;
+
+ // Treat hosted apps as normal web pages (crbug.com/526413).
+ for (const std::string& extension_id : extension_ids) {
+ const Extension* extension =
+ extension_info_map->extensions().GetByID(extension_id);
+ if (extension && !extension->is_hosted_app())
+ return true;
+ }
+ return false;
+}
+
+void ExtractRequestRoutingInfo(const net::URLRequest* request,
+ int* render_process_host_id,
+ int* routing_id) {
+ const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request);
+ if (!info)
+ return;
+ *render_process_host_id = info->GetChildID();
+ *routing_id = info->GetRouteID();
+}
+
+// Given a |request|, this function determines whether it originated from
+// a <webview> guest process or not. If it is from a <webview> guest process,
+// then |web_view_info| is returned with information about the instance ID
+// that uniquely identifies the <webview> and its embedder.
+bool GetWebViewInfo(const net::URLRequest* request,
+ WebViewRendererState::WebViewInfo* web_view_info) {
+ int render_process_host_id = -1;
+ int routing_id = -1;
+ ExtractRequestRoutingInfo(request, &render_process_host_id, &routing_id);
+ return WebViewRendererState::GetInstance()->GetInfo(
+ render_process_host_id, routing_id, web_view_info);
+}
+
+// Converts a HttpHeaders dictionary to a |name|, |value| pair. Returns
+// true if successful.
+bool FromHeaderDictionary(const base::DictionaryValue* header_value,
+ std::string* name,
+ std::string* value) {
+ if (!header_value->GetString(keys::kHeaderNameKey, name))
+ return false;
+
+ // We require either a "value" or a "binaryValue" entry.
+ if (!(header_value->HasKey(keys::kHeaderValueKey) ^
+ header_value->HasKey(keys::kHeaderBinaryValueKey))) {
+ return false;
+ }
+
+ if (header_value->HasKey(keys::kHeaderValueKey)) {
+ if (!header_value->GetString(keys::kHeaderValueKey, value)) {
+ return false;
+ }
+ } else if (header_value->HasKey(keys::kHeaderBinaryValueKey)) {
+ const base::ListValue* list = NULL;
+ if (!header_value->HasKey(keys::kHeaderBinaryValueKey)) {
+ *value = "";
+ } else if (!header_value->GetList(keys::kHeaderBinaryValueKey, &list) ||
+ !helpers::CharListToString(list, value)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Sends an event to subscribers of chrome.declarativeWebRequest.onMessage or
+// to subscribers of webview.onMessage if the action is being operated upon
+// a <webview> guest renderer.
+// |extension_id| identifies the extension that sends and receives the event.
+// |is_web_view_guest| indicates whether the action is for a <webview>.
+// |web_view_info| is a struct containing information about the <webview>
+// embedder.
+// |event_details| is passed to the event listener.
+void SendOnMessageEventOnUI(
+ void* browser_context_id,
+ const std::string& extension_id,
+ bool is_web_view_guest,
+ const WebViewRendererState::WebViewInfo& web_view_info,
+ scoped_ptr<WebRequestEventDetails> event_details) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ content::BrowserContext* browser_context =
+ reinterpret_cast<content::BrowserContext*>(browser_context_id);
+ if (!ExtensionsBrowserClient::Get()->IsValidContext(browser_context))
+ return;
+
+ scoped_ptr<base::ListValue> event_args(new base::ListValue);
+ event_details->DetermineFrameIdOnUI();
+ event_args->Append(event_details->GetAndClearDict());
+
+ EventRouter* event_router = EventRouter::Get(browser_context);
+
+ EventFilteringInfo event_filtering_info;
+
+ events::HistogramValue histogram_value = events::UNKNOWN;
+ std::string event_name;
+ // The instance ID uniquely identifies a <webview> instance within an embedder
+ // process. We use a filter here so that only event listeners for a particular
+ // <webview> will fire.
+ if (is_web_view_guest) {
+ event_filtering_info.SetInstanceID(web_view_info.instance_id);
+ histogram_value = events::WEB_VIEW_INTERNAL_ON_MESSAGE;
+ event_name = webview::kEventMessage;
+ } else {
+ histogram_value = events::DECLARATIVE_WEB_REQUEST_ON_MESSAGE;
+ event_name = declarative_keys::kOnMessage;
+ }
+
+ scoped_ptr<Event> event(new Event(
+ histogram_value, event_name, std::move(event_args), browser_context,
+ GURL(), EventRouter::USER_GESTURE_UNKNOWN, event_filtering_info));
+ event_router->DispatchEventToExtension(extension_id, std::move(event));
+}
+
+void RemoveEventListenerOnIOThread(
+ void* browser_context,
+ const std::string& extension_id,
+ const std::string& sub_event_name,
+ int embedder_process_id,
+ int web_view_instance_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
+ browser_context, extension_id, sub_event_name,
+ embedder_process_id, web_view_instance_id);
+}
+
+events::HistogramValue GetEventHistogramValue(const std::string& event_name) {
+ // Event names will either be webRequest events, or guest view (probably web
+ // view) events that map to webRequest events. Check webRequest first.
+ static struct ValueAndName {
+ events::HistogramValue histogram_value;
+ const char* const event_name;
+ } values_and_names[] = {
+ {events::WEB_REQUEST_ON_BEFORE_REDIRECT, keys::kOnBeforeRedirectEvent},
+ {events::WEB_REQUEST_ON_BEFORE_REQUEST,
+ web_request::OnBeforeRequest::kEventName},
+ {events::WEB_REQUEST_ON_BEFORE_SEND_HEADERS,
+ keys::kOnBeforeSendHeadersEvent},
+ {events::WEB_REQUEST_ON_COMPLETED, keys::kOnCompletedEvent},
+ {events::WEB_REQUEST_ON_ERROR_OCCURRED,
+ web_request::OnErrorOccurred::kEventName},
+ {events::WEB_REQUEST_ON_SEND_HEADERS, keys::kOnSendHeadersEvent},
+ {events::WEB_REQUEST_ON_AUTH_REQUIRED, keys::kOnAuthRequiredEvent},
+ {events::WEB_REQUEST_ON_RESPONSE_STARTED, keys::kOnResponseStartedEvent},
+ {events::WEB_REQUEST_ON_HEADERS_RECEIVED, keys::kOnHeadersReceivedEvent}};
+ static_assert(arraysize(kWebRequestEvents) == arraysize(values_and_names),
+ "kWebRequestEvents and values_and_names must be the same");
+ for (const ValueAndName& value_and_name : values_and_names) {
+ if (value_and_name.event_name == event_name)
+ return value_and_name.histogram_value;
+ }
+
+ // If there is no webRequest event, it might be a guest view webRequest event.
+ events::HistogramValue guest_view_histogram_value =
+ guest_view_events::GetEventHistogramValue(event_name);
+ if (guest_view_histogram_value != events::UNKNOWN)
+ return guest_view_histogram_value;
+
+ // There is no histogram value for this event name. It should be added to
+ // either the mapping here, or in guest_view_events.
+ NOTREACHED() << "Event " << event_name << " must have a histogram value";
+ return events::UNKNOWN;
+}
+
+// We hide events from the system context as well as sensitive requests.
+bool ShouldHideEvent(void* browser_context,
+ const InfoMap* extension_info_map,
+ const net::URLRequest* request) {
+ return (!browser_context ||
+ WebRequestPermissions::HideRequest(extension_info_map, request));
+}
+
+} // namespace
+
+WebRequestAPI::WebRequestAPI(content::BrowserContext* context)
+ : browser_context_(context) {
+ EventRouter* event_router = EventRouter::Get(browser_context_);
+ for (size_t i = 0; i < arraysize(kWebRequestEvents); ++i) {
+ // Observe the webRequest event.
+ std::string event_name = kWebRequestEvents[i];
+ event_router->RegisterObserver(this, event_name);
+
+ // Also observe the corresponding webview event.
+ event_name.replace(
+ 0, sizeof(kWebRequestEventPrefix) - 1, webview::kWebViewEventPrefix);
+ event_router->RegisterObserver(this, event_name);
+ }
+}
+
+WebRequestAPI::~WebRequestAPI() {
+ EventRouter::Get(browser_context_)->UnregisterObserver(this);
+}
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<WebRequestAPI> >
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<WebRequestAPI>*
+WebRequestAPI::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+void WebRequestAPI::OnListenerRemoved(const EventListenerInfo& details) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ // Note that details.event_name includes the sub-event details (e.g. "/123").
+ // TODO(fsamuel): <webview> events will not be removed through this code path.
+ // <webview> events will be removed in RemoveWebViewEventListeners. Ideally,
+ // this code should be decoupled from extensions, we should use the host ID
+ // instead, and not have two different code paths. This is a huge undertaking
+ // unfortunately, so we'll resort to two code paths for now.
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&RemoveEventListenerOnIOThread,
+ details.browser_context,
+ details.extension_id,
+ details.event_name,
+ 0 /* embedder_process_id (ignored) */,
+ 0 /* web_view_instance_id */));
+}
+
+// Represents a single unique listener to an event, along with whatever filter
+// parameters and extra_info_spec were specified at the time the listener was
+// added.
+// NOTE(benjhayden) New APIs should not use this sub_event_name trick! It does
+// not play well with event pages. See downloads.onDeterminingFilename and
+// ExtensionDownloadsEventRouter for an alternative approach.
+struct ExtensionWebRequestEventRouter::EventListener {
+ std::string extension_id;
+ std::string extension_name;
+ events::HistogramValue histogram_value;
+ std::string sub_event_name;
+ RequestFilter filter;
+ int extra_info_spec;
+ int embedder_process_id;
+ int web_view_instance_id;
+ base::WeakPtr<IPC::Sender> ipc_sender;
+ mutable std::set<uint64_t> blocked_requests;
+
+ // Comparator to work with std::set.
+ bool operator<(const EventListener& that) const {
+ if (extension_id != that.extension_id)
+ return extension_id < that.extension_id;
+
+ if (sub_event_name != that.sub_event_name)
+ return sub_event_name < that.sub_event_name;
+
+ if (web_view_instance_id != that.web_view_instance_id)
+ return web_view_instance_id < that.web_view_instance_id;
+
+ if (web_view_instance_id == 0) {
+ // Do not filter by process ID for non-webviews, because this comparator
+ // is also used to find and remove an event listener when an extension is
+ // unloaded. At this point, the event listener cannot be mapped back to
+ // the original process, so 0 is used instead of the actual process ID.
+ if (embedder_process_id == 0 || that.embedder_process_id == 0)
+ return false;
+ }
+
+ if (embedder_process_id != that.embedder_process_id)
+ return embedder_process_id < that.embedder_process_id;
+
+ return false;
+ }
+
+ EventListener()
+ : histogram_value(events::UNKNOWN),
+ extra_info_spec(0),
+ embedder_process_id(0),
+ web_view_instance_id(0) {}
+};
+
+// Contains info about requests that are blocked waiting for a response from
+// an extension.
+struct ExtensionWebRequestEventRouter::BlockedRequest {
+ // The request that is being blocked.
+ net::URLRequest* request;
+
+ // Whether the request originates from an incognito tab.
+ bool is_incognito;
+
+ // The event that we're currently blocked on.
+ EventTypes event;
+
+ // The number of event handlers that we are awaiting a response from.
+ int num_handlers_blocking;
+
+ // Pointer to NetLog to report significant changes to the request for
+ // debugging.
+ const net::BoundNetLog* net_log;
+
+ // The callback to call when we get a response from all event handlers.
+ net::CompletionCallback callback;
+
+ // If non-empty, this contains the new URL that the request will redirect to.
+ // Only valid for OnBeforeRequest and OnHeadersReceived.
+ GURL* new_url;
+
+ // The request headers that will be issued along with this request. Only valid
+ // for OnBeforeSendHeaders.
+ net::HttpRequestHeaders* request_headers;
+
+ // The response headers that were received from the server. Only valid for
+ // OnHeadersReceived.
+ scoped_refptr<const net::HttpResponseHeaders> original_response_headers;
+
+ // Location where to override response headers. Only valid for
+ // OnHeadersReceived.
+ scoped_refptr<net::HttpResponseHeaders>* override_response_headers;
+
+ // If non-empty, this contains the auth credentials that may be filled in.
+ // Only valid for OnAuthRequired.
+ net::AuthCredentials* auth_credentials;
+
+ // The callback to invoke for auth. If |auth_callback.is_null()| is false,
+ // |callback| must be NULL.
+ // Only valid for OnAuthRequired.
+ net::NetworkDelegate::AuthCallback auth_callback;
+
+ // Time the request was paused. Used for logging purposes.
+ base::Time blocking_time;
+
+ // Changes requested by extensions.
+ helpers::EventResponseDeltas response_deltas;
+
+ // Provider of meta data about extensions, only used and non-NULL for events
+ // that are delayed until the rules registry is ready.
+ const InfoMap* extension_info_map;
+
+ BlockedRequest()
+ : request(NULL),
+ is_incognito(false),
+ event(kInvalidEvent),
+ num_handlers_blocking(0),
+ net_log(NULL),
+ new_url(NULL),
+ request_headers(NULL),
+ override_response_headers(NULL),
+ auth_credentials(NULL),
+ extension_info_map(NULL) {}
+};
+
+bool ExtensionWebRequestEventRouter::RequestFilter::InitFromValue(
+ const base::DictionaryValue& value, std::string* error) {
+ if (!value.HasKey("urls"))
+ return false;
+
+ for (base::DictionaryValue::Iterator it(value); !it.IsAtEnd(); it.Advance()) {
+ if (it.key() == "urls") {
+ const base::ListValue* urls_value = NULL;
+ if (!it.value().GetAsList(&urls_value))
+ return false;
+ for (size_t i = 0; i < urls_value->GetSize(); ++i) {
+ std::string url;
+ URLPattern pattern(
+ URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS |
+ URLPattern::SCHEME_FTP | URLPattern::SCHEME_FILE |
+ URLPattern::SCHEME_EXTENSION);
+ if (!urls_value->GetString(i, &url) ||
+ pattern.Parse(url) != URLPattern::PARSE_SUCCESS) {
+ *error = ErrorUtils::FormatErrorMessage(
+ keys::kInvalidRequestFilterUrl, url);
+ return false;
+ }
+ urls.AddPattern(pattern);
+ }
+ } else if (it.key() == "types") {
+ const base::ListValue* types_value = NULL;
+ if (!it.value().GetAsList(&types_value))
+ return false;
+ for (size_t i = 0; i < types_value->GetSize(); ++i) {
+ std::string type_str;
+ if (!types_value->GetString(i, &type_str) ||
+ !helpers::ParseResourceType(type_str, &types)) {
+ return false;
+ }
+ }
+ } else if (it.key() == "tabId") {
+ if (!it.value().GetAsInteger(&tab_id))
+ return false;
+ } else if (it.key() == "windowId") {
+ if (!it.value().GetAsInteger(&window_id))
+ return false;
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+ExtensionWebRequestEventRouter::EventResponse::EventResponse(
+ const std::string& extension_id, const base::Time& extension_install_time)
+ : extension_id(extension_id),
+ extension_install_time(extension_install_time),
+ cancel(false) {
+}
+
+ExtensionWebRequestEventRouter::EventResponse::~EventResponse() {
+}
+
+ExtensionWebRequestEventRouter::RequestFilter::RequestFilter()
+ : tab_id(-1), window_id(-1) {
+}
+
+ExtensionWebRequestEventRouter::RequestFilter::RequestFilter(
+ const RequestFilter& other) = default;
+
+ExtensionWebRequestEventRouter::RequestFilter::~RequestFilter() {
+}
+
+//
+// ExtensionWebRequestEventRouter
+//
+
+// static
+ExtensionWebRequestEventRouter* ExtensionWebRequestEventRouter::GetInstance() {
+ return base::Singleton<ExtensionWebRequestEventRouter>::get();
+}
+
+ExtensionWebRequestEventRouter::ExtensionWebRequestEventRouter()
+ : request_time_tracker_(new ExtensionWebRequestTimeTracker) {
+ web_request_event_router_delegate_.reset(
+ ExtensionsAPIClient::Get()->CreateWebRequestEventRouterDelegate());
+}
+
+ExtensionWebRequestEventRouter::~ExtensionWebRequestEventRouter() {
+}
+
+void ExtensionWebRequestEventRouter::RegisterRulesRegistry(
+ void* browser_context,
+ int rules_registry_id,
+ scoped_refptr<WebRequestRulesRegistry> rules_registry) {
+ RulesRegistryKey key(browser_context, rules_registry_id);
+ if (rules_registry.get())
+ rules_registries_[key] = rules_registry;
+ else
+ rules_registries_.erase(key);
+}
+
+scoped_ptr<WebRequestEventDetails>
+ExtensionWebRequestEventRouter::CreateEventDetails(
+ const net::URLRequest* request,
+ int extra_info_spec) {
+ scoped_ptr<WebRequestEventDetails> event_details(
+ new WebRequestEventDetails(request, extra_info_spec));
+
+ int render_frame_id = -1;
+ int render_process_id = -1;
+ int tab_id = -1;
+ ExtensionApiFrameIdMap::FrameData frame_data;
+ if (content::ResourceRequestInfo::GetRenderFrameForRequest(
+ request, &render_process_id, &render_frame_id) &&
+ ExtensionApiFrameIdMap::Get()->GetCachedFrameDataOnIO(
+ render_process_id, render_frame_id, &frame_data)) {
+ tab_id = frame_data.tab_id;
+ }
+ event_details->SetInteger(keys::kTabIdKey, tab_id);
+ return event_details;
+}
+
+int ExtensionWebRequestEventRouter::OnBeforeRequest(
+ void* browser_context,
+ const InfoMap* extension_info_map,
+ net::URLRequest* request,
+ const net::CompletionCallback& callback,
+ GURL* new_url) {
+ if (ShouldHideEvent(browser_context, extension_info_map, request))
+ return net::OK;
+
+ if (IsPageLoad(request))
+ NotifyPageLoad();
+
+ request_time_tracker_->LogRequestStartTime(request->identifier(),
+ base::Time::Now(),
+ request->url(),
+ browser_context);
+
+ // Whether to initialized |blocked_requests_|.
+ bool initialize_blocked_requests = false;
+
+ initialize_blocked_requests |=
+ ProcessDeclarativeRules(browser_context, extension_info_map,
+ web_request::OnBeforeRequest::kEventName, request,
+ ON_BEFORE_REQUEST, NULL);
+
+ int extra_info_spec = 0;
+ EventListeners listeners = GetMatchingListeners(
+ browser_context, extension_info_map,
+ web_request::OnBeforeRequest::kEventName, request, &extra_info_spec);
+ if (!listeners.empty() &&
+ !GetAndSetSignaled(request->identifier(), kOnBeforeRequest)) {
+ scoped_ptr<WebRequestEventDetails> event_details(
+ CreateEventDetails(request, extra_info_spec));
+ event_details->SetRequestBody(request);
+
+ initialize_blocked_requests |= DispatchEvent(
+ browser_context, request, listeners, std::move(event_details));
+ }
+
+ if (!initialize_blocked_requests)
+ return net::OK; // Nobody saw a reason for modifying the request.
+
+ BlockedRequest& blocked_request = blocked_requests_[request->identifier()];
+ blocked_request.event = kOnBeforeRequest;
+ blocked_request.is_incognito |= IsIncognitoBrowserContext(browser_context);
+ blocked_request.request = request;
+ blocked_request.callback = callback;
+ blocked_request.new_url = new_url;
+ blocked_request.net_log = &request->net_log();
+
+ if (blocked_request.num_handlers_blocking == 0) {
+ // If there are no blocking handlers, only the declarative rules tried
+ // to modify the request and we can respond synchronously.
+ return ExecuteDeltas(browser_context, request->identifier(),
+ false /* call_callback*/);
+ }
+ return net::ERR_IO_PENDING;
+}
+
+int ExtensionWebRequestEventRouter::OnBeforeSendHeaders(
+ void* browser_context,
+ const InfoMap* extension_info_map,
+ net::URLRequest* request,
+ const net::CompletionCallback& callback,
+ net::HttpRequestHeaders* headers) {
+ if (ShouldHideEvent(browser_context, extension_info_map, request))
+ return net::OK;
+
+ bool initialize_blocked_requests = false;
+
+ initialize_blocked_requests |= ProcessDeclarativeRules(
+ browser_context, extension_info_map, keys::kOnBeforeSendHeadersEvent,
+ request, ON_BEFORE_SEND_HEADERS, NULL);
+
+ int extra_info_spec = 0;
+ EventListeners listeners = GetMatchingListeners(
+ browser_context, extension_info_map, keys::kOnBeforeSendHeadersEvent,
+ request, &extra_info_spec);
+ if (!listeners.empty() &&
+ !GetAndSetSignaled(request->identifier(), kOnBeforeSendHeaders)) {
+ scoped_ptr<WebRequestEventDetails> event_details(
+ CreateEventDetails(request, extra_info_spec));
+ event_details->SetRequestHeaders(*headers);
+
+ initialize_blocked_requests |= DispatchEvent(
+ browser_context, request, listeners, std::move(event_details));
+ }
+
+ if (!initialize_blocked_requests)
+ return net::OK; // Nobody saw a reason for modifying the request.
+
+ BlockedRequest& blocked_request = blocked_requests_[request->identifier()];
+ blocked_request.event = kOnBeforeSendHeaders;
+ blocked_request.is_incognito |= IsIncognitoBrowserContext(browser_context);
+ blocked_request.request = request;
+ blocked_request.callback = callback;
+ blocked_request.request_headers = headers;
+ blocked_request.net_log = &request->net_log();
+
+ if (blocked_request.num_handlers_blocking == 0) {
+ // If there are no blocking handlers, only the declarative rules tried
+ // to modify the request and we can respond synchronously.
+ return ExecuteDeltas(browser_context, request->identifier(),
+ false /* call_callback*/);
+ }
+ return net::ERR_IO_PENDING;
+}
+
+void ExtensionWebRequestEventRouter::OnSendHeaders(
+ void* browser_context,
+ const InfoMap* extension_info_map,
+ net::URLRequest* request,
+ const net::HttpRequestHeaders& headers) {
+ if (ShouldHideEvent(browser_context, extension_info_map, request))
+ return;
+
+ if (GetAndSetSignaled(request->identifier(), kOnSendHeaders))
+ return;
+
+ ClearSignaled(request->identifier(), kOnBeforeRedirect);
+
+ int extra_info_spec = 0;
+ EventListeners listeners = GetMatchingListeners(
+ browser_context, extension_info_map, keys::kOnSendHeadersEvent, request,
+ &extra_info_spec);
+ if (listeners.empty())
+ return;
+
+ scoped_ptr<WebRequestEventDetails> event_details(
+ CreateEventDetails(request, extra_info_spec));
+ event_details->SetRequestHeaders(headers);
+
+ DispatchEvent(browser_context, request, listeners, std::move(event_details));
+}
+
+int ExtensionWebRequestEventRouter::OnHeadersReceived(
+ void* browser_context,
+ const InfoMap* extension_info_map,
+ net::URLRequest* request,
+ const net::CompletionCallback& callback,
+ const net::HttpResponseHeaders* original_response_headers,
+ scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
+ GURL* allowed_unsafe_redirect_url) {
+ if (ShouldHideEvent(browser_context, extension_info_map, request))
+ return net::OK;
+
+ bool initialize_blocked_requests = false;
+
+ initialize_blocked_requests |= ProcessDeclarativeRules(
+ browser_context, extension_info_map, keys::kOnHeadersReceivedEvent,
+ request, ON_HEADERS_RECEIVED, original_response_headers);
+
+ int extra_info_spec = 0;
+ EventListeners listeners = GetMatchingListeners(
+ browser_context, extension_info_map, keys::kOnHeadersReceivedEvent,
+ request, &extra_info_spec);
+
+ if (!listeners.empty() &&
+ !GetAndSetSignaled(request->identifier(), kOnHeadersReceived)) {
+ scoped_ptr<WebRequestEventDetails> event_details(
+ CreateEventDetails(request, extra_info_spec));
+ event_details->SetResponseHeaders(request, original_response_headers);
+
+ initialize_blocked_requests |= DispatchEvent(
+ browser_context, request, listeners, std::move(event_details));
+ }
+
+ if (!initialize_blocked_requests)
+ return net::OK; // Nobody saw a reason for modifying the request.
+
+ BlockedRequest& blocked_request = blocked_requests_[request->identifier()];
+ blocked_request.event = kOnHeadersReceived;
+ blocked_request.is_incognito |= IsIncognitoBrowserContext(browser_context);
+ blocked_request.request = request;
+ blocked_request.callback = callback;
+ blocked_request.net_log = &request->net_log();
+ blocked_request.override_response_headers = override_response_headers;
+ blocked_request.original_response_headers = original_response_headers;
+ blocked_request.new_url = allowed_unsafe_redirect_url;
+
+ if (blocked_request.num_handlers_blocking == 0) {
+ // If there are no blocking handlers, only the declarative rules tried
+ // to modify the request and we can respond synchronously.
+ return ExecuteDeltas(browser_context, request->identifier(),
+ false /* call_callback*/);
+ }
+ return net::ERR_IO_PENDING;
+}
+
+net::NetworkDelegate::AuthRequiredResponse
+ExtensionWebRequestEventRouter::OnAuthRequired(
+ void* browser_context,
+ const InfoMap* extension_info_map,
+ net::URLRequest* request,
+ const net::AuthChallengeInfo& auth_info,
+ const net::NetworkDelegate::AuthCallback& callback,
+ net::AuthCredentials* credentials) {
+ // No browser_context means that this is for authentication challenges in the
+ // system context. Skip in that case. Also skip sensitive requests.
+ if (!browser_context ||
+ WebRequestPermissions::HideRequest(extension_info_map, request)) {
+ return net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION;
+ }
+
+ int extra_info_spec = 0;
+ EventListeners listeners = GetMatchingListeners(
+ browser_context, extension_info_map, keys::kOnAuthRequiredEvent, request,
+ &extra_info_spec);
+ if (listeners.empty())
+ return net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION;
+
+ scoped_ptr<WebRequestEventDetails> event_details(
+ CreateEventDetails(request, extra_info_spec));
+ event_details->SetResponseHeaders(request, request->response_headers());
+ event_details->SetAuthInfo(auth_info);
+
+ if (DispatchEvent(browser_context, request, listeners,
+ std::move(event_details))) {
+ BlockedRequest& blocked_request = blocked_requests_[request->identifier()];
+ blocked_request.event = kOnAuthRequired;
+ blocked_request.is_incognito |= IsIncognitoBrowserContext(browser_context);
+ blocked_request.request = request;
+ blocked_request.auth_callback = callback;
+ blocked_request.auth_credentials = credentials;
+ blocked_request.net_log = &request->net_log();
+ return net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_IO_PENDING;
+ }
+ return net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION;
+}
+
+void ExtensionWebRequestEventRouter::OnBeforeRedirect(
+ void* browser_context,
+ const InfoMap* extension_info_map,
+ net::URLRequest* request,
+ const GURL& new_location) {
+ if (ShouldHideEvent(browser_context, extension_info_map, request))
+ return;
+
+ if (GetAndSetSignaled(request->identifier(), kOnBeforeRedirect))
+ return;
+
+ ClearSignaled(request->identifier(), kOnBeforeRequest);
+ ClearSignaled(request->identifier(), kOnBeforeSendHeaders);
+ ClearSignaled(request->identifier(), kOnSendHeaders);
+ ClearSignaled(request->identifier(), kOnHeadersReceived);
+
+ int extra_info_spec = 0;
+ EventListeners listeners = GetMatchingListeners(
+ browser_context, extension_info_map, keys::kOnBeforeRedirectEvent,
+ request, &extra_info_spec);
+ if (listeners.empty())
+ return;
+
+ scoped_ptr<WebRequestEventDetails> event_details(
+ CreateEventDetails(request, extra_info_spec));
+ event_details->SetResponseHeaders(request, request->response_headers());
+ event_details->SetResponseSource(request);
+ event_details->SetString(keys::kRedirectUrlKey, new_location.spec());
+
+ DispatchEvent(browser_context, request, listeners, std::move(event_details));
+}
+
+void ExtensionWebRequestEventRouter::OnResponseStarted(
+ void* browser_context,
+ const InfoMap* extension_info_map,
+ net::URLRequest* request) {
+ if (ShouldHideEvent(browser_context, extension_info_map, request))
+ return;
+
+ // OnResponseStarted is even triggered, when the request was cancelled.
+ if (request->status().status() != net::URLRequestStatus::SUCCESS)
+ return;
+
+ int extra_info_spec = 0;
+ EventListeners listeners = GetMatchingListeners(
+ browser_context, extension_info_map, keys::kOnResponseStartedEvent,
+ request, &extra_info_spec);
+ if (listeners.empty())
+ return;
+
+ scoped_ptr<WebRequestEventDetails> event_details(
+ CreateEventDetails(request, extra_info_spec));
+ event_details->SetResponseHeaders(request, request->response_headers());
+ event_details->SetResponseSource(request);
+
+ DispatchEvent(browser_context, request, listeners, std::move(event_details));
+}
+
+void ExtensionWebRequestEventRouter::OnCompleted(
+ void* browser_context,
+ const InfoMap* extension_info_map,
+ net::URLRequest* request) {
+ // We hide events from the system context as well as sensitive requests.
+ // However, if the request first became sensitive after redirecting we have
+ // already signaled it and thus we have to signal the end of it. This is
+ // risk-free because the handler cannot modify the request now.
+ if (!browser_context ||
+ (WebRequestPermissions::HideRequest(extension_info_map, request) &&
+ !WasSignaled(*request))) {
+ return;
+ }
+
+ request_time_tracker_->LogRequestEndTime(request->identifier(),
+ base::Time::Now());
+
+ DCHECK(request->status().status() == net::URLRequestStatus::SUCCESS);
+
+ DCHECK(!GetAndSetSignaled(request->identifier(), kOnCompleted));
+
+ ClearPendingCallbacks(request);
+
+ int extra_info_spec = 0;
+ EventListeners listeners =
+ GetMatchingListeners(browser_context, extension_info_map,
+ keys::kOnCompletedEvent, request, &extra_info_spec);
+ if (listeners.empty())
+ return;
+
+ scoped_ptr<WebRequestEventDetails> event_details(
+ CreateEventDetails(request, extra_info_spec));
+ event_details->SetResponseHeaders(request, request->response_headers());
+ event_details->SetResponseSource(request);
+
+ DispatchEvent(browser_context, request, listeners, std::move(event_details));
+}
+
+void ExtensionWebRequestEventRouter::OnErrorOccurred(
+ void* browser_context,
+ const InfoMap* extension_info_map,
+ net::URLRequest* request,
+ bool started) {
+ // We hide events from the system context as well as sensitive requests.
+ // However, if the request first became sensitive after redirecting we have
+ // already signaled it and thus we have to signal the end of it. This is
+ // risk-free because the handler cannot modify the request now.
+ if (!browser_context ||
+ (WebRequestPermissions::HideRequest(extension_info_map, request) &&
+ !WasSignaled(*request))) {
+ return;
+ }
+
+ request_time_tracker_->LogRequestEndTime(request->identifier(),
+ base::Time::Now());
+
+ DCHECK(request->status().status() == net::URLRequestStatus::FAILED ||
+ request->status().status() == net::URLRequestStatus::CANCELED);
+
+ DCHECK(!GetAndSetSignaled(request->identifier(), kOnErrorOccurred));
+
+ ClearPendingCallbacks(request);
+
+ int extra_info_spec = 0;
+ EventListeners listeners = GetMatchingListeners(
+ browser_context, extension_info_map,
+ web_request::OnErrorOccurred::kEventName, request, &extra_info_spec);
+ if (listeners.empty())
+ return;
+
+ scoped_ptr<WebRequestEventDetails> event_details(
+ CreateEventDetails(request, extra_info_spec));
+ if (started)
+ event_details->SetResponseSource(request);
+ else
+ event_details->SetBoolean(keys::kFromCache, request->was_cached());
+ event_details->SetString(keys::kErrorKey,
+ net::ErrorToString(request->status().error()));
+
+ DispatchEvent(browser_context, request, listeners, std::move(event_details));
+}
+
+void ExtensionWebRequestEventRouter::OnURLRequestDestroyed(
+ void* browser_context,
+ const net::URLRequest* request) {
+ ClearPendingCallbacks(request);
+
+ signaled_requests_.erase(request->identifier());
+
+ request_time_tracker_->LogRequestEndTime(request->identifier(),
+ base::Time::Now());
+}
+
+void ExtensionWebRequestEventRouter::ClearPendingCallbacks(
+ const net::URLRequest* request) {
+ blocked_requests_.erase(request->identifier());
+}
+
+bool ExtensionWebRequestEventRouter::DispatchEvent(
+ void* browser_context,
+ net::URLRequest* request,
+ const std::vector<const EventListener*>& listeners,
+ scoped_ptr<WebRequestEventDetails> event_details) {
+ // TODO(mpcomplete): Consider consolidating common (extension_id,json_args)
+ // pairs into a single message sent to a list of sub_event_names.
+ int num_handlers_blocking = 0;
+
+ scoped_ptr<std::vector<EventListener>> listeners_to_dispatch(
+ new std::vector<EventListener>());
+ listeners_to_dispatch->reserve(listeners.size());
+ for (const EventListener* listener : listeners) {
+ listeners_to_dispatch->push_back(*listener);
+ if (listener->extra_info_spec &
+ (ExtraInfoSpec::BLOCKING | ExtraInfoSpec::ASYNC_BLOCKING)) {
+ listener->blocked_requests.insert(request->identifier());
+ // If this is the first delegate blocking the request, go ahead and log
+ // it.
+ if (num_handlers_blocking == 0) {
+ std::string delegate_info = l10n_util::GetStringFUTF8(
+ IDS_LOAD_STATE_PARAMETER_EXTENSION,
+ base::UTF8ToUTF16(listener->extension_name));
+ // LobAndReport allows extensions that block requests to be displayed in
+ // the load status bar.
+ request->LogAndReportBlockedBy(delegate_info.c_str());
+ }
+ ++num_handlers_blocking;
+ }
+ }
+
+ event_details.release()->DetermineFrameIdOnIO(base::Bind(
+ &ExtensionWebRequestEventRouter::DispatchEventToListeners, AsWeakPtr(),
+ browser_context, base::Passed(&listeners_to_dispatch)));
+
+ if (num_handlers_blocking > 0) {
+ BlockedRequest& blocked_request = blocked_requests_[request->identifier()];
+ blocked_request.request = request;
+ blocked_request.is_incognito |= IsIncognitoBrowserContext(browser_context);
+ blocked_request.num_handlers_blocking += num_handlers_blocking;
+ blocked_request.blocking_time = base::Time::Now();
+ return true;
+ }
+
+ return false;
+}
+
+void ExtensionWebRequestEventRouter::DispatchEventToListeners(
+ void* browser_context,
+ scoped_ptr<std::vector<EventListener>> listeners,
+ scoped_ptr<WebRequestEventDetails> event_details) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ DCHECK(listeners.get());
+ DCHECK_GT(listeners->size(), 0UL);
+ DCHECK(event_details.get());
+
+ std::string event_name =
+ EventRouter::GetBaseEventName((*listeners)[0].sub_event_name);
+ DCHECK(IsWebRequestEvent(event_name));
+
+ const std::set<EventListener>& event_listeners =
+ listeners_[browser_context][event_name];
+ void* cross_browser_context = GetCrossBrowserContext(browser_context);
+ const std::set<EventListener>* cross_event_listeners =
+ cross_browser_context ? &listeners_[cross_browser_context][event_name]
+ : nullptr;
+
+ for (const EventListener& target : *listeners) {
+ std::set<EventListener>::const_iterator listener =
+ event_listeners.find(target);
+ // Ignore listener if it was removed between the thread hops.
+ if (listener == event_listeners.end()) {
+ if (!cross_event_listeners)
+ continue;
+ listener = cross_event_listeners->find(target);
+ if (listener == cross_event_listeners->end())
+ continue;
+ }
+
+ if (!listener->ipc_sender.get())
+ continue;
+
+ // Filter out the optional keys that this listener didn't request.
+ scoped_ptr<base::ListValue> args_filtered(new base::ListValue);
+ args_filtered->Append(
+ event_details->GetFilteredDict(listener->extra_info_spec));
+
+ EventRouter::DispatchEventToSender(
+ listener->ipc_sender.get(), browser_context, listener->extension_id,
+ listener->histogram_value, listener->sub_event_name,
+ std::move(args_filtered), EventRouter::USER_GESTURE_UNKNOWN,
+ EventFilteringInfo());
+ }
+}
+
+void ExtensionWebRequestEventRouter::OnEventHandled(
+ void* browser_context,
+ const std::string& extension_id,
+ const std::string& event_name,
+ const std::string& sub_event_name,
+ uint64_t request_id,
+ EventResponse* response) {
+ // TODO(robwu): Does this also work with webviews? operator< (used by find)
+ // takes the webview ID into account, which is not set on |listener|.
+ EventListener listener;
+ listener.extension_id = extension_id;
+ listener.sub_event_name = sub_event_name;
+
+ // The listener may have been removed (e.g. due to the process going away)
+ // before we got here.
+ std::set<EventListener>::iterator found =
+ listeners_[browser_context][event_name].find(listener);
+ if (found != listeners_[browser_context][event_name].end())
+ found->blocked_requests.erase(request_id);
+
+ DecrementBlockCount(
+ browser_context, extension_id, event_name, request_id, response);
+}
+
+bool ExtensionWebRequestEventRouter::AddEventListener(
+ void* browser_context,
+ const std::string& extension_id,
+ const std::string& extension_name,
+ events::HistogramValue histogram_value,
+ const std::string& event_name,
+ const std::string& sub_event_name,
+ const RequestFilter& filter,
+ int extra_info_spec,
+ int embedder_process_id,
+ int web_view_instance_id,
+ base::WeakPtr<IPC::Sender> ipc_sender) {
+ if (!IsWebRequestEvent(event_name))
+ return false;
+
+ EventListener listener;
+ listener.extension_id = extension_id;
+ listener.extension_name = extension_name;
+ listener.histogram_value = histogram_value;
+ listener.sub_event_name = sub_event_name;
+ listener.filter = filter;
+ listener.extra_info_spec = extra_info_spec;
+ listener.ipc_sender = ipc_sender;
+ listener.embedder_process_id = embedder_process_id;
+ listener.web_view_instance_id = web_view_instance_id;
+ if (listener.web_view_instance_id) {
+ content::RecordAction(
+ base::UserMetricsAction("WebView.WebRequest.AddListener"));
+ }
+
+ if (ContainsKey(listeners_[browser_context][event_name], listener)) {
+ // This is likely an abuse of the API by a malicious extension.
+ return false;
+ }
+ listeners_[browser_context][event_name].insert(listener);
+ return true;
+}
+
+void ExtensionWebRequestEventRouter::RemoveEventListener(
+ void* browser_context,
+ const std::string& extension_id,
+ const std::string& sub_event_name,
+ int embedder_process_id,
+ int web_view_instance_id) {
+ std::string event_name = EventRouter::GetBaseEventName(sub_event_name);
+ DCHECK(IsWebRequestEvent(event_name));
+
+ EventListener listener;
+ listener.extension_id = extension_id;
+ listener.sub_event_name = sub_event_name;
+ listener.embedder_process_id = embedder_process_id;
+ listener.web_view_instance_id = web_view_instance_id;
+
+ std::set<EventListener>& event_listeners =
+ listeners_[browser_context][event_name];
+ // It's possible for AddEventListener to fail asynchronously. In that case,
+ // the renderer believes the listener exists, while the browser does not.
+ // Ignore a RemoveEventListener in that case.
+ std::set<EventListener>::const_iterator it = event_listeners.find(listener);
+ if (it == event_listeners.end())
+ return;
+
+ CHECK_EQ(event_listeners.count(listener), 1u) <<
+ "extension=" << extension_id << " event=" << event_name;
+
+ // Unblock any request that this event listener may have been blocking.
+ for (uint64_t id : it->blocked_requests)
+ DecrementBlockCount(browser_context, extension_id, event_name, id, NULL);
+
+ event_listeners.erase(listener);
+
+ helpers::ClearCacheOnNavigation();
+}
+
+void ExtensionWebRequestEventRouter::RemoveWebViewEventListeners(
+ void* browser_context,
+ int embedder_process_id,
+ int web_view_instance_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+ // Iterate over all listeners of all WebRequest events to delete
+ // any listeners that belong to the provided <webview>.
+ ListenerMapForBrowserContext& map_for_browser_context =
+ listeners_[browser_context];
+ for (const auto& event_iter : map_for_browser_context) {
+ // Construct a listeners_to_delete vector so that we don't modify the set of
+ // listeners as we iterate through it.
+ std::vector<EventListener> listeners_to_delete;
+ const std::set<EventListener>& listeners = event_iter.second;
+ for (const auto& listener : listeners) {
+ if (listener.embedder_process_id == embedder_process_id &&
+ listener.web_view_instance_id == web_view_instance_id) {
+ listeners_to_delete.push_back(listener);
+ }
+ }
+ // Remove the listeners selected for deletion.
+ for (const auto& listener : listeners_to_delete) {
+ RemoveEventListenerOnIOThread(
+ browser_context,
+ listener.extension_id,
+ listener.sub_event_name,
+ listener.embedder_process_id,
+ listener.web_view_instance_id);
+ }
+ }
+}
+
+void ExtensionWebRequestEventRouter::OnOTRBrowserContextCreated(
+ void* original_browser_context, void* otr_browser_context) {
+ cross_browser_context_map_[original_browser_context] =
+ std::make_pair(false, otr_browser_context);
+ cross_browser_context_map_[otr_browser_context] =
+ std::make_pair(true, original_browser_context);
+}
+
+void ExtensionWebRequestEventRouter::OnOTRBrowserContextDestroyed(
+ void* original_browser_context, void* otr_browser_context) {
+ cross_browser_context_map_.erase(otr_browser_context);
+ cross_browser_context_map_.erase(original_browser_context);
+}
+
+void ExtensionWebRequestEventRouter::AddCallbackForPageLoad(
+ const base::Closure& callback) {
+ callbacks_for_page_load_.push_back(callback);
+}
+
+bool ExtensionWebRequestEventRouter::IsPageLoad(
+ const net::URLRequest* request) const {
+ const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request);
+ if (!info)
+ return false;
+
+ return info->GetResourceType() == content::RESOURCE_TYPE_MAIN_FRAME;
+}
+
+void ExtensionWebRequestEventRouter::NotifyPageLoad() {
+ for (const auto& callback : callbacks_for_page_load_)
+ callback.Run();
+ callbacks_for_page_load_.clear();
+}
+
+void* ExtensionWebRequestEventRouter::GetCrossBrowserContext(
+ void* browser_context) const {
+ CrossBrowserContextMap::const_iterator cross_browser_context =
+ cross_browser_context_map_.find(browser_context);
+ if (cross_browser_context == cross_browser_context_map_.end())
+ return NULL;
+ return cross_browser_context->second.second;
+}
+
+bool ExtensionWebRequestEventRouter::IsIncognitoBrowserContext(
+ void* browser_context) const {
+ CrossBrowserContextMap::const_iterator cross_browser_context =
+ cross_browser_context_map_.find(browser_context);
+ if (cross_browser_context == cross_browser_context_map_.end())
+ return false;
+ return cross_browser_context->second.first;
+}
+
+bool ExtensionWebRequestEventRouter::WasSignaled(
+ const net::URLRequest& request) const {
+ SignaledRequestMap::const_iterator flag =
+ signaled_requests_.find(request.identifier());
+ return (flag != signaled_requests_.end()) && (flag->second != 0);
+}
+
+void ExtensionWebRequestEventRouter::GetMatchingListenersImpl(
+ void* browser_context,
+ const net::URLRequest* request,
+ const InfoMap* extension_info_map,
+ bool crosses_incognito,
+ const std::string& event_name,
+ const GURL& url,
+ int render_process_host_id,
+ int routing_id,
+ content::ResourceType resource_type,
+ bool is_async_request,
+ bool is_request_from_extension,
+ int* extra_info_spec,
+ EventListeners* matching_listeners) {
+ std::string web_request_event_name(event_name);
+ WebViewRendererState::WebViewInfo web_view_info;
+ bool is_web_view_guest = WebViewRendererState::GetInstance()->GetInfo(
+ render_process_host_id, routing_id, &web_view_info);
+ if (is_web_view_guest) {
+ web_request_event_name.replace(
+ 0, sizeof(kWebRequestEventPrefix) - 1, webview::kWebViewEventPrefix);
+ }
+
+ std::set<EventListener>& listeners =
+ listeners_[browser_context][web_request_event_name];
+ for (const EventListener& listener : listeners) {
+ if (!listener.ipc_sender.get()) {
+ // The IPC sender has been deleted. This listener will be removed soon
+ // via a call to RemoveEventListener. For now, just skip it.
+ continue;
+ }
+
+ if (is_web_view_guest &&
+ (listener.embedder_process_id != web_view_info.embedder_process_id ||
+ listener.web_view_instance_id != web_view_info.instance_id)) {
+ continue;
+ }
+
+ // Filter requests from other extensions / apps. This does not work for
+ // content scripts, or extension pages in non-extension processes.
+ if (is_request_from_extension &&
+ listener.embedder_process_id != render_process_host_id) {
+ continue;
+ }
+
+ if (!listener.filter.urls.is_empty() &&
+ !listener.filter.urls.MatchesURL(url)) {
+ continue;
+ }
+
+ int render_process_id = -1;
+ int render_frame_id = -1;
+ // TODO(devlin): Figure out when one/both of these can fail, and if we
+ // need to address it.
+ bool found_render_frame =
+ content::ResourceRequestInfo::GetRenderFrameForRequest(
+ request, &render_process_id, &render_frame_id);
+ UMA_HISTOGRAM_BOOLEAN("Extensions.WebRequestEventFoundFrame",
+ found_render_frame);
+ ExtensionApiFrameIdMap::FrameData frame_data;
+ if (found_render_frame) {
+ ExtensionApiFrameIdMap::Get()->GetCachedFrameDataOnIO(
+ render_process_id, render_frame_id, &frame_data);
+ }
+ // Check if the tab id and window id match, if they were set in the
+ // listener params.
+ if ((listener.filter.tab_id != -1 &&
+ frame_data.tab_id != listener.filter.tab_id) ||
+ (listener.filter.window_id != -1 &&
+ frame_data.window_id != listener.filter.window_id)) {
+ continue;
+ }
+
+ const std::vector<content::ResourceType>& types = listener.filter.types;
+ if (!types.empty() &&
+ std::find(types.begin(), types.end(), resource_type) == types.end()) {
+ continue;
+ }
+
+ if (!is_web_view_guest) {
+ PermissionsData::AccessType access =
+ WebRequestPermissions::CanExtensionAccessURL(
+ extension_info_map, listener.extension_id, url, frame_data.tab_id,
+ crosses_incognito,
+ WebRequestPermissions::REQUIRE_HOST_PERMISSION);
+ if (access != PermissionsData::ACCESS_ALLOWED) {
+ if (access == PermissionsData::ACCESS_WITHHELD) {
+ web_request_event_router_delegate_->NotifyWebRequestWithheld(
+ render_process_id, render_frame_id, listener.extension_id);
+ }
+ continue;
+ }
+ }
+
+ bool blocking_listener =
+ (listener.extra_info_spec &
+ (ExtraInfoSpec::BLOCKING | ExtraInfoSpec::ASYNC_BLOCKING)) != 0;
+
+ // We do not want to notify extensions about XHR requests that are
+ // triggered by themselves. This is a workaround to prevent deadlocks
+ // in case of synchronous XHR requests that block the extension renderer
+ // and therefore prevent the extension from processing the request
+ // handler. This is only a problem for blocking listeners.
+ // http://crbug.com/105656
+ bool synchronous_xhr_from_extension =
+ !is_async_request && is_request_from_extension &&
+ resource_type == content::RESOURCE_TYPE_XHR;
+
+ // Only send webRequest events for URLs the extension has access to.
+ if (blocking_listener && synchronous_xhr_from_extension)
+ continue;
+
+ matching_listeners->push_back(&listener);
+ *extra_info_spec |= listener.extra_info_spec;
+ }
+}
+
+ExtensionWebRequestEventRouter::EventListeners
+ExtensionWebRequestEventRouter::GetMatchingListeners(
+ void* browser_context,
+ const InfoMap* extension_info_map,
+ const std::string& event_name,
+ const net::URLRequest* request,
+ int* extra_info_spec) {
+ // TODO(mpcomplete): handle browser_context == NULL (should collect all
+ // listeners).
+ *extra_info_spec = 0;
+
+ const GURL& url = request->url();
+ int render_process_host_id = content::ChildProcessHost::kInvalidUniqueID;
+ int routing_id = MSG_ROUTING_NONE;
+ content::ResourceType resource_type = content::RESOURCE_TYPE_LAST_TYPE;
+ // We are conservative here and assume requests are asynchronous in case
+ // we don't have an info object. We don't want to risk a deadlock.
+ bool is_async_request = false;
+ bool is_request_from_extension =
+ IsRequestFromExtension(request, extension_info_map);
+
+ const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request);
+ if (info) {
+ is_async_request = info->IsAsync();
+ if (helpers::IsRelevantResourceType(info->GetResourceType()))
+ resource_type = info->GetResourceType();
+ render_process_host_id = info->GetChildID();
+ routing_id = info->GetRouteID();
+ }
+
+ EventListeners matching_listeners;
+ GetMatchingListenersImpl(
+ browser_context, request, extension_info_map, false, event_name,
+ url, render_process_host_id, routing_id, resource_type,
+ is_async_request, is_request_from_extension, extra_info_spec,
+ &matching_listeners);
+ void* cross_browser_context = GetCrossBrowserContext(browser_context);
+ if (cross_browser_context) {
+ GetMatchingListenersImpl(
+ cross_browser_context, request, extension_info_map, true, event_name,
+ url, render_process_host_id, routing_id, resource_type,
+ is_async_request, is_request_from_extension, extra_info_spec,
+ &matching_listeners);
+ }
+
+ return matching_listeners;
+}
+
+namespace {
+
+helpers::EventResponseDelta* CalculateDelta(
+ ExtensionWebRequestEventRouter::BlockedRequest* blocked_request,
+ ExtensionWebRequestEventRouter::EventResponse* response) {
+ switch (blocked_request->event) {
+ case ExtensionWebRequestEventRouter::kOnBeforeRequest:
+ return helpers::CalculateOnBeforeRequestDelta(
+ response->extension_id, response->extension_install_time,
+ response->cancel, response->new_url);
+ case ExtensionWebRequestEventRouter::kOnBeforeSendHeaders: {
+ net::HttpRequestHeaders* old_headers = blocked_request->request_headers;
+ net::HttpRequestHeaders* new_headers = response->request_headers.get();
+ return helpers::CalculateOnBeforeSendHeadersDelta(
+ response->extension_id, response->extension_install_time,
+ response->cancel, old_headers, new_headers);
+ }
+ case ExtensionWebRequestEventRouter::kOnHeadersReceived: {
+ const net::HttpResponseHeaders* old_headers =
+ blocked_request->original_response_headers.get();
+ helpers::ResponseHeaders* new_headers =
+ response->response_headers.get();
+ return helpers::CalculateOnHeadersReceivedDelta(
+ response->extension_id,
+ response->extension_install_time,
+ response->cancel,
+ response->new_url,
+ old_headers,
+ new_headers);
+ }
+ case ExtensionWebRequestEventRouter::kOnAuthRequired:
+ return helpers::CalculateOnAuthRequiredDelta(
+ response->extension_id, response->extension_install_time,
+ response->cancel, &response->auth_credentials);
+ default:
+ NOTREACHED();
+ return nullptr;
+ }
+}
+
+base::Value* SerializeResponseHeaders(const helpers::ResponseHeaders& headers) {
+ scoped_ptr<base::ListValue> serialized_headers(new base::ListValue());
+ for (const auto& it : headers) {
+ serialized_headers->Append(
+ helpers::CreateHeaderDictionary(it.first, it.second));
+ }
+ return serialized_headers.release();
+}
+
+// Convert a RequestCookieModifications/ResponseCookieModifications object to a
+// base::ListValue which summarizes the changes made. This is templated since
+// the two types (request/response) are different but contain essentially the
+// same fields.
+template <typename CookieType>
+base::ListValue* SummarizeCookieModifications(
+ const std::vector<linked_ptr<CookieType>>& modifications) {
+ scoped_ptr<base::ListValue> cookie_modifications(new base::ListValue());
+ for (const auto& it : modifications) {
+ scoped_ptr<base::DictionaryValue> summary(new base::DictionaryValue());
+ const CookieType& mod = *(it.get());
+ switch (mod.type) {
+ case helpers::ADD:
+ summary->SetString(activity_log::kCookieModificationTypeKey,
+ activity_log::kCookieModificationAdd);
+ break;
+ case helpers::EDIT:
+ summary->SetString(activity_log::kCookieModificationTypeKey,
+ activity_log::kCookieModificationEdit);
+ break;
+ case helpers::REMOVE:
+ summary->SetString(activity_log::kCookieModificationTypeKey,
+ activity_log::kCookieModificationRemove);
+ break;
+ }
+ if (mod.filter) {
+ if (mod.filter->name) {
+ summary->SetString(activity_log::kCookieFilterNameKey,
+ *mod.modification->name);
+ }
+ if (mod.filter->domain) {
+ summary->SetString(activity_log::kCookieFilterDomainKey,
+ *mod.modification->name);
+ }
+ }
+ if (mod.modification) {
+ if (mod.modification->name) {
+ summary->SetString(activity_log::kCookieModDomainKey,
+ *mod.modification->name);
+ }
+ if (mod.modification->domain) {
+ summary->SetString(activity_log::kCookieModDomainKey,
+ *mod.modification->name);
+ }
+ }
+ cookie_modifications->Append(summary.release());
+ }
+ return cookie_modifications.release();
+}
+
+// Converts an EventResponseDelta object to a dictionary value suitable for the
+// activity log.
+scoped_ptr<base::DictionaryValue> SummarizeResponseDelta(
+ const std::string& event_name,
+ const helpers::EventResponseDelta& delta) {
+ scoped_ptr<base::DictionaryValue> details(new base::DictionaryValue());
+ if (delta.cancel)
+ details->SetBoolean(activity_log::kCancelKey, true);
+ if (!delta.new_url.is_empty())
+ details->SetString(activity_log::kNewUrlKey, delta.new_url.spec());
+
+ scoped_ptr<base::ListValue> modified_headers(new base::ListValue());
+ net::HttpRequestHeaders::Iterator iter(delta.modified_request_headers);
+ while (iter.GetNext()) {
+ modified_headers->Append(
+ helpers::CreateHeaderDictionary(iter.name(), iter.value()));
+ }
+ if (!modified_headers->empty()) {
+ details->Set(activity_log::kModifiedRequestHeadersKey,
+ modified_headers.release());
+ }
+
+ scoped_ptr<base::ListValue> deleted_headers(new base::ListValue());
+ deleted_headers->AppendStrings(delta.deleted_request_headers);
+ if (!deleted_headers->empty()) {
+ details->Set(activity_log::kDeletedRequestHeadersKey,
+ deleted_headers.release());
+ }
+
+ if (!delta.added_response_headers.empty()) {
+ details->Set(activity_log::kAddedRequestHeadersKey,
+ SerializeResponseHeaders(delta.added_response_headers));
+ }
+ if (!delta.deleted_response_headers.empty()) {
+ details->Set(activity_log::kDeletedResponseHeadersKey,
+ SerializeResponseHeaders(delta.deleted_response_headers));
+ }
+ if (delta.auth_credentials) {
+ details->SetString(
+ activity_log::kAuthCredentialsKey,
+ base::UTF16ToUTF8(delta.auth_credentials->username()) + ":*");
+ }
+
+ if (!delta.response_cookie_modifications.empty()) {
+ details->Set(
+ activity_log::kResponseCookieModificationsKey,
+ SummarizeCookieModifications(delta.response_cookie_modifications));
+ }
+
+ return details;
+}
+
+} // namespace
+
+void ExtensionWebRequestEventRouter::LogExtensionActivity(
+ void* browser_context_id,
+ bool is_incognito,
+ const std::string& extension_id,
+ const GURL& url,
+ const std::string& api_call,
+ scoped_ptr<base::DictionaryValue> details) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&ExtensionWebRequestEventRouter::LogExtensionActivity,
+ base::Unretained(this),
+ browser_context_id,
+ is_incognito,
+ extension_id,
+ url,
+ api_call,
+ base::Passed(&details)));
+ } else {
+ if (web_request_event_router_delegate_) {
+ web_request_event_router_delegate_->LogExtensionActivity(
+ reinterpret_cast<content::BrowserContext*>(browser_context_id),
+ is_incognito, extension_id, url, api_call, std::move(details));
+ }
+ }
+}
+
+void ExtensionWebRequestEventRouter::DecrementBlockCount(
+ void* browser_context,
+ const std::string& extension_id,
+ const std::string& event_name,
+ uint64_t request_id,
+ EventResponse* response) {
+ scoped_ptr<EventResponse> response_scoped(response);
+
+ // It's possible that this request was deleted, or cancelled by a previous
+ // event handler. If so, ignore this response.
+ auto it = blocked_requests_.find(request_id);
+ if (it == blocked_requests_.end())
+ return;
+
+ BlockedRequest& blocked_request = it->second;
+ int num_handlers_blocking = --blocked_request.num_handlers_blocking;
+ CHECK_GE(num_handlers_blocking, 0);
+
+ if (response) {
+ helpers::EventResponseDelta* delta =
+ CalculateDelta(&blocked_request, response);
+
+ LogExtensionActivity(browser_context,
+ blocked_request.is_incognito,
+ extension_id,
+ blocked_request.request->url(),
+ event_name,
+ SummarizeResponseDelta(event_name, *delta));
+
+ blocked_request.response_deltas.push_back(
+ linked_ptr<helpers::EventResponseDelta>(delta));
+ }
+
+ base::TimeDelta block_time =
+ base::Time::Now() - blocked_request.blocking_time;
+ if (!extension_id.empty()) {
+ request_time_tracker_->IncrementExtensionBlockTime(
+ extension_id, request_id, block_time);
+ } else {
+ // |extension_id| is empty for requests blocked on startup waiting for the
+ // declarative rules to be read from disk.
+ UMA_HISTOGRAM_TIMES("Extensions.NetworkDelayStartup", block_time);
+ }
+
+ if (num_handlers_blocking == 0) {
+ blocked_request.request->LogUnblocked();
+ ExecuteDeltas(browser_context, request_id, true);
+ } else {
+ // Update the URLRequest to make sure it's tagged with an extension that's
+ // still blocking it. This may end up being the same extension as before.
+ std::set<EventListener>& listeners =
+ listeners_[browser_context][event_name];
+
+ for (const auto& listener : listeners) {
+ if (!ContainsKey(listener.blocked_requests, request_id))
+ continue;
+ std::string delegate_info =
+ l10n_util::GetStringFUTF8(IDS_LOAD_STATE_PARAMETER_EXTENSION,
+ base::UTF8ToUTF16(listener.extension_name));
+ blocked_request.request->LogAndReportBlockedBy(delegate_info.c_str());
+ break;
+ }
+ }
+}
+
+void ExtensionWebRequestEventRouter::SendMessages(
+ void* browser_context,
+ const BlockedRequest& blocked_request) {
+ const helpers::EventResponseDeltas& deltas = blocked_request.response_deltas;
+ for (const auto& delta : deltas) {
+ const std::set<std::string>& messages = delta->messages_to_extension;
+ for (const std::string& message : messages) {
+ scoped_ptr<WebRequestEventDetails> event_details(
+ CreateEventDetails(blocked_request.request, /* extra_info_spec */ 0));
+ WebViewRendererState::WebViewInfo web_view_info;
+ bool is_web_view_guest = GetWebViewInfo(blocked_request.request,
+ &web_view_info);
+ event_details->SetString(keys::kMessageKey, message);
+ event_details->SetString(keys::kStageKey,
+ GetRequestStageAsString(blocked_request.event));
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&SendOnMessageEventOnUI, browser_context,
+ delta->extension_id, is_web_view_guest, web_view_info,
+ base::Passed(&event_details)));
+ }
+ }
+}
+
+int ExtensionWebRequestEventRouter::ExecuteDeltas(void* browser_context,
+ uint64_t request_id,
+ bool call_callback) {
+ BlockedRequest& blocked_request = blocked_requests_[request_id];
+ CHECK_EQ(0, blocked_request.num_handlers_blocking);
+ helpers::EventResponseDeltas& deltas = blocked_request.response_deltas;
+ base::TimeDelta block_time =
+ base::Time::Now() - blocked_request.blocking_time;
+ request_time_tracker_->IncrementTotalBlockTime(request_id, block_time);
+
+ bool credentials_set = false;
+
+ deltas.sort(&helpers::InDecreasingExtensionInstallationTimeOrder);
+
+ bool canceled = false;
+ helpers::MergeCancelOfResponses(blocked_request.response_deltas, &canceled,
+ blocked_request.net_log);
+
+ WarningSet warnings;
+ if (blocked_request.event == kOnBeforeRequest) {
+ CHECK(!blocked_request.callback.is_null());
+ helpers::MergeOnBeforeRequestResponses(
+ blocked_request.response_deltas,
+ blocked_request.new_url,
+ &warnings,
+ blocked_request.net_log);
+ } else if (blocked_request.event == kOnBeforeSendHeaders) {
+ CHECK(!blocked_request.callback.is_null());
+ helpers::MergeOnBeforeSendHeadersResponses(
+ blocked_request.response_deltas,
+ blocked_request.request_headers,
+ &warnings,
+ blocked_request.net_log);
+ } else if (blocked_request.event == kOnHeadersReceived) {
+ CHECK(!blocked_request.callback.is_null());
+ helpers::MergeOnHeadersReceivedResponses(
+ blocked_request.response_deltas,
+ blocked_request.original_response_headers.get(),
+ blocked_request.override_response_headers,
+ blocked_request.new_url,
+ &warnings,
+ blocked_request.net_log);
+ } else if (blocked_request.event == kOnAuthRequired) {
+ CHECK(blocked_request.callback.is_null());
+ CHECK(!blocked_request.auth_callback.is_null());
+ credentials_set = helpers::MergeOnAuthRequiredResponses(
+ blocked_request.response_deltas,
+ blocked_request.auth_credentials,
+ &warnings,
+ blocked_request.net_log);
+ } else {
+ NOTREACHED();
+ }
+
+ SendMessages(browser_context, blocked_request);
+
+ if (!warnings.empty()) {
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&WarningService::NotifyWarningsOnUI,
+ browser_context, warnings));
+ }
+
+ if (canceled) {
+ request_time_tracker_->SetRequestCanceled(request_id);
+ } else if (blocked_request.new_url &&
+ !blocked_request.new_url->is_empty()) {
+ request_time_tracker_->SetRequestRedirected(request_id);
+ }
+
+ // This triggers onErrorOccurred if canceled is true.
+ int rv = canceled ? net::ERR_BLOCKED_BY_CLIENT : net::OK;
+
+ if (!blocked_request.callback.is_null()) {
+ net::CompletionCallback callback = blocked_request.callback;
+ // Ensure that request is removed before callback because the callback
+ // might trigger the next event.
+ blocked_requests_.erase(request_id);
+ if (call_callback)
+ callback.Run(rv);
+ } else if (!blocked_request.auth_callback.is_null()) {
+ net::NetworkDelegate::AuthRequiredResponse response;
+ if (canceled)
+ response = net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_CANCEL_AUTH;
+ else if (credentials_set)
+ response = net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_SET_AUTH;
+ else
+ response = net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION;
+
+ net::NetworkDelegate::AuthCallback callback = blocked_request.auth_callback;
+ blocked_requests_.erase(request_id);
+ if (call_callback)
+ callback.Run(response);
+ } else {
+ blocked_requests_.erase(request_id);
+ }
+ return rv;
+}
+
+bool ExtensionWebRequestEventRouter::ProcessDeclarativeRules(
+ void* browser_context,
+ const InfoMap* extension_info_map,
+ const std::string& event_name,
+ net::URLRequest* request,
+ RequestStage request_stage,
+ const net::HttpResponseHeaders* original_response_headers) {
+ WebViewRendererState::WebViewInfo web_view_info;
+ bool is_web_view_guest = GetWebViewInfo(request, &web_view_info);
+ int rules_registry_id = is_web_view_guest
+ ? web_view_info.rules_registry_id
+ : RulesRegistryService::kDefaultRulesRegistryID;
+
+ RulesRegistryKey rules_key(browser_context, rules_registry_id);
+ // If this check fails, check that the active stages are up-to-date in
+ // extensions/browser/api/declarative_webrequest/request_stage.h .
+ DCHECK(request_stage & kActiveStages);
+
+ // Rules of the current |browser_context| may apply but we need to check also
+ // whether there are applicable rules from extensions whose background page
+ // spans from regular to incognito mode.
+
+ // First parameter identifies the registry, the second indicates whether the
+ // registry belongs to the cross browser_context.
+ using RelevantRegistry = std::pair<WebRequestRulesRegistry*, bool>;
+ std::vector<RelevantRegistry> relevant_registries;
+
+ auto rules_key_it = rules_registries_.find(rules_key);
+ if (rules_key_it != rules_registries_.end()) {
+ relevant_registries.push_back(
+ std::make_pair(rules_key_it->second.get(), false));
+ }
+
+ void* cross_browser_context = GetCrossBrowserContext(browser_context);
+ RulesRegistryKey cross_browser_context_rules_key(cross_browser_context,
+ rules_registry_id);
+ if (cross_browser_context) {
+ auto it = rules_registries_.find(cross_browser_context_rules_key);
+ if (it != rules_registries_.end())
+ relevant_registries.push_back(std::make_pair(it->second.get(), true));
+ }
+
+ // The following block is experimentally enabled and its impact on load time
+ // logged with UMA Extensions.NetworkDelayRegistryLoad. crbug.com/175961
+ for (auto it : relevant_registries) {
+ WebRequestRulesRegistry* rules_registry = it.first;
+ if (rules_registry->ready().is_signaled())
+ continue;
+
+ // The rules registry is still loading. Block this request until it
+ // finishes.
+ rules_registry->ready().Post(
+ FROM_HERE,
+ base::Bind(&ExtensionWebRequestEventRouter::OnRulesRegistryReady,
+ AsWeakPtr(), browser_context, event_name,
+ request->identifier(), request_stage));
+ BlockedRequest& blocked_request = blocked_requests_[request->identifier()];
+ blocked_request.num_handlers_blocking++;
+ blocked_request.request = request;
+ blocked_request.is_incognito |= IsIncognitoBrowserContext(browser_context);
+ blocked_request.blocking_time = base::Time::Now();
+ blocked_request.original_response_headers = original_response_headers;
+ blocked_request.extension_info_map = extension_info_map;
+ return true;
+ }
+
+ base::Time start = base::Time::Now();
+
+ bool deltas_created = false;
+ for (const auto& it : relevant_registries) {
+ WebRequestRulesRegistry* rules_registry = it.first;
+ helpers::EventResponseDeltas result = rules_registry->CreateDeltas(
+ extension_info_map,
+ WebRequestData(request, request_stage, original_response_headers),
+ it.second);
+
+ if (!result.empty()) {
+ helpers::EventResponseDeltas& deltas =
+ blocked_requests_[request->identifier()].response_deltas;
+ deltas.insert(deltas.end(), result.begin(), result.end());
+ deltas_created = true;
+ }
+ }
+
+ base::TimeDelta elapsed_time = start - base::Time::Now();
+ UMA_HISTOGRAM_TIMES("Extensions.DeclarativeWebRequestNetworkDelay",
+ elapsed_time);
+
+ return deltas_created;
+}
+
+void ExtensionWebRequestEventRouter::OnRulesRegistryReady(
+ void* browser_context,
+ const std::string& event_name,
+ uint64_t request_id,
+ RequestStage request_stage) {
+ // It's possible that this request was deleted, or cancelled by a previous
+ // event handler. If so, ignore this response.
+ auto it = blocked_requests_.find(request_id);
+ if (it == blocked_requests_.end())
+ return;
+
+ BlockedRequest& blocked_request = it->second;
+ base::TimeDelta block_time =
+ base::Time::Now() - blocked_request.blocking_time;
+ UMA_HISTOGRAM_TIMES("Extensions.NetworkDelayRegistryLoad", block_time);
+
+ ProcessDeclarativeRules(browser_context,
+ blocked_request.extension_info_map,
+ event_name,
+ blocked_request.request,
+ request_stage,
+ blocked_request.original_response_headers.get());
+ // Reset to NULL so that nobody relies on this being set.
+ blocked_request.extension_info_map = NULL;
+ DecrementBlockCount(
+ browser_context, std::string(), event_name, request_id, NULL);
+}
+
+bool ExtensionWebRequestEventRouter::GetAndSetSignaled(uint64_t request_id,
+ EventTypes event_type) {
+ SignaledRequestMap::iterator iter = signaled_requests_.find(request_id);
+ if (iter == signaled_requests_.end()) {
+ signaled_requests_[request_id] = event_type;
+ return false;
+ }
+ bool was_signaled_before = (iter->second & event_type) != 0;
+ iter->second |= event_type;
+ return was_signaled_before;
+}
+
+void ExtensionWebRequestEventRouter::ClearSignaled(uint64_t request_id,
+ EventTypes event_type) {
+ SignaledRequestMap::iterator iter = signaled_requests_.find(request_id);
+ if (iter == signaled_requests_.end())
+ return;
+ iter->second &= ~event_type;
+}
+
+// Special QuotaLimitHeuristic for WebRequestHandlerBehaviorChangedFunction.
+//
+// Each call of webRequest.handlerBehaviorChanged() clears the in-memory cache
+// of WebKit at the time of the next page load (top level navigation event).
+// This quota heuristic is intended to limit the number of times the cache is
+// cleared by an extension.
+//
+// As we want to account for the number of times the cache is really cleared
+// (opposed to the number of times webRequest.handlerBehaviorChanged() is
+// called), we cannot decide whether a call of
+// webRequest.handlerBehaviorChanged() should trigger a quota violation at the
+// time it is called. Instead we only decrement the bucket counter at the time
+// when the cache is cleared (when page loads happen).
+class ClearCacheQuotaHeuristic : public QuotaLimitHeuristic {
+ public:
+ ClearCacheQuotaHeuristic(const Config& config, BucketMapper* map)
+ : QuotaLimitHeuristic(
+ config,
+ map,
+ "MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES"),
+ callback_registered_(false),
+ weak_ptr_factory_(this) {}
+ ~ClearCacheQuotaHeuristic() override {}
+ bool Apply(Bucket* bucket, const base::TimeTicks& event_time) override;
+
+ private:
+ // Callback that is triggered by the ExtensionWebRequestEventRouter on a page
+ // load.
+ //
+ // We don't need to take care of the life time of |bucket|: It is owned by the
+ // BucketMapper of our base class in |QuotaLimitHeuristic::bucket_mapper_|. As
+ // long as |this| exists, the respective BucketMapper and its bucket will
+ // exist as well.
+ void OnPageLoad(Bucket* bucket);
+
+ // Flag to prevent that we register more than one call back in-between
+ // clearing the cache.
+ bool callback_registered_;
+
+ base::WeakPtrFactory<ClearCacheQuotaHeuristic> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClearCacheQuotaHeuristic);
+};
+
+bool ClearCacheQuotaHeuristic::Apply(Bucket* bucket,
+ const base::TimeTicks& event_time) {
+ if (event_time > bucket->expiration())
+ bucket->Reset(config(), event_time);
+
+ // Call bucket->DeductToken() on a new page load, this is when
+ // webRequest.handlerBehaviorChanged() clears the cache.
+ if (!callback_registered_) {
+ ExtensionWebRequestEventRouter::GetInstance()->AddCallbackForPageLoad(
+ base::Bind(&ClearCacheQuotaHeuristic::OnPageLoad,
+ weak_ptr_factory_.GetWeakPtr(),
+ bucket));
+ callback_registered_ = true;
+ }
+
+ // We only check whether tokens are left here. Deducting a token happens in
+ // OnPageLoad().
+ return bucket->has_tokens();
+}
+
+void ClearCacheQuotaHeuristic::OnPageLoad(Bucket* bucket) {
+ callback_registered_ = false;
+ bucket->DeductToken();
+}
+
+bool WebRequestInternalAddEventListenerFunction::RunSync() {
+ // Argument 0 is the callback, which we don't use here.
+ ExtensionWebRequestEventRouter::RequestFilter filter;
+ base::DictionaryValue* value = NULL;
+ error_.clear();
+ EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &value));
+ // Failure + an empty error string means a fatal error.
+ EXTENSION_FUNCTION_VALIDATE(filter.InitFromValue(*value, &error_) ||
+ !error_.empty());
+ if (!error_.empty())
+ return false;
+
+ int extra_info_spec = 0;
+ if (HasOptionalArgument(2)) {
+ base::ListValue* value = NULL;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetList(2, &value));
+ EXTENSION_FUNCTION_VALIDATE(
+ ExtraInfoSpec::InitFromValue(*value, &extra_info_spec));
+ }
+
+ std::string event_name;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(3, &event_name));
+
+ std::string sub_event_name;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(4, &sub_event_name));
+
+ int web_view_instance_id = 0;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(5, &web_view_instance_id));
+
+ base::WeakPtr<IOThreadExtensionMessageFilter> ipc_sender = ipc_sender_weak();
+ int embedder_process_id = ipc_sender ? ipc_sender->render_process_id() : 0;
+
+ const Extension* extension =
+ extension_info_map()->extensions().GetByID(extension_id_safe());
+ std::string extension_name =
+ extension ? extension->name() : extension_id_safe();
+
+ if (!web_view_instance_id) {
+ // We check automatically whether the extension has the 'webRequest'
+ // permission. For blocking calls we require the additional permission
+ // 'webRequestBlocking'.
+ if ((extra_info_spec &
+ (ExtraInfoSpec::BLOCKING | ExtraInfoSpec::ASYNC_BLOCKING)) &&
+ !extension->permissions_data()->HasAPIPermission(
+ APIPermission::kWebRequestBlocking)) {
+ error_ = keys::kBlockingPermissionRequired;
+ return false;
+ }
+
+ // We allow to subscribe to patterns that are broader than the host
+ // permissions. E.g., we could subscribe to http://www.example.com/*
+ // while having host permissions for http://www.example.com/foo/* and
+ // http://www.example.com/bar/*.
+ // For this reason we do only a coarse check here to warn the extension
+ // developer if they do something obviously wrong.
+ if (extension->permissions_data()
+ ->GetEffectiveHostPermissions()
+ .is_empty() &&
+ extension->permissions_data()
+ ->withheld_permissions()
+ .explicit_hosts()
+ .is_empty()) {
+ error_ = keys::kHostPermissionsRequired;
+ return false;
+ }
+ }
+
+ bool success =
+ ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
+ profile_id(), extension_id_safe(), extension_name,
+ GetEventHistogramValue(event_name), event_name, sub_event_name,
+ filter, extra_info_spec, embedder_process_id, web_view_instance_id,
+ ipc_sender_weak());
+ EXTENSION_FUNCTION_VALIDATE(success);
+
+ helpers::ClearCacheOnNavigation();
+
+ if (!extension_id_safe().empty()) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&helpers::NotifyWebRequestAPIUsed,
+ profile_id(), extension_id_safe()));
+ }
+
+ return true;
+}
+
+void WebRequestInternalEventHandledFunction::RespondWithError(
+ const std::string& event_name,
+ const std::string& sub_event_name,
+ uint64_t request_id,
+ scoped_ptr<ExtensionWebRequestEventRouter::EventResponse> response,
+ const std::string& error) {
+ error_ = error;
+ ExtensionWebRequestEventRouter::GetInstance()->OnEventHandled(
+ profile_id(),
+ extension_id_safe(),
+ event_name,
+ sub_event_name,
+ request_id,
+ response.release());
+}
+
+bool WebRequestInternalEventHandledFunction::RunSync() {
+ std::string event_name;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &event_name));
+
+ std::string sub_event_name;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &sub_event_name));
+
+ std::string request_id_str;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &request_id_str));
+ uint64_t request_id;
+ EXTENSION_FUNCTION_VALIDATE(base::StringToUint64(request_id_str,
+ &request_id));
+
+ scoped_ptr<ExtensionWebRequestEventRouter::EventResponse> response;
+ if (HasOptionalArgument(3)) {
+ base::DictionaryValue* value = NULL;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(3, &value));
+
+ if (!value->empty()) {
+ base::Time install_time =
+ extension_info_map()->GetInstallTime(extension_id_safe());
+ response.reset(new ExtensionWebRequestEventRouter::EventResponse(
+ extension_id_safe(), install_time));
+ }
+
+ if (value->HasKey("cancel")) {
+ // Don't allow cancel mixed with other keys.
+ if (value->size() != 1) {
+ RespondWithError(event_name, sub_event_name, request_id,
+ std::move(response), keys::kInvalidBlockingResponse);
+ return false;
+ }
+
+ bool cancel = false;
+ EXTENSION_FUNCTION_VALIDATE(value->GetBoolean("cancel", &cancel));
+ response->cancel = cancel;
+ }
+
+ if (value->HasKey("redirectUrl")) {
+ std::string new_url_str;
+ EXTENSION_FUNCTION_VALIDATE(value->GetString("redirectUrl",
+ &new_url_str));
+ response->new_url = GURL(new_url_str);
+ if (!response->new_url.is_valid()) {
+ RespondWithError(event_name, sub_event_name, request_id,
+ std::move(response),
+ ErrorUtils::FormatErrorMessage(
+ keys::kInvalidRedirectUrl, new_url_str));
+ return false;
+ }
+ }
+
+ const bool has_request_headers = value->HasKey("requestHeaders");
+ const bool has_response_headers = value->HasKey("responseHeaders");
+ if (has_request_headers || has_response_headers) {
+ if (has_request_headers && has_response_headers) {
+ // Allow only one of the keys, not both.
+ RespondWithError(event_name, sub_event_name, request_id,
+ std::move(response),
+ keys::kInvalidHeaderKeyCombination);
+ return false;
+ }
+
+ base::ListValue* headers_value = NULL;
+ scoped_ptr<net::HttpRequestHeaders> request_headers;
+ scoped_ptr<helpers::ResponseHeaders> response_headers;
+ if (has_request_headers) {
+ request_headers.reset(new net::HttpRequestHeaders());
+ EXTENSION_FUNCTION_VALIDATE(value->GetList(keys::kRequestHeadersKey,
+ &headers_value));
+ } else {
+ response_headers.reset(new helpers::ResponseHeaders());
+ EXTENSION_FUNCTION_VALIDATE(value->GetList(keys::kResponseHeadersKey,
+ &headers_value));
+ }
+
+ for (size_t i = 0; i < headers_value->GetSize(); ++i) {
+ base::DictionaryValue* header_value = NULL;
+ std::string name;
+ std::string value;
+ EXTENSION_FUNCTION_VALIDATE(
+ headers_value->GetDictionary(i, &header_value));
+ if (!FromHeaderDictionary(header_value, &name, &value)) {
+ std::string serialized_header;
+ base::JSONWriter::Write(*header_value, &serialized_header);
+ RespondWithError(event_name, sub_event_name, request_id,
+ std::move(response),
+ ErrorUtils::FormatErrorMessage(keys::kInvalidHeader,
+ serialized_header));
+ return false;
+ }
+ if (!net::HttpUtil::IsValidHeaderName(name)) {
+ RespondWithError(event_name, sub_event_name, request_id,
+ std::move(response), keys::kInvalidHeaderName);
+ return false;
+ }
+ if (!net::HttpUtil::IsValidHeaderValue(value)) {
+ RespondWithError(
+ event_name, sub_event_name, request_id, std::move(response),
+ ErrorUtils::FormatErrorMessage(keys::kInvalidHeaderValue, name));
+ return false;
+ }
+ if (has_request_headers)
+ request_headers->SetHeader(name, value);
+ else
+ response_headers->push_back(helpers::ResponseHeader(name, value));
+ }
+ if (has_request_headers)
+ response->request_headers.reset(request_headers.release());
+ else
+ response->response_headers.reset(response_headers.release());
+ }
+
+ if (value->HasKey(keys::kAuthCredentialsKey)) {
+ base::DictionaryValue* credentials_value = NULL;
+ EXTENSION_FUNCTION_VALIDATE(value->GetDictionary(
+ keys::kAuthCredentialsKey,
+ &credentials_value));
+ base::string16 username;
+ base::string16 password;
+ EXTENSION_FUNCTION_VALIDATE(
+ credentials_value->GetString(keys::kUsernameKey, &username));
+ EXTENSION_FUNCTION_VALIDATE(
+ credentials_value->GetString(keys::kPasswordKey, &password));
+ response->auth_credentials.reset(
+ new net::AuthCredentials(username, password));
+ }
+ }
+
+ ExtensionWebRequestEventRouter::GetInstance()->OnEventHandled(
+ profile_id(), extension_id_safe(), event_name, sub_event_name, request_id,
+ response.release());
+
+ return true;
+}
+
+void WebRequestHandlerBehaviorChangedFunction::GetQuotaLimitHeuristics(
+ QuotaLimitHeuristics* heuristics) const {
+ QuotaLimitHeuristic::Config config = {
+ // See web_request.json for current value.
+ web_request::MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES,
+ base::TimeDelta::FromMinutes(10)};
+ QuotaLimitHeuristic::BucketMapper* bucket_mapper =
+ new QuotaLimitHeuristic::SingletonBucketMapper();
+ ClearCacheQuotaHeuristic* heuristic =
+ new ClearCacheQuotaHeuristic(config, bucket_mapper);
+ heuristics->push_back(heuristic);
+}
+
+void WebRequestHandlerBehaviorChangedFunction::OnQuotaExceeded(
+ const std::string& violation_error) {
+ // Post warning message.
+ WarningSet warnings;
+ warnings.insert(
+ Warning::CreateRepeatedCacheFlushesWarning(extension_id_safe()));
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&WarningService::NotifyWarningsOnUI, profile_id(), warnings));
+
+ // Continue gracefully.
+ RunSync();
+}
+
+bool WebRequestHandlerBehaviorChangedFunction::RunSync() {
+ helpers::ClearCacheOnNavigation();
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/web_request/web_request_api.h b/chromium/extensions/browser/api/web_request/web_request_api.h
new file mode 100644
index 00000000000..382860d7420
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/web_request_api.h
@@ -0,0 +1,553 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_API_H_
+#define EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_API_H_
+
+#include <stdint.h>
+
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "content/public/common/resource_type.h"
+#include "extensions/browser/api/declarative/rules_registry.h"
+#include "extensions/browser/api/declarative_webrequest/request_stage.h"
+#include "extensions/browser/api/web_request/web_request_api_helpers.h"
+#include "extensions/browser/api/web_request/web_request_permissions.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/common/url_pattern_set.h"
+#include "ipc/ipc_sender.h"
+#include "net/base/completion_callback.h"
+#include "net/base/network_delegate.h"
+#include "net/http/http_request_headers.h"
+
+class ExtensionWebRequestTimeTracker;
+class GURL;
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+class StringValue;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace net {
+class AuthChallengeInfo;
+class AuthCredentials;
+class HttpRequestHeaders;
+class HttpResponseHeaders;
+class URLRequest;
+}
+
+namespace extensions {
+
+class InfoMap;
+class WebRequestEventDetails;
+class WebRequestRulesRegistry;
+class WebRequestEventRouterDelegate;
+
+// Support class for the WebRequest API. Lives on the UI thread. Most of the
+// work is done by ExtensionWebRequestEventRouter below. This class observes
+// extensions::EventRouter to deal with event listeners. There is one instance
+// per BrowserContext which is shared with incognito.
+class WebRequestAPI : public BrowserContextKeyedAPI,
+ public EventRouter::Observer {
+ public:
+ explicit WebRequestAPI(content::BrowserContext* context);
+ ~WebRequestAPI() override;
+
+ // BrowserContextKeyedAPI support:
+ static BrowserContextKeyedAPIFactory<WebRequestAPI>* GetFactoryInstance();
+
+ // EventRouter::Observer overrides:
+ void OnListenerRemoved(const EventListenerInfo& details) override;
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<WebRequestAPI>;
+
+ // BrowserContextKeyedAPI support:
+ static const char* service_name() { return "WebRequestAPI"; }
+ static const bool kServiceRedirectedInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ content::BrowserContext* browser_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRequestAPI);
+};
+
+// This class observes network events and routes them to the appropriate
+// extensions listening to those events. All methods must be called on the IO
+// thread unless otherwise specified.
+class ExtensionWebRequestEventRouter
+ : public base::SupportsWeakPtr<ExtensionWebRequestEventRouter> {
+ public:
+ struct BlockedRequest;
+
+ enum EventTypes {
+ kInvalidEvent = 0,
+ kOnBeforeRequest = 1 << 0,
+ kOnBeforeSendHeaders = 1 << 1,
+ kOnSendHeaders = 1 << 2,
+ kOnHeadersReceived = 1 << 3,
+ kOnBeforeRedirect = 1 << 4,
+ kOnAuthRequired = 1 << 5,
+ kOnResponseStarted = 1 << 6,
+ kOnErrorOccurred = 1 << 7,
+ kOnCompleted = 1 << 8,
+ };
+
+ // Internal representation of the webRequest.RequestFilter type, used to
+ // filter what network events an extension cares about.
+ struct RequestFilter {
+ RequestFilter();
+ RequestFilter(const RequestFilter& other);
+ ~RequestFilter();
+
+ // Returns false if there was an error initializing. If it is a user error,
+ // an error message is provided, otherwise the error is internal (and
+ // unexpected).
+ bool InitFromValue(const base::DictionaryValue& value, std::string* error);
+
+ extensions::URLPatternSet urls;
+ std::vector<content::ResourceType> types;
+ int tab_id;
+ int window_id;
+ };
+
+ // Contains an extension's response to a blocking event.
+ struct EventResponse {
+ EventResponse(const std::string& extension_id,
+ const base::Time& extension_install_time);
+ ~EventResponse();
+
+ // ID of the extension that sent this response.
+ std::string extension_id;
+
+ // The time that the extension was installed. Used for deciding order of
+ // precedence in case multiple extensions respond with conflicting
+ // decisions.
+ base::Time extension_install_time;
+
+ // Response values. These are mutually exclusive.
+ bool cancel;
+ GURL new_url;
+ scoped_ptr<net::HttpRequestHeaders> request_headers;
+ scoped_ptr<extension_web_request_api_helpers::ResponseHeaders>
+ response_headers;
+
+ scoped_ptr<net::AuthCredentials> auth_credentials;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EventResponse);
+ };
+
+ static ExtensionWebRequestEventRouter* GetInstance();
+
+ // Registers a rule registry. Pass null for |rules_registry| to unregister
+ // the rule registry for |browser_context|.
+ void RegisterRulesRegistry(
+ void* browser_context,
+ int rules_registry_id,
+ scoped_refptr<extensions::WebRequestRulesRegistry> rules_registry);
+
+ // Dispatches the OnBeforeRequest event to any extensions whose filters match
+ // the given request. Returns net::ERR_IO_PENDING if an extension is
+ // intercepting the request, OK otherwise.
+ int OnBeforeRequest(void* browser_context,
+ const extensions::InfoMap* extension_info_map,
+ net::URLRequest* request,
+ const net::CompletionCallback& callback,
+ GURL* new_url);
+
+ // Dispatches the onBeforeSendHeaders event. This is fired for HTTP(s)
+ // requests only, and allows modification of the outgoing request headers.
+ // Returns net::ERR_IO_PENDING if an extension is intercepting the request, OK
+ // otherwise.
+ int OnBeforeSendHeaders(void* browser_context,
+ const extensions::InfoMap* extension_info_map,
+ net::URLRequest* request,
+ const net::CompletionCallback& callback,
+ net::HttpRequestHeaders* headers);
+
+ // Dispatches the onSendHeaders event. This is fired for HTTP(s) requests
+ // only.
+ void OnSendHeaders(void* browser_context,
+ const extensions::InfoMap* extension_info_map,
+ net::URLRequest* request,
+ const net::HttpRequestHeaders& headers);
+
+ // Dispatches the onHeadersReceived event. This is fired for HTTP(s)
+ // requests only, and allows modification of incoming response headers.
+ // Returns net::ERR_IO_PENDING if an extension is intercepting the request,
+ // OK otherwise. |original_response_headers| is reference counted. |callback|
+ // |override_response_headers| and |allowed_unsafe_redirect_url| are owned by
+ // a URLRequestJob. They are guaranteed to be valid until |callback| is called
+ // or OnURLRequestDestroyed is called (whatever comes first).
+ // Do not modify |original_response_headers| directly but write new ones
+ // into |override_response_headers|.
+ int OnHeadersReceived(
+ void* browser_context,
+ const extensions::InfoMap* extension_info_map,
+ net::URLRequest* request,
+ const net::CompletionCallback& callback,
+ const net::HttpResponseHeaders* original_response_headers,
+ scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
+ GURL* allowed_unsafe_redirect_url);
+
+ // Dispatches the OnAuthRequired event to any extensions whose filters match
+ // the given request. If the listener is not registered as "blocking", then
+ // AUTH_REQUIRED_RESPONSE_OK is returned. Otherwise,
+ // AUTH_REQUIRED_RESPONSE_IO_PENDING is returned and |callback| will be
+ // invoked later.
+ net::NetworkDelegate::AuthRequiredResponse OnAuthRequired(
+ void* browser_context,
+ const extensions::InfoMap* extension_info_map,
+ net::URLRequest* request,
+ const net::AuthChallengeInfo& auth_info,
+ const net::NetworkDelegate::AuthCallback& callback,
+ net::AuthCredentials* credentials);
+
+ // Dispatches the onBeforeRedirect event. This is fired for HTTP(s) requests
+ // only.
+ void OnBeforeRedirect(void* browser_context,
+ const extensions::InfoMap* extension_info_map,
+ net::URLRequest* request,
+ const GURL& new_location);
+
+ // Dispatches the onResponseStarted event indicating that the first bytes of
+ // the response have arrived.
+ void OnResponseStarted(void* browser_context,
+ const extensions::InfoMap* extension_info_map,
+ net::URLRequest* request);
+
+ // Dispatches the onComplete event.
+ void OnCompleted(void* browser_context,
+ const extensions::InfoMap* extension_info_map,
+ net::URLRequest* request);
+
+ // Dispatches an onErrorOccurred event.
+ void OnErrorOccurred(void* browser_context,
+ const extensions::InfoMap* extension_info_map,
+ net::URLRequest* request,
+ bool started);
+
+ // Notifications when objects are going away.
+ void OnURLRequestDestroyed(void* browser_context,
+ const net::URLRequest* request);
+
+ // Called when an event listener handles a blocking event and responds.
+ void OnEventHandled(void* browser_context,
+ const std::string& extension_id,
+ const std::string& event_name,
+ const std::string& sub_event_name,
+ uint64_t request_id,
+ EventResponse* response);
+
+ // Adds a listener to the given event. |event_name| specifies the event being
+ // listened to. |sub_event_name| is an internal event uniquely generated in
+ // the extension process to correspond to the given filter and
+ // extra_info_spec. It returns true on success, false on failure.
+ bool AddEventListener(void* browser_context,
+ const std::string& extension_id,
+ const std::string& extension_name,
+ events::HistogramValue histogram_value,
+ const std::string& event_name,
+ const std::string& sub_event_name,
+ const RequestFilter& filter,
+ int extra_info_spec,
+ int embedder_process_id,
+ int web_view_instance_id,
+ base::WeakPtr<IPC::Sender> ipc_sender);
+
+ // Removes the listener for the given sub-event.
+ void RemoveEventListener(
+ void* browser_context,
+ const std::string& extension_id,
+ const std::string& sub_event_name,
+ int embedder_process_id,
+ int web_view_instance_id);
+
+ // Removes the listeners for a given <webview>.
+ void RemoveWebViewEventListeners(
+ void* browser_context,
+ int embedder_process_id,
+ int web_view_instance_id);
+
+ // Called when an incognito browser_context is created or destroyed.
+ void OnOTRBrowserContextCreated(void* original_browser_context,
+ void* otr_browser_context);
+ void OnOTRBrowserContextDestroyed(void* original_browser_context,
+ void* otr_browser_context);
+
+ // Registers a |callback| that is executed when the next page load happens.
+ // The callback is then deleted.
+ void AddCallbackForPageLoad(const base::Closure& callback);
+
+ private:
+ friend struct base::DefaultSingletonTraits<ExtensionWebRequestEventRouter>;
+
+ struct EventListener;
+ using EventListeners = std::vector<const EventListener*>;
+ using ListenerMapForBrowserContext =
+ std::map<std::string, std::set<EventListener>>;
+ using ListenerMap = std::map<void*, ListenerMapForBrowserContext>;
+ using BlockedRequestMap = std::map<uint64_t, BlockedRequest>;
+ // Map of request_id -> bit vector of EventTypes already signaled
+ using SignaledRequestMap = std::map<uint64_t, int>;
+ // For each browser_context: a bool indicating whether it is an incognito
+ // browser_context, and a pointer to the corresponding (non-)incognito
+ // browser_context.
+ using CrossBrowserContextMap = std::map<void*, std::pair<bool, void*>>;
+ using CallbacksForPageLoad = std::list<base::Closure>;
+
+ ExtensionWebRequestEventRouter();
+ ~ExtensionWebRequestEventRouter();
+
+ // Ensures that future callbacks for |request| are ignored so that it can be
+ // destroyed safely.
+ void ClearPendingCallbacks(const net::URLRequest* request);
+
+ bool DispatchEvent(void* browser_context,
+ net::URLRequest* request,
+ const std::vector<const EventListener*>& listeners,
+ scoped_ptr<WebRequestEventDetails> event_details);
+
+ void DispatchEventToListeners(
+ void* browser_context,
+ scoped_ptr<std::vector<EventListener>> listeners,
+ scoped_ptr<WebRequestEventDetails> event_details);
+
+ // Returns a list of event listeners that care about the given event, based
+ // on their filter parameters. |extra_info_spec| will contain the combined
+ // set of extra_info_spec flags that every matching listener asked for.
+ std::vector<const EventListener*> GetMatchingListeners(
+ void* browser_context,
+ const extensions::InfoMap* extension_info_map,
+ const std::string& event_name,
+ const net::URLRequest* request,
+ int* extra_info_spec);
+
+ // Helper for the above functions. This is called twice: once for the
+ // browser_context of the event, the next time for the "cross" browser_context
+ // (i.e. the incognito browser_context if the event is originally for the
+ // normal browser_context, or vice versa).
+ void GetMatchingListenersImpl(
+ void* browser_context,
+ const net::URLRequest* request,
+ const extensions::InfoMap* extension_info_map,
+ bool crosses_incognito,
+ const std::string& event_name,
+ const GURL& url,
+ int render_process_host_id,
+ int routing_id,
+ content::ResourceType resource_type,
+ bool is_async_request,
+ bool is_request_from_extension,
+ int* extra_info_spec,
+ std::vector<const EventListener*>* matching_listeners);
+
+ // Decrements the count of event handlers blocking the given request. When the
+ // count reaches 0, we stop blocking the request and proceed it using the
+ // method requested by the extension with the highest precedence. Precedence
+ // is decided by extension install time. If |response| is non-NULL, this
+ // method assumes ownership.
+ void DecrementBlockCount(void* browser_context,
+ const std::string& extension_id,
+ const std::string& event_name,
+ uint64_t request_id,
+ EventResponse* response);
+
+ // Logs an extension action.
+ void LogExtensionActivity(
+ void* browser_context_id,
+ bool is_incognito,
+ const std::string& extension_id,
+ const GURL& url,
+ const std::string& api_call,
+ scoped_ptr<base::DictionaryValue> details);
+
+ // Processes the generated deltas from blocked_requests_ on the specified
+ // request. If |call_back| is true, the callback registered in
+ // |blocked_requests_| is called.
+ // The function returns the error code for the network request. This is
+ // mostly relevant in case the caller passes |call_callback| = false
+ // and wants to return the correct network error code himself.
+ int ExecuteDeltas(void* browser_context,
+ uint64_t request_id,
+ bool call_callback);
+
+ // Evaluates the rules of the declarative webrequest API and stores
+ // modifications to the request that result from WebRequestActions as
+ // deltas in |blocked_requests_|. |original_response_headers| should only be
+ // set for the OnHeadersReceived stage and NULL otherwise. Returns whether any
+ // deltas were generated.
+ bool ProcessDeclarativeRules(
+ void* browser_context,
+ const extensions::InfoMap* extension_info_map,
+ const std::string& event_name,
+ net::URLRequest* request,
+ extensions::RequestStage request_stage,
+ const net::HttpResponseHeaders* original_response_headers);
+
+ // If the BlockedRequest contains messages_to_extension entries in the event
+ // deltas, we send them to subscribers of
+ // chrome.declarativeWebRequest.onMessage.
+ void SendMessages(
+ void* browser_context, const BlockedRequest& blocked_request);
+
+ // Called when the RulesRegistry is ready to unblock a request that was
+ // waiting for said event.
+ void OnRulesRegistryReady(void* browser_context,
+ const std::string& event_name,
+ uint64_t request_id,
+ extensions::RequestStage request_stage);
+
+ // Returns event details for a given request.
+ scoped_ptr<WebRequestEventDetails> CreateEventDetails(
+ const net::URLRequest* request,
+ int extra_info_spec);
+
+ // Sets the flag that |event_type| has been signaled for |request_id|.
+ // Returns the value of the flag before setting it.
+ bool GetAndSetSignaled(uint64_t request_id, EventTypes event_type);
+
+ // Clears the flag that |event_type| has been signaled for |request_id|.
+ void ClearSignaled(uint64_t request_id, EventTypes event_type);
+
+ // Returns whether |request| represents a top level window navigation.
+ bool IsPageLoad(const net::URLRequest* request) const;
+
+ // Called on a page load to process all registered callbacks.
+ void NotifyPageLoad();
+
+ // Returns the matching cross browser_context (the regular browser_context if
+ // |browser_context| is OTR and vice versa).
+ void* GetCrossBrowserContext(void* browser_context) const;
+
+ // Determines whether the specified browser_context is an incognito
+ // browser_context (based on the contents of the cross-browser_context table
+ // and without dereferencing the browser_context pointer).
+ bool IsIncognitoBrowserContext(void* browser_context) const;
+
+ // Returns true if |request| was already signaled to some event handlers.
+ bool WasSignaled(const net::URLRequest& request) const;
+
+ // A map for each browser_context that maps an event name to a set of
+ // extensions that are listening to that event.
+ ListenerMap listeners_;
+
+ // A map of network requests that are waiting for at least one event handler
+ // to respond.
+ BlockedRequestMap blocked_requests_;
+
+ // A map of request ids to a bitvector indicating which events have been
+ // signaled and should not be sent again.
+ SignaledRequestMap signaled_requests_;
+
+ // A map of original browser_context -> corresponding incognito
+ // browser_context (and vice versa).
+ CrossBrowserContextMap cross_browser_context_map_;
+
+ // Keeps track of time spent waiting on extensions using the blocking
+ // webRequest API.
+ scoped_ptr<ExtensionWebRequestTimeTracker> request_time_tracker_;
+
+ CallbacksForPageLoad callbacks_for_page_load_;
+
+ typedef std::pair<void*, int> RulesRegistryKey;
+ // Maps each browser_context (and OTRBrowserContext) and a webview key to its
+ // respective rules registry.
+ std::map<RulesRegistryKey,
+ scoped_refptr<extensions::WebRequestRulesRegistry> > rules_registries_;
+
+ scoped_ptr<extensions::WebRequestEventRouterDelegate>
+ web_request_event_router_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionWebRequestEventRouter);
+};
+
+class WebRequestInternalFunction : public SyncIOThreadExtensionFunction {
+ public:
+ WebRequestInternalFunction() {}
+
+ protected:
+ ~WebRequestInternalFunction() override {}
+
+ const std::string& extension_id_safe() const {
+ return extension() ? extension_id() : base::EmptyString();
+ }
+};
+
+class WebRequestInternalAddEventListenerFunction
+ : public WebRequestInternalFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webRequestInternal.addEventListener",
+ WEBREQUESTINTERNAL_ADDEVENTLISTENER)
+
+ protected:
+ ~WebRequestInternalAddEventListenerFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+class WebRequestInternalEventHandledFunction
+ : public WebRequestInternalFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webRequestInternal.eventHandled",
+ WEBREQUESTINTERNAL_EVENTHANDLED)
+
+ protected:
+ ~WebRequestInternalEventHandledFunction() override {}
+
+ // Unblocks the network request and sets |error_| such that the developer
+ // console will show the respective error message. Use this function to handle
+ // incorrect requests from the extension that cannot be detected by the schema
+ // validator.
+ void RespondWithError(
+ const std::string& event_name,
+ const std::string& sub_event_name,
+ uint64_t request_id,
+ scoped_ptr<ExtensionWebRequestEventRouter::EventResponse> response,
+ const std::string& error);
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+class WebRequestHandlerBehaviorChangedFunction
+ : public WebRequestInternalFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("webRequest.handlerBehaviorChanged",
+ WEBREQUEST_HANDLERBEHAVIORCHANGED)
+
+ protected:
+ ~WebRequestHandlerBehaviorChangedFunction() override {}
+
+ // ExtensionFunction:
+ void GetQuotaLimitHeuristics(
+ extensions::QuotaLimitHeuristics* heuristics) const override;
+ // Handle quota exceeded gracefully: Only warn the user but still execute the
+ // function.
+ void OnQuotaExceeded(const std::string& error) override;
+ bool RunSync() override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_API_H_
diff --git a/chromium/extensions/browser/api/web_request/web_request_api_constants.cc b/chromium/extensions/browser/api/web_request/web_request_api_constants.cc
new file mode 100644
index 00000000000..fd10ca182df
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/web_request_api_constants.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/web_request/web_request_api_constants.h"
+
+namespace extension_web_request_api_constants {
+
+const char kChallengerKey[] = "challenger";
+const char kErrorKey[] = "error";
+const char kFrameIdKey[] = "frameId";
+const char kParentFrameIdKey[] = "parentFrameId";
+const char kProcessIdKey[] = "processId";
+const char kFromCache[] = "fromCache";
+const char kHostKey[] = "host";
+const char kIpKey[] = "ip";
+const char kPortKey[] = "port";
+const char kMethodKey[] = "method";
+const char kRedirectUrlKey[] = "redirectUrl";
+const char kRequestIdKey[] = "requestId";
+const char kStatusCodeKey[] = "statusCode";
+const char kStatusLineKey[] = "statusLine";
+const char kTabIdKey[] = "tabId";
+const char kTimeStampKey[] = "timeStamp";
+const char kTypeKey[] = "type";
+const char kUrlKey[] = "url";
+const char kRequestBodyKey[] = "requestBody";
+const char kRequestBodyErrorKey[] = "error";
+const char kRequestBodyFormDataKey[] = "formData";
+const char kRequestBodyRawKey[] = "raw";
+const char kRequestBodyRawBytesKey[] = "bytes";
+const char kRequestBodyRawFileKey[] = "file";
+const char kRequestHeadersKey[] = "requestHeaders";
+const char kResponseHeadersKey[] = "responseHeaders";
+const char kHeaderNameKey[] = "name";
+const char kHeaderValueKey[] = "value";
+const char kHeaderBinaryValueKey[] = "binaryValue";
+const char kIsProxyKey[] = "isProxy";
+const char kMessageKey[] = "message";
+const char kSchemeKey[] = "scheme";
+const char kStageKey[] = "stage";
+const char kRealmKey[] = "realm";
+const char kAuthCredentialsKey[] = "authCredentials";
+const char kUsernameKey[] = "username";
+const char kPasswordKey[] = "password";
+
+const char kOnBeforeRedirectEvent[] = "webRequest.onBeforeRedirect";
+const char kOnBeforeSendHeadersEvent[] = "webRequest.onBeforeSendHeaders";
+const char kOnCompletedEvent[] = "webRequest.onCompleted";
+const char kOnHeadersReceivedEvent[] = "webRequest.onHeadersReceived";
+const char kOnResponseStartedEvent[] = "webRequest.onResponseStarted";
+const char kOnSendHeadersEvent[] = "webRequest.onSendHeaders";
+const char kOnAuthRequiredEvent[] = "webRequest.onAuthRequired";
+
+const char kOnBeforeRedirect[] = "onBeforeRedirect";
+const char kOnBeforeRequest[] = "onBeforeRequest";
+const char kOnBeforeSendHeaders[] = "onBeforeSendHeaders";
+const char kOnCompleted[] = "onCompleted";
+const char kOnErrorOccurred[] = "onErrorOccurred";
+const char kOnHeadersReceived[] = "onHeadersReceived";
+const char kOnResponseStarted[] = "onResponseStarted";
+const char kOnSendHeaders[] = "onSendHeaders";
+const char kOnAuthRequired[] = "onAuthRequired";
+
+const char kInvalidRedirectUrl[] = "redirectUrl '*' is not a valid URL.";
+const char kInvalidBlockingResponse[] =
+ "cancel cannot be true in the presence of other keys.";
+const char kInvalidRequestFilterUrl[] = "'*' is not a valid URL pattern.";
+const char kBlockingPermissionRequired[] =
+ "You do not have permission to use blocking webRequest listeners. "
+ "Be sure to declare the webRequestBlocking permission in your "
+ "manifest.";
+const char kHostPermissionsRequired[] =
+ "You need to request host permissions in the manifest file in order to "
+ "be notified about requests from the webRequest API.";
+const char kInvalidHeaderKeyCombination[] =
+ "requestHeaders and responseHeaders cannot both be present.";
+const char kInvalidHeader[] = "Invalid header specification '*'.";
+const char kInvalidHeaderName[] = "Invalid header name.";
+const char kInvalidHeaderValue[] = "Header '*' has an invalid value.";
+
+} // namespace extension_web_request_api_constants
diff --git a/chromium/extensions/browser/api/web_request/web_request_api_constants.h b/chromium/extensions/browser/api/web_request/web_request_api_constants.h
new file mode 100644
index 00000000000..ce51d066cc2
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/web_request_api_constants.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Constants used for the WebRequest API.
+
+#ifndef EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_API_CONSTANTS_H_
+#define EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_API_CONSTANTS_H_
+
+namespace extension_web_request_api_constants {
+
+// Keys.
+extern const char kChallengerKey[];
+extern const char kErrorKey[];
+extern const char kFrameIdKey[];
+extern const char kParentFrameIdKey[];
+extern const char kProcessIdKey[];
+extern const char kFromCache[];
+extern const char kHostKey[];
+extern const char kIpKey[];
+extern const char kMethodKey[];
+extern const char kPortKey[];
+extern const char kRedirectUrlKey[];
+extern const char kRequestIdKey[];
+extern const char kStatusCodeKey[];
+extern const char kStatusLineKey[];
+extern const char kTabIdKey[];
+extern const char kTimeStampKey[];
+extern const char kTypeKey[];
+extern const char kUrlKey[];
+extern const char kRequestBodyKey[];
+extern const char kRequestBodyErrorKey[];
+extern const char kRequestBodyFormDataKey[];
+extern const char kRequestBodyRawKey[];
+extern const char kRequestBodyRawBytesKey[];
+extern const char kRequestBodyRawFileKey[];
+extern const char kPostDataKey[];
+extern const char kPostDataFormKey[];
+extern const char kRequestHeadersKey[];
+extern const char kResponseHeadersKey[];
+extern const char kHeadersKey[];
+extern const char kHeaderNameKey[];
+extern const char kHeaderValueKey[];
+extern const char kHeaderBinaryValueKey[];
+extern const char kIsProxyKey[];
+extern const char kMessageKey[];
+extern const char kSchemeKey[];
+extern const char kStageKey[];
+extern const char kRealmKey[];
+extern const char kAuthCredentialsKey[];
+extern const char kUsernameKey[];
+extern const char kPasswordKey[];
+
+// Events.
+extern const char kOnAuthRequiredEvent[];
+extern const char kOnBeforeRedirectEvent[];
+extern const char kOnBeforeRequestEvent[];
+extern const char kOnBeforeSendHeadersEvent[];
+extern const char kOnCompletedEvent[];
+extern const char kOnErrorOccurredEvent[];
+extern const char kOnHeadersReceivedEvent[];
+extern const char kOnResponseStartedEvent[];
+extern const char kOnSendHeadersEvent[];
+
+// Stages.
+extern const char kOnAuthRequired[];
+extern const char kOnBeforeRedirect[];
+extern const char kOnBeforeRequest[];
+extern const char kOnBeforeSendHeaders[];
+extern const char kOnCompleted[];
+extern const char kOnErrorOccurred[];
+extern const char kOnHeadersReceived[];
+extern const char kOnResponseStarted[];
+extern const char kOnSendHeaders[];
+
+// Error messages.
+extern const char kInvalidRedirectUrl[];
+extern const char kInvalidBlockingResponse[];
+extern const char kInvalidRequestFilterUrl[];
+extern const char kBlockingPermissionRequired[];
+extern const char kHostPermissionsRequired[];
+extern const char kInvalidHeaderKeyCombination[];
+extern const char kInvalidHeader[];
+extern const char kInvalidHeaderName[];
+extern const char kInvalidHeaderValue[];
+
+} // namespace extension_web_request_api_constants
+
+#endif // EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_API_CONSTANTS_H_
diff --git a/chromium/extensions/browser/api/web_request/web_request_api_helpers.cc b/chromium/extensions/browser/api/web_request/web_request_api_helpers.cc
new file mode 100644
index 00000000000..6bdb87c7bef
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/web_request_api_helpers.cc
@@ -0,0 +1,1327 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/web_request/web_request_api_helpers.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <cmath>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "components/web_cache/browser/web_cache_manager.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "extensions/browser/api/web_request/web_request_api_constants.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/runtime_data.h"
+#include "extensions/browser/warning_set.h"
+#include "extensions/common/extension_messages.h"
+#include "net/cookies/cookie_util.h"
+#include "net/cookies/parsed_cookie.h"
+#include "net/http/http_util.h"
+#include "net/log/net_log.h"
+#include "net/url_request/url_request.h"
+#include "url/url_constants.h"
+
+// TODO(battre): move all static functions into an anonymous namespace at the
+// top of this file.
+
+using base::Time;
+using content::ResourceType;
+using net::cookie_util::ParsedRequestCookie;
+using net::cookie_util::ParsedRequestCookies;
+
+namespace keys = extension_web_request_api_constants;
+
+namespace extension_web_request_api_helpers {
+
+namespace {
+
+// Multiple ResourceTypes may map to the same string, but the converse is not
+// possible.
+static const char* kResourceTypeStrings[] = {
+ "main_frame",
+ "sub_frame",
+ "stylesheet",
+ "script",
+ "image",
+ "font",
+ "object",
+ "script",
+ "script",
+ "image",
+ "xmlhttprequest",
+ "ping",
+ "script",
+ "object",
+ "other",
+};
+
+const size_t kResourceTypeStringsLength = arraysize(kResourceTypeStrings);
+
+static ResourceType kResourceTypeValues[] = {
+ content::RESOURCE_TYPE_MAIN_FRAME,
+ content::RESOURCE_TYPE_SUB_FRAME,
+ content::RESOURCE_TYPE_STYLESHEET,
+ content::RESOURCE_TYPE_SCRIPT,
+ content::RESOURCE_TYPE_IMAGE,
+ content::RESOURCE_TYPE_FONT_RESOURCE,
+ content::RESOURCE_TYPE_OBJECT,
+ content::RESOURCE_TYPE_WORKER,
+ content::RESOURCE_TYPE_SHARED_WORKER,
+ content::RESOURCE_TYPE_FAVICON,
+ content::RESOURCE_TYPE_XHR,
+ content::RESOURCE_TYPE_PING,
+ content::RESOURCE_TYPE_SERVICE_WORKER,
+ content::RESOURCE_TYPE_PLUGIN_RESOURCE,
+ content::RESOURCE_TYPE_LAST_TYPE, // represents "other"
+};
+
+const size_t kResourceTypeValuesLength = arraysize(kResourceTypeValues);
+
+static_assert(kResourceTypeStringsLength == kResourceTypeValuesLength,
+ "Sizes of string lists and ResourceType lists should be equal");
+
+typedef std::vector<linked_ptr<net::ParsedCookie> > ParsedResponseCookies;
+
+void ClearCacheOnNavigationOnUI() {
+ web_cache::WebCacheManager::GetInstance()->ClearCacheOnNavigation();
+}
+
+bool ParseCookieLifetime(net::ParsedCookie* cookie,
+ int64_t* seconds_till_expiry) {
+ // 'Max-Age' is processed first because according to:
+ // http://tools.ietf.org/html/rfc6265#section-5.3 'Max-Age' attribute
+ // overrides 'Expires' attribute.
+ if (cookie->HasMaxAge() &&
+ base::StringToInt64(cookie->MaxAge(), seconds_till_expiry)) {
+ return true;
+ }
+
+ Time parsed_expiry_time;
+ if (cookie->HasExpires())
+ parsed_expiry_time = net::cookie_util::ParseCookieTime(cookie->Expires());
+
+ if (!parsed_expiry_time.is_null()) {
+ *seconds_till_expiry =
+ ceil((parsed_expiry_time - Time::Now()).InSecondsF());
+ return *seconds_till_expiry >= 0;
+ }
+ return false;
+}
+
+bool NullableEquals(const int* a, const int* b) {
+ if ((a && !b) || (!a && b))
+ return false;
+ return (!a) || (*a == *b);
+}
+
+bool NullableEquals(const bool* a, const bool* b) {
+ if ((a && !b) || (!a && b))
+ return false;
+ return (!a) || (*a == *b);
+}
+
+bool NullableEquals(const std::string* a, const std::string* b) {
+ if ((a && !b) || (!a && b))
+ return false;
+ return (!a) || (*a == *b);
+}
+
+} // namespace
+
+bool ExtraInfoSpec::InitFromValue(const base::ListValue& value,
+ int* extra_info_spec) {
+ *extra_info_spec = 0;
+ for (size_t i = 0; i < value.GetSize(); ++i) {
+ std::string str;
+ if (!value.GetString(i, &str))
+ return false;
+
+ if (str == "requestHeaders")
+ *extra_info_spec |= REQUEST_HEADERS;
+ else if (str == "responseHeaders")
+ *extra_info_spec |= RESPONSE_HEADERS;
+ else if (str == "blocking")
+ *extra_info_spec |= BLOCKING;
+ else if (str == "asyncBlocking")
+ *extra_info_spec |= ASYNC_BLOCKING;
+ else if (str == "requestBody")
+ *extra_info_spec |= REQUEST_BODY;
+ else
+ return false;
+ }
+ // BLOCKING and ASYNC_BLOCKING are mutually exclusive.
+ if ((*extra_info_spec & BLOCKING) && (*extra_info_spec & ASYNC_BLOCKING))
+ return false;
+ return true;
+}
+
+RequestCookie::RequestCookie() {}
+RequestCookie::~RequestCookie() {}
+
+bool NullableEquals(const RequestCookie* a, const RequestCookie* b) {
+ if ((a && !b) || (!a && b))
+ return false;
+ if (!a)
+ return true;
+ return NullableEquals(a->name.get(), b->name.get()) &&
+ NullableEquals(a->value.get(), b->value.get());
+}
+
+ResponseCookie::ResponseCookie() {}
+ResponseCookie::~ResponseCookie() {}
+
+bool NullableEquals(const ResponseCookie* a, const ResponseCookie* b) {
+ if ((a && !b) || (!a && b))
+ return false;
+ if (!a)
+ return true;
+ return NullableEquals(a->name.get(), b->name.get()) &&
+ NullableEquals(a->value.get(), b->value.get()) &&
+ NullableEquals(a->expires.get(), b->expires.get()) &&
+ NullableEquals(a->max_age.get(), b->max_age.get()) &&
+ NullableEquals(a->domain.get(), b->domain.get()) &&
+ NullableEquals(a->path.get(), b->path.get()) &&
+ NullableEquals(a->secure.get(), b->secure.get()) &&
+ NullableEquals(a->http_only.get(), b->http_only.get());
+}
+
+FilterResponseCookie::FilterResponseCookie() {}
+FilterResponseCookie::~FilterResponseCookie() {}
+
+bool NullableEquals(const FilterResponseCookie* a,
+ const FilterResponseCookie* b) {
+ if ((a && !b) || (!a && b))
+ return false;
+ if (!a)
+ return true;
+ return NullableEquals(a->age_lower_bound.get(), b->age_lower_bound.get()) &&
+ NullableEquals(a->age_upper_bound.get(), b->age_upper_bound.get()) &&
+ NullableEquals(a->session_cookie.get(), b->session_cookie.get());
+}
+
+RequestCookieModification::RequestCookieModification() {}
+RequestCookieModification::~RequestCookieModification() {}
+
+bool NullableEquals(const RequestCookieModification* a,
+ const RequestCookieModification* b) {
+ if ((a && !b) || (!a && b))
+ return false;
+ if (!a)
+ return true;
+ return NullableEquals(a->filter.get(), b->filter.get()) &&
+ NullableEquals(a->modification.get(), b->modification.get());
+}
+
+ResponseCookieModification::ResponseCookieModification() : type(ADD) {}
+ResponseCookieModification::~ResponseCookieModification() {}
+
+bool NullableEquals(const ResponseCookieModification* a,
+ const ResponseCookieModification* b) {
+ if ((a && !b) || (!a && b))
+ return false;
+ if (!a)
+ return true;
+ return a->type == b->type &&
+ NullableEquals(a->filter.get(), b->filter.get()) &&
+ NullableEquals(a->modification.get(), b->modification.get());
+}
+
+EventResponseDelta::EventResponseDelta(
+ const std::string& extension_id, const base::Time& extension_install_time)
+ : extension_id(extension_id),
+ extension_install_time(extension_install_time),
+ cancel(false) {
+}
+
+EventResponseDelta::~EventResponseDelta() {
+}
+
+
+// Creates a NetLog callback the returns a Value with the ID of the extension
+// that caused an event. |delta| must remain valid for the lifetime of the
+// callback.
+net::NetLog::ParametersCallback CreateNetLogExtensionIdCallback(
+ const EventResponseDelta* delta) {
+ return net::NetLog::StringCallback("extension_id", &delta->extension_id);
+}
+
+// Creates NetLog parameters to indicate that an extension modified a request.
+scoped_ptr<base::Value> NetLogModificationCallback(
+ const EventResponseDelta* delta,
+ net::NetLogCaptureMode capture_mode) {
+ scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
+ dict->SetString("extension_id", delta->extension_id);
+
+ base::ListValue* modified_headers = new base::ListValue();
+ net::HttpRequestHeaders::Iterator modification(
+ delta->modified_request_headers);
+ while (modification.GetNext()) {
+ std::string line = modification.name() + ": " + modification.value();
+ modified_headers->Append(new base::StringValue(line));
+ }
+ dict->Set("modified_headers", modified_headers);
+
+ base::ListValue* deleted_headers = new base::ListValue();
+ for (std::vector<std::string>::const_iterator key =
+ delta->deleted_request_headers.begin();
+ key != delta->deleted_request_headers.end();
+ ++key) {
+ deleted_headers->Append(new base::StringValue(*key));
+ }
+ dict->Set("deleted_headers", deleted_headers);
+ return std::move(dict);
+}
+
+bool InDecreasingExtensionInstallationTimeOrder(
+ const linked_ptr<EventResponseDelta>& a,
+ const linked_ptr<EventResponseDelta>& b) {
+ return a->extension_install_time > b->extension_install_time;
+}
+
+base::ListValue* StringToCharList(const std::string& s) {
+ base::ListValue* result = new base::ListValue;
+ for (size_t i = 0, n = s.size(); i < n; ++i) {
+ result->Append(
+ new base::FundamentalValue(
+ *reinterpret_cast<const unsigned char*>(&s[i])));
+ }
+ return result;
+}
+
+bool CharListToString(const base::ListValue* list, std::string* out) {
+ if (!list)
+ return false;
+ const size_t list_length = list->GetSize();
+ out->resize(list_length);
+ int value = 0;
+ for (size_t i = 0; i < list_length; ++i) {
+ if (!list->GetInteger(i, &value) || value < 0 || value > 255)
+ return false;
+ unsigned char tmp = static_cast<unsigned char>(value);
+ (*out)[i] = *reinterpret_cast<char*>(&tmp);
+ }
+ return true;
+}
+
+EventResponseDelta* CalculateOnBeforeRequestDelta(
+ const std::string& extension_id,
+ const base::Time& extension_install_time,
+ bool cancel,
+ const GURL& new_url) {
+ EventResponseDelta* result =
+ new EventResponseDelta(extension_id, extension_install_time);
+ result->cancel = cancel;
+ result->new_url = new_url;
+ return result;
+}
+
+EventResponseDelta* CalculateOnBeforeSendHeadersDelta(
+ const std::string& extension_id,
+ const base::Time& extension_install_time,
+ bool cancel,
+ net::HttpRequestHeaders* old_headers,
+ net::HttpRequestHeaders* new_headers) {
+ EventResponseDelta* result =
+ new EventResponseDelta(extension_id, extension_install_time);
+ result->cancel = cancel;
+
+ // The event listener might not have passed any new headers if he
+ // just wanted to cancel the request.
+ if (new_headers) {
+ // Find deleted headers.
+ {
+ net::HttpRequestHeaders::Iterator i(*old_headers);
+ while (i.GetNext()) {
+ if (!new_headers->HasHeader(i.name())) {
+ result->deleted_request_headers.push_back(i.name());
+ }
+ }
+ }
+
+ // Find modified headers.
+ {
+ net::HttpRequestHeaders::Iterator i(*new_headers);
+ while (i.GetNext()) {
+ std::string value;
+ if (!old_headers->GetHeader(i.name(), &value) || i.value() != value) {
+ result->modified_request_headers.SetHeader(i.name(), i.value());
+ }
+ }
+ }
+ }
+ return result;
+}
+
+EventResponseDelta* CalculateOnHeadersReceivedDelta(
+ const std::string& extension_id,
+ const base::Time& extension_install_time,
+ bool cancel,
+ const GURL& new_url,
+ const net::HttpResponseHeaders* old_response_headers,
+ ResponseHeaders* new_response_headers) {
+ EventResponseDelta* result =
+ new EventResponseDelta(extension_id, extension_install_time);
+ result->cancel = cancel;
+ result->new_url = new_url;
+
+ if (!new_response_headers)
+ return result;
+
+ // Find deleted headers (header keys are treated case insensitively).
+ {
+ size_t iter = 0;
+ std::string name;
+ std::string value;
+ while (old_response_headers->EnumerateHeaderLines(&iter, &name, &value)) {
+ std::string name_lowercase = base::ToLowerASCII(name);
+
+ bool header_found = false;
+ for (const auto& i : *new_response_headers) {
+ if (base::LowerCaseEqualsASCII(i.first, name_lowercase) &&
+ value == i.second) {
+ header_found = true;
+ break;
+ }
+ }
+ if (!header_found)
+ result->deleted_response_headers.push_back(ResponseHeader(name, value));
+ }
+ }
+
+ // Find added headers (header keys are treated case insensitively).
+ {
+ for (const auto& i : *new_response_headers) {
+ std::string name_lowercase = base::ToLowerASCII(i.first);
+ size_t iter = 0;
+ std::string name;
+ std::string value;
+ bool header_found = false;
+ while (old_response_headers->EnumerateHeaderLines(&iter, &name, &value)) {
+ if (base::LowerCaseEqualsASCII(name, name_lowercase) &&
+ value == i.second) {
+ header_found = true;
+ break;
+ }
+ }
+ if (!header_found)
+ result->added_response_headers.push_back(i);
+ }
+ }
+
+ return result;
+}
+
+EventResponseDelta* CalculateOnAuthRequiredDelta(
+ const std::string& extension_id,
+ const base::Time& extension_install_time,
+ bool cancel,
+ scoped_ptr<net::AuthCredentials>* auth_credentials) {
+ EventResponseDelta* result =
+ new EventResponseDelta(extension_id, extension_install_time);
+ result->cancel = cancel;
+ result->auth_credentials.swap(*auth_credentials);
+ return result;
+}
+
+void MergeCancelOfResponses(
+ const EventResponseDeltas& deltas,
+ bool* canceled,
+ const net::BoundNetLog* net_log) {
+ for (EventResponseDeltas::const_iterator i = deltas.begin();
+ i != deltas.end(); ++i) {
+ if ((*i)->cancel) {
+ *canceled = true;
+ net_log->AddEvent(
+ net::NetLog::TYPE_CHROME_EXTENSION_ABORTED_REQUEST,
+ CreateNetLogExtensionIdCallback(i->get()));
+ break;
+ }
+ }
+}
+
+// Helper function for MergeRedirectUrlOfResponses() that allows ignoring
+// all redirects but those to data:// urls and about:blank. This is important
+// to treat these URLs as "cancel urls", i.e. URLs that extensions redirect
+// to if they want to express that they want to cancel a request. This reduces
+// the number of conflicts that we need to flag, as canceling is considered
+// a higher precedence operation that redirects.
+// Returns whether a redirect occurred.
+static bool MergeRedirectUrlOfResponsesHelper(
+ const EventResponseDeltas& deltas,
+ GURL* new_url,
+ extensions::WarningSet* conflicting_extensions,
+ const net::BoundNetLog* net_log,
+ bool consider_only_cancel_scheme_urls) {
+ bool redirected = false;
+
+ // Extension that determines the |new_url|.
+ std::string winning_extension_id;
+ EventResponseDeltas::const_iterator delta;
+ for (delta = deltas.begin(); delta != deltas.end(); ++delta) {
+ if ((*delta)->new_url.is_empty())
+ continue;
+ if (consider_only_cancel_scheme_urls &&
+ !(*delta)->new_url.SchemeIs(url::kDataScheme) &&
+ (*delta)->new_url.spec() != "about:blank") {
+ continue;
+ }
+
+ if (!redirected || *new_url == (*delta)->new_url) {
+ *new_url = (*delta)->new_url;
+ winning_extension_id = (*delta)->extension_id;
+ redirected = true;
+ net_log->AddEvent(
+ net::NetLog::TYPE_CHROME_EXTENSION_REDIRECTED_REQUEST,
+ CreateNetLogExtensionIdCallback(delta->get()));
+ } else {
+ conflicting_extensions->insert(
+ extensions::Warning::CreateRedirectConflictWarning(
+ (*delta)->extension_id,
+ winning_extension_id,
+ (*delta)->new_url,
+ *new_url));
+ net_log->AddEvent(
+ net::NetLog::TYPE_CHROME_EXTENSION_IGNORED_DUE_TO_CONFLICT,
+ CreateNetLogExtensionIdCallback(delta->get()));
+ }
+ }
+ return redirected;
+}
+
+void MergeRedirectUrlOfResponses(
+ const EventResponseDeltas& deltas,
+ GURL* new_url,
+ extensions::WarningSet* conflicting_extensions,
+ const net::BoundNetLog* net_log) {
+
+ // First handle only redirects to data:// URLs and about:blank. These are a
+ // special case as they represent a way of cancelling a request.
+ if (MergeRedirectUrlOfResponsesHelper(
+ deltas, new_url, conflicting_extensions, net_log, true)) {
+ // If any extension cancelled a request by redirecting to a data:// URL or
+ // about:blank, we don't consider the other redirects.
+ return;
+ }
+
+ // Handle all other redirects.
+ MergeRedirectUrlOfResponsesHelper(
+ deltas, new_url, conflicting_extensions, net_log, false);
+}
+
+void MergeOnBeforeRequestResponses(
+ const EventResponseDeltas& deltas,
+ GURL* new_url,
+ extensions::WarningSet* conflicting_extensions,
+ const net::BoundNetLog* net_log) {
+ MergeRedirectUrlOfResponses(deltas, new_url, conflicting_extensions, net_log);
+}
+
+static bool DoesRequestCookieMatchFilter(
+ const ParsedRequestCookie& cookie,
+ RequestCookie* filter) {
+ if (!filter) return true;
+ if (filter->name.get() && cookie.first != *filter->name) return false;
+ if (filter->value.get() && cookie.second != *filter->value) return false;
+ return true;
+}
+
+// Applies all CookieModificationType::ADD operations for request cookies of
+// |deltas| to |cookies|. Returns whether any cookie was added.
+static bool MergeAddRequestCookieModifications(
+ const EventResponseDeltas& deltas,
+ ParsedRequestCookies* cookies) {
+ bool modified = false;
+ // We assume here that the deltas are sorted in decreasing extension
+ // precedence (i.e. decreasing extension installation time).
+ EventResponseDeltas::const_reverse_iterator delta;
+ for (delta = deltas.rbegin(); delta != deltas.rend(); ++delta) {
+ const RequestCookieModifications& modifications =
+ (*delta)->request_cookie_modifications;
+ for (RequestCookieModifications::const_iterator mod = modifications.begin();
+ mod != modifications.end(); ++mod) {
+ if ((*mod)->type != ADD || !(*mod)->modification.get())
+ continue;
+ std::string* new_name = (*mod)->modification->name.get();
+ std::string* new_value = (*mod)->modification->value.get();
+ if (!new_name || !new_value)
+ continue;
+
+ bool cookie_with_same_name_found = false;
+ for (ParsedRequestCookies::iterator cookie = cookies->begin();
+ cookie != cookies->end() && !cookie_with_same_name_found; ++cookie) {
+ if (cookie->first == *new_name) {
+ if (cookie->second != *new_value) {
+ cookie->second = *new_value;
+ modified = true;
+ }
+ cookie_with_same_name_found = true;
+ }
+ }
+ if (!cookie_with_same_name_found) {
+ cookies->push_back(std::make_pair(base::StringPiece(*new_name),
+ base::StringPiece(*new_value)));
+ modified = true;
+ }
+ }
+ }
+ return modified;
+}
+
+// Applies all CookieModificationType::EDIT operations for request cookies of
+// |deltas| to |cookies|. Returns whether any cookie was modified.
+static bool MergeEditRequestCookieModifications(
+ const EventResponseDeltas& deltas,
+ ParsedRequestCookies* cookies) {
+ bool modified = false;
+ // We assume here that the deltas are sorted in decreasing extension
+ // precedence (i.e. decreasing extension installation time).
+ EventResponseDeltas::const_reverse_iterator delta;
+ for (delta = deltas.rbegin(); delta != deltas.rend(); ++delta) {
+ const RequestCookieModifications& modifications =
+ (*delta)->request_cookie_modifications;
+ for (RequestCookieModifications::const_iterator mod = modifications.begin();
+ mod != modifications.end(); ++mod) {
+ if ((*mod)->type != EDIT || !(*mod)->modification.get())
+ continue;
+
+ std::string* new_value = (*mod)->modification->value.get();
+ RequestCookie* filter = (*mod)->filter.get();
+ for (ParsedRequestCookies::iterator cookie = cookies->begin();
+ cookie != cookies->end(); ++cookie) {
+ if (!DoesRequestCookieMatchFilter(*cookie, filter))
+ continue;
+ // If the edit operation tries to modify the cookie name, we just ignore
+ // this. We only modify the cookie value.
+ if (new_value && cookie->second != *new_value) {
+ cookie->second = *new_value;
+ modified = true;
+ }
+ }
+ }
+ }
+ return modified;
+}
+
+// Applies all CookieModificationType::REMOVE operations for request cookies of
+// |deltas| to |cookies|. Returns whether any cookie was deleted.
+static bool MergeRemoveRequestCookieModifications(
+ const EventResponseDeltas& deltas,
+ ParsedRequestCookies* cookies) {
+ bool modified = false;
+ // We assume here that the deltas are sorted in decreasing extension
+ // precedence (i.e. decreasing extension installation time).
+ EventResponseDeltas::const_reverse_iterator delta;
+ for (delta = deltas.rbegin(); delta != deltas.rend(); ++delta) {
+ const RequestCookieModifications& modifications =
+ (*delta)->request_cookie_modifications;
+ for (RequestCookieModifications::const_iterator mod = modifications.begin();
+ mod != modifications.end(); ++mod) {
+ if ((*mod)->type != REMOVE)
+ continue;
+
+ RequestCookie* filter = (*mod)->filter.get();
+ ParsedRequestCookies::iterator i = cookies->begin();
+ while (i != cookies->end()) {
+ if (DoesRequestCookieMatchFilter(*i, filter)) {
+ i = cookies->erase(i);
+ modified = true;
+ } else {
+ ++i;
+ }
+ }
+ }
+ }
+ return modified;
+}
+
+void MergeCookiesInOnBeforeSendHeadersResponses(
+ const EventResponseDeltas& deltas,
+ net::HttpRequestHeaders* request_headers,
+ extensions::WarningSet* conflicting_extensions,
+ const net::BoundNetLog* net_log) {
+ // Skip all work if there are no registered cookie modifications.
+ bool cookie_modifications_exist = false;
+ EventResponseDeltas::const_iterator delta;
+ for (delta = deltas.begin(); delta != deltas.end(); ++delta) {
+ cookie_modifications_exist |=
+ !(*delta)->request_cookie_modifications.empty();
+ }
+ if (!cookie_modifications_exist)
+ return;
+
+ // Parse old cookie line.
+ std::string cookie_header;
+ request_headers->GetHeader(net::HttpRequestHeaders::kCookie, &cookie_header);
+ ParsedRequestCookies cookies;
+ net::cookie_util::ParseRequestCookieLine(cookie_header, &cookies);
+
+ // Modify cookies.
+ bool modified = false;
+ modified |= MergeAddRequestCookieModifications(deltas, &cookies);
+ modified |= MergeEditRequestCookieModifications(deltas, &cookies);
+ modified |= MergeRemoveRequestCookieModifications(deltas, &cookies);
+
+ // Reassemble and store new cookie line.
+ if (modified) {
+ std::string new_cookie_header =
+ net::cookie_util::SerializeRequestCookieLine(cookies);
+ request_headers->SetHeader(net::HttpRequestHeaders::kCookie,
+ new_cookie_header);
+ }
+}
+
+// Returns the extension ID of the first extension in |deltas| that sets the
+// request header identified by |key| to |value|.
+static std::string FindSetRequestHeader(
+ const EventResponseDeltas& deltas,
+ const std::string& key,
+ const std::string& value) {
+ EventResponseDeltas::const_iterator delta;
+ for (delta = deltas.begin(); delta != deltas.end(); ++delta) {
+ net::HttpRequestHeaders::Iterator modification(
+ (*delta)->modified_request_headers);
+ while (modification.GetNext()) {
+ if (base::EqualsCaseInsensitiveASCII(key, modification.name()) &&
+ value == modification.value())
+ return (*delta)->extension_id;
+ }
+ }
+ return std::string();
+}
+
+// Returns the extension ID of the first extension in |deltas| that removes the
+// request header identified by |key|.
+static std::string FindRemoveRequestHeader(
+ const EventResponseDeltas& deltas,
+ const std::string& key) {
+ EventResponseDeltas::const_iterator delta;
+ for (delta = deltas.begin(); delta != deltas.end(); ++delta) {
+ std::vector<std::string>::iterator i;
+ for (i = (*delta)->deleted_request_headers.begin();
+ i != (*delta)->deleted_request_headers.end();
+ ++i) {
+ if (base::EqualsCaseInsensitiveASCII(*i, key))
+ return (*delta)->extension_id;
+ }
+ }
+ return std::string();
+}
+
+void MergeOnBeforeSendHeadersResponses(
+ const EventResponseDeltas& deltas,
+ net::HttpRequestHeaders* request_headers,
+ extensions::WarningSet* conflicting_extensions,
+ const net::BoundNetLog* net_log) {
+ EventResponseDeltas::const_iterator delta;
+
+ // Here we collect which headers we have removed or set to new values
+ // so far due to extensions of higher precedence.
+ std::set<std::string> removed_headers;
+ std::set<std::string> set_headers;
+
+ // We assume here that the deltas are sorted in decreasing extension
+ // precedence (i.e. decreasing extension installation time).
+ for (delta = deltas.begin(); delta != deltas.end(); ++delta) {
+ if ((*delta)->modified_request_headers.IsEmpty() &&
+ (*delta)->deleted_request_headers.empty()) {
+ continue;
+ }
+
+ // Check whether any modification affects a request header that
+ // has been modified differently before. As deltas is sorted by decreasing
+ // extension installation order, this takes care of precedence.
+ bool extension_conflicts = false;
+ std::string winning_extension_id;
+ std::string conflicting_header;
+ {
+ net::HttpRequestHeaders::Iterator modification(
+ (*delta)->modified_request_headers);
+ while (modification.GetNext() && !extension_conflicts) {
+ // This modification sets |key| to |value|.
+ const std::string& key = modification.name();
+ const std::string& value = modification.value();
+
+ // We must not delete anything that has been modified before.
+ if (removed_headers.find(key) != removed_headers.end() &&
+ !extension_conflicts) {
+ winning_extension_id = FindRemoveRequestHeader(deltas, key);
+ conflicting_header = key;
+ extension_conflicts = true;
+ }
+
+ // We must not modify anything that has been set to a *different*
+ // value before.
+ if (set_headers.find(key) != set_headers.end() &&
+ !extension_conflicts) {
+ std::string current_value;
+ if (!request_headers->GetHeader(key, &current_value) ||
+ current_value != value) {
+ winning_extension_id =
+ FindSetRequestHeader(deltas, key, current_value);
+ conflicting_header = key;
+ extension_conflicts = true;
+ }
+ }
+ }
+ }
+
+ // Check whether any deletion affects a request header that has been
+ // modified before.
+ {
+ std::vector<std::string>::iterator key;
+ for (key = (*delta)->deleted_request_headers.begin();
+ key != (*delta)->deleted_request_headers.end() &&
+ !extension_conflicts;
+ ++key) {
+ if (set_headers.find(*key) != set_headers.end()) {
+ std::string current_value;
+ request_headers->GetHeader(*key, &current_value);
+ winning_extension_id =
+ FindSetRequestHeader(deltas, *key, current_value);
+ conflicting_header = *key;
+ extension_conflicts = true;
+ }
+ }
+ }
+
+ // Now execute the modifications if there were no conflicts.
+ if (!extension_conflicts) {
+ // Copy all modifications into the original headers.
+ request_headers->MergeFrom((*delta)->modified_request_headers);
+ {
+ // Record which keys were changed.
+ net::HttpRequestHeaders::Iterator modification(
+ (*delta)->modified_request_headers);
+ while (modification.GetNext())
+ set_headers.insert(modification.name());
+ }
+
+ // Perform all deletions and record which keys were deleted.
+ {
+ std::vector<std::string>::iterator key;
+ for (key = (*delta)->deleted_request_headers.begin();
+ key != (*delta)->deleted_request_headers.end();
+ ++key) {
+ request_headers->RemoveHeader(*key);
+ removed_headers.insert(*key);
+ }
+ }
+ net_log->AddEvent(
+ net::NetLog::TYPE_CHROME_EXTENSION_MODIFIED_HEADERS,
+ base::Bind(&NetLogModificationCallback, delta->get()));
+ } else {
+ conflicting_extensions->insert(
+ extensions::Warning::CreateRequestHeaderConflictWarning(
+ (*delta)->extension_id, winning_extension_id,
+ conflicting_header));
+ net_log->AddEvent(
+ net::NetLog::TYPE_CHROME_EXTENSION_IGNORED_DUE_TO_CONFLICT,
+ CreateNetLogExtensionIdCallback(delta->get()));
+ }
+ }
+
+ MergeCookiesInOnBeforeSendHeadersResponses(deltas, request_headers,
+ conflicting_extensions, net_log);
+}
+
+// Retrives all cookies from |override_response_headers|.
+static ParsedResponseCookies GetResponseCookies(
+ scoped_refptr<net::HttpResponseHeaders> override_response_headers) {
+ ParsedResponseCookies result;
+
+ size_t iter = 0;
+ std::string value;
+ while (override_response_headers->EnumerateHeader(&iter, "Set-Cookie",
+ &value)) {
+ result.push_back(make_linked_ptr(new net::ParsedCookie(value)));
+ }
+ return result;
+}
+
+// Stores all |cookies| in |override_response_headers| deleting previously
+// existing cookie definitions.
+static void StoreResponseCookies(
+ const ParsedResponseCookies& cookies,
+ scoped_refptr<net::HttpResponseHeaders> override_response_headers) {
+ override_response_headers->RemoveHeader("Set-Cookie");
+ for (ParsedResponseCookies::const_iterator i = cookies.begin();
+ i != cookies.end(); ++i) {
+ override_response_headers->AddHeader("Set-Cookie: " + (*i)->ToCookieLine());
+ }
+}
+
+// Modifies |cookie| according to |modification|. Each value that is set in
+// |modification| is applied to |cookie|.
+static bool ApplyResponseCookieModification(ResponseCookie* modification,
+ net::ParsedCookie* cookie) {
+ bool modified = false;
+ if (modification->name.get())
+ modified |= cookie->SetName(*modification->name);
+ if (modification->value.get())
+ modified |= cookie->SetValue(*modification->value);
+ if (modification->expires.get())
+ modified |= cookie->SetExpires(*modification->expires);
+ if (modification->max_age.get())
+ modified |= cookie->SetMaxAge(base::IntToString(*modification->max_age));
+ if (modification->domain.get())
+ modified |= cookie->SetDomain(*modification->domain);
+ if (modification->path.get())
+ modified |= cookie->SetPath(*modification->path);
+ if (modification->secure.get())
+ modified |= cookie->SetIsSecure(*modification->secure);
+ if (modification->http_only.get())
+ modified |= cookie->SetIsHttpOnly(*modification->http_only);
+ return modified;
+}
+
+static bool DoesResponseCookieMatchFilter(net::ParsedCookie* cookie,
+ FilterResponseCookie* filter) {
+ if (!cookie->IsValid()) return false;
+ if (!filter) return true;
+ if (filter->name && cookie->Name() != *filter->name)
+ return false;
+ if (filter->value && cookie->Value() != *filter->value)
+ return false;
+ if (filter->expires) {
+ std::string actual_value =
+ cookie->HasExpires() ? cookie->Expires() : std::string();
+ if (actual_value != *filter->expires)
+ return false;
+ }
+ if (filter->max_age) {
+ std::string actual_value =
+ cookie->HasMaxAge() ? cookie->MaxAge() : std::string();
+ if (actual_value != base::IntToString(*filter->max_age))
+ return false;
+ }
+ if (filter->domain) {
+ std::string actual_value =
+ cookie->HasDomain() ? cookie->Domain() : std::string();
+ if (actual_value != *filter->domain)
+ return false;
+ }
+ if (filter->path) {
+ std::string actual_value =
+ cookie->HasPath() ? cookie->Path() : std::string();
+ if (actual_value != *filter->path)
+ return false;
+ }
+ if (filter->secure && cookie->IsSecure() != *filter->secure)
+ return false;
+ if (filter->http_only && cookie->IsHttpOnly() != *filter->http_only)
+ return false;
+ if (filter->age_upper_bound || filter->age_lower_bound ||
+ (filter->session_cookie && *filter->session_cookie)) {
+ int64_t seconds_to_expiry;
+ bool lifetime_parsed = ParseCookieLifetime(cookie, &seconds_to_expiry);
+ if (filter->age_upper_bound && seconds_to_expiry > *filter->age_upper_bound)
+ return false;
+ if (filter->age_lower_bound && seconds_to_expiry < *filter->age_lower_bound)
+ return false;
+ if (filter->session_cookie && *filter->session_cookie && lifetime_parsed)
+ return false;
+ }
+ return true;
+}
+
+// Applies all CookieModificationType::ADD operations for response cookies of
+// |deltas| to |cookies|. Returns whether any cookie was added.
+static bool MergeAddResponseCookieModifications(
+ const EventResponseDeltas& deltas,
+ ParsedResponseCookies* cookies) {
+ bool modified = false;
+ // We assume here that the deltas are sorted in decreasing extension
+ // precedence (i.e. decreasing extension installation time).
+ EventResponseDeltas::const_reverse_iterator delta;
+ for (delta = deltas.rbegin(); delta != deltas.rend(); ++delta) {
+ const ResponseCookieModifications& modifications =
+ (*delta)->response_cookie_modifications;
+ for (ResponseCookieModifications::const_iterator mod =
+ modifications.begin(); mod != modifications.end(); ++mod) {
+ if ((*mod)->type != ADD || !(*mod)->modification.get())
+ continue;
+ // Cookie names are not unique in response cookies so we always append
+ // and never override.
+ linked_ptr<net::ParsedCookie> cookie(
+ new net::ParsedCookie(std::string()));
+ ApplyResponseCookieModification((*mod)->modification.get(), cookie.get());
+ cookies->push_back(cookie);
+ modified = true;
+ }
+ }
+ return modified;
+}
+
+// Applies all CookieModificationType::EDIT operations for response cookies of
+// |deltas| to |cookies|. Returns whether any cookie was modified.
+static bool MergeEditResponseCookieModifications(
+ const EventResponseDeltas& deltas,
+ ParsedResponseCookies* cookies) {
+ bool modified = false;
+ // We assume here that the deltas are sorted in decreasing extension
+ // precedence (i.e. decreasing extension installation time).
+ EventResponseDeltas::const_reverse_iterator delta;
+ for (delta = deltas.rbegin(); delta != deltas.rend(); ++delta) {
+ const ResponseCookieModifications& modifications =
+ (*delta)->response_cookie_modifications;
+ for (ResponseCookieModifications::const_iterator mod =
+ modifications.begin(); mod != modifications.end(); ++mod) {
+ if ((*mod)->type != EDIT || !(*mod)->modification.get())
+ continue;
+
+ for (ParsedResponseCookies::iterator cookie = cookies->begin();
+ cookie != cookies->end(); ++cookie) {
+ if (DoesResponseCookieMatchFilter(cookie->get(),
+ (*mod)->filter.get())) {
+ modified |= ApplyResponseCookieModification(
+ (*mod)->modification.get(), cookie->get());
+ }
+ }
+ }
+ }
+ return modified;
+}
+
+// Applies all CookieModificationType::REMOVE operations for response cookies of
+// |deltas| to |cookies|. Returns whether any cookie was deleted.
+static bool MergeRemoveResponseCookieModifications(
+ const EventResponseDeltas& deltas,
+ ParsedResponseCookies* cookies) {
+ bool modified = false;
+ // We assume here that the deltas are sorted in decreasing extension
+ // precedence (i.e. decreasing extension installation time).
+ EventResponseDeltas::const_reverse_iterator delta;
+ for (delta = deltas.rbegin(); delta != deltas.rend(); ++delta) {
+ const ResponseCookieModifications& modifications =
+ (*delta)->response_cookie_modifications;
+ for (ResponseCookieModifications::const_iterator mod =
+ modifications.begin(); mod != modifications.end(); ++mod) {
+ if ((*mod)->type != REMOVE)
+ continue;
+
+ ParsedResponseCookies::iterator i = cookies->begin();
+ while (i != cookies->end()) {
+ if (DoesResponseCookieMatchFilter(i->get(),
+ (*mod)->filter.get())) {
+ i = cookies->erase(i);
+ modified = true;
+ } else {
+ ++i;
+ }
+ }
+ }
+ }
+ return modified;
+}
+
+void MergeCookiesInOnHeadersReceivedResponses(
+ const EventResponseDeltas& deltas,
+ const net::HttpResponseHeaders* original_response_headers,
+ scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
+ extensions::WarningSet* conflicting_extensions,
+ const net::BoundNetLog* net_log) {
+ // Skip all work if there are no registered cookie modifications.
+ bool cookie_modifications_exist = false;
+ EventResponseDeltas::const_reverse_iterator delta;
+ for (delta = deltas.rbegin(); delta != deltas.rend(); ++delta) {
+ cookie_modifications_exist |=
+ !(*delta)->response_cookie_modifications.empty();
+ }
+ if (!cookie_modifications_exist)
+ return;
+
+ // Only create a copy if we really want to modify the response headers.
+ if (override_response_headers->get() == NULL) {
+ *override_response_headers = new net::HttpResponseHeaders(
+ original_response_headers->raw_headers());
+ }
+
+ ParsedResponseCookies cookies =
+ GetResponseCookies(*override_response_headers);
+
+ bool modified = false;
+ modified |= MergeAddResponseCookieModifications(deltas, &cookies);
+ modified |= MergeEditResponseCookieModifications(deltas, &cookies);
+ modified |= MergeRemoveResponseCookieModifications(deltas, &cookies);
+
+ // Store new value.
+ if (modified)
+ StoreResponseCookies(cookies, *override_response_headers);
+}
+
+// Converts the key of the (key, value) pair to lower case.
+static ResponseHeader ToLowerCase(const ResponseHeader& header) {
+ return ResponseHeader(base::ToLowerASCII(header.first), header.second);
+}
+
+// Returns the extension ID of the first extension in |deltas| that removes the
+// request header identified by |key|.
+static std::string FindRemoveResponseHeader(
+ const EventResponseDeltas& deltas,
+ const std::string& key) {
+ std::string lower_key = base::ToLowerASCII(key);
+ for (const auto& delta : deltas) {
+ for (const auto& deleted_hdr : delta->deleted_response_headers) {
+ if (base::ToLowerASCII(deleted_hdr.first) == lower_key)
+ return delta->extension_id;
+ }
+ }
+ return std::string();
+}
+
+void MergeOnHeadersReceivedResponses(
+ const EventResponseDeltas& deltas,
+ const net::HttpResponseHeaders* original_response_headers,
+ scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
+ GURL* allowed_unsafe_redirect_url,
+ extensions::WarningSet* conflicting_extensions,
+ const net::BoundNetLog* net_log) {
+ EventResponseDeltas::const_iterator delta;
+
+ // Here we collect which headers we have removed or added so far due to
+ // extensions of higher precedence. Header keys are always stored as
+ // lower case.
+ std::set<ResponseHeader> removed_headers;
+ std::set<ResponseHeader> added_headers;
+
+ // We assume here that the deltas are sorted in decreasing extension
+ // precedence (i.e. decreasing extension installation time).
+ for (delta = deltas.begin(); delta != deltas.end(); ++delta) {
+ if ((*delta)->added_response_headers.empty() &&
+ (*delta)->deleted_response_headers.empty()) {
+ continue;
+ }
+
+ // Only create a copy if we really want to modify the response headers.
+ if (override_response_headers->get() == NULL) {
+ *override_response_headers = new net::HttpResponseHeaders(
+ original_response_headers->raw_headers());
+ }
+
+ // We consider modifications as pairs of (delete, add) operations.
+ // If a header is deleted twice by different extensions we assume that the
+ // intention was to modify it to different values and consider this a
+ // conflict. As deltas is sorted by decreasing extension installation order,
+ // this takes care of precedence.
+ bool extension_conflicts = false;
+ std::string conflicting_header;
+ std::string winning_extension_id;
+ ResponseHeaders::const_iterator i;
+ for (i = (*delta)->deleted_response_headers.begin();
+ i != (*delta)->deleted_response_headers.end(); ++i) {
+ if (removed_headers.find(ToLowerCase(*i)) != removed_headers.end()) {
+ winning_extension_id = FindRemoveResponseHeader(deltas, i->first);
+ conflicting_header = i->first;
+ extension_conflicts = true;
+ break;
+ }
+ }
+
+ // Now execute the modifications if there were no conflicts.
+ if (!extension_conflicts) {
+ // Delete headers
+ {
+ for (i = (*delta)->deleted_response_headers.begin();
+ i != (*delta)->deleted_response_headers.end(); ++i) {
+ (*override_response_headers)->RemoveHeaderLine(i->first, i->second);
+ removed_headers.insert(ToLowerCase(*i));
+ }
+ }
+
+ // Add headers.
+ {
+ for (i = (*delta)->added_response_headers.begin();
+ i != (*delta)->added_response_headers.end(); ++i) {
+ ResponseHeader lowercase_header(ToLowerCase(*i));
+ if (added_headers.find(lowercase_header) != added_headers.end())
+ continue;
+ added_headers.insert(lowercase_header);
+ (*override_response_headers)->AddHeader(i->first + ": " + i->second);
+ }
+ }
+ net_log->AddEvent(
+ net::NetLog::TYPE_CHROME_EXTENSION_MODIFIED_HEADERS,
+ CreateNetLogExtensionIdCallback(delta->get()));
+ } else {
+ conflicting_extensions->insert(
+ extensions::Warning::CreateResponseHeaderConflictWarning(
+ (*delta)->extension_id, winning_extension_id,
+ conflicting_header));
+ net_log->AddEvent(
+ net::NetLog::TYPE_CHROME_EXTENSION_IGNORED_DUE_TO_CONFLICT,
+ CreateNetLogExtensionIdCallback(delta->get()));
+ }
+ }
+
+ MergeCookiesInOnHeadersReceivedResponses(deltas, original_response_headers,
+ override_response_headers, conflicting_extensions, net_log);
+
+ GURL new_url;
+ MergeRedirectUrlOfResponses(
+ deltas, &new_url, conflicting_extensions, net_log);
+ if (new_url.is_valid()) {
+ // Only create a copy if we really want to modify the response headers.
+ if (override_response_headers->get() == NULL) {
+ *override_response_headers = new net::HttpResponseHeaders(
+ original_response_headers->raw_headers());
+ }
+ (*override_response_headers)->ReplaceStatusLine("HTTP/1.1 302 Found");
+ (*override_response_headers)->RemoveHeader("location");
+ (*override_response_headers)->AddHeader("Location: " + new_url.spec());
+ // Explicitly mark the URL as safe for redirection, to prevent the request
+ // from being blocked because of net::ERR_UNSAFE_REDIRECT.
+ *allowed_unsafe_redirect_url = new_url;
+ }
+}
+
+bool MergeOnAuthRequiredResponses(
+ const EventResponseDeltas& deltas,
+ net::AuthCredentials* auth_credentials,
+ extensions::WarningSet* conflicting_extensions,
+ const net::BoundNetLog* net_log) {
+ CHECK(auth_credentials);
+ bool credentials_set = false;
+ std::string winning_extension_id;
+
+ for (EventResponseDeltas::const_iterator delta = deltas.begin();
+ delta != deltas.end();
+ ++delta) {
+ if (!(*delta)->auth_credentials.get())
+ continue;
+ bool different =
+ auth_credentials->username() !=
+ (*delta)->auth_credentials->username() ||
+ auth_credentials->password() != (*delta)->auth_credentials->password();
+ if (credentials_set && different) {
+ conflicting_extensions->insert(
+ extensions::Warning::CreateCredentialsConflictWarning(
+ (*delta)->extension_id, winning_extension_id));
+ net_log->AddEvent(
+ net::NetLog::TYPE_CHROME_EXTENSION_IGNORED_DUE_TO_CONFLICT,
+ CreateNetLogExtensionIdCallback(delta->get()));
+ } else {
+ net_log->AddEvent(
+ net::NetLog::TYPE_CHROME_EXTENSION_PROVIDE_AUTH_CREDENTIALS,
+ CreateNetLogExtensionIdCallback(delta->get()));
+ *auth_credentials = *(*delta)->auth_credentials;
+ credentials_set = true;
+ winning_extension_id = (*delta)->extension_id;
+ }
+ }
+ return credentials_set;
+}
+
+void ClearCacheOnNavigation() {
+ if (content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
+ ClearCacheOnNavigationOnUI();
+ } else {
+ content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
+ base::Bind(&ClearCacheOnNavigationOnUI));
+ }
+}
+
+void NotifyWebRequestAPIUsed(void* browser_context_id,
+ const std::string& extension_id) {
+ DCHECK(!extension_id.empty());
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ content::BrowserContext* browser_context =
+ reinterpret_cast<content::BrowserContext*>(browser_context_id);
+ if (!extensions::ExtensionsBrowserClient::Get()->IsValidContext(
+ browser_context))
+ return;
+
+ extensions::RuntimeData* runtime_data =
+ extensions::ExtensionSystem::Get(browser_context)->runtime_data();
+ if (runtime_data->HasUsedWebRequest(extension_id))
+ return;
+ runtime_data->SetHasUsedWebRequest(extension_id, true);
+
+ for (content::RenderProcessHost::iterator it =
+ content::RenderProcessHost::AllHostsIterator();
+ !it.IsAtEnd(); it.Advance()) {
+ content::RenderProcessHost* host = it.GetCurrentValue();
+ if (host->GetBrowserContext() == browser_context)
+ SendExtensionWebRequestStatusToHost(host);
+ }
+}
+
+void SendExtensionWebRequestStatusToHost(content::RenderProcessHost* host) {
+ content::BrowserContext* browser_context = host->GetBrowserContext();
+ if (!browser_context)
+ return;
+
+ bool webrequest_used = false;
+ const extensions::ExtensionSet& extensions =
+ extensions::ExtensionRegistry::Get(browser_context)->enabled_extensions();
+ extensions::RuntimeData* runtime_data =
+ extensions::ExtensionSystem::Get(browser_context)->runtime_data();
+ for (extensions::ExtensionSet::const_iterator it = extensions.begin();
+ !webrequest_used && it != extensions.end();
+ ++it) {
+ webrequest_used |= runtime_data->HasUsedWebRequest((*it)->id());
+ }
+
+ host->Send(new ExtensionMsg_UsingWebRequestAPI(webrequest_used));
+}
+
+// Converts the |name|, |value| pair of a http header to a HttpHeaders
+// dictionary. Ownership is passed to the caller.
+base::DictionaryValue* CreateHeaderDictionary(
+ const std::string& name, const std::string& value) {
+ base::DictionaryValue* header = new base::DictionaryValue();
+ header->SetString(keys::kHeaderNameKey, name);
+ if (base::IsStringUTF8(value)) {
+ header->SetString(keys::kHeaderValueKey, value);
+ } else {
+ header->Set(keys::kHeaderBinaryValueKey,
+ StringToCharList(value));
+ }
+ return header;
+}
+
+bool IsRelevantResourceType(ResourceType type) {
+ ResourceType* iter =
+ std::find(kResourceTypeValues,
+ kResourceTypeValues + kResourceTypeValuesLength,
+ type);
+ return iter != (kResourceTypeValues + kResourceTypeValuesLength);
+}
+
+const char* ResourceTypeToString(ResourceType type) {
+ ResourceType* iter =
+ std::find(kResourceTypeValues,
+ kResourceTypeValues + kResourceTypeValuesLength,
+ type);
+ if (iter == (kResourceTypeValues + kResourceTypeValuesLength))
+ return "other";
+
+ return kResourceTypeStrings[iter - kResourceTypeValues];
+}
+
+bool ParseResourceType(const std::string& type_str,
+ std::vector<ResourceType>* types) {
+ bool found = false;
+ for (size_t i = 0; i < kResourceTypeStringsLength; ++i) {
+ if (type_str == kResourceTypeStrings[i]) {
+ found = true;
+ types->push_back(kResourceTypeValues[i]);
+ }
+ }
+ return found;
+}
+
+} // namespace extension_web_request_api_helpers
diff --git a/chromium/extensions/browser/api/web_request/web_request_api_helpers.h b/chromium/extensions/browser/api/web_request/web_request_api_helpers.h
new file mode 100644
index 00000000000..a41c52ff484
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/web_request_api_helpers.h
@@ -0,0 +1,360 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Helper classes and functions used for the WebRequest API.
+
+#ifndef EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_API_HELPERS_H_
+#define EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_API_HELPERS_H_
+
+#include <list>
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "content/public/common/resource_type.h"
+#include "extensions/browser/warning_set.h"
+#include "net/base/auth.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "url/gurl.h"
+
+namespace base {
+class ListValue;
+class Value;
+}
+
+namespace content {
+class RenderProcessHost;
+}
+
+namespace extensions {
+class Extension;
+}
+
+namespace net {
+class BoundNetLog;
+class URLRequest;
+}
+
+namespace extension_web_request_api_helpers {
+
+typedef std::pair<std::string, std::string> ResponseHeader;
+typedef std::vector<ResponseHeader> ResponseHeaders;
+
+// Internal representation of the extraInfoSpec parameter on webRequest
+// events, used to specify extra information to be included with network
+// events.
+struct ExtraInfoSpec {
+ enum Flags {
+ REQUEST_HEADERS = 1 << 0,
+ RESPONSE_HEADERS = 1 << 1,
+ BLOCKING = 1 << 2,
+ ASYNC_BLOCKING = 1 << 3,
+ REQUEST_BODY = 1 << 4,
+ };
+
+ static bool InitFromValue(const base::ListValue& value, int* extra_info_spec);
+};
+
+// Data container for RequestCookies as defined in the declarative WebRequest
+// API definition.
+struct RequestCookie {
+ RequestCookie();
+ ~RequestCookie();
+ scoped_ptr<std::string> name;
+ scoped_ptr<std::string> value;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RequestCookie);
+};
+
+bool NullableEquals(const RequestCookie* a, const RequestCookie* b);
+
+// Data container for ResponseCookies as defined in the declarative WebRequest
+// API definition.
+struct ResponseCookie {
+ ResponseCookie();
+ ~ResponseCookie();
+ scoped_ptr<std::string> name;
+ scoped_ptr<std::string> value;
+ scoped_ptr<std::string> expires;
+ scoped_ptr<int> max_age;
+ scoped_ptr<std::string> domain;
+ scoped_ptr<std::string> path;
+ scoped_ptr<bool> secure;
+ scoped_ptr<bool> http_only;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResponseCookie);
+};
+
+bool NullableEquals(const ResponseCookie* a, const ResponseCookie* b);
+
+// Data container for FilterResponseCookies as defined in the declarative
+// WebRequest API definition.
+struct FilterResponseCookie : ResponseCookie {
+ FilterResponseCookie();
+ ~FilterResponseCookie();
+ scoped_ptr<int> age_lower_bound;
+ scoped_ptr<int> age_upper_bound;
+ scoped_ptr<bool> session_cookie;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FilterResponseCookie);
+};
+
+bool NullableEquals(const FilterResponseCookie* a,
+ const FilterResponseCookie* b);
+
+enum CookieModificationType {
+ ADD,
+ EDIT,
+ REMOVE,
+};
+
+struct RequestCookieModification {
+ RequestCookieModification();
+ ~RequestCookieModification();
+ CookieModificationType type;
+ // Used for EDIT and REMOVE. NULL for ADD.
+ scoped_ptr<RequestCookie> filter;
+ // Used for ADD and EDIT. NULL for REMOVE.
+ scoped_ptr<RequestCookie> modification;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RequestCookieModification);
+};
+
+bool NullableEquals(const RequestCookieModification* a,
+ const RequestCookieModification* b);
+
+struct ResponseCookieModification {
+ ResponseCookieModification();
+ ~ResponseCookieModification();
+ CookieModificationType type;
+ // Used for EDIT and REMOVE.
+ scoped_ptr<FilterResponseCookie> filter;
+ // Used for ADD and EDIT.
+ scoped_ptr<ResponseCookie> modification;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResponseCookieModification);
+};
+
+bool NullableEquals(const ResponseCookieModification* a,
+ const ResponseCookieModification* b);
+
+typedef std::vector<linked_ptr<RequestCookieModification> >
+ RequestCookieModifications;
+typedef std::vector<linked_ptr<ResponseCookieModification> >
+ ResponseCookieModifications;
+
+// Contains the modification an extension wants to perform on an event.
+struct EventResponseDelta {
+ // ID of the extension that sent this response.
+ std::string extension_id;
+
+ // The time that the extension was installed. Used for deciding order of
+ // precedence in case multiple extensions respond with conflicting
+ // decisions.
+ base::Time extension_install_time;
+
+ // Response values. These are mutually exclusive.
+ bool cancel;
+ GURL new_url;
+
+ // Newly introduced or overridden request headers.
+ net::HttpRequestHeaders modified_request_headers;
+
+ // Keys of request headers to be deleted.
+ std::vector<std::string> deleted_request_headers;
+
+ // Headers that were added to the response. A modification of a header
+ // corresponds to a deletion and subsequent addition of the new header.
+ ResponseHeaders added_response_headers;
+
+ // Headers that were deleted from the response.
+ ResponseHeaders deleted_response_headers;
+
+ // Authentication Credentials to use.
+ scoped_ptr<net::AuthCredentials> auth_credentials;
+
+ // Modifications to cookies in request headers.
+ RequestCookieModifications request_cookie_modifications;
+
+ // Modifications to cookies in response headers.
+ ResponseCookieModifications response_cookie_modifications;
+
+ // Messages that shall be sent to the background/event/... pages of the
+ // extension.
+ std::set<std::string> messages_to_extension;
+
+ EventResponseDelta(const std::string& extension_id,
+ const base::Time& extension_install_time);
+ ~EventResponseDelta();
+
+ DISALLOW_COPY_AND_ASSIGN(EventResponseDelta);
+};
+
+typedef std::list<linked_ptr<EventResponseDelta> > EventResponseDeltas;
+
+// Comparison operator that returns true if the extension that caused
+// |a| was installed after the extension that caused |b|.
+bool InDecreasingExtensionInstallationTimeOrder(
+ const linked_ptr<EventResponseDelta>& a,
+ const linked_ptr<EventResponseDelta>& b);
+
+// Converts a string to a list of integers, each in 0..255. Ownership
+// of the created list is passed to the caller.
+base::ListValue* StringToCharList(const std::string& s);
+
+// Converts a list of integer values between 0 and 255 into a string |*out|.
+// Returns true if the conversion was successful.
+bool CharListToString(const base::ListValue* list, std::string* out);
+
+// The following functions calculate and return the modifications to requests
+// commanded by extension handlers. All functions take the id of the extension
+// that commanded a modification, the installation time of this extension (used
+// for defining a precedence in conflicting modifications) and whether the
+// extension requested to |cancel| the request. Other parameters depend on a
+// the signal handler. Ownership of the returned object is passed to the caller.
+
+EventResponseDelta* CalculateOnBeforeRequestDelta(
+ const std::string& extension_id,
+ const base::Time& extension_install_time,
+ bool cancel,
+ const GURL& new_url);
+EventResponseDelta* CalculateOnBeforeSendHeadersDelta(
+ const std::string& extension_id,
+ const base::Time& extension_install_time,
+ bool cancel,
+ net::HttpRequestHeaders* old_headers,
+ net::HttpRequestHeaders* new_headers);
+EventResponseDelta* CalculateOnHeadersReceivedDelta(
+ const std::string& extension_id,
+ const base::Time& extension_install_time,
+ bool cancel,
+ const GURL& new_url,
+ const net::HttpResponseHeaders* old_response_headers,
+ ResponseHeaders* new_response_headers);
+// Destructively moves the auth credentials from |auth_credentials| to the
+// returned EventResponseDelta.
+EventResponseDelta* CalculateOnAuthRequiredDelta(
+ const std::string& extension_id,
+ const base::Time& extension_install_time,
+ bool cancel,
+ scoped_ptr<net::AuthCredentials>* auth_credentials);
+
+// These functions merge the responses (the |deltas|) of request handlers.
+// The |deltas| need to be sorted in decreasing order of precedence of
+// extensions. In case extensions had |deltas| that could not be honored, their
+// IDs are reported in |conflicting_extensions|. NetLog events that shall be
+// reported will be stored in |event_log_entries|.
+
+// Stores in |canceled| whether any extension wanted to cancel the request.
+void MergeCancelOfResponses(
+ const EventResponseDeltas& deltas,
+ bool* canceled,
+ const net::BoundNetLog* net_log);
+// Stores in |*new_url| the redirect request of the extension with highest
+// precedence. Extensions that did not command to redirect the request are
+// ignored in this logic.
+void MergeRedirectUrlOfResponses(
+ const EventResponseDeltas& deltas,
+ GURL* new_url,
+ extensions::WarningSet* conflicting_extensions,
+ const net::BoundNetLog* net_log);
+// Stores in |*new_url| the redirect request of the extension with highest
+// precedence. Extensions that did not command to redirect the request are
+// ignored in this logic.
+void MergeOnBeforeRequestResponses(
+ const EventResponseDeltas& deltas,
+ GURL* new_url,
+ extensions::WarningSet* conflicting_extensions,
+ const net::BoundNetLog* net_log);
+// Modifies the "Cookie" header in |request_headers| according to
+// |deltas.request_cookie_modifications|. Conflicts are currently ignored
+// silently.
+void MergeCookiesInOnBeforeSendHeadersResponses(
+ const EventResponseDeltas& deltas,
+ net::HttpRequestHeaders* request_headers,
+ extensions::WarningSet* conflicting_extensions,
+ const net::BoundNetLog* net_log);
+// Modifies the headers in |request_headers| according to |deltas|. Conflicts
+// are tried to be resolved.
+void MergeOnBeforeSendHeadersResponses(
+ const EventResponseDeltas& deltas,
+ net::HttpRequestHeaders* request_headers,
+ extensions::WarningSet* conflicting_extensions,
+ const net::BoundNetLog* net_log);
+// Modifies the "Set-Cookie" headers in |override_response_headers| according to
+// |deltas.response_cookie_modifications|. If |override_response_headers| is
+// NULL, a copy of |original_response_headers| is created. Conflicts are
+// currently ignored silently.
+void MergeCookiesInOnHeadersReceivedResponses(
+ const EventResponseDeltas& deltas,
+ const net::HttpResponseHeaders* original_response_headers,
+ scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
+ extensions::WarningSet* conflicting_extensions,
+ const net::BoundNetLog* net_log);
+// Stores a copy of |original_response_header| into |override_response_headers|
+// that is modified according to |deltas|. If |deltas| does not instruct to
+// modify the response headers, |override_response_headers| remains empty.
+// Extension-initiated redirects are written to |override_response_headers|
+// (to request redirection) and |*allowed_unsafe_redirect_url| (to make sure
+// that the request is not cancelled with net::ERR_UNSAFE_REDIRECT).
+void MergeOnHeadersReceivedResponses(
+ const EventResponseDeltas& deltas,
+ const net::HttpResponseHeaders* original_response_headers,
+ scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
+ GURL* allowed_unsafe_redirect_url,
+ extensions::WarningSet* conflicting_extensions,
+ const net::BoundNetLog* net_log);
+// Merge the responses of blocked onAuthRequired handlers. The first
+// registered listener that supplies authentication credentials in a response,
+// if any, will have its authentication credentials used. |request| must be
+// non-NULL, and contain |deltas| that are sorted in decreasing order of
+// precedence.
+// Returns whether authentication credentials are set.
+bool MergeOnAuthRequiredResponses(
+ const EventResponseDeltas& deltas,
+ net::AuthCredentials* auth_credentials,
+ extensions::WarningSet* conflicting_extensions,
+ const net::BoundNetLog* net_log);
+
+// Triggers clearing each renderer's in-memory cache the next time it navigates.
+void ClearCacheOnNavigation();
+
+// Tells renderer processes that the web request or declarative web request
+// API has been used by the extension with the given |extension_id| in the
+// given |browser_context_id| to collect UMA statistics on Page Load Times.
+// Needs to be called on the UI thread.
+void NotifyWebRequestAPIUsed(void* browser_context_id,
+ const std::string& extension_id);
+
+// Send updates to |host| with information about what webRequest-related
+// extensions are installed.
+void SendExtensionWebRequestStatusToHost(content::RenderProcessHost* host);
+
+// Converts the |name|, |value| pair of a http header to a HttpHeaders
+// dictionary. Ownership is passed to the caller.
+base::DictionaryValue* CreateHeaderDictionary(
+ const std::string& name, const std::string& value);
+
+// Returns whether |type| is a ResourceType that is handled by the web request
+// API.
+bool IsRelevantResourceType(content::ResourceType type);
+
+// Returns a string representation of |type| or |other| if |type| is not handled
+// by the web request API.
+const char* ResourceTypeToString(content::ResourceType type);
+
+// Stores a |content::ResourceType| representation in |types| if |type_str| is
+// a resource type handled by the web request API. Returns true in case of
+// success.
+bool ParseResourceType(const std::string& type_str,
+ std::vector<content::ResourceType>* types);
+
+} // namespace extension_web_request_api_helpers
+
+#endif // EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_API_HELPERS_H_
diff --git a/chromium/extensions/browser/api/web_request/web_request_event_details.cc b/chromium/extensions/browser/api/web_request/web_request_event_details.cc
new file mode 100644
index 00000000000..04fa9fbfeaa
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/web_request_event_details.cc
@@ -0,0 +1,197 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/web_request/web_request_event_details.h"
+
+#include "base/callback.h"
+#include "base/strings/string_number_conversions.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/resource_request_info.h"
+#include "content/public/common/child_process_host.h"
+#include "extensions/browser/api/web_request/upload_data_presenter.h"
+#include "extensions/browser/api/web_request/web_request_api_constants.h"
+#include "extensions/browser/api/web_request/web_request_api_helpers.h"
+#include "ipc/ipc_message.h"
+#include "net/base/auth.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/url_request.h"
+
+using extension_web_request_api_helpers::ExtraInfoSpec;
+
+namespace helpers = extension_web_request_api_helpers;
+namespace keys = extension_web_request_api_constants;
+
+namespace extensions {
+
+WebRequestEventDetails::WebRequestEventDetails(const net::URLRequest* request,
+ int extra_info_spec)
+ : extra_info_spec_(extra_info_spec),
+ render_process_id_(content::ChildProcessHost::kInvalidUniqueID),
+ render_frame_id_(MSG_ROUTING_NONE) {
+ content::ResourceType resource_type = content::RESOURCE_TYPE_LAST_TYPE;
+ const content::ResourceRequestInfo* info =
+ content::ResourceRequestInfo::ForRequest(request);
+ if (info) {
+ render_process_id_ = info->GetChildID();
+ render_frame_id_ = info->GetRenderFrameID();
+ resource_type = info->GetResourceType();
+ }
+
+ dict_.SetString(keys::kMethodKey, request->method());
+ dict_.SetString(keys::kRequestIdKey,
+ base::Uint64ToString(request->identifier()));
+ dict_.SetDouble(keys::kTimeStampKey, base::Time::Now().ToDoubleT() * 1000);
+ dict_.SetString(keys::kTypeKey, helpers::ResourceTypeToString(resource_type));
+ dict_.SetString(keys::kUrlKey, request->url().spec());
+}
+
+WebRequestEventDetails::~WebRequestEventDetails() {}
+
+void WebRequestEventDetails::SetRequestBody(const net::URLRequest* request) {
+ if (!(extra_info_spec_ & ExtraInfoSpec::REQUEST_BODY))
+ return;
+
+ const net::UploadDataStream* upload_data = request->get_upload();
+ if (!upload_data ||
+ (request->method() != "POST" && request->method() != "PUT"))
+ return;
+
+ base::DictionaryValue* request_body = new base::DictionaryValue();
+ request_body_.reset(request_body);
+
+ // Get the data presenters, ordered by how specific they are.
+ ParsedDataPresenter parsed_data_presenter(*request);
+ RawDataPresenter raw_data_presenter;
+ UploadDataPresenter* const presenters[] = {
+ &parsed_data_presenter, // 1: any parseable forms? (Specific to forms.)
+ &raw_data_presenter // 2: any data at all? (Non-specific.)
+ };
+ // Keys for the results of the corresponding presenters.
+ static const char* const kKeys[] = {keys::kRequestBodyFormDataKey,
+ keys::kRequestBodyRawKey};
+
+ const std::vector<scoped_ptr<net::UploadElementReader>>* readers =
+ upload_data->GetElementReaders();
+ bool some_succeeded = false;
+ if (readers) {
+ for (size_t i = 0; i < arraysize(presenters); ++i) {
+ for (const auto& reader : *readers)
+ presenters[i]->FeedNext(*reader);
+ if (presenters[i]->Succeeded()) {
+ request_body->Set(kKeys[i], presenters[i]->Result());
+ some_succeeded = true;
+ break;
+ }
+ }
+ }
+ if (!some_succeeded)
+ request_body->SetString(keys::kRequestBodyErrorKey, "Unknown error.");
+}
+
+void WebRequestEventDetails::SetRequestHeaders(
+ const net::HttpRequestHeaders& request_headers) {
+ if (!(extra_info_spec_ & ExtraInfoSpec::REQUEST_HEADERS))
+ return;
+
+ base::ListValue* headers = new base::ListValue();
+ for (net::HttpRequestHeaders::Iterator it(request_headers); it.GetNext();)
+ headers->Append(helpers::CreateHeaderDictionary(it.name(), it.value()));
+ request_headers_.reset(headers);
+}
+
+void WebRequestEventDetails::SetAuthInfo(
+ const net::AuthChallengeInfo& auth_info) {
+ dict_.SetBoolean(keys::kIsProxyKey, auth_info.is_proxy);
+ if (!auth_info.scheme.empty())
+ dict_.SetString(keys::kSchemeKey, auth_info.scheme);
+ if (!auth_info.realm.empty())
+ dict_.SetString(keys::kRealmKey, auth_info.realm);
+ base::DictionaryValue* challenger = new base::DictionaryValue();
+ challenger->SetString(keys::kHostKey, auth_info.challenger.host());
+ challenger->SetInteger(keys::kPortKey, auth_info.challenger.port());
+ dict_.Set(keys::kChallengerKey, challenger);
+}
+
+void WebRequestEventDetails::SetResponseHeaders(
+ const net::URLRequest* request,
+ const net::HttpResponseHeaders* response_headers) {
+ if (!response_headers) {
+ // Not all URLRequestJobs specify response headers. E.g. URLRequestFTPJob,
+ // URLRequestFileJob and some redirects.
+ dict_.SetInteger(keys::kStatusCodeKey, request->GetResponseCode());
+ dict_.SetString(keys::kStatusLineKey, "");
+ } else {
+ dict_.SetInteger(keys::kStatusCodeKey, response_headers->response_code());
+ dict_.SetString(keys::kStatusLineKey, response_headers->GetStatusLine());
+ }
+
+ if (extra_info_spec_ & ExtraInfoSpec::RESPONSE_HEADERS) {
+ base::ListValue* headers = new base::ListValue();
+ if (response_headers) {
+ size_t iter = 0;
+ std::string name;
+ std::string value;
+ while (response_headers->EnumerateHeaderLines(&iter, &name, &value))
+ headers->Append(helpers::CreateHeaderDictionary(name, value));
+ }
+ response_headers_.reset(headers);
+ }
+}
+
+void WebRequestEventDetails::SetResponseSource(const net::URLRequest* request) {
+ dict_.SetBoolean(keys::kFromCache, request->was_cached());
+ const std::string response_ip = request->GetSocketAddress().host();
+ if (!response_ip.empty())
+ dict_.SetString(keys::kIpKey, response_ip);
+}
+
+void WebRequestEventDetails::DetermineFrameIdOnUI() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ content::RenderFrameHost* rfh =
+ content::RenderFrameHost::FromID(render_process_id_, render_frame_id_);
+ dict_.SetInteger(keys::kFrameIdKey, ExtensionApiFrameIdMap::GetFrameId(rfh));
+ dict_.SetInteger(keys::kParentFrameIdKey,
+ ExtensionApiFrameIdMap::GetParentFrameId(rfh));
+}
+
+void WebRequestEventDetails::DetermineFrameIdOnIO(
+ const DeterminedFrameIdCallback& callback) {
+ scoped_ptr<WebRequestEventDetails> self(this);
+ ExtensionApiFrameIdMap::Get()->GetFrameDataOnIO(
+ render_process_id_, render_frame_id_,
+ base::Bind(&WebRequestEventDetails::OnDeterminedFrameId,
+ base::Unretained(this), base::Passed(&self), callback));
+}
+
+scoped_ptr<base::DictionaryValue> WebRequestEventDetails::GetFilteredDict(
+ int extra_info_spec) const {
+ scoped_ptr<base::DictionaryValue> result = dict_.CreateDeepCopy();
+ if ((extra_info_spec & ExtraInfoSpec::REQUEST_BODY) && request_body_)
+ result->Set(keys::kRequestBodyKey, request_body_->CreateDeepCopy());
+ if ((extra_info_spec & ExtraInfoSpec::REQUEST_HEADERS) && request_headers_)
+ result->Set(keys::kRequestHeadersKey, request_headers_->CreateDeepCopy());
+ if ((extra_info_spec & ExtraInfoSpec::RESPONSE_HEADERS) && response_headers_)
+ result->Set(keys::kResponseHeadersKey, response_headers_->CreateDeepCopy());
+ return result;
+}
+
+scoped_ptr<base::DictionaryValue> WebRequestEventDetails::GetAndClearDict() {
+ scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue);
+ dict_.Swap(result.get());
+ return result;
+}
+
+void WebRequestEventDetails::OnDeterminedFrameId(
+ scoped_ptr<WebRequestEventDetails> self,
+ const DeterminedFrameIdCallback& callback,
+ const ExtensionApiFrameIdMap::FrameData& frame_data) {
+ dict_.SetInteger(keys::kFrameIdKey, frame_data.frame_id);
+ dict_.SetInteger(keys::kParentFrameIdKey, frame_data.parent_frame_id);
+ callback.Run(std::move(self));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/web_request/web_request_event_details.h b/chromium/extensions/browser/api/web_request/web_request_event_details.h
new file mode 100644
index 00000000000..7bb36448f0e
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/web_request_event_details.h
@@ -0,0 +1,144 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_EVENT_DETAILS_H_
+#define EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_EVENT_DETAILS_H_
+
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "extensions/browser/extension_api_frame_id_map.h"
+
+namespace net {
+class AuthChallengeInfo;
+class HttpRequestHeaders;
+class HttpResponseHeaders;
+class URLRequest;
+}
+
+namespace extensions {
+
+// This helper class is used to construct the details for a webRequest event
+// dictionary. Some keys are present on every event, others are only relevant
+// for a few events. And some keys must only be added to the event details if
+// requested at the registration of the event listener (in ExtraInfoSpec).
+// This class provides setters that are aware of these rules.
+//
+// Not all keys are managed by this class. Keys that do not require a special
+// treatment can be set using the generic SetBoolean / SetInteger / SetString
+// methods (e.g. to set "error", "message", "redirectUrl", "stage" or "tabId").
+//
+// This class should be constructed on the IO thread. It can safely be used on
+// other threads, as long as there is no concurrent access.
+class WebRequestEventDetails {
+ public:
+ using DeterminedFrameIdCallback =
+ base::Callback<void(scoped_ptr<WebRequestEventDetails>)>;
+
+ // Create a WebRequestEventDetails with the following keys:
+ // - method
+ // - requestId
+ // - tabId
+ // - timeStamp
+ // - type
+ // - url
+ WebRequestEventDetails(const net::URLRequest* request, int extra_info_spec);
+ ~WebRequestEventDetails();
+
+ // Sets the following key:
+ // - requestBody (on demand)
+ void SetRequestBody(const net::URLRequest* request);
+
+ // Sets the following key:
+ // - requestHeaders (on demand)
+ void SetRequestHeaders(const net::HttpRequestHeaders& request_headers);
+
+ // Sets the following keys:
+ // - challenger
+ // - isProxy
+ // - realm
+ // - scheme
+ void SetAuthInfo(const net::AuthChallengeInfo& auth_info);
+
+ // Sets the following keys:
+ // - responseHeaders (on demand)
+ // - statusCode
+ // - statusLine
+ void SetResponseHeaders(const net::URLRequest* request,
+ const net::HttpResponseHeaders* response_headers);
+
+ // Sets the following key:
+ // - fromCache
+ // - ip
+ void SetResponseSource(const net::URLRequest* request);
+
+ void SetBoolean(const std::string& key, bool value) {
+ dict_.SetBoolean(key, value);
+ }
+
+ void SetInteger(const std::string& key, int value) {
+ dict_.SetInteger(key, value);
+ }
+
+ void SetString(const std::string& key, const std::string& value) {
+ dict_.SetString(key, value);
+ }
+
+ // Sets the following keys using information from constructor.
+ // - frameId
+ // - parentFrameId
+ // This must be called from the UI thread.
+ void DetermineFrameIdOnUI();
+
+ // Sets the following keys using information from constructor.
+ // - frameId
+ // - parentFrameId
+ //
+ // This method is more expensive than DetermineFrameIdOnUI because it may
+ // involve thread hops, so prefer using DetermineFrameIdOnUI() when possible.
+ // The callback is called as soon as these IDs are determined, which can be
+ // synchronous or asynchronous.
+ //
+ // The caller must not use or delete this WebRequestEventDetails instance
+ // after calling this method. Ownership of this instance is transferred to
+ // |callback|.
+ void DetermineFrameIdOnIO(const DeterminedFrameIdCallback& callback);
+
+ // Create an event dictionary that contains all required keys, and also the
+ // extra keys as specified by the |extra_info_spec| filter.
+ // This can be called from any thread.
+ scoped_ptr<base::DictionaryValue> GetFilteredDict(int extra_info_spec) const;
+
+ // Get the internal dictionary, unfiltered. After this call, the internal
+ // dictionary is empty.
+ scoped_ptr<base::DictionaryValue> GetAndClearDict();
+
+ private:
+ void OnDeterminedFrameId(scoped_ptr<WebRequestEventDetails> self,
+ const DeterminedFrameIdCallback& callback,
+ const ExtensionApiFrameIdMap::FrameData& frame_data);
+
+ // The details that are always included in a webRequest event object.
+ base::DictionaryValue dict_;
+
+ // Extra event details: Only included when |extra_info_spec_| matches.
+ scoped_ptr<base::DictionaryValue> request_body_;
+ scoped_ptr<base::ListValue> request_headers_;
+ scoped_ptr<base::ListValue> response_headers_;
+
+ int extra_info_spec_;
+
+ // Used to determine the frameId and parentFrameId.
+ int render_process_id_;
+ int render_frame_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRequestEventDetails);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_EVENT_DETAILS_H_
diff --git a/chromium/extensions/browser/api/web_request/web_request_event_router_delegate.cc b/chromium/extensions/browser/api/web_request/web_request_event_router_delegate.cc
new file mode 100644
index 00000000000..646703325b2
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/web_request_event_router_delegate.cc
@@ -0,0 +1,20 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/web_request/web_request_event_router_delegate.h"
+
+#include "extensions/browser/api/web_request/web_request_api_constants.h"
+#include "extensions/browser/api/web_request/web_request_event_details.h"
+
+namespace keys = extension_web_request_api_constants;
+
+namespace extensions {
+
+WebRequestEventRouterDelegate::WebRequestEventRouterDelegate() {
+}
+
+WebRequestEventRouterDelegate::~WebRequestEventRouterDelegate() {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/web_request/web_request_event_router_delegate.h b/chromium/extensions/browser/api/web_request/web_request_event_router_delegate.h
new file mode 100644
index 00000000000..5ccf1adb715
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/web_request_event_router_delegate.h
@@ -0,0 +1,58 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_EVENT_ROUTER_DELEGATE_H_
+#define EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_EVENT_ROUTER_DELEGATE_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+
+class GURL;
+
+namespace base {
+class DictionaryValue;
+} // namespace base
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+namespace net {
+class URLRequest;
+} // namespace net
+
+namespace extensions {
+
+class WebRequestEventDetails;
+
+// A delegate class of WebRequestApi that are not a part of chrome.
+class WebRequestEventRouterDelegate {
+ public:
+ WebRequestEventRouterDelegate();
+ virtual ~WebRequestEventRouterDelegate();
+
+ // Logs an extension action.
+ virtual void LogExtensionActivity(content::BrowserContext* browser_context,
+ bool is_incognito,
+ const std::string& extension_id,
+ const GURL& url,
+ const std::string& api_call,
+ scoped_ptr<base::DictionaryValue> details) {
+ }
+
+ // Notifies that a webRequest event that normally would be forwarded to a
+ // listener was instead blocked because of withheld permissions.
+ virtual void NotifyWebRequestWithheld(int render_process_id,
+ int render_frame_id,
+ const std::string& extension_id) {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebRequestEventRouterDelegate);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_EVENT_ROUTER_DELEGATE_H_
diff --git a/chromium/extensions/browser/api/web_request/web_request_permissions.cc b/chromium/extensions/browser/api/web_request/web_request_permissions.cc
new file mode 100644
index 00000000000..f213890f134
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/web_request_permissions.cc
@@ -0,0 +1,152 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/web_request/web_request_permissions.h"
+
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "content/public/browser/resource_request_info.h"
+#include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
+#include "extensions/browser/info_map.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_urls.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "net/url_request/url_request.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+using content::ResourceRequestInfo;
+using extensions::PermissionsData;
+
+namespace {
+
+// Returns true if the URL is sensitive and requests to this URL must not be
+// modified/canceled by extensions, e.g. because it is targeted to the webstore
+// to check for updates, extension blacklisting, etc.
+bool IsSensitiveURL(const GURL& url) {
+ // TODO(battre) Merge this, CanExtensionAccessURL and
+ // PermissionsData::CanAccessPage into one function.
+ bool sensitive_chrome_url = false;
+ const std::string host = url.host();
+ const char kGoogleCom[] = ".google.com";
+ const char kClient[] = "clients";
+ if (base::EndsWith(host, kGoogleCom, base::CompareCase::SENSITIVE)) {
+ // Check for "clients[0-9]*.google.com" hosts.
+ // This protects requests to several internal services such as sync,
+ // extension update pings, captive portal detection, fraudulent certificate
+ // reporting, autofill and others.
+ if (base::StartsWith(host, kClient, base::CompareCase::SENSITIVE)) {
+ bool match = true;
+ for (std::string::const_iterator i = host.begin() + strlen(kClient),
+ end = host.end() - strlen(kGoogleCom); i != end; ++i) {
+ if (!isdigit(*i)) {
+ match = false;
+ break;
+ }
+ }
+ sensitive_chrome_url = sensitive_chrome_url || match;
+ }
+ // This protects requests to safe browsing, link doctor, and possibly
+ // others.
+ sensitive_chrome_url =
+ sensitive_chrome_url ||
+ base::EndsWith(url.host(), ".clients.google.com",
+ base::CompareCase::SENSITIVE) ||
+ url.host() == "sb-ssl.google.com" ||
+ (url.host() == "chrome.google.com" &&
+ base::StartsWith(url.path(), "/webstore",
+ base::CompareCase::SENSITIVE));
+ }
+ GURL::Replacements replacements;
+ replacements.ClearQuery();
+ replacements.ClearRef();
+ GURL url_without_query = url.ReplaceComponents(replacements);
+ return sensitive_chrome_url ||
+ extension_urls::IsWebstoreUpdateUrl(url_without_query) ||
+ extension_urls::IsBlacklistUpdateUrl(url);
+}
+
+// Returns true if the scheme is one we want to allow extensions to have access
+// to. Extensions still need specific permissions for a given URL, which is
+// covered by CanExtensionAccessURL.
+bool HasWebRequestScheme(const GURL& url) {
+ return (url.SchemeIs(url::kAboutScheme) || url.SchemeIs(url::kFileScheme) ||
+ url.SchemeIs(url::kFileSystemScheme) ||
+ url.SchemeIs(url::kFtpScheme) || url.SchemeIs(url::kHttpScheme) ||
+ url.SchemeIs(url::kHttpsScheme) ||
+ url.SchemeIs(extensions::kExtensionScheme));
+}
+
+} // namespace
+
+// static
+bool WebRequestPermissions::HideRequest(
+ const extensions::InfoMap* extension_info_map,
+ const net::URLRequest* request) {
+ // Hide requests from the Chrome WebStore App or signin process.
+ const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request);
+ if (info) {
+ int process_id = info->GetChildID();
+ // Never hide requests from guest processes.
+ if (extensions::WebViewRendererState::GetInstance()->IsGuest(process_id))
+ return false;
+
+ if (extension_info_map &&
+ extension_info_map->process_map().Contains(extensions::kWebStoreAppId,
+ process_id)) {
+ return true;
+ }
+ }
+
+ const GURL& url = request->url();
+ return IsSensitiveURL(url) || !HasWebRequestScheme(url);
+}
+
+// static
+PermissionsData::AccessType WebRequestPermissions::CanExtensionAccessURL(
+ const extensions::InfoMap* extension_info_map,
+ const std::string& extension_id,
+ const GURL& url,
+ int tab_id,
+ bool crosses_incognito,
+ HostPermissionsCheck host_permissions_check) {
+ // extension_info_map can be NULL in testing.
+ if (!extension_info_map)
+ return PermissionsData::ACCESS_ALLOWED;
+
+ const extensions::Extension* extension =
+ extension_info_map->extensions().GetByID(extension_id);
+ if (!extension)
+ return PermissionsData::ACCESS_DENIED;
+
+ // Check if this event crosses incognito boundaries when it shouldn't.
+ if (crosses_incognito && !extension_info_map->CanCrossIncognito(extension))
+ return PermissionsData::ACCESS_DENIED;
+
+ PermissionsData::AccessType access = PermissionsData::ACCESS_DENIED;
+ switch (host_permissions_check) {
+ case DO_NOT_CHECK_HOST:
+ access = PermissionsData::ACCESS_ALLOWED;
+ break;
+ case REQUIRE_HOST_PERMISSION:
+ // about: URLs are not covered in host permissions, but are allowed
+ // anyway.
+ if (url.SchemeIs(url::kAboutScheme) ||
+ url::IsSameOriginWith(url, extension->url())) {
+ access = PermissionsData::ACCESS_ALLOWED;
+ break;
+ }
+ access = extension->permissions_data()->GetPageAccess(extension, url,
+ tab_id, nullptr);
+ break;
+ case REQUIRE_ALL_URLS:
+ if (extension->permissions_data()->HasEffectiveAccessToAllHosts())
+ access = PermissionsData::ACCESS_ALLOWED;
+ // else ACCESS_DENIED
+ break;
+ }
+
+ return access;
+}
diff --git a/chromium/extensions/browser/api/web_request/web_request_permissions.h b/chromium/extensions/browser/api/web_request/web_request_permissions.h
new file mode 100644
index 00000000000..f24b019eecc
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/web_request_permissions.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_PERMISSIONS_H_
+#define EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_PERMISSIONS_H_
+
+#include <map>
+#include <string>
+
+#include "base/macros.h"
+#include "extensions/common/permissions/permissions_data.h"
+
+class GURL;
+
+namespace extensions {
+class InfoMap;
+}
+
+namespace net {
+class URLRequest;
+}
+
+// This class is used to test whether extensions may modify web requests.
+class WebRequestPermissions {
+ public:
+ // Different host permission checking modes for CanExtensionAccessURL.
+ enum HostPermissionsCheck {
+ DO_NOT_CHECK_HOST = 0, // No check.
+ REQUIRE_HOST_PERMISSION, // Permission needed for given URL.
+ REQUIRE_ALL_URLS // Permission needed for <all_urls>.
+ };
+
+ // Returns true if the request shall not be reported to extensions.
+ static bool HideRequest(const extensions::InfoMap* extension_info_map,
+ const net::URLRequest* request);
+
+ // |host_permission_check| controls how permissions are checked with regard to
+ // |url|.
+ static extensions::PermissionsData::AccessType CanExtensionAccessURL(
+ const extensions::InfoMap* extension_info_map,
+ const std::string& extension_id,
+ const GURL& url,
+ int tab_id,
+ bool crosses_incognito,
+ HostPermissionsCheck host_permissions_check);
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(WebRequestPermissions);
+};
+
+#endif // EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_PERMISSIONS_H_
diff --git a/chromium/extensions/browser/api/web_request/web_request_time_tracker.cc b/chromium/extensions/browser/api/web_request/web_request_time_tracker.cc
new file mode 100644
index 00000000000..58bfbad2e80
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/web_request_time_tracker.cc
@@ -0,0 +1,253 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/web_request/web_request_time_tracker.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/metrics/histogram.h"
+#include "extensions/browser/warning_set.h"
+
+
+// TODO(mpcomplete): tweak all these constants.
+namespace {
+// The number of requests we keep track of at a time.
+const size_t kMaxRequestsLogged = 100u;
+
+// If a request completes faster than this amount (in ms), then we ignore it.
+// Any delays on such a request was negligible.
+const int kMinRequestTimeToCareMs = 10;
+
+// Thresholds above which we consider a delay caused by an extension to be "too
+// much". This is given in percentage of total request time that was spent
+// waiting on the extension.
+const double kThresholdModerateDelay = 0.20;
+const double kThresholdExcessiveDelay = 0.50;
+
+// If this many requests (of the past kMaxRequestsLogged) have had "too much"
+// delay, then we will warn the user.
+const size_t kNumModerateDelaysBeforeWarning = 50u;
+const size_t kNumExcessiveDelaysBeforeWarning = 10u;
+
+// Default implementation for ExtensionWebRequestTimeTrackerDelegate
+// that sets a warning in the extension service of |profile|.
+class DefaultDelegate : public ExtensionWebRequestTimeTrackerDelegate {
+ public:
+ ~DefaultDelegate() override {}
+
+ // Implementation of ExtensionWebRequestTimeTrackerDelegate.
+ void NotifyExcessiveDelays(
+ void* profile,
+ size_t num_delayed_messages,
+ size_t total_num_messages,
+ const std::set<std::string>& extension_ids) override;
+ void NotifyModerateDelays(
+ void* profile,
+ size_t num_delayed_messages,
+ size_t total_num_messages,
+ const std::set<std::string>& extension_ids) override;
+};
+
+void DefaultDelegate::NotifyExcessiveDelays(
+ void* profile,
+ size_t num_delayed_messages,
+ size_t total_num_messages,
+ const std::set<std::string>& extension_ids) {
+ // TODO(battre) Enable warning the user if extensions misbehave as soon as we
+ // have data that allows us to decide on reasonable limits for triggering the
+ // warnings.
+ // BrowserThread::PostTask(
+ // BrowserThread::UI,
+ // FROM_HERE,
+ // base::Bind(&ExtensionWarningSet::NotifyWarningsOnUI,
+ // profile,
+ // extension_ids,
+ // ExtensionWarningSet::kNetworkDelay));
+}
+
+void DefaultDelegate::NotifyModerateDelays(
+ void* profile,
+ size_t num_delayed_messages,
+ size_t total_num_messages,
+ const std::set<std::string>& extension_ids) {
+ // TODO(battre) Enable warning the user if extensions misbehave as soon as we
+ // have data that allows us to decide on reasonable limits for triggering the
+ // warnings.
+ // BrowserThread::PostTask(
+ // BrowserThread::UI,
+ // FROM_HERE,
+ // base::Bind(&ExtensionWarningSet::NotifyWarningsOnUI,
+ // profile,
+ // extension_ids,
+ // ExtensionWarningSet::kNetworkDelay));
+}
+
+} // namespace
+
+ExtensionWebRequestTimeTracker::RequestTimeLog::RequestTimeLog()
+ : profile(NULL), completed(false) {
+}
+
+ExtensionWebRequestTimeTracker::RequestTimeLog::RequestTimeLog(
+ const RequestTimeLog& other) = default;
+
+ExtensionWebRequestTimeTracker::RequestTimeLog::~RequestTimeLog() {
+}
+
+ExtensionWebRequestTimeTracker::ExtensionWebRequestTimeTracker()
+ : delegate_(new DefaultDelegate) {
+}
+
+ExtensionWebRequestTimeTracker::~ExtensionWebRequestTimeTracker() {
+}
+
+void ExtensionWebRequestTimeTracker::LogRequestStartTime(
+ int64_t request_id,
+ const base::Time& start_time,
+ const GURL& url,
+ void* profile) {
+ // Trim old completed request logs.
+ while (request_ids_.size() > kMaxRequestsLogged) {
+ int64_t to_remove = request_ids_.front();
+ request_ids_.pop();
+ std::map<int64_t, RequestTimeLog>::iterator iter =
+ request_time_logs_.find(to_remove);
+ if (iter != request_time_logs_.end() && iter->second.completed) {
+ request_time_logs_.erase(iter);
+ moderate_delays_.erase(to_remove);
+ excessive_delays_.erase(to_remove);
+ }
+ }
+ request_ids_.push(request_id);
+
+ if (request_time_logs_.find(request_id) != request_time_logs_.end()) {
+ RequestTimeLog& log = request_time_logs_[request_id];
+ DCHECK(!log.completed);
+ return;
+ }
+ RequestTimeLog& log = request_time_logs_[request_id];
+ log.request_start_time = start_time;
+ log.url = url;
+ log.profile = profile;
+}
+
+void ExtensionWebRequestTimeTracker::LogRequestEndTime(
+ int64_t request_id,
+ const base::Time& end_time) {
+ if (request_time_logs_.find(request_id) == request_time_logs_.end())
+ return;
+
+ RequestTimeLog& log = request_time_logs_[request_id];
+ if (log.completed)
+ return;
+
+ log.request_duration = end_time - log.request_start_time;
+ log.completed = true;
+
+ if (log.extension_block_durations.empty())
+ return;
+
+ UMA_HISTOGRAM_TIMES("Extensions.NetworkDelay", log.block_duration);
+
+ Analyze(request_id);
+}
+
+std::set<std::string> ExtensionWebRequestTimeTracker::GetExtensionIds(
+ const RequestTimeLog& log) const {
+ std::set<std::string> result;
+ for (std::map<std::string, base::TimeDelta>::const_iterator i =
+ log.extension_block_durations.begin();
+ i != log.extension_block_durations.end();
+ ++i) {
+ result.insert(i->first);
+ }
+ return result;
+}
+
+void ExtensionWebRequestTimeTracker::Analyze(int64_t request_id) {
+ RequestTimeLog& log = request_time_logs_[request_id];
+
+ // Ignore really short requests. Time spent on these is negligible, and any
+ // extra delay the extension adds is likely to be noise.
+ if (log.request_duration.InMilliseconds() < kMinRequestTimeToCareMs)
+ return;
+
+ double percentage =
+ log.block_duration.InMillisecondsF() /
+ log.request_duration.InMillisecondsF();
+ UMA_HISTOGRAM_PERCENTAGE("Extensions.NetworkDelayPercentage",
+ static_cast<int>(100*percentage));
+ VLOG(1) << "WR percent " << request_id << ": " << log.url << ": " <<
+ log.block_duration.InMilliseconds() << "/" <<
+ log.request_duration.InMilliseconds() << " = " << percentage;
+
+ // TODO(mpcomplete): blame a specific extension. Maybe go through the list
+ // of recent requests and find the extension that has caused the most delays.
+ if (percentage > kThresholdExcessiveDelay) {
+ excessive_delays_.insert(request_id);
+ if (excessive_delays_.size() > kNumExcessiveDelaysBeforeWarning) {
+ VLOG(1) << "WR excessive delays:" << excessive_delays_.size();
+ if (delegate_.get()) {
+ delegate_->NotifyExcessiveDelays(log.profile,
+ excessive_delays_.size(),
+ request_ids_.size(),
+ GetExtensionIds(log));
+ }
+ }
+ } else if (percentage > kThresholdModerateDelay) {
+ moderate_delays_.insert(request_id);
+ if (moderate_delays_.size() + excessive_delays_.size() >
+ kNumModerateDelaysBeforeWarning) {
+ VLOG(1) << "WR moderate delays:" << moderate_delays_.size();
+ if (delegate_.get()) {
+ delegate_->NotifyModerateDelays(
+ log.profile,
+ moderate_delays_.size() + excessive_delays_.size(),
+ request_ids_.size(),
+ GetExtensionIds(log));
+ }
+ }
+ }
+}
+
+void ExtensionWebRequestTimeTracker::IncrementExtensionBlockTime(
+ const std::string& extension_id,
+ int64_t request_id,
+ const base::TimeDelta& block_time) {
+ if (request_time_logs_.find(request_id) == request_time_logs_.end())
+ return;
+ RequestTimeLog& log = request_time_logs_[request_id];
+ log.extension_block_durations[extension_id] += block_time;
+}
+
+void ExtensionWebRequestTimeTracker::IncrementTotalBlockTime(
+ int64_t request_id,
+ const base::TimeDelta& block_time) {
+ if (request_time_logs_.find(request_id) == request_time_logs_.end())
+ return;
+ RequestTimeLog& log = request_time_logs_[request_id];
+ log.block_duration += block_time;
+}
+
+void ExtensionWebRequestTimeTracker::SetRequestCanceled(int64_t request_id) {
+ // Canceled requests won't actually hit the network, so we can't compare
+ // their request time to the time spent waiting on the extension. Just ignore
+ // them.
+ // TODO(mpcomplete): may want to count cancels as "bonuses" for an extension.
+ // i.e. if it slows down 50% of requests but cancels 25% of the rest, that
+ // might average out to only being "25% slow".
+ request_time_logs_.erase(request_id);
+}
+
+void ExtensionWebRequestTimeTracker::SetRequestRedirected(int64_t request_id) {
+ // When a request is redirected, we have no way of knowing how long the
+ // request would have taken, so we can't say how much an extension slowed
+ // down this request. Just ignore it.
+ request_time_logs_.erase(request_id);
+}
+
+void ExtensionWebRequestTimeTracker::SetDelegate(
+ ExtensionWebRequestTimeTrackerDelegate* delegate) {
+ delegate_.reset(delegate);
+}
diff --git a/chromium/extensions/browser/api/web_request/web_request_time_tracker.h b/chromium/extensions/browser/api/web_request/web_request_time_tracker.h
new file mode 100644
index 00000000000..af3073189d4
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/web_request_time_tracker.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_TIME_TRACKER_H_
+#define EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_TIME_TRACKER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <map>
+#include <queue>
+#include <set>
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "url/gurl.h"
+
+namespace base {
+class Time;
+}
+
+class ExtensionWebRequestTimeTrackerDelegate {
+ public:
+ virtual ~ExtensionWebRequestTimeTrackerDelegate() {}
+
+ // Notifies the delegate that |num_delayed_messages| of the last
+ // |total_num_messages| inspected messages were excessively/moderately
+ // delayed. Every excessively delayed message is also counted as a moderately
+ // delayed message.
+ virtual void NotifyExcessiveDelays(
+ void* profile,
+ size_t num_delayed_messages,
+ size_t total_num_messages,
+ const std::set<std::string>& extension_ids) = 0;
+ virtual void NotifyModerateDelays(
+ void* profile,
+ size_t num_delayed_messages,
+ size_t total_num_messages,
+ const std::set<std::string>& extension_ids) = 0;
+};
+
+// This class keeps monitors how much delay extensions add to network requests
+// by using the webRequest API. If the delay is sufficient, we will warn the
+// user that extensions are slowing down the browser.
+class ExtensionWebRequestTimeTracker {
+ public:
+ ExtensionWebRequestTimeTracker();
+ ~ExtensionWebRequestTimeTracker();
+
+ // Records the time that a request was created.
+ void LogRequestStartTime(int64_t request_id,
+ const base::Time& start_time,
+ const GURL& url,
+ void* profile);
+
+ // Records the time that a request either completed or encountered an error.
+ void LogRequestEndTime(int64_t request_id, const base::Time& end_time);
+
+ // Records an additional delay for the given request caused by the given
+ // extension.
+ void IncrementExtensionBlockTime(const std::string& extension_id,
+ int64_t request_id,
+ const base::TimeDelta& block_time);
+
+ // Records an additional delay for the given request caused by all extensions
+ // combined.
+ void IncrementTotalBlockTime(int64_t request_id,
+ const base::TimeDelta& block_time);
+
+ // Called when an extension has canceled the given request.
+ void SetRequestCanceled(int64_t request_id);
+
+ // Called when an extension has redirected the given request to another URL.
+ void SetRequestRedirected(int64_t request_id);
+
+ // Takes ownership of |delegate|.
+ void SetDelegate(ExtensionWebRequestTimeTrackerDelegate* delegate);
+
+ private:
+ // Timing information for a single request.
+ struct RequestTimeLog {
+ GURL url; // used for debug purposes only
+ void* profile; // profile that created the request
+ bool completed;
+ base::Time request_start_time;
+ base::TimeDelta request_duration;
+ base::TimeDelta block_duration;
+ std::map<std::string, base::TimeDelta> extension_block_durations;
+ RequestTimeLog();
+ RequestTimeLog(const RequestTimeLog& other);
+ ~RequestTimeLog();
+ };
+
+ // Called after a request finishes, to analyze the delays and warn the user
+ // if necessary.
+ void Analyze(int64_t request_id);
+
+ // Returns a list of all extension IDs that contributed to delay for |log|.
+ std::set<std::string> GetExtensionIds(const RequestTimeLog& log) const;
+
+ // A map of recent request IDs to timing info for each request.
+ std::map<int64_t, RequestTimeLog> request_time_logs_;
+
+ // A list of recent request IDs that we know about. Used to limit the size of
+ // the logs.
+ std::queue<int64_t> request_ids_;
+
+ // The set of recent requests that have been delayed either a large or
+ // moderate amount by extensions.
+ std::set<int64_t> excessive_delays_;
+ std::set<int64_t> moderate_delays_;
+
+ // Defaults to a delegate that sets warnings in the extension service.
+ scoped_ptr<ExtensionWebRequestTimeTrackerDelegate> delegate_;
+
+ FRIEND_TEST_ALL_PREFIXES(ExtensionWebRequestTimeTrackerTest, Basic);
+ FRIEND_TEST_ALL_PREFIXES(ExtensionWebRequestTimeTrackerTest,
+ IgnoreFastRequests);
+ FRIEND_TEST_ALL_PREFIXES(ExtensionWebRequestTimeTrackerTest,
+ CancelOrRedirect);
+ FRIEND_TEST_ALL_PREFIXES(ExtensionWebRequestTimeTrackerTest, Delays);
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionWebRequestTimeTracker);
+};
+
+#endif // EXTENSIONS_BROWSER_API_WEB_REQUEST_WEB_REQUEST_TIME_TRACKER_H_
diff --git a/chromium/extensions/browser/api/web_request/web_request_time_tracker_unittest.cc b/chromium/extensions/browser/api/web_request/web_request_time_tracker_unittest.cc
new file mode 100644
index 00000000000..c3959a70ecf
--- /dev/null
+++ b/chromium/extensions/browser/api/web_request/web_request_time_tracker_unittest.cc
@@ -0,0 +1,150 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/web_request/web_request_time_tracker.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+const base::TimeDelta kRequestDelta = base::TimeDelta::FromMilliseconds(100);
+const base::TimeDelta kTinyDelay = base::TimeDelta::FromMilliseconds(1);
+const base::TimeDelta kModerateDelay = base::TimeDelta::FromMilliseconds(25);
+const base::TimeDelta kExcessiveDelay = base::TimeDelta::FromMilliseconds(75);
+
+class ExtensionWebRequestTimeTrackerDelegateMock
+ : public ExtensionWebRequestTimeTrackerDelegate {
+ public:
+ MOCK_METHOD4(NotifyExcessiveDelays,
+ void (void*, size_t, size_t, const std::set<std::string>&));
+ MOCK_METHOD4(NotifyModerateDelays,
+ void (void*, size_t, size_t, const std::set<std::string>&));
+};
+
+} // namespace
+
+//class ExtensionWebRequestTimeTrackerTest : public testing::Test {};
+
+TEST(ExtensionWebRequestTimeTrackerTest, Basic) {
+ ExtensionWebRequestTimeTracker tracker;
+ base::Time start;
+ void* profile = NULL;
+
+ tracker.LogRequestStartTime(42, start, GURL(), profile);
+ EXPECT_EQ(1u, tracker.request_time_logs_.size());
+ ASSERT_EQ(1u, tracker.request_ids_.size());
+ EXPECT_EQ(42, tracker.request_ids_.front());
+ tracker.LogRequestEndTime(42, start + kRequestDelta);
+ EXPECT_EQ(1u, tracker.request_time_logs_.size());
+ EXPECT_EQ(0u, tracker.moderate_delays_.size());
+ EXPECT_EQ(0u, tracker.excessive_delays_.size());
+}
+
+TEST(ExtensionWebRequestTimeTrackerTest, CancelOrRedirect) {
+ ExtensionWebRequestTimeTracker tracker;
+ base::Time start;
+ void* profile = NULL;
+
+ tracker.LogRequestStartTime(1, start, GURL(), profile);
+ EXPECT_EQ(1u, tracker.request_time_logs_.size());
+ tracker.SetRequestCanceled(1);
+ tracker.LogRequestEndTime(1, start + kRequestDelta);
+ EXPECT_EQ(0u, tracker.request_time_logs_.size());
+
+ tracker.LogRequestStartTime(2, start, GURL(), profile);
+ EXPECT_EQ(1u, tracker.request_time_logs_.size());
+ tracker.SetRequestRedirected(2);
+ tracker.LogRequestEndTime(2, start + kRequestDelta);
+ EXPECT_EQ(0u, tracker.request_time_logs_.size());
+}
+
+TEST(ExtensionWebRequestTimeTrackerTest, Delays) {
+ ExtensionWebRequestTimeTracker tracker;
+ base::Time start;
+ std::string extension1_id("1");
+ std::string extension2_id("2");
+ void* profile = NULL;
+
+ // Start 3 requests with different amounts of delay from 2 extensions.
+ tracker.LogRequestStartTime(1, start, GURL(), profile);
+ tracker.LogRequestStartTime(2, start, GURL(), profile);
+ tracker.LogRequestStartTime(3, start, GURL(), profile);
+ tracker.IncrementExtensionBlockTime(extension1_id, 1, kTinyDelay);
+ tracker.IncrementExtensionBlockTime(extension1_id, 2, kTinyDelay);
+ tracker.IncrementExtensionBlockTime(extension1_id, 3, kTinyDelay);
+ tracker.IncrementExtensionBlockTime(extension2_id, 2, kModerateDelay);
+ tracker.IncrementExtensionBlockTime(extension2_id, 3, kExcessiveDelay);
+ tracker.IncrementTotalBlockTime(1, kTinyDelay);
+ tracker.IncrementTotalBlockTime(2, kModerateDelay);
+ tracker.IncrementTotalBlockTime(3, kExcessiveDelay);
+ tracker.LogRequestEndTime(1, start + kRequestDelta);
+ tracker.LogRequestEndTime(2, start + kRequestDelta);
+ tracker.LogRequestEndTime(3, start + kRequestDelta);
+ EXPECT_EQ(3u, tracker.request_time_logs_.size());
+ EXPECT_EQ(1u, tracker.moderate_delays_.size());
+ EXPECT_EQ(1u, tracker.moderate_delays_.count(2));
+ EXPECT_EQ(1u, tracker.excessive_delays_.size());
+ EXPECT_EQ(1u, tracker.excessive_delays_.count(3));
+
+ // Now issue a bunch more requests and ensure that the old delays are
+ // forgotten.
+ for (int64_t i = 4; i < 500; ++i) {
+ tracker.LogRequestStartTime(i, start, GURL(), profile);
+ tracker.LogRequestEndTime(i, start + kRequestDelta);
+ }
+ EXPECT_EQ(0u, tracker.moderate_delays_.size());
+ EXPECT_EQ(0u, tracker.excessive_delays_.size());
+}
+
+TEST(ExtensionWebRequestTimeTrackerTest, Delegate) {
+ using testing::Mock;
+
+ ExtensionWebRequestTimeTrackerDelegateMock* delegate(
+ new ExtensionWebRequestTimeTrackerDelegateMock);
+ ExtensionWebRequestTimeTracker tracker;
+ tracker.SetDelegate(delegate);
+ base::Time start;
+ std::string extension1_id("1");
+ void* profile = NULL;
+ // Set of all extensions that blocked network requests.
+ std::set<std::string> extensions;
+ extensions.insert(extension1_id);
+
+ const int num_moderate_delays = 51;
+ const int num_excessive_delays = 11;
+ int request_nr = 0;
+
+ // Check that (only) the last moderate delay triggers the delegate callback.
+ for (int64_t i = 0; i < num_moderate_delays; ++i) {
+ request_nr++;
+ if (i == num_moderate_delays-1) {
+ EXPECT_CALL(*delegate,
+ NotifyModerateDelays(profile , i+1, request_nr, extensions));
+ }
+ tracker.LogRequestStartTime(request_nr, start, GURL(), profile);
+ tracker.IncrementExtensionBlockTime(extension1_id, request_nr,
+ kModerateDelay);
+ tracker.IncrementTotalBlockTime(request_nr, kModerateDelay);
+ tracker.LogRequestEndTime(request_nr, start + kRequestDelta);
+ Mock::VerifyAndClearExpectations(delegate);
+ }
+
+ // Check that (only) the last excessive delay triggers the delegate callback.
+ for (int64_t i = 0; i < num_excessive_delays; ++i) {
+ request_nr++;
+ if (i == num_excessive_delays-1) {
+ EXPECT_CALL(*delegate,
+ NotifyExcessiveDelays(profile, i+1, request_nr, extensions));
+ }
+ tracker.LogRequestStartTime(request_nr, start, GURL(), profile);
+ tracker.IncrementExtensionBlockTime(extension1_id, request_nr,
+ kExcessiveDelay);
+ tracker.IncrementTotalBlockTime(request_nr, kExcessiveDelay);
+ tracker.LogRequestEndTime(request_nr, start + kRequestDelta);
+ Mock::VerifyAndClearExpectations(delegate);
+ }
+}
diff --git a/chromium/extensions/browser/api/webcam_private/OWNERS b/chromium/extensions/browser/api/webcam_private/OWNERS
new file mode 100644
index 00000000000..8192dfb0eff
--- /dev/null
+++ b/chromium/extensions/browser/api/webcam_private/OWNERS
@@ -0,0 +1 @@
+zork@chromium.org
diff --git a/chromium/extensions/browser/api/webcam_private/v4l2_webcam.cc b/chromium/extensions/browser/api/webcam_private/v4l2_webcam.cc
new file mode 100644
index 00000000000..0bcbe64d641
--- /dev/null
+++ b/chromium/extensions/browser/api/webcam_private/v4l2_webcam.cc
@@ -0,0 +1,207 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/webcam_private/v4l2_webcam.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/uvcvideo.h>
+#include <linux/videodev2.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "base/macros.h"
+#include "base/posix/eintr_wrapper.h"
+
+#define V4L2_CID_PAN_SPEED (V4L2_CID_CAMERA_CLASS_BASE+32)
+#define V4L2_CID_TILT_SPEED (V4L2_CID_CAMERA_CLASS_BASE+33)
+#define V4L2_CID_PANTILT_CMD (V4L2_CID_CAMERA_CLASS_BASE+34)
+
+// GUID of the Extension Unit for Logitech CC3300e motor control:
+// {212de5ff-3080-2c4e-82d9-f587d00540bd}
+#define UVC_GUID_LOGITECH_CC3000E_MOTORS \
+ {0x21, 0x2d, 0xe5, 0xff, 0x30, 0x80, 0x2c, 0x4e, \
+ 0x82, 0xd9, 0xf5, 0x87, 0xd0, 0x05, 0x40, 0xbd}
+
+#define LOGITECH_MOTORCONTROL_PANTILT_CMD 2
+
+namespace {
+const int kLogitechMenuIndexGoHome = 2;
+
+const uvc_menu_info kLogitechCmdMenu[] = {
+ {1, "Set Preset"}, {2, "Get Preset"}, {3, "Go Home"}
+};
+
+const uvc_xu_control_mapping kLogitechCmdMapping = {
+ V4L2_CID_PANTILT_CMD,
+ "Pan/Tilt Go",
+ UVC_GUID_LOGITECH_CC3000E_MOTORS,
+ LOGITECH_MOTORCONTROL_PANTILT_CMD,
+ 8,
+ 0,
+ V4L2_CTRL_TYPE_MENU,
+ UVC_CTRL_DATA_TYPE_ENUM,
+ const_cast<uvc_menu_info*>(&kLogitechCmdMenu[0]),
+ arraysize(kLogitechCmdMenu),
+};
+} // namespace
+
+namespace extensions {
+
+V4L2Webcam::V4L2Webcam(const std::string& device_id) : device_id_(device_id) {
+}
+
+V4L2Webcam::~V4L2Webcam() {
+}
+
+bool V4L2Webcam::Open() {
+ fd_.reset(HANDLE_EINTR(open(device_id_.c_str(), 0)));
+ return fd_.is_valid();
+}
+
+bool V4L2Webcam::EnsureLogitechCommandsMapped() {
+ int res =
+ HANDLE_EINTR(ioctl(fd_.get(), UVCIOC_CTRL_MAP, &kLogitechCmdMapping));
+ // If mapping is successful or it's already mapped, this is a Logitech
+ // camera.
+ // NOTE: On success, occasionally EFAULT is returned. On a real error,
+ // ENOMEM, EPERM, EINVAL, or EOVERFLOW should be returned.
+ return res >= 0 || errno == EEXIST || errno == EFAULT;
+}
+
+bool V4L2Webcam::SetWebcamParameter(int fd, uint32_t control_id, int value) {
+ struct v4l2_control v4l2_ctrl = {control_id, value};
+ int res = HANDLE_EINTR(ioctl(fd, VIDIOC_S_CTRL, &v4l2_ctrl)) >= 0;
+ return res >= 0;
+}
+
+bool V4L2Webcam::GetWebcamParameter(int fd, uint32_t control_id, int* value) {
+ struct v4l2_control v4l2_ctrl = {control_id};
+
+ if (HANDLE_EINTR(ioctl(fd, VIDIOC_G_CTRL, &v4l2_ctrl)))
+ return false;
+
+ *value = v4l2_ctrl.value;
+ return true;
+}
+
+void V4L2Webcam::GetPan(const GetPTZCompleteCallback& callback) {
+ int value = 0;
+ bool success = GetWebcamParameter(fd_.get(), V4L2_CID_PAN_ABSOLUTE, &value);
+ callback.Run(success, value);
+}
+
+void V4L2Webcam::GetTilt(const GetPTZCompleteCallback& callback) {
+ int value = 0;
+ bool success = GetWebcamParameter(fd_.get(), V4L2_CID_TILT_ABSOLUTE, &value);
+ callback.Run(success, value);
+}
+
+void V4L2Webcam::GetZoom(const GetPTZCompleteCallback& callback) {
+ int value = 0;
+ bool success = GetWebcamParameter(fd_.get(), V4L2_CID_ZOOM_ABSOLUTE, &value);
+ callback.Run(success, value);
+}
+
+void V4L2Webcam::SetPan(int value,
+ int pan_speed,
+ const SetPTZCompleteCallback& callback) {
+ callback.Run(SetWebcamParameter(fd_.get(), V4L2_CID_PAN_ABSOLUTE, value));
+}
+
+void V4L2Webcam::SetTilt(int value,
+ int tilt_speed,
+ const SetPTZCompleteCallback& callback) {
+ callback.Run(SetWebcamParameter(fd_.get(), V4L2_CID_TILT_ABSOLUTE, value));
+}
+
+void V4L2Webcam::SetZoom(int value, const SetPTZCompleteCallback& callback) {
+ callback.Run(SetWebcamParameter(fd_.get(), V4L2_CID_ZOOM_ABSOLUTE, value));
+}
+
+void V4L2Webcam::SetPanDirection(PanDirection direction,
+ int pan_speed,
+ const SetPTZCompleteCallback& callback) {
+ int direction_value = 0;
+ switch (direction) {
+ case PAN_STOP:
+ direction_value = 0;
+ break;
+
+ case PAN_RIGHT:
+ direction_value = 1;
+ break;
+
+ case PAN_LEFT:
+ direction_value = -1;
+ break;
+ }
+ callback.Run(
+ SetWebcamParameter(fd_.get(), V4L2_CID_PAN_SPEED, direction_value));
+}
+
+void V4L2Webcam::SetTiltDirection(TiltDirection direction,
+ int tilt_speed,
+ const SetPTZCompleteCallback& callback) {
+ int direction_value = 0;
+ switch (direction) {
+ case TILT_STOP:
+ direction_value = 0;
+ break;
+
+ case TILT_UP:
+ direction_value = 1;
+ break;
+
+ case TILT_DOWN:
+ direction_value = -1;
+ break;
+ }
+ callback.Run(
+ SetWebcamParameter(fd_.get(), V4L2_CID_TILT_SPEED, direction_value));
+}
+
+void V4L2Webcam::Reset(bool pan,
+ bool tilt,
+ bool zoom,
+ const SetPTZCompleteCallback& callback) {
+ if (pan || tilt) {
+ if (EnsureLogitechCommandsMapped()) {
+ if (!SetWebcamParameter(fd_.get(), V4L2_CID_PANTILT_CMD,
+ kLogitechMenuIndexGoHome)) {
+ callback.Run(false);
+ return;
+ }
+ } else {
+ if (pan) {
+ struct v4l2_control v4l2_ctrl = {V4L2_CID_PAN_RESET};
+ if (!HANDLE_EINTR(ioctl(fd_.get(), VIDIOC_S_CTRL, &v4l2_ctrl))) {
+ callback.Run(false);
+ return;
+ }
+ }
+
+ if (tilt) {
+ struct v4l2_control v4l2_ctrl = {V4L2_CID_TILT_RESET};
+ if (!HANDLE_EINTR(ioctl(fd_.get(), VIDIOC_S_CTRL, &v4l2_ctrl))) {
+ callback.Run(false);
+ return;
+ }
+ }
+ }
+ }
+
+ if (zoom) {
+ const int kDefaultZoom = 100;
+ if (!SetWebcamParameter(fd_.get(), V4L2_CID_ZOOM_ABSOLUTE, kDefaultZoom)) {
+ callback.Run(false);
+ return;
+ }
+ }
+
+ callback.Run(true);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/webcam_private/v4l2_webcam.h b/chromium/extensions/browser/api/webcam_private/v4l2_webcam.h
new file mode 100644
index 00000000000..5c2ac79884b
--- /dev/null
+++ b/chromium/extensions/browser/api/webcam_private/v4l2_webcam.h
@@ -0,0 +1,59 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_WEBCAM_PRIVATE_V4L2_WEBCAM_H_
+#define EXTENSIONS_BROWSER_API_WEBCAM_PRIVATE_V4L2_WEBCAM_H_
+
+#include "extensions/browser/api/webcam_private/webcam.h"
+
+#include <stdint.h>
+
+#include "base/files/scoped_file.h"
+#include "base/macros.h"
+
+namespace extensions {
+
+class V4L2Webcam : public Webcam {
+ public:
+ V4L2Webcam(const std::string& device_id);
+ bool Open();
+
+ private:
+ ~V4L2Webcam() override;
+ bool EnsureLogitechCommandsMapped();
+ bool SetWebcamParameter(int fd, uint32_t control_id, int value);
+ bool GetWebcamParameter(int fd, uint32_t control_id, int* value);
+
+ // Webcam:
+ void GetPan(const GetPTZCompleteCallback& callback) override;
+ void GetTilt(const GetPTZCompleteCallback& callback) override;
+ void GetZoom(const GetPTZCompleteCallback& callback) override;
+ void SetPan(int value,
+ int pan_speed,
+ const SetPTZCompleteCallback& callback) override;
+ void SetTilt(int value,
+ int tilt_speed,
+ const SetPTZCompleteCallback& callback) override;
+ void SetZoom(int value, const SetPTZCompleteCallback& callback) override;
+ void SetPanDirection(PanDirection direction,
+ int pan_speed,
+ const SetPTZCompleteCallback& callback) override;
+ void SetTiltDirection(TiltDirection direction,
+ int tilt_speed,
+ const SetPTZCompleteCallback& callback) override;
+ void Reset(bool pan,
+ bool tilt,
+ bool zoom,
+ const SetPTZCompleteCallback& callback) override;
+
+ const std::string device_id_;
+ base::ScopedFD fd_;
+
+ DISALLOW_COPY_AND_ASSIGN(V4L2Webcam);
+};
+
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_WEBCAM_PRIVATE_V4L2_WEBCAM_H_
diff --git a/chromium/extensions/browser/api/webcam_private/visca_webcam.cc b/chromium/extensions/browser/api/webcam_private/visca_webcam.cc
new file mode 100644
index 00000000000..18c847b7d9f
--- /dev/null
+++ b/chromium/extensions/browser/api/webcam_private/visca_webcam.cc
@@ -0,0 +1,500 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/webcam_private/visca_webcam.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "content/public/browser/browser_thread.h"
+
+using content::BrowserThread;
+
+namespace {
+
+// Message terminator:
+const char kViscaTerminator = 0xFF;
+
+// Response types:
+const char kViscaResponseNetworkChange = 0x38;
+const char kViscaResponseAck = 0x40;
+const char kViscaResponseError = 0x60;
+
+// The default pan speed is kMaxPanSpeed /2 and the default tilt speed is
+// kMaxTiltSpeed / 2.
+const int kMaxPanSpeed = 0x18;
+const int kMaxTiltSpeed = 0x14;
+const int kDefaultPanSpeed = 0x18 / 2;
+const int kDefaultTiltSpeed = 0x14 / 2;
+
+// Pan-Tilt-Zoom movement comands from http://www.manualslib.com/manual/...
+// 557364/Cisco-Precisionhd-1080p12x.html?page=31#manual
+
+// Reset the address of each device in the VISCA chain (broadcast). This is used
+// when resetting the VISCA network.
+const char kSetAddressCommand[] = {0x88, 0x30, 0x01, 0xFF};
+
+// Clear all of the devices, halting any pending commands in the VISCA chain
+// (broadcast). This is used when resetting the VISCA network.
+const char kClearAllCommand[] = {0x88, 0x01, 0x00, 0x01, 0xFF};
+
+// Command: {0x8X, 0x09, 0x06, 0x12, 0xFF}, X = 1 to 7: target device address.
+// Response: {0xY0, 0x50, 0x0p, 0x0q, 0x0r, 0x0s, 0x0t, 0x0u, 0x0v, 0x0w, 0xFF},
+// Y = socket number; pqrs: pan position; tuvw: tilt position.
+const char kGetPanTiltCommand[] = {0x81, 0x09, 0x06, 0x12, 0xFF};
+
+// Command: {0x8X, 0x01, 0x06, 0x02, 0x0p, 0x0t, 0x0q, 0x0r, 0x0s, 0x0u, 0x0v,
+// 0x0w, 0x0y, 0x0z, 0xFF}, X = 1 to 7: target device address; p = pan speed;
+// t = tilt speed; qrsu = pan position; vwyz = tilt position.
+const char kSetPanTiltCommand[] = {0x81, 0x01, 0x06, 0x02, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xFF};
+
+// Command: {0x8X, 0x01, 0x06, 0x05, 0xFF}, X = 1 to 7: target device address.
+const char kResetPanTiltCommand[] = {0x81, 0x01, 0x06, 0x05, 0xFF};
+
+// Command: {0x8X, 0x09, 0x04, 0x47, 0xFF}, X = 1 to 7: target device address.
+// Response: {0xY0, 0x50, 0x0p, 0x0q, 0x0r, 0x0s, 0xFF}, Y = socket number;
+// pqrs: zoom position.
+const char kGetZoomCommand[] = {0x81, 0x09, 0x04, 0x47, 0xFF};
+
+// Command: {0x8X, 0x01, 0x04, 0x47, 0x0p, 0x0q, 0x0r, 0x0s, 0xFF}, X = 1 to 7:
+// target device address; pqrs: zoom position;
+const char kSetZoomCommand[] = {0x81, 0x01, 0x04, 0x47, 0x00,
+ 0x00, 0x00, 0x00, 0xFF};
+
+// Command: {0x8X, 0x01, 0x06, 0x01, 0x0p, 0x0t, 0x03, 0x01, 0xFF}, X = 1 to 7:
+// target device address; p: pan speed; t: tilt speed.
+const char kPTUpCommand[] = {0x81, 0x01, 0x06, 0x01, 0x00,
+ 0x00, 0x03, 0x01, 0xFF};
+
+// Command: {0x8X, 0x01, 0x06, 0x01, 0x0p, 0x0t, 0x03, 0x02, 0xFF}, X = 1 to 7:
+// target device address; p: pan speed; t: tilt speed.
+const char kPTDownCommand[] = {0x81, 0x01, 0x06, 0x01, 0x00,
+ 0x00, 0x03, 0x02, 0xFF};
+
+// Command: {0x8X, 0x01, 0x06, 0x01, 0x0p, 0x0t, 0x0, 0x03, 0xFF}, X = 1 to 7:
+// target device address; p: pan speed; t: tilt speed.
+const char kPTLeftCommand[] = {0x81, 0x01, 0x06, 0x01, 0x00,
+ 0x00, 0x01, 0x03, 0xFF};
+
+// Command: {0x8X, 0x01, 0x06, 0x01, 0x0p, 0x0t, 0x02, 0x03, 0xFF}, X = 1 to 7:
+// target device address; p: pan speed; t: tilt speed.
+const char kPTRightCommand[] = {0x81, 0x01, 0x06, 0x01, 0x00,
+ 0x00, 0x02, 0x03, 0xFF};
+
+// Command: {0x8X, 0x01, 0x06, 0x01, 0x03, 0x03, 0x03, 0x03, 0xFF}, X = 1 to 7:
+// target device address.
+const char kPTStopCommand[] = {0x81, 0x01, 0x06, 0x01, 0x03,
+ 0x03, 0x03, 0x03, 0xFF};
+
+#define CHAR_VECTOR_FROM_ARRAY(array) \
+ std::vector<char>(array, array + arraysize(array))
+
+int ShiftResponseLowerBits(char c, size_t shift) {
+ return static_cast<int>(c & 0x0F) << shift;
+}
+
+bool CanBuildResponseInt(const std::vector<char>& response,
+ size_t start_index) {
+ return response.size() >= start_index + 4;
+}
+
+int BuildResponseInt(const std::vector<char>& response, size_t start_index) {
+ return ShiftResponseLowerBits(response[start_index], 12) +
+ ShiftResponseLowerBits(response[start_index + 1], 8) +
+ ShiftResponseLowerBits(response[start_index + 2], 4) +
+ ShiftResponseLowerBits(response[start_index + 3], 0);
+}
+
+void ResponseToCommand(std::vector<char>* command,
+ size_t start_index,
+ uint16_t response) {
+ DCHECK(command);
+ std::vector<char>& command_ref = *command;
+ command_ref[start_index] |= ((response >> 12) & 0x0F);
+ command_ref[start_index + 1] |= ((response >> 8) & 0x0F);
+ command_ref[start_index + 2] |= ((response >> 4 & 0x0F));
+ command_ref[start_index + 3] |= (response & 0x0F);
+}
+
+int CalculateSpeed(int desired_speed, int max_speed, int default_speed) {
+ int speed = std::min(desired_speed, max_speed);
+ return speed > 0 ? speed : default_speed;
+}
+
+int GetPositiveValue(int value) {
+ return value < 0x8000 ? value : value - 0xFFFF;
+}
+
+} // namespace
+
+namespace extensions {
+
+ViscaWebcam::ViscaWebcam() : pan_(0), tilt_(0), weak_ptr_factory_(this) {}
+
+ViscaWebcam::~ViscaWebcam() {
+}
+
+void ViscaWebcam::Open(const std::string& path,
+ const std::string& extension_id,
+ const OpenCompleteCallback& open_callback) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&ViscaWebcam::OpenOnIOThread, weak_ptr_factory_.GetWeakPtr(),
+ path, extension_id, open_callback));
+}
+
+void ViscaWebcam::OpenOnIOThread(const std::string& path,
+ const std::string& extension_id,
+ const OpenCompleteCallback& open_callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+ api::serial::ConnectionOptions options;
+
+ // Set the receive buffer size to receive the response data 1 by 1.
+ options.buffer_size.reset(new int(1));
+ options.persistent.reset(new bool(false));
+ options.bitrate.reset(new int(9600));
+ options.cts_flow_control.reset(new bool(false));
+ // Enable send and receive timeout error.
+ options.receive_timeout.reset(new int(3000));
+ options.send_timeout.reset(new int(3000));
+ options.data_bits = api::serial::DATA_BITS_EIGHT;
+ options.parity_bit = api::serial::PARITY_BIT_NO;
+ options.stop_bits = api::serial::STOP_BITS_ONE;
+
+ serial_connection_.reset(new SerialConnection(path, extension_id));
+ serial_connection_->Open(
+ options, base::Bind(&ViscaWebcam::OnConnected,
+ weak_ptr_factory_.GetWeakPtr(), open_callback));
+}
+
+void ViscaWebcam::OnConnected(const OpenCompleteCallback& open_callback,
+ bool success) {
+ if (!success) {
+ PostOpenFailureTask(open_callback);
+ return;
+ }
+
+ Send(CHAR_VECTOR_FROM_ARRAY(kSetAddressCommand),
+ base::Bind(&ViscaWebcam::OnAddressSetCompleted,
+ weak_ptr_factory_.GetWeakPtr(), open_callback));
+}
+
+void ViscaWebcam::OnAddressSetCompleted(
+ const OpenCompleteCallback& open_callback,
+ bool success,
+ const std::vector<char>& response) {
+ commands_.pop_front();
+ if (!success) {
+ PostOpenFailureTask(open_callback);
+ return;
+ }
+
+ Send(CHAR_VECTOR_FROM_ARRAY(kClearAllCommand),
+ base::Bind(&ViscaWebcam::OnClearAllCompleted,
+ weak_ptr_factory_.GetWeakPtr(), open_callback));
+}
+
+void ViscaWebcam::OnClearAllCompleted(const OpenCompleteCallback& open_callback,
+ bool success,
+ const std::vector<char>& response) {
+ commands_.pop_front();
+ if (!success) {
+ PostOpenFailureTask(open_callback);
+ return;
+ }
+
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(open_callback, true));
+}
+
+void ViscaWebcam::Send(const std::vector<char>& command,
+ const CommandCompleteCallback& callback) {
+ commands_.push_back(std::make_pair(command, callback));
+ // If this is the only command in the queue, send it now.
+ if (commands_.size() == 1) {
+ serial_connection_->Send(
+ command, base::Bind(&ViscaWebcam::OnSendCompleted,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+ }
+}
+
+void ViscaWebcam::OnSendCompleted(const CommandCompleteCallback& callback,
+ int bytes_sent,
+ api::serial::SendError error) {
+ // TODO(xdai): Check |bytes_sent|?
+ if (error == api::serial::SEND_ERROR_NONE) {
+ ReceiveLoop(callback);
+ } else {
+ callback.Run(false, std::vector<char>());
+ }
+}
+
+void ViscaWebcam::ReceiveLoop(const CommandCompleteCallback& callback) {
+ serial_connection_->Receive(base::Bind(&ViscaWebcam::OnReceiveCompleted,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback));
+}
+
+void ViscaWebcam::OnReceiveCompleted(const CommandCompleteCallback& callback,
+ const std::vector<char>& data,
+ api::serial::ReceiveError error) {
+ data_buffer_.insert(data_buffer_.end(), data.begin(), data.end());
+
+ if (error != api::serial::RECEIVE_ERROR_NONE || data_buffer_.empty()) {
+ // Clear |data_buffer_|.
+ std::vector<char> response;
+ response.swap(data_buffer_);
+ callback.Run(false, response);
+ return;
+ }
+
+ // Success case. If waiting for more data, then loop until encounter the
+ // terminator.
+ if (data_buffer_.back() != kViscaTerminator) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&ViscaWebcam::ReceiveLoop,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+ return;
+ }
+
+ // Success case, and a complete response has been received.
+ // Clear |data_buffer_|.
+ std::vector<char> response;
+ response.swap(data_buffer_);
+
+ if (response.size() < 2 ||
+ (static_cast<int>(response[1]) & 0xF0) == kViscaResponseError) {
+ callback.Run(false, response);
+ } else if ((static_cast<int>(response[1]) & 0xF0) != kViscaResponseAck &&
+ (static_cast<int>(response[1]) & 0xFF) !=
+ kViscaResponseNetworkChange) {
+ callback.Run(true, response);
+ } else {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&ViscaWebcam::ReceiveLoop,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+ }
+}
+
+void ViscaWebcam::OnCommandCompleted(const SetPTZCompleteCallback& callback,
+ bool success,
+ const std::vector<char>& response) {
+ // TODO(xdai): Error handling according to |response|.
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(callback, success));
+ ProcessNextCommand();
+}
+
+void ViscaWebcam::OnInquiryCompleted(InquiryType type,
+ const GetPTZCompleteCallback& callback,
+ bool success,
+ const std::vector<char>& response) {
+ if (!success) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(callback, false, 0));
+ ProcessNextCommand();
+ return;
+ }
+
+ bool is_valid_response = false;
+ switch (type) {
+ case INQUIRY_PAN:
+ is_valid_response = CanBuildResponseInt(response, 2);
+ break;
+ case INQUIRY_TILT:
+ is_valid_response = CanBuildResponseInt(response, 6);
+ break;
+ case INQUIRY_ZOOM:
+ is_valid_response = CanBuildResponseInt(response, 2);
+ break;
+ }
+ if (!is_valid_response) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(callback, false, 0));
+ ProcessNextCommand();
+ return;
+ }
+
+ int value = 0;
+ switch (type) {
+ case INQUIRY_PAN:
+ // See kGetPanTiltCommand for the format of response.
+ pan_ = BuildResponseInt(response, 2);
+ value = GetPositiveValue(pan_);
+ break;
+ case INQUIRY_TILT:
+ // See kGetPanTiltCommand for the format of response.
+ tilt_ = BuildResponseInt(response, 6);
+ value = GetPositiveValue(tilt_);
+ break;
+ case INQUIRY_ZOOM:
+ // See kGetZoomCommand for the format of response.
+ value = BuildResponseInt(response, 2);
+ break;
+ }
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(callback, true, value));
+ ProcessNextCommand();
+}
+
+void ViscaWebcam::ProcessNextCommand() {
+ commands_.pop_front();
+
+ if (commands_.empty())
+ return;
+
+ // If there are pending commands, process the next one.
+ const std::vector<char> next_command = commands_.front().first;
+ const CommandCompleteCallback next_callback = commands_.front().second;
+ serial_connection_->Send(
+ next_command, base::Bind(&ViscaWebcam::OnSendCompleted,
+ weak_ptr_factory_.GetWeakPtr(), next_callback));
+}
+
+void ViscaWebcam::PostOpenFailureTask(
+ const OpenCompleteCallback& open_callback) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(open_callback, false /* success? */));
+}
+
+void ViscaWebcam::GetPan(const GetPTZCompleteCallback& callback) {
+ Send(CHAR_VECTOR_FROM_ARRAY(kGetPanTiltCommand),
+ base::Bind(&ViscaWebcam::OnInquiryCompleted,
+ weak_ptr_factory_.GetWeakPtr(), INQUIRY_PAN, callback));
+}
+
+void ViscaWebcam::GetTilt(const GetPTZCompleteCallback& callback) {
+ Send(CHAR_VECTOR_FROM_ARRAY(kGetPanTiltCommand),
+ base::Bind(&ViscaWebcam::OnInquiryCompleted,
+ weak_ptr_factory_.GetWeakPtr(), INQUIRY_TILT, callback));
+}
+
+void ViscaWebcam::GetZoom(const GetPTZCompleteCallback& callback) {
+ Send(CHAR_VECTOR_FROM_ARRAY(kGetZoomCommand),
+ base::Bind(&ViscaWebcam::OnInquiryCompleted,
+ weak_ptr_factory_.GetWeakPtr(), INQUIRY_ZOOM, callback));
+}
+
+void ViscaWebcam::SetPan(int value,
+ int pan_speed,
+ const SetPTZCompleteCallback& callback) {
+ int actual_pan_speed =
+ CalculateSpeed(pan_speed, kMaxPanSpeed, kDefaultPanSpeed);
+ pan_ = value;
+
+ std::vector<char> command = CHAR_VECTOR_FROM_ARRAY(kSetPanTiltCommand);
+ command[4] |= actual_pan_speed;
+ command[5] |= kDefaultTiltSpeed;
+ ResponseToCommand(&command, 6, static_cast<uint16_t>(pan_));
+ ResponseToCommand(&command, 10, static_cast<uint16_t>(tilt_));
+ Send(command, base::Bind(&ViscaWebcam::OnCommandCompleted,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+}
+
+void ViscaWebcam::SetTilt(int value,
+ int tilt_speed,
+ const SetPTZCompleteCallback& callback) {
+ int actual_tilt_speed =
+ CalculateSpeed(tilt_speed, kMaxTiltSpeed, kDefaultTiltSpeed);
+ tilt_ = value;
+
+ std::vector<char> command = CHAR_VECTOR_FROM_ARRAY(kSetPanTiltCommand);
+ command[4] |= kDefaultPanSpeed;
+ command[5] |= actual_tilt_speed;
+ ResponseToCommand(&command, 6, static_cast<uint16_t>(pan_));
+ ResponseToCommand(&command, 10, static_cast<uint16_t>(tilt_));
+ Send(command, base::Bind(&ViscaWebcam::OnCommandCompleted,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+}
+
+void ViscaWebcam::SetZoom(int value, const SetPTZCompleteCallback& callback) {
+ int actual_value = std::max(value, 0);
+ std::vector<char> command = CHAR_VECTOR_FROM_ARRAY(kSetZoomCommand);
+ ResponseToCommand(&command, 4, actual_value);
+ Send(command, base::Bind(&ViscaWebcam::OnCommandCompleted,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+}
+
+void ViscaWebcam::SetPanDirection(PanDirection direction,
+ int pan_speed,
+ const SetPTZCompleteCallback& callback) {
+ int actual_pan_speed =
+ CalculateSpeed(pan_speed, kMaxPanSpeed, kDefaultPanSpeed);
+ std::vector<char> command = CHAR_VECTOR_FROM_ARRAY(kPTStopCommand);
+ switch (direction) {
+ case PAN_STOP:
+ break;
+ case PAN_RIGHT:
+ command = CHAR_VECTOR_FROM_ARRAY(kPTRightCommand);
+ command[4] |= actual_pan_speed;
+ command[5] |= kDefaultTiltSpeed;
+ break;
+ case PAN_LEFT:
+ command = CHAR_VECTOR_FROM_ARRAY(kPTLeftCommand);
+ command[4] |= actual_pan_speed;
+ command[5] |= kDefaultTiltSpeed;
+ break;
+ }
+ Send(command, base::Bind(&ViscaWebcam::OnCommandCompleted,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+}
+
+void ViscaWebcam::SetTiltDirection(TiltDirection direction,
+ int tilt_speed,
+ const SetPTZCompleteCallback& callback) {
+ int actual_tilt_speed =
+ CalculateSpeed(tilt_speed, kMaxTiltSpeed, kDefaultTiltSpeed);
+ std::vector<char> command = CHAR_VECTOR_FROM_ARRAY(kPTStopCommand);
+ switch (direction) {
+ case TILT_STOP:
+ break;
+ case TILT_UP:
+ command = CHAR_VECTOR_FROM_ARRAY(kPTUpCommand);
+ command[4] |= kDefaultPanSpeed;
+ command[5] |= actual_tilt_speed;
+ break;
+ case TILT_DOWN:
+ command = CHAR_VECTOR_FROM_ARRAY(kPTDownCommand);
+ command[4] |= kDefaultPanSpeed;
+ command[5] |= actual_tilt_speed;
+ break;
+ }
+ Send(command, base::Bind(&ViscaWebcam::OnCommandCompleted,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+}
+
+void ViscaWebcam::Reset(bool pan,
+ bool tilt,
+ bool zoom,
+ const SetPTZCompleteCallback& callback) {
+ // pan and tilt are always reset together in Visca Webcams.
+ if (pan || tilt) {
+ Send(CHAR_VECTOR_FROM_ARRAY(kResetPanTiltCommand),
+ base::Bind(&ViscaWebcam::OnCommandCompleted,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+ }
+ if (zoom) {
+ // Set the default zoom value to 100 to be consistent with V4l2 webcam.
+ const int kDefaultZoom = 100;
+ SetZoom(kDefaultZoom, callback);
+ }
+}
+
+void ViscaWebcam::OpenForTesting(
+ scoped_ptr<SerialConnection> serial_connection) {
+ serial_connection_ = std::move(serial_connection);
+}
+
+SerialConnection* ViscaWebcam::GetSerialConnectionForTesting() {
+ return serial_connection_.get();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/webcam_private/visca_webcam.h b/chromium/extensions/browser/api/webcam_private/visca_webcam.h
new file mode 100644
index 00000000000..0afe201f210
--- /dev/null
+++ b/chromium/extensions/browser/api/webcam_private/visca_webcam.h
@@ -0,0 +1,143 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_WEBCAM_PRIVATE_VISCA_WEBCAM_H_
+#define EXTENSIONS_BROWSER_API_WEBCAM_PRIVATE_VISCA_WEBCAM_H_
+
+#include <deque>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "extensions/browser/api/serial/serial_connection.h"
+#include "extensions/browser/api/webcam_private/webcam.h"
+#include "extensions/common/api/serial.h"
+
+namespace extensions {
+
+class ViscaWebcam : public Webcam {
+ public:
+ ViscaWebcam();
+
+ using OpenCompleteCallback = base::Callback<void(bool)>;
+
+ // Open and initialize the web camera. This is done by the following three
+ // steps (in order): 1. Open the serial port; 2. Request address; 3. Clear the
+ // command buffer. After these three steps completes, |open_callback| will be
+ // called.
+ void Open(const std::string& path,
+ const std::string& extension_id,
+ const OpenCompleteCallback& open_callback);
+
+ private:
+ friend class ViscaWebcamTest;
+
+ enum InquiryType {
+ INQUIRY_PAN,
+ INQUIRY_TILT,
+ INQUIRY_ZOOM,
+ };
+
+ using CommandCompleteCallback =
+ base::Callback<void(bool, const std::vector<char>&)>;
+
+ // Private because WebCam is base::RefCounted.
+ ~ViscaWebcam() override;
+
+ void OpenOnIOThread(const std::string& path,
+ const std::string& extension_id,
+ const OpenCompleteCallback& open_callback);
+
+ // Callback function that will be called after the serial connection has been
+ // opened successfully.
+ void OnConnected(const OpenCompleteCallback& open_callback, bool success);
+
+ // Callback function that will be called after the address has been set
+ // successfully.
+ void OnAddressSetCompleted(const OpenCompleteCallback& open_callback,
+ bool success,
+ const std::vector<char>& response);
+
+ // Callback function that will be called after the command buffer has been
+ // cleared successfully.
+ void OnClearAllCompleted(const OpenCompleteCallback& open_callback,
+ bool success,
+ const std::vector<char>& response);
+
+ // Send or queue a command and wait for the camera's response.
+ void Send(const std::vector<char>& command,
+ const CommandCompleteCallback& callback);
+ void OnSendCompleted(const CommandCompleteCallback& callback,
+ int bytes_sent,
+ api::serial::SendError error);
+ void ReceiveLoop(const CommandCompleteCallback& callback);
+ void OnReceiveCompleted(const CommandCompleteCallback& callback,
+ const std::vector<char>& data,
+ api::serial::ReceiveError error);
+
+ // Callback function that will be called after the send and reply of a command
+ // are both completed.
+ void OnCommandCompleted(const SetPTZCompleteCallback& callback,
+ bool success,
+ const std::vector<char>& response);
+ // Callback function that will be called after the send and reply of an
+ // inquiry are both completed.
+ void OnInquiryCompleted(InquiryType type,
+ const GetPTZCompleteCallback& callback,
+ bool success,
+ const std::vector<char>& response);
+
+ void ProcessNextCommand();
+ void PostOpenFailureTask(const OpenCompleteCallback& open_callback);
+
+ // Webcam Overrides:
+ void GetPan(const GetPTZCompleteCallback& callback) override;
+ void GetTilt(const GetPTZCompleteCallback& callback) override;
+ void GetZoom(const GetPTZCompleteCallback& callback) override;
+ void SetPan(int value,
+ int pan_speed,
+ const SetPTZCompleteCallback& callback) override;
+ void SetTilt(int value,
+ int tilt_speed,
+ const SetPTZCompleteCallback& callback) override;
+ void SetZoom(int value, const SetPTZCompleteCallback& callback) override;
+ void SetPanDirection(PanDirection direction,
+ int pan_speed,
+ const SetPTZCompleteCallback& callback) override;
+ void SetTiltDirection(TiltDirection direction,
+ int tilt_speed,
+ const SetPTZCompleteCallback& callback) override;
+ void Reset(bool pan,
+ bool tilt,
+ bool zoom,
+ const SetPTZCompleteCallback& callback) override;
+
+ // Used only in unit tests in place of Open().
+ void OpenForTesting(scoped_ptr<SerialConnection> serial_connection);
+
+ // Used only in unit tests to retrieve |serial_connection_| since this class
+ // owns it.
+ SerialConnection* GetSerialConnectionForTesting();
+
+ scoped_ptr<SerialConnection> serial_connection_;
+
+ // Stores the response for the current command.
+ std::vector<char> data_buffer_;
+
+ // Queues commands till the current command completes.
+ std::deque<std::pair<std::vector<char>, CommandCompleteCallback>> commands_;
+
+ // Visca webcam always get/set pan-tilt together. |pan| and |tilt| are used to
+ // store the current value of pan and tilt positions.
+ int pan_;
+ int tilt_;
+
+ base::WeakPtrFactory<ViscaWebcam> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViscaWebcam);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_WEBCAM_PRIVATE_VISCA_WEBCAM_H_
diff --git a/chromium/extensions/browser/api/webcam_private/visca_webcam_unittest.cc b/chromium/extensions/browser/api/webcam_private/visca_webcam_unittest.cc
new file mode 100644
index 00000000000..5cf98c49b12
--- /dev/null
+++ b/chromium/extensions/browser/api/webcam_private/visca_webcam_unittest.cc
@@ -0,0 +1,141 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <vector>
+
+#include "base/macros.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "extensions/browser/api/webcam_private/visca_webcam.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+class TestSerialConnection : public SerialConnection {
+ public:
+ TestSerialConnection() : SerialConnection("dummy_path", "dummy_id") {}
+ ~TestSerialConnection() override {}
+
+ void SetReceiveBuffer(const std::vector<char>& receive_buffer) {
+ receive_buffer_ = receive_buffer;
+ }
+
+ void CheckSendBufferAndClear(const std::vector<char>& expectations) {
+ EXPECT_EQ(send_buffer_, expectations);
+ send_buffer_.clear();
+ }
+
+ private:
+ // SerialConnection:
+ void Open(const api::serial::ConnectionOptions& options,
+ const OpenCompleteCallback& callback) override {
+ NOTREACHED();
+ }
+
+ bool Receive(const ReceiveCompleteCallback& callback) override {
+ callback.Run(receive_buffer_, api::serial::RECEIVE_ERROR_NONE);
+ receive_buffer_.clear();
+ return true;
+ }
+
+ bool Send(const std::vector<char>& data,
+ const SendCompleteCallback& callback) override {
+ send_buffer_.insert(send_buffer_.end(), data.begin(), data.end());
+ callback.Run(data.size(), api::serial::SEND_ERROR_NONE);
+ return true;
+ }
+
+ std::vector<char> receive_buffer_;
+ std::vector<char> send_buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestSerialConnection);
+};
+
+class GetPTZExpectations {
+ public:
+ GetPTZExpectations(bool expected_success, int expected_value)
+ : expected_success_(expected_success), expected_value_(expected_value) {}
+
+ void OnCallback(bool success, int value) {
+ EXPECT_EQ(expected_success_, success);
+ EXPECT_EQ(expected_value_, value);
+ }
+
+ private:
+ const bool expected_success_;
+ const int expected_value_;
+
+ DISALLOW_COPY_AND_ASSIGN(GetPTZExpectations);
+};
+
+class SetPTZExpectations {
+ public:
+ explicit SetPTZExpectations(bool expected_success)
+ : expected_success_(expected_success) {}
+
+ void OnCallback(bool success) { EXPECT_EQ(expected_success_, success); }
+
+ private:
+ const bool expected_success_;
+
+ DISALLOW_COPY_AND_ASSIGN(SetPTZExpectations);
+};
+
+#define CHAR_VECTOR_FROM_ARRAY(array) \
+ std::vector<char>(array, array + arraysize(array))
+
+} // namespace
+
+class ViscaWebcamTest : public testing::Test {
+ protected:
+ ViscaWebcamTest() {
+ webcam_ = new ViscaWebcam;
+ webcam_->OpenForTesting(make_scoped_ptr(new TestSerialConnection));
+ }
+ ~ViscaWebcamTest() override {}
+
+ Webcam* webcam() { return webcam_.get(); }
+
+ TestSerialConnection* serial_connection() {
+ return static_cast<TestSerialConnection*>(
+ webcam_->GetSerialConnectionForTesting());
+ }
+
+ private:
+ content::TestBrowserThreadBundle thread_bundle_;
+ scoped_refptr<ViscaWebcam> webcam_;
+};
+
+TEST_F(ViscaWebcamTest, Zoom) {
+ // Check getting the zoom.
+ const char kGetZoomCommand[] = {0x81, 0x09, 0x04, 0x47, 0xFF};
+ const char kGetZoomResponse[] = {0x00, 0x50, 0x01, 0x02, 0x03, 0x04, 0xFF};
+ serial_connection()->SetReceiveBuffer(
+ CHAR_VECTOR_FROM_ARRAY(kGetZoomResponse));
+ Webcam::GetPTZCompleteCallback receive_callback =
+ base::Bind(&GetPTZExpectations::OnCallback,
+ base::Owned(new GetPTZExpectations(true, 0x1234)));
+ webcam()->GetZoom(receive_callback);
+ serial_connection()->CheckSendBufferAndClear(
+ CHAR_VECTOR_FROM_ARRAY(kGetZoomCommand));
+
+ // Check setting the zoom.
+ const char kSetZoomCommand[] = {0x81, 0x01, 0x04, 0x47, 0x06,
+ 0x02, 0x05, 0x03, 0xFF};
+ // Note: this is a valid, but empty value because nothing is checking it.
+ const char kSetZoomResponse[] = {0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0xFF};
+ serial_connection()->SetReceiveBuffer(
+ CHAR_VECTOR_FROM_ARRAY(kSetZoomResponse));
+ Webcam::SetPTZCompleteCallback send_callback =
+ base::Bind(&SetPTZExpectations::OnCallback,
+ base::Owned(new SetPTZExpectations(true)));
+ serial_connection()->SetReceiveBuffer(
+ CHAR_VECTOR_FROM_ARRAY(kSetZoomResponse));
+ webcam()->SetZoom(0x6253, send_callback);
+ serial_connection()->CheckSendBufferAndClear(
+ CHAR_VECTOR_FROM_ARRAY(kSetZoomCommand));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/webcam_private/webcam.cc b/chromium/extensions/browser/api/webcam_private/webcam.cc
new file mode 100644
index 00000000000..d5bd0903739
--- /dev/null
+++ b/chromium/extensions/browser/api/webcam_private/webcam.cc
@@ -0,0 +1,33 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/webcam_private/webcam.h"
+
+namespace extensions {
+
+Webcam::Webcam() {}
+Webcam::~Webcam() {}
+
+WebcamResource::WebcamResource(const std::string& owner_extension_id,
+ Webcam* webcam,
+ const std::string& webcam_id)
+ : ApiResource(owner_extension_id), webcam_(webcam), webcam_id_(webcam_id) {
+}
+
+WebcamResource::~WebcamResource() {
+}
+
+Webcam* WebcamResource::GetWebcam() const {
+ return webcam_.get();
+}
+
+const std::string WebcamResource::GetWebcamId() const {
+ return webcam_id_;
+}
+
+bool WebcamResource::IsPersistent() const {
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/webcam_private/webcam.h b/chromium/extensions/browser/api/webcam_private/webcam.h
new file mode 100644
index 00000000000..6aba62d1c3e
--- /dev/null
+++ b/chromium/extensions/browser/api/webcam_private/webcam.h
@@ -0,0 +1,91 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_WEBCAM_PRIVATE_WEBCAM_H_
+#define EXTENSIONS_BROWSER_API_WEBCAM_PRIVATE_WEBCAM_H_
+
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/api/api_resource.h"
+
+namespace extensions {
+
+class Webcam : public base::RefCounted<Webcam> {
+ public:
+ enum PanDirection {
+ PAN_LEFT,
+ PAN_RIGHT,
+ PAN_STOP,
+ };
+
+ enum TiltDirection {
+ TILT_UP,
+ TILT_DOWN,
+ TILT_STOP,
+ };
+
+ Webcam();
+
+ using GetPTZCompleteCallback = base::Callback<void(bool, int)>;
+ using SetPTZCompleteCallback = base::Callback<void(bool)>;
+
+ virtual void GetPan(const GetPTZCompleteCallback& callback) = 0;
+ virtual void GetTilt(const GetPTZCompleteCallback& callback) = 0;
+ virtual void GetZoom(const GetPTZCompleteCallback& callback) = 0;
+ virtual void SetPan(int value,
+ int pan_speed,
+ const SetPTZCompleteCallback& callback) = 0;
+ virtual void SetTilt(int value,
+ int tilt_speed,
+ const SetPTZCompleteCallback& callback) = 0;
+ virtual void SetZoom(int value, const SetPTZCompleteCallback& callback) = 0;
+ virtual void SetPanDirection(PanDirection direction,
+ int pan_speed,
+ const SetPTZCompleteCallback& callback) = 0;
+ virtual void SetTiltDirection(TiltDirection direction,
+ int tilt_speed,
+ const SetPTZCompleteCallback& callback) = 0;
+ virtual void Reset(bool pan,
+ bool tilt,
+ bool zoom,
+ const SetPTZCompleteCallback& callback) = 0;
+
+ protected:
+ friend class base::RefCounted<Webcam>;
+ virtual ~Webcam();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Webcam);
+};
+
+class WebcamResource : public ApiResource {
+ public:
+ WebcamResource(const std::string& owner_extension_id,
+ Webcam* webcam,
+ const std::string& webcam_id);
+ ~WebcamResource() override;
+
+ static const content::BrowserThread::ID kThreadId =
+ content::BrowserThread::UI;
+
+ Webcam* GetWebcam() const;
+ const std::string GetWebcamId() const;
+
+ // ApiResource overrides.
+ bool IsPersistent() const override;
+
+ private:
+ scoped_refptr<Webcam> webcam_;
+ std::string webcam_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebcamResource);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_WEBCAM_PRIVATE_WEBCAM_H_
diff --git a/chromium/extensions/browser/api/webcam_private/webcam_private_api.h b/chromium/extensions/browser/api/webcam_private/webcam_private_api.h
new file mode 100644
index 00000000000..04efcd2eec5
--- /dev/null
+++ b/chromium/extensions/browser/api/webcam_private/webcam_private_api.h
@@ -0,0 +1,177 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_WEBCAM_PRIVATE_WEBCAM_PRIVATE_API_H_
+#define EXTENSIONS_BROWSER_API_WEBCAM_PRIVATE_WEBCAM_PRIVATE_API_H_
+
+#include <map>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/scoped_observer.h"
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/browser/api/webcam_private/webcam.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/browser/process_manager_observer.h"
+
+class Profile;
+
+namespace extensions {
+
+class ProcessManager;
+
+class WebcamPrivateAPI : public BrowserContextKeyedAPI {
+ public:
+ static BrowserContextKeyedAPIFactory<WebcamPrivateAPI>* GetFactoryInstance();
+
+ // Convenience method to get the WebcamPrivateAPI for a BrowserContext.
+ static WebcamPrivateAPI* Get(content::BrowserContext* context);
+
+ explicit WebcamPrivateAPI(content::BrowserContext* context);
+ ~WebcamPrivateAPI() override;
+
+ Webcam* GetWebcam(const std::string& extension_id,
+ const std::string& device_id);
+
+ bool OpenSerialWebcam(
+ const std::string& extension_id,
+ const std::string& device_path,
+ const base::Callback<void(const std::string&, bool)>& callback);
+ bool CloseWebcam(const std::string& extension_id,
+ const std::string& device_id);
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<WebcamPrivateAPI>;
+
+ void OnOpenSerialWebcam(
+ const std::string& extension_id,
+ const std::string& device_path,
+ scoped_refptr<Webcam> webcam,
+ const base::Callback<void(const std::string&, bool)>& callback,
+ bool success);
+
+ // Note: This function does not work for serial devices. Do not use this
+ // function for serial devices.
+ bool GetDeviceId(const std::string& extension_id,
+ const std::string& webcam_id,
+ std::string* device_id);
+ std::string GetWebcamId(const std::string& extension_id,
+ const std::string& device_id);
+
+ WebcamResource* FindWebcamResource(const std::string& extension_id,
+ const std::string& webcam_id) const;
+ bool RemoveWebcamResource(const std::string& extension_id,
+ const std::string& webcam_id);
+
+ // BrowserContextKeyedAPI:
+ static const char* service_name() {
+ return "WebcamPrivateAPI";
+ }
+ static const bool kServiceIsNULLWhileTesting = true;
+ static const bool kServiceRedirectedInIncognito = true;
+
+ content::BrowserContext* const browser_context_;
+ scoped_ptr<ApiResourceManager<WebcamResource>> webcam_resource_manager_;
+
+ base::WeakPtrFactory<WebcamPrivateAPI> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebcamPrivateAPI);
+};
+
+template <>
+void BrowserContextKeyedAPIFactory<WebcamPrivateAPI>
+ ::DeclareFactoryDependencies();
+
+class WebcamPrivateOpenSerialWebcamFunction : public AsyncExtensionFunction {
+ public:
+ WebcamPrivateOpenSerialWebcamFunction();
+ DECLARE_EXTENSION_FUNCTION("webcamPrivate.openSerialWebcam",
+ WEBCAMPRIVATE_OPENSERIALWEBCAM);
+
+ protected:
+ ~WebcamPrivateOpenSerialWebcamFunction() override;
+ bool RunAsync() override;
+
+ private:
+ void OnOpenWebcam(const std::string& webcam_id, bool success);
+
+ DISALLOW_COPY_AND_ASSIGN(WebcamPrivateOpenSerialWebcamFunction);
+};
+
+class WebcamPrivateCloseWebcamFunction : public AsyncExtensionFunction {
+ public:
+ WebcamPrivateCloseWebcamFunction();
+ DECLARE_EXTENSION_FUNCTION("webcamPrivate.closeWebcam",
+ WEBCAMPRIVATE_CLOSEWEBCAM);
+
+ protected:
+ ~WebcamPrivateCloseWebcamFunction() override;
+ bool RunAsync() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebcamPrivateCloseWebcamFunction);
+};
+
+class WebcamPrivateSetFunction : public AsyncExtensionFunction {
+ public:
+ WebcamPrivateSetFunction();
+ DECLARE_EXTENSION_FUNCTION("webcamPrivate.set", WEBCAMPRIVATE_SET);
+
+ protected:
+ ~WebcamPrivateSetFunction() override;
+ bool RunAsync() override;
+
+ private:
+ void OnSetWebcamParameters(bool success);
+
+ DISALLOW_COPY_AND_ASSIGN(WebcamPrivateSetFunction);
+};
+
+class WebcamPrivateGetFunction : public AsyncExtensionFunction {
+ public:
+ WebcamPrivateGetFunction();
+ DECLARE_EXTENSION_FUNCTION("webcamPrivate.get", WEBCAMPRIVATE_GET);
+
+ protected:
+ ~WebcamPrivateGetFunction() override;
+ bool RunAsync() override;
+
+ private:
+ enum InquiryType {
+ INQUIRY_PAN,
+ INQUIRY_TILT,
+ INQUIRY_ZOOM,
+ };
+ void OnGetWebcamParameters(InquiryType type, bool success, int value);
+
+ int pan_;
+ int tilt_;
+ int zoom_;
+ bool get_pan_;
+ bool get_tilt_;
+ bool get_zoom_;
+ bool success_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebcamPrivateGetFunction);
+};
+
+class WebcamPrivateResetFunction : public AsyncExtensionFunction {
+ public:
+ WebcamPrivateResetFunction();
+ DECLARE_EXTENSION_FUNCTION("webcamPrivate.reset", WEBCAMPRIVATE_RESET);
+
+ protected:
+ ~WebcamPrivateResetFunction() override;
+ bool RunAsync() override;
+
+ private:
+ void OnResetWebcam(bool success);
+
+ DISALLOW_COPY_AND_ASSIGN(WebcamPrivateResetFunction);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_WEBCAM_PRIVATE_WEBCAM_PRIVATE_API_H_
diff --git a/chromium/extensions/browser/api/webcam_private/webcam_private_api_chromeos.cc b/chromium/extensions/browser/api/webcam_private/webcam_private_api_chromeos.cc
new file mode 100644
index 00000000000..31e381b37f9
--- /dev/null
+++ b/chromium/extensions/browser/api/webcam_private/webcam_private_api_chromeos.cc
@@ -0,0 +1,439 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/webcam_private/webcam_private_api.h"
+
+#include "base/lazy_instance.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/media_device_id.h"
+#include "content/public/browser/resource_context.h"
+#include "extensions/browser/api/webcam_private/v4l2_webcam.h"
+#include "extensions/browser/api/webcam_private/visca_webcam.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/browser/process_manager_factory.h"
+#include "extensions/common/api/webcam_private.h"
+
+namespace webcam_private = extensions::api::webcam_private;
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+namespace {
+
+const char kPathInUse[] = "Path in use";
+const char kUnknownWebcam[] = "Unknown webcam id";
+const char kOpenSerialWebcamError[] = "Can't open serial webcam.";
+const char kGetWebcamPTZError[] = "Can't get web camera pan/tilt/zoom.";
+const char kSetWebcamPTZError[] = "Can't set web camera pan/tilt/zoom.";
+const char kResetWebcamError[] = "Can't reset web camera.";
+
+} // namespace
+
+namespace extensions {
+
+// static
+WebcamPrivateAPI* WebcamPrivateAPI::Get(content::BrowserContext* context) {
+ return GetFactoryInstance()->Get(context);
+}
+
+WebcamPrivateAPI::WebcamPrivateAPI(content::BrowserContext* context)
+ : browser_context_(context),
+ weak_ptr_factory_(this) {
+ webcam_resource_manager_.reset(
+ new ApiResourceManager<WebcamResource>(context));
+}
+
+WebcamPrivateAPI::~WebcamPrivateAPI() {
+}
+
+Webcam* WebcamPrivateAPI::GetWebcam(const std::string& extension_id,
+ const std::string& webcam_id) {
+ WebcamResource* webcam_resource = FindWebcamResource(extension_id, webcam_id);
+ if (webcam_resource)
+ return webcam_resource->GetWebcam();
+
+ std::string device_id;
+ GetDeviceId(extension_id, webcam_id, &device_id);
+ V4L2Webcam* v4l2_webcam(new V4L2Webcam(device_id));
+ if (!v4l2_webcam->Open()) {
+ return nullptr;
+ }
+
+ webcam_resource_manager_->Add(
+ new WebcamResource(extension_id, v4l2_webcam, webcam_id));
+
+ return v4l2_webcam;
+}
+
+bool WebcamPrivateAPI::OpenSerialWebcam(
+ const std::string& extension_id,
+ const std::string& device_path,
+ const base::Callback<void(const std::string&, bool)>& callback) {
+ const std::string& webcam_id = GetWebcamId(extension_id, device_path);
+ WebcamResource* webcam_resource = FindWebcamResource(extension_id, webcam_id);
+ if (webcam_resource)
+ return false;
+
+ ViscaWebcam* visca_webcam = new ViscaWebcam;
+ visca_webcam->Open(
+ device_path, extension_id,
+ base::Bind(&WebcamPrivateAPI::OnOpenSerialWebcam,
+ weak_ptr_factory_.GetWeakPtr(), extension_id, device_path,
+ make_scoped_refptr(visca_webcam), callback));
+ return true;
+}
+
+bool WebcamPrivateAPI::CloseWebcam(const std::string& extension_id,
+ const std::string& webcam_id) {
+ if (FindWebcamResource(extension_id, webcam_id)) {
+ RemoveWebcamResource(extension_id, webcam_id);
+ return true;
+ }
+ return false;
+}
+
+void WebcamPrivateAPI::OnOpenSerialWebcam(
+ const std::string& extension_id,
+ const std::string& device_path,
+ scoped_refptr<Webcam> webcam,
+ const base::Callback<void(const std::string&, bool)>& callback,
+ bool success) {
+ if (success) {
+ const std::string& webcam_id = GetWebcamId(extension_id, device_path);
+ webcam_resource_manager_->Add(
+ new WebcamResource(extension_id, webcam.get(), webcam_id));
+ callback.Run(webcam_id, true);
+ } else {
+ callback.Run("", false);
+ }
+}
+
+bool WebcamPrivateAPI::GetDeviceId(const std::string& extension_id,
+ const std::string& webcam_id,
+ std::string* device_id) {
+ GURL security_origin =
+ extensions::Extension::GetBaseURLFromExtensionId(extension_id);
+
+ return content::GetMediaDeviceIDForHMAC(
+ content::MEDIA_DEVICE_VIDEO_CAPTURE,
+ browser_context_->GetResourceContext()->GetMediaDeviceIDSalt(),
+ security_origin,
+ webcam_id,
+ device_id);
+}
+
+std::string WebcamPrivateAPI::GetWebcamId(const std::string& extension_id,
+ const std::string& device_id) {
+ GURL security_origin =
+ extensions::Extension::GetBaseURLFromExtensionId(extension_id);
+
+ return content::GetHMACForMediaDeviceID(
+ browser_context_->GetResourceContext()->GetMediaDeviceIDSalt(),
+ security_origin, device_id);
+}
+
+WebcamResource* WebcamPrivateAPI::FindWebcamResource(
+ const std::string& extension_id,
+ const std::string& webcam_id) const {
+ DCHECK(webcam_resource_manager_);
+
+ base::hash_set<int>* connection_ids =
+ webcam_resource_manager_->GetResourceIds(extension_id);
+ if (!connection_ids)
+ return nullptr;
+
+ for (const auto& connection_id : *connection_ids) {
+ WebcamResource* webcam_resource =
+ webcam_resource_manager_->Get(extension_id, connection_id);
+ if (webcam_resource && webcam_resource->GetWebcamId() == webcam_id)
+ return webcam_resource;
+ }
+
+ return nullptr;
+}
+
+bool WebcamPrivateAPI::RemoveWebcamResource(const std::string& extension_id,
+ const std::string& webcam_id) {
+ DCHECK(webcam_resource_manager_);
+
+ base::hash_set<int>* connection_ids =
+ webcam_resource_manager_->GetResourceIds(extension_id);
+ if (!connection_ids)
+ return false;
+
+ for (const auto& connection_id : *connection_ids) {
+ WebcamResource* webcam_resource =
+ webcam_resource_manager_->Get(extension_id, connection_id);
+ if (webcam_resource && webcam_resource->GetWebcamId() == webcam_id) {
+ webcam_resource_manager_->Remove(extension_id, connection_id);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+WebcamPrivateOpenSerialWebcamFunction::WebcamPrivateOpenSerialWebcamFunction() {
+}
+
+WebcamPrivateOpenSerialWebcamFunction::
+ ~WebcamPrivateOpenSerialWebcamFunction() {
+}
+
+bool WebcamPrivateOpenSerialWebcamFunction::RunAsync() {
+ scoped_ptr<webcam_private::OpenSerialWebcam::Params> params(
+ webcam_private::OpenSerialWebcam::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ if (WebcamPrivateAPI::Get(browser_context())
+ ->OpenSerialWebcam(
+ extension_id(), params->path,
+ base::Bind(&WebcamPrivateOpenSerialWebcamFunction::OnOpenWebcam,
+ this))) {
+ return true;
+ } else {
+ SetError(kPathInUse);
+ return false;
+ }
+}
+
+void WebcamPrivateOpenSerialWebcamFunction::OnOpenWebcam(
+ const std::string& webcam_id,
+ bool success) {
+ if (success) {
+ SetResult(new base::StringValue(webcam_id));
+ SendResponse(true);
+ } else {
+ SetError(kOpenSerialWebcamError);
+ SendResponse(false);
+ }
+}
+
+WebcamPrivateCloseWebcamFunction::WebcamPrivateCloseWebcamFunction() {
+}
+
+WebcamPrivateCloseWebcamFunction::~WebcamPrivateCloseWebcamFunction() {
+}
+
+bool WebcamPrivateCloseWebcamFunction::RunAsync() {
+ scoped_ptr<webcam_private::CloseWebcam::Params> params(
+ webcam_private::CloseWebcam::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ return WebcamPrivateAPI::Get(browser_context())
+ ->CloseWebcam(extension_id(), params->webcam_id);
+}
+
+WebcamPrivateSetFunction::WebcamPrivateSetFunction() {
+}
+
+WebcamPrivateSetFunction::~WebcamPrivateSetFunction() {
+}
+
+bool WebcamPrivateSetFunction::RunAsync() {
+ scoped_ptr<webcam_private::Set::Params> params(
+ webcam_private::Set::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ Webcam* webcam = WebcamPrivateAPI::Get(browser_context())
+ ->GetWebcam(extension_id(), params->webcam_id);
+ if (!webcam) {
+ SetError(kUnknownWebcam);
+ return false;
+ }
+
+ int pan_speed = 0;
+ int tilt_speed = 0;
+ if (params->config.pan_speed)
+ pan_speed = *(params->config.pan_speed);
+
+ if (params->config.tilt_speed)
+ tilt_speed = *(params->config.tilt_speed);
+
+ if (params->config.pan) {
+ webcam->SetPan(
+ *(params->config.pan), pan_speed,
+ base::Bind(&WebcamPrivateSetFunction::OnSetWebcamParameters, this));
+ }
+
+ if (params->config.pan_direction) {
+ Webcam::PanDirection direction = Webcam::PAN_STOP;
+ switch (params->config.pan_direction) {
+ case webcam_private::PAN_DIRECTION_NONE:
+ case webcam_private::PAN_DIRECTION_STOP:
+ direction = Webcam::PAN_STOP;
+ break;
+
+ case webcam_private::PAN_DIRECTION_RIGHT:
+ direction = Webcam::PAN_RIGHT;
+ break;
+
+ case webcam_private::PAN_DIRECTION_LEFT:
+ direction = Webcam::PAN_LEFT;
+ break;
+ }
+ webcam->SetPanDirection(
+ direction, pan_speed,
+ base::Bind(&WebcamPrivateSetFunction::OnSetWebcamParameters, this));
+ }
+
+ if (params->config.tilt) {
+ webcam->SetTilt(
+ *(params->config.tilt), tilt_speed,
+ base::Bind(&WebcamPrivateSetFunction::OnSetWebcamParameters, this));
+ }
+
+ if (params->config.tilt_direction) {
+ Webcam::TiltDirection direction = Webcam::TILT_STOP;
+ switch (params->config.tilt_direction) {
+ case webcam_private::TILT_DIRECTION_NONE:
+ case webcam_private::TILT_DIRECTION_STOP:
+ direction = Webcam::TILT_STOP;
+ break;
+
+ case webcam_private::TILT_DIRECTION_UP:
+ direction = Webcam::TILT_UP;
+ break;
+
+ case webcam_private::TILT_DIRECTION_DOWN:
+ direction = Webcam::TILT_DOWN;
+ break;
+ }
+ webcam->SetTiltDirection(
+ direction, tilt_speed,
+ base::Bind(&WebcamPrivateSetFunction::OnSetWebcamParameters, this));
+ }
+
+ if (params->config.zoom) {
+ webcam->SetZoom(
+ *(params->config.zoom),
+ base::Bind(&WebcamPrivateSetFunction::OnSetWebcamParameters, this));
+ }
+
+ return true;
+}
+
+void WebcamPrivateSetFunction::OnSetWebcamParameters(bool success) {
+ if (!success)
+ SetError(kSetWebcamPTZError);
+}
+
+WebcamPrivateGetFunction::WebcamPrivateGetFunction()
+ : pan_(0),
+ tilt_(0),
+ zoom_(0),
+ get_pan_(false),
+ get_tilt_(false),
+ get_zoom_(false),
+ success_(true) {}
+
+WebcamPrivateGetFunction::~WebcamPrivateGetFunction() {
+}
+
+bool WebcamPrivateGetFunction::RunAsync() {
+ scoped_ptr<webcam_private::Get::Params> params(
+ webcam_private::Get::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ Webcam* webcam = WebcamPrivateAPI::Get(browser_context())
+ ->GetWebcam(extension_id(), params->webcam_id);
+ if (!webcam) {
+ SetError(kUnknownWebcam);
+ return false;
+ }
+
+ webcam->GetPan(base::Bind(&WebcamPrivateGetFunction::OnGetWebcamParameters,
+ this, INQUIRY_PAN));
+ webcam->GetTilt(base::Bind(&WebcamPrivateGetFunction::OnGetWebcamParameters,
+ this, INQUIRY_TILT));
+ webcam->GetZoom(base::Bind(&WebcamPrivateGetFunction::OnGetWebcamParameters,
+ this, INQUIRY_ZOOM));
+
+ return true;
+}
+
+void WebcamPrivateGetFunction::OnGetWebcamParameters(InquiryType type,
+ bool success,
+ int value) {
+ if (!success_)
+ return;
+ success_ = success_ && success;
+
+ if (!success_) {
+ SetError(kGetWebcamPTZError);
+ SendResponse(false);
+ } else {
+ switch (type) {
+ case INQUIRY_PAN:
+ pan_ = value;
+ get_pan_ = true;
+ break;
+ case INQUIRY_TILT:
+ tilt_ = value;
+ get_tilt_ = true;
+ break;
+ case INQUIRY_ZOOM:
+ zoom_ = value;
+ get_zoom_ = true;
+ break;
+ }
+ if (get_pan_ && get_tilt_ && get_zoom_) {
+ webcam_private::WebcamConfiguration result;
+ result.pan.reset(new double(pan_));
+ result.tilt.reset(new double(tilt_));
+ result.zoom.reset(new double(zoom_));
+ SetResult(result.ToValue().release());
+ SendResponse(true);
+ }
+ }
+}
+
+WebcamPrivateResetFunction::WebcamPrivateResetFunction() {
+}
+
+WebcamPrivateResetFunction::~WebcamPrivateResetFunction() {
+}
+
+bool WebcamPrivateResetFunction::RunAsync() {
+ scoped_ptr<webcam_private::Reset::Params> params(
+ webcam_private::Reset::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ Webcam* webcam = WebcamPrivateAPI::Get(browser_context())
+ ->GetWebcam(extension_id(), params->webcam_id);
+ if (!webcam) {
+ SetError(kUnknownWebcam);
+ return false;
+ }
+
+ webcam->Reset(params->config.pan != nullptr, params->config.tilt != nullptr,
+ params->config.zoom != nullptr,
+ base::Bind(&WebcamPrivateResetFunction::OnResetWebcam, this));
+
+ return true;
+}
+
+void WebcamPrivateResetFunction::OnResetWebcam(bool success) {
+ if (!success)
+ SetError(kResetWebcamError);
+}
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<WebcamPrivateAPI>>
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<WebcamPrivateAPI>*
+WebcamPrivateAPI::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+template <>
+void BrowserContextKeyedAPIFactory<WebcamPrivateAPI>
+ ::DeclareFactoryDependencies() {
+ DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
+ DependsOn(ProcessManagerFactory::GetInstance());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api_activity_monitor.h b/chromium/extensions/browser/api_activity_monitor.h
new file mode 100644
index 00000000000..b8f0cc32c40
--- /dev/null
+++ b/chromium/extensions/browser/api_activity_monitor.h
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_ACTIVITY_MONITOR_H_
+#define EXTENSIONS_BROWSER_API_ACTIVITY_MONITOR_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+
+namespace base {
+class ListValue;
+}
+
+namespace extensions {
+
+// ApiActivityMonitor is used to monitor extension API event dispatch and API
+// function calls. An embedder can use this interface to log low-level extension
+// activity.
+class ApiActivityMonitor {
+ public:
+ // Called when an API event is dispatched to an extension.
+ virtual void OnApiEventDispatched(const std::string& extension_id,
+ const std::string& event_name,
+ scoped_ptr<base::ListValue> event_args) = 0;
+
+ // Called when an extension calls an API function.
+ virtual void OnApiFunctionCalled(const std::string& extension_id,
+ const std::string& api_name,
+ scoped_ptr<base::ListValue> args) = 0;
+
+ protected:
+ virtual ~ApiActivityMonitor() {}
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_ACTIVITY_MONITOR_H_
diff --git a/chromium/extensions/browser/api_test_utils.cc b/chromium/extensions/browser/api_test_utils.cc
new file mode 100644
index 00000000000..7d5362423eb
--- /dev/null
+++ b/chromium/extensions/browser/api_test_utils.cc
@@ -0,0 +1,246 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api_test_utils.h"
+
+#include <utility>
+
+#include "base/json/json_reader.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "components/crx_file/id_util.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/test/test_utils.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/browser/extension_function_dispatcher.h"
+#include "extensions/common/extension_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::ExtensionFunctionDispatcher;
+
+namespace {
+
+scoped_ptr<base::Value> ParseJSON(const std::string& data) {
+ return base::JSONReader::Read(data);
+}
+
+scoped_ptr<base::ListValue> ParseList(const std::string& data) {
+ return base::ListValue::From(ParseJSON(data));
+}
+
+// This helps us be able to wait until an UIThreadExtensionFunction calls
+// SendResponse.
+class SendResponseDelegate
+ : public UIThreadExtensionFunction::DelegateForTests {
+ public:
+ SendResponseDelegate() : should_post_quit_(false) {}
+
+ virtual ~SendResponseDelegate() {}
+
+ void set_should_post_quit(bool should_quit) {
+ should_post_quit_ = should_quit;
+ }
+
+ bool HasResponse() { return response_.get() != NULL; }
+
+ bool GetResponse() {
+ EXPECT_TRUE(HasResponse());
+ return *response_.get();
+ }
+
+ void OnSendResponse(UIThreadExtensionFunction* function,
+ bool success,
+ bool bad_message) override {
+ ASSERT_FALSE(bad_message);
+ ASSERT_FALSE(HasResponse());
+ response_.reset(new bool);
+ *response_ = success;
+ if (should_post_quit_) {
+ base::MessageLoopForUI::current()->QuitWhenIdle();
+ }
+ }
+
+ private:
+ scoped_ptr<bool> response_;
+ bool should_post_quit_;
+};
+
+} // namespace
+
+namespace extensions {
+
+namespace api_test_utils {
+
+scoped_ptr<base::DictionaryValue> ParseDictionary(const std::string& data) {
+ return base::DictionaryValue::From(ParseJSON(data));
+}
+
+bool GetBoolean(const base::DictionaryValue* val, const std::string& key) {
+ bool result = false;
+ if (!val->GetBoolean(key, &result))
+ ADD_FAILURE() << key << " does not exist or is not a boolean.";
+ return result;
+}
+
+int GetInteger(const base::DictionaryValue* val, const std::string& key) {
+ int result = 0;
+ if (!val->GetInteger(key, &result))
+ ADD_FAILURE() << key << " does not exist or is not an integer.";
+ return result;
+}
+
+std::string GetString(const base::DictionaryValue* val,
+ const std::string& key) {
+ std::string result;
+ if (!val->GetString(key, &result))
+ ADD_FAILURE() << key << " does not exist or is not a string.";
+ return result;
+}
+
+scoped_refptr<Extension> CreateExtension(
+ Manifest::Location location,
+ base::DictionaryValue* test_extension_value,
+ const std::string& id_input) {
+ std::string error;
+ const base::FilePath test_extension_path;
+ std::string id;
+ if (!id_input.empty())
+ id = crx_file::id_util::GenerateId(id_input);
+ scoped_refptr<Extension> extension(
+ Extension::Create(test_extension_path, location, *test_extension_value,
+ Extension::NO_FLAGS, id, &error));
+ EXPECT_TRUE(error.empty()) << "Could not parse test extension " << error;
+ return extension;
+}
+
+scoped_refptr<Extension> CreateExtension(
+ base::DictionaryValue* test_extension_value) {
+ return CreateExtension(Manifest::INTERNAL, test_extension_value,
+ std::string());
+}
+
+scoped_refptr<Extension> CreateEmptyExtensionWithLocation(
+ Manifest::Location location) {
+ scoped_ptr<base::DictionaryValue> test_extension_value =
+ ParseDictionary("{\"name\": \"Test\", \"version\": \"1.0\"}");
+ return CreateExtension(location, test_extension_value.get(), std::string());
+}
+
+base::Value* RunFunctionWithDelegateAndReturnSingleResult(
+ UIThreadExtensionFunction* function,
+ const std::string& args,
+ content::BrowserContext* context,
+ scoped_ptr<extensions::ExtensionFunctionDispatcher> dispatcher) {
+ return RunFunctionWithDelegateAndReturnSingleResult(
+ function, args, context, std::move(dispatcher), NONE);
+}
+
+base::Value* RunFunctionWithDelegateAndReturnSingleResult(
+ UIThreadExtensionFunction* function,
+ const std::string& args,
+ content::BrowserContext* context,
+ scoped_ptr<extensions::ExtensionFunctionDispatcher> dispatcher,
+ RunFunctionFlags flags) {
+ scoped_refptr<ExtensionFunction> function_owner(function);
+ // Without a callback the function will not generate a result.
+ function->set_has_callback(true);
+ RunFunction(function, args, context, std::move(dispatcher), flags);
+ EXPECT_TRUE(function->GetError().empty())
+ << "Unexpected error: " << function->GetError();
+ const base::Value* single_result = NULL;
+ if (function->GetResultList() != NULL &&
+ function->GetResultList()->Get(0, &single_result)) {
+ return single_result->DeepCopy();
+ }
+ return NULL;
+}
+
+base::Value* RunFunctionAndReturnSingleResult(
+ UIThreadExtensionFunction* function,
+ const std::string& args,
+ content::BrowserContext* context) {
+ return RunFunctionAndReturnSingleResult(function, args, context, NONE);
+}
+
+base::Value* RunFunctionAndReturnSingleResult(
+ UIThreadExtensionFunction* function,
+ const std::string& args,
+ content::BrowserContext* context,
+ RunFunctionFlags flags) {
+ scoped_ptr<ExtensionFunctionDispatcher> dispatcher(
+ new ExtensionFunctionDispatcher(context));
+
+ return RunFunctionWithDelegateAndReturnSingleResult(
+ function, args, context, std::move(dispatcher), flags);
+}
+
+std::string RunFunctionAndReturnError(UIThreadExtensionFunction* function,
+ const std::string& args,
+ content::BrowserContext* context) {
+ return RunFunctionAndReturnError(function, args, context, NONE);
+}
+
+std::string RunFunctionAndReturnError(UIThreadExtensionFunction* function,
+ const std::string& args,
+ content::BrowserContext* context,
+ RunFunctionFlags flags) {
+ scoped_ptr<ExtensionFunctionDispatcher> dispatcher(
+ new ExtensionFunctionDispatcher(context));
+ scoped_refptr<ExtensionFunction> function_owner(function);
+ // Without a callback the function will not generate a result.
+ function->set_has_callback(true);
+ RunFunction(function, args, context, std::move(dispatcher), flags);
+ EXPECT_FALSE(function->GetResultList()) << "Did not expect a result";
+ return function->GetError();
+}
+
+bool RunFunction(UIThreadExtensionFunction* function,
+ const std::string& args,
+ content::BrowserContext* context) {
+ scoped_ptr<ExtensionFunctionDispatcher> dispatcher(
+ new ExtensionFunctionDispatcher(context));
+ return RunFunction(function, args, context, std::move(dispatcher), NONE);
+}
+
+bool RunFunction(UIThreadExtensionFunction* function,
+ const std::string& args,
+ content::BrowserContext* context,
+ scoped_ptr<extensions::ExtensionFunctionDispatcher> dispatcher,
+ RunFunctionFlags flags) {
+ scoped_ptr<base::ListValue> parsed_args = ParseList(args);
+ EXPECT_TRUE(parsed_args.get())
+ << "Could not parse extension function arguments: " << args;
+ return RunFunction(function, std::move(parsed_args), context,
+ std::move(dispatcher), flags);
+}
+
+bool RunFunction(UIThreadExtensionFunction* function,
+ scoped_ptr<base::ListValue> args,
+ content::BrowserContext* context,
+ scoped_ptr<extensions::ExtensionFunctionDispatcher> dispatcher,
+ RunFunctionFlags flags) {
+ SendResponseDelegate response_delegate;
+ function->set_test_delegate(&response_delegate);
+ function->SetArgs(args.get());
+
+ CHECK(dispatcher);
+ function->set_dispatcher(dispatcher->AsWeakPtr());
+
+ function->set_browser_context(context);
+ function->set_include_incognito(flags & INCLUDE_INCOGNITO);
+ function->Run()->Execute();
+
+ // If the RunAsync of |function| didn't already call SendResponse, run the
+ // message loop until they do.
+ if (!response_delegate.HasResponse()) {
+ response_delegate.set_should_post_quit(true);
+ content::RunMessageLoop();
+ }
+
+ EXPECT_TRUE(response_delegate.HasResponse());
+ return response_delegate.GetResponse();
+}
+
+} // namespace api_test_utils
+} // namespace extensions
diff --git a/chromium/extensions/browser/api_test_utils.h b/chromium/extensions/browser/api_test_utils.h
new file mode 100644
index 00000000000..0a74f82df74
--- /dev/null
+++ b/chromium/extensions/browser/api_test_utils.h
@@ -0,0 +1,130 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_TEST_UTILS_H_
+#define EXTENSIONS_BROWSER_API_TEST_UTILS_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/manifest.h"
+
+class UIThreadExtensionFunction;
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+class Value;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+class Extension;
+class ExtensionFunctionDispatcher;
+
+// TODO(yoz): crbug.com/394840: Remove duplicate functionality in
+// chrome/browser/extensions/extension_function_test_utils.h.
+//
+// TODO(ckehoe): Accept args as scoped_ptr<base::Value>,
+// and migrate existing users to the new API.
+namespace api_test_utils {
+
+enum RunFunctionFlags { NONE = 0, INCLUDE_INCOGNITO = 1 << 0 };
+
+// Parse JSON and return as the specified type, or NULL if the JSON is invalid
+// or not the specified type.
+scoped_ptr<base::DictionaryValue> ParseDictionary(const std::string& data);
+
+// Get |key| from |val| as the specified type. If |key| does not exist, or is
+// not of the specified type, adds a failure to the current test and returns
+// false, 0, empty string, etc.
+bool GetBoolean(const base::DictionaryValue* val, const std::string& key);
+int GetInteger(const base::DictionaryValue* val, const std::string& key);
+std::string GetString(const base::DictionaryValue* val, const std::string& key);
+
+// Creates an extension instance with a specified extension value that can be
+// attached to an ExtensionFunction before running.
+scoped_refptr<extensions::Extension> CreateExtension(
+ base::DictionaryValue* test_extension_value);
+
+scoped_refptr<extensions::Extension> CreateExtension(
+ extensions::Manifest::Location location,
+ base::DictionaryValue* test_extension_value,
+ const std::string& id_input);
+
+// Creates an extension instance with a specified location that can be attached
+// to an ExtensionFunction before running.
+scoped_refptr<extensions::Extension> CreateEmptyExtensionWithLocation(
+ extensions::Manifest::Location location);
+
+// Run |function| with |args| and return the result. Adds an error to the
+// current test if |function| returns an error. Takes ownership of
+// |function|. The caller takes ownership of the result.
+base::Value* RunFunctionWithDelegateAndReturnSingleResult(
+ UIThreadExtensionFunction* function,
+ const std::string& args,
+ content::BrowserContext* context,
+ scoped_ptr<ExtensionFunctionDispatcher> dispatcher);
+base::Value* RunFunctionWithDelegateAndReturnSingleResult(
+ UIThreadExtensionFunction* function,
+ const std::string& args,
+ content::BrowserContext* context,
+ scoped_ptr<ExtensionFunctionDispatcher> dispatcher,
+ RunFunctionFlags flags);
+
+// RunFunctionWithDelegateAndReturnSingleResult, except with a NULL
+// implementation of the Delegate.
+base::Value* RunFunctionAndReturnSingleResult(
+ UIThreadExtensionFunction* function,
+ const std::string& args,
+ content::BrowserContext* context);
+base::Value* RunFunctionAndReturnSingleResult(
+ UIThreadExtensionFunction* function,
+ const std::string& args,
+ content::BrowserContext* context,
+ RunFunctionFlags flags);
+
+// Run |function| with |args| and return the resulting error. Adds an error to
+// the current test if |function| returns a result. Takes ownership of
+// |function|.
+std::string RunFunctionAndReturnError(UIThreadExtensionFunction* function,
+ const std::string& args,
+ content::BrowserContext* context,
+ RunFunctionFlags flags);
+std::string RunFunctionAndReturnError(UIThreadExtensionFunction* function,
+ const std::string& args,
+ content::BrowserContext* context);
+
+// Create and run |function| with |args|. Works with both synchronous and async
+// functions. Ownership of |function| remains with the caller.
+//
+// TODO(aa): It would be nice if |args| could be validated against the schema
+// that |function| expects. That way, we know that we are testing something
+// close to what the bindings would actually send.
+//
+// TODO(aa): I'm concerned that this style won't scale to all the bits and bobs
+// we're going to need to frob for all the different extension functions. But
+// we can refactor when we see what is needed.
+bool RunFunction(UIThreadExtensionFunction* function,
+ const std::string& args,
+ content::BrowserContext* context);
+bool RunFunction(UIThreadExtensionFunction* function,
+ const std::string& args,
+ content::BrowserContext* context,
+ scoped_ptr<ExtensionFunctionDispatcher> dispatcher,
+ RunFunctionFlags flags);
+bool RunFunction(UIThreadExtensionFunction* function,
+ scoped_ptr<base::ListValue> args,
+ content::BrowserContext* context,
+ scoped_ptr<ExtensionFunctionDispatcher> dispatcher,
+ RunFunctionFlags flags);
+
+} // namespace api_test_utils
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_TEST_UTILS_H_
diff --git a/chromium/extensions/browser/api_unittest.cc b/chromium/extensions/browser/api_unittest.cc
new file mode 100644
index 00000000000..ada00c01506
--- /dev/null
+++ b/chromium/extensions/browser/api_unittest.cc
@@ -0,0 +1,122 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api_unittest.h"
+
+#include "base/values.h"
+#include "components/user_prefs/user_prefs.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/site_instance.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "content/public/test/web_contents_tester.h"
+#include "extensions/browser/api_test_utils.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/browser/test_extensions_browser_client.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_handlers/background_info.h"
+#include "extensions/common/value_builder.h"
+
+namespace utils = extensions::api_test_utils;
+
+namespace extensions {
+
+ApiUnitTest::ApiUnitTest()
+ : notification_service_(content::NotificationService::Create()) {
+}
+
+ApiUnitTest::~ApiUnitTest() {
+}
+
+void ApiUnitTest::SetUp() {
+ ExtensionsTest::SetUp();
+
+ thread_bundle_.reset(new content::TestBrowserThreadBundle(
+ content::TestBrowserThreadBundle::DEFAULT));
+ user_prefs::UserPrefs::Set(browser_context(), &testing_pref_service_);
+
+ extension_ = ExtensionBuilder()
+ .SetManifest(DictionaryBuilder()
+ .Set("name", "Test")
+ .Set("version", "1.0")
+ .Build())
+ .SetLocation(Manifest::UNPACKED)
+ .Build();
+}
+
+void ApiUnitTest::CreateBackgroundPage() {
+ if (!contents_) {
+ GURL url = BackgroundInfo::GetBackgroundURL(extension());
+ if (url.is_empty())
+ url = GURL(url::kAboutBlankURL);
+ contents_.reset(
+ content::WebContents::Create(content::WebContents::CreateParams(
+ browser_context(),
+ content::SiteInstance::CreateForURL(browser_context(), url))));
+ }
+}
+
+scoped_ptr<base::Value> ApiUnitTest::RunFunctionAndReturnValue(
+ UIThreadExtensionFunction* function,
+ const std::string& args) {
+ function->set_extension(extension());
+ if (contents_)
+ function->SetRenderFrameHost(contents_->GetMainFrame());
+ return scoped_ptr<base::Value>(utils::RunFunctionAndReturnSingleResult(
+ function, args, browser_context()));
+}
+
+scoped_ptr<base::DictionaryValue> ApiUnitTest::RunFunctionAndReturnDictionary(
+ UIThreadExtensionFunction* function,
+ const std::string& args) {
+ base::Value* value = RunFunctionAndReturnValue(function, args).release();
+ base::DictionaryValue* dict = NULL;
+
+ if (value && !value->GetAsDictionary(&dict))
+ delete value;
+
+ // We expect to either have successfuly retrieved a dictionary from the value,
+ // or the value to have been NULL.
+ EXPECT_TRUE(dict || !value);
+ return scoped_ptr<base::DictionaryValue>(dict);
+}
+
+scoped_ptr<base::ListValue> ApiUnitTest::RunFunctionAndReturnList(
+ UIThreadExtensionFunction* function,
+ const std::string& args) {
+ base::Value* value = RunFunctionAndReturnValue(function, args).release();
+ base::ListValue* list = NULL;
+
+ if (value && !value->GetAsList(&list))
+ delete value;
+
+ // We expect to either have successfuly retrieved a list from the value,
+ // or the value to have been NULL.
+ EXPECT_TRUE(list || !value);
+ return scoped_ptr<base::ListValue>(list);
+}
+
+std::string ApiUnitTest::RunFunctionAndReturnError(
+ UIThreadExtensionFunction* function,
+ const std::string& args) {
+ function->set_extension(extension());
+ if (contents_)
+ function->SetRenderFrameHost(contents_->GetMainFrame());
+ return utils::RunFunctionAndReturnError(function, args, browser_context());
+}
+
+void ApiUnitTest::RunFunction(UIThreadExtensionFunction* function,
+ const std::string& args) {
+ RunFunctionAndReturnValue(function, args);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api_unittest.h b/chromium/extensions/browser/api_unittest.h
new file mode 100644
index 00000000000..4e31c32fe88
--- /dev/null
+++ b/chromium/extensions/browser/api_unittest.h
@@ -0,0 +1,104 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_UNITTEST_H_
+#define EXTENSIONS_BROWSER_API_UNITTEST_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/pref_registry/testing_pref_service_syncable.h"
+#include "extensions/browser/extensions_test.h"
+
+namespace base {
+class Value;
+class DictionaryValue;
+class ListValue;
+}
+
+namespace content {
+class NotificationService;
+class TestBrowserThreadBundle;
+class WebContents;
+}
+
+class UIThreadExtensionFunction;
+
+namespace extensions {
+
+// Use this class to enable calling API functions in a unittest.
+// By default, this class will create and load an empty unpacked |extension_|,
+// which will be used in all API function calls. This extension can be
+// overridden using set_extension().
+// When calling RunFunction[AndReturn*], |args| should be in JSON format,
+// wrapped in a list. See also RunFunction* in api_test_utils.h.
+class ApiUnitTest : public ExtensionsTest {
+ public:
+ ApiUnitTest();
+ ~ApiUnitTest() override;
+
+ content::WebContents* contents() { return contents_.get(); }
+ const Extension* extension() const { return extension_.get(); }
+ scoped_refptr<Extension> extension_ref() { return extension_; }
+ void set_extension(scoped_refptr<Extension> extension) {
+ extension_ = extension;
+ }
+
+ protected:
+ // SetUp creates and loads an empty, unpacked Extension.
+ void SetUp() override;
+
+ // Creates a background page for |extension_|, and sets it for the WebContents
+ // to be used in API calls.
+ // If |contents_| is already set, this does nothing.
+ void CreateBackgroundPage();
+
+ // Various ways of running an API function. These methods take ownership of
+ // |function|. |args| should be in JSON format, wrapped in a list.
+ // See also the RunFunction* methods in extension_function_test_utils.h.
+
+ // Return the function result as a base::Value.
+ scoped_ptr<base::Value> RunFunctionAndReturnValue(
+ UIThreadExtensionFunction* function,
+ const std::string& args);
+
+ // Return the function result as a base::DictionaryValue, or NULL.
+ // This will EXPECT-fail if the result is not a DictionaryValue.
+ scoped_ptr<base::DictionaryValue> RunFunctionAndReturnDictionary(
+ UIThreadExtensionFunction* function,
+ const std::string& args);
+
+ // Return the function result as a base::ListValue, or NULL.
+ // This will EXPECT-fail if the result is not a ListValue.
+ scoped_ptr<base::ListValue> RunFunctionAndReturnList(
+ UIThreadExtensionFunction* function,
+ const std::string& args);
+
+ // Return an error thrown from the function, if one exists.
+ // This will EXPECT-fail if any result is returned from the function.
+ std::string RunFunctionAndReturnError(UIThreadExtensionFunction* function,
+ const std::string& args);
+
+ // Run the function and ignore any result.
+ void RunFunction(UIThreadExtensionFunction* function,
+ const std::string& args);
+
+ private:
+ scoped_ptr<content::NotificationService> notification_service_;
+
+ scoped_ptr<content::TestBrowserThreadBundle> thread_bundle_;
+ user_prefs::TestingPrefServiceSyncable testing_pref_service_;
+
+ // The WebContents used to associate a RenderViewHost with API function calls,
+ // or null.
+ scoped_ptr<content::WebContents> contents_;
+
+ // The Extension used when running API function calls.
+ scoped_refptr<Extension> extension_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_UNITTEST_H_
diff --git a/chromium/extensions/browser/app_sorting.h b/chromium/extensions/browser/app_sorting.h
new file mode 100644
index 00000000000..a819e997a3e
--- /dev/null
+++ b/chromium/extensions/browser/app_sorting.h
@@ -0,0 +1,112 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_APP_SORTING_H_
+#define EXTENSIONS_BROWSER_APP_SORTING_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "extensions/common/extension.h"
+#include "sync/api/string_ordinal.h"
+
+namespace extensions {
+
+class ExtensionScopedPrefs;
+
+// An interface that provides a fixed ordering for a set of apps.
+class AppSorting {
+ public:
+ AppSorting() {}
+ virtual ~AppSorting() {}
+
+ // Resolves any conflicts the might be created as a result of syncing that
+ // results in two icons having the same page and app launch ordinal. After
+ // this is called it is guaranteed that there are no collisions of NTP icons.
+ virtual void FixNTPOrdinalCollisions() = 0;
+
+ // This ensures that the extension has valid ordinals, and if it doesn't then
+ // properly initialize them. |suggested_page| will be used if it is valid and
+ // the extension has no valid user-set page ordinal.
+ virtual void EnsureValidOrdinals(
+ const std::string& extension_id,
+ const syncer::StringOrdinal& suggested_page) = 0;
+
+ // Updates the app launcher value for the moved extension so that it is now
+ // located after the given predecessor and before the successor.
+ // Empty strings are used to indicate no successor or predecessor.
+ virtual void OnExtensionMoved(const std::string& moved_extension_id,
+ const std::string& predecessor_extension_id,
+ const std::string& successor_extension_id) = 0;
+
+ // Get the application launch ordinal for an app with |extension_id|. This
+ // determines the order in which the app appears on the page it's on in the
+ // New Tab Page (Note that you can compare app launch ordinals only if the
+ // apps are on the same page). A string value close to |a*| generally
+ // indicates top left. If the extension has no launch ordinal, an invalid
+ // StringOrdinal is returned.
+ virtual syncer::StringOrdinal GetAppLaunchOrdinal(
+ const std::string& extension_id) const = 0;
+
+ // Sets a specific launch ordinal for an app with |extension_id|.
+ virtual void SetAppLaunchOrdinal(
+ const std::string& extension_id,
+ const syncer::StringOrdinal& new_app_launch_ordinal) = 0;
+
+ // Returns a StringOrdinal that is lower than any app launch ordinal for the
+ // given page.
+ virtual syncer::StringOrdinal CreateFirstAppLaunchOrdinal(
+ const syncer::StringOrdinal& page_ordinal) const = 0;
+
+ // Returns a StringOrdinal that is higher than any app launch ordinal for the
+ // given page.
+ virtual syncer::StringOrdinal CreateNextAppLaunchOrdinal(
+ const syncer::StringOrdinal& page_ordinal) const = 0;
+
+ // Returns a StringOrdinal that is lower than any existing page ordinal.
+ virtual syncer::StringOrdinal CreateFirstAppPageOrdinal() const = 0;
+
+ // Gets the page a new app should install to, which is the earliest non-full
+ // page. The returned ordinal may correspond to a page that doesn't yet exist
+ // if all pages are full.
+ virtual syncer::StringOrdinal GetNaturalAppPageOrdinal() const = 0;
+
+ // Get the page ordinal for an app with |extension_id|. This determines
+ // which page an app will appear on in page-based NTPs. If the app has no
+ // page specified, an invalid StringOrdinal is returned.
+ virtual syncer::StringOrdinal GetPageOrdinal(
+ const std::string& extension_id) const = 0;
+
+ // Sets a specific page ordinal for an app with |extension_id|.
+ virtual void SetPageOrdinal(
+ const std::string& extension_id,
+ const syncer::StringOrdinal& new_page_ordinal) = 0;
+
+ // Removes the ordinal values for an app.
+ virtual void ClearOrdinals(const std::string& extension_id) = 0;
+
+ // Convert the page StringOrdinal value to its integer equivalent. This takes
+ // O(# of apps) worst-case.
+ virtual int PageStringOrdinalAsInteger(
+ const syncer::StringOrdinal& page_ordinal) const = 0;
+
+ // Converts the page index integer to its StringOrdinal equivalent. This takes
+ // O(# of apps) worst-case.
+ virtual syncer::StringOrdinal PageIntegerAsStringOrdinal(
+ size_t page_index) = 0;
+
+ // Hides an extension from the new tab page, or makes a previously hidden
+ // extension visible.
+ virtual void SetExtensionVisible(const std::string& extension_id,
+ bool visible) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AppSorting);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_APP_SORTING_H_
diff --git a/chromium/extensions/browser/app_window/app_delegate.h b/chromium/extensions/browser/app_window/app_delegate.h
new file mode 100644
index 00000000000..158b4469ed3
--- /dev/null
+++ b/chromium/extensions/browser/app_window/app_delegate.h
@@ -0,0 +1,89 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_APP_WINDOW_APP_DELEGATE_H_
+#define EXTENSIONS_BROWSER_APP_WINDOW_APP_DELEGATE_H_
+
+#include "base/callback_forward.h"
+#include "content/public/common/media_stream_request.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/window_open_disposition.h"
+
+namespace content {
+class BrowserContext;
+class ColorChooser;
+struct FileChooserParams;
+struct OpenURLParams;
+class RenderViewHost;
+class WebContents;
+}
+
+namespace gfx {
+class Rect;
+class Size;
+}
+
+namespace extensions {
+
+class Extension;
+
+// Interface to give packaged apps access to services in the browser, for things
+// like handling links and showing UI prompts to the user.
+class AppDelegate {
+ public:
+ virtual ~AppDelegate() {}
+
+ // General initialization.
+ virtual void InitWebContents(content::WebContents* web_contents) = 0;
+ virtual void RenderViewCreated(content::RenderViewHost* render_view_host) = 0;
+
+ // Resizes WebContents.
+ virtual void ResizeWebContents(content::WebContents* web_contents,
+ const gfx::Size& size) = 0;
+
+ // Link handling.
+ virtual content::WebContents* OpenURLFromTab(
+ content::BrowserContext* context,
+ content::WebContents* source,
+ const content::OpenURLParams& params) = 0;
+ virtual void AddNewContents(content::BrowserContext* context,
+ content::WebContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_rect,
+ bool user_gesture,
+ bool* was_blocked) = 0;
+
+ // Feature support.
+ virtual content::ColorChooser* ShowColorChooser(
+ content::WebContents* web_contents,
+ SkColor initial_color) = 0;
+ virtual void RunFileChooser(content::WebContents* tab,
+ const content::FileChooserParams& params) = 0;
+ virtual void RequestMediaAccessPermission(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback,
+ const Extension* extension) = 0;
+ virtual bool CheckMediaAccessPermission(content::WebContents* web_contents,
+ const GURL& security_origin,
+ content::MediaStreamType type,
+ const Extension* extension) = 0;
+ virtual int PreferredIconSize() = 0;
+
+ // Web contents modal dialog support.
+ virtual void SetWebContentsBlocked(content::WebContents* web_contents,
+ bool blocked) = 0;
+ virtual bool IsWebContentsVisible(content::WebContents* web_contents) = 0;
+
+ // |callback| will be called when the process is about to terminate.
+ virtual void SetTerminatingCallback(const base::Closure& callback) = 0;
+
+ // Called when the app is hidden or shown.
+ virtual void OnHide() = 0;
+ virtual void OnShow() = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_APP_WINDOW_APP_DELEGATE_H_
diff --git a/chromium/extensions/browser/app_window/app_web_contents_helper.cc b/chromium/extensions/browser/app_window/app_web_contents_helper.cc
new file mode 100644
index 00000000000..72b1376af4b
--- /dev/null
+++ b/chromium/extensions/browser/app_window/app_web_contents_helper.cc
@@ -0,0 +1,115 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/app_window/app_web_contents_helper.h"
+
+#include "base/strings/stringprintf.h"
+#include "content/public/browser/native_web_keyboard_event.h"
+#include "content/public/browser/page_navigator.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/console_message_level.h"
+#include "extensions/browser/app_window/app_delegate.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/suggest_permission_util.h"
+#include "extensions/common/permissions/api_permission.h"
+
+namespace extensions {
+
+AppWebContentsHelper::AppWebContentsHelper(
+ content::BrowserContext* browser_context,
+ const std::string& extension_id,
+ content::WebContents* web_contents,
+ AppDelegate* app_delegate)
+ : browser_context_(browser_context),
+ extension_id_(extension_id),
+ web_contents_(web_contents),
+ app_delegate_(app_delegate) {
+}
+
+// static
+bool AppWebContentsHelper::ShouldSuppressGestureEvent(
+ const blink::WebGestureEvent& event) {
+ // Disable pinch zooming in app windows.
+ return event.type == blink::WebGestureEvent::GesturePinchBegin ||
+ event.type == blink::WebGestureEvent::GesturePinchUpdate ||
+ event.type == blink::WebGestureEvent::GesturePinchEnd;
+}
+
+content::WebContents* AppWebContentsHelper::OpenURLFromTab(
+ const content::OpenURLParams& params) const {
+ // Don't allow the current tab to be navigated. It would be nice to map all
+ // anchor tags (even those without target="_blank") to new tabs, but right
+ // now we can't distinguish between those and <meta> refreshes or window.href
+ // navigations, which we don't want to allow.
+ // TOOD(mihaip): Can we check for user gestures instead?
+ WindowOpenDisposition disposition = params.disposition;
+ if (disposition == CURRENT_TAB) {
+ web_contents_->GetMainFrame()->AddMessageToConsole(
+ content::CONSOLE_MESSAGE_LEVEL_ERROR,
+ base::StringPrintf(
+ "Can't open same-window link to \"%s\"; try target=\"_blank\".",
+ params.url.spec().c_str()));
+ return NULL;
+ }
+
+ // These dispositions aren't really navigations.
+ if (disposition == SUPPRESS_OPEN || disposition == SAVE_TO_DISK ||
+ disposition == IGNORE_ACTION) {
+ return NULL;
+ }
+
+ content::WebContents* contents =
+ app_delegate_->OpenURLFromTab(browser_context_, web_contents_, params);
+ if (!contents) {
+ web_contents_->GetMainFrame()->AddMessageToConsole(
+ content::CONSOLE_MESSAGE_LEVEL_ERROR,
+ base::StringPrintf(
+ "Can't navigate to \"%s\"; apps do not support navigation.",
+ params.url.spec().c_str()));
+ }
+
+ return contents;
+}
+
+void AppWebContentsHelper::RequestToLockMouse() const {
+ const Extension* extension = GetExtension();
+ if (!extension)
+ return;
+
+ bool has_permission = IsExtensionWithPermissionOrSuggestInConsole(
+ APIPermission::kPointerLock, extension, web_contents_->GetMainFrame());
+
+ web_contents_->GotResponseToLockMouseRequest(has_permission);
+}
+
+void AppWebContentsHelper::RequestMediaAccessPermission(
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback) const {
+ const Extension* extension = GetExtension();
+ if (!extension)
+ return;
+
+ app_delegate_->RequestMediaAccessPermission(
+ web_contents_, request, callback, extension);
+}
+
+bool AppWebContentsHelper::CheckMediaAccessPermission(
+ const GURL& security_origin,
+ content::MediaStreamType type) const {
+ const Extension* extension = GetExtension();
+ if (!extension)
+ return false;
+
+ return app_delegate_->CheckMediaAccessPermission(
+ web_contents_, security_origin, type, extension);
+}
+
+const Extension* AppWebContentsHelper::GetExtension() const {
+ return ExtensionRegistry::Get(browser_context_)
+ ->enabled_extensions()
+ .GetByID(extension_id_);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/app_window/app_web_contents_helper.h b/chromium/extensions/browser/app_window/app_web_contents_helper.h
new file mode 100644
index 00000000000..12d4598fae2
--- /dev/null
+++ b/chromium/extensions/browser/app_window/app_web_contents_helper.h
@@ -0,0 +1,74 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_APP_WINDOW_APP_WEB_CONTENTS_HELPER_H_
+#define EXTENSIONS_BROWSER_APP_WINDOW_APP_WEB_CONTENTS_HELPER_H_
+
+#include "base/macros.h"
+#include "content/public/common/media_stream_request.h"
+
+namespace blink {
+class WebGestureEvent;
+}
+
+namespace content {
+class BrowserContext;
+struct OpenURLParams;
+class WebContents;
+}
+
+namespace extensions {
+
+class AppDelegate;
+class Extension;
+
+// Provides common functionality for apps and launcher pages to respond to
+// messages from a WebContents.
+class AppWebContentsHelper {
+ public:
+ AppWebContentsHelper(content::BrowserContext* browser_context,
+ const std::string& extension_id,
+ content::WebContents* web_contents,
+ AppDelegate* app_delegate);
+
+ // Returns true if the given |event| should not be handled by the renderer.
+ static bool ShouldSuppressGestureEvent(const blink::WebGestureEvent& event);
+
+ // Opens a new URL inside the passed in WebContents. See WebContentsDelegate.
+ content::WebContents* OpenURLFromTab(
+ const content::OpenURLParams& params) const;
+
+ // Requests to lock the mouse. See WebContentsDelegate.
+ void RequestToLockMouse() const;
+
+ // Asks permission to use the camera and/or microphone. See
+ // WebContentsDelegate.
+ void RequestMediaAccessPermission(
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback) const;
+
+ // Checks permission to use the camera or microphone. See
+ // WebContentsDelegate.
+ bool CheckMediaAccessPermission(const GURL& security_origin,
+ content::MediaStreamType type) const;
+
+ private:
+ const Extension* GetExtension() const;
+
+ // The browser context with which this window is associated.
+ // AppWindowWebContentsDelegate does not own this object.
+ content::BrowserContext* browser_context_;
+
+ const std::string extension_id_;
+
+ content::WebContents* web_contents_;
+
+ AppDelegate* app_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppWebContentsHelper);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_APP_WINDOW_APP_WEB_CONTENTS_HELPER_H_
diff --git a/chromium/extensions/browser/app_window/app_window.cc b/chromium/extensions/browser/app_window/app_window.cc
new file mode 100644
index 00000000000..7294d6f8463
--- /dev/null
+++ b/chromium/extensions/browser/app_window/app_window.cc
@@ -0,0 +1,1126 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/app_window/app_window.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback_helpers.h"
+#include "base/command_line.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "components/web_modal/web_contents_modal_dialog_manager.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/invalidate_type.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/resource_dispatcher_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/browser_side_navigation_policy.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/media_stream_request.h"
+#include "extensions/browser/app_window/app_delegate.h"
+#include "extensions/browser/app_window/app_web_contents_helper.h"
+#include "extensions/browser/app_window/app_window_client.h"
+#include "extensions/browser/app_window/app_window_geometry_cache.h"
+#include "extensions/browser/app_window/app_window_registry.h"
+#include "extensions/browser/app_window/native_app_window.h"
+#include "extensions/browser/app_window/size_constraints.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/extension_web_contents_observer.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/notification_types.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/browser/suggest_permission_util.h"
+#include "extensions/browser/view_type_utils.h"
+#include "extensions/common/draggable_region.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handlers/icons_handler.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/switches.h"
+#include "extensions/grit/extensions_browser_resources.h"
+#include "third_party/skia/include/core/SkRegion.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+#include "ui/gfx/screen.h"
+
+#if !defined(OS_MACOSX)
+#include "components/prefs/pref_service.h"
+#include "extensions/browser/pref_names.h"
+#endif
+
+using content::BrowserContext;
+using content::ConsoleMessageLevel;
+using content::WebContents;
+using web_modal::WebContentsModalDialogHost;
+using web_modal::WebContentsModalDialogManager;
+
+namespace extensions {
+
+namespace {
+
+const int kDefaultWidth = 512;
+const int kDefaultHeight = 384;
+
+void SetConstraintProperty(const std::string& name,
+ int value,
+ base::DictionaryValue* bounds_properties) {
+ if (value != SizeConstraints::kUnboundedSize)
+ bounds_properties->SetInteger(name, value);
+ else
+ bounds_properties->Set(name, base::Value::CreateNullValue());
+}
+
+void SetBoundsProperties(const gfx::Rect& bounds,
+ const gfx::Size& min_size,
+ const gfx::Size& max_size,
+ const std::string& bounds_name,
+ base::DictionaryValue* window_properties) {
+ scoped_ptr<base::DictionaryValue> bounds_properties(
+ new base::DictionaryValue());
+
+ bounds_properties->SetInteger("left", bounds.x());
+ bounds_properties->SetInteger("top", bounds.y());
+ bounds_properties->SetInteger("width", bounds.width());
+ bounds_properties->SetInteger("height", bounds.height());
+
+ SetConstraintProperty("minWidth", min_size.width(), bounds_properties.get());
+ SetConstraintProperty(
+ "minHeight", min_size.height(), bounds_properties.get());
+ SetConstraintProperty("maxWidth", max_size.width(), bounds_properties.get());
+ SetConstraintProperty(
+ "maxHeight", max_size.height(), bounds_properties.get());
+
+ window_properties->Set(bounds_name, bounds_properties.release());
+}
+
+// Combines the constraints of the content and window, and returns constraints
+// for the window.
+gfx::Size GetCombinedWindowConstraints(const gfx::Size& window_constraints,
+ const gfx::Size& content_constraints,
+ const gfx::Insets& frame_insets) {
+ gfx::Size combined_constraints(window_constraints);
+ if (content_constraints.width() > 0) {
+ combined_constraints.set_width(
+ content_constraints.width() + frame_insets.width());
+ }
+ if (content_constraints.height() > 0) {
+ combined_constraints.set_height(
+ content_constraints.height() + frame_insets.height());
+ }
+ return combined_constraints;
+}
+
+// Combines the constraints of the content and window, and returns constraints
+// for the content.
+gfx::Size GetCombinedContentConstraints(const gfx::Size& window_constraints,
+ const gfx::Size& content_constraints,
+ const gfx::Insets& frame_insets) {
+ gfx::Size combined_constraints(content_constraints);
+ if (window_constraints.width() > 0) {
+ combined_constraints.set_width(
+ std::max(0, window_constraints.width() - frame_insets.width()));
+ }
+ if (window_constraints.height() > 0) {
+ combined_constraints.set_height(
+ std::max(0, window_constraints.height() - frame_insets.height()));
+ }
+ return combined_constraints;
+}
+
+} // namespace
+
+// AppWindow::BoundsSpecification
+
+const int AppWindow::BoundsSpecification::kUnspecifiedPosition = INT_MIN;
+
+AppWindow::BoundsSpecification::BoundsSpecification()
+ : bounds(kUnspecifiedPosition, kUnspecifiedPosition, 0, 0) {}
+
+AppWindow::BoundsSpecification::~BoundsSpecification() {}
+
+void AppWindow::BoundsSpecification::ResetBounds() {
+ bounds.SetRect(kUnspecifiedPosition, kUnspecifiedPosition, 0, 0);
+}
+
+// AppWindow::CreateParams
+
+AppWindow::CreateParams::CreateParams()
+ : window_type(AppWindow::WINDOW_TYPE_DEFAULT),
+ frame(AppWindow::FRAME_CHROME),
+ has_frame_color(false),
+ active_frame_color(SK_ColorBLACK),
+ inactive_frame_color(SK_ColorBLACK),
+ alpha_enabled(false),
+ is_ime_window(false),
+ creator_process_id(0),
+ state(ui::SHOW_STATE_DEFAULT),
+ hidden(false),
+ resizable(true),
+ focused(true),
+ always_on_top(false),
+ visible_on_all_workspaces(false) {
+}
+
+AppWindow::CreateParams::CreateParams(const CreateParams& other) = default;
+
+AppWindow::CreateParams::~CreateParams() {}
+
+gfx::Rect AppWindow::CreateParams::GetInitialWindowBounds(
+ const gfx::Insets& frame_insets) const {
+ // Combine into a single window bounds.
+ gfx::Rect combined_bounds(window_spec.bounds);
+ if (content_spec.bounds.x() != BoundsSpecification::kUnspecifiedPosition)
+ combined_bounds.set_x(content_spec.bounds.x() - frame_insets.left());
+ if (content_spec.bounds.y() != BoundsSpecification::kUnspecifiedPosition)
+ combined_bounds.set_y(content_spec.bounds.y() - frame_insets.top());
+ if (content_spec.bounds.width() > 0) {
+ combined_bounds.set_width(
+ content_spec.bounds.width() + frame_insets.width());
+ }
+ if (content_spec.bounds.height() > 0) {
+ combined_bounds.set_height(
+ content_spec.bounds.height() + frame_insets.height());
+ }
+
+ // Constrain the bounds.
+ SizeConstraints constraints(
+ GetCombinedWindowConstraints(
+ window_spec.minimum_size, content_spec.minimum_size, frame_insets),
+ GetCombinedWindowConstraints(
+ window_spec.maximum_size, content_spec.maximum_size, frame_insets));
+ combined_bounds.set_size(constraints.ClampSize(combined_bounds.size()));
+
+ return combined_bounds;
+}
+
+gfx::Size AppWindow::CreateParams::GetContentMinimumSize(
+ const gfx::Insets& frame_insets) const {
+ return GetCombinedContentConstraints(window_spec.minimum_size,
+ content_spec.minimum_size,
+ frame_insets);
+}
+
+gfx::Size AppWindow::CreateParams::GetContentMaximumSize(
+ const gfx::Insets& frame_insets) const {
+ return GetCombinedContentConstraints(window_spec.maximum_size,
+ content_spec.maximum_size,
+ frame_insets);
+}
+
+gfx::Size AppWindow::CreateParams::GetWindowMinimumSize(
+ const gfx::Insets& frame_insets) const {
+ return GetCombinedWindowConstraints(window_spec.minimum_size,
+ content_spec.minimum_size,
+ frame_insets);
+}
+
+gfx::Size AppWindow::CreateParams::GetWindowMaximumSize(
+ const gfx::Insets& frame_insets) const {
+ return GetCombinedWindowConstraints(window_spec.maximum_size,
+ content_spec.maximum_size,
+ frame_insets);
+}
+
+// AppWindow
+
+AppWindow::AppWindow(BrowserContext* context,
+ AppDelegate* app_delegate,
+ const Extension* extension)
+ : browser_context_(context),
+ extension_id_(extension->id()),
+ window_type_(WINDOW_TYPE_DEFAULT),
+ app_delegate_(app_delegate),
+ fullscreen_types_(FULLSCREEN_TYPE_NONE),
+ show_on_first_paint_(false),
+ first_paint_complete_(false),
+ has_been_shown_(false),
+ can_send_events_(false),
+ is_hidden_(false),
+ delayed_show_type_(SHOW_ACTIVE),
+ cached_always_on_top_(false),
+ requested_alpha_enabled_(false),
+ is_ime_window_(false),
+ image_loader_ptr_factory_(this) {
+ ExtensionsBrowserClient* client = ExtensionsBrowserClient::Get();
+ CHECK(!client->IsGuestSession(context) || context->IsOffTheRecord())
+ << "Only off the record window may be opened in the guest mode.";
+}
+
+void AppWindow::Init(const GURL& url,
+ AppWindowContents* app_window_contents,
+ content::RenderFrameHost* creator_frame,
+ const CreateParams& params) {
+ // Initialize the render interface and web contents
+ app_window_contents_.reset(app_window_contents);
+ app_window_contents_->Initialize(browser_context(), creator_frame, url);
+
+ initial_url_ = url;
+
+ content::WebContentsObserver::Observe(web_contents());
+ SetViewType(web_contents(), VIEW_TYPE_APP_WINDOW);
+ app_delegate_->InitWebContents(web_contents());
+
+ ExtensionWebContentsObserver::GetForWebContents(web_contents())->
+ dispatcher()->set_delegate(this);
+
+ WebContentsModalDialogManager::CreateForWebContents(web_contents());
+
+ web_contents()->SetDelegate(this);
+ WebContentsModalDialogManager::FromWebContents(web_contents())
+ ->SetDelegate(this);
+
+ // Initialize the window
+ CreateParams new_params = LoadDefaults(params);
+ window_type_ = new_params.window_type;
+ window_key_ = new_params.window_key;
+
+ // Windows cannot be always-on-top in fullscreen mode for security reasons.
+ cached_always_on_top_ = new_params.always_on_top;
+ if (new_params.state == ui::SHOW_STATE_FULLSCREEN)
+ new_params.always_on_top = false;
+
+ requested_alpha_enabled_ = new_params.alpha_enabled;
+
+ is_ime_window_ = params.is_ime_window;
+
+ AppWindowClient* app_window_client = AppWindowClient::Get();
+ native_app_window_.reset(
+ app_window_client->CreateNativeAppWindow(this, &new_params));
+
+ helper_.reset(new AppWebContentsHelper(
+ browser_context_, extension_id_, web_contents(), app_delegate_.get()));
+
+ UpdateExtensionAppIcon();
+ AppWindowRegistry::Get(browser_context_)->AddAppWindow(this);
+
+ if (new_params.hidden) {
+ // Although the window starts hidden by default, calling Hide() here
+ // notifies observers of the window being hidden.
+ Hide();
+ } else {
+ // Panels are not activated by default.
+ Show(window_type_is_panel() || !new_params.focused ? SHOW_INACTIVE
+ : SHOW_ACTIVE);
+
+ // These states may cause the window to show, so they are ignored if the
+ // window is initially hidden.
+ if (new_params.state == ui::SHOW_STATE_FULLSCREEN)
+ Fullscreen();
+ else if (new_params.state == ui::SHOW_STATE_MAXIMIZED)
+ Maximize();
+ else if (new_params.state == ui::SHOW_STATE_MINIMIZED)
+ Minimize();
+ }
+
+ OnNativeWindowChanged();
+
+ ExtensionRegistry::Get(browser_context_)->AddObserver(this);
+
+ // Close when the browser process is exiting.
+ app_delegate_->SetTerminatingCallback(
+ base::Bind(&NativeAppWindow::Close,
+ base::Unretained(native_app_window_.get())));
+
+ app_window_contents_->LoadContents(new_params.creator_process_id);
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ extensions::switches::kEnableAppsShowOnFirstPaint)) {
+ // We want to show the window only when the content has been painted. For
+ // that to happen, we need to define a size for the content, otherwise the
+ // layout will happen in a 0x0 area.
+ gfx::Insets frame_insets = native_app_window_->GetFrameInsets();
+ gfx::Rect initial_bounds = new_params.GetInitialWindowBounds(frame_insets);
+ initial_bounds.Inset(frame_insets);
+ app_delegate_->ResizeWebContents(web_contents(), initial_bounds.size());
+ }
+}
+
+AppWindow::~AppWindow() {
+ ExtensionRegistry::Get(browser_context_)->RemoveObserver(this);
+}
+
+void AppWindow::RequestMediaAccessPermission(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback) {
+ DCHECK_EQ(AppWindow::web_contents(), web_contents);
+ helper_->RequestMediaAccessPermission(request, callback);
+}
+
+bool AppWindow::CheckMediaAccessPermission(content::WebContents* web_contents,
+ const GURL& security_origin,
+ content::MediaStreamType type) {
+ DCHECK_EQ(AppWindow::web_contents(), web_contents);
+ return helper_->CheckMediaAccessPermission(security_origin, type);
+}
+
+WebContents* AppWindow::OpenURLFromTab(WebContents* source,
+ const content::OpenURLParams& params) {
+ DCHECK_EQ(web_contents(), source);
+ return helper_->OpenURLFromTab(params);
+}
+
+void AppWindow::AddNewContents(WebContents* source,
+ WebContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_rect,
+ bool user_gesture,
+ bool* was_blocked) {
+ DCHECK(new_contents->GetBrowserContext() == browser_context_);
+ app_delegate_->AddNewContents(browser_context_,
+ new_contents,
+ disposition,
+ initial_rect,
+ user_gesture,
+ was_blocked);
+}
+
+bool AppWindow::PreHandleKeyboardEvent(
+ content::WebContents* source,
+ const content::NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut) {
+ const Extension* extension = GetExtension();
+ if (!extension)
+ return false;
+
+ // Here, we can handle a key event before the content gets it. When we are
+ // fullscreen and it is not forced, we want to allow the user to leave
+ // when ESC is pressed.
+ // However, if the application has the "overrideEscFullscreen" permission, we
+ // should let it override that behavior.
+ // ::HandleKeyboardEvent() will only be called if the KeyEvent's default
+ // action is not prevented.
+ // Thus, we should handle the KeyEvent here only if the permission is not set.
+ if (event.windowsKeyCode == ui::VKEY_ESCAPE && IsFullscreen() &&
+ !IsForcedFullscreen() &&
+ !extension->permissions_data()->HasAPIPermission(
+ APIPermission::kOverrideEscFullscreen)) {
+ Restore();
+ return true;
+ }
+
+ return false;
+}
+
+void AppWindow::HandleKeyboardEvent(
+ WebContents* source,
+ const content::NativeWebKeyboardEvent& event) {
+ // If the window is currently fullscreen and not forced, ESC should leave
+ // fullscreen. If this code is being called for ESC, that means that the
+ // KeyEvent's default behavior was not prevented by the content.
+ if (event.windowsKeyCode == ui::VKEY_ESCAPE && IsFullscreen() &&
+ !IsForcedFullscreen()) {
+ Restore();
+ return;
+ }
+
+ native_app_window_->HandleKeyboardEvent(event);
+}
+
+void AppWindow::RequestToLockMouse(WebContents* web_contents,
+ bool user_gesture,
+ bool last_unlocked_by_target) {
+ DCHECK_EQ(AppWindow::web_contents(), web_contents);
+ helper_->RequestToLockMouse();
+}
+
+bool AppWindow::PreHandleGestureEvent(WebContents* source,
+ const blink::WebGestureEvent& event) {
+ return AppWebContentsHelper::ShouldSuppressGestureEvent(event);
+}
+
+void AppWindow::RenderViewCreated(content::RenderViewHost* render_view_host) {
+ app_delegate_->RenderViewCreated(render_view_host);
+}
+
+void AppWindow::DidFirstVisuallyNonEmptyPaint() {
+ first_paint_complete_ = true;
+ if (show_on_first_paint_) {
+ DCHECK(delayed_show_type_ == SHOW_ACTIVE ||
+ delayed_show_type_ == SHOW_INACTIVE);
+ Show(delayed_show_type_);
+ }
+}
+
+void AppWindow::SetOnFirstCommitCallback(const base::Closure& callback) {
+ DCHECK(on_first_commit_callback_.is_null());
+ on_first_commit_callback_ = callback;
+}
+
+void AppWindow::OnReadyToCommitFirstNavigation() {
+ CHECK(content::IsBrowserSideNavigationEnabled());
+ WindowEventsReady();
+ if (on_first_commit_callback_.is_null())
+ return;
+ // It is important that the callback executes after the calls to
+ // WebContentsObserver::ReadyToCommitNavigation have been processed. The
+ // CommitNavigation IPC that will properly set up the renderer will only be
+ // sent after these, and it must be sent before the callback gets to run,
+ // hence the use of PostTask.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::ResetAndReturn(&on_first_commit_callback_));
+}
+
+void AppWindow::OnNativeClose() {
+ AppWindowRegistry::Get(browser_context_)->RemoveAppWindow(this);
+ if (app_window_contents_) {
+ WebContentsModalDialogManager* modal_dialog_manager =
+ WebContentsModalDialogManager::FromWebContents(web_contents());
+ if (modal_dialog_manager) // May be null in unit tests.
+ modal_dialog_manager->SetDelegate(nullptr);
+ app_window_contents_->NativeWindowClosed();
+ }
+ delete this;
+}
+
+void AppWindow::OnNativeWindowChanged() {
+ // This may be called during Init before |native_app_window_| is set.
+ if (!native_app_window_)
+ return;
+
+#if defined(OS_MACOSX)
+ // On Mac the user can change the window's fullscreen state. If that has
+ // happened, update AppWindow's internal state.
+ if (native_app_window_->IsFullscreen()) {
+ if (!IsFullscreen())
+ fullscreen_types_ = FULLSCREEN_TYPE_OS;
+ } else {
+ fullscreen_types_ = FULLSCREEN_TYPE_NONE;
+ }
+
+ RestoreAlwaysOnTop(); // Same as in SetNativeWindowFullscreen.
+#endif
+
+ SaveWindowPosition();
+
+#if defined(OS_WIN)
+ if (cached_always_on_top_ && !IsFullscreen() &&
+ !native_app_window_->IsMaximized() &&
+ !native_app_window_->IsMinimized()) {
+ UpdateNativeAlwaysOnTop();
+ }
+#endif
+
+ if (app_window_contents_)
+ app_window_contents_->NativeWindowChanged(native_app_window_.get());
+}
+
+void AppWindow::OnNativeWindowActivated() {
+ AppWindowRegistry::Get(browser_context_)->AppWindowActivated(this);
+}
+
+content::WebContents* AppWindow::web_contents() const {
+ if (app_window_contents_)
+ return app_window_contents_->GetWebContents();
+ return nullptr;
+}
+
+const Extension* AppWindow::GetExtension() const {
+ return ExtensionRegistry::Get(browser_context_)
+ ->enabled_extensions()
+ .GetByID(extension_id_);
+}
+
+NativeAppWindow* AppWindow::GetBaseWindow() { return native_app_window_.get(); }
+
+gfx::NativeWindow AppWindow::GetNativeWindow() {
+ return GetBaseWindow()->GetNativeWindow();
+}
+
+gfx::Rect AppWindow::GetClientBounds() const {
+ gfx::Rect bounds = native_app_window_->GetBounds();
+ bounds.Inset(native_app_window_->GetFrameInsets());
+ return bounds;
+}
+
+base::string16 AppWindow::GetTitle() const {
+ const Extension* extension = GetExtension();
+ if (!extension)
+ return base::string16();
+
+ // WebContents::GetTitle() will return the page's URL if there's no <title>
+ // specified. However, we'd prefer to show the name of the extension in that
+ // case, so we directly inspect the NavigationEntry's title.
+ base::string16 title;
+ content::NavigationEntry* entry = web_contents() ?
+ web_contents()->GetController().GetLastCommittedEntry() : nullptr;
+ if (!entry || entry->GetTitle().empty()) {
+ title = base::UTF8ToUTF16(extension->name());
+ } else {
+ title = web_contents()->GetTitle();
+ }
+ base::RemoveChars(title, base::ASCIIToUTF16("\n"), &title);
+ return title;
+}
+
+void AppWindow::SetAppIconUrl(const GURL& url) {
+ // Avoid using any previous icons that were being downloaded.
+ image_loader_ptr_factory_.InvalidateWeakPtrs();
+
+ // Reset |app_icon_image_| to abort pending image load (if any).
+ app_icon_image_.reset();
+
+ app_icon_url_ = url;
+ web_contents()->DownloadImage(
+ url,
+ true, // is a favicon
+ 0, // no maximum size
+ false, // normal cache policy
+ base::Bind(&AppWindow::DidDownloadFavicon,
+ image_loader_ptr_factory_.GetWeakPtr()));
+}
+
+void AppWindow::UpdateShape(scoped_ptr<SkRegion> region) {
+ native_app_window_->UpdateShape(std::move(region));
+}
+
+void AppWindow::UpdateDraggableRegions(
+ const std::vector<DraggableRegion>& regions) {
+ native_app_window_->UpdateDraggableRegions(regions);
+}
+
+void AppWindow::UpdateAppIcon(const gfx::Image& image) {
+ if (image.IsEmpty())
+ return;
+ app_icon_ = image;
+ native_app_window_->UpdateWindowIcon();
+ AppWindowRegistry::Get(browser_context_)->AppWindowIconChanged(this);
+}
+
+void AppWindow::SetFullscreen(FullscreenType type, bool enable) {
+ DCHECK_NE(FULLSCREEN_TYPE_NONE, type);
+
+ if (enable) {
+#if !defined(OS_MACOSX)
+ // Do not enter fullscreen mode if disallowed by pref.
+ // TODO(bartfab): Add a test once it becomes possible to simulate a user
+ // gesture. http://crbug.com/174178
+ if (type != FULLSCREEN_TYPE_FORCED) {
+ PrefService* prefs =
+ ExtensionsBrowserClient::Get()->GetPrefServiceForContext(
+ browser_context());
+ if (!prefs->GetBoolean(pref_names::kAppFullscreenAllowed))
+ return;
+ }
+#endif
+ fullscreen_types_ |= type;
+ } else {
+ fullscreen_types_ &= ~type;
+ }
+ SetNativeWindowFullscreen();
+}
+
+bool AppWindow::IsFullscreen() const {
+ return fullscreen_types_ != FULLSCREEN_TYPE_NONE;
+}
+
+bool AppWindow::IsForcedFullscreen() const {
+ return (fullscreen_types_ & FULLSCREEN_TYPE_FORCED) != 0;
+}
+
+bool AppWindow::IsHtmlApiFullscreen() const {
+ return (fullscreen_types_ & FULLSCREEN_TYPE_HTML_API) != 0;
+}
+
+void AppWindow::Fullscreen() {
+ SetFullscreen(FULLSCREEN_TYPE_WINDOW_API, true);
+}
+
+void AppWindow::Maximize() { GetBaseWindow()->Maximize(); }
+
+void AppWindow::Minimize() { GetBaseWindow()->Minimize(); }
+
+void AppWindow::Restore() {
+ if (IsFullscreen()) {
+ fullscreen_types_ = FULLSCREEN_TYPE_NONE;
+ SetNativeWindowFullscreen();
+ } else {
+ GetBaseWindow()->Restore();
+ }
+}
+
+void AppWindow::OSFullscreen() {
+ SetFullscreen(FULLSCREEN_TYPE_OS, true);
+}
+
+void AppWindow::ForcedFullscreen() {
+ SetFullscreen(FULLSCREEN_TYPE_FORCED, true);
+}
+
+void AppWindow::SetContentSizeConstraints(const gfx::Size& min_size,
+ const gfx::Size& max_size) {
+ SizeConstraints constraints(min_size, max_size);
+ native_app_window_->SetContentSizeConstraints(constraints.GetMinimumSize(),
+ constraints.GetMaximumSize());
+
+ gfx::Rect bounds = GetClientBounds();
+ gfx::Size constrained_size = constraints.ClampSize(bounds.size());
+ if (bounds.size() != constrained_size) {
+ bounds.set_size(constrained_size);
+ bounds.Inset(-native_app_window_->GetFrameInsets());
+ native_app_window_->SetBounds(bounds);
+ }
+ OnNativeWindowChanged();
+}
+
+void AppWindow::Show(ShowType show_type) {
+ app_delegate_->OnShow();
+ bool was_hidden = is_hidden_ || !has_been_shown_;
+ is_hidden_ = false;
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableAppsShowOnFirstPaint)) {
+ show_on_first_paint_ = true;
+
+ if (!first_paint_complete_) {
+ delayed_show_type_ = show_type;
+ return;
+ }
+ }
+
+ switch (show_type) {
+ case SHOW_ACTIVE:
+ GetBaseWindow()->Show();
+ break;
+ case SHOW_INACTIVE:
+ GetBaseWindow()->ShowInactive();
+ break;
+ }
+ AppWindowRegistry::Get(browser_context_)->AppWindowShown(this, was_hidden);
+
+ has_been_shown_ = true;
+ SendOnWindowShownIfShown();
+}
+
+void AppWindow::Hide() {
+ // This is there to prevent race conditions with Hide() being called before
+ // there was a non-empty paint. It should have no effect in a non-racy
+ // scenario where the application is hiding then showing a window: the second
+ // show will not be delayed.
+ is_hidden_ = true;
+ show_on_first_paint_ = false;
+ GetBaseWindow()->Hide();
+ AppWindowRegistry::Get(browser_context_)->AppWindowHidden(this);
+ app_delegate_->OnHide();
+}
+
+void AppWindow::SetAlwaysOnTop(bool always_on_top) {
+ if (cached_always_on_top_ == always_on_top)
+ return;
+
+ cached_always_on_top_ = always_on_top;
+
+ // As a security measure, do not allow fullscreen windows or windows that
+ // overlap the taskbar to be on top. The property will be applied when the
+ // window exits fullscreen and moves away from the taskbar.
+ if (!IsFullscreen() && !IntersectsWithTaskbar())
+ native_app_window_->SetAlwaysOnTop(always_on_top);
+
+ OnNativeWindowChanged();
+}
+
+bool AppWindow::IsAlwaysOnTop() const { return cached_always_on_top_; }
+
+void AppWindow::RestoreAlwaysOnTop() {
+ if (cached_always_on_top_)
+ UpdateNativeAlwaysOnTop();
+}
+
+void AppWindow::WindowEventsReady() {
+ can_send_events_ = true;
+ SendOnWindowShownIfShown();
+}
+
+void AppWindow::NotifyRenderViewReady() {
+ if (app_window_contents_)
+ app_window_contents_->OnWindowReady();
+}
+
+void AppWindow::GetSerializedState(base::DictionaryValue* properties) const {
+ DCHECK(properties);
+
+ properties->SetBoolean("fullscreen",
+ native_app_window_->IsFullscreenOrPending());
+ properties->SetBoolean("minimized", native_app_window_->IsMinimized());
+ properties->SetBoolean("maximized", native_app_window_->IsMaximized());
+ properties->SetBoolean("alwaysOnTop", IsAlwaysOnTop());
+ properties->SetBoolean("hasFrameColor", native_app_window_->HasFrameColor());
+ properties->SetBoolean(
+ "alphaEnabled",
+ requested_alpha_enabled_ && native_app_window_->CanHaveAlphaEnabled());
+
+ // These properties are undocumented and are to enable testing. Alpha is
+ // removed to
+ // make the values easier to check.
+ SkColor transparent_white = ~SK_ColorBLACK;
+ properties->SetInteger(
+ "activeFrameColor",
+ native_app_window_->ActiveFrameColor() & transparent_white);
+ properties->SetInteger(
+ "inactiveFrameColor",
+ native_app_window_->InactiveFrameColor() & transparent_white);
+
+ gfx::Rect content_bounds = GetClientBounds();
+ gfx::Size content_min_size = native_app_window_->GetContentMinimumSize();
+ gfx::Size content_max_size = native_app_window_->GetContentMaximumSize();
+ SetBoundsProperties(content_bounds,
+ content_min_size,
+ content_max_size,
+ "innerBounds",
+ properties);
+
+ gfx::Insets frame_insets = native_app_window_->GetFrameInsets();
+ gfx::Rect frame_bounds = native_app_window_->GetBounds();
+ gfx::Size frame_min_size = SizeConstraints::AddFrameToConstraints(
+ content_min_size, frame_insets);
+ gfx::Size frame_max_size = SizeConstraints::AddFrameToConstraints(
+ content_max_size, frame_insets);
+ SetBoundsProperties(frame_bounds,
+ frame_min_size,
+ frame_max_size,
+ "outerBounds",
+ properties);
+}
+
+//------------------------------------------------------------------------------
+// Private methods
+
+void AppWindow::DidDownloadFavicon(
+ int id,
+ int http_status_code,
+ const GURL& image_url,
+ const std::vector<SkBitmap>& bitmaps,
+ const std::vector<gfx::Size>& original_bitmap_sizes) {
+ if (image_url != app_icon_url_ || bitmaps.empty())
+ return;
+
+ // Bitmaps are ordered largest to smallest. Choose the smallest bitmap
+ // whose height >= the preferred size.
+ int largest_index = 0;
+ for (size_t i = 1; i < bitmaps.size(); ++i) {
+ if (bitmaps[i].height() < app_delegate_->PreferredIconSize())
+ break;
+ largest_index = i;
+ }
+ const SkBitmap& largest = bitmaps[largest_index];
+ UpdateAppIcon(gfx::Image::CreateFrom1xBitmap(largest));
+}
+
+void AppWindow::OnExtensionIconImageChanged(IconImage* image) {
+ DCHECK_EQ(app_icon_image_.get(), image);
+
+ UpdateAppIcon(gfx::Image(app_icon_image_->image_skia()));
+}
+
+void AppWindow::UpdateExtensionAppIcon() {
+ // Avoid using any previous app icons were being downloaded.
+ image_loader_ptr_factory_.InvalidateWeakPtrs();
+
+ const Extension* extension = GetExtension();
+ if (!extension)
+ return;
+
+ gfx::ImageSkia app_default_icon =
+ *ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IDR_APP_DEFAULT_ICON);
+
+ app_icon_image_.reset(new IconImage(browser_context(),
+ extension,
+ IconsInfo::GetIcons(extension),
+ app_delegate_->PreferredIconSize(),
+ app_default_icon,
+ this));
+
+ // Triggers actual image loading with 1x resources. The 2x resource will
+ // be handled by IconImage class when requested.
+ app_icon_image_->image_skia().GetRepresentation(1.0f);
+}
+
+void AppWindow::SetNativeWindowFullscreen() {
+ native_app_window_->SetFullscreen(fullscreen_types_);
+
+ RestoreAlwaysOnTop();
+}
+
+bool AppWindow::IntersectsWithTaskbar() const {
+#if defined(OS_WIN)
+ gfx::Screen* screen = gfx::Screen::GetScreen();
+ gfx::Rect window_bounds = native_app_window_->GetRestoredBounds();
+ std::vector<gfx::Display> displays = screen->GetAllDisplays();
+
+ for (std::vector<gfx::Display>::const_iterator it = displays.begin();
+ it != displays.end();
+ ++it) {
+ gfx::Rect taskbar_bounds = it->bounds();
+ taskbar_bounds.Subtract(it->work_area());
+ if (taskbar_bounds.IsEmpty())
+ continue;
+
+ if (window_bounds.Intersects(taskbar_bounds))
+ return true;
+ }
+#endif
+
+ return false;
+}
+
+void AppWindow::UpdateNativeAlwaysOnTop() {
+ DCHECK(cached_always_on_top_);
+ bool is_on_top = native_app_window_->IsAlwaysOnTop();
+ bool fullscreen = IsFullscreen();
+ bool intersects_taskbar = IntersectsWithTaskbar();
+
+ if (is_on_top && (fullscreen || intersects_taskbar)) {
+ // When entering fullscreen or overlapping the taskbar, ensure windows are
+ // not always-on-top.
+ native_app_window_->SetAlwaysOnTop(false);
+ } else if (!is_on_top && !fullscreen && !intersects_taskbar) {
+ // When exiting fullscreen and moving away from the taskbar, reinstate
+ // always-on-top.
+ native_app_window_->SetAlwaysOnTop(true);
+ }
+}
+
+void AppWindow::SendOnWindowShownIfShown() {
+ if (!can_send_events_ || !has_been_shown_)
+ return;
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ ::switches::kTestType)) {
+ app_window_contents_->DispatchWindowShownForTests();
+ }
+}
+
+void AppWindow::CloseContents(WebContents* contents) {
+ native_app_window_->Close();
+}
+
+bool AppWindow::ShouldSuppressDialogs(WebContents* source) {
+ return true;
+}
+
+content::ColorChooser* AppWindow::OpenColorChooser(
+ WebContents* web_contents,
+ SkColor initial_color,
+ const std::vector<content::ColorSuggestion>& suggestions) {
+ return app_delegate_->ShowColorChooser(web_contents, initial_color);
+}
+
+void AppWindow::RunFileChooser(WebContents* tab,
+ const content::FileChooserParams& params) {
+ if (window_type_is_panel()) {
+ // Panels can't host a file dialog, abort. TODO(stevenjb): allow file
+ // dialogs to be unhosted but still close with the owning web contents.
+ // crbug.com/172502.
+ LOG(WARNING) << "File dialog opened by panel.";
+ return;
+ }
+
+ app_delegate_->RunFileChooser(tab, params);
+}
+
+bool AppWindow::IsPopupOrPanel(const WebContents* source) const { return true; }
+
+void AppWindow::MoveContents(WebContents* source, const gfx::Rect& pos) {
+ native_app_window_->SetBounds(pos);
+}
+
+void AppWindow::NavigationStateChanged(content::WebContents* source,
+ content::InvalidateTypes changed_flags) {
+ if (changed_flags & content::INVALIDATE_TYPE_TITLE)
+ native_app_window_->UpdateWindowTitle();
+ else if (changed_flags & content::INVALIDATE_TYPE_TAB)
+ native_app_window_->UpdateWindowIcon();
+}
+
+void AppWindow::EnterFullscreenModeForTab(content::WebContents* source,
+ const GURL& origin) {
+ ToggleFullscreenModeForTab(source, true);
+}
+
+void AppWindow::ExitFullscreenModeForTab(content::WebContents* source) {
+ ToggleFullscreenModeForTab(source, false);
+}
+
+void AppWindow::ToggleFullscreenModeForTab(content::WebContents* source,
+ bool enter_fullscreen) {
+ const Extension* extension = GetExtension();
+ if (!extension)
+ return;
+
+ if (!IsExtensionWithPermissionOrSuggestInConsole(
+ APIPermission::kFullscreen, extension, source->GetMainFrame())) {
+ return;
+ }
+
+ SetFullscreen(FULLSCREEN_TYPE_HTML_API, enter_fullscreen);
+}
+
+bool AppWindow::IsFullscreenForTabOrPending(const content::WebContents* source)
+ const {
+ return IsHtmlApiFullscreen();
+}
+
+blink::WebDisplayMode AppWindow::GetDisplayMode(
+ const content::WebContents* source) const {
+ return IsFullscreen() ? blink::WebDisplayModeFullscreen
+ : blink::WebDisplayModeStandalone;
+}
+
+WindowController* AppWindow::GetExtensionWindowController() const {
+ return app_window_contents_->GetWindowController();
+}
+
+content::WebContents* AppWindow::GetAssociatedWebContents() const {
+ return web_contents();
+}
+
+void AppWindow::OnExtensionUnloaded(BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ if (extension_id_ == extension->id())
+ native_app_window_->Close();
+}
+
+void AppWindow::SetWebContentsBlocked(content::WebContents* web_contents,
+ bool blocked) {
+ app_delegate_->SetWebContentsBlocked(web_contents, blocked);
+}
+
+bool AppWindow::IsWebContentsVisible(content::WebContents* web_contents) {
+ return app_delegate_->IsWebContentsVisible(web_contents);
+}
+
+WebContentsModalDialogHost* AppWindow::GetWebContentsModalDialogHost() {
+ return native_app_window_.get();
+}
+
+void AppWindow::SaveWindowPosition() {
+ DCHECK(native_app_window_);
+ if (window_key_.empty())
+ return;
+
+ AppWindowGeometryCache* cache =
+ AppWindowGeometryCache::Get(browser_context());
+
+ gfx::Rect bounds = native_app_window_->GetRestoredBounds();
+ gfx::Rect screen_bounds =
+ gfx::Screen::GetScreen()->GetDisplayMatching(bounds).work_area();
+ ui::WindowShowState window_state = native_app_window_->GetRestoredState();
+ cache->SaveGeometry(
+ extension_id(), window_key_, bounds, screen_bounds, window_state);
+}
+
+void AppWindow::AdjustBoundsToBeVisibleOnScreen(
+ const gfx::Rect& cached_bounds,
+ const gfx::Rect& cached_screen_bounds,
+ const gfx::Rect& current_screen_bounds,
+ const gfx::Size& minimum_size,
+ gfx::Rect* bounds) const {
+ *bounds = cached_bounds;
+
+ // Reposition and resize the bounds if the cached_screen_bounds is different
+ // from the current screen bounds and the current screen bounds doesn't
+ // completely contain the bounds.
+ if (cached_screen_bounds != current_screen_bounds &&
+ !current_screen_bounds.Contains(cached_bounds)) {
+ bounds->set_width(
+ std::max(minimum_size.width(),
+ std::min(bounds->width(), current_screen_bounds.width())));
+ bounds->set_height(
+ std::max(minimum_size.height(),
+ std::min(bounds->height(), current_screen_bounds.height())));
+ bounds->set_x(
+ std::max(current_screen_bounds.x(),
+ std::min(bounds->x(),
+ current_screen_bounds.right() - bounds->width())));
+ bounds->set_y(
+ std::max(current_screen_bounds.y(),
+ std::min(bounds->y(),
+ current_screen_bounds.bottom() - bounds->height())));
+ }
+}
+
+AppWindow::CreateParams AppWindow::LoadDefaults(CreateParams params)
+ const {
+ // Ensure width and height are specified.
+ if (params.content_spec.bounds.width() == 0 &&
+ params.window_spec.bounds.width() == 0) {
+ params.content_spec.bounds.set_width(kDefaultWidth);
+ }
+ if (params.content_spec.bounds.height() == 0 &&
+ params.window_spec.bounds.height() == 0) {
+ params.content_spec.bounds.set_height(kDefaultHeight);
+ }
+
+ // If left and top are left undefined, the native app window will center
+ // the window on the main screen in a platform-defined manner.
+
+ // Load cached state if it exists.
+ if (!params.window_key.empty()) {
+ AppWindowGeometryCache* cache =
+ AppWindowGeometryCache::Get(browser_context());
+
+ gfx::Rect cached_bounds;
+ gfx::Rect cached_screen_bounds;
+ ui::WindowShowState cached_state = ui::SHOW_STATE_DEFAULT;
+ if (cache->GetGeometry(extension_id(),
+ params.window_key,
+ &cached_bounds,
+ &cached_screen_bounds,
+ &cached_state)) {
+ // App window has cached screen bounds, make sure it fits on screen in
+ // case the screen resolution changed.
+ gfx::Screen* screen = gfx::Screen::GetScreen();
+ gfx::Display display = screen->GetDisplayMatching(cached_bounds);
+ gfx::Rect current_screen_bounds = display.work_area();
+ SizeConstraints constraints(params.GetWindowMinimumSize(gfx::Insets()),
+ params.GetWindowMaximumSize(gfx::Insets()));
+ AdjustBoundsToBeVisibleOnScreen(cached_bounds,
+ cached_screen_bounds,
+ current_screen_bounds,
+ constraints.GetMinimumSize(),
+ &params.window_spec.bounds);
+ params.state = cached_state;
+
+ // Since we are restoring a cached state, reset the content bounds spec to
+ // ensure it is not used.
+ params.content_spec.ResetBounds();
+ }
+ }
+
+ return params;
+}
+
+// static
+SkRegion* AppWindow::RawDraggableRegionsToSkRegion(
+ const std::vector<DraggableRegion>& regions) {
+ SkRegion* sk_region = new SkRegion;
+ for (std::vector<DraggableRegion>::const_iterator iter = regions.begin();
+ iter != regions.end();
+ ++iter) {
+ const DraggableRegion& region = *iter;
+ sk_region->op(
+ region.bounds.x(),
+ region.bounds.y(),
+ region.bounds.right(),
+ region.bounds.bottom(),
+ region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op);
+ }
+ return sk_region;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/app_window/app_window.h b/chromium/extensions/browser/app_window/app_window.h
new file mode 100644
index 00000000000..e41575dae1a
--- /dev/null
+++ b/chromium/extensions/browser/app_window/app_window.h
@@ -0,0 +1,579 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_APP_WINDOW_APP_WINDOW_H_
+#define EXTENSIONS_BROWSER_APP_WINDOW_APP_WINDOW_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "components/sessions/core/session_id.h"
+#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "extensions/browser/extension_function_dispatcher.h"
+#include "extensions/browser/extension_icon_image.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "ui/base/ui_base_types.h" // WindowShowState
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/image/image.h"
+
+class GURL;
+class SkRegion;
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace content {
+class BrowserContext;
+class RenderFrameHost;
+class WebContents;
+}
+
+namespace extensions {
+
+class AppDelegate;
+class AppWebContentsHelper;
+class Extension;
+class NativeAppWindow;
+class PlatformAppBrowserTest;
+
+struct DraggableRegion;
+
+// Manages the web contents for app windows. The implementation for this
+// class should create and maintain the WebContents for the window, and handle
+// any message passing between the web contents and the extension system or
+// native window.
+class AppWindowContents {
+ public:
+ AppWindowContents() {}
+ virtual ~AppWindowContents() {}
+
+ // Called to initialize the WebContents, before the app window is created.
+ virtual void Initialize(content::BrowserContext* context,
+ content::RenderFrameHost* creator_frame,
+ const GURL& url) = 0;
+
+ // Called to load the contents, after the app window is created.
+ virtual void LoadContents(int32_t creator_process_id) = 0;
+
+ // Called when the native window changes.
+ virtual void NativeWindowChanged(NativeAppWindow* native_app_window) = 0;
+
+ // Called when the native window closes.
+ virtual void NativeWindowClosed() = 0;
+
+ // Called in tests when the window is shown
+ virtual void DispatchWindowShownForTests() const = 0;
+
+ // Called when the renderer notifies the browser that the window is ready.
+ virtual void OnWindowReady() = 0;
+
+ virtual content::WebContents* GetWebContents() const = 0;
+
+ virtual extensions::WindowController* GetWindowController() const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AppWindowContents);
+};
+
+// AppWindow is the type of window used by platform apps. App windows
+// have a WebContents but none of the chrome of normal browser windows.
+class AppWindow : public content::WebContentsDelegate,
+ public content::WebContentsObserver,
+ public web_modal::WebContentsModalDialogManagerDelegate,
+ public IconImage::Observer,
+ public ExtensionFunctionDispatcher::Delegate,
+ public ExtensionRegistryObserver {
+ public:
+ enum WindowType {
+ WINDOW_TYPE_DEFAULT = 1 << 0, // Default app window.
+ WINDOW_TYPE_PANEL = 1 << 1, // OS controlled panel window (Ash only).
+ WINDOW_TYPE_V1_PANEL = 1 << 2, // For apps v1 support in Ash; deprecate
+ // with v1 apps.
+ };
+
+ enum Frame {
+ FRAME_CHROME, // Chrome-style window frame.
+ FRAME_NONE, // Frameless window.
+ };
+
+ enum FullscreenType {
+ // Not fullscreen.
+ FULLSCREEN_TYPE_NONE = 0,
+
+ // Fullscreen entered by the app.window api.
+ FULLSCREEN_TYPE_WINDOW_API = 1 << 0,
+
+ // Fullscreen entered by HTML requestFullscreen().
+ FULLSCREEN_TYPE_HTML_API = 1 << 1,
+
+ // Fullscreen entered by the OS. ChromeOS uses this type of fullscreen to
+ // enter immersive fullscreen when the user hits the <F4> key.
+ FULLSCREEN_TYPE_OS = 1 << 2,
+
+ // Fullscreen mode that could not be exited by the user. ChromeOS uses
+ // this type of fullscreen to run an app in kiosk mode.
+ FULLSCREEN_TYPE_FORCED = 1 << 3,
+ };
+
+ struct BoundsSpecification {
+ // INT_MIN represents an unspecified position component.
+ static const int kUnspecifiedPosition;
+
+ BoundsSpecification();
+ ~BoundsSpecification();
+
+ // INT_MIN designates 'unspecified' for the position components, and 0
+ // designates 'unspecified' for the size components. When unspecified,
+ // they should be replaced with a default value.
+ gfx::Rect bounds;
+
+ gfx::Size minimum_size;
+ gfx::Size maximum_size;
+
+ // Reset the bounds fields to their 'unspecified' values. The minimum and
+ // maximum size constraints remain unchanged.
+ void ResetBounds();
+ };
+
+ struct CreateParams {
+ CreateParams();
+ CreateParams(const CreateParams& other);
+ ~CreateParams();
+
+ WindowType window_type;
+ Frame frame;
+
+ bool has_frame_color;
+ SkColor active_frame_color;
+ SkColor inactive_frame_color;
+ bool alpha_enabled;
+ bool is_ime_window;
+
+ // The initial content/inner bounds specification (excluding any window
+ // decorations).
+ BoundsSpecification content_spec;
+
+ // The initial window/outer bounds specification (including window
+ // decorations).
+ BoundsSpecification window_spec;
+
+ std::string window_key;
+
+ // The process ID of the process that requested the create.
+ int32_t creator_process_id;
+
+ // Initial state of the window.
+ ui::WindowShowState state;
+
+ // If true, don't show the window after creation.
+ bool hidden;
+
+ // If true, the window will be resizable by the user. Defaults to true.
+ bool resizable;
+
+ // If true, the window will be focused on creation. Defaults to true.
+ bool focused;
+
+ // If true, the window will stay on top of other windows that are not
+ // configured to be always on top. Defaults to false.
+ bool always_on_top;
+
+ // If true, the window will be visible on all workspaces. Defaults to false.
+ bool visible_on_all_workspaces;
+
+ // The API enables developers to specify content or window bounds. This
+ // function combines them into a single, constrained window size.
+ gfx::Rect GetInitialWindowBounds(const gfx::Insets& frame_insets) const;
+
+ // The API enables developers to specify content or window size constraints.
+ // These functions combine them so that we can work with one set of
+ // constraints.
+ gfx::Size GetContentMinimumSize(const gfx::Insets& frame_insets) const;
+ gfx::Size GetContentMaximumSize(const gfx::Insets& frame_insets) const;
+ gfx::Size GetWindowMinimumSize(const gfx::Insets& frame_insets) const;
+ gfx::Size GetWindowMaximumSize(const gfx::Insets& frame_insets) const;
+ };
+
+ // Convert draggable regions in raw format to SkRegion format. Caller is
+ // responsible for deleting the returned SkRegion instance.
+ static SkRegion* RawDraggableRegionsToSkRegion(
+ const std::vector<DraggableRegion>& regions);
+
+ // The constructor and Init methods are public for constructing a AppWindow
+ // with a non-standard render interface (e.g. v1 apps using Ash Panels).
+ // Normally AppWindow::Create should be used.
+ // Takes ownership of |app_delegate| and |delegate|.
+ AppWindow(content::BrowserContext* context,
+ AppDelegate* app_delegate,
+ const Extension* extension);
+
+ // Initializes the render interface, web contents, and native window.
+ // |app_window_contents| will become owned by AppWindow.
+ void Init(const GURL& url,
+ AppWindowContents* app_window_contents,
+ content::RenderFrameHost* creator_frame,
+ const CreateParams& params);
+
+ const std::string& window_key() const { return window_key_; }
+ const SessionID& session_id() const { return session_id_; }
+ const std::string& extension_id() const { return extension_id_; }
+ content::WebContents* web_contents() const;
+ WindowType window_type() const { return window_type_; }
+ bool window_type_is_panel() const {
+ return (window_type_ == WINDOW_TYPE_PANEL ||
+ window_type_ == WINDOW_TYPE_V1_PANEL);
+ }
+ content::BrowserContext* browser_context() const { return browser_context_; }
+ const gfx::Image& app_icon() const { return app_icon_; }
+ const GURL& app_icon_url() const { return app_icon_url_; }
+ const GURL& initial_url() const { return initial_url_; }
+ bool is_hidden() const { return is_hidden_; }
+
+ const Extension* GetExtension() const;
+ NativeAppWindow* GetBaseWindow();
+ gfx::NativeWindow GetNativeWindow();
+
+ // Returns the bounds that should be reported to the renderer.
+ gfx::Rect GetClientBounds() const;
+
+ // NativeAppWindows should call this to determine what the window's title
+ // is on startup and from within UpdateWindowTitle().
+ base::string16 GetTitle() const;
+
+ // |callback| will then be called when the first navigation in the window is
+ // ready to commit.
+ void SetOnFirstCommitCallback(const base::Closure& callback);
+
+ // Called when the first navigation in the window is ready to commit.
+ void OnReadyToCommitFirstNavigation();
+
+ // Call to notify ShellRegistry and delete the window. Subclasses should
+ // invoke this method instead of using "delete this".
+ void OnNativeClose();
+
+ // Should be called by native implementations when the window size, position,
+ // or minimized/maximized state has changed.
+ void OnNativeWindowChanged();
+
+ // Should be called by native implementations when the window is activated.
+ void OnNativeWindowActivated();
+
+ // Specifies a url for the launcher icon.
+ void SetAppIconUrl(const GURL& icon_url);
+
+ // Set the window shape. Passing a NULL |region| sets the default shape.
+ void UpdateShape(scoped_ptr<SkRegion> region);
+
+ // Called from the render interface to modify the draggable regions.
+ void UpdateDraggableRegions(const std::vector<DraggableRegion>& regions);
+
+ // Updates the app image to |image|. Called internally from the image loader
+ // callback. Also called externally for v1 apps using Ash Panels.
+ void UpdateAppIcon(const gfx::Image& image);
+
+ // Enable or disable fullscreen mode. |type| specifies which type of
+ // fullscreen mode to change (note that disabling one type of fullscreen may
+ // not exit fullscreen mode because a window may have a different type of
+ // fullscreen enabled). If |type| is not FORCED, checks that the extension has
+ // the required permission.
+ void SetFullscreen(FullscreenType type, bool enable);
+
+ // Returns true if the app window is in a fullscreen state.
+ bool IsFullscreen() const;
+
+ // Returns true if the app window is in a forced fullscreen state (one that
+ // cannot be exited by the user).
+ bool IsForcedFullscreen() const;
+
+ // Returns true if the app window is in a fullscreen state entered from an
+ // HTML API request.
+ bool IsHtmlApiFullscreen() const;
+
+ // Transitions window into fullscreen, maximized, minimized or restores based
+ // on chrome.app.window API.
+ void Fullscreen();
+ void Maximize();
+ void Minimize();
+ void Restore();
+
+ // Transitions to OS fullscreen. See FULLSCREEN_TYPE_OS for more details.
+ void OSFullscreen();
+
+ // Transitions to forced fullscreen. See FULLSCREEN_TYPE_FORCED for more
+ // details.
+ void ForcedFullscreen();
+
+ // Set the minimum and maximum size of the content bounds.
+ void SetContentSizeConstraints(const gfx::Size& min_size,
+ const gfx::Size& max_size);
+
+ enum ShowType { SHOW_ACTIVE, SHOW_INACTIVE };
+
+ // Shows the window if its contents have been painted; otherwise flags the
+ // window to be shown as soon as its contents are painted for the first time.
+ void Show(ShowType show_type);
+
+ // Hides the window. If the window was previously flagged to be shown on
+ // first paint, it will be unflagged.
+ void Hide();
+
+ AppWindowContents* app_window_contents_for_test() {
+ return app_window_contents_.get();
+ }
+
+ int fullscreen_types_for_test() {
+ return fullscreen_types_;
+ }
+
+ // Set whether the window should stay above other windows which are not
+ // configured to be always-on-top.
+ void SetAlwaysOnTop(bool always_on_top);
+
+ // Whether the always-on-top property has been set by the chrome.app.window
+ // API. Note that the actual value of this property in the native app window
+ // may be false if the bit is silently switched off for security reasons.
+ bool IsAlwaysOnTop() const;
+
+ // Restores the always-on-top property according to |cached_always_on_top_|.
+ void RestoreAlwaysOnTop();
+
+ // Retrieve the current state of the app window as a dictionary, to pass to
+ // the renderer.
+ void GetSerializedState(base::DictionaryValue* properties) const;
+
+ // Called by the window API when events can be sent to the window for this
+ // app.
+ void WindowEventsReady();
+
+ // Notifies the window's contents that the render view is ready and it can
+ // unblock resource requests.
+ void NotifyRenderViewReady();
+
+ // Whether the app window wants to be alpha enabled.
+ bool requested_alpha_enabled() const { return requested_alpha_enabled_; }
+
+ // Whether the app window is created by IME extensions.
+ // TODO(bshe): rename to hide_app_window_in_launcher if it is not used
+ // anywhere other than app_window_launcher_controller after M45. Otherwise,
+ // remove this TODO.
+ bool is_ime_window() const { return is_ime_window_; }
+
+ void SetAppWindowContentsForTesting(scoped_ptr<AppWindowContents> contents) {
+ app_window_contents_ = std::move(contents);
+ }
+
+ protected:
+ ~AppWindow() override;
+
+ private:
+ // PlatformAppBrowserTest needs access to web_contents()
+ friend class PlatformAppBrowserTest;
+
+ // content::WebContentsDelegate implementation.
+ void CloseContents(content::WebContents* contents) override;
+ bool ShouldSuppressDialogs(content::WebContents* source) override;
+ content::ColorChooser* OpenColorChooser(
+ content::WebContents* web_contents,
+ SkColor color,
+ const std::vector<content::ColorSuggestion>& suggestions) override;
+ void RunFileChooser(content::WebContents* tab,
+ const content::FileChooserParams& params) override;
+ bool IsPopupOrPanel(const content::WebContents* source) const override;
+ void MoveContents(content::WebContents* source,
+ const gfx::Rect& pos) override;
+ void NavigationStateChanged(content::WebContents* source,
+ content::InvalidateTypes changed_flags) override;
+ void EnterFullscreenModeForTab(content::WebContents* source,
+ const GURL& origin) override;
+ void ExitFullscreenModeForTab(content::WebContents* source) override;
+ bool IsFullscreenForTabOrPending(
+ const content::WebContents* source) const override;
+ blink::WebDisplayMode GetDisplayMode(
+ const content::WebContents* source) const override;
+ void RequestMediaAccessPermission(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback) override;
+ bool CheckMediaAccessPermission(content::WebContents* web_contents,
+ const GURL& security_origin,
+ content::MediaStreamType type) override;
+ content::WebContents* OpenURLFromTab(
+ content::WebContents* source,
+ const content::OpenURLParams& params) override;
+ void AddNewContents(content::WebContents* source,
+ content::WebContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_rect,
+ bool user_gesture,
+ bool* was_blocked) override;
+ bool PreHandleKeyboardEvent(content::WebContents* source,
+ const content::NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut) override;
+ void HandleKeyboardEvent(
+ content::WebContents* source,
+ const content::NativeWebKeyboardEvent& event) override;
+ void RequestToLockMouse(content::WebContents* web_contents,
+ bool user_gesture,
+ bool last_unlocked_by_target) override;
+ bool PreHandleGestureEvent(content::WebContents* source,
+ const blink::WebGestureEvent& event) override;
+
+ // content::WebContentsObserver implementation.
+ void RenderViewCreated(content::RenderViewHost* render_view_host) override;
+ void DidFirstVisuallyNonEmptyPaint() override;
+
+ // ExtensionFunctionDispatcher::Delegate implementation.
+ WindowController* GetExtensionWindowController() const override;
+ content::WebContents* GetAssociatedWebContents() const override;
+
+ // ExtensionRegistryObserver implementation.
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+
+ // web_modal::WebContentsModalDialogManagerDelegate implementation.
+ void SetWebContentsBlocked(content::WebContents* web_contents,
+ bool blocked) override;
+ bool IsWebContentsVisible(content::WebContents* web_contents) override;
+
+ void ToggleFullscreenModeForTab(content::WebContents* source,
+ bool enter_fullscreen);
+
+ // Saves the window geometry/position/screen bounds.
+ void SaveWindowPosition();
+
+ // Helper method to adjust the cached bounds so that we can make sure it can
+ // be visible on the screen. See http://crbug.com/145752.
+ void AdjustBoundsToBeVisibleOnScreen(const gfx::Rect& cached_bounds,
+ const gfx::Rect& cached_screen_bounds,
+ const gfx::Rect& current_screen_bounds,
+ const gfx::Size& minimum_size,
+ gfx::Rect* bounds) const;
+
+ // Loads the appropriate default or cached window bounds. Returns a new
+ // CreateParams that should be used to create the window.
+ CreateParams LoadDefaults(CreateParams params) const;
+
+ // Load the app's image, firing a load state change when loaded.
+ void UpdateExtensionAppIcon();
+
+ // Set the fullscreen state in the native app window.
+ void SetNativeWindowFullscreen();
+
+ // Returns true if there is any overlap between the window and the taskbar
+ // (Windows only).
+ bool IntersectsWithTaskbar() const;
+
+ // Update the always-on-top bit in the native app window.
+ void UpdateNativeAlwaysOnTop();
+
+ // Sends the onWindowShown event to the app if the window has been shown. Only
+ // has an effect in tests.
+ void SendOnWindowShownIfShown();
+
+ // web_modal::WebContentsModalDialogManagerDelegate implementation.
+ web_modal::WebContentsModalDialogHost* GetWebContentsModalDialogHost()
+ override;
+
+ // Callback from web_contents()->DownloadFavicon.
+ void DidDownloadFavicon(int id,
+ int http_status_code,
+ const GURL& image_url,
+ const std::vector<SkBitmap>& bitmaps,
+ const std::vector<gfx::Size>& original_bitmap_sizes);
+
+ // IconImage::Observer implementation.
+ void OnExtensionIconImageChanged(IconImage* image) override;
+
+ // The browser context with which this window is associated. AppWindow does
+ // not own this object.
+ content::BrowserContext* browser_context_;
+
+ const std::string extension_id_;
+
+ // Identifier that is used when saving and restoring geometry for this
+ // window.
+ std::string window_key_;
+
+ const SessionID session_id_;
+ WindowType window_type_;
+
+ // Icon shown in the task bar.
+ gfx::Image app_icon_;
+
+ // Icon URL to be used for setting the app icon. If not empty, app_icon_ will
+ // be fetched and set using this URL.
+ GURL app_icon_url_;
+
+ // An object to load the app's icon as an extension resource.
+ scoped_ptr<IconImage> app_icon_image_;
+
+ scoped_ptr<NativeAppWindow> native_app_window_;
+ scoped_ptr<AppWindowContents> app_window_contents_;
+ scoped_ptr<AppDelegate> app_delegate_;
+ scoped_ptr<AppWebContentsHelper> helper_;
+
+ // The initial url this AppWindow was navigated to.
+ GURL initial_url_;
+
+ // Bit field of FullscreenType.
+ int fullscreen_types_;
+
+ // Show has been called, so the window should be shown once the first visually
+ // non-empty paint occurs.
+ bool show_on_first_paint_;
+
+ // The first visually non-empty paint has completed.
+ bool first_paint_complete_;
+
+ // Whether the window has been shown or not.
+ bool has_been_shown_;
+
+ // Whether events can be sent to the window.
+ bool can_send_events_;
+
+ // Whether the window is hidden or not. Hidden in this context means actively
+ // by the chrome.app.window API, not in an operating system context. For
+ // example windows which are minimized are not hidden, and windows which are
+ // part of a hidden app on OS X are not hidden. Windows which were created
+ // with the |hidden| flag set to true, or which have been programmatically
+ // hidden, are considered hidden.
+ bool is_hidden_;
+
+ // Whether the delayed Show() call was for an active or inactive window.
+ ShowType delayed_show_type_;
+
+ // Cache the desired value of the always-on-top property. When windows enter
+ // fullscreen or overlap the Windows taskbar, this property will be
+ // automatically and silently switched off for security reasons. It is
+ // reinstated when the window exits fullscreen and moves away from the
+ // taskbar.
+ bool cached_always_on_top_;
+
+ // Whether |alpha_enabled| was set in the CreateParams.
+ bool requested_alpha_enabled_;
+
+ // Whether |is_ime_window| was set in the CreateParams.
+ bool is_ime_window_;
+
+ // PlzNavigate: this is called when the first navigation is ready to commit.
+ base::Closure on_first_commit_callback_;
+
+ base::WeakPtrFactory<AppWindow> image_loader_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppWindow);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_APP_WINDOW_APP_WINDOW_H_
diff --git a/chromium/extensions/browser/app_window/app_window_browsertest.cc b/chromium/extensions/browser/app_window/app_window_browsertest.cc
new file mode 100644
index 00000000000..396c882e3d1
--- /dev/null
+++ b/chromium/extensions/browser/app_window/app_window_browsertest.cc
@@ -0,0 +1,71 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "build/build_config.h"
+#include "chrome/browser/apps/app_browsertest_util.h"
+#include "extensions/browser/app_window/native_app_window.h"
+
+namespace extensions {
+
+namespace {
+
+typedef PlatformAppBrowserTest AppWindowBrowserTest;
+
+// This test is disabled on Linux because of the unpredictable nature of native
+// windows. We cannot assume that the window manager will insert any title bar
+// at all, so the test may fail on certain window managers.
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+#define MAYBE_FrameInsetsForDefaultFrame DISABLED_FrameInsetsForDefaultFrame
+#else
+#define MAYBE_FrameInsetsForDefaultFrame FrameInsetsForDefaultFrame
+#endif
+
+// Verifies that the NativeAppWindows implement GetFrameInsets() correctly.
+// See http://crbug.com/346115
+IN_PROC_BROWSER_TEST_F(AppWindowBrowserTest, MAYBE_FrameInsetsForDefaultFrame) {
+ AppWindow* app_window = CreateTestAppWindow("{}");
+ NativeAppWindow* native_window = app_window->GetBaseWindow();
+ gfx::Insets insets = native_window->GetFrameInsets();
+
+ // It is a reasonable assumption that the top padding must be greater than
+ // the bottom padding due to the title bar.
+ EXPECT_GT(insets.top(), insets.bottom());
+
+ CloseAppWindow(app_window);
+}
+
+// Verifies that the NativeAppWindows implement GetFrameInsets() correctly.
+// See http://crbug.com/346115
+IN_PROC_BROWSER_TEST_F(AppWindowBrowserTest, FrameInsetsForColoredFrame) {
+ AppWindow* app_window =
+ CreateTestAppWindow("{ \"frame\": { \"color\": \"#ffffff\" } }");
+ NativeAppWindow* native_window = app_window->GetBaseWindow();
+ gfx::Insets insets = native_window->GetFrameInsets();
+
+ // It is a reasonable assumption that the top padding must be greater than
+ // the bottom padding due to the title bar.
+ EXPECT_GT(insets.top(), insets.bottom());
+
+ CloseAppWindow(app_window);
+}
+
+// Verifies that the NativeAppWindows implement GetFrameInsets() correctly for
+// frameless windows.
+IN_PROC_BROWSER_TEST_F(AppWindowBrowserTest, FrameInsetsForNoFrame) {
+ AppWindow* app_window = CreateTestAppWindow("{ \"frame\": \"none\" }");
+ NativeAppWindow* native_window = app_window->GetBaseWindow();
+ gfx::Insets insets = native_window->GetFrameInsets();
+
+ // All insets must be zero.
+ EXPECT_EQ(0, insets.top());
+ EXPECT_EQ(0, insets.bottom());
+ EXPECT_EQ(0, insets.left());
+ EXPECT_EQ(0, insets.right());
+
+ CloseAppWindow(app_window);
+}
+
+} // namespace
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/app_window/app_window_client.cc b/chromium/extensions/browser/app_window/app_window_client.cc
new file mode 100644
index 00000000000..4b71b96b134
--- /dev/null
+++ b/chromium/extensions/browser/app_window/app_window_client.cc
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/app_window/app_window_client.h"
+
+
+namespace extensions {
+
+namespace {
+
+AppWindowClient* g_client = NULL;
+
+} // namespace
+
+AppWindowClient* AppWindowClient::Get() {
+ return g_client;
+}
+
+void AppWindowClient::Set(AppWindowClient* client) {
+ // This can happen in unit tests, where the utility thread runs in-process.
+ if (g_client)
+ return;
+
+ g_client = client;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/app_window/app_window_client.h b/chromium/extensions/browser/app_window/app_window_client.h
new file mode 100644
index 00000000000..f026d809760
--- /dev/null
+++ b/chromium/extensions/browser/app_window/app_window_client.h
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_APP_WINDOW_APP_WINDOW_CLIENT_H_
+#define EXTENSIONS_BROWSER_APP_WINDOW_APP_WINDOW_CLIENT_H_
+
+#include "base/callback_forward.h"
+#include "extensions/browser/app_window/app_window.h"
+
+namespace content {
+class BrowserContext;
+class WebContents;
+}
+
+namespace extensions {
+
+class Extension;
+class NativeAppWindow;
+
+// Sets up global state for the app window system. Should be Set() once in each
+// process. This should be implemented by the client of the app window system.
+// TODO(hashimoto): Move some functions to ExtensionsClient.
+class AppWindowClient {
+ public:
+ virtual ~AppWindowClient() {}
+
+ // Creates a new AppWindow for the app in |extension| for |context|.
+ // Caller takes ownership.
+ virtual AppWindow* CreateAppWindow(content::BrowserContext* context,
+ const Extension* extension) = 0;
+
+ // Creates a new extensions::NativeAppWindow for |window|.
+ virtual NativeAppWindow* CreateNativeAppWindow(
+ AppWindow* window,
+ AppWindow::CreateParams* params) = 0;
+
+ // Opens DevTools window and runs the callback.
+ virtual void OpenDevToolsWindow(content::WebContents* web_contents,
+ const base::Closure& callback) = 0;
+
+ // Returns true if the current channel is older than dev.
+ virtual bool IsCurrentChannelOlderThanDev() = 0;
+
+ // Return the app window client.
+ static AppWindowClient* Get();
+
+ // Initialize the app window system with this app window client.
+ static void Set(AppWindowClient* client);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_APP_WINDOW_APP_WINDOW_CLIENT_H_
diff --git a/chromium/extensions/browser/app_window/app_window_contents.cc b/chromium/extensions/browser/app_window/app_window_contents.cc
new file mode 100644
index 00000000000..3cb0cb850de
--- /dev/null
+++ b/chromium/extensions/browser/app_window/app_window_contents.cc
@@ -0,0 +1,139 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/app_window/app_window_contents.h"
+
+#include <string>
+#include <utility>
+
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/resource_dispatcher_host.h"
+#include "content/public/browser/site_instance.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/renderer_preferences.h"
+#include "extensions/browser/app_window/native_app_window.h"
+#include "extensions/common/extension_messages.h"
+
+namespace extensions {
+
+AppWindowContentsImpl::AppWindowContentsImpl(AppWindow* host)
+ : host_(host), is_blocking_requests_(false), is_window_ready_(false) {}
+
+AppWindowContentsImpl::~AppWindowContentsImpl() {}
+
+void AppWindowContentsImpl::Initialize(content::BrowserContext* context,
+ content::RenderFrameHost* creator_frame,
+ const GURL& url) {
+ url_ = url;
+
+ content::WebContents::CreateParams create_params(
+ context, creator_frame->GetSiteInstance());
+ create_params.opener_render_process_id = creator_frame->GetProcess()->GetID();
+ create_params.opener_render_frame_id = creator_frame->GetRoutingID();
+ web_contents_.reset(content::WebContents::Create(create_params));
+
+ Observe(web_contents_.get());
+ web_contents_->GetMutableRendererPrefs()->
+ browser_handles_all_top_level_requests = true;
+ web_contents_->GetRenderViewHost()->SyncRendererPrefs();
+}
+
+void AppWindowContentsImpl::LoadContents(int32_t creator_process_id) {
+ // If the new view is in the same process as the creator, block the created
+ // RVH from loading anything until the background page has had a chance to
+ // do any initialization it wants. If it's a different process, the new RVH
+ // shouldn't communicate with the background page anyway (e.g. sandboxed).
+ if (web_contents_->GetMainFrame()->GetProcess()->GetID() ==
+ creator_process_id) {
+ SuspendRenderFrameHost(web_contents_->GetMainFrame());
+ } else {
+ VLOG(1) << "AppWindow created in new process ("
+ << web_contents_->GetMainFrame()->GetProcess()->GetID()
+ << ") != creator (" << creator_process_id << "). Routing disabled.";
+ }
+
+ web_contents_->GetController().LoadURL(
+ url_, content::Referrer(), ui::PAGE_TRANSITION_LINK,
+ std::string());
+}
+
+void AppWindowContentsImpl::NativeWindowChanged(
+ NativeAppWindow* native_app_window) {
+ base::ListValue args;
+ base::DictionaryValue* dictionary = new base::DictionaryValue();
+ args.Append(dictionary);
+ host_->GetSerializedState(dictionary);
+
+ content::RenderFrameHost* rfh = web_contents_->GetMainFrame();
+ rfh->Send(new ExtensionMsg_MessageInvoke(
+ rfh->GetRoutingID(), host_->extension_id(), "app.window",
+ "updateAppWindowProperties", args, false));
+}
+
+void AppWindowContentsImpl::NativeWindowClosed() {
+ content::RenderViewHost* rvh = web_contents_->GetRenderViewHost();
+ rvh->Send(new ExtensionMsg_AppWindowClosed(rvh->GetRoutingID()));
+}
+
+void AppWindowContentsImpl::DispatchWindowShownForTests() const {
+ base::ListValue args;
+ content::RenderFrameHost* rfh = web_contents_->GetMainFrame();
+ rfh->Send(new ExtensionMsg_MessageInvoke(
+ rfh->GetRoutingID(), host_->extension_id(), "app.window",
+ "appWindowShownForTests", args, false));
+}
+
+void AppWindowContentsImpl::OnWindowReady() {
+ is_window_ready_ = true;
+ if (is_blocking_requests_) {
+ is_blocking_requests_ = false;
+ content::ResourceDispatcherHost::ResumeBlockedRequestsForFrameFromUI(
+ web_contents_->GetMainFrame());
+ }
+}
+
+content::WebContents* AppWindowContentsImpl::GetWebContents() const {
+ return web_contents_.get();
+}
+
+WindowController* AppWindowContentsImpl::GetWindowController() const {
+ return nullptr;
+}
+
+bool AppWindowContentsImpl::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(AppWindowContentsImpl, message)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_UpdateDraggableRegions,
+ UpdateDraggableRegions)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void AppWindowContentsImpl::ReadyToCommitNavigation(
+ content::NavigationHandle* handle) {
+ if (!is_window_ready_)
+ host_->OnReadyToCommitFirstNavigation();
+}
+
+void AppWindowContentsImpl::UpdateDraggableRegions(
+ const std::vector<DraggableRegion>& regions) {
+ host_->UpdateDraggableRegions(regions);
+}
+
+void AppWindowContentsImpl::SuspendRenderFrameHost(
+ content::RenderFrameHost* rfh) {
+ DCHECK(rfh);
+ // Don't bother blocking requests if the renderer side is already good to go.
+ if (is_window_ready_)
+ return;
+ is_blocking_requests_ = true;
+ content::ResourceDispatcherHost::BlockRequestsForFrameFromUI(rfh);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/app_window/app_window_contents.h b/chromium/extensions/browser/app_window/app_window_contents.h
new file mode 100644
index 00000000000..732197c391c
--- /dev/null
+++ b/chromium/extensions/browser/app_window/app_window_contents.h
@@ -0,0 +1,65 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_APP_WINDOW_APP_WINDOW_CONTENTS_H_
+#define EXTENSIONS_BROWSER_APP_WINDOW_APP_WINDOW_CONTENTS_H_
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "url/gurl.h"
+
+namespace content {
+class BrowserContext;
+class RenderFrameHost;
+}
+
+namespace extensions {
+
+struct DraggableRegion;
+
+// AppWindowContents class specific to app windows. It maintains a
+// WebContents instance and observes it for the purpose of passing
+// messages to the extensions system.
+class AppWindowContentsImpl : public AppWindowContents,
+ public content::WebContentsObserver {
+ public:
+ explicit AppWindowContentsImpl(AppWindow* host);
+ ~AppWindowContentsImpl() override;
+
+ // AppWindowContents
+ void Initialize(content::BrowserContext* context,
+ content::RenderFrameHost* creator_frame,
+ const GURL& url) override;
+ void LoadContents(int32_t creator_process_id) override;
+ void NativeWindowChanged(NativeAppWindow* native_app_window) override;
+ void NativeWindowClosed() override;
+ void DispatchWindowShownForTests() const override;
+ void OnWindowReady() override;
+ content::WebContents* GetWebContents() const override;
+ WindowController* GetWindowController() const override;
+
+ private:
+ // content::WebContentsObserver
+ bool OnMessageReceived(const IPC::Message& message) override;
+ void ReadyToCommitNavigation(content::NavigationHandle* handle) override;
+
+ void UpdateDraggableRegions(const std::vector<DraggableRegion>& regions);
+ void SuspendRenderFrameHost(content::RenderFrameHost* rfh);
+
+ AppWindow* host_; // This class is owned by |host_|
+ GURL url_;
+ scoped_ptr<content::WebContents> web_contents_;
+ bool is_blocking_requests_;
+ bool is_window_ready_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppWindowContentsImpl);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_APP_WINDOW_APP_WINDOW_CONTENTS_H_
diff --git a/chromium/extensions/browser/app_window/app_window_geometry_cache.cc b/chromium/extensions/browser/app_window/app_window_geometry_cache.cc
new file mode 100644
index 00000000000..adc8631d9d1
--- /dev/null
+++ b/chromium/extensions/browser/app_window/app_window_geometry_cache.cc
@@ -0,0 +1,309 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/app_window/app_window_geometry_cache.h"
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_prefs_factory.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/common/extension.h"
+
+namespace {
+
+// The timeout in milliseconds before we'll persist window geometry to the
+// StateStore.
+const int kSyncTimeoutMilliseconds = 1000;
+
+} // namespace
+
+namespace extensions {
+
+AppWindowGeometryCache::AppWindowGeometryCache(content::BrowserContext* context,
+ ExtensionPrefs* prefs)
+ : prefs_(prefs),
+ sync_delay_(base::TimeDelta::FromMilliseconds(kSyncTimeoutMilliseconds)),
+ extension_registry_observer_(this) {
+ extension_registry_observer_.Add(ExtensionRegistry::Get(context));
+}
+
+AppWindowGeometryCache::~AppWindowGeometryCache() {}
+
+// static
+AppWindowGeometryCache* AppWindowGeometryCache::Get(
+ content::BrowserContext* context) {
+ return Factory::GetForContext(context, true /* create */);
+}
+
+void AppWindowGeometryCache::SaveGeometry(const std::string& extension_id,
+ const std::string& window_id,
+ const gfx::Rect& bounds,
+ const gfx::Rect& screen_bounds,
+ ui::WindowShowState window_state) {
+ ExtensionData& extension_data = cache_[extension_id];
+
+ // If we don't have any unsynced changes and this is a duplicate of what's
+ // already in the cache, just ignore it.
+ if (extension_data[window_id].bounds == bounds &&
+ extension_data[window_id].window_state == window_state &&
+ extension_data[window_id].screen_bounds == screen_bounds &&
+ !ContainsKey(unsynced_extensions_, extension_id))
+ return;
+
+ base::Time now = base::Time::Now();
+
+ extension_data[window_id].bounds = bounds;
+ extension_data[window_id].screen_bounds = screen_bounds;
+ extension_data[window_id].window_state = window_state;
+ extension_data[window_id].last_change = now;
+
+ if (extension_data.size() > kMaxCachedWindows) {
+ ExtensionData::iterator oldest = extension_data.end();
+ // Too many windows in the cache, find the oldest one to remove.
+ for (ExtensionData::iterator it = extension_data.begin();
+ it != extension_data.end();
+ ++it) {
+ // Don't expunge the window that was just added.
+ if (it->first == window_id)
+ continue;
+
+ // If time is in the future, reset it to now to minimize weirdness.
+ if (it->second.last_change > now)
+ it->second.last_change = now;
+
+ if (oldest == extension_data.end() ||
+ it->second.last_change < oldest->second.last_change)
+ oldest = it;
+ }
+ extension_data.erase(oldest);
+ }
+
+ unsynced_extensions_.insert(extension_id);
+
+ // We don't use Reset() because the timer may not yet be running.
+ // (In that case Stop() is a no-op.)
+ sync_timer_.Stop();
+ sync_timer_.Start(
+ FROM_HERE, sync_delay_, this, &AppWindowGeometryCache::SyncToStorage);
+}
+
+void AppWindowGeometryCache::SyncToStorage() {
+ std::set<std::string> tosync;
+ tosync.swap(unsynced_extensions_);
+ for (std::set<std::string>::const_iterator it = tosync.begin(),
+ eit = tosync.end();
+ it != eit;
+ ++it) {
+ const std::string& extension_id = *it;
+ const ExtensionData& extension_data = cache_[extension_id];
+
+ scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+ for (ExtensionData::const_iterator it = extension_data.begin(),
+ eit = extension_data.end();
+ it != eit;
+ ++it) {
+ base::DictionaryValue* value = new base::DictionaryValue;
+ const gfx::Rect& bounds = it->second.bounds;
+ const gfx::Rect& screen_bounds = it->second.screen_bounds;
+ DCHECK(!bounds.IsEmpty());
+ DCHECK(!screen_bounds.IsEmpty());
+ DCHECK(it->second.window_state != ui::SHOW_STATE_DEFAULT);
+ value->SetInteger("x", bounds.x());
+ value->SetInteger("y", bounds.y());
+ value->SetInteger("w", bounds.width());
+ value->SetInteger("h", bounds.height());
+ value->SetInteger("screen_bounds_x", screen_bounds.x());
+ value->SetInteger("screen_bounds_y", screen_bounds.y());
+ value->SetInteger("screen_bounds_w", screen_bounds.width());
+ value->SetInteger("screen_bounds_h", screen_bounds.height());
+ value->SetInteger("state", it->second.window_state);
+ value->SetString(
+ "ts", base::Int64ToString(it->second.last_change.ToInternalValue()));
+ dict->SetWithoutPathExpansion(it->first, value);
+
+ FOR_EACH_OBSERVER(
+ Observer,
+ observers_,
+ OnGeometryCacheChanged(extension_id, it->first, bounds));
+ }
+
+ prefs_->SetGeometryCache(extension_id, std::move(dict));
+ }
+}
+
+bool AppWindowGeometryCache::GetGeometry(const std::string& extension_id,
+ const std::string& window_id,
+ gfx::Rect* bounds,
+ gfx::Rect* screen_bounds,
+ ui::WindowShowState* window_state) {
+ std::map<std::string, ExtensionData>::const_iterator extension_data_it =
+ cache_.find(extension_id);
+
+ // Not in the map means loading data for the extension didn't finish yet or
+ // the cache was not constructed until after the extension was loaded.
+ // Attempt to load from sync to address the latter case.
+ if (extension_data_it == cache_.end()) {
+ LoadGeometryFromStorage(extension_id);
+ extension_data_it = cache_.find(extension_id);
+ DCHECK(extension_data_it != cache_.end());
+ }
+
+ ExtensionData::const_iterator window_data_it =
+ extension_data_it->second.find(window_id);
+
+ if (window_data_it == extension_data_it->second.end())
+ return false;
+
+ const WindowData& window_data = window_data_it->second;
+
+ // Check for and do not return corrupt data.
+ if ((bounds && window_data.bounds.IsEmpty()) ||
+ (screen_bounds && window_data.screen_bounds.IsEmpty()) ||
+ (window_state && window_data.window_state == ui::SHOW_STATE_DEFAULT))
+ return false;
+
+ if (bounds)
+ *bounds = window_data.bounds;
+ if (screen_bounds)
+ *screen_bounds = window_data.screen_bounds;
+ if (window_state)
+ *window_state = window_data.window_state;
+ return true;
+}
+
+void AppWindowGeometryCache::Shutdown() { SyncToStorage(); }
+
+AppWindowGeometryCache::WindowData::WindowData()
+ : window_state(ui::SHOW_STATE_DEFAULT) {}
+
+AppWindowGeometryCache::WindowData::~WindowData() {}
+
+void AppWindowGeometryCache::OnExtensionLoaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension) {
+ LoadGeometryFromStorage(extension->id());
+}
+
+void AppWindowGeometryCache::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ SyncToStorage();
+ cache_.erase(extension->id());
+}
+
+void AppWindowGeometryCache::SetSyncDelayForTests(int timeout_ms) {
+ sync_delay_ = base::TimeDelta::FromMilliseconds(timeout_ms);
+}
+
+void AppWindowGeometryCache::LoadGeometryFromStorage(
+ const std::string& extension_id) {
+ ExtensionData& extension_data = cache_[extension_id];
+
+ const base::DictionaryValue* stored_windows =
+ prefs_->GetGeometryCache(extension_id);
+ if (!stored_windows)
+ return;
+
+ for (base::DictionaryValue::Iterator it(*stored_windows); !it.IsAtEnd();
+ it.Advance()) {
+ // If the cache already contains geometry for this window, don't
+ // overwrite that information since it is probably the result of an
+ // application starting up very quickly.
+ const std::string& window_id = it.key();
+ ExtensionData::iterator cached_window = extension_data.find(window_id);
+ if (cached_window == extension_data.end()) {
+ const base::DictionaryValue* stored_window;
+ if (it.value().GetAsDictionary(&stored_window)) {
+ WindowData& window_data = extension_data[it.key()];
+
+ int i;
+ if (stored_window->GetInteger("x", &i))
+ window_data.bounds.set_x(i);
+ if (stored_window->GetInteger("y", &i))
+ window_data.bounds.set_y(i);
+ if (stored_window->GetInteger("w", &i))
+ window_data.bounds.set_width(i);
+ if (stored_window->GetInteger("h", &i))
+ window_data.bounds.set_height(i);
+ if (stored_window->GetInteger("screen_bounds_x", &i))
+ window_data.screen_bounds.set_x(i);
+ if (stored_window->GetInteger("screen_bounds_y", &i))
+ window_data.screen_bounds.set_y(i);
+ if (stored_window->GetInteger("screen_bounds_w", &i))
+ window_data.screen_bounds.set_width(i);
+ if (stored_window->GetInteger("screen_bounds_h", &i))
+ window_data.screen_bounds.set_height(i);
+ if (stored_window->GetInteger("state", &i)) {
+ window_data.window_state = static_cast<ui::WindowShowState>(i);
+ }
+ std::string ts_as_string;
+ if (stored_window->GetString("ts", &ts_as_string)) {
+ int64_t ts;
+ if (base::StringToInt64(ts_as_string, &ts)) {
+ window_data.last_change = base::Time::FromInternalValue(ts);
+ }
+ }
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Factory boilerplate
+
+// static
+AppWindowGeometryCache* AppWindowGeometryCache::Factory::GetForContext(
+ content::BrowserContext* context,
+ bool create) {
+ return static_cast<AppWindowGeometryCache*>(
+ GetInstance()->GetServiceForBrowserContext(context, create));
+}
+
+AppWindowGeometryCache::Factory*
+AppWindowGeometryCache::Factory::GetInstance() {
+ return base::Singleton<AppWindowGeometryCache::Factory>::get();
+}
+
+AppWindowGeometryCache::Factory::Factory()
+ : BrowserContextKeyedServiceFactory(
+ "AppWindowGeometryCache",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(ExtensionPrefsFactory::GetInstance());
+}
+
+AppWindowGeometryCache::Factory::~Factory() {}
+
+KeyedService* AppWindowGeometryCache::Factory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ return new AppWindowGeometryCache(context, ExtensionPrefs::Get(context));
+}
+
+bool AppWindowGeometryCache::Factory::ServiceIsNULLWhileTesting() const {
+ return false;
+}
+
+content::BrowserContext*
+AppWindowGeometryCache::Factory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+}
+
+void AppWindowGeometryCache::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void AppWindowGeometryCache::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/app_window/app_window_geometry_cache.h b/chromium/extensions/browser/app_window/app_window_geometry_cache.h
new file mode 100644
index 00000000000..1fbdb7333fa
--- /dev/null
+++ b/chromium/extensions/browser/app_window/app_window_geometry_cache.h
@@ -0,0 +1,159 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_APP_WINDOW_APP_WINDOW_GEOMETRY_CACHE_H_
+#define EXTENSIONS_BROWSER_APP_WINDOW_APP_WINDOW_GEOMETRY_CACHE_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/singleton.h"
+#include "base/observer_list.h"
+#include "base/scoped_observer.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "base/values.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace extensions {
+
+class ExtensionPrefs;
+class ExtensionRegistry;
+
+// A cache for persisted geometry of app windows, both to not have to wait
+// for IO when creating a new window, and to not cause IO on every window
+// geometry change.
+class AppWindowGeometryCache : public KeyedService,
+ public ExtensionRegistryObserver {
+ public:
+ class Factory : public BrowserContextKeyedServiceFactory {
+ public:
+ static AppWindowGeometryCache* GetForContext(
+ content::BrowserContext* context,
+ bool create);
+
+ static Factory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<Factory>;
+
+ Factory();
+ ~Factory() override;
+
+ // BrowserContextKeyedServiceFactory
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+ bool ServiceIsNULLWhileTesting() const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+ };
+
+ class Observer {
+ public:
+ virtual void OnGeometryCacheChanged(const std::string& extension_id,
+ const std::string& window_id,
+ const gfx::Rect& bounds) = 0;
+
+ protected:
+ virtual ~Observer() {}
+ };
+
+ AppWindowGeometryCache(content::BrowserContext* context,
+ ExtensionPrefs* prefs);
+
+ ~AppWindowGeometryCache() override;
+
+ // Returns the instance for the given browsing context.
+ static AppWindowGeometryCache* Get(content::BrowserContext* context);
+
+ // Save the geometry and state associated with |extension_id| and |window_id|.
+ void SaveGeometry(const std::string& extension_id,
+ const std::string& window_id,
+ const gfx::Rect& bounds,
+ const gfx::Rect& screen_bounds,
+ ui::WindowShowState state);
+
+ // Get any saved geometry and state associated with |extension_id| and
+ // |window_id|. If saved data exists, sets |bounds|, |screen_bounds| and
+ // |state| if not NULL and returns true.
+ bool GetGeometry(const std::string& extension_id,
+ const std::string& window_id,
+ gfx::Rect* bounds,
+ gfx::Rect* screen_bounds,
+ ui::WindowShowState* state);
+
+ // KeyedService
+ void Shutdown() override;
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Maximum number of windows we'll cache the geometry for per app.
+ static const size_t kMaxCachedWindows = 100;
+
+ protected:
+ friend class AppWindowGeometryCacheTest;
+
+ // For tests, this modifies the timeout delay for saving changes from calls
+ // to SaveGeometry. (Note that even if this is set to 0, you still need to
+ // run the message loop to see the results of any SyncToStorage call).
+ void SetSyncDelayForTests(int timeout_ms);
+
+ private:
+ // Data stored for each window.
+ struct WindowData {
+ WindowData();
+ ~WindowData();
+ gfx::Rect bounds;
+ gfx::Rect screen_bounds;
+ ui::WindowShowState window_state;
+ base::Time last_change;
+ };
+
+ // Data stored for each extension.
+ typedef std::map<std::string, WindowData> ExtensionData;
+
+ // ExtensionRegistryObserver implementation.
+ void OnExtensionLoaded(content::BrowserContext* browser_context,
+ const Extension* extension) override;
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+
+ void LoadGeometryFromStorage(const std::string& extension_id);
+ void SyncToStorage();
+
+ // Preferences storage.
+ ExtensionPrefs* prefs_;
+
+ // Cached data.
+ std::map<std::string, ExtensionData> cache_;
+
+ // Data that still needs saving.
+ std::set<std::string> unsynced_extensions_;
+
+ // The timer used to save the data.
+ base::OneShotTimer sync_timer_;
+
+ // The timeout value we'll use for |sync_timer_|.
+ base::TimeDelta sync_delay_;
+
+ // Listen to extension load, unloaded notifications.
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+
+ base::ObserverList<Observer> observers_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_APP_WINDOW_APP_WINDOW_GEOMETRY_CACHE_H_
diff --git a/chromium/extensions/browser/app_window/app_window_geometry_cache_unittest.cc b/chromium/extensions/browser/app_window/app_window_geometry_cache_unittest.cc
new file mode 100644
index 00000000000..3c664c659fe
--- /dev/null
+++ b/chromium/extensions/browser/app_window/app_window_geometry_cache_unittest.cc
@@ -0,0 +1,433 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/app_window/app_window_geometry_cache.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/mock_pref_change_callback.h"
+#include "components/prefs/pref_service_factory.h"
+#include "components/prefs/testing_pref_store.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread.h"
+#include "content/public/test/test_utils.h"
+#include "extensions/browser/extension_pref_value_map.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extensions_test.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/value_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+namespace {
+const char kWindowId[] = "windowid";
+const char kWindowId2[] = "windowid2";
+
+// Create a very simple extension with id.
+scoped_refptr<Extension> CreateExtension(const std::string& id) {
+ return ExtensionBuilder()
+ .SetManifest(
+ DictionaryBuilder().Set("name", "test").Set("version", "0.1").Build())
+ .SetID(id)
+ .Build();
+}
+
+} // namespace
+
+// Base class for tests.
+class AppWindowGeometryCacheTest : public ExtensionsTest {
+ public:
+ AppWindowGeometryCacheTest()
+ : ui_thread_(BrowserThread::UI, &ui_message_loop_) {}
+
+ // testing::Test overrides:
+ void SetUp() override;
+ void TearDown() override;
+
+ void AddGeometryAndLoadExtension(const std::string& extension_id,
+ const std::string& window_id,
+ const gfx::Rect& bounds,
+ const gfx::Rect& screen_bounds,
+ ui::WindowShowState state);
+
+ // Spins the UI threads' message loops to make sure any task
+ // posted to sync the geometry to the value store gets a chance to run.
+ void WaitForSync();
+
+ void LoadExtension(const std::string& extension_id);
+ void UnloadExtension(const std::string& extension_id);
+
+ // Creates and adds an extension with associated prefs. Returns the extension
+ // ID.
+ std::string AddExtensionWithPrefs(const std::string& name);
+
+ protected:
+ base::MessageLoopForUI ui_message_loop_;
+ content::TestBrowserThread ui_thread_;
+ scoped_ptr<ExtensionPrefValueMap> extension_pref_value_map_;
+ scoped_ptr<PrefService> pref_service_;
+ scoped_ptr<ExtensionPrefs> extension_prefs_;
+ scoped_ptr<AppWindowGeometryCache> cache_;
+};
+
+void AppWindowGeometryCacheTest::SetUp() {
+ ExtensionsTest::SetUp();
+
+ // Set up all the dependencies of ExtensionPrefs.
+ extension_pref_value_map_.reset(new ExtensionPrefValueMap);
+ PrefServiceFactory factory;
+ factory.set_user_prefs(new TestingPrefStore);
+ factory.set_extension_prefs(new TestingPrefStore);
+ user_prefs::PrefRegistrySyncable* pref_registry =
+ new user_prefs::PrefRegistrySyncable;
+ // Prefs should be registered before the PrefService is created.
+ ExtensionPrefs::RegisterProfilePrefs(pref_registry);
+ pref_service_ = factory.Create(pref_registry);
+
+ extension_prefs_.reset(ExtensionPrefs::Create(
+ browser_context(), pref_service_.get(),
+ browser_context()->GetPath().AppendASCII("Extensions"),
+ extension_pref_value_map_.get(), false /* extensions_disabled */,
+ std::vector<ExtensionPrefsObserver*>()));
+
+ cache_.reset(
+ new AppWindowGeometryCache(browser_context(), extension_prefs_.get()));
+ cache_->SetSyncDelayForTests(0);
+}
+
+void AppWindowGeometryCacheTest::TearDown() {
+ cache_.reset();
+ extension_prefs_.reset();
+ pref_service_.reset();
+ extension_pref_value_map_.reset();
+
+ ExtensionsTest::TearDown();
+}
+
+void AppWindowGeometryCacheTest::AddGeometryAndLoadExtension(
+ const std::string& extension_id,
+ const std::string& window_id,
+ const gfx::Rect& bounds,
+ const gfx::Rect& screen_bounds,
+ ui::WindowShowState state) {
+ scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+ base::DictionaryValue* value = new base::DictionaryValue;
+ value->SetInteger("x", bounds.x());
+ value->SetInteger("y", bounds.y());
+ value->SetInteger("w", bounds.width());
+ value->SetInteger("h", bounds.height());
+ value->SetInteger("screen_bounds_x", screen_bounds.x());
+ value->SetInteger("screen_bounds_y", screen_bounds.y());
+ value->SetInteger("screen_bounds_w", screen_bounds.width());
+ value->SetInteger("screen_bounds_h", screen_bounds.height());
+ value->SetInteger("state", state);
+ dict->SetWithoutPathExpansion(window_id, value);
+ extension_prefs_->SetGeometryCache(extension_id, std::move(dict));
+ LoadExtension(extension_id);
+}
+
+void AppWindowGeometryCacheTest::WaitForSync() {
+ content::RunAllPendingInMessageLoop();
+}
+
+void AppWindowGeometryCacheTest::LoadExtension(
+ const std::string& extension_id) {
+ cache_->LoadGeometryFromStorage(extension_id);
+ WaitForSync();
+}
+
+void AppWindowGeometryCacheTest::UnloadExtension(
+ const std::string& extension_id) {
+ scoped_refptr<Extension> extension = CreateExtension(extension_id);
+ cache_->OnExtensionUnloaded(browser_context(),
+ extension.get(),
+ UnloadedExtensionInfo::REASON_DISABLE);
+ WaitForSync();
+}
+
+std::string AppWindowGeometryCacheTest::AddExtensionWithPrefs(
+ const std::string& name) {
+ // Generate the extension with a path based on the name so that extensions
+ // with different names will have different IDs.
+ base::FilePath path =
+ browser_context()->GetPath().AppendASCII("Extensions").AppendASCII(name);
+ scoped_refptr<Extension> extension =
+ ExtensionBuilder()
+ .SetManifest(DictionaryBuilder()
+ .Set("name", "test")
+ .Set("version", "0.1")
+ .Build())
+ .SetPath(path)
+ .Build();
+
+ extension_prefs_->OnExtensionInstalled(
+ extension.get(),
+ Extension::ENABLED,
+ syncer::StringOrdinal::CreateInitialOrdinal(),
+ std::string());
+ return extension->id();
+}
+
+// Test getting geometry from an empty store.
+TEST_F(AppWindowGeometryCacheTest, GetGeometryEmptyStore) {
+ const std::string extension_id = AddExtensionWithPrefs("ext1");
+ ASSERT_FALSE(cache_->GetGeometry(extension_id, kWindowId, NULL, NULL, NULL));
+}
+
+// Test getting geometry for an unknown extension.
+TEST_F(AppWindowGeometryCacheTest, GetGeometryUnkownExtension) {
+ const std::string extension_id1 = AddExtensionWithPrefs("ext1");
+ const std::string extension_id2 = AddExtensionWithPrefs("ext2");
+ AddGeometryAndLoadExtension(extension_id1,
+ kWindowId,
+ gfx::Rect(4, 5, 31, 43),
+ gfx::Rect(0, 0, 1600, 900),
+ ui::SHOW_STATE_NORMAL);
+ ASSERT_FALSE(cache_->GetGeometry(extension_id2, kWindowId, NULL, NULL, NULL));
+}
+
+// Test getting geometry for an unknown window in a known extension.
+TEST_F(AppWindowGeometryCacheTest, GetGeometryUnkownWindow) {
+ const std::string extension_id = AddExtensionWithPrefs("ext1");
+ AddGeometryAndLoadExtension(extension_id,
+ kWindowId,
+ gfx::Rect(4, 5, 31, 43),
+ gfx::Rect(0, 0, 1600, 900),
+ ui::SHOW_STATE_NORMAL);
+ ASSERT_FALSE(cache_->GetGeometry(extension_id, kWindowId2, NULL, NULL, NULL));
+}
+
+// Test that loading geometry, screen_bounds and state from the store works
+// correctly.
+TEST_F(AppWindowGeometryCacheTest, GetGeometryAndStateFromStore) {
+ const std::string extension_id = AddExtensionWithPrefs("ext1");
+ gfx::Rect bounds(4, 5, 31, 43);
+ gfx::Rect screen_bounds(0, 0, 1600, 900);
+ ui::WindowShowState state = ui::SHOW_STATE_NORMAL;
+ AddGeometryAndLoadExtension(
+ extension_id, kWindowId, bounds, screen_bounds, state);
+ gfx::Rect new_bounds;
+ gfx::Rect new_screen_bounds;
+ ui::WindowShowState new_state = ui::SHOW_STATE_DEFAULT;
+ ASSERT_TRUE(cache_->GetGeometry(
+ extension_id, kWindowId, &new_bounds, &new_screen_bounds, &new_state));
+ ASSERT_EQ(bounds, new_bounds);
+ ASSERT_EQ(screen_bounds, new_screen_bounds);
+ ASSERT_EQ(state, new_state);
+}
+
+// Test corrupt bounds will not be loaded.
+TEST_F(AppWindowGeometryCacheTest, CorruptBounds) {
+ const std::string extension_id = AddExtensionWithPrefs("ext1");
+ gfx::Rect bounds;
+ gfx::Rect screen_bounds(0, 0, 1600, 900);
+ ui::WindowShowState state = ui::SHOW_STATE_NORMAL;
+ AddGeometryAndLoadExtension(
+ extension_id, kWindowId, bounds, screen_bounds, state);
+ gfx::Rect new_bounds;
+ gfx::Rect new_screen_bounds;
+ ui::WindowShowState new_state = ui::SHOW_STATE_DEFAULT;
+ ASSERT_FALSE(cache_->GetGeometry(
+ extension_id, kWindowId, &new_bounds, &new_screen_bounds, &new_state));
+ ASSERT_TRUE(new_bounds.IsEmpty());
+ ASSERT_TRUE(new_screen_bounds.IsEmpty());
+ ASSERT_EQ(new_state, ui::SHOW_STATE_DEFAULT);
+}
+
+// Test corrupt screen bounds will not be loaded.
+TEST_F(AppWindowGeometryCacheTest, CorruptScreenBounds) {
+ const std::string extension_id = AddExtensionWithPrefs("ext1");
+ gfx::Rect bounds(4, 5, 31, 43);
+ gfx::Rect screen_bounds;
+ ui::WindowShowState state = ui::SHOW_STATE_NORMAL;
+ AddGeometryAndLoadExtension(
+ extension_id, kWindowId, bounds, screen_bounds, state);
+ gfx::Rect new_bounds;
+ gfx::Rect new_screen_bounds;
+ ui::WindowShowState new_state = ui::SHOW_STATE_DEFAULT;
+ ASSERT_FALSE(cache_->GetGeometry(
+ extension_id, kWindowId, &new_bounds, &new_screen_bounds, &new_state));
+ ASSERT_TRUE(new_bounds.IsEmpty());
+ ASSERT_TRUE(new_screen_bounds.IsEmpty());
+ ASSERT_EQ(new_state, ui::SHOW_STATE_DEFAULT);
+}
+
+// Test corrupt state will not be loaded.
+TEST_F(AppWindowGeometryCacheTest, CorruptState) {
+ const std::string extension_id = AddExtensionWithPrefs("ext1");
+ gfx::Rect bounds(4, 5, 31, 43);
+ gfx::Rect screen_bounds(0, 0, 1600, 900);
+ ui::WindowShowState state = ui::SHOW_STATE_DEFAULT;
+ AddGeometryAndLoadExtension(
+ extension_id, kWindowId, bounds, screen_bounds, state);
+ gfx::Rect new_bounds;
+ gfx::Rect new_screen_bounds;
+ ui::WindowShowState new_state = ui::SHOW_STATE_DEFAULT;
+ ASSERT_FALSE(cache_->GetGeometry(
+ extension_id, kWindowId, &new_bounds, &new_screen_bounds, &new_state));
+ ASSERT_TRUE(new_bounds.IsEmpty());
+ ASSERT_TRUE(new_screen_bounds.IsEmpty());
+ ASSERT_EQ(new_state, ui::SHOW_STATE_DEFAULT);
+}
+
+// Test saving geometry, screen_bounds and state to the cache and state store,
+// and reading it back.
+TEST_F(AppWindowGeometryCacheTest, SaveGeometryAndStateToStore) {
+ const std::string extension_id = AddExtensionWithPrefs("ext1");
+ const std::string window_id(kWindowId);
+
+ // inform cache of extension
+ LoadExtension(extension_id);
+
+ // update geometry stored in cache
+ gfx::Rect bounds(4, 5, 31, 43);
+ gfx::Rect screen_bounds(0, 0, 1600, 900);
+ ui::WindowShowState state = ui::SHOW_STATE_NORMAL;
+ cache_->SaveGeometry(extension_id, window_id, bounds, screen_bounds, state);
+
+ // make sure that immediately reading back geometry works
+ gfx::Rect new_bounds;
+ gfx::Rect new_screen_bounds;
+ ui::WindowShowState new_state = ui::SHOW_STATE_DEFAULT;
+ ASSERT_TRUE(cache_->GetGeometry(
+ extension_id, window_id, &new_bounds, &new_screen_bounds, &new_state));
+ ASSERT_EQ(bounds, new_bounds);
+ ASSERT_EQ(screen_bounds, new_screen_bounds);
+ ASSERT_EQ(state, new_state);
+
+ // unload extension to force cache to save data to the state store
+ UnloadExtension(extension_id);
+
+ // check if geometry got stored correctly in the state store
+ const base::DictionaryValue* dict =
+ extension_prefs_->GetGeometryCache(extension_id);
+ ASSERT_TRUE(dict);
+
+ ASSERT_TRUE(dict->HasKey(window_id));
+ int v;
+ ASSERT_TRUE(dict->GetInteger(window_id + ".x", &v));
+ ASSERT_EQ(bounds.x(), v);
+ ASSERT_TRUE(dict->GetInteger(window_id + ".y", &v));
+ ASSERT_EQ(bounds.y(), v);
+ ASSERT_TRUE(dict->GetInteger(window_id + ".w", &v));
+ ASSERT_EQ(bounds.width(), v);
+ ASSERT_TRUE(dict->GetInteger(window_id + ".h", &v));
+ ASSERT_EQ(bounds.height(), v);
+ ASSERT_TRUE(dict->GetInteger(window_id + ".screen_bounds_x", &v));
+ ASSERT_EQ(screen_bounds.x(), v);
+ ASSERT_TRUE(dict->GetInteger(window_id + ".screen_bounds_y", &v));
+ ASSERT_EQ(screen_bounds.y(), v);
+ ASSERT_TRUE(dict->GetInteger(window_id + ".screen_bounds_w", &v));
+ ASSERT_EQ(screen_bounds.width(), v);
+ ASSERT_TRUE(dict->GetInteger(window_id + ".screen_bounds_h", &v));
+ ASSERT_EQ(screen_bounds.height(), v);
+ ASSERT_TRUE(dict->GetInteger(window_id + ".state", &v));
+ ASSERT_EQ(state, v);
+
+ // reload extension
+ LoadExtension(extension_id);
+ // and make sure the geometry got reloaded properly too
+ ASSERT_TRUE(cache_->GetGeometry(
+ extension_id, window_id, &new_bounds, &new_screen_bounds, &new_state));
+ ASSERT_EQ(bounds, new_bounds);
+ ASSERT_EQ(screen_bounds, new_screen_bounds);
+ ASSERT_EQ(state, new_state);
+}
+
+// Tests that we won't do writes to the state store for SaveGeometry calls
+// which don't change the state we already have.
+TEST_F(AppWindowGeometryCacheTest, NoDuplicateWrites) {
+ using testing::_;
+ using testing::Mock;
+
+ const std::string extension_id = AddExtensionWithPrefs("ext1");
+ gfx::Rect bounds1(100, 200, 300, 400);
+ gfx::Rect bounds2(200, 400, 600, 800);
+ gfx::Rect bounds2_duplicate(200, 400, 600, 800);
+
+ gfx::Rect screen_bounds1(0, 0, 1600, 900);
+ gfx::Rect screen_bounds2(0, 0, 1366, 768);
+ gfx::Rect screen_bounds2_duplicate(0, 0, 1366, 768);
+
+ MockPrefChangeCallback observer(pref_service_.get());
+ PrefChangeRegistrar registrar;
+ registrar.Init(pref_service_.get());
+ registrar.Add("extensions.settings", observer.GetCallback());
+
+ // Write the first bounds - it should do > 0 writes.
+ EXPECT_CALL(observer, OnPreferenceChanged(_));
+ cache_->SaveGeometry(
+ extension_id, kWindowId, bounds1, screen_bounds1, ui::SHOW_STATE_NORMAL);
+ WaitForSync();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Write a different bounds - it should also do > 0 writes.
+ EXPECT_CALL(observer, OnPreferenceChanged(_));
+ cache_->SaveGeometry(
+ extension_id, kWindowId, bounds2, screen_bounds1, ui::SHOW_STATE_NORMAL);
+ WaitForSync();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Write a different screen bounds - it should also do > 0 writes.
+ EXPECT_CALL(observer, OnPreferenceChanged(_));
+ cache_->SaveGeometry(
+ extension_id, kWindowId, bounds2, screen_bounds2, ui::SHOW_STATE_NORMAL);
+ WaitForSync();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Write a different state - it should also do > 0 writes.
+ EXPECT_CALL(observer, OnPreferenceChanged(_));
+ cache_->SaveGeometry(extension_id,
+ kWindowId,
+ bounds2,
+ screen_bounds2,
+ ui::SHOW_STATE_MAXIMIZED);
+ WaitForSync();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Write a bounds, screen bounds and state that's a duplicate of what we
+ // already have. This should not do any writes.
+ EXPECT_CALL(observer, OnPreferenceChanged(_)).Times(0);
+ cache_->SaveGeometry(extension_id,
+ kWindowId,
+ bounds2_duplicate,
+ screen_bounds2_duplicate,
+ ui::SHOW_STATE_MAXIMIZED);
+ WaitForSync();
+ Mock::VerifyAndClearExpectations(&observer);
+}
+
+// Tests that no more than kMaxCachedWindows windows will be cached.
+TEST_F(AppWindowGeometryCacheTest, MaxWindows) {
+ const std::string extension_id = AddExtensionWithPrefs("ext1");
+ // inform cache of extension
+ LoadExtension(extension_id);
+
+ gfx::Rect bounds(4, 5, 31, 43);
+ gfx::Rect screen_bounds(0, 0, 1600, 900);
+ for (size_t i = 0; i < AppWindowGeometryCache::kMaxCachedWindows + 1; ++i) {
+ std::string window_id = "window_" + base::SizeTToString(i);
+ cache_->SaveGeometry(
+ extension_id, window_id, bounds, screen_bounds, ui::SHOW_STATE_NORMAL);
+ }
+
+ // The first added window should no longer have cached geometry.
+ EXPECT_FALSE(cache_->GetGeometry(extension_id, "window_0", NULL, NULL, NULL));
+ // All other windows should still exist.
+ for (size_t i = 1; i < AppWindowGeometryCache::kMaxCachedWindows + 1; ++i) {
+ std::string window_id = "window_" + base::SizeTToString(i);
+ EXPECT_TRUE(cache_->GetGeometry(extension_id, window_id, NULL, NULL, NULL));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/app_window/app_window_interactive_uitest.cc b/chromium/extensions/browser/app_window/app_window_interactive_uitest.cc
new file mode 100644
index 00000000000..cdf5c4c3296
--- /dev/null
+++ b/chromium/extensions/browser/app_window/app_window_interactive_uitest.cc
@@ -0,0 +1,189 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "build/build_config.h"
+#include "chrome/browser/apps/app_browsertest_util.h"
+#include "extensions/browser/app_window/native_app_window.h"
+
+namespace extensions {
+
+namespace {
+
+class AppWindowTest : public PlatformAppBrowserTest {
+ protected:
+ void CheckAlwaysOnTopToFullscreen(AppWindow* window) {
+ ASSERT_TRUE(window->GetBaseWindow()->IsAlwaysOnTop());
+
+ // The always-on-top property should be temporarily disabled when the window
+ // enters fullscreen.
+ window->Fullscreen();
+ EXPECT_FALSE(window->GetBaseWindow()->IsAlwaysOnTop());
+
+ // From the API's point of view, the always-on-top property is enabled.
+ EXPECT_TRUE(window->IsAlwaysOnTop());
+
+ // The always-on-top property is restored when the window exits fullscreen.
+ window->Restore();
+ EXPECT_TRUE(window->GetBaseWindow()->IsAlwaysOnTop());
+ }
+
+ void CheckNormalToFullscreen(AppWindow* window) {
+ // If the always-on-top property is false, it should remain this way when
+ // entering and exiting fullscreen mode.
+ ASSERT_FALSE(window->GetBaseWindow()->IsAlwaysOnTop());
+ window->Fullscreen();
+ EXPECT_FALSE(window->GetBaseWindow()->IsAlwaysOnTop());
+ window->Restore();
+ EXPECT_FALSE(window->GetBaseWindow()->IsAlwaysOnTop());
+ }
+
+ void CheckFullscreenToAlwaysOnTop(AppWindow* window) {
+ ASSERT_TRUE(window->GetBaseWindow()->IsFullscreenOrPending());
+
+ // Now enable always-on-top at runtime and ensure the property does not get
+ // applied immediately because the window is in fullscreen mode.
+ window->SetAlwaysOnTop(true);
+ EXPECT_FALSE(window->GetBaseWindow()->IsAlwaysOnTop());
+
+ // From the API's point of view, the always-on-top property is enabled.
+ EXPECT_TRUE(window->IsAlwaysOnTop());
+
+ // Ensure the always-on-top property is applied when exiting fullscreen.
+ window->Restore();
+ EXPECT_TRUE(window->GetBaseWindow()->IsAlwaysOnTop());
+ }
+};
+
+} // namespace
+
+// Tests are flaky on Mac as transitioning to fullscreen is not instantaneous
+// and throws errors when entering/exiting fullscreen too quickly.
+#if defined(OS_MACOSX)
+#define MAYBE_InitAlwaysOnTopToFullscreen DISABLED_InitAlwaysOnTopToFullscreen
+#else
+#define MAYBE_InitAlwaysOnTopToFullscreen InitAlwaysOnTopToFullscreen
+#endif
+
+// Tests a window created with always-on-top enabled and ensures that the
+// property is temporarily switched off when entering fullscreen mode.
+IN_PROC_BROWSER_TEST_F(AppWindowTest, MAYBE_InitAlwaysOnTopToFullscreen) {
+ AppWindow* window = CreateTestAppWindow("{ \"alwaysOnTop\": true }");
+ ASSERT_TRUE(window);
+ CheckAlwaysOnTopToFullscreen(window);
+
+ window->SetAlwaysOnTop(false);
+ CheckNormalToFullscreen(window);
+
+ CloseAppWindow(window);
+}
+
+#if defined(OS_MACOSX)
+#define MAYBE_RuntimeAlwaysOnTopToFullscreen \
+ DISABLED_RuntimeAlwaysOnTopToFullscreen
+#else
+#define MAYBE_RuntimeAlwaysOnTopToFullscreen RuntimeAlwaysOnTopToFullscreen
+#endif
+
+// Tests a window with always-on-top enabled at runtime and ensures that the
+// property is temporarily switched off when entering fullscreen mode.
+IN_PROC_BROWSER_TEST_F(AppWindowTest, MAYBE_RuntimeAlwaysOnTopToFullscreen) {
+ AppWindow* window = CreateTestAppWindow("{}");
+ ASSERT_TRUE(window);
+ CheckNormalToFullscreen(window);
+
+ window->SetAlwaysOnTop(true);
+ CheckAlwaysOnTopToFullscreen(window);
+
+ CloseAppWindow(window);
+}
+
+#if defined(OS_MACOSX)
+#define MAYBE_InitFullscreenToAlwaysOnTop DISABLED_InitFullscreenToAlwaysOnTop
+#else
+#define MAYBE_InitFullscreenToAlwaysOnTop InitFullscreenToAlwaysOnTop
+#endif
+
+// Tests a window created initially in fullscreen mode and ensures that the
+// always-on-top property does not get applied until it exits fullscreen.
+IN_PROC_BROWSER_TEST_F(AppWindowTest, MAYBE_InitFullscreenToAlwaysOnTop) {
+ AppWindow* window = CreateTestAppWindow("{ \"state\": \"fullscreen\" }");
+ ASSERT_TRUE(window);
+ CheckFullscreenToAlwaysOnTop(window);
+
+ CloseAppWindow(window);
+}
+
+#if defined(OS_MACOSX)
+#define MAYBE_RuntimeFullscreenToAlwaysOnTop \
+ DISABLED_RuntimeFullscreenToAlwaysOnTop
+#else
+#define MAYBE_RuntimeFullscreenToAlwaysOnTop RuntimeFullscreenToAlwaysOnTop
+#endif
+
+// Tests a window that enters fullscreen mode at runtime and ensures that the
+// always-on-top property does not get applied until it exits fullscreen.
+IN_PROC_BROWSER_TEST_F(AppWindowTest, MAYBE_RuntimeFullscreenToAlwaysOnTop) {
+ AppWindow* window = CreateTestAppWindow("{}");
+ ASSERT_TRUE(window);
+
+ window->Fullscreen();
+ CheckFullscreenToAlwaysOnTop(window);
+
+ CloseAppWindow(window);
+}
+
+// Flaky on Windows (see http://crbug.com/581131).
+#if defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_InitFullscreenAndAlwaysOnTop DISABLED_InitFullscreenAndAlwaysOnTop
+#else
+#define MAYBE_InitFullscreenAndAlwaysOnTop InitFullscreenAndAlwaysOnTop
+#endif
+
+// Tests a window created with both fullscreen and always-on-top enabled. Ensure
+// that always-on-top is only applied when the window exits fullscreen.
+IN_PROC_BROWSER_TEST_F(AppWindowTest, MAYBE_InitFullscreenAndAlwaysOnTop) {
+ AppWindow* window = CreateTestAppWindow(
+ "{ \"alwaysOnTop\": true, \"state\": \"fullscreen\" }");
+ ASSERT_TRUE(window);
+
+ EXPECT_TRUE(window->GetBaseWindow()->IsFullscreenOrPending());
+ EXPECT_FALSE(window->GetBaseWindow()->IsAlwaysOnTop());
+
+ // From the API's point of view, the always-on-top property is enabled.
+ EXPECT_TRUE(window->IsAlwaysOnTop());
+
+ window->Restore();
+ EXPECT_TRUE(window->GetBaseWindow()->IsAlwaysOnTop());
+
+ CloseAppWindow(window);
+}
+
+#if defined(OS_MACOSX)
+#define MAYBE_DisableAlwaysOnTopInFullscreen \
+ DISABLED_DisableAlwaysOnTopInFullscreen
+#else
+#define MAYBE_DisableAlwaysOnTopInFullscreen DisableAlwaysOnTopInFullscreen
+#endif
+
+// Tests a window created with always-on-top enabled, but then disabled while
+// in fullscreen mode. After exiting fullscreen, always-on-top should remain
+// disabled.
+IN_PROC_BROWSER_TEST_F(AppWindowTest, MAYBE_DisableAlwaysOnTopInFullscreen) {
+ AppWindow* window = CreateTestAppWindow("{ \"alwaysOnTop\": true }");
+ ASSERT_TRUE(window);
+
+ // Disable always-on-top while in fullscreen mode.
+ window->Fullscreen();
+ EXPECT_FALSE(window->GetBaseWindow()->IsAlwaysOnTop());
+ window->SetAlwaysOnTop(false);
+ EXPECT_FALSE(window->GetBaseWindow()->IsAlwaysOnTop());
+
+ // Ensure that always-on-top remains disabled.
+ window->Restore();
+ EXPECT_FALSE(window->GetBaseWindow()->IsAlwaysOnTop());
+
+ CloseAppWindow(window);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/app_window/app_window_registry.cc b/chromium/extensions/browser/app_window/app_window_registry.cc
new file mode 100644
index 00000000000..6fc0dedccfa
--- /dev/null
+++ b/chromium/extensions/browser/app_window/app_window_registry.cc
@@ -0,0 +1,270 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/app_window/app_window_registry.h"
+
+#include <string>
+#include <vector>
+
+#include "base/strings/stringprintf.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/devtools_agent_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/site_instance.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/native_app_window.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/common/extension.h"
+
+namespace extensions {
+
+void AppWindowRegistry::Observer::OnAppWindowAdded(AppWindow* app_window) {
+}
+
+void AppWindowRegistry::Observer::OnAppWindowIconChanged(
+ AppWindow* app_window) {
+}
+
+void AppWindowRegistry::Observer::OnAppWindowRemoved(AppWindow* app_window) {
+}
+
+void AppWindowRegistry::Observer::OnAppWindowHidden(AppWindow* app_window) {
+}
+
+void AppWindowRegistry::Observer::OnAppWindowShown(AppWindow* app_window,
+ bool was_shown) {
+}
+
+void AppWindowRegistry::Observer::OnAppWindowActivated(AppWindow* app_window) {
+}
+
+AppWindowRegistry::Observer::~Observer() {
+}
+
+AppWindowRegistry::AppWindowRegistry(content::BrowserContext* context)
+ : context_(context),
+ devtools_callback_(base::Bind(&AppWindowRegistry::OnDevToolsStateChanged,
+ base::Unretained(this))) {
+ content::DevToolsAgentHost::AddAgentStateCallback(devtools_callback_);
+}
+
+AppWindowRegistry::~AppWindowRegistry() {
+ content::DevToolsAgentHost::RemoveAgentStateCallback(devtools_callback_);
+}
+
+// static
+AppWindowRegistry* AppWindowRegistry::Get(content::BrowserContext* context) {
+ return Factory::GetForBrowserContext(context, true /* create */);
+}
+
+void AppWindowRegistry::AddAppWindow(AppWindow* app_window) {
+ BringToFront(app_window);
+ FOR_EACH_OBSERVER(Observer, observers_, OnAppWindowAdded(app_window));
+}
+
+void AppWindowRegistry::AppWindowIconChanged(AppWindow* app_window) {
+ AddAppWindowToList(app_window);
+ FOR_EACH_OBSERVER(Observer, observers_, OnAppWindowIconChanged(app_window));
+}
+
+void AppWindowRegistry::AppWindowActivated(AppWindow* app_window) {
+ BringToFront(app_window);
+ FOR_EACH_OBSERVER(Observer, observers_, OnAppWindowActivated(app_window));
+}
+
+void AppWindowRegistry::AppWindowHidden(AppWindow* app_window) {
+ FOR_EACH_OBSERVER(Observer, observers_, OnAppWindowHidden(app_window));
+}
+
+void AppWindowRegistry::AppWindowShown(AppWindow* app_window, bool was_hidden) {
+ FOR_EACH_OBSERVER(Observer, observers_,
+ OnAppWindowShown(app_window, was_hidden));
+}
+
+void AppWindowRegistry::RemoveAppWindow(AppWindow* app_window) {
+ const AppWindowList::iterator it =
+ std::find(app_windows_.begin(), app_windows_.end(), app_window);
+ if (it != app_windows_.end())
+ app_windows_.erase(it);
+ FOR_EACH_OBSERVER(Observer, observers_, OnAppWindowRemoved(app_window));
+}
+
+void AppWindowRegistry::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void AppWindowRegistry::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+AppWindowRegistry::AppWindowList AppWindowRegistry::GetAppWindowsForApp(
+ const std::string& app_id) const {
+ AppWindowList app_windows;
+ for (AppWindowList::const_iterator i = app_windows_.begin();
+ i != app_windows_.end();
+ ++i) {
+ if ((*i)->extension_id() == app_id)
+ app_windows.push_back(*i);
+ }
+ return app_windows;
+}
+
+void AppWindowRegistry::CloseAllAppWindowsForApp(const std::string& app_id) {
+ const AppWindowList windows = GetAppWindowsForApp(app_id);
+ for (AppWindowRegistry::const_iterator it = windows.begin();
+ it != windows.end();
+ ++it) {
+ (*it)->GetBaseWindow()->Close();
+ }
+}
+
+AppWindow* AppWindowRegistry::GetAppWindowForWebContents(
+ const content::WebContents* web_contents) const {
+ for (AppWindow* window : app_windows_) {
+ if (window->web_contents() == web_contents)
+ return window;
+ }
+ return nullptr;
+}
+
+AppWindow* AppWindowRegistry::GetAppWindowForNativeWindow(
+ gfx::NativeWindow window) const {
+ for (AppWindowList::const_iterator i = app_windows_.begin();
+ i != app_windows_.end();
+ ++i) {
+ if ((*i)->GetNativeWindow() == window)
+ return *i;
+ }
+
+ return NULL;
+}
+
+AppWindow* AppWindowRegistry::GetCurrentAppWindowForApp(
+ const std::string& app_id) const {
+ AppWindow* result = NULL;
+ for (AppWindowList::const_iterator i = app_windows_.begin();
+ i != app_windows_.end();
+ ++i) {
+ if ((*i)->extension_id() == app_id) {
+ result = *i;
+ if (result->GetBaseWindow()->IsActive())
+ return result;
+ }
+ }
+
+ return result;
+}
+
+AppWindow* AppWindowRegistry::GetAppWindowForAppAndKey(
+ const std::string& app_id,
+ const std::string& window_key) const {
+ AppWindow* result = NULL;
+ for (AppWindowList::const_iterator i = app_windows_.begin();
+ i != app_windows_.end();
+ ++i) {
+ if ((*i)->extension_id() == app_id && (*i)->window_key() == window_key) {
+ result = *i;
+ if (result->GetBaseWindow()->IsActive())
+ return result;
+ }
+ }
+ return result;
+}
+
+bool AppWindowRegistry::HadDevToolsAttached(
+ content::WebContents* web_contents) const {
+ std::string key = GetWindowKeyForWebContents(web_contents);
+ return key.empty() ? false : inspected_windows_.count(key) != 0;
+}
+
+void AppWindowRegistry::OnDevToolsStateChanged(
+ content::DevToolsAgentHost* agent_host,
+ bool attached) {
+ content::WebContents* web_contents = agent_host->GetWebContents();
+ // Ignore unrelated notifications.
+ if (!web_contents || web_contents->GetBrowserContext() != context_)
+ return;
+
+ std::string key = GetWindowKeyForWebContents(web_contents);
+ if (key.empty())
+ return;
+
+ if (attached)
+ inspected_windows_.insert(key);
+ else
+ inspected_windows_.erase(key);
+}
+
+void AppWindowRegistry::AddAppWindowToList(AppWindow* app_window) {
+ const AppWindowList::iterator it =
+ std::find(app_windows_.begin(), app_windows_.end(), app_window);
+ if (it != app_windows_.end())
+ return;
+ app_windows_.push_back(app_window);
+}
+
+void AppWindowRegistry::BringToFront(AppWindow* app_window) {
+ const AppWindowList::iterator it =
+ std::find(app_windows_.begin(), app_windows_.end(), app_window);
+ if (it != app_windows_.end())
+ app_windows_.erase(it);
+ app_windows_.push_front(app_window);
+}
+
+std::string AppWindowRegistry::GetWindowKeyForWebContents(
+ content::WebContents* web_contents) const {
+ AppWindow* app_window = GetAppWindowForWebContents(web_contents);
+ if (!app_window)
+ return std::string(); // Not an AppWindow.
+
+ if (app_window->window_key().empty())
+ return web_contents->GetURL().possibly_invalid_spec();
+
+ return base::StringPrintf("%s:%s", app_window->extension_id().c_str(),
+ app_window->window_key().c_str());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Factory boilerplate
+
+// static
+AppWindowRegistry* AppWindowRegistry::Factory::GetForBrowserContext(
+ content::BrowserContext* context,
+ bool create) {
+ return static_cast<AppWindowRegistry*>(
+ GetInstance()->GetServiceForBrowserContext(context, create));
+}
+
+AppWindowRegistry::Factory* AppWindowRegistry::Factory::GetInstance() {
+ return base::Singleton<AppWindowRegistry::Factory>::get();
+}
+
+AppWindowRegistry::Factory::Factory()
+ : BrowserContextKeyedServiceFactory(
+ "AppWindowRegistry",
+ BrowserContextDependencyManager::GetInstance()) {}
+
+AppWindowRegistry::Factory::~Factory() {}
+
+KeyedService* AppWindowRegistry::Factory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ return new AppWindowRegistry(context);
+}
+
+bool AppWindowRegistry::Factory::ServiceIsCreatedWithBrowserContext() const {
+ return true;
+}
+
+bool AppWindowRegistry::Factory::ServiceIsNULLWhileTesting() const {
+ return false;
+}
+
+content::BrowserContext* AppWindowRegistry::Factory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/app_window/app_window_registry.h b/chromium/extensions/browser/app_window/app_window_registry.h
new file mode 100644
index 00000000000..d273fbb131c
--- /dev/null
+++ b/chromium/extensions/browser/app_window/app_window_registry.h
@@ -0,0 +1,157 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_APP_WINDOW_APP_WINDOW_REGISTRY_H_
+#define EXTENSIONS_BROWSER_APP_WINDOW_APP_WINDOW_REGISTRY_H_
+
+#include <list>
+#include <set>
+#include <string>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/singleton.h"
+#include "base/observer_list.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace content {
+class BrowserContext;
+class DevToolsAgentHost;
+class WebContents;
+}
+
+namespace extensions {
+
+class AppWindow;
+
+// The AppWindowRegistry tracks the AppWindows for all platform apps for a
+// particular browser context.
+class AppWindowRegistry : public KeyedService {
+ public:
+ class Observer {
+ public:
+ // Called just after a app window was added.
+ virtual void OnAppWindowAdded(AppWindow* app_window);
+ // Called when the window icon changes.
+ virtual void OnAppWindowIconChanged(AppWindow* app_window);
+ // Called just after a app window was removed.
+ virtual void OnAppWindowRemoved(AppWindow* app_window);
+ // Called just after a app window was hidden. This is different from
+ // window visibility as a minimize does not hide a window, but does make
+ // it not visible.
+ virtual void OnAppWindowHidden(AppWindow* app_window);
+ // Called just after a app window was shown.
+ virtual void OnAppWindowShown(AppWindow* app_window, bool was_hidden);
+ // Called just after a app window was activated.
+ virtual void OnAppWindowActivated(AppWindow* app_window);
+
+ protected:
+ virtual ~Observer();
+ };
+
+ typedef std::list<AppWindow*> AppWindowList;
+ typedef AppWindowList::const_iterator const_iterator;
+ typedef std::set<std::string> InspectedWindowSet;
+
+ explicit AppWindowRegistry(content::BrowserContext* context);
+ ~AppWindowRegistry() override;
+
+ // Returns the instance for the given browser context, or NULL if none. This
+ // is a convenience wrapper around
+ // AppWindowRegistry::Factory::GetForBrowserContext().
+ static AppWindowRegistry* Get(content::BrowserContext* context);
+
+ void AddAppWindow(AppWindow* app_window);
+ void AppWindowIconChanged(AppWindow* app_window);
+ // Called by |app_window| when it is activated.
+ void AppWindowActivated(AppWindow* app_window);
+ void AppWindowHidden(AppWindow* app_window);
+ void AppWindowShown(AppWindow* app_window, bool was_hidden);
+ void RemoveAppWindow(AppWindow* app_window);
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Returns a set of windows owned by the application identified by app_id.
+ AppWindowList GetAppWindowsForApp(const std::string& app_id) const;
+ const AppWindowList& app_windows() const { return app_windows_; }
+
+ // Close all app windows associated with an app.
+ void CloseAllAppWindowsForApp(const std::string& app_id);
+
+ // Helper functions to find app windows with particular attributes.
+ AppWindow* GetAppWindowForWebContents(
+ const content::WebContents* web_contents) const;
+ AppWindow* GetAppWindowForNativeWindow(gfx::NativeWindow window) const;
+ // Returns an app window for the given app, or NULL if no app windows are
+ // open. If there is a window for the given app that is active, that one will
+ // be returned, otherwise an arbitrary window will be returned.
+ AppWindow* GetCurrentAppWindowForApp(const std::string& app_id) const;
+ // Returns an app window for the given app and window key, or NULL if no app
+ // window with the key are open. If there is a window for the given app and
+ // key that is active, that one will be returned, otherwise an arbitrary
+ // window will be returned.
+ AppWindow* GetAppWindowForAppAndKey(const std::string& app_id,
+ const std::string& window_key) const;
+
+ // Returns whether a AppWindow's ID was last known to have a DevToolsAgent
+ // attached to it, which should be restored during a reload of a corresponding
+ // newly created |web_contents|.
+ bool HadDevToolsAttached(content::WebContents* web_contents) const;
+
+ class Factory : public BrowserContextKeyedServiceFactory {
+ public:
+ static AppWindowRegistry* GetForBrowserContext(
+ content::BrowserContext* context,
+ bool create);
+
+ static Factory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<Factory>;
+
+ Factory();
+ ~Factory() override;
+
+ // BrowserContextKeyedServiceFactory
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+ bool ServiceIsCreatedWithBrowserContext() const override;
+ bool ServiceIsNULLWhileTesting() const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+ };
+
+ protected:
+ void OnDevToolsStateChanged(content::DevToolsAgentHost*, bool attached);
+
+ private:
+ // Ensures the specified |app_window| is included in |app_windows_|.
+ // Otherwise adds |app_window| to the back of |app_windows_|.
+ void AddAppWindowToList(AppWindow* app_window);
+
+ // Bring |app_window| to the front of |app_windows_|. If it is not in the
+ // list, add it first.
+ void BringToFront(AppWindow* app_window);
+
+ // Create a key that identifies an AppWindow across App reloads. If the window
+ // was given an id in CreateParams, the key is the extension id, a colon
+ // separator, and the AppWindow's |id|. If there is no |id|, the
+ // chrome-extension://extension-id/page.html URL will be used. If the
+ // WebContents is not for a AppWindow, return an empty string.
+ std::string GetWindowKeyForWebContents(
+ content::WebContents* web_contents) const;
+
+ content::BrowserContext* context_;
+ AppWindowList app_windows_;
+ InspectedWindowSet inspected_windows_;
+ base::ObserverList<Observer> observers_;
+ base::Callback<void(content::DevToolsAgentHost*, bool)> devtools_callback_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_APP_WINDOW_APP_WINDOW_REGISTRY_H_
diff --git a/chromium/extensions/browser/app_window/native_app_window.h b/chromium/extensions/browser/app_window/native_app_window.h
new file mode 100644
index 00000000000..40da36834b1
--- /dev/null
+++ b/chromium/extensions/browser/app_window/native_app_window.h
@@ -0,0 +1,100 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_APP_WINDOW_NATIVE_APP_WINDOW_H_
+#define EXTENSIONS_BROWSER_APP_WINDOW_NATIVE_APP_WINDOW_H_
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "components/web_modal/web_contents_modal_dialog_host.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/base_window.h"
+#include "ui/gfx/geometry/insets.h"
+
+namespace content {
+struct NativeWebKeyboardEvent;
+}
+
+namespace extensions {
+
+struct DraggableRegion;
+
+// This is an interface to a native implementation of a app window, used for
+// new-style packaged apps. App windows contain a web contents, but no tabs
+// or URL bar.
+class NativeAppWindow : public ui::BaseWindow,
+ public web_modal::WebContentsModalDialogHost {
+ public:
+ // Sets whether the window is fullscreen and the type of fullscreen.
+ // |fullscreen_types| is a bit field of AppWindow::FullscreenType.
+ virtual void SetFullscreen(int fullscreen_types) = 0;
+
+ // Returns whether the window is fullscreen or about to enter fullscreen.
+ virtual bool IsFullscreenOrPending() const = 0;
+
+ // Called when the icon of the window changes.
+ virtual void UpdateWindowIcon() = 0;
+
+ // Called when the title of the window changes.
+ virtual void UpdateWindowTitle() = 0;
+
+ // Called when the draggable regions are changed.
+ virtual void UpdateDraggableRegions(
+ const std::vector<DraggableRegion>& regions) = 0;
+
+ // Returns the region used by frameless windows for dragging. May return NULL.
+ virtual SkRegion* GetDraggableRegion() = 0;
+
+ // Called when the window shape is changed. If |region| is NULL then the
+ // window is restored to the default shape.
+ virtual void UpdateShape(scoped_ptr<SkRegion> region) = 0;
+
+ // Allows the window to handle unhandled keyboard messages coming back from
+ // the renderer.
+ virtual void HandleKeyboardEvent(
+ const content::NativeWebKeyboardEvent& event) = 0;
+
+ // Returns true if the window has no frame, as for a window opened by
+ // chrome.app.window.create with the option 'frame' set to 'none'.
+ virtual bool IsFrameless() const = 0;
+
+ // Returns information about the window's frame.
+ virtual bool HasFrameColor() const = 0;
+ virtual SkColor ActiveFrameColor() const = 0;
+ virtual SkColor InactiveFrameColor() const = 0;
+
+ // Returns the difference between the window bounds (including titlebar and
+ // borders) and the content bounds, if any.
+ virtual gfx::Insets GetFrameInsets() const = 0;
+
+ // Hide or show this window as part of hiding or showing the app.
+ // This may have different logic to Hide, Show, and ShowInactive as those are
+ // called via the AppWindow javascript API.
+ virtual void ShowWithApp() = 0;
+ virtual void HideWithApp() = 0;
+
+ // Returns the minimum size constraints of the content.
+ virtual gfx::Size GetContentMinimumSize() const = 0;
+
+ // Returns the maximum size constraints of the content.
+ virtual gfx::Size GetContentMaximumSize() const = 0;
+
+ // Updates the minimum and maximum size constraints of the content.
+ virtual void SetContentSizeConstraints(const gfx::Size& min_size,
+ const gfx::Size& max_size) = 0;
+
+ // Sets whether the window should be visible on all workspaces.
+ virtual void SetVisibleOnAllWorkspaces(bool always_visible) = 0;
+
+ // Returns false if the underlying native window ignores alpha transparency
+ // when compositing.
+ virtual bool CanHaveAlphaEnabled() const = 0;
+
+ ~NativeAppWindow() override {}
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_APP_WINDOW_NATIVE_APP_WINDOW_H_
diff --git a/chromium/extensions/browser/app_window/size_constraints.cc b/chromium/extensions/browser/app_window/size_constraints.cc
new file mode 100644
index 00000000000..6d248c16017
--- /dev/null
+++ b/chromium/extensions/browser/app_window/size_constraints.cc
@@ -0,0 +1,83 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/app_window/size_constraints.h"
+
+#include <algorithm>
+
+#include "ui/gfx/geometry/insets.h"
+
+namespace extensions {
+
+SizeConstraints::SizeConstraints()
+ : maximum_size_(kUnboundedSize, kUnboundedSize) {}
+
+SizeConstraints::SizeConstraints(const gfx::Size& min_size,
+ const gfx::Size& max_size)
+ : minimum_size_(min_size), maximum_size_(max_size) {}
+
+SizeConstraints::~SizeConstraints() {}
+
+// static
+gfx::Size SizeConstraints::AddFrameToConstraints(
+ const gfx::Size& size_constraints,
+ const gfx::Insets& frame_insets) {
+ return gfx::Size(
+ size_constraints.width() == kUnboundedSize
+ ? kUnboundedSize
+ : size_constraints.width() + frame_insets.width(),
+ size_constraints.height() == kUnboundedSize
+ ? kUnboundedSize
+ : size_constraints.height() + frame_insets.height());
+}
+
+gfx::Size SizeConstraints::ClampSize(gfx::Size size) const {
+ const gfx::Size max_size = GetMaximumSize();
+ if (max_size.width() != kUnboundedSize)
+ size.set_width(std::min(size.width(), max_size.width()));
+ if (max_size.height() != kUnboundedSize)
+ size.set_height(std::min(size.height(), max_size.height()));
+ size.SetToMax(GetMinimumSize());
+ return size;
+}
+
+bool SizeConstraints::HasMinimumSize() const {
+ const gfx::Size min_size = GetMinimumSize();
+ return min_size.width() != kUnboundedSize ||
+ min_size.height() != kUnboundedSize;
+}
+
+bool SizeConstraints::HasMaximumSize() const {
+ const gfx::Size max_size = GetMaximumSize();
+ return max_size.width() != kUnboundedSize ||
+ max_size.height() != kUnboundedSize;
+}
+
+bool SizeConstraints::HasFixedSize() const {
+ return !GetMinimumSize().IsEmpty() && GetMinimumSize() == GetMaximumSize();
+}
+
+gfx::Size SizeConstraints::GetMinimumSize() const {
+ return minimum_size_;
+}
+
+gfx::Size SizeConstraints::GetMaximumSize() const {
+ return gfx::Size(
+ maximum_size_.width() == kUnboundedSize
+ ? kUnboundedSize
+ : std::max(maximum_size_.width(), minimum_size_.width()),
+ maximum_size_.height() == kUnboundedSize
+ ? kUnboundedSize
+ : std::max(maximum_size_.height(), minimum_size_.height()));
+}
+
+void SizeConstraints::set_minimum_size(const gfx::Size& min_size) {
+ minimum_size_ = min_size;
+}
+
+void SizeConstraints::set_maximum_size(const gfx::Size& max_size) {
+ maximum_size_ = max_size;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/app_window/size_constraints.h b/chromium/extensions/browser/app_window/size_constraints.h
new file mode 100644
index 00000000000..ecacf1e5eb1
--- /dev/null
+++ b/chromium/extensions/browser/app_window/size_constraints.h
@@ -0,0 +1,57 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_APP_WINDOW_SIZE_CONSTRAINTS_H_
+#define EXTENSIONS_BROWSER_APP_WINDOW_SIZE_CONSTRAINTS_H_
+
+#include "ui/gfx/geometry/size.h"
+
+namespace gfx {
+class Insets;
+}
+
+namespace extensions {
+
+class SizeConstraints {
+ public:
+ // The value SizeConstraints uses to represent an unbounded width or height.
+ // This is an enum so that it can be declared inline here.
+ enum { kUnboundedSize = 0 };
+
+ SizeConstraints();
+ SizeConstraints(const gfx::Size& min_size, const gfx::Size& max_size);
+ ~SizeConstraints();
+
+ // Adds frame insets to a size constraint.
+ static gfx::Size AddFrameToConstraints(const gfx::Size& size_constraints,
+ const gfx::Insets& frame_insets);
+
+ // Returns the bounds with its size clamped to the min/max size.
+ gfx::Size ClampSize(gfx::Size size) const;
+
+ // When gfx::Size is used as a min/max size, a zero represents an unbounded
+ // component. This method checks whether either component is specified.
+ // Note we can't use gfx::Size::IsEmpty as it returns true if either width
+ // or height is zero.
+ bool HasMinimumSize() const;
+ bool HasMaximumSize() const;
+
+ // This returns true if all components are specified, and min and max are
+ // equal.
+ bool HasFixedSize() const;
+
+ gfx::Size GetMaximumSize() const;
+ gfx::Size GetMinimumSize() const;
+
+ void set_minimum_size(const gfx::Size& min_size);
+ void set_maximum_size(const gfx::Size& max_size);
+
+ private:
+ gfx::Size minimum_size_;
+ gfx::Size maximum_size_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_APP_WINDOW_SIZE_CONSTRAINTS_H_
diff --git a/chromium/extensions/browser/app_window/test_app_window_contents.cc b/chromium/extensions/browser/app_window/test_app_window_contents.cc
new file mode 100644
index 00000000000..5613a2092ee
--- /dev/null
+++ b/chromium/extensions/browser/app_window/test_app_window_contents.cc
@@ -0,0 +1,44 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/app_window/test_app_window_contents.h"
+
+#include "content/public/browser/web_contents.h"
+
+namespace extensions {
+
+TestAppWindowContents::TestAppWindowContents(content::WebContents* web_contents)
+ : web_contents_(web_contents) {
+}
+
+TestAppWindowContents::~TestAppWindowContents() {
+}
+
+void TestAppWindowContents::Initialize(content::BrowserContext* context,
+ content::RenderFrameHost* creator_frame,
+ const GURL& url) {}
+
+void TestAppWindowContents::LoadContents(int32_t creator_process_id) {}
+
+void TestAppWindowContents::NativeWindowChanged(
+ NativeAppWindow* native_app_window) {
+}
+
+void TestAppWindowContents::NativeWindowClosed() {
+}
+
+void TestAppWindowContents::DispatchWindowShownForTests() const {
+}
+
+void TestAppWindowContents::OnWindowReady() {}
+
+content::WebContents* TestAppWindowContents::GetWebContents() const {
+ return web_contents_.get();
+}
+
+WindowController* TestAppWindowContents::GetWindowController() const {
+ return nullptr;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/app_window/test_app_window_contents.h b/chromium/extensions/browser/app_window/test_app_window_contents.h
new file mode 100644
index 00000000000..c118fe65e9e
--- /dev/null
+++ b/chromium/extensions/browser/app_window/test_app_window_contents.h
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_APP_WINDOW_TEST_APP_WINDOW_CONTENTS_
+#define EXTENSIONS_BROWSER_APP_WINDOW_TEST_APP_WINDOW_CONTENTS_
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "extensions/browser/app_window/app_window.h"
+
+namespace content {
+class WebContents;
+}
+
+namespace extensions {
+
+// A dummy version of AppWindowContents for unit tests.
+// Best used with AppWindow::SetAppWindowContentsForTesting().
+class TestAppWindowContents : public AppWindowContents {
+ public:
+ explicit TestAppWindowContents(content::WebContents* web_contents);
+ ~TestAppWindowContents() override;
+
+ // apps:AppWindowContents:
+ void Initialize(content::BrowserContext* context,
+ content::RenderFrameHost* creator_frame,
+ const GURL& url) override;
+ void LoadContents(int32_t creator_process_id) override;
+ void NativeWindowChanged(NativeAppWindow* native_app_window) override;
+ void NativeWindowClosed() override;
+ void DispatchWindowShownForTests() const override;
+ void OnWindowReady() override;
+ content::WebContents* GetWebContents() const override;
+ WindowController* GetWindowController() const override;
+
+ private:
+ scoped_ptr<content::WebContents> web_contents_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestAppWindowContents);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_APP_WINDOW_TEST_APP_WINDOW_CONTENTS_
diff --git a/chromium/extensions/browser/bad_message.cc b/chromium/extensions/browser/bad_message.cc
new file mode 100644
index 00000000000..a0dacaac13b
--- /dev/null
+++ b/chromium/extensions/browser/bad_message.cc
@@ -0,0 +1,24 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/bad_message.h"
+
+#include "base/logging.h"
+#include "base/metrics/sparse_histogram.h"
+#include "content/public/browser/render_process_host.h"
+
+namespace extensions {
+namespace bad_message {
+
+void ReceivedBadMessage(content::RenderProcessHost* host,
+ BadMessageReason reason) {
+ LOG(ERROR) << "Terminating extension renderer for bad IPC message, reason "
+ << reason;
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Stability.BadMessageTerminated.Extensions",
+ reason);
+ host->ShutdownForBadMessage();
+}
+
+} // namespace bad_message
+} // namespace extensions
diff --git a/chromium/extensions/browser/bad_message.h b/chromium/extensions/browser/bad_message.h
new file mode 100644
index 00000000000..bef0c712b38
--- /dev/null
+++ b/chromium/extensions/browser/bad_message.h
@@ -0,0 +1,47 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_BAD_MESSAGE_H_
+#define EXTENSIONS_BROWSER_BAD_MESSAGE_H_
+
+namespace content {
+class RenderProcessHost;
+}
+
+namespace extensions {
+namespace bad_message {
+
+// The browser process often chooses to terminate a renderer if it receives
+// a bad IPC message. The reasons are tracked for metrics.
+//
+// See also content/browser/bad_message.h.
+//
+// NOTE: Do not remove or reorder elements in this list. Add new entries at the
+// end. Items may be renamed but do not change the values. We rely on the enum
+// values in histograms.
+enum BadMessageReason {
+ EOG_BAD_ORIGIN = 0,
+ EVG_BAD_ORIGIN = 1,
+ BH_BLOB_NOT_OWNED = 2,
+ EH_BAD_EVENT_ID = 3,
+ AVG_BAD_INST_ID = 4,
+ AVG_BAD_EXT_ID = 5,
+ AVG_NULL_AVG = 6,
+ // Please add new elements here. The naming convention is abbreviated class
+ // name (e.g. ExtensionHost becomes EH) plus a unique description of the
+ // reason. After making changes, you MUST update histograms.xml by running:
+ // "python tools/metrics/histograms/update_bad_message_reasons.py"
+ BAD_MESSAGE_MAX
+};
+
+// Called when the browser receives a bad IPC message from an extension process.
+// Logs the event, records a histogram metric for the |reason|, and terminates
+// the process for |host|.
+void ReceivedBadMessage(content::RenderProcessHost* host,
+ BadMessageReason reason);
+
+} // namespace bad_message
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_BAD_MESSAGE_H_
diff --git a/chromium/extensions/browser/blacklist_state.h b/chromium/extensions/browser/blacklist_state.h
new file mode 100644
index 00000000000..d7f16e23158
--- /dev/null
+++ b/chromium/extensions/browser/blacklist_state.h
@@ -0,0 +1,24 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_BLACKLIST_STATE_H_
+#define EXTENSIONS_BROWSER_BLACKLIST_STATE_H_
+
+namespace extensions {
+
+// The numeric values here match the values of the respective enum in
+// ClientCRXListInfoResponse proto.
+enum BlacklistState {
+ NOT_BLACKLISTED = 0,
+ BLACKLISTED_MALWARE = 1,
+ BLACKLISTED_SECURITY_VULNERABILITY = 2,
+ BLACKLISTED_CWS_POLICY_VIOLATION = 3,
+ BLACKLISTED_POTENTIALLY_UNWANTED = 4,
+ BLACKLISTED_UNKNOWN // Used when we couldn't connect to server,
+ // e.g. when offline.
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_BLACKLIST_STATE_H_
diff --git a/chromium/extensions/browser/blob_holder.cc b/chromium/extensions/browser/blob_holder.cc
new file mode 100644
index 00000000000..552c17ada34
--- /dev/null
+++ b/chromium/extensions/browser/blob_holder.cc
@@ -0,0 +1,87 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/blob_holder.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/logging.h"
+#include "content/public/browser/blob_handle.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "extensions/browser/bad_message.h"
+
+namespace extensions {
+
+namespace {
+
+// Address to this variable used as the user data key.
+const int kBlobHolderUserDataKey = 0;
+}
+
+// static
+BlobHolder* BlobHolder::FromRenderProcessHost(
+ content::RenderProcessHost* render_process_host) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK(render_process_host);
+ BlobHolder* existing = static_cast<BlobHolder*>(
+ render_process_host->GetUserData(&kBlobHolderUserDataKey));
+
+ if (existing)
+ return existing;
+
+ BlobHolder* new_instance = new BlobHolder(render_process_host);
+ render_process_host->SetUserData(&kBlobHolderUserDataKey, new_instance);
+ return new_instance;
+}
+
+BlobHolder::~BlobHolder() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+}
+
+void BlobHolder::HoldBlobReference(scoped_ptr<content::BlobHandle> blob) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK(!ContainsBlobHandle(blob.get()));
+
+ std::string uuid = blob->GetUUID();
+ held_blobs_.insert(make_pair(uuid, make_linked_ptr(blob.release())));
+}
+
+BlobHolder::BlobHolder(content::RenderProcessHost* render_process_host)
+ : render_process_host_(render_process_host) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+}
+
+bool BlobHolder::ContainsBlobHandle(content::BlobHandle* handle) const {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ for (BlobHandleMultimap::const_iterator it = held_blobs_.begin();
+ it != held_blobs_.end();
+ ++it) {
+ if (it->second.get() == handle)
+ return true;
+ }
+
+ return false;
+}
+
+void BlobHolder::DropBlobs(const std::vector<std::string>& blob_uuids) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ for (std::vector<std::string>::const_iterator uuid_it = blob_uuids.begin();
+ uuid_it != blob_uuids.end();
+ ++uuid_it) {
+ BlobHandleMultimap::iterator it = held_blobs_.find(*uuid_it);
+
+ if (it != held_blobs_.end()) {
+ held_blobs_.erase(it);
+ } else {
+ DLOG(ERROR) << "Tried to release a Blob we don't have ownership to."
+ << "UUID: " << *uuid_it;
+ bad_message::ReceivedBadMessage(render_process_host_,
+ bad_message::BH_BLOB_NOT_OWNED);
+ }
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/blob_holder.h b/chromium/extensions/browser/blob_holder.h
new file mode 100644
index 00000000000..efaed928074
--- /dev/null
+++ b/chromium/extensions/browser/blob_holder.h
@@ -0,0 +1,63 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_BLOB_HOLDER_H_
+#define EXTENSIONS_BROWSER_BLOB_HOLDER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/supports_user_data.h"
+
+namespace content {
+class BlobHandle;
+class RenderProcessHost;
+}
+
+namespace extensions {
+
+class ExtensionMessageFilter;
+
+// Used for holding onto Blobs created into the browser process until a
+// renderer takes over ownership. This class operates on the UI thread.
+class BlobHolder : public base::SupportsUserData::Data {
+ public:
+ // Will create the BlobHolder if it doesn't already exist.
+ static BlobHolder* FromRenderProcessHost(
+ content::RenderProcessHost* render_process_host);
+
+ ~BlobHolder() override;
+
+ // Causes BlobHolder to take ownership of |blob|.
+ void HoldBlobReference(scoped_ptr<content::BlobHandle> blob);
+
+ private:
+ typedef std::multimap<std::string, linked_ptr<content::BlobHandle> >
+ BlobHandleMultimap;
+
+ explicit BlobHolder(content::RenderProcessHost* render_process_host);
+
+ // BlobHolder will drop a blob handle for each element in blob_uuids.
+ // If caller wishes to drop multiple references to the same blob,
+ // |blob_uuids| may contain duplicate UUIDs.
+ void DropBlobs(const std::vector<std::string>& blob_uuids);
+ friend class ExtensionMessageFilter;
+
+ bool ContainsBlobHandle(content::BlobHandle* handle) const;
+
+ // A reference to the owner of this class.
+ content::RenderProcessHost* render_process_host_;
+
+ BlobHandleMultimap held_blobs_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlobHolder);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_BLOB_HOLDER_H_
diff --git a/chromium/extensions/browser/blocked_action_type.h b/chromium/extensions/browser/blocked_action_type.h
new file mode 100644
index 00000000000..4b008c0d8ee
--- /dev/null
+++ b/chromium/extensions/browser/blocked_action_type.h
@@ -0,0 +1,21 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_BLOCKED_ACTION_TYPE_H_
+#define EXTENSIONS_BROWSER_BLOCKED_ACTION_TYPE_H_
+
+namespace extensions {
+
+// Types of actions that an extension can perform that can be blocked (typically
+// while waiting for user action).
+enum BlockedActionType {
+ BLOCKED_ACTION_NONE = 0,
+ BLOCKED_ACTION_SCRIPT_AT_START = 1 << 0,
+ BLOCKED_ACTION_SCRIPT_OTHER = 1 << 1,
+ BLOCKED_ACTION_WEB_REQUEST = 1 << 2,
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_BLOCKED_ACTION_TYPE_H_
diff --git a/chromium/extensions/browser/browser_context_keyed_api_factory.h b/chromium/extensions/browser/browser_context_keyed_api_factory.h
new file mode 100644
index 00000000000..47a19a0d47f
--- /dev/null
+++ b/chromium/extensions/browser/browser_context_keyed_api_factory.h
@@ -0,0 +1,142 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_BROWSER_CONTEXT_KEYED_API_FACTORY_H_
+#define EXTENSIONS_BROWSER_BROWSER_CONTEXT_KEYED_API_FACTORY_H_
+
+#include "base/macros.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/browser/extension_system_provider.h"
+#include "extensions/browser/extensions_browser_client.h"
+
+namespace extensions {
+
+template <typename T>
+class BrowserContextKeyedAPIFactory;
+
+// Instantiations of BrowserContextKeyedAPIFactory should use this base class
+// and also define a static const char* service_name() function (used in the
+// BrowserContextKeyedBaseFactory constructor). These fields should
+// be accessible to the BrowserContextKeyedAPIFactory for the service.
+class BrowserContextKeyedAPI : public KeyedService {
+ protected:
+ // Defaults for flags that control BrowserContextKeyedAPIFactory behavior.
+ // These can be overridden by subclasses to change that behavior.
+ // See BrowserContextKeyedBaseFactory for usage.
+
+ // These flags affect what instance is returned when Get() is called
+ // on an incognito profile. By default, it returns NULL. If
+ // kServiceRedirectedInIncognito is true, it returns the instance for the
+ // corresponding regular profile. If kServiceHasOwnInstanceInIncognito
+ // is true, it returns a separate instance.
+ static const bool kServiceRedirectedInIncognito = false;
+ static const bool kServiceHasOwnInstanceInIncognito = false;
+
+ // If set to false, don't start the service at BrowserContext creation time.
+ // (The default differs from the BrowserContextKeyedBaseFactory default,
+ // because historically, BrowserContextKeyedAPIs often do tasks at startup.)
+ static const bool kServiceIsCreatedWithBrowserContext = true;
+
+ // If set to true, GetForProfile returns NULL for TestingBrowserContexts.
+ static const bool kServiceIsNULLWhileTesting = false;
+
+ // Users of this factory template must define a GetFactoryInstance()
+ // and manage their own instances (using LazyInstance), because those cannot
+ // be included in more than one translation unit (and thus cannot be
+ // initialized in a header file).
+ //
+ // In the header file, declare GetFactoryInstance(), e.g.:
+ // class HistoryAPI {
+ // ...
+ // public:
+ // static BrowserContextKeyedAPIFactory<HistoryAPI>* GetFactoryInstance();
+ // };
+ //
+ // In the cc file, provide the implementation, e.g.:
+ // static base::LazyInstance<BrowserContextKeyedAPIFactory<HistoryAPI> >
+ // g_factory = LAZY_INSTANCE_INITIALIZER;
+ //
+ // // static
+ // BrowserContextKeyedAPIFactory<HistoryAPI>*
+ // HistoryAPI::GetFactoryInstance() {
+ // return g_factory.Pointer();
+ // }
+};
+
+// A template for factories for KeyedServices that manage extension APIs. T is
+// a KeyedService that uses this factory template instead of its own separate
+// factory definition to manage its per-profile instances.
+template <typename T>
+class BrowserContextKeyedAPIFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static T* Get(content::BrowserContext* context) {
+ return static_cast<T*>(
+ T::GetFactoryInstance()->GetServiceForBrowserContext(context, true));
+ }
+
+ static T* GetIfExists(content::BrowserContext* context) {
+ return static_cast<T*>(
+ T::GetFactoryInstance()->GetServiceForBrowserContext(context, false));
+ }
+
+ // Declare dependencies on other factories.
+ // By default, ExtensionSystemFactory is the only dependency; however,
+ // specializations can override this. Declare your specialization in
+ // your header file after the BrowserContextKeyedAPI class definition.
+ // Then in the cc file (or inline in the header), define it, e.g.:
+ // template <>
+ // void BrowserContextKeyedAPIFactory<
+ // PushMessagingAPI>::DeclareFactoryDependencies() {
+ // DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
+ // DependsOn(ProfileSyncServiceFactory::GetInstance());
+ // }
+ void DeclareFactoryDependencies() {
+ DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
+ }
+
+ BrowserContextKeyedAPIFactory()
+ : BrowserContextKeyedServiceFactory(
+ T::service_name(),
+ BrowserContextDependencyManager::GetInstance()) {
+ DeclareFactoryDependencies();
+ }
+
+ ~BrowserContextKeyedAPIFactory() override {}
+
+ private:
+ // BrowserContextKeyedServiceFactory implementation.
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override {
+ return new T(context);
+ }
+
+ // BrowserContextKeyedBaseFactory implementation.
+ // These can be effectively overridden with template specializations.
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override {
+ if (T::kServiceRedirectedInIncognito)
+ return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+
+ if (T::kServiceHasOwnInstanceInIncognito)
+ return context;
+
+ return BrowserContextKeyedServiceFactory::GetBrowserContextToUse(context);
+ }
+
+ bool ServiceIsCreatedWithBrowserContext() const override {
+ return T::kServiceIsCreatedWithBrowserContext;
+ }
+
+ bool ServiceIsNULLWhileTesting() const override {
+ return T::kServiceIsNULLWhileTesting;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserContextKeyedAPIFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_BROWSER_CONTEXT_KEYED_API_FACTORY_H_
diff --git a/chromium/extensions/browser/browser_context_keyed_service_factories.cc b/chromium/extensions/browser/browser_context_keyed_service_factories.cc
new file mode 100644
index 00000000000..3c524491d77
--- /dev/null
+++ b/chromium/extensions/browser/browser_context_keyed_service_factories.cc
@@ -0,0 +1,94 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/browser_context_keyed_service_factories.h"
+
+#include "build/build_config.h"
+#include "extensions/browser/api/alarms/alarm_manager.h"
+#include "extensions/browser/api/api_resource_manager.h"
+#include "extensions/browser/api/audio/audio_api.h"
+#include "extensions/browser/api/bluetooth/bluetooth_api.h"
+#include "extensions/browser/api/bluetooth/bluetooth_private_api.h"
+#include "extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.h"
+#include "extensions/browser/api/display_source/display_source_event_router_factory.h"
+#include "extensions/browser/api/hid/hid_device_manager.h"
+#include "extensions/browser/api/idle/idle_manager_factory.h"
+#include "extensions/browser/api/management/management_api.h"
+#include "extensions/browser/api/networking_config/networking_config_service_factory.h"
+#include "extensions/browser/api/networking_private/networking_private_event_router_factory.h"
+#include "extensions/browser/api/power/power_api.h"
+#include "extensions/browser/api/runtime/runtime_api.h"
+#include "extensions/browser/api/serial/serial_connection.h"
+#include "extensions/browser/api/socket/socket.h"
+#include "extensions/browser/api/socket/tcp_socket.h"
+#include "extensions/browser/api/socket/udp_socket.h"
+#include "extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.h"
+#include "extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h"
+#include "extensions/browser/api/sockets_udp/udp_socket_event_dispatcher.h"
+#include "extensions/browser/api/storage/storage_frontend.h"
+#include "extensions/browser/api/system_info/system_info_api.h"
+#include "extensions/browser/api/usb/usb_device_resource.h"
+#include "extensions/browser/api/usb/usb_event_router.h"
+#include "extensions/browser/api/usb/usb_guid_map.h"
+#include "extensions/browser/api/virtual_keyboard_private/virtual_keyboard_private_api.h"
+#include "extensions/browser/api/vpn_provider/vpn_service_factory.h"
+#include "extensions/browser/api/web_request/web_request_api.h"
+#include "extensions/browser/api/webcam_private/webcam_private_api.h"
+#include "extensions/browser/declarative_user_script_manager_factory.h"
+#include "extensions/browser/event_router_factory.h"
+#include "extensions/browser/extension_message_filter.h"
+#include "extensions/browser/extension_prefs_factory.h"
+#include "extensions/browser/process_manager_factory.h"
+#include "extensions/browser/renderer_startup_helper.h"
+
+namespace extensions {
+
+void EnsureBrowserContextKeyedServiceFactoriesBuilt() {
+ AlarmManager::GetFactoryInstance();
+ ApiResourceManager<ResumableTCPServerSocket>::GetFactoryInstance();
+ ApiResourceManager<ResumableTCPSocket>::GetFactoryInstance();
+ ApiResourceManager<ResumableUDPSocket>::GetFactoryInstance();
+ ApiResourceManager<SerialConnection>::GetFactoryInstance();
+ ApiResourceManager<Socket>::GetFactoryInstance();
+ ApiResourceManager<UsbDeviceResource>::GetFactoryInstance();
+ AudioAPI::GetFactoryInstance();
+ BluetoothAPI::GetFactoryInstance();
+ BluetoothPrivateAPI::GetFactoryInstance();
+#if defined(OS_CHROMEOS)
+ chromeos::VpnServiceFactory::GetInstance();
+#endif
+ api::BluetoothSocketEventDispatcher::GetFactoryInstance();
+ api::TCPServerSocketEventDispatcher::GetFactoryInstance();
+ api::TCPSocketEventDispatcher::GetFactoryInstance();
+ api::UDPSocketEventDispatcher::GetFactoryInstance();
+ DeclarativeUserScriptManagerFactory::GetInstance();
+ DisplaySourceEventRouterFactory::GetInstance();
+ EventRouterFactory::GetInstance();
+ ExtensionMessageFilter::EnsureShutdownNotifierFactoryBuilt();
+ ExtensionPrefsFactory::GetInstance();
+ HidDeviceManager::GetFactoryInstance();
+ IdleManagerFactory::GetInstance();
+ ManagementAPI::GetFactoryInstance();
+#if defined(OS_CHROMEOS)
+ NetworkingConfigServiceFactory::GetInstance();
+#endif
+#if defined(OS_LINUX) || defined(OS_WIN) || defined(OS_MACOSX)
+ NetworkingPrivateEventRouterFactory::GetInstance();
+#endif
+ PowerAPI::GetFactoryInstance();
+ ProcessManagerFactory::GetInstance();
+ RendererStartupHelperFactory::GetInstance();
+ RuntimeAPI::GetFactoryInstance();
+ StorageFrontend::GetFactoryInstance();
+ SystemInfoAPI::GetFactoryInstance();
+ UsbEventRouter::GetFactoryInstance();
+ UsbGuidMap::GetFactoryInstance();
+#if defined(OS_CHROMEOS)
+ VirtualKeyboardAPI::GetFactoryInstance();
+ WebcamPrivateAPI::GetFactoryInstance();
+#endif
+ WebRequestAPI::GetFactoryInstance();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/browser_context_keyed_service_factories.h b/chromium/extensions/browser/browser_context_keyed_service_factories.h
new file mode 100644
index 00000000000..ab8c8c24e62
--- /dev/null
+++ b/chromium/extensions/browser/browser_context_keyed_service_factories.h
@@ -0,0 +1,16 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_BROWSER_CONTEXT_KEYED_SERVICE_FACTORIES_H_
+#define EXTENSIONS_BROWSER_BROWSER_CONTEXT_KEYED_SERVICE_FACTORIES_H_
+
+namespace extensions {
+
+// Ensures the existence of any BrowserContextKeyedServiceFactory provided by
+// the core extensions code.
+void EnsureBrowserContextKeyedServiceFactoriesBuilt();
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_BROWSER_CONTEXT_KEYED_SERVICE_FACTORIES_H_
diff --git a/chromium/extensions/browser/component_extension_resource_manager.h b/chromium/extensions/browser/component_extension_resource_manager.h
new file mode 100644
index 00000000000..6495f10d32f
--- /dev/null
+++ b/chromium/extensions/browser/component_extension_resource_manager.h
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_COMPONENT_EXTENSION_RESOURCE_MANAGER_H_
+#define EXTENSIONS_BROWSER_COMPONENT_EXTENSION_RESOURCE_MANAGER_H_
+
+namespace base {
+class FilePath;
+}
+
+namespace extensions {
+
+// This class manages which extension resources actually come from
+// the resource bundle.
+class ComponentExtensionResourceManager {
+ public:
+ virtual ~ComponentExtensionResourceManager() {}
+
+ // Checks whether image is a component extension resource. Returns false
+ // if a given |resource| does not have a corresponding image in bundled
+ // resources. Otherwise fills |resource_id|. This doesn't check if the
+ // extension the resource is in is actually a component extension.
+ virtual bool IsComponentExtensionResource(
+ const base::FilePath& extension_path,
+ const base::FilePath& resource_path,
+ int* resource_id) const = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_COMPONENT_EXTENSION_RESOURCE_MANAGER_H_
diff --git a/chromium/extensions/browser/computed_hashes.cc b/chromium/extensions/browser/computed_hashes.cc
new file mode 100644
index 00000000000..35ea525508b
--- /dev/null
+++ b/chromium/extensions/browser/computed_hashes.cc
@@ -0,0 +1,197 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/computed_hashes.h"
+
+#include "base/base64.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/stl_util.h"
+#include "base/values.h"
+#include "crypto/secure_hash.h"
+#include "crypto/sha2.h"
+
+namespace {
+const char kBlockHashesKey[] = "block_hashes";
+const char kBlockSizeKey[] = "block_size";
+const char kFileHashesKey[] = "file_hashes";
+const char kPathKey[] = "path";
+const char kVersionKey[] = "version";
+const int kVersion = 2;
+} // namespace
+
+namespace extensions {
+
+ComputedHashes::Reader::Reader() {
+}
+
+ComputedHashes::Reader::~Reader() {
+}
+
+bool ComputedHashes::Reader::InitFromFile(const base::FilePath& path) {
+ std::string contents;
+ if (!base::ReadFileToString(path, &contents))
+ return false;
+
+ base::DictionaryValue* top_dictionary = NULL;
+ scoped_ptr<base::Value> value(base::JSONReader::Read(contents));
+ if (!value.get() || !value->GetAsDictionary(&top_dictionary))
+ return false;
+
+ // For now we don't support forwards or backwards compatability in the
+ // format, so we return false on version mismatch.
+ int version = 0;
+ if (!top_dictionary->GetInteger(kVersionKey, &version) || version != kVersion)
+ return false;
+
+ base::ListValue* all_hashes = NULL;
+ if (!top_dictionary->GetList(kFileHashesKey, &all_hashes))
+ return false;
+
+ for (size_t i = 0; i < all_hashes->GetSize(); i++) {
+ base::DictionaryValue* dictionary = NULL;
+ if (!all_hashes->GetDictionary(i, &dictionary))
+ return false;
+
+ std::string relative_path_utf8;
+ if (!dictionary->GetString(kPathKey, &relative_path_utf8))
+ return false;
+
+ int block_size;
+ if (!dictionary->GetInteger(kBlockSizeKey, &block_size))
+ return false;
+ if (block_size <= 0 || ((block_size % 1024) != 0)) {
+ LOG(ERROR) << "Invalid block size: " << block_size;
+ block_size = 0;
+ return false;
+ }
+
+ base::ListValue* hashes_list = NULL;
+ if (!dictionary->GetList(kBlockHashesKey, &hashes_list))
+ return false;
+
+ base::FilePath relative_path =
+ base::FilePath::FromUTF8Unsafe(relative_path_utf8);
+ relative_path = relative_path.NormalizePathSeparatorsTo('/');
+
+ data_[relative_path] = HashInfo(block_size, std::vector<std::string>());
+ std::vector<std::string>* hashes = &(data_[relative_path].second);
+
+ for (size_t j = 0; j < hashes_list->GetSize(); j++) {
+ std::string encoded;
+ if (!hashes_list->GetString(j, &encoded))
+ return false;
+
+ hashes->push_back(std::string());
+ std::string* decoded = &hashes->back();
+ if (!base::Base64Decode(encoded, decoded)) {
+ hashes->clear();
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool ComputedHashes::Reader::GetHashes(const base::FilePath& relative_path,
+ int* block_size,
+ std::vector<std::string>* hashes) {
+ base::FilePath path = relative_path.NormalizePathSeparatorsTo('/');
+ std::map<base::FilePath, HashInfo>::iterator i = data_.find(path);
+ if (i == data_.end()) {
+ // If we didn't find the entry using exact match, it's possible the
+ // developer is using a path with some letters in the incorrect case, which
+ // happens to work on windows/osx. So try doing a linear scan to look for a
+ // case-insensitive match. In practice most extensions don't have that big
+ // a list of files so the performance penalty is probably not too big
+ // here. Also for crbug.com/29941 we plan to start warning developers when
+ // they are making this mistake, since their extension will be broken on
+ // linux/chromeos.
+ for (i = data_.begin(); i != data_.end(); ++i) {
+ const base::FilePath& entry = i->first;
+ if (base::FilePath::CompareEqualIgnoreCase(entry.value(), path.value()))
+ break;
+ }
+ if (i == data_.end())
+ return false;
+ }
+ HashInfo& info = i->second;
+ *block_size = info.first;
+ *hashes = info.second;
+ return true;
+}
+
+ComputedHashes::Writer::Writer() : file_list_(new base::ListValue) {
+}
+
+ComputedHashes::Writer::~Writer() {
+}
+
+void ComputedHashes::Writer::AddHashes(const base::FilePath& relative_path,
+ int block_size,
+ const std::vector<std::string>& hashes) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ base::ListValue* block_hashes = new base::ListValue();
+ file_list_->Append(dict);
+ dict->SetString(kPathKey,
+ relative_path.NormalizePathSeparatorsTo('/').AsUTF8Unsafe());
+ dict->SetInteger(kBlockSizeKey, block_size);
+ dict->Set(kBlockHashesKey, block_hashes);
+
+ for (std::vector<std::string>::const_iterator i = hashes.begin();
+ i != hashes.end();
+ ++i) {
+ std::string encoded;
+ base::Base64Encode(*i, &encoded);
+ block_hashes->AppendString(encoded);
+ }
+}
+
+bool ComputedHashes::Writer::WriteToFile(const base::FilePath& path) {
+ std::string json;
+ base::DictionaryValue top_dictionary;
+ top_dictionary.SetInteger(kVersionKey, kVersion);
+ top_dictionary.Set(kFileHashesKey, file_list_.release());
+
+ if (!base::JSONWriter::Write(top_dictionary, &json))
+ return false;
+ int written = base::WriteFile(path, json.data(), json.size());
+ if (static_cast<unsigned>(written) != json.size()) {
+ LOG(ERROR) << "Error writing " << path.AsUTF8Unsafe()
+ << " ; write result:" << written << " expected:" << json.size();
+ return false;
+ }
+ return true;
+}
+
+void ComputedHashes::ComputeHashesForContent(const std::string& contents,
+ size_t block_size,
+ std::vector<std::string>* hashes) {
+ size_t offset = 0;
+ // Even when the contents is empty, we want to output at least one hash
+ // block (the hash of the empty string).
+ do {
+ const char* block_start = contents.data() + offset;
+ DCHECK(offset <= contents.size());
+ size_t bytes_to_read = std::min(contents.size() - offset, block_size);
+ scoped_ptr<crypto::SecureHash> hash(
+ crypto::SecureHash::Create(crypto::SecureHash::SHA256));
+ hash->Update(block_start, bytes_to_read);
+
+ hashes->push_back(std::string());
+ std::string* buffer = &(hashes->back());
+ buffer->resize(crypto::kSHA256Length);
+ hash->Finish(string_as_array(buffer), buffer->size());
+
+ // If |contents| is empty, then we want to just exit here.
+ if (bytes_to_read == 0)
+ break;
+
+ offset += bytes_to_read;
+ } while (offset < contents.size());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/computed_hashes.h b/chromium/extensions/browser/computed_hashes.h
new file mode 100644
index 00000000000..071abf08767
--- /dev/null
+++ b/chromium/extensions/browser/computed_hashes.h
@@ -0,0 +1,74 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_COMPUTED_HASHES_H_
+#define EXTENSIONS_BROWSER_COMPUTED_HASHES_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+
+namespace base {
+class FilePath;
+class ListValue;
+}
+
+namespace extensions {
+
+// A pair of classes for serialization of a set of SHA256 block hashes computed
+// over the files inside an extension.
+class ComputedHashes {
+ public:
+ class Reader {
+ public:
+ Reader();
+ ~Reader();
+ bool InitFromFile(const base::FilePath& path);
+
+ // The block size and hashes for |relative_path| will be copied into the
+ // out parameters.
+ bool GetHashes(const base::FilePath& relative_path,
+ int* block_size,
+ std::vector<std::string>* hashes);
+
+ private:
+ typedef std::pair<int, std::vector<std::string> > HashInfo;
+
+ // This maps a relative path to a pair of (block size, hashes)
+ std::map<base::FilePath, HashInfo> data_;
+ };
+
+ class Writer {
+ public:
+ Writer();
+ ~Writer();
+
+ // Adds hashes for |relative_path|. Should not be called more than once
+ // for a given |relative_path|.
+ void AddHashes(const base::FilePath& relative_path,
+ int block_size,
+ const std::vector<std::string>& hashes);
+
+ bool WriteToFile(const base::FilePath& path);
+
+ private:
+ // Each element of this list contains the path and block hashes for one
+ // file.
+ scoped_ptr<base::ListValue> file_list_;
+ };
+
+ // Computes the SHA256 hash of each |block_size| chunk in |contents|, placing
+ // the results into |hashes|.
+ static void ComputeHashesForContent(const std::string& contents,
+ size_t block_size,
+ std::vector<std::string>* hashes);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_COMPUTED_HASHES_H_
diff --git a/chromium/extensions/browser/computed_hashes_unittest.cc b/chromium/extensions/browser/computed_hashes_unittest.cc
new file mode 100644
index 00000000000..aecbd650d53
--- /dev/null
+++ b/chromium/extensions/browser/computed_hashes_unittest.cc
@@ -0,0 +1,125 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/base64.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "crypto/sha2.h"
+#include "extensions/browser/computed_hashes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Helper to return base64 encode result by value.
+std::string Base64Encode(const std::string& data) {
+ std::string result;
+ base::Base64Encode(data, &result);
+ return result;
+}
+
+} // namespace
+
+namespace extensions {
+
+TEST(ComputedHashes, ComputedHashes) {
+ base::ScopedTempDir scoped_dir;
+ ASSERT_TRUE(scoped_dir.CreateUniqueTempDir());
+ base::FilePath computed_hashes =
+ scoped_dir.path().AppendASCII("computed_hashes.json");
+
+ // We'll add hashes for 2 files, one of which uses a subdirectory
+ // path. The first file will have a list of 1 block hash, and the
+ // second file will have 2 block hashes.
+ base::FilePath path1(FILE_PATH_LITERAL("foo.txt"));
+ base::FilePath path2 =
+ base::FilePath(FILE_PATH_LITERAL("foo")).AppendASCII("bar.txt");
+ std::vector<std::string> hashes1;
+ std::vector<std::string> hashes2;
+ hashes1.push_back(crypto::SHA256HashString("first"));
+ hashes2.push_back(crypto::SHA256HashString("second"));
+ hashes2.push_back(crypto::SHA256HashString("third"));
+
+ // Write them into the file.
+ ComputedHashes::Writer writer;
+ writer.AddHashes(path1, 4096, hashes1);
+ writer.AddHashes(path2, 2048, hashes2);
+ EXPECT_TRUE(writer.WriteToFile(computed_hashes));
+
+ // Now read them back again and assert that we got what we wrote.
+ ComputedHashes::Reader reader;
+ std::vector<std::string> read_hashes1;
+ std::vector<std::string> read_hashes2;
+ EXPECT_TRUE(reader.InitFromFile(computed_hashes));
+
+ int block_size = 0;
+ EXPECT_TRUE(reader.GetHashes(path1, &block_size, &read_hashes1));
+ EXPECT_EQ(block_size, 4096);
+ block_size = 0;
+ EXPECT_TRUE(reader.GetHashes(path2, &block_size, &read_hashes2));
+ EXPECT_EQ(block_size, 2048);
+
+ EXPECT_EQ(hashes1, read_hashes1);
+ EXPECT_EQ(hashes2, read_hashes2);
+
+ // Make sure we can lookup hashes for a file using incorrect case
+ base::FilePath path1_badcase(FILE_PATH_LITERAL("FoO.txt"));
+ std::vector<std::string> read_hashes1_badcase;
+ EXPECT_TRUE(
+ reader.GetHashes(path1_badcase, &block_size, &read_hashes1_badcase));
+ EXPECT_EQ(block_size, 4096);
+ EXPECT_EQ(hashes1, read_hashes1_badcase);
+
+ // Finally make sure that we can retrieve the hashes for the subdir
+ // path even when that path contains forward slashes (on windows).
+ base::FilePath path2_fwd_slashes =
+ base::FilePath::FromUTF8Unsafe("foo/bar.txt");
+ block_size = 0;
+ EXPECT_TRUE(reader.GetHashes(path2_fwd_slashes, &block_size, &read_hashes2));
+ EXPECT_EQ(hashes2, read_hashes2);
+}
+
+// Note: the expected hashes used in this test were generated using linux
+// command line tools. E.g., from a bash prompt:
+// $ printf "hello world" | openssl dgst -sha256 -binary | base64
+//
+// The file with multiple-blocks expectations were generated by doing:
+// $ for i in `seq 500 ; do printf "hello world" ; done > hello.txt
+// $ dd if=hello.txt bs=4096 count=1 | openssl dgst -sha256 -binary | base64
+// $ dd if=hello.txt skip=1 bs=4096 count=1 |
+// openssl dgst -sha256 -binary | base64
+TEST(ComputedHashes, ComputeHashesForContent) {
+ const int block_size = 4096;
+
+ // Simple short input.
+ std::string content1 = "hello world";
+ std::string content1_expected_hash =
+ "uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=";
+ std::vector<std::string> hashes1;
+ ComputedHashes::ComputeHashesForContent(content1, block_size, &hashes1);
+ ASSERT_EQ(1u, hashes1.size());
+ EXPECT_EQ(content1_expected_hash, Base64Encode(hashes1[0]));
+
+ // Multiple blocks input.
+ std::string content2;
+ for (int i = 0; i < 500; i++)
+ content2 += "hello world";
+ const char* content2_expected_hashes[] = {
+ "bvtt5hXo8xvHrlzGAhhoqPL/r+4zJXHx+6wAvkv15V8=",
+ "lTD45F7P6I/HOdi8u7FLRA4qzAYL+7xSNVeusG6MJI0="};
+ std::vector<std::string> hashes2;
+ ComputedHashes::ComputeHashesForContent(content2, block_size, &hashes2);
+ ASSERT_EQ(2u, hashes2.size());
+ EXPECT_EQ(content2_expected_hashes[0], Base64Encode(hashes2[0]));
+ EXPECT_EQ(content2_expected_hashes[1], Base64Encode(hashes2[1]));
+
+ // Now an empty input.
+ std::string content3;
+ std::vector<std::string> hashes3;
+ ComputedHashes::ComputeHashesForContent(content3, block_size, &hashes3);
+ ASSERT_EQ(1u, hashes3.size());
+ ASSERT_EQ(std::string("47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="),
+ Base64Encode(hashes3[0]));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/content_hash_fetcher.cc b/chromium/extensions/browser/content_hash_fetcher.cc
new file mode 100644
index 00000000000..dbe0f2185ab
--- /dev/null
+++ b/chromium/extensions/browser/content_hash_fetcher.cc
@@ -0,0 +1,504 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/content_hash_fetcher.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+
+#include "base/base64.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/metrics/histogram.h"
+#include "base/synchronization/lock.h"
+#include "base/task_runner_util.h"
+#include "base/timer/elapsed_timer.h"
+#include "base/version.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "crypto/sha2.h"
+#include "extensions/browser/computed_hashes.h"
+#include "extensions/browser/content_hash_tree.h"
+#include "extensions/browser/content_verifier_delegate.h"
+#include "extensions/browser/verified_contents.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/file_util.h"
+#include "net/base/load_flags.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_status.h"
+
+namespace {
+
+typedef std::set<base::FilePath> SortedFilePathSet;
+
+} // namespace
+
+namespace extensions {
+
+// This class takes care of doing the disk and network I/O work to ensure we
+// have both verified_contents.json files from the webstore and
+// computed_hashes.json files computed over the files in an extension's
+// directory.
+class ContentHashFetcherJob
+ : public base::RefCountedThreadSafe<ContentHashFetcherJob>,
+ public net::URLFetcherDelegate {
+ public:
+ typedef base::Callback<void(ContentHashFetcherJob*)> CompletionCallback;
+ ContentHashFetcherJob(net::URLRequestContextGetter* request_context,
+ const ContentVerifierKey& key,
+ const std::string& extension_id,
+ const base::FilePath& extension_path,
+ const GURL& fetch_url,
+ bool force,
+ const CompletionCallback& callback);
+
+ void Start();
+
+ // Cancels this job, which will attempt to stop I/O operations sooner than
+ // just waiting for the entire job to complete. Safe to call from any thread.
+ void Cancel();
+
+ // Checks whether this job has been cancelled. Safe to call from any thread.
+ bool IsCancelled();
+
+ // Returns whether this job was successful (we have both verified contents
+ // and computed hashes). Even if the job was a success, there might have been
+ // files that were found to have contents not matching expectations; these
+ // are available by calling hash_mismatch_paths().
+ bool success() { return success_; }
+
+ bool force() { return force_; }
+
+ const std::string& extension_id() { return extension_id_; }
+
+ // Returns the set of paths that had a hash mismatch.
+ const std::set<base::FilePath>& hash_mismatch_paths() {
+ return hash_mismatch_paths_;
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<ContentHashFetcherJob>;
+ ~ContentHashFetcherJob() override;
+
+ // Tries to load a verified_contents.json file at |path|. On successfully
+ // reading and validing the file, the verified_contents_ member variable will
+ // be set and this function will return true. If the file does not exist, or
+ // exists but is invalid, it will return false. Also, any invalid
+ // file will be removed from disk and
+ bool LoadVerifiedContents(const base::FilePath& path);
+
+ // Callback for when we're done doing file I/O to see if we already have
+ // a verified contents file. If we don't, this will kick off a network
+ // request to get one.
+ void DoneCheckingForVerifiedContents(bool found);
+
+ // URLFetcherDelegate interface
+ void OnURLFetchComplete(const net::URLFetcher* source) override;
+
+ // Callback for when we're done ensuring we have verified contents, and are
+ // ready to move on to MaybeCreateHashes.
+ void DoneFetchingVerifiedContents(bool success);
+
+ // Callback for the job to write the verified contents to the filesystem.
+ void OnVerifiedContentsWritten(size_t expected_size, int write_result);
+
+ // The verified contents file from the webstore only contains the treehash
+ // root hash, but for performance we want to cache the individual block level
+ // hashes. This function will create that cache with block-level hashes for
+ // each file in the extension if needed (the treehash root hash for each of
+ // these should equal what is in the verified contents file from the
+ // webstore).
+ void MaybeCreateHashes();
+
+ // Computes hashes for all files in |extension_path_|, and uses a
+ // ComputedHashes::Writer to write that information into
+ // |hashes_file|. Returns true on success.
+ bool CreateHashes(const base::FilePath& hashes_file);
+
+ // Will call the callback, if we haven't been cancelled.
+ void DispatchCallback();
+
+ net::URLRequestContextGetter* request_context_;
+ std::string extension_id_;
+ base::FilePath extension_path_;
+
+ // The url we'll need to use to fetch a verified_contents.json file.
+ GURL fetch_url_;
+
+ bool force_;
+
+ CompletionCallback callback_;
+ content::BrowserThread::ID creation_thread_;
+
+ // Used for fetching content signatures.
+ scoped_ptr<net::URLFetcher> url_fetcher_;
+
+ // The key used to validate verified_contents.json.
+ ContentVerifierKey key_;
+
+ // The parsed contents of the verified_contents.json file, either read from
+ // disk or fetched from the network and then written to disk.
+ scoped_ptr<VerifiedContents> verified_contents_;
+
+ // Whether this job succeeded.
+ bool success_;
+
+ // Paths that were found to have a mismatching hash.
+ std::set<base::FilePath> hash_mismatch_paths_;
+
+ // The block size to use for hashing.
+ int block_size_;
+
+ // Note: this may be accessed from multiple threads, so all access should
+ // be protected by |cancelled_lock_|.
+ bool cancelled_;
+
+ // A lock for synchronizing access to |cancelled_|.
+ base::Lock cancelled_lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentHashFetcherJob);
+};
+
+ContentHashFetcherJob::ContentHashFetcherJob(
+ net::URLRequestContextGetter* request_context,
+ const ContentVerifierKey& key,
+ const std::string& extension_id,
+ const base::FilePath& extension_path,
+ const GURL& fetch_url,
+ bool force,
+ const CompletionCallback& callback)
+ : request_context_(request_context),
+ extension_id_(extension_id),
+ extension_path_(extension_path),
+ fetch_url_(fetch_url),
+ force_(force),
+ callback_(callback),
+ key_(key),
+ success_(false),
+ // TODO(asargent) - use the value from verified_contents.json for each
+ // file, instead of using a constant.
+ block_size_(4096),
+ cancelled_(false) {
+ bool got_id =
+ content::BrowserThread::GetCurrentThreadIdentifier(&creation_thread_);
+ DCHECK(got_id);
+}
+
+void ContentHashFetcherJob::Start() {
+ base::FilePath verified_contents_path =
+ file_util::GetVerifiedContentsPath(extension_path_);
+ base::PostTaskAndReplyWithResult(
+ content::BrowserThread::GetBlockingPool(),
+ FROM_HERE,
+ base::Bind(&ContentHashFetcherJob::LoadVerifiedContents,
+ this,
+ verified_contents_path),
+ base::Bind(&ContentHashFetcherJob::DoneCheckingForVerifiedContents,
+ this));
+}
+
+void ContentHashFetcherJob::Cancel() {
+ base::AutoLock autolock(cancelled_lock_);
+ cancelled_ = true;
+}
+
+bool ContentHashFetcherJob::IsCancelled() {
+ base::AutoLock autolock(cancelled_lock_);
+ bool result = cancelled_;
+ return result;
+}
+
+ContentHashFetcherJob::~ContentHashFetcherJob() {
+}
+
+bool ContentHashFetcherJob::LoadVerifiedContents(const base::FilePath& path) {
+ if (!base::PathExists(path))
+ return false;
+ verified_contents_.reset(new VerifiedContents(key_.data, key_.size));
+ if (!verified_contents_->InitFrom(path, false)) {
+ verified_contents_.reset();
+ if (!base::DeleteFile(path, false))
+ LOG(WARNING) << "Failed to delete " << path.value();
+ return false;
+ }
+ return true;
+}
+
+void ContentHashFetcherJob::DoneCheckingForVerifiedContents(bool found) {
+ if (IsCancelled())
+ return;
+ if (found) {
+ VLOG(1) << "Found verified contents for " << extension_id_;
+ DoneFetchingVerifiedContents(true);
+ } else {
+ VLOG(1) << "Missing verified contents for " << extension_id_
+ << ", fetching...";
+ url_fetcher_ =
+ net::URLFetcher::Create(fetch_url_, net::URLFetcher::GET, this);
+ url_fetcher_->SetRequestContext(request_context_);
+ url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES |
+ net::LOAD_DISABLE_CACHE);
+ url_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3);
+ url_fetcher_->Start();
+ }
+}
+
+// Helper function to let us pass ownership of a string via base::Bind with the
+// contents to be written into a file. Also ensures that the directory for
+// |path| exists, creating it if needed.
+static int WriteFileHelper(const base::FilePath& path,
+ scoped_ptr<std::string> content) {
+ base::FilePath dir = path.DirName();
+ return (base::CreateDirectoryAndGetError(dir, NULL) &&
+ base::WriteFile(path, content->data(), content->size()));
+}
+
+void ContentHashFetcherJob::OnURLFetchComplete(const net::URLFetcher* source) {
+ VLOG(1) << "URLFetchComplete for " << extension_id_
+ << " is_success:" << url_fetcher_->GetStatus().is_success() << " "
+ << fetch_url_.possibly_invalid_spec();
+ if (IsCancelled())
+ return;
+ scoped_ptr<std::string> response(new std::string);
+ if (!url_fetcher_->GetStatus().is_success() ||
+ !url_fetcher_->GetResponseAsString(response.get())) {
+ DoneFetchingVerifiedContents(false);
+ return;
+ }
+
+ // Parse the response to make sure it is valid json (on staging sometimes it
+ // can be a login redirect html, xml file, etc. if you aren't logged in with
+ // the right cookies). TODO(asargent) - It would be a nice enhancement to
+ // move to parsing this in a sandboxed helper (crbug.com/372878).
+ scoped_ptr<base::Value> parsed(base::JSONReader::Read(*response));
+ if (parsed) {
+ VLOG(1) << "JSON parsed ok for " << extension_id_;
+
+ parsed.reset(); // no longer needed
+ base::FilePath destination =
+ file_util::GetVerifiedContentsPath(extension_path_);
+ size_t size = response->size();
+ base::PostTaskAndReplyWithResult(
+ content::BrowserThread::GetBlockingPool(),
+ FROM_HERE,
+ base::Bind(&WriteFileHelper, destination, base::Passed(&response)),
+ base::Bind(
+ &ContentHashFetcherJob::OnVerifiedContentsWritten, this, size));
+ } else {
+ DoneFetchingVerifiedContents(false);
+ }
+}
+
+void ContentHashFetcherJob::OnVerifiedContentsWritten(size_t expected_size,
+ int write_result) {
+ bool success =
+ (write_result >= 0 && static_cast<size_t>(write_result) == expected_size);
+ DoneFetchingVerifiedContents(success);
+}
+
+void ContentHashFetcherJob::DoneFetchingVerifiedContents(bool success) {
+ if (IsCancelled())
+ return;
+
+ if (!success) {
+ DispatchCallback();
+ return;
+ }
+
+ content::BrowserThread::PostBlockingPoolSequencedTask(
+ "ContentHashFetcher",
+ FROM_HERE,
+ base::Bind(&ContentHashFetcherJob::MaybeCreateHashes, this));
+}
+
+void ContentHashFetcherJob::MaybeCreateHashes() {
+ if (IsCancelled())
+ return;
+ base::FilePath hashes_file =
+ file_util::GetComputedHashesPath(extension_path_);
+
+ if (!force_ && base::PathExists(hashes_file)) {
+ success_ = true;
+ } else {
+ if (force_)
+ base::DeleteFile(hashes_file, false /* recursive */);
+ success_ = CreateHashes(hashes_file);
+ }
+
+ content::BrowserThread::PostTask(
+ creation_thread_,
+ FROM_HERE,
+ base::Bind(&ContentHashFetcherJob::DispatchCallback, this));
+}
+
+bool ContentHashFetcherJob::CreateHashes(const base::FilePath& hashes_file) {
+ base::ElapsedTimer timer;
+ if (IsCancelled())
+ return false;
+ // Make sure the directory exists.
+ if (!base::CreateDirectoryAndGetError(hashes_file.DirName(), NULL))
+ return false;
+
+ if (!verified_contents_.get()) {
+ base::FilePath verified_contents_path =
+ file_util::GetVerifiedContentsPath(extension_path_);
+ verified_contents_.reset(new VerifiedContents(key_.data, key_.size));
+ if (!verified_contents_->InitFrom(verified_contents_path, false))
+ return false;
+ verified_contents_.reset();
+ }
+
+ base::FileEnumerator enumerator(extension_path_,
+ true, /* recursive */
+ base::FileEnumerator::FILES);
+ // First discover all the file paths and put them in a sorted set.
+ SortedFilePathSet paths;
+ for (;;) {
+ if (IsCancelled())
+ return false;
+
+ base::FilePath full_path = enumerator.Next();
+ if (full_path.empty())
+ break;
+ paths.insert(full_path);
+ }
+
+ // Now iterate over all the paths in sorted order and compute the block hashes
+ // for each one.
+ ComputedHashes::Writer writer;
+ for (SortedFilePathSet::iterator i = paths.begin(); i != paths.end(); ++i) {
+ if (IsCancelled())
+ return false;
+ const base::FilePath& full_path = *i;
+ base::FilePath relative_path;
+ extension_path_.AppendRelativePath(full_path, &relative_path);
+ relative_path = relative_path.NormalizePathSeparatorsTo('/');
+
+ if (!verified_contents_->HasTreeHashRoot(relative_path))
+ continue;
+
+ std::string contents;
+ if (!base::ReadFileToString(full_path, &contents)) {
+ LOG(ERROR) << "Could not read " << full_path.MaybeAsASCII();
+ continue;
+ }
+
+ // Iterate through taking the hash of each block of size (block_size_) of
+ // the file.
+ std::vector<std::string> hashes;
+ ComputedHashes::ComputeHashesForContent(contents, block_size_, &hashes);
+ std::string root =
+ ComputeTreeHashRoot(hashes, block_size_ / crypto::kSHA256Length);
+ if (!verified_contents_->TreeHashRootEquals(relative_path, root)) {
+ VLOG(1) << "content mismatch for " << relative_path.AsUTF8Unsafe();
+ hash_mismatch_paths_.insert(relative_path);
+ continue;
+ }
+
+ writer.AddHashes(relative_path, block_size_, hashes);
+ }
+ bool result = writer.WriteToFile(hashes_file);
+ UMA_HISTOGRAM_TIMES("ExtensionContentHashFetcher.CreateHashesTime",
+ timer.Elapsed());
+ return result;
+}
+
+void ContentHashFetcherJob::DispatchCallback() {
+ {
+ base::AutoLock autolock(cancelled_lock_);
+ if (cancelled_)
+ return;
+ }
+ callback_.Run(this);
+}
+
+// ----
+
+ContentHashFetcher::ContentHashFetcher(content::BrowserContext* context,
+ ContentVerifierDelegate* delegate,
+ const FetchCallback& callback)
+ : context_(context),
+ delegate_(delegate),
+ fetch_callback_(callback),
+ weak_ptr_factory_(this) {
+}
+
+ContentHashFetcher::~ContentHashFetcher() {
+ for (JobMap::iterator i = jobs_.begin(); i != jobs_.end(); ++i) {
+ i->second->Cancel();
+ }
+}
+
+void ContentHashFetcher::DoFetch(const Extension* extension, bool force) {
+ DCHECK(extension);
+
+ IdAndVersion key(extension->id(), extension->version()->GetString());
+ JobMap::iterator found = jobs_.find(key);
+ if (found != jobs_.end()) {
+ if (!force || found->second->force()) {
+ // Just let the existing job keep running.
+ return;
+ } else {
+ // Kill the existing non-force job, so we can start a new one below.
+ found->second->Cancel();
+ jobs_.erase(found);
+ }
+ }
+
+ // TODO(asargent) - we should do something here to remember recent attempts
+ // to fetch signatures by extension id, and use exponential backoff to avoid
+ // hammering the server when we aren't successful in getting them.
+ // crbug.com/373397
+
+ DCHECK(extension->version());
+ GURL url =
+ delegate_->GetSignatureFetchUrl(extension->id(), *extension->version());
+ ContentHashFetcherJob* job = new ContentHashFetcherJob(
+ context_->GetRequestContext(), delegate_->GetPublicKey(), extension->id(),
+ extension->path(), url, force,
+ base::Bind(&ContentHashFetcher::JobFinished,
+ weak_ptr_factory_.GetWeakPtr()));
+ jobs_.insert(std::make_pair(key, job));
+ job->Start();
+}
+
+void ContentHashFetcher::ExtensionLoaded(const Extension* extension) {
+ CHECK(extension);
+ DoFetch(extension, false);
+}
+
+void ContentHashFetcher::ExtensionUnloaded(const Extension* extension) {
+ CHECK(extension);
+ IdAndVersion key(extension->id(), extension->version()->GetString());
+ JobMap::iterator found = jobs_.find(key);
+ if (found != jobs_.end()) {
+ found->second->Cancel();
+ jobs_.erase(found);
+ }
+}
+
+void ContentHashFetcher::JobFinished(ContentHashFetcherJob* job) {
+ if (!job->IsCancelled()) {
+ fetch_callback_.Run(job->extension_id(),
+ job->success(),
+ job->force(),
+ job->hash_mismatch_paths());
+ }
+
+ for (JobMap::iterator i = jobs_.begin(); i != jobs_.end(); ++i) {
+ if (i->second.get() == job) {
+ jobs_.erase(i);
+ break;
+ }
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/content_hash_fetcher.h b/chromium/extensions/browser/content_hash_fetcher.h
new file mode 100644
index 00000000000..7367ca3992c
--- /dev/null
+++ b/chromium/extensions/browser/content_hash_fetcher.h
@@ -0,0 +1,84 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_CONTENT_HASH_FETCHER_H_
+#define EXTENSIONS_BROWSER_CONTENT_HASH_FETCHER_H_
+
+#include <set>
+#include <string>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "extensions/common/extension.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class ExtensionRegistry;
+class ContentHashFetcherJob;
+class ContentVerifierDelegate;
+
+// This class is responsible for getting signed expected hashes for use in
+// extension content verification. As extensions are loaded it will fetch and
+// parse/validate/cache this data as needed, including calculating expected
+// hashes for each block of each file within an extension. (These unsigned leaf
+// node block level hashes will always be checked at time of use use to make
+// sure they match the signed treehash root hash).
+class ContentHashFetcher {
+ public:
+ // A callback for when a fetch is complete. This reports back:
+ // -extension id
+ // -whether we were successful or not (have verified_contents.json and
+ // -computed_hashes.json files)
+ // -was it a forced check?
+ // -a set of paths whose contents didn't match expected values
+ typedef base::Callback<
+ void(const std::string&, bool, bool, const std::set<base::FilePath>&)>
+ FetchCallback;
+
+ // The consumer of this class needs to ensure that context and delegate
+ // outlive this object.
+ ContentHashFetcher(content::BrowserContext* context,
+ ContentVerifierDelegate* delegate,
+ const FetchCallback& callback);
+ virtual ~ContentHashFetcher();
+
+ // Explicitly ask to fetch hashes for |extension|. If |force| is true,
+ // we will always check the validity of the verified_contents.json and
+ // re-check the contents of the files in the filesystem.
+ void DoFetch(const Extension* extension, bool force);
+
+ // These should be called when an extension is loaded or unloaded.
+ virtual void ExtensionLoaded(const Extension* extension);
+ virtual void ExtensionUnloaded(const Extension* extension);
+
+ private:
+ // Callback for when a job getting content hashes has completed.
+ void JobFinished(ContentHashFetcherJob* job);
+
+ content::BrowserContext* context_;
+ ContentVerifierDelegate* delegate_;
+ FetchCallback fetch_callback_;
+
+ // We keep around pointers to in-progress jobs, both so we can avoid
+ // scheduling duplicate work if fetching is already in progress, and so that
+ // we can cancel in-progress work at shutdown time.
+ typedef std::pair<ExtensionId, std::string> IdAndVersion;
+ typedef std::map<IdAndVersion, scoped_refptr<ContentHashFetcherJob> > JobMap;
+ JobMap jobs_;
+
+ // Used for binding callbacks passed to jobs.
+ base::WeakPtrFactory<ContentHashFetcher> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentHashFetcher);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_CONTENT_HASH_FETCHER_H_
diff --git a/chromium/extensions/browser/content_hash_reader.cc b/chromium/extensions/browser/content_hash_reader.cc
new file mode 100644
index 00000000000..f1a39a6c9bb
--- /dev/null
+++ b/chromium/extensions/browser/content_hash_reader.cc
@@ -0,0 +1,122 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/content_hash_reader.h"
+
+#include "base/base64.h"
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "base/timer/elapsed_timer.h"
+#include "base/values.h"
+#include "crypto/sha2.h"
+#include "extensions/browser/computed_hashes.h"
+#include "extensions/browser/content_hash_tree.h"
+#include "extensions/browser/verified_contents.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/file_util.h"
+
+using base::DictionaryValue;
+using base::ListValue;
+using base::Value;
+
+namespace extensions {
+
+ContentHashReader::ContentHashReader(const std::string& extension_id,
+ const base::Version& extension_version,
+ const base::FilePath& extension_root,
+ const base::FilePath& relative_path,
+ const ContentVerifierKey& key)
+ : extension_id_(extension_id),
+ extension_version_(extension_version.GetString()),
+ extension_root_(extension_root),
+ relative_path_(relative_path),
+ key_(key),
+ status_(NOT_INITIALIZED),
+ content_exists_(false),
+ have_verified_contents_(false),
+ have_computed_hashes_(false),
+ block_size_(0) {
+}
+
+ContentHashReader::~ContentHashReader() {
+}
+
+bool ContentHashReader::Init() {
+ base::ElapsedTimer timer;
+ DCHECK_EQ(status_, NOT_INITIALIZED);
+ status_ = FAILURE;
+ base::FilePath verified_contents_path =
+ file_util::GetVerifiedContentsPath(extension_root_);
+
+ // Check that this is a valid resource to verify (i.e., it exists).
+ base::FilePath content_path = extension_root_.Append(relative_path_);
+ if (!base::PathExists(content_path) || base::DirectoryExists(content_path))
+ return false;
+
+ content_exists_ = true;
+
+ if (!base::PathExists(verified_contents_path))
+ return false;
+
+ verified_contents_.reset(new VerifiedContents(key_.data, key_.size));
+ if (!verified_contents_->InitFrom(verified_contents_path, false) ||
+ !verified_contents_->valid_signature() ||
+ verified_contents_->version() != extension_version_ ||
+ verified_contents_->extension_id() != extension_id_)
+ return false;
+
+ have_verified_contents_ = true;
+
+ base::FilePath computed_hashes_path =
+ file_util::GetComputedHashesPath(extension_root_);
+ if (!base::PathExists(computed_hashes_path))
+ return false;
+
+ ComputedHashes::Reader reader;
+ if (!reader.InitFromFile(computed_hashes_path))
+ return false;
+
+ have_computed_hashes_ = true;
+
+ if (!reader.GetHashes(relative_path_, &block_size_, &hashes_) ||
+ block_size_ % crypto::kSHA256Length != 0)
+ return false;
+
+ std::string root =
+ ComputeTreeHashRoot(hashes_, block_size_ / crypto::kSHA256Length);
+ if (!verified_contents_->TreeHashRootEquals(relative_path_, root))
+ return false;
+
+ status_ = SUCCESS;
+ UMA_HISTOGRAM_TIMES("ExtensionContentHashReader.InitLatency",
+ timer.Elapsed());
+ return true;
+}
+
+int ContentHashReader::block_count() const {
+ DCHECK(status_ != NOT_INITIALIZED);
+ return hashes_.size();
+}
+
+int ContentHashReader::block_size() const {
+ DCHECK(status_ != NOT_INITIALIZED);
+ return block_size_;
+}
+
+bool ContentHashReader::GetHashForBlock(int block_index,
+ const std::string** result) const {
+ if (status_ != SUCCESS)
+ return false;
+ DCHECK(block_index >= 0);
+
+ if (static_cast<unsigned>(block_index) >= hashes_.size())
+ return false;
+ *result = &hashes_[block_index];
+
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/content_hash_reader.h b/chromium/extensions/browser/content_hash_reader.h
new file mode 100644
index 00000000000..882b20b612e
--- /dev/null
+++ b/chromium/extensions/browser/content_hash_reader.h
@@ -0,0 +1,94 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_CONTENT_HASH_READER_H_
+#define EXTENSIONS_BROWSER_CONTENT_HASH_READER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/version.h"
+#include "extensions/browser/content_verifier_delegate.h"
+
+namespace extensions {
+
+class VerifiedContents;
+
+// This class creates an object that will read expected hashes that may have
+// been fetched/calculated by the ContentHashFetcher, and vends them out for
+// use in ContentVerifyJob's.
+class ContentHashReader : public base::RefCountedThreadSafe<ContentHashReader> {
+ public:
+ // Create one of these to get expected hashes for the file at |relative_path|
+ // within an extension.
+ ContentHashReader(const std::string& extension_id,
+ const base::Version& extension_version,
+ const base::FilePath& extension_root,
+ const base::FilePath& relative_path,
+ const ContentVerifierKey& key);
+
+ const std::string& extension_id() const { return extension_id_; }
+ const base::FilePath& relative_path() const { return relative_path_; }
+
+ // This should be called to initialize this object (reads the expected hashes
+ // from storage, etc.). Must be called on a thread that is allowed to do file
+ // I/O. Returns a boolean indicating success/failure. On failure, this object
+ // should likely be discarded.
+ bool Init();
+
+ // Indicates whether the content in question exists in the local extension
+ // installation. This may be |false| if Init fails.
+ bool content_exists() const { return content_exists_; }
+
+ // These return whether we found valid verified_contents.json /
+ // computed_hashes.json files respectively. Note that both of these can be
+ // true but we still didn't find an entry for |relative_path_| in them.
+ bool have_verified_contents() const { return have_verified_contents_; }
+ bool have_computed_hashes() const { return have_computed_hashes_; }
+
+ // Return the number of blocks and block size, respectively. Only valid after
+ // calling Init().
+ int block_count() const;
+ int block_size() const;
+
+ // Returns a pointer to the expected sha256 hash value for the block at the
+ // given index. Only valid after calling Init().
+ bool GetHashForBlock(int block_index, const std::string** result) const;
+
+ private:
+ friend class base::RefCountedThreadSafe<ContentHashReader>;
+ virtual ~ContentHashReader();
+
+ enum InitStatus { NOT_INITIALIZED, SUCCESS, FAILURE };
+
+ std::string extension_id_;
+ base::Version extension_version_;
+ base::FilePath extension_root_;
+ base::FilePath relative_path_;
+ ContentVerifierKey key_;
+
+ InitStatus status_;
+
+ bool content_exists_;
+
+ bool have_verified_contents_;
+ bool have_computed_hashes_;
+
+ // The blocksize used for generating the hashes.
+ int block_size_;
+
+ scoped_ptr<VerifiedContents> verified_contents_;
+
+ std::vector<std::string> hashes_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentHashReader);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_CONTENT_HASH_READER_H_
diff --git a/chromium/extensions/browser/content_hash_tree.cc b/chromium/extensions/browser/content_hash_tree.cc
new file mode 100644
index 00000000000..9831c12e7f8
--- /dev/null
+++ b/chromium/extensions/browser/content_hash_tree.cc
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/content_hash_tree.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "crypto/secure_hash.h"
+#include "crypto/sha2.h"
+
+namespace extensions {
+
+std::string ComputeTreeHashRoot(const std::vector<std::string>& leaf_hashes,
+ int branch_factor) {
+ if (leaf_hashes.empty() || branch_factor < 2)
+ return std::string();
+
+ // The nodes of the tree we're currently operating on.
+ std::vector<std::string> current_nodes;
+
+ // We avoid having to copy all of the input leaf nodes into |current_nodes|
+ // by using a pointer. So the first iteration of the loop this points at
+ // |leaf_hashes|, but thereafter it points at |current_nodes|.
+ const std::vector<std::string>* current = &leaf_hashes;
+
+ // Where we're inserting new hashes computed from the current level.
+ std::vector<std::string> parent_nodes;
+
+ while (current->size() > 1) {
+ // Iterate over the current level of hashes, computing the hash of up to
+ // |branch_factor| elements to form the hash of each parent node.
+ std::vector<std::string>::const_iterator i = current->begin();
+ while (i != current->end()) {
+ scoped_ptr<crypto::SecureHash> hash(
+ crypto::SecureHash::Create(crypto::SecureHash::SHA256));
+ for (int j = 0; j < branch_factor && i != current->end(); j++) {
+ DCHECK_EQ(i->size(), crypto::kSHA256Length);
+ hash->Update(i->data(), i->size());
+ ++i;
+ }
+ parent_nodes.push_back(std::string(crypto::kSHA256Length, 0));
+ std::string* output = &(parent_nodes.back());
+ hash->Finish(string_as_array(output), output->size());
+ }
+ current_nodes.swap(parent_nodes);
+ parent_nodes.clear();
+ current = &current_nodes;
+ }
+ DCHECK_EQ(1u, current->size());
+ return (*current)[0];
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/content_hash_tree.h b/chromium/extensions/browser/content_hash_tree.h
new file mode 100644
index 00000000000..534f69e7928
--- /dev/null
+++ b/chromium/extensions/browser/content_hash_tree.h
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_CONTENT_HASH_TREE_H_
+#define EXTENSIONS_BROWSER_CONTENT_HASH_TREE_H_
+
+#include <string>
+#include <vector>
+
+namespace extensions {
+
+// This takes a list of sha256 hashes, considers them to be leaf nodes of a
+// hash tree (aka Merkle tree), and computes the root node of the tree using
+// the given branching factor to hash lower level nodes together. Tree hash
+// implementations differ in how they handle the case where the number of
+// leaves isn't an integral power of the branch factor. This implementation
+// just hashes together however many are left at a given level, even if that is
+// less than the branching factor (instead of, for instance, directly promoting
+// elements). E.g., imagine we use a branch factor of 3 for a vector of 4 leaf
+// nodes [A,B,C,D]. This implemention will compute the root hash G as follows:
+//
+// | G |
+// | / \ |
+// | E F |
+// | /|\ \ |
+// | A B C D |
+//
+// where E = Hash(A||B||C), F = Hash(D), and G = Hash(E||F)
+//
+// The one exception to this rule is when there is only one node left. This
+// means that the root hash of any vector with just one leaf is the same as
+// that leaf. Ie RootHash([A]) == A, not Hash(A).
+std::string ComputeTreeHashRoot(const std::vector<std::string>& leaf_hashes,
+ int branch_factor);
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_CONTENT_HASH_TREE_H_
diff --git a/chromium/extensions/browser/content_hash_tree_unittest.cc b/chromium/extensions/browser/content_hash_tree_unittest.cc
new file mode 100644
index 00000000000..b5aada8c9cc
--- /dev/null
+++ b/chromium/extensions/browser/content_hash_tree_unittest.cc
@@ -0,0 +1,77 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "crypto/secure_hash.h"
+#include "crypto/sha2.h"
+#include "extensions/browser/content_hash_tree.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using crypto::kSHA256Length;
+using crypto::SecureHash;
+
+// Helper to return a fake sha256 signature based on a seed.
+static std::string FakeSignatureWithSeed(int seed) {
+ std::string input;
+ for (int i = 0; i < seed * 3; i++) {
+ input.push_back(static_cast<char>(((i + 19) * seed) % 256));
+ }
+ return crypto::SHA256HashString(input);
+}
+
+namespace extensions {
+
+TEST(ContentHashTreeTest, HashTreeBasics) {
+ std::vector<std::string> nodes;
+ // Empty array.
+ EXPECT_EQ(std::string(), ComputeTreeHashRoot(nodes, 16));
+
+ // One node.
+ std::string node1 = FakeSignatureWithSeed(1);
+ nodes.push_back(node1);
+ EXPECT_EQ(node1, ComputeTreeHashRoot(nodes, 16));
+
+ // Two nodes.
+ std::string node2 = FakeSignatureWithSeed(2);
+ nodes.push_back(node2);
+
+ std::string expected(kSHA256Length, 0);
+ scoped_ptr<SecureHash> hash(SecureHash::Create(SecureHash::SHA256));
+ hash->Update(node1.data(), node1.size());
+ hash->Update(node2.data(), node2.size());
+ hash->Finish(string_as_array(&expected), expected.size());
+ EXPECT_EQ(expected, ComputeTreeHashRoot(nodes, 16));
+}
+
+TEST(ContentHashTreeTest, HashTreeMultipleLevels) {
+ std::vector<std::string> nodes;
+ for (int i = 0; i < 3; i++) {
+ std::string node;
+ nodes.push_back(FakeSignatureWithSeed(i));
+ }
+
+ // First try a test where our branch factor is >= 3, so we expect the result
+ // to be the hash of all 3 concatenated together. E.g the expected top hash
+ // should be 4 in the following diagram:
+ // 4
+ // 1 2 3
+ std::string expected =
+ crypto::SHA256HashString(nodes[0] + nodes[1] + nodes[2]);
+ EXPECT_EQ(expected, ComputeTreeHashRoot(nodes, 4));
+
+ // Now try making the branch factor be 2, so that we
+ // should get the following:
+ // 6
+ // 4 5
+ // 1 2 3
+ // where 4 is the hash of 1 and 2, 5 is the hash of 3, and 6 is the
+ // hash of 4 and 5.
+ std::string hash_of_first_2 = crypto::SHA256HashString(nodes[0] + nodes[1]);
+ std::string hash_of_third = crypto::SHA256HashString(nodes[2]);
+ expected = crypto::SHA256HashString(hash_of_first_2 + hash_of_third);
+ EXPECT_EQ(expected, ComputeTreeHashRoot(nodes, 2));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/content_verifier.cc b/chromium/extensions/browser/content_verifier.cc
new file mode 100644
index 00000000000..ea464e81be8
--- /dev/null
+++ b/chromium/extensions/browser/content_verifier.cc
@@ -0,0 +1,290 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/content_verifier.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/content_hash_fetcher.h"
+#include "extensions/browser/content_hash_reader.h"
+#include "extensions/browser/content_verifier_delegate.h"
+#include "extensions/browser/content_verifier_io_data.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension_l10n_util.h"
+
+namespace extensions {
+
+namespace {
+
+ContentVerifier::TestObserver* g_test_observer = NULL;
+
+// This function converts paths like "//foo/bar", "./foo/bar", and
+// "/foo/bar" to "foo/bar". It also converts path separators to "/".
+base::FilePath NormalizeRelativePath(const base::FilePath& path) {
+ if (path.ReferencesParent())
+ return base::FilePath();
+
+ std::vector<base::FilePath::StringType> parts;
+ path.GetComponents(&parts);
+ if (parts.empty())
+ return base::FilePath();
+
+ // Remove the first component if it is '.' or '/' or '//'.
+ const base::FilePath::StringType separators(
+ base::FilePath::kSeparators, base::FilePath::kSeparatorsLength);
+ if (!parts[0].empty() &&
+ (parts[0] == base::FilePath::kCurrentDirectory ||
+ parts[0].find_first_not_of(separators) == std::string::npos))
+ parts.erase(parts.begin());
+
+ // Note that elsewhere we always normalize path separators to '/' so this
+ // should work for all platforms.
+ return base::FilePath(
+ base::JoinString(parts, base::FilePath::StringType(1, '/')));
+}
+
+} // namespace
+
+// static
+void ContentVerifier::SetObserverForTests(TestObserver* observer) {
+ g_test_observer = observer;
+}
+
+ContentVerifier::ContentVerifier(content::BrowserContext* context,
+ ContentVerifierDelegate* delegate)
+ : shutdown_(false),
+ context_(context),
+ delegate_(delegate),
+ fetcher_(new ContentHashFetcher(
+ context,
+ delegate,
+ base::Bind(&ContentVerifier::OnFetchComplete, this))),
+ observer_(this),
+ io_data_(new ContentVerifierIOData) {
+}
+
+ContentVerifier::~ContentVerifier() {
+}
+
+void ContentVerifier::Start() {
+ ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
+ observer_.Add(registry);
+}
+
+void ContentVerifier::Shutdown() {
+ shutdown_ = true;
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&ContentVerifierIOData::Clear, io_data_));
+ observer_.RemoveAll();
+ fetcher_.reset();
+}
+
+ContentVerifyJob* ContentVerifier::CreateJobFor(
+ const std::string& extension_id,
+ const base::FilePath& extension_root,
+ const base::FilePath& relative_path) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+
+ const ContentVerifierIOData::ExtensionData* data =
+ io_data_->GetData(extension_id);
+ if (!data)
+ return NULL;
+
+ base::FilePath normalized_path = NormalizeRelativePath(relative_path);
+
+ std::set<base::FilePath> paths;
+ paths.insert(normalized_path);
+ if (!ShouldVerifyAnyPaths(extension_id, extension_root, paths))
+ return NULL;
+
+ // TODO(asargent) - we can probably get some good performance wins by having
+ // a cache of ContentHashReader's that we hold onto past the end of each job.
+ return new ContentVerifyJob(
+ new ContentHashReader(extension_id, data->version, extension_root,
+ normalized_path, delegate_->GetPublicKey()),
+ base::Bind(&ContentVerifier::VerifyFailed, this, extension_id));
+}
+
+void ContentVerifier::VerifyFailed(const std::string& extension_id,
+ ContentVerifyJob::FailureReason reason) {
+ if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&ContentVerifier::VerifyFailed, this, extension_id, reason));
+ return;
+ }
+ if (shutdown_)
+ return;
+
+ VLOG(1) << "VerifyFailed " << extension_id << " reason:" << reason;
+
+ ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
+ const Extension* extension =
+ registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
+
+ if (!extension)
+ return;
+
+ if (reason == ContentVerifyJob::MISSING_ALL_HASHES) {
+ // If we failed because there were no hashes yet for this extension, just
+ // request some.
+ fetcher_->DoFetch(extension, true /* force */);
+ } else {
+ delegate_->VerifyFailed(extension_id, reason);
+ }
+}
+
+void ContentVerifier::OnExtensionLoaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension) {
+ if (shutdown_)
+ return;
+
+ ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension);
+ if (mode != ContentVerifierDelegate::NONE) {
+ // The browser image paths from the extension may not be relative (eg
+ // they might have leading '/' or './'), so we strip those to make
+ // comparing to actual relative paths work later on.
+ std::set<base::FilePath> original_image_paths =
+ delegate_->GetBrowserImagePaths(extension);
+
+ scoped_ptr<std::set<base::FilePath>> image_paths(
+ new std::set<base::FilePath>);
+ for (const auto& path : original_image_paths) {
+ image_paths->insert(NormalizeRelativePath(path));
+ }
+
+ scoped_ptr<ContentVerifierIOData::ExtensionData> data(
+ new ContentVerifierIOData::ExtensionData(
+ std::move(image_paths),
+ extension->version() ? *extension->version() : base::Version()));
+ content::BrowserThread::PostTask(content::BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&ContentVerifierIOData::AddData,
+ io_data_,
+ extension->id(),
+ base::Passed(&data)));
+ fetcher_->ExtensionLoaded(extension);
+ }
+}
+
+void ContentVerifier::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ if (shutdown_)
+ return;
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(
+ &ContentVerifierIOData::RemoveData, io_data_, extension->id()));
+ if (fetcher_)
+ fetcher_->ExtensionUnloaded(extension);
+}
+
+void ContentVerifier::OnFetchCompleteHelper(const std::string& extension_id,
+ bool shouldVerifyAnyPathsResult) {
+ if (shouldVerifyAnyPathsResult)
+ delegate_->VerifyFailed(extension_id, ContentVerifyJob::MISSING_ALL_HASHES);
+}
+
+void ContentVerifier::OnFetchComplete(
+ const std::string& extension_id,
+ bool success,
+ bool was_force_check,
+ const std::set<base::FilePath>& hash_mismatch_paths) {
+ if (g_test_observer)
+ g_test_observer->OnFetchComplete(extension_id, success);
+
+ if (shutdown_)
+ return;
+
+ VLOG(1) << "OnFetchComplete " << extension_id << " success:" << success;
+
+ ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
+ const Extension* extension =
+ registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
+ if (!delegate_ || !extension)
+ return;
+
+ ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension);
+ if (was_force_check && !success &&
+ mode == ContentVerifierDelegate::ENFORCE_STRICT) {
+ // We weren't able to get verified_contents.json or weren't able to compute
+ // hashes.
+ delegate_->VerifyFailed(extension_id, ContentVerifyJob::MISSING_ALL_HASHES);
+ } else {
+ content::BrowserThread::PostTaskAndReplyWithResult(
+ content::BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&ContentVerifier::ShouldVerifyAnyPaths,
+ this,
+ extension_id,
+ extension->path(),
+ hash_mismatch_paths),
+ base::Bind(
+ &ContentVerifier::OnFetchCompleteHelper, this, extension_id));
+ }
+}
+
+bool ContentVerifier::ShouldVerifyAnyPaths(
+ const std::string& extension_id,
+ const base::FilePath& extension_root,
+ const std::set<base::FilePath>& relative_paths) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+ const ContentVerifierIOData::ExtensionData* data =
+ io_data_->GetData(extension_id);
+ if (!data)
+ return false;
+
+ const std::set<base::FilePath>& browser_images = *(data->browser_image_paths);
+
+ base::FilePath locales_dir = extension_root.Append(kLocaleFolder);
+ scoped_ptr<std::set<std::string> > all_locales;
+
+ for (std::set<base::FilePath>::const_iterator i = relative_paths.begin();
+ i != relative_paths.end();
+ ++i) {
+ const base::FilePath& relative_path = *i;
+
+ if (relative_path == base::FilePath(kManifestFilename))
+ continue;
+
+ if (ContainsKey(browser_images, relative_path))
+ continue;
+
+ base::FilePath full_path = extension_root.Append(relative_path);
+ if (locales_dir.IsParent(full_path)) {
+ if (!all_locales) {
+ // TODO(asargent) - see if we can cache this list longer to avoid
+ // having to fetch it more than once for a given run of the
+ // browser. Maybe it can never change at runtime? (Or if it can, maybe
+ // there is an event we can listen for to know to drop our cache).
+ all_locales.reset(new std::set<std::string>);
+ extension_l10n_util::GetAllLocales(all_locales.get());
+ }
+
+ // Since message catalogs get transcoded during installation, we want
+ // to skip those paths.
+ if (full_path.DirName().DirName() == locales_dir &&
+ !extension_l10n_util::ShouldSkipValidation(
+ locales_dir, full_path.DirName(), *all_locales))
+ continue;
+ }
+ return true;
+ }
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/content_verifier.h b/chromium/extensions/browser/content_verifier.h
new file mode 100644
index 00000000000..8d6f47b5c64
--- /dev/null
+++ b/chromium/extensions/browser/content_verifier.h
@@ -0,0 +1,112 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_CONTENT_VERIFIER_H_
+#define EXTENSIONS_BROWSER_CONTENT_VERIFIER_H_
+
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/scoped_observer.h"
+#include "base/version.h"
+#include "extensions/browser/content_verifier_delegate.h"
+#include "extensions/browser/content_verify_job.h"
+#include "extensions/browser/extension_registry_observer.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class Extension;
+class ContentHashFetcher;
+class ContentVerifierIOData;
+
+// Used for managing overall content verification - both fetching content
+// hashes as needed, and supplying job objects to verify file contents as they
+// are read.
+class ContentVerifier : public base::RefCountedThreadSafe<ContentVerifier>,
+ public ExtensionRegistryObserver {
+ public:
+ class TestObserver {
+ public:
+ virtual void OnFetchComplete(const std::string& extension_id,
+ bool success) = 0;
+ };
+ static void SetObserverForTests(TestObserver* observer);
+
+ // Takes ownership of |delegate|.
+ ContentVerifier(content::BrowserContext* context,
+ ContentVerifierDelegate* delegate);
+ void Start();
+ void Shutdown();
+
+ // Call this before reading a file within an extension. The caller owns the
+ // returned job.
+ ContentVerifyJob* CreateJobFor(const std::string& extension_id,
+ const base::FilePath& extension_root,
+ const base::FilePath& relative_path);
+
+ // Called (typically by a verification job) to indicate that verification
+ // failed while reading some file in |extension_id|.
+ void VerifyFailed(const std::string& extension_id,
+ ContentVerifyJob::FailureReason reason);
+
+ // ExtensionRegistryObserver interface
+ void OnExtensionLoaded(content::BrowserContext* browser_context,
+ const Extension* extension) override;
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ContentVerifier);
+
+ friend class base::RefCountedThreadSafe<ContentVerifier>;
+ ~ContentVerifier() override;
+
+ void OnFetchComplete(const std::string& extension_id,
+ bool success,
+ bool was_force_check,
+ const std::set<base::FilePath>& hash_mismatch_paths);
+
+ void OnFetchCompleteHelper(const std::string& extension_id,
+ bool shouldVerifyAnyPathsResult);
+
+ // Returns true if any of the paths in |relative_paths| *should* have their
+ // contents verified. (Some files get transcoded during the install process,
+ // so we don't want to verify their contents because they are expected not
+ // to match).
+ bool ShouldVerifyAnyPaths(const std::string& extension_id,
+ const base::FilePath& extension_root,
+ const std::set<base::FilePath>& relative_paths);
+
+ // Set to true once we've begun shutting down.
+ bool shutdown_;
+
+ content::BrowserContext* context_;
+
+ scoped_ptr<ContentVerifierDelegate> delegate_;
+
+ // For fetching content hash signatures.
+ scoped_ptr<ContentHashFetcher> fetcher_;
+
+ // For observing the ExtensionRegistry.
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver> observer_;
+
+ // Data that should only be used on the IO thread.
+ scoped_refptr<ContentVerifierIOData> io_data_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_CONTENT_VERIFIER_H_
diff --git a/chromium/extensions/browser/content_verifier_delegate.h b/chromium/extensions/browser/content_verifier_delegate.h
new file mode 100644
index 00000000000..5fb72a2617c
--- /dev/null
+++ b/chromium/extensions/browser/content_verifier_delegate.h
@@ -0,0 +1,88 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_CONTENT_VERIFIER_DELEGATE_H_
+#define EXTENSIONS_BROWSER_CONTENT_VERIFIER_DELEGATE_H_
+
+#include <stdint.h>
+
+#include <set>
+
+#include "extensions/browser/content_verify_job.h"
+#include "url/gurl.h"
+
+namespace base {
+class FilePath;
+class Version;
+}
+
+namespace extensions {
+
+class Extension;
+
+// A pointer to the bytes of a public key, and the number of bytes.
+struct ContentVerifierKey {
+ const uint8_t* data;
+ int size;
+
+ ContentVerifierKey() : data(NULL), size(0) {}
+
+ ContentVerifierKey(const uint8_t* data, int size) {
+ this->data = data;
+ this->size = size;
+ }
+};
+
+// This is an interface for clients that want to use a ContentVerifier.
+class ContentVerifierDelegate {
+ public:
+ // Note that it is important for these to appear in increasing "severity"
+ // order, because we use this to let command line flags increase, but not
+ // decrease, the mode you're running in compared to the experiment group.
+ enum Mode {
+ // Do not try to fetch content hashes if they are missing, and do not
+ // enforce them if they are present.
+ NONE = 0,
+
+ // If content hashes are missing, try to fetch them, but do not enforce.
+ BOOTSTRAP,
+
+ // If hashes are present, enforce them. If they are missing, try to fetch
+ // them.
+ ENFORCE,
+
+ // Treat the absence of hashes the same as a verification failure.
+ ENFORCE_STRICT
+ };
+
+ virtual ~ContentVerifierDelegate() {}
+
+ // This should return what verification mode is appropriate for the given
+ // extension, if any.
+ virtual Mode ShouldBeVerified(const Extension& extension) = 0;
+
+ // Should return the public key to use for validating signatures via the two
+ // out parameters.
+ virtual ContentVerifierKey GetPublicKey() = 0;
+
+ // This should return a URL that can be used to fetch the
+ // verified_contents.json containing signatures for the given extension
+ // id/version pair.
+ virtual GURL GetSignatureFetchUrl(const std::string& extension_id,
+ const base::Version& version) = 0;
+
+ // This should return the set of file paths for images used within the
+ // browser process. (These may get transcoded during the install process).
+ virtual std::set<base::FilePath> GetBrowserImagePaths(
+ const extensions::Extension* extension) = 0;
+
+ // Called when the content verifier detects that a read of a file inside
+ // an extension did not match its expected hash.
+ virtual void VerifyFailed(const std::string& extension_id,
+ ContentVerifyJob::FailureReason reason) = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_CONTENT_VERIFIER_DELEGATE_H_
diff --git a/chromium/extensions/browser/content_verifier_io_data.cc b/chromium/extensions/browser/content_verifier_io_data.cc
new file mode 100644
index 00000000000..d3353be2526
--- /dev/null
+++ b/chromium/extensions/browser/content_verifier_io_data.cc
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/content_verifier_io_data.h"
+
+#include <utility>
+
+#include "content/public/browser/browser_thread.h"
+
+namespace extensions {
+
+ContentVerifierIOData::ExtensionData::ExtensionData(
+ scoped_ptr<std::set<base::FilePath>> browser_image_paths,
+ const base::Version& version) {
+ this->browser_image_paths = std::move(browser_image_paths);
+ this->version = version;
+}
+
+ContentVerifierIOData::ContentVerifierIOData() {
+}
+
+ContentVerifierIOData::ExtensionData::~ExtensionData() {
+}
+
+ContentVerifierIOData::~ContentVerifierIOData() {
+}
+
+void ContentVerifierIOData::AddData(const std::string& extension_id,
+ scoped_ptr<ExtensionData> data) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+ CHECK(data->browser_image_paths.get());
+ data_map_[extension_id] = linked_ptr<ExtensionData>(data.release());
+}
+
+void ContentVerifierIOData::RemoveData(const std::string& extension_id) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+ std::map<std::string, linked_ptr<ExtensionData> >::iterator found =
+ data_map_.find(extension_id);
+ if (found != data_map_.end())
+ data_map_.erase(found);
+}
+
+void ContentVerifierIOData::Clear() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+ data_map_.clear();
+}
+
+const ContentVerifierIOData::ExtensionData* ContentVerifierIOData::GetData(
+ const std::string& extension_id) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+ std::map<std::string, linked_ptr<ExtensionData> >::iterator found =
+ data_map_.find(extension_id);
+ if (found != data_map_.end())
+ return found->second.get();
+ else
+ return NULL;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/content_verifier_io_data.h b/chromium/extensions/browser/content_verifier_io_data.h
new file mode 100644
index 00000000000..92e8248b8ca
--- /dev/null
+++ b/chromium/extensions/browser/content_verifier_io_data.h
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_CONTENT_VERIFIER_IO_DATA_H_
+#define EXTENSIONS_BROWSER_CONTENT_VERIFIER_IO_DATA_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/version.h"
+
+namespace extensions {
+
+// A helper class for keeping track of data for the ContentVerifier that should
+// only be accessed on the IO thread.
+class ContentVerifierIOData
+ : public base::RefCountedThreadSafe<ContentVerifierIOData> {
+ public:
+ struct ExtensionData {
+ scoped_ptr<std::set<base::FilePath>> browser_image_paths;
+ base::Version version;
+
+ ExtensionData(scoped_ptr<std::set<base::FilePath>> browser_image_paths,
+ const base::Version& version);
+ ~ExtensionData();
+ };
+
+ ContentVerifierIOData();
+
+ void AddData(const std::string& extension_id, scoped_ptr<ExtensionData> data);
+ void RemoveData(const std::string& extension_id);
+ void Clear();
+
+ // This should be called on the IO thread, and the return value should not
+ // be retained or used on other threads.
+ const ExtensionData* GetData(const std::string& extension_id);
+
+ protected:
+ friend class base::RefCountedThreadSafe<ContentVerifierIOData>;
+ virtual ~ContentVerifierIOData();
+
+ std::map<std::string, linked_ptr<ExtensionData> > data_map_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_CONTENT_VERIFIER_IO_DATA_H_
diff --git a/chromium/extensions/browser/content_verify_job.cc b/chromium/extensions/browser/content_verify_job.cc
new file mode 100644
index 00000000000..701c01d4107
--- /dev/null
+++ b/chromium/extensions/browser/content_verify_job.cc
@@ -0,0 +1,220 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/content_verify_job.h"
+
+#include <algorithm>
+
+#include "base/metrics/histogram.h"
+#include "base/stl_util.h"
+#include "base/task_runner_util.h"
+#include "base/timer/elapsed_timer.h"
+#include "content/public/browser/browser_thread.h"
+#include "crypto/secure_hash.h"
+#include "crypto/sha2.h"
+#include "extensions/browser/content_hash_reader.h"
+
+namespace extensions {
+
+namespace {
+
+ContentVerifyJob::TestDelegate* g_test_delegate = NULL;
+ContentVerifyJob::TestObserver* g_test_observer = NULL;
+
+class ScopedElapsedTimer {
+ public:
+ explicit ScopedElapsedTimer(base::TimeDelta* total) : total_(total) {
+ DCHECK(total_);
+ }
+
+ ~ScopedElapsedTimer() { *total_ += timer.Elapsed(); }
+
+ private:
+ // Some total amount of time we should add our elapsed time to at
+ // destruction.
+ base::TimeDelta* total_;
+
+ // A timer for how long this object has been alive.
+ base::ElapsedTimer timer;
+};
+
+} // namespace
+
+ContentVerifyJob::ContentVerifyJob(ContentHashReader* hash_reader,
+ const FailureCallback& failure_callback)
+ : done_reading_(false),
+ hashes_ready_(false),
+ total_bytes_read_(0),
+ current_block_(0),
+ current_hash_byte_count_(0),
+ hash_reader_(hash_reader),
+ failure_callback_(failure_callback),
+ failed_(false) {
+ // It's ok for this object to be constructed on a different thread from where
+ // it's used.
+ thread_checker_.DetachFromThread();
+}
+
+ContentVerifyJob::~ContentVerifyJob() {
+ UMA_HISTOGRAM_COUNTS("ExtensionContentVerifyJob.TimeSpentUS",
+ time_spent_.InMicroseconds());
+}
+
+void ContentVerifyJob::Start() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (g_test_observer)
+ g_test_observer->JobStarted(hash_reader_->extension_id(),
+ hash_reader_->relative_path());
+ base::PostTaskAndReplyWithResult(
+ content::BrowserThread::GetBlockingPool(),
+ FROM_HERE,
+ base::Bind(&ContentHashReader::Init, hash_reader_),
+ base::Bind(&ContentVerifyJob::OnHashesReady, this));
+}
+
+void ContentVerifyJob::BytesRead(int count, const char* data) {
+ ScopedElapsedTimer timer(&time_spent_);
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (failed_)
+ return;
+ if (g_test_delegate) {
+ FailureReason reason =
+ g_test_delegate->BytesRead(hash_reader_->extension_id(), count, data);
+ if (reason != NONE)
+ DispatchFailureCallback(reason);
+ return;
+ }
+ if (!hashes_ready_) {
+ queue_.append(data, count);
+ return;
+ }
+ DCHECK_GE(count, 0);
+ int bytes_added = 0;
+
+ while (bytes_added < count) {
+ if (current_block_ >= hash_reader_->block_count())
+ return DispatchFailureCallback(HASH_MISMATCH);
+
+ if (!current_hash_.get()) {
+ current_hash_byte_count_ = 0;
+ current_hash_.reset(
+ crypto::SecureHash::Create(crypto::SecureHash::SHA256));
+ }
+ // Compute how many bytes we should hash, and add them to the current hash.
+ int bytes_to_hash =
+ std::min(hash_reader_->block_size() - current_hash_byte_count_,
+ count - bytes_added);
+ DCHECK_GT(bytes_to_hash, 0);
+ current_hash_->Update(data + bytes_added, bytes_to_hash);
+ bytes_added += bytes_to_hash;
+ current_hash_byte_count_ += bytes_to_hash;
+ total_bytes_read_ += bytes_to_hash;
+
+ // If we finished reading a block worth of data, finish computing the hash
+ // for it and make sure the expected hash matches.
+ if (current_hash_byte_count_ == hash_reader_->block_size() &&
+ !FinishBlock()) {
+ DispatchFailureCallback(HASH_MISMATCH);
+ return;
+ }
+ }
+}
+
+void ContentVerifyJob::DoneReading() {
+ ScopedElapsedTimer timer(&time_spent_);
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (failed_)
+ return;
+ if (g_test_delegate) {
+ FailureReason reason =
+ g_test_delegate->DoneReading(hash_reader_->extension_id());
+ if (reason != NONE)
+ DispatchFailureCallback(reason);
+ return;
+ }
+ done_reading_ = true;
+ if (hashes_ready_) {
+ if (!FinishBlock())
+ DispatchFailureCallback(HASH_MISMATCH);
+ else if (g_test_observer)
+ g_test_observer->JobFinished(hash_reader_->extension_id(),
+ hash_reader_->relative_path(), failed_);
+ }
+}
+
+bool ContentVerifyJob::FinishBlock() {
+ if (current_hash_byte_count_ <= 0)
+ return true;
+ std::string final(crypto::kSHA256Length, 0);
+ current_hash_->Finish(string_as_array(&final), final.size());
+ current_hash_.reset();
+ current_hash_byte_count_ = 0;
+
+ int block = current_block_++;
+
+ const std::string* expected_hash = NULL;
+ if (!hash_reader_->GetHashForBlock(block, &expected_hash) ||
+ *expected_hash != final)
+ return false;
+
+ return true;
+}
+
+void ContentVerifyJob::OnHashesReady(bool success) {
+ if (!success && !g_test_delegate) {
+ if (!hash_reader_->content_exists()) {
+ // Ignore verification of non-existent resources.
+ return;
+ } else if (hash_reader_->have_verified_contents() &&
+ hash_reader_->have_computed_hashes()) {
+ DispatchFailureCallback(NO_HASHES_FOR_FILE);
+ } else {
+ DispatchFailureCallback(MISSING_ALL_HASHES);
+ }
+ return;
+ }
+
+ hashes_ready_ = true;
+ if (!queue_.empty()) {
+ std::string tmp;
+ queue_.swap(tmp);
+ BytesRead(tmp.size(), string_as_array(&tmp));
+ }
+ if (done_reading_) {
+ ScopedElapsedTimer timer(&time_spent_);
+ if (!FinishBlock()) {
+ DispatchFailureCallback(HASH_MISMATCH);
+ } else if (g_test_observer) {
+ g_test_observer->JobFinished(hash_reader_->extension_id(),
+ hash_reader_->relative_path(), failed_);
+ }
+ }
+}
+
+// static
+void ContentVerifyJob::SetDelegateForTests(TestDelegate* delegate) {
+ g_test_delegate = delegate;
+}
+
+// static
+void ContentVerifyJob::SetObserverForTests(TestObserver* observer) {
+ g_test_observer = observer;
+}
+
+void ContentVerifyJob::DispatchFailureCallback(FailureReason reason) {
+ DCHECK(!failed_);
+ failed_ = true;
+ if (!failure_callback_.is_null()) {
+ VLOG(1) << "job failed for " << hash_reader_->extension_id() << " "
+ << hash_reader_->relative_path().MaybeAsASCII()
+ << " reason:" << reason;
+ failure_callback_.Run(reason);
+ failure_callback_.Reset();
+ }
+ if (g_test_observer)
+ g_test_observer->JobFinished(
+ hash_reader_->extension_id(), hash_reader_->relative_path(), failed_);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/content_verify_job.h b/chromium/extensions/browser/content_verify_job.h
new file mode 100644
index 00000000000..30e46e1d145
--- /dev/null
+++ b/chromium/extensions/browser/content_verify_job.h
@@ -0,0 +1,153 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_CONTENT_VERIFY_JOB_H_
+#define EXTENSIONS_BROWSER_CONTENT_VERIFY_JOB_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread_checker.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace crypto {
+class SecureHash;
+}
+
+namespace extensions {
+
+class ContentHashReader;
+
+// Objects of this class are responsible for verifying that the actual content
+// read from an extension file matches an expected set of hashes. This class
+// can be created on any thread but the rest of the methods should be called
+// from only one thread.
+class ContentVerifyJob : public base::RefCountedThreadSafe<ContentVerifyJob> {
+ public:
+ enum FailureReason {
+ // No failure.
+ NONE,
+
+ // Failed because there were no expected hashes at all (eg they haven't
+ // been fetched yet).
+ MISSING_ALL_HASHES,
+
+ // Failed because this file wasn't found in the list of expected hashes.
+ NO_HASHES_FOR_FILE,
+
+ // Some of the content read did not match the expected hash.
+ HASH_MISMATCH,
+
+ FAILURE_REASON_MAX
+ };
+ typedef base::Callback<void(FailureReason)> FailureCallback;
+
+ // The |failure_callback| will be called at most once if there was a failure.
+ ContentVerifyJob(ContentHashReader* hash_reader,
+ const FailureCallback& failure_callback);
+
+ // This begins the process of getting expected hashes, so it should be called
+ // as early as possible.
+ void Start();
+
+ // Call this to add more bytes to verify. If at any point the read bytes
+ // don't match the expected hashes, this will dispatch the failure
+ // callback. The failure callback will only be run once even if more bytes
+ // are read. Make sure to call DoneReading so that any final bytes that were
+ // read that didn't align exactly on a block size boundary get their hash
+ // checked as well.
+ void BytesRead(int count, const char* data);
+
+ // Call once when finished adding bytes via BytesRead.
+ void DoneReading();
+
+ class TestDelegate {
+ public:
+ // These methods will be called inside BytesRead/DoneReading respectively.
+ // If either return something other than NONE, then the failure callback
+ // will be dispatched with that reason.
+ virtual FailureReason BytesRead(const std::string& extension_id,
+ int count,
+ const char* data) = 0;
+ virtual FailureReason DoneReading(const std::string& extension_id) = 0;
+ };
+
+ class TestObserver {
+ public:
+ virtual void JobStarted(const std::string& extension_id,
+ const base::FilePath& relative_path) = 0;
+
+ virtual void JobFinished(const std::string& extension_id,
+ const base::FilePath& relative_path,
+ bool failed) = 0;
+ };
+
+ static void SetDelegateForTests(TestDelegate* delegate);
+ static void SetObserverForTests(TestObserver* observer);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ContentVerifyJob);
+
+ virtual ~ContentVerifyJob();
+ friend class base::RefCountedThreadSafe<ContentVerifyJob>;
+
+ // Called each time we're done adding bytes for the current block, and are
+ // ready to finish the hash operation for those bytes and make sure it
+ // matches what was expected for that block. Returns true if everything is
+ // still ok so far, or false if a mismatch was detected.
+ bool FinishBlock();
+
+ // Dispatches the failure callback with the given reason.
+ void DispatchFailureCallback(FailureReason reason);
+
+ // Called when our ContentHashReader has finished initializing.
+ void OnHashesReady(bool success);
+
+ // Indicates whether the caller has told us they are done calling BytesRead.
+ bool done_reading_;
+
+ // Set to true once hash_reader_ has read its expected hashes.
+ bool hashes_ready_;
+
+ // While we're waiting for the callback from the ContentHashReader, we need
+ // to queue up bytes any bytes that are read.
+ std::string queue_;
+
+ // The total bytes we've read.
+ int64_t total_bytes_read_;
+
+ // The index of the block we're currently on.
+ int current_block_;
+
+ // The hash we're building up for the bytes of |current_block_|.
+ scoped_ptr<crypto::SecureHash> current_hash_;
+
+ // The number of bytes we've already input into |current_hash_|.
+ int current_hash_byte_count_;
+
+ scoped_refptr<ContentHashReader> hash_reader_;
+
+ base::TimeDelta time_spent_;
+
+ // Called once if verification fails.
+ FailureCallback failure_callback_;
+
+ // Set to true if we detected a mismatch and called the failure callback.
+ bool failed_;
+
+ // For ensuring methods on called on the right thread.
+ base::ThreadChecker thread_checker_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_CONTENT_VERIFY_JOB_H_
diff --git a/chromium/extensions/browser/crx_file_info.cc b/chromium/extensions/browser/crx_file_info.cc
new file mode 100644
index 00000000000..34c48820dca
--- /dev/null
+++ b/chromium/extensions/browser/crx_file_info.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/crx_file_info.h"
+
+#include "base/logging.h"
+
+namespace extensions {
+
+CRXFileInfo::CRXFileInfo() : path() {
+}
+
+CRXFileInfo::CRXFileInfo(const std::string& i,
+ const base::FilePath& p,
+ const std::string& h)
+ : extension_id(i), path(p), expected_hash(h) {
+ DCHECK(!path.empty());
+}
+
+CRXFileInfo::CRXFileInfo(const std::string& i, const base::FilePath& p)
+ : extension_id(i), path(p), expected_hash() {
+ DCHECK(!path.empty());
+}
+
+CRXFileInfo::CRXFileInfo(const base::FilePath& p)
+ : extension_id(), path(p), expected_hash() {
+ DCHECK(!path.empty());
+}
+
+bool CRXFileInfo::operator==(const CRXFileInfo& that) const {
+ return extension_id == that.extension_id && path == that.path &&
+ expected_hash == that.expected_hash;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/crx_file_info.h b/chromium/extensions/browser/crx_file_info.h
new file mode 100644
index 00000000000..49a53415c37
--- /dev/null
+++ b/chromium/extensions/browser/crx_file_info.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_CRX_FILE_INFO_H_
+#define EXTENSIONS_BROWSER_CRX_FILE_INFO_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+
+namespace extensions {
+
+// CRXFileInfo holds general information about a cached CRX file
+struct CRXFileInfo {
+ CRXFileInfo();
+ CRXFileInfo(const std::string& extension_id,
+ const base::FilePath& path,
+ const std::string& hash);
+ CRXFileInfo(const std::string& extension_id, const base::FilePath& path);
+ explicit CRXFileInfo(const base::FilePath& path);
+
+ bool operator==(const CRXFileInfo& that) const;
+
+ // The only mandatory field is the file path, whereas extension_id and hash
+ // are only being checked if those are non-empty.
+ std::string extension_id;
+ base::FilePath path;
+ std::string expected_hash;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_CRX_FILE_H_
diff --git a/chromium/extensions/browser/declarative_user_script_manager.cc b/chromium/extensions/browser/declarative_user_script_manager.cc
new file mode 100644
index 00000000000..9c10ab3f2f2
--- /dev/null
+++ b/chromium/extensions/browser/declarative_user_script_manager.cc
@@ -0,0 +1,62 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/declarative_user_script_manager.h"
+
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/declarative_user_script_manager_factory.h"
+#include "extensions/browser/declarative_user_script_master.h"
+#include "extensions/browser/extension_registry.h"
+
+namespace extensions {
+
+DeclarativeUserScriptManager::DeclarativeUserScriptManager(
+ content::BrowserContext* browser_context)
+ : browser_context_(browser_context), extension_registry_observer_(this) {
+ extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context));
+}
+
+DeclarativeUserScriptManager::~DeclarativeUserScriptManager() {
+}
+
+// static
+DeclarativeUserScriptManager* DeclarativeUserScriptManager::Get(
+ content::BrowserContext* browser_context) {
+ return DeclarativeUserScriptManagerFactory::GetForBrowserContext(
+ browser_context);
+}
+
+DeclarativeUserScriptMaster*
+DeclarativeUserScriptManager::GetDeclarativeUserScriptMasterByID(
+ const HostID& host_id) {
+ UserScriptMasterMap::iterator it =
+ declarative_user_script_masters_.find(host_id);
+
+ if (it != declarative_user_script_masters_.end())
+ return it->second.get();
+
+ return CreateDeclarativeUserScriptMaster(host_id);
+}
+
+DeclarativeUserScriptMaster*
+DeclarativeUserScriptManager::CreateDeclarativeUserScriptMaster(
+ const HostID& host_id) {
+ linked_ptr<DeclarativeUserScriptMaster> master(
+ new DeclarativeUserScriptMaster(browser_context_, host_id));
+ declarative_user_script_masters_[host_id] = master;
+ return master.get();
+}
+
+void DeclarativeUserScriptManager::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ for (const auto& val : declarative_user_script_masters_) {
+ DeclarativeUserScriptMaster* master = val.second.get();
+ if (master->host_id().id() == extension->id())
+ master->ClearScripts();
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/declarative_user_script_manager.h b/chromium/extensions/browser/declarative_user_script_manager.h
new file mode 100644
index 00000000000..00779bf066f
--- /dev/null
+++ b/chromium/extensions/browser/declarative_user_script_manager.h
@@ -0,0 +1,68 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_DECLARATIVE_USER_SCRIPT_MANAGER_H_
+#define EXTENSIONS_BROWSER_DECLARATIVE_USER_SCRIPT_MANAGER_H_
+
+#include <map>
+
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/scoped_observer.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/common/host_id.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+class DeclarativeUserScriptMaster;
+
+// Manages a set of DeclarativeUserScriptMaster objects for script injections.
+class DeclarativeUserScriptManager : public KeyedService,
+ public ExtensionRegistryObserver {
+ public:
+ explicit DeclarativeUserScriptManager(
+ content::BrowserContext* browser_context);
+ ~DeclarativeUserScriptManager() override;
+
+ // Convenience method to return the DeclarativeUserScriptManager for a given
+ // |context|.
+ static DeclarativeUserScriptManager* Get(content::BrowserContext* context);
+
+ // Gets the user script master for declarative scripts by the given
+ // HostID; if one does not exist, a new object will be created.
+ DeclarativeUserScriptMaster* GetDeclarativeUserScriptMasterByID(
+ const HostID& host_id);
+
+ private:
+ using UserScriptMasterMap =
+ std::map<HostID, linked_ptr<DeclarativeUserScriptMaster>>;
+
+ // ExtensionRegistryObserver:
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+
+ // Creates a DeclarativeUserScriptMaster object.
+ DeclarativeUserScriptMaster* CreateDeclarativeUserScriptMaster(
+ const HostID& host_id);
+
+ // A map of DeclarativeUserScriptMasters for each host; each master
+ // is lazily initialized.
+ UserScriptMasterMap declarative_user_script_masters_;
+
+ content::BrowserContext* browser_context_;
+
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeclarativeUserScriptManager);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_DECLARATIVE_USER_SCRIPT_MANAGER_H_
diff --git a/chromium/extensions/browser/declarative_user_script_manager_factory.cc b/chromium/extensions/browser/declarative_user_script_manager_factory.cc
new file mode 100644
index 00000000000..cff2c69b5e8
--- /dev/null
+++ b/chromium/extensions/browser/declarative_user_script_manager_factory.cc
@@ -0,0 +1,51 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/declarative_user_script_manager_factory.h"
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "extensions/browser/declarative_user_script_manager.h"
+#include "extensions/browser/extension_registry_factory.h"
+#include "extensions/browser/extensions_browser_client.h"
+
+using content::BrowserContext;
+
+namespace extensions {
+
+// static
+DeclarativeUserScriptManager*
+DeclarativeUserScriptManagerFactory::GetForBrowserContext(
+ BrowserContext* context) {
+ return static_cast<DeclarativeUserScriptManager*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+DeclarativeUserScriptManagerFactory*
+DeclarativeUserScriptManagerFactory::GetInstance() {
+ return base::Singleton<DeclarativeUserScriptManagerFactory>::get();
+}
+
+DeclarativeUserScriptManagerFactory::DeclarativeUserScriptManagerFactory()
+ : BrowserContextKeyedServiceFactory(
+ "DeclarativeUserScriptManager",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(ExtensionRegistryFactory::GetInstance());
+}
+
+DeclarativeUserScriptManagerFactory::~DeclarativeUserScriptManagerFactory() {
+}
+
+KeyedService* DeclarativeUserScriptManagerFactory::BuildServiceInstanceFor(
+ BrowserContext* context) const {
+ return new DeclarativeUserScriptManager(context);
+}
+
+BrowserContext* DeclarativeUserScriptManagerFactory::GetBrowserContextToUse(
+ BrowserContext* context) const {
+ // Redirected in incognito.
+ return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/declarative_user_script_manager_factory.h b/chromium/extensions/browser/declarative_user_script_manager_factory.h
new file mode 100644
index 00000000000..8ba68b552b3
--- /dev/null
+++ b/chromium/extensions/browser/declarative_user_script_manager_factory.h
@@ -0,0 +1,41 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_DECLARATIVE_USER_SCRIPT_MANAGER_FACTORY_H_
+#define EXTENSIONS_BROWSER_DECLARATIVE_USER_SCRIPT_MANAGER_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace extensions {
+
+class DeclarativeUserScriptManager;
+
+class DeclarativeUserScriptManagerFactory
+ : public BrowserContextKeyedServiceFactory {
+ public:
+ static DeclarativeUserScriptManager* GetForBrowserContext(
+ content::BrowserContext* context);
+ static DeclarativeUserScriptManagerFactory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<
+ DeclarativeUserScriptManagerFactory>;
+
+ DeclarativeUserScriptManagerFactory();
+ ~DeclarativeUserScriptManagerFactory() override;
+
+ // BrowserContextKeyedServiceFactory implementation
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(DeclarativeUserScriptManagerFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_DECLARATIVE_USER_SCRIPT_MANAGER_FACTORY_H_
diff --git a/chromium/extensions/browser/declarative_user_script_master.cc b/chromium/extensions/browser/declarative_user_script_master.cc
new file mode 100644
index 00000000000..68fc0c65343
--- /dev/null
+++ b/chromium/extensions/browser/declarative_user_script_master.cc
@@ -0,0 +1,62 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/declarative_user_script_master.h"
+
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/extension_user_script_loader.h"
+#include "extensions/browser/user_script_loader.h"
+#include "extensions/browser/web_ui_user_script_loader.h"
+#include "extensions/common/user_script.h"
+
+namespace extensions {
+
+DeclarativeUserScriptMaster::DeclarativeUserScriptMaster(
+ content::BrowserContext* browser_context,
+ const HostID& host_id)
+ : host_id_(host_id) {
+ switch (host_id_.type()) {
+ case HostID::EXTENSIONS:
+ loader_.reset(new ExtensionUserScriptLoader(
+ browser_context, host_id,
+ false /* listen_for_extension_system_loaded */));
+ break;
+ case HostID::WEBUI:
+ loader_.reset(new WebUIUserScriptLoader(browser_context, host_id));
+ break;
+ }
+}
+
+DeclarativeUserScriptMaster::~DeclarativeUserScriptMaster() {
+}
+
+void DeclarativeUserScriptMaster::AddScript(const UserScript& script) {
+ std::set<UserScript> set;
+ set.insert(script);
+ loader_->AddScripts(set);
+}
+
+void DeclarativeUserScriptMaster::AddScripts(
+ const std::set<UserScript>& scripts,
+ int render_process_id,
+ int render_view_id) {
+ loader_->AddScripts(scripts, render_process_id, render_view_id);
+}
+
+void DeclarativeUserScriptMaster::RemoveScript(const UserScript& script) {
+ std::set<UserScript> set;
+ set.insert(script);
+ loader_->RemoveScripts(set);
+}
+
+void DeclarativeUserScriptMaster::RemoveScripts(
+ const std::set<UserScript>& scripts) {
+ loader_->RemoveScripts(scripts);
+}
+
+void DeclarativeUserScriptMaster::ClearScripts() {
+ loader_->ClearScripts();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/declarative_user_script_master.h b/chromium/extensions/browser/declarative_user_script_master.h
new file mode 100644
index 00000000000..64f8daec6b3
--- /dev/null
+++ b/chromium/extensions/browser/declarative_user_script_master.h
@@ -0,0 +1,75 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_DECLARATIVE_USER_SCRIPT_MASTER_H_
+#define EXTENSIONS_BROWSER_DECLARATIVE_USER_SCRIPT_MASTER_H_
+
+#include <set>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/scoped_observer.h"
+#include "extensions/common/host_id.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class UserScript;
+class UserScriptLoader;
+
+// Manages declarative user scripts for a single extension. Owns a
+// UserScriptLoader to which file loading and shared memory management
+// operations are delegated, and provides an interface for adding, removing,
+// and clearing scripts.
+class DeclarativeUserScriptMaster {
+ public:
+ DeclarativeUserScriptMaster(content::BrowserContext* browser_context,
+ const HostID& host_id);
+ ~DeclarativeUserScriptMaster();
+
+ // Adds script to shared memory region. This may not happen right away if a
+ // script load is in progress.
+ void AddScript(const UserScript& script);
+
+ // Adds a set of scripts to shared meomory region. The fetch of the content
+ // of the script on WebUI requires to start URL request to the associated
+ // render specified by |render_process_id, render_view_id|.
+ // This may not happen right away if a script load is in progress.
+ void AddScripts(const std::set<UserScript>& scripts,
+ int render_process_id,
+ int render_view_id);
+
+ // Removes script from shared memory region. This may not happen right away if
+ // a script load is in progress.
+ void RemoveScript(const UserScript& script);
+
+ // Removes a set of scripts from shared memory region. This may not happen
+ // right away if a script load is in progress.
+ void RemoveScripts(const std::set<UserScript>& scripts);
+
+ // Removes all scripts from shared memory region. This may not happen right
+ // away if a script load is in progress.
+ void ClearScripts();
+
+ const HostID& host_id() const { return host_id_; }
+
+ UserScriptLoader* loader() { return loader_.get(); }
+
+ private:
+ // ID of host that owns scripts that this component manages.
+ HostID host_id_;
+
+ // Script loader that handles loading contents of scripts into shared memory
+ // and notifying renderers of script updates.
+ scoped_ptr<UserScriptLoader> loader_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeclarativeUserScriptMaster);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_DECLARATIVE_USER_SCRIPT_MASTER_H_
diff --git a/chromium/extensions/browser/deferred_start_render_host.h b/chromium/extensions/browser/deferred_start_render_host.h
new file mode 100644
index 00000000000..53b9be533f3
--- /dev/null
+++ b/chromium/extensions/browser/deferred_start_render_host.h
@@ -0,0 +1,34 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_DEFERRED_START_RENDER_HOST_H_
+#define EXTENSIONS_BROWSER_DEFERRED_START_RENDER_HOST_H_
+
+namespace extensions {
+class DeferredStartRenderHostObserver;
+
+// A browser component that tracks a renderer. It allows for its renderer
+// startup to be deferred, to throttle resource usage upon profile startup.
+// To be used with ExtensionHostQueue.
+//
+// Note that if BackgroundContents and ExtensionHost are unified
+// (crbug.com/77790), this interface will be no longer needed.
+class DeferredStartRenderHost {
+ public:
+ virtual ~DeferredStartRenderHost() {}
+
+ // DeferredStartRenderHost lifetime can be observed.
+ virtual void AddDeferredStartRenderHostObserver(
+ DeferredStartRenderHostObserver* observer) = 0;
+ virtual void RemoveDeferredStartRenderHostObserver(
+ DeferredStartRenderHostObserver* observer) = 0;
+
+ // DO NOT CALL THIS unless you're implementing an ExtensionHostQueue.
+ // Called by the ExtensionHostQueue to create the RenderView.
+ virtual void CreateRenderViewNow() = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_DEFERRED_START_RENDER_HOST_H_
diff --git a/chromium/extensions/browser/deferred_start_render_host_observer.h b/chromium/extensions/browser/deferred_start_render_host_observer.h
new file mode 100644
index 00000000000..09f04a27af3
--- /dev/null
+++ b/chromium/extensions/browser/deferred_start_render_host_observer.h
@@ -0,0 +1,34 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_DEFERRED_START_RENDER_HOST_OBSERVER_H_
+#define EXTENSIONS_BROWSER_DEFERRED_START_RENDER_HOST_OBSERVER_H_
+
+namespace extensions {
+class DeferredStartRenderHost;
+
+// Observer of DeferredStartRenderHost lifetime.
+//
+// Note that if BackgroundContents and ExtensionHost are unified
+// (crbug.com/77790), this can be replaced by ExtensionHostObserver.
+class DeferredStartRenderHostObserver {
+ public:
+ virtual ~DeferredStartRenderHostObserver() {}
+
+ // Called when a DeferredStartRenderHost started loading.
+ virtual void OnDeferredStartRenderHostDidStartFirstLoad(
+ const DeferredStartRenderHost* host) {}
+
+ // Called when a DeferredStartRenderHost stopped loading.
+ virtual void OnDeferredStartRenderHostDidStopFirstLoad(
+ const DeferredStartRenderHost* host) {}
+
+ // Called when a DeferredStartRenderHost is destroyed.
+ virtual void OnDeferredStartRenderHostDestroyed(
+ const DeferredStartRenderHost* host) {}
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_DEFERRED_START_RENDER_HOST_OBSERVER_H_
diff --git a/chromium/extensions/browser/entry_info.h b/chromium/extensions/browser/entry_info.h
new file mode 100644
index 00000000000..eb5f5d59ca5
--- /dev/null
+++ b/chromium/extensions/browser/entry_info.h
@@ -0,0 +1,28 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_ENTRY_INFO_H_
+#define EXTENSIONS_BROWSER_ENTRY_INFO_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+
+namespace extensions {
+
+// Contains information about files and directories.
+struct EntryInfo {
+ EntryInfo(const base::FilePath& path,
+ const std::string& mime_type,
+ bool is_directory)
+ : path(path), mime_type(mime_type), is_directory(is_directory) {}
+
+ base::FilePath path;
+ std::string mime_type; // Useful only if is_directory = false.
+ bool is_directory;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_ENTRY_INFO_H_
diff --git a/chromium/extensions/browser/error_map.cc b/chromium/extensions/browser/error_map.cc
new file mode 100644
index 00000000000..5f94e34284a
--- /dev/null
+++ b/chromium/extensions/browser/error_map.cc
@@ -0,0 +1,216 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/error_map.h"
+
+#include <utility>
+
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/stl_util.h"
+
+namespace extensions {
+
+namespace {
+
+// The maximum number of errors to be stored per extension.
+const size_t kMaxErrorsPerExtension = 100;
+
+base::LazyInstance<ErrorList> g_empty_error_list = LAZY_INSTANCE_INITIALIZER;
+
+// An incrementing counter for the next error id. Overflowing this is very
+// unlikely, since the number of errors per extension is capped at 100.
+int kNextErrorId = 1;
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// ErrorMap::Filter
+ErrorMap::Filter::Filter(const std::string& restrict_to_extension_id,
+ int restrict_to_type,
+ const std::set<int>& restrict_to_ids,
+ bool restrict_to_incognito)
+ : restrict_to_extension_id(restrict_to_extension_id),
+ restrict_to_type(restrict_to_type),
+ restrict_to_ids(restrict_to_ids),
+ restrict_to_incognito(restrict_to_incognito) {
+}
+
+ErrorMap::Filter::Filter(const Filter& other) = default;
+
+ErrorMap::Filter::~Filter() {
+}
+
+ErrorMap::Filter ErrorMap::Filter::ErrorsForExtension(
+ const std::string& extension_id) {
+ return Filter(extension_id, -1, std::set<int>(), false);
+}
+
+ErrorMap::Filter ErrorMap::Filter::ErrorsForExtensionWithType(
+ const std::string& extension_id,
+ ExtensionError::Type type) {
+ return Filter(extension_id, type, std::set<int>(), false);
+}
+
+ErrorMap::Filter ErrorMap::Filter::ErrorsForExtensionWithIds(
+ const std::string& extension_id,
+ const std::set<int>& ids) {
+ return Filter(extension_id, -1, ids, false);
+}
+
+ErrorMap::Filter ErrorMap::Filter::ErrorsForExtensionWithTypeAndIds(
+ const std::string& extension_id,
+ ExtensionError::Type type,
+ const std::set<int>& ids) {
+ return Filter(extension_id, type, ids, false);
+}
+
+ErrorMap::Filter ErrorMap::Filter::IncognitoErrors() {
+ return Filter(std::string(), -1, std::set<int>(), true);
+}
+
+bool ErrorMap::Filter::Matches(const ExtensionError* error) const {
+ if (restrict_to_type != -1 && restrict_to_type != error->type())
+ return false;
+ if (restrict_to_incognito && !error->from_incognito())
+ return false;
+ if (!restrict_to_extension_id.empty() &&
+ error->extension_id() != restrict_to_extension_id)
+ return false;
+ if (!restrict_to_ids.empty() && restrict_to_ids.count(error->id()) == 0)
+ return false;
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ErrorMap::ExtensionEntry
+class ErrorMap::ExtensionEntry {
+ public:
+ ExtensionEntry();
+ ~ExtensionEntry();
+
+ // Delete any errors in the entry that match the given ids and type, if
+ // provided.
+ // Returns true if any errors were deleted.
+ bool DeleteErrors(const ErrorMap::Filter& filter);
+ // Delete all errors in the entry.
+ void DeleteAllErrors();
+
+ // Add the error to the list, and return a weak reference.
+ const ExtensionError* AddError(scoped_ptr<ExtensionError> error);
+
+ const ErrorList* list() const { return &list_; }
+
+ private:
+ // The list of all errors associated with the extension. The errors are
+ // owned by the Entry (in turn owned by the ErrorMap) and are deleted upon
+ // destruction.
+ ErrorList list_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionEntry);
+};
+
+ErrorMap::ExtensionEntry::ExtensionEntry() {
+}
+
+ErrorMap::ExtensionEntry::~ExtensionEntry() {
+ DeleteAllErrors();
+}
+
+bool ErrorMap::ExtensionEntry::DeleteErrors(const Filter& filter) {
+ bool deleted = false;
+ for (ErrorList::iterator iter = list_.begin(); iter != list_.end();) {
+ if (filter.Matches(*iter)) {
+ delete *iter;
+ iter = list_.erase(iter);
+ deleted = true;
+ } else {
+ ++iter;
+ }
+ }
+ return deleted;
+}
+
+void ErrorMap::ExtensionEntry::DeleteAllErrors() {
+ STLDeleteContainerPointers(list_.begin(), list_.end());
+ list_.clear();
+}
+
+const ExtensionError* ErrorMap::ExtensionEntry::AddError(
+ scoped_ptr<ExtensionError> error) {
+ for (ErrorList::iterator iter = list_.begin(); iter != list_.end(); ++iter) {
+ // If we find a duplicate error, remove the old error and add the new one,
+ // incrementing the occurrence count of the error. We use the new error
+ // for runtime errors, so we can link to the latest context, inspectable
+ // view, etc.
+ if (error->IsEqual(*iter)) {
+ error->set_occurrences((*iter)->occurrences() + 1);
+ error->set_id((*iter)->id());
+ delete *iter;
+ list_.erase(iter);
+ break;
+ }
+ }
+
+ // If there are too many errors for an extension already, limit ourselves to
+ // the most recent ones.
+ if (list_.size() >= kMaxErrorsPerExtension) {
+ delete list_.front();
+ list_.pop_front();
+ }
+
+ if (error->id() == 0)
+ error->set_id(kNextErrorId++);
+
+ list_.push_back(error.release());
+ return list_.back();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ErrorMap
+ErrorMap::ErrorMap() {
+}
+
+ErrorMap::~ErrorMap() {
+ RemoveAllErrors();
+}
+
+const ErrorList& ErrorMap::GetErrorsForExtension(
+ const std::string& extension_id) const {
+ EntryMap::const_iterator iter = map_.find(extension_id);
+ return iter != map_.end() ? *iter->second->list() : g_empty_error_list.Get();
+}
+
+const ExtensionError* ErrorMap::AddError(scoped_ptr<ExtensionError> error) {
+ EntryMap::iterator iter = map_.find(error->extension_id());
+ if (iter == map_.end()) {
+ iter = map_.insert(std::pair<std::string, ExtensionEntry*>(
+ error->extension_id(), new ExtensionEntry)).first;
+ }
+ return iter->second->AddError(std::move(error));
+}
+
+void ErrorMap::RemoveErrors(const Filter& filter,
+ std::set<std::string>* affected_ids) {
+ if (!filter.restrict_to_extension_id.empty()) {
+ EntryMap::iterator iter = map_.find(filter.restrict_to_extension_id);
+ if (iter != map_.end()) {
+ if (iter->second->DeleteErrors(filter) && affected_ids)
+ affected_ids->insert(filter.restrict_to_extension_id);
+ }
+ } else {
+ for (auto& key_val : map_) {
+ if (key_val.second->DeleteErrors(filter) && affected_ids)
+ affected_ids->insert(key_val.first);
+ }
+ }
+}
+
+void ErrorMap::RemoveAllErrors() {
+ for (EntryMap::iterator iter = map_.begin(); iter != map_.end(); ++iter)
+ delete iter->second;
+ map_.clear();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/error_map.h b/chromium/extensions/browser/error_map.h
new file mode 100644
index 00000000000..929335ab48d
--- /dev/null
+++ b/chromium/extensions/browser/error_map.h
@@ -0,0 +1,90 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_ERROR_MAP_H_
+#define EXTENSIONS_BROWSER_ERROR_MAP_H_
+
+#include <stddef.h>
+
+#include <deque>
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/browser/extension_error.h"
+
+namespace extensions {
+
+typedef std::deque<const ExtensionError*> ErrorList;
+
+// An ErrorMap is responsible for storing Extension-related errors, keyed by
+// Extension ID. The errors are owned by the ErrorMap, and are deleted upon
+// destruction.
+class ErrorMap {
+ public:
+ ErrorMap();
+ ~ErrorMap();
+
+ struct Filter {
+ Filter(const std::string& restrict_to_extension_id,
+ int restrict_to_type,
+ const std::set<int>& restrict_to_ids,
+ bool restrict_to_incognito);
+ Filter(const Filter& other);
+ ~Filter();
+
+ // Convenience methods to get a specific type of filter. Prefer these over
+ // the constructor when possible.
+ static Filter ErrorsForExtension(const std::string& extension_id);
+ static Filter ErrorsForExtensionWithType(const std::string& extension_id,
+ ExtensionError::Type type);
+ static Filter ErrorsForExtensionWithIds(const std::string& extension_id,
+ const std::set<int>& ids);
+ static Filter ErrorsForExtensionWithTypeAndIds(
+ const std::string& extension_id,
+ ExtensionError::Type type,
+ const std::set<int>& ids);
+ static Filter IncognitoErrors();
+
+ bool Matches(const ExtensionError* error) const;
+
+ const std::string restrict_to_extension_id;
+ const int restrict_to_type;
+ const std::set<int> restrict_to_ids;
+ const bool restrict_to_incognito;
+ };
+
+ // Return the list of all errors associated with the given extension.
+ const ErrorList& GetErrorsForExtension(const std::string& extension_id) const;
+
+ // Add the |error| to the ErrorMap.
+ const ExtensionError* AddError(scoped_ptr<ExtensionError> error);
+
+ // Removes errors that match the given |filter| from the map. If non-null,
+ // |affected_ids| will be populated with the set of extension ids that were
+ // affected by this removal.
+ void RemoveErrors(const Filter& filter, std::set<std::string>* affected_ids);
+
+ // Remove all errors for all extensions, and clear the map.
+ void RemoveAllErrors();
+
+ size_t size() const { return map_.size(); }
+
+ private:
+ // An Entry is created for each Extension ID, and stores the errors related to
+ // that Extension.
+ class ExtensionEntry;
+ using EntryMap = std::map<std::string, ExtensionEntry*>;
+
+ // The mapping between Extension IDs and their corresponding Entries.
+ EntryMap map_;
+
+ DISALLOW_COPY_AND_ASSIGN(ErrorMap);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_ERROR_MAP_H_
diff --git a/chromium/extensions/browser/error_map_unittest.cc b/chromium/extensions/browser/error_map_unittest.cc
new file mode 100644
index 00000000000..a118f6e53c8
--- /dev/null
+++ b/chromium/extensions/browser/error_map_unittest.cc
@@ -0,0 +1,171 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/error_map.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/crx_file/id_util.h"
+#include "extensions/browser/extension_error.h"
+#include "extensions/browser/extension_error_test_util.h"
+#include "extensions/common/constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+using error_test_util::CreateNewRuntimeError;
+using error_test_util::CreateNewManifestError;
+
+class ErrorMapUnitTest : public testing::Test {
+ public:
+ ErrorMapUnitTest() { }
+ ~ErrorMapUnitTest() override {}
+
+ void SetUp() override { testing::Test::SetUp(); }
+
+ protected:
+ ErrorMap errors_;
+};
+
+// Test adding errors, and removing them by reference, by incognito status,
+// and in bulk.
+TEST_F(ErrorMapUnitTest, AddAndRemoveErrors) {
+ ASSERT_EQ(0u, errors_.size());
+
+ const size_t kNumTotalErrors = 6;
+ const size_t kNumNonIncognitoErrors = 3;
+ const std::string kId = crx_file::id_util::GenerateId("id");
+ // Populate with both incognito and non-incognito errors (evenly distributed).
+ for (size_t i = 0; i < kNumTotalErrors; ++i) {
+ ASSERT_TRUE(errors_.AddError(
+ CreateNewRuntimeError(kId, base::SizeTToString(i), i % 2 == 0)));
+ }
+
+ // There should only be one entry in the map, since errors are stored in lists
+ // keyed by extension id.
+ EXPECT_EQ(1u, errors_.size());
+
+ EXPECT_EQ(kNumTotalErrors, errors_.GetErrorsForExtension(kId).size());
+
+ // Remove the incognito errors; three errors should remain, and all should
+ // be from non-incognito contexts.
+ std::set<std::string> affected_ids;
+ errors_.RemoveErrors(ErrorMap::Filter::IncognitoErrors(), &affected_ids);
+ const ErrorList& list = errors_.GetErrorsForExtension(kId);
+ EXPECT_EQ(kNumNonIncognitoErrors, list.size());
+ for (size_t i = 0; i < list.size(); ++i)
+ EXPECT_FALSE(list[i]->from_incognito());
+ EXPECT_EQ(1u, affected_ids.size());
+ EXPECT_TRUE(affected_ids.count(kId));
+
+ // Add another error for a different extension id.
+ const std::string kSecondId = crx_file::id_util::GenerateId("id2");
+ EXPECT_TRUE(errors_.AddError(CreateNewRuntimeError(kSecondId, "foo")));
+
+ // There should be two entries now, one for each id, and there should be one
+ // error for the second extension.
+ EXPECT_EQ(2u, errors_.size());
+ EXPECT_EQ(1u, errors_.GetErrorsForExtension(kSecondId).size());
+
+ // Remove all errors for the second id.
+ affected_ids.clear();
+ errors_.RemoveErrors(ErrorMap::Filter::ErrorsForExtension(kSecondId),
+ &affected_ids);
+ EXPECT_EQ(0u, errors_.GetErrorsForExtension(kSecondId).size());
+ // First extension should be unaffected.
+ EXPECT_EQ(kNumNonIncognitoErrors, errors_.GetErrorsForExtension(kId).size());
+ EXPECT_EQ(1u, affected_ids.size());
+ EXPECT_TRUE(affected_ids.count(kSecondId));
+
+ errors_.AddError(CreateNewManifestError(kId, "manifest error"));
+ EXPECT_EQ(kNumNonIncognitoErrors + 1,
+ errors_.GetErrorsForExtension(kId).size());
+ errors_.RemoveErrors(ErrorMap::Filter::ErrorsForExtensionWithType(
+ kId, ExtensionError::MANIFEST_ERROR), nullptr);
+ EXPECT_EQ(kNumNonIncognitoErrors, errors_.GetErrorsForExtension(kId).size());
+
+ const ExtensionError* added_error =
+ errors_.AddError(CreateNewManifestError(kId, "manifest error2"));
+ EXPECT_EQ(kNumNonIncognitoErrors + 1,
+ errors_.GetErrorsForExtension(kId).size());
+ std::set<int> ids;
+ ids.insert(added_error->id());
+ errors_.RemoveErrors(ErrorMap::Filter::ErrorsForExtensionWithIds(kId, ids),
+ nullptr);
+ EXPECT_EQ(kNumNonIncognitoErrors, errors_.GetErrorsForExtension(kId).size());
+
+ // Remove remaining errors.
+ errors_.RemoveAllErrors();
+ EXPECT_EQ(0u, errors_.size());
+ EXPECT_EQ(0u, errors_.GetErrorsForExtension(kId).size());
+}
+
+// Test that if we add enough errors, only the most recent
+// kMaxErrorsPerExtension are kept.
+TEST_F(ErrorMapUnitTest, ExcessiveErrorsGetCropped) {
+ ASSERT_EQ(0u, errors_.size());
+
+ // This constant matches one of the same name in error_console.cc.
+ const size_t kMaxErrorsPerExtension = 100;
+ const size_t kNumExtraErrors = 5;
+ const std::string kId = crx_file::id_util::GenerateId("id");
+
+ // Add new errors, with each error's message set to its number.
+ for (size_t i = 0; i < kMaxErrorsPerExtension + kNumExtraErrors; ++i) {
+ ASSERT_TRUE(
+ errors_.AddError(CreateNewRuntimeError(kId, base::SizeTToString(i))));
+ }
+
+ ASSERT_EQ(1u, errors_.size());
+
+ const ErrorList& list = errors_.GetErrorsForExtension(kId);
+ ASSERT_EQ(kMaxErrorsPerExtension, list.size());
+
+ // We should have popped off errors in the order they arrived, so the
+ // first stored error should be the 6th reported (zero-based)...
+ ASSERT_EQ(base::SizeTToString16(kNumExtraErrors), list.front()->message());
+ // ..and the last stored should be the 105th reported.
+ ASSERT_EQ(base::SizeTToString16(kMaxErrorsPerExtension + kNumExtraErrors - 1),
+ list.back()->message());
+}
+
+// Test to ensure that the error console will not add duplicate errors, but will
+// keep the latest version of an error.
+TEST_F(ErrorMapUnitTest, DuplicateErrorsAreReplaced) {
+ ASSERT_EQ(0u, errors_.size());
+
+ const std::string kId = crx_file::id_util::GenerateId("id");
+ const size_t kNumErrors = 3u;
+
+ // Report three errors.
+ for (size_t i = 0; i < kNumErrors; ++i) {
+ ASSERT_TRUE(
+ errors_.AddError(CreateNewRuntimeError(kId, base::SizeTToString(i))));
+ }
+
+ // Create an error identical to the second error reported, save its
+ // location, and add it to the error map.
+ scoped_ptr<ExtensionError> runtime_error2 =
+ CreateNewRuntimeError(kId, base::UintToString(1u));
+ const ExtensionError* weak_error = runtime_error2.get();
+ ASSERT_TRUE(errors_.AddError(std::move(runtime_error2)));
+
+ // We should only have three errors stored, since two of the four reported
+ // were identical, and the older should have been replaced.
+ ASSERT_EQ(1u, errors_.size());
+ const ErrorList& list = errors_.GetErrorsForExtension(kId);
+ ASSERT_EQ(kNumErrors, list.size());
+
+ // The duplicate error should be the last reported (pointer comparison)...
+ ASSERT_EQ(weak_error, list.back());
+ // ... and should have two reported occurrences.
+ ASSERT_EQ(2u, list.back()->occurrences());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/event_listener_map.cc b/chromium/extensions/browser/event_listener_map.cc
new file mode 100644
index 00000000000..af91b6fbec2
--- /dev/null
+++ b/chromium/extensions/browser/event_listener_map.cc
@@ -0,0 +1,285 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/event_listener_map.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/values.h"
+#include "content/public/browser/render_process_host.h"
+#include "extensions/browser/event_router.h"
+#include "ipc/ipc_message.h"
+#include "url/gurl.h"
+
+using base::DictionaryValue;
+
+namespace extensions {
+
+typedef EventFilter::MatcherID MatcherID;
+
+// static
+scoped_ptr<EventListener> EventListener::ForExtension(
+ const std::string& event_name,
+ const std::string& extension_id,
+ content::RenderProcessHost* process,
+ scoped_ptr<base::DictionaryValue> filter) {
+ return make_scoped_ptr(new EventListener(event_name, extension_id, GURL(),
+ process, std::move(filter)));
+}
+
+// static
+scoped_ptr<EventListener> EventListener::ForURL(
+ const std::string& event_name,
+ const GURL& listener_url,
+ content::RenderProcessHost* process,
+ scoped_ptr<base::DictionaryValue> filter) {
+ return make_scoped_ptr(new EventListener(event_name, "", listener_url,
+ process, std::move(filter)));
+}
+
+EventListener::~EventListener() {}
+
+bool EventListener::Equals(const EventListener* other) const {
+ // We don't check matcher_id equality because we want a listener with a
+ // filter that hasn't been added to EventFilter to match one that is
+ // equivalent but has.
+ return event_name_ == other->event_name_ &&
+ extension_id_ == other->extension_id_ &&
+ listener_url_ == other->listener_url_ && process_ == other->process_ &&
+ ((!!filter_.get()) == (!!other->filter_.get())) &&
+ (!filter_.get() || filter_->Equals(other->filter_.get()));
+}
+
+scoped_ptr<EventListener> EventListener::Copy() const {
+ scoped_ptr<DictionaryValue> filter_copy;
+ if (filter_)
+ filter_copy.reset(filter_->DeepCopy());
+ return scoped_ptr<EventListener>(new EventListener(event_name_, extension_id_,
+ listener_url_, process_,
+ std::move(filter_copy)));
+}
+
+bool EventListener::IsLazy() const {
+ return !process_;
+}
+
+void EventListener::MakeLazy() {
+ process_ = NULL;
+}
+
+content::BrowserContext* EventListener::GetBrowserContext() const {
+ return process_ ? process_->GetBrowserContext() : NULL;
+}
+
+EventListener::EventListener(const std::string& event_name,
+ const std::string& extension_id,
+ const GURL& listener_url,
+ content::RenderProcessHost* process,
+ scoped_ptr<DictionaryValue> filter)
+ : event_name_(event_name),
+ extension_id_(extension_id),
+ listener_url_(listener_url),
+ process_(process),
+ filter_(std::move(filter)),
+ matcher_id_(-1) {}
+
+EventListenerMap::EventListenerMap(Delegate* delegate)
+ : delegate_(delegate) {
+}
+
+EventListenerMap::~EventListenerMap() {}
+
+bool EventListenerMap::AddListener(scoped_ptr<EventListener> listener) {
+ if (HasListener(listener.get()))
+ return false;
+ if (listener->filter()) {
+ scoped_ptr<EventMatcher> matcher(ParseEventMatcher(listener->filter()));
+ MatcherID id = event_filter_.AddEventMatcher(listener->event_name(),
+ std::move(matcher));
+ listener->set_matcher_id(id);
+ listeners_by_matcher_id_[id] = listener.get();
+ filtered_events_.insert(listener->event_name());
+ }
+ linked_ptr<EventListener> listener_ptr(listener.release());
+ listeners_[listener_ptr->event_name()].push_back(listener_ptr);
+
+ delegate_->OnListenerAdded(listener_ptr.get());
+
+ return true;
+}
+
+scoped_ptr<EventMatcher> EventListenerMap::ParseEventMatcher(
+ DictionaryValue* filter_dict) {
+ return scoped_ptr<EventMatcher>(new EventMatcher(
+ make_scoped_ptr(filter_dict->DeepCopy()), MSG_ROUTING_NONE));
+}
+
+bool EventListenerMap::RemoveListener(const EventListener* listener) {
+ ListenerList& listeners = listeners_[listener->event_name()];
+ for (ListenerList::iterator it = listeners.begin(); it != listeners.end();
+ it++) {
+ if ((*it)->Equals(listener)) {
+ CleanupListener(it->get());
+ // Popping from the back should be cheaper than erase(it).
+ std::swap(*it, listeners.back());
+ listeners.pop_back();
+ delegate_->OnListenerRemoved(listener);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool EventListenerMap::HasListenerForEvent(const std::string& event_name) {
+ ListenerMap::iterator it = listeners_.find(event_name);
+ return it != listeners_.end() && !it->second.empty();
+}
+
+bool EventListenerMap::HasListenerForExtension(
+ const std::string& extension_id,
+ const std::string& event_name) {
+ ListenerMap::iterator it = listeners_.find(event_name);
+ if (it == listeners_.end())
+ return false;
+
+ for (ListenerList::iterator it2 = it->second.begin();
+ it2 != it->second.end(); it2++) {
+ if ((*it2)->extension_id() == extension_id)
+ return true;
+ }
+ return false;
+}
+
+bool EventListenerMap::HasListener(const EventListener* listener) {
+ ListenerMap::iterator it = listeners_.find(listener->event_name());
+ if (it == listeners_.end())
+ return false;
+ for (ListenerList::iterator it2 = it->second.begin();
+ it2 != it->second.end(); it2++) {
+ if ((*it2)->Equals(listener)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool EventListenerMap::HasProcessListener(content::RenderProcessHost* process,
+ const std::string& extension_id) {
+ for (ListenerMap::iterator it = listeners_.begin(); it != listeners_.end();
+ it++) {
+ for (ListenerList::iterator it2 = it->second.begin();
+ it2 != it->second.end(); it2++) {
+ if ((*it2)->process() == process &&
+ (*it2)->extension_id() == extension_id)
+ return true;
+ }
+ }
+ return false;
+}
+
+void EventListenerMap::RemoveListenersForExtension(
+ const std::string& extension_id) {
+ for (ListenerMap::iterator it = listeners_.begin(); it != listeners_.end();
+ it++) {
+ for (ListenerList::iterator it2 = it->second.begin();
+ it2 != it->second.end();) {
+ if ((*it2)->extension_id() == extension_id) {
+ linked_ptr<EventListener> listener(*it2);
+ CleanupListener(listener.get());
+ it2 = it->second.erase(it2);
+ delegate_->OnListenerRemoved(listener.get());
+ } else {
+ it2++;
+ }
+ }
+ }
+}
+
+void EventListenerMap::LoadUnfilteredLazyListeners(
+ const std::string& extension_id,
+ const std::set<std::string>& event_names) {
+ for (std::set<std::string>::const_iterator it = event_names.begin();
+ it != event_names.end(); ++it) {
+ AddListener(EventListener::ForExtension(
+ *it, extension_id, NULL, scoped_ptr<DictionaryValue>()));
+ }
+}
+
+void EventListenerMap::LoadFilteredLazyListeners(
+ const std::string& extension_id,
+ const DictionaryValue& filtered) {
+ for (DictionaryValue::Iterator it(filtered); !it.IsAtEnd(); it.Advance()) {
+ // We skip entries if they are malformed.
+ const base::ListValue* filter_list = NULL;
+ if (!it.value().GetAsList(&filter_list))
+ continue;
+ for (size_t i = 0; i < filter_list->GetSize(); i++) {
+ const DictionaryValue* filter = NULL;
+ if (!filter_list->GetDictionary(i, &filter))
+ continue;
+ AddListener(EventListener::ForExtension(
+ it.key(), extension_id, NULL, make_scoped_ptr(filter->DeepCopy())));
+ }
+ }
+}
+
+std::set<const EventListener*> EventListenerMap::GetEventListeners(
+ const Event& event) {
+ std::set<const EventListener*> interested_listeners;
+ if (IsFilteredEvent(event)) {
+ // Look up the interested listeners via the EventFilter.
+ std::set<MatcherID> ids =
+ event_filter_.MatchEvent(event.event_name, event.filter_info,
+ MSG_ROUTING_NONE);
+ for (std::set<MatcherID>::iterator id = ids.begin(); id != ids.end();
+ id++) {
+ EventListener* listener = listeners_by_matcher_id_[*id];
+ CHECK(listener);
+ interested_listeners.insert(listener);
+ }
+ } else {
+ ListenerList& listeners = listeners_[event.event_name];
+ for (ListenerList::const_iterator it = listeners.begin();
+ it != listeners.end(); it++) {
+ interested_listeners.insert(it->get());
+ }
+ }
+
+ return interested_listeners;
+}
+
+void EventListenerMap::RemoveListenersForProcess(
+ const content::RenderProcessHost* process) {
+ CHECK(process);
+ for (ListenerMap::iterator it = listeners_.begin(); it != listeners_.end();
+ it++) {
+ for (ListenerList::iterator it2 = it->second.begin();
+ it2 != it->second.end();) {
+ if ((*it2)->process() == process) {
+ linked_ptr<EventListener> listener(*it2);
+ CleanupListener(it2->get());
+ it2 = it->second.erase(it2);
+ delegate_->OnListenerRemoved(listener.get());
+ } else {
+ it2++;
+ }
+ }
+ }
+}
+
+void EventListenerMap::CleanupListener(EventListener* listener) {
+ // If the listener doesn't have a filter then we have nothing to clean up.
+ if (listener->matcher_id() == -1)
+ return;
+ event_filter_.RemoveEventMatcher(listener->matcher_id());
+ CHECK_EQ(1u, listeners_by_matcher_id_.erase(listener->matcher_id()));
+}
+
+bool EventListenerMap::IsFilteredEvent(const Event& event) const {
+ return filtered_events_.count(event.event_name) > 0u;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/event_listener_map.h b/chromium/extensions/browser/event_listener_map.h
new file mode 100644
index 00000000000..0ce5f3c3ac5
--- /dev/null
+++ b/chromium/extensions/browser/event_listener_map.h
@@ -0,0 +1,199 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EVENT_LISTENER_MAP_H_
+#define EXTENSIONS_BROWSER_EVENT_LISTENER_MAP_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/event_filter.h"
+#include "url/gurl.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace content {
+class BrowserContext;
+class RenderProcessHost;
+}
+
+class ListenerRemovalListener;
+
+namespace extensions {
+struct Event;
+
+// A listener for an extension event. A listener is essentially an endpoint
+// that an event can be dispatched to.
+//
+// This is a lazy listener if |IsLazy| is returns true, and a filtered listener
+// if |filter| is defined.
+//
+// A lazy listener is added to an event to indicate that a lazy background page
+// is listening to the event. It is associated with no process, so to dispatch
+// an event to a lazy listener one must start a process running the associated
+// extension and dispatch the event to that.
+class EventListener {
+ public:
+ // Constructs EventListeners for either an Extension or a URL.
+ //
+ // |filter| represents a generic filter structure that EventFilter knows how
+ // to filter events with. A typical filter instance will look like
+ //
+ // {
+ // url: [{hostSuffix: 'google.com'}],
+ // tabId: 5
+ // }
+ static scoped_ptr<EventListener> ForExtension(
+ const std::string& event_name,
+ const std::string& extension_id,
+ content::RenderProcessHost* process,
+ scoped_ptr<base::DictionaryValue> filter);
+ static scoped_ptr<EventListener> ForURL(
+ const std::string& event_name,
+ const GURL& listener_url,
+ content::RenderProcessHost* process,
+ scoped_ptr<base::DictionaryValue> filter);
+
+ ~EventListener();
+
+ bool Equals(const EventListener* other) const;
+
+ scoped_ptr<EventListener> Copy() const;
+
+ // Returns true in the case of a lazy background page, and thus no process.
+ bool IsLazy() const;
+
+ // Modifies this listener to be a lazy listener, clearing process references.
+ void MakeLazy();
+
+ // Returns the browser context associated with the listener, or NULL if
+ // IsLazy.
+ content::BrowserContext* GetBrowserContext() const;
+
+ const std::string& event_name() const { return event_name_; }
+ const std::string& extension_id() const { return extension_id_; }
+ const GURL& listener_url() const { return listener_url_; }
+ content::RenderProcessHost* process() const { return process_; }
+ base::DictionaryValue* filter() const { return filter_.get(); }
+ EventFilter::MatcherID matcher_id() const { return matcher_id_; }
+ void set_matcher_id(EventFilter::MatcherID id) { matcher_id_ = id; }
+
+ private:
+ EventListener(const std::string& event_name,
+ const std::string& extension_id,
+ const GURL& listener_url,
+ content::RenderProcessHost* process,
+ scoped_ptr<base::DictionaryValue> filter);
+
+ const std::string event_name_;
+ const std::string extension_id_;
+ const GURL listener_url_;
+ content::RenderProcessHost* process_;
+ scoped_ptr<base::DictionaryValue> filter_;
+ EventFilter::MatcherID matcher_id_; // -1 if unset.
+
+ DISALLOW_COPY_AND_ASSIGN(EventListener);
+};
+
+// Holds listeners for extension events and can answer questions about which
+// listeners are interested in what events.
+class EventListenerMap {
+ public:
+ typedef std::vector<linked_ptr<EventListener> > ListenerList;
+
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+ virtual void OnListenerAdded(const EventListener* listener) = 0;
+ virtual void OnListenerRemoved(const EventListener* listener) = 0;
+ };
+
+ explicit EventListenerMap(Delegate* delegate);
+ ~EventListenerMap();
+
+ // Add a listener for a particular event. GetEventListeners() will include a
+ // weak pointer to |listener| in its results if passed a relevant
+ // extensions::Event.
+ // Returns true if the listener was added (in the case that it has never been
+ // seen before).
+ bool AddListener(scoped_ptr<EventListener> listener);
+
+ // Remove a listener that .Equals() |listener|.
+ // Returns true if the listener was removed .
+ bool RemoveListener(const EventListener* listener);
+
+ // Returns the set of listeners that want to be notified of |event|.
+ std::set<const EventListener*> GetEventListeners(const Event& event);
+
+ const ListenerList& GetEventListenersByName(const std::string& event_name) {
+ return listeners_[event_name];
+ }
+
+ // Removes all listeners with process equal to |process|.
+ void RemoveListenersForProcess(const content::RenderProcessHost* process);
+
+ // Returns true if there are any listeners on the event named |event_name|.
+ bool HasListenerForEvent(const std::string& event_name);
+
+ // Returns true if there are any listeners on |event_name| from
+ // |extension_id|.
+ bool HasListenerForExtension(const std::string& extension_id,
+ const std::string& event_name);
+
+ // Returns true if this map contains an EventListener that .Equals()
+ // |listener|.
+ bool HasListener(const EventListener* listener);
+
+ // Returns true if there is a listener for |extension_id| in |process|.
+ bool HasProcessListener(content::RenderProcessHost* process,
+ const std::string& extension_id);
+
+ // Removes any listeners that |extension_id| has added, both lazy and regular.
+ void RemoveListenersForExtension(const std::string& extension_id);
+
+ // Adds unfiltered lazy listeners as described their serialised descriptions.
+ // |event_names| the names of the lazy events.
+ // Note that we can only load lazy listeners in this fashion, because there
+ // is no way to serialise a RenderProcessHost*.
+ void LoadUnfilteredLazyListeners(const std::string& extension_id,
+ const std::set<std::string>& event_names);
+
+ // Adds filtered lazy listeners as described their serialised descriptions.
+ // |filtered| contains a map from event names to filters, each pairing
+ // defining a lazy filtered listener.
+ void LoadFilteredLazyListeners(
+ const std::string& extension_id,
+ const base::DictionaryValue& filtered);
+
+ private:
+ // The key here is an event name.
+ typedef std::map<std::string, ListenerList> ListenerMap;
+
+ void CleanupListener(EventListener* listener);
+ bool IsFilteredEvent(const Event& event) const;
+ scoped_ptr<EventMatcher> ParseEventMatcher(
+ base::DictionaryValue* filter_dict);
+
+ // Listens for removals from this map.
+ Delegate* delegate_;
+
+ std::set<std::string> filtered_events_;
+ ListenerMap listeners_;
+
+ std::map<EventFilter::MatcherID, EventListener*> listeners_by_matcher_id_;
+
+ EventFilter event_filter_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventListenerMap);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EVENT_LISTENER_MAP_H_
diff --git a/chromium/extensions/browser/event_listener_map_unittest.cc b/chromium/extensions/browser/event_listener_map_unittest.cc
new file mode 100644
index 00000000000..0fa17d08267
--- /dev/null
+++ b/chromium/extensions/browser/event_listener_map_unittest.cc
@@ -0,0 +1,389 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/event_listener_map.h"
+
+#include "base/bind.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_browser_context.h"
+#include "extensions/browser/event_router.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using base::DictionaryValue;
+using base::ListValue;
+using base::StringValue;
+
+namespace extensions {
+
+namespace {
+
+const char kExt1Id[] = "extension_1";
+const char kExt2Id[] = "extension_2";
+const char kEvent1Name[] = "event1";
+const char kEvent2Name[] = "event2";
+const char kURL[] = "https://google.com/some/url";
+
+typedef base::Callback<scoped_ptr<EventListener>(
+ const std::string&, // event_name
+ content::RenderProcessHost*, // process
+ base::DictionaryValue* // filter (takes ownership)
+ )> EventListenerConstructor;
+
+class EmptyDelegate : public EventListenerMap::Delegate {
+ void OnListenerAdded(const EventListener* listener) override{};
+ void OnListenerRemoved(const EventListener* listener) override{};
+};
+
+class EventListenerMapTest : public testing::Test {
+ public:
+ EventListenerMapTest()
+ : delegate_(new EmptyDelegate),
+ listeners_(new EventListenerMap(delegate_.get())),
+ browser_context_(new content::TestBrowserContext),
+ process_(new content::MockRenderProcessHost(browser_context_.get())) {}
+
+ scoped_ptr<DictionaryValue> CreateHostSuffixFilter(
+ const std::string& suffix) {
+ scoped_ptr<DictionaryValue> filter(new DictionaryValue);
+ scoped_ptr<ListValue> filter_list(new ListValue);
+ scoped_ptr<DictionaryValue> filter_dict(new DictionaryValue);
+
+ filter_dict->Set("hostSuffix", new StringValue(suffix));
+
+ filter_list->Append(filter_dict.release());
+ filter->Set("url", filter_list.release());
+ return filter;
+ }
+
+ scoped_ptr<Event> CreateNamedEvent(const std::string& event_name) {
+ return CreateEvent(event_name, GURL());
+ }
+
+ scoped_ptr<Event> CreateEvent(const std::string& event_name,
+ const GURL& url) {
+ EventFilteringInfo info;
+ info.SetURL(url);
+ scoped_ptr<Event> result(new Event(
+ events::FOR_TEST, event_name, make_scoped_ptr(new ListValue()), NULL,
+ GURL(), EventRouter::USER_GESTURE_UNKNOWN, info));
+ return result;
+ }
+
+ protected:
+ void TestUnfilteredEventsGoToAllListeners(
+ const EventListenerConstructor& constructor);
+ void TestRemovingByProcess(const EventListenerConstructor& constructor);
+ void TestRemovingByListener(const EventListenerConstructor& constructor);
+ void TestAddExistingUnfilteredListener(
+ const EventListenerConstructor& constructor);
+ void TestHasListenerForEvent(const EventListenerConstructor& constructor);
+
+ scoped_ptr<EventListenerMap::Delegate> delegate_;
+ scoped_ptr<EventListenerMap> listeners_;
+ scoped_ptr<content::TestBrowserContext> browser_context_;
+ scoped_ptr<content::MockRenderProcessHost> process_;
+};
+
+scoped_ptr<EventListener> CreateEventListenerForExtension(
+ const std::string& extension_id,
+ const std::string& event_name,
+ content::RenderProcessHost* process,
+ base::DictionaryValue* filter) {
+ return EventListener::ForExtension(
+ event_name, extension_id, process, make_scoped_ptr(filter));
+}
+
+scoped_ptr<EventListener> CreateEventListenerForURL(
+ const GURL& listener_url,
+ const std::string& event_name,
+ content::RenderProcessHost* process,
+ base::DictionaryValue* filter) {
+ return EventListener::ForURL(
+ event_name, listener_url, process, make_scoped_ptr(filter));
+}
+
+void EventListenerMapTest::TestUnfilteredEventsGoToAllListeners(
+ const EventListenerConstructor& constructor) {
+ listeners_->AddListener(
+ constructor.Run(kEvent1Name, NULL, new DictionaryValue()));
+ scoped_ptr<Event> event(CreateNamedEvent(kEvent1Name));
+ ASSERT_EQ(1u, listeners_->GetEventListeners(*event).size());
+}
+
+TEST_F(EventListenerMapTest, UnfilteredEventsGoToAllListenersForExtensions) {
+ TestUnfilteredEventsGoToAllListeners(
+ base::Bind(&CreateEventListenerForExtension, kExt1Id));
+}
+
+TEST_F(EventListenerMapTest, UnfilteredEventsGoToAllListenersForURLs) {
+ TestUnfilteredEventsGoToAllListeners(
+ base::Bind(&CreateEventListenerForURL, GURL(kURL)));
+}
+
+TEST_F(EventListenerMapTest, FilteredEventsGoToAllMatchingListeners) {
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name,
+ kExt1Id,
+ NULL,
+ scoped_ptr<DictionaryValue>(new DictionaryValue)));
+
+ scoped_ptr<Event> event(CreateNamedEvent(kEvent1Name));
+ event->filter_info.SetURL(GURL("http://www.google.com"));
+ std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
+ ASSERT_EQ(2u, targets.size());
+}
+
+TEST_F(EventListenerMapTest, FilteredEventsOnlyGoToMatchingListeners) {
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("yahoo.com")));
+
+ scoped_ptr<Event> event(CreateNamedEvent(kEvent1Name));
+ event->filter_info.SetURL(GURL("http://www.google.com"));
+ std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
+ ASSERT_EQ(1u, targets.size());
+}
+
+TEST_F(EventListenerMapTest, LazyAndUnlazyListenersGetReturned) {
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
+
+ listeners_->AddListener(
+ EventListener::ForExtension(kEvent1Name,
+ kExt1Id,
+ process_.get(),
+ CreateHostSuffixFilter("google.com")));
+
+ scoped_ptr<Event> event(CreateNamedEvent(kEvent1Name));
+ event->filter_info.SetURL(GURL("http://www.google.com"));
+ std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
+ ASSERT_EQ(2u, targets.size());
+}
+
+void EventListenerMapTest::TestRemovingByProcess(
+ const EventListenerConstructor& constructor) {
+ listeners_->AddListener(constructor.Run(
+ kEvent1Name, NULL, CreateHostSuffixFilter("google.com").release()));
+
+ listeners_->AddListener(
+ constructor.Run(kEvent1Name,
+ process_.get(),
+ CreateHostSuffixFilter("google.com").release()));
+
+ listeners_->RemoveListenersForProcess(process_.get());
+
+ scoped_ptr<Event> event(CreateNamedEvent(kEvent1Name));
+ event->filter_info.SetURL(GURL("http://www.google.com"));
+ ASSERT_EQ(1u, listeners_->GetEventListeners(*event).size());
+}
+
+TEST_F(EventListenerMapTest, TestRemovingByProcessForExtension) {
+ TestRemovingByProcess(base::Bind(&CreateEventListenerForExtension, kExt1Id));
+}
+
+TEST_F(EventListenerMapTest, TestRemovingByProcessForURL) {
+ TestRemovingByProcess(base::Bind(&CreateEventListenerForURL, GURL(kURL)));
+}
+
+void EventListenerMapTest::TestRemovingByListener(
+ const EventListenerConstructor& constructor) {
+ listeners_->AddListener(constructor.Run(
+ kEvent1Name, NULL, CreateHostSuffixFilter("google.com").release()));
+
+ listeners_->AddListener(
+ constructor.Run(kEvent1Name,
+ process_.get(),
+ CreateHostSuffixFilter("google.com").release()));
+
+ scoped_ptr<EventListener> listener(
+ constructor.Run(kEvent1Name,
+ process_.get(),
+ CreateHostSuffixFilter("google.com").release()));
+ listeners_->RemoveListener(listener.get());
+
+ scoped_ptr<Event> event(CreateNamedEvent(kEvent1Name));
+ event->filter_info.SetURL(GURL("http://www.google.com"));
+ ASSERT_EQ(1u, listeners_->GetEventListeners(*event).size());
+}
+
+TEST_F(EventListenerMapTest, TestRemovingByListenerForExtension) {
+ TestRemovingByListener(base::Bind(&CreateEventListenerForExtension, kExt1Id));
+}
+
+TEST_F(EventListenerMapTest, TestRemovingByListenerForURL) {
+ TestRemovingByListener(base::Bind(&CreateEventListenerForURL, GURL(kURL)));
+}
+
+TEST_F(EventListenerMapTest, TestLazyDoubleAddIsUndoneByRemove) {
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
+
+ scoped_ptr<EventListener> listener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
+ listeners_->RemoveListener(listener.get());
+
+ scoped_ptr<Event> event(CreateNamedEvent(kEvent1Name));
+ event->filter_info.SetURL(GURL("http://www.google.com"));
+ std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
+ ASSERT_EQ(0u, targets.size());
+}
+
+TEST_F(EventListenerMapTest, HostSuffixFilterEquality) {
+ scoped_ptr<DictionaryValue> filter1(CreateHostSuffixFilter("google.com"));
+ scoped_ptr<DictionaryValue> filter2(CreateHostSuffixFilter("google.com"));
+ ASSERT_TRUE(filter1->Equals(filter2.get()));
+}
+
+TEST_F(EventListenerMapTest, RemoveListenersForExtension) {
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent2Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
+
+ listeners_->RemoveListenersForExtension(kExt1Id);
+
+ scoped_ptr<Event> event(CreateNamedEvent(kEvent1Name));
+ event->filter_info.SetURL(GURL("http://www.google.com"));
+ std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
+ ASSERT_EQ(0u, targets.size());
+
+ event->event_name = kEvent2Name;
+ targets = listeners_->GetEventListeners(*event);
+ ASSERT_EQ(0u, targets.size());
+}
+
+TEST_F(EventListenerMapTest, AddExistingFilteredListener) {
+ bool first_new = listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
+ bool second_new = listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
+
+ ASSERT_TRUE(first_new);
+ ASSERT_FALSE(second_new);
+}
+
+void EventListenerMapTest::TestAddExistingUnfilteredListener(
+ const EventListenerConstructor& constructor) {
+ bool first_add = listeners_->AddListener(
+ constructor.Run(kEvent1Name, NULL, new DictionaryValue()));
+ bool second_add = listeners_->AddListener(
+ constructor.Run(kEvent1Name, NULL, new DictionaryValue()));
+
+ scoped_ptr<EventListener> listener(
+ constructor.Run(kEvent1Name, NULL, new DictionaryValue()));
+ bool first_remove = listeners_->RemoveListener(listener.get());
+ bool second_remove = listeners_->RemoveListener(listener.get());
+
+ ASSERT_TRUE(first_add);
+ ASSERT_FALSE(second_add);
+ ASSERT_TRUE(first_remove);
+ ASSERT_FALSE(second_remove);
+}
+
+TEST_F(EventListenerMapTest, AddExistingUnfilteredListenerForExtensions) {
+ TestAddExistingUnfilteredListener(
+ base::Bind(&CreateEventListenerForExtension, kExt1Id));
+}
+
+TEST_F(EventListenerMapTest, AddExistingUnfilteredListenerForURLs) {
+ TestAddExistingUnfilteredListener(
+ base::Bind(&CreateEventListenerForURL, GURL(kURL)));
+}
+
+TEST_F(EventListenerMapTest, RemovingRouters) {
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, process_.get(), scoped_ptr<DictionaryValue>()));
+ listeners_->AddListener(EventListener::ForURL(
+ kEvent1Name, GURL(kURL), process_.get(), scoped_ptr<DictionaryValue>()));
+ listeners_->RemoveListenersForProcess(process_.get());
+ ASSERT_FALSE(listeners_->HasListenerForEvent(kEvent1Name));
+}
+
+void EventListenerMapTest::TestHasListenerForEvent(
+ const EventListenerConstructor& constructor) {
+ ASSERT_FALSE(listeners_->HasListenerForEvent(kEvent1Name));
+
+ listeners_->AddListener(
+ constructor.Run(kEvent1Name, process_.get(), new DictionaryValue()));
+
+ ASSERT_FALSE(listeners_->HasListenerForEvent(kEvent2Name));
+ ASSERT_TRUE(listeners_->HasListenerForEvent(kEvent1Name));
+ listeners_->RemoveListenersForProcess(process_.get());
+ ASSERT_FALSE(listeners_->HasListenerForEvent(kEvent1Name));
+}
+
+TEST_F(EventListenerMapTest, HasListenerForEventForExtension) {
+ TestHasListenerForEvent(
+ base::Bind(&CreateEventListenerForExtension, kExt1Id));
+}
+
+TEST_F(EventListenerMapTest, HasListenerForEventForURL) {
+ TestHasListenerForEvent(base::Bind(&CreateEventListenerForURL, GURL(kURL)));
+}
+
+TEST_F(EventListenerMapTest, HasListenerForExtension) {
+ ASSERT_FALSE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name));
+
+ // Non-lazy listener.
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, process_.get(), scoped_ptr<DictionaryValue>()));
+ // Lazy listener.
+ listeners_->AddListener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, scoped_ptr<DictionaryValue>()));
+
+ ASSERT_FALSE(listeners_->HasListenerForExtension(kExt1Id, kEvent2Name));
+ ASSERT_TRUE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name));
+ ASSERT_FALSE(listeners_->HasListenerForExtension(kExt2Id, kEvent1Name));
+ listeners_->RemoveListenersForProcess(process_.get());
+ ASSERT_TRUE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name));
+ listeners_->RemoveListenersForExtension(kExt1Id);
+ ASSERT_FALSE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name));
+}
+
+TEST_F(EventListenerMapTest, AddLazyListenersFromPreferences) {
+ scoped_ptr<DictionaryValue> filter1(CreateHostSuffixFilter("google.com"));
+ scoped_ptr<DictionaryValue> filter2(CreateHostSuffixFilter("yahoo.com"));
+
+ DictionaryValue filtered_listeners;
+ ListValue* filter_list = new ListValue();
+ filtered_listeners.Set(kEvent1Name, filter_list);
+
+ filter_list->Append(filter1.release());
+ filter_list->Append(filter2.release());
+
+ listeners_->LoadFilteredLazyListeners(kExt1Id, filtered_listeners);
+
+ scoped_ptr<Event> event(CreateEvent(kEvent1Name,
+ GURL("http://www.google.com")));
+ std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
+ ASSERT_EQ(1u, targets.size());
+ scoped_ptr<EventListener> listener(EventListener::ForExtension(
+ kEvent1Name, kExt1Id, NULL, CreateHostSuffixFilter("google.com")));
+ ASSERT_TRUE((*targets.begin())->Equals(listener.get()));
+}
+
+TEST_F(EventListenerMapTest, CorruptedExtensionPrefsShouldntCrash) {
+ scoped_ptr<DictionaryValue> filter1(CreateHostSuffixFilter("google.com"));
+
+ DictionaryValue filtered_listeners;
+ // kEvent1Name should be associated with a list, not a dictionary.
+ filtered_listeners.Set(kEvent1Name, filter1.release());
+
+ listeners_->LoadFilteredLazyListeners(kExt1Id, filtered_listeners);
+
+ scoped_ptr<Event> event(CreateEvent(kEvent1Name,
+ GURL("http://www.google.com")));
+ std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
+ ASSERT_EQ(0u, targets.size());
+}
+
+} // namespace
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/event_page_tracker.h b/chromium/extensions/browser/event_page_tracker.h
new file mode 100644
index 00000000000..fd5eb6b8e54
--- /dev/null
+++ b/chromium/extensions/browser/event_page_tracker.h
@@ -0,0 +1,39 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EVENT_PAGE_TRACKER_H_
+#define EXTENSIONS_BROWSER_EVENT_PAGE_TRACKER_H_
+
+#include <string>
+
+#include "base/callback.h"
+
+namespace extensions {
+
+class Extension;
+class ExtensionHost;
+
+// Tracks an extension's event page suspend state.
+class EventPageTracker {
+ public:
+ // Returns true if an extension's event page is active,
+ // or false if it is suspended.
+ virtual bool IsEventPageSuspended(const std::string& extension_id) = 0;
+
+ // Wakes an extension's event page from a suspended state and calls
+ // |callback| after it is reactivated.
+ //
+ // |callback| will be passed true if the extension was reactivated
+ // successfully, or false if an error occurred.
+ //
+ // Returns true if a wake operation was scheduled successfully,
+ // or false if the event page was already awake.
+ // Callback will be run asynchronously if true, and never run if false.
+ virtual bool WakeEventPage(const std::string& extension_id,
+ const base::Callback<void(bool)>& callback) = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EVENT_PAGE_TRACKER_H_
diff --git a/chromium/extensions/browser/event_router.cc b/chromium/extensions/browser/event_router.cc
new file mode 100644
index 00000000000..0f56fe73906
--- /dev/null
+++ b/chromium/extensions/browser/event_router.cc
@@ -0,0 +1,920 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/event_router.h"
+
+#include <stddef.h>
+
+#include <tuple>
+#include <utility>
+
+#include "base/atomic_sequence_num.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/stl_util.h"
+#include "base/values.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/render_process_host.h"
+#include "extensions/browser/api_activity_monitor.h"
+#include "extensions/browser/event_router_factory.h"
+#include "extensions/browser/extension_host.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/lazy_background_task_queue.h"
+#include "extensions/browser/notification_types.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/browser/process_map.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_api.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/extension_urls.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/features/feature_provider.h"
+#include "extensions/common/manifest_handlers/background_info.h"
+#include "extensions/common/manifest_handlers/incognito_info.h"
+#include "extensions/common/permissions/permissions_data.h"
+
+using base::DictionaryValue;
+using base::ListValue;
+using content::BrowserContext;
+using content::BrowserThread;
+
+namespace extensions {
+
+namespace {
+
+void DoNothing(ExtensionHost* host) {}
+
+// A dictionary of event names to lists of filters that this extension has
+// registered from its lazy background page.
+const char kFilteredEvents[] = "filtered_events";
+
+// Sends a notification about an event to the API activity monitor and the
+// ExtensionHost for |extension_id| on the UI thread. Can be called from any
+// thread.
+void NotifyEventDispatched(void* browser_context_id,
+ const std::string& extension_id,
+ const std::string& event_name,
+ scoped_ptr<ListValue> args) {
+ // The ApiActivityMonitor can only be accessed from the UI thread.
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&NotifyEventDispatched, browser_context_id, extension_id,
+ event_name, base::Passed(&args)));
+ return;
+ }
+
+ // Notify the ApiActivityMonitor about the event dispatch.
+ BrowserContext* context = static_cast<BrowserContext*>(browser_context_id);
+ if (!ExtensionsBrowserClient::Get()->IsValidContext(context))
+ return;
+ ApiActivityMonitor* monitor =
+ ExtensionsBrowserClient::Get()->GetApiActivityMonitor(context);
+ if (monitor)
+ monitor->OnApiEventDispatched(extension_id, event_name, std::move(args));
+}
+
+// A global identifier used to distinguish extension events.
+base::StaticAtomicSequenceNumber g_extension_event_id;
+
+} // namespace
+
+const char EventRouter::kRegisteredEvents[] = "events";
+
+struct EventRouter::ListenerProcess {
+ content::RenderProcessHost* process;
+ std::string extension_id;
+
+ ListenerProcess(content::RenderProcessHost* process,
+ const std::string& extension_id)
+ : process(process), extension_id(extension_id) {}
+
+ bool operator<(const ListenerProcess& that) const {
+ return std::tie(process, extension_id) <
+ std::tie(that.process, that.extension_id);
+ }
+};
+
+// static
+void EventRouter::DispatchExtensionMessage(IPC::Sender* ipc_sender,
+ void* browser_context_id,
+ const std::string& extension_id,
+ int event_id,
+ const std::string& event_name,
+ ListValue* event_args,
+ UserGestureState user_gesture,
+ const EventFilteringInfo& info) {
+ NotifyEventDispatched(browser_context_id, extension_id, event_name,
+ make_scoped_ptr(event_args->DeepCopy()));
+
+ // TODO(chirantan): Make event dispatch a separate IPC so that it doesn't
+ // piggyback off MessageInvoke, which is used for other things.
+ ListValue args;
+ args.Set(0, new base::StringValue(event_name));
+ args.Set(1, event_args);
+ args.Set(2, info.AsValue().release());
+ args.Set(3, new base::FundamentalValue(event_id));
+ ipc_sender->Send(new ExtensionMsg_MessageInvoke(
+ MSG_ROUTING_CONTROL,
+ extension_id,
+ kEventBindings,
+ "dispatchEvent",
+ args,
+ user_gesture == USER_GESTURE_ENABLED));
+
+ // DispatchExtensionMessage does _not_ take ownership of event_args, so we
+ // must ensure that the destruction of args does not attempt to free it.
+ scoped_ptr<base::Value> removed_event_args;
+ args.Remove(1, &removed_event_args);
+ ignore_result(removed_event_args.release());
+}
+
+// static
+EventRouter* EventRouter::Get(content::BrowserContext* browser_context) {
+ return EventRouterFactory::GetForBrowserContext(browser_context);
+}
+
+// static
+std::string EventRouter::GetBaseEventName(const std::string& full_event_name) {
+ size_t slash_sep = full_event_name.find('/');
+ return full_event_name.substr(0, slash_sep);
+}
+
+// static
+void EventRouter::DispatchEventToSender(IPC::Sender* ipc_sender,
+ void* browser_context_id,
+ const std::string& extension_id,
+ events::HistogramValue histogram_value,
+ const std::string& event_name,
+ scoped_ptr<ListValue> event_args,
+ UserGestureState user_gesture,
+ const EventFilteringInfo& info) {
+ int event_id = g_extension_event_id.GetNext();
+
+ if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ DoDispatchEventToSenderBookkeepingOnUI(browser_context_id, extension_id,
+ event_id, histogram_value,
+ event_name);
+ } else {
+ // This is called from WebRequest API.
+ // TODO(lazyboy): Skip this entirely: http://crbug.com/488747.
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&EventRouter::DoDispatchEventToSenderBookkeepingOnUI,
+ browser_context_id, extension_id, event_id, histogram_value,
+ event_name));
+ }
+
+ DispatchExtensionMessage(ipc_sender, browser_context_id, extension_id,
+ event_id, event_name, event_args.get(), user_gesture,
+ info);
+}
+
+EventRouter::EventRouter(BrowserContext* browser_context,
+ ExtensionPrefs* extension_prefs)
+ : browser_context_(browser_context),
+ extension_prefs_(extension_prefs),
+ extension_registry_observer_(this),
+ listeners_(this) {
+ registrar_.Add(this,
+ extensions::NOTIFICATION_EXTENSION_ENABLED,
+ content::Source<BrowserContext>(browser_context_));
+ extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
+}
+
+EventRouter::~EventRouter() {
+ for (auto process : observed_process_set_)
+ process->RemoveObserver(this);
+}
+
+void EventRouter::AddEventListener(const std::string& event_name,
+ content::RenderProcessHost* process,
+ const std::string& extension_id) {
+ listeners_.AddListener(EventListener::ForExtension(
+ event_name, extension_id, process, scoped_ptr<DictionaryValue>()));
+}
+
+void EventRouter::RemoveEventListener(const std::string& event_name,
+ content::RenderProcessHost* process,
+ const std::string& extension_id) {
+ scoped_ptr<EventListener> listener = EventListener::ForExtension(
+ event_name, extension_id, process, scoped_ptr<DictionaryValue>());
+ listeners_.RemoveListener(listener.get());
+}
+
+void EventRouter::AddEventListenerForURL(const std::string& event_name,
+ content::RenderProcessHost* process,
+ const GURL& listener_url) {
+ listeners_.AddListener(EventListener::ForURL(
+ event_name, listener_url, process, scoped_ptr<DictionaryValue>()));
+}
+
+void EventRouter::RemoveEventListenerForURL(const std::string& event_name,
+ content::RenderProcessHost* process,
+ const GURL& listener_url) {
+ scoped_ptr<EventListener> listener = EventListener::ForURL(
+ event_name, listener_url, process, scoped_ptr<DictionaryValue>());
+ listeners_.RemoveListener(listener.get());
+}
+
+void EventRouter::RegisterObserver(Observer* observer,
+ const std::string& event_name) {
+ // Observing sub-event names like "foo.onBar/123" is not allowed.
+ DCHECK(event_name.find('/') == std::string::npos);
+ observers_[event_name] = observer;
+}
+
+void EventRouter::UnregisterObserver(Observer* observer) {
+ std::vector<ObserverMap::iterator> iters_to_remove;
+ for (ObserverMap::iterator iter = observers_.begin();
+ iter != observers_.end(); ++iter) {
+ if (iter->second == observer)
+ iters_to_remove.push_back(iter);
+ }
+ for (size_t i = 0; i < iters_to_remove.size(); ++i)
+ observers_.erase(iters_to_remove[i]);
+}
+
+void EventRouter::OnListenerAdded(const EventListener* listener) {
+ const EventListenerInfo details(listener->event_name(),
+ listener->extension_id(),
+ listener->listener_url(),
+ listener->GetBrowserContext());
+ std::string base_event_name = GetBaseEventName(listener->event_name());
+ ObserverMap::iterator observer = observers_.find(base_event_name);
+ if (observer != observers_.end())
+ observer->second->OnListenerAdded(details);
+
+ content::RenderProcessHost* process = listener->process();
+ if (process) {
+ bool inserted = observed_process_set_.insert(process).second;
+ if (inserted)
+ process->AddObserver(this);
+ }
+}
+
+void EventRouter::OnListenerRemoved(const EventListener* listener) {
+ const EventListenerInfo details(listener->event_name(),
+ listener->extension_id(),
+ listener->listener_url(),
+ listener->GetBrowserContext());
+ std::string base_event_name = GetBaseEventName(listener->event_name());
+ ObserverMap::iterator observer = observers_.find(base_event_name);
+ if (observer != observers_.end())
+ observer->second->OnListenerRemoved(details);
+}
+
+void EventRouter::RenderProcessExited(content::RenderProcessHost* host,
+ base::TerminationStatus status,
+ int exit_code) {
+ listeners_.RemoveListenersForProcess(host);
+ observed_process_set_.erase(host);
+ host->RemoveObserver(this);
+}
+
+void EventRouter::RenderProcessHostDestroyed(content::RenderProcessHost* host) {
+ listeners_.RemoveListenersForProcess(host);
+ observed_process_set_.erase(host);
+ host->RemoveObserver(this);
+}
+
+void EventRouter::AddLazyEventListener(const std::string& event_name,
+ const std::string& extension_id) {
+ bool is_new = listeners_.AddListener(EventListener::ForExtension(
+ event_name, extension_id, NULL, scoped_ptr<DictionaryValue>()));
+
+ if (is_new) {
+ std::set<std::string> events = GetRegisteredEvents(extension_id);
+ bool prefs_is_new = events.insert(event_name).second;
+ if (prefs_is_new)
+ SetRegisteredEvents(extension_id, events);
+ }
+}
+
+void EventRouter::RemoveLazyEventListener(const std::string& event_name,
+ const std::string& extension_id) {
+ scoped_ptr<EventListener> listener = EventListener::ForExtension(
+ event_name, extension_id, NULL, scoped_ptr<DictionaryValue>());
+ bool did_exist = listeners_.RemoveListener(listener.get());
+
+ if (did_exist) {
+ std::set<std::string> events = GetRegisteredEvents(extension_id);
+ bool prefs_did_exist = events.erase(event_name) > 0;
+ DCHECK(prefs_did_exist);
+ SetRegisteredEvents(extension_id, events);
+ }
+}
+
+void EventRouter::AddFilteredEventListener(const std::string& event_name,
+ content::RenderProcessHost* process,
+ const std::string& extension_id,
+ const base::DictionaryValue& filter,
+ bool add_lazy_listener) {
+ listeners_.AddListener(EventListener::ForExtension(
+ event_name,
+ extension_id,
+ process,
+ scoped_ptr<DictionaryValue>(filter.DeepCopy())));
+
+ if (add_lazy_listener) {
+ bool added = listeners_.AddListener(EventListener::ForExtension(
+ event_name,
+ extension_id,
+ NULL,
+ scoped_ptr<DictionaryValue>(filter.DeepCopy())));
+
+ if (added)
+ AddFilterToEvent(event_name, extension_id, &filter);
+ }
+}
+
+void EventRouter::RemoveFilteredEventListener(
+ const std::string& event_name,
+ content::RenderProcessHost* process,
+ const std::string& extension_id,
+ const base::DictionaryValue& filter,
+ bool remove_lazy_listener) {
+ scoped_ptr<EventListener> listener = EventListener::ForExtension(
+ event_name,
+ extension_id,
+ process,
+ scoped_ptr<DictionaryValue>(filter.DeepCopy()));
+
+ listeners_.RemoveListener(listener.get());
+
+ if (remove_lazy_listener) {
+ listener->MakeLazy();
+ bool removed = listeners_.RemoveListener(listener.get());
+
+ if (removed)
+ RemoveFilterFromEvent(event_name, extension_id, &filter);
+ }
+}
+
+bool EventRouter::HasEventListener(const std::string& event_name) {
+ return listeners_.HasListenerForEvent(event_name);
+}
+
+bool EventRouter::ExtensionHasEventListener(const std::string& extension_id,
+ const std::string& event_name) {
+ return listeners_.HasListenerForExtension(extension_id, event_name);
+}
+
+bool EventRouter::HasEventListenerImpl(const ListenerMap& listener_map,
+ const std::string& extension_id,
+ const std::string& event_name) {
+ ListenerMap::const_iterator it = listener_map.find(event_name);
+ if (it == listener_map.end())
+ return false;
+
+ const std::set<ListenerProcess>& listeners = it->second;
+ if (extension_id.empty())
+ return !listeners.empty();
+
+ for (std::set<ListenerProcess>::const_iterator listener = listeners.begin();
+ listener != listeners.end(); ++listener) {
+ if (listener->extension_id == extension_id)
+ return true;
+ }
+ return false;
+}
+
+std::set<std::string> EventRouter::GetRegisteredEvents(
+ const std::string& extension_id) {
+ std::set<std::string> events;
+ const ListValue* events_value = NULL;
+
+ if (!extension_prefs_ ||
+ !extension_prefs_->ReadPrefAsList(
+ extension_id, kRegisteredEvents, &events_value)) {
+ return events;
+ }
+
+ for (size_t i = 0; i < events_value->GetSize(); ++i) {
+ std::string event;
+ if (events_value->GetString(i, &event))
+ events.insert(event);
+ }
+ return events;
+}
+
+void EventRouter::SetRegisteredEvents(const std::string& extension_id,
+ const std::set<std::string>& events) {
+ ListValue* events_value = new ListValue;
+ for (std::set<std::string>::const_iterator iter = events.begin();
+ iter != events.end(); ++iter) {
+ events_value->Append(new base::StringValue(*iter));
+ }
+ extension_prefs_->UpdateExtensionPref(
+ extension_id, kRegisteredEvents, events_value);
+}
+
+void EventRouter::AddFilterToEvent(const std::string& event_name,
+ const std::string& extension_id,
+ const DictionaryValue* filter) {
+ ExtensionPrefs::ScopedDictionaryUpdate update(
+ extension_prefs_, extension_id, kFilteredEvents);
+ DictionaryValue* filtered_events = update.Get();
+ if (!filtered_events)
+ filtered_events = update.Create();
+
+ ListValue* filter_list = NULL;
+ if (!filtered_events->GetList(event_name, &filter_list)) {
+ filter_list = new ListValue;
+ filtered_events->SetWithoutPathExpansion(event_name, filter_list);
+ }
+
+ filter_list->Append(filter->DeepCopy());
+}
+
+void EventRouter::RemoveFilterFromEvent(const std::string& event_name,
+ const std::string& extension_id,
+ const DictionaryValue* filter) {
+ ExtensionPrefs::ScopedDictionaryUpdate update(
+ extension_prefs_, extension_id, kFilteredEvents);
+ DictionaryValue* filtered_events = update.Get();
+ ListValue* filter_list = NULL;
+ if (!filtered_events ||
+ !filtered_events->GetListWithoutPathExpansion(event_name, &filter_list)) {
+ return;
+ }
+
+ for (size_t i = 0; i < filter_list->GetSize(); i++) {
+ DictionaryValue* filter = NULL;
+ CHECK(filter_list->GetDictionary(i, &filter));
+ if (filter->Equals(filter)) {
+ filter_list->Remove(i, NULL);
+ break;
+ }
+ }
+}
+
+const DictionaryValue* EventRouter::GetFilteredEvents(
+ const std::string& extension_id) {
+ const DictionaryValue* events = NULL;
+ extension_prefs_->ReadPrefAsDictionary(
+ extension_id, kFilteredEvents, &events);
+ return events;
+}
+
+void EventRouter::BroadcastEvent(scoped_ptr<Event> event) {
+ DispatchEventImpl(std::string(), linked_ptr<Event>(event.release()));
+}
+
+void EventRouter::DispatchEventToExtension(const std::string& extension_id,
+ scoped_ptr<Event> event) {
+ DCHECK(!extension_id.empty());
+ DispatchEventImpl(extension_id, linked_ptr<Event>(event.release()));
+}
+
+void EventRouter::DispatchEventWithLazyListener(const std::string& extension_id,
+ scoped_ptr<Event> event) {
+ DCHECK(!extension_id.empty());
+ std::string event_name = event->event_name;
+ bool has_listener = ExtensionHasEventListener(extension_id, event_name);
+ if (!has_listener)
+ AddLazyEventListener(event_name, extension_id);
+ DispatchEventToExtension(extension_id, std::move(event));
+ if (!has_listener)
+ RemoveLazyEventListener(event_name, extension_id);
+}
+
+void EventRouter::DispatchEventImpl(const std::string& restrict_to_extension_id,
+ const linked_ptr<Event>& event) {
+ // We don't expect to get events from a completely different browser context.
+ DCHECK(!event->restrict_to_browser_context ||
+ ExtensionsBrowserClient::Get()->IsSameContext(
+ browser_context_, event->restrict_to_browser_context));
+
+ std::set<const EventListener*> listeners(
+ listeners_.GetEventListeners(*event));
+
+ std::set<EventDispatchIdentifier> already_dispatched;
+
+ // We dispatch events for lazy background pages first because attempting to do
+ // so will cause those that are being suspended to cancel that suspension.
+ // As canceling a suspension entails sending an event to the affected
+ // background page, and as that event needs to be delivered before we dispatch
+ // the event we are dispatching here, we dispatch to the lazy listeners here
+ // first.
+ for (const EventListener* listener : listeners) {
+ if (restrict_to_extension_id.empty() ||
+ restrict_to_extension_id == listener->extension_id()) {
+ if (listener->IsLazy()) {
+ DispatchLazyEvent(listener->extension_id(), event, &already_dispatched,
+ listener->filter());
+ }
+ }
+ }
+
+ for (const EventListener* listener : listeners) {
+ if (restrict_to_extension_id.empty() ||
+ restrict_to_extension_id == listener->extension_id()) {
+ if (listener->process()) {
+ EventDispatchIdentifier dispatch_id(listener->GetBrowserContext(),
+ listener->extension_id());
+ if (!ContainsKey(already_dispatched, dispatch_id)) {
+ DispatchEventToProcess(listener->extension_id(),
+ listener->listener_url(), listener->process(),
+ event, listener->filter(),
+ false /* did_enqueue */);
+ }
+ }
+ }
+ }
+}
+
+void EventRouter::DispatchLazyEvent(
+ const std::string& extension_id,
+ const linked_ptr<Event>& event,
+ std::set<EventDispatchIdentifier>* already_dispatched,
+ const base::DictionaryValue* listener_filter) {
+ // Check both the original and the incognito browser context to see if we
+ // should load a lazy bg page to handle the event. The latter case
+ // occurs in the case of split-mode extensions.
+ const Extension* extension =
+ ExtensionRegistry::Get(browser_context_)->enabled_extensions().GetByID(
+ extension_id);
+ if (!extension)
+ return;
+
+ if (MaybeLoadLazyBackgroundPageToDispatchEvent(browser_context_, extension,
+ event, listener_filter)) {
+ already_dispatched->insert(std::make_pair(browser_context_, extension_id));
+ }
+
+ ExtensionsBrowserClient* browser_client = ExtensionsBrowserClient::Get();
+ if (browser_client->HasOffTheRecordContext(browser_context_) &&
+ IncognitoInfo::IsSplitMode(extension)) {
+ BrowserContext* incognito_context =
+ browser_client->GetOffTheRecordContext(browser_context_);
+ if (MaybeLoadLazyBackgroundPageToDispatchEvent(incognito_context, extension,
+ event, listener_filter)) {
+ already_dispatched->insert(
+ std::make_pair(incognito_context, extension_id));
+ }
+ }
+}
+
+void EventRouter::DispatchEventToProcess(
+ const std::string& extension_id,
+ const GURL& listener_url,
+ content::RenderProcessHost* process,
+ const linked_ptr<Event>& event,
+ const base::DictionaryValue* listener_filter,
+ bool did_enqueue) {
+ BrowserContext* listener_context = process->GetBrowserContext();
+ ProcessMap* process_map = ProcessMap::Get(listener_context);
+
+ // NOTE: |extension| being NULL does not necessarily imply that this event
+ // shouldn't be dispatched. Events can be dispatched to WebUI and webviews as
+ // well. It all depends on what GetMostLikelyContextType returns.
+ const Extension* extension =
+ ExtensionRegistry::Get(browser_context_)->enabled_extensions().GetByID(
+ extension_id);
+
+ if (!extension && !extension_id.empty()) {
+ // Trying to dispatch an event to an extension that doesn't exist. The
+ // extension could have been removed, but we do not unregister it until the
+ // extension process is unloaded.
+ return;
+ }
+
+ if (extension) {
+ // Extension-specific checks.
+ // Firstly, if the event is for a URL, the Extension must have permission
+ // to access that URL.
+ if (!event->event_url.is_empty() &&
+ event->event_url.host() != extension->id() && // event for self is ok
+ !extension->permissions_data()
+ ->active_permissions()
+ .HasEffectiveAccessToURL(event->event_url)) {
+ return;
+ }
+ // Secondly, if the event is for incognito mode, the Extension must be
+ // enabled in incognito mode.
+ if (!CanDispatchEventToBrowserContext(listener_context, extension, event)) {
+ return;
+ }
+ }
+
+ Feature::Context target_context =
+ process_map->GetMostLikelyContextType(extension, process->GetID());
+
+ // We shouldn't be dispatching an event to a webpage, since all such events
+ // (e.g. messaging) don't go through EventRouter.
+ DCHECK_NE(Feature::WEB_PAGE_CONTEXT, target_context)
+ << "Trying to dispatch event " << event->event_name << " to a webpage,"
+ << " but this shouldn't be possible";
+
+ Feature::Availability availability =
+ ExtensionAPI::GetSharedInstance()->IsAvailable(
+ event->event_name, extension, target_context, listener_url);
+ if (!availability.is_available()) {
+ // It shouldn't be possible to reach here, because access is checked on
+ // registration. However, for paranoia, check on dispatch as well.
+ NOTREACHED() << "Trying to dispatch event " << event->event_name
+ << " which the target does not have access to: "
+ << availability.message();
+ return;
+ }
+
+ if (!event->will_dispatch_callback.is_null() &&
+ !event->will_dispatch_callback.Run(listener_context, extension,
+ event.get(), listener_filter)) {
+ return;
+ }
+
+ int event_id = g_extension_event_id.GetNext();
+ DispatchExtensionMessage(process, listener_context, extension_id, event_id,
+ event->event_name, event->event_args.get(),
+ event->user_gesture, event->filter_info);
+
+ if (extension) {
+ ReportEvent(event->histogram_value, extension, did_enqueue);
+ IncrementInFlightEvents(listener_context, extension, event_id,
+ event->event_name);
+ }
+}
+
+bool EventRouter::CanDispatchEventToBrowserContext(
+ BrowserContext* context,
+ const Extension* extension,
+ const linked_ptr<Event>& event) {
+ // Is this event from a different browser context than the renderer (ie, an
+ // incognito tab event sent to a normal process, or vice versa).
+ bool cross_incognito = event->restrict_to_browser_context &&
+ context != event->restrict_to_browser_context;
+ if (!cross_incognito)
+ return true;
+ return ExtensionsBrowserClient::Get()->CanExtensionCrossIncognito(
+ extension, context);
+}
+
+bool EventRouter::MaybeLoadLazyBackgroundPageToDispatchEvent(
+ BrowserContext* context,
+ const Extension* extension,
+ const linked_ptr<Event>& event,
+ const base::DictionaryValue* listener_filter) {
+ if (!CanDispatchEventToBrowserContext(context, extension, event))
+ return false;
+
+ LazyBackgroundTaskQueue* queue = LazyBackgroundTaskQueue::Get(context);
+ if (queue->ShouldEnqueueTask(context, extension)) {
+ linked_ptr<Event> dispatched_event(event);
+
+ // If there's a dispatch callback, call it now (rather than dispatch time)
+ // to avoid lifetime issues. Use a separate copy of the event args, so they
+ // last until the event is dispatched.
+ if (!event->will_dispatch_callback.is_null()) {
+ dispatched_event.reset(event->DeepCopy());
+ if (!dispatched_event->will_dispatch_callback.Run(
+ context, extension, dispatched_event.get(), listener_filter)) {
+ // The event has been canceled.
+ return true;
+ }
+ // Ensure we don't call it again at dispatch time.
+ dispatched_event->will_dispatch_callback.Reset();
+ }
+
+ queue->AddPendingTask(context, extension->id(),
+ base::Bind(&EventRouter::DispatchPendingEvent,
+ base::Unretained(this), dispatched_event));
+ return true;
+ }
+
+ return false;
+}
+
+// static
+void EventRouter::DoDispatchEventToSenderBookkeepingOnUI(
+ void* browser_context_id,
+ const std::string& extension_id,
+ int event_id,
+ events::HistogramValue histogram_value,
+ const std::string& event_name) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ BrowserContext* browser_context =
+ reinterpret_cast<BrowserContext*>(browser_context_id);
+ if (!ExtensionsBrowserClient::Get()->IsValidContext(browser_context))
+ return;
+ const Extension* extension =
+ ExtensionRegistry::Get(browser_context)->enabled_extensions().GetByID(
+ extension_id);
+ if (!extension)
+ return;
+ EventRouter* event_router = EventRouter::Get(browser_context);
+ event_router->IncrementInFlightEvents(browser_context, extension, event_id,
+ event_name);
+ event_router->ReportEvent(histogram_value, extension,
+ false /* did_enqueue */);
+}
+
+void EventRouter::IncrementInFlightEvents(BrowserContext* context,
+ const Extension* extension,
+ int event_id,
+ const std::string& event_name) {
+ // TODO(chirantan): Turn this on once crbug.com/464513 is fixed.
+ // DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ // Only increment in-flight events if the lazy background page is active,
+ // because that's the only time we'll get an ACK.
+ if (BackgroundInfo::HasLazyBackgroundPage(extension)) {
+ ProcessManager* pm = ProcessManager::Get(context);
+ ExtensionHost* host = pm->GetBackgroundHostForExtension(extension->id());
+ if (host) {
+ pm->IncrementLazyKeepaliveCount(extension);
+ host->OnBackgroundEventDispatched(event_name, event_id);
+ }
+ }
+}
+
+void EventRouter::OnEventAck(BrowserContext* context,
+ const std::string& extension_id) {
+ ProcessManager* pm = ProcessManager::Get(context);
+ ExtensionHost* host = pm->GetBackgroundHostForExtension(extension_id);
+ // The event ACK is routed to the background host, so this should never be
+ // NULL.
+ CHECK(host);
+ // TODO(mpcomplete): We should never get this message unless
+ // HasLazyBackgroundPage is true. Find out why we're getting it anyway.
+ if (host->extension() &&
+ BackgroundInfo::HasLazyBackgroundPage(host->extension()))
+ pm->DecrementLazyKeepaliveCount(host->extension());
+}
+
+void EventRouter::ReportEvent(events::HistogramValue histogram_value,
+ const Extension* extension,
+ bool did_enqueue) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ // Record every event fired.
+ UMA_HISTOGRAM_ENUMERATION("Extensions.Events.Dispatch", histogram_value,
+ events::ENUM_BOUNDARY);
+
+ bool is_component = Manifest::IsComponentLocation(extension->location());
+
+ // Record events for component extensions. These should be kept to a minimum,
+ // especially if they wake its event page. Component extensions should use
+ // declarative APIs as much as possible.
+ if (is_component) {
+ UMA_HISTOGRAM_ENUMERATION("Extensions.Events.DispatchToComponent",
+ histogram_value, events::ENUM_BOUNDARY);
+ }
+
+ // Record events for background pages, if any. The most important statistic
+ // is DispatchWithSuspendedEventPage. Events reported there woke an event
+ // page. Implementing either filtered or declarative versions of these events
+ // should be prioritised.
+ //
+ // Note: all we know is that the extension *has* a persistent or event page,
+ // not that the event is being dispatched *to* such a page. However, this is
+ // academic, since extensions with any background page have that background
+ // page running (or in the case of suspended event pages, must be started)
+ // regardless of where the event is being dispatched. Events are dispatched
+ // to a *process* not a *frame*.
+ if (BackgroundInfo::HasPersistentBackgroundPage(extension)) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "Extensions.Events.DispatchWithPersistentBackgroundPage",
+ histogram_value, events::ENUM_BOUNDARY);
+ } else if (BackgroundInfo::HasLazyBackgroundPage(extension)) {
+ if (did_enqueue) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "Extensions.Events.DispatchWithSuspendedEventPage", histogram_value,
+ events::ENUM_BOUNDARY);
+ if (is_component) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "Extensions.Events.DispatchToComponentWithSuspendedEventPage",
+ histogram_value, events::ENUM_BOUNDARY);
+ }
+ } else {
+ UMA_HISTOGRAM_ENUMERATION(
+ "Extensions.Events.DispatchWithRunningEventPage", histogram_value,
+ events::ENUM_BOUNDARY);
+ }
+ }
+}
+
+void EventRouter::DispatchPendingEvent(const linked_ptr<Event>& event,
+ ExtensionHost* host) {
+ if (!host)
+ return;
+
+ if (listeners_.HasProcessListener(host->render_process_host(),
+ host->extension()->id())) {
+ DispatchEventToProcess(host->extension()->id(), host->GetURL(),
+ host->render_process_host(), event, nullptr,
+ true /* did_enqueue */);
+ }
+}
+
+void EventRouter::Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ switch (type) {
+ case extensions::NOTIFICATION_EXTENSION_ENABLED: {
+ // If the extension has a lazy background page, make sure it gets loaded
+ // to register the events the extension is interested in.
+ const Extension* extension =
+ content::Details<const Extension>(details).ptr();
+ if (BackgroundInfo::HasLazyBackgroundPage(extension)) {
+ LazyBackgroundTaskQueue* queue =
+ LazyBackgroundTaskQueue::Get(browser_context_);
+ queue->AddPendingTask(browser_context_, extension->id(),
+ base::Bind(&DoNothing));
+ }
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+}
+
+void EventRouter::OnExtensionLoaded(content::BrowserContext* browser_context,
+ const Extension* extension) {
+ // Add all registered lazy listeners to our cache.
+ std::set<std::string> registered_events =
+ GetRegisteredEvents(extension->id());
+ listeners_.LoadUnfilteredLazyListeners(extension->id(), registered_events);
+ const DictionaryValue* filtered_events = GetFilteredEvents(extension->id());
+ if (filtered_events)
+ listeners_.LoadFilteredLazyListeners(extension->id(), *filtered_events);
+}
+
+void EventRouter::OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ // Remove all registered listeners from our cache.
+ listeners_.RemoveListenersForExtension(extension->id());
+}
+
+Event::Event(events::HistogramValue histogram_value,
+ const std::string& event_name,
+ scoped_ptr<base::ListValue> event_args)
+ : Event(histogram_value, event_name, std::move(event_args), nullptr) {}
+
+Event::Event(events::HistogramValue histogram_value,
+ const std::string& event_name,
+ scoped_ptr<base::ListValue> event_args,
+ BrowserContext* restrict_to_browser_context)
+ : Event(histogram_value,
+ event_name,
+ std::move(event_args),
+ restrict_to_browser_context,
+ GURL(),
+ EventRouter::USER_GESTURE_UNKNOWN,
+ EventFilteringInfo()) {}
+
+Event::Event(events::HistogramValue histogram_value,
+ const std::string& event_name,
+ scoped_ptr<ListValue> event_args_tmp,
+ BrowserContext* restrict_to_browser_context,
+ const GURL& event_url,
+ EventRouter::UserGestureState user_gesture,
+ const EventFilteringInfo& filter_info)
+ : histogram_value(histogram_value),
+ event_name(event_name),
+ event_args(std::move(event_args_tmp)),
+ restrict_to_browser_context(restrict_to_browser_context),
+ event_url(event_url),
+ user_gesture(user_gesture),
+ filter_info(filter_info) {
+ DCHECK(event_args);
+ DCHECK_NE(events::UNKNOWN, histogram_value)
+ << "events::UNKNOWN cannot be used as a histogram value.\n"
+ << "If this is a test, use events::FOR_TEST.\n"
+ << "If this is production code, it is important that you use a realistic "
+ << "value so that we can accurately track event usage. "
+ << "See extension_event_histogram_value.h for inspiration.";
+}
+
+Event::~Event() {}
+
+Event* Event::DeepCopy() {
+ Event* copy = new Event(histogram_value, event_name,
+ scoped_ptr<base::ListValue>(event_args->DeepCopy()),
+ restrict_to_browser_context, event_url, user_gesture,
+ filter_info);
+ copy->will_dispatch_callback = will_dispatch_callback;
+ return copy;
+}
+
+EventListenerInfo::EventListenerInfo(const std::string& event_name,
+ const std::string& extension_id,
+ const GURL& listener_url,
+ content::BrowserContext* browser_context)
+ : event_name(event_name),
+ extension_id(extension_id),
+ listener_url(listener_url),
+ browser_context(browser_context) {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/event_router.h b/chromium/extensions/browser/event_router.h
new file mode 100644
index 00000000000..c90a335b6b1
--- /dev/null
+++ b/chromium/extensions/browser/event_router.h
@@ -0,0 +1,438 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EVENT_ROUTER_H_
+#define EXTENSIONS_BROWSER_EVENT_ROUTER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/containers/hash_tables.h"
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/scoped_observer.h"
+#include "base/values.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/render_process_host_observer.h"
+#include "extensions/browser/event_listener_map.h"
+#include "extensions/browser/extension_event_histogram_value.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/common/event_filtering_info.h"
+#include "ipc/ipc_sender.h"
+#include "url/gurl.h"
+
+class GURL;
+class PrefService;
+
+namespace content {
+class BrowserContext;
+class RenderProcessHost;
+}
+
+namespace extensions {
+class ActivityLog;
+class Extension;
+class ExtensionHost;
+class ExtensionPrefs;
+class ExtensionRegistry;
+
+struct Event;
+struct EventDispatchInfo;
+struct EventListenerInfo;
+
+class EventRouter : public KeyedService,
+ public content::NotificationObserver,
+ public ExtensionRegistryObserver,
+ public EventListenerMap::Delegate,
+ public content::RenderProcessHostObserver {
+ public:
+ // These constants convey the state of our knowledge of whether we're in
+ // a user-caused gesture as part of DispatchEvent.
+ enum UserGestureState {
+ USER_GESTURE_UNKNOWN = 0,
+ USER_GESTURE_ENABLED = 1,
+ USER_GESTURE_NOT_ENABLED = 2,
+ };
+
+ // The pref key for the list of event names for which an extension has
+ // registered from its lazy background page.
+ static const char kRegisteredEvents[];
+
+ // Observers register interest in events with a particular name and are
+ // notified when a listener is added or removed. Observers are matched by
+ // the base name of the event (e.g. adding an event listener for event name
+ // "foo.onBar/123" will trigger observers registered for "foo.onBar").
+ class Observer {
+ public:
+ // Called when a listener is added.
+ virtual void OnListenerAdded(const EventListenerInfo& details) {}
+ // Called when a listener is removed.
+ virtual void OnListenerRemoved(const EventListenerInfo& details) {}
+ };
+
+ // Gets the EventRouter for |browser_context|.
+ static EventRouter* Get(content::BrowserContext* browser_context);
+
+ // Converts event names like "foo.onBar/123" into "foo.onBar". Event names
+ // without a "/" are returned unchanged.
+ static std::string GetBaseEventName(const std::string& full_event_name);
+
+ // Sends an event via ipc_sender to the given extension. Can be called on any
+ // thread.
+ //
+ // It is very rare to call this function directly. Instead use the instance
+ // methods BroadcastEvent or DispatchEventToExtension.
+ static void DispatchEventToSender(IPC::Sender* ipc_sender,
+ void* browser_context_id,
+ const std::string& extension_id,
+ events::HistogramValue histogram_value,
+ const std::string& event_name,
+ scoped_ptr<base::ListValue> event_args,
+ UserGestureState user_gesture,
+ const EventFilteringInfo& info);
+
+ // An EventRouter is shared between |browser_context| and its associated
+ // incognito context. |extension_prefs| may be NULL in tests.
+ EventRouter(content::BrowserContext* browser_context,
+ ExtensionPrefs* extension_prefs);
+ ~EventRouter() override;
+
+ // Add or remove an extension as an event listener for |event_name|.
+ //
+ // Note that multiple extensions can share a process due to process
+ // collapsing. Also, a single extension can have 2 processes if it is a split
+ // mode extension.
+ void AddEventListener(const std::string& event_name,
+ content::RenderProcessHost* process,
+ const std::string& extension_id);
+ void RemoveEventListener(const std::string& event_name,
+ content::RenderProcessHost* process,
+ const std::string& extension_id);
+
+ // Add or remove a URL as an event listener for |event_name|.
+ void AddEventListenerForURL(const std::string& event_name,
+ content::RenderProcessHost* process,
+ const GURL& listener_url);
+ void RemoveEventListenerForURL(const std::string& event_name,
+ content::RenderProcessHost* process,
+ const GURL& listener_url);
+
+ EventListenerMap& listeners() { return listeners_; }
+
+ // Registers an observer to be notified when an event listener for
+ // |event_name| is added or removed. There can currently be only one observer
+ // for each distinct |event_name|.
+ void RegisterObserver(Observer* observer,
+ const std::string& event_name);
+
+ // Unregisters an observer from all events.
+ void UnregisterObserver(Observer* observer);
+
+ // Add or remove the extension as having a lazy background page that listens
+ // to the event. The difference from the above methods is that these will be
+ // remembered even after the process goes away. We use this list to decide
+ // which extension pages to load when dispatching an event.
+ void AddLazyEventListener(const std::string& event_name,
+ const std::string& extension_id);
+ void RemoveLazyEventListener(const std::string& event_name,
+ const std::string& extension_id);
+
+ // If |add_lazy_listener| is true also add the lazy version of this listener.
+ void AddFilteredEventListener(const std::string& event_name,
+ content::RenderProcessHost* process,
+ const std::string& extension_id,
+ const base::DictionaryValue& filter,
+ bool add_lazy_listener);
+
+ // If |remove_lazy_listener| is true also remove the lazy version of this
+ // listener.
+ void RemoveFilteredEventListener(const std::string& event_name,
+ content::RenderProcessHost* process,
+ const std::string& extension_id,
+ const base::DictionaryValue& filter,
+ bool remove_lazy_listener);
+
+ // Returns true if there is at least one listener for the given event.
+ bool HasEventListener(const std::string& event_name);
+
+ // Returns true if the extension is listening to the given event.
+ bool ExtensionHasEventListener(const std::string& extension_id,
+ const std::string& event_name);
+
+ // Return or set the list of events for which the given extension has
+ // registered.
+ std::set<std::string> GetRegisteredEvents(const std::string& extension_id);
+ void SetRegisteredEvents(const std::string& extension_id,
+ const std::set<std::string>& events);
+
+ // Broadcasts an event to every listener registered for that event.
+ virtual void BroadcastEvent(scoped_ptr<Event> event);
+
+ // Dispatches an event to the given extension.
+ virtual void DispatchEventToExtension(const std::string& extension_id,
+ scoped_ptr<Event> event);
+
+ // Dispatches |event| to the given extension as if the extension has a lazy
+ // listener for it. NOTE: This should be used rarely, for dispatching events
+ // to extensions that haven't had a chance to add their own listeners yet, eg:
+ // newly installed extensions.
+ void DispatchEventWithLazyListener(const std::string& extension_id,
+ scoped_ptr<Event> event);
+
+ // Record the Event Ack from the renderer. (One less event in-flight.)
+ void OnEventAck(content::BrowserContext* context,
+ const std::string& extension_id);
+
+ // Reports UMA for an event dispatched to |extension| with histogram value
+ // |histogram_value|. Must be called on the UI thread.
+ //
+ // |did_enqueue| should be true if the event was queued waiting for a process
+ // to start, like an event page.
+ void ReportEvent(events::HistogramValue histogram_value,
+ const Extension* extension,
+ bool did_enqueue);
+
+ private:
+ friend class EventRouterTest;
+
+ // The extension and process that contains the event listener for a given
+ // event.
+ struct ListenerProcess;
+
+ // A map between an event name and a set of extensions that are listening
+ // to that event.
+ typedef std::map<std::string, std::set<ListenerProcess> > ListenerMap;
+
+ // An identifier for an event dispatch that is used to prevent double dispatch
+ // due to race conditions between the direct and lazy dispatch paths.
+ typedef std::pair<const content::BrowserContext*, std::string>
+ EventDispatchIdentifier;
+
+ // TODO(gdk): Document this.
+ static void DispatchExtensionMessage(
+ IPC::Sender* ipc_sender,
+ void* browser_context_id,
+ const std::string& extension_id,
+ int event_id,
+ const std::string& event_name,
+ base::ListValue* event_args,
+ UserGestureState user_gesture,
+ const extensions::EventFilteringInfo& info);
+
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override;
+ // ExtensionRegistryObserver implementation.
+ void OnExtensionLoaded(content::BrowserContext* browser_context,
+ const Extension* extension) override;
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+
+ // Returns true if the given listener map contains a event listeners for
+ // the given event. If |extension_id| is non-empty, we also check that that
+ // extension is one of the listeners.
+ bool HasEventListenerImpl(const ListenerMap& listeners,
+ const std::string& extension_id,
+ const std::string& event_name);
+
+ // Shared by all event dispatch methods. If |restrict_to_extension_id| is
+ // empty, the event is broadcast. An event that just came off the pending
+ // list may not be delayed again.
+ void DispatchEventImpl(const std::string& restrict_to_extension_id,
+ const linked_ptr<Event>& event);
+
+ // Ensures that all lazy background pages that are interested in the given
+ // event are loaded, and queues the event if the page is not ready yet.
+ // Inserts an EventDispatchIdentifier into |already_dispatched| for each lazy
+ // event dispatch that is queued.
+ void DispatchLazyEvent(const std::string& extension_id,
+ const linked_ptr<Event>& event,
+ std::set<EventDispatchIdentifier>* already_dispatched,
+ const base::DictionaryValue* listener_filter);
+
+ // Dispatches the event to the specified extension or URL running in
+ // |process|.
+ void DispatchEventToProcess(const std::string& extension_id,
+ const GURL& listener_url,
+ content::RenderProcessHost* process,
+ const linked_ptr<Event>& event,
+ const base::DictionaryValue* listener_filter,
+ bool did_enqueue);
+
+ // Returns false when the event is scoped to a context and the listening
+ // extension does not have access to events from that context. Also fills
+ // |event_args| with the proper arguments to send, which may differ if
+ // the event crosses the incognito boundary.
+ bool CanDispatchEventToBrowserContext(content::BrowserContext* context,
+ const Extension* extension,
+ const linked_ptr<Event>& event);
+
+ // Possibly loads given extension's background page in preparation to
+ // dispatch an event. Returns true if the event was queued for subsequent
+ // dispatch, false otherwise.
+ bool MaybeLoadLazyBackgroundPageToDispatchEvent(
+ content::BrowserContext* context,
+ const Extension* extension,
+ const linked_ptr<Event>& event,
+ const base::DictionaryValue* listener_filter);
+
+ // Adds a filter to an event.
+ void AddFilterToEvent(const std::string& event_name,
+ const std::string& extension_id,
+ const base::DictionaryValue* filter);
+
+ // Removes a filter from an event.
+ void RemoveFilterFromEvent(const std::string& event_name,
+ const std::string& extension_id,
+ const base::DictionaryValue* filter);
+
+ // Returns the dictionary of event filters that the given extension has
+ // registered.
+ const base::DictionaryValue* GetFilteredEvents(
+ const std::string& extension_id);
+
+ // Track the dispatched events that have not yet sent an ACK from the
+ // renderer.
+ void IncrementInFlightEvents(content::BrowserContext* context,
+ const Extension* extension,
+ int event_id,
+ const std::string& event_name);
+
+ // static
+ static void DoDispatchEventToSenderBookkeepingOnUI(
+ void* browser_context_id,
+ const std::string& extension_id,
+ int event_id,
+ events::HistogramValue histogram_value,
+ const std::string& event_name);
+
+ void DispatchPendingEvent(const linked_ptr<Event>& event,
+ ExtensionHost* host);
+
+ // Implementation of EventListenerMap::Delegate.
+ void OnListenerAdded(const EventListener* listener) override;
+ void OnListenerRemoved(const EventListener* listener) override;
+
+ // RenderProcessHostObserver implementation.
+ void RenderProcessExited(content::RenderProcessHost* host,
+ base::TerminationStatus status,
+ int exit_code) override;
+ void RenderProcessHostDestroyed(content::RenderProcessHost* host) override;
+
+ content::BrowserContext* browser_context_;
+
+ // The ExtensionPrefs associated with |browser_context_|. May be NULL in
+ // tests.
+ ExtensionPrefs* extension_prefs_;
+
+ content::NotificationRegistrar registrar_;
+
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+
+ EventListenerMap listeners_;
+
+ // Map from base event name to observer.
+ typedef base::hash_map<std::string, Observer*> ObserverMap;
+ ObserverMap observers_;
+
+ std::set<content::RenderProcessHost*> observed_process_set_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventRouter);
+};
+
+struct Event {
+ // This callback should return true if the event should be dispatched to the
+ // given context and extension, and false otherwise.
+ typedef base::Callback<bool(content::BrowserContext*,
+ const Extension*,
+ Event*,
+ const base::DictionaryValue*)>
+ WillDispatchCallback;
+
+ // The identifier for the event, for histograms. In most cases this
+ // correlates 1:1 with |event_name|, in some cases events will generate
+ // their own names, but they cannot generate their own identifier.
+ events::HistogramValue histogram_value;
+
+ // The event to dispatch.
+ std::string event_name;
+
+ // Arguments to send to the event listener.
+ scoped_ptr<base::ListValue> event_args;
+
+ // If non-NULL, then the event will not be sent to other BrowserContexts
+ // unless the extension has permission (e.g. incognito tab update -> normal
+ // tab only works if extension is allowed incognito access).
+ content::BrowserContext* restrict_to_browser_context;
+
+ // If not empty, the event is only sent to extensions with host permissions
+ // for this url.
+ GURL event_url;
+
+ // Whether a user gesture triggered the event.
+ EventRouter::UserGestureState user_gesture;
+
+ // Extra information used to filter which events are sent to the listener.
+ EventFilteringInfo filter_info;
+
+ // If specified, this is called before dispatching an event to each
+ // extension. The third argument is a mutable reference to event_args,
+ // allowing the caller to provide different arguments depending on the
+ // extension and profile. This is guaranteed to be called synchronously with
+ // DispatchEvent, so callers don't need to worry about lifetime.
+ //
+ // NOTE: the Extension argument to this may be NULL because it's possible for
+ // this event to be dispatched to non-extension processes, like WebUI.
+ WillDispatchCallback will_dispatch_callback;
+
+ Event(events::HistogramValue histogram_value,
+ const std::string& event_name,
+ scoped_ptr<base::ListValue> event_args);
+
+ Event(events::HistogramValue histogram_value,
+ const std::string& event_name,
+ scoped_ptr<base::ListValue> event_args,
+ content::BrowserContext* restrict_to_browser_context);
+
+ Event(events::HistogramValue histogram_value,
+ const std::string& event_name,
+ scoped_ptr<base::ListValue> event_args,
+ content::BrowserContext* restrict_to_browser_context,
+ const GURL& event_url,
+ EventRouter::UserGestureState user_gesture,
+ const EventFilteringInfo& info);
+
+ ~Event();
+
+ // Makes a deep copy of this instance. Ownership is transferred to the
+ // caller.
+ Event* DeepCopy();
+};
+
+struct EventListenerInfo {
+ EventListenerInfo(const std::string& event_name,
+ const std::string& extension_id,
+ const GURL& listener_url,
+ content::BrowserContext* browser_context);
+ // The event name including any sub-event, e.g. "runtime.onStartup" or
+ // "webRequest.onCompleted/123".
+ const std::string event_name;
+
+ const std::string extension_id;
+ const GURL listener_url;
+ content::BrowserContext* browser_context;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EVENT_ROUTER_H_
diff --git a/chromium/extensions/browser/event_router_factory.cc b/chromium/extensions/browser/event_router_factory.cc
new file mode 100644
index 00000000000..0fe03bbe6ab
--- /dev/null
+++ b/chromium/extensions/browser/event_router_factory.cc
@@ -0,0 +1,52 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/event_router_factory.h"
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_prefs_factory.h"
+#include "extensions/browser/extension_registry_factory.h"
+#include "extensions/browser/extensions_browser_client.h"
+
+using content::BrowserContext;
+
+namespace extensions {
+
+// static
+EventRouter* EventRouterFactory::GetForBrowserContext(BrowserContext* context) {
+ return static_cast<EventRouter*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+EventRouterFactory* EventRouterFactory::GetInstance() {
+ return base::Singleton<EventRouterFactory>::get();
+}
+
+EventRouterFactory::EventRouterFactory()
+ : BrowserContextKeyedServiceFactory(
+ "EventRouter",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(ExtensionRegistryFactory::GetInstance());
+ DependsOn(ExtensionPrefsFactory::GetInstance());
+}
+
+EventRouterFactory::~EventRouterFactory() {
+}
+
+KeyedService* EventRouterFactory::BuildServiceInstanceFor(
+ BrowserContext* context) const {
+ return new EventRouter(context, ExtensionPrefs::Get(context));
+}
+
+BrowserContext* EventRouterFactory::GetBrowserContextToUse(
+ BrowserContext* context) const {
+ // Redirected in incognito.
+ return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/event_router_factory.h b/chromium/extensions/browser/event_router_factory.h
new file mode 100644
index 00000000000..e4747131318
--- /dev/null
+++ b/chromium/extensions/browser/event_router_factory.h
@@ -0,0 +1,38 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EVENT_ROUTER_FACTORY_H_
+#define EXTENSIONS_BROWSER_EVENT_ROUTER_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace extensions {
+
+class EventRouter;
+
+class EventRouterFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static EventRouter* GetForBrowserContext(content::BrowserContext* context);
+ static EventRouterFactory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<EventRouterFactory>;
+
+ EventRouterFactory();
+ ~EventRouterFactory() override;
+
+ // BrowserContextKeyedServiceFactory implementation
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(EventRouterFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EVENT_ROUTER_FACTORY_H_
diff --git a/chromium/extensions/browser/event_router_unittest.cc b/chromium/extensions/browser/event_router_unittest.cc
new file mode 100644
index 00000000000..bcc38fbd0e7
--- /dev/null
+++ b/chromium/extensions/browser/event_router_unittest.cc
@@ -0,0 +1,279 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/event_router.h"
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/test/histogram_tester.h"
+#include "base/values.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "extensions/browser/event_listener_map.h"
+#include "extensions/browser/extensions_test.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+// A simple mock to keep track of listener additions and removals.
+class MockEventRouterObserver : public EventRouter::Observer {
+ public:
+ MockEventRouterObserver()
+ : listener_added_count_(0),
+ listener_removed_count_(0) {}
+ virtual ~MockEventRouterObserver() {}
+
+ int listener_added_count() const { return listener_added_count_; }
+ int listener_removed_count() const { return listener_removed_count_; }
+ const std::string& last_event_name() const { return last_event_name_; }
+
+ void Reset() {
+ listener_added_count_ = 0;
+ listener_removed_count_ = 0;
+ last_event_name_.clear();
+ }
+
+ // EventRouter::Observer overrides:
+ void OnListenerAdded(const EventListenerInfo& details) override {
+ listener_added_count_++;
+ last_event_name_ = details.event_name;
+ }
+
+ void OnListenerRemoved(const EventListenerInfo& details) override {
+ listener_removed_count_++;
+ last_event_name_ = details.event_name;
+ }
+
+ private:
+ int listener_added_count_;
+ int listener_removed_count_;
+ std::string last_event_name_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockEventRouterObserver);
+};
+
+typedef base::Callback<scoped_ptr<EventListener>(
+ const std::string&, // event_name
+ content::RenderProcessHost*, // process
+ base::DictionaryValue* // filter (takes ownership)
+ )> EventListenerConstructor;
+
+scoped_ptr<EventListener> CreateEventListenerForExtension(
+ const std::string& extension_id,
+ const std::string& event_name,
+ content::RenderProcessHost* process,
+ base::DictionaryValue* filter) {
+ return EventListener::ForExtension(
+ event_name, extension_id, process, make_scoped_ptr(filter));
+}
+
+scoped_ptr<EventListener> CreateEventListenerForURL(
+ const GURL& listener_url,
+ const std::string& event_name,
+ content::RenderProcessHost* process,
+ base::DictionaryValue* filter) {
+ return EventListener::ForURL(
+ event_name, listener_url, process, make_scoped_ptr(filter));
+}
+
+// Creates an extension. If |component| is true, it is created as a component
+// extension. If |persistent| is true, it is created with a persistent
+// background page; otherwise it is created with an event page.
+scoped_refptr<Extension> CreateExtension(bool component, bool persistent) {
+ ExtensionBuilder builder;
+ scoped_ptr<base::DictionaryValue> manifest =
+ make_scoped_ptr(new base::DictionaryValue());
+ manifest->SetString("name", "foo");
+ manifest->SetString("version", "1.0.0");
+ manifest->SetInteger("manifest_version", 2);
+ manifest->SetString("background.page", "background.html");
+ manifest->SetBoolean("background.persistent", persistent);
+ builder.SetManifest(std::move(manifest));
+ if (component)
+ builder.SetLocation(Manifest::Location::COMPONENT);
+
+ return builder.Build();
+}
+
+} // namespace
+
+class EventRouterTest : public ExtensionsTest {
+ public:
+ EventRouterTest()
+ : notification_service_(content::NotificationService::Create()) {}
+
+ protected:
+ // Tests adding and removing observers from EventRouter.
+ void RunEventRouterObserverTest(const EventListenerConstructor& constructor);
+
+ // Tests that the correct counts are recorded for the Extensions.Events
+ // histograms.
+ void ExpectHistogramCounts(int dispatch_count,
+ int component_count,
+ int persistent_count,
+ int suspended_count,
+ int component_suspended_count,
+ int running_count) {
+ if (dispatch_count) {
+ histogram_tester_.ExpectBucketCount("Extensions.Events.Dispatch",
+ events::HistogramValue::FOR_TEST,
+ dispatch_count);
+ }
+ if (component_count) {
+ histogram_tester_.ExpectBucketCount(
+ "Extensions.Events.DispatchToComponent",
+ events::HistogramValue::FOR_TEST, component_count);
+ }
+ if (persistent_count) {
+ histogram_tester_.ExpectBucketCount(
+ "Extensions.Events.DispatchWithPersistentBackgroundPage",
+ events::HistogramValue::FOR_TEST, persistent_count);
+ }
+ if (suspended_count) {
+ histogram_tester_.ExpectBucketCount(
+ "Extensions.Events.DispatchWithSuspendedEventPage",
+ events::HistogramValue::FOR_TEST, suspended_count);
+ }
+ if (component_suspended_count) {
+ histogram_tester_.ExpectBucketCount(
+ "Extensions.Events.DispatchToComponentWithSuspendedEventPage",
+ events::HistogramValue::FOR_TEST, component_suspended_count);
+ }
+ if (running_count) {
+ histogram_tester_.ExpectBucketCount(
+ "Extensions.Events.DispatchWithRunningEventPage",
+ events::HistogramValue::FOR_TEST, running_count);
+ }
+ }
+
+ private:
+ scoped_ptr<content::NotificationService> notification_service_;
+ content::TestBrowserThreadBundle thread_bundle_;
+ base::HistogramTester histogram_tester_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventRouterTest);
+};
+
+TEST_F(EventRouterTest, GetBaseEventName) {
+ // Normal event names are passed through unchanged.
+ EXPECT_EQ("foo.onBar", EventRouter::GetBaseEventName("foo.onBar"));
+
+ // Sub-events are converted to the part before the slash.
+ EXPECT_EQ("foo.onBar", EventRouter::GetBaseEventName("foo.onBar/123"));
+}
+
+// Tests adding and removing observers from EventRouter.
+void EventRouterTest::RunEventRouterObserverTest(
+ const EventListenerConstructor& constructor) {
+ EventRouter router(NULL, NULL);
+ scoped_ptr<EventListener> listener =
+ constructor.Run("event_name", NULL, new base::DictionaryValue());
+
+ // Add/remove works without any observers.
+ router.OnListenerAdded(listener.get());
+ router.OnListenerRemoved(listener.get());
+
+ // Register observers that both match and don't match the event above.
+ MockEventRouterObserver matching_observer;
+ router.RegisterObserver(&matching_observer, "event_name");
+ MockEventRouterObserver non_matching_observer;
+ router.RegisterObserver(&non_matching_observer, "other");
+
+ // Adding a listener notifies the appropriate observers.
+ router.OnListenerAdded(listener.get());
+ EXPECT_EQ(1, matching_observer.listener_added_count());
+ EXPECT_EQ(0, non_matching_observer.listener_added_count());
+
+ // Removing a listener notifies the appropriate observers.
+ router.OnListenerRemoved(listener.get());
+ EXPECT_EQ(1, matching_observer.listener_removed_count());
+ EXPECT_EQ(0, non_matching_observer.listener_removed_count());
+
+ // Adding the listener again notifies again.
+ router.OnListenerAdded(listener.get());
+ EXPECT_EQ(2, matching_observer.listener_added_count());
+ EXPECT_EQ(0, non_matching_observer.listener_added_count());
+
+ // Removing the listener again notifies again.
+ router.OnListenerRemoved(listener.get());
+ EXPECT_EQ(2, matching_observer.listener_removed_count());
+ EXPECT_EQ(0, non_matching_observer.listener_removed_count());
+
+ // Adding a listener with a sub-event notifies the main observer with
+ // proper details.
+ matching_observer.Reset();
+ scoped_ptr<EventListener> sub_event_listener =
+ constructor.Run("event_name/1", NULL, new base::DictionaryValue());
+ router.OnListenerAdded(sub_event_listener.get());
+ EXPECT_EQ(1, matching_observer.listener_added_count());
+ EXPECT_EQ(0, matching_observer.listener_removed_count());
+ EXPECT_EQ("event_name/1", matching_observer.last_event_name());
+
+ // Ditto for removing the listener.
+ matching_observer.Reset();
+ router.OnListenerRemoved(sub_event_listener.get());
+ EXPECT_EQ(0, matching_observer.listener_added_count());
+ EXPECT_EQ(1, matching_observer.listener_removed_count());
+ EXPECT_EQ("event_name/1", matching_observer.last_event_name());
+}
+
+TEST_F(EventRouterTest, EventRouterObserverForExtensions) {
+ RunEventRouterObserverTest(
+ base::Bind(&CreateEventListenerForExtension, "extension_id"));
+}
+
+TEST_F(EventRouterTest, EventRouterObserverForURLs) {
+ RunEventRouterObserverTest(
+ base::Bind(&CreateEventListenerForURL, GURL("http://google.com/path")));
+}
+
+TEST_F(EventRouterTest, TestReportEvent) {
+ EventRouter router(browser_context(), NULL);
+ scoped_refptr<Extension> normal = test_util::CreateEmptyExtension("id1");
+ router.ReportEvent(events::HistogramValue::FOR_TEST, normal.get(),
+ false /** did_enqueue */);
+ ExpectHistogramCounts(1 /** Dispatch */, 0 /** DispatchToComponent */,
+ 0 /** DispatchWithPersistentBackgroundPage */,
+ 0 /** DispatchWithSuspendedEventPage */,
+ 0 /** DispatchToComponentWithSuspendedEventPage */,
+ 0 /** DispatchWithRunningEventPage */);
+
+ scoped_refptr<Extension> component =
+ CreateExtension(true /** component */, true /** persistent */);
+ router.ReportEvent(events::HistogramValue::FOR_TEST, component.get(),
+ false /** did_enqueue */);
+ ExpectHistogramCounts(2, 1, 1, 0, 0, 0);
+
+ scoped_refptr<Extension> persistent = CreateExtension(false, true);
+ router.ReportEvent(events::HistogramValue::FOR_TEST, persistent.get(),
+ false /** did_enqueue */);
+ ExpectHistogramCounts(3, 1, 2, 0, 0, 0);
+
+ scoped_refptr<Extension> event = CreateExtension(false, false);
+ router.ReportEvent(events::HistogramValue::FOR_TEST, event.get(),
+ false /** did_enqueue */);
+ ExpectHistogramCounts(4, 1, 2, 0, 0, 0);
+ router.ReportEvent(events::HistogramValue::FOR_TEST, event.get(),
+ true /** did_enqueue */);
+ ExpectHistogramCounts(5, 1, 2, 1, 0, 1);
+
+ scoped_refptr<Extension> component_event = CreateExtension(true, false);
+ router.ReportEvent(events::HistogramValue::FOR_TEST, component_event.get(),
+ false /** did_enqueue */);
+ ExpectHistogramCounts(6, 2, 2, 1, 0, 2);
+ router.ReportEvent(events::HistogramValue::FOR_TEST, component_event.get(),
+ true /** did_enqueue */);
+ ExpectHistogramCounts(7, 3, 2, 2, 1, 2);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_api_frame_id_map.cc b/chromium/extensions/browser/extension_api_frame_id_map.cc
new file mode 100644
index 00000000000..d8566f97598
--- /dev/null
+++ b/chromium/extensions/browser/extension_api_frame_id_map.cc
@@ -0,0 +1,354 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_api_frame_id_map.h"
+
+#include <tuple>
+
+#include "base/metrics/histogram_macros.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/child_process_host.h"
+#include "extensions/browser/extensions_browser_client.h"
+
+namespace extensions {
+
+namespace {
+
+// The map is accessed on the IO and UI thread, so construct it once and never
+// delete it.
+base::LazyInstance<ExtensionApiFrameIdMap>::Leaky g_map_instance =
+ LAZY_INSTANCE_INITIALIZER;
+
+bool IsFrameRoutingIdValid(int frame_routing_id) {
+ // frame_routing_id == -2 = MSG_ROUTING_NONE -> not a RenderFrameHost.
+ // frame_routing_id == -1 -> should be MSG_ROUTING_NONE, but there are
+ // callers that use "-1" for unknown frames.
+ return frame_routing_id > -1;
+}
+
+} // namespace
+
+const int ExtensionApiFrameIdMap::kInvalidFrameId = -1;
+const int ExtensionApiFrameIdMap::kTopFrameId = 0;
+
+ExtensionApiFrameIdMap::FrameData::FrameData()
+ : frame_id(kInvalidFrameId),
+ parent_frame_id(kInvalidFrameId),
+ tab_id(-1),
+ window_id(-1) {}
+
+ExtensionApiFrameIdMap::FrameData::FrameData(int frame_id,
+ int parent_frame_id,
+ int tab_id,
+ int window_id)
+ : frame_id(frame_id),
+ parent_frame_id(parent_frame_id),
+ tab_id(tab_id),
+ window_id(window_id) {}
+
+ExtensionApiFrameIdMap::RenderFrameIdKey::RenderFrameIdKey()
+ : render_process_id(content::ChildProcessHost::kInvalidUniqueID),
+ frame_routing_id(MSG_ROUTING_NONE) {}
+
+ExtensionApiFrameIdMap::RenderFrameIdKey::RenderFrameIdKey(
+ int render_process_id,
+ int frame_routing_id)
+ : render_process_id(render_process_id),
+ frame_routing_id(frame_routing_id) {}
+
+ExtensionApiFrameIdMap::FrameDataCallbacks::FrameDataCallbacks()
+ : is_iterating(false) {}
+
+ExtensionApiFrameIdMap::FrameDataCallbacks::FrameDataCallbacks(
+ const FrameDataCallbacks& other) = default;
+
+ExtensionApiFrameIdMap::FrameDataCallbacks::~FrameDataCallbacks() {}
+
+bool ExtensionApiFrameIdMap::RenderFrameIdKey::operator<(
+ const RenderFrameIdKey& other) const {
+ return std::tie(render_process_id, frame_routing_id) <
+ std::tie(other.render_process_id, other.frame_routing_id);
+}
+
+bool ExtensionApiFrameIdMap::RenderFrameIdKey::operator==(
+ const RenderFrameIdKey& other) const {
+ return render_process_id == other.render_process_id &&
+ frame_routing_id == other.frame_routing_id;
+}
+
+ExtensionApiFrameIdMap::ExtensionApiFrameIdMap() {
+ // The browser client can be null in unittests.
+ if (ExtensionsBrowserClient::Get()) {
+ helper_ =
+ ExtensionsBrowserClient::Get()->CreateExtensionApiFrameIdMapHelper(
+ this);
+ }
+}
+
+ExtensionApiFrameIdMap::~ExtensionApiFrameIdMap() {}
+
+// static
+ExtensionApiFrameIdMap* ExtensionApiFrameIdMap::Get() {
+ return g_map_instance.Pointer();
+}
+
+// static
+int ExtensionApiFrameIdMap::GetFrameId(content::RenderFrameHost* rfh) {
+ if (!rfh)
+ return kInvalidFrameId;
+ if (rfh->GetParent())
+ return rfh->GetFrameTreeNodeId();
+ return kTopFrameId;
+}
+
+// static
+int ExtensionApiFrameIdMap::GetFrameId(
+ content::NavigationHandle* navigation_handle) {
+ return navigation_handle->IsInMainFrame()
+ ? 0
+ : navigation_handle->GetFrameTreeNodeId();
+}
+
+// static
+int ExtensionApiFrameIdMap::GetParentFrameId(content::RenderFrameHost* rfh) {
+ return rfh ? GetFrameId(rfh->GetParent()) : kInvalidFrameId;
+}
+
+// static
+int ExtensionApiFrameIdMap::GetParentFrameId(
+ content::NavigationHandle* navigation_handle) {
+ if (navigation_handle->IsInMainFrame())
+ return -1;
+
+ if (navigation_handle->IsParentMainFrame())
+ return 0;
+
+ return navigation_handle->GetParentFrameTreeNodeId();
+}
+
+// static
+content::RenderFrameHost* ExtensionApiFrameIdMap::GetRenderFrameHostById(
+ content::WebContents* web_contents,
+ int frame_id) {
+ // Although it is technically possible to map |frame_id| to a RenderFrameHost
+ // without WebContents, we choose to not do that because in the extension API
+ // frameIds are only guaranteed to be meaningful in combination with a tabId.
+ if (!web_contents)
+ return nullptr;
+
+ if (frame_id == kInvalidFrameId)
+ return nullptr;
+
+ if (frame_id == kTopFrameId)
+ return web_contents->GetMainFrame();
+
+ DCHECK_GE(frame_id, 1);
+ return web_contents->FindFrameByFrameTreeNodeId(frame_id);
+}
+
+ExtensionApiFrameIdMap::FrameData ExtensionApiFrameIdMap::KeyToValue(
+ const RenderFrameIdKey& key) const {
+ content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
+ key.render_process_id, key.frame_routing_id);
+ int tab_id = -1;
+ int window_id = -1;
+ if (helper_)
+ helper_->GetTabAndWindowId(rfh, &tab_id, &window_id);
+ return FrameData(GetFrameId(rfh), GetParentFrameId(rfh), tab_id, window_id);
+}
+
+ExtensionApiFrameIdMap::FrameData ExtensionApiFrameIdMap::LookupFrameDataOnUI(
+ const RenderFrameIdKey& key,
+ bool for_lookup) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ bool lookup_successful = false;
+ FrameData data;
+ FrameDataMap::const_iterator frame_id_iter = frame_data_map_.find(key);
+ if (frame_id_iter != frame_data_map_.end()) {
+ lookup_successful = true;
+ data = frame_id_iter->second;
+ } else {
+ data = KeyToValue(key);
+ // Don't save invalid values in the map.
+ if (data.frame_id != kInvalidFrameId) {
+ lookup_successful = true;
+ auto kvpair = FrameDataMap::value_type(key, data);
+ base::AutoLock lock(frame_data_map_lock_);
+ frame_data_map_.insert(kvpair);
+ }
+ }
+
+ // TODO(devlin): Depending on how the data looks, this may be removable after
+ // a few cycles. Check back in M52 to see if it's still needed.
+ if (for_lookup) {
+ UMA_HISTOGRAM_BOOLEAN("Extensions.ExtensionFrameMapLookupSuccessful",
+ lookup_successful);
+ }
+
+ return data;
+}
+
+void ExtensionApiFrameIdMap::ReceivedFrameDataOnIO(
+ const RenderFrameIdKey& key,
+ const FrameData& cached_frame_data) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+
+ FrameDataCallbacksMap::iterator map_iter = callbacks_map_.find(key);
+ if (map_iter == callbacks_map_.end()) {
+ // Can happen if ReceivedFrameIdOnIO was called after the frame ID was
+ // resolved (e.g. via GetFrameDataOnIO), but before PostTaskAndReply
+ // replied.
+ return;
+ }
+
+ FrameDataCallbacks& callbacks = map_iter->second;
+
+ if (callbacks.is_iterating)
+ return;
+ callbacks.is_iterating = true;
+
+ // Note: Extra items can be appended to |callbacks| during this loop if a
+ // callback calls GetFrameDataOnIO().
+ for (std::list<FrameDataCallback>::iterator it = callbacks.callbacks.begin();
+ it != callbacks.callbacks.end(); ++it) {
+ it->Run(cached_frame_data);
+ }
+ callbacks_map_.erase(key);
+}
+
+void ExtensionApiFrameIdMap::GetFrameDataOnIO(
+ int render_process_id,
+ int frame_routing_id,
+ const FrameDataCallback& callback) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+
+ // TODO(robwu): Enable assertion when all callers have been fixed.
+ // DCHECK_EQ(MSG_ROUTING_NONE, -1);
+ if (!IsFrameRoutingIdValid(frame_routing_id)) {
+ callback.Run(FrameData());
+ return;
+ }
+
+ if (frame_routing_id <= -1) {
+ // frame_routing_id == -2 = MSG_ROUTING_NONE -> not a RenderFrameHost.
+ // frame_routing_id == -1 -> should be MSG_ROUTING_NONE, but there are
+ // callers that use "-1" for unknown frames.
+ callback.Run(FrameData());
+ return;
+ }
+
+ FrameData cached_frame_data;
+ bool did_find_cached_frame_data = GetCachedFrameDataOnIO(
+ render_process_id, frame_routing_id, &cached_frame_data);
+
+ const RenderFrameIdKey key(render_process_id, frame_routing_id);
+ FrameDataCallbacksMap::iterator map_iter = callbacks_map_.find(key);
+
+ if (did_find_cached_frame_data) {
+ // Value already cached, thread hopping is not needed.
+ if (map_iter == callbacks_map_.end()) {
+ // If the frame ID was cached, then it is likely that there are no pending
+ // callbacks. So do not unnecessarily copy the callback, but run it.
+ callback.Run(cached_frame_data);
+ } else {
+ map_iter->second.callbacks.push_back(callback);
+ ReceivedFrameDataOnIO(key, cached_frame_data);
+ }
+ return;
+ }
+
+ // The key was seen for the first time (or the frame has been removed).
+ // Hop to the UI thread to look up the extension API frame ID.
+ callbacks_map_[key].callbacks.push_back(callback);
+ content::BrowserThread::PostTaskAndReplyWithResult(
+ content::BrowserThread::UI, FROM_HERE,
+ base::Bind(&ExtensionApiFrameIdMap::LookupFrameDataOnUI,
+ base::Unretained(this), key, true /* for lookup */),
+ base::Bind(&ExtensionApiFrameIdMap::ReceivedFrameDataOnIO,
+ base::Unretained(this), key));
+}
+
+bool ExtensionApiFrameIdMap::GetCachedFrameDataOnIO(int render_process_id,
+ int frame_routing_id,
+ FrameData* frame_data_out) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+
+ // TODO(robwu): Enable assertion when all callers have been fixed.
+ // DCHECK_EQ(MSG_ROUTING_NONE, -1);
+ if (!IsFrameRoutingIdValid(frame_routing_id))
+ return false;
+
+ // A valid routing ID is only meaningful with a valid process ID.
+ DCHECK_GE(render_process_id, 0);
+
+ bool found = false;
+ {
+ base::AutoLock lock(frame_data_map_lock_);
+ FrameDataMap::const_iterator frame_id_iter = frame_data_map_.find(
+ RenderFrameIdKey(render_process_id, frame_routing_id));
+ if (frame_id_iter != frame_data_map_.end()) {
+ // This is very likely to happen because CacheFrameId() is called as soon
+ // as the frame is created.
+ *frame_data_out = frame_id_iter->second;
+ found = true;
+ }
+ }
+
+ // TODO(devlin): Depending on how the data looks, this may be removable after
+ // a few cycles. Check back in M52 to see if it's still needed.
+ UMA_HISTOGRAM_BOOLEAN("Extensions.ExtensionFrameMapCacheHit", found);
+ return found;
+}
+
+void ExtensionApiFrameIdMap::CacheFrameData(content::RenderFrameHost* rfh) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ const RenderFrameIdKey key(rfh->GetProcess()->GetID(), rfh->GetRoutingID());
+ CacheFrameData(key);
+ DCHECK(frame_data_map_.find(key) != frame_data_map_.end());
+}
+
+void ExtensionApiFrameIdMap::CacheFrameData(const RenderFrameIdKey& key) {
+ LookupFrameDataOnUI(key, false /* not for lookup */);
+}
+
+void ExtensionApiFrameIdMap::RemoveFrameData(content::RenderFrameHost* rfh) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK(rfh);
+
+ const RenderFrameIdKey key(rfh->GetProcess()->GetID(), rfh->GetRoutingID());
+ RemoveFrameData(key);
+}
+
+void ExtensionApiFrameIdMap::UpdateTabAndWindowId(
+ int tab_id,
+ int window_id,
+ content::RenderFrameHost* rfh) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK(rfh);
+ const RenderFrameIdKey key(rfh->GetProcess()->GetID(), rfh->GetRoutingID());
+ base::AutoLock lock(frame_data_map_lock_);
+ FrameDataMap::iterator iter = frame_data_map_.find(key);
+ if (iter != frame_data_map_.end()) {
+ iter->second.tab_id = tab_id;
+ iter->second.window_id = window_id;
+ } else {
+ frame_data_map_[key] =
+ FrameData(GetFrameId(rfh), GetParentFrameId(rfh), tab_id, window_id);
+ }
+}
+
+void ExtensionApiFrameIdMap::RemoveFrameData(const RenderFrameIdKey& key) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ base::AutoLock lock(frame_data_map_lock_);
+ frame_data_map_.erase(key);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_api_frame_id_map.h b/chromium/extensions/browser/extension_api_frame_id_map.h
new file mode 100644
index 00000000000..62330626df9
--- /dev/null
+++ b/chromium/extensions/browser/extension_api_frame_id_map.h
@@ -0,0 +1,214 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_API_FRAME_ID_MAP_H_
+#define EXTENSIONS_BROWSER_EXTENSION_API_FRAME_ID_MAP_H_
+
+#include <list>
+#include <map>
+#include <memory>
+
+#include "base/callback.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/synchronization/lock.h"
+
+namespace content {
+class NavigationHandle;
+class RenderFrameHost;
+class WebContents;
+} // namespace content
+
+namespace extensions {
+
+class ExtensionApiFrameIdMapHelper {
+ public:
+ virtual void GetTabAndWindowId(content::RenderFrameHost* rfh,
+ int* tab_id_out,
+ int* window_id_out) = 0;
+ virtual ~ExtensionApiFrameIdMapHelper() {}
+};
+
+// Extension frame IDs are exposed through the chrome.* APIs and have the
+// following characteristics:
+// - The top-level frame has ID 0.
+// - Any child frame has a positive ID.
+// - A non-existant frame has ID -1.
+// - They are only guaranteed to be unique within a tab.
+// - The ID does not change during the frame's lifetime and is not re-used after
+// the frame is removed. The frame may change its current RenderFrameHost over
+// time, so multiple RenderFrameHosts may map to the same extension frame ID.
+//
+// This class provides a mapping from a (render_process_id, frame_routing_id)
+// pair to a FrameData struct, which includes the extension's frame id (as
+// described above), the parent frame id, and the tab id (the latter can be
+// invalid if it's not in a tab).
+//
+// Unless stated otherwise, the methods can only be called on the UI thread.
+//
+// The non-static methods of this class use an internal cache. This cache is
+// used to minimize IO->UI->IO round-trips of GetFrameIdOnIO. If the cost of
+// attaching FrameTreeNode IDs to requests is negligible (crbug.com/524228),
+// then we can remove all key caching and remove the cache from this class.
+// TODO(robwu): Keep an eye on crbug.com/524228 and act upon the outcome.
+// TODO(devlin): Also keep an eye on FrameIOData to see if that could help.
+class ExtensionApiFrameIdMap {
+ public:
+ // The data for a RenderFrame. Every RenderFrameIdKey maps to a FrameData.
+ struct FrameData {
+ FrameData();
+ FrameData(int frame_id, int parent_frame_id, int tab_id, int window_id);
+
+ // The extension API frame ID of the frame.
+ int frame_id;
+
+ // The extension API frame ID of the parent of the frame.
+ int parent_frame_id;
+
+ // The id of the tab that the frame is in, or -1 if the frame isn't in a
+ // tab.
+ int tab_id;
+
+ // The id of the window that the frame is in, or -1 if the frame isn't in a
+ // window.
+ int window_id;
+ };
+
+ using FrameDataCallback = base::Callback<void(const FrameData&)>;
+
+ // An invalid extension API frame ID.
+ static const int kInvalidFrameId;
+
+ // Extension API frame ID of the top-level frame.
+ static const int kTopFrameId;
+
+ static ExtensionApiFrameIdMap* Get();
+
+ // Get the extension API frame ID for |rfh|.
+ static int GetFrameId(content::RenderFrameHost* rfh);
+
+ // Get the extension API frame ID for |navigation_handle|.
+ static int GetFrameId(content::NavigationHandle* navigation_handle);
+
+ // Get the extension API frame ID for the parent of |rfh|.
+ static int GetParentFrameId(content::RenderFrameHost* rfh);
+
+ // Get the extension API frame ID for the parent of |navigation_handle|.
+ static int GetParentFrameId(content::NavigationHandle* navigation_handle);
+
+ // Find the current RenderFrameHost for a given WebContents and extension
+ // frame ID.
+ // Returns nullptr if not found.
+ static content::RenderFrameHost* GetRenderFrameHostById(
+ content::WebContents* web_contents,
+ int frame_id);
+
+ // Runs |callback| with the result that is equivalent to calling GetFrameId()
+ // on the UI thread. Thread hopping is minimized if possible. Callbacks for
+ // the same |render_process_id| and |frame_routing_id| are guaranteed to be
+ // run in order. The order of other callbacks is undefined.
+ void GetFrameDataOnIO(int render_process_id,
+ int frame_routing_id,
+ const FrameDataCallback& callback);
+
+ // Attempts to populate |frame_data_out| with the FrameData for the specified
+ // frame, but only does so if the data is already cached. Returns true if
+ // cached frame data was found.
+ bool GetCachedFrameDataOnIO(int render_process_id,
+ int frame_routing_id,
+ FrameData* frame_data_out);
+
+ // Looks up the frame ID and stores it in the map. This method should be
+ // called as early as possible, e.g. in a
+ // WebContentsObserver::RenderFrameCreated notification.
+ void CacheFrameData(content::RenderFrameHost* rfh);
+
+ // Removes the frame ID mapping for a given frame. This method can be called
+ // at any time, but it is typically called when a frame is destroyed.
+ // If this method is not called, the cached mapping for the frame is retained
+ // forever.
+ void RemoveFrameData(content::RenderFrameHost* rfh);
+
+ // Updates the tab and window id for the given RenderFrameHost, if any exists.
+ void UpdateTabAndWindowId(int tab_id,
+ int window_id,
+ content::RenderFrameHost* rfh);
+
+ protected:
+ friend struct base::DefaultLazyInstanceTraits<ExtensionApiFrameIdMap>;
+
+ // A set of identifiers that uniquely identifies a RenderFrame.
+ struct RenderFrameIdKey {
+ RenderFrameIdKey();
+ RenderFrameIdKey(int render_process_id, int frame_routing_id);
+
+ // The process ID of the renderer that contains the RenderFrame.
+ int render_process_id;
+
+ // The routing ID of the RenderFrame.
+ int frame_routing_id;
+
+ bool operator<(const RenderFrameIdKey& other) const;
+ bool operator==(const RenderFrameIdKey& other) const;
+ };
+
+ struct FrameDataCallbacks {
+ FrameDataCallbacks();
+ FrameDataCallbacks(const FrameDataCallbacks& other);
+ ~FrameDataCallbacks();
+
+ // This is a std::list so that iterators are not invalidated when the list
+ // is modified during an iteration.
+ std::list<FrameDataCallback> callbacks;
+
+ // To avoid re-entrant processing of callbacks.
+ bool is_iterating;
+ };
+
+ using FrameDataMap = std::map<RenderFrameIdKey, FrameData>;
+ using FrameDataCallbacksMap = std::map<RenderFrameIdKey, FrameDataCallbacks>;
+
+ ExtensionApiFrameIdMap();
+ virtual ~ExtensionApiFrameIdMap();
+
+ // Determines the value to be stored in |frame_id_map_| for a given key. This
+ // method is only called when |key| is not in |frame_id_map_|.
+ // virtual for testing.
+ virtual FrameData KeyToValue(const RenderFrameIdKey& key) const;
+
+ // Looks up the data for the given |key| and adds it to the |frame_data_map_|.
+ // |for_lookup| indicates whether this is for a pending lookup (as opposed to
+ // preemptively caching the frame data).
+ FrameData LookupFrameDataOnUI(const RenderFrameIdKey& key, bool for_lookup);
+
+ // Called as soon as the frame ID is found for the given |key|, and runs all
+ // queued callbacks with |cached_frame_id_pair|.
+ void ReceivedFrameDataOnIO(const RenderFrameIdKey& key,
+ const FrameData& cached_frame_id_pair);
+
+ // Implementation of CacheFrameId(RenderFrameHost), separated for testing.
+ void CacheFrameData(const RenderFrameIdKey& key);
+
+ // Implementation of RemoveFrameId(RenderFrameHost), separated for testing.
+ void RemoveFrameData(const RenderFrameIdKey& key);
+
+ std::unique_ptr<ExtensionApiFrameIdMapHelper> helper_;
+
+ // Queued callbacks for use on the IO thread.
+ FrameDataCallbacksMap callbacks_map_;
+
+ // This map is only modified on the UI thread and is used to minimize the
+ // number of thread hops on the IO thread.
+ FrameDataMap frame_data_map_;
+
+ // This lock protects |frame_id_map_| from being concurrently written on the
+ // UI thread and read on the IO thread.
+ base::Lock frame_data_map_lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionApiFrameIdMap);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_API_FRAME_ID_MAP_H_
diff --git a/chromium/extensions/browser/extension_api_frame_id_map_unittest.cc b/chromium/extensions/browser/extension_api_frame_id_map_unittest.cc
new file mode 100644
index 00000000000..8fb7def9af0
--- /dev/null
+++ b/chromium/extensions/browser/extension_api_frame_id_map_unittest.cc
@@ -0,0 +1,278 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "extensions/browser/extension_api_frame_id_map.h"
+#include "ipc/ipc_message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using FrameDataCallback = extensions::ExtensionApiFrameIdMap::FrameDataCallback;
+
+namespace extensions {
+
+namespace {
+
+int ToTestFrameId(int render_process_id, int frame_routing_id) {
+ if (render_process_id < 0 && frame_routing_id < 0)
+ return ExtensionApiFrameIdMap::kInvalidFrameId;
+ // Return a deterministic value (yet different from the input) for testing.
+ // To make debugging easier: Ending with 0 = frame ID.
+ return render_process_id * 1000 + frame_routing_id * 10;
+}
+
+int ToTestParentFrameId(int render_process_id, int frame_routing_id) {
+ if (render_process_id < 0 && frame_routing_id < 0)
+ return ExtensionApiFrameIdMap::kInvalidFrameId;
+ // Return a deterministic value (yet different from the input) for testing.
+ // To make debugging easier: Ending with 7 = parent frame ID.
+ return render_process_id * 1000 + frame_routing_id * 10 + 7;
+}
+
+int ToTestTabId(int render_process_id, int frame_routing_id) {
+ if (render_process_id < 0 && frame_routing_id < 0)
+ return -1;
+ // Return a deterministic value (yet different from the input) for testing.
+ // To make debugging easier: Ending with 5 = tab ID.
+ return render_process_id * 1000 + frame_routing_id * 10 + 5;
+}
+
+int ToTestWindowId(int render_process_id, int frame_routing_id) {
+ if (render_process_id < 0 && frame_routing_id < 0)
+ return -1;
+ // Return a deterministic value (yet different from the input) for testing.
+ // To make debugging easier: Ending with 4 = window ID.
+ return render_process_id * 1000 + frame_routing_id * 10 + 4;
+}
+
+class TestExtensionApiFrameIdMap : public ExtensionApiFrameIdMap {
+ public:
+ TestExtensionApiFrameIdMap() {}
+ ~TestExtensionApiFrameIdMap() override {}
+
+ int GetInternalSize() { return frame_data_map_.size(); }
+ int GetInternalCallbackCount() {
+ int count = 0;
+ for (auto& it : callbacks_map_)
+ count += it.second.callbacks.size();
+ return count;
+ }
+
+ // These indirections are used because we cannot mock RenderFrameHost with
+ // fixed IDs in unit tests.
+ // TODO(robwu): Use content/public/test/test_renderer_host.h to mock
+ // RenderFrameHosts and update the tests to test against these mocks.
+ // After doing that, there is no need for CacheFrameData/RemoveFrameData
+ // methods that take a RenderFrameIdKey, so the methods can be merged.
+ void SetInternalFrameData(int render_process_id, int frame_routing_id) {
+ CacheFrameData(RenderFrameIdKey(render_process_id, frame_routing_id));
+ }
+ void RemoveInternalFrameData(int render_process_id, int frame_routing_id) {
+ RemoveFrameData(RenderFrameIdKey(render_process_id, frame_routing_id));
+ }
+
+ private:
+ // ExtensionApiFrameIdMap:
+ FrameData KeyToValue(const RenderFrameIdKey& key) const override {
+ return FrameData(
+ ToTestFrameId(key.render_process_id, key.frame_routing_id),
+ ToTestParentFrameId(key.render_process_id, key.frame_routing_id),
+ ToTestTabId(key.render_process_id, key.frame_routing_id),
+ ToTestWindowId(key.render_process_id, key.frame_routing_id));
+ }
+};
+
+class ExtensionApiFrameIdMapTest : public testing::Test {
+ public:
+ ExtensionApiFrameIdMapTest()
+ : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {}
+
+ FrameDataCallback CreateCallback(
+ int render_process_id,
+ int frame_routing_id,
+ const std::string& callback_name_for_testing) {
+ return base::Bind(&ExtensionApiFrameIdMapTest::OnCalledCallback,
+ base::Unretained(this), render_process_id,
+ frame_routing_id, callback_name_for_testing);
+ }
+
+ void OnCalledCallback(int render_process_id,
+ int frame_routing_id,
+ const std::string& callback_name_for_testing,
+ const ExtensionApiFrameIdMap::FrameData& frame_data) {
+ results_.push_back(callback_name_for_testing);
+
+ // If this fails, then the mapping is completely wrong.
+ EXPECT_EQ(ToTestFrameId(render_process_id, frame_routing_id),
+ frame_data.frame_id);
+ EXPECT_EQ(ToTestParentFrameId(render_process_id, frame_routing_id),
+ frame_data.parent_frame_id);
+ EXPECT_EQ(ToTestTabId(render_process_id, frame_routing_id),
+ frame_data.tab_id);
+ EXPECT_EQ(ToTestWindowId(render_process_id, frame_routing_id),
+ frame_data.window_id);
+ }
+
+ const std::vector<std::string>& results() { return results_; }
+ void ClearResults() { results_.clear(); }
+
+ private:
+ content::TestBrowserThreadBundle thread_bundle_;
+ // Used to verify the order of callbacks.
+ std::vector<std::string> results_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionApiFrameIdMapTest);
+};
+
+} // namespace
+
+TEST_F(ExtensionApiFrameIdMapTest, GetFrameDataOnIO) {
+ TestExtensionApiFrameIdMap map;
+ EXPECT_EQ(0, map.GetInternalSize());
+
+ // Two identical calls, should be processed at the next message loop.
+ map.GetFrameDataOnIO(1, 2, CreateCallback(1, 2, "first"));
+ EXPECT_EQ(1, map.GetInternalCallbackCount());
+ EXPECT_EQ(0, map.GetInternalSize());
+
+ map.GetFrameDataOnIO(1, 2, CreateCallback(1, 2, "first again"));
+ EXPECT_EQ(2, map.GetInternalCallbackCount());
+ EXPECT_EQ(0, map.GetInternalSize());
+
+ // First get the frame ID on IO (queued on message loop), then set it on UI.
+ // No callbacks should be invoked because the IO thread cannot know that the
+ // frame ID was set on the UI thread.
+ map.GetFrameDataOnIO(2, 1, CreateCallback(2, 1, "something else"));
+ EXPECT_EQ(3, map.GetInternalCallbackCount());
+ EXPECT_EQ(0, map.GetInternalSize());
+
+ map.SetInternalFrameData(2, 1);
+ EXPECT_EQ(1, map.GetInternalSize());
+ EXPECT_EQ(0U, results().size());
+
+ // Run some self-contained test. They should not affect the above callbacks.
+ {
+ // Callbacks for invalid IDs should immediately be run because it doesn't
+ // require a thread hop to determine their invalidity.
+ map.GetFrameDataOnIO(-1, MSG_ROUTING_NONE,
+ CreateCallback(-1, MSG_ROUTING_NONE, "invalid IDs"));
+ EXPECT_EQ(3, map.GetInternalCallbackCount()); // No change.
+ EXPECT_EQ(1, map.GetInternalSize()); // No change.
+ ASSERT_EQ(1U, results().size()); // +1
+ EXPECT_EQ("invalid IDs", results()[0]);
+ ClearResults();
+ }
+
+ {
+ // First set the frame ID on UI, then get it on IO. Callback should
+ // immediately be invoked.
+ map.SetInternalFrameData(3, 1);
+ EXPECT_EQ(2, map.GetInternalSize());
+
+ map.GetFrameDataOnIO(3, 1, CreateCallback(3, 1, "the only result"));
+ EXPECT_EQ(3, map.GetInternalCallbackCount()); // No change.
+ EXPECT_EQ(2, map.GetInternalSize()); // +1
+ ASSERT_EQ(1U, results().size()); // +1
+ EXPECT_EQ("the only result", results()[0]);
+ ClearResults();
+ }
+
+ {
+ // Request the frame ID on IO, set the frame ID (in reality, set on the UI),
+ // and request another frame ID. The last query should cause both callbacks
+ // to run because the frame ID is known at the time of the call.
+ map.GetFrameDataOnIO(7, 2, CreateCallback(7, 2, "queued"));
+ EXPECT_EQ(4, map.GetInternalCallbackCount()); // +1
+
+ map.SetInternalFrameData(7, 2);
+ EXPECT_EQ(3, map.GetInternalSize()); // +1
+
+ map.GetFrameDataOnIO(7, 2, CreateCallback(7, 2, "not queued"));
+ EXPECT_EQ(3, map.GetInternalCallbackCount()); // -1 (first callback ran).
+ EXPECT_EQ(3, map.GetInternalSize()); // No change.
+ ASSERT_EQ(2U, results().size()); // +2 (both callbacks ran).
+ EXPECT_EQ("queued", results()[0]);
+ EXPECT_EQ("not queued", results()[1]);
+ ClearResults();
+ }
+
+ // A call identical to the very first call.
+ map.GetFrameDataOnIO(1, 2, CreateCallback(1, 2, "same as first"));
+ EXPECT_EQ(4, map.GetInternalCallbackCount());
+ EXPECT_EQ(3, map.GetInternalSize());
+
+ // Trigger the queued callbacks.
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, map.GetInternalCallbackCount()); // -4 (no queued callbacks).
+
+ EXPECT_EQ(4, map.GetInternalSize()); // +1 (1 new cached frame ID).
+ ASSERT_EQ(4U, results().size()); // +4 (callbacks ran).
+
+ // PostTasks are processed in order, so the very first callbacks should be
+ // processed. As soon as the first callback is available, all of its callbacks
+ // should be run (no deferrals!).
+ EXPECT_EQ("first", results()[0]);
+ EXPECT_EQ("first again", results()[1]);
+ EXPECT_EQ("same as first", results()[2]);
+ // This was queued after "first again", but has a different frame ID, so it
+ // is received after "same as first".
+ EXPECT_EQ("something else", results()[3]);
+ ClearResults();
+
+ // Request the frame ID for input that was already looked up. Should complete
+ // synchronously.
+ map.GetFrameDataOnIO(1, 2, CreateCallback(1, 2, "first and cached"));
+ EXPECT_EQ(0, map.GetInternalCallbackCount()); // No change.
+ EXPECT_EQ(4, map.GetInternalSize()); // No change.
+ ASSERT_EQ(1U, results().size()); // +1 (synchronous callback).
+ EXPECT_EQ("first and cached", results()[0]);
+ ClearResults();
+
+ // Trigger frame removal and look up frame ID. The frame ID should no longer
+ // be available. and GetFrameDataOnIO() should require a thread hop.
+ map.RemoveInternalFrameData(1, 2);
+ EXPECT_EQ(3, map.GetInternalSize()); // -1
+ map.GetFrameDataOnIO(1, 2, CreateCallback(1, 2, "first was removed"));
+ EXPECT_EQ(1, map.GetInternalCallbackCount()); // +1
+ ASSERT_EQ(0U, results().size()); // No change (queued callback).
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, map.GetInternalCallbackCount()); // -1 (callback not in queue).
+ EXPECT_EQ(4, map.GetInternalSize()); // +1 (cached frame ID).
+ ASSERT_EQ(1U, results().size()); // +1 (callback ran).
+ EXPECT_EQ("first was removed", results()[0]);
+}
+
+TEST_F(ExtensionApiFrameIdMapTest, GetCachedFrameDataOnIO) {
+ TestExtensionApiFrameIdMap map;
+ EXPECT_EQ(0, map.GetInternalSize());
+
+ const int kRenderProcessId = 1;
+ const int kFrameRoutingId = 2;
+
+ // Getting cached data on the IO thread should fail if there is no cached
+ // data for the entry...
+ ExtensionApiFrameIdMap::FrameData data;
+ EXPECT_FALSE(
+ map.GetCachedFrameDataOnIO(kRenderProcessId, kFrameRoutingId, &data));
+ // ... should not queue any callbacks...
+ EXPECT_EQ(0, map.GetInternalCallbackCount());
+ base::RunLoop().RunUntilIdle();
+ // ... and should not add any entries to the map.
+ EXPECT_EQ(0, map.GetInternalSize());
+
+ // Getting cached data should succeed if there is a cached entry.
+ map.SetInternalFrameData(kRenderProcessId, kFrameRoutingId);
+ EXPECT_EQ(1, map.GetInternalSize());
+ EXPECT_TRUE(
+ map.GetCachedFrameDataOnIO(kRenderProcessId, kFrameRoutingId, &data));
+ EXPECT_EQ(0, map.GetInternalCallbackCount());
+ EXPECT_EQ(ToTestFrameId(kRenderProcessId, kFrameRoutingId), data.frame_id);
+ EXPECT_EQ(ToTestParentFrameId(kRenderProcessId, kFrameRoutingId),
+ data.parent_frame_id);
+ EXPECT_EQ(ToTestTabId(kRenderProcessId, kFrameRoutingId), data.tab_id);
+ EXPECT_EQ(ToTestWindowId(kRenderProcessId, kFrameRoutingId), data.window_id);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_dialog_auto_confirm.cc b/chromium/extensions/browser/extension_dialog_auto_confirm.cc
new file mode 100644
index 00000000000..e17fa6a74a9
--- /dev/null
+++ b/chromium/extensions/browser/extension_dialog_auto_confirm.cc
@@ -0,0 +1,29 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_dialog_auto_confirm.h"
+
+namespace extensions {
+
+namespace {
+ScopedTestDialogAutoConfirm::AutoConfirm g_extension_dialog_auto_confirm =
+ ScopedTestDialogAutoConfirm::NONE;
+}
+
+ScopedTestDialogAutoConfirm::ScopedTestDialogAutoConfirm(
+ ScopedTestDialogAutoConfirm::AutoConfirm override_value)
+ : old_value_(g_extension_dialog_auto_confirm) {
+ g_extension_dialog_auto_confirm = override_value;
+}
+
+ScopedTestDialogAutoConfirm::~ScopedTestDialogAutoConfirm() {
+ g_extension_dialog_auto_confirm = old_value_;
+}
+
+ScopedTestDialogAutoConfirm::AutoConfirm
+ScopedTestDialogAutoConfirm::GetAutoConfirmValue() {
+ return g_extension_dialog_auto_confirm;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_dialog_auto_confirm.h b/chromium/extensions/browser/extension_dialog_auto_confirm.h
new file mode 100644
index 00000000000..9ae29b08ea5
--- /dev/null
+++ b/chromium/extensions/browser/extension_dialog_auto_confirm.h
@@ -0,0 +1,34 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_DIALOG_AUTO_CONFIRM_H_
+#define EXTENSIONS_BROWSER_EXTENSION_DIALOG_AUTO_CONFIRM_H_
+
+#include "base/auto_reset.h"
+#include "base/macros.h"
+
+namespace extensions {
+
+class ScopedTestDialogAutoConfirm {
+ public:
+ enum AutoConfirm {
+ NONE, // The prompt will show normally.
+ ACCEPT, // The prompt will always accept.
+ CANCEL, // The prompt will always cancel.
+ };
+
+ explicit ScopedTestDialogAutoConfirm(AutoConfirm override_value);
+ ~ScopedTestDialogAutoConfirm();
+
+ static AutoConfirm GetAutoConfirmValue();
+
+ private:
+ AutoConfirm old_value_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedTestDialogAutoConfirm);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_DIALOG_AUTO_CONFIRM_H_
diff --git a/chromium/extensions/browser/extension_error.cc b/chromium/extensions/browser/extension_error.cc
new file mode 100644
index 00000000000..0b8aa386d3b
--- /dev/null
+++ b/chromium/extensions/browser/extension_error.cc
@@ -0,0 +1,193 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_error.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/common/constants.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+////////////////////////////////////////////////////////////////////////////////
+// ExtensionError
+
+ExtensionError::ExtensionError(Type type,
+ const std::string& extension_id,
+ bool from_incognito,
+ logging::LogSeverity level,
+ const base::string16& source,
+ const base::string16& message)
+ : type_(type),
+ extension_id_(extension_id),
+ id_(0),
+ from_incognito_(from_incognito),
+ level_(level),
+ source_(source),
+ message_(message),
+ occurrences_(1u) {
+}
+
+ExtensionError::~ExtensionError() {
+}
+
+std::string ExtensionError::GetDebugString() const {
+ return std::string("Extension Error:") +
+ "\n OTR: " + std::string(from_incognito_ ? "true" : "false") +
+ "\n Level: " + base::IntToString(static_cast<int>(level_)) +
+ "\n Source: " + base::UTF16ToUTF8(source_) +
+ "\n Message: " + base::UTF16ToUTF8(message_) +
+ "\n ID: " + extension_id_;
+}
+
+bool ExtensionError::IsEqual(const ExtensionError* rhs) const {
+ // We don't check |source_| or |level_| here, since they are constant for
+ // manifest errors. Check them in RuntimeError::IsEqualImpl() instead.
+ return type_ == rhs->type_ &&
+ extension_id_ == rhs->extension_id_ &&
+ message_ == rhs->message_ &&
+ IsEqualImpl(rhs);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ManifestError
+
+ManifestError::ManifestError(const std::string& extension_id,
+ const base::string16& message,
+ const base::string16& manifest_key,
+ const base::string16& manifest_specific)
+ : ExtensionError(ExtensionError::MANIFEST_ERROR,
+ extension_id,
+ false, // extensions can't be installed while incognito.
+ logging::LOG_WARNING, // All manifest errors are warnings.
+ base::FilePath(kManifestFilename).AsUTF16Unsafe(),
+ message),
+ manifest_key_(manifest_key),
+ manifest_specific_(manifest_specific) {
+}
+
+ManifestError::~ManifestError() {
+}
+
+std::string ManifestError::GetDebugString() const {
+ return ExtensionError::GetDebugString() +
+ "\n Type: ManifestError";
+}
+
+bool ManifestError::IsEqualImpl(const ExtensionError* rhs) const {
+ // If two manifest errors have the same extension id and message (which are
+ // both checked in ExtensionError::IsEqual), then they are equal.
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RuntimeError
+
+RuntimeError::RuntimeError(const std::string& extension_id,
+ bool from_incognito,
+ const base::string16& source,
+ const base::string16& message,
+ const StackTrace& stack_trace,
+ const GURL& context_url,
+ logging::LogSeverity level,
+ int render_frame_id,
+ int render_process_id)
+ : ExtensionError(ExtensionError::RUNTIME_ERROR,
+ !extension_id.empty() ? extension_id : GURL(source).host(),
+ from_incognito,
+ level,
+ source,
+ message),
+ context_url_(context_url),
+ stack_trace_(stack_trace),
+ render_frame_id_(render_frame_id),
+ render_process_id_(render_process_id) {
+ CleanUpInit();
+}
+
+RuntimeError::~RuntimeError() {
+}
+
+std::string RuntimeError::GetDebugString() const {
+ std::string result = ExtensionError::GetDebugString() +
+ "\n Type: RuntimeError"
+ "\n Context: " + context_url_.spec() +
+ "\n Stack Trace: ";
+ for (StackTrace::const_iterator iter = stack_trace_.begin();
+ iter != stack_trace_.end(); ++iter) {
+ // The "NL" comments are to force clang-format to choose the right layout.
+ result += "\n {";
+ result +=
+ "\n Line: " + base::SizeTToString(iter->line_number) + // NL
+ "\n Column: " + base::SizeTToString(iter->column_number) + // NL
+ "\n URL: " + base::UTF16ToUTF8(iter->source) + // NL
+ "\n Function: " + base::UTF16ToUTF8(iter->function) + // NL
+ "\n }"; // NL
+ }
+ return result;
+}
+
+bool RuntimeError::IsEqualImpl(const ExtensionError* rhs) const {
+ const RuntimeError* error = static_cast<const RuntimeError*>(rhs);
+
+ // Only look at the first frame of a stack trace to save time and group
+ // nearly-identical errors. The most recent error is kept, so there's no risk
+ // of displaying an old and inaccurate stack trace.
+ return level_ == error->level_ &&
+ source_ == error->source_ &&
+ context_url_ == error->context_url_ &&
+ stack_trace_.size() == error->stack_trace_.size() &&
+ (stack_trace_.empty() || stack_trace_[0] == error->stack_trace_[0]);
+}
+
+void RuntimeError::CleanUpInit() {
+ // If the error came from a generated background page, the "context" is empty
+ // because there's no visible URL. We should set context to be the generated
+ // background page in this case.
+ GURL source_url = GURL(source_);
+ if (context_url_.is_empty() &&
+ source_url.path() ==
+ std::string("/") + kGeneratedBackgroundPageFilename) {
+ context_url_ = source_url;
+ }
+
+ // In some instances (due to the fact that we're reusing error reporting from
+ // other systems), the source won't match up with the final entry in the stack
+ // trace. (For instance, in a browser action error, the source is the page -
+ // sometimes the background page - but the error is thrown from the script.)
+ // Make the source match the stack trace, since that is more likely the cause
+ // of the error.
+ if (!stack_trace_.empty() && source_ != stack_trace_[0].source)
+ source_ = stack_trace_[0].source;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// InternalError
+
+InternalError::InternalError(const std::string& extension_id,
+ const base::string16& message,
+ logging::LogSeverity level)
+ : ExtensionError(ExtensionError::INTERNAL_ERROR,
+ extension_id,
+ false, // not incognito.
+ level,
+ base::string16(),
+ message) {
+}
+
+InternalError::~InternalError() {
+}
+
+std::string InternalError::GetDebugString() const {
+ return ExtensionError::GetDebugString() +
+ "\n Type: InternalError";
+}
+
+bool InternalError::IsEqualImpl(const ExtensionError* rhs) const {
+ // ExtensionError logic is sufficient for comparison.
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_error.h b/chromium/extensions/browser/extension_error.h
new file mode 100644
index 00000000000..7caa95cddd5
--- /dev/null
+++ b/chromium/extensions/browser/extension_error.h
@@ -0,0 +1,165 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_ERROR_H_
+#define EXTENSIONS_BROWSER_EXTENSION_ERROR_H_
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "extensions/common/stack_frame.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+class ExtensionError {
+ public:
+ enum Type {
+ MANIFEST_ERROR = 0,
+ RUNTIME_ERROR,
+ INTERNAL_ERROR,
+ NUM_ERROR_TYPES, // Put new values above this.
+ };
+
+ virtual ~ExtensionError();
+
+ virtual std::string GetDebugString() const;
+
+ // Return true if this error and |rhs| are considered equal, and should be
+ // grouped together.
+ bool IsEqual(const ExtensionError* rhs) const;
+
+ Type type() const { return type_; }
+ const std::string& extension_id() const { return extension_id_; }
+ int id() const { return id_; }
+ void set_id(int id) { id_ = id; }
+ bool from_incognito() const { return from_incognito_; }
+ logging::LogSeverity level() const { return level_; }
+ const base::string16& source() const { return source_; }
+ const base::string16& message() const { return message_; }
+ size_t occurrences() const { return occurrences_; }
+ void set_occurrences(size_t occurrences) { occurrences_ = occurrences; }
+
+ protected:
+ ExtensionError(Type type,
+ const std::string& extension_id,
+ bool from_incognito,
+ logging::LogSeverity level,
+ const base::string16& source,
+ const base::string16& message);
+
+ virtual bool IsEqualImpl(const ExtensionError* rhs) const = 0;
+
+ // Which type of error this is.
+ Type type_;
+ // The ID of the extension which caused the error.
+ std::string extension_id_;
+ // The id of this particular error. This can be zero if the id is never set.
+ int id_;
+ // Whether or not the error was caused while incognito.
+ bool from_incognito_;
+ // The severity level of the error.
+ logging::LogSeverity level_;
+ // The source for the error; this can be a script, web page, or manifest file.
+ // This is stored as a string (rather than a url) since it can be a Chrome
+ // script file (e.g., event_bindings.js).
+ base::string16 source_;
+ // The error message itself.
+ base::string16 message_;
+ // The number of times this error has occurred.
+ size_t occurrences_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ExtensionError);
+};
+
+class ManifestError : public ExtensionError {
+ public:
+ ManifestError(const std::string& extension_id,
+ const base::string16& message,
+ const base::string16& manifest_key,
+ const base::string16& manifest_specific);
+ ~ManifestError() override;
+
+ std::string GetDebugString() const override;
+
+ const base::string16& manifest_key() const { return manifest_key_; }
+ const base::string16& manifest_specific() const { return manifest_specific_; }
+
+ private:
+ bool IsEqualImpl(const ExtensionError* rhs) const override;
+
+ // If present, this indicates the feature in the manifest which caused the
+ // error.
+ base::string16 manifest_key_;
+ // If present, this is a more-specific location of the error - for instance,
+ // a specific permission which is incorrect, rather than simply "permissions".
+ base::string16 manifest_specific_;
+
+ DISALLOW_COPY_AND_ASSIGN(ManifestError);
+};
+
+class RuntimeError : public ExtensionError {
+ public:
+ RuntimeError(const std::string& extension_id, // optional, sometimes unknown.
+ bool from_incognito,
+ const base::string16& source,
+ const base::string16& message,
+ const StackTrace& stack_trace,
+ const GURL& context_url,
+ logging::LogSeverity level,
+ int render_view_id,
+ int render_process_id);
+ ~RuntimeError() override;
+
+ std::string GetDebugString() const override;
+
+ const GURL& context_url() const { return context_url_; }
+ const StackTrace& stack_trace() const { return stack_trace_; }
+ int render_frame_id() const { return render_frame_id_; }
+ int render_process_id() const { return render_process_id_; }
+
+ private:
+ bool IsEqualImpl(const ExtensionError* rhs) const override;
+
+ // Since we piggy-back onto other error reporting systems (like V8 and
+ // WebKit), the reported information may need to be cleaned up in order to be
+ // in a consistent format.
+ void CleanUpInit();
+
+ GURL context_url_;
+ StackTrace stack_trace_;
+
+ // Keep track of the render process which caused the error in order to
+ // inspect the frame later, if possible.
+ int render_frame_id_;
+ int render_process_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(RuntimeError);
+};
+
+class InternalError : public ExtensionError {
+ public:
+ InternalError(const std::string& extension_id,
+ const base::string16& message,
+ logging::LogSeverity level);
+ ~InternalError() override;
+
+ std::string GetDebugString() const override;
+
+ private:
+ bool IsEqualImpl(const ExtensionError* rhs) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(InternalError);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_ERROR_H_
diff --git a/chromium/extensions/browser/extension_error_test_util.cc b/chromium/extensions/browser/extension_error_test_util.cc
new file mode 100644
index 00000000000..0396d8604aa
--- /dev/null
+++ b/chromium/extensions/browser/extension_error_test_util.cc
@@ -0,0 +1,62 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_error_test_util.h"
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/common/url_constants.h"
+#include "extensions/browser/extension_error.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/stack_frame.h"
+#include "url/gurl.h"
+
+namespace extensions {
+namespace error_test_util {
+
+namespace {
+const char kDefaultStackTrace[] = "function_name (https://url.com:1:1)";
+}
+
+scoped_ptr<ExtensionError> CreateNewRuntimeError(
+ const std::string& extension_id,
+ const std::string& message,
+ bool from_incognito) {
+ StackTrace stack_trace;
+ scoped_ptr<StackFrame> frame =
+ StackFrame::CreateFromText(base::ASCIIToUTF16(kDefaultStackTrace));
+ CHECK(frame.get());
+ stack_trace.push_back(*frame);
+
+ base::string16 source =
+ base::UTF8ToUTF16(std::string(kExtensionScheme) +
+ url::kStandardSchemeSeparator +
+ extension_id);
+
+ return scoped_ptr<ExtensionError>(
+ new RuntimeError(extension_id, from_incognito, source,
+ base::UTF8ToUTF16(message), stack_trace,
+ GURL::EmptyGURL(), // no context url
+ logging::LOG_INFO,
+ 0, // Render frame id
+ 0)); // Render process id
+}
+
+scoped_ptr<ExtensionError> CreateNewRuntimeError(
+ const std::string& extension_id, const std::string& message) {
+ return CreateNewRuntimeError(extension_id, message, false);
+}
+
+scoped_ptr<ExtensionError> CreateNewManifestError(
+ const std::string& extension_id, const std::string& message) {
+ return scoped_ptr<ExtensionError>(
+ new ManifestError(extension_id,
+ base::UTF8ToUTF16(message),
+ base::string16(),
+ base::string16()));
+}
+
+} // namespace error_test_util
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_error_test_util.h b/chromium/extensions/browser/extension_error_test_util.h
new file mode 100644
index 00000000000..6ea2da65d44
--- /dev/null
+++ b/chromium/extensions/browser/extension_error_test_util.h
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_ERROR_TEST_UTIL_H_
+#define EXTENSIONS_BROWSER_EXTENSION_ERROR_TEST_UTIL_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+
+namespace extensions {
+
+class ExtensionError;
+
+namespace error_test_util {
+
+// Create a new RuntimeError.
+scoped_ptr<ExtensionError> CreateNewRuntimeError(
+ const std::string& extension_id,
+ const std::string& message,
+ bool from_incognito);
+
+// Create a new RuntimeError; incognito defaults to "false".
+scoped_ptr<ExtensionError> CreateNewRuntimeError(
+ const std::string& extension_id, const std::string& message);
+
+// Create a new ManifestError.
+scoped_ptr<ExtensionError> CreateNewManifestError(
+ const std::string& extension_id,
+ const std::string& message);
+
+} // namespace error_test_util
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_ERROR_TEST_UTIL_H_
diff --git a/chromium/extensions/browser/extension_event_histogram_value.h b/chromium/extensions/browser/extension_event_histogram_value.h
new file mode 100644
index 00000000000..479ce7db2f4
--- /dev/null
+++ b/chromium/extensions/browser/extension_event_histogram_value.h
@@ -0,0 +1,426 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_EVENT_HISTOGRAM_VALUE_H_
+#define EXTENSIONS_BROWSER_EXTENSION_EVENT_HISTOGRAM_VALUE_H_
+
+namespace extensions {
+namespace events {
+
+// TODO(kalman): I am still in the process of migrating Event construction away
+// from using "UNKNOWN" to their real histogram values. See crbug.com/503402.
+//
+// Short version:
+// *Never* reorder or delete entries in the |HistogramValue| enumeration.
+// When creating a new extension event, add a new entry at the end of the
+// enum, just prior to ENUM_BOUNDARY.
+//
+// Long version: See extension_function_histogram_value.h
+enum HistogramValue {
+ UNKNOWN = 0,
+ FOR_TEST, // Tests should use this for a stub histogram value (not UNKNOWN).
+ ACCESSIBILITY_PRIVATE_ON_INTRODUCE_CHROME_VOX,
+ ACTIVITY_LOG_PRIVATE_ON_EXTENSION_ACTIVITY,
+ ALARMS_ON_ALARM,
+ APP_CURRENT_WINDOW_INTERNAL_ON_ALPHA_ENABLED_CHANGED,
+ APP_CURRENT_WINDOW_INTERNAL_ON_BOUNDS_CHANGED,
+ APP_CURRENT_WINDOW_INTERNAL_ON_CLOSED,
+ APP_CURRENT_WINDOW_INTERNAL_ON_FULLSCREENED,
+ APP_CURRENT_WINDOW_INTERNAL_ON_MAXIMIZED,
+ APP_CURRENT_WINDOW_INTERNAL_ON_MINIMIZED,
+ APP_CURRENT_WINDOW_INTERNAL_ON_RESTORED,
+ APP_CURRENT_WINDOW_INTERNAL_ON_WINDOW_SHOWN_FOR_TESTS,
+ APP_RUNTIME_ON_EMBED_REQUESTED,
+ APP_RUNTIME_ON_LAUNCHED,
+ APP_RUNTIME_ON_RESTARTED,
+ APP_WINDOW_ON_BOUNDS_CHANGED,
+ APP_WINDOW_ON_CLOSED,
+ APP_WINDOW_ON_FULLSCREENED,
+ APP_WINDOW_ON_MAXIMIZED,
+ APP_WINDOW_ON_MINIMIZED,
+ APP_WINDOW_ON_RESTORED,
+ AUDIO_MODEM_ON_RECEIVED,
+ AUDIO_MODEM_ON_TRANSMIT_FAIL,
+ AUDIO_ON_DEVICE_CHANGED,
+ AUDIO_ON_DEVICES_CHANGED,
+ AUDIO_ON_LEVEL_CHANGED,
+ AUDIO_ON_MUTE_CHANGED,
+ AUTOFILL_PRIVATE_ON_ADDRESS_LIST_CHANGED,
+ AUTOFILL_PRIVATE_ON_CREDIT_CARD_LIST_CHANGED,
+ AUTOMATION_INTERNAL_ON_ACCESSIBILITY_EVENT,
+ AUTOMATION_INTERNAL_ON_ACCESSIBILITY_TREE_DESTROYED,
+ BLUETOOTH_LOW_ENERGY_ON_CHARACTERISTIC_VALUE_CHANGED,
+ BLUETOOTH_LOW_ENERGY_ON_DESCRIPTOR_VALUE_CHANGED,
+ BLUETOOTH_LOW_ENERGY_ON_SERVICE_ADDED,
+ BLUETOOTH_LOW_ENERGY_ON_SERVICE_CHANGED,
+ BLUETOOTH_LOW_ENERGY_ON_SERVICE_REMOVED,
+ BLUETOOTH_ON_ADAPTER_STATE_CHANGED,
+ BLUETOOTH_ON_DEVICE_ADDED,
+ BLUETOOTH_ON_DEVICE_CHANGED,
+ BLUETOOTH_ON_DEVICE_REMOVED,
+ BLUETOOTH_PRIVATE_ON_PAIRING,
+ BLUETOOTH_SOCKET_ON_ACCEPT,
+ BLUETOOTH_SOCKET_ON_ACCEPT_ERROR,
+ BLUETOOTH_SOCKET_ON_RECEIVE,
+ BLUETOOTH_SOCKET_ON_RECEIVE_ERROR,
+ BOOKMARK_MANAGER_PRIVATE_ON_DRAG_ENTER,
+ BOOKMARK_MANAGER_PRIVATE_ON_DRAG_LEAVE,
+ BOOKMARK_MANAGER_PRIVATE_ON_DROP,
+ BOOKMARK_MANAGER_PRIVATE_ON_META_INFO_CHANGED,
+ BOOKMARKS_ON_CHANGED,
+ BOOKMARKS_ON_CHILDREN_REORDERED,
+ BOOKMARKS_ON_CREATED,
+ BOOKMARKS_ON_IMPORT_BEGAN,
+ BOOKMARKS_ON_IMPORT_ENDED,
+ BOOKMARKS_ON_MOVED,
+ BOOKMARKS_ON_REMOVED,
+ BRAILLE_DISPLAY_PRIVATE_ON_DISPLAY_STATE_CHANGED,
+ BRAILLE_DISPLAY_PRIVATE_ON_KEY_EVENT,
+ BROWSER_ACTION_ON_CLICKED,
+ CAST_STREAMING_RTP_STREAM_ON_ERROR,
+ CAST_STREAMING_RTP_STREAM_ON_STARTED,
+ CAST_STREAMING_RTP_STREAM_ON_STOPPED,
+ COMMANDS_ON_COMMAND,
+ CONTEXT_MENUS_INTERNAL_ON_CLICKED,
+ CONTEXT_MENUS_ON_CLICKED,
+ COOKIES_ON_CHANGED,
+ COPRESENCE_ON_MESSAGES_RECEIVED,
+ COPRESENCE_ON_STATUS_UPDATED,
+ COPRESENCE_PRIVATE_ON_CONFIG_AUDIO,
+ COPRESENCE_PRIVATE_ON_DECODE_SAMPLES_REQUEST,
+ COPRESENCE_PRIVATE_ON_ENCODE_TOKEN_REQUEST,
+ DEBUGGER_ON_DETACH,
+ DEBUGGER_ON_EVENT,
+ DECLARATIVE_CONTENT_ON_PAGE_CHANGED,
+ DECLARATIVE_WEB_REQUEST_ON_MESSAGE,
+ DECLARATIVE_WEB_REQUEST_ON_REQUEST,
+ DEVELOPER_PRIVATE_ON_ITEM_STATE_CHANGED,
+ DEVELOPER_PRIVATE_ON_PROFILE_STATE_CHANGED,
+ DEVTOOLS_INSPECTED_WINDOW_ON_RESOURCE_ADDED,
+ DEVTOOLS_INSPECTED_WINDOW_ON_RESOURCE_CONTENT_COMMITTED,
+ DEVTOOLS_NETWORK_ON_NAVIGATED,
+ DEVTOOLS_NETWORK_ON_REQUEST_FINISHED,
+ DOWNLOADS_ON_CHANGED,
+ DOWNLOADS_ON_CREATED,
+ DOWNLOADS_ON_DETERMINING_FILENAME,
+ DOWNLOADS_ON_ERASED,
+ EASY_UNLOCK_PRIVATE_ON_START_AUTO_PAIRING,
+ EASY_UNLOCK_PRIVATE_ON_USER_INFO_UPDATED,
+ EXPERIENCE_SAMPLING_PRIVATE_ON_DECISION,
+ EXPERIENCE_SAMPLING_PRIVATE_ON_DISPLAYED,
+ EXPERIMENTAL_DEVTOOLS_CONSOLE_ON_MESSAGE_ADDED,
+ EXTENSION_ON_REQUEST,
+ EXTENSION_ON_REQUEST_EXTERNAL,
+ EXTENSION_OPTIONS_INTERNAL_ON_CLOSE,
+ EXTENSION_OPTIONS_INTERNAL_ON_LOAD,
+ EXTENSION_OPTIONS_INTERNAL_ON_PREFERRED_SIZE_CHANGED,
+ FEEDBACK_PRIVATE_ON_FEEDBACK_REQUESTED,
+ FILE_BROWSER_HANDLER_ON_EXECUTE,
+ FILE_MANAGER_PRIVATE_ON_COPY_PROGRESS,
+ FILE_MANAGER_PRIVATE_ON_DEVICE_CHANGED,
+ FILE_MANAGER_PRIVATE_ON_DIRECTORY_CHANGED,
+ FILE_MANAGER_PRIVATE_ON_DRIVE_CONNECTION_STATUS_CHANGED,
+ FILE_MANAGER_PRIVATE_ON_DRIVE_SYNC_ERROR,
+ FILE_MANAGER_PRIVATE_ON_FILE_TRANSFERS_UPDATED,
+ FILE_MANAGER_PRIVATE_ON_MOUNT_COMPLETED,
+ FILE_MANAGER_PRIVATE_ON_PREFERENCES_CHANGED,
+ FILE_SYSTEM_ON_VOLUME_LIST_CHANGED,
+ FILE_SYSTEM_PROVIDER_ON_ABORT_REQUESTED,
+ FILE_SYSTEM_PROVIDER_ON_ADD_WATCHER_REQUESTED,
+ FILE_SYSTEM_PROVIDER_ON_CLOSE_FILE_REQUESTED,
+ FILE_SYSTEM_PROVIDER_ON_CONFIGURE_REQUESTED,
+ FILE_SYSTEM_PROVIDER_ON_COPY_ENTRY_REQUESTED,
+ FILE_SYSTEM_PROVIDER_ON_CREATE_DIRECTORY_REQUESTED,
+ FILE_SYSTEM_PROVIDER_ON_CREATE_FILE_REQUESTED,
+ FILE_SYSTEM_PROVIDER_ON_DELETE_ENTRY_REQUESTED,
+ FILE_SYSTEM_PROVIDER_ON_GET_METADATA_REQUESTED,
+ FILE_SYSTEM_PROVIDER_ON_MOUNT_REQUESTED,
+ FILE_SYSTEM_PROVIDER_ON_MOVE_ENTRY_REQUESTED,
+ FILE_SYSTEM_PROVIDER_ON_OPEN_FILE_REQUESTED,
+ FILE_SYSTEM_PROVIDER_ON_READ_DIRECTORY_REQUESTED,
+ FILE_SYSTEM_PROVIDER_ON_READ_FILE_REQUESTED,
+ FILE_SYSTEM_PROVIDER_ON_REMOVE_WATCHER_REQUESTED,
+ FILE_SYSTEM_PROVIDER_ON_TRUNCATE_REQUESTED,
+ FILE_SYSTEM_PROVIDER_ON_UNMOUNT_REQUESTED,
+ FILE_SYSTEM_PROVIDER_ON_WRITE_FILE_REQUESTED,
+ FONT_SETTINGS_ON_DEFAULT_FIXED_FONT_SIZE_CHANGED,
+ FONT_SETTINGS_ON_DEFAULT_FONT_SIZE_CHANGED,
+ FONT_SETTINGS_ON_FONT_CHANGED,
+ FONT_SETTINGS_ON_MINIMUM_FONT_SIZE_CHANGED,
+ GCD_PRIVATE_ON_DEVICE_REMOVED,
+ GCD_PRIVATE_ON_DEVICE_STATE_CHANGED,
+ GCM_ON_MESSAGE,
+ GCM_ON_MESSAGES_DELETED,
+ GCM_ON_SEND_ERROR,
+ HANGOUTS_PRIVATE_ON_HANGOUT_REQUESTED,
+ HID_ON_DEVICE_ADDED,
+ HID_ON_DEVICE_REMOVED,
+ HISTORY_ON_VISITED,
+ HISTORY_ON_VISIT_REMOVED,
+ HOTWORD_PRIVATE_ON_DELETE_SPEAKER_MODEL,
+ HOTWORD_PRIVATE_ON_ENABLED_CHANGED,
+ HOTWORD_PRIVATE_ON_FINALIZE_SPEAKER_MODEL,
+ HOTWORD_PRIVATE_ON_HOTWORD_SESSION_REQUESTED,
+ HOTWORD_PRIVATE_ON_HOTWORD_SESSION_STOPPED,
+ HOTWORD_PRIVATE_ON_HOTWORD_TRIGGERED,
+ HOTWORD_PRIVATE_ON_MICROPHONE_STATE_CHANGED,
+ HOTWORD_PRIVATE_ON_SPEAKER_MODEL_EXISTS,
+ HOTWORD_PRIVATE_ON_SPEAKER_MODEL_SAVED,
+ IDENTITY_ON_SIGN_IN_CHANGED,
+ IDENTITY_PRIVATE_ON_WEB_FLOW_REQUEST,
+ IDLE_ON_STATE_CHANGED,
+ IMAGE_WRITER_PRIVATE_ON_DEVICE_INSERTED,
+ IMAGE_WRITER_PRIVATE_ON_DEVICE_REMOVED,
+ IMAGE_WRITER_PRIVATE_ON_WRITE_COMPLETE,
+ IMAGE_WRITER_PRIVATE_ON_WRITE_ERROR,
+ IMAGE_WRITER_PRIVATE_ON_WRITE_PROGRESS,
+ INPUT_IME_ON_ACTIVATE,
+ INPUT_IME_ON_BLUR,
+ INPUT_IME_ON_CANDIDATE_CLICKED,
+ INPUT_IME_ON_DEACTIVATED,
+ INPUT_IME_ON_FOCUS,
+ INPUT_IME_ON_INPUT_CONTEXT_UPDATE,
+ INPUT_IME_ON_KEY_EVENT,
+ INPUT_IME_ON_MENU_ITEM_ACTIVATED,
+ INPUT_IME_ON_RESET,
+ INPUT_IME_ON_SURROUNDING_TEXT_CHANGED,
+ INPUT_METHOD_PRIVATE_ON_CHANGED,
+ INPUT_METHOD_PRIVATE_ON_COMPOSITION_BOUNDS_CHANGED,
+ INPUT_METHOD_PRIVATE_ON_DICTIONARY_CHANGED,
+ INPUT_METHOD_PRIVATE_ON_DICTIONARY_LOADED,
+ INSTANCE_ID_ON_TOKEN_REFRESH,
+ DELETED_LOCATION_ON_LOCATION_ERROR,
+ DELETED_LOCATION_ON_LOCATION_UPDATE,
+ LOG_PRIVATE_ON_CAPTURED_EVENTS,
+ MANAGEMENT_ON_DISABLED,
+ MANAGEMENT_ON_ENABLED,
+ MANAGEMENT_ON_INSTALLED,
+ MANAGEMENT_ON_UNINSTALLED,
+ MDNS_ON_SERVICE_LIST,
+ MEDIA_GALLERIES_ON_GALLERY_CHANGED,
+ MEDIA_GALLERIES_ON_SCAN_PROGRESS,
+ MEDIA_PLAYER_PRIVATE_ON_NEXT_TRACK,
+ MEDIA_PLAYER_PRIVATE_ON_PREV_TRACK,
+ MEDIA_PLAYER_PRIVATE_ON_TOGGLE_PLAY_STATE,
+ NETWORKING_CONFIG_ON_CAPTIVE_PORTAL_DETECTED,
+ NETWORKING_PRIVATE_ON_DEVICE_STATE_LIST_CHANGED,
+ NETWORKING_PRIVATE_ON_NETWORK_LIST_CHANGED,
+ NETWORKING_PRIVATE_ON_NETWORKS_CHANGED,
+ NETWORKING_PRIVATE_ON_PORTAL_DETECTION_COMPLETED,
+ NOTIFICATION_PROVIDER_ON_CLEARED,
+ NOTIFICATION_PROVIDER_ON_CREATED,
+ NOTIFICATION_PROVIDER_ON_UPDATED,
+ NOTIFICATIONS_ON_BUTTON_CLICKED,
+ NOTIFICATIONS_ON_CLICKED,
+ NOTIFICATIONS_ON_CLOSED,
+ NOTIFICATIONS_ON_PERMISSION_LEVEL_CHANGED,
+ NOTIFICATIONS_ON_SHOW_SETTINGS,
+ OMNIBOX_ON_INPUT_CANCELLED,
+ OMNIBOX_ON_INPUT_CHANGED,
+ OMNIBOX_ON_INPUT_ENTERED,
+ OMNIBOX_ON_INPUT_STARTED,
+ PAGE_ACTION_ON_CLICKED,
+ PASSWORDS_PRIVATE_ON_PASSWORD_EXCEPTIONS_LIST_CHANGED,
+ PASSWORDS_PRIVATE_ON_PLAINTEXT_PASSWORD_RETRIEVED,
+ PASSWORDS_PRIVATE_ON_SAVED_PASSWORDS_LIST_CHANGED,
+ PERMISSIONS_ON_ADDED,
+ PERMISSIONS_ON_REMOVED,
+ PRINTER_PROVIDER_ON_GET_CAPABILITY_REQUESTED,
+ PRINTER_PROVIDER_ON_GET_PRINTERS_REQUESTED,
+ PRINTER_PROVIDER_ON_GET_USB_PRINTER_INFO_REQUESTED,
+ PRINTER_PROVIDER_ON_PRINT_REQUESTED,
+ PROCESSES_ON_CREATED,
+ PROCESSES_ON_EXITED,
+ PROCESSES_ON_UNRESPONSIVE,
+ PROCESSES_ON_UPDATED,
+ PROCESSES_ON_UPDATED_WITH_MEMORY,
+ PROXY_ON_PROXY_ERROR,
+ RUNTIME_ON_BROWSER_UPDATE_AVAILABLE,
+ RUNTIME_ON_CONNECT,
+ RUNTIME_ON_CONNECT_EXTERNAL,
+ RUNTIME_ON_INSTALLED,
+ RUNTIME_ON_MESSAGE,
+ RUNTIME_ON_MESSAGE_EXTERNAL,
+ RUNTIME_ON_RESTART_REQUIRED,
+ RUNTIME_ON_STARTUP,
+ RUNTIME_ON_SUSPEND,
+ RUNTIME_ON_SUSPEND_CANCELED,
+ RUNTIME_ON_UPDATE_AVAILABLE,
+ SEARCH_ENGINES_PRIVATE_ON_SEARCH_ENGINES_CHANGED,
+ SERIAL_ON_RECEIVE,
+ SERIAL_ON_RECEIVE_ERROR,
+ SESSIONS_ON_CHANGED,
+ SETTINGS_PRIVATE_ON_PREFS_CHANGED,
+ SIGNED_IN_DEVICES_ON_DEVICE_INFO_CHANGE,
+ SOCKETS_TCP_ON_RECEIVE,
+ SOCKETS_TCP_ON_RECEIVE_ERROR,
+ SOCKETS_TCP_SERVER_ON_ACCEPT,
+ SOCKETS_TCP_SERVER_ON_ACCEPT_ERROR,
+ SOCKETS_UDP_ON_RECEIVE,
+ SOCKETS_UDP_ON_RECEIVE_ERROR,
+ STORAGE_ON_CHANGED,
+ STREAMS_PRIVATE_ON_EXECUTE_MIME_TYPE_HANDLER,
+ SYNC_FILE_SYSTEM_ON_FILE_STATUS_CHANGED,
+ SYNC_FILE_SYSTEM_ON_SERVICE_STATUS_CHANGED,
+ SYSTEM_DISPLAY_ON_DISPLAY_CHANGED,
+ SYSTEM_INDICATOR_ON_CLICKED,
+ SYSTEM_PRIVATE_ON_BRIGHTNESS_CHANGED,
+ SYSTEM_PRIVATE_ON_SCREEN_UNLOCKED,
+ SYSTEM_PRIVATE_ON_VOLUME_CHANGED,
+ SYSTEM_PRIVATE_ON_WOKE_UP,
+ SYSTEM_STORAGE_ON_ATTACHED,
+ SYSTEM_STORAGE_ON_DETACHED,
+ TAB_CAPTURE_ON_STATUS_CHANGED,
+ TABS_ON_ACTIVATED,
+ TABS_ON_ACTIVE_CHANGED,
+ TABS_ON_ATTACHED,
+ TABS_ON_CREATED,
+ TABS_ON_DETACHED,
+ TABS_ON_HIGHLIGHT_CHANGED,
+ TABS_ON_HIGHLIGHTED,
+ TABS_ON_MOVED,
+ TABS_ON_REMOVED,
+ TABS_ON_REPLACED,
+ TABS_ON_SELECTION_CHANGED,
+ TABS_ON_UPDATED,
+ TABS_ON_ZOOM_CHANGE,
+ TERMINAL_PRIVATE_ON_PROCESS_OUTPUT,
+ TEST_ON_MESSAGE,
+ TTS_ENGINE_ON_PAUSE,
+ TTS_ENGINE_ON_RESUME,
+ TTS_ENGINE_ON_SPEAK,
+ TTS_ENGINE_ON_STOP,
+ USB_ON_DEVICE_ADDED,
+ USB_ON_DEVICE_REMOVED,
+ VIRTUAL_KEYBOARD_PRIVATE_ON_BOUNDS_CHANGED,
+ VIRTUAL_KEYBOARD_PRIVATE_ON_TEXT_INPUT_BOX_FOCUSED,
+ VPN_PROVIDER_ON_CONFIG_CREATED,
+ VPN_PROVIDER_ON_CONFIG_REMOVED,
+ VPN_PROVIDER_ON_PACKET_RECEIVED,
+ VPN_PROVIDER_ON_PLATFORM_MESSAGE,
+ VPN_PROVIDER_ON_UI_EVENT,
+ WALLPAPER_PRIVATE_ON_WALLPAPER_CHANGED_BY_3RD_PARTY,
+ WEB_NAVIGATION_ON_BEFORE_NAVIGATE,
+ WEB_NAVIGATION_ON_COMMITTED,
+ WEB_NAVIGATION_ON_COMPLETED,
+ WEB_NAVIGATION_ON_CREATED_NAVIGATION_TARGET,
+ WEB_NAVIGATION_ON_DOM_CONTENT_LOADED,
+ WEB_NAVIGATION_ON_ERROR_OCCURRED,
+ WEB_NAVIGATION_ON_HISTORY_STATE_UPDATED,
+ WEB_NAVIGATION_ON_REFERENCE_FRAGMENT_UPDATED,
+ WEB_NAVIGATION_ON_TAB_REPLACED,
+ WEB_REQUEST_ON_AUTH_REQUIRED,
+ WEB_REQUEST_ON_BEFORE_REDIRECT,
+ WEB_REQUEST_ON_BEFORE_REQUEST,
+ WEB_REQUEST_ON_BEFORE_SEND_HEADERS,
+ WEB_REQUEST_ON_COMPLETED,
+ WEB_REQUEST_ON_ERROR_OCCURRED,
+ WEB_REQUEST_ON_HEADERS_RECEIVED,
+ WEB_REQUEST_ON_RESPONSE_STARTED,
+ WEB_REQUEST_ON_SEND_HEADERS,
+ WEBRTC_AUDIO_PRIVATE_ON_SINKS_CHANGED,
+ WEBSTORE_ON_DOWNLOAD_PROGRESS,
+ WEBSTORE_ON_INSTALL_STAGE_CHANGED,
+ WEBSTORE_WIDGET_PRIVATE_ON_SHOW_WIDGET,
+ WEBVIEW_TAG_CLOSE,
+ WEBVIEW_TAG_CONSOLEMESSAGE,
+ WEBVIEW_TAG_CONTENTLOAD,
+ WEBVIEW_TAG_DIALOG,
+ WEBVIEW_TAG_EXIT,
+ WEBVIEW_TAG_FINDUPDATE,
+ WEBVIEW_TAG_LOADABORT,
+ WEBVIEW_TAG_LOADCOMMIT,
+ WEBVIEW_TAG_LOADREDIRECT,
+ WEBVIEW_TAG_LOADSTART,
+ WEBVIEW_TAG_LOADSTOP,
+ WEBVIEW_TAG_NEWWINDOW,
+ WEBVIEW_TAG_PERMISSIONREQUEST,
+ WEBVIEW_TAG_RESPONSIVE,
+ WEBVIEW_TAG_SIZECHANGED,
+ WEBVIEW_TAG_UNRESPONSIVE,
+ WEBVIEW_TAG_ZOOMCHANGE,
+ WINDOWS_ON_CREATED,
+ WINDOWS_ON_FOCUS_CHANGED,
+ WINDOWS_ON_REMOVED,
+ FILE_SYSTEM_PROVIDER_ON_EXECUTE_ACTION_REQUESTED,
+ FILE_SYSTEM_PROVIDER_ON_GET_ACTIONS_REQUESTED,
+ LAUNCHER_SEARCH_PROVIDER_ON_QUERY_STARTED,
+ LAUNCHER_SEARCH_PROVIDER_ON_QUERY_ENDED,
+ LAUNCHER_SEARCH_PROVIDER_ON_OPEN_RESULT,
+ CHROME_WEB_VIEW_INTERNAL_ON_CLICKED,
+ WEB_VIEW_INTERNAL_CONTEXT_MENUS,
+ CONTEXT_MENUS,
+ TTS_ON_EVENT,
+ LAUNCHER_PAGE_ON_TRANSITION_CHANGED,
+ LAUNCHER_PAGE_ON_POP_SUBPAGE,
+ DIAL_ON_DEVICE_LIST,
+ DIAL_ON_ERROR,
+ CAST_CHANNEL_ON_MESSAGE,
+ CAST_CHANNEL_ON_ERROR,
+ SCREENLOCK_PRIVATE_ON_CHANGED,
+ SCREENLOCK_PRIVATE_ON_AUTH_ATTEMPTED,
+ TYPES_CHROME_SETTING_ON_CHANGE,
+ TYPES_PRIVATE_CHROME_DIRECT_SETTING_ON_CHANGE,
+ WEB_VIEW_INTERNAL_ON_MESSAGE,
+ EXTENSION_VIEW_INTERNAL_ON_LOAD_COMMIT,
+ RUNTIME_ON_REQUEST,
+ RUNTIME_ON_REQUEST_EXTERNAL,
+ CHROME_WEB_VIEW_INTERNAL_ON_CONTEXT_MENU_SHOW,
+ WEB_VIEW_INTERNAL_ON_BEFORE_REQUEST,
+ WEB_VIEW_INTERNAL_ON_BEFORE_SEND_HEADERS,
+ WEB_VIEW_INTERNAL_ON_CLOSE,
+ WEB_VIEW_INTERNAL_ON_COMPLETED,
+ WEB_VIEW_INTERNAL_ON_CONSOLE_MESSAGE,
+ WEB_VIEW_INTERNAL_ON_CONTENT_LOAD,
+ WEB_VIEW_INTERNAL_ON_DIALOG,
+ WEB_VIEW_INTERNAL_ON_DROP_LINK,
+ WEB_VIEW_INTERNAL_ON_EXIT,
+ WEB_VIEW_INTERNAL_ON_EXIT_FULLSCREEN,
+ WEB_VIEW_INTERNAL_ON_FIND_REPLY,
+ WEB_VIEW_INTERNAL_ON_FRAME_NAME_CHANGED,
+ WEB_VIEW_INTERNAL_ON_HEADERS_RECEIVED,
+ WEB_VIEW_INTERNAL_ON_LOAD_ABORT,
+ WEB_VIEW_INTERNAL_ON_LOAD_COMMIT,
+ WEB_VIEW_INTERNAL_ON_LOAD_PROGRESS,
+ WEB_VIEW_INTERNAL_ON_LOAD_REDIRECT,
+ WEB_VIEW_INTERNAL_ON_LOAD_START,
+ WEB_VIEW_INTERNAL_ON_LOAD_STOP,
+ WEB_VIEW_INTERNAL_ON_NEW_WINDOW,
+ WEB_VIEW_INTERNAL_ON_PERMISSION_REQUEST,
+ WEB_VIEW_INTERNAL_ON_RESPONSE_STARTED,
+ WEB_VIEW_INTERNAL_ON_RESPONSIVE,
+ WEB_VIEW_INTERNAL_ON_SIZE_CHANGED,
+ WEB_VIEW_INTERNAL_ON_UNRESPONSIVE,
+ WEB_VIEW_INTERNAL_ON_ZOOM_CHANGE,
+ GUEST_VIEW_INTERNAL_ON_RESIZE,
+ LANGUAGE_SETTINGS_PRIVATE_ON_INPUT_METHOD_ADDED,
+ LANGUAGE_SETTINGS_PRIVATE_ON_INPUT_METHOD_REMOVED,
+ LANGUAGE_SETTINGS_PRIVATE_ON_SPELLCHECK_DICTIONARIES_CHANGED,
+ LANGUAGE_SETTINGS_PRIVATE_ON_CUSTOM_DICTIONARY_CHANGED,
+ CAST_DEVICES_PRIVATE_ON_UPDATE_DEVICES_REQUESTED,
+ CAST_DEVICES_PRIVATE_ON_START_CAST,
+ CAST_DEVICES_PRIVATE_ON_STOP_CAST,
+ CERTIFICATEPROVIDER_ON_CERTIFICATES_REQUESTED,
+ CERTIFICATEPROVIDER_ON_SIGN_DIGEST_REQUESTED,
+ WEB_VIEW_INTERNAL_ON_AUTH_REQUIRED,
+ WEB_VIEW_INTERNAL_ON_BEFORE_REDIRECT,
+ WEB_VIEW_INTERNAL_ON_ERROR_OCCURRED,
+ WEB_VIEW_INTERNAL_ON_SEND_HEADERS,
+ EASY_UNLOCK_PRIVATE_ON_CONNECTION_STATUS_CHANGED,
+ EASY_UNLOCK_PRIVATE_ON_DATA_RECEIVED,
+ EASY_UNLOCK_PRIVATE_ON_SEND_COMPLETED,
+ DISPLAY_SOURCE_ON_SINKS_UPDATED,
+ INPUT_IME_ON_COMPOSITION_BOUNDS_CHANGED,
+ INPUT_METHOD_PRIVATE_ON_IME_MENU_ACTIVATION_CHANGED,
+ INPUT_METHOD_PRIVATE_ON_IME_MENU_LIST_CHANGED,
+ INPUT_METHOD_PRIVATE_ON_IME_MENU_ITEMS_CHANGED,
+ // Last entry: Add new entries above, then run:
+ // python tools/metrics/histograms/update_extension_histograms.py
+ ENUM_BOUNDARY
+};
+
+} // namespace events
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_EVENT_HISTOGRAM_VALUE_H_
diff --git a/chromium/extensions/browser/extension_function.cc b/chromium/extensions/browser/extension_function.cc
new file mode 100644
index 00000000000..b11621fa96e
--- /dev/null
+++ b/chromium/extensions/browser/extension_function.cc
@@ -0,0 +1,566 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_function.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "base/synchronization/lock.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "extensions/browser/extension_function_dispatcher.h"
+#include "extensions/browser/extension_message_filter.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension_api.h"
+#include "extensions/common/extension_messages.h"
+
+using content::BrowserThread;
+using content::RenderViewHost;
+using content::WebContents;
+using extensions::ErrorUtils;
+using extensions::ExtensionAPI;
+using extensions::Feature;
+
+namespace {
+
+class ArgumentListResponseValue
+ : public ExtensionFunction::ResponseValueObject {
+ public:
+ ArgumentListResponseValue(const std::string& function_name,
+ const char* title,
+ ExtensionFunction* function,
+ scoped_ptr<base::ListValue> result)
+ : function_name_(function_name), title_(title) {
+ if (function->GetResultList()) {
+ DCHECK_EQ(function->GetResultList(), result.get())
+ << "The result set on this function (" << function_name_ << ") "
+ << "either by calling SetResult() or directly modifying |result_| is "
+ << "different to the one passed to " << title_ << "(). "
+ << "The best way to fix this problem is to exclusively use " << title_
+ << "(). SetResult() and |result_| are deprecated.";
+ } else {
+ function->SetResultList(std::move(result));
+ }
+ // It would be nice to DCHECK(error.empty()) but some legacy extension
+ // function implementations... I'm looking at chrome.input.ime... do this
+ // for some reason.
+ }
+
+ ~ArgumentListResponseValue() override {}
+
+ bool Apply() override { return true; }
+
+ private:
+ std::string function_name_;
+ const char* title_;
+};
+
+class ErrorWithArgumentsResponseValue : public ArgumentListResponseValue {
+ public:
+ ErrorWithArgumentsResponseValue(const std::string& function_name,
+ const char* title,
+ ExtensionFunction* function,
+ scoped_ptr<base::ListValue> result,
+ const std::string& error)
+ : ArgumentListResponseValue(function_name,
+ title,
+ function,
+ std::move(result)) {
+ function->SetError(error);
+ }
+
+ ~ErrorWithArgumentsResponseValue() override {}
+
+ bool Apply() override { return false; }
+};
+
+class ErrorResponseValue : public ExtensionFunction::ResponseValueObject {
+ public:
+ ErrorResponseValue(ExtensionFunction* function, const std::string& error) {
+ // It would be nice to DCHECK(!error.empty()) but too many legacy extension
+ // function implementations don't set error but signal failure.
+ function->SetError(error);
+ }
+
+ ~ErrorResponseValue() override {}
+
+ bool Apply() override { return false; }
+};
+
+class BadMessageResponseValue : public ExtensionFunction::ResponseValueObject {
+ public:
+ explicit BadMessageResponseValue(ExtensionFunction* function) {
+ function->set_bad_message(true);
+ NOTREACHED() << function->name() << ": bad message";
+ }
+
+ ~BadMessageResponseValue() override {}
+
+ bool Apply() override { return false; }
+};
+
+class RespondNowAction : public ExtensionFunction::ResponseActionObject {
+ public:
+ typedef base::Callback<void(bool)> SendResponseCallback;
+ RespondNowAction(ExtensionFunction::ResponseValue result,
+ const SendResponseCallback& send_response)
+ : result_(std::move(result)), send_response_(send_response) {}
+ ~RespondNowAction() override {}
+
+ void Execute() override { send_response_.Run(result_->Apply()); }
+
+ private:
+ ExtensionFunction::ResponseValue result_;
+ SendResponseCallback send_response_;
+};
+
+class RespondLaterAction : public ExtensionFunction::ResponseActionObject {
+ public:
+ ~RespondLaterAction() override {}
+
+ void Execute() override {}
+};
+
+// Used in implementation of ScopedUserGestureForTests.
+class UserGestureForTests {
+ public:
+ static UserGestureForTests* GetInstance();
+
+ // Returns true if there is at least one ScopedUserGestureForTests object
+ // alive.
+ bool HaveGesture();
+
+ // These should be called when a ScopedUserGestureForTests object is
+ // created/destroyed respectively.
+ void IncrementCount();
+ void DecrementCount();
+
+ private:
+ UserGestureForTests();
+ friend struct base::DefaultSingletonTraits<UserGestureForTests>;
+
+ base::Lock lock_; // for protecting access to count_
+ int count_;
+};
+
+// static
+UserGestureForTests* UserGestureForTests::GetInstance() {
+ return base::Singleton<UserGestureForTests>::get();
+}
+
+UserGestureForTests::UserGestureForTests() : count_(0) {}
+
+bool UserGestureForTests::HaveGesture() {
+ base::AutoLock autolock(lock_);
+ return count_ > 0;
+}
+
+void UserGestureForTests::IncrementCount() {
+ base::AutoLock autolock(lock_);
+ ++count_;
+}
+
+void UserGestureForTests::DecrementCount() {
+ base::AutoLock autolock(lock_);
+ --count_;
+}
+
+
+} // namespace
+
+// static
+void ExtensionFunctionDeleteTraits::Destruct(const ExtensionFunction* x) {
+ x->Destruct();
+}
+
+// Helper class to track the lifetime of ExtensionFunction's RenderFrameHost and
+// notify the function when it is deleted, as well as forwarding any messages
+// to the ExtensionFunction.
+class UIThreadExtensionFunction::RenderFrameHostTracker
+ : public content::WebContentsObserver {
+ public:
+ explicit RenderFrameHostTracker(UIThreadExtensionFunction* function)
+ : content::WebContentsObserver(
+ WebContents::FromRenderFrameHost(function->render_frame_host())),
+ function_(function) {
+ }
+
+ private:
+ // content::WebContentsObserver:
+ void RenderFrameDeleted(
+ content::RenderFrameHost* render_frame_host) override {
+ if (render_frame_host == function_->render_frame_host())
+ function_->SetRenderFrameHost(nullptr);
+ }
+
+ bool OnMessageReceived(const IPC::Message& message,
+ content::RenderFrameHost* render_frame_host) override {
+ return render_frame_host == function_->render_frame_host() &&
+ function_->OnMessageReceived(message);
+ }
+
+ bool OnMessageReceived(const IPC::Message& message) override {
+ return function_->OnMessageReceived(message);
+ }
+
+ UIThreadExtensionFunction* function_; // Owns us.
+
+ DISALLOW_COPY_AND_ASSIGN(RenderFrameHostTracker);
+};
+
+ExtensionFunction::ExtensionFunction()
+ : request_id_(-1),
+ profile_id_(NULL),
+ name_(""),
+ has_callback_(false),
+ include_incognito_(false),
+ user_gesture_(false),
+ bad_message_(false),
+ histogram_value_(extensions::functions::UNKNOWN),
+ source_tab_id_(-1),
+ source_context_type_(Feature::UNSPECIFIED_CONTEXT),
+ source_process_id_(-1) {
+}
+
+ExtensionFunction::~ExtensionFunction() {
+}
+
+UIThreadExtensionFunction* ExtensionFunction::AsUIThreadExtensionFunction() {
+ return NULL;
+}
+
+IOThreadExtensionFunction* ExtensionFunction::AsIOThreadExtensionFunction() {
+ return NULL;
+}
+
+bool ExtensionFunction::HasPermission() {
+ Feature::Availability availability =
+ ExtensionAPI::GetSharedInstance()->IsAvailable(
+ name_, extension_.get(), source_context_type_, source_url());
+ return availability.is_available();
+}
+
+void ExtensionFunction::OnQuotaExceeded(const std::string& violation_error) {
+ error_ = violation_error;
+ SendResponse(false);
+}
+
+void ExtensionFunction::SetArgs(const base::ListValue* args) {
+ DCHECK(!args_.get()); // Should only be called once.
+ args_.reset(args->DeepCopy());
+}
+
+void ExtensionFunction::SetResult(base::Value* result) {
+ results_.reset(new base::ListValue());
+ results_->Append(result);
+}
+
+void ExtensionFunction::SetResult(scoped_ptr<base::Value> result) {
+ results_.reset(new base::ListValue());
+ results_->Append(std::move(result));
+}
+
+void ExtensionFunction::SetResultList(scoped_ptr<base::ListValue> results) {
+ results_ = std::move(results);
+}
+
+const base::ListValue* ExtensionFunction::GetResultList() const {
+ return results_.get();
+}
+
+std::string ExtensionFunction::GetError() const {
+ return error_;
+}
+
+void ExtensionFunction::SetError(const std::string& error) {
+ error_ = error;
+}
+
+bool ExtensionFunction::user_gesture() const {
+ return user_gesture_ || UserGestureForTests::GetInstance()->HaveGesture();
+}
+
+ExtensionFunction::ResponseValue ExtensionFunction::NoArguments() {
+ return ResponseValue(new ArgumentListResponseValue(
+ name(), "NoArguments", this, make_scoped_ptr(new base::ListValue())));
+}
+
+ExtensionFunction::ResponseValue ExtensionFunction::OneArgument(
+ base::Value* arg) {
+ scoped_ptr<base::ListValue> args(new base::ListValue());
+ args->Append(arg);
+ return ResponseValue(new ArgumentListResponseValue(name(), "OneArgument",
+ this, std::move(args)));
+}
+
+ExtensionFunction::ResponseValue ExtensionFunction::OneArgument(
+ scoped_ptr<base::Value> arg) {
+ return OneArgument(arg.release());
+}
+
+ExtensionFunction::ResponseValue ExtensionFunction::TwoArguments(
+ base::Value* arg1,
+ base::Value* arg2) {
+ scoped_ptr<base::ListValue> args(new base::ListValue());
+ args->Append(arg1);
+ args->Append(arg2);
+ return ResponseValue(new ArgumentListResponseValue(name(), "TwoArguments",
+ this, std::move(args)));
+}
+
+ExtensionFunction::ResponseValue ExtensionFunction::ArgumentList(
+ scoped_ptr<base::ListValue> args) {
+ return ResponseValue(new ArgumentListResponseValue(name(), "ArgumentList",
+ this, std::move(args)));
+}
+
+ExtensionFunction::ResponseValue ExtensionFunction::Error(
+ const std::string& error) {
+ return ResponseValue(new ErrorResponseValue(this, error));
+}
+
+ExtensionFunction::ResponseValue ExtensionFunction::Error(
+ const std::string& format,
+ const std::string& s1) {
+ return ResponseValue(
+ new ErrorResponseValue(this, ErrorUtils::FormatErrorMessage(format, s1)));
+}
+
+ExtensionFunction::ResponseValue ExtensionFunction::Error(
+ const std::string& format,
+ const std::string& s1,
+ const std::string& s2) {
+ return ResponseValue(new ErrorResponseValue(
+ this, ErrorUtils::FormatErrorMessage(format, s1, s2)));
+}
+
+ExtensionFunction::ResponseValue ExtensionFunction::Error(
+ const std::string& format,
+ const std::string& s1,
+ const std::string& s2,
+ const std::string& s3) {
+ return ResponseValue(new ErrorResponseValue(
+ this, ErrorUtils::FormatErrorMessage(format, s1, s2, s3)));
+}
+
+ExtensionFunction::ResponseValue ExtensionFunction::ErrorWithArguments(
+ scoped_ptr<base::ListValue> args,
+ const std::string& error) {
+ return ResponseValue(new ErrorWithArgumentsResponseValue(
+ name(), "ErrorWithArguments", this, std::move(args), error));
+}
+
+ExtensionFunction::ResponseValue ExtensionFunction::BadMessage() {
+ return ResponseValue(new BadMessageResponseValue(this));
+}
+
+ExtensionFunction::ResponseAction ExtensionFunction::RespondNow(
+ ResponseValue result) {
+ return ResponseAction(new RespondNowAction(
+ std::move(result), base::Bind(&ExtensionFunction::SendResponse, this)));
+}
+
+ExtensionFunction::ResponseAction ExtensionFunction::RespondLater() {
+ return ResponseAction(new RespondLaterAction());
+}
+
+// static
+ExtensionFunction::ResponseAction ExtensionFunction::ValidationFailure(
+ ExtensionFunction* function) {
+ return function->RespondNow(function->BadMessage());
+}
+
+void ExtensionFunction::Respond(ResponseValue result) {
+ SendResponse(result->Apply());
+}
+
+bool ExtensionFunction::ShouldSkipQuotaLimiting() const {
+ return false;
+}
+
+bool ExtensionFunction::HasOptionalArgument(size_t index) {
+ base::Value* value;
+ return args_->Get(index, &value) && !value->IsType(base::Value::TYPE_NULL);
+}
+
+void ExtensionFunction::SendResponseImpl(bool success) {
+ DCHECK(!response_callback_.is_null());
+
+ ResponseType type = success ? SUCCEEDED : FAILED;
+ if (bad_message_) {
+ type = BAD_MESSAGE;
+ LOG(ERROR) << "Bad extension message " << name_;
+ }
+
+ // If results were never set, we send an empty argument list.
+ if (!results_)
+ results_.reset(new base::ListValue());
+
+ response_callback_.Run(type, *results_, GetError(), histogram_value());
+}
+
+void ExtensionFunction::OnRespondingLater(ResponseValue value) {
+ SendResponse(value->Apply());
+}
+
+UIThreadExtensionFunction::UIThreadExtensionFunction()
+ : context_(nullptr),
+ render_frame_host_(nullptr),
+ delegate_(nullptr) {
+}
+
+UIThreadExtensionFunction::~UIThreadExtensionFunction() {
+ if (dispatcher() && render_frame_host())
+ dispatcher()->OnExtensionFunctionCompleted(extension());
+}
+
+UIThreadExtensionFunction*
+UIThreadExtensionFunction::AsUIThreadExtensionFunction() {
+ return this;
+}
+
+bool UIThreadExtensionFunction::OnMessageReceived(const IPC::Message& message) {
+ return false;
+}
+
+void UIThreadExtensionFunction::Destruct() const {
+ BrowserThread::DeleteOnUIThread::Destruct(this);
+}
+
+content::RenderViewHost*
+UIThreadExtensionFunction::render_view_host_do_not_use() const {
+ return render_frame_host_ ? render_frame_host_->GetRenderViewHost() : nullptr;
+}
+
+void UIThreadExtensionFunction::SetRenderFrameHost(
+ content::RenderFrameHost* render_frame_host) {
+ DCHECK_NE(render_frame_host_ == nullptr, render_frame_host == nullptr);
+ render_frame_host_ = render_frame_host;
+ tracker_.reset(
+ render_frame_host ? new RenderFrameHostTracker(this) : nullptr);
+}
+
+content::WebContents* UIThreadExtensionFunction::GetAssociatedWebContents() {
+ content::WebContents* web_contents = NULL;
+ if (dispatcher())
+ web_contents = dispatcher()->GetAssociatedWebContents();
+
+ return web_contents;
+}
+
+content::WebContents* UIThreadExtensionFunction::GetSenderWebContents() {
+ return render_frame_host_ ?
+ content::WebContents::FromRenderFrameHost(render_frame_host_) : nullptr;
+}
+
+void UIThreadExtensionFunction::SendResponse(bool success) {
+ if (delegate_)
+ delegate_->OnSendResponse(this, success, bad_message_);
+ else
+ SendResponseImpl(success);
+
+ if (!transferred_blob_uuids_.empty()) {
+ DCHECK(!delegate_) << "Blob transfer not supported with test delegate.";
+ render_frame_host_->Send(
+ new ExtensionMsg_TransferBlobs(transferred_blob_uuids_));
+ }
+}
+
+void UIThreadExtensionFunction::SetTransferredBlobUUIDs(
+ const std::vector<std::string>& blob_uuids) {
+ DCHECK(transferred_blob_uuids_.empty()); // Should only be called once.
+ transferred_blob_uuids_ = blob_uuids;
+}
+
+void UIThreadExtensionFunction::WriteToConsole(
+ content::ConsoleMessageLevel level,
+ const std::string& message) {
+ // Only the main frame handles dev tools messages.
+ WebContents::FromRenderFrameHost(render_frame_host_)
+ ->GetMainFrame()
+ ->AddMessageToConsole(level, message);
+}
+
+IOThreadExtensionFunction::IOThreadExtensionFunction()
+ : routing_id_(MSG_ROUTING_NONE) {
+}
+
+IOThreadExtensionFunction::~IOThreadExtensionFunction() {
+}
+
+IOThreadExtensionFunction*
+IOThreadExtensionFunction::AsIOThreadExtensionFunction() {
+ return this;
+}
+
+void IOThreadExtensionFunction::Destruct() const {
+ BrowserThread::DeleteOnIOThread::Destruct(this);
+}
+
+void IOThreadExtensionFunction::SendResponse(bool success) {
+ SendResponseImpl(success);
+}
+
+AsyncExtensionFunction::AsyncExtensionFunction() {
+}
+
+AsyncExtensionFunction::~AsyncExtensionFunction() {
+}
+
+ExtensionFunction::ScopedUserGestureForTests::ScopedUserGestureForTests() {
+ UserGestureForTests::GetInstance()->IncrementCount();
+}
+
+ExtensionFunction::ScopedUserGestureForTests::~ScopedUserGestureForTests() {
+ UserGestureForTests::GetInstance()->DecrementCount();
+}
+
+ExtensionFunction::ResponseAction AsyncExtensionFunction::Run() {
+ return RunAsync() ? RespondLater() : RespondNow(Error(error_));
+}
+
+// static
+bool AsyncExtensionFunction::ValidationFailure(
+ AsyncExtensionFunction* function) {
+ return false;
+}
+
+SyncExtensionFunction::SyncExtensionFunction() {
+}
+
+SyncExtensionFunction::~SyncExtensionFunction() {
+}
+
+ExtensionFunction::ResponseAction SyncExtensionFunction::Run() {
+ return RespondNow(RunSync() ? ArgumentList(std::move(results_))
+ : Error(error_));
+}
+
+// static
+bool SyncExtensionFunction::ValidationFailure(SyncExtensionFunction* function) {
+ return false;
+}
+
+SyncIOThreadExtensionFunction::SyncIOThreadExtensionFunction() {
+}
+
+SyncIOThreadExtensionFunction::~SyncIOThreadExtensionFunction() {
+}
+
+ExtensionFunction::ResponseAction SyncIOThreadExtensionFunction::Run() {
+ return RespondNow(RunSync() ? ArgumentList(std::move(results_))
+ : Error(error_));
+}
+
+// static
+bool SyncIOThreadExtensionFunction::ValidationFailure(
+ SyncIOThreadExtensionFunction* function) {
+ return false;
+}
diff --git a/chromium/extensions/browser/extension_function.h b/chromium/extensions/browser/extension_function.h
new file mode 100644
index 00000000000..cd712e3c316
--- /dev/null
+++ b/chromium/extensions/browser/extension_function.h
@@ -0,0 +1,688 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_FUNCTION_H_
+#define EXTENSIONS_BROWSER_EXTENSION_FUNCTION_H_
+
+#include <stddef.h>
+
+#include <list>
+#include <string>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/process/process.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/console_message_level.h"
+#include "extensions/browser/extension_function_histogram_value.h"
+#include "extensions/browser/info_map.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/features/feature.h"
+#include "ipc/ipc_message.h"
+
+class ExtensionFunction;
+class UIThreadExtensionFunction;
+class IOThreadExtensionFunction;
+
+namespace base {
+class ListValue;
+class Value;
+}
+
+namespace content {
+class BrowserContext;
+class RenderFrameHost;
+class RenderViewHost;
+class WebContents;
+}
+
+namespace extensions {
+class ExtensionFunctionDispatcher;
+class IOThreadExtensionMessageFilter;
+class QuotaLimitHeuristic;
+}
+
+namespace IPC {
+class Sender;
+}
+
+#ifdef NDEBUG
+#define EXTENSION_FUNCTION_VALIDATE(test) \
+ do { \
+ if (!(test)) { \
+ this->bad_message_ = true; \
+ return ValidationFailure(this); \
+ } \
+ } while (0)
+#else // NDEBUG
+#define EXTENSION_FUNCTION_VALIDATE(test) CHECK(test)
+#endif // NDEBUG
+
+#define EXTENSION_FUNCTION_ERROR(error) \
+ do { \
+ error_ = error; \
+ this->bad_message_ = true; \
+ return ValidationFailure(this); \
+ } while (0)
+
+// Declares a callable extension function with the given |name|. You must also
+// supply a unique |histogramvalue| used for histograms of extension function
+// invocation (add new ones at the end of the enum in
+// extension_function_histogram_value.h).
+#define DECLARE_EXTENSION_FUNCTION(name, histogramvalue) \
+ public: static const char* function_name() { return name; } \
+ public: static extensions::functions::HistogramValue histogram_value() \
+ { return extensions::functions::histogramvalue; }
+
+// Traits that describe how ExtensionFunction should be deleted. This just calls
+// the virtual "Destruct" method on ExtensionFunction, allowing derived classes
+// to override the behavior.
+struct ExtensionFunctionDeleteTraits {
+ public:
+ static void Destruct(const ExtensionFunction* x);
+};
+
+// Abstract base class for extension functions the ExtensionFunctionDispatcher
+// knows how to dispatch to.
+class ExtensionFunction
+ : public base::RefCountedThreadSafe<ExtensionFunction,
+ ExtensionFunctionDeleteTraits> {
+ public:
+ enum ResponseType {
+ // The function has succeeded.
+ SUCCEEDED,
+ // The function has failed.
+ FAILED,
+ // The input message is malformed.
+ BAD_MESSAGE
+ };
+
+ using ResponseCallback = base::Callback<void(
+ ResponseType type,
+ const base::ListValue& results,
+ const std::string& error,
+ extensions::functions::HistogramValue histogram_value)>;
+
+ ExtensionFunction();
+
+ virtual UIThreadExtensionFunction* AsUIThreadExtensionFunction();
+ virtual IOThreadExtensionFunction* AsIOThreadExtensionFunction();
+
+ // Returns true if the function has permission to run.
+ //
+ // The default implementation is to check the Extension's permissions against
+ // what this function requires to run, but some APIs may require finer
+ // grained control, such as tabs.executeScript being allowed for active tabs.
+ //
+ // This will be run after the function has been set up but before Run().
+ virtual bool HasPermission();
+
+ // The result of a function call.
+ //
+ // Use NoArguments(), OneArgument(), ArgumentList(), or Error()
+ // rather than this class directly.
+ class ResponseValueObject {
+ public:
+ virtual ~ResponseValueObject() {}
+
+ // Returns true for success, false for failure.
+ virtual bool Apply() = 0;
+ };
+ typedef scoped_ptr<ResponseValueObject> ResponseValue;
+
+ // The action to use when returning from RunAsync.
+ //
+ // Use RespondNow() or RespondLater() rather than this class directly.
+ class ResponseActionObject {
+ public:
+ virtual ~ResponseActionObject() {}
+
+ virtual void Execute() = 0;
+ };
+ typedef scoped_ptr<ResponseActionObject> ResponseAction;
+
+ // Helper class for tests to force all ExtensionFunction::user_gesture()
+ // calls to return true as long as at least one instance of this class
+ // exists.
+ class ScopedUserGestureForTests {
+ public:
+ ScopedUserGestureForTests();
+ ~ScopedUserGestureForTests();
+ };
+
+ // Runs the function and returns the action to take when the caller is ready
+ // to respond.
+ //
+ // Typical return values might be:
+ // * RespondNow(NoArguments())
+ // * RespondNow(OneArgument(42))
+ // * RespondNow(ArgumentList(my_result.ToValue()))
+ // * RespondNow(Error("Warp core breach"))
+ // * RespondNow(Error("Warp core breach on *", GetURL()))
+ // * RespondLater(), then later,
+ // * Respond(NoArguments())
+ // * ... etc.
+ //
+ //
+ // Callers must call Execute() on the return ResponseAction at some point,
+ // exactly once.
+ //
+ // SyncExtensionFunction and AsyncExtensionFunction implement this in terms
+ // of SyncExtensionFunction::RunSync and AsyncExtensionFunction::RunAsync,
+ // but this is deprecated. ExtensionFunction implementations are encouraged
+ // to just implement Run.
+ virtual ResponseAction Run() WARN_UNUSED_RESULT = 0;
+
+ // Gets whether quota should be applied to this individual function
+ // invocation. This is different to GetQuotaLimitHeuristics which is only
+ // invoked once and then cached.
+ //
+ // Returns false by default.
+ virtual bool ShouldSkipQuotaLimiting() const;
+
+ // Optionally adds one or multiple QuotaLimitHeuristic instances suitable for
+ // this function to |heuristics|. The ownership of the new QuotaLimitHeuristic
+ // instances is passed to the owner of |heuristics|.
+ // No quota limiting by default.
+ //
+ // Only called once per lifetime of the QuotaService.
+ virtual void GetQuotaLimitHeuristics(
+ extensions::QuotaLimitHeuristics* heuristics) const {}
+
+ // Called when the quota limit has been exceeded. The default implementation
+ // returns an error.
+ virtual void OnQuotaExceeded(const std::string& violation_error);
+
+ // Specifies the raw arguments to the function, as a JSON value.
+ virtual void SetArgs(const base::ListValue* args);
+
+ // Sets a single Value as the results of the function.
+ void SetResult(scoped_ptr<base::Value> result);
+ // As above, but deprecated. TODO(estade): remove.
+ void SetResult(base::Value* result);
+
+ // Sets multiple Values as the results of the function.
+ void SetResultList(scoped_ptr<base::ListValue> results);
+
+ // Retrieves the results of the function as a ListValue.
+ const base::ListValue* GetResultList() const;
+
+ // Retrieves any error string from the function.
+ virtual std::string GetError() const;
+
+ // Sets the function's error string.
+ virtual void SetError(const std::string& error);
+
+ // Sets the function's bad message state.
+ void set_bad_message(bool bad_message) { bad_message_ = bad_message; }
+
+ // Specifies the name of the function. A long-lived string (such as a string
+ // literal) must be provided.
+ void set_name(const char* name) { name_ = name; }
+ const char* name() const { return name_; }
+
+ void set_profile_id(void* profile_id) { profile_id_ = profile_id; }
+ void* profile_id() const { return profile_id_; }
+
+ void set_extension(
+ const scoped_refptr<const extensions::Extension>& extension) {
+ extension_ = extension;
+ }
+ const extensions::Extension* extension() const { return extension_.get(); }
+ const std::string& extension_id() const {
+ DCHECK(extension())
+ << "extension_id() called without an Extension. If " << name()
+ << " is allowed to be called without any Extension then you should "
+ << "check extension() first. If not, there is a bug in the Extension "
+ << "platform, so page somebody in extensions/OWNERS";
+ return extension_->id();
+ }
+
+ void set_request_id(int request_id) { request_id_ = request_id; }
+ int request_id() { return request_id_; }
+
+ void set_source_url(const GURL& source_url) { source_url_ = source_url; }
+ const GURL& source_url() { return source_url_; }
+
+ void set_has_callback(bool has_callback) { has_callback_ = has_callback; }
+ bool has_callback() { return has_callback_; }
+
+ void set_include_incognito(bool include) { include_incognito_ = include; }
+ bool include_incognito() const { return include_incognito_; }
+
+ // Note: consider using ScopedUserGestureForTests instead of calling
+ // set_user_gesture directly.
+ void set_user_gesture(bool user_gesture) { user_gesture_ = user_gesture; }
+ bool user_gesture() const;
+
+ void set_histogram_value(
+ extensions::functions::HistogramValue histogram_value) {
+ histogram_value_ = histogram_value; }
+ extensions::functions::HistogramValue histogram_value() const {
+ return histogram_value_; }
+
+ void set_response_callback(const ResponseCallback& callback) {
+ response_callback_ = callback;
+ }
+
+ void set_source_tab_id(int source_tab_id) { source_tab_id_ = source_tab_id; }
+ int source_tab_id() const { return source_tab_id_; }
+
+ void set_source_context_type(extensions::Feature::Context type) {
+ source_context_type_ = type;
+ }
+ extensions::Feature::Context source_context_type() const {
+ return source_context_type_;
+ }
+
+ void set_source_process_id(int source_process_id) {
+ source_process_id_ = source_process_id;
+ }
+ int source_process_id() const {
+ return source_process_id_;
+ }
+
+ protected:
+ friend struct ExtensionFunctionDeleteTraits;
+
+ // ResponseValues.
+ //
+ // Success, no arguments to pass to caller.
+ ResponseValue NoArguments();
+ // Success, a single argument |arg| to pass to caller. TAKES OWNERSHIP - a
+ // raw pointer for convenience, since callers usually construct the argument
+ // to this by hand.
+ ResponseValue OneArgument(base::Value* arg);
+ // Success, a single argument |arg| to pass to caller.
+ ResponseValue OneArgument(scoped_ptr<base::Value> arg);
+ // Success, two arguments |arg1| and |arg2| to pass to caller. TAKES
+ // OWNERSHIP - raw pointers for convenience, since callers usually construct
+ // the argument to this by hand. Note that use of this function may imply you
+ // should be using the generated Result struct and ArgumentList.
+ ResponseValue TwoArguments(base::Value* arg1, base::Value* arg2);
+ // Success, a list of arguments |results| to pass to caller. TAKES OWNERSHIP
+ // - a scoped_ptr<> for convenience, since callers usually get this from the
+ // result of a Create(...) call on the generated Results struct, for example,
+ // alarms::Get::Results::Create(alarm).
+ ResponseValue ArgumentList(scoped_ptr<base::ListValue> results);
+ // Error. chrome.runtime.lastError.message will be set to |error|.
+ ResponseValue Error(const std::string& error);
+ // Error with formatting. Args are processed using
+ // ErrorUtils::FormatErrorMessage, that is, each occurence of * is replaced
+ // by the corresponding |s*|:
+ // Error("Error in *: *", "foo", "bar") <--> Error("Error in foo: bar").
+ ResponseValue Error(const std::string& format, const std::string& s1);
+ ResponseValue Error(const std::string& format,
+ const std::string& s1,
+ const std::string& s2);
+ ResponseValue Error(const std::string& format,
+ const std::string& s1,
+ const std::string& s2,
+ const std::string& s3);
+ // Error with a list of arguments |args| to pass to caller. TAKES OWNERSHIP.
+ // Using this ResponseValue indicates something is wrong with the API.
+ // It shouldn't be possible to have both an error *and* some arguments.
+ // Some legacy APIs do rely on it though, like webstorePrivate.
+ ResponseValue ErrorWithArguments(scoped_ptr<base::ListValue> args,
+ const std::string& error);
+ // Bad message. A ResponseValue equivalent to EXTENSION_FUNCTION_VALIDATE(),
+ // so this will actually kill the renderer and not respond at all.
+ ResponseValue BadMessage();
+
+ // ResponseActions.
+ //
+ // These are exclusively used as return values from Run(). Call Respond(...)
+ // to respond at any other time - but as described below, only after Run()
+ // has already executed, and only if it returned RespondLater().
+ //
+ // Respond to the extension immediately with |result|.
+ ResponseAction RespondNow(ResponseValue result) WARN_UNUSED_RESULT;
+ // Don't respond now, but promise to call Respond(...) later.
+ ResponseAction RespondLater() WARN_UNUSED_RESULT;
+
+ // This is the return value of the EXTENSION_FUNCTION_VALIDATE macro, which
+ // needs to work from Run(), RunAsync(), and RunSync(). The former of those
+ // has a different return type (ResponseAction) than the latter two (bool).
+ static ResponseAction ValidationFailure(ExtensionFunction* function)
+ WARN_UNUSED_RESULT;
+
+ // If RespondLater() was returned from Run(), functions must at some point
+ // call Respond() with |result| as their result.
+ //
+ // More specifically: call this iff Run() has already executed, it returned
+ // RespondLater(), and Respond(...) hasn't already been called.
+ void Respond(ResponseValue result);
+
+ virtual ~ExtensionFunction();
+
+ // Helper method for ExtensionFunctionDeleteTraits. Deletes this object.
+ virtual void Destruct() const = 0;
+
+ // Do not call this function directly, return the appropriate ResponseAction
+ // from Run() instead. If using RespondLater then call Respond().
+ //
+ // Call with true to indicate success, false to indicate failure, in which
+ // case please set |error_|.
+ virtual void SendResponse(bool success) = 0;
+
+ // Common implementation for SendResponse.
+ void SendResponseImpl(bool success);
+
+ // Return true if the argument to this function at |index| was provided and
+ // is non-null.
+ bool HasOptionalArgument(size_t index);
+
+ // Id of this request, used to map the response back to the caller.
+ int request_id_;
+
+ // The id of the profile of this function's extension.
+ void* profile_id_;
+
+ // The extension that called this function.
+ scoped_refptr<const extensions::Extension> extension_;
+
+ // The name of this function.
+ const char* name_;
+
+ // The URL of the frame which is making this request
+ GURL source_url_;
+
+ // True if the js caller provides a callback function to receive the response
+ // of this call.
+ bool has_callback_;
+
+ // True if this callback should include information from incognito contexts
+ // even if our profile_ is non-incognito. Note that in the case of a "split"
+ // mode extension, this will always be false, and we will limit access to
+ // data from within the same profile_ (either incognito or not).
+ bool include_incognito_;
+
+ // True if the call was made in response of user gesture.
+ bool user_gesture_;
+
+ // The arguments to the API. Only non-null if argument were specified.
+ scoped_ptr<base::ListValue> args_;
+
+ // The results of the API. This should be populated by the derived class
+ // before SendResponse() is called.
+ scoped_ptr<base::ListValue> results_;
+
+ // Any detailed error from the API. This should be populated by the derived
+ // class before Run() returns.
+ std::string error_;
+
+ // Any class that gets a malformed message should set this to true before
+ // returning. Usually we want to kill the message sending process.
+ bool bad_message_;
+
+ // The sample value to record with the histogram API when the function
+ // is invoked.
+ extensions::functions::HistogramValue histogram_value_;
+
+ // The callback to run once the function has done execution.
+ ResponseCallback response_callback_;
+
+ // The ID of the tab triggered this function call, or -1 if there is no tab.
+ int source_tab_id_;
+
+ // The type of the JavaScript context where this call originated.
+ extensions::Feature::Context source_context_type_;
+
+ // The process ID of the page that triggered this function call, or -1
+ // if unknown.
+ int source_process_id_;
+
+ private:
+ void OnRespondingLater(ResponseValue response);
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionFunction);
+};
+
+// Extension functions that run on the UI thread. Most functions fall into
+// this category.
+class UIThreadExtensionFunction : public ExtensionFunction {
+ public:
+ // TODO(yzshen): We should be able to remove this interface now that we
+ // support overriding the response callback.
+ // A delegate for use in testing, to intercept the call to SendResponse.
+ class DelegateForTests {
+ public:
+ virtual void OnSendResponse(UIThreadExtensionFunction* function,
+ bool success,
+ bool bad_message) = 0;
+ };
+
+ UIThreadExtensionFunction();
+
+ UIThreadExtensionFunction* AsUIThreadExtensionFunction() override;
+
+ void set_test_delegate(DelegateForTests* delegate) {
+ delegate_ = delegate;
+ }
+
+ // Called when a message was received.
+ // Should return true if it processed the message.
+ virtual bool OnMessageReceived(const IPC::Message& message);
+
+ // Set the browser context which contains the extension that has originated
+ // this function call.
+ void set_browser_context(content::BrowserContext* context) {
+ context_ = context;
+ }
+ content::BrowserContext* browser_context() const { return context_; }
+
+ // DEPRECATED: Please use render_frame_host().
+ // TODO(devlin): Remove this once all callers are updated to use
+ // render_frame_host().
+ content::RenderViewHost* render_view_host_do_not_use() const;
+
+ void SetRenderFrameHost(content::RenderFrameHost* render_frame_host);
+ content::RenderFrameHost* render_frame_host() const {
+ return render_frame_host_;
+ }
+
+ void set_dispatcher(const base::WeakPtr<
+ extensions::ExtensionFunctionDispatcher>& dispatcher) {
+ dispatcher_ = dispatcher;
+ }
+ extensions::ExtensionFunctionDispatcher* dispatcher() const {
+ return dispatcher_.get();
+ }
+
+ // Gets the "current" web contents if any. If there is no associated web
+ // contents then defaults to the foremost one.
+ // NOTE: "current" can mean different things in different contexts. You
+ // probably want to use GetSenderWebContents().
+ virtual content::WebContents* GetAssociatedWebContents();
+
+ // Returns the web contents associated with the sending |render_frame_host_|.
+ // This can be null.
+ content::WebContents* GetSenderWebContents();
+
+ protected:
+ // Emits a message to the extension's devtools console.
+ void WriteToConsole(content::ConsoleMessageLevel level,
+ const std::string& message);
+
+ friend struct content::BrowserThread::DeleteOnThread<
+ content::BrowserThread::UI>;
+ friend class base::DeleteHelper<UIThreadExtensionFunction>;
+
+ ~UIThreadExtensionFunction() override;
+
+ void SendResponse(bool success) override;
+
+ // Sets the Blob UUIDs whose ownership is being transferred to the renderer.
+ void SetTransferredBlobUUIDs(const std::vector<std::string>& blob_uuids);
+
+ // The BrowserContext of this function's extension.
+ // TODO(devlin): Grr... protected members. Move this to be private.
+ content::BrowserContext* context_;
+
+ private:
+ class RenderFrameHostTracker;
+
+ void Destruct() const override;
+
+ // The dispatcher that will service this extension function call.
+ base::WeakPtr<extensions::ExtensionFunctionDispatcher> dispatcher_;
+
+ // The RenderFrameHost we will send responses to.
+ content::RenderFrameHost* render_frame_host_;
+
+ scoped_ptr<RenderFrameHostTracker> tracker_;
+
+ DelegateForTests* delegate_;
+
+ // The blobs transferred to the renderer process.
+ std::vector<std::string> transferred_blob_uuids_;
+
+ DISALLOW_COPY_AND_ASSIGN(UIThreadExtensionFunction);
+};
+
+// Extension functions that run on the IO thread. This type of function avoids
+// a roundtrip to and from the UI thread (because communication with the
+// extension process happens on the IO thread). It's intended to be used when
+// performance is critical (e.g. the webRequest API which can block network
+// requests). Generally, UIThreadExtensionFunction is more appropriate and will
+// be easier to use and interface with the rest of the browser.
+class IOThreadExtensionFunction : public ExtensionFunction {
+ public:
+ IOThreadExtensionFunction();
+
+ IOThreadExtensionFunction* AsIOThreadExtensionFunction() override;
+
+ void set_ipc_sender(
+ base::WeakPtr<extensions::IOThreadExtensionMessageFilter> ipc_sender,
+ int routing_id) {
+ ipc_sender_ = ipc_sender;
+ routing_id_ = routing_id;
+ }
+
+ base::WeakPtr<extensions::IOThreadExtensionMessageFilter> ipc_sender_weak()
+ const {
+ return ipc_sender_;
+ }
+
+ int routing_id() const { return routing_id_; }
+
+ void set_extension_info_map(const extensions::InfoMap* extension_info_map) {
+ extension_info_map_ = extension_info_map;
+ }
+ const extensions::InfoMap* extension_info_map() const {
+ return extension_info_map_.get();
+ }
+
+ protected:
+ friend struct content::BrowserThread::DeleteOnThread<
+ content::BrowserThread::IO>;
+ friend class base::DeleteHelper<IOThreadExtensionFunction>;
+
+ ~IOThreadExtensionFunction() override;
+
+ void Destruct() const override;
+
+ void SendResponse(bool success) override;
+
+ private:
+ base::WeakPtr<extensions::IOThreadExtensionMessageFilter> ipc_sender_;
+ int routing_id_;
+
+ scoped_refptr<const extensions::InfoMap> extension_info_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(IOThreadExtensionFunction);
+};
+
+// Base class for an extension function that runs asynchronously *relative to
+// the browser's UI thread*.
+class AsyncExtensionFunction : public UIThreadExtensionFunction {
+ public:
+ AsyncExtensionFunction();
+
+ protected:
+ ~AsyncExtensionFunction() override;
+
+ // Deprecated: Override UIThreadExtensionFunction and implement Run() instead.
+ //
+ // AsyncExtensionFunctions implement this method. Return true to indicate that
+ // nothing has gone wrong yet; SendResponse must be called later. Return false
+ // to respond immediately with an error.
+ virtual bool RunAsync() = 0;
+
+ // ValidationFailure override to match RunAsync().
+ static bool ValidationFailure(AsyncExtensionFunction* function);
+
+ private:
+ // If you're hitting a compile error here due to "final" - great! You're
+ // doing the right thing, you just need to extend UIThreadExtensionFunction
+ // instead of AsyncExtensionFunction.
+ ResponseAction Run() final;
+
+ DISALLOW_COPY_AND_ASSIGN(AsyncExtensionFunction);
+};
+
+// A SyncExtensionFunction is an ExtensionFunction that runs synchronously
+// *relative to the browser's UI thread*. Note that this has nothing to do with
+// running synchronously relative to the extension process. From the extension
+// process's point of view, the function is still asynchronous.
+//
+// This kind of function is convenient for implementing simple APIs that just
+// need to interact with things on the browser UI thread.
+class SyncExtensionFunction : public UIThreadExtensionFunction {
+ public:
+ SyncExtensionFunction();
+
+ protected:
+ ~SyncExtensionFunction() override;
+
+ // Deprecated: Override UIThreadExtensionFunction and implement Run() instead.
+ //
+ // SyncExtensionFunctions implement this method. Return true to respond
+ // immediately with success, false to respond immediately with an error.
+ virtual bool RunSync() = 0;
+
+ // ValidationFailure override to match RunSync().
+ static bool ValidationFailure(SyncExtensionFunction* function);
+
+ private:
+ // If you're hitting a compile error here due to "final" - great! You're
+ // doing the right thing, you just need to extend UIThreadExtensionFunction
+ // instead of SyncExtensionFunction.
+ ResponseAction Run() final;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncExtensionFunction);
+};
+
+class SyncIOThreadExtensionFunction : public IOThreadExtensionFunction {
+ public:
+ SyncIOThreadExtensionFunction();
+
+ protected:
+ ~SyncIOThreadExtensionFunction() override;
+
+ // Deprecated: Override IOThreadExtensionFunction and implement Run() instead.
+ //
+ // SyncIOThreadExtensionFunctions implement this method. Return true to
+ // respond immediately with success, false to respond immediately with an
+ // error.
+ virtual bool RunSync() = 0;
+
+ // ValidationFailure override to match RunSync().
+ static bool ValidationFailure(SyncIOThreadExtensionFunction* function);
+
+ private:
+ // If you're hitting a compile error here due to "final" - great! You're
+ // doing the right thing, you just need to extend IOThreadExtensionFunction
+ // instead of SyncIOExtensionFunction.
+ ResponseAction Run() final;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncIOThreadExtensionFunction);
+};
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_FUNCTION_H_
diff --git a/chromium/extensions/browser/extension_function_dispatcher.cc b/chromium/extensions/browser/extension_function_dispatcher.cc
new file mode 100644
index 00000000000..1ad91613b23
--- /dev/null
+++ b/chromium/extensions/browser/extension_function_dispatcher.cc
@@ -0,0 +1,504 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_function_dispatcher.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/process/process.h"
+#include "base/profiler/scoped_profile.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/result_codes.h"
+#include "extensions/browser/api_activity_monitor.h"
+#include "extensions/browser/extension_function_registry.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/io_thread_extension_message_filter.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/browser/process_map.h"
+#include "extensions/browser/quota_service.h"
+#include "extensions/common/extension_api.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/extension_set.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_macros.h"
+
+using content::BrowserThread;
+using content::RenderViewHost;
+
+namespace extensions {
+namespace {
+
+// Notifies the ApiActivityMonitor that an extension API function has been
+// called. May be called from any thread.
+void NotifyApiFunctionCalled(const std::string& extension_id,
+ const std::string& api_name,
+ scoped_ptr<base::ListValue> args,
+ content::BrowserContext* browser_context) {
+ // The ApiActivityMonitor can only be accessed from the main (UI) thread. If
+ // we're running on the wrong thread, re-dispatch from the main thread.
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&NotifyApiFunctionCalled,
+ extension_id,
+ api_name,
+ base::Passed(&args),
+ browser_context));
+ return;
+ }
+ // The BrowserContext may become invalid after the task above is posted.
+ if (!ExtensionsBrowserClient::Get()->IsValidContext(browser_context))
+ return;
+
+ ApiActivityMonitor* monitor =
+ ExtensionsBrowserClient::Get()->GetApiActivityMonitor(browser_context);
+ if (monitor)
+ monitor->OnApiFunctionCalled(extension_id, api_name, std::move(args));
+}
+
+// Separate copy of ExtensionAPI used for IO thread extension functions. We need
+// this because ExtensionAPI has mutable data. It should be possible to remove
+// this once all the extension APIs are updated to the feature system.
+struct Static {
+ Static() : api(ExtensionAPI::CreateWithDefaultConfiguration()) {}
+ scoped_ptr<ExtensionAPI> api;
+};
+base::LazyInstance<Static> g_global_io_data = LAZY_INSTANCE_INITIALIZER;
+
+// Kills the specified process because it sends us a malformed message.
+// Track the specific function's |histogram_value|, as this may indicate a bug
+// in that API's implementation on the renderer.
+void KillBadMessageSender(const base::Process& process,
+ functions::HistogramValue histogram_value) {
+ NOTREACHED();
+ content::RecordAction(base::UserMetricsAction("BadMessageTerminate_EFD"));
+ UMA_HISTOGRAM_ENUMERATION("Extensions.BadMessageFunctionName",
+ histogram_value, functions::ENUM_BOUNDARY);
+ if (process.IsValid())
+ process.Terminate(content::RESULT_CODE_KILLED_BAD_MESSAGE, false);
+}
+
+void CommonResponseCallback(IPC::Sender* ipc_sender,
+ int routing_id,
+ const base::Process& peer_process,
+ int request_id,
+ ExtensionFunction::ResponseType type,
+ const base::ListValue& results,
+ const std::string& error,
+ functions::HistogramValue histogram_value) {
+ DCHECK(ipc_sender);
+
+ if (type == ExtensionFunction::BAD_MESSAGE) {
+ // The renderer has done validation before sending extension api requests.
+ // Therefore, we should never receive a request that is invalid in a way
+ // that JSON validation in the renderer should have caught. It could be an
+ // attacker trying to exploit the browser, so we crash the renderer instead.
+ LOG(ERROR) <<
+ "Terminating renderer because of malformed extension message.";
+ if (content::RenderProcessHost::run_renderer_in_process()) {
+ // In single process mode it is better if we don't suicide but just crash.
+ CHECK(false);
+ } else {
+ KillBadMessageSender(peer_process, histogram_value);
+ }
+ return;
+ }
+
+ ipc_sender->Send(new ExtensionMsg_Response(
+ routing_id, request_id, type == ExtensionFunction::SUCCEEDED, results,
+ error));
+}
+
+void IOThreadResponseCallback(
+ const base::WeakPtr<IOThreadExtensionMessageFilter>& ipc_sender,
+ int routing_id,
+ int request_id,
+ ExtensionFunction::ResponseType type,
+ const base::ListValue& results,
+ const std::string& error,
+ functions::HistogramValue histogram_value) {
+ if (!ipc_sender.get())
+ return;
+
+ base::Process peer_process =
+ base::Process::DeprecatedGetProcessFromHandle(ipc_sender->PeerHandle());
+ CommonResponseCallback(ipc_sender.get(), routing_id, peer_process, request_id,
+ type, results, error, histogram_value);
+}
+
+} // namespace
+
+class ExtensionFunctionDispatcher::UIThreadResponseCallbackWrapper
+ : public content::WebContentsObserver {
+ public:
+ UIThreadResponseCallbackWrapper(
+ const base::WeakPtr<ExtensionFunctionDispatcher>& dispatcher,
+ content::RenderFrameHost* render_frame_host)
+ : content::WebContentsObserver(
+ content::WebContents::FromRenderFrameHost(render_frame_host)),
+ dispatcher_(dispatcher),
+ render_frame_host_(render_frame_host),
+ weak_ptr_factory_(this) {
+ }
+
+ ~UIThreadResponseCallbackWrapper() override {}
+
+ // content::WebContentsObserver overrides.
+ void RenderFrameDeleted(
+ content::RenderFrameHost* render_frame_host) override {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (render_frame_host != render_frame_host_)
+ return;
+
+ if (dispatcher_.get()) {
+ dispatcher_->ui_thread_response_callback_wrappers_
+ .erase(render_frame_host);
+ }
+
+ delete this;
+ }
+
+ ExtensionFunction::ResponseCallback CreateCallback(int request_id) {
+ return base::Bind(
+ &UIThreadResponseCallbackWrapper::OnExtensionFunctionCompleted,
+ weak_ptr_factory_.GetWeakPtr(),
+ request_id);
+ }
+
+ private:
+ void OnExtensionFunctionCompleted(int request_id,
+ ExtensionFunction::ResponseType type,
+ const base::ListValue& results,
+ const std::string& error,
+ functions::HistogramValue histogram_value) {
+ base::Process process =
+ content::RenderProcessHost::run_renderer_in_process()
+ ? base::Process::Current()
+ : base::Process::DeprecatedGetProcessFromHandle(
+ render_frame_host_->GetProcess()->GetHandle());
+ CommonResponseCallback(render_frame_host_,
+ render_frame_host_->GetRoutingID(),
+ process, request_id, type, results, error,
+ histogram_value);
+ }
+
+ base::WeakPtr<ExtensionFunctionDispatcher> dispatcher_;
+ content::RenderFrameHost* render_frame_host_;
+ base::WeakPtrFactory<UIThreadResponseCallbackWrapper> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(UIThreadResponseCallbackWrapper);
+};
+
+WindowController*
+ExtensionFunctionDispatcher::Delegate::GetExtensionWindowController() const {
+ return nullptr;
+}
+
+content::WebContents*
+ExtensionFunctionDispatcher::Delegate::GetAssociatedWebContents() const {
+ return nullptr;
+}
+
+content::WebContents*
+ExtensionFunctionDispatcher::Delegate::GetVisibleWebContents() const {
+ return GetAssociatedWebContents();
+}
+
+bool ExtensionFunctionDispatcher::OverrideFunction(
+ const std::string& name, ExtensionFunctionFactory factory) {
+ return ExtensionFunctionRegistry::GetInstance()->OverrideFunction(name,
+ factory);
+}
+
+// static
+void ExtensionFunctionDispatcher::DispatchOnIOThread(
+ InfoMap* extension_info_map,
+ void* profile_id,
+ int render_process_id,
+ base::WeakPtr<IOThreadExtensionMessageFilter> ipc_sender,
+ int routing_id,
+ const ExtensionHostMsg_Request_Params& params) {
+ const Extension* extension =
+ extension_info_map->extensions().GetByID(params.extension_id);
+
+ ExtensionFunction::ResponseCallback callback(
+ base::Bind(&IOThreadResponseCallback, ipc_sender, routing_id,
+ params.request_id));
+
+ scoped_refptr<ExtensionFunction> function(
+ CreateExtensionFunction(params,
+ extension,
+ render_process_id,
+ extension_info_map->process_map(),
+ g_global_io_data.Get().api.get(),
+ profile_id,
+ callback));
+ if (!function.get())
+ return;
+
+ IOThreadExtensionFunction* function_io =
+ function->AsIOThreadExtensionFunction();
+ if (!function_io) {
+ NOTREACHED();
+ return;
+ }
+ function_io->set_ipc_sender(ipc_sender, routing_id);
+ function_io->set_extension_info_map(extension_info_map);
+ if (extension) {
+ function->set_include_incognito(
+ extension_info_map->IsIncognitoEnabled(extension->id()));
+ }
+
+ if (!CheckPermissions(function.get(), params, callback))
+ return;
+
+ if (!extension) {
+ // Skip all of the UMA, quota, event page, activity logging stuff if there
+ // isn't an extension, e.g. if the function call was from WebUI.
+ function->Run()->Execute();
+ return;
+ }
+
+ QuotaService* quota = extension_info_map->GetQuotaService();
+ std::string violation_error = quota->Assess(extension->id(),
+ function.get(),
+ &params.arguments,
+ base::TimeTicks::Now());
+ if (violation_error.empty()) {
+ scoped_ptr<base::ListValue> args(params.arguments.DeepCopy());
+ NotifyApiFunctionCalled(extension->id(), params.name, std::move(args),
+ static_cast<content::BrowserContext*>(profile_id));
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Extensions.FunctionCalls",
+ function->histogram_value());
+ tracked_objects::ScopedProfile scoped_profile(
+ FROM_HERE_WITH_EXPLICIT_FUNCTION(function->name()),
+ tracked_objects::ScopedProfile::ENABLED);
+ function->Run()->Execute();
+ } else {
+ function->OnQuotaExceeded(violation_error);
+ }
+}
+
+ExtensionFunctionDispatcher::ExtensionFunctionDispatcher(
+ content::BrowserContext* browser_context)
+ : browser_context_(browser_context) {
+}
+
+ExtensionFunctionDispatcher::~ExtensionFunctionDispatcher() {
+}
+
+void ExtensionFunctionDispatcher::Dispatch(
+ const ExtensionHostMsg_Request_Params& params,
+ content::RenderFrameHost* render_frame_host) {
+ UIThreadResponseCallbackWrapperMap::const_iterator
+ iter = ui_thread_response_callback_wrappers_.find(render_frame_host);
+ UIThreadResponseCallbackWrapper* callback_wrapper = nullptr;
+ if (iter == ui_thread_response_callback_wrappers_.end()) {
+ callback_wrapper = new UIThreadResponseCallbackWrapper(AsWeakPtr(),
+ render_frame_host);
+ ui_thread_response_callback_wrappers_[render_frame_host] = callback_wrapper;
+ } else {
+ callback_wrapper = iter->second;
+ }
+
+ DispatchWithCallbackInternal(
+ params, render_frame_host,
+ callback_wrapper->CreateCallback(params.request_id));
+}
+
+void ExtensionFunctionDispatcher::DispatchWithCallbackInternal(
+ const ExtensionHostMsg_Request_Params& params,
+ content::RenderFrameHost* render_frame_host,
+ const ExtensionFunction::ResponseCallback& callback) {
+ DCHECK(render_frame_host);
+ // TODO(yzshen): There is some shared logic between this method and
+ // DispatchOnIOThread(). It is nice to deduplicate.
+ ProcessMap* process_map = ProcessMap::Get(browser_context_);
+ if (!process_map)
+ return;
+
+ ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_);
+ const Extension* extension =
+ registry->enabled_extensions().GetByID(params.extension_id);
+ if (!extension) {
+ extension =
+ registry->enabled_extensions().GetHostedAppByURL(params.source_url);
+ }
+
+ int process_id = render_frame_host->GetProcess()->GetID();
+ scoped_refptr<ExtensionFunction> function(
+ CreateExtensionFunction(params,
+ extension,
+ process_id,
+ *process_map,
+ ExtensionAPI::GetSharedInstance(),
+ browser_context_,
+ callback));
+ if (!function.get())
+ return;
+
+ UIThreadExtensionFunction* function_ui =
+ function->AsUIThreadExtensionFunction();
+ if (!function_ui) {
+ NOTREACHED();
+ return;
+ }
+ function_ui->SetRenderFrameHost(render_frame_host);
+ function_ui->set_dispatcher(AsWeakPtr());
+ function_ui->set_browser_context(browser_context_);
+ if (extension &&
+ ExtensionsBrowserClient::Get()->CanExtensionCrossIncognito(
+ extension, browser_context_)) {
+ function->set_include_incognito(true);
+ }
+
+ if (!CheckPermissions(function.get(), params, callback))
+ return;
+
+ if (!extension) {
+ // Skip all of the UMA, quota, event page, activity logging stuff if there
+ // isn't an extension, e.g. if the function call was from WebUI.
+ function->Run()->Execute();
+ return;
+ }
+
+ // Fetch the ProcessManager before |this| is possibly invalidated.
+ ProcessManager* process_manager = ProcessManager::Get(browser_context_);
+
+ ExtensionSystem* extension_system = ExtensionSystem::Get(browser_context_);
+ QuotaService* quota = extension_system->quota_service();
+ std::string violation_error = quota->Assess(extension->id(),
+ function.get(),
+ &params.arguments,
+ base::TimeTicks::Now());
+
+ if (violation_error.empty()) {
+ scoped_ptr<base::ListValue> args(params.arguments.DeepCopy());
+
+ // See crbug.com/39178.
+ ExtensionsBrowserClient::Get()->PermitExternalProtocolHandler();
+ NotifyApiFunctionCalled(extension->id(), params.name, std::move(args),
+ browser_context_);
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Extensions.FunctionCalls",
+ function->histogram_value());
+ tracked_objects::ScopedProfile scoped_profile(
+ FROM_HERE_WITH_EXPLICIT_FUNCTION(function->name()),
+ tracked_objects::ScopedProfile::ENABLED);
+ function->Run()->Execute();
+ } else {
+ function->OnQuotaExceeded(violation_error);
+ }
+
+ // Note: do not access |this| after this point. We may have been deleted
+ // if function->Run() ended up closing the tab that owns us.
+
+ // Check if extension was uninstalled by management.uninstall.
+ if (!registry->enabled_extensions().GetByID(params.extension_id))
+ return;
+
+ // We only adjust the keepalive count for UIThreadExtensionFunction for
+ // now, largely for simplicity's sake. This is OK because currently, only
+ // the webRequest API uses IOThreadExtensionFunction, and that API is not
+ // compatible with lazy background pages.
+ process_manager->IncrementLazyKeepaliveCount(extension);
+}
+
+void ExtensionFunctionDispatcher::OnExtensionFunctionCompleted(
+ const Extension* extension) {
+ if (extension) {
+ ProcessManager::Get(browser_context_)
+ ->DecrementLazyKeepaliveCount(extension);
+ }
+}
+
+WindowController*
+ExtensionFunctionDispatcher::GetExtensionWindowController() const {
+ return delegate_ ? delegate_->GetExtensionWindowController() : nullptr;
+}
+
+content::WebContents*
+ExtensionFunctionDispatcher::GetAssociatedWebContents() const {
+ return delegate_ ? delegate_->GetAssociatedWebContents() : nullptr;
+}
+
+content::WebContents*
+ExtensionFunctionDispatcher::GetVisibleWebContents() const {
+ return delegate_ ? delegate_->GetVisibleWebContents() :
+ GetAssociatedWebContents();
+}
+
+// static
+bool ExtensionFunctionDispatcher::CheckPermissions(
+ ExtensionFunction* function,
+ const ExtensionHostMsg_Request_Params& params,
+ const ExtensionFunction::ResponseCallback& callback) {
+ if (!function->HasPermission()) {
+ LOG(ERROR) << "Permission denied for " << params.name;
+ SendAccessDenied(callback, function->histogram_value());
+ return false;
+ }
+ return true;
+}
+
+// static
+ExtensionFunction* ExtensionFunctionDispatcher::CreateExtensionFunction(
+ const ExtensionHostMsg_Request_Params& params,
+ const Extension* extension,
+ int requesting_process_id,
+ const ProcessMap& process_map,
+ ExtensionAPI* api,
+ void* profile_id,
+ const ExtensionFunction::ResponseCallback& callback) {
+ ExtensionFunction* function =
+ ExtensionFunctionRegistry::GetInstance()->NewFunction(params.name);
+ if (!function) {
+ LOG(ERROR) << "Unknown Extension API - " << params.name;
+ SendAccessDenied(callback, extensions::functions::UNKNOWN);
+ return NULL;
+ }
+
+ function->SetArgs(&params.arguments);
+ function->set_source_url(params.source_url);
+ function->set_request_id(params.request_id);
+ function->set_has_callback(params.has_callback);
+ function->set_user_gesture(params.user_gesture);
+ function->set_extension(extension);
+ function->set_profile_id(profile_id);
+ function->set_response_callback(callback);
+ function->set_source_tab_id(params.source_tab_id);
+ function->set_source_context_type(
+ process_map.GetMostLikelyContextType(extension, requesting_process_id));
+ function->set_source_process_id(requesting_process_id);
+
+ return function;
+}
+
+// static
+void ExtensionFunctionDispatcher::SendAccessDenied(
+ const ExtensionFunction::ResponseCallback& callback,
+ functions::HistogramValue histogram_value) {
+ base::ListValue empty_list;
+ callback.Run(ExtensionFunction::FAILED, empty_list,
+ "Access to extension API denied.", histogram_value);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_function_dispatcher.h b/chromium/extensions/browser/extension_function_dispatcher.h
new file mode 100644
index 00000000000..be8132e970e
--- /dev/null
+++ b/chromium/extensions/browser/extension_function_dispatcher.h
@@ -0,0 +1,171 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_FUNCTION_DISPATCHER_H_
+#define EXTENSIONS_BROWSER_EXTENSION_FUNCTION_DISPATCHER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "extensions/browser/extension_function.h"
+#include "ipc/ipc_sender.h"
+
+struct ExtensionHostMsg_Request_Params;
+
+namespace content {
+class BrowserContext;
+class RenderFrameHost;
+class WebContents;
+}
+
+namespace extensions {
+
+class Extension;
+class ExtensionAPI;
+class InfoMap;
+class IOThreadExtensionMessageFilter;
+class ProcessMap;
+class WindowController;
+
+// A factory function for creating new ExtensionFunction instances.
+typedef ExtensionFunction* (*ExtensionFunctionFactory)();
+
+// ExtensionFunctionDispatcher receives requests to execute functions from
+// Chrome extensions running in a RenderFrameHost and dispatches them to the
+// appropriate handler. It lives entirely on the UI thread.
+//
+// ExtensionFunctionDispatcher should be a member of some class that hosts
+// RenderFrameHosts and wants them to be able to display extension content.
+// This class should also implement ExtensionFunctionDispatcher::Delegate.
+//
+// Note that a single ExtensionFunctionDispatcher does *not* correspond to a
+// single RVH, a single extension, or a single URL. This is by design so that
+// we can gracefully handle cases like WebContents, where the RVH, extension,
+// and URL can all change over the lifetime of the tab. Instead, these items
+// are all passed into each request.
+class ExtensionFunctionDispatcher
+ : public base::SupportsWeakPtr<ExtensionFunctionDispatcher> {
+ public:
+ class Delegate {
+ public:
+ // Returns the WindowController associated with this delegate, or NULL if no
+ // window is associated with the delegate.
+ virtual WindowController* GetExtensionWindowController() const;
+
+ // Asks the delegate for any relevant WebContents associated with this
+ // context. For example, the WebContents in which an infobar or
+ // chrome-extension://<id> URL are being shown. Callers must check for a
+ // NULL return value (as in the case of a background page).
+ virtual content::WebContents* GetAssociatedWebContents() const;
+
+ // If the associated web contents is not null, returns that. Otherwise,
+ // returns the next most relevant visible web contents. Callers must check
+ // for a NULL return value (as in the case of a background page).
+ virtual content::WebContents* GetVisibleWebContents() const;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ // Override a previously registered function. Returns true if successful,
+ // false if no such function was registered.
+ static bool OverrideFunction(const std::string& name,
+ ExtensionFunctionFactory factory);
+
+ // Dispatches an IO-thread extension function. Only used for specific
+ // functions that must be handled on the IO-thread.
+ static void DispatchOnIOThread(
+ InfoMap* extension_info_map,
+ void* profile_id,
+ int render_process_id,
+ base::WeakPtr<IOThreadExtensionMessageFilter> ipc_sender,
+ int routing_id,
+ const ExtensionHostMsg_Request_Params& params);
+
+ // Public constructor. Callers must ensure that:
+ // - This object outlives any RenderFrameHost's passed to created
+ // ExtensionFunctions.
+ explicit ExtensionFunctionDispatcher(
+ content::BrowserContext* browser_context);
+ ~ExtensionFunctionDispatcher();
+
+ // Message handlers.
+ // The response is sent to the corresponding render view in an
+ // ExtensionMsg_Response message.
+ void Dispatch(const ExtensionHostMsg_Request_Params& params,
+ content::RenderFrameHost* render_frame_host);
+
+ // Called when an ExtensionFunction is done executing, after it has sent
+ // a response (if any) to the extension.
+ void OnExtensionFunctionCompleted(const Extension* extension);
+
+ // See the Delegate class for documentation on these methods.
+ // TODO(devlin): None of these belong here. We should kill
+ // ExtensionFunctionDispatcher::Delegate.
+ WindowController* GetExtensionWindowController() const;
+ content::WebContents* GetAssociatedWebContents() const;
+ content::WebContents* GetVisibleWebContents() const;
+
+ // The BrowserContext that this dispatcher is associated with.
+ content::BrowserContext* browser_context() { return browser_context_; }
+
+ void set_delegate(Delegate* delegate) { delegate_ = delegate; }
+
+ private:
+ // For a given RenderFrameHost instance, UIThreadResponseCallbackWrapper
+ // creates ExtensionFunction::ResponseCallback instances which send responses
+ // to the corresponding render view in ExtensionMsg_Response messages.
+ // This class tracks the lifespan of the RenderFrameHost instance, and will be
+ // destroyed automatically when it goes away.
+ class UIThreadResponseCallbackWrapper;
+
+ // Helper to check whether an ExtensionFunction has the required permissions.
+ // This should be called after the function is fully initialized.
+ // If the check fails, |callback| is run with an access-denied error and false
+ // is returned. |function| must not be run in that case.
+ static bool CheckPermissions(
+ ExtensionFunction* function,
+ const ExtensionHostMsg_Request_Params& params,
+ const ExtensionFunction::ResponseCallback& callback);
+
+ // Helper to create an ExtensionFunction to handle the function given by
+ // |params|. Can be called on any thread.
+ // Does not set subclass properties, or include_incognito.
+ static ExtensionFunction* CreateExtensionFunction(
+ const ExtensionHostMsg_Request_Params& params,
+ const Extension* extension,
+ int requesting_process_id,
+ const ProcessMap& process_map,
+ ExtensionAPI* api,
+ void* profile_id,
+ const ExtensionFunction::ResponseCallback& callback);
+
+ // Helper to run the response callback with an access denied error. Can be
+ // called on any thread.
+ static void SendAccessDenied(
+ const ExtensionFunction::ResponseCallback& callback,
+ functions::HistogramValue histogram_value);
+
+ void DispatchWithCallbackInternal(
+ const ExtensionHostMsg_Request_Params& params,
+ content::RenderFrameHost* render_frame_host,
+ const ExtensionFunction::ResponseCallback& callback);
+
+ content::BrowserContext* browser_context_;
+
+ Delegate* delegate_;
+
+ // This map doesn't own either the keys or the values. When a RenderFrameHost
+ // instance goes away, the corresponding entry in this map (if exists) will be
+ // removed.
+ typedef std::map<content::RenderFrameHost*, UIThreadResponseCallbackWrapper*>
+ UIThreadResponseCallbackWrapperMap;
+ UIThreadResponseCallbackWrapperMap ui_thread_response_callback_wrappers_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_FUNCTION_DISPATCHER_H_
diff --git a/chromium/extensions/browser/extension_function_registry.cc b/chromium/extensions/browser/extension_function_registry.cc
new file mode 100644
index 00000000000..a5b6b6b6e37
--- /dev/null
+++ b/chromium/extensions/browser/extension_function_registry.cc
@@ -0,0 +1,63 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_function_registry.h"
+
+#include "base/memory/singleton.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/browser/extensions_browser_client.h"
+
+// static
+ExtensionFunctionRegistry* ExtensionFunctionRegistry::GetInstance() {
+ return base::Singleton<ExtensionFunctionRegistry>::get();
+}
+
+ExtensionFunctionRegistry::ExtensionFunctionRegistry() {
+ extensions::ExtensionsBrowserClient* client =
+ extensions::ExtensionsBrowserClient::Get();
+ if (client) {
+ client->RegisterExtensionFunctions(this);
+ }
+}
+
+ExtensionFunctionRegistry::~ExtensionFunctionRegistry() {}
+
+bool ExtensionFunctionRegistry::OverrideFunction(
+ const std::string& name,
+ ExtensionFunctionFactory factory) {
+ FactoryMap::iterator iter = factories_.find(name);
+ if (iter == factories_.end()) {
+ return false;
+ } else {
+ iter->second.factory_ = factory;
+ return true;
+ }
+}
+
+ExtensionFunction* ExtensionFunctionRegistry::NewFunction(
+ const std::string& name) {
+ FactoryMap::iterator iter = factories_.find(name);
+ if (iter == factories_.end()) {
+ return NULL;
+ }
+ ExtensionFunction* function = iter->second.factory_();
+ function->set_name(iter->second.function_name_);
+ function->set_histogram_value(iter->second.histogram_value_);
+ return function;
+}
+
+ExtensionFunctionRegistry::FactoryEntry::FactoryEntry()
+ : factory_(0),
+ function_name_(nullptr),
+ histogram_value_(extensions::functions::UNKNOWN) {
+}
+
+ExtensionFunctionRegistry::FactoryEntry::FactoryEntry(
+ ExtensionFunctionFactory factory,
+ const char* function_name,
+ extensions::functions::HistogramValue histogram_value)
+ : factory_(factory),
+ function_name_(function_name),
+ histogram_value_(histogram_value) {
+}
diff --git a/chromium/extensions/browser/extension_host.cc b/chromium/extensions/browser/extension_host.cc
new file mode 100644
index 00000000000..352619a895e
--- /dev/null
+++ b/chromium/extensions/browser/extension_host.cc
@@ -0,0 +1,495 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_host.h"
+
+#include "base/logging.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/profiler/scoped_tracker.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/native_web_keyboard_event.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/site_instance.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/browser/bad_message.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_error.h"
+#include "extensions/browser/extension_host_delegate.h"
+#include "extensions/browser/extension_host_observer.h"
+#include "extensions/browser/extension_host_queue.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/extension_web_contents_observer.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/load_monitoring_extension_host_queue.h"
+#include "extensions/browser/notification_types.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/browser/runtime_data.h"
+#include "extensions/browser/view_type_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/extension_urls.h"
+#include "extensions/common/feature_switch.h"
+#include "extensions/common/manifest_handlers/background_info.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/window_open_disposition.h"
+
+using content::BrowserContext;
+using content::OpenURLParams;
+using content::RenderProcessHost;
+using content::RenderViewHost;
+using content::SiteInstance;
+using content::WebContents;
+
+namespace extensions {
+
+ExtensionHost::ExtensionHost(const Extension* extension,
+ SiteInstance* site_instance,
+ const GURL& url,
+ ViewType host_type)
+ : delegate_(ExtensionsBrowserClient::Get()->CreateExtensionHostDelegate()),
+ extension_(extension),
+ extension_id_(extension->id()),
+ browser_context_(site_instance->GetBrowserContext()),
+ render_view_host_(nullptr),
+ is_render_view_creation_pending_(false),
+ has_loaded_once_(false),
+ document_element_available_(false),
+ initial_url_(url),
+ extension_host_type_(host_type) {
+ // Not used for panels, see PanelHost.
+ DCHECK(host_type == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE ||
+ host_type == VIEW_TYPE_EXTENSION_DIALOG ||
+ host_type == VIEW_TYPE_EXTENSION_POPUP);
+ host_contents_.reset(WebContents::Create(
+ WebContents::CreateParams(browser_context_, site_instance))),
+ content::WebContentsObserver::Observe(host_contents_.get());
+ host_contents_->SetDelegate(this);
+ SetViewType(host_contents_.get(), host_type);
+
+ render_view_host_ = host_contents_->GetRenderViewHost();
+
+ // Listen for when an extension is unloaded from the same profile, as it may
+ // be the same extension that this points to.
+ ExtensionRegistry::Get(browser_context_)->AddObserver(this);
+
+ // Set up web contents observers and pref observers.
+ delegate_->OnExtensionHostCreated(host_contents());
+
+ ExtensionWebContentsObserver::GetForWebContents(host_contents())->
+ dispatcher()->set_delegate(this);
+}
+
+ExtensionHost::~ExtensionHost() {
+ ExtensionRegistry::Get(browser_context_)->RemoveObserver(this);
+
+ if (extension_host_type_ == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE &&
+ extension_ && BackgroundInfo::HasLazyBackgroundPage(extension_) &&
+ load_start_.get()) {
+ UMA_HISTOGRAM_LONG_TIMES("Extensions.EventPageActiveTime2",
+ load_start_->Elapsed());
+ }
+
+ content::NotificationService::current()->Notify(
+ extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED,
+ content::Source<BrowserContext>(browser_context_),
+ content::Details<ExtensionHost>(this));
+ FOR_EACH_OBSERVER(ExtensionHostObserver, observer_list_,
+ OnExtensionHostDestroyed(this));
+ FOR_EACH_OBSERVER(DeferredStartRenderHostObserver,
+ deferred_start_render_host_observer_list_,
+ OnDeferredStartRenderHostDestroyed(this));
+
+ // Remove ourselves from the queue as late as possible (before effectively
+ // destroying self, but after everything else) so that queues that are
+ // monitoring lifetime get a chance to see stop-loading events.
+ delegate_->GetExtensionHostQueue()->Remove(this);
+
+ // Deliberately stop observing |host_contents_| because its destruction
+ // events (like DidStopLoading, it turns out) can call back into
+ // ExtensionHost re-entrantly, when anything declared after |host_contents_|
+ // has already been destroyed.
+ content::WebContentsObserver::Observe(nullptr);
+}
+
+content::RenderProcessHost* ExtensionHost::render_process_host() const {
+ return render_view_host()->GetProcess();
+}
+
+RenderViewHost* ExtensionHost::render_view_host() const {
+ // TODO(mpcomplete): This can be null. How do we handle that?
+ return render_view_host_;
+}
+
+bool ExtensionHost::IsRenderViewLive() const {
+ return render_view_host()->IsRenderViewLive();
+}
+
+void ExtensionHost::CreateRenderViewSoon() {
+ if (render_process_host() && render_process_host()->HasConnection()) {
+ // If the process is already started, go ahead and initialize the RenderView
+ // synchronously. The process creation is the real meaty part that we want
+ // to defer.
+ CreateRenderViewNow();
+ } else {
+ delegate_->GetExtensionHostQueue()->Add(this);
+ }
+}
+
+void ExtensionHost::CreateRenderViewNow() {
+ // TODO(robliao): Remove ScopedTracker below once crbug.com/464206 is fixed.
+ tracked_objects::ScopedTracker tracking_profile1(
+ FROM_HERE_WITH_EXPLICIT_FUNCTION(
+ "464206 ExtensionHost::CreateRenderViewNow1"));
+ if (!ExtensionRegistry::Get(browser_context_)
+ ->ready_extensions()
+ .Contains(extension_->id())) {
+ is_render_view_creation_pending_ = true;
+ return;
+ }
+ is_render_view_creation_pending_ = false;
+ LoadInitialURL();
+ if (IsBackgroundPage()) {
+ // TODO(robliao): Remove ScopedTracker below once crbug.com/464206 is fixed.
+ tracked_objects::ScopedTracker tracking_profile2(
+ FROM_HERE_WITH_EXPLICIT_FUNCTION(
+ "464206 ExtensionHost::CreateRenderViewNow2"));
+ DCHECK(IsRenderViewLive());
+ if (extension_) {
+ std::string group_name = base::FieldTrialList::FindFullName(
+ "ThrottleExtensionBackgroundPages");
+ if ((group_name == "ThrottlePersistent" &&
+ extensions::BackgroundInfo::HasPersistentBackgroundPage(
+ extension_)) ||
+ group_name == "ThrottleAll") {
+ host_contents_->WasHidden();
+ }
+ }
+ // TODO(robliao): Remove ScopedTracker below once crbug.com/464206 is fixed.
+ tracked_objects::ScopedTracker tracking_profile3(
+ FROM_HERE_WITH_EXPLICIT_FUNCTION(
+ "464206 ExtensionHost::CreateRenderViewNow3"));
+ // Connect orphaned dev-tools instances.
+ delegate_->OnRenderViewCreatedForBackgroundPage(this);
+ }
+}
+
+void ExtensionHost::AddDeferredStartRenderHostObserver(
+ DeferredStartRenderHostObserver* observer) {
+ deferred_start_render_host_observer_list_.AddObserver(observer);
+}
+
+void ExtensionHost::RemoveDeferredStartRenderHostObserver(
+ DeferredStartRenderHostObserver* observer) {
+ deferred_start_render_host_observer_list_.RemoveObserver(observer);
+}
+
+void ExtensionHost::Close() {
+ content::NotificationService::current()->Notify(
+ extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE,
+ content::Source<BrowserContext>(browser_context_),
+ content::Details<ExtensionHost>(this));
+}
+
+void ExtensionHost::AddObserver(ExtensionHostObserver* observer) {
+ observer_list_.AddObserver(observer);
+}
+
+void ExtensionHost::RemoveObserver(ExtensionHostObserver* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+void ExtensionHost::OnBackgroundEventDispatched(const std::string& event_name,
+ int event_id) {
+ CHECK(IsBackgroundPage());
+ unacked_messages_.insert(event_id);
+ FOR_EACH_OBSERVER(ExtensionHostObserver, observer_list_,
+ OnBackgroundEventDispatched(this, event_name, event_id));
+}
+
+void ExtensionHost::OnNetworkRequestStarted(uint64_t request_id) {
+ FOR_EACH_OBSERVER(ExtensionHostObserver, observer_list_,
+ OnNetworkRequestStarted(this, request_id));
+}
+
+void ExtensionHost::OnNetworkRequestDone(uint64_t request_id) {
+ FOR_EACH_OBSERVER(ExtensionHostObserver, observer_list_,
+ OnNetworkRequestDone(this, request_id));
+}
+
+const GURL& ExtensionHost::GetURL() const {
+ return host_contents()->GetURL();
+}
+
+void ExtensionHost::LoadInitialURL() {
+ load_start_.reset(new base::ElapsedTimer());
+ host_contents_->GetController().LoadURL(
+ initial_url_, content::Referrer(), ui::PAGE_TRANSITION_LINK,
+ std::string());
+}
+
+bool ExtensionHost::IsBackgroundPage() const {
+ DCHECK_EQ(extension_host_type_, VIEW_TYPE_EXTENSION_BACKGROUND_PAGE);
+ return true;
+}
+
+void ExtensionHost::OnExtensionReady(content::BrowserContext* browser_context,
+ const Extension* extension) {
+ if (is_render_view_creation_pending_)
+ CreateRenderViewNow();
+}
+
+void ExtensionHost::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ // The extension object will be deleted after this notification has been sent.
+ // Null it out so that dirty pointer issues don't arise in cases when multiple
+ // ExtensionHost objects pointing to the same Extension are present.
+ if (extension_ == extension) {
+ extension_ = nullptr;
+ }
+}
+
+void ExtensionHost::RenderProcessGone(base::TerminationStatus status) {
+ // During browser shutdown, we may use sudden termination on an extension
+ // process, so it is expected to lose our connection to the render view.
+ // Do nothing.
+ RenderProcessHost* process_host = host_contents_->GetRenderProcessHost();
+ if (process_host && process_host->FastShutdownStarted())
+ return;
+
+ // In certain cases, multiple ExtensionHost objects may have pointed to
+ // the same Extension at some point (one with a background page and a
+ // popup, for example). When the first ExtensionHost goes away, the extension
+ // is unloaded, and any other host that pointed to that extension will have
+ // its pointer to it null'd out so that any attempt to unload a dirty pointer
+ // will be averted.
+ if (!extension_)
+ return;
+
+ // TODO(aa): This is suspicious. There can be multiple views in an extension,
+ // and they aren't all going to use ExtensionHost. This should be in someplace
+ // more central, like EPM maybe.
+ content::NotificationService::current()->Notify(
+ extensions::NOTIFICATION_EXTENSION_PROCESS_TERMINATED,
+ content::Source<BrowserContext>(browser_context_),
+ content::Details<ExtensionHost>(this));
+}
+
+void ExtensionHost::DidStartLoading() {
+ if (!has_loaded_once_) {
+ FOR_EACH_OBSERVER(DeferredStartRenderHostObserver,
+ deferred_start_render_host_observer_list_,
+ OnDeferredStartRenderHostDidStartFirstLoad(this));
+ }
+}
+
+void ExtensionHost::DidStopLoading() {
+ // Only record UMA for the first load. Subsequent loads will likely behave
+ // quite different, and it's first load we're most interested in.
+ bool first_load = !has_loaded_once_;
+ has_loaded_once_ = true;
+ if (first_load) {
+ RecordStopLoadingUMA();
+ OnDidStopFirstLoad();
+ content::NotificationService::current()->Notify(
+ extensions::NOTIFICATION_EXTENSION_HOST_DID_STOP_FIRST_LOAD,
+ content::Source<BrowserContext>(browser_context_),
+ content::Details<ExtensionHost>(this));
+ FOR_EACH_OBSERVER(DeferredStartRenderHostObserver,
+ deferred_start_render_host_observer_list_,
+ OnDeferredStartRenderHostDidStopFirstLoad(this));
+ }
+}
+
+void ExtensionHost::OnDidStopFirstLoad() {
+ DCHECK_EQ(extension_host_type_, VIEW_TYPE_EXTENSION_BACKGROUND_PAGE);
+ // Nothing to do for background pages.
+}
+
+void ExtensionHost::DocumentAvailableInMainFrame() {
+ // If the document has already been marked as available for this host, then
+ // bail. No need for the redundant setup. http://crbug.com/31170
+ if (document_element_available_)
+ return;
+ document_element_available_ = true;
+
+ if (extension_host_type_ == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) {
+ ExtensionSystem::Get(browser_context_)
+ ->runtime_data()
+ ->SetBackgroundPageReady(extension_->id(), true);
+ content::NotificationService::current()->Notify(
+ extensions::NOTIFICATION_EXTENSION_BACKGROUND_PAGE_READY,
+ content::Source<const Extension>(extension_),
+ content::NotificationService::NoDetails());
+ }
+}
+
+void ExtensionHost::CloseContents(WebContents* contents) {
+ Close();
+}
+
+bool ExtensionHost::OnMessageReceived(const IPC::Message& message,
+ content::RenderFrameHost* host) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(ExtensionHost, message)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_EventAck, OnEventAck)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_IncrementLazyKeepaliveCount,
+ OnIncrementLazyKeepaliveCount)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_DecrementLazyKeepaliveCount,
+ OnDecrementLazyKeepaliveCount)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void ExtensionHost::OnEventAck(int event_id) {
+ EventRouter* router = EventRouter::Get(browser_context_);
+ if (router)
+ router->OnEventAck(browser_context_, extension_id());
+
+ // This should always be false since event acks are only sent by extensions
+ // with lazy background pages but it doesn't hurt to be extra careful.
+ if (!IsBackgroundPage()) {
+ NOTREACHED() << "Received EventAck from extension " << extension_id()
+ << ", which does not have a lazy background page.";
+ return;
+ }
+
+ // A compromised renderer could start sending out arbitrary event ids, which
+ // may affect other renderers by causing downstream methods to think that
+ // events for other extensions have been acked. Make sure that the event id
+ // sent by the renderer is one that this ExtensionHost expects to receive.
+ // This way if a renderer _is_ compromised, it can really only affect itself.
+ if (unacked_messages_.erase(event_id) > 0) {
+ FOR_EACH_OBSERVER(ExtensionHostObserver, observer_list_,
+ OnBackgroundEventAcked(this, event_id));
+ } else {
+ // We have received an unexpected event id from the renderer. It might be
+ // compromised or it might have some other issue. Kill it just to be safe.
+ DCHECK(render_process_host());
+ LOG(ERROR) << "Killing renderer for extension " << extension_id() << " for "
+ << "sending an EventAck message with a bad event id.";
+ bad_message::ReceivedBadMessage(render_process_host(),
+ bad_message::EH_BAD_EVENT_ID);
+ }
+}
+
+void ExtensionHost::OnIncrementLazyKeepaliveCount() {
+ ProcessManager::Get(browser_context_)
+ ->IncrementLazyKeepaliveCount(extension());
+}
+
+void ExtensionHost::OnDecrementLazyKeepaliveCount() {
+ ProcessManager::Get(browser_context_)
+ ->DecrementLazyKeepaliveCount(extension());
+}
+
+// content::WebContentsObserver
+
+void ExtensionHost::RenderViewCreated(RenderViewHost* render_view_host) {
+ render_view_host_ = render_view_host;
+}
+
+void ExtensionHost::RenderViewDeleted(RenderViewHost* render_view_host) {
+ // If our RenderViewHost is deleted, fall back to the host_contents' current
+ // RVH. There is sometimes a small gap between the pending RVH being deleted
+ // and RenderViewCreated being called, so we update it here.
+ if (render_view_host == render_view_host_)
+ render_view_host_ = host_contents_->GetRenderViewHost();
+}
+
+content::JavaScriptDialogManager* ExtensionHost::GetJavaScriptDialogManager(
+ WebContents* source) {
+ return delegate_->GetJavaScriptDialogManager();
+}
+
+void ExtensionHost::AddNewContents(WebContents* source,
+ WebContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_rect,
+ bool user_gesture,
+ bool* was_blocked) {
+ // First, if the creating extension view was associated with a tab contents,
+ // use that tab content's delegate. We must be careful here that the
+ // associated tab contents has the same profile as the new tab contents. In
+ // the case of extensions in 'spanning' incognito mode, they can mismatch.
+ // We don't want to end up putting a normal tab into an incognito window, or
+ // vice versa.
+ // Note that we don't do this for popup windows, because we need to associate
+ // those with their extension_app_id.
+ if (disposition != NEW_POPUP) {
+ WebContents* associated_contents = GetAssociatedWebContents();
+ if (associated_contents &&
+ associated_contents->GetBrowserContext() ==
+ new_contents->GetBrowserContext()) {
+ WebContentsDelegate* delegate = associated_contents->GetDelegate();
+ if (delegate) {
+ delegate->AddNewContents(
+ associated_contents, new_contents, disposition, initial_rect,
+ user_gesture, was_blocked);
+ return;
+ }
+ }
+ }
+
+ delegate_->CreateTab(
+ new_contents, extension_id_, disposition, initial_rect, user_gesture);
+}
+
+void ExtensionHost::RenderViewReady() {
+ content::NotificationService::current()->Notify(
+ extensions::NOTIFICATION_EXTENSION_HOST_CREATED,
+ content::Source<BrowserContext>(browser_context_),
+ content::Details<ExtensionHost>(this));
+}
+
+void ExtensionHost::RequestMediaAccessPermission(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback) {
+ delegate_->ProcessMediaAccessRequest(
+ web_contents, request, callback, extension());
+}
+
+bool ExtensionHost::CheckMediaAccessPermission(
+ content::WebContents* web_contents,
+ const GURL& security_origin,
+ content::MediaStreamType type) {
+ return delegate_->CheckMediaAccessPermission(
+ web_contents, security_origin, type, extension());
+}
+
+bool ExtensionHost::IsNeverVisible(content::WebContents* web_contents) {
+ ViewType view_type = extensions::GetViewType(web_contents);
+ return view_type == extensions::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE;
+}
+
+void ExtensionHost::RecordStopLoadingUMA() {
+ CHECK(load_start_.get());
+ if (extension_host_type_ == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) {
+ if (extension_ && BackgroundInfo::HasLazyBackgroundPage(extension_)) {
+ UMA_HISTOGRAM_MEDIUM_TIMES("Extensions.EventPageLoadTime2",
+ load_start_->Elapsed());
+ } else {
+ UMA_HISTOGRAM_MEDIUM_TIMES("Extensions.BackgroundPageLoadTime2",
+ load_start_->Elapsed());
+ }
+ } else if (extension_host_type_ == VIEW_TYPE_EXTENSION_POPUP) {
+ UMA_HISTOGRAM_MEDIUM_TIMES("Extensions.PopupLoadTime2",
+ load_start_->Elapsed());
+ UMA_HISTOGRAM_MEDIUM_TIMES("Extensions.PopupCreateTime",
+ create_start_.Elapsed());
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_host.h b/chromium/extensions/browser/extension_host.h
new file mode 100644
index 00000000000..50ddf21b4fb
--- /dev/null
+++ b/chromium/extensions/browser/extension_host.h
@@ -0,0 +1,227 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_HOST_H_
+#define EXTENSIONS_BROWSER_EXTENSION_HOST_H_
+
+#include <stdint.h>
+
+#include <set>
+#include <string>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "base/timer/elapsed_timer.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "extensions/browser/deferred_start_render_host.h"
+#include "extensions/browser/extension_function_dispatcher.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/common/stack_frame.h"
+#include "extensions/common/view_type.h"
+
+class PrefsTabHelper;
+
+namespace content {
+class BrowserContext;
+class RenderProcessHost;
+class RenderWidgetHostView;
+class SiteInstance;
+}
+
+namespace extensions {
+class Extension;
+class ExtensionHostDelegate;
+class ExtensionHostObserver;
+class ExtensionHostQueue;
+class WindowController;
+
+// This class is the browser component of an extension component's RenderView.
+// It handles setting up the renderer process, if needed, with special
+// privileges available to extensions. It may have a view to be shown in the
+// browser UI, or it may be hidden.
+//
+// If you are adding code that only affects visible extension views (and not
+// invisible background pages) you should add it to ExtensionViewHost.
+class ExtensionHost : public DeferredStartRenderHost,
+ public content::WebContentsDelegate,
+ public content::WebContentsObserver,
+ public ExtensionFunctionDispatcher::Delegate,
+ public ExtensionRegistryObserver {
+ public:
+ ExtensionHost(const Extension* extension,
+ content::SiteInstance* site_instance,
+ const GURL& url, ViewType host_type);
+ ~ExtensionHost() override;
+
+ const Extension* extension() const { return extension_; }
+ const std::string& extension_id() const { return extension_id_; }
+ content::WebContents* host_contents() const { return host_contents_.get(); }
+ content::RenderViewHost* render_view_host() const;
+ content::RenderProcessHost* render_process_host() const;
+ bool has_loaded_once() const { return has_loaded_once_; }
+ const GURL& initial_url() const { return initial_url_; }
+ bool document_element_available() const {
+ return document_element_available_;
+ }
+
+ content::BrowserContext* browser_context() { return browser_context_; }
+
+ ViewType extension_host_type() const { return extension_host_type_; }
+ const GURL& GetURL() const;
+
+ // Returns true if the render view is initialized and didn't crash.
+ bool IsRenderViewLive() const;
+
+ // Prepares to initializes our RenderViewHost by creating its RenderView and
+ // navigating to this host's url. Uses host_view for the RenderViewHost's view
+ // (can be NULL). This happens delayed to avoid locking the UI.
+ void CreateRenderViewSoon();
+
+ // Closes this host (results in [possibly asynchronous] deletion).
+ void Close();
+
+ // Typical observer interface.
+ void AddObserver(ExtensionHostObserver* observer);
+ void RemoveObserver(ExtensionHostObserver* observer);
+
+ // Called when an event is dispatched to the event page associated with this
+ // ExtensionHost.
+ void OnBackgroundEventDispatched(const std::string& event_name, int event_id);
+
+ // Called by the ProcessManager when a network request is started by the
+ // extension corresponding to this ExtensionHost.
+ void OnNetworkRequestStarted(uint64_t request_id);
+
+ // Called by the ProcessManager when a previously started network request is
+ // finished.
+ void OnNetworkRequestDone(uint64_t request_id);
+
+ // content::WebContentsObserver:
+ bool OnMessageReceived(const IPC::Message& message,
+ content::RenderFrameHost* host) override;
+ void RenderViewCreated(content::RenderViewHost* render_view_host) override;
+ void RenderViewDeleted(content::RenderViewHost* render_view_host) override;
+ void RenderViewReady() override;
+ void RenderProcessGone(base::TerminationStatus status) override;
+ void DocumentAvailableInMainFrame() override;
+ void DidStartLoading() override;
+ void DidStopLoading() override;
+
+ // content::WebContentsDelegate:
+ content::JavaScriptDialogManager* GetJavaScriptDialogManager(
+ content::WebContents* source) override;
+ void AddNewContents(content::WebContents* source,
+ content::WebContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_rect,
+ bool user_gesture,
+ bool* was_blocked) override;
+ void CloseContents(content::WebContents* contents) override;
+ void RequestMediaAccessPermission(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback) override;
+ bool CheckMediaAccessPermission(content::WebContents* web_contents,
+ const GURL& security_origin,
+ content::MediaStreamType type) override;
+ bool IsNeverVisible(content::WebContents* web_contents) override;
+
+ // ExtensionRegistryObserver:
+ void OnExtensionReady(content::BrowserContext* browser_context,
+ const Extension* extension) override;
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+
+ protected:
+ // Called each time this ExtensionHost completes a load finishes loading,
+ // before any stop-loading notifications or observer methods are called.
+ virtual void OnDidStopFirstLoad();
+
+ // Navigates to the initial page.
+ virtual void LoadInitialURL();
+
+ // Returns true if we're hosting a background page.
+ virtual bool IsBackgroundPage() const;
+
+ private:
+ // DeferredStartRenderHost:
+ void CreateRenderViewNow() override;
+ void AddDeferredStartRenderHostObserver(
+ DeferredStartRenderHostObserver* observer) override;
+ void RemoveDeferredStartRenderHostObserver(
+ DeferredStartRenderHostObserver* observer) override;
+
+ // Message handlers.
+ void OnEventAck(int event_id);
+ void OnIncrementLazyKeepaliveCount();
+ void OnDecrementLazyKeepaliveCount();
+
+ // Records UMA for load events.
+ void RecordStopLoadingUMA();
+
+ // Delegate for functionality that cannot exist in the extensions module.
+ scoped_ptr<ExtensionHostDelegate> delegate_;
+
+ // The extension that we're hosting in this view.
+ const Extension* extension_;
+
+ // Id of extension that we're hosting in this view.
+ const std::string extension_id_;
+
+ // The browser context that this host is tied to.
+ content::BrowserContext* browser_context_;
+
+ // The host for our HTML content.
+ scoped_ptr<content::WebContents> host_contents_;
+
+ // A weak pointer to the current or pending RenderViewHost. We don't access
+ // this through the host_contents because we want to deal with the pending
+ // host, so we can send messages to it before it finishes loading.
+ content::RenderViewHost* render_view_host_;
+
+ // Whether CreateRenderViewNow was called before the extension was ready.
+ bool is_render_view_creation_pending_;
+
+ // Whether the ExtensionHost has finished loading some content at least once.
+ // There may be subsequent loads - such as reloads and navigations - and this
+ // will not affect its value (it will remain true).
+ bool has_loaded_once_;
+
+ // True if the main frame has finished parsing.
+ bool document_element_available_;
+
+ // The original URL of the page being hosted.
+ GURL initial_url_;
+
+ // Messages sent out to the renderer that have not been acknowledged yet.
+ std::set<int> unacked_messages_;
+
+ // The type of view being hosted.
+ ViewType extension_host_type_;
+
+ // Measures how long since the ExtensionHost object was created. This can be
+ // used to measure the responsiveness of UI. For example, it's important to
+ // keep this as low as possible for popups. Contrast this to |load_start_|,
+ // for which a low value does not necessarily mean a responsive UI, as
+ // ExtensionHosts may sit in an ExtensionHostQueue for a long time.
+ base::ElapsedTimer create_start_;
+
+ // Measures how long since the initial URL started loading. This timer is
+ // started only once the ExtensionHost has exited the ExtensionHostQueue.
+ scoped_ptr<base::ElapsedTimer> load_start_;
+
+ base::ObserverList<ExtensionHostObserver> observer_list_;
+ base::ObserverList<DeferredStartRenderHostObserver>
+ deferred_start_render_host_observer_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionHost);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_HOST_H_
diff --git a/chromium/extensions/browser/extension_host_delegate.h b/chromium/extensions/browser/extension_host_delegate.h
new file mode 100644
index 00000000000..86726ff0dc8
--- /dev/null
+++ b/chromium/extensions/browser/extension_host_delegate.h
@@ -0,0 +1,76 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_HOST_DELEGATE_H_
+#define EXTENSIONS_BROWSER_EXTENSION_HOST_DELEGATE_H_
+
+#include <string>
+
+#include "content/public/common/media_stream_request.h"
+#include "ui/base/window_open_disposition.h"
+
+namespace content {
+class JavaScriptDialogManager;
+class WebContents;
+}
+
+namespace gfx {
+class Rect;
+}
+
+namespace extensions {
+class Extension;
+class ExtensionHost;
+class ExtensionHostQueue;
+
+// A delegate to support functionality that cannot exist in the extensions
+// module. This is not an inner class of ExtensionHost to allow it to be forward
+// declared.
+class ExtensionHostDelegate {
+ public:
+ virtual ~ExtensionHostDelegate() {}
+
+ // Called after the hosting |web_contents| for an extension is created. The
+ // implementation may wish to add preference observers to |web_contents|.
+ virtual void OnExtensionHostCreated(content::WebContents* web_contents) = 0;
+
+ // Called after |host| creates a RenderView for an extension.
+ virtual void OnRenderViewCreatedForBackgroundPage(ExtensionHost* host) = 0;
+
+ // Returns the embedder's JavaScriptDialogManager or NULL if the embedder
+ // does not support JavaScript dialogs.
+ virtual content::JavaScriptDialogManager* GetJavaScriptDialogManager() = 0;
+
+ // Creates a new tab or popup window with |web_contents|. The embedder may
+ // choose to do nothing if tabs and popups are not supported.
+ virtual void CreateTab(content::WebContents* web_contents,
+ const std::string& extension_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_rect,
+ bool user_gesture) = 0;
+
+ // Requests access to an audio or video media stream. Invokes |callback|
+ // with the response.
+ virtual void ProcessMediaAccessRequest(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback,
+ const Extension* extension) = 0;
+
+ // Checks if we have permission to access the microphone or camera. Note that
+ // this does not query the user. |type| must be MEDIA_DEVICE_AUDIO_CAPTURE
+ // or MEDIA_DEVICE_VIDEO_CAPTURE.
+ virtual bool CheckMediaAccessPermission(content::WebContents* web_contents,
+ const GURL& security_origin,
+ content::MediaStreamType type,
+ const Extension* extension) = 0;
+
+ // Returns the ExtensionHostQueue implementation to use for creating
+ // ExtensionHost renderers.
+ virtual ExtensionHostQueue* GetExtensionHostQueue() const = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_HOST_DELEGATE_H_
diff --git a/chromium/extensions/browser/extension_host_observer.h b/chromium/extensions/browser/extension_host_observer.h
new file mode 100644
index 00000000000..39aa0a8cf75
--- /dev/null
+++ b/chromium/extensions/browser/extension_host_observer.h
@@ -0,0 +1,49 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_HOST_OBSERVER_H_
+#define EXTENSIONS_BROWSER_EXTENSION_HOST_OBSERVER_H_
+
+#include <stdint.h>
+
+#include <string>
+
+namespace extensions {
+class ExtensionHost;
+
+class ExtensionHostObserver {
+ public:
+ virtual ~ExtensionHostObserver() {}
+
+ // TODO(kalman): Why do these all return const ExtensionHosts? It seems
+ // perfectly reasonable for an Observer implementation to mutate any
+ // ExtensionHost it's given.
+
+ // Called when an ExtensionHost is destroyed.
+ virtual void OnExtensionHostDestroyed(const ExtensionHost* host) {}
+
+ // Called when a message has been disptached to the event page corresponding
+ // to |host|.
+ virtual void OnBackgroundEventDispatched(const ExtensionHost* host,
+ const std::string& event_name,
+ int event_id) {}
+
+ // Called when a previously dispatched message has been acked by the
+ // event page for |host|.
+ virtual void OnBackgroundEventAcked(const ExtensionHost* host, int event_id) {
+ }
+
+ // Called when the extension associated with |host| starts a new network
+ // request.
+ virtual void OnNetworkRequestStarted(const ExtensionHost* host,
+ uint64_t request_id) {}
+
+ // Called when the network request with |request_id| is done.
+ virtual void OnNetworkRequestDone(const ExtensionHost* host,
+ uint64_t request_id) {}
+};
+
+} // namespace extensions
+
+#endif /* EXTENSIONS_BROWSER_EXTENSION_HOST_OBSERVER_H_ */
diff --git a/chromium/extensions/browser/extension_host_queue.h b/chromium/extensions/browser/extension_host_queue.h
new file mode 100644
index 00000000000..420800bf240
--- /dev/null
+++ b/chromium/extensions/browser/extension_host_queue.h
@@ -0,0 +1,28 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_HOST_QUEUE_H_
+#define EXTENSIONS_BROWSER_EXTENSION_HOST_QUEUE_H_
+
+namespace extensions {
+
+class DeferredStartRenderHost;
+
+// An interface for a queue of ExtensionHosts waiting for initialization.
+// This is used to implement different throttling strategies.
+class ExtensionHostQueue {
+ public:
+ virtual ~ExtensionHostQueue() {}
+
+ // Adds a host to the queue for RenderView creation.
+ virtual void Add(DeferredStartRenderHost* host) = 0;
+
+ // Removes a host from the queue (for example, it may be deleted before
+ // having a chance to start).
+ virtual void Remove(DeferredStartRenderHost* host) = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_HOST_QUEUE_H_
diff --git a/chromium/extensions/browser/extension_icon_image.cc b/chromium/extensions/browser/extension_icon_image.cc
new file mode 100644
index 00000000000..2cd317d3c46
--- /dev/null
+++ b/chromium/extensions/browser/extension_icon_image.cc
@@ -0,0 +1,265 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_icon_image.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "content/public/browser/notification_service.h"
+#include "extensions/browser/image_loader.h"
+#include "extensions/browser/notification_types.h"
+#include "extensions/common/extension.h"
+#include "ui/base/layout.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/geometry/size_conversions.h"
+#include "ui/gfx/image/canvas_image_source.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/gfx/image/image_skia_source.h"
+
+// The ImageSkia provided by extensions::IconImage contains ImageSkiaReps that
+// are computed and updated using the following algorithm (if no default icon
+// was supplied, transparent icon is considered the default):
+// - |LoadImageForScaleFactors()| searches the extension for an icon of an
+// appropriate size. If the extension doesn't have a icon resource needed for
+// the image representation, the default icon's representation for the
+// requested scale factor is returned by ImageSkiaSource.
+// - If the extension has the resource, IconImage tries to load it using
+// ImageLoader.
+// - |ImageLoader| is asynchronous.
+// - ImageSkiaSource will initially return transparent image resource of the
+// desired size.
+// - The image will be updated with an appropriate image representation when
+// the |ImageLoader| finishes. The image representation is chosen the same
+// way as in the synchronous case. The observer is notified of the image
+// change, unless the added image representation is transparent (in which
+// case the image had already contained the appropriate image
+// representation).
+
+namespace {
+
+extensions::ExtensionResource GetExtensionIconResource(
+ const extensions::Extension* extension,
+ const ExtensionIconSet& icons,
+ int size,
+ ExtensionIconSet::MatchType match_type) {
+ const std::string& path = icons.Get(size, match_type);
+ return path.empty() ? extensions::ExtensionResource()
+ : extension->GetResource(path);
+}
+
+class BlankImageSource : public gfx::CanvasImageSource {
+ public:
+ explicit BlankImageSource(const gfx::Size& size_in_dip)
+ : CanvasImageSource(size_in_dip, /*is_opaque =*/ false) {
+ }
+ ~BlankImageSource() override {}
+
+ private:
+ // gfx::CanvasImageSource overrides:
+ void Draw(gfx::Canvas* canvas) override {
+ canvas->DrawColor(SkColorSetARGB(0, 0, 0, 0));
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(BlankImageSource);
+};
+
+} // namespace
+
+namespace extensions {
+
+////////////////////////////////////////////////////////////////////////////////
+// IconImage::Source
+
+class IconImage::Source : public gfx::ImageSkiaSource {
+ public:
+ Source(IconImage* host, const gfx::Size& size_in_dip);
+ ~Source() override;
+
+ void ResetHost();
+
+ private:
+ // gfx::ImageSkiaSource overrides:
+ gfx::ImageSkiaRep GetImageForScale(float scale) override;
+
+ // Used to load images, possibly asynchronously. NULLed out when the IconImage
+ // is destroyed.
+ IconImage* host_;
+
+ // Image whose representations will be used until |host_| loads the real
+ // representations for the image.
+ gfx::ImageSkia blank_image_;
+
+ DISALLOW_COPY_AND_ASSIGN(Source);
+};
+
+IconImage::Source::Source(IconImage* host, const gfx::Size& size_in_dip)
+ : host_(host),
+ blank_image_(new BlankImageSource(size_in_dip), size_in_dip) {
+}
+
+IconImage::Source::~Source() {
+}
+
+void IconImage::Source::ResetHost() {
+ host_ = NULL;
+}
+
+gfx::ImageSkiaRep IconImage::Source::GetImageForScale(float scale) {
+ gfx::ImageSkiaRep representation;
+ if (host_) {
+ representation =
+ host_->LoadImageForScaleFactor(ui::GetSupportedScaleFactor(scale));
+ }
+
+ if (!representation.is_null())
+ return representation;
+
+ return blank_image_.GetRepresentation(scale);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// IconImage
+
+IconImage::IconImage(
+ content::BrowserContext* context,
+ const Extension* extension,
+ const ExtensionIconSet& icon_set,
+ int resource_size_in_dip,
+ const gfx::ImageSkia& default_icon,
+ Observer* observer)
+ : browser_context_(context),
+ extension_(extension),
+ icon_set_(icon_set),
+ resource_size_in_dip_(resource_size_in_dip),
+ source_(NULL),
+ default_icon_(gfx::ImageSkiaOperations::CreateResizedImage(
+ default_icon,
+ skia::ImageOperations::RESIZE_BEST,
+ gfx::Size(resource_size_in_dip, resource_size_in_dip))),
+ weak_ptr_factory_(this) {
+ if (observer)
+ AddObserver(observer);
+ gfx::Size resource_size(resource_size_in_dip, resource_size_in_dip);
+ source_ = new Source(this, resource_size);
+ image_skia_ = gfx::ImageSkia(source_, resource_size);
+ image_ = gfx::Image(image_skia_);
+
+ registrar_.Add(this,
+ extensions::NOTIFICATION_EXTENSION_REMOVED,
+ content::NotificationService::AllSources());
+}
+
+void IconImage::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void IconImage::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+IconImage::~IconImage() {
+ FOR_EACH_OBSERVER(Observer, observers_, OnExtensionIconImageDestroyed(this));
+ source_->ResetHost();
+}
+
+gfx::ImageSkiaRep IconImage::LoadImageForScaleFactor(
+ ui::ScaleFactor scale_factor) {
+ // Do nothing if extension is unloaded.
+ if (!extension_)
+ return gfx::ImageSkiaRep();
+
+ const float scale = ui::GetScaleForScaleFactor(scale_factor);
+ const int resource_size_in_pixel =
+ static_cast<int>(resource_size_in_dip_ * scale);
+
+ extensions::ExtensionResource resource;
+
+ // Find extension resource for non bundled component extensions.
+ resource = GetExtensionIconResource(extension_,
+ icon_set_,
+ resource_size_in_pixel,
+ ExtensionIconSet::MATCH_BIGGER);
+
+ // If resource is not found by now, try matching smaller one.
+ if (resource.empty()) {
+ resource = GetExtensionIconResource(extension_, icon_set_,
+ resource_size_in_pixel, ExtensionIconSet::MATCH_SMALLER);
+ }
+
+ // If there is no resource found, return default icon.
+ if (resource.empty())
+ return default_icon_.GetRepresentation(scale);
+
+ std::vector<ImageLoader::ImageRepresentation> info_list;
+ info_list.push_back(ImageLoader::ImageRepresentation(
+ resource, ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
+ gfx::ScaleToFlooredSize(
+ gfx::Size(resource_size_in_dip_, resource_size_in_dip_), scale),
+ scale_factor));
+
+ extensions::ImageLoader* loader =
+ extensions::ImageLoader::Get(browser_context_);
+ loader->LoadImagesAsync(extension_, info_list,
+ base::Bind(&IconImage::OnImageLoaded,
+ weak_ptr_factory_.GetWeakPtr(),
+ scale));
+
+ return gfx::ImageSkiaRep();
+}
+
+void IconImage::OnImageLoaded(float scale, const gfx::Image& image_in) {
+ const gfx::ImageSkia* image =
+ image_in.IsEmpty() ? &default_icon_ : image_in.ToImageSkia();
+
+ // Maybe default icon was not set.
+ if (image->isNull())
+ return;
+
+ gfx::ImageSkiaRep rep = image->GetRepresentation(scale);
+ DCHECK(!rep.is_null());
+ DCHECK_EQ(scale, rep.scale());
+
+ // Remove fractional scale image representations as they may have become
+ // stale here. These images are generated by ImageSkia on request from
+ // supported scales like 1x, 2x, etc.
+ // TODO(oshima)
+ // A better approach might be to set the |image_| member using the ImageSkia
+ // copy from the image passed in and set the |image_skia_| member using
+ // image_.ToImageSkia(). However that does not work correctly as the
+ // ImageSkia from the image does not contain a source which breaks requests
+ // for scaled images.
+ std::vector<gfx::ImageSkiaRep> reps = image_skia_.image_reps();
+ for (const auto& image_rep : reps) {
+ if (!ui::IsSupportedScale(image_rep.scale()))
+ image_skia_.RemoveRepresentation(image_rep.scale());
+ }
+
+ image_skia_.RemoveRepresentation(scale);
+ image_skia_.AddRepresentation(rep);
+
+ // Update the image to use the updated image skia.
+ // It's a shame we have to do this because it means that all the other
+ // representations stored on |image_| will be deleted, but unfortunately
+ // there's no way to combine the storage of two images.
+ image_ = gfx::Image(image_skia_);
+
+ FOR_EACH_OBSERVER(Observer, observers_, OnExtensionIconImageChanged(this));
+}
+
+void IconImage::Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ DCHECK_EQ(type, extensions::NOTIFICATION_EXTENSION_REMOVED);
+
+ const Extension* extension = content::Details<const Extension>(details).ptr();
+
+ if (extension_ == extension)
+ extension_ = NULL;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_icon_image.h b/chromium/extensions/browser/extension_icon_image.h
new file mode 100644
index 00000000000..75a4c2d8cb9
--- /dev/null
+++ b/chromium/extensions/browser/extension_icon_image.h
@@ -0,0 +1,129 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_ICON_IMAGE_H_
+#define EXTENSIONS_BROWSER_EXTENSION_ICON_IMAGE_H_
+
+#include <map>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "extensions/common/extension_icon_set.h"
+#include "ui/base/layout.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+class Extension;
+}
+
+namespace gfx {
+class Size;
+class Image;
+}
+
+namespace extensions {
+
+// A class that provides an ImageSkia for UI code to use. It handles extension
+// icon resource loading, screen scale factor change etc. UI code that uses
+// extension icon should host this class. In painting code, UI code paints with
+// the ImageSkia provided by this class. If the required extension icon resource
+// is not already present, this class tries to load it and calls its observer
+// interface when the image get updated. Until the resource is loaded, the UI
+// code will be provided with a blank, transparent image.
+// If the requested resource doesn't exist or can't be loaded and a default
+// icon was supplied in the constructor, icon image will be updated with the
+// default icon's resource.
+// The default icon doesn't need to be supplied, but in that case, icon image
+// representation will be left blank if the resource loading fails.
+// If default icon is supplied, it is assumed that it contains or can
+// synchronously create (when |GetRepresentation| is called on it)
+// representations for all the scale factors supported by the current platform.
+// Note that |IconImage| is not thread safe.
+class IconImage : public content::NotificationObserver {
+ public:
+ class Observer {
+ public:
+ // Invoked when a new image rep for an additional scale factor
+ // is loaded and added to |image|.
+ virtual void OnExtensionIconImageChanged(IconImage* image) = 0;
+
+ // Called when this object is deleted. Objects should observe this if there
+ // is a question about the lifetime of the icon image vs observer.
+ virtual void OnExtensionIconImageDestroyed(IconImage* image) {}
+
+ protected:
+ virtual ~Observer() {}
+ };
+
+ // |context| is required by the underlying implementation to retrieve the
+ // |ImageLoader| instance associated with the given context. |ImageLoader| is
+ // used to perform the asynchronous image load work.
+ IconImage(content::BrowserContext* context,
+ const Extension* extension,
+ const ExtensionIconSet& icon_set,
+ int resource_size_in_dip,
+ const gfx::ImageSkia& default_icon,
+ Observer* observer);
+ ~IconImage() override;
+
+ gfx::Image image() const { return image_; }
+ const gfx::ImageSkia& image_skia() const { return image_skia_; }
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ private:
+ class Source;
+
+ // Loads an image representation for the scale factor.
+ // If the representation gets loaded synchronously, it is returned by this
+ // method.
+ // If representation loading is asynchronous, an empty image
+ // representation is returned. When the representation gets loaded the
+ // observers' OnExtensionIconImageLoaded() will be called.
+ gfx::ImageSkiaRep LoadImageForScaleFactor(ui::ScaleFactor scale_factor);
+
+ void OnImageLoaded(float scale_factor, const gfx::Image& image);
+
+ // content::NotificationObserver overrides:
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override;
+
+ content::BrowserContext* browser_context_;
+ const Extension* extension_;
+ const ExtensionIconSet& icon_set_;
+ const int resource_size_in_dip_;
+
+ base::ObserverList<Observer> observers_;
+
+ Source* source_; // Owned by ImageSkia storage.
+ gfx::ImageSkia image_skia_;
+ // The icon with whose representation |image_skia_| should be updated if
+ // its own representation load fails.
+ gfx::ImageSkia default_icon_;
+
+ // The image wrapper around |image_skia_|.
+ // Note: this is reset each time a new representation is loaded.
+ gfx::Image image_;
+
+ content::NotificationRegistrar registrar_;
+
+ base::WeakPtrFactory<IconImage> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(IconImage);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_ICON_IMAGE_H_
diff --git a/chromium/extensions/browser/extension_icon_image_unittest.cc b/chromium/extensions/browser/extension_icon_image_unittest.cc
new file mode 100644
index 00000000000..dba5e05dbd0
--- /dev/null
+++ b/chromium/extensions/browser/extension_icon_image_unittest.cc
@@ -0,0 +1,553 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_icon_image.h"
+
+#include <vector>
+
+#include "base/json/json_file_value_serializer.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread.h"
+#include "extensions/browser/extensions_test.h"
+#include "extensions/browser/image_loader.h"
+#include "extensions/browser/test_image_loader.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_paths.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_handlers/icons_handler.h"
+#include "skia/ext/image_operations.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/image/image_skia_source.h"
+#include "ui/gfx/skia_util.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+namespace {
+
+SkBitmap CreateBlankBitmapForScale(int size_dip, ui::ScaleFactor scale_factor) {
+ SkBitmap bitmap;
+ const float scale = ui::GetScaleForScaleFactor(scale_factor);
+ bitmap.allocN32Pixels(static_cast<int>(size_dip * scale),
+ static_cast<int>(size_dip * scale));
+ bitmap.eraseColor(SkColorSetARGB(0, 0, 0, 0));
+ return bitmap;
+}
+
+SkBitmap EnsureBitmapSize(const SkBitmap& original, int size) {
+ if (original.width() == size && original.height() == size)
+ return original;
+
+ SkBitmap resized = skia::ImageOperations::Resize(
+ original, skia::ImageOperations::RESIZE_LANCZOS3, size, size);
+ return resized;
+}
+
+// Used to test behavior including images defined by an image skia source.
+// |GetImageForScale| simply returns image representation from the image given
+// in the ctor.
+class MockImageSkiaSource : public gfx::ImageSkiaSource {
+ public:
+ explicit MockImageSkiaSource(const gfx::ImageSkia& image)
+ : image_(image) {
+ }
+ ~MockImageSkiaSource() override {}
+
+ gfx::ImageSkiaRep GetImageForScale(float scale) override {
+ return image_.GetRepresentation(scale);
+ }
+
+ private:
+ gfx::ImageSkia image_;
+};
+
+class ExtensionIconImageTest : public ExtensionsTest,
+ public IconImage::Observer {
+ public:
+ ExtensionIconImageTest()
+ : image_loaded_count_(0),
+ quit_in_image_loaded_(false),
+ ui_thread_(BrowserThread::UI, &ui_loop_),
+ file_thread_(BrowserThread::FILE),
+ io_thread_(BrowserThread::IO),
+ notification_service_(content::NotificationService::Create()) {}
+
+ ~ExtensionIconImageTest() override {}
+
+ void WaitForImageLoad() {
+ quit_in_image_loaded_ = true;
+ base::MessageLoop::current()->Run();
+ quit_in_image_loaded_ = false;
+ }
+
+ int ImageLoadedCount() {
+ int result = image_loaded_count_;
+ image_loaded_count_ = 0;
+ return result;
+ }
+
+ scoped_refptr<Extension> CreateExtension(const char* name,
+ Manifest::Location location) {
+ // Create and load an extension.
+ base::FilePath test_file;
+ if (!PathService::Get(DIR_TEST_DATA, &test_file)) {
+ EXPECT_FALSE(true);
+ return NULL;
+ }
+ test_file = test_file.AppendASCII(name);
+ int error_code = 0;
+ std::string error;
+ JSONFileValueDeserializer deserializer(
+ test_file.AppendASCII("manifest.json"));
+ scoped_ptr<base::DictionaryValue> valid_value = base::DictionaryValue::From(
+ deserializer.Deserialize(&error_code, &error));
+ EXPECT_EQ(0, error_code) << error;
+ if (error_code != 0)
+ return NULL;
+
+ EXPECT_TRUE(valid_value.get());
+ if (!valid_value)
+ return NULL;
+
+ return Extension::Create(test_file, location, *valid_value,
+ Extension::NO_FLAGS, &error);
+ }
+
+ // testing::Test overrides:
+ void SetUp() override {
+ file_thread_.Start();
+ io_thread_.Start();
+ }
+
+ // IconImage::Delegate overrides:
+ void OnExtensionIconImageChanged(IconImage* image) override {
+ image_loaded_count_++;
+ if (quit_in_image_loaded_)
+ base::MessageLoop::current()->QuitWhenIdle();
+ }
+
+ gfx::ImageSkia GetDefaultIcon() {
+ return gfx::ImageSkia(gfx::ImageSkiaRep(gfx::Size(16, 16), 1.0f));
+ }
+
+ private:
+ int image_loaded_count_;
+ bool quit_in_image_loaded_;
+ base::MessageLoop ui_loop_;
+ content::TestBrowserThread ui_thread_;
+ content::TestBrowserThread file_thread_;
+ content::TestBrowserThread io_thread_;
+ scoped_ptr<content::NotificationService> notification_service_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionIconImageTest);
+};
+
+} // namespace
+
+TEST_F(ExtensionIconImageTest, Basic) {
+ std::vector<ui::ScaleFactor> supported_factors;
+ supported_factors.push_back(ui::SCALE_FACTOR_100P);
+ supported_factors.push_back(ui::SCALE_FACTOR_200P);
+ ui::test::ScopedSetSupportedScaleFactors scoped_supported(supported_factors);
+ scoped_refptr<Extension> extension(CreateExtension(
+ "extension_icon_image", Manifest::INVALID_LOCATION));
+ ASSERT_TRUE(extension.get() != NULL);
+
+ gfx::ImageSkia default_icon = GetDefaultIcon();
+
+ // Load images we expect to find as representations in icon_image, so we
+ // can later use them to validate icon_image.
+ SkBitmap bitmap_16 =
+ TestImageLoader::LoadAndGetExtensionBitmap(extension.get(), "16.png", 16);
+ ASSERT_FALSE(bitmap_16.empty());
+
+ // There is no image of size 32 defined in the extension manifest, so we
+ // should expect manifest image of size 48 resized to size 32.
+ SkBitmap bitmap_48_resized_to_32 =
+ TestImageLoader::LoadAndGetExtensionBitmap(extension.get(), "48.png", 32);
+ ASSERT_FALSE(bitmap_48_resized_to_32.empty());
+
+ IconImage image(browser_context(),
+ extension.get(),
+ IconsInfo::GetIcons(extension.get()),
+ 16,
+ default_icon,
+ this);
+
+ // No representations in |image_| yet.
+ gfx::ImageSkia::ImageSkiaReps image_reps = image.image_skia().image_reps();
+ ASSERT_EQ(0u, image_reps.size());
+
+ // Gets representation for a scale factor.
+ gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
+
+ // Before the image representation is loaded, image should contain blank
+ // image representation.
+ EXPECT_TRUE(gfx::BitmapsAreEqual(
+ representation.sk_bitmap(),
+ CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_100P)));
+
+ WaitForImageLoad();
+ EXPECT_EQ(1, ImageLoadedCount());
+ ASSERT_EQ(1u, image.image_skia().image_reps().size());
+
+ representation = image.image_skia().GetRepresentation(1.0f);
+
+ // We should get the right representation now.
+ EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(), bitmap_16));
+ EXPECT_EQ(16, representation.pixel_width());
+
+ // Gets representation for an additional scale factor.
+ representation = image.image_skia().GetRepresentation(2.0f);
+
+ EXPECT_TRUE(gfx::BitmapsAreEqual(
+ representation.sk_bitmap(),
+ CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_200P)));
+
+ WaitForImageLoad();
+ EXPECT_EQ(1, ImageLoadedCount());
+ ASSERT_EQ(2u, image.image_skia().image_reps().size());
+
+ representation = image.image_skia().GetRepresentation(2.0f);
+
+ // Image should have been resized.
+ EXPECT_EQ(32, representation.pixel_width());
+ EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(),
+ bitmap_48_resized_to_32));
+}
+
+// There is no resource with either exact or bigger size, but there is a smaller
+// resource.
+TEST_F(ExtensionIconImageTest, FallbackToSmallerWhenNoBigger) {
+ std::vector<ui::ScaleFactor> supported_factors;
+ supported_factors.push_back(ui::SCALE_FACTOR_100P);
+ supported_factors.push_back(ui::SCALE_FACTOR_200P);
+ ui::test::ScopedSetSupportedScaleFactors scoped_supported(supported_factors);
+ scoped_refptr<Extension> extension(CreateExtension(
+ "extension_icon_image", Manifest::INVALID_LOCATION));
+ ASSERT_TRUE(extension.get() != NULL);
+
+ gfx::ImageSkia default_icon = GetDefaultIcon();
+
+ // Load images we expect to find as representations in icon_image, so we
+ // can later use them to validate icon_image.
+ SkBitmap bitmap_48 =
+ TestImageLoader::LoadAndGetExtensionBitmap(extension.get(), "48.png", 48);
+ ASSERT_FALSE(bitmap_48.empty());
+
+ IconImage image(browser_context(),
+ extension.get(),
+ IconsInfo::GetIcons(extension.get()),
+ 32,
+ default_icon,
+ this);
+
+ gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(2.0f);
+
+ WaitForImageLoad();
+ EXPECT_EQ(1, ImageLoadedCount());
+ ASSERT_EQ(1u, image.image_skia().image_reps().size());
+
+ representation = image.image_skia().GetRepresentation(2.0f);
+
+ // We should have loaded the biggest smaller resource resized to the actual
+ // size.
+ EXPECT_EQ(2.0f, representation.scale());
+ EXPECT_EQ(64, representation.pixel_width());
+ EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(),
+ EnsureBitmapSize(bitmap_48, 64)));
+}
+
+// There is no resource with exact size, but there is a smaller and a bigger
+// one. The bigger resource should be loaded.
+TEST_F(ExtensionIconImageTest, FallbackToBigger) {
+ scoped_refptr<Extension> extension(CreateExtension(
+ "extension_icon_image", Manifest::INVALID_LOCATION));
+ ASSERT_TRUE(extension.get() != NULL);
+
+ gfx::ImageSkia default_icon = GetDefaultIcon();
+
+ // Load images we expect to find as representations in icon_image, so we
+ // can later use them to validate icon_image.
+ SkBitmap bitmap_24 =
+ TestImageLoader::LoadAndGetExtensionBitmap(extension.get(), "24.png", 24);
+ ASSERT_FALSE(bitmap_24.empty());
+
+ IconImage image(browser_context(),
+ extension.get(),
+ IconsInfo::GetIcons(extension.get()),
+ 17,
+ default_icon,
+ this);
+
+ gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
+
+ WaitForImageLoad();
+ EXPECT_EQ(1, ImageLoadedCount());
+ ASSERT_EQ(1u, image.image_skia().image_reps().size());
+
+ representation = image.image_skia().GetRepresentation(1.0f);
+
+ // We should have loaded the smallest bigger (resized) resource.
+ EXPECT_EQ(1.0f, representation.scale());
+ EXPECT_EQ(17, representation.pixel_width());
+ EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(),
+ EnsureBitmapSize(bitmap_24, 17)));
+}
+
+// If resource set is empty, |GetRepresentation| should synchronously return
+// default icon, without notifying observer of image change.
+TEST_F(ExtensionIconImageTest, NoResources) {
+ scoped_refptr<Extension> extension(CreateExtension(
+ "extension_icon_image", Manifest::INVALID_LOCATION));
+ ASSERT_TRUE(extension.get() != NULL);
+
+ ExtensionIconSet empty_icon_set;
+ gfx::ImageSkia default_icon = GetDefaultIcon();
+
+ const int kRequestedSize = 24;
+ IconImage image(browser_context(),
+ extension.get(),
+ empty_icon_set,
+ kRequestedSize,
+ default_icon,
+ this);
+
+ gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
+ EXPECT_TRUE(gfx::BitmapsAreEqual(
+ representation.sk_bitmap(),
+ EnsureBitmapSize(
+ default_icon.GetRepresentation(1.0f).sk_bitmap(),
+ kRequestedSize)));
+
+ EXPECT_EQ(0, ImageLoadedCount());
+ // We should have a default icon representation.
+ ASSERT_EQ(1u, image.image_skia().image_reps().size());
+
+ representation = image.image_skia().GetRepresentation(1.0f);
+ EXPECT_TRUE(gfx::BitmapsAreEqual(
+ representation.sk_bitmap(),
+ EnsureBitmapSize(
+ default_icon.GetRepresentation(1.0f).sk_bitmap(),
+ kRequestedSize)));
+}
+
+// If resource set is invalid, image load should be done asynchronously and
+// the observer should be notified when it's done. |GetRepresentation| should
+// return the default icon representation once image load is done.
+TEST_F(ExtensionIconImageTest, InvalidResource) {
+ scoped_refptr<Extension> extension(CreateExtension(
+ "extension_icon_image", Manifest::INVALID_LOCATION));
+ ASSERT_TRUE(extension.get() != NULL);
+
+ const int kInvalidIconSize = 24;
+ ExtensionIconSet invalid_icon_set;
+ invalid_icon_set.Add(kInvalidIconSize, "invalid.png");
+
+ gfx::ImageSkia default_icon = GetDefaultIcon();
+
+ IconImage image(browser_context(),
+ extension.get(),
+ invalid_icon_set,
+ kInvalidIconSize,
+ default_icon,
+ this);
+
+ gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
+ EXPECT_TRUE(gfx::BitmapsAreEqual(
+ representation.sk_bitmap(),
+ CreateBlankBitmapForScale(kInvalidIconSize, ui::SCALE_FACTOR_100P)));
+
+ WaitForImageLoad();
+ EXPECT_EQ(1, ImageLoadedCount());
+ // We should have default icon representation now.
+ ASSERT_EQ(1u, image.image_skia().image_reps().size());
+
+ representation = image.image_skia().GetRepresentation(1.0f);
+ EXPECT_TRUE(gfx::BitmapsAreEqual(
+ representation.sk_bitmap(),
+ EnsureBitmapSize(
+ default_icon.GetRepresentation(1.0f).sk_bitmap(),
+ kInvalidIconSize)));
+}
+
+// Test that IconImage works with lazily (but synchronously) created default
+// icon when IconImage returns synchronously.
+TEST_F(ExtensionIconImageTest, LazyDefaultIcon) {
+ scoped_refptr<Extension> extension(CreateExtension(
+ "extension_icon_image", Manifest::INVALID_LOCATION));
+ ASSERT_TRUE(extension.get() != NULL);
+
+ gfx::ImageSkia default_icon = GetDefaultIcon();
+ gfx::ImageSkia lazy_default_icon(new MockImageSkiaSource(default_icon),
+ default_icon.size());
+
+ ExtensionIconSet empty_icon_set;
+
+ const int kRequestedSize = 128;
+ IconImage image(browser_context(),
+ extension.get(),
+ empty_icon_set,
+ kRequestedSize,
+ lazy_default_icon,
+ this);
+
+ ASSERT_FALSE(lazy_default_icon.HasRepresentation(1.0f));
+
+ gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
+
+ // The resouce set is empty, so we should get the result right away.
+ EXPECT_TRUE(lazy_default_icon.HasRepresentation(1.0f));
+ EXPECT_TRUE(gfx::BitmapsAreEqual(
+ representation.sk_bitmap(),
+ EnsureBitmapSize(
+ default_icon.GetRepresentation(1.0f).sk_bitmap(),
+ kRequestedSize)));
+
+ // We should have a default icon representation.
+ ASSERT_EQ(1u, image.image_skia().image_reps().size());
+}
+
+// Test that IconImage works with lazily (but synchronously) created default
+// icon when IconImage returns asynchronously.
+TEST_F(ExtensionIconImageTest, LazyDefaultIcon_AsyncIconImage) {
+ scoped_refptr<Extension> extension(CreateExtension(
+ "extension_icon_image", Manifest::INVALID_LOCATION));
+ ASSERT_TRUE(extension.get() != NULL);
+
+ gfx::ImageSkia default_icon = GetDefaultIcon();
+ gfx::ImageSkia lazy_default_icon(new MockImageSkiaSource(default_icon),
+ default_icon.size());
+
+ const int kInvalidIconSize = 24;
+ ExtensionIconSet invalid_icon_set;
+ invalid_icon_set.Add(kInvalidIconSize, "invalid.png");
+
+ IconImage image(browser_context(),
+ extension.get(),
+ invalid_icon_set,
+ kInvalidIconSize,
+ lazy_default_icon,
+ this);
+
+ ASSERT_FALSE(lazy_default_icon.HasRepresentation(1.0f));
+
+ gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
+
+ WaitForImageLoad();
+ EXPECT_EQ(1, ImageLoadedCount());
+ // We should have default icon representation now.
+ ASSERT_EQ(1u, image.image_skia().image_reps().size());
+
+ EXPECT_TRUE(lazy_default_icon.HasRepresentation(1.0f));
+
+ representation = image.image_skia().GetRepresentation(1.0f);
+ EXPECT_TRUE(gfx::BitmapsAreEqual(
+ representation.sk_bitmap(),
+ EnsureBitmapSize(
+ default_icon.GetRepresentation(1.0f).sk_bitmap(),
+ kInvalidIconSize)));
+}
+
+// Tests behavior of image created by IconImage after IconImage host goes
+// away. The image should still return loaded representations. If requested
+// representation was not loaded while IconImage host was around, transparent
+// representations should be returned.
+TEST_F(ExtensionIconImageTest, IconImageDestruction) {
+ scoped_refptr<Extension> extension(CreateExtension(
+ "extension_icon_image", Manifest::INVALID_LOCATION));
+ ASSERT_TRUE(extension.get() != NULL);
+
+ gfx::ImageSkia default_icon = GetDefaultIcon();
+
+ // Load images we expect to find as representations in icon_image, so we
+ // can later use them to validate icon_image.
+ SkBitmap bitmap_16 =
+ TestImageLoader::LoadAndGetExtensionBitmap(extension.get(), "16.png", 16);
+ ASSERT_FALSE(bitmap_16.empty());
+
+ scoped_ptr<IconImage> image(
+ new IconImage(browser_context(),
+ extension.get(),
+ IconsInfo::GetIcons(extension.get()),
+ 16,
+ default_icon,
+ this));
+
+ // Load an image representation.
+ gfx::ImageSkiaRep representation =
+ image->image_skia().GetRepresentation(1.0f);
+
+ WaitForImageLoad();
+ EXPECT_EQ(1, ImageLoadedCount());
+ ASSERT_EQ(1u, image->image_skia().image_reps().size());
+
+ // Stash loaded image skia, and destroy |image|.
+ gfx::ImageSkia image_skia = image->image_skia();
+ image.reset();
+ extension = NULL;
+
+ // Image skia should still be able to get previously loaded representation.
+ representation = image_skia.GetRepresentation(1.0f);
+
+ EXPECT_EQ(1.0f, representation.scale());
+ EXPECT_EQ(16, representation.pixel_width());
+ EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(), bitmap_16));
+
+ // When requesting another representation, we should not crash and return some
+ // image of the size. It could be blank or a rescale from the existing 1.0f
+ // icon.
+ representation = image_skia.GetRepresentation(2.0f);
+
+ EXPECT_EQ(16, representation.GetWidth());
+ EXPECT_EQ(16, representation.GetHeight());
+ EXPECT_EQ(2.0f, representation.scale());
+}
+
+// Test that new representations added to the image of an IconImage are cached
+// for future use.
+TEST_F(ExtensionIconImageTest, ImageCachesNewRepresentations) {
+ // Load up an extension and create an icon image.
+ scoped_refptr<Extension> extension(
+ CreateExtension("extension_icon_image", Manifest::INVALID_LOCATION));
+ ASSERT_TRUE(extension.get() != NULL);
+ gfx::ImageSkia default_icon = GetDefaultIcon();
+ scoped_ptr<IconImage> icon_image(
+ new IconImage(browser_context(),
+ extension.get(),
+ IconsInfo::GetIcons(extension.get()),
+ 16,
+ default_icon,
+ this));
+
+ // Load an image representation.
+ gfx::ImageSkiaRep representation =
+ icon_image->image_skia().GetRepresentation(1.0f);
+ WaitForImageLoad();
+
+ // Cache for later use.
+ gfx::Image prior_image = icon_image->image();
+
+ // Now the fun part: access the image from the IconImage, and create a png
+ // representation of it.
+ gfx::Image image = icon_image->image();
+ scoped_refptr<base::RefCountedMemory> image_as_png = image.As1xPNGBytes();
+
+ // Access the image from the IconImage again, and get a png representation.
+ // The two png representations should be exactly equal (the same object),
+ // because image storage is shared, so when we created one from the first
+ // image, all other images should also have that representation...
+ gfx::Image image2 = icon_image->image();
+ EXPECT_EQ(image_as_png.get(), image2.As1xPNGBytes().get());
+
+ // ...even images that were copied before the representation was constructed.
+ EXPECT_EQ(image_as_png.get(), prior_image.As1xPNGBytes().get());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_icon_placeholder.cc b/chromium/extensions/browser/extension_icon_placeholder.cc
new file mode 100644
index 00000000000..fc372864004
--- /dev/null
+++ b/chromium/extensions/browser/extension_icon_placeholder.cc
@@ -0,0 +1,103 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_icon_placeholder.h"
+
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/grit/extensions_browser_resources.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/image/canvas_image_source.h"
+#include "ui/gfx/image/image_skia.h"
+
+namespace extensions {
+
+namespace {
+
+// Returns the FontStyle to use for the given icon |size|.
+ui::ResourceBundle::FontStyle GetFontStyleForIconSize(
+ extension_misc::ExtensionIcons size) {
+ switch (size) {
+ case extension_misc::EXTENSION_ICON_INVALID:
+ case extension_misc::EXTENSION_ICON_BITTY:
+ return ui::ResourceBundle::SmallFont;
+ case extension_misc::EXTENSION_ICON_ACTION:
+ case extension_misc::EXTENSION_ICON_SMALLISH:
+ case extension_misc::EXTENSION_ICON_SMALL:
+ return ui::ResourceBundle::MediumFont;
+ case extension_misc::EXTENSION_ICON_MEDIUM:
+ case extension_misc::EXTENSION_ICON_LARGE:
+ case extension_misc::EXTENSION_ICON_EXTRA_LARGE:
+ case extension_misc::EXTENSION_ICON_GIGANTOR:
+ return ui::ResourceBundle::LargeFont;
+ }
+ NOTREACHED();
+ return ui::ResourceBundle::MediumFont;
+}
+
+// Returns the background image to use for the given icon |size|.
+gfx::Image GetBackgroundImageForIconSize(extension_misc::ExtensionIcons size) {
+ int resource_id = 0;
+ // Right now, we have resources for a 19x19 (action) and a 48x48 (extensions
+ // page icon). The implementation of the placeholder scales these correctly,
+ // so it's not a big deal to use these for other sizes, but if it's something
+ // that will be done frequently, we should probably make a devoted asset for
+ // that size.
+ switch (size) {
+ case extension_misc::EXTENSION_ICON_INVALID:
+ case extension_misc::EXTENSION_ICON_BITTY:
+ case extension_misc::EXTENSION_ICON_ACTION:
+ case extension_misc::EXTENSION_ICON_SMALLISH:
+ case extension_misc::EXTENSION_ICON_SMALL:
+ resource_id = IDR_EXTENSION_ACTION_PLAIN_BACKGROUND;
+ break;
+ case extension_misc::EXTENSION_ICON_MEDIUM:
+ case extension_misc::EXTENSION_ICON_LARGE:
+ case extension_misc::EXTENSION_ICON_EXTRA_LARGE:
+ case extension_misc::EXTENSION_ICON_GIGANTOR:
+ resource_id = IDR_EXTENSION_ICON_PLAIN_BACKGROUND;
+ break;
+ }
+ return ui::ResourceBundle::GetSharedInstance().GetImageNamed(resource_id);
+}
+
+} // namespace
+
+ExtensionIconPlaceholder::ExtensionIconPlaceholder(
+ extension_misc::ExtensionIcons size,
+ const std::string& letter)
+ : gfx::CanvasImageSource(gfx::Size(size, size), false),
+ icon_size_(size),
+ letter_(base::UTF8ToUTF16(letter.substr(0, 1))),
+ base_image_(GetBackgroundImageForIconSize(size)) {
+}
+
+ExtensionIconPlaceholder::~ExtensionIconPlaceholder() {
+}
+
+gfx::Image ExtensionIconPlaceholder::CreateImage(
+ extension_misc::ExtensionIcons size,
+ const std::string& name) {
+ return gfx::Image(gfx::ImageSkia(new ExtensionIconPlaceholder(size, name),
+ gfx::Size(size, size)));
+}
+
+void ExtensionIconPlaceholder::Draw(gfx::Canvas* canvas) {
+ // Draw the background image, correctly scaled.
+ canvas->DrawImageInt(*base_image_.ToImageSkia(), 0, 0,
+ base_image_.Size().width(), base_image_.Size().height(),
+ 0, 0, size().width(), size().height(), true);
+ gfx::Rect bounds(size().width(), size().height());
+ // Draw the letter on top.
+ canvas->DrawStringRectWithFlags(
+ letter_, ui::ResourceBundle::GetSharedInstance().GetFontList(
+ GetFontStyleForIconSize(icon_size_)),
+ SK_ColorWHITE, bounds, gfx::Canvas::TEXT_ALIGN_CENTER);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_icon_placeholder.h b/chromium/extensions/browser/extension_icon_placeholder.h
new file mode 100644
index 00000000000..5c5e90238f9
--- /dev/null
+++ b/chromium/extensions/browser/extension_icon_placeholder.h
@@ -0,0 +1,54 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_ICON_PLACEHOLDER_H_
+#define EXTENSIONS_BROWSER_EXTENSION_ICON_PLACEHOLDER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "extensions/common/constants.h"
+#include "ui/gfx/image/canvas_image_source.h"
+#include "ui/gfx/image/image.h"
+
+namespace gfx {
+class Canvas;
+class Size;
+}
+
+namespace extensions {
+
+// An extension icon image with a gray background and the first letter of the
+// extension name, so that not all extensions without an icon look the same.
+class ExtensionIconPlaceholder : public gfx::CanvasImageSource {
+ public:
+ ExtensionIconPlaceholder(extension_misc::ExtensionIcons size,
+ const std::string& name);
+ ~ExtensionIconPlaceholder() override;
+
+ // Creates an image backed by an ImageSkia with the ExtensionIconPlaceholder
+ // as its image source.
+ static gfx::Image CreateImage(extension_misc::ExtensionIcons size,
+ const std::string& name);
+
+ private:
+ // gfx::CanvasImageSource:
+ void Draw(gfx::Canvas* canvas) override;
+
+ // The size this placeholder is filling.
+ extension_misc::ExtensionIcons icon_size_;
+
+ // The first letter of the extension's name.
+ base::string16 letter_;
+
+ // The gray background image, on top of which the letter is drawn.
+ gfx::Image base_image_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionIconPlaceholder);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_ICON_PLACEHOLDER_H_
diff --git a/chromium/extensions/browser/extension_message_filter.cc b/chromium/extensions/browser/extension_message_filter.cc
new file mode 100644
index 00000000000..6bdfac024e5
--- /dev/null
+++ b/chromium/extensions/browser/extension_message_filter.cc
@@ -0,0 +1,321 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_message_filter.h"
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "components/crx_file/id_util.h"
+#include "components/keyed_service/content/browser_context_keyed_service_shutdown_notifier_factory.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "extensions/browser/blob_holder.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/event_router_factory.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/browser/process_manager_factory.h"
+#include "extensions/browser/process_map.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/manifest_handlers/background_info.h"
+#include "ipc/ipc_message_macros.h"
+
+using content::BrowserThread;
+using content::RenderProcessHost;
+
+namespace extensions {
+
+namespace {
+
+class ShutdownNotifierFactory
+ : public BrowserContextKeyedServiceShutdownNotifierFactory {
+ public:
+ static ShutdownNotifierFactory* GetInstance() {
+ return base::Singleton<ShutdownNotifierFactory>::get();
+ }
+
+ private:
+ friend struct base::DefaultSingletonTraits<ShutdownNotifierFactory>;
+
+ ShutdownNotifierFactory()
+ : BrowserContextKeyedServiceShutdownNotifierFactory(
+ "ExtensionMessageFilter") {
+ DependsOn(EventRouterFactory::GetInstance());
+ DependsOn(ProcessManagerFactory::GetInstance());
+ }
+ ~ShutdownNotifierFactory() override {}
+
+ DISALLOW_COPY_AND_ASSIGN(ShutdownNotifierFactory);
+};
+
+} // namespace
+
+ExtensionMessageFilter::ExtensionMessageFilter(int render_process_id,
+ content::BrowserContext* context)
+ : BrowserMessageFilter(ExtensionMsgStart),
+ render_process_id_(render_process_id),
+ browser_context_(context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ shutdown_notifier_ =
+ ShutdownNotifierFactory::GetInstance()->Get(context)->Subscribe(
+ base::Bind(&ExtensionMessageFilter::ShutdownOnUIThread,
+ base::Unretained(this)));
+}
+
+void ExtensionMessageFilter::EnsureShutdownNotifierFactoryBuilt() {
+ ShutdownNotifierFactory::GetInstance();
+}
+
+ExtensionMessageFilter::~ExtensionMessageFilter() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+EventRouter* ExtensionMessageFilter::GetEventRouter() {
+ DCHECK(browser_context_);
+ return EventRouter::Get(browser_context_);
+}
+
+void ExtensionMessageFilter::ShutdownOnUIThread() {
+ browser_context_ = nullptr;
+ shutdown_notifier_.reset();
+}
+
+void ExtensionMessageFilter::OverrideThreadForMessage(
+ const IPC::Message& message,
+ BrowserThread::ID* thread) {
+ switch (message.type()) {
+ case ExtensionHostMsg_AddListener::ID:
+ case ExtensionHostMsg_RemoveListener::ID:
+ case ExtensionHostMsg_AddLazyListener::ID:
+ case ExtensionHostMsg_RemoveLazyListener::ID:
+ case ExtensionHostMsg_AddFilteredListener::ID:
+ case ExtensionHostMsg_RemoveFilteredListener::ID:
+ case ExtensionHostMsg_ShouldSuspendAck::ID:
+ case ExtensionHostMsg_SuspendAck::ID:
+ case ExtensionHostMsg_TransferBlobsAck::ID:
+ case ExtensionHostMsg_WakeEventPage::ID:
+ *thread = BrowserThread::UI;
+ break;
+ default:
+ break;
+ }
+}
+
+void ExtensionMessageFilter::OnDestruct() const {
+ BrowserThread::DeleteOnUIThread::Destruct(this);
+}
+
+bool ExtensionMessageFilter::OnMessageReceived(const IPC::Message& message) {
+ // If we have been shut down already, return.
+ if (!browser_context_)
+ return true;
+
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(ExtensionMessageFilter, message)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_AddListener,
+ OnExtensionAddListener)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_RemoveListener,
+ OnExtensionRemoveListener)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_AddLazyListener,
+ OnExtensionAddLazyListener)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_RemoveLazyListener,
+ OnExtensionRemoveLazyListener)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_AddFilteredListener,
+ OnExtensionAddFilteredListener)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_RemoveFilteredListener,
+ OnExtensionRemoveFilteredListener)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_ShouldSuspendAck,
+ OnExtensionShouldSuspendAck)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_SuspendAck,
+ OnExtensionSuspendAck)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_TransferBlobsAck,
+ OnExtensionTransferBlobsAck)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_WakeEventPage,
+ OnExtensionWakeEventPage)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void ExtensionMessageFilter::OnExtensionAddListener(
+ const std::string& extension_id,
+ const GURL& listener_url,
+ const std::string& event_name) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
+ return;
+
+ RenderProcessHost* process = RenderProcessHost::FromID(render_process_id_);
+ if (!process)
+ return;
+
+ if (crx_file::id_util::IdIsValid(extension_id)) {
+ GetEventRouter()->AddEventListener(event_name, process, extension_id);
+ } else if (listener_url.is_valid()) {
+ GetEventRouter()->AddEventListenerForURL(event_name, process, listener_url);
+ } else {
+ NOTREACHED() << "Tried to add an event listener without a valid "
+ << "extension ID nor listener URL";
+ }
+}
+
+void ExtensionMessageFilter::OnExtensionRemoveListener(
+ const std::string& extension_id,
+ const GURL& listener_url,
+ const std::string& event_name) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
+ return;
+
+ RenderProcessHost* process = RenderProcessHost::FromID(render_process_id_);
+ if (!process)
+ return;
+
+ if (crx_file::id_util::IdIsValid(extension_id)) {
+ GetEventRouter()->RemoveEventListener(event_name, process, extension_id);
+ } else if (listener_url.is_valid()) {
+ GetEventRouter()->RemoveEventListenerForURL(event_name, process,
+ listener_url);
+ } else {
+ NOTREACHED() << "Tried to remove an event listener without a valid "
+ << "extension ID nor listener URL";
+ }
+}
+
+void ExtensionMessageFilter::OnExtensionAddLazyListener(
+ const std::string& extension_id, const std::string& event_name) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
+ return;
+
+ GetEventRouter()->AddLazyEventListener(event_name, extension_id);
+}
+
+void ExtensionMessageFilter::OnExtensionRemoveLazyListener(
+ const std::string& extension_id, const std::string& event_name) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
+ return;
+
+ GetEventRouter()->RemoveLazyEventListener(event_name, extension_id);
+}
+
+void ExtensionMessageFilter::OnExtensionAddFilteredListener(
+ const std::string& extension_id,
+ const std::string& event_name,
+ const base::DictionaryValue& filter,
+ bool lazy) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
+ return;
+
+ RenderProcessHost* process = RenderProcessHost::FromID(render_process_id_);
+ if (!process)
+ return;
+
+ GetEventRouter()->AddFilteredEventListener(event_name, process, extension_id,
+ filter, lazy);
+}
+
+void ExtensionMessageFilter::OnExtensionRemoveFilteredListener(
+ const std::string& extension_id,
+ const std::string& event_name,
+ const base::DictionaryValue& filter,
+ bool lazy) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
+ return;
+
+ RenderProcessHost* process = RenderProcessHost::FromID(render_process_id_);
+ if (!process)
+ return;
+
+ GetEventRouter()->RemoveFilteredEventListener(event_name, process,
+ extension_id, filter, lazy);
+}
+
+void ExtensionMessageFilter::OnExtensionShouldSuspendAck(
+ const std::string& extension_id, int sequence_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
+ return;
+
+ ProcessManager::Get(browser_context_)
+ ->OnShouldSuspendAck(extension_id, sequence_id);
+}
+
+void ExtensionMessageFilter::OnExtensionSuspendAck(
+ const std::string& extension_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
+ return;
+
+ ProcessManager::Get(browser_context_)->OnSuspendAck(extension_id);
+}
+
+void ExtensionMessageFilter::OnExtensionTransferBlobsAck(
+ const std::vector<std::string>& blob_uuids) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
+ return;
+
+ RenderProcessHost* process = RenderProcessHost::FromID(render_process_id_);
+ if (!process)
+ return;
+
+ BlobHolder::FromRenderProcessHost(process)->DropBlobs(blob_uuids);
+}
+
+void ExtensionMessageFilter::OnExtensionWakeEventPage(
+ int request_id,
+ const std::string& extension_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!browser_context_)
+ return;
+
+ const Extension* extension = ExtensionRegistry::Get(browser_context_)
+ ->enabled_extensions()
+ .GetByID(extension_id);
+ if (!extension) {
+ // Don't kill the renderer, it might just be some context which hasn't
+ // caught up to extension having been uninstalled.
+ return;
+ }
+
+ ProcessManager* process_manager = ProcessManager::Get(browser_context_);
+
+ if (BackgroundInfo::HasLazyBackgroundPage(extension)) {
+ // Wake the event page if it's asleep, or immediately repond with success
+ // if it's already awake.
+ if (process_manager->IsEventPageSuspended(extension_id)) {
+ process_manager->WakeEventPage(
+ extension_id,
+ base::Bind(&ExtensionMessageFilter::SendWakeEventPageResponse, this,
+ request_id));
+ } else {
+ SendWakeEventPageResponse(request_id, true);
+ }
+ return;
+ }
+
+ if (BackgroundInfo::HasPersistentBackgroundPage(extension)) {
+ // No point in trying to wake a persistent background page. If it's open,
+ // immediately return and call it a success. If it's closed, fail.
+ SendWakeEventPageResponse(request_id,
+ process_manager->GetBackgroundHostForExtension(
+ extension_id) != nullptr);
+ return;
+ }
+
+ // The extension has no background page, so there is nothing to wake.
+ SendWakeEventPageResponse(request_id, false);
+}
+
+void ExtensionMessageFilter::SendWakeEventPageResponse(int request_id,
+ bool success) {
+ Send(new ExtensionMsg_WakeEventPageResponse(request_id, success));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_message_filter.h b/chromium/extensions/browser/extension_message_filter.h
new file mode 100644
index 00000000000..65900d13848
--- /dev/null
+++ b/chromium/extensions/browser/extension_message_filter.h
@@ -0,0 +1,99 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_MESSAGE_FILTER_H_
+#define EXTENSIONS_BROWSER_EXTENSION_MESSAGE_FILTER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "components/keyed_service/core/keyed_service_shutdown_notifier.h"
+#include "content/public/browser/browser_message_filter.h"
+
+class GURL;
+
+namespace content {
+class BrowserContext;
+}
+
+namespace gfx {
+class Size;
+}
+
+namespace extensions {
+class EventRouter;
+
+// This class filters out incoming extension-specific IPC messages from the
+// renderer process. It is created and destroyed on the UI thread and handles
+// messages there.
+class ExtensionMessageFilter : public content::BrowserMessageFilter {
+ public:
+ ExtensionMessageFilter(int render_process_id,
+ content::BrowserContext* context);
+
+ int render_process_id() { return render_process_id_; }
+
+ static void EnsureShutdownNotifierFactoryBuilt();
+
+ private:
+ friend class base::DeleteHelper<ExtensionMessageFilter>;
+ friend class content::BrowserThread;
+
+ ~ExtensionMessageFilter() override;
+
+ EventRouter* GetEventRouter();
+
+ void ShutdownOnUIThread();
+
+ // content::BrowserMessageFilter implementation:
+ void OverrideThreadForMessage(const IPC::Message& message,
+ content::BrowserThread::ID* thread) override;
+ void OnDestruct() const override;
+ bool OnMessageReceived(const IPC::Message& message) override;
+
+ // Message handlers on the UI thread.
+ void OnExtensionAddListener(const std::string& extension_id,
+ const GURL& listener_url,
+ const std::string& event_name);
+ void OnExtensionRemoveListener(const std::string& extension_id,
+ const GURL& listener_url,
+ const std::string& event_name);
+ void OnExtensionAddLazyListener(const std::string& extension_id,
+ const std::string& event_name);
+ void OnExtensionRemoveLazyListener(const std::string& extension_id,
+ const std::string& event_name);
+ void OnExtensionAddFilteredListener(const std::string& extension_id,
+ const std::string& event_name,
+ const base::DictionaryValue& filter,
+ bool lazy);
+ void OnExtensionRemoveFilteredListener(const std::string& extension_id,
+ const std::string& event_name,
+ const base::DictionaryValue& filter,
+ bool lazy);
+ void OnExtensionShouldSuspendAck(const std::string& extension_id,
+ int sequence_id);
+ void OnExtensionSuspendAck(const std::string& extension_id);
+ void OnExtensionTransferBlobsAck(const std::vector<std::string>& blob_uuids);
+ void OnExtensionWakeEventPage(int request_id,
+ const std::string& extension_id);
+
+ // Responds to the ExtensionHostMsg_WakeEventPage message.
+ void SendWakeEventPageResponse(int request_id, bool success);
+
+ const int render_process_id_;
+
+ scoped_ptr<KeyedServiceShutdownNotifier::Subscription> shutdown_notifier_;
+
+ // Only access from the UI thread.
+ content::BrowserContext* browser_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionMessageFilter);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_MESSAGE_FILTER_H_
diff --git a/chromium/extensions/browser/extension_pref_store.cc b/chromium/extensions/browser/extension_pref_store.cc
new file mode 100644
index 00000000000..80e7a08b9b9
--- /dev/null
+++ b/chromium/extensions/browser/extension_pref_store.cc
@@ -0,0 +1,45 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_pref_store.h"
+
+#include "base/values.h"
+#include "extensions/browser/extension_pref_value_map.h"
+
+ExtensionPrefStore::ExtensionPrefStore(
+ ExtensionPrefValueMap* extension_pref_value_map,
+ bool incognito_pref_store)
+ : extension_pref_value_map_(extension_pref_value_map),
+ incognito_pref_store_(incognito_pref_store) {
+ extension_pref_value_map_->AddObserver(this);
+}
+
+void ExtensionPrefStore::OnInitializationCompleted() {
+ NotifyInitializationCompleted();
+}
+
+void ExtensionPrefStore::OnPrefValueChanged(const std::string& key) {
+ CHECK(extension_pref_value_map_);
+ const base::Value *winner =
+ extension_pref_value_map_->GetEffectivePrefValue(key,
+ incognito_pref_store_,
+ NULL);
+ if (winner) {
+ SetValue(key, winner->CreateDeepCopy(),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+ } else {
+ RemoveValue(key, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+ }
+}
+
+void ExtensionPrefStore::OnExtensionPrefValueMapDestruction() {
+ CHECK(extension_pref_value_map_);
+ extension_pref_value_map_->RemoveObserver(this);
+ extension_pref_value_map_ = NULL;
+}
+
+ExtensionPrefStore::~ExtensionPrefStore() {
+ if (extension_pref_value_map_)
+ extension_pref_value_map_->RemoveObserver(this);
+}
diff --git a/chromium/extensions/browser/extension_pref_store.h b/chromium/extensions/browser/extension_pref_store.h
new file mode 100644
index 00000000000..3585c3311b1
--- /dev/null
+++ b/chromium/extensions/browser/extension_pref_store.h
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_PREF_STORE_H_
+#define EXTENSIONS_BROWSER_EXTENSION_PREF_STORE_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "components/prefs/value_map_pref_store.h"
+#include "extensions/browser/extension_pref_value_map.h"
+
+// A (non-persistent) PrefStore implementation that holds effective preferences
+// set by extensions. These preferences are managed by and fetched from an
+// ExtensionPrefValueMap.
+class ExtensionPrefStore : public ValueMapPrefStore,
+ public ExtensionPrefValueMap::Observer {
+ public:
+ // Constructs an ExtensionPrefStore for a regular or an incognito profile.
+ ExtensionPrefStore(ExtensionPrefValueMap* extension_pref_value_map,
+ bool incognito_pref_store);
+
+ // Overrides for ExtensionPrefValueMap::Observer:
+ void OnInitializationCompleted() override;
+ void OnPrefValueChanged(const std::string& key) override;
+ void OnExtensionPrefValueMapDestruction() override;
+
+ protected:
+ ~ExtensionPrefStore() override;
+
+ private:
+ ExtensionPrefValueMap* extension_pref_value_map_; // Weak pointer.
+ bool incognito_pref_store_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionPrefStore);
+};
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_PREF_STORE_H_
diff --git a/chromium/extensions/browser/extension_pref_value_map.cc b/chromium/extensions/browser/extension_pref_value_map.cc
new file mode 100644
index 00000000000..bb1fde5dd61
--- /dev/null
+++ b/chromium/extensions/browser/extension_pref_value_map.cc
@@ -0,0 +1,398 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_pref_value_map.h"
+
+#include "base/values.h"
+#include "components/prefs/pref_value_map.h"
+
+using extensions::ExtensionPrefsScope;
+
+struct ExtensionPrefValueMap::ExtensionEntry {
+ // Installation time of the extension.
+ base::Time install_time;
+ // Whether extension is enabled in the profile.
+ bool enabled;
+ // Whether the extension has access to the incognito profile.
+ bool incognito_enabled;
+ // Extension controlled preferences for the regular profile.
+ PrefValueMap regular_profile_preferences;
+ // Extension controlled preferences that should *only* apply to the regular
+ // profile.
+ PrefValueMap regular_only_profile_preferences;
+ // Persistent extension controlled preferences for the incognito profile,
+ // empty for regular profile ExtensionPrefStore.
+ PrefValueMap incognito_profile_preferences_persistent;
+ // Session only extension controlled preferences for the incognito profile.
+ // These preferences are deleted when the incognito profile is destroyed.
+ PrefValueMap incognito_profile_preferences_session_only;
+};
+
+ExtensionPrefValueMap::ExtensionPrefValueMap() : destroyed_(false) {
+}
+
+ExtensionPrefValueMap::~ExtensionPrefValueMap() {
+ if (!destroyed_) {
+ NotifyOfDestruction();
+ destroyed_ = true;
+ }
+}
+
+void ExtensionPrefValueMap::Shutdown() {
+ NotifyOfDestruction();
+ destroyed_ = true;
+}
+
+void ExtensionPrefValueMap::SetExtensionPref(const std::string& ext_id,
+ const std::string& key,
+ ExtensionPrefsScope scope,
+ base::Value* value) {
+ PrefValueMap* prefs = GetExtensionPrefValueMap(ext_id, scope);
+
+ if (prefs->SetValue(key, make_scoped_ptr(value)))
+ NotifyPrefValueChanged(key);
+}
+
+void ExtensionPrefValueMap::RemoveExtensionPref(
+ const std::string& ext_id,
+ const std::string& key,
+ ExtensionPrefsScope scope) {
+ PrefValueMap* prefs = GetExtensionPrefValueMap(ext_id, scope);
+ if (prefs->RemoveValue(key))
+ NotifyPrefValueChanged(key);
+}
+
+bool ExtensionPrefValueMap::CanExtensionControlPref(
+ const std::string& extension_id,
+ const std::string& pref_key,
+ bool incognito) const {
+ ExtensionEntryMap::const_iterator ext = entries_.find(extension_id);
+ if (ext == entries_.end()) {
+ NOTREACHED() << "Extension " << extension_id
+ << " is not registered but accesses pref " << pref_key
+ << " (incognito: " << incognito << ")."
+ << " http://crbug.com/454513";
+ return false;
+ }
+
+ if (incognito && !ext->second->incognito_enabled)
+ return false;
+
+ ExtensionEntryMap::const_iterator winner =
+ GetEffectivePrefValueController(pref_key, incognito, NULL);
+ if (winner == entries_.end())
+ return true;
+
+ return winner->second->install_time <= ext->second->install_time;
+}
+
+void ExtensionPrefValueMap::ClearAllIncognitoSessionOnlyPreferences() {
+ typedef std::set<std::string> KeySet;
+ KeySet deleted_keys;
+
+ for (const auto& entry : entries_) {
+ PrefValueMap& inc_prefs =
+ entry.second->incognito_profile_preferences_session_only;
+ for (const auto& pref : inc_prefs)
+ deleted_keys.insert(pref.first);
+ inc_prefs.Clear();
+ }
+
+ for (const auto& key : deleted_keys)
+ NotifyPrefValueChanged(key);
+}
+
+bool ExtensionPrefValueMap::DoesExtensionControlPref(
+ const std::string& extension_id,
+ const std::string& pref_key,
+ bool* from_incognito) const {
+ bool incognito = (from_incognito != NULL);
+ ExtensionEntryMap::const_iterator winner =
+ GetEffectivePrefValueController(pref_key, incognito, from_incognito);
+ if (winner == entries_.end())
+ return false;
+ return winner->first == extension_id;
+}
+
+void ExtensionPrefValueMap::RegisterExtension(const std::string& ext_id,
+ const base::Time& install_time,
+ bool is_enabled,
+ bool is_incognito_enabled) {
+ if (entries_.find(ext_id) == entries_.end()) {
+ entries_[ext_id] = make_scoped_ptr(new ExtensionEntry);
+
+ // Only update the install time if the extension is newly installed.
+ entries_[ext_id]->install_time = install_time;
+ }
+
+ entries_[ext_id]->enabled = is_enabled;
+ entries_[ext_id]->incognito_enabled = is_incognito_enabled;
+}
+
+void ExtensionPrefValueMap::UnregisterExtension(const std::string& ext_id) {
+ ExtensionEntryMap::iterator i = entries_.find(ext_id);
+ if (i == entries_.end())
+ return;
+ std::set<std::string> keys; // keys set by this extension
+ GetExtensionControlledKeys(*(i->second.get()), &keys);
+
+ entries_.erase(i);
+
+ NotifyPrefValueChanged(keys);
+}
+
+void ExtensionPrefValueMap::SetExtensionState(const std::string& ext_id,
+ bool is_enabled) {
+ ExtensionEntryMap::const_iterator i = entries_.find(ext_id);
+ // This may happen when sync sets the extension state for an
+ // extension that is not installed.
+ if (i == entries_.end())
+ return;
+ if (i->second->enabled == is_enabled)
+ return;
+ std::set<std::string> keys; // keys set by this extension
+ GetExtensionControlledKeys(*(i->second), &keys);
+ i->second->enabled = is_enabled;
+ NotifyPrefValueChanged(keys);
+}
+
+void ExtensionPrefValueMap::SetExtensionIncognitoState(
+ const std::string& ext_id,
+ bool is_incognito_enabled) {
+ ExtensionEntryMap::const_iterator i = entries_.find(ext_id);
+ // This may happen when sync sets the extension state for an
+ // extension that is not installed.
+ if (i == entries_.end())
+ return;
+ if (i->second->incognito_enabled == is_incognito_enabled)
+ return;
+ std::set<std::string> keys; // keys set by this extension
+ GetExtensionControlledKeys(*(i->second), &keys);
+ i->second->incognito_enabled = is_incognito_enabled;
+ NotifyPrefValueChanged(keys);
+}
+
+PrefValueMap* ExtensionPrefValueMap::GetExtensionPrefValueMap(
+ const std::string& ext_id,
+ ExtensionPrefsScope scope) {
+ ExtensionEntryMap::const_iterator i = entries_.find(ext_id);
+ CHECK(i != entries_.end());
+ switch (scope) {
+ case extensions::kExtensionPrefsScopeRegular:
+ return &(i->second->regular_profile_preferences);
+ case extensions::kExtensionPrefsScopeRegularOnly:
+ return &(i->second->regular_only_profile_preferences);
+ case extensions::kExtensionPrefsScopeIncognitoPersistent:
+ return &(i->second->incognito_profile_preferences_persistent);
+ case extensions::kExtensionPrefsScopeIncognitoSessionOnly:
+ return &(i->second->incognito_profile_preferences_session_only);
+ }
+ NOTREACHED();
+ return NULL;
+}
+
+const PrefValueMap* ExtensionPrefValueMap::GetExtensionPrefValueMap(
+ const std::string& ext_id,
+ ExtensionPrefsScope scope) const {
+ ExtensionEntryMap::const_iterator i = entries_.find(ext_id);
+ CHECK(i != entries_.end());
+ switch (scope) {
+ case extensions::kExtensionPrefsScopeRegular:
+ return &(i->second->regular_profile_preferences);
+ case extensions::kExtensionPrefsScopeRegularOnly:
+ return &(i->second->regular_only_profile_preferences);
+ case extensions::kExtensionPrefsScopeIncognitoPersistent:
+ return &(i->second->incognito_profile_preferences_persistent);
+ case extensions::kExtensionPrefsScopeIncognitoSessionOnly:
+ return &(i->second->incognito_profile_preferences_session_only);
+ }
+ NOTREACHED();
+ return NULL;
+}
+
+void ExtensionPrefValueMap::GetExtensionControlledKeys(
+ const ExtensionEntry& entry,
+ std::set<std::string>* out) const {
+ PrefValueMap::const_iterator i;
+
+ const PrefValueMap& regular_prefs = entry.regular_profile_preferences;
+ for (i = regular_prefs.begin(); i != regular_prefs.end(); ++i)
+ out->insert(i->first);
+
+ const PrefValueMap& regular_only_prefs =
+ entry.regular_only_profile_preferences;
+ for (i = regular_only_prefs.begin(); i != regular_only_prefs.end(); ++i)
+ out->insert(i->first);
+
+ const PrefValueMap& inc_prefs_pers =
+ entry.incognito_profile_preferences_persistent;
+ for (i = inc_prefs_pers.begin(); i != inc_prefs_pers.end(); ++i)
+ out->insert(i->first);
+
+ const PrefValueMap& inc_prefs_session =
+ entry.incognito_profile_preferences_session_only;
+ for (i = inc_prefs_session.begin(); i != inc_prefs_session.end(); ++i)
+ out->insert(i->first);
+}
+
+const base::Value* ExtensionPrefValueMap::GetEffectivePrefValue(
+ const std::string& key,
+ bool incognito,
+ bool* from_incognito) const {
+ ExtensionEntryMap::const_iterator winner =
+ GetEffectivePrefValueController(key, incognito, from_incognito);
+ if (winner == entries_.end())
+ return NULL;
+
+ const base::Value* value = NULL;
+ const std::string& ext_id = winner->first;
+
+ // First search for incognito session only preferences.
+ if (incognito) {
+ DCHECK(winner->second->incognito_enabled);
+ const PrefValueMap* prefs = GetExtensionPrefValueMap(
+ ext_id, extensions::kExtensionPrefsScopeIncognitoSessionOnly);
+ prefs->GetValue(key, &value);
+ if (value)
+ return value;
+
+ // If no incognito session only preference exists, fall back to persistent
+ // incognito preference.
+ prefs = GetExtensionPrefValueMap(
+ ext_id,
+ extensions::kExtensionPrefsScopeIncognitoPersistent);
+ prefs->GetValue(key, &value);
+ if (value)
+ return value;
+ } else {
+ // Regular-only preference.
+ const PrefValueMap* prefs = GetExtensionPrefValueMap(
+ ext_id, extensions::kExtensionPrefsScopeRegularOnly);
+ prefs->GetValue(key, &value);
+ if (value)
+ return value;
+ }
+
+ // Regular preference.
+ const PrefValueMap* prefs = GetExtensionPrefValueMap(
+ ext_id, extensions::kExtensionPrefsScopeRegular);
+ prefs->GetValue(key, &value);
+ return value;
+}
+
+ExtensionPrefValueMap::ExtensionEntryMap::const_iterator
+ExtensionPrefValueMap::GetEffectivePrefValueController(
+ const std::string& key,
+ bool incognito,
+ bool* from_incognito) const {
+ ExtensionEntryMap::const_iterator winner = entries_.end();
+ base::Time winners_install_time;
+
+ ExtensionEntryMap::const_iterator i;
+ for (i = entries_.begin(); i != entries_.end(); ++i) {
+ const std::string& ext_id = i->first;
+ const base::Time& install_time = i->second->install_time;
+ const bool enabled = i->second->enabled;
+ const bool incognito_enabled = i->second->incognito_enabled;
+
+ if (!enabled)
+ continue;
+ if (install_time < winners_install_time)
+ continue;
+ if (incognito && !incognito_enabled)
+ continue;
+
+ const base::Value* value = NULL;
+ const PrefValueMap* prefs = GetExtensionPrefValueMap(
+ ext_id, extensions::kExtensionPrefsScopeRegular);
+ if (prefs->GetValue(key, &value)) {
+ winner = i;
+ winners_install_time = install_time;
+ if (from_incognito)
+ *from_incognito = false;
+ }
+
+ if (!incognito) {
+ const PrefValueMap* prefs = GetExtensionPrefValueMap(
+ ext_id, extensions::kExtensionPrefsScopeRegularOnly);
+ if (prefs->GetValue(key, &value)) {
+ winner = i;
+ winners_install_time = install_time;
+ if (from_incognito)
+ *from_incognito = false;
+ }
+ // Ignore the following prefs, because they're incognito-only.
+ continue;
+ }
+
+ prefs = GetExtensionPrefValueMap(
+ ext_id, extensions::kExtensionPrefsScopeIncognitoPersistent);
+ if (prefs->GetValue(key, &value)) {
+ winner = i;
+ winners_install_time = install_time;
+ if (from_incognito)
+ *from_incognito = true;
+ }
+
+ prefs = GetExtensionPrefValueMap(
+ ext_id, extensions::kExtensionPrefsScopeIncognitoSessionOnly);
+ if (prefs->GetValue(key, &value)) {
+ winner = i;
+ winners_install_time = install_time;
+ if (from_incognito)
+ *from_incognito = true;
+ }
+ }
+ return winner;
+}
+
+void ExtensionPrefValueMap::AddObserver(
+ ExtensionPrefValueMap::Observer* observer) {
+ observers_.AddObserver(observer);
+
+ // Collect all currently used keys and notify the new observer.
+ std::set<std::string> keys;
+ ExtensionEntryMap::const_iterator i;
+ for (i = entries_.begin(); i != entries_.end(); ++i)
+ GetExtensionControlledKeys(*(i->second), &keys);
+
+ std::set<std::string>::const_iterator j;
+ for (j = keys.begin(); j != keys.end(); ++j)
+ observer->OnPrefValueChanged(*j);
+}
+
+void ExtensionPrefValueMap::RemoveObserver(
+ ExtensionPrefValueMap::Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+std::string ExtensionPrefValueMap::GetExtensionControllingPref(
+ const std::string& pref_key) const {
+ ExtensionEntryMap::const_iterator winner =
+ GetEffectivePrefValueController(pref_key, false, NULL);
+ if (winner == entries_.end())
+ return std::string();
+ return winner->first;
+}
+
+void ExtensionPrefValueMap::NotifyInitializationCompleted() {
+ FOR_EACH_OBSERVER(ExtensionPrefValueMap::Observer, observers_,
+ OnInitializationCompleted());
+}
+
+void ExtensionPrefValueMap::NotifyPrefValueChanged(
+ const std::set<std::string>& keys) {
+ for (const auto& key : keys)
+ NotifyPrefValueChanged(key);
+}
+
+void ExtensionPrefValueMap::NotifyPrefValueChanged(const std::string& key) {
+ FOR_EACH_OBSERVER(ExtensionPrefValueMap::Observer, observers_,
+ OnPrefValueChanged(key));
+}
+
+void ExtensionPrefValueMap::NotifyOfDestruction() {
+ FOR_EACH_OBSERVER(ExtensionPrefValueMap::Observer, observers_,
+ OnExtensionPrefValueMapDestruction());
+}
diff --git a/chromium/extensions/browser/extension_pref_value_map.h b/chromium/extensions/browser/extension_pref_value_map.h
new file mode 100644
index 00000000000..aed71c5c590
--- /dev/null
+++ b/chromium/extensions/browser/extension_pref_value_map.h
@@ -0,0 +1,211 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_PREF_VALUE_MAP_H_
+#define EXTENSIONS_BROWSER_EXTENSION_PREF_VALUE_MAP_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/browser/extension_prefs_scope.h"
+
+class PrefValueMap;
+
+namespace base {
+class Time;
+class Value;
+}
+
+// Non-persistent data container that is shared by ExtensionPrefStores. All
+// extension pref values (incognito and regular) are stored herein and
+// provided to ExtensionPrefStores.
+//
+// The semantics of the ExtensionPrefValueMap are:
+// - A regular setting applies to regular browsing sessions as well as incognito
+// browsing sessions.
+// - An incognito setting applies only to incognito browsing sessions, not to
+// regular ones. It takes precedence over a regular setting set by the same
+// extension.
+// - A regular-only setting applies only to regular browsing sessions, not to
+// incognito ones. It takes precedence over a regular setting set by the same
+// extension.
+// - If two different extensions set a value for the same preference (and both
+// values apply to the regular/incognito browsing session), the extension that
+// was installed later takes precedence, regardless of whether the settings
+// are regular, incognito or regular-only.
+//
+// The following table illustrates the behavior:
+// A.reg | A.reg_only | A.inc | B.reg | B.reg_only | B.inc | E.reg | E.inc
+// 1 | - | - | - | - | - | 1 | 1
+// 1 | 2 | - | - | - | - | 2 | 1
+// 1 | - | 3 | - | - | - | 1 | 3
+// 1 | 2 | 3 | - | - | - | 2 | 3
+// 1 | - | - | 4 | - | - | 4 | 4
+// 1 | 2 | 3 | 4 | - | - | 4 | 4
+// 1 | - | - | - | 5 | - | 5 | 1
+// 1 | - | 3 | 4 | 5 | - | 5 | 4
+// 1 | - | - | - | - | 6 | 1 | 6
+// 1 | 2 | - | 4 | - | 6 | 4 | 6
+// 1 | 2 | 3 | - | 5 | 6 | 5 | 6
+//
+// A = extension A, B = extension B, E = effective value
+// .reg = regular value
+// .reg_only = regular-only value
+// .inc = incognito value
+// Extension B has higher precedence than A.
+class ExtensionPrefValueMap : public KeyedService {
+ public:
+ // Observer interface for monitoring ExtensionPrefValueMap.
+ class Observer {
+ public:
+ // Called when the value for the given |key| set by one of the extensions
+ // changes. This does not necessarily mean that the effective value has
+ // changed.
+ virtual void OnPrefValueChanged(const std::string& key) = 0;
+ // Notification about the ExtensionPrefValueMap being fully initialized.
+ virtual void OnInitializationCompleted() = 0;
+ // Called when the ExtensionPrefValueMap is being destroyed. When called,
+ // observers must unsubscribe.
+ virtual void OnExtensionPrefValueMapDestruction() = 0;
+
+ protected:
+ virtual ~Observer() {}
+ };
+
+ ExtensionPrefValueMap();
+ ~ExtensionPrefValueMap() override;
+
+ // KeyedService implementation.
+ void Shutdown() override;
+
+ // Set an extension preference |value| for |key| of extension |ext_id|.
+ // Takes ownership of |value|.
+ // Note that regular extension pref values need to be reported to
+ // incognito and to regular ExtensionPrefStores.
+ // Precondition: the extension must be registered.
+ void SetExtensionPref(const std::string& ext_id,
+ const std::string& key,
+ extensions::ExtensionPrefsScope scope,
+ base::Value* value);
+
+ // Remove the extension preference value for |key| of extension |ext_id|.
+ // Precondition: the extension must be registered.
+ void RemoveExtensionPref(const std::string& ext_id,
+ const std::string& key,
+ extensions::ExtensionPrefsScope scope);
+
+ // Returns true if currently no extension with higher precedence controls the
+ // preference. If |incognito| is true and the extension does not have
+ // incognito permission, CanExtensionControlPref returns false.
+ // Note that this function does does not consider the existence of
+ // policies. An extension is only really able to control a preference if
+ // PrefService::Preference::IsExtensionModifiable() returns true as well.
+ bool CanExtensionControlPref(const std::string& extension_id,
+ const std::string& pref_key,
+ bool incognito) const;
+
+ // Removes all "incognito session only" preference values.
+ void ClearAllIncognitoSessionOnlyPreferences();
+
+ // Returns true if an extension identified by |extension_id| controls the
+ // preference. This means this extension has set a preference value and no
+ // other extension with higher precedence overrides it. If |from_incognito|
+ // is not NULL, looks at incognito preferences first, and |from_incognito| is
+ // set to true if the effective pref value is coming from the incognito
+ // preferences, false if it is coming from the normal ones.
+ // Note that the this function does does not consider the existence of
+ // policies. An extension is only really able to control a preference if
+ // PrefService::Preference::IsExtensionModifiable() returns true as well.
+ bool DoesExtensionControlPref(const std::string& extension_id,
+ const std::string& pref_key,
+ bool* from_incognito) const;
+
+ // Returns the ID of the extension that currently controls this preference
+ // for a regular profile. Incognito settings are ignored.
+ // Returns an empty string if this preference is not controlled by an
+ // extension.
+ std::string GetExtensionControllingPref(const std::string& pref_key) const;
+
+ // Tell the store it's now fully initialized.
+ void NotifyInitializationCompleted();
+
+ // Registers the time when an extension |ext_id| is installed.
+ void RegisterExtension(const std::string& ext_id,
+ const base::Time& install_time,
+ bool is_enabled,
+ bool is_incognito_enabled);
+
+ // Deletes all entries related to extension |ext_id|.
+ void UnregisterExtension(const std::string& ext_id);
+
+ // Hides or makes the extension preference values of the specified extension
+ // visible.
+ void SetExtensionState(const std::string& ext_id, bool is_enabled);
+
+ // Sets whether the extension has permission to access incognito state.
+ void SetExtensionIncognitoState(const std::string& ext_id,
+ bool is_incognito_enabled);
+
+ // Adds an observer and notifies it about the currently stored keys.
+ void AddObserver(Observer* observer);
+
+ void RemoveObserver(Observer* observer);
+
+ const base::Value* GetEffectivePrefValue(const std::string& key,
+ bool incognito,
+ bool* from_incognito) const;
+
+ private:
+ struct ExtensionEntry;
+
+ typedef std::map<std::string, scoped_ptr<ExtensionEntry>> ExtensionEntryMap;
+
+ const PrefValueMap* GetExtensionPrefValueMap(
+ const std::string& ext_id,
+ extensions::ExtensionPrefsScope scope) const;
+
+ PrefValueMap* GetExtensionPrefValueMap(
+ const std::string& ext_id,
+ extensions::ExtensionPrefsScope scope);
+
+ // Returns all keys of pref values that are set by the extension of |entry|,
+ // regardless whether they are set for incognito or regular pref values.
+ void GetExtensionControlledKeys(const ExtensionEntry& entry,
+ std::set<std::string>* out) const;
+
+ // Returns an iterator to the extension which controls the preference |key|.
+ // If |incognito| is true, looks at incognito preferences first. In that case,
+ // if |from_incognito| is not NULL, it is set to true if the effective pref
+ // value is coming from the incognito preferences, false if it is coming from
+ // the normal ones.
+ ExtensionEntryMap::const_iterator GetEffectivePrefValueController(
+ const std::string& key,
+ bool incognito,
+ bool* from_incognito) const;
+
+ void NotifyOfDestruction();
+ void NotifyPrefValueChanged(const std::string& key);
+ void NotifyPrefValueChanged(const std::set<std::string>& keys);
+
+ // Mapping of which extension set which preference value. The effective
+ // preferences values (i.e. the ones with the highest precedence)
+ // are stored in ExtensionPrefStores.
+ ExtensionEntryMap entries_;
+
+ // In normal Profile shutdown, Shutdown() notifies observers that we are
+ // being destroyed. In tests, it isn't called, so the notification must
+ // be done in the destructor. This bit tracks whether it has been done yet.
+ bool destroyed_;
+
+ base::ObserverList<Observer, true> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionPrefValueMap);
+};
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_PREF_VALUE_MAP_H_
diff --git a/chromium/extensions/browser/extension_pref_value_map_factory.cc b/chromium/extensions/browser/extension_pref_value_map_factory.cc
new file mode 100644
index 00000000000..54b8633abc2
--- /dev/null
+++ b/chromium/extensions/browser/extension_pref_value_map_factory.cc
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_pref_value_map_factory.h"
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "extensions/browser/extension_pref_value_map.h"
+#include "extensions/browser/extensions_browser_client.h"
+
+ExtensionPrefValueMapFactory::ExtensionPrefValueMapFactory()
+ : BrowserContextKeyedServiceFactory(
+ "ExtensionPrefValueMap",
+ BrowserContextDependencyManager::GetInstance()) {
+}
+
+ExtensionPrefValueMapFactory::~ExtensionPrefValueMapFactory() {
+}
+
+// static
+ExtensionPrefValueMap* ExtensionPrefValueMapFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<ExtensionPrefValueMap*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+ExtensionPrefValueMapFactory* ExtensionPrefValueMapFactory::GetInstance() {
+ return base::Singleton<ExtensionPrefValueMapFactory>::get();
+}
+
+KeyedService* ExtensionPrefValueMapFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ return new ExtensionPrefValueMap();
+}
+
+content::BrowserContext* ExtensionPrefValueMapFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ // Redirected in incognito.
+ return extensions::ExtensionsBrowserClient::Get()->GetOriginalContext(
+ context);
+}
diff --git a/chromium/extensions/browser/extension_pref_value_map_factory.h b/chromium/extensions/browser/extension_pref_value_map_factory.h
new file mode 100644
index 00000000000..543e6ea9662
--- /dev/null
+++ b/chromium/extensions/browser/extension_pref_value_map_factory.h
@@ -0,0 +1,33 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_PREF_VALUE_MAP_FACTORY_H_
+#define EXTENSIONS_BROWSER_EXTENSION_PREF_VALUE_MAP_FACTORY_H_
+
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+class ExtensionPrefValueMap;
+
+// The usual factory boilerplate for ExtensionPrefValueMap.
+class ExtensionPrefValueMapFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static ExtensionPrefValueMap* GetForBrowserContext(
+ content::BrowserContext* context);
+
+ static ExtensionPrefValueMapFactory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<ExtensionPrefValueMapFactory>;
+
+ ExtensionPrefValueMapFactory();
+ ~ExtensionPrefValueMapFactory() override;
+
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+};
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_PREF_VALUE_MAP_FACTORY_H_
diff --git a/chromium/extensions/browser/extension_pref_value_map_unittest.cc b/chromium/extensions/browser/extension_pref_value_map_unittest.cc
new file mode 100644
index 00000000000..984448ba63b
--- /dev/null
+++ b/chromium/extensions/browser/extension_pref_value_map_unittest.cc
@@ -0,0 +1,443 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "components/prefs/pref_store_observer_mock.h"
+#include "extensions/browser/extension_pref_value_map.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+const char kExt1[] = "ext1";
+const char kExt2[] = "ext2";
+const char kExt3[] = "ext3";
+
+const char kPref1[] = "path1.subpath";
+const char kPref2[] = "path2";
+const char kPref3[] = "path3";
+const char kPref4[] = "path4";
+} // namespace
+
+static base::Value* CreateVal(const char* str) {
+ return new base::StringValue(str);
+}
+
+static base::Time CreateTime(int64_t t) {
+ return base::Time::FromInternalValue(t);
+}
+
+template <typename BASECLASS>
+class ExtensionPrefValueMapTestBase : public BASECLASS {
+ public:
+ static const extensions::ExtensionPrefsScope kRegular =
+ extensions::kExtensionPrefsScopeRegular;
+ static const extensions::ExtensionPrefsScope kRegularOnly =
+ extensions::kExtensionPrefsScopeRegularOnly;
+ static const extensions::ExtensionPrefsScope kIncognitoPersistent =
+ extensions::kExtensionPrefsScopeIncognitoPersistent;
+ static const extensions::ExtensionPrefsScope kIncognitoSessionOnly =
+ extensions::kExtensionPrefsScopeIncognitoSessionOnly;
+
+ // Returns an empty string if the key is not set.
+ std::string GetValue(const char * key, bool incognito) const {
+ const base::Value *value =
+ epvm_.GetEffectivePrefValue(key, incognito, NULL);
+ std::string string_value;
+ if (value)
+ value->GetAsString(&string_value);
+ return string_value;
+ }
+
+ // Registers the extension as enabled but without incognito permission.
+ void RegisterExtension(const std::string& ext_id,
+ const base::Time& install_time) {
+ epvm_.RegisterExtension(
+ ext_id, install_time, true /*enabled*/, false /*incognito*/);
+ }
+
+ protected:
+ ExtensionPrefValueMap epvm_;
+};
+
+class ExtensionPrefValueMapTest
+ : public ExtensionPrefValueMapTestBase<testing::Test> {
+};
+
+// A gmock-ified implementation of PrefStore::Observer.
+class ExtensionPrefValueMapObserverMock
+ : public ExtensionPrefValueMap::Observer {
+ public:
+ ExtensionPrefValueMapObserverMock() {}
+ virtual ~ExtensionPrefValueMapObserverMock() {}
+
+ MOCK_METHOD1(OnPrefValueChanged, void(const std::string&));
+ MOCK_METHOD0(OnInitializationCompleted, void());
+ MOCK_METHOD0(OnExtensionPrefValueMapDestruction, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ExtensionPrefValueMapObserverMock);
+};
+
+TEST_F(ExtensionPrefValueMapTest, SetAndGetPrefValue) {
+ RegisterExtension(kExt1, CreateTime(10));
+ epvm_.SetExtensionPref(kExt1, kPref1, kRegular, CreateVal("val1"));
+ EXPECT_EQ("val1", GetValue(kPref1, false));
+};
+
+TEST_F(ExtensionPrefValueMapTest, GetNotSetPrefValue) {
+ RegisterExtension(kExt1, CreateTime(10));
+ EXPECT_EQ(std::string(), GetValue(kPref1, false));
+};
+
+// Make sure the last-installed extension wins for each preference.
+TEST_F(ExtensionPrefValueMapTest, Override) {
+ RegisterExtension(kExt1, CreateTime(10));
+ RegisterExtension(kExt2, CreateTime(20));
+ RegisterExtension(kExt3, CreateTime(30));
+
+ epvm_.SetExtensionPref(kExt1, kPref1, kRegular, CreateVal("val1"));
+ epvm_.SetExtensionPref(kExt2, kPref1, kRegular, CreateVal("val2"));
+ epvm_.SetExtensionPref(kExt3, kPref1, kRegular, CreateVal("val3"));
+
+ epvm_.SetExtensionPref(kExt1, kPref2, kRegular, CreateVal("val4"));
+ epvm_.SetExtensionPref(kExt2, kPref2, kRegular, CreateVal("val5"));
+
+ epvm_.SetExtensionPref(kExt1, kPref1, kRegular, CreateVal("val6"));
+ epvm_.SetExtensionPref(kExt1, kPref2, kRegular, CreateVal("val7"));
+ epvm_.SetExtensionPref(kExt1, kPref3, kRegular, CreateVal("val8"));
+
+ EXPECT_EQ("val3", GetValue(kPref1, false));
+ EXPECT_EQ("val5", GetValue(kPref2, false));
+ EXPECT_EQ("val8", GetValue(kPref3, false));
+}
+
+TEST_F(ExtensionPrefValueMapTest, OverrideChecks) {
+ RegisterExtension(kExt1, CreateTime(10));
+ RegisterExtension(kExt2, CreateTime(20));
+ RegisterExtension(kExt3, CreateTime(30));
+
+ EXPECT_FALSE(epvm_.DoesExtensionControlPref(kExt1, kPref1, NULL));
+ EXPECT_FALSE(epvm_.DoesExtensionControlPref(kExt2, kPref1, NULL));
+ EXPECT_FALSE(epvm_.DoesExtensionControlPref(kExt3, kPref1, NULL));
+ EXPECT_TRUE(epvm_.CanExtensionControlPref(kExt1, kPref1, false));
+ EXPECT_TRUE(epvm_.CanExtensionControlPref(kExt2, kPref1, false));
+ EXPECT_TRUE(epvm_.CanExtensionControlPref(kExt3, kPref1, false));
+
+ epvm_.SetExtensionPref(kExt2, kPref1, kRegular, CreateVal("val1"));
+
+ EXPECT_FALSE(epvm_.DoesExtensionControlPref(kExt1, kPref1, NULL));
+ EXPECT_TRUE(epvm_.DoesExtensionControlPref(kExt2, kPref1, NULL));
+ EXPECT_FALSE(epvm_.DoesExtensionControlPref(kExt3, kPref1, NULL));
+ EXPECT_FALSE(epvm_.CanExtensionControlPref(kExt1, kPref1, false));
+ EXPECT_TRUE(epvm_.CanExtensionControlPref(kExt2, kPref1, false));
+ EXPECT_TRUE(epvm_.CanExtensionControlPref(kExt3, kPref1, false));
+}
+
+TEST_F(ExtensionPrefValueMapTest, SetAndGetPrefValueIncognito) {
+ RegisterExtension(kExt1, CreateTime(10));
+ epvm_.SetExtensionPref(kExt1, kPref1, kRegular, CreateVal("val1"));
+ // Check that the value is not propagated until the extension gets incognito
+ // permission.
+ EXPECT_EQ(std::string(), GetValue(kPref1, true));
+ epvm_.SetExtensionIncognitoState(kExt1, true);
+ EXPECT_EQ("val1", GetValue(kPref1, true));
+ epvm_.SetExtensionIncognitoState(kExt1, false);
+ EXPECT_EQ(std::string(), GetValue(kPref1, true));
+}
+
+TEST_F(ExtensionPrefValueMapTest, UninstallOnlyExtension) {
+ RegisterExtension(kExt1, CreateTime(10));
+ epvm_.SetExtensionPref(kExt1, kPref1, kRegular, CreateVal("val1"));
+ epvm_.UnregisterExtension(kExt1);
+
+ EXPECT_EQ(std::string(), GetValue(kPref1, false));
+}
+
+// Tests uninstalling an extension that wasn't winning for any preferences.
+TEST_F(ExtensionPrefValueMapTest, UninstallIrrelevantExtension) {
+ RegisterExtension(kExt1, CreateTime(10));
+ RegisterExtension(kExt2, CreateTime(10));
+
+ epvm_.SetExtensionPref(kExt1, kPref1, kRegular, CreateVal("val1"));
+ epvm_.SetExtensionPref(kExt2, kPref1, kRegular, CreateVal("val2"));
+
+ epvm_.SetExtensionPref(kExt1, kPref2, kRegular, CreateVal("val3"));
+ epvm_.SetExtensionPref(kExt2, kPref2, kRegular, CreateVal("val4"));
+
+ epvm_.UnregisterExtension(kExt1);
+
+ EXPECT_EQ("val2", GetValue(kPref1, false));
+ EXPECT_EQ("val4", GetValue(kPref2, false));
+}
+
+// Tests uninstalling an extension that was winning for all preferences.
+TEST_F(ExtensionPrefValueMapTest, UninstallExtensionFromTop) {
+ RegisterExtension(kExt1, CreateTime(10));
+ RegisterExtension(kExt2, CreateTime(20));
+ RegisterExtension(kExt3, CreateTime(30));
+
+ epvm_.SetExtensionPref(kExt1, kPref1, kRegular, CreateVal("val1"));
+ epvm_.SetExtensionPref(kExt2, kPref1, kRegular, CreateVal("val2"));
+ epvm_.SetExtensionPref(kExt3, kPref1, kRegular, CreateVal("val3"));
+
+ epvm_.SetExtensionPref(kExt1, kPref2, kRegular, CreateVal("val4"));
+ epvm_.SetExtensionPref(kExt3, kPref2, kRegular, CreateVal("val5"));
+
+ epvm_.UnregisterExtension(kExt3);
+
+ EXPECT_EQ("val2", GetValue(kPref1, false));
+ EXPECT_EQ("val4", GetValue(kPref2, false));
+}
+
+// Tests uninstalling an extension that was winning for only some preferences.
+TEST_F(ExtensionPrefValueMapTest, UninstallExtensionFromMiddle) {
+ RegisterExtension(kExt1, CreateTime(10));
+ RegisterExtension(kExt2, CreateTime(20));
+ RegisterExtension(kExt3, CreateTime(30));
+
+ epvm_.SetExtensionPref(kExt1, kPref1, kRegular, CreateVal("val1"));
+ epvm_.SetExtensionPref(kExt2, kPref1, kRegular, CreateVal("val2"));
+ epvm_.SetExtensionPref(kExt3, kPref1, kRegular, CreateVal("val3"));
+
+ epvm_.SetExtensionPref(kExt1, kPref2, kRegular, CreateVal("val4"));
+ epvm_.SetExtensionPref(kExt2, kPref2, kRegular, CreateVal("val5"));
+
+ epvm_.SetExtensionPref(kExt1, kPref3, kRegular, CreateVal("val6"));
+
+ epvm_.SetExtensionPref(kExt2, kPref4, kRegular, CreateVal("val7"));
+
+ epvm_.UnregisterExtension(kExt2);
+
+ EXPECT_EQ("val3", GetValue(kPref1, false));
+ EXPECT_EQ("val4", GetValue(kPref2, false));
+ EXPECT_EQ("val6", GetValue(kPref3, false));
+ EXPECT_EQ(std::string(), GetValue(kPref4, false));
+}
+
+// Tests triggering of notifications to registered observers.
+TEST_F(ExtensionPrefValueMapTest, NotifyWhenNeeded) {
+ using testing::Mock;
+ using testing::StrEq;
+
+ RegisterExtension(kExt1, CreateTime(10));
+
+ ExtensionPrefValueMapObserverMock observer;
+ epvm_.AddObserver(&observer);
+
+ EXPECT_CALL(observer, OnPrefValueChanged(std::string(kPref1)));
+ epvm_.SetExtensionPref(kExt1, kPref1, kRegular, CreateVal("val1"));
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Write the same value again.
+ EXPECT_CALL(observer, OnPrefValueChanged(std::string(kPref1))).Times(0);
+ epvm_.SetExtensionPref(kExt1, kPref1, kRegular, CreateVal("val1"));
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Override incognito value.
+ EXPECT_CALL(observer, OnPrefValueChanged(std::string(kPref1)));
+ epvm_.SetExtensionPref(kExt1, kPref1, kRegular, CreateVal("val2"));
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Override non-incognito value.
+ EXPECT_CALL(observer, OnPrefValueChanged(std::string(kPref1)));
+ epvm_.SetExtensionPref(kExt1, kPref1, kRegular, CreateVal("val3"));
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Disable.
+ EXPECT_CALL(observer, OnPrefValueChanged(std::string(kPref1)));
+ epvm_.SetExtensionState(kExt1, false);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Enable.
+ EXPECT_CALL(observer, OnPrefValueChanged(std::string(kPref1)));
+ epvm_.SetExtensionState(kExt1, true);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Uninstall
+ EXPECT_CALL(observer, OnPrefValueChanged(std::string(kPref1)));
+ epvm_.UnregisterExtension(kExt1);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ epvm_.RemoveObserver(&observer);
+
+ // Write new value --> no notification after removing observer.
+ EXPECT_CALL(observer, OnPrefValueChanged(std::string(kPref1))).Times(0);
+ RegisterExtension(kExt1, CreateTime(10));
+ epvm_.SetExtensionPref(kExt1, kPref1, kRegular, CreateVal("val4"));
+ Mock::VerifyAndClearExpectations(&observer);
+}
+
+// Tests disabling an extension.
+TEST_F(ExtensionPrefValueMapTest, DisableExt) {
+ RegisterExtension(kExt1, CreateTime(10));
+
+ epvm_.SetExtensionPref(kExt1, kPref1, kRegular, CreateVal("val1"));
+ epvm_.SetExtensionState(kExt1, false);
+ EXPECT_EQ(std::string(), GetValue(kPref1, false));
+}
+
+// Tests disabling and reenabling an extension.
+TEST_F(ExtensionPrefValueMapTest, ReenableExt) {
+ RegisterExtension(kExt1, CreateTime(10));
+
+ epvm_.SetExtensionPref(kExt1, kPref1, kRegular, CreateVal("val1"));
+ epvm_.SetExtensionState(kExt1, false);
+ epvm_.SetExtensionState(kExt1, true);
+ EXPECT_EQ("val1", GetValue(kPref1, false));
+}
+
+struct OverrideIncognitoTestCase {
+ OverrideIncognitoTestCase(bool enable_ext1_in_incognito,
+ bool enable_ext2_in_incognito,
+ int val_ext1_regular,
+ int val_ext1_regular_only,
+ int val_ext1_incognito_pers,
+ int val_ext1_incognito_sess,
+ int val_ext2_regular,
+ int val_ext2_regular_only,
+ int val_ext2_incognito_pers,
+ int val_ext2_incognito_sess,
+ int effective_value_regular,
+ int effective_value_incognito)
+ : enable_ext1_in_incognito_(enable_ext1_in_incognito),
+ enable_ext2_in_incognito_(enable_ext2_in_incognito),
+ val_ext1_regular_(val_ext1_regular),
+ val_ext1_regular_only_(val_ext1_regular_only),
+ val_ext1_incognito_pers_(val_ext1_incognito_pers),
+ val_ext1_incognito_sess_(val_ext1_incognito_sess),
+ val_ext2_regular_(val_ext2_regular),
+ val_ext2_regular_only_(val_ext2_regular_only),
+ val_ext2_incognito_pers_(val_ext2_incognito_pers),
+ val_ext2_incognito_sess_(val_ext2_incognito_sess),
+ effective_value_regular_(effective_value_regular),
+ effective_value_incognito_(effective_value_incognito) {}
+
+ bool enable_ext1_in_incognito_;
+ bool enable_ext2_in_incognito_;
+
+ // pers. = persistent
+ // sess. = session only
+ int val_ext1_regular_; // pref value of extension 1
+ int val_ext1_regular_only_; // pref value of extension 1 regular-only.
+ int val_ext1_incognito_pers_; // pref value of extension 1 incognito pers.
+ int val_ext1_incognito_sess_; // pref value of extension 1 incognito sess.
+ int val_ext2_regular_; // pref value of extension 2
+ int val_ext2_regular_only_; // pref value of extension 2 regular-only.
+ int val_ext2_incognito_pers_; // pref value of extension 2 incognito pers.
+ int val_ext2_incognito_sess_; // pref value of extension 2 incognito sess.
+ int effective_value_regular_; // desired winner regular
+ int effective_value_incognito_; // desired winner incognito
+};
+
+class ExtensionPrefValueMapTestIncognitoTests
+ : public ExtensionPrefValueMapTestBase<
+ testing::TestWithParam<OverrideIncognitoTestCase> > {
+};
+
+TEST_P(ExtensionPrefValueMapTestIncognitoTests, OverrideIncognito) {
+ OverrideIncognitoTestCase test = GetParam();
+ const char* strings[] = {
+ "", // undefined
+ "val1",
+ "val2",
+ "val3",
+ "val4",
+ "val5",
+ "val6",
+ "val7",
+ "val8",
+ };
+
+ const bool kEnabled = true;
+ epvm_.RegisterExtension(
+ kExt1, CreateTime(10), kEnabled, test.enable_ext1_in_incognito_);
+ epvm_.RegisterExtension(
+ kExt2, CreateTime(20), kEnabled, test.enable_ext2_in_incognito_);
+ if (test.val_ext1_regular_) {
+ epvm_.SetExtensionPref(kExt1, kPref1, kRegular,
+ CreateVal(strings[test.val_ext1_regular_]));
+ }
+ if (test.val_ext1_regular_only_) {
+ epvm_.SetExtensionPref(kExt1, kPref1, kRegularOnly,
+ CreateVal(strings[test.val_ext1_regular_only_]));
+ }
+ if (test.val_ext1_incognito_pers_) {
+ epvm_.SetExtensionPref(kExt1, kPref1, kIncognitoPersistent,
+ CreateVal(strings[test.val_ext1_incognito_pers_]));
+ }
+ if (test.val_ext1_incognito_sess_) {
+ epvm_.SetExtensionPref(kExt1, kPref1, kIncognitoSessionOnly,
+ CreateVal(strings[test.val_ext1_incognito_sess_]));
+ }
+ if (test.val_ext2_regular_) {
+ epvm_.SetExtensionPref(kExt2, kPref1, kRegular,
+ CreateVal(strings[test.val_ext2_regular_]));
+ }
+ if (test.val_ext2_regular_only_) {
+ epvm_.SetExtensionPref(kExt2, kPref1, kRegularOnly,
+ CreateVal(strings[test.val_ext2_regular_only_]));
+ }
+ if (test.val_ext2_incognito_pers_) {
+ epvm_.SetExtensionPref(kExt2, kPref1, kIncognitoPersistent,
+ CreateVal(strings[test.val_ext2_incognito_pers_]));
+ }
+ if (test.val_ext2_incognito_sess_) {
+ epvm_.SetExtensionPref(kExt2, kPref1, kIncognitoSessionOnly,
+ CreateVal(strings[test.val_ext2_incognito_sess_]));
+ }
+ std::string actual;
+ EXPECT_EQ(strings[test.effective_value_regular_], GetValue(kPref1, false));
+ EXPECT_EQ(strings[test.effective_value_incognito_], GetValue(kPref1, true));
+ epvm_.UnregisterExtension(kExt1);
+ epvm_.UnregisterExtension(kExt2);
+}
+
+INSTANTIATE_TEST_CASE_P(
+ ExtensionPrefValueMapTestIncognitoTestsInstance,
+ ExtensionPrefValueMapTestIncognitoTests,
+ testing::Values(
+ // e.g. (true, 1, 0, 0, 0, 0, 0, 7, 0, 1, 7), means:
+ // ext1 regular is set to "val1", ext2 incognito persistent is set to
+ // "val7"
+ // --> the winning regular value is "val1", the winning incognito
+ // value is "val7".
+ OverrideIncognitoTestCase(true, true, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1),
+ OverrideIncognitoTestCase(true, true, 1, 2, 0, 0, 0, 0, 0, 0, 2, 1),
+ OverrideIncognitoTestCase(true, true, 1, 0, 3, 0, 0, 0, 0, 0, 1, 3),
+ OverrideIncognitoTestCase(true, true, 1, 0, 0, 4, 0, 0, 0, 0, 1, 4),
+ OverrideIncognitoTestCase(true, true, 1, 0, 3, 4, 0, 0, 0, 0, 1, 4),
+ OverrideIncognitoTestCase(true, true, 1, 2, 3, 0, 0, 0, 0, 0, 2, 3),
+ OverrideIncognitoTestCase(true, true, 1, 0, 0, 0, 5, 0, 0, 0, 5, 5),
+ OverrideIncognitoTestCase(true, true, 1, 2, 3, 0, 5, 0, 0, 0, 5, 5),
+ OverrideIncognitoTestCase(true, true, 1, 0, 0, 0, 0, 6, 0, 0, 6, 1),
+ OverrideIncognitoTestCase(true, true, 1, 0, 3, 0, 5, 6, 0, 0, 6, 5),
+ OverrideIncognitoTestCase(true, true, 1, 0, 0, 4, 5, 6, 0, 0, 6, 5),
+ OverrideIncognitoTestCase(true, true, 1, 0, 0, 0, 0, 0, 7, 0, 1, 7),
+ OverrideIncognitoTestCase(true, true, 1, 2, 0, 0, 5, 0, 7, 0, 5, 7),
+ OverrideIncognitoTestCase(true, true, 1, 2, 0, 0, 5, 0, 0, 8, 5, 8),
+ OverrideIncognitoTestCase(true, true, 1, 2, 0, 0, 5, 0, 7, 8, 5, 8),
+ OverrideIncognitoTestCase(true, true, 1, 2, 3, 0, 0, 6, 7, 0, 6, 7),
+ // Same tests as above but w/o incognito permission.
+ OverrideIncognitoTestCase(false, false, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0),
+ OverrideIncognitoTestCase(false, false, 1, 2, 0, 0, 0, 0, 0, 0, 2, 0),
+ OverrideIncognitoTestCase(false, false, 1, 0, 3, 0, 0, 0, 0, 0, 1, 0),
+ OverrideIncognitoTestCase(false, false, 1, 0, 0, 4, 0, 0, 0, 0, 1, 0),
+ OverrideIncognitoTestCase(false, false, 1, 0, 3, 4, 0, 0, 0, 0, 1, 0),
+ OverrideIncognitoTestCase(false, false, 1, 2, 3, 0, 0, 0, 0, 0, 2, 0),
+ OverrideIncognitoTestCase(false, false, 1, 0, 0, 0, 5, 0, 0, 0, 5, 0),
+ OverrideIncognitoTestCase(false, false, 1, 2, 3, 0, 5, 0, 0, 0, 5, 0),
+ OverrideIncognitoTestCase(false, false, 1, 0, 0, 0, 0, 6, 0, 0, 6, 0),
+ OverrideIncognitoTestCase(false, false, 1, 0, 3, 0, 5, 6, 0, 0, 6, 0),
+ OverrideIncognitoTestCase(false, false, 1, 0, 0, 4, 5, 6, 0, 0, 6, 0),
+ OverrideIncognitoTestCase(false, false, 1, 0, 0, 0, 0, 0, 7, 0, 1, 0),
+ OverrideIncognitoTestCase(false, false, 1, 2, 0, 0, 5, 0, 7, 0, 5, 0),
+ OverrideIncognitoTestCase(false, false, 1, 2, 0, 0, 5, 0, 0, 8, 5, 0),
+ OverrideIncognitoTestCase(false, false, 1, 2, 0, 0, 5, 0, 7, 8, 5, 0),
+ OverrideIncognitoTestCase(false, false, 1, 2, 3, 0, 0, 6, 7, 0, 6, 0)
+ ));
diff --git a/chromium/extensions/browser/extension_prefs.cc b/chromium/extensions/browser/extension_prefs.cc
new file mode 100644
index 00000000000..df21c374979
--- /dev/null
+++ b/chromium/extensions/browser/extension_prefs.cc
@@ -0,0 +1,1983 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_prefs.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <iterator>
+#include <utility>
+
+#include "base/macros.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/trace_event/trace_event.h"
+#include "build/build_config.h"
+#include "components/crx_file/id_util.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "extensions/browser/app_sorting.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_pref_store.h"
+#include "extensions/browser/extension_prefs_factory.h"
+#include "extensions/browser/extension_prefs_observer.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/install_flag.h"
+#include "extensions/browser/pref_names.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/permissions/permission_set.h"
+#include "extensions/common/permissions/permissions_info.h"
+#include "extensions/common/url_pattern.h"
+#include "extensions/common/user_script.h"
+
+namespace extensions {
+
+namespace {
+
+// Additional preferences keys, which are not needed by external clients.
+
+// True if this extension is running. Note this preference stops getting updated
+// during Chrome shutdown (and won't be updated on a browser crash) and so can
+// be used at startup to determine whether the extension was running when Chrome
+// was last terminated.
+const char kPrefRunning[] = "running";
+
+// Whether this extension had windows when it was last running.
+const char kIsActive[] = "is_active";
+
+// Where an extension was installed from. (see Manifest::Location)
+const char kPrefLocation[] = "location";
+
+// Enabled, disabled, killed, etc. (see Extension::State)
+const char kPrefState[] = "state";
+
+// The path to the current version's manifest file.
+const char kPrefPath[] = "path";
+
+// The dictionary containing the extension's manifest.
+const char kPrefManifest[] = "manifest";
+
+// The version number.
+const char kPrefVersion[] = "manifest.version";
+
+// Indicates whether an extension is blacklisted.
+const char kPrefBlacklist[] = "blacklist";
+
+// If extension is greylisted.
+const char kPrefBlacklistState[] = "blacklist_state";
+
+// The count of how many times we prompted the user to acknowledge an
+// extension.
+const char kPrefAcknowledgePromptCount[] = "ack_prompt_count";
+
+// Indicates whether the user has acknowledged various types of extensions.
+const char kPrefExternalAcknowledged[] = "ack_external";
+const char kPrefBlacklistAcknowledged[] = "ack_blacklist";
+
+// Indicates whether the external extension was installed during the first
+// run of this profile.
+const char kPrefExternalInstallFirstRun[] = "external_first_run";
+
+// A bitmask of all the reasons an extension is disabled.
+const char kPrefDisableReasons[] = "disable_reasons";
+
+// The key for a serialized Time value indicating the start of the day (from the
+// server's perspective) an extension last included a "ping" parameter during
+// its update check.
+const char kLastPingDay[] = "lastpingday";
+
+// Similar to kLastPingDay, but for "active" instead of "rollcall" pings.
+const char kLastActivePingDay[] = "last_active_pingday";
+
+// A bit we use to keep track of whether we need to do an "active" ping.
+const char kActiveBit[] = "active_bit";
+
+// Path for settings specific to blacklist update.
+const char kExtensionsBlacklistUpdate[] = "extensions.blacklistupdate";
+
+// Path for the delayed install info dictionary preference. The actual string
+// value is a legacy artifact for when delayed installs only pertained to
+// updates that were waiting for idle.
+const char kDelayedInstallInfo[] = "idle_install_info";
+
+// Reason why the extension's install was delayed.
+const char kDelayedInstallReason[] = "delay_install_reason";
+
+// Path for the suggested page ordinal of a delayed extension install.
+const char kPrefSuggestedPageOrdinal[] = "suggested_page_ordinal";
+
+// A preference that, if true, will allow this extension to run in incognito
+// mode.
+const char kPrefIncognitoEnabled[] = "incognito";
+
+// A preference to control whether an extension is allowed to inject script in
+// pages with file URLs.
+const char kPrefAllowFileAccess[] = "newAllowFileAccess";
+// TODO(jstritar): As part of fixing http://crbug.com/91577, we revoked all
+// extension file access by renaming the pref. We should eventually clean up
+// the old flag and possibly go back to that name.
+// const char kPrefAllowFileAccessOld[] = "allowFileAccess";
+
+// A preference specifying if the user dragged the app on the NTP.
+const char kPrefUserDraggedApp[] = "user_dragged_app_ntp";
+
+// Preferences that hold which permissions the user has granted the extension.
+// We explicitly keep track of these so that extensions can contain unknown
+// permissions, for backwards compatibility reasons, and we can still prompt
+// the user to accept them once recognized. We store the active permission
+// permissions because they may differ from those defined in the manifest.
+const char kPrefActivePermissions[] = "active_permissions";
+const char kPrefGrantedPermissions[] = "granted_permissions";
+
+// The preference names for PermissionSet values.
+const char kPrefAPIs[] = "api";
+const char kPrefManifestPermissions[] = "manifest_permissions";
+const char kPrefExplicitHosts[] = "explicit_host";
+const char kPrefScriptableHosts[] = "scriptable_host";
+
+// A preference that indicates when an extension was installed.
+const char kPrefInstallTime[] = "install_time";
+
+// A preference which saves the creation flags for extensions.
+const char kPrefCreationFlags[] = "creation_flags";
+
+// A preference that indicates whether the extension was installed from the
+// Chrome Web Store.
+const char kPrefFromWebStore[] = "from_webstore";
+
+// A preference that indicates whether the extension was installed from a
+// mock App created from a bookmark.
+const char kPrefFromBookmark[] = "from_bookmark";
+
+// A preference that indicates whether the extension was installed as a
+// default app.
+const char kPrefWasInstalledByDefault[] = "was_installed_by_default";
+
+// A preference that indicates whether the extension was installed as an
+// OEM app.
+const char kPrefWasInstalledByOem[] = "was_installed_by_oem";
+
+// Key for Geometry Cache preference.
+const char kPrefGeometryCache[] = "geometry_cache";
+
+// A preference that indicates when an extension is last launched.
+const char kPrefLastLaunchTime[] = "last_launch_time";
+
+// Am installation parameter bundled with an extension.
+const char kPrefInstallParam[] = "install_parameter";
+
+// A list of installed ids and a signature.
+const char kInstallSignature[] = "extensions.install_signature";
+
+// A boolean preference that indicates whether the extension should not be
+// synced. Default value is false.
+const char kPrefDoNotSync[] = "do_not_sync";
+
+const char kCorruptedDisableCount[] = "extensions.corrupted_disable_count";
+
+// A boolean preference that indicates whether the extension has local changes
+// that need to be synced. Default value is false.
+const char kPrefNeedsSync[] = "needs_sync";
+
+// Provider of write access to a dictionary storing extension prefs.
+class ScopedExtensionPrefUpdate : public DictionaryPrefUpdate {
+ public:
+ ScopedExtensionPrefUpdate(PrefService* service,
+ const std::string& extension_id) :
+ DictionaryPrefUpdate(service, pref_names::kExtensions),
+ extension_id_(extension_id) {}
+
+ ~ScopedExtensionPrefUpdate() override {}
+
+ // DictionaryPrefUpdate overrides:
+ base::DictionaryValue* Get() override {
+ base::DictionaryValue* dict = DictionaryPrefUpdate::Get();
+ base::DictionaryValue* extension = NULL;
+ if (!dict->GetDictionary(extension_id_, &extension)) {
+ // Extension pref does not exist, create it.
+ extension = new base::DictionaryValue();
+ dict->SetWithoutPathExpansion(extension_id_, extension);
+ }
+ return extension;
+ }
+
+ private:
+ const std::string extension_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedExtensionPrefUpdate);
+};
+
+std::string JoinPrefs(const std::string& parent, const char* child) {
+ return parent + "." + child;
+}
+
+// Checks if kPrefBlacklist is set to true in the base::DictionaryValue.
+// Return false if the value is false or kPrefBlacklist does not exist.
+// This is used to decide if an extension is blacklisted.
+bool IsBlacklistBitSet(const base::DictionaryValue* ext) {
+ bool bool_value;
+ return ext->GetBoolean(kPrefBlacklist, &bool_value) && bool_value;
+}
+
+void LoadExtensionControlledPrefs(ExtensionPrefs* prefs,
+ ExtensionPrefValueMap* value_map,
+ const std::string& extension_id,
+ ExtensionPrefsScope scope) {
+ std::string scope_string;
+ if (!pref_names::ScopeToPrefName(scope, &scope_string))
+ return;
+ std::string key = extension_id + "." + scope_string;
+
+ const base::DictionaryValue* source_dict =
+ prefs->pref_service()->GetDictionary(pref_names::kExtensions);
+ const base::DictionaryValue* preferences = NULL;
+ if (!source_dict->GetDictionary(key, &preferences))
+ return;
+
+ for (base::DictionaryValue::Iterator iter(*preferences); !iter.IsAtEnd();
+ iter.Advance()) {
+ value_map->SetExtensionPref(
+ extension_id, iter.key(), scope, iter.value().DeepCopy());
+ }
+}
+
+} // namespace
+
+//
+// TimeProvider
+//
+
+ExtensionPrefs::TimeProvider::TimeProvider() {
+}
+
+ExtensionPrefs::TimeProvider::~TimeProvider() {
+}
+
+base::Time ExtensionPrefs::TimeProvider::GetCurrentTime() const {
+ return base::Time::Now();
+}
+
+//
+// ScopedUpdate
+//
+template <typename T, base::Value::Type type_enum_value>
+ExtensionPrefs::ScopedUpdate<T, type_enum_value>::ScopedUpdate(
+ ExtensionPrefs* prefs,
+ const std::string& extension_id,
+ const std::string& key)
+ : update_(prefs->pref_service(), pref_names::kExtensions),
+ extension_id_(extension_id),
+ key_(key) {
+ DCHECK(crx_file::id_util::IdIsValid(extension_id_));
+}
+
+template <typename T, base::Value::Type type_enum_value>
+ExtensionPrefs::ScopedUpdate<T, type_enum_value>::~ScopedUpdate() {
+}
+
+template <typename T, base::Value::Type type_enum_value>
+T* ExtensionPrefs::ScopedUpdate<T, type_enum_value>::Get() {
+ base::DictionaryValue* dict = update_.Get();
+ base::DictionaryValue* extension = NULL;
+ base::Value* key_value = NULL;
+ if (!dict->GetDictionary(extension_id_, &extension) ||
+ !extension->Get(key_, &key_value)) {
+ return NULL;
+ }
+ return key_value->GetType() == type_enum_value ?
+ static_cast<T*>(key_value) :
+ NULL;
+}
+
+template <typename T, base::Value::Type type_enum_value>
+T* ExtensionPrefs::ScopedUpdate<T, type_enum_value>::Create() {
+ base::DictionaryValue* dict = update_.Get();
+ base::DictionaryValue* extension = NULL;
+ base::Value* key_value = NULL;
+ T* value_as_t = NULL;
+ if (!dict->GetDictionary(extension_id_, &extension)) {
+ extension = new base::DictionaryValue;
+ dict->SetWithoutPathExpansion(extension_id_, extension);
+ }
+ if (!extension->Get(key_, &key_value)) {
+ value_as_t = new T;
+ extension->SetWithoutPathExpansion(key_, value_as_t);
+ } else {
+ // It would be nice to CHECK that this doesn't happen, but since prefs can
+ // get into a mangled state, we can't really do that. Instead, handle it
+ // gracefully (by overwriting whatever was previously there).
+ if (key_value->GetType() != type_enum_value) {
+ NOTREACHED();
+ value_as_t = new T();
+ extension->SetWithoutPathExpansion(key_, value_as_t);
+ } else {
+ value_as_t = static_cast<T*>(key_value);
+ }
+ }
+ return value_as_t;
+}
+
+// Explicit instantiations for Dictionary and List value types.
+template class ExtensionPrefs::ScopedUpdate<base::DictionaryValue,
+ base::Value::TYPE_DICTIONARY>;
+template class ExtensionPrefs::ScopedUpdate<base::ListValue,
+ base::Value::TYPE_LIST>;
+
+//
+// ExtensionPrefs
+//
+
+// static
+ExtensionPrefs* ExtensionPrefs::Create(
+ content::BrowserContext* browser_context,
+ PrefService* prefs,
+ const base::FilePath& root_dir,
+ ExtensionPrefValueMap* extension_pref_value_map,
+ bool extensions_disabled,
+ const std::vector<ExtensionPrefsObserver*>& early_observers) {
+ return ExtensionPrefs::Create(browser_context, prefs, root_dir,
+ extension_pref_value_map, extensions_disabled,
+ early_observers,
+ make_scoped_ptr(new TimeProvider()));
+}
+
+// static
+ExtensionPrefs* ExtensionPrefs::Create(
+ content::BrowserContext* browser_context,
+ PrefService* pref_service,
+ const base::FilePath& root_dir,
+ ExtensionPrefValueMap* extension_pref_value_map,
+ bool extensions_disabled,
+ const std::vector<ExtensionPrefsObserver*>& early_observers,
+ scoped_ptr<TimeProvider> time_provider) {
+ return new ExtensionPrefs(browser_context, pref_service, root_dir,
+ extension_pref_value_map, std::move(time_provider),
+ extensions_disabled, early_observers);
+}
+
+ExtensionPrefs::~ExtensionPrefs() {
+}
+
+// static
+ExtensionPrefs* ExtensionPrefs::Get(content::BrowserContext* context) {
+ return ExtensionPrefsFactory::GetInstance()->GetForBrowserContext(context);
+}
+
+static base::FilePath::StringType MakePathRelative(const base::FilePath& parent,
+ const base::FilePath& child) {
+ if (!parent.IsParent(child))
+ return child.value();
+
+ base::FilePath::StringType retval = child.value().substr(
+ parent.value().length());
+ if (base::FilePath::IsSeparator(retval[0]))
+ return retval.substr(1);
+ else
+ return retval;
+}
+
+void ExtensionPrefs::MakePathsRelative() {
+ const base::DictionaryValue* dict =
+ prefs_->GetDictionary(pref_names::kExtensions);
+ if (!dict || dict->empty())
+ return;
+
+ // Collect all extensions ids with absolute paths in |absolute_keys|.
+ std::set<std::string> absolute_keys;
+ for (base::DictionaryValue::Iterator i(*dict); !i.IsAtEnd(); i.Advance()) {
+ const base::DictionaryValue* extension_dict = NULL;
+ if (!i.value().GetAsDictionary(&extension_dict))
+ continue;
+ int location_value;
+ if (extension_dict->GetInteger(kPrefLocation, &location_value) &&
+ Manifest::IsUnpackedLocation(
+ static_cast<Manifest::Location>(location_value))) {
+ // Unpacked extensions can have absolute paths.
+ continue;
+ }
+ base::FilePath::StringType path_string;
+ if (!extension_dict->GetString(kPrefPath, &path_string))
+ continue;
+ base::FilePath path(path_string);
+ if (path.IsAbsolute())
+ absolute_keys.insert(i.key());
+ }
+ if (absolute_keys.empty())
+ return;
+
+ // Fix these paths.
+ DictionaryPrefUpdate update(prefs_, pref_names::kExtensions);
+ base::DictionaryValue* update_dict = update.Get();
+ for (std::set<std::string>::iterator i = absolute_keys.begin();
+ i != absolute_keys.end(); ++i) {
+ base::DictionaryValue* extension_dict = NULL;
+ if (!update_dict->GetDictionaryWithoutPathExpansion(*i, &extension_dict)) {
+ NOTREACHED() << "Control should never reach here for extension " << *i;
+ continue;
+ }
+ base::FilePath::StringType path_string;
+ extension_dict->GetString(kPrefPath, &path_string);
+ base::FilePath path(path_string);
+ extension_dict->SetString(kPrefPath,
+ MakePathRelative(install_directory_, path));
+ }
+}
+
+const base::DictionaryValue* ExtensionPrefs::GetExtensionPref(
+ const std::string& extension_id) const {
+ const base::DictionaryValue* extensions =
+ prefs_->GetDictionary(pref_names::kExtensions);
+ const base::DictionaryValue* extension_dict = NULL;
+ if (!extensions ||
+ !extensions->GetDictionary(extension_id, &extension_dict)) {
+ return NULL;
+ }
+ return extension_dict;
+}
+
+void ExtensionPrefs::UpdateExtensionPref(const std::string& extension_id,
+ const std::string& key,
+ base::Value* data_value) {
+ if (!crx_file::id_util::IdIsValid(extension_id)) {
+ NOTREACHED() << "Invalid extension_id " << extension_id;
+ return;
+ }
+ ScopedExtensionPrefUpdate update(prefs_, extension_id);
+ if (data_value)
+ update->Set(key, data_value);
+ else
+ update->Remove(key, NULL);
+}
+
+void ExtensionPrefs::DeleteExtensionPrefs(const std::string& extension_id) {
+ extension_pref_value_map_->UnregisterExtension(extension_id);
+ FOR_EACH_OBSERVER(ExtensionPrefsObserver,
+ observer_list_,
+ OnExtensionPrefsDeleted(extension_id));
+ DictionaryPrefUpdate update(prefs_, pref_names::kExtensions);
+ base::DictionaryValue* dict = update.Get();
+ dict->Remove(extension_id, NULL);
+}
+
+bool ExtensionPrefs::ReadPrefAsBoolean(const std::string& extension_id,
+ const std::string& pref_key,
+ bool* out_value) const {
+ const base::DictionaryValue* ext = GetExtensionPref(extension_id);
+ if (!ext || !ext->GetBoolean(pref_key, out_value))
+ return false;
+
+ return true;
+}
+
+bool ExtensionPrefs::ReadPrefAsInteger(const std::string& extension_id,
+ const std::string& pref_key,
+ int* out_value) const {
+ const base::DictionaryValue* ext = GetExtensionPref(extension_id);
+ if (!ext || !ext->GetInteger(pref_key, out_value))
+ return false;
+
+ return true;
+}
+
+bool ExtensionPrefs::ReadPrefAsString(const std::string& extension_id,
+ const std::string& pref_key,
+ std::string* out_value) const {
+ const base::DictionaryValue* ext = GetExtensionPref(extension_id);
+ if (!ext || !ext->GetString(pref_key, out_value))
+ return false;
+
+ return true;
+}
+
+bool ExtensionPrefs::ReadPrefAsList(const std::string& extension_id,
+ const std::string& pref_key,
+ const base::ListValue** out_value) const {
+ const base::DictionaryValue* ext = GetExtensionPref(extension_id);
+ const base::ListValue* out = NULL;
+ if (!ext || !ext->GetList(pref_key, &out))
+ return false;
+ if (out_value)
+ *out_value = out;
+
+ return true;
+}
+
+bool ExtensionPrefs::ReadPrefAsDictionary(
+ const std::string& extension_id,
+ const std::string& pref_key,
+ const base::DictionaryValue** out_value) const {
+ const base::DictionaryValue* ext = GetExtensionPref(extension_id);
+ const base::DictionaryValue* out = NULL;
+ if (!ext || !ext->GetDictionary(pref_key, &out))
+ return false;
+ if (out_value)
+ *out_value = out;
+
+ return true;
+}
+
+bool ExtensionPrefs::HasPrefForExtension(
+ const std::string& extension_id) const {
+ return GetExtensionPref(extension_id) != NULL;
+}
+
+bool ExtensionPrefs::ReadPrefAsURLPatternSet(const std::string& extension_id,
+ const std::string& pref_key,
+ URLPatternSet* result,
+ int valid_schemes) const {
+ const base::ListValue* value = NULL;
+ if (!ReadPrefAsList(extension_id, pref_key, &value))
+ return false;
+ const base::DictionaryValue* extension = GetExtensionPref(extension_id);
+ if (!extension)
+ return false;
+ int location;
+ if (extension->GetInteger(kPrefLocation, &location) &&
+ static_cast<Manifest::Location>(location) == Manifest::COMPONENT) {
+ valid_schemes |= URLPattern::SCHEME_CHROMEUI;
+ }
+
+ bool allow_file_access = AllowFileAccess(extension_id);
+ return result->Populate(*value, valid_schemes, allow_file_access, NULL);
+}
+
+void ExtensionPrefs::SetExtensionPrefURLPatternSet(
+ const std::string& extension_id,
+ const std::string& pref_key,
+ const URLPatternSet& new_value) {
+ UpdateExtensionPref(extension_id, pref_key, new_value.ToValue().release());
+}
+
+bool ExtensionPrefs::ReadPrefAsBooleanAndReturn(
+ const std::string& extension_id,
+ const std::string& pref_key) const {
+ bool out_value = false;
+ return ReadPrefAsBoolean(extension_id, pref_key, &out_value) && out_value;
+}
+
+scoped_ptr<const PermissionSet> ExtensionPrefs::ReadPrefAsPermissionSet(
+ const std::string& extension_id,
+ const std::string& pref_key) const {
+ if (!GetExtensionPref(extension_id))
+ return nullptr;
+
+ // Retrieve the API permissions. Please refer SetExtensionPrefPermissionSet()
+ // for api_values format.
+ APIPermissionSet apis;
+ const base::ListValue* api_values = NULL;
+ std::string api_pref = JoinPrefs(pref_key, kPrefAPIs);
+ if (ReadPrefAsList(extension_id, api_pref, &api_values)) {
+ APIPermissionSet::ParseFromJSON(api_values,
+ APIPermissionSet::kAllowInternalPermissions,
+ &apis, NULL, NULL);
+ }
+
+ // Retrieve the Manifest Keys permissions. Please refer to
+ // |SetExtensionPrefPermissionSet| for manifest_permissions_values format.
+ ManifestPermissionSet manifest_permissions;
+ const base::ListValue* manifest_permissions_values = NULL;
+ std::string manifest_permission_pref =
+ JoinPrefs(pref_key, kPrefManifestPermissions);
+ if (ReadPrefAsList(extension_id, manifest_permission_pref,
+ &manifest_permissions_values)) {
+ ManifestPermissionSet::ParseFromJSON(
+ manifest_permissions_values, &manifest_permissions, NULL, NULL);
+ }
+
+ // Retrieve the explicit host permissions.
+ URLPatternSet explicit_hosts;
+ ReadPrefAsURLPatternSet(
+ extension_id, JoinPrefs(pref_key, kPrefExplicitHosts),
+ &explicit_hosts, Extension::kValidHostPermissionSchemes);
+
+ // Retrieve the scriptable host permissions.
+ URLPatternSet scriptable_hosts;
+ ReadPrefAsURLPatternSet(
+ extension_id, JoinPrefs(pref_key, kPrefScriptableHosts),
+ &scriptable_hosts, UserScript::ValidUserScriptSchemes());
+
+ return make_scoped_ptr(new PermissionSet(apis, manifest_permissions,
+ explicit_hosts, scriptable_hosts));
+}
+
+// Set the API or Manifest permissions.
+// The format of api_values is:
+// [ "permission_name1", // permissions do not support detail.
+// "permission_name2",
+// {"permission_name3": value },
+// // permission supports detail, permission detail will be stored in value.
+// ...
+// ]
+template<typename T>
+static base::ListValue* CreatePermissionList(const T& permissions) {
+ base::ListValue* values = new base::ListValue();
+ for (typename T::const_iterator i = permissions.begin();
+ i != permissions.end(); ++i) {
+ scoped_ptr<base::Value> detail(i->ToValue());
+ if (detail) {
+ base::DictionaryValue* tmp = new base::DictionaryValue();
+ tmp->Set(i->name(), detail.release());
+ values->Append(tmp);
+ } else {
+ values->Append(new base::StringValue(i->name()));
+ }
+ }
+ return values;
+}
+
+void ExtensionPrefs::SetExtensionPrefPermissionSet(
+ const std::string& extension_id,
+ const std::string& pref_key,
+ const PermissionSet& new_value) {
+ std::string api_pref = JoinPrefs(pref_key, kPrefAPIs);
+ base::ListValue* api_values = CreatePermissionList(new_value.apis());
+ UpdateExtensionPref(extension_id, api_pref, api_values);
+
+ std::string manifest_permissions_pref =
+ JoinPrefs(pref_key, kPrefManifestPermissions);
+ base::ListValue* manifest_permissions_values =
+ CreatePermissionList(new_value.manifest_permissions());
+ UpdateExtensionPref(extension_id,
+ manifest_permissions_pref,
+ manifest_permissions_values);
+
+ // Set the explicit host permissions.
+ if (!new_value.explicit_hosts().is_empty()) {
+ SetExtensionPrefURLPatternSet(extension_id,
+ JoinPrefs(pref_key, kPrefExplicitHosts),
+ new_value.explicit_hosts());
+ }
+
+ // Set the scriptable host permissions.
+ if (!new_value.scriptable_hosts().is_empty()) {
+ SetExtensionPrefURLPatternSet(extension_id,
+ JoinPrefs(pref_key, kPrefScriptableHosts),
+ new_value.scriptable_hosts());
+ }
+}
+
+int ExtensionPrefs::IncrementAcknowledgePromptCount(
+ const std::string& extension_id) {
+ int count = 0;
+ ReadPrefAsInteger(extension_id, kPrefAcknowledgePromptCount, &count);
+ ++count;
+ UpdateExtensionPref(extension_id, kPrefAcknowledgePromptCount,
+ new base::FundamentalValue(count));
+ return count;
+}
+
+bool ExtensionPrefs::IsExternalExtensionAcknowledged(
+ const std::string& extension_id) const {
+ return ReadPrefAsBooleanAndReturn(extension_id, kPrefExternalAcknowledged);
+}
+
+void ExtensionPrefs::AcknowledgeExternalExtension(
+ const std::string& extension_id) {
+ DCHECK(crx_file::id_util::IdIsValid(extension_id));
+ UpdateExtensionPref(extension_id, kPrefExternalAcknowledged,
+ new base::FundamentalValue(true));
+ UpdateExtensionPref(extension_id, kPrefAcknowledgePromptCount, NULL);
+}
+
+bool ExtensionPrefs::IsBlacklistedExtensionAcknowledged(
+ const std::string& extension_id) const {
+ return ReadPrefAsBooleanAndReturn(extension_id, kPrefBlacklistAcknowledged);
+}
+
+void ExtensionPrefs::AcknowledgeBlacklistedExtension(
+ const std::string& extension_id) {
+ DCHECK(crx_file::id_util::IdIsValid(extension_id));
+ UpdateExtensionPref(extension_id, kPrefBlacklistAcknowledged,
+ new base::FundamentalValue(true));
+ UpdateExtensionPref(extension_id, kPrefAcknowledgePromptCount, NULL);
+}
+
+bool ExtensionPrefs::IsExternalInstallFirstRun(
+ const std::string& extension_id) const {
+ return ReadPrefAsBooleanAndReturn(extension_id, kPrefExternalInstallFirstRun);
+}
+
+void ExtensionPrefs::SetExternalInstallFirstRun(
+ const std::string& extension_id) {
+ DCHECK(crx_file::id_util::IdIsValid(extension_id));
+ UpdateExtensionPref(extension_id, kPrefExternalInstallFirstRun,
+ new base::FundamentalValue(true));
+}
+
+bool ExtensionPrefs::SetAlertSystemFirstRun() {
+ if (prefs_->GetBoolean(pref_names::kAlertsInitialized)) {
+ return true;
+ }
+ prefs_->SetBoolean(pref_names::kAlertsInitialized, true);
+ return false;
+}
+
+bool ExtensionPrefs::DidExtensionEscalatePermissions(
+ const std::string& extension_id) const {
+ return HasDisableReason(extension_id,
+ Extension::DISABLE_PERMISSIONS_INCREASE) ||
+ HasDisableReason(extension_id, Extension::DISABLE_REMOTE_INSTALL);
+}
+
+int ExtensionPrefs::GetDisableReasons(const std::string& extension_id) const {
+ int value = -1;
+ if (ReadPrefAsInteger(extension_id, kPrefDisableReasons, &value) &&
+ value >= 0) {
+ return value;
+ }
+ return Extension::DISABLE_NONE;
+}
+
+bool ExtensionPrefs::HasDisableReason(
+ const std::string& extension_id,
+ Extension::DisableReason disable_reason) const {
+ return (GetDisableReasons(extension_id) & disable_reason) != 0;
+}
+
+void ExtensionPrefs::AddDisableReason(const std::string& extension_id,
+ Extension::DisableReason disable_reason) {
+ DCHECK(!DoesExtensionHaveState(extension_id, Extension::ENABLED));
+ ModifyDisableReasons(extension_id, disable_reason, DISABLE_REASON_ADD);
+}
+
+void ExtensionPrefs::AddDisableReasons(const std::string& extension_id,
+ int disable_reasons) {
+ DCHECK(!DoesExtensionHaveState(extension_id, Extension::ENABLED));
+ ModifyDisableReasons(extension_id, disable_reasons, DISABLE_REASON_ADD);
+}
+
+void ExtensionPrefs::RemoveDisableReason(
+ const std::string& extension_id,
+ Extension::DisableReason disable_reason) {
+ ModifyDisableReasons(extension_id, disable_reason, DISABLE_REASON_REMOVE);
+}
+
+void ExtensionPrefs::ReplaceDisableReasons(const std::string& extension_id,
+ int disable_reasons) {
+ ModifyDisableReasons(extension_id, disable_reasons, DISABLE_REASON_REPLACE);
+}
+
+void ExtensionPrefs::ClearDisableReasons(const std::string& extension_id) {
+ ModifyDisableReasons(extension_id, Extension::DISABLE_NONE,
+ DISABLE_REASON_CLEAR);
+}
+
+void ExtensionPrefs::ModifyDisableReasons(const std::string& extension_id,
+ int reasons,
+ DisableReasonChange change) {
+ int old_value = GetDisableReasons(extension_id);
+ int new_value = old_value;
+ switch (change) {
+ case DISABLE_REASON_ADD:
+ new_value |= reasons;
+ break;
+ case DISABLE_REASON_REMOVE:
+ new_value &= ~reasons;
+ break;
+ case DISABLE_REASON_REPLACE:
+ new_value = reasons;
+ break;
+ case DISABLE_REASON_CLEAR:
+ new_value = Extension::DISABLE_NONE;
+ break;
+ }
+
+ if (old_value == new_value) // no change, return.
+ return;
+
+ if (new_value == Extension::DISABLE_NONE) {
+ UpdateExtensionPref(extension_id, kPrefDisableReasons, NULL);
+ } else {
+ UpdateExtensionPref(extension_id,
+ kPrefDisableReasons,
+ new base::FundamentalValue(new_value));
+ }
+
+ FOR_EACH_OBSERVER(ExtensionPrefsObserver,
+ observer_list_,
+ OnExtensionDisableReasonsChanged(extension_id, new_value));
+}
+
+std::set<std::string> ExtensionPrefs::GetBlacklistedExtensions() const {
+ std::set<std::string> ids;
+
+ const base::DictionaryValue* extensions =
+ prefs_->GetDictionary(pref_names::kExtensions);
+ if (!extensions)
+ return ids;
+
+ for (base::DictionaryValue::Iterator it(*extensions);
+ !it.IsAtEnd(); it.Advance()) {
+ if (!it.value().IsType(base::Value::TYPE_DICTIONARY)) {
+ NOTREACHED() << "Invalid pref for extension " << it.key();
+ continue;
+ }
+ if (IsBlacklistBitSet(
+ static_cast<const base::DictionaryValue*>(&it.value()))) {
+ ids.insert(it.key());
+ }
+ }
+
+ return ids;
+}
+
+void ExtensionPrefs::SetExtensionBlacklisted(const std::string& extension_id,
+ bool is_blacklisted) {
+ bool currently_blacklisted = IsExtensionBlacklisted(extension_id);
+ if (is_blacklisted == currently_blacklisted)
+ return;
+
+ // Always make sure the "acknowledged" bit is cleared since the blacklist bit
+ // is changing.
+ UpdateExtensionPref(extension_id, kPrefBlacklistAcknowledged, NULL);
+
+ if (is_blacklisted) {
+ UpdateExtensionPref(extension_id,
+ kPrefBlacklist,
+ new base::FundamentalValue(true));
+ } else {
+ UpdateExtensionPref(extension_id, kPrefBlacklist, NULL);
+ const base::DictionaryValue* dict = GetExtensionPref(extension_id);
+ if (dict && dict->empty())
+ DeleteExtensionPrefs(extension_id);
+ }
+}
+
+bool ExtensionPrefs::IsExtensionBlacklisted(const std::string& id) const {
+ const base::DictionaryValue* ext_prefs = GetExtensionPref(id);
+ return ext_prefs && IsBlacklistBitSet(ext_prefs);
+}
+
+namespace {
+
+// Serializes a 64bit integer as a string value.
+void SaveInt64(base::DictionaryValue* dictionary,
+ const char* key,
+ const int64_t value) {
+ if (!dictionary)
+ return;
+
+ std::string string_value = base::Int64ToString(value);
+ dictionary->SetString(key, string_value);
+}
+
+// Deserializes a 64bit integer stored as a string value.
+bool ReadInt64(const base::DictionaryValue* dictionary,
+ const char* key,
+ int64_t* value) {
+ if (!dictionary)
+ return false;
+
+ std::string string_value;
+ if (!dictionary->GetString(key, &string_value))
+ return false;
+
+ return base::StringToInt64(string_value, value);
+}
+
+// Serializes |time| as a string value mapped to |key| in |dictionary|.
+void SaveTime(base::DictionaryValue* dictionary,
+ const char* key,
+ const base::Time& time) {
+ SaveInt64(dictionary, key, time.ToInternalValue());
+}
+
+// The opposite of SaveTime. If |key| is not found, this returns an empty Time
+// (is_null() will return true).
+base::Time ReadTime(const base::DictionaryValue* dictionary, const char* key) {
+ int64_t value;
+ if (ReadInt64(dictionary, key, &value))
+ return base::Time::FromInternalValue(value);
+
+ return base::Time();
+}
+
+} // namespace
+
+base::Time ExtensionPrefs::LastPingDay(const std::string& extension_id) const {
+ DCHECK(crx_file::id_util::IdIsValid(extension_id));
+ return ReadTime(GetExtensionPref(extension_id), kLastPingDay);
+}
+
+void ExtensionPrefs::SetLastPingDay(const std::string& extension_id,
+ const base::Time& time) {
+ DCHECK(crx_file::id_util::IdIsValid(extension_id));
+ ScopedExtensionPrefUpdate update(prefs_, extension_id);
+ SaveTime(update.Get(), kLastPingDay, time);
+}
+
+base::Time ExtensionPrefs::BlacklistLastPingDay() const {
+ return ReadTime(prefs_->GetDictionary(kExtensionsBlacklistUpdate),
+ kLastPingDay);
+}
+
+void ExtensionPrefs::SetBlacklistLastPingDay(const base::Time& time) {
+ DictionaryPrefUpdate update(prefs_, kExtensionsBlacklistUpdate);
+ SaveTime(update.Get(), kLastPingDay, time);
+}
+
+base::Time ExtensionPrefs::LastActivePingDay(
+ const std::string& extension_id) const {
+ DCHECK(crx_file::id_util::IdIsValid(extension_id));
+ return ReadTime(GetExtensionPref(extension_id), kLastActivePingDay);
+}
+
+void ExtensionPrefs::SetLastActivePingDay(const std::string& extension_id,
+ const base::Time& time) {
+ DCHECK(crx_file::id_util::IdIsValid(extension_id));
+ ScopedExtensionPrefUpdate update(prefs_, extension_id);
+ SaveTime(update.Get(), kLastActivePingDay, time);
+}
+
+bool ExtensionPrefs::GetActiveBit(const std::string& extension_id) const {
+ const base::DictionaryValue* dictionary = GetExtensionPref(extension_id);
+ bool result = false;
+ if (dictionary && dictionary->GetBoolean(kActiveBit, &result))
+ return result;
+ return false;
+}
+
+void ExtensionPrefs::SetActiveBit(const std::string& extension_id,
+ bool active) {
+ UpdateExtensionPref(extension_id, kActiveBit,
+ new base::FundamentalValue(active));
+}
+
+scoped_ptr<const PermissionSet> ExtensionPrefs::GetGrantedPermissions(
+ const std::string& extension_id) const {
+ CHECK(crx_file::id_util::IdIsValid(extension_id));
+ return ReadPrefAsPermissionSet(extension_id, kPrefGrantedPermissions);
+}
+
+void ExtensionPrefs::AddGrantedPermissions(const std::string& extension_id,
+ const PermissionSet& permissions) {
+ CHECK(crx_file::id_util::IdIsValid(extension_id));
+ scoped_ptr<const PermissionSet> granted = GetGrantedPermissions(extension_id);
+ scoped_ptr<const PermissionSet> union_set;
+ if (granted)
+ union_set = PermissionSet::CreateUnion(permissions, *granted);
+ // The new granted permissions are the union of the already granted
+ // permissions and the newly granted permissions.
+ SetExtensionPrefPermissionSet(extension_id, kPrefGrantedPermissions,
+ union_set ? *union_set : permissions);
+}
+
+void ExtensionPrefs::RemoveGrantedPermissions(
+ const std::string& extension_id,
+ const PermissionSet& permissions) {
+ CHECK(crx_file::id_util::IdIsValid(extension_id));
+
+ // The new granted permissions are the difference of the already granted
+ // permissions and the newly ungranted permissions.
+ SetExtensionPrefPermissionSet(
+ extension_id, kPrefGrantedPermissions,
+ *PermissionSet::CreateDifference(*GetGrantedPermissions(extension_id),
+ permissions));
+}
+
+scoped_ptr<const PermissionSet> ExtensionPrefs::GetActivePermissions(
+ const std::string& extension_id) const {
+ CHECK(crx_file::id_util::IdIsValid(extension_id));
+ return ReadPrefAsPermissionSet(extension_id, kPrefActivePermissions);
+}
+
+void ExtensionPrefs::SetActivePermissions(const std::string& extension_id,
+ const PermissionSet& permissions) {
+ SetExtensionPrefPermissionSet(
+ extension_id, kPrefActivePermissions, permissions);
+}
+
+void ExtensionPrefs::SetExtensionRunning(const std::string& extension_id,
+ bool is_running) {
+ base::Value* value = new base::FundamentalValue(is_running);
+ UpdateExtensionPref(extension_id, kPrefRunning, value);
+}
+
+bool ExtensionPrefs::IsExtensionRunning(const std::string& extension_id) const {
+ const base::DictionaryValue* extension = GetExtensionPref(extension_id);
+ if (!extension)
+ return false;
+ bool running = false;
+ extension->GetBoolean(kPrefRunning, &running);
+ return running;
+}
+
+void ExtensionPrefs::SetIsActive(const std::string& extension_id,
+ bool is_active) {
+ base::Value* value = new base::FundamentalValue(is_active);
+ UpdateExtensionPref(extension_id, kIsActive, value);
+}
+
+bool ExtensionPrefs::IsActive(const std::string& extension_id) const {
+ const base::DictionaryValue* extension = GetExtensionPref(extension_id);
+ if (!extension)
+ return false;
+ bool is_active = false;
+ extension->GetBoolean(kIsActive, &is_active);
+ return is_active;
+}
+
+bool ExtensionPrefs::IsIncognitoEnabled(const std::string& extension_id) const {
+ return ReadPrefAsBooleanAndReturn(extension_id, kPrefIncognitoEnabled);
+}
+
+void ExtensionPrefs::SetIsIncognitoEnabled(const std::string& extension_id,
+ bool enabled) {
+ UpdateExtensionPref(extension_id, kPrefIncognitoEnabled,
+ new base::FundamentalValue(enabled));
+ extension_pref_value_map_->SetExtensionIncognitoState(extension_id, enabled);
+}
+
+bool ExtensionPrefs::AllowFileAccess(const std::string& extension_id) const {
+ return ReadPrefAsBooleanAndReturn(extension_id, kPrefAllowFileAccess);
+}
+
+void ExtensionPrefs::SetAllowFileAccess(const std::string& extension_id,
+ bool allow) {
+ UpdateExtensionPref(extension_id, kPrefAllowFileAccess,
+ new base::FundamentalValue(allow));
+}
+
+bool ExtensionPrefs::HasAllowFileAccessSetting(
+ const std::string& extension_id) const {
+ const base::DictionaryValue* ext = GetExtensionPref(extension_id);
+ return ext && ext->HasKey(kPrefAllowFileAccess);
+}
+
+bool ExtensionPrefs::DoesExtensionHaveState(
+ const std::string& id, Extension::State check_state) const {
+ const base::DictionaryValue* extension = GetExtensionPref(id);
+ int state = -1;
+ if (!extension || !extension->GetInteger(kPrefState, &state))
+ return false;
+
+ if (state < 0 || state >= Extension::NUM_STATES) {
+ LOG(ERROR) << "Bad pref 'state' for extension '" << id << "'";
+ return false;
+ }
+
+ return state == check_state;
+}
+
+bool ExtensionPrefs::IsExternalExtensionUninstalled(
+ const std::string& id) const {
+ return DoesExtensionHaveState(id, Extension::EXTERNAL_EXTENSION_UNINSTALLED);
+}
+
+bool ExtensionPrefs::IsExtensionDisabled(const std::string& id) const {
+ return DoesExtensionHaveState(id, Extension::DISABLED);
+}
+
+ExtensionIdList ExtensionPrefs::GetToolbarOrder() const {
+ ExtensionIdList id_list_out;
+ GetUserExtensionPrefIntoContainer(pref_names::kToolbar, &id_list_out);
+ return id_list_out;
+}
+
+void ExtensionPrefs::SetToolbarOrder(const ExtensionIdList& extension_ids) {
+ SetExtensionPrefFromContainer(pref_names::kToolbar, extension_ids);
+}
+
+void ExtensionPrefs::OnExtensionInstalled(
+ const Extension* extension,
+ Extension::State initial_state,
+ const syncer::StringOrdinal& page_ordinal,
+ int install_flags,
+ const std::string& install_parameter) {
+ ScopedExtensionPrefUpdate update(prefs_, extension->id());
+ base::DictionaryValue* extension_dict = update.Get();
+ const base::Time install_time = time_provider_->GetCurrentTime();
+ PopulateExtensionInfoPrefs(extension,
+ install_time,
+ initial_state,
+ install_flags,
+ install_parameter,
+ extension_dict);
+
+ FinishExtensionInfoPrefs(extension->id(), install_time,
+ extension->RequiresSortOrdinal(), page_ordinal,
+ extension_dict);
+}
+
+void ExtensionPrefs::OnExtensionUninstalled(const std::string& extension_id,
+ const Manifest::Location& location,
+ bool external_uninstall) {
+ app_sorting()->ClearOrdinals(extension_id);
+
+ // For external extensions, we save a preference reminding ourself not to try
+ // and install the extension anymore (except when |external_uninstall| is
+ // true, which signifies that the registry key was deleted or the pref file
+ // no longer lists the extension).
+ if (!external_uninstall && Manifest::IsExternalLocation(location)) {
+ UpdateExtensionPref(extension_id, kPrefState,
+ new base::FundamentalValue(
+ Extension::EXTERNAL_EXTENSION_UNINSTALLED));
+ extension_pref_value_map_->SetExtensionState(extension_id, false);
+ FOR_EACH_OBSERVER(ExtensionPrefsObserver,
+ observer_list_,
+ OnExtensionStateChanged(extension_id, false));
+ } else {
+ DeleteExtensionPrefs(extension_id);
+ }
+}
+
+void ExtensionPrefs::SetExtensionEnabled(const std::string& extension_id) {
+ UpdateExtensionPref(extension_id, kPrefState,
+ new base::FundamentalValue(Extension::ENABLED));
+ extension_pref_value_map_->SetExtensionState(extension_id, true);
+ UpdateExtensionPref(extension_id, kPrefDisableReasons, nullptr);
+ FOR_EACH_OBSERVER(ExtensionPrefsObserver, observer_list_,
+ OnExtensionStateChanged(extension_id, true));
+}
+
+void ExtensionPrefs::SetExtensionDisabled(const std::string& extension_id,
+ int disable_reasons) {
+ if (!IsExternalExtensionUninstalled(extension_id)) {
+ UpdateExtensionPref(extension_id, kPrefState,
+ new base::FundamentalValue(Extension::DISABLED));
+ extension_pref_value_map_->SetExtensionState(extension_id, false);
+ }
+ UpdateExtensionPref(extension_id, kPrefDisableReasons,
+ new base::FundamentalValue(disable_reasons));
+ FOR_EACH_OBSERVER(ExtensionPrefsObserver, observer_list_,
+ OnExtensionStateChanged(extension_id, false));
+}
+
+void ExtensionPrefs::SetExtensionBlacklistState(const std::string& extension_id,
+ BlacklistState state) {
+ SetExtensionBlacklisted(extension_id, state == BLACKLISTED_MALWARE);
+ UpdateExtensionPref(extension_id, kPrefBlacklistState,
+ new base::FundamentalValue(state));
+}
+
+BlacklistState ExtensionPrefs::GetExtensionBlacklistState(
+ const std::string& extension_id) const {
+ if (IsExtensionBlacklisted(extension_id))
+ return BLACKLISTED_MALWARE;
+ const base::DictionaryValue* ext_prefs = GetExtensionPref(extension_id);
+ int int_value = 0;
+ if (ext_prefs && ext_prefs->GetInteger(kPrefBlacklistState, &int_value))
+ return static_cast<BlacklistState>(int_value);
+
+ return NOT_BLACKLISTED;
+}
+
+std::string ExtensionPrefs::GetVersionString(
+ const std::string& extension_id) const {
+ const base::DictionaryValue* extension = GetExtensionPref(extension_id);
+ if (!extension)
+ return std::string();
+
+ std::string version;
+ extension->GetString(kPrefVersion, &version);
+
+ return version;
+}
+
+void ExtensionPrefs::UpdateManifest(const Extension* extension) {
+ if (!Manifest::IsUnpackedLocation(extension->location())) {
+ const base::DictionaryValue* extension_dict =
+ GetExtensionPref(extension->id());
+ if (!extension_dict)
+ return;
+ const base::DictionaryValue* old_manifest = NULL;
+ bool update_required =
+ !extension_dict->GetDictionary(kPrefManifest, &old_manifest) ||
+ !extension->manifest()->value()->Equals(old_manifest);
+ if (update_required) {
+ UpdateExtensionPref(extension->id(), kPrefManifest,
+ extension->manifest()->value()->DeepCopy());
+ }
+ }
+}
+
+scoped_ptr<ExtensionInfo> ExtensionPrefs::GetInstalledInfoHelper(
+ const std::string& extension_id,
+ const base::DictionaryValue* extension) const {
+ int location_value;
+ if (!extension->GetInteger(kPrefLocation, &location_value))
+ return scoped_ptr<ExtensionInfo>();
+
+ Manifest::Location location = static_cast<Manifest::Location>(location_value);
+ if (location == Manifest::COMPONENT) {
+ // Component extensions are ignored. Component extensions may have data
+ // saved in preferences, but they are already loaded at this point (by
+ // ComponentLoader) and shouldn't be populated into the result of
+ // GetInstalledExtensionsInfo, otherwise InstalledLoader would also want to
+ // load them.
+ return scoped_ptr<ExtensionInfo>();
+ }
+
+ // Only the following extension types have data saved in the preferences.
+ if (location != Manifest::INTERNAL &&
+ !Manifest::IsUnpackedLocation(location) &&
+ !Manifest::IsExternalLocation(location)) {
+ NOTREACHED();
+ return scoped_ptr<ExtensionInfo>();
+ }
+
+ const base::DictionaryValue* manifest = NULL;
+ if (!Manifest::IsUnpackedLocation(location) &&
+ !extension->GetDictionary(kPrefManifest, &manifest)) {
+ LOG(WARNING) << "Missing manifest for extension " << extension_id;
+ // Just a warning for now.
+ }
+
+ base::FilePath::StringType path;
+ if (!extension->GetString(kPrefPath, &path))
+ return scoped_ptr<ExtensionInfo>();
+
+ // Make path absolute. Most (but not all) extension types have relative paths.
+ if (!base::FilePath(path).IsAbsolute())
+ path = install_directory_.Append(path).value();
+
+ return scoped_ptr<ExtensionInfo>(new ExtensionInfo(
+ manifest, extension_id, base::FilePath(path), location));
+}
+
+scoped_ptr<ExtensionInfo> ExtensionPrefs::GetInstalledExtensionInfo(
+ const std::string& extension_id) const {
+ const base::DictionaryValue* ext = NULL;
+ const base::DictionaryValue* extensions =
+ prefs_->GetDictionary(pref_names::kExtensions);
+ if (!extensions ||
+ !extensions->GetDictionaryWithoutPathExpansion(extension_id, &ext))
+ return scoped_ptr<ExtensionInfo>();
+ int state_value;
+ if (ext->GetInteger(kPrefState, &state_value) &&
+ state_value == Extension::EXTERNAL_EXTENSION_UNINSTALLED) {
+ LOG(WARNING) << "External extension with id " << extension_id
+ << " has been uninstalled by the user";
+ return scoped_ptr<ExtensionInfo>();
+ }
+
+ return GetInstalledInfoHelper(extension_id, ext);
+}
+
+scoped_ptr<ExtensionPrefs::ExtensionsInfo>
+ExtensionPrefs::GetInstalledExtensionsInfo() const {
+ scoped_ptr<ExtensionsInfo> extensions_info(new ExtensionsInfo);
+
+ const base::DictionaryValue* extensions =
+ prefs_->GetDictionary(pref_names::kExtensions);
+ for (base::DictionaryValue::Iterator extension_id(*extensions);
+ !extension_id.IsAtEnd(); extension_id.Advance()) {
+ if (!crx_file::id_util::IdIsValid(extension_id.key()))
+ continue;
+
+ scoped_ptr<ExtensionInfo> info =
+ GetInstalledExtensionInfo(extension_id.key());
+ if (info)
+ extensions_info->push_back(linked_ptr<ExtensionInfo>(info.release()));
+ }
+
+ return extensions_info;
+}
+
+scoped_ptr<ExtensionPrefs::ExtensionsInfo>
+ExtensionPrefs::GetUninstalledExtensionsInfo() const {
+ scoped_ptr<ExtensionsInfo> extensions_info(new ExtensionsInfo);
+
+ const base::DictionaryValue* extensions =
+ prefs_->GetDictionary(pref_names::kExtensions);
+ for (base::DictionaryValue::Iterator extension_id(*extensions);
+ !extension_id.IsAtEnd(); extension_id.Advance()) {
+ const base::DictionaryValue* ext = NULL;
+ if (!crx_file::id_util::IdIsValid(extension_id.key()) ||
+ !IsExternalExtensionUninstalled(extension_id.key()) ||
+ !extension_id.value().GetAsDictionary(&ext))
+ continue;
+
+ scoped_ptr<ExtensionInfo> info =
+ GetInstalledInfoHelper(extension_id.key(), ext);
+ if (info)
+ extensions_info->push_back(linked_ptr<ExtensionInfo>(info.release()));
+ }
+
+ return extensions_info;
+}
+
+void ExtensionPrefs::SetDelayedInstallInfo(
+ const Extension* extension,
+ Extension::State initial_state,
+ int install_flags,
+ DelayReason delay_reason,
+ const syncer::StringOrdinal& page_ordinal,
+ const std::string& install_parameter) {
+ base::DictionaryValue* extension_dict = new base::DictionaryValue();
+ PopulateExtensionInfoPrefs(extension,
+ time_provider_->GetCurrentTime(),
+ initial_state,
+ install_flags,
+ install_parameter,
+ extension_dict);
+
+ // Add transient data that is needed by FinishDelayedInstallInfo(), but
+ // should not be in the final extension prefs. All entries here should have
+ // a corresponding Remove() call in FinishDelayedInstallInfo().
+ if (extension->RequiresSortOrdinal()) {
+ extension_dict->SetString(
+ kPrefSuggestedPageOrdinal,
+ page_ordinal.IsValid() ? page_ordinal.ToInternalValue()
+ : std::string());
+ }
+ extension_dict->SetInteger(kDelayedInstallReason,
+ static_cast<int>(delay_reason));
+
+ UpdateExtensionPref(extension->id(), kDelayedInstallInfo, extension_dict);
+}
+
+bool ExtensionPrefs::RemoveDelayedInstallInfo(
+ const std::string& extension_id) {
+ if (!GetExtensionPref(extension_id))
+ return false;
+ ScopedExtensionPrefUpdate update(prefs_, extension_id);
+ bool result = update->Remove(kDelayedInstallInfo, NULL);
+ return result;
+}
+
+bool ExtensionPrefs::FinishDelayedInstallInfo(
+ const std::string& extension_id) {
+ CHECK(crx_file::id_util::IdIsValid(extension_id));
+ ScopedExtensionPrefUpdate update(prefs_, extension_id);
+ base::DictionaryValue* extension_dict = update.Get();
+ base::DictionaryValue* pending_install_dict = NULL;
+ if (!extension_dict->GetDictionary(kDelayedInstallInfo,
+ &pending_install_dict)) {
+ return false;
+ }
+
+ // Retrieve and clear transient values populated by SetDelayedInstallInfo().
+ // Also do any other data cleanup that makes sense.
+ std::string serialized_ordinal;
+ syncer::StringOrdinal suggested_page_ordinal;
+ bool needs_sort_ordinal = false;
+ if (pending_install_dict->GetString(kPrefSuggestedPageOrdinal,
+ &serialized_ordinal)) {
+ suggested_page_ordinal = syncer::StringOrdinal(serialized_ordinal);
+ needs_sort_ordinal = true;
+ pending_install_dict->Remove(kPrefSuggestedPageOrdinal, NULL);
+ }
+ pending_install_dict->Remove(kDelayedInstallReason, NULL);
+
+ const base::Time install_time = time_provider_->GetCurrentTime();
+ pending_install_dict->Set(
+ kPrefInstallTime,
+ new base::StringValue(
+ base::Int64ToString(install_time.ToInternalValue())));
+
+ // Commit the delayed install data.
+ for (base::DictionaryValue::Iterator it(*pending_install_dict); !it.IsAtEnd();
+ it.Advance()) {
+ extension_dict->Set(it.key(), it.value().DeepCopy());
+ }
+ FinishExtensionInfoPrefs(extension_id, install_time, needs_sort_ordinal,
+ suggested_page_ordinal, extension_dict);
+ return true;
+}
+
+scoped_ptr<ExtensionInfo> ExtensionPrefs::GetDelayedInstallInfo(
+ const std::string& extension_id) const {
+ const base::DictionaryValue* extension_prefs =
+ GetExtensionPref(extension_id);
+ if (!extension_prefs)
+ return scoped_ptr<ExtensionInfo>();
+
+ const base::DictionaryValue* ext = NULL;
+ if (!extension_prefs->GetDictionary(kDelayedInstallInfo, &ext))
+ return scoped_ptr<ExtensionInfo>();
+
+ return GetInstalledInfoHelper(extension_id, ext);
+}
+
+ExtensionPrefs::DelayReason ExtensionPrefs::GetDelayedInstallReason(
+ const std::string& extension_id) const {
+ const base::DictionaryValue* extension_prefs =
+ GetExtensionPref(extension_id);
+ if (!extension_prefs)
+ return DELAY_REASON_NONE;
+
+ const base::DictionaryValue* ext = NULL;
+ if (!extension_prefs->GetDictionary(kDelayedInstallInfo, &ext))
+ return DELAY_REASON_NONE;
+
+ int delay_reason;
+ if (!ext->GetInteger(kDelayedInstallReason, &delay_reason))
+ return DELAY_REASON_NONE;
+
+ return static_cast<DelayReason>(delay_reason);
+}
+
+scoped_ptr<ExtensionPrefs::ExtensionsInfo> ExtensionPrefs::
+ GetAllDelayedInstallInfo() const {
+ scoped_ptr<ExtensionsInfo> extensions_info(new ExtensionsInfo);
+
+ const base::DictionaryValue* extensions =
+ prefs_->GetDictionary(pref_names::kExtensions);
+ for (base::DictionaryValue::Iterator extension_id(*extensions);
+ !extension_id.IsAtEnd(); extension_id.Advance()) {
+ if (!crx_file::id_util::IdIsValid(extension_id.key()))
+ continue;
+
+ scoped_ptr<ExtensionInfo> info = GetDelayedInstallInfo(extension_id.key());
+ if (info)
+ extensions_info->push_back(linked_ptr<ExtensionInfo>(info.release()));
+ }
+
+ return extensions_info;
+}
+
+bool ExtensionPrefs::WasAppDraggedByUser(
+ const std::string& extension_id) const {
+ return ReadPrefAsBooleanAndReturn(extension_id, kPrefUserDraggedApp);
+}
+
+void ExtensionPrefs::SetAppDraggedByUser(const std::string& extension_id) {
+ UpdateExtensionPref(extension_id, kPrefUserDraggedApp,
+ new base::FundamentalValue(true));
+}
+
+bool ExtensionPrefs::IsFromWebStore(
+ const std::string& extension_id) const {
+ const base::DictionaryValue* dictionary = GetExtensionPref(extension_id);
+ bool result = false;
+ if (dictionary && dictionary->GetBoolean(kPrefFromWebStore, &result))
+ return result;
+ return false;
+}
+
+bool ExtensionPrefs::IsFromBookmark(
+ const std::string& extension_id) const {
+ const base::DictionaryValue* dictionary = GetExtensionPref(extension_id);
+ bool result = false;
+ if (dictionary && dictionary->GetBoolean(kPrefFromBookmark, &result))
+ return result;
+ return false;
+}
+
+int ExtensionPrefs::GetCreationFlags(const std::string& extension_id) const {
+ int creation_flags = Extension::NO_FLAGS;
+ if (!ReadPrefAsInteger(extension_id, kPrefCreationFlags, &creation_flags)) {
+ // Since kPrefCreationFlags was added later, it will be missing for
+ // previously installed extensions.
+ if (IsFromBookmark(extension_id))
+ creation_flags |= Extension::FROM_BOOKMARK;
+ if (IsFromWebStore(extension_id))
+ creation_flags |= Extension::FROM_WEBSTORE;
+ if (WasInstalledByDefault(extension_id))
+ creation_flags |= Extension::WAS_INSTALLED_BY_DEFAULT;
+ if (WasInstalledByOem(extension_id))
+ creation_flags |= Extension::WAS_INSTALLED_BY_OEM;
+ }
+ return creation_flags;
+}
+
+int ExtensionPrefs::GetDelayedInstallCreationFlags(
+ const std::string& extension_id) const {
+ int creation_flags = Extension::NO_FLAGS;
+ const base::DictionaryValue* delayed_info = NULL;
+ if (ReadPrefAsDictionary(extension_id, kDelayedInstallInfo, &delayed_info)) {
+ delayed_info->GetInteger(kPrefCreationFlags, &creation_flags);
+ }
+ return creation_flags;
+}
+
+bool ExtensionPrefs::WasInstalledByDefault(
+ const std::string& extension_id) const {
+ const base::DictionaryValue* dictionary = GetExtensionPref(extension_id);
+ bool result = false;
+ if (dictionary &&
+ dictionary->GetBoolean(kPrefWasInstalledByDefault, &result))
+ return result;
+ return false;
+}
+
+bool ExtensionPrefs::WasInstalledByOem(const std::string& extension_id) const {
+ const base::DictionaryValue* dictionary = GetExtensionPref(extension_id);
+ bool result = false;
+ if (dictionary && dictionary->GetBoolean(kPrefWasInstalledByOem, &result))
+ return result;
+ return false;
+}
+
+base::Time ExtensionPrefs::GetInstallTime(
+ const std::string& extension_id) const {
+ const base::DictionaryValue* extension = GetExtensionPref(extension_id);
+ if (!extension) {
+ NOTREACHED();
+ return base::Time();
+ }
+ std::string install_time_str;
+ if (!extension->GetString(kPrefInstallTime, &install_time_str))
+ return base::Time();
+ int64_t install_time_i64 = 0;
+ if (!base::StringToInt64(install_time_str, &install_time_i64))
+ return base::Time();
+ return base::Time::FromInternalValue(install_time_i64);
+}
+
+bool ExtensionPrefs::DoNotSync(const std::string& extension_id) const {
+ bool do_not_sync;
+ if (!ReadPrefAsBoolean(extension_id, kPrefDoNotSync, &do_not_sync))
+ return false;
+
+ return do_not_sync;
+}
+
+base::Time ExtensionPrefs::GetLastLaunchTime(
+ const std::string& extension_id) const {
+ const base::DictionaryValue* extension = GetExtensionPref(extension_id);
+ if (!extension)
+ return base::Time();
+
+ std::string launch_time_str;
+ if (!extension->GetString(kPrefLastLaunchTime, &launch_time_str))
+ return base::Time();
+ int64_t launch_time_i64 = 0;
+ if (!base::StringToInt64(launch_time_str, &launch_time_i64))
+ return base::Time();
+ return base::Time::FromInternalValue(launch_time_i64);
+}
+
+void ExtensionPrefs::SetLastLaunchTime(const std::string& extension_id,
+ const base::Time& time) {
+ DCHECK(crx_file::id_util::IdIsValid(extension_id));
+ ScopedExtensionPrefUpdate update(prefs_, extension_id);
+ SaveTime(update.Get(), kPrefLastLaunchTime, time);
+}
+
+void ExtensionPrefs::ClearLastLaunchTimes() {
+ const base::DictionaryValue* dict =
+ prefs_->GetDictionary(pref_names::kExtensions);
+ if (!dict || dict->empty())
+ return;
+
+ // Collect all the keys to remove the last launched preference from.
+ DictionaryPrefUpdate update(prefs_, pref_names::kExtensions);
+ base::DictionaryValue* update_dict = update.Get();
+ for (base::DictionaryValue::Iterator i(*update_dict); !i.IsAtEnd();
+ i.Advance()) {
+ base::DictionaryValue* extension_dict = NULL;
+ if (!update_dict->GetDictionary(i.key(), &extension_dict))
+ continue;
+
+ if (extension_dict->HasKey(kPrefLastLaunchTime))
+ extension_dict->Remove(kPrefLastLaunchTime, NULL);
+ }
+}
+
+void ExtensionPrefs::GetExtensions(ExtensionIdList* out) const {
+ CHECK(out);
+
+ scoped_ptr<ExtensionsInfo> extensions_info(GetInstalledExtensionsInfo());
+
+ for (size_t i = 0; i < extensions_info->size(); ++i) {
+ ExtensionInfo* info = extensions_info->at(i).get();
+ out->push_back(info->extension_id);
+ }
+}
+
+// static
+ExtensionIdList ExtensionPrefs::GetExtensionsFrom(
+ const PrefService* pref_service) {
+ ExtensionIdList result;
+
+ const base::DictionaryValue* extension_prefs = NULL;
+ const base::Value* extension_prefs_value =
+ pref_service->GetUserPrefValue(pref_names::kExtensions);
+ if (!extension_prefs_value ||
+ !extension_prefs_value->GetAsDictionary(&extension_prefs)) {
+ return result; // Empty set
+ }
+
+ for (base::DictionaryValue::Iterator it(*extension_prefs); !it.IsAtEnd();
+ it.Advance()) {
+ const base::DictionaryValue* ext = NULL;
+ if (!it.value().GetAsDictionary(&ext)) {
+ NOTREACHED() << "Invalid pref for extension " << it.key();
+ continue;
+ }
+ if (!IsBlacklistBitSet(ext))
+ result.push_back(it.key());
+ }
+ return result;
+}
+
+void ExtensionPrefs::AddObserver(ExtensionPrefsObserver* observer) {
+ observer_list_.AddObserver(observer);
+}
+
+void ExtensionPrefs::RemoveObserver(ExtensionPrefsObserver* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+void ExtensionPrefs::InitPrefStore() {
+ TRACE_EVENT0("browser,startup", "ExtensionPrefs::InitPrefStore")
+ SCOPED_UMA_HISTOGRAM_TIMER("Extensions.InitPrefStoreTime");
+
+ if (extensions_disabled_) {
+ extension_pref_value_map_->NotifyInitializationCompleted();
+ return;
+ }
+
+ // When this is called, the PrefService is initialized and provides access
+ // to the user preferences stored in a JSON file.
+ ExtensionIdList extension_ids;
+ {
+ SCOPED_UMA_HISTOGRAM_TIMER("Extensions.InitPrefGetExtensionsTime");
+ GetExtensions(&extension_ids);
+ }
+ // Create empty preferences dictionary for each extension (these dictionaries
+ // are pruned when persisting the preferences to disk).
+ for (ExtensionIdList::iterator ext_id = extension_ids.begin();
+ ext_id != extension_ids.end(); ++ext_id) {
+ ScopedExtensionPrefUpdate update(prefs_, *ext_id);
+ // This creates an empty dictionary if none is stored.
+ update.Get();
+ }
+
+ InitExtensionControlledPrefs(extension_pref_value_map_);
+
+ extension_pref_value_map_->NotifyInitializationCompleted();
+}
+
+bool ExtensionPrefs::HasIncognitoPrefValue(const std::string& pref_key) const {
+ bool has_incognito_pref_value = false;
+ extension_pref_value_map_->GetEffectivePrefValue(pref_key,
+ true,
+ &has_incognito_pref_value);
+ return has_incognito_pref_value;
+}
+
+const base::DictionaryValue* ExtensionPrefs::GetGeometryCache(
+ const std::string& extension_id) const {
+ const base::DictionaryValue* extension_prefs = GetExtensionPref(extension_id);
+ if (!extension_prefs)
+ return NULL;
+
+ const base::DictionaryValue* ext = NULL;
+ if (!extension_prefs->GetDictionary(kPrefGeometryCache, &ext))
+ return NULL;
+
+ return ext;
+}
+
+void ExtensionPrefs::SetGeometryCache(
+ const std::string& extension_id,
+ scoped_ptr<base::DictionaryValue> cache) {
+ UpdateExtensionPref(extension_id, kPrefGeometryCache, cache.release());
+}
+
+const base::DictionaryValue* ExtensionPrefs::GetInstallSignature() const {
+ return prefs_->GetDictionary(kInstallSignature);
+}
+
+void ExtensionPrefs::SetInstallSignature(
+ const base::DictionaryValue* signature) {
+ if (signature) {
+ prefs_->Set(kInstallSignature, *signature);
+ DVLOG(1) << "SetInstallSignature - saving";
+ } else {
+ DVLOG(1) << "SetInstallSignature - clearing";
+ prefs_->ClearPref(kInstallSignature);
+ }
+}
+
+std::string ExtensionPrefs::GetInstallParam(
+ const std::string& extension_id) const {
+ const base::DictionaryValue* extension = GetExtensionPref(extension_id);
+ if (!extension) // Expected during unit testing.
+ return std::string();
+ std::string install_parameter;
+ if (!extension->GetString(kPrefInstallParam, &install_parameter))
+ return std::string();
+ return install_parameter;
+}
+
+void ExtensionPrefs::SetInstallParam(const std::string& extension_id,
+ const std::string& install_parameter) {
+ UpdateExtensionPref(extension_id,
+ kPrefInstallParam,
+ new base::StringValue(install_parameter));
+}
+
+int ExtensionPrefs::GetCorruptedDisableCount() const {
+ return prefs_->GetInteger(kCorruptedDisableCount);
+}
+
+void ExtensionPrefs::IncrementCorruptedDisableCount() {
+ int count = prefs_->GetInteger(kCorruptedDisableCount);
+ prefs_->SetInteger(kCorruptedDisableCount, count + 1);
+}
+
+bool ExtensionPrefs::NeedsSync(const std::string& extension_id) const {
+ return ReadPrefAsBooleanAndReturn(extension_id, kPrefNeedsSync);
+}
+
+void ExtensionPrefs::SetNeedsSync(const std::string& extension_id,
+ bool needs_sync) {
+ UpdateExtensionPref(extension_id, kPrefNeedsSync,
+ needs_sync ? new base::FundamentalValue(true) : nullptr);
+}
+
+ExtensionPrefs::ExtensionPrefs(
+ content::BrowserContext* browser_context,
+ PrefService* prefs,
+ const base::FilePath& root_dir,
+ ExtensionPrefValueMap* extension_pref_value_map,
+ scoped_ptr<TimeProvider> time_provider,
+ bool extensions_disabled,
+ const std::vector<ExtensionPrefsObserver*>& early_observers)
+ : browser_context_(browser_context),
+ prefs_(prefs),
+ install_directory_(root_dir),
+ extension_pref_value_map_(extension_pref_value_map),
+ time_provider_(std::move(time_provider)),
+ extensions_disabled_(extensions_disabled) {
+ MakePathsRelative();
+
+ // Ensure that any early observers are watching before prefs are initialized.
+ for (std::vector<ExtensionPrefsObserver*>::const_iterator iter =
+ early_observers.begin();
+ iter != early_observers.end();
+ ++iter) {
+ AddObserver(*iter);
+ }
+
+ InitPrefStore();
+}
+
+AppSorting* ExtensionPrefs::app_sorting() const {
+ return ExtensionSystem::Get(browser_context_)->app_sorting();
+}
+
+void ExtensionPrefs::SetNeedsStorageGarbageCollection(bool value) {
+ prefs_->SetBoolean(pref_names::kStorageGarbageCollect, value);
+}
+
+bool ExtensionPrefs::NeedsStorageGarbageCollection() const {
+ return prefs_->GetBoolean(pref_names::kStorageGarbageCollect);
+}
+
+// static
+void ExtensionPrefs::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) {
+ registry->RegisterDictionaryPref(pref_names::kExtensions);
+ registry->RegisterListPref(pref_names::kToolbar,
+ user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+ registry->RegisterIntegerPref(pref_names::kToolbarSize, -1);
+ registry->RegisterDictionaryPref(kExtensionsBlacklistUpdate);
+ registry->RegisterListPref(pref_names::kInstallAllowList);
+ registry->RegisterListPref(pref_names::kInstallDenyList);
+ registry->RegisterDictionaryPref(pref_names::kInstallForceList);
+ registry->RegisterListPref(pref_names::kAllowedTypes);
+ registry->RegisterBooleanPref(pref_names::kStorageGarbageCollect, false);
+ registry->RegisterInt64Pref(pref_names::kLastUpdateCheck, 0);
+ registry->RegisterInt64Pref(pref_names::kNextUpdateCheck, 0);
+ registry->RegisterListPref(pref_names::kAllowedInstallSites);
+ registry->RegisterStringPref(pref_names::kLastChromeVersion, std::string());
+ registry->RegisterDictionaryPref(kInstallSignature);
+
+ registry->RegisterListPref(pref_names::kNativeMessagingBlacklist);
+ registry->RegisterListPref(pref_names::kNativeMessagingWhitelist);
+ registry->RegisterBooleanPref(pref_names::kNativeMessagingUserLevelHosts,
+ true);
+ registry->RegisterIntegerPref(kCorruptedDisableCount, 0);
+
+#if !defined(OS_MACOSX)
+ registry->RegisterBooleanPref(pref_names::kAppFullscreenAllowed, true);
+#endif
+}
+
+template <class ExtensionIdContainer>
+bool ExtensionPrefs::GetUserExtensionPrefIntoContainer(
+ const char* pref,
+ ExtensionIdContainer* id_container_out) const {
+ DCHECK(id_container_out->empty());
+
+ const base::Value* user_pref_value = prefs_->GetUserPrefValue(pref);
+ const base::ListValue* user_pref_as_list;
+ if (!user_pref_value || !user_pref_value->GetAsList(&user_pref_as_list))
+ return false;
+
+ std::insert_iterator<ExtensionIdContainer> insert_iterator(
+ *id_container_out, id_container_out->end());
+ std::string extension_id;
+ for (base::ListValue::const_iterator value_it = user_pref_as_list->begin();
+ value_it != user_pref_as_list->end(); ++value_it) {
+ if (!(*value_it)->GetAsString(&extension_id)) {
+ NOTREACHED();
+ continue;
+ }
+ insert_iterator = extension_id;
+ }
+ return true;
+}
+
+template <class ExtensionIdContainer>
+void ExtensionPrefs::SetExtensionPrefFromContainer(
+ const char* pref,
+ const ExtensionIdContainer& strings) {
+ ListPrefUpdate update(prefs_, pref);
+ base::ListValue* list_of_values = update.Get();
+ list_of_values->Clear();
+ for (typename ExtensionIdContainer::const_iterator iter = strings.begin();
+ iter != strings.end(); ++iter) {
+ list_of_values->Append(new base::StringValue(*iter));
+ }
+}
+
+void ExtensionPrefs::PopulateExtensionInfoPrefs(
+ const Extension* extension,
+ const base::Time install_time,
+ Extension::State initial_state,
+ int install_flags,
+ const std::string& install_parameter,
+ base::DictionaryValue* extension_dict) const {
+ extension_dict->Set(kPrefState, new base::FundamentalValue(initial_state));
+ extension_dict->Set(kPrefLocation,
+ new base::FundamentalValue(extension->location()));
+ extension_dict->Set(kPrefCreationFlags,
+ new base::FundamentalValue(extension->creation_flags()));
+ extension_dict->Set(kPrefFromWebStore,
+ new base::FundamentalValue(extension->from_webstore()));
+ extension_dict->Set(kPrefFromBookmark,
+ new base::FundamentalValue(extension->from_bookmark()));
+ extension_dict->Set(
+ kPrefWasInstalledByDefault,
+ new base::FundamentalValue(extension->was_installed_by_default()));
+ extension_dict->Set(
+ kPrefWasInstalledByOem,
+ new base::FundamentalValue(extension->was_installed_by_oem()));
+ extension_dict->Set(kPrefInstallTime,
+ new base::StringValue(
+ base::Int64ToString(install_time.ToInternalValue())));
+ if (install_flags & kInstallFlagIsBlacklistedForMalware)
+ extension_dict->Set(kPrefBlacklist, new base::FundamentalValue(true));
+
+ base::FilePath::StringType path = MakePathRelative(install_directory_,
+ extension->path());
+ extension_dict->Set(kPrefPath, new base::StringValue(path));
+ if (!install_parameter.empty()) {
+ extension_dict->Set(kPrefInstallParam,
+ new base::StringValue(install_parameter));
+ }
+ // We store prefs about LOAD extensions, but don't cache their manifest
+ // since it may change on disk.
+ if (!Manifest::IsUnpackedLocation(extension->location())) {
+ extension_dict->Set(kPrefManifest,
+ extension->manifest()->value()->DeepCopy());
+ }
+
+ // Only writes kPrefDoNotSync when it is not the default.
+ if (install_flags & kInstallFlagDoNotSync)
+ extension_dict->Set(kPrefDoNotSync, new base::FundamentalValue(true));
+ else
+ extension_dict->Remove(kPrefDoNotSync, NULL);
+}
+
+void ExtensionPrefs::InitExtensionControlledPrefs(
+ ExtensionPrefValueMap* value_map) {
+ TRACE_EVENT0("browser,startup",
+ "ExtensionPrefs::InitExtensionControlledPrefs")
+ SCOPED_UMA_HISTOGRAM_TIMER("Extensions.InitExtensionControlledPrefsTime");
+
+ ExtensionIdList extension_ids;
+ GetExtensions(&extension_ids);
+
+ for (ExtensionIdList::iterator extension_id = extension_ids.begin();
+ extension_id != extension_ids.end();
+ ++extension_id) {
+ base::Time install_time = GetInstallTime(*extension_id);
+ bool is_enabled = !IsExtensionDisabled(*extension_id);
+ bool is_incognito_enabled = IsIncognitoEnabled(*extension_id);
+ value_map->RegisterExtension(
+ *extension_id, install_time, is_enabled, is_incognito_enabled);
+
+ FOR_EACH_OBSERVER(
+ ExtensionPrefsObserver,
+ observer_list_,
+ OnExtensionRegistered(*extension_id, install_time, is_enabled));
+
+ // Set regular extension controlled prefs.
+ LoadExtensionControlledPrefs(
+ this, value_map, *extension_id, kExtensionPrefsScopeRegular);
+ // Set incognito extension controlled prefs.
+ LoadExtensionControlledPrefs(this,
+ value_map,
+ *extension_id,
+ kExtensionPrefsScopeIncognitoPersistent);
+ // Set regular-only extension controlled prefs.
+ LoadExtensionControlledPrefs(
+ this, value_map, *extension_id, kExtensionPrefsScopeRegularOnly);
+
+ FOR_EACH_OBSERVER(ExtensionPrefsObserver,
+ observer_list_,
+ OnExtensionPrefsLoaded(*extension_id, this));
+ }
+}
+
+void ExtensionPrefs::FinishExtensionInfoPrefs(
+ const std::string& extension_id,
+ const base::Time install_time,
+ bool needs_sort_ordinal,
+ const syncer::StringOrdinal& suggested_page_ordinal,
+ base::DictionaryValue* extension_dict) {
+ // Reinitializes various preferences with empty dictionaries.
+ if (!extension_dict->HasKey(pref_names::kPrefPreferences)) {
+ extension_dict->Set(pref_names::kPrefPreferences,
+ new base::DictionaryValue);
+ }
+
+ if (!extension_dict->HasKey(pref_names::kPrefIncognitoPreferences)) {
+ extension_dict->Set(pref_names::kPrefIncognitoPreferences,
+ new base::DictionaryValue);
+ }
+
+ if (!extension_dict->HasKey(pref_names::kPrefRegularOnlyPreferences)) {
+ extension_dict->Set(pref_names::kPrefRegularOnlyPreferences,
+ new base::DictionaryValue);
+ }
+
+ if (!extension_dict->HasKey(pref_names::kPrefContentSettings))
+ extension_dict->Set(pref_names::kPrefContentSettings, new base::ListValue);
+
+ if (!extension_dict->HasKey(pref_names::kPrefIncognitoContentSettings)) {
+ extension_dict->Set(pref_names::kPrefIncognitoContentSettings,
+ new base::ListValue);
+ }
+
+ // If this point has been reached, any pending installs should be considered
+ // out of date.
+ extension_dict->Remove(kDelayedInstallInfo, NULL);
+
+ // Clear state that may be registered from a previous install.
+ extension_dict->Remove(EventRouter::kRegisteredEvents, NULL);
+
+ // FYI, all code below here races on sudden shutdown because |extension_dict|,
+ // |app_sorting|, |extension_pref_value_map_|, and (potentially) observers
+ // are updated non-transactionally. This is probably not fixable without
+ // nested transactional updates to pref dictionaries.
+ if (needs_sort_ordinal)
+ app_sorting()->EnsureValidOrdinals(extension_id, suggested_page_ordinal);
+
+ bool is_enabled = false;
+ int initial_state;
+ if (extension_dict->GetInteger(kPrefState, &initial_state)) {
+ is_enabled = initial_state == Extension::ENABLED;
+ }
+ bool is_incognito_enabled = IsIncognitoEnabled(extension_id);
+
+ extension_pref_value_map_->RegisterExtension(
+ extension_id, install_time, is_enabled, is_incognito_enabled);
+
+ FOR_EACH_OBSERVER(
+ ExtensionPrefsObserver,
+ observer_list_,
+ OnExtensionRegistered(extension_id, install_time, is_enabled));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_prefs.h b/chromium/extensions/browser/extension_prefs.h
new file mode 100644
index 00000000000..80b5e371677
--- /dev/null
+++ b/chromium/extensions/browser/extension_prefs.h
@@ -0,0 +1,695 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_PREFS_H_
+#define EXTENSIONS_BROWSER_EXTENSION_PREFS_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "extensions/browser/blacklist_state.h"
+#include "extensions/browser/extension_scoped_prefs.h"
+#include "extensions/browser/install_flag.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/url_pattern_set.h"
+#include "sync/api/string_ordinal.h"
+
+class ExtensionPrefValueMap;
+class PrefService;
+
+namespace content {
+class BrowserContext;
+}
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+namespace extensions {
+
+class AppSorting;
+class ExtensionPrefsObserver;
+class URLPatternSet;
+
+// Class for managing global and per-extension preferences.
+//
+// This class distinguishes the following kinds of preferences:
+// - global preferences:
+// internal state for the extension system in general, not associated
+// with an individual extension, such as lastUpdateTime.
+// - per-extension preferences:
+// meta-preferences describing properties of the extension like
+// installation time, whether the extension is enabled, etc.
+// - extension controlled preferences:
+// browser preferences that an extension controls. For example, an
+// extension could use the proxy API to specify the browser's proxy
+// preference. Extension-controlled preferences are stored in
+// PrefValueStore::extension_prefs(), which this class populates and
+// maintains as the underlying extensions change.
+class ExtensionPrefs : public ExtensionScopedPrefs, public KeyedService {
+ public:
+ typedef std::vector<linked_ptr<ExtensionInfo> > ExtensionsInfo;
+
+ // Vector containing identifiers for preferences.
+ typedef std::set<std::string> PrefKeySet;
+
+ // This enum is used to store the reason an extension's install has been
+ // delayed. Do not remove items or re-order this enum as it is used in
+ // preferences.
+ enum DelayReason {
+ DELAY_REASON_NONE = 0,
+ DELAY_REASON_GC = 1,
+ DELAY_REASON_WAIT_FOR_IDLE = 2,
+ DELAY_REASON_WAIT_FOR_IMPORTS = 3,
+ };
+
+ // Creates base::Time classes. The default implementation is just to return
+ // the current time, but tests can inject alternative implementations.
+ class TimeProvider {
+ public:
+ TimeProvider();
+
+ virtual ~TimeProvider();
+
+ // By default, returns the current time (base::Time::Now()).
+ virtual base::Time GetCurrentTime() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TimeProvider);
+ };
+
+ // A wrapper around a ScopedUserPrefUpdate, which allows us to access the
+ // entry of a particular key for an extension. Use this if you need a mutable
+ // record of a dictionary or list in the current settings. Otherwise, prefer
+ // ReadPrefAsT() and UpdateExtensionPref() methods.
+ template <typename T, base::Value::Type type_enum_value>
+ class ScopedUpdate {
+ public:
+ ScopedUpdate(ExtensionPrefs* prefs,
+ const std::string& extension_id,
+ const std::string& key);
+ virtual ~ScopedUpdate();
+
+ // Returns a mutable value for the key (ownership remains with the prefs),
+ // if one exists. Otherwise, returns NULL.
+ virtual T* Get();
+
+ // Creates and returns a mutable value for the key (the prefs own the new
+ // value), if one does not already exist. Otherwise, returns the current
+ // value.
+ virtual T* Create();
+
+ private:
+ DictionaryPrefUpdate update_;
+ const std::string extension_id_;
+ const std::string key_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedUpdate);
+ };
+ typedef ScopedUpdate<base::DictionaryValue, base::Value::TYPE_DICTIONARY>
+ ScopedDictionaryUpdate;
+ typedef ScopedUpdate<base::ListValue, base::Value::TYPE_LIST>
+ ScopedListUpdate;
+
+ // Creates an ExtensionPrefs object.
+ // Does not take ownership of |prefs| or |extension_pref_value_map|.
+ // If |extensions_disabled| is true, extension controlled preferences and
+ // content settings do not become effective. ExtensionPrefsObservers should be
+ // included in |early_observers| if they need to observe events which occur
+ // during initialization of the ExtensionPrefs object.
+ static ExtensionPrefs* Create(
+ content::BrowserContext* browser_context,
+ PrefService* prefs,
+ const base::FilePath& root_dir,
+ ExtensionPrefValueMap* extension_pref_value_map,
+ bool extensions_disabled,
+ const std::vector<ExtensionPrefsObserver*>& early_observers);
+
+ // A version of Create which allows injection of a custom base::Time provider.
+ // Use this as needed for testing.
+ static ExtensionPrefs* Create(
+ content::BrowserContext* browser_context,
+ PrefService* prefs,
+ const base::FilePath& root_dir,
+ ExtensionPrefValueMap* extension_pref_value_map,
+ bool extensions_disabled,
+ const std::vector<ExtensionPrefsObserver*>& early_observers,
+ scoped_ptr<TimeProvider> time_provider);
+
+ ~ExtensionPrefs() override;
+
+ // Convenience function to get the ExtensionPrefs for a BrowserContext.
+ static ExtensionPrefs* Get(content::BrowserContext* context);
+
+ // Returns all installed extensions from extension preferences provided by
+ // |pref_service|. This is exposed for ProtectedPrefsWatcher because it needs
+ // access to the extension ID list before the ExtensionService is initialized.
+ static ExtensionIdList GetExtensionsFrom(const PrefService* pref_service);
+
+ // Add or remove an observer from the ExtensionPrefs.
+ void AddObserver(ExtensionPrefsObserver* observer);
+ void RemoveObserver(ExtensionPrefsObserver* observer);
+
+ // Returns true if the specified external extension was uninstalled by the
+ // user.
+ bool IsExternalExtensionUninstalled(const std::string& id) const;
+
+ // Checks whether |extension_id| is disabled. If there's no state pref for
+ // the extension, this will return false. Generally you should use
+ // ExtensionService::IsExtensionEnabled instead.
+ // Note that blacklisted extensions are NOT marked as disabled!
+ bool IsExtensionDisabled(const std::string& id) const;
+
+ // Get/Set the order that the browser actions appear in the toolbar.
+ ExtensionIdList GetToolbarOrder() const;
+ void SetToolbarOrder(const ExtensionIdList& extension_ids);
+
+ // Called when an extension is installed, so that prefs get created.
+ // If |page_ordinal| is invalid then a page will be found for the App.
+ // |install_flags| are a bitmask of extension::InstallFlags.
+ void OnExtensionInstalled(const Extension* extension,
+ Extension::State initial_state,
+ const syncer::StringOrdinal& page_ordinal,
+ int install_flags,
+ const std::string& install_parameter);
+ // OnExtensionInstalled with no install flags.
+ void OnExtensionInstalled(const Extension* extension,
+ Extension::State initial_state,
+ const syncer::StringOrdinal& page_ordinal,
+ const std::string& install_parameter) {
+ OnExtensionInstalled(extension,
+ initial_state,
+ page_ordinal,
+ kInstallFlagNone,
+ install_parameter);
+ }
+
+ // Called when an extension is uninstalled, so that prefs get cleaned up.
+ void OnExtensionUninstalled(const std::string& extension_id,
+ const Manifest::Location& location,
+ bool external_uninstall);
+
+ // Sets the extension's state to enabled and clears disable reasons.
+ void SetExtensionEnabled(const std::string& extension_id);
+
+ // Sets the extension's state to disabled and sets the disable reasons.
+ // However, if the current state is EXTERNAL_EXTENSION_UNINSTALLED then that
+ // is preserved (but the disable reasons are still set).
+ void SetExtensionDisabled(const std::string& extension_id,
+ int disable_reasons);
+
+ // Called to change the extension's BlacklistState. Currently only used for
+ // non-malicious extensions.
+ // TODO(oleg): replace SetExtensionBlacklisted by this function.
+ void SetExtensionBlacklistState(const std::string& extension_id,
+ BlacklistState state);
+
+ // Checks whether |extension_id| is marked as greylisted.
+ // TODO(oleg): Replace IsExtensionBlacklisted by this method.
+ BlacklistState GetExtensionBlacklistState(
+ const std::string& extension_id) const;
+
+ // Populates |out| with the ids of all installed extensions.
+ void GetExtensions(ExtensionIdList* out) const;
+
+ // ExtensionScopedPrefs methods:
+ void UpdateExtensionPref(const std::string& id,
+ const std::string& key,
+ base::Value* value) override;
+
+ void DeleteExtensionPrefs(const std::string& id) override;
+
+ bool ReadPrefAsBoolean(const std::string& extension_id,
+ const std::string& pref_key,
+ bool* out_value) const override;
+
+ bool ReadPrefAsInteger(const std::string& extension_id,
+ const std::string& pref_key,
+ int* out_value) const override;
+
+ bool ReadPrefAsString(const std::string& extension_id,
+ const std::string& pref_key,
+ std::string* out_value) const override;
+
+ bool ReadPrefAsList(const std::string& extension_id,
+ const std::string& pref_key,
+ const base::ListValue** out_value) const override;
+
+ bool ReadPrefAsDictionary(
+ const std::string& extension_id,
+ const std::string& pref_key,
+ const base::DictionaryValue** out_value) const override;
+
+ bool HasPrefForExtension(const std::string& extension_id) const override;
+
+ // Did the extension ask to escalate its permission during an upgrade?
+ bool DidExtensionEscalatePermissions(const std::string& id) const;
+
+ // Getters and setters for disabled reason.
+ // Note that you should rarely need to modify disable reasons directly -
+ // pass the proper value to SetExtensionState instead when you enable/disable
+ // an extension. In particular, AddDisableReason(s) is only legal when the
+ // extension is not enabled.
+ int GetDisableReasons(const std::string& extension_id) const;
+ bool HasDisableReason(const std::string& extension_id,
+ Extension::DisableReason disable_reason) const;
+ void AddDisableReason(const std::string& extension_id,
+ Extension::DisableReason disable_reason);
+ void AddDisableReasons(const std::string& extension_id, int disable_reasons);
+ void RemoveDisableReason(const std::string& extension_id,
+ Extension::DisableReason disable_reason);
+ void ReplaceDisableReasons(const std::string& extension_id,
+ int disable_reasons);
+ void ClearDisableReasons(const std::string& extension_id);
+
+ // Gets the set of extensions that have been blacklisted in prefs. This will
+ // return only the blocked extensions, not the "greylist" extensions.
+ // TODO(oleg): Make method names consistent here, in extension service and in
+ // blacklist.
+ std::set<std::string> GetBlacklistedExtensions() const;
+
+ // Sets whether the extension with |id| is blacklisted.
+ void SetExtensionBlacklisted(const std::string& extension_id,
+ bool is_blacklisted);
+
+ // Returns the version string for the currently installed extension, or
+ // the empty string if not found.
+ std::string GetVersionString(const std::string& extension_id) const;
+
+ // Re-writes the extension manifest into the prefs.
+ // Called to change the extension's manifest when it's re-localized.
+ void UpdateManifest(const Extension* extension);
+
+ // Returns base extensions install directory.
+ const base::FilePath& install_directory() const { return install_directory_; }
+
+ // Returns whether the extension with |id| has its blacklist bit set.
+ //
+ // WARNING: this only checks the extension's entry in prefs, so by definition
+ // can only check extensions that prefs knows about. There may be other
+ // sources of blacklist information, such as safebrowsing. You probably want
+ // to use Blacklist::GetBlacklistedIDs rather than this method.
+ bool IsExtensionBlacklisted(const std::string& id) const;
+
+ // Increment the count of how many times we prompted the user to acknowledge
+ // the given extension, and return the new count.
+ int IncrementAcknowledgePromptCount(const std::string& extension_id);
+
+ // Whether the user has acknowledged an external extension.
+ bool IsExternalExtensionAcknowledged(const std::string& extension_id) const;
+ void AcknowledgeExternalExtension(const std::string& extension_id);
+
+ // Whether the user has acknowledged a blacklisted extension.
+ bool IsBlacklistedExtensionAcknowledged(
+ const std::string& extension_id) const;
+ void AcknowledgeBlacklistedExtension(const std::string& extension_id);
+
+ // Whether the external extension was installed during the first run
+ // of this profile.
+ bool IsExternalInstallFirstRun(const std::string& extension_id) const;
+ void SetExternalInstallFirstRun(const std::string& extension_id);
+
+ // Returns true if the extension notification code has already run for the
+ // first time for this profile. Currently we use this flag to mean that any
+ // extensions that would trigger notifications should get silently
+ // acknowledged. This is a fuse. Calling it the first time returns false.
+ // Subsequent calls return true. It's not possible through an API to ever
+ // reset it. Don't call it unless you mean it!
+ bool SetAlertSystemFirstRun();
+
+ // Returns the last value set via SetLastPingDay. If there isn't such a
+ // pref, the returned Time will return true for is_null().
+ base::Time LastPingDay(const std::string& extension_id) const;
+
+ // The time stored is based on the server's perspective of day start time, not
+ // the client's.
+ void SetLastPingDay(const std::string& extension_id, const base::Time& time);
+
+ // Similar to the 2 above, but for the extensions blacklist.
+ base::Time BlacklistLastPingDay() const;
+ void SetBlacklistLastPingDay(const base::Time& time);
+
+ // Similar to LastPingDay/SetLastPingDay, but for sending "days since active"
+ // ping.
+ base::Time LastActivePingDay(const std::string& extension_id) const;
+ void SetLastActivePingDay(const std::string& extension_id,
+ const base::Time& time);
+
+ // A bit we use for determining if we should send the "days since active"
+ // ping. A value of true means the item has been active (launched) since the
+ // last update check.
+ bool GetActiveBit(const std::string& extension_id) const;
+ void SetActiveBit(const std::string& extension_id, bool active);
+
+ // Returns the granted permission set for the extension with |extension_id|,
+ // and NULL if no preferences were found for |extension_id|.
+ // This passes ownership of the returned set to the caller.
+ scoped_ptr<const PermissionSet> GetGrantedPermissions(
+ const std::string& extension_id) const;
+
+ // Adds |permissions| to the granted permissions set for the extension with
+ // |extension_id|. The new granted permissions set will be the union of
+ // |permissions| and the already granted permissions.
+ void AddGrantedPermissions(const std::string& extension_id,
+ const PermissionSet& permissions);
+
+ // As above, but subtracts the given |permissions| from the granted set.
+ void RemoveGrantedPermissions(const std::string& extension_id,
+ const PermissionSet& permissions);
+
+ // Gets the active permission set for the specified extension. This may
+ // differ from the permissions in the manifest due to the optional
+ // permissions API. This passes ownership of the set to the caller.
+ scoped_ptr<const PermissionSet> GetActivePermissions(
+ const std::string& extension_id) const;
+
+ // Sets the active |permissions| for the extension with |extension_id|.
+ void SetActivePermissions(const std::string& extension_id,
+ const PermissionSet& permissions);
+
+ // Records whether or not this extension is currently running.
+ void SetExtensionRunning(const std::string& extension_id, bool is_running);
+
+ // Returns whether or not this extension is marked as running. This is used to
+ // restart apps across browser restarts.
+ bool IsExtensionRunning(const std::string& extension_id) const;
+
+ // Set/Get whether or not the app is active. Used to force a launch of apps
+ // that don't handle onRestarted() on a restart. We can only safely do that if
+ // the app was active when it was last running.
+ void SetIsActive(const std::string& extension_id, bool is_active);
+ bool IsActive(const std::string& extension_id) const;
+
+ // Returns true if the user enabled this extension to be loaded in incognito
+ // mode.
+ //
+ // IMPORTANT: you probably want to use extensions::util::IsIncognitoEnabled
+ // instead of this method.
+ bool IsIncognitoEnabled(const std::string& extension_id) const;
+ void SetIsIncognitoEnabled(const std::string& extension_id, bool enabled);
+
+ // Returns true if the user has chosen to allow this extension to inject
+ // scripts into pages with file URLs.
+ //
+ // IMPORTANT: you probably want to use extensions::util::AllowFileAccess
+ // instead of this method.
+ bool AllowFileAccess(const std::string& extension_id) const;
+ void SetAllowFileAccess(const std::string& extension_id, bool allow);
+ bool HasAllowFileAccessSetting(const std::string& extension_id) const;
+
+ // Saves ExtensionInfo for each installed extension with the path to the
+ // version directory and the location. Blacklisted extensions won't be saved
+ // and neither will external extensions the user has explicitly uninstalled.
+ // Caller takes ownership of returned structure.
+ scoped_ptr<ExtensionsInfo> GetInstalledExtensionsInfo() const;
+
+ // Same as above, but only includes external extensions the user has
+ // explicitly uninstalled.
+ scoped_ptr<ExtensionsInfo> GetUninstalledExtensionsInfo() const;
+
+ // Returns the ExtensionInfo from the prefs for the given extension. If the
+ // extension is not present, NULL is returned.
+ scoped_ptr<ExtensionInfo> GetInstalledExtensionInfo(
+ const std::string& extension_id) const;
+
+ // We've downloaded an updated .crx file for the extension, but are waiting
+ // to install it.
+ //
+ // |install_flags| are a bitmask of extension::InstallFlags.
+ void SetDelayedInstallInfo(const Extension* extension,
+ Extension::State initial_state,
+ int install_flags,
+ DelayReason delay_reason,
+ const syncer::StringOrdinal& page_ordinal,
+ const std::string& install_parameter);
+
+ // Removes any delayed install information we have for the given
+ // |extension_id|. Returns true if there was info to remove; false otherwise.
+ bool RemoveDelayedInstallInfo(const std::string& extension_id);
+
+ // Update the prefs to finish the update for an extension.
+ bool FinishDelayedInstallInfo(const std::string& extension_id);
+
+ // Returns the ExtensionInfo from the prefs for delayed install information
+ // for |extension_id|, if we have any. Otherwise returns NULL.
+ scoped_ptr<ExtensionInfo> GetDelayedInstallInfo(
+ const std::string& extension_id) const;
+
+ DelayReason GetDelayedInstallReason(const std::string& extension_id) const;
+
+ // Returns information about all the extensions that have delayed install
+ // information.
+ scoped_ptr<ExtensionsInfo> GetAllDelayedInstallInfo() const;
+
+ // Returns true if the user repositioned the app on the app launcher via drag
+ // and drop.
+ bool WasAppDraggedByUser(const std::string& extension_id) const;
+
+ // Sets a flag indicating that the user repositioned the app on the app
+ // launcher by drag and dropping it.
+ void SetAppDraggedByUser(const std::string& extension_id);
+
+ // Returns true if there is an extension which controls the preference value
+ // for |pref_key| *and* it is specific to incognito mode.
+ bool HasIncognitoPrefValue(const std::string& pref_key) const;
+
+ // Returns the creation flags mask for the extension.
+ int GetCreationFlags(const std::string& extension_id) const;
+
+ // Returns the creation flags mask for a delayed install extension.
+ int GetDelayedInstallCreationFlags(const std::string& extension_id) const;
+
+ // Returns true if the extension was installed from the Chrome Web Store.
+ bool IsFromWebStore(const std::string& extension_id) const;
+
+ // Returns true if the extension was installed from an App generated from a
+ // bookmark.
+ bool IsFromBookmark(const std::string& extension_id) const;
+
+ // Returns true if the extension was installed as a default app.
+ bool WasInstalledByDefault(const std::string& extension_id) const;
+
+ // Returns true if the extension was installed as an oem app.
+ bool WasInstalledByOem(const std::string& extension_id) const;
+
+ // Helper method to acquire the installation time of an extension.
+ // Returns base::Time() if the installation time could not be parsed or
+ // found.
+ base::Time GetInstallTime(const std::string& extension_id) const;
+
+ // Returns true if the extension should not be synced.
+ bool DoNotSync(const std::string& extension_id) const;
+
+ // Gets/sets the last launch time of an extension.
+ base::Time GetLastLaunchTime(const std::string& extension_id) const;
+ void SetLastLaunchTime(const std::string& extension_id,
+ const base::Time& time);
+
+ // Clear any launch times. This is called by the browsing data remover when
+ // history is cleared.
+ void ClearLastLaunchTimes();
+
+ static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+ bool extensions_disabled() const { return extensions_disabled_; }
+
+ // The underlying PrefService.
+ PrefService* pref_service() const { return prefs_; }
+
+ // The underlying AppSorting.
+ AppSorting* app_sorting() const;
+
+ // Schedules garbage collection of an extension's on-disk data on the next
+ // start of this ExtensionService. Applies only to extensions with isolated
+ // storage.
+ void SetNeedsStorageGarbageCollection(bool value);
+ bool NeedsStorageGarbageCollection() const;
+
+ // Used by AppWindowGeometryCache to persist its cache. These methods
+ // should not be called directly.
+ const base::DictionaryValue* GetGeometryCache(
+ const std::string& extension_id) const;
+ void SetGeometryCache(const std::string& extension_id,
+ scoped_ptr<base::DictionaryValue> cache);
+
+ // Used for verification of installed extension ids. For the Set method, pass
+ // null to remove the preference.
+ const base::DictionaryValue* GetInstallSignature() const;
+ void SetInstallSignature(const base::DictionaryValue* signature);
+
+ // The installation parameter associated with the extension.
+ std::string GetInstallParam(const std::string& extension_id) const;
+ void SetInstallParam(const std::string& extension_id,
+ const std::string& install_parameter);
+
+ // The total number of times we've disabled an extension due to corrupted
+ // contents.
+ int GetCorruptedDisableCount() const;
+ void IncrementCorruptedDisableCount();
+
+ // Whether the extension with the given |id| needs to be synced. This is set
+ // when the state (such as enabled/disabled or allowed in incognito) is
+ // changed before Sync is ready.
+ bool NeedsSync(const std::string& extension_id) const;
+ void SetNeedsSync(const std::string& extension_id, bool needs_sync);
+
+ private:
+ friend class ExtensionPrefsBlacklistedExtensions; // Unit test.
+ friend class ExtensionPrefsComponentExtension; // Unit test.
+ friend class ExtensionPrefsUninstallExtension; // Unit test.
+
+ enum DisableReasonChange {
+ DISABLE_REASON_ADD,
+ DISABLE_REASON_REMOVE,
+ DISABLE_REASON_REPLACE,
+ DISABLE_REASON_CLEAR
+ };
+
+ // See the Create methods.
+ ExtensionPrefs(content::BrowserContext* browser_context,
+ PrefService* prefs,
+ const base::FilePath& root_dir,
+ ExtensionPrefValueMap* extension_pref_value_map,
+ scoped_ptr<TimeProvider> time_provider,
+ bool extensions_disabled,
+ const std::vector<ExtensionPrefsObserver*>& early_observers);
+
+ // Converts absolute paths in the pref to paths relative to the
+ // install_directory_.
+ void MakePathsRelative();
+
+ // Converts internal relative paths to be absolute. Used for export to
+ // consumers who expect full paths.
+ void MakePathsAbsolute(base::DictionaryValue* dict);
+
+ // Helper function used by GetInstalledExtensionInfo() and
+ // GetDelayedInstallInfo() to construct an ExtensionInfo from the provided
+ // |extension| dictionary.
+ scoped_ptr<ExtensionInfo> GetInstalledInfoHelper(
+ const std::string& extension_id,
+ const base::DictionaryValue* extension) const;
+
+ // Interprets the list pref, |pref_key| in |extension_id|'s preferences, as a
+ // URLPatternSet. The |valid_schemes| specify how to parse the URLPatterns.
+ bool ReadPrefAsURLPatternSet(const std::string& extension_id,
+ const std::string& pref_key,
+ URLPatternSet* result,
+ int valid_schemes) const;
+
+ // Converts |new_value| to a list of strings and sets the |pref_key| pref
+ // belonging to |extension_id|.
+ void SetExtensionPrefURLPatternSet(const std::string& extension_id,
+ const std::string& pref_key,
+ const URLPatternSet& new_value);
+
+ // Read the boolean preference entry and return true if the preference exists
+ // and the preference's value is true; false otherwise.
+ bool ReadPrefAsBooleanAndReturn(const std::string& extension_id,
+ const std::string& key) const;
+
+ // Interprets |pref_key| in |extension_id|'s preferences as an
+ // PermissionSet, and passes ownership of the set to the caller.
+ scoped_ptr<const PermissionSet> ReadPrefAsPermissionSet(
+ const std::string& extension_id,
+ const std::string& pref_key) const;
+
+ // Converts the |new_value| to its value and sets the |pref_key| pref
+ // belonging to |extension_id|.
+ void SetExtensionPrefPermissionSet(const std::string& extension_id,
+ const std::string& pref_key,
+ const PermissionSet& new_value);
+
+ // Returns an immutable dictionary for extension |id|'s prefs, or NULL if it
+ // doesn't exist.
+ const base::DictionaryValue* GetExtensionPref(const std::string& id) const;
+
+ // Modifies the extensions disable reasons to add a new reason, remove an
+ // existing reason, or clear all reasons. Notifies observers if the set of
+ // DisableReasons has changed.
+ // If |change| is DISABLE_REASON_CLEAR, then |reason| is ignored.
+ void ModifyDisableReasons(const std::string& extension_id,
+ int reasons,
+ DisableReasonChange change);
+
+ // Installs the persistent extension preferences into |prefs_|'s extension
+ // pref store. Does nothing if extensions_disabled_ is true.
+ void InitPrefStore();
+
+ // Checks whether there is a state pref for the extension and if so, whether
+ // it matches |check_state|.
+ bool DoesExtensionHaveState(const std::string& id,
+ Extension::State check_state) const;
+
+ // Reads the list of strings for |pref| from user prefs into
+ // |id_container_out|. Returns false if the pref wasn't found in the user
+ // pref store.
+ template <class ExtensionIdContainer>
+ bool GetUserExtensionPrefIntoContainer(
+ const char* pref,
+ ExtensionIdContainer* id_container_out) const;
+
+ // Writes the list of strings contained in |strings| to |pref| in prefs.
+ template <class ExtensionIdContainer>
+ void SetExtensionPrefFromContainer(const char* pref,
+ const ExtensionIdContainer& strings);
+
+ // Helper function to populate |extension_dict| with the values needed
+ // by a newly installed extension. Work is broken up between this
+ // function and FinishExtensionInfoPrefs() to accommodate delayed
+ // installations.
+ //
+ // |install_flags| are a bitmask of extension::InstallFlags.
+ void PopulateExtensionInfoPrefs(const Extension* extension,
+ const base::Time install_time,
+ Extension::State initial_state,
+ int install_flags,
+ const std::string& install_parameter,
+ base::DictionaryValue* extension_dict) const;
+
+ void InitExtensionControlledPrefs(ExtensionPrefValueMap* value_map);
+
+ // Helper function to complete initialization of the values in
+ // |extension_dict| for an extension install. Also see
+ // PopulateExtensionInfoPrefs().
+ void FinishExtensionInfoPrefs(
+ const std::string& extension_id,
+ const base::Time install_time,
+ bool needs_sort_ordinal,
+ const syncer::StringOrdinal& suggested_page_ordinal,
+ base::DictionaryValue* extension_dict);
+
+ content::BrowserContext* browser_context_;
+
+ // The pref service specific to this set of extension prefs. Owned by the
+ // BrowserContext.
+ PrefService* prefs_;
+
+ // Base extensions install directory.
+ base::FilePath install_directory_;
+
+ // Weak pointer, owned by BrowserContext.
+ ExtensionPrefValueMap* extension_pref_value_map_;
+
+ scoped_ptr<TimeProvider> time_provider_;
+
+ bool extensions_disabled_;
+
+ base::ObserverList<ExtensionPrefsObserver> observer_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionPrefs);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_PREFS_H_
diff --git a/chromium/extensions/browser/extension_prefs_factory.cc b/chromium/extensions/browser/extension_prefs_factory.cc
new file mode 100644
index 00000000000..888d9bd4f9f
--- /dev/null
+++ b/chromium/extensions/browser/extension_prefs_factory.cc
@@ -0,0 +1,68 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_prefs_factory.h"
+
+#include <utility>
+
+#include "base/command_line.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/extension_pref_value_map.h"
+#include "extensions/browser/extension_pref_value_map_factory.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/common/constants.h"
+
+namespace extensions {
+
+// static
+ExtensionPrefs* ExtensionPrefsFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<ExtensionPrefs*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+ExtensionPrefsFactory* ExtensionPrefsFactory::GetInstance() {
+ return base::Singleton<ExtensionPrefsFactory>::get();
+}
+
+void ExtensionPrefsFactory::SetInstanceForTesting(
+ content::BrowserContext* context,
+ scoped_ptr<ExtensionPrefs> prefs) {
+ Disassociate(context);
+ Associate(context, std::move(prefs));
+}
+
+ExtensionPrefsFactory::ExtensionPrefsFactory()
+ : BrowserContextKeyedServiceFactory(
+ "ExtensionPrefs",
+ BrowserContextDependencyManager::GetInstance()) {
+}
+
+ExtensionPrefsFactory::~ExtensionPrefsFactory() {
+}
+
+KeyedService* ExtensionPrefsFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ ExtensionsBrowserClient* client = ExtensionsBrowserClient::Get();
+ std::vector<ExtensionPrefsObserver*> prefs_observers;
+ client->GetEarlyExtensionPrefsObservers(context, &prefs_observers);
+ return ExtensionPrefs::Create(
+ context, client->GetPrefServiceForContext(context),
+ context->GetPath().AppendASCII(extensions::kInstallDirectoryName),
+ ExtensionPrefValueMapFactory::GetForBrowserContext(context),
+ client->AreExtensionsDisabled(*base::CommandLine::ForCurrentProcess(),
+ context),
+ prefs_observers);
+}
+
+content::BrowserContext* ExtensionPrefsFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_prefs_factory.h b/chromium/extensions/browser/extension_prefs_factory.h
new file mode 100644
index 00000000000..3ddc2b96399
--- /dev/null
+++ b/chromium/extensions/browser/extension_prefs_factory.h
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_PREFS_FACTORY_H_
+#define EXTENSIONS_BROWSER_EXTENSION_PREFS_FACTORY_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace extensions {
+
+class ExtensionPrefs;
+
+class ExtensionPrefsFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static ExtensionPrefs* GetForBrowserContext(content::BrowserContext* context);
+
+ static ExtensionPrefsFactory* GetInstance();
+
+ void SetInstanceForTesting(content::BrowserContext* context,
+ scoped_ptr<ExtensionPrefs> prefs);
+
+ private:
+ friend struct base::DefaultSingletonTraits<ExtensionPrefsFactory>;
+
+ ExtensionPrefsFactory();
+ ~ExtensionPrefsFactory() override;
+
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_PREFS_FACTORY_H_
diff --git a/chromium/extensions/browser/extension_prefs_observer.h b/chromium/extensions/browser/extension_prefs_observer.h
new file mode 100644
index 00000000000..c127979a002
--- /dev/null
+++ b/chromium/extensions/browser/extension_prefs_observer.h
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_PREFS_OBSERVER_H_
+#define EXTENSIONS_BROWSER_EXTENSION_PREFS_OBSERVER_H_
+
+#include <string>
+
+#include "base/time/time.h"
+
+namespace extensions {
+
+class ExtensionPrefs;
+
+class ExtensionPrefsObserver {
+ public:
+ // Called when the reasons for an extension being disabled have changed.
+ // This is *not* called when the disable reasons change due to the extension
+ // being enabled/disabled.
+ virtual void OnExtensionDisableReasonsChanged(const std::string& extension_id,
+ int disabled_reasons) {}
+
+ // Called when an extension is registered with ExtensionPrefs.
+ virtual void OnExtensionRegistered(const std::string& extension_id,
+ const base::Time& install_time,
+ bool is_enabled) {}
+
+ // Called when an extension's prefs have been loaded.
+ virtual void OnExtensionPrefsLoaded(const std::string& extension_id,
+ const ExtensionPrefs* prefs) {}
+
+ // Called when an extension's prefs are deleted.
+ virtual void OnExtensionPrefsDeleted(const std::string& extension_id) {}
+
+ // Called when an extension's enabled state pref is changed.
+ // Note: This does not necessarily correspond to the extension being loaded/
+ // unloaded. For that, observe the ExtensionRegistry, and reconcile that the
+ // events might not match up.
+ virtual void OnExtensionStateChanged(const std::string& extension_id,
+ bool state) {}
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_PREFS_OBSERVER_H_
diff --git a/chromium/extensions/browser/extension_prefs_scope.h b/chromium/extensions/browser/extension_prefs_scope.h
new file mode 100644
index 00000000000..a249a3c2d8e
--- /dev/null
+++ b/chromium/extensions/browser/extension_prefs_scope.h
@@ -0,0 +1,27 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_PREFS_SCOPE_H_
+#define EXTENSIONS_BROWSER_EXTENSION_PREFS_SCOPE_H_
+
+
+namespace extensions {
+
+// Scope for a preference.
+enum ExtensionPrefsScope {
+ // Regular profile and incognito.
+ kExtensionPrefsScopeRegular,
+ // Regular profile only.
+ kExtensionPrefsScopeRegularOnly,
+ // Incognito profile; preference is persisted to disk and remains active
+ // after a browser restart.
+ kExtensionPrefsScopeIncognitoPersistent,
+ // Incognito profile; preference is kept in memory and deleted when the
+ // incognito session is terminated.
+ kExtensionPrefsScopeIncognitoSessionOnly
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_PREFS_SCOPE_H_
diff --git a/chromium/extensions/browser/extension_protocols.cc b/chromium/extensions/browser/extension_protocols.cc
new file mode 100644
index 00000000000..2dbbf31b9dd
--- /dev/null
+++ b/chromium/extensions/browser/extension_protocols.cc
@@ -0,0 +1,585 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_protocols.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/timer/elapsed_timer.h"
+#include "build/build_config.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/resource_request_info.h"
+#include "crypto/secure_hash.h"
+#include "crypto/sha2.h"
+#include "extensions/browser/content_verifier.h"
+#include "extensions/browser/content_verify_job.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/info_map.h"
+#include "extensions/browser/url_request_util.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_resource.h"
+#include "extensions/common/file_util.h"
+#include "extensions/common/manifest_handlers/background_info.h"
+#include "extensions/common/manifest_handlers/csp_info.h"
+#include "extensions/common/manifest_handlers/icons_handler.h"
+#include "extensions/common/manifest_handlers/incognito_info.h"
+#include "extensions/common/manifest_handlers/shared_module_info.h"
+#include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/url_request/url_request_file_job.h"
+#include "net/url_request/url_request_simple_job.h"
+#include "url/url_util.h"
+
+using content::BrowserThread;
+using content::ResourceRequestInfo;
+using content::ResourceType;
+using extensions::Extension;
+using extensions::SharedModuleInfo;
+
+namespace extensions {
+namespace {
+
+class GeneratedBackgroundPageJob : public net::URLRequestSimpleJob {
+ public:
+ GeneratedBackgroundPageJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const scoped_refptr<const Extension> extension,
+ const std::string& content_security_policy)
+ : net::URLRequestSimpleJob(request, network_delegate),
+ extension_(extension) {
+ const bool send_cors_headers = false;
+ // Leave cache headers out of generated background page jobs.
+ response_info_.headers = BuildHttpHeaders(content_security_policy,
+ send_cors_headers,
+ base::Time());
+ }
+
+ // Overridden from URLRequestSimpleJob:
+ int GetData(std::string* mime_type,
+ std::string* charset,
+ std::string* data,
+ const net::CompletionCallback& callback) const override {
+ *mime_type = "text/html";
+ *charset = "utf-8";
+
+ *data = "<!DOCTYPE html>\n<body>\n";
+ const std::vector<std::string>& background_scripts =
+ extensions::BackgroundInfo::GetBackgroundScripts(extension_.get());
+ for (size_t i = 0; i < background_scripts.size(); ++i) {
+ *data += "<script src=\"";
+ *data += background_scripts[i];
+ *data += "\"></script>\n";
+ }
+
+ return net::OK;
+ }
+
+ void GetResponseInfo(net::HttpResponseInfo* info) override {
+ *info = response_info_;
+ }
+
+ private:
+ ~GeneratedBackgroundPageJob() override {}
+
+ scoped_refptr<const Extension> extension_;
+ net::HttpResponseInfo response_info_;
+};
+
+base::Time GetFileLastModifiedTime(const base::FilePath& filename) {
+ if (base::PathExists(filename)) {
+ base::File::Info info;
+ if (base::GetFileInfo(filename, &info))
+ return info.last_modified;
+ }
+ return base::Time();
+}
+
+base::Time GetFileCreationTime(const base::FilePath& filename) {
+ if (base::PathExists(filename)) {
+ base::File::Info info;
+ if (base::GetFileInfo(filename, &info))
+ return info.creation_time;
+ }
+ return base::Time();
+}
+
+void ReadResourceFilePathAndLastModifiedTime(
+ const extensions::ExtensionResource& resource,
+ const base::FilePath& directory,
+ base::FilePath* file_path,
+ base::Time* last_modified_time) {
+ *file_path = resource.GetFilePath();
+ *last_modified_time = GetFileLastModifiedTime(*file_path);
+ // While we're here, log the delta between extension directory
+ // creation time and the resource's last modification time.
+ base::ElapsedTimer query_timer;
+ base::Time dir_creation_time = GetFileCreationTime(directory);
+ UMA_HISTOGRAM_TIMES("Extensions.ResourceDirectoryTimestampQueryLatency",
+ query_timer.Elapsed());
+ int64_t delta_seconds = (*last_modified_time - dir_creation_time).InSeconds();
+ if (delta_seconds >= 0) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ResourceLastModifiedDelta",
+ delta_seconds,
+ 0,
+ base::TimeDelta::FromDays(30).InSeconds(),
+ 50);
+ } else {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ResourceLastModifiedNegativeDelta",
+ -delta_seconds,
+ 1,
+ base::TimeDelta::FromDays(30).InSeconds(),
+ 50);
+ }
+}
+
+class URLRequestExtensionJob : public net::URLRequestFileJob {
+ public:
+ URLRequestExtensionJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const std::string& extension_id,
+ const base::FilePath& directory_path,
+ const base::FilePath& relative_path,
+ const std::string& content_security_policy,
+ bool send_cors_header,
+ bool follow_symlinks_anywhere,
+ ContentVerifyJob* verify_job)
+ : net::URLRequestFileJob(
+ request,
+ network_delegate,
+ base::FilePath(),
+ BrowserThread::GetBlockingPool()->GetTaskRunnerWithShutdownBehavior(
+ base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)),
+ verify_job_(verify_job),
+ seek_position_(0),
+ bytes_read_(0),
+ directory_path_(directory_path),
+ // TODO(tc): Move all of these files into resources.pak so we don't
+ // break when updating on Linux.
+ resource_(extension_id, directory_path, relative_path),
+ content_security_policy_(content_security_policy),
+ send_cors_header_(send_cors_header),
+ weak_factory_(this) {
+ if (follow_symlinks_anywhere) {
+ resource_.set_follow_symlinks_anywhere();
+ }
+ }
+
+ void GetResponseInfo(net::HttpResponseInfo* info) override {
+ *info = response_info_;
+ }
+
+ // This always returns 200 because a URLRequestExtensionJob will only get
+ // created in MaybeCreateJob() if the file exists.
+ int GetResponseCode() const override { return 200; }
+
+ void Start() override {
+ request_timer_.reset(new base::ElapsedTimer());
+ base::FilePath* read_file_path = new base::FilePath;
+ base::Time* last_modified_time = new base::Time();
+ bool posted = BrowserThread::PostBlockingPoolTaskAndReply(
+ FROM_HERE,
+ base::Bind(&ReadResourceFilePathAndLastModifiedTime,
+ resource_,
+ directory_path_,
+ base::Unretained(read_file_path),
+ base::Unretained(last_modified_time)),
+ base::Bind(&URLRequestExtensionJob::OnFilePathAndLastModifiedTimeRead,
+ weak_factory_.GetWeakPtr(),
+ base::Owned(read_file_path),
+ base::Owned(last_modified_time)));
+ DCHECK(posted);
+ }
+
+ bool IsRedirectResponse(GURL* location, int* http_status_code) override {
+ return false;
+ }
+
+ void SetExtraRequestHeaders(const net::HttpRequestHeaders& headers) override {
+ // TODO(asargent) - we'll need to add proper support for range headers.
+ // crbug.com/369895.
+ std::string range_header;
+ if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) {
+ if (verify_job_.get())
+ verify_job_ = NULL;
+ }
+ URLRequestFileJob::SetExtraRequestHeaders(headers);
+ }
+
+ void OnSeekComplete(int64_t result) override {
+ DCHECK_EQ(seek_position_, 0);
+ seek_position_ = result;
+ // TODO(asargent) - we'll need to add proper support for range headers.
+ // crbug.com/369895.
+ if (result > 0 && verify_job_.get())
+ verify_job_ = NULL;
+ }
+
+ void OnReadComplete(net::IOBuffer* buffer, int result) override {
+ if (result >= 0)
+ UMA_HISTOGRAM_COUNTS("ExtensionUrlRequest.OnReadCompleteResult", result);
+ else
+ UMA_HISTOGRAM_SPARSE_SLOWLY("ExtensionUrlRequest.OnReadCompleteError",
+ -result);
+ if (result > 0) {
+ bytes_read_ += result;
+ if (verify_job_.get()) {
+ verify_job_->BytesRead(result, buffer->data());
+ if (!remaining_bytes())
+ verify_job_->DoneReading();
+ }
+ }
+ }
+
+ private:
+ ~URLRequestExtensionJob() override {
+ UMA_HISTOGRAM_COUNTS("ExtensionUrlRequest.TotalKbRead", bytes_read_ / 1024);
+ UMA_HISTOGRAM_COUNTS("ExtensionUrlRequest.SeekPosition", seek_position_);
+ if (request_timer_.get())
+ UMA_HISTOGRAM_TIMES("ExtensionUrlRequest.Latency",
+ request_timer_->Elapsed());
+ }
+
+ void OnFilePathAndLastModifiedTimeRead(base::FilePath* read_file_path,
+ base::Time* last_modified_time) {
+ file_path_ = *read_file_path;
+ response_info_.headers = BuildHttpHeaders(
+ content_security_policy_,
+ send_cors_header_,
+ *last_modified_time);
+ URLRequestFileJob::Start();
+ }
+
+ scoped_refptr<ContentVerifyJob> verify_job_;
+
+ scoped_ptr<base::ElapsedTimer> request_timer_;
+
+ // The position we seeked to in the file.
+ int64_t seek_position_;
+
+ // The number of bytes of content we read from the file.
+ int bytes_read_;
+
+ net::HttpResponseInfo response_info_;
+ base::FilePath directory_path_;
+ extensions::ExtensionResource resource_;
+ std::string content_security_policy_;
+ bool send_cors_header_;
+ base::WeakPtrFactory<URLRequestExtensionJob> weak_factory_;
+};
+
+bool ExtensionCanLoadInIncognito(const ResourceRequestInfo* info,
+ const std::string& extension_id,
+ extensions::InfoMap* extension_info_map) {
+ if (!extension_info_map->IsIncognitoEnabled(extension_id))
+ return false;
+
+ // Only allow incognito toplevel navigations to extension resources in
+ // split mode. In spanning mode, the extension must run in a single process,
+ // and an incognito tab prevents that.
+ if (info->GetResourceType() == content::RESOURCE_TYPE_MAIN_FRAME) {
+ const Extension* extension =
+ extension_info_map->extensions().GetByID(extension_id);
+ return extension && extensions::IncognitoInfo::IsSplitMode(extension);
+ }
+
+ return true;
+}
+
+// Returns true if an chrome-extension:// resource should be allowed to load.
+// Pass true for |is_incognito| only for incognito profiles and not Chrome OS
+// guest mode profiles.
+// TODO(aa): This should be moved into ExtensionResourceRequestPolicy, but we
+// first need to find a way to get CanLoadInIncognito state into the renderers.
+bool AllowExtensionResourceLoad(net::URLRequest* request,
+ bool is_incognito,
+ const Extension* extension,
+ extensions::InfoMap* extension_info_map) {
+ const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request);
+
+ // We have seen crashes where info is NULL: crbug.com/52374.
+ if (!info) {
+ // SeviceWorker net requests created through ServiceWorkerWriteToCacheJob
+ // do not have ResourceRequestInfo associated with them. So skip logging
+ // spurious errors below.
+ // TODO(falken): Either consider attaching ResourceRequestInfo to these or
+ // finish refactoring ServiceWorkerWriteToCacheJob so that it doesn't spawn
+ // a new URLRequest.
+ if (!ResourceRequestInfo::OriginatedFromServiceWorker(request)) {
+ LOG(ERROR) << "Allowing load of " << request->url().spec()
+ << "from unknown origin. Could not find user data for "
+ << "request.";
+ }
+ return true;
+ }
+
+ if (is_incognito && !ExtensionCanLoadInIncognito(
+ info, request->url().host(), extension_info_map)) {
+ return false;
+ }
+
+ // The following checks are meant to replicate similar set of checks in the
+ // renderer process, performed by ResourceRequestPolicy::CanRequestResource.
+ // These are not exactly equivalent, because we don't have the same bits of
+ // information. The two checks need to be kept in sync as much as possible, as
+ // an exploited renderer can bypass the checks in ResourceRequestPolicy.
+
+ // Check if the extension for which this request is made is indeed loaded in
+ // the process sending the request. If not, we need to explicitly check if
+ // the resource is explicitly accessible or fits in a set of exception cases.
+ // Note: This allows a case where two extensions execute in the same renderer
+ // process to request each other's resources. We can't do a more precise
+ // check, since the renderer can lie about which extension has made the
+ // request.
+ if (extension_info_map->process_map().Contains(
+ request->url().host(), info->GetChildID())) {
+ return true;
+ }
+
+ // Allow the extension module embedder to grant permission for loads.
+ if (ExtensionsBrowserClient::Get()->AllowCrossRendererResourceLoad(
+ request, is_incognito, extension, extension_info_map)) {
+ return true;
+ }
+
+ // No special exceptions for cross-process loading. Block the load.
+ return false;
+}
+
+// Returns true if the given URL references an icon in the given extension.
+bool URLIsForExtensionIcon(const GURL& url, const Extension* extension) {
+ DCHECK(url.SchemeIs(extensions::kExtensionScheme));
+
+ if (!extension)
+ return false;
+
+ std::string path = url.path();
+ DCHECK_EQ(url.host(), extension->id());
+ DCHECK(path.length() > 0 && path[0] == '/');
+ path = path.substr(1);
+ return extensions::IconsInfo::GetIcons(extension).ContainsPath(path);
+}
+
+class ExtensionProtocolHandler
+ : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ ExtensionProtocolHandler(bool is_incognito,
+ extensions::InfoMap* extension_info_map)
+ : is_incognito_(is_incognito), extension_info_map_(extension_info_map) {}
+
+ ~ExtensionProtocolHandler() override {}
+
+ net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const override;
+
+ private:
+ const bool is_incognito_;
+ extensions::InfoMap* const extension_info_map_;
+ DISALLOW_COPY_AND_ASSIGN(ExtensionProtocolHandler);
+};
+
+// Creates URLRequestJobs for extension:// URLs.
+net::URLRequestJob*
+ExtensionProtocolHandler::MaybeCreateJob(
+ net::URLRequest* request, net::NetworkDelegate* network_delegate) const {
+ // chrome-extension://extension-id/resource/path.js
+ std::string extension_id = request->url().host();
+ const Extension* extension =
+ extension_info_map_->extensions().GetByID(extension_id);
+
+ // TODO(mpcomplete): better error code.
+ if (!AllowExtensionResourceLoad(
+ request, is_incognito_, extension, extension_info_map_)) {
+ return new net::URLRequestErrorJob(
+ request, network_delegate, net::ERR_ADDRESS_UNREACHABLE);
+ }
+
+ // If this is a disabled extension only allow the icon to load.
+ base::FilePath directory_path;
+ if (extension)
+ directory_path = extension->path();
+ if (directory_path.value().empty()) {
+ const Extension* disabled_extension =
+ extension_info_map_->disabled_extensions().GetByID(extension_id);
+ if (URLIsForExtensionIcon(request->url(), disabled_extension))
+ directory_path = disabled_extension->path();
+ if (directory_path.value().empty()) {
+ LOG(WARNING) << "Failed to GetPathForExtension: " << extension_id;
+ return NULL;
+ }
+ }
+
+ // Set up content security policy.
+ std::string content_security_policy;
+ bool send_cors_header = false;
+ bool follow_symlinks_anywhere = false;
+
+ if (extension) {
+ std::string resource_path = request->url().path();
+
+ // Use default CSP for <webview>.
+ if (!url_request_util::IsWebViewRequest(request)) {
+ content_security_policy =
+ extensions::CSPInfo::GetResourceContentSecurityPolicy(extension,
+ resource_path);
+ }
+
+ if ((extension->manifest_version() >= 2 ||
+ extensions::WebAccessibleResourcesInfo::HasWebAccessibleResources(
+ extension)) &&
+ extensions::WebAccessibleResourcesInfo::IsResourceWebAccessible(
+ extension, resource_path)) {
+ send_cors_header = true;
+ }
+
+ follow_symlinks_anywhere =
+ (extension->creation_flags() & Extension::FOLLOW_SYMLINKS_ANYWHERE)
+ != 0;
+ }
+
+ // Create a job for a generated background page.
+ std::string path = request->url().path();
+ if (path.size() > 1 &&
+ path.substr(1) == extensions::kGeneratedBackgroundPageFilename) {
+ return new GeneratedBackgroundPageJob(
+ request, network_delegate, extension, content_security_policy);
+ }
+
+ // Component extension resources may be part of the embedder's resource files,
+ // for example component_extension_resources.pak in Chrome.
+ net::URLRequestJob* resource_bundle_job =
+ extensions::ExtensionsBrowserClient::Get()
+ ->MaybeCreateResourceBundleRequestJob(request,
+ network_delegate,
+ directory_path,
+ content_security_policy,
+ send_cors_header);
+ if (resource_bundle_job)
+ return resource_bundle_job;
+
+ base::FilePath relative_path =
+ extensions::file_util::ExtensionURLToRelativeFilePath(request->url());
+
+ // Do not allow requests for resources in the _metadata folder, since any
+ // files there are internal implementation details that should not be
+ // considered part of the extension.
+ if (base::FilePath(kMetadataFolder).IsParent(relative_path))
+ return nullptr;
+
+ // Handle shared resources (extension A loading resources out of extension B).
+ if (SharedModuleInfo::IsImportedPath(path)) {
+ std::string new_extension_id;
+ std::string new_relative_path;
+ SharedModuleInfo::ParseImportedPath(path, &new_extension_id,
+ &new_relative_path);
+ const Extension* new_extension =
+ extension_info_map_->extensions().GetByID(new_extension_id);
+
+ if (SharedModuleInfo::ImportsExtensionById(extension, new_extension_id) &&
+ new_extension) {
+ directory_path = new_extension->path();
+ extension_id = new_extension_id;
+ relative_path = base::FilePath::FromUTF8Unsafe(new_relative_path);
+ } else {
+ return NULL;
+ }
+ }
+ ContentVerifyJob* verify_job = NULL;
+ ContentVerifier* verifier = extension_info_map_->content_verifier();
+ if (verifier) {
+ verify_job =
+ verifier->CreateJobFor(extension_id, directory_path, relative_path);
+ if (verify_job)
+ verify_job->Start();
+ }
+
+ return new URLRequestExtensionJob(request,
+ network_delegate,
+ extension_id,
+ directory_path,
+ relative_path,
+ content_security_policy,
+ send_cors_header,
+ follow_symlinks_anywhere,
+ verify_job);
+}
+
+} // namespace
+
+net::HttpResponseHeaders* BuildHttpHeaders(
+ const std::string& content_security_policy,
+ bool send_cors_header,
+ const base::Time& last_modified_time) {
+ std::string raw_headers;
+ raw_headers.append("HTTP/1.1 200 OK");
+ if (!content_security_policy.empty()) {
+ raw_headers.append(1, '\0');
+ raw_headers.append("Content-Security-Policy: ");
+ raw_headers.append(content_security_policy);
+ }
+
+ if (send_cors_header) {
+ raw_headers.append(1, '\0');
+ raw_headers.append("Access-Control-Allow-Origin: *");
+ }
+
+ if (!last_modified_time.is_null()) {
+ // Hash the time and make an etag to avoid exposing the exact
+ // user installation time of the extension.
+ std::string hash =
+ base::StringPrintf("%" PRId64, last_modified_time.ToInternalValue());
+ hash = base::SHA1HashString(hash);
+ std::string etag;
+ base::Base64Encode(hash, &etag);
+ raw_headers.append(1, '\0');
+ raw_headers.append("ETag: \"");
+ raw_headers.append(etag);
+ raw_headers.append("\"");
+ // Also force revalidation.
+ raw_headers.append(1, '\0');
+ raw_headers.append("cache-control: no-cache");
+ }
+
+ raw_headers.append(2, '\0');
+ return new net::HttpResponseHeaders(raw_headers);
+}
+
+scoped_ptr<net::URLRequestJobFactory::ProtocolHandler>
+CreateExtensionProtocolHandler(bool is_incognito,
+ extensions::InfoMap* extension_info_map) {
+ return make_scoped_ptr(
+ new ExtensionProtocolHandler(is_incognito, extension_info_map));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_protocols.h b/chromium/extensions/browser/extension_protocols.h
new file mode 100644
index 00000000000..190b50fca2b
--- /dev/null
+++ b/chromium/extensions/browser/extension_protocols.h
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_PROTOCOLS_H_
+#define EXTENSIONS_BROWSER_EXTENSION_PROTOCOLS_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "net/url_request/url_request_job_factory.h"
+
+namespace base {
+class Time;
+}
+
+namespace net {
+class HttpResponseHeaders;
+}
+
+namespace extensions {
+
+class InfoMap;
+
+// Builds HTTP headers for an extension request. Hashes the time to avoid
+// exposing the exact user installation time of the extension.
+net::HttpResponseHeaders* BuildHttpHeaders(
+ const std::string& content_security_policy,
+ bool send_cors_header,
+ const base::Time& last_modified_time);
+
+// Creates the handlers for the chrome-extension:// scheme. Pass true for
+// |is_incognito| only for incognito profiles and not for Chrome OS guest mode
+// profiles.
+scoped_ptr<net::URLRequestJobFactory::ProtocolHandler>
+CreateExtensionProtocolHandler(bool is_incognito, InfoMap* extension_info_map);
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_PROTOCOLS_H_
diff --git a/chromium/extensions/browser/extension_registry.cc b/chromium/extensions/browser/extension_registry.cc
new file mode 100644
index 00000000000..49c499a005b
--- /dev/null
+++ b/chromium/extensions/browser/extension_registry.cc
@@ -0,0 +1,217 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_registry.h"
+
+#include "base/strings/string_util.h"
+#include "extensions/browser/extension_registry_factory.h"
+#include "extensions/browser/extension_registry_observer.h"
+
+namespace extensions {
+
+ExtensionRegistry::ExtensionRegistry(content::BrowserContext* browser_context)
+ : browser_context_(browser_context) {}
+ExtensionRegistry::~ExtensionRegistry() {}
+
+// static
+ExtensionRegistry* ExtensionRegistry::Get(content::BrowserContext* context) {
+ return ExtensionRegistryFactory::GetForBrowserContext(context);
+}
+
+scoped_ptr<ExtensionSet> ExtensionRegistry::GenerateInstalledExtensionsSet()
+ const {
+ return GenerateInstalledExtensionsSet(EVERYTHING);
+}
+
+scoped_ptr<ExtensionSet> ExtensionRegistry::GenerateInstalledExtensionsSet(
+ int include_mask) const {
+ scoped_ptr<ExtensionSet> installed_extensions(new ExtensionSet);
+ if (include_mask & IncludeFlag::ENABLED)
+ installed_extensions->InsertAll(enabled_extensions_);
+ if (include_mask & IncludeFlag::DISABLED)
+ installed_extensions->InsertAll(disabled_extensions_);
+ if (include_mask & IncludeFlag::TERMINATED)
+ installed_extensions->InsertAll(terminated_extensions_);
+ if (include_mask & IncludeFlag::BLACKLISTED)
+ installed_extensions->InsertAll(blacklisted_extensions_);
+ if (include_mask & IncludeFlag::BLOCKED)
+ installed_extensions->InsertAll(blocked_extensions_);
+ return installed_extensions;
+}
+
+void ExtensionRegistry::AddObserver(ExtensionRegistryObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void ExtensionRegistry::RemoveObserver(ExtensionRegistryObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void ExtensionRegistry::TriggerOnLoaded(const Extension* extension) {
+ CHECK(extension);
+ DCHECK(enabled_extensions_.Contains(extension->id()));
+ FOR_EACH_OBSERVER(ExtensionRegistryObserver,
+ observers_,
+ OnExtensionLoaded(browser_context_, extension));
+}
+
+void ExtensionRegistry::TriggerOnReady(const Extension* extension) {
+ CHECK(extension);
+ DCHECK(enabled_extensions_.Contains(extension->id()));
+ FOR_EACH_OBSERVER(ExtensionRegistryObserver, observers_,
+ OnExtensionReady(browser_context_, extension));
+}
+
+void ExtensionRegistry::TriggerOnUnloaded(
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ CHECK(extension);
+ DCHECK(!enabled_extensions_.Contains(extension->id()));
+ FOR_EACH_OBSERVER(ExtensionRegistryObserver,
+ observers_,
+ OnExtensionUnloaded(browser_context_, extension, reason));
+}
+
+void ExtensionRegistry::TriggerOnWillBeInstalled(const Extension* extension,
+ bool is_update,
+ const std::string& old_name) {
+ CHECK(extension);
+ DCHECK_EQ(is_update,
+ GenerateInstalledExtensionsSet()->Contains(extension->id()));
+ DCHECK_EQ(is_update, !old_name.empty());
+ FOR_EACH_OBSERVER(ExtensionRegistryObserver, observers_,
+ OnExtensionWillBeInstalled(browser_context_, extension,
+ is_update, old_name));
+}
+
+void ExtensionRegistry::TriggerOnInstalled(const Extension* extension,
+ bool is_update) {
+ CHECK(extension);
+ DCHECK(GenerateInstalledExtensionsSet()->Contains(extension->id()));
+ FOR_EACH_OBSERVER(ExtensionRegistryObserver,
+ observers_,
+ OnExtensionInstalled(
+ browser_context_, extension, is_update));
+}
+
+void ExtensionRegistry::TriggerOnUninstalled(const Extension* extension,
+ UninstallReason reason) {
+ CHECK(extension);
+ DCHECK(!GenerateInstalledExtensionsSet()->Contains(extension->id()));
+ FOR_EACH_OBSERVER(
+ ExtensionRegistryObserver,
+ observers_,
+ OnExtensionUninstalled(browser_context_, extension, reason));
+}
+
+const Extension* ExtensionRegistry::GetExtensionById(const std::string& id,
+ int include_mask) const {
+ std::string lowercase_id = base::ToLowerASCII(id);
+ if (include_mask & ENABLED) {
+ const Extension* extension = enabled_extensions_.GetByID(lowercase_id);
+ if (extension)
+ return extension;
+ }
+ if (include_mask & DISABLED) {
+ const Extension* extension = disabled_extensions_.GetByID(lowercase_id);
+ if (extension)
+ return extension;
+ }
+ if (include_mask & TERMINATED) {
+ const Extension* extension = terminated_extensions_.GetByID(lowercase_id);
+ if (extension)
+ return extension;
+ }
+ if (include_mask & BLACKLISTED) {
+ const Extension* extension = blacklisted_extensions_.GetByID(lowercase_id);
+ if (extension)
+ return extension;
+ }
+ if (include_mask & BLOCKED) {
+ const Extension* extension = blocked_extensions_.GetByID(lowercase_id);
+ if (extension)
+ return extension;
+ }
+ return NULL;
+}
+
+const Extension* ExtensionRegistry::GetInstalledExtension(
+ const std::string& id) const {
+ return GetExtensionById(id, ExtensionRegistry::EVERYTHING);
+}
+
+bool ExtensionRegistry::AddEnabled(
+ const scoped_refptr<const Extension>& extension) {
+ return enabled_extensions_.Insert(extension);
+}
+
+bool ExtensionRegistry::RemoveEnabled(const std::string& id) {
+ // Only enabled extensions can be ready, so removing an enabled extension
+ // should also remove from the ready set if possible.
+ if (ready_extensions_.Contains(id))
+ RemoveReady(id);
+ return enabled_extensions_.Remove(id);
+}
+
+bool ExtensionRegistry::AddDisabled(
+ const scoped_refptr<const Extension>& extension) {
+ return disabled_extensions_.Insert(extension);
+}
+
+bool ExtensionRegistry::RemoveDisabled(const std::string& id) {
+ return disabled_extensions_.Remove(id);
+}
+
+bool ExtensionRegistry::AddTerminated(
+ const scoped_refptr<const Extension>& extension) {
+ return terminated_extensions_.Insert(extension);
+}
+
+bool ExtensionRegistry::RemoveTerminated(const std::string& id) {
+ return terminated_extensions_.Remove(id);
+}
+
+bool ExtensionRegistry::AddBlacklisted(
+ const scoped_refptr<const Extension>& extension) {
+ return blacklisted_extensions_.Insert(extension);
+}
+
+bool ExtensionRegistry::RemoveBlacklisted(const std::string& id) {
+ return blacklisted_extensions_.Remove(id);
+}
+
+bool ExtensionRegistry::AddBlocked(
+ const scoped_refptr<const Extension>& extension) {
+ return blocked_extensions_.Insert(extension);
+}
+
+bool ExtensionRegistry::RemoveBlocked(const std::string& id) {
+ return blocked_extensions_.Remove(id);
+}
+
+bool ExtensionRegistry::AddReady(
+ const scoped_refptr<const Extension>& extension) {
+ return ready_extensions_.Insert(extension);
+}
+
+bool ExtensionRegistry::RemoveReady(const std::string& id) {
+ return ready_extensions_.Remove(id);
+}
+
+void ExtensionRegistry::ClearAll() {
+ enabled_extensions_.Clear();
+ disabled_extensions_.Clear();
+ terminated_extensions_.Clear();
+ blacklisted_extensions_.Clear();
+ blocked_extensions_.Clear();
+ ready_extensions_.Clear();
+}
+
+void ExtensionRegistry::Shutdown() {
+ // Release references to all Extension objects in the sets.
+ ClearAll();
+ FOR_EACH_OBSERVER(ExtensionRegistryObserver, observers_, OnShutdown(this));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_registry.h b/chromium/extensions/browser/extension_registry.h
new file mode 100644
index 00000000000..405d07261e6
--- /dev/null
+++ b/chromium/extensions/browser/extension_registry.h
@@ -0,0 +1,204 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_REGISTRY_H_
+#define EXTENSIONS_BROWSER_EXTENSION_REGISTRY_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/browser/uninstall_reason.h"
+#include "extensions/common/extension_set.h"
+
+#if !defined(ENABLE_EXTENSIONS)
+#error "Extensions must be enabled"
+#endif
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+class Extension;
+class ExtensionRegistryObserver;
+
+// ExtensionRegistry holds sets of the installed extensions for a given
+// BrowserContext. An incognito browser context and its master browser context
+// share a single registry.
+class ExtensionRegistry : public KeyedService {
+ public:
+ // Flags to pass to GetExtensionById() to select which sets to look in.
+ enum IncludeFlag {
+ NONE = 0,
+ ENABLED = 1 << 0,
+ DISABLED = 1 << 1,
+ TERMINATED = 1 << 2,
+ BLACKLISTED = 1 << 3,
+ BLOCKED = 1 << 4,
+ EVERYTHING = (1 << 5) - 1,
+ };
+
+ explicit ExtensionRegistry(content::BrowserContext* browser_context);
+ ~ExtensionRegistry() override;
+
+ // Returns the instance for the given |browser_context|.
+ static ExtensionRegistry* Get(content::BrowserContext* browser_context);
+
+ content::BrowserContext* browser_context() const { return browser_context_; }
+
+ // NOTE: These sets are *eventually* mutually exclusive, but an extension can
+ // appear in two sets for short periods of time.
+ const ExtensionSet& enabled_extensions() const {
+ return enabled_extensions_;
+ }
+ const ExtensionSet& disabled_extensions() const {
+ return disabled_extensions_;
+ }
+ const ExtensionSet& terminated_extensions() const {
+ return terminated_extensions_;
+ }
+ const ExtensionSet& blacklisted_extensions() const {
+ return blacklisted_extensions_;
+ }
+ const ExtensionSet& blocked_extensions() const { return blocked_extensions_; }
+ const ExtensionSet& ready_extensions() const { return ready_extensions_; }
+
+ // Returns the set of all installed extensions, regardless of state (enabled,
+ // disabled, etc). Equivalent to GenerateInstalledExtensionSet(EVERYTHING).
+ scoped_ptr<ExtensionSet> GenerateInstalledExtensionsSet() const;
+
+ // Returns a set of all extensions in the subsets specified by |include_mask|.
+ // * enabled_extensions() --> ExtensionRegistry::ENABLED
+ // * disabled_extensions() --> ExtensionRegistry::DISABLED
+ // * terminated_extensions() --> ExtensionRegistry::TERMINATED
+ // * blacklisted_extensions() --> ExtensionRegistry::BLACKLISTED
+ // * blocked_extensions() --> ExtensionRegistry::BLOCKED
+ scoped_ptr<ExtensionSet> GenerateInstalledExtensionsSet(
+ int include_mask) const;
+
+ // The usual observer interface.
+ void AddObserver(ExtensionRegistryObserver* observer);
+ void RemoveObserver(ExtensionRegistryObserver* observer);
+
+ // Invokes the observer method OnExtensionLoaded(). The extension must be
+ // enabled at the time of the call.
+ void TriggerOnLoaded(const Extension* extension);
+
+ // Invokes the observer method OnExtensionReady(). This always follows
+ // an OnLoaded event, but is not called until it's safe to create the
+ // extension's child process.
+ void TriggerOnReady(const Extension* extension);
+
+ // Invokes the observer method OnExtensionUnloaded(). The extension must not
+ // be enabled at the time of the call.
+ void TriggerOnUnloaded(const Extension* extension,
+ UnloadedExtensionInfo::Reason reason);
+
+ // If this is a fresh install then |is_update| is false and there must not be
+ // any installed extension with |extension|'s ID. If this is an update then
+ // |is_update| is true and must be an installed extension with |extension|'s
+ // ID, and |old_name| must be non-empty.
+ void TriggerOnWillBeInstalled(const Extension* extension,
+ bool is_update,
+ const std::string& old_name);
+
+ // Invokes the observer method OnExtensionInstalled(). The extension must be
+ // contained in one of the registry's extension sets.
+ void TriggerOnInstalled(const Extension* extension,
+ bool is_update);
+
+ // Invokes the observer method OnExtensionUninstalled(). The extension must
+ // not be any installed extension with |extension|'s ID.
+ void TriggerOnUninstalled(const Extension* extension, UninstallReason reason);
+
+ // Find an extension by ID using |include_mask| to pick the sets to search:
+ // * enabled_extensions() --> ExtensionRegistry::ENABLED
+ // * disabled_extensions() --> ExtensionRegistry::DISABLED
+ // * terminated_extensions() --> ExtensionRegistry::TERMINATED
+ // * blacklisted_extensions() --> ExtensionRegistry::BLACKLISTED
+ // * blocked_extensions() --> ExtensionRegistry::BLOCKED
+ // Returns NULL if the extension is not found in the selected sets.
+ const Extension* GetExtensionById(const std::string& id,
+ int include_mask) const;
+
+ // Looks up an extension by ID, regardless of whether it's enabled,
+ // disabled, blacklisted, or terminated.
+ const Extension* GetInstalledExtension(const std::string& id) const;
+
+ // Adds the specified extension to the enabled set. The registry becomes an
+ // owner. Any previous extension with the same ID is removed.
+ // Returns true if there is no previous extension.
+ // NOTE: You probably want to use ExtensionService instead of calling this
+ // method directly.
+ bool AddEnabled(const scoped_refptr<const Extension>& extension);
+
+ // Removes the specified extension from the enabled set.
+ // Returns true if the set contained the specified extension.
+ // NOTE: You probably want to use ExtensionService instead of calling this
+ // method directly.
+ bool RemoveEnabled(const std::string& id);
+
+ // As above, but for the disabled set.
+ bool AddDisabled(const scoped_refptr<const Extension>& extension);
+ bool RemoveDisabled(const std::string& id);
+
+ // As above, but for the terminated set.
+ bool AddTerminated(const scoped_refptr<const Extension>& extension);
+ bool RemoveTerminated(const std::string& id);
+
+ // As above, but for the blacklisted set.
+ bool AddBlacklisted(const scoped_refptr<const Extension>& extension);
+ bool RemoveBlacklisted(const std::string& id);
+
+ // As above, but for the blocked set.
+ bool AddBlocked(const scoped_refptr<const Extension>& extension);
+ bool RemoveBlocked(const std::string& id);
+
+ // As above, but for the ready set.
+ bool AddReady(const scoped_refptr<const Extension>& extension);
+ bool RemoveReady(const std::string& id);
+
+ // Removes all extensions from all sets.
+ void ClearAll();
+
+ // KeyedService implementation:
+ void Shutdown() override;
+
+ private:
+ // Extensions that are installed, enabled and not terminated.
+ ExtensionSet enabled_extensions_;
+
+ // Extensions that are installed and disabled.
+ ExtensionSet disabled_extensions_;
+
+ // Extensions that are installed and terminated.
+ ExtensionSet terminated_extensions_;
+
+ // Extensions that are installed and blacklisted. Generally these shouldn't be
+ // considered as installed by the extension platform: we only keep them around
+ // so that if extensions are blacklisted by mistake they can easily be
+ // un-blacklisted.
+ ExtensionSet blacklisted_extensions_;
+
+ // Extensions that are installed and blocked. Will never be loaded.
+ ExtensionSet blocked_extensions_;
+
+ // Extensions that are ready for execution. This set is a non-exclusive
+ // subset of |enabled_extensions_|.
+ ExtensionSet ready_extensions_;
+
+ base::ObserverList<ExtensionRegistryObserver> observers_;
+
+ content::BrowserContext* const browser_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionRegistry);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_REGISTRY_H_
diff --git a/chromium/extensions/browser/extension_registry_factory.cc b/chromium/extensions/browser/extension_registry_factory.cc
new file mode 100644
index 00000000000..61c3c80341f
--- /dev/null
+++ b/chromium/extensions/browser/extension_registry_factory.cc
@@ -0,0 +1,47 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_registry_factory.h"
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extensions_browser_client.h"
+
+using content::BrowserContext;
+
+namespace extensions {
+
+// static
+ExtensionRegistry* ExtensionRegistryFactory::GetForBrowserContext(
+ BrowserContext* context) {
+ return static_cast<ExtensionRegistry*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+ExtensionRegistryFactory* ExtensionRegistryFactory::GetInstance() {
+ return base::Singleton<ExtensionRegistryFactory>::get();
+}
+
+ExtensionRegistryFactory::ExtensionRegistryFactory()
+ : BrowserContextKeyedServiceFactory(
+ "ExtensionRegistry",
+ BrowserContextDependencyManager::GetInstance()) {
+ // No dependencies on other services.
+}
+
+ExtensionRegistryFactory::~ExtensionRegistryFactory() {}
+
+KeyedService* ExtensionRegistryFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ return new ExtensionRegistry(context);
+}
+
+BrowserContext* ExtensionRegistryFactory::GetBrowserContextToUse(
+ BrowserContext* context) const {
+ // Redirected in incognito.
+ return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_registry_factory.h b/chromium/extensions/browser/extension_registry_factory.h
new file mode 100644
index 00000000000..e2fcfe5d862
--- /dev/null
+++ b/chromium/extensions/browser/extension_registry_factory.h
@@ -0,0 +1,43 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_REGISTRY_FACTORY_H_
+#define EXTENSIONS_BROWSER_EXTENSION_REGISTRY_FACTORY_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace extensions {
+
+class ExtensionRegistry;
+
+// Factory for ExtensionRegistry objects. ExtensionRegistry objects are shared
+// between an incognito browser context and its master browser context.
+class ExtensionRegistryFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static ExtensionRegistry* GetForBrowserContext(
+ content::BrowserContext* context);
+
+ static ExtensionRegistryFactory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<ExtensionRegistryFactory>;
+
+ ExtensionRegistryFactory();
+ ~ExtensionRegistryFactory() override;
+
+ // BrowserContextKeyedServiceFactory implementation:
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionRegistryFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_REGISTRY_FACTORY_H_
diff --git a/chromium/extensions/browser/extension_registry_observer.h b/chromium/extensions/browser/extension_registry_observer.h
new file mode 100644
index 00000000000..2b984651e18
--- /dev/null
+++ b/chromium/extensions/browser/extension_registry_observer.h
@@ -0,0 +1,83 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_REGISTRY_OBSERVER_H_
+#define EXTENSIONS_BROWSER_EXTENSION_REGISTRY_OBSERVER_H_
+
+#include "extensions/browser/uninstall_reason.h"
+#include "extensions/common/extension.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class Extension;
+class ExtensionRegistry;
+struct UnloadedExtensionInfo;
+
+// Observer for ExtensionRegistry. Exists in a separate header file to reduce
+// the include file burden for typical clients of ExtensionRegistry.
+class ExtensionRegistryObserver {
+ public:
+ virtual ~ExtensionRegistryObserver() {}
+
+ // Called after an extension is loaded. The extension will exclusively exist
+ // in the enabled_extensions set of ExtensionRegistry.
+ virtual void OnExtensionLoaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension) {}
+
+ // Called after an extension is loaded and all necessary browser state is
+ // initialized to support the start of the extension's child process.
+ virtual void OnExtensionReady(content::BrowserContext* browser_context,
+ const Extension* extension) {}
+
+ // Called after an extension is unloaded. The extension no longer exists in
+ // the set |ExtensionRegistry::enabled_extensions()|, but it can still be a
+ // member of one of the other sets, like disabled, blacklisted or terminated.
+ virtual void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {}
+
+ // Called when |extension| is about to be installed. |is_update| is true if
+ // the installation is the result of it updating, in which case |old_name| is
+ // the name of the extension's previous version.
+ // The ExtensionRegistry will not be tracking |extension| at the time this
+ // event is fired, but will be immediately afterwards (note: not necessarily
+ // enabled; it might be installed in the disabled or even blacklisted sets,
+ // for example).
+ // Note that it's much more common to care about extensions being loaded
+ // (OnExtensionLoaded).
+ //
+ // TODO(tmdiep): We should stash the state of the previous extension version
+ // somewhere and have observers retrieve it. |is_update|, and |old_name| can
+ // be removed when this is done.
+ virtual void OnExtensionWillBeInstalled(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ bool is_update,
+ const std::string& old_name) {}
+
+ // Called when the installation of |extension| is complete. At this point the
+ // extension is tracked in one of the ExtensionRegistry sets, but is not
+ // necessarily enabled.
+ virtual void OnExtensionInstalled(content::BrowserContext* browser_context,
+ const Extension* extension,
+ bool is_update) {}
+
+ // Called after an extension is uninstalled. The extension no longer exists in
+ // any of the ExtensionRegistry sets (enabled, disabled, etc.).
+ virtual void OnExtensionUninstalled(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UninstallReason reason) {}
+
+ // Notifies observers that the observed object is going away.
+ virtual void OnShutdown(ExtensionRegistry* registry) {}
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_REGISTRY_OBSERVER_H_
diff --git a/chromium/extensions/browser/extension_registry_unittest.cc b/chromium/extensions/browser/extension_registry_unittest.cc
new file mode 100644
index 00000000000..995df167197
--- /dev/null
+++ b/chromium/extensions/browser/extension_registry_unittest.cc
@@ -0,0 +1,277 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_registry.h"
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/browser/uninstall_reason.h"
+#include "extensions/common/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+namespace {
+
+typedef testing::Test ExtensionRegistryTest;
+
+testing::AssertionResult HasSingleExtension(
+ const ExtensionList& list,
+ const scoped_refptr<const Extension>& extension) {
+ if (list.empty())
+ return testing::AssertionFailure() << "No extensions in list";
+ if (list.size() > 1) {
+ return testing::AssertionFailure() << list.size()
+ << " extensions, expected 1";
+ }
+ const Extension* did_load = list[0].get();
+ if (did_load != extension.get()) {
+ return testing::AssertionFailure() << "Expected " << extension->id()
+ << " found " << did_load->id();
+ }
+ return testing::AssertionSuccess();
+}
+
+class TestObserver : public ExtensionRegistryObserver {
+ public:
+ TestObserver() {}
+
+ void Reset() {
+ loaded_.clear();
+ unloaded_.clear();
+ installed_.clear();
+ uninstalled_.clear();
+ }
+
+ const ExtensionList& loaded() { return loaded_; }
+ const ExtensionList& unloaded() { return unloaded_; }
+ const ExtensionList& installed() { return installed_; }
+ const ExtensionList& uninstalled() { return uninstalled_; }
+
+ private:
+ void OnExtensionLoaded(content::BrowserContext* browser_context,
+ const Extension* extension) override {
+ loaded_.push_back(extension);
+ }
+
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override {
+ unloaded_.push_back(extension);
+ }
+
+ void OnExtensionWillBeInstalled(content::BrowserContext* browser_context,
+ const Extension* extension,
+ bool is_update,
+ const std::string& old_name) override {
+ installed_.push_back(extension);
+ }
+
+ void OnExtensionUninstalled(content::BrowserContext* browser_context,
+ const Extension* extension,
+ extensions::UninstallReason reason) override {
+ uninstalled_.push_back(extension);
+ }
+
+ void OnShutdown(extensions::ExtensionRegistry* registry) override { Reset(); }
+
+ ExtensionList loaded_;
+ ExtensionList unloaded_;
+ ExtensionList installed_;
+ ExtensionList uninstalled_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestObserver);
+};
+
+TEST_F(ExtensionRegistryTest, FillAndClearRegistry) {
+ ExtensionRegistry registry(NULL);
+ scoped_refptr<Extension> extension1 = test_util::CreateEmptyExtension("id1");
+ scoped_refptr<Extension> extension2 = test_util::CreateEmptyExtension("id2");
+ scoped_refptr<Extension> extension3 = test_util::CreateEmptyExtension("id3");
+ scoped_refptr<Extension> extension4 = test_util::CreateEmptyExtension("id4");
+
+ // All the sets start empty.
+ EXPECT_EQ(0u, registry.enabled_extensions().size());
+ EXPECT_EQ(0u, registry.disabled_extensions().size());
+ EXPECT_EQ(0u, registry.terminated_extensions().size());
+ EXPECT_EQ(0u, registry.blacklisted_extensions().size());
+
+ // Extensions can be added to each set.
+ registry.AddEnabled(extension1);
+ registry.AddDisabled(extension2);
+ registry.AddTerminated(extension3);
+ registry.AddBlacklisted(extension4);
+
+ EXPECT_EQ(1u, registry.enabled_extensions().size());
+ EXPECT_EQ(1u, registry.disabled_extensions().size());
+ EXPECT_EQ(1u, registry.terminated_extensions().size());
+ EXPECT_EQ(1u, registry.blacklisted_extensions().size());
+
+ // Clearing the registry clears all sets.
+ registry.ClearAll();
+
+ EXPECT_EQ(0u, registry.enabled_extensions().size());
+ EXPECT_EQ(0u, registry.disabled_extensions().size());
+ EXPECT_EQ(0u, registry.terminated_extensions().size());
+ EXPECT_EQ(0u, registry.blacklisted_extensions().size());
+}
+
+// A simple test of adding and removing things from sets.
+TEST_F(ExtensionRegistryTest, AddAndRemoveExtensionFromRegistry) {
+ ExtensionRegistry registry(NULL);
+
+ // Adding an extension works.
+ scoped_refptr<Extension> extension = test_util::CreateEmptyExtension("id");
+ EXPECT_TRUE(registry.AddEnabled(extension));
+ EXPECT_EQ(1u, registry.enabled_extensions().size());
+
+ // The extension was only added to one set.
+ EXPECT_EQ(0u, registry.disabled_extensions().size());
+ EXPECT_EQ(0u, registry.terminated_extensions().size());
+ EXPECT_EQ(0u, registry.blacklisted_extensions().size());
+
+ // Removing an extension works.
+ EXPECT_TRUE(registry.RemoveEnabled(extension->id()));
+ EXPECT_EQ(0u, registry.enabled_extensions().size());
+
+ // Trying to remove an extension that isn't in the set fails cleanly.
+ EXPECT_FALSE(registry.RemoveEnabled(extension->id()));
+}
+
+TEST_F(ExtensionRegistryTest, AddExtensionToRegistryTwice) {
+ ExtensionRegistry registry(NULL);
+ scoped_refptr<Extension> extension = test_util::CreateEmptyExtension("id");
+
+ // An extension can exist in two sets at once. It would be nice to eliminate
+ // this functionality, but some users of ExtensionRegistry need it.
+ EXPECT_TRUE(registry.AddEnabled(extension));
+ EXPECT_TRUE(registry.AddDisabled(extension));
+
+ EXPECT_EQ(1u, registry.enabled_extensions().size());
+ EXPECT_EQ(1u, registry.disabled_extensions().size());
+ EXPECT_EQ(0u, registry.terminated_extensions().size());
+ EXPECT_EQ(0u, registry.blacklisted_extensions().size());
+}
+
+TEST_F(ExtensionRegistryTest, GetExtensionById) {
+ ExtensionRegistry registry(NULL);
+
+ // Trying to get an extension fails cleanly when the sets are empty.
+ EXPECT_FALSE(
+ registry.GetExtensionById("id", ExtensionRegistry::EVERYTHING));
+
+ scoped_refptr<Extension> enabled = test_util::CreateEmptyExtension("enabled");
+ scoped_refptr<Extension> disabled =
+ test_util::CreateEmptyExtension("disabled");
+ scoped_refptr<Extension> terminated =
+ test_util::CreateEmptyExtension("terminated");
+ scoped_refptr<Extension> blacklisted =
+ test_util::CreateEmptyExtension("blacklisted");
+
+ // Add an extension to each set.
+ registry.AddEnabled(enabled);
+ registry.AddDisabled(disabled);
+ registry.AddTerminated(terminated);
+ registry.AddBlacklisted(blacklisted);
+
+ // Enabled is part of everything and the enabled list.
+ EXPECT_TRUE(
+ registry.GetExtensionById("enabled", ExtensionRegistry::EVERYTHING));
+ EXPECT_TRUE(
+ registry.GetExtensionById("enabled", ExtensionRegistry::ENABLED));
+ EXPECT_FALSE(
+ registry.GetExtensionById("enabled", ExtensionRegistry::DISABLED));
+ EXPECT_FALSE(
+ registry.GetExtensionById("enabled", ExtensionRegistry::TERMINATED));
+ EXPECT_FALSE(
+ registry.GetExtensionById("enabled", ExtensionRegistry::BLACKLISTED));
+
+ // Disabled is part of everything and the disabled list.
+ EXPECT_TRUE(
+ registry.GetExtensionById("disabled", ExtensionRegistry::EVERYTHING));
+ EXPECT_FALSE(
+ registry.GetExtensionById("disabled", ExtensionRegistry::ENABLED));
+ EXPECT_TRUE(
+ registry.GetExtensionById("disabled", ExtensionRegistry::DISABLED));
+ EXPECT_FALSE(
+ registry.GetExtensionById("disabled", ExtensionRegistry::TERMINATED));
+ EXPECT_FALSE(
+ registry.GetExtensionById("disabled", ExtensionRegistry::BLACKLISTED));
+
+ // Terminated is part of everything and the terminated list.
+ EXPECT_TRUE(
+ registry.GetExtensionById("terminated", ExtensionRegistry::EVERYTHING));
+ EXPECT_FALSE(
+ registry.GetExtensionById("terminated", ExtensionRegistry::ENABLED));
+ EXPECT_FALSE(
+ registry.GetExtensionById("terminated", ExtensionRegistry::DISABLED));
+ EXPECT_TRUE(
+ registry.GetExtensionById("terminated", ExtensionRegistry::TERMINATED));
+ EXPECT_FALSE(
+ registry.GetExtensionById("terminated", ExtensionRegistry::BLACKLISTED));
+
+ // Blacklisted is part of everything and the blacklisted list.
+ EXPECT_TRUE(
+ registry.GetExtensionById("blacklisted", ExtensionRegistry::EVERYTHING));
+ EXPECT_FALSE(
+ registry.GetExtensionById("blacklisted", ExtensionRegistry::ENABLED));
+ EXPECT_FALSE(
+ registry.GetExtensionById("blacklisted", ExtensionRegistry::DISABLED));
+ EXPECT_FALSE(
+ registry.GetExtensionById("blacklisted", ExtensionRegistry::TERMINATED));
+ EXPECT_TRUE(
+ registry.GetExtensionById("blacklisted", ExtensionRegistry::BLACKLISTED));
+
+ // Enabled can be found with multiple flags set.
+ EXPECT_TRUE(registry.GetExtensionById(
+ "enabled", ExtensionRegistry::ENABLED | ExtensionRegistry::TERMINATED));
+
+ // Enabled isn't found if the wrong flags are set.
+ EXPECT_FALSE(registry.GetExtensionById(
+ "enabled", ExtensionRegistry::DISABLED | ExtensionRegistry::BLACKLISTED));
+}
+
+TEST_F(ExtensionRegistryTest, Observer) {
+ ExtensionRegistry registry(NULL);
+ TestObserver observer;
+ registry.AddObserver(&observer);
+
+ EXPECT_TRUE(observer.loaded().empty());
+ EXPECT_TRUE(observer.unloaded().empty());
+ EXPECT_TRUE(observer.installed().empty());
+
+ scoped_refptr<const Extension> extension =
+ test_util::CreateEmptyExtension("id");
+
+ registry.TriggerOnWillBeInstalled(extension.get(), false, std::string());
+ EXPECT_TRUE(HasSingleExtension(observer.installed(), extension.get()));
+
+ registry.AddEnabled(extension);
+ registry.TriggerOnLoaded(extension.get());
+
+ registry.TriggerOnWillBeInstalled(extension.get(), true, "foo");
+
+ EXPECT_TRUE(HasSingleExtension(observer.loaded(), extension.get()));
+ EXPECT_TRUE(observer.unloaded().empty());
+ registry.Shutdown();
+
+ registry.RemoveEnabled(extension->id());
+ registry.TriggerOnUnloaded(extension.get(),
+ UnloadedExtensionInfo::REASON_DISABLE);
+
+ EXPECT_TRUE(observer.loaded().empty());
+ EXPECT_TRUE(HasSingleExtension(observer.unloaded(), extension.get()));
+ registry.Shutdown();
+
+ registry.TriggerOnUninstalled(extension.get(), UNINSTALL_REASON_FOR_TESTING);
+ EXPECT_TRUE(observer.installed().empty());
+ EXPECT_TRUE(HasSingleExtension(observer.uninstalled(), extension.get()));
+
+ registry.RemoveObserver(&observer);
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_request_limiting_throttle.cc b/chromium/extensions/browser/extension_request_limiting_throttle.cc
new file mode 100644
index 00000000000..05d041014e3
--- /dev/null
+++ b/chromium/extensions/browser/extension_request_limiting_throttle.cc
@@ -0,0 +1,58 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_request_limiting_throttle.h"
+
+#include "base/logging.h"
+#include "content/public/browser/resource_controller.h"
+#include "extensions/browser/extension_throttle_entry.h"
+#include "extensions/browser/extension_throttle_manager.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/redirect_info.h"
+#include "net/url_request/url_request.h"
+
+namespace extensions {
+
+ExtensionRequestLimitingThrottle::ExtensionRequestLimitingThrottle(
+ const net::URLRequest* request,
+ ExtensionThrottleManager* manager)
+ : request_(request), manager_(manager) {
+ DCHECK(manager_);
+}
+
+ExtensionRequestLimitingThrottle::~ExtensionRequestLimitingThrottle() {
+}
+
+void ExtensionRequestLimitingThrottle::WillStartRequest(bool* defer) {
+ throttling_entry_ = manager_->RegisterRequestUrl(request_->url());
+ if (throttling_entry_->ShouldRejectRequest(*request_))
+ controller()->CancelWithError(net::ERR_TEMPORARILY_THROTTLED);
+}
+
+void ExtensionRequestLimitingThrottle::WillRedirectRequest(
+ const net::RedirectInfo& redirect_info,
+ bool* defer) {
+ DCHECK_EQ(manager_->GetIdFromUrl(request_->url()),
+ throttling_entry_->GetURLIdForDebugging());
+
+ throttling_entry_->UpdateWithResponse(redirect_info.status_code);
+
+ throttling_entry_ = manager_->RegisterRequestUrl(redirect_info.new_url);
+ if (throttling_entry_->ShouldRejectRequest(*request_))
+ controller()->CancelWithError(net::ERR_TEMPORARILY_THROTTLED);
+}
+
+void ExtensionRequestLimitingThrottle::WillProcessResponse(bool* defer) {
+ DCHECK_EQ(manager_->GetIdFromUrl(request_->url()),
+ throttling_entry_->GetURLIdForDebugging());
+
+ if (!request_->was_cached())
+ throttling_entry_->UpdateWithResponse(request_->GetResponseCode());
+}
+
+const char* ExtensionRequestLimitingThrottle::GetNameForLogging() const {
+ return "ExtensionRequestLimitingThrottle";
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_request_limiting_throttle.h b/chromium/extensions/browser/extension_request_limiting_throttle.h
new file mode 100644
index 00000000000..bd9dd4c46b2
--- /dev/null
+++ b/chromium/extensions/browser/extension_request_limiting_throttle.h
@@ -0,0 +1,52 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_REQUEST_LIMITING_THROTTLE_H_
+#define EXTENSIONS_BROWSER_EXTENSION_REQUEST_LIMITING_THROTTLE_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "content/public/browser/resource_throttle.h"
+
+namespace net {
+struct RedirectInfo;
+class URLRequest;
+}
+
+namespace extensions {
+
+class ExtensionThrottleEntryInterface;
+class ExtensionThrottleManager;
+
+// This class monitors requests issued by extensions and throttles the request
+// if there are too many requests made within a short time to urls with the same
+// scheme, host, port and path. For the exact criteria for throttling, please
+// also see extension_throttle_manager.cc.
+class ExtensionRequestLimitingThrottle : public content::ResourceThrottle {
+ public:
+ ExtensionRequestLimitingThrottle(const net::URLRequest* request,
+ ExtensionThrottleManager* manager);
+ ~ExtensionRequestLimitingThrottle() override;
+
+ // content::ResourceThrottle implementation (called on IO thread):
+ void WillStartRequest(bool* defer) override;
+ void WillRedirectRequest(const net::RedirectInfo& redirect_info,
+ bool* defer) override;
+ void WillProcessResponse(bool* defer) override;
+
+ const char* GetNameForLogging() const override;
+
+ private:
+ const net::URLRequest* request_;
+ ExtensionThrottleManager* manager_;
+
+ // This is used to supervise traffic and enforce exponential back-off.
+ scoped_refptr<ExtensionThrottleEntryInterface> throttling_entry_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionRequestLimitingThrottle);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_REQUEST_LIMITING_THROTTLE_H_
diff --git a/chromium/extensions/browser/extension_scoped_prefs.h b/chromium/extensions/browser/extension_scoped_prefs.h
new file mode 100644
index 00000000000..15fc577f2d0
--- /dev/null
+++ b/chromium/extensions/browser/extension_scoped_prefs.h
@@ -0,0 +1,56 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_SCOPED_PREFS_H_
+#define EXTENSIONS_BROWSER_EXTENSION_SCOPED_PREFS_H_
+
+namespace extensions {
+
+class ExtensionScopedPrefs {
+ public:
+ ExtensionScopedPrefs() {}
+ ~ExtensionScopedPrefs() {}
+
+ // Sets the pref |key| for extension |id| to |value|.
+ virtual void UpdateExtensionPref(const std::string& id,
+ const std::string& key,
+ base::Value* value) = 0;
+
+ // Deletes the pref dictionary for extension |id|.
+ virtual void DeleteExtensionPrefs(const std::string& id) = 0;
+
+ // Reads a boolean pref |pref_key| from extension with id |extension_id|.
+ virtual bool ReadPrefAsBoolean(const std::string& extension_id,
+ const std::string& pref_key,
+ bool* out_value) const = 0;
+
+ // Reads an integer pref |pref_key| from extension with id |extension_id|.
+ virtual bool ReadPrefAsInteger(const std::string& extension_id,
+ const std::string& pref_key,
+ int* out_value) const = 0;
+
+ // Reads a string pref |pref_key| from extension with id |extension_id|.
+ virtual bool ReadPrefAsString(const std::string& extension_id,
+ const std::string& pref_key,
+ std::string* out_value) const = 0;
+
+ // Reads a list pref |pref_key| from extension with id |extension_id|.
+ virtual bool ReadPrefAsList(const std::string& extension_id,
+ const std::string& pref_key,
+ const base::ListValue** out_value) const = 0;
+
+ // Reads a dictionary pref |pref_key| from extension with id |extension_id|.
+ virtual bool ReadPrefAsDictionary(
+ const std::string& extension_id,
+ const std::string& pref_key,
+ const base::DictionaryValue** out_value) const = 0;
+
+ // Returns true if the prefs contain an entry for an extension with id
+ // |extension_id|.
+ virtual bool HasPrefForExtension(const std::string& extension_id) const = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_SCOPED_PREFS_H_
diff --git a/chromium/extensions/browser/extension_system.cc b/chromium/extensions/browser/extension_system.cc
new file mode 100644
index 00000000000..fc2abc0a8f0
--- /dev/null
+++ b/chromium/extensions/browser/extension_system.cc
@@ -0,0 +1,26 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_system.h"
+
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "extensions/browser/extension_system_provider.h"
+#include "extensions/browser/extensions_browser_client.h"
+
+namespace extensions {
+
+ExtensionSystem::ExtensionSystem() {
+}
+
+ExtensionSystem::~ExtensionSystem() {
+}
+
+// static
+ExtensionSystem* ExtensionSystem::Get(content::BrowserContext* context) {
+ return ExtensionsBrowserClient::Get()
+ ->GetExtensionSystemFactory()
+ ->GetForBrowserContext(context);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_system.h b/chromium/extensions/browser/extension_system.h
new file mode 100644
index 00000000000..5382d3f0c39
--- /dev/null
+++ b/chromium/extensions/browser/extension_system.h
@@ -0,0 +1,141 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_SYSTEM_H_
+#define EXTENSIONS_BROWSER_EXTENSION_SYSTEM_H_
+
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "build/build_config.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/common/extension.h"
+
+#if !defined(ENABLE_EXTENSIONS)
+#error "Extensions must be enabled"
+#endif
+
+class ExtensionService;
+
+#if defined(OS_CHROMEOS)
+namespace chromeos {
+class DeviceLocalAccountManagementPolicyProvider;
+}
+#endif // defined(OS_CHROMEOS)
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class AppSorting;
+class ContentVerifier;
+class Extension;
+class ExtensionSet;
+class InfoMap;
+class ManagementPolicy;
+class OneShotEvent;
+class QuotaService;
+class RuntimeData;
+class ServiceWorkerManager;
+class SharedUserScriptMaster;
+class StateStore;
+class ValueStoreFactory;
+
+// ExtensionSystem manages the lifetime of many of the services used by the
+// extensions and apps system, and it handles startup and shutdown as needed.
+// Eventually, we'd like to make more of these services into KeyedServices in
+// their own right.
+class ExtensionSystem : public KeyedService {
+ public:
+ ExtensionSystem();
+ ~ExtensionSystem() override;
+
+ // Returns the instance for the given browser context, or NULL if none.
+ static ExtensionSystem* Get(content::BrowserContext* context);
+
+ // Initializes extensions machinery.
+ // Component extensions are always enabled, external and user extensions are
+ // controlled by |extensions_enabled|.
+ virtual void InitForRegularProfile(bool extensions_enabled) = 0;
+
+ // The ExtensionService is created at startup.
+ virtual ExtensionService* extension_service() = 0;
+
+ // Per-extension data that can change during the life of the process but
+ // does not persist across restarts. Lives on UI thread. Created at startup.
+ virtual RuntimeData* runtime_data() = 0;
+
+ // The class controlling whether users are permitted to perform certain
+ // actions on extensions (install, uninstall, disable, etc.).
+ // The ManagementPolicy is created at startup.
+ virtual ManagementPolicy* management_policy() = 0;
+
+ // The ServiceWorkerManager is created at startup.
+ virtual ServiceWorkerManager* service_worker_manager() = 0;
+
+ // The SharedUserScriptMaster is created at startup.
+ virtual SharedUserScriptMaster* shared_user_script_master() = 0;
+
+ // The StateStore is created at startup.
+ virtual StateStore* state_store() = 0;
+
+ // The rules store is created at startup.
+ virtual StateStore* rules_store() = 0;
+
+ // Returns the |ValueStore| factory created at startup.
+ virtual scoped_refptr<ValueStoreFactory> store_factory() = 0;
+
+ // Returns the IO-thread-accessible extension data.
+ virtual InfoMap* info_map() = 0;
+
+ // Returns the QuotaService that limits calls to certain extension functions.
+ // Lives on the UI thread. Created at startup.
+ virtual QuotaService* quota_service() = 0;
+
+ // Returns the AppSorting which provides an ordering for all installed apps.
+ virtual AppSorting* app_sorting() = 0;
+
+ // Called by the ExtensionService that lives in this system. Gives the
+ // info map a chance to react to the load event before the EXTENSION_LOADED
+ // notification has fired. The purpose for handling this event first is to
+ // avoid race conditions by making sure URLRequestContexts learn about new
+ // extensions before anything else needs them to know. This operation happens
+ // asynchronously. |callback| is run on the calling thread once completed.
+ virtual void RegisterExtensionWithRequestContexts(
+ const Extension* extension,
+ const base::Closure& callback) {}
+
+ // Called by the ExtensionService that lives in this system. Lets the
+ // info map clean up its RequestContexts once all the listeners to the
+ // EXTENSION_UNLOADED notification have finished running.
+ virtual void UnregisterExtensionWithRequestContexts(
+ const std::string& extension_id,
+ const UnloadedExtensionInfo::Reason reason) {}
+
+ // Signaled when the extension system has completed its startup tasks.
+ virtual const OneShotEvent& ready() const = 0;
+
+ // Returns the content verifier, if any.
+ virtual ContentVerifier* content_verifier() = 0;
+
+ // Get a set of extensions that depend on the given extension.
+ // TODO(elijahtaylor): Move SharedModuleService out of chrome/browser
+ // so it can be retrieved from ExtensionSystem directly.
+ virtual scoped_ptr<ExtensionSet> GetDependentExtensions(
+ const Extension* extension) = 0;
+
+ // Install an updated version of |extension_id| with the version given in
+ // temp_dir. Ownership of |temp_dir| in the filesystem is transferred and
+ // implementors of this function are responsible for cleaning it up on
+ // errors, etc.
+ virtual void InstallUpdate(const std::string& extension_id,
+ const base::FilePath& temp_dir) = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_SYSTEM_H_
diff --git a/chromium/extensions/browser/extension_system_provider.cc b/chromium/extensions/browser/extension_system_provider.cc
new file mode 100644
index 00000000000..606ec909a90
--- /dev/null
+++ b/chromium/extensions/browser/extension_system_provider.cc
@@ -0,0 +1,18 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_system_provider.h"
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+
+namespace extensions {
+
+ExtensionSystemProvider::ExtensionSystemProvider(
+ const char* name,
+ BrowserContextDependencyManager* manager)
+ : BrowserContextKeyedServiceFactory(name, manager) {}
+
+ExtensionSystemProvider::~ExtensionSystemProvider() {}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_system_provider.h b/chromium/extensions/browser/extension_system_provider.h
new file mode 100644
index 00000000000..763afce1081
--- /dev/null
+++ b/chromium/extensions/browser/extension_system_provider.h
@@ -0,0 +1,35 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_SYSTEM_PROVIDER_H_
+#define EXTENSIONS_BROWSER_EXTENSION_SYSTEM_PROVIDER_H_
+
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+class BrowserContextDependencyManager;
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+class ExtensionSystem;
+
+// An ExtensionSystemProvider maps a BrowserContext to its ExtensionSystem.
+// Different applications may use this to provide differing implementations
+// of ExtensionSystem.
+// TODO(yoz): Rename to ExtensionSystemFactory.
+class ExtensionSystemProvider : public BrowserContextKeyedServiceFactory {
+ public:
+ ExtensionSystemProvider(const char* name,
+ BrowserContextDependencyManager* manager);
+ ~ExtensionSystemProvider() override;
+
+ virtual ExtensionSystem* GetForBrowserContext(
+ content::BrowserContext* context) = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_SYSTEM_PROVIDER_H_
diff --git a/chromium/extensions/browser/extension_throttle_entry.cc b/chromium/extensions/browser/extension_throttle_entry.cc
new file mode 100644
index 00000000000..577ac55d661
--- /dev/null
+++ b/chromium/extensions/browser/extension_throttle_entry.cc
@@ -0,0 +1,287 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_throttle_entry.h"
+
+#include <cmath>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "extensions/browser/extension_throttle_manager.h"
+#include "net/base/load_flags.h"
+#include "net/log/net_log.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+
+namespace extensions {
+
+const int ExtensionThrottleEntry::kDefaultSlidingWindowPeriodMs = 2000;
+const int ExtensionThrottleEntry::kDefaultMaxSendThreshold = 20;
+
+// This set of back-off parameters will (at maximum values, i.e. without
+// the reduction caused by jitter) add 0-41% (distributed uniformly
+// in that range) to the "perceived downtime" of the remote server, once
+// exponential back-off kicks in and is throttling requests for more than
+// about a second at a time. Once the maximum back-off is reached, the added
+// perceived downtime decreases rapidly, percentage-wise.
+//
+// Another way to put it is that the maximum additional perceived downtime
+// with these numbers is a couple of seconds shy of 15 minutes, and such
+// a delay would not occur until the remote server has been actually
+// unavailable at the end of each back-off period for a total of about
+// 48 minutes.
+//
+// Ignoring the first couple of errors is just a conservative measure to
+// avoid false positives. It should help avoid back-off from kicking in e.g.
+// on flaky connections.
+const int ExtensionThrottleEntry::kDefaultNumErrorsToIgnore = 2;
+const int ExtensionThrottleEntry::kDefaultInitialDelayMs = 700;
+const double ExtensionThrottleEntry::kDefaultMultiplyFactor = 1.4;
+const double ExtensionThrottleEntry::kDefaultJitterFactor = 0.4;
+const int ExtensionThrottleEntry::kDefaultMaximumBackoffMs = 15 * 60 * 1000;
+const int ExtensionThrottleEntry::kDefaultEntryLifetimeMs = 2 * 60 * 1000;
+
+// Returns NetLog parameters when a request is rejected by throttling.
+scoped_ptr<base::Value> NetLogRejectedRequestCallback(
+ const std::string* url_id,
+ int num_failures,
+ const base::TimeDelta& release_after,
+ net::NetLogCaptureMode /* capture_mode */) {
+ scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
+ dict->SetString("url", *url_id);
+ dict->SetInteger("num_failures", num_failures);
+ dict->SetInteger("release_after_ms",
+ static_cast<int>(release_after.InMilliseconds()));
+ return std::move(dict);
+}
+
+ExtensionThrottleEntry::ExtensionThrottleEntry(
+ ExtensionThrottleManager* manager,
+ const std::string& url_id)
+ : ExtensionThrottleEntry(manager, url_id, false) {
+}
+
+ExtensionThrottleEntry::ExtensionThrottleEntry(
+ ExtensionThrottleManager* manager,
+ const std::string& url_id,
+ bool ignore_user_gesture_load_flag_for_tests)
+ : sliding_window_period_(
+ base::TimeDelta::FromMilliseconds(kDefaultSlidingWindowPeriodMs)),
+ max_send_threshold_(kDefaultMaxSendThreshold),
+ is_backoff_disabled_(false),
+ backoff_entry_(&backoff_policy_),
+ manager_(manager),
+ url_id_(url_id),
+ net_log_(net::BoundNetLog::Make(
+ manager->net_log(),
+ net::NetLog::SOURCE_EXPONENTIAL_BACKOFF_THROTTLING)),
+ ignore_user_gesture_load_flag_for_tests_(
+ ignore_user_gesture_load_flag_for_tests) {
+ DCHECK(manager_);
+ Initialize();
+}
+
+ExtensionThrottleEntry::ExtensionThrottleEntry(
+ ExtensionThrottleManager* manager,
+ const std::string& url_id,
+ const net::BackoffEntry::Policy* backoff_policy,
+ bool ignore_user_gesture_load_flag_for_tests)
+ : sliding_window_period_(
+ base::TimeDelta::FromMilliseconds(kDefaultSlidingWindowPeriodMs)),
+ max_send_threshold_(kDefaultMaxSendThreshold),
+ is_backoff_disabled_(false),
+ backoff_entry_(&backoff_policy_),
+ manager_(manager),
+ url_id_(url_id),
+ ignore_user_gesture_load_flag_for_tests_(
+ ignore_user_gesture_load_flag_for_tests) {
+ DCHECK_GE(backoff_policy->initial_delay_ms, 0);
+ DCHECK_GT(backoff_policy->multiply_factor, 0);
+ DCHECK_GE(backoff_policy->jitter_factor, 0.0);
+ DCHECK_LT(backoff_policy->jitter_factor, 1.0);
+ DCHECK_GE(backoff_policy->maximum_backoff_ms, 0);
+ DCHECK(manager_);
+
+ Initialize();
+ backoff_policy_ = *backoff_policy;
+}
+
+bool ExtensionThrottleEntry::IsEntryOutdated() const {
+ // This function is called by the ExtensionThrottleManager to determine
+ // whether entries should be discarded from its url_entries_ map. We
+ // want to ensure that it does not remove entries from the map while there
+ // are clients (objects other than the manager) holding references to
+ // the entry, otherwise separate clients could end up holding separate
+ // entries for a request to the same URL, which is undesirable. Therefore,
+ // if an entry has more than one reference (the map will always hold one),
+ // it should not be considered outdated.
+ //
+ // We considered whether to make ExtensionThrottleEntry objects
+ // non-refcounted, but since any means of knowing whether they are
+ // currently in use by others than the manager would be more or less
+ // equivalent to a refcount, we kept them refcounted.
+ if (!HasOneRef())
+ return false;
+
+ // If there are send events in the sliding window period, we still need this
+ // entry.
+ if (!send_log_.empty() &&
+ send_log_.back() + sliding_window_period_ > ImplGetTimeNow()) {
+ return false;
+ }
+
+ return GetBackoffEntry()->CanDiscard();
+}
+
+void ExtensionThrottleEntry::DisableBackoffThrottling() {
+ is_backoff_disabled_ = true;
+}
+
+void ExtensionThrottleEntry::DetachManager() {
+ manager_ = NULL;
+}
+
+bool ExtensionThrottleEntry::ShouldRejectRequest(
+ const net::URLRequest& request) const {
+ bool reject_request = false;
+ if (!is_backoff_disabled_ && (ignore_user_gesture_load_flag_for_tests_ ||
+ !ExplicitUserRequest(request.load_flags())) &&
+ GetBackoffEntry()->ShouldRejectRequest()) {
+ net_log_.AddEvent(net::NetLog::TYPE_THROTTLING_REJECTED_REQUEST,
+ base::Bind(&NetLogRejectedRequestCallback, &url_id_,
+ GetBackoffEntry()->failure_count(),
+ GetBackoffEntry()->GetTimeUntilRelease()));
+ reject_request = true;
+ }
+ return reject_request;
+}
+
+int64_t ExtensionThrottleEntry::ReserveSendingTimeForNextRequest(
+ const base::TimeTicks& earliest_time) {
+ base::TimeTicks now = ImplGetTimeNow();
+
+ // If a lot of requests were successfully made recently,
+ // sliding_window_release_time_ may be greater than
+ // exponential_backoff_release_time_.
+ base::TimeTicks recommended_sending_time =
+ std::max(std::max(now, earliest_time),
+ std::max(GetBackoffEntry()->GetReleaseTime(),
+ sliding_window_release_time_));
+
+ DCHECK(send_log_.empty() || recommended_sending_time >= send_log_.back());
+ // Log the new send event.
+ send_log_.push(recommended_sending_time);
+
+ sliding_window_release_time_ = recommended_sending_time;
+
+ // Drop the out-of-date events in the event list.
+ // We don't need to worry that the queue may become empty during this
+ // operation, since the last element is sliding_window_release_time_.
+ while ((send_log_.front() + sliding_window_period_ <=
+ sliding_window_release_time_) ||
+ send_log_.size() > static_cast<unsigned>(max_send_threshold_)) {
+ send_log_.pop();
+ }
+
+ // Check if there are too many send events in recent time.
+ if (send_log_.size() == static_cast<unsigned>(max_send_threshold_))
+ sliding_window_release_time_ = send_log_.front() + sliding_window_period_;
+
+ return (recommended_sending_time - now).InMillisecondsRoundedUp();
+}
+
+base::TimeTicks ExtensionThrottleEntry::GetExponentialBackoffReleaseTime()
+ const {
+ // If a site opts out, it's likely because they have problems that trigger
+ // the back-off mechanism when it shouldn't be triggered, in which case
+ // returning the calculated back-off release time would probably be the
+ // wrong thing to do (i.e. it would likely be too long). Therefore, we
+ // return "now" so that retries are not delayed.
+ if (is_backoff_disabled_)
+ return ImplGetTimeNow();
+
+ return GetBackoffEntry()->GetReleaseTime();
+}
+
+void ExtensionThrottleEntry::UpdateWithResponse(int status_code) {
+ GetBackoffEntry()->InformOfRequest(IsConsideredSuccess(status_code));
+}
+
+void ExtensionThrottleEntry::ReceivedContentWasMalformed(int response_code) {
+ // A malformed body can only occur when the request to fetch a resource
+ // was successful. Therefore, in such a situation, we will receive one
+ // call to ReceivedContentWasMalformed() and one call to
+ // UpdateWithResponse() with a response categorized as "good". To end
+ // up counting one failure, we need to count two failures here against
+ // the one success in UpdateWithResponse().
+ //
+ // We do nothing for a response that is already being considered an error
+ // based on its status code (otherwise we would count 3 errors instead of 1).
+ if (IsConsideredSuccess(response_code)) {
+ GetBackoffEntry()->InformOfRequest(false);
+ GetBackoffEntry()->InformOfRequest(false);
+ }
+}
+
+const std::string& ExtensionThrottleEntry::GetURLIdForDebugging() const {
+ return url_id_;
+}
+
+ExtensionThrottleEntry::~ExtensionThrottleEntry() {
+}
+
+void ExtensionThrottleEntry::Initialize() {
+ sliding_window_release_time_ = base::TimeTicks::Now();
+ backoff_policy_.num_errors_to_ignore = kDefaultNumErrorsToIgnore;
+ backoff_policy_.initial_delay_ms = kDefaultInitialDelayMs;
+ backoff_policy_.multiply_factor = kDefaultMultiplyFactor;
+ backoff_policy_.jitter_factor = kDefaultJitterFactor;
+ backoff_policy_.maximum_backoff_ms = kDefaultMaximumBackoffMs;
+ backoff_policy_.entry_lifetime_ms = kDefaultEntryLifetimeMs;
+ backoff_policy_.always_use_initial_delay = false;
+}
+
+bool ExtensionThrottleEntry::IsConsideredSuccess(int response_code) {
+ // We throttle only for the status codes most likely to indicate the server
+ // is failing because it is too busy or otherwise are likely to be
+ // because of DDoS.
+ //
+ // 500 is the generic error when no better message is suitable, and
+ // as such does not necessarily indicate a temporary state, but
+ // other status codes cover most of the permanent error states.
+ // 503 is explicitly documented as a temporary state where the server
+ // is either overloaded or down for maintenance.
+ // 509 is the (non-standard but widely implemented) Bandwidth Limit Exceeded
+ // status code, which might indicate DDoS.
+ //
+ // We do not back off on 502 or 504, which are reported by gateways
+ // (proxies) on timeouts or failures, because in many cases these requests
+ // have not made it to the destination server and so we do not actually
+ // know that it is down or busy. One degenerate case could be a proxy on
+ // localhost, where you are not actually connected to the network.
+ return !(response_code == 500 || response_code == 503 ||
+ response_code == 509);
+}
+
+base::TimeTicks ExtensionThrottleEntry::ImplGetTimeNow() const {
+ return base::TimeTicks::Now();
+}
+
+const net::BackoffEntry* ExtensionThrottleEntry::GetBackoffEntry() const {
+ return &backoff_entry_;
+}
+
+net::BackoffEntry* ExtensionThrottleEntry::GetBackoffEntry() {
+ return &backoff_entry_;
+}
+
+// static
+bool ExtensionThrottleEntry::ExplicitUserRequest(const int load_flags) {
+ return (load_flags & net::LOAD_MAYBE_USER_GESTURE) != 0;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_throttle_entry.h b/chromium/extensions/browser/extension_throttle_entry.h
new file mode 100644
index 00000000000..18194ac81ca
--- /dev/null
+++ b/chromium/extensions/browser/extension_throttle_entry.h
@@ -0,0 +1,166 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_THROTTLE_ENTRY_H_
+#define EXTENSIONS_BROWSER_EXTENSION_THROTTLE_ENTRY_H_
+
+#include <stdint.h>
+
+#include <queue>
+#include <string>
+
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "extensions/browser/extension_throttle_entry_interface.h"
+#include "net/base/backoff_entry.h"
+#include "net/log/net_log.h"
+
+namespace extensions {
+
+class ExtensionThrottleManager;
+
+// ExtensionThrottleEntry represents an entry of ExtensionThrottleManager.
+// It analyzes requests of a specific URL over some period of time, in order to
+// deduce the back-off time for every request.
+// The back-off algorithm consists of two parts. Firstly, exponential back-off
+// is used when receiving 5XX server errors or malformed response bodies.
+// The exponential back-off rule is enforced by URLRequestHttpJob. Any
+// request sent during the back-off period will be cancelled.
+// Secondly, a sliding window is used to count recent requests to a given
+// destination and provide guidance (to the application level only) on whether
+// too many requests have been sent and when a good time to send the next one
+// would be. This is never used to deny requests at the network level.
+class ExtensionThrottleEntry : public ExtensionThrottleEntryInterface {
+ public:
+ // Sliding window period.
+ static const int kDefaultSlidingWindowPeriodMs;
+
+ // Maximum number of requests allowed in sliding window period.
+ static const int kDefaultMaxSendThreshold;
+
+ // Number of initial errors to ignore before starting exponential back-off.
+ static const int kDefaultNumErrorsToIgnore;
+
+ // Initial delay for exponential back-off.
+ static const int kDefaultInitialDelayMs;
+
+ // Factor by which the waiting time will be multiplied.
+ static const double kDefaultMultiplyFactor;
+
+ // Fuzzing percentage. ex: 10% will spread requests randomly
+ // between 90%-100% of the calculated time.
+ static const double kDefaultJitterFactor;
+
+ // Maximum amount of time we are willing to delay our request.
+ static const int kDefaultMaximumBackoffMs;
+
+ // Time after which the entry is considered outdated.
+ static const int kDefaultEntryLifetimeMs;
+
+ // The manager object's lifetime must enclose the lifetime of this object.
+ ExtensionThrottleEntry(ExtensionThrottleManager* manager,
+ const std::string& url_id);
+
+ // Same as above, but exposes the option to ignore
+ // net::LOAD_MAYBE_USER_GESTURE flag of the request.
+ ExtensionThrottleEntry(ExtensionThrottleManager* manager,
+ const std::string& url_id,
+ bool ignore_user_gesture_load_flag_for_tests);
+
+ // The life span of instances created with this constructor is set to
+ // infinite, and the number of initial errors to ignore is set to 0.
+ // It is only used by unit tests.
+ ExtensionThrottleEntry(ExtensionThrottleManager* manager,
+ const std::string& url_id,
+ const net::BackoffEntry::Policy* backoff_policy,
+ bool ignore_user_gesture_load_flag_for_tests);
+
+ // Used by the manager, returns true if the entry needs to be garbage
+ // collected.
+ bool IsEntryOutdated() const;
+
+ // Causes this entry to never reject requests due to back-off.
+ void DisableBackoffThrottling();
+
+ // Causes this entry to NULL its manager pointer.
+ void DetachManager();
+
+ // Implementation of ExtensionThrottleEntryInterface.
+ bool ShouldRejectRequest(const net::URLRequest& request) const override;
+ int64_t ReserveSendingTimeForNextRequest(
+ const base::TimeTicks& earliest_time) override;
+ base::TimeTicks GetExponentialBackoffReleaseTime() const override;
+ void UpdateWithResponse(int status_code) override;
+ void ReceivedContentWasMalformed(int response_code) override;
+ const std::string& GetURLIdForDebugging() const override;
+
+ protected:
+ ~ExtensionThrottleEntry() override;
+
+ void Initialize();
+
+ // Returns true if the given response code is considered a success for
+ // throttling purposes.
+ bool IsConsideredSuccess(int response_code);
+
+ // Equivalent to TimeTicks::Now(), virtual to be mockable for testing purpose.
+ virtual base::TimeTicks ImplGetTimeNow() const;
+
+ // Retrieves the back-off entry object we're using. Used to enable a
+ // unit testing seam for dependency injection in tests.
+ virtual const net::BackoffEntry* GetBackoffEntry() const;
+ virtual net::BackoffEntry* GetBackoffEntry();
+
+ // Returns true if |load_flags| contains a flag that indicates an
+ // explicit request by the user to load the resource. We never
+ // throttle requests with such load flags.
+ static bool ExplicitUserRequest(const int load_flags);
+
+ // Used by tests.
+ base::TimeTicks sliding_window_release_time() const {
+ return sliding_window_release_time_;
+ }
+
+ // Used by tests.
+ void set_sliding_window_release_time(const base::TimeTicks& release_time) {
+ sliding_window_release_time_ = release_time;
+ }
+
+ // Valid and immutable after construction time.
+ net::BackoffEntry::Policy backoff_policy_;
+
+ private:
+ // Timestamp calculated by the sliding window algorithm for when we advise
+ // clients the next request should be made, at the earliest. Advisory only,
+ // not used to deny requests.
+ base::TimeTicks sliding_window_release_time_;
+
+ // A list of the recent send events. We use them to decide whether there are
+ // too many requests sent in sliding window.
+ std::queue<base::TimeTicks> send_log_;
+
+ const base::TimeDelta sliding_window_period_;
+ const int max_send_threshold_;
+
+ // True if DisableBackoffThrottling() has been called on this object.
+ bool is_backoff_disabled_;
+
+ // Access it through GetBackoffEntry() to allow a unit test seam.
+ net::BackoffEntry backoff_entry_;
+
+ // Weak back-reference to the manager object managing us.
+ ExtensionThrottleManager* manager_;
+
+ // Canonicalized URL string that this entry is for; used for logging only.
+ std::string url_id_;
+
+ net::BoundNetLog net_log_;
+ bool ignore_user_gesture_load_flag_for_tests_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionThrottleEntry);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_THROTTLE_ENTRY_H_
diff --git a/chromium/extensions/browser/extension_throttle_entry_interface.h b/chromium/extensions/browser/extension_throttle_entry_interface.h
new file mode 100644
index 00000000000..57d1b02c7da
--- /dev/null
+++ b/chromium/extensions/browser/extension_throttle_entry_interface.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_THROTTLE_ENTRY_INTERFACE_H_
+#define EXTENSIONS_BROWSER_EXTENSION_THROTTLE_ENTRY_INTERFACE_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+namespace net {
+class URLRequest;
+} // namespace net
+
+namespace extensions {
+
+// Interface provided on entries of the URL request throttler manager.
+class ExtensionThrottleEntryInterface
+ : public base::RefCountedThreadSafe<ExtensionThrottleEntryInterface> {
+ public:
+ ExtensionThrottleEntryInterface() {}
+
+ // Returns true when we have encountered server errors and are doing
+ // exponential back-off, unless the request has load flags that mean
+ // it is likely to be user-initiated, or the NetworkDelegate returns
+ // false for |CanThrottleRequest(request)|.
+ //
+ // URLRequestHttpJob checks this method prior to every request; it
+ // cancels requests if this method returns true.
+ virtual bool ShouldRejectRequest(const net::URLRequest& request) const = 0;
+
+ // Calculates a recommended sending time for the next request and reserves it.
+ // The sending time is not earlier than the current exponential back-off
+ // release time or |earliest_time|. Moreover, the previous results of
+ // the method are taken into account, in order to make sure they are spread
+ // properly over time.
+ // Returns the recommended delay before sending the next request, in
+ // milliseconds. The return value is always positive or 0.
+ // Although it is not mandatory, respecting the value returned by this method
+ // is helpful to avoid traffic overload.
+ virtual int64_t ReserveSendingTimeForNextRequest(
+ const base::TimeTicks& earliest_time) = 0;
+
+ // Returns the time after which requests are allowed.
+ virtual base::TimeTicks GetExponentialBackoffReleaseTime() const = 0;
+
+ // This method needs to be called each time a response is received.
+ virtual void UpdateWithResponse(int status_code) = 0;
+
+ // Lets higher-level modules, that know how to parse particular response
+ // bodies, notify of receiving malformed content for the given URL. This will
+ // be handled by the throttler as if an HTTP 503 response had been received to
+ // the request, i.e. it will count as a failure, unless the HTTP response code
+ // indicated is already one of those that will be counted as an error.
+ virtual void ReceivedContentWasMalformed(int response_code) = 0;
+
+ // Get the URL ID associated with his entry. Should only be used for debugging
+ // purpose.
+ virtual const std::string& GetURLIdForDebugging() const = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<ExtensionThrottleEntryInterface>;
+ virtual ~ExtensionThrottleEntryInterface() {}
+
+ private:
+ friend class base::RefCounted<ExtensionThrottleEntryInterface>;
+ DISALLOW_COPY_AND_ASSIGN(ExtensionThrottleEntryInterface);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_THROTTLE_ENTRY_INTERFACE_H_
diff --git a/chromium/extensions/browser/extension_throttle_manager.cc b/chromium/extensions/browser/extension_throttle_manager.cc
new file mode 100644
index 00000000000..16d9501b015
--- /dev/null
+++ b/chromium/extensions/browser/extension_throttle_manager.cc
@@ -0,0 +1,217 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_throttle_manager.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "extensions/browser/extension_request_limiting_throttle.h"
+#include "extensions/common/constants.h"
+#include "net/base/url_util.h"
+#include "net/log/net_log.h"
+#include "net/url_request/url_request.h"
+
+namespace extensions {
+
+const unsigned int ExtensionThrottleManager::kMaximumNumberOfEntries = 1500;
+const unsigned int ExtensionThrottleManager::kRequestsBetweenCollecting = 200;
+
+ExtensionThrottleManager::ExtensionThrottleManager()
+ : requests_since_last_gc_(0),
+ enable_thread_checks_(false),
+ logged_for_localhost_disabled_(false),
+ registered_from_thread_(base::kInvalidThreadId),
+ ignore_user_gesture_load_flag_for_tests_(false) {
+ url_id_replacements_.ClearPassword();
+ url_id_replacements_.ClearUsername();
+ url_id_replacements_.ClearQuery();
+ url_id_replacements_.ClearRef();
+
+ net::NetworkChangeNotifier::AddIPAddressObserver(this);
+ net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
+}
+
+ExtensionThrottleManager::~ExtensionThrottleManager() {
+ net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
+ net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
+
+ // Since the manager object might conceivably go away before the
+ // entries, detach the entries' back-pointer to the manager.
+ UrlEntryMap::iterator i = url_entries_.begin();
+ while (i != url_entries_.end()) {
+ if (i->second.get() != NULL) {
+ i->second->DetachManager();
+ }
+ ++i;
+ }
+
+ // Delete all entries.
+ url_entries_.clear();
+}
+
+scoped_ptr<content::ResourceThrottle>
+ExtensionThrottleManager::MaybeCreateThrottle(const net::URLRequest* request) {
+ if (request->first_party_for_cookies().scheme() !=
+ extensions::kExtensionScheme) {
+ return nullptr;
+ }
+ return make_scoped_ptr(
+ new extensions::ExtensionRequestLimitingThrottle(request, this));
+}
+
+scoped_refptr<ExtensionThrottleEntryInterface>
+ExtensionThrottleManager::RegisterRequestUrl(const GURL& url) {
+ DCHECK(!enable_thread_checks_ || CalledOnValidThread());
+
+ // Normalize the url.
+ std::string url_id = GetIdFromUrl(url);
+
+ // Periodically garbage collect old entries.
+ GarbageCollectEntriesIfNecessary();
+
+ // Find the entry in the map or create a new NULL entry.
+ scoped_refptr<ExtensionThrottleEntry>& entry = url_entries_[url_id];
+
+ // If the entry exists but could be garbage collected at this point, we
+ // start with a fresh entry so that we possibly back off a bit less
+ // aggressively (i.e. this resets the error count when the entry's URL
+ // hasn't been requested in long enough).
+ if (entry.get() && entry->IsEntryOutdated()) {
+ entry = NULL;
+ }
+
+ // Create the entry if needed.
+ if (entry.get() == NULL) {
+ if (backoff_policy_for_tests_) {
+ entry = new ExtensionThrottleEntry(
+ this, url_id, backoff_policy_for_tests_.get(),
+ ignore_user_gesture_load_flag_for_tests_);
+ } else {
+ entry = new ExtensionThrottleEntry(
+ this, url_id, ignore_user_gesture_load_flag_for_tests_);
+ }
+
+ // We only disable back-off throttling on an entry that we have
+ // just constructed. This is to allow unit tests to explicitly override
+ // the entry for localhost URLs.
+ std::string host = url.host();
+ if (net::IsLocalhost(host)) {
+ if (!logged_for_localhost_disabled_ && net::IsLocalhost(host)) {
+ logged_for_localhost_disabled_ = true;
+ net_log_.AddEvent(net::NetLog::TYPE_THROTTLING_DISABLED_FOR_HOST,
+ net::NetLog::StringCallback("host", &host));
+ }
+
+ // TODO(joi): Once sliding window is separate from back-off throttling,
+ // we can simply return a dummy implementation of
+ // ExtensionThrottleEntryInterface here that never blocks anything.
+ entry->DisableBackoffThrottling();
+ }
+ }
+
+ return entry;
+}
+
+void ExtensionThrottleManager::SetBackoffPolicyForTests(
+ scoped_ptr<net::BackoffEntry::Policy> policy) {
+ backoff_policy_for_tests_ = std::move(policy);
+}
+
+void ExtensionThrottleManager::OverrideEntryForTests(
+ const GURL& url,
+ ExtensionThrottleEntry* entry) {
+ // Normalize the url.
+ std::string url_id = GetIdFromUrl(url);
+
+ // Periodically garbage collect old entries.
+ GarbageCollectEntriesIfNecessary();
+
+ url_entries_[url_id] = entry;
+}
+
+void ExtensionThrottleManager::EraseEntryForTests(const GURL& url) {
+ // Normalize the url.
+ std::string url_id = GetIdFromUrl(url);
+ url_entries_.erase(url_id);
+}
+
+void ExtensionThrottleManager::SetIgnoreUserGestureLoadFlagForTests(
+ bool ignore_user_gesture_load_flag_for_tests) {
+ ignore_user_gesture_load_flag_for_tests_ = true;
+}
+
+void ExtensionThrottleManager::set_enable_thread_checks(bool enable) {
+ enable_thread_checks_ = enable;
+}
+
+bool ExtensionThrottleManager::enable_thread_checks() const {
+ return enable_thread_checks_;
+}
+
+void ExtensionThrottleManager::set_net_log(net::NetLog* net_log) {
+ DCHECK(net_log);
+ net_log_ = net::BoundNetLog::Make(
+ net_log, net::NetLog::SOURCE_EXPONENTIAL_BACKOFF_THROTTLING);
+}
+
+net::NetLog* ExtensionThrottleManager::net_log() const {
+ return net_log_.net_log();
+}
+
+void ExtensionThrottleManager::OnIPAddressChanged() {
+ OnNetworkChange();
+}
+
+void ExtensionThrottleManager::OnConnectionTypeChanged(
+ net::NetworkChangeNotifier::ConnectionType type) {
+ OnNetworkChange();
+}
+
+std::string ExtensionThrottleManager::GetIdFromUrl(const GURL& url) const {
+ if (!url.is_valid())
+ return url.possibly_invalid_spec();
+
+ GURL id = url.ReplaceComponents(url_id_replacements_);
+ return base::ToLowerASCII(id.spec());
+}
+
+void ExtensionThrottleManager::GarbageCollectEntriesIfNecessary() {
+ requests_since_last_gc_++;
+ if (requests_since_last_gc_ < kRequestsBetweenCollecting)
+ return;
+ requests_since_last_gc_ = 0;
+
+ GarbageCollectEntries();
+}
+
+void ExtensionThrottleManager::GarbageCollectEntries() {
+ UrlEntryMap::iterator i = url_entries_.begin();
+ while (i != url_entries_.end()) {
+ if ((i->second)->IsEntryOutdated()) {
+ url_entries_.erase(i++);
+ } else {
+ ++i;
+ }
+ }
+
+ // In case something broke we want to make sure not to grow indefinitely.
+ while (url_entries_.size() > kMaximumNumberOfEntries) {
+ url_entries_.erase(url_entries_.begin());
+ }
+}
+
+void ExtensionThrottleManager::OnNetworkChange() {
+ // Remove all entries. Any entries that in-flight requests have a reference
+ // to will live until those requests end, and these entries may be
+ // inconsistent with new entries for the same URLs, but since what we
+ // want is a clean slate for the new connection type, this is OK.
+ url_entries_.clear();
+ requests_since_last_gc_ = 0;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_throttle_manager.h b/chromium/extensions/browser/extension_throttle_manager.h
new file mode 100644
index 00000000000..5a5b2845c52
--- /dev/null
+++ b/chromium/extensions/browser/extension_throttle_manager.h
@@ -0,0 +1,185 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_THROTTLE_MANAGER_H_
+#define EXTENSIONS_BROWSER_EXTENSION_THROTTLE_MANAGER_H_
+
+#include <map>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/threading/platform_thread.h"
+#include "extensions/browser/extension_throttle_entry.h"
+#include "net/base/backoff_entry.h"
+#include "net/base/net_export.h"
+#include "net/base/network_change_notifier.h"
+#include "url/gurl.h"
+
+namespace content {
+class ResourceThrottle;
+}
+
+namespace net {
+class BoundNetLog;
+class NetLog;
+}
+
+namespace extensions {
+
+// Class that registers URL request throttler entries for URLs being accessed
+// in order to supervise traffic. URL requests for HTTP contents should
+// register their URLs in this manager on each request.
+//
+// ExtensionThrottleManager maintains a map of URL IDs to URL request
+// throttler entries. It creates URL request throttler entries when new URLs
+// are registered, and does garbage collection from time to time in order to
+// clean out outdated entries. URL ID consists of lowercased scheme, host, port
+// and path. All URLs converted to the same ID will share the same entry.
+class ExtensionThrottleManager
+ : NON_EXPORTED_BASE(public base::NonThreadSafe),
+ public net::NetworkChangeNotifier::IPAddressObserver,
+ public net::NetworkChangeNotifier::ConnectionTypeObserver {
+ public:
+ ExtensionThrottleManager();
+ ~ExtensionThrottleManager() override;
+
+ // Creates a content::ResourceThrottle for |request| to prevent extensions
+ // from requesting a URL too often, if such a throttle is needed.
+ scoped_ptr<content::ResourceThrottle> MaybeCreateThrottle(
+ const net::URLRequest* request);
+
+ // TODO(xunjieli): Remove this method and replace with
+ // ShouldRejectRequest(request) and UpdateWithResponse(request, status_code),
+ // which will also allow ExtensionThrottleEntry to no longer be reference
+ // counted, and ExtensionThrottleEntryInterface to be removed.
+
+ // Must be called for every request, returns the URL request throttler entry
+ // associated with the URL. The caller must inform this entry of some events.
+ // Please refer to extension_throttle_entry_interface.h for further
+ // informations.
+ scoped_refptr<ExtensionThrottleEntryInterface> RegisterRequestUrl(
+ const GURL& url);
+
+ void SetBackoffPolicyForTests(scoped_ptr<net::BackoffEntry::Policy> policy);
+
+ // Registers a new entry in this service and overrides the existing entry (if
+ // any) for the URL. The service will hold a reference to the entry.
+ // It is only used by unit tests.
+ void OverrideEntryForTests(const GURL& url, ExtensionThrottleEntry* entry);
+
+ // Explicitly erases an entry.
+ // This is useful to remove those entries which have got infinite lifetime and
+ // thus won't be garbage collected.
+ // It is only used by unit tests.
+ void EraseEntryForTests(const GURL& url);
+
+ // Sets whether to ignore net::LOAD_MAYBE_USER_GESTURE of the request for
+ // testing. Otherwise, requests will not be throttled when they may have been
+ // throttled in response to a recent user gesture, though they're still
+ // counted for the purpose of throttling other requests.
+ void SetIgnoreUserGestureLoadFlagForTests(
+ bool ignore_user_gesture_load_flag_for_tests);
+
+ // Turns threading model verification on or off. Any code that correctly
+ // uses the network stack should preferably call this function to enable
+ // verification of correct adherence to the network stack threading model.
+ void set_enable_thread_checks(bool enable);
+ bool enable_thread_checks() const;
+
+ // Whether throttling is enabled or not.
+ void set_enforce_throttling(bool enforce);
+ bool enforce_throttling();
+
+ // Sets the net::NetLog instance to use.
+ void set_net_log(net::NetLog* net_log);
+ net::NetLog* net_log() const;
+
+ // IPAddressObserver interface.
+ void OnIPAddressChanged() override;
+
+ // ConnectionTypeObserver interface.
+ void OnConnectionTypeChanged(
+ net::NetworkChangeNotifier::ConnectionType type) override;
+
+ // Method that allows us to transform a URL into an ID that can be used in our
+ // map. Resulting IDs will be lowercase and consist of the scheme, host, port
+ // and path (without query string, fragment, etc.).
+ // If the URL is invalid, the invalid spec will be returned, without any
+ // transformation.
+ std::string GetIdFromUrl(const GURL& url) const;
+
+ // Method that ensures the map gets cleaned from time to time. The period at
+ // which garbage collecting happens is adjustable with the
+ // kRequestBetweenCollecting constant.
+ void GarbageCollectEntriesIfNecessary();
+
+ // Method that does the actual work of garbage collecting.
+ void GarbageCollectEntries();
+
+ // When we switch from online to offline or change IP addresses, we
+ // clear all back-off history. This is a precaution in case the change in
+ // online state now lets us communicate without error with servers that
+ // we were previously getting 500 or 503 responses from (perhaps the
+ // responses are from a badly-written proxy that should have returned a
+ // 502 or 504 because it's upstream connection was down or it had no route
+ // to the server).
+ void OnNetworkChange();
+
+ // Used by tests.
+ int GetNumberOfEntriesForTests() const { return url_entries_.size(); }
+
+ private:
+ // From each URL we generate an ID composed of the scheme, host, port and path
+ // that allows us to uniquely map an entry to it.
+ typedef std::map<std::string, scoped_refptr<ExtensionThrottleEntry>>
+ UrlEntryMap;
+
+ // Maximum number of entries that we are willing to collect in our map.
+ static const unsigned int kMaximumNumberOfEntries;
+ // Number of requests that will be made between garbage collection.
+ static const unsigned int kRequestsBetweenCollecting;
+
+ // Map that contains a list of URL ID and their matching
+ // ExtensionThrottleEntry.
+ UrlEntryMap url_entries_;
+
+ // This keeps track of how many requests have been made. Used with
+ // GarbageCollectEntries.
+ unsigned int requests_since_last_gc_;
+
+ // Valid after construction.
+ GURL::Replacements url_id_replacements_;
+
+ // Certain tests do not obey the net component's threading policy, so we
+ // keep track of whether we're being used by tests, and turn off certain
+ // checks.
+ //
+ // TODO(joi): See if we can fix the offending unit tests and remove this
+ // workaround.
+ bool enable_thread_checks_;
+
+ // Initially false, switches to true once we have logged because of back-off
+ // being disabled for localhost.
+ bool logged_for_localhost_disabled_;
+
+ // net::NetLog to use, if configured.
+ net::BoundNetLog net_log_;
+
+ // Valid once we've registered for network notifications.
+ base::PlatformThreadId registered_from_thread_;
+
+ bool ignore_user_gesture_load_flag_for_tests_;
+
+ // This is NULL when it is not set for tests.
+ scoped_ptr<net::BackoffEntry::Policy> backoff_policy_for_tests_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionThrottleManager);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_THROTTLE_MANAGER_H_
diff --git a/chromium/extensions/browser/extension_throttle_simulation_unittest.cc b/chromium/extensions/browser/extension_throttle_simulation_unittest.cc
new file mode 100644
index 00000000000..d81ceed3b60
--- /dev/null
+++ b/chromium/extensions/browser/extension_throttle_simulation_unittest.cc
@@ -0,0 +1,745 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The tests in this file attempt to verify the following through simulation:
+// a) That a server experiencing overload will actually benefit from the
+// anti-DDoS throttling logic, i.e. that its traffic spike will subside
+// and be distributed over a longer period of time;
+// b) That "well-behaved" clients of a server under DDoS attack actually
+// benefit from the anti-DDoS throttling logic; and
+// c) That the approximate increase in "perceived downtime" introduced by
+// anti-DDoS throttling for various different actual downtimes is what
+// we expect it to be.
+
+#include <stddef.h>
+
+#include <cmath>
+#include <limits>
+#include <vector>
+
+#include "base/environment.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/rand_util.h"
+#include "base/time/time.h"
+#include "extensions/browser/extension_throttle_manager.h"
+#include "extensions/browser/extension_throttle_test_support.h"
+#include "net/base/request_priority.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+using net::BackoffEntry;
+using net::TestURLRequestContext;
+using net::URLRequest;
+using net::URLRequestContext;
+
+namespace extensions {
+namespace {
+
+// Set this variable in your environment if you want to see verbose results
+// of the simulation tests.
+const char kShowSimulationVariableName[] = "SHOW_SIMULATION_RESULTS";
+
+// Prints output only if a given environment variable is set. We use this
+// to not print any output for human evaluation when the test is run without
+// supervision.
+void VerboseOut(const char* format, ...) {
+ static bool have_checked_environment = false;
+ static bool should_print = false;
+ if (!have_checked_environment) {
+ have_checked_environment = true;
+ scoped_ptr<base::Environment> env(base::Environment::Create());
+ if (env->HasVar(kShowSimulationVariableName))
+ should_print = true;
+ }
+
+ if (should_print) {
+ va_list arglist;
+ va_start(arglist, format);
+ vprintf(format, arglist);
+ va_end(arglist);
+ }
+}
+
+// A simple two-phase discrete time simulation. Actors are added in the order
+// they should take action at every tick of the clock. Ticks of the clock
+// are two-phase:
+// - Phase 1 advances every actor's time to a new absolute time.
+// - Phase 2 asks each actor to perform their action.
+class DiscreteTimeSimulation {
+ public:
+ class Actor {
+ public:
+ virtual ~Actor() {}
+ virtual void AdvanceTime(const TimeTicks& absolute_time) = 0;
+ virtual void PerformAction() = 0;
+ };
+
+ DiscreteTimeSimulation() {}
+
+ // Adds an |actor| to the simulation. The client of the simulation maintains
+ // ownership of |actor| and must ensure its lifetime exceeds that of the
+ // simulation. Actors should be added in the order you wish for them to
+ // act at each tick of the simulation.
+ void AddActor(Actor* actor) { actors_.push_back(actor); }
+
+ // Runs the simulation for, pretending |time_between_ticks| passes from one
+ // tick to the next. The start time will be the current real time. The
+ // simulation will stop when the simulated duration is equal to or greater
+ // than |maximum_simulated_duration|.
+ void RunSimulation(const TimeDelta& maximum_simulated_duration,
+ const TimeDelta& time_between_ticks) {
+ TimeTicks start_time = TimeTicks();
+ TimeTicks now = start_time;
+ while ((now - start_time) <= maximum_simulated_duration) {
+ for (std::vector<Actor*>::iterator it = actors_.begin();
+ it != actors_.end(); ++it) {
+ (*it)->AdvanceTime(now);
+ }
+
+ for (std::vector<Actor*>::iterator it = actors_.begin();
+ it != actors_.end(); ++it) {
+ (*it)->PerformAction();
+ }
+
+ now += time_between_ticks;
+ }
+ }
+
+ private:
+ std::vector<Actor*> actors_;
+
+ DISALLOW_COPY_AND_ASSIGN(DiscreteTimeSimulation);
+};
+
+// Represents a web server in a simulation of a server under attack by
+// a lot of clients. Must be added to the simulation's list of actors
+// after all |Requester| objects.
+class Server : public DiscreteTimeSimulation::Actor {
+ public:
+ Server(int max_queries_per_tick, double request_drop_ratio)
+ : max_queries_per_tick_(max_queries_per_tick),
+ request_drop_ratio_(request_drop_ratio),
+ num_overloaded_ticks_remaining_(0),
+ num_current_tick_queries_(0),
+ num_overloaded_ticks_(0),
+ max_experienced_queries_per_tick_(0),
+ mock_request_(
+ context_.CreateRequest(GURL(), net::DEFAULT_PRIORITY, NULL)) {}
+
+ void SetDowntime(const TimeTicks& start_time, const TimeDelta& duration) {
+ start_downtime_ = start_time;
+ end_downtime_ = start_time + duration;
+ }
+
+ void AdvanceTime(const TimeTicks& absolute_time) override {
+ now_ = absolute_time;
+ }
+
+ void PerformAction() override {
+ // We are inserted at the end of the actor's list, so all Requester
+ // instances have already done their bit.
+ if (num_current_tick_queries_ > max_experienced_queries_per_tick_)
+ max_experienced_queries_per_tick_ = num_current_tick_queries_;
+
+ if (num_current_tick_queries_ > max_queries_per_tick_) {
+ // We pretend the server fails for the next several ticks after it
+ // gets overloaded.
+ num_overloaded_ticks_remaining_ = 5;
+ ++num_overloaded_ticks_;
+ } else if (num_overloaded_ticks_remaining_ > 0) {
+ --num_overloaded_ticks_remaining_;
+ }
+
+ requests_per_tick_.push_back(num_current_tick_queries_);
+ num_current_tick_queries_ = 0;
+ }
+
+ // This is called by Requester. It returns the response code from
+ // the server.
+ int HandleRequest() {
+ ++num_current_tick_queries_;
+ if (!start_downtime_.is_null() && start_downtime_ < now_ &&
+ now_ < end_downtime_) {
+ // For the simulation measuring the increase in perceived
+ // downtime, it might be interesting to count separately the
+ // queries seen by the server (assuming a front-end reverse proxy
+ // is what actually serves up the 503s in this case) so that we could
+ // visualize the traffic spike seen by the server when it comes up,
+ // which would in many situations be ameliorated by the anti-DDoS
+ // throttling.
+ return 503;
+ }
+
+ if ((num_overloaded_ticks_remaining_ > 0 ||
+ num_current_tick_queries_ > max_queries_per_tick_) &&
+ base::RandDouble() < request_drop_ratio_) {
+ return 503;
+ }
+
+ return 200;
+ }
+
+ int num_overloaded_ticks() const { return num_overloaded_ticks_; }
+
+ int max_experienced_queries_per_tick() const {
+ return max_experienced_queries_per_tick_;
+ }
+
+ const URLRequest& mock_request() const { return *mock_request_.get(); }
+
+ std::string VisualizeASCII(int terminal_width) {
+ // Account for | characters we place at left of graph.
+ terminal_width -= 1;
+
+ VerboseOut("Overloaded for %d of %d ticks.\n", num_overloaded_ticks_,
+ requests_per_tick_.size());
+ VerboseOut("Got maximum of %d requests in a tick.\n\n",
+ max_experienced_queries_per_tick_);
+
+ VerboseOut("Traffic graph:\n\n");
+
+ // Printing the graph like this is a bit overkill, but was very useful
+ // while developing the various simulations to see if they were testing
+ // the corner cases we want to simulate.
+
+ // Find the smallest number of whole ticks we need to group into a
+ // column that will let all ticks fit into the column width we have.
+ int num_ticks = requests_per_tick_.size();
+ double ticks_per_column_exact =
+ static_cast<double>(num_ticks) / static_cast<double>(terminal_width);
+ int ticks_per_column = std::ceil(ticks_per_column_exact);
+ DCHECK_GE(ticks_per_column * terminal_width, num_ticks);
+
+ // Sum up the column values.
+ int num_columns = num_ticks / ticks_per_column;
+ if (num_ticks % ticks_per_column)
+ ++num_columns;
+ DCHECK_LE(num_columns, terminal_width);
+ scoped_ptr<int[]> columns(new int[num_columns]);
+ for (int tx = 0; tx < num_ticks; ++tx) {
+ int cx = tx / ticks_per_column;
+ if (tx % ticks_per_column == 0)
+ columns[cx] = 0;
+ columns[cx] += requests_per_tick_[tx];
+ }
+
+ // Find the lowest integer divisor that will let the column values
+ // be represented in a graph of maximum height 50.
+ int max_value = 0;
+ for (int cx = 0; cx < num_columns; ++cx)
+ max_value = std::max(max_value, columns[cx]);
+ const int kNumRows = 50;
+ double row_divisor_exact = max_value / static_cast<double>(kNumRows);
+ int row_divisor = std::ceil(row_divisor_exact);
+ DCHECK_GE(row_divisor * kNumRows, max_value);
+
+ // To show the overload line, we calculate the appropriate value.
+ int overload_value = max_queries_per_tick_ * ticks_per_column;
+
+ // When num_ticks is not a whole multiple of ticks_per_column, the last
+ // column includes fewer ticks than the others. In this case, don't
+ // print it so that we don't show an inconsistent value.
+ int num_printed_columns = num_columns;
+ if (num_ticks % ticks_per_column)
+ --num_printed_columns;
+
+ // This is a top-to-bottom traversal of rows, left-to-right per row.
+ std::string output;
+ for (int rx = 0; rx < kNumRows; ++rx) {
+ int range_min = (kNumRows - rx) * row_divisor;
+ int range_max = range_min + row_divisor;
+ if (range_min == 0)
+ range_min = -1; // Make 0 values fit in the bottom range.
+ output.append("|");
+ for (int cx = 0; cx < num_printed_columns; ++cx) {
+ char block = ' ';
+ // Show the overload line.
+ if (range_min < overload_value && overload_value <= range_max)
+ block = '-';
+
+ // Preferentially, show the graph line.
+ if (range_min < columns[cx] && columns[cx] <= range_max)
+ block = '#';
+
+ output.append(1, block);
+ }
+ output.append("\n");
+ }
+ output.append("|");
+ output.append(num_printed_columns, '=');
+
+ return output;
+ }
+
+ const URLRequestContext& context() const { return context_; }
+
+ private:
+ TimeTicks now_;
+ TimeTicks start_downtime_; // Can be 0 to say "no downtime".
+ TimeTicks end_downtime_;
+ const int max_queries_per_tick_;
+ const double request_drop_ratio_; // Ratio of requests to 503 when failing.
+ int num_overloaded_ticks_remaining_;
+ int num_current_tick_queries_;
+ int num_overloaded_ticks_;
+ int max_experienced_queries_per_tick_;
+ std::vector<int> requests_per_tick_;
+
+ TestURLRequestContext context_;
+ scoped_ptr<URLRequest> mock_request_;
+
+ DISALLOW_COPY_AND_ASSIGN(Server);
+};
+
+// Mock throttler entry used by Requester class.
+class MockExtensionThrottleEntry : public ExtensionThrottleEntry {
+ public:
+ explicit MockExtensionThrottleEntry(ExtensionThrottleManager* manager)
+ : ExtensionThrottleEntry(manager, std::string()),
+ backoff_entry_(&backoff_policy_, &fake_clock_) {}
+
+ const BackoffEntry* GetBackoffEntry() const override {
+ return &backoff_entry_;
+ }
+
+ BackoffEntry* GetBackoffEntry() override { return &backoff_entry_; }
+
+ TimeTicks ImplGetTimeNow() const override { return fake_clock_.NowTicks(); }
+
+ void SetFakeNow(const TimeTicks& fake_time) {
+ fake_clock_.set_now(fake_time);
+ }
+
+ protected:
+ ~MockExtensionThrottleEntry() override {}
+
+ private:
+ mutable TestTickClock fake_clock_;
+ BackoffEntry backoff_entry_;
+};
+
+// Registry of results for a class of |Requester| objects (e.g. attackers vs.
+// regular clients).
+class RequesterResults {
+ public:
+ RequesterResults()
+ : num_attempts_(0), num_successful_(0), num_failed_(0), num_blocked_(0) {}
+
+ void AddSuccess() {
+ ++num_attempts_;
+ ++num_successful_;
+ }
+
+ void AddFailure() {
+ ++num_attempts_;
+ ++num_failed_;
+ }
+
+ void AddBlocked() {
+ ++num_attempts_;
+ ++num_blocked_;
+ }
+
+ int num_attempts() const { return num_attempts_; }
+ int num_successful() const { return num_successful_; }
+ int num_failed() const { return num_failed_; }
+ int num_blocked() const { return num_blocked_; }
+
+ double GetBlockedRatio() {
+ DCHECK(num_attempts_);
+ return static_cast<double>(num_blocked_) /
+ static_cast<double>(num_attempts_);
+ }
+
+ double GetSuccessRatio() {
+ DCHECK(num_attempts_);
+ return static_cast<double>(num_successful_) /
+ static_cast<double>(num_attempts_);
+ }
+
+ void PrintResults(const char* class_description) {
+ if (num_attempts_ == 0) {
+ VerboseOut("No data for %s\n", class_description);
+ return;
+ }
+
+ VerboseOut("Requester results for %s\n", class_description);
+ VerboseOut(" %d attempts\n", num_attempts_);
+ VerboseOut(" %d successes\n", num_successful_);
+ VerboseOut(" %d 5xx responses\n", num_failed_);
+ VerboseOut(" %d requests blocked\n", num_blocked_);
+ VerboseOut(" %.2f success ratio\n", GetSuccessRatio());
+ VerboseOut(" %.2f blocked ratio\n", GetBlockedRatio());
+ VerboseOut("\n");
+ }
+
+ private:
+ int num_attempts_;
+ int num_successful_;
+ int num_failed_;
+ int num_blocked_;
+};
+
+// Represents an Requester in a simulated DDoS situation, that periodically
+// requests a specific resource.
+class Requester : public DiscreteTimeSimulation::Actor {
+ public:
+ Requester(MockExtensionThrottleEntry* throttler_entry,
+ const TimeDelta& time_between_requests,
+ Server* server,
+ RequesterResults* results)
+ : throttler_entry_(throttler_entry),
+ time_between_requests_(time_between_requests),
+ last_attempt_was_failure_(false),
+ server_(server),
+ results_(results) {
+ DCHECK(server_);
+ }
+
+ void AdvanceTime(const TimeTicks& absolute_time) override {
+ if (time_of_last_success_.is_null())
+ time_of_last_success_ = absolute_time;
+
+ throttler_entry_->SetFakeNow(absolute_time);
+ }
+
+ void PerformAction() override {
+ TimeDelta effective_delay = time_between_requests_;
+ TimeDelta current_jitter = TimeDelta::FromMilliseconds(
+ request_jitter_.InMilliseconds() * base::RandDouble());
+ if (base::RandInt(0, 1)) {
+ effective_delay -= current_jitter;
+ } else {
+ effective_delay += current_jitter;
+ }
+
+ if (throttler_entry_->ImplGetTimeNow() - time_of_last_attempt_ >
+ effective_delay) {
+ if (!throttler_entry_->ShouldRejectRequest(server_->mock_request())) {
+ int status_code = server_->HandleRequest();
+ throttler_entry_->UpdateWithResponse(status_code);
+
+ if (status_code == 200) {
+ if (results_)
+ results_->AddSuccess();
+
+ if (last_attempt_was_failure_) {
+ last_downtime_duration_ =
+ throttler_entry_->ImplGetTimeNow() - time_of_last_success_;
+ }
+
+ time_of_last_success_ = throttler_entry_->ImplGetTimeNow();
+ last_attempt_was_failure_ = false;
+ } else {
+ if (results_)
+ results_->AddFailure();
+ last_attempt_was_failure_ = true;
+ }
+ } else {
+ if (results_)
+ results_->AddBlocked();
+ last_attempt_was_failure_ = true;
+ }
+
+ time_of_last_attempt_ = throttler_entry_->ImplGetTimeNow();
+ }
+ }
+
+ // Adds a delay until the first request, equal to a uniformly distributed
+ // value between now and now + max_delay.
+ void SetStartupJitter(const TimeDelta& max_delay) {
+ int delay_ms = base::RandInt(0, max_delay.InMilliseconds());
+ time_of_last_attempt_ = TimeTicks() +
+ TimeDelta::FromMilliseconds(delay_ms) -
+ time_between_requests_;
+ }
+
+ void SetRequestJitter(const TimeDelta& request_jitter) {
+ request_jitter_ = request_jitter;
+ }
+
+ TimeDelta last_downtime_duration() const { return last_downtime_duration_; }
+
+ private:
+ scoped_refptr<MockExtensionThrottleEntry> throttler_entry_;
+ const TimeDelta time_between_requests_;
+ TimeDelta request_jitter_;
+ TimeTicks time_of_last_attempt_;
+ TimeTicks time_of_last_success_;
+ bool last_attempt_was_failure_;
+ TimeDelta last_downtime_duration_;
+ Server* const server_;
+ RequesterResults* const results_; // May be NULL.
+
+ DISALLOW_COPY_AND_ASSIGN(Requester);
+};
+
+void SimulateAttack(Server* server,
+ RequesterResults* attacker_results,
+ RequesterResults* client_results,
+ bool enable_throttling) {
+ const size_t kNumAttackers = 50;
+ const size_t kNumClients = 50;
+ DiscreteTimeSimulation simulation;
+ ExtensionThrottleManager manager;
+ std::vector<scoped_ptr<Requester>> requesters;
+ for (size_t i = 0; i < kNumAttackers; ++i) {
+ // Use a tiny time_between_requests so the attackers will ping the
+ // server at every tick of the simulation.
+ scoped_refptr<MockExtensionThrottleEntry> throttler_entry(
+ new MockExtensionThrottleEntry(&manager));
+ if (!enable_throttling)
+ throttler_entry->DisableBackoffThrottling();
+
+ Requester* attacker =
+ new Requester(throttler_entry.get(), TimeDelta::FromMilliseconds(1),
+ server, attacker_results);
+ attacker->SetStartupJitter(TimeDelta::FromSeconds(120));
+ requesters.push_back(make_scoped_ptr(attacker));
+ simulation.AddActor(attacker);
+ }
+ for (size_t i = 0; i < kNumClients; ++i) {
+ // Normal clients only make requests every 2 minutes, plus/minus 1 minute.
+ scoped_refptr<MockExtensionThrottleEntry> throttler_entry(
+ new MockExtensionThrottleEntry(&manager));
+ if (!enable_throttling)
+ throttler_entry->DisableBackoffThrottling();
+
+ Requester* client =
+ new Requester(throttler_entry.get(), TimeDelta::FromMinutes(2), server,
+ client_results);
+ client->SetStartupJitter(TimeDelta::FromSeconds(120));
+ client->SetRequestJitter(TimeDelta::FromMinutes(1));
+ requesters.push_back(make_scoped_ptr(client));
+ simulation.AddActor(client);
+ }
+ simulation.AddActor(server);
+
+ simulation.RunSimulation(TimeDelta::FromMinutes(6),
+ TimeDelta::FromSeconds(1));
+}
+
+TEST(URLRequestThrottlerSimulation, HelpsInAttack) {
+ base::MessageLoopForIO message_loop;
+ Server unprotected_server(30, 1.0);
+ RequesterResults unprotected_attacker_results;
+ RequesterResults unprotected_client_results;
+ Server protected_server(30, 1.0);
+ RequesterResults protected_attacker_results;
+ RequesterResults protected_client_results;
+ SimulateAttack(&unprotected_server, &unprotected_attacker_results,
+ &unprotected_client_results, false);
+ SimulateAttack(&protected_server, &protected_attacker_results,
+ &protected_client_results, true);
+
+ // These assert that the DDoS protection actually benefits the
+ // server. Manual inspection of the traffic graphs will show this
+ // even more clearly.
+ EXPECT_GT(unprotected_server.num_overloaded_ticks(),
+ protected_server.num_overloaded_ticks());
+ EXPECT_GT(unprotected_server.max_experienced_queries_per_tick(),
+ protected_server.max_experienced_queries_per_tick());
+
+ // These assert that the DDoS protection actually benefits non-malicious
+ // (and non-degenerate/accidentally DDoSing) users.
+ EXPECT_LT(protected_client_results.GetBlockedRatio(),
+ protected_attacker_results.GetBlockedRatio());
+ EXPECT_GT(protected_client_results.GetSuccessRatio(),
+ unprotected_client_results.GetSuccessRatio());
+
+ // The rest is just for optional manual evaluation of the results;
+ // in particular the traffic pattern is interesting.
+
+ VerboseOut("\nUnprotected server's results:\n\n");
+ VerboseOut(unprotected_server.VisualizeASCII(132).c_str());
+ VerboseOut("\n\n");
+ VerboseOut("Protected server's results:\n\n");
+ VerboseOut(protected_server.VisualizeASCII(132).c_str());
+ VerboseOut("\n\n");
+
+ unprotected_attacker_results.PrintResults(
+ "attackers attacking unprotected server.");
+ unprotected_client_results.PrintResults(
+ "normal clients making requests to unprotected server.");
+ protected_attacker_results.PrintResults(
+ "attackers attacking protected server.");
+ protected_client_results.PrintResults(
+ "normal clients making requests to protected server.");
+}
+
+// Returns the downtime perceived by the client, as a ratio of the
+// actual downtime.
+double SimulateDowntime(const TimeDelta& duration,
+ const TimeDelta& average_client_interval,
+ bool enable_throttling) {
+ TimeDelta time_between_ticks = duration / 200;
+ TimeTicks start_downtime = TimeTicks() + (duration / 2);
+
+ // A server that never rejects requests, but will go down for maintenance.
+ Server server(std::numeric_limits<int>::max(), 1.0);
+ server.SetDowntime(start_downtime, duration);
+
+ ExtensionThrottleManager manager;
+ scoped_refptr<MockExtensionThrottleEntry> throttler_entry(
+ new MockExtensionThrottleEntry(&manager));
+ if (!enable_throttling)
+ throttler_entry->DisableBackoffThrottling();
+
+ Requester requester(throttler_entry.get(), average_client_interval, &server,
+ NULL);
+ requester.SetStartupJitter(duration / 3);
+ requester.SetRequestJitter(average_client_interval);
+
+ DiscreteTimeSimulation simulation;
+ simulation.AddActor(&requester);
+ simulation.AddActor(&server);
+
+ simulation.RunSimulation(duration * 2, time_between_ticks);
+
+ return static_cast<double>(
+ requester.last_downtime_duration().InMilliseconds()) /
+ static_cast<double>(duration.InMilliseconds());
+}
+
+TEST(URLRequestThrottlerSimulation, PerceivedDowntimeRatio) {
+ base::MessageLoopForIO message_loop;
+ struct Stats {
+ // Expected interval that we expect the ratio of downtime when anti-DDoS
+ // is enabled and downtime when anti-DDoS is not enabled to fall within.
+ //
+ // The expected interval depends on two things: The exponential back-off
+ // policy encoded in ExtensionThrottleEntry, and the test or set of
+ // tests that the Stats object is tracking (e.g. a test where the client
+ // retries very rapidly on a very long downtime will tend to increase the
+ // number).
+ //
+ // To determine an appropriate new interval when parameters have changed,
+ // run the test a few times (you may have to Ctrl-C out of it after a few
+ // seconds) and choose an interval that the test converges quickly and
+ // reliably to. Then set the new interval, and run the test e.g. 20 times
+ // in succession to make sure it never takes an obscenely long time to
+ // converge to this interval.
+ double expected_min_increase;
+ double expected_max_increase;
+
+ size_t num_runs;
+ double total_ratio_unprotected;
+ double total_ratio_protected;
+
+ bool DidConverge(double* increase_ratio_out) {
+ double unprotected_ratio = total_ratio_unprotected / num_runs;
+ double protected_ratio = total_ratio_protected / num_runs;
+ double increase_ratio = protected_ratio / unprotected_ratio;
+ if (increase_ratio_out)
+ *increase_ratio_out = increase_ratio;
+ return expected_min_increase <= increase_ratio &&
+ increase_ratio <= expected_max_increase;
+ }
+
+ void ReportTrialResult(double increase_ratio) {
+ VerboseOut(
+ " Perceived downtime with throttling is %.4f times without.\n",
+ increase_ratio);
+ VerboseOut(" Test result after %d trials.\n", num_runs);
+ }
+ };
+
+ Stats global_stats = {1.08, 1.15};
+
+ struct Trial {
+ TimeDelta duration;
+ TimeDelta average_client_interval;
+ Stats stats;
+
+ void PrintTrialDescription() {
+ double duration_minutes =
+ static_cast<double>(duration.InSeconds()) / 60.0;
+ double interval_minutes =
+ static_cast<double>(average_client_interval.InSeconds()) / 60.0;
+ VerboseOut("Trial with %.2f min downtime, avg. interval %.2f min.\n",
+ duration_minutes, interval_minutes);
+ }
+ };
+
+ // We don't set or check expected ratio intervals on individual
+ // experiments as this might make the test too fragile, but we
+ // print them out at the end for manual evaluation (we want to be
+ // able to make claims about the expected ratios depending on the
+ // type of behavior of the client and the downtime, e.g. the difference
+ // in behavior between a client making requests every few minutes vs.
+ // one that makes a request every 15 seconds).
+ Trial trials[] = {
+ {TimeDelta::FromSeconds(10), TimeDelta::FromSeconds(3)},
+ {TimeDelta::FromSeconds(30), TimeDelta::FromSeconds(7)},
+ {TimeDelta::FromMinutes(5), TimeDelta::FromSeconds(30)},
+ {TimeDelta::FromMinutes(10), TimeDelta::FromSeconds(20)},
+ {TimeDelta::FromMinutes(20), TimeDelta::FromSeconds(15)},
+ {TimeDelta::FromMinutes(20), TimeDelta::FromSeconds(50)},
+ {TimeDelta::FromMinutes(30), TimeDelta::FromMinutes(2)},
+ {TimeDelta::FromMinutes(30), TimeDelta::FromMinutes(5)},
+ {TimeDelta::FromMinutes(40), TimeDelta::FromMinutes(7)},
+ {TimeDelta::FromMinutes(40), TimeDelta::FromMinutes(2)},
+ {TimeDelta::FromMinutes(40), TimeDelta::FromSeconds(15)},
+ {TimeDelta::FromMinutes(60), TimeDelta::FromMinutes(7)},
+ {TimeDelta::FromMinutes(60), TimeDelta::FromMinutes(2)},
+ {TimeDelta::FromMinutes(60), TimeDelta::FromSeconds(15)},
+ {TimeDelta::FromMinutes(80), TimeDelta::FromMinutes(20)},
+ {TimeDelta::FromMinutes(80), TimeDelta::FromMinutes(3)},
+ {TimeDelta::FromMinutes(80), TimeDelta::FromSeconds(15)},
+
+ // Most brutal?
+ {TimeDelta::FromMinutes(45), TimeDelta::FromMilliseconds(500)},
+ };
+
+ // If things don't converge by the time we've done 100K trials, then
+ // clearly one or more of the expected intervals are wrong.
+ while (global_stats.num_runs < 100000) {
+ for (size_t i = 0; i < arraysize(trials); ++i) {
+ ++global_stats.num_runs;
+ ++trials[i].stats.num_runs;
+ double ratio_unprotected = SimulateDowntime(
+ trials[i].duration, trials[i].average_client_interval, false);
+ double ratio_protected = SimulateDowntime(
+ trials[i].duration, trials[i].average_client_interval, true);
+ global_stats.total_ratio_unprotected += ratio_unprotected;
+ global_stats.total_ratio_protected += ratio_protected;
+ trials[i].stats.total_ratio_unprotected += ratio_unprotected;
+ trials[i].stats.total_ratio_protected += ratio_protected;
+ }
+
+ double increase_ratio;
+ if (global_stats.DidConverge(&increase_ratio))
+ break;
+
+ if (global_stats.num_runs > 200) {
+ VerboseOut("Test has not yet converged on expected interval.\n");
+ global_stats.ReportTrialResult(increase_ratio);
+ }
+ }
+
+ double average_increase_ratio;
+ EXPECT_TRUE(global_stats.DidConverge(&average_increase_ratio));
+
+ // Print individual trial results for optional manual evaluation.
+ double max_increase_ratio = 0.0;
+ for (size_t i = 0; i < arraysize(trials); ++i) {
+ double increase_ratio;
+ trials[i].stats.DidConverge(&increase_ratio);
+ max_increase_ratio = std::max(max_increase_ratio, increase_ratio);
+ trials[i].PrintTrialDescription();
+ trials[i].stats.ReportTrialResult(increase_ratio);
+ }
+
+ VerboseOut("Average increase ratio was %.4f\n", average_increase_ratio);
+ VerboseOut("Maximum increase ratio was %.4f\n", max_increase_ratio);
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_throttle_test_support.cc b/chromium/extensions/browser/extension_throttle_test_support.cc
new file mode 100644
index 00000000000..d5a522891e5
--- /dev/null
+++ b/chromium/extensions/browser/extension_throttle_test_support.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_throttle_test_support.h"
+
+namespace extensions {
+
+TestTickClock::TestTickClock() {
+}
+
+TestTickClock::TestTickClock(base::TimeTicks now) : now_ticks_(now) {
+}
+
+TestTickClock::~TestTickClock() {
+}
+
+base::TimeTicks TestTickClock::NowTicks() {
+ return now_ticks_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_throttle_test_support.h b/chromium/extensions/browser/extension_throttle_test_support.h
new file mode 100644
index 00000000000..a6fde034924
--- /dev/null
+++ b/chromium/extensions/browser/extension_throttle_test_support.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_THROTTLE_TEST_SUPPORT_H_
+#define EXTENSIONS_BROWSER_EXTENSION_THROTTLE_TEST_SUPPORT_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "net/base/backoff_entry.h"
+
+namespace extensions {
+
+class TestTickClock : public base::TickClock {
+ public:
+ TestTickClock();
+ explicit TestTickClock(base::TimeTicks now);
+ ~TestTickClock() override;
+
+ base::TimeTicks NowTicks() override;
+ void set_now(base::TimeTicks now) { now_ticks_ = now; }
+
+ private:
+ base::TimeTicks now_ticks_;
+ DISALLOW_COPY_AND_ASSIGN(TestTickClock);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_THROTTLE_TEST_SUPPORT_H_
diff --git a/chromium/extensions/browser/extension_throttle_unittest.cc b/chromium/extensions/browser/extension_throttle_unittest.cc
new file mode 100644
index 00000000000..e894ee68367
--- /dev/null
+++ b/chromium/extensions/browser/extension_throttle_unittest.cc
@@ -0,0 +1,470 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/pickle.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "extensions/browser/extension_throttle_entry.h"
+#include "extensions/browser/extension_throttle_manager.h"
+#include "extensions/browser/extension_throttle_test_support.h"
+#include "net/base/load_flags.h"
+#include "net/base/request_priority.h"
+#include "net/base/test_completion_callback.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+using net::BackoffEntry;
+using net::NetworkChangeNotifier;
+using net::TestNetworkDelegate;
+using net::TestURLRequestContext;
+using net::URLRequest;
+using net::URLRequestContext;
+
+namespace extensions {
+
+namespace {
+
+class MockExtensionThrottleEntry : public ExtensionThrottleEntry {
+ public:
+ explicit MockExtensionThrottleEntry(ExtensionThrottleManager* manager)
+ : ExtensionThrottleEntry(manager, std::string()),
+ backoff_entry_(&backoff_policy_, &fake_clock_) {
+ InitPolicy();
+ }
+ MockExtensionThrottleEntry(ExtensionThrottleManager* manager,
+ const TimeTicks& exponential_backoff_release_time,
+ const TimeTicks& sliding_window_release_time,
+ const TimeTicks& fake_now)
+ : ExtensionThrottleEntry(manager, std::string()),
+ fake_clock_(fake_now),
+ backoff_entry_(&backoff_policy_, &fake_clock_) {
+ InitPolicy();
+
+ set_exponential_backoff_release_time(exponential_backoff_release_time);
+ set_sliding_window_release_time(sliding_window_release_time);
+ }
+
+ void InitPolicy() {
+ // Some tests become flaky if we have jitter.
+ backoff_policy_.jitter_factor = 0.0;
+
+ // This lets us avoid having to make multiple failures initially (this
+ // logic is already tested in the BackoffEntry unit tests).
+ backoff_policy_.num_errors_to_ignore = 0;
+ }
+
+ const BackoffEntry* GetBackoffEntry() const override {
+ return &backoff_entry_;
+ }
+
+ BackoffEntry* GetBackoffEntry() override { return &backoff_entry_; }
+
+ static bool ExplicitUserRequest(int load_flags) {
+ return ExtensionThrottleEntry::ExplicitUserRequest(load_flags);
+ }
+
+ void ResetToBlank(const TimeTicks& time_now) {
+ fake_clock_.set_now(time_now);
+
+ GetBackoffEntry()->Reset();
+ set_sliding_window_release_time(time_now);
+ }
+
+ // Overridden for tests.
+ TimeTicks ImplGetTimeNow() const override { return fake_clock_.NowTicks(); }
+
+ void set_fake_now(const TimeTicks& now) { fake_clock_.set_now(now); }
+
+ void set_exponential_backoff_release_time(const TimeTicks& release_time) {
+ GetBackoffEntry()->SetCustomReleaseTime(release_time);
+ }
+
+ TimeTicks sliding_window_release_time() const {
+ return ExtensionThrottleEntry::sliding_window_release_time();
+ }
+
+ void set_sliding_window_release_time(const TimeTicks& release_time) {
+ ExtensionThrottleEntry::set_sliding_window_release_time(release_time);
+ }
+
+ protected:
+ ~MockExtensionThrottleEntry() override {}
+
+ private:
+ mutable TestTickClock fake_clock_;
+ BackoffEntry backoff_entry_;
+};
+
+class MockExtensionThrottleManager : public ExtensionThrottleManager {
+ public:
+ MockExtensionThrottleManager() : create_entry_index_(0) {}
+
+ // Method to process the URL using ExtensionThrottleManager protected
+ // method.
+ std::string DoGetUrlIdFromUrl(const GURL& url) { return GetIdFromUrl(url); }
+
+ // Method to use the garbage collecting method of ExtensionThrottleManager.
+ void DoGarbageCollectEntries() { GarbageCollectEntries(); }
+
+ // Returns the number of entries in the map.
+ int GetNumberOfEntries() const { return GetNumberOfEntriesForTests(); }
+
+ void CreateEntry(bool is_outdated) {
+ TimeTicks time = TimeTicks::Now();
+ if (is_outdated) {
+ time -= TimeDelta::FromMilliseconds(
+ MockExtensionThrottleEntry::kDefaultEntryLifetimeMs + 1000);
+ }
+ std::string fake_url_string("http://www.fakeurl.com/");
+ fake_url_string.append(base::IntToString(create_entry_index_++));
+ GURL fake_url(fake_url_string);
+ OverrideEntryForTests(
+ fake_url, new MockExtensionThrottleEntry(this, time, TimeTicks::Now(),
+ TimeTicks::Now()));
+ }
+
+ private:
+ int create_entry_index_;
+};
+
+struct TimeAndBool {
+ TimeAndBool(const TimeTicks& time_value, bool expected, int line_num) {
+ time = time_value;
+ result = expected;
+ line = line_num;
+ }
+ TimeTicks time;
+ bool result;
+ int line;
+};
+
+struct GurlAndString {
+ GurlAndString(const GURL& url_value,
+ const std::string& expected,
+ int line_num) {
+ url = url_value;
+ result = expected;
+ line = line_num;
+ }
+ GURL url;
+ std::string result;
+ int line;
+};
+
+} // namespace
+
+class ExtensionThrottleEntryTest : public testing::Test {
+ protected:
+ ExtensionThrottleEntryTest()
+ : request_(context_.CreateRequest(GURL(), net::DEFAULT_PRIORITY, NULL)) {}
+
+ void SetUp() override;
+
+ TimeTicks now_;
+ MockExtensionThrottleManager manager_; // Dummy object, not used.
+ scoped_refptr<MockExtensionThrottleEntry> entry_;
+ base::MessageLoopForIO message_loop_;
+
+ TestURLRequestContext context_;
+ scoped_ptr<URLRequest> request_;
+};
+
+void ExtensionThrottleEntryTest::SetUp() {
+ request_->SetLoadFlags(0);
+
+ now_ = TimeTicks::Now();
+ entry_ = new MockExtensionThrottleEntry(&manager_);
+ entry_->ResetToBlank(now_);
+}
+
+std::ostream& operator<<(std::ostream& out, const base::TimeTicks& time) {
+ return out << time.ToInternalValue();
+}
+
+TEST_F(ExtensionThrottleEntryTest, CanThrottleRequest) {
+ entry_->set_exponential_backoff_release_time(entry_->ImplGetTimeNow() +
+ TimeDelta::FromMilliseconds(1));
+
+ EXPECT_TRUE(entry_->ShouldRejectRequest(*request_));
+}
+
+TEST_F(ExtensionThrottleEntryTest, InterfaceDuringExponentialBackoff) {
+ entry_->set_exponential_backoff_release_time(entry_->ImplGetTimeNow() +
+ TimeDelta::FromMilliseconds(1));
+ EXPECT_TRUE(entry_->ShouldRejectRequest(*request_));
+
+ // Also end-to-end test the load flags exceptions.
+ request_->SetLoadFlags(net::LOAD_MAYBE_USER_GESTURE);
+ EXPECT_FALSE(entry_->ShouldRejectRequest(*request_));
+}
+
+TEST_F(ExtensionThrottleEntryTest, InterfaceNotDuringExponentialBackoff) {
+ entry_->set_exponential_backoff_release_time(entry_->ImplGetTimeNow());
+ EXPECT_FALSE(entry_->ShouldRejectRequest(*request_));
+ entry_->set_exponential_backoff_release_time(entry_->ImplGetTimeNow() -
+ TimeDelta::FromMilliseconds(1));
+ EXPECT_FALSE(entry_->ShouldRejectRequest(*request_));
+}
+
+TEST_F(ExtensionThrottleEntryTest, InterfaceUpdateFailure) {
+ entry_->UpdateWithResponse(503);
+ EXPECT_GT(entry_->GetExponentialBackoffReleaseTime(),
+ entry_->ImplGetTimeNow())
+ << "A failure should increase the release_time";
+}
+
+TEST_F(ExtensionThrottleEntryTest, InterfaceUpdateSuccess) {
+ entry_->UpdateWithResponse(200);
+ EXPECT_EQ(entry_->GetExponentialBackoffReleaseTime(),
+ entry_->ImplGetTimeNow())
+ << "A success should not add any delay";
+}
+
+TEST_F(ExtensionThrottleEntryTest, InterfaceUpdateSuccessThenFailure) {
+ entry_->UpdateWithResponse(200);
+ entry_->UpdateWithResponse(503);
+ EXPECT_GT(entry_->GetExponentialBackoffReleaseTime(),
+ entry_->ImplGetTimeNow())
+ << "This scenario should add delay";
+ entry_->UpdateWithResponse(200);
+}
+
+TEST_F(ExtensionThrottleEntryTest, IsEntryReallyOutdated) {
+ TimeDelta lifetime = TimeDelta::FromMilliseconds(
+ MockExtensionThrottleEntry::kDefaultEntryLifetimeMs);
+ const TimeDelta kFiveMs = TimeDelta::FromMilliseconds(5);
+
+ TimeAndBool test_values[] = {
+ TimeAndBool(now_, false, __LINE__),
+ TimeAndBool(now_ - kFiveMs, false, __LINE__),
+ TimeAndBool(now_ + kFiveMs, false, __LINE__),
+ TimeAndBool(now_ - (lifetime - kFiveMs), false, __LINE__),
+ TimeAndBool(now_ - lifetime, true, __LINE__),
+ TimeAndBool(now_ - (lifetime + kFiveMs), true, __LINE__)};
+
+ for (unsigned int i = 0; i < arraysize(test_values); ++i) {
+ entry_->set_exponential_backoff_release_time(test_values[i].time);
+ EXPECT_EQ(entry_->IsEntryOutdated(), test_values[i].result)
+ << "Test case #" << i << " line " << test_values[i].line << " failed";
+ }
+}
+
+TEST_F(ExtensionThrottleEntryTest, MaxAllowedBackoff) {
+ for (int i = 0; i < 30; ++i) {
+ entry_->UpdateWithResponse(503);
+ }
+
+ TimeDelta delay = entry_->GetExponentialBackoffReleaseTime() - now_;
+ EXPECT_EQ(delay.InMilliseconds(),
+ MockExtensionThrottleEntry::kDefaultMaximumBackoffMs);
+}
+
+TEST_F(ExtensionThrottleEntryTest, MalformedContent) {
+ for (int i = 0; i < 5; ++i)
+ entry_->UpdateWithResponse(503);
+
+ TimeTicks release_after_failures = entry_->GetExponentialBackoffReleaseTime();
+
+ // Inform the entry that a response body was malformed, which is supposed to
+ // increase the back-off time. Note that we also submit a successful
+ // UpdateWithResponse to pair with ReceivedContentWasMalformed() since that
+ // is what happens in practice (if a body is received, then a non-500
+ // response must also have been received).
+ entry_->ReceivedContentWasMalformed(200);
+ entry_->UpdateWithResponse(200);
+ EXPECT_GT(entry_->GetExponentialBackoffReleaseTime(), release_after_failures);
+}
+
+TEST_F(ExtensionThrottleEntryTest, SlidingWindow) {
+ int max_send = ExtensionThrottleEntry::kDefaultMaxSendThreshold;
+ int sliding_window = ExtensionThrottleEntry::kDefaultSlidingWindowPeriodMs;
+
+ TimeTicks time_1 = entry_->ImplGetTimeNow() +
+ TimeDelta::FromMilliseconds(sliding_window / 3);
+ TimeTicks time_2 = entry_->ImplGetTimeNow() +
+ TimeDelta::FromMilliseconds(2 * sliding_window / 3);
+ TimeTicks time_3 =
+ entry_->ImplGetTimeNow() + TimeDelta::FromMilliseconds(sliding_window);
+ TimeTicks time_4 =
+ entry_->ImplGetTimeNow() +
+ TimeDelta::FromMilliseconds(sliding_window + 2 * sliding_window / 3);
+
+ entry_->set_exponential_backoff_release_time(time_1);
+
+ for (int i = 0; i < max_send / 2; ++i) {
+ EXPECT_EQ(2 * sliding_window / 3,
+ entry_->ReserveSendingTimeForNextRequest(time_2));
+ }
+ EXPECT_EQ(time_2, entry_->sliding_window_release_time());
+
+ entry_->set_fake_now(time_3);
+
+ for (int i = 0; i < (max_send + 1) / 2; ++i)
+ EXPECT_EQ(0, entry_->ReserveSendingTimeForNextRequest(TimeTicks()));
+
+ EXPECT_EQ(time_4, entry_->sliding_window_release_time());
+}
+
+TEST_F(ExtensionThrottleEntryTest, ExplicitUserRequest) {
+ ASSERT_FALSE(MockExtensionThrottleEntry::ExplicitUserRequest(0));
+ ASSERT_TRUE(MockExtensionThrottleEntry::ExplicitUserRequest(
+ net::LOAD_MAYBE_USER_GESTURE));
+ ASSERT_FALSE(MockExtensionThrottleEntry::ExplicitUserRequest(
+ ~net::LOAD_MAYBE_USER_GESTURE));
+}
+
+class ExtensionThrottleManagerTest : public testing::Test {
+ protected:
+ ExtensionThrottleManagerTest()
+ : request_(context_.CreateRequest(GURL(), net::DEFAULT_PRIORITY, NULL)) {}
+
+ void SetUp() override { request_->SetLoadFlags(0); }
+
+ void ExpectEntryAllowsAllOnErrorIfOptedOut(
+ ExtensionThrottleEntryInterface* entry,
+ bool opted_out,
+ const URLRequest& request) {
+ EXPECT_FALSE(entry->ShouldRejectRequest(request));
+ for (int i = 0; i < 10; ++i) {
+ entry->UpdateWithResponse(503);
+ }
+ EXPECT_NE(opted_out, entry->ShouldRejectRequest(request));
+
+ if (opted_out) {
+ // We're not mocking out GetTimeNow() in this scenario
+ // so add a 100 ms buffer to avoid flakiness (that should always
+ // give enough time to get from the TimeTicks::Now() call here
+ // to the TimeTicks::Now() call in the entry class).
+ EXPECT_GT(TimeTicks::Now() + TimeDelta::FromMilliseconds(100),
+ entry->GetExponentialBackoffReleaseTime());
+ } else {
+ // As above, add 100 ms.
+ EXPECT_LT(TimeTicks::Now() + TimeDelta::FromMilliseconds(100),
+ entry->GetExponentialBackoffReleaseTime());
+ }
+ }
+
+ base::MessageLoopForIO message_loop_;
+ // context_ must be declared before request_.
+ TestURLRequestContext context_;
+ scoped_ptr<URLRequest> request_;
+};
+
+TEST_F(ExtensionThrottleManagerTest, IsUrlStandardised) {
+ MockExtensionThrottleManager manager;
+ GurlAndString test_values[] = {
+ GurlAndString(GURL("http://www.example.com"),
+ std::string("http://www.example.com/"), __LINE__),
+ GurlAndString(GURL("http://www.Example.com"),
+ std::string("http://www.example.com/"), __LINE__),
+ GurlAndString(GURL("http://www.ex4mple.com/Pr4c71c41"),
+ std::string("http://www.ex4mple.com/pr4c71c41"), __LINE__),
+ GurlAndString(GURL("http://www.example.com/0/token/false"),
+ std::string("http://www.example.com/0/token/false"),
+ __LINE__),
+ GurlAndString(GURL("http://www.example.com/index.php?code=javascript"),
+ std::string("http://www.example.com/index.php"), __LINE__),
+ GurlAndString(GURL("http://www.example.com/index.php?code=1#superEntry"),
+ std::string("http://www.example.com/index.php"), __LINE__),
+ GurlAndString(GURL("http://www.example.com/index.php#superEntry"),
+ std::string("http://www.example.com/index.php"), __LINE__),
+ GurlAndString(GURL("http://www.example.com:1234/"),
+ std::string("http://www.example.com:1234/"), __LINE__)};
+
+ for (unsigned int i = 0; i < arraysize(test_values); ++i) {
+ std::string temp = manager.DoGetUrlIdFromUrl(test_values[i].url);
+ EXPECT_EQ(temp, test_values[i].result) << "Test case #" << i << " line "
+ << test_values[i].line << " failed";
+ }
+}
+
+TEST_F(ExtensionThrottleManagerTest, AreEntriesBeingCollected) {
+ MockExtensionThrottleManager manager;
+
+ manager.CreateEntry(true); // true = Entry is outdated.
+ manager.CreateEntry(true);
+ manager.CreateEntry(true);
+ manager.DoGarbageCollectEntries();
+ EXPECT_EQ(0, manager.GetNumberOfEntries());
+
+ manager.CreateEntry(false);
+ manager.CreateEntry(false);
+ manager.CreateEntry(false);
+ manager.CreateEntry(true);
+ manager.DoGarbageCollectEntries();
+ EXPECT_EQ(3, manager.GetNumberOfEntries());
+}
+
+TEST_F(ExtensionThrottleManagerTest, IsHostBeingRegistered) {
+ MockExtensionThrottleManager manager;
+
+ manager.RegisterRequestUrl(GURL("http://www.example.com/"));
+ manager.RegisterRequestUrl(GURL("http://www.google.com/"));
+ manager.RegisterRequestUrl(GURL("http://www.google.com/index/0"));
+ manager.RegisterRequestUrl(GURL("http://www.google.com/index/0?code=1"));
+ manager.RegisterRequestUrl(GURL("http://www.google.com/index/0#lolsaure"));
+
+ EXPECT_EQ(3, manager.GetNumberOfEntries());
+}
+
+TEST_F(ExtensionThrottleManagerTest, LocalHostOptedOut) {
+ MockExtensionThrottleManager manager;
+ // A localhost entry should always be opted out.
+ scoped_refptr<ExtensionThrottleEntryInterface> localhost_entry =
+ manager.RegisterRequestUrl(GURL("http://localhost/hello"));
+ EXPECT_FALSE(localhost_entry->ShouldRejectRequest((*request_)));
+ for (int i = 0; i < 10; ++i) {
+ localhost_entry->UpdateWithResponse(503);
+ }
+ EXPECT_FALSE(localhost_entry->ShouldRejectRequest((*request_)));
+
+ // We're not mocking out GetTimeNow() in this scenario
+ // so add a 100 ms buffer to avoid flakiness (that should always
+ // give enough time to get from the TimeTicks::Now() call here
+ // to the TimeTicks::Now() call in the entry class).
+ EXPECT_GT(TimeTicks::Now() + TimeDelta::FromMilliseconds(100),
+ localhost_entry->GetExponentialBackoffReleaseTime());
+}
+
+TEST_F(ExtensionThrottleManagerTest, ClearOnNetworkChange) {
+ for (int i = 0; i < 3; ++i) {
+ MockExtensionThrottleManager manager;
+ scoped_refptr<ExtensionThrottleEntryInterface> entry_before =
+ manager.RegisterRequestUrl(GURL("http://www.example.com/"));
+ for (int j = 0; j < 10; ++j) {
+ entry_before->UpdateWithResponse(503);
+ }
+ EXPECT_TRUE(entry_before->ShouldRejectRequest(*request_));
+
+ switch (i) {
+ case 0:
+ manager.OnIPAddressChanged();
+ break;
+ case 1:
+ manager.OnConnectionTypeChanged(
+ NetworkChangeNotifier::CONNECTION_UNKNOWN);
+ break;
+ case 2:
+ manager.OnConnectionTypeChanged(NetworkChangeNotifier::CONNECTION_NONE);
+ break;
+ default:
+ FAIL();
+ }
+
+ scoped_refptr<ExtensionThrottleEntryInterface> entry_after =
+ manager.RegisterRequestUrl(GURL("http://www.example.com/"));
+ EXPECT_FALSE(entry_after->ShouldRejectRequest(*request_));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_user_script_loader.cc b/chromium/extensions/browser/extension_user_script_loader.cc
new file mode 100644
index 00000000000..3b3f10cea83
--- /dev/null
+++ b/chromium/extensions/browser/extension_user_script_loader.cc
@@ -0,0 +1,239 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_user_script_loader.h"
+
+#include <stddef.h>
+
+#include <set>
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/version.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/render_process_host.h"
+#include "extensions/browser/component_extension_resource_manager.h"
+#include "extensions/browser/content_verifier.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/common/file_util.h"
+#include "extensions/common/manifest_handlers/default_locale_handler.h"
+#include "extensions/common/message_bundle.h"
+#include "extensions/common/one_shot_event.h"
+#include "ui/base/resource/resource_bundle.h"
+
+using content::BrowserContext;
+
+namespace extensions {
+
+namespace {
+
+using SubstitutionMap = std::map<std::string, std::string>;
+
+// Verifies file contents as they are read.
+void VerifyContent(const scoped_refptr<ContentVerifier>& verifier,
+ const std::string& extension_id,
+ const base::FilePath& extension_root,
+ const base::FilePath& relative_path,
+ const std::string& content) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+ scoped_refptr<ContentVerifyJob> job(
+ verifier->CreateJobFor(extension_id, extension_root, relative_path));
+ if (job.get()) {
+ job->Start();
+ job->BytesRead(content.size(), content.data());
+ job->DoneReading();
+ }
+}
+
+// Loads user scripts from the extension who owns these scripts.
+bool LoadScriptContent(const HostID& host_id,
+ UserScript::File* script_file,
+ const SubstitutionMap* localization_messages,
+ const scoped_refptr<ContentVerifier>& verifier) {
+ DCHECK(script_file);
+ std::string content;
+ const base::FilePath& path = ExtensionResource::GetFilePath(
+ script_file->extension_root(), script_file->relative_path(),
+ ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT);
+ if (path.empty()) {
+ int resource_id = 0;
+ if (ExtensionsBrowserClient::Get()
+ ->GetComponentExtensionResourceManager()
+ ->IsComponentExtensionResource(script_file->extension_root(),
+ script_file->relative_path(),
+ &resource_id)) {
+ const ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ content = rb.GetRawDataResource(resource_id).as_string();
+ } else {
+ LOG(WARNING) << "Failed to get file path to "
+ << script_file->relative_path().value() << " from "
+ << script_file->extension_root().value();
+ return false;
+ }
+ } else {
+ if (!base::ReadFileToString(path, &content)) {
+ LOG(WARNING) << "Failed to load user script file: " << path.value();
+ return false;
+ }
+ if (verifier.get()) {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO, FROM_HERE,
+ base::Bind(&VerifyContent, verifier, host_id.id(),
+ script_file->extension_root(),
+ script_file->relative_path(), content));
+ }
+ }
+
+ // Localize the content.
+ if (localization_messages) {
+ std::string error;
+ MessageBundle::ReplaceMessagesWithExternalDictionary(*localization_messages,
+ &content, &error);
+ if (!error.empty())
+ LOG(WARNING) << "Failed to replace messages in script: " << error;
+ }
+
+ // Remove BOM from the content.
+ std::string::size_type index = content.find(base::kUtf8ByteOrderMark);
+ if (index == 0)
+ script_file->set_content(content.substr(strlen(base::kUtf8ByteOrderMark)));
+ else
+ script_file->set_content(content);
+
+ return true;
+}
+
+SubstitutionMap* GetLocalizationMessages(
+ const ExtensionUserScriptLoader::HostsInfo& hosts_info,
+ const HostID& host_id) {
+ ExtensionUserScriptLoader::HostsInfo::const_iterator iter =
+ hosts_info.find(host_id);
+ if (iter == hosts_info.end())
+ return nullptr;
+ return file_util::LoadMessageBundleSubstitutionMap(
+ iter->second.first, host_id.id(), iter->second.second);
+}
+
+void LoadUserScripts(UserScriptList* user_scripts,
+ const ExtensionUserScriptLoader::HostsInfo& hosts_info,
+ const std::set<int>& added_script_ids,
+ const scoped_refptr<ContentVerifier>& verifier) {
+ for (UserScript& script : *user_scripts) {
+ if (added_script_ids.count(script.id()) == 0)
+ continue;
+ scoped_ptr<SubstitutionMap> localization_messages(
+ GetLocalizationMessages(hosts_info, script.host_id()));
+ for (UserScript::File& script_file : script.js_scripts()) {
+ if (script_file.GetContent().empty())
+ LoadScriptContent(script.host_id(), &script_file, nullptr, verifier);
+ }
+ for (UserScript::File& script_file : script.css_scripts()) {
+ if (script_file.GetContent().empty())
+ LoadScriptContent(script.host_id(), &script_file,
+ localization_messages.get(), verifier);
+ }
+ }
+}
+
+void LoadScriptsOnFileThread(
+ scoped_ptr<UserScriptList> user_scripts,
+ const ExtensionUserScriptLoader::HostsInfo& hosts_info,
+ const std::set<int>& added_script_ids,
+ const scoped_refptr<ContentVerifier>& verifier,
+ UserScriptLoader::LoadScriptsCallback callback) {
+ DCHECK(user_scripts.get());
+ LoadUserScripts(user_scripts.get(), hosts_info, added_script_ids, verifier);
+ scoped_ptr<base::SharedMemory> memory =
+ UserScriptLoader::Serialize(*user_scripts);
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI, FROM_HERE,
+ base::Bind(callback, base::Passed(&user_scripts), base::Passed(&memory)));
+}
+
+} // namespace
+
+ExtensionUserScriptLoader::ExtensionUserScriptLoader(
+ BrowserContext* browser_context,
+ const HostID& host_id,
+ bool listen_for_extension_system_loaded)
+ : UserScriptLoader(browser_context, host_id),
+ content_verifier_(
+ ExtensionSystem::Get(browser_context)->content_verifier()),
+ extension_registry_observer_(this),
+ weak_factory_(this) {
+ extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context));
+ if (listen_for_extension_system_loaded) {
+ ExtensionSystem::Get(browser_context)
+ ->ready()
+ .Post(FROM_HERE,
+ base::Bind(&ExtensionUserScriptLoader::OnExtensionSystemReady,
+ weak_factory_.GetWeakPtr()));
+ } else {
+ SetReady(true);
+ }
+}
+
+ExtensionUserScriptLoader::~ExtensionUserScriptLoader() {
+}
+
+void ExtensionUserScriptLoader::LoadScriptsForTest(
+ UserScriptList* user_scripts) {
+ HostsInfo info;
+ std::set<int> added_script_ids;
+ for (UserScript& script : *user_scripts)
+ added_script_ids.insert(script.id());
+
+ LoadUserScripts(user_scripts, info, added_script_ids,
+ nullptr /* no verifier for testing */);
+}
+
+void ExtensionUserScriptLoader::LoadScripts(
+ scoped_ptr<UserScriptList> user_scripts,
+ const std::set<HostID>& changed_hosts,
+ const std::set<int>& added_script_ids,
+ LoadScriptsCallback callback) {
+ UpdateHostsInfo(changed_hosts);
+
+ content::BrowserThread::PostTask(
+ content::BrowserThread::FILE, FROM_HERE,
+ base::Bind(&LoadScriptsOnFileThread, base::Passed(&user_scripts),
+ hosts_info_, added_script_ids, content_verifier_, callback));
+}
+
+void ExtensionUserScriptLoader::UpdateHostsInfo(
+ const std::set<HostID>& changed_hosts) {
+ ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
+ for (const HostID& host_id : changed_hosts) {
+ const Extension* extension =
+ registry->GetExtensionById(host_id.id(), ExtensionRegistry::ENABLED);
+ // |changed_hosts_| may include hosts that have been removed,
+ // which leads to the above lookup failing. In this case, just continue.
+ if (!extension)
+ continue;
+ if (hosts_info_.find(host_id) != hosts_info_.end())
+ continue;
+ hosts_info_[host_id] = ExtensionSet::ExtensionPathAndDefaultLocale(
+ extension->path(), LocaleInfo::GetDefaultLocale(extension));
+ }
+}
+
+void ExtensionUserScriptLoader::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ hosts_info_.erase(HostID(HostID::EXTENSIONS, extension->id()));
+}
+
+void ExtensionUserScriptLoader::OnExtensionSystemReady() {
+ SetReady(true);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_user_script_loader.h b/chromium/extensions/browser/extension_user_script_loader.h
new file mode 100644
index 00000000000..ff785fcc6ba
--- /dev/null
+++ b/chromium/extensions/browser/extension_user_script_loader.h
@@ -0,0 +1,77 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_USER_SCRIPT_LOADER_H_
+#define EXTENSIONS_BROWSER_EXTENSION_USER_SCRIPT_LOADER_H_
+
+#include "base/macros.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/browser/user_script_loader.h"
+#include "extensions/common/extension.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class ContentVerifier;
+class ExtensionRegistry;
+
+// UserScriptLoader for extensions.
+class ExtensionUserScriptLoader : public UserScriptLoader,
+ public ExtensionRegistryObserver {
+ public:
+ using PathAndDefaultLocale = std::pair<base::FilePath, std::string>;
+ using HostsInfo = std::map<HostID, PathAndDefaultLocale>;
+
+ // The listen_for_extension_system_loaded is only set true when initilizing
+ // the Extension System, e.g, when constructs SharedUserScriptMaster in
+ // ExtensionSystemImpl.
+ ExtensionUserScriptLoader(content::BrowserContext* browser_context,
+ const HostID& host_id,
+ bool listen_for_extension_system_loaded);
+ ~ExtensionUserScriptLoader() override;
+
+ // A wrapper around the method to load user scripts, which is normally run on
+ // the file thread. Exposed only for tests.
+ void LoadScriptsForTest(UserScriptList* user_scripts);
+
+ private:
+ // UserScriptLoader:
+ void LoadScripts(scoped_ptr<UserScriptList> user_scripts,
+ const std::set<HostID>& changed_hosts,
+ const std::set<int>& added_script_ids,
+ LoadScriptsCallback callback) override;
+
+ // Updates |hosts_info_| to contain info for each element of
+ // |changed_hosts_|.
+ void UpdateHostsInfo(const std::set<HostID>& changed_hosts);
+
+ // ExtensionRegistryObserver:
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+
+ // Initiates script load when we have been waiting for the extension system
+ // to be ready.
+ void OnExtensionSystemReady();
+
+ // Maps host info needed for localization to a host ID.
+ HostsInfo hosts_info_;
+
+ // Manages content verification of the loaded user scripts.
+ scoped_refptr<ContentVerifier> content_verifier_;
+
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+
+ base::WeakPtrFactory<ExtensionUserScriptLoader> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionUserScriptLoader);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_USER_SCRIPT_LOADER_H_
diff --git a/chromium/extensions/browser/extension_util.cc b/chromium/extensions/browser/extension_util.cc
new file mode 100644
index 00000000000..4981790a9ed
--- /dev/null
+++ b/chromium/extensions/browser/extension_util.cc
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_util.h"
+
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/manifest_handlers/app_isolation_info.h"
+#include "extensions/common/manifest_handlers/incognito_info.h"
+
+namespace extensions {
+namespace util {
+
+bool HasIsolatedStorage(const ExtensionInfo& info) {
+ if (!info.extension_manifest.get())
+ return false;
+
+ std::string error;
+ scoped_refptr<const Extension> extension(Extension::Create(
+ info.extension_path,
+ info.extension_location,
+ *info.extension_manifest,
+ Extension::NO_FLAGS,
+ info.extension_id,
+ &error));
+
+ return extension.get() &&
+ AppIsolationInfo::HasIsolatedStorage(extension.get());
+}
+
+bool SiteHasIsolatedStorage(const GURL& extension_site_url,
+ content::BrowserContext* context) {
+ const Extension* extension = ExtensionRegistry::Get(context)->
+ enabled_extensions().GetExtensionOrAppByURL(extension_site_url);
+
+ return extension && AppIsolationInfo::HasIsolatedStorage(extension);
+}
+
+bool CanBeIncognitoEnabled(const Extension* extension) {
+ return IncognitoInfo::IsIncognitoAllowed(extension) &&
+ (!extension->is_platform_app() ||
+ extension->location() == Manifest::COMPONENT);
+}
+
+} // namespace util
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_util.h b/chromium/extensions/browser/extension_util.h
new file mode 100644
index 00000000000..cdd4218ad14
--- /dev/null
+++ b/chromium/extensions/browser/extension_util.h
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_UTIL_H_
+#define EXTENSIONS_BROWSER_EXTENSION_UTIL_H_
+
+#include <string>
+
+class GURL;
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+class Extension;
+struct ExtensionInfo;
+
+namespace util {
+
+// TODO(benwells): Move functions from
+// chrome/browser/extensions/extension_util.h/cc that are only dependent on
+// extensions/ here.
+
+// Returns true if the extension has isolated storage.
+bool HasIsolatedStorage(const ExtensionInfo& info);
+
+// Returns true if the site URL corresponds to an extension or app and has
+// isolated storage.
+bool SiteHasIsolatedStorage(const GURL& extension_site_url,
+ content::BrowserContext* context);
+
+// Returns true if the extension can be enabled in incognito mode.
+bool CanBeIncognitoEnabled(const Extension* extension);
+
+} // namespace util
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_UTIL_H_
diff --git a/chromium/extensions/browser/extension_web_contents_observer.cc b/chromium/extensions/browser/extension_web_contents_observer.cc
new file mode 100644
index 00000000000..e00f7f1a46e
--- /dev/null
+++ b/chromium/extensions/browser/extension_web_contents_observer.cc
@@ -0,0 +1,288 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_web_contents_observer.h"
+
+#include "content/public/browser/child_process_security_policy.h"
+#include "content/public/browser/navigation_details.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/site_instance.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/url_constants.h"
+#include "extensions/browser/extension_api_frame_id_map.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/mojo/service_registration.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/browser/view_type_utils.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/view_type.h"
+
+namespace extensions {
+
+// static
+ExtensionWebContentsObserver* ExtensionWebContentsObserver::GetForWebContents(
+ content::WebContents* web_contents) {
+ return ExtensionsBrowserClient::Get()->GetExtensionWebContentsObserver(
+ web_contents);
+}
+
+ExtensionWebContentsObserver::ExtensionWebContentsObserver(
+ content::WebContents* web_contents)
+ : content::WebContentsObserver(web_contents),
+ browser_context_(web_contents->GetBrowserContext()),
+ dispatcher_(browser_context_) {
+ web_contents->ForEachFrame(
+ base::Bind(&ExtensionWebContentsObserver::InitializeFrameHelper,
+ base::Unretained(this)));
+ dispatcher_.set_delegate(this);
+}
+
+ExtensionWebContentsObserver::~ExtensionWebContentsObserver() {
+}
+
+void ExtensionWebContentsObserver::InitializeRenderFrame(
+ content::RenderFrameHost* render_frame_host) {
+ DCHECK(render_frame_host);
+ DCHECK(render_frame_host->IsRenderFrameLive());
+
+ // At the initialization of the render frame, the last committed URL is not
+ // reliable, so do not take it into account in determining whether it is an
+ // extension frame.
+ const Extension* frame_extension =
+ GetExtensionFromFrame(render_frame_host, false);
+ // This observer is attached to every WebContents, so we are also notified of
+ // frames that are not in an extension process.
+ if (!frame_extension)
+ return;
+
+ // Notify the render frame of the view type.
+ render_frame_host->Send(new ExtensionMsg_NotifyRenderViewType(
+ render_frame_host->GetRoutingID(), GetViewType(web_contents())));
+
+ ExtensionsBrowserClient::Get()->RegisterMojoServices(render_frame_host,
+ frame_extension);
+ ProcessManager::Get(browser_context_)
+ ->RegisterRenderFrameHost(web_contents(), render_frame_host,
+ frame_extension);
+}
+
+content::WebContents* ExtensionWebContentsObserver::GetAssociatedWebContents()
+ const {
+ return web_contents();
+}
+
+void ExtensionWebContentsObserver::RenderViewCreated(
+ content::RenderViewHost* render_view_host) {
+ // TODO(devlin): Most/all of this should move to RenderFrameCreated.
+ const Extension* extension = GetExtension(render_view_host);
+ if (!extension)
+ return;
+
+ Manifest::Type type = extension->GetType();
+
+ // Some extensions use file:// URLs.
+ if (type == Manifest::TYPE_EXTENSION ||
+ type == Manifest::TYPE_LEGACY_PACKAGED_APP) {
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context_);
+ if (prefs->AllowFileAccess(extension->id())) {
+ content::ChildProcessSecurityPolicy::GetInstance()->GrantScheme(
+ render_view_host->GetProcess()->GetID(), url::kFileScheme);
+ }
+ }
+
+ // Tells the new view that it's hosted in an extension process.
+ //
+ // This will often be a rendant IPC, because activating extensions happens at
+ // the process level, not at the view level. However, without some mild
+ // refactoring this isn't trivial to do, and this way is simpler.
+ //
+ // Plus, we can delete the concept of activating an extension once site
+ // isolation is turned on.
+ render_view_host->Send(new ExtensionMsg_ActivateExtension(extension->id()));
+}
+
+void ExtensionWebContentsObserver::RenderFrameCreated(
+ content::RenderFrameHost* render_frame_host) {
+ InitializeRenderFrame(render_frame_host);
+
+ // Optimization: Look up the extension API frame ID to force the mapping to be
+ // cached. This minimizes the number of IO->UI->IO thread hops when the ID is
+ // looked up again on the IO thread for the webRequest API.
+ ExtensionApiFrameIdMap::Get()->CacheFrameData(render_frame_host);
+}
+
+void ExtensionWebContentsObserver::RenderFrameDeleted(
+ content::RenderFrameHost* render_frame_host) {
+ ProcessManager::Get(browser_context_)
+ ->UnregisterRenderFrameHost(render_frame_host);
+ ExtensionApiFrameIdMap::Get()->RemoveFrameData(render_frame_host);
+}
+
+void ExtensionWebContentsObserver::DidCommitProvisionalLoadForFrame(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& url,
+ ui::PageTransition transition_type) {
+ ProcessManager* pm = ProcessManager::Get(browser_context_);
+
+ if (pm->IsRenderFrameHostRegistered(render_frame_host)) {
+ const Extension* frame_extension =
+ GetExtensionFromFrame(render_frame_host, true);
+
+ if (!frame_extension)
+ pm->UnregisterRenderFrameHost(render_frame_host);
+ }
+}
+
+void ExtensionWebContentsObserver::DidNavigateAnyFrame(
+ content::RenderFrameHost* render_frame_host,
+ const content::LoadCommittedDetails& details,
+ const content::FrameNavigateParams& params) {
+ if (details.is_in_page)
+ return;
+
+ const Extension* frame_extension =
+ GetExtensionFromFrame(render_frame_host, true);
+ ProcessManager* pm = ProcessManager::Get(browser_context_);
+
+ if (!frame_extension) {
+ // Should have been unregistered by DidCommitProvisionalLoadForFrame.
+ DCHECK(!pm->IsRenderFrameHostRegistered(render_frame_host));
+ return;
+ }
+
+ if (pm->IsRenderFrameHostRegistered(render_frame_host)) {
+ // Notify ProcessManager, because some clients do not just want to know
+ // whether the frame is in an extension process, but also whether the frame
+ // was navigated.
+ pm->DidNavigateRenderFrameHost(render_frame_host);
+ } else {
+ pm->RegisterRenderFrameHost(web_contents(), render_frame_host,
+ frame_extension);
+ }
+}
+
+bool ExtensionWebContentsObserver::OnMessageReceived(
+ const IPC::Message& message,
+ content::RenderFrameHost* render_frame_host) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(
+ ExtensionWebContentsObserver, message, render_frame_host)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request, OnRequest)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void ExtensionWebContentsObserver::PepperInstanceCreated() {
+ if (GetViewType(web_contents()) == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) {
+ ProcessManager* const process_manager =
+ ProcessManager::Get(browser_context_);
+ const Extension* const extension =
+ process_manager->GetExtensionForWebContents(web_contents());
+ if (extension)
+ process_manager->IncrementLazyKeepaliveCount(extension);
+ }
+}
+
+void ExtensionWebContentsObserver::PepperInstanceDeleted() {
+ if (GetViewType(web_contents()) == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) {
+ ProcessManager* const process_manager =
+ ProcessManager::Get(browser_context_);
+ const Extension* const extension =
+ process_manager->GetExtensionForWebContents(web_contents());
+ if (extension)
+ process_manager->DecrementLazyKeepaliveCount(extension);
+ }
+}
+
+std::string ExtensionWebContentsObserver::GetExtensionIdFromFrame(
+ content::RenderFrameHost* render_frame_host) const {
+ // The second argument is false because |render_frame_host| need not be an
+ // active RenderFrameHost (crbug.com/567277).
+ // TODO(robwu): If there is a method to check whether |render_frame_host| is
+ // an active host, use it.
+ const Extension* extension = GetExtensionFromFrame(render_frame_host, false);
+ return extension ? extension->id() : std::string();
+}
+
+const Extension* ExtensionWebContentsObserver::GetExtensionFromFrame(
+ content::RenderFrameHost* render_frame_host,
+ bool verify_url) const {
+ const GURL site_url(render_frame_host->GetSiteInstance()->GetSiteURL());
+ if (!site_url.SchemeIs(kExtensionScheme))
+ return nullptr;
+
+ const std::string& extension_id = site_url.host();
+ content::BrowserContext* browser_context =
+ render_frame_host->GetProcess()->GetBrowserContext();
+ const Extension* extension = ExtensionRegistry::Get(browser_context)
+ ->enabled_extensions()
+ .GetByID(extension_id);
+ if (!extension)
+ return nullptr;
+
+ if (verify_url) {
+ const url::Origin& origin(render_frame_host->GetLastCommittedOrigin());
+ // Without site isolation, this check is needed to eliminate non-extension
+ // schemes. With site isolation, this is still needed to exclude sandboxed
+ // extension frames with a unique origin.
+ if (origin.unique() ||
+ site_url != content::SiteInstance::GetSiteForURL(
+ browser_context, GURL(origin.Serialize())))
+ return nullptr;
+ }
+
+ return extension;
+}
+
+const Extension* ExtensionWebContentsObserver::GetExtension(
+ content::RenderViewHost* render_view_host) {
+ std::string extension_id = GetExtensionId(render_view_host);
+ if (extension_id.empty())
+ return NULL;
+
+ // May be null if the extension doesn't exist, for example if somebody typos
+ // a chrome-extension:// URL.
+ return ExtensionRegistry::Get(browser_context_)
+ ->GetExtensionById(extension_id, ExtensionRegistry::ENABLED);
+}
+
+// static
+std::string ExtensionWebContentsObserver::GetExtensionId(
+ content::RenderViewHost* render_view_host) {
+ // Note that due to ChromeContentBrowserClient::GetEffectiveURL(), hosted apps
+ // (excluding bookmark apps) will have a chrome-extension:// URL for their
+ // site, so we can ignore that wrinkle here.
+ const GURL& site = render_view_host->GetSiteInstance()->GetSiteURL();
+
+ if (!site.SchemeIs(kExtensionScheme))
+ return std::string();
+
+ return site.host();
+}
+
+void ExtensionWebContentsObserver::OnRequest(
+ content::RenderFrameHost* render_frame_host,
+ const ExtensionHostMsg_Request_Params& params) {
+ dispatcher_.Dispatch(params, render_frame_host);
+}
+
+void ExtensionWebContentsObserver::InitializeFrameHelper(
+ content::RenderFrameHost* render_frame_host) {
+ // Since this is called for all existing RenderFrameHosts during the
+ // ExtensionWebContentsObserver's creation, it's possible that not all hosts
+ // are ready.
+ // We only initialize the frame if the renderer counterpart is live; otherwise
+ // we wait for the RenderFrameCreated notification.
+ if (render_frame_host->IsRenderFrameLive())
+ InitializeRenderFrame(render_frame_host);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extension_web_contents_observer.h b/chromium/extensions/browser/extension_web_contents_observer.h
new file mode 100644
index 00000000000..5b86dc08eba
--- /dev/null
+++ b/chromium/extensions/browser/extension_web_contents_observer.h
@@ -0,0 +1,138 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_WEB_CONTENTS_OBSERVER_H_
+#define EXTENSIONS_BROWSER_EXTENSION_WEB_CONTENTS_OBSERVER_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "extensions/browser/extension_function_dispatcher.h"
+
+namespace content {
+class BrowserContext;
+class RenderViewHost;
+class WebContents;
+}
+
+namespace extensions {
+class Extension;
+
+// A web contents observer used for renderer and extension processes. Grants the
+// renderer access to certain URL scheme patterns for extensions and notifies
+// the renderer that the extension was loaded.
+//
+// Extension system embedders must create an instance for every extension
+// WebContents. It must be a subclass so that creating an instance via
+// content::WebContentsUserData::CreateForWebContents() provides an object of
+// the correct type. For an example, see ChromeExtensionWebContentsObserver.
+//
+// This class is responsible for maintaining the registrations of extension
+// frames with the ProcessManager. Only frames in an extension process are
+// registered. If out-of-process frames are enabled, every frame hosts a
+// chrome-extension: page. Otherwise non-extension frames may erroneously be
+// registered, but only briefly until they are correctly classified. This is
+// achieved using the following notifications:
+// 1. RenderFrameCreated - registers all new frames in extension processes.
+// 2. DidCommitProvisionalLoadForFrame - unregisters non-extension frames.
+// 3. DidNavigateAnyFrame - registers extension frames if they had been
+// unregistered.
+//
+// Without OOPIF, non-extension frames created by the Chrome extension are also
+// registered at RenderFrameCreated. When the non-extension page is committed,
+// we detect that the unexpected URL and unregister the frame.
+// With OOPIF only the first notification is sufficient in most cases, except
+// for sandboxed frames with a unique origin.
+class ExtensionWebContentsObserver
+ : public content::WebContentsObserver,
+ public ExtensionFunctionDispatcher::Delegate {
+ public:
+ // Returns the ExtensionWebContentsObserver for the given |web_contents|.
+ static ExtensionWebContentsObserver* GetForWebContents(
+ content::WebContents* web_contents);
+
+ ExtensionFunctionDispatcher* dispatcher() { return &dispatcher_; }
+
+ protected:
+ explicit ExtensionWebContentsObserver(content::WebContents* web_contents);
+ ~ExtensionWebContentsObserver() override;
+
+ content::BrowserContext* browser_context() { return browser_context_; }
+
+ // Initializes a new render frame. Subclasses should invoke this
+ // implementation if extending.
+ virtual void InitializeRenderFrame(
+ content::RenderFrameHost* render_frame_host);
+
+ // ExtensionFunctionDispatcher::Delegate overrides.
+ content::WebContents* GetAssociatedWebContents() const override;
+
+ // content::WebContentsObserver overrides.
+
+ // A subclass should invoke this method to finish extension process setup.
+ void RenderViewCreated(content::RenderViewHost* render_view_host) override;
+
+ void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override;
+ void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
+ void DidCommitProvisionalLoadForFrame(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& url,
+ ui::PageTransition transition_type) override;
+ void DidNavigateAnyFrame(content::RenderFrameHost* render_frame_host,
+ const content::LoadCommittedDetails& details,
+ const content::FrameNavigateParams& params) override;
+
+ // Subclasses should call this first before doing their own message handling.
+ bool OnMessageReceived(const IPC::Message& message,
+ content::RenderFrameHost* render_frame_host) override;
+
+ // Per the documentation in WebContentsObserver, these two methods are invoked
+ // when a Pepper plugin instance is attached/detached in the page DOM.
+ void PepperInstanceCreated() override;
+ void PepperInstanceDeleted() override;
+
+ // Returns the extension id associated with the given |render_frame_host|, or
+ // the empty string if there is none.
+ std::string GetExtensionIdFromFrame(
+ content::RenderFrameHost* render_frame_host) const;
+
+ // Returns the extension associated with the given |render_frame_host|, or
+ // null if there is none.
+ // If |verify_url| is false, only the SiteInstance is taken into account.
+ // If |verify_url| is true, the frame's last committed URL is also used to
+ // improve the classification of the frame.
+ const Extension* GetExtensionFromFrame(
+ content::RenderFrameHost* render_frame_host,
+ bool verify_url) const;
+
+ // TODO(devlin): Remove these once callers are updated to use the FromFrame
+ // equivalents.
+ // Returns the extension or app associated with a render view host. Returns
+ // NULL if the render view host is not for a valid extension.
+ const Extension* GetExtension(content::RenderViewHost* render_view_host);
+ // Returns the extension or app ID associated with a render view host. Returns
+ // the empty string if the render view host is not for a valid extension.
+ static std::string GetExtensionId(content::RenderViewHost* render_view_host);
+
+ private:
+ void OnRequest(content::RenderFrameHost* render_frame_host,
+ const ExtensionHostMsg_Request_Params& params);
+
+ // A helper function for initializing render frames at the creation of the
+ // observer.
+ void InitializeFrameHelper(content::RenderFrameHost* render_frame_host);
+
+ // The BrowserContext associated with the WebContents being observed.
+ content::BrowserContext* browser_context_;
+
+ ExtensionFunctionDispatcher dispatcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionWebContentsObserver);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_WEB_CONTENTS_OBSERVER_H_
diff --git a/chromium/extensions/browser/extension_zoom_request_client.cc b/chromium/extensions/browser/extension_zoom_request_client.cc
new file mode 100644
index 00000000000..ce8b45ce4ac
--- /dev/null
+++ b/chromium/extensions/browser/extension_zoom_request_client.cc
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_zoom_request_client.h"
+
+#include "extensions/common/features/behavior_feature.h"
+#include "extensions/common/features/feature_provider.h"
+
+namespace extensions {
+
+ExtensionZoomRequestClient::ExtensionZoomRequestClient(
+ scoped_refptr<const Extension> extension)
+ : extension_(extension) {
+}
+
+bool ExtensionZoomRequestClient::ShouldSuppressBubble() const {
+ return FeatureProvider::GetBehaviorFeature(
+ BehaviorFeature::kZoomWithoutBubble)
+ ->IsAvailableToExtension(extension())
+ .is_available();
+}
+
+ExtensionZoomRequestClient::~ExtensionZoomRequestClient() {
+}
+
+} // namespace Extensions
diff --git a/chromium/extensions/browser/extension_zoom_request_client.h b/chromium/extensions/browser/extension_zoom_request_client.h
new file mode 100644
index 00000000000..380b6da6b4f
--- /dev/null
+++ b/chromium/extensions/browser/extension_zoom_request_client.h
@@ -0,0 +1,37 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSION_ZOOM_REQUEST_CLIENT_H_
+#define EXTENSIONS_BROWSER_EXTENSION_ZOOM_REQUEST_CLIENT_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/ui/zoom/zoom_controller.h"
+#include "extensions/common/extension.h"
+
+namespace extensions {
+
+class Extension;
+
+// This class implements ZoomRequestClient in order to encapsulate a ref pointer
+// back to an extension requesting a zoom level change. This is important so
+// that zoom event observers can determine if an extension made the request
+// as opposed to direct user input.
+class ExtensionZoomRequestClient : public ui_zoom::ZoomRequestClient {
+ public:
+ explicit ExtensionZoomRequestClient(scoped_refptr<const Extension> extension);
+
+ bool ShouldSuppressBubble() const override;
+ const Extension* extension() const { return extension_.get(); }
+
+ private:
+ ~ExtensionZoomRequestClient() override;
+ scoped_refptr<const Extension> extension_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionZoomRequestClient);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_ZOOM_REQUEST_CLIENT_H_
diff --git a/chromium/extensions/browser/extensions_browser_client.cc b/chromium/extensions/browser/extensions_browser_client.cc
new file mode 100644
index 00000000000..5a917879f90
--- /dev/null
+++ b/chromium/extensions/browser/extensions_browser_client.cc
@@ -0,0 +1,45 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extensions_browser_client.h"
+
+#include "base/logging.h"
+#include "components/update_client/update_client.h"
+#include "extensions/browser/extension_api_frame_id_map.h"
+#include "extensions/browser/extension_error.h"
+#include "extensions/browser/updater/update_client_config.h"
+
+namespace extensions {
+
+namespace {
+
+ExtensionsBrowserClient* g_client = NULL;
+
+} // namespace
+
+scoped_refptr<update_client::UpdateClient>
+ExtensionsBrowserClient::CreateUpdateClient(content::BrowserContext* context) {
+ return scoped_refptr<update_client::UpdateClient>(nullptr);
+}
+
+std::unique_ptr<ExtensionApiFrameIdMapHelper>
+ExtensionsBrowserClient::CreateExtensionApiFrameIdMapHelper(
+ ExtensionApiFrameIdMap* map) {
+ return nullptr;
+}
+
+void ExtensionsBrowserClient::ReportError(content::BrowserContext* context,
+ scoped_ptr<ExtensionError> error) {
+ LOG(ERROR) << error->GetDebugString();
+}
+
+ExtensionsBrowserClient* ExtensionsBrowserClient::Get() {
+ return g_client;
+}
+
+void ExtensionsBrowserClient::Set(ExtensionsBrowserClient* client) {
+ g_client = client;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extensions_browser_client.h b/chromium/extensions/browser/extensions_browser_client.h
new file mode 100644
index 00000000000..c8dd9ee4a15
--- /dev/null
+++ b/chromium/extensions/browser/extensions_browser_client.h
@@ -0,0 +1,264 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSIONS_BROWSER_CLIENT_H_
+#define EXTENSIONS_BROWSER_EXTENSIONS_BROWSER_CLIENT_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "build/build_config.h"
+#include "extensions/browser/extension_event_histogram_value.h"
+#include "extensions/browser/extension_prefs_observer.h"
+#include "extensions/common/view_type.h"
+
+class ExtensionFunctionRegistry;
+class PrefService;
+
+namespace base {
+class CommandLine;
+class FilePath;
+class ListValue;
+}
+
+namespace content {
+class BrowserContext;
+class RenderFrameHost;
+class WebContents;
+}
+
+namespace net {
+class NetLog;
+class NetworkDelegate;
+class URLRequest;
+class URLRequestJob;
+}
+
+namespace update_client {
+class UpdateClient;
+}
+
+namespace extensions {
+
+class ApiActivityMonitor;
+class ComponentExtensionResourceManager;
+class Extension;
+class ExtensionCache;
+class ExtensionError;
+class ExtensionHostDelegate;
+class ExtensionPrefsObserver;
+class ExtensionApiFrameIdMap;
+class ExtensionApiFrameIdMapHelper;
+class ExtensionSystem;
+class ExtensionSystemProvider;
+class ExtensionWebContentsObserver;
+class InfoMap;
+class ProcessManagerDelegate;
+class RuntimeAPIDelegate;
+
+// Interface to allow the extensions module to make browser-process-specific
+// queries of the embedder. Should be Set() once in the browser process.
+//
+// NOTE: Methods that do not require knowledge of browser concepts should be
+// added in ExtensionsClient (extensions/common/extensions_client.h) even if
+// they are only used in the browser process.
+class ExtensionsBrowserClient {
+ public:
+ virtual ~ExtensionsBrowserClient() {}
+
+ // Returns true if the embedder has started shutting down.
+ virtual bool IsShuttingDown() = 0;
+
+ // Returns true if extensions have been disabled (e.g. via a command-line flag
+ // or preference).
+ virtual bool AreExtensionsDisabled(const base::CommandLine& command_line,
+ content::BrowserContext* context) = 0;
+
+ // Returns true if the |context| is known to the embedder.
+ virtual bool IsValidContext(content::BrowserContext* context) = 0;
+
+ // Returns true if the BrowserContexts could be considered equivalent, for
+ // example, if one is an off-the-record context owned by the other.
+ virtual bool IsSameContext(content::BrowserContext* first,
+ content::BrowserContext* second) = 0;
+
+ // Returns true if |context| has an off-the-record context associated with it.
+ virtual bool HasOffTheRecordContext(content::BrowserContext* context) = 0;
+
+ // Returns the off-the-record context associated with |context|. If |context|
+ // is already off-the-record, returns |context|.
+ // WARNING: This may create a new off-the-record context. To avoid creating
+ // another context, check HasOffTheRecordContext() first.
+ virtual content::BrowserContext* GetOffTheRecordContext(
+ content::BrowserContext* context) = 0;
+
+ // Returns the original "recording" context. This method returns |context| if
+ // |context| is not incognito.
+ virtual content::BrowserContext* GetOriginalContext(
+ content::BrowserContext* context) = 0;
+
+#if defined(OS_CHROMEOS)
+ // Returns a user id hash from |context| or an empty string if no hash could
+ // be extracted.
+ virtual std::string GetUserIdHashFromContext(
+ content::BrowserContext* context) = 0;
+#endif
+
+ // Returns true if |context| corresponds to a guest session.
+ virtual bool IsGuestSession(content::BrowserContext* context) const = 0;
+
+ // Returns true if |extension_id| can run in an incognito window.
+ virtual bool IsExtensionIncognitoEnabled(
+ const std::string& extension_id,
+ content::BrowserContext* context) const = 0;
+
+ // Returns true if |extension| can see events and data from another
+ // sub-profile (incognito to original profile, or vice versa).
+ virtual bool CanExtensionCrossIncognito(
+ const extensions::Extension* extension,
+ content::BrowserContext* context) const = 0;
+
+ // Returns an URLRequestJob to load an extension resource from the embedder's
+ // resource bundle (.pak) files. Returns NULL if the request is not for a
+ // resource bundle resource or if the embedder does not support this feature.
+ // Used for component extensions. Called on the IO thread.
+ virtual net::URLRequestJob* MaybeCreateResourceBundleRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const base::FilePath& directory_path,
+ const std::string& content_security_policy,
+ bool send_cors_header) = 0;
+
+ // Returns true if the embedder wants to allow a chrome-extension:// resource
+ // request coming from renderer A to access a resource in an extension running
+ // in renderer B. For example, Chrome overrides this to provide support for
+ // webview and dev tools. Called on the IO thread.
+ virtual bool AllowCrossRendererResourceLoad(net::URLRequest* request,
+ bool is_incognito,
+ const Extension* extension,
+ InfoMap* extension_info_map) = 0;
+
+ // Returns the PrefService associated with |context|.
+ virtual PrefService* GetPrefServiceForContext(
+ content::BrowserContext* context) = 0;
+
+ // Populates a list of ExtensionPrefs observers to be attached to each
+ // BrowserContext's ExtensionPrefs upon construction. These observers
+ // are not owned by ExtensionPrefs.
+ virtual void GetEarlyExtensionPrefsObservers(
+ content::BrowserContext* context,
+ std::vector<ExtensionPrefsObserver*>* observers) const = 0;
+
+ // Returns the ProcessManagerDelegate shared across all BrowserContexts. May
+ // return NULL in tests or for simple embedders.
+ virtual ProcessManagerDelegate* GetProcessManagerDelegate() const = 0;
+
+ // Creates a new ExtensionHostDelegate instance.
+ virtual scoped_ptr<ExtensionHostDelegate> CreateExtensionHostDelegate() = 0;
+
+ // Returns true if the client version has updated since the last run. Called
+ // once each time the extensions system is loaded per browser_context. The
+ // implementation may wish to use the BrowserContext to record the current
+ // version for later comparison.
+ virtual bool DidVersionUpdate(content::BrowserContext* context) = 0;
+
+ // Permits an external protocol handler to be launched. See
+ // ExternalProtocolHandler::PermitLaunchUrl() in Chrome.
+ virtual void PermitExternalProtocolHandler() = 0;
+
+ // Return true if the system is run in forced app mode.
+ virtual bool IsRunningInForcedAppMode() = 0;
+
+ // Return true if the user is logged in as a public session.
+ virtual bool IsLoggedInAsPublicAccount() = 0;
+
+ // Returns the embedder's ApiActivityMonitor for |context|. Returns NULL if
+ // the embedder does not monitor extension API activity.
+ virtual ApiActivityMonitor* GetApiActivityMonitor(
+ content::BrowserContext* context) = 0;
+
+ // Returns the factory that provides an ExtensionSystem to be returned from
+ // ExtensionSystem::Get.
+ virtual ExtensionSystemProvider* GetExtensionSystemFactory() = 0;
+
+ // Registers extension functions not belonging to the core extensions APIs.
+ virtual void RegisterExtensionFunctions(
+ ExtensionFunctionRegistry* registry) const = 0;
+
+ // Registers Mojo services for a RenderFrame.
+ virtual void RegisterMojoServices(content::RenderFrameHost* render_frame_host,
+ const Extension* extension) const = 0;
+
+ // Creates a RuntimeAPIDelegate responsible for handling extensions
+ // management-related events such as update and installation on behalf of the
+ // core runtime API implementation.
+ virtual scoped_ptr<RuntimeAPIDelegate> CreateRuntimeAPIDelegate(
+ content::BrowserContext* context) const = 0;
+
+ // Returns the manager of resource bundles used in extensions. Returns NULL if
+ // the manager doesn't exist.
+ virtual const ComponentExtensionResourceManager*
+ GetComponentExtensionResourceManager() = 0;
+
+ // Propagate a event to all the renderers in every browser context. The
+ // implementation must be safe to call from any thread.
+ virtual void BroadcastEventToRenderers(events::HistogramValue histogram_value,
+ const std::string& event_name,
+ scoped_ptr<base::ListValue> args) = 0;
+
+ // Returns the embedder's net::NetLog.
+ virtual net::NetLog* GetNetLog() = 0;
+
+ // Gets the single ExtensionCache instance shared across the browser process.
+ virtual ExtensionCache* GetExtensionCache() = 0;
+
+ // Indicates whether extension update checks should be allowed.
+ virtual bool IsBackgroundUpdateAllowed() = 0;
+
+ // Indicates whether an extension update which specifies its minimum browser
+ // version as |min_version| can be installed by the client. Not all extensions
+ // embedders share the same versioning model, so interpretation of the string
+ // is left up to the embedder.
+ virtual bool IsMinBrowserVersionSupported(const std::string& min_version) = 0;
+
+ // Embedders can override this function to handle extension errors.
+ virtual void ReportError(content::BrowserContext* context,
+ scoped_ptr<ExtensionError> error);
+
+ // Returns the ExtensionWebContentsObserver for the given |web_contents|.
+ virtual ExtensionWebContentsObserver* GetExtensionWebContentsObserver(
+ content::WebContents* web_contents) = 0;
+
+ // Cleans up browser-side state associated with a WebView that is being
+ // destroyed.
+ virtual void CleanUpWebView(content::BrowserContext* browser_context,
+ int embedder_process_id,
+ int view_instance_id) {}
+
+ // Attaches the task manager extension tag to |web_contents|, if needed based
+ // on |view_type|, so that its corresponding task shows up in the task
+ // manager.
+ virtual void AttachExtensionTaskManagerTag(content::WebContents* web_contents,
+ ViewType view_type) {}
+
+ // Returns a new UpdateClient.
+ virtual scoped_refptr<update_client::UpdateClient> CreateUpdateClient(
+ content::BrowserContext* context);
+
+ virtual std::unique_ptr<ExtensionApiFrameIdMapHelper>
+ CreateExtensionApiFrameIdMapHelper(ExtensionApiFrameIdMap* map);
+
+ // Returns the single instance of |this|.
+ static ExtensionsBrowserClient* Get();
+
+ // Initialize the single instance.
+ static void Set(ExtensionsBrowserClient* client);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSIONS_BROWSER_CLIENT_H_
diff --git a/chromium/extensions/browser/extensions_test.cc b/chromium/extensions/browser/extensions_test.cc
new file mode 100644
index 00000000000..c6962ed0566
--- /dev/null
+++ b/chromium/extensions/browser/extensions_test.cc
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extensions_test.h"
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/test_browser_context.h"
+#include "extensions/browser/test_extensions_browser_client.h"
+#include "extensions/test/test_content_utility_client.h"
+
+namespace extensions {
+
+// This class does work in the constructor instead of SetUp() to give subclasses
+// a valid BrowserContext to use while initializing their members. For example:
+//
+// class MyExtensionsTest : public ExtensionsTest {
+// MyExtensionsTest()
+// : my_object_(browser_context())) {
+// }
+// };
+ExtensionsTest::ExtensionsTest()
+ : content_client_(new content::ContentClient),
+ content_utility_client_(new TestContentUtilityClient),
+ content_browser_client_(new content::ContentBrowserClient),
+ browser_context_(new content::TestBrowserContext),
+ extensions_browser_client_(
+ new TestExtensionsBrowserClient(browser_context_.get())) {
+ content::SetContentClient(content_client_.get());
+ content::SetUtilityClientForTesting(content_utility_client_.get());
+ content::SetBrowserClientForTesting(content_browser_client_.get());
+ ExtensionsBrowserClient::Set(extensions_browser_client_.get());
+ extensions_browser_client_->set_extension_system_factory(
+ &extension_system_factory_);
+}
+
+ExtensionsTest::~ExtensionsTest() {
+ ExtensionsBrowserClient::Set(NULL);
+ content::SetBrowserClientForTesting(NULL);
+ content::SetUtilityClientForTesting(NULL);
+ content::SetContentClient(NULL);
+}
+
+void ExtensionsTest::SetUp() {
+ // Crashing here? Don't use this class in Chrome's unit_tests. See header.
+ BrowserContextDependencyManager::GetInstance()
+ ->CreateBrowserContextServicesForTest(browser_context_.get());
+}
+
+void ExtensionsTest::TearDown() {
+ // Allows individual tests to have BrowserContextKeyedServiceFactory objects
+ // as member variables instead of singletons. The individual services will be
+ // cleaned up before the factories are destroyed.
+ BrowserContextDependencyManager::GetInstance()->DestroyBrowserContextServices(
+ browser_context_.get());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/extensions_test.h b/chromium/extensions/browser/extensions_test.h
new file mode 100644
index 00000000000..e930421a1e1
--- /dev/null
+++ b/chromium/extensions/browser/extensions_test.h
@@ -0,0 +1,72 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTENSIONS_TEST_H_
+#define EXTENSIONS_BROWSER_EXTENSIONS_TEST_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/public/test/test_renderer_host.h"
+#include "extensions/browser/mock_extension_system.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+class BrowserContext;
+class ContentBrowserClient;
+class ContentClient;
+class ContentUtilityClient;
+class RenderViewHostTestEnabler;
+}
+
+namespace extensions {
+class TestExtensionsBrowserClient;
+
+// Base class for extensions module unit tests of browser process code. Sets up
+// the content module and extensions module client interfaces. Initializes
+// services for a browser context.
+//
+// NOTE: Use this class only in extensions_unittests, not in Chrome unit_tests.
+// BrowserContextKeyedServiceFactory singletons persist between tests.
+// In Chrome those factories assume any BrowserContext is a Profile and will
+// cause crashes if it is not. http://crbug.com/395820
+class ExtensionsTest : public testing::Test {
+ public:
+ ExtensionsTest();
+ ~ExtensionsTest() override;
+
+ // Returned as a BrowserContext since most users don't need methods from
+ // TestBrowserContext.
+ content::BrowserContext* browser_context() { return browser_context_.get(); }
+
+ // Returned as a TestExtensionsBrowserClient since most users need to call
+ // test-specific methods on it.
+ TestExtensionsBrowserClient* extensions_browser_client() {
+ return extensions_browser_client_.get();
+ }
+
+ // testing::Test overrides:
+ void SetUp() override;
+ void TearDown() override;
+
+ private:
+ // TODO(yoz): Add a NotificationService here; it's used widely enough.
+ scoped_ptr<content::ContentClient> content_client_;
+ scoped_ptr<content::ContentUtilityClient> content_utility_client_;
+ scoped_ptr<content::ContentBrowserClient> content_browser_client_;
+ scoped_ptr<content::BrowserContext> browser_context_;
+ scoped_ptr<TestExtensionsBrowserClient> extensions_browser_client_;
+
+ // The existence of this object enables tests via
+ // RenderViewHostTester.
+ content::RenderViewHostTestEnabler rvh_test_enabler_;
+
+ MockExtensionSystemFactory<MockExtensionSystem> extension_system_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionsTest);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSIONS_TEST_H_
diff --git a/chromium/extensions/browser/external_install_info.cc b/chromium/extensions/browser/external_install_info.cc
new file mode 100644
index 00000000000..e6da3fa8a2c
--- /dev/null
+++ b/chromium/extensions/browser/external_install_info.cc
@@ -0,0 +1,49 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/external_install_info.h"
+
+#include "base/version.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+ExternalInstallInfo::ExternalInstallInfo(const std::string& extension_id,
+ int creation_flags,
+ bool mark_acknowledged)
+ : extension_id(extension_id),
+ creation_flags(creation_flags),
+ mark_acknowledged(mark_acknowledged) {}
+
+ExternalInstallInfoFile::ExternalInstallInfoFile(
+ const std::string& extension_id,
+ scoped_ptr<base::Version> version,
+ const base::FilePath& path,
+ Manifest::Location crx_location,
+ int creation_flags,
+ bool mark_acknowledged,
+ bool install_immediately)
+ : ExternalInstallInfo(extension_id, creation_flags, mark_acknowledged),
+ version(std::move(version)),
+ path(path),
+ crx_location(crx_location),
+ install_immediately(install_immediately) {}
+
+ExternalInstallInfoFile::~ExternalInstallInfoFile() {}
+
+ExternalInstallInfoUpdateUrl::ExternalInstallInfoUpdateUrl(
+ const std::string& extension_id,
+ const std::string& install_parameter,
+ scoped_ptr<GURL> update_url,
+ Manifest::Location download_location,
+ int creation_flags,
+ bool mark_acknowledged)
+ : ExternalInstallInfo(extension_id, creation_flags, mark_acknowledged),
+ install_parameter(install_parameter),
+ update_url(std::move(update_url)),
+ download_location(download_location) {}
+
+ExternalInstallInfoUpdateUrl::~ExternalInstallInfoUpdateUrl() {}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/external_install_info.h b/chromium/extensions/browser/external_install_info.h
new file mode 100644
index 00000000000..72a19e8b9c9
--- /dev/null
+++ b/chromium/extensions/browser/external_install_info.h
@@ -0,0 +1,67 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTERNAL_INSTALL_INFO_H_
+#define EXTENSIONS_BROWSER_EXTERNAL_INSTALL_INFO_H_
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_vector.h"
+#include "extensions/common/manifest.h"
+
+class GURL;
+
+namespace base {
+class Version;
+}
+
+namespace extensions {
+
+// Holds information about an external extension install from an external
+// provider.
+struct ExternalInstallInfo {
+ ExternalInstallInfo(const std::string& extension_id,
+ int creation_flags,
+ bool mark_acknowledged);
+ virtual ~ExternalInstallInfo() {}
+
+ std::string extension_id;
+ int creation_flags;
+ bool mark_acknowledged;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ExternalInstallInfo);
+};
+
+struct ExternalInstallInfoFile : public ExternalInstallInfo {
+ ExternalInstallInfoFile(const std::string& extension_id,
+ scoped_ptr<base::Version> version,
+ const base::FilePath& path,
+ Manifest::Location crx_location,
+ int creation_flags,
+ bool mark_acknowledged,
+ bool install_immediately);
+ ~ExternalInstallInfoFile() override;
+
+ scoped_ptr<base::Version> version;
+ base::FilePath path;
+ Manifest::Location crx_location;
+ bool install_immediately;
+};
+
+struct ExternalInstallInfoUpdateUrl : public ExternalInstallInfo {
+ ExternalInstallInfoUpdateUrl(const std::string& extension_id,
+ const std::string& install_parameter,
+ scoped_ptr<GURL> update_url,
+ Manifest::Location download_location,
+ int creation_flags,
+ bool mark_acknowledged);
+ ~ExternalInstallInfoUpdateUrl() override;
+
+ std::string install_parameter;
+ scoped_ptr<GURL> update_url;
+ Manifest::Location download_location;
+};
+
+} // namespace extensions
+#endif // EXTENSIONS_BROWSER_EXTERNAL_INSTALL_INFO_H_
diff --git a/chromium/extensions/browser/external_provider_interface.h b/chromium/extensions/browser/external_provider_interface.h
new file mode 100644
index 00000000000..19102c045e2
--- /dev/null
+++ b/chromium/extensions/browser/external_provider_interface.h
@@ -0,0 +1,104 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_EXTERNAL_PROVIDER_INTERFACE_H_
+#define EXTENSIONS_BROWSER_EXTERNAL_PROVIDER_INTERFACE_H_
+
+#include <vector>
+
+#include "base/memory/linked_ptr.h"
+#include "extensions/common/manifest.h"
+
+class GURL;
+
+namespace base {
+class Version;
+}
+
+namespace extensions {
+
+struct ExternalInstallInfoFile;
+struct ExternalInstallInfoUpdateUrl;
+
+// This class is an abstract class for implementing external extensions
+// providers.
+class ExternalProviderInterface {
+ public:
+ // ExternalProvider uses this interface to communicate back to the
+ // caller what extensions are registered, and which |id|, |version| and |path|
+ // they have. See also VisitRegisteredExtension below. Ownership of |version|
+ // is not transferred to the visitor. Callers of the methods below must
+ // ensure that |id| is a valid extension id (use
+ // crx_file::id_util::IdIsValid(id)).
+ class VisitorInterface {
+ public:
+ // Return true if the extension install will proceed. Install will not
+ // proceed if the extension is already installed from a higher priority
+ // location.
+ virtual bool OnExternalExtensionFileFound(
+ const ExternalInstallInfoFile& info) = 0;
+
+ // Return true if the extension install will proceed. Install might not
+ // proceed if the extension is already installed from a higher priority
+ // location.
+ virtual bool OnExternalExtensionUpdateUrlFound(
+ const ExternalInstallInfoUpdateUrl& info,
+ bool is_initial_load) = 0;
+
+ // Called after all the external extensions have been reported
+ // through the above two methods. |provider| is a pointer to the
+ // provider that is now ready (typically this), and the
+ // implementation of OnExternalProviderReady() should be able to
+ // safely assert that provider->IsReady().
+ virtual void OnExternalProviderReady(
+ const ExternalProviderInterface* provider) = 0;
+
+ // Once this provider becomes "ready", it can send additional external
+ // extensions it learns about later on through
+ // OnExternalExtensionUpdateUrlFound() or OnExternalExtensionFileFound().
+ // This method will be called each time the provider finds a set of
+ // updated external extensions.
+ virtual void OnExternalProviderUpdateComplete(
+ const ExternalProviderInterface* provider,
+ const ScopedVector<ExternalInstallInfoUpdateUrl>& update_url_extensions,
+ const ScopedVector<ExternalInstallInfoFile>& file_extensions,
+ const std::set<std::string>& removed_extensions) = 0;
+
+ protected:
+ virtual ~VisitorInterface() {}
+ };
+
+ virtual ~ExternalProviderInterface() {}
+
+ // The visitor (ExtensionsService) calls this function before it goes away.
+ virtual void ServiceShutdown() = 0;
+
+ // Enumerate registered extensions, calling
+ // OnExternalExtension(File|UpdateUrl)Found on the |visitor| object for each
+ // registered extension found.
+ virtual void VisitRegisteredExtension() = 0;
+
+ // Test if this provider has an extension with id |id| registered.
+ virtual bool HasExtension(const std::string& id) const = 0;
+
+ // Gets details of an extension by its id. Output params will be set only
+ // if they are not NULL. If an output parameter is not specified by the
+ // provider type, it will not be changed.
+ // This function is no longer used outside unit tests.
+ virtual bool GetExtensionDetails(
+ const std::string& id,
+ Manifest::Location* location,
+ scoped_ptr<base::Version>* version) const = 0;
+
+ // Determines if this provider had loaded the list of external extensions
+ // from its source.
+ virtual bool IsReady() const = 0;
+};
+
+typedef std::vector<linked_ptr<ExternalProviderInterface> >
+ ProviderCollection;
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTERNAL_PROVIDER_INTERFACE_H_
diff --git a/chromium/extensions/browser/file_highlighter.cc b/chromium/extensions/browser/file_highlighter.cc
new file mode 100644
index 00000000000..2cc669ee2f5
--- /dev/null
+++ b/chromium/extensions/browser/file_highlighter.cc
@@ -0,0 +1,228 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/file_highlighter.h"
+
+#include <stack>
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+
+namespace extensions {
+
+namespace {
+
+// Keys for a highlighted dictionary.
+const char kBeforeHighlightKey[] = "beforeHighlight";
+const char kHighlightKey[] = "highlight";
+const char kAfterHighlightKey[] = "afterHighlight";
+
+// Increment |index| to the position of the next quote ('"') in |str|, skipping
+// over any escaped quotes. If no next quote is found, |index| is set to
+// std::string::npos. Assumes |index| currently points to a quote.
+void QuoteIncrement(const std::string& str, size_t* index) {
+ size_t i = *index + 1; // Skip over the first quote.
+ bool found = false;
+ while (!found && i < str.size()) {
+ if (str[i] == '\\')
+ i += 2; // if we find an escaped character, skip it.
+ else if (str[i] == '"')
+ found = true;
+ else
+ ++i;
+ }
+ *index = found ? i : std::string::npos;
+}
+
+// Increment |index| by one if the next character is not a comment. Increment
+// index until the end of the comment if it is a comment.
+void CommentSafeIncrement(const std::string& str, size_t* index) {
+ size_t i = *index;
+ if (str[i] == '/' && i + 1 < str.size()) {
+ // Eat a single-line comment.
+ if (str[i + 1] == '/') {
+ i += 2; // Eat the '//'.
+ while (i < str.size() && str[i] != '\n' && str[i] != '\r')
+ ++i;
+ } else if (str[i + 1] == '*') { // Eat a multi-line comment.
+ i += 3; // Advance to the first possible comment end.
+ while (i < str.size() && !(str[i - 1] == '*' && str[i] == '/'))
+ ++i;
+ }
+ }
+ *index = i + 1;
+}
+
+// Increment index until the end of the current "chunk"; a "chunk" is a JSON-
+// style list, object, or string literal, without exceeding |end|. Assumes
+// |index| currently points to a chunk's starting character ('{', '[', or '"').
+void ChunkIncrement(const std::string& str, size_t* index, size_t end) {
+ char c = str[*index];
+ std::stack<char> stack;
+ do {
+ if (c == '"')
+ QuoteIncrement(str, index);
+ else if (c == '[')
+ stack.push(']');
+ else if (c == '{')
+ stack.push('}');
+ else if (!stack.empty() && c == stack.top())
+ stack.pop();
+ CommentSafeIncrement(str, index);
+ c = str[*index];
+ } while (!stack.empty() && *index < end);
+}
+
+} // namespace
+
+FileHighlighter::FileHighlighter(const std::string& contents)
+ : contents_(contents), start_(0u), end_(contents_.size()) {
+}
+
+FileHighlighter::~FileHighlighter() {
+}
+
+std::string FileHighlighter::GetBeforeFeature() const {
+ return contents_.substr(0, start_);
+}
+
+std::string FileHighlighter::GetFeature() const {
+ return contents_.substr(start_, end_ - start_);
+}
+
+std::string FileHighlighter::GetAfterFeature() const {
+ return contents_.substr(end_);
+}
+
+void FileHighlighter::SetHighlightedRegions(base::DictionaryValue* dict) const {
+ std::string before_feature = GetBeforeFeature();
+ if (!before_feature.empty())
+ dict->SetString(kBeforeHighlightKey, base::UTF8ToUTF16(before_feature));
+
+ std::string feature = GetFeature();
+ if (!feature.empty())
+ dict->SetString(kHighlightKey, base::UTF8ToUTF16(feature));
+
+ std::string after_feature = GetAfterFeature();
+ if (!after_feature.empty())
+ dict->SetString(kAfterHighlightKey, base::UTF8ToUTF16(after_feature));
+}
+
+ManifestHighlighter::ManifestHighlighter(const std::string& manifest,
+ const std::string& key,
+ const std::string& specific)
+ : FileHighlighter(manifest) {
+ start_ = contents_.find('{') + 1;
+ end_ = contents_.rfind('}');
+ Parse(key, specific);
+}
+
+ManifestHighlighter::~ManifestHighlighter() {
+}
+
+
+void ManifestHighlighter::Parse(const std::string& key,
+ const std::string& specific) {
+ // First, try to find the bounds of the full key.
+ if (FindBounds(key, true) /* enforce at top level */ ) {
+ // If we succeed, and we have a specific location, find the bounds of the
+ // specific.
+ if (!specific.empty())
+ FindBounds(specific, false /* don't enforce at top level */ );
+
+ // We may have found trailing whitespace. Don't use base::TrimWhitespace,
+ // because we want to keep any whitespace we find - just not highlight it.
+ size_t trim = contents_.find_last_not_of(" \t\n\r", end_ - 1);
+ if (trim < end_ && trim > start_)
+ end_ = trim + 1;
+ } else {
+ // If we fail, then we set start to end so that the highlighted portion is
+ // empty.
+ start_ = end_;
+ }
+}
+
+bool ManifestHighlighter::FindBounds(const std::string& feature,
+ bool enforce_at_top_level) {
+ char c = '\0';
+ while (start_ < end_) {
+ c = contents_[start_];
+ if (c == '"') {
+ // The feature may be quoted.
+ size_t quote_end = start_;
+ QuoteIncrement(contents_, &quote_end);
+ if (contents_.substr(start_ + 1, quote_end - 1 - start_) == feature) {
+ FindBoundsEnd(feature, quote_end + 1);
+ return true;
+ } else {
+ // If it's not the feature, then we can skip the quoted section.
+ start_ = quote_end + 1;
+ }
+ } else if (contents_.substr(start_, feature.size()) == feature) {
+ FindBoundsEnd(feature, start_ + feature.size() + 1);
+ return true;
+ } else if (enforce_at_top_level && (c == '{' || c == '[')) {
+ // If we don't have to be at the top level, then we can skip any chunks
+ // we find.
+ ChunkIncrement(contents_, &start_, end_);
+ } else {
+ CommentSafeIncrement(contents_, &start_);
+ }
+ }
+ return false;
+}
+
+void ManifestHighlighter::FindBoundsEnd(const std::string& feature,
+ size_t local_start) {
+ char c = '\0';
+ while (local_start < end_) {
+ c = contents_[local_start];
+ // We're done when we find a terminating character (i.e., either a comma or
+ // an ending bracket.
+ if (c == ',' || c == '}' || c == ']') {
+ end_ = local_start;
+ return;
+ }
+ // We can skip any chunks we find, since we are looking for the end of the
+ // current feature, and don't want to go any deeper.
+ if (c == '"' || c == '{' || c == '[')
+ ChunkIncrement(contents_, &local_start, end_);
+ else
+ CommentSafeIncrement(contents_, &local_start);
+ }
+}
+
+SourceHighlighter::SourceHighlighter(const std::string& contents,
+ size_t line_number)
+ : FileHighlighter(contents) {
+ Parse(line_number);
+}
+
+SourceHighlighter::~SourceHighlighter() {
+}
+
+void SourceHighlighter::Parse(size_t line_number) {
+ // If line 0 is requested, highlight nothing.
+ if (line_number == 0) {
+ start_ = contents_.size();
+ return;
+ }
+
+ for (size_t i = 1; i < line_number; ++i) {
+ start_ = contents_.find('\n', start_);
+ if (start_ == std::string::npos)
+ break;
+ start_ += 1;
+ }
+
+ end_ = contents_.find('\n', start_);
+
+ // If we went off the end of the string (i.e., the line number was invalid),
+ // then move start and end to the end of the string, so that the highlighted
+ // portion is empty.
+ start_ = start_ == std::string::npos ? contents_.size() : start_;
+ end_ = end_ == std::string::npos ? contents_.size() : end_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/file_highlighter.h b/chromium/extensions/browser/file_highlighter.h
new file mode 100644
index 00000000000..6070b951aed
--- /dev/null
+++ b/chromium/extensions/browser/file_highlighter.h
@@ -0,0 +1,114 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_FILE_HIGHLIGHTER_H_
+#define EXTENSIONS_BROWSER_FILE_HIGHLIGHTER_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/macros.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace extensions {
+
+// The FileHighlighter class is used in order to isolate and highlight a portion
+// of a given file (in string form). The Highlighter will split the source into
+// three portions: the portion before the highlighted feature, the highlighted
+// feature, and the portion following the highlighted feature.
+// The file will be parsed for highlighting upon construction of the Highlighter
+// object.
+class FileHighlighter {
+ public:
+ virtual ~FileHighlighter();
+
+ // Get the portion of the manifest which should not be highlighted and is
+ // before the feature.
+ std::string GetBeforeFeature() const;
+
+ // Get the feature portion of the manifest, which should be highlighted.
+ std::string GetFeature() const;
+
+ // Get the portion of the manifest which should not be highlighted and is
+ // after the feature.
+ std::string GetAfterFeature() const;
+
+ // Populate a DictionaryValue with the highlighted portions (in UTF16) of the
+ // source file.
+ void SetHighlightedRegions(base::DictionaryValue* dict) const;
+
+ protected:
+ explicit FileHighlighter(const std::string& contents);
+
+ // The contents of the file we are parsing.
+ std::string contents_;
+
+ // The start of the feature.
+ size_t start_;
+
+ // The end of the feature.
+ size_t end_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileHighlighter);
+};
+
+// Use the ManifestHighlighter class to find the bounds of a feature in the
+// manifest.
+// A feature can be at any level in the hierarchy. The "start" of a feature is
+// the first character of the feature name, or the beginning quote of the name,
+// if present. The "end" of a feature is wherever the next item at the same
+// level starts.
+// For instance, the bounds for the 'permissions' feature at the top level could
+// be '"permissions": { "tabs", "history", "downloads" }', but the feature for
+// 'tabs' within 'permissions' would just be '"tabs"'.
+// We can't use the JSONParser to do this, because we want to display the actual
+// manifest, and once we parse it into Values, we lose any formatting the user
+// may have had.
+// If a feature cannot be found, the feature will have zero-length.
+class ManifestHighlighter : public FileHighlighter {
+ public:
+ ManifestHighlighter(const std::string& manifest,
+ const std::string& key,
+ const std::string& specific /* optional */);
+ ~ManifestHighlighter() override;
+
+ private:
+ // Called from the constructor; determine the start and end bounds of a
+ // feature, using both the key and specific information.
+ void Parse(const std::string& key, const std::string& specific);
+
+ // Find the bounds of any feature, either a full key or a specific item within
+ // the key. |enforce_at_top_level| means that the feature we find must be at
+ // the same level as |start_| (i.e., ignore nested elements).
+ // Returns true on success.
+ bool FindBounds(const std::string& feature, bool enforce_at_top_level);
+
+ // Finds the end of the feature.
+ void FindBoundsEnd(const std::string& feature, size_t local_start);
+
+ DISALLOW_COPY_AND_ASSIGN(ManifestHighlighter);
+};
+
+// Use the SourceHighlighter to highlight a particular line in a given source
+// file.
+class SourceHighlighter : public FileHighlighter {
+ public:
+ SourceHighlighter(const std::string& source, size_t line_number);
+ ~SourceHighlighter() override;
+
+ private:
+ // Called from the constructor; determine the bounds of the line in the source
+ // file.
+ void Parse(size_t line_number);
+
+ DISALLOW_COPY_AND_ASSIGN(SourceHighlighter);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_FILE_HIGHLIGHTER_H_
diff --git a/chromium/extensions/browser/file_highlighter_unittest.cc b/chromium/extensions/browser/file_highlighter_unittest.cc
new file mode 100644
index 00000000000..f419109577e
--- /dev/null
+++ b/chromium/extensions/browser/file_highlighter_unittest.cc
@@ -0,0 +1,120 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/file_highlighter.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+const char kManifest[] =
+"{\n"
+" \"name\": \"Content Scripts\",\n"
+" \"version\": \"2.0\",\n"
+" // this is a comment with the word permissions.\n"
+" /* This is a multine\n"
+" comment with the word permissions\n"
+" that shouldn't be highlighted */\n"
+" \"permissions\": [\n"
+" /* This is a tricky comment because it has brackets }]*/\n"
+" \"tabs\"\n"
+" ],\n"
+" \"content_scripts\": [\n"
+" {\n"
+" \"matches\": [\"*://aaronboodman.com/*\", \"*://rdcronin.com/*\"],\n"
+" \"js\": [\"myscript.js\"]\n"
+" }\n"
+" ],\n"
+" \"test_key\": {\n"
+" \"escaped_quoted\\\"\",\n"
+" \"/*foo*/\"\n"
+" },\n"
+" \"manifest_version\": 2,\n"
+" \"international_key\": \"還是不要\"\n"
+"}";
+
+} // namespace
+
+TEST(ManifestHighlighterUnitTest, ManifestHighlighterUnitTest) {
+ // Get a full key.
+ const char kPermissionsFeature[] =
+ "\"permissions\": [\n"
+ " /* This is a tricky comment because it has brackets }]*/\n"
+ " \"tabs\"\n"
+ " ]";
+ ManifestHighlighter permissions(kManifest, "permissions", std::string());
+ EXPECT_EQ(kPermissionsFeature, permissions.GetFeature());
+
+ // Get a specific portion of a key.
+ const char kTabsFeature[] = "\"tabs\"";
+ ManifestHighlighter tabs(kManifest, "permissions", "tabs");
+ EXPECT_EQ(kTabsFeature, tabs.GetFeature());
+
+ // Get a single-character, non-quoted entity of a key.
+ const char kManifestVersionFeature[] = "2";
+ ManifestHighlighter version(kManifest, "manifest_version", "2");
+ EXPECT_EQ(kManifestVersionFeature, version.GetFeature());
+
+ // Get a compound portion of a key, including quoted '//' (which shouldn't be
+ // mistaken for comments).
+ const char kMatchesFeature[] =
+ "\"matches\": [\"*://aaronboodman.com/*\", \"*://rdcronin.com/*\"]";
+ ManifestHighlighter matches(kManifest, "content_scripts", "matches");
+ EXPECT_EQ(kMatchesFeature, matches.GetFeature());
+
+ // If a feature isn't present, we should get an empty string.
+ ManifestHighlighter not_present(kManifest, "a_fake_feature", std::string());
+ EXPECT_EQ(std::string(), not_present.GetFeature());
+
+ // If we request a specific portion of a key which is not found, we should
+ // get an empty string.
+ ManifestHighlighter specific_portion_not_present(
+ kManifest, "permissions", "a_fake_feature");
+ EXPECT_EQ(std::string(), specific_portion_not_present.GetFeature());
+
+ const char kEscapedQuotedFeature[] = "\"escaped_quoted\\\"\"";
+ ManifestHighlighter escaped_quoted(
+ kManifest, "test_key", "escaped_quoted\\\"");
+ EXPECT_EQ(kEscapedQuotedFeature, escaped_quoted.GetFeature());
+
+ const char kFeatureWithComment[] = "\"/*foo*/\"";
+ ManifestHighlighter feature_with_comment(kManifest, "test_key", "/*foo*/");
+ EXPECT_EQ(kFeatureWithComment, feature_with_comment.GetFeature());
+
+ // Check with non-ascii characters.
+ const char kInternationalFeature[] = "\"international_key\": \"還是不要\"";
+ ManifestHighlighter international_feature(
+ kManifest, "international_key", std::string());
+ EXPECT_EQ(kInternationalFeature, international_feature.GetFeature());
+}
+
+TEST(SouceHighlighterUnitTest, SourceHighlighterUnitTest) {
+ const char kBasicSourceFile[] = "line one\nline two\nline three";
+
+ SourceHighlighter basic1(kBasicSourceFile, 1u);
+ EXPECT_EQ("line one", basic1.GetFeature());
+ SourceHighlighter basic2(kBasicSourceFile, 2u);
+ EXPECT_EQ("line two", basic2.GetFeature());
+ SourceHighlighter basic3(kBasicSourceFile, 3u);
+ EXPECT_EQ("line three", basic3.GetFeature());
+
+ const char kNoNewlineSourceFile[] = "thisisonelonglinewithnobreaksinit";
+
+ SourceHighlighter full_line(kNoNewlineSourceFile, 1u);
+ EXPECT_EQ(kNoNewlineSourceFile, full_line.GetFeature());
+
+ SourceHighlighter line_zero(kNoNewlineSourceFile, 0u);
+ EXPECT_EQ(std::string(), line_zero.GetFeature());
+
+ SourceHighlighter out_of_bounds(kNoNewlineSourceFile, 2u);
+ EXPECT_EQ(std::string(), out_of_bounds.GetFeature());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/file_reader.cc b/chromium/extensions/browser/file_reader.cc
new file mode 100644
index 00000000000..ebb9ed4bac6
--- /dev/null
+++ b/chromium/extensions/browser/file_reader.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/file_reader.h"
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/message_loop/message_loop.h"
+#include "content/public/browser/browser_thread.h"
+
+using content::BrowserThread;
+
+FileReader::FileReader(const extensions::ExtensionResource& resource,
+ const Callback& callback)
+ : resource_(resource),
+ callback_(callback),
+ origin_loop_(base::MessageLoop::current()) {}
+
+void FileReader::Start() {
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&FileReader::ReadFileOnBackgroundThread, this));
+}
+
+FileReader::~FileReader() {}
+
+void FileReader::ReadFileOnBackgroundThread() {
+ std::string data;
+ bool success = base::ReadFileToString(resource_.GetFilePath(), &data);
+ origin_loop_->PostTask(FROM_HERE, base::Bind(callback_, success, data));
+}
diff --git a/chromium/extensions/browser/file_reader.h b/chromium/extensions/browser/file_reader.h
new file mode 100644
index 00000000000..109a97a8b47
--- /dev/null
+++ b/chromium/extensions/browser/file_reader.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_FILE_READER_H_
+#define EXTENSIONS_BROWSER_FILE_READER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "extensions/common/extension_resource.h"
+
+namespace base {
+class MessageLoop;
+}
+
+// This file defines an interface for reading a file asynchronously on a
+// background thread.
+// Consider abstracting out a FilePathProvider (ExtensionResource) and moving
+// back to chrome/browser/net if other subsystems want to use it.
+class FileReader : public base::RefCountedThreadSafe<FileReader> {
+ public:
+ // Reports success or failure and the data of the file upon success.
+ typedef base::Callback<void(bool, const std::string&)> Callback;
+
+ FileReader(const extensions::ExtensionResource& resource,
+ const Callback& callback);
+
+ // Called to start reading the file on a background thread. Upon completion,
+ // the callback will be notified of the results.
+ void Start();
+
+ private:
+ friend class base::RefCountedThreadSafe<FileReader>;
+
+ virtual ~FileReader();
+
+ void ReadFileOnBackgroundThread();
+
+ extensions::ExtensionResource resource_;
+ Callback callback_;
+ base::MessageLoop* origin_loop_;
+};
+
+#endif // EXTENSIONS_BROWSER_FILE_READER_H_
diff --git a/chromium/extensions/browser/file_reader_unittest.cc b/chromium/extensions/browser/file_reader_unittest.cc
new file mode 100644
index 00000000000..5d40d31a682
--- /dev/null
+++ b/chromium/extensions/browser/file_reader_unittest.cc
@@ -0,0 +1,105 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "components/crx_file/id_util.h"
+#include "content/public/test/test_browser_thread.h"
+#include "extensions/browser/file_reader.h"
+#include "extensions/common/extension_paths.h"
+#include "extensions/common/extension_resource.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+class FileReaderTest : public testing::Test {
+ public:
+ FileReaderTest() : file_thread_(BrowserThread::FILE) {
+ file_thread_.Start();
+ }
+ private:
+ base::MessageLoop message_loop_;
+ content::TestBrowserThread file_thread_;
+};
+
+class Receiver {
+ public:
+ Receiver() : succeeded_(false) {
+ }
+
+ FileReader::Callback NewCallback() {
+ return base::Bind(&Receiver::DidReadFile, base::Unretained(this));
+ }
+
+ bool succeeded() const { return succeeded_; }
+ const std::string& data() const { return data_; }
+
+ private:
+ void DidReadFile(bool success, const std::string& data) {
+ succeeded_ = success;
+ data_ = data;
+ base::MessageLoop::current()->QuitWhenIdle();
+ }
+
+ bool succeeded_;
+ std::string data_;
+};
+
+void RunBasicTest(const char* filename) {
+ base::FilePath path;
+ PathService::Get(DIR_TEST_DATA, &path);
+ std::string extension_id = crx_file::id_util::GenerateId("test");
+ ExtensionResource resource(
+ extension_id, path, base::FilePath().AppendASCII(filename));
+ path = path.AppendASCII(filename);
+
+ std::string file_contents;
+ ASSERT_TRUE(base::ReadFileToString(path, &file_contents));
+
+ Receiver receiver;
+
+ scoped_refptr<FileReader> file_reader(
+ new FileReader(resource, receiver.NewCallback()));
+ file_reader->Start();
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(receiver.succeeded());
+ EXPECT_EQ(file_contents, receiver.data());
+}
+
+TEST_F(FileReaderTest, SmallFile) {
+ RunBasicTest("smallfile");
+}
+
+TEST_F(FileReaderTest, BiggerFile) {
+ RunBasicTest("bigfile");
+}
+
+TEST_F(FileReaderTest, NonExistantFile) {
+ base::FilePath path;
+ PathService::Get(DIR_TEST_DATA, &path);
+ std::string extension_id = crx_file::id_util::GenerateId("test");
+ ExtensionResource resource(extension_id, path, base::FilePath(
+ FILE_PATH_LITERAL("file_that_does_not_exist")));
+ path = path.AppendASCII("file_that_does_not_exist");
+
+ Receiver receiver;
+
+ scoped_refptr<FileReader> file_reader(
+ new FileReader(resource, receiver.NewCallback()));
+ file_reader->Start();
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_FALSE(receiver.succeeded());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/granted_file_entry.cc b/chromium/extensions/browser/granted_file_entry.cc
new file mode 100644
index 00000000000..4f4003d1cf9
--- /dev/null
+++ b/chromium/extensions/browser/granted_file_entry.cc
@@ -0,0 +1,15 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/granted_file_entry.h"
+
+namespace extensions {
+
+GrantedFileEntry::GrantedFileEntry() {
+}
+
+GrantedFileEntry::~GrantedFileEntry() {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/granted_file_entry.h b/chromium/extensions/browser/granted_file_entry.h
new file mode 100644
index 00000000000..97755bae311
--- /dev/null
+++ b/chromium/extensions/browser/granted_file_entry.h
@@ -0,0 +1,24 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GRANTED_FILE_ENTRY_H_
+#define EXTENSIONS_BROWSER_GRANTED_FILE_ENTRY_H_
+
+#include <string>
+
+namespace extensions {
+
+// Refers to a file entry that a renderer has been given access to.
+struct GrantedFileEntry {
+ GrantedFileEntry();
+ ~GrantedFileEntry();
+
+ std::string id;
+ std::string filesystem_id;
+ std::string registered_name;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GRANTED_FILE_ENTRY_H_
diff --git a/chromium/extensions/browser/guest_view/OWNERS b/chromium/extensions/browser/guest_view/OWNERS
new file mode 100644
index 00000000000..010739147b9
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/OWNERS
@@ -0,0 +1,6 @@
+fsamuel@chromium.org
+lazyboy@chromium.org
+lfg@chromium.org
+hanxi@chromium.org
+paulmeyer@chromium.org
+wjmaclean@chromium.org
diff --git a/chromium/extensions/browser/guest_view/app_view/app_view_apitest.cc b/chromium/extensions/browser/guest_view/app_view/app_view_apitest.cc
new file mode 100644
index 00000000000..ab6323174ca
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/app_view/app_view_apitest.cc
@@ -0,0 +1,185 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/path_service.h"
+#include "base/strings/stringprintf.h"
+#include "components/guest_view/browser/guest_view_manager.h"
+#include "components/guest_view/browser/guest_view_manager_factory.h"
+#include "components/guest_view/browser/test_guest_view_manager.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/app_window_registry.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_paths.h"
+#include "extensions/shell/browser/shell_app_delegate.h"
+#include "extensions/shell/browser/shell_app_view_guest_delegate.h"
+#include "extensions/shell/browser/shell_content_browser_client.h"
+#include "extensions/shell/browser/shell_extension_system.h"
+#include "extensions/shell/browser/shell_extensions_api_client.h"
+#include "extensions/shell/browser/shell_extensions_browser_client.h"
+#include "extensions/shell/test/shell_test.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "net/base/filename_util.h"
+
+using guest_view::GuestViewManager;
+using guest_view::TestGuestViewManager;
+using guest_view::TestGuestViewManagerFactory;
+
+namespace {
+
+class MockShellAppDelegate : public extensions::ShellAppDelegate {
+ public:
+ MockShellAppDelegate() : requested_(false) {
+ DCHECK(instance_ == nullptr);
+ instance_ = this;
+ }
+ ~MockShellAppDelegate() override { instance_ = nullptr; }
+
+ void RequestMediaAccessPermission(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback,
+ const extensions::Extension* extension) override {
+ requested_ = true;
+ if (request_message_loop_runner_.get())
+ request_message_loop_runner_->Quit();
+ }
+
+ void WaitForRequestMediaPermission() {
+ if (requested_)
+ return;
+ request_message_loop_runner_ = new content::MessageLoopRunner;
+ request_message_loop_runner_->Run();
+ }
+
+ static MockShellAppDelegate* Get() { return instance_; }
+
+ private:
+ bool requested_;
+ scoped_refptr<content::MessageLoopRunner> request_message_loop_runner_;
+ static MockShellAppDelegate* instance_;
+};
+
+MockShellAppDelegate* MockShellAppDelegate::instance_ = nullptr;
+
+class MockShellAppViewGuestDelegate
+ : public extensions::ShellAppViewGuestDelegate {
+ public:
+ MockShellAppViewGuestDelegate() {}
+
+ extensions::AppDelegate* CreateAppDelegate() override {
+ return new MockShellAppDelegate();
+ }
+};
+
+class MockExtensionsAPIClient : public extensions::ShellExtensionsAPIClient {
+ public:
+ MockExtensionsAPIClient() {}
+
+ extensions::AppViewGuestDelegate* CreateAppViewGuestDelegate()
+ const override {
+ return new MockShellAppViewGuestDelegate();
+ }
+};
+
+} // namespace
+
+namespace extensions {
+
+class AppViewTest : public AppShellTest {
+ protected:
+ AppViewTest() { GuestViewManager::set_factory_for_testing(&factory_); }
+
+ TestGuestViewManager* GetGuestViewManager() {
+ return static_cast<TestGuestViewManager*>(
+ TestGuestViewManager::FromBrowserContext(
+ ShellContentBrowserClient::Get()->GetBrowserContext()));
+ }
+
+ content::WebContents* GetFirstAppWindowWebContents() {
+ const AppWindowRegistry::AppWindowList& app_window_list =
+ AppWindowRegistry::Get(browser_context_)->app_windows();
+ DCHECK(app_window_list.size() == 1);
+ return (*app_window_list.begin())->web_contents();
+ }
+
+ const Extension* LoadApp(const std::string& app_location) {
+ base::FilePath test_data_dir;
+ PathService::Get(DIR_TEST_DATA, &test_data_dir);
+ test_data_dir = test_data_dir.AppendASCII(app_location.c_str());
+ return extension_system_->LoadApp(test_data_dir);
+ }
+
+ void RunTest(const std::string& test_name,
+ const std::string& app_location,
+ const std::string& app_to_embed) {
+ extension_system_->Init();
+
+ const Extension* app_embedder = LoadApp(app_location);
+ ASSERT_TRUE(app_embedder);
+ const Extension* app_embedded = LoadApp(app_to_embed);
+ ASSERT_TRUE(app_embedded);
+
+ extension_system_->LaunchApp(app_embedder->id());
+
+ ExtensionTestMessageListener launch_listener("LAUNCHED", false);
+ ASSERT_TRUE(launch_listener.WaitUntilSatisfied());
+
+ embedder_web_contents_ = GetFirstAppWindowWebContents();
+
+ ExtensionTestMessageListener done_listener("TEST_PASSED", false);
+ done_listener.set_failure_message("TEST_FAILED");
+ ASSERT_TRUE(
+ content::ExecuteScript(embedder_web_contents_,
+ base::StringPrintf("runTest('%s', '%s')",
+ test_name.c_str(),
+ app_embedded->id().c_str())))
+ << "Unable to start test.";
+ ASSERT_TRUE(done_listener.WaitUntilSatisfied());
+ }
+
+ protected:
+ content::WebContents* embedder_web_contents_;
+ TestGuestViewManagerFactory factory_;
+};
+
+// Tests that <appview> correctly processes parameters passed on connect.
+IN_PROC_BROWSER_TEST_F(AppViewTest, TestAppViewGoodDataShouldSucceed) {
+ RunTest("testAppViewGoodDataShouldSucceed",
+ "app_view/apitest",
+ "app_view/apitest/skeleton");
+}
+
+// Tests that <appview> can handle media permission requests.
+IN_PROC_BROWSER_TEST_F(AppViewTest, TestAppViewMediaRequest) {
+ static_cast<ShellExtensionsBrowserClient*>(ExtensionsBrowserClient::Get())
+ ->SetAPIClientForTest(nullptr);
+ static_cast<ShellExtensionsBrowserClient*>(ExtensionsBrowserClient::Get())
+ ->SetAPIClientForTest(new MockExtensionsAPIClient);
+
+ RunTest("testAppViewMediaRequest", "app_view/apitest",
+ "app_view/apitest/media_request");
+
+ MockShellAppDelegate::Get()->WaitForRequestMediaPermission();
+}
+
+// Tests that <appview> correctly processes parameters passed on connect.
+// This test should fail to connect because the embedded app (skeleton) will
+// refuse the data passed by the embedder app and deny the request.
+IN_PROC_BROWSER_TEST_F(AppViewTest, TestAppViewRefusedDataShouldFail) {
+ RunTest("testAppViewRefusedDataShouldFail",
+ "app_view/apitest",
+ "app_view/apitest/skeleton");
+}
+
+// Tests that <appview> is able to navigate to another installed app.
+IN_PROC_BROWSER_TEST_F(AppViewTest, TestAppViewWithUndefinedDataShouldSucceed) {
+ RunTest("testAppViewWithUndefinedDataShouldSucceed",
+ "app_view/apitest",
+ "app_view/apitest/skeleton");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/app_view/app_view_constants.cc b/chromium/extensions/browser/guest_view/app_view/app_view_constants.cc
new file mode 100644
index 00000000000..71ff89b4771
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/app_view/app_view_constants.cc
@@ -0,0 +1,20 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/app_view/app_view_constants.h"
+
+namespace appview {
+
+// API namespace for the embedder.
+const char kEmbedderAPINamespace[] = "appViewEmbedderInternal";
+
+// Create parameters.
+const char kAppID[] = "appId";
+const char kData[] = "data";
+
+// Parameters/properties on events.
+const char kEmbedderID[] ="embedderId";
+const char kGuestInstanceID[] = "guestInstanceId";
+
+} // namespace appview
diff --git a/chromium/extensions/browser/guest_view/app_view/app_view_constants.h b/chromium/extensions/browser/guest_view/app_view/app_view_constants.h
new file mode 100644
index 00000000000..ab1659effdd
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/app_view/app_view_constants.h
@@ -0,0 +1,25 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Constants used for the AppView API.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_APP_VIEW_APP_VIEW_CONSTANTS_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_APP_VIEW_APP_VIEW_CONSTANTS_H_
+
+namespace appview {
+
+// API namespace for the *embedder*. The embedder and guest use different APIs.
+extern const char kEmbedderAPINamespace[];
+
+// Create parameters.
+extern const char kAppID[];
+extern const char kData[];
+
+// Parameters/properties on events.
+extern const char kEmbedderID[];
+extern const char kGuestInstanceID[];
+
+} // namespace appview
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_APP_VIEW_APP_VIEW_CONSTANTS_H_
diff --git a/chromium/extensions/browser/guest_view/app_view/app_view_guest.cc b/chromium/extensions/browser/guest_view/app_view/app_view_guest.cc
new file mode 100644
index 00000000000..d5a4e858b3f
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/app_view/app_view_guest.cc
@@ -0,0 +1,290 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/app_view/app_view_guest.h"
+
+#include <utility>
+
+#include "base/command_line.h"
+#include "components/guest_view/browser/guest_view_manager.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/common/renderer_preferences.h"
+#include "extensions/browser/api/app_runtime/app_runtime_api.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/app_window/app_delegate.h"
+#include "extensions/browser/bad_message.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_host.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/guest_view/app_view/app_view_constants.h"
+#include "extensions/browser/lazy_background_task_queue.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/browser/view_type_utils.h"
+#include "extensions/common/api/app_runtime.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/strings/grit/extensions_strings.h"
+#include "ipc/ipc_message_macros.h"
+
+namespace app_runtime = extensions::api::app_runtime;
+
+using content::RenderFrameHost;
+using content::WebContents;
+using extensions::ExtensionHost;
+using guest_view::GuestViewBase;
+
+namespace extensions {
+
+namespace {
+
+struct ResponseInfo {
+ scoped_refptr<const Extension> guest_extension;
+ base::WeakPtr<AppViewGuest> app_view_guest;
+ GuestViewBase::WebContentsCreatedCallback callback;
+
+ ResponseInfo(const Extension* guest_extension,
+ const base::WeakPtr<AppViewGuest>& app_view_guest,
+ const GuestViewBase::WebContentsCreatedCallback& callback)
+ : guest_extension(guest_extension),
+ app_view_guest(app_view_guest),
+ callback(callback) {}
+
+ ~ResponseInfo() {}
+};
+
+typedef std::map<int, linked_ptr<ResponseInfo> > PendingResponseMap;
+static base::LazyInstance<PendingResponseMap> pending_response_map =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// static.
+const char AppViewGuest::Type[] = "appview";
+
+// static.
+bool AppViewGuest::CompletePendingRequest(
+ content::BrowserContext* browser_context,
+ const GURL& url,
+ int guest_instance_id,
+ const std::string& guest_extension_id,
+ content::RenderProcessHost* guest_render_process_host) {
+ PendingResponseMap* response_map = pending_response_map.Pointer();
+ PendingResponseMap::iterator it = response_map->find(guest_instance_id);
+ // Kill the requesting process if it is not the real guest.
+ if (it == response_map->end()) {
+ // The requester used an invalid |guest_instance_id|.
+ bad_message::ReceivedBadMessage(guest_render_process_host,
+ bad_message::AVG_BAD_INST_ID);
+ return false;
+ }
+
+ linked_ptr<ResponseInfo> response_info = it->second;
+ if (!response_info->app_view_guest ||
+ (response_info->guest_extension->id() != guest_extension_id)) {
+ // The app is trying to communicate with an <appview> not assigned to it, or
+ // the <appview> is already dead "nullptr".
+ bad_message::BadMessageReason reason = !response_info->app_view_guest
+ ? bad_message::AVG_NULL_AVG
+ : bad_message::AVG_BAD_EXT_ID;
+ bad_message::ReceivedBadMessage(guest_render_process_host, reason);
+ return false;
+ }
+
+ response_info->app_view_guest->CompleteCreateWebContents(
+ url, response_info->guest_extension.get(), response_info->callback);
+
+ response_map->erase(guest_instance_id);
+ return true;
+}
+
+// static
+GuestViewBase* AppViewGuest::Create(WebContents* owner_web_contents) {
+ return new AppViewGuest(owner_web_contents);
+}
+
+AppViewGuest::AppViewGuest(WebContents* owner_web_contents)
+ : GuestView<AppViewGuest>(owner_web_contents),
+ app_view_guest_delegate_(ExtensionsAPIClient::Get()
+ ->CreateAppViewGuestDelegate()),
+ weak_ptr_factory_(this) {
+ if (app_view_guest_delegate_)
+ app_delegate_.reset(app_view_guest_delegate_->CreateAppDelegate());
+}
+
+AppViewGuest::~AppViewGuest() {
+}
+
+bool AppViewGuest::HandleContextMenu(const content::ContextMenuParams& params) {
+ if (app_view_guest_delegate_) {
+ return app_view_guest_delegate_->HandleContextMenu(web_contents(), params);
+ }
+ return false;
+}
+
+void AppViewGuest::RequestMediaAccessPermission(
+ WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback) {
+ if (!app_delegate_) {
+ WebContentsDelegate::RequestMediaAccessPermission(web_contents, request,
+ callback);
+ return;
+ }
+ const ExtensionSet& enabled_extensions =
+ ExtensionRegistry::Get(browser_context())->enabled_extensions();
+ const Extension* guest_extension =
+ enabled_extensions.GetByID(guest_extension_id_);
+
+ app_delegate_->RequestMediaAccessPermission(web_contents, request, callback,
+ guest_extension);
+}
+
+bool AppViewGuest::CheckMediaAccessPermission(WebContents* web_contents,
+ const GURL& security_origin,
+ content::MediaStreamType type) {
+ if (!app_delegate_) {
+ return WebContentsDelegate::CheckMediaAccessPermission(
+ web_contents, security_origin, type);
+ }
+ const ExtensionSet& enabled_extensions =
+ ExtensionRegistry::Get(browser_context())->enabled_extensions();
+ const Extension* guest_extension =
+ enabled_extensions.GetByID(guest_extension_id_);
+
+ return app_delegate_->CheckMediaAccessPermission(
+ web_contents, security_origin, type, guest_extension);
+}
+
+bool AppViewGuest::CanRunInDetachedState() const {
+ return true;
+}
+
+void AppViewGuest::CreateWebContents(
+ const base::DictionaryValue& create_params,
+ const WebContentsCreatedCallback& callback) {
+ std::string app_id;
+ if (!create_params.GetString(appview::kAppID, &app_id)) {
+ callback.Run(nullptr);
+ return;
+ }
+ // Verifying that the appId is not the same as the host application.
+ if (owner_host() == app_id) {
+ callback.Run(nullptr);
+ return;
+ }
+ const base::DictionaryValue* data = nullptr;
+ if (!create_params.GetDictionary(appview::kData, &data)) {
+ callback.Run(nullptr);
+ return;
+ }
+
+ const ExtensionSet& enabled_extensions =
+ ExtensionRegistry::Get(browser_context())->enabled_extensions();
+ const Extension* guest_extension = enabled_extensions.GetByID(app_id);
+ const Extension* embedder_extension =
+ enabled_extensions.GetByID(GetOwnerSiteURL().host());
+
+ if (!guest_extension || !guest_extension->is_platform_app() ||
+ !embedder_extension | !embedder_extension->is_platform_app()) {
+ callback.Run(nullptr);
+ return;
+ }
+
+ pending_response_map.Get().insert(
+ std::make_pair(guest_instance_id(),
+ make_linked_ptr(new ResponseInfo(
+ guest_extension,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback))));
+
+ LazyBackgroundTaskQueue* queue =
+ LazyBackgroundTaskQueue::Get(browser_context());
+ if (queue->ShouldEnqueueTask(browser_context(), guest_extension)) {
+ queue->AddPendingTask(browser_context(),
+ guest_extension->id(),
+ base::Bind(
+ &AppViewGuest::LaunchAppAndFireEvent,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Passed(make_scoped_ptr(data->DeepCopy())),
+ callback));
+ return;
+ }
+
+ ProcessManager* process_manager = ProcessManager::Get(browser_context());
+ ExtensionHost* host =
+ process_manager->GetBackgroundHostForExtension(guest_extension->id());
+ DCHECK(host);
+ LaunchAppAndFireEvent(make_scoped_ptr(data->DeepCopy()), callback, host);
+}
+
+void AppViewGuest::DidInitialize(const base::DictionaryValue& create_params) {
+ ExtensionsAPIClient::Get()->AttachWebContentsHelpers(web_contents());
+
+ if (!url_.is_valid())
+ return;
+
+ web_contents()->GetController().LoadURL(
+ url_, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
+}
+
+const char* AppViewGuest::GetAPINamespace() const {
+ return appview::kEmbedderAPINamespace;
+}
+
+int AppViewGuest::GetTaskPrefix() const {
+ return IDS_EXTENSION_TASK_MANAGER_APPVIEW_TAG_PREFIX;
+}
+
+void AppViewGuest::CompleteCreateWebContents(
+ const GURL& url,
+ const Extension* guest_extension,
+ const WebContentsCreatedCallback& callback) {
+ if (!url.is_valid()) {
+ callback.Run(nullptr);
+ return;
+ }
+ url_ = url;
+ guest_extension_id_ = guest_extension->id();
+
+ WebContents::CreateParams params(
+ browser_context(),
+ content::SiteInstance::CreateForURL(browser_context(),
+ guest_extension->url()));
+ params.guest_delegate = this;
+ callback.Run(WebContents::Create(params));
+}
+
+void AppViewGuest::LaunchAppAndFireEvent(
+ scoped_ptr<base::DictionaryValue> data,
+ const WebContentsCreatedCallback& callback,
+ ExtensionHost* extension_host) {
+ bool has_event_listener = EventRouter::Get(browser_context())
+ ->ExtensionHasEventListener(
+ extension_host->extension()->id(),
+ app_runtime::OnEmbedRequested::kEventName);
+ if (!has_event_listener) {
+ callback.Run(nullptr);
+ return;
+ }
+
+ scoped_ptr<base::DictionaryValue> embed_request(new base::DictionaryValue());
+ embed_request->SetInteger(appview::kGuestInstanceID, guest_instance_id());
+ embed_request->SetString(appview::kEmbedderID, owner_host());
+ embed_request->Set(appview::kData, data.release());
+ AppRuntimeEventRouter::DispatchOnEmbedRequestedEvent(
+ browser_context(), std::move(embed_request), extension_host->extension());
+}
+
+void AppViewGuest::SetAppDelegateForTest(AppDelegate* delegate) {
+ app_delegate_.reset(delegate);
+}
+
+std::vector<int> AppViewGuest::GetAllRegisteredInstanceIdsForTesting() {
+ std::vector<int> instances;
+ for (const auto& key_value : pending_response_map.Get()) {
+ instances.push_back(key_value.first);
+ }
+ return instances;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/app_view/app_view_guest.h b/chromium/extensions/browser/guest_view/app_view/app_view_guest.h
new file mode 100644
index 00000000000..20bfdd166da
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/app_view/app_view_guest.h
@@ -0,0 +1,93 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_APP_VIEW_APP_VIEW_GUEST_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_APP_VIEW_APP_VIEW_GUEST_H_
+
+#include "base/id_map.h"
+#include "base/macros.h"
+#include "components/guest_view/browser/guest_view.h"
+#include "extensions/browser/guest_view/app_view/app_view_guest_delegate.h"
+
+namespace extensions {
+class Extension;
+class ExtensionHost;
+
+// An AppViewGuest provides the browser-side implementation of <appview> API.
+// AppViewGuest is created on attachment. That is, when a guest WebContents is
+// associated with a particular embedder WebContents. This happens on calls to
+// the connect API.
+class AppViewGuest : public guest_view::GuestView<AppViewGuest> {
+ public:
+ static const char Type[];
+
+ // Completes the creation of a WebContents associated with the provided
+ // |guest_extension_id| and |guest_instance_id| for the given
+ // |browser_context|.
+ // |guest_render_process_host| is the RenderProcessHost and |url| is the
+ // resource GURL of the extension instance making this request. If there is
+ // any mismatch between the expected |guest_instance_id| and
+ // |guest_extension_id| provided and the recorded copies from when the the
+ // <appview> was created, the RenderProcessHost of the extension instance
+ // behind this request will be killed.
+ static bool CompletePendingRequest(
+ content::BrowserContext* browser_context,
+ const GURL& url,
+ int guest_instance_id,
+ const std::string& guest_extension_id,
+ content::RenderProcessHost* guest_render_process_host);
+
+ static GuestViewBase* Create(content::WebContents* owner_web_contents);
+
+ static std::vector<int> GetAllRegisteredInstanceIdsForTesting();
+
+ // Sets the AppDelegate for this guest.
+ void SetAppDelegateForTest(AppDelegate* delegate);
+
+ private:
+ explicit AppViewGuest(content::WebContents* owner_web_contents);
+
+ ~AppViewGuest() override;
+
+ // GuestViewBase implementation.
+ bool CanRunInDetachedState() const final;
+ void CreateWebContents(const base::DictionaryValue& create_params,
+ const WebContentsCreatedCallback& callback) final;
+ void DidInitialize(const base::DictionaryValue& create_params) final;
+ const char* GetAPINamespace() const final;
+ int GetTaskPrefix() const final;
+
+ // content::WebContentsDelegate implementation.
+ bool HandleContextMenu(const content::ContextMenuParams& params) final;
+ void RequestMediaAccessPermission(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback) final;
+ bool CheckMediaAccessPermission(content::WebContents* web_contents,
+ const GURL& security_origin,
+ content::MediaStreamType type) final;
+
+ void CompleteCreateWebContents(const GURL& url,
+ const Extension* guest_extension,
+ const WebContentsCreatedCallback& callback);
+
+ void LaunchAppAndFireEvent(scoped_ptr<base::DictionaryValue> data,
+ const WebContentsCreatedCallback& callback,
+ ExtensionHost* extension_host);
+
+ GURL url_;
+ std::string guest_extension_id_;
+ scoped_ptr<AppViewGuestDelegate> app_view_guest_delegate_;
+ scoped_ptr<AppDelegate> app_delegate_;
+
+ // This is used to ensure pending tasks will not fire after this object is
+ // destroyed.
+ base::WeakPtrFactory<AppViewGuest> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppViewGuest);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_APP_VIEW_APP_VIEW_GUEST_H_
diff --git a/chromium/extensions/browser/guest_view/app_view/app_view_guest_delegate.cc b/chromium/extensions/browser/guest_view/app_view/app_view_guest_delegate.cc
new file mode 100644
index 00000000000..8166305c051
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/app_view/app_view_guest_delegate.cc
@@ -0,0 +1,12 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/app_view/app_view_guest_delegate.h"
+
+namespace extensions {
+
+AppViewGuestDelegate::~AppViewGuestDelegate() {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/app_view/app_view_guest_delegate.h b/chromium/extensions/browser/guest_view/app_view/app_view_guest_delegate.h
new file mode 100644
index 00000000000..01e90dde5f4
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/app_view/app_view_guest_delegate.h
@@ -0,0 +1,33 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_APP_VIEW_APP_VIEW_GUEST_DELEGATE_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_APP_VIEW_APP_VIEW_GUEST_DELEGATE_H_
+
+namespace content {
+struct ContextMenuParams;
+class WebContents;
+}
+
+namespace extensions {
+class AppDelegate;
+
+// Interface to handle communication between AppView (in extensions) with the
+// browser.
+class AppViewGuestDelegate {
+ public:
+ virtual ~AppViewGuestDelegate();
+
+ // Shows the context menu for the guest.
+ // Returns true if the context menu was handled.
+ virtual bool HandleContextMenu(content::WebContents* web_contents,
+ const content::ContextMenuParams& params) = 0;
+
+ // Returns an AppDelegate to be used by the AppViewGuest.
+ virtual AppDelegate* CreateAppDelegate() = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_APP_VIEW_APP_VIEW_GUEST_DELEGATE_H_
diff --git a/chromium/extensions/browser/guest_view/extension_options/DEPS b/chromium/extensions/browser/guest_view/extension_options/DEPS
new file mode 100644
index 00000000000..de9193f852c
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/extension_options/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+extensions/strings/grit/extensions_strings.h"
+]
diff --git a/chromium/extensions/browser/guest_view/extension_options/extension_options_apitest.cc b/chromium/extensions/browser/guest_view/extension_options/extension_options_apitest.cc
new file mode 100644
index 00000000000..264cde9705b
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/extension_options/extension_options_apitest.cc
@@ -0,0 +1,81 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_path.h"
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/common/content_switches.h"
+#include "extensions/common/feature_switch.h"
+#include "extensions/common/switches.h"
+#include "extensions/test/result_catcher.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::Extension;
+using extensions::FeatureSwitch;
+
+class ExtensionOptionsApiTest : public ExtensionApiTest,
+ public testing::WithParamInterface<bool> {
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ ExtensionApiTest::SetUpCommandLine(command_line);
+
+ bool use_cross_process_frames_for_guests = GetParam();
+ if (use_cross_process_frames_for_guests)
+ command_line->AppendSwitch(switches::kUseCrossProcessFramesForGuests);
+ }
+};
+
+INSTANTIATE_TEST_CASE_P(ExtensionOptionsApiTests,
+ ExtensionOptionsApiTest,
+ testing::Bool());
+
+// crbug/415949.
+#if defined(OS_MACOSX)
+#define MAYBE_ExtensionCanEmbedOwnOptions DISABLED_ExtensionCanEmbedOwnOptions
+#else
+#define MAYBE_ExtensionCanEmbedOwnOptions ExtensionCanEmbedOwnOptions
+#endif
+IN_PROC_BROWSER_TEST_P(ExtensionOptionsApiTest,
+ MAYBE_ExtensionCanEmbedOwnOptions) {
+ base::FilePath extension_dir =
+ test_data_dir_.AppendASCII("extension_options").AppendASCII("embed_self");
+ ASSERT_TRUE(LoadExtension(extension_dir));
+ ASSERT_TRUE(RunExtensionSubtest("extension_options/embed_self", "test.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(ExtensionOptionsApiTest,
+ ShouldNotEmbedOtherExtensionsOptions) {
+ base::FilePath dir = test_data_dir_.AppendASCII("extension_options")
+ .AppendASCII("embed_other");
+
+ const Extension* embedder = InstallExtension(dir.AppendASCII("embedder"), 1);
+ const Extension* embedded = InstallExtension(dir.AppendASCII("embedded"), 1);
+
+ ASSERT_TRUE(embedder);
+ ASSERT_TRUE(embedded);
+
+ // Since the extension id of the embedded extension is not always the same,
+ // store the embedded extension id in the embedder's storage before running
+ // the tests.
+ std::string script = base::StringPrintf(
+ "chrome.storage.local.set({'embeddedId': '%s'}, function() {"
+ "window.domAutomationController.send('done injecting');});",
+ embedded->id().c_str());
+
+ ExecuteScriptInBackgroundPage(embedder->id(), script);
+ extensions::ResultCatcher catcher;
+ ui_test_utils::NavigateToURL(browser(),
+ embedder->GetResourceURL("test.html"));
+ ASSERT_TRUE(catcher.GetNextResult());
+}
+
+IN_PROC_BROWSER_TEST_P(ExtensionOptionsApiTest,
+ CannotEmbedUsingInvalidExtensionIds) {
+ ASSERT_TRUE(InstallExtension(test_data_dir_.AppendASCII("extension_options")
+ .AppendASCII("embed_invalid"),
+ 1));
+ ASSERT_TRUE(
+ RunExtensionSubtest("extension_options/embed_invalid", "test.html"));
+}
diff --git a/chromium/extensions/browser/guest_view/extension_options/extension_options_constants.cc b/chromium/extensions/browser/guest_view/extension_options/extension_options_constants.cc
new file mode 100644
index 00000000000..7ce80e594f2
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/extension_options/extension_options_constants.cc
@@ -0,0 +1,14 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/extension_options/extension_options_constants.h"
+
+namespace extensionoptions {
+
+// API namespace.
+extern const char kAPINamespace[] = "extensionOptionsInternal";
+
+const char kExtensionId[] = "extension";
+
+} // namespace extensionoptions
diff --git a/chromium/extensions/browser/guest_view/extension_options/extension_options_constants.h b/chromium/extensions/browser/guest_view/extension_options/extension_options_constants.h
new file mode 100644
index 00000000000..28228c2462d
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/extension_options/extension_options_constants.h
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSION_OPTIONS_EXTENSION_OPTIONS_CONSTANTS_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSION_OPTIONS_EXTENSION_OPTIONS_CONSTANTS_H_
+
+namespace extensionoptions {
+
+// API namespace.
+extern const char kAPINamespace[];
+
+extern const char kExtensionId[];
+
+} // namespace extensionoptions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSION_OPTIONS_EXTENSION_OPTIONS_CONSTANTS_H_
diff --git a/chromium/extensions/browser/guest_view/extension_options/extension_options_guest.cc b/chromium/extensions/browser/guest_view/extension_options/extension_options_guest.cc
new file mode 100644
index 00000000000..57eb9c2f905
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/extension_options/extension_options_guest.cc
@@ -0,0 +1,241 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/extension_options/extension_options_guest.h"
+
+#include <utility>
+
+#include "base/values.h"
+#include "components/crx_file/id_util.h"
+#include "components/guest_view/browser/guest_view_event.h"
+#include "components/guest_view/browser/guest_view_manager.h"
+#include "content/public/browser/navigation_details.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/site_instance.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/result_codes.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/bad_message.h"
+#include "extensions/browser/extension_function_dispatcher.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/guest_view/extension_options/extension_options_constants.h"
+#include "extensions/browser/guest_view/extension_options/extension_options_guest_delegate.h"
+#include "extensions/browser/view_type_utils.h"
+#include "extensions/common/api/extension_options_internal.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/manifest_handlers/options_page_info.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/strings/grit/extensions_strings.h"
+#include "ipc/ipc_message_macros.h"
+
+using content::WebContents;
+using guest_view::GuestViewBase;
+using guest_view::GuestViewEvent;
+using namespace extensions::api;
+
+namespace extensions {
+
+// static
+const char ExtensionOptionsGuest::Type[] = "extensionoptions";
+
+ExtensionOptionsGuest::ExtensionOptionsGuest(WebContents* owner_web_contents)
+ : GuestView<ExtensionOptionsGuest>(owner_web_contents),
+ extension_options_guest_delegate_(
+ extensions::ExtensionsAPIClient::Get()
+ ->CreateExtensionOptionsGuestDelegate(this)) {}
+
+ExtensionOptionsGuest::~ExtensionOptionsGuest() {
+}
+
+// static
+GuestViewBase* ExtensionOptionsGuest::Create(WebContents* owner_web_contents) {
+ return new ExtensionOptionsGuest(owner_web_contents);
+}
+
+bool ExtensionOptionsGuest::CanRunInDetachedState() const {
+ return true;
+}
+
+void ExtensionOptionsGuest::CreateWebContents(
+ const base::DictionaryValue& create_params,
+ const WebContentsCreatedCallback& callback) {
+ // Get the extension's base URL.
+ std::string extension_id;
+ create_params.GetString(extensionoptions::kExtensionId, &extension_id);
+
+ if (!crx_file::id_util::IdIsValid(extension_id)) {
+ callback.Run(nullptr);
+ return;
+ }
+
+ std::string embedder_extension_id = GetOwnerSiteURL().host();
+ if (crx_file::id_util::IdIsValid(embedder_extension_id) &&
+ extension_id != embedder_extension_id) {
+ // Extensions cannot embed other extensions' options pages.
+ callback.Run(nullptr);
+ return;
+ }
+
+ GURL extension_url =
+ extensions::Extension::GetBaseURLFromExtensionId(extension_id);
+ if (!extension_url.is_valid()) {
+ callback.Run(nullptr);
+ return;
+ }
+
+ // Get the options page URL for later use.
+ extensions::ExtensionRegistry* registry =
+ extensions::ExtensionRegistry::Get(browser_context());
+ const extensions::Extension* extension =
+ registry->enabled_extensions().GetByID(extension_id);
+ if (!extension) {
+ // The ID was valid but the extension didn't exist. Typically this will
+ // happen when an extension is disabled.
+ callback.Run(nullptr);
+ return;
+ }
+
+ options_page_ = extensions::OptionsPageInfo::GetOptionsPage(extension);
+ if (!options_page_.is_valid()) {
+ callback.Run(nullptr);
+ return;
+ }
+
+ // Create a WebContents using the extension URL. The options page's
+ // WebContents should live in the same process as its parent extension's
+ // WebContents, so we can use |extension_url| for creating the SiteInstance.
+ WebContents::CreateParams params(
+ browser_context(),
+ content::SiteInstance::CreateForURL(browser_context(), extension_url));
+ params.guest_delegate = this;
+ WebContents* wc = WebContents::Create(params);
+ SetViewType(wc, VIEW_TYPE_EXTENSION_GUEST);
+ callback.Run(wc);
+}
+
+void ExtensionOptionsGuest::DidInitialize(
+ const base::DictionaryValue& create_params) {
+ ExtensionsAPIClient::Get()->AttachWebContentsHelpers(web_contents());
+ web_contents()->GetController().LoadURL(options_page_,
+ content::Referrer(),
+ ui::PAGE_TRANSITION_LINK,
+ std::string());
+}
+
+void ExtensionOptionsGuest::GuestViewDidStopLoading() {
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ DispatchEventToView(make_scoped_ptr(new GuestViewEvent(
+ extension_options_internal::OnLoad::kEventName, std::move(args))));
+}
+
+const char* ExtensionOptionsGuest::GetAPINamespace() const {
+ return extensionoptions::kAPINamespace;
+}
+
+int ExtensionOptionsGuest::GetTaskPrefix() const {
+ return IDS_EXTENSION_TASK_MANAGER_EXTENSIONOPTIONS_TAG_PREFIX;
+}
+
+bool ExtensionOptionsGuest::IsPreferredSizeModeEnabled() const {
+ return true;
+}
+
+void ExtensionOptionsGuest::OnPreferredSizeChanged(const gfx::Size& pref_size) {
+ extension_options_internal::PreferredSizeChangedOptions options;
+ // Convert the size from physical pixels to logical pixels.
+ options.width = PhysicalPixelsToLogicalPixels(pref_size.width());
+ options.height = PhysicalPixelsToLogicalPixels(pref_size.height());
+ DispatchEventToView(make_scoped_ptr(new GuestViewEvent(
+ extension_options_internal::OnPreferredSizeChanged::kEventName,
+ options.ToValue())));
+}
+
+bool ExtensionOptionsGuest::ShouldHandleFindRequestsForEmbedder() const {
+ return true;
+}
+
+WebContents* ExtensionOptionsGuest::OpenURLFromTab(
+ WebContents* source,
+ const content::OpenURLParams& params) {
+ if (!extension_options_guest_delegate_)
+ return nullptr;
+
+ // Don't allow external URLs with the CURRENT_TAB disposition be opened in
+ // this guest view, change the disposition to NEW_FOREGROUND_TAB.
+ if ((!params.url.SchemeIs(extensions::kExtensionScheme) ||
+ params.url.host() != options_page_.host()) &&
+ params.disposition == CURRENT_TAB) {
+ return extension_options_guest_delegate_->OpenURLInNewTab(
+ content::OpenURLParams(params.url,
+ params.referrer,
+ params.frame_tree_node_id,
+ NEW_FOREGROUND_TAB,
+ params.transition,
+ params.is_renderer_initiated));
+ }
+ return extension_options_guest_delegate_->OpenURLInNewTab(params);
+}
+
+void ExtensionOptionsGuest::CloseContents(WebContents* source) {
+ DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(extension_options_internal::OnClose::kEventName,
+ make_scoped_ptr(new base::DictionaryValue()))));
+}
+
+bool ExtensionOptionsGuest::HandleContextMenu(
+ const content::ContextMenuParams& params) {
+ if (!extension_options_guest_delegate_)
+ return false;
+
+ return extension_options_guest_delegate_->HandleContextMenu(params);
+}
+
+bool ExtensionOptionsGuest::ShouldCreateWebContents(
+ WebContents* web_contents,
+ int32_t route_id,
+ int32_t main_frame_route_id,
+ int32_t main_frame_widget_route_id,
+ WindowContainerType window_container_type,
+ const std::string& frame_name,
+ const GURL& target_url,
+ const std::string& partition_id,
+ content::SessionStorageNamespace* session_storage_namespace) {
+ // This method handles opening links from within the guest. Since this guest
+ // view is used for displaying embedded extension options, we want any
+ // external links to be opened in a new tab, not in a new guest view.
+ // Therefore we just open the URL in a new tab, and since we aren't handling
+ // the new web contents, we return false.
+ // TODO(ericzeng): Open the tab in the background if the click was a
+ // ctrl-click or middle mouse button click
+ if (extension_options_guest_delegate_) {
+ extension_options_guest_delegate_->OpenURLInNewTab(
+ content::OpenURLParams(target_url,
+ content::Referrer(),
+ NEW_FOREGROUND_TAB,
+ ui::PAGE_TRANSITION_LINK,
+ false));
+ }
+ return false;
+}
+
+void ExtensionOptionsGuest::DidNavigateMainFrame(
+ const content::LoadCommittedDetails& details,
+ const content::FrameNavigateParams& params) {
+ if (attached()) {
+ auto guest_zoom_controller =
+ ui_zoom::ZoomController::FromWebContents(web_contents());
+ guest_zoom_controller->SetZoomMode(
+ ui_zoom::ZoomController::ZOOM_MODE_ISOLATED);
+ SetGuestZoomLevelToMatchEmbedder();
+
+ if (!url::IsSameOriginWith(params.url, options_page_)) {
+ bad_message::ReceivedBadMessage(web_contents()->GetRenderProcessHost(),
+ bad_message::EOG_BAD_ORIGIN);
+ }
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/extension_options/extension_options_guest.h b/chromium/extensions/browser/guest_view/extension_options/extension_options_guest.h
new file mode 100644
index 00000000000..b81fe414624
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/extension_options/extension_options_guest.h
@@ -0,0 +1,74 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSION_OPTIONS_EXTENSION_OPTIONS_GUEST_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSION_OPTIONS_EXTENSION_OPTIONS_GUEST_H_
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "components/guest_view/browser/guest_view.h"
+#include "extensions/browser/guest_view/extension_options/extension_options_guest_delegate.h"
+#include "url/gurl.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class ExtensionOptionsGuest
+ : public guest_view::GuestView<ExtensionOptionsGuest> {
+ public:
+ static const char Type[];
+ static guest_view::GuestViewBase* Create(
+ content::WebContents* owner_web_contents);
+
+ private:
+ explicit ExtensionOptionsGuest(content::WebContents* owner_web_contents);
+ ~ExtensionOptionsGuest() override;
+
+ // GuestViewBase implementation.
+ bool CanRunInDetachedState() const final;
+ void CreateWebContents(const base::DictionaryValue& create_params,
+ const WebContentsCreatedCallback& callback) final;
+ void DidInitialize(const base::DictionaryValue& create_params) final;
+ void GuestViewDidStopLoading() final;
+ const char* GetAPINamespace() const final;
+ int GetTaskPrefix() const final;
+ bool IsPreferredSizeModeEnabled() const final;
+ void OnPreferredSizeChanged(const gfx::Size& pref_size) final;
+ bool ShouldHandleFindRequestsForEmbedder() const final;
+
+ // content::WebContentsDelegate implementation.
+ content::WebContents* OpenURLFromTab(
+ content::WebContents* source,
+ const content::OpenURLParams& params) final;
+ void CloseContents(content::WebContents* source) final;
+ bool HandleContextMenu(const content::ContextMenuParams& params) final;
+ bool ShouldCreateWebContents(
+ content::WebContents* web_contents,
+ int32_t route_id,
+ int32_t main_frame_route_id,
+ int32_t main_frame_widget_route_id,
+ WindowContainerType window_container_type,
+ const std::string& frame_name,
+ const GURL& target_url,
+ const std::string& partition_id,
+ content::SessionStorageNamespace* session_storage_namespace) final;
+
+ // content::WebContentsObserver implementation.
+ void DidNavigateMainFrame(const content::LoadCommittedDetails& details,
+ const content::FrameNavigateParams& params) final;
+
+ scoped_ptr<extensions::ExtensionOptionsGuestDelegate>
+ extension_options_guest_delegate_;
+ GURL options_page_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionOptionsGuest);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSION_OPTIONS_EXTENSION_OPTIONS_GUEST_H_
diff --git a/chromium/extensions/browser/guest_view/extension_options/extension_options_guest_delegate.cc b/chromium/extensions/browser/guest_view/extension_options/extension_options_guest_delegate.cc
new file mode 100644
index 00000000000..7f6c78338ca
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/extension_options/extension_options_guest_delegate.cc
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/extension_options/extension_options_guest_delegate.h"
+
+namespace extensions {
+
+ExtensionOptionsGuestDelegate::ExtensionOptionsGuestDelegate(
+ ExtensionOptionsGuest* guest)
+ : guest_(guest) {
+}
+
+ExtensionOptionsGuestDelegate::~ExtensionOptionsGuestDelegate() {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/extension_options/extension_options_guest_delegate.h b/chromium/extensions/browser/guest_view/extension_options/extension_options_guest_delegate.h
new file mode 100644
index 00000000000..4052c8b7012
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/extension_options/extension_options_guest_delegate.h
@@ -0,0 +1,44 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSION_OPTIONS_EXTENSION_OPTIONS_GUEST_DELEGATE_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSION_OPTIONS_EXTENSION_OPTIONS_GUEST_DELEGATE_H_
+
+#include "base/macros.h"
+
+namespace content {
+struct ContextMenuParams;
+struct OpenURLParams;
+class WebContents;
+}
+
+namespace extensions {
+
+class ExtensionOptionsGuest;
+
+// Interface to handle communication between ExtensionOptionsGuest (in
+// extensions) with the browser.
+class ExtensionOptionsGuestDelegate {
+ public:
+ explicit ExtensionOptionsGuestDelegate(ExtensionOptionsGuest* guest);
+ virtual ~ExtensionOptionsGuestDelegate();
+
+ // Shows the context menu for the guest.
+ // Returns true if the context menu was handled.
+ virtual bool HandleContextMenu(const content::ContextMenuParams& params) = 0;
+
+ virtual content::WebContents* OpenURLInNewTab(
+ const content::OpenURLParams& params) = 0;
+
+ ExtensionOptionsGuest* extension_options_guest() const { return guest_; }
+
+ private:
+ ExtensionOptionsGuest* const guest_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionOptionsGuestDelegate);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSION_OPTIONS_EXTENSION_OPTIONS_GUEST_DELEGATE_H_
diff --git a/chromium/extensions/browser/guest_view/extension_view/extension_view_constants.cc b/chromium/extensions/browser/guest_view/extension_view/extension_view_constants.cc
new file mode 100644
index 00000000000..ba40c3ec8a1
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/extension_view/extension_view_constants.cc
@@ -0,0 +1,19 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/extension_view/extension_view_constants.h"
+
+namespace extensionview {
+
+// API namespace.
+extern const char kAPINamespace[] = "extensionViewInternal";
+
+// Attributes.
+const char kAttributeExtension[] = "extension";
+const char kAttributeSrc[] = "src";
+
+// Events.
+const char kEventLoadCommit[] = "extensionViewInternal.onLoadCommit";
+
+} // namespace extensionview
diff --git a/chromium/extensions/browser/guest_view/extension_view/extension_view_constants.h b/chromium/extensions/browser/guest_view/extension_view/extension_view_constants.h
new file mode 100644
index 00000000000..e2e07801c96
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/extension_view/extension_view_constants.h
@@ -0,0 +1,22 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSION_OPTIONS_EXTENSION_VIEW_CONSTANTS_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSION_OPTIONS_EXTENSION_VIEW_CONSTANTS_H_
+
+namespace extensionview {
+
+// API namespace.
+extern const char kAPINamespace[];
+
+// Attributes.
+extern const char kAttributeExtension[];
+extern const char kAttributeSrc[];
+
+// Events.
+extern const char kEventLoadCommit[];
+
+} // namespace extensionview
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSION_VIEW_EXTENSION_OPTIONS_CONSTANTS_H_
diff --git a/chromium/extensions/browser/guest_view/extension_view/extension_view_guest.cc b/chromium/extensions/browser/guest_view/extension_view/extension_view_guest.cc
new file mode 100644
index 00000000000..0c961df1f19
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/extension_view/extension_view_guest.cc
@@ -0,0 +1,149 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/extension_view/extension_view_guest.h"
+
+#include <utility>
+
+#include "components/crx_file/id_util.h"
+#include "components/guest_view/browser/guest_view_event.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/common/result_codes.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/bad_message.h"
+#include "extensions/browser/guest_view/extension_view/extension_view_constants.h"
+#include "extensions/browser/guest_view/extension_view/whitelist/extension_view_whitelist.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/strings/grit/extensions_strings.h"
+#include "url/origin.h"
+
+using content::WebContents;
+using guest_view::GuestViewBase;
+using guest_view::GuestViewEvent;
+using namespace extensions::api;
+
+namespace extensions {
+
+// static
+const char ExtensionViewGuest::Type[] = "extensionview";
+
+ExtensionViewGuest::ExtensionViewGuest(WebContents* owner_web_contents)
+ : GuestView<ExtensionViewGuest>(owner_web_contents) {}
+
+ExtensionViewGuest::~ExtensionViewGuest() {
+}
+
+// static
+GuestViewBase* ExtensionViewGuest::Create(WebContents* owner_web_contents) {
+ return new ExtensionViewGuest(owner_web_contents);
+}
+
+bool ExtensionViewGuest::NavigateGuest(const std::string& src,
+ bool force_navigation) {
+ GURL url = extension_url_.Resolve(src);
+
+ // If the URL is not valid, about:blank, or the same origin as the extension,
+ // then navigate to about:blank.
+ bool url_not_allowed = url != GURL(url::kAboutBlankURL) &&
+ !url::IsSameOriginWith(url, extension_url_);
+ if (!url.is_valid() || url_not_allowed)
+ return NavigateGuest(url::kAboutBlankURL, true /* force_navigation */);
+
+ if (!force_navigation && (url_ == url))
+ return false;
+
+ web_contents()->GetRenderProcessHost()->FilterURL(false, &url);
+ web_contents()->GetController().LoadURL(url, content::Referrer(),
+ ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
+ std::string());
+
+ url_ = url;
+ return true;
+}
+
+// GuestViewBase implementation.
+bool ExtensionViewGuest::CanRunInDetachedState() const {
+ return true;
+}
+
+void ExtensionViewGuest::CreateWebContents(
+ const base::DictionaryValue& create_params,
+ const WebContentsCreatedCallback& callback) {
+ // Gets the extension ID.
+ std::string extension_id;
+ create_params.GetString(extensionview::kAttributeExtension, &extension_id);
+
+ if (!crx_file::id_util::IdIsValid(extension_id) ||
+ !IsExtensionIdWhitelisted(extension_id)) {
+ callback.Run(nullptr);
+ return;
+ }
+
+ // Gets the extension URL.
+ extension_url_ =
+ extensions::Extension::GetBaseURLFromExtensionId(extension_id);
+
+ if (!extension_url_.is_valid()) {
+ callback.Run(nullptr);
+ return;
+ }
+
+ WebContents::CreateParams params(
+ browser_context(),
+ content::SiteInstance::CreateForURL(browser_context(), extension_url_));
+ params.guest_delegate = this;
+ callback.Run(WebContents::Create(params));
+}
+
+void ExtensionViewGuest::DidInitialize(
+ const base::DictionaryValue& create_params) {
+ ExtensionsAPIClient::Get()->AttachWebContentsHelpers(web_contents());
+
+ ApplyAttributes(create_params);
+}
+
+void ExtensionViewGuest::DidAttachToEmbedder() {
+ ApplyAttributes(*attach_params());
+}
+
+const char* ExtensionViewGuest::GetAPINamespace() const {
+ return extensionview::kAPINamespace;
+}
+
+int ExtensionViewGuest::GetTaskPrefix() const {
+ return IDS_EXTENSION_TASK_MANAGER_EXTENSIONVIEW_TAG_PREFIX;
+}
+
+void ExtensionViewGuest::DidCommitProvisionalLoadForFrame(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& url,
+ ui::PageTransition transition_type) {
+ if (render_frame_host->GetParent())
+ return;
+
+ url_ = url;
+
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ args->SetString(guest_view::kUrl, url_.spec());
+ DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(extensionview::kEventLoadCommit, std::move(args))));
+}
+
+void ExtensionViewGuest::DidNavigateMainFrame(
+ const content::LoadCommittedDetails& details,
+ const content::FrameNavigateParams& params) {
+ if (attached() && !url::IsSameOriginWith(params.url, url_)) {
+ bad_message::ReceivedBadMessage(web_contents()->GetRenderProcessHost(),
+ bad_message::EVG_BAD_ORIGIN);
+ }
+}
+
+void ExtensionViewGuest::ApplyAttributes(const base::DictionaryValue& params) {
+ std::string src;
+ params.GetString(extensionview::kAttributeSrc, &src);
+ NavigateGuest(src, false /* force_navigation */);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/extension_view/extension_view_guest.h b/chromium/extensions/browser/guest_view/extension_view/extension_view_guest.h
new file mode 100644
index 00000000000..4a9e1248a6b
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/extension_view/extension_view_guest.h
@@ -0,0 +1,65 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSION_VIEW_EXTENSION_VIEW_GUEST_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSION_VIEW_EXTENSION_VIEW_GUEST_H_
+
+#include "base/macros.h"
+#include "components/guest_view/browser/guest_view.h"
+#include "extensions/browser/extension_function_dispatcher.h"
+#include "url/gurl.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class ExtensionViewGuest
+ : public guest_view::GuestView<ExtensionViewGuest> {
+ public:
+ static const char Type[];
+ static guest_view::GuestViewBase* Create(
+ content::WebContents* owner_web_contents);
+
+ // Request navigating the guest to the provided |src| URL.
+ // Returns true if the navigation is successful.
+ bool NavigateGuest(const std::string& src, bool force_navigation);
+
+ private:
+ ExtensionViewGuest(content::WebContents* owner_web_contents);
+ ~ExtensionViewGuest() override;
+
+ // GuestViewBase implementation.
+ bool CanRunInDetachedState() const final;
+ void CreateWebContents(const base::DictionaryValue& create_params,
+ const WebContentsCreatedCallback& callback) final;
+ void DidInitialize(const base::DictionaryValue& create_params) final;
+ void DidAttachToEmbedder() final;
+ const char* GetAPINamespace() const final;
+ int GetTaskPrefix() const final;
+
+ // content::WebContentsObserver implementation.
+ void DidCommitProvisionalLoadForFrame(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& url,
+ ui::PageTransition transition_type) final;
+ void DidNavigateMainFrame(const content::LoadCommittedDetails& details,
+ const content::FrameNavigateParams& params) final;
+
+ // Applies attributes to the extensionview.
+ void ApplyAttributes(const base::DictionaryValue& params);
+
+ // The full URL that the extensionview is currently navigated to.
+ GURL url_;
+
+ // The extension URL, including the extension scheme and extension ID.
+ GURL extension_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionViewGuest);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSION_VIEW_EXTENSION_VIEW_GUEST_H_
diff --git a/chromium/extensions/browser/guest_view/extension_view/whitelist/OWNERS b/chromium/extensions/browser/guest_view/extension_view/whitelist/OWNERS
new file mode 100644
index 00000000000..4b008986519
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/extension_view/whitelist/OWNERS
@@ -0,0 +1,11 @@
+# Whitelisting new extension ids for ExtensionView use requires approval from
+# chrome-eng-review@google.com.
+set noparent
+
+ben@chromium.org
+brettw@chromium.org
+cpu@chromium.org
+darin@chromium.org
+dglazkov@chromium.org
+jam@chromium.org
+jochen@chromium.org \ No newline at end of file
diff --git a/chromium/extensions/browser/guest_view/extension_view/whitelist/extension_view_whitelist.cc b/chromium/extensions/browser/guest_view/extension_view/whitelist/extension_view_whitelist.cc
new file mode 100644
index 00000000000..a08cf536f78
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/extension_view/whitelist/extension_view_whitelist.cc
@@ -0,0 +1,44 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/extension_view/whitelist/extension_view_whitelist.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+
+namespace extensions {
+
+namespace {
+
+// =============================================================================
+//
+// ADDING NEW EXTENSIONS REQUIRES APPROVAL from chrome-eng-review@google.com
+//
+// =============================================================================
+
+const char* const kWhitelist[] = {
+ "pemeknaakobkocgmimdeamlcklioagkh", // Used in browser tests
+ "dppcjffonoklmpdmljnpdojmoaefcabf", // Used in browser tests
+ "pkedcjkdefgpdelpbcmbmeomcjbeemfm", // http://crbug.com/574889
+ "ekpaaapppgpmolpcldedioblbkmijaca", // http://crbug.com/552208
+ "lhkfccafpkdlaodkicmokbmfapjadkij", // http://crbug.com/552208
+ "ibiljbkambkbohapfhoonkcpcikdglop", // http://crbug.com/552208
+ "enhhojjnijigcajfphajepfemndkmdlo", // http://crbug.com/552208
+};
+
+} // namespace
+
+// static
+bool IsExtensionIdWhitelisted(const std::string& extension_id) {
+ for (size_t i = 0; i < arraysize(kWhitelist); ++i) {
+ if (extension_id == kWhitelist[i])
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/extension_view/whitelist/extension_view_whitelist.h b/chromium/extensions/browser/guest_view/extension_view/whitelist/extension_view_whitelist.h
new file mode 100644
index 00000000000..512600b65d6
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/extension_view/whitelist/extension_view_whitelist.h
@@ -0,0 +1,17 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSION_VIEW_WHITELIST_EXTENSION_VIEW_WHITELIST_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSION_VIEW_WHITELIST_EXTENSION_VIEW_WHITELIST_H_
+
+#include <string>
+
+namespace extensions {
+
+// Checks whether |extension_id| is whitelisted to be used by ExtensionView.
+bool IsExtensionIdWhitelisted(const std::string& extension_id);
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSION_VIEW_WHITELIST_EXTENSION_VIEW_WHITELIST_H_
diff --git a/chromium/extensions/browser/guest_view/extensions_guest_view_manager_delegate.cc b/chromium/extensions/browser/guest_view/extensions_guest_view_manager_delegate.cc
new file mode 100644
index 00000000000..ec44ed2d595
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/extensions_guest_view_manager_delegate.cc
@@ -0,0 +1,104 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/extensions_guest_view_manager_delegate.h"
+
+#include <utility>
+
+#include "components/guest_view/browser/guest_view_base.h"
+#include "components/guest_view/browser/guest_view_manager.h"
+#include "components/guest_view/common/guest_view_constants.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/render_process_host.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/guest_view/app_view/app_view_guest.h"
+#include "extensions/browser/guest_view/extension_options/extension_options_guest.h"
+#include "extensions/browser/guest_view/extension_view/extension_view_guest.h"
+#include "extensions/browser/guest_view/guest_view_events.h"
+#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
+#include "extensions/browser/guest_view/web_view/web_view_guest.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/browser/process_map.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/features/feature_provider.h"
+
+using guest_view::GuestViewBase;
+using guest_view::GuestViewManager;
+
+namespace extensions {
+
+ExtensionsGuestViewManagerDelegate::ExtensionsGuestViewManagerDelegate(
+ content::BrowserContext* context)
+ : context_(context) {
+}
+
+ExtensionsGuestViewManagerDelegate::~ExtensionsGuestViewManagerDelegate() {
+}
+
+void ExtensionsGuestViewManagerDelegate::DispatchEvent(
+ const std::string& event_name,
+ scoped_ptr<base::DictionaryValue> args,
+ GuestViewBase* guest,
+ int instance_id) {
+ EventFilteringInfo info;
+ info.SetInstanceID(instance_id);
+ scoped_ptr<base::ListValue> event_args(new base::ListValue());
+ event_args->Append(args.release());
+
+ // GetEventHistogramValue maps guest view event names to their histogram
+ // value. It needs to be like this because the guest view component doesn't
+ // know about extensions, so GuestViewEvent can't have an
+ // extensions::events::HistogramValue as an argument.
+ events::HistogramValue histogram_value =
+ guest_view_events::GetEventHistogramValue(event_name);
+ DCHECK_NE(events::UNKNOWN, histogram_value) << "Event " << event_name
+ << " must have a histogram value";
+
+ content::WebContents* owner = guest->owner_web_contents();
+ EventRouter::DispatchEventToSender(owner, guest->browser_context(),
+ guest->owner_host(), histogram_value,
+ event_name, std::move(event_args),
+ EventRouter::USER_GESTURE_UNKNOWN, info);
+}
+
+bool ExtensionsGuestViewManagerDelegate::IsGuestAvailableToContext(
+ GuestViewBase* guest) {
+ const Feature* feature =
+ FeatureProvider::GetAPIFeature(guest->GetAPINamespace());
+ CHECK(feature);
+
+ ProcessMap* process_map = ProcessMap::Get(context_);
+ CHECK(process_map);
+
+ const Extension* owner_extension = ProcessManager::Get(context_)->
+ GetExtensionForWebContents(guest->owner_web_contents());
+
+ // Ok for |owner_extension| to be nullptr, the embedder might be WebUI.
+ Feature::Availability availability = feature->IsAvailableToContext(
+ owner_extension,
+ process_map->GetMostLikelyContextType(
+ owner_extension,
+ guest->owner_web_contents()->GetRenderProcessHost()->GetID()),
+ guest->GetOwnerSiteURL());
+
+ return availability.is_available();
+}
+
+bool ExtensionsGuestViewManagerDelegate::IsOwnedByExtension(
+ GuestViewBase* guest) {
+ return !!ProcessManager::Get(context_)->
+ GetExtensionForWebContents(guest->owner_web_contents());
+}
+
+void ExtensionsGuestViewManagerDelegate::RegisterAdditionalGuestViewTypes() {
+ GuestViewManager* manager = GuestViewManager::FromBrowserContext(context_);
+ manager->RegisterGuestViewType<AppViewGuest>();
+ manager->RegisterGuestViewType<ExtensionOptionsGuest>();
+ manager->RegisterGuestViewType<ExtensionViewGuest>();
+ manager->RegisterGuestViewType<MimeHandlerViewGuest>();
+ manager->RegisterGuestViewType<WebViewGuest>();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/extensions_guest_view_manager_delegate.h b/chromium/extensions/browser/guest_view/extensions_guest_view_manager_delegate.h
new file mode 100644
index 00000000000..8a2d649cf73
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/extensions_guest_view_manager_delegate.h
@@ -0,0 +1,39 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSIONS_GUEST_VIEW_MANAGER_DELEGATE_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSIONS_GUEST_VIEW_MANAGER_DELEGATE_H_
+
+#include "components/guest_view/browser/guest_view_manager_delegate.h"
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+namespace extensions {
+
+// ExtensionsGuestViewManagerDelegate implements GuestViewManager functionality
+// specific to Chromium builds that include the extensions module.
+class ExtensionsGuestViewManagerDelegate
+ : public guest_view::GuestViewManagerDelegate {
+ public:
+ explicit ExtensionsGuestViewManagerDelegate(content::BrowserContext* context);
+ ~ExtensionsGuestViewManagerDelegate() override;
+
+ // GuestViewManagerDelegate implementation.
+ void DispatchEvent(const std::string& event_name,
+ scoped_ptr<base::DictionaryValue> args,
+ guest_view::GuestViewBase* guest,
+ int instance_id) override;
+ bool IsGuestAvailableToContext(guest_view::GuestViewBase* guest) override;
+ bool IsOwnedByExtension(guest_view::GuestViewBase* guest) override;
+ void RegisterAdditionalGuestViewTypes() override;
+
+ private:
+ content::BrowserContext* const context_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSIONS_GUEST_VIEW_MANAGER_DELEGATE_H_
diff --git a/chromium/extensions/browser/guest_view/extensions_guest_view_message_filter.cc b/chromium/extensions/browser/guest_view/extensions_guest_view_message_filter.cc
new file mode 100644
index 00000000000..9bf71d146ea
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/extensions_guest_view_message_filter.cc
@@ -0,0 +1,187 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/extensions_guest_view_message_filter.h"
+
+#include "base/macros.h"
+#include "components/guest_view/browser/guest_view_base.h"
+#include "components/guest_view/browser/guest_view_manager.h"
+#include "components/guest_view/browser/guest_view_manager_delegate.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_constants.h"
+#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
+#include "extensions/browser/guest_view/web_view/web_view_content_script_manager.h"
+#include "extensions/browser/guest_view/web_view/web_view_guest.h"
+#include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
+#include "extensions/common/guest_view/extensions_guest_view_messages.h"
+#include "ipc/ipc_message_macros.h"
+
+using content::BrowserContext;
+using content::BrowserThread;
+using content::RenderFrameHost;
+using content::WebContents;
+using guest_view::GuestViewManager;
+using guest_view::GuestViewManagerDelegate;
+using guest_view::GuestViewMessageFilter;
+
+namespace extensions {
+
+const uint32_t ExtensionsGuestViewMessageFilter::kFilteredMessageClasses[] = {
+ GuestViewMsgStart, ExtensionsGuestViewMsgStart};
+
+ExtensionsGuestViewMessageFilter::ExtensionsGuestViewMessageFilter(
+ int render_process_id,
+ BrowserContext* context)
+ : GuestViewMessageFilter(kFilteredMessageClasses,
+ arraysize(kFilteredMessageClasses),
+ render_process_id,
+ context) {}
+
+ExtensionsGuestViewMessageFilter::~ExtensionsGuestViewMessageFilter() {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+}
+
+void ExtensionsGuestViewMessageFilter::OverrideThreadForMessage(
+ const IPC::Message& message,
+ BrowserThread::ID* thread) {
+ switch (message.type()) {
+ case ExtensionsGuestViewHostMsg_CreateMimeHandlerViewGuest::ID:
+ case ExtensionsGuestViewHostMsg_ResizeGuest::ID:
+ *thread = BrowserThread::UI;
+ break;
+ default:
+ GuestViewMessageFilter::OverrideThreadForMessage(message, thread);
+ }
+}
+
+bool ExtensionsGuestViewMessageFilter::OnMessageReceived(
+ const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(ExtensionsGuestViewMessageFilter, message)
+ IPC_MESSAGE_HANDLER(ExtensionsGuestViewHostMsg_CanExecuteContentScriptSync,
+ OnCanExecuteContentScript)
+ IPC_MESSAGE_HANDLER(ExtensionsGuestViewHostMsg_CreateMimeHandlerViewGuest,
+ OnCreateMimeHandlerViewGuest)
+ IPC_MESSAGE_HANDLER(ExtensionsGuestViewHostMsg_ResizeGuest, OnResizeGuest)
+ IPC_MESSAGE_UNHANDLED(
+ handled = GuestViewMessageFilter::OnMessageReceived(message))
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+GuestViewManager* ExtensionsGuestViewMessageFilter::
+ GetOrCreateGuestViewManager() {
+ auto manager = GuestViewManager::FromBrowserContext(browser_context_);
+ if (!manager) {
+ manager = GuestViewManager::CreateWithDelegate(
+ browser_context_,
+ ExtensionsAPIClient::Get()->CreateGuestViewManagerDelegate(
+ browser_context_));
+ }
+ return manager;
+}
+
+void ExtensionsGuestViewMessageFilter::OnCanExecuteContentScript(
+ int render_view_id,
+ int script_id,
+ bool* allowed) {
+ WebViewRendererState::WebViewInfo info;
+ WebViewRendererState::GetInstance()->GetInfo(render_process_id_,
+ render_view_id, &info);
+
+ *allowed =
+ info.content_script_ids.find(script_id) != info.content_script_ids.end();
+}
+
+void ExtensionsGuestViewMessageFilter::OnCreateMimeHandlerViewGuest(
+ int render_frame_id,
+ const std::string& view_id,
+ int element_instance_id,
+ const gfx::Size& element_size) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ auto manager = GetOrCreateGuestViewManager();
+
+ auto rfh = RenderFrameHost::FromID(render_process_id_, render_frame_id);
+ auto embedder_web_contents = WebContents::FromRenderFrameHost(rfh);
+ if (!embedder_web_contents)
+ return;
+
+ GuestViewManager::WebContentsCreatedCallback callback =
+ base::Bind(
+ &ExtensionsGuestViewMessageFilter::
+ MimeHandlerViewGuestCreatedCallback,
+ this,
+ element_instance_id,
+ render_process_id_,
+ render_frame_id,
+ element_size);
+
+ base::DictionaryValue create_params;
+ create_params.SetString(mime_handler_view::kViewId, view_id);
+ create_params.SetInteger(guest_view::kElementWidth, element_size.width());
+ create_params.SetInteger(guest_view::kElementHeight, element_size.height());
+ manager->CreateGuest(MimeHandlerViewGuest::Type,
+ embedder_web_contents,
+ create_params,
+ callback);
+}
+
+void ExtensionsGuestViewMessageFilter::OnResizeGuest(
+ int render_frame_id,
+ int element_instance_id,
+ const gfx::Size& new_size) {
+ auto manager = GuestViewManager::FromBrowserContext(browser_context_);
+ // We should have a GuestViewManager at this point. If we don't then the
+ // embedder is misbehaving.
+ if (!manager)
+ return;
+
+ auto guest_web_contents =
+ manager->GetGuestByInstanceID(render_process_id_, element_instance_id);
+ auto mhvg = MimeHandlerViewGuest::FromWebContents(guest_web_contents);
+ if (!mhvg)
+ return;
+
+ guest_view::SetSizeParams set_size_params;
+ set_size_params.enable_auto_size.reset(new bool(false));
+ set_size_params.normal_size.reset(new gfx::Size(new_size));
+ mhvg->SetSize(set_size_params);
+}
+
+void ExtensionsGuestViewMessageFilter::MimeHandlerViewGuestCreatedCallback(
+ int element_instance_id,
+ int embedder_render_process_id,
+ int embedder_render_frame_id,
+ const gfx::Size& element_size,
+ WebContents* web_contents) {
+ auto guest_view = MimeHandlerViewGuest::FromWebContents(web_contents);
+ if (!guest_view)
+ return;
+
+ int guest_instance_id = guest_view->guest_instance_id();
+ auto rfh = RenderFrameHost::FromID(embedder_render_process_id,
+ embedder_render_frame_id);
+ if (!rfh)
+ return;
+
+ base::DictionaryValue attach_params;
+ attach_params.SetInteger(guest_view::kElementWidth, element_size.width());
+ attach_params.SetInteger(guest_view::kElementHeight, element_size.height());
+ auto manager = GuestViewManager::FromBrowserContext(browser_context_);
+ CHECK(manager);
+ manager->AttachGuest(embedder_render_process_id,
+ element_instance_id,
+ guest_instance_id,
+ attach_params);
+
+ rfh->Send(
+ new ExtensionsGuestViewMsg_CreateMimeHandlerViewGuestACK(
+ element_instance_id));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/extensions_guest_view_message_filter.h b/chromium/extensions/browser/guest_view/extensions_guest_view_message_filter.h
new file mode 100644
index 00000000000..a8b7f52276f
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/extensions_guest_view_message_filter.h
@@ -0,0 +1,79 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSIONS_GUEST_VIEW_MESSAGE_FILTER_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSIONS_GUEST_VIEW_MESSAGE_FILTER_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "components/guest_view/browser/guest_view_message_filter.h"
+#include "content/public/browser/browser_message_filter.h"
+
+namespace content {
+class BrowserContext;
+class WebContents;
+}
+
+namespace gfx {
+class Size;
+}
+
+namespace guest_view {
+class GuestViewManager;
+}
+
+namespace extensions {
+
+// This class filters out incoming extensions GuestView-specific IPC messages
+// from thw renderer process. It is created on the UI thread. Messages may be
+// handled on the IO thread or the UI thread.
+class ExtensionsGuestViewMessageFilter
+ : public guest_view::GuestViewMessageFilter {
+ public:
+ ExtensionsGuestViewMessageFilter(int render_process_id,
+ content::BrowserContext* context);
+
+ private:
+ friend class content::BrowserThread;
+ friend class base::DeleteHelper<ExtensionsGuestViewMessageFilter>;
+
+ ~ExtensionsGuestViewMessageFilter() override;
+
+ // GuestViewMessageFilter implementation.
+ void OverrideThreadForMessage(const IPC::Message& message,
+ content::BrowserThread::ID* thread) override;
+ bool OnMessageReceived(const IPC::Message& message) override;
+ guest_view::GuestViewManager* GetOrCreateGuestViewManager() override;
+
+ // Message handlers on the UI thread.
+ void OnCanExecuteContentScript(int render_view_id,
+ int script_id,
+ bool* allowed);
+ void OnCreateMimeHandlerViewGuest(int render_frame_id,
+ const std::string& view_id,
+ int element_instance_id,
+ const gfx::Size& element_size);
+ void OnResizeGuest(int render_frame_id,
+ int element_instance_id,
+ const gfx::Size& new_size);
+
+ // Runs on UI thread.
+ void MimeHandlerViewGuestCreatedCallback(int element_instance_id,
+ int embedder_render_process_id,
+ int embedder_render_frame_id,
+ const gfx::Size& element_size,
+ content::WebContents* web_contents);
+
+ static const uint32_t kFilteredMessageClasses[];
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionsGuestViewMessageFilter);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_EXTENSIONS_GUEST_VIEW_MESSAGE_FILTER_H_
diff --git a/chromium/extensions/browser/guest_view/guest_view_events.cc b/chromium/extensions/browser/guest_view/guest_view_events.cc
new file mode 100644
index 00000000000..8e8c739d972
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/guest_view_events.cc
@@ -0,0 +1,110 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/guest_view_events.h"
+
+#include <map>
+
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "components/guest_view/common/guest_view_constants.h"
+#include "extensions/browser/guest_view/extension_options/extension_options_constants.h"
+#include "extensions/browser/guest_view/extension_view/extension_view_constants.h"
+#include "extensions/browser/guest_view/web_view/web_view_constants.h"
+#include "extensions/common/api/extension_options_internal.h"
+
+namespace extensions {
+namespace guest_view_events {
+
+namespace {
+
+class EventMap {
+ public:
+ EventMap() {
+ struct NameAndValue {
+ const char* name;
+ events::HistogramValue value;
+ } names_and_values[] = {
+ {webview::kEventContextMenuShow,
+ events::CHROME_WEB_VIEW_INTERNAL_ON_CONTEXT_MENU_SHOW},
+ {api::extension_options_internal::OnClose::kEventName,
+ events::EXTENSION_OPTIONS_INTERNAL_ON_CLOSE},
+ {api::extension_options_internal::OnLoad::kEventName,
+ events::EXTENSION_OPTIONS_INTERNAL_ON_LOAD},
+ {api::extension_options_internal::OnPreferredSizeChanged::kEventName,
+ events::EXTENSION_OPTIONS_INTERNAL_ON_PREFERRED_SIZE_CHANGED},
+ {extensionview::kEventLoadCommit,
+ events::EXTENSION_VIEW_INTERNAL_ON_LOAD_COMMIT},
+ {guest_view::kEventResize, events::GUEST_VIEW_INTERNAL_ON_RESIZE},
+ {webview::kEventBeforeRequest,
+ events::WEB_VIEW_INTERNAL_ON_BEFORE_REQUEST},
+ {webview::kEventBeforeSendHeaders,
+ events::WEB_VIEW_INTERNAL_ON_BEFORE_SEND_HEADERS},
+ {webview::kEventClose, events::WEB_VIEW_INTERNAL_ON_CLOSE},
+ {webview::kEventCompleted, events::WEB_VIEW_INTERNAL_ON_COMPLETED},
+ {webview::kEventConsoleMessage,
+ events::WEB_VIEW_INTERNAL_ON_CONSOLE_MESSAGE},
+ {webview::kEventContentLoad, events::WEB_VIEW_INTERNAL_ON_CONTENT_LOAD},
+ {webview::kEventDialog, events::WEB_VIEW_INTERNAL_ON_DIALOG},
+ {webview::kEventDropLink, events::WEB_VIEW_INTERNAL_ON_DROP_LINK},
+ {webview::kEventExit, events::WEB_VIEW_INTERNAL_ON_EXIT},
+ {webview::kEventExitFullscreen,
+ events::WEB_VIEW_INTERNAL_ON_EXIT_FULLSCREEN},
+ {webview::kEventFindReply, events::WEB_VIEW_INTERNAL_ON_FIND_REPLY},
+ {webview::kEventHeadersReceived,
+ events::WEB_VIEW_INTERNAL_ON_HEADERS_RECEIVED},
+ {webview::kEventFrameNameChanged,
+ events::WEB_VIEW_INTERNAL_ON_FRAME_NAME_CHANGED},
+ {webview::kEventLoadAbort, events::WEB_VIEW_INTERNAL_ON_LOAD_ABORT},
+ {webview::kEventLoadCommit, events::WEB_VIEW_INTERNAL_ON_LOAD_COMMIT},
+ {webview::kEventLoadProgress,
+ events::WEB_VIEW_INTERNAL_ON_LOAD_PROGRESS},
+ {webview::kEventLoadRedirect,
+ events::WEB_VIEW_INTERNAL_ON_LOAD_REDIRECT},
+ {webview::kEventLoadStart, events::WEB_VIEW_INTERNAL_ON_LOAD_START},
+ {webview::kEventLoadStop, events::WEB_VIEW_INTERNAL_ON_LOAD_STOP},
+ {webview::kEventNewWindow, events::WEB_VIEW_INTERNAL_ON_NEW_WINDOW},
+ {webview::kEventPermissionRequest,
+ events::WEB_VIEW_INTERNAL_ON_PERMISSION_REQUEST},
+ {webview::kEventResponseStarted,
+ events::WEB_VIEW_INTERNAL_ON_RESPONSE_STARTED},
+ {webview::kEventResponsive, events::WEB_VIEW_INTERNAL_ON_RESPONSIVE},
+ {webview::kEventSizeChanged, events::WEB_VIEW_INTERNAL_ON_SIZE_CHANGED},
+ {webview::kEventUnresponsive,
+ events::WEB_VIEW_INTERNAL_ON_UNRESPONSIVE},
+ {webview::kEventZoomChange, events::WEB_VIEW_INTERNAL_ON_ZOOM_CHANGE},
+ {webview::kEventAuthRequired,
+ events::WEB_VIEW_INTERNAL_ON_AUTH_REQUIRED},
+ {webview::kEventBeforeRedirect,
+ events::WEB_VIEW_INTERNAL_ON_BEFORE_REDIRECT},
+ {webview::kEventErrorOccurred,
+ events::WEB_VIEW_INTERNAL_ON_ERROR_OCCURRED},
+ {webview::kEventSendHeaders, events::WEB_VIEW_INTERNAL_ON_SEND_HEADERS},
+ };
+ for (const auto& name_and_value : names_and_values) {
+ values_[name_and_value.name] = name_and_value.value;
+ }
+ }
+
+ events::HistogramValue Get(const std::string& event_name) {
+ auto value = values_.find(event_name);
+ return value != values_.end() ? value->second : events::UNKNOWN;
+ }
+
+ private:
+ std::map<std::string, events::HistogramValue> values_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventMap);
+};
+
+base::LazyInstance<EventMap> g_event_map = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+events::HistogramValue GetEventHistogramValue(const std::string& event_name) {
+ return g_event_map.Get().Get(event_name);
+}
+
+} // namespace guest_view_events
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/guest_view_events.h b/chromium/extensions/browser/guest_view/guest_view_events.h
new file mode 100644
index 00000000000..ba7e496152f
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/guest_view_events.h
@@ -0,0 +1,23 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_GUEST_VIEW_EVENTS_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_GUEST_VIEW_EVENTS_H_
+
+#include <string>
+
+#include "extensions/browser/extension_event_histogram_value.h"
+
+namespace extensions {
+namespace guest_view_events {
+
+// Returns the events::HistogramValue for the |event_name| guest view event.
+// This knows about all events for all guest view types, whether web view,
+// extension options, the guest view base class, etc.
+events::HistogramValue GetEventHistogramValue(const std::string& event_name);
+
+} // namespace guest_view_events
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_GUEST_VIEW_EVENTS_H_
diff --git a/chromium/extensions/browser/guest_view/mime_handler_view/OWNERS b/chromium/extensions/browser/guest_view/mime_handler_view/OWNERS
new file mode 100644
index 00000000000..db781ac5adc
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/mime_handler_view/OWNERS
@@ -0,0 +1 @@
+raymes@chromium.org
diff --git a/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_stream_manager.cc b/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_stream_manager.cc
new file mode 100644
index 00000000000..b0b8feb6bef
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_stream_manager.cc
@@ -0,0 +1,206 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/mime_handler_view/mime_handler_stream_manager.h"
+
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
+
+namespace extensions {
+namespace {
+
+class MimeHandlerStreamManagerFactory
+ : public BrowserContextKeyedServiceFactory {
+ public:
+ MimeHandlerStreamManagerFactory();
+ static MimeHandlerStreamManagerFactory* GetInstance();
+ MimeHandlerStreamManager* Get(content::BrowserContext* context);
+
+ private:
+ // BrowserContextKeyedServiceFactory overrides.
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* profile) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+};
+
+MimeHandlerStreamManagerFactory::MimeHandlerStreamManagerFactory()
+ : BrowserContextKeyedServiceFactory(
+ "MimeHandlerStreamManager",
+ BrowserContextDependencyManager::GetInstance()) {
+}
+
+// static
+MimeHandlerStreamManagerFactory*
+MimeHandlerStreamManagerFactory::GetInstance() {
+ return base::Singleton<MimeHandlerStreamManagerFactory>::get();
+}
+
+MimeHandlerStreamManager* MimeHandlerStreamManagerFactory::Get(
+ content::BrowserContext* context) {
+ return static_cast<MimeHandlerStreamManager*>(
+ GetServiceForBrowserContext(context, true));
+}
+
+KeyedService* MimeHandlerStreamManagerFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ return new MimeHandlerStreamManager();
+}
+
+content::BrowserContext*
+MimeHandlerStreamManagerFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ return extensions::ExtensionsBrowserClient::Get()->GetOriginalContext(
+ context);
+}
+
+} // namespace
+
+// A WebContentsObserver that observes for a particular RenderFrameHost either
+// navigating or closing (including by crashing). This is necessary to ensure
+// that streams that aren't claimed by a MimeHandlerViewGuest are not leaked, by
+// aborting the stream if any of those events occurs.
+class MimeHandlerStreamManager::EmbedderObserver
+ : public content::WebContentsObserver {
+ public:
+ EmbedderObserver(MimeHandlerStreamManager* stream_manager,
+ int render_process_id,
+ int render_frame_id,
+ const std::string& view_id);
+
+ private:
+ // WebContentsObserver overrides.
+ void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
+ void RenderProcessGone(base::TerminationStatus status) override;
+ void DidStartProvisionalLoadForFrame(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& validated_url,
+ bool is_error_page,
+ bool is_iframe_srcdoc) override;
+ void WebContentsDestroyed() override;
+
+ void AbortStream();
+
+ bool IsTrackedRenderFrameHost(content::RenderFrameHost* render_frame_host);
+
+ MimeHandlerStreamManager* const stream_manager_;
+ const int render_process_id_;
+ const int render_frame_id_;
+ const std::string view_id_;
+};
+
+MimeHandlerStreamManager::MimeHandlerStreamManager()
+ : extension_registry_observer_(this) {
+}
+
+MimeHandlerStreamManager::~MimeHandlerStreamManager() {
+}
+
+// static
+MimeHandlerStreamManager* MimeHandlerStreamManager::Get(
+ content::BrowserContext* context) {
+ return MimeHandlerStreamManagerFactory::GetInstance()->Get(context);
+}
+
+void MimeHandlerStreamManager::AddStream(const std::string& view_id,
+ scoped_ptr<StreamContainer> stream,
+ int render_process_id,
+ int render_frame_id) {
+ streams_by_extension_id_[stream->extension_id()].insert(view_id);
+ auto result = streams_.insert(
+ std::make_pair(view_id, make_linked_ptr(stream.release())));
+ DCHECK(result.second);
+ embedder_observers_[view_id] = make_linked_ptr(
+ new EmbedderObserver(this, render_process_id, render_frame_id, view_id));
+}
+
+scoped_ptr<StreamContainer> MimeHandlerStreamManager::ReleaseStream(
+ const std::string& view_id) {
+ auto stream = streams_.find(view_id);
+ if (stream == streams_.end())
+ return nullptr;
+
+ scoped_ptr<StreamContainer> result =
+ make_scoped_ptr(stream->second.release());
+ streams_by_extension_id_[result->extension_id()].erase(view_id);
+ streams_.erase(stream);
+ embedder_observers_.erase(view_id);
+ return result;
+}
+
+void MimeHandlerStreamManager::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ auto streams = streams_by_extension_id_.find(extension->id());
+ if (streams == streams_by_extension_id_.end())
+ return;
+
+ for (const auto& view_id : streams->second) {
+ streams_.erase(view_id);
+ embedder_observers_.erase(view_id);
+ }
+ streams_by_extension_id_.erase(streams);
+}
+
+MimeHandlerStreamManager::EmbedderObserver::EmbedderObserver(
+ MimeHandlerStreamManager* stream_manager,
+ int render_process_id,
+ int render_frame_id,
+ const std::string& view_id)
+ : WebContentsObserver(content::WebContents::FromRenderFrameHost(
+ content::RenderFrameHost::FromID(render_process_id,
+ render_frame_id))),
+ stream_manager_(stream_manager),
+ render_process_id_(render_process_id),
+ render_frame_id_(render_frame_id),
+ view_id_(view_id) {
+}
+
+void MimeHandlerStreamManager::EmbedderObserver::RenderFrameDeleted(
+ content::RenderFrameHost* render_frame_host) {
+ if (!IsTrackedRenderFrameHost(render_frame_host))
+ return;
+
+ AbortStream();
+}
+
+void MimeHandlerStreamManager::EmbedderObserver::RenderProcessGone(
+ base::TerminationStatus status) {
+ AbortStream();
+}
+void MimeHandlerStreamManager::EmbedderObserver::
+ DidStartProvisionalLoadForFrame(content::RenderFrameHost* render_frame_host,
+ const GURL& validated_url,
+ bool is_error_page,
+ bool is_iframe_srcdoc) {
+ if (!IsTrackedRenderFrameHost(render_frame_host))
+ return;
+
+ AbortStream();
+}
+
+void MimeHandlerStreamManager::EmbedderObserver::WebContentsDestroyed() {
+ AbortStream();
+}
+
+void MimeHandlerStreamManager::EmbedderObserver::AbortStream() {
+ Observe(nullptr);
+ // This will cause the stream to be destroyed.
+ stream_manager_->ReleaseStream(view_id_);
+}
+
+bool MimeHandlerStreamManager::EmbedderObserver::IsTrackedRenderFrameHost(
+ content::RenderFrameHost* render_frame_host) {
+ return render_frame_host->GetRoutingID() == render_frame_id_ &&
+ render_frame_host->GetProcess()->GetID() == render_process_id_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_stream_manager.h b/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_stream_manager.h
new file mode 100644
index 00000000000..5e46ac31054
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_stream_manager.h
@@ -0,0 +1,71 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_MIME_HANDLER_VIEW_MIME_HANDLER_STREAM_MANAGER_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_MIME_HANDLER_VIEW_MIME_HANDLER_STREAM_MANAGER_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/scoped_observer.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/browser/extension_registry_observer.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+class Extension;
+class StreamContainer;
+
+// A container for streams that have not yet been claimed by a
+// MimeHandlerViewGuest. If the embedding RenderFrameHost is closed or navigates
+// away from the resource being streamed, the stream is aborted. This is
+// BrowserContext-keyed because mime handlers are extensions, which are
+// per-BrowserContext.
+class MimeHandlerStreamManager : public KeyedService,
+ public ExtensionRegistryObserver {
+ public:
+ MimeHandlerStreamManager();
+ ~MimeHandlerStreamManager() override;
+ static MimeHandlerStreamManager* Get(content::BrowserContext* context);
+
+ void AddStream(const std::string& view_id,
+ scoped_ptr<StreamContainer> stream,
+ int render_process_id,
+ int render_frame_id);
+
+ scoped_ptr<StreamContainer> ReleaseStream(const std::string& view_id);
+
+ // ExtensionRegistryObserver override.
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+
+ private:
+ class EmbedderObserver;
+
+ // Maps view id->StreamContainer to maintain their lifetime until they are
+ // used or removed.
+ std::map<std::string, linked_ptr<StreamContainer>> streams_;
+
+ // Maps extension id->view id for removing the associated streams when an
+ // extension is unloaded.
+ std::map<std::string, std::set<std::string>> streams_by_extension_id_;
+
+ // Maps view id->EmbedderObserver for maintaining the lifetime of the
+ // EmbedderObserver until it is removed.
+ std::map<std::string, linked_ptr<EmbedderObserver>> embedder_observers_;
+
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_MIME_HANDLER_VIEW_MIME_HANDLER_STREAM_MANAGER_H_
diff --git a/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_browsertest.cc b/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_browsertest.cc
new file mode 100644
index 00000000000..ec1c0ca9e86
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_browsertest.cc
@@ -0,0 +1,158 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/base_paths.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/guest_view/browser/test_guest_view_manager.h"
+#include "content/public/test/browser_test_utils.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/guest_view/extensions_guest_view_manager_delegate.h"
+#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
+#include "extensions/browser/guest_view/mime_handler_view/test_mime_handler_view_guest.h"
+#include "extensions/test/result_catcher.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+using extensions::ExtensionsAPIClient;
+using extensions::MimeHandlerViewGuest;
+using extensions::TestMimeHandlerViewGuest;
+using guest_view::GuestViewManager;
+using guest_view::GuestViewManagerDelegate;
+using guest_view::TestGuestViewManager;
+using guest_view::TestGuestViewManagerFactory;
+
+// The test extension id is set by the key value in the manifest.
+const char* kExtensionId = "oickdpebdnfbgkcaoklfcdhjniefkcji";
+
+class MimeHandlerViewTest : public ExtensionApiTest {
+ public:
+ MimeHandlerViewTest() {
+ GuestViewManager::set_factory_for_testing(&factory_);
+ }
+
+ ~MimeHandlerViewTest() override {}
+
+ // TODO(paulmeyer): This function is implemented over and over by the
+ // different GuestView test classes. It really needs to be refactored out to
+ // some kind of GuestViewTest base class.
+ TestGuestViewManager* GetGuestViewManager() {
+ TestGuestViewManager* manager = static_cast<TestGuestViewManager*>(
+ TestGuestViewManager::FromBrowserContext(browser()->profile()));
+ // TestGuestViewManager::WaitForSingleGuestCreated can and will get called
+ // before a guest is created. Since GuestViewManager is usually not created
+ // until the first guest is created, this means that |manager| will be
+ // nullptr if trying to use the manager to wait for the first guest. Because
+ // of this, the manager must be created here if it does not already exist.
+ if (!manager) {
+ manager = static_cast<TestGuestViewManager*>(
+ GuestViewManager::CreateWithDelegate(
+ browser()->profile(),
+ ExtensionsAPIClient::Get()->CreateGuestViewManagerDelegate(
+ browser()->profile())));
+ }
+ return manager;
+ }
+
+ const extensions::Extension* LoadTestExtension() {
+ const extensions::Extension* extension =
+ LoadExtension(test_data_dir_.AppendASCII("mime_handler_view"));
+ if (!extension)
+ return nullptr;
+
+ CHECK_EQ(std::string(kExtensionId), extension->id());
+
+ return extension;
+ }
+
+ void RunTestWithUrl(const GURL& url) {
+ // Use the testing subclass of MimeHandlerViewGuest.
+ GetGuestViewManager()->RegisterTestGuestViewType<MimeHandlerViewGuest>(
+ base::Bind(&TestMimeHandlerViewGuest::Create));
+
+ const extensions::Extension* extension = LoadTestExtension();
+ ASSERT_TRUE(extension);
+
+ extensions::ResultCatcher catcher;
+ ui_test_utils::NavigateToURL(browser(), url);
+
+ if (!catcher.GetNextResult())
+ FAIL() << catcher.message();
+ }
+
+ void RunTest(const std::string& path) {
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ embedded_test_server()->ServeFilesFromDirectory(
+ test_data_dir_.AppendASCII("mime_handler_view"));
+
+ RunTestWithUrl(embedded_test_server()->GetURL("/" + path));
+ }
+
+ private:
+ TestGuestViewManagerFactory factory_;
+};
+
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, PostMessage) {
+ RunTest("test_postmessage.html");
+}
+
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, Basic) {
+ RunTest("testBasic.csv");
+}
+
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, Embedded) {
+ RunTest("test_embedded.html");
+}
+
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, Iframe) {
+ RunTest("test_iframe.html");
+}
+
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, Abort) {
+ RunTest("testAbort.csv");
+}
+
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, NonAsciiHeaders) {
+ RunTest("testNonAsciiHeaders.csv");
+}
+
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, DataUrl) {
+ const char* kDataUrlCsv = "data:text/csv;base64,Y29udGVudCB0byByZWFkCg==";
+ RunTestWithUrl(GURL(kDataUrlCsv));
+}
+
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, EmbeddedDataUrlObject) {
+ RunTest("test_embedded_data_url_object.html");
+}
+
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, EmbeddedDataUrlEmbed) {
+ RunTest("test_embedded_data_url_embed.html");
+}
+
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, EmbeddedDataUrlLong) {
+ RunTest("test_embedded_data_url_long.html");
+}
+
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, ResizeBeforeAttach) {
+ // Delay the creation of the guest's WebContents in order to delay the guest's
+ // attachment to the embedder. This will allow us to resize the <object> tag
+ // after the guest is created, but before it is attached in
+ // "test_resize_before_attach.html".
+ TestMimeHandlerViewGuest::DelayNextCreateWebContents(500);
+ RunTest("test_resize_before_attach.html");
+
+ // Wait for the guest to attach.
+ content::WebContents* guest_web_contents =
+ GetGuestViewManager()->WaitForSingleGuestCreated();
+ TestMimeHandlerViewGuest* guest = static_cast<TestMimeHandlerViewGuest*>(
+ MimeHandlerViewGuest::FromWebContents(guest_web_contents));
+ guest->WaitForGuestAttached();
+
+ // Ensure that the guest has the correct size after it has attached.
+ auto guest_size = guest->size();
+ CHECK_EQ(guest_size.width(), 500);
+ CHECK_EQ(guest_size.height(), 400);
+}
diff --git a/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_constants.cc b/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_constants.cc
new file mode 100644
index 00000000000..16e231be6fa
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_constants.cc
@@ -0,0 +1,11 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_constants.h"
+
+namespace mime_handler_view {
+
+const char kViewId[] = "viewId";
+
+} // namespace mime_handler_view
diff --git a/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_constants.h b/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_constants.h
new file mode 100644
index 00000000000..f894f64f785
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_constants.h
@@ -0,0 +1,16 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Constants used by MimeHandlerView..
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_MIME_HANDLER_VIEW_MIME_HANDLER_VIEW_CONSTANTS_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_MIME_HANDLER_VIEW_MIME_HANDLER_VIEW_CONSTANTS_H_
+
+namespace mime_handler_view {
+
+extern const char kViewId[];
+
+} // namespace mime_handler_view
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_MIME_HANDLER_VIEW_MIME_HANDLER_VIEW_CONSTANTS_H_
diff --git a/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.cc b/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.cc
new file mode 100644
index 00000000000..27297f33a6e
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.cc
@@ -0,0 +1,244 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
+
+#include <utility>
+
+#include "base/strings/stringprintf.h"
+#include "components/guest_view/common/guest_view_constants.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/host_zoom_map.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/stream_handle.h"
+#include "content/public/browser/stream_info.h"
+#include "content/public/common/service_registry.h"
+#include "content/public/common/url_constants.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/api/mime_handler_private/mime_handler_private.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/guest_view/mime_handler_view/mime_handler_stream_manager.h"
+#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_constants.h"
+#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest_delegate.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/guest_view/extensions_guest_view_messages.h"
+#include "extensions/strings/grit/extensions_strings.h"
+#include "ipc/ipc_message_macros.h"
+#include "net/base/url_util.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+
+using content::WebContents;
+using guest_view::GuestViewBase;
+
+namespace extensions {
+
+StreamContainer::StreamContainer(scoped_ptr<content::StreamInfo> stream,
+ int tab_id,
+ bool embedded,
+ const GURL& handler_url,
+ const std::string& extension_id)
+ : stream_(std::move(stream)),
+ embedded_(embedded),
+ tab_id_(tab_id),
+ handler_url_(handler_url),
+ extension_id_(extension_id),
+ weak_factory_(this) {
+ DCHECK(stream_);
+}
+
+StreamContainer::~StreamContainer() {
+}
+
+void StreamContainer::Abort(const base::Closure& callback) {
+ if (!stream_->handle) {
+ callback.Run();
+ return;
+ }
+ stream_->handle->AddCloseListener(callback);
+ stream_->handle.reset();
+}
+
+base::WeakPtr<StreamContainer> StreamContainer::GetWeakPtr() {
+ return weak_factory_.GetWeakPtr();
+}
+
+// static
+const char MimeHandlerViewGuest::Type[] = "mimehandler";
+
+// static
+GuestViewBase* MimeHandlerViewGuest::Create(WebContents* owner_web_contents) {
+ return new MimeHandlerViewGuest(owner_web_contents);
+}
+
+MimeHandlerViewGuest::MimeHandlerViewGuest(WebContents* owner_web_contents)
+ : GuestView<MimeHandlerViewGuest>(owner_web_contents),
+ delegate_(ExtensionsAPIClient::Get()->CreateMimeHandlerViewGuestDelegate(
+ this)) {}
+
+MimeHandlerViewGuest::~MimeHandlerViewGuest() {
+}
+
+const char* MimeHandlerViewGuest::GetAPINamespace() const {
+ return "mimeHandlerViewGuestInternal";
+}
+
+int MimeHandlerViewGuest::GetTaskPrefix() const {
+ return IDS_EXTENSION_TASK_MANAGER_MIMEHANDLERVIEW_TAG_PREFIX;
+}
+
+void MimeHandlerViewGuest::CreateWebContents(
+ const base::DictionaryValue& create_params,
+ const WebContentsCreatedCallback& callback) {
+ create_params.GetString(mime_handler_view::kViewId, &view_id_);
+ if (view_id_.empty()) {
+ callback.Run(nullptr);
+ return;
+ }
+ stream_ =
+ MimeHandlerStreamManager::Get(browser_context())->ReleaseStream(view_id_);
+ if (!stream_) {
+ callback.Run(nullptr);
+ return;
+ }
+ const Extension* mime_handler_extension =
+ // TODO(lazyboy): Do we need handle the case where the extension is
+ // terminated (ExtensionRegistry::TERMINATED)?
+ ExtensionRegistry::Get(browser_context())
+ ->enabled_extensions()
+ .GetByID(stream_->extension_id());
+ if (!mime_handler_extension) {
+ LOG(ERROR) << "Extension for mime_type not found, mime_type = "
+ << stream_->stream_info()->mime_type;
+ callback.Run(nullptr);
+ return;
+ }
+
+ // Use the mime handler extension's SiteInstance to create the guest so it
+ // goes under the same process as the extension.
+ ProcessManager* process_manager = ProcessManager::Get(browser_context());
+ scoped_refptr<content::SiteInstance> guest_site_instance =
+ process_manager->GetSiteInstanceForURL(stream_->handler_url());
+
+ // Clear the zoom level for the mime handler extension. The extension is
+ // responsible for managing its own zoom. This is necessary for OOP PDF, as
+ // otherwise the UI is zoomed and the calculations to determine the PDF size
+ // mix zoomed and unzoomed units.
+ content::HostZoomMap::Get(guest_site_instance.get())
+ ->SetZoomLevelForHostAndScheme(kExtensionScheme, stream_->extension_id(),
+ 0);
+
+ WebContents::CreateParams params(browser_context(),
+ guest_site_instance.get());
+ params.guest_delegate = this;
+ callback.Run(WebContents::Create(params));
+}
+
+void MimeHandlerViewGuest::DidAttachToEmbedder() {
+ web_contents()->GetController().LoadURL(
+ stream_->handler_url(), content::Referrer(),
+ ui::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string());
+ web_contents()->GetMainFrame()->GetServiceRegistry()->AddService(
+ base::Bind(&MimeHandlerServiceImpl::Create, stream_->GetWeakPtr()));
+}
+
+void MimeHandlerViewGuest::DidInitialize(
+ const base::DictionaryValue& create_params) {
+ ExtensionsAPIClient::Get()->AttachWebContentsHelpers(web_contents());
+}
+
+bool MimeHandlerViewGuest::ShouldHandleFindRequestsForEmbedder() const {
+ return is_full_page_plugin();
+}
+
+bool MimeHandlerViewGuest::ZoomPropagatesFromEmbedderToGuest() const {
+ return false;
+}
+
+WebContents* MimeHandlerViewGuest::OpenURLFromTab(
+ WebContents* source,
+ const content::OpenURLParams& params) {
+ return embedder_web_contents()->GetDelegate()->OpenURLFromTab(
+ embedder_web_contents(), params);
+}
+
+void MimeHandlerViewGuest::NavigationStateChanged(
+ WebContents* source,
+ content::InvalidateTypes changed_flags) {
+ if (!(changed_flags & content::INVALIDATE_TYPE_TITLE))
+ return;
+
+ // Only consider title changes not triggered by URL changes. Otherwise, the
+ // URL of the mime handler will be displayed.
+ if (changed_flags & content::INVALIDATE_TYPE_URL)
+ return;
+
+ if (!is_full_page_plugin())
+ return;
+
+ content::NavigationEntry* last_committed_entry =
+ embedder_web_contents()->GetController().GetLastCommittedEntry();
+ if (last_committed_entry) {
+ last_committed_entry->SetTitle(source->GetTitle());
+ embedder_web_contents()->GetDelegate()->NavigationStateChanged(
+ embedder_web_contents(), changed_flags);
+ }
+}
+
+bool MimeHandlerViewGuest::HandleContextMenu(
+ const content::ContextMenuParams& params) {
+ if (delegate_)
+ return delegate_->HandleContextMenu(web_contents(), params);
+
+ return false;
+}
+
+bool MimeHandlerViewGuest::PreHandleGestureEvent(
+ WebContents* source,
+ const blink::WebGestureEvent& event) {
+ if (event.type == blink::WebGestureEvent::GesturePinchBegin ||
+ event.type == blink::WebGestureEvent::GesturePinchUpdate ||
+ event.type == blink::WebGestureEvent::GesturePinchEnd) {
+ // If we're an embedded plugin we drop pinch-gestures to avoid zooming the
+ // guest.
+ return !is_full_page_plugin();
+ }
+ return false;
+}
+
+content::JavaScriptDialogManager*
+MimeHandlerViewGuest::GetJavaScriptDialogManager(
+ WebContents* source) {
+ return owner_web_contents()->GetDelegate()->GetJavaScriptDialogManager(
+ web_contents());
+}
+
+bool MimeHandlerViewGuest::SaveFrame(const GURL& url,
+ const content::Referrer& referrer) {
+ if (!attached())
+ return false;
+
+ embedder_web_contents()->SaveFrame(stream_->stream_info()->original_url,
+ referrer);
+ return true;
+}
+
+void MimeHandlerViewGuest::DocumentOnLoadCompletedInMainFrame() {
+ // Assume the embedder WebContents is valid here.
+ DCHECK(embedder_web_contents());
+
+ embedder_web_contents()->Send(
+ new ExtensionsGuestViewMsg_MimeHandlerViewGuestOnLoadCompleted(
+ element_instance_id()));
+}
+
+base::WeakPtr<StreamContainer> MimeHandlerViewGuest::GetStream() const {
+ if (!stream_)
+ return base::WeakPtr<StreamContainer>();
+ return stream_->GetWeakPtr();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h b/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h
new file mode 100644
index 00000000000..4618d9f86fa
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h
@@ -0,0 +1,106 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_MIME_HANDLER_VIEW_MIME_HANDLER_VIEW_GUEST_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_MIME_HANDLER_VIEW_MIME_HANDLER_VIEW_GUEST_H_
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "components/guest_view/browser/guest_view.h"
+
+namespace content {
+class WebContents;
+struct ContextMenuParams;
+struct StreamInfo;
+} // namespace content
+
+namespace extensions {
+class MimeHandlerViewGuestDelegate;
+
+// A container for a StreamHandle and any other information necessary for a
+// MimeHandler to handle a resource stream.
+class StreamContainer {
+ public:
+ StreamContainer(scoped_ptr<content::StreamInfo> stream,
+ int tab_id,
+ bool embedded,
+ const GURL& handler_url,
+ const std::string& extension_id);
+ ~StreamContainer();
+
+ // Aborts the stream.
+ void Abort(const base::Closure& callback);
+
+ base::WeakPtr<StreamContainer> GetWeakPtr();
+
+ const content::StreamInfo* stream_info() const { return stream_.get(); }
+ bool embedded() const { return embedded_; }
+ int tab_id() const { return tab_id_; }
+ GURL handler_url() const { return handler_url_; }
+ std::string extension_id() const { return extension_id_; }
+
+ private:
+ const scoped_ptr<content::StreamInfo> stream_;
+ const bool embedded_;
+ const int tab_id_;
+ const GURL handler_url_;
+ const std::string extension_id_;
+
+ base::WeakPtrFactory<StreamContainer> weak_factory_;
+};
+
+class MimeHandlerViewGuest :
+ public guest_view::GuestView<MimeHandlerViewGuest> {
+ public:
+ static guest_view::GuestViewBase* Create(
+ content::WebContents* owner_web_contents);
+
+ static const char Type[];
+
+ protected:
+ explicit MimeHandlerViewGuest(content::WebContents* owner_web_contents);
+ ~MimeHandlerViewGuest() override;
+
+ private:
+ friend class TestMimeHandlerViewGuest;
+
+ // GuestViewBase implementation.
+ const char* GetAPINamespace() const final;
+ int GetTaskPrefix() const final;
+ void CreateWebContents(const base::DictionaryValue& create_params,
+ const WebContentsCreatedCallback& callback) override;
+ void DidAttachToEmbedder() override;
+ void DidInitialize(const base::DictionaryValue& create_params) final;
+ bool ShouldHandleFindRequestsForEmbedder() const final;
+ bool ZoomPropagatesFromEmbedderToGuest() const final;
+
+ // WebContentsDelegate implementation.
+ content::WebContents* OpenURLFromTab(
+ content::WebContents* source,
+ const content::OpenURLParams& params) final;
+ void NavigationStateChanged(content::WebContents* source,
+ content::InvalidateTypes changed_flags) final;
+ bool HandleContextMenu(const content::ContextMenuParams& params) final;
+ bool PreHandleGestureEvent(content::WebContents* source,
+ const blink::WebGestureEvent& event) final;
+ content::JavaScriptDialogManager* GetJavaScriptDialogManager(
+ content::WebContents* source) final;
+ bool SaveFrame(const GURL& url, const content::Referrer& referrer) final;
+
+ // content::WebContentsObserver implementation.
+ void DocumentOnLoadCompletedInMainFrame() final;
+
+ std::string view_id() const { return view_id_; }
+ base::WeakPtr<StreamContainer> GetStream() const;
+
+ scoped_ptr<MimeHandlerViewGuestDelegate> delegate_;
+ scoped_ptr<StreamContainer> stream_;
+ std::string view_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(MimeHandlerViewGuest);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_MIME_HANDLER_VIEW_MIME_HANDLER_VIEW_GUEST_H_
diff --git a/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest_delegate.cc b/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest_delegate.cc
new file mode 100644
index 00000000000..63b81b8a76c
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest_delegate.cc
@@ -0,0 +1,15 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest_delegate.h"
+
+namespace extensions {
+
+bool MimeHandlerViewGuestDelegate::HandleContextMenu(
+ content::WebContents* web_contents,
+ const content::ContextMenuParams& params) {
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest_delegate.h b/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest_delegate.h
new file mode 100644
index 00000000000..e065104dbec
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest_delegate.h
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_MIME_HANDLER_VIEW_MIME_HANDLER_VIEW_GUEST_DELEGATE_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_MIME_HANDLER_VIEW_MIME_HANDLER_VIEW_GUEST_DELEGATE_H_
+
+#include "base/macros.h"
+
+namespace content {
+class WebContents;
+struct ContextMenuParams;
+} // namespace content
+
+namespace extensions {
+
+class MimeHandlerViewGuest;
+
+// A delegate class of MimeHandlerViewGuest that are not a part of chrome.
+class MimeHandlerViewGuestDelegate {
+ public:
+ MimeHandlerViewGuestDelegate() {}
+ virtual ~MimeHandlerViewGuestDelegate() {}
+
+ // Handles context menu, or returns false if unhandled.
+ virtual bool HandleContextMenu(content::WebContents* web_contents,
+ const content::ContextMenuParams& params);
+
+ // Request to change the zoom level of the top level page containing
+ // this view.
+ virtual void ChangeZoom(bool zoom_in) {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MimeHandlerViewGuestDelegate);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_MIME_HANDLER_VIEW_MIME_HANDLER_VIEW_GUEST_DELEGATE_H_
diff --git a/chromium/extensions/browser/guest_view/mime_handler_view/test_mime_handler_view_guest.cc b/chromium/extensions/browser/guest_view/mime_handler_view/test_mime_handler_view_guest.cc
new file mode 100644
index 00000000000..ce68bdb589f
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/mime_handler_view/test_mime_handler_view_guest.cc
@@ -0,0 +1,80 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/mime_handler_view/test_mime_handler_view_guest.h"
+
+#include "base/time/time.h"
+#include "content/public/test/test_utils.h"
+
+using guest_view::GuestViewBase;
+
+namespace extensions {
+
+TestMimeHandlerViewGuest::TestMimeHandlerViewGuest(
+ content::WebContents* owner_web_contents)
+ : MimeHandlerViewGuest(owner_web_contents),
+ weak_ptr_factory_(this) {}
+
+TestMimeHandlerViewGuest::~TestMimeHandlerViewGuest() {}
+
+// static
+GuestViewBase* TestMimeHandlerViewGuest::Create(
+ content::WebContents* owner_web_contents) {
+ return new TestMimeHandlerViewGuest(owner_web_contents);
+}
+
+// static
+void TestMimeHandlerViewGuest::DelayNextCreateWebContents(int delay) {
+ TestMimeHandlerViewGuest::delay_ = delay;
+}
+
+void TestMimeHandlerViewGuest::WaitForGuestAttached() {
+ if (attached())
+ return;
+ created_message_loop_runner_ = new content::MessageLoopRunner;
+ created_message_loop_runner_->Run();
+}
+
+void TestMimeHandlerViewGuest::CreateWebContents(
+ const base::DictionaryValue& create_params,
+ const WebContentsCreatedCallback& callback) {
+ // Delay the creation of the guest's WebContents if |delay_| is set.
+ if (delay_) {
+ auto delta = base::TimeDelta::FromMilliseconds(
+ delay_);
+ scoped_ptr<base::DictionaryValue> params(create_params.DeepCopy());
+ content::BrowserThread::PostDelayedTask(
+ content::BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&TestMimeHandlerViewGuest::CallBaseCreateWebContents,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Passed(&params),
+ callback),
+ delta);
+
+ // Reset the delay for the next creation.
+ delay_ = 0;
+ return;
+ }
+
+ MimeHandlerViewGuest::CreateWebContents(create_params, callback);
+}
+
+void TestMimeHandlerViewGuest::DidAttachToEmbedder() {
+ MimeHandlerViewGuest::DidAttachToEmbedder();
+ if (created_message_loop_runner_.get())
+ created_message_loop_runner_->Quit();
+}
+
+void TestMimeHandlerViewGuest::CallBaseCreateWebContents(
+ scoped_ptr<base::DictionaryValue> create_params,
+ const WebContentsCreatedCallback& callback) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ MimeHandlerViewGuest::CreateWebContents(*create_params.get(), callback);
+}
+
+// static
+int TestMimeHandlerViewGuest::delay_ = 0;
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/mime_handler_view/test_mime_handler_view_guest.h b/chromium/extensions/browser/guest_view/mime_handler_view/test_mime_handler_view_guest.h
new file mode 100644
index 00000000000..aac62c11724
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/mime_handler_view/test_mime_handler_view_guest.h
@@ -0,0 +1,64 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_MIME_HANDLER_VIEW_TEST_MIME_HANDLER_VIEW_GUEST_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_MIME_HANDLER_VIEW_TEST_MIME_HANDLER_VIEW_GUEST_H_
+
+#include "base/macros.h"
+#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
+
+using guest_view::GuestViewBase;
+
+namespace content {
+class MessageLoopRunner;
+} // namespace content
+
+namespace extensions {
+
+// TestMimeHandlerViewGuest is used instead of its base class,
+// MimeHandlerViewGuest, during MimeHandlerView tests. It allows for more
+// control over the MimeHandlerViewGuest for the purposes of testing.
+class TestMimeHandlerViewGuest : public MimeHandlerViewGuest {
+ public:
+ static GuestViewBase* Create(content::WebContents* owner_web_contents);
+
+ // Set a delay in the next creation of a guest's WebContents by |delay|
+ // milliseconds.
+ static void DelayNextCreateWebContents(int delay);
+
+ // Wait until the guest has attached to the embedder.
+ void WaitForGuestAttached();
+
+ // MimeHandlerViewGuest override:
+ void CreateWebContents(const base::DictionaryValue& create_params,
+ const WebContentsCreatedCallback& callback) override;
+ void DidAttachToEmbedder() override;
+
+ private:
+ explicit TestMimeHandlerViewGuest(content::WebContents* owner_web_contents);
+ ~TestMimeHandlerViewGuest() override;
+
+ // Used to call MimeHandlerViewGuest::CreateWebContents using a scoped_ptr for
+ // |create_params|.
+ void CallBaseCreateWebContents(
+ scoped_ptr<base::DictionaryValue> create_params,
+ const WebContentsCreatedCallback& callback);
+
+ // A value in milliseconds that the next creation of a guest's WebContents
+ // will be delayed. After this creation is delayed, |delay_| will be reset to
+ // 0.
+ static int delay_;
+
+ scoped_refptr<content::MessageLoopRunner> created_message_loop_runner_;
+
+ // This is used to ensure pending tasks will not fire after this object is
+ // destroyed.
+ base::WeakPtrFactory<TestMimeHandlerViewGuest> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestMimeHandlerViewGuest);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_MIME_HANDLER_VIEW_TEST_MIME_HANDLER_VIEW_GUEST_H_
diff --git a/chromium/extensions/browser/guest_view/web_view/javascript_dialog_helper.cc b/chromium/extensions/browser/guest_view/web_view/javascript_dialog_helper.cc
new file mode 100644
index 00000000000..bc55c9eed08
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/javascript_dialog_helper.cc
@@ -0,0 +1,104 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/web_view/javascript_dialog_helper.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "components/guest_view/common/guest_view_constants.h"
+#include "extensions/browser/guest_view/web_view/web_view_constants.h"
+#include "extensions/browser/guest_view/web_view/web_view_guest.h"
+#include "extensions/browser/guest_view/web_view/web_view_permission_helper.h"
+#include "extensions/browser/guest_view/web_view/web_view_permission_types.h"
+
+namespace extensions {
+
+namespace {
+
+std::string JavaScriptMessageTypeToString(
+ content::JavaScriptMessageType message_type) {
+ switch (message_type) {
+ case content::JAVASCRIPT_MESSAGE_TYPE_ALERT:
+ return "alert";
+ case content::JAVASCRIPT_MESSAGE_TYPE_CONFIRM:
+ return "confirm";
+ case content::JAVASCRIPT_MESSAGE_TYPE_PROMPT:
+ return "prompt";
+ default:
+ NOTREACHED() << "Unknown JavaScript Message Type.";
+ return "unknown";
+ }
+}
+
+} // namespace
+
+JavaScriptDialogHelper::JavaScriptDialogHelper(WebViewGuest* guest)
+ : web_view_guest_(guest) {
+}
+
+JavaScriptDialogHelper::~JavaScriptDialogHelper() {
+}
+
+void JavaScriptDialogHelper::RunJavaScriptDialog(
+ content::WebContents* web_contents,
+ const GURL& origin_url,
+ content::JavaScriptMessageType javascript_message_type,
+ const base::string16& message_text,
+ const base::string16& default_prompt_text,
+ const DialogClosedCallback& callback,
+ bool* did_suppress_message) {
+ base::DictionaryValue request_info;
+ request_info.Set(
+ webview::kDefaultPromptText,
+ new base::StringValue(base::UTF16ToUTF8(default_prompt_text)));
+ request_info.Set(webview::kMessageText,
+ new base::StringValue(base::UTF16ToUTF8(message_text)));
+ request_info.Set(webview::kMessageType,
+ new base::StringValue(
+ JavaScriptMessageTypeToString(javascript_message_type)));
+ request_info.Set(guest_view::kUrl, new base::StringValue(origin_url.spec()));
+ WebViewPermissionHelper* web_view_permission_helper =
+ WebViewPermissionHelper::FromWebContents(web_contents);
+ web_view_permission_helper->RequestPermission(
+ WEB_VIEW_PERMISSION_TYPE_JAVASCRIPT_DIALOG,
+ request_info,
+ base::Bind(&JavaScriptDialogHelper::OnPermissionResponse,
+ base::Unretained(this),
+ callback),
+ false /* allowed_by_default */);
+}
+
+void JavaScriptDialogHelper::RunBeforeUnloadDialog(
+ content::WebContents* web_contents,
+ bool is_reload,
+ const DialogClosedCallback& callback) {
+ // This is called if the guest has a beforeunload event handler.
+ // This callback allows navigation to proceed.
+ callback.Run(true, base::string16());
+}
+
+bool JavaScriptDialogHelper::HandleJavaScriptDialog(
+ content::WebContents* web_contents,
+ bool accept,
+ const base::string16* prompt_override) {
+ return false;
+}
+
+void JavaScriptDialogHelper::CancelActiveAndPendingDialogs(
+ content::WebContents* web_contents) {
+}
+
+void JavaScriptDialogHelper::ResetDialogState(
+ content::WebContents* web_contents) {
+}
+
+void JavaScriptDialogHelper::OnPermissionResponse(
+ const DialogClosedCallback& callback,
+ bool allow,
+ const std::string& user_input) {
+ callback.Run(allow && web_view_guest_->attached(),
+ base::UTF8ToUTF16(user_input));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/web_view/javascript_dialog_helper.h b/chromium/extensions/browser/guest_view/web_view/javascript_dialog_helper.h
new file mode 100644
index 00000000000..2be5bbbc4d1
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/javascript_dialog_helper.h
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_JAVASCRIPT_DIALOG_HELPER_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_JAVASCRIPT_DIALOG_HELPER_H_
+
+#include "base/macros.h"
+#include "content/public/browser/javascript_dialog_manager.h"
+
+namespace extensions {
+
+class WebViewGuest;
+
+class JavaScriptDialogHelper : public content::JavaScriptDialogManager {
+ public:
+ explicit JavaScriptDialogHelper(WebViewGuest* guest);
+ ~JavaScriptDialogHelper() override;
+
+ // JavaScriptDialogManager implementation.
+ void RunJavaScriptDialog(
+ content::WebContents* web_contents,
+ const GURL& origin_url,
+ content::JavaScriptMessageType javascript_message_type,
+ const base::string16& message_text,
+ const base::string16& default_prompt_text,
+ const DialogClosedCallback& callback,
+ bool* did_suppress_message) override;
+ void RunBeforeUnloadDialog(content::WebContents* web_contents,
+ bool is_reload,
+ const DialogClosedCallback& callback) override;
+ bool HandleJavaScriptDialog(content::WebContents* web_contents,
+ bool accept,
+ const base::string16* prompt_override) override;
+ void CancelActiveAndPendingDialogs(
+ content::WebContents* web_contents) override;
+ void ResetDialogState(content::WebContents* web_contents) override;
+
+ private:
+ void OnPermissionResponse(
+ const DialogClosedCallback& callback,
+ bool allow,
+ const std::string& user_input);
+
+ // Pointer to the webview that is being helped.
+ WebViewGuest* const web_view_guest_;
+
+ DISALLOW_COPY_AND_ASSIGN(JavaScriptDialogHelper);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_JAVASCRIPT_DIALOG_HELPER_H_
diff --git a/chromium/extensions/browser/guest_view/web_view/web_ui/web_ui_url_fetcher.cc b/chromium/extensions/browser/guest_view/web_view/web_ui/web_ui_url_fetcher.cc
new file mode 100644
index 00000000000..9c2b738d07e
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_ui/web_ui_url_fetcher.cc
@@ -0,0 +1,52 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/web_view/web_ui/web_ui_url_fetcher.h"
+
+#include "content/public/browser/browser_context.h"
+#include "content/public/common/url_fetcher.h"
+#include "net/base/load_flags.h"
+#include "net/url_request/url_fetcher.h"
+
+WebUIURLFetcher::WebUIURLFetcher(content::BrowserContext* context,
+ int render_process_id,
+ int render_view_id,
+ const GURL& url,
+ const WebUILoadFileCallback& callback)
+ : context_(context),
+ render_process_id_(render_process_id),
+ render_view_id_(render_view_id),
+ url_(url),
+ callback_(callback) {
+}
+
+WebUIURLFetcher::~WebUIURLFetcher() {
+}
+
+void WebUIURLFetcher::Start() {
+ fetcher_ = net::URLFetcher::Create(url_, net::URLFetcher::GET, this);
+ fetcher_->SetRequestContext(context_->GetRequestContext());
+ fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES);
+
+ content::AssociateURLFetcherWithRenderFrame(
+ fetcher_.get(), url_, render_process_id_, render_view_id_);
+ fetcher_->Start();
+}
+
+void WebUIURLFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
+ CHECK_EQ(fetcher_.get(), source);
+
+ std::string data;
+ bool result = false;
+ if (fetcher_->GetStatus().status() == net::URLRequestStatus::SUCCESS) {
+ result = fetcher_->GetResponseAsString(&data);
+ DCHECK(result);
+ }
+ fetcher_.reset();
+ // We cache the callback and reset it so that any references stored within it
+ // are destroyed at the end of the method.
+ auto callback_cache = callback_;
+ callback_.Reset();
+ callback_cache.Run(result, data);
+}
diff --git a/chromium/extensions/browser/guest_view/web_view/web_ui/web_ui_url_fetcher.h b/chromium/extensions/browser/guest_view/web_view/web_ui/web_ui_url_fetcher.h
new file mode 100644
index 00000000000..8124fc63779
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_ui/web_ui_url_fetcher.h
@@ -0,0 +1,56 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_UI_URL_FETCHER_H
+#define EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_UI_URL_FETCHER_H
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "url/gurl.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace net {
+class URLFetcher;
+}
+
+// WebUIURLFetcher downloads the content of a file by giving its |url| on WebUI.
+// Each WebUIURLFetcher is associated with a given |render_process_id,
+// render_view_id| pair.
+class WebUIURLFetcher : public net::URLFetcherDelegate {
+ public:
+ // Called when a file URL request is complete.
+ // Parameters:
+ // - whether the request is success.
+ // - If yes, the content of the file.
+ using WebUILoadFileCallback = base::Callback<void(bool, const std::string&)>;
+
+ WebUIURLFetcher(content::BrowserContext* context,
+ int render_process_id,
+ int render_view_id,
+ const GURL& url,
+ const WebUILoadFileCallback& callback);
+ ~WebUIURLFetcher() override;
+
+ void Start();
+
+ private:
+ // net::URLFetcherDelegate:
+ void OnURLFetchComplete(const net::URLFetcher* source) override;
+
+ content::BrowserContext* context_;
+ int render_process_id_;
+ int render_view_id_;
+ GURL url_;
+ WebUILoadFileCallback callback_;
+ scoped_ptr<net::URLFetcher> fetcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebUIURLFetcher);
+};
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_UI_URL_FETCHER_H
diff --git a/chromium/extensions/browser/guest_view/web_view/web_view_apitest.cc b/chromium/extensions/browser/guest_view/web_view/web_view_apitest.cc
new file mode 100644
index 00000000000..89bbf05e017
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_view_apitest.cc
@@ -0,0 +1,743 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/web_view/web_view_apitest.h"
+
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+#include "components/guest_view/browser/guest_view_manager.h"
+#include "components/guest_view/browser/guest_view_manager_delegate.h"
+#include "components/guest_view/browser/guest_view_manager_factory.h"
+#include "components/guest_view/browser/test_guest_view_manager.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_renderer_host.h"
+#include "content/public/test/test_utils.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/api/test/test_api.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/app_window_registry.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_paths.h"
+#include "extensions/common/switches.h"
+#include "extensions/shell/browser/shell_content_browser_client.h"
+#include "extensions/shell/browser/shell_extension_system.h"
+#include "extensions/shell/test/shell_test.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "net/base/filename_util.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "ui/gfx/switches.h"
+
+using guest_view::GuestViewManager;
+using guest_view::TestGuestViewManager;
+
+namespace {
+
+const char kEmptyResponsePath[] = "/close-socket";
+const char kRedirectResponsePath[] = "/server-redirect";
+const char kRedirectResponseFullPath[] = "/guest_redirect.html";
+const char kUserAgentRedirectResponsePath[] = "/detect-user-agent";
+const char kTestDataDirectory[] = "testDataDirectory";
+const char kTestServerPort[] = "testServer.port";
+const char kTestWebSocketPort[] = "testWebSocketPort";
+const char kIsolateExtensions[] = "isolateExtensions";
+
+// Handles |request| by serving a redirect response if the |User-Agent| is
+// foobar.
+static scoped_ptr<net::test_server::HttpResponse> UserAgentResponseHandler(
+ const std::string& path,
+ const GURL& redirect_target,
+ const net::test_server::HttpRequest& request) {
+ if (!base::StartsWith(path, request.relative_url,
+ base::CompareCase::SENSITIVE))
+ return scoped_ptr<net::test_server::HttpResponse>();
+
+ auto it = request.headers.find("User-Agent");
+ EXPECT_TRUE(it != request.headers.end());
+ if (!base::StartsWith("foobar", it->second, base::CompareCase::SENSITIVE))
+ return scoped_ptr<net::test_server::HttpResponse>();
+
+ scoped_ptr<net::test_server::BasicHttpResponse> http_response(
+ new net::test_server::BasicHttpResponse);
+ http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
+ http_response->AddCustomHeader("Location", redirect_target.spec());
+ return std::move(http_response);
+}
+
+class WebContentsHiddenObserver : public content::WebContentsObserver {
+ public:
+ WebContentsHiddenObserver(content::WebContents* web_contents,
+ const base::Closure& hidden_callback)
+ : WebContentsObserver(web_contents),
+ hidden_callback_(hidden_callback),
+ hidden_observed_(false) {
+ }
+
+ // WebContentsObserver.
+ void WasHidden() override {
+ hidden_observed_ = true;
+ hidden_callback_.Run();
+ }
+
+ bool hidden_observed() { return hidden_observed_; }
+
+ private:
+ base::Closure hidden_callback_;
+ bool hidden_observed_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentsHiddenObserver);
+};
+
+// Handles |request| by serving a redirect response.
+scoped_ptr<net::test_server::HttpResponse> RedirectResponseHandler(
+ const std::string& path,
+ const GURL& redirect_target,
+ const net::test_server::HttpRequest& request) {
+ if (!base::StartsWith(path, request.relative_url,
+ base::CompareCase::SENSITIVE))
+ return scoped_ptr<net::test_server::HttpResponse>();
+
+ scoped_ptr<net::test_server::BasicHttpResponse> http_response(
+ new net::test_server::BasicHttpResponse);
+ http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
+ http_response->AddCustomHeader("Location", redirect_target.spec());
+ return std::move(http_response);
+}
+
+// Handles |request| by serving an empty response.
+scoped_ptr<net::test_server::HttpResponse> EmptyResponseHandler(
+ const std::string& path,
+ const net::test_server::HttpRequest& request) {
+ if (base::StartsWith(path, request.relative_url,
+ base::CompareCase::SENSITIVE)) {
+ return scoped_ptr<net::test_server::HttpResponse>(
+ new net::test_server::RawHttpResponse("", ""));
+ }
+
+ return scoped_ptr<net::test_server::HttpResponse>();
+}
+
+} // namespace
+
+namespace extensions {
+
+WebViewAPITest::WebViewAPITest() {
+ GuestViewManager::set_factory_for_testing(&factory_);
+}
+
+void WebViewAPITest::LaunchApp(const std::string& app_location) {
+ base::FilePath test_data_dir;
+ PathService::Get(DIR_TEST_DATA, &test_data_dir);
+ test_data_dir = test_data_dir.AppendASCII(app_location.c_str());
+
+ test_config_.SetString(kTestDataDirectory,
+ net::FilePathToFileURL(test_data_dir).spec());
+
+ embedded_test_server()->ServeFilesFromDirectory(test_data_dir);
+
+ const Extension* extension = extension_system_->LoadApp(test_data_dir);
+ ASSERT_TRUE(extension);
+ extension_system_->LaunchApp(extension->id());
+
+ ExtensionTestMessageListener launch_listener("LAUNCHED", false);
+ launch_listener.set_failure_message("FAILURE");
+ ASSERT_TRUE(launch_listener.WaitUntilSatisfied());
+
+ embedder_web_contents_ = GetFirstAppWindowWebContents();
+}
+
+content::WebContents* WebViewAPITest::GetFirstAppWindowWebContents() {
+ const AppWindowRegistry::AppWindowList& app_window_list =
+ AppWindowRegistry::Get(browser_context_)->app_windows();
+ DCHECK_EQ(1u, app_window_list.size());
+ return (*app_window_list.begin())->web_contents();
+}
+
+void WebViewAPITest::RunTest(const std::string& test_name,
+ const std::string& app_location) {
+ LaunchApp(app_location);
+
+ ExtensionTestMessageListener done_listener("TEST_PASSED", false);
+ done_listener.set_failure_message("TEST_FAILED");
+ ASSERT_TRUE(content::ExecuteScript(
+ embedder_web_contents_,
+ base::StringPrintf("runTest('%s')", test_name.c_str())))
+ << "Unable to start test.";
+ ASSERT_TRUE(done_listener.WaitUntilSatisfied());
+}
+
+void WebViewAPITest::RunTestOnMainThreadLoop() {
+ AppShellTest::RunTestOnMainThreadLoop();
+ GetGuestViewManager()->WaitForAllGuestsDeleted();
+}
+
+void WebViewAPITest::SetUpCommandLine(base::CommandLine* command_line) {
+ AppShellTest::SetUpCommandLine(command_line);
+ command_line->AppendSwitchASCII(::switches::kJavaScriptFlags, "--expose-gc");
+}
+
+void WebViewAPITest::SetUpOnMainThread() {
+ AppShellTest::SetUpOnMainThread();
+
+ TestGetConfigFunction::set_test_config_state(&test_config_);
+ base::FilePath test_data_dir;
+ test_config_.SetInteger(kTestWebSocketPort, 0);
+ bool isolate_extensions = base::CommandLine::ForCurrentProcess()->HasSwitch(
+ ::switches::kSitePerProcess) ||
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ extensions::switches::kIsolateExtensions);
+ test_config_.SetBoolean(kIsolateExtensions, isolate_extensions);
+}
+
+void WebViewAPITest::StartTestServer() {
+ // For serving guest pages.
+ if (!embedded_test_server()->Start()) {
+ LOG(ERROR) << "Failed to start test server.";
+ return;
+ }
+
+ test_config_.SetInteger(kTestServerPort, embedded_test_server()->port());
+
+ embedded_test_server()->RegisterRequestHandler(
+ base::Bind(&RedirectResponseHandler,
+ kRedirectResponsePath,
+ embedded_test_server()->GetURL(kRedirectResponseFullPath)));
+
+ embedded_test_server()->RegisterRequestHandler(
+ base::Bind(&EmptyResponseHandler, kEmptyResponsePath));
+
+ embedded_test_server()->RegisterRequestHandler(
+ base::Bind(
+ &UserAgentResponseHandler,
+ kUserAgentRedirectResponsePath,
+ embedded_test_server()->GetURL(kRedirectResponseFullPath)));
+}
+
+void WebViewAPITest::StopTestServer() {
+ if (!embedded_test_server()->ShutdownAndWaitUntilComplete()) {
+ LOG(ERROR) << "Failed to shutdown test server.";
+ }
+}
+
+void WebViewAPITest::TearDownOnMainThread() {
+ TestGetConfigFunction::set_test_config_state(nullptr);
+
+ AppShellTest::TearDownOnMainThread();
+}
+
+void WebViewAPITest::SendMessageToEmbedder(const std::string& message) {
+ EXPECT_TRUE(
+ content::ExecuteScript(
+ GetEmbedderWebContents(),
+ base::StringPrintf("onAppCommand('%s');", message.c_str())));
+}
+
+content::WebContents* WebViewAPITest::GetEmbedderWebContents() {
+ if (!embedder_web_contents_)
+ embedder_web_contents_ = GetFirstAppWindowWebContents();
+ return embedder_web_contents_;
+}
+
+TestGuestViewManager* WebViewAPITest::GetGuestViewManager() {
+ content::BrowserContext* context =
+ ShellContentBrowserClient::Get()->GetBrowserContext();
+ TestGuestViewManager* manager = static_cast<TestGuestViewManager*>(
+ TestGuestViewManager::FromBrowserContext(context));
+ // TestGuestViewManager::WaitForSingleGuestCreated may and will get called
+ // before a guest is created.
+ if (!manager) {
+ manager =
+ static_cast<TestGuestViewManager*>(GuestViewManager::CreateWithDelegate(
+ context,
+ ExtensionsAPIClient::Get()->CreateGuestViewManagerDelegate(
+ context)));
+ }
+ return manager;
+}
+
+void WebViewAPITest::SendMessageToGuestAndWait(
+ const std::string& message,
+ const std::string& wait_message) {
+ scoped_ptr<ExtensionTestMessageListener> listener;
+ if (!wait_message.empty())
+ listener.reset(new ExtensionTestMessageListener(wait_message, false));
+
+ EXPECT_TRUE(
+ content::ExecuteScript(
+ GetGuestWebContents(),
+ base::StringPrintf("onAppCommand('%s');", message.c_str())));
+
+ if (listener)
+ ASSERT_TRUE(listener->WaitUntilSatisfied());
+}
+
+void WebViewDPIAPITest::SetUp() {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ command_line->AppendSwitchASCII(::switches::kForceDeviceScaleFactor,
+ base::StringPrintf("%f", scale()));
+ WebViewAPITest::SetUp();
+}
+
+content::WebContents* WebViewAPITest::GetGuestWebContents() {
+ return GetGuestViewManager()->WaitForSingleGuestCreated();
+}
+
+// Occasionally hits NOTIMPLEMENTED on Linux. https://crbug.com/422998
+#if defined(OS_LINUX)
+#define MAYBE_AcceptTouchEvents DISABLED_AcceptTouchEvents
+#else
+#define MAYBE_AcceptTouchEvents AcceptTouchEvents
+#endif
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, MAYBE_AcceptTouchEvents) {
+ LaunchApp("web_view/accept_touch_events");
+
+ content::RenderViewHost* embedder_rvh =
+ GetEmbedderWebContents()->GetRenderViewHost();
+
+ bool embedder_has_touch_handler =
+ content::RenderViewHostTester::HasTouchEventHandler(embedder_rvh);
+ EXPECT_FALSE(embedder_has_touch_handler);
+
+ SendMessageToGuestAndWait("install-touch-handler", "installed-touch-handler");
+
+ // Note that we need to wait for the installed/registered touch handler to
+ // appear in browser process before querying |embedder_rvh|.
+ // In practice, since we do a roundrtip from browser process to guest and
+ // back, this is sufficient.
+ embedder_has_touch_handler =
+ content::RenderViewHostTester::HasTouchEventHandler(embedder_rvh);
+ EXPECT_TRUE(embedder_has_touch_handler);
+
+ SendMessageToGuestAndWait("uninstall-touch-handler",
+ "uninstalled-touch-handler");
+ // Same as the note above about waiting.
+ embedder_has_touch_handler =
+ content::RenderViewHostTester::HasTouchEventHandler(embedder_rvh);
+ EXPECT_FALSE(embedder_has_touch_handler);
+}
+
+// This test verifies that hiding the embedder also hides the guest.
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, EmbedderVisibilityChanged) {
+ LaunchApp("web_view/visibility_changed");
+
+ scoped_refptr<content::MessageLoopRunner> loop_runner(
+ new content::MessageLoopRunner);
+ WebContentsHiddenObserver observer(GetGuestWebContents(),
+ loop_runner->QuitClosure());
+
+ // Handled in web_view/visibility_changed/main.js
+ SendMessageToEmbedder("hide-embedder");
+ if (!observer.hidden_observed())
+ loop_runner->Run();
+}
+
+// Test for http://crbug.com/419611.
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, DisplayNoneSetSrc) {
+ LaunchApp("web_view/display_none_set_src");
+ // Navigate the guest while it's in "display: none" state.
+ SendMessageToEmbedder("navigate-guest");
+ GetGuestViewManager()->WaitForSingleGuestCreated();
+
+ // Now attempt to navigate the guest again.
+ SendMessageToEmbedder("navigate-guest");
+
+ ExtensionTestMessageListener test_passed_listener("WebViewTest.PASSED",
+ false);
+ // Making the guest visible would trigger loadstop.
+ SendMessageToEmbedder("show-guest");
+ EXPECT_TRUE(test_passed_listener.WaitUntilSatisfied());
+}
+
+// This test verifies that hiding the guest triggers WebContents::WasHidden().
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, GuestVisibilityChanged) {
+ LaunchApp("web_view/visibility_changed");
+
+ scoped_refptr<content::MessageLoopRunner> loop_runner(
+ new content::MessageLoopRunner);
+ WebContentsHiddenObserver observer(GetGuestWebContents(),
+ loop_runner->QuitClosure());
+
+ // Handled in web_view/visibility_changed/main.js
+ SendMessageToEmbedder("hide-guest");
+ if (!observer.hidden_observed())
+ loop_runner->Run();
+}
+
+// This test ensures that closing app window on 'loadcommit' does not crash.
+// The test launches an app with guest and closes the window on loadcommit. It
+// then launches the app window again. The process is repeated 3 times.
+// http://crbug.com/291278
+#if defined(OS_WIN)
+#define MAYBE_CloseOnLoadcommit DISABLED_CloseOnLoadcommit
+#else
+#define MAYBE_CloseOnLoadcommit CloseOnLoadcommit
+#endif
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, MAYBE_CloseOnLoadcommit) {
+ LaunchApp("web_view/close_on_loadcommit");
+ ExtensionTestMessageListener test_done_listener("done-close-on-loadcommit",
+ false);
+ ASSERT_TRUE(test_done_listener.WaitUntilSatisfied());
+}
+
+// This test verifies that reloading the embedder reloads the guest (and doest
+// not crash).
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, ReloadEmbedder) {
+ // Just load a guest from other test, we do not want to add a separate
+ // app for this test.
+ LaunchApp("web_view/visibility_changed");
+
+ ExtensionTestMessageListener launched_again_listener("LAUNCHED", false);
+ embedder_web_contents_->GetController().Reload(false);
+ ASSERT_TRUE(launched_again_listener.WaitUntilSatisfied());
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestAllowTransparencyAttribute) {
+ RunTest("testAllowTransparencyAttribute", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestAPIMethodExistence) {
+ RunTest("testAPIMethodExistence", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestAssignSrcAfterCrash) {
+ RunTest("testAssignSrcAfterCrash", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestAutosizeAfterNavigation) {
+ RunTest("testAutosizeAfterNavigation", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestAutosizeBeforeNavigation) {
+ RunTest("testAutosizeBeforeNavigation", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewDPIAPITest, TestAutosizeBeforeNavigation) {
+ RunTest("testAutosizeBeforeNavigation", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestAutosizeHeight) {
+ RunTest("testAutosizeHeight", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewDPIAPITest, TestAutosizeHeight) {
+ RunTest("testAutosizeHeight", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestAutosizeRemoveAttributes) {
+ RunTest("testAutosizeRemoveAttributes", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewDPIAPITest, TestAutosizeRemoveAttributes) {
+ RunTest("testAutosizeRemoveAttributes", "web_view/apitest");
+}
+
+// http://crbug.com/473177
+IN_PROC_BROWSER_TEST_F(WebViewAPITest,
+ DISABLED_TestAutosizeWithPartialAttributes) {
+ RunTest("testAutosizeWithPartialAttributes", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestCannotMutateEventName) {
+ RunTest("testCannotMutateEventName", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestChromeExtensionRelativePath) {
+ RunTest("testChromeExtensionRelativePath", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestChromeExtensionURL) {
+ RunTest("testChromeExtensionURL", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestContentLoadEvent) {
+ RunTest("testContentLoadEvent", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestDeclarativeWebRequestAPI) {
+ StartTestServer();
+ RunTest("testDeclarativeWebRequestAPI", "web_view/apitest");
+ StopTestServer();
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest,
+ TestDeclarativeWebRequestAPISendMessage) {
+ StartTestServer();
+ RunTest("testDeclarativeWebRequestAPISendMessage", "web_view/apitest");
+ StopTestServer();
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestDestroyOnEventListener) {
+ RunTest("testDestroyOnEventListener", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestDialogAlert) {
+ RunTest("testDialogAlert", "web_view/dialog");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestDialogConfirm) {
+ RunTest("testDialogConfirm", "web_view/dialog");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestDialogConfirmCancel) {
+ RunTest("testDialogConfirmCancel", "web_view/dialog");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestDialogConfirmDefaultCancel) {
+ RunTest("testDialogConfirmDefaultCancel", "web_view/dialog");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestDialogConfirmDefaultGCCancel) {
+ RunTest("testDialogConfirmDefaultGCCancel", "web_view/dialog");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestDialogPrompt) {
+ RunTest("testDialogPrompt", "web_view/dialog");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestDisplayNoneWebviewLoad) {
+ RunTest("testDisplayNoneWebviewLoad", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestDisplayNoneWebviewRemoveChild) {
+ RunTest("testDisplayNoneWebviewRemoveChild", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestEventName) {
+ RunTest("testEventName", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestExecuteScript) {
+ RunTest("testExecuteScript", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestExecuteScriptFail) {
+ RunTest("testExecuteScriptFail", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest,
+ TestExecuteScriptIsAbortedWhenWebViewSourceIsChanged) {
+ RunTest("testExecuteScriptIsAbortedWhenWebViewSourceIsChanged",
+ "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestFindAPI) {
+ RunTest("testFindAPI", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestFindAPI_findupdate) {
+ RunTest("testFindAPI_findupdate", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestGetProcessId) {
+ RunTest("testGetProcessId", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestHiddenBeforeNavigation) {
+ RunTest("testHiddenBeforeNavigation", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest,
+ TestInlineScriptFromAccessibleResources) {
+ RunTest("testInlineScriptFromAccessibleResources", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestInvalidChromeExtensionURL) {
+ RunTest("testInvalidChromeExtensionURL", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest,
+ TestLoadAbortChromeExtensionURLWrongPartition) {
+ RunTest("testLoadAbortChromeExtensionURLWrongPartition", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestLoadAbortEmptyResponse) {
+ StartTestServer();
+ RunTest("testLoadAbortEmptyResponse", "web_view/apitest");
+ StopTestServer();
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestLoadAbortIllegalChromeURL) {
+ RunTest("testLoadAbortIllegalChromeURL", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestLoadAbortIllegalFileURL) {
+ RunTest("testLoadAbortIllegalFileURL", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestLoadAbortIllegalJavaScriptURL) {
+ RunTest("testLoadAbortIllegalJavaScriptURL", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestLoadAbortInvalidNavigation) {
+ RunTest("testLoadAbortInvalidNavigation", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestLoadAbortNonWebSafeScheme) {
+ RunTest("testLoadAbortNonWebSafeScheme", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestLoadProgressEvent) {
+ RunTest("testLoadProgressEvent", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestLoadStartLoadRedirect) {
+ StartTestServer();
+ RunTest("testLoadStartLoadRedirect", "web_view/apitest");
+ StopTestServer();
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestNavigateAfterResize) {
+ RunTest("testNavigateAfterResize", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestNavigationToExternalProtocol) {
+ RunTest("testNavigationToExternalProtocol", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest,
+ TestNavOnConsecutiveSrcAttributeChanges) {
+ RunTest("testNavOnConsecutiveSrcAttributeChanges", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestNavOnSrcAttributeChange) {
+ RunTest("testNavOnSrcAttributeChange", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestNewWindow) {
+ StartTestServer();
+ RunTest("testNewWindow", "web_view/apitest");
+ StopTestServer();
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestNewWindowNoPreventDefault) {
+ StartTestServer();
+ RunTest("testNewWindowNoPreventDefault", "web_view/apitest");
+ StopTestServer();
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestNewWindowNoReferrerLink) {
+ StartTestServer();
+ RunTest("testNewWindowNoReferrerLink", "web_view/apitest");
+ StopTestServer();
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestNewWindowTwoListeners) {
+ StartTestServer();
+ RunTest("testNewWindowTwoListeners", "web_view/apitest");
+ StopTestServer();
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestOnEventProperty) {
+ RunTest("testOnEventProperties", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestPartitionChangeAfterNavigation) {
+ RunTest("testPartitionChangeAfterNavigation", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest,
+ TestPartitionRemovalAfterNavigationFails) {
+ RunTest("testPartitionRemovalAfterNavigationFails", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestReassignSrcAttribute) {
+ RunTest("testReassignSrcAttribute", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestRemoveWebviewOnExit) {
+ StartTestServer();
+
+ // Launch the app and wait until it's ready to load a test.
+ LaunchApp("web_view/apitest");
+
+ GURL::Replacements replace_host;
+ replace_host.SetHostStr("localhost");
+
+ // Run the test and wait until the guest WebContents is available and has
+ // finished loading.
+ ExtensionTestMessageListener guest_loaded_listener("guest-loaded", false);
+ EXPECT_TRUE(content::ExecuteScript(embedder_web_contents_,
+ "runTest('testRemoveWebviewOnExit')"));
+
+ content::WebContents* guest_web_contents = GetGuestWebContents();
+ EXPECT_TRUE(guest_web_contents->GetRenderProcessHost()->IsForGuestsOnly());
+ ASSERT_TRUE(guest_loaded_listener.WaitUntilSatisfied());
+
+ content::WebContentsDestroyedWatcher destroyed_watcher(guest_web_contents);
+
+ // Tell the embedder to kill the guest.
+ EXPECT_TRUE(content::ExecuteScript(embedder_web_contents_,
+ "removeWebviewOnExitDoCrash()"));
+
+ // Wait until the guest WebContents is destroyed.
+ destroyed_watcher.Wait();
+ StopTestServer();
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestReload) {
+ RunTest("testReload", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestReloadAfterTerminate) {
+ RunTest("testReloadAfterTerminate", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestRemoveSrcAttribute) {
+ RunTest("testRemoveSrcAttribute", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestRemoveWebviewAfterNavigation) {
+ RunTest("testRemoveWebviewAfterNavigation", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestResizeWebviewResizesContent) {
+ RunTest("testResizeWebviewResizesContent", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestTerminateAfterExit) {
+ RunTest("testTerminateAfterExit", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestWebRequestAPI) {
+ StartTestServer();
+ RunTest("testWebRequestAPI", "web_view/apitest");
+ StopTestServer();
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestWebRequestAPIWithHeaders) {
+ StartTestServer();
+ RunTest("testWebRequestAPIWithHeaders", "web_view/apitest");
+ StopTestServer();
+}
+
+// Tests the existence of WebRequest API event objects on the request
+// object, on the webview element, and hanging directly off webview.
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestWebRequestAPIExistence) {
+ RunTest("testWebRequestAPIExistence", "web_view/apitest");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestWebRequestAPIGoogleProperty) {
+ RunTest("testWebRequestAPIGoogleProperty", "web_view/apitest");
+}
+
+// This test verifies that webview.contentWindow works inside an iframe
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestWebViewInsideFrame) {
+ LaunchApp("web_view/inside_iframe");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestCaptureVisibleRegion) {
+ RunTest("testCaptureVisibleRegion", "web_view/apitest");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/web_view/web_view_apitest.h b/chromium/extensions/browser/guest_view/web_view/web_view_apitest.h
new file mode 100644
index 00000000000..2824b73467d
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_view_apitest.h
@@ -0,0 +1,66 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/values.h"
+#include "components/guest_view/browser/test_guest_view_manager.h"
+#include "extensions/shell/test/shell_test.h"
+#include "ui/gfx/switches.h"
+
+namespace content {
+class WebContents;
+} // namespace content
+
+namespace guestview {
+class TestGuestViewManager;
+} // namesapce guestview
+
+namespace extensions {
+
+// Base class for WebView tests in app_shell.
+class WebViewAPITest : public AppShellTest {
+ protected:
+ WebViewAPITest();
+
+ // Launches the app_shell app in |app_location|.
+ void LaunchApp(const std::string& app_location);
+
+ // Runs the test |test_name| in |app_location|. RunTest will launch the app
+ // and execute the javascript function runTest(test_name) inside the app.
+ void RunTest(const std::string& test_name, const std::string& app_location);
+
+ // Starts/Stops the embedded test server.
+ void StartTestServer();
+ void StopTestServer();
+
+ content::WebContents* GetEmbedderWebContents();
+
+ // Returns the GuestViewManager singleton.
+ guest_view::TestGuestViewManager* GetGuestViewManager();
+
+ content::WebContents* GetGuestWebContents();
+ void SendMessageToGuestAndWait(const std::string& message,
+ const std::string& wait_message);
+ void SendMessageToEmbedder(const std::string& message);
+
+ // content::BrowserTestBase implementation.
+ void RunTestOnMainThreadLoop() override;
+ void SetUpCommandLine(base::CommandLine* command_line) override;
+ void SetUpOnMainThread() override;
+ void TearDownOnMainThread() override;
+
+ content::WebContents* embedder_web_contents_;
+ guest_view::TestGuestViewManagerFactory factory_;
+ base::DictionaryValue test_config_;
+
+ private:
+ content::WebContents* GetFirstAppWindowWebContents();
+};
+
+class WebViewDPIAPITest : public WebViewAPITest {
+ protected:
+ void SetUp() override;
+ static float scale() { return 2.0f; }
+};
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/web_view/web_view_constants.cc b/chromium/extensions/browser/guest_view/web_view/web_view_constants.cc
new file mode 100644
index 00000000000..9ed1ed5ab7f
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_view_constants.cc
@@ -0,0 +1,145 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/web_view/web_view_constants.h"
+
+namespace webview {
+
+// Attributes.
+const char kAttributeAllowTransparency[] = "allowtransparency";
+const char kAttributeAllowScaling[] = "allowscaling";
+const char kAttributeName[] = "name";
+const char kAttributeSrc[] = "src";
+
+// API namespace.
+const char kAPINamespace[] = "webViewInternal";
+
+// API error messages.
+const char kAPILoadDataInvalidDataURL[] = "Invalid data URL \"%s\".";
+const char kAPILoadDataInvalidBaseURL[] = "Invalid base URL \"%s\".";
+const char kAPILoadDataInvalidVirtualURL[] = "Invalid virtual URL \"%s\".";
+
+// Events.
+const char kEventClose[] = "webViewInternal.onClose";
+const char kEventConsoleMessage[] = "webViewInternal.onConsoleMessage";
+const char kEventContentLoad[] = "webViewInternal.onContentLoad";
+const char kEventContextMenuShow[] = "chromeWebViewInternal.onContextMenuShow";
+const char kEventDialog[] = "webViewInternal.onDialog";
+const char kEventDropLink[] = "webViewInternal.onDropLink";
+const char kEventExit[] = "webViewInternal.onExit";
+const char kEventExitFullscreen[] = "webViewInternal.onExitFullscreen";
+const char kEventFindReply[] = "webViewInternal.onFindReply";
+const char kEventFrameNameChanged[] = "webViewInternal.onFrameNameChanged";
+const char kEventHeadersReceived[] = "webViewInternal.onHeadersReceived";
+const char kEventLoadAbort[] = "webViewInternal.onLoadAbort";
+const char kEventLoadCommit[] = "webViewInternal.onLoadCommit";
+const char kEventLoadProgress[] = "webViewInternal.onLoadProgress";
+const char kEventLoadRedirect[] = "webViewInternal.onLoadRedirect";
+const char kEventLoadStart[] = "webViewInternal.onLoadStart";
+const char kEventLoadStop[] = "webViewInternal.onLoadStop";
+const char kEventMessage[] = "webViewInternal.onMessage";
+const char kEventNewWindow[] = "webViewInternal.onNewWindow";
+const char kEventPermissionRequest[] = "webViewInternal.onPermissionRequest";
+const char kEventResponseStarted[] = "webViewInternal.onResponseStarted";
+const char kEventResponsive[] = "webViewInternal.onResponsive";
+const char kEventSizeChanged[] = "webViewInternal.onSizeChanged";
+const char kEventUnresponsive[] = "webViewInternal.onUnresponsive";
+const char kEventZoomChange[] = "webViewInternal.onZoomChange";
+
+// WebRequest API events.
+const char kEventAuthRequired[] = "webViewInternal.onAuthRequired";
+const char kEventBeforeRedirect[] = "webViewInternal.onBeforeRedirect";
+const char kEventBeforeRequest[] = "webViewInternal.onBeforeRequest";
+const char kEventBeforeSendHeaders[] = "webViewInternal.onBeforeSendHeaders";
+const char kEventCompleted[] = "webViewInternal.onCompleted";
+const char kEventErrorOccurred[] = "webViewInternal.onErrorOccurred";
+const char kEventSendHeaders[] = "webViewInternal.onSendHeaders";
+
+// Event related constants.
+const char kWebViewEventPrefix[] = "webViewInternal.";
+
+// Parameters/properties on events.
+const char kContextMenuItems[] = "items";
+const char kDefaultPromptText[] = "defaultPromptText";
+const char kFindSearchText[] = "searchText";
+const char kFindFinalUpdate[] = "finalUpdate";
+const char kInitialHeight[] = "initialHeight";
+const char kInitialWidth[] = "initialWidth";
+const char kLastUnlockedBySelf[] = "lastUnlockedBySelf";
+const char kLevel[] = "level";
+const char kLine[] = "line";
+const char kMessage[] = "message";
+const char kMessageText[] = "messageText";
+const char kMessageType[] = "messageType";
+const char kName[] = "name";
+const char kNewHeight[] = "newHeight";
+const char kNewURL[] = "newUrl";
+const char kNewWidth[] = "newWidth";
+const char kOldHeight[] = "oldHeight";
+const char kOldURL[] = "oldUrl";
+const char kOrigin[] = "origin";
+const char kPermission[] = "permission";
+const char kPermissionTypeDialog[] = "dialog";
+const char kPermissionTypeDownload[] = "download";
+const char kPermissionTypeFileSystem[] = "filesystem";
+const char kPermissionTypeFullscreen[] = "fullscreen";
+const char kPermissionTypeGeolocation[] = "geolocation";
+const char kPermissionTypeLoadPlugin[] = "loadplugin";
+const char kPermissionTypeMedia[] = "media";
+const char kPermissionTypeNewWindow[] = "newwindow";
+const char kPermissionTypePointerLock[] = "pointerLock";
+const char kOldWidth[] = "oldWidth";
+const char kProcessId[] = "processId";
+const char kProgress[] = "progress";
+const char kReason[] = "reason";
+const char kRequestId[] = "requestId";
+const char kRequestInfo[] = "requestInfo";
+const char kSourceId[] = "sourceId";
+const char kTargetURL[] = "targetUrl";
+const char kWindowID[] = "windowId";
+const char kWindowOpenDisposition[] = "windowOpenDisposition";
+const char kOldZoomFactor[] = "oldZoomFactor";
+const char kNewZoomFactor[] = "newZoomFactor";
+
+// Internal parameters/properties on events.
+const char kInternalBaseURLForDataURL[] = "baseUrlForDataUrl";
+const char kInternalCurrentEntryIndex[] = "currentEntryIndex";
+const char kInternalEntryCount[] = "entryCount";
+const char kInternalProcessId[] = "processId";
+
+// Parameters to callback functions.
+const char kFindNumberOfMatches[] = "numberOfMatches";
+const char kFindActiveMatchOrdinal[] = "activeMatchOrdinal";
+const char kFindSelectionRect[] = "selectionRect";
+const char kFindRectLeft[] = "left";
+const char kFindRectTop[] = "top";
+const char kFindRectWidth[] = "width";
+const char kFindRectHeight[] = "height";
+const char kFindCanceled[] = "canceled";
+
+// Initialization parameters.
+const char kInitialZoomFactor[] = "initialZoomFactor";
+const char kParameterUserAgentOverride[] = "userAgentOverride";
+
+// Miscellaneous.
+const char kMenuItemCommandId[] = "commandId";
+const char kMenuItemLabel[] = "label";
+const char kPersistPrefix[] = "persist:";
+const char kStoragePartitionId[] = "partition";
+const unsigned int kMaxOutstandingPermissionRequests = 1024;
+const int kInvalidPermissionRequestID = 0;
+
+// ClearData API constants.
+const uint32_t WEB_VIEW_REMOVE_DATA_MASK_APPCACHE = 1 << 0;
+const uint32_t WEB_VIEW_REMOVE_DATA_MASK_CACHE = 1 << 1;
+const uint32_t WEB_VIEW_REMOVE_DATA_MASK_COOKIES = 1 << 2;
+const uint32_t WEB_VIEW_REMOVE_DATA_MASK_FILE_SYSTEMS = 1 << 3;
+const uint32_t WEB_VIEW_REMOVE_DATA_MASK_INDEXEDDB = 1 << 4;
+const uint32_t WEB_VIEW_REMOVE_DATA_MASK_LOCAL_STORAGE = 1 << 5;
+const uint32_t WEB_VIEW_REMOVE_DATA_MASK_WEBSQL = 1 << 6;
+
+// Other.
+const char kWebViewContentScriptManagerKeyName[] =
+ "web_view_content_script_manager";
+} // namespace webview
diff --git a/chromium/extensions/browser/guest_view/web_view/web_view_constants.h b/chromium/extensions/browser/guest_view/web_view/web_view_constants.h
new file mode 100644
index 00000000000..6b82c19297d
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_view_constants.h
@@ -0,0 +1,157 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Constants used for the WebView API.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_CONSTANTS_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_CONSTANTS_H_
+
+#include <stdint.h>
+
+namespace webview {
+
+// Attributes.
+extern const char kAttributeAllowTransparency[];
+extern const char kAttributeAllowScaling[];
+extern const char kAttributeName[];
+extern const char kAttributeSrc[];
+
+// API namespace.
+// TODO(kalman): Consolidate this with the other API constants.
+extern const char kAPINamespace[];
+
+// API error messages.
+extern const char kAPILoadDataInvalidDataURL[];
+extern const char kAPILoadDataInvalidBaseURL[];
+extern const char kAPILoadDataInvalidVirtualURL[];
+
+// Events.
+extern const char kEventClose[];
+extern const char kEventConsoleMessage[];
+extern const char kEventContentLoad[];
+extern const char kEventContextMenuShow[];
+extern const char kEventDialog[];
+extern const char kEventDropLink[];
+extern const char kEventExit[];
+extern const char kEventExitFullscreen[];
+extern const char kEventFindReply[];
+extern const char kEventFrameNameChanged[];
+extern const char kEventHeadersReceived[];
+extern const char kEventLoadAbort[];
+extern const char kEventLoadCommit[];
+extern const char kEventLoadProgress[];
+extern const char kEventLoadRedirect[];
+extern const char kEventLoadStart[];
+extern const char kEventLoadStop[];
+extern const char kEventMessage[];
+extern const char kEventNewWindow[];
+extern const char kEventPermissionRequest[];
+extern const char kEventResponseStarted[];
+extern const char kEventResponsive[];
+extern const char kEventSizeChanged[];
+extern const char kEventUnresponsive[];
+extern const char kEventZoomChange[];
+
+// WebRequest API events.
+extern const char kEventAuthRequired[];
+extern const char kEventBeforeRedirect[];
+extern const char kEventBeforeRequest[];
+extern const char kEventBeforeSendHeaders[];
+extern const char kEventCompleted[];
+extern const char kEventErrorOccurred[];
+extern const char kEventSendHeaders[];
+
+// Event related constants.
+extern const char kWebViewEventPrefix[];
+
+// Parameters/properties on events.
+extern const char kContextMenuItems[];
+extern const char kDefaultPromptText[];
+extern const char kFindSearchText[];
+extern const char kFindFinalUpdate[];
+extern const char kInitialHeight[];
+extern const char kInitialWidth[];
+extern const char kLastUnlockedBySelf[];
+extern const char kLevel[];
+extern const char kLine[];
+extern const char kMessage[];
+extern const char kMessageText[];
+extern const char kMessageType[];
+extern const char kName[];
+extern const char kNewHeight[];
+extern const char kNewURL[];
+extern const char kNewWidth[];
+extern const char kOldHeight[];
+extern const char kOldURL[];
+extern const char kOrigin[];
+extern const char kPermission[];
+extern const char kPermissionTypeDialog[];
+extern const char kPermissionTypeDownload[];
+extern const char kPermissionTypeFileSystem[];
+extern const char kPermissionTypeFullscreen[];
+extern const char kPermissionTypeGeolocation[];
+extern const char kPermissionTypeLoadPlugin[];
+extern const char kPermissionTypeMedia[];
+extern const char kPermissionTypeNewWindow[];
+extern const char kPermissionTypePointerLock[];
+extern const char kOldWidth[];
+extern const char kProcessId[];
+extern const char kProgress[];
+extern const char kReason[];
+extern const char kRequestId[];
+extern const char kRequestInfo[];
+extern const char kSourceId[];
+extern const char kTargetURL[];
+extern const char kWindowID[];
+extern const char kWindowOpenDisposition[];
+extern const char kOldZoomFactor[];
+extern const char kNewZoomFactor[];
+
+// Internal parameters/properties on events.
+extern const char kInternalBaseURLForDataURL[];
+extern const char kInternalCurrentEntryIndex[];
+extern const char kInternalEntryCount[];
+extern const char kInternalProcessId[];
+
+// Parameters to callback functions.
+extern const char kFindNumberOfMatches[];
+extern const char kFindActiveMatchOrdinal[];
+extern const char kFindSelectionRect[];
+extern const char kFindRectLeft[];
+extern const char kFindRectTop[];
+extern const char kFindRectWidth[];
+extern const char kFindRectHeight[];
+extern const char kFindCanceled[];
+extern const char kFindDone[];
+
+// Initialization parameters.
+extern const char kInitialZoomFactor[];
+extern const char kParameterUserAgentOverride[];
+
+// Miscellaneous.
+extern const char kMenuItemCommandId[];
+extern const char kMenuItemLabel[];
+extern const char kPersistPrefix[];
+extern const char kStoragePartitionId[];
+extern const unsigned int kMaxOutstandingPermissionRequests;
+extern const int kInvalidPermissionRequestID;
+
+// ClearData API constants.
+//
+// Note that these are not in an enum because using enums to declare bitmasks
+// results in the enum values being signed.
+extern const uint32_t WEB_VIEW_REMOVE_DATA_MASK_APPCACHE;
+extern const uint32_t WEB_VIEW_REMOVE_DATA_MASK_CACHE;
+extern const uint32_t WEB_VIEW_REMOVE_DATA_MASK_COOKIES;
+extern const uint32_t WEB_VIEW_REMOVE_DATA_MASK_FILE_SYSTEMS;
+extern const uint32_t WEB_VIEW_REMOVE_DATA_MASK_INDEXEDDB;
+extern const uint32_t WEB_VIEW_REMOVE_DATA_MASK_LOCAL_STORAGE;
+extern const uint32_t WEB_VIEW_REMOVE_DATA_MASK_WEBSQL;
+
+// Other.
+extern const char kWebViewContentScriptManagerKeyName[];
+
+} // namespace webview
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_CONSTANTS_H_
diff --git a/chromium/extensions/browser/guest_view/web_view/web_view_content_script_manager.cc b/chromium/extensions/browser/guest_view/web_view/web_view_content_script_manager.cc
new file mode 100644
index 00000000000..8a5592bddb5
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_view_content_script_manager.cc
@@ -0,0 +1,251 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/web_view/web_view_content_script_manager.h"
+
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/navigation_details.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "extensions/browser/declarative_user_script_manager.h"
+#include "extensions/browser/declarative_user_script_master.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/guest_view/web_view/web_view_constants.h"
+#include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+WebViewContentScriptManager::WebViewContentScriptManager(
+ content::BrowserContext* browser_context)
+ : user_script_loader_observer_(this), browser_context_(browser_context) {
+}
+
+WebViewContentScriptManager::~WebViewContentScriptManager() {
+}
+
+WebViewContentScriptManager* WebViewContentScriptManager::Get(
+ content::BrowserContext* browser_context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ WebViewContentScriptManager* manager =
+ static_cast<WebViewContentScriptManager*>(browser_context->GetUserData(
+ webview::kWebViewContentScriptManagerKeyName));
+ if (!manager) {
+ manager = new WebViewContentScriptManager(browser_context);
+ browser_context->SetUserData(webview::kWebViewContentScriptManagerKeyName,
+ manager);
+ }
+ return manager;
+}
+
+void WebViewContentScriptManager::AddContentScripts(
+ int embedder_process_id,
+ content::RenderViewHost* render_view_host,
+ int view_instance_id,
+ const HostID& host_id,
+ const std::set<UserScript>& scripts) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ DeclarativeUserScriptMaster* master =
+ DeclarativeUserScriptManager::Get(browser_context_)
+ ->GetDeclarativeUserScriptMasterByID(host_id);
+ DCHECK(master);
+
+ // We need to update WebViewRenderState in the IO thread if the guest exists.
+ std::set<int> ids_to_add;
+
+ GuestMapKey key = std::pair<int, int>(embedder_process_id, view_instance_id);
+ GuestContentScriptMap::iterator iter = guest_content_script_map_.find(key);
+
+ // Step 1: finds the entry in guest_content_script_map_ by the given |key|.
+ // If there isn't any content script added for the given guest yet, insert an
+ // empty map first.
+ if (iter == guest_content_script_map_.end()) {
+ iter = guest_content_script_map_.insert(
+ iter,
+ std::pair<GuestMapKey, ContentScriptMap>(key, ContentScriptMap()));
+ }
+
+ // Step 2: updates the guest_content_script_map_.
+ ContentScriptMap& map = iter->second;
+ std::set<UserScript> scripts_to_delete;
+ for (const UserScript& script : scripts) {
+ auto map_iter = map.find(script.name());
+ // If a content script has the same name as the new one, remove the old
+ // script first, and insert the new one.
+ if (map_iter != map.end()) {
+ scripts_to_delete.insert(map_iter->second);
+ map.erase(map_iter);
+ }
+ map.insert(std::pair<std::string, UserScript>(script.name(), script));
+ ids_to_add.insert(script.id());
+ }
+
+ if (!scripts_to_delete.empty()) {
+ master->RemoveScripts(scripts_to_delete);
+ }
+
+ // Step 3: makes WebViewContentScriptManager become an observer of the
+ // |loader| for scripts loaded event.
+ UserScriptLoader* loader = master->loader();
+ DCHECK(loader);
+ if (!user_script_loader_observer_.IsObserving(loader))
+ user_script_loader_observer_.Add(loader);
+
+ // Step 4: adds new scripts to the master.
+ master->AddScripts(scripts, embedder_process_id,
+ render_view_host->GetRoutingID());
+
+ // Step 5: creates an entry in |webview_host_id_map_| for the given
+ // |embedder_process_id| and |view_instance_id| if it doesn't exist.
+ auto host_it = webview_host_id_map_.find(key);
+ if (host_it == webview_host_id_map_.end())
+ webview_host_id_map_.insert(std::make_pair(key, host_id));
+
+ // Step 6: updates WebViewRenderState in the IO thread.
+ // It is safe to use base::Unretained(WebViewRendererState::GetInstance())
+ // since WebViewRendererState::GetInstance() always returns a Singleton of
+ // WebViewRendererState.
+ if (!ids_to_add.empty()) {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO, FROM_HERE,
+ base::Bind(&WebViewRendererState::AddContentScriptIDs,
+ base::Unretained(WebViewRendererState::GetInstance()),
+ embedder_process_id, view_instance_id, ids_to_add));
+ }
+}
+
+void WebViewContentScriptManager::RemoveAllContentScriptsForWebView(
+ int embedder_process_id,
+ int view_instance_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ // Look up the host ID for the WebView.
+ GuestMapKey key = std::make_pair(embedder_process_id, view_instance_id);
+ auto host_it = webview_host_id_map_.find(key);
+ // If no entry exists, then this WebView has no content scripts.
+ if (host_it == webview_host_id_map_.end())
+ return;
+
+ // Remove all content scripts for the WebView.
+ RemoveContentScripts(embedder_process_id, view_instance_id, host_it->second,
+ std::vector<std::string>());
+ webview_host_id_map_.erase(host_it);
+}
+
+void WebViewContentScriptManager::RemoveContentScripts(
+ int embedder_process_id,
+ int view_instance_id,
+ const HostID& host_id,
+ const std::vector<std::string>& script_name_list) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ GuestMapKey key = std::pair<int, int>(embedder_process_id, view_instance_id);
+ GuestContentScriptMap::iterator script_map_iter =
+ guest_content_script_map_.find(key);
+ if (script_map_iter == guest_content_script_map_.end())
+ return;
+
+ DeclarativeUserScriptMaster* master =
+ DeclarativeUserScriptManager::Get(browser_context_)
+ ->GetDeclarativeUserScriptMasterByID(host_id);
+ CHECK(master);
+
+ // We need to update WebViewRenderState in the IO thread if the guest exists.
+ std::set<int> ids_to_delete;
+ std::set<UserScript> scripts_to_delete;
+
+ // Step 1: removes content scripts from |master| and updates
+ // |guest_content_script_map_|.
+ std::map<std::string, UserScript>& map = script_map_iter->second;
+ // If the |script_name_list| is empty, all the content scripts added by the
+ // guest will be removed; otherwise, removes the scripts in the
+ // |script_name_list|.
+ if (script_name_list.empty()) {
+ auto it = map.begin();
+ while (it != map.end()) {
+ scripts_to_delete.insert(it->second);
+ ids_to_delete.insert(it->second.id());
+ map.erase(it++);
+ }
+ } else {
+ for (const std::string& name : script_name_list) {
+ ContentScriptMap::iterator iter = map.find(name);
+ if (iter == map.end())
+ continue;
+ const UserScript& script = iter->second;
+ ids_to_delete.insert(script.id());
+ scripts_to_delete.insert(script);
+ map.erase(iter);
+ }
+ }
+
+ // Step 2: makes WebViewContentScriptManager become an observer of the
+ // |loader| for scripts loaded event.
+ UserScriptLoader* loader = master->loader();
+ DCHECK(loader);
+ if (!user_script_loader_observer_.IsObserving(loader))
+ user_script_loader_observer_.Add(loader);
+
+ // Step 3: removes content scripts from master.
+ master->RemoveScripts(scripts_to_delete);
+
+ // Step 4: updates WebViewRenderState in the IO thread.
+ if (!ids_to_delete.empty()) {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO, FROM_HERE,
+ base::Bind(&WebViewRendererState::RemoveContentScriptIDs,
+ base::Unretained(WebViewRendererState::GetInstance()),
+ embedder_process_id, view_instance_id, ids_to_delete));
+ }
+}
+
+std::set<int> WebViewContentScriptManager::GetContentScriptIDSet(
+ int embedder_process_id,
+ int view_instance_id) {
+ std::set<int> ids;
+
+ GuestMapKey key = std::pair<int, int>(embedder_process_id, view_instance_id);
+ GuestContentScriptMap::const_iterator iter =
+ guest_content_script_map_.find(key);
+ if (iter == guest_content_script_map_.end())
+ return ids;
+ const ContentScriptMap& map = iter->second;
+ for (const auto& pair : map)
+ ids.insert(pair.second.id());
+
+ return ids;
+}
+
+void WebViewContentScriptManager::SignalOnScriptsLoaded(
+ const base::Closure& callback) {
+ if (!user_script_loader_observer_.IsObservingSources()) {
+ callback.Run();
+ return;
+ }
+ pending_scripts_loading_callbacks_.push_back(callback);
+}
+
+void WebViewContentScriptManager::OnScriptsLoaded(UserScriptLoader* loader) {
+ user_script_loader_observer_.Remove(loader);
+ RunCallbacksIfReady();
+}
+
+void WebViewContentScriptManager::OnUserScriptLoaderDestroyed(
+ UserScriptLoader* loader) {
+ user_script_loader_observer_.Remove(loader);
+ RunCallbacksIfReady();
+}
+
+void WebViewContentScriptManager::RunCallbacksIfReady() {
+ if (user_script_loader_observer_.IsObservingSources())
+ return;
+ for (auto& callback : pending_scripts_loading_callbacks_)
+ callback.Run();
+ pending_scripts_loading_callbacks_.clear();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/web_view/web_view_content_script_manager.h b/chromium/extensions/browser/guest_view/web_view/web_view_content_script_manager.h
new file mode 100644
index 00000000000..fa1dc87b28b
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_view_content_script_manager.h
@@ -0,0 +1,110 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_CONTENT_SCRIPT_MANAGER_H
+#define EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_CONTENT_SCRIPT_MANAGER_H
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/supports_user_data.h"
+#include "extensions/browser/user_script_loader.h"
+
+struct HostID;
+
+namespace content {
+class BrowserContext;
+class RenderViewHost;
+class WebContents;
+}
+
+namespace extensions {
+class UserScript;
+
+// WebViewContentScriptManager manages the content scripts that each webview
+// guest adds and removes programmatically.
+class WebViewContentScriptManager : public base::SupportsUserData::Data,
+ public UserScriptLoader::Observer {
+ public:
+ explicit WebViewContentScriptManager(
+ content::BrowserContext* browser_context);
+ ~WebViewContentScriptManager() override;
+
+ static WebViewContentScriptManager* Get(
+ content::BrowserContext* browser_context);
+
+ // Adds content scripts for the WebView specified by
+ // |embedder_process_id| and |view_instance_id|.
+ void AddContentScripts(int embedder_process_id,
+ content::RenderViewHost* render_view_host,
+ int view_instance_id,
+ const HostID& host_id,
+ const std::set<UserScript>& user_scripts);
+
+ // Removes all content scripts for the WebView identified by
+ // |embedder_process_id| and |view_instance_id|.
+ void RemoveAllContentScriptsForWebView(int embedder_process_id,
+ int view_instance_id);
+
+ // Removes contents scipts whose names are in the |script_name_list| for the
+ // WebView specified by |embedder_process_id| and |view_instance_id|.
+ // If the |script_name_list| is empty, removes all the content scripts added
+ // for this WebView.
+ void RemoveContentScripts(int embedder_process_id,
+ int view_instance_id,
+ const HostID& host_id,
+ const std::vector<std::string>& script_name_list);
+
+ // Returns the content script IDs added by the WebView specified by
+ // |embedder_process_id| and |view_instance_id|.
+ std::set<int> GetContentScriptIDSet(int embedder_process_id,
+ int view_instance_id);
+
+ // Checks if there is any pending content scripts to load.
+ // If no, run |callback| immediately; otherwise caches the |callback|, and
+ // the |callback| will be called after all the pending content scripts are
+ // loaded.
+ void SignalOnScriptsLoaded(const base::Closure& callback);
+
+ private:
+ using GuestMapKey = std::pair<int, int>;
+ using ContentScriptMap = std::map<std::string, extensions::UserScript>;
+ using GuestContentScriptMap = std::map<GuestMapKey, ContentScriptMap>;
+
+ // UserScriptLoader::Observer implementation:
+ void OnScriptsLoaded(UserScriptLoader* loader) override;
+ void OnUserScriptLoaderDestroyed(UserScriptLoader* loader) override;
+
+ // If |user_script_loader_observer_| doesn't observe any source, we will run
+ // all the remaining callbacks in |pending_scripts_loading_callbacks_|.
+ void RunCallbacksIfReady();
+
+ // A map from embedder process ID and view instance ID (uniquely identifying
+ // one webview) to that webview's host ID. All webviews that have content
+ // scripts registered through this WebViewContentScriptManager will have an
+ // entry in this map.
+ std::map<GuestMapKey, HostID> webview_host_id_map_;
+
+ GuestContentScriptMap guest_content_script_map_;
+
+ // WebViewContentScriptManager observes UserScriptLoader to wait for scripts
+ // loaded event.
+ ScopedObserver<UserScriptLoader, UserScriptLoader::Observer>
+ user_script_loader_observer_;
+
+ // Caches callbacks and resumes them when all the scripts are loaded.
+ std::vector<base::Closure> pending_scripts_loading_callbacks_;
+
+ content::BrowserContext* browser_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewContentScriptManager);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_CONTENT_SCRIPT_MANAGER_H
diff --git a/chromium/extensions/browser/guest_view/web_view/web_view_find_helper.cc b/chromium/extensions/browser/guest_view/web_view/web_view_find_helper.cc
new file mode 100644
index 00000000000..f9a315f68f3
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_view_find_helper.cc
@@ -0,0 +1,290 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/web_view/web_view_find_helper.h"
+
+#include <utility>
+
+#include "components/guest_view/browser/guest_view_event.h"
+#include "extensions/browser/api/guest_view/web_view/web_view_internal_api.h"
+#include "extensions/browser/guest_view/web_view/web_view_constants.h"
+
+using guest_view::GuestViewEvent;
+
+namespace extensions {
+
+WebViewFindHelper::WebViewFindHelper(WebViewGuest* webview_guest)
+ : webview_guest_(webview_guest), current_find_request_id_(0) {
+}
+
+WebViewFindHelper::~WebViewFindHelper() {
+}
+
+void WebViewFindHelper::CancelAllFindSessions() {
+ current_find_session_ = nullptr;
+ while (!find_info_map_.empty()) {
+ find_info_map_.begin()->second->SendResponse(true /* canceled */);
+ find_info_map_.erase(find_info_map_.begin());
+ }
+ if (find_update_event_)
+ DispatchFindUpdateEvent(true /* canceled */, true /* final_update */);
+ find_update_event_.reset();
+}
+
+void WebViewFindHelper::DispatchFindUpdateEvent(bool canceled,
+ bool final_update) {
+ DCHECK(find_update_event_.get());
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ find_update_event_->PrepareResults(args.get());
+ args->SetBoolean(webview::kFindCanceled, canceled);
+ args->SetBoolean(webview::kFindFinalUpdate, final_update);
+ DCHECK(webview_guest_);
+ webview_guest_->DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventFindReply, std::move(args))));
+}
+
+void WebViewFindHelper::EndFindSession(int session_request_id, bool canceled) {
+ FindInfoMap::iterator session_iterator =
+ find_info_map_.find(session_request_id);
+ DCHECK(session_iterator != find_info_map_.end());
+ FindInfo* find_info = session_iterator->second.get();
+
+ // Call the callback function of the first request of the find session.
+ find_info->SendResponse(canceled);
+
+ // For every subsequent find request of the find session.
+ for (std::vector<base::WeakPtr<WebViewFindHelper::FindInfo> >::iterator i =
+ find_info->find_next_requests_.begin();
+ i != find_info->find_next_requests_.end();
+ ++i) {
+ DCHECK(i->get());
+
+ // Do not call callbacks for subsequent find requests that have not been
+ // replied to yet. These requests will get their own final updates in the
+ // same order as they appear in |find_next_requests_|, i.e. the order that
+ // the requests were made in. Once one request is found that has not been
+ // replied to, none that follow will be replied to either, and do not need
+ // to be checked.
+ if (!(*i)->replied_)
+ break;
+
+ // Update the request's number of matches (if not canceled).
+ if (!canceled) {
+ (*i)->find_results_.number_of_matches_ =
+ find_info->find_results_.number_of_matches_;
+ }
+
+ // Call the request's callback function with the find results, and then
+ // delete its map entry to free the WebViewInternalFindFunction object.
+ (*i)->SendResponse(canceled);
+ find_info_map_.erase((*i)->request_id_);
+ }
+
+ // Erase the first find request's map entry to free the
+ // WebViewInternalFindFunction
+ // object.
+ find_info_map_.erase(session_request_id);
+}
+
+void WebViewFindHelper::Find(
+ content::WebContents* guest_web_contents,
+ const base::string16& search_text,
+ const blink::WebFindOptions& options,
+ scoped_refptr<WebViewInternalFindFunction> find_function) {
+ // Need a new request_id for each new find request.
+ ++current_find_request_id_;
+
+ // Stores the find request information by request_id so that its callback
+ // function can be called when the find results are available.
+ std::pair<FindInfoMap::iterator, bool> insert_result =
+ find_info_map_.insert(std::make_pair(
+ current_find_request_id_,
+ make_scoped_refptr(new FindInfo(current_find_request_id_, search_text,
+ options, find_function))));
+ // No duplicate insertions.
+ DCHECK(insert_result.second);
+
+ // Find options including the implicit |findNext| field.
+ blink::WebFindOptions* full_options = insert_result.first->second->options();
+
+ // Set |findNext| implicitly.
+ if (current_find_session_) {
+ const base::string16& current_search_text =
+ current_find_session_->search_text();
+ bool current_match_case = current_find_session_->options()->matchCase;
+ full_options->findNext = !current_search_text.empty() &&
+ current_search_text == search_text &&
+ current_match_case == options.matchCase;
+ } else {
+ full_options->findNext = false;
+ }
+
+ // Link find requests that are a part of the same find session.
+ if (full_options->findNext && current_find_session_) {
+ DCHECK(current_find_request_id_ != current_find_session_->request_id());
+ current_find_session_->AddFindNextRequest(
+ insert_result.first->second->AsWeakPtr());
+ }
+
+ // Update the current find session, if necessary.
+ if (!full_options->findNext)
+ current_find_session_ = insert_result.first->second;
+
+ // Handle the empty |search_text| case internally.
+ if (search_text.empty()) {
+ guest_web_contents->StopFinding(content::STOP_FIND_ACTION_CLEAR_SELECTION);
+ FindReply(current_find_request_id_, 0, gfx::Rect(), 0, true);
+ return;
+ }
+
+ guest_web_contents->Find(current_find_request_id_,
+ search_text, *full_options);
+}
+
+void WebViewFindHelper::FindReply(int request_id,
+ int number_of_matches,
+ const gfx::Rect& selection_rect,
+ int active_match_ordinal,
+ bool final_update) {
+ FindInfoMap::iterator find_iterator = find_info_map_.find(request_id);
+
+ // Ignore slow replies to canceled find requests.
+ if (find_iterator == find_info_map_.end())
+ return;
+
+ // This find request must be a part of an existing find session.
+ DCHECK(current_find_session_);
+
+ WebViewFindHelper::FindInfo* find_info = find_iterator->second.get();
+
+ // Handle canceled find requests.
+ if (!find_info->options()->findNext &&
+ find_info_map_.begin()->first < request_id) {
+ DCHECK_NE(current_find_session_->request_id(),
+ find_info_map_.begin()->first);
+ DispatchFindUpdateEvent(true /* canceled */, true /* final_update */);
+ EndFindSession(find_info_map_.begin()->first, true /* canceled */);
+ }
+
+ // Clears the results for |findupdate| for a new find session.
+ if (!find_info->replied() && !find_info->options()->findNext)
+ find_update_event_.reset(new FindUpdateEvent(find_info->search_text()));
+
+ // Aggregate the find results.
+ find_info->AggregateResults(number_of_matches, selection_rect,
+ active_match_ordinal, final_update);
+ find_update_event_->AggregateResults(number_of_matches, selection_rect,
+ active_match_ordinal, final_update);
+
+ // Propagate incremental results to the |findupdate| event.
+ DispatchFindUpdateEvent(false /* canceled */, final_update);
+
+ // Call the callback functions of completed find requests.
+ if (final_update)
+ EndFindSession(request_id, false /* canceled */);
+}
+
+WebViewFindHelper::FindResults::FindResults()
+ : number_of_matches_(0), active_match_ordinal_(0) {
+}
+
+WebViewFindHelper::FindResults::~FindResults() {
+}
+
+void WebViewFindHelper::FindResults::AggregateResults(
+ int number_of_matches,
+ const gfx::Rect& selection_rect,
+ int active_match_ordinal,
+ bool final_update) {
+ if (number_of_matches != -1)
+ number_of_matches_ = number_of_matches;
+
+ if (active_match_ordinal != -1)
+ active_match_ordinal_ = active_match_ordinal;
+
+ if (final_update && active_match_ordinal_ == 0) {
+ // No match found, so the selection rectangle is empty.
+ selection_rect_ = gfx::Rect();
+ } else if (!selection_rect.IsEmpty()) {
+ selection_rect_ = selection_rect;
+ }
+}
+
+void WebViewFindHelper::FindResults::PrepareResults(
+ base::DictionaryValue* results) {
+ results->SetInteger(webview::kFindNumberOfMatches, number_of_matches_);
+ results->SetInteger(webview::kFindActiveMatchOrdinal, active_match_ordinal_);
+ base::DictionaryValue rect;
+ rect.SetInteger(webview::kFindRectLeft, selection_rect_.x());
+ rect.SetInteger(webview::kFindRectTop, selection_rect_.y());
+ rect.SetInteger(webview::kFindRectWidth, selection_rect_.width());
+ rect.SetInteger(webview::kFindRectHeight, selection_rect_.height());
+ results->Set(webview::kFindSelectionRect, rect.DeepCopy());
+}
+
+WebViewFindHelper::FindUpdateEvent::FindUpdateEvent(
+ const base::string16& search_text)
+ : search_text_(search_text) {
+}
+
+WebViewFindHelper::FindUpdateEvent::~FindUpdateEvent() {
+}
+
+void WebViewFindHelper::FindUpdateEvent::AggregateResults(
+ int number_of_matches,
+ const gfx::Rect& selection_rect,
+ int active_match_ordinal,
+ bool final_update) {
+ find_results_.AggregateResults(number_of_matches, selection_rect,
+ active_match_ordinal, final_update);
+}
+
+void WebViewFindHelper::FindUpdateEvent::PrepareResults(
+ base::DictionaryValue* results) {
+ results->SetString(webview::kFindSearchText, search_text_);
+ find_results_.PrepareResults(results);
+}
+
+WebViewFindHelper::FindInfo::FindInfo(
+ int request_id,
+ const base::string16& search_text,
+ const blink::WebFindOptions& options,
+ scoped_refptr<WebViewInternalFindFunction> find_function)
+ : request_id_(request_id),
+ search_text_(search_text),
+ options_(options),
+ find_function_(find_function),
+ replied_(false),
+ weak_ptr_factory_(this) {
+}
+
+void WebViewFindHelper::FindInfo::AggregateResults(
+ int number_of_matches,
+ const gfx::Rect& selection_rect,
+ int active_match_ordinal,
+ bool final_update) {
+ replied_ = true;
+ find_results_.AggregateResults(number_of_matches, selection_rect,
+ active_match_ordinal, final_update);
+}
+
+base::WeakPtr<WebViewFindHelper::FindInfo>
+WebViewFindHelper::FindInfo::AsWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+}
+
+void WebViewFindHelper::FindInfo::SendResponse(bool canceled) {
+ // Prepare the find results to pass to the callback function.
+ base::DictionaryValue results;
+ find_results_.PrepareResults(&results);
+ results.SetBoolean(webview::kFindCanceled, canceled);
+
+ // Call the callback.
+ find_function_->SetResult(results.DeepCopy());
+ find_function_->SendResponse(true);
+}
+
+WebViewFindHelper::FindInfo::~FindInfo() {}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/web_view/web_view_find_helper.h b/chromium/extensions/browser/guest_view/web_view/web_view_find_helper.h
new file mode 100644
index 00000000000..df93c0c139f
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_view_find_helper.h
@@ -0,0 +1,194 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_FIND_HELPER_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_FIND_HELPER_H_
+
+#include <map>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "content/public/browser/web_contents.h"
+#include "third_party/WebKit/public/web/WebFindOptions.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace extensions {
+class WebViewInternalFindFunction;
+class WebViewGuest;
+
+// Helper class for find requests and replies for the web_view_internal find
+// API.
+class WebViewFindHelper {
+ public:
+ explicit WebViewFindHelper(WebViewGuest* webview_guest);
+ ~WebViewFindHelper();
+
+ // Cancels all find requests in progress and calls their callback functions.
+ void CancelAllFindSessions();
+
+ // Dispatches the |findupdate| event.
+ void DispatchFindUpdateEvent(bool canceled, bool final_update);
+
+ // Ends the find session with id |session_request_id| and calls the
+ // appropriate callbacks.
+ void EndFindSession(int session_request_id, bool canceled);
+
+ // Helper function for WebViewGuest::Find().
+ void Find(content::WebContents* guest_web_contents,
+ const base::string16& search_text,
+ const blink::WebFindOptions& options,
+ scoped_refptr<WebViewInternalFindFunction> find_function);
+
+ // Helper function for WeViewGuest:FindReply().
+ void FindReply(int request_id,
+ int number_of_matches,
+ const gfx::Rect& selection_rect,
+ int active_match_ordinal,
+ bool final_update);
+
+ private:
+ // A wrapper to store find results.
+ class FindResults {
+ public:
+ FindResults();
+ ~FindResults();
+
+ // Aggregate the find results.
+ void AggregateResults(int number_of_matches,
+ const gfx::Rect& selection_rect,
+ int active_match_ordinal,
+ bool final_update);
+
+ // Stores find results into a DictionaryValue.
+ void PrepareResults(base::DictionaryValue* results);
+
+ private:
+ int number_of_matches_;
+ int active_match_ordinal_;
+ gfx::Rect selection_rect_;
+
+ friend void WebViewFindHelper::EndFindSession(int session_request_id,
+ bool canceled);
+
+ DISALLOW_COPY_AND_ASSIGN(FindResults);
+ };
+
+ // Stores and processes the results for the |findupdate| event.
+ class FindUpdateEvent {
+ public:
+ explicit FindUpdateEvent(const base::string16& search_text);
+ ~FindUpdateEvent();
+
+ // Aggregate the find results.
+ void AggregateResults(int number_of_matches,
+ const gfx::Rect& selection_rect,
+ int active_match_ordinal,
+ bool final_update);
+
+ // Stores find results and other event info into a DictionaryValue.
+ void PrepareResults(base::DictionaryValue* results);
+
+ private:
+ const base::string16 search_text_;
+ FindResults find_results_;
+
+ DISALLOW_COPY_AND_ASSIGN(FindUpdateEvent);
+ };
+
+ // Handles all information about a find request and its results.
+ class FindInfo : public base::RefCounted<FindInfo> {
+ public:
+ FindInfo(int request_id,
+ const base::string16& search_text,
+ const blink::WebFindOptions& options,
+ scoped_refptr<WebViewInternalFindFunction> find_function);
+
+ // Add another request to |find_next_requests_|.
+ void AddFindNextRequest(const base::WeakPtr<FindInfo>& request) {
+ find_next_requests_.push_back(request);
+ }
+
+ // Aggregate the find results.
+ void AggregateResults(int number_of_matches,
+ const gfx::Rect& selection_rect,
+ int active_match_ordinal,
+ bool final_update);
+
+ base::WeakPtr<FindInfo> AsWeakPtr();
+
+ blink::WebFindOptions* options() {
+ return &options_;
+ }
+
+ bool replied() {
+ return replied_;
+ }
+
+ int request_id() {
+ return request_id_;
+ }
+
+ const base::string16& search_text() {
+ return search_text_;
+ }
+
+ // Calls the callback function within |find_function_| with the find results
+ // from within |find_results_|.
+ void SendResponse(bool canceled);
+
+ private:
+ friend class base::RefCounted<FindInfo>;
+
+ ~FindInfo();
+
+ const int request_id_;
+ const base::string16 search_text_;
+ blink::WebFindOptions options_;
+ scoped_refptr<WebViewInternalFindFunction> find_function_;
+ FindResults find_results_;
+
+ // A find reply has been received for this find request.
+ bool replied_;
+
+ // Stores pointers to all the find next requests if this is the first
+ // request of a find session.
+ std::vector<base::WeakPtr<FindInfo> > find_next_requests_;
+
+ friend void WebViewFindHelper::EndFindSession(int session_request_id,
+ bool canceled);
+
+ base::WeakPtrFactory<FindInfo> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FindInfo);
+ };
+
+ // Pointer to the webview that is being helped.
+ WebViewGuest* const webview_guest_;
+
+ // A counter to generate a unique request id for a find request.
+ // We only need the ids to be unique for a given WebViewGuest.
+ int current_find_request_id_;
+
+ // Stores aggregated find results and other info for the |findupdate| event.
+ scoped_ptr<FindUpdateEvent> find_update_event_;
+
+ // Pointer to the first request of the current find session. find_info_map_
+ // retains ownership.
+ scoped_refptr<FindInfo> current_find_session_;
+
+ // Stores each find request's information by request_id so that its callback
+ // function can be called when its find results are available.
+ using FindInfoMap = std::map<int, scoped_refptr<FindInfo>>;
+ FindInfoMap find_info_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewFindHelper);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_FIND_HELPER_H_
diff --git a/chromium/extensions/browser/guest_view/web_view/web_view_guest.cc b/chromium/extensions/browser/guest_view/web_view/web_view_guest.cc
new file mode 100644
index 00000000000..70336af302e
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_view_guest.cc
@@ -0,0 +1,1506 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/web_view/web_view_guest.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/message_loop/message_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "components/browsing_data/storage_partition_http_cache_data_remover.h"
+#include "components/guest_view/browser/guest_view_event.h"
+#include "components/guest_view/browser/guest_view_manager.h"
+#include "components/guest_view/common/guest_view_constants.h"
+#include "components/web_cache/browser/web_cache_manager.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/child_process_security_policy.h"
+#include "content/public/browser/native_web_keyboard_event.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/resource_request_details.h"
+#include "content/public/browser/site_instance.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/common/media_stream_request.h"
+#include "content/public/common/page_zoom.h"
+#include "content/public/common/result_codes.h"
+#include "content/public/common/stop_find_action.h"
+#include "content/public/common/url_constants.h"
+#include "extensions/browser/api/declarative/rules_registry_service.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/api/guest_view/web_view/web_view_internal_api.h"
+#include "extensions/browser/api/web_request/web_request_api.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/guest_view/web_view/web_view_constants.h"
+#include "extensions/browser/guest_view/web_view/web_view_content_script_manager.h"
+#include "extensions/browser/guest_view/web_view/web_view_permission_helper.h"
+#include "extensions/browser/guest_view/web_view/web_view_permission_types.h"
+#include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/strings/grit/extensions_strings.h"
+#include "ipc/ipc_message_macros.h"
+#include "net/base/escape.h"
+#include "net/base/net_errors.h"
+#include "ui/base/models/simple_menu_model.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+#include "url/url_constants.h"
+
+using base::UserMetricsAction;
+using content::GlobalRequestID;
+using content::RenderFrameHost;
+using content::RenderProcessHost;
+using content::ResourceType;
+using content::StoragePartition;
+using content::WebContents;
+using guest_view::GuestViewBase;
+using guest_view::GuestViewEvent;
+using guest_view::GuestViewManager;
+using ui_zoom::ZoomController;
+
+namespace extensions {
+
+namespace {
+
+// Returns storage partition removal mask from web_view clearData mask. Note
+// that storage partition mask is a subset of webview's data removal mask.
+uint32_t GetStoragePartitionRemovalMask(uint32_t web_view_removal_mask) {
+ uint32_t mask = 0;
+ if (web_view_removal_mask & webview::WEB_VIEW_REMOVE_DATA_MASK_APPCACHE)
+ mask |= StoragePartition::REMOVE_DATA_MASK_APPCACHE;
+ if (web_view_removal_mask & webview::WEB_VIEW_REMOVE_DATA_MASK_COOKIES)
+ mask |= StoragePartition::REMOVE_DATA_MASK_COOKIES;
+ if (web_view_removal_mask & webview::WEB_VIEW_REMOVE_DATA_MASK_FILE_SYSTEMS)
+ mask |= StoragePartition::REMOVE_DATA_MASK_FILE_SYSTEMS;
+ if (web_view_removal_mask & webview::WEB_VIEW_REMOVE_DATA_MASK_INDEXEDDB)
+ mask |= StoragePartition::REMOVE_DATA_MASK_INDEXEDDB;
+ if (web_view_removal_mask & webview::WEB_VIEW_REMOVE_DATA_MASK_LOCAL_STORAGE)
+ mask |= StoragePartition::REMOVE_DATA_MASK_LOCAL_STORAGE;
+ if (web_view_removal_mask & webview::WEB_VIEW_REMOVE_DATA_MASK_WEBSQL)
+ mask |= StoragePartition::REMOVE_DATA_MASK_WEBSQL;
+
+ return mask;
+}
+
+std::string WindowOpenDispositionToString(
+ WindowOpenDisposition window_open_disposition) {
+ switch (window_open_disposition) {
+ case IGNORE_ACTION:
+ return "ignore";
+ case SAVE_TO_DISK:
+ return "save_to_disk";
+ case CURRENT_TAB:
+ return "current_tab";
+ case NEW_BACKGROUND_TAB:
+ return "new_background_tab";
+ case NEW_FOREGROUND_TAB:
+ return "new_foreground_tab";
+ case NEW_WINDOW:
+ return "new_window";
+ case NEW_POPUP:
+ return "new_popup";
+ default:
+ NOTREACHED() << "Unknown Window Open Disposition";
+ return "ignore";
+ }
+}
+
+static std::string TerminationStatusToString(base::TerminationStatus status) {
+ switch (status) {
+ case base::TERMINATION_STATUS_NORMAL_TERMINATION:
+ return "normal";
+ case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
+ case base::TERMINATION_STATUS_STILL_RUNNING:
+ return "abnormal";
+#if defined(OS_CHROMEOS)
+ case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM:
+ return "oom killed";
+#endif
+ case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
+ return "killed";
+ case base::TERMINATION_STATUS_PROCESS_CRASHED:
+ return "crashed";
+ case base::TERMINATION_STATUS_LAUNCH_FAILED:
+ return "failed to launch";
+ case base::TERMINATION_STATUS_MAX_ENUM:
+ break;
+ }
+ NOTREACHED() << "Unknown Termination Status.";
+ return "unknown";
+}
+
+std::string GetStoragePartitionIdFromSiteURL(const GURL& site_url) {
+ const std::string& partition_id = site_url.query();
+ bool persist_storage = site_url.path().find("persist") != std::string::npos;
+ return (persist_storage ? webview::kPersistPrefix : "") + partition_id;
+}
+
+void ParsePartitionParam(const base::DictionaryValue& create_params,
+ std::string* storage_partition_id,
+ bool* persist_storage) {
+ std::string partition_str;
+ if (!create_params.GetString(webview::kStoragePartitionId, &partition_str)) {
+ return;
+ }
+
+ // Since the "persist:" prefix is in ASCII, base::StartsWith will work fine on
+ // UTF-8 encoded |partition_id|. If the prefix is a match, we can safely
+ // remove the prefix without splicing in the middle of a multi-byte codepoint.
+ // We can use the rest of the string as UTF-8 encoded one.
+ if (base::StartsWith(partition_str, "persist:",
+ base::CompareCase::SENSITIVE)) {
+ size_t index = partition_str.find(":");
+ CHECK(index != std::string::npos);
+ // It is safe to do index + 1, since we tested for the full prefix above.
+ *storage_partition_id = partition_str.substr(index + 1);
+
+ if (storage_partition_id->empty()) {
+ // TODO(lazyboy): Better way to deal with this error.
+ return;
+ }
+ *persist_storage = true;
+ } else {
+ *storage_partition_id = partition_str;
+ *persist_storage = false;
+ }
+}
+
+void RemoveWebViewEventListenersOnIOThread(
+ void* profile,
+ int embedder_process_id,
+ int view_instance_id) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+ ExtensionWebRequestEventRouter::GetInstance()->RemoveWebViewEventListeners(
+ profile,
+ embedder_process_id,
+ view_instance_id);
+}
+
+double ConvertZoomLevelToZoomFactor(double zoom_level) {
+ double zoom_factor = content::ZoomLevelToZoomFactor(zoom_level);
+ // Because the conversion from zoom level to zoom factor isn't perfect, the
+ // resulting zoom factor is rounded to the nearest 6th decimal place.
+ zoom_factor = round(zoom_factor * 1000000) / 1000000;
+ return zoom_factor;
+}
+
+using WebViewKey = std::pair<int, int>;
+using WebViewKeyToIDMap = std::map<WebViewKey, int>;
+static base::LazyInstance<WebViewKeyToIDMap> web_view_key_to_id_map =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// static
+void WebViewGuest::CleanUp(content::BrowserContext* browser_context,
+ int embedder_process_id,
+ int view_instance_id) {
+ GuestViewBase::CleanUp(browser_context, embedder_process_id,
+ view_instance_id);
+
+ // Clean up rules registries for the WebView.
+ WebViewKey key(embedder_process_id, view_instance_id);
+ auto it = web_view_key_to_id_map.Get().find(key);
+ if (it != web_view_key_to_id_map.Get().end()) {
+ auto rules_registry_id = it->second;
+ web_view_key_to_id_map.Get().erase(it);
+ RulesRegistryService* rrs =
+ RulesRegistryService::GetIfExists(browser_context);
+ if (rrs)
+ rrs->RemoveRulesRegistriesByID(rules_registry_id);
+ }
+
+ // Clean up web request event listeners for the WebView.
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(
+ &RemoveWebViewEventListenersOnIOThread,
+ browser_context,
+ embedder_process_id,
+ view_instance_id));
+
+ // Clean up content scripts for the WebView.
+ auto csm = WebViewContentScriptManager::Get(browser_context);
+ csm->RemoveAllContentScriptsForWebView(embedder_process_id, view_instance_id);
+
+ // Allow an extensions browser client to potentially perform more cleanup.
+ ExtensionsBrowserClient::Get()->CleanUpWebView(
+ browser_context, embedder_process_id, view_instance_id);
+}
+
+// static
+GuestViewBase* WebViewGuest::Create(WebContents* owner_web_contents) {
+ return new WebViewGuest(owner_web_contents);
+}
+
+// static
+bool WebViewGuest::GetGuestPartitionConfigForSite(
+ const GURL& site,
+ std::string* partition_domain,
+ std::string* partition_name,
+ bool* in_memory) {
+ if (!site.SchemeIs(content::kGuestScheme))
+ return false;
+
+ // Since guest URLs are only used for packaged apps, there must be an app
+ // id in the URL.
+ CHECK(site.has_host());
+ *partition_domain = site.host();
+ // Since persistence is optional, the path must either be empty or the
+ // literal string.
+ *in_memory = (site.path() != "/persist");
+ // The partition name is user supplied value, which we have encoded when the
+ // URL was created, so it needs to be decoded.
+ *partition_name =
+ net::UnescapeURLComponent(site.query(), net::UnescapeRule::NORMAL);
+ return true;
+}
+
+// static
+std::string WebViewGuest::GetPartitionID(
+ const RenderProcessHost* render_process_host) {
+ WebViewRendererState* renderer_state = WebViewRendererState::GetInstance();
+ int process_id = render_process_host->GetID();
+ std::string partition_id;
+ if (renderer_state->IsGuest(process_id))
+ renderer_state->GetPartitionID(process_id, &partition_id);
+
+ return partition_id;
+}
+
+// static
+const char WebViewGuest::Type[] = "webview";
+
+// static
+int WebViewGuest::GetOrGenerateRulesRegistryID(
+ int embedder_process_id,
+ int webview_instance_id) {
+ bool is_web_view = embedder_process_id && webview_instance_id;
+ if (!is_web_view)
+ return RulesRegistryService::kDefaultRulesRegistryID;
+
+ WebViewKey key = std::make_pair(embedder_process_id, webview_instance_id);
+ auto it = web_view_key_to_id_map.Get().find(key);
+ if (it != web_view_key_to_id_map.Get().end())
+ return it->second;
+
+ auto rph = RenderProcessHost::FromID(embedder_process_id);
+ int rules_registry_id =
+ RulesRegistryService::Get(rph->GetBrowserContext())->
+ GetNextRulesRegistryID();
+ web_view_key_to_id_map.Get()[key] = rules_registry_id;
+ return rules_registry_id;
+}
+
+bool WebViewGuest::CanRunInDetachedState() const {
+ return true;
+}
+
+void WebViewGuest::CreateWebContents(
+ const base::DictionaryValue& create_params,
+ const WebContentsCreatedCallback& callback) {
+ RenderProcessHost* owner_render_process_host =
+ owner_web_contents()->GetRenderProcessHost();
+ std::string storage_partition_id;
+ bool persist_storage = false;
+ ParsePartitionParam(create_params, &storage_partition_id, &persist_storage);
+ // Validate that the partition id coming from the renderer is valid UTF-8,
+ // since we depend on this in other parts of the code, such as FilePath
+ // creation. If the validation fails, treat it as a bad message and kill the
+ // renderer process.
+ if (!base::IsStringUTF8(storage_partition_id)) {
+ content::RecordAction(
+ base::UserMetricsAction("BadMessageTerminate_BPGM"));
+ owner_render_process_host->Shutdown(content::RESULT_CODE_KILLED_BAD_MESSAGE,
+ false);
+ callback.Run(nullptr);
+ return;
+ }
+ std::string url_encoded_partition = net::EscapeQueryParamValue(
+ storage_partition_id, false);
+ std::string partition_domain = GetOwnerSiteURL().host();
+ GURL guest_site(base::StringPrintf("%s://%s/%s?%s",
+ content::kGuestScheme,
+ partition_domain.c_str(),
+ persist_storage ? "persist" : "",
+ url_encoded_partition.c_str()));
+
+ // If we already have a webview tag in the same app using the same storage
+ // partition, we should use the same SiteInstance so the existing tag and
+ // the new tag can script each other.
+ auto guest_view_manager = GuestViewManager::FromBrowserContext(
+ owner_render_process_host->GetBrowserContext());
+ scoped_refptr<content::SiteInstance> guest_site_instance =
+ guest_view_manager->GetGuestSiteInstance(guest_site);
+ if (!guest_site_instance) {
+ // Create the SiteInstance in a new BrowsingInstance, which will ensure
+ // that webview tags are also not allowed to send messages across
+ // different partitions.
+ guest_site_instance = content::SiteInstance::CreateForURL(
+ owner_render_process_host->GetBrowserContext(), guest_site);
+ }
+ WebContents::CreateParams params(
+ owner_render_process_host->GetBrowserContext(),
+ std::move(guest_site_instance));
+ params.guest_delegate = this;
+ callback.Run(WebContents::Create(params));
+}
+
+void WebViewGuest::DidAttachToEmbedder() {
+ ApplyAttributes(*attach_params());
+}
+
+void WebViewGuest::DidDropLink(const GURL& url) {
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ args->SetString(guest_view::kUrl, url.spec());
+ DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventDropLink, std::move(args))));
+}
+
+void WebViewGuest::DidInitialize(const base::DictionaryValue& create_params) {
+ script_executor_.reset(
+ new ScriptExecutor(web_contents(), &script_observers_));
+
+ notification_registrar_.Add(this,
+ content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
+ content::Source<WebContents>(web_contents()));
+
+ notification_registrar_.Add(this,
+ content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT,
+ content::Source<WebContents>(web_contents()));
+
+ if (web_view_guest_delegate_)
+ web_view_guest_delegate_->OnDidInitialize();
+ ExtensionsAPIClient::Get()->AttachWebContentsHelpers(web_contents());
+ web_view_permission_helper_.reset(new WebViewPermissionHelper(this));
+
+ rules_registry_id_ = GetOrGenerateRulesRegistryID(
+ owner_web_contents()->GetRenderProcessHost()->GetID(),
+ view_instance_id());
+
+ // We must install the mapping from guests to WebViews prior to resuming
+ // suspended resource loads so that the WebRequest API will catch resource
+ // requests.
+ PushWebViewStateToIOThread();
+
+ ApplyAttributes(create_params);
+}
+
+void WebViewGuest::ClearDataInternal(base::Time remove_since,
+ uint32_t removal_mask,
+ const base::Closure& callback) {
+ uint32_t storage_partition_removal_mask =
+ GetStoragePartitionRemovalMask(removal_mask);
+ if (!storage_partition_removal_mask) {
+ callback.Run();
+ return;
+ }
+ content::StoragePartition* partition =
+ content::BrowserContext::GetStoragePartition(
+ web_contents()->GetBrowserContext(),
+ web_contents()->GetSiteInstance());
+ partition->ClearData(
+ storage_partition_removal_mask,
+ content::StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL, GURL(),
+ content::StoragePartition::OriginMatcherFunction(), remove_since,
+ base::Time::Now(), callback);
+}
+
+void WebViewGuest::GuestViewDidStopLoading() {
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventLoadStop, std::move(args))));
+}
+
+void WebViewGuest::EmbedderFullscreenToggled(bool entered_fullscreen) {
+ is_embedder_fullscreen_ = entered_fullscreen;
+ // If the embedder has got out of fullscreen, we get out of fullscreen
+ // mode as well.
+ if (!entered_fullscreen)
+ SetFullscreenState(false);
+}
+
+const char* WebViewGuest::GetAPINamespace() const {
+ return webview::kAPINamespace;
+}
+
+int WebViewGuest::GetTaskPrefix() const {
+ return IDS_EXTENSION_TASK_MANAGER_WEBVIEW_TAG_PREFIX;
+}
+
+void WebViewGuest::GuestDestroyed() {
+ RemoveWebViewStateFromIOThread(web_contents());
+}
+
+void WebViewGuest::GuestReady() {
+ // The guest RenderView should always live in an isolated guest process.
+ CHECK(web_contents()->GetRenderProcessHost()->IsForGuestsOnly());
+ Send(new ExtensionMsg_SetFrameName(web_contents()->GetRoutingID(), name_));
+
+ // We don't want to accidentally set the opacity of an interstitial page.
+ // WebContents::GetRenderWidgetHostView will return the RWHV of an
+ // interstitial page if one is showing at this time. We only want opacity
+ // to apply to web pages.
+ if (allow_transparency_) {
+ web_contents()
+ ->GetRenderViewHost()
+ ->GetWidget()
+ ->GetView()
+ ->SetBackgroundColor(SK_ColorTRANSPARENT);
+ } else {
+ web_contents()
+ ->GetRenderViewHost()
+ ->GetWidget()
+ ->GetView()
+ ->SetBackgroundColorToDefault();
+ }
+}
+
+void WebViewGuest::GuestSizeChangedDueToAutoSize(const gfx::Size& old_size,
+ const gfx::Size& new_size) {
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ args->SetInteger(webview::kOldHeight, old_size.height());
+ args->SetInteger(webview::kOldWidth, old_size.width());
+ args->SetInteger(webview::kNewHeight, new_size.height());
+ args->SetInteger(webview::kNewWidth, new_size.width());
+ DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventSizeChanged, std::move(args))));
+}
+
+bool WebViewGuest::IsAutoSizeSupported() const {
+ return true;
+}
+
+void WebViewGuest::GuestZoomChanged(double old_zoom_level,
+ double new_zoom_level) {
+ // Dispatch the zoomchange event.
+ double old_zoom_factor = ConvertZoomLevelToZoomFactor(old_zoom_level);
+ double new_zoom_factor = ConvertZoomLevelToZoomFactor(new_zoom_level);
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ args->SetDouble(webview::kOldZoomFactor, old_zoom_factor);
+ args->SetDouble(webview::kNewZoomFactor, new_zoom_factor);
+ DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventZoomChange, std::move(args))));
+}
+
+void WebViewGuest::WillDestroy() {
+ if (!attached() && GetOpener())
+ GetOpener()->pending_new_windows_.erase(this);
+}
+
+bool WebViewGuest::AddMessageToConsole(WebContents* source,
+ int32_t level,
+ const base::string16& message,
+ int32_t line_no,
+ const base::string16& source_id) {
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ // Log levels are from base/logging.h: LogSeverity.
+ args->SetInteger(webview::kLevel, level);
+ args->SetString(webview::kMessage, message);
+ args->SetInteger(webview::kLine, line_no);
+ args->SetString(webview::kSourceId, source_id);
+ DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventConsoleMessage, std::move(args))));
+ return true;
+}
+
+void WebViewGuest::CloseContents(WebContents* source) {
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventClose, std::move(args))));
+}
+
+void WebViewGuest::FindReply(WebContents* source,
+ int request_id,
+ int number_of_matches,
+ const gfx::Rect& selection_rect,
+ int active_match_ordinal,
+ bool final_update) {
+ GuestViewBase::FindReply(source, request_id, number_of_matches,
+ selection_rect, active_match_ordinal, final_update);
+ find_helper_.FindReply(request_id, number_of_matches, selection_rect,
+ active_match_ordinal, final_update);
+}
+
+double WebViewGuest::GetZoom() const {
+ double zoom_level =
+ ZoomController::FromWebContents(web_contents())->GetZoomLevel();
+ return ConvertZoomLevelToZoomFactor(zoom_level);
+}
+
+ZoomController::ZoomMode WebViewGuest::GetZoomMode() {
+ return ZoomController::FromWebContents(web_contents())->zoom_mode();
+}
+
+bool WebViewGuest::HandleContextMenu(
+ const content::ContextMenuParams& params) {
+ if (!web_view_guest_delegate_)
+ return false;
+ return web_view_guest_delegate_->HandleContextMenu(params);
+}
+
+void WebViewGuest::HandleKeyboardEvent(
+ WebContents* source,
+ const content::NativeWebKeyboardEvent& event) {
+ if (HandleKeyboardShortcuts(event))
+ return;
+
+ GuestViewBase::HandleKeyboardEvent(source, event);
+}
+
+bool WebViewGuest::PreHandleGestureEvent(WebContents* source,
+ const blink::WebGestureEvent& event) {
+ return !allow_scaling_ && GuestViewBase::PreHandleGestureEvent(source, event);
+}
+
+void WebViewGuest::LoadProgressChanged(WebContents* source, double progress) {
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ args->SetString(guest_view::kUrl, web_contents()->GetURL().spec());
+ args->SetDouble(webview::kProgress, progress);
+ DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventLoadProgress, std::move(args))));
+}
+
+void WebViewGuest::LoadAbort(bool is_top_level,
+ const GURL& url,
+ int error_code,
+ const std::string& error_type) {
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ args->SetBoolean(guest_view::kIsTopLevel, is_top_level);
+ args->SetString(guest_view::kUrl, url.possibly_invalid_spec());
+ args->SetInteger(guest_view::kCode, error_code);
+ args->SetString(guest_view::kReason, error_type);
+ DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventLoadAbort, std::move(args))));
+}
+
+void WebViewGuest::SetContextMenuPosition(const gfx::Point& position) {
+ if (web_view_guest_delegate_)
+ web_view_guest_delegate_->SetContextMenuPosition(position);
+}
+
+void WebViewGuest::CreateNewGuestWebViewWindow(
+ const content::OpenURLParams& params) {
+ GuestViewManager* guest_manager =
+ GuestViewManager::FromBrowserContext(browser_context());
+ // Set the attach params to use the same partition as the opener.
+ // We pull the partition information from the site's URL, which is of the
+ // form guest://site/{persist}?{partition_name}.
+ const GURL& site_url = web_contents()->GetSiteInstance()->GetSiteURL();
+ const std::string storage_partition_id =
+ GetStoragePartitionIdFromSiteURL(site_url);
+ base::DictionaryValue create_params;
+ create_params.SetString(webview::kStoragePartitionId, storage_partition_id);
+
+ guest_manager->CreateGuest(WebViewGuest::Type,
+ embedder_web_contents(),
+ create_params,
+ base::Bind(&WebViewGuest::NewGuestWebViewCallback,
+ weak_ptr_factory_.GetWeakPtr(),
+ params));
+}
+
+void WebViewGuest::NewGuestWebViewCallback(const content::OpenURLParams& params,
+ WebContents* guest_web_contents) {
+ WebViewGuest* new_guest = WebViewGuest::FromWebContents(guest_web_contents);
+ new_guest->SetOpener(this);
+
+ // Take ownership of |new_guest|.
+ pending_new_windows_.insert(
+ std::make_pair(new_guest, NewWindowInfo(params.url, std::string())));
+
+ // Request permission to show the new window.
+ RequestNewWindowPermission(params.disposition,
+ gfx::Rect(),
+ params.user_gesture,
+ new_guest->web_contents());
+}
+
+// TODO(fsamuel): Find a reliable way to test the 'responsive' and
+// 'unresponsive' events.
+void WebViewGuest::RendererResponsive(WebContents* source) {
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ args->SetInteger(webview::kProcessId,
+ web_contents()->GetRenderProcessHost()->GetID());
+ DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventResponsive, std::move(args))));
+}
+
+void WebViewGuest::RendererUnresponsive(WebContents* source) {
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ args->SetInteger(webview::kProcessId,
+ web_contents()->GetRenderProcessHost()->GetID());
+ DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventUnresponsive, std::move(args))));
+}
+
+void WebViewGuest::Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ switch (type) {
+ case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME: {
+ DCHECK_EQ(content::Source<WebContents>(source).ptr(), web_contents());
+ if (content::Source<WebContents>(source).ptr() == web_contents())
+ LoadHandlerCalled();
+ break;
+ }
+ case content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT: {
+ DCHECK_EQ(content::Source<WebContents>(source).ptr(), web_contents());
+ content::ResourceRedirectDetails* resource_redirect_details =
+ content::Details<content::ResourceRedirectDetails>(details).ptr();
+ bool is_top_level = resource_redirect_details->resource_type ==
+ content::RESOURCE_TYPE_MAIN_FRAME;
+ LoadRedirect(resource_redirect_details->url,
+ resource_redirect_details->new_url,
+ is_top_level);
+ break;
+ }
+ default:
+ NOTREACHED() << "Unexpected notification sent.";
+ break;
+ }
+}
+
+void WebViewGuest::StartFind(
+ const base::string16& search_text,
+ const blink::WebFindOptions& options,
+ scoped_refptr<WebViewInternalFindFunction> find_function) {
+ find_helper_.Find(web_contents(), search_text, options, find_function);
+}
+
+void WebViewGuest::StopFinding(content::StopFindAction action) {
+ find_helper_.CancelAllFindSessions();
+ web_contents()->StopFinding(action);
+}
+
+bool WebViewGuest::Go(int relative_index) {
+ content::NavigationController& controller = web_contents()->GetController();
+ if (!controller.CanGoToOffset(relative_index))
+ return false;
+
+ controller.GoToOffset(relative_index);
+ return true;
+}
+
+void WebViewGuest::Reload() {
+ // TODO(fsamuel): Don't check for repost because we don't want to show
+ // Chromium's repost warning. We might want to implement a separate API
+ // for registering a callback if a repost is about to happen.
+ web_contents()->GetController().Reload(false);
+}
+
+void WebViewGuest::SetUserAgentOverride(
+ const std::string& user_agent_override) {
+ is_overriding_user_agent_ = !user_agent_override.empty();
+ if (is_overriding_user_agent_) {
+ content::RecordAction(UserMetricsAction("WebView.Guest.OverrideUA"));
+ }
+ web_contents()->SetUserAgentOverride(user_agent_override);
+}
+
+void WebViewGuest::Stop() {
+ web_contents()->Stop();
+}
+
+void WebViewGuest::Terminate() {
+ content::RecordAction(UserMetricsAction("WebView.Guest.Terminate"));
+ base::ProcessHandle process_handle =
+ web_contents()->GetRenderProcessHost()->GetHandle();
+ if (process_handle)
+ web_contents()->GetRenderProcessHost()->Shutdown(
+ content::RESULT_CODE_KILLED, false);
+}
+
+bool WebViewGuest::ClearData(base::Time remove_since,
+ uint32_t removal_mask,
+ const base::Closure& callback) {
+ content::RecordAction(UserMetricsAction("WebView.Guest.ClearData"));
+ content::StoragePartition* partition =
+ content::BrowserContext::GetStoragePartition(
+ web_contents()->GetBrowserContext(),
+ web_contents()->GetSiteInstance());
+
+ if (!partition)
+ return false;
+
+ if (removal_mask & webview::WEB_VIEW_REMOVE_DATA_MASK_CACHE) {
+ // First clear http cache data and then clear the rest in
+ // |ClearDataInternal|.
+ int render_process_id = web_contents()->GetRenderProcessHost()->GetID();
+ // We need to clear renderer cache separately for our process because
+ // StoragePartitionHttpCacheDataRemover::ClearData() does not clear that.
+ web_cache::WebCacheManager::GetInstance()->ClearCacheForProcess(
+ render_process_id);
+ web_cache::WebCacheManager::GetInstance()->Remove(render_process_id);
+
+ base::Closure cache_removal_done_callback = base::Bind(
+ &WebViewGuest::ClearDataInternal, weak_ptr_factory_.GetWeakPtr(),
+ remove_since, removal_mask, callback);
+ // StoragePartitionHttpCacheDataRemover removes itself when it is done.
+ // components/, move |ClearCache| to WebViewGuest: http//crbug.com/471287.
+ browsing_data::StoragePartitionHttpCacheDataRemover::CreateForRange(
+ partition, remove_since, base::Time::Now())
+ ->Remove(cache_removal_done_callback);
+
+ return true;
+ }
+
+ ClearDataInternal(remove_since, removal_mask, callback);
+ return true;
+}
+
+WebViewGuest::WebViewGuest(WebContents* owner_web_contents)
+ : GuestView<WebViewGuest>(owner_web_contents),
+ rules_registry_id_(RulesRegistryService::kInvalidRulesRegistryID),
+ find_helper_(this),
+ is_overriding_user_agent_(false),
+ allow_transparency_(false),
+ javascript_dialog_helper_(this),
+ allow_scaling_(false),
+ is_guest_fullscreen_(false),
+ is_embedder_fullscreen_(false),
+ last_fullscreen_permission_was_allowed_by_embedder_(false),
+ pending_zoom_factor_(0.0),
+ weak_ptr_factory_(this) {
+ web_view_guest_delegate_.reset(
+ ExtensionsAPIClient::Get()->CreateWebViewGuestDelegate(this));
+}
+
+WebViewGuest::~WebViewGuest() {
+}
+
+void WebViewGuest::DidCommitProvisionalLoadForFrame(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& url,
+ ui::PageTransition transition_type) {
+ if (!render_frame_host->GetParent()) {
+ // For LoadDataWithBaseURL loads, |url| contains the data URL, but the
+ // virtual URL is needed in that case. So use WebContents::GetURL instead.
+ src_ = web_contents()->GetURL();
+ // Handle a pending zoom if one exists.
+ if (pending_zoom_factor_) {
+ SetZoom(pending_zoom_factor_);
+ pending_zoom_factor_ = 0.0;
+ }
+ }
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ args->SetString(guest_view::kUrl, src_.spec());
+ args->SetBoolean(guest_view::kIsTopLevel, !render_frame_host->GetParent());
+ args->SetString(webview::kInternalBaseURLForDataURL,
+ web_contents()
+ ->GetController()
+ .GetLastCommittedEntry()
+ ->GetBaseURLForDataURL()
+ .spec());
+ args->SetInteger(webview::kInternalCurrentEntryIndex,
+ web_contents()->GetController().GetCurrentEntryIndex());
+ args->SetInteger(webview::kInternalEntryCount,
+ web_contents()->GetController().GetEntryCount());
+ args->SetInteger(webview::kInternalProcessId,
+ web_contents()->GetRenderProcessHost()->GetID());
+ DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventLoadCommit, std::move(args))));
+
+ find_helper_.CancelAllFindSessions();
+}
+
+void WebViewGuest::DidFailProvisionalLoad(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& validated_url,
+ int error_code,
+ const base::string16& error_description,
+ bool was_ignored_by_handler) {
+ // Suppress loadabort for "mailto" URLs.
+ if (validated_url.SchemeIs(url::kMailToScheme))
+ return;
+
+ LoadAbort(!render_frame_host->GetParent(), validated_url, error_code,
+ net::ErrorToShortString(error_code));
+}
+
+void WebViewGuest::DidStartProvisionalLoadForFrame(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& validated_url,
+ bool is_error_page,
+ bool is_iframe_srcdoc) {
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ args->SetString(guest_view::kUrl, validated_url.spec());
+ args->SetBoolean(guest_view::kIsTopLevel, !render_frame_host->GetParent());
+ DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventLoadStart, std::move(args))));
+}
+
+void WebViewGuest::RenderProcessGone(base::TerminationStatus status) {
+ // Cancel all find sessions in progress.
+ find_helper_.CancelAllFindSessions();
+
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ args->SetInteger(webview::kProcessId,
+ web_contents()->GetRenderProcessHost()->GetID());
+ args->SetString(webview::kReason, TerminationStatusToString(status));
+ DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventExit, std::move(args))));
+}
+
+void WebViewGuest::UserAgentOverrideSet(const std::string& user_agent) {
+ content::NavigationController& controller = web_contents()->GetController();
+ content::NavigationEntry* entry = controller.GetVisibleEntry();
+ if (!entry)
+ return;
+ entry->SetIsOverridingUserAgent(!user_agent.empty());
+ web_contents()->GetController().Reload(false);
+}
+
+void WebViewGuest::FrameNameChanged(RenderFrameHost* render_frame_host,
+ const std::string& name) {
+ if (render_frame_host->GetParent())
+ return;
+
+ if (name_ == name)
+ return;
+
+ ReportFrameNameChange(name);
+}
+
+void WebViewGuest::ReportFrameNameChange(const std::string& name) {
+ name_ = name;
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ args->SetString(webview::kName, name);
+ DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventFrameNameChanged, std::move(args))));
+}
+
+void WebViewGuest::LoadHandlerCalled() {
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventContentLoad, std::move(args))));
+}
+
+void WebViewGuest::LoadRedirect(const GURL& old_url,
+ const GURL& new_url,
+ bool is_top_level) {
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ args->SetBoolean(guest_view::kIsTopLevel, is_top_level);
+ args->SetString(webview::kNewURL, new_url.spec());
+ args->SetString(webview::kOldURL, old_url.spec());
+ DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventLoadRedirect, std::move(args))));
+}
+
+void WebViewGuest::PushWebViewStateToIOThread() {
+ const GURL& site_url = web_contents()->GetSiteInstance()->GetSiteURL();
+ std::string partition_domain;
+ std::string partition_id;
+ bool in_memory;
+ if (!GetGuestPartitionConfigForSite(
+ site_url, &partition_domain, &partition_id, &in_memory)) {
+ NOTREACHED();
+ return;
+ }
+
+ WebViewRendererState::WebViewInfo web_view_info;
+ web_view_info.embedder_process_id =
+ owner_web_contents()->GetRenderProcessHost()->GetID();
+ web_view_info.instance_id = view_instance_id();
+ web_view_info.partition_id = partition_id;
+ web_view_info.owner_host = owner_host();
+ web_view_info.rules_registry_id = rules_registry_id_;
+
+ // Get content scripts IDs added by the guest.
+ WebViewContentScriptManager* manager =
+ WebViewContentScriptManager::Get(browser_context());
+ DCHECK(manager);
+ web_view_info.content_script_ids = manager->GetContentScriptIDSet(
+ web_view_info.embedder_process_id, web_view_info.instance_id);
+
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&WebViewRendererState::AddGuest,
+ base::Unretained(WebViewRendererState::GetInstance()),
+ web_contents()->GetRenderProcessHost()->GetID(),
+ web_contents()->GetRoutingID(),
+ web_view_info));
+}
+
+// static
+void WebViewGuest::RemoveWebViewStateFromIOThread(
+ WebContents* web_contents) {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO, FROM_HERE,
+ base::Bind(
+ &WebViewRendererState::RemoveGuest,
+ base::Unretained(WebViewRendererState::GetInstance()),
+ web_contents->GetRenderProcessHost()->GetID(),
+ web_contents->GetRoutingID()));
+}
+
+void WebViewGuest::RequestMediaAccessPermission(
+ WebContents* source,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback) {
+ web_view_permission_helper_->RequestMediaAccessPermission(source,
+ request,
+ callback);
+}
+
+bool WebViewGuest::CheckMediaAccessPermission(WebContents* source,
+ const GURL& security_origin,
+ content::MediaStreamType type) {
+ return web_view_permission_helper_->CheckMediaAccessPermission(
+ source, security_origin, type);
+}
+
+void WebViewGuest::CanDownload(
+ const GURL& url,
+ const std::string& request_method,
+ const base::Callback<void(bool)>& callback) {
+ web_view_permission_helper_->CanDownload(url, request_method, callback);
+}
+
+void WebViewGuest::RequestPointerLockPermission(
+ bool user_gesture,
+ bool last_unlocked_by_target,
+ const base::Callback<void(bool)>& callback) {
+ web_view_permission_helper_->RequestPointerLockPermission(
+ user_gesture,
+ last_unlocked_by_target,
+ callback);
+}
+
+void WebViewGuest::SignalWhenReady(const base::Closure& callback) {
+ auto manager = WebViewContentScriptManager::Get(browser_context());
+ manager->SignalOnScriptsLoaded(callback);
+}
+
+bool WebViewGuest::ShouldHandleFindRequestsForEmbedder() const {
+ if (web_view_guest_delegate_)
+ return web_view_guest_delegate_->ShouldHandleFindRequestsForEmbedder();
+ return false;
+}
+
+void WebViewGuest::WillAttachToEmbedder() {
+ rules_registry_id_ = GetOrGenerateRulesRegistryID(
+ owner_web_contents()->GetRenderProcessHost()->GetID(),
+ view_instance_id());
+
+ // We must install the mapping from guests to WebViews prior to resuming
+ // suspended resource loads so that the WebRequest API will catch resource
+ // requests.
+ PushWebViewStateToIOThread();
+}
+
+content::JavaScriptDialogManager* WebViewGuest::GetJavaScriptDialogManager(
+ WebContents* source) {
+ return &javascript_dialog_helper_;
+}
+
+void WebViewGuest::NavigateGuest(const std::string& src,
+ bool force_navigation) {
+ if (src.empty())
+ return;
+
+ GURL url = ResolveURL(src);
+
+ // We wait for all the content scripts to load and then navigate the guest
+ // if the navigation is embedder-initiated. For browser-initiated navigations,
+ // content scripts will be ready.
+ if (force_navigation) {
+ SignalWhenReady(
+ base::Bind(&WebViewGuest::LoadURLWithParams,
+ weak_ptr_factory_.GetWeakPtr(), url, content::Referrer(),
+ ui::PAGE_TRANSITION_AUTO_TOPLEVEL, force_navigation));
+ return;
+ }
+ LoadURLWithParams(url, content::Referrer(), ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
+ force_navigation);
+}
+
+bool WebViewGuest::HandleKeyboardShortcuts(
+ const content::NativeWebKeyboardEvent& event) {
+ // <webview> outside of Chrome Apps do not handle keyboard shortcuts.
+ if (!GuestViewManager::FromBrowserContext(browser_context())->
+ IsOwnedByExtension(this)) {
+ return false;
+ }
+
+ if (event.type != blink::WebInputEvent::RawKeyDown)
+ return false;
+
+ // If the user hits the escape key without any modifiers then unlock the
+ // mouse if necessary.
+ if ((event.windowsKeyCode == ui::VKEY_ESCAPE) &&
+ !(event.modifiers & blink::WebInputEvent::InputModifiers)) {
+ return web_contents()->GotResponseToLockMouseRequest(false);
+ }
+
+#if defined(OS_MACOSX)
+ if (event.modifiers != blink::WebInputEvent::MetaKey)
+ return false;
+
+ if (event.windowsKeyCode == ui::VKEY_OEM_4) {
+ Go(-1);
+ return true;
+ }
+
+ if (event.windowsKeyCode == ui::VKEY_OEM_6) {
+ Go(1);
+ return true;
+ }
+#else
+ if (event.windowsKeyCode == ui::VKEY_BROWSER_BACK) {
+ Go(-1);
+ return true;
+ }
+
+ if (event.windowsKeyCode == ui::VKEY_BROWSER_FORWARD) {
+ Go(1);
+ return true;
+ }
+#endif
+
+ return false;
+}
+
+void WebViewGuest::ApplyAttributes(const base::DictionaryValue& params) {
+ std::string name;
+ if (params.GetString(webview::kAttributeName, &name)) {
+ // If the guest window's name is empty, then the WebView tag's name is
+ // assigned. Otherwise, the guest window's name takes precedence over the
+ // WebView tag's name.
+ if (name_.empty())
+ SetName(name);
+ }
+ if (attached())
+ ReportFrameNameChange(name_);
+
+ std::string user_agent_override;
+ params.GetString(webview::kParameterUserAgentOverride, &user_agent_override);
+ SetUserAgentOverride(user_agent_override);
+
+ bool allow_transparency = false;
+ if (params.GetBoolean(webview::kAttributeAllowTransparency,
+ &allow_transparency)) {
+ // We need to set the background opaque flag after navigation to ensure that
+ // there is a RenderWidgetHostView available.
+ SetAllowTransparency(allow_transparency);
+ }
+
+ bool allow_scaling = false;
+ if (params.GetBoolean(webview::kAttributeAllowScaling, &allow_scaling))
+ SetAllowScaling(allow_scaling);
+
+ // Check for a pending zoom from before the first navigation.
+ params.GetDouble(webview::kInitialZoomFactor, &pending_zoom_factor_);
+
+ bool is_pending_new_window = false;
+ if (GetOpener()) {
+ // We need to do a navigation here if the target URL has changed between
+ // the time the WebContents was created and the time it was attached.
+ // We also need to do an initial navigation if a RenderView was never
+ // created for the new window in cases where there is no referrer.
+ auto it = GetOpener()->pending_new_windows_.find(this);
+ if (it != GetOpener()->pending_new_windows_.end()) {
+ const NewWindowInfo& new_window_info = it->second;
+ if (new_window_info.changed || !web_contents()->HasOpener())
+ NavigateGuest(new_window_info.url.spec(), false /* force_navigation */);
+
+ // Once a new guest is attached to the DOM of the embedder page, then the
+ // lifetime of the new guest is no longer managed by the opener guest.
+ GetOpener()->pending_new_windows_.erase(this);
+
+ is_pending_new_window = true;
+ }
+ }
+
+ // Only read the src attribute if this is not a New Window API flow.
+ if (!is_pending_new_window) {
+ std::string src;
+ if (params.GetString(webview::kAttributeSrc, &src))
+ NavigateGuest(src, true /* force_navigation */);
+ }
+}
+
+void WebViewGuest::ShowContextMenu(int request_id) {
+ if (web_view_guest_delegate_)
+ web_view_guest_delegate_->OnShowContextMenu(request_id);
+}
+
+void WebViewGuest::SetName(const std::string& name) {
+ if (name_ == name)
+ return;
+ name_ = name;
+
+ Send(new ExtensionMsg_SetFrameName(routing_id(), name_));
+}
+
+void WebViewGuest::SetZoom(double zoom_factor) {
+ auto zoom_controller = ZoomController::FromWebContents(web_contents());
+ DCHECK(zoom_controller);
+ double zoom_level = content::ZoomFactorToZoomLevel(zoom_factor);
+ zoom_controller->SetZoomLevel(zoom_level);
+}
+
+void WebViewGuest::SetZoomMode(ZoomController::ZoomMode zoom_mode) {
+ ZoomController::FromWebContents(web_contents())->SetZoomMode(zoom_mode);
+}
+
+void WebViewGuest::SetAllowTransparency(bool allow) {
+ if (allow_transparency_ == allow)
+ return;
+
+ allow_transparency_ = allow;
+ if (!web_contents()->GetRenderViewHost()->GetWidget()->GetView())
+ return;
+
+ if (allow_transparency_) {
+ web_contents()
+ ->GetRenderViewHost()
+ ->GetWidget()
+ ->GetView()
+ ->SetBackgroundColor(SK_ColorTRANSPARENT);
+ } else {
+ web_contents()
+ ->GetRenderViewHost()
+ ->GetWidget()
+ ->GetView()
+ ->SetBackgroundColorToDefault();
+ }
+}
+
+void WebViewGuest::SetAllowScaling(bool allow) {
+ allow_scaling_ = allow;
+}
+
+bool WebViewGuest::LoadDataWithBaseURL(const std::string& data_url,
+ const std::string& base_url,
+ const std::string& virtual_url,
+ std::string* error) {
+ // Make GURLs from URLs.
+ const GURL data_gurl = GURL(data_url);
+ const GURL base_gurl = GURL(base_url);
+ const GURL virtual_gurl = GURL(virtual_url);
+
+ // Check that the provided URLs are valid.
+ // |data_url| must be a valid data URL.
+ if (!data_gurl.is_valid() || !data_gurl.SchemeIs(url::kDataScheme)) {
+ base::SStringPrintf(
+ error, webview::kAPILoadDataInvalidDataURL, data_url.c_str());
+ return false;
+ }
+ // |base_url| must be a valid URL.
+ if (!base_gurl.is_valid()) {
+ base::SStringPrintf(
+ error, webview::kAPILoadDataInvalidBaseURL, base_url.c_str());
+ return false;
+ }
+ // |virtual_url| must be a valid URL.
+ if (!virtual_gurl.is_valid()) {
+ base::SStringPrintf(
+ error, webview::kAPILoadDataInvalidVirtualURL, virtual_url.c_str());
+ return false;
+ }
+
+ // Set up the parameters to load |data_url| with the specified |base_url|.
+ content::NavigationController::LoadURLParams load_params(data_gurl);
+ load_params.load_type = content::NavigationController::LOAD_TYPE_DATA;
+ load_params.base_url_for_data_url = base_gurl;
+ load_params.virtual_url_for_data_url = virtual_gurl;
+ load_params.override_user_agent =
+ content::NavigationController::UA_OVERRIDE_INHERIT;
+
+ // Navigate to the data URL.
+ GuestViewBase::LoadURLWithParams(load_params);
+
+ return true;
+}
+
+void WebViewGuest::AddNewContents(WebContents* source,
+ WebContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_rect,
+ bool user_gesture,
+ bool* was_blocked) {
+ if (was_blocked)
+ *was_blocked = false;
+ RequestNewWindowPermission(disposition,
+ initial_rect,
+ user_gesture,
+ new_contents);
+}
+
+WebContents* WebViewGuest::OpenURLFromTab(
+ WebContents* source,
+ const content::OpenURLParams& params) {
+ // Most navigations should be handled by WebViewGuest::LoadURLWithParams,
+ // which takes care of blocking chrome:// URLs and other web-unsafe schemes.
+ // (NavigateGuest and CreateNewGuestWebViewWindow also go through
+ // LoadURLWithParams.)
+ //
+ // We make an exception here for context menu items, since the Language
+ // Settings item uses a browser-initiated navigation to a chrome:// URL.
+ // These can be passed to the embedder's WebContentsDelegate so that the
+ // browser performs the action for the <webview>. Navigations to a new
+ // tab, etc., are also handled by the WebContentsDelegate.
+ if (!params.is_renderer_initiated &&
+ (!content::ChildProcessSecurityPolicy::GetInstance()->IsWebSafeScheme(
+ params.url.scheme()) ||
+ params.disposition != CURRENT_TAB)) {
+ if (!owner_web_contents()->GetDelegate())
+ return nullptr;
+ return owner_web_contents()->GetDelegate()->OpenURLFromTab(
+ owner_web_contents(), params);
+ }
+
+ if (!attached()) {
+ WebViewGuest* opener = GetOpener();
+ // If the guest wishes to navigate away prior to attachment then we save the
+ // navigation to perform upon attachment. Navigation initializes a lot of
+ // state that assumes an embedder exists, such as RenderWidgetHostViewGuest.
+ // Navigation also resumes resource loading. If we were created using
+ // newwindow (i.e. we have an opener), we don't allow navigation until
+ // attachment.
+ if (opener) {
+ auto it = opener->pending_new_windows_.find(this);
+ if (it == opener->pending_new_windows_.end())
+ return nullptr;
+ const NewWindowInfo& info = it->second;
+ NewWindowInfo new_window_info(params.url, info.name);
+ new_window_info.changed = new_window_info.url != info.url;
+ it->second = new_window_info;
+ return nullptr;
+ }
+ }
+
+ // This code path is taken if RenderFrameImpl::DecidePolicyForNavigation
+ // decides that a fork should happen. At the time of writing this comment,
+ // the only way a well behaving guest could hit this code path is if it
+ // navigates to a URL that's associated with the default search engine.
+ // This list of URLs is generated by search::GetSearchURLs. Validity checks
+ // are performed inside LoadURLWithParams such that if the guest attempts
+ // to navigate to a URL that it is not allowed to navigate to, a 'loadabort'
+ // event will fire in the embedder, and the guest will be navigated to
+ // about:blank.
+ if (params.disposition == CURRENT_TAB) {
+ LoadURLWithParams(params.url, params.referrer, params.transition,
+ true /* force_navigation */);
+ return web_contents();
+ }
+
+ // This code path is taken if Ctrl+Click, middle click or any of the
+ // keyboard/mouse combinations are used to open a link in a new tab/window.
+ // This code path is also taken on client-side redirects from about:blank.
+ CreateNewGuestWebViewWindow(params);
+ return nullptr;
+}
+
+void WebViewGuest::WebContentsCreated(WebContents* source_contents,
+ int opener_render_frame_id,
+ const std::string& frame_name,
+ const GURL& target_url,
+ WebContents* new_contents) {
+ auto guest = WebViewGuest::FromWebContents(new_contents);
+ CHECK(guest);
+ guest->SetOpener(this);
+ guest->name_ = frame_name;
+ pending_new_windows_.insert(
+ std::make_pair(guest, NewWindowInfo(target_url, frame_name)));
+}
+
+void WebViewGuest::EnterFullscreenModeForTab(WebContents* web_contents,
+ const GURL& origin) {
+ // Ask the embedder for permission.
+ base::DictionaryValue request_info;
+ request_info.SetString(webview::kOrigin, origin.spec());
+ web_view_permission_helper_->RequestPermission(
+ WEB_VIEW_PERMISSION_TYPE_FULLSCREEN, request_info,
+ base::Bind(&WebViewGuest::OnFullscreenPermissionDecided,
+ weak_ptr_factory_.GetWeakPtr()),
+ false /* allowed_by_default */);
+
+ // TODO(lazyboy): Right now the guest immediately goes fullscreen within its
+ // bounds. If the embedder denies the permission then we will see a flicker.
+ // Once we have the ability to "cancel" a renderer/ fullscreen request:
+ // http://crbug.com/466854 this won't be necessary and we should be
+ // Calling SetFullscreenState(true) once the embedder allowed the request.
+ // Otherwise we would cancel renderer/ fullscreen if the embedder denied.
+ SetFullscreenState(true);
+}
+
+void WebViewGuest::ExitFullscreenModeForTab(WebContents* web_contents) {
+ SetFullscreenState(false);
+}
+
+bool WebViewGuest::IsFullscreenForTabOrPending(
+ const WebContents* web_contents) const {
+ return is_guest_fullscreen_;
+}
+
+void WebViewGuest::LoadURLWithParams(
+ const GURL& url,
+ const content::Referrer& referrer,
+ ui::PageTransition transition_type,
+ bool force_navigation) {
+ if (!url.is_valid()) {
+ LoadAbort(true /* is_top_level */, url, net::ERR_INVALID_URL,
+ net::ErrorToShortString(net::ERR_INVALID_URL));
+ NavigateGuest(url::kAboutBlankURL, false /* force_navigation */);
+ return;
+ }
+
+ bool scheme_is_blocked =
+ (!content::ChildProcessSecurityPolicy::GetInstance()->IsWebSafeScheme(
+ url.scheme()) &&
+ !url.SchemeIs(url::kAboutScheme)) ||
+ url.SchemeIs(url::kJavaScriptScheme);
+
+ // Do not allow navigating a guest to schemes other than known safe schemes.
+ // This will block the embedder trying to load unwanted schemes, e.g.
+ // chrome://.
+ if (scheme_is_blocked) {
+ LoadAbort(true /* is_top_level */, url, net::ERR_DISALLOWED_URL_SCHEME,
+ net::ErrorToShortString(net::ERR_DISALLOWED_URL_SCHEME));
+ NavigateGuest(url::kAboutBlankURL, false /* force_navigation */);
+ return;
+ }
+
+ if (!force_navigation && (src_ == url))
+ return;
+
+ GURL validated_url(url);
+ web_contents()->GetRenderProcessHost()->FilterURL(false, &validated_url);
+ // As guests do not swap processes on navigation, only navigations to
+ // normal web URLs are supported. No protocol handlers are installed for
+ // other schemes (e.g., WebUI or extensions), and no permissions or bindings
+ // can be granted to the guest process.
+ content::NavigationController::LoadURLParams load_url_params(validated_url);
+ load_url_params.referrer = referrer;
+ load_url_params.transition_type = transition_type;
+ load_url_params.extra_headers = std::string();
+ load_url_params.transferred_global_request_id = GlobalRequestID();
+ if (is_overriding_user_agent_) {
+ load_url_params.override_user_agent =
+ content::NavigationController::UA_OVERRIDE_TRUE;
+ }
+ GuestViewBase::LoadURLWithParams(load_url_params);
+
+ src_ = validated_url;
+}
+
+void WebViewGuest::RequestNewWindowPermission(WindowOpenDisposition disposition,
+ const gfx::Rect& initial_bounds,
+ bool user_gesture,
+ WebContents* new_contents) {
+ auto guest = WebViewGuest::FromWebContents(new_contents);
+ if (!guest)
+ return;
+ auto it = pending_new_windows_.find(guest);
+ if (it == pending_new_windows_.end())
+ return;
+ const NewWindowInfo& new_window_info = it->second;
+
+ // Retrieve the opener partition info if we have it.
+ const GURL& site_url = new_contents->GetSiteInstance()->GetSiteURL();
+ std::string storage_partition_id = GetStoragePartitionIdFromSiteURL(site_url);
+
+ base::DictionaryValue request_info;
+ request_info.SetInteger(webview::kInitialHeight, initial_bounds.height());
+ request_info.SetInteger(webview::kInitialWidth, initial_bounds.width());
+ request_info.Set(webview::kTargetURL,
+ new base::StringValue(new_window_info.url.spec()));
+ request_info.Set(webview::kName, new base::StringValue(new_window_info.name));
+ request_info.SetInteger(webview::kWindowID, guest->guest_instance_id());
+ // We pass in partition info so that window-s created through newwindow
+ // API can use it to set their partition attribute.
+ request_info.Set(webview::kStoragePartitionId,
+ new base::StringValue(storage_partition_id));
+ request_info.Set(
+ webview::kWindowOpenDisposition,
+ new base::StringValue(WindowOpenDispositionToString(disposition)));
+
+ web_view_permission_helper_->
+ RequestPermission(WEB_VIEW_PERMISSION_TYPE_NEW_WINDOW,
+ request_info,
+ base::Bind(&WebViewGuest::OnWebViewNewWindowResponse,
+ weak_ptr_factory_.GetWeakPtr(),
+ guest->guest_instance_id()),
+ false /* allowed_by_default */);
+}
+
+GURL WebViewGuest::ResolveURL(const std::string& src) {
+ if (!GuestViewManager::FromBrowserContext(browser_context())->
+ IsOwnedByExtension(this)) {
+ return GURL(src);
+ }
+
+ GURL default_url(base::StringPrintf("%s://%s/",
+ kExtensionScheme,
+ owner_host().c_str()));
+ return default_url.Resolve(src);
+}
+
+void WebViewGuest::OnWebViewNewWindowResponse(
+ int new_window_instance_id,
+ bool allow,
+ const std::string& user_input) {
+ auto guest =
+ WebViewGuest::From(owner_web_contents()->GetRenderProcessHost()->GetID(),
+ new_window_instance_id);
+ if (!guest)
+ return;
+
+ if (!allow)
+ guest->Destroy();
+}
+
+void WebViewGuest::OnFullscreenPermissionDecided(
+ bool allowed,
+ const std::string& user_input) {
+ last_fullscreen_permission_was_allowed_by_embedder_ = allowed;
+ SetFullscreenState(allowed);
+}
+
+bool WebViewGuest::GuestMadeEmbedderFullscreen() const {
+ return last_fullscreen_permission_was_allowed_by_embedder_ &&
+ is_embedder_fullscreen_;
+}
+
+void WebViewGuest::SetFullscreenState(bool is_fullscreen) {
+ if (is_fullscreen == is_guest_fullscreen_)
+ return;
+
+ bool was_fullscreen = is_guest_fullscreen_;
+ is_guest_fullscreen_ = is_fullscreen;
+ // If the embedder entered fullscreen because of us, it should exit fullscreen
+ // when we exit fullscreen.
+ if (was_fullscreen && GuestMadeEmbedderFullscreen()) {
+ // Dispatch a message so we can call document.webkitCancelFullscreen()
+ // on the embedder.
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventExitFullscreen, std::move(args))));
+ }
+ // Since we changed fullscreen state, sending a Resize message ensures that
+ // renderer/ sees the change.
+ web_contents()->GetRenderViewHost()->GetWidget()->WasResized();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/web_view/web_view_guest.h b/chromium/extensions/browser/guest_view/web_view/web_view_guest.h
new file mode 100644
index 00000000000..d1baac5b64c
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_view_guest.h
@@ -0,0 +1,389 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_GUEST_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_GUEST_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "components/guest_view/browser/guest_view.h"
+#include "content/public/browser/javascript_dialog_manager.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "extensions/browser/guest_view/web_view/javascript_dialog_helper.h"
+#include "extensions/browser/guest_view/web_view/web_view_find_helper.h"
+#include "extensions/browser/guest_view/web_view/web_view_guest_delegate.h"
+#include "extensions/browser/guest_view/web_view/web_view_permission_helper.h"
+#include "extensions/browser/guest_view/web_view/web_view_permission_types.h"
+#include "extensions/browser/script_executor.h"
+
+namespace blink {
+struct WebFindOptions;
+} // nanespace blink
+
+namespace content {
+struct GlobalRequestID;
+} // namespace content
+
+namespace extensions {
+
+class WebViewInternalFindFunction;
+
+// A WebViewGuest provides the browser-side implementation of the <webview> API
+// and manages the dispatch of <webview> extension events. WebViewGuest is
+// created on attachment. That is, when a guest WebContents is associated with
+// a particular embedder WebContents. This happens on either initial navigation
+// or through the use of the New Window API, when a new window is attached to
+// a particular <webview>.
+class WebViewGuest : public guest_view::GuestView<WebViewGuest>,
+ public content::NotificationObserver {
+ public:
+ // Clean up state when this GuestView is being destroyed. See
+ // GuestViewBase::CleanUp().
+ static void CleanUp(content::BrowserContext* browser_context,
+ int embedder_process_id,
+ int view_instance_id);
+
+ static GuestViewBase* Create(content::WebContents* owner_web_contents);
+
+ // For WebViewGuest, we create special guest processes, which host the
+ // tag content separately from the main application that embeds the tag.
+ // A <webview> can specify both the partition name and whether the storage
+ // for that partition should be persisted. Each tag gets a SiteInstance with
+ // a specially formatted URL, based on the application it is hosted by and
+ // the partition requested by it. The format for that URL is:
+ // chrome-guest://partition_domain/persist?partition_name
+ static bool GetGuestPartitionConfigForSite(const GURL& site,
+ std::string* partition_domain,
+ std::string* partition_name,
+ bool* in_memory);
+
+ // Returns the WebView partition ID associated with the render process
+ // represented by |render_process_host|, if any. Otherwise, an empty string is
+ // returned.
+ static std::string GetPartitionID(
+ const content::RenderProcessHost* render_process_host);
+
+ static const char Type[];
+
+ // Returns the stored rules registry ID of the given webview. Will generate
+ // an ID for the first query.
+ static int GetOrGenerateRulesRegistryID(
+ int embedder_process_id,
+ int web_view_instance_id);
+
+ // Get the current zoom.
+ double GetZoom() const;
+
+ // Get the current zoom mode.
+ ui_zoom::ZoomController::ZoomMode GetZoomMode();
+
+ // Request navigating the guest to the provided |src| URL.
+ void NavigateGuest(const std::string& src, bool force_navigation);
+
+ // Shows the context menu for the guest.
+ void ShowContextMenu(int request_id);
+
+ // Sets the frame name of the guest.
+ void SetName(const std::string& name);
+ const std::string& name() { return name_; }
+
+ // Set the zoom factor.
+ void SetZoom(double zoom_factor);
+
+ // Set the zoom mode.
+ void SetZoomMode(ui_zoom::ZoomController::ZoomMode zoom_mode);
+
+ void SetAllowScaling(bool allow);
+ bool allow_scaling() const { return allow_scaling_; }
+
+ // Sets the transparency of the guest.
+ void SetAllowTransparency(bool allow);
+ bool allow_transparency() const { return allow_transparency_; }
+
+ // Loads a data URL with a specified base URL and virtual URL.
+ bool LoadDataWithBaseURL(const std::string& data_url,
+ const std::string& base_url,
+ const std::string& virtual_url,
+ std::string* error);
+
+ // Begin or continue a find request.
+ void StartFind(const base::string16& search_text,
+ const blink::WebFindOptions& options,
+ scoped_refptr<WebViewInternalFindFunction> find_function);
+
+ // Conclude a find request to clear highlighting.
+ void StopFinding(content::StopFindAction);
+
+ // If possible, navigate the guest to |relative_index| entries away from the
+ // current navigation entry. Returns true on success.
+ bool Go(int relative_index);
+
+ // Reload the guest.
+ void Reload();
+
+ // Overrides the user agent for this guest.
+ // This affects subsequent guest navigations.
+ void SetUserAgentOverride(const std::string& user_agent_override);
+
+ // Stop loading the guest.
+ void Stop();
+
+ // Kill the guest process.
+ void Terminate();
+
+ // Clears data in the storage partition of this guest.
+ //
+ // Partition data that are newer than |removal_since| will be removed.
+ // |removal_mask| corresponds to bitmask in StoragePartition::RemoveDataMask.
+ bool ClearData(const base::Time remove_since,
+ uint32_t removal_mask,
+ const base::Closure& callback);
+
+ ScriptExecutor* script_executor() { return script_executor_.get(); }
+
+ private:
+ friend class WebViewPermissionHelper;
+
+ explicit WebViewGuest(content::WebContents* owner_web_contents);
+
+ ~WebViewGuest() override;
+
+ void ClearDataInternal(const base::Time remove_since,
+ uint32_t removal_mask,
+ const base::Closure& callback);
+
+ void OnWebViewNewWindowResponse(int new_window_instance_id,
+ bool allow,
+ const std::string& user_input);
+
+ void OnFullscreenPermissionDecided(bool allowed,
+ const std::string& user_input);
+ bool GuestMadeEmbedderFullscreen() const;
+ void SetFullscreenState(bool is_fullscreen);
+
+ // GuestViewBase implementation.
+ bool CanRunInDetachedState() const final;
+ void CreateWebContents(const base::DictionaryValue& create_params,
+ const WebContentsCreatedCallback& callback) final;
+ void DidAttachToEmbedder() final;
+ void DidDropLink(const GURL& url) final;
+ void DidInitialize(const base::DictionaryValue& create_params) final;
+ void EmbedderFullscreenToggled(bool entered_fullscreen) final;
+ void FindReply(content::WebContents* source,
+ int request_id,
+ int number_of_matches,
+ const gfx::Rect& selection_rect,
+ int active_match_ordinal,
+ bool final_update) final;
+ const char* GetAPINamespace() const final;
+ int GetTaskPrefix() const final;
+ void GuestDestroyed() final;
+ void GuestReady() final;
+ void GuestSizeChangedDueToAutoSize(const gfx::Size& old_size,
+ const gfx::Size& new_size) final;
+ void GuestViewDidStopLoading() final;
+ void GuestZoomChanged(double old_zoom_level, double new_zoom_level) final;
+ bool IsAutoSizeSupported() const final;
+ void SetContextMenuPosition(const gfx::Point& position) final;
+ void SignalWhenReady(const base::Closure& callback) final;
+ bool ShouldHandleFindRequestsForEmbedder() const final;
+ void WillAttachToEmbedder() final;
+ void WillDestroy() final;
+
+ // NotificationObserver implementation.
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) final;
+
+ // WebContentsDelegate implementation.
+ bool AddMessageToConsole(content::WebContents* source,
+ int32_t level,
+ const base::string16& message,
+ int32_t line_no,
+ const base::string16& source_id) final;
+ void CloseContents(content::WebContents* source) final;
+ bool HandleContextMenu(const content::ContextMenuParams& params) final;
+ void HandleKeyboardEvent(content::WebContents* source,
+ const content::NativeWebKeyboardEvent& event) final;
+ void LoadProgressChanged(content::WebContents* source, double progress) final;
+ bool PreHandleGestureEvent(content::WebContents* source,
+ const blink::WebGestureEvent& event) final;
+ void RendererResponsive(content::WebContents* source) final;
+ void RendererUnresponsive(content::WebContents* source) final;
+ void RequestMediaAccessPermission(
+ content::WebContents* source,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback) final;
+ void RequestPointerLockPermission(
+ bool user_gesture,
+ bool last_unlocked_by_target,
+ const base::Callback<void(bool)>& callback) final;
+ bool CheckMediaAccessPermission(content::WebContents* source,
+ const GURL& security_origin,
+ content::MediaStreamType type) final;
+ void CanDownload(const GURL& url,
+ const std::string& request_method,
+ const base::Callback<void(bool)>& callback) final;
+ content::JavaScriptDialogManager* GetJavaScriptDialogManager(
+ content::WebContents* source) final;
+ void AddNewContents(content::WebContents* source,
+ content::WebContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_rect,
+ bool user_gesture,
+ bool* was_blocked) final;
+ content::WebContents* OpenURLFromTab(
+ content::WebContents* source,
+ const content::OpenURLParams& params) final;
+ void WebContentsCreated(content::WebContents* source_contents,
+ int opener_render_frame_id,
+ const std::string& frame_name,
+ const GURL& target_url,
+ content::WebContents* new_contents) final;
+ void EnterFullscreenModeForTab(content::WebContents* web_contents,
+ const GURL& origin) final;
+ void ExitFullscreenModeForTab(content::WebContents* web_contents) final;
+ bool IsFullscreenForTabOrPending(
+ const content::WebContents* web_contents) const final;
+
+ // WebContentsObserver implementation.
+ void DidCommitProvisionalLoadForFrame(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& url,
+ ui::PageTransition transition_type) final;
+ void DidFailProvisionalLoad(content::RenderFrameHost* render_frame_host,
+ const GURL& validated_url,
+ int error_code,
+ const base::string16& error_description,
+ bool was_ignored_by_handler) final;
+ void DidStartProvisionalLoadForFrame(
+ content::RenderFrameHost* render_frame_host,
+ const GURL& validated_url,
+ bool is_error_page,
+ bool is_iframe_srcdoc) final;
+ void RenderProcessGone(base::TerminationStatus status) final;
+ void UserAgentOverrideSet(const std::string& user_agent) final;
+ void FrameNameChanged(content::RenderFrameHost* render_frame_host,
+ const std::string& name) final;
+
+ // Informs the embedder of a frame name change.
+ void ReportFrameNameChange(const std::string& name);
+
+ // Called after the load handler is called in the guest's main frame.
+ void LoadHandlerCalled();
+
+ // Called when a redirect notification occurs.
+ void LoadRedirect(const GURL& old_url,
+ const GURL& new_url,
+ bool is_top_level);
+
+ void PushWebViewStateToIOThread();
+ static void RemoveWebViewStateFromIOThread(
+ content::WebContents* web_contents);
+
+ // Loads the |url| provided. |force_navigation| indicates whether to reload
+ // the content if the provided |url| matches the current page of the guest.
+ void LoadURLWithParams(
+ const GURL& url,
+ const content::Referrer& referrer,
+ ui::PageTransition transition_type,
+ bool force_navigation);
+
+ void RequestNewWindowPermission(
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_bounds,
+ bool user_gesture,
+ content::WebContents* new_contents);
+
+ // Requests resolution of a potentially relative URL.
+ GURL ResolveURL(const std::string& src);
+
+ // Notification that a load in the guest resulted in abort. Note that |url|
+ // may be invalid.
+ void LoadAbort(bool is_top_level,
+ const GURL& url,
+ int error_code,
+ const std::string& error_type);
+
+ // Creates a new guest window owned by this WebViewGuest.
+ void CreateNewGuestWebViewWindow(const content::OpenURLParams& params);
+
+ void NewGuestWebViewCallback(const content::OpenURLParams& params,
+ content::WebContents* guest_web_contents);
+
+ bool HandleKeyboardShortcuts(const content::NativeWebKeyboardEvent& event);
+
+ void ApplyAttributes(const base::DictionaryValue& params);
+
+ // Identifies the set of rules registries belonging to this guest.
+ int rules_registry_id_;
+
+ // Handles find requests and replies for the webview find API.
+ WebViewFindHelper find_helper_;
+
+ base::ObserverList<ScriptExecutionObserver> script_observers_;
+ scoped_ptr<ScriptExecutor> script_executor_;
+
+ content::NotificationRegistrar notification_registrar_;
+
+ // True if the user agent is overridden.
+ bool is_overriding_user_agent_;
+
+ // Stores the window name of the main frame of the guest.
+ std::string name_;
+
+ // Stores whether the contents of the guest can be transparent.
+ bool allow_transparency_;
+
+ // Stores the src URL of the WebView.
+ GURL src_;
+
+ // Handles the JavaScript dialog requests.
+ JavaScriptDialogHelper javascript_dialog_helper_;
+
+ // Handles permission requests.
+ scoped_ptr<WebViewPermissionHelper> web_view_permission_helper_;
+
+ scoped_ptr<WebViewGuestDelegate> web_view_guest_delegate_;
+
+ // Tracks the name, and target URL of the new window. Once the first
+ // navigation commits, we no longer track this information.
+ struct NewWindowInfo {
+ GURL url;
+ std::string name;
+ bool changed;
+ NewWindowInfo(const GURL& url, const std::string& name) :
+ url(url),
+ name(name),
+ changed(false) {}
+ };
+
+ using PendingWindowMap = std::map<WebViewGuest*, NewWindowInfo>;
+ PendingWindowMap pending_new_windows_;
+
+ // Determines if this guest accepts pinch-zoom gestures.
+ bool allow_scaling_;
+ bool is_guest_fullscreen_;
+ bool is_embedder_fullscreen_;
+ bool last_fullscreen_permission_was_allowed_by_embedder_;
+
+ // Tracks whether the webview has a pending zoom from before the first
+ // navigation. This will be equal to 0 when there is no pending zoom.
+ double pending_zoom_factor_;
+
+ // This is used to ensure pending tasks will not fire after this object is
+ // destroyed.
+ base::WeakPtrFactory<WebViewGuest> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewGuest);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_GUEST_H_
diff --git a/chromium/extensions/browser/guest_view/web_view/web_view_guest_delegate.h b/chromium/extensions/browser/guest_view/web_view/web_view_guest_delegate.h
new file mode 100644
index 00000000000..7ea61cad14f
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_view_guest_delegate.h
@@ -0,0 +1,50 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_GUEST_DELEGATE_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_GUEST_DELEGATE_H_
+
+#include "base/callback.h"
+#include "components/guest_view/browser/guest_view_base.h"
+
+namespace content {
+class RenderViewHost;
+class WebContents;
+} // namespace content
+
+namespace extensions {
+
+class WebViewGuest;
+
+namespace api {
+namespace web_view_internal {
+
+struct ContextMenuItem;
+} // namespace web_view_internal
+} // namespace api
+
+// A delegate class of WebViewGuest that are not a part of chrome.
+class WebViewGuestDelegate {
+ public :
+ virtual ~WebViewGuestDelegate() {}
+
+ // Called when context menu operation was handled.
+ virtual bool HandleContextMenu(const content::ContextMenuParams& params) = 0;
+
+ // Called just after additional initialization is performed.
+ virtual void OnDidInitialize() = 0;
+
+ // Shows the context menu for the guest.
+ virtual void OnShowContextMenu(int request_id) = 0;
+
+ // Returns true if the WebViewGuest should handle find requests for its
+ // embedder.
+ virtual bool ShouldHandleFindRequestsForEmbedder() const = 0;
+
+ virtual void SetContextMenuPosition(const gfx::Point& position) = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_GUEST_DELEGATE_H_
diff --git a/chromium/extensions/browser/guest_view/web_view/web_view_media_access_apitest.cc b/chromium/extensions/browser/guest_view/web_view/web_view_media_access_apitest.cc
new file mode 100644
index 00000000000..dcf41907cc1
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_view_media_access_apitest.cc
@@ -0,0 +1,172 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/test/browser_test_utils.h"
+#include "extensions/browser/guest_view/web_view/web_view_apitest.h"
+#include "extensions/test/extension_test_message_listener.h"
+
+namespace {
+
+// This class intercepts media access request from the embedder. The request
+// should be triggered only if the embedder API (from tests) allows the request
+// in Javascript.
+// We do not issue the actual media request; the fact that the request reached
+// embedder's WebContents is good enough for our tests. This is also to make
+// the test run successfully on trybots.
+class MockWebContentsDelegate : public content::WebContentsDelegate {
+ public:
+ MockWebContentsDelegate() : requested_(false), checked_(false) {}
+ ~MockWebContentsDelegate() override {}
+
+ void RequestMediaAccessPermission(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback) override {
+ requested_ = true;
+ if (request_message_loop_runner_.get())
+ request_message_loop_runner_->Quit();
+ }
+
+ bool CheckMediaAccessPermission(content::WebContents* web_contents,
+ const GURL& security_origin,
+ content::MediaStreamType type) override {
+ checked_ = true;
+ if (check_message_loop_runner_.get())
+ check_message_loop_runner_->Quit();
+ return true;
+ }
+
+ void WaitForRequestMediaPermission() {
+ if (requested_)
+ return;
+ request_message_loop_runner_ = new content::MessageLoopRunner;
+ request_message_loop_runner_->Run();
+ }
+
+ void WaitForCheckMediaPermission() {
+ if (checked_)
+ return;
+ check_message_loop_runner_ = new content::MessageLoopRunner;
+ check_message_loop_runner_->Run();
+ }
+
+ private:
+ bool requested_;
+ bool checked_;
+ scoped_refptr<content::MessageLoopRunner> request_message_loop_runner_;
+ scoped_refptr<content::MessageLoopRunner> check_message_loop_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockWebContentsDelegate);
+};
+
+} // namespace
+
+namespace extensions {
+
+class WebViewMediaAccessAPITest : public WebViewAPITest {
+ protected:
+ WebViewMediaAccessAPITest() {}
+
+ // Runs media_access tests.
+ void RunTest(const std::string& test_name) {
+ ExtensionTestMessageListener test_run_listener("TEST_PASSED", false);
+ test_run_listener.set_failure_message("TEST_FAILED");
+ EXPECT_TRUE(content::ExecuteScript(
+ embedder_web_contents_,
+ base::StringPrintf("runTest('%s');", test_name.c_str())));
+ ASSERT_TRUE(test_run_listener.WaitUntilSatisfied());
+ }
+
+ // content::BrowserTestBase implementation
+ void SetUpOnMainThread() override {
+ WebViewAPITest::SetUpOnMainThread();
+ StartTestServer();
+ }
+
+ void TearDownOnMainThread() override {
+ WebViewAPITest::TearDownOnMainThread();
+ StopTestServer();
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(WebViewMediaAccessAPITest, TestAllow) {
+ LaunchApp("web_view/media_access/allow");
+ scoped_ptr<MockWebContentsDelegate> mock(new MockWebContentsDelegate());
+ embedder_web_contents_->SetDelegate(mock.get());
+
+ RunTest("testAllow");
+
+ mock->WaitForRequestMediaPermission();
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewMediaAccessAPITest, TestAllowAndThenDeny) {
+ LaunchApp("web_view/media_access/allow");
+ scoped_ptr<MockWebContentsDelegate> mock(new MockWebContentsDelegate());
+ embedder_web_contents_->SetDelegate(mock.get());
+
+ RunTest("testAllowAndThenDeny");
+
+ mock->WaitForRequestMediaPermission();
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewMediaAccessAPITest, TestAllowAsync) {
+ LaunchApp("web_view/media_access/allow");
+ scoped_ptr<MockWebContentsDelegate> mock(new MockWebContentsDelegate());
+ embedder_web_contents_->SetDelegate(mock.get());
+
+ RunTest("testAllowAsync");
+
+ mock->WaitForRequestMediaPermission();
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewMediaAccessAPITest, TestAllowTwice) {
+ LaunchApp("web_view/media_access/allow");
+ scoped_ptr<MockWebContentsDelegate> mock(new MockWebContentsDelegate());
+ embedder_web_contents_->SetDelegate(mock.get());
+
+ RunTest("testAllowTwice");
+
+ mock->WaitForRequestMediaPermission();
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewMediaAccessAPITest, TestCheck) {
+ LaunchApp("web_view/media_access/check");
+ scoped_ptr<MockWebContentsDelegate> mock(new MockWebContentsDelegate());
+ embedder_web_contents_->SetDelegate(mock.get());
+
+ RunTest("testCheck");
+
+ mock->WaitForCheckMediaPermission();
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewMediaAccessAPITest, TestDeny) {
+ LaunchApp("web_view/media_access/deny");
+ RunTest("testDeny");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewMediaAccessAPITest, TestDenyThenAllowThrows) {
+ LaunchApp("web_view/media_access/deny");
+ RunTest("testDenyThenAllowThrows");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewMediaAccessAPITest, TestDenyWithPreventDefault) {
+ LaunchApp("web_view/media_access/deny");
+ RunTest("testDenyWithPreventDefault");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewMediaAccessAPITest, TestNoListenersImplyDeny) {
+ LaunchApp("web_view/media_access/deny");
+ RunTest("testNoListenersImplyDeny");
+}
+
+IN_PROC_BROWSER_TEST_F(WebViewMediaAccessAPITest,
+ TestNoPreventDefaultImpliesDeny) {
+ LaunchApp("web_view/media_access/deny");
+ RunTest("testNoPreventDefaultImpliesDeny");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/web_view/web_view_permission_helper.cc b/chromium/extensions/browser/guest_view/web_view/web_view_permission_helper.cc
new file mode 100644
index 00000000000..822f1312f5c
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_view_permission_helper.cc
@@ -0,0 +1,412 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/web_view/web_view_permission_helper.h"
+
+#include <utility>
+
+#include "components/guest_view/browser/guest_view_event.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/user_metrics.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/guest_view/web_view/web_view_constants.h"
+#include "extensions/browser/guest_view/web_view/web_view_guest.h"
+#include "extensions/browser/guest_view/web_view/web_view_permission_helper_delegate.h"
+#include "extensions/browser/guest_view/web_view/web_view_permission_types.h"
+
+using content::BrowserPluginGuestDelegate;
+using content::RenderViewHost;
+using guest_view::GuestViewEvent;
+
+namespace extensions {
+
+namespace {
+static std::string PermissionTypeToString(WebViewPermissionType type) {
+ switch (type) {
+ case WEB_VIEW_PERMISSION_TYPE_DOWNLOAD:
+ return webview::kPermissionTypeDownload;
+ case WEB_VIEW_PERMISSION_TYPE_FILESYSTEM:
+ return webview::kPermissionTypeFileSystem;
+ case WEB_VIEW_PERMISSION_TYPE_FULLSCREEN:
+ return webview::kPermissionTypeFullscreen;
+ case WEB_VIEW_PERMISSION_TYPE_GEOLOCATION:
+ return webview::kPermissionTypeGeolocation;
+ case WEB_VIEW_PERMISSION_TYPE_JAVASCRIPT_DIALOG:
+ return webview::kPermissionTypeDialog;
+ case WEB_VIEW_PERMISSION_TYPE_LOAD_PLUGIN:
+ return webview::kPermissionTypeLoadPlugin;
+ case WEB_VIEW_PERMISSION_TYPE_MEDIA:
+ return webview::kPermissionTypeMedia;
+ case WEB_VIEW_PERMISSION_TYPE_NEW_WINDOW:
+ return webview::kPermissionTypeNewWindow;
+ case WEB_VIEW_PERMISSION_TYPE_POINTER_LOCK:
+ return webview::kPermissionTypePointerLock;
+ default:
+ NOTREACHED();
+ return std::string();
+ }
+}
+
+// static
+void RecordUserInitiatedUMA(
+ const WebViewPermissionHelper::PermissionResponseInfo& info,
+ bool allow) {
+ if (allow) {
+ // Note that |allow| == true means the embedder explicitly allowed the
+ // request. For some requests they might still fail. An example of such
+ // scenario would be: an embedder allows geolocation request but doesn't
+ // have geolocation access on its own.
+ switch (info.permission_type) {
+ case WEB_VIEW_PERMISSION_TYPE_DOWNLOAD:
+ content::RecordAction(
+ UserMetricsAction("WebView.PermissionAllow.Download"));
+ break;
+ case WEB_VIEW_PERMISSION_TYPE_FILESYSTEM:
+ content::RecordAction(
+ UserMetricsAction("WebView.PermissionAllow.FileSystem"));
+ break;
+ case WEB_VIEW_PERMISSION_TYPE_FULLSCREEN:
+ content::RecordAction(
+ UserMetricsAction("WebView.PermissionAllow.Fullscreen"));
+ break;
+ case WEB_VIEW_PERMISSION_TYPE_GEOLOCATION:
+ content::RecordAction(
+ UserMetricsAction("WebView.PermissionAllow.Geolocation"));
+ break;
+ case WEB_VIEW_PERMISSION_TYPE_JAVASCRIPT_DIALOG:
+ content::RecordAction(
+ UserMetricsAction("WebView.PermissionAllow.JSDialog"));
+ break;
+ case WEB_VIEW_PERMISSION_TYPE_LOAD_PLUGIN:
+ content::RecordAction(
+ UserMetricsAction("WebView.Guest.PermissionAllow.PluginLoad"));
+ break;
+ case WEB_VIEW_PERMISSION_TYPE_MEDIA:
+ content::RecordAction(
+ UserMetricsAction("WebView.PermissionAllow.Media"));
+ break;
+ case WEB_VIEW_PERMISSION_TYPE_NEW_WINDOW:
+ content::RecordAction(
+ UserMetricsAction("BrowserPlugin.PermissionAllow.NewWindow"));
+ break;
+ case WEB_VIEW_PERMISSION_TYPE_POINTER_LOCK:
+ content::RecordAction(
+ UserMetricsAction("WebView.PermissionAllow.PointerLock"));
+ break;
+ default:
+ break;
+ }
+ } else {
+ switch (info.permission_type) {
+ case WEB_VIEW_PERMISSION_TYPE_DOWNLOAD:
+ content::RecordAction(
+ UserMetricsAction("WebView.PermissionDeny.Download"));
+ break;
+ case WEB_VIEW_PERMISSION_TYPE_FILESYSTEM:
+ content::RecordAction(
+ UserMetricsAction("WebView.PermissionDeny.FileSystem"));
+ break;
+ case WEB_VIEW_PERMISSION_TYPE_FULLSCREEN:
+ content::RecordAction(
+ UserMetricsAction("WebView.PermissionDeny.Fullscreen"));
+ break;
+ case WEB_VIEW_PERMISSION_TYPE_GEOLOCATION:
+ content::RecordAction(
+ UserMetricsAction("WebView.PermissionDeny.Geolocation"));
+ break;
+ case WEB_VIEW_PERMISSION_TYPE_JAVASCRIPT_DIALOG:
+ content::RecordAction(
+ UserMetricsAction("WebView.PermissionDeny.JSDialog"));
+ break;
+ case WEB_VIEW_PERMISSION_TYPE_LOAD_PLUGIN:
+ content::RecordAction(
+ UserMetricsAction("WebView.Guest.PermissionDeny.PluginLoad"));
+ break;
+ case WEB_VIEW_PERMISSION_TYPE_MEDIA:
+ content::RecordAction(
+ UserMetricsAction("WebView.PermissionDeny.Media"));
+ break;
+ case WEB_VIEW_PERMISSION_TYPE_NEW_WINDOW:
+ content::RecordAction(
+ UserMetricsAction("BrowserPlugin.PermissionDeny.NewWindow"));
+ break;
+ case WEB_VIEW_PERMISSION_TYPE_POINTER_LOCK:
+ content::RecordAction(
+ UserMetricsAction("WebView.PermissionDeny.PointerLock"));
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+} // namespace
+
+WebViewPermissionHelper::WebViewPermissionHelper(WebViewGuest* web_view_guest)
+ : content::WebContentsObserver(web_view_guest->web_contents()),
+ next_permission_request_id_(guest_view::kInstanceIDNone),
+ web_view_guest_(web_view_guest),
+ weak_factory_(this) {
+ web_view_permission_helper_delegate_.reset(
+ ExtensionsAPIClient::Get()->CreateWebViewPermissionHelperDelegate(
+ this));
+}
+
+WebViewPermissionHelper::~WebViewPermissionHelper() {
+}
+
+// static
+WebViewPermissionHelper* WebViewPermissionHelper::FromFrameID(
+ int render_process_id,
+ int render_frame_id) {
+ WebViewGuest* web_view_guest = WebViewGuest::FromFrameID(
+ render_process_id, render_frame_id);
+ if (!web_view_guest) {
+ return NULL;
+ }
+ return web_view_guest->web_view_permission_helper_.get();
+}
+
+// static
+WebViewPermissionHelper* WebViewPermissionHelper::FromWebContents(
+ content::WebContents* web_contents) {
+ WebViewGuest* web_view_guest = WebViewGuest::FromWebContents(web_contents);
+ if (!web_view_guest)
+ return NULL;
+ return web_view_guest->web_view_permission_helper_.get();
+}
+
+#if defined(ENABLE_PLUGINS)
+bool WebViewPermissionHelper::OnMessageReceived(
+ const IPC::Message& message,
+ content::RenderFrameHost* render_frame_host) {
+ return web_view_permission_helper_delegate_->OnMessageReceived(
+ message, render_frame_host);
+}
+
+bool WebViewPermissionHelper::OnMessageReceived(const IPC::Message& message) {
+ return web_view_permission_helper_delegate_->OnMessageReceived(message);
+}
+#endif // defined(ENABLE_PLUGINS)
+
+void WebViewPermissionHelper::RequestMediaAccessPermission(
+ content::WebContents* source,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback) {
+ base::DictionaryValue request_info;
+ request_info.SetString(guest_view::kUrl, request.security_origin.spec());
+ RequestPermission(
+ WEB_VIEW_PERMISSION_TYPE_MEDIA,
+ request_info,
+ base::Bind(&WebViewPermissionHelper::OnMediaPermissionResponse,
+ weak_factory_.GetWeakPtr(),
+ request,
+ callback),
+ false /* allowed_by_default */);
+}
+
+bool WebViewPermissionHelper::CheckMediaAccessPermission(
+ content::WebContents* source,
+ const GURL& security_origin,
+ content::MediaStreamType type) {
+ if (!web_view_guest()->attached() ||
+ !web_view_guest()->embedder_web_contents()->GetDelegate()) {
+ return false;
+ }
+ return web_view_guest()
+ ->embedder_web_contents()
+ ->GetDelegate()
+ ->CheckMediaAccessPermission(
+ web_view_guest()->embedder_web_contents(), security_origin, type);
+}
+
+void WebViewPermissionHelper::OnMediaPermissionResponse(
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback,
+ bool allow,
+ const std::string& user_input) {
+ if (!allow) {
+ callback.Run(content::MediaStreamDevices(),
+ content::MEDIA_DEVICE_PERMISSION_DENIED,
+ scoped_ptr<content::MediaStreamUI>());
+ return;
+ }
+ if (!web_view_guest()->attached() ||
+ !web_view_guest()->embedder_web_contents()->GetDelegate()) {
+ callback.Run(content::MediaStreamDevices(),
+ content::MEDIA_DEVICE_INVALID_STATE,
+ scoped_ptr<content::MediaStreamUI>());
+ return;
+ }
+
+ web_view_guest()
+ ->embedder_web_contents()
+ ->GetDelegate()
+ ->RequestMediaAccessPermission(
+ web_view_guest()->embedder_web_contents(), request, callback);
+}
+
+void WebViewPermissionHelper::CanDownload(
+ const GURL& url,
+ const std::string& request_method,
+ const base::Callback<void(bool)>& callback) {
+ web_view_permission_helper_delegate_->CanDownload(url, request_method,
+ callback);
+}
+
+void WebViewPermissionHelper::RequestPointerLockPermission(
+ bool user_gesture,
+ bool last_unlocked_by_target,
+ const base::Callback<void(bool)>& callback) {
+ web_view_permission_helper_delegate_->RequestPointerLockPermission(
+ user_gesture, last_unlocked_by_target, callback);
+}
+
+void WebViewPermissionHelper::RequestGeolocationPermission(
+ int bridge_id,
+ const GURL& requesting_frame,
+ const base::Callback<void(bool)>& callback) {
+ web_view_permission_helper_delegate_->RequestGeolocationPermission(
+ bridge_id, requesting_frame, callback);
+}
+
+void WebViewPermissionHelper::CancelGeolocationPermissionRequest(
+ int bridge_id) {
+ web_view_permission_helper_delegate_->CancelGeolocationPermissionRequest(
+ bridge_id);
+}
+
+void WebViewPermissionHelper::RequestFileSystemPermission(
+ const GURL& url,
+ bool allowed_by_default,
+ const base::Callback<void(bool)>& callback) {
+ web_view_permission_helper_delegate_->RequestFileSystemPermission(
+ url, allowed_by_default, callback);
+}
+
+void WebViewPermissionHelper::FileSystemAccessedAsync(int render_process_id,
+ int render_frame_id,
+ int request_id,
+ const GURL& url,
+ bool blocked_by_policy) {
+ web_view_permission_helper_delegate_->FileSystemAccessedAsync(
+ render_process_id, render_frame_id, request_id, url, blocked_by_policy);
+}
+
+void WebViewPermissionHelper::FileSystemAccessedSync(int render_process_id,
+ int render_frame_id,
+ const GURL& url,
+ bool blocked_by_policy,
+ IPC::Message* reply_msg) {
+ web_view_permission_helper_delegate_->FileSystemAccessedSync(
+ render_process_id, render_frame_id, url, blocked_by_policy, reply_msg);
+}
+
+int WebViewPermissionHelper::RequestPermission(
+ WebViewPermissionType permission_type,
+ const base::DictionaryValue& request_info,
+ const PermissionResponseCallback& callback,
+ bool allowed_by_default) {
+ // If there are too many pending permission requests then reject this request.
+ if (pending_permission_requests_.size() >=
+ webview::kMaxOutstandingPermissionRequests) {
+ // Let the stack unwind before we deny the permission request so that
+ // objects held by the permission request are not destroyed immediately
+ // after creation. This is to allow those same objects to be accessed again
+ // in the same scope without fear of use after freeing.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&PermissionResponseCallback::Run,
+ base::Owned(new PermissionResponseCallback(callback)),
+ allowed_by_default,
+ std::string()));
+ return webview::kInvalidPermissionRequestID;
+ }
+
+ int request_id = next_permission_request_id_++;
+ pending_permission_requests_[request_id] =
+ PermissionResponseInfo(callback, permission_type, allowed_by_default);
+ scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
+ args->Set(webview::kRequestInfo, request_info.DeepCopy());
+ args->SetInteger(webview::kRequestId, request_id);
+ switch (permission_type) {
+ case WEB_VIEW_PERMISSION_TYPE_NEW_WINDOW: {
+ web_view_guest_->DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventNewWindow, std::move(args))));
+ break;
+ }
+ case WEB_VIEW_PERMISSION_TYPE_JAVASCRIPT_DIALOG: {
+ web_view_guest_->DispatchEventToView(make_scoped_ptr(
+ new GuestViewEvent(webview::kEventDialog, std::move(args))));
+ break;
+ }
+ default: {
+ args->SetString(webview::kPermission,
+ PermissionTypeToString(permission_type));
+ web_view_guest_->DispatchEventToView(make_scoped_ptr(new GuestViewEvent(
+ webview::kEventPermissionRequest, std::move(args))));
+ break;
+ }
+ }
+ return request_id;
+}
+
+WebViewPermissionHelper::SetPermissionResult
+WebViewPermissionHelper::SetPermission(
+ int request_id,
+ PermissionResponseAction action,
+ const std::string& user_input) {
+ RequestMap::iterator request_itr =
+ pending_permission_requests_.find(request_id);
+
+ if (request_itr == pending_permission_requests_.end())
+ return SET_PERMISSION_INVALID;
+
+ const PermissionResponseInfo& info = request_itr->second;
+ bool allow = (action == ALLOW) ||
+ ((action == DEFAULT) && info.allowed_by_default);
+
+ info.callback.Run(allow, user_input);
+
+ // Only record user initiated (i.e. non-default) actions.
+ if (action != DEFAULT)
+ RecordUserInitiatedUMA(info, allow);
+
+ pending_permission_requests_.erase(request_itr);
+
+ return allow ? SET_PERMISSION_ALLOWED : SET_PERMISSION_DENIED;
+}
+
+void WebViewPermissionHelper::CancelPendingPermissionRequest(int request_id) {
+ RequestMap::iterator request_itr =
+ pending_permission_requests_.find(request_id);
+
+ if (request_itr == pending_permission_requests_.end())
+ return;
+
+ pending_permission_requests_.erase(request_itr);
+}
+
+WebViewPermissionHelper::PermissionResponseInfo::PermissionResponseInfo()
+ : permission_type(WEB_VIEW_PERMISSION_TYPE_UNKNOWN),
+ allowed_by_default(false) {
+}
+
+WebViewPermissionHelper::PermissionResponseInfo::PermissionResponseInfo(
+ const PermissionResponseCallback& callback,
+ WebViewPermissionType permission_type,
+ bool allowed_by_default)
+ : callback(callback),
+ permission_type(permission_type),
+ allowed_by_default(allowed_by_default) {
+}
+
+WebViewPermissionHelper::PermissionResponseInfo::PermissionResponseInfo(
+ const PermissionResponseInfo& other) = default;
+
+WebViewPermissionHelper::PermissionResponseInfo::~PermissionResponseInfo() {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/web_view/web_view_permission_helper.h b/chromium/extensions/browser/guest_view/web_view/web_view_permission_helper.h
new file mode 100644
index 00000000000..f5d037ddc43
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_view_permission_helper.h
@@ -0,0 +1,168 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_PERMISSION_HELPER_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_PERMISSION_HELPER_H_
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/metrics/user_metrics_action.h"
+#include "components/guest_view/common/guest_view_constants.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/media_stream_request.h"
+#include "extensions/browser/guest_view/web_view/web_view_permission_types.h"
+
+using base::UserMetricsAction;
+
+namespace extensions {
+
+class WebViewGuest;
+class WebViewPermissionHelperDelegate;
+
+// WebViewPermissionHelper manages <webview> permission requests. This helper
+// class is owned by WebViewGuest. Its purpose is to request permission for
+// various operations from the <webview> embedder, and reply back via callbacks
+// to the callers on a response from the embedder.
+class WebViewPermissionHelper
+ : public content::WebContentsObserver {
+ public:
+ explicit WebViewPermissionHelper(WebViewGuest* guest);
+ ~WebViewPermissionHelper() override;
+ typedef base::Callback<
+ void(bool /* allow */, const std::string& /* user_input */)>
+ PermissionResponseCallback;
+
+ // A map to store the callback for a request keyed by the request's id.
+ struct PermissionResponseInfo {
+ PermissionResponseCallback callback;
+ WebViewPermissionType permission_type;
+ bool allowed_by_default;
+ PermissionResponseInfo();
+ PermissionResponseInfo(const PermissionResponseCallback& callback,
+ WebViewPermissionType permission_type,
+ bool allowed_by_default);
+ PermissionResponseInfo(const PermissionResponseInfo& other);
+ ~PermissionResponseInfo();
+ };
+
+ typedef std::map<int, PermissionResponseInfo> RequestMap;
+
+ int RequestPermission(WebViewPermissionType permission_type,
+ const base::DictionaryValue& request_info,
+ const PermissionResponseCallback& callback,
+ bool allowed_by_default);
+
+ static WebViewPermissionHelper* FromWebContents(
+ content::WebContents* web_contents);
+ static WebViewPermissionHelper* FromFrameID(int render_process_id,
+ int render_frame_id);
+ void RequestMediaAccessPermission(
+ content::WebContents* source,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback);
+ bool CheckMediaAccessPermission(content::WebContents* source,
+ const GURL& security_origin,
+ content::MediaStreamType type);
+ void CanDownload(const GURL& url,
+ const std::string& request_method,
+ const base::Callback<void(bool)>& callback);
+ void RequestPointerLockPermission(bool user_gesture,
+ bool last_unlocked_by_target,
+ const base::Callback<void(bool)>& callback);
+
+ // Requests Geolocation Permission from the embedder.
+ void RequestGeolocationPermission(int bridge_id,
+ const GURL& requesting_frame,
+ const base::Callback<void(bool)>& callback);
+ void CancelGeolocationPermissionRequest(int bridge_id);
+
+ void RequestFileSystemPermission(const GURL& url,
+ bool allowed_by_default,
+ const base::Callback<void(bool)>& callback);
+
+ // Called when file system access is requested by the guest content using the
+ // asynchronous HTML5 file system API. The request is plumbed through the
+ // <webview> permission request API. The request will be:
+ // - Allowed if the embedder explicitly allowed it.
+ // - Denied if the embedder explicitly denied.
+ // - Determined by the guest's content settings if the embedder does not
+ // perform an explicit action.
+ // If access was blocked due to the page's content settings,
+ // |blocked_by_policy| should be true, and this function should invoke
+ // OnContentBlocked.
+ void FileSystemAccessedAsync(int render_process_id,
+ int render_frame_id,
+ int request_id,
+ const GURL& url,
+ bool blocked_by_policy);
+
+ // Called when file system access is requested by the guest content using the
+ // synchronous HTML5 file system API in a worker thread or shared worker. The
+ // request is plumbed through the <webview> permission request API. The
+ // request will be:
+ // - Allowed if the embedder explicitly allowed it.
+ // - Denied if the embedder explicitly denied.
+ // - Determined by the guest's content settings if the embedder does not
+ // perform an explicit action.
+ // If access was blocked due to the page's content settings,
+ // |blocked_by_policy| should be true, and this function should invoke
+ // OnContentBlocked.
+ void FileSystemAccessedSync(int render_process_id,
+ int render_frame_id,
+ const GURL& url,
+ bool blocked_by_policy,
+ IPC::Message* reply_msg);
+
+ enum PermissionResponseAction { DENY, ALLOW, DEFAULT };
+
+ enum SetPermissionResult {
+ SET_PERMISSION_INVALID,
+ SET_PERMISSION_ALLOWED,
+ SET_PERMISSION_DENIED
+ };
+
+ // Responds to the permission request |request_id| with |action| and
+ // |user_input|. Returns whether there was a pending request for the provided
+ // |request_id|.
+ SetPermissionResult SetPermission(int request_id,
+ PermissionResponseAction action,
+ const std::string& user_input);
+
+ void CancelPendingPermissionRequest(int request_id);
+
+ WebViewGuest* web_view_guest() { return web_view_guest_; }
+
+ private:
+ void OnMediaPermissionResponse(const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback,
+ bool allow,
+ const std::string& user_input);
+
+#if defined(ENABLE_PLUGINS)
+ // content::WebContentsObserver implementation.
+ bool OnMessageReceived(const IPC::Message& message,
+ content::RenderFrameHost* render_frame_host) override;
+ bool OnMessageReceived(const IPC::Message& message) override;
+#endif // defined(ENABLE_PLUGINS)
+
+ // A counter to generate a unique request id for a permission request.
+ // We only need the ids to be unique for a given WebViewGuest.
+ int next_permission_request_id_;
+
+ WebViewPermissionHelper::RequestMap pending_permission_requests_;
+
+ scoped_ptr<WebViewPermissionHelperDelegate>
+ web_view_permission_helper_delegate_;
+
+ WebViewGuest* const web_view_guest_;
+
+ base::WeakPtrFactory<WebViewPermissionHelper> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewPermissionHelper);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_PERMISSION_HELPER_H_
diff --git a/chromium/extensions/browser/guest_view/web_view/web_view_permission_helper_delegate.cc b/chromium/extensions/browser/guest_view/web_view/web_view_permission_helper_delegate.cc
new file mode 100644
index 00000000000..1ce7c289b0d
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_view_permission_helper_delegate.cc
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/guest_view/web_view/web_view_permission_helper_delegate.h"
+
+#include "extensions/browser/guest_view/web_view/web_view_guest.h"
+
+namespace extensions {
+
+WebViewPermissionHelperDelegate::WebViewPermissionHelperDelegate(
+ WebViewPermissionHelper* web_view_permission_helper)
+ : content::WebContentsObserver(
+ web_view_permission_helper->web_view_guest()->web_contents()),
+ web_view_permission_helper_(web_view_permission_helper) {
+}
+
+WebViewPermissionHelperDelegate::~WebViewPermissionHelperDelegate() {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/web_view/web_view_permission_helper_delegate.h b/chromium/extensions/browser/guest_view/web_view/web_view_permission_helper_delegate.h
new file mode 100644
index 00000000000..eea7ad4d94d
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_view_permission_helper_delegate.h
@@ -0,0 +1,94 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIWE_PERMISSION_HELPER_DELEGATE_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIWE_PERMISSION_HELPER_DELEGATE_H_
+
+#include "base/macros.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/media_stream_request.h"
+#include "extensions/browser/guest_view/web_view/web_view_permission_helper.h"
+
+namespace extensions {
+
+// A delegate class of WebViewPermissionHelper to request permissions that are
+// not a part of extensions.
+class WebViewPermissionHelperDelegate : public content::WebContentsObserver {
+ public:
+ explicit WebViewPermissionHelperDelegate(
+ WebViewPermissionHelper* web_view_permission_helper);
+ ~WebViewPermissionHelperDelegate() override;
+
+ virtual void CanDownload(
+ const GURL& url,
+ const std::string& request_method,
+ const base::Callback<void(bool)>& callback) {}
+
+ virtual void RequestPointerLockPermission(
+ bool user_gesture,
+ bool last_unlocked_by_target,
+ const base::Callback<void(bool)>& callback) {}
+
+ // Requests Geolocation Permission from the embedder.
+ virtual void RequestGeolocationPermission(
+ int bridge_id,
+ const GURL& requesting_frame,
+ const base::Callback<void(bool)>& callback) {}
+
+ virtual void CancelGeolocationPermissionRequest(int bridge_id) {}
+
+ virtual void RequestFileSystemPermission(
+ const GURL& url,
+ bool allowed_by_default,
+ const base::Callback<void(bool)>& callback) {}
+
+ // Called when file system access is requested by the guest content using the
+ // asynchronous HTML5 file system API. The request is plumbed through the
+ // <webview> permission request API. The request will be:
+ // - Allowed if the embedder explicitly allowed it.
+ // - Denied if the embedder explicitly denied.
+ // - Determined by the guest's content settings if the embedder does not
+ // perform an explicit action.
+ // If access was blocked due to the page's content settings,
+ // |blocked_by_policy| should be true, and this function should invoke
+ // OnContentBlocked.
+ virtual void FileSystemAccessedAsync(
+ int render_process_id,
+ int render_frame_id,
+ int request_id,
+ const GURL& url,
+ bool blocked_by_policy) {}
+
+ // Called when file system access is requested by the guest content using the
+ // synchronous HTML5 file system API in a worker thread or shared worker. The
+ // request is plumbed through the <webview> permission request API. The
+ // request will be:
+ // - Allowed if the embedder explicitly allowed it.
+ // - Denied if the embedder explicitly denied.
+ // - Determined by the guest's content settings if the embedder does not
+ // perform an explicit action.
+ // If access was blocked due to the page's content settings,
+ // |blocked_by_policy| should be true, and this function should invoke
+ // OnContentBlocked.
+ virtual void FileSystemAccessedSync(
+ int render_process_id,
+ int render_frame_id,
+ const GURL& url,
+ bool blocked_by_policy,
+ IPC::Message* reply_msg) {}
+
+ WebViewPermissionHelper* web_view_permission_helper() const {
+ return web_view_permission_helper_;
+ }
+
+ private:
+ WebViewPermissionHelper* const web_view_permission_helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewPermissionHelperDelegate);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIWE_PERMISSION_HELPER_DELEGATE_H_
diff --git a/chromium/extensions/browser/guest_view/web_view/web_view_permission_types.h b/chromium/extensions/browser/guest_view/web_view/web_view_permission_types.h
new file mode 100644
index 00000000000..efa420ae7ed
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_view_permission_types.h
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_PERMISSION_TYPES_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_PERMISSION_TYPES_H_
+
+enum WebViewPermissionType {
+ // Unknown type of permission request.
+ WEB_VIEW_PERMISSION_TYPE_UNKNOWN,
+
+ WEB_VIEW_PERMISSION_TYPE_DOWNLOAD,
+
+ WEB_VIEW_PERMISSION_TYPE_FILESYSTEM,
+
+ // html5 fullscreen permission.
+ WEB_VIEW_PERMISSION_TYPE_FULLSCREEN,
+
+ WEB_VIEW_PERMISSION_TYPE_GEOLOCATION,
+
+ // JavaScript Dialogs: prompt, alert, confirm
+ // Note: Even through dialogs do not use the permission API, the dialog API
+ // is sufficiently similiar that it's convenient to consider it a permission
+ // type for code reuse.
+ WEB_VIEW_PERMISSION_TYPE_JAVASCRIPT_DIALOG,
+
+ WEB_VIEW_PERMISSION_TYPE_LOAD_PLUGIN,
+
+ // Media access (audio/video) permission request type.
+ WEB_VIEW_PERMISSION_TYPE_MEDIA,
+
+ // New window requests.
+ // Note: Even though new windows don't use the permission API, the new window
+ // API is sufficiently similar that it's convenient to consider it a
+ // permission type for code reuse.
+ WEB_VIEW_PERMISSION_TYPE_NEW_WINDOW,
+
+ WEB_VIEW_PERMISSION_TYPE_POINTER_LOCK
+};
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_PERMISSION_TYPES_H_
diff --git a/chromium/extensions/browser/guest_view/web_view/web_view_renderer_state.cc b/chromium/extensions/browser/guest_view/web_view/web_view_renderer_state.cc
new file mode 100644
index 00000000000..c95fac91649
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_view_renderer_state.cc
@@ -0,0 +1,155 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+WebViewRendererState::WebViewInfo::WebViewInfo() {
+}
+
+WebViewRendererState::WebViewInfo::WebViewInfo(const WebViewInfo& other) =
+ default;
+
+WebViewRendererState::WebViewInfo::~WebViewInfo() {
+}
+
+// static
+WebViewRendererState* WebViewRendererState::GetInstance() {
+ return base::Singleton<WebViewRendererState>::get();
+}
+
+WebViewRendererState::WebViewRendererState() {
+}
+
+WebViewRendererState::~WebViewRendererState() {
+}
+
+bool WebViewRendererState::IsGuest(int render_process_id) const {
+ base::AutoLock auto_lock(web_view_partition_id_map_lock_);
+ return web_view_partition_id_map_.find(render_process_id) !=
+ web_view_partition_id_map_.end();
+}
+
+void WebViewRendererState::AddGuest(int guest_process_id,
+ int guest_routing_id,
+ const WebViewInfo& web_view_info) {
+ base::AutoLock auto_lock(web_view_info_map_lock_);
+ base::AutoLock auto_lock2(web_view_partition_id_map_lock_);
+
+ RenderId render_id(guest_process_id, guest_routing_id);
+ bool updating =
+ web_view_info_map_.find(render_id) != web_view_info_map_.end();
+ web_view_info_map_[render_id] = web_view_info;
+ if (updating)
+ return;
+
+ auto iter = web_view_partition_id_map_.find(guest_process_id);
+ if (iter != web_view_partition_id_map_.end()) {
+ ++iter->second.web_view_count;
+ return;
+ }
+ WebViewPartitionInfo partition_info(1, web_view_info.partition_id);
+ web_view_partition_id_map_[guest_process_id] = partition_info;
+}
+
+void WebViewRendererState::RemoveGuest(int guest_process_id,
+ int guest_routing_id) {
+ base::AutoLock auto_lock(web_view_info_map_lock_);
+ base::AutoLock auto_lock2(web_view_partition_id_map_lock_);
+
+ RenderId render_id(guest_process_id, guest_routing_id);
+ web_view_info_map_.erase(render_id);
+ auto iter = web_view_partition_id_map_.find(guest_process_id);
+ if (iter != web_view_partition_id_map_.end() &&
+ iter->second.web_view_count > 1) {
+ --iter->second.web_view_count;
+ return;
+ }
+ web_view_partition_id_map_.erase(guest_process_id);
+}
+
+bool WebViewRendererState::GetInfo(int guest_process_id,
+ int guest_routing_id,
+ WebViewInfo* web_view_info) const {
+ base::AutoLock auto_lock(web_view_info_map_lock_);
+
+ RenderId render_id(guest_process_id, guest_routing_id);
+ auto iter = web_view_info_map_.find(render_id);
+ if (iter != web_view_info_map_.end()) {
+ *web_view_info = iter->second;
+ return true;
+ }
+ return false;
+}
+
+bool WebViewRendererState::GetOwnerInfo(int guest_process_id,
+ int* owner_process_id,
+ std::string* owner_host) const {
+ base::AutoLock auto_lock(web_view_info_map_lock_);
+
+ // TODO(fsamuel): Store per-process info in WebViewPartitionInfo instead of in
+ // WebViewInfo.
+ for (const auto& info : web_view_info_map_) {
+ if (info.first.first == guest_process_id) {
+ if (owner_process_id)
+ *owner_process_id = info.second.embedder_process_id;
+ if (owner_host)
+ *owner_host = info.second.owner_host;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool WebViewRendererState::GetPartitionID(int guest_process_id,
+ std::string* partition_id) const {
+ base::AutoLock auto_lock(web_view_partition_id_map_lock_);
+
+ auto iter = web_view_partition_id_map_.find(guest_process_id);
+ if (iter != web_view_partition_id_map_.end()){
+ *partition_id = iter->second.partition_id;
+ return true;
+ }
+ return false;
+}
+
+void WebViewRendererState::AddContentScriptIDs(
+ int embedder_process_id,
+ int view_instance_id,
+ const std::set<int>& script_ids) {
+ base::AutoLock auto_lock(web_view_info_map_lock_);
+
+ for (auto& render_id_info : web_view_info_map_) {
+ WebViewInfo& info = render_id_info.second;
+ if (info.embedder_process_id == embedder_process_id &&
+ info.instance_id == view_instance_id) {
+ for (int id : script_ids)
+ info.content_script_ids.insert(id);
+ return;
+ }
+ }
+}
+
+void WebViewRendererState::RemoveContentScriptIDs(
+ int embedder_process_id,
+ int view_instance_id,
+ const std::set<int>& script_ids) {
+ base::AutoLock auto_lock(web_view_info_map_lock_);
+
+ for (auto& render_id_info : web_view_info_map_) {
+ WebViewInfo& info = render_id_info.second;
+ if (info.embedder_process_id == embedder_process_id &&
+ info.instance_id == view_instance_id) {
+ for (int id : script_ids)
+ info.content_script_ids.erase(id);
+ return;
+ }
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/guest_view/web_view/web_view_renderer_state.h b/chromium/extensions/browser/guest_view/web_view/web_view_renderer_state.h
new file mode 100644
index 00000000000..c1c92427abd
--- /dev/null
+++ b/chromium/extensions/browser/guest_view/web_view/web_view_renderer_state.h
@@ -0,0 +1,112 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// WebViewRendererState manages state data for WebView guest renderer processes.
+//
+// This class's data can be accessed via its methods from both the UI and IO
+// threads, and uses locks to mediate this access. When making changes to this
+// class, ensure that you avoid introducing any reentrant code in the methods,
+// and that you always aquire the locks in the order |web_view_info_map_lock_|
+// -> |web_view_partition_id_map_lock_| (if both are needed in one method).
+
+#ifndef EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_RENDERER_STATE_H_
+#define EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_RENDERER_STATE_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+
+namespace extensions {
+
+class WebViewGuest;
+
+class WebViewRendererState {
+ public:
+ struct WebViewInfo {
+ int embedder_process_id;
+ int instance_id;
+ int rules_registry_id;
+ std::string partition_id;
+ std::string owner_host;
+ std::set<int> content_script_ids;
+
+ WebViewInfo();
+ WebViewInfo(const WebViewInfo& other);
+ ~WebViewInfo();
+ };
+
+ static WebViewRendererState* GetInstance();
+
+ // Looks up the information for the embedder WebView for a RenderViewHost,
+ // given its process and view ID. Returns true and writes the information to
+ // |web_view_info| if found, otherwise returns false.
+ bool GetInfo(int guest_process_id,
+ int guest_routing_id,
+ WebViewInfo* web_view_info) const;
+
+ // Looks up the information for the owner of a WebView guest process, given
+ // its process ID. Returns true and writes the info to |owner_process_id| and
+ // |owner_host| if found, otherwise returns false.
+ bool GetOwnerInfo(int guest_process_id,
+ int* owner_process_id,
+ std::string* owner_host) const;
+
+ // Looks up the partition ID for a WebView guest process, given its
+ // process ID. Returns true and writes the partition ID to |partition_id| if
+ // found, otherwise returns false.
+ bool GetPartitionID(int guest_process_id, std::string* partition_id) const;
+
+ // Returns true if the renderer with process ID |render_process_id| is a
+ // WebView guest process.
+ bool IsGuest(int render_process_id) const;
+
+ void AddContentScriptIDs(int embedder_process_id,
+ int view_instance_id,
+ const std::set<int>& script_ids);
+ void RemoveContentScriptIDs(int embedder_process_id,
+ int view_instance_id,
+ const std::set<int>& script_ids);
+
+ private:
+ friend class WebViewGuest;
+ friend struct base::DefaultSingletonTraits<WebViewRendererState>;
+
+ using RenderId = std::pair<int, int>;
+ using WebViewInfoMap = std::map<RenderId, WebViewInfo>;
+
+ struct WebViewPartitionInfo {
+ int web_view_count;
+ std::string partition_id;
+ WebViewPartitionInfo() {}
+ WebViewPartitionInfo(int count, const std::string& partition)
+ : web_view_count(count), partition_id(partition) {}
+ };
+
+ using WebViewPartitionIDMap = std::map<int, WebViewPartitionInfo>;
+
+ WebViewRendererState();
+ ~WebViewRendererState();
+
+ // Adds/removes a WebView guest render process to/from the set.
+ void AddGuest(int render_process_host_id, int routing_id,
+ const WebViewInfo& web_view_info);
+ void RemoveGuest(int render_process_host_id, int routing_id);
+
+ // Locks are used to mediate access to these maps from both the UI and IO
+ // threads.
+ WebViewInfoMap web_view_info_map_;
+ mutable base::Lock web_view_info_map_lock_;
+ WebViewPartitionIDMap web_view_partition_id_map_;
+ mutable base::Lock web_view_partition_id_map_lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebViewRendererState);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_GUEST_VIEW_WEB_VIEW_WEB_VIEW_RENDERER_STATE_H_
diff --git a/chromium/extensions/browser/image_loader.cc b/chromium/extensions/browser/image_loader.cc
new file mode 100644
index 00000000000..f090ffba1ef
--- /dev/null
+++ b/chromium/extensions/browser/image_loader.cc
@@ -0,0 +1,333 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/image_loader.h"
+
+#include <stddef.h>
+
+#include <map>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/component_extension_resource_manager.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/image_loader_factory.h"
+#include "extensions/common/extension.h"
+#include "skia/ext/image_operations.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "ui/gfx/image/image_family.h"
+#include "ui/gfx/image/image_skia.h"
+
+using content::BrowserThread;
+using extensions::Extension;
+using extensions::ExtensionsBrowserClient;
+using extensions::ImageLoader;
+using extensions::Manifest;
+
+namespace {
+
+bool ShouldResizeImageRepresentation(
+ ImageLoader::ImageRepresentation::ResizeCondition resize_method,
+ const gfx::Size& decoded_size,
+ const gfx::Size& desired_size) {
+ switch (resize_method) {
+ case ImageLoader::ImageRepresentation::ALWAYS_RESIZE:
+ return decoded_size != desired_size;
+ case ImageLoader::ImageRepresentation::RESIZE_WHEN_LARGER:
+ return decoded_size.width() > desired_size.width() ||
+ decoded_size.height() > desired_size.height();
+ case ImageLoader::ImageRepresentation::NEVER_RESIZE:
+ return false;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+SkBitmap ResizeIfNeeded(const SkBitmap& bitmap,
+ const ImageLoader::ImageRepresentation& image_info) {
+ gfx::Size original_size(bitmap.width(), bitmap.height());
+ if (ShouldResizeImageRepresentation(image_info.resize_condition,
+ original_size,
+ image_info.desired_size)) {
+ return skia::ImageOperations::Resize(
+ bitmap, skia::ImageOperations::RESIZE_LANCZOS3,
+ image_info.desired_size.width(), image_info.desired_size.height());
+ }
+
+ return bitmap;
+}
+
+void LoadResourceOnUIThread(int resource_id, SkBitmap* bitmap) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ gfx::ImageSkia image(
+ *ResourceBundle::GetSharedInstance().GetImageSkiaNamed(resource_id));
+ image.MakeThreadSafe();
+ *bitmap = *image.bitmap();
+}
+
+void LoadImageOnBlockingPool(const ImageLoader::ImageRepresentation& image_info,
+ SkBitmap* bitmap) {
+ DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
+
+ // Read the file from disk.
+ std::string file_contents;
+ base::FilePath path = image_info.resource.GetFilePath();
+ if (path.empty() || !base::ReadFileToString(path, &file_contents)) {
+ return;
+ }
+
+ const unsigned char* data =
+ reinterpret_cast<const unsigned char*>(file_contents.data());
+ // Note: This class only decodes bitmaps from extension resources. Chrome
+ // doesn't (for security reasons) directly load extension resources provided
+ // by the extension author, but instead decodes them in a separate
+ // locked-down utility process. Only if the decoding succeeds is the image
+ // saved from memory to disk and subsequently used in the Chrome UI.
+ // Chrome is therefore decoding bitmaps here that were generated by Chrome.
+ gfx::PNGCodec::Decode(data, file_contents.length(), bitmap);
+}
+
+std::vector<SkBitmap> LoadResourceBitmaps(
+ const Extension* extension,
+ const std::vector<ImageLoader::ImageRepresentation>& info_list) {
+ // Loading resources has to happen on the UI thread. So do this first, and
+ // pass the rest of the work off as a blocking pool task.
+ std::vector<SkBitmap> bitmaps;
+ bitmaps.resize(info_list.size());
+
+ int i = 0;
+ for (std::vector<ImageLoader::ImageRepresentation>::const_iterator
+ it = info_list.begin();
+ it != info_list.end();
+ ++it, ++i) {
+ DCHECK(it->resource.relative_path().empty() ||
+ extension->path() == it->resource.extension_root());
+
+ int resource_id;
+ if (extension->location() == Manifest::COMPONENT) {
+ const extensions::ComponentExtensionResourceManager* manager =
+ extensions::ExtensionsBrowserClient::Get()
+ ->GetComponentExtensionResourceManager();
+ if (manager && manager->IsComponentExtensionResource(
+ extension->path(), it->resource.relative_path(), &resource_id)) {
+ LoadResourceOnUIThread(resource_id, &bitmaps[i]);
+ }
+ }
+ }
+ return bitmaps;
+}
+
+} // namespace
+
+namespace extensions {
+
+////////////////////////////////////////////////////////////////////////////////
+// ImageLoader::ImageRepresentation
+
+ImageLoader::ImageRepresentation::ImageRepresentation(
+ const ExtensionResource& resource,
+ ResizeCondition resize_condition,
+ const gfx::Size& desired_size,
+ ui::ScaleFactor scale_factor)
+ : resource(resource),
+ resize_condition(resize_condition),
+ desired_size(desired_size),
+ scale_factor(scale_factor) {
+}
+
+ImageLoader::ImageRepresentation::~ImageRepresentation() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ImageLoader::LoadResult
+
+struct ImageLoader::LoadResult {
+ LoadResult(const SkBitmap& bitmap,
+ const gfx::Size& original_size,
+ const ImageRepresentation& image_representation);
+ ~LoadResult();
+
+ SkBitmap bitmap;
+ gfx::Size original_size;
+ ImageRepresentation image_representation;
+};
+
+ImageLoader::LoadResult::LoadResult(
+ const SkBitmap& bitmap,
+ const gfx::Size& original_size,
+ const ImageLoader::ImageRepresentation& image_representation)
+ : bitmap(bitmap),
+ original_size(original_size),
+ image_representation(image_representation) {
+}
+
+ImageLoader::LoadResult::~LoadResult() {
+}
+
+namespace {
+
+// Need to be after ImageRepresentation and LoadResult are defined.
+std::vector<ImageLoader::LoadResult> LoadImagesOnBlockingPool(
+ const std::vector<ImageLoader::ImageRepresentation>& info_list,
+ const std::vector<SkBitmap>& bitmaps) {
+ DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
+ std::vector<ImageLoader::LoadResult> load_result;
+
+ for (size_t i = 0; i < info_list.size(); ++i) {
+ const ImageLoader::ImageRepresentation& image = info_list[i];
+
+ // If we don't have a path there isn't anything we can do, just skip it.
+ if (image.resource.relative_path().empty())
+ continue;
+
+ SkBitmap bitmap;
+ if (bitmaps[i].isNull())
+ LoadImageOnBlockingPool(image, &bitmap);
+ else
+ bitmap = bitmaps[i];
+
+ // If the image failed to load, skip it.
+ if (bitmap.isNull() || bitmap.empty())
+ continue;
+
+ gfx::Size original_size(bitmap.width(), bitmap.height());
+ bitmap = ResizeIfNeeded(bitmap, image);
+
+ load_result.push_back(
+ ImageLoader::LoadResult(bitmap, original_size, image));
+ }
+
+ return load_result;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// ImageLoader
+
+ImageLoader::ImageLoader()
+ : weak_ptr_factory_(this) {
+}
+
+ImageLoader::~ImageLoader() {
+}
+
+// static
+ImageLoader* ImageLoader::Get(content::BrowserContext* context) {
+ return ImageLoaderFactory::GetForBrowserContext(context);
+}
+
+void ImageLoader::LoadImageAsync(const Extension* extension,
+ const ExtensionResource& resource,
+ const gfx::Size& max_size,
+ const ImageLoaderImageCallback& callback) {
+ std::vector<ImageRepresentation> info_list;
+ info_list.push_back(ImageRepresentation(
+ resource,
+ ImageRepresentation::RESIZE_WHEN_LARGER,
+ max_size,
+ ui::SCALE_FACTOR_100P));
+ LoadImagesAsync(extension, info_list, callback);
+}
+
+void ImageLoader::LoadImagesAsync(
+ const Extension* extension,
+ const std::vector<ImageRepresentation>& info_list,
+ const ImageLoaderImageCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(!BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
+ base::PostTaskAndReplyWithResult(
+ BrowserThread::GetBlockingPool(),
+ FROM_HERE,
+ base::Bind(LoadImagesOnBlockingPool,
+ info_list,
+ LoadResourceBitmaps(extension, info_list)),
+ base::Bind(
+ &ImageLoader::ReplyBack, weak_ptr_factory_.GetWeakPtr(), callback));
+}
+
+void ImageLoader::LoadImageFamilyAsync(
+ const extensions::Extension* extension,
+ const std::vector<ImageRepresentation>& info_list,
+ const ImageLoaderImageFamilyCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(!BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
+ base::PostTaskAndReplyWithResult(
+ BrowserThread::GetBlockingPool(),
+ FROM_HERE,
+ base::Bind(LoadImagesOnBlockingPool,
+ info_list,
+ LoadResourceBitmaps(extension, info_list)),
+ base::Bind(&ImageLoader::ReplyBackWithImageFamily,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback));
+}
+
+void ImageLoader::ReplyBack(const ImageLoaderImageCallback& callback,
+ const std::vector<LoadResult>& load_result) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ gfx::ImageSkia image_skia;
+
+ for (std::vector<LoadResult>::const_iterator it = load_result.begin();
+ it != load_result.end(); ++it) {
+ const SkBitmap& bitmap = it->bitmap;
+ const ImageRepresentation& image_rep = it->image_representation;
+
+ image_skia.AddRepresentation(gfx::ImageSkiaRep(
+ bitmap,
+ ui::GetScaleForScaleFactor(image_rep.scale_factor)));
+ }
+
+ gfx::Image image;
+ if (!image_skia.isNull()) {
+ image_skia.MakeThreadSafe();
+ image = gfx::Image(image_skia);
+ }
+
+ callback.Run(image);
+}
+
+void ImageLoader::ReplyBackWithImageFamily(
+ const ImageLoaderImageFamilyCallback& callback,
+ const std::vector<LoadResult>& load_result) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ std::map<std::pair<int, int>, gfx::ImageSkia> image_skia_map;
+ gfx::ImageFamily image_family;
+
+ for (std::vector<LoadResult>::const_iterator it = load_result.begin();
+ it != load_result.end();
+ ++it) {
+ const SkBitmap& bitmap = it->bitmap;
+ const ImageRepresentation& image_rep = it->image_representation;
+ const std::pair<int, int> key = std::make_pair(
+ image_rep.desired_size.width(), image_rep.desired_size.height());
+ // Create a new ImageSkia for this width/height, or add a representation to
+ // an existing ImageSkia with the same width/height.
+ image_skia_map[key].AddRepresentation(
+ gfx::ImageSkiaRep(bitmap,
+ ui::GetScaleForScaleFactor(image_rep.scale_factor)));
+ }
+
+ for (std::map<std::pair<int, int>, gfx::ImageSkia>::iterator it =
+ image_skia_map.begin();
+ it != image_skia_map.end();
+ ++it) {
+ it->second.MakeThreadSafe();
+ image_family.Add(it->second);
+ }
+
+ callback.Run(image_family);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/image_loader.h b/chromium/extensions/browser/image_loader.h
new file mode 100644
index 00000000000..ca2c77805f1
--- /dev/null
+++ b/chromium/extensions/browser/image_loader.h
@@ -0,0 +1,121 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_IMAGE_LOADER_H_
+#define EXTENSIONS_BROWSER_IMAGE_LOADER_H_
+
+#include <set>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/common/extension_resource.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/layout.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace gfx {
+class Image;
+class ImageFamily;
+}
+
+namespace extensions {
+
+class Extension;
+
+typedef base::Callback<void(const gfx::Image&)> ImageLoaderImageCallback;
+typedef base::Callback<void(const gfx::ImageFamily&)>
+ ImageLoaderImageFamilyCallback;
+
+// This class is responsible for asynchronously loading extension images and
+// calling a callback when an image is loaded.
+// The views need to load their icons asynchronously might be deleted before
+// the images have loaded. If you pass your callback using a weak_ptr, this
+// will make sure the callback won't be called after the view is deleted.
+class ImageLoader : public KeyedService {
+ public:
+ // Information about a singe image representation to load from an extension
+ // resource.
+ struct ImageRepresentation {
+ // Enum values to indicate whether to resize loaded bitmap when it is larger
+ // than |desired_size| or always resize it.
+ enum ResizeCondition { RESIZE_WHEN_LARGER, ALWAYS_RESIZE, NEVER_RESIZE };
+
+ ImageRepresentation(const ExtensionResource& resource,
+ ResizeCondition resize_condition,
+ const gfx::Size& desired_size,
+ ui::ScaleFactor scale_factor);
+ ~ImageRepresentation();
+
+ // Extension resource to load.
+ ExtensionResource resource;
+
+ ResizeCondition resize_condition;
+
+ // When |resize_method| is ALWAYS_RESIZE or when the loaded image is larger
+ // than |desired_size| it will be resized to these dimensions.
+ gfx::Size desired_size;
+
+ // |scale_factor| is used to construct the loaded gfx::ImageSkia.
+ ui::ScaleFactor scale_factor;
+ };
+
+ struct LoadResult;
+
+ // Returns the instance for the given |context| or NULL if none. This is
+ // a convenience wrapper around ImageLoaderFactory::GetForBrowserContext.
+ static ImageLoader* Get(content::BrowserContext* context);
+
+ ImageLoader();
+ ~ImageLoader() override;
+
+ // Specify image resource to load. If the loaded image is larger than
+ // |max_size| it will be resized to those dimensions. IMPORTANT NOTE: this
+ // function may call back your callback synchronously (ie before it returns)
+ // if the image was found in the cache.
+ // Note this method loads a raw bitmap from the resource. All sizes given are
+ // assumed to be in pixels.
+ void LoadImageAsync(const extensions::Extension* extension,
+ const ExtensionResource& resource,
+ const gfx::Size& max_size,
+ const ImageLoaderImageCallback& callback);
+
+ // Same as LoadImageAsync() above except it loads multiple images from the
+ // same extension. This is used to load multiple resolutions of the same image
+ // type.
+ void LoadImagesAsync(const extensions::Extension* extension,
+ const std::vector<ImageRepresentation>& info_list,
+ const ImageLoaderImageCallback& callback);
+
+ // Same as LoadImagesAsync() above except it loads into an image family. This
+ // is used to load multiple images of different logical sizes as opposed to
+ // LoadImagesAsync() which loads different scale factors of the same logical
+ // image size.
+ //
+ // If multiple images of the same logical size are loaded, they will be
+ // combined into a single ImageSkia in the ImageFamily.
+ void LoadImageFamilyAsync(const extensions::Extension* extension,
+ const std::vector<ImageRepresentation>& info_list,
+ const ImageLoaderImageFamilyCallback& callback);
+
+ private:
+ void ReplyBack(const ImageLoaderImageCallback& callback,
+ const std::vector<LoadResult>& load_result);
+
+ void ReplyBackWithImageFamily(const ImageLoaderImageFamilyCallback& callback,
+ const std::vector<LoadResult>& load_result);
+
+ base::WeakPtrFactory<ImageLoader> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageLoader);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_IMAGE_LOADER_H_
diff --git a/chromium/extensions/browser/image_loader_factory.cc b/chromium/extensions/browser/image_loader_factory.cc
new file mode 100644
index 00000000000..877cbb6aca1
--- /dev/null
+++ b/chromium/extensions/browser/image_loader_factory.cc
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/image_loader_factory.h"
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/image_loader.h"
+
+namespace extensions {
+
+// static
+ImageLoader* ImageLoaderFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<ImageLoader*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+ImageLoaderFactory* ImageLoaderFactory::GetInstance() {
+ return base::Singleton<ImageLoaderFactory>::get();
+}
+
+ImageLoaderFactory::ImageLoaderFactory()
+ : BrowserContextKeyedServiceFactory(
+ "ImageLoader",
+ BrowserContextDependencyManager::GetInstance()) {
+}
+
+ImageLoaderFactory::~ImageLoaderFactory() {
+}
+
+KeyedService* ImageLoaderFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ return new ImageLoader;
+}
+
+bool ImageLoaderFactory::ServiceIsCreatedWithBrowserContext() const {
+ return false;
+}
+
+content::BrowserContext* ImageLoaderFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/image_loader_factory.h b/chromium/extensions/browser/image_loader_factory.h
new file mode 100644
index 00000000000..f973fc2865e
--- /dev/null
+++ b/chromium/extensions/browser/image_loader_factory.h
@@ -0,0 +1,45 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_IMAGE_LOADER_FACTORY_H_
+#define EXTENSIONS_BROWSER_IMAGE_LOADER_FACTORY_H_
+
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class ImageLoader;
+
+// Singleton that owns all ImageLoaders and associates them with
+// BrowserContexts. Listens for the BrowserContext's destruction notification
+// and cleans up the associated ImageLoader. Uses the original BrowserContext
+// for incognito contexts.
+class ImageLoaderFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static ImageLoader* GetForBrowserContext(content::BrowserContext* context);
+
+ static ImageLoaderFactory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<ImageLoaderFactory>;
+
+ ImageLoaderFactory();
+ ~ImageLoaderFactory() override;
+
+ // BrowserContextKeyedServiceFactory:
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+ bool ServiceIsCreatedWithBrowserContext() const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_IMAGE_LOADER_FACTORY_H_
diff --git a/chromium/extensions/browser/image_loader_unittest.cc b/chromium/extensions/browser/image_loader_unittest.cc
new file mode 100644
index 00000000000..06c48d02d2d
--- /dev/null
+++ b/chromium/extensions/browser/image_loader_unittest.cc
@@ -0,0 +1,313 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/image_loader.h"
+
+#include <stddef.h>
+
+#include "base/files/file_path.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/extensions_test.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_icon_set.h"
+#include "extensions/common/extension_paths.h"
+#include "extensions/common/extension_resource.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_handlers/icons_handler.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_family.h"
+#include "ui/gfx/image/image_skia.h"
+
+using content::BrowserThread;
+using content::NotificationService;
+
+namespace extensions {
+
+class ImageLoaderTest : public ExtensionsTest {
+ public:
+ ImageLoaderTest()
+ : image_loaded_count_(0),
+ quit_in_image_loaded_(false),
+ ui_thread_(BrowserThread::UI, &ui_loop_),
+ file_thread_(BrowserThread::FILE),
+ io_thread_(BrowserThread::IO),
+ notification_service_(NotificationService::Create()) {}
+
+ void OnImageLoaded(const gfx::Image& image) {
+ image_loaded_count_++;
+ if (quit_in_image_loaded_)
+ base::MessageLoop::current()->QuitWhenIdle();
+ image_ = image;
+ }
+
+ void OnImageFamilyLoaded(const gfx::ImageFamily& image_family) {
+ image_loaded_count_++;
+ if (quit_in_image_loaded_)
+ base::MessageLoop::current()->QuitWhenIdle();
+ image_family_ = image_family;
+ }
+
+ void WaitForImageLoad() {
+ quit_in_image_loaded_ = true;
+ base::MessageLoop::current()->Run();
+ quit_in_image_loaded_ = false;
+ }
+
+ int image_loaded_count() {
+ int result = image_loaded_count_;
+ image_loaded_count_ = 0;
+ return result;
+ }
+
+ scoped_refptr<Extension> CreateExtension(const char* dir_name,
+ Manifest::Location location) {
+ // Create and load an extension.
+ base::FilePath extension_dir;
+ if (!PathService::Get(DIR_TEST_DATA, &extension_dir)) {
+ EXPECT_FALSE(true);
+ return NULL;
+ }
+ extension_dir = extension_dir.AppendASCII(dir_name);
+ int error_code = 0;
+ std::string error;
+ JSONFileValueDeserializer deserializer(
+ extension_dir.AppendASCII("manifest.json"));
+ scoped_ptr<base::DictionaryValue> valid_value = base::DictionaryValue::From(
+ deserializer.Deserialize(&error_code, &error));
+ EXPECT_EQ(0, error_code) << error;
+ if (error_code != 0)
+ return NULL;
+
+ EXPECT_TRUE(valid_value.get());
+ if (!valid_value)
+ return NULL;
+
+ return Extension::Create(
+ extension_dir, location, *valid_value, Extension::NO_FLAGS, &error);
+ }
+
+ gfx::Image image_;
+ gfx::ImageFamily image_family_;
+
+ private:
+ void SetUp() override {
+ testing::Test::SetUp();
+ file_thread_.Start();
+ io_thread_.Start();
+ }
+
+ int image_loaded_count_;
+ bool quit_in_image_loaded_;
+ base::MessageLoop ui_loop_;
+ content::TestBrowserThread ui_thread_;
+ content::TestBrowserThread file_thread_;
+ content::TestBrowserThread io_thread_;
+ scoped_ptr<NotificationService> notification_service_;
+};
+
+// Tests loading an image works correctly.
+TEST_F(ImageLoaderTest, LoadImage) {
+ scoped_refptr<Extension> extension(
+ CreateExtension("image_loader", Manifest::INVALID_LOCATION));
+ ASSERT_TRUE(extension.get() != NULL);
+
+ ExtensionResource image_resource =
+ IconsInfo::GetIconResource(extension.get(),
+ extension_misc::EXTENSION_ICON_SMALLISH,
+ ExtensionIconSet::MATCH_EXACTLY);
+ gfx::Size max_size(extension_misc::EXTENSION_ICON_SMALLISH,
+ extension_misc::EXTENSION_ICON_SMALLISH);
+ ImageLoader loader;
+ loader.LoadImageAsync(extension.get(),
+ image_resource,
+ max_size,
+ base::Bind(&ImageLoaderTest::OnImageLoaded,
+ base::Unretained(this)));
+
+ // The image isn't cached, so we should not have received notification.
+ EXPECT_EQ(0, image_loaded_count());
+
+ WaitForImageLoad();
+
+ // We should have gotten the image.
+ EXPECT_FALSE(image_.IsEmpty());
+ EXPECT_EQ(1, image_loaded_count());
+
+ // Check that the image was loaded.
+ EXPECT_EQ(extension_misc::EXTENSION_ICON_SMALLISH,
+ image_.ToSkBitmap()->width());
+}
+
+// Tests deleting an extension while waiting for the image to load doesn't cause
+// problems.
+TEST_F(ImageLoaderTest, DeleteExtensionWhileWaitingForCache) {
+ scoped_refptr<Extension> extension(
+ CreateExtension("image_loader", Manifest::INVALID_LOCATION));
+ ASSERT_TRUE(extension.get() != NULL);
+
+ ExtensionResource image_resource =
+ IconsInfo::GetIconResource(extension.get(),
+ extension_misc::EXTENSION_ICON_SMALLISH,
+ ExtensionIconSet::MATCH_EXACTLY);
+ gfx::Size max_size(extension_misc::EXTENSION_ICON_SMALLISH,
+ extension_misc::EXTENSION_ICON_SMALLISH);
+ ImageLoader loader;
+ std::set<int> sizes;
+ sizes.insert(extension_misc::EXTENSION_ICON_SMALLISH);
+ loader.LoadImageAsync(extension.get(),
+ image_resource,
+ max_size,
+ base::Bind(&ImageLoaderTest::OnImageLoaded,
+ base::Unretained(this)));
+
+ // The image isn't cached, so we should not have received notification.
+ EXPECT_EQ(0, image_loaded_count());
+
+ // Send out notification the extension was uninstalled.
+ ExtensionRegistry::Get(browser_context())->TriggerOnUnloaded(
+ extension.get(), UnloadedExtensionInfo::REASON_UNINSTALL);
+
+ // Chuck the extension, that way if anyone tries to access it we should crash
+ // or get valgrind errors.
+ extension = NULL;
+
+ WaitForImageLoad();
+
+ // Even though we deleted the extension, we should still get the image.
+ // We should still have gotten the image.
+ EXPECT_EQ(1, image_loaded_count());
+
+ // Check that the image was loaded.
+ EXPECT_EQ(extension_misc::EXTENSION_ICON_SMALLISH,
+ image_.ToSkBitmap()->width());
+}
+
+// Tests loading multiple dimensions of the same image.
+TEST_F(ImageLoaderTest, MultipleImages) {
+ scoped_refptr<Extension> extension(
+ CreateExtension("image_loader", Manifest::INVALID_LOCATION));
+ ASSERT_TRUE(extension.get() != NULL);
+
+ std::vector<ImageLoader::ImageRepresentation> info_list;
+ int sizes[] = {extension_misc::EXTENSION_ICON_BITTY,
+ extension_misc::EXTENSION_ICON_SMALLISH, };
+ for (size_t i = 0; i < arraysize(sizes); ++i) {
+ ExtensionResource resource = IconsInfo::GetIconResource(
+ extension.get(), sizes[i], ExtensionIconSet::MATCH_EXACTLY);
+ info_list.push_back(ImageLoader::ImageRepresentation(
+ resource,
+ ImageLoader::ImageRepresentation::RESIZE_WHEN_LARGER,
+ gfx::Size(sizes[i], sizes[i]),
+ ui::SCALE_FACTOR_NONE));
+ }
+
+ ImageLoader loader;
+ loader.LoadImagesAsync(extension.get(), info_list,
+ base::Bind(&ImageLoaderTest::OnImageLoaded,
+ base::Unretained(this)));
+
+ // The image isn't cached, so we should not have received notification.
+ EXPECT_EQ(0, image_loaded_count());
+
+ WaitForImageLoad();
+
+ // We should have gotten the image.
+ EXPECT_EQ(1, image_loaded_count());
+
+ // Check that all images were loaded.
+ std::vector<gfx::ImageSkiaRep> image_reps =
+ image_.ToImageSkia()->image_reps();
+ ASSERT_EQ(2u, image_reps.size());
+
+ const gfx::ImageSkiaRep* img_rep1 = &image_reps[0];
+ const gfx::ImageSkiaRep* img_rep2 = &image_reps[1];
+ EXPECT_EQ(extension_misc::EXTENSION_ICON_BITTY,
+ img_rep1->pixel_width());
+ EXPECT_EQ(extension_misc::EXTENSION_ICON_SMALLISH,
+ img_rep2->pixel_width());
+}
+
+// Tests loading multiple dimensions of the same image into an image family.
+TEST_F(ImageLoaderTest, LoadImageFamily) {
+ scoped_refptr<Extension> extension(
+ CreateExtension("image_loader", Manifest::INVALID_LOCATION));
+ ASSERT_TRUE(extension.get() != NULL);
+
+ std::vector<ImageLoader::ImageRepresentation> info_list;
+ int sizes[] = {extension_misc::EXTENSION_ICON_BITTY,
+ extension_misc::EXTENSION_ICON_SMALLISH, };
+ for (size_t i = 0; i < arraysize(sizes); ++i) {
+ ExtensionResource resource = IconsInfo::GetIconResource(
+ extension.get(), sizes[i], ExtensionIconSet::MATCH_EXACTLY);
+ info_list.push_back(ImageLoader::ImageRepresentation(
+ resource,
+ ImageLoader::ImageRepresentation::NEVER_RESIZE,
+ gfx::Size(sizes[i], sizes[i]),
+ ui::SCALE_FACTOR_100P));
+ }
+
+ // Add a second icon of 200P which should get grouped with the smaller icon's
+ // ImageSkia.
+ ExtensionResource resource =
+ IconsInfo::GetIconResource(extension.get(),
+ extension_misc::EXTENSION_ICON_SMALLISH,
+ ExtensionIconSet::MATCH_EXACTLY);
+ info_list.push_back(ImageLoader::ImageRepresentation(
+ resource,
+ ImageLoader::ImageRepresentation::NEVER_RESIZE,
+ gfx::Size(extension_misc::EXTENSION_ICON_BITTY,
+ extension_misc::EXTENSION_ICON_BITTY),
+ ui::SCALE_FACTOR_200P));
+
+ ImageLoader loader;
+ loader.LoadImageFamilyAsync(extension.get(),
+ info_list,
+ base::Bind(&ImageLoaderTest::OnImageFamilyLoaded,
+ base::Unretained(this)));
+
+ // The image isn't cached, so we should not have received notification.
+ EXPECT_EQ(0, image_loaded_count());
+
+ WaitForImageLoad();
+
+ // We should have gotten the image.
+ EXPECT_EQ(1, image_loaded_count());
+
+ // Check that all images were loaded.
+ for (size_t i = 0; i < arraysize(sizes); ++i) {
+ const gfx::Image* image = image_family_.GetBest(sizes[i], sizes[i]);
+ EXPECT_EQ(sizes[i], image->Width());
+ }
+
+ // Check the smaller image has 2 representations of different scale factors.
+ std::vector<gfx::ImageSkiaRep> image_reps =
+ image_family_.GetBest(extension_misc::EXTENSION_ICON_BITTY,
+ extension_misc::EXTENSION_ICON_BITTY)
+ ->ToImageSkia()
+ ->image_reps();
+
+ ASSERT_EQ(2u, image_reps.size());
+
+ const gfx::ImageSkiaRep* img_rep1 = &image_reps[0];
+ const gfx::ImageSkiaRep* img_rep2 = &image_reps[1];
+ EXPECT_EQ(extension_misc::EXTENSION_ICON_BITTY, img_rep1->pixel_width());
+ EXPECT_EQ(1.0f, img_rep1->scale());
+ EXPECT_EQ(extension_misc::EXTENSION_ICON_SMALLISH, img_rep2->pixel_width());
+ EXPECT_EQ(2.0f, img_rep2->scale());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/info_map.cc b/chromium/extensions/browser/info_map.cc
new file mode 100644
index 00000000000..c97832ca4b2
--- /dev/null
+++ b/chromium/extensions/browser/info_map.cc
@@ -0,0 +1,246 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/info_map.h"
+
+#include "base/strings/string_util.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/content_verifier.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_resource.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/manifest_handlers/incognito_info.h"
+#include "extensions/common/manifest_handlers/shared_module_info.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "url/gurl.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+namespace {
+
+void CheckOnValidThread() { DCHECK_CURRENTLY_ON(BrowserThread::IO); }
+
+} // namespace
+
+struct InfoMap::ExtraData {
+ // When the extension was installed.
+ base::Time install_time;
+
+ // True if the user has allowed this extension to run in incognito mode.
+ bool incognito_enabled;
+
+ // True if the user has disabled notifications for this extension manually.
+ bool notifications_disabled;
+
+ ExtraData();
+ ~ExtraData();
+};
+
+InfoMap::ExtraData::ExtraData()
+ : incognito_enabled(false), notifications_disabled(false) {
+}
+
+InfoMap::ExtraData::~ExtraData() {}
+
+InfoMap::InfoMap() {
+}
+
+void InfoMap::AddExtension(const Extension* extension,
+ base::Time install_time,
+ bool incognito_enabled,
+ bool notifications_disabled) {
+ CheckOnValidThread();
+ extensions_.Insert(extension);
+ disabled_extensions_.Remove(extension->id());
+
+ extra_data_[extension->id()].install_time = install_time;
+ extra_data_[extension->id()].incognito_enabled = incognito_enabled;
+ extra_data_[extension->id()].notifications_disabled = notifications_disabled;
+}
+
+void InfoMap::RemoveExtension(const std::string& extension_id,
+ const UnloadedExtensionInfo::Reason reason) {
+ CheckOnValidThread();
+ const Extension* extension = extensions_.GetByID(extension_id);
+ extra_data_.erase(extension_id); // we don't care about disabled extra data
+ bool was_uninstalled = (reason != UnloadedExtensionInfo::REASON_DISABLE &&
+ reason != UnloadedExtensionInfo::REASON_TERMINATE);
+ if (extension) {
+ if (!was_uninstalled)
+ disabled_extensions_.Insert(extension);
+ extensions_.Remove(extension_id);
+ } else if (was_uninstalled) {
+ // If the extension was uninstalled, make sure it's removed from the map of
+ // disabled extensions.
+ disabled_extensions_.Remove(extension_id);
+ } else {
+ // NOTE: This can currently happen if we receive multiple unload
+ // notifications, e.g. setting incognito-enabled state for a
+ // disabled extension (e.g., via sync). See
+ // http://code.google.com/p/chromium/issues/detail?id=50582 .
+ NOTREACHED() << extension_id;
+ }
+}
+
+base::Time InfoMap::GetInstallTime(const std::string& extension_id) const {
+ ExtraDataMap::const_iterator iter = extra_data_.find(extension_id);
+ if (iter != extra_data_.end())
+ return iter->second.install_time;
+ return base::Time();
+}
+
+bool InfoMap::IsIncognitoEnabled(const std::string& extension_id) const {
+ // Keep in sync with duplicate in extensions/browser/process_manager.cc.
+ ExtraDataMap::const_iterator iter = extra_data_.find(extension_id);
+ if (iter != extra_data_.end())
+ return iter->second.incognito_enabled;
+ return false;
+}
+
+bool InfoMap::CanCrossIncognito(const Extension* extension) const {
+ // This is duplicated from ExtensionService :(.
+ return IsIncognitoEnabled(extension->id()) &&
+ !IncognitoInfo::IsSplitMode(extension);
+}
+
+void InfoMap::RegisterExtensionProcess(const std::string& extension_id,
+ int process_id,
+ int site_instance_id) {
+ if (!process_map_.Insert(extension_id, process_id, site_instance_id)) {
+ NOTREACHED() << "Duplicate extension process registration for: "
+ << extension_id << "," << process_id << ".";
+ }
+}
+
+void InfoMap::UnregisterExtensionProcess(const std::string& extension_id,
+ int process_id,
+ int site_instance_id) {
+ if (!process_map_.Remove(extension_id, process_id, site_instance_id)) {
+ NOTREACHED() << "Unknown extension process registration for: "
+ << extension_id << "," << process_id << ".";
+ }
+}
+
+void InfoMap::UnregisterAllExtensionsInProcess(int process_id) {
+ process_map_.RemoveAllFromProcess(process_id);
+}
+
+bool InfoMap::SecurityOriginHasAPIPermission(
+ const GURL& origin,
+ int process_id,
+ APIPermission::ID permission) const {
+ CheckOnValidThread();
+ if (origin.SchemeIs(kExtensionScheme)) {
+ const std::string& id = origin.host();
+ const Extension* extension = extensions_.GetByID(id);
+ return extension &&
+ extension->permissions_data()->HasAPIPermission(permission) &&
+ process_map_.Contains(id, process_id);
+ }
+ for (const auto& extension : extensions_) {
+ if (extension->web_extent().MatchesSecurityOrigin(origin) &&
+ extension->permissions_data()->HasAPIPermission(permission) &&
+ process_map_.Contains(extension->id(), process_id)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// This function is security sensitive. Bugs could cause problems that break
+// restrictions on local file access or NaCl's validation caching. If you modify
+// this function, please get a security review from a NaCl person.
+bool InfoMap::MapUrlToLocalFilePath(const GURL& file_url,
+ bool use_blocking_api,
+ base::FilePath* file_path) {
+ // Check that the URL is recognized by the extension system.
+ const Extension* extension = extensions_.GetExtensionOrAppByURL(file_url);
+ if (!extension)
+ return false;
+
+ // This is a short-cut which avoids calling a blocking file operation
+ // (GetFilePath()), so that this can be called on the IO thread. It only
+ // handles a subset of the urls.
+ if (!use_blocking_api) {
+ if (file_url.SchemeIs(extensions::kExtensionScheme)) {
+ std::string path = file_url.path();
+ base::TrimString(path, "/", &path); // Remove first slash
+ *file_path = extension->path().AppendASCII(path);
+ return true;
+ }
+ return false;
+ }
+
+ std::string path = file_url.path();
+ ExtensionResource resource;
+
+ if (SharedModuleInfo::IsImportedPath(path)) {
+ // Check if this is a valid path that is imported for this extension.
+ std::string new_extension_id;
+ std::string new_relative_path;
+ SharedModuleInfo::ParseImportedPath(
+ path, &new_extension_id, &new_relative_path);
+ const Extension* new_extension = extensions_.GetByID(new_extension_id);
+ if (!new_extension)
+ return false;
+
+ if (!SharedModuleInfo::ImportsExtensionById(extension, new_extension_id))
+ return false;
+
+ resource = new_extension->GetResource(new_relative_path);
+ } else {
+ // Check that the URL references a resource in the extension.
+ resource = extension->GetResource(path);
+ }
+
+ if (resource.empty())
+ return false;
+
+ // GetFilePath is a blocking function call.
+ const base::FilePath resource_file_path = resource.GetFilePath();
+ if (resource_file_path.empty())
+ return false;
+
+ *file_path = resource_file_path;
+ return true;
+}
+
+QuotaService* InfoMap::GetQuotaService() {
+ CheckOnValidThread();
+ if (!quota_service_)
+ quota_service_.reset(new QuotaService());
+ return quota_service_.get();
+}
+
+void InfoMap::SetNotificationsDisabled(
+ const std::string& extension_id,
+ bool notifications_disabled) {
+ ExtraDataMap::iterator iter = extra_data_.find(extension_id);
+ if (iter != extra_data_.end())
+ iter->second.notifications_disabled = notifications_disabled;
+}
+
+bool InfoMap::AreNotificationsDisabled(
+ const std::string& extension_id) const {
+ ExtraDataMap::const_iterator iter = extra_data_.find(extension_id);
+ if (iter != extra_data_.end())
+ return iter->second.notifications_disabled;
+ return false;
+}
+
+void InfoMap::SetContentVerifier(ContentVerifier* verifier) {
+ content_verifier_ = verifier;
+}
+
+InfoMap::~InfoMap() {
+ if (quota_service_) {
+ BrowserThread::DeleteSoon(
+ BrowserThread::IO, FROM_HERE, quota_service_.release());
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/info_map.h b/chromium/extensions/browser/info_map.h
new file mode 100644
index 00000000000..c540cef6549
--- /dev/null
+++ b/chromium/extensions/browser/info_map.h
@@ -0,0 +1,127 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_INFO_MAP_H_
+#define EXTENSIONS_BROWSER_INFO_MAP_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "extensions/browser/process_map.h"
+#include "extensions/browser/quota_service.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/permissions/api_permission.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace extensions {
+class ContentVerifier;
+class Extension;
+
+// Contains extension data that needs to be accessed on the IO thread. It can
+// be created/destroyed on any thread, but all other methods must be called on
+// the IO thread.
+class InfoMap : public base::RefCountedThreadSafe<InfoMap> {
+ public:
+ InfoMap();
+
+ const ExtensionSet& extensions() const { return extensions_; }
+ const ExtensionSet& disabled_extensions() const {
+ return disabled_extensions_;
+ }
+
+ // Information about which extensions are assigned to which render processes.
+ const ProcessMap& process_map() const { return process_map_; }
+
+ // Callback for when new extensions are loaded.
+ void AddExtension(const extensions::Extension* extension,
+ base::Time install_time,
+ bool incognito_enabled,
+ bool notifications_disabled);
+
+ // Callback for when an extension is unloaded.
+ void RemoveExtension(const std::string& extension_id,
+ const extensions::UnloadedExtensionInfo::Reason reason);
+
+ // Returns the time the extension was installed, or base::Time() if not found.
+ base::Time GetInstallTime(const std::string& extension_id) const;
+
+ // Returns true if the user has allowed this extension to run in incognito
+ // mode.
+ bool IsIncognitoEnabled(const std::string& extension_id) const;
+
+ // Returns true if the given extension can see events and data from another
+ // sub-profile (incognito to original profile, or vice versa).
+ bool CanCrossIncognito(const extensions::Extension* extension) const;
+
+ // Adds an entry to process_map_.
+ void RegisterExtensionProcess(const std::string& extension_id,
+ int process_id,
+ int site_instance_id);
+
+ // Removes an entry from process_map_.
+ void UnregisterExtensionProcess(const std::string& extension_id,
+ int process_id,
+ int site_instance_id);
+ void UnregisterAllExtensionsInProcess(int process_id);
+
+ // Returns true if there is exists an extension with the same origin as
+ // |origin| in |process_id| with |permission|.
+ bool SecurityOriginHasAPIPermission(const GURL& origin,
+ int process_id,
+ extensions::APIPermission::ID permission)
+ const;
+
+ // Maps a |file_url| to a |file_path| on the local filesystem, including
+ // resources in extensions. Returns true on success. See NaClBrowserDelegate
+ // for full details.
+ bool MapUrlToLocalFilePath(const GURL& file_url,
+ bool use_blocking_api,
+ base::FilePath* file_path);
+
+ // Returns the IO thread QuotaService. Creates the instance on first call.
+ QuotaService* GetQuotaService();
+
+ // Notifications can be enabled/disabled in real time by the user.
+ void SetNotificationsDisabled(const std::string& extension_id,
+ bool notifications_disabled);
+ bool AreNotificationsDisabled(const std::string& extension_id) const;
+
+ void SetContentVerifier(ContentVerifier* verifier);
+ ContentVerifier* content_verifier() { return content_verifier_.get(); }
+
+ private:
+ friend class base::RefCountedThreadSafe<InfoMap>;
+
+ // Extra dynamic data related to an extension.
+ struct ExtraData;
+ // Map of extension_id to ExtraData.
+ typedef std::map<std::string, ExtraData> ExtraDataMap;
+
+ ~InfoMap();
+
+ ExtensionSet extensions_;
+ ExtensionSet disabled_extensions_;
+
+ // Extra data associated with enabled extensions.
+ ExtraDataMap extra_data_;
+
+ // Used by dispatchers to limit API quota for individual extensions.
+ // The QuotaService is not thread safe. We need to create and destroy it on
+ // the IO thread.
+ scoped_ptr<QuotaService> quota_service_;
+
+ // Assignment of extensions to renderer processes.
+ extensions::ProcessMap process_map_;
+
+ scoped_refptr<ContentVerifier> content_verifier_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_INFO_MAP_H_
diff --git a/chromium/extensions/browser/info_map_unittest.cc b/chromium/extensions/browser/info_map_unittest.cc
new file mode 100644
index 00000000000..5dfe7ded96c
--- /dev/null
+++ b/chromium/extensions/browser/info_map_unittest.cc
@@ -0,0 +1,134 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "content/public/test/test_browser_thread.h"
+#include "extensions/browser/info_map.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_paths.h"
+#include "extensions/common/manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::BrowserThread;
+
+namespace keys = extensions::manifest_keys;
+
+namespace extensions {
+
+class InfoMapTest : public testing::Test {
+ public:
+ InfoMapTest()
+ : ui_thread_(BrowserThread::UI, &message_loop_),
+ io_thread_(BrowserThread::IO, &message_loop_) {}
+
+ private:
+ base::MessageLoop message_loop_;
+ content::TestBrowserThread ui_thread_;
+ content::TestBrowserThread io_thread_;
+};
+
+// Returns a barebones test Extension object with the given name.
+static scoped_refptr<Extension> CreateExtension(const std::string& name) {
+ base::FilePath path;
+ PathService::Get(DIR_TEST_DATA, &path);
+
+ base::DictionaryValue manifest;
+ manifest.SetString(keys::kVersion, "1.0.0.0");
+ manifest.SetString(keys::kName, name);
+
+ std::string error;
+ scoped_refptr<Extension> extension =
+ Extension::Create(path.AppendASCII(name),
+ Manifest::INVALID_LOCATION,
+ manifest,
+ Extension::NO_FLAGS,
+ &error);
+ EXPECT_TRUE(extension.get()) << error;
+
+ return extension;
+}
+
+// Test that the InfoMap handles refcounting properly.
+TEST_F(InfoMapTest, RefCounting) {
+ scoped_refptr<InfoMap> info_map(new InfoMap());
+
+ // New extensions should have a single reference holding onto them.
+ scoped_refptr<Extension> extension1(CreateExtension("extension1"));
+ scoped_refptr<Extension> extension2(CreateExtension("extension2"));
+ scoped_refptr<Extension> extension3(CreateExtension("extension3"));
+ EXPECT_TRUE(extension1->HasOneRef());
+ EXPECT_TRUE(extension2->HasOneRef());
+ EXPECT_TRUE(extension3->HasOneRef());
+
+ // Add a ref to each extension and give it to the info map.
+ info_map->AddExtension(extension1.get(), base::Time(), false, false);
+ info_map->AddExtension(extension2.get(), base::Time(), false, false);
+ info_map->AddExtension(extension3.get(), base::Time(), false, false);
+
+ // Release extension1, and the info map should have the only ref.
+ const Extension* weak_extension1 = extension1.get();
+ extension1 = NULL;
+ EXPECT_TRUE(weak_extension1->HasOneRef());
+
+ // Remove extension2, and the extension2 object should have the only ref.
+ info_map->RemoveExtension(
+ extension2->id(), extensions::UnloadedExtensionInfo::REASON_UNINSTALL);
+ EXPECT_TRUE(extension2->HasOneRef());
+
+ // Delete the info map, and the extension3 object should have the only ref.
+ info_map = NULL;
+ EXPECT_TRUE(extension3->HasOneRef());
+}
+
+// Tests that we can query a few extension properties from the InfoMap.
+TEST_F(InfoMapTest, Properties) {
+ scoped_refptr<InfoMap> info_map(new InfoMap());
+
+ scoped_refptr<Extension> extension1(CreateExtension("extension1"));
+ scoped_refptr<Extension> extension2(CreateExtension("extension2"));
+
+ info_map->AddExtension(extension1.get(), base::Time(), false, false);
+ info_map->AddExtension(extension2.get(), base::Time(), false, false);
+
+ EXPECT_EQ(2u, info_map->extensions().size());
+ EXPECT_EQ(extension1.get(), info_map->extensions().GetByID(extension1->id()));
+ EXPECT_EQ(extension2.get(), info_map->extensions().GetByID(extension2->id()));
+}
+
+// Tests that extension URLs are properly mapped to local file paths.
+TEST_F(InfoMapTest, MapUrlToLocalFilePath) {
+ scoped_refptr<InfoMap> info_map(new InfoMap());
+ scoped_refptr<Extension> app(CreateExtension("platform_app"));
+ info_map->AddExtension(app.get(), base::Time(), false, false);
+
+ // Non-extension URLs don't map to anything.
+ base::FilePath non_extension_path;
+ GURL non_extension_url("http://not-an-extension.com/");
+ EXPECT_FALSE(info_map->MapUrlToLocalFilePath(
+ non_extension_url, false, &non_extension_path));
+ EXPECT_TRUE(non_extension_path.empty());
+
+ // Valid resources return a valid path.
+ base::FilePath valid_path;
+ GURL valid_url = app->GetResourceURL("manifest.json");
+ EXPECT_TRUE(info_map->MapUrlToLocalFilePath(
+ valid_url, true /* use_blocking_api */, &valid_path));
+ EXPECT_FALSE(valid_path.empty());
+
+ // A file must exist to be mapped to a path using the blocking API.
+ base::FilePath does_not_exist_path;
+ GURL does_not_exist_url = app->GetResourceURL("does-not-exist.html");
+ EXPECT_FALSE(info_map->MapUrlToLocalFilePath(
+ does_not_exist_url, true /* use_blocking_api */, &does_not_exist_path));
+ EXPECT_TRUE(does_not_exist_path.empty());
+
+ // A file does not need to exist to be mapped to a path with the non-blocking
+ // API. This avoids hitting the disk to see if it exists.
+ EXPECT_TRUE(info_map->MapUrlToLocalFilePath(
+ does_not_exist_url, false /* use_blocking_api */, &does_not_exist_path));
+ EXPECT_FALSE(does_not_exist_path.empty());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/install/crx_install_error.h b/chromium/extensions/browser/install/crx_install_error.h
new file mode 100644
index 00000000000..af25df66dbc
--- /dev/null
+++ b/chromium/extensions/browser/install/crx_install_error.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_INSTALL_CRX_INSTALL_ERROR_H_
+#define EXTENSIONS_BROWSER_INSTALL_CRX_INSTALL_ERROR_H_
+
+#include "base/strings/string16.h"
+
+namespace extensions {
+
+// Simple error class for CrxInstaller.
+class CrxInstallError {
+ public:
+ // Typed errors that need to be handled specially by clients.
+ // ERROR_OFF_STORE for disallowed off-store installations.
+ // ERROR_DECLINED for situations when a .crx file seems to be OK, but there
+ // are some policy restrictions or unmet dependencies that prevent it from
+ // being installed.
+ // ERROR_HASH_MISMATCH if the expected extension SHA256 hash sum is different
+ // from the actual one.
+ enum Type {
+ ERROR_NONE,
+ ERROR_OFF_STORE,
+ ERROR_DECLINED,
+ ERROR_HASH_MISMATCH,
+ ERROR_OTHER
+ };
+
+ CrxInstallError() : type_(ERROR_NONE) {}
+
+ explicit CrxInstallError(const base::string16& message)
+ : type_(message.empty() ? ERROR_NONE : ERROR_OTHER), message_(message) {}
+
+ CrxInstallError(Type type, const base::string16& message)
+ : type_(type), message_(message) {}
+
+ Type type() const { return type_; }
+ const base::string16& message() const { return message_; }
+
+ private:
+ Type type_;
+ base::string16 message_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_INSTALL_CRX_INSTALL_ERROR_H_
diff --git a/chromium/extensions/browser/install/extension_install_ui.cc b/chromium/extensions/browser/install/extension_install_ui.cc
new file mode 100644
index 00000000000..03441b66ea9
--- /dev/null
+++ b/chromium/extensions/browser/install/extension_install_ui.cc
@@ -0,0 +1,18 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/install/extension_install_ui.h"
+
+namespace extensions {
+
+// static
+bool ExtensionInstallUI::disable_failure_ui_for_tests_ = false;
+
+ExtensionInstallUI::ExtensionInstallUI() {
+}
+
+ExtensionInstallUI::~ExtensionInstallUI() {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/install/extension_install_ui.h b/chromium/extensions/browser/install/extension_install_ui.h
new file mode 100644
index 00000000000..e17529c1620
--- /dev/null
+++ b/chromium/extensions/browser/install/extension_install_ui.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_INSTALL_EXTENSION_INSTALL_UI_H_
+#define EXTENSIONS_BROWSER_INSTALL_EXTENSION_INSTALL_UI_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "ui/gfx/native_widget_types.h"
+
+class SkBitmap;
+
+namespace extensions {
+class CrxInstallError;
+class Extension;
+
+// Interface that should be implemented for each platform to display all the UI
+// around extension installation.
+class ExtensionInstallUI {
+ public:
+ ExtensionInstallUI();
+ virtual ~ExtensionInstallUI();
+
+ // Called when an extension was installed.
+ virtual void OnInstallSuccess(const extensions::Extension* extension,
+ const SkBitmap* icon) = 0;
+
+ // Called when an extension failed to install.
+ virtual void OnInstallFailure(const extensions::CrxInstallError& error) = 0;
+
+ // TODO(asargent) Normally we navigate to the new tab page when an app is
+ // installed, but we're experimenting with instead showing a bubble when
+ // an app is installed which points to the new tab button. This may become
+ // the default behavior in the future.
+ virtual void SetUseAppInstalledBubble(bool use_bubble) = 0;
+
+ // Opens apps UI and animates the app icon for the app with id |app_id|.
+ virtual void OpenAppInstalledUI(const std::string& app_id) = 0;
+
+ // Sets whether to show the default UI after completing the installation.
+ virtual void SetSkipPostInstallUI(bool skip_ui) = 0;
+
+ // Returns the gfx::NativeWindow to use as the parent for install dialogs.
+ // Returns NULL if the install dialog should be a top level window. This
+ // method is deprecated - do not add new callers.
+ // TODO(pkotwicz): Remove this method. crbug.com/422474
+ virtual gfx::NativeWindow GetDefaultInstallDialogParent() = 0;
+
+#if defined(UNIT_TEST)
+ static void set_disable_failure_ui_for_tests() {
+ disable_failure_ui_for_tests_ = true;
+ }
+#endif
+
+ protected:
+ static bool disable_failure_ui_for_tests() {
+ return disable_failure_ui_for_tests_;
+ }
+
+ private:
+ static bool disable_failure_ui_for_tests_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionInstallUI);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_INSTALL_EXTENSION_INSTALL_UI_H_
diff --git a/chromium/extensions/browser/install_flag.h b/chromium/extensions/browser/install_flag.h
new file mode 100644
index 00000000000..90850ee9ac3
--- /dev/null
+++ b/chromium/extensions/browser/install_flag.h
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_INSTALL_FLAG_H_
+#define EXTENSIONS_BROWSER_INSTALL_FLAG_H_
+
+namespace extensions {
+
+// Flags used when installing an extension, through ExtensionService and
+// ExtensionPrefs and beyond.
+enum InstallFlag {
+ kInstallFlagNone = 0,
+
+ // The requirements of the extension weren't met (for example graphics
+ // capabilities).
+ kInstallFlagHasRequirementErrors = 1 << 0,
+
+ // Extension is blacklisted for being malware.
+ kInstallFlagIsBlacklistedForMalware = 1 << 1,
+
+ // This is an ephemeral app.
+ kInstallFlagIsEphemeral_Deprecated = 1 << 2,
+
+ // Install the extension immediately, don't wait until idle.
+ kInstallFlagInstallImmediately = 1 << 3,
+
+ // Do not sync the installed extension.
+ kInstallFlagDoNotSync = 1 << 4,
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_INSTALL_FLAG_H_
diff --git a/chromium/extensions/browser/io_thread_extension_message_filter.cc b/chromium/extensions/browser/io_thread_extension_message_filter.cc
new file mode 100644
index 00000000000..ebc24066455
--- /dev/null
+++ b/chromium/extensions/browser/io_thread_extension_message_filter.cc
@@ -0,0 +1,108 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/io_thread_extension_message_filter.h"
+
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/resource_dispatcher_host.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/app_window_registry.h"
+#include "extensions/browser/extension_function_dispatcher.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/info_map.h"
+#include "extensions/common/extension_messages.h"
+#include "ipc/ipc_message_macros.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+namespace {
+
+void NotifyAppWindowReadyOnUIThread(int render_process_id, int route_id) {
+ content::RenderProcessHost* process =
+ content::RenderProcessHost::FromID(render_process_id);
+ if (!process)
+ return;
+ content::BrowserContext* browser_context = process->GetBrowserContext();
+ if (!browser_context)
+ return;
+ content::RenderViewHost* rvh =
+ content::RenderViewHost::FromID(render_process_id, route_id);
+ if (!rvh)
+ return;
+ content::WebContents* contents =
+ content::WebContents::FromRenderViewHost(rvh);
+ if (!contents)
+ return;
+ AppWindowRegistry* registry = AppWindowRegistry::Get(browser_context);
+ DCHECK(registry);
+ AppWindow* window = registry->GetAppWindowForWebContents(contents);
+ if (window)
+ window->NotifyRenderViewReady();
+}
+
+} // namespace
+
+IOThreadExtensionMessageFilter::IOThreadExtensionMessageFilter(
+ int render_process_id,
+ content::BrowserContext* context)
+ : BrowserMessageFilter(ExtensionMsgStart),
+ render_process_id_(render_process_id),
+ browser_context_id_(context),
+ extension_info_map_(ExtensionSystem::Get(context)->info_map()),
+ weak_ptr_factory_(this) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+IOThreadExtensionMessageFilter::~IOThreadExtensionMessageFilter() {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+}
+
+void IOThreadExtensionMessageFilter::OnDestruct() const {
+ // Destroy the filter on the IO thread since that's where its weak pointers
+ // are being used.
+ BrowserThread::DeleteOnIOThread::Destruct(this);
+}
+
+bool IOThreadExtensionMessageFilter::OnMessageReceived(
+ const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(IOThreadExtensionMessageFilter, message)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_AppWindowReady, OnAppWindowReady);
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_GenerateUniqueID,
+ OnExtensionGenerateUniqueID)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_RequestForIOThread,
+ OnExtensionRequestForIOThread)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void IOThreadExtensionMessageFilter::OnAppWindowReady(int route_id) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&NotifyAppWindowReadyOnUIThread,
+ render_process_id_, route_id));
+}
+
+void IOThreadExtensionMessageFilter::OnExtensionGenerateUniqueID(
+ int* unique_id) {
+ static int next_unique_id = 0;
+ *unique_id = ++next_unique_id;
+}
+
+void IOThreadExtensionMessageFilter::OnExtensionRequestForIOThread(
+ int routing_id,
+ const ExtensionHostMsg_Request_Params& params) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ ExtensionFunctionDispatcher::DispatchOnIOThread(
+ extension_info_map_.get(), browser_context_id_, render_process_id_,
+ weak_ptr_factory_.GetWeakPtr(), routing_id, params);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/io_thread_extension_message_filter.h b/chromium/extensions/browser/io_thread_extension_message_filter.h
new file mode 100644
index 00000000000..f6f41bb4835
--- /dev/null
+++ b/chromium/extensions/browser/io_thread_extension_message_filter.h
@@ -0,0 +1,66 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_IO_THREAD_EXTENSION_MESSAGE_FILTER_H_
+#define EXTENSIONS_BROWSER_IO_THREAD_EXTENSION_MESSAGE_FILTER_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "content/public/browser/browser_message_filter.h"
+
+struct ExtensionHostMsg_Request_Params;
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class InfoMap;
+
+// This class filters out incoming extension-specific IPC messages from the
+// renderer process. It is created on the UI thread, but handles messages on the
+// IO thread and is destroyed there.
+class IOThreadExtensionMessageFilter : public content::BrowserMessageFilter {
+ public:
+ IOThreadExtensionMessageFilter(int render_process_id,
+ content::BrowserContext* context);
+
+ int render_process_id() { return render_process_id_; }
+
+ private:
+ friend class base::DeleteHelper<IOThreadExtensionMessageFilter>;
+ friend class content::BrowserThread;
+
+ ~IOThreadExtensionMessageFilter() override;
+
+ // content::BrowserMessageFilter implementation.
+ void OnDestruct() const override;
+ bool OnMessageReceived(const IPC::Message& message) override;
+
+ // Message handlers on the IO thread.
+ void OnAppWindowReady(int route_id);
+ void OnExtensionGenerateUniqueID(int* unique_id);
+ void OnExtensionRequestForIOThread(
+ int routing_id,
+ const ExtensionHostMsg_Request_Params& params);
+
+ const int render_process_id_;
+
+ // The browser context as a void pointer, for use as an identifier on the IO
+ // thread.
+ void* browser_context_id_;
+
+ scoped_refptr<extensions::InfoMap> extension_info_map_;
+
+ // Weak pointers produced by this factory are bound to the IO thread.
+ base::WeakPtrFactory<IOThreadExtensionMessageFilter> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(IOThreadExtensionMessageFilter);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_IO_THREAD_EXTENSION_MESSAGE_FILTER_H_
diff --git a/chromium/extensions/browser/lazy_background_task_queue.cc b/chromium/extensions/browser/lazy_background_task_queue.cc
new file mode 100644
index 00000000000..fcbde098967
--- /dev/null
+++ b/chromium/extensions/browser/lazy_background_task_queue.cc
@@ -0,0 +1,193 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/lazy_background_task_queue.h"
+
+#include "base/callback.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/site_instance.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/browser/extension_host.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/lazy_background_task_queue_factory.h"
+#include "extensions/browser/notification_types.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/browser/process_map.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handlers/background_info.h"
+#include "extensions/common/view_type.h"
+
+namespace extensions {
+
+LazyBackgroundTaskQueue::LazyBackgroundTaskQueue(
+ content::BrowserContext* browser_context)
+ : browser_context_(browser_context), extension_registry_observer_(this) {
+ registrar_.Add(this,
+ extensions::NOTIFICATION_EXTENSION_HOST_DID_STOP_FIRST_LOAD,
+ content::NotificationService::AllBrowserContextsAndSources());
+ registrar_.Add(this,
+ extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED,
+ content::NotificationService::AllBrowserContextsAndSources());
+
+ extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context));
+}
+
+LazyBackgroundTaskQueue::~LazyBackgroundTaskQueue() {
+}
+
+// static
+LazyBackgroundTaskQueue* LazyBackgroundTaskQueue::Get(
+ content::BrowserContext* browser_context) {
+ return LazyBackgroundTaskQueueFactory::GetForBrowserContext(browser_context);
+}
+
+bool LazyBackgroundTaskQueue::ShouldEnqueueTask(
+ content::BrowserContext* browser_context,
+ const Extension* extension) {
+ // Note: browser_context may not be the same as browser_context_ for incognito
+ // extension tasks.
+ DCHECK(extension);
+ if (BackgroundInfo::HasBackgroundPage(extension)) {
+ ProcessManager* pm = ProcessManager::Get(browser_context);
+ ExtensionHost* background_host =
+ pm->GetBackgroundHostForExtension(extension->id());
+ if (!background_host || !background_host->has_loaded_once())
+ return true;
+ if (pm->IsBackgroundHostClosing(extension->id()))
+ pm->CancelSuspend(extension);
+ }
+
+ return false;
+}
+
+void LazyBackgroundTaskQueue::AddPendingTask(
+ content::BrowserContext* browser_context,
+ const std::string& extension_id,
+ const PendingTask& task) {
+ if (ExtensionsBrowserClient::Get()->IsShuttingDown()) {
+ task.Run(NULL);
+ return;
+ }
+ PendingTasksList* tasks_list = NULL;
+ PendingTasksKey key(browser_context, extension_id);
+ PendingTasksMap::iterator it = pending_tasks_.find(key);
+ if (it == pending_tasks_.end()) {
+ tasks_list = new PendingTasksList();
+ pending_tasks_[key] = linked_ptr<PendingTasksList>(tasks_list);
+
+ const Extension* extension =
+ ExtensionRegistry::Get(browser_context)->enabled_extensions().GetByID(
+ extension_id);
+ if (extension && BackgroundInfo::HasLazyBackgroundPage(extension)) {
+ // If this is the first enqueued task, and we're not waiting for the
+ // background page to unload, ensure the background page is loaded.
+ ProcessManager* pm = ProcessManager::Get(browser_context);
+ pm->IncrementLazyKeepaliveCount(extension);
+ // Creating the background host may fail, e.g. if |profile| is incognito
+ // but the extension isn't enabled in incognito mode.
+ if (!pm->CreateBackgroundHost(
+ extension, BackgroundInfo::GetBackgroundURL(extension))) {
+ task.Run(NULL);
+ return;
+ }
+ }
+ } else {
+ tasks_list = it->second.get();
+ }
+
+ tasks_list->push_back(task);
+}
+
+void LazyBackgroundTaskQueue::ProcessPendingTasks(
+ ExtensionHost* host,
+ content::BrowserContext* browser_context,
+ const Extension* extension) {
+ if (!ExtensionsBrowserClient::Get()->IsSameContext(browser_context,
+ browser_context_))
+ return;
+
+ PendingTasksKey key(browser_context, extension->id());
+ PendingTasksMap::iterator map_it = pending_tasks_.find(key);
+ if (map_it == pending_tasks_.end()) {
+ if (BackgroundInfo::HasLazyBackgroundPage(extension))
+ CHECK(!host); // lazy page should not load without any pending tasks
+ return;
+ }
+
+ // Swap the pending tasks to a temporary, to avoid problems if the task
+ // list is modified during processing.
+ PendingTasksList tasks;
+ tasks.swap(*map_it->second);
+ for (PendingTasksList::const_iterator it = tasks.begin();
+ it != tasks.end(); ++it) {
+ it->Run(host);
+ }
+
+ pending_tasks_.erase(key);
+
+ // Balance the keepalive in AddPendingTask. Note we don't do this on a
+ // failure to load, because the keepalive count is reset in that case.
+ if (host && BackgroundInfo::HasLazyBackgroundPage(extension)) {
+ ProcessManager::Get(browser_context)
+ ->DecrementLazyKeepaliveCount(extension);
+ }
+}
+
+void LazyBackgroundTaskQueue::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ switch (type) {
+ case extensions::NOTIFICATION_EXTENSION_HOST_DID_STOP_FIRST_LOAD: {
+ // If an on-demand background page finished loading, dispatch queued up
+ // events for it.
+ ExtensionHost* host =
+ content::Details<ExtensionHost>(details).ptr();
+ if (host->extension_host_type() == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) {
+ CHECK(host->has_loaded_once());
+ ProcessPendingTasks(host, host->browser_context(), host->extension());
+ }
+ break;
+ }
+ case extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED: {
+ // Notify consumers about the load failure when the background host dies.
+ // This can happen if the extension crashes. This is not strictly
+ // necessary, since we also unload the extension in that case (which
+ // dispatches the tasks below), but is a good extra precaution.
+ content::BrowserContext* browser_context =
+ content::Source<content::BrowserContext>(source).ptr();
+ ExtensionHost* host =
+ content::Details<ExtensionHost>(details).ptr();
+ if (host->extension_host_type() == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) {
+ ProcessPendingTasks(NULL, browser_context, host->extension());
+ }
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+void LazyBackgroundTaskQueue::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ // Notify consumers that the page failed to load.
+ ProcessPendingTasks(NULL, browser_context, extension);
+ // If this extension is also running in an off-the-record context, notify that
+ // task queue as well.
+ ExtensionsBrowserClient* browser_client = ExtensionsBrowserClient::Get();
+ if (browser_client->HasOffTheRecordContext(browser_context)) {
+ ProcessPendingTasks(NULL,
+ browser_client->GetOffTheRecordContext(browser_context),
+ extension);
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/lazy_background_task_queue.h b/chromium/extensions/browser/lazy_background_task_queue.h
new file mode 100644
index 00000000000..6aeecb859cd
--- /dev/null
+++ b/chromium/extensions/browser/lazy_background_task_queue.h
@@ -0,0 +1,109 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_LAZY_BACKGROUND_TASK_QUEUE_H_
+#define EXTENSIONS_BROWSER_LAZY_BACKGROUND_TASK_QUEUE_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/linked_ptr.h"
+#include "base/scoped_observer.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "extensions/browser/extension_registry_observer.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+class Extension;
+class ExtensionHost;
+class ExtensionRegistry;
+
+// This class maintains a queue of tasks that should execute when an
+// extension's lazy background page is loaded. It is also in charge of loading
+// the page when the first task is queued.
+//
+// It is the consumer's responsibility to use this class when appropriate, i.e.
+// only with extensions that have not-yet-loaded lazy background pages.
+class LazyBackgroundTaskQueue : public KeyedService,
+ public content::NotificationObserver,
+ public ExtensionRegistryObserver {
+ public:
+ typedef base::Callback<void(ExtensionHost*)> PendingTask;
+
+ explicit LazyBackgroundTaskQueue(content::BrowserContext* browser_context);
+ ~LazyBackgroundTaskQueue() override;
+
+ // Convenience method to return the LazyBackgroundTaskQueue for a given
+ // |context|.
+ static LazyBackgroundTaskQueue* Get(content::BrowserContext* context);
+
+ // Returns the number of extensions having pending tasks.
+ size_t extensions_with_pending_tasks() { return pending_tasks_.size(); }
+
+ // Returns true if the task should be added to the queue (that is, if the
+ // extension has a lazy background page that isn't ready yet). If the
+ // extension has a lazy background page that is being suspended this method
+ // cancels that suspension.
+ bool ShouldEnqueueTask(content::BrowserContext* context,
+ const Extension* extension);
+
+ // Adds a task to the queue for a given extension. If this is the first
+ // task added for the extension, its lazy background page will be loaded.
+ // The task will be called either when the page is loaded, or when the
+ // page fails to load for some reason (e.g. a crash or browser
+ // shutdown). In the latter case, the ExtensionHost parameter is NULL.
+ void AddPendingTask(
+ content::BrowserContext* context,
+ const std::string& extension_id,
+ const PendingTask& task);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(LazyBackgroundTaskQueueTest, ProcessPendingTasks);
+
+ // A map between a BrowserContext/extension_id pair and the queue of tasks
+ // pending the load of its background page.
+ typedef std::string ExtensionID;
+ typedef std::pair<content::BrowserContext*, ExtensionID> PendingTasksKey;
+ typedef std::vector<PendingTask> PendingTasksList;
+ typedef std::map<PendingTasksKey,
+ linked_ptr<PendingTasksList> > PendingTasksMap;
+
+ // content::NotificationObserver interface.
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override;
+
+ // ExtensionRegistryObserver interface.
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+
+ // Called when a lazy background page has finished loading, or has failed to
+ // load (host is NULL in that case). All enqueued tasks are run in order.
+ void ProcessPendingTasks(
+ ExtensionHost* host,
+ content::BrowserContext* context,
+ const Extension* extension);
+
+ content::BrowserContext* browser_context_;
+ content::NotificationRegistrar registrar_;
+ PendingTasksMap pending_tasks_;
+
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_LAZY_BACKGROUND_TASK_QUEUE_H_
diff --git a/chromium/extensions/browser/lazy_background_task_queue_factory.cc b/chromium/extensions/browser/lazy_background_task_queue_factory.cc
new file mode 100644
index 00000000000..c73223945b5
--- /dev/null
+++ b/chromium/extensions/browser/lazy_background_task_queue_factory.cc
@@ -0,0 +1,50 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/lazy_background_task_queue_factory.h"
+
+//#include "chrome/browser/profiles/profile.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "extensions/browser/extension_registry_factory.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/lazy_background_task_queue.h"
+
+using content::BrowserContext;
+
+namespace extensions {
+
+// static
+LazyBackgroundTaskQueue* LazyBackgroundTaskQueueFactory::GetForBrowserContext(
+ BrowserContext* context) {
+ return static_cast<LazyBackgroundTaskQueue*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+LazyBackgroundTaskQueueFactory* LazyBackgroundTaskQueueFactory::GetInstance() {
+ return base::Singleton<LazyBackgroundTaskQueueFactory>::get();
+}
+
+LazyBackgroundTaskQueueFactory::LazyBackgroundTaskQueueFactory()
+ : BrowserContextKeyedServiceFactory(
+ "LazyBackgroundTaskQueue",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(ExtensionRegistryFactory::GetInstance());
+}
+
+LazyBackgroundTaskQueueFactory::~LazyBackgroundTaskQueueFactory() {
+}
+
+KeyedService* LazyBackgroundTaskQueueFactory::BuildServiceInstanceFor(
+ BrowserContext* context) const {
+ return new LazyBackgroundTaskQueue(context);
+}
+
+BrowserContext* LazyBackgroundTaskQueueFactory::GetBrowserContextToUse(
+ BrowserContext* context) const {
+ // Redirected in incognito.
+ return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/lazy_background_task_queue_factory.h b/chromium/extensions/browser/lazy_background_task_queue_factory.h
new file mode 100644
index 00000000000..9bec2726ab4
--- /dev/null
+++ b/chromium/extensions/browser/lazy_background_task_queue_factory.h
@@ -0,0 +1,40 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_LAZY_BACKGROUND_TASK_QUEUE_FACTORY_H_
+#define EXTENSIONS_BROWSER_LAZY_BACKGROUND_TASK_QUEUE_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace extensions {
+
+class LazyBackgroundTaskQueue;
+
+class LazyBackgroundTaskQueueFactory
+ : public BrowserContextKeyedServiceFactory {
+ public:
+ static LazyBackgroundTaskQueue* GetForBrowserContext(
+ content::BrowserContext* context);
+ static LazyBackgroundTaskQueueFactory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<LazyBackgroundTaskQueueFactory>;
+
+ LazyBackgroundTaskQueueFactory();
+ ~LazyBackgroundTaskQueueFactory() override;
+
+ // BrowserContextKeyedServiceFactory implementation
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(LazyBackgroundTaskQueueFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_LAZY_BACKGROUND_TASK_QUEUE_FACTORY_H_
diff --git a/chromium/extensions/browser/lazy_background_task_queue_unittest.cc b/chromium/extensions/browser/lazy_background_task_queue_unittest.cc
new file mode 100644
index 00000000000..02ec3bbedd3
--- /dev/null
+++ b/chromium/extensions/browser/lazy_background_task_queue_unittest.cc
@@ -0,0 +1,221 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/lazy_background_task_queue.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/pref_registry/testing_pref_service_syncable.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/user_prefs/user_prefs.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/test/test_browser_context.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_registry_factory.h"
+#include "extensions/browser/extensions_test.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/browser/process_manager_factory.h"
+#include "extensions/browser/test_extensions_browser_client.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::BrowserContext;
+
+namespace extensions {
+namespace {
+
+// A ProcessManager that doesn't create background host pages.
+class TestProcessManager : public ProcessManager {
+ public:
+ explicit TestProcessManager(BrowserContext* context)
+ : ProcessManager(context, context, ExtensionRegistry::Get(context)),
+ create_count_(0) {
+ // ProcessManager constructor above assumes non-incognito.
+ DCHECK(!context->IsOffTheRecord());
+ }
+ ~TestProcessManager() override {}
+
+ int create_count() { return create_count_; }
+
+ // ProcessManager overrides:
+ bool CreateBackgroundHost(const Extension* extension,
+ const GURL& url) override {
+ // Don't actually try to create a web contents.
+ create_count_++;
+ return false;
+ }
+
+ private:
+ int create_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestProcessManager);
+};
+
+scoped_ptr<KeyedService> CreateTestProcessManager(BrowserContext* context) {
+ return make_scoped_ptr(new TestProcessManager(context));
+}
+
+} // namespace
+
+// Derives from ExtensionsTest to provide content module and keyed service
+// initialization.
+class LazyBackgroundTaskQueueTest : public ExtensionsTest {
+ public:
+ LazyBackgroundTaskQueueTest()
+ : notification_service_(content::NotificationService::Create()),
+ task_run_count_(0) {
+ }
+ ~LazyBackgroundTaskQueueTest() override {}
+
+ int task_run_count() { return task_run_count_; }
+
+ // A simple callback for AddPendingTask.
+ void RunPendingTask(ExtensionHost* host) {
+ task_run_count_++;
+ }
+
+ // Creates and registers an extension without a background page.
+ scoped_refptr<Extension> CreateSimpleExtension() {
+ scoped_refptr<Extension> extension =
+ ExtensionBuilder()
+ .SetManifest(DictionaryBuilder()
+ .Set("name", "No background")
+ .Set("version", "1")
+ .Set("manifest_version", 2)
+ .Build())
+ .SetID("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+ .Build();
+ ExtensionRegistry::Get(browser_context())->AddEnabled(extension);
+ return extension;
+ }
+
+ // Creates and registers an extension with a lazy background page.
+ scoped_refptr<Extension> CreateLazyBackgroundExtension() {
+ scoped_refptr<Extension> extension =
+ ExtensionBuilder()
+ .SetManifest(
+ DictionaryBuilder()
+ .Set("name", "Lazy background")
+ .Set("version", "1")
+ .Set("manifest_version", 2)
+ .Set("background", DictionaryBuilder()
+ .Set("page", "background.html")
+ .SetBoolean("persistent", false)
+ .Build())
+ .Build())
+ .SetID("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
+ .Build();
+ ExtensionRegistry::Get(browser_context())->AddEnabled(extension);
+ return extension;
+ }
+
+ protected:
+ void SetUp() override {
+ user_prefs::UserPrefs::Set(browser_context(), &testing_pref_service_);
+ }
+
+ private:
+ scoped_ptr<content::NotificationService> notification_service_;
+
+ user_prefs::TestingPrefServiceSyncable testing_pref_service_;
+
+ // The total number of pending tasks that have been executed.
+ int task_run_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(LazyBackgroundTaskQueueTest);
+};
+
+// Tests that only extensions with background pages should have tasks queued.
+TEST_F(LazyBackgroundTaskQueueTest, ShouldEnqueueTask) {
+ LazyBackgroundTaskQueue queue(browser_context());
+
+ // Build a simple extension with no background page.
+ scoped_refptr<Extension> no_background = CreateSimpleExtension();
+ EXPECT_FALSE(queue.ShouldEnqueueTask(browser_context(), no_background.get()));
+
+ // Build another extension with a background page.
+ scoped_refptr<Extension> with_background = CreateLazyBackgroundExtension();
+ EXPECT_TRUE(
+ queue.ShouldEnqueueTask(browser_context(), with_background.get()));
+}
+
+// Tests that adding tasks actually increases the pending task count, and that
+// multiple extensions can have pending tasks.
+TEST_F(LazyBackgroundTaskQueueTest, AddPendingTask) {
+ // Get our TestProcessManager.
+ TestProcessManager* process_manager = static_cast<TestProcessManager*>(
+ ProcessManagerFactory::GetInstance()->SetTestingFactoryAndUse(
+ browser_context(), CreateTestProcessManager));
+
+ LazyBackgroundTaskQueue queue(browser_context());
+
+ // Build a simple extension with no background page.
+ scoped_refptr<Extension> no_background = CreateSimpleExtension();
+
+ // Adding a pending task increases the number of extensions with tasks, but
+ // doesn't run the task.
+ queue.AddPendingTask(browser_context(),
+ no_background->id(),
+ base::Bind(&LazyBackgroundTaskQueueTest::RunPendingTask,
+ base::Unretained(this)));
+ EXPECT_EQ(1u, queue.extensions_with_pending_tasks());
+ EXPECT_EQ(0, task_run_count());
+
+ // Another task on the same extension doesn't increase the number of
+ // extensions that have tasks and doesn't run any tasks.
+ queue.AddPendingTask(browser_context(),
+ no_background->id(),
+ base::Bind(&LazyBackgroundTaskQueueTest::RunPendingTask,
+ base::Unretained(this)));
+ EXPECT_EQ(1u, queue.extensions_with_pending_tasks());
+ EXPECT_EQ(0, task_run_count());
+
+ // Adding a task on an extension with a lazy background page tries to create
+ // a background host, and if that fails, runs the task immediately.
+ scoped_refptr<Extension> lazy_background = CreateLazyBackgroundExtension();
+ queue.AddPendingTask(browser_context(),
+ lazy_background->id(),
+ base::Bind(&LazyBackgroundTaskQueueTest::RunPendingTask,
+ base::Unretained(this)));
+ EXPECT_EQ(2u, queue.extensions_with_pending_tasks());
+ // The process manager tried to create a background host.
+ EXPECT_EQ(1, process_manager->create_count());
+ // The task ran immediately because the creation failed.
+ EXPECT_EQ(1, task_run_count());
+}
+
+// Tests that pending tasks are actually run.
+TEST_F(LazyBackgroundTaskQueueTest, ProcessPendingTasks) {
+ LazyBackgroundTaskQueue queue(browser_context());
+
+ // ProcessPendingTasks is a no-op if there are no tasks.
+ scoped_refptr<Extension> extension = CreateSimpleExtension();
+ queue.ProcessPendingTasks(NULL, browser_context(), extension.get());
+ EXPECT_EQ(0, task_run_count());
+
+ // Schedule a task to run.
+ queue.AddPendingTask(browser_context(),
+ extension->id(),
+ base::Bind(&LazyBackgroundTaskQueueTest::RunPendingTask,
+ base::Unretained(this)));
+ EXPECT_EQ(0, task_run_count());
+ EXPECT_EQ(1u, queue.extensions_with_pending_tasks());
+
+ // Trying to run tasks for an unrelated BrowserContext should do nothing.
+ content::TestBrowserContext unrelated_context;
+ queue.ProcessPendingTasks(NULL, &unrelated_context, extension.get());
+ EXPECT_EQ(0, task_run_count());
+ EXPECT_EQ(1u, queue.extensions_with_pending_tasks());
+
+ // Processing tasks when there is one pending runs the task and removes the
+ // extension from the list of extensions with pending tasks.
+ queue.ProcessPendingTasks(NULL, browser_context(), extension.get());
+ EXPECT_EQ(1, task_run_count());
+ EXPECT_EQ(0u, queue.extensions_with_pending_tasks());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/load_monitoring_extension_host_queue.cc b/chromium/extensions/browser/load_monitoring_extension_host_queue.cc
new file mode 100644
index 00000000000..47e8557abc1
--- /dev/null
+++ b/chromium/extensions/browser/load_monitoring_extension_host_queue.cc
@@ -0,0 +1,120 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/load_monitoring_extension_host_queue.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram_macros.h"
+#include "content/public/browser/render_frame_host.h"
+#include "extensions/browser/extension_host.h"
+#include "extensions/browser/extension_host_observer.h"
+#include "extensions/browser/serial_extension_host_queue.h"
+
+namespace extensions {
+
+LoadMonitoringExtensionHostQueue::LoadMonitoringExtensionHostQueue(
+ scoped_ptr<ExtensionHostQueue> delegate,
+ base::TimeDelta monitor_time,
+ const FinishedCallback& finished_callback)
+ : delegate_(std::move(delegate)),
+ monitor_time_(monitor_time),
+ finished_callback_(finished_callback),
+ started_(false),
+ num_queued_(0u),
+ num_loaded_(0u),
+ max_awaiting_loading_(0u),
+ max_active_loading_(0u),
+ weak_ptr_factory_(this) {}
+
+LoadMonitoringExtensionHostQueue::LoadMonitoringExtensionHostQueue(
+ scoped_ptr<ExtensionHostQueue> delegate)
+ : LoadMonitoringExtensionHostQueue(std::move(delegate),
+ base::TimeDelta::FromMinutes(1),
+ FinishedCallback()) {}
+
+LoadMonitoringExtensionHostQueue::~LoadMonitoringExtensionHostQueue() {
+}
+
+void LoadMonitoringExtensionHostQueue::StartMonitoring() {
+ if (started_) {
+ return;
+ }
+ started_ = true;
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, base::Bind(&LoadMonitoringExtensionHostQueue::FinishMonitoring,
+ weak_ptr_factory_.GetWeakPtr()),
+ monitor_time_);
+}
+
+void LoadMonitoringExtensionHostQueue::Add(DeferredStartRenderHost* host) {
+ StartMonitoring();
+ delegate_->Add(host);
+ host->AddDeferredStartRenderHostObserver(this);
+ if (awaiting_loading_.insert(host).second) {
+ ++num_queued_;
+ max_awaiting_loading_ =
+ std::max(max_awaiting_loading_, awaiting_loading_.size());
+ }
+}
+
+void LoadMonitoringExtensionHostQueue::Remove(DeferredStartRenderHost* host) {
+ delegate_->Remove(host);
+ host->RemoveDeferredStartRenderHostObserver(this);
+}
+
+void LoadMonitoringExtensionHostQueue::
+ OnDeferredStartRenderHostDidStartFirstLoad(
+ const DeferredStartRenderHost* host) {
+ StartMonitoringHost(host);
+}
+
+void LoadMonitoringExtensionHostQueue::
+ OnDeferredStartRenderHostDidStopFirstLoad(
+ const DeferredStartRenderHost* host) {
+ FinishMonitoringHost(host);
+}
+
+void LoadMonitoringExtensionHostQueue::OnDeferredStartRenderHostDestroyed(
+ const DeferredStartRenderHost* host) {
+ FinishMonitoringHost(host);
+}
+
+void LoadMonitoringExtensionHostQueue::StartMonitoringHost(
+ const DeferredStartRenderHost* host) {
+ awaiting_loading_.erase(host);
+ if (active_loading_.insert(host).second) {
+ max_active_loading_ = std::max(max_active_loading_, active_loading_.size());
+ }
+}
+
+void LoadMonitoringExtensionHostQueue::FinishMonitoringHost(
+ const DeferredStartRenderHost* host) {
+ if (active_loading_.erase(host)) {
+ ++num_loaded_;
+ }
+}
+
+void LoadMonitoringExtensionHostQueue::FinishMonitoring() {
+ CHECK(started_);
+ UMA_HISTOGRAM_COUNTS_100("Extensions.ExtensionHostMonitoring.NumQueued",
+ num_queued_);
+ UMA_HISTOGRAM_COUNTS_100("Extensions.ExtensionHostMonitoring.NumLoaded",
+ num_loaded_);
+ UMA_HISTOGRAM_COUNTS_100("Extensions.ExtensionHostMonitoring.MaxInQueue",
+ max_awaiting_loading_);
+ UMA_HISTOGRAM_COUNTS_100(
+ "Extensions.ExtensionHostMonitoring.MaxActiveLoading",
+ max_active_loading_);
+ if (!finished_callback_.is_null()) {
+ finished_callback_.Run(num_queued_, num_loaded_, max_awaiting_loading_,
+ max_active_loading_);
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/load_monitoring_extension_host_queue.h b/chromium/extensions/browser/load_monitoring_extension_host_queue.h
new file mode 100644
index 00000000000..661e80fe6ca
--- /dev/null
+++ b/chromium/extensions/browser/load_monitoring_extension_host_queue.h
@@ -0,0 +1,116 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_LOAD_MONITORING_EXTENSION_HOST_QUEUE_H_
+#define EXTENSIONS_BROWSER_LOAD_MONITORING_EXTENSION_HOST_QUEUE_H_
+
+#include <stddef.h>
+
+#include <set>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/scoped_observer.h"
+#include "base/time/time.h"
+#include "extensions/browser/deferred_start_render_host_observer.h"
+#include "extensions/browser/extension_host_queue.h"
+
+namespace extensions {
+
+// An ExtensionHostQueue which just monitors, and later reports, how many
+// ExtensionHosts are being loaded for some period of time.
+class LoadMonitoringExtensionHostQueue
+ : public ExtensionHostQueue,
+ public DeferredStartRenderHostObserver {
+ public:
+ // Construction for testing.
+ // Allows overriding the default timeout and triggering a callback when
+ // monitoring has finished (timeout has elapsed and UMA is logged).
+ using FinishedCallback = base::Callback<void(size_t, // num_queued
+ size_t, // max_loaded
+ size_t, // max_awaiting_loading
+ size_t // max_active_loading
+ )>;
+ LoadMonitoringExtensionHostQueue(scoped_ptr<ExtensionHostQueue> delegate,
+ base::TimeDelta monitor_time,
+ const FinishedCallback& finished_callback);
+
+ // Production code should use this constructor.
+ //
+ // Monitoring will not start until the first Add()ed
+ // DeferredStartRenderHost starts loading, or StartMonitoring() is called.
+ explicit LoadMonitoringExtensionHostQueue(
+ scoped_ptr<ExtensionHostQueue> delegate);
+
+ ~LoadMonitoringExtensionHostQueue() override;
+
+ // Starts monitoring.
+ //
+ // This can be called multiple times, but it has no effect if monitoring has
+ // already started (or finished). Monitoring cannot be restarted.
+ //
+ // Note that monitoring will automatically start when Add() is called, so it
+ // may not be necessary to call this at all.
+ void StartMonitoring();
+
+ // ExtensionHostQueue:
+ void Add(DeferredStartRenderHost* host) override;
+ void Remove(DeferredStartRenderHost* host) override;
+
+ // DeferredStartRenderHostObserver, public to be triggered by tests:
+ void OnDeferredStartRenderHostDidStartFirstLoad(
+ const DeferredStartRenderHost* host) override;
+ void OnDeferredStartRenderHostDidStopFirstLoad(
+ const DeferredStartRenderHost* host) override;
+ void OnDeferredStartRenderHostDestroyed(
+ const DeferredStartRenderHost* host) override;
+
+ private:
+ // Starts/finishes monitoring |host|, though either will have no effect if
+ // monitoring has already finished.
+ void StartMonitoringHost(const DeferredStartRenderHost* host);
+ void FinishMonitoringHost(const DeferredStartRenderHost* host);
+
+ // Called when monitoring should finish. Metrics are recorded, and from this
+ // point on no monitoring will take place.
+ void FinishMonitoring();
+
+ // Delegate actually loading DeferredStartRenderHosts to another queue.
+ scoped_ptr<ExtensionHostQueue> delegate_;
+
+ // The amount of time to monitor for. By default this is 1 minute, but it can
+ // be overriden by tests.
+ base::TimeDelta monitor_time_;
+
+ // A callback to run when monitoring has finished. Intended for testing.
+ FinishedCallback finished_callback_;
+
+ // The hosts which are waiting to start loading.
+ std::set<const DeferredStartRenderHost*> awaiting_loading_;
+ // The hosts which are currently loading.
+ std::set<const DeferredStartRenderHost*> active_loading_;
+
+ // True if this has started monitoring.
+ bool started_;
+
+ // Metrics:
+ // The total number of hosts that were added to the queue.
+ size_t num_queued_;
+ // The total number of hosts that started loading.
+ size_t num_loaded_;
+ // The maximum number of hosts waiting to load at the same time.
+ size_t max_awaiting_loading_;
+ // The maximum number of hosts that were loading at the same time.
+ size_t max_active_loading_;
+
+ base::WeakPtrFactory<LoadMonitoringExtensionHostQueue> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(LoadMonitoringExtensionHostQueue);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_LOAD_MONITORING_EXTENSION_HOST_QUEUE_H_
diff --git a/chromium/extensions/browser/load_monitoring_extension_host_queue_unittest.cc b/chromium/extensions/browser/load_monitoring_extension_host_queue_unittest.cc
new file mode 100644
index 00000000000..b5ba4468696
--- /dev/null
+++ b/chromium/extensions/browser/load_monitoring_extension_host_queue_unittest.cc
@@ -0,0 +1,355 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <limits>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "extensions/browser/deferred_start_render_host.h"
+#include "extensions/browser/extensions_test.h"
+#include "extensions/browser/load_monitoring_extension_host_queue.h"
+#include "extensions/browser/serial_extension_host_queue.h"
+
+namespace extensions {
+
+namespace {
+
+class StubDeferredStartRenderHost : public DeferredStartRenderHost {
+ public:
+ // Returns true if this host is being observed by |observer|.
+ bool IsObservedBy(DeferredStartRenderHostObserver* observer) {
+ return observers_.count(observer) > 0;
+ }
+
+ private:
+ // DeferredStartRenderHost:
+ void AddDeferredStartRenderHostObserver(
+ DeferredStartRenderHostObserver* observer) override {
+ observers_.insert(observer);
+ }
+ void RemoveDeferredStartRenderHostObserver(
+ DeferredStartRenderHostObserver* observer) override {
+ observers_.erase(observer);
+ }
+ void CreateRenderViewNow() override {}
+
+ std::set<DeferredStartRenderHostObserver*> observers_;
+};
+
+const size_t g_invalid_size_t = std::numeric_limits<size_t>::max();
+
+} // namespace
+
+class LoadMonitoringExtensionHostQueueTest : public ExtensionsTest {
+ public:
+ LoadMonitoringExtensionHostQueueTest()
+ : finished_(false),
+ // Arbitrary choice of an invalid size_t.
+ num_queued_(g_invalid_size_t),
+ num_loaded_(g_invalid_size_t),
+ max_awaiting_loading_(g_invalid_size_t),
+ max_active_loading_(g_invalid_size_t) {}
+
+ void SetUp() override {
+ queue_.reset(new LoadMonitoringExtensionHostQueue(
+ // Use a SerialExtensionHostQueue because it's simple.
+ scoped_ptr<ExtensionHostQueue>(new SerialExtensionHostQueue()),
+ base::TimeDelta(), // no delay, easier to test
+ base::Bind(&LoadMonitoringExtensionHostQueueTest::Finished,
+ base::Unretained(this))));
+ }
+
+ protected:
+ // Creates a new DeferredStartRenderHost. Ownership is held by this class,
+ // not passed to caller.
+ StubDeferredStartRenderHost* CreateHost() {
+ stubs_.push_back(make_scoped_ptr(new StubDeferredStartRenderHost()));
+ return stubs_.back().get();
+ }
+
+ // Our single LoadMonitoringExtensionHostQueue instance.
+ LoadMonitoringExtensionHostQueue* queue() { return queue_.get(); }
+
+ // Returns true if the queue has finished monitoring.
+ bool finished() const { return finished_; }
+
+ // These are available after the queue has finished (in which case finished()
+ // will return true).
+ size_t num_queued() { return num_queued_; }
+ size_t num_loaded() { return num_loaded_; }
+ size_t max_awaiting_loading() { return max_awaiting_loading_; }
+ size_t max_active_loading() { return max_active_loading_; }
+
+ private:
+ // Callback when queue has finished monitoring.
+ void Finished(size_t num_queued,
+ size_t num_loaded,
+ size_t max_awaiting_loading,
+ size_t max_active_loading) {
+ CHECK(!finished_);
+ finished_ = true;
+ num_queued_ = num_queued;
+ num_loaded_ = num_loaded;
+ max_awaiting_loading_ = max_awaiting_loading;
+ max_active_loading_ = max_active_loading;
+ }
+
+ content::TestBrowserThreadBundle thread_bundle_;
+ scoped_ptr<LoadMonitoringExtensionHostQueue> queue_;
+ std::vector<scoped_ptr<StubDeferredStartRenderHost>> stubs_;
+
+ // Set after the queue has finished monitoring.
+ bool finished_;
+ size_t num_queued_;
+ size_t num_loaded_;
+ size_t max_awaiting_loading_;
+ size_t max_active_loading_;
+};
+
+// Tests that if monitoring is never started, nor any hosts added, nothing is
+// recorded.
+TEST_F(LoadMonitoringExtensionHostQueueTest, NeverStarted) {
+ base::RunLoop().RunUntilIdle();
+ ASSERT_FALSE(finished());
+}
+
+// Tests that if monitoring has started but no hosts added, it's recorded as 0.
+TEST_F(LoadMonitoringExtensionHostQueueTest, NoHosts) {
+ queue()->StartMonitoring();
+
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(finished());
+ EXPECT_EQ(0u, num_queued());
+ EXPECT_EQ(0u, num_loaded());
+ EXPECT_EQ(0u, max_awaiting_loading());
+ EXPECT_EQ(0u, max_active_loading());
+}
+
+// Tests that adding a host starts monitoring.
+TEST_F(LoadMonitoringExtensionHostQueueTest, AddOneHost) {
+ queue()->Add(CreateHost());
+
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(finished());
+ EXPECT_EQ(1u, num_queued());
+ EXPECT_EQ(0u, num_loaded());
+ EXPECT_EQ(1u, max_awaiting_loading());
+ EXPECT_EQ(0u, max_active_loading());
+}
+
+// Tests that a host added and removed is still recorded, but not as a load
+// finished.
+TEST_F(LoadMonitoringExtensionHostQueueTest, AddAndRemoveOneHost) {
+ DeferredStartRenderHost* host = CreateHost();
+ queue()->Add(host);
+ queue()->Remove(host);
+
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(finished());
+ EXPECT_EQ(1u, num_queued());
+ EXPECT_EQ(0u, num_loaded());
+ EXPECT_EQ(1u, max_awaiting_loading());
+ EXPECT_EQ(0u, max_active_loading());
+}
+
+// Tests adding and starting a single host.
+TEST_F(LoadMonitoringExtensionHostQueueTest, AddAndStartOneHost) {
+ DeferredStartRenderHost* host = CreateHost();
+ queue()->Add(host);
+ queue()->OnDeferredStartRenderHostDidStartFirstLoad(host);
+
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(finished());
+ EXPECT_EQ(1u, num_queued());
+ EXPECT_EQ(0u, num_loaded());
+ EXPECT_EQ(1u, max_awaiting_loading());
+ EXPECT_EQ(1u, max_active_loading());
+}
+
+// Tests adding and destroying a single host without starting it.
+TEST_F(LoadMonitoringExtensionHostQueueTest, AddAndDestroyOneHost) {
+ DeferredStartRenderHost* host = CreateHost();
+ queue()->Add(host);
+ queue()->OnDeferredStartRenderHostDestroyed(host);
+
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(finished());
+ EXPECT_EQ(1u, num_queued());
+ EXPECT_EQ(0u, num_loaded());
+ EXPECT_EQ(1u, max_awaiting_loading());
+ EXPECT_EQ(0u, max_active_loading());
+}
+
+// Tests adding, starting, and stopping a single host.
+TEST_F(LoadMonitoringExtensionHostQueueTest, AddAndStartAndStopOneHost) {
+ DeferredStartRenderHost* host = CreateHost();
+ queue()->Add(host);
+ queue()->OnDeferredStartRenderHostDidStartFirstLoad(host);
+ queue()->OnDeferredStartRenderHostDidStopFirstLoad(host);
+
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(finished());
+ EXPECT_EQ(1u, num_queued());
+ EXPECT_EQ(1u, num_loaded());
+ EXPECT_EQ(1u, max_awaiting_loading());
+ EXPECT_EQ(1u, max_active_loading());
+}
+
+// Tests adding, starting, and stopping a single host - twice.
+TEST_F(LoadMonitoringExtensionHostQueueTest, AddAndStartAndStopOneHostTwice) {
+ DeferredStartRenderHost* host = CreateHost();
+ queue()->Add(host);
+ queue()->OnDeferredStartRenderHostDidStartFirstLoad(host);
+ queue()->OnDeferredStartRenderHostDidStopFirstLoad(host);
+
+ // Re-starting loading should also be recorded fine (e.g. navigations could
+ // trigger this).
+ queue()->OnDeferredStartRenderHostDidStartFirstLoad(host);
+ queue()->OnDeferredStartRenderHostDidStopFirstLoad(host);
+
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(finished());
+ EXPECT_EQ(1u, num_queued());
+ EXPECT_EQ(2u, num_loaded()); // 2 loaded this time, because we ran twice
+ EXPECT_EQ(1u, max_awaiting_loading());
+ EXPECT_EQ(1u, max_active_loading());
+}
+
+// Tests adding, starting, and destroying (i.e. an implicit stop) a single host.
+TEST_F(LoadMonitoringExtensionHostQueueTest, AddAndStartAndDestroyOneHost) {
+ DeferredStartRenderHost* host = CreateHost();
+ queue()->Add(host);
+ queue()->OnDeferredStartRenderHostDidStartFirstLoad(host);
+ queue()->OnDeferredStartRenderHostDestroyed(host);
+
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(finished());
+ EXPECT_EQ(1u, num_queued());
+ EXPECT_EQ(1u, num_loaded());
+ EXPECT_EQ(1u, max_awaiting_loading());
+ EXPECT_EQ(1u, max_active_loading());
+}
+
+// Tests adding, starting, and removing a single host.
+TEST_F(LoadMonitoringExtensionHostQueueTest, AddAndStartAndRemoveOneHost) {
+ DeferredStartRenderHost* host = CreateHost();
+ queue()->Add(host);
+ queue()->OnDeferredStartRenderHostDidStartFirstLoad(host);
+ queue()->Remove(host);
+
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(finished());
+ EXPECT_EQ(1u, num_queued());
+ EXPECT_EQ(0u, num_loaded());
+ EXPECT_EQ(1u, max_awaiting_loading());
+ EXPECT_EQ(1u, max_active_loading());
+}
+
+// Tests monitoring a sequence of hosts.
+TEST_F(LoadMonitoringExtensionHostQueueTest, Sequence) {
+ // Scenario:
+ //
+ // 6 hosts will be added, only 5 will start loading, with a maximum of 4 in
+ // the queue and 3 loading at any time. Only 2 will finish.
+ DeferredStartRenderHost* host1 = CreateHost();
+ DeferredStartRenderHost* host2 = CreateHost();
+ DeferredStartRenderHost* host3 = CreateHost();
+ DeferredStartRenderHost* host4 = CreateHost();
+ DeferredStartRenderHost* host5 = CreateHost();
+ DeferredStartRenderHost* host6 = CreateHost();
+
+ queue()->Add(host1);
+ queue()->Add(host2);
+ queue()->Add(host3);
+
+ queue()->OnDeferredStartRenderHostDidStartFirstLoad(host1);
+ queue()->OnDeferredStartRenderHostDidStartFirstLoad(host2);
+ queue()->OnDeferredStartRenderHostDidStopFirstLoad(host1);
+
+ queue()->Add(host4);
+ queue()->Add(host5);
+ queue()->Add(host6);
+
+ queue()->OnDeferredStartRenderHostDidStartFirstLoad(host3);
+ queue()->OnDeferredStartRenderHostDidStartFirstLoad(host4);
+ queue()->OnDeferredStartRenderHostDidStopFirstLoad(host4);
+ queue()->OnDeferredStartRenderHostDidStartFirstLoad(host5);
+
+ base::RunLoop().RunUntilIdle();
+ ASSERT_TRUE(finished());
+ EXPECT_EQ(6u, num_queued());
+ EXPECT_EQ(2u, num_loaded());
+ EXPECT_EQ(4u, max_awaiting_loading());
+ EXPECT_EQ(3u, max_active_loading());
+
+ // Complete a realistic sequence by stopping and/or destroying all hosts.
+ queue()->OnDeferredStartRenderHostDestroyed(host1);
+ queue()->OnDeferredStartRenderHostDidStopFirstLoad(host2);
+ queue()->OnDeferredStartRenderHostDestroyed(host2);
+ queue()->OnDeferredStartRenderHostDidStopFirstLoad(host3);
+ queue()->OnDeferredStartRenderHostDestroyed(host3);
+ queue()->OnDeferredStartRenderHostDestroyed(host4);
+ queue()->OnDeferredStartRenderHostDestroyed(host5); // never stopped
+ queue()->OnDeferredStartRenderHostDestroyed(host6); // never started/stopped
+}
+
+// Tests that the queue is observing Hosts from adding them through to being
+// removed - that the load sequence itself is irrelevant.
+//
+// This is an unfortunate implementation-style test, but it used to be a bug
+// and difficult to catch outside of a proper test framework - one in which we
+// don't have to trigger events by hand.
+TEST_F(LoadMonitoringExtensionHostQueueTest, ObserverLifetime) {
+ StubDeferredStartRenderHost* host1 = CreateHost();
+ StubDeferredStartRenderHost* host2 = CreateHost();
+ StubDeferredStartRenderHost* host3 = CreateHost();
+ StubDeferredStartRenderHost* host4 = CreateHost();
+
+ EXPECT_FALSE(host1->IsObservedBy(queue()));
+ EXPECT_FALSE(host2->IsObservedBy(queue()));
+ EXPECT_FALSE(host3->IsObservedBy(queue()));
+ EXPECT_FALSE(host4->IsObservedBy(queue()));
+
+ queue()->Add(host1);
+ queue()->Add(host2);
+ queue()->Add(host3);
+ queue()->Add(host4);
+
+ EXPECT_TRUE(host1->IsObservedBy(queue()));
+ EXPECT_TRUE(host2->IsObservedBy(queue()));
+ EXPECT_TRUE(host3->IsObservedBy(queue()));
+ EXPECT_TRUE(host4->IsObservedBy(queue()));
+
+ queue()->OnDeferredStartRenderHostDidStartFirstLoad(host1);
+ queue()->OnDeferredStartRenderHostDidStartFirstLoad(host2);
+ queue()->OnDeferredStartRenderHostDidStartFirstLoad(host3);
+ // host4 will test that we Remove before Starting - so don't start.
+
+ EXPECT_TRUE(host1->IsObservedBy(queue()));
+ EXPECT_TRUE(host2->IsObservedBy(queue()));
+ EXPECT_TRUE(host3->IsObservedBy(queue()));
+ EXPECT_TRUE(host4->IsObservedBy(queue()));
+
+ queue()->OnDeferredStartRenderHostDidStopFirstLoad(host1);
+ queue()->OnDeferredStartRenderHostDestroyed(host2);
+
+ EXPECT_TRUE(host1->IsObservedBy(queue()));
+ EXPECT_TRUE(host2->IsObservedBy(queue()));
+
+ queue()->Remove(host1);
+ queue()->Remove(host2);
+ queue()->Remove(host3);
+ queue()->Remove(host4);
+
+ EXPECT_FALSE(host1->IsObservedBy(queue()));
+ EXPECT_FALSE(host2->IsObservedBy(queue()));
+ EXPECT_FALSE(host3->IsObservedBy(queue()));
+ EXPECT_FALSE(host4->IsObservedBy(queue()));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/management_policy.cc b/chromium/extensions/browser/management_policy.cc
new file mode 100644
index 00000000000..b09734a908b
--- /dev/null
+++ b/chromium/extensions/browser/management_policy.cc
@@ -0,0 +1,133 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/management_policy.h"
+
+namespace extensions {
+
+namespace {
+
+void GetExtensionNameAndId(const Extension* extension,
+ std::string* name,
+ std::string* id) {
+ // The extension may be NULL in testing.
+ *id = extension ? extension->id() : "[test]";
+ *name = extension ? extension->name() : "test";
+}
+
+} // namespace
+
+ManagementPolicy::ManagementPolicy() {
+}
+
+ManagementPolicy::~ManagementPolicy() {
+}
+
+bool ManagementPolicy::Provider::UserMayLoad(const Extension* extension,
+ base::string16* error) const {
+ return true;
+}
+
+bool ManagementPolicy::Provider::UserMayModifySettings(
+ const Extension* extension, base::string16* error) const {
+ return true;
+}
+
+bool ManagementPolicy::Provider::MustRemainEnabled(const Extension* extension,
+ base::string16* error)
+ const {
+ return false;
+}
+
+bool ManagementPolicy::Provider::MustRemainDisabled(
+ const Extension* extension,
+ Extension::DisableReason* reason,
+ base::string16* error) const {
+ return false;
+}
+
+bool ManagementPolicy::Provider::MustRemainInstalled(
+ const Extension* extension,
+ base::string16* error) const {
+ return false;
+}
+
+void ManagementPolicy::RegisterProvider(Provider* provider) {
+ providers_.insert(provider);
+}
+
+void ManagementPolicy::UnregisterProvider(Provider* provider) {
+ providers_.erase(provider);
+}
+
+void ManagementPolicy::RegisterProviders(std::vector<Provider*> providers) {
+ providers_.insert(providers.begin(), providers.end());
+}
+
+bool ManagementPolicy::UserMayLoad(const Extension* extension,
+ base::string16* error) const {
+ return ApplyToProviderList(
+ &Provider::UserMayLoad, "Installation", true, extension, error);
+}
+
+bool ManagementPolicy::UserMayModifySettings(const Extension* extension,
+ base::string16* error) const {
+ return ApplyToProviderList(
+ &Provider::UserMayModifySettings, "Modification", true, extension, error);
+}
+
+bool ManagementPolicy::MustRemainEnabled(const Extension* extension,
+ base::string16* error) const {
+ return ApplyToProviderList(
+ &Provider::MustRemainEnabled, "Disabling", false, extension, error);
+}
+
+bool ManagementPolicy::MustRemainDisabled(const Extension* extension,
+ Extension::DisableReason* reason,
+ base::string16* error) const {
+ for (ProviderList::const_iterator it = providers_.begin();
+ it != providers_.end(); ++it)
+ if ((*it)->MustRemainDisabled(extension, reason, error))
+ return true;
+
+ return false;
+}
+
+bool ManagementPolicy::MustRemainInstalled(const Extension* extension,
+ base::string16* error) const {
+ return ApplyToProviderList(
+ &Provider::MustRemainInstalled, "Removing", false, extension, error);
+}
+
+void ManagementPolicy::UnregisterAllProviders() {
+ providers_.clear();
+}
+
+int ManagementPolicy::GetNumProviders() const {
+ return providers_.size();
+}
+
+bool ManagementPolicy::ApplyToProviderList(ProviderFunction function,
+ const char* debug_operation_name,
+ bool normal_result,
+ const Extension* extension,
+ base::string16* error) const {
+ for (ProviderList::const_iterator it = providers_.begin();
+ it != providers_.end(); ++it) {
+ const Provider* provider = *it;
+ bool result = (provider->*function)(extension, error);
+ if (result != normal_result) {
+ std::string id;
+ std::string name;
+ GetExtensionNameAndId(extension, &name, &id);
+ DVLOG(1) << debug_operation_name << " of extension " << name
+ << " (" << id << ")"
+ << " prohibited by " << provider->GetDebugPolicyProviderName();
+ return !normal_result;
+ }
+ }
+ return normal_result;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/management_policy.h b/chromium/extensions/browser/management_policy.h
new file mode 100644
index 00000000000..584af58bed9
--- /dev/null
+++ b/chromium/extensions/browser/management_policy.h
@@ -0,0 +1,164 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_MANAGEMENT_POLICY_H_
+#define EXTENSIONS_BROWSER_MANAGEMENT_POLICY_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "extensions/common/extension.h"
+
+namespace extensions {
+
+// This class registers providers that want to prohibit certain actions from
+// being applied to extensions. It must be called, via the ExtensionService,
+// before allowing a user or a user-level mechanism to perform the respective
+// action. (That is, installing or otherwise modifying an extension in order
+// to conform to enterprise administrator policy must be exempted from these
+// checks.)
+//
+// This "policy" and its providers should not be confused with administrator
+// policy, although admin policy is one of the sources ("Providers") of
+// restrictions registered with and exposed by the ManagementPolicy.
+class ManagementPolicy {
+ public:
+ // Each mechanism that wishes to limit users' ability to control extensions,
+ // whether one individual extension or the whole system, should implement
+ // the methods of this Provider interface that it needs. In each case, if the
+ // provider does not need to control a certain action, that method does not
+ // need to be implemented.
+ //
+ // It is not guaranteed that a particular Provider's methods will be called
+ // each time a user tries to perform one of the controlled actions (the list
+ // of providers is short-circuited as soon as a decision is possible), so
+ // implementations of these methods must have no side effects.
+ //
+ // For all of the Provider methods below, if |error| is not NULL and the
+ // method imposes a restriction on the desired action, |error| may be set
+ // to an applicable error message, but this is not required.
+ class Provider {
+ public:
+ Provider() {}
+ virtual ~Provider() {}
+
+ // A human-readable name for this provider, for use in debug messages.
+ // Implementers should return an empty string in non-debug builds, to save
+ // executable size.
+ virtual std::string GetDebugPolicyProviderName() const = 0;
+
+ // Providers should return false if a user may not install the |extension|,
+ // or load or run it if it has already been installed.
+ // TODO(treib,pam): The method name is misleading, since this applies to all
+ // extension installations, not just user-initiated ones. Fix either the
+ // name or the semantics. crbug.com/461747
+ virtual bool UserMayLoad(const Extension* extension,
+ base::string16* error) const;
+
+ // Providers should return false if a user may not enable, disable, or
+ // uninstall the |extension|, or change its usage options (incognito
+ // permission, file access, etc.).
+ // TODO(treib,pam): The method name is misleading, since this applies to all
+ // setting modifications, not just user-initiated ones. Fix either the
+ // name or the semantics. crbug.com/461747
+ virtual bool UserMayModifySettings(const Extension* extension,
+ base::string16* error) const;
+
+ // Providers should return true if the |extension| must always remain
+ // enabled. This is distinct from UserMayModifySettings() in that the latter
+ // also prohibits enabling the extension if it is currently disabled.
+ // Providers implementing this method should also implement the others
+ // above, if they wish to completely lock in an extension.
+ virtual bool MustRemainEnabled(const Extension* extension,
+ base::string16* error) const;
+
+ // Similar to MustRemainEnabled, but for whether an extension must remain
+ // disabled, and returns an error and/or reason if the caller needs it.
+ virtual bool MustRemainDisabled(const Extension* extension,
+ Extension::DisableReason* reason,
+ base::string16* error) const;
+
+ // Similar to MustRemainEnabled, but for whether an extension must remain
+ // installed, and returns an error and/or reason if the caller needs it.
+ virtual bool MustRemainInstalled(const Extension* extension,
+ base::string16* error) const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Provider);
+ };
+
+ ManagementPolicy();
+ ~ManagementPolicy();
+
+ // Registers or unregisters a provider, causing it to be added to or removed
+ // from the list of providers queried. Ownership of the provider remains with
+ // the caller. Providers do not need to be unregistered on shutdown.
+ void RegisterProvider(Provider* provider);
+ void UnregisterProvider(Provider* provider);
+
+ // Like RegisterProvider(), but registers multiple providers instead.
+ void RegisterProviders(std::vector<Provider*> providers);
+
+ // Returns true if the user is permitted to install, load, and run the given
+ // extension. If not, |error| may be set to an appropriate message.
+ // TODO(treib,pam): Misleading name; see comment in Provider. crbug.com/461747
+ bool UserMayLoad(const Extension* extension, base::string16* error) const;
+
+ // Returns true if the user is permitted to enable, disable, or uninstall the
+ // given extension, or change the extension's usage options (incognito mode,
+ // file access, etc.). If not, |error| may be set to an appropriate message.
+ // TODO(treib,pam): Misleading name; see comment in Provider. crbug.com/461747
+ bool UserMayModifySettings(const Extension* extension,
+ base::string16* error) const;
+
+ // Returns true if the extension must remain enabled at all times (e.g. a
+ // component extension). In that case, |error| may be set to an appropriate
+ // message.
+ bool MustRemainEnabled(const Extension* extension,
+ base::string16* error) const;
+
+ // Returns true immediately if any registered provider's MustRemainDisabled
+ // function returns true.
+ bool MustRemainDisabled(const Extension* extension,
+ Extension::DisableReason* reason,
+ base::string16* error) const;
+
+ // Returns true immediately if any registered provider's MustRemainInstalled
+ // function returns true.
+ bool MustRemainInstalled(const Extension* extension,
+ base::string16* error) const;
+
+ // For use in testing.
+ void UnregisterAllProviders();
+ int GetNumProviders() const;
+
+ private:
+ // This is a pointer to a function in the Provider interface, used in
+ // ApplyToProviderList.
+ typedef bool (Provider::*ProviderFunction)(const Extension*,
+ base::string16*) const;
+
+ typedef std::set<Provider*> ProviderList;
+
+ // This is a helper to apply a method in the Provider interface to each of
+ // the Provider objects in |providers_|. The return value of this function
+ // will be |normal_result|, unless any of the Provider calls to |function|
+ // return !normal_result, in which case this function will then early-return
+ // !normal_result.
+ bool ApplyToProviderList(ProviderFunction function,
+ const char* debug_operation_name,
+ bool normal_result,
+ const Extension* extension,
+ base::string16* error) const;
+
+ ProviderList providers_;
+
+ DISALLOW_COPY_AND_ASSIGN(ManagementPolicy);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_MANAGEMENT_POLICY_H_
diff --git a/chromium/extensions/browser/management_policy_unittest.cc b/chromium/extensions/browser/management_policy_unittest.cc
new file mode 100644
index 00000000000..7bf60a9bf44
--- /dev/null
+++ b/chromium/extensions/browser/management_policy_unittest.cc
@@ -0,0 +1,242 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/browser/management_policy.h"
+#include "extensions/browser/test_management_policy.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+typedef extensions::TestManagementPolicyProvider TestProvider;
+using extensions::Extension;
+
+class ManagementPolicyTest : public testing::Test {
+ public:
+ void SetUp() override {
+ allow_all_.SetProhibitedActions(TestProvider::ALLOW_ALL);
+ no_modify_status_.SetProhibitedActions(
+ TestProvider::PROHIBIT_MODIFY_STATUS);
+ no_load_.SetProhibitedActions(TestProvider::PROHIBIT_LOAD);
+ must_remain_enabled_.SetProhibitedActions(
+ TestProvider::MUST_REMAIN_ENABLED);
+ must_remain_disabled_.SetProhibitedActions(
+ TestProvider::MUST_REMAIN_DISABLED);
+ must_remain_disabled_.SetDisableReason(Extension::DISABLE_SIDELOAD_WIPEOUT);
+ must_remain_installed_.SetProhibitedActions(
+ TestProvider::MUST_REMAIN_INSTALLED);
+ restrict_all_.SetProhibitedActions(TestProvider::PROHIBIT_MODIFY_STATUS |
+ TestProvider::PROHIBIT_LOAD |
+ TestProvider::MUST_REMAIN_ENABLED);
+ }
+
+ protected:
+ extensions::ManagementPolicy policy_;
+
+ TestProvider allow_all_;
+ TestProvider no_modify_status_;
+ TestProvider no_load_;
+ TestProvider must_remain_enabled_;
+ TestProvider must_remain_disabled_;
+ TestProvider must_remain_installed_;
+ TestProvider restrict_all_;
+};
+
+TEST_F(ManagementPolicyTest, RegisterAndUnregister) {
+ EXPECT_EQ(0, policy_.GetNumProviders());
+ policy_.RegisterProvider(&allow_all_);
+ EXPECT_EQ(1, policy_.GetNumProviders());
+ policy_.RegisterProvider(&allow_all_);
+ EXPECT_EQ(1, policy_.GetNumProviders());
+
+ policy_.RegisterProvider(&no_modify_status_);
+ EXPECT_EQ(2, policy_.GetNumProviders());
+ policy_.UnregisterProvider(&allow_all_);
+ EXPECT_EQ(1, policy_.GetNumProviders());
+ policy_.UnregisterProvider(&allow_all_);
+ EXPECT_EQ(1, policy_.GetNumProviders());
+ policy_.UnregisterProvider(&no_modify_status_);
+ EXPECT_EQ(0, policy_.GetNumProviders());
+
+ policy_.RegisterProvider(&allow_all_);
+ policy_.RegisterProvider(&no_modify_status_);
+ EXPECT_EQ(2, policy_.GetNumProviders());
+ policy_.UnregisterAllProviders();
+ EXPECT_EQ(0, policy_.GetNumProviders());
+}
+
+TEST_F(ManagementPolicyTest, UserMayLoad) {
+ // No providers registered.
+ base::string16 error;
+ // The extension and location are irrelevant to the
+ // TestManagementPolicyProviders.
+ EXPECT_TRUE(policy_.UserMayLoad(NULL, &error));
+ EXPECT_TRUE(error.empty());
+
+ // One provider, no relevant restriction.
+ policy_.RegisterProvider(&no_modify_status_);
+ EXPECT_TRUE(policy_.UserMayLoad(NULL, &error));
+ EXPECT_TRUE(error.empty());
+
+ // Two providers, no relevant restrictions.
+ policy_.RegisterProvider(&must_remain_enabled_);
+ EXPECT_TRUE(policy_.UserMayLoad(NULL, &error));
+ EXPECT_TRUE(error.empty());
+
+ // Three providers, one with a relevant restriction.
+ policy_.RegisterProvider(&no_load_);
+ EXPECT_FALSE(policy_.UserMayLoad(NULL, &error));
+ EXPECT_FALSE(error.empty());
+
+ // Remove the restriction.
+ policy_.UnregisterProvider(&no_load_);
+ error.clear();
+ EXPECT_TRUE(policy_.UserMayLoad(NULL, &error));
+ EXPECT_TRUE(error.empty());
+}
+TEST_F(ManagementPolicyTest, UserMayModifySettings) {
+ // No providers registered.
+ base::string16 error;
+ EXPECT_TRUE(policy_.UserMayModifySettings(NULL, &error));
+ EXPECT_TRUE(error.empty());
+
+ // One provider, no relevant restriction.
+ policy_.RegisterProvider(&allow_all_);
+ EXPECT_TRUE(policy_.UserMayModifySettings(NULL, &error));
+ EXPECT_TRUE(error.empty());
+
+ // Two providers, no relevant restrictions.
+ policy_.RegisterProvider(&no_load_);
+ EXPECT_TRUE(policy_.UserMayModifySettings(NULL, &error));
+ EXPECT_TRUE(error.empty());
+
+ // Three providers, one with a relevant restriction.
+ policy_.RegisterProvider(&no_modify_status_);
+ EXPECT_FALSE(policy_.UserMayModifySettings(NULL, &error));
+ EXPECT_FALSE(error.empty());
+
+ // Remove the restriction.
+ policy_.UnregisterProvider(&no_modify_status_);
+ error.clear();
+ EXPECT_TRUE(policy_.UserMayModifySettings(NULL, &error));
+ EXPECT_TRUE(error.empty());
+}
+
+TEST_F(ManagementPolicyTest, MustRemainEnabled) {
+ // No providers registered.
+ base::string16 error;
+ EXPECT_FALSE(policy_.MustRemainEnabled(NULL, &error));
+ EXPECT_TRUE(error.empty());
+
+ // One provider, no relevant restriction.
+ policy_.RegisterProvider(&allow_all_);
+ EXPECT_FALSE(policy_.MustRemainEnabled(NULL, &error));
+ EXPECT_TRUE(error.empty());
+
+ // Two providers, no relevant restrictions.
+ policy_.RegisterProvider(&no_modify_status_);
+ EXPECT_FALSE(policy_.MustRemainEnabled(NULL, &error));
+ EXPECT_TRUE(error.empty());
+
+ // Three providers, one with a relevant restriction.
+ policy_.RegisterProvider(&must_remain_enabled_);
+ EXPECT_TRUE(policy_.MustRemainEnabled(NULL, &error));
+ EXPECT_FALSE(error.empty());
+
+ // Remove the restriction.
+ policy_.UnregisterProvider(&must_remain_enabled_);
+ error.clear();
+ EXPECT_FALSE(policy_.MustRemainEnabled(NULL, &error));
+ EXPECT_TRUE(error.empty());
+}
+
+TEST_F(ManagementPolicyTest, MustRemainDisabled) {
+ // No providers registered.
+ base::string16 error;
+ EXPECT_FALSE(policy_.MustRemainDisabled(NULL, NULL, &error));
+ EXPECT_TRUE(error.empty());
+
+ // One provider, no relevant restriction.
+ policy_.RegisterProvider(&allow_all_);
+ EXPECT_FALSE(policy_.MustRemainDisabled(NULL, NULL, &error));
+ EXPECT_TRUE(error.empty());
+
+ // Two providers, no relevant restrictions.
+ policy_.RegisterProvider(&no_modify_status_);
+ EXPECT_FALSE(policy_.MustRemainDisabled(NULL, NULL, &error));
+ EXPECT_TRUE(error.empty());
+
+ // Three providers, one with a relevant restriction.
+ Extension::DisableReason reason = Extension::DISABLE_NONE;
+ policy_.RegisterProvider(&must_remain_disabled_);
+ EXPECT_TRUE(policy_.MustRemainDisabled(NULL, &reason, &error));
+ EXPECT_FALSE(error.empty());
+ EXPECT_EQ(Extension::DISABLE_SIDELOAD_WIPEOUT, reason);
+
+ // Remove the restriction.
+ policy_.UnregisterProvider(&must_remain_disabled_);
+ error.clear();
+ EXPECT_FALSE(policy_.MustRemainDisabled(NULL, NULL, &error));
+ EXPECT_TRUE(error.empty());
+}
+
+TEST_F(ManagementPolicyTest, MustRemainInstalled) {
+ // No providers registered.
+ base::string16 error;
+ EXPECT_FALSE(policy_.MustRemainInstalled(NULL, &error));
+ EXPECT_TRUE(error.empty());
+
+ // One provider, no relevant restriction.
+ policy_.RegisterProvider(&allow_all_);
+ EXPECT_FALSE(policy_.MustRemainInstalled(NULL, &error));
+ EXPECT_TRUE(error.empty());
+
+ // Two providers, no relevant restrictions.
+ policy_.RegisterProvider(&no_modify_status_);
+ EXPECT_FALSE(policy_.MustRemainInstalled(NULL, &error));
+ EXPECT_TRUE(error.empty());
+
+ // Three providers, one with a relevant restriction.
+ policy_.RegisterProvider(&must_remain_installed_);
+ EXPECT_TRUE(policy_.MustRemainInstalled(NULL, &error));
+ EXPECT_FALSE(error.empty());
+
+ // Remove the restriction.
+ policy_.UnregisterProvider(&must_remain_installed_);
+ error.clear();
+ EXPECT_FALSE(policy_.MustRemainInstalled(NULL, &error));
+ EXPECT_TRUE(error.empty());
+}
+
+// Tests error handling in the ManagementPolicy.
+TEST_F(ManagementPolicyTest, ErrorHandling) {
+ // The error parameter should be unchanged if no restriction was found.
+ std::string original_error = "Ceci est en effet une erreur.";
+ base::string16 original_error16 = base::UTF8ToUTF16(original_error);
+ base::string16 error = original_error16;
+ EXPECT_TRUE(policy_.UserMayLoad(NULL, &error));
+ EXPECT_EQ(original_error, base::UTF16ToUTF8(error));
+ EXPECT_TRUE(policy_.UserMayModifySettings(NULL, &error));
+ EXPECT_EQ(original_error, base::UTF16ToUTF8(error));
+ EXPECT_FALSE(policy_.MustRemainEnabled(NULL, &error));
+ EXPECT_EQ(original_error, base::UTF16ToUTF8(error));
+
+ // Ensure no crashes if no error message was requested.
+ EXPECT_TRUE(policy_.UserMayLoad(NULL, NULL));
+ EXPECT_TRUE(policy_.UserMayModifySettings(NULL, NULL));
+ EXPECT_FALSE(policy_.MustRemainEnabled(NULL, NULL));
+ policy_.RegisterProvider(&restrict_all_);
+ EXPECT_FALSE(policy_.UserMayLoad(NULL, NULL));
+ EXPECT_FALSE(policy_.UserMayModifySettings(NULL, NULL));
+ EXPECT_TRUE(policy_.MustRemainEnabled(NULL, NULL));
+
+ // Make sure returned error is correct.
+ error = original_error16;
+ EXPECT_FALSE(policy_.UserMayLoad(NULL, &error));
+ EXPECT_EQ(base::UTF8ToUTF16(TestProvider::expected_error()), error);
+ error = original_error16;
+ EXPECT_FALSE(policy_.UserMayModifySettings(NULL, &error));
+ EXPECT_EQ(base::UTF8ToUTF16(TestProvider::expected_error()), error);
+ error = original_error16;
+ EXPECT_TRUE(policy_.MustRemainEnabled(NULL, &error));
+ EXPECT_EQ(base::UTF8ToUTF16(TestProvider::expected_error()), error);
+}
diff --git a/chromium/extensions/browser/mock_extension_system.cc b/chromium/extensions/browser/mock_extension_system.cc
new file mode 100644
index 00000000000..6658832e744
--- /dev/null
+++ b/chromium/extensions/browser/mock_extension_system.cc
@@ -0,0 +1,84 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/mock_extension_system.h"
+
+#include "extensions/browser/value_store/value_store_factory.h"
+#include "extensions/common/extension_set.h"
+
+namespace extensions {
+
+MockExtensionSystem::MockExtensionSystem(content::BrowserContext* context)
+ : browser_context_(context) {
+}
+
+MockExtensionSystem::~MockExtensionSystem() {
+}
+
+void MockExtensionSystem::InitForRegularProfile(bool extensions_enabled) {
+}
+
+ExtensionService* MockExtensionSystem::extension_service() {
+ return nullptr;
+}
+
+RuntimeData* MockExtensionSystem::runtime_data() {
+ return nullptr;
+}
+
+ManagementPolicy* MockExtensionSystem::management_policy() {
+ return nullptr;
+}
+
+ServiceWorkerManager* MockExtensionSystem::service_worker_manager() {
+ return nullptr;
+}
+
+SharedUserScriptMaster* MockExtensionSystem::shared_user_script_master() {
+ return nullptr;
+}
+
+StateStore* MockExtensionSystem::state_store() {
+ return nullptr;
+}
+
+StateStore* MockExtensionSystem::rules_store() {
+ return nullptr;
+}
+
+scoped_refptr<ValueStoreFactory> MockExtensionSystem::store_factory() {
+ return nullptr;
+}
+
+InfoMap* MockExtensionSystem::info_map() {
+ return nullptr;
+}
+
+QuotaService* MockExtensionSystem::quota_service() {
+ return nullptr;
+}
+
+AppSorting* MockExtensionSystem::app_sorting() {
+ return nullptr;
+}
+
+const OneShotEvent& MockExtensionSystem::ready() const {
+ return ready_;
+}
+
+ContentVerifier* MockExtensionSystem::content_verifier() {
+ return nullptr;
+}
+
+scoped_ptr<ExtensionSet> MockExtensionSystem::GetDependentExtensions(
+ const Extension* extension) {
+ return scoped_ptr<ExtensionSet>();
+}
+
+void MockExtensionSystem::InstallUpdate(const std::string& extension_id,
+ const base::FilePath& temp_dir) {
+ NOTREACHED();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/mock_extension_system.h b/chromium/extensions/browser/mock_extension_system.h
new file mode 100644
index 00000000000..b926baad84b
--- /dev/null
+++ b/chromium/extensions/browser/mock_extension_system.h
@@ -0,0 +1,91 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_MOCK_EXTENSION_SYSTEM_H_
+#define EXTENSIONS_BROWSER_MOCK_EXTENSION_SYSTEM_H_
+
+#include "base/macros.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "extensions/browser/extension_registry_factory.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/extension_system_provider.h"
+#include "extensions/common/one_shot_event.h"
+
+namespace extensions {
+
+// An empty ExtensionSystem for testing. Tests that need only specific
+// parts of ExtensionSystem should derive from this class and override
+// functions as needed. To use this, use
+// TestExtensionsBrowserClient::set_extension_system_factory
+// with the MockExtensionSystemFactory below.
+class MockExtensionSystem : public ExtensionSystem {
+ public:
+ explicit MockExtensionSystem(content::BrowserContext* context);
+ ~MockExtensionSystem() override;
+
+ content::BrowserContext* browser_context() { return browser_context_; }
+
+ // ExtensionSystem overrides:
+ void InitForRegularProfile(bool extensions_enabled) override;
+ ExtensionService* extension_service() override;
+ RuntimeData* runtime_data() override;
+ ManagementPolicy* management_policy() override;
+ ServiceWorkerManager* service_worker_manager() override;
+ SharedUserScriptMaster* shared_user_script_master() override;
+ StateStore* state_store() override;
+ StateStore* rules_store() override;
+ scoped_refptr<ValueStoreFactory> store_factory() override;
+ InfoMap* info_map() override;
+ QuotaService* quota_service() override;
+ AppSorting* app_sorting() override;
+ const OneShotEvent& ready() const override;
+ ContentVerifier* content_verifier() override;
+ scoped_ptr<ExtensionSet> GetDependentExtensions(
+ const Extension* extension) override;
+ void InstallUpdate(const std::string& extension_id,
+ const base::FilePath& temp_dir) override;
+
+ private:
+ content::BrowserContext* browser_context_;
+ OneShotEvent ready_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockExtensionSystem);
+};
+
+// A factory to create a MockExtensionSystem. Sample use:
+//
+// MockExtensionSystemFactory<MockExtensionSystemSubclass> factory;
+// TestExtensionsBrowserClient::set_extension_system_factory(factory);
+template <typename T>
+class MockExtensionSystemFactory : public ExtensionSystemProvider {
+ public:
+ MockExtensionSystemFactory()
+ : ExtensionSystemProvider(
+ "MockExtensionSystem",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(ExtensionRegistryFactory::GetInstance());
+ }
+
+ ~MockExtensionSystemFactory() override {}
+
+ // BrowserContextKeyedServiceFactory overrides:
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override {
+ return new T(context);
+ }
+
+ // ExtensionSystemProvider overrides:
+ ExtensionSystem* GetForBrowserContext(
+ content::BrowserContext* context) override {
+ return static_cast<ExtensionSystem*>(
+ GetServiceForBrowserContext(context, true));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockExtensionSystemFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_MOCK_EXTENSION_SYSTEM_H_
diff --git a/chromium/extensions/browser/mojo/DEPS b/chromium/extensions/browser/mojo/DEPS
new file mode 100644
index 00000000000..2273ad20a57
--- /dev/null
+++ b/chromium/extensions/browser/mojo/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+mojo/message_pump",
+ "+mojo/shell/public/interfaces",
+]
diff --git a/chromium/extensions/browser/mojo/keep_alive_impl.cc b/chromium/extensions/browser/mojo/keep_alive_impl.cc
new file mode 100644
index 00000000000..8f175822132
--- /dev/null
+++ b/chromium/extensions/browser/mojo/keep_alive_impl.cc
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/mojo/keep_alive_impl.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/process_manager.h"
+
+namespace extensions {
+
+// static
+void KeepAliveImpl::Create(content::BrowserContext* context,
+ const Extension* extension,
+ mojo::InterfaceRequest<KeepAlive> request) {
+ new KeepAliveImpl(context, extension, std::move(request));
+}
+
+KeepAliveImpl::KeepAliveImpl(content::BrowserContext* context,
+ const Extension* extension,
+ mojo::InterfaceRequest<KeepAlive> request)
+ : context_(context),
+ extension_(extension),
+ extension_registry_observer_(this),
+ binding_(this, std::move(request)) {
+ ProcessManager::Get(context_)->IncrementLazyKeepaliveCount(extension_);
+ binding_.set_connection_error_handler(
+ base::Bind(&KeepAliveImpl::OnDisconnected, base::Unretained(this)));
+ extension_registry_observer_.Add(ExtensionRegistry::Get(context_));
+}
+
+KeepAliveImpl::~KeepAliveImpl() = default;
+
+void KeepAliveImpl::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ if (browser_context == context_ && extension == extension_)
+ delete this;
+}
+
+void KeepAliveImpl::OnShutdown(ExtensionRegistry* registry) {
+ delete this;
+}
+
+void KeepAliveImpl::OnDisconnected() {
+ ProcessManager::Get(context_)->DecrementLazyKeepaliveCount(extension_);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/mojo/keep_alive_impl.h b/chromium/extensions/browser/mojo/keep_alive_impl.h
new file mode 100644
index 00000000000..d9da48f783c
--- /dev/null
+++ b/chromium/extensions/browser/mojo/keep_alive_impl.h
@@ -0,0 +1,58 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_MOJO_KEEP_ALIVE_IMPL_H_
+#define EXTENSIONS_BROWSER_MOJO_KEEP_ALIVE_IMPL_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/scoped_observer.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/common/mojo/keep_alive.mojom.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+class Extension;
+
+// An RAII mojo service implementation for extension keep alives. This adds a
+// keep alive on construction and removes it on destruction.
+class KeepAliveImpl : public KeepAlive, public ExtensionRegistryObserver {
+ public:
+ // Create a keep alive for |extension| running in |context| and connect it to
+ // |request|. When the requester closes its pipe, the keep alive ends.
+ static void Create(content::BrowserContext* context,
+ const Extension* extension,
+ mojo::InterfaceRequest<KeepAlive> request);
+
+ private:
+ KeepAliveImpl(content::BrowserContext* context,
+ const Extension* extension,
+ mojo::InterfaceRequest<KeepAlive> request);
+ ~KeepAliveImpl() override;
+
+ // ExtensionRegistryObserver overrides.
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+ void OnShutdown(ExtensionRegistry* registry) override;
+
+ // Invoked when the mojo connection is disconnected.
+ void OnDisconnected();
+
+ content::BrowserContext* context_;
+ const Extension* extension_;
+ ScopedObserver<ExtensionRegistry, KeepAliveImpl> extension_registry_observer_;
+ mojo::StrongBinding<KeepAlive> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeepAliveImpl);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_MOJO_KEEP_ALIVE_IMPL_H_
diff --git a/chromium/extensions/browser/mojo/keep_alive_impl_unittest.cc b/chromium/extensions/browser/mojo/keep_alive_impl_unittest.cc
new file mode 100644
index 00000000000..5d640e055d3
--- /dev/null
+++ b/chromium/extensions/browser/mojo/keep_alive_impl_unittest.cc
@@ -0,0 +1,169 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/mojo/keep_alive_impl.h"
+
+#include <utility>
+
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "content/public/browser/notification_service.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extensions_test.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/common/extension_builder.h"
+
+namespace extensions {
+
+class KeepAliveTest : public ExtensionsTest {
+ public:
+ KeepAliveTest()
+ : notification_service_(content::NotificationService::Create()) {}
+ ~KeepAliveTest() override {}
+
+ void SetUp() override {
+ ExtensionsTest::SetUp();
+ message_loop_.reset(new base::MessageLoop);
+ extension_ =
+ ExtensionBuilder()
+ .SetManifest(
+ DictionaryBuilder()
+ .Set("name", "app")
+ .Set("version", "1")
+ .Set("manifest_version", 2)
+ .Set("app", DictionaryBuilder()
+ .Set("background",
+ DictionaryBuilder()
+ .Set("scripts",
+ ListBuilder()
+ .Append("background.js")
+ .Build())
+ .Build())
+ .Build())
+ .Build())
+ .SetID("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+ .Build();
+ }
+
+ void TearDown() override {
+ message_loop_.reset();
+ ExtensionsTest::TearDown();
+ }
+
+ void WaitUntilLazyKeepAliveChanges() {
+ int initial_keep_alive_count = GetKeepAliveCount();
+ while (GetKeepAliveCount() == initial_keep_alive_count) {
+ base::RunLoop().RunUntilIdle();
+ }
+ }
+
+ void CreateKeepAlive(mojo::InterfaceRequest<KeepAlive> request) {
+ KeepAliveImpl::Create(browser_context(), extension_.get(),
+ std::move(request));
+ }
+
+ const Extension* extension() { return extension_.get(); }
+
+ int GetKeepAliveCount() {
+ return ProcessManager::Get(browser_context())
+ ->GetLazyKeepaliveCount(extension());
+ }
+
+ private:
+ scoped_ptr<base::MessageLoop> message_loop_;
+ scoped_ptr<content::NotificationService> notification_service_;
+ scoped_refptr<const Extension> extension_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeepAliveTest);
+};
+
+TEST_F(KeepAliveTest, Basic) {
+ mojo::InterfacePtr<KeepAlive> keep_alive;
+ CreateKeepAlive(mojo::GetProxy(&keep_alive));
+ EXPECT_EQ(1, GetKeepAliveCount());
+
+ keep_alive.reset();
+ WaitUntilLazyKeepAliveChanges();
+ EXPECT_EQ(0, GetKeepAliveCount());
+}
+
+TEST_F(KeepAliveTest, TwoKeepAlives) {
+ mojo::InterfacePtr<KeepAlive> keep_alive;
+ CreateKeepAlive(mojo::GetProxy(&keep_alive));
+ EXPECT_EQ(1, GetKeepAliveCount());
+
+ mojo::InterfacePtr<KeepAlive> other_keep_alive;
+ CreateKeepAlive(mojo::GetProxy(&other_keep_alive));
+ EXPECT_EQ(2, GetKeepAliveCount());
+
+ keep_alive.reset();
+ WaitUntilLazyKeepAliveChanges();
+ EXPECT_EQ(1, GetKeepAliveCount());
+
+ other_keep_alive.reset();
+ WaitUntilLazyKeepAliveChanges();
+ EXPECT_EQ(0, GetKeepAliveCount());
+}
+
+TEST_F(KeepAliveTest, UnloadExtension) {
+ mojo::InterfacePtr<KeepAlive> keep_alive;
+ CreateKeepAlive(mojo::GetProxy(&keep_alive));
+ EXPECT_EQ(1, GetKeepAliveCount());
+
+ scoped_refptr<const Extension> other_extension =
+ ExtensionBuilder()
+ .SetManifest(
+ DictionaryBuilder()
+ .Set("name", "app")
+ .Set("version", "1")
+ .Set("manifest_version", 2)
+ .Set("app",
+ DictionaryBuilder()
+ .Set("background",
+ DictionaryBuilder()
+ .Set("scripts", ListBuilder()
+ .Append("background.js")
+ .Build())
+ .Build())
+ .Build())
+ .Build())
+ .SetID("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
+ .Build();
+
+ ExtensionRegistry::Get(browser_context())
+ ->TriggerOnUnloaded(other_extension.get(),
+ UnloadedExtensionInfo::REASON_DISABLE);
+ EXPECT_EQ(1, GetKeepAliveCount());
+
+ ExtensionRegistry::Get(browser_context())
+ ->TriggerOnUnloaded(extension(), UnloadedExtensionInfo::REASON_DISABLE);
+ // When its extension is unloaded, the KeepAliveImpl should not modify the
+ // keep-alive count for its extension. However, ProcessManager resets its
+ // keep-alive count for an unloaded extension.
+ EXPECT_EQ(0, GetKeepAliveCount());
+
+ // Wait for |keep_alive| to disconnect.
+ base::RunLoop run_loop;
+ keep_alive.set_connection_error_handler(run_loop.QuitClosure());
+ run_loop.Run();
+}
+
+TEST_F(KeepAliveTest, Shutdown) {
+ mojo::InterfacePtr<KeepAlive> keep_alive;
+ CreateKeepAlive(mojo::GetProxy(&keep_alive));
+ EXPECT_EQ(1, GetKeepAliveCount());
+
+ ExtensionRegistry::Get(browser_context())->Shutdown();
+ // After a shutdown event, the KeepAliveImpl should not access its
+ // ProcessManager and so the keep-alive count should remain unchanged.
+ EXPECT_EQ(1, GetKeepAliveCount());
+
+ // Wait for |keep_alive| to disconnect.
+ base::RunLoop run_loop;
+ keep_alive.set_connection_error_handler(run_loop.QuitClosure());
+ run_loop.Run();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/mojo/service_registration.cc b/chromium/extensions/browser/mojo/service_registration.cc
new file mode 100644
index 00000000000..c6367f070f9
--- /dev/null
+++ b/chromium/extensions/browser/mojo/service_registration.cc
@@ -0,0 +1,72 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/mojo/service_registration.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/site_instance.h"
+#include "content/public/common/service_registry.h"
+#include "extensions/browser/api/serial/serial_service_factory.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/mojo/keep_alive_impl.h"
+#include "extensions/browser/process_map.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension_api.h"
+#include "extensions/common/switches.h"
+
+#if defined(ENABLE_WIFI_DISPLAY)
+#include "extensions/browser/api/display_source/wifi_display/wifi_display_session_service_impl.h"
+#endif
+
+namespace extensions {
+namespace {
+
+bool ExtensionHasPermission(const Extension* extension,
+ content::RenderProcessHost* render_process_host,
+ const std::string& permission_name) {
+ Feature::Context context =
+ ProcessMap::Get(render_process_host->GetBrowserContext())
+ ->GetMostLikelyContextType(extension, render_process_host->GetID());
+
+ return ExtensionAPI::GetSharedInstance()
+ ->IsAvailable(permission_name, extension, context, extension->url())
+ .is_available();
+}
+
+} // namespace
+
+void RegisterServicesForFrame(content::RenderFrameHost* render_frame_host,
+ const Extension* extension) {
+ DCHECK(extension);
+
+ content::ServiceRegistry* service_registry =
+ render_frame_host->GetServiceRegistry();
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableMojoSerialService)) {
+ if (ExtensionHasPermission(extension, render_frame_host->GetProcess(),
+ "serial")) {
+ service_registry->AddService(base::Bind(&BindToSerialServiceRequest));
+ }
+ }
+ service_registry->AddService(base::Bind(
+ KeepAliveImpl::Create,
+ render_frame_host->GetProcess()->GetBrowserContext(), extension));
+
+#if defined(ENABLE_WIFI_DISPLAY)
+ if (ExtensionHasPermission(extension, render_frame_host->GetProcess(),
+ "displaySource")) {
+ service_registry->AddService(
+ base::Bind(WiFiDisplaySessionServiceImpl::BindToRequest,
+ render_frame_host->GetProcess()->GetBrowserContext()));
+ }
+#endif
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/mojo/service_registration.h b/chromium/extensions/browser/mojo/service_registration.h
new file mode 100644
index 00000000000..a371392b7b8
--- /dev/null
+++ b/chromium/extensions/browser/mojo/service_registration.h
@@ -0,0 +1,21 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_MOJO_SERVICE_REGISTRATION_H_
+#define EXTENSIONS_BROWSER_MOJO_SERVICE_REGISTRATION_H_
+
+namespace content {
+class RenderFrameHost;
+}
+
+namespace extensions {
+
+class Extension;
+
+void RegisterServicesForFrame(content::RenderFrameHost* render_frame_host,
+ const Extension* extension);
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_MOJO_SERVICE_REGISTRATION_H_
diff --git a/chromium/extensions/browser/mojo/stash_backend.cc b/chromium/extensions/browser/mojo/stash_backend.cc
new file mode 100644
index 00000000000..8d3eb40dbee
--- /dev/null
+++ b/chromium/extensions/browser/mojo/stash_backend.cc
@@ -0,0 +1,175 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/mojo/stash_backend.h"
+
+#include <stddef.h>
+
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "mojo/message_pump/handle_watcher.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+
+namespace extensions {
+namespace {
+
+// An implementation of StashService that forwards calls to a StashBackend.
+class StashServiceImpl : public StashService {
+ public:
+ StashServiceImpl(mojo::InterfaceRequest<StashService> request,
+ base::WeakPtr<StashBackend> backend);
+ ~StashServiceImpl() override;
+
+ // StashService overrides.
+ void AddToStash(mojo::Array<StashedObjectPtr> stash) override;
+ void RetrieveStash(
+ const mojo::Callback<void(mojo::Array<StashedObjectPtr> stash)>& callback)
+ override;
+
+ private:
+ mojo::StrongBinding<StashService> binding_;
+ base::WeakPtr<StashBackend> backend_;
+
+ DISALLOW_COPY_AND_ASSIGN(StashServiceImpl);
+};
+
+StashServiceImpl::StashServiceImpl(mojo::InterfaceRequest<StashService> request,
+ base::WeakPtr<StashBackend> backend)
+ : binding_(this, std::move(request)), backend_(backend) {}
+
+StashServiceImpl::~StashServiceImpl() {
+}
+
+void StashServiceImpl::AddToStash(
+ mojo::Array<StashedObjectPtr> stashed_objects) {
+ if (!backend_)
+ return;
+ backend_->AddToStash(std::move(stashed_objects));
+}
+
+void StashServiceImpl::RetrieveStash(
+ const mojo::Callback<void(mojo::Array<StashedObjectPtr>)>& callback) {
+ if (!backend_) {
+ callback.Run(mojo::Array<StashedObjectPtr>());
+ return;
+ }
+ callback.Run(backend_->RetrieveStash());
+}
+
+} // namespace
+
+// A stash entry for a stashed object. This handles notifications if a handle
+// within the stashed object is readable.
+class StashBackend::StashEntry {
+ public:
+ // Construct a StashEntry for |stashed_object|. If |on_handle_readable| is
+ // non-null, it will be invoked when any handle on |stashed_object| is
+ // readable.
+ StashEntry(StashedObjectPtr stashed_object,
+ const base::Closure& on_handle_readable);
+ ~StashEntry();
+
+ // Returns the stashed object.
+ StashedObjectPtr Release();
+
+ // Cancels notifications for handles becoming readable.
+ void CancelHandleNotifications();
+
+ private:
+ // Invoked when a handle within |stashed_object_| is readable.
+ void OnHandleReady(MojoResult result);
+
+ // The waiters that are waiting for handles to be readable.
+ std::vector<scoped_ptr<mojo::common::HandleWatcher>> waiters_;
+
+ StashedObjectPtr stashed_object_;
+
+ // If non-null, a callback to call when a handle contained within
+ // |stashed_object_| is readable.
+ const base::Closure on_handle_readable_;
+};
+
+StashBackend::StashBackend(const base::Closure& on_handle_readable)
+ : on_handle_readable_(on_handle_readable),
+ has_notified_(false),
+ weak_factory_(this) {
+}
+
+StashBackend::~StashBackend() {
+}
+
+void StashBackend::AddToStash(mojo::Array<StashedObjectPtr> stashed_objects) {
+ for (size_t i = 0; i < stashed_objects.size(); i++) {
+ stashed_objects_.push_back(make_scoped_ptr(new StashEntry(
+ std::move(stashed_objects[i]),
+ has_notified_ ? base::Closure()
+ : base::Bind(&StashBackend::OnHandleReady,
+ weak_factory_.GetWeakPtr()))));
+ }
+}
+
+mojo::Array<StashedObjectPtr> StashBackend::RetrieveStash() {
+ has_notified_ = false;
+ mojo::Array<StashedObjectPtr> result;
+ for (auto& entry : stashed_objects_) {
+ result.push_back(entry->Release());
+ }
+ stashed_objects_.clear();
+ return result;
+}
+
+void StashBackend::BindToRequest(mojo::InterfaceRequest<StashService> request) {
+ new StashServiceImpl(std::move(request), weak_factory_.GetWeakPtr());
+}
+
+void StashBackend::OnHandleReady() {
+ DCHECK(!has_notified_);
+ has_notified_ = true;
+ for (auto& entry : stashed_objects_) {
+ entry->CancelHandleNotifications();
+ }
+ if (!on_handle_readable_.is_null())
+ on_handle_readable_.Run();
+}
+
+StashBackend::StashEntry::StashEntry(StashedObjectPtr stashed_object,
+ const base::Closure& on_handle_readable)
+ : stashed_object_(std::move(stashed_object)),
+ on_handle_readable_(on_handle_readable) {
+ if (on_handle_readable_.is_null() || !stashed_object_->monitor_handles)
+ return;
+
+ for (size_t i = 0; i < stashed_object_->stashed_handles.size(); i++) {
+ scoped_ptr<mojo::common::HandleWatcher> watcher(
+ new mojo::common::HandleWatcher());
+ watcher->Start(stashed_object_->stashed_handles[i].get(),
+ MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE,
+ base::Bind(&StashBackend::StashEntry::OnHandleReady,
+ base::Unretained(this)));
+ waiters_.push_back(std::move(watcher));
+ }
+}
+
+StashBackend::StashEntry::~StashEntry() {
+}
+
+StashedObjectPtr StashBackend::StashEntry::Release() {
+ waiters_.clear();
+ return std::move(stashed_object_);
+}
+
+void StashBackend::StashEntry::CancelHandleNotifications() {
+ waiters_.clear();
+}
+
+void StashBackend::StashEntry::OnHandleReady(MojoResult result) {
+ if (result != MOJO_RESULT_OK)
+ return;
+ on_handle_readable_.Run();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/mojo/stash_backend.h b/chromium/extensions/browser/mojo/stash_backend.h
new file mode 100644
index 00000000000..f374e00f957
--- /dev/null
+++ b/chromium/extensions/browser/mojo/stash_backend.h
@@ -0,0 +1,57 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_MOJO_STASH_BACKEND_H_
+#define EXTENSIONS_BROWSER_MOJO_STASH_BACKEND_H_
+
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "extensions/common/mojo/stash.mojom.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+
+namespace extensions {
+
+// A backend that provides access to StashService for a single extension.
+class StashBackend {
+ public:
+ explicit StashBackend(const base::Closure& on_handle_readable);
+ ~StashBackend();
+
+ // Creates a StashService that forwards calls to this StashBackend and bind it
+ // to |request|.
+ void BindToRequest(mojo::InterfaceRequest<StashService> request);
+
+ // Adds the StashedObjects contained within |stash| to the stash.
+ void AddToStash(mojo::Array<StashedObjectPtr> stash);
+
+ // Returns all StashedObjects added to the stash since the last call to
+ // RetrieveStash.
+ mojo::Array<StashedObjectPtr> RetrieveStash();
+
+ private:
+ class StashEntry;
+
+ // Invoked when a handle is readable.
+ void OnHandleReady();
+
+ // The objects that have been stashed.
+ std::vector<scoped_ptr<StashEntry>> stashed_objects_;
+
+ // The callback to call when a handle is readable.
+ const base::Closure on_handle_readable_;
+
+ // Whether a handle has become readable since the last RetrieveStash() call.
+ bool has_notified_;
+
+ base::WeakPtrFactory<StashBackend> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(StashBackend);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_MOJO_STASH_BACKEND_H_
diff --git a/chromium/extensions/browser/mojo/stash_backend_unittest.cc b/chromium/extensions/browser/mojo/stash_backend_unittest.cc
new file mode 100644
index 00000000000..cf69f98c43b
--- /dev/null
+++ b/chromium/extensions/browser/mojo/stash_backend_unittest.cc
@@ -0,0 +1,301 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/mojo/stash_backend.h"
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "mojo/shell/public/interfaces/interface_provider.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+namespace {
+
+// Create a data pipe, write some data to the producer handle and return the
+// consumer handle.
+mojo::ScopedHandle CreateReadableHandle() {
+ mojo::ScopedDataPipeConsumerHandle consumer_handle;
+ mojo::ScopedDataPipeProducerHandle producer_handle;
+ MojoCreateDataPipeOptions options = {
+ sizeof(options), MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, 1, 1,
+ };
+ MojoResult result =
+ mojo::CreateDataPipe(&options, &producer_handle, &consumer_handle);
+ EXPECT_EQ(MOJO_RESULT_OK, result);
+ uint32_t num_bytes = 1;
+ result = mojo::WriteDataRaw(producer_handle.get(), "a", &num_bytes,
+ MOJO_WRITE_DATA_FLAG_NONE);
+ EXPECT_EQ(MOJO_RESULT_OK, result);
+ EXPECT_EQ(1u, num_bytes);
+ return mojo::ScopedHandle::From(std::move(consumer_handle));
+}
+
+} // namespace
+
+class StashServiceTest : public testing::Test {
+ public:
+ enum Event {
+ EVENT_NONE,
+ EVENT_STASH_RETRIEVED,
+ EVENT_HANDLE_READY,
+ };
+
+ StashServiceTest() {}
+
+ void SetUp() override {
+ expecting_error_ = false;
+ expected_event_ = EVENT_NONE;
+ stash_backend_.reset(new StashBackend(base::Bind(
+ &StashServiceTest::OnHandleReadyToRead, base::Unretained(this))));
+ stash_backend_->BindToRequest(mojo::GetProxy(&stash_service_));
+ stash_service_.set_connection_error_handler(base::Bind(&OnConnectionError));
+ handles_ready_ = 0;
+ }
+
+ static void OnConnectionError() { FAIL() << "Unexpected connection error"; }
+
+ mojo::Array<StashedObjectPtr> RetrieveStash() {
+ mojo::Array<StashedObjectPtr> stash;
+ stash_service_->RetrieveStash(base::Bind(
+ &StashServiceTest::StashRetrieved, base::Unretained(this), &stash));
+ WaitForEvent(EVENT_STASH_RETRIEVED);
+ return stash;
+ }
+
+ void StashRetrieved(mojo::Array<StashedObjectPtr>* output,
+ mojo::Array<StashedObjectPtr> stash) {
+ *output = std::move(stash);
+ EventReceived(EVENT_STASH_RETRIEVED);
+ }
+
+ void WaitForEvent(Event event) {
+ expected_event_ = event;
+ base::RunLoop run_loop;
+ stop_run_loop_ = run_loop.QuitClosure();
+ run_loop.Run();
+ }
+
+ void EventReceived(Event event) {
+ if (event == expected_event_ && !stop_run_loop_.is_null())
+ stop_run_loop_.Run();
+ }
+
+ void OnHandleReadyToRead() {
+ handles_ready_++;
+ EventReceived(EVENT_HANDLE_READY);
+ }
+
+ protected:
+ base::MessageLoop message_loop_;
+ base::Closure stop_run_loop_;
+ scoped_ptr<StashBackend> stash_backend_;
+ Event expected_event_;
+ bool expecting_error_;
+ mojo::InterfacePtr<StashService> stash_service_;
+ int handles_ready_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StashServiceTest);
+};
+
+// Test that adding stashed objects in multiple calls can all be retrieved by a
+// Retrieve call.
+TEST_F(StashServiceTest, AddTwiceAndRetrieve) {
+ mojo::Array<StashedObjectPtr> stashed_objects;
+ StashedObjectPtr stashed_object(StashedObject::New());
+ stashed_object->id = "test type";
+ stashed_object->data.push_back(1);
+ stashed_object->stashed_handles = mojo::Array<mojo::ScopedHandle>();
+ stashed_objects.push_back(std::move(stashed_object));
+ stash_service_->AddToStash(std::move(stashed_objects));
+ stashed_object = StashedObject::New();
+ stashed_object->id = "test type2";
+ stashed_object->data.push_back(2);
+ stashed_object->data.push_back(3);
+ stashed_object->stashed_handles = mojo::Array<mojo::ScopedHandle>();
+ stashed_objects.push_back(std::move(stashed_object));
+ stash_service_->AddToStash(std::move(stashed_objects));
+ stashed_objects = RetrieveStash();
+ ASSERT_EQ(2u, stashed_objects.size());
+ EXPECT_EQ("test type", stashed_objects[0]->id);
+ EXPECT_EQ(0u, stashed_objects[0]->stashed_handles.size());
+ EXPECT_EQ(1u, stashed_objects[0]->data.size());
+ EXPECT_EQ(1, stashed_objects[0]->data[0]);
+ EXPECT_EQ("test type2", stashed_objects[1]->id);
+ EXPECT_EQ(0u, stashed_objects[1]->stashed_handles.size());
+ EXPECT_EQ(2u, stashed_objects[1]->data.size());
+ EXPECT_EQ(2, stashed_objects[1]->data[0]);
+ EXPECT_EQ(3, stashed_objects[1]->data[1]);
+}
+
+// Test that handles survive a round-trip through the stash.
+TEST_F(StashServiceTest, StashAndRetrieveHandles) {
+ mojo::Array<StashedObjectPtr> stashed_objects;
+ StashedObjectPtr stashed_object(StashedObject::New());
+ stashed_object->id = "test type";
+ stashed_object->data.push_back(1);
+
+ mojo::ScopedDataPipeConsumerHandle consumer;
+ mojo::ScopedDataPipeProducerHandle producer;
+ MojoCreateDataPipeOptions options = {
+ sizeof(options), MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, 1, 1,
+ };
+ mojo::CreateDataPipe(&options, &producer, &consumer);
+ uint32_t num_bytes = 1;
+ MojoResult result = mojo::WriteDataRaw(
+ producer.get(), "1", &num_bytes, MOJO_WRITE_DATA_FLAG_ALL_OR_NONE);
+ ASSERT_EQ(MOJO_RESULT_OK, result);
+ ASSERT_EQ(1u, num_bytes);
+
+ stashed_object->stashed_handles.push_back(
+ mojo::ScopedHandle::From(std::move(producer)));
+ stashed_object->stashed_handles.push_back(
+ mojo::ScopedHandle::From(std::move(consumer)));
+ stashed_objects.push_back(std::move(stashed_object));
+ stash_service_->AddToStash(std::move(stashed_objects));
+ stashed_objects = RetrieveStash();
+ ASSERT_EQ(1u, stashed_objects.size());
+ EXPECT_EQ("test type", stashed_objects[0]->id);
+ ASSERT_EQ(2u, stashed_objects[0]->stashed_handles.size());
+
+ consumer = mojo::ScopedDataPipeConsumerHandle::From(
+ std::move(stashed_objects[0]->stashed_handles[1]));
+ result = mojo::Wait(
+ consumer.get(), MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE,
+ nullptr);
+ ASSERT_EQ(MOJO_RESULT_OK, result);
+ char data = '\0';
+ result = mojo::ReadDataRaw(
+ consumer.get(), &data, &num_bytes, MOJO_READ_DATA_FLAG_ALL_OR_NONE);
+ ASSERT_EQ(MOJO_RESULT_OK, result);
+ ASSERT_EQ(1u, num_bytes);
+ EXPECT_EQ('1', data);
+}
+
+TEST_F(StashServiceTest, RetrieveWithoutStashing) {
+ mojo::Array<StashedObjectPtr> stashed_objects = RetrieveStash();
+ ASSERT_TRUE(!stashed_objects.is_null());
+ EXPECT_EQ(0u, stashed_objects.size());
+}
+
+TEST_F(StashServiceTest, NotifyOnReadableHandle) {
+ mojo::Array<StashedObjectPtr> stash_entries;
+ StashedObjectPtr stashed_object(StashedObject::New());
+ stashed_object->id = "test type";
+ stashed_object->data.push_back(0);
+ stashed_object->monitor_handles = true;
+ mojo::shell::mojom::InterfaceProviderPtr service_provider;
+
+ // Stash the ServiceProvider request. When we make a call on
+ // |service_provider|, the stashed handle will become readable.
+ stashed_object->stashed_handles.push_back(mojo::ScopedHandle::From(
+ mojo::GetProxy(&service_provider).PassMessagePipe()));
+
+ stash_entries.push_back(std::move(stashed_object));
+ stash_service_->AddToStash(std::move(stash_entries));
+
+ mojo::MessagePipe pipe;
+ service_provider->GetInterface("", std::move(pipe.handle0));
+
+ WaitForEvent(EVENT_HANDLE_READY);
+ EXPECT_EQ(1, handles_ready_);
+}
+
+TEST_F(StashServiceTest, NotifyOnReadableDataPipeHandle) {
+ mojo::Array<StashedObjectPtr> stash_entries;
+ StashedObjectPtr stashed_object(StashedObject::New());
+ stashed_object->id = "test type";
+ stashed_object->monitor_handles = true;
+
+ MojoCreateDataPipeOptions options = {
+ sizeof(options), MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, 1, 1,
+ };
+ mojo::ScopedDataPipeConsumerHandle consumer_handle;
+ mojo::ScopedDataPipeProducerHandle producer_handle;
+ uint32_t num_bytes = 1;
+ MojoResult result =
+ mojo::CreateDataPipe(&options, &producer_handle, &consumer_handle);
+ ASSERT_EQ(MOJO_RESULT_OK, result);
+ result = mojo::WriteDataRaw(producer_handle.get(), "a", &num_bytes,
+ MOJO_WRITE_DATA_FLAG_NONE);
+ ASSERT_EQ(MOJO_RESULT_OK, result);
+ ASSERT_EQ(1u, num_bytes);
+ stashed_object->stashed_handles.push_back(
+ mojo::ScopedHandle::From(std::move(producer_handle)));
+ stashed_object->stashed_handles.push_back(
+ mojo::ScopedHandle::From(std::move(consumer_handle)));
+ stashed_object->data.push_back(1);
+
+ stash_entries.push_back(std::move(stashed_object));
+ stash_service_->AddToStash(std::move(stash_entries));
+ WaitForEvent(EVENT_HANDLE_READY);
+ EXPECT_EQ(1, handles_ready_);
+}
+
+TEST_F(StashServiceTest, NotifyOncePerStashOnReadableHandles) {
+ mojo::Array<StashedObjectPtr> stash_entries;
+ StashedObjectPtr stashed_object(StashedObject::New());
+ stashed_object->id = "test type";
+ stashed_object->data.push_back(1);
+ stashed_object->monitor_handles = true;
+ stashed_object->stashed_handles.push_back(CreateReadableHandle());
+ stashed_object->stashed_handles.push_back(CreateReadableHandle());
+ stash_entries.push_back(std::move(stashed_object));
+ stashed_object = StashedObject::New();
+ stashed_object->id = "another test type";
+ stashed_object->data.push_back(2);
+ stashed_object->monitor_handles = true;
+ stashed_object->stashed_handles.push_back(CreateReadableHandle());
+ stashed_object->stashed_handles.push_back(CreateReadableHandle());
+ stash_entries.push_back(std::move(stashed_object));
+ stash_service_->AddToStash(std::move(stash_entries));
+ WaitForEvent(EVENT_HANDLE_READY);
+ EXPECT_EQ(1, handles_ready_);
+
+ stashed_object = StashedObject::New();
+ stashed_object->id = "yet another test type";
+ stashed_object->data.push_back(3);
+ stashed_object->monitor_handles = true;
+ stashed_object->stashed_handles.push_back(CreateReadableHandle());
+ stashed_object->stashed_handles.push_back(CreateReadableHandle());
+ stash_entries.push_back(std::move(stashed_object));
+ stash_service_->AddToStash(std::move(stash_entries));
+
+ stash_service_->AddToStash(RetrieveStash());
+ WaitForEvent(EVENT_HANDLE_READY);
+ EXPECT_EQ(2, handles_ready_);
+}
+
+// Test that a stash service discards stashed objects when the backend no longer
+// exists.
+TEST_F(StashServiceTest, ServiceWithDeletedBackend) {
+ stash_backend_.reset();
+ stash_service_.set_connection_error_handler(base::Bind(&OnConnectionError));
+
+ mojo::Array<StashedObjectPtr> stashed_objects;
+ StashedObjectPtr stashed_object(StashedObject::New());
+ stashed_object->id = "test type";
+ stashed_object->data.push_back(1);
+ mojo::MessagePipe message_pipe;
+ stashed_object->stashed_handles.push_back(
+ mojo::ScopedHandle::From(std::move(message_pipe.handle0)));
+ stashed_objects.push_back(std::move(stashed_object));
+ stash_service_->AddToStash(std::move(stashed_objects));
+ stashed_objects = RetrieveStash();
+ ASSERT_EQ(0u, stashed_objects.size());
+ // Check that the stashed handle has been closed.
+ MojoResult result =
+ mojo::Wait(message_pipe.handle1.get(),
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, nullptr);
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/null_app_sorting.cc b/chromium/extensions/browser/null_app_sorting.cc
new file mode 100644
index 00000000000..6161d5d81dc
--- /dev/null
+++ b/chromium/extensions/browser/null_app_sorting.cc
@@ -0,0 +1,95 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/null_app_sorting.h"
+
+#include "sync/api/string_ordinal.h"
+
+namespace {
+
+// Ordinals for a single app on a single page.
+const char kFirstApp[] = "a";
+const char kNextApp[] = "b";
+const char kFirstPage[] = "a";
+
+} // namespace
+
+namespace extensions {
+
+NullAppSorting::NullAppSorting() {
+}
+
+NullAppSorting::~NullAppSorting() {
+}
+
+void NullAppSorting::FixNTPOrdinalCollisions() {
+}
+
+void NullAppSorting::EnsureValidOrdinals(
+ const std::string& extension_id,
+ const syncer::StringOrdinal& suggested_page) {
+}
+
+void NullAppSorting::OnExtensionMoved(
+ const std::string& moved_extension_id,
+ const std::string& predecessor_extension_id,
+ const std::string& successor_extension_id) {
+}
+
+syncer::StringOrdinal NullAppSorting::GetAppLaunchOrdinal(
+ const std::string& extension_id) const {
+ return syncer::StringOrdinal(kFirstApp);
+}
+
+void NullAppSorting::SetAppLaunchOrdinal(
+ const std::string& extension_id,
+ const syncer::StringOrdinal& new_app_launch_ordinal) {
+}
+
+syncer::StringOrdinal NullAppSorting::CreateFirstAppLaunchOrdinal(
+ const syncer::StringOrdinal& page_ordinal) const {
+ return syncer::StringOrdinal(kFirstApp);
+}
+
+syncer::StringOrdinal NullAppSorting::CreateNextAppLaunchOrdinal(
+ const syncer::StringOrdinal& page_ordinal) const {
+ return syncer::StringOrdinal(kNextApp);
+}
+
+syncer::StringOrdinal NullAppSorting::CreateFirstAppPageOrdinal() const {
+ return syncer::StringOrdinal(kFirstPage);
+}
+
+syncer::StringOrdinal NullAppSorting::GetNaturalAppPageOrdinal() const {
+ return syncer::StringOrdinal(kFirstPage);
+}
+
+syncer::StringOrdinal NullAppSorting::GetPageOrdinal(
+ const std::string& extension_id) const {
+ return syncer::StringOrdinal(kFirstPage);
+}
+
+void NullAppSorting::SetPageOrdinal(
+ const std::string& extension_id,
+ const syncer::StringOrdinal& new_page_ordinal) {
+}
+
+void NullAppSorting::ClearOrdinals(const std::string& extension_id) {
+}
+
+int NullAppSorting::PageStringOrdinalAsInteger(
+ const syncer::StringOrdinal& page_ordinal) const {
+ return 0;
+}
+
+syncer::StringOrdinal NullAppSorting::PageIntegerAsStringOrdinal(
+ size_t page_index) {
+ return syncer::StringOrdinal(kFirstPage);
+}
+
+void NullAppSorting::SetExtensionVisible(const std::string& extension_id,
+ bool visible) {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/null_app_sorting.h b/chromium/extensions/browser/null_app_sorting.h
new file mode 100644
index 00000000000..b23af1f9f8a
--- /dev/null
+++ b/chromium/extensions/browser/null_app_sorting.h
@@ -0,0 +1,58 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_NULL_APP_SORTING_H_
+#define EXTENSIONS_BROWSER_NULL_APP_SORTING_H_
+
+#include <stddef.h>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "extensions/browser/app_sorting.h"
+
+namespace extensions {
+
+// An AppSorting that doesn't provide any ordering.
+class NullAppSorting : public AppSorting {
+ public:
+ NullAppSorting();
+ ~NullAppSorting() override;
+
+ // AppSorting overrides:
+ void FixNTPOrdinalCollisions() override;
+ void EnsureValidOrdinals(
+ const std::string& extension_id,
+ const syncer::StringOrdinal& suggested_page) override;
+ void OnExtensionMoved(const std::string& moved_extension_id,
+ const std::string& predecessor_extension_id,
+ const std::string& successor_extension_id) override;
+ syncer::StringOrdinal GetAppLaunchOrdinal(
+ const std::string& extension_id) const override;
+ void SetAppLaunchOrdinal(
+ const std::string& extension_id,
+ const syncer::StringOrdinal& new_app_launch_ordinal) override;
+ syncer::StringOrdinal CreateFirstAppLaunchOrdinal(
+ const syncer::StringOrdinal& page_ordinal) const override;
+ syncer::StringOrdinal CreateNextAppLaunchOrdinal(
+ const syncer::StringOrdinal& page_ordinal) const override;
+ syncer::StringOrdinal CreateFirstAppPageOrdinal() const override;
+ syncer::StringOrdinal GetNaturalAppPageOrdinal() const override;
+ syncer::StringOrdinal GetPageOrdinal(
+ const std::string& extension_id) const override;
+ void SetPageOrdinal(const std::string& extension_id,
+ const syncer::StringOrdinal& new_page_ordinal) override;
+ void ClearOrdinals(const std::string& extension_id) override;
+ int PageStringOrdinalAsInteger(
+ const syncer::StringOrdinal& page_ordinal) const override;
+ syncer::StringOrdinal PageIntegerAsStringOrdinal(size_t page_index) override;
+ void SetExtensionVisible(const std::string& extension_id,
+ bool visible) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NullAppSorting);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_NULL_APP_SORTING_H_
diff --git a/chromium/extensions/browser/pref_names.cc b/chromium/extensions/browser/pref_names.cc
new file mode 100644
index 00000000000..29c3945a992
--- /dev/null
+++ b/chromium/extensions/browser/pref_names.cc
@@ -0,0 +1,59 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/pref_names.h"
+
+#include "base/logging.h"
+
+namespace extensions {
+namespace pref_names {
+
+bool ScopeToPrefName(ExtensionPrefsScope scope, std::string* result) {
+ switch (scope) {
+ case kExtensionPrefsScopeRegular:
+ *result = kPrefPreferences;
+ return true;
+ case kExtensionPrefsScopeRegularOnly:
+ *result = kPrefRegularOnlyPreferences;
+ return true;
+ case kExtensionPrefsScopeIncognitoPersistent:
+ *result = kPrefIncognitoPreferences;
+ return true;
+ case kExtensionPrefsScopeIncognitoSessionOnly:
+ return false;
+ }
+ NOTREACHED();
+ return false;
+}
+
+const char kAlertsInitialized[] = "extensions.alerts.initialized";
+const char kAllowedInstallSites[] = "extensions.allowed_install_sites";
+const char kAllowedTypes[] = "extensions.allowed_types";
+const char kAppFullscreenAllowed[] = "apps.fullscreen.allowed";
+const char kBookmarkAppCreationLaunchType[] =
+ "extensions.bookmark_app_creation_launch_type";
+const char kExtensions[] = "extensions.settings";
+const char kExtensionManagement[] = "extensions.management";
+const char kInstallAllowList[] = "extensions.install.allowlist";
+const char kInstallDenyList[] = "extensions.install.denylist";
+const char kInstallForceList[] = "extensions.install.forcelist";
+const char kLastChromeVersion[] = "extensions.last_chrome_version";
+const char kLastUpdateCheck[] = "extensions.autoupdate.last_check";
+const char kNativeMessagingBlacklist[] = "native_messaging.blacklist";
+const char kNativeMessagingWhitelist[] = "native_messaging.whitelist";
+const char kNativeMessagingUserLevelHosts[] =
+ "native_messaging.user_level_hosts";
+const char kNextUpdateCheck[] = "extensions.autoupdate.next_check";
+const char kStorageGarbageCollect[] = "extensions.storage.garbagecollect";
+const char kToolbar[] = "extensions.toolbar";
+const char kToolbarSize[] = "extensions.toolbarsize";
+
+const char kPrefPreferences[] = "preferences";
+const char kPrefIncognitoPreferences[] = "incognito_preferences";
+const char kPrefRegularOnlyPreferences[] = "regular_only_preferences";
+const char kPrefContentSettings[] = "content_settings";
+const char kPrefIncognitoContentSettings[] = "incognito_content_settings";
+
+} // namespace pref_names
+} // namespace extensions
diff --git a/chromium/extensions/browser/pref_names.h b/chromium/extensions/browser/pref_names.h
new file mode 100644
index 00000000000..e221c35cc97
--- /dev/null
+++ b/chromium/extensions/browser/pref_names.h
@@ -0,0 +1,120 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_PREF_NAMES_H_
+#define EXTENSIONS_BROWSER_PREF_NAMES_H_
+
+#include <string>
+
+#include "extensions/browser/extension_prefs_scope.h"
+
+// Preference keys which are needed by both the ExtensionPrefs and by external
+// clients, such as APIs.
+
+namespace extensions {
+namespace pref_names {
+
+// If the given |scope| is persisted, return true and populate |result| with the
+// appropriate property (i.e. one of kPref*) within a kExtensions dictionary. If
+// |scope| is not persisted, return false, and leave |result| unchanged.
+bool ScopeToPrefName(ExtensionPrefsScope scope, std::string* result);
+
+// Browser-level preferences ---------------------------------------------------
+
+// Whether we have run the extension-alert system (see ExtensionGlobalError)
+// at least once for this profile.
+extern const char kAlertsInitialized[];
+
+// The sites that are allowed to install extensions. These sites should be
+// allowed to install extensions without the scary dangerous downloads bar.
+// Also, when off-store-extension installs are disabled, these sites are exempt.
+extern const char kAllowedInstallSites[];
+
+// A list of allowed extension types. Extensions can only be installed if their
+// type is on this whitelist or alternatively on kInstallAllowList or
+// kInstallForceList.
+extern const char kAllowedTypes[];
+
+// A boolean that tracks whether apps are allowed to enter fullscreen mode.
+extern const char kAppFullscreenAllowed[];
+
+// Integer which specifies the launch type that bookmark apps are created with
+// by default.
+extern const char kBookmarkAppCreationLaunchType[];
+
+// Dictionary pref that keeps track of per-extension settings. The keys are
+// extension ids.
+extern const char kExtensions[];
+
+// Dictionary pref that manages extensions, controlled by policy.
+// Values are expected to conform to the schema of the ExtensionManagement
+// policy.
+extern const char kExtensionManagement[];
+
+// A whitelist of extension ids the user can install: exceptions from the
+// following blacklist.
+extern const char kInstallAllowList[];
+
+// A blacklist, containing extensions the user cannot install. This list can
+// contain "*" meaning all extensions. This list should not be confused with the
+// extension blacklist, which is Google controlled.
+extern const char kInstallDenyList[];
+
+// A list containing extensions that Chrome will silently install
+// at startup time. It is a list of strings, each string contains
+// an extension ID and an update URL, delimited by a semicolon.
+// This preference is set by an admin policy, and meant to be only
+// accessed through extensions::ExternalPolicyProvider.
+extern const char kInstallForceList[];
+
+// String pref for what version chrome was last time the extension prefs were
+// loaded.
+extern const char kLastChromeVersion[];
+
+// Time of the last extensions auto-update check.
+extern const char kLastUpdateCheck[];
+
+// Blacklist and whitelist for Native Messaging Hosts.
+extern const char kNativeMessagingBlacklist[];
+extern const char kNativeMessagingWhitelist[];
+
+// Flag allowing usage of Native Messaging hosts installed on user level.
+extern const char kNativeMessagingUserLevelHosts[];
+
+// Time of the next scheduled extensions auto-update checks.
+extern const char kNextUpdateCheck[];
+
+// Indicates on-disk data might have skeletal data that needs to be cleaned
+// on the next start of the browser.
+extern const char kStorageGarbageCollect[];
+
+// A preference that tracks browser action toolbar configuration. This is a list
+// object stored in the Preferences file. The extensions are stored by ID.
+extern const char kToolbar[];
+
+// Integer pref that tracks the number of browser actions visible in the browser
+// actions toolbar.
+extern const char kToolbarSize[];
+
+// Properties in kExtensions dictionaries --------------------------------------
+
+// Extension-controlled preferences.
+extern const char kPrefPreferences[];
+
+// Extension-controlled incognito preferences.
+extern const char kPrefIncognitoPreferences[];
+
+// Extension-controlled regular-only preferences.
+extern const char kPrefRegularOnlyPreferences[];
+
+// Extension-set content settings.
+extern const char kPrefContentSettings[];
+
+// Extension-set incognito content settings.
+extern const char kPrefIncognitoContentSettings[];
+
+} // namespace pref_names
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_PREF_NAMES_H_
diff --git a/chromium/extensions/browser/process_manager.cc b/chromium/extensions/browser/process_manager.cc
new file mode 100644
index 00000000000..a71b4c9d89f
--- /dev/null
+++ b/chromium/extensions/browser/process_manager.cc
@@ -0,0 +1,986 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/process_manager.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/stl_util.h"
+#include "base/time/time.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/devtools_agent_host.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/site_instance.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/url_constants.h"
+#include "extensions/browser/extension_host.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/lazy_background_task_queue.h"
+#include "extensions/browser/notification_types.h"
+#include "extensions/browser/process_manager_delegate.h"
+#include "extensions/browser/process_manager_factory.h"
+#include "extensions/browser/process_manager_observer.h"
+#include "extensions/browser/view_type_utils.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/manifest_handlers/background_info.h"
+#include "extensions/common/manifest_handlers/incognito_info.h"
+#include "extensions/common/one_shot_event.h"
+
+using content::BrowserContext;
+
+namespace extensions {
+
+namespace {
+
+// The time to delay between an extension becoming idle and
+// sending a ShouldSuspend message.
+// Note: Must be sufficiently larger (e.g. 2x) than
+// kKeepaliveThrottleIntervalInSeconds in ppapi/proxy/plugin_globals.
+unsigned g_event_page_idle_time_msec = 10000;
+
+// The time to delay between sending a ShouldSuspend message and
+// sending a Suspend message.
+unsigned g_event_page_suspending_time_msec = 5000;
+
+std::string GetExtensionIdForSiteInstance(
+ content::SiteInstance* site_instance) {
+ if (!site_instance)
+ return std::string();
+
+ // This works for both apps and extensions because the site has been
+ // normalized to the extension URL for hosted apps.
+ const GURL& site_url = site_instance->GetSiteURL();
+
+ if (!site_url.SchemeIs(kExtensionScheme) &&
+ !site_url.SchemeIs(content::kGuestScheme))
+ return std::string();
+
+ return site_url.host();
+}
+
+std::string GetExtensionID(content::RenderFrameHost* render_frame_host) {
+ CHECK(render_frame_host);
+ return GetExtensionIdForSiteInstance(render_frame_host->GetSiteInstance());
+}
+
+bool IsFrameInExtensionHost(ExtensionHost* extension_host,
+ content::RenderFrameHost* render_frame_host) {
+ return content::WebContents::FromRenderFrameHost(render_frame_host) ==
+ extension_host->host_contents();
+}
+
+// Incognito profiles use this process manager. It is mostly a shim that decides
+// whether to fall back on the original profile's ProcessManager based
+// on whether a given extension uses "split" or "spanning" incognito behavior.
+// TODO(devlin): Given how little this does and the amount of cruft it adds to
+// the .h file (in the form of protected members), we should consider just
+// moving the incognito logic into the base class.
+class IncognitoProcessManager : public ProcessManager {
+ public:
+ IncognitoProcessManager(BrowserContext* incognito_context,
+ BrowserContext* original_context,
+ ExtensionRegistry* extension_registry);
+ ~IncognitoProcessManager() override {}
+ bool CreateBackgroundHost(const Extension* extension,
+ const GURL& url) override;
+ scoped_refptr<content::SiteInstance> GetSiteInstanceForURL(const GURL& url)
+ override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(IncognitoProcessManager);
+};
+
+static void CreateBackgroundHostForExtensionLoad(
+ ProcessManager* manager, const Extension* extension) {
+ DVLOG(1) << "CreateBackgroundHostForExtensionLoad";
+ if (BackgroundInfo::HasPersistentBackgroundPage(extension))
+ manager->CreateBackgroundHost(extension,
+ BackgroundInfo::GetBackgroundURL(extension));
+}
+
+void PropagateExtensionWakeResult(const base::Callback<void(bool)>& callback,
+ extensions::ExtensionHost* host) {
+ callback.Run(host != nullptr);
+}
+
+} // namespace
+
+struct ProcessManager::BackgroundPageData {
+ // The count of things keeping the lazy background page alive.
+ int lazy_keepalive_count;
+
+ // Tracks if an impulse event has occured since the last polling check.
+ bool keepalive_impulse;
+ bool previous_keepalive_impulse;
+
+ // True if the page responded to the ShouldSuspend message and is currently
+ // dispatching the suspend event. During this time any events that arrive will
+ // cancel the suspend process and an onSuspendCanceled event will be
+ // dispatched to the page.
+ bool is_closing;
+
+ // Stores the value of the incremented
+ // ProcessManager::last_background_close_sequence_id_ whenever the extension
+ // is active. A copy of the ID is also passed in the callbacks and IPC
+ // messages leading up to CloseLazyBackgroundPageNow. The process is aborted
+ // if the IDs ever differ due to new activity.
+ uint64_t close_sequence_id;
+
+ // Keeps track of when this page was last suspended. Used for perf metrics.
+ linked_ptr<base::ElapsedTimer> since_suspended;
+
+ BackgroundPageData()
+ : lazy_keepalive_count(0),
+ keepalive_impulse(false),
+ previous_keepalive_impulse(false),
+ is_closing(false),
+ close_sequence_id(0) {}
+};
+
+// Data of a RenderFrameHost associated with an extension.
+struct ProcessManager::ExtensionRenderFrameData {
+ // The type of the view.
+ extensions::ViewType view_type;
+
+ // Whether the view is keeping the lazy background page alive or not.
+ bool has_keepalive;
+
+ ExtensionRenderFrameData()
+ : view_type(VIEW_TYPE_INVALID), has_keepalive(false) {}
+
+ // Returns whether the view can keep the lazy background page alive or not.
+ bool CanKeepalive() const {
+ switch (view_type) {
+ case VIEW_TYPE_APP_WINDOW:
+ case VIEW_TYPE_BACKGROUND_CONTENTS:
+ case VIEW_TYPE_COMPONENT:
+ case VIEW_TYPE_EXTENSION_DIALOG:
+ case VIEW_TYPE_EXTENSION_GUEST:
+ case VIEW_TYPE_EXTENSION_POPUP:
+ case VIEW_TYPE_LAUNCHER_PAGE:
+ case VIEW_TYPE_PANEL:
+ case VIEW_TYPE_TAB_CONTENTS:
+ return true;
+
+ case VIEW_TYPE_INVALID:
+ case VIEW_TYPE_EXTENSION_BACKGROUND_PAGE:
+ return false;
+ }
+ NOTREACHED();
+ return false;
+ }
+};
+
+//
+// ProcessManager
+//
+
+// static
+ProcessManager* ProcessManager::Get(BrowserContext* context) {
+ return ProcessManagerFactory::GetForBrowserContext(context);
+}
+
+// static
+ProcessManager* ProcessManager::Create(BrowserContext* context) {
+ ExtensionRegistry* extension_registry = ExtensionRegistry::Get(context);
+ ExtensionsBrowserClient* client = ExtensionsBrowserClient::Get();
+ if (client->IsGuestSession(context)) {
+ // In the guest session, there is a single off-the-record context. Unlike
+ // a regular incognito mode, background pages of extensions must be
+ // created regardless of whether extensions use "spanning" or "split"
+ // incognito behavior.
+ BrowserContext* original_context = client->GetOriginalContext(context);
+ return new ProcessManager(context, original_context, extension_registry);
+ }
+
+ if (context->IsOffTheRecord()) {
+ BrowserContext* original_context = client->GetOriginalContext(context);
+ return new IncognitoProcessManager(
+ context, original_context, extension_registry);
+ }
+
+ return new ProcessManager(context, context, extension_registry);
+}
+
+// static
+ProcessManager* ProcessManager::CreateForTesting(
+ BrowserContext* context,
+ ExtensionRegistry* extension_registry) {
+ DCHECK(!context->IsOffTheRecord());
+ return new ProcessManager(context, context, extension_registry);
+}
+
+// static
+ProcessManager* ProcessManager::CreateIncognitoForTesting(
+ BrowserContext* incognito_context,
+ BrowserContext* original_context,
+ ExtensionRegistry* extension_registry) {
+ DCHECK(incognito_context->IsOffTheRecord());
+ DCHECK(!original_context->IsOffTheRecord());
+ return new IncognitoProcessManager(incognito_context,
+ original_context,
+ extension_registry);
+}
+
+ProcessManager::ProcessManager(BrowserContext* context,
+ BrowserContext* original_context,
+ ExtensionRegistry* extension_registry)
+ : extension_registry_(extension_registry),
+ site_instance_(content::SiteInstance::Create(context)),
+ browser_context_(context),
+ startup_background_hosts_created_(false),
+ last_background_close_sequence_id_(0),
+ weak_ptr_factory_(this) {
+ // ExtensionRegistry is shared between incognito and regular contexts.
+ DCHECK_EQ(original_context, extension_registry_->browser_context());
+ extension_registry_->AddObserver(this);
+
+ if (!context->IsOffTheRecord()) {
+ // Only the original profile needs to listen for ready to create background
+ // pages for all spanning extensions.
+ registrar_.Add(this,
+ extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
+ content::Source<BrowserContext>(original_context));
+ }
+ registrar_.Add(this,
+ extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED,
+ content::Source<BrowserContext>(context));
+ registrar_.Add(this,
+ extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE,
+ content::Source<BrowserContext>(context));
+ devtools_callback_ = base::Bind(&ProcessManager::OnDevToolsStateChanged,
+ weak_ptr_factory_.GetWeakPtr());
+ content::DevToolsAgentHost::AddAgentStateCallback(devtools_callback_);
+
+ OnKeepaliveImpulseCheck();
+}
+
+ProcessManager::~ProcessManager() {
+ extension_registry_->RemoveObserver(this);
+ CloseBackgroundHosts();
+ DCHECK(background_hosts_.empty());
+ content::DevToolsAgentHost::RemoveAgentStateCallback(devtools_callback_);
+}
+
+void ProcessManager::RegisterRenderFrameHost(
+ content::WebContents* web_contents,
+ content::RenderFrameHost* render_frame_host,
+ const Extension* extension) {
+ ExtensionRenderFrameData* data = &all_extension_frames_[render_frame_host];
+ data->view_type = GetViewType(web_contents);
+
+ // Keep the lazy background page alive as long as any non-background-page
+ // extension views are visible. Keepalive count balanced in
+ // UnregisterRenderFrame.
+ AcquireLazyKeepaliveCountForFrame(render_frame_host);
+
+ FOR_EACH_OBSERVER(ProcessManagerObserver,
+ observer_list_,
+ OnExtensionFrameRegistered(extension->id(),
+ render_frame_host));
+}
+
+void ProcessManager::UnregisterRenderFrameHost(
+ content::RenderFrameHost* render_frame_host) {
+ ExtensionRenderFrames::iterator frame =
+ all_extension_frames_.find(render_frame_host);
+
+ if (frame != all_extension_frames_.end()) {
+ std::string extension_id = GetExtensionID(render_frame_host);
+ // Keepalive count, balanced in RegisterRenderFrame.
+ ReleaseLazyKeepaliveCountForFrame(render_frame_host);
+ all_extension_frames_.erase(frame);
+
+ FOR_EACH_OBSERVER(ProcessManagerObserver,
+ observer_list_,
+ OnExtensionFrameUnregistered(extension_id,
+ render_frame_host));
+ }
+}
+
+void ProcessManager::DidNavigateRenderFrameHost(
+ content::RenderFrameHost* render_frame_host) {
+ ExtensionRenderFrames::iterator frame =
+ all_extension_frames_.find(render_frame_host);
+
+ if (frame != all_extension_frames_.end()) {
+ std::string extension_id = GetExtensionID(render_frame_host);
+
+ FOR_EACH_OBSERVER(ProcessManagerObserver,
+ observer_list_,
+ OnExtensionFrameNavigated(extension_id,
+ render_frame_host));
+ }
+}
+
+scoped_refptr<content::SiteInstance> ProcessManager::GetSiteInstanceForURL(
+ const GURL& url) {
+ return site_instance_->GetRelatedSiteInstance(url);
+}
+
+const ProcessManager::FrameSet ProcessManager::GetAllFrames() const {
+ FrameSet result;
+ for (const auto& key_value : all_extension_frames_)
+ result.insert(key_value.first);
+ return result;
+}
+
+ProcessManager::FrameSet ProcessManager::GetRenderFrameHostsForExtension(
+ const std::string& extension_id) {
+ FrameSet result;
+ for (const auto& key_value : all_extension_frames_) {
+ if (GetExtensionID(key_value.first) == extension_id)
+ result.insert(key_value.first);
+ }
+ return result;
+}
+
+bool ProcessManager::IsRenderFrameHostRegistered(
+ content::RenderFrameHost* render_frame_host) {
+ return all_extension_frames_.find(render_frame_host) !=
+ all_extension_frames_.end();
+}
+
+void ProcessManager::AddObserver(ProcessManagerObserver* observer) {
+ observer_list_.AddObserver(observer);
+}
+
+void ProcessManager::RemoveObserver(ProcessManagerObserver* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+bool ProcessManager::CreateBackgroundHost(const Extension* extension,
+ const GURL& url) {
+ // Hosted apps are taken care of from BackgroundContentsService. Ignore them
+ // here.
+ if (extension->is_hosted_app())
+ return false;
+
+ // Don't create hosts if the embedder doesn't allow it.
+ ProcessManagerDelegate* delegate =
+ ExtensionsBrowserClient::Get()->GetProcessManagerDelegate();
+ if (delegate && !delegate->IsBackgroundPageAllowed(browser_context_))
+ return false;
+
+ // Don't create multiple background hosts for an extension.
+ if (GetBackgroundHostForExtension(extension->id()))
+ return true; // TODO(kalman): return false here? It might break things...
+
+ ExtensionHost* host =
+ new ExtensionHost(extension, GetSiteInstanceForURL(url).get(), url,
+ VIEW_TYPE_EXTENSION_BACKGROUND_PAGE);
+ host->CreateRenderViewSoon();
+ OnBackgroundHostCreated(host);
+ return true;
+}
+
+void ProcessManager::MaybeCreateStartupBackgroundHosts() {
+ if (startup_background_hosts_created_)
+ return;
+
+ // The embedder might disallow background pages entirely.
+ ProcessManagerDelegate* delegate =
+ ExtensionsBrowserClient::Get()->GetProcessManagerDelegate();
+ if (delegate && !delegate->IsBackgroundPageAllowed(browser_context_))
+ return;
+
+ // The embedder might want to defer background page loading. For example,
+ // Chrome defers background page loading when it is launched to show the app
+ // list, then triggers a load later when a browser window opens.
+ if (delegate &&
+ delegate->DeferCreatingStartupBackgroundHosts(browser_context_))
+ return;
+
+ CreateStartupBackgroundHosts();
+ startup_background_hosts_created_ = true;
+
+ // Background pages should only be loaded once. To prevent any further loads
+ // occurring, we remove the notification listeners.
+ BrowserContext* original_context =
+ ExtensionsBrowserClient::Get()->GetOriginalContext(browser_context_);
+ if (registrar_.IsRegistered(
+ this,
+ extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
+ content::Source<BrowserContext>(original_context))) {
+ registrar_.Remove(this,
+ extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
+ content::Source<BrowserContext>(original_context));
+ }
+}
+
+ExtensionHost* ProcessManager::GetBackgroundHostForExtension(
+ const std::string& extension_id) {
+ for (ExtensionHost* host : background_hosts_) {
+ if (host->extension_id() == extension_id)
+ return host;
+ }
+ return nullptr;
+}
+
+ExtensionHost* ProcessManager::GetExtensionHostForRenderFrameHost(
+ content::RenderFrameHost* render_frame_host) {
+ content::WebContents* web_contents =
+ content::WebContents::FromRenderFrameHost(render_frame_host);
+ for (ExtensionHost* extension_host : background_hosts_) {
+ if (extension_host->host_contents() == web_contents)
+ return extension_host;
+ }
+ return nullptr;
+}
+
+bool ProcessManager::IsEventPageSuspended(const std::string& extension_id) {
+ return GetBackgroundHostForExtension(extension_id) == nullptr;
+}
+
+bool ProcessManager::WakeEventPage(const std::string& extension_id,
+ const base::Callback<void(bool)>& callback) {
+ if (GetBackgroundHostForExtension(extension_id)) {
+ // Run the callback immediately if the extension is already awake.
+ return false;
+ }
+ LazyBackgroundTaskQueue* queue =
+ LazyBackgroundTaskQueue::Get(browser_context_);
+ queue->AddPendingTask(browser_context_, extension_id,
+ base::Bind(&PropagateExtensionWakeResult, callback));
+ return true;
+}
+
+bool ProcessManager::IsBackgroundHostClosing(const std::string& extension_id) {
+ ExtensionHost* host = GetBackgroundHostForExtension(extension_id);
+ return (host && background_page_data_[extension_id].is_closing);
+}
+
+const Extension* ProcessManager::GetExtensionForRenderFrameHost(
+ content::RenderFrameHost* render_frame_host) {
+ return extension_registry_->enabled_extensions().GetByID(
+ GetExtensionID(render_frame_host));
+}
+
+const Extension* ProcessManager::GetExtensionForWebContents(
+ const content::WebContents* web_contents) {
+ if (!web_contents->GetSiteInstance())
+ return nullptr;
+ return extension_registry_->enabled_extensions().GetByID(
+ GetExtensionIdForSiteInstance(web_contents->GetSiteInstance()));
+}
+
+int ProcessManager::GetLazyKeepaliveCount(const Extension* extension) {
+ if (!BackgroundInfo::HasLazyBackgroundPage(extension))
+ return 0;
+
+ return background_page_data_[extension->id()].lazy_keepalive_count;
+}
+
+void ProcessManager::IncrementLazyKeepaliveCount(const Extension* extension) {
+ if (BackgroundInfo::HasLazyBackgroundPage(extension)) {
+ int& count = background_page_data_[extension->id()].lazy_keepalive_count;
+ if (++count == 1)
+ OnLazyBackgroundPageActive(extension->id());
+ }
+}
+
+void ProcessManager::DecrementLazyKeepaliveCount(const Extension* extension) {
+ if (BackgroundInfo::HasLazyBackgroundPage(extension))
+ DecrementLazyKeepaliveCount(extension->id());
+}
+
+// This implementation layers on top of the keepalive count. An impulse sets
+// a per extension flag. On a regular interval that flag is checked. Changes
+// from the flag not being set to set cause an IncrementLazyKeepaliveCount.
+void ProcessManager::KeepaliveImpulse(const Extension* extension) {
+ if (!BackgroundInfo::HasLazyBackgroundPage(extension))
+ return;
+
+ BackgroundPageData& bd = background_page_data_[extension->id()];
+
+ if (!bd.keepalive_impulse) {
+ bd.keepalive_impulse = true;
+ if (!bd.previous_keepalive_impulse) {
+ IncrementLazyKeepaliveCount(extension);
+ }
+ }
+
+ if (!keepalive_impulse_callback_for_testing_.is_null()) {
+ ImpulseCallbackForTesting callback_may_clear_callbacks_reentrantly =
+ keepalive_impulse_callback_for_testing_;
+ callback_may_clear_callbacks_reentrantly.Run(extension->id());
+ }
+}
+
+// static
+void ProcessManager::OnKeepaliveFromPlugin(int render_process_id,
+ int render_frame_id,
+ const std::string& extension_id) {
+ content::RenderFrameHost* render_frame_host =
+ content::RenderFrameHost::FromID(render_process_id, render_frame_id);
+ if (!render_frame_host)
+ return;
+
+ content::SiteInstance* site_instance = render_frame_host->GetSiteInstance();
+ if (!site_instance)
+ return;
+
+ BrowserContext* browser_context = site_instance->GetBrowserContext();
+ const Extension* extension =
+ ExtensionRegistry::Get(browser_context)->enabled_extensions().GetByID(
+ extension_id);
+ if (!extension)
+ return;
+
+ ProcessManager::Get(browser_context)->KeepaliveImpulse(extension);
+}
+
+void ProcessManager::OnShouldSuspendAck(const std::string& extension_id,
+ uint64_t sequence_id) {
+ ExtensionHost* host = GetBackgroundHostForExtension(extension_id);
+ if (host &&
+ sequence_id == background_page_data_[extension_id].close_sequence_id) {
+ host->render_process_host()->Send(new ExtensionMsg_Suspend(extension_id));
+ }
+}
+
+void ProcessManager::OnSuspendAck(const std::string& extension_id) {
+ background_page_data_[extension_id].is_closing = true;
+ uint64_t sequence_id = background_page_data_[extension_id].close_sequence_id;
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ProcessManager::CloseLazyBackgroundPageNow,
+ weak_ptr_factory_.GetWeakPtr(),
+ extension_id,
+ sequence_id),
+ base::TimeDelta::FromMilliseconds(g_event_page_suspending_time_msec));
+}
+
+void ProcessManager::OnNetworkRequestStarted(
+ content::RenderFrameHost* render_frame_host,
+ uint64_t request_id) {
+ ExtensionHost* host = GetBackgroundHostForExtension(
+ GetExtensionID(render_frame_host));
+ auto result = pending_network_requests_.insert(request_id);
+ DCHECK(result.second) << "Duplicate network request IDs.";
+ if (host && IsFrameInExtensionHost(host, render_frame_host)) {
+ IncrementLazyKeepaliveCount(host->extension());
+ host->OnNetworkRequestStarted(request_id);
+ }
+}
+
+void ProcessManager::OnNetworkRequestDone(
+ content::RenderFrameHost* render_frame_host,
+ uint64_t request_id) {
+ ExtensionHost* host = GetBackgroundHostForExtension(
+ GetExtensionID(render_frame_host));
+ if (host && IsFrameInExtensionHost(host, render_frame_host)) {
+ host->OnNetworkRequestDone(request_id);
+ if (pending_network_requests_.erase(request_id))
+ DecrementLazyKeepaliveCount(host->extension());
+ }
+}
+
+void ProcessManager::CancelSuspend(const Extension* extension) {
+ bool& is_closing = background_page_data_[extension->id()].is_closing;
+ ExtensionHost* host = GetBackgroundHostForExtension(extension->id());
+ if (host && is_closing) {
+ is_closing = false;
+ host->render_process_host()->Send(
+ new ExtensionMsg_CancelSuspend(extension->id()));
+ // This increment / decrement is to simulate an instantaneous event. This
+ // has the effect of invalidating close_sequence_id, preventing any in
+ // progress closes from completing and starting a new close process if
+ // necessary.
+ IncrementLazyKeepaliveCount(extension);
+ DecrementLazyKeepaliveCount(extension);
+ }
+}
+
+void ProcessManager::CloseBackgroundHosts() {
+ STLDeleteElements(&background_hosts_);
+}
+
+void ProcessManager::SetKeepaliveImpulseCallbackForTesting(
+ const ImpulseCallbackForTesting& callback) {
+ keepalive_impulse_callback_for_testing_ = callback;
+}
+
+void ProcessManager::SetKeepaliveImpulseDecrementCallbackForTesting(
+ const ImpulseCallbackForTesting& callback) {
+ keepalive_impulse_decrement_callback_for_testing_ = callback;
+}
+
+// static
+void ProcessManager::SetEventPageIdleTimeForTesting(unsigned idle_time_msec) {
+ CHECK_GT(idle_time_msec, 0u); // OnKeepaliveImpulseCheck requires non zero.
+ g_event_page_idle_time_msec = idle_time_msec;
+}
+
+// static
+void ProcessManager::SetEventPageSuspendingTimeForTesting(
+ unsigned suspending_time_msec) {
+ g_event_page_suspending_time_msec = suspending_time_msec;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Private
+
+void ProcessManager::Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ TRACE_EVENT0("browser,startup", "ProcessManager::Observe");
+ switch (type) {
+ case extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED: {
+ // TODO(jamescook): Convert this to use ExtensionSystem::ready() instead
+ // of a notification.
+ SCOPED_UMA_HISTOGRAM_TIMER("Extensions.ProcessManagerStartupHostsTime");
+ MaybeCreateStartupBackgroundHosts();
+ break;
+ }
+ case extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED: {
+ ExtensionHost* host = content::Details<ExtensionHost>(details).ptr();
+ if (background_hosts_.erase(host)) {
+ ClearBackgroundPageData(host->extension()->id());
+ background_page_data_[host->extension()->id()].since_suspended.reset(
+ new base::ElapsedTimer());
+ }
+ break;
+ }
+ case extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE: {
+ ExtensionHost* host = content::Details<ExtensionHost>(details).ptr();
+ if (host->extension_host_type() == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) {
+ CloseBackgroundHost(host);
+ }
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+}
+
+void ProcessManager::OnExtensionLoaded(BrowserContext* browser_context,
+ const Extension* extension) {
+ if (ExtensionSystem::Get(browser_context)->ready().is_signaled()) {
+ // The extension system is ready, so create the background host.
+ CreateBackgroundHostForExtensionLoad(this, extension);
+ }
+}
+
+void ProcessManager::OnExtensionUnloaded(
+ BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ ExtensionHost* host = GetBackgroundHostForExtension(extension->id());
+ if (host != nullptr)
+ CloseBackgroundHost(host);
+ UnregisterExtension(extension->id());
+}
+
+void ProcessManager::CreateStartupBackgroundHosts() {
+ DCHECK(!startup_background_hosts_created_);
+ for (const scoped_refptr<const Extension>& extension :
+ extension_registry_->enabled_extensions()) {
+ CreateBackgroundHostForExtensionLoad(this, extension.get());
+ FOR_EACH_OBSERVER(ProcessManagerObserver,
+ observer_list_,
+ OnBackgroundHostStartup(extension.get()));
+ }
+}
+
+void ProcessManager::OnBackgroundHostCreated(ExtensionHost* host) {
+ DCHECK_EQ(browser_context_, host->browser_context());
+ background_hosts_.insert(host);
+
+ if (BackgroundInfo::HasLazyBackgroundPage(host->extension())) {
+ linked_ptr<base::ElapsedTimer> since_suspended(
+ background_page_data_[host->extension()->id()].
+ since_suspended.release());
+ if (since_suspended.get()) {
+ UMA_HISTOGRAM_LONG_TIMES("Extensions.EventPageIdleTime",
+ since_suspended->Elapsed());
+ }
+ }
+ FOR_EACH_OBSERVER(ProcessManagerObserver, observer_list_,
+ OnBackgroundHostCreated(host));
+}
+
+void ProcessManager::CloseBackgroundHost(ExtensionHost* host) {
+ ExtensionId extension_id = host->extension_id();
+ CHECK(host->extension_host_type() == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE);
+ delete host;
+ // |host| should deregister itself from our structures.
+ CHECK(background_hosts_.find(host) == background_hosts_.end());
+
+ FOR_EACH_OBSERVER(ProcessManagerObserver,
+ observer_list_,
+ OnBackgroundHostClose(extension_id));
+}
+
+void ProcessManager::AcquireLazyKeepaliveCountForFrame(
+ content::RenderFrameHost* render_frame_host) {
+ ExtensionRenderFrames::iterator it =
+ all_extension_frames_.find(render_frame_host);
+ if (it == all_extension_frames_.end())
+ return;
+
+ ExtensionRenderFrameData& data = it->second;
+ if (data.CanKeepalive() && !data.has_keepalive) {
+ const Extension* extension =
+ GetExtensionForRenderFrameHost(render_frame_host);
+ if (extension) {
+ IncrementLazyKeepaliveCount(extension);
+ data.has_keepalive = true;
+ }
+ }
+}
+
+void ProcessManager::ReleaseLazyKeepaliveCountForFrame(
+ content::RenderFrameHost* render_frame_host) {
+ ExtensionRenderFrames::iterator iter =
+ all_extension_frames_.find(render_frame_host);
+ if (iter == all_extension_frames_.end())
+ return;
+
+ ExtensionRenderFrameData& data = iter->second;
+ if (data.CanKeepalive() && data.has_keepalive) {
+ const Extension* extension =
+ GetExtensionForRenderFrameHost(render_frame_host);
+ if (extension) {
+ DecrementLazyKeepaliveCount(extension);
+ data.has_keepalive = false;
+ }
+ }
+}
+
+void ProcessManager::DecrementLazyKeepaliveCount(
+ const std::string& extension_id) {
+ int& count = background_page_data_[extension_id].lazy_keepalive_count;
+ DCHECK(count > 0 ||
+ !extension_registry_->enabled_extensions().Contains(extension_id));
+
+ // If we reach a zero keepalive count when the lazy background page is about
+ // to be closed, incrementing close_sequence_id will cancel the close
+ // sequence and cause the background page to linger. So check is_closing
+ // before initiating another close sequence.
+ if (--count == 0 && !background_page_data_[extension_id].is_closing) {
+ background_page_data_[extension_id].close_sequence_id =
+ ++last_background_close_sequence_id_;
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ProcessManager::OnLazyBackgroundPageIdle,
+ weak_ptr_factory_.GetWeakPtr(),
+ extension_id,
+ last_background_close_sequence_id_),
+ base::TimeDelta::FromMilliseconds(g_event_page_idle_time_msec));
+ }
+}
+
+// DecrementLazyKeepaliveCount is called when no calls to KeepaliveImpulse
+// have been made for at least g_event_page_idle_time_msec. In the best case an
+// impulse was made just before being cleared, and the decrement will occur
+// g_event_page_idle_time_msec later, causing a 2 * g_event_page_idle_time_msec
+// total time for extension to be shut down based on impulses. Worst case is
+// an impulse just after a clear, adding one check cycle and resulting in 3x
+// total time.
+void ProcessManager::OnKeepaliveImpulseCheck() {
+ for (BackgroundPageDataMap::iterator i = background_page_data_.begin();
+ i != background_page_data_.end();
+ ++i) {
+ if (i->second.previous_keepalive_impulse && !i->second.keepalive_impulse) {
+ DecrementLazyKeepaliveCount(i->first);
+ if (!keepalive_impulse_decrement_callback_for_testing_.is_null()) {
+ ImpulseCallbackForTesting callback_may_clear_callbacks_reentrantly =
+ keepalive_impulse_decrement_callback_for_testing_;
+ callback_may_clear_callbacks_reentrantly.Run(i->first);
+ }
+ }
+
+ i->second.previous_keepalive_impulse = i->second.keepalive_impulse;
+ i->second.keepalive_impulse = false;
+ }
+
+ // OnKeepaliveImpulseCheck() is always called in constructor, but in unit
+ // tests there will be no message loop. In that event don't schedule tasks.
+ if (base::MessageLoop::current()) {
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ProcessManager::OnKeepaliveImpulseCheck,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(g_event_page_idle_time_msec));
+ }
+}
+
+void ProcessManager::OnLazyBackgroundPageIdle(const std::string& extension_id,
+ uint64_t sequence_id) {
+ ExtensionHost* host = GetBackgroundHostForExtension(extension_id);
+ if (host && !background_page_data_[extension_id].is_closing &&
+ sequence_id == background_page_data_[extension_id].close_sequence_id) {
+ // Tell the renderer we are about to close. This is a simple ping that the
+ // renderer will respond to. The purpose is to control sequencing: if the
+ // extension remains idle until the renderer responds with an ACK, then we
+ // know that the extension process is ready to shut down. If our
+ // close_sequence_id has already changed, then we would ignore the
+ // ShouldSuspendAck, so we don't send the ping.
+ host->render_process_host()->Send(new ExtensionMsg_ShouldSuspend(
+ extension_id, sequence_id));
+ }
+}
+
+void ProcessManager::OnLazyBackgroundPageActive(
+ const std::string& extension_id) {
+ if (!background_page_data_[extension_id].is_closing) {
+ // Cancel the current close sequence by changing the close_sequence_id,
+ // which causes us to ignore the next ShouldSuspendAck.
+ background_page_data_[extension_id].close_sequence_id =
+ ++last_background_close_sequence_id_;
+ }
+}
+
+void ProcessManager::CloseLazyBackgroundPageNow(const std::string& extension_id,
+ uint64_t sequence_id) {
+ ExtensionHost* host = GetBackgroundHostForExtension(extension_id);
+ if (host &&
+ sequence_id == background_page_data_[extension_id].close_sequence_id) {
+ // Handle the case where the keepalive count was increased after the
+ // OnSuspend event was sent.
+ if (background_page_data_[extension_id].lazy_keepalive_count > 0) {
+ CancelSuspend(host->extension());
+ return;
+ }
+
+ // Close remaining views.
+ std::vector<content::RenderFrameHost*> frames_to_close;
+ for (const auto& key_value : all_extension_frames_) {
+ if (key_value.second.CanKeepalive() &&
+ GetExtensionID(key_value.first) == extension_id) {
+ DCHECK(!key_value.second.has_keepalive);
+ frames_to_close.push_back(key_value.first);
+ }
+ }
+ for (content::RenderFrameHost* frame : frames_to_close) {
+ content::WebContents::FromRenderFrameHost(frame)->ClosePage();
+ // WebContents::ClosePage() may result in calling
+ // UnregisterRenderViewHost() asynchronously and may cause race conditions
+ // when the background page is reloaded.
+ // To avoid this, unregister the view now.
+ UnregisterRenderFrameHost(frame);
+ }
+
+ ExtensionHost* host = GetBackgroundHostForExtension(extension_id);
+ if (host)
+ CloseBackgroundHost(host);
+ }
+}
+
+void ProcessManager::OnDevToolsStateChanged(
+ content::DevToolsAgentHost* agent_host,
+ bool attached) {
+ content::WebContents* web_contents = agent_host->GetWebContents();
+ // Ignore unrelated notifications.
+ if (!web_contents || web_contents->GetBrowserContext() != browser_context_)
+ return;
+ if (GetViewType(web_contents) != VIEW_TYPE_EXTENSION_BACKGROUND_PAGE)
+ return;
+ const Extension* extension =
+ extension_registry_->enabled_extensions().GetByID(
+ GetExtensionIdForSiteInstance(web_contents->GetSiteInstance()));
+ if (!extension)
+ return;
+ if (attached) {
+ // Keep the lazy background page alive while it's being inspected.
+ CancelSuspend(extension);
+ IncrementLazyKeepaliveCount(extension);
+ } else {
+ DecrementLazyKeepaliveCount(extension);
+ }
+}
+
+void ProcessManager::UnregisterExtension(const std::string& extension_id) {
+ // The lazy_keepalive_count may be greater than zero at this point because
+ // RenderFrameHosts are still alive. During extension reloading, they will
+ // decrement the lazy_keepalive_count to negative for the new extension
+ // instance when they are destroyed. Since we are erasing the background page
+ // data for the unloaded extension, unregister the RenderFrameHosts too.
+ for (ExtensionRenderFrames::iterator it = all_extension_frames_.begin();
+ it != all_extension_frames_.end(); ) {
+ content::RenderFrameHost* host = it->first;
+ if (GetExtensionID(host) == extension_id) {
+ all_extension_frames_.erase(it++);
+ FOR_EACH_OBSERVER(ProcessManagerObserver,
+ observer_list_,
+ OnExtensionFrameUnregistered(extension_id, host));
+ } else {
+ ++it;
+ }
+ }
+
+ background_page_data_.erase(extension_id);
+}
+
+void ProcessManager::ClearBackgroundPageData(const std::string& extension_id) {
+ background_page_data_.erase(extension_id);
+
+ // Re-register all RenderViews for this extension. We do this to restore
+ // the lazy_keepalive_count (if any) to properly reflect the number of open
+ // views.
+ for (const auto& key_value : all_extension_frames_) {
+ // Do not increment the count when |has_keepalive| is false
+ // (i.e. ReleaseLazyKeepaliveCountForView() was called).
+ if (GetExtensionID(key_value.first) == extension_id &&
+ key_value.second.has_keepalive) {
+ const Extension* extension =
+ GetExtensionForRenderFrameHost(key_value.first);
+ if (extension)
+ IncrementLazyKeepaliveCount(extension);
+ }
+ }
+}
+
+//
+// IncognitoProcessManager
+//
+
+IncognitoProcessManager::IncognitoProcessManager(
+ BrowserContext* incognito_context,
+ BrowserContext* original_context,
+ ExtensionRegistry* extension_registry)
+ : ProcessManager(incognito_context, original_context, extension_registry) {
+ DCHECK(incognito_context->IsOffTheRecord());
+}
+
+bool IncognitoProcessManager::CreateBackgroundHost(const Extension* extension,
+ const GURL& url) {
+ if (IncognitoInfo::IsSplitMode(extension)) {
+ if (ExtensionsBrowserClient::Get()->IsExtensionIncognitoEnabled(
+ extension->id(), browser_context()))
+ return ProcessManager::CreateBackgroundHost(extension, url);
+ } else {
+ // Do nothing. If an extension is spanning, then its original-profile
+ // background page is shared with incognito, so we don't create another.
+ }
+ return false;
+}
+
+scoped_refptr<content::SiteInstance>
+IncognitoProcessManager::GetSiteInstanceForURL(const GURL& url) {
+ const Extension* extension =
+ extension_registry_->enabled_extensions().GetExtensionOrAppByURL(url);
+ if (extension && !IncognitoInfo::IsSplitMode(extension)) {
+ BrowserContext* original_context =
+ ExtensionsBrowserClient::Get()->GetOriginalContext(browser_context());
+ return ProcessManager::Get(original_context)->GetSiteInstanceForURL(url);
+ }
+
+ return ProcessManager::GetSiteInstanceForURL(url);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/process_manager.h b/chromium/extensions/browser/process_manager.h
new file mode 100644
index 00000000000..65ee76f8a1d
--- /dev/null
+++ b/chromium/extensions/browser/process_manager.h
@@ -0,0 +1,339 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_PROCESS_MANAGER_H_
+#define EXTENSIONS_BROWSER_PROCESS_MANAGER_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "extensions/browser/event_page_tracker.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/view_type.h"
+
+class GURL;
+
+namespace content {
+class BrowserContext;
+class DevToolsAgentHost;
+class RenderFrameHost;
+class SiteInstance;
+class WebContents;
+};
+
+namespace extensions {
+
+class Extension;
+class ExtensionHost;
+class ExtensionRegistry;
+class ProcessManagerObserver;
+
+// Manages dynamic state of running Chromium extensions. There is one instance
+// of this class per Profile. OTR Profiles have a separate instance that keeps
+// track of split-mode extensions only.
+class ProcessManager : public KeyedService,
+ public content::NotificationObserver,
+ public ExtensionRegistryObserver,
+ public EventPageTracker {
+ public:
+ using ExtensionHostSet = std::set<extensions::ExtensionHost*>;
+
+ static ProcessManager* Get(content::BrowserContext* context);
+ ~ProcessManager() override;
+
+ void RegisterRenderFrameHost(content::WebContents* web_contents,
+ content::RenderFrameHost* render_frame_host,
+ const Extension* extension);
+ void UnregisterRenderFrameHost(content::RenderFrameHost* render_frame_host);
+ void DidNavigateRenderFrameHost(content::RenderFrameHost* render_frame_host);
+
+ // Returns the SiteInstance that the given URL belongs to.
+ // TODO(aa): This only returns correct results for extensions and packaged
+ // apps, not hosted apps.
+ virtual scoped_refptr<content::SiteInstance> GetSiteInstanceForURL(
+ const GURL& url);
+
+ using FrameSet = std::set<content::RenderFrameHost*>;
+ const FrameSet GetAllFrames() const;
+
+ // Returns all RenderFrameHosts that are registered for the specified
+ // extension.
+ ProcessManager::FrameSet GetRenderFrameHostsForExtension(
+ const std::string& extension_id);
+
+ bool IsRenderFrameHostRegistered(content::RenderFrameHost* render_frame_host);
+
+ void AddObserver(ProcessManagerObserver* observer);
+ void RemoveObserver(ProcessManagerObserver* observer);
+
+ // Creates a new UI-less extension instance. Like CreateViewHost, but not
+ // displayed anywhere. Returns false if no background host can be created,
+ // for example for hosted apps and extensions that aren't enabled in
+ // Incognito.
+ virtual bool CreateBackgroundHost(const Extension* extension,
+ const GURL& url);
+
+ // Creates background hosts if the embedder is ready and they are not already
+ // loaded.
+ void MaybeCreateStartupBackgroundHosts();
+
+ // Gets the ExtensionHost for the background page for an extension, or null if
+ // the extension isn't running or doesn't have a background page.
+ ExtensionHost* GetBackgroundHostForExtension(const std::string& extension_id);
+
+ // Returns the ExtensionHost for the given |render_frame_host|, if there is
+ // one.
+ ExtensionHost* GetExtensionHostForRenderFrameHost(
+ content::RenderFrameHost* render_frame_host);
+
+ // Returns true if the (lazy) background host for the given extension has
+ // already been sent the unload event and is shutting down.
+ bool IsBackgroundHostClosing(const std::string& extension_id);
+
+ // Returns the extension associated with the specified RenderFrameHost/
+ // WebContents, or null.
+ const Extension* GetExtensionForRenderFrameHost(
+ content::RenderFrameHost* render_frame_host);
+ const Extension* GetExtensionForWebContents(
+ const content::WebContents* web_contents);
+
+ // Getter and setter for the lazy background page's keepalive count. This is
+ // the count of how many outstanding "things" are keeping the page alive.
+ // When this reaches 0, we will begin the process of shutting down the page.
+ // "Things" include pending events, resource loads, and API calls.
+ int GetLazyKeepaliveCount(const Extension* extension);
+ void IncrementLazyKeepaliveCount(const Extension* extension);
+ void DecrementLazyKeepaliveCount(const Extension* extension);
+
+ // Keeps a background page alive. Unlike IncrementLazyKeepaliveCount, these
+ // impulses will only keep the page alive for a limited amount of time unless
+ // called regularly.
+ void KeepaliveImpulse(const Extension* extension);
+
+ // Triggers a keepalive impulse for a plugin (e.g NaCl).
+ static void OnKeepaliveFromPlugin(int render_process_id,
+ int render_frame_id,
+ const std::string& extension_id);
+
+ // Handles a response to the ShouldSuspend message, used for lazy background
+ // pages.
+ void OnShouldSuspendAck(const std::string& extension_id,
+ uint64_t sequence_id);
+
+ // Same as above, for the Suspend message.
+ void OnSuspendAck(const std::string& extension_id);
+
+ // Tracks network requests for a given RenderFrameHost, used to know
+ // when network activity is idle for lazy background pages.
+ void OnNetworkRequestStarted(content::RenderFrameHost* render_frame_host,
+ uint64_t request_id);
+ void OnNetworkRequestDone(content::RenderFrameHost* render_frame_host,
+ uint64_t request_id);
+
+ // Prevents |extension|'s background page from being closed and sends the
+ // onSuspendCanceled() event to it.
+ void CancelSuspend(const Extension* extension);
+
+ // Called on shutdown to close our extension hosts.
+ void CloseBackgroundHosts();
+
+ // Sets callbacks for testing keepalive impulse behavior.
+ using ImpulseCallbackForTesting =
+ base::Callback<void(const std::string& extension_id)>;
+ void SetKeepaliveImpulseCallbackForTesting(
+ const ImpulseCallbackForTesting& callback);
+ void SetKeepaliveImpulseDecrementCallbackForTesting(
+ const ImpulseCallbackForTesting& callback);
+
+ // EventPageTracker implementation.
+ bool IsEventPageSuspended(const std::string& extension_id) override;
+ bool WakeEventPage(const std::string& extension_id,
+ const base::Callback<void(bool)>& callback) override;
+
+ // Sets the time in milliseconds that an extension event page can
+ // be idle before it is shut down; must be > 0.
+ static void SetEventPageIdleTimeForTesting(unsigned idle_time_msec);
+
+ // Sets the time in milliseconds that an extension event page has
+ // between being notified of its impending unload and that unload
+ // happening.
+ static void SetEventPageSuspendingTimeForTesting(
+ unsigned suspending_time_msec);
+
+ // Creates a non-incognito instance for tests. |registry| allows unit tests
+ // to inject an ExtensionRegistry that is not managed by the usual
+ // BrowserContextKeyedServiceFactory system.
+ static ProcessManager* CreateForTesting(content::BrowserContext* context,
+ ExtensionRegistry* registry);
+
+ // Creates an incognito-context instance for tests.
+ static ProcessManager* CreateIncognitoForTesting(
+ content::BrowserContext* incognito_context,
+ content::BrowserContext* original_context,
+ ExtensionRegistry* registry);
+
+ content::BrowserContext* browser_context() const { return browser_context_; }
+
+ const ExtensionHostSet& background_hosts() const {
+ return background_hosts_;
+ }
+
+ bool startup_background_hosts_created_for_test() const {
+ return startup_background_hosts_created_;
+ }
+
+ protected:
+ static ProcessManager* Create(content::BrowserContext* context);
+
+ // |context| is incognito pass the master context as |original_context|.
+ // Otherwise pass the same context for both. Pass the ExtensionRegistry for
+ // |context| as |registry|, or override it for testing.
+ ProcessManager(content::BrowserContext* context,
+ content::BrowserContext* original_context,
+ ExtensionRegistry* registry);
+
+ // Not owned. Also used by IncognitoProcessManager.
+ ExtensionRegistry* extension_registry_;
+
+ private:
+ friend class ProcessManagerFactory;
+ friend class ProcessManagerTest;
+
+ // content::NotificationObserver:
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override;
+
+ // ExtensionRegistryObserver:
+ void OnExtensionLoaded(content::BrowserContext* browser_context,
+ const Extension* extension) override;
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+
+ // Extra information we keep for each extension's background page.
+ struct BackgroundPageData;
+ struct ExtensionRenderFrameData;
+ using BackgroundPageDataMap = std::map<ExtensionId, BackgroundPageData>;
+ using ExtensionRenderFrames =
+ std::map<content::RenderFrameHost*, ExtensionRenderFrameData>;
+
+ // Load all background pages once the profile data is ready and the pages
+ // should be loaded.
+ void CreateStartupBackgroundHosts();
+
+ // Called just after |host| is created so it can be registered in our lists.
+ void OnBackgroundHostCreated(ExtensionHost* host);
+
+ // Close the given |host| iff it's a background page.
+ void CloseBackgroundHost(ExtensionHost* host);
+
+ // If the frame isn't keeping the lazy background page alive, increments the
+ // keepalive count to do so.
+ void AcquireLazyKeepaliveCountForFrame(
+ content::RenderFrameHost* render_frame_host);
+
+ // If the frame is keeping the lazy background page alive, decrements the
+ // keepalive count to stop doing it.
+ void ReleaseLazyKeepaliveCountForFrame(
+ content::RenderFrameHost* render_frame_host);
+
+ // Internal implementation of DecrementLazyKeepaliveCount with an
+ // |extension_id| known to have a lazy background page.
+ void DecrementLazyKeepaliveCount(const std::string& extension_id);
+
+ // Checks if keepalive impulses have occured, and adjusts keep alive count.
+ void OnKeepaliveImpulseCheck();
+
+ // These are called when the extension transitions between idle and active.
+ // They control the process of closing the background page when idle.
+ void OnLazyBackgroundPageIdle(const std::string& extension_id,
+ uint64_t sequence_id);
+ void OnLazyBackgroundPageActive(const std::string& extension_id);
+ void CloseLazyBackgroundPageNow(const std::string& extension_id,
+ uint64_t sequence_id);
+
+ void OnDevToolsStateChanged(content::DevToolsAgentHost*, bool attached);
+
+ // Unregister RenderFrameHosts and clear background page data for an extension
+ // which has been unloaded.
+ void UnregisterExtension(const std::string& extension_id);
+
+ // Clears background page data for this extension.
+ void ClearBackgroundPageData(const std::string& extension_id);
+
+ content::NotificationRegistrar registrar_;
+
+ // The set of ExtensionHosts running viewless background extensions.
+ ExtensionHostSet background_hosts_;
+
+ // A SiteInstance related to the SiteInstance for all extensions in
+ // this profile. We create it in such a way that a new
+ // browsing instance is created. This controls process grouping.
+ scoped_refptr<content::SiteInstance> site_instance_;
+
+ // The browser context associated with the |site_instance_|.
+ content::BrowserContext* browser_context_;
+
+ // Contains all active extension-related RenderFrameHost instances for all
+ // extensions. We also keep a cache of the host's view type, because that
+ // information is not accessible at registration/deregistration time.
+ ExtensionRenderFrames all_extension_frames_;
+
+ BackgroundPageDataMap background_page_data_;
+
+ // True if we have created the startup set of background hosts.
+ bool startup_background_hosts_created_;
+
+ base::Callback<void(content::DevToolsAgentHost*, bool)> devtools_callback_;
+
+ ImpulseCallbackForTesting keepalive_impulse_callback_for_testing_;
+ ImpulseCallbackForTesting keepalive_impulse_decrement_callback_for_testing_;
+
+ base::ObserverList<ProcessManagerObserver> observer_list_;
+
+ // ID Counter used to set ProcessManager::BackgroundPageData close_sequence_id
+ // members. These IDs are tracked per extension in background_page_data_ and
+ // are used to verify that nothing has interrupted the process of closing a
+ // lazy background process.
+ //
+ // Any interruption obtains a new ID by incrementing
+ // last_background_close_sequence_id_ and storing it in background_page_data_
+ // for a particular extension. Callbacks and round-trip IPC messages store the
+ // value of the extension's close_sequence_id at the beginning of the process.
+ // Thus comparisons can be done to halt when IDs no longer match.
+ //
+ // This counter provides unique IDs even when BackgroundPageData objects are
+ // reset.
+ uint64_t last_background_close_sequence_id_;
+
+ // Tracks pending network requests by opaque ID. This is used to ensure proper
+ // keepalive counting in response to request status updates; e.g., if an
+ // extension URLRequest is constructed and then destroyed without ever
+ // starting, we can receive a completion notification without a corresponding
+ // start notification. In that case we want to avoid decrementing keepalive.
+ std::set<int> pending_network_requests_;
+
+ // Must be last member, see doc on WeakPtrFactory.
+ base::WeakPtrFactory<ProcessManager> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProcessManager);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_PROCESS_MANAGER_H_
diff --git a/chromium/extensions/browser/process_manager_delegate.h b/chromium/extensions/browser/process_manager_delegate.h
new file mode 100644
index 00000000000..7cc48c0af74
--- /dev/null
+++ b/chromium/extensions/browser/process_manager_delegate.h
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_PROCESS_MANAGER_DELEGATE_H_
+#define EXTENSIONS_BROWSER_PROCESS_MANAGER_DELEGATE_H_
+
+namespace content {
+class BrowserContext;
+};
+
+namespace extensions {
+
+// Customization of ProcessManager for the extension system embedder.
+class ProcessManagerDelegate {
+ public:
+ virtual ~ProcessManagerDelegate() {}
+
+ // Returns true if the embedder allows background pages for the given
+ // |context|.
+ virtual bool IsBackgroundPageAllowed(
+ content::BrowserContext* context) const = 0;
+
+ // Returns true if the embedder wishes to defer starting up the renderers for
+ // extension background pages. If the embedder returns true it must call
+ // ProcessManager::MaybeCreateStartupBackgroundHosts() when it is ready. See
+ // ChromeProcessManagerDelegate for examples of how this is useful.
+ virtual bool DeferCreatingStartupBackgroundHosts(
+ content::BrowserContext* context) const = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_PROCESS_MANAGER_DELEGATE_H_
diff --git a/chromium/extensions/browser/process_manager_factory.cc b/chromium/extensions/browser/process_manager_factory.cc
new file mode 100644
index 00000000000..f6c001caed4
--- /dev/null
+++ b/chromium/extensions/browser/process_manager_factory.cc
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "extensions/browser/extension_registry_factory.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/browser/process_manager_factory.h"
+
+using content::BrowserContext;
+
+namespace extensions {
+
+// static
+ProcessManager* ProcessManagerFactory::GetForBrowserContext(
+ BrowserContext* context) {
+ return static_cast<ProcessManager*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+ProcessManager* ProcessManagerFactory::GetForBrowserContextIfExists(
+ BrowserContext* context) {
+ return static_cast<ProcessManager*>(
+ GetInstance()->GetServiceForBrowserContext(context, false));
+}
+
+// static
+ProcessManagerFactory* ProcessManagerFactory::GetInstance() {
+ return base::Singleton<ProcessManagerFactory>::get();
+}
+
+ProcessManagerFactory::ProcessManagerFactory()
+ : BrowserContextKeyedServiceFactory(
+ "ProcessManager",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(extensions::ExtensionRegistryFactory::GetInstance());
+}
+
+ProcessManagerFactory::~ProcessManagerFactory() {
+}
+
+KeyedService* ProcessManagerFactory::BuildServiceInstanceFor(
+ BrowserContext* context) const {
+ return ProcessManager::Create(context);
+}
+
+BrowserContext* ProcessManagerFactory::GetBrowserContextToUse(
+ BrowserContext* context) const {
+ // ProcessManager::Create handles guest and incognito profiles, returning an
+ // IncognitoProcessManager in incognito mode.
+ return context;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/process_manager_factory.h b/chromium/extensions/browser/process_manager_factory.h
new file mode 100644
index 00000000000..3589d207b3f
--- /dev/null
+++ b/chromium/extensions/browser/process_manager_factory.h
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_PROCESS_MANAGER_FACTORY_H_
+#define EXTENSIONS_BROWSER_PROCESS_MANAGER_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace extensions {
+
+class ProcessManager;
+
+class ProcessManagerFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static ProcessManager* GetForBrowserContext(content::BrowserContext* context);
+ // Returns NULL if there is no ProcessManager associated with this context.
+ static ProcessManager* GetForBrowserContextIfExists(
+ content::BrowserContext* context);
+ static ProcessManagerFactory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<ProcessManagerFactory>;
+
+ ProcessManagerFactory();
+ ~ProcessManagerFactory() override;
+
+ // BrowserContextKeyedServiceFactory
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(ProcessManagerFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_PROCESS_MANAGER_FACTORY_H_
diff --git a/chromium/extensions/browser/process_manager_observer.h b/chromium/extensions/browser/process_manager_observer.h
new file mode 100644
index 00000000000..47883ef62e7
--- /dev/null
+++ b/chromium/extensions/browser/process_manager_observer.h
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_PROCESS_MANAGER_OBSERVER_H_
+#define EXTENSIONS_BROWSER_PROCESS_MANAGER_OBSERVER_H_
+
+#include <string>
+
+namespace content {
+class RenderFrameHost;
+}
+
+namespace extensions {
+class Extension;
+class ExtensionHost;
+
+class ProcessManagerObserver {
+ public:
+ // Called immediately after an extension background host is started. This
+ // corresponds with the loading of background hosts immediately after profile
+ // startup.
+ virtual void OnBackgroundHostStartup(const Extension* extension) {}
+
+ // Called immediately after an ExtensionHost for an extension is created.
+ // This corresponds with any time ProcessManager::OnBackgroundHostCreated is
+ // called.
+ virtual void OnBackgroundHostCreated(ExtensionHost* host) {}
+
+ // Called immediately after the extension background host is destroyed.
+ virtual void OnBackgroundHostClose(const std::string& extension_id) {}
+
+ // Called when a RenderFrameHost has been registered in an extension process.
+ virtual void OnExtensionFrameRegistered(
+ const std::string& extension_id,
+ content::RenderFrameHost* render_frame_host) {}
+
+ // Called when a RenderFrameHost is no longer part of an extension process.
+ virtual void OnExtensionFrameUnregistered(
+ const std::string& extension_id,
+ content::RenderFrameHost* render_frame_host) {}
+
+ // Called when a RenderFrameHost was navigated to another page within the
+ // extension process.
+ virtual void OnExtensionFrameNavigated(
+ const std::string& extension_id,
+ content::RenderFrameHost* render_frame_host) {}
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_PROCESS_MANAGER_OBSERVER_H_
diff --git a/chromium/extensions/browser/process_manager_unittest.cc b/chromium/extensions/browser/process_manager_unittest.cc
new file mode 100644
index 00000000000..e5336c6736c
--- /dev/null
+++ b/chromium/extensions/browser/process_manager_unittest.cc
@@ -0,0 +1,251 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/process_manager.h"
+
+#include "base/macros.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/site_instance.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/test_browser_context.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extensions_test.h"
+#include "extensions/browser/notification_types.h"
+#include "extensions/browser/process_manager_delegate.h"
+#include "extensions/browser/test_extensions_browser_client.h"
+
+using content::BrowserContext;
+using content::SiteInstance;
+using content::TestBrowserContext;
+
+namespace extensions {
+
+namespace {
+
+// An incognito version of a TestBrowserContext.
+class TestBrowserContextIncognito : public TestBrowserContext {
+ public:
+ TestBrowserContextIncognito() {}
+ ~TestBrowserContextIncognito() override {}
+
+ // TestBrowserContext implementation.
+ bool IsOffTheRecord() const override { return true; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestBrowserContextIncognito);
+};
+
+// A trivial ProcessManagerDelegate.
+class TestProcessManagerDelegate : public ProcessManagerDelegate {
+ public:
+ TestProcessManagerDelegate()
+ : is_background_page_allowed_(true),
+ defer_creating_startup_background_hosts_(false) {}
+ ~TestProcessManagerDelegate() override {}
+
+ // ProcessManagerDelegate implementation.
+ bool IsBackgroundPageAllowed(BrowserContext* context) const override {
+ return is_background_page_allowed_;
+ }
+ bool DeferCreatingStartupBackgroundHosts(
+ BrowserContext* context) const override {
+ return defer_creating_startup_background_hosts_;
+ }
+
+ bool is_background_page_allowed_;
+ bool defer_creating_startup_background_hosts_;
+};
+
+} // namespace
+
+class ProcessManagerTest : public ExtensionsTest {
+ public:
+ ProcessManagerTest()
+ : notification_service_(content::NotificationService::Create()),
+ extension_registry_(browser_context()) {
+ extensions_browser_client()->SetIncognitoContext(&incognito_context_);
+ extensions_browser_client()->set_process_manager_delegate(
+ &process_manager_delegate_);
+ }
+
+ ~ProcessManagerTest() override {}
+
+ // Use original_context() to make it clear it is a non-incognito context.
+ BrowserContext* original_context() { return browser_context(); }
+ BrowserContext* incognito_context() { return &incognito_context_; }
+ ExtensionRegistry* extension_registry() { return &extension_registry_; }
+ TestProcessManagerDelegate* process_manager_delegate() {
+ return &process_manager_delegate_;
+ }
+
+ // Returns true if the notification |type| is registered for |manager| with
+ // source |context|. Pass NULL for |context| for all sources.
+ static bool IsRegistered(ProcessManager* manager,
+ int type,
+ BrowserContext* context) {
+ return manager->registrar_.IsRegistered(
+ manager, type, content::Source<BrowserContext>(context));
+ }
+
+ private:
+ scoped_ptr<content::NotificationService> notification_service_;
+ TestBrowserContextIncognito incognito_context_;
+ ExtensionRegistry extension_registry_; // Shared between BrowserContexts.
+ TestProcessManagerDelegate process_manager_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProcessManagerTest);
+};
+
+// Test that notification registration works properly.
+TEST_F(ProcessManagerTest, ExtensionNotificationRegistration) {
+ // Test for a normal context ProcessManager.
+ scoped_ptr<ProcessManager> manager1(ProcessManager::CreateForTesting(
+ original_context(), extension_registry()));
+
+ EXPECT_EQ(original_context(), manager1->browser_context());
+ EXPECT_EQ(0u, manager1->background_hosts().size());
+
+ // It observes other notifications from this context.
+ EXPECT_TRUE(IsRegistered(manager1.get(),
+ extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
+ original_context()));
+ EXPECT_TRUE(IsRegistered(manager1.get(),
+ extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED,
+ original_context()));
+
+ // Test for an incognito context ProcessManager.
+ scoped_ptr<ProcessManager> manager2(
+ ProcessManager::CreateIncognitoForTesting(incognito_context(),
+ original_context(),
+ extension_registry()));
+
+ EXPECT_EQ(incognito_context(), manager2->browser_context());
+ EXPECT_EQ(0u, manager2->background_hosts().size());
+
+ // Some notifications are observed for the incognito context.
+ EXPECT_TRUE(IsRegistered(manager2.get(),
+ extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED,
+ incognito_context()));
+
+ // Some are not observed at all.
+ EXPECT_FALSE(
+ IsRegistered(manager2.get(),
+ extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
+ original_context()));
+}
+
+// Test that startup background hosts are created when the extension system
+// becomes ready.
+//
+// NOTE: This test and those that follow do not try to create ExtensionsHosts
+// because ExtensionHost is tightly coupled to WebContents and can't be
+// constructed in unit tests.
+TEST_F(ProcessManagerTest, CreateBackgroundHostsOnExtensionsReady) {
+ scoped_ptr<ProcessManager> manager(ProcessManager::CreateForTesting(
+ original_context(), extension_registry()));
+ ASSERT_FALSE(manager->startup_background_hosts_created_for_test());
+
+ // Simulate the extension system becoming ready.
+ content::NotificationService::current()->Notify(
+ extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
+ content::Source<BrowserContext>(original_context()),
+ content::NotificationService::NoDetails());
+ EXPECT_TRUE(manager->startup_background_hosts_created_for_test());
+}
+
+// Test that startup background hosts can be created explicitly before the
+// extension system is ready (this is the normal pattern in Chrome).
+TEST_F(ProcessManagerTest, CreateBackgroundHostsExplicitly) {
+ scoped_ptr<ProcessManager> manager(ProcessManager::CreateForTesting(
+ original_context(), extension_registry()));
+ ASSERT_FALSE(manager->startup_background_hosts_created_for_test());
+
+ // Embedder explicitly asks for hosts to be created. Chrome does this on
+ // normal startup.
+ manager->MaybeCreateStartupBackgroundHosts();
+ EXPECT_TRUE(manager->startup_background_hosts_created_for_test());
+}
+
+// Test that the embedder can defer background host creation. Chrome does this
+// when the profile is created asynchronously, which may take a while.
+TEST_F(ProcessManagerTest, CreateBackgroundHostsDeferred) {
+ scoped_ptr<ProcessManager> manager(ProcessManager::CreateForTesting(
+ original_context(), extension_registry()));
+ ASSERT_FALSE(manager->startup_background_hosts_created_for_test());
+
+ // Don't create background hosts if the delegate says to defer them.
+ process_manager_delegate()->defer_creating_startup_background_hosts_ = true;
+ manager->MaybeCreateStartupBackgroundHosts();
+ EXPECT_FALSE(manager->startup_background_hosts_created_for_test());
+
+ // The extension system becoming ready still doesn't create the hosts.
+ content::NotificationService::current()->Notify(
+ extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
+ content::Source<BrowserContext>(original_context()),
+ content::NotificationService::NoDetails());
+ EXPECT_FALSE(manager->startup_background_hosts_created_for_test());
+
+ // Once the embedder is ready the background hosts can be created.
+ process_manager_delegate()->defer_creating_startup_background_hosts_ = false;
+ manager->MaybeCreateStartupBackgroundHosts();
+ EXPECT_TRUE(manager->startup_background_hosts_created_for_test());
+}
+
+// Test that the embedder can disallow background host creation.
+// Chrome OS does this in guest mode.
+TEST_F(ProcessManagerTest, IsBackgroundHostAllowed) {
+ scoped_ptr<ProcessManager> manager(ProcessManager::CreateForTesting(
+ original_context(), extension_registry()));
+ ASSERT_FALSE(manager->startup_background_hosts_created_for_test());
+
+ // Don't create background hosts if the delegate disallows them.
+ process_manager_delegate()->is_background_page_allowed_ = false;
+ manager->MaybeCreateStartupBackgroundHosts();
+ EXPECT_FALSE(manager->startup_background_hosts_created_for_test());
+
+ // The extension system becoming ready still doesn't create the hosts.
+ content::NotificationService::current()->Notify(
+ extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
+ content::Source<BrowserContext>(original_context()),
+ content::NotificationService::NoDetails());
+ EXPECT_FALSE(manager->startup_background_hosts_created_for_test());
+}
+
+// Test that extensions get grouped in the right SiteInstance (and therefore
+// process) based on their URLs.
+TEST_F(ProcessManagerTest, ProcessGrouping) {
+ // Extensions in different browser contexts should always be different
+ // SiteInstances.
+ scoped_ptr<ProcessManager> manager1(ProcessManager::CreateForTesting(
+ original_context(), extension_registry()));
+ // NOTE: This context is not associated with the TestExtensionsBrowserClient.
+ // That's OK because we're not testing regular vs. incognito behavior.
+ TestBrowserContext another_context;
+ ExtensionRegistry another_registry(&another_context);
+ scoped_ptr<ProcessManager> manager2(
+ ProcessManager::CreateForTesting(&another_context, &another_registry));
+
+ // Extensions with common origins ("scheme://id/") should be grouped in the
+ // same SiteInstance.
+ GURL ext1_url1("chrome-extension://ext1_id/index.html");
+ GURL ext1_url2("chrome-extension://ext1_id/monkey/monkey.html");
+ GURL ext2_url1("chrome-extension://ext2_id/index.html");
+
+ scoped_refptr<SiteInstance> site11 =
+ manager1->GetSiteInstanceForURL(ext1_url1);
+ scoped_refptr<SiteInstance> site12 =
+ manager1->GetSiteInstanceForURL(ext1_url2);
+ EXPECT_EQ(site11, site12);
+
+ scoped_refptr<SiteInstance> site21 =
+ manager1->GetSiteInstanceForURL(ext2_url1);
+ EXPECT_NE(site11, site21);
+
+ scoped_refptr<SiteInstance> other_profile_site =
+ manager2->GetSiteInstanceForURL(ext1_url1);
+ EXPECT_NE(site11, other_profile_site);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/process_map.cc b/chromium/extensions/browser/process_map.cc
new file mode 100644
index 00000000000..b3100201d01
--- /dev/null
+++ b/chromium/extensions/browser/process_map.cc
@@ -0,0 +1,146 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/process_map.h"
+
+#include <tuple>
+
+#include "content/public/browser/child_process_security_policy.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/process_map_factory.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/features/feature.h"
+
+namespace extensions {
+
+// Item
+struct ProcessMap::Item {
+ Item() : process_id(0), site_instance_id(0) {
+ }
+
+ // Purposely implicit constructor needed on older gcc's. See:
+ // http://codereview.chromium.org/8769022/
+ explicit Item(const ProcessMap::Item& other)
+ : extension_id(other.extension_id),
+ process_id(other.process_id),
+ site_instance_id(other.site_instance_id) {
+ }
+
+ Item(const std::string& extension_id, int process_id,
+ int site_instance_id)
+ : extension_id(extension_id),
+ process_id(process_id),
+ site_instance_id(site_instance_id) {
+ }
+
+ ~Item() {
+ }
+
+ bool operator<(const ProcessMap::Item& other) const {
+ return std::tie(extension_id, process_id, site_instance_id) <
+ std::tie(other.extension_id, other.process_id,
+ other.site_instance_id);
+ }
+
+ std::string extension_id;
+ int process_id;
+ int site_instance_id;
+};
+
+
+// ProcessMap
+ProcessMap::ProcessMap() {
+}
+
+ProcessMap::~ProcessMap() {
+}
+
+// static
+ProcessMap* ProcessMap::Get(content::BrowserContext* browser_context) {
+ return ProcessMapFactory::GetForBrowserContext(browser_context);
+}
+
+bool ProcessMap::Insert(const std::string& extension_id, int process_id,
+ int site_instance_id) {
+ return items_.insert(Item(extension_id, process_id, site_instance_id)).second;
+}
+
+bool ProcessMap::Remove(const std::string& extension_id, int process_id,
+ int site_instance_id) {
+ return items_.erase(Item(extension_id, process_id, site_instance_id)) > 0;
+}
+
+int ProcessMap::RemoveAllFromProcess(int process_id) {
+ int result = 0;
+ for (ItemSet::iterator iter = items_.begin(); iter != items_.end(); ) {
+ if (iter->process_id == process_id) {
+ items_.erase(iter++);
+ ++result;
+ } else {
+ ++iter;
+ }
+ }
+ return result;
+}
+
+bool ProcessMap::Contains(const std::string& extension_id,
+ int process_id) const {
+ for (ItemSet::const_iterator iter = items_.begin(); iter != items_.end();
+ ++iter) {
+ if (iter->process_id == process_id && iter->extension_id == extension_id)
+ return true;
+ }
+ return false;
+}
+
+bool ProcessMap::Contains(int process_id) const {
+ for (ItemSet::const_iterator iter = items_.begin(); iter != items_.end();
+ ++iter) {
+ if (iter->process_id == process_id)
+ return true;
+ }
+ return false;
+}
+
+std::set<std::string> ProcessMap::GetExtensionsInProcess(int process_id) const {
+ std::set<std::string> result;
+ for (ItemSet::const_iterator iter = items_.begin(); iter != items_.end();
+ ++iter) {
+ if (iter->process_id == process_id)
+ result.insert(iter->extension_id);
+ }
+ return result;
+}
+
+Feature::Context ProcessMap::GetMostLikelyContextType(
+ const Extension* extension,
+ int process_id) const {
+ // WARNING: This logic must match Dispatcher::ClassifyJavaScriptContext, as
+ // much as possible.
+
+ if (content::ChildProcessSecurityPolicy::GetInstance()->HasWebUIBindings(
+ process_id)) {
+ return Feature::WEBUI_CONTEXT;
+ }
+
+ if (!extension) {
+ return Feature::WEB_PAGE_CONTEXT;
+ }
+
+ if (!Contains(extension->id(), process_id)) {
+ // This could equally be UNBLESSED_EXTENSION_CONTEXT, but we don't record
+ // which processes have extension frames in them.
+ // TODO(kalman): Investigate this.
+ return Feature::CONTENT_SCRIPT_CONTEXT;
+ }
+
+ if (extension->is_hosted_app() &&
+ extension->location() != Manifest::COMPONENT) {
+ return Feature::BLESSED_WEB_PAGE_CONTEXT;
+ }
+
+ return Feature::BLESSED_EXTENSION_CONTEXT;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/process_map.h b/chromium/extensions/browser/process_map.h
new file mode 100644
index 00000000000..ed28a5272d3
--- /dev/null
+++ b/chromium/extensions/browser/process_map.h
@@ -0,0 +1,142 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_PROCESS_MAP_H_
+#define EXTENSIONS_BROWSER_PROCESS_MAP_H_
+
+#include <stddef.h>
+
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/common/features/feature.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+class Extension;
+
+// Contains information about which extensions are assigned to which processes.
+//
+// The relationship between extensions and processes is complex:
+//
+// - Extensions can be either "split" mode or "spanning" mode.
+// - In spanning mode, extensions share a single process between all incognito
+// and normal windows. This was the original mode for extensions.
+// - In split mode, extensions have separate processes in incognito windows.
+// - There are also hosted apps, which are a kind of extensions, and those
+// usually have a process model similar to normal web sites: multiple
+// processes per-profile.
+// - A single hosted app can have more than one SiteInstance in the same process
+// if we're over the process limit and force them to share a process.
+//
+// In general, we seem to play with the process model of extensions a lot, so
+// it is safest to assume it is many-to-many in most places in the codebase.
+//
+// Note that because of content scripts, frames, and other edge cases in
+// Chrome's process isolation, extension code can still end up running outside
+// an assigned process.
+//
+// But we only allow high-privilege operations to be performed by an extension
+// when it is running in an assigned process.
+//
+// ===========================================================================
+// WARNINGS - PLEASE UNDERSTAND THESE BEFORE CALLING OR MODIFYING THIS CLASS
+// ===========================================================================
+//
+// 1. This class contains the processes for hosted apps as well as extensions
+// and packaged apps. Just because a process is present here *does not* mean
+// it is an "extension process" (e.g., for UI purposes). It may contain only
+// hosted apps. See crbug.com/102533.
+//
+// 2. An extension can show up in multiple processes. That is why there is no
+// GetExtensionProcess() method here. There are two cases: a) The extension
+// is actually a hosted app, in which case this is normal, or b) there is an
+// incognito window open and the extension is "split mode". It is *not safe*
+// to assume that there is one process per extension. If you only care about
+// extensions (not hosted apps), and you are on the UI thread, and you don't
+// care about incognito version of this extension (or vice versa if you're in
+// an incognito profile) then use
+// extensions::ProcessManager::GetSiteInstanceForURL()->[Has|Get]Process().
+//
+// 3. The process ids contained in this class are *not limited* to the Profile
+// you got this map from. They can also be associated with that profile's
+// incognito/normal twin. If you care about this, use
+// RenderProcessHost::FromID() and check the profile of the resulting object.
+//
+// TODO(aa): The above warnings suggest this class could use improvement :).
+//
+// TODO(kalman): This class is not threadsafe, but is used on both the UI and
+// IO threads. Somebody should fix that, either make it
+// threadsafe or enforce single thread. Investigation required.
+class ProcessMap : public KeyedService {
+ public:
+ ProcessMap();
+ ~ProcessMap() override;
+
+ // Returns the instance for |browser_context|. An instance is shared between
+ // an incognito and a regular context.
+ static ProcessMap* Get(content::BrowserContext* browser_context);
+
+ size_t size() const { return items_.size(); }
+
+ bool Insert(const std::string& extension_id, int process_id,
+ int site_instance_id);
+
+ bool Remove(const std::string& extension_id, int process_id,
+ int site_instance_id);
+ int RemoveAllFromProcess(int process_id);
+
+ bool Contains(const std::string& extension_id, int process_id) const;
+ bool Contains(int process_id) const;
+
+ std::set<std::string> GetExtensionsInProcess(int process_id) const;
+
+ // Gets the most likely context type for the process with ID |process_id|
+ // which hosts Extension |extension|, if any (may be NULL). Context types are
+ // renderer (JavaScript) concepts but the browser can do a decent job in
+ // guessing what the process hosts.
+ //
+ // |extension| is the funky part - unfortunately we need to trust the
+ // caller of this method to be correct that indeed the context does feature
+ // an extension. This matters for iframes, where an extension could be
+ // hosted in another extension's process (privilege level needs to be
+ // downgraded) or in a web page's process (privilege level needs to be
+ // upgraded).
+ //
+ // The latter of these is slightly problematic from a security perspective;
+ // if a web page renderer gets owned it could try to pretend it's an
+ // extension and get access to some unprivileged APIs. Luckly, when OOP
+ // iframes lauch, it won't be an issue.
+ //
+ // Anyhow, the expected behaviour is:
+ // - For hosted app processes, this will be blessed_web_page.
+ // - For other extension processes, this will be blessed_extension.
+ // - For WebUI processes, this will be a webui.
+ // - For any other extension we have the choice of unblessed_extension or
+ // content_script. Since content scripts are more common, guess that.
+ // We *could* in theory track which web processes have extension frames
+ // in them, and those would be unblessed_extension, but we don't at the
+ // moment, and once OOP iframes exist then there won't even be such a
+ // thing as an unblessed_extension context.
+ // - For anything else, web_page.
+ Feature::Context GetMostLikelyContextType(const Extension* extension,
+ int process_id) const;
+
+ private:
+ struct Item;
+
+ typedef std::set<Item> ItemSet;
+ ItemSet items_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProcessMap);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_PROCESS_MAP_H_
diff --git a/chromium/extensions/browser/process_map_factory.cc b/chromium/extensions/browser/process_map_factory.cc
new file mode 100644
index 00000000000..26c448799fd
--- /dev/null
+++ b/chromium/extensions/browser/process_map_factory.cc
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/process_map_factory.h"
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/process_map.h"
+
+using content::BrowserContext;
+
+namespace extensions {
+
+// static
+ProcessMap* ProcessMapFactory::GetForBrowserContext(BrowserContext* context) {
+ return static_cast<ProcessMap*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+ProcessMapFactory* ProcessMapFactory::GetInstance() {
+ return base::Singleton<ProcessMapFactory>::get();
+}
+
+ProcessMapFactory::ProcessMapFactory()
+ : BrowserContextKeyedServiceFactory(
+ "ProcessMap",
+ BrowserContextDependencyManager::GetInstance()) {
+ // No dependencies on other services.
+}
+
+ProcessMapFactory::~ProcessMapFactory() {}
+
+KeyedService* ProcessMapFactory::BuildServiceInstanceFor(
+ BrowserContext* context) const {
+ return new ProcessMap;
+}
+
+BrowserContext* ProcessMapFactory::GetBrowserContextToUse(
+ BrowserContext* context) const {
+ // Redirected in incognito.
+ return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/process_map_factory.h b/chromium/extensions/browser/process_map_factory.h
new file mode 100644
index 00000000000..051df653150
--- /dev/null
+++ b/chromium/extensions/browser/process_map_factory.h
@@ -0,0 +1,43 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_PROCESS_MAP_FACTORY_H_
+#define EXTENSIONS_BROWSER_PROCESS_MAP_FACTORY_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace extensions {
+
+class ProcessMap;
+
+// Factory for ProcessMap objects. ProcessMap objects are shared between an
+// incognito browser context and its master browser context.
+class ProcessMapFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static ProcessMap* GetForBrowserContext(content::BrowserContext* context);
+
+ static ProcessMapFactory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<ProcessMapFactory>;
+
+ ProcessMapFactory();
+ ~ProcessMapFactory() override;
+
+ // BrowserContextKeyedServiceFactory implementation:
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ProcessMapFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_PROCESS_MAP_FACTORY_H_
diff --git a/chromium/extensions/browser/process_map_unittest.cc b/chromium/extensions/browser/process_map_unittest.cc
new file mode 100644
index 00000000000..5512f75043a
--- /dev/null
+++ b/chromium/extensions/browser/process_map_unittest.cc
@@ -0,0 +1,65 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/process_map.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::ProcessMap;
+
+TEST(ExtensionProcessMapTest, Test) {
+ ProcessMap map;
+
+ // Test behavior when empty.
+ EXPECT_FALSE(map.Contains("a", 1));
+ EXPECT_FALSE(map.Remove("a", 1, 1));
+ EXPECT_EQ(0u, map.size());
+
+ // Test insertion and behavior with one item.
+ EXPECT_TRUE(map.Insert("a", 1, 1));
+ EXPECT_TRUE(map.Contains("a", 1));
+ EXPECT_FALSE(map.Contains("a", 2));
+ EXPECT_FALSE(map.Contains("b", 1));
+ EXPECT_EQ(1u, map.size());
+
+ // Test inserting a duplicate item.
+ EXPECT_FALSE(map.Insert("a", 1, 1));
+ EXPECT_TRUE(map.Contains("a", 1));
+ EXPECT_EQ(1u, map.size());
+
+ // Insert some more items.
+ EXPECT_TRUE(map.Insert("a", 2, 2));
+ EXPECT_TRUE(map.Insert("b", 1, 3));
+ EXPECT_TRUE(map.Insert("b", 2, 4));
+ EXPECT_EQ(4u, map.size());
+
+ EXPECT_TRUE(map.Contains("a", 1));
+ EXPECT_TRUE(map.Contains("a", 2));
+ EXPECT_TRUE(map.Contains("b", 1));
+ EXPECT_TRUE(map.Contains("b", 2));
+ EXPECT_FALSE(map.Contains("a", 3));
+
+ // Note that this only differs from an existing item because of the site
+ // instance id.
+ EXPECT_TRUE(map.Insert("a", 1, 5));
+ EXPECT_TRUE(map.Contains("a", 1));
+
+ // Test removal.
+ EXPECT_TRUE(map.Remove("a", 1, 1));
+ EXPECT_FALSE(map.Remove("a", 1, 1));
+ EXPECT_EQ(4u, map.size());
+
+ // Should still return true because there were two site instances for this
+ // extension/process pair.
+ EXPECT_TRUE(map.Contains("a", 1));
+
+ EXPECT_TRUE(map.Remove("a", 1, 5));
+ EXPECT_EQ(3u, map.size());
+ EXPECT_FALSE(map.Contains("a", 1));
+
+ EXPECT_EQ(2, map.RemoveAllFromProcess(2));
+ EXPECT_EQ(1u, map.size());
+ EXPECT_EQ(0, map.RemoveAllFromProcess(2));
+ EXPECT_EQ(1u, map.size());
+}
diff --git a/chromium/extensions/browser/quota_service.cc b/chromium/extensions/browser/quota_service.cc
new file mode 100644
index 00000000000..dbd11768bf7
--- /dev/null
+++ b/chromium/extensions/browser/quota_service.cc
@@ -0,0 +1,139 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/quota_service.h"
+
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/common/error_utils.h"
+
+namespace {
+
+// If the browser stays open long enough, we reset state once a day.
+// Whatever this value is, it should be an order of magnitude longer than
+// the longest interval in any of the QuotaLimitHeuristics in use.
+const int kPurgeIntervalInDays = 1;
+
+const char kOverQuotaError[] = "This request exceeds the * quota.";
+
+} // namespace
+
+namespace extensions {
+
+QuotaService::QuotaService() {
+ if (base::MessageLoop::current() != NULL) { // Null in unit tests.
+ purge_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromDays(kPurgeIntervalInDays),
+ this,
+ &QuotaService::Purge);
+ }
+}
+
+QuotaService::~QuotaService() {
+ DCHECK(CalledOnValidThread());
+ purge_timer_.Stop();
+ Purge();
+}
+
+std::string QuotaService::Assess(const std::string& extension_id,
+ ExtensionFunction* function,
+ const base::ListValue* args,
+ const base::TimeTicks& event_time) {
+ DCHECK(CalledOnValidThread());
+
+ if (function->ShouldSkipQuotaLimiting())
+ return std::string();
+
+ // Lookup function list for extension.
+ FunctionHeuristicsMap& functions = function_heuristics_[extension_id];
+
+ // Lookup heuristics for function, create if necessary.
+ QuotaLimitHeuristics& heuristics = functions[function->name()];
+ if (heuristics.empty())
+ function->GetQuotaLimitHeuristics(&heuristics);
+
+ if (heuristics.empty())
+ return std::string(); // No heuristic implies no limit.
+
+ QuotaLimitHeuristic* failed_heuristic = NULL;
+ for (QuotaLimitHeuristics::iterator heuristic = heuristics.begin();
+ heuristic != heuristics.end();
+ ++heuristic) {
+ // Apply heuristic to each item (bucket).
+ if (!(*heuristic)->ApplyToArgs(args, event_time)) {
+ failed_heuristic = *heuristic;
+ break;
+ }
+ }
+
+ if (!failed_heuristic)
+ return std::string();
+
+ std::string error = failed_heuristic->GetError();
+ DCHECK_GT(error.length(), 0u);
+ return error;
+}
+
+void QuotaService::PurgeFunctionHeuristicsMap(FunctionHeuristicsMap* map) {
+ FunctionHeuristicsMap::iterator heuristics = map->begin();
+ while (heuristics != map->end()) {
+ STLDeleteElements(&heuristics->second);
+ map->erase(heuristics++);
+ }
+}
+
+void QuotaService::Purge() {
+ DCHECK(CalledOnValidThread());
+ std::map<std::string, FunctionHeuristicsMap>::iterator it =
+ function_heuristics_.begin();
+ for (; it != function_heuristics_.end(); function_heuristics_.erase(it++))
+ PurgeFunctionHeuristicsMap(&it->second);
+}
+
+void QuotaLimitHeuristic::Bucket::Reset(const Config& config,
+ const base::TimeTicks& start) {
+ num_tokens_ = config.refill_token_count;
+ expiration_ = start + config.refill_interval;
+}
+
+void QuotaLimitHeuristic::SingletonBucketMapper::GetBucketsForArgs(
+ const base::ListValue* args,
+ BucketList* buckets) {
+ buckets->push_back(&bucket_);
+}
+
+QuotaLimitHeuristic::QuotaLimitHeuristic(const Config& config,
+ BucketMapper* map,
+ const std::string& name)
+ : config_(config), bucket_mapper_(map), name_(name) {}
+
+QuotaLimitHeuristic::~QuotaLimitHeuristic() {}
+
+bool QuotaLimitHeuristic::ApplyToArgs(const base::ListValue* args,
+ const base::TimeTicks& event_time) {
+ BucketList buckets;
+ bucket_mapper_->GetBucketsForArgs(args, &buckets);
+ for (BucketList::iterator i = buckets.begin(); i != buckets.end(); ++i) {
+ if ((*i)->expiration().is_null()) // A brand new bucket.
+ (*i)->Reset(config_, event_time);
+ if (!Apply(*i, event_time))
+ return false; // It only takes one to spoil it for everyone.
+ }
+ return true;
+}
+
+std::string QuotaLimitHeuristic::GetError() const {
+ return extensions::ErrorUtils::FormatErrorMessage(kOverQuotaError, name_);
+}
+
+bool QuotaService::TimedLimit::Apply(Bucket* bucket,
+ const base::TimeTicks& event_time) {
+ if (event_time > bucket->expiration())
+ bucket->Reset(config(), event_time);
+
+ return bucket->DeductToken();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/quota_service.h b/chromium/extensions/browser/quota_service.h
new file mode 100644
index 00000000000..b4079ded513
--- /dev/null
+++ b/chromium/extensions/browser/quota_service.h
@@ -0,0 +1,214 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The QuotaService uses heuristics to limit abusive requests
+// made by extensions. In this model 'items' (e.g individual bookmarks) are
+// represented by a 'Bucket' that holds state for that item for one single
+// interval of time. The interval of time is defined as 'how long we need to
+// watch an item (for a particular heuristic) before making a decision about
+// quota violations'. A heuristic is two functions: one mapping input
+// arguments to a unique Bucket (the BucketMapper), and another to determine
+// if a new request involving such an item at a given time is a violation.
+
+#ifndef EXTENSIONS_BROWSER_QUOTA_SERVICE_H_
+#define EXTENSIONS_BROWSER_QUOTA_SERVICE_H_
+
+#include <stdint.h>
+
+#include <list>
+#include <map>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/containers/hash_tables.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "base/values.h"
+
+class ExtensionFunction;
+
+namespace extensions {
+class QuotaLimitHeuristic;
+
+typedef std::list<QuotaLimitHeuristic*> QuotaLimitHeuristics;
+
+// The QuotaService takes care that calls to certain extension
+// functions do not exceed predefined quotas.
+//
+// The QuotaService needs to live entirely on one thread, i.e. be created,
+// called and destroyed on the same thread, due to its use of a RepeatingTimer.
+// It is not a KeyedService because instances exist on both the UI
+// and IO threads.
+class QuotaService : public base::NonThreadSafe {
+ public:
+ // Some concrete heuristics (declared below) that ExtensionFunctions can
+ // use to help the service make decisions about quota violations.
+ class TimedLimit;
+
+ QuotaService();
+ virtual ~QuotaService();
+
+ // Decide whether the invocation of |function| with argument |args| by the
+ // extension specified by |extension_id| results in a quota limit violation.
+ // Returns an error message representing the failure if quota was exceeded,
+ // or empty-string if the request is fine and can proceed.
+ std::string Assess(const std::string& extension_id,
+ ExtensionFunction* function,
+ const base::ListValue* args,
+ const base::TimeTicks& event_time);
+
+ private:
+ typedef std::string ExtensionId;
+ typedef std::string FunctionName;
+ // All QuotaLimitHeuristic instances in this map are owned by us.
+ typedef std::map<FunctionName, QuotaLimitHeuristics> FunctionHeuristicsMap;
+
+ // Purge resets all accumulated data as if the service was just created.
+ // Called periodically so we don't consume an unbounded amount of memory
+ // while tracking quota.
+ void Purge();
+ void PurgeFunctionHeuristicsMap(FunctionHeuristicsMap* map);
+ base::RepeatingTimer purge_timer_;
+
+ // Our quota tracking state for extensions that have invoked quota limited
+ // functions. Each extension is treated separately, so extension ids are the
+ // key for the mapping. As an extension invokes functions, the map keeps
+ // track of which functions it has invoked and the heuristics for each one.
+ // Each heuristic will be evaluated and ANDed together to get a final answer.
+ std::map<ExtensionId, FunctionHeuristicsMap> function_heuristics_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuotaService);
+};
+
+// A QuotaLimitHeuristic is two things: 1, A heuristic to map extension
+// function arguments to corresponding Buckets for each input arg, and 2) a
+// heuristic for determining if a new event involving a particular item
+// (represented by its Bucket) constitutes a quota violation.
+class QuotaLimitHeuristic {
+ public:
+ // Parameters to configure the amount of tokens allotted to individual
+ // Bucket objects (see Below) and how often they are replenished.
+ struct Config {
+ // The maximum number of tokens a bucket can contain, and is refilled to
+ // every epoch.
+ int64_t refill_token_count;
+
+ // Specifies how frequently the bucket is logically refilled with tokens.
+ base::TimeDelta refill_interval;
+ };
+
+ // A Bucket is how the heuristic portrays an individual item (since quota
+ // limits are per item) and all associated state for an item that needs to
+ // carry through multiple calls to Apply. It "holds" tokens, which are
+ // debited and credited in response to new events involving the item being
+ // being represented. For convenience, instead of actually periodically
+ // refilling buckets they are just 'Reset' on-demand (e.g. when new events
+ // come in). So, a bucket has an expiration to denote it has becomes stale.
+ class Bucket {
+ public:
+ Bucket() : num_tokens_(0) {}
+ // Removes a token from this bucket, and returns true if the bucket had
+ // any tokens in the first place.
+ bool DeductToken() { return num_tokens_-- > 0; }
+
+ // Returns true if this bucket has tokens to deduct.
+ bool has_tokens() const { return num_tokens_ > 0; }
+
+ // Reset this bucket to specification (from internal configuration), to be
+ // valid from |start| until the first refill interval elapses and it needs
+ // to be reset again.
+ void Reset(const Config& config, const base::TimeTicks& start);
+
+ // The time at which the token count and next expiration should be reset,
+ // via a call to Reset.
+ const base::TimeTicks& expiration() { return expiration_; }
+
+ private:
+ base::TimeTicks expiration_;
+ int64_t num_tokens_;
+ DISALLOW_COPY_AND_ASSIGN(Bucket);
+ };
+ typedef std::list<Bucket*> BucketList;
+
+ // A helper interface to retrieve the bucket corresponding to |args| from
+ // the set of buckets (which is typically stored in the BucketMapper itself)
+ // for this QuotaLimitHeuristic.
+ class BucketMapper {
+ public:
+ virtual ~BucketMapper() {}
+ // In most cases, this should simply extract item IDs from the arguments
+ // (e.g for bookmark operations involving an existing item). If a problem
+ // occurs while parsing |args|, the function aborts - buckets may be non-
+ // empty). The expectation is that invalid args and associated errors are
+ // handled by the ExtensionFunction itself so we don't concern ourselves.
+ virtual void GetBucketsForArgs(const base::ListValue* args,
+ BucketList* buckets) = 0;
+ };
+
+ // Maps all calls to the same bucket, regardless of |args|, for this
+ // QuotaLimitHeuristic.
+ class SingletonBucketMapper : public BucketMapper {
+ public:
+ SingletonBucketMapper() {}
+ ~SingletonBucketMapper() override {}
+ void GetBucketsForArgs(const base::ListValue* args,
+ BucketList* buckets) override;
+
+ private:
+ Bucket bucket_;
+ DISALLOW_COPY_AND_ASSIGN(SingletonBucketMapper);
+ };
+
+ // Ownership of |map| is given to the new QuotaLimitHeuristic.
+ QuotaLimitHeuristic(const Config& config,
+ BucketMapper* map,
+ const std::string& name);
+ virtual ~QuotaLimitHeuristic();
+
+ // Determines if sufficient quota exists (according to the Apply
+ // implementation of a derived class) to perform an operation with |args|,
+ // based on the history of similar operations with similar arguments (which
+ // is retrieved using the BucketMapper).
+ bool ApplyToArgs(const base::ListValue* args,
+ const base::TimeTicks& event_time);
+
+ // Returns an error formatted according to this heuristic.
+ std::string GetError() const;
+
+ protected:
+ const Config& config() { return config_; }
+
+ // Determine if the new event occurring at |event_time| involving |bucket|
+ // constitutes a quota violation according to this heuristic.
+ virtual bool Apply(Bucket* bucket, const base::TimeTicks& event_time) = 0;
+
+ private:
+ friend class QuotaLimitHeuristicTest;
+
+ const Config config_;
+
+ // The mapper used in Map. Cannot be NULL.
+ scoped_ptr<BucketMapper> bucket_mapper_;
+
+ // The name of the heuristic for formatting error messages.
+ std::string name_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuotaLimitHeuristic);
+};
+
+// A simple per-item heuristic to limit the number of events that can occur in
+// a given period of time; e.g "no more than 100 events in an hour".
+class QuotaService::TimedLimit : public QuotaLimitHeuristic {
+ public:
+ TimedLimit(const Config& config, BucketMapper* map, const std::string& name)
+ : QuotaLimitHeuristic(config, map, name) {}
+ bool Apply(Bucket* bucket, const base::TimeTicks& event_time) override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_QUOTA_SERVICE_H_
diff --git a/chromium/extensions/browser/quota_service_unittest.cc b/chromium/extensions/browser/quota_service_unittest.cc
new file mode 100644
index 00000000000..a70e7f2cbe0
--- /dev/null
+++ b/chromium/extensions/browser/quota_service_unittest.cc
@@ -0,0 +1,334 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/process/process.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "content/public/test/test_browser_thread.h"
+#include "extensions/browser/extension_function.h"
+#include "extensions/browser/quota_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+using content::BrowserThread;
+
+namespace extensions {
+
+typedef QuotaLimitHeuristic::Bucket Bucket;
+typedef QuotaLimitHeuristic::Config Config;
+typedef QuotaLimitHeuristic::BucketList BucketList;
+typedef QuotaService::TimedLimit TimedLimit;
+
+namespace {
+
+const char kGenericName[] = "name";
+const Config kFrozenConfig = {0, TimeDelta::FromDays(0)};
+const Config k2PerMinute = {2, TimeDelta::FromMinutes(1)};
+const Config k20PerHour = {20, TimeDelta::FromHours(1)};
+const TimeTicks kStartTime = TimeTicks();
+const TimeTicks k1MinuteAfterStart = kStartTime + TimeDelta::FromMinutes(1);
+
+class Mapper : public QuotaLimitHeuristic::BucketMapper {
+ public:
+ Mapper() {}
+ ~Mapper() override { STLDeleteValues(&buckets_); }
+ void GetBucketsForArgs(const base::ListValue* args,
+ BucketList* buckets) override {
+ for (size_t i = 0; i < args->GetSize(); i++) {
+ int id;
+ ASSERT_TRUE(args->GetInteger(i, &id));
+ if (buckets_.find(id) == buckets_.end())
+ buckets_[id] = new Bucket();
+ buckets->push_back(buckets_[id]);
+ }
+ }
+
+ private:
+ typedef std::map<int, Bucket*> BucketMap;
+ BucketMap buckets_;
+ DISALLOW_COPY_AND_ASSIGN(Mapper);
+};
+
+class MockMapper : public QuotaLimitHeuristic::BucketMapper {
+ public:
+ void GetBucketsForArgs(const base::ListValue* args,
+ BucketList* buckets) override {}
+};
+
+class MockFunction : public ExtensionFunction {
+ public:
+ explicit MockFunction(const char* name) { set_name(name); }
+
+ void SetArgs(const base::ListValue* args) override {}
+ std::string GetError() const override { return std::string(); }
+ void SetError(const std::string& error) override {}
+ void Destruct() const override { delete this; }
+ ResponseAction Run() override { return RespondLater(); }
+ void SendResponse(bool) override {}
+
+ protected:
+ ~MockFunction() override {}
+};
+
+class TimedLimitMockFunction : public MockFunction {
+ public:
+ explicit TimedLimitMockFunction(const char* name) : MockFunction(name) {}
+ void GetQuotaLimitHeuristics(
+ QuotaLimitHeuristics* heuristics) const override {
+ heuristics->push_back(
+ new TimedLimit(k2PerMinute, new Mapper(), kGenericName));
+ }
+
+ private:
+ ~TimedLimitMockFunction() override {}
+};
+
+class FrozenMockFunction : public MockFunction {
+ public:
+ explicit FrozenMockFunction(const char* name) : MockFunction(name) {}
+ void GetQuotaLimitHeuristics(
+ QuotaLimitHeuristics* heuristics) const override {
+ heuristics->push_back(
+ new TimedLimit(kFrozenConfig, new Mapper(), kGenericName));
+ }
+
+ private:
+ ~FrozenMockFunction() override {}
+};
+} // namespace
+
+class QuotaServiceTest : public testing::Test {
+ public:
+ QuotaServiceTest()
+ : extension_a_("a"),
+ extension_b_("b"),
+ extension_c_("c"),
+ loop_(),
+ ui_thread_(BrowserThread::UI, &loop_) {}
+ void SetUp() override { service_.reset(new QuotaService()); }
+ void TearDown() override {
+ loop_.RunUntilIdle();
+ service_.reset();
+ }
+
+ protected:
+ std::string extension_a_;
+ std::string extension_b_;
+ std::string extension_c_;
+ scoped_ptr<QuotaService> service_;
+ base::MessageLoop loop_;
+ content::TestBrowserThread ui_thread_;
+};
+
+class QuotaLimitHeuristicTest : public testing::Test {
+ public:
+ static void DoMoreThan2PerMinuteFor5Minutes(const TimeTicks& start_time,
+ QuotaLimitHeuristic* lim,
+ Bucket* b,
+ int an_unexhausted_minute) {
+ for (int i = 0; i < 5; i++) {
+ // Perform one operation in each minute.
+ int m = i * 60;
+ EXPECT_TRUE(lim->Apply(b, start_time + TimeDelta::FromSeconds(10 + m)));
+ EXPECT_TRUE(b->has_tokens());
+
+ if (i == an_unexhausted_minute)
+ continue; // Don't exhaust all tokens this minute.
+
+ EXPECT_TRUE(lim->Apply(b, start_time + TimeDelta::FromSeconds(15 + m)));
+ EXPECT_FALSE(b->has_tokens());
+
+ // These are OK because we haven't exhausted all buckets.
+ EXPECT_TRUE(lim->Apply(b, start_time + TimeDelta::FromSeconds(20 + m)));
+ EXPECT_FALSE(b->has_tokens());
+ EXPECT_TRUE(lim->Apply(b, start_time + TimeDelta::FromSeconds(50 + m)));
+ EXPECT_FALSE(b->has_tokens());
+ }
+ }
+};
+
+TEST_F(QuotaLimitHeuristicTest, Timed) {
+ TimedLimit lim(k2PerMinute, new MockMapper(), kGenericName);
+ Bucket b;
+
+ b.Reset(k2PerMinute, kStartTime);
+ EXPECT_TRUE(lim.Apply(&b, kStartTime));
+ EXPECT_TRUE(b.has_tokens());
+ EXPECT_TRUE(lim.Apply(&b, kStartTime + TimeDelta::FromSeconds(30)));
+ EXPECT_FALSE(b.has_tokens());
+ EXPECT_FALSE(lim.Apply(&b, k1MinuteAfterStart));
+
+ b.Reset(k2PerMinute, kStartTime);
+ EXPECT_TRUE(lim.Apply(&b, k1MinuteAfterStart - TimeDelta::FromSeconds(1)));
+ EXPECT_TRUE(lim.Apply(&b, k1MinuteAfterStart));
+ EXPECT_TRUE(lim.Apply(&b, k1MinuteAfterStart + TimeDelta::FromSeconds(1)));
+ EXPECT_TRUE(lim.Apply(&b, k1MinuteAfterStart + TimeDelta::FromSeconds(2)));
+ EXPECT_FALSE(lim.Apply(&b, k1MinuteAfterStart + TimeDelta::FromSeconds(3)));
+}
+
+TEST_F(QuotaServiceTest, NoHeuristic) {
+ scoped_refptr<MockFunction> f(new MockFunction("foo"));
+ base::ListValue args;
+ EXPECT_EQ("", service_->Assess(extension_a_, f.get(), &args, kStartTime));
+}
+
+TEST_F(QuotaServiceTest, FrozenHeuristic) {
+ scoped_refptr<MockFunction> f(new FrozenMockFunction("foo"));
+ base::ListValue args;
+ args.Append(new base::FundamentalValue(1));
+ EXPECT_NE("", service_->Assess(extension_a_, f.get(), &args, kStartTime));
+}
+
+TEST_F(QuotaServiceTest, SingleHeuristic) {
+ scoped_refptr<MockFunction> f(new TimedLimitMockFunction("foo"));
+ base::ListValue args;
+ args.Append(new base::FundamentalValue(1));
+ EXPECT_EQ("", service_->Assess(extension_a_, f.get(), &args, kStartTime));
+ EXPECT_EQ("",
+ service_->Assess(extension_a_,
+ f.get(),
+ &args,
+ kStartTime + TimeDelta::FromSeconds(10)));
+ EXPECT_NE("",
+ service_->Assess(extension_a_,
+ f.get(),
+ &args,
+ kStartTime + TimeDelta::FromSeconds(15)));
+
+ base::ListValue args2;
+ args2.Append(new base::FundamentalValue(1));
+ args2.Append(new base::FundamentalValue(2));
+ EXPECT_EQ("", service_->Assess(extension_b_, f.get(), &args2, kStartTime));
+ EXPECT_EQ("",
+ service_->Assess(extension_b_,
+ f.get(),
+ &args2,
+ kStartTime + TimeDelta::FromSeconds(10)));
+
+ TimeDelta peace = TimeDelta::FromMinutes(30);
+ EXPECT_EQ("",
+ service_->Assess(extension_b_, f.get(), &args, kStartTime + peace));
+ EXPECT_EQ("",
+ service_->Assess(extension_b_,
+ f.get(),
+ &args,
+ kStartTime + peace + TimeDelta::FromSeconds(10)));
+ EXPECT_NE("",
+ service_->Assess(extension_b_,
+ f.get(),
+ &args2,
+ kStartTime + peace + TimeDelta::FromSeconds(15)));
+
+ // Test that items are independent.
+ base::ListValue args3;
+ args3.Append(new base::FundamentalValue(3));
+ EXPECT_EQ("", service_->Assess(extension_c_, f.get(), &args, kStartTime));
+ EXPECT_EQ("",
+ service_->Assess(extension_c_,
+ f.get(),
+ &args3,
+ kStartTime + TimeDelta::FromSeconds(10)));
+ EXPECT_EQ("",
+ service_->Assess(extension_c_,
+ f.get(),
+ &args,
+ kStartTime + TimeDelta::FromSeconds(15)));
+ EXPECT_EQ("",
+ service_->Assess(extension_c_,
+ f.get(),
+ &args3,
+ kStartTime + TimeDelta::FromSeconds(20)));
+ EXPECT_NE("",
+ service_->Assess(extension_c_,
+ f.get(),
+ &args,
+ kStartTime + TimeDelta::FromSeconds(25)));
+ EXPECT_NE("",
+ service_->Assess(extension_c_,
+ f.get(),
+ &args3,
+ kStartTime + TimeDelta::FromSeconds(30)));
+}
+
+TEST_F(QuotaServiceTest, MultipleFunctionsDontInterfere) {
+ scoped_refptr<MockFunction> f(new TimedLimitMockFunction("foo"));
+ scoped_refptr<MockFunction> g(new TimedLimitMockFunction("bar"));
+
+ base::ListValue args_f;
+ base::ListValue args_g;
+ args_f.Append(new base::FundamentalValue(1));
+ args_g.Append(new base::FundamentalValue(2));
+
+ EXPECT_EQ("", service_->Assess(extension_a_, f.get(), &args_f, kStartTime));
+ EXPECT_EQ("", service_->Assess(extension_a_, g.get(), &args_g, kStartTime));
+ EXPECT_EQ("",
+ service_->Assess(extension_a_,
+ f.get(),
+ &args_f,
+ kStartTime + TimeDelta::FromSeconds(10)));
+ EXPECT_EQ("",
+ service_->Assess(extension_a_,
+ g.get(),
+ &args_g,
+ kStartTime + TimeDelta::FromSeconds(10)));
+ EXPECT_NE("",
+ service_->Assess(extension_a_,
+ f.get(),
+ &args_f,
+ kStartTime + TimeDelta::FromSeconds(15)));
+ EXPECT_NE("",
+ service_->Assess(extension_a_,
+ g.get(),
+ &args_g,
+ kStartTime + TimeDelta::FromSeconds(15)));
+}
+
+TEST_F(QuotaServiceTest, ViolatorsWillBeForgiven) {
+ scoped_refptr<MockFunction> f(new TimedLimitMockFunction("foo"));
+ base::ListValue arg;
+ arg.Append(new base::FundamentalValue(1));
+ EXPECT_EQ("", service_->Assess(extension_a_, f.get(), &arg, kStartTime));
+ EXPECT_EQ("",
+ service_->Assess(extension_a_,
+ f.get(),
+ &arg,
+ kStartTime + TimeDelta::FromSeconds(10)));
+ EXPECT_NE("",
+ service_->Assess(extension_a_,
+ f.get(),
+ &arg,
+ kStartTime + TimeDelta::FromSeconds(15)));
+
+ // Waiting a while will give the extension access to the function again.
+ EXPECT_EQ("", service_->Assess(extension_a_, f.get(), &arg,
+ kStartTime + TimeDelta::FromDays(1)));
+
+ // And lose it again soon after.
+ EXPECT_EQ("", service_->Assess(extension_a_, f.get(), &arg,
+ kStartTime + TimeDelta::FromDays(1) +
+ TimeDelta::FromSeconds(10)));
+ EXPECT_NE("", service_->Assess(extension_a_, f.get(), &arg,
+ kStartTime + TimeDelta::FromDays(1) +
+ TimeDelta::FromSeconds(15)));
+
+ // Going further over quota should continue to fail within this time period,
+ // but still all restored later.
+ EXPECT_NE("", service_->Assess(extension_a_, f.get(), &arg,
+ kStartTime + TimeDelta::FromDays(1) +
+ TimeDelta::FromSeconds(20)));
+ EXPECT_NE("", service_->Assess(extension_a_, f.get(), &arg,
+ kStartTime + TimeDelta::FromDays(1) +
+ TimeDelta::FromSeconds(25)));
+
+ // Like now.
+ EXPECT_EQ("", service_->Assess(extension_a_, f.get(), &arg,
+ kStartTime + TimeDelta::FromDays(2)));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/renderer_startup_helper.cc b/chromium/extensions/browser/renderer_startup_helper.cc
new file mode 100644
index 00000000000..d268a63b09a
--- /dev/null
+++ b/chromium/extensions/browser/renderer_startup_helper.cc
@@ -0,0 +1,120 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/renderer_startup_helper.h"
+
+#include "base/values.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_process_host.h"
+#include "extensions/browser/extension_function_dispatcher.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/guest_view/web_view/web_view_guest.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/extensions_client.h"
+#include "ui/base/webui/web_ui_util.h"
+
+using content::BrowserContext;
+
+namespace extensions {
+
+RendererStartupHelper::RendererStartupHelper(BrowserContext* browser_context)
+ : browser_context_(browser_context) {
+ DCHECK(browser_context);
+ registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED,
+ content::NotificationService::AllBrowserContextsAndSources());
+}
+
+RendererStartupHelper::~RendererStartupHelper() {}
+
+void RendererStartupHelper::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ DCHECK_EQ(content::NOTIFICATION_RENDERER_PROCESS_CREATED, type);
+ content::RenderProcessHost* process =
+ content::Source<content::RenderProcessHost>(source).ptr();
+ if (!ExtensionsBrowserClient::Get()->IsSameContext(
+ browser_context_, process->GetBrowserContext()))
+ return;
+
+ // Platform apps need to know the system font.
+ // TODO(dbeam): this is not the system font in all cases.
+ process->Send(new ExtensionMsg_SetSystemFont(webui::GetFontFamily(),
+ webui::GetFontSize()));
+
+ // Scripting whitelist. This is modified by tests and must be communicated
+ // to renderers.
+ process->Send(new ExtensionMsg_SetScriptingWhitelist(
+ extensions::ExtensionsClient::Get()->GetScriptingWhitelist()));
+
+ // If the new render process is a WebView guest process, propagate the WebView
+ // partition ID to it.
+ std::string webview_partition_id = WebViewGuest::GetPartitionID(process);
+ if (!webview_partition_id.empty()) {
+ process->Send(new ExtensionMsg_SetWebViewPartitionID(
+ WebViewGuest::GetPartitionID(process)));
+ }
+
+ // Loaded extensions.
+ std::vector<ExtensionMsg_Loaded_Params> loaded_extensions;
+ const ExtensionSet& extensions =
+ ExtensionRegistry::Get(browser_context_)->enabled_extensions();
+ for (const auto& ext : extensions) {
+ // Renderers don't need to know about themes.
+ if (!ext->is_theme()) {
+ // TODO(kalman): Only include tab specific permissions for extension
+ // processes, no other process needs it, so it's mildly wasteful.
+ // I am not sure this is possible to know this here, at such a low
+ // level of the stack. Perhaps site isolation can help.
+ bool include_tab_permissions = true;
+ loaded_extensions.push_back(
+ ExtensionMsg_Loaded_Params(ext.get(), include_tab_permissions));
+ }
+ }
+ process->Send(new ExtensionMsg_Loaded(loaded_extensions));
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+// static
+RendererStartupHelper* RendererStartupHelperFactory::GetForBrowserContext(
+ BrowserContext* context) {
+ return static_cast<RendererStartupHelper*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+RendererStartupHelperFactory* RendererStartupHelperFactory::GetInstance() {
+ return base::Singleton<RendererStartupHelperFactory>::get();
+}
+
+RendererStartupHelperFactory::RendererStartupHelperFactory()
+ : BrowserContextKeyedServiceFactory(
+ "RendererStartupHelper",
+ BrowserContextDependencyManager::GetInstance()) {
+ // No dependencies on other services.
+}
+
+RendererStartupHelperFactory::~RendererStartupHelperFactory() {}
+
+KeyedService* RendererStartupHelperFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ return new RendererStartupHelper(context);
+}
+
+BrowserContext* RendererStartupHelperFactory::GetBrowserContextToUse(
+ BrowserContext* context) const {
+ // Redirected in incognito.
+ return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+}
+
+bool RendererStartupHelperFactory::ServiceIsCreatedWithBrowserContext() const {
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/renderer_startup_helper.h b/chromium/extensions/browser/renderer_startup_helper.h
new file mode 100644
index 00000000000..5aa66154d7e
--- /dev/null
+++ b/chromium/extensions/browser/renderer_startup_helper.h
@@ -0,0 +1,75 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_RENDERER_STARTUP_HELPER_H_
+#define EXTENSIONS_BROWSER_RENDERER_STARTUP_HELPER_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+namespace content {
+class BrowserContext;
+class RenderProcessHost;
+}
+
+namespace extensions {
+
+// Informs renderers about extensions-related data (loaded extensions, available
+// functions, etc.) when they start. Sends this information to both extension
+// and non-extension renderers, as the non-extension renderers may have content
+// scripts. Lives on the UI thread. Shared between incognito and non-incognito
+// browser contexts.
+class RendererStartupHelper : public KeyedService,
+ public content::NotificationObserver {
+ public:
+ // This class sends messages to all renderers started for |browser_context|.
+ explicit RendererStartupHelper(content::BrowserContext* browser_context);
+ ~RendererStartupHelper() override;
+
+ // content::NotificationObserver overrides:
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override;
+
+ private:
+ content::BrowserContext* browser_context_; // Not owned.
+
+ content::NotificationRegistrar registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(RendererStartupHelper);
+};
+
+// Factory for RendererStartupHelpers. Declared here because this header is
+// rarely included and it's probably cheaper to put it here than to make the
+// compiler generate another object file.
+class RendererStartupHelperFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static RendererStartupHelper* GetForBrowserContext(
+ content::BrowserContext* context);
+ static RendererStartupHelperFactory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<RendererStartupHelperFactory>;
+
+ RendererStartupHelperFactory();
+ ~RendererStartupHelperFactory() override;
+
+ // BrowserContextKeyedServiceFactory implementation:
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* profile) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+ bool ServiceIsCreatedWithBrowserContext() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(RendererStartupHelperFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_RENDERER_STARTUP_HELPER_H_
diff --git a/chromium/extensions/browser/requirements_checker.h b/chromium/extensions/browser/requirements_checker.h
new file mode 100644
index 00000000000..a5c16140f87
--- /dev/null
+++ b/chromium/extensions/browser/requirements_checker.h
@@ -0,0 +1,38 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_REQUIREMENTS_CHECKER_H_
+#define CHROME_BROWSER_EXTENSIONS_REQUIREMENTS_CHECKER_H_
+
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+
+namespace extensions {
+class Extension;
+
+// Validates the 'requirements' extension manifest field. This is an
+// asynchronous process that involves several threads, but the public interface
+// of this class (including constructor and destructor) must only be used on
+// the UI thread.
+class RequirementsChecker {
+ public:
+ virtual ~RequirementsChecker() {}
+
+ using RequirementsCheckedCallback =
+ base::Callback<void(const std::vector<std::string>& /* requirements */)>;
+
+ // The vector passed to the callback are any localized errors describing
+ // requirement violations. If this vector is non-empty, requirements checking
+ // failed. This should only be called once. |callback| will always be invoked
+ // asynchronously on the UI thread. |callback| will only be called once, and
+ // will be reset after called.
+ virtual void Check(const scoped_refptr<const Extension>& extension,
+ const RequirementsCheckedCallback& callback) = 0;
+};
+
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_REQUIREMENTS_CHECKER_H_
diff --git a/chromium/extensions/browser/resources/default_100_percent/app_default_icon.png b/chromium/extensions/browser/resources/default_100_percent/app_default_icon.png
new file mode 100644
index 00000000000..0454cfbee5b
--- /dev/null
+++ b/chromium/extensions/browser/resources/default_100_percent/app_default_icon.png
Binary files differ
diff --git a/chromium/extensions/browser/resources/default_100_percent/extension_action_plain_background.png b/chromium/extensions/browser/resources/default_100_percent/extension_action_plain_background.png
new file mode 100644
index 00000000000..75aff12f544
--- /dev/null
+++ b/chromium/extensions/browser/resources/default_100_percent/extension_action_plain_background.png
Binary files differ
diff --git a/chromium/extensions/browser/resources/default_100_percent/extension_default_icon.png b/chromium/extensions/browser/resources/default_100_percent/extension_default_icon.png
new file mode 100644
index 00000000000..8730a53deec
--- /dev/null
+++ b/chromium/extensions/browser/resources/default_100_percent/extension_default_icon.png
Binary files differ
diff --git a/chromium/extensions/browser/resources/default_100_percent/extension_icon_plain_background.png b/chromium/extensions/browser/resources/default_100_percent/extension_icon_plain_background.png
new file mode 100644
index 00000000000..7bd8080d480
--- /dev/null
+++ b/chromium/extensions/browser/resources/default_100_percent/extension_icon_plain_background.png
Binary files differ
diff --git a/chromium/extensions/browser/resources/default_200_percent/app_default_icon.png b/chromium/extensions/browser/resources/default_200_percent/app_default_icon.png
new file mode 100644
index 00000000000..ff822cade43
--- /dev/null
+++ b/chromium/extensions/browser/resources/default_200_percent/app_default_icon.png
Binary files differ
diff --git a/chromium/extensions/browser/resources/default_200_percent/extension_action_plain_background.png b/chromium/extensions/browser/resources/default_200_percent/extension_action_plain_background.png
new file mode 100644
index 00000000000..75aff12f544
--- /dev/null
+++ b/chromium/extensions/browser/resources/default_200_percent/extension_action_plain_background.png
Binary files differ
diff --git a/chromium/extensions/browser/resources/default_200_percent/extension_default_icon.png b/chromium/extensions/browser/resources/default_200_percent/extension_default_icon.png
new file mode 100644
index 00000000000..e1d3585f143
--- /dev/null
+++ b/chromium/extensions/browser/resources/default_200_percent/extension_default_icon.png
Binary files differ
diff --git a/chromium/extensions/browser/resources/default_200_percent/extension_icon_plain_background.png b/chromium/extensions/browser/resources/default_200_percent/extension_icon_plain_background.png
new file mode 100644
index 00000000000..41919295286
--- /dev/null
+++ b/chromium/extensions/browser/resources/default_200_percent/extension_icon_plain_background.png
Binary files differ
diff --git a/chromium/extensions/browser/resources/extensions_browser_resources.grd b/chromium/extensions/browser/resources/extensions_browser_resources.grd
new file mode 100644
index 00000000000..986216389ce
--- /dev/null
+++ b/chromium/extensions/browser/resources/extensions_browser_resources.grd
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1">
+ <outputs>
+ <output filename="grit/extensions_browser_resources.h" type="rc_header" context="default_100_percent">
+ <emit emit_type='prepend'></emit>
+ </output>
+ <output filename="grit/extensions_browser_resources_map.cc" type="resource_map_source" context="default_100_percent" />
+ <output filename="grit/extensions_browser_resources_map.h" type="resource_map_header" context="default_100_percent" />
+ <output filename="extensions_browser_resources_100_percent.pak" type="data_package" context="default_100_percent" />
+ <output filename="extensions_browser_resources_200_percent.pak" type="data_package" context="default_200_percent" />
+ </outputs>
+ <release seq="1">
+ <structures fallback_to_low_resolution="true">
+ <structure type="chrome_scaled_image" name="IDR_APP_DEFAULT_ICON" file="app_default_icon.png" />
+ <structure type="chrome_scaled_image" name="IDR_EXTENSION_DEFAULT_ICON" file="extension_default_icon.png" />
+ <structure type="chrome_scaled_image" name="IDR_EXTENSION_ACTION_PLAIN_BACKGROUND" file="extension_action_plain_background.png"/>
+ <structure type="chrome_scaled_image" name="IDR_EXTENSION_ICON_PLAIN_BACKGROUND" file="extension_icon_plain_background.png"/>
+ </structures>
+ </release>
+</grit>
diff --git a/chromium/extensions/browser/runtime_data.cc b/chromium/extensions/browser/runtime_data.cc
new file mode 100644
index 00000000000..3cee8466d59
--- /dev/null
+++ b/chromium/extensions/browser/runtime_data.cc
@@ -0,0 +1,84 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/runtime_data.h"
+
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handlers/background_info.h"
+
+namespace extensions {
+
+RuntimeData::RuntimeData(ExtensionRegistry* registry) : registry_(registry) {
+ registry_->AddObserver(this);
+}
+
+RuntimeData::~RuntimeData() {
+ registry_->RemoveObserver(this);
+}
+
+bool RuntimeData::IsBackgroundPageReady(const Extension* extension) const {
+ if (!BackgroundInfo::HasPersistentBackgroundPage(extension))
+ return true;
+ return HasFlag(extension->id(), BACKGROUND_PAGE_READY);
+}
+
+void RuntimeData::SetBackgroundPageReady(const std::string& extension_id,
+ bool value) {
+ SetFlag(extension_id, BACKGROUND_PAGE_READY, value);
+}
+
+bool RuntimeData::IsBeingUpgraded(const std::string& extension_id) const {
+ return HasFlag(extension_id, BEING_UPGRADED);
+}
+
+void RuntimeData::SetBeingUpgraded(const std::string& extension_id,
+ bool value) {
+ SetFlag(extension_id, BEING_UPGRADED, value);
+}
+
+bool RuntimeData::HasUsedWebRequest(const std::string& extension_id) const {
+ return HasFlag(extension_id, HAS_USED_WEBREQUEST);
+}
+
+void RuntimeData::SetHasUsedWebRequest(const std::string& extension_id,
+ bool value) {
+ SetFlag(extension_id, HAS_USED_WEBREQUEST, value);
+}
+
+bool RuntimeData::HasExtensionForTesting(
+ const std::string& extension_id) const {
+ return extension_flags_.find(extension_id) != extension_flags_.end();
+}
+
+void RuntimeData::ClearAll() {
+ extension_flags_.clear();
+}
+
+void RuntimeData::OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ ExtensionFlagsMap::iterator iter = extension_flags_.find(extension->id());
+ if (iter != extension_flags_.end())
+ iter->second = iter->second & kPersistAcrossUnloadMask;
+}
+
+bool RuntimeData::HasFlag(const std::string& extension_id,
+ RuntimeFlag flag) const {
+ ExtensionFlagsMap::const_iterator it = extension_flags_.find(extension_id);
+ if (it == extension_flags_.end())
+ return false;
+ return !!(it->second & flag);
+}
+
+void RuntimeData::SetFlag(const std::string& extension_id,
+ RuntimeFlag flag,
+ bool value) {
+ if (value)
+ extension_flags_[extension_id] |= flag;
+ else
+ extension_flags_[extension_id] &= ~flag;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/runtime_data.h b/chromium/extensions/browser/runtime_data.h
new file mode 100644
index 00000000000..c937a99f7f7
--- /dev/null
+++ b/chromium/extensions/browser/runtime_data.h
@@ -0,0 +1,92 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_RUNTIME_DATA_H_
+#define EXTENSIONS_BROWSER_RUNTIME_DATA_H_
+
+#include <map>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "extensions/browser/extension_registry_observer.h"
+
+namespace extensions {
+
+class ExtensionRegistry;
+
+// Contains per-extension data that can change during the life of the process,
+// but does not persist across restarts. Shared between incognito and regular
+// browser contexts. Lives on the UI thread. Must be destroyed before
+// ExtensionRegistry.
+// If we start putting to much into this, we should expose the generic
+// "[G|S]etRuntimeProperty(const std::string& extension_id, RuntimeFlag flag)"
+// instead of all these.
+class RuntimeData : public ExtensionRegistryObserver {
+ public:
+ // Observes |registry| to clean itself up when extensions change state.
+ // |registry| must not be NULL.
+ explicit RuntimeData(ExtensionRegistry* registry);
+ ~RuntimeData() override;
+
+ // Whether the persistent background page, if any, is ready. We don't load
+ // other components until then. If there is no background page, or if it is
+ // non-persistent (lazy), we consider it to be ready.
+ bool IsBackgroundPageReady(const Extension* extension) const;
+ void SetBackgroundPageReady(const std::string& extension_id, bool value);
+
+ // Getter and setter for the flag that specifies whether the extension is
+ // being upgraded.
+ // For these purposes, a reload counts as an upgrade.
+ bool IsBeingUpgraded(const std::string& extension_id) const;
+ void SetBeingUpgraded(const std::string& extension_id, bool value);
+
+ // Getter and setter for the flag that specifies if the extension has used
+ // the webrequest API.
+ bool HasUsedWebRequest(const std::string& extension_id) const;
+ void SetHasUsedWebRequest(const std::string& extension_id, bool value);
+
+ // Returns true if the extension is being tracked. Used only for testing.
+ bool HasExtensionForTesting(const std::string& extension_id) const;
+
+ // Erase runtime data for all extensions. Used only for testing. Cannot be
+ // named ClearAllForTesting due to false-positive presubmit errors.
+ void ClearAll();
+
+ // ExtensionRegistryObserver overrides. Public for testing.
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+
+ private:
+ // Bitmasks for runtime states.
+ enum RuntimeFlag {
+ // Set if the background page is ready.
+ BACKGROUND_PAGE_READY = 1 << 0,
+ // Set while the extension is being upgraded.
+ BEING_UPGRADED = 1 << 1,
+ // Set if the extension has used the webRequest API.
+ HAS_USED_WEBREQUEST = 1 << 2,
+ };
+
+ // The mask of any data that should persist across the extension being
+ // unloaded.
+ static const int kPersistAcrossUnloadMask = BEING_UPGRADED;
+
+ // Returns the setting for the flag or false if the extension isn't found.
+ bool HasFlag(const std::string& extension_id, RuntimeFlag flag) const;
+
+ // Sets |flag| for |extension| to |value|. Adds |extension| to the list of
+ // extensions if it isn't present.
+ void SetFlag(const std::string& extension_id, RuntimeFlag flag, bool value);
+
+ // Map from extension ID to the RuntimeFlags bits.
+ typedef std::map<std::string, int> ExtensionFlagsMap;
+ ExtensionFlagsMap extension_flags_;
+
+ ExtensionRegistry* registry_; // Not owned.
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_RUNTIME_DATA_H_
diff --git a/chromium/extensions/browser/runtime_data_unittest.cc b/chromium/extensions/browser/runtime_data_unittest.cc
new file mode 100644
index 00000000000..790dd257bc0
--- /dev/null
+++ b/chromium/extensions/browser/runtime_data_unittest.cc
@@ -0,0 +1,106 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/runtime_data.h"
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/test_util.h"
+#include "extensions/common/value_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+namespace {
+
+// Creates a very simple extension with a background page.
+scoped_refptr<Extension> CreateExtensionWithBackgroundPage() {
+ return ExtensionBuilder()
+ .SetManifest(DictionaryBuilder()
+ .Set("name", "test")
+ .Set("version", "0.1")
+ .Set("background",
+ DictionaryBuilder().Set("page", "bg.html").Build())
+ .Build())
+ .SetID("id2")
+ .Build();
+}
+
+class RuntimeDataTest : public testing::Test {
+ public:
+ RuntimeDataTest() : registry_(NULL), runtime_data_(&registry_) {}
+ ~RuntimeDataTest() override {}
+
+ protected:
+ ExtensionRegistry registry_;
+ RuntimeData runtime_data_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RuntimeDataTest);
+};
+
+TEST_F(RuntimeDataTest, IsBackgroundPageReady) {
+ // An extension without a background page is always considered ready.
+ scoped_refptr<Extension> no_background = test_util::CreateEmptyExtension();
+ EXPECT_TRUE(runtime_data_.IsBackgroundPageReady(no_background.get()));
+
+ // An extension with a background page is not ready until the flag is set.
+ scoped_refptr<Extension> with_background =
+ CreateExtensionWithBackgroundPage();
+ EXPECT_FALSE(runtime_data_.IsBackgroundPageReady(with_background.get()));
+
+ // The flag can be toggled.
+ runtime_data_.SetBackgroundPageReady(with_background->id(), true);
+ EXPECT_TRUE(runtime_data_.IsBackgroundPageReady(with_background.get()));
+ runtime_data_.SetBackgroundPageReady(with_background->id(), false);
+ EXPECT_FALSE(runtime_data_.IsBackgroundPageReady(with_background.get()));
+}
+
+TEST_F(RuntimeDataTest, IsBeingUpgraded) {
+ scoped_refptr<Extension> extension = test_util::CreateEmptyExtension();
+
+ // An extension is not being upgraded until the flag is set.
+ EXPECT_FALSE(runtime_data_.IsBeingUpgraded(extension->id()));
+
+ // The flag can be toggled.
+ runtime_data_.SetBeingUpgraded(extension->id(), true);
+ EXPECT_TRUE(runtime_data_.IsBeingUpgraded(extension->id()));
+ runtime_data_.SetBeingUpgraded(extension->id(), false);
+ EXPECT_FALSE(runtime_data_.IsBeingUpgraded(extension->id()));
+}
+
+TEST_F(RuntimeDataTest, HasUsedWebRequest) {
+ scoped_refptr<Extension> extension = test_util::CreateEmptyExtension();
+
+ // An extension has not used web request until the flag is set.
+ EXPECT_FALSE(runtime_data_.HasUsedWebRequest(extension->id()));
+
+ // The flag can be toggled.
+ runtime_data_.SetHasUsedWebRequest(extension->id(), true);
+ EXPECT_TRUE(runtime_data_.HasUsedWebRequest(extension->id()));
+ runtime_data_.SetHasUsedWebRequest(extension->id(), false);
+ EXPECT_FALSE(runtime_data_.HasUsedWebRequest(extension->id()));
+}
+
+// Unloading an extension erases any data that shouldn't explicitly be kept
+// across loads.
+TEST_F(RuntimeDataTest, OnExtensionUnloaded) {
+ scoped_refptr<Extension> extension = CreateExtensionWithBackgroundPage();
+ runtime_data_.SetBackgroundPageReady(extension->id(), true);
+ ASSERT_TRUE(runtime_data_.HasExtensionForTesting(extension->id()));
+ runtime_data_.SetBeingUpgraded(extension->id(), true);
+
+ runtime_data_.OnExtensionUnloaded(
+ NULL, extension.get(), UnloadedExtensionInfo::REASON_DISABLE);
+ EXPECT_TRUE(runtime_data_.HasExtensionForTesting(extension->id()));
+ EXPECT_FALSE(runtime_data_.IsBackgroundPageReady(extension.get()));
+ EXPECT_TRUE(runtime_data_.IsBeingUpgraded(extension->id()));
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/browser/sandboxed_unpacker.cc b/chromium/extensions/browser/sandboxed_unpacker.cc
new file mode 100644
index 00000000000..ab84c2adbd9
--- /dev/null
+++ b/chromium/extensions/browser/sandboxed_unpacker.cc
@@ -0,0 +1,934 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/sandboxed_unpacker.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <set>
+#include <tuple>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/path_service.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "build/build_config.h"
+#include "components/crx_file/crx_file.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/utility_process_host.h"
+#include "content/public/common/common_param_traits.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_l10n_util.h"
+#include "extensions/common/extension_utility_messages.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/file_util.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/icons_handler.h"
+#include "extensions/common/switches.h"
+#include "grit/extensions_strings.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/codec/png_codec.h"
+
+using base::ASCIIToUTF16;
+using content::BrowserThread;
+using content::UtilityProcessHost;
+using crx_file::CrxFile;
+
+// The following macro makes histograms that record the length of paths
+// in this file much easier to read.
+// Windows has a short max path length. If the path length to a
+// file being unpacked from a CRX exceeds the max length, we might
+// fail to install. To see if this is happening, see how long the
+// path to the temp unpack directory is. See crbug.com/69693 .
+#define PATH_LENGTH_HISTOGRAM(name, path) \
+ UMA_HISTOGRAM_CUSTOM_COUNTS(name, path.value().length(), 0, 500, 100)
+
+// Record a rate (kB per second) at which extensions are unpacked.
+// Range from 1kB/s to 100mB/s.
+#define UNPACK_RATE_HISTOGRAM(name, rate) \
+ UMA_HISTOGRAM_CUSTOM_COUNTS(name, rate, 1, 100000, 100);
+
+namespace extensions {
+namespace {
+
+void RecordSuccessfulUnpackTimeHistograms(const base::FilePath& crx_path,
+ const base::TimeDelta unpack_time) {
+ const int64_t kBytesPerKb = 1024;
+ const int64_t kBytesPerMb = 1024 * 1024;
+
+ UMA_HISTOGRAM_TIMES("Extensions.SandboxUnpackSuccessTime", unpack_time);
+
+ // To get a sense of how CRX size impacts unpack time, record unpack
+ // time for several increments of CRX size.
+ int64_t crx_file_size;
+ if (!base::GetFileSize(crx_path, &crx_file_size)) {
+ UMA_HISTOGRAM_COUNTS("Extensions.SandboxUnpackSuccessCantGetCrxSize", 1);
+ return;
+ }
+
+ // Cast is safe as long as the number of bytes in the CRX is less than
+ // 2^31 * 2^10.
+ int crx_file_size_kb = static_cast<int>(crx_file_size / kBytesPerKb);
+ UMA_HISTOGRAM_COUNTS("Extensions.SandboxUnpackSuccessCrxSize",
+ crx_file_size_kb);
+
+ // We have time in seconds and file size in bytes. We want the rate bytes are
+ // unpacked in kB/s.
+ double file_size_kb =
+ static_cast<double>(crx_file_size) / static_cast<double>(kBytesPerKb);
+ int unpack_rate_kb_per_s =
+ static_cast<int>(file_size_kb / unpack_time.InSecondsF());
+ UNPACK_RATE_HISTOGRAM("Extensions.SandboxUnpackRate", unpack_rate_kb_per_s);
+
+ if (crx_file_size < 50.0 * kBytesPerKb) {
+ UNPACK_RATE_HISTOGRAM("Extensions.SandboxUnpackRateUnder50kB",
+ unpack_rate_kb_per_s);
+
+ } else if (crx_file_size < 1 * kBytesPerMb) {
+ UNPACK_RATE_HISTOGRAM("Extensions.SandboxUnpackRate50kBTo1mB",
+ unpack_rate_kb_per_s);
+
+ } else if (crx_file_size < 2 * kBytesPerMb) {
+ UNPACK_RATE_HISTOGRAM("Extensions.SandboxUnpackRate1To2mB",
+ unpack_rate_kb_per_s);
+
+ } else if (crx_file_size < 5 * kBytesPerMb) {
+ UNPACK_RATE_HISTOGRAM("Extensions.SandboxUnpackRate2To5mB",
+ unpack_rate_kb_per_s);
+
+ } else if (crx_file_size < 10 * kBytesPerMb) {
+ UNPACK_RATE_HISTOGRAM("Extensions.SandboxUnpackRate5To10mB",
+ unpack_rate_kb_per_s);
+
+ } else {
+ UNPACK_RATE_HISTOGRAM("Extensions.SandboxUnpackRateOver10mB",
+ unpack_rate_kb_per_s);
+ }
+}
+
+// Work horse for FindWritableTempLocation. Creates a temp file in the folder
+// and uses NormalizeFilePath to check if the path is junction free.
+bool VerifyJunctionFreeLocation(base::FilePath* temp_dir) {
+ if (temp_dir->empty())
+ return false;
+
+ base::FilePath temp_file;
+ if (!base::CreateTemporaryFileInDir(*temp_dir, &temp_file)) {
+ LOG(ERROR) << temp_dir->value() << " is not writable";
+ return false;
+ }
+ // NormalizeFilePath requires a non-empty file, so write some data.
+ // If you change the exit points of this function please make sure all
+ // exit points delete this temp file!
+ if (base::WriteFile(temp_file, ".", 1) != 1)
+ return false;
+
+ base::FilePath normalized_temp_file;
+ bool normalized = base::NormalizeFilePath(temp_file, &normalized_temp_file);
+ if (!normalized) {
+ // If |temp_file| contains a link, the sandbox will block al file system
+ // operations, and the install will fail.
+ LOG(ERROR) << temp_dir->value() << " seem to be on remote drive.";
+ } else {
+ *temp_dir = normalized_temp_file.DirName();
+ }
+ // Clean up the temp file.
+ base::DeleteFile(temp_file, false);
+
+ return normalized;
+}
+
+// This function tries to find a location for unpacking the extension archive
+// that is writable and does not lie on a shared drive so that the sandboxed
+// unpacking process can write there. If no such location exists we can not
+// proceed and should fail.
+// The result will be written to |temp_dir|. The function will write to this
+// parameter even if it returns false.
+bool FindWritableTempLocation(const base::FilePath& extensions_dir,
+ base::FilePath* temp_dir) {
+// On ChromeOS, we will only attempt to unpack extension in cryptohome (profile)
+// directory to provide additional security/privacy and speed up the rest of
+// the extension install process.
+#if !defined(OS_CHROMEOS)
+ PathService::Get(base::DIR_TEMP, temp_dir);
+ if (VerifyJunctionFreeLocation(temp_dir))
+ return true;
+#endif
+
+ *temp_dir = file_util::GetInstallTempDir(extensions_dir);
+ if (VerifyJunctionFreeLocation(temp_dir))
+ return true;
+ // Neither paths is link free chances are good installation will fail.
+ LOG(ERROR) << "Both the %TEMP% folder and the profile seem to be on "
+ << "remote drives or read-only. Installation can not complete!";
+ return false;
+}
+
+// Read the decoded images back from the file we saved them to.
+// |extension_path| is the path to the extension we unpacked that wrote the
+// data. Returns true on success.
+bool ReadImagesFromFile(const base::FilePath& extension_path,
+ DecodedImages* images) {
+ base::FilePath path = extension_path.AppendASCII(kDecodedImagesFilename);
+ std::string file_str;
+ if (!base::ReadFileToString(path, &file_str))
+ return false;
+
+ IPC::Message pickle(file_str.data(), file_str.size());
+ base::PickleIterator iter(pickle);
+ return IPC::ReadParam(&pickle, &iter, images);
+}
+
+// Read the decoded message catalogs back from the file we saved them to.
+// |extension_path| is the path to the extension we unpacked that wrote the
+// data. Returns true on success.
+bool ReadMessageCatalogsFromFile(const base::FilePath& extension_path,
+ base::DictionaryValue* catalogs) {
+ base::FilePath path =
+ extension_path.AppendASCII(kDecodedMessageCatalogsFilename);
+ std::string file_str;
+ if (!base::ReadFileToString(path, &file_str))
+ return false;
+
+ IPC::Message pickle(file_str.data(), file_str.size());
+ base::PickleIterator iter(pickle);
+ return IPC::ReadParam(&pickle, &iter, catalogs);
+}
+
+} // namespace
+
+SandboxedUnpackerClient::SandboxedUnpackerClient()
+ : RefCountedDeleteOnMessageLoop<SandboxedUnpackerClient>(
+ content::BrowserThread::GetMessageLoopProxyForThread(
+ content::BrowserThread::UI)) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+SandboxedUnpacker::SandboxedUnpacker(
+ Manifest::Location location,
+ int creation_flags,
+ const base::FilePath& extensions_dir,
+ const scoped_refptr<base::SequencedTaskRunner>& unpacker_io_task_runner,
+ SandboxedUnpackerClient* client)
+ : client_(client),
+ extensions_dir_(extensions_dir),
+ got_response_(false),
+ location_(location),
+ creation_flags_(creation_flags),
+ unpacker_io_task_runner_(unpacker_io_task_runner),
+ utility_wrapper_(new UtilityHostWrapper) {}
+
+bool SandboxedUnpacker::CreateTempDirectory() {
+ CHECK(unpacker_io_task_runner_->RunsTasksOnCurrentThread());
+
+ base::FilePath temp_dir;
+ if (!FindWritableTempLocation(extensions_dir_, &temp_dir)) {
+ ReportFailure(COULD_NOT_GET_TEMP_DIRECTORY,
+ l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("COULD_NOT_GET_TEMP_DIRECTORY")));
+ return false;
+ }
+
+ if (!temp_dir_.CreateUniqueTempDirUnderPath(temp_dir)) {
+ ReportFailure(COULD_NOT_CREATE_TEMP_DIRECTORY,
+ l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("COULD_NOT_CREATE_TEMP_DIRECTORY")));
+ return false;
+ }
+
+ return true;
+}
+
+void SandboxedUnpacker::StartWithCrx(const CRXFileInfo& crx_info) {
+ // We assume that we are started on the thread that the client wants us to do
+ // file IO on.
+ CHECK(unpacker_io_task_runner_->RunsTasksOnCurrentThread());
+
+ crx_unpack_start_time_ = base::TimeTicks::Now();
+ std::string expected_hash;
+ if (!crx_info.expected_hash.empty() &&
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ extensions::switches::kEnableCrxHashCheck)) {
+ expected_hash = base::ToLowerASCII(crx_info.expected_hash);
+ }
+
+ PATH_LENGTH_HISTOGRAM("Extensions.SandboxUnpackInitialCrxPathLength",
+ crx_info.path);
+ if (!CreateTempDirectory())
+ return; // ReportFailure() already called.
+
+ // Initialize the path that will eventually contain the unpacked extension.
+ extension_root_ = temp_dir_.path().AppendASCII(kTempExtensionName);
+ PATH_LENGTH_HISTOGRAM("Extensions.SandboxUnpackUnpackedCrxPathLength",
+ extension_root_);
+
+ // Extract the public key and validate the package.
+ if (!ValidateSignature(crx_info.path, expected_hash))
+ return; // ValidateSignature() already reported the error.
+
+ // Copy the crx file into our working directory.
+ base::FilePath temp_crx_path =
+ temp_dir_.path().Append(crx_info.path.BaseName());
+ PATH_LENGTH_HISTOGRAM("Extensions.SandboxUnpackTempCrxPathLength",
+ temp_crx_path);
+
+ if (!base::CopyFile(crx_info.path, temp_crx_path)) {
+ // Failed to copy extension file to temporary directory.
+ ReportFailure(
+ FAILED_TO_COPY_EXTENSION_FILE_TO_TEMP_DIRECTORY,
+ l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("FAILED_TO_COPY_EXTENSION_FILE_TO_TEMP_DIRECTORY")));
+ return;
+ }
+
+ // The utility process will have access to the directory passed to
+ // SandboxedUnpacker. That directory should not contain a symlink or NTFS
+ // reparse point. When the path is used, following the link/reparse point
+ // will cause file system access outside the sandbox path, and the sandbox
+ // will deny the operation.
+ base::FilePath link_free_crx_path;
+ if (!base::NormalizeFilePath(temp_crx_path, &link_free_crx_path)) {
+ LOG(ERROR) << "Could not get the normalized path of "
+ << temp_crx_path.value();
+ ReportFailure(COULD_NOT_GET_SANDBOX_FRIENDLY_PATH,
+ l10n_util::GetStringUTF16(IDS_EXTENSION_UNPACK_FAILED));
+ return;
+ }
+ PATH_LENGTH_HISTOGRAM("Extensions.SandboxUnpackLinkFreeCrxPathLength",
+ link_free_crx_path);
+
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&SandboxedUnpacker::StartUnzipOnIOThread,
+ this, link_free_crx_path));
+}
+
+void SandboxedUnpacker::StartWithDirectory(const std::string& extension_id,
+ const std::string& public_key,
+ const base::FilePath& directory) {
+ extension_id_ = extension_id;
+ public_key_ = public_key;
+ if (!CreateTempDirectory())
+ return; // ReportFailure() already called.
+
+ extension_root_ = temp_dir_.path().AppendASCII(kTempExtensionName);
+
+ if (!base::Move(directory, extension_root_)) {
+ LOG(ERROR) << "Could not move " << directory.value() << " to "
+ << extension_root_.value();
+ ReportFailure(
+ DIRECTORY_MOVE_FAILED,
+ l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("DIRECTORY_MOVE_FAILED")));
+ return;
+ }
+
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&SandboxedUnpacker::StartUnpackOnIOThread,
+ this, extension_root_));
+}
+
+SandboxedUnpacker::~SandboxedUnpacker() {
+ // To avoid blocking shutdown, don't delete temporary directory here if it
+ // hasn't been cleaned up or passed on to another owner yet.
+ temp_dir_.Take();
+}
+
+bool SandboxedUnpacker::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(SandboxedUnpacker, message)
+ IPC_MESSAGE_HANDLER(ExtensionUtilityHostMsg_UnzipToDir_Succeeded,
+ OnUnzipToDirSucceeded)
+ IPC_MESSAGE_HANDLER(ExtensionUtilityHostMsg_UnzipToDir_Failed,
+ OnUnzipToDirFailed)
+ IPC_MESSAGE_HANDLER(ExtensionUtilityHostMsg_UnpackExtension_Succeeded,
+ OnUnpackExtensionSucceeded)
+ IPC_MESSAGE_HANDLER(ExtensionUtilityHostMsg_UnpackExtension_Failed,
+ OnUnpackExtensionFailed)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void SandboxedUnpacker::OnProcessCrashed(int exit_code) {
+ // Don't report crashes if they happen after we got a response.
+ if (got_response_)
+ return;
+
+ // Utility process crashed while trying to install.
+ ReportFailure(
+ UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL,
+ l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL")) +
+ ASCIIToUTF16(". ") +
+ l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALL_PROCESS_CRASHED));
+}
+
+void SandboxedUnpacker::StartUnzipOnIOThread(const base::FilePath& crx_path) {
+ if (!utility_wrapper_->StartIfNeeded(temp_dir_.path(), this,
+ unpacker_io_task_runner_)) {
+ ReportFailure(
+ COULD_NOT_START_UTILITY_PROCESS,
+ l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ FailureReasonToString16(COULD_NOT_START_UTILITY_PROCESS)));
+ return;
+ }
+ DCHECK(crx_path.DirName() == temp_dir_.path());
+ base::FilePath unzipped_dir =
+ crx_path.DirName().AppendASCII(kTempExtensionName);
+ utility_wrapper_->host()->Send(
+ new ExtensionUtilityMsg_UnzipToDir(crx_path, unzipped_dir));
+}
+
+void SandboxedUnpacker::StartUnpackOnIOThread(
+ const base::FilePath& directory_path) {
+ if (!utility_wrapper_->StartIfNeeded(temp_dir_.path(), this,
+ unpacker_io_task_runner_)) {
+ ReportFailure(
+ COULD_NOT_START_UTILITY_PROCESS,
+ l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ FailureReasonToString16(COULD_NOT_START_UTILITY_PROCESS)));
+ return;
+ }
+ DCHECK(directory_path.DirName() == temp_dir_.path());
+ utility_wrapper_->host()->Send(new ExtensionUtilityMsg_UnpackExtension(
+ directory_path, extension_id_, location_, creation_flags_));
+}
+
+void SandboxedUnpacker::OnUnzipToDirSucceeded(const base::FilePath& directory) {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&SandboxedUnpacker::StartUnpackOnIOThread, this, directory));
+}
+
+void SandboxedUnpacker::OnUnzipToDirFailed(const std::string& error) {
+ got_response_ = true;
+ utility_wrapper_ = nullptr;
+ ReportFailure(UNZIP_FAILED,
+ l10n_util::GetStringUTF16(IDS_EXTENSION_PACKAGE_UNZIP_ERROR));
+}
+
+void SandboxedUnpacker::OnUnpackExtensionSucceeded(
+ const base::DictionaryValue& manifest) {
+ CHECK(unpacker_io_task_runner_->RunsTasksOnCurrentThread());
+ got_response_ = true;
+ utility_wrapper_ = nullptr;
+
+ scoped_ptr<base::DictionaryValue> final_manifest(
+ RewriteManifestFile(manifest));
+ if (!final_manifest)
+ return;
+
+ // Create an extension object that refers to the temporary location the
+ // extension was unpacked to. We use this until the extension is finally
+ // installed. For example, the install UI shows images from inside the
+ // extension.
+
+ // Localize manifest now, so confirm UI gets correct extension name.
+
+ // TODO(rdevlin.cronin): Continue removing std::string errors and replacing
+ // with base::string16
+ std::string utf8_error;
+ if (!extension_l10n_util::LocalizeExtension(
+ extension_root_, final_manifest.get(), &utf8_error)) {
+ ReportFailure(
+ COULD_NOT_LOCALIZE_EXTENSION,
+ l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_ERROR_MESSAGE,
+ base::UTF8ToUTF16(utf8_error)));
+ return;
+ }
+
+ extension_ =
+ Extension::Create(extension_root_, location_, *final_manifest,
+ Extension::REQUIRE_KEY | creation_flags_, &utf8_error);
+
+ if (!extension_.get()) {
+ ReportFailure(INVALID_MANIFEST,
+ ASCIIToUTF16("Manifest is invalid: " + utf8_error));
+ return;
+ }
+
+ SkBitmap install_icon;
+ if (!RewriteImageFiles(&install_icon))
+ return;
+
+ if (!RewriteCatalogFiles())
+ return;
+
+ ReportSuccess(manifest, install_icon);
+}
+
+void SandboxedUnpacker::OnUnpackExtensionFailed(const base::string16& error) {
+ CHECK(unpacker_io_task_runner_->RunsTasksOnCurrentThread());
+ got_response_ = true;
+ utility_wrapper_ = nullptr;
+ ReportFailure(
+ UNPACKER_CLIENT_FAILED,
+ l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_ERROR_MESSAGE, error));
+}
+
+base::string16 SandboxedUnpacker::FailureReasonToString16(
+ FailureReason reason) {
+ switch (reason) {
+ case COULD_NOT_GET_TEMP_DIRECTORY:
+ return ASCIIToUTF16("COULD_NOT_GET_TEMP_DIRECTORY");
+ case COULD_NOT_CREATE_TEMP_DIRECTORY:
+ return ASCIIToUTF16("COULD_NOT_CREATE_TEMP_DIRECTORY");
+ case FAILED_TO_COPY_EXTENSION_FILE_TO_TEMP_DIRECTORY:
+ return ASCIIToUTF16("FAILED_TO_COPY_EXTENSION_FILE_TO_TEMP_DIRECTORY");
+ case COULD_NOT_GET_SANDBOX_FRIENDLY_PATH:
+ return ASCIIToUTF16("COULD_NOT_GET_SANDBOX_FRIENDLY_PATH");
+ case COULD_NOT_LOCALIZE_EXTENSION:
+ return ASCIIToUTF16("COULD_NOT_LOCALIZE_EXTENSION");
+ case INVALID_MANIFEST:
+ return ASCIIToUTF16("INVALID_MANIFEST");
+ case UNPACKER_CLIENT_FAILED:
+ return ASCIIToUTF16("UNPACKER_CLIENT_FAILED");
+ case UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL:
+ return ASCIIToUTF16("UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL");
+
+ case CRX_FILE_NOT_READABLE:
+ return ASCIIToUTF16("CRX_FILE_NOT_READABLE");
+ case CRX_HEADER_INVALID:
+ return ASCIIToUTF16("CRX_HEADER_INVALID");
+ case CRX_MAGIC_NUMBER_INVALID:
+ return ASCIIToUTF16("CRX_MAGIC_NUMBER_INVALID");
+ case CRX_VERSION_NUMBER_INVALID:
+ return ASCIIToUTF16("CRX_VERSION_NUMBER_INVALID");
+ case CRX_EXCESSIVELY_LARGE_KEY_OR_SIGNATURE:
+ return ASCIIToUTF16("CRX_EXCESSIVELY_LARGE_KEY_OR_SIGNATURE");
+ case CRX_ZERO_KEY_LENGTH:
+ return ASCIIToUTF16("CRX_ZERO_KEY_LENGTH");
+ case CRX_ZERO_SIGNATURE_LENGTH:
+ return ASCIIToUTF16("CRX_ZERO_SIGNATURE_LENGTH");
+ case CRX_PUBLIC_KEY_INVALID:
+ return ASCIIToUTF16("CRX_PUBLIC_KEY_INVALID");
+ case CRX_SIGNATURE_INVALID:
+ return ASCIIToUTF16("CRX_SIGNATURE_INVALID");
+ case CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED:
+ return ASCIIToUTF16("CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED");
+ case CRX_SIGNATURE_VERIFICATION_FAILED:
+ return ASCIIToUTF16("CRX_SIGNATURE_VERIFICATION_FAILED");
+
+ case ERROR_SERIALIZING_MANIFEST_JSON:
+ return ASCIIToUTF16("ERROR_SERIALIZING_MANIFEST_JSON");
+ case ERROR_SAVING_MANIFEST_JSON:
+ return ASCIIToUTF16("ERROR_SAVING_MANIFEST_JSON");
+
+ case COULD_NOT_READ_IMAGE_DATA_FROM_DISK:
+ return ASCIIToUTF16("COULD_NOT_READ_IMAGE_DATA_FROM_DISK");
+ case DECODED_IMAGES_DO_NOT_MATCH_THE_MANIFEST:
+ return ASCIIToUTF16("DECODED_IMAGES_DO_NOT_MATCH_THE_MANIFEST");
+ case INVALID_PATH_FOR_BROWSER_IMAGE:
+ return ASCIIToUTF16("INVALID_PATH_FOR_BROWSER_IMAGE");
+ case ERROR_REMOVING_OLD_IMAGE_FILE:
+ return ASCIIToUTF16("ERROR_REMOVING_OLD_IMAGE_FILE");
+ case INVALID_PATH_FOR_BITMAP_IMAGE:
+ return ASCIIToUTF16("INVALID_PATH_FOR_BITMAP_IMAGE");
+ case ERROR_RE_ENCODING_THEME_IMAGE:
+ return ASCIIToUTF16("ERROR_RE_ENCODING_THEME_IMAGE");
+ case ERROR_SAVING_THEME_IMAGE:
+ return ASCIIToUTF16("ERROR_SAVING_THEME_IMAGE");
+ case ABORTED_DUE_TO_SHUTDOWN:
+ return ASCIIToUTF16("ABORTED_DUE_TO_SHUTDOWN");
+
+ case COULD_NOT_READ_CATALOG_DATA_FROM_DISK:
+ return ASCIIToUTF16("COULD_NOT_READ_CATALOG_DATA_FROM_DISK");
+ case INVALID_CATALOG_DATA:
+ return ASCIIToUTF16("INVALID_CATALOG_DATA");
+ case INVALID_PATH_FOR_CATALOG:
+ return ASCIIToUTF16("INVALID_PATH_FOR_CATALOG");
+ case ERROR_SERIALIZING_CATALOG:
+ return ASCIIToUTF16("ERROR_SERIALIZING_CATALOG");
+ case ERROR_SAVING_CATALOG:
+ return ASCIIToUTF16("ERROR_SAVING_CATALOG");
+
+ case CRX_HASH_VERIFICATION_FAILED:
+ return ASCIIToUTF16("CRX_HASH_VERIFICATION_FAILED");
+
+ case UNZIP_FAILED:
+ return ASCIIToUTF16("UNZIP_FAILED");
+ case DIRECTORY_MOVE_FAILED:
+ return ASCIIToUTF16("DIRECTORY_MOVE_FAILED");
+ case COULD_NOT_START_UTILITY_PROCESS:
+ return ASCIIToUTF16("COULD_NOT_START_UTILITY_PROCESS");
+
+ case NUM_FAILURE_REASONS:
+ NOTREACHED();
+ return base::string16();
+ }
+ NOTREACHED();
+ return base::string16();
+}
+
+void SandboxedUnpacker::FailWithPackageError(FailureReason reason) {
+ ReportFailure(reason,
+ l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_ERROR_CODE,
+ FailureReasonToString16(reason)));
+}
+
+bool SandboxedUnpacker::ValidateSignature(const base::FilePath& crx_path,
+ const std::string& expected_hash) {
+ CrxFile::ValidateError error = CrxFile::ValidateSignature(
+ crx_path, expected_hash, &public_key_, &extension_id_, nullptr);
+
+ switch (error) {
+ case CrxFile::ValidateError::NONE: {
+ if (!expected_hash.empty())
+ UMA_HISTOGRAM_BOOLEAN("Extensions.SandboxUnpackHashCheck", true);
+ return true;
+ }
+
+ case CrxFile::ValidateError::CRX_FILE_NOT_READABLE:
+ FailWithPackageError(CRX_FILE_NOT_READABLE);
+ break;
+ case CrxFile::ValidateError::CRX_HEADER_INVALID:
+ FailWithPackageError(CRX_HEADER_INVALID);
+ break;
+ case CrxFile::ValidateError::CRX_MAGIC_NUMBER_INVALID:
+ FailWithPackageError(CRX_MAGIC_NUMBER_INVALID);
+ break;
+ case CrxFile::ValidateError::CRX_VERSION_NUMBER_INVALID:
+ FailWithPackageError(CRX_VERSION_NUMBER_INVALID);
+ break;
+ case CrxFile::ValidateError::CRX_EXCESSIVELY_LARGE_KEY_OR_SIGNATURE:
+ FailWithPackageError(CRX_EXCESSIVELY_LARGE_KEY_OR_SIGNATURE);
+ break;
+ case CrxFile::ValidateError::CRX_ZERO_KEY_LENGTH:
+ FailWithPackageError(CRX_ZERO_KEY_LENGTH);
+ break;
+ case CrxFile::ValidateError::CRX_ZERO_SIGNATURE_LENGTH:
+ FailWithPackageError(CRX_ZERO_SIGNATURE_LENGTH);
+ break;
+ case CrxFile::ValidateError::CRX_PUBLIC_KEY_INVALID:
+ FailWithPackageError(CRX_PUBLIC_KEY_INVALID);
+ break;
+ case CrxFile::ValidateError::CRX_SIGNATURE_INVALID:
+ FailWithPackageError(CRX_SIGNATURE_INVALID);
+ break;
+ case CrxFile::ValidateError::
+ CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED:
+ FailWithPackageError(CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED);
+ break;
+ case CrxFile::ValidateError::CRX_SIGNATURE_VERIFICATION_FAILED:
+ FailWithPackageError(CRX_SIGNATURE_VERIFICATION_FAILED);
+ break;
+ case CrxFile::ValidateError::CRX_HASH_VERIFICATION_FAILED:
+ // We should never get this result unless we had specifically asked for
+ // verification of the crx file's hash.
+ CHECK(!expected_hash.empty());
+ UMA_HISTOGRAM_BOOLEAN("Extensions.SandboxUnpackHashCheck", false);
+ FailWithPackageError(CRX_HASH_VERIFICATION_FAILED);
+ break;
+ }
+ return false;
+}
+
+void SandboxedUnpacker::ReportFailure(FailureReason reason,
+ const base::string16& error) {
+ utility_wrapper_ = nullptr;
+ UMA_HISTOGRAM_ENUMERATION("Extensions.SandboxUnpackFailureReason", reason,
+ NUM_FAILURE_REASONS);
+ if (!crx_unpack_start_time_.is_null())
+ UMA_HISTOGRAM_TIMES("Extensions.SandboxUnpackFailureTime",
+ base::TimeTicks::Now() - crx_unpack_start_time_);
+ Cleanup();
+
+ CrxInstallError error_info(reason == CRX_HASH_VERIFICATION_FAILED
+ ? CrxInstallError::ERROR_HASH_MISMATCH
+ : CrxInstallError::ERROR_OTHER,
+ error);
+
+ client_->OnUnpackFailure(error_info);
+}
+
+void SandboxedUnpacker::ReportSuccess(
+ const base::DictionaryValue& original_manifest,
+ const SkBitmap& install_icon) {
+ utility_wrapper_ = nullptr;
+ UMA_HISTOGRAM_COUNTS("Extensions.SandboxUnpackSuccess", 1);
+
+ if (!crx_unpack_start_time_.is_null())
+ RecordSuccessfulUnpackTimeHistograms(
+ crx_path_for_histograms_,
+ base::TimeTicks::Now() - crx_unpack_start_time_);
+ DCHECK(!temp_dir_.path().empty());
+
+ // Client takes ownership of temporary directory and extension.
+ client_->OnUnpackSuccess(temp_dir_.Take(), extension_root_,
+ &original_manifest, extension_.get(), install_icon);
+ extension_ = NULL;
+}
+
+base::DictionaryValue* SandboxedUnpacker::RewriteManifestFile(
+ const base::DictionaryValue& manifest) {
+ // Add the public key extracted earlier to the parsed manifest and overwrite
+ // the original manifest. We do this to ensure the manifest doesn't contain an
+ // exploitable bug that could be used to compromise the browser.
+ DCHECK(!public_key_.empty());
+ scoped_ptr<base::DictionaryValue> final_manifest(manifest.DeepCopy());
+ final_manifest->SetString(manifest_keys::kPublicKey, public_key_);
+
+ std::string manifest_json;
+ JSONStringValueSerializer serializer(&manifest_json);
+ serializer.set_pretty_print(true);
+ if (!serializer.Serialize(*final_manifest)) {
+ // Error serializing manifest.json.
+ ReportFailure(ERROR_SERIALIZING_MANIFEST_JSON,
+ l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("ERROR_SERIALIZING_MANIFEST_JSON")));
+ return NULL;
+ }
+
+ base::FilePath manifest_path = extension_root_.Append(kManifestFilename);
+ int size = base::checked_cast<int>(manifest_json.size());
+ if (base::WriteFile(manifest_path, manifest_json.data(), size) != size) {
+ // Error saving manifest.json.
+ ReportFailure(
+ ERROR_SAVING_MANIFEST_JSON,
+ l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("ERROR_SAVING_MANIFEST_JSON")));
+ return NULL;
+ }
+
+ return final_manifest.release();
+}
+
+bool SandboxedUnpacker::RewriteImageFiles(SkBitmap* install_icon) {
+ DCHECK(!temp_dir_.path().empty());
+ DecodedImages images;
+ if (!ReadImagesFromFile(temp_dir_.path(), &images)) {
+ // Couldn't read image data from disk.
+ ReportFailure(COULD_NOT_READ_IMAGE_DATA_FROM_DISK,
+ l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("COULD_NOT_READ_IMAGE_DATA_FROM_DISK")));
+ return false;
+ }
+
+ // Delete any images that may be used by the browser. We're going to write
+ // out our own versions of the parsed images, and we want to make sure the
+ // originals are gone for good.
+ std::set<base::FilePath> image_paths =
+ ExtensionsClient::Get()->GetBrowserImagePaths(extension_.get());
+ if (image_paths.size() != images.size()) {
+ // Decoded images don't match what's in the manifest.
+ ReportFailure(
+ DECODED_IMAGES_DO_NOT_MATCH_THE_MANIFEST,
+ l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("DECODED_IMAGES_DO_NOT_MATCH_THE_MANIFEST")));
+ return false;
+ }
+
+ for (std::set<base::FilePath>::iterator it = image_paths.begin();
+ it != image_paths.end(); ++it) {
+ base::FilePath path = *it;
+ if (path.IsAbsolute() || path.ReferencesParent()) {
+ // Invalid path for browser image.
+ ReportFailure(INVALID_PATH_FOR_BROWSER_IMAGE,
+ l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("INVALID_PATH_FOR_BROWSER_IMAGE")));
+ return false;
+ }
+ if (!base::DeleteFile(extension_root_.Append(path), false)) {
+ // Error removing old image file.
+ ReportFailure(ERROR_REMOVING_OLD_IMAGE_FILE,
+ l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("ERROR_REMOVING_OLD_IMAGE_FILE")));
+ return false;
+ }
+ }
+
+ const std::string& install_icon_path =
+ IconsInfo::GetIcons(extension_.get())
+ .Get(extension_misc::EXTENSION_ICON_LARGE,
+ ExtensionIconSet::MATCH_BIGGER);
+
+ // Write our parsed images back to disk as well.
+ for (size_t i = 0; i < images.size(); ++i) {
+ if (BrowserThread::GetBlockingPool()->IsShutdownInProgress()) {
+ // Abort package installation if shutdown was initiated, crbug.com/235525
+ ReportFailure(
+ ABORTED_DUE_TO_SHUTDOWN,
+ l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("ABORTED_DUE_TO_SHUTDOWN")));
+ return false;
+ }
+
+ const SkBitmap& image = std::get<0>(images[i]);
+ base::FilePath path_suffix = std::get<1>(images[i]);
+ if (path_suffix.MaybeAsASCII() == install_icon_path)
+ *install_icon = image;
+
+ if (path_suffix.IsAbsolute() || path_suffix.ReferencesParent()) {
+ // Invalid path for bitmap image.
+ ReportFailure(INVALID_PATH_FOR_BITMAP_IMAGE,
+ l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("INVALID_PATH_FOR_BITMAP_IMAGE")));
+ return false;
+ }
+ base::FilePath path = extension_root_.Append(path_suffix);
+
+ std::vector<unsigned char> image_data;
+ // TODO(mpcomplete): It's lame that we're encoding all images as PNG, even
+ // though they may originally be .jpg, etc. Figure something out.
+ // http://code.google.com/p/chromium/issues/detail?id=12459
+ if (!gfx::PNGCodec::EncodeBGRASkBitmap(image, false, &image_data)) {
+ // Error re-encoding theme image.
+ ReportFailure(ERROR_RE_ENCODING_THEME_IMAGE,
+ l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("ERROR_RE_ENCODING_THEME_IMAGE")));
+ return false;
+ }
+
+ // Note: we're overwriting existing files that the utility process wrote,
+ // so we can be sure the directory exists.
+ const char* image_data_ptr = reinterpret_cast<const char*>(&image_data[0]);
+ int size = base::checked_cast<int>(image_data.size());
+ if (base::WriteFile(path, image_data_ptr, size) != size) {
+ // Error saving theme image.
+ ReportFailure(
+ ERROR_SAVING_THEME_IMAGE,
+ l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("ERROR_SAVING_THEME_IMAGE")));
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool SandboxedUnpacker::RewriteCatalogFiles() {
+ base::DictionaryValue catalogs;
+ if (!ReadMessageCatalogsFromFile(temp_dir_.path(), &catalogs)) {
+ // Could not read catalog data from disk.
+ ReportFailure(COULD_NOT_READ_CATALOG_DATA_FROM_DISK,
+ l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("COULD_NOT_READ_CATALOG_DATA_FROM_DISK")));
+ return false;
+ }
+
+ // Write our parsed catalogs back to disk.
+ for (base::DictionaryValue::Iterator it(catalogs); !it.IsAtEnd();
+ it.Advance()) {
+ const base::DictionaryValue* catalog = NULL;
+ if (!it.value().GetAsDictionary(&catalog)) {
+ // Invalid catalog data.
+ ReportFailure(
+ INVALID_CATALOG_DATA,
+ l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("INVALID_CATALOG_DATA")));
+ return false;
+ }
+
+ base::FilePath relative_path = base::FilePath::FromUTF8Unsafe(it.key());
+ relative_path = relative_path.Append(kMessagesFilename);
+ if (relative_path.IsAbsolute() || relative_path.ReferencesParent()) {
+ // Invalid path for catalog.
+ ReportFailure(
+ INVALID_PATH_FOR_CATALOG,
+ l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("INVALID_PATH_FOR_CATALOG")));
+ return false;
+ }
+ base::FilePath path = extension_root_.Append(relative_path);
+
+ std::string catalog_json;
+ JSONStringValueSerializer serializer(&catalog_json);
+ serializer.set_pretty_print(true);
+ if (!serializer.Serialize(*catalog)) {
+ // Error serializing catalog.
+ ReportFailure(ERROR_SERIALIZING_CATALOG,
+ l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("ERROR_SERIALIZING_CATALOG")));
+ return false;
+ }
+
+ // Note: we're overwriting existing files that the utility process read,
+ // so we can be sure the directory exists.
+ int size = base::checked_cast<int>(catalog_json.size());
+ if (base::WriteFile(path, catalog_json.c_str(), size) != size) {
+ // Error saving catalog.
+ ReportFailure(
+ ERROR_SAVING_CATALOG,
+ l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+ ASCIIToUTF16("ERROR_SAVING_CATALOG")));
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void SandboxedUnpacker::Cleanup() {
+ DCHECK(unpacker_io_task_runner_->RunsTasksOnCurrentThread());
+ if (!temp_dir_.Delete()) {
+ LOG(WARNING) << "Can not delete temp directory at "
+ << temp_dir_.path().value();
+ }
+}
+
+SandboxedUnpacker::UtilityHostWrapper::UtilityHostWrapper() {}
+
+bool SandboxedUnpacker::UtilityHostWrapper::StartIfNeeded(
+ const base::FilePath& exposed_dir,
+ const scoped_refptr<UtilityProcessHostClient>& client,
+ const scoped_refptr<base::SequencedTaskRunner>& client_task_runner) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ if (!utility_host_) {
+ utility_host_ =
+ UtilityProcessHost::Create(client, client_task_runner)->AsWeakPtr();
+ utility_host_->SetName(
+ l10n_util::GetStringUTF16(IDS_UTILITY_PROCESS_EXTENSION_UNPACKER_NAME));
+
+ // Grant the subprocess access to our temp dir so it can write out files.
+ DCHECK(!exposed_dir.empty());
+ utility_host_->SetExposedDir(exposed_dir);
+ if (!utility_host_->StartBatchMode()) {
+ utility_host_.reset();
+ return false;
+ }
+ }
+ return true;
+}
+
+content::UtilityProcessHost* SandboxedUnpacker::UtilityHostWrapper::host()
+ const {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ return utility_host_.get();
+}
+
+SandboxedUnpacker::UtilityHostWrapper::~UtilityHostWrapper() {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ if (utility_host_) {
+ utility_host_->EndBatchMode();
+ utility_host_.reset();
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/sandboxed_unpacker.h b/chromium/extensions/browser/sandboxed_unpacker.h
new file mode 100644
index 00000000000..0b08125d27d
--- /dev/null
+++ b/chromium/extensions/browser/sandboxed_unpacker.h
@@ -0,0 +1,317 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_SANDBOXED_UNPACKER_H_
+#define EXTENSIONS_BROWSER_SANDBOXED_UNPACKER_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted_delete_on_message_loop.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/utility_process_host_client.h"
+#include "extensions/browser/crx_file_info.h"
+#include "extensions/browser/install/crx_install_error.h"
+#include "extensions/common/manifest.h"
+
+class SkBitmap;
+
+namespace base {
+class DictionaryValue;
+class SequencedTaskRunner;
+}
+
+namespace content {
+class UtilityProcessHost;
+}
+
+namespace crypto {
+class SecureHash;
+}
+
+namespace extensions {
+class Extension;
+
+class SandboxedUnpackerClient
+ : public base::RefCountedDeleteOnMessageLoop<SandboxedUnpackerClient> {
+ public:
+ // Initialize the ref-counted base to always delete on the UI thread. Note
+ // the constructor call must also happen on the UI thread.
+ SandboxedUnpackerClient();
+
+ // temp_dir - A temporary directory containing the results of the extension
+ // unpacking. The client is responsible for deleting this directory.
+ //
+ // extension_root - The path to the extension root inside of temp_dir.
+ //
+ // original_manifest - The parsed but unmodified version of the manifest,
+ // with no modifications such as localization, etc.
+ //
+ // extension - The extension that was unpacked. The client is responsible
+ // for deleting this memory.
+ //
+ // install_icon - The icon we will display in the installation UI, if any.
+ virtual void OnUnpackSuccess(const base::FilePath& temp_dir,
+ const base::FilePath& extension_root,
+ const base::DictionaryValue* original_manifest,
+ const Extension* extension,
+ const SkBitmap& install_icon) = 0;
+ virtual void OnUnpackFailure(const CrxInstallError& error) = 0;
+
+ protected:
+ friend class base::RefCountedDeleteOnMessageLoop<SandboxedUnpackerClient>;
+ friend class base::DeleteHelper<SandboxedUnpackerClient>;
+
+ virtual ~SandboxedUnpackerClient() {}
+};
+
+// SandboxedUnpacker does work to optionally unpack and then validate/sanitize
+// an extension, either starting from a crx file or an already unzipped
+// directory (eg from differential update). This is done in a sandboxed
+// subprocess to protect the browser process from parsing complex formats like
+// JPEG or JSON from untrusted sources.
+//
+// Unpacking an extension using this class makes minor changes to its source,
+// such as transcoding all images to PNG, parsing all message catalogs
+// and rewriting the manifest JSON. As such, it should not be used when the
+// output is not intended to be given back to the author.
+//
+//
+// Lifetime management:
+//
+// This class is ref-counted by each call it makes to itself on another thread,
+// and by UtilityProcessHost.
+//
+// Additionally, we hold a reference to our own client so that it lives at least
+// long enough to receive the result of unpacking.
+//
+//
+// NOTE: This class should only be used on the file thread.
+class SandboxedUnpacker : public content::UtilityProcessHostClient {
+ public:
+ // Creates a SanboxedUnpacker that will do work to unpack an extension,
+ // passing the |location| and |creation_flags| to Extension::Create. The
+ // |extensions_dir| parameter should specify the directory under which we'll
+ // create a subdirectory to write the unpacked extension contents.
+ SandboxedUnpacker(
+ Manifest::Location location,
+ int creation_flags,
+ const base::FilePath& extensions_dir,
+ const scoped_refptr<base::SequencedTaskRunner>& unpacker_io_task_runner,
+ SandboxedUnpackerClient* client);
+
+ // Start processing the extension, either from a CRX file or already unzipped
+ // in a directory. The client is called with the results. The directory form
+ // requires the id and base64-encoded public key (for insertion into the
+ // 'key' field of the manifest.json file).
+ void StartWithCrx(const CRXFileInfo& crx_info);
+ void StartWithDirectory(const std::string& extension_id,
+ const std::string& public_key_base64,
+ const base::FilePath& directory);
+
+ private:
+ class ProcessHostClient;
+
+ // Enumerate all the ways unpacking can fail. Calls to ReportFailure()
+ // take a failure reason as an argument, and put it in histogram
+ // Extensions.SandboxUnpackFailureReason.
+ enum FailureReason {
+ // SandboxedUnpacker::CreateTempDirectory()
+ COULD_NOT_GET_TEMP_DIRECTORY,
+ COULD_NOT_CREATE_TEMP_DIRECTORY,
+
+ // SandboxedUnpacker::Start()
+ FAILED_TO_COPY_EXTENSION_FILE_TO_TEMP_DIRECTORY,
+ COULD_NOT_GET_SANDBOX_FRIENDLY_PATH,
+
+ // SandboxedUnpacker::OnUnpackExtensionSucceeded()
+ COULD_NOT_LOCALIZE_EXTENSION,
+ INVALID_MANIFEST,
+
+ // SandboxedUnpacker::OnUnpackExtensionFailed()
+ UNPACKER_CLIENT_FAILED,
+
+ // SandboxedUnpacker::OnProcessCrashed()
+ UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL,
+
+ // SandboxedUnpacker::ValidateSignature()
+ CRX_FILE_NOT_READABLE,
+ CRX_HEADER_INVALID,
+ CRX_MAGIC_NUMBER_INVALID,
+ CRX_VERSION_NUMBER_INVALID,
+ CRX_EXCESSIVELY_LARGE_KEY_OR_SIGNATURE,
+ CRX_ZERO_KEY_LENGTH,
+ CRX_ZERO_SIGNATURE_LENGTH,
+ CRX_PUBLIC_KEY_INVALID,
+ CRX_SIGNATURE_INVALID,
+ CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED,
+ CRX_SIGNATURE_VERIFICATION_FAILED,
+
+ // SandboxedUnpacker::RewriteManifestFile()
+ ERROR_SERIALIZING_MANIFEST_JSON,
+ ERROR_SAVING_MANIFEST_JSON,
+
+ // SandboxedUnpacker::RewriteImageFiles()
+ COULD_NOT_READ_IMAGE_DATA_FROM_DISK,
+ DECODED_IMAGES_DO_NOT_MATCH_THE_MANIFEST,
+ INVALID_PATH_FOR_BROWSER_IMAGE,
+ ERROR_REMOVING_OLD_IMAGE_FILE,
+ INVALID_PATH_FOR_BITMAP_IMAGE,
+ ERROR_RE_ENCODING_THEME_IMAGE,
+ ERROR_SAVING_THEME_IMAGE,
+ ABORTED_DUE_TO_SHUTDOWN,
+
+ // SandboxedUnpacker::RewriteCatalogFiles()
+ COULD_NOT_READ_CATALOG_DATA_FROM_DISK,
+ INVALID_CATALOG_DATA,
+ INVALID_PATH_FOR_CATALOG,
+ ERROR_SERIALIZING_CATALOG,
+ ERROR_SAVING_CATALOG,
+
+ // SandboxedUnpacker::ValidateSignature()
+ CRX_HASH_VERIFICATION_FAILED,
+
+ UNZIP_FAILED,
+ DIRECTORY_MOVE_FAILED,
+ COULD_NOT_START_UTILITY_PROCESS,
+
+ NUM_FAILURE_REASONS
+ };
+
+ friend class ProcessHostClient;
+ friend class SandboxedUnpackerTest;
+
+ ~SandboxedUnpacker() override;
+
+ // Set |temp_dir_| as a temporary directory to unpack the extension in.
+ // Return true on success.
+ virtual bool CreateTempDirectory();
+
+ // Helper functions to simplify calls to ReportFailure.
+ base::string16 FailureReasonToString16(FailureReason reason);
+ void FailWithPackageError(FailureReason reason);
+
+ // Validates the signature of the extension and extract the key to
+ // |public_key_|. Returns true if the signature validates, false otherwise.
+ bool ValidateSignature(const base::FilePath& crx_path,
+ const std::string& expected_hash);
+
+ void StartUnzipOnIOThread(const base::FilePath& crx_path);
+ void StartUnpackOnIOThread(const base::FilePath& directory_path);
+
+ // UtilityProcessHostClient
+ bool OnMessageReceived(const IPC::Message& message) override;
+ void OnProcessCrashed(int exit_code) override;
+
+ // IPC message handlers.
+ void OnUnzipToDirSucceeded(const base::FilePath& directory);
+ void OnUnzipToDirFailed(const std::string& error);
+ void OnUnpackExtensionSucceeded(const base::DictionaryValue& manifest);
+ void OnUnpackExtensionFailed(const base::string16& error_message);
+
+ void ReportFailure(FailureReason reason, const base::string16& message);
+ void ReportSuccess(const base::DictionaryValue& original_manifest,
+ const SkBitmap& install_icon);
+
+ // Overwrites original manifest with safe result from utility process.
+ // Returns NULL on error. Caller owns the returned object.
+ base::DictionaryValue* RewriteManifestFile(
+ const base::DictionaryValue& manifest);
+
+ // Overwrites original files with safe results from utility process.
+ // Reports error and returns false if it fails.
+ bool RewriteImageFiles(SkBitmap* install_icon);
+ bool RewriteCatalogFiles();
+
+ // Cleans up temp directory artifacts.
+ void Cleanup();
+
+ // This is a helper class to make it easier to keep track of the lifecycle of
+ // a UtilityProcessHost, including automatic begin and end of batch mode.
+ class UtilityHostWrapper : public base::RefCountedThreadSafe<
+ UtilityHostWrapper,
+ content::BrowserThread::DeleteOnIOThread> {
+ public:
+ UtilityHostWrapper();
+
+ // Start up the utility process if it is not already started, putting it
+ // into batch mode and giving it access to |exposed_dir|. This should only
+ // be called on the IO thread. Returns false if there was an error starting
+ // the utility process or putting it into batch mode.
+ bool StartIfNeeded(
+ const base::FilePath& exposed_dir,
+ const scoped_refptr<UtilityProcessHostClient>& client,
+ const scoped_refptr<base::SequencedTaskRunner>& client_task_runner);
+
+ // This should only be called on the IO thread.
+ content::UtilityProcessHost* host() const;
+
+ private:
+ friend struct content::BrowserThread::DeleteOnThread<
+ content::BrowserThread::IO>;
+ friend class base::DeleteHelper<UtilityHostWrapper>;
+ ~UtilityHostWrapper();
+
+ // Should only be used on the IO thread.
+ base::WeakPtr<content::UtilityProcessHost> utility_host_;
+
+ DISALLOW_COPY_AND_ASSIGN(UtilityHostWrapper);
+ };
+
+ // If we unpacked a crx file, we hold on to the path for use in various
+ // histograms.
+ base::FilePath crx_path_for_histograms_;
+
+ // Our client.
+ scoped_refptr<SandboxedUnpackerClient> client_;
+
+ // The Extensions directory inside the profile.
+ base::FilePath extensions_dir_;
+
+ // A temporary directory to use for unpacking.
+ base::ScopedTempDir temp_dir_;
+
+ // The root directory of the unpacked extension. This is a child of temp_dir_.
+ base::FilePath extension_root_;
+
+ // Represents the extension we're unpacking.
+ scoped_refptr<Extension> extension_;
+
+ // Whether we've received a response from the utility process yet.
+ bool got_response_;
+
+ // The public key that was extracted from the CRX header.
+ std::string public_key_;
+
+ // The extension's ID. This will be calculated from the public key in the crx
+ // header.
+ std::string extension_id_;
+
+ // If we unpacked a .crx file, the time at which unpacking started. Used to
+ // compute the time unpacking takes.
+ base::TimeTicks crx_unpack_start_time_;
+
+ // Location to use for the unpacked extension.
+ Manifest::Location location_;
+
+ // Creation flags to use for the extension. These flags will be used
+ // when calling Extenion::Create() by the crx installer.
+ int creation_flags_;
+
+ // Sequenced task runner where file I/O operations will be performed at.
+ scoped_refptr<base::SequencedTaskRunner> unpacker_io_task_runner_;
+
+ // Used for sending tasks to the utility process.
+ scoped_refptr<UtilityHostWrapper> utility_wrapper_;
+
+ DISALLOW_COPY_AND_ASSIGN(SandboxedUnpacker);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_SANDBOXED_UNPACKER_H_
diff --git a/chromium/extensions/browser/sandboxed_unpacker_unittest.cc b/chromium/extensions/browser/sandboxed_unpacker_unittest.cc
new file mode 100644
index 00000000000..a66b2fa5423
--- /dev/null
+++ b/chromium/extensions/browser/sandboxed_unpacker_unittest.cc
@@ -0,0 +1,191 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/strings/string_util.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "components/crx_file/id_util.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "content/public/test/test_utils.h"
+#include "extensions/browser/extensions_test.h"
+#include "extensions/browser/sandboxed_unpacker.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_paths.h"
+#include "extensions/common/switches.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/zlib/google/zip.h"
+
+namespace extensions {
+
+class MockSandboxedUnpackerClient : public SandboxedUnpackerClient {
+ public:
+ void WaitForUnpack() {
+ scoped_refptr<content::MessageLoopRunner> runner =
+ new content::MessageLoopRunner;
+ quit_closure_ = runner->QuitClosure();
+ runner->Run();
+ }
+
+ base::FilePath temp_dir() const { return temp_dir_; }
+ base::string16 unpack_err() const { return error_; }
+
+ private:
+ ~MockSandboxedUnpackerClient() override {}
+
+ void OnUnpackSuccess(const base::FilePath& temp_dir,
+ const base::FilePath& extension_root,
+ const base::DictionaryValue* original_manifest,
+ const Extension* extension,
+ const SkBitmap& install_icon) override {
+ temp_dir_ = temp_dir;
+ quit_closure_.Run();
+ }
+
+ void OnUnpackFailure(const CrxInstallError& error) override {
+ error_ = error.message();
+ quit_closure_.Run();
+ }
+
+ base::string16 error_;
+ base::Closure quit_closure_;
+ base::FilePath temp_dir_;
+};
+
+class SandboxedUnpackerTest : public ExtensionsTest {
+ public:
+ void SetUp() override {
+ ExtensionsTest::SetUp();
+ ASSERT_TRUE(extensions_dir_.CreateUniqueTempDir());
+ browser_threads_.reset(new content::TestBrowserThreadBundle(
+ content::TestBrowserThreadBundle::IO_MAINLOOP));
+ in_process_utility_thread_helper_.reset(
+ new content::InProcessUtilityThreadHelper);
+ // It will delete itself.
+ client_ = new MockSandboxedUnpackerClient;
+
+ sandboxed_unpacker_ = new SandboxedUnpacker(
+ Manifest::INTERNAL, Extension::NO_FLAGS, extensions_dir_.path(),
+ base::ThreadTaskRunnerHandle::Get(), client_);
+ }
+
+ void TearDown() override {
+ // Need to destruct SandboxedUnpacker before the message loop since
+ // it posts a task to it.
+ sandboxed_unpacker_ = NULL;
+ base::RunLoop().RunUntilIdle();
+ ExtensionsTest::TearDown();
+ }
+
+ base::FilePath GetCrxFullPath(const std::string& crx_name) {
+ base::FilePath full_path;
+ EXPECT_TRUE(PathService::Get(extensions::DIR_TEST_DATA, &full_path));
+ full_path = full_path.AppendASCII("unpacker").AppendASCII(crx_name);
+ EXPECT_TRUE(base::PathExists(full_path)) << full_path.value();
+ return full_path;
+ }
+
+ void SetupUnpacker(const std::string& crx_name,
+ const std::string& package_hash) {
+ base::FilePath crx_path = GetCrxFullPath(crx_name);
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &SandboxedUnpacker::StartWithCrx, sandboxed_unpacker_,
+ extensions::CRXFileInfo(std::string(), crx_path, package_hash)));
+ client_->WaitForUnpack();
+ }
+
+ void SetupUnpackerWithDirectory(const std::string& crx_name) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath crx_path = GetCrxFullPath(crx_name);
+ ASSERT_TRUE(zip::Unzip(crx_path, temp_dir.path()));
+
+ std::string fake_id = crx_file::id_util::GenerateId(crx_name);
+ std::string fake_public_key;
+ base::Base64Encode(std::string(2048, 'k'), &fake_public_key);
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&SandboxedUnpacker::StartWithDirectory,
+ sandboxed_unpacker_.get(), fake_id,
+ fake_public_key, temp_dir.Take()));
+ client_->WaitForUnpack();
+ }
+
+ base::FilePath GetInstallPath() {
+ return client_->temp_dir().AppendASCII(kTempExtensionName);
+ }
+
+ base::string16 GetInstallError() { return client_->unpack_err(); }
+
+ protected:
+ base::ScopedTempDir extensions_dir_;
+ MockSandboxedUnpackerClient* client_;
+ scoped_refptr<SandboxedUnpacker> sandboxed_unpacker_;
+ scoped_ptr<content::TestBrowserThreadBundle> browser_threads_;
+ scoped_ptr<content::InProcessUtilityThreadHelper>
+ in_process_utility_thread_helper_;
+};
+
+TEST_F(SandboxedUnpackerTest, NoCatalogsSuccess) {
+ SetupUnpacker("no_l10n.crx", "");
+ // Check that there is no _locales folder.
+ base::FilePath install_path = GetInstallPath().Append(kLocaleFolder);
+ EXPECT_FALSE(base::PathExists(install_path));
+}
+
+TEST_F(SandboxedUnpackerTest, FromDirNoCatalogsSuccess) {
+ SetupUnpackerWithDirectory("no_l10n.crx");
+ // Check that there is no _locales folder.
+ base::FilePath install_path = GetInstallPath().Append(kLocaleFolder);
+ EXPECT_FALSE(base::PathExists(install_path));
+}
+
+TEST_F(SandboxedUnpackerTest, WithCatalogsSuccess) {
+ SetupUnpacker("good_l10n.crx", "");
+ // Check that there is _locales folder.
+ base::FilePath install_path = GetInstallPath().Append(kLocaleFolder);
+ EXPECT_TRUE(base::PathExists(install_path));
+}
+
+TEST_F(SandboxedUnpackerTest, FromDirWithCatalogsSuccess) {
+ SetupUnpackerWithDirectory("good_l10n.crx");
+ // Check that there is _locales folder.
+ base::FilePath install_path = GetInstallPath().Append(kLocaleFolder);
+ EXPECT_TRUE(base::PathExists(install_path));
+}
+
+TEST_F(SandboxedUnpackerTest, FailHashCheck) {
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(
+ extensions::switches::kEnableCrxHashCheck);
+ SetupUnpacker("good_l10n.crx", "badhash");
+ // Check that there is an error message.
+ EXPECT_NE(base::string16(), GetInstallError());
+}
+
+TEST_F(SandboxedUnpackerTest, PassHashCheck) {
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(
+ extensions::switches::kEnableCrxHashCheck);
+ SetupUnpacker(
+ "good_l10n.crx",
+ "6fa171c726373785aa4fcd2df448c3db0420a95d5044fbee831f089b979c4068");
+ // Check that there is no error message.
+ EXPECT_EQ(base::string16(), GetInstallError());
+}
+
+TEST_F(SandboxedUnpackerTest, SkipHashCheck) {
+ SetupUnpacker("good_l10n.crx", "badhash");
+ // Check that there is no error message.
+ EXPECT_EQ(base::string16(), GetInstallError());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/script_execution_observer.h b/chromium/extensions/browser/script_execution_observer.h
new file mode 100644
index 00000000000..4204f137bd8
--- /dev/null
+++ b/chromium/extensions/browser/script_execution_observer.h
@@ -0,0 +1,44 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_SCRIPT_EXECUTION_OBSERVER_H_
+#define EXTENSIONS_BROWSER_SCRIPT_EXECUTION_OBSERVER_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+class GURL;
+
+namespace content {
+class WebContents;
+}
+
+namespace extensions {
+
+// Observer base class for classes that need to be notified when content
+// scripts and/or tabs.executeScript calls run on a page.
+class ScriptExecutionObserver {
+ public:
+ // Map of extensions IDs to the executing script paths.
+ typedef std::map<std::string, std::set<std::string> > ExecutingScriptsMap;
+
+ // Called when script(s) have executed on a page.
+ //
+ // |executing_scripts_map| contains all extensions that are executing
+ // scripts, mapped to the paths for those scripts. The paths may be an empty
+ // set if the script has no path associated with it (e.g. in the case of
+ // tabs.executeScript), but there will still be an entry for the extension.
+ virtual void OnScriptsExecuted(
+ const content::WebContents* web_contents,
+ const ExecutingScriptsMap& executing_scripts_map,
+ const GURL& on_url) = 0;
+
+ protected:
+ virtual ~ScriptExecutionObserver();
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_SCRIPT_EXECUTION_OBSERVER_H_
diff --git a/chromium/extensions/browser/script_executor.cc b/chromium/extensions/browser/script_executor.cc
new file mode 100644
index 00000000000..230bca3b3f8
--- /dev/null
+++ b/chromium/extensions/browser/script_executor.cc
@@ -0,0 +1,278 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/script_executor.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/pickle.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "extensions/browser/extension_api_frame_id_map.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/script_execution_observer.h"
+#include "extensions/common/extension_messages.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_macros.h"
+
+namespace base {
+class ListValue;
+} // namespace base
+
+namespace extensions {
+
+namespace {
+
+const char* kRendererDestroyed = "The tab was closed.";
+const char* kFrameRemoved = "The frame was removed.";
+
+// A handler for a single injection request. On creation this will send the
+// injection request to the renderer, and it will be destroyed after either the
+// corresponding response comes from the renderer, or the renderer is destroyed.
+class Handler : public content::WebContentsObserver {
+ public:
+ Handler(base::ObserverList<ScriptExecutionObserver>* script_observers,
+ content::WebContents* web_contents,
+ const ExtensionMsg_ExecuteCode_Params& params,
+ ScriptExecutor::FrameScope scope,
+ int frame_id,
+ const ScriptExecutor::ExecuteScriptCallback& callback)
+ : content::WebContentsObserver(web_contents),
+ script_observers_(AsWeakPtr(script_observers)),
+ host_id_(params.host_id),
+ request_id_(params.request_id),
+ include_sub_frames_(scope == ScriptExecutor::INCLUDE_SUB_FRAMES),
+ root_rfh_(ExtensionApiFrameIdMap::GetRenderFrameHostById(web_contents,
+ frame_id)),
+ root_is_main_frame_(root_rfh_ ? !root_rfh_->GetParent() : false),
+ callback_(callback) {
+ if (root_rfh_) {
+ if (include_sub_frames_) {
+ web_contents->ForEachFrame(base::Bind(&Handler::SendExecuteCode,
+ base::Unretained(this), params));
+ } else {
+ SendExecuteCode(params, root_rfh_);
+ }
+ }
+
+ if (pending_render_frames_.empty())
+ Finish();
+ }
+
+ private:
+ // This class manages its own lifetime.
+ ~Handler() override {}
+
+ // content::WebContentsObserver:
+ void WebContentsDestroyed() override { Finish(); }
+
+ bool OnMessageReceived(const IPC::Message& message,
+ content::RenderFrameHost* render_frame_host) override {
+ // Unpack by hand to check the request_id, since there may be multiple
+ // requests in flight but only one is for this.
+ if (message.type() != ExtensionHostMsg_ExecuteCodeFinished::ID)
+ return false;
+
+ int message_request_id;
+ base::PickleIterator iter(message);
+ CHECK(iter.ReadInt(&message_request_id));
+
+ if (message_request_id != request_id_)
+ return false;
+
+ IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(Handler, message, render_frame_host)
+ IPC_MESSAGE_HANDLER(ExtensionHostMsg_ExecuteCodeFinished,
+ OnExecuteCodeFinished)
+ IPC_END_MESSAGE_MAP()
+ return true;
+ }
+
+ void RenderFrameDeleted(
+ content::RenderFrameHost* render_frame_host) override {
+ if (pending_render_frames_.erase(render_frame_host) == 1 &&
+ pending_render_frames_.empty()) {
+ Finish();
+ }
+ }
+
+ // Sends an ExecuteCode message to the given frame host, and increments
+ // the number of pending messages.
+ void SendExecuteCode(const ExtensionMsg_ExecuteCode_Params& params,
+ content::RenderFrameHost* frame) {
+ if (!frame->IsRenderFrameLive())
+ return;
+ DCHECK(!root_is_main_frame_ || ShouldIncludeFrame(frame));
+ if (!root_is_main_frame_ && !ShouldIncludeFrame(frame))
+ return;
+ pending_render_frames_.insert(frame);
+ frame->Send(new ExtensionMsg_ExecuteCode(frame->GetRoutingID(), params));
+ }
+
+ // Returns whether a frame is the root frame or a descendant of it.
+ bool ShouldIncludeFrame(content::RenderFrameHost* frame) {
+ while (frame) {
+ if (frame == root_rfh_)
+ return true;
+ frame = frame->GetParent();
+ }
+ return false;
+ }
+
+ // Handles the ExecuteCodeFinished message.
+ void OnExecuteCodeFinished(content::RenderFrameHost* render_frame_host,
+ int request_id,
+ const std::string& error,
+ const GURL& on_url,
+ const base::ListValue& result_list) {
+ DCHECK_EQ(request_id_, request_id);
+ DCHECK(!pending_render_frames_.empty());
+ bool erased = pending_render_frames_.erase(render_frame_host) == 1;
+ DCHECK(erased);
+ bool is_root_frame = root_rfh_ == render_frame_host;
+
+ // Set the result, if there is one.
+ const base::Value* script_value = nullptr;
+ if (result_list.Get(0u, &script_value)) {
+ // If this is the main result, we put it at index 0. Otherwise, we just
+ // append it at the end.
+ if (is_root_frame && !results_.empty())
+ CHECK(results_.Insert(0u, script_value->DeepCopy()));
+ else
+ results_.Append(script_value->DeepCopy());
+ }
+
+ if (is_root_frame) { // Only use the root frame's error and url.
+ root_frame_error_ = error;
+ root_frame_url_ = on_url;
+ }
+
+ // Wait until the final request finishes before reporting back.
+ if (pending_render_frames_.empty())
+ Finish();
+ }
+
+ void Finish() {
+ if (root_frame_url_.is_empty()) {
+ // We never finished the root frame injection.
+ root_frame_error_ =
+ root_is_main_frame_ ? kRendererDestroyed : kFrameRemoved;
+ results_.Clear();
+ }
+
+ if (script_observers_.get() && root_frame_error_.empty() &&
+ host_id_.type() == HostID::EXTENSIONS) {
+ ScriptExecutionObserver::ExecutingScriptsMap id_map;
+ id_map[host_id_.id()] = std::set<std::string>();
+ FOR_EACH_OBSERVER(
+ ScriptExecutionObserver, *script_observers_,
+ OnScriptsExecuted(web_contents(), id_map, root_frame_url_));
+ }
+
+ if (!callback_.is_null())
+ callback_.Run(root_frame_error_, root_frame_url_, results_);
+ delete this;
+ }
+
+ base::WeakPtr<base::ObserverList<ScriptExecutionObserver>> script_observers_;
+
+ // The id of the host (the extension or the webui) doing the injection.
+ HostID host_id_;
+
+ // The request id of the injection.
+ int request_id_;
+
+ // Whether to inject in |root_rfh_| and all of its descendant frames.
+ bool include_sub_frames_;
+
+ // The frame (and optionally its descendant frames) where the injection will
+ // occur.
+ content::RenderFrameHost* root_rfh_;
+
+ // Whether |root_rfh_| is the main frame of a tab.
+ bool root_is_main_frame_;
+
+ // The hosts of the still-running injections.
+ std::set<content::RenderFrameHost*> pending_render_frames_;
+
+ // The results of the injection.
+ base::ListValue results_;
+
+ // The error from injecting into the root frame.
+ std::string root_frame_error_;
+
+ // The url of the root frame.
+ GURL root_frame_url_;
+
+ // The callback to run after all injections complete.
+ ScriptExecutor::ExecuteScriptCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(Handler);
+};
+
+} // namespace
+
+ScriptExecutionObserver::~ScriptExecutionObserver() {
+}
+
+ScriptExecutor::ScriptExecutor(
+ content::WebContents* web_contents,
+ base::ObserverList<ScriptExecutionObserver>* script_observers)
+ : next_request_id_(0),
+ web_contents_(web_contents),
+ script_observers_(script_observers) {
+ CHECK(web_contents_);
+}
+
+ScriptExecutor::~ScriptExecutor() {
+}
+
+void ScriptExecutor::ExecuteScript(const HostID& host_id,
+ ScriptExecutor::ScriptType script_type,
+ const std::string& code,
+ ScriptExecutor::FrameScope frame_scope,
+ int frame_id,
+ ScriptExecutor::MatchAboutBlank about_blank,
+ UserScript::RunLocation run_at,
+ ScriptExecutor::WorldType world_type,
+ ScriptExecutor::ProcessType process_type,
+ const GURL& webview_src,
+ const GURL& file_url,
+ bool user_gesture,
+ ScriptExecutor::ResultType result_type,
+ const ExecuteScriptCallback& callback) {
+ if (host_id.type() == HostID::EXTENSIONS) {
+ // Don't execute if the extension has been unloaded.
+ const Extension* extension =
+ ExtensionRegistry::Get(web_contents_->GetBrowserContext())
+ ->enabled_extensions().GetByID(host_id.id());
+ if (!extension)
+ return;
+ } else {
+ CHECK(process_type == WEB_VIEW_PROCESS);
+ }
+
+ ExtensionMsg_ExecuteCode_Params params;
+ params.request_id = next_request_id_++;
+ params.host_id = host_id;
+ params.is_javascript = (script_type == JAVASCRIPT);
+ params.code = code;
+ params.match_about_blank = (about_blank == MATCH_ABOUT_BLANK);
+ params.run_at = static_cast<int>(run_at);
+ params.in_main_world = (world_type == MAIN_WORLD);
+ params.is_web_view = (process_type == WEB_VIEW_PROCESS);
+ params.webview_src = webview_src;
+ params.file_url = file_url;
+ params.wants_result = (result_type == JSON_SERIALIZED_RESULT);
+ params.user_gesture = user_gesture;
+
+ // Handler handles IPCs and deletes itself on completion.
+ new Handler(script_observers_, web_contents_, params, frame_scope, frame_id,
+ callback);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/script_executor.h b/chromium/extensions/browser/script_executor.h
new file mode 100644
index 00000000000..25e6c54a344
--- /dev/null
+++ b/chromium/extensions/browser/script_executor.h
@@ -0,0 +1,118 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_SCRIPT_EXECUTOR_H_
+#define EXTENSIONS_BROWSER_SCRIPT_EXECUTOR_H_
+
+#include "base/callback_forward.h"
+#include "base/observer_list.h"
+#include "extensions/common/user_script.h"
+
+class GURL;
+struct ExtensionMsg_ExecuteCode_Params;
+
+namespace base {
+class ListValue;
+} // namespace base
+
+namespace content {
+class WebContents;
+}
+
+namespace extensions {
+class ScriptExecutionObserver;
+
+// Interface for executing extension content scripts (e.g. executeScript) as
+// described by the ExtensionMsg_ExecuteCode_Params IPC, and notifying the
+// caller when responded with ExtensionHostMsg_ExecuteCodeFinished.
+class ScriptExecutor {
+ public:
+ ScriptExecutor(
+ content::WebContents* web_contents,
+ // |script_observers| is assumed to be owned by |this|'s owner, and in
+ // such a way that |this| is destroyed first.
+ base::ObserverList<ScriptExecutionObserver>* script_observers);
+
+ ~ScriptExecutor();
+
+ // The type of script being injected.
+ enum ScriptType {
+ JAVASCRIPT,
+ CSS,
+ };
+
+ // The scope of the script injection across the frames.
+ enum FrameScope {
+ SINGLE_FRAME,
+ INCLUDE_SUB_FRAMES,
+ };
+
+ // Whether to insert the script in about: frames when its origin matches
+ // the extension's host permissions.
+ enum MatchAboutBlank {
+ DONT_MATCH_ABOUT_BLANK,
+ MATCH_ABOUT_BLANK,
+ };
+
+ // The type of world to inject into (main world, or its own isolated world).
+ enum WorldType {
+ MAIN_WORLD,
+ ISOLATED_WORLD,
+ };
+
+ // The type of process the target is.
+ enum ProcessType {
+ DEFAULT_PROCESS,
+ WEB_VIEW_PROCESS,
+ };
+
+ // The type of result the caller is interested in.
+ enum ResultType {
+ NO_RESULT,
+ JSON_SERIALIZED_RESULT,
+ };
+
+ // Callback from ExecuteScript. The arguments are (error, on_url, result).
+ // Success is implied by an empty error.
+ typedef base::Callback<
+ void(const std::string&, const GURL&, const base::ListValue&)>
+ ExecuteScriptCallback;
+
+ // Executes a script. The arguments match ExtensionMsg_ExecuteCode_Params in
+ // extension_messages.h (request_id is populated automatically).
+ //
+ // The script will be executed in the frame identified by |frame_id| (which is
+ // an extension API frame ID). If |frame_scope| is INCLUDE_SUB_FRAMES, then
+ // the script will also be executed in all descendants of the frame.
+ //
+ // |callback| will always be called even if the IPC'd renderer is destroyed
+ // before a response is received (in this case the callback will be with a
+ // failure and appropriate error message).
+ void ExecuteScript(const HostID& host_id,
+ ScriptType script_type,
+ const std::string& code,
+ FrameScope frame_scope,
+ int frame_id,
+ MatchAboutBlank match_about_blank,
+ UserScript::RunLocation run_at,
+ WorldType world_type,
+ ProcessType process_type,
+ const GURL& webview_src,
+ const GURL& file_url,
+ bool user_gesture,
+ ResultType result_type,
+ const ExecuteScriptCallback& callback);
+
+ private:
+ // The next value to use for request_id in ExtensionMsg_ExecuteCode_Params.
+ int next_request_id_;
+
+ content::WebContents* web_contents_;
+
+ base::ObserverList<ScriptExecutionObserver>* script_observers_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_SCRIPT_EXECUTOR_H_
diff --git a/chromium/extensions/browser/serial_extension_host_queue.cc b/chromium/extensions/browser/serial_extension_host_queue.cc
new file mode 100644
index 00000000000..c305c93240f
--- /dev/null
+++ b/chromium/extensions/browser/serial_extension_host_queue.cc
@@ -0,0 +1,84 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/serial_extension_host_queue.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/time.h"
+#include "components/variations/variations_associated_data.h"
+#include "extensions/browser/deferred_start_render_host.h"
+
+namespace extensions {
+
+namespace {
+
+// Gets the number of milliseconds to delay between loading ExtensionHosts. By
+// default this is 0, but it can be overridden by field trials.
+int GetDelayMs() {
+ // A sanity check for the maximum delay, to guard against a bad field trial
+ // config being pushed that delays loading too much (e.g. using wrong units).
+ static const int kMaxDelayMs = 30 * 1000;
+ static int delay_ms = -1;
+ if (delay_ms == -1) {
+ std::string delay_ms_param =
+ variations::GetVariationParamValue("ExtensionSpeed", "SerialEHQDelay");
+ if (delay_ms_param.empty()) {
+ delay_ms = 0;
+ } else if (!base::StringToInt(delay_ms_param, &delay_ms)) {
+ LOG(ERROR) << "Could not parse SerialEHQDelay: " << delay_ms_param;
+ delay_ms = 0;
+ } else if (delay_ms < 0 || delay_ms > kMaxDelayMs) {
+ LOG(ERROR) << "SerialEHQDelay out of range: " << delay_ms;
+ delay_ms = 0;
+ }
+ }
+ return delay_ms;
+}
+
+} // namespace
+
+SerialExtensionHostQueue::SerialExtensionHostQueue()
+ : pending_create_(false), ptr_factory_(this) {
+}
+
+SerialExtensionHostQueue::~SerialExtensionHostQueue() {
+}
+
+void SerialExtensionHostQueue::Add(DeferredStartRenderHost* host) {
+ queue_.push_back(host);
+ PostTask();
+}
+
+void SerialExtensionHostQueue::Remove(DeferredStartRenderHost* host) {
+ std::list<DeferredStartRenderHost*>::iterator it =
+ std::find(queue_.begin(), queue_.end(), host);
+ if (it != queue_.end())
+ queue_.erase(it);
+}
+
+void SerialExtensionHostQueue::PostTask() {
+ if (!pending_create_) {
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, base::Bind(&SerialExtensionHostQueue::ProcessOneHost,
+ ptr_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(GetDelayMs()));
+ pending_create_ = true;
+ }
+}
+
+void SerialExtensionHostQueue::ProcessOneHost() {
+ pending_create_ = false;
+ if (queue_.empty())
+ return; // can happen on shutdown
+
+ queue_.front()->CreateRenderViewNow();
+ queue_.pop_front();
+
+ if (!queue_.empty())
+ PostTask();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/serial_extension_host_queue.h b/chromium/extensions/browser/serial_extension_host_queue.h
new file mode 100644
index 00000000000..40d11edd222
--- /dev/null
+++ b/chromium/extensions/browser/serial_extension_host_queue.h
@@ -0,0 +1,52 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_SERIAL_EXTENSION_HOST_QUEUE_H_
+#define EXTENSIONS_BROWSER_SERIAL_EXTENSION_HOST_QUEUE_H_
+
+#include <list>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "extensions/browser/extension_host_queue.h"
+
+namespace extensions {
+
+class DeferredStartRenderHost;
+
+// An ExtensionHostQueue which initializes DeferredStartRenderHosts in the order
+// they're Add()ed, with simple rate limiting logic that re-posts each task to
+// the UI thread, to avoid clogging it for a long period of time.
+class SerialExtensionHostQueue : public ExtensionHostQueue {
+ public:
+ SerialExtensionHostQueue();
+ ~SerialExtensionHostQueue() override;
+
+ private:
+ // ExtensionHostQueue:
+ void Add(DeferredStartRenderHost* host) override;
+ void Remove(DeferredStartRenderHost* host) override;
+
+ // Queues up a delayed task to process the next DeferredStartRenderHost in
+ // the queue.
+ void PostTask();
+
+ // Creates the RenderView for the next host in the queue.
+ void ProcessOneHost();
+
+ // True if this queue is currently in the process of starting an
+ // DeferredStartRenderHost.
+ bool pending_create_;
+
+ // The list of DeferredStartRenderHosts waiting to be started.
+ std::list<DeferredStartRenderHost*> queue_;
+
+ base::WeakPtrFactory<SerialExtensionHostQueue> ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SerialExtensionHostQueue);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_SERIAL_EXTENSION_HOST_QUEUE_H_
diff --git a/chromium/extensions/browser/service_worker_manager.cc b/chromium/extensions/browser/service_worker_manager.cc
new file mode 100644
index 00000000000..16167d4e3d9
--- /dev/null
+++ b/chromium/extensions/browser/service_worker_manager.cc
@@ -0,0 +1,51 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/service_worker_manager.h"
+
+#include "base/bind.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/service_worker_context.h"
+#include "content/public/browser/storage_partition.h"
+#include "extensions/browser/extension_registry.h"
+
+namespace extensions {
+
+namespace {
+void EmptySuccessCallback(bool success) {}
+}
+
+ServiceWorkerManager::ServiceWorkerManager(
+ content::BrowserContext* browser_context)
+ : browser_context_(browser_context), registry_observer_(this) {
+ registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
+}
+
+ServiceWorkerManager::~ServiceWorkerManager() {}
+
+void ServiceWorkerManager::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ content::BrowserContext::GetStoragePartitionForSite(browser_context_,
+ extension->url())
+ ->GetServiceWorkerContext()
+ ->StopAllServiceWorkersForOrigin(extension->url());
+}
+
+void ServiceWorkerManager::OnExtensionUninstalled(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ extensions::UninstallReason reason) {
+ // TODO(devlin): Technically, this can fail. We should ideally:
+ // a) Keep track of extensions with registered service workers.
+ // b) Add a callback to the (Un)SuspendServiceWorkersOnOrigin() method.
+ // c) Check for any orphaned workers.
+ content::BrowserContext::GetStoragePartitionForSite(browser_context_,
+ extension->url())
+ ->GetServiceWorkerContext()
+ ->DeleteForOrigin(extension->url(), base::Bind(&EmptySuccessCallback));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/service_worker_manager.h b/chromium/extensions/browser/service_worker_manager.h
new file mode 100644
index 00000000000..2ba3ad10e1a
--- /dev/null
+++ b/chromium/extensions/browser/service_worker_manager.h
@@ -0,0 +1,40 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/macros.h"
+#include "base/scoped_observer.h"
+#include "extensions/browser/extension_registry_observer.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+// A helper class to manage extension service workers. Handles suspending
+// them when the extension is unloaded and removing them when the extension is
+// uninstalled.
+class ServiceWorkerManager : public ExtensionRegistryObserver {
+ public:
+ explicit ServiceWorkerManager(content::BrowserContext* browser_context);
+ ~ServiceWorkerManager() override;
+
+ private:
+ // ExtensionRegistryObserver:
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+ void OnExtensionUninstalled(content::BrowserContext* browser_context,
+ const Extension* extension,
+ extensions::UninstallReason reason) override;
+
+ content::BrowserContext* browser_context_;
+
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ registry_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ServiceWorkerManager);
+};
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/state_store.cc b/chromium/extensions/browser/state_store.cc
new file mode 100644
index 00000000000..2c8c9f9f1c8
--- /dev/null
+++ b/chromium/extensions/browser/state_store.cc
@@ -0,0 +1,186 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/state_store.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/value_store/value_store_factory.h"
+#include "extensions/common/extension.h"
+
+namespace {
+
+// Delay, in seconds, before we should open the State Store database. We
+// defer it to avoid slowing down startup. See http://crbug.com/161848
+const int kInitDelaySeconds = 1;
+
+std::string GetFullKey(const std::string& extension_id,
+ const std::string& key) {
+ return extension_id + "." + key;
+}
+
+} // namespace
+
+namespace extensions {
+
+// Helper class to delay tasks until we're ready to start executing them.
+class StateStore::DelayedTaskQueue {
+ public:
+ DelayedTaskQueue() : ready_(false) {}
+ ~DelayedTaskQueue() {}
+
+ // Queues up a task for invoking once we're ready. Invokes immediately if
+ // we're already ready.
+ void InvokeWhenReady(base::Closure task);
+
+ // Marks us ready, and invokes all pending tasks.
+ void SetReady();
+
+ // Return whether or not the DelayedTaskQueue is |ready_|.
+ bool ready() const { return ready_; }
+
+ private:
+ bool ready_;
+ std::vector<base::Closure> pending_tasks_;
+};
+
+void StateStore::DelayedTaskQueue::InvokeWhenReady(base::Closure task) {
+ if (ready_) {
+ task.Run();
+ } else {
+ pending_tasks_.push_back(task);
+ }
+}
+
+void StateStore::DelayedTaskQueue::SetReady() {
+ ready_ = true;
+
+ for (size_t i = 0; i < pending_tasks_.size(); ++i)
+ pending_tasks_[i].Run();
+ pending_tasks_.clear();
+}
+
+StateStore::StateStore(content::BrowserContext* context,
+ const scoped_refptr<ValueStoreFactory>& store_factory,
+ ValueStoreFrontend::BackendType backend_type,
+ bool deferred_load)
+ : store_(new ValueStoreFrontend(store_factory, backend_type)),
+ task_queue_(new DelayedTaskQueue()),
+ extension_registry_observer_(this) {
+ extension_registry_observer_.Add(ExtensionRegistry::Get(context));
+
+ if (deferred_load) {
+ // Don't Init() until the first page is loaded or the embedder explicitly
+ // requests it.
+ registrar_.Add(
+ this,
+ content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
+ content::NotificationService::AllBrowserContextsAndSources());
+ } else {
+ Init();
+ }
+}
+
+StateStore::~StateStore() {
+}
+
+void StateStore::RequestInitAfterDelay() {
+ InitAfterDelay();
+}
+
+void StateStore::RegisterKey(const std::string& key) {
+ registered_keys_.insert(key);
+}
+
+void StateStore::GetExtensionValue(const std::string& extension_id,
+ const std::string& key,
+ ReadCallback callback) {
+ task_queue_->InvokeWhenReady(
+ base::Bind(&ValueStoreFrontend::Get, base::Unretained(store_.get()),
+ GetFullKey(extension_id, key), callback));
+}
+
+void StateStore::SetExtensionValue(const std::string& extension_id,
+ const std::string& key,
+ scoped_ptr<base::Value> value) {
+ task_queue_->InvokeWhenReady(
+ base::Bind(&ValueStoreFrontend::Set, base::Unretained(store_.get()),
+ GetFullKey(extension_id, key), base::Passed(&value)));
+}
+
+void StateStore::RemoveExtensionValue(const std::string& extension_id,
+ const std::string& key) {
+ task_queue_->InvokeWhenReady(base::Bind(&ValueStoreFrontend::Remove,
+ base::Unretained(store_.get()),
+ GetFullKey(extension_id, key)));
+}
+
+bool StateStore::IsInitialized() const {
+ return task_queue_->ready();
+}
+
+void StateStore::Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ DCHECK_EQ(type, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME);
+ registrar_.RemoveAll();
+ InitAfterDelay();
+}
+
+void StateStore::OnExtensionWillBeInstalled(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ bool is_update,
+ const std::string& old_name) {
+ RemoveKeysForExtension(extension->id());
+}
+
+void StateStore::OnExtensionUninstalled(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ extensions::UninstallReason reason) {
+ RemoveKeysForExtension(extension->id());
+}
+
+void StateStore::Init() {
+ // Could be called twice if InitAfterDelay() is requested explicitly by the
+ // embedder in addition to internally after first page load.
+ if (IsInitialized())
+ return;
+
+ // TODO(cmumford): The store now always lazily initializes upon first access.
+ // A follow-on CL will remove this deferred initialization implementation
+ // which is now vestigial.
+ task_queue_->SetReady();
+}
+
+void StateStore::InitAfterDelay() {
+ if (IsInitialized())
+ return;
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&StateStore::Init, AsWeakPtr()),
+ base::TimeDelta::FromSeconds(kInitDelaySeconds));
+}
+
+void StateStore::RemoveKeysForExtension(const std::string& extension_id) {
+ for (std::set<std::string>::iterator key = registered_keys_.begin();
+ key != registered_keys_.end();
+ ++key) {
+ task_queue_->InvokeWhenReady(base::Bind(&ValueStoreFrontend::Remove,
+ base::Unretained(store_.get()),
+ GetFullKey(extension_id, *key)));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/state_store.h b/chromium/extensions/browser/state_store.h
new file mode 100644
index 00000000000..5cc0037cdb5
--- /dev/null
+++ b/chromium/extensions/browser/state_store.h
@@ -0,0 +1,116 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_STATE_STORE_H_
+#define EXTENSIONS_BROWSER_STATE_STORE_H_
+
+#include <set>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "base/scoped_observer.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/browser/value_store/value_store_frontend.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class ExtensionRegistry;
+class ValueStoreFactory;
+
+// A storage area for per-extension state that needs to be persisted to disk.
+class StateStore : public base::SupportsWeakPtr<StateStore>,
+ public ExtensionRegistryObserver,
+ public content::NotificationObserver {
+ public:
+ typedef ValueStoreFrontend::ReadCallback ReadCallback;
+
+ // If |deferred_load| is true, we won't load the database until the first
+ // page has been loaded.
+ StateStore(content::BrowserContext* context,
+ const scoped_refptr<ValueStoreFactory>& store_factory,
+ ValueStoreFrontend::BackendType backend_type,
+ bool deferred_load);
+ // This variant is useful for testing (using a mock ValueStore).
+ StateStore(content::BrowserContext* context, scoped_ptr<ValueStore> store);
+ ~StateStore() override;
+
+ // Requests that the state store to be initialized after its usual delay. Can
+ // be explicitly called by an embedder when the embedder does not trigger the
+ // usual page load notifications.
+ void RequestInitAfterDelay();
+
+ // Register a key for removal upon extension install/uninstall. We remove
+ // for install to reset state when an extension upgrades.
+ void RegisterKey(const std::string& key);
+
+ // Get the value associated with the given extension and key, and pass
+ // it to |callback| asynchronously.
+ void GetExtensionValue(const std::string& extension_id,
+ const std::string& key,
+ ReadCallback callback);
+
+ // Sets a value for a given extension and key.
+ void SetExtensionValue(const std::string& extension_id,
+ const std::string& key,
+ scoped_ptr<base::Value> value);
+
+ // Removes a value for a given extension and key.
+ void RemoveExtensionValue(const std::string& extension_id,
+ const std::string& key);
+
+ // Return whether or not the StateStore has initialized itself.
+ bool IsInitialized() const;
+
+ private:
+ class DelayedTaskQueue;
+
+ // content::NotificationObserver
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override;
+
+ void Init();
+
+ // When StateStore is constructed with |deferred_load| its initialization is
+ // delayed to avoid slowing down startup.
+ void InitAfterDelay();
+
+ // Removes all keys registered for the given extension.
+ void RemoveKeysForExtension(const std::string& extension_id);
+
+ // ExtensionRegistryObserver implementation.
+ void OnExtensionUninstalled(content::BrowserContext* browser_context,
+ const Extension* extension,
+ extensions::UninstallReason reason) override;
+ void OnExtensionWillBeInstalled(content::BrowserContext* browser_context,
+ const Extension* extension,
+ bool is_update,
+ const std::string& old_name) override;
+
+ // The store that holds our key/values.
+ scoped_ptr<ValueStoreFrontend> store_;
+
+ // List of all known keys. They will be cleared for each extension when it is
+ // (un)installed.
+ std::set<std::string> registered_keys_;
+
+ // Keeps track of tasks we have delayed while starting up.
+ scoped_ptr<DelayedTaskQueue> task_queue_;
+
+ content::NotificationRegistrar registrar_;
+
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_STATE_STORE_H_
diff --git a/chromium/extensions/browser/suggest_permission_util.cc b/chromium/extensions/browser/suggest_permission_util.cc
new file mode 100644
index 00000000000..d0b018f7738
--- /dev/null
+++ b/chromium/extensions/browser/suggest_permission_util.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/suggest_permission_util.h"
+
+#include "base/strings/stringprintf.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/console_message_level.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/permissions/permissions_info.h"
+
+using content::CONSOLE_MESSAGE_LEVEL_WARNING;
+
+namespace extensions {
+
+namespace {
+
+const char kPermissionsHelpURLForExtensions[] =
+ "http://developer.chrome.com/extensions/manifest.html#permissions";
+const char kPermissionsHelpURLForApps[] =
+ "http://developer.chrome.com/apps/declare_permissions.html";
+
+void SuggestAPIPermissionInDevToolsConsole(
+ APIPermission::ID permission,
+ const Extension* extension,
+ content::RenderFrameHost* render_frame_host) {
+ const APIPermissionInfo* permission_info =
+ PermissionsInfo::GetInstance()->GetByID(permission);
+ CHECK(permission_info);
+
+ // Note, intentionally not internationalizing this string, as it is output
+ // as a log message to developers in the developer tools console.
+ std::string message = base::StringPrintf(
+ "Is the '%s' permission appropriate? See %s.",
+ permission_info->name(),
+ extension->is_platform_app() ?
+ kPermissionsHelpURLForApps : kPermissionsHelpURLForExtensions);
+
+ // Only the main frame handles dev tools messages.
+ content::WebContents::FromRenderFrameHost(render_frame_host)
+ ->GetMainFrame()
+ ->AddMessageToConsole(CONSOLE_MESSAGE_LEVEL_WARNING, message);
+}
+
+} // namespace
+
+bool IsExtensionWithPermissionOrSuggestInConsole(
+ APIPermission::ID permission,
+ const Extension* extension,
+ content::RenderFrameHost* render_frame_host) {
+ if (extension && extension->permissions_data()->HasAPIPermission(permission))
+ return true;
+
+ if (extension && render_frame_host) {
+ SuggestAPIPermissionInDevToolsConsole(permission, extension,
+ render_frame_host);
+ }
+
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/suggest_permission_util.h b/chromium/extensions/browser/suggest_permission_util.h
new file mode 100644
index 00000000000..512aaf27075
--- /dev/null
+++ b/chromium/extensions/browser/suggest_permission_util.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_SUGGEST_PERMISSION_UTIL_H_
+#define EXTENSIONS_BROWSER_SUGGEST_PERMISSION_UTIL_H_
+
+#include "extensions/common/permissions/api_permission.h"
+
+namespace content {
+class RenderFrameHost;
+}
+
+namespace extensions {
+
+class Extension;
+
+// Checks that |extension| is not NULL and that it has |permission|. If
+// |extension| is NULL, just returns false. If an extension without |permission|
+// returns false and suggests |permision| in the developer tools console.
+bool IsExtensionWithPermissionOrSuggestInConsole(
+ APIPermission::ID permission,
+ const Extension* extension,
+ content::RenderFrameHost* render_frame_host);
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_SUGGEST_PERMISSION_UTIL_H_
diff --git a/chromium/extensions/browser/test_extension_registry_observer.cc b/chromium/extensions/browser/test_extension_registry_observer.cc
new file mode 100644
index 00000000000..ab53b7a437a
--- /dev/null
+++ b/chromium/extensions/browser/test_extension_registry_observer.cc
@@ -0,0 +1,118 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/test_extension_registry_observer.h"
+
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "extensions/browser/extension_registry.h"
+
+namespace extensions {
+
+class TestExtensionRegistryObserver::Waiter {
+ public:
+ Waiter() : observed_(false), extension_(nullptr) {}
+
+ void Wait() {
+ if (!observed_)
+ run_loop_.Run();
+ }
+
+ void OnObserved(const Extension* extension) {
+ observed_ = true;
+ run_loop_.Quit();
+ extension_ = extension;
+ }
+
+ const Extension* extension() const { return extension_; }
+
+ private:
+ bool observed_;
+ base::RunLoop run_loop_;
+ const Extension* extension_;
+
+ DISALLOW_COPY_AND_ASSIGN(Waiter);
+};
+
+TestExtensionRegistryObserver::TestExtensionRegistryObserver(
+ ExtensionRegistry* registry)
+ : TestExtensionRegistryObserver(registry, std::string()) {
+}
+
+TestExtensionRegistryObserver::TestExtensionRegistryObserver(
+ ExtensionRegistry* registry,
+ const std::string& extension_id)
+ : will_be_installed_waiter_(new Waiter()),
+ uninstalled_waiter_(new Waiter()),
+ loaded_waiter_(new Waiter()),
+ unloaded_waiter_(new Waiter()),
+ extension_registry_observer_(this),
+ extension_id_(extension_id) {
+ extension_registry_observer_.Add(registry);
+}
+
+TestExtensionRegistryObserver::~TestExtensionRegistryObserver() {
+}
+
+const Extension* TestExtensionRegistryObserver::WaitForExtensionUninstalled() {
+ return Wait(&uninstalled_waiter_);
+}
+
+const Extension*
+TestExtensionRegistryObserver::WaitForExtensionWillBeInstalled() {
+ return Wait(&will_be_installed_waiter_);
+}
+
+const Extension* TestExtensionRegistryObserver::WaitForExtensionLoaded() {
+ return Wait(&loaded_waiter_);
+}
+
+const Extension* TestExtensionRegistryObserver::WaitForExtensionUnloaded() {
+ return Wait(&unloaded_waiter_);
+}
+
+void TestExtensionRegistryObserver::OnExtensionWillBeInstalled(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ bool is_update,
+ const std::string& old_name) {
+ if (extension_id_.empty() || extension->id() == extension_id_)
+ will_be_installed_waiter_->OnObserved(extension);
+}
+
+void TestExtensionRegistryObserver::OnExtensionUninstalled(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ extensions::UninstallReason reason) {
+ if (extension_id_.empty() || extension->id() == extension_id_)
+ uninstalled_waiter_->OnObserved(extension);
+}
+
+void TestExtensionRegistryObserver::OnExtensionLoaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension) {
+ if (extension_id_.empty() || extension->id() == extension_id_)
+ loaded_waiter_->OnObserved(extension);
+}
+
+void TestExtensionRegistryObserver::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ if (extension_id_.empty() || extension->id() == extension_id_)
+ unloaded_waiter_->OnObserved(extension);
+}
+
+const Extension* TestExtensionRegistryObserver::Wait(
+ scoped_ptr<Waiter>* waiter) {
+ waiter->get()->Wait();
+ const Extension* extension = waiter->get()->extension();
+ // Reset the waiter for future uses.
+ // We could have a Waiter::Reset method, but it would reset every field in the
+ // class, so let's just reset the pointer.
+ waiter->reset(new Waiter());
+ return extension;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/test_extension_registry_observer.h b/chromium/extensions/browser/test_extension_registry_observer.h
new file mode 100644
index 00000000000..ce0a3f86e41
--- /dev/null
+++ b/chromium/extensions/browser/test_extension_registry_observer.h
@@ -0,0 +1,69 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_TEST_EXTENSION_REGISTRY_OBSERVER_H_
+#define EXTENSIONS_BROWSER_TEST_EXTENSION_REGISTRY_OBSERVER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/scoped_observer.h"
+#include "extensions/browser/extension_registry_observer.h"
+
+namespace extensions {
+class ExtensionRegistry;
+
+// A helper class that listen for ExtensionRegistry notifications.
+class TestExtensionRegistryObserver : public ExtensionRegistryObserver {
+ public:
+ // If |extension_id| is provided, listens only to events relating to that
+ // extension. Otherwise, listens to all events.
+ explicit TestExtensionRegistryObserver(ExtensionRegistry* registry);
+ TestExtensionRegistryObserver(ExtensionRegistry* registry,
+ const std::string& extension_id);
+
+ ~TestExtensionRegistryObserver() override;
+
+ // Waits for the notification, and returns the extension that caused it.
+ const Extension* WaitForExtensionWillBeInstalled();
+ const Extension* WaitForExtensionUninstalled();
+ const Extension* WaitForExtensionLoaded();
+ const Extension* WaitForExtensionUnloaded();
+
+ private:
+ class Waiter;
+
+ // ExtensionRegistryObserver.
+ void OnExtensionWillBeInstalled(content::BrowserContext* browser_context,
+ const Extension* extension,
+ bool is_update,
+ const std::string& old_name) override;
+ void OnExtensionUninstalled(content::BrowserContext* browser_context,
+ const Extension* extension,
+ extensions::UninstallReason reason) override;
+ void OnExtensionLoaded(content::BrowserContext* browser_context,
+ const Extension* extension) override;
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+
+ const Extension* Wait(scoped_ptr<Waiter>* waiter);
+
+ scoped_ptr<Waiter> will_be_installed_waiter_;
+ scoped_ptr<Waiter> uninstalled_waiter_;
+ scoped_ptr<Waiter> loaded_waiter_;
+ scoped_ptr<Waiter> unloaded_waiter_;
+
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+
+ std::string extension_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestExtensionRegistryObserver);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_TEST_EXTENSION_REGISTRY_OBSERVER_H_
diff --git a/chromium/extensions/browser/test_extensions_browser_client.cc b/chromium/extensions/browser/test_extensions_browser_client.cc
new file mode 100644
index 00000000000..7ba4dfac65c
--- /dev/null
+++ b/chromium/extensions/browser/test_extensions_browser_client.cc
@@ -0,0 +1,221 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/test_extensions_browser_client.h"
+
+#include "base/values.h"
+#include "build/build_config.h"
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/extension_host_delegate.h"
+#include "extensions/browser/test_runtime_api_delegate.h"
+#include "extensions/browser/updater/null_extension_cache.h"
+
+using content::BrowserContext;
+
+namespace extensions {
+
+TestExtensionsBrowserClient::TestExtensionsBrowserClient(
+ BrowserContext* main_context)
+ : main_context_(main_context),
+ incognito_context_(NULL),
+ process_manager_delegate_(NULL),
+ extension_system_factory_(NULL),
+ extension_cache_(new NullExtensionCache) {
+ DCHECK(main_context_);
+ DCHECK(!main_context_->IsOffTheRecord());
+}
+
+TestExtensionsBrowserClient::~TestExtensionsBrowserClient() {}
+
+void TestExtensionsBrowserClient::SetUpdateClientFactory(
+ const base::Callback<update_client::UpdateClient*(void)>& factory) {
+ update_client_factory_ = factory;
+}
+
+void TestExtensionsBrowserClient::SetIncognitoContext(BrowserContext* context) {
+ // If a context is provided it must be off-the-record.
+ DCHECK(!context || context->IsOffTheRecord());
+ incognito_context_ = context;
+}
+
+bool TestExtensionsBrowserClient::IsShuttingDown() { return false; }
+
+bool TestExtensionsBrowserClient::AreExtensionsDisabled(
+ const base::CommandLine& command_line,
+ BrowserContext* context) {
+ return false;
+}
+
+bool TestExtensionsBrowserClient::IsValidContext(BrowserContext* context) {
+ return context == main_context_ ||
+ (incognito_context_ && context == incognito_context_);
+}
+
+bool TestExtensionsBrowserClient::IsSameContext(BrowserContext* first,
+ BrowserContext* second) {
+ DCHECK(first);
+ DCHECK(second);
+ return first == second ||
+ (first == main_context_ && second == incognito_context_) ||
+ (first == incognito_context_ && second == main_context_);
+}
+
+bool TestExtensionsBrowserClient::HasOffTheRecordContext(
+ BrowserContext* context) {
+ return context == main_context_ && incognito_context_ != NULL;
+}
+
+BrowserContext* TestExtensionsBrowserClient::GetOffTheRecordContext(
+ BrowserContext* context) {
+ if (context == main_context_)
+ return incognito_context_;
+ return NULL;
+}
+
+BrowserContext* TestExtensionsBrowserClient::GetOriginalContext(
+ BrowserContext* context) {
+ return main_context_;
+}
+
+#if defined(OS_CHROMEOS)
+std::string TestExtensionsBrowserClient::GetUserIdHashFromContext(
+ content::BrowserContext* context) {
+ return "";
+}
+#endif
+
+bool TestExtensionsBrowserClient::IsGuestSession(
+ BrowserContext* context) const {
+ return false;
+}
+
+bool TestExtensionsBrowserClient::IsExtensionIncognitoEnabled(
+ const std::string& extension_id,
+ content::BrowserContext* context) const {
+ return false;
+}
+
+bool TestExtensionsBrowserClient::CanExtensionCrossIncognito(
+ const extensions::Extension* extension,
+ content::BrowserContext* context) const {
+ return false;
+}
+
+net::URLRequestJob*
+TestExtensionsBrowserClient::MaybeCreateResourceBundleRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const base::FilePath& directory_path,
+ const std::string& content_security_policy,
+ bool send_cors_header) {
+ return NULL;
+}
+
+bool TestExtensionsBrowserClient::AllowCrossRendererResourceLoad(
+ net::URLRequest* request,
+ bool is_incognito,
+ const Extension* extension,
+ InfoMap* extension_info_map) {
+ return false;
+}
+
+PrefService* TestExtensionsBrowserClient::GetPrefServiceForContext(
+ BrowserContext* context) {
+ return NULL;
+}
+
+void TestExtensionsBrowserClient::GetEarlyExtensionPrefsObservers(
+ content::BrowserContext* context,
+ std::vector<ExtensionPrefsObserver*>* observers) const {}
+
+ProcessManagerDelegate* TestExtensionsBrowserClient::GetProcessManagerDelegate()
+ const {
+ return process_manager_delegate_;
+}
+
+scoped_ptr<ExtensionHostDelegate>
+TestExtensionsBrowserClient::CreateExtensionHostDelegate() {
+ return scoped_ptr<ExtensionHostDelegate>();
+}
+
+bool TestExtensionsBrowserClient::DidVersionUpdate(BrowserContext* context) {
+ return false;
+}
+
+void TestExtensionsBrowserClient::PermitExternalProtocolHandler() {
+}
+
+bool TestExtensionsBrowserClient::IsRunningInForcedAppMode() { return false; }
+
+bool TestExtensionsBrowserClient::IsLoggedInAsPublicAccount() {
+ return false;
+}
+
+ApiActivityMonitor* TestExtensionsBrowserClient::GetApiActivityMonitor(
+ BrowserContext* context) {
+ return NULL;
+}
+
+ExtensionSystemProvider*
+TestExtensionsBrowserClient::GetExtensionSystemFactory() {
+ DCHECK(extension_system_factory_);
+ return extension_system_factory_;
+}
+
+void TestExtensionsBrowserClient::RegisterExtensionFunctions(
+ ExtensionFunctionRegistry* registry) const {}
+
+void TestExtensionsBrowserClient::RegisterMojoServices(
+ content::RenderFrameHost* render_frame_host,
+ const Extension* extension) const {
+}
+
+scoped_ptr<RuntimeAPIDelegate>
+TestExtensionsBrowserClient::CreateRuntimeAPIDelegate(
+ content::BrowserContext* context) const {
+ return scoped_ptr<RuntimeAPIDelegate>(new TestRuntimeAPIDelegate());
+}
+
+const ComponentExtensionResourceManager*
+TestExtensionsBrowserClient::GetComponentExtensionResourceManager() {
+ return NULL;
+}
+
+void TestExtensionsBrowserClient::BroadcastEventToRenderers(
+ events::HistogramValue histogram_value,
+ const std::string& event_name,
+ scoped_ptr<base::ListValue> args) {}
+
+net::NetLog* TestExtensionsBrowserClient::GetNetLog() {
+ return NULL;
+}
+
+ExtensionCache* TestExtensionsBrowserClient::GetExtensionCache() {
+ return extension_cache_.get();
+}
+
+bool TestExtensionsBrowserClient::IsBackgroundUpdateAllowed() {
+ return true;
+}
+
+bool TestExtensionsBrowserClient::IsMinBrowserVersionSupported(
+ const std::string& min_version) {
+ return true;
+}
+
+ExtensionWebContentsObserver*
+TestExtensionsBrowserClient::GetExtensionWebContentsObserver(
+ content::WebContents* web_contents) {
+ return nullptr;
+}
+
+scoped_refptr<update_client::UpdateClient>
+TestExtensionsBrowserClient::CreateUpdateClient(
+ content::BrowserContext* context) {
+ return update_client_factory_.is_null()
+ ? nullptr
+ : make_scoped_refptr(update_client_factory_.Run());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/test_extensions_browser_client.h b/chromium/extensions/browser/test_extensions_browser_client.h
new file mode 100644
index 00000000000..2b0dafb0b7d
--- /dev/null
+++ b/chromium/extensions/browser/test_extensions_browser_client.h
@@ -0,0 +1,135 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_TEST_EXTENSIONS_BROWSER_CLIENT_H_
+#define EXTENSIONS_BROWSER_TEST_EXTENSIONS_BROWSER_CLIENT_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "build/build_config.h"
+#include "components/update_client/update_client.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/updater/extension_cache.h"
+
+namespace extensions {
+
+// A simplified ExtensionsBrowserClient for a single normal browser context and
+// an optional incognito browser context associated with it. A test that uses
+// this class should call ExtensionsBrowserClient::Set() with its instance.
+class TestExtensionsBrowserClient : public ExtensionsBrowserClient {
+ public:
+ // |main_context| is required and must not be an incognito context.
+ explicit TestExtensionsBrowserClient(content::BrowserContext* main_context);
+ ~TestExtensionsBrowserClient() override;
+
+ void set_process_manager_delegate(ProcessManagerDelegate* delegate) {
+ process_manager_delegate_ = delegate;
+ }
+ void set_extension_system_factory(ExtensionSystemProvider* factory) {
+ extension_system_factory_ = factory;
+ }
+ void set_extension_cache(scoped_ptr<ExtensionCache> extension_cache) {
+ extension_cache_ = std::move(extension_cache);
+ }
+
+ // Sets a factory to respond to calls of the CreateUpdateClient method.
+ void SetUpdateClientFactory(
+ const base::Callback<update_client::UpdateClient*(void)>& factory);
+
+ // Associates an incognito context with |main_context_|.
+ void SetIncognitoContext(content::BrowserContext* incognito_context);
+
+ // ExtensionsBrowserClient overrides:
+ bool IsShuttingDown() override;
+ bool AreExtensionsDisabled(const base::CommandLine& command_line,
+ content::BrowserContext* context) override;
+ bool IsValidContext(content::BrowserContext* context) override;
+ bool IsSameContext(content::BrowserContext* first,
+ content::BrowserContext* second) override;
+ bool HasOffTheRecordContext(content::BrowserContext* context) override;
+ content::BrowserContext* GetOffTheRecordContext(
+ content::BrowserContext* context) override;
+ content::BrowserContext* GetOriginalContext(
+ content::BrowserContext* context) override;
+#if defined(OS_CHROMEOS)
+ std::string GetUserIdHashFromContext(
+ content::BrowserContext* context) override;
+#endif
+ bool IsGuestSession(content::BrowserContext* context) const override;
+ bool IsExtensionIncognitoEnabled(
+ const std::string& extension_id,
+ content::BrowserContext* context) const override;
+ bool CanExtensionCrossIncognito(
+ const extensions::Extension* extension,
+ content::BrowserContext* context) const override;
+ net::URLRequestJob* MaybeCreateResourceBundleRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const base::FilePath& directory_path,
+ const std::string& content_security_policy,
+ bool send_cors_header) override;
+ bool AllowCrossRendererResourceLoad(net::URLRequest* request,
+ bool is_incognito,
+ const Extension* extension,
+ InfoMap* extension_info_map) override;
+ PrefService* GetPrefServiceForContext(
+ content::BrowserContext* context) override;
+ void GetEarlyExtensionPrefsObservers(
+ content::BrowserContext* context,
+ std::vector<ExtensionPrefsObserver*>* observers) const override;
+ ProcessManagerDelegate* GetProcessManagerDelegate() const override;
+ scoped_ptr<ExtensionHostDelegate> CreateExtensionHostDelegate() override;
+ bool DidVersionUpdate(content::BrowserContext* context) override;
+ void PermitExternalProtocolHandler() override;
+ bool IsRunningInForcedAppMode() override;
+ bool IsLoggedInAsPublicAccount() override;
+ ApiActivityMonitor* GetApiActivityMonitor(
+ content::BrowserContext* context) override;
+ ExtensionSystemProvider* GetExtensionSystemFactory() override;
+ void RegisterExtensionFunctions(
+ ExtensionFunctionRegistry* registry) const override;
+ void RegisterMojoServices(content::RenderFrameHost* render_frame_host,
+ const Extension* extension) const override;
+ scoped_ptr<RuntimeAPIDelegate> CreateRuntimeAPIDelegate(
+ content::BrowserContext* context) const override;
+ const ComponentExtensionResourceManager*
+ GetComponentExtensionResourceManager() override;
+ void BroadcastEventToRenderers(events::HistogramValue histogram_value,
+ const std::string& event_name,
+ scoped_ptr<base::ListValue> args) override;
+ net::NetLog* GetNetLog() override;
+ ExtensionCache* GetExtensionCache() override;
+ bool IsBackgroundUpdateAllowed() override;
+ bool IsMinBrowserVersionSupported(const std::string& min_version) override;
+ ExtensionWebContentsObserver* GetExtensionWebContentsObserver(
+ content::WebContents* web_contents) override;
+ scoped_refptr<update_client::UpdateClient> CreateUpdateClient(
+ content::BrowserContext* context) override;
+
+ private:
+ content::BrowserContext* main_context_; // Not owned.
+ content::BrowserContext* incognito_context_; // Not owned, defaults to NULL.
+
+ // Not owned, defaults to NULL.
+ ProcessManagerDelegate* process_manager_delegate_;
+
+ // Not owned, defaults to NULL.
+ ExtensionSystemProvider* extension_system_factory_;
+
+ scoped_ptr<ExtensionCache> extension_cache_;
+
+ base::Callback<update_client::UpdateClient*(void)> update_client_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestExtensionsBrowserClient);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_TEST_EXTENSIONS_BROWSER_CLIENT_H_
diff --git a/chromium/extensions/browser/test_image_loader.cc b/chromium/extensions/browser/test_image_loader.cc
new file mode 100644
index 00000000000..af6ce25cfb1
--- /dev/null
+++ b/chromium/extensions/browser/test_image_loader.cc
@@ -0,0 +1,58 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/test_image_loader.h"
+
+#include "base/bind.h"
+#include "extensions/browser/image_loader.h"
+#include "extensions/common/extension.h"
+
+namespace extensions {
+
+TestImageLoader::TestImageLoader() : waiting_(false), image_loaded_(false) {}
+
+TestImageLoader::~TestImageLoader() {}
+
+// static
+SkBitmap TestImageLoader::LoadAndGetExtensionBitmap(
+ const Extension* extension,
+ const std::string& image_path,
+ int size) {
+ TestImageLoader image_loader;
+ return image_loader.LoadAndGetBitmap(extension, image_path, size);
+}
+
+void TestImageLoader::OnImageLoaded(const gfx::Image& image) {
+ image_ = image;
+ image_loaded_ = true;
+ if (waiting_)
+ loader_message_loop_quit_.Run();
+}
+
+SkBitmap TestImageLoader::LoadAndGetBitmap(const Extension* extension,
+ const std::string& path,
+ int size) {
+ image_loaded_ = false;
+
+ ImageLoader image_loader;
+ image_loader.LoadImageAsync(
+ extension, extension->GetResource(path), gfx::Size(size, size),
+ base::Bind(&TestImageLoader::OnImageLoaded, base::Unretained(this)));
+
+ // If |image_| still hasn't been loaded (i.e. it is being loaded
+ // asynchronously), wait for it.
+ if (!image_loaded_) {
+ waiting_ = true;
+ base::RunLoop run_loop;
+ loader_message_loop_quit_ = run_loop.QuitClosure();
+ run_loop.Run();
+ waiting_ = false;
+ }
+
+ DCHECK(image_loaded_);
+
+ return image_.IsEmpty() ? SkBitmap() : *image_.ToSkBitmap();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/test_image_loader.h b/chromium/extensions/browser/test_image_loader.h
new file mode 100644
index 00000000000..c1fb4425c13
--- /dev/null
+++ b/chromium/extensions/browser/test_image_loader.h
@@ -0,0 +1,45 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_TEST_IMAGE_LOADER_H_
+#define EXTENSIONS_BROWSER_TEST_IMAGE_LOADER_H_
+
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "ui/gfx/image/image.h"
+
+namespace extensions {
+
+class Extension;
+
+// Helper class for synchronously loading an extension image resource.
+class TestImageLoader {
+ public:
+ TestImageLoader();
+ ~TestImageLoader();
+
+ // Loads an image to be used in test from |extension|.
+ // The image will be loaded from the relative path |image_path|.
+ static SkBitmap LoadAndGetExtensionBitmap(const Extension* extension,
+ const std::string& image_path,
+ int size);
+
+ private:
+ void OnImageLoaded(const gfx::Image& image);
+
+ SkBitmap LoadAndGetBitmap(const Extension* extension,
+ const std::string& path,
+ int size);
+
+ gfx::Image image_;
+ base::Closure loader_message_loop_quit_;
+ bool waiting_;
+ bool image_loaded_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestImageLoader);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_TEST_IMAGE_LOADER_H_
diff --git a/chromium/extensions/browser/test_management_policy.cc b/chromium/extensions/browser/test_management_policy.cc
new file mode 100644
index 00000000000..839fde6dc99
--- /dev/null
+++ b/chromium/extensions/browser/test_management_policy.cc
@@ -0,0 +1,88 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/test_management_policy.h"
+
+#include "base/strings/utf_string_conversions.h"
+
+namespace extensions {
+
+TestManagementPolicyProvider::TestManagementPolicyProvider()
+ : may_load_(true),
+ may_modify_status_(true),
+ must_remain_enabled_(false),
+ must_remain_disabled_(false),
+ must_remain_installed_(false),
+ disable_reason_(Extension::DISABLE_NONE) {
+ error_message_ = base::UTF8ToUTF16(expected_error());
+}
+
+TestManagementPolicyProvider::TestManagementPolicyProvider(
+ int prohibited_actions) {
+ SetProhibitedActions(prohibited_actions);
+ error_message_ = base::UTF8ToUTF16(expected_error());
+}
+
+void TestManagementPolicyProvider::SetProhibitedActions(
+ int prohibited_actions) {
+ may_load_ = (prohibited_actions & PROHIBIT_LOAD) == 0;
+ may_modify_status_ = (prohibited_actions & PROHIBIT_MODIFY_STATUS) == 0;
+ must_remain_enabled_ = (prohibited_actions & MUST_REMAIN_ENABLED) != 0;
+ must_remain_disabled_ = (prohibited_actions & MUST_REMAIN_DISABLED) != 0;
+ must_remain_installed_ = (prohibited_actions & MUST_REMAIN_INSTALLED) != 0;
+}
+
+void TestManagementPolicyProvider::SetDisableReason(
+ Extension::DisableReason reason) {
+ disable_reason_ = reason;
+}
+
+std::string TestManagementPolicyProvider::GetDebugPolicyProviderName() const {
+ return "the test management policy provider";
+}
+
+bool TestManagementPolicyProvider::UserMayLoad(const Extension* extension,
+ base::string16* error) const {
+ if (error && !may_load_)
+ *error = error_message_;
+ return may_load_;
+}
+
+bool TestManagementPolicyProvider::UserMayModifySettings(
+ const Extension* extension, base::string16* error) const {
+ if (error && !may_modify_status_)
+ *error = error_message_;
+ return may_modify_status_;
+}
+
+bool TestManagementPolicyProvider::MustRemainEnabled(const Extension* extension,
+ base::string16* error)
+ const {
+ if (error && must_remain_enabled_)
+ *error = error_message_;
+ return must_remain_enabled_;
+}
+
+bool TestManagementPolicyProvider::MustRemainDisabled(
+ const Extension* extension,
+ Extension::DisableReason* reason,
+ base::string16* error) const {
+ if (must_remain_disabled_) {
+ if (error)
+ *error = error_message_;
+ if (reason)
+ *reason = disable_reason_;
+ }
+ return must_remain_disabled_;
+}
+
+bool TestManagementPolicyProvider::MustRemainInstalled(
+ const Extension* extension,
+ base::string16* error) const {
+ if (error && must_remain_installed_)
+ *error = error_message_;
+ return must_remain_installed_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/test_management_policy.h b/chromium/extensions/browser/test_management_policy.h
new file mode 100644
index 00000000000..cf91143cdea
--- /dev/null
+++ b/chromium/extensions/browser/test_management_policy.h
@@ -0,0 +1,69 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_TEST_MANAGEMENT_POLICY_H_
+#define EXTENSIONS_BROWSER_TEST_MANAGEMENT_POLICY_H_
+
+#include <string>
+
+#include "base/strings/string16.h"
+#include "extensions/browser/management_policy.h"
+
+namespace extensions {
+
+// This class provides a simple way to create providers with specific
+// restrictions and a known error message, for use in testing.
+class TestManagementPolicyProvider : public ManagementPolicy::Provider {
+ public:
+ enum AllowedActionFlag {
+ ALLOW_ALL = 0,
+ PROHIBIT_LOAD = 1 << 0,
+ PROHIBIT_MODIFY_STATUS = 1 << 1,
+ MUST_REMAIN_ENABLED = 1 << 2,
+ MUST_REMAIN_DISABLED = 1 << 3,
+ MUST_REMAIN_INSTALLED = 1 << 4,
+ };
+
+ static std::string expected_error() {
+ return "Action prohibited by test provider.";
+ }
+
+ TestManagementPolicyProvider();
+ explicit TestManagementPolicyProvider(int prohibited_actions);
+
+ void SetProhibitedActions(int prohibited_actions);
+ void SetDisableReason(Extension::DisableReason reason);
+
+ std::string GetDebugPolicyProviderName() const override;
+
+ bool UserMayLoad(const Extension* extension,
+ base::string16* error) const override;
+
+ bool UserMayModifySettings(const Extension* extension,
+ base::string16* error) const override;
+
+ bool MustRemainEnabled(const Extension* extension,
+ base::string16* error) const override;
+
+ bool MustRemainDisabled(const Extension* extension,
+ Extension::DisableReason* reason,
+ base::string16* error) const override;
+
+ bool MustRemainInstalled(const Extension* extension,
+ base::string16* error) const override;
+
+ private:
+ bool may_load_;
+ bool may_modify_status_;
+ bool must_remain_enabled_;
+ bool must_remain_disabled_;
+ bool must_remain_installed_;
+ Extension::DisableReason disable_reason_;
+
+ base::string16 error_message_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_TEST_MANAGEMENT_POLICY_H_
diff --git a/chromium/extensions/browser/test_runtime_api_delegate.cc b/chromium/extensions/browser/test_runtime_api_delegate.cc
new file mode 100644
index 00000000000..d9624b5839d
--- /dev/null
+++ b/chromium/extensions/browser/test_runtime_api_delegate.cc
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/test_runtime_api_delegate.h"
+
+#include "extensions/common/api/runtime.h"
+
+namespace extensions {
+
+using api::runtime::PlatformInfo;
+
+TestRuntimeAPIDelegate::TestRuntimeAPIDelegate() {
+}
+
+TestRuntimeAPIDelegate::~TestRuntimeAPIDelegate() {
+}
+
+void TestRuntimeAPIDelegate::AddUpdateObserver(UpdateObserver* observer) {
+}
+
+void TestRuntimeAPIDelegate::RemoveUpdateObserver(UpdateObserver* observer) {
+}
+
+base::Version TestRuntimeAPIDelegate::GetPreviousExtensionVersion(
+ const Extension* extension) {
+ return base::Version();
+}
+
+void TestRuntimeAPIDelegate::ReloadExtension(const std::string& extension_id) {
+}
+
+bool TestRuntimeAPIDelegate::CheckForUpdates(
+ const std::string& extension_id,
+ const UpdateCheckCallback& callback) {
+ return false;
+}
+
+void TestRuntimeAPIDelegate::OpenURL(const GURL& uninstall_url) {
+}
+
+bool TestRuntimeAPIDelegate::GetPlatformInfo(PlatformInfo* info) {
+ // TODO(rockot): This probably isn't right. Maybe this delegate should just
+ // support manual PlatformInfo override for tests if necessary.
+ info->os = api::runtime::PLATFORM_OS_CROS;
+ return true;
+}
+
+bool TestRuntimeAPIDelegate::RestartDevice(std::string* error_message) {
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/test_runtime_api_delegate.h b/chromium/extensions/browser/test_runtime_api_delegate.h
new file mode 100644
index 00000000000..76031e75cb6
--- /dev/null
+++ b/chromium/extensions/browser/test_runtime_api_delegate.h
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_TEST_RUNTIME_API_DELEGATE_H_
+#define EXTENSIONS_BROWSER_TEST_RUNTIME_API_DELEGATE_H_
+
+#include "base/macros.h"
+#include "extensions/browser/api/runtime/runtime_api_delegate.h"
+
+namespace extensions {
+
+class TestRuntimeAPIDelegate : public RuntimeAPIDelegate {
+ public:
+ TestRuntimeAPIDelegate();
+ ~TestRuntimeAPIDelegate() override;
+
+ // RuntimeAPIDelegate implementation.
+ void AddUpdateObserver(UpdateObserver* observer) override;
+ void RemoveUpdateObserver(UpdateObserver* observer) override;
+ base::Version GetPreviousExtensionVersion(
+ const Extension* extension) override;
+ void ReloadExtension(const std::string& extension_id) override;
+ bool CheckForUpdates(const std::string& extension_id,
+ const UpdateCheckCallback& callback) override;
+ void OpenURL(const GURL& uninstall_url) override;
+ bool GetPlatformInfo(api::runtime::PlatformInfo* info) override;
+ bool RestartDevice(std::string* error_message) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestRuntimeAPIDelegate);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_TEST_RUNTIME_API_DELEGATE_H_
diff --git a/chromium/extensions/browser/uninstall_ping_sender.cc b/chromium/extensions/browser/uninstall_ping_sender.cc
new file mode 100644
index 00000000000..003cf137b85
--- /dev/null
+++ b/chromium/extensions/browser/uninstall_ping_sender.cc
@@ -0,0 +1,33 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/uninstall_ping_sender.h"
+
+#include "base/version.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/updater/update_service.h"
+
+namespace extensions {
+
+UninstallPingSender::UninstallPingSender(ExtensionRegistry* registry,
+ const Filter& filter)
+ : filter_(filter), observer_(this) {
+ observer_.Add(registry);
+}
+
+UninstallPingSender::~UninstallPingSender() {}
+
+void UninstallPingSender::OnExtensionUninstalled(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UninstallReason reason) {
+ if (filter_.Run(extension, reason) == SEND_PING) {
+ UpdateService* updater = UpdateService::Get(browser_context);
+ base::Version version =
+ extension->version() ? *extension->version() : base::Version("0");
+ updater->SendUninstallPing(extension->id(), version, reason);
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/uninstall_ping_sender.h b/chromium/extensions/browser/uninstall_ping_sender.h
new file mode 100644
index 00000000000..a349d5c0e8c
--- /dev/null
+++ b/chromium/extensions/browser/uninstall_ping_sender.h
@@ -0,0 +1,51 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_UNINSTALL_PING_SENDER_H_
+#define EXTENSIONS_BROWSER_UNINSTALL_PING_SENDER_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/scoped_observer.h"
+#include "extensions/browser/extension_registry_observer.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+// A class that watches the ExtensionRegistry for uninstall events, and
+// uses the UpdateService to send uninstall pings.
+class UninstallPingSender : public ExtensionRegistryObserver {
+ public:
+ enum FilterResult { SEND_PING, DO_NOT_SEND_PING };
+
+ // A callback function that will be called each time an extension is
+ // uninstalled, with the result used to determine if a ping should be
+ // sent or not.
+ using Filter = base::Callback<FilterResult(const Extension* extension,
+ UninstallReason reason)>;
+
+ UninstallPingSender(ExtensionRegistry* registry, const Filter& filter);
+ ~UninstallPingSender() override;
+
+ protected:
+ // ExtensionRegistryObserver:
+ void OnExtensionUninstalled(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UninstallReason reason) override;
+
+ // Callback for determining whether to send uninstall pings.
+ Filter filter_;
+
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver> observer_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UninstallPingSender);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_UNINSTALL_PING_SENDER_H_
diff --git a/chromium/extensions/browser/uninstall_reason.h b/chromium/extensions/browser/uninstall_reason.h
new file mode 100644
index 00000000000..8484aa9a107
--- /dev/null
+++ b/chromium/extensions/browser/uninstall_reason.h
@@ -0,0 +1,50 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_UNINSTALL_REASON_H_
+#define EXTENSIONS_BROWSER_UNINSTALL_REASON_H_
+
+namespace extensions {
+
+// Do not remove/reorder these, as they are used in uninstall ping data and we
+// depend on their values being stable.
+enum UninstallReason {
+ UNINSTALL_REASON_FOR_TESTING, // Used for testing code only
+ UNINSTALL_REASON_USER_INITIATED, // User performed some UI gesture
+ UNINSTALL_REASON_EXTENSION_DISABLED, // Extension disabled due to error
+ UNINSTALL_REASON_STORAGE_THRESHOLD_EXCEEDED,
+ UNINSTALL_REASON_INSTALL_CANCELED,
+ UNINSTALL_REASON_MANAGEMENT_API,
+ UNINSTALL_REASON_SYNC,
+ UNINSTALL_REASON_ORPHANED_THEME,
+ UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION,
+ // The entries below imply bypassing checking user has permission to
+ // uninstall the corresponding extension id.
+ UNINSTALL_REASON_ORPHANED_EXTERNAL_EXTENSION,
+ UNINSTALL_REASON_ORPHANED_SHARED_MODULE,
+ UNINSTALL_REASON_INTERNAL_MANAGEMENT, // Internal extensions (see usages)
+ UNINSTALL_REASON_REINSTALL,
+ UNINSTALL_REASON_COMPONENT_REMOVED,
+
+ UNINSTALL_REASON_MAX, // Should always be the last value
+};
+
+// The source of an uninstall. Do *NOT* adjust the order of these, as they are
+// used in UMA.
+enum UninstallSource {
+ UNINSTALL_SOURCE_FOR_TESTING,
+ UNINSTALL_SOURCE_TOOLBAR_CONTEXT_MENU,
+ UNINSTALL_SOURCE_PERMISSIONS_INCREASE,
+ UNINSTALL_SOURCE_STORAGE_THRESHOLD_EXCEEDED,
+ UNINSTALL_SOURCE_APP_LIST,
+ UNINSTALL_SOURCE_APP_INFO_DIALOG,
+ UNINSTALL_SOURCE_CHROME_APPS_PAGE,
+ UNINSTALL_SOURCE_CHROME_EXTENSIONS_PAGE,
+ UNINSTALL_SOURCE_EXTENSION,
+ NUM_UNINSTALL_SOURCES,
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_UNINSTALL_REASON_H_
diff --git a/chromium/extensions/browser/update_observer.h b/chromium/extensions/browser/update_observer.h
new file mode 100644
index 00000000000..68c6e22368e
--- /dev/null
+++ b/chromium/extensions/browser/update_observer.h
@@ -0,0 +1,27 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_UPDATE_OBSERVER_H_
+#define EXTENSIONS_BROWSER_UPDATE_OBSERVER_H_
+
+#include <string>
+
+namespace extensions {
+class Extension;
+
+class UpdateObserver {
+ public:
+ // Invoked when an app update is available.
+ virtual void OnAppUpdateAvailable(const Extension* extension) = 0;
+
+ // Invoked when Chrome update is available.
+ virtual void OnChromeUpdateAvailable() = 0;
+
+ protected:
+ virtual ~UpdateObserver() {}
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_UPDATE_OBSERVER_H_
diff --git a/chromium/extensions/browser/updater/extension_cache.h b/chromium/extensions/browser/updater/extension_cache.h
new file mode 100644
index 00000000000..e5a2a3f296c
--- /dev/null
+++ b/chromium/extensions/browser/updater/extension_cache.h
@@ -0,0 +1,66 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_UPDATER_EXTENSION_CACHE_H_
+#define EXTENSIONS_BROWSER_UPDATER_EXTENSION_CACHE_H_
+
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+
+namespace extensions {
+
+// ExtensionCache interface that caches extensions .crx files to share them
+// between multiple users and profiles on the machine.
+class ExtensionCache {
+ public:
+ // Callback that is invoked when the file placed when PutExtension done.
+ typedef base::Callback<void(const base::FilePath& file_path,
+ bool file_ownership_passed)> PutExtensionCallback;
+
+ ExtensionCache() {}
+ virtual ~ExtensionCache() {}
+
+ // Initialize cache in background. The |callback| is called when cache ready.
+ // Can be called multiple times. The |callback| can be called immediately if
+ // cache is ready.
+ virtual void Start(const base::Closure& callback) = 0;
+
+ // Shut down the cache. Must be called at most once on browser shutdown.
+ virtual void Shutdown(const base::Closure& callback) = 0;
+
+ // Allow caching for the extension with given |id|. User specific extensions
+ // should not be cached for privacy reasons. But default apps including policy
+ // configured can be cached. Can be called before Init.
+ virtual void AllowCaching(const std::string& id) = 0;
+
+ // If extension with |id| exists in the cache, returns |true|, |file_path| and
+ // |version| for the extension. Extension will be marked as used with current
+ // timestamp.
+ virtual bool GetExtension(const std::string& id,
+ const std::string& expected_hash,
+ base::FilePath* file_path,
+ std::string* version) = 0;
+
+ // Put extension with |id| and |version| into local cache. Older version in
+ // the cache will removed be on next run so it can be safely used. Extension
+ // will be marked as used with current timestamp. The file will be available
+ // via GetExtension when |callback| is called. Original |file_path| won't be
+ // deleted from the disk. There is no guarantee that |callback| will be
+ // called.
+ virtual void PutExtension(const std::string& id,
+ const std::string& expected_hash,
+ const base::FilePath& file_path,
+ const std::string& version,
+ const PutExtensionCallback& callback) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ExtensionCache);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_UPDATER_EXTENSION_CACHE_H_
diff --git a/chromium/extensions/browser/updater/extension_downloader.cc b/chromium/extensions/browser/updater/extension_downloader.cc
new file mode 100644
index 00000000000..116ef7abb15
--- /dev/null
+++ b/chromium/extensions/browser/updater/extension_downloader.cc
@@ -0,0 +1,947 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/updater/extension_downloader.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "base/version.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_service.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/notification_types.h"
+#include "extensions/browser/updater/extension_cache.h"
+#include "extensions/browser/updater/request_queue_impl.h"
+#include "extensions/browser/updater/safe_manifest_parser.h"
+#include "extensions/common/extension_urls.h"
+#include "extensions/common/manifest_url_handlers.h"
+#include "google_apis/gaia/identity_provider.h"
+#include "net/base/backoff_entry.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_status.h"
+
+using base::Time;
+using base::TimeDelta;
+using content::BrowserThread;
+
+namespace extensions {
+
+const char ExtensionDownloader::kBlacklistAppID[] = "com.google.crx.blacklist";
+
+namespace {
+
+const net::BackoffEntry::Policy kDefaultBackoffPolicy = {
+ // Number of initial errors (in sequence) to ignore before applying
+ // exponential back-off rules.
+ 0,
+
+ // Initial delay for exponential back-off in ms.
+ 2000,
+
+ // Factor by which the waiting time will be multiplied.
+ 2,
+
+ // Fuzzing percentage. ex: 10% will spread requests randomly
+ // between 90%-100% of the calculated time.
+ 0.1,
+
+ // Maximum amount of time we are willing to delay our request in ms.
+ -1,
+
+ // Time to keep an entry from being discarded even when it
+ // has no significant state, -1 to never discard.
+ -1,
+
+ // Don't use initial delay unless the last request was an error.
+ false,
+};
+
+const char kAuthUserQueryKey[] = "authuser";
+
+const int kMaxAuthUserValue = 10;
+const int kMaxOAuth2Attempts = 3;
+
+const char kNotFromWebstoreInstallSource[] = "notfromwebstore";
+const char kDefaultInstallSource[] = "";
+
+const char kGoogleDotCom[] = "google.com";
+const char kTokenServiceConsumerId[] = "extension_downloader";
+const char kWebstoreOAuth2Scope[] =
+ "https://www.googleapis.com/auth/chromewebstore.readonly";
+
+#define RETRY_HISTOGRAM(name, retry_count, url) \
+ if ((url).DomainIs(kGoogleDotCom)) { \
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions." name "RetryCountGoogleUrl", \
+ retry_count, \
+ 1, \
+ kMaxRetries, \
+ kMaxRetries + 1); \
+ } else { \
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions." name "RetryCountOtherUrl", \
+ retry_count, \
+ 1, \
+ kMaxRetries, \
+ kMaxRetries + 1); \
+ }
+
+bool ShouldRetryRequest(const net::URLRequestStatus& status,
+ int response_code) {
+ // Retry if the response code is a server error, or the request failed because
+ // of network errors as opposed to file errors.
+ return ((response_code >= 500 && status.is_success()) ||
+ status.status() == net::URLRequestStatus::FAILED);
+}
+
+// This parses and updates a URL query such that the value of the |authuser|
+// query parameter is incremented by 1. If parameter was not present in the URL,
+// it will be added with a value of 1. All other query keys and values are
+// preserved as-is. Returns |false| if the user index exceeds a hard-coded
+// maximum.
+bool IncrementAuthUserIndex(GURL* url) {
+ int user_index = 0;
+ std::string old_query = url->query();
+ std::vector<std::string> new_query_parts;
+ url::Component query(0, old_query.length());
+ url::Component key, value;
+ while (url::ExtractQueryKeyValue(old_query.c_str(), &query, &key, &value)) {
+ std::string key_string = old_query.substr(key.begin, key.len);
+ std::string value_string = old_query.substr(value.begin, value.len);
+ if (key_string == kAuthUserQueryKey) {
+ base::StringToInt(value_string, &user_index);
+ } else {
+ new_query_parts.push_back(base::StringPrintf(
+ "%s=%s", key_string.c_str(), value_string.c_str()));
+ }
+ }
+ if (user_index >= kMaxAuthUserValue)
+ return false;
+ new_query_parts.push_back(
+ base::StringPrintf("%s=%d", kAuthUserQueryKey, user_index + 1));
+ std::string new_query_string = base::JoinString(new_query_parts, "&");
+ url::Component new_query(0, new_query_string.size());
+ url::Replacements<char> replacements;
+ replacements.SetQuery(new_query_string.c_str(), new_query);
+ *url = url->ReplaceComponents(replacements);
+ return true;
+}
+
+} // namespace
+
+UpdateDetails::UpdateDetails(const std::string& id, const Version& version)
+ : id(id), version(version) {
+}
+
+UpdateDetails::~UpdateDetails() {
+}
+
+ExtensionDownloader::ExtensionFetch::ExtensionFetch()
+ : url(), credentials(CREDENTIALS_NONE) {
+}
+
+ExtensionDownloader::ExtensionFetch::ExtensionFetch(
+ const std::string& id,
+ const GURL& url,
+ const std::string& package_hash,
+ const std::string& version,
+ const std::set<int>& request_ids)
+ : id(id),
+ url(url),
+ package_hash(package_hash),
+ version(version),
+ request_ids(request_ids),
+ credentials(CREDENTIALS_NONE),
+ oauth2_attempt_count(0) {
+}
+
+ExtensionDownloader::ExtensionFetch::~ExtensionFetch() {
+}
+
+ExtensionDownloader::ExtensionDownloader(
+ ExtensionDownloaderDelegate* delegate,
+ net::URLRequestContextGetter* request_context)
+ : OAuth2TokenService::Consumer(kTokenServiceConsumerId),
+ delegate_(delegate),
+ request_context_(request_context),
+ manifests_queue_(&kDefaultBackoffPolicy,
+ base::Bind(&ExtensionDownloader::CreateManifestFetcher,
+ base::Unretained(this))),
+ extensions_queue_(&kDefaultBackoffPolicy,
+ base::Bind(&ExtensionDownloader::CreateExtensionFetcher,
+ base::Unretained(this))),
+ extension_cache_(NULL),
+ enable_extra_update_metrics_(false),
+ weak_ptr_factory_(this) {
+ DCHECK(delegate_);
+ DCHECK(request_context_.get());
+}
+
+ExtensionDownloader::~ExtensionDownloader() {
+}
+
+bool ExtensionDownloader::AddExtension(const Extension& extension,
+ int request_id) {
+ // Skip extensions with empty update URLs converted from user
+ // scripts.
+ if (extension.converted_from_user_script() &&
+ ManifestURL::GetUpdateURL(&extension).is_empty()) {
+ return false;
+ }
+
+ // If the extension updates itself from the gallery, ignore any update URL
+ // data. At the moment there is no extra data that an extension can
+ // communicate to the the gallery update servers.
+ std::string update_url_data;
+ if (!ManifestURL::UpdatesFromGallery(&extension))
+ update_url_data = delegate_->GetUpdateUrlData(extension.id());
+
+ return AddExtensionData(
+ extension.id(), *extension.version(), extension.GetType(),
+ ManifestURL::GetUpdateURL(&extension), update_url_data, request_id);
+}
+
+bool ExtensionDownloader::AddPendingExtension(const std::string& id,
+ const GURL& update_url,
+ int request_id) {
+ // Use a zero version to ensure that a pending extension will always
+ // be updated, and thus installed (assuming all extensions have
+ // non-zero versions).
+ Version version("0.0.0.0");
+ DCHECK(version.IsValid());
+
+ return AddExtensionData(id, version, Manifest::TYPE_UNKNOWN, update_url,
+ std::string(), request_id);
+}
+
+void ExtensionDownloader::StartAllPending(ExtensionCache* cache) {
+ if (cache) {
+ extension_cache_ = cache;
+ extension_cache_->Start(base::Bind(&ExtensionDownloader::DoStartAllPending,
+ weak_ptr_factory_.GetWeakPtr()));
+ } else {
+ DoStartAllPending();
+ }
+}
+
+void ExtensionDownloader::DoStartAllPending() {
+ ReportStats();
+ url_stats_ = URLStats();
+
+ for (FetchMap::iterator it = fetches_preparing_.begin();
+ it != fetches_preparing_.end();
+ ++it) {
+ std::vector<linked_ptr<ManifestFetchData>>& list = it->second;
+ for (size_t i = 0; i < list.size(); ++i) {
+ StartUpdateCheck(scoped_ptr<ManifestFetchData>(list[i].release()));
+ }
+ }
+ fetches_preparing_.clear();
+}
+
+void ExtensionDownloader::StartBlacklistUpdate(
+ const std::string& version,
+ const ManifestFetchData::PingData& ping_data,
+ int request_id) {
+ // Note: it is very important that we use the https version of the update
+ // url here to avoid DNS hijacking of the blacklist, which is not validated
+ // by a public key signature like .crx files are.
+ scoped_ptr<ManifestFetchData> blacklist_fetch(CreateManifestFetchData(
+ extension_urls::GetWebstoreUpdateUrl(), request_id));
+ DCHECK(blacklist_fetch->base_url().SchemeIsCryptographic());
+ blacklist_fetch->AddExtension(kBlacklistAppID, version, &ping_data,
+ std::string(), kDefaultInstallSource);
+ StartUpdateCheck(std::move(blacklist_fetch));
+}
+
+void ExtensionDownloader::SetWebstoreIdentityProvider(
+ scoped_ptr<IdentityProvider> identity_provider) {
+ identity_provider_.swap(identity_provider);
+}
+
+bool ExtensionDownloader::AddExtensionData(const std::string& id,
+ const Version& version,
+ Manifest::Type extension_type,
+ const GURL& extension_update_url,
+ const std::string& update_url_data,
+ int request_id) {
+ GURL update_url(extension_update_url);
+ // Skip extensions with non-empty invalid update URLs.
+ if (!update_url.is_empty() && !update_url.is_valid()) {
+ DLOG(WARNING) << "Extension " << id << " has invalid update url "
+ << update_url;
+ return false;
+ }
+
+ // Make sure we use SSL for store-hosted extensions.
+ if (extension_urls::IsWebstoreUpdateUrl(update_url) &&
+ !update_url.SchemeIsCryptographic())
+ update_url = extension_urls::GetWebstoreUpdateUrl();
+
+ // Skip extensions with empty IDs.
+ if (id.empty()) {
+ DLOG(WARNING) << "Found extension with empty ID";
+ return false;
+ }
+
+ if (update_url.DomainIs(kGoogleDotCom)) {
+ url_stats_.google_url_count++;
+ } else if (update_url.is_empty()) {
+ url_stats_.no_url_count++;
+ // Fill in default update URL.
+ update_url = extension_urls::GetWebstoreUpdateUrl();
+ } else {
+ url_stats_.other_url_count++;
+ }
+
+ switch (extension_type) {
+ case Manifest::TYPE_THEME:
+ ++url_stats_.theme_count;
+ break;
+ case Manifest::TYPE_EXTENSION:
+ case Manifest::TYPE_USER_SCRIPT:
+ ++url_stats_.extension_count;
+ break;
+ case Manifest::TYPE_HOSTED_APP:
+ case Manifest::TYPE_LEGACY_PACKAGED_APP:
+ ++url_stats_.app_count;
+ break;
+ case Manifest::TYPE_PLATFORM_APP:
+ ++url_stats_.platform_app_count;
+ break;
+ case Manifest::TYPE_UNKNOWN:
+ default:
+ ++url_stats_.pending_count;
+ break;
+ }
+
+ std::vector<GURL> update_urls;
+ update_urls.push_back(update_url);
+ // If metrics are enabled, also add to ManifestFetchData for the
+ // webstore update URL.
+ if (!extension_urls::IsWebstoreUpdateUrl(update_url) &&
+ enable_extra_update_metrics_) {
+ update_urls.push_back(extension_urls::GetWebstoreUpdateUrl());
+ }
+
+ for (size_t i = 0; i < update_urls.size(); ++i) {
+ DCHECK(!update_urls[i].is_empty());
+ DCHECK(update_urls[i].is_valid());
+
+ std::string install_source =
+ i == 0 ? kDefaultInstallSource : kNotFromWebstoreInstallSource;
+
+ ManifestFetchData::PingData ping_data;
+ ManifestFetchData::PingData* optional_ping_data = NULL;
+ if (delegate_->GetPingDataForExtension(id, &ping_data))
+ optional_ping_data = &ping_data;
+
+ // Find or create a ManifestFetchData to add this extension to.
+ bool added = false;
+ FetchMap::iterator existing_iter =
+ fetches_preparing_.find(std::make_pair(request_id, update_urls[i]));
+ if (existing_iter != fetches_preparing_.end() &&
+ !existing_iter->second.empty()) {
+ // Try to add to the ManifestFetchData at the end of the list.
+ ManifestFetchData* existing_fetch = existing_iter->second.back().get();
+ if (existing_fetch->AddExtension(id, version.GetString(),
+ optional_ping_data, update_url_data,
+ install_source)) {
+ added = true;
+ }
+ }
+ if (!added) {
+ // Otherwise add a new element to the list, if the list doesn't exist or
+ // if its last element is already full.
+ linked_ptr<ManifestFetchData> fetch(
+ CreateManifestFetchData(update_urls[i], request_id));
+ fetches_preparing_[std::make_pair(request_id, update_urls[i])].push_back(
+ fetch);
+ added = fetch->AddExtension(id, version.GetString(), optional_ping_data,
+ update_url_data, install_source);
+ DCHECK(added);
+ }
+ }
+
+ return true;
+}
+
+void ExtensionDownloader::ReportStats() const {
+ UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckExtension",
+ url_stats_.extension_count);
+ UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckTheme",
+ url_stats_.theme_count);
+ UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckApp", url_stats_.app_count);
+ UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckPackagedApp",
+ url_stats_.platform_app_count);
+ UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckPending",
+ url_stats_.pending_count);
+ UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckGoogleUrl",
+ url_stats_.google_url_count);
+ UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckOtherUrl",
+ url_stats_.other_url_count);
+ UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckNoUrl",
+ url_stats_.no_url_count);
+}
+
+void ExtensionDownloader::StartUpdateCheck(
+ scoped_ptr<ManifestFetchData> fetch_data) {
+ const std::set<std::string>& id_set(fetch_data->extension_ids());
+
+ if (!ExtensionsBrowserClient::Get()->IsBackgroundUpdateAllowed()) {
+ NotifyExtensionsDownloadFailed(id_set,
+ fetch_data->request_ids(),
+ ExtensionDownloaderDelegate::DISABLED);
+ return;
+ }
+
+ RequestQueue<ManifestFetchData>::iterator i;
+ for (i = manifests_queue_.begin(); i != manifests_queue_.end(); ++i) {
+ if (fetch_data->full_url() == i->full_url()) {
+ // This url is already scheduled to be fetched.
+ i->Merge(*fetch_data);
+ return;
+ }
+ }
+
+ if (manifests_queue_.active_request() &&
+ manifests_queue_.active_request()->full_url() == fetch_data->full_url()) {
+ manifests_queue_.active_request()->Merge(*fetch_data);
+ } else {
+ UMA_HISTOGRAM_COUNTS(
+ "Extensions.UpdateCheckUrlLength",
+ fetch_data->full_url().possibly_invalid_spec().length());
+
+ manifests_queue_.ScheduleRequest(std::move(fetch_data));
+ }
+}
+
+void ExtensionDownloader::CreateManifestFetcher() {
+ if (VLOG_IS_ON(2)) {
+ std::vector<std::string> id_vector(
+ manifests_queue_.active_request()->extension_ids().begin(),
+ manifests_queue_.active_request()->extension_ids().end());
+ std::string id_list = base::JoinString(id_vector, ",");
+ VLOG(2) << "Fetching " << manifests_queue_.active_request()->full_url()
+ << " for " << id_list;
+ }
+
+ manifest_fetcher_ = net::URLFetcher::Create(
+ kManifestFetcherId, manifests_queue_.active_request()->full_url(),
+ net::URLFetcher::GET, this);
+ manifest_fetcher_->SetRequestContext(request_context_.get());
+ manifest_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES |
+ net::LOAD_DISABLE_CACHE);
+ // Update checks can be interrupted if a network change is detected; this is
+ // common for the retail mode AppPack on ChromeOS. Retrying once should be
+ // enough to recover in those cases; let the fetcher retry up to 3 times
+ // just in case. http://crosbug.com/130602
+ manifest_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3);
+ manifest_fetcher_->Start();
+}
+
+void ExtensionDownloader::OnURLFetchComplete(const net::URLFetcher* source) {
+ VLOG(2) << source->GetResponseCode() << " " << source->GetURL();
+
+ if (source == manifest_fetcher_.get()) {
+ std::string data;
+ source->GetResponseAsString(&data);
+ OnManifestFetchComplete(source->GetURL(),
+ source->GetStatus(),
+ source->GetResponseCode(),
+ source->GetBackoffDelay(),
+ data);
+ } else if (source == extension_fetcher_.get()) {
+ OnCRXFetchComplete(source,
+ source->GetURL(),
+ source->GetStatus(),
+ source->GetResponseCode(),
+ source->GetBackoffDelay());
+ } else {
+ NOTREACHED();
+ }
+}
+
+void ExtensionDownloader::OnManifestFetchComplete(
+ const GURL& url,
+ const net::URLRequestStatus& status,
+ int response_code,
+ const base::TimeDelta& backoff_delay,
+ const std::string& data) {
+ // We want to try parsing the manifest, and if it indicates updates are
+ // available, we want to fire off requests to fetch those updates.
+ if (status.status() == net::URLRequestStatus::SUCCESS &&
+ (response_code == 200 || (url.SchemeIsFile() && data.length() > 0))) {
+ RETRY_HISTOGRAM("ManifestFetchSuccess",
+ manifests_queue_.active_request_failure_count(),
+ url);
+ VLOG(2) << "beginning manifest parse for " << url;
+ scoped_refptr<SafeManifestParser> safe_parser(new SafeManifestParser(
+ data,
+ base::Bind(
+ &ExtensionDownloader::HandleManifestResults,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Owned(manifests_queue_.reset_active_request().release()))));
+ safe_parser->Start();
+ } else {
+ VLOG(1) << "Failed to fetch manifest '" << url.possibly_invalid_spec()
+ << "' response code:" << response_code;
+ if (ShouldRetryRequest(status, response_code) &&
+ manifests_queue_.active_request_failure_count() < kMaxRetries) {
+ manifests_queue_.RetryRequest(backoff_delay);
+ } else {
+ RETRY_HISTOGRAM("ManifestFetchFailure",
+ manifests_queue_.active_request_failure_count(),
+ url);
+ NotifyExtensionsDownloadFailed(
+ manifests_queue_.active_request()->extension_ids(),
+ manifests_queue_.active_request()->request_ids(),
+ ExtensionDownloaderDelegate::MANIFEST_FETCH_FAILED);
+ }
+ }
+ manifest_fetcher_.reset();
+ manifests_queue_.reset_active_request();
+
+ // If we have any pending manifest requests, fire off the next one.
+ manifests_queue_.StartNextRequest();
+}
+
+void ExtensionDownloader::HandleManifestResults(
+ const ManifestFetchData* fetch_data,
+ const UpdateManifest::Results* results) {
+ // Keep a list of extensions that will not be updated, so that the |delegate_|
+ // can be notified once we're done here.
+ std::set<std::string> not_updated(fetch_data->extension_ids());
+
+ if (!results) {
+ VLOG(2) << "parsing manifest failed (" << fetch_data->full_url() << ")";
+ NotifyExtensionsDownloadFailed(
+ not_updated, fetch_data->request_ids(),
+ ExtensionDownloaderDelegate::MANIFEST_INVALID);
+ return;
+ } else {
+ VLOG(2) << "parsing manifest succeeded (" << fetch_data->full_url() << ")";
+ }
+
+ // Examine the parsed manifest and kick off fetches of any new crx files.
+ std::vector<int> updates;
+ DetermineUpdates(*fetch_data, *results, &updates);
+ for (size_t i = 0; i < updates.size(); i++) {
+ const UpdateManifest::Result* update = &(results->list.at(updates[i]));
+ const std::string& id = update->extension_id;
+ not_updated.erase(id);
+
+ GURL crx_url = update->crx_url;
+ if (id != kBlacklistAppID) {
+ NotifyUpdateFound(update->extension_id, update->version);
+ } else {
+ // The URL of the blacklist file is returned by the server and we need to
+ // be sure that we continue to be able to reliably detect whether a URL
+ // references a blacklist file.
+ DCHECK(extension_urls::IsBlacklistUpdateUrl(crx_url)) << crx_url;
+
+ // Force https (crbug.com/129587).
+ if (!crx_url.SchemeIsCryptographic()) {
+ url::Replacements<char> replacements;
+ std::string scheme("https");
+ replacements.SetScheme(scheme.c_str(),
+ url::Component(0, scheme.size()));
+ crx_url = crx_url.ReplaceComponents(replacements);
+ }
+ }
+ scoped_ptr<ExtensionFetch> fetch(
+ new ExtensionFetch(update->extension_id, crx_url, update->package_hash,
+ update->version, fetch_data->request_ids()));
+ FetchUpdatedExtension(std::move(fetch));
+ }
+
+ // If the manifest response included a <daystart> element, we want to save
+ // that value for any extensions which had sent a ping in the request.
+ if (fetch_data->base_url().DomainIs(kGoogleDotCom) &&
+ results->daystart_elapsed_seconds >= 0) {
+ Time day_start =
+ Time::Now() - TimeDelta::FromSeconds(results->daystart_elapsed_seconds);
+
+ const std::set<std::string>& extension_ids = fetch_data->extension_ids();
+ std::set<std::string>::const_iterator i;
+ for (i = extension_ids.begin(); i != extension_ids.end(); i++) {
+ const std::string& id = *i;
+ ExtensionDownloaderDelegate::PingResult& result = ping_results_[id];
+ result.did_ping = fetch_data->DidPing(id, ManifestFetchData::ROLLCALL);
+ result.day_start = day_start;
+ }
+ }
+
+ NotifyExtensionsDownloadFailed(
+ not_updated, fetch_data->request_ids(),
+ ExtensionDownloaderDelegate::NO_UPDATE_AVAILABLE);
+}
+
+void ExtensionDownloader::DetermineUpdates(
+ const ManifestFetchData& fetch_data,
+ const UpdateManifest::Results& possible_updates,
+ std::vector<int>* result) {
+ for (size_t i = 0; i < possible_updates.list.size(); i++) {
+ const UpdateManifest::Result* update = &possible_updates.list[i];
+ const std::string& id = update->extension_id;
+
+ if (!fetch_data.Includes(id)) {
+ VLOG(2) << "Ignoring " << id << " from this manifest";
+ continue;
+ }
+
+ if (VLOG_IS_ON(2)) {
+ if (update->version.empty())
+ VLOG(2) << "manifest indicates " << id << " has no update";
+ else
+ VLOG(2) << "manifest indicates " << id << " latest version is '"
+ << update->version << "'";
+ }
+
+ if (!delegate_->IsExtensionPending(id)) {
+ // If we're not installing pending extension, and the update
+ // version is the same or older than what's already installed,
+ // we don't want it.
+ std::string version;
+ if (!delegate_->GetExtensionExistingVersion(id, &version)) {
+ VLOG(2) << id << " is not installed";
+ continue;
+ }
+
+ VLOG(2) << id << " is at '" << version << "'";
+
+ Version existing_version(version);
+ Version update_version(update->version);
+ if (!update_version.IsValid() ||
+ update_version.CompareTo(existing_version) <= 0) {
+ continue;
+ }
+ }
+
+ // If the update specifies a browser minimum version, do we qualify?
+ if (update->browser_min_version.length() > 0 &&
+ !ExtensionsBrowserClient::Get()->IsMinBrowserVersionSupported(
+ update->browser_min_version)) {
+ // TODO(asargent) - We may want this to show up in the extensions UI
+ // eventually. (http://crbug.com/12547).
+ DLOG(WARNING) << "Updated version of extension " << id
+ << " available, but requires chrome version "
+ << update->browser_min_version;
+ continue;
+ }
+ VLOG(2) << "will try to update " << id;
+ result->push_back(i);
+ }
+}
+
+// Begins (or queues up) download of an updated extension.
+void ExtensionDownloader::FetchUpdatedExtension(
+ scoped_ptr<ExtensionFetch> fetch_data) {
+ if (!fetch_data->url.is_valid()) {
+ // TODO(asargent): This can sometimes be invalid. See crbug.com/130881.
+ DLOG(WARNING) << "Invalid URL: '" << fetch_data->url.possibly_invalid_spec()
+ << "' for extension " << fetch_data->id;
+ return;
+ }
+
+ for (RequestQueue<ExtensionFetch>::iterator iter = extensions_queue_.begin();
+ iter != extensions_queue_.end();
+ ++iter) {
+ if (iter->id == fetch_data->id || iter->url == fetch_data->url) {
+ iter->request_ids.insert(fetch_data->request_ids.begin(),
+ fetch_data->request_ids.end());
+ return; // already scheduled
+ }
+ }
+
+ if (extensions_queue_.active_request() &&
+ extensions_queue_.active_request()->url == fetch_data->url) {
+ extensions_queue_.active_request()->request_ids.insert(
+ fetch_data->request_ids.begin(), fetch_data->request_ids.end());
+ } else {
+ std::string version;
+ if (extension_cache_ &&
+ extension_cache_->GetExtension(fetch_data->id, fetch_data->package_hash,
+ NULL, &version) &&
+ version == fetch_data->version) {
+ base::FilePath crx_path;
+ // Now get .crx file path and mark extension as used.
+ extension_cache_->GetExtension(fetch_data->id, fetch_data->package_hash,
+ &crx_path, &version);
+ NotifyDelegateDownloadFinished(std::move(fetch_data), true, crx_path,
+ false);
+ } else {
+ extensions_queue_.ScheduleRequest(std::move(fetch_data));
+ }
+ }
+}
+
+void ExtensionDownloader::NotifyDelegateDownloadFinished(
+ scoped_ptr<ExtensionFetch> fetch_data,
+ bool from_cache,
+ const base::FilePath& crx_path,
+ bool file_ownership_passed) {
+ // Dereference required params before passing a scoped_ptr.
+ const std::string& id = fetch_data->id;
+ const std::string& package_hash = fetch_data->package_hash;
+ const GURL& url = fetch_data->url;
+ const std::string& version = fetch_data->version;
+ const std::set<int>& request_ids = fetch_data->request_ids;
+ delegate_->OnExtensionDownloadFinished(
+ CRXFileInfo(id, crx_path, package_hash), file_ownership_passed, url,
+ version, ping_results_[id], request_ids,
+ from_cache ? base::Bind(&ExtensionDownloader::CacheInstallDone,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Passed(&fetch_data))
+ : ExtensionDownloaderDelegate::InstallCallback());
+ if (!from_cache)
+ ping_results_.erase(id);
+}
+
+void ExtensionDownloader::CacheInstallDone(
+ scoped_ptr<ExtensionFetch> fetch_data,
+ bool should_download) {
+ ping_results_.erase(fetch_data->id);
+ if (should_download) {
+ // Resume download from cached manifest data.
+ extensions_queue_.ScheduleRequest(std::move(fetch_data));
+ }
+}
+
+void ExtensionDownloader::CreateExtensionFetcher() {
+ const ExtensionFetch* fetch = extensions_queue_.active_request();
+ extension_fetcher_ = net::URLFetcher::Create(kExtensionFetcherId, fetch->url,
+ net::URLFetcher::GET, this);
+ extension_fetcher_->SetRequestContext(request_context_.get());
+ extension_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3);
+
+ int load_flags = net::LOAD_DISABLE_CACHE;
+ bool is_secure = fetch->url.SchemeIsCryptographic();
+ if (fetch->credentials != ExtensionFetch::CREDENTIALS_COOKIES || !is_secure) {
+ load_flags |= net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES;
+ }
+ extension_fetcher_->SetLoadFlags(load_flags);
+
+ // Download CRX files to a temp file. The blacklist is small and will be
+ // processed in memory, so it is fetched into a string.
+ if (fetch->id != kBlacklistAppID) {
+ extension_fetcher_->SaveResponseToTemporaryFile(
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE));
+ }
+
+ if (fetch->credentials == ExtensionFetch::CREDENTIALS_OAUTH2_TOKEN &&
+ is_secure) {
+ if (access_token_.empty()) {
+ // We should try OAuth2, but we have no token cached. This
+ // ExtensionFetcher will be started once the token fetch is complete,
+ // in either OnTokenFetchSuccess or OnTokenFetchFailure.
+ DCHECK(identity_provider_.get());
+ OAuth2TokenService::ScopeSet webstore_scopes;
+ webstore_scopes.insert(kWebstoreOAuth2Scope);
+ access_token_request_ =
+ identity_provider_->GetTokenService()->StartRequest(
+ identity_provider_->GetActiveAccountId(), webstore_scopes, this);
+ return;
+ }
+ extension_fetcher_->AddExtraRequestHeader(
+ base::StringPrintf("%s: Bearer %s",
+ net::HttpRequestHeaders::kAuthorization,
+ access_token_.c_str()));
+ }
+
+ VLOG(2) << "Starting fetch of " << fetch->url << " for " << fetch->id;
+ extension_fetcher_->Start();
+}
+
+void ExtensionDownloader::OnCRXFetchComplete(
+ const net::URLFetcher* source,
+ const GURL& url,
+ const net::URLRequestStatus& status,
+ int response_code,
+ const base::TimeDelta& backoff_delay) {
+ ExtensionFetch& active_request = *extensions_queue_.active_request();
+ const std::string& id = active_request.id;
+ if (status.status() == net::URLRequestStatus::SUCCESS &&
+ (response_code == 200 || url.SchemeIsFile())) {
+ RETRY_HISTOGRAM("CrxFetchSuccess",
+ extensions_queue_.active_request_failure_count(),
+ url);
+ base::FilePath crx_path;
+ // Take ownership of the file at |crx_path|.
+ CHECK(source->GetResponseAsFilePath(true, &crx_path));
+ scoped_ptr<ExtensionFetch> fetch_data =
+ extensions_queue_.reset_active_request();
+ if (extension_cache_) {
+ const std::string& version = fetch_data->version;
+ const std::string& expected_hash = fetch_data->package_hash;
+ extension_cache_->PutExtension(
+ id, expected_hash, crx_path, version,
+ base::Bind(&ExtensionDownloader::NotifyDelegateDownloadFinished,
+ weak_ptr_factory_.GetWeakPtr(), base::Passed(&fetch_data),
+ false));
+ } else {
+ NotifyDelegateDownloadFinished(std::move(fetch_data), false, crx_path,
+ true);
+ }
+ } else if (IterateFetchCredentialsAfterFailure(
+ &active_request, status, response_code)) {
+ extensions_queue_.RetryRequest(backoff_delay);
+ } else {
+ const std::set<int>& request_ids = active_request.request_ids;
+ const ExtensionDownloaderDelegate::PingResult& ping = ping_results_[id];
+ VLOG(1) << "Failed to fetch extension '" << url.possibly_invalid_spec()
+ << "' response code:" << response_code;
+ if (ShouldRetryRequest(status, response_code) &&
+ extensions_queue_.active_request_failure_count() < kMaxRetries) {
+ extensions_queue_.RetryRequest(backoff_delay);
+ } else {
+ RETRY_HISTOGRAM("CrxFetchFailure",
+ extensions_queue_.active_request_failure_count(),
+ url);
+ // status.error() is 0 (net::OK) or negative. (See net/base/net_errors.h)
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Extensions.CrxFetchError", -status.error());
+ delegate_->OnExtensionDownloadFailed(
+ id, ExtensionDownloaderDelegate::CRX_FETCH_FAILED, ping, request_ids);
+ }
+ ping_results_.erase(id);
+ extensions_queue_.reset_active_request();
+ }
+
+ extension_fetcher_.reset();
+
+ // If there are any pending downloads left, start the next one.
+ extensions_queue_.StartNextRequest();
+}
+
+void ExtensionDownloader::NotifyExtensionsDownloadFailed(
+ const std::set<std::string>& extension_ids,
+ const std::set<int>& request_ids,
+ ExtensionDownloaderDelegate::Error error) {
+ for (std::set<std::string>::const_iterator it = extension_ids.begin();
+ it != extension_ids.end();
+ ++it) {
+ const ExtensionDownloaderDelegate::PingResult& ping = ping_results_[*it];
+ delegate_->OnExtensionDownloadFailed(*it, error, ping, request_ids);
+ ping_results_.erase(*it);
+ }
+}
+
+void ExtensionDownloader::NotifyUpdateFound(const std::string& id,
+ const std::string& version) {
+ UpdateDetails updateInfo(id, Version(version));
+ content::NotificationService::current()->Notify(
+ extensions::NOTIFICATION_EXTENSION_UPDATE_FOUND,
+ content::NotificationService::AllBrowserContextsAndSources(),
+ content::Details<UpdateDetails>(&updateInfo));
+}
+
+bool ExtensionDownloader::IterateFetchCredentialsAfterFailure(
+ ExtensionFetch* fetch,
+ const net::URLRequestStatus& status,
+ int response_code) {
+ bool auth_failure = status.status() == net::URLRequestStatus::CANCELED ||
+ (status.status() == net::URLRequestStatus::SUCCESS &&
+ (response_code == net::HTTP_UNAUTHORIZED ||
+ response_code == net::HTTP_FORBIDDEN));
+ if (!auth_failure) {
+ return false;
+ }
+ // Here we decide what to do next if the server refused to authorize this
+ // fetch.
+ switch (fetch->credentials) {
+ case ExtensionFetch::CREDENTIALS_NONE:
+ if (fetch->url.DomainIs(kGoogleDotCom) && identity_provider_) {
+ fetch->credentials = ExtensionFetch::CREDENTIALS_OAUTH2_TOKEN;
+ } else {
+ fetch->credentials = ExtensionFetch::CREDENTIALS_COOKIES;
+ }
+ return true;
+ case ExtensionFetch::CREDENTIALS_OAUTH2_TOKEN:
+ fetch->oauth2_attempt_count++;
+ // OAuth2 may fail due to an expired access token, in which case we
+ // should invalidate the token and try again.
+ if (response_code == net::HTTP_UNAUTHORIZED &&
+ fetch->oauth2_attempt_count <= kMaxOAuth2Attempts) {
+ DCHECK(identity_provider_.get());
+ OAuth2TokenService::ScopeSet webstore_scopes;
+ webstore_scopes.insert(kWebstoreOAuth2Scope);
+ identity_provider_->GetTokenService()->InvalidateAccessToken(
+ identity_provider_->GetActiveAccountId(), webstore_scopes,
+ access_token_);
+ access_token_.clear();
+ return true;
+ }
+ // Either there is no Gaia identity available, the active identity
+ // doesn't have access to this resource, or the server keeps returning
+ // 401s and we've retried too many times. Fall back on cookies.
+ if (access_token_.empty() || response_code == net::HTTP_FORBIDDEN ||
+ fetch->oauth2_attempt_count > kMaxOAuth2Attempts) {
+ fetch->credentials = ExtensionFetch::CREDENTIALS_COOKIES;
+ return true;
+ }
+ // Something else is wrong. Time to give up.
+ return false;
+ case ExtensionFetch::CREDENTIALS_COOKIES:
+ if (response_code == net::HTTP_FORBIDDEN) {
+ // Try the next session identity, up to some maximum.
+ return IncrementAuthUserIndex(&fetch->url);
+ }
+ return false;
+ default:
+ NOTREACHED();
+ }
+ NOTREACHED();
+ return false;
+}
+
+void ExtensionDownloader::OnGetTokenSuccess(
+ const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) {
+ access_token_ = access_token;
+ extension_fetcher_->AddExtraRequestHeader(
+ base::StringPrintf("%s: Bearer %s",
+ net::HttpRequestHeaders::kAuthorization,
+ access_token_.c_str()));
+ extension_fetcher_->Start();
+}
+
+void ExtensionDownloader::OnGetTokenFailure(
+ const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) {
+ // If we fail to get an access token, kick the pending fetch and let it fall
+ // back on cookies.
+ extension_fetcher_->Start();
+}
+
+ManifestFetchData* ExtensionDownloader::CreateManifestFetchData(
+ const GURL& update_url,
+ int request_id) {
+ ManifestFetchData::PingMode ping_mode = ManifestFetchData::NO_PING;
+ if (update_url.DomainIs(ping_enabled_domain_.c_str()))
+ ping_mode = ManifestFetchData::PING_WITH_ENABLED_STATE;
+ return new ManifestFetchData(
+ update_url, request_id, brand_code_, manifest_query_params_, ping_mode);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/updater/extension_downloader.h b/chromium/extensions/browser/updater/extension_downloader.h
new file mode 100644
index 00000000000..81b9134ed0f
--- /dev/null
+++ b/chromium/extensions/browser/updater/extension_downloader.h
@@ -0,0 +1,337 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_UPDATER_EXTENSION_DOWNLOADER_H_
+#define EXTENSIONS_BROWSER_UPDATER_EXTENSION_DOWNLOADER_H_
+
+#include <deque>
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/version.h"
+#include "extensions/browser/updater/extension_downloader_delegate.h"
+#include "extensions/browser/updater/manifest_fetch_data.h"
+#include "extensions/browser/updater/request_queue.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/update_manifest.h"
+#include "google_apis/gaia/oauth2_token_service.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "url/gurl.h"
+
+class IdentityProvider;
+
+namespace net {
+class URLFetcher;
+class URLRequestContextGetter;
+class URLRequestStatus;
+}
+
+namespace extensions {
+
+struct UpdateDetails {
+ UpdateDetails(const std::string& id, const base::Version& version);
+ ~UpdateDetails();
+
+ std::string id;
+ base::Version version;
+};
+
+class ExtensionCache;
+class ExtensionUpdaterTest;
+
+// A class that checks for updates of a given list of extensions, and downloads
+// the crx file when updates are found. It uses a |ExtensionDownloaderDelegate|
+// that takes ownership of the downloaded crx files, and handles events during
+// the update check.
+class ExtensionDownloader : public net::URLFetcherDelegate,
+ public OAuth2TokenService::Consumer {
+ public:
+ // A closure which constructs a new ExtensionDownloader to be owned by the
+ // caller.
+ typedef base::Callback<scoped_ptr<ExtensionDownloader>(
+ ExtensionDownloaderDelegate* delegate)> Factory;
+
+ // |delegate| is stored as a raw pointer and must outlive the
+ // ExtensionDownloader.
+ ExtensionDownloader(ExtensionDownloaderDelegate* delegate,
+ net::URLRequestContextGetter* request_context);
+ ~ExtensionDownloader() override;
+
+ // Adds |extension| to the list of extensions to check for updates.
+ // Returns false if the |extension| can't be updated due to invalid details.
+ // In that case, no callbacks will be performed on the |delegate_|.
+ // The |request_id| is passed on as is to the various |delegate_| callbacks.
+ // This is used for example by ExtensionUpdater to keep track of when
+ // potentially concurrent update checks complete.
+ bool AddExtension(const Extension& extension, int request_id);
+
+ // Adds extension |id| to the list of extensions to check for updates.
+ // Returns false if the |id| can't be updated due to invalid details.
+ // In that case, no callbacks will be performed on the |delegate_|.
+ // The |request_id| is passed on as is to the various |delegate_| callbacks.
+ // This is used for example by ExtensionUpdater to keep track of when
+ // potentially concurrent update checks complete.
+ bool AddPendingExtension(const std::string& id,
+ const GURL& update_url,
+ int request_id);
+
+ // Schedules a fetch of the manifest of all the extensions added with
+ // AddExtension() and AddPendingExtension().
+ void StartAllPending(ExtensionCache* cache);
+
+ // Schedules an update check of the blacklist.
+ void StartBlacklistUpdate(const std::string& version,
+ const ManifestFetchData::PingData& ping_data,
+ int request_id);
+
+ // Sets an IdentityProvider to be used for OAuth2 authentication on protected
+ // Webstore downloads.
+ void SetWebstoreIdentityProvider(
+ scoped_ptr<IdentityProvider> identity_provider);
+
+ void set_brand_code(const std::string& brand_code) {
+ brand_code_ = brand_code;
+ }
+
+ void set_manifest_query_params(const std::string& params) {
+ manifest_query_params_ = params;
+ }
+
+ void set_ping_enabled_domain(const std::string& domain) {
+ ping_enabled_domain_ = domain;
+ }
+
+ void set_enable_extra_update_metrics(bool enable) {
+ enable_extra_update_metrics_ = enable;
+ }
+
+ // These are needed for unit testing, to help identify the correct mock
+ // URLFetcher objects.
+ static const int kManifestFetcherId = 1;
+ static const int kExtensionFetcherId = 2;
+
+ // Update AppID for extension blacklist.
+ static const char kBlacklistAppID[];
+
+ static const int kMaxRetries = 10;
+
+ private:
+ friend class ExtensionUpdaterTest;
+
+ // These counters are bumped as extensions are added to be fetched. They
+ // are then recorded as UMA metrics when all the extensions have been added.
+ struct URLStats {
+ URLStats()
+ : no_url_count(0),
+ google_url_count(0),
+ other_url_count(0),
+ extension_count(0),
+ theme_count(0),
+ app_count(0),
+ platform_app_count(0),
+ pending_count(0) {}
+
+ int no_url_count, google_url_count, other_url_count;
+ int extension_count, theme_count, app_count, platform_app_count,
+ pending_count;
+ };
+
+ // We need to keep track of some information associated with a url
+ // when doing a fetch.
+ struct ExtensionFetch {
+ ExtensionFetch();
+ ExtensionFetch(const std::string& id,
+ const GURL& url,
+ const std::string& package_hash,
+ const std::string& version,
+ const std::set<int>& request_ids);
+ ~ExtensionFetch();
+
+ std::string id;
+ GURL url;
+ std::string package_hash;
+ std::string version;
+ std::set<int> request_ids;
+
+ enum CredentialsMode {
+ CREDENTIALS_NONE = 0,
+ CREDENTIALS_OAUTH2_TOKEN,
+ CREDENTIALS_COOKIES,
+ };
+
+ // Indicates the type of credentials to include with this fetch.
+ CredentialsMode credentials;
+
+ // Counts the number of times OAuth2 authentication has been attempted for
+ // this fetch.
+ int oauth2_attempt_count;
+ };
+
+ // Helper for AddExtension() and AddPendingExtension().
+ bool AddExtensionData(const std::string& id,
+ const base::Version& version,
+ Manifest::Type extension_type,
+ const GURL& extension_update_url,
+ const std::string& update_url_data,
+ int request_id);
+
+ // Adds all recorded stats taken so far to histogram counts.
+ void ReportStats() const;
+
+ // Begins an update check.
+ void StartUpdateCheck(scoped_ptr<ManifestFetchData> fetch_data);
+
+ // Called by RequestQueue when a new manifest fetch request is started.
+ void CreateManifestFetcher();
+
+ // net::URLFetcherDelegate implementation.
+ void OnURLFetchComplete(const net::URLFetcher* source) override;
+
+ // Handles the result of a manifest fetch.
+ void OnManifestFetchComplete(const GURL& url,
+ const net::URLRequestStatus& status,
+ int response_code,
+ const base::TimeDelta& backoff_delay,
+ const std::string& data);
+
+ // Once a manifest is parsed, this starts fetches of any relevant crx files.
+ // If |results| is null, it means something went wrong when parsing it.
+ void HandleManifestResults(const ManifestFetchData* fetch_data,
+ const UpdateManifest::Results* results);
+
+ // Given a list of potential updates, returns the indices of the ones that are
+ // applicable (are actually a new version, etc.) in |result|.
+ void DetermineUpdates(const ManifestFetchData& fetch_data,
+ const UpdateManifest::Results& possible_updates,
+ std::vector<int>* result);
+
+ // Begins (or queues up) download of an updated extension.
+ void FetchUpdatedExtension(scoped_ptr<ExtensionFetch> fetch_data);
+
+ // Called by RequestQueue when a new extension fetch request is started.
+ void CreateExtensionFetcher();
+
+ // Handles the result of a crx fetch.
+ void OnCRXFetchComplete(const net::URLFetcher* source,
+ const GURL& url,
+ const net::URLRequestStatus& status,
+ int response_code,
+ const base::TimeDelta& backoff_delay);
+
+ // Invokes OnExtensionDownloadFailed() on the |delegate_| for each extension
+ // in the set, with |error| as the reason for failure.
+ void NotifyExtensionsDownloadFailed(const std::set<std::string>& id_set,
+ const std::set<int>& request_ids,
+ ExtensionDownloaderDelegate::Error error);
+
+ // Send a notification that an update was found for |id| that we'll
+ // attempt to download.
+ void NotifyUpdateFound(const std::string& id, const std::string& version);
+
+ // Do real work of StartAllPending. If .crx cache is used, this function
+ // is called when cache is ready.
+ void DoStartAllPending();
+
+ // Notify delegate and remove ping results.
+ void NotifyDelegateDownloadFinished(scoped_ptr<ExtensionFetch> fetch_data,
+ bool from_cache,
+ const base::FilePath& crx_path,
+ bool file_ownership_passed);
+
+ // Cached extension installation completed. If it was not successful, we will
+ // try to download it from the web store using already fetched manifest.
+ void CacheInstallDone(scoped_ptr<ExtensionFetch> fetch_data, bool installed);
+
+ // Potentially updates an ExtensionFetch's authentication state and returns
+ // |true| if the fetch should be retried. Returns |false| if the failure was
+ // not related to authentication, leaving the ExtensionFetch data unmodified.
+ bool IterateFetchCredentialsAfterFailure(ExtensionFetch* fetch,
+ const net::URLRequestStatus& status,
+ int response_code);
+
+ // OAuth2TokenService::Consumer implementation.
+ void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) override;
+ void OnGetTokenFailure(const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) override;
+
+ ManifestFetchData* CreateManifestFetchData(const GURL& update_url,
+ int request_id);
+
+ // The delegate that receives the crx files downloaded by the
+ // ExtensionDownloader, and that fills in optional ping and update url data.
+ ExtensionDownloaderDelegate* delegate_;
+
+ // The request context to use for the URLFetchers.
+ scoped_refptr<net::URLRequestContextGetter> request_context_;
+
+ // Collects UMA samples that are reported when ReportStats() is called.
+ URLStats url_stats_;
+
+ // List of data on fetches we're going to do. We limit the number of
+ // extensions grouped together in one batch to avoid running into the limits
+ // on the length of http GET requests, so there might be multiple
+ // ManifestFetchData* objects with the same base_url.
+ typedef std::map<std::pair<int, GURL>,
+ std::vector<linked_ptr<ManifestFetchData>>> FetchMap;
+ FetchMap fetches_preparing_;
+
+ // Outstanding url fetch requests for manifests and updates.
+ scoped_ptr<net::URLFetcher> manifest_fetcher_;
+ scoped_ptr<net::URLFetcher> extension_fetcher_;
+
+ // Pending manifests and extensions to be fetched when the appropriate fetcher
+ // is available.
+ RequestQueue<ManifestFetchData> manifests_queue_;
+ RequestQueue<ExtensionFetch> extensions_queue_;
+
+ // Maps an extension-id to its PingResult data.
+ std::map<std::string, ExtensionDownloaderDelegate::PingResult> ping_results_;
+
+ // Cache for .crx files.
+ ExtensionCache* extension_cache_;
+
+ // An IdentityProvider which may be used for authentication on protected
+ // download requests. May be NULL.
+ scoped_ptr<IdentityProvider> identity_provider_;
+
+ // A Webstore download-scoped access token for the |identity_provider_|'s
+ // active account, if any.
+ std::string access_token_;
+
+ // A pending token fetch request.
+ scoped_ptr<OAuth2TokenService::Request> access_token_request_;
+
+ // Brand code to include with manifest fetch queries if sending ping data.
+ std::string brand_code_;
+
+ // Baseline parameters to include with manifest fetch queries.
+ std::string manifest_query_params_;
+
+ // Domain to enable ping data. Ping data will be sent with manifest fetches
+ // to update URLs which match this domain. Defaults to empty (no domain).
+ std::string ping_enabled_domain_;
+
+ // Indicates whether or not extra metrics should be included with ping data.
+ // Defaults to |false|.
+ bool enable_extra_update_metrics_;
+
+ // Used to create WeakPtrs to |this|.
+ base::WeakPtrFactory<ExtensionDownloader> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionDownloader);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_UPDATER_EXTENSION_DOWNLOADER_H_
diff --git a/chromium/extensions/browser/updater/extension_downloader_delegate.cc b/chromium/extensions/browser/updater/extension_downloader_delegate.cc
new file mode 100644
index 00000000000..5ccf9c96a88
--- /dev/null
+++ b/chromium/extensions/browser/updater/extension_downloader_delegate.cc
@@ -0,0 +1,39 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/updater/extension_downloader_delegate.h"
+
+#include "base/logging.h"
+#include "base/version.h"
+
+namespace extensions {
+
+ExtensionDownloaderDelegate::PingResult::PingResult() : did_ping(false) {
+}
+
+ExtensionDownloaderDelegate::PingResult::~PingResult() {
+}
+
+ExtensionDownloaderDelegate::~ExtensionDownloaderDelegate() {
+}
+
+void ExtensionDownloaderDelegate::OnExtensionDownloadFailed(
+ const std::string& id,
+ ExtensionDownloaderDelegate::Error error,
+ const ExtensionDownloaderDelegate::PingResult& ping_result,
+ const std::set<int>& request_id) {
+}
+
+bool ExtensionDownloaderDelegate::GetPingDataForExtension(
+ const std::string& id,
+ ManifestFetchData::PingData* ping) {
+ return false;
+}
+
+std::string ExtensionDownloaderDelegate::GetUpdateUrlData(
+ const std::string& id) {
+ return std::string();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/updater/extension_downloader_delegate.h b/chromium/extensions/browser/updater/extension_downloader_delegate.h
new file mode 100644
index 00000000000..155e569d51a
--- /dev/null
+++ b/chromium/extensions/browser/updater/extension_downloader_delegate.h
@@ -0,0 +1,134 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_UPDATER_EXTENSION_DOWNLOADER_DELEGATE_H_
+#define EXTENSIONS_BROWSER_UPDATER_EXTENSION_DOWNLOADER_DELEGATE_H_
+
+#include <set>
+#include <string>
+
+#include "base/callback.h"
+#include "base/time/time.h"
+#include "extensions/browser/crx_file_info.h"
+#include "extensions/browser/updater/manifest_fetch_data.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+}
+
+namespace extensions {
+
+class ExtensionDownloaderDelegate {
+ public:
+ virtual ~ExtensionDownloaderDelegate();
+
+ // Passed as an argument to ExtensionDownloader::OnExtensionDownloadFailed()
+ // to detail the reason for the failure.
+ enum Error {
+ // Background networking is disabled.
+ DISABLED,
+
+ // Failed to fetch the manifest for this extension.
+ MANIFEST_FETCH_FAILED,
+
+ // The manifest couldn't be parsed.
+ MANIFEST_INVALID,
+
+ // The manifest was fetched and parsed, and there are no updates for
+ // this extension.
+ NO_UPDATE_AVAILABLE,
+
+ // There was an update for this extension but the download of the crx
+ // failed.
+ CRX_FETCH_FAILED,
+ };
+
+ // Passed as an argument to the completion callbacks to signal whether
+ // the extension update sent a ping.
+ struct PingResult {
+ PingResult();
+ ~PingResult();
+
+ // Whether a ping was sent.
+ bool did_ping;
+
+ // The start of day, from the server's perspective. This is only valid
+ // when |did_ping| is true.
+ base::Time day_start;
+ };
+
+ // A callback that is called to indicate if ExtensionDownloader should ignore
+ // the cached entry and download a new .crx file.
+ typedef base::Callback<void(bool should_download)> InstallCallback;
+
+ // One of the following 3 methods is always invoked for a given extension
+ // id, if AddExtension() or AddPendingExtension() returned true when that
+ // extension was added to the ExtensionDownloader.
+ // To avoid duplicate work, ExtensionDownloader might merge multiple identical
+ // requests, so there is not necessarily a separate invocation of one of these
+ // methods for each call to AddExtension/AddPendingExtension. If it is
+ // important to be able to match up AddExtension calls with
+ // OnExtensionDownload callbacks, you need to make sure that for every call to
+ // AddExtension/AddPendingExtension the combination of extension id and
+ // request id is unique. The OnExtensionDownload related callbacks will then
+ // be called with all request ids that resulted in that extension being
+ // checked.
+
+ // Invoked if the extension couldn't be downloaded. |error| contains the
+ // failure reason.
+ virtual void OnExtensionDownloadFailed(const std::string& id,
+ Error error,
+ const PingResult& ping_result,
+ const std::set<int>& request_ids);
+
+ // Invoked if the extension had an update available and its crx was
+ // successfully downloaded to |path|. |ownership_passed| is true if delegate
+ // should get ownership of the file. The downloader may be able to get the
+ // .crx file both from a locally cached version or by issuing a network
+ // request. If the install attempt by the delegate fails and the source was
+ // the cache, the cached version may be corrupt (or simply not the desired
+ // one), and we'd like to try downloading the .crx from the network and have
+ // the delegate attempt install again. So if the |callback| parameter is
+ // non-null (if the file was taken from the cache), on install failure the
+ // downloader should be notified to try download from network by calling the
+ // callback with true; on successful install it should be called with false so
+ // that downloader could release all downloaded metadata. After downloading
+ // the delegate will be once again called with OnExtensionDownloadFinished (or
+ // OnExtensionDownloadFailed) called again with the same |request_ids|.
+ virtual void OnExtensionDownloadFinished(const CRXFileInfo& file,
+ bool file_ownership_passed,
+ const GURL& download_url,
+ const std::string& version,
+ const PingResult& ping_result,
+ const std::set<int>& request_ids,
+ const InstallCallback& callback) = 0;
+
+ // The remaining methods are used by the ExtensionDownloader to retrieve
+ // information about extensions from the delegate.
+
+ // Invoked to fill the PingData for the given extension id. Returns false
+ // if PingData should not be included for this extension's update check
+ // (this is the default).
+ virtual bool GetPingDataForExtension(const std::string& id,
+ ManifestFetchData::PingData* ping);
+
+ // Invoked to get the update url data for this extension's update url, if
+ // there is any. The default implementation returns an empty string.
+ virtual std::string GetUpdateUrlData(const std::string& id);
+
+ // Invoked to determine whether extension |id| is currently
+ // pending installation.
+ virtual bool IsExtensionPending(const std::string& id) = 0;
+
+ // Invoked to get the current version of extension |id|. Returns false if
+ // that extension is not installed.
+ virtual bool GetExtensionExistingVersion(const std::string& id,
+ std::string* version) = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_UPDATER_EXTENSION_DOWNLOADER_DELEGATE_H_
diff --git a/chromium/extensions/browser/updater/manifest_fetch_data.cc b/chromium/extensions/browser/updater/manifest_fetch_data.cc
new file mode 100644
index 00000000000..5bec4f6d570
--- /dev/null
+++ b/chromium/extensions/browser/updater/manifest_fetch_data.cc
@@ -0,0 +1,179 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/updater/manifest_fetch_data.h"
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "extensions/common/extension.h"
+#include "net/base/escape.h"
+
+namespace extensions {
+
+namespace {
+
+// Maximum length of an extension manifest update check url, since it is a GET
+// request. We want to stay under 2K because of proxies, etc.
+const int kExtensionsManifestMaxURLSize = 2000;
+
+void AddEnabledStateToPing(std::string* ping_value,
+ const ManifestFetchData::PingData* ping_data) {
+ *ping_value += "&e=" + std::string(ping_data->is_enabled ? "1" : "0");
+ if (!ping_data->is_enabled) {
+ // Add a dr=<number> param for each bit set in disable reasons.
+ for (int enum_value = 1; enum_value < Extension::DISABLE_REASON_LAST;
+ enum_value <<= 1) {
+ if (ping_data->disable_reasons & enum_value)
+ *ping_value += "&dr=" + base::IntToString(enum_value);
+ }
+ }
+}
+
+} // namespace
+
+ManifestFetchData::ManifestFetchData(const GURL& update_url,
+ int request_id,
+ const std::string& brand_code,
+ const std::string& base_query_params,
+ PingMode ping_mode)
+ : base_url_(update_url),
+ full_url_(update_url),
+ brand_code_(brand_code),
+ ping_mode_(ping_mode) {
+ std::string query =
+ full_url_.has_query() ? full_url_.query() + "&" : std::string();
+ query += base_query_params;
+ GURL::Replacements replacements;
+ replacements.SetQueryStr(query);
+ full_url_ = full_url_.ReplaceComponents(replacements);
+
+ request_ids_.insert(request_id);
+}
+
+ManifestFetchData::~ManifestFetchData() {
+}
+
+// The format for request parameters in update checks is:
+//
+// ?x=EXT1_INFO&x=EXT2_INFO
+//
+// where EXT1_INFO and EXT2_INFO are url-encoded strings of the form:
+//
+// id=EXTENSION_ID&v=VERSION&uc
+//
+// Provide ping data with the parameter ping=PING_DATA where PING_DATA
+// looks like r=DAYS or a=DAYS for extensions in the Chrome extensions gallery.
+// ('r' refers to 'roll call' ie installation, and 'a' refers to 'active').
+// These values will each be present at most once every 24 hours, and indicate
+// the number of days since the last time it was present in an update check.
+//
+// So for two extensions like:
+// Extension 1- id:aaaa version:1.1
+// Extension 2- id:bbbb version:2.0
+//
+// the full update url would be:
+// http://somehost/path?x=id%3Daaaa%26v%3D1.1%26uc&x=id%3Dbbbb%26v%3D2.0%26uc
+//
+// (Note that '=' is %3D and '&' is %26 when urlencoded.)
+bool ManifestFetchData::AddExtension(const std::string& id,
+ const std::string& version,
+ const PingData* ping_data,
+ const std::string& update_url_data,
+ const std::string& install_source) {
+ if (extension_ids_.find(id) != extension_ids_.end()) {
+ NOTREACHED() << "Duplicate extension id " << id;
+ return false;
+ }
+
+ // Compute the string we'd append onto the full_url_, and see if it fits.
+ std::vector<std::string> parts;
+ parts.push_back("id=" + id);
+ parts.push_back("v=" + version);
+ if (!install_source.empty())
+ parts.push_back("installsource=" + install_source);
+ parts.push_back("uc");
+
+ if (!update_url_data.empty()) {
+ // Make sure the update_url_data string is escaped before using it so that
+ // there is no chance of overriding the id or v other parameter value
+ // we place into the x= value.
+ parts.push_back("ap=" + net::EscapeQueryParamValue(update_url_data, true));
+ }
+
+ // Append brand code, rollcall and active ping parameters.
+ if (ping_mode_ != NO_PING) {
+ if (!brand_code_.empty())
+ parts.push_back(base::StringPrintf("brand=%s", brand_code_.c_str()));
+
+ std::string ping_value;
+ pings_[id] = PingData(0, 0, false, 0);
+ if (ping_data) {
+ if (ping_data->rollcall_days == kNeverPinged ||
+ ping_data->rollcall_days > 0) {
+ ping_value += "r=" + base::IntToString(ping_data->rollcall_days);
+ if (ping_mode_ == PING_WITH_ENABLED_STATE)
+ AddEnabledStateToPing(&ping_value, ping_data);
+ pings_[id].rollcall_days = ping_data->rollcall_days;
+ pings_[id].is_enabled = ping_data->is_enabled;
+ }
+ if (ping_data->active_days == kNeverPinged ||
+ ping_data->active_days > 0) {
+ if (!ping_value.empty())
+ ping_value += "&";
+ ping_value += "a=" + base::IntToString(ping_data->active_days);
+ pings_[id].active_days = ping_data->active_days;
+ }
+ }
+ if (!ping_value.empty())
+ parts.push_back("ping=" + net::EscapeQueryParamValue(ping_value, true));
+ }
+
+ std::string extra = full_url_.has_query() ? "&" : "?";
+ extra +=
+ "x=" + net::EscapeQueryParamValue(base::JoinString(parts, "&"), true);
+
+ // Check against our max url size, exempting the first extension added.
+ int new_size = full_url_.possibly_invalid_spec().size() + extra.size();
+ if (!extension_ids_.empty() && new_size > kExtensionsManifestMaxURLSize) {
+ UMA_HISTOGRAM_PERCENTAGE("Extensions.UpdateCheckHitUrlSizeLimit", 1);
+ return false;
+ }
+ UMA_HISTOGRAM_PERCENTAGE("Extensions.UpdateCheckHitUrlSizeLimit", 0);
+
+ // We have room so go ahead and add the extension.
+ extension_ids_.insert(id);
+ full_url_ = GURL(full_url_.possibly_invalid_spec() + extra);
+ return true;
+}
+
+bool ManifestFetchData::Includes(const std::string& extension_id) const {
+ return extension_ids_.find(extension_id) != extension_ids_.end();
+}
+
+bool ManifestFetchData::DidPing(const std::string& extension_id,
+ PingType type) const {
+ std::map<std::string, PingData>::const_iterator i = pings_.find(extension_id);
+ if (i == pings_.end())
+ return false;
+ int value = 0;
+ if (type == ROLLCALL)
+ value = i->second.rollcall_days;
+ else if (type == ACTIVE)
+ value = i->second.active_days;
+ else
+ NOTREACHED();
+ return value == kNeverPinged || value > 0;
+}
+
+void ManifestFetchData::Merge(const ManifestFetchData& other) {
+ DCHECK(full_url() == other.full_url());
+ request_ids_.insert(other.request_ids_.begin(), other.request_ids_.end());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/updater/manifest_fetch_data.h b/chromium/extensions/browser/updater/manifest_fetch_data.h
new file mode 100644
index 00000000000..0a31846d4ec
--- /dev/null
+++ b/chromium/extensions/browser/updater/manifest_fetch_data.h
@@ -0,0 +1,140 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_UPDATER_MANIFEST_FETCH_DATA_H_
+#define EXTENSIONS_BROWSER_UPDATER_MANIFEST_FETCH_DATA_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+// To save on server resources we can request updates for multiple extensions
+// in one manifest check. This class helps us keep track of the id's for a
+// given fetch, building up the actual URL, and what if anything to include
+// in the ping parameter.
+class ManifestFetchData {
+ public:
+ static const int kNeverPinged = -1;
+
+ // What ping mode this fetch should use.
+ enum PingMode {
+ // No ping, no extra metrics.
+ NO_PING,
+
+ // Ping without extra metrics.
+ PING,
+
+ // Ping with information about enabled/disabled state.
+ PING_WITH_ENABLED_STATE,
+ };
+
+ // Each ping type is sent at most once per day.
+ enum PingType {
+ // Used for counting total installs of an extension/app/theme.
+ ROLLCALL,
+
+ // Used for counting number of active users of an app, where "active" means
+ // the app was launched at least once since the last active ping.
+ ACTIVE,
+ };
+
+ struct PingData {
+ // The number of days it's been since our last rollcall or active ping,
+ // respectively. These are calculated based on the start of day from the
+ // server's perspective.
+ int rollcall_days;
+ int active_days;
+
+ // Whether the extension is enabled or not.
+ bool is_enabled;
+
+ // A bitmask of Extension::DisableReason's, which may contain one or more
+ // reasons why an extension is disabled.
+ int disable_reasons;
+
+ PingData()
+ : rollcall_days(0),
+ active_days(0),
+ is_enabled(true),
+ disable_reasons(0) {}
+ PingData(int rollcall, int active, bool enabled, int reasons)
+ : rollcall_days(rollcall),
+ active_days(active),
+ is_enabled(enabled),
+ disable_reasons(reasons) {}
+ };
+
+ ManifestFetchData(const GURL& update_url,
+ int request_id,
+ const std::string& brand_code,
+ const std::string& base_query_params,
+ PingMode ping_mode);
+ ~ManifestFetchData();
+
+ // Returns true if this extension information was successfully added. If the
+ // return value is false it means the full_url would have become too long, and
+ // this ManifestFetchData object remains unchanged.
+ bool AddExtension(const std::string& id,
+ const std::string& version,
+ const PingData* ping_data,
+ const std::string& update_url_data,
+ const std::string& install_source);
+
+ const GURL& base_url() const { return base_url_; }
+ const GURL& full_url() const { return full_url_; }
+ const std::set<std::string>& extension_ids() const { return extension_ids_; }
+ const std::set<int>& request_ids() const { return request_ids_; }
+
+ // Returns true if the given id is included in this manifest fetch.
+ bool Includes(const std::string& extension_id) const;
+
+ // Returns true if a ping parameter for |type| was added to full_url for this
+ // extension id.
+ bool DidPing(const std::string& extension_id, PingType type) const;
+
+ // Assuming that both this ManifestFetchData and |other| have the same
+ // full_url, this method merges the other information associated with the
+ // fetch (in particular this adds all request ids associated with |other|
+ // to this ManifestFetchData).
+ void Merge(const ManifestFetchData& other);
+
+ private:
+ // The set of extension id's for this ManifestFetchData.
+ std::set<std::string> extension_ids_;
+
+ // The set of ping data we actually sent.
+ std::map<std::string, PingData> pings_;
+
+ // The base update url without any arguments added.
+ GURL base_url_;
+
+ // The base update url plus arguments indicating the id, version, etc.
+ // information about each extension.
+ GURL full_url_;
+
+ // The set of request ids associated with this manifest fetch. If multiple
+ // requests are trying to fetch the same manifest, they can be merged into
+ // one fetch, so potentially multiple request ids can get associated with
+ // one ManifestFetchData.
+ std::set<int> request_ids_;
+
+ // The brand code to include with manifest fetch queries, if non-empty and
+ // |ping_mode_| >= PING.
+ const std::string brand_code_;
+
+ // The ping mode for this fetch. This determines whether or not ping data
+ // (and possibly extra metrics) will be included in the fetch query.
+ const PingMode ping_mode_;
+
+ DISALLOW_COPY_AND_ASSIGN(ManifestFetchData);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_UPDATER_MANIFEST_FETCH_DATA_H_
diff --git a/chromium/extensions/browser/updater/null_extension_cache.cc b/chromium/extensions/browser/updater/null_extension_cache.cc
new file mode 100644
index 00000000000..3f60d6083ae
--- /dev/null
+++ b/chromium/extensions/browser/updater/null_extension_cache.cc
@@ -0,0 +1,43 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/updater/null_extension_cache.h"
+
+#include "base/callback.h"
+
+namespace extensions {
+
+NullExtensionCache::NullExtensionCache() {
+}
+
+NullExtensionCache::~NullExtensionCache() {
+}
+
+void NullExtensionCache::Start(const base::Closure& callback) {
+ callback.Run();
+}
+
+void NullExtensionCache::Shutdown(const base::Closure& callback) {
+ callback.Run();
+}
+
+void NullExtensionCache::AllowCaching(const std::string& id) {
+}
+
+bool NullExtensionCache::GetExtension(const std::string& id,
+ const std::string& expected_hash,
+ base::FilePath* file_path,
+ std::string* version) {
+ return false;
+}
+
+void NullExtensionCache::PutExtension(const std::string& id,
+ const std::string& expected_hash,
+ const base::FilePath& file_path,
+ const std::string& version,
+ const PutExtensionCallback& callback) {
+ callback.Run(file_path, true);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/updater/null_extension_cache.h b/chromium/extensions/browser/updater/null_extension_cache.h
new file mode 100644
index 00000000000..465b3a0aacf
--- /dev/null
+++ b/chromium/extensions/browser/updater/null_extension_cache.h
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_UPDATER_NULL_EXTENSION_CACHE_H_
+#define EXTENSIONS_BROWSER_UPDATER_NULL_EXTENSION_CACHE_H_
+
+#include "base/macros.h"
+#include "extensions/browser/updater/extension_cache.h"
+
+namespace extensions {
+
+// Implements a pass-thru (i.e. do-nothing) ExtensionCache.
+class NullExtensionCache : public ExtensionCache {
+ public:
+ NullExtensionCache();
+ ~NullExtensionCache() override;
+
+ // ExtensionCache implementation.
+ void Start(const base::Closure& callback) override;
+ void Shutdown(const base::Closure& callback) override;
+ void AllowCaching(const std::string& id) override;
+ bool GetExtension(const std::string& id,
+ const std::string& expected_hash,
+ base::FilePath* file_path,
+ std::string* version) override;
+ void PutExtension(const std::string& id,
+ const std::string& expected_hash,
+ const base::FilePath& file_path,
+ const std::string& version,
+ const PutExtensionCallback& callback) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NullExtensionCache);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_UPDATER_NULL_EXTENSION_CACHE_H_
diff --git a/chromium/extensions/browser/updater/request_queue.h b/chromium/extensions/browser/updater/request_queue.h
new file mode 100644
index 00000000000..cce1a93de1c
--- /dev/null
+++ b/chromium/extensions/browser/updater/request_queue.h
@@ -0,0 +1,144 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_UPDATER_REQUEST_QUEUE_H_
+#define EXTENSIONS_BROWSER_UPDATER_REQUEST_QUEUE_H_
+
+#include <stddef.h>
+
+#include <deque>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "net/base/backoff_entry.h"
+
+namespace extensions {
+
+// This class keeps track of a queue of requests, and contains the logic to
+// retry requests with some backoff policy. Each request has a
+// net::BackoffEntry instance associated with it.
+//
+// The general flow when using this class would be something like this:
+// - requests are queued up by calling ScheduleRequest.
+// - when a request is ready to be executed, RequestQueue removes the
+// request from the queue, assigns it as active request, and calls
+// the callback that was passed to the constructor.
+// - (optionally) when a request has completed unsuccessfully call
+// RetryRequest to put the request back in the queue, using the
+// backoff policy and minimum backoff delay to determine when to
+// next schedule this request.
+// - call reset_active_request() to indicate that the active request has
+// been dealt with.
+// - call StartNextRequest to schedule the next pending request (if any).
+template <typename T>
+class RequestQueue {
+ public:
+ class iterator;
+
+ RequestQueue(const net::BackoffEntry::Policy* backoff_policy,
+ const base::Closure& start_request_callback);
+ ~RequestQueue();
+
+ // Returns the request that is currently being processed.
+ T* active_request();
+
+ // Returns the number of times the current request has been retried already.
+ int active_request_failure_count();
+
+ // Signals RequestQueue that processing of the current request has completed.
+ scoped_ptr<T> reset_active_request();
+
+ // Add the given request to the queue, and starts the next request if no
+ // request is currently being processed.
+ void ScheduleRequest(scoped_ptr<T> request);
+
+ bool empty() const;
+ size_t size() const;
+
+ // Returns the earliest release time of all requests currently in the queue.
+ base::TimeTicks NextReleaseTime() const;
+
+ // Starts the next request, if no request is currently active. This will
+ // synchronously call the start_request_callback if the release time of the
+ // earliest available request is in the past, otherwise it will call that
+ // callback asynchronously after enough time has passed.
+ void StartNextRequest();
+
+ // Tell RequestQueue to put the current request back in the queue, after
+ // applying the backoff policy to determine when to next try this request.
+ // If the policy results in a backoff delay smaller than |min_backoff_delay|,
+ // that delay is used instead.
+ void RetryRequest(const base::TimeDelta& min_backoff_delay);
+
+ iterator begin();
+ iterator end();
+
+ // Change the backoff policy used by the queue.
+ void set_backoff_policy(const net::BackoffEntry::Policy* backoff_policy);
+
+ private:
+ struct Request {
+ Request(net::BackoffEntry* backoff_entry, T* request)
+ : backoff_entry(backoff_entry), request(request) {}
+ linked_ptr<net::BackoffEntry> backoff_entry;
+ linked_ptr<T> request;
+ };
+
+ // Compares the release time of two pending requests.
+ static bool CompareRequests(const Request& a, const Request& b);
+
+ // Pushes a request with a given backoff entry onto the queue.
+ void PushImpl(scoped_ptr<T> request,
+ scoped_ptr<net::BackoffEntry> backoff_entry);
+
+ // The backoff policy used to determine backoff delays.
+ const net::BackoffEntry::Policy* backoff_policy_;
+
+ // Callback to call when a new request has become the active request.
+ base::Closure start_request_callback_;
+
+ // Priority queue of pending requests. Not using std::priority_queue since
+ // the code needs to be able to iterate over all pending requests.
+ std::deque<Request> pending_requests_;
+
+ // Active request and its associated backoff entry.
+ scoped_ptr<T> active_request_;
+ scoped_ptr<net::BackoffEntry> active_backoff_entry_;
+
+ // Timer to schedule calls to StartNextRequest, if the first pending request
+ // hasn't passed its release time yet.
+ base::Timer timer_;
+};
+
+// Iterator class that wraps a std::deque<> iterator, only giving access to the
+// actual request part of each item.
+template <typename T>
+class RequestQueue<T>::iterator {
+ public:
+ iterator() {}
+
+ T* operator*() { return it_->request.get(); }
+ T* operator->() { return it_->request.get(); }
+ iterator& operator++() {
+ ++it_;
+ return *this;
+ }
+ bool operator!=(const iterator& b) const { return it_ != b.it_; }
+
+ private:
+ friend class RequestQueue<T>;
+ typedef std::deque<typename RequestQueue<T>::Request> Container;
+
+ explicit iterator(const typename Container::iterator& it) : it_(it) {}
+
+ typename Container::iterator it_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_UPDATER_REQUEST_QUEUE_H_
diff --git a/chromium/extensions/browser/updater/request_queue_impl.h b/chromium/extensions/browser/updater/request_queue_impl.h
new file mode 100644
index 00000000000..24adca6a658
--- /dev/null
+++ b/chromium/extensions/browser/updater/request_queue_impl.h
@@ -0,0 +1,156 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_UPDATER_REQUEST_QUEUE_IMPL_H_
+#define EXTENSIONS_BROWSER_UPDATER_REQUEST_QUEUE_IMPL_H_
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "extensions/browser/updater/request_queue.h"
+
+namespace extensions {
+
+template <typename T>
+RequestQueue<T>::RequestQueue(
+ const net::BackoffEntry::Policy* const backoff_policy,
+ const base::Closure& start_request_callback)
+ : backoff_policy_(backoff_policy),
+ start_request_callback_(start_request_callback),
+ timer_(false, false) {
+}
+
+template <typename T>
+RequestQueue<T>::~RequestQueue() {
+}
+
+template <typename T>
+T* RequestQueue<T>::active_request() {
+ return active_request_.get();
+}
+
+template <typename T>
+int RequestQueue<T>::active_request_failure_count() {
+ return active_backoff_entry_->failure_count();
+}
+
+template <typename T>
+scoped_ptr<T> RequestQueue<T>::reset_active_request() {
+ active_backoff_entry_.reset();
+ return std::move(active_request_);
+}
+
+template <typename T>
+void RequestQueue<T>::ScheduleRequest(scoped_ptr<T> request) {
+ PushImpl(std::move(request), scoped_ptr<net::BackoffEntry>(
+ new net::BackoffEntry(backoff_policy_)));
+ StartNextRequest();
+}
+
+template <typename T>
+void RequestQueue<T>::PushImpl(scoped_ptr<T> request,
+ scoped_ptr<net::BackoffEntry> backoff_entry) {
+ pending_requests_.push_back(
+ Request(backoff_entry.release(), request.release()));
+ std::push_heap(
+ pending_requests_.begin(), pending_requests_.end(), CompareRequests);
+}
+
+template <typename T>
+bool RequestQueue<T>::empty() const {
+ return pending_requests_.empty();
+}
+
+template <typename T>
+size_t RequestQueue<T>::size() const {
+ return pending_requests_.size();
+}
+
+template <typename T>
+base::TimeTicks RequestQueue<T>::NextReleaseTime() const {
+ return pending_requests_.front().backoff_entry->GetReleaseTime();
+}
+
+template <typename T>
+void RequestQueue<T>::StartNextRequest() {
+ if (active_request_)
+ // Already running a request, assume this method will be called again when
+ // the request is done.
+ return;
+
+ if (empty())
+ // No requests in the queue, so we're done.
+ return;
+
+ base::TimeTicks next_release = NextReleaseTime();
+ base::TimeTicks now = base::TimeTicks::Now();
+ if (next_release > now) {
+ // Not ready for the next update check yet, call this method when it is
+ // time.
+ timer_.Start(
+ FROM_HERE,
+ next_release - now,
+ base::Bind(&RequestQueue<T>::StartNextRequest, base::Unretained(this)));
+ return;
+ }
+
+ // pop_heap swaps the first and last elements of pending_requests_, and after
+ // that assures that the rest of pending_requests_ (excluding the
+ // now last/formerly first element) forms a proper heap. After pop_heap
+ // [begin, end-1) is a valid heap, and *(end - 1) contains the element that
+ // used to be at the top of the heap. Since no elements are actually
+ // removed from the container it is safe to read the entry being removed after
+ // pop_heap is called (but before pop_back is called).
+ std::pop_heap(
+ pending_requests_.begin(), pending_requests_.end(), CompareRequests);
+
+ active_backoff_entry_.reset(pending_requests_.back().backoff_entry.release());
+ active_request_.reset(pending_requests_.back().request.release());
+
+ pending_requests_.pop_back();
+
+ start_request_callback_.Run();
+}
+
+template <typename T>
+void RequestQueue<T>::RetryRequest(const base::TimeDelta& min_backoff_delay) {
+ active_backoff_entry_->InformOfRequest(false);
+ if (active_backoff_entry_->GetTimeUntilRelease() < min_backoff_delay) {
+ active_backoff_entry_->SetCustomReleaseTime(base::TimeTicks::Now() +
+ min_backoff_delay);
+ }
+ PushImpl(std::move(active_request_), std::move(active_backoff_entry_));
+}
+
+template <typename T>
+typename RequestQueue<T>::iterator RequestQueue<T>::begin() {
+ return iterator(pending_requests_.begin());
+}
+
+template <typename T>
+typename RequestQueue<T>::iterator RequestQueue<T>::end() {
+ return iterator(pending_requests_.end());
+}
+
+template <typename T>
+void RequestQueue<T>::set_backoff_policy(
+ const net::BackoffEntry::Policy* backoff_policy) {
+ backoff_policy_ = backoff_policy;
+}
+
+// static
+template <typename T>
+bool RequestQueue<T>::CompareRequests(const Request& a, const Request& b) {
+ return a.backoff_entry->GetReleaseTime() > b.backoff_entry->GetReleaseTime();
+}
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_UPDATER_REQUEST_QUEUE_IMPL_H_
diff --git a/chromium/extensions/browser/updater/safe_manifest_parser.cc b/chromium/extensions/browser/updater/safe_manifest_parser.cc
new file mode 100644
index 00000000000..05c75a95596
--- /dev/null
+++ b/chromium/extensions/browser/updater/safe_manifest_parser.cc
@@ -0,0 +1,80 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/updater/safe_manifest_parser.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/utility_process_host.h"
+#include "content/public/common/content_switches.h"
+#include "extensions/common/extension_utility_messages.h"
+#include "ipc/ipc_message_macros.h"
+#include "grit/extensions_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+SafeManifestParser::SafeManifestParser(const std::string& xml,
+ const ResultsCallback& results_callback)
+ : xml_(xml), results_callback_(results_callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void SafeManifestParser::Start() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&SafeManifestParser::ParseInSandbox, this))) {
+ NOTREACHED();
+ }
+}
+
+SafeManifestParser::~SafeManifestParser() {
+ // If we're using UtilityProcessHost, we may not be destroyed on
+ // the UI or IO thread.
+}
+
+void SafeManifestParser::ParseInSandbox() {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+ content::UtilityProcessHost* host = content::UtilityProcessHost::Create(
+ this,
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI).get());
+ host->SetName(
+ l10n_util::GetStringUTF16(IDS_UTILITY_PROCESS_MANIFEST_PARSER_NAME));
+ host->Send(new ExtensionUtilityMsg_ParseUpdateManifest(xml_));
+}
+
+bool SafeManifestParser::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(SafeManifestParser, message)
+ IPC_MESSAGE_HANDLER(ExtensionUtilityHostMsg_ParseUpdateManifest_Succeeded,
+ OnParseUpdateManifestSucceeded)
+ IPC_MESSAGE_HANDLER(ExtensionUtilityHostMsg_ParseUpdateManifest_Failed,
+ OnParseUpdateManifestFailed)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void SafeManifestParser::OnParseUpdateManifestSucceeded(
+ const UpdateManifest::Results& results) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ results_callback_.Run(&results);
+}
+
+void SafeManifestParser::OnParseUpdateManifestFailed(
+ const std::string& error_message) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ LOG(WARNING) << "Error parsing update manifest:\n" << error_message;
+ results_callback_.Run(NULL);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/updater/safe_manifest_parser.h b/chromium/extensions/browser/updater/safe_manifest_parser.h
new file mode 100644
index 00000000000..b6559834f7e
--- /dev/null
+++ b/chromium/extensions/browser/updater/safe_manifest_parser.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_UPDATER_SAFE_MANIFEST_PARSER_H_
+#define EXTENSIONS_BROWSER_UPDATER_SAFE_MANIFEST_PARSER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/public/browser/utility_process_host_client.h"
+#include "extensions/browser/updater/manifest_fetch_data.h"
+#include "extensions/common/update_manifest.h"
+
+namespace extensions {
+
+// Utility class to handle doing xml parsing in a sandboxed utility process.
+class SafeManifestParser : public content::UtilityProcessHostClient {
+ public:
+ // Callback that is invoked when the manifest results are ready.
+ typedef base::Callback<void(const UpdateManifest::Results*)> ResultsCallback;
+
+ SafeManifestParser(const std::string& xml,
+ const ResultsCallback& results_callback);
+
+ // Posts a task over to the IO loop to start the parsing of xml_ in a
+ // utility process.
+ void Start();
+
+ private:
+ ~SafeManifestParser() override;
+
+ // Creates the sandboxed utility process and tells it to start parsing.
+ void ParseInSandbox();
+
+ // content::UtilityProcessHostClient implementation.
+ bool OnMessageReceived(const IPC::Message& message) override;
+
+ void OnParseUpdateManifestSucceeded(const UpdateManifest::Results& results);
+ void OnParseUpdateManifestFailed(const std::string& error_message);
+
+ const std::string xml_;
+
+ // Should be accessed only on UI thread.
+ ResultsCallback results_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(SafeManifestParser);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_UPDATER_SAFE_MANIFEST_PARSER_H_
diff --git a/chromium/extensions/browser/updater/update_client_config.cc b/chromium/extensions/browser/updater/update_client_config.cc
new file mode 100644
index 00000000000..36ab34f4898
--- /dev/null
+++ b/chromium/extensions/browser/updater/update_client_config.cc
@@ -0,0 +1,23 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/updater/update_client_config.h"
+
+#include "content/public/browser/browser_thread.h"
+
+namespace extensions {
+
+UpdateClientConfig::UpdateClientConfig() {}
+
+scoped_refptr<base::SequencedTaskRunner>
+UpdateClientConfig::GetSequencedTaskRunner() const {
+ return content::BrowserThread::GetBlockingPool()
+ ->GetSequencedTaskRunnerWithShutdownBehavior(
+ base::SequencedWorkerPool::GetSequenceToken(),
+ base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
+}
+
+UpdateClientConfig::~UpdateClientConfig() {}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/updater/update_client_config.h b/chromium/extensions/browser/updater/update_client_config.h
new file mode 100644
index 00000000000..e54fde4b090
--- /dev/null
+++ b/chromium/extensions/browser/updater/update_client_config.h
@@ -0,0 +1,38 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_UPDATER_UPDATE_CLIENT_CONFIG_H_
+#define EXTENSIONS_BROWSER_UPDATER_UPDATE_CLIENT_CONFIG_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/update_client/configurator.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace extensions {
+
+// Used to provide configuration settings to the UpdateClient.
+class UpdateClientConfig : public update_client::Configurator {
+ public:
+ UpdateClientConfig();
+
+ scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner()
+ const override;
+
+ protected:
+ friend class base::RefCountedThreadSafe<UpdateClientConfig>;
+ ~UpdateClientConfig() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UpdateClientConfig);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_UPDATER_UPDATE_CLIENT_CONFIG_H_
diff --git a/chromium/extensions/browser/updater/update_data_provider.cc b/chromium/extensions/browser/updater/update_data_provider.cc
new file mode 100644
index 00000000000..b86eaa8c19f
--- /dev/null
+++ b/chromium/extensions/browser/updater/update_data_provider.cc
@@ -0,0 +1,71 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/updater/update_data_provider.h"
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/strings/string_util.h"
+#include "components/update_client/update_client.h"
+#include "content/public/browser/browser_thread.h"
+#include "crypto/sha2.h"
+#include "extensions/browser/content_verifier.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/updater/update_install_shim.h"
+#include "extensions/common/extension.h"
+
+namespace extensions {
+
+UpdateDataProvider::UpdateDataProvider(content::BrowserContext* context,
+ const InstallCallback& callback)
+ : context_(context), callback_(callback) {}
+
+UpdateDataProvider::~UpdateDataProvider() {}
+
+void UpdateDataProvider::Shutdown() {
+ context_ = nullptr;
+}
+
+void UpdateDataProvider::GetData(
+ const std::vector<std::string>& ids,
+ std::vector<update_client::CrxComponent>* data) {
+ if (!context_)
+ return;
+ const ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
+ for (const auto& id : ids) {
+ const Extension* extension = registry->GetInstalledExtension(id);
+ if (!extension)
+ continue;
+ data->push_back(update_client::CrxComponent());
+ update_client::CrxComponent* info = &data->back();
+ std::string pubkey_bytes;
+ base::Base64Decode(extension->public_key(), &pubkey_bytes);
+ info->pk_hash.resize(crypto::kSHA256Length, 0);
+ crypto::SHA256HashString(pubkey_bytes, info->pk_hash.data(),
+ info->pk_hash.size());
+ info->version = *extension->version();
+ info->allows_background_download = false;
+ info->requires_network_encryption = true;
+ info->installer = new UpdateInstallShim(
+ id, extension->path(),
+ base::Bind(&UpdateDataProvider::RunInstallCallback, this));
+ }
+}
+
+void UpdateDataProvider::RunInstallCallback(const std::string& extension_id,
+ const base::FilePath& temp_dir) {
+ if (!context_) {
+ content::BrowserThread::PostBlockingPoolTask(
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(&base::DeleteFile), temp_dir, false));
+ return;
+ } else {
+ callback_.Run(context_, extension_id, temp_dir);
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/updater/update_data_provider.h b/chromium/extensions/browser/updater/update_data_provider.h
new file mode 100644
index 00000000000..95cea59c645
--- /dev/null
+++ b/chromium/extensions/browser/updater/update_data_provider.h
@@ -0,0 +1,69 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_UPDATER_UPDATE_DATA_PROVIDER_H_
+#define EXTENSIONS_BROWSER_UPDATER_UPDATE_DATA_PROVIDER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace update_client {
+struct CrxComponent;
+}
+
+namespace extensions {
+
+// This class exists to let an UpdateClient retrieve information about a set of
+// extensions it is doing an update check for.
+class UpdateDataProvider : public base::RefCounted<UpdateDataProvider> {
+ public:
+ typedef base::Callback<void(content::BrowserContext* context,
+ const std::string& /* extension_id */,
+ const base::FilePath& /* temp_dir */)>
+ InstallCallback;
+
+ // We need a browser context to use when retrieving data for a set of
+ // extension ids, as well as a callback for proceeding with installation
+ // steps once the UpdateClient has downloaded and unpacked an update for an
+ // extension.
+ UpdateDataProvider(content::BrowserContext* context,
+ const InstallCallback& callback);
+
+ // Notify this object that the associated browser context is being shut down
+ // the pointer to the context should be dropped and no more work should be
+ // done.
+ void Shutdown();
+
+ // Matches update_client::UpdateClient::CrxDataCallback
+ void GetData(const std::vector<std::string>& ids,
+ std::vector<update_client::CrxComponent>* data);
+
+ private:
+ friend class base::RefCounted<UpdateDataProvider>;
+ ~UpdateDataProvider();
+
+ void RunInstallCallback(const std::string& extension_id,
+ const base::FilePath& temp_dir);
+
+ content::BrowserContext* context_;
+ InstallCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateDataProvider);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_UPDATER_UPDATE_DATA_PROVIDER_H_
diff --git a/chromium/extensions/browser/updater/update_install_shim.cc b/chromium/extensions/browser/updater/update_install_shim.cc
new file mode 100644
index 00000000000..4b6f58294aa
--- /dev/null
+++ b/chromium/extensions/browser/updater/update_install_shim.cc
@@ -0,0 +1,83 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/updater/update_install_shim.h"
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace extensions {
+
+UpdateInstallShim::UpdateInstallShim(std::string extension_id,
+ const base::FilePath& extension_root,
+ const UpdateInstallShimCallback& callback)
+ : extension_id_(extension_id),
+ extension_root_(extension_root),
+ callback_(callback) {}
+
+void UpdateInstallShim::OnUpdateError(int error) {
+ VLOG(1) << "OnUpdateError (" << extension_id_ << ") " << error;
+}
+
+bool UpdateInstallShim::Install(const base::DictionaryValue& manifest,
+ const base::FilePath& unpack_path) {
+ base::ScopedTempDir temp_dir;
+ if (!temp_dir.CreateUniqueTempDir())
+ return false;
+
+ // The UpdateClient code will delete unpack_path if it still exists after
+ // this method is done, so we rename it on top of our temp dir.
+ if (!base::DeleteFile(temp_dir.path(), true) ||
+ !base::Move(unpack_path, temp_dir.path())) {
+ LOG(ERROR) << "Trying to install update for " << extension_id_
+ << "and failed to move " << unpack_path.value() << " to "
+ << temp_dir.path().value();
+ return false;
+ }
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI, FROM_HERE,
+ base::Bind(&UpdateInstallShim::RunCallbackOnUIThread, this,
+ temp_dir.Take()));
+ return true;
+}
+
+bool UpdateInstallShim::GetInstalledFile(const std::string& file,
+ base::FilePath* installed_file) {
+ base::FilePath relative_path = base::FilePath::FromUTF8Unsafe(file);
+ if (relative_path.IsAbsolute() || relative_path.ReferencesParent())
+ return false;
+ *installed_file = extension_root_.Append(relative_path);
+ if (!extension_root_.IsParent(*installed_file) ||
+ !base::PathExists(*installed_file)) {
+ VLOG(1) << "GetInstalledFile failed to find " << installed_file->value();
+ installed_file->clear();
+ return false;
+ }
+ return true;
+}
+
+bool UpdateInstallShim::Uninstall() {
+ NOTREACHED();
+ return false;
+}
+
+UpdateInstallShim::~UpdateInstallShim() {}
+
+void UpdateInstallShim::RunCallbackOnUIThread(const base::FilePath& temp_dir) {
+ if (callback_.is_null()) {
+ content::BrowserThread::PostBlockingPoolTask(
+ FROM_HERE, base::Bind(base::IgnoreResult(&base::DeleteFile), temp_dir,
+ true /*recursive */));
+ return;
+ }
+ callback_.Run(extension_id_, temp_dir);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/updater/update_install_shim.h b/chromium/extensions/browser/updater/update_install_shim.h
new file mode 100644
index 00000000000..518866a6062
--- /dev/null
+++ b/chromium/extensions/browser/updater/update_install_shim.h
@@ -0,0 +1,77 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_UPDATER_UPDATE_INSTALL_SHIM_H_
+#define EXTENSIONS_BROWSER_UPDATER_UPDATE_INSTALL_SHIM_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "components/update_client/update_client.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace extensions {
+
+// A callback to implement the install of a new version of the extension.
+// Takes ownership of the directory at |temp_dir|.
+using UpdateInstallShimCallback =
+ base::Callback<void(const std::string& extension_id,
+ const base::FilePath& temp_dir)>;
+
+// This class is used as a shim between the components::update_client and
+// extensions code, to help the generic update_client code prepare and then
+// install an updated version of an extension. Because the update_client code
+// doesn't have the notion of extension ids, we use instances of this class to
+// map an install request back to the original update check for a given
+// extension.
+class UpdateInstallShim : public update_client::CrxInstaller {
+ public:
+ // This method takes the id and root directory for an extension we're doing
+ // an update check for, as well as a callback to call if we get a new version
+ // of it to install.
+ UpdateInstallShim(std::string extension_id,
+ const base::FilePath& extension_root,
+ const UpdateInstallShimCallback& callback);
+
+ // Called when an update attempt failed.
+ void OnUpdateError(int error) override;
+
+ // This is called when a new version of an extension is unpacked at
+ // |unpack_path| and is ready for install.
+ bool Install(const base::DictionaryValue& manifest,
+ const base::FilePath& unpack_path) override;
+
+ // This is called by the generic differential update code in the
+ // update_client to provide the path to an existing file in the current
+ // version of the extension, so that it can be copied (or serve as the input
+ // to diff-patching) with output going to the directory with the new version
+ // being staged on disk for install.
+ bool GetInstalledFile(const std::string& file,
+ base::FilePath* installed_file) override;
+
+ // This method is not relevant to extension updating.
+ bool Uninstall() override;
+
+ private:
+ friend class base::RefCountedThreadSafe<UpdateInstallShim>;
+ ~UpdateInstallShim() override;
+
+ // Takes ownership of the directory at path |temp_dir|.
+ void RunCallbackOnUIThread(const base::FilePath& temp_dir);
+
+ std::string extension_id_;
+ base::FilePath extension_root_;
+ UpdateInstallShimCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateInstallShim);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_UPDATER_UPDATE_INSTALL_SHIM_H_
diff --git a/chromium/extensions/browser/updater/update_service.cc b/chromium/extensions/browser/updater/update_service.cc
new file mode 100644
index 00000000000..d356f59fec7
--- /dev/null
+++ b/chromium/extensions/browser/updater/update_service.cc
@@ -0,0 +1,70 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/updater/update_service.h"
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "components/update_client/update_client.h"
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/updater/update_data_provider.h"
+#include "extensions/browser/updater/update_service_factory.h"
+
+namespace {
+
+void UpdateCheckCompleteCallback(int error) {}
+
+void InstallUpdateCallback(content::BrowserContext* context,
+ const std::string& extension_id,
+ const base::FilePath& temp_dir) {
+ extensions::ExtensionSystem::Get(context)
+ ->InstallUpdate(extension_id, temp_dir);
+}
+
+} // namespace
+
+namespace extensions {
+
+// static
+UpdateService* UpdateService::Get(content::BrowserContext* context) {
+ return UpdateServiceFactory::GetForBrowserContext(context);
+}
+
+void UpdateService::Shutdown() {
+ if (update_data_provider_) {
+ update_data_provider_->Shutdown();
+ update_data_provider_ = nullptr;
+ }
+ update_client_ = nullptr;
+ context_ = nullptr;
+}
+
+void UpdateService::SendUninstallPing(const std::string& id,
+ const Version& version,
+ int reason) {
+ update_client_->SendUninstallPing(id, version, reason);
+}
+
+void UpdateService::StartUpdateCheck(std::vector<std::string> extension_ids) {
+ if (!update_client_)
+ return;
+ update_client_->Update(extension_ids, base::Bind(&UpdateDataProvider::GetData,
+ update_data_provider_),
+ base::Bind(&UpdateCheckCompleteCallback));
+}
+
+UpdateService::UpdateService(
+ content::BrowserContext* context,
+ scoped_refptr<update_client::UpdateClient> update_client)
+ : context_(context), update_client_(update_client) {
+ CHECK(update_client_);
+ update_data_provider_ =
+ new UpdateDataProvider(context_, base::Bind(&InstallUpdateCallback));
+}
+
+UpdateService::~UpdateService() {}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/updater/update_service.h b/chromium/extensions/browser/updater/update_service.h
new file mode 100644
index 00000000000..992f55ec4ca
--- /dev/null
+++ b/chromium/extensions/browser/updater/update_service.h
@@ -0,0 +1,70 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_UPDATER_UPDATE_SERVICE_H_
+#define EXTENSIONS_BROWSER_UPDATER_UPDATE_SERVICE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+namespace base {
+class Version;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace update_client {
+class UpdateClient;
+}
+
+namespace extensions {
+
+class UpdateDataProvider;
+class UpdateService;
+class UpdateServiceFactory;
+
+// This service manages the autoupdate of extensions. It should eventually
+// replace ExtensionUpdater in Chrome.
+// TODO(rockot): Replace ExtensionUpdater with this service.
+class UpdateService : public KeyedService {
+ public:
+ static UpdateService* Get(content::BrowserContext* context);
+
+ void Shutdown() override;
+
+ void SendUninstallPing(const std::string& id,
+ const base::Version& version,
+ int reason);
+
+ // Starts an update check for each of |extension_ids|. If there are any
+ // updates available, they will be downloaded, checked for integrity,
+ // unpacked, and then passed off to the ExtensionSystem::InstallUpdate method
+ // for install completion.
+ void StartUpdateCheck(std::vector<std::string> extension_ids);
+
+ private:
+ friend class UpdateServiceFactory;
+
+ UpdateService(content::BrowserContext* context,
+ scoped_refptr<update_client::UpdateClient> update_client);
+ ~UpdateService() override;
+
+ content::BrowserContext* context_;
+
+ scoped_refptr<update_client::UpdateClient> update_client_;
+ scoped_refptr<UpdateDataProvider> update_data_provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateService);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_UPDATER_UPDATE_SERVICE_H_
diff --git a/chromium/extensions/browser/updater/update_service_factory.cc b/chromium/extensions/browser/updater/update_service_factory.cc
new file mode 100644
index 00000000000..e441dc30fcf
--- /dev/null
+++ b/chromium/extensions/browser/updater/update_service_factory.cc
@@ -0,0 +1,43 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/updater/update_service_factory.h"
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/update_client/update_client.h"
+#include "extensions/browser/extension_registry_factory.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/process_manager_factory.h"
+#include "extensions/browser/updater/update_service.h"
+
+namespace extensions {
+
+// static
+UpdateService* UpdateServiceFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<UpdateService*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+UpdateServiceFactory* UpdateServiceFactory::GetInstance() {
+ return base::Singleton<UpdateServiceFactory>::get();
+}
+
+UpdateServiceFactory::UpdateServiceFactory()
+ : BrowserContextKeyedServiceFactory(
+ "UpdateService",
+ BrowserContextDependencyManager::GetInstance()) {
+}
+
+UpdateServiceFactory::~UpdateServiceFactory() {
+}
+
+KeyedService* UpdateServiceFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ return new UpdateService(
+ context, ExtensionsBrowserClient::Get()->CreateUpdateClient(context));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/updater/update_service_factory.h b/chromium/extensions/browser/updater/update_service_factory.h
new file mode 100644
index 00000000000..521070a07e5
--- /dev/null
+++ b/chromium/extensions/browser/updater/update_service_factory.h
@@ -0,0 +1,38 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_UPDATER_UPDATE_SERVICE_FACTORY_H_
+#define EXTENSIONS_BROWSER_UPDATER_UPDATE_SERVICE_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace extensions {
+
+class UpdateService;
+
+// Service factory to construct UpdateService instances per BrowserContext.
+// Note that OTR browser contexts do not get an UpdateService.
+class UpdateServiceFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static UpdateService* GetForBrowserContext(content::BrowserContext* context);
+ static UpdateServiceFactory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<UpdateServiceFactory>;
+
+ UpdateServiceFactory();
+ ~UpdateServiceFactory() override;
+
+ // BrowserContextKeyedServiceFactory:
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateServiceFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_UPDATER_UPDATE_SERVICE_FACTORY_H_
diff --git a/chromium/extensions/browser/updater/update_service_unittest.cc b/chromium/extensions/browser/updater/update_service_unittest.cc
new file mode 100644
index 00000000000..eb8213ad6dd
--- /dev/null
+++ b/chromium/extensions/browser/updater/update_service_unittest.cc
@@ -0,0 +1,351 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <vector>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "base/values.h"
+#include "components/crx_file/id_util.h"
+#include "components/update_client/update_client.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "content/public/test/test_utils.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extensions_test.h"
+#include "extensions/browser/mock_extension_system.h"
+#include "extensions/browser/test_extensions_browser_client.h"
+#include "extensions/browser/uninstall_ping_sender.h"
+#include "extensions/browser/updater/update_service.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/test_util.h"
+#include "extensions/common/value_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class FakeUpdateClient : public update_client::UpdateClient {
+ public:
+ FakeUpdateClient();
+
+ // Returns the data we've gotten from the CrxDataCallback for ids passed to
+ // the Update function.
+ std::vector<update_client::CrxComponent>* data() { return &data_; }
+
+ // Used for tests that uninstall pings get requested properly.
+ struct UninstallPing {
+ std::string id;
+ Version version;
+ int reason;
+ UninstallPing(const std::string& id, const Version& version, int reason)
+ : id(id), version(version), reason(reason) {}
+ };
+ std::vector<UninstallPing>& uninstall_pings() { return uninstall_pings_; }
+
+ // update_client::UpdateClient
+ void AddObserver(Observer* observer) override {}
+ void RemoveObserver(Observer* observer) override {}
+ void Install(const std::string& id,
+ const CrxDataCallback& crx_data_callback,
+ const CompletionCallback& completion_callback) override {}
+ void Update(const std::vector<std::string>& ids,
+ const CrxDataCallback& crx_data_callback,
+ const CompletionCallback& completion_callback) override;
+ bool GetCrxUpdateState(
+ const std::string& id,
+ update_client::CrxUpdateItem* update_item) const override {
+ return false;
+ }
+ bool IsUpdating(const std::string& id) const override { return false; }
+ void Stop() override {}
+ void SendUninstallPing(const std::string& id,
+ const Version& version,
+ int reason) override {
+ uninstall_pings_.emplace_back(id, version, reason);
+ }
+
+ protected:
+ friend class base::RefCounted<FakeUpdateClient>;
+ ~FakeUpdateClient() override {}
+
+ std::vector<update_client::CrxComponent> data_;
+ std::vector<UninstallPing> uninstall_pings_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FakeUpdateClient);
+};
+
+FakeUpdateClient::FakeUpdateClient() {}
+
+void FakeUpdateClient::Update(const std::vector<std::string>& ids,
+ const CrxDataCallback& crx_data_callback,
+ const CompletionCallback& completion_callback) {
+ crx_data_callback.Run(ids, &data_);
+}
+
+} // namespace
+
+namespace extensions {
+
+namespace {
+
+// A global variable for controlling whether uninstalls should cause uninstall
+// pings to be sent.
+UninstallPingSender::FilterResult g_should_ping =
+ UninstallPingSender::DO_NOT_SEND_PING;
+
+// Helper method to serve as an uninstall ping filter.
+UninstallPingSender::FilterResult ShouldPing(const Extension* extension,
+ UninstallReason reason) {
+ return g_should_ping;
+}
+
+// A fake ExtensionSystem that lets us intercept calls to install new
+// versions of an extension.
+class FakeExtensionSystem : public MockExtensionSystem {
+ public:
+ explicit FakeExtensionSystem(content::BrowserContext* context)
+ : MockExtensionSystem(context) {}
+ ~FakeExtensionSystem() override {}
+
+ struct InstallUpdateRequest {
+ std::string extension_id;
+ base::FilePath temp_dir;
+ };
+
+ std::vector<InstallUpdateRequest>* install_requests() {
+ return &install_requests_;
+ }
+
+ void set_install_callback(const base::Closure& callback) {
+ next_install_callback_ = callback;
+ }
+
+ // ExtensionSystem override
+ void InstallUpdate(const std::string& extension_id,
+ const base::FilePath& temp_dir) override {
+ base::DeleteFile(temp_dir, true /*recursive*/);
+ InstallUpdateRequest request;
+ request.extension_id = extension_id;
+ request.temp_dir = temp_dir;
+ install_requests_.push_back(request);
+ if (!next_install_callback_.is_null()) {
+ base::Closure tmp = next_install_callback_;
+ next_install_callback_.Reset();
+ tmp.Run();
+ }
+ }
+
+ private:
+ std::vector<InstallUpdateRequest> install_requests_;
+ base::Closure next_install_callback_;
+};
+
+class UpdateServiceTest : public ExtensionsTest {
+ public:
+ UpdateServiceTest() {
+ extensions_browser_client()->set_extension_system_factory(
+ &fake_extension_system_factory_);
+ }
+ ~UpdateServiceTest() override {}
+
+ void SetUp() override {
+ ExtensionsTest::SetUp();
+ browser_threads_.reset(new content::TestBrowserThreadBundle(
+ content::TestBrowserThreadBundle::DEFAULT));
+
+ extensions_browser_client()->SetUpdateClientFactory(base::Bind(
+ &UpdateServiceTest::CreateUpdateClient, base::Unretained(this)));
+
+ update_service_ = UpdateService::Get(browser_context());
+ }
+
+ protected:
+ UpdateService* update_service() const { return update_service_; }
+ FakeUpdateClient* update_client() const { return update_client_.get(); }
+
+ update_client::UpdateClient* CreateUpdateClient() {
+ // We only expect that this will get called once, so consider it an error
+ // if our update_client_ is already non-null.
+ EXPECT_EQ(nullptr, update_client_.get());
+ update_client_ = new FakeUpdateClient();
+ return update_client_.get();
+ }
+
+ // Helper function that creates a file at |relative_path| within |directory|
+ // and fills it with |content|.
+ bool AddFileToDirectory(const base::FilePath& directory,
+ const base::FilePath& relative_path,
+ const std::string& content) {
+ base::FilePath full_path = directory.Append(relative_path);
+ if (!CreateDirectory(full_path.DirName()))
+ return false;
+ int result = base::WriteFile(full_path, content.data(), content.size());
+ return (static_cast<size_t>(result) == content.size());
+ }
+
+ FakeExtensionSystem* extension_system() {
+ return static_cast<FakeExtensionSystem*>(
+ fake_extension_system_factory_.GetForBrowserContext(browser_context()));
+ }
+
+ private:
+ UpdateService* update_service_;
+ scoped_refptr<FakeUpdateClient> update_client_;
+ scoped_ptr<content::TestBrowserThreadBundle> browser_threads_;
+ MockExtensionSystemFactory<FakeExtensionSystem>
+ fake_extension_system_factory_;
+};
+
+TEST_F(UpdateServiceTest, BasicUpdateOperations) {
+ // Create a temporary directory that a fake extension will live in and fill
+ // it with some test files.
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath foo_js(FILE_PATH_LITERAL("foo.js"));
+ base::FilePath bar_html(FILE_PATH_LITERAL("bar/bar.html"));
+ ASSERT_TRUE(AddFileToDirectory(temp_dir.path(), foo_js, "hello"))
+ << "Failed to write " << temp_dir.path().value() << "/" << foo_js.value();
+ ASSERT_TRUE(AddFileToDirectory(temp_dir.path(), bar_html, "world"));
+
+ ExtensionBuilder builder;
+ builder.SetManifest(DictionaryBuilder()
+ .Set("name", "Foo")
+ .Set("version", "1.0")
+ .Set("manifest_version", 2)
+ .Build());
+ builder.SetID(crx_file::id_util::GenerateId("whatever"));
+ builder.SetPath(temp_dir.path());
+
+ scoped_refptr<Extension> extension1(builder.Build());
+
+ ExtensionRegistry::Get(browser_context())->AddEnabled(extension1);
+ std::vector<std::string> ids;
+ ids.push_back(extension1->id());
+
+ // Start an update check and verify that the UpdateClient was sent the right
+ // data.
+ update_service()->StartUpdateCheck(ids);
+ std::vector<update_client::CrxComponent>* data = update_client()->data();
+ ASSERT_NE(nullptr, data);
+ ASSERT_EQ(1u, data->size());
+
+ ASSERT_EQ(data->at(0).version, *extension1->version());
+ update_client::CrxInstaller* installer = data->at(0).installer.get();
+ ASSERT_NE(installer, nullptr);
+
+ // The GetInstalledFile method is used when processing differential updates
+ // to get a path to an existing file in an extension. We want to test a
+ // number of scenarios to be user we handle invalid relative paths, don't
+ // accidentally return paths outside the extension's dir, etc.
+ base::FilePath tmp;
+ EXPECT_TRUE(installer->GetInstalledFile(foo_js.MaybeAsASCII(), &tmp));
+ EXPECT_EQ(temp_dir.path().Append(foo_js), tmp) << tmp.value();
+
+ EXPECT_TRUE(installer->GetInstalledFile(bar_html.MaybeAsASCII(), &tmp));
+ EXPECT_EQ(temp_dir.path().Append(bar_html), tmp) << tmp.value();
+
+ EXPECT_FALSE(installer->GetInstalledFile("does_not_exist", &tmp));
+ EXPECT_FALSE(installer->GetInstalledFile("does/not/exist", &tmp));
+ EXPECT_FALSE(installer->GetInstalledFile("/does/not/exist", &tmp));
+ EXPECT_FALSE(installer->GetInstalledFile("C:\\tmp", &tmp));
+
+ base::FilePath system_temp_dir;
+ ASSERT_TRUE(base::GetTempDir(&system_temp_dir));
+ EXPECT_FALSE(
+ installer->GetInstalledFile(system_temp_dir.MaybeAsASCII(), &tmp));
+
+ // Test the install callback.
+ base::ScopedTempDir new_version_dir;
+ ASSERT_TRUE(new_version_dir.CreateUniqueTempDir());
+ scoped_ptr<base::DictionaryValue> new_manifest(
+ extension1->manifest()->value()->DeepCopy());
+ new_manifest->SetString("version", "2.0");
+
+ installer->Install(*new_manifest, new_version_dir.path());
+
+ scoped_refptr<content::MessageLoopRunner> loop_runner =
+ new content::MessageLoopRunner();
+ extension_system()->set_install_callback(loop_runner->QuitClosure());
+ loop_runner->Run();
+
+ std::vector<FakeExtensionSystem::InstallUpdateRequest>* requests =
+ extension_system()->install_requests();
+ ASSERT_EQ(1u, requests->size());
+ EXPECT_EQ(requests->at(0).extension_id, extension1->id());
+ EXPECT_NE(requests->at(0).temp_dir.value(), new_version_dir.path().value());
+}
+
+TEST_F(UpdateServiceTest, UninstallPings) {
+ UninstallPingSender sender(ExtensionRegistry::Get(browser_context()),
+ base::Bind(&ShouldPing));
+
+ // Build 3 extensions.
+ scoped_refptr<Extension> extension1 =
+ test_util::BuildExtension(ExtensionBuilder())
+ .SetID(crx_file::id_util::GenerateId("1"))
+ .MergeManifest(DictionaryBuilder().Set("version", "1.2").Build())
+ .Build();
+ scoped_refptr<Extension> extension2 =
+ test_util::BuildExtension(ExtensionBuilder())
+ .SetID(crx_file::id_util::GenerateId("2"))
+ .MergeManifest(DictionaryBuilder().Set("version", "2.3").Build())
+ .Build();
+ scoped_refptr<Extension> extension3 =
+ test_util::BuildExtension(ExtensionBuilder())
+ .SetID(crx_file::id_util::GenerateId("3"))
+ .MergeManifest(DictionaryBuilder().Set("version", "3.4").Build())
+ .Build();
+ EXPECT_TRUE(extension1->id() != extension2->id() &&
+ extension1->id() != extension3->id() &&
+ extension2->id() != extension3->id());
+
+ ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
+
+ // Run tests for each uninstall reason.
+ for (int reason_val = static_cast<int>(UNINSTALL_REASON_FOR_TESTING);
+ reason_val < static_cast<int>(UNINSTALL_REASON_MAX); ++reason_val) {
+ UninstallReason reason = static_cast<UninstallReason>(reason_val);
+
+ // Start with 2 enabled and 1 disabled extensions.
+ EXPECT_TRUE(registry->AddEnabled(extension1)) << reason;
+ EXPECT_TRUE(registry->AddEnabled(extension2)) << reason;
+ EXPECT_TRUE(registry->AddDisabled(extension3)) << reason;
+
+ // Uninstall the first extension, instructing our filter not to send pings,
+ // and verify none were sent.
+ g_should_ping = UninstallPingSender::DO_NOT_SEND_PING;
+ EXPECT_TRUE(registry->RemoveEnabled(extension1->id())) << reason;
+ registry->TriggerOnUninstalled(extension1.get(), reason);
+ EXPECT_TRUE(update_client()->uninstall_pings().empty()) << reason;
+
+ // Uninstall the second and third extensions, instructing the filter to
+ // send pings, and make sure we got the expected data.
+ g_should_ping = UninstallPingSender::SEND_PING;
+ EXPECT_TRUE(registry->RemoveEnabled(extension2->id())) << reason;
+ registry->TriggerOnUninstalled(extension2.get(), reason);
+ EXPECT_TRUE(registry->RemoveDisabled(extension3->id())) << reason;
+ registry->TriggerOnUninstalled(extension3.get(), reason);
+
+ std::vector<FakeUpdateClient::UninstallPing>& pings =
+ update_client()->uninstall_pings();
+ ASSERT_EQ(2u, pings.size()) << reason;
+
+ EXPECT_EQ(extension2->id(), pings[0].id) << reason;
+ EXPECT_EQ(*extension2->version(), pings[0].version) << reason;
+ EXPECT_EQ(reason, pings[0].reason) << reason;
+
+ EXPECT_EQ(extension3->id(), pings[1].id) << reason;
+ EXPECT_EQ(*extension3->version(), pings[1].version) << reason;
+ EXPECT_EQ(reason, pings[1].reason) << reason;
+
+ pings.clear();
+ }
+}
+
+} // namespace
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/url_request_util.cc b/chromium/extensions/browser/url_request_util.cc
new file mode 100644
index 00000000000..c7321eb5f08
--- /dev/null
+++ b/chromium/extensions/browser/url_request_util.cc
@@ -0,0 +1,112 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/url_request_util.h"
+
+#include <string>
+
+#include "content/public/browser/resource_request_info.h"
+#include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
+#include "extensions/browser/info_map.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handlers/icons_handler.h"
+#include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
+#include "extensions/common/manifest_handlers/webview_info.h"
+#include "net/url_request/url_request.h"
+
+namespace extensions {
+namespace url_request_util {
+
+bool AllowCrossRendererResourceLoad(net::URLRequest* request,
+ bool is_incognito,
+ const Extension* extension,
+ InfoMap* extension_info_map,
+ bool* allowed) {
+ const content::ResourceRequestInfo* info =
+ content::ResourceRequestInfo::ForRequest(request);
+
+ // Extensions with webview: allow loading certain resources by guest renderers
+ // with privileged partition IDs as specified in owner's extension the
+ // manifest file.
+ std::string owner_extension_id;
+ int owner_process_id;
+ WebViewRendererState::GetInstance()->GetOwnerInfo(
+ info->GetChildID(), &owner_process_id, &owner_extension_id);
+ const Extension* owner_extension =
+ extension_info_map->extensions().GetByID(owner_extension_id);
+ std::string partition_id;
+ bool is_guest = WebViewRendererState::GetInstance()->GetPartitionID(
+ info->GetChildID(), &partition_id);
+ std::string resource_path = request->url().path();
+ // |owner_extension == extension| needs to be checked because extension
+ // resources should only be accessible to WebViews owned by that extension.
+ if (is_guest && owner_extension == extension &&
+ WebviewInfo::IsResourceWebviewAccessible(extension, partition_id,
+ resource_path)) {
+ *allowed = true;
+ return true;
+ }
+
+ // If the request is for navigations outside of webviews, then it should be
+ // allowed. The navigation logic in CrossSiteResourceHandler will properly
+ // transfer the navigation to a privileged process before it commits.
+ if (content::IsResourceTypeFrame(info->GetResourceType()) && !is_guest) {
+ *allowed = true;
+ return true;
+ }
+
+ if (!ui::PageTransitionIsWebTriggerable(info->GetPageTransition())) {
+ *allowed = false;
+ return true;
+ }
+
+ // The following checks require that we have an actual extension object. If we
+ // don't have it, allow the request handling to continue with the rest of the
+ // checks.
+ if (!extension) {
+ *allowed = true;
+ return true;
+ }
+
+ // Disallow loading of packaged resources for hosted apps. We don't allow
+ // hybrid hosted/packaged apps. The one exception is access to icons, since
+ // some extensions want to be able to do things like create their own
+ // launchers.
+ std::string resource_root_relative_path =
+ request->url().path().empty() ? std::string()
+ : request->url().path().substr(1);
+ if (extension->is_hosted_app() &&
+ !IconsInfo::GetIcons(extension)
+ .ContainsPath(resource_root_relative_path)) {
+ LOG(ERROR) << "Denying load of " << request->url().spec() << " from "
+ << "hosted app.";
+ *allowed = false;
+ return true;
+ }
+
+ // Extensions with web_accessible_resources: allow loading by regular
+ // renderers. Since not all subresources are required to be listed in a v2
+ // manifest, we must allow all loads if there are any web accessible
+ // resources. See http://crbug.com/179127.
+ if (extension->manifest_version() < 2 ||
+ WebAccessibleResourcesInfo::HasWebAccessibleResources(extension)) {
+ *allowed = true;
+ return true;
+ }
+
+ // Couldn't determine if the resource is allowed or not.
+ return false;
+}
+
+bool IsWebViewRequest(const net::URLRequest* request) {
+ const content::ResourceRequestInfo* info =
+ content::ResourceRequestInfo::ForRequest(request);
+ // |info| can be NULL sometimes: http://crbug.com/370070.
+ if (!info)
+ return false;
+ return WebViewRendererState::GetInstance()->IsGuest(info->GetChildID());
+}
+
+} // namespace url_request_util
+} // namespace extensions
diff --git a/chromium/extensions/browser/url_request_util.h b/chromium/extensions/browser/url_request_util.h
new file mode 100644
index 00000000000..afa0edb5a86
--- /dev/null
+++ b/chromium/extensions/browser/url_request_util.h
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_URL_REQUEST_UTIL_H_
+#define EXTENSIONS_BROWSER_URL_REQUEST_UTIL_H_
+
+namespace net {
+class URLRequest;
+}
+
+namespace extensions {
+class Extension;
+class InfoMap;
+
+// Utilities related to URLRequest jobs for extension resources. See
+// chrome/browser/extensions/extension_protocols_unittest.cc for related tests.
+namespace url_request_util {
+
+// Sets allowed=true to allow a chrome-extension:// resource request coming from
+// renderer A to access a resource in an extension running in renderer B.
+// Returns false when it couldn't determine if the resource is allowed or not
+bool AllowCrossRendererResourceLoad(net::URLRequest* request,
+ bool is_incognito,
+ const Extension* extension,
+ InfoMap* extension_info_map,
+ bool* allowed);
+
+// Returns true if |request| corresponds to a resource request from a
+// <webview>.
+bool IsWebViewRequest(const net::URLRequest* request);
+
+} // namespace url_request_util
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_URL_REQUEST_UTIL_H_
diff --git a/chromium/extensions/browser/user_script_loader.cc b/chromium/extensions/browser/user_script_loader.cc
new file mode 100644
index 00000000000..91af052718e
--- /dev/null
+++ b/chromium/extensions/browser/user_script_loader.cc
@@ -0,0 +1,409 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/user_script_loader.h"
+
+#include <stddef.h>
+
+#include <set>
+#include <string>
+#include <utility>
+
+#include "base/version.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_process_host.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/notification_types.h"
+#include "extensions/common/extension_messages.h"
+
+using content::BrowserThread;
+using content::BrowserContext;
+
+namespace extensions {
+
+namespace {
+
+// Helper function to parse greasesmonkey headers
+bool GetDeclarationValue(const base::StringPiece& line,
+ const base::StringPiece& prefix,
+ std::string* value) {
+ base::StringPiece::size_type index = line.find(prefix);
+ if (index == base::StringPiece::npos)
+ return false;
+
+ std::string temp(line.data() + index + prefix.length(),
+ line.length() - index - prefix.length());
+
+ if (temp.empty() || !base::IsUnicodeWhitespace(temp[0]))
+ return false;
+
+ base::TrimWhitespaceASCII(temp, base::TRIM_ALL, value);
+ return true;
+}
+
+} // namespace
+
+// static
+bool UserScriptLoader::ParseMetadataHeader(const base::StringPiece& script_text,
+ UserScript* script) {
+ // http://wiki.greasespot.net/Metadata_block
+ base::StringPiece line;
+ size_t line_start = 0;
+ size_t line_end = line_start;
+ bool in_metadata = false;
+
+ static const base::StringPiece kUserScriptBegin("// ==UserScript==");
+ static const base::StringPiece kUserScriptEng("// ==/UserScript==");
+ static const base::StringPiece kNamespaceDeclaration("// @namespace");
+ static const base::StringPiece kNameDeclaration("// @name");
+ static const base::StringPiece kVersionDeclaration("// @version");
+ static const base::StringPiece kDescriptionDeclaration("// @description");
+ static const base::StringPiece kIncludeDeclaration("// @include");
+ static const base::StringPiece kExcludeDeclaration("// @exclude");
+ static const base::StringPiece kMatchDeclaration("// @match");
+ static const base::StringPiece kExcludeMatchDeclaration("// @exclude_match");
+ static const base::StringPiece kRunAtDeclaration("// @run-at");
+ static const base::StringPiece kRunAtDocumentStartValue("document-start");
+ static const base::StringPiece kRunAtDocumentEndValue("document-end");
+ static const base::StringPiece kRunAtDocumentIdleValue("document-idle");
+
+ while (line_start < script_text.length()) {
+ line_end = script_text.find('\n', line_start);
+
+ // Handle the case where there is no trailing newline in the file.
+ if (line_end == std::string::npos)
+ line_end = script_text.length() - 1;
+
+ line.set(script_text.data() + line_start, line_end - line_start);
+
+ if (!in_metadata) {
+ if (line.starts_with(kUserScriptBegin))
+ in_metadata = true;
+ } else {
+ if (line.starts_with(kUserScriptEng))
+ break;
+
+ std::string value;
+ if (GetDeclarationValue(line, kIncludeDeclaration, &value)) {
+ // We escape some characters that MatchPattern() considers special.
+ base::ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\");
+ base::ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?");
+ script->add_glob(value);
+ } else if (GetDeclarationValue(line, kExcludeDeclaration, &value)) {
+ base::ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\");
+ base::ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?");
+ script->add_exclude_glob(value);
+ } else if (GetDeclarationValue(line, kNamespaceDeclaration, &value)) {
+ script->set_name_space(value);
+ } else if (GetDeclarationValue(line, kNameDeclaration, &value)) {
+ script->set_name(value);
+ } else if (GetDeclarationValue(line, kVersionDeclaration, &value)) {
+ Version version(value);
+ if (version.IsValid())
+ script->set_version(version.GetString());
+ } else if (GetDeclarationValue(line, kDescriptionDeclaration, &value)) {
+ script->set_description(value);
+ } else if (GetDeclarationValue(line, kMatchDeclaration, &value)) {
+ URLPattern pattern(UserScript::ValidUserScriptSchemes());
+ if (URLPattern::PARSE_SUCCESS != pattern.Parse(value))
+ return false;
+ script->add_url_pattern(pattern);
+ } else if (GetDeclarationValue(line, kExcludeMatchDeclaration, &value)) {
+ URLPattern exclude(UserScript::ValidUserScriptSchemes());
+ if (URLPattern::PARSE_SUCCESS != exclude.Parse(value))
+ return false;
+ script->add_exclude_url_pattern(exclude);
+ } else if (GetDeclarationValue(line, kRunAtDeclaration, &value)) {
+ if (value == kRunAtDocumentStartValue)
+ script->set_run_location(UserScript::DOCUMENT_START);
+ else if (value == kRunAtDocumentEndValue)
+ script->set_run_location(UserScript::DOCUMENT_END);
+ else if (value == kRunAtDocumentIdleValue)
+ script->set_run_location(UserScript::DOCUMENT_IDLE);
+ else
+ return false;
+ }
+
+ // TODO(aa): Handle more types of metadata.
+ }
+
+ line_start = line_end + 1;
+ }
+
+ // If no patterns were specified, default to @include *. This is what
+ // Greasemonkey does.
+ if (script->globs().empty() && script->url_patterns().is_empty())
+ script->add_glob("*");
+
+ return true;
+}
+
+UserScriptLoader::UserScriptLoader(BrowserContext* browser_context,
+ const HostID& host_id)
+ : user_scripts_(new UserScriptList()),
+ clear_scripts_(false),
+ ready_(false),
+ pending_load_(false),
+ browser_context_(browser_context),
+ host_id_(host_id),
+ weak_factory_(this) {
+ registrar_.Add(this,
+ content::NOTIFICATION_RENDERER_PROCESS_CREATED,
+ content::NotificationService::AllBrowserContextsAndSources());
+}
+
+UserScriptLoader::~UserScriptLoader() {
+ FOR_EACH_OBSERVER(Observer, observers_, OnUserScriptLoaderDestroyed(this));
+}
+
+void UserScriptLoader::AddScripts(const std::set<UserScript>& scripts) {
+ for (std::set<UserScript>::const_iterator it = scripts.begin();
+ it != scripts.end();
+ ++it) {
+ removed_scripts_.erase(*it);
+ added_scripts_.insert(*it);
+ }
+ AttemptLoad();
+}
+
+void UserScriptLoader::AddScripts(const std::set<UserScript>& scripts,
+ int render_process_id,
+ int render_view_id) {
+ AddScripts(scripts);
+}
+
+void UserScriptLoader::RemoveScripts(const std::set<UserScript>& scripts) {
+ for (std::set<UserScript>::const_iterator it = scripts.begin();
+ it != scripts.end();
+ ++it) {
+ added_scripts_.erase(*it);
+ removed_scripts_.insert(*it);
+ }
+ AttemptLoad();
+}
+
+void UserScriptLoader::ClearScripts() {
+ clear_scripts_ = true;
+ added_scripts_.clear();
+ removed_scripts_.clear();
+ AttemptLoad();
+}
+
+void UserScriptLoader::Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ DCHECK_EQ(type, content::NOTIFICATION_RENDERER_PROCESS_CREATED);
+ content::RenderProcessHost* process =
+ content::Source<content::RenderProcessHost>(source).ptr();
+ if (!ExtensionsBrowserClient::Get()->IsSameContext(
+ browser_context_, process->GetBrowserContext()))
+ return;
+ if (scripts_ready()) {
+ SendUpdate(process, shared_memory_.get(),
+ std::set<HostID>()); // Include all hosts.
+ }
+}
+
+bool UserScriptLoader::ScriptsMayHaveChanged() const {
+ // Scripts may have changed if there are scripts added, scripts removed, or
+ // if scripts were cleared and either:
+ // (1) A load is in progress (which may result in a non-zero number of
+ // scripts that need to be cleared), or
+ // (2) The current set of scripts is non-empty (so they need to be cleared).
+ return (added_scripts_.size() ||
+ removed_scripts_.size() ||
+ (clear_scripts_ &&
+ (is_loading() || user_scripts_->size())));
+}
+
+void UserScriptLoader::AttemptLoad() {
+ if (ready_ && ScriptsMayHaveChanged()) {
+ if (is_loading())
+ pending_load_ = true;
+ else
+ StartLoad();
+ }
+}
+
+void UserScriptLoader::StartLoad() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK(!is_loading());
+
+ // If scripts were marked for clearing before adding and removing, then clear
+ // them.
+ if (clear_scripts_) {
+ user_scripts_->clear();
+ } else {
+ for (UserScriptList::iterator it = user_scripts_->begin();
+ it != user_scripts_->end();) {
+ if (removed_scripts_.count(*it))
+ it = user_scripts_->erase(it);
+ else
+ ++it;
+ }
+ }
+
+ user_scripts_->insert(
+ user_scripts_->end(), added_scripts_.begin(), added_scripts_.end());
+
+ std::set<int> added_script_ids;
+ for (std::set<UserScript>::const_iterator it = added_scripts_.begin();
+ it != added_scripts_.end();
+ ++it) {
+ added_script_ids.insert(it->id());
+ }
+
+ // Expand |changed_hosts_| for OnScriptsLoaded, which will use it in
+ // its IPC message. This must be done before we clear |added_scripts_| and
+ // |removed_scripts_| below.
+ std::set<UserScript> changed_scripts(added_scripts_);
+ changed_scripts.insert(removed_scripts_.begin(), removed_scripts_.end());
+ for (const UserScript& script : changed_scripts)
+ changed_hosts_.insert(script.host_id());
+
+ LoadScripts(std::move(user_scripts_), changed_hosts_, added_script_ids,
+ base::Bind(&UserScriptLoader::OnScriptsLoaded,
+ weak_factory_.GetWeakPtr()));
+
+ clear_scripts_ = false;
+ added_scripts_.clear();
+ removed_scripts_.clear();
+ user_scripts_.reset();
+}
+
+// static
+scoped_ptr<base::SharedMemory> UserScriptLoader::Serialize(
+ const UserScriptList& scripts) {
+ base::Pickle pickle;
+ pickle.WriteUInt32(scripts.size());
+ for (const UserScript& script : scripts) {
+ // TODO(aa): This can be replaced by sending content script metadata to
+ // renderers along with other extension data in ExtensionMsg_Loaded.
+ // See crbug.com/70516.
+ script.Pickle(&pickle);
+ // Write scripts as 'data' so that we can read it out in the slave without
+ // allocating a new string.
+ for (const UserScript::File& script_file : script.js_scripts()) {
+ base::StringPiece contents = script_file.GetContent();
+ pickle.WriteData(contents.data(), contents.length());
+ }
+ for (const UserScript::File& script_file : script.css_scripts()) {
+ base::StringPiece contents = script_file.GetContent();
+ pickle.WriteData(contents.data(), contents.length());
+ }
+ }
+
+ // Create the shared memory object.
+ base::SharedMemory shared_memory;
+
+ base::SharedMemoryCreateOptions options;
+ options.size = pickle.size();
+ options.share_read_only = true;
+ if (!shared_memory.Create(options))
+ return scoped_ptr<base::SharedMemory>();
+
+ if (!shared_memory.Map(pickle.size()))
+ return scoped_ptr<base::SharedMemory>();
+
+ // Copy the pickle to shared memory.
+ memcpy(shared_memory.memory(), pickle.data(), pickle.size());
+
+ base::SharedMemoryHandle readonly_handle;
+ if (!shared_memory.ShareReadOnlyToProcess(base::GetCurrentProcessHandle(),
+ &readonly_handle))
+ return scoped_ptr<base::SharedMemory>();
+
+ return make_scoped_ptr(new base::SharedMemory(readonly_handle,
+ /*read_only=*/true));
+}
+
+void UserScriptLoader::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void UserScriptLoader::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void UserScriptLoader::SetReady(bool ready) {
+ bool was_ready = ready_;
+ ready_ = ready;
+ if (ready_ && !was_ready)
+ AttemptLoad();
+}
+
+void UserScriptLoader::OnScriptsLoaded(
+ scoped_ptr<UserScriptList> user_scripts,
+ scoped_ptr<base::SharedMemory> shared_memory) {
+ user_scripts_.reset(user_scripts.release());
+ if (pending_load_) {
+ // While we were loading, there were further changes. Don't bother
+ // notifying about these scripts and instead just immediately reload.
+ pending_load_ = false;
+ StartLoad();
+ return;
+ }
+
+ if (shared_memory.get() == NULL) {
+ // This can happen if we run out of file descriptors. In that case, we
+ // have a choice between silently omitting all user scripts for new tabs,
+ // by nulling out shared_memory_, or only silently omitting new ones by
+ // leaving the existing object in place. The second seems less bad, even
+ // though it removes the possibility that freeing the shared memory block
+ // would open up enough FDs for long enough for a retry to succeed.
+
+ // Pretend the extension change didn't happen.
+ return;
+ }
+
+ // We've got scripts ready to go.
+ shared_memory_.reset(shared_memory.release());
+
+ for (content::RenderProcessHost::iterator i(
+ content::RenderProcessHost::AllHostsIterator());
+ !i.IsAtEnd(); i.Advance()) {
+ SendUpdate(i.GetCurrentValue(), shared_memory_.get(), changed_hosts_);
+ }
+ changed_hosts_.clear();
+
+ // TODO(hanxi): Remove the NOTIFICATION_USER_SCRIPTS_UPDATED.
+ content::NotificationService::current()->Notify(
+ extensions::NOTIFICATION_USER_SCRIPTS_UPDATED,
+ content::Source<BrowserContext>(browser_context_),
+ content::Details<base::SharedMemory>(shared_memory_.get()));
+ FOR_EACH_OBSERVER(Observer, observers_, OnScriptsLoaded(this));
+}
+
+void UserScriptLoader::SendUpdate(content::RenderProcessHost* process,
+ base::SharedMemory* shared_memory,
+ const std::set<HostID>& changed_hosts) {
+ // Don't allow injection of non-whitelisted extensions' content scripts
+ // into <webview>.
+ bool whitelisted_only = process->IsForGuestsOnly() && host_id().id().empty();
+
+ // Make sure we only send user scripts to processes in our browser_context.
+ if (!ExtensionsBrowserClient::Get()->IsSameContext(
+ browser_context_, process->GetBrowserContext()))
+ return;
+
+ // If the process is being started asynchronously, early return. We'll end up
+ // calling InitUserScripts when it's created which will call this again.
+ base::ProcessHandle handle = process->GetHandle();
+ if (!handle)
+ return;
+
+ base::SharedMemoryHandle handle_for_process;
+ if (!shared_memory->ShareToProcess(handle, &handle_for_process))
+ return; // This can legitimately fail if the renderer asserts at startup.
+
+ if (base::SharedMemory::IsHandleValid(handle_for_process)) {
+ process->Send(new ExtensionMsg_UpdateUserScripts(
+ handle_for_process, host_id(), changed_hosts, whitelisted_only));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/user_script_loader.h b/chromium/extensions/browser/user_script_loader.h
new file mode 100644
index 00000000000..ff6772aa9b0
--- /dev/null
+++ b/chromium/extensions/browser/user_script_loader.h
@@ -0,0 +1,185 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_USER_SCRIPT_LOADER_H_
+#define EXTENSIONS_BROWSER_USER_SCRIPT_LOADER_H_
+
+#include <map>
+#include <set>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/shared_memory.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/scoped_observer.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "extensions/common/host_id.h"
+#include "extensions/common/user_script.h"
+
+namespace base {
+class SharedMemory;
+}
+
+namespace content {
+class BrowserContext;
+class RenderProcessHost;
+}
+
+namespace extensions {
+
+// Manages one "logical unit" of user scripts in shared memory by constructing a
+// new shared memory region when the set of scripts changes. Also notifies
+// renderers of new shared memory region when new renderers appear, or when
+// script reloading completes. Script loading lives on the UI thread. Instances
+// of this class are embedded within classes with names ending in
+// UserScriptMaster. These "master" classes implement the strategy for which
+// scripts to load/unload on this logical unit of scripts.
+class UserScriptLoader : public content::NotificationObserver {
+ public:
+ using LoadScriptsCallback =
+ base::Callback<void(scoped_ptr<UserScriptList>,
+ scoped_ptr<base::SharedMemory>)>;
+ class Observer {
+ public:
+ virtual void OnScriptsLoaded(UserScriptLoader* loader) = 0;
+ virtual void OnUserScriptLoaderDestroyed(UserScriptLoader* loader) = 0;
+ };
+
+ // Parses the includes out of |script| and returns them in |includes|.
+ static bool ParseMetadataHeader(const base::StringPiece& script_text,
+ UserScript* script);
+
+ UserScriptLoader(content::BrowserContext* browser_context,
+ const HostID& host_id);
+ ~UserScriptLoader() override;
+
+ // Add |scripts| to the set of scripts managed by this loader.
+ void AddScripts(const std::set<UserScript>& scripts);
+
+ // Add |scripts| to the set of scripts managed by this loader.
+ // The fetch of the content of the script starts URL request
+ // to the associated render specified by
+ // |render_process_id, render_view_id|.
+ // TODO(hanxi): The renderer information doesn't really belong in this base
+ // class, but it's not an easy fix.
+ virtual void AddScripts(const std::set<UserScript>& scripts,
+ int render_process_id,
+ int render_view_id);
+
+ // Remove |scripts| from the set of scripts managed by this loader.
+ void RemoveScripts(const std::set<UserScript>& scripts);
+
+ // Clears the set of scripts managed by this loader.
+ void ClearScripts();
+
+ // Initiates procedure to start loading scripts on the file thread.
+ void StartLoad();
+
+ // Returns true if we have any scripts ready.
+ bool scripts_ready() const { return shared_memory_.get() != NULL; }
+
+ // Pickle user scripts and return pointer to the shared memory.
+ static scoped_ptr<base::SharedMemory> Serialize(
+ const extensions::UserScriptList& scripts);
+
+ // Adds or removes observers.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ protected:
+ // Allows the derived classes have different ways to load user scripts.
+ virtual void LoadScripts(scoped_ptr<UserScriptList> user_scripts,
+ const std::set<HostID>& changed_hosts,
+ const std::set<int>& added_script_ids,
+ LoadScriptsCallback callback) = 0;
+
+ // Sets the flag if the initial set of hosts has finished loading; if it's
+ // set to be true, calls AttempLoad() to bootstrap.
+ void SetReady(bool ready);
+
+ content::BrowserContext* browser_context() const { return browser_context_; }
+ const HostID& host_id() const { return host_id_; }
+
+ private:
+ // content::NotificationObserver implementation.
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override;
+
+ // Returns whether or not it is possible that calls to AddScripts(),
+ // RemoveScripts(), and/or ClearScripts() have caused any real change in the
+ // set of scripts to be loaded.
+ bool ScriptsMayHaveChanged() const;
+
+ // Attempts to initiate a load.
+ void AttemptLoad();
+
+ // Called once we have finished loading the scripts on the file thread.
+ void OnScriptsLoaded(scoped_ptr<UserScriptList> user_scripts,
+ scoped_ptr<base::SharedMemory> shared_memory);
+
+ // Sends the renderer process a new set of user scripts. If
+ // |changed_hosts| is not empty, this signals that only the scripts from
+ // those hosts should be updated. Otherwise, all hosts will be
+ // updated.
+ void SendUpdate(content::RenderProcessHost* process,
+ base::SharedMemory* shared_memory,
+ const std::set<HostID>& changed_hosts);
+
+ bool is_loading() const {
+ // Ownership of |user_scripts_| is passed to the file thread when loading.
+ return user_scripts_.get() == NULL;
+ }
+
+ // Manages our notification registrations.
+ content::NotificationRegistrar registrar_;
+
+ // Contains the scripts that were found the last time scripts were updated.
+ scoped_ptr<base::SharedMemory> shared_memory_;
+
+ // List of scripts from currently-installed extensions we should load.
+ scoped_ptr<UserScriptList> user_scripts_;
+
+ // The mutually-exclusive sets of scripts that were added or removed since the
+ // last script load.
+ std::set<UserScript> added_scripts_;
+ std::set<UserScript> removed_scripts_;
+
+ // Indicates whether the the collection of scripts should be cleared before
+ // additions and removals on the next script load.
+ bool clear_scripts_;
+
+ // The IDs of the extensions which changed in the last update sent to the
+ // renderer.
+ std::set<HostID> changed_hosts_;
+
+ // If the initial set of hosts has finished loading.
+ bool ready_;
+
+ // If list of user scripts is modified while we're loading it, we note
+ // that we're currently mid-load and then start over again once the load
+ // finishes. This boolean tracks whether another load is pending.
+ bool pending_load_;
+
+ // The browser_context for which the scripts managed here are installed.
+ content::BrowserContext* browser_context_;
+
+ // ID of the host that owns these scripts, if any. This is only set to a
+ // non-empty value for declarative user script shared memory regions.
+ HostID host_id_;
+
+ // The associated observers.
+ base::ObserverList<Observer> observers_;
+
+ base::WeakPtrFactory<UserScriptLoader> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserScriptLoader);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_USER_SCRIPT_LOADER_H_
diff --git a/chromium/extensions/browser/value_store/lazy_leveldb.cc b/chromium/extensions/browser/value_store/lazy_leveldb.cc
new file mode 100644
index 00000000000..6061bd32b1b
--- /dev/null
+++ b/chromium/extensions/browser/value_store/lazy_leveldb.cc
@@ -0,0 +1,283 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/value_store/lazy_leveldb.h"
+
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/strings/string_util.h"
+#include "content/public/browser/browser_thread.h"
+#include "third_party/leveldatabase/env_chromium.h"
+#include "third_party/leveldatabase/src/include/leveldb/iterator.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+
+using base::StringPiece;
+using content::BrowserThread;
+
+namespace {
+
+const char kInvalidJson[] = "Invalid JSON";
+const char kRestoredDuringOpen[] = "Database corruption repaired during open";
+
+// UMA values used when recovering from a corrupted leveldb.
+// Do not change/delete these values as you will break reporting for older
+// copies of Chrome. Only add new values to the end.
+enum LevelDBDatabaseCorruptionRecoveryValue {
+ LEVELDB_DB_RESTORE_DELETE_SUCCESS = 0,
+ LEVELDB_DB_RESTORE_DELETE_FAILURE,
+ LEVELDB_DB_RESTORE_REPAIR_SUCCESS,
+ LEVELDB_DB_RESTORE_MAX
+};
+
+// UMA values used when recovering from a corrupted leveldb.
+// Do not change/delete these values as you will break reporting for older
+// copies of Chrome. Only add new values to the end.
+enum LevelDBValueCorruptionRecoveryValue {
+ LEVELDB_VALUE_RESTORE_DELETE_SUCCESS,
+ LEVELDB_VALUE_RESTORE_DELETE_FAILURE,
+ LEVELDB_VALUE_RESTORE_MAX
+};
+
+ValueStore::StatusCode LevelDbToValueStoreStatusCode(
+ const leveldb::Status& status) {
+ if (status.ok())
+ return ValueStore::OK;
+ if (status.IsCorruption())
+ return ValueStore::CORRUPTION;
+ return ValueStore::OTHER_ERROR;
+}
+
+leveldb::Status DeleteValue(leveldb::DB* db, const std::string& key) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+
+ leveldb::WriteBatch batch;
+ batch.Delete(key);
+
+ return db->Write(leveldb::WriteOptions(), &batch);
+}
+
+} // namespace
+
+LazyLevelDb::LazyLevelDb(const std::string& uma_client_name,
+ const base::FilePath& path)
+ : db_path_(path) {
+ open_options_.create_if_missing = true;
+ open_options_.paranoid_checks = true;
+ open_options_.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue;
+
+ read_options_.verify_checksums = true;
+
+ // Used in lieu of UMA_HISTOGRAM_ENUMERATION because the histogram name is
+ // not a constant.
+ open_histogram_ = base::LinearHistogram::FactoryGet(
+ "Extensions.Database.Open." + uma_client_name, 1,
+ leveldb_env::LEVELDB_STATUS_MAX, leveldb_env::LEVELDB_STATUS_MAX + 1,
+ base::Histogram::kUmaTargetedHistogramFlag);
+ db_restore_histogram_ = base::LinearHistogram::FactoryGet(
+ "Extensions.Database.Database.Restore." + uma_client_name, 1,
+ LEVELDB_DB_RESTORE_MAX, LEVELDB_DB_RESTORE_MAX + 1,
+ base::Histogram::kUmaTargetedHistogramFlag);
+ value_restore_histogram_ = base::LinearHistogram::FactoryGet(
+ "Extensions.Database.Value.Restore." + uma_client_name, 1,
+ LEVELDB_VALUE_RESTORE_MAX, LEVELDB_VALUE_RESTORE_MAX + 1,
+ base::Histogram::kUmaTargetedHistogramFlag);
+}
+
+LazyLevelDb::~LazyLevelDb() {
+ if (db_ && !BrowserThread::CurrentlyOn(BrowserThread::FILE))
+ BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, db_.release());
+}
+
+ValueStore::Status LazyLevelDb::Read(const std::string& key,
+ scoped_ptr<base::Value>* value) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ DCHECK(value);
+
+ std::string value_as_json;
+ leveldb::Status s = db_->Get(read_options_, key, &value_as_json);
+
+ if (s.IsNotFound()) {
+ // Despite there being no value, it was still a success. Check this first
+ // because ok() is false on IsNotFound.
+ return ValueStore::Status();
+ }
+
+ if (!s.ok())
+ return ToValueStoreError(s);
+
+ scoped_ptr<base::Value> val = base::JSONReader().ReadToValue(value_as_json);
+ if (!val)
+ return ValueStore::Status(ValueStore::CORRUPTION, FixCorruption(&key),
+ kInvalidJson);
+
+ *value = std::move(val);
+ return ValueStore::Status();
+}
+
+ValueStore::Status LazyLevelDb::Delete(const std::string& key) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+
+ ValueStore::Status status = EnsureDbIsOpen();
+ if (!status.ok())
+ return status;
+
+ return ToValueStoreError(DeleteValue(db_.get(), key));
+}
+
+ValueStore::BackingStoreRestoreStatus LazyLevelDb::LogRestoreStatus(
+ ValueStore::BackingStoreRestoreStatus restore_status) const {
+ switch (restore_status) {
+ case ValueStore::RESTORE_NONE:
+ NOTREACHED();
+ break;
+ case ValueStore::DB_RESTORE_DELETE_SUCCESS:
+ db_restore_histogram_->Add(LEVELDB_DB_RESTORE_DELETE_SUCCESS);
+ break;
+ case ValueStore::DB_RESTORE_DELETE_FAILURE:
+ db_restore_histogram_->Add(LEVELDB_DB_RESTORE_DELETE_FAILURE);
+ break;
+ case ValueStore::DB_RESTORE_REPAIR_SUCCESS:
+ db_restore_histogram_->Add(LEVELDB_DB_RESTORE_REPAIR_SUCCESS);
+ break;
+ case ValueStore::VALUE_RESTORE_DELETE_SUCCESS:
+ value_restore_histogram_->Add(LEVELDB_VALUE_RESTORE_DELETE_SUCCESS);
+ break;
+ case ValueStore::VALUE_RESTORE_DELETE_FAILURE:
+ value_restore_histogram_->Add(LEVELDB_VALUE_RESTORE_DELETE_FAILURE);
+ break;
+ }
+ return restore_status;
+}
+
+ValueStore::BackingStoreRestoreStatus LazyLevelDb::FixCorruption(
+ const std::string* key) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ leveldb::Status s;
+ if (key && db_) {
+ s = DeleteValue(db_.get(), *key);
+ // Deleting involves writing to the log, so it's possible to have a
+ // perfectly OK database but still have a delete fail.
+ if (s.ok())
+ return LogRestoreStatus(ValueStore::VALUE_RESTORE_DELETE_SUCCESS);
+ else if (s.IsIOError())
+ return LogRestoreStatus(ValueStore::VALUE_RESTORE_DELETE_FAILURE);
+ // Any other kind of failure triggers a db repair.
+ }
+
+ // Make sure database is closed.
+ db_.reset();
+
+ // First try the less lossy repair.
+ ValueStore::BackingStoreRestoreStatus restore_status =
+ ValueStore::RESTORE_NONE;
+
+ leveldb::Options repair_options;
+ repair_options.create_if_missing = true;
+ repair_options.paranoid_checks = true;
+
+ // RepairDB can drop an unbounded number of leveldb tables (key/value sets).
+ s = leveldb::RepairDB(db_path_.AsUTF8Unsafe(), repair_options);
+
+ leveldb::DB* db = nullptr;
+ if (s.ok()) {
+ restore_status = ValueStore::DB_RESTORE_REPAIR_SUCCESS;
+ s = leveldb::DB::Open(open_options_, db_path_.AsUTF8Unsafe(), &db);
+ }
+
+ if (!s.ok()) {
+ if (DeleteDbFile()) {
+ restore_status = ValueStore::DB_RESTORE_DELETE_SUCCESS;
+ s = leveldb::DB::Open(open_options_, db_path_.AsUTF8Unsafe(), &db);
+ } else {
+ restore_status = ValueStore::DB_RESTORE_DELETE_FAILURE;
+ }
+ }
+
+ if (s.ok())
+ db_.reset(db);
+ else
+ db_unrecoverable_ = true;
+
+ if (s.ok() && key) {
+ s = DeleteValue(db_.get(), *key);
+ if (s.ok()) {
+ restore_status = ValueStore::VALUE_RESTORE_DELETE_SUCCESS;
+ } else if (s.IsIOError()) {
+ restore_status = ValueStore::VALUE_RESTORE_DELETE_FAILURE;
+ } else {
+ db_.reset(db);
+ if (!DeleteDbFile())
+ db_unrecoverable_ = true;
+ restore_status = ValueStore::DB_RESTORE_DELETE_FAILURE;
+ }
+ }
+
+ // Only log for the final and most extreme form of database restoration.
+ LogRestoreStatus(restore_status);
+
+ return restore_status;
+}
+
+ValueStore::Status LazyLevelDb::EnsureDbIsOpen() {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+
+ if (db_)
+ return ValueStore::Status();
+
+ if (db_unrecoverable_) {
+ return ValueStore::Status(ValueStore::CORRUPTION,
+ ValueStore::DB_RESTORE_DELETE_FAILURE,
+ "Database corrupted");
+ }
+
+ leveldb::DB* db = nullptr;
+ leveldb::Status ldb_status =
+ leveldb::DB::Open(open_options_, db_path_.AsUTF8Unsafe(), &db);
+ open_histogram_->Add(leveldb_env::GetLevelDBStatusUMAValue(ldb_status));
+ ValueStore::Status status = ToValueStoreError(ldb_status);
+ if (ldb_status.ok()) {
+ db_.reset(db);
+ } else if (ldb_status.IsCorruption()) {
+ status.restore_status = FixCorruption(nullptr);
+ if (status.restore_status != ValueStore::DB_RESTORE_DELETE_FAILURE) {
+ status.code = ValueStore::OK;
+ status.message = kRestoredDuringOpen;
+ }
+ }
+
+ return status;
+}
+
+ValueStore::Status LazyLevelDb::ToValueStoreError(
+ const leveldb::Status& status) {
+ CHECK(!status.IsNotFound()); // not an error
+
+ std::string message = status.ToString();
+ // The message may contain |db_path_|, which may be considered sensitive
+ // data, and those strings are passed to the extension, so strip it out.
+ base::ReplaceSubstringsAfterOffset(&message, 0u, db_path_.AsUTF8Unsafe(),
+ "...");
+
+ return ValueStore::Status(LevelDbToValueStoreStatusCode(status), message);
+}
+
+bool LazyLevelDb::DeleteDbFile() {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ db_.reset(); // release any lock on the directory
+ if (!base::DeleteFile(db_path_, true /* recursive */)) {
+ LOG(WARNING) << "Failed to delete leveldb database at " << db_path_.value();
+ return false;
+ }
+ return true;
+}
+
+ValueStore::Status LazyLevelDb::CreateIterator(
+ const leveldb::ReadOptions& read_options,
+ scoped_ptr<leveldb::Iterator>* iterator) {
+ ValueStore::Status status = EnsureDbIsOpen();
+ if (!status.ok())
+ return status;
+ *iterator = make_scoped_ptr(db_->NewIterator(read_options));
+ return ValueStore::Status();
+}
diff --git a/chromium/extensions/browser/value_store/lazy_leveldb.h b/chromium/extensions/browser/value_store/lazy_leveldb.h
new file mode 100644
index 00000000000..4fb93a63d71
--- /dev/null
+++ b/chromium/extensions/browser/value_store/lazy_leveldb.h
@@ -0,0 +1,98 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_VALUE_STORE_LAZY_LEVELDB_H_
+#define EXTENSIONS_BROWSER_VALUE_STORE_LAZY_LEVELDB_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram_base.h"
+#include "extensions/browser/value_store/value_store.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+
+namespace leveldb {
+class Iterator;
+} // namespace leveldb
+
+// Manages a lazy connection to a leveldb database. "lazy" means that the
+// leveldb database will be opened, when necessary, when any *public* method is
+// called. Derived classes are responsible for calling EnsureDbIsOpen() before
+// calling any other *protected* method.
+class LazyLevelDb {
+ public:
+ // Creates a new database iterator. This iterator *must* be deleted before
+ // this database is closed.
+ ValueStore::Status CreateIterator(const leveldb::ReadOptions& read_options,
+ scoped_ptr<leveldb::Iterator>* iterator);
+
+ // Converts a leveldb::Status to a ValueStore::Status. Will also sanitize path
+ // to eliminate user data path.
+ ValueStore::Status ToValueStoreError(const leveldb::Status& status);
+
+ // Deletes a value (identified by |key|) from the database.
+ ValueStore::Status Delete(const std::string& key);
+
+ protected:
+ LazyLevelDb(const std::string& uma_client_name, const base::FilePath& path);
+ ~LazyLevelDb();
+
+ // Closes, if necessary, and deletes the database directory.
+ bool DeleteDbFile();
+
+ // Fixes the |key| or database. If |key| is not null and the database is open
+ // then the key will be deleted. Otherwise the database will be repaired, and
+ // failing that will be deleted.
+ ValueStore::BackingStoreRestoreStatus FixCorruption(const std::string* key);
+
+ // Reads a |key| from the database, and populates |value| with the result. If
+ // the specified value does not exist in the database then an "OK" status will
+ // be returned and value will be unchanged. Caller must ensure the database is
+ // open before calling this method.
+ ValueStore::Status Read(const std::string& key,
+ scoped_ptr<base::Value>* value);
+
+ // Opens the underlying database if not yet open. If the open fails due to
+ // corruption will attempt to repair the database. Failing that, will attempt
+ // to delete the database. Will only attempt a single recovery.
+ ValueStore::Status EnsureDbIsOpen();
+
+ const std::string& open_histogram_name() const {
+ return open_histogram_->histogram_name();
+ }
+
+ leveldb::DB* db() { return db_.get(); }
+
+ const leveldb::ReadOptions& read_options() const { return read_options_; }
+
+ const leveldb::WriteOptions& write_options() const { return write_options_; }
+
+ private:
+ ValueStore::BackingStoreRestoreStatus LogRestoreStatus(
+ ValueStore::BackingStoreRestoreStatus restore_status) const;
+
+ // The leveldb to which this class reads/writes.
+ scoped_ptr<leveldb::DB> db_;
+ // The path to the underlying leveldb.
+ const base::FilePath db_path_;
+ // The options to be used when this database is lazily opened.
+ leveldb::Options open_options_;
+ // The options to be used for all database read operations.
+ leveldb::ReadOptions read_options_;
+ // The options to be used for all database write operations.
+ leveldb::WriteOptions write_options_;
+ // Set when this database has tried to repair (and failed) to prevent
+ // unbounded attempts to open a bad/unrecoverable database.
+ bool db_unrecoverable_ = false;
+ // Used for UMA logging.
+ base::HistogramBase* open_histogram_ = nullptr;
+ base::HistogramBase* db_restore_histogram_ = nullptr;
+ base::HistogramBase* value_restore_histogram_ = nullptr;
+
+ DISALLOW_COPY_AND_ASSIGN(LazyLevelDb);
+};
+
+#endif // EXTENSIONS_BROWSER_VALUE_STORE_LAZY_LEVELDB_H_
diff --git a/chromium/extensions/browser/value_store/legacy_value_store_factory.cc b/chromium/extensions/browser/value_store/legacy_value_store_factory.cc
new file mode 100644
index 00000000000..bafb93ee2a3
--- /dev/null
+++ b/chromium/extensions/browser/value_store/legacy_value_store_factory.cc
@@ -0,0 +1,264 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/value_store/legacy_value_store_factory.h"
+
+#include <string>
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/value_store/leveldb_value_store.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+
+using base::AutoLock;
+using content::BrowserThread;
+
+namespace {
+
+// Statistics are logged to UMA with these strings as part of histogram name.
+// They can all be found under Extensions.Database.Open.<client>. Changing this
+// needs to synchronize with histograms.xml, AND will also become incompatible
+// with older browsers still reporting the previous values.
+const char kSettingsDatabaseUMAClientName[] = "Settings";
+const char kRulesDatabaseUMAClientName[] = "Rules";
+const char kStateDatabaseUMAClientName[] = "State";
+
+bool ValidDBExists(const base::FilePath& path) {
+ // TODO(cmumford): Enhance to detect if dir contains valid database.
+ return base::DirectoryExists(path);
+}
+
+} // namespace
+
+namespace extensions {
+
+//
+// ModelSettings
+//
+LegacyValueStoreFactory::ModelSettings::ModelSettings(
+ const base::FilePath& data_path)
+ : data_path_(data_path) {}
+
+base::FilePath LegacyValueStoreFactory::ModelSettings::GetDBPath(
+ const ExtensionId& extension_id) const {
+ return data_path_.AppendASCII(extension_id);
+}
+
+bool LegacyValueStoreFactory::ModelSettings::DeleteData(
+ const ExtensionId& extension_id) {
+ return base::DeleteFile(GetDBPath(extension_id), true /* recursive */);
+}
+
+bool LegacyValueStoreFactory::ModelSettings::DataExists(
+ const ExtensionId& extension_id) const {
+ return ValidDBExists(GetDBPath(extension_id));
+}
+
+std::set<ExtensionId>
+LegacyValueStoreFactory::ModelSettings::GetKnownExtensionIDs() const {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ std::set<ExtensionId> result;
+
+ // Leveldb databases are directories inside |base_path_|.
+ base::FileEnumerator extension_dirs(data_path_, false,
+ base::FileEnumerator::DIRECTORIES);
+ while (!extension_dirs.Next().empty()) {
+ base::FilePath extension_dir = extension_dirs.GetInfo().GetName();
+ DCHECK(!extension_dir.IsAbsolute());
+ // Extension ID's are 'a'..'p', so any directory within this folder will
+ // either be ASCII, or created by some other application and safe to ignore.
+ std::string maybe_as_ascii(extension_dir.MaybeAsASCII());
+ if (!maybe_as_ascii.empty()) {
+ result.insert(maybe_as_ascii);
+ }
+ }
+
+ return result;
+}
+
+//
+// SettingsRoot
+//
+
+LegacyValueStoreFactory::SettingsRoot::SettingsRoot(
+ const base::FilePath& base_path,
+ const std::string& extension_dirname,
+ const std::string& app_dirname) {
+ if (!extension_dirname.empty())
+ extensions_.reset(
+ new ModelSettings(base_path.AppendASCII(extension_dirname)));
+
+ if (!app_dirname.empty())
+ apps_.reset(new ModelSettings(base_path.AppendASCII(app_dirname)));
+}
+
+LegacyValueStoreFactory::SettingsRoot::~SettingsRoot() = default;
+
+const LegacyValueStoreFactory::ModelSettings*
+LegacyValueStoreFactory::SettingsRoot::GetModel(ModelType model_type) const {
+ switch (model_type) {
+ case ValueStoreFactory::ModelType::APP:
+ DCHECK(apps_ != nullptr);
+ return apps_.get();
+ case ValueStoreFactory::ModelType::EXTENSION:
+ DCHECK(extensions_ != nullptr);
+ return extensions_.get();
+ }
+ NOTREACHED();
+ return nullptr;
+}
+
+LegacyValueStoreFactory::ModelSettings*
+LegacyValueStoreFactory::SettingsRoot::GetModel(ModelType model_type) {
+ switch (model_type) {
+ case ValueStoreFactory::ModelType::APP:
+ DCHECK(apps_ != nullptr);
+ return apps_.get();
+ case ValueStoreFactory::ModelType::EXTENSION:
+ DCHECK(extensions_ != nullptr);
+ return extensions_.get();
+ }
+ NOTREACHED();
+ return nullptr;
+}
+
+std::set<ExtensionId>
+LegacyValueStoreFactory::SettingsRoot::GetKnownExtensionIDs(
+ ModelType model_type) const {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ switch (model_type) {
+ case ValueStoreFactory::ModelType::APP:
+ DCHECK(apps_ != nullptr);
+ return apps_->GetKnownExtensionIDs();
+ case ValueStoreFactory::ModelType::EXTENSION:
+ DCHECK(extensions_ != nullptr);
+ return extensions_->GetKnownExtensionIDs();
+ }
+ NOTREACHED();
+ return std::set<ExtensionId>();
+}
+
+//
+// LegacyValueStoreFactory
+//
+
+LegacyValueStoreFactory::LegacyValueStoreFactory(
+ const base::FilePath& profile_path)
+ : profile_path_(profile_path),
+ local_settings_(profile_path,
+ kLocalExtensionSettingsDirectoryName,
+ kLocalAppSettingsDirectoryName),
+ sync_settings_(profile_path,
+ kSyncExtensionSettingsDirectoryName,
+ kSyncAppSettingsDirectoryName),
+ // Currently no such thing as a managed app - only an extension.
+ managed_settings_(profile_path, kManagedSettingsDirectoryName, "") {}
+
+LegacyValueStoreFactory::~LegacyValueStoreFactory() = default;
+
+bool LegacyValueStoreFactory::RulesDBExists() const {
+ return ValidDBExists(GetRulesDBPath());
+}
+
+bool LegacyValueStoreFactory::StateDBExists() const {
+ return ValidDBExists(GetStateDBPath());
+}
+
+scoped_ptr<ValueStore> LegacyValueStoreFactory::CreateRulesStore() {
+ return make_scoped_ptr(
+ new LeveldbValueStore(kRulesDatabaseUMAClientName, GetRulesDBPath()));
+}
+
+scoped_ptr<ValueStore> LegacyValueStoreFactory::CreateStateStore() {
+ return make_scoped_ptr(
+ new LeveldbValueStore(kStateDatabaseUMAClientName, GetStateDBPath()));
+}
+
+scoped_ptr<ValueStore> LegacyValueStoreFactory::CreateSettingsStore(
+ settings_namespace::Namespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) {
+ const ModelSettings* settings_root =
+ GetSettingsRoot(settings_namespace).GetModel(model_type);
+ DCHECK(settings_root != nullptr);
+ return make_scoped_ptr(new LeveldbValueStore(
+ kSettingsDatabaseUMAClientName, settings_root->GetDBPath(extension_id)));
+}
+
+void LegacyValueStoreFactory::DeleteSettings(
+ settings_namespace::Namespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) {
+ // TODO(cmumford): Verify that we always need to be called on FILE thread.
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ ModelSettings* model_settings =
+ GetSettingsRoot(settings_namespace).GetModel(model_type);
+ if (model_settings == nullptr) {
+ NOTREACHED();
+ return;
+ }
+ model_settings->DeleteData(extension_id);
+}
+
+bool LegacyValueStoreFactory::HasSettings(
+ settings_namespace::Namespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) {
+ const ModelSettings* model_settings =
+ GetSettingsRoot(settings_namespace).GetModel(model_type);
+ if (model_settings == nullptr)
+ return false;
+ return model_settings->DataExists(extension_id);
+}
+
+std::set<ExtensionId> LegacyValueStoreFactory::GetKnownExtensionIDs(
+ settings_namespace::Namespace settings_type,
+ ModelType model_type) const {
+ return GetSettingsRoot(settings_type).GetKnownExtensionIDs(model_type);
+}
+
+const LegacyValueStoreFactory::SettingsRoot&
+LegacyValueStoreFactory::GetSettingsRoot(
+ settings_namespace::Namespace settings_namespace) const {
+ switch (settings_namespace) {
+ case settings_namespace::LOCAL:
+ return local_settings_;
+ case settings_namespace::SYNC:
+ return sync_settings_;
+ case settings_namespace::MANAGED:
+ return managed_settings_;
+ case settings_namespace::INVALID:
+ break;
+ }
+ NOTREACHED();
+ return local_settings_;
+}
+
+LegacyValueStoreFactory::SettingsRoot& LegacyValueStoreFactory::GetSettingsRoot(
+ settings_namespace::Namespace settings_namespace) {
+ switch (settings_namespace) {
+ case settings_namespace::LOCAL:
+ return local_settings_;
+ case settings_namespace::SYNC:
+ return sync_settings_;
+ case settings_namespace::MANAGED:
+ return managed_settings_;
+ case settings_namespace::INVALID:
+ break;
+ }
+ NOTREACHED();
+ return local_settings_;
+}
+
+base::FilePath LegacyValueStoreFactory::GetRulesDBPath() const {
+ return profile_path_.AppendASCII(kRulesStoreName);
+}
+
+base::FilePath LegacyValueStoreFactory::GetStateDBPath() const {
+ return profile_path_.AppendASCII(kStateStoreName);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/value_store/legacy_value_store_factory.h b/chromium/extensions/browser/value_store/legacy_value_store_factory.h
new file mode 100644
index 00000000000..65db0275a71
--- /dev/null
+++ b/chromium/extensions/browser/value_store/legacy_value_store_factory.h
@@ -0,0 +1,111 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_VALUE_STORE_LEGACY_VALUE_STORE_FACTORY_H_
+#define EXTENSIONS_BROWSER_VALUE_STORE_LEGACY_VALUE_STORE_FACTORY_H_
+
+#include <set>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/browser/value_store/value_store.h"
+#include "extensions/browser/value_store/value_store_factory.h"
+#include "extensions/common/extension.h"
+
+namespace extensions {
+
+// A factory to create legacy ValueStore instances for storing extension
+// state/rules/settings. "legacy" refers to the initial storage implementation
+// which created a settings database per extension.
+class LegacyValueStoreFactory : public ValueStoreFactory {
+ public:
+ explicit LegacyValueStoreFactory(const base::FilePath& profile_path);
+
+ bool RulesDBExists() const;
+ bool StateDBExists() const;
+
+ // ValueStoreFactory:
+ scoped_ptr<ValueStore> CreateRulesStore() override;
+ scoped_ptr<ValueStore> CreateStateStore() override;
+ scoped_ptr<ValueStore> CreateSettingsStore(
+ settings_namespace::Namespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) override;
+
+ void DeleteSettings(settings_namespace::Namespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) override;
+ bool HasSettings(settings_namespace::Namespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) override;
+ std::set<ExtensionId> GetKnownExtensionIDs(
+ settings_namespace::Namespace settings_namespace,
+ ModelType model_type) const override;
+
+ private:
+ friend class base::RefCounted<LegacyValueStoreFactory>;
+
+ // Manages a collection of legacy settings databases all within a common
+ // directory.
+ class ModelSettings {
+ public:
+ explicit ModelSettings(const base::FilePath& data_path);
+
+ base::FilePath GetDBPath(const ExtensionId& extension_id) const;
+ bool DeleteData(const ExtensionId& extension_id);
+ bool DataExists(const ExtensionId& extension_id) const;
+ std::set<ExtensionId> GetKnownExtensionIDs() const;
+
+ private:
+ // The path containing all settings databases under this root.
+ const base::FilePath data_path_;
+
+ DISALLOW_COPY_AND_ASSIGN(ModelSettings);
+ };
+
+ // Manages two collections of legacy settings databases (apps & extensions)
+ // within a common base directory.
+ class SettingsRoot {
+ public:
+ // If either |extension_dirname| or |app_dirname| are empty then that
+ // ModelSetting will *not* be created.
+ SettingsRoot(const base::FilePath& base_path,
+ const std::string& extension_dirname,
+ const std::string& app_dirname);
+ ~SettingsRoot();
+
+ std::set<ExtensionId> GetKnownExtensionIDs(ModelType model_type) const;
+ const ModelSettings* GetModel(ModelType model_type) const;
+ ModelSettings* GetModel(ModelType model_type);
+
+ private:
+ scoped_ptr<ModelSettings> extensions_;
+ scoped_ptr<ModelSettings> apps_;
+
+ DISALLOW_COPY_AND_ASSIGN(SettingsRoot);
+ };
+
+ ~LegacyValueStoreFactory() override;
+
+ const SettingsRoot& GetSettingsRoot(
+ settings_namespace::Namespace settings_namespace) const;
+ SettingsRoot& GetSettingsRoot(
+ settings_namespace::Namespace settings_namespace);
+
+ base::FilePath GetRulesDBPath() const;
+ base::FilePath GetStateDBPath() const;
+
+ const base::FilePath profile_path_;
+ SettingsRoot local_settings_;
+ SettingsRoot sync_settings_;
+ SettingsRoot managed_settings_;
+
+ DISALLOW_COPY_AND_ASSIGN(LegacyValueStoreFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_VALUE_STORE_LEGACY_VALUE_STORE_FACTORY_H_
diff --git a/chromium/extensions/browser/value_store/leveldb_scoped_database.cc b/chromium/extensions/browser/value_store/leveldb_scoped_database.cc
new file mode 100644
index 00000000000..2fb8a9b77ea
--- /dev/null
+++ b/chromium/extensions/browser/value_store/leveldb_scoped_database.cc
@@ -0,0 +1,168 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/value_store/leveldb_scoped_database.h"
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "content/public/browser/browser_thread.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+
+using content::BrowserThread;
+
+namespace {
+
+// Note: Changing the delimiter will change the database schema.
+const char kKeyDelimiter = ':'; // delimits scope and key
+const char kCannotSerialize[] = "Cannot serialize value to JSON";
+const char kInvalidJson[] = "Invalid JSON";
+const char kInvalidScope[] = "Invalid scope";
+
+} // namespace
+
+bool LeveldbScopedDatabase::SplitKey(const std::string& full_key,
+ std::string* scope,
+ std::string* key) {
+ size_t pos = full_key.find(kKeyDelimiter);
+ if (pos == std::string::npos)
+ return false;
+ if (pos == 0)
+ return false;
+ *scope = full_key.substr(0, pos);
+ *key = full_key.substr(pos + 1);
+ return true;
+}
+
+bool LeveldbScopedDatabase::CreateKey(const std::string& scope,
+ const std::string& key,
+ std::string* scoped_key) {
+ if (scope.empty() || scope.find(kKeyDelimiter) != std::string::npos)
+ return false;
+ *scoped_key = scope + kKeyDelimiter + key;
+ return true;
+}
+
+LeveldbScopedDatabase::LeveldbScopedDatabase(const std::string& uma_client_name,
+ const base::FilePath& path)
+ : LazyLevelDb(uma_client_name, path) {}
+
+LeveldbScopedDatabase::~LeveldbScopedDatabase() {}
+
+ValueStore::Status LeveldbScopedDatabase::Read(const std::string& scope,
+ const std::string& key,
+ scoped_ptr<base::Value>* value) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+
+ ValueStore::Status status = EnsureDbIsOpen();
+ if (!status.ok())
+ return status;
+ std::string scoped_key;
+ if (!CreateKey(scope, key, &scoped_key))
+ return ValueStore::Status(ValueStore::OTHER_ERROR, kInvalidScope);
+
+ return LazyLevelDb::Read(scoped_key, value);
+}
+
+ValueStore::Status LeveldbScopedDatabase::Read(const std::string& scope,
+ base::DictionaryValue* values) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+
+ ValueStore::Status status = EnsureDbIsOpen();
+ if (!status.ok())
+ return status;
+
+ std::string prefix;
+ if (!CreateKey(scope, "", &prefix))
+ return ValueStore::Status(ValueStore::OTHER_ERROR, kInvalidScope);
+
+ scoped_ptr<leveldb::Iterator> it(db()->NewIterator(read_options()));
+
+ base::JSONReader json_reader;
+ scoped_ptr<base::DictionaryValue> settings(new base::DictionaryValue());
+
+ for (it->Seek(prefix); it->Valid() && it->key().starts_with(prefix);
+ it->Next()) {
+ leveldb::Slice descoped_key(it->key());
+ descoped_key.remove_prefix(prefix.size());
+ scoped_ptr<base::Value> value = json_reader.Read(
+ base::StringPiece(it->value().data(), it->value().size()));
+ if (!value) {
+ return ValueStore::Status(ValueStore::CORRUPTION,
+ LazyLevelDb::Delete(it->key().ToString()).ok()
+ ? ValueStore::VALUE_RESTORE_DELETE_SUCCESS
+ : ValueStore::VALUE_RESTORE_DELETE_FAILURE,
+ kInvalidJson);
+ }
+ values->SetWithoutPathExpansion(descoped_key.ToString(), std::move(value));
+ }
+
+ return status;
+}
+
+ValueStore::Status LeveldbScopedDatabase::Write(const std::string& scope,
+ const std::string& key,
+ const base::Value& value) {
+ ValueStore::Status status = EnsureDbIsOpen();
+ if (!status.ok())
+ return status;
+
+ leveldb::WriteBatch batch;
+ status = AddToWriteBatch(&batch, scope, key, value);
+ if (!status.ok())
+ return status;
+ return ToValueStoreError(db()->Write(write_options(), &batch));
+}
+
+ValueStore::Status LeveldbScopedDatabase::Write(
+ const std::string& scope,
+ const base::DictionaryValue& values) {
+ ValueStore::Status status = EnsureDbIsOpen();
+ if (!status.ok())
+ return status;
+
+ leveldb::WriteBatch batch;
+ for (base::DictionaryValue::Iterator it(values); !it.IsAtEnd();
+ it.Advance()) {
+ status = AddToWriteBatch(&batch, scope, it.key(), it.value());
+ if (!status.ok())
+ return status;
+ }
+
+ return ToValueStoreError(db()->Write(write_options(), &batch));
+}
+
+ValueStore::Status LeveldbScopedDatabase::DeleteValues(
+ const std::string& scope,
+ const std::vector<std::string>& keys) {
+ ValueStore::Status status = EnsureDbIsOpen();
+ if (!status.ok())
+ return status;
+
+ leveldb::WriteBatch batch;
+ std::string scoped_key;
+ for (const auto& key : keys) {
+ if (!CreateKey(scope, key, &scoped_key))
+ return ValueStore::Status(ValueStore::OTHER_ERROR, kInvalidScope);
+ batch.Delete(scoped_key);
+ }
+
+ return ToValueStoreError(db()->Write(write_options(), &batch));
+}
+
+ValueStore::Status LeveldbScopedDatabase::AddToWriteBatch(
+ leveldb::WriteBatch* batch,
+ const std::string& scope,
+ const std::string& key,
+ const base::Value& value) {
+ std::string scoped_key;
+ if (!CreateKey(scope, key, &scoped_key))
+ return ValueStore::Status(ValueStore::OTHER_ERROR, kInvalidScope);
+
+ std::string value_as_json;
+ if (!base::JSONWriter::Write(value, &value_as_json))
+ return ValueStore::Status(ValueStore::OTHER_ERROR, kCannotSerialize);
+
+ batch->Put(scoped_key, value_as_json);
+ return ValueStore::Status();
+}
diff --git a/chromium/extensions/browser/value_store/leveldb_scoped_database.h b/chromium/extensions/browser/value_store/leveldb_scoped_database.h
new file mode 100644
index 00000000000..ef52f1d7769
--- /dev/null
+++ b/chromium/extensions/browser/value_store/leveldb_scoped_database.h
@@ -0,0 +1,81 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_VALUE_STORE_LEVELDB_SCOPED_DATABASE_H_
+#define EXTENSIONS_BROWSER_VALUE_STORE_LEVELDB_SCOPED_DATABASE_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "extensions/browser/value_store/lazy_leveldb.h"
+#include "extensions/browser/value_store/value_store.h"
+
+// This database is used to persist values with their keys scoped within a
+// specified namespace - AKA |scope|. Values will be written as follows:
+//
+// <scope><delimiter><scoped-key> -> <value>
+//
+// Note: |scope| must not contain the delimiter, but the |key| may.
+//
+class LeveldbScopedDatabase
+ : public LazyLevelDb,
+ public base::RefCountedThreadSafe<LeveldbScopedDatabase> {
+ public:
+ // Splits the full key into the scope and inner (scoped) key.
+ // Returns true if successfully split, and false if not and leaves |scope| and
+ // |key| unchanged.
+ static bool SplitKey(const std::string& full_key,
+ std::string* scope,
+ std::string* key);
+
+ // Creates a fully scoped key. |scope| cannot be an empty key and cannot
+ // contain the delimiter. |scoped_key| will be set to:
+ //
+ // <scope><delimiter><key>
+ //
+ // Will return true when successful, false if not.
+ static bool CreateKey(const std::string& scope,
+ const std::string& key,
+ std::string* scoped_key);
+
+ LeveldbScopedDatabase(const std::string& uma_client_name,
+ const base::FilePath& path);
+
+ // Reads a single |value| from the database for the specified |key|.
+ ValueStore::Status Read(const std::string& scope,
+ const std::string& key,
+ scoped_ptr<base::Value>* value);
+
+ // Reads all |values| from the database stored within the specified |scope|.
+ ValueStore::Status Read(const std::string& scope,
+ base::DictionaryValue* values);
+
+ // Writes a single |key| => |value| to the database.
+ ValueStore::Status Write(const std::string& scope,
+ const std::string& key,
+ const base::Value& value);
+
+ // Writes all |values| to the database with the keys scoped with |scope|.
+ ValueStore::Status Write(const std::string& scope,
+ const base::DictionaryValue& values);
+
+ // Deletes all |keys| from the databases withing the specified |scope|.
+ ValueStore::Status DeleteValues(const std::string& scope,
+ const std::vector<std::string>& keys);
+
+ protected:
+ friend class base::RefCountedThreadSafe<LeveldbScopedDatabase>;
+ virtual ~LeveldbScopedDatabase();
+
+ static ValueStore::Status AddToWriteBatch(leveldb::WriteBatch* batch,
+ const std::string& scope,
+ const std::string& key,
+ const base::Value& value);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LeveldbScopedDatabase);
+};
+
+#endif // EXTENSIONS_BROWSER_VALUE_STORE_LEVELDB_SCOPED_DATABASE_H_
diff --git a/chromium/extensions/browser/value_store/leveldb_scoped_database_unittest.cc b/chromium/extensions/browser/value_store/leveldb_scoped_database_unittest.cc
new file mode 100644
index 00000000000..738802bfc98
--- /dev/null
+++ b/chromium/extensions/browser/value_store/leveldb_scoped_database_unittest.cc
@@ -0,0 +1,205 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/value_store/leveldb_scoped_database.h"
+
+#include <stddef.h>
+
+#include <map>
+#include <string>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ref_counted.h"
+#include "base/values.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+const char kTestUMAClientName[] = "Test";
+} // namespace
+
+class LeveldbScopedDatabaseUnitTest : public testing::Test {
+ public:
+ LeveldbScopedDatabaseUnitTest() {}
+ ~LeveldbScopedDatabaseUnitTest() override {}
+
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(database_dir_.CreateUniqueTempDir());
+ db_ = new LeveldbScopedDatabase(kTestUMAClientName, database_dir_.path());
+ }
+
+ void TearDown() override {
+ db_ = nullptr;
+ base::DeleteFile(database_dir_.path(), true);
+ }
+
+ ValueStore::Status ReadAllValues(
+ std::map<std::string, std::string>* values) const {
+ values->clear();
+ leveldb::ReadOptions read_options;
+ read_options.verify_checksums = true;
+ scoped_ptr<leveldb::Iterator> iterator;
+ ValueStore::Status status = db_->CreateIterator(read_options, &iterator);
+ if (!status.ok())
+ return status;
+ iterator->SeekToFirst();
+ while (iterator->Valid()) {
+ // The LeveldbProfileDatabase writes all values as JSON strings.
+ // This method returns the encoded strings.
+ (*values)[iterator->key().ToString()] = iterator->value().ToString();
+ iterator->Next();
+ }
+ return db_->ToValueStoreError(iterator->status());
+ }
+
+ content::TestBrowserThreadBundle thread_bundle_;
+ base::ScopedTempDir database_dir_;
+ scoped_refptr<LeveldbScopedDatabase> db_;
+};
+
+TEST_F(LeveldbScopedDatabaseUnitTest, TestSplitKey) {
+ std::string scope;
+ std::string key;
+ EXPECT_TRUE(LeveldbScopedDatabase::SplitKey("s:k", &scope, &key));
+ EXPECT_EQ(scope, "s");
+ EXPECT_EQ(key, "k");
+ EXPECT_TRUE(LeveldbScopedDatabase::SplitKey("s:", &scope, &key));
+ EXPECT_EQ(scope, "s");
+ EXPECT_EQ(key, "");
+ EXPECT_TRUE(LeveldbScopedDatabase::SplitKey("s:k:o", &scope, &key));
+ EXPECT_EQ(scope, "s");
+ EXPECT_EQ(key, "k:o");
+ EXPECT_FALSE(LeveldbScopedDatabase::SplitKey("s-k", &scope, &key));
+ EXPECT_FALSE(LeveldbScopedDatabase::SplitKey("", &scope, &key));
+ EXPECT_FALSE(LeveldbScopedDatabase::SplitKey(":k", &scope, &key));
+}
+
+TEST_F(LeveldbScopedDatabaseUnitTest, TestCreateKey) {
+ std::string scoped_key;
+
+ EXPECT_TRUE(LeveldbScopedDatabase::CreateKey("scope", "key", &scoped_key));
+ EXPECT_EQ("scope:key", scoped_key);
+ EXPECT_TRUE(LeveldbScopedDatabase::CreateKey("scope", "", &scoped_key));
+ EXPECT_EQ("scope:", scoped_key);
+ EXPECT_TRUE(LeveldbScopedDatabase::CreateKey("scope", "key:o", &scoped_key));
+ EXPECT_EQ("scope:key:o", scoped_key);
+
+ EXPECT_FALSE(LeveldbScopedDatabase::CreateKey("", "key", &scoped_key));
+ EXPECT_FALSE(
+ LeveldbScopedDatabase::CreateKey("scope:withdelim", "key", &scoped_key));
+}
+
+TEST_F(LeveldbScopedDatabaseUnitTest, TestWrite) {
+ std::map<std::string, std::string> db_values;
+ EXPECT_TRUE(ReadAllValues(&db_values).ok());
+ EXPECT_EQ(0u, db_values.size());
+
+ base::DictionaryValue scope1_values;
+ scope1_values.SetString("s1_key1", "s1_value1");
+ scope1_values.SetString("s1_key2", "s1_value2");
+ EXPECT_FALSE(db_->Write("", scope1_values).ok());
+ EXPECT_TRUE(db_->Write("scope1", scope1_values).ok());
+
+ base::DictionaryValue scope2_values;
+ scope2_values.SetString("s2_key1", "s2_value1");
+ scope2_values.SetString("s2_key2", "s2_value2");
+ EXPECT_TRUE(db_->Write("scope2", scope2_values).ok());
+
+ // Read all values using raw leveldb. Values are JSON strings.
+ EXPECT_TRUE(ReadAllValues(&db_values).ok());
+ EXPECT_EQ(4u, db_values.size());
+ EXPECT_EQ("\"s1_value1\"", db_values["scope1:s1_key1"]);
+ EXPECT_EQ("\"s1_value2\"", db_values["scope1:s1_key2"]);
+ EXPECT_EQ("\"s2_value1\"", db_values["scope2:s2_key1"]);
+ EXPECT_EQ("\"s2_value2\"", db_values["scope2:s2_key2"]);
+
+ // Intentionally overwrite value (with a new value).
+ base::DictionaryValue changed_scope2_values;
+ changed_scope2_values.SetString("s2_key1", "s2_value1");
+ changed_scope2_values.SetString("s2_key2", "s2_value3");
+ EXPECT_TRUE(db_->Write("scope2", changed_scope2_values).ok());
+
+ EXPECT_TRUE(ReadAllValues(&db_values).ok());
+ EXPECT_EQ(4u, db_values.size());
+ EXPECT_EQ("\"s1_value1\"", db_values["scope1:s1_key1"]);
+ EXPECT_EQ("\"s1_value2\"", db_values["scope1:s1_key2"]);
+ EXPECT_EQ("\"s2_value1\"", db_values["scope2:s2_key1"]);
+ EXPECT_EQ("\"s2_value3\"", db_values["scope2:s2_key2"]);
+}
+
+TEST_F(LeveldbScopedDatabaseUnitTest, TestRead) {
+ base::DictionaryValue scope1_values;
+ scope1_values.SetString("s1_key1", "s1_value1");
+ scope1_values.SetString("s1_key2", "s1_value2");
+ EXPECT_TRUE(db_->Write("scope1", scope1_values).ok());
+
+ base::DictionaryValue scope2_values;
+ scope2_values.SetString("s2_key1", "s2_value1");
+ scope2_values.SetString("s2_key2", "s2_value2");
+ EXPECT_TRUE(db_->Write("scope2", scope2_values).ok());
+
+ // And test an empty scope.
+ EXPECT_FALSE(db_->Write("", scope2_values).ok());
+
+ base::DictionaryValue read_s1_vals;
+ EXPECT_FALSE(db_->Read("", &read_s1_vals).ok());
+ EXPECT_TRUE(db_->Read("scope1", &read_s1_vals).ok());
+ EXPECT_TRUE(scope1_values.Equals(&read_s1_vals));
+
+ base::DictionaryValue read_s2_vals;
+ EXPECT_TRUE(db_->Read("scope2", &read_s2_vals).ok());
+ EXPECT_TRUE(scope2_values.Equals(&read_s2_vals));
+}
+
+TEST_F(LeveldbScopedDatabaseUnitTest, TestEmptyValue) {
+ base::DictionaryValue values;
+ values.SetString("s1_key1", "");
+ EXPECT_TRUE(db_->Write("scope1", values).ok());
+
+ scoped_ptr<base::Value> value;
+ ASSERT_TRUE(db_->Read("scope1", "s1_key1", &value).ok());
+ std::string str;
+ EXPECT_TRUE(value->GetAsString(&str));
+ EXPECT_EQ(str, "");
+}
+
+TEST_F(LeveldbScopedDatabaseUnitTest, TestValueContainingDelimiter) {
+ base::DictionaryValue values;
+ values.SetString("s1_key1", "with:delimiter");
+ EXPECT_TRUE(db_->Write("scope1", values).ok());
+
+ scoped_ptr<base::Value> value;
+ ASSERT_TRUE(db_->Read("scope1", "s1_key1", &value).ok());
+ std::string str;
+ EXPECT_TRUE(value->GetAsString(&str));
+ EXPECT_EQ(str, "with:delimiter");
+}
+
+TEST_F(LeveldbScopedDatabaseUnitTest, TestDeleteValues) {
+ base::DictionaryValue scope1_values;
+ scope1_values.SetString("s1_key1", "s1_value1");
+ scope1_values.SetString("s1_key2", "s1_value2");
+ EXPECT_TRUE(db_->Write("scope1", scope1_values).ok());
+
+ base::DictionaryValue scope2_values;
+ scope2_values.SetString("s2_key1", "s2_value1");
+ scope2_values.SetString("s2_key2", "s2_value2");
+ EXPECT_TRUE(db_->Write("scope2", scope2_values).ok());
+
+ std::vector<std::string> keys;
+ keys.push_back("s2_key1");
+ keys.push_back("s2_key2");
+ keys.push_back("s1_key1");
+ EXPECT_TRUE(db_->DeleteValues("scope2", keys).ok());
+
+ base::DictionaryValue read_s1_vals;
+ EXPECT_TRUE(db_->Read("scope1", &read_s1_vals).ok());
+ EXPECT_TRUE(scope1_values.Equals(&read_s1_vals));
+
+ base::DictionaryValue read_s2_vals;
+ EXPECT_TRUE(db_->Read("scope2", &read_s2_vals).ok());
+ EXPECT_TRUE(read_s2_vals.empty());
+}
diff --git a/chromium/extensions/browser/value_store/leveldb_value_store.cc b/chromium/extensions/browser/value_store/leveldb_value_store.cc
new file mode 100644
index 00000000000..a65aa8b85b9
--- /dev/null
+++ b/chromium/extensions/browser/value_store/leveldb_value_store.cc
@@ -0,0 +1,301 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/value_store/leveldb_value_store.h"
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/trace_event/memory_dump_manager.h"
+#include "base/trace_event/process_memory_dump.h"
+#include "content/public/browser/browser_thread.h"
+#include "third_party/leveldatabase/env_chromium.h"
+#include "third_party/leveldatabase/src/include/leveldb/iterator.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+
+using base::StringPiece;
+using content::BrowserThread;
+
+namespace {
+
+const char kInvalidJson[] = "Invalid JSON";
+const char kCannotSerialize[] = "Cannot serialize value to JSON";
+
+} // namespace
+
+LeveldbValueStore::LeveldbValueStore(const std::string& uma_client_name,
+ const base::FilePath& db_path)
+ : LazyLevelDb(uma_client_name, db_path) {
+ base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
+ this, "LeveldbValueStore", base::ThreadTaskRunnerHandle::Get());
+}
+
+LeveldbValueStore::~LeveldbValueStore() {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
+ this);
+}
+
+size_t LeveldbValueStore::GetBytesInUse(const std::string& key) {
+ // Let SettingsStorageQuotaEnforcer implement this.
+ NOTREACHED() << "Not implemented";
+ return 0;
+}
+
+size_t LeveldbValueStore::GetBytesInUse(
+ const std::vector<std::string>& keys) {
+ // Let SettingsStorageQuotaEnforcer implement this.
+ NOTREACHED() << "Not implemented";
+ return 0;
+}
+
+size_t LeveldbValueStore::GetBytesInUse() {
+ // Let SettingsStorageQuotaEnforcer implement this.
+ NOTREACHED() << "Not implemented";
+ return 0;
+}
+
+ValueStore::ReadResult LeveldbValueStore::Get(const std::string& key) {
+ return Get(std::vector<std::string>(1, key));
+}
+
+ValueStore::ReadResult LeveldbValueStore::Get(
+ const std::vector<std::string>& keys) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+
+ Status status = EnsureDbIsOpen();
+ if (!status.ok())
+ return MakeReadResult(status);
+
+ scoped_ptr<base::DictionaryValue> settings(new base::DictionaryValue());
+
+ for (const std::string& key : keys) {
+ scoped_ptr<base::Value> setting;
+ status.Merge(Read(key, &setting));
+ if (!status.ok())
+ return MakeReadResult(status);
+ if (setting)
+ settings->SetWithoutPathExpansion(key, setting.release());
+ }
+
+ return MakeReadResult(std::move(settings), status);
+}
+
+ValueStore::ReadResult LeveldbValueStore::Get() {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+
+ Status status = EnsureDbIsOpen();
+ if (!status.ok())
+ return MakeReadResult(status);
+
+ base::JSONReader json_reader;
+ scoped_ptr<base::DictionaryValue> settings(new base::DictionaryValue());
+
+ scoped_ptr<leveldb::Iterator> it(db()->NewIterator(read_options()));
+ for (it->SeekToFirst(); it->Valid(); it->Next()) {
+ std::string key = it->key().ToString();
+ scoped_ptr<base::Value> value =
+ json_reader.Read(StringPiece(it->value().data(), it->value().size()));
+ if (!value) {
+ return MakeReadResult(
+ Status(CORRUPTION, Delete(key).ok() ? VALUE_RESTORE_DELETE_SUCCESS
+ : VALUE_RESTORE_DELETE_FAILURE,
+ kInvalidJson));
+ }
+ settings->SetWithoutPathExpansion(key, std::move(value));
+ }
+
+ if (!it->status().ok()) {
+ status.Merge(ToValueStoreError(it->status()));
+ return MakeReadResult(status);
+ }
+
+ return MakeReadResult(std::move(settings), status);
+}
+
+ValueStore::WriteResult LeveldbValueStore::Set(WriteOptions options,
+ const std::string& key,
+ const base::Value& value) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+
+ Status status = EnsureDbIsOpen();
+ if (!status.ok())
+ return MakeWriteResult(status);
+
+ leveldb::WriteBatch batch;
+ scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
+ status.Merge(AddToBatch(options, key, value, &batch, changes.get()));
+ if (!status.ok())
+ return MakeWriteResult(status);
+
+ status.Merge(WriteToDb(&batch));
+ return status.ok() ? MakeWriteResult(std::move(changes), status)
+ : MakeWriteResult(status);
+}
+
+ValueStore::WriteResult LeveldbValueStore::Set(
+ WriteOptions options,
+ const base::DictionaryValue& settings) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+
+ Status status = EnsureDbIsOpen();
+ if (!status.ok())
+ return MakeWriteResult(status);
+
+ leveldb::WriteBatch batch;
+ scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
+
+ for (base::DictionaryValue::Iterator it(settings);
+ !it.IsAtEnd(); it.Advance()) {
+ status.Merge(
+ AddToBatch(options, it.key(), it.value(), &batch, changes.get()));
+ if (!status.ok())
+ return MakeWriteResult(status);
+ }
+
+ status.Merge(WriteToDb(&batch));
+ return status.ok() ? MakeWriteResult(std::move(changes), status)
+ : MakeWriteResult(status);
+}
+
+ValueStore::WriteResult LeveldbValueStore::Remove(const std::string& key) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ return Remove(std::vector<std::string>(1, key));
+}
+
+ValueStore::WriteResult LeveldbValueStore::Remove(
+ const std::vector<std::string>& keys) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+
+ Status status = EnsureDbIsOpen();
+ if (!status.ok())
+ return MakeWriteResult(status);
+
+ leveldb::WriteBatch batch;
+ scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
+
+ for (const std::string& key : keys) {
+ scoped_ptr<base::Value> old_value;
+ status.Merge(Read(key, &old_value));
+ if (!status.ok())
+ return MakeWriteResult(status);
+
+ if (old_value) {
+ changes->push_back(ValueStoreChange(key, old_value.release(), NULL));
+ batch.Delete(key);
+ }
+ }
+
+ leveldb::Status ldb_status = db()->Write(leveldb::WriteOptions(), &batch);
+ if (!ldb_status.ok() && !ldb_status.IsNotFound()) {
+ status.Merge(ToValueStoreError(ldb_status));
+ return MakeWriteResult(status);
+ }
+ return MakeWriteResult(std::move(changes), status);
+}
+
+ValueStore::WriteResult LeveldbValueStore::Clear() {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+
+ scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
+
+ ReadResult read_result = Get();
+ if (!read_result->status().ok())
+ return MakeWriteResult(read_result->status());
+
+ base::DictionaryValue& whole_db = read_result->settings();
+ while (!whole_db.empty()) {
+ std::string next_key = base::DictionaryValue::Iterator(whole_db).key();
+ scoped_ptr<base::Value> next_value;
+ whole_db.RemoveWithoutPathExpansion(next_key, &next_value);
+ changes->push_back(ValueStoreChange(next_key, next_value.release(), NULL));
+ }
+
+ DeleteDbFile();
+ return MakeWriteResult(std::move(changes), read_result->status());
+}
+
+bool LeveldbValueStore::WriteToDbForTest(leveldb::WriteBatch* batch) {
+ Status status = EnsureDbIsOpen();
+ if (!status.ok())
+ return false;
+ return WriteToDb(batch).ok();
+}
+
+bool LeveldbValueStore::OnMemoryDump(
+ const base::trace_event::MemoryDumpArgs& args,
+ base::trace_event::ProcessMemoryDump* pmd) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+
+ // Return true so that the provider is not disabled.
+ if (!db())
+ return true;
+
+ std::string value;
+ uint64_t size;
+ bool res = db()->GetProperty("leveldb.approximate-memory-usage", &value);
+ DCHECK(res);
+ res = base::StringToUint64(value, &size);
+ DCHECK(res);
+
+ auto dump = pmd->CreateAllocatorDump(base::StringPrintf(
+ "leveldb/value_store/%s/%p", open_histogram_name().c_str(), this));
+ dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
+ base::trace_event::MemoryAllocatorDump::kUnitsBytes, size);
+
+ // Memory is allocated from system allocator (malloc).
+ const char* system_allocator_name =
+ base::trace_event::MemoryDumpManager::GetInstance()
+ ->system_allocator_pool_name();
+ if (system_allocator_name)
+ pmd->AddSuballocation(dump->guid(), system_allocator_name);
+
+ return true;
+}
+
+ValueStore::Status LeveldbValueStore::AddToBatch(
+ ValueStore::WriteOptions options,
+ const std::string& key,
+ const base::Value& value,
+ leveldb::WriteBatch* batch,
+ ValueStoreChangeList* changes) {
+ bool write_new_value = true;
+
+ if (!(options & NO_GENERATE_CHANGES)) {
+ scoped_ptr<base::Value> old_value;
+ Status status = Read(key, &old_value);
+ if (!status.ok())
+ return status;
+ if (!old_value || !old_value->Equals(&value)) {
+ changes->push_back(
+ ValueStoreChange(key, old_value.release(), value.DeepCopy()));
+ } else {
+ write_new_value = false;
+ }
+ }
+
+ if (write_new_value) {
+ std::string value_as_json;
+ if (!base::JSONWriter::Write(value, &value_as_json))
+ return Status(OTHER_ERROR, kCannotSerialize);
+ batch->Put(key, value_as_json);
+ }
+
+ return Status();
+}
+
+ValueStore::Status LeveldbValueStore::WriteToDb(leveldb::WriteBatch* batch) {
+ return ToValueStoreError(db()->Write(write_options(), batch));
+}
diff --git a/chromium/extensions/browser/value_store/leveldb_value_store.h b/chromium/extensions/browser/value_store/leveldb_value_store.h
new file mode 100644
index 00000000000..41e91a3c3fc
--- /dev/null
+++ b/chromium/extensions/browser/value_store/leveldb_value_store.h
@@ -0,0 +1,83 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_VALUE_STORE_LEVELDB_VALUE_STORE_H_
+#define EXTENSIONS_BROWSER_VALUE_STORE_LEVELDB_VALUE_STORE_H_
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/trace_event/memory_dump_provider.h"
+#include "extensions/browser/value_store/lazy_leveldb.h"
+#include "extensions/browser/value_store/value_store.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+
+namespace base {
+class HistogramBase;
+} // namespace base
+
+// Value store area, backed by a leveldb database.
+// All methods must be run on the FILE thread.
+class LeveldbValueStore : public ValueStore,
+ public LazyLevelDb,
+ public base::trace_event::MemoryDumpProvider {
+ public:
+ // Creates a database bound to |path|. The underlying database won't be
+ // opened (i.e. may not be created) until one of the get/set/etc methods are
+ // called - this is because opening the database may fail, and extensions
+ // need to be notified of that, but we don't want to permanently give up.
+ //
+ // Must be created on the FILE thread.
+ LeveldbValueStore(const std::string& uma_client_name,
+ const base::FilePath& path);
+
+ // Must be deleted on the FILE thread.
+ ~LeveldbValueStore() override;
+
+ // ValueStore implementation.
+ size_t GetBytesInUse(const std::string& key) override;
+ size_t GetBytesInUse(const std::vector<std::string>& keys) override;
+ size_t GetBytesInUse() override;
+ ReadResult Get(const std::string& key) override;
+ ReadResult Get(const std::vector<std::string>& keys) override;
+ ReadResult Get() override;
+ WriteResult Set(ValueStore::WriteOptions options,
+ const std::string& key,
+ const base::Value& value) override;
+ WriteResult Set(ValueStore::WriteOptions options,
+ const base::DictionaryValue& values) override;
+ WriteResult Remove(const std::string& key) override;
+ WriteResult Remove(const std::vector<std::string>& keys) override;
+ WriteResult Clear() override;
+
+ // Write directly to the backing levelDB. Only used for testing to cause
+ // corruption in the database.
+ bool WriteToDbForTest(leveldb::WriteBatch* batch);
+
+ // base::trace_event::MemoryDumpProvider implementation.
+ bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
+ base::trace_event::ProcessMemoryDump* pmd) override;
+
+ private:
+ // Adds a setting to a WriteBatch, and logs the change in |changes|. For use
+ // with WriteToDb.
+ ValueStore::Status AddToBatch(ValueStore::WriteOptions options,
+ const std::string& key,
+ const base::Value& value,
+ leveldb::WriteBatch* batch,
+ ValueStoreChangeList* changes);
+
+ // Commits the changes in |batch| to the database.
+ ValueStore::Status WriteToDb(leveldb::WriteBatch* batch);
+
+ DISALLOW_COPY_AND_ASSIGN(LeveldbValueStore);
+};
+
+#endif // EXTENSIONS_BROWSER_VALUE_STORE_LEVELDB_VALUE_STORE_H_
diff --git a/chromium/extensions/browser/value_store/leveldb_value_store_unittest.cc b/chromium/extensions/browser/value_store/leveldb_value_store_unittest.cc
new file mode 100644
index 00000000000..cf532240417
--- /dev/null
+++ b/chromium/extensions/browser/value_store/leveldb_value_store_unittest.cc
@@ -0,0 +1,190 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/value_store/value_store_unittest.h"
+
+#include <stddef.h>
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/values.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "extensions/browser/value_store/leveldb_value_store.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+
+namespace {
+
+const char kDatabaseUMAClientName[] = "Test";
+
+ValueStore* Param(const base::FilePath& file_path) {
+ return new LeveldbValueStore(kDatabaseUMAClientName, file_path);
+}
+
+} // namespace
+
+INSTANTIATE_TEST_CASE_P(
+ LeveldbValueStore,
+ ValueStoreTest,
+ testing::Values(&Param));
+
+class LeveldbValueStoreUnitTest : public testing::Test {
+ public:
+ LeveldbValueStoreUnitTest() {}
+ ~LeveldbValueStoreUnitTest() override {}
+
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(database_dir_.CreateUniqueTempDir());
+ CreateStore();
+ ASSERT_TRUE(store_->Get()->status().ok());
+ }
+
+ void TearDown() override {
+ if (!store_)
+ return;
+ store_->Clear();
+ CloseStore();
+ }
+
+ void CloseStore() { store_.reset(); }
+
+ void CreateStore() {
+ store_.reset(
+ new LeveldbValueStore(kDatabaseUMAClientName, database_path()));
+ }
+
+ LeveldbValueStore* store() { return store_.get(); }
+ const base::FilePath& database_path() { return database_dir_.path(); }
+
+ private:
+ scoped_ptr<LeveldbValueStore> store_;
+ base::ScopedTempDir database_dir_;
+
+ content::TestBrowserThreadBundle thread_bundle_;
+};
+
+// Check that we can restore a single corrupted key in the LeveldbValueStore.
+TEST_F(LeveldbValueStoreUnitTest, RestoreKeyTest) {
+ const char kNotCorruptKey[] = "not-corrupt";
+ const char kValue[] = "value";
+
+ // Insert a valid pair.
+ scoped_ptr<base::Value> value(new base::StringValue(kValue));
+ ASSERT_TRUE(store()
+ ->Set(ValueStore::DEFAULTS, kNotCorruptKey, *value)
+ ->status().ok());
+
+ // Insert a corrupt pair.
+ const char kCorruptKey[] = "corrupt";
+ leveldb::WriteBatch batch;
+ batch.Put(kCorruptKey, "[{(.*+\"\'\\");
+ ASSERT_TRUE(store()->WriteToDbForTest(&batch));
+
+ // Verify corruption (the first Get will return corruption).
+ ValueStore::ReadResult result = store()->Get(kCorruptKey);
+ ASSERT_FALSE(result->status().ok());
+ ASSERT_EQ(ValueStore::CORRUPTION, result->status().code);
+
+ // Verify restored (was deleted in the first Get).
+ result = store()->Get(kCorruptKey);
+ EXPECT_TRUE(result->status().ok()) << "Get result not OK: "
+ << result->status().message;
+ EXPECT_TRUE(result->settings().empty());
+
+ // Verify that the valid pair is still present.
+ result = store()->Get(kNotCorruptKey);
+ EXPECT_TRUE(result->status().ok());
+ EXPECT_TRUE(result->settings().HasKey(kNotCorruptKey));
+ std::string value_string;
+ EXPECT_TRUE(result->settings().GetString(kNotCorruptKey, &value_string));
+ EXPECT_EQ(kValue, value_string);
+}
+
+// Test that the Restore() method does not just delete the entire database
+// (unless absolutely necessary), and instead only removes corrupted keys.
+TEST_F(LeveldbValueStoreUnitTest, RestoreDoesMinimumNecessary) {
+ const char* kNotCorruptKeys[] = {"a", "n", "z"};
+ const size_t kNotCorruptKeysSize = 3u;
+ const char kCorruptKey1[] = "f";
+ const char kCorruptKey2[] = "s";
+ const char kValue[] = "value";
+ const char kCorruptValue[] = "[{(.*+\"\'\\";
+
+ // Insert a collection of non-corrupted pairs.
+ scoped_ptr<base::Value> value(new base::StringValue(kValue));
+ for (size_t i = 0; i < kNotCorruptKeysSize; ++i) {
+ ASSERT_TRUE(store()
+ ->Set(ValueStore::DEFAULTS, kNotCorruptKeys[i], *value)
+ ->status().ok());
+ }
+
+ // Insert a few corrupted pairs.
+ leveldb::WriteBatch batch;
+ batch.Put(kCorruptKey1, kCorruptValue);
+ batch.Put(kCorruptKey2, kCorruptValue);
+ ASSERT_TRUE(store()->WriteToDbForTest(&batch));
+
+ // Verify that we broke it and that it was repaired by the value store.
+ ValueStore::ReadResult result = store()->Get();
+ ASSERT_FALSE(result->status().ok());
+ ASSERT_EQ(ValueStore::CORRUPTION, result->status().code);
+ ASSERT_EQ(ValueStore::VALUE_RESTORE_DELETE_SUCCESS,
+ result->status().restore_status);
+
+ // We should still have all valid pairs present in the database.
+ std::string value_string;
+ for (size_t i = 0; i < kNotCorruptKeysSize; ++i) {
+ result = store()->Get(kNotCorruptKeys[i]);
+ EXPECT_TRUE(result->status().ok());
+ ASSERT_EQ(ValueStore::RESTORE_NONE, result->status().restore_status);
+ EXPECT_TRUE(result->settings().HasKey(kNotCorruptKeys[i]));
+ EXPECT_TRUE(
+ result->settings().GetString(kNotCorruptKeys[i], &value_string));
+ EXPECT_EQ(kValue, value_string);
+ }
+}
+
+// Test that the LeveldbValueStore can recover in the case of a CATastrophic
+// failure and we have total corruption. In this case, the database is plagued
+// by LolCats.
+// Full corruption has been known to happen occasionally in strange edge cases,
+// such as after users use Windows Restore. We can't prevent it, but we need to
+// be able to handle it smoothly.
+TEST_F(LeveldbValueStoreUnitTest, RestoreFullDatabase) {
+ const std::string kLolCats("I can haz leveldb filez?");
+ const char* kNotCorruptKeys[] = {"a", "n", "z"};
+ const size_t kNotCorruptKeysSize = 3u;
+ const char kValue[] = "value";
+
+ // Generate a database.
+ scoped_ptr<base::Value> value(new base::StringValue(kValue));
+ for (size_t i = 0; i < kNotCorruptKeysSize; ++i) {
+ ASSERT_TRUE(store()
+ ->Set(ValueStore::DEFAULTS, kNotCorruptKeys[i], *value)
+ ->status().ok());
+ }
+
+ // Close it (so we remove the lock), and replace all files with LolCats.
+ CloseStore();
+ base::FileEnumerator enumerator(
+ database_path(), true /* recursive */, base::FileEnumerator::FILES);
+ for (base::FilePath file = enumerator.Next(); !file.empty();
+ file = enumerator.Next()) {
+ // WriteFile() failure is a result of -1.
+ ASSERT_NE(base::WriteFile(file, kLolCats.c_str(), kLolCats.length()), -1);
+ }
+ CreateStore();
+
+ // We couldn't recover anything, but we should be in a sane state again.
+ ValueStore::ReadResult result = store()->Get();
+ ASSERT_EQ(ValueStore::DB_RESTORE_REPAIR_SUCCESS,
+ result->status().restore_status);
+ EXPECT_TRUE(result->status().ok());
+ EXPECT_EQ(0u, result->settings().size());
+}
diff --git a/chromium/extensions/browser/value_store/test_value_store_factory.cc b/chromium/extensions/browser/value_store/test_value_store_factory.cc
new file mode 100644
index 00000000000..f5135dfa16c
--- /dev/null
+++ b/chromium/extensions/browser/value_store/test_value_store_factory.cc
@@ -0,0 +1,190 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/value_store/test_value_store_factory.h"
+
+#include "extensions/browser/value_store/leveldb_value_store.h"
+#include "extensions/browser/value_store/testing_value_store.h"
+
+namespace {
+
+const char kUMAClientName[] = "Test";
+
+} // namespace
+
+namespace extensions {
+
+using SettingsNamespace = settings_namespace::Namespace;
+
+TestValueStoreFactory::StorageHelper::StorageHelper() = default;
+
+TestValueStoreFactory::StorageHelper::~StorageHelper() = default;
+
+std::set<ExtensionId>
+TestValueStoreFactory::StorageHelper::GetKnownExtensionIDs(
+ ModelType model_type) const {
+ std::set<ExtensionId> ids;
+ switch (model_type) {
+ case ValueStoreFactory::ModelType::APP:
+ for (const auto& key : app_stores_)
+ ids.insert(key.first);
+ break;
+ case ValueStoreFactory::ModelType::EXTENSION:
+ for (const auto& key : extension_stores_)
+ ids.insert(key.first);
+ break;
+ }
+ return ids;
+}
+
+void TestValueStoreFactory::StorageHelper::Reset() {
+ app_stores_.clear();
+ extension_stores_.clear();
+}
+
+ValueStore* TestValueStoreFactory::StorageHelper::AddValueStore(
+ const ExtensionId& extension_id,
+ ValueStore* value_store,
+ ModelType model_type) {
+ if (model_type == ValueStoreFactory::ModelType::APP) {
+ DCHECK(app_stores_.find(extension_id) == app_stores_.end());
+ app_stores_[extension_id] = value_store;
+ } else {
+ DCHECK(extension_stores_.find(extension_id) == extension_stores_.end());
+ extension_stores_[extension_id] = value_store;
+ }
+ return value_store;
+}
+
+void TestValueStoreFactory::StorageHelper::DeleteSettings(
+ const ExtensionId& extension_id,
+ ModelType model_type) {
+ switch (model_type) {
+ case ValueStoreFactory::ModelType::APP:
+ app_stores_.erase(extension_id);
+ break;
+ case ValueStoreFactory::ModelType::EXTENSION:
+ extension_stores_.erase(extension_id);
+ break;
+ }
+}
+
+bool TestValueStoreFactory::StorageHelper::HasSettings(
+ const ExtensionId& extension_id,
+ ModelType model_type) const {
+ switch (model_type) {
+ case ValueStoreFactory::ModelType::APP:
+ return app_stores_.find(extension_id) != app_stores_.end();
+ case ValueStoreFactory::ModelType::EXTENSION:
+ return extension_stores_.find(extension_id) != extension_stores_.end();
+ }
+ NOTREACHED();
+ return false;
+}
+
+ValueStore* TestValueStoreFactory::StorageHelper::GetExisting(
+ const ExtensionId& extension_id) const {
+ auto it = app_stores_.find(extension_id);
+ if (it != app_stores_.end())
+ return it->second;
+ it = extension_stores_.find(extension_id);
+ if (it != extension_stores_.end())
+ return it->second;
+ return nullptr;
+}
+
+TestValueStoreFactory::TestValueStoreFactory() = default;
+
+TestValueStoreFactory::TestValueStoreFactory(const base::FilePath& db_path)
+ : db_path_(db_path) {}
+
+TestValueStoreFactory::~TestValueStoreFactory() {}
+
+scoped_ptr<ValueStore> TestValueStoreFactory::CreateRulesStore() {
+ if (db_path_.empty())
+ last_created_store_ = new TestingValueStore();
+ else
+ last_created_store_ = new LeveldbValueStore(kUMAClientName, db_path_);
+ return make_scoped_ptr(last_created_store_);
+}
+
+scoped_ptr<ValueStore> TestValueStoreFactory::CreateStateStore() {
+ return CreateRulesStore();
+}
+
+TestValueStoreFactory::StorageHelper& TestValueStoreFactory::GetStorageHelper(
+ SettingsNamespace settings_namespace) {
+ switch (settings_namespace) {
+ case settings_namespace::LOCAL:
+ return local_helper_;
+ case settings_namespace::SYNC:
+ return sync_helper_;
+ case settings_namespace::MANAGED:
+ return managed_helper_;
+ case settings_namespace::INVALID:
+ break;
+ }
+ NOTREACHED();
+ return local_helper_;
+}
+
+scoped_ptr<ValueStore> TestValueStoreFactory::CreateSettingsStore(
+ SettingsNamespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) {
+ scoped_ptr<ValueStore> settings_store(CreateRulesStore());
+ // Note: This factory is purposely keeping the raw pointers to each ValueStore
+ // created. Tests using TestValueStoreFactory must be careful to keep
+ // those ValueStore's alive for the duration of their test.
+ GetStorageHelper(settings_namespace)
+ .AddValueStore(extension_id, settings_store.get(), model_type);
+ return settings_store;
+}
+
+ValueStore* TestValueStoreFactory::LastCreatedStore() const {
+ return last_created_store_;
+}
+
+void TestValueStoreFactory::DeleteSettings(SettingsNamespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) {
+ GetStorageHelper(settings_namespace).DeleteSettings(extension_id, model_type);
+}
+
+bool TestValueStoreFactory::HasSettings(SettingsNamespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) {
+ return GetStorageHelper(settings_namespace)
+ .HasSettings(extension_id, model_type);
+}
+
+std::set<ExtensionId> TestValueStoreFactory::GetKnownExtensionIDs(
+ SettingsNamespace settings_namespace,
+ ModelType model_type) const {
+ return const_cast<TestValueStoreFactory*>(this)
+ ->GetStorageHelper(settings_namespace)
+ .GetKnownExtensionIDs(model_type);
+}
+
+ValueStore* TestValueStoreFactory::GetExisting(
+ const ExtensionId& extension_id) const {
+ ValueStore* existing_store = local_helper_.GetExisting(extension_id);
+ if (existing_store)
+ return existing_store;
+ existing_store = sync_helper_.GetExisting(extension_id);
+ if (existing_store)
+ return existing_store;
+ existing_store = managed_helper_.GetExisting(extension_id);
+ DCHECK(existing_store != nullptr);
+ return existing_store;
+}
+
+void TestValueStoreFactory::Reset() {
+ last_created_store_ = nullptr;
+ local_helper_.Reset();
+ sync_helper_.Reset();
+ managed_helper_.Reset();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/value_store/test_value_store_factory.h b/chromium/extensions/browser/value_store/test_value_store_factory.h
new file mode 100644
index 00000000000..86fbee77c8a
--- /dev/null
+++ b/chromium/extensions/browser/value_store/test_value_store_factory.h
@@ -0,0 +1,95 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_VALUE_STORE_TEST_VALUE_STORE_FACTORY_H_
+#define EXTENSIONS_BROWSER_VALUE_STORE_TEST_VALUE_STORE_FACTORY_H_
+
+#include <map>
+#include <set>
+
+#include "base/memory/scoped_ptr.h"
+#include "extensions/browser/value_store/value_store_factory.h"
+#include "extensions/common/extension.h"
+
+class ValueStore;
+
+namespace extensions {
+
+// Used for tests when a new test ValueStore is required. Will either open a
+// database on disk (if path provided) returning a |LeveldbValueStore|.
+// Otherwise a new |TestingValueStore| instance will be returned.
+class TestValueStoreFactory : public ValueStoreFactory {
+ public:
+ TestValueStoreFactory();
+ explicit TestValueStoreFactory(const base::FilePath& db_path);
+
+ // ValueStoreFactory
+ scoped_ptr<ValueStore> CreateRulesStore() override;
+ scoped_ptr<ValueStore> CreateStateStore() override;
+ scoped_ptr<ValueStore> CreateSettingsStore(
+ settings_namespace::Namespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) override;
+ void DeleteSettings(settings_namespace::Namespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) override;
+ bool HasSettings(settings_namespace::Namespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) override;
+ std::set<ExtensionId> GetKnownExtensionIDs(
+ settings_namespace::Namespace settings_namespace,
+ ModelType model_type) const override;
+
+ // Return the last created |ValueStore|. Use with caution as this may return
+ // a dangling pointer since the creator now owns the ValueStore which can be
+ // deleted at any time.
+ ValueStore* LastCreatedStore() const;
+ // Return a previously created |ValueStore| for an extension.
+ ValueStore* GetExisting(const ExtensionId& extension_id) const;
+ // Reset this class (as if just created).
+ void Reset();
+
+ private:
+ // Manages a collection of |ValueStore|'s created for an app/extension.
+ // One of these exists for each setting type.
+ class StorageHelper {
+ public:
+ StorageHelper();
+ ~StorageHelper();
+ std::set<ExtensionId> GetKnownExtensionIDs(ModelType model_type) const;
+ ValueStore* AddValueStore(const ExtensionId& extension_id,
+ ValueStore* value_store,
+ ModelType model_type);
+ void DeleteSettings(const ExtensionId& extension_id, ModelType model_type);
+ bool HasSettings(const ExtensionId& extension_id,
+ ModelType model_type) const;
+ void Reset();
+ ValueStore* GetExisting(const ExtensionId& extension_id) const;
+
+ private:
+ std::map<ExtensionId, ValueStore*> app_stores_;
+ std::map<ExtensionId, ValueStore*> extension_stores_;
+
+ DISALLOW_COPY_AND_ASSIGN(StorageHelper);
+ };
+
+ StorageHelper& GetStorageHelper(
+ settings_namespace::Namespace settings_namespace);
+
+ ~TestValueStoreFactory() override;
+ base::FilePath db_path_;
+ ValueStore* last_created_store_ = nullptr;
+
+ // None of these value stores are owned by this factory, so care must be
+ // taken when calling GetExisting.
+ StorageHelper local_helper_;
+ StorageHelper sync_helper_;
+ StorageHelper managed_helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestValueStoreFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_VALUE_STORE_TEST_VALUE_STORE_FACTORY_H_
diff --git a/chromium/extensions/browser/value_store/testing_value_store.cc b/chromium/extensions/browser/value_store/testing_value_store.cc
new file mode 100644
index 00000000000..fee2aa0f096
--- /dev/null
+++ b/chromium/extensions/browser/value_store/testing_value_store.cc
@@ -0,0 +1,130 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/value_store/testing_value_store.h"
+
+#include <utility>
+
+#include "base/logging.h"
+
+namespace {
+
+const char kGenericErrorMessage[] = "TestingValueStore configured to error";
+
+} // namespace
+
+TestingValueStore::TestingValueStore() : read_count_(0), write_count_(0) {}
+
+TestingValueStore::~TestingValueStore() {}
+
+void TestingValueStore::set_status_code(StatusCode status_code) {
+ status_ = ValueStore::Status(status_code, kGenericErrorMessage);
+}
+
+size_t TestingValueStore::GetBytesInUse(const std::string& key) {
+ // Let SettingsStorageQuotaEnforcer implement this.
+ NOTREACHED() << "Not implemented";
+ return 0;
+}
+
+size_t TestingValueStore::GetBytesInUse(
+ const std::vector<std::string>& keys) {
+ // Let SettingsStorageQuotaEnforcer implement this.
+ NOTREACHED() << "Not implemented";
+ return 0;
+}
+
+size_t TestingValueStore::GetBytesInUse() {
+ // Let SettingsStorageQuotaEnforcer implement this.
+ NOTREACHED() << "Not implemented";
+ return 0;
+}
+
+ValueStore::ReadResult TestingValueStore::Get(const std::string& key) {
+ return Get(std::vector<std::string>(1, key));
+}
+
+ValueStore::ReadResult TestingValueStore::Get(
+ const std::vector<std::string>& keys) {
+ read_count_++;
+ if (!status_.ok())
+ return MakeReadResult(status_);
+
+ base::DictionaryValue* settings = new base::DictionaryValue();
+ for (std::vector<std::string>::const_iterator it = keys.begin();
+ it != keys.end(); ++it) {
+ base::Value* value = NULL;
+ if (storage_.GetWithoutPathExpansion(*it, &value)) {
+ settings->SetWithoutPathExpansion(*it, value->DeepCopy());
+ }
+ }
+ return MakeReadResult(make_scoped_ptr(settings), status_);
+}
+
+ValueStore::ReadResult TestingValueStore::Get() {
+ read_count_++;
+ if (!status_.ok())
+ return MakeReadResult(status_);
+ return MakeReadResult(make_scoped_ptr(storage_.DeepCopy()), status_);
+}
+
+ValueStore::WriteResult TestingValueStore::Set(
+ WriteOptions options, const std::string& key, const base::Value& value) {
+ base::DictionaryValue settings;
+ settings.SetWithoutPathExpansion(key, value.DeepCopy());
+ return Set(options, settings);
+}
+
+ValueStore::WriteResult TestingValueStore::Set(
+ WriteOptions options, const base::DictionaryValue& settings) {
+ write_count_++;
+ if (!status_.ok())
+ return MakeWriteResult(status_);
+
+ scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
+ for (base::DictionaryValue::Iterator it(settings);
+ !it.IsAtEnd(); it.Advance()) {
+ base::Value* old_value = NULL;
+ if (!storage_.GetWithoutPathExpansion(it.key(), &old_value) ||
+ !old_value->Equals(&it.value())) {
+ changes->push_back(
+ ValueStoreChange(
+ it.key(),
+ old_value ? old_value->DeepCopy() : old_value,
+ it.value().DeepCopy()));
+ storage_.SetWithoutPathExpansion(it.key(), it.value().DeepCopy());
+ }
+ }
+ return MakeWriteResult(std::move(changes), status_);
+}
+
+ValueStore::WriteResult TestingValueStore::Remove(const std::string& key) {
+ return Remove(std::vector<std::string>(1, key));
+}
+
+ValueStore::WriteResult TestingValueStore::Remove(
+ const std::vector<std::string>& keys) {
+ write_count_++;
+ if (!status_.ok())
+ return MakeWriteResult(status_);
+
+ scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
+ for (std::vector<std::string>::const_iterator it = keys.begin();
+ it != keys.end(); ++it) {
+ scoped_ptr<base::Value> old_value;
+ if (storage_.RemoveWithoutPathExpansion(*it, &old_value)) {
+ changes->push_back(ValueStoreChange(*it, old_value.release(), NULL));
+ }
+ }
+ return MakeWriteResult(std::move(changes), status_);
+}
+
+ValueStore::WriteResult TestingValueStore::Clear() {
+ std::vector<std::string> keys;
+ for (base::DictionaryValue::Iterator it(storage_);
+ !it.IsAtEnd(); it.Advance()) {
+ keys.push_back(it.key());
+ }
+ return Remove(keys);
+}
diff --git a/chromium/extensions/browser/value_store/testing_value_store.h b/chromium/extensions/browser/value_store/testing_value_store.h
new file mode 100644
index 00000000000..55ed6b62e95
--- /dev/null
+++ b/chromium/extensions/browser/value_store/testing_value_store.h
@@ -0,0 +1,61 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_VALUE_STORE_TESTING_VALUE_STORE_H_
+#define EXTENSIONS_BROWSER_VALUE_STORE_TESTING_VALUE_STORE_H_
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "extensions/browser/value_store/value_store.h"
+
+// ValueStore for testing, with an in-memory storage but the ability to
+// optionally fail all operations.
+class TestingValueStore : public ValueStore {
+ public:
+ TestingValueStore();
+ ~TestingValueStore() override;
+
+ // Sets the error code for requests. If OK, errors won't be thrown.
+ // Defaults to OK.
+ void set_status_code(StatusCode status_code);
+
+ // Accessors for the number of reads/writes done by this value store. Each
+ // Get* operation (except for the BytesInUse ones) counts as one read, and
+ // each Set*/Remove/Clear operation counts as one write. This is useful in
+ // tests seeking to assert that some number of reads/writes to their
+ // underlying value store have (or have not) happened.
+ int read_count() { return read_count_; }
+ int write_count() { return write_count_; }
+
+ // ValueStore implementation.
+ size_t GetBytesInUse(const std::string& key) override;
+ size_t GetBytesInUse(const std::vector<std::string>& keys) override;
+ size_t GetBytesInUse() override;
+ ReadResult Get(const std::string& key) override;
+ ReadResult Get(const std::vector<std::string>& keys) override;
+ ReadResult Get() override;
+ WriteResult Set(WriteOptions options,
+ const std::string& key,
+ const base::Value& value) override;
+ WriteResult Set(WriteOptions options,
+ const base::DictionaryValue& values) override;
+ WriteResult Remove(const std::string& key) override;
+ WriteResult Remove(const std::vector<std::string>& keys) override;
+ WriteResult Clear() override;
+
+ private:
+ base::DictionaryValue storage_;
+ int read_count_;
+ int write_count_;
+ ValueStore::Status status_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestingValueStore);
+};
+
+#endif // EXTENSIONS_BROWSER_VALUE_STORE_TESTING_VALUE_STORE_H_
diff --git a/chromium/extensions/browser/value_store/testing_value_store_unittest.cc b/chromium/extensions/browser/value_store/testing_value_store_unittest.cc
new file mode 100644
index 00000000000..487512cc16b
--- /dev/null
+++ b/chromium/extensions/browser/value_store/testing_value_store_unittest.cc
@@ -0,0 +1,24 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/value_store/value_store_unittest.h"
+
+#include "extensions/browser/value_store/testing_value_store.h"
+
+namespace extensions {
+
+namespace {
+
+ValueStore* Param(const base::FilePath& file_path) {
+ return new TestingValueStore();
+}
+
+} // namespace
+
+INSTANTIATE_TEST_CASE_P(
+ TestingValueStore,
+ ValueStoreTest,
+ testing::Values(&Param));
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/value_store/value_store.cc b/chromium/extensions/browser/value_store/value_store.cc
new file mode 100644
index 00000000000..c198f105566
--- /dev/null
+++ b/chromium/extensions/browser/value_store/value_store.cc
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/value_store/value_store.h"
+
+#include <utility>
+
+#include "base/logging.h"
+
+// Implementation of Status.
+
+ValueStore::Status::Status() : code(OK), restore_status(RESTORE_NONE) {}
+
+ValueStore::Status::Status(StatusCode code, const std::string& message)
+ : Status(code, RESTORE_NONE, message) {}
+
+ValueStore::Status::Status(StatusCode code,
+ BackingStoreRestoreStatus restore_status,
+ const std::string& message)
+ : code(code), restore_status(restore_status), message(message) {}
+
+ValueStore::Status::~Status() {}
+
+void ValueStore::Status::Merge(const Status& status) {
+ if (code == OK)
+ code = status.code;
+ if (message.empty() && !status.message.empty())
+ message = status.message;
+ if (restore_status == RESTORE_NONE)
+ restore_status = status.restore_status;
+}
+
+// Implementation of ReadResultType.
+
+ValueStore::ReadResultType::ReadResultType(
+ scoped_ptr<base::DictionaryValue> settings,
+ const Status& status)
+ : settings_(std::move(settings)), status_(status) {
+ CHECK(settings_);
+}
+
+ValueStore::ReadResultType::ReadResultType(const Status& status)
+ : status_(status) {}
+
+ValueStore::ReadResultType::~ReadResultType() {}
+
+// Implementation of WriteResultType.
+
+ValueStore::WriteResultType::WriteResultType(
+ scoped_ptr<ValueStoreChangeList> changes,
+ const Status& status)
+ : changes_(std::move(changes)), status_(status) {
+ CHECK(changes_);
+}
+
+ValueStore::WriteResultType::WriteResultType(const Status& status)
+ : status_(status) {}
+
+ValueStore::WriteResultType::~WriteResultType() {}
diff --git a/chromium/extensions/browser/value_store/value_store.h b/chromium/extensions/browser/value_store/value_store.h
new file mode 100644
index 00000000000..b6aa0bab5d7
--- /dev/null
+++ b/chromium/extensions/browser/value_store/value_store.h
@@ -0,0 +1,215 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_H_
+#define EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_H_
+
+#include <stddef.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "extensions/browser/value_store/value_store_change.h"
+
+// Interface for a storage area for Value objects.
+class ValueStore {
+ public:
+ // Status codes returned from storage methods.
+ enum StatusCode {
+ OK,
+
+ // The failure was due to some kind of database corruption. Depending on
+ // what is corrupted, some part of the database may be recoverable.
+ //
+ // For example, if the on-disk representation of leveldb is corrupted, it's
+ // likely the whole database will need to be wiped and started again.
+ //
+ // If a single key has been committed with an invalid JSON representation,
+ // just that key can be deleted without affecting the rest of the database.
+ CORRUPTION,
+
+ // The failure was due to the store being read-only (for example, policy).
+ READ_ONLY,
+
+ // The failure was due to the store running out of space.
+ QUOTA_EXCEEDED,
+
+ // Any other error.
+ OTHER_ERROR,
+ };
+
+ enum BackingStoreRestoreStatus {
+ // No restore attempted.
+ RESTORE_NONE,
+ // Corrupted backing store successfully deleted.
+ DB_RESTORE_DELETE_SUCCESS,
+ // Corrupted backing store cannot be deleted.
+ DB_RESTORE_DELETE_FAILURE,
+ // Corrupted backing store successfully repaired.
+ DB_RESTORE_REPAIR_SUCCESS,
+ // Corrupted value successfully deleted.
+ VALUE_RESTORE_DELETE_SUCCESS,
+ // Corrupted value cannot be deleted.
+ VALUE_RESTORE_DELETE_FAILURE,
+ };
+
+ // The status (result) of an operation on a ValueStore.
+ struct Status {
+ Status();
+ Status(StatusCode code,
+ BackingStoreRestoreStatus restore_status,
+ const std::string& message);
+ Status(StatusCode code, const std::string& message);
+ ~Status();
+
+ bool ok() const { return code == OK; }
+
+ bool IsCorrupted() const { return code == CORRUPTION; }
+
+ // Merge |status| into this object. Any members (either |code|,
+ // |restore_status|, or |message| in |status| will be used, but only if this
+ // object's members are at their default value.
+ void Merge(const Status& status);
+
+ // The status code.
+ StatusCode code;
+
+ BackingStoreRestoreStatus restore_status;
+
+ // Message associated with the status (error) if there is one.
+ std::string message;
+ };
+
+ // The result of a read operation (Get).
+ class ReadResultType {
+ public:
+ ReadResultType(scoped_ptr<base::DictionaryValue> settings,
+ const Status& status);
+ explicit ReadResultType(const Status& status);
+ ~ReadResultType();
+
+ // Gets the settings read from the storage. Note that this represents
+ // the root object. If you request the value for key "foo", that value will
+ // be in |settings|.|foo|.
+ //
+ // Must only be called if there is no error.
+ base::DictionaryValue& settings() { return *settings_; }
+ scoped_ptr<base::DictionaryValue> PassSettings() {
+ return std::move(settings_);
+ }
+
+ const Status& status() const { return status_; }
+
+ private:
+ scoped_ptr<base::DictionaryValue> settings_;
+ Status status_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReadResultType);
+ };
+ typedef scoped_ptr<ReadResultType> ReadResult;
+
+ // The result of a write operation (Set/Remove/Clear).
+ class WriteResultType {
+ public:
+ WriteResultType(scoped_ptr<ValueStoreChangeList> changes,
+ const Status& status);
+ explicit WriteResultType(const Status& status);
+ ~WriteResultType();
+
+ // Gets the list of changes to the settings which resulted from the write.
+ // Won't be present if the NO_GENERATE_CHANGES WriteOptions was given.
+ // Only call if no error.
+ ValueStoreChangeList& changes() { return *changes_; }
+ scoped_ptr<ValueStoreChangeList> PassChanges() {
+ return std::move(changes_);
+ }
+
+ const Status& status() const { return status_; }
+
+ private:
+ scoped_ptr<ValueStoreChangeList> changes_;
+ Status status_;
+
+ DISALLOW_COPY_AND_ASSIGN(WriteResultType);
+ };
+ typedef scoped_ptr<WriteResultType> WriteResult;
+
+ // Options for write operations.
+ enum WriteOptionsValues {
+ // Callers should usually use this.
+ DEFAULTS = 0,
+
+ // Ignore any quota restrictions.
+ IGNORE_QUOTA = 1<<1,
+
+ // Don't generate the changes for a WriteResult.
+ NO_GENERATE_CHANGES = 1<<2,
+ };
+ typedef int WriteOptions;
+
+ virtual ~ValueStore() {}
+
+ // Helpers for making a Read/WriteResult.
+ template <typename T>
+ static ReadResult MakeReadResult(scoped_ptr<T> arg, const Status& status) {
+ return ReadResult(new ReadResultType(std::move(arg), status));
+ }
+ static ReadResult MakeReadResult(const Status& status) {
+ return ReadResult(new ReadResultType(status));
+ }
+
+ template <typename T>
+ static WriteResult MakeWriteResult(scoped_ptr<T> arg, const Status& status) {
+ return WriteResult(new WriteResultType(std::move(arg), status));
+ }
+ static WriteResult MakeWriteResult(const Status& status) {
+ return WriteResult(new WriteResultType(status));
+ }
+
+ // Gets the amount of space being used by a single value, in bytes.
+ // Note: The GetBytesInUse methods are only used by extension settings at the
+ // moment. If these become more generally useful, the
+ // SettingsStorageQuotaEnforcer and WeakUnlimitedSettingsStorage classes
+ // should be moved to the value_store directory.
+ virtual size_t GetBytesInUse(const std::string& key) = 0;
+
+ // Gets the total amount of space being used by multiple values, in bytes.
+ virtual size_t GetBytesInUse(const std::vector<std::string>& keys) = 0;
+
+ // Gets the total amount of space being used by this storage area, in bytes.
+ virtual size_t GetBytesInUse() = 0;
+
+ // Gets a single value from storage.
+ virtual ReadResult Get(const std::string& key) = 0;
+
+ // Gets multiple values from storage.
+ virtual ReadResult Get(const std::vector<std::string>& keys) = 0;
+
+ // Gets all values from storage.
+ virtual ReadResult Get() = 0;
+
+ // Sets a single key to a new value.
+ virtual WriteResult Set(WriteOptions options,
+ const std::string& key,
+ const base::Value& value) = 0;
+
+ // Sets multiple keys to new values.
+ virtual WriteResult Set(
+ WriteOptions options, const base::DictionaryValue& values) = 0;
+
+ // Removes a key from the storage.
+ virtual WriteResult Remove(const std::string& key) = 0;
+
+ // Removes multiple keys from the storage.
+ virtual WriteResult Remove(const std::vector<std::string>& keys) = 0;
+
+ // Clears the storage.
+ virtual WriteResult Clear() = 0;
+};
+
+#endif // EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_H_
diff --git a/chromium/extensions/browser/value_store/value_store_change.cc b/chromium/extensions/browser/value_store/value_store_change.cc
new file mode 100644
index 00000000000..3bbad592d06
--- /dev/null
+++ b/chromium/extensions/browser/value_store/value_store_change.cc
@@ -0,0 +1,57 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/value_store/value_store_change.h"
+
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+
+// static
+std::string ValueStoreChange::ToJson(
+ const ValueStoreChangeList& changes) {
+ base::DictionaryValue changes_value;
+ for (ValueStoreChangeList::const_iterator it = changes.begin();
+ it != changes.end(); ++it) {
+ base::DictionaryValue* change_value = new base::DictionaryValue();
+ if (it->old_value()) {
+ change_value->Set("oldValue", it->old_value()->DeepCopy());
+ }
+ if (it->new_value()) {
+ change_value->Set("newValue", it->new_value()->DeepCopy());
+ }
+ changes_value.SetWithoutPathExpansion(it->key(), change_value);
+ }
+ std::string json;
+ base::JSONWriter::Write(changes_value, &json);
+ return json;
+}
+
+ValueStoreChange::ValueStoreChange(
+ const std::string& key, base::Value* old_value, base::Value* new_value)
+ : inner_(new Inner(key, old_value, new_value)) {}
+
+ValueStoreChange::ValueStoreChange(const ValueStoreChange& other) = default;
+
+ValueStoreChange::~ValueStoreChange() {}
+
+const std::string& ValueStoreChange::key() const {
+ DCHECK(inner_.get());
+ return inner_->key_;
+}
+
+const base::Value* ValueStoreChange::old_value() const {
+ DCHECK(inner_.get());
+ return inner_->old_value_.get();
+}
+
+const base::Value* ValueStoreChange::new_value() const {
+ DCHECK(inner_.get());
+ return inner_->new_value_.get();
+}
+
+ValueStoreChange::Inner::Inner(
+ const std::string& key, base::Value* old_value, base::Value* new_value)
+ : key_(key), old_value_(old_value), new_value_(new_value) {}
+
+ValueStoreChange::Inner::~Inner() {}
diff --git a/chromium/extensions/browser/value_store/value_store_change.h b/chromium/extensions/browser/value_store/value_store_change.h
new file mode 100644
index 00000000000..a5e6bf5b818
--- /dev/null
+++ b/chromium/extensions/browser/value_store/value_store_change.h
@@ -0,0 +1,62 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_CHANGE_H_
+#define EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_CHANGE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+
+class ValueStoreChange;
+typedef std::vector<ValueStoreChange> ValueStoreChangeList;
+
+// A change to a setting. Safe/efficient to copy.
+class ValueStoreChange {
+ public:
+ // Converts an ValueStoreChangeList into JSON of the form:
+ // { "foo": { "key": "foo", "oldValue": "bar", "newValue": "baz" } }
+ static std::string ToJson(const ValueStoreChangeList& changes);
+
+ // Ownership of |old_value| and |new_value| taken.
+ ValueStoreChange(
+ const std::string& key, base::Value* old_value, base::Value* new_value);
+
+ ValueStoreChange(const ValueStoreChange& other);
+
+ ~ValueStoreChange();
+
+ // Gets the key of the setting which changed.
+ const std::string& key() const;
+
+ // Gets the value of the setting before the change, or NULL if there was no
+ // old value.
+ const base::Value* old_value() const;
+
+ // Gets the value of the setting after the change, or NULL if there is no new
+ // value.
+ const base::Value* new_value() const;
+
+ private:
+ class Inner : public base::RefCountedThreadSafe<Inner> {
+ public:
+ Inner(
+ const std::string& key, base::Value* old_value, base::Value* new_value);
+
+ const std::string key_;
+ const scoped_ptr<base::Value> old_value_;
+ const scoped_ptr<base::Value> new_value_;
+
+ private:
+ friend class base::RefCountedThreadSafe<Inner>;
+ virtual ~Inner();
+ };
+
+ scoped_refptr<Inner> inner_;
+};
+
+#endif // EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_CHANGE_H_
diff --git a/chromium/extensions/browser/value_store/value_store_change_unittest.cc b/chromium/extensions/browser/value_store/value_store_change_unittest.cc
new file mode 100644
index 00000000000..895f1ac71ee
--- /dev/null
+++ b/chromium/extensions/browser/value_store/value_store_change_unittest.cc
@@ -0,0 +1,97 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "extensions/browser/value_store/value_store_change.h"
+#include "extensions/common/value_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::DictionaryValue;
+using base::Value;
+using extensions::DictionaryBuilder;
+using extensions::ListBuilder;
+
+namespace {
+
+TEST(ValueStoreChangeTest, NullOldValue) {
+ ValueStoreChange change("key", NULL, new base::StringValue("value"));
+
+ EXPECT_EQ("key", change.key());
+ EXPECT_EQ(NULL, change.old_value());
+ {
+ scoped_ptr<base::Value> expected(new base::StringValue("value"));
+ EXPECT_TRUE(change.new_value()->Equals(expected.get()));
+ }
+}
+
+TEST(ValueStoreChangeTest, NullNewValue) {
+ ValueStoreChange change("key", new base::StringValue("value"), NULL);
+
+ EXPECT_EQ("key", change.key());
+ {
+ scoped_ptr<base::Value> expected(new base::StringValue("value"));
+ EXPECT_TRUE(change.old_value()->Equals(expected.get()));
+ }
+ EXPECT_EQ(NULL, change.new_value());
+}
+
+TEST(ValueStoreChangeTest, NonNullValues) {
+ ValueStoreChange change("key",
+ new base::StringValue("old_value"),
+ new base::StringValue("new_value"));
+
+ EXPECT_EQ("key", change.key());
+ {
+ scoped_ptr<base::Value> expected(new base::StringValue("old_value"));
+ EXPECT_TRUE(change.old_value()->Equals(expected.get()));
+ }
+ {
+ scoped_ptr<base::Value> expected(new base::StringValue("new_value"));
+ EXPECT_TRUE(change.new_value()->Equals(expected.get()));
+ }
+}
+
+TEST(ValueStoreChangeTest, ToJson) {
+ // Create a mildly complicated structure that has dots in it.
+ scoped_ptr<base::DictionaryValue> value =
+ DictionaryBuilder()
+ .Set("key", "value")
+ .Set("key.with.dots", "value.with.dots")
+ .Set("tricked", DictionaryBuilder().Set("you", "nodots").Build())
+ .Set("tricked.you", "with.dots")
+ .Build();
+
+ ValueStoreChangeList change_list;
+ change_list.push_back(
+ ValueStoreChange("key", value->DeepCopy(), value->DeepCopy()));
+ change_list.push_back(
+ ValueStoreChange("key.with.dots", value->DeepCopy(), value->DeepCopy()));
+
+ std::string json = ValueStoreChange::ToJson(change_list);
+ scoped_ptr<base::Value> from_json(base::JSONReader::Read(json));
+ ASSERT_TRUE(from_json.get());
+
+ DictionaryBuilder v1(*value);
+ DictionaryBuilder v2(*value);
+ DictionaryBuilder v3(*value);
+ DictionaryBuilder v4(*value);
+ scoped_ptr<base::DictionaryValue> expected_from_json =
+ DictionaryBuilder()
+ .Set("key", DictionaryBuilder()
+ .Set("oldValue", v1.Build())
+ .Set("newValue", v2.Build())
+ .Build())
+ .Set("key.with.dots", DictionaryBuilder()
+ .Set("oldValue", v3.Build())
+ .Set("newValue", v4.Build())
+ .Build())
+ .Build();
+
+ EXPECT_TRUE(from_json->Equals(expected_from_json.get()));
+}
+
+} // namespace
diff --git a/chromium/extensions/browser/value_store/value_store_factory.h b/chromium/extensions/browser/value_store/value_store_factory.h
new file mode 100644
index 00000000000..ec43547e880
--- /dev/null
+++ b/chromium/extensions/browser/value_store/value_store_factory.h
@@ -0,0 +1,67 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_FACTORY_H_
+#define EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_FACTORY_H_
+
+#include <set>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/browser/api/storage/settings_namespace.h"
+#include "extensions/common/extension.h"
+
+class ValueStore;
+
+namespace extensions {
+
+// Create new value stores for rules, state, or settings. For settings will
+// also create stores for the specified namespace and model type.
+//
+// Note: This factory creates the lower level stores that directly read/write to
+// disk. Sync/Managed stores are created directly, but delegate their
+// calls to a |ValueStore| created by this interface.
+class ValueStoreFactory : public base::RefCountedThreadSafe<ValueStoreFactory> {
+ public:
+ enum class ModelType { APP, EXTENSION };
+
+ // Create a |ValueStore| to contain rules data.
+ virtual scoped_ptr<ValueStore> CreateRulesStore() = 0;
+
+ // Create a |ValueStore| to contain state data.
+ virtual scoped_ptr<ValueStore> CreateStateStore() = 0;
+
+ // Create a |ValueStore| to contain settings data for a specific extension
+ // namespace and model type.
+ virtual scoped_ptr<ValueStore> CreateSettingsStore(
+ settings_namespace::Namespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) = 0;
+
+ // Delete all settings for specified given extension in the specified
+ // namespace/model_type.
+ virtual void DeleteSettings(settings_namespace::Namespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) = 0;
+
+ // Are there any settings stored in the specified namespace/model_type for
+ // the given extension?
+ virtual bool HasSettings(settings_namespace::Namespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) = 0;
+
+ // Return all extension ID's with settings stored in the given
+ // namespace/model_type.
+ virtual std::set<ExtensionId> GetKnownExtensionIDs(
+ settings_namespace::Namespace settings_namespace,
+ ModelType model_type) const = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<ValueStoreFactory>;
+ virtual ~ValueStoreFactory() {}
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_FACTORY_H_
diff --git a/chromium/extensions/browser/value_store/value_store_factory_impl.cc b/chromium/extensions/browser/value_store/value_store_factory_impl.cc
new file mode 100644
index 00000000000..27324fd2bd5
--- /dev/null
+++ b/chromium/extensions/browser/value_store/value_store_factory_impl.cc
@@ -0,0 +1,53 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/value_store/value_store_factory_impl.h"
+
+#include "extensions/browser/value_store/legacy_value_store_factory.h"
+
+namespace extensions {
+
+using SettingsNamespace = settings_namespace::Namespace;
+
+ValueStoreFactoryImpl::ValueStoreFactoryImpl(const base::FilePath& profile_path)
+ : legacy_factory_(new LegacyValueStoreFactory(profile_path)) {}
+
+ValueStoreFactoryImpl::~ValueStoreFactoryImpl() = default;
+
+scoped_ptr<ValueStore> ValueStoreFactoryImpl::CreateRulesStore() {
+ return legacy_factory_->CreateRulesStore();
+}
+
+scoped_ptr<ValueStore> ValueStoreFactoryImpl::CreateStateStore() {
+ return legacy_factory_->CreateStateStore();
+}
+
+scoped_ptr<ValueStore> ValueStoreFactoryImpl::CreateSettingsStore(
+ SettingsNamespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) {
+ return legacy_factory_->CreateSettingsStore(settings_namespace, model_type,
+ extension_id);
+}
+
+void ValueStoreFactoryImpl::DeleteSettings(SettingsNamespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) {
+ legacy_factory_->DeleteSettings(settings_namespace, model_type, extension_id);
+}
+
+bool ValueStoreFactoryImpl::HasSettings(SettingsNamespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) {
+ return legacy_factory_->HasSettings(settings_namespace, model_type,
+ extension_id);
+}
+
+std::set<ExtensionId> ValueStoreFactoryImpl::GetKnownExtensionIDs(
+ SettingsNamespace settings_namespace,
+ ModelType model_type) const {
+ return legacy_factory_->GetKnownExtensionIDs(settings_namespace, model_type);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/value_store/value_store_factory_impl.h b/chromium/extensions/browser/value_store/value_store_factory_impl.h
new file mode 100644
index 00000000000..d0a0677ec97
--- /dev/null
+++ b/chromium/extensions/browser/value_store/value_store_factory_impl.h
@@ -0,0 +1,56 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_FACTORY_IMPL_H_
+#define EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_FACTORY_IMPL_H_
+
+#include <set>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/browser/value_store/value_store.h"
+#include "extensions/browser/value_store/value_store_factory.h"
+#include "extensions/common/extension.h"
+
+namespace extensions {
+
+class LegacyValueStoreFactory;
+
+// Mint new |ValueStore| instances for use by the extensions system. These are
+// used for extension rules, state, and settings.
+class ValueStoreFactoryImpl : public ValueStoreFactory {
+ public:
+ explicit ValueStoreFactoryImpl(const base::FilePath& profile_path);
+
+ // ValueStoreFactory
+ scoped_ptr<ValueStore> CreateRulesStore() override;
+ scoped_ptr<ValueStore> CreateStateStore() override;
+ scoped_ptr<ValueStore> CreateSettingsStore(
+ settings_namespace::Namespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) override;
+ void DeleteSettings(settings_namespace::Namespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) override;
+ bool HasSettings(settings_namespace::Namespace settings_namespace,
+ ModelType model_type,
+ const ExtensionId& extension_id) override;
+ std::set<ExtensionId> GetKnownExtensionIDs(
+ settings_namespace::Namespace settings_namespace,
+ ModelType model_type) const override;
+
+ private:
+ // ValueStoreFactory is refcounted.
+ ~ValueStoreFactoryImpl() override;
+
+ scoped_refptr<LegacyValueStoreFactory> legacy_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ValueStoreFactoryImpl);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_FACTORY_IMPL_H_
diff --git a/chromium/extensions/browser/value_store/value_store_frontend.cc b/chromium/extensions/browser/value_store/value_store_frontend.cc
new file mode 100644
index 00000000000..b3912d6febb
--- /dev/null
+++ b/chromium/extensions/browser/value_store/value_store_frontend.cc
@@ -0,0 +1,143 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/value_store/value_store_frontend.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/trace_event/trace_event.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/value_store/leveldb_value_store.h"
+#include "extensions/browser/value_store/value_store_factory.h"
+
+using content::BrowserThread;
+using extensions::ValueStoreFactory;
+
+class ValueStoreFrontend::Backend : public base::RefCountedThreadSafe<Backend> {
+ public:
+ Backend(const scoped_refptr<ValueStoreFactory>& store_factory,
+ BackendType backend_type)
+ : store_factory_(store_factory), backend_type_(backend_type) {}
+
+ void Get(const std::string& key,
+ const ValueStoreFrontend::ReadCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ LazyInit();
+ ValueStore::ReadResult result = storage_->Get(key);
+
+ // Extract the value from the ReadResult and pass ownership of it to the
+ // callback.
+ scoped_ptr<base::Value> value;
+ if (result->status().ok()) {
+ result->settings().RemoveWithoutPathExpansion(key, &value);
+ } else {
+ LOG(WARNING) << "Reading " << key << " from " << db_path_.value()
+ << " failed: " << result->status().message;
+ }
+
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&ValueStoreFrontend::Backend::RunCallback,
+ this, callback, base::Passed(&value)));
+ }
+
+ void Set(const std::string& key, scoped_ptr<base::Value> value) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ LazyInit();
+ // We don't need the old value, so skip generating changes.
+ ValueStore::WriteResult result = storage_->Set(
+ ValueStore::IGNORE_QUOTA | ValueStore::NO_GENERATE_CHANGES,
+ key,
+ *value.get());
+ LOG_IF(ERROR, !result->status().ok()) << "Error while writing " << key
+ << " to " << db_path_.value();
+ }
+
+ void Remove(const std::string& key) {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ LazyInit();
+ storage_->Remove(key);
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<Backend>;
+
+ virtual ~Backend() {
+ if (storage_ && !BrowserThread::CurrentlyOn(BrowserThread::FILE))
+ BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE,
+ storage_.release());
+ }
+
+ void LazyInit() {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+ if (storage_)
+ return;
+ TRACE_EVENT0("ValueStoreFrontend::Backend", "LazyInit");
+ switch (backend_type_) {
+ case BackendType::RULES:
+ storage_ = store_factory_->CreateRulesStore();
+ break;
+ case BackendType::STATE:
+ storage_ = store_factory_->CreateStateStore();
+ break;
+ }
+ }
+
+ void RunCallback(const ValueStoreFrontend::ReadCallback& callback,
+ scoped_ptr<base::Value> value) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ callback.Run(std::move(value));
+ }
+
+ // The factory which will be used to lazily create the ValueStore when needed.
+ // Used exclusively on the FILE thread.
+ scoped_refptr<ValueStoreFactory> store_factory_;
+ BackendType backend_type_;
+
+ // The actual ValueStore that handles persisting the data to disk. Used
+ // exclusively on the FILE thread.
+ scoped_ptr<ValueStore> storage_;
+
+ base::FilePath db_path_;
+
+ DISALLOW_COPY_AND_ASSIGN(Backend);
+};
+
+ValueStoreFrontend::ValueStoreFrontend(
+ const scoped_refptr<ValueStoreFactory>& store_factory,
+ BackendType backend_type)
+ : backend_(new Backend(store_factory, backend_type)) {}
+
+ValueStoreFrontend::~ValueStoreFrontend() {
+ DCHECK(CalledOnValidThread());
+}
+
+void ValueStoreFrontend::Get(const std::string& key,
+ const ReadCallback& callback) {
+ DCHECK(CalledOnValidThread());
+
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
+ base::Bind(&ValueStoreFrontend::Backend::Get,
+ backend_, key, callback));
+}
+
+void ValueStoreFrontend::Set(const std::string& key,
+ scoped_ptr<base::Value> value) {
+ DCHECK(CalledOnValidThread());
+
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
+ base::Bind(&ValueStoreFrontend::Backend::Set,
+ backend_, key, base::Passed(&value)));
+}
+
+void ValueStoreFrontend::Remove(const std::string& key) {
+ DCHECK(CalledOnValidThread());
+
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
+ base::Bind(&ValueStoreFrontend::Backend::Remove,
+ backend_, key));
+}
diff --git a/chromium/extensions/browser/value_store/value_store_frontend.h b/chromium/extensions/browser/value_store/value_store_frontend.h
new file mode 100644
index 00000000000..55253fb4218
--- /dev/null
+++ b/chromium/extensions/browser/value_store/value_store_frontend.h
@@ -0,0 +1,62 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_FRONTEND_H_
+#define EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_FRONTEND_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/values.h"
+#include "extensions/browser/value_store/value_store.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace extensions {
+class ValueStoreFactory;
+} // namespace extensions
+
+// A frontend for a LeveldbValueStore, for use on the UI thread.
+class ValueStoreFrontend
+ : public base::SupportsWeakPtr<ValueStoreFrontend>,
+ public base::NonThreadSafe {
+ public:
+ // The kind of extensions data stored in a backend.
+ enum class BackendType { RULES, STATE };
+
+ typedef base::Callback<void(scoped_ptr<base::Value>)> ReadCallback;
+
+ ValueStoreFrontend(
+ const scoped_refptr<extensions::ValueStoreFactory>& store_factory,
+ BackendType backend_type);
+ ~ValueStoreFrontend();
+
+ // Retrieves a value from the database asynchronously, passing a copy to
+ // |callback| when ready. NULL is passed if no matching entry is found.
+ void Get(const std::string& key, const ReadCallback& callback);
+
+ // Sets a value with the given key.
+ void Set(const std::string& key, scoped_ptr<base::Value> value);
+
+ // Removes the value with the given key.
+ void Remove(const std::string& key);
+
+ private:
+ class Backend;
+
+ // A helper class to manage lifetime of the backing ValueStore, which lives
+ // on the FILE thread.
+ scoped_refptr<Backend> backend_;
+
+ DISALLOW_COPY_AND_ASSIGN(ValueStoreFrontend);
+};
+
+#endif // EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_FRONTEND_H_
diff --git a/chromium/extensions/browser/value_store/value_store_frontend_unittest.cc b/chromium/extensions/browser/value_store/value_store_frontend_unittest.cc
new file mode 100644
index 00000000000..21f95b6153e
--- /dev/null
+++ b/chromium/extensions/browser/value_store/value_store_frontend_unittest.cc
@@ -0,0 +1,120 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/value_store/value_store_frontend.h"
+
+#include <utility>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "content/public/test/test_browser_thread.h"
+#include "extensions/browser/value_store/test_value_store_factory.h"
+#include "extensions/common/extension_paths.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::BrowserThread;
+
+class ValueStoreFrontendTest : public testing::Test {
+ public:
+ ValueStoreFrontendTest()
+ : ui_thread_(BrowserThread::UI, base::MessageLoop::current()),
+ file_thread_(BrowserThread::FILE, base::MessageLoop::current()) {
+ }
+
+ void SetUp() override {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ base::FilePath test_data_dir;
+ ASSERT_TRUE(PathService::Get(extensions::DIR_TEST_DATA, &test_data_dir));
+ base::FilePath src_db(test_data_dir.AppendASCII("value_store_db"));
+ db_path_ = temp_dir_.path().AppendASCII("temp_db");
+ base::CopyDirectory(src_db, db_path_, true);
+
+ factory_ = new extensions::TestValueStoreFactory(db_path_);
+
+ ResetStorage();
+ }
+
+ void TearDown() override {
+ base::MessageLoop::current()->RunUntilIdle(); // wait for storage to delete
+ storage_.reset();
+ }
+
+ // Reset the value store, reloading the DB from disk.
+ void ResetStorage() {
+ storage_.reset(new ValueStoreFrontend(
+ factory_, ValueStoreFrontend::BackendType::RULES));
+ }
+
+ bool Get(const std::string& key, scoped_ptr<base::Value>* output) {
+ storage_->Get(key, base::Bind(&ValueStoreFrontendTest::GetAndWait,
+ base::Unretained(this), output));
+ base::MessageLoop::current()->Run(); // wait for GetAndWait
+ return !!output->get();
+ }
+
+ protected:
+ void GetAndWait(scoped_ptr<base::Value>* output,
+ scoped_ptr<base::Value> result) {
+ *output = std::move(result);
+ base::MessageLoop::current()->QuitWhenIdle();
+ }
+
+ scoped_refptr<extensions::TestValueStoreFactory> factory_;
+ scoped_ptr<ValueStoreFrontend> storage_;
+ base::ScopedTempDir temp_dir_;
+ base::FilePath db_path_;
+ base::MessageLoop message_loop_;
+ content::TestBrowserThread ui_thread_;
+ content::TestBrowserThread file_thread_;
+};
+
+TEST_F(ValueStoreFrontendTest, GetExistingData) {
+ scoped_ptr<base::Value> value;
+ ASSERT_FALSE(Get("key0", &value));
+
+ // Test existing keys in the DB.
+ {
+ ASSERT_TRUE(Get("key1", &value));
+ std::string result;
+ ASSERT_TRUE(value->GetAsString(&result));
+ EXPECT_EQ("value1", result);
+ }
+
+ {
+ ASSERT_TRUE(Get("key2", &value));
+ int result;
+ ASSERT_TRUE(value->GetAsInteger(&result));
+ EXPECT_EQ(2, result);
+ }
+}
+
+TEST_F(ValueStoreFrontendTest, ChangesPersistAfterReload) {
+ storage_->Set("key0", scoped_ptr<base::Value>(new base::FundamentalValue(0)));
+ storage_->Set("key1", scoped_ptr<base::Value>(new base::StringValue("new1")));
+ storage_->Remove("key2");
+
+ // Reload the DB and test our changes.
+ ResetStorage();
+
+ scoped_ptr<base::Value> value;
+ {
+ ASSERT_TRUE(Get("key0", &value));
+ int result;
+ ASSERT_TRUE(value->GetAsInteger(&result));
+ EXPECT_EQ(0, result);
+ }
+
+ {
+ ASSERT_TRUE(Get("key1", &value));
+ std::string result;
+ ASSERT_TRUE(value->GetAsString(&result));
+ EXPECT_EQ("new1", result);
+ }
+
+ ASSERT_FALSE(Get("key2", &value));
+}
diff --git a/chromium/extensions/browser/value_store/value_store_unittest.cc b/chromium/extensions/browser/value_store/value_store_unittest.cc
new file mode 100644
index 00000000000..8c5f7aec06c
--- /dev/null
+++ b/chromium/extensions/browser/value_store/value_store_unittest.cc
@@ -0,0 +1,480 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/value_store/value_store_unittest.h"
+
+#include "base/json/json_writer.h"
+#include "base/memory/linked_ptr.h"
+#include "base/values.h"
+
+using content::BrowserThread;
+
+namespace {
+
+// To save typing ValueStore::DEFAULTS everywhere.
+const ValueStore::WriteOptions DEFAULTS = ValueStore::DEFAULTS;
+
+// Gets the pretty-printed JSON for a value.
+std::string GetJSON(const base::Value& value) {
+ std::string json;
+ base::JSONWriter::WriteWithOptions(
+ value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
+ return json;
+}
+
+} // namespace
+
+// Compares two possibly NULL values for equality, filling |error| with an
+// appropriate error message if they're different.
+bool ValuesEqual(const base::Value* expected,
+ const base::Value* actual,
+ std::string* error) {
+ if (expected == actual) {
+ return true;
+ }
+ if (expected && !actual) {
+ *error = "Expected: " + GetJSON(*expected) + ", actual: NULL";
+ return false;
+ }
+ if (actual && !expected) {
+ *error = "Expected: NULL, actual: " + GetJSON(*actual);
+ return false;
+ }
+ if (!expected->Equals(actual)) {
+ *error =
+ "Expected: " + GetJSON(*expected) + ", actual: " + GetJSON(*actual);
+ return false;
+ }
+ return true;
+}
+
+// Returns whether the read result of a storage operation has the expected
+// settings.
+testing::AssertionResult SettingsEq(
+ const char* _1, const char* _2,
+ const base::DictionaryValue& expected,
+ ValueStore::ReadResult actual_result) {
+ if (!actual_result->status().ok()) {
+ return testing::AssertionFailure() << "Result has error: "
+ << actual_result->status().message;
+ }
+
+ std::string error;
+ if (!ValuesEqual(&expected, &actual_result->settings(), &error)) {
+ return testing::AssertionFailure() << error;
+ }
+
+ return testing::AssertionSuccess();
+}
+
+// Returns whether the write result of a storage operation has the expected
+// changes.
+testing::AssertionResult ChangesEq(
+ const char* _1, const char* _2,
+ const ValueStoreChangeList& expected,
+ ValueStore::WriteResult actual_result) {
+ if (!actual_result->status().ok()) {
+ return testing::AssertionFailure() << "Result has error: "
+ << actual_result->status().message;
+ }
+
+ const ValueStoreChangeList& actual = actual_result->changes();
+ if (expected.size() != actual.size()) {
+ return testing::AssertionFailure() <<
+ "Actual has wrong size, expecting " << expected.size() <<
+ " but was " << actual.size();
+ }
+
+ std::map<std::string, linked_ptr<ValueStoreChange> > expected_as_map;
+ for (ValueStoreChangeList::const_iterator it = expected.begin();
+ it != expected.end(); ++it) {
+ expected_as_map[it->key()] =
+ linked_ptr<ValueStoreChange>(new ValueStoreChange(*it));
+ }
+
+ std::set<std::string> keys_seen;
+
+ for (ValueStoreChangeList::const_iterator it = actual.begin();
+ it != actual.end(); ++it) {
+ if (keys_seen.count(it->key())) {
+ return testing::AssertionFailure() <<
+ "Multiple changes seen for key: " << it->key();
+ }
+ keys_seen.insert(it->key());
+
+ if (!expected_as_map.count(it->key())) {
+ return testing::AssertionFailure() <<
+ "Actual has unexpected change for key: " << it->key();
+ }
+
+ ValueStoreChange expected_change = *expected_as_map[it->key()];
+ std::string error;
+ if (!ValuesEqual(expected_change.new_value(), it->new_value(), &error)) {
+ return testing::AssertionFailure() <<
+ "New value for " << it->key() << " was unexpected: " << error;
+ }
+ if (!ValuesEqual(expected_change.old_value(), it->old_value(), &error)) {
+ return testing::AssertionFailure() <<
+ "Old value for " << it->key() << " was unexpected: " << error;
+ }
+ }
+
+ return testing::AssertionSuccess();
+}
+
+ValueStoreTest::ValueStoreTest()
+ : key1_("foo"),
+ key2_("bar"),
+ key3_("baz"),
+ empty_dict_(new base::DictionaryValue()),
+ dict1_(new base::DictionaryValue()),
+ dict3_(new base::DictionaryValue()),
+ dict12_(new base::DictionaryValue()),
+ dict123_(new base::DictionaryValue()),
+ ui_thread_(BrowserThread::UI, base::MessageLoop::current()),
+ file_thread_(BrowserThread::FILE, base::MessageLoop::current()) {
+ val1_.reset(new base::StringValue(key1_ + "Value"));
+ val2_.reset(new base::StringValue(key2_ + "Value"));
+ val3_.reset(new base::StringValue(key3_ + "Value"));
+
+ list1_.push_back(key1_);
+ list2_.push_back(key2_);
+ list3_.push_back(key3_);
+ list12_.push_back(key1_);
+ list12_.push_back(key2_);
+ list13_.push_back(key1_);
+ list13_.push_back(key3_);
+ list123_.push_back(key1_);
+ list123_.push_back(key2_);
+ list123_.push_back(key3_);
+
+ set1_.insert(list1_.begin(), list1_.end());
+ set2_.insert(list2_.begin(), list2_.end());
+ set3_.insert(list3_.begin(), list3_.end());
+ set12_.insert(list12_.begin(), list12_.end());
+ set13_.insert(list13_.begin(), list13_.end());
+ set123_.insert(list123_.begin(), list123_.end());
+
+ dict1_->Set(key1_, val1_->DeepCopy());
+ dict3_->Set(key3_, val3_->DeepCopy());
+ dict12_->Set(key1_, val1_->DeepCopy());
+ dict12_->Set(key2_, val2_->DeepCopy());
+ dict123_->Set(key1_, val1_->DeepCopy());
+ dict123_->Set(key2_, val2_->DeepCopy());
+ dict123_->Set(key3_, val3_->DeepCopy());
+}
+
+ValueStoreTest::~ValueStoreTest() {}
+
+void ValueStoreTest::SetUp() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ storage_.reset((GetParam())(temp_dir_.path().AppendASCII("dbName")));
+ ASSERT_TRUE(storage_.get());
+}
+
+void ValueStoreTest::TearDown() {
+ storage_.reset();
+}
+
+TEST_P(ValueStoreTest, GetWhenEmpty) {
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(empty_list_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list123_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get());
+}
+
+TEST_P(ValueStoreTest, GetWithSingleValue) {
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, NULL, val1_->DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ changes, storage_->Set(DEFAULTS, key1_, *val1_));
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key2_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key3_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(empty_list_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get(list123_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get());
+}
+
+TEST_P(ValueStoreTest, GetWithMultipleValues) {
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, NULL, val1_->DeepCopy()));
+ changes.push_back(ValueStoreChange(key2_, NULL, val2_->DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Set(DEFAULTS, *dict12_));
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key3_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(empty_list_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict12_, storage_->Get(list123_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict12_, storage_->Get());
+}
+
+TEST_P(ValueStoreTest, RemoveWhenEmpty) {
+ EXPECT_PRED_FORMAT2(ChangesEq, ValueStoreChangeList(),
+ storage_->Remove(key1_));
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get());
+}
+
+TEST_P(ValueStoreTest, RemoveWithSingleValue) {
+ storage_->Set(DEFAULTS, *dict1_);
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, val1_->DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Remove(key1_));
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key2_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list12_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get());
+}
+
+TEST_P(ValueStoreTest, RemoveWithMultipleValues) {
+ storage_->Set(DEFAULTS, *dict123_);
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key3_, val3_->DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Remove(key3_));
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key3_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(empty_list_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get(list1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict12_, storage_->Get(list12_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get(list13_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict12_, storage_->Get(list123_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict12_, storage_->Get());
+
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, val1_->DeepCopy(), NULL));
+ changes.push_back(ValueStoreChange(key2_, val2_->DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Remove(list12_));
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key3_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(empty_list_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list12_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list13_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list123_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get());
+}
+
+TEST_P(ValueStoreTest, SetWhenOverwriting) {
+ storage_->Set(DEFAULTS, key1_, *val2_);
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(
+ ValueStoreChange(key1_, val2_->DeepCopy(), val1_->DeepCopy()));
+ changes.push_back(ValueStoreChange(key2_, NULL, val2_->DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Set(DEFAULTS, *dict12_));
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key3_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(empty_list_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get(list1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict12_, storage_->Get(list12_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get(list13_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict12_, storage_->Get(list123_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict12_, storage_->Get());
+}
+
+TEST_P(ValueStoreTest, ClearWhenEmpty) {
+ EXPECT_PRED_FORMAT2(ChangesEq, ValueStoreChangeList(), storage_->Clear());
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(empty_list_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list123_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get());
+}
+
+TEST_P(ValueStoreTest, ClearWhenNotEmpty) {
+ storage_->Set(DEFAULTS, *dict12_);
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, val1_->DeepCopy(), NULL));
+ changes.push_back(ValueStoreChange(key2_, val2_->DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Clear());
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(empty_list_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list123_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get());
+}
+
+// Dots should be allowed in key names; they shouldn't be interpreted as
+// indexing into a dictionary.
+TEST_P(ValueStoreTest, DotsInKeyNames) {
+ std::string dot_key("foo.bar");
+ base::StringValue dot_value("baz.qux");
+ std::vector<std::string> dot_list;
+ dot_list.push_back(dot_key);
+ base::DictionaryValue dot_dict;
+ dot_dict.SetWithoutPathExpansion(dot_key, dot_value.DeepCopy());
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(dot_key));
+
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(
+ ValueStoreChange(dot_key, NULL, dot_value.DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ changes, storage_->Set(DEFAULTS, dot_key, dot_value));
+ }
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ ValueStoreChangeList(), storage_->Set(DEFAULTS, dot_key, dot_value));
+
+ EXPECT_PRED_FORMAT2(SettingsEq, dot_dict, storage_->Get(dot_key));
+
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(
+ ValueStoreChange(dot_key, dot_value.DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Remove(dot_key));
+ }
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ ValueStoreChangeList(), storage_->Remove(dot_key));
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(
+ ValueStoreChange(dot_key, NULL, dot_value.DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Set(DEFAULTS, dot_dict));
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, dot_dict, storage_->Get(dot_list));
+ EXPECT_PRED_FORMAT2(SettingsEq, dot_dict, storage_->Get());
+
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(
+ ValueStoreChange(dot_key, dot_value.DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Remove(dot_list));
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(dot_key));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get());
+}
+
+TEST_P(ValueStoreTest, DotsInKeyNamesWithDicts) {
+ base::DictionaryValue outer_dict;
+ base::DictionaryValue* inner_dict = new base::DictionaryValue();
+ outer_dict.Set("foo", inner_dict);
+ inner_dict->SetString("bar", "baz");
+
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(
+ ValueStoreChange("foo", NULL, inner_dict->DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ changes, storage_->Set(DEFAULTS, outer_dict));
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, outer_dict, storage_->Get("foo"));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get("foo.bar"));
+}
+
+TEST_P(ValueStoreTest, ComplexChangedKeysScenarios) {
+ // Test:
+ // - Setting over missing/changed/same keys, combinations.
+ // - Removing over missing and present keys, combinations.
+ // - Clearing.
+ std::vector<std::string> complex_list;
+ base::DictionaryValue complex_changed_dict;
+
+ storage_->Set(DEFAULTS, key1_, *val1_);
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ ValueStoreChangeList(), storage_->Set(DEFAULTS, key1_, *val1_));
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(
+ key1_, val1_->DeepCopy(), val2_->DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ changes, storage_->Set(DEFAULTS, key1_, *val2_));
+ }
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, val2_->DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Remove(key1_));
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ ValueStoreChangeList(), storage_->Remove(key1_));
+ }
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, NULL, val1_->DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ changes, storage_->Set(DEFAULTS, key1_, *val1_));
+ }
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, val1_->DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Clear());
+ EXPECT_PRED_FORMAT2(ChangesEq, ValueStoreChangeList(), storage_->Clear());
+ }
+
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, NULL, val1_->DeepCopy()));
+ changes.push_back(ValueStoreChange(key2_, NULL, val2_->DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Set(DEFAULTS, *dict12_));
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ ValueStoreChangeList(), storage_->Set(DEFAULTS, *dict12_));
+ }
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key3_, NULL, val3_->DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Set(DEFAULTS, *dict123_));
+ }
+ {
+ base::DictionaryValue to_set;
+ to_set.Set(key1_, val2_->DeepCopy());
+ to_set.Set(key2_, val2_->DeepCopy());
+ to_set.Set("asdf", val1_->DeepCopy());
+ to_set.Set("qwerty", val3_->DeepCopy());
+
+ ValueStoreChangeList changes;
+ changes.push_back(
+ ValueStoreChange(key1_, val1_->DeepCopy(), val2_->DeepCopy()));
+ changes.push_back(ValueStoreChange("asdf", NULL, val1_->DeepCopy()));
+ changes.push_back(
+ ValueStoreChange("qwerty", NULL, val3_->DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Set(DEFAULTS, to_set));
+ }
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, val2_->DeepCopy(), NULL));
+ changes.push_back(ValueStoreChange(key2_, val2_->DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Remove(list12_));
+ }
+ {
+ std::vector<std::string> to_remove;
+ to_remove.push_back(key1_);
+ to_remove.push_back("asdf");
+
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange("asdf", val1_->DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Remove(to_remove));
+ }
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key3_, val3_->DeepCopy(), NULL));
+ changes.push_back(
+ ValueStoreChange("qwerty", val3_->DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Clear());
+ EXPECT_PRED_FORMAT2(ChangesEq, ValueStoreChangeList(), storage_->Clear());
+ }
+}
diff --git a/chromium/extensions/browser/value_store/value_store_unittest.h b/chromium/extensions/browser/value_store/value_store_unittest.h
new file mode 100644
index 00000000000..f50e6902570
--- /dev/null
+++ b/chromium/extensions/browser/value_store/value_store_unittest.h
@@ -0,0 +1,72 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_UNITTEST_H_
+#define EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_UNITTEST_H_
+
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "content/public/test/test_browser_thread.h"
+#include "extensions/browser/value_store/value_store.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Parameter type for the value-parameterized tests.
+typedef ValueStore* (*ValueStoreTestParam)(const base::FilePath& file_path);
+
+// Test fixture for ValueStore tests. Tests are defined in
+// settings_storage_unittest.cc with configurations for both cached
+// and non-cached leveldb storage, and cached no-op storage.
+class ValueStoreTest : public testing::TestWithParam<ValueStoreTestParam> {
+ public:
+ ValueStoreTest();
+ virtual ~ValueStoreTest();
+
+ void SetUp() override;
+ void TearDown() override;
+
+ protected:
+ scoped_ptr<ValueStore> storage_;
+
+ std::string key1_;
+ std::string key2_;
+ std::string key3_;
+
+ scoped_ptr<base::Value> val1_;
+ scoped_ptr<base::Value> val2_;
+ scoped_ptr<base::Value> val3_;
+
+ std::vector<std::string> empty_list_;
+ std::vector<std::string> list1_;
+ std::vector<std::string> list2_;
+ std::vector<std::string> list3_;
+ std::vector<std::string> list12_;
+ std::vector<std::string> list13_;
+ std::vector<std::string> list123_;
+
+ std::set<std::string> empty_set_;
+ std::set<std::string> set1_;
+ std::set<std::string> set2_;
+ std::set<std::string> set3_;
+ std::set<std::string> set12_;
+ std::set<std::string> set13_;
+ std::set<std::string> set123_;
+
+ scoped_ptr<base::DictionaryValue> empty_dict_;
+ scoped_ptr<base::DictionaryValue> dict1_;
+ scoped_ptr<base::DictionaryValue> dict3_;
+ scoped_ptr<base::DictionaryValue> dict12_;
+ scoped_ptr<base::DictionaryValue> dict123_;
+
+ private:
+ base::ScopedTempDir temp_dir_;
+
+ // Need these so that the DCHECKs for running on FILE or UI threads pass.
+ base::MessageLoop message_loop_;
+ content::TestBrowserThread ui_thread_;
+ content::TestBrowserThread file_thread_;
+};
+
+#endif // EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_UNITTEST_H_
diff --git a/chromium/extensions/browser/verified_contents.cc b/chromium/extensions/browser/verified_contents.cc
new file mode 100644
index 00000000000..fc2471dc79a
--- /dev/null
+++ b/chromium/extensions/browser/verified_contents.cc
@@ -0,0 +1,328 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/verified_contents.h"
+
+#include <stddef.h>
+
+#include "base/base64url.h"
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "components/crx_file/id_util.h"
+#include "crypto/signature_verifier.h"
+#include "extensions/common/extension.h"
+
+using base::DictionaryValue;
+using base::ListValue;
+using base::Value;
+
+namespace {
+
+const char kBlockSizeKey[] = "block_size";
+const char kContentHashesKey[] = "content_hashes";
+const char kDescriptionKey[] = "description";
+const char kFilesKey[] = "files";
+const char kFormatKey[] = "format";
+const char kHashBlockSizeKey[] = "hash_block_size";
+const char kHeaderKidKey[] = "header.kid";
+const char kItemIdKey[] = "item_id";
+const char kItemVersionKey[] = "item_version";
+const char kPathKey[] = "path";
+const char kPayloadKey[] = "payload";
+const char kProtectedKey[] = "protected";
+const char kRootHashKey[] = "root_hash";
+const char kSignatureKey[] = "signature";
+const char kSignaturesKey[] = "signatures";
+const char kSignedContentKey[] = "signed_content";
+const char kTreeHashPerFile[] = "treehash per file";
+const char kTreeHash[] = "treehash";
+const char kWebstoreKId[] = "webstore";
+
+// Helper function to iterate over a list of dictionaries, returning the
+// dictionary that has |key| -> |value| in it, if any, or NULL.
+DictionaryValue* FindDictionaryWithValue(const ListValue* list,
+ std::string key,
+ std::string value) {
+ for (ListValue::const_iterator i = list->begin(); i != list->end(); ++i) {
+ if (!(*i)->IsType(Value::TYPE_DICTIONARY))
+ continue;
+ DictionaryValue* dictionary = static_cast<DictionaryValue*>(*i);
+ std::string found_value;
+ if (dictionary->GetString(key, &found_value) && found_value == value)
+ return dictionary;
+ }
+ return NULL;
+}
+
+} // namespace
+
+namespace extensions {
+
+VerifiedContents::VerifiedContents(const uint8_t* public_key,
+ int public_key_size)
+ : public_key_(public_key),
+ public_key_size_(public_key_size),
+ valid_signature_(false), // Guilty until proven innocent.
+ block_size_(0) {}
+
+VerifiedContents::~VerifiedContents() {
+}
+
+// The format of the payload json is:
+// {
+// "item_id": "<extension id>",
+// "item_version": "<extension version>",
+// "content_hashes": [
+// {
+// "block_size": 4096,
+// "hash_block_size": 4096,
+// "format": "treehash",
+// "files": [
+// {
+// "path": "foo/bar",
+// "root_hash": "<base64url encoded bytes>"
+// },
+// ...
+// ]
+// }
+// ]
+// }
+bool VerifiedContents::InitFrom(const base::FilePath& path,
+ bool ignore_invalid_signature) {
+ std::string payload;
+ if (!GetPayload(path, &payload, ignore_invalid_signature))
+ return false;
+
+ scoped_ptr<base::Value> value(base::JSONReader::Read(payload));
+ if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY))
+ return false;
+ DictionaryValue* dictionary = static_cast<DictionaryValue*>(value.get());
+
+ std::string item_id;
+ if (!dictionary->GetString(kItemIdKey, &item_id) ||
+ !crx_file::id_util::IdIsValid(item_id))
+ return false;
+ extension_id_ = item_id;
+
+ std::string version_string;
+ if (!dictionary->GetString(kItemVersionKey, &version_string))
+ return false;
+ version_ = base::Version(version_string);
+ if (!version_.IsValid())
+ return false;
+
+ ListValue* hashes_list = NULL;
+ if (!dictionary->GetList(kContentHashesKey, &hashes_list))
+ return false;
+
+ for (size_t i = 0; i < hashes_list->GetSize(); i++) {
+ DictionaryValue* hashes = NULL;
+ if (!hashes_list->GetDictionary(i, &hashes))
+ return false;
+ std::string format;
+ if (!hashes->GetString(kFormatKey, &format) || format != kTreeHash)
+ continue;
+
+ int block_size = 0;
+ int hash_block_size = 0;
+ if (!hashes->GetInteger(kBlockSizeKey, &block_size) ||
+ !hashes->GetInteger(kHashBlockSizeKey, &hash_block_size))
+ return false;
+ block_size_ = block_size;
+
+ // We don't support using a different block_size and hash_block_size at
+ // the moment.
+ if (block_size_ != hash_block_size)
+ return false;
+
+ ListValue* files = NULL;
+ if (!hashes->GetList(kFilesKey, &files))
+ return false;
+
+ for (size_t j = 0; j < files->GetSize(); j++) {
+ DictionaryValue* data = NULL;
+ if (!files->GetDictionary(j, &data))
+ return false;
+ std::string file_path_string;
+ std::string encoded_root_hash;
+ std::string root_hash;
+ if (!data->GetString(kPathKey, &file_path_string) ||
+ !base::IsStringUTF8(file_path_string) ||
+ !data->GetString(kRootHashKey, &encoded_root_hash) ||
+ !base::Base64UrlDecode(encoded_root_hash,
+ base::Base64UrlDecodePolicy::IGNORE_PADDING,
+ &root_hash))
+ return false;
+ base::FilePath file_path =
+ base::FilePath::FromUTF8Unsafe(file_path_string);
+ RootHashes::iterator i = root_hashes_.insert(std::make_pair(
+ base::ToLowerASCII(file_path.value()), std::string()));
+ i->second.swap(root_hash);
+ }
+
+ break;
+ }
+ return true;
+}
+
+bool VerifiedContents::HasTreeHashRoot(
+ const base::FilePath& relative_path) const {
+ base::FilePath::StringType path = base::ToLowerASCII(
+ relative_path.NormalizePathSeparatorsTo('/').value());
+ return root_hashes_.find(path) != root_hashes_.end();
+}
+
+bool VerifiedContents::TreeHashRootEquals(const base::FilePath& relative_path,
+ const std::string& expected) const {
+ base::FilePath::StringType path = base::ToLowerASCII(
+ relative_path.NormalizePathSeparatorsTo('/').value());
+ for (RootHashes::const_iterator i = root_hashes_.find(path);
+ i != root_hashes_.end();
+ ++i) {
+ if (expected == i->second)
+ return true;
+ }
+ return false;
+}
+
+// We're loosely following the "JSON Web Signature" draft spec for signing
+// a JSON payload:
+//
+// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-26
+//
+// The idea is that you have some JSON that you want to sign, so you
+// base64-encode that and put it as the "payload" field in a containing
+// dictionary. There might be signatures of it done with multiple
+// algorithms/parameters, so the payload is followed by a list of one or more
+// signature sections. Each signature section specifies the
+// algorithm/parameters in a JSON object which is base64url encoded into one
+// string and put into a "protected" field in the signature. Then the encoded
+// "payload" and "protected" strings are concatenated with a "." in between
+// them and those bytes are signed and the resulting signature is base64url
+// encoded and placed in the "signature" field. To allow for extensibility, we
+// wrap this, so we can include additional kinds of payloads in the future. E.g.
+// [
+// {
+// "description": "treehash per file",
+// "signed_content": {
+// "payload": "<base64url encoded JSON to sign>",
+// "signatures": [
+// {
+// "protected": "<base64url encoded JSON with algorithm/parameters>",
+// "header": {
+// <object with metadata about this signature, eg a key identifier>
+// }
+// "signature":
+// "<base64url encoded signature over payload || . || protected>"
+// },
+// ... <zero or more additional signatures> ...
+// ]
+// }
+// }
+// ]
+// There might be both a signature generated with a webstore private key and a
+// signature generated with the extension's private key - for now we only
+// verify the webstore one (since the id is in the payload, so we can trust
+// that it is for a given extension), but in the future we may validate using
+// the extension's key too (eg for non-webstore hosted extensions such as
+// enterprise installs).
+bool VerifiedContents::GetPayload(const base::FilePath& path,
+ std::string* payload,
+ bool ignore_invalid_signature) {
+ std::string contents;
+ if (!base::ReadFileToString(path, &contents))
+ return false;
+ scoped_ptr<base::Value> value(base::JSONReader::Read(contents));
+ if (!value.get() || !value->IsType(Value::TYPE_LIST))
+ return false;
+ ListValue* top_list = static_cast<ListValue*>(value.get());
+
+ // Find the "treehash per file" signed content, e.g.
+ // [
+ // {
+ // "description": "treehash per file",
+ // "signed_content": {
+ // "signatures": [ ... ],
+ // "payload": "..."
+ // }
+ // }
+ // ]
+ DictionaryValue* dictionary =
+ FindDictionaryWithValue(top_list, kDescriptionKey, kTreeHashPerFile);
+ DictionaryValue* signed_content = NULL;
+ if (!dictionary ||
+ !dictionary->GetDictionaryWithoutPathExpansion(kSignedContentKey,
+ &signed_content)) {
+ return false;
+ }
+
+ ListValue* signatures = NULL;
+ if (!signed_content->GetList(kSignaturesKey, &signatures))
+ return false;
+
+ DictionaryValue* signature_dict =
+ FindDictionaryWithValue(signatures, kHeaderKidKey, kWebstoreKId);
+ if (!signature_dict)
+ return false;
+
+ std::string protected_value;
+ std::string encoded_signature;
+ std::string decoded_signature;
+ if (!signature_dict->GetString(kProtectedKey, &protected_value) ||
+ !signature_dict->GetString(kSignatureKey, &encoded_signature) ||
+ !base::Base64UrlDecode(encoded_signature,
+ base::Base64UrlDecodePolicy::IGNORE_PADDING,
+ &decoded_signature))
+ return false;
+
+ std::string encoded_payload;
+ if (!signed_content->GetString(kPayloadKey, &encoded_payload))
+ return false;
+
+ valid_signature_ =
+ VerifySignature(protected_value, encoded_payload, decoded_signature);
+ if (!valid_signature_ && !ignore_invalid_signature)
+ return false;
+
+ if (!base::Base64UrlDecode(encoded_payload,
+ base::Base64UrlDecodePolicy::IGNORE_PADDING,
+ payload))
+ return false;
+
+ return true;
+}
+
+bool VerifiedContents::VerifySignature(const std::string& protected_value,
+ const std::string& payload,
+ const std::string& signature_bytes) {
+ crypto::SignatureVerifier signature_verifier;
+ if (!signature_verifier.VerifyInit(
+ crypto::SignatureVerifier::RSA_PKCS1_SHA256,
+ reinterpret_cast<const uint8_t*>(signature_bytes.data()),
+ signature_bytes.size(), public_key_, public_key_size_)) {
+ VLOG(1) << "Could not verify signature - VerifyInit failure";
+ return false;
+ }
+
+ signature_verifier.VerifyUpdate(
+ reinterpret_cast<const uint8_t*>(protected_value.data()),
+ protected_value.size());
+
+ std::string dot(".");
+ signature_verifier.VerifyUpdate(reinterpret_cast<const uint8_t*>(dot.data()),
+ dot.size());
+
+ signature_verifier.VerifyUpdate(
+ reinterpret_cast<const uint8_t*>(payload.data()), payload.size());
+
+ if (!signature_verifier.VerifyFinal()) {
+ VLOG(1) << "Could not verify signature - VerifyFinal failure";
+ return false;
+ }
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/verified_contents.h b/chromium/extensions/browser/verified_contents.h
new file mode 100644
index 00000000000..38265e51ddb
--- /dev/null
+++ b/chromium/extensions/browser/verified_contents.h
@@ -0,0 +1,96 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_VERIFIED_CONTENTS_H_
+#define EXTENSIONS_BROWSER_VERIFIED_CONTENTS_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/version.h"
+
+namespace extensions {
+
+// This class encapsulates the data in a "verified_contents.json" file
+// generated by the webstore for a .crx file. That data includes a set of
+// signed expected hashes of file content which can be used to check for
+// corruption of extension files on local disk.
+class VerifiedContents {
+ public:
+ // Note: the public_key must remain valid for the lifetime of this object.
+ VerifiedContents(const uint8_t* public_key, int public_key_size);
+ ~VerifiedContents();
+
+ // Returns true if we successfully parsed the verified_contents.json file at
+ // |path| and validated the enclosed signature. The
+ // |ignore_invalid_signature| argument can be set to make this still succeed
+ // if the contents of the file were parsed successfully but the signature did
+ // not validate. (Use with caution!)
+ bool InitFrom(const base::FilePath& path, bool ignore_invalid_signature);
+
+ int block_size() const { return block_size_; }
+ const std::string& extension_id() const { return extension_id_; }
+ const base::Version& version() const { return version_; }
+
+ bool HasTreeHashRoot(const base::FilePath& relative_path) const;
+
+ bool TreeHashRootEquals(const base::FilePath& relative_path,
+ const std::string& expected) const;
+
+ // If InitFrom has not been called yet, or was used in "ignore invalid
+ // signature" mode, this can return false.
+ bool valid_signature() { return valid_signature_; }
+
+ private:
+ // Returns the base64url-decoded "payload" field from the json at |path|, if
+ // the signature was valid (or ignore_invalid_signature was set to true).
+ bool GetPayload(const base::FilePath& path,
+ std::string* payload,
+ bool ignore_invalid_signature);
+
+ // The |protected_value| and |payload| arguments should be base64url encoded
+ // strings, and |signature_bytes| should be a byte array. See comments in the
+ // .cc file on GetPayload for where these come from in the overall input
+ // file.
+ bool VerifySignature(const std::string& protected_value,
+ const std::string& payload,
+ const std::string& signature_bytes);
+
+ // The public key we should use for signature verification.
+ const uint8_t* public_key_;
+ const int public_key_size_;
+
+ // Indicates whether the signature was successfully validated or not.
+ bool valid_signature_;
+
+ // The block size used for computing the treehash root hashes.
+ int block_size_;
+
+ // Information about which extension these signed hashes are for.
+ std::string extension_id_;
+ base::Version version_;
+
+ // The expected treehash root hashes for each file, lower cased so we can do
+ // case-insensitive lookups.
+ //
+ // We use a multi-map here so that we can do fast lookups of paths from
+ // requests on case-insensitive systems (windows, mac) where the request path
+ // might not have the exact right capitalization, but not break
+ // case-sensitive systems (linux, chromeos). TODO(asargent) - we should give
+ // developers client-side warnings in each of those cases, and have the
+ // webstore reject the cases they can statically detect. See crbug.com/29941
+ typedef std::multimap<base::FilePath::StringType, std::string> RootHashes;
+ RootHashes root_hashes_;
+
+ DISALLOW_COPY_AND_ASSIGN(VerifiedContents);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_VERIFIED_CONTENTS_H_
diff --git a/chromium/extensions/browser/verified_contents_unittest.cc b/chromium/extensions/browser/verified_contents_unittest.cc
new file mode 100644
index 00000000000..4d3c43dd96d
--- /dev/null
+++ b/chromium/extensions/browser/verified_contents_unittest.cc
@@ -0,0 +1,153 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/base64url.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/stl_util.h"
+#include "extensions/browser/verified_contents.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_paths.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+const char kContentVerifierDirectory[] = "content_verifier/";
+const char kPublicKeyPem[] = "public_key.pem";
+
+std::string DecodeBase64Url(const std::string& encoded) {
+ std::string decoded;
+ if (!base::Base64UrlDecode(
+ encoded, base::Base64UrlDecodePolicy::IGNORE_PADDING, &decoded))
+ return std::string();
+
+ return decoded;
+}
+
+bool GetPublicKey(const base::FilePath& path, std::string* public_key) {
+ std::string public_key_pem;
+ if (!base::ReadFileToString(path, &public_key_pem))
+ return false;
+ if (!Extension::ParsePEMKeyBytes(public_key_pem, public_key))
+ return false;
+ return true;
+}
+
+} // namespace
+
+TEST(VerifiedContents, Simple) {
+ // Figure out our test data directory.
+ base::FilePath path;
+ PathService::Get(DIR_TEST_DATA, &path);
+ path = path.AppendASCII(kContentVerifierDirectory);
+
+ // Initialize the VerifiedContents object.
+ std::string public_key;
+ ASSERT_TRUE(GetPublicKey(path.AppendASCII(kPublicKeyPem), &public_key));
+ VerifiedContents contents(reinterpret_cast<const uint8_t*>(public_key.data()),
+ public_key.size());
+ base::FilePath verified_contents_path =
+ path.AppendASCII("verified_contents.json");
+
+ ASSERT_TRUE(contents.InitFrom(verified_contents_path, false));
+
+ // Make sure we get expected values.
+ EXPECT_EQ(contents.block_size(), 4096);
+ EXPECT_EQ(contents.extension_id(), "abcdefghijklmnopabcdefghijklmnop");
+ EXPECT_EQ("1.2.3", contents.version().GetString());
+
+ EXPECT_TRUE(contents.TreeHashRootEquals(
+ base::FilePath::FromUTF8Unsafe("manifest.json"),
+ DecodeBase64Url("-vyyIIn7iSCzg7X3ICUI5wZa3tG7w7vyiCckxZdJGfs")));
+
+ EXPECT_TRUE(contents.TreeHashRootEquals(
+ base::FilePath::FromUTF8Unsafe("background.js"),
+ DecodeBase64Url("txHiG5KQvNoPOSH5FbQo9Zb5gJ23j3oFB0Ru9DOnziw")));
+
+ base::FilePath foo_bar_html =
+ base::FilePath(FILE_PATH_LITERAL("foo")).AppendASCII("bar.html");
+ EXPECT_FALSE(foo_bar_html.IsAbsolute());
+ EXPECT_TRUE(contents.TreeHashRootEquals(
+ foo_bar_html,
+ DecodeBase64Url("L37LFbT_hmtxRL7AfGZN9YTpW6yoz_ZiQ1opLJn1NZU")));
+
+ base::FilePath nonexistent = base::FilePath::FromUTF8Unsafe("nonexistent");
+ EXPECT_FALSE(contents.HasTreeHashRoot(nonexistent));
+
+ EXPECT_TRUE(contents.TreeHashRootEquals(
+ base::FilePath::FromUTF8Unsafe("lowercase.html"),
+ DecodeBase64Url("HpLotLGCmmOdKYvGQmD3OkXMKGs458dbanY4WcfAZI0")));
+ EXPECT_TRUE(contents.TreeHashRootEquals(
+ base::FilePath::FromUTF8Unsafe("Lowercase.Html"),
+ DecodeBase64Url("HpLotLGCmmOdKYvGQmD3OkXMKGs458dbanY4WcfAZI0")));
+ EXPECT_TRUE(contents.TreeHashRootEquals(
+ base::FilePath::FromUTF8Unsafe("LOWERCASE.HTML"),
+ DecodeBase64Url("HpLotLGCmmOdKYvGQmD3OkXMKGs458dbanY4WcfAZI0")));
+
+ EXPECT_TRUE(contents.TreeHashRootEquals(
+ base::FilePath::FromUTF8Unsafe("ALLCAPS.HTML"),
+ DecodeBase64Url("bl-eV8ENowvtw6P14D4X1EP0mlcMoG-_aOx5o9C1364")));
+ EXPECT_TRUE(contents.TreeHashRootEquals(
+ base::FilePath::FromUTF8Unsafe("AllCaps.Html"),
+ DecodeBase64Url("bl-eV8ENowvtw6P14D4X1EP0mlcMoG-_aOx5o9C1364")));
+ EXPECT_TRUE(contents.TreeHashRootEquals(
+ base::FilePath::FromUTF8Unsafe("allcaps.html"),
+ DecodeBase64Url("bl-eV8ENowvtw6P14D4X1EP0mlcMoG-_aOx5o9C1364")));
+
+ EXPECT_TRUE(contents.TreeHashRootEquals(
+ base::FilePath::FromUTF8Unsafe("MixedCase.Html"),
+ DecodeBase64Url("zEAO9FwciigMNy3NtU2XNb-dS5TQMmVNx0T9h7WvXbQ")));
+ EXPECT_TRUE(contents.TreeHashRootEquals(
+ base::FilePath::FromUTF8Unsafe("MIXEDCASE.HTML"),
+ DecodeBase64Url("zEAO9FwciigMNy3NtU2XNb-dS5TQMmVNx0T9h7WvXbQ")));
+ EXPECT_TRUE(contents.TreeHashRootEquals(
+ base::FilePath::FromUTF8Unsafe("mixedcase.html"),
+ DecodeBase64Url("zEAO9FwciigMNy3NtU2XNb-dS5TQMmVNx0T9h7WvXbQ")));
+ EXPECT_TRUE(contents.TreeHashRootEquals(
+ base::FilePath::FromUTF8Unsafe("mIxedcAse.Html"),
+ DecodeBase64Url("zEAO9FwciigMNy3NtU2XNb-dS5TQMmVNx0T9h7WvXbQ")));
+
+ EXPECT_TRUE(contents.TreeHashRootEquals(
+ base::FilePath::FromUTF8Unsafe("mIxedcAse.Html"),
+ DecodeBase64Url("nKRqUcJg1_QZWAeCb4uFd5ouC0McuGavKp8TFDRqBgg")));
+ EXPECT_TRUE(contents.TreeHashRootEquals(
+ base::FilePath::FromUTF8Unsafe("MIXEDCASE.HTML"),
+ DecodeBase64Url("nKRqUcJg1_QZWAeCb4uFd5ouC0McuGavKp8TFDRqBgg")));
+ EXPECT_TRUE(contents.TreeHashRootEquals(
+ base::FilePath::FromUTF8Unsafe("mixedcase.html"),
+ DecodeBase64Url("nKRqUcJg1_QZWAeCb4uFd5ouC0McuGavKp8TFDRqBgg")));
+ EXPECT_TRUE(contents.TreeHashRootEquals(
+ base::FilePath::FromUTF8Unsafe("MixedCase.Html"),
+ DecodeBase64Url("nKRqUcJg1_QZWAeCb4uFd5ouC0McuGavKp8TFDRqBgg")));
+}
+
+TEST(VerifiedContents, FailsOnBase64) {
+ // Accepting base64-encoded input where base64url-encoded input is expected
+ // will be considered to be invalid data. Verify that it gets rejected.
+
+ base::FilePath path;
+ PathService::Get(DIR_TEST_DATA, &path);
+ path = path.AppendASCII(kContentVerifierDirectory);
+
+ // Initialize the VerifiedContents object.
+ std::string public_key;
+ ASSERT_TRUE(GetPublicKey(path.AppendASCII(kPublicKeyPem), &public_key));
+ VerifiedContents contents(reinterpret_cast<const uint8_t*>(public_key.data()),
+ public_key.size());
+
+ base::FilePath verified_contents_path =
+ path.AppendASCII("verified_contents_base64.json");
+
+ ASSERT_FALSE(contents.InitFrom(verified_contents_path, false));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/view_type_utils.cc b/chromium/extensions/browser/view_type_utils.cc
new file mode 100644
index 00000000000..933d669037d
--- /dev/null
+++ b/chromium/extensions/browser/view_type_utils.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/view_type_utils.h"
+
+#include "base/lazy_instance.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/browser/extensions_browser_client.h"
+
+using content::WebContents;
+
+namespace extensions {
+
+namespace {
+
+const char kViewTypeUserDataKey[] = "ViewTypeUserData";
+
+class ViewTypeUserData : public base::SupportsUserData::Data {
+ public:
+ explicit ViewTypeUserData(ViewType type) : type_(type) {}
+ ~ViewTypeUserData() override {}
+ ViewType type() { return type_; }
+
+ private:
+ ViewType type_;
+};
+
+} // namespace
+
+ViewType GetViewType(WebContents* tab) {
+ if (!tab)
+ return VIEW_TYPE_INVALID;
+
+ ViewTypeUserData* user_data = static_cast<ViewTypeUserData*>(
+ tab->GetUserData(&kViewTypeUserDataKey));
+
+ return user_data ? user_data->type() : VIEW_TYPE_INVALID;
+}
+
+void SetViewType(WebContents* tab, ViewType type) {
+ tab->SetUserData(&kViewTypeUserDataKey, new ViewTypeUserData(type));
+
+ ExtensionsBrowserClient::Get()->AttachExtensionTaskManagerTag(tab, type);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/view_type_utils.h b/chromium/extensions/browser/view_type_utils.h
new file mode 100644
index 00000000000..aea3d24e5a0
--- /dev/null
+++ b/chromium/extensions/browser/view_type_utils.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_VIEW_TYPE_UTILS_H_
+#define EXTENSIONS_BROWSER_VIEW_TYPE_UTILS_H_
+
+#include "extensions/common/view_type.h"
+
+namespace content {
+class WebContents;
+}
+
+namespace extensions {
+
+// Get/Set the type of a WebContents.
+// GetViewType handles a NULL |tab| for convenience by returning
+// VIEW_TYPE_INVALID.
+ViewType GetViewType(content::WebContents* tab);
+void SetViewType(content::WebContents* tab, ViewType type);
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_VIEW_TYPE_UTILS_H_
diff --git a/chromium/extensions/browser/warning_service.cc b/chromium/extensions/browser/warning_service.cc
new file mode 100644
index 00000000000..8ba356aceab
--- /dev/null
+++ b/chromium/extensions/browser/warning_service.cc
@@ -0,0 +1,135 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/warning_service.h"
+
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/warning_service_factory.h"
+#include "extensions/common/extension_set.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+WarningService::WarningService(content::BrowserContext* browser_context)
+ : browser_context_(browser_context), extension_registry_observer_(this) {
+ DCHECK(CalledOnValidThread());
+ if (browser_context_) {
+ extension_registry_observer_.Add(ExtensionRegistry::Get(
+ ExtensionsBrowserClient::Get()->GetOriginalContext(browser_context_)));
+ }
+}
+
+WarningService::~WarningService() {}
+
+// static
+WarningService* WarningService::Get(content::BrowserContext* browser_context) {
+ return WarningServiceFactory::GetForBrowserContext(browser_context);
+}
+
+void WarningService::ClearWarnings(
+ const std::set<Warning::WarningType>& types) {
+ DCHECK(CalledOnValidThread());
+ ExtensionIdSet affected_extensions;
+ for (WarningSet::iterator i = warnings_.begin();
+ i != warnings_.end();) {
+ if (types.find(i->warning_type()) != types.end()) {
+ affected_extensions.insert(i->extension_id());
+ warnings_.erase(i++);
+ } else {
+ ++i;
+ }
+ }
+
+ if (!affected_extensions.empty())
+ NotifyWarningsChanged(affected_extensions);
+}
+
+std::set<Warning::WarningType> WarningService::
+ GetWarningTypesAffectingExtension(const std::string& extension_id) const {
+ DCHECK(CalledOnValidThread());
+ std::set<Warning::WarningType> result;
+ for (WarningSet::const_iterator i = warnings_.begin();
+ i != warnings_.end(); ++i) {
+ if (i->extension_id() == extension_id)
+ result.insert(i->warning_type());
+ }
+ return result;
+}
+
+std::vector<std::string> WarningService::GetWarningMessagesForExtension(
+ const std::string& extension_id) const {
+ DCHECK(CalledOnValidThread());
+ std::vector<std::string> result;
+
+ const ExtensionSet& extension_set =
+ ExtensionRegistry::Get(browser_context_)->enabled_extensions();
+
+ for (WarningSet::const_iterator i = warnings_.begin();
+ i != warnings_.end(); ++i) {
+ if (i->extension_id() == extension_id)
+ result.push_back(i->GetLocalizedMessage(&extension_set));
+ }
+ return result;
+}
+
+void WarningService::AddWarnings(const WarningSet& warnings) {
+ DCHECK(CalledOnValidThread());
+
+ ExtensionIdSet affected_extensions;
+ for (const Warning& warning : warnings) {
+ if (warnings_.insert(warning).second)
+ affected_extensions.insert(warning.extension_id());
+ }
+ if (!affected_extensions.empty())
+ NotifyWarningsChanged(affected_extensions);
+}
+
+// static
+void WarningService::NotifyWarningsOnUI(
+ void* profile_id,
+ const WarningSet& warnings) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ content::BrowserContext* browser_context =
+ reinterpret_cast<content::BrowserContext*>(profile_id);
+
+ if (!browser_context ||
+ !ExtensionsBrowserClient::Get() ||
+ !ExtensionsBrowserClient::Get()->IsValidContext(browser_context)) {
+ return;
+ }
+
+ WarningService* warning_service = WarningService::Get(browser_context);
+
+ warning_service->AddWarnings(warnings);
+}
+
+void WarningService::AddObserver(Observer* observer) {
+ observer_list_.AddObserver(observer);
+}
+
+void WarningService::RemoveObserver(Observer* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+void WarningService::NotifyWarningsChanged(
+ const ExtensionIdSet& affected_extensions) {
+ FOR_EACH_OBSERVER(Observer, observer_list_,
+ ExtensionWarningsChanged(affected_extensions));
+}
+
+void WarningService::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ // Unloading one extension might have solved the problems of others.
+ // Therefore, we clear warnings of this type for all extensions.
+ std::set<Warning::WarningType> warning_types =
+ GetWarningTypesAffectingExtension(extension->id());
+ ClearWarnings(warning_types);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/warning_service.h b/chromium/extensions/browser/warning_service.h
new file mode 100644
index 00000000000..1d6d89fc5bd
--- /dev/null
+++ b/chromium/extensions/browser/warning_service.h
@@ -0,0 +1,100 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_WARNING_SERVICE_H_
+#define EXTENSIONS_BROWSER_WARNING_SERVICE_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/observer_list.h"
+#include "base/scoped_observer.h"
+#include "base/threading/non_thread_safe.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/browser/warning_set.h"
+
+// TODO(battre) Remove the Extension prefix.
+
+namespace content {
+class BrowserContext;
+class NotificationDetails;
+class NotificationSource;
+}
+
+namespace extensions {
+
+class ExtensionRegistry;
+
+// Manages a set of warnings caused by extensions. These warnings (e.g.
+// conflicting modifications of network requests by extensions, slow extensions,
+// etc.) trigger a warning badge in the UI and and provide means to resolve
+// them. This class must be used on the UI thread only.
+class WarningService : public KeyedService,
+ public ExtensionRegistryObserver,
+ public base::NonThreadSafe {
+ public:
+ class Observer {
+ public:
+ virtual void ExtensionWarningsChanged(
+ const ExtensionIdSet& affected_extensions) = 0;
+ };
+
+ // |browser_context| may be NULL for testing. In this case, be sure to not
+ // insert any warnings.
+ explicit WarningService(content::BrowserContext* browser_context);
+ ~WarningService() override;
+
+ // Get the instance of the WarningService for |browser_context|.
+ // Redirected in incognito.
+ static WarningService* Get(content::BrowserContext* browser_context);
+
+ // Clears all warnings of types contained in |types| and notifies observers
+ // of the changed warnings.
+ void ClearWarnings(const std::set<Warning::WarningType>& types);
+
+ // Returns all types of warnings effecting extension |extension_id|.
+ std::set<Warning::WarningType> GetWarningTypesAffectingExtension(
+ const std::string& extension_id) const;
+
+ // Returns all localized warnings for extension |extension_id| in |result|.
+ std::vector<std::string> GetWarningMessagesForExtension(
+ const std::string& extension_id) const;
+
+ const WarningSet& warnings() const { return warnings_; }
+
+ // Adds a set of warnings and notifies observers if any warning is new.
+ void AddWarnings(const WarningSet& warnings);
+
+ // Notifies the WarningService of browser_context |browser_context_id| that
+ // new |warnings| occurred and triggers a warning badge.
+ static void NotifyWarningsOnUI(void* profile_id, const WarningSet& warnings);
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ private:
+ void NotifyWarningsChanged(const ExtensionIdSet& affected_extensions);
+
+ // ExtensionRegistryObserver implementation.
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+
+ // Currently existing warnings.
+ WarningSet warnings_;
+
+ content::BrowserContext* const browser_context_;
+
+ // Listen to extension unloaded notifications.
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+
+ base::ObserverList<Observer> observer_list_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_WARNING_SERVICE_H_
diff --git a/chromium/extensions/browser/warning_service_factory.cc b/chromium/extensions/browser/warning_service_factory.cc
new file mode 100644
index 00000000000..22298047a44
--- /dev/null
+++ b/chromium/extensions/browser/warning_service_factory.cc
@@ -0,0 +1,49 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/warning_service_factory.h"
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "extensions/browser/extension_registry_factory.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/warning_service.h"
+
+using content::BrowserContext;
+
+namespace extensions {
+
+// static
+WarningService* WarningServiceFactory::GetForBrowserContext(
+ BrowserContext* context) {
+ return static_cast<WarningService*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+WarningServiceFactory* WarningServiceFactory::GetInstance() {
+ return base::Singleton<WarningServiceFactory>::get();
+}
+
+WarningServiceFactory::WarningServiceFactory()
+ : BrowserContextKeyedServiceFactory(
+ "WarningService",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(ExtensionRegistryFactory::GetInstance());
+}
+
+WarningServiceFactory::~WarningServiceFactory() {
+}
+
+KeyedService* WarningServiceFactory::BuildServiceInstanceFor(
+ BrowserContext* context) const {
+ return new WarningService(context);
+}
+
+BrowserContext* WarningServiceFactory::GetBrowserContextToUse(
+ BrowserContext* context) const {
+ // Redirected in incognito.
+ return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/warning_service_factory.h b/chromium/extensions/browser/warning_service_factory.h
new file mode 100644
index 00000000000..22b198d444f
--- /dev/null
+++ b/chromium/extensions/browser/warning_service_factory.h
@@ -0,0 +1,38 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_WARNING_SERVICE_FACTORY_H_
+#define EXTENSIONS_BROWSER_WARNING_SERVICE_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace extensions {
+
+class WarningService;
+
+class WarningServiceFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static WarningService* GetForBrowserContext(content::BrowserContext* context);
+ static WarningServiceFactory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<WarningServiceFactory>;
+
+ WarningServiceFactory();
+ ~WarningServiceFactory() override;
+
+ // BrowserContextKeyedServiceFactory implementation
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(WarningServiceFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_WARNING_SERVICE_FACTORY_H_
diff --git a/chromium/extensions/browser/warning_service_unittest.cc b/chromium/extensions/browser/warning_service_unittest.cc
new file mode 100644
index 00000000000..abe7851a35c
--- /dev/null
+++ b/chromium/extensions/browser/warning_service_unittest.cc
@@ -0,0 +1,123 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/warning_service.h"
+
+#include "content/public/test/test_browser_context.h"
+#include "extensions/browser/extensions_test.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+class TestWarningService : public WarningService {
+ public:
+ explicit TestWarningService(content::BrowserContext* browser_context)
+ : WarningService(browser_context) {
+ }
+ ~TestWarningService() override {}
+
+ void AddWarning(const Warning& warning) {
+ WarningSet warnings;
+ warnings.insert(warning);
+ AddWarnings(warnings);
+ }
+};
+
+class MockObserver : public WarningService::Observer {
+ public:
+ virtual ~MockObserver() {}
+ MOCK_METHOD1(ExtensionWarningsChanged, void(const ExtensionIdSet&));
+};
+
+typedef ExtensionsTest WarningServiceTest;
+
+const char* ext1_id = "extension1";
+const char* ext2_id = "extension2";
+const Warning::WarningType warning_1 = Warning::kNetworkDelay;
+const Warning::WarningType warning_2 = Warning::kNetworkConflict;
+
+} // namespace
+
+// Check that inserting a warning triggers notifications, whereas inserting
+// the same warning again is silent.
+TEST_F(WarningServiceTest, SetWarning) {
+ content::TestBrowserContext browser_context;
+ TestWarningService warning_service(&browser_context);
+ MockObserver observer;
+ warning_service.AddObserver(&observer);
+
+ ExtensionIdSet affected_extensions;
+ affected_extensions.insert(ext1_id);
+ // Insert warning for the first time.
+ EXPECT_CALL(observer, ExtensionWarningsChanged(affected_extensions));
+ warning_service.AddWarning(
+ Warning::CreateNetworkDelayWarning(ext1_id));
+ testing::Mock::VerifyAndClearExpectations(&warning_service);
+
+ // Second insertion of same warning does not trigger anything.
+ warning_service.AddWarning(Warning::CreateNetworkDelayWarning(ext1_id));
+ testing::Mock::VerifyAndClearExpectations(&warning_service);
+
+ warning_service.RemoveObserver(&observer);
+}
+
+// Check that ClearWarnings deletes exactly the specified warnings and
+// triggers notifications where appropriate.
+TEST_F(WarningServiceTest, ClearWarnings) {
+ content::TestBrowserContext browser_context;
+ TestWarningService warning_service(&browser_context);
+ MockObserver observer;
+ warning_service.AddObserver(&observer);
+
+ // Insert two unique warnings in one batch.
+ std::set<std::string> affected_extensions;
+ affected_extensions.insert(ext1_id);
+ affected_extensions.insert(ext2_id);
+ EXPECT_CALL(observer, ExtensionWarningsChanged(affected_extensions));
+ WarningSet warning_set;
+ warning_set.insert(Warning::CreateNetworkDelayWarning(ext1_id));
+ warning_set.insert(Warning::CreateNetworkConflictWarning(ext2_id));
+ warning_service.AddWarnings(warning_set);
+ testing::Mock::VerifyAndClearExpectations(&warning_service);
+
+ // Remove one warning and check that the badge remains.
+ affected_extensions.clear();
+ affected_extensions.insert(ext2_id);
+ EXPECT_CALL(observer, ExtensionWarningsChanged(affected_extensions));
+ std::set<Warning::WarningType> to_clear;
+ to_clear.insert(warning_2);
+ warning_service.ClearWarnings(to_clear);
+ testing::Mock::VerifyAndClearExpectations(&warning_service);
+
+ // Check that the correct warnings appear in |warnings|.
+ std::set<Warning::WarningType> existing_warnings =
+ warning_service.GetWarningTypesAffectingExtension(ext1_id);
+ EXPECT_EQ(1u, existing_warnings.size());
+ existing_warnings =
+ warning_service.GetWarningTypesAffectingExtension(ext2_id);
+ EXPECT_EQ(0u, existing_warnings.size());
+
+ // Remove the other one warning.
+ affected_extensions.clear();
+ affected_extensions.insert(ext1_id);
+ EXPECT_CALL(observer, ExtensionWarningsChanged(affected_extensions));
+ to_clear.insert(warning_1);
+ warning_service.ClearWarnings(to_clear);
+ testing::Mock::VerifyAndClearExpectations(&warning_service);
+
+ // Check that no warnings remain.
+ existing_warnings =
+ warning_service.GetWarningTypesAffectingExtension(ext1_id);
+ EXPECT_EQ(0u, existing_warnings.size());
+ existing_warnings =
+ warning_service.GetWarningTypesAffectingExtension(ext2_id);
+ EXPECT_EQ(0u, existing_warnings.size());
+
+ warning_service.RemoveObserver(&observer);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/warning_set.cc b/chromium/extensions/browser/warning_set.cc
new file mode 100644
index 00000000000..8f750d182b6
--- /dev/null
+++ b/chromium/extensions/browser/warning_set.cc
@@ -0,0 +1,237 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/warning_set.h"
+
+#include <stddef.h>
+
+#include "base/files/file_path.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/strings/grit/extensions_strings.h"
+#include "net/base/escape.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using content::BrowserThread;
+
+namespace {
+// Prefix for message parameters indicating that the parameter needs to
+// be translated from an extension id to the extension name.
+const char kTranslate[] = "TO_TRANSLATE:";
+const size_t kMaxNumberOfParameters = 4;
+}
+
+namespace extensions {
+
+//
+// Warning
+//
+
+Warning::Warning(
+ WarningType type,
+ const std::string& extension_id,
+ int message_id,
+ const std::vector<std::string>& message_parameters)
+ : type_(type),
+ extension_id_(extension_id),
+ message_id_(message_id),
+ message_parameters_(message_parameters) {
+ // These are invalid here because they do not have corresponding warning
+ // messages in the UI.
+ CHECK_NE(type, kInvalid);
+ CHECK_NE(type, kMaxWarningType);
+ CHECK_LE(message_parameters.size(), kMaxNumberOfParameters);
+}
+
+Warning::Warning(const Warning& other)
+ : type_(other.type_),
+ extension_id_(other.extension_id_),
+ message_id_(other.message_id_),
+ message_parameters_(other.message_parameters_) {}
+
+Warning::~Warning() {
+}
+
+Warning& Warning::operator=(const Warning& other) {
+ type_ = other.type_;
+ extension_id_ = other.extension_id_;
+ message_id_ = other.message_id_;
+ message_parameters_ = other.message_parameters_;
+ return *this;
+}
+
+// static
+Warning Warning::CreateNetworkDelayWarning(
+ const std::string& extension_id) {
+ std::vector<std::string> message_parameters;
+ message_parameters.push_back(ExtensionsClient::Get()->GetProductName());
+ return Warning(
+ kNetworkDelay,
+ extension_id,
+ IDS_EXTENSION_WARNINGS_NETWORK_DELAY,
+ message_parameters);
+}
+
+// static
+Warning Warning::CreateNetworkConflictWarning(const std::string& extension_id) {
+ std::vector<std::string> message_parameters;
+ return Warning(
+ kNetworkConflict,
+ extension_id,
+ IDS_EXTENSION_WARNINGS_NETWORK_CONFLICT,
+ message_parameters);
+}
+
+// static
+Warning Warning::CreateRedirectConflictWarning(
+ const std::string& extension_id,
+ const std::string& winning_extension_id,
+ const GURL& attempted_redirect_url,
+ const GURL& winning_redirect_url) {
+ std::vector<std::string> message_parameters;
+ message_parameters.push_back(attempted_redirect_url.spec());
+ message_parameters.push_back(kTranslate + winning_extension_id);
+ message_parameters.push_back(winning_redirect_url.spec());
+ return Warning(
+ kRedirectConflict,
+ extension_id,
+ IDS_EXTENSION_WARNINGS_REDIRECT_CONFLICT,
+ message_parameters);
+}
+
+// static
+Warning Warning::CreateRequestHeaderConflictWarning(
+ const std::string& extension_id,
+ const std::string& winning_extension_id,
+ const std::string& conflicting_header) {
+ std::vector<std::string> message_parameters;
+ message_parameters.push_back(conflicting_header);
+ message_parameters.push_back(kTranslate + winning_extension_id);
+ return Warning(
+ kNetworkConflict,
+ extension_id,
+ IDS_EXTENSION_WARNINGS_REQUEST_HEADER_CONFLICT,
+ message_parameters);
+}
+
+// static
+Warning Warning::CreateResponseHeaderConflictWarning(
+ const std::string& extension_id,
+ const std::string& winning_extension_id,
+ const std::string& conflicting_header) {
+ std::vector<std::string> message_parameters;
+ message_parameters.push_back(conflicting_header);
+ message_parameters.push_back(kTranslate + winning_extension_id);
+ return Warning(
+ kNetworkConflict,
+ extension_id,
+ IDS_EXTENSION_WARNINGS_RESPONSE_HEADER_CONFLICT,
+ message_parameters);
+}
+
+// static
+Warning Warning::CreateCredentialsConflictWarning(
+ const std::string& extension_id,
+ const std::string& winning_extension_id) {
+ std::vector<std::string> message_parameters;
+ message_parameters.push_back(kTranslate + winning_extension_id);
+ return Warning(
+ kNetworkConflict,
+ extension_id,
+ IDS_EXTENSION_WARNINGS_CREDENTIALS_CONFLICT,
+ message_parameters);
+}
+
+// static
+Warning Warning::CreateRepeatedCacheFlushesWarning(
+ const std::string& extension_id) {
+ std::vector<std::string> message_parameters;
+ message_parameters.push_back(ExtensionsClient::Get()->GetProductName());
+ return Warning(
+ kRepeatedCacheFlushes,
+ extension_id,
+ IDS_EXTENSION_WARNINGS_NETWORK_DELAY,
+ message_parameters);
+}
+
+// static
+Warning Warning::CreateDownloadFilenameConflictWarning(
+ const std::string& losing_extension_id,
+ const std::string& winning_extension_id,
+ const base::FilePath& losing_filename,
+ const base::FilePath& winning_filename) {
+ std::vector<std::string> message_parameters;
+ message_parameters.push_back(base::UTF16ToUTF8(
+ losing_filename.LossyDisplayName()));
+ message_parameters.push_back(kTranslate + winning_extension_id);
+ message_parameters.push_back(base::UTF16ToUTF8(
+ winning_filename.LossyDisplayName()));
+ return Warning(
+ kDownloadFilenameConflict,
+ losing_extension_id,
+ IDS_EXTENSION_WARNINGS_DOWNLOAD_FILENAME_CONFLICT,
+ message_parameters);
+}
+
+// static
+Warning Warning::CreateReloadTooFrequentWarning(
+ const std::string& extension_id) {
+ std::vector<std::string> message_parameters;
+ return Warning(kReloadTooFrequent,
+ extension_id,
+ IDS_EXTENSION_WARNING_RELOAD_TOO_FREQUENT,
+ message_parameters);
+}
+
+std::string Warning::GetLocalizedMessage(const ExtensionSet* extensions) const {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ // These parameters may be unsafe (URLs and Extension names) and need
+ // to be HTML-escaped before being embedded in the UI. Also extension IDs
+ // are translated to full extension names.
+ std::vector<base::string16> final_parameters;
+ for (size_t i = 0; i < message_parameters_.size(); ++i) {
+ std::string message = message_parameters_[i];
+ if (base::StartsWith(message, kTranslate, base::CompareCase::SENSITIVE)) {
+ std::string extension_id = message.substr(sizeof(kTranslate) - 1);
+ const extensions::Extension* extension =
+ extensions->GetByID(extension_id);
+ message = extension ? extension->name() : extension_id;
+ }
+ final_parameters.push_back(base::UTF8ToUTF16(net::EscapeForHTML(message)));
+ }
+
+ static_assert(kMaxNumberOfParameters == 4u,
+ "You Need To Add More Case Statements");
+ switch (final_parameters.size()) {
+ case 0:
+ return l10n_util::GetStringUTF8(message_id_);
+ case 1:
+ return l10n_util::GetStringFUTF8(message_id_, final_parameters[0]);
+ case 2:
+ return l10n_util::GetStringFUTF8(message_id_, final_parameters[0],
+ final_parameters[1]);
+ case 3:
+ return l10n_util::GetStringFUTF8(message_id_, final_parameters[0],
+ final_parameters[1], final_parameters[2]);
+ case 4:
+ return l10n_util::GetStringFUTF8(message_id_, final_parameters[0],
+ final_parameters[1], final_parameters[2], final_parameters[3]);
+ default:
+ NOTREACHED();
+ return std::string();
+ }
+}
+
+bool operator<(const Warning& a, const Warning& b) {
+ if (a.extension_id() != b.extension_id())
+ return a.extension_id() < b.extension_id();
+ return a.warning_type() < b.warning_type();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/warning_set.h b/chromium/extensions/browser/warning_set.h
new file mode 100644
index 00000000000..015c98aebce
--- /dev/null
+++ b/chromium/extensions/browser/warning_set.h
@@ -0,0 +1,121 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_WARNING_SET_H_
+#define EXTENSIONS_BROWSER_WARNING_SET_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "url/gurl.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace extensions {
+
+class ExtensionSet;
+
+// This class is used by the WarningService to represent warnings if extensions
+// misbehave. Note that the WarningService deals only with specific warnings
+// that should trigger a badge on the Chrome menu button.
+class Warning {
+ public:
+ enum WarningType {
+ // Don't use this, it is only intended for the default constructor and
+ // does not have localized warning messages for the UI.
+ kInvalid = 0,
+ // An extension caused excessive network delays.
+ kNetworkDelay,
+ // This extension failed to modify a network request because the
+ // modification conflicted with a modification of another extension.
+ kNetworkConflict,
+ // This extension failed to redirect a network request because another
+ // extension with higher precedence redirected to a different target.
+ kRedirectConflict,
+ // The extension repeatedly flushed WebKit's in-memory cache, which slows
+ // down the overall performance.
+ kRepeatedCacheFlushes,
+ // The extension failed to determine the filename of a download because
+ // another extension with higher precedence determined a different filename.
+ kDownloadFilenameConflict,
+ kReloadTooFrequent,
+ kMaxWarningType
+ };
+
+ // We allow copy&assign for passing containers of Warnings between threads.
+ Warning(const Warning& other);
+ ~Warning();
+ Warning& operator=(const Warning& other);
+
+ // Factory methods for various warning types.
+ static Warning CreateNetworkDelayWarning(
+ const std::string& extension_id);
+ static Warning CreateNetworkConflictWarning(
+ const std::string& extension_id);
+ static Warning CreateRedirectConflictWarning(
+ const std::string& extension_id,
+ const std::string& winning_extension_id,
+ const GURL& attempted_redirect_url,
+ const GURL& winning_redirect_url);
+ static Warning CreateRequestHeaderConflictWarning(
+ const std::string& extension_id,
+ const std::string& winning_extension_id,
+ const std::string& conflicting_header);
+ static Warning CreateResponseHeaderConflictWarning(
+ const std::string& extension_id,
+ const std::string& winning_extension_id,
+ const std::string& conflicting_header);
+ static Warning CreateCredentialsConflictWarning(
+ const std::string& extension_id,
+ const std::string& winning_extension_id);
+ static Warning CreateRepeatedCacheFlushesWarning(
+ const std::string& extension_id);
+ static Warning CreateDownloadFilenameConflictWarning(
+ const std::string& losing_extension_id,
+ const std::string& winning_extension_id,
+ const base::FilePath& losing_filename,
+ const base::FilePath& winning_filename);
+ static Warning CreateReloadTooFrequentWarning(
+ const std::string& extension_id);
+
+ // Returns the specific warning type.
+ WarningType warning_type() const { return type_; }
+
+ // Returns the id of the extension for which this warning is valid.
+ const std::string& extension_id() const { return extension_id_; }
+
+ // Returns a localized warning message.
+ std::string GetLocalizedMessage(const ExtensionSet* extensions) const;
+
+ private:
+ // Constructs a warning of type |type| for extension |extension_id|. This
+ // could indicate for example the fact that an extension conflicted with
+ // others. The |message_id| refers to an IDS_ string ID. The
+ // |message_parameters| are filled into the message template.
+ Warning(WarningType type,
+ const std::string& extension_id,
+ int message_id,
+ const std::vector<std::string>& message_parameters);
+
+ WarningType type_;
+ std::string extension_id_;
+ // IDS_* resource ID.
+ int message_id_;
+ // Parameters to be filled into the string identified by |message_id_|.
+ std::vector<std::string> message_parameters_;
+};
+
+// Compare Warnings based on the tuple of (extension_id, type).
+// The message associated with Warnings is purely informational
+// and does not contribute to distinguishing extensions.
+bool operator<(const Warning& a, const Warning& b);
+
+typedef std::set<Warning> WarningSet;
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_WARNING_SET_H_
diff --git a/chromium/extensions/browser/web_ui_user_script_loader.cc b/chromium/extensions/browser/web_ui_user_script_loader.cc
new file mode 100644
index 00000000000..6954f25188e
--- /dev/null
+++ b/chromium/extensions/browser/web_ui_user_script_loader.cc
@@ -0,0 +1,153 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/web_ui_user_script_loader.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/strings/string_util.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "extensions/browser/guest_view/web_view/web_ui/web_ui_url_fetcher.h"
+
+namespace {
+
+void SerializeOnFileThread(
+ scoped_ptr<extensions::UserScriptList> user_scripts,
+ extensions::UserScriptLoader::LoadScriptsCallback callback) {
+ scoped_ptr<base::SharedMemory> memory =
+ extensions::UserScriptLoader::Serialize(*user_scripts);
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI, FROM_HERE,
+ base::Bind(callback, base::Passed(&user_scripts), base::Passed(&memory)));
+}
+
+} // namespace
+
+struct WebUIUserScriptLoader::UserScriptRenderInfo {
+ int render_process_id;
+ int render_view_id;
+
+ UserScriptRenderInfo() : render_process_id(-1), render_view_id(-1) {}
+
+ UserScriptRenderInfo(int render_process_id, int render_view_id)
+ : render_process_id(render_process_id), render_view_id(render_view_id) {}
+};
+
+WebUIUserScriptLoader::WebUIUserScriptLoader(
+ content::BrowserContext* browser_context,
+ const HostID& host_id)
+ : UserScriptLoader(browser_context, host_id), complete_fetchers_(0) {
+ SetReady(true);
+}
+
+WebUIUserScriptLoader::~WebUIUserScriptLoader() {
+}
+
+void WebUIUserScriptLoader::AddScripts(
+ const std::set<extensions::UserScript>& scripts,
+ int render_process_id,
+ int render_view_id) {
+ UserScriptRenderInfo info(render_process_id, render_view_id);
+ for (const extensions::UserScript& script : scripts) {
+ script_render_info_map_.insert(
+ std::pair<int, UserScriptRenderInfo>(script.id(), info));
+ }
+
+ extensions::UserScriptLoader::AddScripts(scripts);
+}
+
+void WebUIUserScriptLoader::LoadScripts(
+ scoped_ptr<extensions::UserScriptList> user_scripts,
+ const std::set<HostID>& changed_hosts,
+ const std::set<int>& added_script_ids,
+ LoadScriptsCallback callback) {
+ user_scripts_cache_.swap(user_scripts);
+ scripts_loaded_callback_ = callback;
+
+ // The total number of the tasks is used to trace whether all the fetches
+ // are complete. Therefore, we store all the fetcher pointers in |fetchers_|
+ // before we get theis number. Once we get the total number, start each
+ // fetch tasks.
+ DCHECK_EQ(0u, complete_fetchers_);
+
+ for (extensions::UserScript& script : *user_scripts_cache_) {
+ if (added_script_ids.count(script.id()) == 0)
+ continue;
+
+ auto iter = script_render_info_map_.find(script.id());
+ DCHECK(iter != script_render_info_map_.end());
+ int render_process_id = iter->second.render_process_id;
+ int render_view_id = iter->second.render_view_id;
+
+ content::BrowserContext* browser_context =
+ content::RenderProcessHost::FromID(render_process_id)
+ ->GetBrowserContext();
+
+ CreateWebUIURLFetchers(&script.js_scripts(), browser_context,
+ render_process_id, render_view_id);
+ CreateWebUIURLFetchers(&script.css_scripts(), browser_context,
+ render_process_id, render_view_id);
+
+ script_render_info_map_.erase(script.id());
+ }
+
+ // If no fetch is needed, call OnWebUIURLFetchComplete directly.
+ if (fetchers_.empty()) {
+ OnWebUIURLFetchComplete();
+ return;
+ }
+ for (const auto& fetcher : fetchers_)
+ fetcher->Start();
+}
+
+void WebUIUserScriptLoader::CreateWebUIURLFetchers(
+ extensions::UserScript::FileList* script_files,
+ content::BrowserContext* browser_context,
+ int render_process_id,
+ int render_view_id) {
+ for (extensions::UserScript::File& file : *script_files) {
+ if (file.GetContent().empty()) {
+ // The WebUIUserScriptLoader owns these WebUIURLFetchers. Once the
+ // loader is destroyed, all the fetchers will be destroyed. Therefore,
+ // we are sure it is safe to use base::Unretained(this) here.
+ scoped_ptr<WebUIURLFetcher> fetcher(new WebUIURLFetcher(
+ browser_context, render_process_id, render_view_id, file.url(),
+ base::Bind(&WebUIUserScriptLoader::OnSingleWebUIURLFetchComplete,
+ base::Unretained(this), &file)));
+ fetchers_.push_back(std::move(fetcher));
+ }
+ }
+}
+
+void WebUIUserScriptLoader::OnSingleWebUIURLFetchComplete(
+ extensions::UserScript::File* script_file,
+ bool success,
+ const std::string& data) {
+ if (success) {
+ // Remove BOM from the content.
+ std::string::size_type index = data.find(base::kUtf8ByteOrderMark);
+ if (index == 0)
+ script_file->set_content(data.substr(strlen(base::kUtf8ByteOrderMark)));
+ else
+ script_file->set_content(data);
+ }
+
+ ++complete_fetchers_;
+ if (complete_fetchers_ == fetchers_.size()) {
+ complete_fetchers_ = 0;
+ OnWebUIURLFetchComplete();
+ fetchers_.clear();
+ }
+}
+
+void WebUIUserScriptLoader::OnWebUIURLFetchComplete() {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::FILE, FROM_HERE,
+ base::Bind(&SerializeOnFileThread, base::Passed(&user_scripts_cache_),
+ scripts_loaded_callback_));
+ scripts_loaded_callback_.Reset();
+}
diff --git a/chromium/extensions/browser/web_ui_user_script_loader.h b/chromium/extensions/browser/web_ui_user_script_loader.h
new file mode 100644
index 00000000000..f0e3c905435
--- /dev/null
+++ b/chromium/extensions/browser/web_ui_user_script_loader.h
@@ -0,0 +1,76 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_WEB_UI_USER_SCRIPT_LOADER_H_
+#define EXTENSIONS_BROWSER_WEB_UI_USER_SCRIPT_LOADER_H_
+
+#include <stddef.h>
+
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/browser/user_script_loader.h"
+
+class WebUIURLFetcher;
+
+namespace content {
+class BrowserContext;
+}
+
+// UserScriptLoader for WebUI.
+class WebUIUserScriptLoader : public extensions::UserScriptLoader {
+ public:
+ WebUIUserScriptLoader(content::BrowserContext* browser_context,
+ const HostID& host_id);
+ ~WebUIUserScriptLoader() override;
+
+ private:
+ struct UserScriptRenderInfo;
+ using UserScriptRenderInfoMap = std::map<int, UserScriptRenderInfo>;
+
+ // UserScriptLoader:
+ void AddScripts(const std::set<extensions::UserScript>& scripts,
+ int render_process_id,
+ int render_view_id) override;
+ void LoadScripts(scoped_ptr<extensions::UserScriptList> user_scripts,
+ const std::set<HostID>& changed_hosts,
+ const std::set<int>& added_script_ids,
+ LoadScriptsCallback callback) override;
+
+ // Called at the end of each fetch, tracking whether all fetches are done.
+ void OnSingleWebUIURLFetchComplete(extensions::UserScript::File* script_file,
+ bool success,
+ const std::string& data);
+
+ // Called when the loads of the user scripts are done.
+ void OnWebUIURLFetchComplete();
+
+ // Creates WebUiURLFetchers for the given |script_files|.
+ void CreateWebUIURLFetchers(extensions::UserScript::FileList* script_files,
+ content::BrowserContext* browser_context,
+ int render_process_id,
+ int render_view_id);
+
+ // Caches the render info of script from WebUI when AddScripts is called.
+ // When starting to load the script, we look up this map to retrieve the
+ // render info. It is used for the script from WebUI only, since the fetch
+ // of script content requires the info of associated render.
+ UserScriptRenderInfoMap script_render_info_map_;
+
+ // The number of complete fetchs.
+ size_t complete_fetchers_;
+
+ // Caches |user_scripts_| from UserScriptLoader when loading.
+ scoped_ptr<extensions::UserScriptList> user_scripts_cache_;
+
+ LoadScriptsCallback scripts_loaded_callback_;
+
+ std::vector<scoped_ptr<WebUIURLFetcher>> fetchers_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebUIUserScriptLoader);
+};
+
+#endif // EXTENSIONS_BROWSER_WEB_UI_USER_SCRIPT_LOADER_H_
diff --git a/chromium/extensions/common/BUILD.gn b/chromium/extensions/common/BUILD.gn
new file mode 100644
index 00000000000..5ed19135b10
--- /dev/null
+++ b/chromium/extensions/common/BUILD.gn
@@ -0,0 +1,90 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/features.gni")
+import("//extensions/extensions.gni")
+import("//mojo/public/tools/bindings/mojom.gni")
+
+# GYP version: extensions/extensions.gyp:extensions_common_constants
+source_set("common_constants") {
+ sources =
+ rebase_path(extensions_gypi_values.extensions_common_constants_sources,
+ ".",
+ "//extensions")
+
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ configs += [ "//build/config/compiler:no_size_t_to_int_warning" ]
+}
+
+if (enable_extensions) {
+ mojom("mojo") {
+ sources = rebase_path(extensions_gypi_values.extensions_common_mojo_sources,
+ ".",
+ "//extensions")
+ if (enable_wifi_display) {
+ wifi_display_sources = rebase_path(
+ extensions_gypi_values.extensions_common_mojo_sources_wifi_display,
+ ".",
+ "//extensions")
+ sources += wifi_display_sources
+ }
+ }
+
+ # GYP version: extensions/extensions.gyp:extensions_common
+ # This must be a static library because extensions common depends on
+ # GetTrustedICAPublicKey in extensions/browser which isn't always linked
+ # in. TODO(brettw): This reverse dependency should be fixed.
+ static_library("common") {
+ sources = rebase_path(extensions_gypi_values.extensions_common_sources,
+ ".",
+ "//extensions")
+
+ configs += [
+ "//build/config:precompiled_headers",
+
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ "//build/config/compiler:no_size_t_to_int_warning",
+ ]
+
+ public_deps = [
+ ":common_constants",
+ ":mojo",
+ ]
+
+ deps = [
+ # TODO(benwells): figure out what to do with the api target and
+ # api resources compiled into the chrome resource bundle.
+ # http://crbug.com/162530
+ "//chrome:resources",
+ "//components/crx_file",
+ "//components/url_matcher",
+ "//content/public/common",
+ "//crypto",
+ "//device/bluetooth",
+ "//device/usb",
+ "//extensions:extensions_resources",
+ "//extensions/common/api",
+ "//extensions/strings",
+ "//ipc",
+ "//net",
+ "//third_party/boringssl",
+ "//third_party/icu",
+ "//third_party/libxml",
+ "//third_party/re2",
+ "//ui/base",
+ "//ui/gfx/geometry",
+ "//ui/gfx/ipc",
+ "//ui/gfx/ipc/skia",
+ "//url",
+ ]
+
+ if (enable_nacl) {
+ nacl_sources =
+ rebase_path(extensions_gypi_values.extensions_common_sources_nacl,
+ ".",
+ "//extensions")
+ sources += nacl_sources
+ }
+ }
+} # enable_extensions
diff --git a/chromium/extensions/common/DEPS b/chromium/extensions/common/DEPS
new file mode 100644
index 00000000000..bef368e18fe
--- /dev/null
+++ b/chromium/extensions/common/DEPS
@@ -0,0 +1,9 @@
+include_rules = [
+ "+device/bluetooth", # For BluetoothPermission
+ "+device/usb",
+ "+grit/extensions_strings.h",
+ "+libxml",
+ "+net",
+ "+third_party/libxml",
+ "+third_party/re2",
+]
diff --git a/chromium/extensions/common/OWNERS b/chromium/extensions/common/OWNERS
new file mode 100644
index 00000000000..b5f2a45334a
--- /dev/null
+++ b/chromium/extensions/common/OWNERS
@@ -0,0 +1,12 @@
+# For security review of IPC message files.
+per-file *_messages*.h=set noparent
+per-file *_messages*.h=dcheng@chromium.org
+per-file *_messages*.h=inferno@chromium.org
+per-file *_messages*.h=jln@chromium.org
+per-file *_messages*.h=jschuh@chromium.org
+per-file *_messages*.h=kenrb@chromium.org
+per-file *_messages*.h=mkwst@chromium.org
+per-file *_messages*.h=nasko@chromium.org
+per-file *_messages*.h=palmer@chromium.org
+per-file *_messages*.h=tsepez@chromium.org
+per-file *_messages*.h=wfh@chromium.org
diff --git a/chromium/extensions/common/PRESUBMIT.py b/chromium/extensions/common/PRESUBMIT.py
new file mode 100644
index 00000000000..16c19465c42
--- /dev/null
+++ b/chromium/extensions/common/PRESUBMIT.py
@@ -0,0 +1,14 @@
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Chromium presubmit script for src/extensions/common.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details on the presubmit API built into depot_tools.
+"""
+
+import sys
+
+def CheckChangeOnUpload(input_api, output_api):
+ return input_api.canned_checks.CheckPatchFormatted(input_api, output_api)
diff --git a/chromium/extensions/common/api/BUILD.gn b/chromium/extensions/common/api/BUILD.gn
new file mode 100644
index 00000000000..5f259ecae8a
--- /dev/null
+++ b/chromium/extensions/common/api/BUILD.gn
@@ -0,0 +1,46 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/features.gni")
+import("//build/json_schema_api.gni")
+import("//mojo/public/tools/bindings/mojom.gni")
+import("schemas.gni")
+
+assert(enable_extensions)
+
+json_schema_api("generated_api") {
+ schemas = true
+ bundle = true
+ bundle_name = ""
+}
+
+# GYP version: extensions/common/api/api.gyp:extensions_api_mojom
+mojom("mojom") {
+ sources = []
+ sources = [
+ "mime_handler.mojom",
+ ]
+}
+
+# GYP version: extensions/common/api/api.gyp:extensions_api
+group("api") {
+ public_deps = [
+ ":generated_api",
+ ":mojom",
+ ]
+}
+
+# GYP version: extensions/browser/api/api_registration.gyp:extensions_api_registration
+json_schema_api("api_registration") {
+ impl_dir = "//extensions/browser/api"
+ bundle_registration = true
+ bundle_name = ""
+
+ deps = [
+ ":api",
+ "//device/serial",
+ "//extensions/common/api/cast_channel:cast_channel_proto",
+ "//skia",
+ ]
+}
diff --git a/chromium/extensions/common/api/OWNERS b/chromium/extensions/common/api/OWNERS
new file mode 100644
index 00000000000..2c7f4bc4486
--- /dev/null
+++ b/chromium/extensions/common/api/OWNERS
@@ -0,0 +1,6 @@
+per-file *view*.json=fsamuel@chromium.org
+per-file *view*.json=lazyboy@chromium.org
+per-file *view*.json=hanxi@chromium.org
+per-file *view*.json=wjmaclean@chromium.org
+per-file *view*.json=lfg@chromium.org
+per-file *view*.json=paulmeyer@chromium.org \ No newline at end of file
diff --git a/chromium/extensions/common/api/PRESUBMIT.py b/chromium/extensions/common/api/PRESUBMIT.py
new file mode 100644
index 00000000000..b74059fe4c2
--- /dev/null
+++ b/chromium/extensions/common/api/PRESUBMIT.py
@@ -0,0 +1,37 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Chromium presubmit script for src/extensions/common.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details on the presubmit API built into depot_tools.
+"""
+
+import sys
+
+
+def _CheckExterns(input_api, output_api):
+ original_sys_path = sys.path
+
+ try:
+ sys.path.append(input_api.PresubmitLocalPath())
+ from externs_checker import ExternsChecker
+ finally:
+ sys.path = original_sys_path
+
+ join = input_api.os_path.join
+ api_root = input_api.PresubmitLocalPath()
+ externs_root = join(api_root, '..', '..', '..', 'third_party',
+ 'closure_compiler', 'externs')
+
+ api_pairs = {
+ join(api_root, 'bluetooth.idl'): join(externs_root, 'bluetooth.js'),
+ # TODO(rdevlin.cronin): Add more!
+ }
+
+ return ExternsChecker(input_api, output_api, api_pairs).RunChecks()
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return _CheckExterns(input_api, output_api)
diff --git a/chromium/extensions/common/api/README b/chromium/extensions/common/api/README
new file mode 100644
index 00000000000..6a5f7de946e
--- /dev/null
+++ b/chromium/extensions/common/api/README
@@ -0,0 +1 @@
+This is a dummy file to make the doc server happy. This should go away ASAP.
diff --git a/chromium/extensions/common/api/_api_features.json b/chromium/extensions/common/api/_api_features.json
new file mode 100644
index 00000000000..e57362b175d
--- /dev/null
+++ b/chromium/extensions/common/api/_api_features.json
@@ -0,0 +1,489 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This features file defines extension APIs implemented under src/extensions.
+// See extensions/common/features/* to understand this file, in particular
+// feature.h, simple_feature.h, and base_feature_provider.h.
+//
+// Note that specifying "web_page", "blessed_web_page", or "all" as a context
+// type will require manually updating chrome/renderer/resources/dispatcher.cc.
+
+// To add a new whitelisted ID, SHA-1 it and force it to uppercase. In Bash:
+//
+// $ echo -n "aaaabbbbccccddddeeeeffffgggghhhh" | \
+// sha1sum | tr '[:lower:]' '[:upper:]'
+// 9A0417016F345C934A1A88F55CA17C05014EEEBA -
+//
+// Google employees: please update http://go/chrome-api-whitelist to map
+// hashes back to ids.
+
+{
+ "alarms": {
+ "dependencies": ["permission:alarms"],
+ "contexts": ["blessed_extension"]
+ },
+ "app.runtime": [{
+ "channel": "stable",
+ "contexts": ["blessed_extension"],
+ "extension_types": ["platform_app"],
+ "noparent": true
+ }, {
+ "channel": "stable",
+ "component_extensions_auto_granted": false,
+ "contexts": ["blessed_extension"],
+ "extension_types": ["extension"],
+ "noparent": true,
+ "whitelist": [
+ "2FC374607C2DF285634B67C64A2E356C607091C3", // Quickoffice
+ "3727DD3E564B6055387425027AD74C58784ACC15", // Quickoffice internal
+ "12E618C3C6E97495AAECF2AC12DEB082353241C6" // QO component extension
+ ]
+ }],
+ "app.window": [{
+ "channel": "stable",
+ "contexts": ["blessed_extension"],
+ "extension_types": ["platform_app"],
+ "noparent": true
+ }, {
+ "channel": "stable",
+ "contexts": ["blessed_extension"],
+ "extension_types": ["extension"],
+ "noparent": true,
+ "component_extensions_auto_granted": false,
+ "whitelist": [
+ "B9EF10DDFEA11EF77873CC5009809E5037FC4C7A", // Google input tools
+ "06BE211D5F014BAB34BC22D9DDA09C63A81D828E", // Official xkb extension
+ "F94EE6AB36D6C6588670B2B01EB65212D9C64E33" // Open source xkb extension
+ ]
+ }],
+ "app.currentWindowInternal": {
+ "noparent": true,
+ "internal": true,
+ "channel": "stable",
+ "contexts": ["blessed_extension"]
+ },
+ "app.currentWindowInternal.setShape": {
+ "dependencies": ["permission:app.window.shape"],
+ "contexts": ["blessed_extension"]
+ },
+ // The API for the *embedder* of appview. Appview has both an embedder and
+ // guest API, which are different.
+ "appViewEmbedderInternal": {
+ "internal": true,
+ "contexts": ["blessed_extension"],
+ "dependencies": ["permission:appview"]
+ },
+ // Note that exposing this doesn't necessarily expose AppView,
+ // appViewEmbedderInternal is required for that.
+ // See http://crbug.com/437891.
+ "appViewGuestInternal": {
+ "internal": true,
+ "channel": "stable",
+ "contexts": ["blessed_extension"]
+ },
+ "audio": {
+ "dependencies": ["permission:audio"],
+ "contexts": ["blessed_extension"]
+ },
+ "bluetooth": [{
+ "dependencies": ["manifest:bluetooth"],
+ "contexts": ["blessed_extension"]
+ }, {
+ "channel": "stable",
+ "contexts": ["webui"],
+ "matches": [
+ "chrome://bluetooth-pairing/*",
+ "chrome://md-settings/*",
+ "chrome://settings/*",
+ "chrome://settings-frame/*"
+ ]
+ }],
+ "bluetoothLowEnergy": {
+ "dependencies": ["manifest:bluetooth"],
+ "contexts": ["blessed_extension"]
+ },
+ "bluetoothPrivate": [{
+ "dependencies": ["permission:bluetoothPrivate"],
+ "contexts": ["blessed_extension"]
+ }, {
+ "channel": "stable",
+ "contexts": ["webui"],
+ "matches": [
+ "chrome://bluetooth-pairing/*",
+ "chrome://md-settings/*",
+ "chrome://settings/*",
+ "chrome://settings-frame/*"
+ ]
+ }],
+ "bluetoothSocket": {
+ "dependencies": ["manifest:bluetooth"],
+ "contexts": ["blessed_extension"]
+ },
+ "declarativeWebRequest": {
+ "dependencies": ["permission:declarativeWebRequest"],
+ "contexts": ["blessed_extension"]
+ },
+ "diagnostics": {
+ "dependencies": ["permission:diagnostics"],
+ "extension_types": ["platform_app"],
+ "contexts": ["blessed_extension"]
+ },
+ "displaySource": {
+ "dependencies": ["permission:displaySource"],
+ "contexts": ["blessed_extension"]
+ },
+ "dns": {
+ "dependencies": ["permission:dns"],
+ "contexts": ["blessed_extension"]
+ },
+ "documentScan": {
+ "dependencies": ["permission:documentScan"],
+ "contexts": ["blessed_extension"]
+ },
+ // This is not a real API, only here for documentation purposes.
+ // See http://crbug.com/275944 for background.
+ "extensionTypes": {
+ "internal": true,
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app", "platform_app"],
+ "contexts": ["blessed_extension"]
+ },
+ "extensionViewInternal": [
+ {
+ "internal": true,
+ "contexts": ["blessed_extension"],
+ "dependencies": ["permission:extensionview"]
+ }, {
+ "internal": true,
+ "channel": "stable",
+ "contexts": ["webui"],
+ "matches": ["chrome://media-router/*"]
+ }
+ ],
+ "events": {
+ "internal": true,
+ "channel": "stable",
+ "extension_types": ["platform_app", "extension"],
+ "contexts": "all",
+ "matches": ["<all_urls>"]
+ },
+ "guestViewInternal": [
+ {
+ "internal": true,
+ "channel": "stable",
+ "contexts": ["blessed_extension"]
+ }, {
+ "internal": true,
+ "channel": "trunk",
+ "contexts": ["webui"],
+ "matches": [
+ "chrome://extensions-frame/*",
+ "chrome://extensions/*",
+ "chrome://chrome-signin/*",
+ "chrome://media-router/*",
+ "chrome://oobe/*"
+ ]
+ },
+ // This allows GuestViews to be created on regular web pages for the Worker
+ // Thread Frame prototype: http://crbug.com/434226.
+ {
+ "internal": true,
+ "channel": "trunk",
+ "contexts": "all",
+ "matches": ["<all_urls>"]
+ }
+ ],
+ "hid": {
+ "dependencies": ["permission:hid"],
+ "contexts": ["blessed_extension"]
+ },
+ "hid.getUserSelectedDevices": {
+ "contexts": ["blessed_extension"],
+ "channel": "dev",
+ "dependencies": ["permission:hid"]
+ },
+ "idle": {
+ "dependencies": ["permission:idle"],
+ "contexts": ["blessed_extension"]
+ },
+ "management": [{
+ "dependencies": ["permission:management"],
+ "contexts": ["blessed_extension"],
+ "default_parent": true
+ }, {
+ "channel": "stable",
+ "contexts": ["webui"],
+ "matches": [
+ "chrome://extensions/*",
+ "chrome://extensions-frame/*",
+ "chrome://chrome/extensions/*",
+ "chrome://md-settings/*",
+ "chrome://settings/*",
+ "chrome://settings-frame/*"
+ ]
+ }],
+ "management.getPermissionWarningsByManifest": {
+ "dependencies": [],
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app", "platform_app"]
+ },
+ "management.getSelf": {
+ "dependencies": [],
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app", "platform_app"]
+ },
+ "management.uninstallSelf": {
+ "dependencies": [],
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app", "platform_app"]
+ },
+ "mimeHandlerPrivate": {
+ "dependencies": ["manifest:mime_types_handler"],
+ "contexts": ["blessed_extension"]
+ },
+ "mojoPrivate": {
+ "contexts": ["blessed_extension"],
+ "channel": "stable",
+ "extension_types": ["platform_app", "extension"],
+ "whitelist": [
+ "226CF815E39A363090A1E547D53063472B8279FA", // http://crbug.com/574889
+ "C17CD9E6868D7B9C67926E0EC612EA25C768418F", // http://crbug.com/448569
+ "A45DABDB47A31CC812E5490AB748C7D05E2D32E9",
+ "BFEE2E3B80BE21A645E63E9346DFC383E7CB3BDA",
+ "63ED55E43214C211F82122ED56407FF1A807F2A3"
+ ]
+ },
+ "networking.config": {
+ "dependencies": ["permission:networking.config"],
+ "contexts": ["blessed_extension"]
+ },
+ "networkingPrivate": [{
+ "dependencies": ["permission:networkingPrivate"],
+ "contexts": ["blessed_extension"]
+ }, {
+ "channel": "stable",
+ "contexts": ["webui"],
+ "matches": [
+ "chrome://md-settings/*",
+ "chrome://network/*",
+ "chrome://settings/*",
+ "chrome://settings-frame/*"
+ ]
+ }],
+ "power": {
+ "dependencies": ["permission:power"],
+ "contexts": ["blessed_extension"]
+ },
+ "printerProvider": {
+ "dependencies": ["permission:printerProvider"],
+ "contexts": ["blessed_extension"]
+ },
+ "printerProviderInternal": {
+ "internal": true,
+ "dependencies": ["permission:printerProvider"],
+ "contexts": ["blessed_extension"]
+ },
+ "runtime": {
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app", "platform_app"],
+ "contexts": ["blessed_extension"]
+ },
+ "runtime.getManifest": {
+ "contexts": ["blessed_extension", "unblessed_extension", "content_script"]
+ },
+ "runtime.connect": {
+ // Everything except WebUI.
+ "contexts": [
+ "blessed_web_page",
+ "content_script",
+ "blessed_extension",
+ "unblessed_extension",
+ "web_page"
+ ],
+ "matches": ["<all_urls>"]
+ },
+ "runtime.connectNative": {
+ "dependencies": ["permission:nativeMessaging"],
+ "contexts": ["blessed_extension"]
+ },
+ "runtime.getURL": {
+ "contexts": ["blessed_extension", "unblessed_extension", "content_script"]
+ },
+ "runtime.id": {
+ "contexts": ["blessed_extension", "unblessed_extension", "content_script"]
+ },
+ "runtime.lastError": {
+ "contexts": "all",
+ "extension_types": "all",
+ "matches": ["<all_urls>"]
+ },
+ "runtime.onConnect": {
+ "contexts": ["blessed_extension", "unblessed_extension", "content_script"]
+ },
+ "runtime.onMessage": {
+ "contexts": ["blessed_extension", "unblessed_extension", "content_script"]
+ },
+ "runtime.sendMessage": {
+ // Everything except WebUI.
+ "contexts": [
+ "blessed_web_page",
+ "content_script",
+ "blessed_extension",
+ "unblessed_extension",
+ "web_page"
+ ],
+ "matches": ["<all_urls>"]
+ },
+ "runtime.sendNativeMessage": {
+ "dependencies": ["permission:nativeMessaging"],
+ "contexts": ["blessed_extension"]
+ },
+ "serial": {
+ "dependencies": ["permission:serial"],
+ "contexts": ["blessed_extension"]
+ },
+ "socket": {
+ "dependencies": ["permission:socket"],
+ "contexts": ["blessed_extension"]
+ },
+ "sockets.tcp": {
+ "dependencies": ["manifest:sockets"],
+ "contexts": ["blessed_extension"]
+ },
+ "sockets.tcpServer": {
+ "dependencies": ["manifest:sockets"],
+ "contexts": ["blessed_extension"]
+ },
+ "sockets.udp": {
+ "dependencies": ["manifest:sockets"],
+ "contexts": ["blessed_extension"]
+ },
+ "storage": {
+ "dependencies": ["permission:storage"],
+ "contexts": ["blessed_extension", "unblessed_extension", "content_script"]
+ },
+ "system.cpu": {
+ "dependencies": ["permission:system.cpu"],
+ "contexts": ["blessed_extension"]
+ },
+ "system.display": [{
+ "dependencies": ["permission:system.display"],
+ "contexts": ["blessed_extension"]
+ }, {
+ "channel": "stable",
+ "contexts": ["webui"],
+ "matches": [
+ "chrome://md-settings/*",
+ "chrome://settings/*",
+ "chrome://settings-frame/*"
+ ]
+ }],
+ "system.memory": {
+ "dependencies": ["permission:system.memory"],
+ "contexts": ["blessed_extension"]
+ },
+ "system.network": {
+ "dependencies": ["permission:system.network"],
+ "contexts": ["blessed_extension"]
+ },
+ "system.storage": {
+ "dependencies": ["permission:system.storage"],
+ "contexts": ["blessed_extension"]
+ },
+ "system.storage.getAvailableCapacity": {
+ "channel": "dev"
+ },
+ "test": [{
+ "channel": "stable",
+ "extension_types": "all",
+ // Everything except web pages and WebUI. WebUI is declared in a separate
+ // rule to keep the "matches" property isolated.
+ "contexts": [
+ "blessed_extension",
+ "blessed_web_page",
+ "content_script",
+ "unblessed_extension"
+ ]
+ }, {
+ "channel": "stable",
+ "contexts": ["webui"],
+ "matches": [
+ "chrome://extensions/*",
+ "chrome://extensions-frame/*",
+ "chrome://chrome/extensions/*"
+ ]
+ }],
+ "types": {
+ "internal": true,
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app", "platform_app"],
+ "contexts": ["blessed_extension"]
+ },
+ "types.private": {
+ // preferencesPrivate is the only API that uses types.private.
+ // If any other APIs need it then they'll need to be added in
+ // separate rules.
+ "dependencies": ["permission:preferencesPrivate"],
+ "contexts": ["blessed_extension"]
+ },
+ "usb": {
+ "dependencies": ["permission:usb"],
+ "contexts": ["blessed_extension"]
+ },
+ "vpnProvider": {
+ "dependencies": ["permission:vpnProvider"],
+ "contexts": ["blessed_extension"]
+ },
+ "webRequest": {
+ "dependencies": ["permission:webRequest"],
+ "contexts": ["blessed_extension"]
+ },
+ "webRequestInternal": [{
+ "internal": true,
+ "channel": "stable",
+ "contexts": ["blessed_extension"]
+ }, {
+ // webview uses webRequestInternal API.
+ "channel": "stable",
+ "internal": true,
+ "contexts": ["webui"],
+ "matches": [
+ "chrome://chrome-signin/*",
+ "chrome://media-router/*",
+ "chrome://oobe/*"
+ ]
+ }],
+ "webViewInternal": [{
+ "internal": true,
+ "dependencies": ["permission:webview"],
+ "contexts": ["blessed_extension"]
+ }, {
+ "internal": true,
+ "channel": "dev",
+ "contexts": ["webui"],
+ "matches": [
+ "chrome://chrome-signin/*",
+ "chrome://media-router/*",
+ "chrome://oobe/*"
+ ]
+ }],
+ "webViewExperimentalInternal": [{
+ "internal": true,
+ "channel": "dev",
+ "dependencies": ["permission:webview"],
+ "contexts": ["blessed_extension"]
+ }],
+ "webViewRequest": [{
+ "dependencies": ["permission:webview"],
+ "contexts": ["blessed_extension"]
+ }, {
+ "channel": "stable",
+ "contexts": ["webui"],
+ "matches": [
+ "chrome://chrome-signin/*",
+ "chrome://media-router/*",
+ "chrome://oobe/*"
+ ]
+ }]
+}
diff --git a/chromium/extensions/common/api/_behavior_features.json b/chromium/extensions/common/api/_behavior_features.json
new file mode 100644
index 00000000000..c3177fa945c
--- /dev/null
+++ b/chromium/extensions/common/api/_behavior_features.json
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This features file defines switches used to control Extension behaviour,
+// typically whitelist configuration.
+//
+// See extensions/common/features/* to understand this file, in particular
+// feature.h, simple_feature.h, and base_feature_provider.h.
+//
+// To add a new whitelisted ID, SHA-1 it and force it to uppercase. In Bash:
+//
+// $ echo -n "aaaabbbbccccddddeeeeffffgggghhhh" | \
+// sha1sum | tr '[:lower:]' '[:upper:]'
+// 9A0417016F345C934A1A88F55CA17C05014EEEBA -
+//
+// Google employees: please update http://go/chrome-api-whitelist to map
+// hashes back to ids.
+
+{
+ "whitelisted_for_incognito": {
+ "channel": "stable",
+ "extension_types": "all",
+ // This is "external_component" for legacy reasons; it should be
+ // unnecessary given there's a whitelist.
+ "location": "external_component",
+ "whitelist": [
+ "D5736E4B5CF695CB93A2FB57E4FDC6E5AFAB6FE2", // http://crbug.com/312900
+ "D57DE394F36DC1C3220E7604C575D29C51A6C495", // http://crbug.com/319444
+ "3F65507A3B39259B38C8173C6FFA3D12DF64CCE9" // http://crbug.com/371562
+ ]
+ },
+ "do_not_sync": {
+ "channel": "stable",
+ "component_extensions_auto_granted": false,
+ "extension_types": "all",
+ "whitelist": [
+ "226CF815E39A363090A1E547D53063472B8279FA", // http://crbug.com/574889
+ "C17CD9E6868D7B9C67926E0EC612EA25C768418F", // http://crbug.com/505879
+ "A45DABDB47A31CC812E5490AB748C7D05E2D32E9", // http://crbug.com/505879
+ "BFEE2E3B80BE21A645E63E9346DFC383E7CB3BDA", // http://crbug.com/505879
+ "16CA7A47AAE4BE49B1E75A6B960C3875E945B264", // http://crbug.com/505879
+ "F155646B5D1CA545F7E1E4E20D573DFDD44C2540", // http://crbug.com/505879
+ "B11A93E7E5B541F8010245EBDE2C74647D6C14B9", // http://crbug.com/505879
+ "FA01E0B81978950F2BC5A50512FD769725F57510" // http://crbug.com/505879
+ ]
+ },
+ "zoom_without_bubble": {
+ "channel": "stable",
+ "extension_types": "all",
+ "whitelist": [
+ "CBCC42ABED43A4B58FE3810E62AFFA010EB0349F" // https://crbug.com/538252
+ ]
+ }
+}
diff --git a/chromium/extensions/common/api/_manifest_features.json b/chromium/extensions/common/api/_manifest_features.json
new file mode 100644
index 00000000000..a793f935d02
--- /dev/null
+++ b/chromium/extensions/common/api/_manifest_features.json
@@ -0,0 +1,338 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This features file defines manifest keys implemented under src/extensions.
+// See extensions/common/features/* to understand this file, in particular
+// feature.h, simple_feature.h, and base_feature_provider.h.
+
+// To add a new whitelisted ID, SHA-1 it and force it to uppercase. In Bash:
+//
+// $ echo -n "aaaabbbbccccddddeeeeffffgggghhhh" | \
+// sha1sum | tr '[:lower:]' '[:upper:]'
+// 9A0417016F345C934A1A88F55CA17C05014EEEBA -
+//
+// Google employees: please update http://go/chrome-api-whitelist to map
+// hashes back to ids.
+
+{
+ "app": {
+ "channel": "stable",
+ "extension_types": ["legacy_packaged_app", "hosted_app", "platform_app"]
+ },
+ // The default platform app CSP can only be overridden by whitelisted apps.
+ // This is a separate key from the top-level content_security_policy one since
+ // we can't combine type restrictions with whitelisted ID restrictions. If
+ // there is a need for additional whitelisted entries, the feature system
+ // should instead be extended to support OR-ing of restrictions.
+ "app.content_security_policy": {
+ "channel": "stable",
+ "extension_types": ["platform_app"],
+ "min_manifest_version": 2,
+ "whitelist": [
+ // Keep this list in sync with extensions_misc::kHangoutsExtensionIds but
+ // omit the Packaged App ids.
+ "nckgahadagoaajjgafhacjanaoiihapd", // Hangouts Production.
+ "ljclpkphhpbpinifbeabbhlfddcpfdde", // Hangouts Debug.
+ "ppleadejekpmccmnpjdimmlfljlkdfej", // Hangouts Alpha.
+ "eggnbpckecmjlblplehfpjjdhhidfdoj", // Hangouts Beta.
+
+ "lphgohfeebnhcpiohjndkgbhhkoapkjc" // Apps Debugger
+ ]
+ },
+ "app.background": {
+ "channel": "stable",
+ "extension_types": ["platform_app"],
+ "min_manifest_version": 2
+ },
+ "background": {
+ "channel": "stable",
+ "extension_types": [
+ // Platform apps specify their background page via app.background.
+ "extension", "legacy_packaged_app", "hosted_app"
+ ]
+ },
+ "background.persistent": {
+ "channel": "stable",
+ "extension_types": [
+ "extension", "legacy_packaged_app"
+ ],
+ "min_manifest_version": 2
+ },
+ "background_page": {
+ "channel": "stable",
+ "extension_types": [
+ "extension", "legacy_packaged_app", "hosted_app"
+ ],
+ "max_manifest_version": 1
+ },
+ "bluetooth": [{
+ // Note: The "bluetooth" manifest permission is used by the
+ // chrome.bluetooth, chrome.bluetoothSocket and chrome.bluetoothLowEnergy
+ // APIs.
+ "channel": "stable",
+ "extension_types": ["platform_app"],
+ "platforms": ["chromeos", "win", "mac"]
+ }, {
+ "channel": "stable",
+ "extension_types": ["platform_app"],
+ "platforms": ["linux"],
+ "whitelist": [
+ "9E287A8257E58EFB13E89C86A4B75A3AC4B058D8", // unit_tests browser_tests
+ "1C93BD3CF875F4A73C0B2A163BB8FBDA8B8B3D80", // http://crbug.com/396117
+ "A3BC37E2148AC4E99BE4B16AF9D42DD1E592BBBE", // http://crbug.com/396117
+ "E703483CEF33DEC18B4B6DD84B5C776FB9182BDB", // http://crbug.com/396117
+ "307E96539209F95A1A8740C713E6998A73657D96", // http://crbug.com/396117
+ "4F25792AF1AA7483936DE29C07806F203C7170A0", // http://crbug.com/407693
+ "BD8781D757D830FC2E85470A1B6E8A718B7EE0D9", // http://crbug.com/407693
+ "4AC2B6C63C6480D150DFDA13E4A5956EB1D0DDBB", // http://crbug.com/407693
+ "81986D4F846CEDDDB962643FA501D1780DD441BB" // http://crbug.com/407693
+ ]
+ }],
+ "content_capabilities": [{
+ "channel": "stable",
+ "extension_types": ["extension"],
+ "whitelist": [
+ "950D13BB9B4794F4CA2A68D3597E5DFAA47C88AE", // Drive
+ "0EEB39B7A9A52CAAE6A072F83320435749B184A4", // http://crbug.com/481210
+ "D4AF239830E1E038C9B60DA365B1EC9CB3BC5658", // http://crbug.com/481210
+ "4895B1DBB92D52488F8D9FFDF9CC7B95C7258C9A", // http://crbug.com/505532
+ "A3880AA78DB0004DE841CC980959D8443F3A8E40", // http://crbug.com/521615
+ "C8B53B3C2CC39CB504B19D990165684FF0CE880C" // http://crbug.com/521615
+ ]
+ }, {
+ "channel": "beta",
+ "extension_types": ["extension"],
+ "whitelist": [
+ "4895B1DBB92D52488F8D9FFDF9CC7B95C7258C9A", // https://crbug.com/570337
+ "A3880AA78DB0004DE841CC980959D8443F3A8E40", // https://crbug.com/570337
+ "C8B53B3C2CC39CB504B19D990165684FF0CE880C" // https://crbug.com/570337
+ ]
+ }, {
+ "channel": "dev",
+ "extension_types": ["extension"],
+ "whitelist": [
+ "4895B1DBB92D52488F8D9FFDF9CC7B95C7258C9A", // https://crbug.com/570337
+ "A3880AA78DB0004DE841CC980959D8443F3A8E40", // https://crbug.com/570337
+ "C8B53B3C2CC39CB504B19D990165684FF0CE880C" // https://crbug.com/570337
+ ]
+ }, {
+ "channel": "canary",
+ "extension_types": ["extension"]
+ }],
+ "content_security_policy": {
+ "channel": "stable",
+ // Platform apps have a restricted content security policy that cannot be
+ // overriden (except for a whitelist of exceptions, see the
+ // app.content_security_policy whitelist).
+ "extension_types": ["extension", "legacy_packaged_app"]
+ },
+ "copresence" : {
+ "channel": "stable",
+ "extension_types": "all"
+ },
+ "current_locale": {
+ "channel": "stable",
+ "extension_types": "all"
+ },
+ "default_locale": {
+ "channel": "stable",
+ "extension_types": "all"
+ },
+ "description": {
+ "channel": "stable",
+ "extension_types": "all"
+ },
+ "externally_connectable": {
+ "channel": "stable",
+ "extension_types": [
+ "extension", "hosted_app", "legacy_packaged_app", "platform_app"
+ ]
+ },
+ "file_handlers": [
+ {
+ "channel": "stable",
+ "extension_types": ["platform_app"]
+ }, {
+ "channel": "stable",
+ "extension_types": [ "extension"],
+ "whitelist": [
+ "2FC374607C2DF285634B67C64A2E356C607091C3", // Quickoffice
+ "3727DD3E564B6055387425027AD74C58784ACC15", // Quickoffice internal
+ "12E618C3C6E97495AAECF2AC12DEB082353241C6" // QO component extension
+ ]
+ }
+ ],
+ "icons": {
+ "channel": "stable",
+ "extension_types": "all"
+ },
+ "incognito": [
+ {
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app"]
+ },
+ {
+ "channel": "stable",
+ "extension_types": ["platform_app"],
+ "location": "component"
+ }
+ ],
+ "key": {
+ "channel": "stable",
+ "extension_types": "all"
+ },
+ "kiosk": {
+ "channel": "dev",
+ "extension_types": ["platform_app"]
+ },
+ "kiosk.required_platform_version": {
+ "channel": "dev",
+ "extension_types": ["platform_app"]
+ },
+ "kiosk_enabled": {
+ "channel": "stable",
+ "extension_types": [
+ "platform_app"
+ ]
+ },
+ "kiosk_only": {
+ "channel": "stable",
+ "extension_types": [
+ "platform_app"
+ ],
+ "platforms": ["chromeos"]
+ },
+ "kiosk_secondary_apps": {
+ "channel": "stable",
+ "extension_types": [
+ "platform_app"
+ ]
+ },
+ "launcher_page": {
+ "channel": "stable",
+ "min_manifest_version": 2,
+ "extension_types": ["platform_app"],
+ "whitelist": [
+ "07BD6A765FFC289FF755D7CAB2893A40EC337FEC", // http://crbug.com/404000
+ "896B85CC7E913E11C34892C1425A093C0701D386", // http://crbug.com/404000
+ "11A01C82EF355E674E4F9728A801F5C3CB40D83F", // http://crbug.com/404000
+ "F410C88469990EE7947450311D24B8AF2ADB2595" // http://crbug.com/404000
+ ]
+ },
+ "manifest_version": {
+ "channel": "stable",
+ "extension_types": "all"
+ },
+ "mime_types": {
+ "channel": "stable",
+ "extension_types": [ "extension", "legacy_packaged_app", "platform_app" ],
+ "whitelist": [
+ "oickdpebdnfbgkcaoklfcdhjniefkcji", // browser_tests
+ "gbkeegbaiigmenfmjfclcdgdpimamgkj", // QuickOffice
+ "ionpfmkccalenbmnddpbmocokhaknphg", // QuickOffice Dev
+ "bpmcpldpdmajfigpchkicefoigmkfalc", // Quickoffice component extension
+ "ehibbfinohgbchlgdbfpikodjaojhccn", // Editor
+ "mhjfbmdgcfjbbpaeojofohoefgiehjai" // PDF
+ ]
+ },
+ "mime_types_handler": {
+ "channel": "stable",
+ "extension_types": [ "extension", "legacy_packaged_app", "platform_app" ],
+ "whitelist": [
+ "oickdpebdnfbgkcaoklfcdhjniefkcji", // browser_tests
+ "mhjfbmdgcfjbbpaeojofohoefgiehjai" // PDF
+ ]
+ },
+ "nacl_modules": {
+ "channel": "stable",
+ "extension_types": [
+ "extension", "legacy_packaged_app", "hosted_app", "platform_app"
+ ]
+ },
+ "name": {
+ "channel": "stable",
+ "extension_types": "all"
+ },
+ "oauth2": {
+ "channel": "stable",
+ "extension_types": [
+ "extension", "legacy_packaged_app", "platform_app"
+ ]
+ },
+ "oauth2.auto_approve": {
+ "channel": "stable",
+ "extension_types": [
+ "extension", "platform_app"
+ ],
+ "whitelist": [
+ "mdbihdcgjmagbcapkhhkjbbdlkflmbfo", // unit_tests
+ "pafkbggdmjlpgkdkcbjmhmfcdpncadgh", // Google Now
+ "nmmhkkegccagdldgiimedpiccmgmieda", // In-app payments support app.
+ "4B1D0E19C6C43C008C44A8278C8B5BFE15ABEB3C",
+ "F7FA7ABC1ECB89BA8EE6656847EFABBF43BB9BCA",
+
+ "07BD6A765FFC289FF755D7CAB2893A40EC337FEC", // http://crbug.com/430730
+ "896B85CC7E913E11C34892C1425A093C0701D386", // http://crbug.com/430730
+ "11A01C82EF355E674E4F9728A801F5C3CB40D83F", // http://crbug.com/430730
+ "F410C88469990EE7947450311D24B8AF2ADB2595", // http://crbug.com/430730
+
+ // TODO(joaodasilva): remove these two once we have the new policy to
+ // grant auto approval by ID. http://crbug.com/399392
+ "A8208CCC87F8261AFAEB6B85D5E8D47372DDEA6B",
+ "A4577D8C2AF4CF26F40CBCA83FFA4251D6F6C8F8",
+ "EFCF5358672FEE04789FD2EC3638A67ADEDB6C8C" // http://crbug.com/514696
+ ]
+ },
+ "offline_enabled": {
+ "channel": "stable",
+ "extension_types": [
+ "extension", "legacy_packaged_app", "hosted_app", "platform_app"
+ ]
+ },
+ "options_ui": {
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app"]
+ },
+ "sandbox": {
+ "channel": "stable",
+ "extension_types": [
+ "extension", "platform_app", "legacy_packaged_app"
+ ],
+ "min_manifest_version": 2
+ },
+ "sockets": {
+ "channel": "stable",
+ "extension_types": ["platform_app"]
+ },
+ "usb_printers": {
+ "channel": "stable",
+ "extension_types": ["platform_app"]
+ },
+ "version": {
+ "channel": "stable",
+ "extension_types": "all"
+ },
+ "version_name": {
+ "channel": "stable",
+ "extension_types": "all"
+ },
+ "web_accessible_resources": [
+ {
+ "channel": "stable",
+ "extension_types": [
+ "extension", "legacy_packaged_app", "hosted_app"
+ ]
+ },
+ {
+ "channel": "stable",
+ "extension_types": ["platform_app"],
+ "location": "component"
+ }
+ ],
+ "webview": {
+ "channel": "stable",
+ "extension_types": ["platform_app"],
+ "min_manifest_version": 2
+ }
+}
diff --git a/chromium/extensions/common/api/_permission_features.json b/chromium/extensions/common/api/_permission_features.json
new file mode 100644
index 00000000000..2f9d159e981
--- /dev/null
+++ b/chromium/extensions/common/api/_permission_features.json
@@ -0,0 +1,495 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This features file defines permissions for extension APIs implemented
+// under src/extensions.
+
+// See extensions/common/features/* to understand this file, in particular
+// feature.h, simple_feature.h, and base_feature_provider.h.
+
+// To add a new whitelisted ID, SHA-1 it and force it to uppercase. In Bash:
+//
+// $ echo -n "aaaabbbbccccddddeeeeffffgggghhhh" | \
+// sha1sum | tr '[:lower:]' '[:upper:]'
+// 9A0417016F345C934A1A88F55CA17C05014EEEBA -
+//
+// Google employees: please update http://go/chrome-api-whitelist to map
+// hashes back to ids.
+
+// If you add a new platform_app permission please update the "stubs_app" test:
+// chrome/test/data/extensions/api_test/stubs_app/manifest.json
+
+{
+ "alarms": {
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app", "platform_app"],
+ "min_manifest_version": 2
+ },
+ "app.window.alwaysOnTop": {
+ "channel": "stable",
+ "extension_types": ["platform_app"]
+ },
+ "app.window.fullscreen": {
+ "channel": "stable",
+ "extension_types": ["platform_app"]
+ },
+ "app.window.fullscreen.overrideEsc": {
+ "channel": "stable",
+ "extension_types": ["platform_app"]
+ },
+ "app.window.alpha": [
+ {
+ "channel": "dev",
+ "extension_types": ["platform_app"]
+ },
+ {
+ "channel": "stable",
+ "extension_types": ["platform_app"],
+ "whitelist": [
+ "0F42756099D914A026DADFA182871C015735DD95", // http://crbug.com/323773
+ "2D22CDB6583FD0A13758AEBE8B15E45208B4E9A7",
+ "E7E2461CE072DF036CF9592740196159E2D7C089", // http://crbug.com/356200
+ "A74A4D44C7CFCD8844830E6140C8D763E12DD8F3",
+ "312745D9BF916161191143F6490085EEA0434997",
+ "53041A2FA309EECED01FFC751E7399186E860B2C",
+ "A07A5B743CD82A1C2579DB77D353C98A23201EEF", // http://crbug.com/413748
+ "F16F23C83C5F6DAD9B65A120448B34056DD80691",
+ "0F585FB1D0FDFBEBCE1FEB5E9DFFB6DA476B8C9B"
+ ]
+ },
+ {
+ "channel": "stable",
+ "extension_types": ["extension"],
+ "whitelist": [
+ "06BE211D5F014BAB34BC22D9DDA09C63A81D828E", // http://crbug.com/425539
+ "F94EE6AB36D6C6588670B2B01EB65212D9C64E33",
+ "B9EF10DDFEA11EF77873CC5009809E5037FC4C7A" // http://crbug.com/435380
+ ]
+ }
+ ],
+ "app.window.shape": {
+ "channel": "stable",
+ "extension_types": ["platform_app"]
+ },
+ "app.window.ime": [
+ {
+ "channel": "stable",
+ "extension_types": ["extension"],
+ "platforms": ["chromeos"],
+ "whitelist": [
+ "06BE211D5F014BAB34BC22D9DDA09C63A81D828E",
+ "F94EE6AB36D6C6588670B2B01EB65212D9C64E33",
+ "B9EF10DDFEA11EF77873CC5009809E5037FC4C7A" // http://crbug.com/435380
+ ]
+ }
+ ],
+ "appview": {
+ "channel": "stable",
+ "extension_types": ["platform_app"]
+ },
+ "audio": [
+ {
+ "channel": "dev",
+ "extension_types": ["platform_app"]
+ },
+ {
+ "channel": "stable",
+ "extension_types": ["platform_app"],
+ "whitelist": [
+ "8C3741E3AF0B93B6E8E0DDD499BB0B74839EA578",
+ "E703483CEF33DEC18B4B6DD84B5C776FB9182BDB",
+ "A3BC37E2148AC4E99BE4B16AF9D42DD1E592BBBE", // http://crbug.com/335729
+ "1C93BD3CF875F4A73C0B2A163BB8FBDA8B8B3D80", // http://crbug.com/335729
+ "307E96539209F95A1A8740C713E6998A73657D96", // http://crbug.com/335729
+ "4F25792AF1AA7483936DE29C07806F203C7170A0", // http://crbug.com/407693
+ "BD8781D757D830FC2E85470A1B6E8A718B7EE0D9", // http://crbug.com/407693
+ "4AC2B6C63C6480D150DFDA13E4A5956EB1D0DDBB", // http://crbug.com/407693
+ "81986D4F846CEDDDB962643FA501D1780DD441BB" // http://crbug.com/407693
+ ]
+ }
+ ],
+ "audioCapture": [
+ {
+ "channel": "stable",
+ "extension_types": ["platform_app"]
+ },
+ {
+ "channel": "stable",
+ "extension_types": ["extension"],
+ "whitelist": [
+ // http://crbug.com/292856
+ "3F50C3A83839D9C76334BCE81CDEC06174F266AF",
+ "39BE69F11F68E4EED080DA3DC2394F7885B7AFF9",
+ "FF78670081967CE21DB86A04AD94A0498F01E20A", // http://crbug.com/409192
+ // Hotword component extension
+ "62CCAAD339E6451BBF97C4BBDF758E934A05AD0B",
+ "0C0426C12F94156F330FFAF346A976BA8878DE78", // http://crbug.com/496954
+ "AC4538682FCECD28587C7A0F80849F78F4872BC2", // http://crbug.com/496954
+ "CCA4D85A67ADD65DA6C02E49EE3C080C54A8211C", // http://crbug.com/496954
+ "05EBA3051DFCA6AF17070AEE5FE8C66322FF4738", // http://crbug.com/431978
+ "11B478CEC461C766A2DC1E5BEEB7970AE06DC9C2", // http://crbug.com/458218
+ "0EFB879311E9EFBB7C45251F89EC655711B1F6ED", // http://crbug.com/458218
+ "9193D3A51E2FE33B496CDA53EA330423166E7F02", // http://crbug.com/458218
+ "F9119B8B18C7C82B51E7BC6FF816B694F2EC3E89" // http://crbug.com/458218
+ ]
+ }
+ ],
+ "bluetoothPrivate": {
+ "channel": "stable",
+ "extension_types": ["platform_app"],
+ "platforms": ["chromeos", "win", "mac", "linux"],
+ "whitelist": [
+ "1C93BD3CF875F4A73C0B2A163BB8FBDA8B8B3D80", // http://crbug.com/387169
+ "A3BC37E2148AC4E99BE4B16AF9D42DD1E592BBBE", // http://crbug.com/387169
+ "E703483CEF33DEC18B4B6DD84B5C776FB9182BDB", // http://crbug.com/387169
+ "307E96539209F95A1A8740C713E6998A73657D96", // http://crbug.com/387169
+ "4F25792AF1AA7483936DE29C07806F203C7170A0", // http://crbug.com/407693
+ "BD8781D757D830FC2E85470A1B6E8A718B7EE0D9", // http://crbug.com/407693
+ "4AC2B6C63C6480D150DFDA13E4A5956EB1D0DDBB", // http://crbug.com/407693
+ "81986D4F846CEDDDB962643FA501D1780DD441BB", // http://crbug.com/407693
+ "89715614FAA2B4C2853802D70261D2A9D0756FC8", // http://crbug.com/455986
+ "61FF4757F9420B62B19BA5C96084649339DB31F5", // http://crbug.com/587613
+ "F3013F58BED982D1BC75943792FF877E5D458672" // http://crbug.com/587613
+ ]
+ },
+ "cast": {
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app", "platform_app"],
+ "whitelist": [
+ "9448CAB302F268FB4917D06F70703893DCDA26C4", // Cast Test Extension
+ "63ED55E43214C211F82122ED56407FF1A807F2A3", // Dev
+ "FA01E0B81978950F2BC5A50512FD769725F57510", // Beta
+ "B11A93E7E5B541F8010245EBDE2C74647D6C14B9", // Canary
+ "F155646B5D1CA545F7E1E4E20D573DFDD44C2540", // Google Cast Beta
+ "16CA7A47AAE4BE49B1E75A6B960C3875E945B264", // Google Cast Stable
+ "C17CD9E6868D7B9C67926E0EC612EA25C768418F", // http://crbug.com/457908
+ "226CF815E39A363090A1E547D53063472B8279FA" // http://crbug.com/574889
+ ]
+ },
+ "declarativeWebRequest": {
+ "channel": "beta",
+ "extension_types": ["extension", "legacy_packaged_app"]
+ },
+ "diagnostics": [
+ {
+ "channel": "dev",
+ "extension_types": ["platform_app"]
+ },
+ {
+ "channel": "stable",
+ "extension_types": ["platform_app"],
+ "whitelist": [
+ "7AE714FFD394E073F0294CFA134C9F91DB5FBAA4", // CCD Development
+ "C7DA3A55C2355F994D3FDDAD120B426A0DF63843", // CCD Testing
+ "75E3CFFFC530582C583E4690EF97C70B9C8423B7" // CCD Release
+ ]
+ }
+ ],
+ "displaySource": {
+ "channel": "dev",
+ "extension_types": ["extension", "platform_app"]
+ },
+ "dns": [
+ {
+ "channel": "dev",
+ "extension_types": ["extension", "platform_app"]
+ },
+ {
+ "channel": "stable",
+ "extension_types": ["extension", "platform_app"],
+ "whitelist": [
+ "7AE714FFD394E073F0294CFA134C9F91DB5FBAA4", // CCD Development
+ "C7DA3A55C2355F994D3FDDAD120B426A0DF63843", // CCD Testing
+ "75E3CFFFC530582C583E4690EF97C70B9C8423B7" // CCD Release
+ ]
+ }
+ ],
+ "documentScan": {
+ "channel": "stable",
+ "extension_types": ["extension", "platform_app"],
+ "platforms": ["chromeos"]
+ },
+ "extensionview": {
+ "channel": "stable",
+ "extension_types": ["platform_app"],
+ "whitelist": [
+ // Used in browser tests: http://crbug.com/515284
+ "BD2EB5085B5324203BCCC3DF3CF102B8AB850402"
+ ]
+ },
+ "externally_connectable.all_urls": {
+ "channel": "stable",
+ "extension_types": [
+ "extension", "hosted_app", "legacy_packaged_app", "platform_app"
+ ],
+ "whitelist": [
+ "54ECAB4579BDE8FDAF9B29ED335F9946EE504A52", // Used in unit tests
+ "E24F1786D842E91E74C27929B0B3715A4689A473", // http://crbug.com/417494
+ "A28C9619C4C41306FA5236FB4D94DA812F504DE8" // http://crbug.com/429886
+ ]
+ },
+ "hid": [
+ {
+ "channel": "stable",
+ "extension_types": ["platform_app"]
+ },
+ {
+ "channel": "stable",
+ "extension_types": ["extension"],
+ "whitelist": [
+ "496B6890097EB6E19809ADEADD095A8721FBB2E0", // FIDO U2F APIs
+ "AD8ED80B705E1818AAD4684F9FF62B43D6D79620", // FIDO U2F APIs (dev)
+ "E24F1786D842E91E74C27929B0B3715A4689A473", // CryptoToken
+ "A28C9619C4C41306FA5236FB4D94DA812F504DE8" // CryptoToken (dev)
+ ]
+ }
+ ],
+ "idle": {
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app", "platform_app"]
+ },
+ "networking.config": {
+ "channel": "stable",
+ "platforms": ["chromeos"],
+ "extension_types": ["extension", "platform_app"]
+ },
+ "networkingPrivate": {
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app", "platform_app"],
+ "platforms": ["chromeos", "mac", "win", "linux"],
+ "whitelist": [
+ "0DE0F05680A4A056BCEC864ED8DDA84296F82B40", // http://crbug.com/434651
+ "1C93BD3CF875F4A73C0B2A163BB8FBDA8B8B3D80", // http://crbug.com/293683
+ "A3BC37E2148AC4E99BE4B16AF9D42DD1E592BBBE", // http://crbug.com/293683
+ "8C3741E3AF0B93B6E8E0DDD499BB0B74839EA578", // http://crbug.com/234235
+ "E703483CEF33DEC18B4B6DD84B5C776FB9182BDB", // http://crbug.com/234235
+ "307E96539209F95A1A8740C713E6998A73657D96", // http://crbug.com/329690
+ "11B478CEC461C766A2DC1E5BEEB7970AE06DC9C2", // http://crbug.com/380890
+ "0EFB879311E9EFBB7C45251F89EC655711B1F6ED", // http://crbug.com/380890
+ "9193D3A51E2FE33B496CDA53EA330423166E7F02", // http://crbug.com/380890
+ "F9119B8B18C7C82B51E7BC6FF816B694F2EC3E89", // http://crbug.com/380890
+ "63ED55E43214C211F82122ED56407FF1A807F2A3", // Dev
+ "FA01E0B81978950F2BC5A50512FD769725F57510", // Beta
+ "B11A93E7E5B541F8010245EBDE2C74647D6C14B9", // Canary
+ "F155646B5D1CA545F7E1E4E20D573DFDD44C2540", // Google Cast Beta
+ "16CA7A47AAE4BE49B1E75A6B960C3875E945B264", // Google Cast Stable
+ "226CF815E39A363090A1E547D53063472B8279FA", // http://crbug.com/588179
+ "7AE714FFD394E073F0294CFA134C9F91DB5FBAA4", // CCD Development
+ "C7DA3A55C2355F994D3FDDAD120B426A0DF63843", // CCD Testing
+ "75E3CFFFC530582C583E4690EF97C70B9C8423B7", // CCD Release
+ "4F25792AF1AA7483936DE29C07806F203C7170A0", // http://crbug.com/407693
+ "BD8781D757D830FC2E85470A1B6E8A718B7EE0D9", // http://crbug.com/407693
+ "4AC2B6C63C6480D150DFDA13E4A5956EB1D0DDBB", // http://crbug.com/407693
+ "81986D4F846CEDDDB962643FA501D1780DD441BB" // http://crbug.com/407693
+ ]
+ },
+ "power": {
+ "channel": "stable",
+ "extension_types": [ "extension", "legacy_packaged_app", "platform_app" ]
+ },
+ "printerProvider": {
+ "channel": "stable",
+ "extension_types": ["extension", "platform_app" ]
+ },
+ // Note: runtime is not actually a permission, but some systems check these
+ // values to verify restrictions.
+ "runtime": {
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app", "platform_app"]
+ },
+ "serial": {
+ "channel": "stable",
+ "extension_types": ["platform_app"]
+ },
+ "socket": [
+ {
+ "channel": "stable",
+ "extension_types": ["platform_app"]
+ },
+ {
+ "channel": "stable",
+ "extension_types": ["extension"],
+ "whitelist": [
+ // The connectivity diagnostic utility is a component extension that is
+ // used to try to provide suggestions on how to fix connection issues.
+ // It should be the only non-app allowed to use the socket API.
+ "32A1BA997F8AB8DE29ED1BA94AAF00CF2A3FEFA7"
+ ]
+ }
+ ],
+ "storage": {
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app", "platform_app"],
+ "min_manifest_version": 2
+ },
+ "system.cpu": [
+ {
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app", "platform_app"]
+ },
+ {
+ "channel": "stable",
+ "extension_types": ["hosted_app"],
+ "whitelist": ["B44D08FD98F1523ED5837D78D0A606EA9D6206E5"] // Web Store
+ }
+ ],
+ "system.memory": [
+ {
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app", "platform_app"]
+ },
+ {
+ "channel": "stable",
+ "extension_types": ["hosted_app"],
+ "whitelist": ["B44D08FD98F1523ED5837D78D0A606EA9D6206E5"] // Web Store
+ }
+ ],
+ "system.network": [
+ {
+ "channel": "stable",
+ "extension_types": ["platform_app"]
+ },
+ {
+ "channel": "stable",
+ "extension_types": ["hosted_app"],
+ "whitelist": ["B44D08FD98F1523ED5837D78D0A606EA9D6206E5"] // Web Store
+ }
+ ],
+ "system.storage": [
+ {
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app", "platform_app"]
+ },
+ {
+ "channel": "stable",
+ "extension_types": ["hosted_app"],
+ "whitelist": ["B44D08FD98F1523ED5837D78D0A606EA9D6206E5"] // Web Store
+ }
+ ],
+ "system.display": [
+ {
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app", "platform_app"]
+ },
+ {
+ "channel": "stable",
+ "extension_types": ["hosted_app"],
+ "whitelist": ["B44D08FD98F1523ED5837D78D0A606EA9D6206E5"] // Web Store
+ }
+ ],
+ "u2fDevices": [
+ {
+ "channel": "stable",
+ "extension_types": ["extension", "platform_app"],
+ "whitelist": [
+ "496B6890097EB6E19809ADEADD095A8721FBB2E0", // FIDO U2F APIs
+ "AD8ED80B705E1818AAD4684F9FF62B43D6D79620", // FIDO U2F APIs (dev)
+ "E24F1786D842E91E74C27929B0B3715A4689A473", // CryptoToken
+ "A28C9619C4C41306FA5236FB4D94DA812F504DE8", // CryptoToken (dev)
+ "6F9E349A0561C78A0D3F41496FE521C5151C7F71", // Security Key
+ "C06709A259378015404ED20F75C7D08547E0F10B" // Security Key (dev)
+ ]
+ }
+ ],
+ "unlimitedStorage": {
+ "channel": "stable",
+ "extension_types": [
+ "extension", "legacy_packaged_app", "hosted_app", "platform_app"
+ ]
+ },
+ "usb": [
+ {
+ "channel": "stable",
+ "extension_types": ["platform_app"]
+ },
+ {
+ "channel": "stable",
+ "extension_types": ["extension"],
+ "whitelist": [
+ "496B6890097EB6E19809ADEADD095A8721FBB2E0", // FIDO U2F APIs
+ "AD8ED80B705E1818AAD4684F9FF62B43D6D79620", // FIDO U2F APIs (dev)
+ "E24F1786D842E91E74C27929B0B3715A4689A473", // CryptoToken
+ "A28C9619C4C41306FA5236FB4D94DA812F504DE8" // CryptoToken (dev)
+ ]
+ }
+ ],
+ "usbDevices": [
+ {
+ "channel": "stable",
+ "extension_types": ["platform_app"]
+ },
+ {
+ "channel": "stable",
+ "extension_types": ["extension"],
+ "whitelist": [
+ "496B6890097EB6E19809ADEADD095A8721FBB2E0", // FIDO U2F APIs
+ "AD8ED80B705E1818AAD4684F9FF62B43D6D79620", // FIDO U2F APIs (dev)
+ "E24F1786D842E91E74C27929B0B3715A4689A473", // CryptoToken
+ "A28C9619C4C41306FA5236FB4D94DA812F504DE8" // CryptoToken (dev)
+ ]
+ }
+ ],
+ "videoCapture": [
+ {
+ "channel": "stable",
+ "extension_types": ["platform_app"]
+ },
+ {
+ "channel": "stable",
+ "extension_types": ["extension"],
+ "whitelist": [
+ // http://crbug.com/292856
+ "A434B90223C3C52F2B69DB494736B63C612C774D"
+ ]
+ }
+ ],
+ "virtualKeyboardPrivate": {
+ "channel": "stable",
+ "extension_types": ["extension", "platform_app"],
+ "platforms": ["chromeos"],
+ "whitelist": [
+ "3F50C3A83839D9C76334BCE81CDEC06174F266AF", // System-level virtual kbd
+ "06BE211D5F014BAB34BC22D9DDA09C63A81D828E", // Official XKB virtual kbd
+ "CFBF7EE448FA48960FFDA7CEB30F7A21B26AA981", // Official m17n virtual kbd
+ "F94EE6AB36D6C6588670B2B01EB65212D9C64E33", // Public XKB virtual kbd
+ "3E03D9B67FDD31B2438D1CF5070573415DCB3CBA", // Public m17n virtual kbd
+ "E703483CEF33DEC18B4B6DD84B5C776FB9182BDB", // Stable external hotrod app
+ "A3BC37E2148AC4E99BE4B16AF9D42DD1E592BBBE", // Beta external hotrod app
+ "1C93BD3CF875F4A73C0B2A163BB8FBDA8B8B3D80", // Alpha external hotrod app
+ "307E96539209F95A1A8740C713E6998A73657D96", // Dev external hotrod app
+ "4F25792AF1AA7483936DE29C07806F203C7170A0", // Stable internal hotrod app
+ "BD8781D757D830FC2E85470A1B6E8A718B7EE0D9", // Beta internal hotrod app
+ "4AC2B6C63C6480D150DFDA13E4A5956EB1D0DDBB", // Alpha external hotrod app
+ "81986D4F846CEDDDB962643FA501D1780DD441BB" // Dev external hotrod app
+ ]
+ },
+ "vpnProvider": {
+ "channel": "stable",
+ "extension_types": ["extension", "platform_app"],
+ "platforms": ["chromeos"]
+ },
+ "webview": [{
+ "channel": "stable",
+ "extension_types": ["platform_app"]
+ }, {
+ "channel": "stable",
+ "extension_types": ["extension"],
+ "whitelist": [
+ "16CA7A47AAE4BE49B1E75A6B960C3875E945B264", // http://crbug.com/500075
+ "F155646B5D1CA545F7E1E4E20D573DFDD44C2540", // http://crbug.com/500075
+ "FA01E0B81978950F2BC5A50512FD769725F57510", // http://crbug.com/500075
+ "B11A93E7E5B541F8010245EBDE2C74647D6C14B9", // http://crbug.com/500075
+ "63ED55E43214C211F82122ED56407FF1A807F2A3", // http://crbug.com/500075
+ "226CF815E39A363090A1E547D53063472B8279FA" // http://crbug.com/500075
+ ]
+ }],
+ "webRequest": {
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app"]
+ },
+ "webRequestBlocking": {
+ "channel": "stable",
+ "extension_types": ["extension", "legacy_packaged_app"]
+ }
+}
diff --git a/chromium/extensions/common/api/alarms.idl b/chromium/extensions/common/api/alarms.idl
new file mode 100644
index 00000000000..500d2ed9518
--- /dev/null
+++ b/chromium/extensions/common/api/alarms.idl
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.alarms</code> API to schedule code to run
+// periodically or at a specified time in the future.
+namespace alarms {
+ dictionary Alarm {
+ // Name of this alarm.
+ DOMString name;
+
+ // Time at which this alarm was scheduled to fire, in milliseconds past the
+ // epoch (e.g. <code>Date.now() + n</code>). For performance reasons, the
+ // alarm may have been delayed an arbitrary amount beyond this.
+ double scheduledTime;
+
+ // If not null, the alarm is a repeating alarm and will fire again in
+ // <var>periodInMinutes</var> minutes.
+ double? periodInMinutes;
+ };
+
+ // TODO(mpcomplete): rename to CreateInfo when http://crbug.com/123073 is
+ // fixed.
+ dictionary AlarmCreateInfo {
+ // Time at which the alarm should fire, in milliseconds past the epoch
+ // (e.g. <code>Date.now() + n</code>).
+ double? when;
+
+ // Length of time in minutes after which the <code>onAlarm</code> event
+ // should fire.
+ //
+ // <!-- TODO: need minimum=0 -->
+ double? delayInMinutes;
+
+ // If set, the onAlarm event should fire every <var>periodInMinutes</var>
+ // minutes after the initial event specified by <var>when</var> or
+ // <var>delayInMinutes</var>. If not set, the alarm will only fire once.
+ //
+ // <!-- TODO: need minimum=0 -->
+ double? periodInMinutes;
+ };
+
+ callback AlarmCallback = void (optional Alarm alarm);
+ callback AlarmListCallback = void (Alarm[] alarms);
+ callback ClearCallback = void (boolean wasCleared);
+
+ interface Functions {
+ // Creates an alarm. Near the time(s) specified by <var>alarmInfo</var>,
+ // the <code>onAlarm</code> event is fired. If there is another alarm with
+ // the same name (or no name if none is specified), it will be cancelled and
+ // replaced by this alarm.
+ //
+ // In order to reduce the load on the user's machine, Chrome limits alarms
+ // to at most once every 1 minute but may delay them an arbitrary amount
+ // more. That is, setting <code>delayInMinutes</code> or
+ // <code>periodInMinutes</code> to less than <code>1</code> will not be
+ // honored and will cause a warning. <code>when</code> can be set to less
+ // than 1 minute after "now" without warning but won't actually cause the
+ // alarm to fire for at least 1 minute.
+ //
+ // To help you debug your app or extension, when you've loaded it unpacked,
+ // there's no limit to how often the alarm can fire.
+ //
+ // |name|: Optional name to identify this alarm. Defaults to the empty
+ // string.
+ //
+ // |alarmInfo|: Describes when the alarm should fire. The initial time must
+ // be specified by either <var>when</var> or <var>delayInMinutes</var> (but
+ // not both). If <var>periodInMinutes</var> is set, the alarm will repeat
+ // every <var>periodInMinutes</var> minutes after the initial event. If
+ // neither <var>when</var> or <var>delayInMinutes</var> is set for a
+ // repeating alarm, <var>periodInMinutes</var> is used as the default for
+ // <var>delayInMinutes</var>.
+ static void create(optional DOMString name, AlarmCreateInfo alarmInfo);
+
+ // Retrieves details about the specified alarm.
+ // |name|: The name of the alarm to get. Defaults to the empty string.
+ static void get(optional DOMString name, AlarmCallback callback);
+
+ // Gets an array of all the alarms.
+ static void getAll(AlarmListCallback callback);
+
+ // Clears the alarm with the given name.
+ // |name|: The name of the alarm to clear. Defaults to the empty string.
+ static void clear(optional DOMString name, optional ClearCallback callback);
+
+ // Clears all alarms.
+ static void clearAll(optional ClearCallback callback);
+ };
+
+ interface Events {
+ // Fired when an alarm has elapsed. Useful for event pages.
+ // |alarm|: The alarm that has elapsed.
+ static void onAlarm(Alarm alarm);
+ };
+};
diff --git a/chromium/extensions/common/api/app_current_window_internal.idl b/chromium/extensions/common/api/app_current_window_internal.idl
new file mode 100644
index 00000000000..e6dc17d198a
--- /dev/null
+++ b/chromium/extensions/common/api/app_current_window_internal.idl
@@ -0,0 +1,67 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This is used by the app window API internally to pass through messages to
+// the shell window.
+namespace app.currentWindowInternal {
+
+ // Null or undefined indicates that a value should not change.
+ dictionary Bounds {
+ long? left;
+ long? top;
+ long? width;
+ long? height;
+ };
+
+ // Null or undefined indicates that a value should not change. A value of 0
+ // will clear the constraints.
+ dictionary SizeConstraints {
+ long? minWidth;
+ long? minHeight;
+ long? maxWidth;
+ long? maxHeight;
+ };
+
+ dictionary RegionRect {
+ long left;
+ long top;
+ long width;
+ long height;
+ };
+
+ dictionary Region {
+ RegionRect[]? rects;
+ };
+
+ interface Functions {
+ static void focus();
+ static void fullscreen();
+ static void minimize();
+ static void maximize();
+ static void restore();
+ static void drawAttention();
+ static void clearAttention();
+ static void show(optional boolean focused);
+ static void hide();
+ static void setBounds(DOMString boundsType, Bounds bounds);
+ static void setSizeConstraints(DOMString boundsType,
+ SizeConstraints constraints);
+ static void setIcon(DOMString icon_url);
+ static void setShape(Region region);
+ static void setAlwaysOnTop(boolean always_on_top);
+ static void setVisibleOnAllWorkspaces(boolean always_visible);
+ };
+
+ interface Events {
+ static void onClosed();
+ static void onBoundsChanged();
+ static void onFullscreened();
+ static void onMinimized();
+ static void onMaximized();
+ static void onRestored();
+ static void onAlphaEnabledChanged();
+ // Only sent in tests.
+ static void onWindowShownForTests();
+ };
+};
diff --git a/chromium/extensions/common/api/app_runtime.idl b/chromium/extensions/common/api/app_runtime.idl
new file mode 100644
index 00000000000..daf953cb20c
--- /dev/null
+++ b/chromium/extensions/common/api/app_runtime.idl
@@ -0,0 +1,111 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.app.runtime</code> API to manage the app lifecycle.
+// The app runtime manages app installation, controls the event page, and can
+// shut down the app at anytime.
+namespace app.runtime {
+
+ [inline_doc] dictionary LaunchItem {
+ // Entry for the item.
+ [instanceOf=Entry] object entry;
+
+ // The MIME type of the file.
+ DOMString? type;
+ };
+
+ // Enumeration of app launch sources.
+ enum LaunchSource {
+ app_launcher,
+ new_tab_page,
+ reload,
+ restart,
+ load_and_launch,
+ command_line,
+ file_handler,
+ url_handler,
+ system_tray,
+ about_page,
+ keyboard,
+ extensions_page,
+ management_api,
+ ephemeral_app,
+ background,
+ kiosk,
+ chrome_internal,
+ test
+ };
+
+ // Optional data for the launch. Either <code>items</code>, or
+ // the pair (<code>url, referrerUrl</code>) can be present for any given
+ // launch.
+ [inline_doc] dictionary LaunchData {
+ // The ID of the file or URL handler that the app is being invoked with.
+ // Handler IDs are the top-level keys in the <code>file_handlers</code>
+ // and/or <code>url_handlers</code> dictionaries in the manifest.
+ DOMString? id;
+
+ // The file entries for the <code>onLaunched</code> event triggered by a
+ // matching file handler in the <code>file_handlers</code> manifest key.
+ LaunchItem[]? items;
+
+ // The URL for the <code>onLaunched</code> event triggered by a matching
+ // URL handler in the <code>url_handlers</code> manifest key.
+ DOMString? url;
+
+ // The referrer URL for the <code>onLaunched</code> event triggered by a
+ // matching URL handler in the <code>url_handlers</code> manifest key.
+ DOMString? referrerUrl;
+
+ // Whether the app is being launched in a <a
+ // href="https://support.google.com/chromebook/answer/3134673">Chrome OS
+ // kiosk session</a>.
+ boolean? isKioskSession;
+
+ // Whether the app is being launched in a <a
+ // href="https://support.google.com/chrome/a/answer/3017014">Chrome OS
+ // public session</a>.
+ boolean? isPublicSession;
+
+ // Where the app is launched from.
+ LaunchSource? source;
+ };
+
+ // This object specifies details and operations to perform on the embedding
+ // request. The app to be embedded can make a decision on whether or not to
+ // allow the embedding and what to embed based on the embedder making the
+ // request.
+ dictionary EmbedRequest {
+ DOMString embedderId;
+
+ // Optional developer specified data that the app to be embedded can use
+ // when making an embedding decision.
+ any? data;
+
+ // Allows <code>embedderId</code> to embed this app in an &lt;appview&gt;
+ // element. The <code>url</code> specifies the content to embed.
+ [nocompile] static void allow(DOMString url);
+
+ // Prevents <code> embedderId</code> from embedding this app in an
+ // &lt;appview&gt; element.
+ [nocompile] static void deny();
+ };
+
+ interface Events {
+ // Fired when an embedding app requests to embed this app. This event is
+ // only available on dev channel with the flag --enable-app-view.
+ static void onEmbedRequested(EmbedRequest request);
+
+ // Fired when an app is launched from the launcher.
+ static void onLaunched(optional LaunchData launchData);
+
+ // Fired at Chrome startup to apps that were running when Chrome last shut
+ // down, or when apps have been requested to restart from their previous
+ // state for other reasons (e.g. when the user revokes access to an app's
+ // retained files the runtime will restart the app). In these situations if
+ // apps do not have an <code>onRestarted</code> handler they will be sent
+ // an <code>onLaunched </code> event instead.
+ static void onRestarted();
+ };
+};
diff --git a/chromium/extensions/common/api/app_view_guest_internal.json b/chromium/extensions/common/api/app_view_guest_internal.json
new file mode 100644
index 00000000000..37fe657e6f5
--- /dev/null
+++ b/chromium/extensions/common/api/app_view_guest_internal.json
@@ -0,0 +1,56 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "appViewGuestInternal",
+ "compiler_options": {
+ "implemented_in": "extensions/browser/api/guest_view/app_view/app_view_guest_internal_api.h"
+ },
+ "description": "none",
+ "functions": [
+ {
+ "name": "attachFrame",
+ "type": "function",
+ "description": "Attaches the specified url to the AppView with the provided instance ID.",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "url",
+ "nodoc": true
+ },
+ {
+ "type": "integer",
+ "name": "guestInstanceId",
+ "nodoc": true
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "denyRequest",
+ "type": "function",
+ "description": "Denies the embedding request made by the AppView with the provided instance ID.",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "guestInstanceId",
+ "nodoc": true
+ }
+ ]
+ }
+ ]
+ }
+]
+
diff --git a/chromium/extensions/common/api/app_window.idl b/chromium/extensions/common/api/app_window.idl
new file mode 100644
index 00000000000..f75978ec268
--- /dev/null
+++ b/chromium/extensions/common/api/app_window.idl
@@ -0,0 +1,485 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.app.window</code> API to create windows. Windows
+// have an optional frame with title bar and size controls. They are not
+// associated with any Chrome browser windows. See the <a
+// href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/samples/window-state">
+// Window State Sample</a> for a demonstration of these options.
+namespace app.window {
+
+ // Previously named Bounds.
+ dictionary ContentBounds {
+ long? left;
+ long? top;
+ long? width;
+ long? height;
+ };
+
+ dictionary BoundsSpecification {
+ // The X coordinate of the content or window.
+ long? left;
+
+ // The Y coordinate of the content or window.
+ long? top;
+
+ // The width of the content or window.
+ long? width;
+
+ // The height of the content or window.
+ long? height;
+
+ // The minimum width of the content or window.
+ long? minWidth;
+
+ // The minimum height of the content or window.
+ long? minHeight;
+
+ // The maximum width of the content or window.
+ long? maxWidth;
+
+ // The maximum height of the content or window.
+ long? maxHeight;
+ };
+
+ dictionary Bounds {
+ // This property can be used to read or write the current X coordinate of
+ // the content or window.
+ long left;
+
+ // This property can be used to read or write the current Y coordinate of
+ // the content or window.
+ long top;
+
+ // This property can be used to read or write the current width of the
+ // content or window.
+ long width;
+
+ // This property can be used to read or write the current height of the
+ // content or window.
+ long height;
+
+ // This property can be used to read or write the current minimum width of
+ // the content or window. A value of <code>null</code> indicates
+ // 'unspecified'.
+ long? minWidth;
+
+ // This property can be used to read or write the current minimum height of
+ // the content or window. A value of <code>null</code> indicates
+ // 'unspecified'.
+ long? minHeight;
+
+ // This property can be used to read or write the current maximum width of
+ // the content or window. A value of <code>null</code> indicates
+ // 'unspecified'.
+ long? maxWidth;
+
+ // This property can be used to read or write the current maximum height of
+ // the content or window. A value of <code>null</code> indicates
+ // 'unspecified'.
+ long? maxHeight;
+
+ // Set the left and top position of the content or window.
+ static void setPosition(long left, long top);
+
+ // Set the width and height of the content or window.
+ static void setSize(long width, long height);
+
+ // Set the minimum size constraints of the content or window. The minimum
+ // width or height can be set to <code>null</code> to remove the constraint.
+ // A value of <code>undefined</code> will leave a constraint unchanged.
+ static void setMinimumSize(long minWidth, long minHeight);
+
+ // Set the maximum size constraints of the content or window. The maximum
+ // width or height can be set to <code>null</code> to remove the constraint.
+ // A value of <code>undefined</code> will leave a constraint unchanged.
+ static void setMaximumSize(long maxWidth, long maxHeight);
+ };
+
+ dictionary FrameOptions {
+ // Frame type: <code>none</code> or <code>chrome</code> (defaults to
+ // <code>chrome</code>).
+ //
+ // For <code>none</code>, the <code>-webkit-app-region</code> CSS property
+ // can be used to apply draggability to the app's window.
+ //
+ // <code>-webkit-app-region: drag</code> can be used to mark regions
+ // draggable. <code>no-drag</code> can be used to disable this style on
+ // nested elements.
+ DOMString? type;
+ // Allows the frame color to be set. Frame coloring is only available if the
+ // frame type is <code>chrome</code>.
+ //
+ // Frame coloring is new in Chrome 36.
+ DOMString? color;
+ // Allows the frame color of the window when active to be set. Frame
+ // coloring is only available if the frame type is <code>chrome</code>.
+ //
+ // Frame coloring is only available if the frame type is
+ // <code>chrome</code>.
+ //
+ // Frame coloring is new in Chrome 36.
+ DOMString? activeColor;
+ // Allows the frame color of the window when inactive to be set differently
+ // to the active color. Frame
+ // coloring is only available if the frame type is <code>chrome</code>.
+ //
+ // <code>inactiveColor</code> must be used in conjunction with <code>
+ // color</code>.
+ //
+ // Frame coloring is new in Chrome 36.
+ DOMString? inactiveColor;
+ };
+
+ // State of a window: normal, fullscreen, maximized, minimized.
+ enum State { normal, fullscreen, maximized, minimized };
+
+ // Specifies the type of window to create.
+ enum WindowType {
+ // Default window type.
+ shell,
+ // OS managed window (Chrome OS only).
+ panel
+ };
+
+ [noinline_doc] dictionary CreateWindowOptions {
+ // Id to identify the window. This will be used to remember the size
+ // and position of the window and restore that geometry when a window
+ // with the same id is later opened.
+ // If a window with a given id is created while another window with the same
+ // id already exists, the currently opened window will be focused instead of
+ // creating a new window.
+ DOMString? id;
+
+ // Used to specify the initial position, initial size and constraints of the
+ // window's content (excluding window decorations).
+ // If an <code>id</code> is also specified and a window with a matching
+ // <code>id</code> has been shown before, the remembered bounds will be used
+ // instead.
+ //
+ // Note that the padding between the inner and outer bounds is determined by
+ // the OS. Therefore setting the same bounds property for both the
+ // <code>innerBounds</code> and <code>outerBounds</code> will result in an
+ // error.
+ //
+ // This property is new in Chrome 36.
+ BoundsSpecification? innerBounds;
+
+ // Used to specify the initial position, initial size and constraints of the
+ // window (including window decorations such as the title bar and frame).
+ // If an <code>id</code> is also specified and a window with a matching
+ // <code>id</code> has been shown before, the remembered bounds will be used
+ // instead.
+ //
+ // Note that the padding between the inner and outer bounds is determined by
+ // the OS. Therefore setting the same bounds property for both the
+ // <code>innerBounds</code> and <code>outerBounds</code> will result in an
+ // error.
+ //
+ // This property is new in Chrome 36.
+ BoundsSpecification? outerBounds;
+
+ // Default width of the window.
+ [nodoc, deprecated="Use $(ref:BoundsSpecification)."] long? defaultWidth;
+
+ // Default height of the window.
+ [nodoc, deprecated="Use $(ref:BoundsSpecification)."] long? defaultHeight;
+
+ // Default X coordinate of the window.
+ [nodoc, deprecated="Use $(ref:BoundsSpecification)."] long? defaultLeft;
+
+ // Default Y coordinate of the window.
+ [nodoc, deprecated="Use $(ref:BoundsSpecification)."] long? defaultTop;
+
+ // Width of the window.
+ [nodoc, deprecated="Use $(ref:BoundsSpecification)."] long? width;
+
+ // Height of the window.
+ [nodoc, deprecated="Use $(ref:BoundsSpecification)."] long? height;
+
+ // X coordinate of the window.
+ [nodoc, deprecated="Use $(ref:BoundsSpecification)."] long? left;
+
+ // Y coordinate of the window.
+ [nodoc, deprecated="Use $(ref:BoundsSpecification)."] long? top;
+
+ // Minimum width of the window.
+ [deprecated="Use innerBounds or outerBounds."] long? minWidth;
+
+ // Minimum height of the window.
+ [deprecated="Use innerBounds or outerBounds."] long? minHeight;
+
+ // Maximum width of the window.
+ [deprecated="Use innerBounds or outerBounds."] long? maxWidth;
+
+ // Maximum height of the window.
+ [deprecated="Use innerBounds or outerBounds."] long? maxHeight;
+
+ // Type of window to create.
+ WindowType? type;
+
+ // Creates a special ime window. This window is not focusable and can be
+ // stacked above virtual keyboard window. This is restriced to component ime
+ // extensions.
+ // Requires the <code>app.window.ime</code> API permission.
+ [nodoc] boolean? ime;
+
+ // Frame type: <code>none</code> or <code>chrome</code> (defaults to
+ // <code>chrome</code>). For <code>none</code>, the
+ // <code>-webkit-app-region</code> CSS property can be used to apply
+ // draggability to the app's window. <code>-webkit-app-region: drag</code>
+ // can be used to mark regions draggable. <code>no-drag</code> can be used
+ // to disable this style on nested elements.
+ //
+ // Use of <code>FrameOptions</code> is new in M36.
+ (DOMString or FrameOptions)? frame;
+
+ // Size and position of the content in the window (excluding the titlebar).
+ // If an id is also specified and a window with a matching id has been shown
+ // before, the remembered bounds of the window will be used instead.
+ [deprecated="Use innerBounds or outerBounds."] ContentBounds? bounds;
+
+ // Enable window background transparency.
+ // Only supported in ash. Requires the <code>app.window.alpha</code> API
+ // permission.
+ [nodoc] boolean? alphaEnabled;
+
+ // The initial state of the window, allowing it to be created already
+ // fullscreen, maximized, or minimized. Defaults to 'normal'.
+ State? state;
+
+ // If true, the window will be created in a hidden state. Call show() on
+ // the window to show it once it has been created. Defaults to false.
+ boolean? hidden;
+
+ // If true, the window will be resizable by the user. Defaults to true.
+ boolean? resizable;
+
+ // By default if you specify an id for the window, the window will only be
+ // created if another window with the same id doesn't already exist. If a
+ // window with the same id already exists that window is activated instead.
+ // If you do want to create multiple windows with the same id, you can
+ // set this property to false.
+ [deprecated="Multiple windows with the same id is no longer supported."] boolean? singleton;
+
+ // If true, the window will stay above most other windows. If there are
+ // multiple windows of this kind, the currently focused window will be in
+ // the foreground. Requires the <code>alwaysOnTopWindows</code>
+ // permission. Defaults to false.
+ //
+ // Call <code>setAlwaysOnTop()</code> on the window to change this property
+ // after creation.
+ boolean? alwaysOnTop;
+
+ // If true, the window will be focused when created. Defaults to true.
+ boolean? focused;
+
+ // If true, and supported by the platform, the window will be visible on all
+ // workspaces.
+ boolean? visibleOnAllWorkspaces;
+ };
+
+ // Called in the creating window (parent) before the load event is called in
+ // the created window (child). The parent can set fields or functions on the
+ // child usable from onload. E.g. background.js:
+ //
+ // <code>function(createdWindow) { createdWindow.contentWindow.foo =
+ // function () { }; };</code>
+ //
+ // window.js:
+ //
+ // <code>window.onload = function () { foo(); }</code>
+ callback CreateWindowCallback =
+ void ([instanceOf=AppWindow] object createdWindow);
+
+ [noinline_doc] dictionary AppWindow {
+ // Focus the window.
+ static void focus();
+
+ // Fullscreens the window.
+ //
+ // The user will be able to restore the window by pressing ESC. An
+ // application can prevent the fullscreen state to be left when ESC is
+ // pressed by requesting the <code>app.window.fullscreen.overrideEsc</code>
+ // permission and canceling the event by calling .preventDefault(), in the
+ // keydown and keyup handlers, like this:
+ //
+ // <code>window.onkeydown = window.onkeyup = function(e) { if (e.keyCode ==
+ // 27 /* ESC */) { e.preventDefault(); } };</code>
+ //
+ // Note <code>window.fullscreen()</code> will cause the entire window to
+ // become fullscreen and does not require a user gesture. The HTML5
+ // fullscreen API can also be used to enter fullscreen mode (see
+ // <a href="http://developer.chrome.com/apps/api_other.html">Web APIs</a>
+ // for more details).
+ static void fullscreen();
+
+ // Is the window fullscreen? This will be true if the window has been
+ // created fullscreen or was made fullscreen via the
+ // <code>AppWindow</code> or HTML5 fullscreen APIs.
+ static boolean isFullscreen();
+
+ // Minimize the window.
+ static void minimize();
+
+ // Is the window minimized?
+ static boolean isMinimized();
+
+ // Maximize the window.
+ static void maximize();
+
+ // Is the window maximized?
+ static boolean isMaximized();
+
+ // Restore the window, exiting a maximized, minimized, or fullscreen state.
+ static void restore();
+
+ // Move the window to the position (|left|, |top|).
+ [deprecated="Use outerBounds."] static void moveTo(long left, long top);
+
+ // Resize the window to |width|x|height| pixels in size.
+ [deprecated="Use outerBounds."] static void resizeTo(long width, long height);
+
+ // Draw attention to the window.
+ static void drawAttention();
+
+ // Clear attention to the window.
+ static void clearAttention();
+
+ // Close the window.
+ static void close();
+
+ // Show the window. Does nothing if the window is already visible.
+ // Focus the window if |focused| is set to true or omitted.
+ static void show(optional boolean focused);
+
+ // Hide the window. Does nothing if the window is already hidden.
+ static void hide();
+
+ // Get the window's inner bounds as a $(ref:ContentBounds) object.
+ [nocompile, deprecated="Use innerBounds or outerBounds."] static ContentBounds getBounds();
+
+ // Set the window's inner bounds.
+ [nocompile, deprecated="Use innerBounds or outerBounds."] static void setBounds(ContentBounds bounds);
+
+ // Set the app icon for the window (experimental).
+ // Currently this is only being implemented on Ash.
+ // TODO(stevenjb): Investigate implementing this on Windows and OSX.
+ [nodoc] static void setIcon(DOMString iconUrl);
+
+ // Is the window always on top?
+ static boolean isAlwaysOnTop();
+
+ // Accessors for testing.
+ [nodoc] boolean hasFrameColor;
+ [nodoc] long activeFrameColor;
+ [nodoc] long inactiveFrameColor;
+ [nodoc] boolean? firstShowHasHappened;
+
+ // Set whether the window should stay above most other windows. Requires the
+ // <code>alwaysOnTopWindows</code> permission.
+ static void setAlwaysOnTop(boolean alwaysOnTop);
+
+ // Can the window use alpha transparency?
+ // TODO(jackhou): Document this properly before going to stable.
+ [nodoc] static boolean alphaEnabled();
+
+ // Set whether the window is visible on all workspaces. (Only for platforms
+ // that support this).
+ static void setVisibleOnAllWorkspaces(boolean alwaysVisible);
+
+ // The JavaScript 'window' object for the created child.
+ [instanceOf=Window] object contentWindow;
+
+ // The id the window was created with.
+ DOMString id;
+
+ // The position, size and constraints of the window's content, which does
+ // not include window decorations.
+ // This property is new in Chrome 36.
+ Bounds innerBounds;
+
+ // The position, size and constraints of the window, which includes window
+ // decorations, such as the title bar and frame.
+ // This property is new in Chrome 36.
+ Bounds outerBounds;
+ };
+
+ interface Functions {
+ // The size and position of a window can be specified in a number of
+ // different ways. The most simple option is not specifying anything at
+ // all, in which case a default size and platform dependent position will
+ // be used.
+ //
+ // To set the position, size and constraints of the window, use the
+ // <code>innerBounds</code> or <code>outerBounds</code> properties. Inner
+ // bounds do not include window decorations. Outer bounds include the
+ // window's title bar and frame. Note that the padding between the inner and
+ // outer bounds is determined by the OS. Therefore setting the same property
+ // for both inner and outer bounds is considered an error (for example,
+ // setting both <code>innerBounds.left</code> and
+ // <code>outerBounds.left</code>).
+ //
+ // To automatically remember the positions of windows you can give them ids.
+ // If a window has an id, This id is used to remember the size and position
+ // of the window whenever it is moved or resized. This size and position is
+ // then used instead of the specified bounds on subsequent opening of a
+ // window with the same id. If you need to open a window with an id at a
+ // location other than the remembered default, you can create it hidden,
+ // move it to the desired location, then show it.
+ static void create(DOMString url,
+ optional CreateWindowOptions options,
+ optional CreateWindowCallback callback);
+
+ // Returns an $(ref:AppWindow) object for the
+ // current script context (ie JavaScript 'window' object). This can also be
+ // called on a handle to a script context for another page, for example:
+ // otherWindow.chrome.app.window.current().
+ [nocompile] static AppWindow current();
+ [nocompile, nodoc] static void initializeAppWindow(object state);
+
+ // Gets an array of all currently created app windows. This method is new in
+ // Chrome 33.
+ [nocompile] static AppWindow[] getAll();
+
+ // Gets an $(ref:AppWindow) with the given id. If no window with the given id
+ // exists null is returned. This method is new in Chrome 33.
+ [nocompile] static AppWindow get(DOMString id);
+
+ // Whether the current platform supports windows being visible on all
+ // workspaces.
+ [nocompile] static boolean canSetVisibleOnAllWorkspaces();
+ };
+
+ interface Events {
+ // Fired when the window is resized.
+ [nocompile] static void onBoundsChanged();
+
+ // Fired when the window is closed. Note, this should be listened to from
+ // a window other than the window being closed, for example from the
+ // background page. This is because the window being closed will be in the
+ // process of being torn down when the event is fired, which means not all
+ // APIs in the window's script context will be functional.
+ [nocompile] static void onClosed();
+
+ // Fired when the window is fullscreened (either via the
+ // <code>AppWindow</code> or HTML5 APIs).
+ [nocompile] static void onFullscreened();
+
+ // Fired when the window is maximized.
+ [nocompile] static void onMaximized();
+
+ // Fired when the window is minimized.
+ [nocompile] static void onMinimized();
+
+ // Fired when the window is restored from being minimized or maximized.
+ [nocompile] static void onRestored();
+
+ // Fired when the window's ability to use alpha transparency changes.
+ [nocompile, nodoc] static void onAlphaEnabledChanged();
+
+ // Event for testing. Lets tests wait until a window has been shown.
+ [nocompile, nodoc] static void onWindowFirstShown();
+ };
+};
diff --git a/chromium/extensions/common/api/audio.idl b/chromium/extensions/common/api/audio.idl
new file mode 100644
index 00000000000..36a1124bd3b
--- /dev/null
+++ b/chromium/extensions/common/api/audio.idl
@@ -0,0 +1,115 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The <code>chrome.audio</code> API is provided to allow users to
+// get information about and control the audio devices attached to the
+// system. This API is currently only implemented for ChromeOS.
+namespace audio {
+
+ dictionary OutputDeviceInfo {
+ // The unique identifier of the audio output device.
+ DOMString id;
+ // The user-friendly name (e.g. "Bose Amplifier").
+ DOMString name;
+ // True if this is the current active device.
+ boolean isActive;
+ // True if this is muted.
+ boolean isMuted;
+ // The output volume ranging from 0.0 to 100.0.
+ double volume;
+ };
+
+ dictionary InputDeviceInfo {
+ // The unique identifier of the audio input device.
+ DOMString id;
+ // The user-friendly name (e.g. "USB Microphone").
+ DOMString name;
+ // True if this is the current active device.
+ boolean isActive;
+ // True if this is muted.
+ boolean isMuted;
+ // The input gain ranging from 0.0 to 100.0.
+ double gain;
+ };
+
+ dictionary AudioDeviceInfo {
+ // The unique identifier of the audio device.
+ DOMString id;
+ // True for input device; false for output device.
+ boolean isInput;
+ // Type of the device, including "INTERNAL_SPEAKER", "INTERNAL_MIC",
+ // "HEADPHONE", "USB", "BLUETOOTH", "HDMI", "MIC", "KEYBOARD_MIC",
+ // "AOKR", and "OTHER".
+ DOMString deviceType;
+ // The user-friendly name (e.g. "USB Microphone").
+ DOMString displayName;
+ // Device name.
+ DOMString deviceName;
+ // True if this is the current active device.
+ boolean isActive;
+ // True if this is muted.
+ boolean isMuted;
+ // The sound level of the device, volume for output, gain for input.
+ long level;
+ // The stable/persisted device id string when available.
+ DOMString? stableDeviceId;
+ };
+
+ dictionary DeviceProperties {
+ // True if this is muted.
+ boolean isMuted;
+ // If this is an output device then this field indicates the output volume.
+ // If this is an input device then this field is ignored.
+ double? volume;
+ // If this is an input device then this field indicates the input gain.
+ // If this is an output device then this field is ignored.
+ double? gain;
+ };
+
+ callback GetInfoCallback = void(OutputDeviceInfo[] outputInfo,
+ InputDeviceInfo[] inputInfo);
+ callback SetActiveDevicesCallback = void();
+ callback SetPropertiesCallback = void();
+
+ interface Functions {
+ // Gets the information of all audio output and input devices.
+ static void getInfo(GetInfoCallback callback);
+
+ // Sets the active devices to the devices specified by |ids|.
+ // It can pass in the "complete" active device id list of either input
+ // devices, or output devices, or both. If only input device ids are passed
+ // in, it will only change the input devices' active status, output devices will
+ // NOT be changed; similarly for the case if only output devices are passed.
+ // If the devices specified in |new_active_ids| are already active, they will
+ // remain active. Otherwise, the old active devices will be de-activated
+ // before we activate the new devices with the same type(input/output).
+ static void setActiveDevices(DOMString[] ids,
+ SetActiveDevicesCallback callback);
+
+ // Sets the properties for the input or output device.
+ static void setProperties(DOMString id,
+ DeviceProperties properties,
+ SetPropertiesCallback callback);
+ };
+
+ interface Events {
+ // Fired when anything changes to the audio device configuration.
+ static void onDeviceChanged();
+
+ // Fired when sound level changes for an active audio device.
+ // |id|: id of the audio device.
+ // |level|: new sound level of device(volume for output, gain for input).
+ static void OnLevelChanged(DOMString id, long level);
+
+ // Fired when the mute state of the audio input or output changes.
+ // |isInput|: true indicating audio input; false indicating audio output.
+ // |isMuted|: new value of mute state.
+ static void OnMuteChanged(boolean isInput, boolean isMuted);
+
+ // Fired when audio devices change, either new devices being added, or
+ // existing devices being removed.
+ // |devices|: List of all present audio devices after the change.
+ static void OnDevicesChanged(AudioDeviceInfo[] devices);
+ };
+};
diff --git a/chromium/extensions/common/api/bluetooth.idl b/chromium/extensions/common/api/bluetooth.idl
new file mode 100644
index 00000000000..b5523dc14f4
--- /dev/null
+++ b/chromium/extensions/common/api/bluetooth.idl
@@ -0,0 +1,155 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.bluetooth</code> API to connect to a Bluetooth
+// device. All functions report failures via chrome.runtime.lastError.
+namespace bluetooth {
+ // Allocation authorities for Vendor IDs.
+ enum VendorIdSource {bluetooth, usb};
+
+ // Common device types recognized by Chrome.
+ enum DeviceType {computer, phone, modem, audio, carAudio, video, peripheral,
+ joystick, gamepad, keyboard, mouse, tablet,
+ keyboardMouseCombo};
+
+ // Information about the state of the Bluetooth adapter.
+ dictionary AdapterState {
+ // The address of the adapter, in the format 'XX:XX:XX:XX:XX:XX'.
+ DOMString address;
+
+ // The human-readable name of the adapter.
+ DOMString name;
+
+ // Indicates whether or not the adapter has power.
+ boolean powered;
+
+ // Indicates whether or not the adapter is available (i.e. enabled).
+ boolean available;
+
+ // Indicates whether or not the adapter is currently discovering.
+ boolean discovering;
+ };
+
+ // Callback from the <code>getAdapterState</code> method.
+ // |adapterInfo| : Object containing the adapter information.
+ callback AdapterStateCallback = void(AdapterState adapterInfo);
+
+ // Information about the state of a known Bluetooth device. Note: this
+ // dictionary is also used in bluetooth_private.idl
+ dictionary Device {
+ // The address of the device, in the format 'XX:XX:XX:XX:XX:XX'.
+ DOMString address;
+
+ // The human-readable name of the device.
+ DOMString? name;
+
+ // The class of the device, a bit-field defined by
+ // http://www.bluetooth.org/en-us/specification/assigned-numbers/baseband.
+ long? deviceClass;
+
+ // The Device ID record of the device, where available.
+ VendorIdSource? vendorIdSource;
+ long? vendorId;
+ long? productId;
+ long? deviceId;
+
+ // The type of the device, if recognized by Chrome. This is obtained from
+ // the |deviceClass| field and only represents a small fraction of the
+ // possible device types. When in doubt you should use the |deviceClass|
+ // field directly.
+ DeviceType? type;
+
+ // Indicates whether or not the device is paired with the system.
+ boolean? paired;
+
+ // Indicates whether the device is currently connected to the system.
+ boolean? connected;
+
+ // Indicates whether the device is currently connecting to the system.
+ boolean? connecting;
+
+ // Indicates whether the device is connectable.
+ boolean? connectable;
+
+ // UUIDs of protocols, profiles and services advertised by the device.
+ // For classic Bluetooth devices, this list is obtained from EIR data and
+ // SDP tables. For Low Energy devices, this list is obtained from AD and
+ // GATT primary services. For dual mode devices this may be obtained from
+ // both.
+ DOMString[]? uuids;
+
+ // The received signal strength, in dBm. This field is avaliable and valid
+ // only during discovery. Outside of discovery it's value is not specified.
+ long? inquiryRssi;
+
+ // The transmitted power level. This field is avaliable only for LE devices
+ // that include this field in AD. It is avaliable and valid only during
+ // discovery.
+ long? inquiryTxPower;
+ };
+
+ // Callback from the <code>getDevice</code> method.
+ // |deviceInfo| : Object containing the device information.
+ callback GetDeviceCallback = void(Device deviceInfo);
+
+ // Callback from the <code>getDevices</code> method.
+ // |deviceInfos| : Array of object containing device information.
+ callback GetDevicesCallback = void(Device[] deviceInfos);
+
+ // Callback from the <code>startDiscovery</code> method.
+ callback StartDiscoveryCallback = void();
+
+ // Callback from the <code>stopDiscovery</code> method.
+ callback StopDiscoveryCallback = void();
+
+ // These functions all report failures via chrome.runtime.lastError.
+ interface Functions {
+ // Get information about the Bluetooth adapter.
+ // |callback| : Called with an AdapterState object describing the adapter
+ // state.
+ static void getAdapterState(AdapterStateCallback callback);
+
+ // Get information about a Bluetooth device known to the system.
+ // |deviceAddress| : Address of device to get.
+ // |callback| : Called with the Device object describing the device.
+ static void getDevice(DOMString deviceAddress, GetDeviceCallback callback);
+
+ // Get a list of Bluetooth devices known to the system, including paired
+ // and recently discovered devices.
+ // |callback| : Called when the search is completed.
+ static void getDevices(GetDevicesCallback callback);
+
+ // Start discovery. Newly discovered devices will be returned via the
+ // onDeviceAdded event. Previously discovered devices already known to
+ // the adapter must be obtained using getDevices and will only be updated
+ // using the |onDeviceChanged| event if information about them changes.
+ //
+ // Discovery will fail to start if this application has already called
+ // startDiscovery. Discovery can be resource intensive: stopDiscovery
+ // should be called as soon as possible.
+ // |callback| : Called to indicate success or failure.
+ static void startDiscovery(optional StartDiscoveryCallback callback);
+
+ // Stop discovery.
+ // |callback| : Called to indicate success or failure.
+ static void stopDiscovery(optional StopDiscoveryCallback callback);
+ };
+
+ interface Events {
+ // Fired when the state of the Bluetooth adapter changes.
+ // |state| : The new state of the adapter.
+ static void onAdapterStateChanged(AdapterState state);
+
+ // Fired when information about a new Bluetooth device is available.
+ static void onDeviceAdded(Device device);
+
+ // Fired when information about a known Bluetooth device has changed.
+ static void onDeviceChanged(Device device);
+
+ // Fired when a Bluetooth device that was previously discovered has been
+ // out of range for long enough to be considered unavailable again, and
+ // when a paired device is removed.
+ static void onDeviceRemoved(Device device);
+ };
+};
diff --git a/chromium/extensions/common/api/bluetooth/bluetooth_manifest_data.cc b/chromium/extensions/common/api/bluetooth/bluetooth_manifest_data.cc
new file mode 100644
index 00000000000..94ba7fe3bf2
--- /dev/null
+++ b/chromium/extensions/common/api/bluetooth/bluetooth_manifest_data.cc
@@ -0,0 +1,77 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/api/bluetooth/bluetooth_manifest_data.h"
+
+#include <utility>
+
+#include "extensions/common/api/bluetooth/bluetooth_manifest_permission.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+BluetoothManifestData::BluetoothManifestData(
+ scoped_ptr<BluetoothManifestPermission> permission)
+ : permission_(std::move(permission)) {
+ DCHECK(permission_);
+}
+
+BluetoothManifestData::~BluetoothManifestData() {}
+
+// static
+BluetoothManifestData* BluetoothManifestData::Get(const Extension* extension) {
+ return static_cast<BluetoothManifestData*>(
+ extension->GetManifestData(manifest_keys::kBluetooth));
+}
+
+// static
+bool BluetoothManifestData::CheckRequest(
+ const Extension* extension,
+ const BluetoothPermissionRequest& request) {
+ const BluetoothManifestData* data = BluetoothManifestData::Get(extension);
+ return data && data->permission()->CheckRequest(extension, request);
+}
+
+// static
+bool BluetoothManifestData::CheckSocketPermitted(
+ const Extension* extension) {
+ const BluetoothManifestData* data = BluetoothManifestData::Get(extension);
+ return data && data->permission()->CheckSocketPermitted(extension);
+}
+
+// static
+bool BluetoothManifestData::CheckLowEnergyPermitted(
+ const Extension* extension) {
+ const BluetoothManifestData* data = BluetoothManifestData::Get(extension);
+ return data && data->permission()->CheckLowEnergyPermitted(extension);
+}
+
+// static
+bool BluetoothManifestData::CheckPeripheralPermitted(
+ const Extension* extension) {
+ const BluetoothManifestData* data = BluetoothManifestData::Get(extension);
+ return data && data->permission()->CheckLowEnergyPermitted(extension) &&
+ data->permission()->CheckPeripheralPermitted(extension);
+}
+
+// static
+scoped_ptr<BluetoothManifestData> BluetoothManifestData::FromValue(
+ const base::Value& value,
+ base::string16* error) {
+ scoped_ptr<BluetoothManifestPermission> permission =
+ BluetoothManifestPermission::FromValue(value, error);
+ if (!permission)
+ return scoped_ptr<BluetoothManifestData>();
+
+ return scoped_ptr<BluetoothManifestData>(
+ new BluetoothManifestData(std::move(permission)));
+}
+
+BluetoothPermissionRequest::BluetoothPermissionRequest(
+ const std::string& uuid)
+ : uuid(uuid) {}
+
+BluetoothPermissionRequest::~BluetoothPermissionRequest() {}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/api/bluetooth/bluetooth_manifest_data.h b/chromium/extensions/common/api/bluetooth/bluetooth_manifest_data.h
new file mode 100644
index 00000000000..b47cf8bdde6
--- /dev/null
+++ b/chromium/extensions/common/api/bluetooth/bluetooth_manifest_data.h
@@ -0,0 +1,62 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_API_BLUETOOTH_BLUETOOTH_MANIFEST_DATA_H_
+#define EXTENSIONS_COMMON_API_BLUETOOTH_BLUETOOTH_MANIFEST_DATA_H_
+
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+class BluetoothManifestPermission;
+struct BluetoothPermissionRequest;
+}
+
+namespace extensions {
+
+// The parsed form of the "bluetooth" manifest entry.
+class BluetoothManifestData : public Extension::ManifestData {
+ public:
+ explicit BluetoothManifestData(
+ scoped_ptr<BluetoothManifestPermission> permission);
+ ~BluetoothManifestData() override;
+
+ // Gets the BluetoothManifestData for |extension|, or NULL if none was
+ // specified.
+ static BluetoothManifestData* Get(const Extension* extension);
+
+ static bool CheckRequest(const Extension* extension,
+ const BluetoothPermissionRequest& request);
+
+ static bool CheckSocketPermitted(const Extension* extension);
+ static bool CheckLowEnergyPermitted(const Extension* extension);
+ static bool CheckPeripheralPermitted(const Extension* extension);
+
+ // Tries to construct the info based on |value|, as it would have appeared in
+ // the manifest. Sets |error| and returns an empty scoped_ptr on failure.
+ static scoped_ptr<BluetoothManifestData> FromValue(const base::Value& value,
+ base::string16* error);
+
+ const BluetoothManifestPermission* permission() const {
+ return permission_.get();
+ }
+
+ private:
+ scoped_ptr<BluetoothManifestPermission> permission_;
+};
+
+// Used for checking bluetooth permission.
+struct BluetoothPermissionRequest {
+ explicit BluetoothPermissionRequest(const std::string& uuid);
+ ~BluetoothPermissionRequest();
+
+ std::string uuid;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_API_BLUETOOTH_BLUETOOTH_MANIFEST_DATA_H_
diff --git a/chromium/extensions/common/api/bluetooth/bluetooth_manifest_handler.cc b/chromium/extensions/common/api/bluetooth/bluetooth_manifest_handler.cc
new file mode 100644
index 00000000000..da143fa8c2f
--- /dev/null
+++ b/chromium/extensions/common/api/bluetooth/bluetooth_manifest_handler.cc
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/api/bluetooth/bluetooth_manifest_handler.h"
+
+#include "extensions/common/api/bluetooth/bluetooth_manifest_data.h"
+#include "extensions/common/api/bluetooth/bluetooth_manifest_permission.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+BluetoothManifestHandler::BluetoothManifestHandler() {}
+
+BluetoothManifestHandler::~BluetoothManifestHandler() {}
+
+bool BluetoothManifestHandler::Parse(Extension* extension,
+ base::string16* error) {
+ const base::Value* bluetooth = NULL;
+ CHECK(extension->manifest()->Get(manifest_keys::kBluetooth, &bluetooth));
+ scoped_ptr<BluetoothManifestData> data =
+ BluetoothManifestData::FromValue(*bluetooth, error);
+ if (!data)
+ return false;
+
+ extension->SetManifestData(manifest_keys::kBluetooth, data.release());
+ return true;
+}
+
+ManifestPermission* BluetoothManifestHandler::CreatePermission() {
+ return new BluetoothManifestPermission();
+}
+
+ManifestPermission* BluetoothManifestHandler::CreateInitialRequiredPermission(
+ const Extension* extension) {
+ BluetoothManifestData* data = BluetoothManifestData::Get(extension);
+ if (data)
+ return data->permission()->Clone();
+ return NULL;
+}
+
+const std::vector<std::string> BluetoothManifestHandler::Keys() const {
+ return SingleKey(manifest_keys::kBluetooth);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/api/bluetooth/bluetooth_manifest_handler.h b/chromium/extensions/common/api/bluetooth/bluetooth_manifest_handler.h
new file mode 100644
index 00000000000..16f749e3825
--- /dev/null
+++ b/chromium/extensions/common/api/bluetooth/bluetooth_manifest_handler.h
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_API_BLUETOOTH_BLUETOOTH_MANIFEST_HANDLER_H_
+#define EXTENSIONS_COMMON_API_BLUETOOTH_BLUETOOTH_MANIFEST_HANDLER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+class Extension;
+class ManifestPermission;
+}
+
+namespace extensions {
+
+// Parses the "bluetooth" manifest key.
+class BluetoothManifestHandler : public ManifestHandler {
+ public:
+ BluetoothManifestHandler();
+ ~BluetoothManifestHandler() override;
+
+ // ManifestHandler overrides.
+ bool Parse(Extension* extension, base::string16* error) override;
+ ManifestPermission* CreatePermission() override;
+ ManifestPermission* CreateInitialRequiredPermission(
+ const Extension* extension) override;
+
+ private:
+ // ManifestHandler overrides.
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothManifestHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_API_BLUETOOTH_BLUETOOTH_MANIFEST_HANDLER_H_
diff --git a/chromium/extensions/common/api/bluetooth/bluetooth_manifest_permission.cc b/chromium/extensions/common/api/bluetooth/bluetooth_manifest_permission.cc
new file mode 100644
index 00000000000..c2c6d2c69a5
--- /dev/null
+++ b/chromium/extensions/common/api/bluetooth/bluetooth_manifest_permission.cc
@@ -0,0 +1,200 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/api/bluetooth/bluetooth_manifest_permission.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "device/bluetooth/bluetooth_uuid.h"
+#include "extensions/common/api/bluetooth/bluetooth_manifest_data.h"
+#include "extensions/common/api/extensions_manifest_types.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/features/behavior_feature.h"
+#include "extensions/common/features/feature_provider.h"
+#include "extensions/common/manifest_constants.h"
+#include "grit/extensions_strings.h"
+#include "ipc/ipc_message.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+namespace bluetooth_errors {
+const char kErrorInvalidUuid[] = "Invalid UUID '*'";
+}
+
+namespace errors = bluetooth_errors;
+
+namespace {
+
+bool ParseUuid(BluetoothManifestPermission* permission,
+ const std::string& uuid,
+ base::string16* error) {
+ device::BluetoothUUID bt_uuid(uuid);
+ if (!bt_uuid.IsValid()) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kErrorInvalidUuid, uuid);
+ return false;
+ }
+ permission->AddPermission(uuid);
+ return true;
+}
+
+bool ParseUuidArray(BluetoothManifestPermission* permission,
+ const scoped_ptr<std::vector<std::string> >& uuids,
+ base::string16* error) {
+ for (std::vector<std::string>::const_iterator it = uuids->begin();
+ it != uuids->end();
+ ++it) {
+ if (!ParseUuid(permission, *it, error)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace
+
+BluetoothManifestPermission::BluetoothManifestPermission()
+ : socket_(false), low_energy_(false), peripheral_(false) {
+}
+
+BluetoothManifestPermission::~BluetoothManifestPermission() {}
+
+// static
+scoped_ptr<BluetoothManifestPermission> BluetoothManifestPermission::FromValue(
+ const base::Value& value,
+ base::string16* error) {
+ scoped_ptr<api::extensions_manifest_types::Bluetooth> bluetooth =
+ api::extensions_manifest_types::Bluetooth::FromValue(value, error);
+ if (!bluetooth)
+ return scoped_ptr<BluetoothManifestPermission>();
+
+ scoped_ptr<BluetoothManifestPermission> result(
+ new BluetoothManifestPermission());
+ if (bluetooth->uuids) {
+ if (!ParseUuidArray(result.get(), bluetooth->uuids, error)) {
+ return scoped_ptr<BluetoothManifestPermission>();
+ }
+ }
+ if (bluetooth->socket) {
+ result->socket_ = *(bluetooth->socket);
+ }
+ if (bluetooth->low_energy) {
+ result->low_energy_ = *(bluetooth->low_energy);
+ }
+ if (bluetooth->peripheral) {
+ result->peripheral_ = *(bluetooth->peripheral);
+ }
+ return result;
+}
+
+bool BluetoothManifestPermission::CheckRequest(
+ const Extension* extension,
+ const BluetoothPermissionRequest& request) const {
+
+ device::BluetoothUUID param_uuid(request.uuid);
+ for (BluetoothUuidSet::const_iterator it = uuids_.begin();
+ it != uuids_.end();
+ ++it) {
+ device::BluetoothUUID uuid(*it);
+ if (param_uuid == uuid)
+ return true;
+ }
+ return false;
+}
+
+bool BluetoothManifestPermission::CheckSocketPermitted(
+ const Extension* extension) const {
+ return socket_;
+}
+
+bool BluetoothManifestPermission::CheckLowEnergyPermitted(
+ const Extension* extension) const {
+ return low_energy_;
+}
+
+bool BluetoothManifestPermission::CheckPeripheralPermitted(
+ const Extension* extension) const {
+ return peripheral_;
+}
+
+std::string BluetoothManifestPermission::name() const {
+ return manifest_keys::kBluetooth;
+}
+
+std::string BluetoothManifestPermission::id() const { return name(); }
+
+PermissionIDSet BluetoothManifestPermission::GetPermissions() const {
+ PermissionIDSet permissions;
+ permissions.insert(APIPermission::kBluetooth);
+ if (!uuids_.empty()) {
+ permissions.insert(APIPermission::kBluetoothDevices);
+ }
+ return permissions;
+}
+
+bool BluetoothManifestPermission::FromValue(const base::Value* value) {
+ if (!value)
+ return false;
+ base::string16 error;
+ scoped_ptr<BluetoothManifestPermission> manifest_permission(
+ BluetoothManifestPermission::FromValue(*value, &error));
+
+ if (!manifest_permission)
+ return false;
+
+ uuids_ = manifest_permission->uuids_;
+ return true;
+}
+
+scoped_ptr<base::Value> BluetoothManifestPermission::ToValue() const {
+ api::extensions_manifest_types::Bluetooth bluetooth;
+ bluetooth.uuids.reset(new std::vector<std::string>(uuids_.begin(),
+ uuids_.end()));
+ return bluetooth.ToValue();
+}
+
+ManifestPermission* BluetoothManifestPermission::Diff(
+ const ManifestPermission* rhs) const {
+ const BluetoothManifestPermission* other =
+ static_cast<const BluetoothManifestPermission*>(rhs);
+
+ scoped_ptr<BluetoothManifestPermission> result(
+ new BluetoothManifestPermission());
+ result->uuids_ = base::STLSetDifference<BluetoothUuidSet>(
+ uuids_, other->uuids_);
+ return result.release();
+}
+
+ManifestPermission* BluetoothManifestPermission::Union(
+ const ManifestPermission* rhs) const {
+ const BluetoothManifestPermission* other =
+ static_cast<const BluetoothManifestPermission*>(rhs);
+
+ scoped_ptr<BluetoothManifestPermission> result(
+ new BluetoothManifestPermission());
+ result->uuids_ = base::STLSetUnion<BluetoothUuidSet>(
+ uuids_, other->uuids_);
+ return result.release();
+}
+
+ManifestPermission* BluetoothManifestPermission::Intersect(
+ const ManifestPermission* rhs) const {
+ const BluetoothManifestPermission* other =
+ static_cast<const BluetoothManifestPermission*>(rhs);
+
+ scoped_ptr<BluetoothManifestPermission> result(
+ new BluetoothManifestPermission());
+ result->uuids_ = base::STLSetIntersection<BluetoothUuidSet>(
+ uuids_, other->uuids_);
+ return result.release();
+}
+
+void BluetoothManifestPermission::AddPermission(const std::string& uuid) {
+ uuids_.insert(uuid);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/api/bluetooth/bluetooth_manifest_permission.h b/chromium/extensions/common/api/bluetooth/bluetooth_manifest_permission.h
new file mode 100644
index 00000000000..d72b226efff
--- /dev/null
+++ b/chromium/extensions/common/api/bluetooth/bluetooth_manifest_permission.h
@@ -0,0 +1,67 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_API_BLUETOOTH_BLUETOOTH_MANIFEST_PERMISSION_H_
+#define EXTENSIONS_COMMON_API_BLUETOOTH_BLUETOOTH_MANIFEST_PERMISSION_H_
+
+#include <set>
+#include <vector>
+
+#include "extensions/common/install_warning.h"
+#include "extensions/common/permissions/manifest_permission.h"
+
+namespace extensions {
+class Extension;
+}
+
+namespace extensions {
+struct BluetoothPermissionRequest;
+}
+
+namespace extensions {
+
+class BluetoothManifestPermission : public ManifestPermission {
+ public:
+ typedef std::set<std::string> BluetoothUuidSet;
+ BluetoothManifestPermission();
+ ~BluetoothManifestPermission() override;
+
+ // Tries to construct the info based on |value|, as it would have appeared in
+ // the manifest. Sets |error| and returns an empty scoped_ptr on failure.
+ static scoped_ptr<BluetoothManifestPermission> FromValue(
+ const base::Value& value,
+ base::string16* error);
+
+ bool CheckRequest(const Extension* extension,
+ const BluetoothPermissionRequest& request) const;
+ bool CheckSocketPermitted(const Extension* extension) const;
+ bool CheckLowEnergyPermitted(const Extension* extension) const;
+ bool CheckPeripheralPermitted(const Extension* extension) const;
+
+ void AddPermission(const std::string& uuid);
+
+ // extensions::ManifestPermission overrides.
+ std::string name() const override;
+ std::string id() const override;
+ PermissionIDSet GetPermissions() const override;
+ bool FromValue(const base::Value* value) override;
+ scoped_ptr<base::Value> ToValue() const override;
+ ManifestPermission* Diff(const ManifestPermission* rhs) const override;
+ ManifestPermission* Union(const ManifestPermission* rhs) const override;
+ ManifestPermission* Intersect(const ManifestPermission* rhs) const override;
+
+ const BluetoothUuidSet& uuids() const {
+ return uuids_;
+ }
+
+ private:
+ BluetoothUuidSet uuids_;
+ bool socket_;
+ bool low_energy_;
+ bool peripheral_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_API_BLUETOOTH_BLUETOOTH_MANIFEST_PERMISSION_H_
diff --git a/chromium/extensions/common/api/bluetooth_private.idl b/chromium/extensions/common/api/bluetooth_private.idl
new file mode 100644
index 00000000000..f943ab5616f
--- /dev/null
+++ b/chromium/extensions/common/api/bluetooth_private.idl
@@ -0,0 +1,167 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.bluetoothPrivate</code> API to control the Bluetooth
+// adapter state and handle device pairing.
+// NOTE: This IDL is dependent on bluetooth.idl.
+
+[implemented_in = "extensions/browser/api/bluetooth/bluetooth_private_api.h"]
+namespace bluetoothPrivate {
+ // Events that can occur during pairing. The method used for pairing varies
+ // depending on the capability of the two devices.
+ enum PairingEventType {
+ // An alphanumeric PIN code is required to be entered by the user.
+ requestPincode,
+
+ // Display a PIN code to the user.
+ displayPincode,
+
+ // A numeric passkey is required to be entered by the user.
+ requestPasskey,
+
+ // Display a zero padded 6 digit numeric passkey that the user entered on
+ // the remote device. This event may occur multiple times during pairing to
+ // update the entered passkey.
+ displayPasskey,
+
+ // The number of keys inputted by the user on the remote device when
+ // entering a passkey. This event may be called multiple times during
+ // pairing to update the number of keys inputted.
+ keysEntered,
+
+ // Requests that a 6 digit passkey be displayed and the user confirms that
+ // both devies show the same passkey.
+ confirmPasskey,
+
+ // Requests authorization for a pairing under the just-works model. It is up
+ // to the app to ask for user confirmation.
+ requestAuthorization,
+
+ // Pairing is completed.
+ complete
+ };
+
+ // Results for connect(). See function declaration for details.
+ enum ConnectResultType {
+ alreadyConnected,
+ attributeLengthInvalid,
+ authCanceled,
+ authFailed,
+ authRejected,
+ authTimeout,
+ connectionCongested,
+ failed,
+ inProgress,
+ insufficientEncryption,
+ offsetInvalid,
+ readNotPermitted,
+ requestNotSupported,
+ success,
+ unknownError,
+ unsupportedDevice,
+ writeNotPermitted
+ };
+
+ // Valid pairing responses.
+ enum PairingResponse {
+ confirm, reject, cancel
+ };
+
+ enum TransportType {
+ le, bredr, dual
+ };
+
+ // A pairing event received from a Bluetooth device.
+ dictionary PairingEvent {
+ PairingEventType pairing;
+ bluetooth.Device device;
+ DOMString? pincode;
+ long? passkey;
+ long? enteredKey;
+ };
+
+ dictionary NewAdapterState {
+ // The human-readable name of the adapter.
+ DOMString? name;
+
+ // Whether or not the adapter has power.
+ boolean? powered;
+
+ // Whether the adapter is discoverable by other devices.
+ boolean? discoverable;
+ };
+
+ dictionary SetPairingResponseOptions {
+ // The remote device to send the pairing response.
+ bluetooth.Device device;
+
+ // The response type.
+ PairingResponse response;
+
+ // A 1-16 character alphanumeric set in response to
+ // <code>requestPincode</code>.
+ DOMString? pincode;
+
+ // An integer between 0-999999 set in response to
+ // <code>requestPasskey</code>.
+ long? passkey;
+ };
+
+ dictionary DiscoveryFilter {
+ // Transport type.
+ TransportType? transport;
+
+ // uuid of service or array of uuids
+ (DOMString or DOMString[])? uuids;
+
+ // RSSI ranging value. Only devices with RSSI higher than this value will be
+ // reported.
+ long? rssi;
+
+ // Pathloss ranging value. Only devices with pathloss lower than this value
+ // will be reported.
+ long? pathloss;
+ };
+
+ callback VoidCallback = void();
+ callback ConnectCallback = void(ConnectResultType result);
+
+ // These functions all report failures via chrome.runtime.lastError.
+ interface Functions {
+ // Changes the state of the Bluetooth adapter.
+ // |adapterState|:
+ static void setAdapterState(NewAdapterState adapterState,
+ optional VoidCallback callback);
+
+ static void setPairingResponse(SetPairingResponseOptions options,
+ optional VoidCallback callback);
+
+ // Tears down all connections to the given device.
+ static void disconnectAll(DOMString deviceAddress,
+ optional VoidCallback callback);
+
+ // Forgets the given device.
+ static void forgetDevice(DOMString deviceAddress,
+ optional VoidCallback callback);
+
+ // Set or clear discovery filter.
+ static void setDiscoveryFilter(DiscoveryFilter discoveryFilter,
+ optional VoidCallback callback);
+
+ // Connects to the given device. This will only throw an error if the
+ // device address is invalid or the device is already connected. Otherwise
+ // this will succeed and invoke |callback| with ConnectResultType.
+ static void connect(DOMString deviceAddress,
+ optional ConnectCallback callback);
+
+ // Pairs the given device.
+ static void pair(DOMString deviceAddress, optional VoidCallback callback);
+ };
+
+ interface Events {
+ // Fired when a pairing event occurs.
+ // |pairingEvent|: A pairing event.
+ [maxListeners=1] static void onPairing(PairingEvent pairingEvent);
+ };
+};
diff --git a/chromium/extensions/common/api/bluetooth_socket.idl b/chromium/extensions/common/api/bluetooth_socket.idl
new file mode 100644
index 00000000000..cb95c02adc8
--- /dev/null
+++ b/chromium/extensions/common/api/bluetooth_socket.idl
@@ -0,0 +1,316 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.bluetoothSocket</code> API to send and receive data
+// to Bluetooth devices using RFCOMM and L2CAP connections.
+namespace bluetoothSocket {
+ // The socket properties specified in the $ref:create or $ref:update
+ // function. Each property is optional. If a property value is not specified,
+ // a default value is used when calling $ref:create, or the existing value is
+ // preserved when calling $ref:update.
+ dictionary SocketProperties {
+ // Flag indicating whether the socket is left open when the event page of
+ // the application is unloaded (see <a
+ // href="http://developer.chrome.com/apps/app_lifecycle.html">Manage App
+ // Lifecycle</a>). The default value is <code>false.</code> When the
+ // application is loaded, any sockets previously opened with persistent=true
+ // can be fetched with $ref:getSockets.
+ boolean? persistent;
+
+ // An application-defined string associated with the socket.
+ DOMString? name;
+
+ // The size of the buffer used to receive data. The default value is 4096.
+ long? bufferSize;
+ };
+
+ // Result of <code>create</code> call.
+ dictionary CreateInfo {
+ // The ID of the newly created socket. Note that socket IDs created
+ // from this API are not compatible with socket IDs created from other APIs,
+ // such as the <code>$(ref:sockets.tcp)</code> API.
+ long socketId;
+ };
+
+ // Callback from the <code>create</code> method.
+ // |createInfo| : The result of the socket creation.
+ callback CreateCallback = void (CreateInfo createInfo);
+
+ // Callback from the <code>update</code> method.
+ callback UpdateCallback = void ();
+
+ // Callback from the <code>setPaused</code> method.
+ callback SetPausedCallback = void ();
+
+ // Options that may be passed to the <code>listenUsingRfcomm</code> and
+ // <code>listenUsingL2cap</code> methods. Each property is optional with a
+ // default being used if not specified.
+ dictionary ListenOptions {
+ // The RFCOMM Channel used by <code>listenUsingRfcomm</code>. If specified,
+ // this channel must not be previously in use or the method call will fail.
+ // When not specified, an unused channel will be automatically allocated.
+ long? channel;
+
+ // The L2CAP PSM used by <code>listenUsingL2cap</code>. If specified, this
+ // PSM must not be previously in use or the method call with fail. When
+ // not specified, an unused PSM will be automatically allocated.
+ long? psm;
+
+ // Length of the socket's listen queue. The default value depends on the
+ // operating system's host subsystem.
+ long? backlog;
+ };
+
+ // Callback from the <code>listenUsingRfcomm</code> and
+ // <code>listenUsingL2cap</code> methods.
+ callback ListenCallback = void ();
+
+ // Callback from the <code>connect</code> method.
+ callback ConnectCallback = void ();
+
+ // Callback from the <code>disconnect</code> method.
+ callback DisconnectCallback = void ();
+
+ // Callback from the <code>close</code> method.
+ callback CloseCallback = void ();
+
+ // Callback from the <code>send</code> method.
+ // |bytesSent| : The number of bytes sent.
+ callback SendCallback = void (long bytesSent);
+
+ // Result of the <code>getInfo</code> method.
+ dictionary SocketInfo {
+ // The socket identifier.
+ long socketId;
+
+ // Flag indicating if the socket remains open when the event page of the
+ // application is unloaded (see <code>SocketProperties.persistent</code>).
+ // The default value is "false".
+ boolean persistent;
+
+ // Application-defined string associated with the socket.
+ DOMString? name;
+
+ // The size of the buffer used to receive data. If no buffer size has been
+ // specified explictly, the value is not provided.
+ long? bufferSize;
+
+ // Flag indicating whether a connected socket blocks its peer from sending
+ // more data, or whether connection requests on a listening socket are
+ // dispatched through the <code>onAccept</code> event or queued up in the
+ // listen queue backlog.
+ // See <code>setPaused</code>. The default value is "false".
+ boolean paused;
+
+ // Flag indicating whether the socket is connected to a remote peer.
+ boolean connected;
+
+ // If the underlying socket is connected, contains the Bluetooth address of
+ // the device it is connected to.
+ DOMString? address;
+
+ // If the underlying socket is connected, contains information about the
+ // service UUID it is connected to, otherwise if the underlying socket is
+ // listening, contains information about the service UUID it is listening
+ // on.
+ DOMString? uuid;
+ };
+
+ // Callback from the <code>getInfo</code> method.
+ // |socketInfo| : Object containing the socket information.
+ callback GetInfoCallback = void (SocketInfo socketInfo);
+
+ // Callback from the <code>getSockets</code> method.
+ // |socketInfos| : Array of object containing socket information.
+ callback GetSocketsCallback = void (SocketInfo[] sockets);
+
+ // Data from an <code>onAccept</code> event.
+ dictionary AcceptInfo {
+ // The server socket identifier.
+ long socketId;
+
+ // The client socket identifier, i.e. the socket identifier of the newly
+ // established connection. This socket identifier should be used only with
+ // functions from the <code>chrome.bluetoothSocket</code> namespace. Note
+ // the client socket is initially paused and must be explictly un-paused by
+ // the application to start receiving data.
+ long clientSocketId;
+ };
+
+ enum AcceptError {
+ // A system error occurred and the connection may be unrecoverable.
+ system_error,
+
+ // The socket is not listening.
+ not_listening
+ };
+
+ // Data from an <code>onAcceptError</code> event.
+ dictionary AcceptErrorInfo {
+ // The server socket identifier.
+ long socketId;
+
+ // The error message.
+ DOMString errorMessage;
+
+ // An error code indicating what went wrong.
+ AcceptError error;
+ };
+
+ // Data from an <code>onReceive</code> event.
+ dictionary ReceiveInfo {
+ // The socket identifier.
+ long socketId;
+
+ // The data received, with a maxium size of <code>bufferSize</code>.
+ ArrayBuffer data;
+ };
+
+ enum ReceiveError {
+ // The connection was disconnected.
+ disconnected,
+
+ // A system error occurred and the connection may be unrecoverable.
+ system_error,
+
+ // The socket has not been connected.
+ not_connected
+ };
+
+ // Data from an <code>onReceiveError</code> event.
+ dictionary ReceiveErrorInfo {
+ // The socket identifier.
+ long socketId;
+
+ // The error message.
+ DOMString errorMessage;
+
+ // An error code indicating what went wrong.
+ ReceiveError error;
+ };
+
+ // These functions all report failures via chrome.runtime.lastError.
+ interface Functions {
+ // Creates a Bluetooth socket.
+ // |properties| : The socket properties (optional).
+ // |callback| : Called when the socket has been created.
+ static void create(optional SocketProperties properties,
+ CreateCallback callback);
+
+ // Updates the socket properties.
+ // |socketId| : The socket identifier.
+ // |properties| : The properties to update.
+ // |callback| : Called when the properties are updated.
+ static void update(long socketId,
+ SocketProperties properties,
+ optional UpdateCallback callback);
+
+ // Enables or disables a connected socket from receiving messages from its
+ // peer, or a listening socket from accepting new connections. The default
+ // value is "false". Pausing a connected socket is typically used by an
+ // application to throttle data sent by its peer. When a connected socket
+ // is paused, no <code>onReceive</code>event is raised. When a socket is
+ // connected and un-paused, <code>onReceive</code> events are raised again
+ // when messages are received. When a listening socket is paused, new
+ // connections are accepted until its backlog is full then additional
+ // connection requests are refused. <code>onAccept</code> events are raised
+ // only when the socket is un-paused.
+ static void setPaused(long socketId,
+ boolean paused,
+ optional SetPausedCallback callback);
+
+ // Listen for connections using the RFCOMM protocol.
+ // |socketId| : The socket identifier.
+ // |uuid| : Service UUID to listen on.
+ // |options| : Optional additional options for the service.
+ // |callback| : Called when listen operation completes.
+ static void listenUsingRfcomm(long socketId,
+ DOMString uuid,
+ optional ListenOptions options,
+ ListenCallback callback);
+
+ // Listen for connections using the L2CAP protocol.
+ // |socketId| : The socket identifier.
+ // |uuid| : Service UUID to listen on.
+ // |options| : Optional additional options for the service.
+ // |callback| : Called when listen operation completes.
+ static void listenUsingL2cap(long socketId,
+ DOMString uuid,
+ optional ListenOptions options,
+ ListenCallback callback);
+
+ // Connects the socket to a remote Bluetooth device. When the
+ // <code>connect</code> operation completes successfully,
+ // <code>onReceive</code> events are raised when data is received from the
+ // peer. If a network error occur while the runtime is receiving packets,
+ // a <code>onReceiveError</code> event is raised, at which point no more
+ // <code>onReceive</code> event will be raised for this socket until the
+ // <code>setPaused(false)</code> method is called.
+ // |socketId| : The socket identifier.
+ // |address| : The address of the Bluetooth device.
+ // |uuid| : The UUID of the service to connect to.
+ // |callback| : Called when the connect attempt is complete.
+ static void connect(long socketId,
+ DOMString address,
+ DOMString uuid,
+ ConnectCallback callback);
+
+ // Disconnects the socket. The socket identifier remains valid.
+ // |socketId| : The socket identifier.
+ // |callback| : Called when the disconnect attempt is complete.
+ static void disconnect(long socketId,
+ optional DisconnectCallback callback);
+
+ // Disconnects and destroys the socket. Each socket created should be
+ // closed after use. The socket id is no longer valid as soon at the
+ // function is called. However, the socket is guaranteed to be closed only
+ // when the callback is invoked.
+ // |socketId| : The socket identifier.
+ // |callback| : Called when the <code>close</code> operation completes.
+ static void close(long socketId,
+ optional CloseCallback callback);
+
+ // Sends data on the given Bluetooth socket.
+ // |socketId| : The socket identifier.
+ // |data| : The data to send.
+ // |callback| : Called with the number of bytes sent.
+ static void send(long socketId,
+ ArrayBuffer data,
+ optional SendCallback callback);
+
+ // Retrieves the state of the given socket.
+ // |socketId| : The socket identifier.
+ // |callback| : Called when the socket state is available.
+ static void getInfo(long socketId,
+ GetInfoCallback callback);
+
+ // Retrieves the list of currently opened sockets owned by the application.
+ // |callback| : Called when the list of sockets is available.
+ static void getSockets(GetSocketsCallback callback);
+ };
+
+ interface Events {
+ // Event raised when a connection has been established for a given socket.
+ // |info| : The event data.
+ static void onAccept(AcceptInfo info);
+
+ // Event raised when a network error occurred while the runtime was waiting
+ // for new connections on the given socket. Once this event is raised, the
+ // socket is set to <code>paused</code> and no more <code>onAccept</code>
+ // events are raised for this socket.
+ // |info| : The event data.
+ static void onAcceptError(AcceptErrorInfo info);
+
+ // Event raised when data has been received for a given socket.
+ // |info| : The event data.
+ static void onReceive(ReceiveInfo info);
+
+ // Event raised when a network error occured while the runtime was waiting
+ // for data on the socket. Once this event is raised, the socket is set to
+ // <code>paused</code> and no more <code>onReceive</code> events are raised
+ // for this socket.
+ // |info| : The event data.
+ static void onReceiveError(ReceiveErrorInfo info);
+ };
+};
diff --git a/chromium/extensions/common/api/cast_channel.idl b/chromium/extensions/common/api/cast_channel.idl
new file mode 100644
index 00000000000..33af514fda9
--- /dev/null
+++ b/chromium/extensions/common/api/cast_channel.idl
@@ -0,0 +1,235 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// API for communicating with a Google Cast device over an authenticated
+// channel.
+namespace cast.channel {
+ // The state of the channel.
+ enum ReadyState {
+ // The channel is connecting.
+ connecting,
+ // The channel is open and available for messaging.
+ open,
+ // The channel is closing.
+ closing,
+ // The channel is closed.
+ closed
+ };
+
+ // Error conditions that the channel may encounter. All error conditions
+ // are terminal. When an error condition is encountered the API will:
+ // (1) Transition the channel to readyState == 'closed'.
+ // (2) Set ChannelInfo.lastError to the error condition.
+ // (3) Fire an onError event with the error condition.
+ // (4) Fire an onClose event.
+ enum ChannelError {
+ // cast.channel.send() was called when ChannelInfo.readyState != 'open'.
+ channel_not_open,
+ // Authentication was requested and the receiver could not be
+ // authenticated (invalid signature, invalid handhake, TLS error, etc.)
+ authentication_error,
+ // A new channel could not be created for reasons unrelated to
+ // authentication (e.g., there is already an open channel to the same URL).
+ connect_error,
+ // There was an error writing or reading from the underlying socket.
+ socket_error,
+ // A transport level occurred (like an unparseable message).
+ transport_error,
+ // The client attempted to send an unsupported message type through the
+ // channel.
+ invalid_message,
+ // An invalid channel id was passed.
+ invalid_channel_id,
+ // The connection could not be established before timing out.
+ connect_timeout,
+ // The receiving end became unresponsive.
+ ping_timeout,
+ // Unspecified error.
+ unknown
+ };
+
+ // Authentication methods that may be required to connect to a Cast receiver.
+ enum ChannelAuthType {
+ // SSL over TCP.
+ ssl,
+ // SSL over TCP with challenge and receiver signature verification.
+ ssl_verified
+ };
+
+ // Describes the information needed to connect to a Cast receiver.
+ // This replaces the prior use of cast:// and casts:// URLs.
+ dictionary ConnectInfo {
+ // The IPV4 address of the Cast receiver, e.g. '198.1.0.2'.
+ // TODO(mfoltz): Investigate whether IPV6 addresses "just work."
+ DOMString ipAddress;
+
+ // The port number to connect to, 0-65535.
+ long port;
+
+ // The amount of time to wait in milliseconds before stopping the
+ // connection process. Timeouts are disabled if the value is zero.
+ // The default timeout is 8000ms.
+ long? timeout;
+
+ // The authentication method required for the channel.
+ ChannelAuthType auth;
+
+ // ------------------------------------------------------------------------
+ // Both pingInterval and livenessTimeout must be set to enable keep-alive
+ // handling.
+
+ // The amount of time to wait in milliseconds before sending pings
+ // to idle channels.
+ long? pingInterval;
+
+ // The maximum amount of idle time allowed before a channel is closed.
+ long? livenessTimeout;
+
+ // If set, CastDeviceCapability bitmask values describing capability of the
+ // cast device.
+ long? capabilities;
+ };
+
+ // Describes the state of a channel to a Cast receiver.
+ dictionary ChannelInfo {
+ // Id for the channel.
+ long channelId;
+
+ // Connection information that was used to establish the channel to the
+ // receiver.
+ ConnectInfo connectInfo;
+
+ // The current state of the channel.
+ ReadyState readyState;
+
+ // If set, the last error condition encountered by the channel.
+ ChannelError? errorState;
+
+ // If true, keep-alive messages are handled automatically by the channel.
+ boolean keepAlive;
+
+ // Whether the channel is audio only as identified by the device
+ // certificate during channel authentication.
+ boolean audioOnly;
+ };
+
+ // Describes a message sent or received over the channel. Currently only
+ // string messages are supported, although ArrayBuffer and Blob types may be
+ // supported in the future.
+ dictionary MessageInfo {
+ // The message namespace. A namespace is a URN of the form
+ // urn:cast-x:<namespace> that is used to interpret and route Cast messages.
+ DOMString namespace_;
+
+ // source and destination ids identify the origin and destination of the
+ // message. They are used to route messages between endpoints that share a
+ // device-to-device channel.
+ //
+ // For messages between applications:
+ // - The sender application id is a unique identifier generated on behalf
+ // of the sender application.
+ // - The receiver id is always the the session id for the application.
+ //
+ // For messages to or from the sender or receiver platform, the special ids
+ // 'sender-0' and 'receiver-0' can be used.
+ //
+ // For messages intended for all endpoints using a given channel, the
+ // wildcard destination_id '*' can be used.
+ DOMString sourceId;
+ DOMString destinationId;
+
+ // The content of the message. Must be either a string or an ArrayBuffer.
+ any data;
+ };
+
+ // Describes a terminal error encountered by the channel with details of the
+ // error that caused the channel to be closed. One or more of the optional
+ // fields may be set with specific error codes from the underlying
+ // implementation.
+ dictionary ErrorInfo {
+ // The type of error encountered by the channel.
+ ChannelError errorState;
+
+ // The event that was occurring when the error happened. Values are defined
+ // in the enum EventType in logging.proto.
+ long? eventType;
+
+ // An error encountered when processing the authentication handshake.
+ // Values are defined in the enum ChallengeReplyErrorType in logging.proto.
+ long? challengeReplyErrorType;
+
+ // A return value from the underlying net:: socket libraries. Values are
+ // defined in net/base/net_error_list.h.
+ long? netReturnValue;
+
+ // An error code returned by NSS. Values are defined in secerr.h.
+ long? nssErrorCode;
+ };
+
+ // Callback holding the result of a channel operation.
+ callback ChannelInfoCallback = void (ChannelInfo result);
+
+ // Callback from <code>getLogs</code> method.
+ // |log|: compressed serialized raw bytes containing the logs.
+ // The log is formatted using protocol buffer.
+ // See extensions/browser/api/cast_channel/logging.proto for definition.
+ // Compression is in gzip format.
+ callback GetLogsCallback = void (ArrayBuffer log);
+
+ // Callback from <code>setAuthorityKeys</code> method.
+ callback SetAuthorityKeysCallback = void ();
+
+ interface Functions {
+ // Opens a new channel to the Cast receiver specified by connectInfo. Only
+ // one channel may be connected to same receiver from the same extension at
+ // a time. If the open request is successful, the callback will be invoked
+ // with a ChannelInfo with readyState == 'connecting'. If unsuccessful, the
+ // callback will be invoked with a ChannelInfo with channel.readyState ==
+ // 'closed', channel.errorState will be set to the error condition, and
+ // onError will be fired with error details.
+ static void open(ConnectInfo connectInfo,
+ ChannelInfoCallback callback);
+
+ // Sends a message on the channel and invokes callback with the resulting
+ // channel status. The channel must be in readyState == 'open'. If
+ // unsuccessful, channel.readyState will be set to 'closed',
+ // channel.errorState will be set to the error condition, and onError will
+ // be fired with error details.
+ static void send(ChannelInfo channel,
+ MessageInfo message,
+ ChannelInfoCallback callback);
+
+ // Requests that the channel be closed and invokes callback with the
+ // resulting channel status. The channel must be in readyState == 'open' or
+ // 'connecting'. If successful, onClose will be fired with readyState ==
+ // 'closed'. If unsuccessful, channel.readyState will be set to 'closed',
+ // and channel.errorState will be set to the error condition.
+ static void close(ChannelInfo channel,
+ ChannelInfoCallback callback);
+
+ // Get logs in compressed serialized format. See GetLogsCallback for
+ // details.
+ // |callback|: If successful, |callback| is invoked with data. Otherwise,
+ // an error will be raised.
+ static void getLogs(GetLogsCallback callback);
+
+ // Sets trusted certificate authorities where |signature| is a base64
+ // encoded RSA-PSS signature and |keys| is base64 encoded AuthorityKeys
+ // protobuf.
+ static void setAuthorityKeys(DOMString keys,
+ DOMString signature,
+ SetAuthorityKeysCallback callback);
+ };
+
+ // Events on the channel.
+ interface Events {
+ // Fired when a message is received on an open channel.
+ static void onMessage(ChannelInfo channel,
+ MessageInfo message);
+
+ // Fired when an error occurs as a result of a channel operation or a
+ // network event. |error| contains details of the error.
+ static void onError(ChannelInfo channel, ErrorInfo error);
+ };
+};
diff --git a/chromium/extensions/common/api/cast_channel/BUILD.gn b/chromium/extensions/common/api/cast_channel/BUILD.gn
new file mode 100644
index 00000000000..475fa129193
--- /dev/null
+++ b/chromium/extensions/common/api/cast_channel/BUILD.gn
@@ -0,0 +1,14 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/protobuf/proto_library.gni")
+
+# GYP version: extensions/common/api/api.gyp:cast_channel_proto
+proto_library("cast_channel_proto") {
+ sources = [
+ "authority_keys.proto",
+ "cast_channel.proto",
+ "logging.proto",
+ ]
+}
diff --git a/chromium/extensions/common/api/cast_channel/OWNERS b/chromium/extensions/common/api/cast_channel/OWNERS
new file mode 100644
index 00000000000..94f54644c75
--- /dev/null
+++ b/chromium/extensions/common/api/cast_channel/OWNERS
@@ -0,0 +1,3 @@
+mfoltz@chromium.org
+kmarshall@chromium.org
+wez@chromium.org
diff --git a/chromium/extensions/common/api/cast_channel/authority_keys.proto b/chromium/extensions/common/api/cast_channel/authority_keys.proto
new file mode 100644
index 00000000000..791e831482e
--- /dev/null
+++ b/chromium/extensions/common/api/cast_channel/authority_keys.proto
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package extensions.api.cast_channel.proto;
+
+message AuthorityKeys {
+ message Key {
+ required bytes fingerprint = 1;
+ required bytes public_key = 2;
+ }
+ repeated Key keys = 1;
+}
diff --git a/chromium/extensions/common/api/cast_channel/cast_channel.proto b/chromium/extensions/common/api/cast_channel/cast_channel.proto
new file mode 100644
index 00000000000..1bd2877bdbe
--- /dev/null
+++ b/chromium/extensions/common/api/cast_channel/cast_channel.proto
@@ -0,0 +1,91 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package extensions.api.cast_channel;
+
+message CastMessage {
+ // Always pass a version of the protocol for future compatibility
+ // requirements.
+ enum ProtocolVersion {
+ CASTV2_1_0 = 0;
+ }
+ required ProtocolVersion protocol_version = 1;
+
+ // source and destination ids identify the origin and destination of the
+ // message. They are used to route messages between endpoints that share a
+ // device-to-device channel.
+ //
+ // For messages between applications:
+ // - The sender application id is a unique identifier generated on behalf of
+ // the sender application.
+ // - The receiver id is always the the session id for the application.
+ //
+ // For messages to or from the sender or receiver platform, the special ids
+ // 'sender-0' and 'receiver-0' can be used.
+ //
+ // For messages intended for all endpoints using a given channel, the
+ // wildcard destination_id '*' can be used.
+ required string source_id = 2;
+ required string destination_id = 3;
+
+ // This is the core multiplexing key. All messages are sent on a namespace
+ // and endpoints sharing a channel listen on one or more namespaces. The
+ // namespace defines the protocol and semantics of the message.
+ required string namespace = 4;
+
+ // Encoding and payload info follows.
+
+ // What type of data do we have in this message.
+ enum PayloadType {
+ STRING = 0;
+ BINARY = 1;
+ }
+ required PayloadType payload_type = 5;
+
+ // Depending on payload_type, exactly one of the following optional fields
+ // will always be set.
+ optional string payload_utf8 = 6;
+ optional bytes payload_binary = 7;
+}
+
+enum SignatureAlgorithm {
+ UNSPECIFIED = 0;
+ RSASSA_PKCS1v15 = 1;
+ RSASSA_PSS = 2;
+}
+
+// Messages for authentication protocol between a sender and a receiver.
+message AuthChallenge {
+ optional SignatureAlgorithm signature_algorithm = 1
+ [default = RSASSA_PKCS1v15];
+}
+
+message AuthResponse {
+ required bytes signature = 1;
+ required bytes client_auth_certificate = 2;
+ repeated bytes intermediate_certificate = 3;
+ optional SignatureAlgorithm signature_algorithm = 4
+ [default = RSASSA_PKCS1v15];
+}
+
+message AuthError {
+ enum ErrorType {
+ INTERNAL_ERROR = 0;
+ NO_TLS = 1; // The underlying connection is not TLS
+ SIGNATURE_ALGORITHM_UNAVAILABLE = 2;
+ }
+ required ErrorType error_type = 1;
+}
+
+message DeviceAuthMessage {
+ // Request fields
+ optional AuthChallenge challenge = 1;
+ // Response fields
+ optional AuthResponse response = 2;
+ optional AuthError error = 3;
+}
diff --git a/chromium/extensions/common/api/cast_channel/logging.proto b/chromium/extensions/common/api/cast_channel/logging.proto
new file mode 100644
index 00000000000..6ac52e49b15
--- /dev/null
+++ b/chromium/extensions/common/api/cast_channel/logging.proto
@@ -0,0 +1,167 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package extensions.api.cast_channel.proto;
+
+enum EventType {
+ EVENT_TYPE_UNKNOWN = 0;
+ CAST_SOCKET_CREATED = 1;
+ READY_STATE_CHANGED = 2;
+ CONNECTION_STATE_CHANGED = 3;
+ READ_STATE_CHANGED = 4;
+ WRITE_STATE_CHANGED = 5;
+ ERROR_STATE_CHANGED = 6;
+ CONNECT_FAILED = 7;
+ TCP_SOCKET_CONNECT = 8; // Logged with RV.
+ TCP_SOCKET_SET_KEEP_ALIVE = 9;
+ SSL_CERT_WHITELISTED = 10;
+ SSL_SOCKET_CONNECT = 11; // Logged with RV.
+ SSL_INFO_OBTAINED = 12;
+ DER_ENCODED_CERT_OBTAIN = 13; // Logged with RV.
+ RECEIVED_CHALLENGE_REPLY = 14;
+ AUTH_CHALLENGE_REPLY = 15;
+ CONNECT_TIMED_OUT = 16;
+ SEND_MESSAGE_FAILED = 17;
+ MESSAGE_ENQUEUED = 18; // Message
+ SOCKET_WRITE = 19; // Logged with RV.
+ MESSAGE_WRITTEN = 20; // Message
+ SOCKET_READ = 21; // Logged with RV.
+ MESSAGE_READ = 22; // Message
+ SOCKET_CLOSED = 25;
+ SSL_CERT_EXCESSIVE_LIFETIME = 26;
+ CHANNEL_POLICY_ENFORCED = 27;
+ TCP_SOCKET_CONNECT_COMPLETE = 28; // Logged with RV.
+ SSL_SOCKET_CONNECT_COMPLETE = 29; // Logged with RV.
+ SSL_SOCKET_CONNECT_FAILED = 30; // Logged with RV.
+ SEND_AUTH_CHALLENGE_FAILED = 31; // Logged with RV.
+ AUTH_CHALLENGE_REPLY_INVALID = 32;
+ PING_WRITE_ERROR = 33; // Logged with RV.
+}
+
+enum ChannelAuth {
+ // SSL over TCP.
+ SSL = 1;
+ // SSL over TCP with challenge and receiver signature verification.
+ SSL_VERIFIED = 2;
+}
+
+enum ReadyState {
+ READY_STATE_NONE = 1;
+ READY_STATE_CONNECTING = 2;
+ READY_STATE_OPEN = 3;
+ READY_STATE_CLOSING = 4;
+ READY_STATE_CLOSED = 5;
+}
+
+enum ConnectionState {
+ CONN_STATE_UNKNOWN = 1;
+ CONN_STATE_TCP_CONNECT = 2;
+ CONN_STATE_TCP_CONNECT_COMPLETE = 3;
+ CONN_STATE_SSL_CONNECT = 4;
+ CONN_STATE_SSL_CONNECT_COMPLETE = 5;
+ CONN_STATE_AUTH_CHALLENGE_SEND = 6;
+ CONN_STATE_AUTH_CHALLENGE_SEND_COMPLETE = 7;
+ CONN_STATE_AUTH_CHALLENGE_REPLY_COMPLETE = 8;
+ CONN_STATE_START_CONNECT = 9;
+
+ // Terminal states follow.
+ CONN_STATE_FINISHED = 100;
+ CONN_STATE_ERROR = 101;
+ CONN_STATE_TIMEOUT = 102;
+}
+
+enum ReadState {
+ READ_STATE_UNKNOWN = 1;
+ READ_STATE_READ = 2;
+ READ_STATE_READ_COMPLETE = 3;
+ READ_STATE_DO_CALLBACK = 4;
+ READ_STATE_HANDLE_ERROR = 5;
+ READ_STATE_ERROR = 100; // Terminal state.
+}
+
+enum WriteState {
+ WRITE_STATE_UNKNOWN = 1;
+ WRITE_STATE_WRITE = 2;
+ WRITE_STATE_WRITE_COMPLETE = 3;
+ WRITE_STATE_DO_CALLBACK = 4;
+ WRITE_STATE_HANDLE_ERROR = 5;
+
+ // Terminal states follow.
+ WRITE_STATE_ERROR = 100;
+ WRITE_STATE_IDLE = 101;
+}
+
+enum ErrorState {
+ CHANNEL_ERROR_NONE = 1;
+ CHANNEL_ERROR_CHANNEL_NOT_OPEN = 2;
+ CHANNEL_ERROR_AUTHENTICATION_ERROR = 3;
+ CHANNEL_ERROR_CONNECT_ERROR = 4;
+ CHANNEL_ERROR_SOCKET_ERROR = 5;
+ CHANNEL_ERROR_TRANSPORT_ERROR = 6;
+ CHANNEL_ERROR_INVALID_MESSAGE = 7;
+ CHANNEL_ERROR_INVALID_CHANNEL_ID = 8;
+ CHANNEL_ERROR_CONNECT_TIMEOUT = 9;
+ CHANNEL_ERROR_UNKNOWN = 10;
+}
+
+enum ChallengeReplyErrorType {
+ CHALLENGE_REPLY_ERROR_NONE = 1;
+ CHALLENGE_REPLY_ERROR_PEER_CERT_EMPTY = 2;
+ CHALLENGE_REPLY_ERROR_WRONG_PAYLOAD_TYPE = 3;
+ CHALLENGE_REPLY_ERROR_NO_PAYLOAD = 4;
+ CHALLENGE_REPLY_ERROR_PAYLOAD_PARSING_FAILED = 5;
+ CHALLENGE_REPLY_ERROR_MESSAGE_ERROR = 6;
+ CHALLENGE_REPLY_ERROR_NO_RESPONSE = 7;
+ CHALLENGE_REPLY_ERROR_FINGERPRINT_NOT_FOUND = 8;
+ CHALLENGE_REPLY_ERROR_CERT_PARSING_FAILED = 9;
+ CHALLENGE_REPLY_ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA = 10;
+ CHALLENGE_REPLY_ERROR_CANNOT_EXTRACT_PUBLIC_KEY = 11;
+ CHALLENGE_REPLY_ERROR_SIGNED_BLOBS_MISMATCH = 12;
+}
+
+message SocketEvent {
+ // Required
+ optional EventType type = 1;
+ optional int64 timestamp_micros = 2;
+
+ optional string details = 3;
+
+ optional int32 net_return_value = 4;
+
+ optional string message_namespace = 5;
+
+ optional ReadyState ready_state = 6;
+ optional ConnectionState connection_state = 7;
+ optional ReadState read_state = 8;
+ optional WriteState write_state = 9;
+ optional ErrorState error_state = 10;
+
+ optional ChallengeReplyErrorType challenge_reply_error_type = 11;
+ // No longer used.
+ optional int32 nss_error_code = 12;
+}
+
+message AggregatedSocketEvent {
+ optional int32 id = 1;
+ optional int32 endpoint_id = 2;
+ optional ChannelAuth channel_auth_type = 3;
+ repeated SocketEvent socket_event = 4;
+ optional int64 bytes_read = 5;
+ optional int64 bytes_written = 6;
+}
+
+message Log {
+ // Each AggregatedSocketEvent represents events recorded for a socket.
+ repeated AggregatedSocketEvent aggregated_socket_event = 1;
+
+ // Number of socket log entries evicted by the logger due to size constraints.
+ optional int32 num_evicted_aggregated_socket_events = 2;
+
+ // Number of event log entries evicted by the logger due to size constraints.
+ optional int32 num_evicted_socket_events = 3;
+}
diff --git a/chromium/extensions/common/api/declarative/declarative_manifest_data.cc b/chromium/extensions/common/api/declarative/declarative_manifest_data.cc
new file mode 100644
index 00000000000..77460f1a8d5
--- /dev/null
+++ b/chromium/extensions/common/api/declarative/declarative_manifest_data.cc
@@ -0,0 +1,181 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/api/declarative/declarative_manifest_data.h"
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/common/manifest_constants.h"
+
+using base::UTF8ToUTF16;
+using base::StringPrintf;
+
+namespace extensions {
+
+namespace {
+
+const char* ValueTypeToString(const base::Value* value) {
+ const base::Value::Type type = value->GetType();
+ static const char* strings[] = {"null",
+ "boolean",
+ "integer",
+ "double",
+ "string",
+ "binary",
+ "dictionary",
+ "list"};
+ CHECK(static_cast<size_t>(type) < arraysize(strings));
+ return strings[type];
+}
+
+class ErrorBuilder {
+ public:
+ explicit ErrorBuilder(base::string16* error) : error_(error) {}
+
+ // Appends a literal string |error|.
+ void Append(const char* error) {
+ if (error_->length())
+ error_->append(UTF8ToUTF16("; "));
+ error_->append(UTF8ToUTF16(error));
+ }
+
+ // Appends a string |error| with the first %s replaced by |sub|.
+ void Append(const char* error, const char* sub) {
+ Append(base::StringPrintf(error, sub).c_str());
+ }
+
+ private:
+ base::string16* error_;
+ DISALLOW_COPY_AND_ASSIGN(ErrorBuilder);
+};
+
+// Converts a rule defined in the manifest into a JSON internal format. The
+// difference is that actions and conditions use a "type" key to define the
+// type of rule/condition, while the internal format uses a "instanceType" key
+// for this. This function walks through all the conditions and rules to swap
+// the manifest key for the internal key.
+bool ConvertManifestRule(const linked_ptr<DeclarativeManifestData::Rule>& rule,
+ ErrorBuilder* error_builder) {
+ auto convert_list =
+ [error_builder](std::vector<scoped_ptr<base::Value>>& list) {
+ for (const scoped_ptr<base::Value>& value : list) {
+ base::DictionaryValue* dictionary = nullptr;
+ if (!value->GetAsDictionary(&dictionary)) {
+ error_builder->Append("expected dictionary, got %s",
+ ValueTypeToString(value.get()));
+ return false;
+ }
+ std::string type;
+ if (!dictionary->GetString("type", &type)) {
+ error_builder->Append("'type' is required and must be a string");
+ return false;
+ }
+ dictionary->Remove("type", nullptr);
+ dictionary->SetString("instanceType", type);
+ }
+ return true;
+ };
+ return convert_list(rule->actions) && convert_list(rule->conditions);
+}
+
+} // namespace
+
+DeclarativeManifestData::DeclarativeManifestData() {
+}
+
+DeclarativeManifestData::~DeclarativeManifestData() {
+}
+
+// static
+DeclarativeManifestData* DeclarativeManifestData::Get(
+ const Extension* extension) {
+ return static_cast<DeclarativeManifestData*>(
+ extension->GetManifestData(manifest_keys::kEventRules));
+}
+
+// static
+scoped_ptr<DeclarativeManifestData> DeclarativeManifestData::FromValue(
+ const base::Value& value,
+ base::string16* error) {
+ // The following is an example of how an event programmatic rule definition
+ // translates to a manifest definition.
+ //
+ // From javascript:
+ //
+ // chrome.declarativeContent.onPageChanged.addRules([{
+ // actions: [
+ // new chrome.declarativeContent.ShowPageAction()
+ // ],
+ // conditions: [
+ // new chrome.declarativeContent.PageStateMatcher({css: ["video"]})
+ // ]
+ // }]);
+ //
+ // In manifest:
+ //
+ // "event_rules": [{
+ // "event" : "declarativeContent.onPageChanged",
+ // "actions" : [{
+ // "type": "declarativeContent.ShowPageAction"
+ // }],
+ // "conditions" : [{
+ // "css": ["video"],
+ // "type" : "declarativeContent.PageStateMatcher"
+ // }]
+ // }]
+ //
+ // The javascript objects get translated into JSON objects with a "type"
+ // field to indicate the instance type. Instead of adding rules to a
+ // specific event list, each rule has an "event" field to indicate which
+ // event it applies to.
+ //
+ ErrorBuilder error_builder(error);
+ scoped_ptr<DeclarativeManifestData> result(new DeclarativeManifestData());
+ const base::ListValue* list = nullptr;
+ if (!value.GetAsList(&list)) {
+ error_builder.Append("'event_rules' expected list, got %s",
+ ValueTypeToString(&value));
+ return scoped_ptr<DeclarativeManifestData>();
+ }
+
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ const base::DictionaryValue* dict = nullptr;
+ if (!list->GetDictionary(i, &dict)) {
+ const base::Value* value = nullptr;
+ if (list->Get(i, &value))
+ error_builder.Append("expected dictionary, got %s",
+ ValueTypeToString(value));
+ else
+ error_builder.Append("expected dictionary");
+ return scoped_ptr<DeclarativeManifestData>();
+ }
+ std::string event;
+ if (!dict->GetString("event", &event)) {
+ error_builder.Append("'event' is required");
+ return scoped_ptr<DeclarativeManifestData>();
+ }
+
+ linked_ptr<Rule> rule(new Rule());
+ if (!Rule::Populate(*dict, rule.get())) {
+ error_builder.Append("rule failed to populate");
+ return scoped_ptr<DeclarativeManifestData>();
+ }
+
+ if (!ConvertManifestRule(rule, &error_builder))
+ return scoped_ptr<DeclarativeManifestData>();
+
+ result->event_rules_map_[event].push_back(rule);
+ }
+ return result;
+}
+
+std::vector<linked_ptr<DeclarativeManifestData::Rule>>&
+DeclarativeManifestData::RulesForEvent(const std::string& event) {
+ return event_rules_map_[event];
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/api/declarative/declarative_manifest_data.h b/chromium/extensions/common/api/declarative/declarative_manifest_data.h
new file mode 100644
index 00000000000..d3147044d19
--- /dev/null
+++ b/chromium/extensions/common/api/declarative/declarative_manifest_data.h
@@ -0,0 +1,46 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_API_DECLARATIVE_DECLARATIVE_MANIFEST_DATA_H_
+#define EXTENSIONS_COMMON_API_DECLARATIVE_DECLARATIVE_MANIFEST_DATA_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/strings/string16.h"
+#include "extensions/common/api/events.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+// The parsed form of the "event_rules" manifest entry.
+class DeclarativeManifestData : public Extension::ManifestData {
+ public:
+ typedef extensions::api::events::Rule Rule;
+
+ DeclarativeManifestData();
+ ~DeclarativeManifestData() override;
+
+ // Gets the DeclarativeManifestData for |extension|, or NULL if none was
+ // specified.
+ static DeclarativeManifestData* Get(const Extension* extension);
+
+ // Tries to construct the info based on |value|, as it would have appeared in
+ // the manifest. Sets |error| and returns an empty scoped_ptr on failure.
+ static scoped_ptr<DeclarativeManifestData> FromValue(const base::Value& value,
+ base::string16* error);
+
+ std::vector<linked_ptr<DeclarativeManifestData::Rule>>& RulesForEvent(
+ const std::string& event);
+
+ private:
+ std::map<std::string, std::vector<linked_ptr<Rule>>> event_rules_map_;
+ DISALLOW_COPY_AND_ASSIGN(DeclarativeManifestData);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_API_DECLARATIVE_DECLARATIVE_MANIFEST_DATA_H_
diff --git a/chromium/extensions/common/api/declarative/declarative_manifest_handler.cc b/chromium/extensions/common/api/declarative/declarative_manifest_handler.cc
new file mode 100644
index 00000000000..b96ddf68ad6
--- /dev/null
+++ b/chromium/extensions/common/api/declarative/declarative_manifest_handler.cc
@@ -0,0 +1,36 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/api/declarative/declarative_manifest_handler.h"
+
+#include "extensions/common/api/declarative/declarative_manifest_data.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+DeclarativeManifestHandler::DeclarativeManifestHandler() {
+}
+
+DeclarativeManifestHandler::~DeclarativeManifestHandler() {
+}
+
+bool DeclarativeManifestHandler::Parse(Extension* extension,
+ base::string16* error) {
+ const base::Value* event_rules = NULL;
+ CHECK(extension->manifest()->Get(manifest_keys::kEventRules, &event_rules));
+ scoped_ptr<DeclarativeManifestData> data =
+ DeclarativeManifestData::FromValue(*event_rules, error);
+ if (!data)
+ return false;
+
+ extension->SetManifestData(manifest_keys::kEventRules, data.release());
+ return true;
+}
+
+const std::vector<std::string> DeclarativeManifestHandler::Keys() const {
+ return SingleKey(manifest_keys::kEventRules);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/api/declarative/declarative_manifest_handler.h b/chromium/extensions/common/api/declarative/declarative_manifest_handler.h
new file mode 100644
index 00000000000..3107a3d8dba
--- /dev/null
+++ b/chromium/extensions/common/api/declarative/declarative_manifest_handler.h
@@ -0,0 +1,39 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_API_DECLARATIVE_DECLARATIVE_MANIFEST_HANDLER_H_
+#define EXTENSIONS_COMMON_API_DECLARATIVE_DECLARATIVE_MANIFEST_HANDLER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+class Extension;
+class ManifestPermission;
+}
+
+namespace extensions {
+
+// Parses the "event_rules" manifest key.
+class DeclarativeManifestHandler : public ManifestHandler {
+ public:
+ DeclarativeManifestHandler();
+ ~DeclarativeManifestHandler() override;
+
+ // ManifestHandler overrides.
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+ // ManifestHandler overrides.
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(DeclarativeManifestHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_API_DECLARATIVE_DECLARATIVE_MANIFEST_HANDLER_H_
diff --git a/chromium/extensions/common/api/declarative/declarative_manifest_unittest.cc b/chromium/extensions/common/api/declarative/declarative_manifest_unittest.cc
new file mode 100644
index 00000000000..fc785e5c587
--- /dev/null
+++ b/chromium/extensions/common/api/declarative/declarative_manifest_unittest.cc
@@ -0,0 +1,182 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <utility>
+
+#include "extensions/browser/api_test_utils.h"
+#include "extensions/common/api/declarative/declarative_manifest_data.h"
+#include "extensions/common/manifest_test.h"
+
+namespace extensions {
+
+using api_test_utils::ParseDictionary;
+using DeclarativeManifestTest = ManifestTest;
+
+TEST_F(DeclarativeManifestTest, Valid) {
+ scoped_refptr<Extension> extension = LoadAndExpectSuccess("event_rules.json");
+ DeclarativeManifestData* manifest_data =
+ DeclarativeManifestData::Get(extension.get());
+ ASSERT_TRUE(manifest_data);
+ std::vector<linked_ptr<DeclarativeManifestData::Rule>>& rules =
+ manifest_data->RulesForEvent("foo");
+ EXPECT_EQ(1u, rules.size());
+ scoped_ptr<base::DictionaryValue> expected_rule = ParseDictionary(
+ "{"
+ " \"actions\": [{"
+ " \"instanceType\": \"action_type\""
+ " }],"
+ " \"conditions\" : [{"
+ " \"instanceType\" : \"condition_type\""
+ " }]"
+ "}");
+ EXPECT_TRUE(expected_rule->Equals(rules[0]->ToValue().get()));
+}
+
+TEST_F(DeclarativeManifestTest, ConditionMissingType) {
+ // Create extension
+ scoped_ptr<base::DictionaryValue> manifest_data = ParseDictionary(
+ "{"
+ " \"name\": \"Test\","
+ " \"version\": \"1\","
+ " \"event_rules\": ["
+ " {"
+ " \"event\": \"declarativeContent.onPageChanged\","
+ " \"actions\": [{"
+ " \"type\": \"declarativeContent.ShowPageAction\""
+ " }],"
+ " \"conditions\" : [{"
+ " \"css\": [\"video\"]"
+ " }]"
+ " }"
+ " ]"
+ "}");
+ ManifestData manifest(std::move(manifest_data), "test");
+ LoadAndExpectError(manifest, "'type' is required and must be a string");
+}
+
+TEST_F(DeclarativeManifestTest, ConditionNotDictionary) {
+ // Create extension
+ scoped_ptr<base::DictionaryValue> manifest_data = ParseDictionary(
+ "{"
+ " \"name\": \"Test\","
+ " \"version\": \"1\","
+ " \"event_rules\": ["
+ " {"
+ " \"event\": \"declarativeContent.onPageChanged\","
+ " \"actions\": [{"
+ " \"type\": \"declarativeContent.ShowPageAction\""
+ " }],"
+ " \"conditions\" : [true]"
+ " }"
+ " ]"
+ "}");
+ ManifestData manifest(std::move(manifest_data), "test");
+ LoadAndExpectError(manifest, "expected dictionary, got boolean");
+}
+
+TEST_F(DeclarativeManifestTest, ActionMissingType) {
+ // Create extension
+ scoped_ptr<base::DictionaryValue> manifest_data = ParseDictionary(
+ "{"
+ " \"name\": \"Test\","
+ " \"version\": \"1\","
+ " \"event_rules\": ["
+ " {"
+ " \"event\": \"declarativeContent.onPageChanged\","
+ " \"actions\": [{}],"
+ " \"conditions\" : [{"
+ " \"css\": [\"video\"],"
+ " \"type\" : \"declarativeContent.PageStateMatcher\""
+ " }]"
+ " }"
+ " ]"
+ "}");
+ ManifestData manifest(std::move(manifest_data), "test");
+ LoadAndExpectError(manifest, "'type' is required and must be a string");
+}
+
+TEST_F(DeclarativeManifestTest, ActionNotDictionary) {
+ // Create extension
+ scoped_ptr<base::DictionaryValue> manifest_data = ParseDictionary(
+ "{"
+ " \"name\": \"Test\","
+ " \"version\": \"1\","
+ " \"event_rules\": ["
+ " {"
+ " \"event\": \"declarativeContent.onPageChanged\","
+ " \"actions\": [[]],"
+ " \"conditions\" : [{"
+ " \"css\": [\"video\"],"
+ " \"type\" : \"declarativeContent.PageStateMatcher\""
+ " }]"
+ " }"
+ " ]"
+ "}");
+ ManifestData manifest(std::move(manifest_data), "test");
+ LoadAndExpectError(manifest, "expected dictionary, got list");
+}
+
+TEST_F(DeclarativeManifestTest, EventRulesNotList) {
+ // Create extension
+ scoped_ptr<base::DictionaryValue> manifest_data = ParseDictionary(
+ "{"
+ " \"name\": \"Test\","
+ " \"version\": \"1\","
+ " \"event_rules\": {}"
+ "}");
+ ManifestData manifest(std::move(manifest_data), "test");
+ LoadAndExpectError(manifest, "'event_rules' expected list, got dictionary");
+}
+
+TEST_F(DeclarativeManifestTest, EventRuleNotDictionary) {
+ // Create extension
+ scoped_ptr<base::DictionaryValue> manifest_data = ParseDictionary(
+ "{"
+ " \"name\": \"Test\","
+ " \"version\": \"1\","
+ " \"event_rules\": [0,1,2]"
+ "}");
+ ManifestData manifest(std::move(manifest_data), "test");
+ LoadAndExpectError(manifest, "expected dictionary, got integer");
+}
+
+TEST_F(DeclarativeManifestTest, EventMissingFromRule) {
+ // Create extension
+ scoped_ptr<base::DictionaryValue> manifest_data = ParseDictionary(
+ "{"
+ " \"name\": \"Test\","
+ " \"version\": \"1\","
+ " \"event_rules\": ["
+ " {"
+ " \"actions\": [{"
+ " \"type\": \"declarativeContent.ShowPageAction\""
+ " }],"
+ " \"conditions\" : [{"
+ " \"css\": [\"video\"],"
+ " \"type\" : \"declarativeContent.PageStateMatcher\""
+ " }]"
+ " }"
+ " ]"
+ "}");
+ ManifestData manifest(std::move(manifest_data), "test");
+ LoadAndExpectError(manifest, "'event' is required");
+}
+
+TEST_F(DeclarativeManifestTest, RuleFailedToPopulate) {
+ // Create extension
+ scoped_ptr<base::DictionaryValue> manifest_data = ParseDictionary(
+ "{"
+ " \"name\": \"Test\","
+ " \"version\": \"1\","
+ " \"event_rules\": ["
+ " {"
+ " \"event\": \"declarativeContent.onPageChanged\""
+ " }"
+ " ]"
+ "}");
+ ManifestData manifest(std::move(manifest_data), "test");
+ LoadAndExpectError(manifest, "rule failed to populate");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/api/declarative_web_request.json b/chromium/extensions/common/api/declarative_web_request.json
new file mode 100644
index 00000000000..f504070bb23
--- /dev/null
+++ b/chromium/extensions/common/api/declarative_web_request.json
@@ -0,0 +1,597 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "declarativeWebRequest",
+ "description": "<em><strong>Note:</strong> this API is currently on hold, without concrete plans to move to stable.</em> Use the <code>chrome.declarativeWebRequest</code> API to intercept, block, or modify requests in-flight. It is significantly faster than the <a href='webRequest'><code>chrome.webRequest</code> API</a> because you can register rules that are evaluated in the browser rather than the JavaScript engine with reduces roundtrip latencies and allows higher efficiency.",
+ "types": [
+ {
+ "id": "HeaderFilter",
+ "type": "object",
+ "description": "Filters request headers for various criteria. Multiple criteria are evaluated as a conjunction.",
+ "properties": {
+ "namePrefix": {
+ "description" : "Matches if the header name starts with the specified string.",
+ "type": "string",
+ "optional": true
+ },
+ "nameSuffix": {
+ "type": "string",
+ "optional": true,
+ "description" : "Matches if the header name ends with the specified string."
+ },
+ "nameContains": {
+ "choices": [
+ {"type": "array", "items": {"type": "string"}},
+ {"type": "string"}
+ ],
+ "optional": true,
+ "description" : "Matches if the header name contains all of the specified strings."
+ },
+ "nameEquals": {
+ "type": "string",
+ "optional": true,
+ "description" : "Matches if the header name is equal to the specified string."
+ },
+ "valuePrefix": {
+ "type": "string",
+ "optional": true,
+ "description" : "Matches if the header value starts with the specified string."
+ },
+ "valueSuffix": {
+ "type": "string",
+ "optional": true,
+ "description" : "Matches if the header value ends with the specified string."
+ },
+ "valueContains": {
+ "choices": [
+ {"type": "array", "items": {"type": "string"}},
+ {"type": "string"}
+ ],
+ "optional": true,
+ "description" : "Matches if the header value contains all of the specified strings."
+ },
+ "valueEquals": {
+ "type": "string",
+ "optional": true,
+ "description" : "Matches if the header value is equal to the specified string."
+ }
+ }
+ },
+ {
+ "id": "RequestMatcher",
+ "type": "object",
+ "description": "Matches network events by various criteria.",
+ "properties": {
+ "url": {
+ "$ref": "events.UrlFilter",
+ "description": "Matches if the conditions of the UrlFilter are fulfilled for the URL of the request.",
+ "optional": true
+ },
+ "firstPartyForCookiesUrl": {
+ "$ref": "events.UrlFilter",
+ "description": "Matches if the conditions of the UrlFilter are fulfilled for the 'first party' URL of the request. The 'first party' URL of a request, when present, can be different from the request's target URL, and describes what is considered 'first party' for the sake of third-party checks for cookies.",
+ "optional": true
+ },
+ "resourceType": {
+ "type": "array",
+ "optional": true,
+ "description": "Matches if the request type of a request is contained in the list. Requests that cannot match any of the types will be filtered out.",
+ "items": { "type": "string", "enum": ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "object", "xmlhttprequest", "ping", "other"] }
+ },
+ "contentType": {
+ "type": "array",
+ "optional": true,
+ "description": "Matches if the MIME media type of a response (from the HTTP Content-Type header) is contained in the list.",
+ "items": { "type": "string" }
+ },
+ "excludeContentType": {
+ "type": "array",
+ "optional": true,
+ "description": "Matches if the MIME media type of a response (from the HTTP Content-Type header) is <em>not</em> contained in the list.",
+ "items": { "type": "string" }
+ },
+ "requestHeaders": {
+ "type": "array",
+ "optional": true,
+ "description": "Matches if some of the request headers is matched by one of the HeaderFilters.",
+ "items": { "$ref": "HeaderFilter" }
+ },
+ "excludeRequestHeaders": {
+ "type": "array",
+ "optional": true,
+ "description": "Matches if none of the request headers is matched by any of the HeaderFilters.",
+ "items": { "$ref": "HeaderFilter" }
+ },
+ "responseHeaders": {
+ "type": "array",
+ "optional": true,
+ "description": "Matches if some of the response headers is matched by one of the HeaderFilters.",
+ "items": { "$ref": "HeaderFilter" }
+ },
+ "excludeResponseHeaders": {
+ "type": "array",
+ "optional": true,
+ "description": "Matches if none of the response headers is matched by any of the HeaderFilters.",
+ "items": { "$ref": "HeaderFilter" }
+ },
+ "thirdPartyForCookies": {
+ "type": "boolean",
+ "optional": true,
+ "description": "If set to true, matches requests that are subject to third-party cookie policies. If set to false, matches all other requests."
+ },
+ "stages": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": ["onBeforeRequest", "onBeforeSendHeaders", "onHeadersReceived", "onAuthRequired"]
+ },
+ "optional": true,
+ "description": "Contains a list of strings describing stages. Allowed values are 'onBeforeRequest', 'onBeforeSendHeaders', 'onHeadersReceived', 'onAuthRequired'. If this attribute is present, then it limits the applicable stages to those listed. Note that the whole condition is only applicable in stages compatible with all attributes."
+ },
+ "instanceType": {
+ "type": "string", "enum": ["declarativeWebRequest.RequestMatcher"],
+ "nodoc": true
+ }
+ }
+ },
+ {
+ "id": "CancelRequest",
+ "description": "Declarative event action that cancels a network request.",
+ "type": "object",
+ "properties": {
+ "instanceType": {
+ "type": "string", "enum": ["declarativeWebRequest.CancelRequest"],
+ "nodoc": true
+ }
+ }
+ },
+ {
+ "id": "RedirectRequest",
+ "description": "Declarative event action that redirects a network request.",
+ "type": "object",
+ "properties": {
+ "instanceType": {
+ "type": "string", "enum": ["declarativeWebRequest.RedirectRequest"],
+ "nodoc": true
+ },
+ "redirectUrl": { "type": "string", "description": "Destination to where the request is redirected."}
+ }
+ },
+ {
+ "id": "declarativeWebRequest.RedirectToTransparentImage",
+ "description": "Declarative event action that redirects a network request to a transparent image.",
+ "type": "object",
+ "properties": {
+ "instanceType": {
+ "type": "string", "enum": ["declarativeWebRequest.RedirectToTransparentImage"],
+ "nodoc": true
+ }
+ }
+ },
+ {
+ "id": "declarativeWebRequest.RedirectToEmptyDocument",
+ "description": "Declarative event action that redirects a network request to an empty document.",
+ "type": "object",
+ "properties": {
+ "instanceType": {
+ "type": "string", "enum": ["declarativeWebRequest.RedirectToEmptyDocument"],
+ "nodoc": true
+ }
+ }
+ },
+ {
+ "id": "declarativeWebRequest.RedirectByRegEx",
+ "description": "Redirects a request by applying a regular expression on the URL. The regular expressions use the <a href=\"https://github.com/google/re2/blob/master/doc/syntax.txt\">RE2 syntax</a>.",
+ "type": "object",
+ "properties": {
+ "instanceType": {
+ "type": "string", "enum": ["declarativeWebRequest.RedirectByRegEx"],
+ "nodoc": true
+ },
+ "from": {
+ "type": "string",
+ "description": "A match pattern that may contain capture groups. Capture groups are referenced in the Perl syntax ($1, $2, ...) instead of the RE2 syntax (\\1, \\2, ...) in order to be closer to JavaScript Regular Expressions."
+ },
+ "to": {
+ "type": "string",
+ "description": "Destination pattern."
+ }
+ }
+ },
+ {
+ "id": "declarativeWebRequest.SetRequestHeader",
+ "description": "Sets the request header of the specified name to the specified value. If a header with the specified name did not exist before, a new one is created. Header name comparison is always case-insensitive. Each request header name occurs only once in each request.",
+ "type": "object",
+ "properties": {
+ "instanceType": {
+ "type": "string", "enum": ["declarativeWebRequest.SetRequestHeader"],
+ "nodoc": true
+ },
+ "name": {
+ "type": "string",
+ "description": "HTTP request header name."
+ },
+ "value": {
+ "type": "string",
+ "description": "HTTP request header value."
+ }
+ }
+ },
+ {
+ "id": "declarativeWebRequest.RemoveRequestHeader",
+ "description": "Removes the request header of the specified name. Do not use SetRequestHeader and RemoveRequestHeader with the same header name on the same request. Each request header name occurs only once in each request.",
+ "type": "object",
+ "properties": {
+ "instanceType": {
+ "type": "string", "enum": ["declarativeWebRequest.RemoveRequestHeader"],
+ "nodoc": true
+ },
+ "name": {
+ "type": "string",
+ "description": "HTTP request header name (case-insensitive)."
+ }
+ }
+ },
+ {
+ "id": "declarativeWebRequest.AddResponseHeader",
+ "description": "Adds the response header to the response of this web request. As multiple response headers may share the same name, you need to first remove and then add a new response header in order to replace one.",
+ "type": "object",
+ "properties": {
+ "instanceType": {
+ "type": "string", "enum": ["declarativeWebRequest.AddResponseHeader"],
+ "nodoc": true
+ },
+ "name": {
+ "type": "string",
+ "description": "HTTP response header name."
+ },
+ "value": {
+ "type": "string",
+ "description": "HTTP response header value."
+ }
+ }
+ },
+ {
+ "id": "declarativeWebRequest.RemoveResponseHeader",
+ "description": "Removes all response headers of the specified names and values.",
+ "type": "object",
+ "properties": {
+ "instanceType": {
+ "type": "string", "enum": ["declarativeWebRequest.RemoveResponseHeader"],
+ "nodoc": true
+ },
+ "name": {
+ "type": "string",
+ "description": "HTTP request header name (case-insensitive)."
+ },
+ "value": {
+ "type": "string",
+ "description": "HTTP request header value (case-insensitive).",
+ "optional": true
+ }
+ }
+ },
+ {
+ "id": "declarativeWebRequest.IgnoreRules",
+ "description": "Masks all rules that match the specified criteria.",
+ "type": "object",
+ "properties": {
+ "instanceType": {
+ "type": "string", "enum": ["declarativeWebRequest.IgnoreRules"],
+ "nodoc": true
+ },
+ "lowerPriorityThan": {
+ "type": "integer",
+ "description": "If set, rules with a lower priority than the specified value are ignored. This boundary is not persisted, it affects only rules and their actions of the same network request stage.",
+ "optional": true
+ },
+ "hasTag": {
+ "type": "string",
+ "description": "If set, rules with the specified tag are ignored. This ignoring is not persisted, it affects only rules and their actions of the same network request stage. Note that rules are executed in descending order of their priorities. This action affects rules of lower priority than the current rule. Rules with the same priority may or may not be ignored.",
+ "optional": true
+ }
+ }
+ },
+ {
+ "id": "declarativeWebRequest.SendMessageToExtension",
+ "description": "Triggers the $(ref:declarativeWebRequest.onMessage) event.",
+ "type": "object",
+ "properties": {
+ "instanceType": {
+ "type": "string", "enum": ["declarativeWebRequest.SendMessageToExtension"],
+ "nodoc": true
+ },
+ "message": {
+ "type": "string",
+ "description": "The value that will be passed in the <code>message</code> attribute of the dictionary that is passed to the event handler."
+ }
+ }
+ },
+ {
+ "id": "declarativeWebRequest.RequestCookie",
+ "description": "A filter or specification of a cookie in HTTP Requests.",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Name of a cookie.",
+ "optional": true
+ },
+ "value": {
+ "type": "string",
+ "description": "Value of a cookie, may be padded in double-quotes.",
+ "optional": true
+ }
+ }
+ },
+ {
+ "id": "declarativeWebRequest.ResponseCookie",
+ "description": "A specification of a cookie in HTTP Responses.",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Name of a cookie.",
+ "optional": true
+ },
+ "value": {
+ "type": "string",
+ "description": "Value of a cookie, may be padded in double-quotes.",
+ "optional": true
+ },
+ "expires": {
+ "type": "string",
+ "description": "Value of the Expires cookie attribute.",
+ "optional": true
+ },
+ "maxAge": {
+ "type": "number",
+ "description": "Value of the Max-Age cookie attribute",
+ "optional": true
+ },
+ "domain": {
+ "type": "string",
+ "description": "Value of the Domain cookie attribute.",
+ "optional": true
+ },
+ "path": {
+ "type": "string",
+ "description": "Value of the Path cookie attribute.",
+ "optional": true
+ },
+ "secure": {
+ "type": "string",
+ "description": "Existence of the Secure cookie attribute.",
+ "optional": true
+ },
+ "httpOnly": {
+ "type": "string",
+ "description": "Existence of the HttpOnly cookie attribute.",
+ "optional": true
+ }
+ }
+ },
+ {
+ "id": "declarativeWebRequest.FilterResponseCookie",
+ "description": "A filter of a cookie in HTTP Responses.",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Name of a cookie.",
+ "optional": true
+ },
+ "value": {
+ "type": "string",
+ "description": "Value of a cookie, may be padded in double-quotes.",
+ "optional": true
+ },
+ "expires": {
+ "type": "string",
+ "description": "Value of the Expires cookie attribute.",
+ "optional": true
+ },
+ "maxAge": {
+ "type": "number",
+ "description": "Value of the Max-Age cookie attribute",
+ "optional": true
+ },
+ "domain": {
+ "type": "string",
+ "description": "Value of the Domain cookie attribute.",
+ "optional": true
+ },
+ "path": {
+ "type": "string",
+ "description": "Value of the Path cookie attribute.",
+ "optional": true
+ },
+ "secure": {
+ "type": "string",
+ "description": "Existence of the Secure cookie attribute.",
+ "optional": true
+ },
+ "httpOnly": {
+ "type": "string",
+ "description": "Existence of the HttpOnly cookie attribute.",
+ "optional": true
+ },
+ "ageUpperBound": {
+ "type": "integer",
+ "description": "Inclusive upper bound on the cookie lifetime (specified in seconds after current time). Only cookies whose expiration date-time is in the interval [now, now + ageUpperBound] fulfill this criterion. Session cookies and cookies whose expiration date-time is in the past do not meet the criterion of this filter. The cookie lifetime is calculated from either 'max-age' or 'expires' cookie attributes. If both are specified, 'max-age' is used to calculate the cookie lifetime.",
+ "minimum": 0,
+ "optional": true
+ },
+ "ageLowerBound": {
+ "type": "integer",
+ "description": "Inclusive lower bound on the cookie lifetime (specified in seconds after current time). Only cookies whose expiration date-time is set to 'now + ageLowerBound' or later fulfill this criterion. Session cookies do not meet the criterion of this filter. The cookie lifetime is calculated from either 'max-age' or 'expires' cookie attributes. If both are specified, 'max-age' is used to calculate the cookie lifetime.",
+ "minimum": 0,
+ "optional": true
+ },
+ "sessionCookie": {
+ "type": "boolean",
+ "description": "Filters session cookies. Session cookies have no lifetime specified in any of 'max-age' or 'expires' attributes.",
+ "optional": true
+ }
+ }
+ },
+ {
+ "id": "declarativeWebRequest.AddRequestCookie",
+ "description": "Adds a cookie to the request or overrides a cookie, in case another cookie of the same name exists already. Note that it is preferred to use the Cookies API because this is computationally less expensive.",
+ "type": "object",
+ "properties": {
+ "instanceType": {
+ "type": "string", "enum": ["declarativeWebRequest.AddRequestCookie"],
+ "nodoc": true
+ },
+ "cookie": {
+ "$ref": "declarativeWebRequest.RequestCookie",
+ "description": "Cookie to be added to the request. No field may be undefined."
+ }
+ }
+ },
+ {
+ "id": "declarativeWebRequest.AddResponseCookie",
+ "description": "Adds a cookie to the response or overrides a cookie, in case another cookie of the same name exists already. Note that it is preferred to use the Cookies API because this is computationally less expensive.",
+ "type": "object",
+ "properties": {
+ "instanceType": {
+ "type": "string", "enum": ["declarativeWebRequest.AddResponseCookie"],
+ "nodoc": true
+ },
+ "cookie": {
+ "$ref": "declarativeWebRequest.ResponseCookie",
+ "description": "Cookie to be added to the response. The name and value need to be specified."
+ }
+ }
+ },
+ {
+ "id": "declarativeWebRequest.EditRequestCookie",
+ "description": "Edits one or more cookies of request. Note that it is preferred to use the Cookies API because this is computationally less expensive.",
+ "type": "object",
+ "properties": {
+ "instanceType": {
+ "type": "string", "enum": ["declarativeWebRequest.EditRequestCookie"],
+ "nodoc": true
+ },
+ "filter": {
+ "$ref": "declarativeWebRequest.RequestCookie",
+ "description": "Filter for cookies that will be modified. All empty entries are ignored."
+ },
+ "modification": {
+ "$ref": "declarativeWebRequest.RequestCookie",
+ "description": "Attributes that shall be overridden in cookies that machted the filter. Attributes that are set to an empty string are removed."
+ }
+ }
+ },
+ {
+ "id": "declarativeWebRequest.EditResponseCookie",
+ "description": "Edits one or more cookies of response. Note that it is preferred to use the Cookies API because this is computationally less expensive.",
+ "type": "object",
+ "properties": {
+ "instanceType": {
+ "type": "string", "enum": ["declarativeWebRequest.EditResponseCookie"],
+ "nodoc": true
+ },
+ "filter": {
+ "$ref": "declarativeWebRequest.FilterResponseCookie",
+ "description": "Filter for cookies that will be modified. All empty entries are ignored."
+ },
+ "modification": {
+ "$ref": "declarativeWebRequest.ResponseCookie",
+ "description": "Attributes that shall be overridden in cookies that machted the filter. Attributes that are set to an empty string are removed."
+ }
+ }
+ },
+ {
+ "id": "declarativeWebRequest.RemoveRequestCookie",
+ "description": "Removes one or more cookies of request. Note that it is preferred to use the Cookies API because this is computationally less expensive.",
+ "type": "object",
+ "properties": {
+ "instanceType": {
+ "type": "string", "enum": ["declarativeWebRequest.RemoveRequestCookie"],
+ "nodoc": true
+ },
+ "filter": {
+ "$ref": "declarativeWebRequest.RequestCookie",
+ "description": "Filter for cookies that will be removed. All empty entries are ignored."
+ }
+ }
+ },
+ {
+ "id": "declarativeWebRequest.RemoveResponseCookie",
+ "description": "Removes one or more cookies of response. Note that it is preferred to use the Cookies API because this is computationally less expensive.",
+ "type": "object",
+ "properties": {
+ "instanceType": {
+ "type": "string", "enum": ["declarativeWebRequest.RemoveResponseCookie"],
+ "nodoc": true
+ },
+ "filter": {
+ "$ref": "declarativeWebRequest.FilterResponseCookie",
+ "description": "Filter for cookies that will be removed. All empty entries are ignored."
+ }
+ }
+ }
+ ],
+ "functions": [
+ ],
+ "events": [
+ {
+ "name": "onRequest",
+ "options": {
+ "supportsListeners": false,
+ "supportsRules": true,
+ "conditions": ["declarativeWebRequest.RequestMatcher"],
+ "actions": [
+ "declarativeWebRequest.AddRequestCookie",
+ "declarativeWebRequest.AddResponseCookie",
+ "declarativeWebRequest.AddResponseHeader",
+ "declarativeWebRequest.CancelRequest",
+ "declarativeWebRequest.EditRequestCookie",
+ "declarativeWebRequest.EditResponseCookie",
+ "declarativeWebRequest.RedirectRequest",
+ "declarativeWebRequest.RedirectToTransparentImage",
+ "declarativeWebRequest.RedirectToEmptyDocument",
+ "declarativeWebRequest.RedirectByRegEx",
+ "declarativeWebRequest.RemoveRequestCookie",
+ "declarativeWebRequest.RemoveResponseCookie",
+ "declarativeWebRequest.RemoveRequestHeader",
+ "declarativeWebRequest.RemoveResponseHeader",
+ "declarativeWebRequest.SetRequestHeader",
+ "declarativeWebRequest.SendMessageToExtension",
+ "declarativeWebRequest.IgnoreRules"
+ ]
+ }
+ },
+ {
+ "name": "onMessage",
+ "type": "function",
+ "description": "Fired when a message is sent via $(ref:declarativeWebRequest.SendMessageToExtension) from an action of the declarative web request API.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "details",
+ "properties": {
+ "message": {"type": "string", "description": "The message sent by the calling script."},
+ "stage": {"type": "string",
+ "enum": ["onBeforeRequest", "onBeforeSendHeaders", "onHeadersReceived", "onAuthRequired"],
+ "description": "The stage of the network request during which the event was triggered."},
+ "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
+ "url": {"type": "string"},
+ "method": {"type": "string", "description": "Standard HTTP method."},
+ "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
+ "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+ "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
+ "type": {"type": "string", "enum": ["main_frame", "sub_frame", "stylesheet", "script", "image", "object", "xmlhttprequest", "other"], "description": "How the requested resource will be used."},
+ "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}
+ }
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/chromium/extensions/common/api/diagnostics.idl b/chromium/extensions/common/api/diagnostics.idl
new file mode 100644
index 00000000000..5f8f31d76ae
--- /dev/null
+++ b/chromium/extensions/common/api/diagnostics.idl
@@ -0,0 +1,37 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.diagnostics</code> API to query various properties of
+// the environment that may be useful for diagnostics.
+namespace diagnostics {
+ dictionary SendPacketOptions {
+ // Target IP address.
+ DOMString ip;
+ // Packet time to live value. If omitted, the system default value will be
+ // used.
+ long? ttl;
+ // Packet timeout in seconds. If omitted, the system default value will be
+ // used.
+ long? timeout;
+ // Size of the payload. If omitted, the system default value will be used.
+ long? size;
+ };
+
+ dictionary SendPacketResult {
+ // The IP of the host which we receives the ICMP reply from.
+ // The IP may differs from our target IP if the packet's ttl is used up.
+ DOMString ip;
+
+ // Latency in millisenconds.
+ double latency;
+ };
+
+ callback SendPacketCallback = void(SendPacketResult result);
+
+ interface Functions {
+ // Send a packet of the given type with the given parameters.
+ static void sendPacket(SendPacketOptions options,
+ SendPacketCallback callback);
+ };
+};
diff --git a/chromium/extensions/common/api/display_source.idl b/chromium/extensions/common/api/display_source.idl
new file mode 100644
index 00000000000..f8c599c76f5
--- /dev/null
+++ b/chromium/extensions/common/api/display_source.idl
@@ -0,0 +1,143 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The <code>chrome.displaySource</code> API creates a Display
+// session using WebMediaStreamTrack as sources.
+namespace displaySource {
+ enum ErrorType {
+ // The connection with sink cannot be established or has dropped
+ // unexpectedly.
+ connection_error,
+
+ // The capabilities of this Display Source and the connected
+ // sink do not fit (e.g. the sink cannot play the media content of
+ // the formats given by the source).
+ capabilities_negotiation_error,
+
+ // There was an error in media pipeline: while encoding, packetizing or
+ // sending the media content.
+ media_pipeline_error,
+
+ // The sink became unresponsive.
+ timeout_error,
+
+ // Unspecified error.
+ unknown_error
+ };
+
+ dictionary ErrorInfo {
+ ErrorType type;
+ DOMString? description;
+ };
+
+ enum SinkState {
+ // Connected using this Display Source (i.e., there is an active session)
+ Connected,
+ // In process of connection to this Display Source
+ Connecting,
+ // Disconnected from this Display Source
+ Disconnected
+ };
+
+ dictionary SinkInfo {
+ // Id of the sink. It is guaranteed to be unique during the browser session.
+ long id;
+ // Human readable name of the sink.
+ DOMString name;
+ // State of the sink.
+ SinkState state;
+ };
+
+ enum AuthenticationMethod {
+ // Push Button Config authentication method.
+ PBC,
+ // PIN authentication method.
+ PIN
+ };
+
+ dictionary AuthenticationInfo {
+ // Authentication method.
+ AuthenticationMethod method;
+ // Authentication data (e.g. PIN value).
+ DOMString? data;
+ };
+
+ dictionary StartSessionInfo {
+ // Id of the sink to connect.
+ long sinkId;
+ // Authentication information.
+ AuthenticationInfo? authenticationInfo;
+ // The source audio track.
+ [instanceOf=MediaStreamTrack] object? audioTrack;
+ // The source audio track.
+ [instanceOf=MediaStreamTrack] object? videoTrack;
+ };
+
+ callback GetSinksCallback = void (SinkInfo[] result);
+ callback RequestAuthenticationCallback = void (AuthenticationInfo result);
+
+ // The callback is used by <code>startSession, terminateSession</code>
+ // to signal completion. The callback is called with
+ // <code>chrome.runtime.lastError</code> set to error
+ // message if the call has failed.
+ [inline_doc] callback CallCompleteCallback = void ();
+
+ interface Functions {
+ // Queries the list of the currently available Display sinks.
+ //
+ // |callback| : Called when the request is completed. The argument list
+ // is empty if no available sinks were found.
+ static void getAvailableSinks(GetSinksCallback callback);
+
+ // Queries authentication data from the sink device.
+ //
+ // |sinkId| : Id of the sink
+ // |callback| : Called when authentication info retrieved from the sink.
+ // The argument |method| field contains the authentication method required
+ // by the sink for connection; the |data| field can be null or can contain
+ // some supplementary data provided by the sink. If authentication info
+ // cannot be retrieved from the sink the "chrome.runtime.lastError" property
+ // is defined.
+ static void requestAuthentication(long sinkId,
+ RequestAuthenticationCallback callback);
+
+ // Creates a Display session using the provided StartSessionInfo instance.
+ // The input argument fields must be initialized as described below:
+ // The |sinkId| must be a valid id of a sink (obtained via
+ // ‘getAvailableSinks’).
+ //
+ // The |audioTrack| or |videoTrack| must be of type MediaStreamTrack.
+ // Either |audioTrack| or |videoTrack| can be null but not both. This
+ // means creating a session with only audio or video.
+ //
+ // The |authenticationInfo| can be null if no additional authentication data
+ // are required by the sink; otherwise its |data| field must contain the
+ // required authentication data (e.g. PIN value) and its |method| field must
+ // be the same as one obtained from ‘requestAuthentication’.
+ // |callback| : Called when the session is started.
+ [nocompile] static void startSession(
+ StartSessionInfo sessionInfo, optional CallCompleteCallback callback);
+
+ // Terminates the active Display session.
+ // |sinkId| : Id of the connected sink.
+ // |callback| : Called when the session is terminated.
+ [nocompile] static void terminateSession(
+ long sinkId, optional CallCompleteCallback callback);
+ };
+
+ interface Events {
+ // Event fired when the available sinks are modified (either their amount
+ // or properties)
+ // |sinks| the list of all currently available sinks
+ static void onSinksUpdated(SinkInfo[] sinks);
+ // Event fired when the Display session is terminated.
+ // |sinkId| Id of the peer sink
+ [nocompile] static void onSessionTerminated(long sinkId);
+ // Event fired when an error occurs.
+ // |sinkId| Id of the peer sink
+ // |errorInfo| error description
+ [nocompile] static void onSessionErrorOccured(long sinkId,
+ ErrorInfo errorInfo);
+ };
+};
diff --git a/chromium/extensions/common/api/dns.idl b/chromium/extensions/common/api/dns.idl
new file mode 100644
index 00000000000..0500c0fa077
--- /dev/null
+++ b/chromium/extensions/common/api/dns.idl
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.dns</code> API for dns resolution.
+namespace dns {
+
+ dictionary ResolveCallbackResolveInfo {
+ // The result code. Zero indicates success.
+ long resultCode;
+
+ // A string representing the IP address literal. Supplied only if resultCode
+ // indicates success. Note that we presently return only IPv4 addresses.
+ DOMString? address;
+ };
+
+ callback ResolveCallback = void (ResolveCallbackResolveInfo resolveInfo);
+
+ interface Functions {
+ // Resolves the given hostname or IP address literal.
+ // |hostname| : The hostname to resolve.
+ // |callback| : Called when the resolution operation completes.
+ static void resolve(DOMString hostname,
+ ResolveCallback callback);
+ };
+
+};
diff --git a/chromium/extensions/common/api/document_scan.idl b/chromium/extensions/common/api/document_scan.idl
new file mode 100644
index 00000000000..a2bdcca45c0
--- /dev/null
+++ b/chromium/extensions/common/api/document_scan.idl
@@ -0,0 +1,37 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.documentScan</code> API to discover and retrieve
+// images from attached paper document scanners.
+namespace documentScan {
+ dictionary ScanOptions {
+ // The MIME types that are accepted by the caller.
+ DOMString[]? mimeTypes;
+
+ // The number of scanned images allowed (defaults to 1).
+ long? maxImages;
+ };
+
+ dictionary ScanResults {
+ // The data image URLs in a form that can be passed as the "src" value to
+ // an image tag.
+ DOMString[] dataUrls;
+
+ // The MIME type of <code>dataUrls</code>.
+ DOMString mimeType;
+ };
+
+ // Callback from the <code>scan</code> method.
+ // |result| The results from the scan, if successful.
+ // Otherwise will return null and set runtime.lastError.
+ callback ScanCallback = void (ScanResults result);
+
+ interface Functions {
+ // Performs a document scan. On success, the PNG data will be
+ // sent to the callback.
+ // |options| : Object containing scan parameters.
+ // |callback| : Called with the result and data from the scan.
+ static void scan(ScanOptions options, ScanCallback callback);
+ };
+};
diff --git a/chromium/extensions/common/api/events.json b/chromium/extensions/common/api/events.json
new file mode 100644
index 00000000000..ba31dea3ae6
--- /dev/null
+++ b/chromium/extensions/common/api/events.json
@@ -0,0 +1,335 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "events",
+ "description": "The <code>chrome.events</code> namespace contains common types used by APIs dispatching events to notify you when something interesting happens.",
+ "compiler_options": {
+ "implemented_in": "extensions/browser/api/declarative/declarative_api.h"
+ },
+ "types": [
+ {
+ "id": "Rule",
+ "type": "object",
+ "description": "Description of a declarative rule for handling events.",
+ "properties": {
+ "id": {
+ "type": "string",
+ "optional": true,
+ "description": "Optional identifier that allows referencing this rule."
+ },
+ "tags": {
+ "type": "array",
+ "items": {"type": "string"},
+ "optional": true,
+ "description": "Tags can be used to annotate rules and perform operations on sets of rules."
+ },
+ "conditions": {
+ "type": "array",
+ "items": {"type": "any"},
+ "description": "List of conditions that can trigger the actions."
+ },
+ "actions": {
+ "type": "array",
+ "items": {"type": "any"},
+ "description": "List of actions that are triggered if one of the condtions is fulfilled."
+ },
+ "priority": {
+ "type": "integer",
+ "optional": true,
+ "description": "Optional priority of this rule. Defaults to 100."
+ }
+ }
+ },
+ {
+ "id": "Event",
+ "type": "object",
+ "description": "An object which allows the addition and removal of listeners for a Chrome event.",
+ "additionalProperties": { "type": "any"},
+ "functions": [
+ {
+ "name": "addListener",
+ "nocompile": true,
+ "type": "function",
+ "description": "Registers an event listener <em>callback</em> to an event.",
+ "parameters": [
+ {
+ "name": "callback",
+ "type": "function",
+ "description": "Called when an event occurs. The parameters of this function depend on the type of event."
+ }
+ ]
+ },
+ {
+ "name": "removeListener",
+ "nocompile": true,
+ "type": "function",
+ "description": "Deregisters an event listener <em>callback</em> from an event.",
+ "parameters": [
+ {
+ "name": "callback",
+ "type": "function",
+ "description": "Listener that shall be unregistered."
+ }
+ ]
+ },
+ {
+ "name": "hasListener",
+ "nocompile": true,
+ "type": "function",
+ "parameters": [
+ {
+ "name": "callback",
+ "type": "function",
+ "description": "Listener whose registration status shall be tested."
+ }
+ ],
+ "returns": {
+ "type": "boolean",
+ "description": "True if <em>callback</em> is registered to the event."
+ }
+ },
+ {
+ "name": "hasListeners",
+ "nocompile": true,
+ "type": "function",
+ "parameters": [],
+ "returns": {
+ "type": "boolean",
+ "description": "True if any event listeners are registered to the event."
+ }
+ },
+ {
+ "name": "addRules",
+ "type": "function",
+ "description": "Registers rules to handle events.",
+ "parameters": [
+ {
+ "nodoc": "true",
+ "name": "eventName",
+ "type": "string",
+ "description": "Name of the event this function affects."
+ },
+ {
+ "name": "webViewInstanceId",
+ "type": "integer",
+ "nodoc": true,
+ "description": "If provided, this is an integer that uniquely identfies the <webview> associated with this function call."
+ },
+ {
+ "name": "rules",
+ "type": "array",
+ "items": {"$ref": "Rule"},
+ "description": "Rules to be registered. These do not replace previously registered rules."
+ },
+ {
+ "name": "callback",
+ "optional": true,
+ "type": "function",
+ "parameters": [
+ {
+ "name": "rules",
+ "type": "array",
+ "items": {"$ref": "Rule"},
+ "description": "Rules that were registered, the optional parameters are filled with values."
+ }
+ ],
+ "description": "Called with registered rules."
+ }
+ ]
+ },
+ {
+ "name": "getRules",
+ "type": "function",
+ "description": "Returns currently registered rules.",
+ "parameters": [
+ {
+ "nodoc": "true",
+ "name": "eventName",
+ "type": "string",
+ "description": "Name of the event this function affects."
+ },
+ {
+ "name": "webViewInstanceId",
+ "type": "integer",
+ "nodoc": true,
+ "description": "If provided, this is an integer that uniquely identfies the <webview> associated with this function call."
+ },
+ {
+ "name": "ruleIdentifiers",
+ "optional": true,
+ "type": "array",
+ "items": {"type": "string"},
+ "description": "If an array is passed, only rules with identifiers contained in this array are returned."
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "parameters": [
+ {
+ "name": "rules",
+ "type": "array",
+ "items": {"$ref": "Rule"},
+ "description": "Rules that were registered, the optional parameters are filled with values."
+ }
+ ],
+ "description": "Called with registered rules."
+ }
+ ]
+ },
+ {
+ "name": "removeRules",
+ "type": "function",
+ "description": "Unregisters currently registered rules.",
+ "parameters": [
+ {
+ "nodoc": "true",
+ "name": "eventName",
+ "type": "string",
+ "description": "Name of the event this function affects."
+ },
+ {
+ "name": "webViewInstanceId",
+ "type": "integer",
+ "nodoc": true,
+ "description": "If provided, this is an integer that uniquely identfies the <webview> associated with this function call."
+ },
+ {
+ "name": "ruleIdentifiers",
+ "optional": true,
+ "type": "array",
+ "items": {"type": "string"},
+ "description": "If an array is passed, only rules with identifiers contained in this array are unregistered."
+ },
+ {
+ "name": "callback",
+ "optional": true,
+ "type": "function",
+ "parameters": [],
+ "description": "Called when rules were unregistered."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "UrlFilter",
+ "type": "object",
+ "description": "Filters URLs for various criteria. See <a href='events#filtered'>event filtering</a>. All criteria are case sensitive.",
+ "nocompile": true,
+ "properties": {
+ "hostContains": {
+ "type": "string",
+ "description": "Matches if the host name of the URL contains a specified string. To test whether a host name component has a prefix 'foo', use hostContains: '.foo'. This matches 'www.foobar.com' and 'foo.com', because an implicit dot is added at the beginning of the host name. Similarly, hostContains can be used to match against component suffix ('foo.') and to exactly match against components ('.foo.'). Suffix- and exact-matching for the last components need to be done separately using hostSuffix, because no implicit dot is added at the end of the host name.",
+ "optional": true
+ },
+ "hostEquals": {
+ "type": "string",
+ "description": "Matches if the host name of the URL is equal to a specified string.",
+ "optional": true
+ },
+ "hostPrefix": {
+ "type": "string",
+ "description": "Matches if the host name of the URL starts with a specified string.",
+ "optional": true
+ },
+ "hostSuffix": {
+ "type": "string",
+ "description": "Matches if the host name of the URL ends with a specified string.",
+ "optional": true
+ },
+ "pathContains": {
+ "type": "string",
+ "description": "Matches if the path segment of the URL contains a specified string.",
+ "optional": true
+ },
+ "pathEquals": {
+ "type": "string",
+ "description": "Matches if the path segment of the URL is equal to a specified string.",
+ "optional": true
+ },
+ "pathPrefix": {
+ "type": "string",
+ "description": "Matches if the path segment of the URL starts with a specified string.",
+ "optional": true
+ },
+ "pathSuffix": {
+ "type": "string",
+ "description": "Matches if the path segment of the URL ends with a specified string.",
+ "optional": true
+ },
+ "queryContains": {
+ "type": "string",
+ "description": "Matches if the query segment of the URL contains a specified string.",
+ "optional": true
+ },
+ "queryEquals": {
+ "type": "string",
+ "description": "Matches if the query segment of the URL is equal to a specified string.",
+ "optional": true
+ },
+ "queryPrefix": {
+ "type": "string",
+ "description": "Matches if the query segment of the URL starts with a specified string.",
+ "optional": true
+ },
+ "querySuffix": {
+ "type": "string",
+ "description": "Matches if the query segment of the URL ends with a specified string.",
+ "optional": true
+ },
+ "urlContains": {
+ "type": "string",
+ "description": "Matches if the URL (without fragment identifier) contains a specified string. Port numbers are stripped from the URL if they match the default port number.",
+ "optional": true
+ },
+ "urlEquals": {
+ "type": "string",
+ "description": "Matches if the URL (without fragment identifier) is equal to a specified string. Port numbers are stripped from the URL if they match the default port number.",
+ "optional": true
+ },
+ "urlMatches": {
+ "type": "string",
+ "description": "Matches if the URL (without fragment identifier) matches a specified regular expression. Port numbers are stripped from the URL if they match the default port number. The regular expressions use the <a href=\"https://github.com/google/re2/blob/master/doc/syntax.txt\">RE2 syntax</a>.",
+ "optional": true
+ },
+ "originAndPathMatches": {
+ "type": "string",
+ "description": "Matches if the URL without query segment and fragment identifier matches a specified regular expression. Port numbers are stripped from the URL if they match the default port number. The regular expressions use the <a href=\"https://github.com/google/re2/blob/master/doc/syntax.txt\">RE2 syntax</a>.",
+ "optional": true
+ },
+ "urlPrefix": {
+ "type": "string",
+ "description": "Matches if the URL (without fragment identifier) starts with a specified string. Port numbers are stripped from the URL if they match the default port number.",
+ "optional": true
+ },
+ "urlSuffix": {
+ "type": "string",
+ "description": "Matches if the URL (without fragment identifier) ends with a specified string. Port numbers are stripped from the URL if they match the default port number.",
+ "optional": true
+ },
+ "schemes": {
+ "type": "array",
+ "description": "Matches if the scheme of the URL is equal to any of the schemes specified in the array.",
+ "optional": true,
+ "items": { "type": "string" }
+ },
+ "ports": {
+ "type": "array",
+ "description": "Matches if the port of the URL is contained in any of the specified port lists. For example <code>[80, 443, [1000, 1200]]</code> matches all requests on port 80, 443 and in the range 1000-1200.",
+ "optional": true,
+ "items": {
+ "choices": [
+ {"type": "integer", "description": "A specific port."},
+ {"type": "array", "items": {"type": "integer"}, "description": "A pair of integers identiying the start and end (both inclusive) of a port range."}
+ ]
+ }
+ }
+ }
+ }
+ ]
+ }
+]
+
diff --git a/chromium/extensions/common/api/extension_options_internal.idl b/chromium/extensions/common/api/extension_options_internal.idl
new file mode 100644
index 00000000000..78052d14508
--- /dev/null
+++ b/chromium/extensions/common/api/extension_options_internal.idl
@@ -0,0 +1,24 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Internal API for the &lt;extensiontoptions&gt; tag
+namespace extensionOptionsInternal {
+ dictionary SizeChangedOptions {
+ long oldWidth;
+ long oldHeight;
+ long newWidth;
+ long newHeight;
+ };
+
+ dictionary PreferredSizeChangedOptions {
+ double width;
+ double height;
+ };
+
+ interface Events {
+ static void onClose();
+ static void onLoad();
+ static void onPreferredSizeChanged(PreferredSizeChangedOptions options);
+ };
+};
diff --git a/chromium/extensions/common/api/extension_types.json b/chromium/extensions/common/api/extension_types.json
new file mode 100644
index 00000000000..9f27590d41d
--- /dev/null
+++ b/chromium/extensions/common/api/extension_types.json
@@ -0,0 +1,69 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "extensionTypes",
+ "description": "The <code>chrome.extensionTypes</code> API contains type declarations for Chrome extensions.",
+ "types": [
+ {
+ "id": "ImageFormat",
+ "type": "string",
+ "enum": ["jpeg", "png"],
+ "description": "The format of an image."
+ },
+ {
+ "id": "ImageDetails",
+ "type": "object",
+ "description": "Details about the format and quality of an image.",
+ "properties": {
+ "format": {
+ "$ref": "ImageFormat",
+ "optional": true,
+ "description": "The format of the resulting image. Default is <code>\"jpeg\"</code>."
+ },
+ "quality": {
+ "type": "integer",
+ "optional": true,
+ "minimum": 0,
+ "maximum": 100,
+ "description": "When format is <code>\"jpeg\"</code>, controls the quality of the resulting image. This value is ignored for PNG images. As quality is decreased, the resulting image will have more visual artifacts, and the number of bytes needed to store it will decrease."
+ }
+ }
+ },
+ {
+ "id": "RunAt",
+ "type": "string",
+ "enum": ["document_start", "document_end", "document_idle"],
+ "description": "The soonest that the JavaScript or CSS will be injected into the tab."
+ },
+ {
+ "id": "InjectDetails",
+ "type": "object",
+ "description": "Details of the script or CSS to inject. Either the code or the file property must be set, but both may not be set at the same time.",
+ "properties": {
+ "code": {"type": "string", "optional": true, "description": "JavaScript or CSS code to inject.<br><br><b>Warning:</b><br>Be careful using the <code>code</code> parameter. Incorrect use of it may open your extension to <a href=\"https://en.wikipedia.org/wiki/Cross-site_scripting\">cross site scripting</a> attacks."},
+ "file": {"type": "string", "optional": true, "description": "JavaScript or CSS file to inject."},
+ "allFrames": {
+ "type": "boolean",
+ "optional": true,
+ "description": "If allFrames is <code>true</code>, implies that the JavaScript or CSS should be injected into all frames of current page. By default, it's <code>false</code> and is only injected into the top frame. If <code>true</code> and <code>frameId</code> is set, then the code is inserted in the selected frame and all of its child frames."
+ },
+ "frameId": {
+ "type": "integer",
+ "optional": true,
+ "minimum": 0,
+ "description": "The <a href='webNavigation#frame_ids'>frame</a> where the script or CSS should be injected. Defaults to 0 (the top-level frame)."
+ },
+ "matchAboutBlank": {"type": "boolean", "optional": true, "description": "If matchAboutBlank is true, then the code is also injected in about:blank and about:srcdoc frames if your extension has access to its parent document. Code cannot be inserted in top-level about:-frames. By default it is <code>false</code>."},
+ "runAt": {
+ "$ref": "RunAt",
+ "optional": true,
+ "description": "The soonest that the JavaScript or CSS will be injected into the tab. Defaults to \"document_idle\"."
+ }
+ }
+ }
+ ]
+ }
+]
diff --git a/chromium/extensions/common/api/extension_view_internal.json b/chromium/extensions/common/api/extension_view_internal.json
new file mode 100644
index 00000000000..3160c68369c
--- /dev/null
+++ b/chromium/extensions/common/api/extension_view_internal.json
@@ -0,0 +1,66 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "extensionViewInternal",
+ "description": "none",
+ "compiler_options": {
+ "implemented_in": "extensions/browser/api/guest_view/extension_view/extension_view_internal_api.h"
+ },
+ "functions": [
+ {
+ "name": "loadSrc",
+ "type": "function",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId"
+ },
+ {
+ "type": "string",
+ "name": "src"
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "hasLoadSucceeded",
+ "type": "boolean",
+ "description": "Whether or not loading the src has succeeded."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "parseSrc",
+ "type": "function",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "src"
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "isSrcValid",
+ "type": "boolean",
+ "description": "Whether or not the src is valid."
+ },
+ {
+ "name": "extensionId",
+ "type": "string",
+ "description": "The extension ID of the src."
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/chromium/extensions/common/api/extensions_manifest_types.json b/chromium/extensions/common/api/extensions_manifest_types.json
new file mode 100644
index 00000000000..23a73faa1b4
--- /dev/null
+++ b/chromium/extensions/common/api/extensions_manifest_types.json
@@ -0,0 +1,225 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The type schemas for structured manifest items. Not actually a callable API.
+
+[
+ {
+ "namespace": "extensionsManifestTypes",
+ "description": "Schemas for structured manifest entries",
+ "compiler_options": { "generate_error_messages": true },
+ "types": [
+ {
+ "id": "ContentCapabilities",
+ "type": "object",
+ "description": "The <code>content_capabilities</code> manifest entry allows an extension to grant certain additional capabilities to web contents whose locations match a given set of URL patterns.",
+ "properties": {
+ "matches": {
+ "description": "The set of URL patterns to match against. If any of the given patterns match a URL, its contents will be granted the specified capabilities.",
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "permissions": {
+ "description": "The set of capabilities to grant matched contents. This is currently limited to <code>clipboardRead</code>, <code>clipboardWrite</code>, and <code>unlimitedStorage</code>.",
+ "type": "array",
+ "items": { "type": "string" }
+ }
+ }
+ },
+ {
+ "id": "ExternallyConnectable",
+ "type": "object",
+ // Note: description commented out because externally_connectable.html
+ // already describes it, and the repetition looks odd.
+ // "description": "The <code>externally_connectable</code> manifest property declares which extensions, apps, and web pages can connect to your extension via $(ref:runtime.connect) and $(ref:runtime.sendMessage).",
+ "properties": {
+ "ids": {
+ "description": "<p>The IDs of extensions or apps that are allowed to connect. If left empty or unspecified, no extensions or apps can connect.</p><p>The wildcard <code>\"*\"</code> will allow all extensions and apps to connect.</p>",
+ "optional": true,
+ "type": "array",
+ "items": {"type": "string"}
+ },
+ "matches": {
+ "description": "<p>The URL patterns for <em>web pages</em> that are allowed to connect. <em>This does not affect content scripts.</em> If left empty or unspecified, no web pages can connect.</p><p>Patterns cannot include wildcard domains nor subdomains of <a href=\"http://publicsuffix.org/list/\">(effective) top level domains</a>; <code>*://google.com/*</code> and <code>http://*.chromium.org/*</code> are valid, while <code>&lt;all_urls&gt;</code>, <code>http://*/*</code>, <code>*://*.com/*</code>, and even <code>http://*.appspot.com/*</code> are not.</p>",
+ "optional": true,
+ "type": "array",
+ "items": {"type": "string"}
+ },
+ "accepts_tls_channel_id": {
+ "description": "If <code>true</code>, messages sent via $(ref:runtime.connect) or $(ref:runtime.sendMessage) will set $(ref:runtime.MessageSender.tlsChannelId) if those methods request it to be. If <code>false</code>, $(ref:runtime.MessageSender.tlsChannelId) will never be set under any circumstance.",
+ "optional": true,
+ "type": "boolean"
+ }
+ }
+ },
+ {
+ "id": "OptionsUI",
+ "type": "object",
+ "description": "The <code>options_ui</code> manifest property declares how the options page should be displayed.",
+ "properties": {
+ "page": {
+ "description": "The path to your options page, relative to your extension's root.",
+ "type": "string"
+ },
+ "chrome_style": {
+ "description": "If <code>true</code>, a Chrome user agent stylesheet will be applied to your options page. The default value is <code>false</code>, but we recommend you enable it for a consistent UI with Chrome.",
+ "optional": true,
+ "type": "boolean"
+ },
+ "open_in_tab": {
+ "description": "<p>If <code>true</code>, your extension's options page will be opened in a new tab rather than embedded in <em>chrome://extensions</em>. The default is <code>false</code>, and we recommend that you don't change it.</p><p><strong>This is only useful to delay the inevitable deprecation of the old options UI!</strong> It will be removed soon, so try not to use it. It will break.</p>",
+ "optional": true,
+ "type": "boolean"
+ }
+ }
+ },
+ {
+ "id": "SocketHostPatterns",
+ "description": "<p>A single string or a list of strings representing host:port patterns.</p>",
+ "choices": [
+ { "type": "string" },
+ { "type": "array", "items": { "type": "string" } }
+ ]
+ },
+ {
+ "id": "sockets",
+ "type": "object",
+ "description": "The <code>sockets</code> manifest property declares which sockets operations an app can issue.",
+ "properties": {
+ "udp": {
+ "description": "The <code>udp</code> manifest property declares which sockets.udp operations an app can issue.",
+ "optional": true,
+ "type": "object",
+ "properties": {
+ "bind": {
+ "description": "<p>The host:port pattern for <code>bind</code> operations.</p>",
+ "optional": true,
+ "$ref": "SocketHostPatterns"
+ },
+ "send": {
+ "description": "<p>The host:port pattern for <code>send</code> operations.</p>",
+ "optional": true,
+ "$ref": "SocketHostPatterns"
+ },
+ "multicastMembership": {
+ "description": "<p>The host:port pattern for <code>joinGroup</code> operations.</p>",
+ "optional": true,
+ "$ref": "SocketHostPatterns"
+ }
+ }
+ },
+ "tcp": {
+ "description": "The <code>tcp</code> manifest property declares which sockets.tcp operations an app can issue.",
+ "optional": true,
+ "type": "object",
+ "properties": {
+ "connect": {
+ "description": "<p>The host:port pattern for <code>connect</code> operations.</p>",
+ "optional": true,
+ "$ref": "SocketHostPatterns"
+ }
+ }
+ },
+ "tcpServer": {
+ "description": "The <code>tcpServer</code> manifest property declares which sockets.tcpServer operations an app can issue.",
+ "optional": true,
+ "type": "object",
+ "properties": {
+ "listen": {
+ "description": "<p>The host:port pattern for <code>listen</code> operations.</p>",
+ "optional": true,
+ "$ref": "SocketHostPatterns"
+ }
+ }
+ }
+ }
+ },
+ {
+ "id": "bluetooth",
+ "type": "object",
+ "description": "The <code>bluetooth</code> manifest property give permission to an app to use the $(ref:bluetooth) API. A list of UUIDs can be optionally specified to enable communication with devices.",
+ "properties": {
+ "uuids": {
+ "description": "The <code>uuids</code> manifest property declares the list of protocols, profiles and services that an app can communicate using.",
+ "optional": true,
+ "type": "array",
+ "items": {
+ "description": "<p>The list specified as UUID strings.</p>",
+ "type": "string"
+ }
+ },
+ "socket": {
+ "type": "boolean",
+ "description": "If <code>true</code>, gives permission to an app to use the $(ref:bluetoothSocket) API",
+ "optional": true
+ },
+ "low_energy": {
+ "type": "boolean",
+ "description": "If <code>true</code>, gives permission to an app to use the $(ref:bluetoothLowEnergy) API",
+ "optional": true
+ },
+ "peripheral": {
+ "type": "boolean",
+ "description": "If <code>true</code>, gives permission to an app to use the advertisement functions in the $(ref:bluetoothLowEnergy) API",
+ "optional": true
+ }
+ }
+ },
+ {
+ "id": "UsbPrinters",
+ "type": "object",
+ "description": "The <code>usb_printers</code> manifest property lists the USB printers supported by an app implementing the $(ref:printerProvider) API.",
+ "properties": {
+ "filters": {
+ "description": "A list of $(ref:usb.DeviceFilter USB device filters) matching supported devices. A device only needs to match one of the provided filters. A <code>vendorId</code> is required and only one of <code>productId</code> or <code>interfaceClass</code> may be provided.",
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "vendorId": {
+ "description": "USB vendor ID of matching devices",
+ "type": "integer"
+ },
+ "productId": {
+ "description": "USB product ID of matching devices",
+ "type": "integer",
+ "optional": true
+ },
+ "interfaceClass": {
+ "description": "USB interface class implemented by any interface of a matching device.",
+ "type": "integer",
+ "optional": true
+ },
+ "interfaceSubclass": {
+ "description": "USB interface sub-class implemented by the interface matching $(ref:interfaceClass).",
+ "type": "integer",
+ "optional": true
+ },
+ "interfaceProtocol": {
+ "description": "USB interface protocol implemented by the interface matching $(ref:interfaceClass) and $(ref:interfaceSubclass).",
+ "type": "integer",
+ "optional": true
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "id": "KioskSecondaryApps",
+ "type": "array",
+ "description": "The <code>kiosk_secondary_apps</code> manifest property lists the secondary kiosk apps to be deployed by the primary kiosk app.",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "description": "ID of secondary kiosk app",
+ "type": "string"
+ }
+ }
+ }
+ }
+ ]
+ }
+]
diff --git a/chromium/extensions/common/api/externs_checker.py b/chromium/extensions/common/api/externs_checker.py
new file mode 100644
index 00000000000..fd60fdd87df
--- /dev/null
+++ b/chromium/extensions/common/api/externs_checker.py
@@ -0,0 +1,36 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+class ExternsChecker(object):
+ _UPDATE_MESSAGE = """To update the externs, run:
+ src/ $ python tools/json_schema_compiler/compiler.py\
+ %s --root=. --generator=externs > %s"""
+
+ def __init__(self, input_api, output_api, api_pairs):
+ self._input_api = input_api
+ self._output_api = output_api
+ self._api_pairs = api_pairs
+
+ for path in api_pairs.keys() + api_pairs.values():
+ if not input_api.os_path.exists(path):
+ raise OSError('Path Not Found: %s' % path)
+
+ def RunChecks(self):
+ bad_files = []
+ affected = [f.AbsoluteLocalPath() for f in self._input_api.AffectedFiles()]
+ for path in affected:
+ pair = self._api_pairs.get(path)
+ if pair != None and pair not in affected:
+ bad_files.append({'source': path, 'extern': pair})
+ results = []
+ if bad_files:
+ replacements = (('<source_file>', '<output_file>') if len(bad_files) > 1
+ else (bad_files[0]['source'], bad_files[0]['extern']))
+ long_text = self._UPDATE_MESSAGE % replacements
+ results.append(self._output_api.PresubmitPromptWarning(
+ str('Found updated extension api files without updated extern files. '
+ 'Please update the extern files.'),
+ [f['source'] for f in bad_files],
+ long_text))
+ return results
diff --git a/chromium/extensions/common/api/externs_checker_test.py b/chromium/extensions/common/api/externs_checker_test.py
new file mode 100755
index 00000000000..9bcc651b679
--- /dev/null
+++ b/chromium/extensions/common/api/externs_checker_test.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import sys
+import unittest
+
+from externs_checker import ExternsChecker
+
+sys.path.append(
+ os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', '..'))
+
+from PRESUBMIT_test_mocks import MockInputApi, MockOutputApi, MockFile
+
+
+class ExternsCheckerTest(unittest.TestCase):
+ API_PAIRS = {'a': '1', 'b': '2', 'c': '3'}
+
+ def _runChecks(self, files, exists=lambda f: True):
+ input_api = MockInputApi()
+ input_api.os_path.exists = exists
+ input_api.files = [MockFile(f, '') for f in files]
+ output_api = MockOutputApi()
+ checker = ExternsChecker(input_api, output_api, self.API_PAIRS)
+ return checker.RunChecks()
+
+ def testModifiedSourceWithoutModifiedExtern(self):
+ results = self._runChecks(['b', 'test', 'random'])
+ self.assertEquals(1, len(results))
+ self.assertEquals(1, len(results[0].items))
+ self.assertEquals('b', results[0].items[0])
+ self.assertEquals(
+ 'To update the externs, run:\n'
+ ' src/ $ python tools/json_schema_compiler/compiler.py b --root=. '
+ '--generator=externs > 2',
+ results[0].long_text)
+
+ def testModifiedSourceWithModifiedExtern(self):
+ results = self._runChecks(['b', '2', 'test', 'random'])
+ self.assertEquals(0, len(results))
+
+ def testModifiedMultipleSourcesWithNoModifiedExterns(self):
+ results = self._runChecks(['b', 'test', 'c', 'random'])
+ self.assertEquals(1, len(results))
+ self.assertEquals(2, len(results[0].items))
+ self.assertTrue('b' in results[0].items)
+ self.assertTrue('c' in results[0].items)
+ self.assertEquals(
+ 'To update the externs, run:\n'
+ ' src/ $ python tools/json_schema_compiler/compiler.py <source_file> '
+ '--root=. --generator=externs > <output_file>',
+ results[0].long_text)
+
+ def testModifiedMultipleSourcesWithOneModifiedExtern(self):
+ results = self._runChecks(['b', 'test', 'c', 'random', '2'])
+ self.assertEquals(1, len(results))
+ self.assertEquals(1, len(results[0].items))
+ self.assertEquals('c', results[0].items[0])
+
+ def testApiFileDoesNotExist(self):
+ exists = lambda f: f in ['a', 'b', 'c', '1', '2']
+ with self.assertRaises(OSError) as e:
+ self._runChecks(['a'], exists)
+ self.assertEqual('Path Not Found: 3', str(e.exception))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/extensions/common/api/guest_view_internal.json b/chromium/extensions/common/api/guest_view_internal.json
new file mode 100644
index 00000000000..90c5d4e7b0e
--- /dev/null
+++ b/chromium/extensions/common/api/guest_view_internal.json
@@ -0,0 +1,116 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "guestViewInternal",
+ "compiler_options": {
+ "implemented_in": "extensions/browser/api/guest_view/guest_view_internal_api.h"
+ },
+ "description": "none",
+ "types": [
+ {
+ "id": "Size",
+ "type": "object",
+ "properties": {
+ "width": {
+ "type": "integer"
+ },
+ "height": {
+ "type": "integer"
+ }
+ }
+ },
+ {
+ "id": "SizeParams",
+ "type": "object",
+ "description": "Size parameters.",
+ "properties": {
+ "enableAutoSize": {
+ "type": "boolean",
+ "optional": true
+ },
+ "min": {
+ "$ref": "Size",
+ "optional": true
+ },
+ "max": {
+ "$ref": "Size",
+ "optional": true
+ },
+ "normal": {
+ "$ref": "Size",
+ "optional": true
+ }
+ }
+ }
+ ],
+ "functions": [
+ {
+ "name": "createGuest",
+ "type": "function",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "viewType",
+ "nodoc": true
+ },
+ {
+ "type": "object",
+ "name": "createParams",
+ "additionalProperties": {"type": "any"}
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "returnParams",
+ "additionalProperties": {"type": "any"}
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "destroyGuest",
+ "type": "function",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId"
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [],
+ "optional": true
+ }
+ ]
+ },
+ {
+ "name": "setSize",
+ "type": "function",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId",
+ "description": "The instance ID of the guest &lt;webview&gt; process. This not exposed to developers through the API."
+ },
+ {
+ "$ref": "SizeParams",
+ "name": "params"
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [],
+ "optional": true
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/chromium/extensions/common/api/hid.idl b/chromium/extensions/common/api/hid.idl
new file mode 100644
index 00000000000..2e81ebe08f9
--- /dev/null
+++ b/chromium/extensions/common/api/hid.idl
@@ -0,0 +1,171 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.hid</code> API to interact with connected HID devices.
+// This API provides access to HID operations from within the context of an app.
+// Using this API, apps can function as drivers for hardware devices.
+//
+// Errors generated by this API are reported by setting
+// $(ref:runtime.lastError) and executing the function's regular callback. The
+// callback's regular parameters will be undefined in this case.
+namespace hid {
+ dictionary HidCollectionInfo {
+ // HID usage page identifier.
+ long usagePage;
+ // Page-defined usage identifier.
+ long usage;
+ // Report IDs which belong to the collection and to its children.
+ long[] reportIds;
+ };
+
+ [noinline_doc] dictionary HidDeviceInfo {
+ // Opaque device ID.
+ long deviceId;
+ // Vendor ID.
+ long vendorId;
+ // Product ID.
+ long productId;
+ // The product name read from the device, if available.
+ DOMString productName;
+ // The serial number read from the device, if available.
+ DOMString serialNumber;
+ // Top-level collections from this device's report descriptors.
+ HidCollectionInfo[] collections;
+ // Top-level collection's maximum input report size.
+ long maxInputReportSize;
+ // Top-level collection's maximum output report size.
+ long maxOutputReportSize;
+ // Top-level collection's maximum feature report size.
+ long maxFeatureReportSize;
+ // Raw device report descriptor (not available on Windows).
+ ArrayBuffer reportDescriptor;
+ };
+
+ dictionary HidConnectInfo {
+ // The opaque ID used to identify this connection in all other functions.
+ long connectionId;
+ };
+
+ [noinline_doc] dictionary DeviceFilter {
+ // Device vendor ID.
+ long? vendorId;
+ // Device product ID, only checked only if the vendor ID matches.
+ long? productId;
+ // HID usage page identifier.
+ long? usagePage;
+ // HID usage identifier, checked only if the HID usage page matches.
+ long? usage;
+ };
+
+ dictionary GetDevicesOptions {
+ [deprecated="Equivalent to setting $(ref:DeviceFilter.vendorId)."]
+ long? vendorId;
+ [deprecated="Equivalent to setting $(ref:DeviceFilter.productId)."]
+ long? productId;
+ // A device matching any given filter will be returned. An empty filter list
+ // will return all devices the app has permission for.
+ DeviceFilter[]? filters;
+ };
+
+ dictionary DevicePromptOptions {
+ // Allow the user to select multiple devices.
+ boolean? multiple;
+ // Filter the list of devices presented to the user. If multiple filters
+ // are provided devices matching any filter will be displayed.
+ DeviceFilter[]? filters;
+ };
+
+ callback GetDevicesCallback = void (HidDeviceInfo[] devices);
+ callback ConnectCallback = void (HidConnectInfo connection);
+ callback DisconnectCallback = void ();
+
+ // |reportId|: The report ID or <code>0</code> if none.
+ // |data|: The report data, the report ID prefix (if present) is removed.
+ callback ReceiveCallback = void (long reportId, ArrayBuffer data);
+
+ // |data|: The report data, including a report ID prefix if one is sent by the
+ // device.
+ callback ReceiveFeatureReportCallback = void (ArrayBuffer data);
+
+ callback SendCallback = void();
+
+ interface Functions {
+ // Enumerate connected HID devices.
+ // |options|: The properties to search for on target devices.
+ static void getDevices(GetDevicesOptions options,
+ GetDevicesCallback callback);
+
+ // Presents a device picker to the user and returns $(ref:HidDeviceInfo)
+ // objects for the devices selected.
+ // If the user cancels the picker devices will be empty. A user gesture
+ // is required for the dialog to display. Without a user gesture, the
+ // callback will run as though the user cancelled. If multiple filters are
+ // provided devices matching any filter will be displayed.
+ // |options|: Configuration of the device picker dialog box.
+ // |callback|: Invoked with a list of chosen $(ref:Device)s.
+ static void getUserSelectedDevices(optional DevicePromptOptions options,
+ GetDevicesCallback callback);
+
+ // Open a connection to an HID device for communication.
+ // |deviceId|: The $(ref:HidDeviceInfo.deviceId) of the device to open.
+ static void connect(long deviceId,
+ ConnectCallback callback);
+
+ // Disconnect from a device. Invoking operations on a device after calling
+ // this is safe but has no effect.
+ // |connectionId|: The <code>connectionId</code> returned by $(ref:connect).
+ static void disconnect(long connectionId,
+ optional DisconnectCallback callback);
+
+ // Receive the next input report from the device.
+ // |connectionId|: The <code>connectionId</code> returned by $(ref:connect).
+ static void receive(long connectionId,
+ ReceiveCallback callback);
+
+ // Send an output report to the device.
+ //
+ // <em>Note:</em> Do not include a report ID prefix in <code>data</code>.
+ // It will be added if necessary.
+ // |connectionId|: The <code>connectionId</code> returned by $(ref:connect).
+ // |reportId|: The report ID to use, or <code>0</code> if none.
+ // |data|: The report data.
+ static void send(long connectionId,
+ long reportId,
+ ArrayBuffer data,
+ SendCallback callback);
+
+ // Request a feature report from the device.
+ // |connectionId|: The <code>connectionId</code> returned by $(ref:connect).
+ // |reportId|: The report ID, or <code>0</code> if none.
+ static void receiveFeatureReport(long connectionId,
+ long reportId,
+ ReceiveFeatureReportCallback callback);
+
+ // Send a feature report to the device.
+ //
+ // <em>Note:</em> Do not include a report ID prefix in <code>data</code>.
+ // It will be added if necessary.
+ // |connectionId|: The <code>connectionId</code> returned by $(ref:connect).
+ // |reportId|: The report ID to use, or <code>0</code> if none.
+ // |data|: The report data.
+ static void sendFeatureReport(long connectionId,
+ long reportId,
+ ArrayBuffer data,
+ SendCallback callback);
+ };
+
+ interface Events {
+ // Event generated when a device is added to the system. Events are only
+ // broadcast to apps and extensions that have permission to access the
+ // device. Permission may have been granted at install time or when the user
+ // accepted an optional permission (see $(ref:permissions.request)).
+ static void onDeviceAdded(HidDeviceInfo device);
+
+ // Event generated when a device is removed from the system. See
+ // $(ref:onDeviceAdded) for which events are delivered.
+ // |deviceId|: The <code>deviceId</code> property of the device passed to
+ // $(ref:onDeviceAdded).
+ static void onDeviceRemoved(long deviceId);
+ };
+};
diff --git a/chromium/extensions/common/api/idle.json b/chromium/extensions/common/api/idle.json
new file mode 100644
index 00000000000..43a9d071b6a
--- /dev/null
+++ b/chromium/extensions/common/api/idle.json
@@ -0,0 +1,68 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "idle",
+ "description": "Use the <code>chrome.idle</code> API to detect when the machine's idle state changes.",
+ "types": [
+ {
+ "id": "IdleState",
+ "type": "string",
+ "enum": ["active", "idle", "locked"]
+ }
+ ],
+ "functions": [
+ {
+ "name": "queryState",
+ "type": "function",
+ "description": "Returns \"locked\" if the system is locked, \"idle\" if the user has not generated any input for a specified number of seconds, or \"active\" otherwise.",
+ "parameters": [
+ {
+ "name": "detectionIntervalInSeconds",
+ "type": "integer",
+ "minimum": 15,
+ "description": "The system is considered idle if detectionIntervalInSeconds seconds have elapsed since the last user input detected."
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "parameters": [
+ {
+ "name": "newState",
+ "$ref": "IdleState"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "setDetectionInterval",
+ "type": "function",
+ "description": "Sets the interval, in seconds, used to determine when the system is in an idle state for onStateChanged events. The default interval is 60 seconds.",
+ "parameters": [
+ {
+ "name": "intervalInSeconds",
+ "type": "integer",
+ "minimum": 15,
+ "description": "Threshold, in seconds, used to determine when the system is in an idle state."
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "onStateChanged",
+ "type": "function",
+ "description": "Fired when the system changes to an active, idle or locked state. The event fires with \"locked\" if the screen is locked or the screensaver activates, \"idle\" if the system is unlocked and the user has not generated any input for a specified number of seconds, and \"active\" when the user generates input on an idle system.",
+ "parameters": [
+ {
+ "name": "newState",
+ "$ref": "IdleState"
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/chromium/extensions/common/api/management.json b/chromium/extensions/common/api/management.json
new file mode 100644
index 00000000000..2e059406fa9
--- /dev/null
+++ b/chromium/extensions/common/api/management.json
@@ -0,0 +1,457 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace":"management",
+ "description": "The <code>chrome.management</code> API provides ways to manage the list of extensions/apps that are installed and running. It is particularly useful for extensions that <a href='override'>override</a> the built-in New Tab page.",
+ "types": [
+ {
+ "id": "IconInfo",
+ "description": "Information about an icon belonging to an extension, app, or theme.",
+ "type": "object",
+ "properties": {
+ "size": { "type": "integer", "description": "A number representing the width and height of the icon. Likely values include (but are not limited to) 128, 48, 24, and 16." },
+ "url": { "type": "string", "description": "The URL for this icon image. To display a grayscale version of the icon (to indicate that an extension is disabled, for example), append <code>?grayscale=true</code> to the URL." }
+ }
+ },
+ {
+ "id": "LaunchType",
+ "type": "string",
+ "enum": ["OPEN_AS_REGULAR_TAB", "OPEN_AS_PINNED_TAB", "OPEN_AS_WINDOW", "OPEN_FULL_SCREEN"],
+ "description": "These are all possible app launch types."
+ },
+ {
+ "id": "ExtensionDisabledReason",
+ "description": "A reason the item is disabled.",
+ "type": "string",
+ "enum": ["unknown", "permissions_increase"]
+ },
+ {
+ "id": "ExtensionType",
+ "description": "The type of this extension, app, or theme.",
+ "type": "string",
+ "enum": ["extension", "hosted_app", "packaged_app", "legacy_packaged_app", "theme"]
+ },
+ {
+ "id": "ExtensionInstallType",
+ "description": "How the extension was installed. One of<br><var>admin</var>: The extension was installed because of an administrative policy,<br><var>development</var>: The extension was loaded unpacked in developer mode,<br><var>normal</var>: The extension was installed normally via a .crx file,<br><var>sideload</var>: The extension was installed by other software on the machine,<br><var>other</var>: The extension was installed by other means.",
+ "type": "string",
+ "enum": ["admin", "development", "normal", "sideload", "other"]
+ },
+ {
+ "id": "ExtensionInfo",
+ "description": "Information about an installed extension, app, or theme.",
+ "type": "object",
+ "properties": {
+ "id": {
+ "description": "The extension's unique identifier.",
+ "type": "string"
+ },
+ "name": {
+ "description": "The name of this extension, app, or theme.",
+ "type": "string"
+ },
+ "shortName": {
+ "description": "A short version of the name of this extension, app, or theme.",
+ "type": "string"
+ },
+ "description": {
+ "description": "The description of this extension, app, or theme.",
+ "type": "string"
+ },
+ "version": {
+ "description": "The <a href='manifest/version'>version</a> of this extension, app, or theme.",
+ "type": "string"
+ },
+ "versionName": {
+ "description": "The <a href='manifest/version#version_name'>version name</a> of this extension, app, or theme if the manifest specified one.",
+ "type": "string",
+ "optional": true
+ },
+ "mayDisable": {
+ "description": "Whether this extension can be disabled or uninstalled by the user.",
+ "type": "boolean"
+ },
+ "enabled": {
+ "description": "Whether it is currently enabled or disabled.",
+ "type": "boolean"
+ },
+ "disabledReason": {
+ "description": "A reason the item is disabled.",
+ "$ref": "ExtensionDisabledReason",
+ "optional": true
+ },
+ "isApp": {
+ "description": "True if this is an app.",
+ "type": "boolean",
+ "deprecated": "Please use $(ref:management.ExtensionInfo.type)."
+ },
+ "type": {
+ "description": "The type of this extension, app, or theme.",
+ "$ref": "ExtensionType"
+ },
+ "appLaunchUrl": {
+ "description": "The launch url (only present for apps).",
+ "type": "string",
+ "optional": true
+ },
+ "homepageUrl": {
+ "description": "The URL of the homepage of this extension, app, or theme.",
+ "type": "string",
+ "optional": true
+ },
+ "updateUrl": {
+ "description": "The update URL of this extension, app, or theme.",
+ "type": "string",
+ "optional": true
+ },
+ "offlineEnabled": {
+ "description": "Whether the extension, app, or theme declares that it supports offline.",
+ "type": "boolean"
+ },
+ "optionsUrl": {
+ "description": "The url for the item's options page, if it has one.",
+ "type": "string"
+ },
+ "icons": {
+ "description": "A list of icon information. Note that this just reflects what was declared in the manifest, and the actual image at that url may be larger or smaller than what was declared, so you might consider using explicit width and height attributes on img tags referencing these images. See the <a href='manifest/icons'>manifest documentation on icons</a> for more details.",
+ "type": "array",
+ "optional": true,
+ "items": {
+ "$ref": "IconInfo"
+ }
+ },
+ "permissions": {
+ "description": "Returns a list of API based permissions.",
+ "type": "array",
+ "items" : {
+ "type": "string"
+ }
+ },
+ "hostPermissions": {
+ "description": "Returns a list of host based permissions.",
+ "type": "array",
+ "items" : {
+ "type": "string"
+ }
+ },
+ "installType": {
+ "description": "How the extension was installed.",
+ "$ref": "ExtensionInstallType"
+ },
+ "launchType": {
+ "description": "The app launch type (only present for apps).",
+ "$ref": "LaunchType",
+ "optional": true
+ },
+ "availableLaunchTypes": {
+ "description": "The currently available launch types (only present for apps).",
+ "type": "array",
+ "optional": true,
+ "items": {
+ "$ref": "LaunchType"
+ }
+ }
+ }
+ }
+ ],
+ "functions": [
+ {
+ "name": "getAll",
+ "description": "Returns a list of information about installed extensions and apps.",
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "type": "array",
+ "name": "result",
+ "items": {
+ "$ref": "ExtensionInfo"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "get",
+ "description": "Returns information about the installed extension, app, or theme that has the given ID.",
+ "parameters": [
+ {
+ "name": "id",
+ "type": "string",
+ "description": "The ID from an item of $(ref:management.ExtensionInfo)."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "result",
+ "$ref": "ExtensionInfo"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getSelf",
+ "description": "Returns information about the calling extension, app, or theme. Note: This function can be used without requesting the 'management' permission in the manifest.",
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "result",
+ "$ref": "ExtensionInfo"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getPermissionWarningsById",
+ "description": "Returns a list of <a href='permission_warnings'>permission warnings</a> for the given extension id.",
+ "parameters": [
+ { "name": "id",
+ "type": "string",
+ "description": "The ID of an already installed extension."
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "permissionWarnings",
+ "type": "array",
+ "items": { "type": "string" }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getPermissionWarningsByManifest",
+ "description": "Returns a list of <a href='permission_warnings'>permission warnings</a> for the given extension manifest string. Note: This function can be used without requesting the 'management' permission in the manifest.",
+ "parameters": [
+ {
+ "name": "manifestStr",
+ "type": "string",
+ "description": "Extension manifest JSON string."
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "permissionWarnings",
+ "type": "array",
+ "items": { "type": "string" }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "setEnabled",
+ "description": "Enables or disables an app or extension. In most cases this function must be called in the context of a user gesture (e.g. an onclick handler for a button), and may present the user with a native confirmation UI as a way of preventing abuse.",
+ "parameters": [
+ {
+ "name": "id",
+ "type": "string",
+ "description": "This should be the id from an item of $(ref:management.ExtensionInfo)."
+ },
+ {
+ "name": "enabled",
+ "type": "boolean",
+ "description": "Whether this item should be enabled or disabled."
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "uninstall",
+ "description": "Uninstalls a currently installed app or extension.",
+ "parameters": [
+ {
+ "name": "id",
+ "type": "string",
+ "description": "This should be the id from an item of $(ref:management.ExtensionInfo)."
+ },
+ {
+ "type": "object",
+ "name": "options",
+ "optional": true,
+ "properties": {
+ "showConfirmDialog": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether or not a confirm-uninstall dialog should prompt the user. Defaults to false for self uninstalls. If an extension uninstalls another extension, this parameter is ignored and the dialog is always shown."
+ }
+ }
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "uninstallSelf",
+ "description": "Uninstalls the calling extension. Note: This function can be used without requesting the 'management' permission in the manifest.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "options",
+ "optional": true,
+ "properties": {
+ "showConfirmDialog": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether or not a confirm-uninstall dialog should prompt the user. Defaults to false."
+ }
+ }
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "launchApp",
+ "description": "Launches an application.",
+ "parameters": [
+ {
+ "name": "id",
+ "type": "string",
+ "description": "The extension id of the application."
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "createAppShortcut",
+ "description": "Display options to create shortcuts for an app. On Mac, only packaged app shortcuts can be created.",
+ "parameters": [
+ {
+ "name": "id",
+ "type": "string",
+ "description": "This should be the id from an app item of $(ref:management.ExtensionInfo)."
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "setLaunchType",
+ "description": "Set the launch type of an app.",
+ "parameters": [
+ {
+ "name": "id",
+ "type": "string",
+ "description": "This should be the id from an app item of $(ref:management.ExtensionInfo)."
+ },
+ {
+ "name": "launchType",
+ "$ref": "LaunchType",
+ "description": "The target launch type. Always check and make sure this launch type is in $(ref:ExtensionInfo.availableLaunchTypes), because the available launch types vary on different platforms and configurations."
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "generateAppForLink",
+ "description": "Generate an app for a URL. Returns the generated bookmark app.",
+ "parameters": [
+ {
+ "name": "url",
+ "type": "string",
+ "description": "The URL of a web page. The scheme of the URL can only be \"http\" or \"https\"."
+ },
+ {
+ "name": "title",
+ "type": "string",
+ "description": "The title of the generated app."
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "result",
+ "$ref": "ExtensionInfo"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "onInstalled",
+ "description": "Fired when an app or extension has been installed.",
+ "type": "function",
+ "parameters": [{"name": "info", "$ref":"ExtensionInfo"}]
+ },
+ {
+ "name": "onUninstalled",
+ "description": "Fired when an app or extension has been uninstalled.",
+ "type": "function",
+ "parameters": [
+ {
+ "name": "id",
+ "type": "string",
+ "description": "The id of the extension, app, or theme that was uninstalled."
+ }
+ ]
+ },
+ {
+ "name": "onEnabled",
+ "description": "Fired when an app or extension has been enabled.",
+ "type": "function",
+ "parameters": [{"name": "info", "$ref":"ExtensionInfo"}]
+ },
+ {
+ "name": "onDisabled",
+ "description": "Fired when an app or extension has been disabled.",
+ "type": "function",
+ "parameters": [{"name": "info", "$ref":"ExtensionInfo"}]
+ }
+ ]
+ }
+]
diff --git a/chromium/extensions/common/api/messaging/message.h b/chromium/extensions/common/api/messaging/message.h
new file mode 100644
index 00000000000..64ecb82a9e9
--- /dev/null
+++ b/chromium/extensions/common/api/messaging/message.h
@@ -0,0 +1,22 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_API_MESSAGING_MESSAGE_H_
+#define EXTENSIONS_COMMON_API_MESSAGING_MESSAGE_H_
+
+namespace extensions {
+
+// A message consists of both the data itself as well as a user gesture state.
+struct Message {
+ std::string data;
+ bool user_gesture;
+
+ Message() : data(), user_gesture(false) {}
+ Message(const std::string& data, bool user_gesture)
+ : data(data), user_gesture(user_gesture) {}
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_API_MESSAGING_MESSAGE_H_
diff --git a/chromium/extensions/common/api/mime_handler.mojom b/chromium/extensions/common/api/mime_handler.mojom
new file mode 100644
index 00000000000..7eefc02c1be
--- /dev/null
+++ b/chromium/extensions/common/api/mime_handler.mojom
@@ -0,0 +1,40 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module extensions.mime_handler;
+
+// Information about a stream.
+struct StreamInfo {
+ // The MIME type of the intercepted URL request.
+ string mime_type;
+
+ // The original URL that was intercepted.
+ string original_url;
+
+ // The URL that the stream can be read from.
+ string stream_url;
+
+ // The ID of the tab that opened the stream. If the stream is not opened in a
+ // tab, it will be -1.
+ int32 tab_id;
+
+ // The HTTP response headers of the intercepted request stored as a dictionary
+ // mapping header name to header value. If a header name appears multiple
+ // times, the header values are merged in the dictionary and separated by a
+ // ",". Non-ASCII headers are dropped.
+ map<string, string> response_headers;
+
+ // Whether the stream is embedded within another document.
+ bool embedded;
+};
+
+interface MimeHandlerService {
+ // Returns information about the stream associated with this service instance.
+ // If the stream has been aborted, |stream_info| will be null.
+ GetStreamInfo() => (StreamInfo? stream_info);
+
+ // Aborts the stream associated with this service instance. This is an
+ // idempotent operation.
+ AbortStream() => ();
+};
diff --git a/chromium/extensions/common/api/mime_handler_private.idl b/chromium/extensions/common/api/mime_handler_private.idl
new file mode 100644
index 00000000000..dfd47be6ae9
--- /dev/null
+++ b/chromium/extensions/common/api/mime_handler_private.idl
@@ -0,0 +1,41 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Mime handler API.
+[nodoc] namespace mimeHandlerPrivate {
+ dictionary StreamInfo {
+ // The MIME type of the intercepted URL request.
+ DOMString mimeType;
+
+ // The original URL that was intercepted.
+ DOMString originalUrl;
+
+ // The URL that the stream can be read from.
+ DOMString streamUrl;
+
+ // The ID of the tab that opened the stream. If the stream is not opened in
+ // a tab, it will be -1.
+ long tabId;
+
+ // The HTTP response headers of the intercepted request stored as a
+ // dictionary mapping header name to header value. If a header name appears
+ // multiple times, the header values are merged in the dictionary and
+ // separated by a ", ". Non-ASCII headers are dropped.
+ object responseHeaders;
+
+ // Whether the stream is embedded within another document.
+ boolean embedded;
+ };
+
+ callback AbortCallback = void ();
+ callback GetStreamDetailsCallback = void (StreamInfo streamInfo);
+
+ interface Functions {
+ // Returns the StreamInfo for the stream for this context if there is one.
+ [nocompile] static void getStreamInfo(GetStreamDetailsCallback callback);
+
+ // Aborts the stream for this context if there is one.
+ [nocompile] static void abortStream(optional AbortCallback callback);
+ };
+};
diff --git a/chromium/extensions/common/api/mime_handler_view_guest_internal.json b/chromium/extensions/common/api/mime_handler_view_guest_internal.json
new file mode 100644
index 00000000000..884ffd062ea
--- /dev/null
+++ b/chromium/extensions/common/api/mime_handler_view_guest_internal.json
@@ -0,0 +1,11 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "mimeHandlerViewGuestInternal",
+ "description": "Placeholder for mime handler view guest API, currently unused."
+ }
+]
+
diff --git a/chromium/extensions/common/api/mojo_private.idl b/chromium/extensions/common/api/mojo_private.idl
new file mode 100644
index 00000000000..7a8ddd0621d
--- /dev/null
+++ b/chromium/extensions/common/api/mojo_private.idl
@@ -0,0 +1,23 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The chrome.mojoPrivate API provides access to the mojo modules.
+namespace mojoPrivate {
+
+ // |modules| is an array of the values returned by |dependencies| factories.
+ callback DefineCallback = void (any[] modules);
+
+ interface Functions {
+ // Defines a AMD module.
+ [nocompile] static void define(
+ DOMString moduleName,
+ optional DOMString[] dependencies,
+ DefineCallback factory);
+
+ // Returns a promise that will resolve to an asynchronously
+ // loaded module.
+ [nocompile] static any requireAsync(DOMString name);
+ };
+
+};
diff --git a/chromium/extensions/common/api/networking_config.idl b/chromium/extensions/common/api/networking_config.idl
new file mode 100644
index 00000000000..78b5bc6653e
--- /dev/null
+++ b/chromium/extensions/common/api/networking_config.idl
@@ -0,0 +1,103 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>networking.config</code> API to authenticate to captive
+// portals.
+namespace networking.config {
+ // Indicator for the type of network used in $(ref:NetworkInfo).
+ enum NetworkType { WiFi };
+
+ // A dictionary identifying filtered networks. One of <code>GUID</code>,
+ // <code>SSID</code> or <code>HexSSID</code> must be set. <code>BSSID</code>
+ // and <code>Security</code> are ignored when filtering networks.
+ dictionary NetworkInfo {
+ // Currently only WiFi supported.
+ NetworkType Type;
+
+ // A unique identifier of the network.
+ DOMString? GUID;
+
+ // A hex-encoded byte sequence.
+ DOMString? HexSSID;
+
+ // The decoded SSID of the network (default encoding is UTF-8). To filter
+ // for non-UTF-8 SSIDs, use HexSSID instead.
+ DOMString? SSID;
+
+ // The basic service set identification (BSSID) uniquely identifying the
+ // basic service set. <code>BSSID</code> is represented as a human readable,
+ // hex-encoded string with bytes separated by colons, e.g.
+ // 45:67:89:ab:cd:ef.
+ DOMString? BSSID;
+
+ // Identifier indicating the security type of the network. Valid values are
+ // <code>None</code>, <code>WEP-PSK</code>, <code>WPA-PSK</code> and
+ // <code>WPA-EAP</code>.
+ DOMString? Security;
+ };
+
+ // Argument to $(ref:finishAuthentication) indicating the result of the
+ // captive portal authentication attempt.
+ enum AuthenticationResult {
+ // The extension does not handle this network or captive portal (e.g. server
+ // end-point not found or not compatible).
+ unhandled,
+
+ // The extension handled this network and authenticated successfully.
+ succeeded,
+
+ // The extension handled this network, tried to authenticate, however was
+ // rejected by the server.
+ rejected,
+
+ // The extension handled this network, tried to authenticate, however failed
+ // due to an unspecified error.
+ failed
+ };
+
+ // Invoked by $(ref:setNetworkFilter) when the respective operation is
+ // finished.
+ callback SetNetworkFilterCallback = void();
+
+ // Invoked by $(ref:finishAuthentication) when the respective operation is
+ // finished.
+ callback FinishAuthenticationCallback = void();
+
+ interface Functions {
+ // Allows an extension to define network filters for the networks it can
+ // handle. A call to this function will remove all filters previously
+ // installed by the extension before setting the new list.
+ // |networks|: Network filters to set. Every <code>NetworkInfo</code> must
+ // either have the <code>SSID</code> or <code>HexSSID</code>
+ // set. Other fields will be ignored.
+ // |callback|: Called back when this operation is finished.
+ void setNetworkFilter(NetworkInfo[] networks,
+ SetNetworkFilterCallback callback);
+
+ // Called by the extension to notify the network config API that it finished
+ // a captive portal authentication attempt and hand over the result of the
+ // attempt. This function must only be called with the GUID of the latest
+ // $(ref:onCaptivePortalDetected) event.
+ // |GUID|: Unique network identifier obtained from
+ // $(ref:onCaptivePortalDetected).
+ // |result|: The result of the authentication attempt.
+ // |callback|: Called back when this operation is finished.
+ void finishAuthentication(DOMString GUID, AuthenticationResult result,
+ optional FinishAuthenticationCallback callback);
+ };
+
+ interface Events {
+ // This event fires everytime a captive portal is detected on a network
+ // matching any of the currently registered network filters and the user
+ // consents to use the extension for authentication. Network filters may be
+ // set using the $(ref:setNetworkFilter).
+ // Upon receiving this event the extension should start its authentication
+ // attempt with the captive portal. When the extension finishes its attempt,
+ // it must call $(ref:finishAuthentication) with the <code>GUID</code>
+ // received with this event and the appropriate authentication result.
+ // |networkInfo|: Information about the network on which a captive portal
+ // was detected.
+ static void onCaptivePortalDetected(NetworkInfo networkInfo);
+ };
+};
diff --git a/chromium/extensions/common/api/networking_private.idl b/chromium/extensions/common/api/networking_private.idl
new file mode 100644
index 00000000000..1df8ec7ace6
--- /dev/null
+++ b/chromium/extensions/common/api/networking_private.idl
@@ -0,0 +1,1053 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The <code>chrome.networkingPrivate</code> API is used for configuring
+// network connections (Cellular, Ethernet, VPN, WiFi or WiMAX). This private
+// API is only valid if called from a browser or app associated with the
+// primary user. See the Open Network Configuration (ONC) documentation for
+// descriptions of properties:
+// <a href="https://code.google.com/p/chromium/codesearch#chromium/src/components/onc/docs/onc_spec.html">
+// src/components/onc/docs/onc_spec.html</a>, or the
+// <a href="http://www.chromium.org/chromium-os/chromiumos-design-docs/open-network-configuration">
+// Open Network Configuration</a> page at chromium.org.
+// <br><br>
+// NOTE: Most dictionary properties and enum values use UpperCamelCase to match
+// the ONC spec instead of the JavaScript lowerCamelCase convention.
+// <br><br>
+// "State" properties describe just the ONC properties returned by
+// $(ref:networkingPrivate.getState) and $(ref:networkingPrivate.getNetworks).
+// <br><br>
+// "Config" properties describe just the ONC properties that can be configured
+// through this API. NOTE: Not all configuration properties are exposed at this
+// time, only those currently required by the Chrome Settings UI.
+// TODO(stevenjb): Provide all configuration properties and types,
+// crbug.com/380937.
+// <br><br>
+// TODO(stevenjb/pneubeck): Merge the ONC documentation with this document and
+// use it as the ONC specification.
+
+namespace networkingPrivate {
+ enum ActivationStateType {
+ Activated, Activating, NotActivated, PartiallyActivated
+ };
+
+ enum CaptivePortalStatus {
+ Unknown, Offline, Online, Portal, ProxyAuthRequired
+ };
+
+ enum ConnectionStateType {
+ Connected, Connecting, NotConnected
+ };
+
+ enum DeviceStateType {
+ // Device is available but not initialized.
+ Uninitialized,
+ // Device is initialized but not enabled.
+ Disabled,
+ // Enabled state has been requested but has not completed.
+ Enabling,
+ // Device is enabled.
+ Enabled,
+ // Device is prohibited.
+ Prohibited
+ };
+
+ enum IPConfigType {
+ DHCP, Static
+ };
+
+ enum NetworkType {
+ All, Cellular, Ethernet, VPN, Wireless, WiFi, WiMAX
+ };
+
+ enum ProxySettingsType {
+ Direct, Manual, PAC, WPAD
+ };
+
+ // Managed property types. These types all share a common structure:
+ // Active: For properties that are translated from the configuration
+ // manager (e.g. Shill), the 'active' value currently in use by the
+ // configuration manager.
+ // Effective: The effective source for the property: UserPolicy, DevicePolicy,
+ // UserSetting or SharedSetting.
+ // UserPolicy: The value provided by the user policy.
+ // DevicePolicy: The value provided by the device policy.
+ // UserSetting: The value set by the logged in user. Only provided if
+ // UserEditable is true (i.e. no policy affects the property or the
+ // policy provided value is recommened only).
+ // SharedSetting: The value set for all users of the device. Only provided if
+ // DeviceEditiable is true (i.e. no policy affects the property or the
+ // policy provided value is recommened only).
+ // UserEditable: True if a UserPolicy exists and allows the property to be
+ // edited (i.e. is a recommended value). Defaults to False.
+ // DeviceEditable: True if a DevicePolicy exists and allows the property to be
+ // edited (i.e. is a recommended value). Defaults to False.
+
+ dictionary ManagedBoolean {
+ boolean? Active;
+ DOMString? Effective;
+ boolean? UserPolicy;
+ boolean? DevicePolicy;
+ boolean? UserSetting;
+ boolean? SharedSetting;
+ boolean? UserEditable;
+ boolean? DeviceEditable;
+ };
+
+ dictionary ManagedLong {
+ long? Active;
+ DOMString? Effective;
+ long? UserPolicy;
+ long? DevicePolicy;
+ long? UserSetting;
+ long? SharedSetting;
+ boolean? UserEditable;
+ boolean? DeviceEditable;
+ };
+
+ dictionary ManagedDOMString {
+ DOMString? Active;
+ DOMString? Effective;
+ DOMString? UserPolicy;
+ DOMString? DevicePolicy;
+ DOMString? UserSetting;
+ DOMString? SharedSetting;
+ boolean? UserEditable;
+ boolean? DeviceEditable;
+ };
+
+ dictionary ManagedDOMStringList {
+ DOMString[]? Active;
+ DOMString? Effective;
+ DOMString[]? UserPolicy;
+ DOMString[]? DevicePolicy;
+ DOMString[]? UserSetting;
+ DOMString[]? SharedSetting;
+ boolean? UserEditable;
+ boolean? DeviceEditable;
+ };
+
+ dictionary ManagedIPConfigType {
+ IPConfigType? Active;
+ DOMString? Effective;
+ IPConfigType? UserPolicy;
+ IPConfigType? DevicePolicy;
+ IPConfigType? UserSetting;
+ IPConfigType? SharedSetting;
+ boolean? UserEditable;
+ boolean? DeviceEditable;
+ };
+
+ dictionary ManagedProxySettingsType {
+ ProxySettingsType? Active;
+ DOMString? Effective;
+ ProxySettingsType? UserPolicy;
+ ProxySettingsType? DevicePolicy;
+ ProxySettingsType? UserSetting;
+ ProxySettingsType? SharedSetting;
+ boolean? UserEditable;
+ boolean? DeviceEditable;
+ };
+
+ // Sub-dictionary types.
+
+ dictionary APNProperties {
+ DOMString AccessPointName;
+ DOMString? Language;
+ DOMString? LocalizedName;
+ DOMString? Name;
+ DOMString? Password;
+ DOMString? Username;
+ };
+
+ dictionary ManagedAPNProperties {
+ ManagedDOMString AccessPointName;
+ ManagedDOMString? Language;
+ ManagedDOMString? LocalizedName;
+ ManagedDOMString? Name;
+ ManagedDOMString? Password;
+ ManagedDOMString? Username;
+ };
+
+ dictionary ManagedAPNList {
+ APNProperties[]? Active;
+ DOMString? Effective;
+ APNProperties[]? UserPolicy;
+ APNProperties[]? DevicePolicy;
+ APNProperties[]? UserSetting;
+ APNProperties[]? SharedSetting;
+ boolean? UserEditable;
+ boolean? DeviceEditable;
+ };
+
+ dictionary CellularProviderProperties {
+ DOMString Name;
+ DOMString Code;
+ DOMString? Country;
+ };
+
+ dictionary CellularSimState {
+ // Whether or not a PIN should be required.
+ boolean requirePin;
+
+ // The current PIN (required for any change, even when the SIM is unlocked).
+ DOMString currentPin;
+
+ // If provided, change the PIN to |newPin|. |requirePin| must be true.
+ DOMString? newPin;
+ };
+
+ dictionary IssuerSubjectPattern {
+ DOMString? CommonName;
+ DOMString? Locality;
+ DOMString? Organization;
+ DOMString? OrganizationalUnit;
+ };
+
+ dictionary ManagedIssuerSubjectPattern {
+ ManagedDOMString? CommonName;
+ ManagedDOMString? Locality;
+ ManagedDOMString? Organization;
+ ManagedDOMString? OrganizationalUnit;
+ };
+
+ dictionary CertificatePattern {
+ DOMString[]? EnrollmentURI;
+ IssuerSubjectPattern? Issuer;
+ DOMString[]? IssuerCARef;
+ IssuerSubjectPattern? Subject;
+ };
+
+ dictionary ManagedCertificatePattern {
+ ManagedDOMStringList? EnrollmentURI;
+ ManagedIssuerSubjectPattern? Issuer;
+ ManagedDOMStringList? IssuerCARef;
+ ManagedIssuerSubjectPattern? Subject;
+ };
+
+ dictionary EAPProperties {
+ DOMString? AnonymousIdentity;
+ CertificatePattern? ClientCertPattern;
+ DOMString? ClientCertRef;
+ DOMString? ClientCertType;
+ DOMString? Identity;
+ DOMString? Inner;
+ DOMString Outer;
+ DOMString? Password;
+ boolean? SaveCredentials;
+ DOMString[]? ServerCARefs;
+ boolean? UseProactiveKeyCaching;
+ boolean? UseSystemCAs;
+ };
+
+ dictionary ManagedEAPProperties {
+ ManagedDOMString? AnonymousIdentity;
+ ManagedCertificatePattern? ClientCertPattern;
+ ManagedDOMString? ClientCertRef;
+ ManagedDOMString? ClientCertType;
+ ManagedDOMString? Identity;
+ ManagedDOMString? Inner;
+ ManagedDOMString Outer;
+ ManagedDOMString? Password;
+ ManagedBoolean? SaveCredentials;
+ ManagedDOMStringList? ServerCARefs;
+ ManagedBoolean? UseProactiveKeyCaching;
+ ManagedBoolean? UseSystemCAs;
+ };
+
+ dictionary FoundNetworkProperties {
+ DOMString Status;
+ DOMString NetworkId;
+ DOMString Technology;
+ DOMString? ShortName;
+ DOMString? LongName;
+ };
+
+ dictionary IPConfigProperties {
+ DOMString? Gateway;
+ DOMString? IPAddress;
+ DOMString[]? NameServers;
+ long? RoutingPrefix;
+ DOMString? Type;
+ DOMString? WebProxyAutoDiscoveryUrl;
+ };
+
+ dictionary ManagedIPConfigProperties {
+ ManagedDOMString? Gateway;
+ ManagedDOMString? IPAddress;
+ ManagedDOMString[]? NameServers;
+ ManagedLong? RoutingPrefix;
+ ManagedDOMString? Type;
+ ManagedDOMString? WebProxyAutoDiscoveryUrl;
+ };
+
+ dictionary XAUTHProperties {
+ DOMString? Password;
+ boolean? SaveCredentials;
+ DOMString? Username;
+ };
+
+ dictionary ManagedXAUTHProperties {
+ ManagedDOMString? Password;
+ ManagedBoolean? SaveCredentials;
+ ManagedDOMString? Username;
+ };
+
+ dictionary IPSecProperties {
+ DOMString AuthenticationType;
+ CertificatePattern? ClientCertPattern;
+ DOMString? ClientCertRef;
+ DOMString? ClientCertType;
+ EAPProperties? EAP;
+ DOMString? Group;
+ long? IKEVersion;
+ DOMString? PSK;
+ boolean? SaveCredentials;
+ DOMString[]? ServerCARefs;
+ XAUTHProperties? XAUTH;
+ };
+
+ dictionary ManagedIPSecProperties {
+ ManagedDOMString AuthenticationType;
+ ManagedCertificatePattern? ClientCertPattern;
+ ManagedDOMString? ClientCertRef;
+ ManagedDOMString? ClientCertType;
+ ManagedEAPProperties? EAP;
+ ManagedDOMString? Group;
+ ManagedLong IKEVersion;
+ ManagedDOMString? PSK;
+ ManagedBoolean? SaveCredentials;
+ ManagedDOMStringList? ServerCARefs;
+ ManagedXAUTHProperties? XAUTH;
+ };
+
+ dictionary L2TPProperties {
+ boolean? LcpEchoDisabled;
+ DOMString? Password;
+ boolean? SaveCredentials;
+ DOMString? Username;
+ };
+
+ dictionary ManagedL2TPProperties {
+ ManagedBoolean? LcpEchoDisabled;
+ ManagedDOMString? Password;
+ ManagedBoolean? SaveCredentials;
+ ManagedDOMString? Username;
+ };
+
+ dictionary PaymentPortal {
+ DOMString Method;
+ DOMString? PostData;
+ DOMString? Url;
+ };
+
+ dictionary ProxyLocation {
+ DOMString Host;
+ long Port;
+ };
+
+ dictionary ManagedProxyLocation {
+ ManagedDOMString Host;
+ ManagedLong Port;
+ };
+
+ dictionary ManualProxySettings {
+ ProxyLocation? HTTPProxy;
+ ProxyLocation? SecureHTTPProxy;
+ ProxyLocation? FTPProxy;
+ ProxyLocation? SOCKS;
+ };
+
+ dictionary ManagedManualProxySettings {
+ ManagedProxyLocation? HTTPProxy;
+ ManagedProxyLocation? SecureHTTPProxy;
+ ManagedProxyLocation? FTPProxy;
+ ManagedProxyLocation? SOCKS;
+ };
+
+ dictionary ProxySettings {
+ ProxySettingsType Type;
+ ManualProxySettings? Manual;
+ DOMString[]? ExcludeDomains;
+ DOMString? PAC;
+ };
+
+ dictionary ManagedProxySettings {
+ ManagedProxySettingsType Type;
+ ManagedManualProxySettings? Manual;
+ ManagedDOMStringList? ExcludeDomains;
+ ManagedDOMString? PAC;
+ };
+
+ dictionary VerifyX509 {
+ DOMString? Name;
+ DOMString? Type;
+ };
+
+ dictionary ManagedVerifyX509 {
+ ManagedDOMString? Name;
+ ManagedDOMString? Type;
+ };
+
+ dictionary OpenVPNProperties {
+ DOMString? Auth;
+ DOMString? AuthRetry;
+ boolean? AuthNoCache;
+ DOMString? Cipher;
+ DOMString? ClientCertRef;
+ CertificatePattern? ClientCertPattern;
+ DOMString? ClientCertType;
+ DOMString? CompLZO;
+ boolean? CompNoAdapt;
+ boolean? IgnoreDefaultRoute;
+ DOMString? KeyDirection;
+ DOMString? NsCertType;
+ DOMString? OTP;
+ DOMString? Password;
+ long? Port;
+ DOMString? Proto;
+ DOMString? PushPeerInfo;
+ DOMString? RemoteCertEKU;
+ DOMString[]? RemoteCertKU;
+ DOMString? RemoteCertTLS;
+ long? RenegSec;
+ boolean? SaveCredentials;
+ DOMString[]? ServerCARefs;
+ DOMString? ServerCertRef;
+ long? ServerPollTimeout;
+ long? Shaper;
+ DOMString? StaticChallenge;
+ DOMString? TLSAuthContents;
+ DOMString? TLSRemote;
+ DOMString? UserAuthenticationType;
+ DOMString? Username;
+ DOMString? Verb;
+ DOMString? VerifyHash;
+ VerifyX509? VerifyX509;
+ };
+
+ dictionary ManagedOpenVPNProperties {
+ ManagedDOMString? Auth;
+ ManagedDOMString? AuthRetry;
+ ManagedBoolean? AuthNoCache;
+ ManagedDOMString? Cipher;
+ ManagedDOMString? ClientCertRef;
+ ManagedCertificatePattern? ClientCertPattern;
+ ManagedDOMString? ClientCertType;
+ ManagedDOMString? CompLZO;
+ ManagedBoolean? CompNoAdapt;
+ ManagedBoolean? IgnoreDefaultRoute;
+ ManagedDOMString? KeyDirection;
+ ManagedDOMString? NsCertType;
+ ManagedDOMString? OTP;
+ ManagedDOMString? Password;
+ ManagedLong? Port;
+ ManagedDOMString? Proto;
+ ManagedDOMString? PushPeerInfo;
+ ManagedDOMString? RemoteCertEKU;
+ ManagedDOMString[]? RemoteCertKU;
+ ManagedDOMString? RemoteCertTLS;
+ ManagedLong? RenegSec;
+ ManagedBoolean? SaveCredentials;
+ ManagedDOMString[]? ServerCARefs;
+ ManagedDOMString? ServerCertRef;
+ ManagedLong? ServerPollTimeout;
+ ManagedLong? Shaper;
+ ManagedDOMString? StaticChallenge;
+ ManagedDOMString? TLSAuthContents;
+ ManagedDOMString? TLSRemote;
+ ManagedDOMString? UserAuthenticationType;
+ ManagedDOMString? Username;
+ ManagedDOMString? Verb;
+ ManagedDOMString? VerifyHash;
+ ManagedVerifyX509? VerifyX509;
+ };
+
+ dictionary SIMLockStatus {
+ DOMString LockType; // sim-pin, sim-puk, or ''
+ boolean LockEnabled;
+ long? RetriesLeft;
+ };
+
+ dictionary ThirdPartyVPNProperties {
+ DOMString ExtensionID;
+ DOMString? ProviderName;
+ };
+
+ dictionary ManagedThirdPartyVPNProperties {
+ ManagedDOMString ExtensionID;
+ DOMString? ProviderName;
+ };
+
+ // Network type dictionary types.
+
+ dictionary CellularProperties {
+ boolean? AutoConnect;
+ APNProperties? APN;
+ APNProperties[]? APNList;
+ DOMString? ActivationType;
+ ActivationStateType? ActivationState;
+ boolean? AllowRoaming;
+ DOMString? Carrier;
+ DOMString? ESN;
+ DOMString? Family;
+ DOMString? FirmwareRevision;
+ FoundNetworkProperties[]? FoundNetworks;
+ DOMString? HardwareRevision;
+ CellularProviderProperties? HomeProvider;
+ DOMString? ICCID;
+ DOMString? IMEI;
+ APNProperties? LastGoodAPN;
+ DOMString? Manufacturer;
+ DOMString? MDN;
+ DOMString? MEID;
+ DOMString? MIN;
+ DOMString? ModelID;
+ DOMString? NetworkTechnology;
+ PaymentPortal? PaymentPortal;
+ long? PRLVersion;
+ DOMString? RoamingState;
+ CellularProviderProperties? ServingOperator;
+ SIMLockStatus? SIMLockStatus;
+ boolean? SIMPresent;
+ boolean? SupportNetworkScan;
+ DOMString[]? SupportedCarriers;
+ };
+
+ dictionary ManagedCellularProperties {
+ ManagedBoolean? AutoConnect;
+ ManagedAPNProperties? APN;
+ ManagedAPNList? APNList;
+ DOMString? ActivationType;
+ ActivationStateType? ActivationState;
+ boolean? AllowRoaming;
+ ManagedDOMString? Carrier;
+ DOMString? ESN;
+ DOMString? Family;
+ DOMString? FirmwareRevision;
+ FoundNetworkProperties[]? FoundNetworks;
+ DOMString? HardwareRevision;
+ CellularProviderProperties[]? HomeProvider;
+ DOMString? ICCID;
+ DOMString? IMEI;
+ APNProperties? LastGoodAPN;
+ DOMString? Manufacturer;
+ DOMString? MDN;
+ DOMString? MEID;
+ DOMString? MIN;
+ DOMString? ModelID;
+ DOMString? NetworkTechnology;
+ PaymentPortal? PaymentPortal;
+ long? PRLVersion;
+ DOMString? RoamingState;
+ CellularProviderProperties? ServingOperator;
+ SIMLockStatus? SIMLockStatus;
+ boolean? SIMPresent;
+ boolean? SupportNetworkScan;
+ DOMString[]? SupportedCarriers;
+ };
+
+ dictionary CellularStateProperties {
+ ActivationStateType? ActivationState;
+ DOMString? NetworkTechnology;
+ DOMString? RoamingState;
+ boolean? SIMPresent;
+ long? SignalStrength;
+ };
+
+ dictionary EthernetProperties {
+ boolean? AutoConnect;
+ DOMString? Authentication;
+ EAPProperties? EAP;
+ };
+
+ dictionary ManagedEthernetProperties {
+ ManagedBoolean? AutoConnect;
+ ManagedDOMString? Authentication;
+ ManagedEAPProperties? EAP;
+ };
+
+ dictionary EthernetStateProperties {
+ DOMString Authentication;
+ };
+
+ dictionary VPNProperties {
+ boolean? AutoConnect;
+ DOMString? Host;
+ IPSecProperties? IPsec;
+ L2TPProperties? L2TP;
+ OpenVPNProperties? OpenVPN;
+ ThirdPartyVPNProperties? ThirdPartyVPN;
+ DOMString? Type;
+ };
+
+ dictionary ManagedVPNProperties {
+ ManagedBoolean? AutoConnect;
+ ManagedDOMString? Host;
+ ManagedIPSecProperties? IPsec;
+ ManagedL2TPProperties? L2TP;
+ ManagedOpenVPNProperties? OpenVPN;
+ ManagedThirdPartyVPNProperties? ThirdPartyVPN;
+ ManagedDOMString Type;
+ };
+
+ dictionary VPNStateProperties {
+ DOMString Type;
+ IPSecProperties? IPsec;
+ ThirdPartyVPNProperties? ThirdPartyVPN;
+ };
+
+ dictionary WiFiProperties {
+ boolean? AllowGatewayARPPolling;
+ boolean? AutoConnect;
+ DOMString? BSSID;
+ EAPProperties? EAP;
+ long? Frequency;
+ long[]? FrequencyList;
+ DOMString? HexSSID;
+ boolean? HiddenSSID;
+ DOMString? Passphrase;
+ long? RoamThreshold;
+ DOMString? SSID;
+ DOMString? Security;
+ long? SignalStrength;
+ };
+
+ dictionary ManagedWiFiProperties {
+ ManagedBoolean? AllowGatewayARPPolling;
+ ManagedBoolean? AutoConnect;
+ DOMString? BSSID;
+ ManagedEAPProperties? EAP;
+ long? Frequency;
+ long[]? FrequencyList;
+ ManagedDOMString? HexSSID;
+ ManagedBoolean? HiddenSSID;
+ ManagedDOMString? Passphrase;
+ ManagedLong? RoamThreshold;
+ ManagedDOMString? SSID;
+ ManagedDOMString Security;
+ long? SignalStrength;
+ };
+
+ dictionary WiFiStateProperties {
+ DOMString? BSSID;
+ long? Frequency;
+ DOMString Security;
+ long? SignalStrength;
+ };
+
+ dictionary WiMAXProperties {
+ boolean? AutoConnect;
+ EAPProperties? EAP;
+ long? SignalStrength;
+ };
+
+ dictionary ManagedWiMAXProperties {
+ ManagedBoolean? AutoConnect;
+ ManagedEAPProperties? EAP;
+ long? SignalStrength;
+ };
+
+ dictionary WiMAXStateProperties {
+ long? SignalStrength;
+ };
+
+ dictionary NetworkConfigProperties {
+ CellularProperties? Cellular;
+ EthernetProperties? Ethernet;
+ DOMString? GUID;
+ IPConfigType? IPAddressConfigType;
+ DOMString? Name;
+ IPConfigType? NameServersConfigType;
+ long? Priority;
+ ProxySettings? ProxySettings;
+ IPConfigProperties? StaticIPConfig;
+ NetworkType? Type;
+ VPNProperties? VPN;
+ WiFiProperties? WiFi;
+ WiMAXProperties? WiMAX;
+ };
+
+ dictionary NetworkProperties {
+ CellularProperties? Cellular;
+ boolean? Connectable;
+ ConnectionStateType? ConnectionState;
+ DOMString? ErrorState;
+ EthernetProperties? Ethernet;
+ DOMString GUID;
+ IPConfigType? IPAddressConfigType;
+ IPConfigProperties[]? IPConfigs;
+ DOMString? MacAddress;
+ DOMString? Name;
+ IPConfigType? NameServersConfigType;
+ long? Priority;
+ ProxySettings? ProxySettings;
+ boolean? RestrictedConnectivity;
+ IPConfigProperties? StaticIPConfig;
+ IPConfigProperties? SavedIPConfig;
+ DOMString? Source;
+ NetworkType Type;
+ VPNProperties? VPN;
+ WiFiProperties? WiFi;
+ WiMAXProperties? WiMAX;
+ };
+
+ dictionary ManagedProperties {
+ ManagedCellularProperties? Cellular;
+ boolean? Connectable;
+ ConnectionStateType? ConnectionState;
+ DOMString? ErrorState;
+ ManagedEthernetProperties? Ethernet;
+ DOMString GUID;
+ ManagedIPConfigType? IPAddressConfigType;
+ IPConfigProperties[]? IPConfigs;
+ DOMString? MacAddress;
+ ManagedDOMString? Name;
+ ManagedIPConfigType? NameServersConfigType;
+ ManagedLong? Priority;
+ ManagedProxySettings? ProxySettings;
+ boolean? RestrictedConnectivity;
+ ManagedIPConfigProperties? StaticIPConfig;
+ IPConfigProperties? SavedIPConfig;
+ DOMString? Source;
+ NetworkType Type;
+ ManagedVPNProperties? VPN;
+ ManagedWiFiProperties? WiFi;
+ ManagedWiMAXProperties? WiMAX;
+ };
+
+ dictionary NetworkStateProperties {
+ CellularStateProperties? Cellular;
+ boolean? Connectable;
+ ConnectionStateType? ConnectionState;
+ EthernetStateProperties? Ethernet;
+ DOMString? ErrorState;
+ DOMString GUID;
+ DOMString? Name;
+ long? Priority;
+ DOMString? Source;
+ NetworkType Type;
+ VPNStateProperties? VPN;
+ WiFiStateProperties? WiFi;
+ WiMAXStateProperties? WiMAX;
+ };
+
+ dictionary DeviceStateProperties {
+ // Set if the device is enabled. True if the device is currently scanning.
+ boolean? Scanning;
+
+ // Set to the SIM lock type if the device type is Cellular and the device
+ // is locked.
+ DOMString? SimLockType;
+
+ // Set to the SIM present state if the device type is Cellular.
+ boolean? SimPresent;
+
+ // The current state of the device.
+ DeviceStateType State;
+
+ // The network type associated with the device (Cellular, Ethernet, WiFi, or
+ // WiMAX).
+ NetworkType Type;
+ };
+
+ dictionary VerificationProperties {
+ // A string containing a PEM-encoded (including the 'BEGIN CERTIFICATE'
+ // header and 'END CERTIFICATE' footer) X.509 certificate for use in
+ // verifying the signed data.
+ DOMString certificate;
+
+ // An array of PEM-encoded X.509 intermediate certificate authority
+ // certificates. Each PEM-encoded certificate is expected to have the
+ // 'BEGIN CERTIFICATE' header and 'END CERTIFICATE' footer.
+ DOMString[]? intermediateCertificates;
+
+ // A string containing a base64-encoded RSAPublicKey ASN.1 structure,
+ // representing the public key to be used by
+ // $(ref:verifyAndEncryptCredentials) and $(ref:verifyAndEncryptData)
+ // methods.
+ DOMString publicKey;
+
+ // A string containing a base64-encoded random binary data for use in
+ // verifying the signed data.
+ DOMString nonce;
+
+ // A string containing the identifying data string signed by the device.
+ DOMString signedData;
+
+ // A string containing the serial number of the device.
+ DOMString deviceSerial;
+
+ // A string containing the SSID of the device. Should be empty for new
+ // configurations.
+ DOMString deviceSsid;
+
+ // A string containing the BSSID of the device. Should be empty for new
+ // configurations.
+ DOMString deviceBssid;
+ };
+
+ dictionary NetworkFilter {
+ // The type of networks to return.
+ NetworkType networkType;
+
+ // If true, only include visible (physically connected or in-range)
+ // networks. Defaults to 'false'.
+ boolean? visible;
+
+ // If true, only include configured (saved) networks. Defaults to 'false'.
+ boolean? configured;
+
+ // Maximum number of networks to return. Defaults to 1000 if unspecified.
+ // Use 0 for no limit.
+ long? limit;
+ };
+
+ callback VoidCallback = void();
+ callback BooleanCallback = void(boolean result);
+ callback StringCallback = void(DOMString result);
+ // TODO(stevenjb): Use NetworkProperties for |result| once defined.
+ callback GetPropertiesCallback = void(NetworkProperties result);
+ // TODO(stevenjb): Use ManagedNetworkProperties for |result| once defined.
+ callback GetManagedPropertiesCallback = void(ManagedProperties result);
+ callback GetStatePropertiesCallback = void(NetworkStateProperties result);
+ callback GetNetworksCallback = void(NetworkStateProperties[] result);
+ callback GetDeviceStatesCallback = void(DeviceStateProperties[] result);
+ callback GetEnabledNetworkTypesCallback = void(NetworkType[] result);
+ callback CaptivePortalStatusCallback = void(CaptivePortalStatus result);
+
+ // These functions all report failures via chrome.runtime.lastError.
+ interface Functions {
+ // Gets all the properties of the network with id networkGuid. Includes all
+ // properties of the network (read-only and read/write values).
+ // |networkGuid|: The GUID of the network to get properties for.
+ // |callback|: Called with the network properties when received.
+ static void getProperties(DOMString networkGuid,
+ GetPropertiesCallback callback);
+
+ // Gets the merged properties of the network with id networkGuid from the
+ // sources: User settings, shared settings, user policy, device policy and
+ // the currently active settings.
+ // |networkGuid|: The GUID of the network to get properties for.
+ // |callback|: Called with the managed network properties when received.
+ static void getManagedProperties(DOMString networkGuid,
+ GetManagedPropertiesCallback callback);
+
+ // Gets the cached read-only properties of the network with id networkGuid.
+ // This is meant to be a higher performance function than
+ // $(ref:getProperties), which requires a round trip to query the networking
+ // subsystem. The following properties are returned for all networks: GUID,
+ // Type, Name, WiFi.Security. Additional properties are provided for visible
+ // networks: ConnectionState, ErrorState, WiFi.SignalStrength,
+ // Cellular.NetworkTechnology, Cellular.ActivationState,
+ // Cellular.RoamingState.
+ // |networkGuid|: The GUID of the network to get properties for.
+ // |callback|: Called immediately with the network state properties.
+ static void getState(DOMString networkGuid,
+ GetStatePropertiesCallback callback);
+
+ // Sets the properties of the network with id networkGuid.
+ // |networkGuid|: The GUID of the network to set properties for.
+ // |properties|: The properties to set.
+ // |callback|: Called when the operation has completed.
+ static void setProperties(DOMString networkGuid,
+ NetworkConfigProperties properties,
+ optional VoidCallback callback);
+
+ // Creates a new network configuration from properties. If a matching
+ // configured network already exists, this will fail. Otherwise returns the
+ // guid of the new network.
+ // |shared|: If true, share this network configuration with other users.
+ // |properties|: The properties to configure the new network with.
+ // |callback|: Called with the GUID for the new network configuration once
+ // the network has been created.
+ static void createNetwork(boolean shared,
+ NetworkConfigProperties properties,
+ optional StringCallback callback);
+
+ // Forgets a network configuration by clearing any configured properties for
+ // the network with GUID 'networkGuid'. This may also include any other
+ // networks with matching identifiers (e.g. WiFi SSID and Security). If no
+ // such configuration exists, an error will be set and the operation will
+ // fail.
+ // |networkGuid|: The GUID of the network to forget.
+ // |callback|: Called when the operation has completed.
+ static void forgetNetwork(DOMString networkGuid,
+ optional VoidCallback callback);
+
+ // Returns a list of network objects with the same properties provided by
+ // $(ref:networkingPrivate.getState). A filter is provided to specify the
+ // type of networks returned and to limit the number of networks. Networks
+ // are ordered by the system based on their priority, with connected or
+ // connecting networks listed first.
+ // |filter|: Describes which networks to return.
+ // |callback|: Called with a dictionary of networks and their state
+ // properties when received.
+ static void getNetworks(NetworkFilter filter,
+ GetNetworksCallback callback);
+
+ // Deprecated. Please use $(ref:networkingPrivate.getNetworks) with
+ // filter.visible = true instead.
+ [deprecated="Use getNetworks."] static void getVisibleNetworks(
+ NetworkType networkType,
+ GetNetworksCallback callback);
+
+ // Deprecated. Please use $(ref:networkingPrivate.getDeviceStates) instead.
+ [deprecated="Use getDeviceStates."] static void getEnabledNetworkTypes(
+ GetEnabledNetworkTypesCallback callback);
+
+ // Returns a list of $(ref:networkingPrivate.DeviceStateProperties) objects.
+ // |callback|: Called with a list of devices and their state.
+ static void getDeviceStates(GetDeviceStatesCallback callback);
+
+ // Enables any devices matching the specified network type. Note, the type
+ // might represent multiple network types (e.g. 'Wireless').
+ // |networkType|: The type of network to enable.
+ static void enableNetworkType(NetworkType networkType);
+
+ // Disables any devices matching the specified network type. See note for
+ // $(ref:networkingPrivate.enableNetworkType).
+ // |networkType|: The type of network to disable.
+ static void disableNetworkType(NetworkType networkType);
+
+ // Requests that the networking subsystem scan for new networks and
+ // update the list returned by $(ref:getVisibleNetworks). This is only a
+ // request: the network subsystem can choose to ignore it. If the list
+ // is updated, then the $(ref:onNetworkListChanged) event will be fired.
+ static void requestNetworkScan();
+
+ // Starts a connection to the network with networkGuid.
+ // |networkGuid|: The GUID of the network to connect to.
+ // |callback|: Called when the connect request has been sent. Note: the
+ // connection may not have completed. Observe $(ref:onNetworksChanged)
+ // to be notified when a network state changes.
+ static void startConnect(DOMString networkGuid,
+ optional VoidCallback callback);
+
+ // Starts a disconnect from the network with networkGuid.
+ // |networkGuid|: The GUID of the network to disconnect from.
+ // |callback|: Called when the disconnect request has been sent. See note
+ // for $(ref:startConnect).
+ static void startDisconnect(DOMString networkGuid,
+ optional VoidCallback callback);
+
+ // Starts activation of the Cellular network with networkGuid. If called
+ // for a network that is already activated, or for a network with a carrier
+ // that can not be directly activated, this will show the account details
+ // page for the carrier if possible.
+ // |networkGuid|: The GUID of the Cellular network to activate.
+ // |carrier|: Optional name of carrier to activate.
+ // |callback|: Called when the activation request has been sent. See note
+ // for $(ref:startConnect).
+ static void startActivate(DOMString networkGuid,
+ optional DOMString carrier,
+ optional VoidCallback callback);
+
+ // Verifies that the device is a trusted device.
+ // |properties|: Properties of the destination to use in verifying that it
+ // is a trusted device.
+ // |callback|: A callback function that indicates whether or not the device
+ // is a trusted device.
+ static void verifyDestination(VerificationProperties properties,
+ BooleanCallback callback);
+
+ // Verifies that the device is a trusted device and retrieves encrypted
+ // network credentials.
+ // |properties|: Properties of the destination to use in verifying that it
+ // is a trusted device.
+ // |networkGuid|: The GUID of the Cellular network to activate.
+ // |callback|: A callback function that receives base64-encoded encrypted
+ // credential data to send to a trusted device.
+ static void verifyAndEncryptCredentials(VerificationProperties properties,
+ DOMString networkGuid,
+ StringCallback callback);
+
+ // Verifies that the device is a trusted device and encrypts supplied
+ // data with device public key.
+ // |properties|: Properties of the destination to use in verifying that it
+ // is a trusted device.
+ // |data|: A string containing the base64-encoded data to encrypt.
+ // |callback|: A callback function that receives base64-encoded encrypted
+ // data to send to a trusted device.
+ static void verifyAndEncryptData(VerificationProperties properties,
+ DOMString data,
+ StringCallback callback);
+
+ // Enables TDLS for WiFi traffic with a specified peer if available.
+ // |ip_or_mac_address|: The IP or MAC address of the peer with which to
+ // enable a TDLS connection.
+ // |enabled| If true, enable TDLS, otherwise disable TDLS.
+ // |callback|: A callback function that receives a string with an error or
+ // the current TDLS status. 'Failed' indicates that the request failed
+ // (e.g. MAC address lookup failed). 'Timeout' indicates that the lookup
+ // timed out. Otherwise a valid status is returned (see
+ // $(ref:getWifiTDLSStatus)).
+ static void setWifiTDLSEnabledState(DOMString ip_or_mac_address,
+ boolean enabled,
+ optional StringCallback callback);
+
+ // Returns the current TDLS status for the specified peer.
+ // |ip_or_mac_address|: The IP or MAC address of the peer.
+ // |callback|: A callback function that receives a string with the current
+ // TDLS status which can be 'Connected', 'Disabled', 'Disconnected',
+ // 'Nonexistent', or 'Unknown'.
+ static void getWifiTDLSStatus(DOMString ip_or_mac_address,
+ StringCallback callback);
+
+ // Returns captive portal status for the network matching 'networkGuid'.
+ // |networkGuid|: The GUID of the network to get captive portal status for.
+ // |callback|: A callback function that returns the results of the query for
+ // network captive portal status.
+ static void getCaptivePortalStatus(DOMString networkGuid,
+ CaptivePortalStatusCallback callback);
+
+ // Unlocks a Cellular SIM card.
+ // * If the SIM is PIN locked, |pin| will be used to unlock the SIM and
+ // the |puk| argument will be ignored if provided.
+ // * If the SIM is PUK locked, |puk| and |pin| must be provided. If the
+ // operation succeeds (|puk| is valid), the PIN will be set to |pin|.
+ // (If |pin| is empty or invalid the operation will fail).
+ // |networkGuid|: The GUID of the cellular network to unlock.
+ // |pin|: The current SIM PIN, or the new PIN if PUK is provided.
+ // |puk|: The operator provided PUK for unblocking a blocked SIM.
+ // |callback|: Called when the operation has completed.
+ static void unlockCellularSim(DOMString networkGuid,
+ DOMString pin,
+ optional DOMString puk,
+ optional VoidCallback callback);
+
+ // Sets whether or not SIM locking is enabled (i.e a PIN will be required
+ // when the device is powered) and changes the PIN if a new PIN is
+ // specified. If the new PIN is provided but not valid (e.g. too short)
+ // the operation will fail. This will not lock the SIM; that is handled
+ // automatically by the device. NOTE: If the SIM is locked, it must first be
+ // unlocked with unlockCellularSim() before this can be called (otherwise it
+ // will fail and chrome.runtime.lastError will be set to Error.SimLocked).
+ // |networkGuid|: The GUID of the cellular network to set the SIM state of.
+ // |simState|: The SIM state to set.
+ // |callback|: Called when the operation has completed.
+ static void setCellularSimState(DOMString networkGuid,
+ CellularSimState simState,
+ optional VoidCallback callback);
+ };
+
+ interface Events {
+ // Fired when the properties change on any of the networks. Sends a list of
+ // GUIDs for networks whose properties have changed.
+ static void onNetworksChanged(DOMString[] changes);
+
+ // Fired when the list of networks has changed. Sends a complete list of
+ // GUIDs for all the current networks.
+ static void onNetworkListChanged(DOMString[] changes);
+
+ // Fired when the list of devices has changed or any device state properties
+ // have changed.
+ static void onDeviceStateListChanged();
+
+ // Fired when a portal detection for a network completes. Sends the guid of
+ // the network and the corresponding captive portal status.
+ static void onPortalDetectionCompleted(DOMString networkGuid,
+ CaptivePortalStatus status);
+ };
+};
diff --git a/chromium/extensions/common/api/power.idl b/chromium/extensions/common/api/power.idl
new file mode 100644
index 00000000000..2a25353ac89
--- /dev/null
+++ b/chromium/extensions/common/api/power.idl
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.power</code> API to override the system's power
+// management features.
+namespace power {
+ [noinline_doc] enum Level {
+ // Prevent the system from sleeping in response to user inactivity.
+ system,
+
+ // Prevent the display from being turned off or dimmed or the system
+ // from sleeping in response to user inactivity.
+ display
+ };
+
+ interface Functions {
+ // Requests that power management be temporarily disabled. |level|
+ // describes the degree to which power management should be disabled.
+ // If a request previously made by the same app is still active, it
+ // will be replaced by the new request.
+ static void requestKeepAwake(Level level);
+
+ // Releases a request previously made via requestKeepAwake().
+ static void releaseKeepAwake();
+ };
+};
diff --git a/chromium/extensions/common/api/printer_provider.idl b/chromium/extensions/common/api/printer_provider.idl
new file mode 100644
index 00000000000..3de58fa051e
--- /dev/null
+++ b/chromium/extensions/common/api/printer_provider.idl
@@ -0,0 +1,109 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The <code>chrome.printerProvider</code> API exposes events used by print
+// manager to query printers controlled by extensions, to query their
+// capabilities and to submit print jobs to these printers.
+namespace printerProvider {
+ // Error codes returned in response to $(ref:onPrintRequested) event.
+ enum PrintError {
+ // Operation completed successfully.
+ OK,
+
+ // General failure.
+ FAILED,
+
+ // Print ticket is invalid. For example, ticket is inconsistent with
+ // capabilities or extension is not able to handle all settings from the
+ // ticket.
+ INVALID_TICKET,
+
+ // Document is invalid. For example, data may be corrupted or the format is
+ // incompatible with the extension.
+ INVALID_DATA
+ };
+
+ // Printer description for $(ref:onGetPrintersRequested) event.
+ dictionary PrinterInfo {
+ // Unique printer ID.
+ DOMString id;
+
+ // Printer's human readable name.
+ DOMString name;
+
+ // Printer's human readable description.
+ DOMString? description;
+ };
+
+ // Printing request parameters. Passed to $(ref:onPrintRequested) event.
+ dictionary PrintJob {
+ // ID of the printer which should handle the job.
+ DOMString printerId;
+
+ // The print job title.
+ DOMString title;
+
+ // Print ticket in
+ // <a href="https://developers.google.com/cloud-print/docs/cdd#cjt">
+ // CJT format</a>.
+ object ticket;
+
+ // The document content type. Supported formats are
+ // <code>"application/pdf"</code> and <code>"image/pwg-raster"</code>.
+ DOMString contentType;
+
+ // Blob containing the document data to print. Format must match
+ // |contentType|.
+ [instanceOf=Blob] object document;
+ };
+
+ callback PrintersCallback = void(PrinterInfo[] printerInfo);
+
+ callback PrinterInfoCallback = void(optional PrinterInfo printerInfo);
+
+ // |capabilities|: Device capabilities in
+ // <a href="https://developers.google.com/cloud-print/docs/cdd#cdd">CDD
+ // format</a>.
+ callback CapabilitiesCallback = void(object capabilities);
+
+ callback PrintCallback = void(PrintError result);
+
+ interface Events {
+ // Event fired when print manager requests printers provided by extensions.
+ // |resultCallback|: Callback to return printer list. Every listener must
+ // call callback exactly once.
+ static void onGetPrintersRequested(PrintersCallback resultCallback);
+
+ // Event fired when print manager requests information about a USB device
+ // that may be a printer.
+ // <p><em>Note:</em> An application should not rely on this event being
+ // fired more than once per device. If a connected device is supported it
+ // should be returned in the $(ref:onGetPrintersRequested) event.</p>
+ // |device|: The USB device.
+ // |resultCallback|: Callback to return printer info. The receiving listener
+ // must call callback exactly once. If the parameter to this callback is
+ // undefined that indicates that the application has determined that the
+ // device is not supported.
+ static void onGetUsbPrinterInfoRequested(
+ usb.Device device,
+ PrinterInfoCallback resultCallback);
+
+ // Event fired when print manager requests printer capabilities.
+ // |printerId|: Unique ID of the printer whose capabilities are requested.
+ // |resultCallback|: Callback to return device capabilities in
+ // <a href="https://developers.google.com/cloud-print/docs/cdd#cdd">CDD
+ // format</a>.
+ // The receiving listener must call callback exectly once.
+ static void onGetCapabilityRequested(DOMString printerId,
+ CapabilitiesCallback resultCallback);
+
+ // Event fired when print manager requests printing.
+ // |printJob|: The printing request parameters.
+ // |resultCallback|: Callback that should be called when the printing
+ // request is completed.
+ static void onPrintRequested(PrintJob printJob,
+ PrintCallback resultCallback);
+ };
+};
+
diff --git a/chromium/extensions/common/api/printer_provider/usb_printer_manifest_data.cc b/chromium/extensions/common/api/printer_provider/usb_printer_manifest_data.cc
new file mode 100644
index 00000000000..d8480139edc
--- /dev/null
+++ b/chromium/extensions/common/api/printer_provider/usb_printer_manifest_data.cc
@@ -0,0 +1,71 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/api/printer_provider/usb_printer_manifest_data.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "device/usb/usb_device.h"
+#include "device/usb/usb_device_filter.h"
+#include "extensions/common/api/extensions_manifest_types.h"
+#include "extensions/common/manifest_constants.h"
+
+using device::UsbDeviceFilter;
+
+namespace extensions {
+
+UsbPrinterManifestData::UsbPrinterManifestData() {
+}
+
+UsbPrinterManifestData::~UsbPrinterManifestData() {
+}
+
+// static
+const UsbPrinterManifestData* UsbPrinterManifestData::Get(
+ const Extension* extension) {
+ return static_cast<UsbPrinterManifestData*>(
+ extension->GetManifestData(manifest_keys::kUsbPrinters));
+}
+
+// static
+scoped_ptr<UsbPrinterManifestData> UsbPrinterManifestData::FromValue(
+ const base::Value& value,
+ base::string16* error) {
+ scoped_ptr<api::extensions_manifest_types::UsbPrinters> usb_printers =
+ api::extensions_manifest_types::UsbPrinters::FromValue(value, error);
+ if (!usb_printers) {
+ return nullptr;
+ }
+
+ scoped_ptr<UsbPrinterManifestData> result(new UsbPrinterManifestData());
+ for (const auto& input : usb_printers->filters) {
+ UsbDeviceFilter output;
+ output.SetVendorId(input.vendor_id);
+ if (input.product_id && input.interface_class) {
+ *error = base::ASCIIToUTF16(
+ "Only one of productId or interfaceClass may be specified.");
+ return nullptr;
+ }
+ if (input.product_id) {
+ output.SetProductId(*input.product_id);
+ }
+ if (input.interface_class) {
+ output.SetInterfaceClass(*input.interface_class);
+ if (input.interface_subclass) {
+ output.SetInterfaceSubclass(*input.interface_subclass);
+ if (input.interface_protocol) {
+ output.SetInterfaceProtocol(*input.interface_protocol);
+ }
+ }
+ }
+ result->filters_.push_back(output);
+ }
+ return result;
+}
+
+bool UsbPrinterManifestData::SupportsDevice(
+ const scoped_refptr<device::UsbDevice>& device) const {
+ return UsbDeviceFilter::MatchesAny(device, filters_);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/api/printer_provider/usb_printer_manifest_data.h b/chromium/extensions/common/api/printer_provider/usb_printer_manifest_data.h
new file mode 100644
index 00000000000..ed397f0ede0
--- /dev/null
+++ b/chromium/extensions/common/api/printer_provider/usb_printer_manifest_data.h
@@ -0,0 +1,46 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_API_PRINTER_PROVIDER_USB_PRINTER_MANIFEST_DATA_H_
+#define EXTENSIONS_COMMON_API_PRINTER_PROVIDER_USB_PRINTER_MANIFEST_DATA_H_
+
+#include <vector>
+
+#include "extensions/common/extension.h"
+
+namespace device {
+class UsbDevice;
+class UsbDeviceFilter;
+}
+
+namespace extensions {
+
+// The parsed form of the "usb_printers" manifest entry.
+class UsbPrinterManifestData : public Extension::ManifestData {
+ public:
+ UsbPrinterManifestData();
+ ~UsbPrinterManifestData() override;
+
+ // Gets the UsbPrinterManifestData for |extension|, or NULL if none was
+ // specified.
+ static const UsbPrinterManifestData* Get(const Extension* extension);
+
+ // Parses the data stored in |value|. Sets |error| and returns an empty
+ // scoped_ptr on failure.
+ static scoped_ptr<UsbPrinterManifestData> FromValue(const base::Value& value,
+ base::string16* error);
+
+ bool SupportsDevice(const scoped_refptr<device::UsbDevice>& device) const;
+
+ const std::vector<device::UsbDeviceFilter>& filters() const {
+ return filters_;
+ }
+
+ private:
+ std::vector<device::UsbDeviceFilter> filters_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_API_PRINTER_PROVIDER_USB_PRINTER_MANIFEST_DATA_H_
diff --git a/chromium/extensions/common/api/printer_provider/usb_printer_manifest_handler.cc b/chromium/extensions/common/api/printer_provider/usb_printer_manifest_handler.cc
new file mode 100644
index 00000000000..e356050b7e1
--- /dev/null
+++ b/chromium/extensions/common/api/printer_provider/usb_printer_manifest_handler.cc
@@ -0,0 +1,37 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/api/printer_provider/usb_printer_manifest_handler.h"
+
+#include "extensions/common/api/printer_provider/usb_printer_manifest_data.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+UsbPrinterManifestHandler::UsbPrinterManifestHandler() {
+}
+
+UsbPrinterManifestHandler::~UsbPrinterManifestHandler() {
+}
+
+bool UsbPrinterManifestHandler::Parse(Extension* extension,
+ base::string16* error) {
+ const base::Value* usb_printers = nullptr;
+ CHECK(extension->manifest()->Get(manifest_keys::kUsbPrinters, &usb_printers));
+ scoped_ptr<UsbPrinterManifestData> data =
+ UsbPrinterManifestData::FromValue(*usb_printers, error);
+ if (!data) {
+ return false;
+ }
+
+ extension->SetManifestData(manifest_keys::kUsbPrinters, data.release());
+ return true;
+}
+
+const std::vector<std::string> UsbPrinterManifestHandler::Keys() const {
+ return SingleKey(manifest_keys::kUsbPrinters);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/api/printer_provider/usb_printer_manifest_handler.h b/chromium/extensions/common/api/printer_provider/usb_printer_manifest_handler.h
new file mode 100644
index 00000000000..beccce254c1
--- /dev/null
+++ b/chromium/extensions/common/api/printer_provider/usb_printer_manifest_handler.h
@@ -0,0 +1,26 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_API_PRINTER_PROVIDER_USB_PRINTER_MANIFEST_HANDLER_H_
+#define EXTENSIONS_COMMON_API_PRINTER_PROVIDER_USB_PRINTER_MANIFEST_HANDLER_H_
+
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+// Parses the "usb_printers" manifest key.
+class UsbPrinterManifestHandler : public ManifestHandler {
+ public:
+ UsbPrinterManifestHandler();
+ ~UsbPrinterManifestHandler() override;
+
+ private:
+ // ManifestHandler overrides.
+ bool Parse(Extension* extension, base::string16* error) override;
+ const std::vector<std::string> Keys() const override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_API_PRINTER_PROVIDER_USB_PRINTER_MANIFEST_HANDLER_H_
diff --git a/chromium/extensions/common/api/printer_provider/usb_printer_manifest_unittest.cc b/chromium/extensions/common/api/printer_provider/usb_printer_manifest_unittest.cc
new file mode 100644
index 00000000000..cd35af12c1d
--- /dev/null
+++ b/chromium/extensions/common/api/printer_provider/usb_printer_manifest_unittest.cc
@@ -0,0 +1,45 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/usb/usb_device_filter.h"
+#include "extensions/common/api/printer_provider/usb_printer_manifest_data.h"
+#include "extensions/common/manifest_test.h"
+#include "extensions/common/value_builder.h"
+
+namespace extensions {
+
+class UsbPrinterManifestTest : public ManifestTest {
+ public:
+ UsbPrinterManifestTest() {}
+ ~UsbPrinterManifestTest() override {}
+};
+
+TEST_F(UsbPrinterManifestTest, Filters) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("usb_printers_filters.json");
+ const UsbPrinterManifestData* manifest_data =
+ UsbPrinterManifestData::Get(extension.get());
+ ASSERT_TRUE(manifest_data);
+ EXPECT_EQ(2u, manifest_data->filters().size());
+ EXPECT_TRUE(DictionaryBuilder()
+ .Set("vendorId", 1)
+ .Set("productId", 2)
+ .Build()
+ ->Equals(manifest_data->filters()[0].ToValue().get()));
+ EXPECT_TRUE(DictionaryBuilder()
+ .Set("vendorId", 1)
+ .Set("interfaceClass", 2)
+ .Set("interfaceSubclass", 3)
+ .Set("interfaceProtocol", 4)
+ .Build()
+ ->Equals(manifest_data->filters()[1].ToValue().get()));
+}
+
+TEST_F(UsbPrinterManifestTest, InvalidFilter) {
+ LoadAndExpectError(
+ "usb_printers_invalid_filter.json",
+ "Only one of productId or interfaceClass may be specified.");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/api/printer_provider_internal.idl b/chromium/extensions/common/api/printer_provider_internal.idl
new file mode 100644
index 00000000000..d34e472ffff
--- /dev/null
+++ b/chromium/extensions/common/api/printer_provider_internal.idl
@@ -0,0 +1,71 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// printerProviderInternal
+// Internal API used to run callbacks passed to chrome.printerProvider API
+// events.
+// When dispatching a chrome.printerProvider API event, its arguments will be
+// massaged in custom bindings so a callback is added. The callback uses
+// chrome.printerProviderInternal API to report the event results.
+// In order to identify the event for which the callback is called, the event
+// is internally dispatched having a requestId argument (which is removed from
+// the argument list before the event actually reaches the event listeners). The
+// requestId is forwarded to the chrome.printerProviderInternal API functions.
+namespace printerProviderInternal {
+ // Same as in printerProvider.PrintError enum API.
+ enum PrintError { OK, FAILED, INVALID_TICKET, INVALID_DATA };
+
+ // Information needed by a renderer to create a blob instance.
+ dictionary BlobInfo {
+ // The blob UUID.
+ DOMString blobUuid;
+
+ // The blob content type.
+ DOMString type;
+
+ // The blob size.
+ long size;
+ };
+
+ // Callback carrying information needed by a renderer to create a blob.
+ callback BlobCallback = void(BlobInfo blobInfo);
+
+ interface Functions {
+ // Runs callback to printerProvider.onGetPrintersRequested event.
+ // |requestId|: Parameter identifying the event instance for which the
+ // callback is run.
+ // |printers|: List of printers reported by the extension.
+ void reportPrinters(long requestId,
+ optional printerProvider.PrinterInfo[] printers);
+
+ // Runs callback to printerProvider.onUsbAccessGranted event.
+ // |requestId|: Parameter identifying the event instance for which the
+ // callback is run.
+ // |printerInfo|: Printer information reported by the extension.
+ void reportUsbPrinterInfo(long requestId,
+ optional printerProvider.PrinterInfo printerInfo);
+
+ // Runs callback to printerProvider.onGetCapabilityRequested event.
+ // |requestId|: Parameter identifying the event instance for which the
+ // callback is run.
+ // |error|: The printer capability returned by the extension.
+ void reportPrinterCapability(long request_id, optional object capability);
+
+ // Runs callback to printerProvider.onPrintRequested event.
+ // |requestId|: Parameter identifying the event instance for which the
+ // callback is run.
+ // |error|: The requested print job result.
+ void reportPrintResult(long request_id, optional PrintError error);
+
+ // Gets information needed to create a print data blob for a print request.
+ // The blob will be dispatched to the extension via
+ // printerProvider.onPrintRequested event.
+ // |requestId|: The request id for the print request for which data is
+ // needed.
+ // |callback|: Callback called with the information needed to create a blob
+ // of print data.
+ void getPrintData(long requestId, BlobCallback callback);
+ };
+};
+
diff --git a/chromium/extensions/common/api/runtime.json b/chromium/extensions/common/api/runtime.json
new file mode 100644
index 00000000000..d4a4e5980ca
--- /dev/null
+++ b/chromium/extensions/common/api/runtime.json
@@ -0,0 +1,533 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Note: Many of these functions and events are implemented by hand and should
+// not elicit any code generation from the schema compiler. These items are
+// marked "nocompile."
+[
+ {
+ "namespace": "runtime",
+ "description": "Use the <code>chrome.runtime</code> API to retrieve the background page, return details about the manifest, and listen for and respond to events in the app or extension lifecycle. You can also use this API to convert the relative path of URLs to fully-qualified URLs.",
+ "types": [
+ {
+ "id": "Port",
+ "type": "object",
+ "nocompile": true,
+ "description": "An object which allows two way communication with other pages.",
+ "properties": {
+ "name": {"type": "string"},
+ "disconnect": { "type": "function" },
+ "onDisconnect": { "$ref": "events.Event" },
+ "onMessage": { "$ref": "events.Event" },
+ "postMessage": {"type": "function"},
+ "sender": {
+ "$ref": "MessageSender",
+ "optional": true,
+ "description": "This property will <b>only</b> be present on ports passed to onConnect/onConnectExternal listeners."
+ }
+ },
+ "additionalProperties": { "type": "any"}
+ },
+ {
+ "id": "MessageSender",
+ "type": "object",
+ "nocompile": true,
+ "description": "An object containing information about the script context that sent a message or request.",
+ "properties": {
+ "tab": {"$ref": "tabs.Tab", "optional": true, "description": "The $(ref:tabs.Tab) which opened the connection, if any. This property will <strong>only</strong> be present when the connection was opened from a tab (including content scripts), and <strong>only</strong> if the receiver is an extension, not an app.", "extension_types": ["extension", "legacy_packaged_app"]},
+ "frameId": {"type": "integer", "optional": true, "description": "The <a href='webNavigation#frame_ids'>frame</a> that opened the connection. 0 for top-level frames, positive for child frames. This will only be set when <code>tab</code> is set.", "extension_types": ["extension", "legacy_packaged_app"]},
+ "guestProcessId": {"type": "integer", "optional": true, "nodoc": true, "description": "The guest process id of the requesting webview, if available. Only available for component extensions.", "extension_types": ["extension"]},
+ "guestRenderFrameRoutingId": {"type": "integer", "optional": true, "nodoc": true, "description": "The guest render frame routing id of the requesting webview, if available. Only available for component extensions.", "extension_types": ["extension"]},
+ "id": {"type": "string", "optional": true, "description": "The ID of the extension or app that opened the connection, if any."},
+ "url": {"type": "string", "optional": true, "description": "The URL of the page or frame that opened the connection. If the sender is in an iframe, it will be iframe's URL not the URL of the page which hosts it."},
+ "tlsChannelId": {"type": "string", "optional": true, "description": "The TLS channel ID of the page or frame that opened the connection, if requested by the extension or app, and if available."}
+ }
+ },
+ {
+ "id": "PlatformOs",
+ "type": "string",
+ "description": "The operating system chrome is running on.",
+ "enum": ["mac", "win", "android", "cros", "linux", "openbsd"]
+ },
+ {
+ "id": "PlatformArch",
+ "type": "string",
+ "enum": ["arm", "x86-32", "x86-64"],
+ "description": "The machine's processor architecture."
+ },
+ {
+ "id": "PlatformNaclArch",
+ "description": "The native client architecture. This may be different from arch on some platforms.",
+ "type": "string",
+ "enum": ["arm", "x86-32", "x86-64"]
+ },
+ {
+ "id": "PlatformInfo",
+ "type": "object",
+ "description": "An object containing information about the current platform.",
+ "properties": {
+ "os": {
+ "$ref": "PlatformOs",
+ "description": "The operating system chrome is running on."
+ },
+ "arch": {
+ "$ref": "PlatformArch",
+ "description": "The machine's processor architecture."
+ },
+ "nacl_arch" : {
+ "description": "The native client architecture. This may be different from arch on some platforms.",
+ "$ref": "PlatformNaclArch"
+ }
+ }
+ },
+ {
+ "id": "RequestUpdateCheckStatus",
+ "type": "string",
+ "enum": ["throttled", "no_update", "update_available"],
+ "description": "Result of the update check."
+ },
+ {
+ "id": "OnInstalledReason",
+ "type": "string",
+ "enum": ["install", "update", "chrome_update", "shared_module_update"],
+ "description": "The reason that this event is being dispatched."
+ },
+ {
+ "id": "OnRestartRequiredReason",
+ "type": "string",
+ "description": "The reason that the event is being dispatched. 'app_update' is used when the restart is needed because the application is updated to a newer version. 'os_update' is used when the restart is needed because the browser/OS is updated to a newer version. 'periodic' is used when the system runs for more than the permitted uptime set in the enterprise policy.",
+ "enum": ["app_update", "os_update", "periodic"]
+ }
+ ],
+ "properties": {
+ "lastError": {
+ "type": "object",
+ "optional": true,
+ "description": "This will be defined during an API method callback if there was an error",
+ "properties": {
+ "message": {
+ "optional": true,
+ "type": "string",
+ "description": "Details about the error which occurred."
+ }
+ }
+ },
+ "id": {
+ "type": "string",
+ "description": "The ID of the extension/app."
+ }
+ },
+ "functions": [
+ {
+ "name": "getBackgroundPage",
+ "type": "function",
+ "description": "Retrieves the JavaScript 'window' object for the background page running inside the current extension/app. If the background page is an event page, the system will ensure it is loaded before calling the callback. If there is no background page, an error is set.",
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "backgroundPage",
+ // Note: Only optional because we don't support validation
+ // for custom callbacks.
+ "optional": true,
+ "type": "object",
+ "isInstanceOf": "Window",
+ "additionalProperties": { "type": "any" },
+ "description": "The JavaScript 'window' object for the background page."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "openOptionsPage",
+ "type": "function",
+ "description": "<p>Open your Extension's options page, if possible.</p><p>The precise behavior may depend on your manifest's <code><a href=\"optionsV2\">options_ui</a></code> or <code><a href=\"options\">options_page</a></code> key, or what Chrome happens to support at the time. For example, the page may be opened in a new tab, within chrome://extensions, within an App, or it may just focus an open options page. It will never cause the caller page to reload.</p><p>If your Extension does not declare an options page, or Chrome failed to create one for some other reason, the callback will set $(ref:lastError).</p>",
+ "parameters": [{
+ "type": "function",
+ "name": "callback",
+ "parameters": [],
+ "optional": true
+ }]
+ },
+ {
+ "name": "getManifest",
+ "description": "Returns details about the app or extension from the manifest. The object returned is a serialization of the full <a href=\"manifest.html\">manifest file</a>.",
+ "type": "function",
+ "nocompile": true,
+ "parameters": [],
+ "returns": {
+ "type": "object",
+ "properties": {},
+ "additionalProperties": { "type": "any" },
+ "description": "The manifest details."
+ }
+ },
+ {
+ "name": "getURL",
+ "type": "function",
+ "nocompile": true,
+ "description": "Converts a relative path within an app/extension install directory to a fully-qualified URL.",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "path",
+ "description": "A path to a resource within an app/extension expressed relative to its install directory."
+ }
+ ],
+ "returns": {
+ "type": "string",
+ "description": "The fully-qualified URL to the resource."
+ }
+ },
+ {
+ "name": "setUninstallURL",
+ "type": "function",
+ "description": "Sets the URL to be visited upon uninstallation. This may be used to clean up server-side data, do analytics, and implement surveys. Maximum 255 characters.",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "url",
+ "maxLength": 255,
+ "description": "URL to be opened after the extension is uninstalled. This URL must have an http: or https: scheme. Set an empty string to not open a new tab upon uninstallation."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "description": "Called when the uninstall URL is set. If the given URL is invalid, $(ref:runtime.lastError) will be set.",
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "reload",
+ "description": "Reloads the app or extension. This method is not supported in kiosk mode. For kiosk mode, use chrome.runtime.restart() method.",
+ "type": "function",
+ "parameters": []
+ },
+ {
+ "name": "requestUpdateCheck",
+ "type": "function",
+ "description": "Requests an update check for this app/extension.",
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "status",
+ "$ref": "RequestUpdateCheckStatus",
+ "description": "Result of the update check."
+ },
+ {
+ "name": "details",
+ "type": "object",
+ "optional": true,
+ "properties": {
+ "version": {
+ "type": "string",
+ "description": "The version of the available update."
+ }
+ },
+ "description": "If an update is available, this contains more information about the available update."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "restart",
+ "description": "Restart the ChromeOS device when the app runs in kiosk mode. Otherwise, it's no-op.",
+ "type": "function",
+ "parameters": []
+ },
+ {
+ "name": "connect",
+ "type": "function",
+ "nocompile": true,
+ "description": "Attempts to connect to connect listeners within an extension/app (such as the background page), or other extensions/apps. This is useful for content scripts connecting to their extension processes, inter-app/extension communication, and <a href=\"manifest/externally_connectable.html\">web messaging</a>. Note that this does not connect to any listeners in a content script. Extensions may connect to content scripts embedded in tabs via <a href=\"extensions/tabs#method-connect\">tabs.connect</a>.",
+ "parameters": [
+ {"type": "string", "name": "extensionId", "optional": true, "description": "The ID of the extension or app to connect to. If omitted, a connection will be attempted with your own extension. Required if sending messages from a web page for <a href=\"manifest/externally_connectable.html\">web messaging</a>."},
+ {
+ "type": "object",
+ "name": "connectInfo",
+ "properties": {
+ "name": { "type": "string", "optional": true, "description": "Will be passed into onConnect for processes that are listening for the connection event." },
+ "includeTlsChannelId": { "type": "boolean", "optional": true, "description": "Whether the TLS channel ID will be passed into onConnectExternal for processes that are listening for the connection event." }
+ },
+ "optional": true
+ }
+ ],
+ "returns": {
+ "$ref": "Port",
+ "description": "Port through which messages can be sent and received. The port's $(ref:runtime.Port onDisconnect) event is fired if the extension/app does not exist. "
+ }
+ },
+ {
+ "name": "connectNative",
+ "type": "function",
+ "nocompile": true,
+ "description": "Connects to a native application in the host machine.",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "application",
+ "description": "The name of the registered application to connect to."
+ }
+ ],
+ "returns": {
+ "$ref": "Port",
+ "description": "Port through which messages can be sent and received with the application"
+ }
+ },
+ {
+ "name": "sendMessage",
+ "type": "function",
+ "nocompile": true,
+ "allowAmbiguousOptionalArguments": true,
+ "description": "Sends a single message to event listeners within your extension/app or a different extension/app. Similar to $(ref:runtime.connect) but only sends a single message, with an optional response. If sending to your extension, the $(ref:runtime.onMessage) event will be fired in each page, or $(ref:runtime.onMessageExternal), if a different extension. Note that extensions cannot send messages to content scripts using this method. To send messages to content scripts, use <a href=\"extensions/tabs#method-sendMessage\">tabs.sendMessage</a>.",
+ "parameters": [
+ {"type": "string", "name": "extensionId", "optional": true, "description": "The ID of the extension/app to send the message to. If omitted, the message will be sent to your own extension/app. Required if sending messages from a web page for <a href=\"manifest/externally_connectable.html\">web messaging</a>."},
+ { "type": "any", "name": "message" },
+ {
+ "type": "object",
+ "name": "options",
+ "properties": {
+ "includeTlsChannelId": { "type": "boolean", "optional": true, "description": "Whether the TLS channel ID will be passed into onMessageExternal for processes that are listening for the connection event." }
+ },
+ "optional": true
+ },
+ {
+ "type": "function",
+ "name": "responseCallback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "response",
+ "type": "any",
+ "description": "The JSON response object sent by the handler of the message. If an error occurs while connecting to the extension, the callback will be called with no arguments and $(ref:runtime.lastError) will be set to the error message."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "sendNativeMessage",
+ "type": "function",
+ "nocompile": true,
+ "description": "Send a single message to a native application.",
+ "parameters": [
+ {
+ "name": "application",
+ "description": "The name of the native messaging host.",
+ "type": "string"
+ },
+ {
+ "name": "message",
+ "description": "The message that will be passed to the native messaging host.",
+ "type": "object",
+ "additionalProperties": {
+ "type": "any"
+ }
+ },
+ {
+ "type": "function",
+ "name": "responseCallback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "response",
+ "type": "any",
+ "description": "The response message sent by the native messaging host. If an error occurs while connecting to the native messaging host, the callback will be called with no arguments and $(ref:runtime.lastError) will be set to the error message.",
+ "additionalProperties": {
+ "type": "any"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getPlatformInfo",
+ "type": "function",
+ "description": "Returns information about the current platform.",
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "description": "Called with results",
+ "parameters": [
+ {
+ "name": "platformInfo",
+ "$ref": "PlatformInfo"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getPackageDirectoryEntry",
+ "type": "function",
+ "description": "Returns a DirectoryEntry for the package directory.",
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "directoryEntry",
+ "type": "object",
+ "additionalProperties": { "type": "any" },
+ "isInstanceOf": "DirectoryEntry"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "onStartup",
+ "type": "function",
+ "description": "Fired when a profile that has this extension installed first starts up. This event is not fired when an incognito profile is started, even if this extension is operating in 'split' incognito mode."
+ },
+ {
+ "name": "onInstalled",
+ "type": "function",
+ "description": "Fired when the extension is first installed, when the extension is updated to a new version, and when Chrome is updated to a new version.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "details",
+ "properties": {
+ "reason": {
+ "$ref": "OnInstalledReason",
+ "description": "The reason that this event is being dispatched."
+ },
+ "previousVersion": {
+ "type": "string",
+ "optional": true,
+ "description": "Indicates the previous version of the extension, which has just been updated. This is present only if 'reason' is 'update'."
+ },
+ "id": {
+ "type": "string",
+ "optional": true,
+ "description": "Indicates the ID of the imported shared module extension which updated. This is present only if 'reason' is 'shared_module_update'."
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "onSuspend",
+ "type": "function",
+ "description": "Sent to the event page just before it is unloaded. This gives the extension opportunity to do some clean up. Note that since the page is unloading, any asynchronous operations started while handling this event are not guaranteed to complete. If more activity for the event page occurs before it gets unloaded the onSuspendCanceled event will be sent and the page won't be unloaded. "
+ },
+ {
+ "name": "onSuspendCanceled",
+ "type": "function",
+ "description": "Sent after onSuspend to indicate that the app won't be unloaded after all."
+ },
+ {
+ "name": "onUpdateAvailable",
+ "type": "function",
+ "description": "Fired when an update is available, but isn't installed immediately because the app is currently running. If you do nothing, the update will be installed the next time the background page gets unloaded, if you want it to be installed sooner you can explicitly call chrome.runtime.reload(). If your extension is using a persistent background page, the background page of course never gets unloaded, so unless you call chrome.runtime.reload() manually in response to this event the update will not get installed until the next time chrome itself restarts. If no handlers are listening for this event, and your extension has a persistent background page, it behaves as if chrome.runtime.reload() is called in response to this event.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "details",
+ "properties": {
+ "version": {
+ "type": "string",
+ "description": "The version number of the available update."
+ }
+ },
+ "additionalProperties": { "type": "any" },
+ "description": "The manifest details of the available update."
+ }
+ ]
+ },
+ {
+ // TODO(xiyuan): onBrowserUpdateAvailable is deprecated in favor of
+ // onRestartRequired. We should remove it when we are sure it is unused.
+ "name": "onBrowserUpdateAvailable",
+ "type": "function",
+ "description": "Fired when a Chrome update is available, but isn't installed immediately because a browser restart is required.",
+ "deprecated": "Please use $(ref:runtime.onRestartRequired).",
+ "parameters": []
+ },
+ {
+ "name": "onConnect",
+ "type": "function",
+ "nocompile": true,
+ "options": {
+ "unmanaged": true
+ },
+ "description": "Fired when a connection is made from either an extension process or a content script.",
+ "parameters": [
+ {"$ref": "Port", "name": "port"}
+ ]
+ },
+ {
+ "name": "onConnectExternal",
+ "type": "function",
+ "nocompile": true,
+ "description": "Fired when a connection is made from another extension.",
+ "parameters": [
+ {"$ref": "Port", "name": "port"}
+ ]
+ },
+ {
+ "name": "onMessage",
+ "type": "function",
+ "nocompile": true,
+ "options": {
+ "unmanaged": true
+ },
+ "description": "Fired when a message is sent from either an extension process or a content script.",
+ "parameters": [
+ {"name": "message", "type": "any", "optional": true, "description": "The message sent by the calling script."},
+ {"name": "sender", "$ref": "MessageSender" },
+ {"name": "sendResponse", "type": "function", "description": "Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object. If you have more than one <code>onMessage</code> listener in the same document, then only one may send a response. This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until <code>sendResponse</code> is called)." }
+ ],
+ "returns": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Return true from the event listener if you wish to call <code>sendResponse</code> after the event listener returns."
+ }
+ },
+ {
+ "name": "onMessageExternal",
+ "type": "function",
+ "nocompile": true,
+ "description": "Fired when a message is sent from another extension/app. Cannot be used in a content script.",
+ "parameters": [
+ {"name": "message", "type": "any", "optional": true, "description": "The message sent by the calling script."},
+ {"name": "sender", "$ref": "MessageSender" },
+ {"name": "sendResponse", "type": "function", "description": "Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object. If you have more than one <code>onMessage</code> listener in the same document, then only one may send a response. This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until <code>sendResponse</code> is called)." }
+ ],
+ "returns": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Return true from the event listener if you wish to call <code>sendResponse</code> after the event listener returns."
+ }
+ },
+ {
+ "name": "onRestartRequired",
+ "type": "function",
+ "description": "Fired when an app or the device that it runs on needs to be restarted. The app should close all its windows at its earliest convenient time to let the restart to happen. If the app does nothing, a restart will be enforced after a 24-hour grace period has passed. Currently, this event is only fired for Chrome OS kiosk apps.",
+ "parameters": [
+ {
+ "$ref": "OnRestartRequiredReason",
+ "name": "reason",
+ "description": "The reason that the event is being dispatched."
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/chromium/extensions/common/api/schemas.gni b/chromium/extensions/common/api/schemas.gni
new file mode 100644
index 00000000000..d584aa6b37b
--- /dev/null
+++ b/chromium/extensions/common/api/schemas.gni
@@ -0,0 +1,17 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+gypi_values = exec_script("//build/gypi_to_gn.py",
+ [ rebase_path("schemas.gypi") ],
+ "scope",
+ [ "schemas.gypi" ])
+
+sources = gypi_values.schema_files
+if (is_chromeos) {
+ sources += gypi_values.chromeos_schema_files
+}
+
+uncompiled_sources = gypi_values.non_compiled_schema_files
+
+root_namespace = "extensions::api::%(namespace)s"
diff --git a/chromium/extensions/common/api/serial.idl b/chromium/extensions/common/api/serial.idl
new file mode 100644
index 00000000000..94029dcc1c4
--- /dev/null
+++ b/chromium/extensions/common/api/serial.idl
@@ -0,0 +1,354 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.serial</code> API to read from and write to a device
+// connected to a serial port.
+namespace serial {
+
+ dictionary DeviceInfo {
+ // The device's system path. This should be passed as the <code>path</code>
+ // argument to <code>chrome.serial.connect</code> in order to connect to
+ // this device.
+ DOMString path;
+
+ // A PCI or USB vendor ID if one can be determined for the underlying
+ // device.
+ long? vendorId;
+
+ // A USB product ID if one can be determined for the underlying device.
+ long? productId;
+
+ // A human-readable display name for the underlying device if one can be
+ // queried from the host driver.
+ DOMString? displayName;
+ };
+
+ callback GetDevicesCallback = void (DeviceInfo[] ports);
+
+ enum DataBits { seven, eight };
+ enum ParityBit { no, odd, even };
+ enum StopBits { one, two };
+
+ dictionary ConnectionOptions {
+ // Flag indicating whether or not the connection should be left open when
+ // the application is suspended (see
+ // <a href="http://developer.chrome.com/apps/app_lifecycle.html">Manage App
+ // Lifecycle</a>). The default value is "false." When the application is
+ // loaded, any serial connections previously opened with persistent=true
+ // can be fetched with <code>getConnections</code>.
+ boolean? persistent;
+
+ // An application-defined string to associate with the connection.
+ DOMString? name;
+
+ // The size of the buffer used to receive data. The default value is 4096.
+ long? bufferSize;
+
+ // The requested bitrate of the connection to be opened. For compatibility
+ // with the widest range of hardware, this number should match one of
+ // commonly-available bitrates, such as 110, 300, 1200, 2400, 4800, 9600,
+ // 14400, 19200, 38400, 57600, 115200. There is no guarantee, of course,
+ // that the device connected to the serial port will support the requested
+ // bitrate, even if the port itself supports that bitrate. <code>9600</code>
+ // will be passed by default.
+ long? bitrate;
+
+ // <code>"eight"</code> will be passed by default.
+ DataBits? dataBits;
+
+ // <code>"no"</code> will be passed by default.
+ ParityBit? parityBit;
+
+ // <code>"one"</code> will be passed by default.
+ StopBits? stopBits;
+
+ // Flag indicating whether or not to enable RTS/CTS hardware flow control.
+ // Defaults to false.
+ boolean? ctsFlowControl;
+
+ // The maximum amount of time (in milliseconds) to wait for new data before
+ // raising an <code>onReceiveError</code> event with a "timeout" error.
+ // If zero, receive timeout errors will not be raised for the connection.
+ // Defaults to 0.
+ long? receiveTimeout;
+
+ // The maximum amount of time (in milliseconds) to wait for a
+ // <code>send</code> operation to complete before calling the callback with
+ // a "timeout" error. If zero, send timeout errors will not be triggered.
+ // Defaults to 0.
+ long? sendTimeout;
+ };
+
+ // Result of the <code>getInfo</code> method.
+ dictionary ConnectionInfo {
+ // The id of the serial port connection.
+ long connectionId;
+
+ // Flag indicating whether the connection is blocked from firing onReceive
+ // events.
+ boolean paused;
+
+ // See <code>ConnectionOptions.persistent</code>
+ boolean persistent;
+
+ // See <code>ConnectionOptions.name</code>
+ DOMString name;
+
+ // See <code>ConnectionOptions.bufferSize</code>
+ long bufferSize;
+
+ // See <code>ConnectionOptions.receiveTimeout</code>
+ long receiveTimeout;
+
+ // See <code>ConnectionOptions.sendTimeout</code>
+ long sendTimeout;
+
+ // See <code>ConnectionOptions.bitrate</code>. This field may be omitted
+ // or inaccurate if a non-standard bitrate is in use, or if an error
+ // occurred while querying the underlying device.
+ long? bitrate;
+
+ // See <code>ConnectionOptions.dataBits</code>. This field may be omitted
+ // if an error occurred while querying the underlying device.
+ DataBits? dataBits;
+
+ // See <code>ConnectionOptions.parityBit</code>. This field may be omitted
+ // if an error occurred while querying the underlying device.
+ ParityBit? parityBit;
+
+ // See <code>ConnectionOptions.stopBits</code>. This field may be omitted
+ // if an error occurred while querying the underlying device.
+ StopBits? stopBits;
+
+ // See <code>ConnectionOptions.ctsFlowControl</code>. This field may be
+ // omitted if an error occurred while querying the underlying device.
+ boolean? ctsFlowControl;
+ };
+
+ // Callback from the <code>connect</code> method;
+ callback ConnectCallback = void (ConnectionInfo connectionInfo);
+
+ // Callback from the <code>update</code> method.
+ callback UpdateCallback = void (boolean result);
+
+ // Callback from the <code>disconnect</code> method. Returns true if the
+ // operation was successful.
+ callback DisconnectCallback = void (boolean result);
+
+ // Callback from the <code>setPaused</code> method.
+ callback SetPausedCallback = void ();
+
+ // Callback from the <code>getInfo</code> method.
+ callback GetInfoCallback = void (ConnectionInfo connectionInfo);
+
+ // Callback from the <code>getConnections</code> method.
+ callback GetConnectionsCallback = void (ConnectionInfo[] connectionInfos);
+
+ enum SendError {
+ // The connection was disconnected.
+ disconnected,
+
+ // A send was already pending.
+ pending,
+
+ // The send timed out.
+ timeout,
+
+ // A system error occurred and the connection may be unrecoverable.
+ system_error
+ };
+
+ dictionary SendInfo {
+ // The number of bytes sent.
+ long bytesSent;
+
+ // An error code if an error occurred.
+ SendError? error;
+ };
+
+ callback SendCallback = void (SendInfo sendInfo);
+
+ callback FlushCallback = void (boolean result);
+
+ callback SetBreakCallback = void (boolean result);
+
+ callback ClearBreakCallback = void (boolean result);
+
+ // The set of control signals which may be sent to a connected serial device
+ // using <code>setControlSignals</code>. Note that support for these signals
+ // is device-dependent.
+ dictionary HostControlSignals {
+ // DTR (Data Terminal Ready).
+ boolean? dtr;
+
+ // RTS (Request To Send).
+ boolean? rts;
+ };
+
+ // The set of control signals which may be set by a connected serial device.
+ // These can be queried using <code>getControlSignals</code>. Note that
+ // support for these signals is device-dependent.
+ dictionary DeviceControlSignals {
+ // DCD (Data Carrier Detect) or RLSD (Receive Line Signal/ Detect).
+ boolean dcd;
+
+ // CTS (Clear To Send).
+ boolean cts;
+
+ // RI (Ring Indicator).
+ boolean ri;
+
+ // DSR (Data Set Ready).
+ boolean dsr;
+ };
+
+ // Returns a snapshot of current control signals.
+ callback GetControlSignalsCallback = void (DeviceControlSignals signals);
+
+ // Returns true if operation was successful.
+ callback SetControlSignalsCallback = void (boolean result);
+
+ // Data from an <code>onReceive</code> event.
+ dictionary ReceiveInfo {
+ // The connection identifier.
+ long connectionId;
+
+ // The data received.
+ ArrayBuffer data;
+ };
+
+ enum ReceiveError {
+ // The connection was disconnected.
+ disconnected,
+
+ // No data has been received for <code>receiveTimeout</code> milliseconds.
+ timeout,
+
+ // The device was most likely disconnected from the host.
+ device_lost,
+
+ // The device detected a break condition.
+ break,
+
+ // The device detected a framing error.
+ frame_error,
+
+ // A character-buffer overrun has occurred. The next character is lost.
+ overrun,
+
+ // An input buffer overflow has occurred. There is either no room in the
+ // input buffer, or a character was received after the end-of-file (EOF)
+ // character.
+ buffer_overflow,
+
+ // The device detected a parity error.
+ parity_error,
+
+ // A system error occurred and the connection may be unrecoverable.
+ system_error
+ };
+
+ // Data from an <code>onReceiveError</code> event.
+ dictionary ReceiveErrorInfo {
+ // The connection identifier.
+ long connectionId;
+
+ // An error code indicating what went wrong.
+ ReceiveError error;
+ };
+
+ interface Functions {
+ // Returns information about available serial devices on the system.
+ // The list is regenerated each time this method is called.
+ // |callback| : Called with the list of <code>DeviceInfo</code> objects.
+ static void getDevices(GetDevicesCallback callback);
+
+ // Connects to a given serial port.
+ // |path| : The system path of the serial port to open.
+ // |options| : Port configuration options.
+ // |callback| : Called when the connection has been opened.
+ static void connect(DOMString path,
+ optional ConnectionOptions options,
+ ConnectCallback callback);
+
+ // Update the option settings on an open serial port connection.
+ // |connectionId| : The id of the opened connection.
+ // |options| : Port configuration options.
+ // |callback| : Called when the configuation has completed.
+ static void update(long connectionId,
+ ConnectionOptions options,
+ UpdateCallback callback);
+
+ // Disconnects from a serial port.
+ // |connectionId| : The id of the opened connection.
+ // |callback| : Called when the connection has been closed.
+ static void disconnect(long connectionId, DisconnectCallback callback);
+
+ // Pauses or unpauses an open connection.
+ // |connectionId| : The id of the opened connection.
+ // |paused| : Flag to indicate whether to pause or unpause.
+ // |callback| : Called when the connection has been successfully paused or
+ // unpaused.
+ static void setPaused(long connectionId,
+ boolean paused,
+ SetPausedCallback callback);
+
+ // Retrieves the state of a given connection.
+ // |connectionId| : The id of the opened connection.
+ // |callback| : Called with connection state information when available.
+ static void getInfo(long connectionId, GetInfoCallback callback);
+
+ // Retrieves the list of currently opened serial port connections owned by
+ // the application.
+ // |callback| : Called with the list of connections when available.
+ static void getConnections(GetConnectionsCallback callback);
+
+ // Writes data to the given connection.
+ // |connectionId| : The id of the connection.
+ // |data| : The data to send.
+ // |callback| : Called when the operation has completed.
+ static void send(long connectionId,
+ ArrayBuffer data,
+ SendCallback callback);
+
+ // Flushes all bytes in the given connection's input and output buffers.
+ static void flush(long connectionId, FlushCallback callback);
+
+ // Retrieves the state of control signals on a given connection.
+ // |connectionId| : The id of the connection.
+ // |callback| : Called when the control signals are available.
+ static void getControlSignals(long connectionId,
+ GetControlSignalsCallback callback);
+
+ // Sets the state of control signals on a given connection.
+ // |connectionId| : The id of the connection.
+ // |signals| : The set of signal changes to send to the device.
+ // |callback| : Called once the control signals have been set.
+ static void setControlSignals(long connectionId,
+ HostControlSignals signals,
+ SetControlSignalsCallback callback);
+
+ // Suspends character transmission on a given connection and places the
+ // transmission line in a break state until the clearBreak is called.
+ // |connectionId| : The id of the connection.
+ static void setBreak(long connectionId, SetBreakCallback callback);
+
+ // Restore character transmission on a given connection and place the
+ // transmission line in a nonbreak state.
+ // |connectionId| : The id of the connection.
+ static void clearBreak(long connectionId, ClearBreakCallback callback);
+ };
+
+ interface Events {
+ // Event raised when data has been read from the connection.
+ // |info| : Event data.
+ static void onReceive(ReceiveInfo info);
+
+ // Event raised when an error occurred while the runtime was waiting for
+ // data on the serial port. Once this event is raised, the connection may be
+ // set to <code>paused</code>. A <code>"timeout"</code> error does not pause
+ // the connection.
+ static void onReceiveError(ReceiveErrorInfo info);
+ };
+};
diff --git a/chromium/extensions/common/api/socket.idl b/chromium/extensions/common/api/socket.idl
new file mode 100644
index 00000000000..8a8559ee957
--- /dev/null
+++ b/chromium/extensions/common/api/socket.idl
@@ -0,0 +1,360 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.socket</code> API to send and receive data over the
+// network using TCP and UDP connections. <b>Note:</b> Starting with Chrome 33,
+// this API is deprecated in favor of the $(ref:sockets.udp), $(ref:sockets.tcp) and
+// $(ref:sockets.tcpServer) APIs.
+namespace socket {
+ enum SocketType {
+ tcp,
+ udp
+ };
+
+ // The socket options.
+ dictionary CreateOptions {
+ };
+
+ dictionary CreateInfo {
+ // The id of the newly created socket.
+ long socketId;
+ };
+
+ callback CreateCallback = void (CreateInfo createInfo);
+
+ callback ConnectCallback = void (long result);
+
+ callback BindCallback = void (long result);
+
+ callback ListenCallback = void (long result);
+
+ callback SecureCallback = void (long result);
+
+ dictionary AcceptInfo {
+ long resultCode;
+ // The id of the accepted socket.
+ long? socketId;
+ };
+
+ callback AcceptCallback = void (AcceptInfo acceptInfo);
+
+ dictionary ReadInfo {
+ // The resultCode returned from the underlying read() call.
+ long resultCode;
+
+ ArrayBuffer data;
+ };
+
+ callback ReadCallback = void (ReadInfo readInfo);
+
+ dictionary WriteInfo {
+ // The number of bytes sent, or a negative error code.
+ long bytesWritten;
+ };
+
+ callback WriteCallback = void (WriteInfo writeInfo);
+
+ dictionary RecvFromInfo {
+ // The resultCode returned from the underlying recvfrom() call.
+ long resultCode;
+
+ ArrayBuffer data;
+
+ // The address of the remote machine.
+ DOMString address;
+
+ long port;
+ };
+
+ dictionary SocketInfo {
+ // The type of the passed socket. This will be <code>tcp</code> or
+ // <code>udp</code>.
+ SocketType socketType;
+
+ // Whether or not the underlying socket is connected.
+ //
+ // For <code>tcp</code> sockets, this will remain true even if the remote
+ // peer has disconnected. Reading or writing to the socket may then result
+ // in an error, hinting that this socket should be disconnected via
+ // <code>disconnect()</code>.
+ //
+ // For <code>udp</code> sockets, this just represents whether a default
+ // remote address has been specified for reading and writing packets.
+ boolean connected;
+
+ // If the underlying socket is connected, contains the IPv4/6 address of
+ // the peer.
+ DOMString? peerAddress;
+
+ // If the underlying socket is connected, contains the port of the
+ // connected peer.
+ long? peerPort;
+
+ // If the underlying socket is bound or connected, contains its local
+ // IPv4/6 address.
+ DOMString? localAddress;
+
+ // If the underlying socket is bound or connected, contains its local port.
+ long? localPort;
+ };
+
+ dictionary NetworkInterface {
+ // The underlying name of the adapter. On *nix, this will typically be
+ // "eth0", "lo", etc.
+ DOMString name;
+
+ // The available IPv4/6 address.
+ DOMString address;
+
+ // The prefix length
+ long prefixLength;
+ };
+
+ dictionary TLSVersionConstraints {
+ // The minimum and maximum acceptable versions of TLS. These will
+ // be <code>tls1</code>, <code>tls1.1</code>, or <code>tls1.2</code>.
+ DOMString? min;
+ DOMString? max;
+ };
+
+ dictionary SecureOptions {
+ TLSVersionConstraints? tlsVersion;
+ };
+
+ callback RecvFromCallback = void (RecvFromInfo recvFromInfo);
+
+ callback SendToCallback = void (WriteInfo writeInfo);
+
+ callback SetKeepAliveCallback = void (boolean result);
+
+ callback SetNoDelayCallback = void (boolean result);
+
+ callback GetInfoCallback = void (SocketInfo result);
+
+ callback GetNetworkCallback = void (NetworkInterface[] result);
+
+ callback JoinGroupCallback = void (long result);
+
+ callback LeaveGroupCallback = void (long result);
+
+ callback SetMulticastTimeToLiveCallback = void (long result);
+
+ callback SetMulticastLoopbackModeCallback = void (long result);
+
+ callback GetJoinedGroupsCallback = void (DOMString[] groups);
+
+ interface Functions {
+ // Creates a socket of the specified type that will connect to the specified
+ // remote machine.
+ // |type| : The type of socket to create. Must be <code>tcp</code> or
+ // <code>udp</code>.
+ // |options| : The socket options.
+ // |callback| : Called when the socket has been created.
+ static void create(SocketType type,
+ optional CreateOptions options,
+ CreateCallback callback);
+
+ // Destroys the socket. Each socket created should be destroyed after use.
+ // |socketId| : The socketId.
+ static void destroy(long socketId);
+
+ // Connects the socket to the remote machine (for a <code>tcp</code>
+ // socket). For a <code>udp</code> socket, this sets the default address
+ // which packets are sent to and read from for <code>read()</code>
+ // and <code>write()</code> calls.
+ // |socketId| : The socketId.
+ // |hostname| : The hostname or IP address of the remote machine.
+ // |port| : The port of the remote machine.
+ // |callback| : Called when the connection attempt is complete.
+ static void connect(long socketId,
+ DOMString hostname,
+ long port,
+ ConnectCallback callback);
+
+ // Binds the local address for socket. Currently, it does not support
+ // TCP socket.
+ // |socketId| : The socketId.
+ // |address| : The address of the local machine.
+ // |port| : The port of the local machine.
+ // |callback| : Called when the bind attempt is complete.
+ static void bind(long socketId,
+ DOMString address,
+ long port,
+ BindCallback callback);
+
+ // Disconnects the socket. For UDP sockets, <code>disconnect</code> is a
+ // non-operation but is safe to call.
+ // |socketId| : The socketId.
+ static void disconnect(long socketId);
+
+ // Reads data from the given connected socket.
+ // |socketId| : The socketId.
+ // |bufferSize| : The read buffer size.
+ // |callback| : Delivers data that was available to be read without
+ // blocking.
+ static void read(long socketId,
+ optional long bufferSize,
+ ReadCallback callback);
+
+ // Writes data on the given connected socket.
+ // |socketId| : The socketId.
+ // |data| : The data to write.
+ // |callback| : Called when the write operation completes without blocking
+ // or an error occurs.
+ static void write(long socketId,
+ ArrayBuffer data,
+ WriteCallback callback);
+
+ // Receives data from the given UDP socket.
+ // |socketId| : The socketId.
+ // |bufferSize| : The receive buffer size.
+ // |callback| : Returns result of the recvFrom operation.
+ static void recvFrom(long socketId,
+ optional long bufferSize,
+ RecvFromCallback callback);
+
+ // Sends data on the given UDP socket to the given address and port.
+ // |socketId| : The socketId.
+ // |data| : The data to write.
+ // |address| : The address of the remote machine.
+ // |port| : The port of the remote machine.
+ // |callback| : Called when the send operation completes without blocking
+ // or an error occurs.
+ static void sendTo(long socketId,
+ ArrayBuffer data,
+ DOMString address,
+ long port,
+ SendToCallback callback);
+
+ // This method applies to TCP sockets only.
+ // Listens for connections on the specified port and address. This
+ // effectively makes this a server socket, and client socket
+ // functions (connect, read, write) can no longer be used on this socket.
+ // |socketId| : The socketId.
+ // |address| : The address of the local machine.
+ // |port| : The port of the local machine.
+ // |backlog| : Length of the socket's listen queue.
+ // |callback| : Called when listen operation completes.
+ static void listen(long socketId,
+ DOMString address,
+ long port,
+ optional long backlog,
+ ListenCallback callback);
+
+ // This method applies to TCP sockets only.
+ // Registers a callback function to be called when a connection is
+ // accepted on this listening server socket. Listen must be called first.
+ // If there is already an active accept callback, this callback will be
+ // invoked immediately with an error as the resultCode.
+ // |socketId| : The socketId.
+ // |callback| : The callback is invoked when a new socket is accepted.
+ static void accept(long socketId,
+ AcceptCallback callback);
+
+ // Enables or disables the keep-alive functionality for a TCP connection.
+ // |socketId| : The socketId.
+ // |enable| : If true, enable keep-alive functionality.
+ // |delay| : Set the delay seconds between the last data packet received
+ // and the first keepalive probe. Default is 0.
+ // |callback| : Called when the setKeepAlive attempt is complete.
+ static void setKeepAlive(long socketId,
+ boolean enable,
+ optional long delay,
+ SetKeepAliveCallback callback);
+
+ // Sets or clears <code>TCP_NODELAY</code> for a TCP connection. Nagle's
+ // algorithm will be disabled when <code>TCP_NODELAY</code> is set.
+ // |socketId| : The socketId.
+ // |noDelay| : If true, disables Nagle's algorithm.
+ // |callback| : Called when the setNoDelay attempt is complete.
+ static void setNoDelay(long socketId,
+ boolean noDelay,
+ SetNoDelayCallback callback);
+
+ // Retrieves the state of the given socket.
+ // |socketId| : The socketId.
+ // |callback| : Called when the state is available.
+ static void getInfo(long socketId,
+ GetInfoCallback callback);
+
+ // Retrieves information about local adapters on this system.
+ // |callback| : Called when local adapter information is available.
+ static void getNetworkList(GetNetworkCallback callback);
+
+ // Join the multicast group and start to receive packets from that group.
+ // The socket must be of UDP type and must be bound to a local port
+ // before calling this method.
+ // |socketId| : The socketId.
+ // |address| : The group address to join. Domain names are not supported.
+ // |callback| : Called when the join group operation is done with an
+ // integer parameter indicating the platform-independent error code.
+ static void joinGroup(long socketId,
+ DOMString address,
+ JoinGroupCallback callback);
+
+ // Leave the multicast group previously joined using <code>joinGroup</code>.
+ // It's not necessary to leave the multicast group before destroying the
+ // socket or exiting. This is automatically called by the OS.
+ //
+ // Leaving the group will prevent the router from sending multicast
+ // datagrams to the local host, presuming no other process on the host is
+ // still joined to the group.
+ //
+ // |socketId| : The socketId.
+ // |address| : The group address to leave. Domain names are not supported.
+ // |callback| : Called when the leave group operation is done with an
+ // integer parameter indicating the platform-independent error code.
+ static void leaveGroup(long socketId, DOMString address,
+ LeaveGroupCallback callback);
+
+ // Set the time-to-live of multicast packets sent to the multicast group.
+ //
+ // Calling this method does not require multicast permissions.
+ //
+ // |socketId| : The socketId.
+ // |ttl| : The time-to-live value.
+ // |callback| : Called when the configuration operation is done.
+ static void setMulticastTimeToLive(
+ long socketId,
+ long ttl,
+ SetMulticastTimeToLiveCallback callback);
+
+ // Set whether multicast packets sent from the host to the multicast
+ // group will be looped back to the host.
+ //
+ // Note: the behavior of <code>setMulticastLoopbackMode</code> is slightly
+ // different between Windows and Unix-like systems. The inconsistency
+ // happens only when there is more than one application on the same host
+ // joined to the same multicast group while having different settings on
+ // multicast loopback mode. On Windows, the applications with loopback off
+ // will not RECEIVE the loopback packets; while on Unix-like systems, the
+ // applications with loopback off will not SEND the loopback packets to
+ // other applications on the same host. See MSDN: http://goo.gl/6vqbj
+ //
+ // Calling this method does not require multicast permissions.
+ //
+ // |socketId| : The socketId.
+ // |enabled| : Indicate whether to enable loopback mode.
+ // |callback| : Called when the configuration operation is done.
+ static void setMulticastLoopbackMode(
+ long socketId,
+ boolean enabled,
+ SetMulticastLoopbackModeCallback callback);
+
+ // Get the multicast group addresses the socket is currently joined to.
+ // |socketId| : The socketId.
+ // |callback| : Called with an array of strings of the result.
+ static void getJoinedGroups(long socketId,
+ GetJoinedGroupsCallback callback);
+
+ // Start a TLS client connection over a connected TCP client socket.
+ // |socketId| : The connected socket to use.
+ // |options| : Constraints and parameters for the TLS connection.
+ // |callback| : Called when the TLS connection attempt is complete.
+ static void secure(long socketId,
+ optional SecureOptions options,
+ SecureCallback callback);
+ };
+
+};
diff --git a/chromium/extensions/common/api/sockets/sockets_manifest_data.cc b/chromium/extensions/common/api/sockets/sockets_manifest_data.cc
new file mode 100644
index 00000000000..9cedcf8c6af
--- /dev/null
+++ b/chromium/extensions/common/api/sockets/sockets_manifest_data.cc
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/api/sockets/sockets_manifest_data.h"
+
+#include <utility>
+
+#include "extensions/common/api/sockets/sockets_manifest_permission.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+SocketsManifestData::SocketsManifestData(
+ scoped_ptr<SocketsManifestPermission> permission)
+ : permission_(std::move(permission)) {
+ DCHECK(permission_);
+}
+
+SocketsManifestData::~SocketsManifestData() {}
+
+// static
+SocketsManifestData* SocketsManifestData::Get(const Extension* extension) {
+ return static_cast<SocketsManifestData*>(
+ extension->GetManifestData(manifest_keys::kSockets));
+}
+
+// static
+bool SocketsManifestData::CheckRequest(
+ const Extension* extension,
+ const content::SocketPermissionRequest& request) {
+ const SocketsManifestData* data = SocketsManifestData::Get(extension);
+ if (data)
+ return data->permission()->CheckRequest(extension, request);
+
+ return false;
+}
+
+// static
+scoped_ptr<SocketsManifestData> SocketsManifestData::FromValue(
+ const base::Value& value,
+ base::string16* error) {
+ scoped_ptr<SocketsManifestPermission> permission =
+ SocketsManifestPermission::FromValue(value, error);
+ if (!permission)
+ return scoped_ptr<SocketsManifestData>();
+
+ return scoped_ptr<SocketsManifestData>(
+ new SocketsManifestData(std::move(permission)));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/api/sockets/sockets_manifest_data.h b/chromium/extensions/common/api/sockets/sockets_manifest_data.h
new file mode 100644
index 00000000000..de9dd8f5717
--- /dev/null
+++ b/chromium/extensions/common/api/sockets/sockets_manifest_data.h
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_API_SOCKETS_SOCKETS_MANIFEST_DATA_H_
+#define EXTENSIONS_COMMON_API_SOCKETS_SOCKETS_MANIFEST_DATA_H_
+
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace content {
+struct SocketPermissionRequest;
+}
+
+namespace extensions {
+class SocketsManifestPermission;
+}
+
+namespace extensions {
+
+// The parsed form of the "sockets" manifest entry.
+class SocketsManifestData : public Extension::ManifestData {
+ public:
+ explicit SocketsManifestData(
+ scoped_ptr<SocketsManifestPermission> permission);
+ ~SocketsManifestData() override;
+
+ // Gets the SocketsManifestData for |extension|, or NULL if none was
+ // specified.
+ static SocketsManifestData* Get(const Extension* extension);
+
+ static bool CheckRequest(const Extension* extension,
+ const content::SocketPermissionRequest& request);
+
+ // Tries to construct the info based on |value|, as it would have appeared in
+ // the manifest. Sets |error| and returns an empty scoped_ptr on failure.
+ static scoped_ptr<SocketsManifestData> FromValue(const base::Value& value,
+ base::string16* error);
+
+ const SocketsManifestPermission* permission() const {
+ return permission_.get();
+ }
+
+ private:
+ scoped_ptr<SocketsManifestPermission> permission_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_API_SOCKETS_SOCKETS_MANIFEST_DATA_H_
diff --git a/chromium/extensions/common/api/sockets/sockets_manifest_handler.cc b/chromium/extensions/common/api/sockets/sockets_manifest_handler.cc
new file mode 100644
index 00000000000..113dc6eca5a
--- /dev/null
+++ b/chromium/extensions/common/api/sockets/sockets_manifest_handler.cc
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/api/sockets/sockets_manifest_handler.h"
+
+#include "extensions/common/api/sockets/sockets_manifest_data.h"
+#include "extensions/common/api/sockets/sockets_manifest_permission.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+SocketsManifestHandler::SocketsManifestHandler() {}
+
+SocketsManifestHandler::~SocketsManifestHandler() {}
+
+bool SocketsManifestHandler::Parse(Extension* extension,
+ base::string16* error) {
+ const base::Value* sockets = NULL;
+ CHECK(extension->manifest()->Get(manifest_keys::kSockets, &sockets));
+ scoped_ptr<SocketsManifestData> data =
+ SocketsManifestData::FromValue(*sockets, error);
+ if (!data)
+ return false;
+
+ extension->SetManifestData(manifest_keys::kSockets, data.release());
+ return true;
+}
+
+ManifestPermission* SocketsManifestHandler::CreatePermission() {
+ return new SocketsManifestPermission();
+}
+
+ManifestPermission* SocketsManifestHandler::CreateInitialRequiredPermission(
+ const Extension* extension) {
+ SocketsManifestData* data = SocketsManifestData::Get(extension);
+ if (data)
+ return data->permission()->Clone();
+ return NULL;
+}
+
+const std::vector<std::string> SocketsManifestHandler::Keys() const {
+ return SingleKey(manifest_keys::kSockets);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/api/sockets/sockets_manifest_handler.h b/chromium/extensions/common/api/sockets/sockets_manifest_handler.h
new file mode 100644
index 00000000000..03bea8db97a
--- /dev/null
+++ b/chromium/extensions/common/api/sockets/sockets_manifest_handler.h
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_API_SOCKETS_SOCKETS_MANIFEST_HANDLER_H_
+#define EXTENSIONS_COMMON_API_SOCKETS_SOCKETS_MANIFEST_HANDLER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+class Extension;
+class ManifestPermission;
+}
+
+namespace extensions {
+
+// Parses the "sockets" manifest key.
+class SocketsManifestHandler : public ManifestHandler {
+ public:
+ SocketsManifestHandler();
+ ~SocketsManifestHandler() override;
+
+ // ManifestHandler overrides.
+ bool Parse(Extension* extension, base::string16* error) override;
+ ManifestPermission* CreatePermission() override;
+ ManifestPermission* CreateInitialRequiredPermission(
+ const Extension* extension) override;
+
+ private:
+ // ManifestHandler overrides.
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketsManifestHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_API_SOCKETS_SOCKETS_MANIFEST_HANDLER_H_
diff --git a/chromium/extensions/common/api/sockets/sockets_manifest_permission.cc b/chromium/extensions/common/api/sockets/sockets_manifest_permission.cc
new file mode 100644
index 00000000000..fa0c98ac692
--- /dev/null
+++ b/chromium/extensions/common/api/sockets/sockets_manifest_permission.cc
@@ -0,0 +1,319 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/api/sockets/sockets_manifest_permission.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/api/extensions_manifest_types.h"
+#include "extensions/common/api/sockets/sockets_manifest_data.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest_constants.h"
+#include "grit/extensions_strings.h"
+#include "ipc/ipc_message.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+namespace sockets_errors {
+const char kErrorInvalidHostPattern[] = "Invalid host:port pattern '*'";
+}
+
+namespace errors = sockets_errors;
+using api::extensions_manifest_types::Sockets;
+using api::extensions_manifest_types::SocketHostPatterns;
+using content::SocketPermissionRequest;
+
+namespace {
+
+static bool ParseHostPattern(
+ SocketsManifestPermission* permission,
+ content::SocketPermissionRequest::OperationType operation_type,
+ const std::string& host_pattern,
+ base::string16* error) {
+ SocketPermissionEntry entry;
+ if (!SocketPermissionEntry::ParseHostPattern(
+ operation_type, host_pattern, &entry)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kErrorInvalidHostPattern, host_pattern);
+ return false;
+ }
+ permission->AddPermission(entry);
+ return true;
+}
+
+static bool ParseHostPatterns(
+ SocketsManifestPermission* permission,
+ content::SocketPermissionRequest::OperationType operation_type,
+ const scoped_ptr<SocketHostPatterns>& host_patterns,
+ base::string16* error) {
+ if (!host_patterns)
+ return true;
+
+ if (host_patterns->as_string) {
+ return ParseHostPattern(
+ permission, operation_type, *host_patterns->as_string, error);
+ }
+
+ CHECK(host_patterns->as_strings);
+ for (std::vector<std::string>::const_iterator it =
+ host_patterns->as_strings->begin();
+ it != host_patterns->as_strings->end();
+ ++it) {
+ if (!ParseHostPattern(permission, operation_type, *it, error)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static void SetHostPatterns(
+ scoped_ptr<SocketHostPatterns>& host_patterns,
+ const SocketsManifestPermission* permission,
+ content::SocketPermissionRequest::OperationType operation_type) {
+ host_patterns.reset(new SocketHostPatterns());
+ host_patterns->as_strings.reset(new std::vector<std::string>());
+ for (SocketPermissionEntrySet::const_iterator it =
+ permission->entries().begin();
+ it != permission->entries().end(); ++it) {
+ if (it->pattern().type == operation_type) {
+ host_patterns->as_strings->push_back(it->GetHostPatternAsString());
+ }
+ }
+}
+
+// Helper function for adding the 'any host' permission. Determines if the
+// message is needed from |sockets|, and adds the permission to |ids|.
+// Returns true if it added the message.
+bool AddAnyHostMessage(const SocketPermissionEntrySet& sockets,
+ PermissionIDSet* ids) {
+ for (const auto& socket : sockets) {
+ if (socket.IsAddressBoundType() &&
+ socket.GetHostType() == SocketPermissionEntry::ANY_HOST) {
+ ids->insert(APIPermission::kSocketAnyHost);
+ return true;
+ }
+ }
+ return false;
+}
+
+// Helper function for adding subdomain socket permissions. Determines what
+// messages are needed from |sockets|, and adds permissions to |ids|.
+void AddSubdomainHostMessage(const SocketPermissionEntrySet& sockets,
+ PermissionIDSet* ids) {
+ std::set<base::string16> domains;
+ for (const auto& socket : sockets) {
+ if (socket.GetHostType() == SocketPermissionEntry::HOSTS_IN_DOMAINS)
+ domains.insert(base::UTF8ToUTF16(socket.pattern().host));
+ }
+ if (!domains.empty()) {
+ for (const auto& domain : domains)
+ ids->insert(APIPermission::kSocketDomainHosts, domain);
+ }
+}
+
+// Helper function for adding specific host socket permissions. Determines what
+// messages are needed from |sockets|, and adds permissions to |ids|.
+void AddSpecificHostMessage(const SocketPermissionEntrySet& sockets,
+ PermissionIDSet* ids) {
+ std::set<base::string16> hostnames;
+ for (const auto& socket : sockets) {
+ if (socket.GetHostType() == SocketPermissionEntry::SPECIFIC_HOSTS)
+ hostnames.insert(base::UTF8ToUTF16(socket.pattern().host));
+ }
+ if (!hostnames.empty()) {
+ for (const auto& hostname : hostnames)
+ ids->insert(APIPermission::kSocketSpecificHosts, hostname);
+ }
+}
+
+// Helper function for adding the network list socket permission. Determines if
+// the message is needed from |sockets|, and adds the permission to |ids|.
+void AddNetworkListMessage(const SocketPermissionEntrySet& sockets,
+ PermissionIDSet* ids) {
+ for (const auto& socket : sockets) {
+ if (socket.pattern().type == SocketPermissionRequest::NETWORK_STATE) {
+ ids->insert(APIPermission::kNetworkState);
+ }
+ }
+}
+
+} // namespace
+
+SocketsManifestPermission::SocketsManifestPermission() {}
+
+SocketsManifestPermission::~SocketsManifestPermission() {}
+
+// static
+scoped_ptr<SocketsManifestPermission> SocketsManifestPermission::FromValue(
+ const base::Value& value,
+ base::string16* error) {
+ scoped_ptr<Sockets> sockets = Sockets::FromValue(value, error);
+ if (!sockets)
+ return scoped_ptr<SocketsManifestPermission>();
+
+ scoped_ptr<SocketsManifestPermission> result(new SocketsManifestPermission());
+ if (sockets->udp) {
+ if (!ParseHostPatterns(result.get(),
+ SocketPermissionRequest::UDP_BIND,
+ sockets->udp->bind,
+ error)) {
+ return scoped_ptr<SocketsManifestPermission>();
+ }
+ if (!ParseHostPatterns(result.get(),
+ SocketPermissionRequest::UDP_SEND_TO,
+ sockets->udp->send,
+ error)) {
+ return scoped_ptr<SocketsManifestPermission>();
+ }
+ if (!ParseHostPatterns(result.get(),
+ SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP,
+ sockets->udp->multicast_membership,
+ error)) {
+ return scoped_ptr<SocketsManifestPermission>();
+ }
+ }
+ if (sockets->tcp) {
+ if (!ParseHostPatterns(result.get(),
+ SocketPermissionRequest::TCP_CONNECT,
+ sockets->tcp->connect,
+ error)) {
+ return scoped_ptr<SocketsManifestPermission>();
+ }
+ }
+ if (sockets->tcp_server) {
+ if (!ParseHostPatterns(result.get(),
+ SocketPermissionRequest::TCP_LISTEN,
+ sockets->tcp_server->listen,
+ error)) {
+ return scoped_ptr<SocketsManifestPermission>();
+ }
+ }
+ return result;
+}
+
+bool SocketsManifestPermission::CheckRequest(
+ const Extension* extension,
+ const SocketPermissionRequest& request) const {
+ for (SocketPermissionEntrySet::const_iterator it = permissions_.begin();
+ it != permissions_.end();
+ ++it) {
+ if (it->Check(request))
+ return true;
+ }
+ return false;
+}
+
+std::string SocketsManifestPermission::name() const {
+ return manifest_keys::kSockets;
+}
+
+std::string SocketsManifestPermission::id() const { return name(); }
+
+PermissionIDSet SocketsManifestPermission::GetPermissions() const {
+ PermissionIDSet ids;
+ AddSocketHostPermissions(permissions_, &ids);
+ return ids;
+}
+
+bool SocketsManifestPermission::FromValue(const base::Value* value) {
+ if (!value)
+ return false;
+ base::string16 error;
+ scoped_ptr<SocketsManifestPermission> manifest_permission(
+ SocketsManifestPermission::FromValue(*value, &error));
+
+ if (!manifest_permission)
+ return false;
+
+ permissions_ = manifest_permission->permissions_;
+ return true;
+}
+
+scoped_ptr<base::Value> SocketsManifestPermission::ToValue() const {
+ Sockets sockets;
+
+ sockets.udp.reset(new Sockets::Udp());
+ SetHostPatterns(sockets.udp->bind, this, SocketPermissionRequest::UDP_BIND);
+ SetHostPatterns(
+ sockets.udp->send, this, SocketPermissionRequest::UDP_SEND_TO);
+ SetHostPatterns(sockets.udp->multicast_membership,
+ this,
+ SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP);
+ if (sockets.udp->bind->as_strings->size() == 0 &&
+ sockets.udp->send->as_strings->size() == 0 &&
+ sockets.udp->multicast_membership->as_strings->size() == 0) {
+ sockets.udp.reset(NULL);
+ }
+
+ sockets.tcp.reset(new Sockets::Tcp());
+ SetHostPatterns(
+ sockets.tcp->connect, this, SocketPermissionRequest::TCP_CONNECT);
+ if (sockets.tcp->connect->as_strings->size() == 0) {
+ sockets.tcp.reset(NULL);
+ }
+
+ sockets.tcp_server.reset(new Sockets::TcpServer());
+ SetHostPatterns(
+ sockets.tcp_server->listen, this, SocketPermissionRequest::TCP_LISTEN);
+ if (sockets.tcp_server->listen->as_strings->size() == 0) {
+ sockets.tcp_server.reset(NULL);
+ }
+
+ return scoped_ptr<base::Value>(sockets.ToValue().release());
+}
+
+ManifestPermission* SocketsManifestPermission::Diff(
+ const ManifestPermission* rhs) const {
+ const SocketsManifestPermission* other =
+ static_cast<const SocketsManifestPermission*>(rhs);
+
+ scoped_ptr<SocketsManifestPermission> result(new SocketsManifestPermission());
+ result->permissions_ = base::STLSetDifference<SocketPermissionEntrySet>(
+ permissions_, other->permissions_);
+ return result.release();
+}
+
+ManifestPermission* SocketsManifestPermission::Union(
+ const ManifestPermission* rhs) const {
+ const SocketsManifestPermission* other =
+ static_cast<const SocketsManifestPermission*>(rhs);
+
+ scoped_ptr<SocketsManifestPermission> result(new SocketsManifestPermission());
+ result->permissions_ = base::STLSetUnion<SocketPermissionEntrySet>(
+ permissions_, other->permissions_);
+ return result.release();
+}
+
+ManifestPermission* SocketsManifestPermission::Intersect(
+ const ManifestPermission* rhs) const {
+ const SocketsManifestPermission* other =
+ static_cast<const SocketsManifestPermission*>(rhs);
+
+ scoped_ptr<SocketsManifestPermission> result(new SocketsManifestPermission());
+ result->permissions_ = base::STLSetIntersection<SocketPermissionEntrySet>(
+ permissions_, other->permissions_);
+ return result.release();
+}
+
+void SocketsManifestPermission::AddPermission(
+ const SocketPermissionEntry& entry) {
+ permissions_.insert(entry);
+}
+
+// static
+void SocketsManifestPermission::AddSocketHostPermissions(
+ const SocketPermissionEntrySet& sockets,
+ PermissionIDSet* ids) {
+ if (!AddAnyHostMessage(sockets, ids)) {
+ AddSpecificHostMessage(sockets, ids);
+ AddSubdomainHostMessage(sockets, ids);
+ }
+ AddNetworkListMessage(sockets, ids);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/api/sockets/sockets_manifest_permission.h b/chromium/extensions/common/api/sockets/sockets_manifest_permission.h
new file mode 100644
index 00000000000..555795769a0
--- /dev/null
+++ b/chromium/extensions/common/api/sockets/sockets_manifest_permission.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_API_SOCKETS_SOCKETS_MANIFEST_PERMISSION_H_
+#define EXTENSIONS_COMMON_API_SOCKETS_SOCKETS_MANIFEST_PERMISSION_H_
+
+#include <set>
+#include <vector>
+
+#include "extensions/common/install_warning.h"
+#include "extensions/common/permissions/manifest_permission.h"
+#include "extensions/common/permissions/socket_permission_entry.h"
+
+namespace content {
+struct SocketPermissionRequest;
+}
+
+namespace extensions {
+class Extension;
+}
+
+namespace extensions {
+
+typedef std::set<SocketPermissionEntry> SocketPermissionEntrySet;
+
+class SocketsManifestPermission : public ManifestPermission {
+ public:
+ SocketsManifestPermission();
+ ~SocketsManifestPermission() override;
+
+ // Tries to construct the info based on |value|, as it would have appeared in
+ // the manifest. Sets |error| and returns an empty scoped_ptr on failure.
+ static scoped_ptr<SocketsManifestPermission> FromValue(
+ const base::Value& value,
+ base::string16* error);
+
+ bool CheckRequest(const Extension* extension,
+ const content::SocketPermissionRequest& request) const;
+
+ void AddPermission(const SocketPermissionEntry& entry);
+
+ // extensions::ManifestPermission overrides.
+ std::string name() const override;
+ std::string id() const override;
+ PermissionIDSet GetPermissions() const override;
+ bool FromValue(const base::Value* value) override;
+ scoped_ptr<base::Value> ToValue() const override;
+ ManifestPermission* Diff(const ManifestPermission* rhs) const override;
+ ManifestPermission* Union(const ManifestPermission* rhs) const override;
+ ManifestPermission* Intersect(const ManifestPermission* rhs) const override;
+
+ const SocketPermissionEntrySet& entries() const { return permissions_; }
+
+ // Adds the permissions from |sockets| into the permission lists |ids| and
+ // |messages|. If either is NULL, that list is ignored.
+ static void AddSocketHostPermissions(const SocketPermissionEntrySet& sockets,
+ PermissionIDSet* ids);
+
+ SocketPermissionEntrySet permissions_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_API_SOCKETS_SOCKETS_MANIFEST_PERMISSION_H_
diff --git a/chromium/extensions/common/api/sockets/sockets_manifest_permission_unittest.cc b/chromium/extensions/common/api/sockets/sockets_manifest_permission_unittest.cc
new file mode 100644
index 00000000000..d0dc6099e8d
--- /dev/null
+++ b/chromium/extensions/common/api/sockets/sockets_manifest_permission_unittest.cc
@@ -0,0 +1,407 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <set>
+#include <tuple>
+
+#include "base/json/json_reader.h"
+#include "base/macros.h"
+#include "base/pickle.h"
+#include "base/values.h"
+#include "extensions/common/api/sockets/sockets_manifest_permission.h"
+#include "extensions/common/manifest_constants.h"
+#include "ipc/ipc_message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::SocketPermissionRequest;
+
+namespace extensions {
+
+namespace {
+
+const char kUdpBindPermission[] =
+ "{ \"udp\": { \"bind\": [\"127.0.0.1:3007\", \"a.com:80\"] } }";
+
+const char kUdpSendPermission[] =
+ "{ \"udp\": { \"send\": [\"\", \"a.com:80\"] } }";
+
+const char kTcpConnectPermission[] =
+ "{ \"tcp\": { \"connect\": [\"127.0.0.1:80\", \"a.com:80\"] } }";
+
+const char kTcpServerListenPermission[] =
+ "{ \"tcpServer\": { \"listen\": [\"127.0.0.1:80\", \"a.com:80\"] } }";
+
+static void AssertEmptyPermission(const SocketsManifestPermission* permission) {
+ EXPECT_TRUE(permission);
+ EXPECT_EQ(std::string(extensions::manifest_keys::kSockets), permission->id());
+ EXPECT_EQ(permission->id(), permission->name());
+ EXPECT_TRUE(permission->GetPermissions().empty());
+ EXPECT_EQ(0u, permission->entries().size());
+}
+
+static scoped_ptr<base::Value> ParsePermissionJSON(const std::string& json) {
+ scoped_ptr<base::Value> result(base::JSONReader::Read(json));
+ EXPECT_TRUE(result) << "Invalid JSON string: " << json;
+ return result;
+}
+
+static scoped_ptr<SocketsManifestPermission> PermissionFromValue(
+ const base::Value& value) {
+ base::string16 error16;
+ scoped_ptr<SocketsManifestPermission> permission(
+ SocketsManifestPermission::FromValue(value, &error16));
+ EXPECT_TRUE(permission) << "Error parsing Value into permission: " << error16;
+ return permission;
+}
+
+static scoped_ptr<SocketsManifestPermission> PermissionFromJSON(
+ const std::string& json) {
+ scoped_ptr<base::Value> value(ParsePermissionJSON(json));
+ return PermissionFromValue(*value);
+}
+
+struct CheckFormatEntry {
+ CheckFormatEntry(SocketPermissionRequest::OperationType operation_type,
+ std::string host_pattern)
+ : operation_type(operation_type), host_pattern(host_pattern) {}
+
+ // operators <, == are needed by container std::set and algorithms
+ // std::set_includes and std::set_differences.
+ bool operator<(const CheckFormatEntry& rhs) const {
+ return std::tie(operation_type, host_pattern) <
+ std::tie(rhs.operation_type, rhs.host_pattern);
+ }
+
+ bool operator==(const CheckFormatEntry& rhs) const {
+ return operation_type == rhs.operation_type &&
+ host_pattern == rhs.host_pattern;
+ }
+
+ SocketPermissionRequest::OperationType operation_type;
+ std::string host_pattern;
+};
+
+static testing::AssertionResult CheckFormat(
+ std::multiset<CheckFormatEntry> permissions,
+ const std::string& json) {
+ scoped_ptr<SocketsManifestPermission> permission(PermissionFromJSON(json));
+ if (!permission)
+ return testing::AssertionFailure() << "Invalid permission " << json;
+
+ if (permissions.size() != permission->entries().size()) {
+ return testing::AssertionFailure()
+ << "Incorrect # of entries in json: " << json;
+ }
+
+ // Note: We use multiset because SocketsManifestPermission does not have to
+ // store entries in the order found in the json message.
+ std::multiset<CheckFormatEntry> parsed_permissions;
+ for (SocketPermissionEntrySet::const_iterator it =
+ permission->entries().begin();
+ it != permission->entries().end(); ++it) {
+ parsed_permissions.insert(
+ CheckFormatEntry(it->pattern().type, it->GetHostPatternAsString()));
+ }
+
+ if (!std::equal(
+ permissions.begin(), permissions.end(), parsed_permissions.begin())) {
+ return testing::AssertionFailure() << "Incorrect socket operations.";
+ }
+ return testing::AssertionSuccess();
+}
+
+static testing::AssertionResult CheckFormat(const std::string& json) {
+ return CheckFormat(std::multiset<CheckFormatEntry>(), json);
+}
+
+static testing::AssertionResult CheckFormat(const std::string& json,
+ const CheckFormatEntry& op1) {
+ CheckFormatEntry entries[] = {op1};
+ return CheckFormat(
+ std::multiset<CheckFormatEntry>(entries, entries + arraysize(entries)),
+ json);
+}
+
+static testing::AssertionResult CheckFormat(const std::string& json,
+ const CheckFormatEntry& op1,
+ const CheckFormatEntry& op2) {
+ CheckFormatEntry entries[] = {op1, op2};
+ return CheckFormat(
+ std::multiset<CheckFormatEntry>(entries, entries + arraysize(entries)),
+ json);
+}
+
+static testing::AssertionResult CheckFormat(const std::string& json,
+ const CheckFormatEntry& op1,
+ const CheckFormatEntry& op2,
+ const CheckFormatEntry& op3,
+ const CheckFormatEntry& op4,
+ const CheckFormatEntry& op5,
+ const CheckFormatEntry& op6,
+ const CheckFormatEntry& op7,
+ const CheckFormatEntry& op8,
+ const CheckFormatEntry& op9) {
+ CheckFormatEntry entries[] = {op1, op2, op3, op4, op5, op6, op7, op8, op9};
+ return CheckFormat(
+ std::multiset<CheckFormatEntry>(entries, entries + arraysize(entries)),
+ json);
+}
+
+} // namespace
+
+TEST(SocketsManifestPermissionTest, Empty) {
+ // Construction
+ scoped_ptr<SocketsManifestPermission> permission(
+ new SocketsManifestPermission());
+ AssertEmptyPermission(permission.get());
+
+ // Clone()/Equal()
+ scoped_ptr<SocketsManifestPermission> clone(
+ static_cast<SocketsManifestPermission*>(permission->Clone()));
+ AssertEmptyPermission(clone.get());
+
+ EXPECT_TRUE(permission->Equal(clone.get()));
+
+ // ToValue()/FromValue()
+ scoped_ptr<const base::Value> value(permission->ToValue());
+ EXPECT_TRUE(value.get());
+
+ scoped_ptr<SocketsManifestPermission> permission2(
+ new SocketsManifestPermission());
+ EXPECT_TRUE(permission2->FromValue(value.get()));
+ AssertEmptyPermission(permission2.get());
+
+ // Union/Diff/Intersection
+ scoped_ptr<SocketsManifestPermission> diff_perm(
+ static_cast<SocketsManifestPermission*>(permission->Diff(clone.get())));
+ AssertEmptyPermission(diff_perm.get());
+
+ scoped_ptr<SocketsManifestPermission> union_perm(
+ static_cast<SocketsManifestPermission*>(permission->Union(clone.get())));
+ AssertEmptyPermission(union_perm.get());
+
+ scoped_ptr<SocketsManifestPermission> intersect_perm(
+ static_cast<SocketsManifestPermission*>(
+ permission->Intersect(clone.get())));
+ AssertEmptyPermission(intersect_perm.get());
+
+ // IPC
+ scoped_ptr<SocketsManifestPermission> ipc_perm(
+ new SocketsManifestPermission());
+ scoped_ptr<SocketsManifestPermission> ipc_perm2(
+ new SocketsManifestPermission());
+
+ IPC::Message m;
+ ipc_perm->Write(&m);
+ base::PickleIterator iter(m);
+ EXPECT_TRUE(ipc_perm2->Read(&m, &iter));
+ AssertEmptyPermission(ipc_perm2.get());
+}
+
+TEST(SocketsManifestPermissionTest, JSONFormats) {
+ EXPECT_TRUE(CheckFormat(
+ "{\"udp\":{\"send\":\"\"}}",
+ CheckFormatEntry(SocketPermissionRequest::UDP_SEND_TO, "*:*")));
+ EXPECT_TRUE(CheckFormat("{\"udp\":{\"send\":[]}}"));
+ EXPECT_TRUE(CheckFormat(
+ "{\"udp\":{\"send\":[\"\"]}}",
+ CheckFormatEntry(SocketPermissionRequest::UDP_SEND_TO, "*:*")));
+ EXPECT_TRUE(CheckFormat(
+ "{\"udp\":{\"send\":[\"a:80\", \"b:10\"]}}",
+ CheckFormatEntry(SocketPermissionRequest::UDP_SEND_TO, "a:80"),
+ CheckFormatEntry(SocketPermissionRequest::UDP_SEND_TO, "b:10")));
+
+ EXPECT_TRUE(
+ CheckFormat("{\"udp\":{\"bind\":\"\"}}",
+ CheckFormatEntry(SocketPermissionRequest::UDP_BIND, "*:*")));
+ EXPECT_TRUE(CheckFormat("{\"udp\":{\"bind\":[]}}"));
+ EXPECT_TRUE(
+ CheckFormat("{\"udp\":{\"bind\":[\"\"]}}",
+ CheckFormatEntry(SocketPermissionRequest::UDP_BIND, "*:*")));
+ EXPECT_TRUE(
+ CheckFormat("{\"udp\":{\"bind\":[\"a:80\", \"b:10\"]}}",
+ CheckFormatEntry(SocketPermissionRequest::UDP_BIND, "a:80"),
+ CheckFormatEntry(SocketPermissionRequest::UDP_BIND, "b:10")));
+
+ EXPECT_TRUE(CheckFormat(
+ "{\"udp\":{\"multicastMembership\":\"\"}}",
+ CheckFormatEntry(SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP, "")));
+ EXPECT_TRUE(CheckFormat("{\"udp\":{\"multicastMembership\":[]}}"));
+ EXPECT_TRUE(CheckFormat(
+ "{\"udp\":{\"multicastMembership\":[\"\"]}}",
+ CheckFormatEntry(SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP, "")));
+ EXPECT_TRUE(CheckFormat(
+ "{\"udp\":{\"multicastMembership\":[\"\", \"\"]}}",
+ CheckFormatEntry(SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP, "")));
+
+ EXPECT_TRUE(CheckFormat(
+ "{\"tcp\":{\"connect\":\"\"}}",
+ CheckFormatEntry(SocketPermissionRequest::TCP_CONNECT, "*:*")));
+ EXPECT_TRUE(CheckFormat("{\"tcp\":{\"connect\":[]}}"));
+ EXPECT_TRUE(CheckFormat(
+ "{\"tcp\":{\"connect\":[\"\"]}}",
+ CheckFormatEntry(SocketPermissionRequest::TCP_CONNECT, "*:*")));
+ EXPECT_TRUE(CheckFormat(
+ "{\"tcp\":{\"connect\":[\"a:80\", \"b:10\"]}}",
+ CheckFormatEntry(SocketPermissionRequest::TCP_CONNECT, "a:80"),
+ CheckFormatEntry(SocketPermissionRequest::TCP_CONNECT, "b:10")));
+
+ EXPECT_TRUE(CheckFormat(
+ "{\"tcpServer\":{\"listen\":\"\"}}",
+ CheckFormatEntry(SocketPermissionRequest::TCP_LISTEN, "*:*")));
+ EXPECT_TRUE(CheckFormat("{\"tcpServer\":{\"listen\":[]}}"));
+ EXPECT_TRUE(CheckFormat(
+ "{\"tcpServer\":{\"listen\":[\"\"]}}",
+ CheckFormatEntry(SocketPermissionRequest::TCP_LISTEN, "*:*")));
+ EXPECT_TRUE(CheckFormat(
+ "{\"tcpServer\":{\"listen\":[\"a:80\", \"b:10\"]}}",
+ CheckFormatEntry(SocketPermissionRequest::TCP_LISTEN, "a:80"),
+ CheckFormatEntry(SocketPermissionRequest::TCP_LISTEN, "b:10")));
+
+ EXPECT_TRUE(CheckFormat(
+ "{"
+ "\"udp\":{"
+ "\"send\":[\"a:80\", \"b:10\"],"
+ "\"bind\":[\"a:80\", \"b:10\"],"
+ "\"multicastMembership\":\"\""
+ "},"
+ "\"tcp\":{\"connect\":[\"a:80\", \"b:10\"]},"
+ "\"tcpServer\":{\"listen\":[\"a:80\", \"b:10\"]}"
+ "}",
+ CheckFormatEntry(SocketPermissionRequest::UDP_SEND_TO, "a:80"),
+ CheckFormatEntry(SocketPermissionRequest::UDP_SEND_TO, "b:10"),
+ CheckFormatEntry(SocketPermissionRequest::UDP_BIND, "a:80"),
+ CheckFormatEntry(SocketPermissionRequest::UDP_BIND, "b:10"),
+ CheckFormatEntry(SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP, ""),
+ CheckFormatEntry(SocketPermissionRequest::TCP_CONNECT, "a:80"),
+ CheckFormatEntry(SocketPermissionRequest::TCP_CONNECT, "b:10"),
+ CheckFormatEntry(SocketPermissionRequest::TCP_LISTEN, "a:80"),
+ CheckFormatEntry(SocketPermissionRequest::TCP_LISTEN, "b:10")));
+}
+
+TEST(SocketsManifestPermissionTest, FromToValue) {
+ scoped_ptr<base::Value> udp_send(ParsePermissionJSON(kUdpBindPermission));
+ scoped_ptr<base::Value> udp_bind(ParsePermissionJSON(kUdpSendPermission));
+ scoped_ptr<base::Value> tcp_connect(
+ ParsePermissionJSON(kTcpConnectPermission));
+ scoped_ptr<base::Value> tcp_server_listen(
+ ParsePermissionJSON(kTcpServerListenPermission));
+
+ // FromValue()
+ scoped_ptr<SocketsManifestPermission> permission1(
+ new SocketsManifestPermission());
+ EXPECT_TRUE(permission1->FromValue(udp_send.get()));
+ EXPECT_EQ(2u, permission1->entries().size());
+
+ scoped_ptr<SocketsManifestPermission> permission2(
+ new SocketsManifestPermission());
+ EXPECT_TRUE(permission2->FromValue(udp_bind.get()));
+ EXPECT_EQ(2u, permission2->entries().size());
+
+ scoped_ptr<SocketsManifestPermission> permission3(
+ new SocketsManifestPermission());
+ EXPECT_TRUE(permission3->FromValue(tcp_connect.get()));
+ EXPECT_EQ(2u, permission3->entries().size());
+
+ scoped_ptr<SocketsManifestPermission> permission4(
+ new SocketsManifestPermission());
+ EXPECT_TRUE(permission4->FromValue(tcp_server_listen.get()));
+ EXPECT_EQ(2u, permission4->entries().size());
+
+ // ToValue()
+ scoped_ptr<base::Value> value1 = permission1->ToValue();
+ EXPECT_TRUE(value1);
+ scoped_ptr<SocketsManifestPermission> permission1_1(
+ new SocketsManifestPermission());
+ EXPECT_TRUE(permission1_1->FromValue(value1.get()));
+ EXPECT_TRUE(permission1->Equal(permission1_1.get()));
+
+ scoped_ptr<base::Value> value2 = permission2->ToValue();
+ EXPECT_TRUE(value2);
+ scoped_ptr<SocketsManifestPermission> permission2_1(
+ new SocketsManifestPermission());
+ EXPECT_TRUE(permission2_1->FromValue(value2.get()));
+ EXPECT_TRUE(permission2->Equal(permission2_1.get()));
+
+ scoped_ptr<base::Value> value3 = permission3->ToValue();
+ EXPECT_TRUE(value3);
+ scoped_ptr<SocketsManifestPermission> permission3_1(
+ new SocketsManifestPermission());
+ EXPECT_TRUE(permission3_1->FromValue(value3.get()));
+ EXPECT_TRUE(permission3->Equal(permission3_1.get()));
+
+ scoped_ptr<base::Value> value4 = permission4->ToValue();
+ EXPECT_TRUE(value4);
+ scoped_ptr<SocketsManifestPermission> permission4_1(
+ new SocketsManifestPermission());
+ EXPECT_TRUE(permission4_1->FromValue(value4.get()));
+ EXPECT_TRUE(permission4->Equal(permission4_1.get()));
+}
+
+TEST(SocketsManifestPermissionTest, SetOperations) {
+ scoped_ptr<SocketsManifestPermission> permission1(
+ PermissionFromJSON(kUdpBindPermission));
+ scoped_ptr<SocketsManifestPermission> permission2(
+ PermissionFromJSON(kUdpSendPermission));
+ scoped_ptr<SocketsManifestPermission> permission3(
+ PermissionFromJSON(kTcpConnectPermission));
+ scoped_ptr<SocketsManifestPermission> permission4(
+ PermissionFromJSON(kTcpServerListenPermission));
+
+ // Union
+ scoped_ptr<SocketsManifestPermission> union_perm(
+ static_cast<SocketsManifestPermission*>(
+ permission1->Union(permission2.get())));
+ EXPECT_TRUE(union_perm);
+ EXPECT_EQ(4u, union_perm->entries().size());
+
+ EXPECT_TRUE(union_perm->Contains(permission1.get()));
+ EXPECT_TRUE(union_perm->Contains(permission2.get()));
+ EXPECT_FALSE(union_perm->Contains(permission3.get()));
+ EXPECT_FALSE(union_perm->Contains(permission4.get()));
+
+ // Diff
+ scoped_ptr<SocketsManifestPermission> diff_perm1(
+ static_cast<SocketsManifestPermission*>(
+ permission1->Diff(permission2.get())));
+ EXPECT_TRUE(diff_perm1);
+ EXPECT_EQ(2u, diff_perm1->entries().size());
+
+ EXPECT_TRUE(permission1->Equal(diff_perm1.get()));
+ EXPECT_TRUE(diff_perm1->Equal(permission1.get()));
+
+ scoped_ptr<SocketsManifestPermission> diff_perm2(
+ static_cast<SocketsManifestPermission*>(
+ permission1->Diff(union_perm.get())));
+ EXPECT_TRUE(diff_perm2);
+ AssertEmptyPermission(diff_perm2.get());
+
+ // Intersection
+ scoped_ptr<SocketsManifestPermission> intersect_perm1(
+ static_cast<SocketsManifestPermission*>(
+ union_perm->Intersect(permission1.get())));
+ EXPECT_TRUE(intersect_perm1);
+ EXPECT_EQ(2u, intersect_perm1->entries().size());
+
+ EXPECT_TRUE(permission1->Equal(intersect_perm1.get()));
+ EXPECT_TRUE(intersect_perm1->Equal(permission1.get()));
+}
+
+TEST(SocketsManifestPermissionTest, IPC) {
+ scoped_ptr<SocketsManifestPermission> permission(
+ PermissionFromJSON(kUdpBindPermission));
+
+ scoped_ptr<SocketsManifestPermission> ipc_perm(
+ static_cast<SocketsManifestPermission*>(permission->Clone()));
+ scoped_ptr<SocketsManifestPermission> ipc_perm2(
+ new SocketsManifestPermission());
+
+ IPC::Message m;
+ ipc_perm->Write(&m);
+ base::PickleIterator iter(m);
+ EXPECT_TRUE(ipc_perm2->Read(&m, &iter));
+ EXPECT_TRUE(permission->Equal(ipc_perm2.get()));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/api/sockets_tcp.idl b/chromium/extensions/common/api/sockets_tcp.idl
new file mode 100644
index 00000000000..2dffa1729b1
--- /dev/null
+++ b/chromium/extensions/common/api/sockets_tcp.idl
@@ -0,0 +1,273 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.sockets.tcp</code> API to send and receive data over the
+// network using TCP connections. This API supersedes the TCP functionality
+// previously found in the <code>chrome.socket</code> API.
+namespace sockets.tcp {
+ // The socket properties specified in the <code>create</code> or
+ // <code>update</code> function. Each property is optional. If a property
+ // value is not specified, a default value is used when calling
+ // <code>create</code>, or the existing value if preserved when calling
+ // <code>update</code>.
+ dictionary SocketProperties {
+ // Flag indicating if the socket is left open when the event page of
+ // the application is unloaded (see
+ // <a href="http://developer.chrome.com/apps/app_lifecycle.html">Manage App
+ // Lifecycle</a>). The default value is "false." When the application is
+ // loaded, any sockets previously opened with persistent=true can be fetched
+ // with <code>getSockets</code>.
+ boolean? persistent;
+
+ // An application-defined string associated with the socket.
+ DOMString? name;
+
+ // The size of the buffer used to receive data. The default value is 4096.
+ long? bufferSize;
+ };
+
+ // Result of <code>create</code> call.
+ dictionary CreateInfo {
+ // The ID of the newly created socket. Note that socket IDs created from
+ // this API are not compatible with socket IDs created from other APIs, such
+ // as the deprecated <code>$(ref:socket)</code> API.
+ long socketId;
+ };
+
+ // Callback from the <code>create</code> method.
+ // |createInfo| : The result of the socket creation.
+ callback CreateCallback = void (CreateInfo createInfo);
+
+ // Callback from the <code>connect</code> method.
+ // |result| : The result code returned from the underlying network call.
+ // A negative value indicates an error.
+ callback ConnectCallback = void (long result);
+
+ // Callback from the <code>disconnect</code> method.
+ callback DisconnectCallback = void ();
+
+ // Result of the <code>send</code> method.
+ dictionary SendInfo {
+ // The result code returned from the underlying network call.
+ // A negative value indicates an error.
+ long resultCode;
+
+ // The number of bytes sent (if result == 0)
+ long? bytesSent;
+ };
+
+ // Callback from the <code>send</code> method.
+ // |sendInfo| : Result of the <code>send</code> method.
+ callback SendCallback = void (SendInfo sendInfo);
+
+ // Callback from the <code>close</code> method.
+ callback CloseCallback = void ();
+
+ // Callback from the <code>update</code> method.
+ callback UpdateCallback = void ();
+
+ // Callback from the <code>setPaused</code> method.
+ callback SetPausedCallback = void ();
+
+ // Callback from the <code>setKeepAliveCallback</code> method.
+ // |result| : The result code returned from the underlying network call.
+ // A negative value indicates an error.
+ callback SetKeepAliveCallback = void (long result);
+
+ // Callback from the <code>setNodeDelay</code> method.
+ // |result| : The result code returned from the underlying network call.
+ // A negative value indicates an error.
+ callback SetNoDelayCallback = void (long result);
+
+ dictionary TLSVersionConstraints {
+ // The minimum and maximum acceptable versions of TLS. These will
+ // be <code>tls1</code>, <code>tls1.1</code>, or <code>tls1.2</code>.
+ DOMString? min;
+ DOMString? max;
+ };
+
+ dictionary SecureOptions {
+ TLSVersionConstraints? tlsVersion;
+ };
+
+ callback SecureCallback = void (long result);
+
+ // Result of the <code>getInfo</code> method.
+ dictionary SocketInfo {
+ // The socket identifier.
+ long socketId;
+
+ // Flag indicating whether the socket is left open when the application is
+ // suspended (see <code>SocketProperties.persistent</code>).
+ boolean persistent;
+
+ // Application-defined string associated with the socket.
+ DOMString? name;
+
+ // The size of the buffer used to receive data. If no buffer size has been
+ // specified explictly, the value is not provided.
+ long? bufferSize;
+
+ // Flag indicating whether a connected socket blocks its peer from sending
+ // more data (see <code>setPaused</code>).
+ boolean paused;
+
+ // Flag indicating whether the socket is connected to a remote peer.
+ boolean connected;
+
+ // If the underlying socket is connected, contains its local IPv4/6 address.
+ DOMString? localAddress;
+
+ // If the underlying socket is connected, contains its local port.
+ long? localPort;
+
+ // If the underlying socket is connected, contains the peer/ IPv4/6 address.
+ DOMString? peerAddress;
+
+ // If the underlying socket is connected, contains the peer port.
+ long? peerPort;
+ };
+
+ // Callback from the <code>getInfo</code> method.
+ // |socketInfo| : Object containing the socket information.
+ callback GetInfoCallback = void (SocketInfo socketInfo);
+
+ // Callback from the <code>getSockets</code> method.
+ // |socketInfos| : Array of object containing socket information.
+ callback GetSocketsCallback = void (SocketInfo[] socketInfos);
+
+ // Data from an <code>onReceive</code> event.
+ dictionary ReceiveInfo {
+ // The socket identifier.
+ long socketId;
+
+ // The data received, with a maxium size of <code>bufferSize</code>.
+ ArrayBuffer data;
+ };
+
+ // Data from an <code>onReceiveError</code> event.
+ dictionary ReceiveErrorInfo {
+ // The socket identifier.
+ long socketId;
+
+ // The result code returned from the underlying network call.
+ long resultCode;
+ };
+
+ interface Functions {
+ // Creates a TCP socket.
+ // |properties| : The socket properties (optional).
+ // |callback| : Called when the socket has been created.
+ static void create(optional SocketProperties properties,
+ CreateCallback callback);
+
+ // Updates the socket properties.
+ // |socketId| : The socket identifier.
+ // |properties| : The properties to update.
+ // |callback| : Called when the properties are updated.
+ static void update(long socketId,
+ SocketProperties properties,
+ optional UpdateCallback callback);
+
+ // Enables or disables the application from receiving messages from its
+ // peer. The default value is "false". Pausing a socket is typically used
+ // by an application to throttle data sent by its peer. When a socket is
+ // paused, no <code>onReceive</code> event is raised. When a socket is
+ // connected and un-paused, <code>onReceive</code> events are raised again
+ // when messages are received.
+ static void setPaused(long socketId,
+ boolean paused,
+ optional SetPausedCallback callback);
+
+ // Enables or disables the keep-alive functionality for a TCP connection.
+ // |socketId| : The socket identifier.
+ // |enable| : If true, enable keep-alive functionality.
+ // |delay| : Set the delay seconds between the last data packet received
+ // and the first keepalive probe. Default is 0.
+ // |callback| : Called when the setKeepAlive attempt is complete.
+ static void setKeepAlive(long socketId,
+ boolean enable,
+ optional long delay,
+ SetKeepAliveCallback callback);
+
+ // Sets or clears <code>TCP_NODELAY</code> for a TCP connection. Nagle's
+ // algorithm will be disabled when <code>TCP_NODELAY</code> is set.
+ // |socketId| : The socket identifier.
+ // |noDelay| : If true, disables Nagle's algorithm.
+ // |callback| : Called when the setNoDelay attempt is complete.
+ static void setNoDelay(long socketId,
+ boolean noDelay,
+ SetNoDelayCallback callback);
+
+ // Connects the socket to a remote machine. When the <code>connect</code>
+ // operation completes successfully, <code>onReceive</code> events are
+ // raised when data is received from the peer. If a network error occurs
+ // while the runtime is receiving packets, a <code>onReceiveError</code>
+ // event is raised, at which point no more <code>onReceive</code> event will
+ // be raised for this socket until the <code>resume</code> method is called.
+ // |socketId| : The socket identifier.
+ // |peerAddress| : The address of the remote machine. DNS name, IPv4 and
+ // IPv6 formats are supported.
+ // |peerPort| : The port of the remote machine.
+ // |callback| : Called when the connect attempt is complete.
+ static void connect(long socketId,
+ DOMString peerAddress,
+ long peerPort,
+ ConnectCallback callback);
+
+ // Disconnects the socket.
+ // |socketId| : The socket identifier.
+ // |callback| : Called when the disconnect attempt is complete.
+ static void disconnect(long socketId,
+ optional DisconnectCallback callback);
+
+ // Start a TLS client connection over the connected TCP client socket.
+ // |socketId| : The existing, connected socket to use.
+ // |options| : Constraints and parameters for the TLS connection.
+ // |callback| : Called when the connection attempt is complete.
+ static void secure(long socketId,
+ optional SecureOptions options,
+ SecureCallback callback);
+
+ // Sends data on the given TCP socket.
+ // |socketId| : The socket identifier.
+ // |data| : The data to send.
+ // |callback| : Called when the <code>send</code> operation completes.
+ static void send(long socketId,
+ ArrayBuffer data,
+ SendCallback callback);
+
+ // Closes the socket and releases the address/port the socket is bound to.
+ // Each socket created should be closed after use. The socket id is no
+ // no longer valid as soon at the function is called. However, the socket is
+ // guaranteed to be closed only when the callback is invoked.
+ // |socketId| : The socket identifier.
+ // |callback| : Called when the <code>close</code> operation completes.
+ static void close(long socketId,
+ optional CloseCallback callback);
+
+ // Retrieves the state of the given socket.
+ // |socketId| : The socket identifier.
+ // |callback| : Called when the socket state is available.
+ static void getInfo(long socketId,
+ GetInfoCallback callback);
+
+ // Retrieves the list of currently opened sockets owned by the application.
+ // |callback| : Called when the list of sockets is available.
+ static void getSockets(GetSocketsCallback callback);
+ };
+
+ interface Events {
+ // Event raised when data has been received for a given socket.
+ // |info| : The event data.
+ static void onReceive(ReceiveInfo info);
+
+ // Event raised when a network error occured while the runtime was waiting
+ // for data on the socket address and port. Once this event is raised, the
+ // socket is set to <code>paused</code> and no more <code>onReceive</code>
+ // events are raised for this socket.
+ // |info| : The event data.
+ static void onReceiveError(ReceiveErrorInfo info);
+ };
+};
diff --git a/chromium/extensions/common/api/sockets_tcp_server.idl b/chromium/extensions/common/api/sockets_tcp_server.idl
new file mode 100644
index 00000000000..4a04fa9e9bd
--- /dev/null
+++ b/chromium/extensions/common/api/sockets_tcp_server.idl
@@ -0,0 +1,195 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.sockets.tcpServer</code> API to create server
+// applications using TCP connections. This API supersedes the TCP functionality
+// previously found in the <code>chrome.socket</code> API.
+namespace sockets.tcpServer {
+ // The socket properties specified in the <code>create</code> or
+ // <code>update</code> function. Each property is optional. If a property
+ // value is not specified, a default value is used when calling
+ // <code>create</code>, or the existing value if preserved when calling
+ // <code>update</code>.
+ dictionary SocketProperties {
+ // Flag indicating if the socket remains open when the event page of the
+ // application is unloaded (see
+ // <a href="http://developer.chrome.com/apps/app_lifecycle.html">Manage App
+ // Lifecycle</a>). The default value is "false." When the application is
+ // loaded, any sockets previously opened with persistent=true can be fetched
+ // with <code>getSockets</code>.
+ boolean? persistent;
+
+ // An application-defined string associated with the socket.
+ DOMString? name;
+ };
+
+ // Result of <code>create</code> call.
+ dictionary CreateInfo {
+ // The ID of the newly created server socket. Note that socket IDs created
+ // from this API are not compatible with socket IDs created from other APIs,
+ // such as the deprecated <code>$(ref:socket)</code> API.
+ long socketId;
+ };
+
+ // Callback from the <code>create</code> method.
+ // |createInfo| : The result of the socket creation.
+ callback CreateCallback = void (CreateInfo createInfo);
+
+ // Callback from the <code>listen</code> method.
+ // |result| : The result code returned from the underlying network call.
+ // A negative value indicates an error.
+ callback ListenCallback = void (long result);
+
+ // Callback from the <code>disconnect</code> method.
+ callback DisconnectCallback = void ();
+
+ // Callback from the <code>close</code> method.
+ callback CloseCallback = void ();
+
+ // Callback from the <code>update</code> method.
+ callback UpdateCallback = void ();
+
+ // Callback from the <code>setPaused</code> method.
+ callback SetPausedCallback = void ();
+
+ // Result of the <code>getInfo</code> method.
+ dictionary SocketInfo {
+ // The socket identifier.
+ long socketId;
+
+ // Flag indicating if the socket remains open when the event page of the
+ // application is unloaded (see <code>SocketProperties.persistent</code>).
+ // The default value is "false".
+ boolean persistent;
+
+ // Application-defined string associated with the socket.
+ DOMString? name;
+
+ // Flag indicating whether connection requests on a listening socket are
+ // dispatched through the <code>onAccept</code> event or queued up in the
+ // listen queue backlog.
+ // See <code>setPaused</code>. The default value is "false".
+ boolean paused;
+
+ // If the socket is listening, contains its local IPv4/6 address.
+ DOMString? localAddress;
+
+ // If the socket is listening, contains its local port.
+ long? localPort;
+ };
+
+ // Callback from the <code>getInfo</code> method.
+ // |socketInfo| : Object containing the socket information.
+ callback GetInfoCallback = void (SocketInfo socketInfo);
+
+ // Callback from the <code>getSockets</code> method.
+ // |socketInfos| : Array of object containing socket information.
+ callback GetSocketsCallback = void (SocketInfo[] socketInfos);
+
+ // Data from an <code>onAccept</code> event.
+ dictionary AcceptInfo {
+ // The server socket identifier.
+ long socketId;
+
+ // The client socket identifier, i.e. the socket identifier of the newly
+ // established connection. This socket identifier should be used only with
+ // functions from the <code>chrome.sockets.tcp</code> namespace. Note the
+ // client socket is initially paused and must be explictly un-paused by the
+ // application to start receiving data.
+ long clientSocketId;
+ };
+
+ // Data from an <code>onAcceptError</code> event.
+ dictionary AcceptErrorInfo {
+ // The server socket identifier.
+ long socketId;
+
+ // The result code returned from the underlying network call.
+ long resultCode;
+ };
+
+ interface Functions {
+ // Creates a TCP server socket.
+ // |properties| : The socket properties (optional).
+ // |callback| : Called when the socket has been created.
+ static void create(optional SocketProperties properties,
+ CreateCallback callback);
+
+ // Updates the socket properties.
+ // |socketId| : The socket identifier.
+ // |properties| : The properties to update.
+ // |callback| : Called when the properties are updated.
+ static void update(long socketId,
+ SocketProperties properties,
+ optional UpdateCallback callback);
+
+ // Enables or disables a listening socket from accepting new connections.
+ // When paused, a listening socket accepts new connections until its backlog
+ // (see <code>listen</code> function) is full then refuses additional
+ // connection requests. <code>onAccept</code> events are raised only when
+ // the socket is un-paused.
+ static void setPaused(long socketId,
+ boolean paused,
+ optional SetPausedCallback callback);
+
+ // Listens for connections on the specified port and address.
+ // If the port/address is in use, the callback indicates a failure.
+ // |socketId| : The socket identifier.
+ // |address| : The address of the local machine.
+ // |port| : The port of the local machine. When set to <code>0</code>, a
+ // free port is chosen dynamically. The dynamically allocated port can be
+ // found by calling <code>getInfo</code>.
+ // |backlog| : Length of the socket's listen queue. The default value
+ // depends on the Operating System (SOMAXCONN), which ensures a reasonable
+ // queue length for most applications.
+ // |callback| : Called when listen operation completes.
+ static void listen(long socketId,
+ DOMString address,
+ long port,
+ optional long backlog,
+ ListenCallback callback);
+
+ // Disconnects the listening socket, i.e. stops accepting new connections
+ // and releases the address/port the socket is bound to. The socket
+ // identifier remains valid, e.g. it can be used with <code>listen</code> to
+ // accept connections on a new port and address.
+ // |socketId| : The socket identifier.
+ // |callback| : Called when the disconnect attempt is complete.
+ static void disconnect(long socketId,
+ optional DisconnectCallback callback);
+
+ // Disconnects and destroys the socket. Each socket created should be
+ // closed after use. The socket id is no longer valid as soon at the
+ // function is called. However, the socket is guaranteed to be closed only
+ // when the callback is invoked.
+ // |socketId| : The socket identifier.
+ // |callback| : Called when the <code>close</code> operation completes.
+ static void close(long socketId,
+ optional CloseCallback callback);
+
+ // Retrieves the state of the given socket.
+ // |socketId| : The socket identifier.
+ // |callback| : Called when the socket state is available.
+ static void getInfo(long socketId,
+ GetInfoCallback callback);
+
+ // Retrieves the list of currently opened sockets owned by the application.
+ // |callback| : Called when the list of sockets is available.
+ static void getSockets(GetSocketsCallback callback);
+ };
+
+ interface Events {
+ // Event raised when a connection has been made to the server socket.
+ // |info| : The event data.
+ static void onAccept(AcceptInfo info);
+
+ // Event raised when a network error occured while the runtime was waiting
+ // for new connections on the socket address and port. Once this event is
+ // raised, the socket is set to <code>paused</code> and no more
+ // <code>onAccept</code> events are raised for this socket until the socket
+ // is resumed.
+ // |info| : The event data.
+ static void onAcceptError(AcceptErrorInfo info);
+ };
+};
diff --git a/chromium/extensions/common/api/sockets_udp.idl b/chromium/extensions/common/api/sockets_udp.idl
new file mode 100644
index 00000000000..784a31d25d7
--- /dev/null
+++ b/chromium/extensions/common/api/sockets_udp.idl
@@ -0,0 +1,321 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.sockets.udp</code> API to send and receive data over the
+// network using UDP connections. This API supersedes the UDP functionality
+// previously found in the "socket" API.
+namespace sockets.udp {
+ // The socket properties specified in the <code>create</code> or
+ // <code>update</code> function. Each property is optional. If a property
+ // value is not specified, a default value is used when calling
+ // <code>create</code>, or the existing value if preserved when calling
+ // <code>update</code>.
+ dictionary SocketProperties {
+ // Flag indicating if the socket is left open when the event page of the
+ // application is unloaded (see
+ // <a href="http://developer.chrome.com/apps/app_lifecycle.html">Manage App
+ // Lifecycle</a>). The default value is "false." When the application is
+ // loaded, any sockets previously opened with persistent=true can be fetched
+ // with <code>getSockets</code>.
+ boolean? persistent;
+
+ // An application-defined string associated with the socket.
+ DOMString? name;
+
+ // The size of the buffer used to receive data. If the buffer is too small
+ // to receive the UDP packet, data is lost. The default value is 4096.
+ long? bufferSize;
+ };
+
+ // Result of <code>create</code> call.
+ dictionary CreateInfo {
+ // The ID of the newly created socket. Note that socket IDs created from
+ // this API are not compatible with socket IDs created from other APIs, such
+ // as the deprecated <code>$(ref:socket)</code> API.
+ long socketId;
+ };
+
+ // Callback from the <code>create</code> method.
+ // |createInfo| : The result of the socket creation.
+ callback CreateCallback = void (CreateInfo createInfo);
+
+ // Callback from the <code>bind</code> method.
+ // |result| : The result code returned from the underlying network call.
+ // A negative value indicates an error.
+ callback BindCallback = void (long result);
+
+ // Result of the <code>send</code> method.
+ dictionary SendInfo {
+ // The result code returned from the underlying network call.
+ // A negative value indicates an error.
+ long resultCode;
+
+ // The number of bytes sent (if result == 0)
+ long? bytesSent;
+ };
+
+ // Callback from the <code>send</code> method.
+ // |sendInfo| : Result of the <code>send</code> method.
+ callback SendCallback = void (SendInfo sendInfo);
+
+ // Callback from the <code>close</code> method.
+ callback CloseCallback = void ();
+
+ // Callback from the <code>update</code> method.
+ callback UpdateCallback = void ();
+
+ // Callback from the <code>setPaused</code> method.
+ callback SetPausedCallback = void ();
+
+ // Result of the <code>getInfo</code> method.
+ dictionary SocketInfo {
+ // The socket identifier.
+ long socketId;
+
+ // Flag indicating whether the socket is left open when the application is
+ // suspended (see <code>SocketProperties.persistent</code>).
+ boolean persistent;
+
+ // Application-defined string associated with the socket.
+ DOMString? name;
+
+ // The size of the buffer used to receive data. If no buffer size has been
+ // specified explictly, the value is not provided.
+ long? bufferSize;
+
+ // Flag indicating whether the socket is blocked from firing onReceive
+ // events.
+ boolean paused;
+
+ // If the underlying socket is bound, contains its local
+ // IPv4/6 address.
+ DOMString? localAddress;
+
+ // If the underlying socket is bound, contains its local port.
+ long? localPort;
+ };
+
+ // Callback from the <code>getInfo</code> method.
+ // |socketInfo| : Object containing the socket information.
+ callback GetInfoCallback = void (SocketInfo socketInfo);
+
+ // Callback from the <code>getSockets</code> method.
+ // |socketInfos| : Array of object containing socket information.
+ callback GetSocketsCallback = void (SocketInfo[] socketInfos);
+
+ // Callback from the <code>joinGroup</code> method.
+ // |result| : The result code returned from the underlying network call.
+ // A negative value indicates an error.
+ callback JoinGroupCallback = void (long result);
+
+ // Callback from the <code>leaveGroup</code> method.
+ // |result| : The result code returned from the underlying network call.
+ // A negative value indicates an error.
+ callback LeaveGroupCallback = void (long result);
+
+ // Callback from the <code>setMulticastTimeToLive</code> method.
+ // |result| : The result code returned from the underlying network call.
+ // A negative value indicates an error.
+ callback SetMulticastTimeToLiveCallback = void (long result);
+
+ // Callback from the <code>setMulticastLoopbackMode</code> method.
+ // |result| : The result code returned from the underlying network call.
+ // A negative value indicates an error.
+ callback SetMulticastLoopbackModeCallback = void (long result);
+
+ // Callback from the <code>getJoinedGroupsCallback</code> method.
+ // |groups| : Array of groups the socket joined.
+ callback GetJoinedGroupsCallback = void (DOMString[] groups);
+
+ // Callback from the <code>setBroadcast</code> method.
+ // |result| : The result code returned from the underlying network call.
+ callback SetBroadcastCallback = void (long result);
+
+ // Data from an <code>onReceive</code> event.
+ dictionary ReceiveInfo {
+ // The socket ID.
+ long socketId;
+
+ // The UDP packet content (truncated to the current buffer size).
+ ArrayBuffer data;
+
+ // The address of the host the packet comes from.
+ DOMString remoteAddress;
+
+ // The port of the host the packet comes from.
+ long remotePort;
+ };
+
+ // Data from an <code>onReceiveError</code> event.
+ dictionary ReceiveErrorInfo {
+ // The socket ID.
+ long socketId;
+
+ // The result code returned from the underlying recvfrom() call.
+ long resultCode;
+ };
+
+ interface Functions {
+ // Creates a UDP socket with the given properties.
+ // |properties| : The socket properties (optional).
+ // |callback| : Called when the socket has been created.
+ static void create(optional SocketProperties properties,
+ CreateCallback callback);
+
+ // Updates the socket properties.
+ // |socketId| : The socket ID.
+ // |properties| : The properties to update.
+ // |callback| : Called when the properties are updated.
+ static void update(long socketId,
+ SocketProperties properties,
+ optional UpdateCallback callback);
+
+ // Pauses or unpauses a socket. A paused socket is blocked from firing
+ // <code>onReceive</code> events.
+ // |connectionId| : The socket ID.
+ // |paused| : Flag to indicate whether to pause or unpause.
+ // |callback| : Called when the socket has been successfully paused or
+ // unpaused.
+ static void setPaused(long socketId,
+ boolean paused,
+ optional SetPausedCallback callback);
+
+ // Binds the local address and port for the socket. For a client socket, it
+ // is recommended to use port 0 to let the platform pick a free port.
+ //
+ // Once the <code>bind</code> operation completes successfully,
+ // <code>onReceive</code> events are raised when UDP packets arrive on the
+ // address/port specified -- unless the socket is paused.
+ //
+ // |socketId| : The socket ID.
+ // |address| : The address of the local machine. DNS name, IPv4 and IPv6
+ // formats are supported. Use "0.0.0.0" to accept packets from all local
+ // available network interfaces.
+ // |port| : The port of the local machine. Use "0" to bind to a free port.
+ // |callback| : Called when the <code>bind</code> operation completes.
+ static void bind(long socketId,
+ DOMString address,
+ long port,
+ BindCallback callback);
+
+ // Sends data on the given socket to the given address and port. The socket
+ // must be bound to a local port before calling this method.
+ // |socketId| : The socket ID.
+ // |data| : The data to send.
+ // |address| : The address of the remote machine.
+ // |port| : The port of the remote machine.
+ // |callback| : Called when the <code>send</code> operation completes.
+ static void send(long socketId,
+ ArrayBuffer data,
+ DOMString address,
+ long port,
+ SendCallback callback);
+
+ // Closes the socket and releases the address/port the socket is bound to.
+ // Each socket created should be closed after use. The socket id is no
+ // longer valid as soon at the function is called. However, the socket is
+ // guaranteed to be closed only when the callback is invoked.
+ // |socketId| : The socket ID.
+ // |callback| : Called when the <code>close</code> operation completes.
+ static void close(long socketId,
+ optional CloseCallback callback);
+
+ // Retrieves the state of the given socket.
+ // |socketId| : The socket ID.
+ // |callback| : Called when the socket state is available.
+ static void getInfo(long socketId,
+ GetInfoCallback callback);
+
+ // Retrieves the list of currently opened sockets owned by the application.
+ // |callback| : Called when the list of sockets is available.
+ static void getSockets(GetSocketsCallback callback);
+
+ // Joins the multicast group and starts to receive packets from that group.
+ // The socket must be bound to a local port before calling this method.
+ // |socketId| : The socket ID.
+ // |address| : The group address to join. Domain names are not supported.
+ // |callback| : Called when the <code>joinGroup</code> operation completes.
+ static void joinGroup(long socketId,
+ DOMString address,
+ JoinGroupCallback callback);
+
+ // Leaves the multicast group previously joined using
+ // <code>joinGroup</code>. This is only necessary to call if you plan to
+ // keep using the socketafterwards, since it will be done automatically by
+ // the OS when the socket is closed.
+ //
+ // Leaving the group will prevent the router from sending multicast
+ // datagrams to the local host, presuming no other process on the host is
+ // still joined to the group.
+ //
+ // |socketId| : The socket ID.
+ // |address| : The group address to leave. Domain names are not supported.
+ // |callback| : Called when the <code>leaveGroup</code> operation completes.
+ static void leaveGroup(long socketId,
+ DOMString address,
+ LeaveGroupCallback callback);
+
+ // Sets the time-to-live of multicast packets sent to the multicast group.
+ //
+ // Calling this method does not require multicast permissions.
+ //
+ // |socketId| : The socket ID.
+ // |ttl| : The time-to-live value.
+ // |callback| : Called when the configuration operation completes.
+ static void setMulticastTimeToLive(
+ long socketId,
+ long ttl,
+ SetMulticastTimeToLiveCallback callback);
+
+ // Sets whether multicast packets sent from the host to the multicast group
+ // will be looped back to the host.
+ //
+ // Note: the behavior of <code>setMulticastLoopbackMode</code> is slightly
+ // different between Windows and Unix-like systems. The inconsistency
+ // happens only when there is more than one application on the same host
+ // joined to the same multicast group while having different settings on
+ // multicast loopback mode. On Windows, the applications with loopback off
+ // will not RECEIVE the loopback packets; while on Unix-like systems, the
+ // applications with loopback off will not SEND the loopback packets to
+ // other applications on the same host. See MSDN: http://goo.gl/6vqbj
+ //
+ // Calling this method does not require multicast permissions.
+ //
+ // |socketId| : The socket ID.
+ // |enabled| : Indicate whether to enable loopback mode.
+ // |callback| : Called when the configuration operation completes.
+ static void setMulticastLoopbackMode(
+ long socketId,
+ boolean enabled,
+ SetMulticastLoopbackModeCallback callback);
+
+ // Gets the multicast group addresses the socket is currently joined to.
+ // |socketId| : The socket ID.
+ // |callback| : Called with an array of strings of the result.
+ static void getJoinedGroups(long socketId,
+ GetJoinedGroupsCallback callback);
+
+ // Enables or disables broadcast packets on this socket.
+ //
+ // |socketId| : The socket ID.
+ // |enabled| : <code>true</code> to enable broadcast packets,
+ // <code>false</code> to disable them.
+ static void setBroadcast(long socketId,
+ boolean enabled,
+ SetBroadcastCallback callback);
+ };
+
+ interface Events {
+ // Event raised when a UDP packet has been received for the given socket.
+ // |info| : The event data.
+ static void onReceive(ReceiveInfo info);
+
+ // Event raised when a network error occured while the runtime was waiting
+ // for data on the socket address and port. Once this event is raised, the
+ // socket is paused and no more <code>onReceive</code> events will be raised
+ // for this socket until the socket is resumed.
+ // |info| : The event data.
+ static void onReceiveError(ReceiveErrorInfo info);
+ };
+};
diff --git a/chromium/extensions/common/api/storage.json b/chromium/extensions/common/api/storage.json
new file mode 100644
index 00000000000..57d864e9fc9
--- /dev/null
+++ b/chromium/extensions/common/api/storage.json
@@ -0,0 +1,224 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "storage",
+ "description": "Use the <code>chrome.storage</code> API to store, retrieve, and track changes to user data.",
+ "unprivileged": true,
+ "types": [
+ {
+ "id": "StorageChange",
+ "type": "object",
+ "properties": {
+ "oldValue": {
+ "type": "any",
+ "description": "The old value of the item, if there was an old value.",
+ "optional": true
+ },
+ "newValue": {
+ "type": "any",
+ "description": "The new value of the item, if there is a new value.",
+ "optional": true
+ }
+ }
+ },
+ {
+ "id": "StorageArea",
+ "type": "object",
+ "js_module": "StorageArea",
+ "functions": [
+ {
+ "name": "get",
+ "type": "function",
+ "description": "Gets one or more items from storage.",
+ "parameters": [
+ {
+ "name": "keys",
+ "choices": [
+ { "type": "string" },
+ { "type": "array", "items": { "type": "string" } },
+ {
+ "type": "object",
+ "description": "Storage items to return in the callback, where the values are replaced with those from storage if they exist.",
+ "additionalProperties": { "type": "any" }
+ }
+ ],
+ "description": "A single key to get, list of keys to get, or a dictionary specifying default values (see description of the object). An empty list or object will return an empty result object. Pass in <code>null</code> to get the entire contents of storage.",
+ "optional": true
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "description": "Callback with storage items, or on failure (in which case $(ref:runtime.lastError) will be set).",
+ "parameters": [
+ {
+ "name": "items",
+ "type": "object",
+ "additionalProperties": { "type": "any" },
+ "description": "Object with items in their key-value mappings."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getBytesInUse",
+ "type": "function",
+ "description": "Gets the amount of space (in bytes) being used by one or more items.",
+ "parameters": [
+ {
+ "name": "keys",
+ "choices": [
+ { "type": "string" },
+ { "type": "array", "items": { "type": "string" } }
+ ],
+ "description": "A single key or list of keys to get the total usage for. An empty list will return 0. Pass in <code>null</code> to get the total usage of all of storage.",
+ "optional": true
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "description": "Callback with the amount of space being used by storage, or on failure (in which case $(ref:runtime.lastError) will be set).",
+ "parameters": [
+ {
+ "name": "bytesInUse",
+ "type": "integer",
+ "description": "Amount of space being used in storage, in bytes."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "set",
+ "type": "function",
+ "description": "Sets multiple items.",
+ "parameters": [
+ {
+ "name": "items",
+ "type": "object",
+ "additionalProperties": { "type": "any" },
+ "description": "<p>An object which gives each key/value pair to update storage with. Any other key/value pairs in storage will not be affected.</p><p>Primitive values such as numbers will serialize as expected. Values with a <code>typeof</code> <code>\"object\"</code> and <code>\"function\"</code> will typically serialize to <code>{}</code>, with the exception of <code>Array</code> (serializes as expected), <code>Date</code>, and <code>Regex</code> (serialize using their <code>String</code> representation).</p>"
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "description": "Callback on success, or on failure (in which case $(ref:runtime.lastError) will be set).",
+ "parameters": [],
+ "optional": true
+ }
+ ]
+ },
+ {
+ "name": "remove",
+ "type": "function",
+ "description": "Removes one or more items from storage.",
+ "parameters": [
+ {
+ "name": "keys",
+ "choices": [
+ {"type": "string"},
+ {"type": "array", "items": {"type": "string"}}
+ ],
+ "description": "A single key or a list of keys for items to remove."
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "description": "Callback on success, or on failure (in which case $(ref:runtime.lastError) will be set).",
+ "parameters": [],
+ "optional": true
+ }
+ ]
+ },
+ {
+ "name": "clear",
+ "type": "function",
+ "description": "Removes all items from storage.",
+ "parameters": [
+ {
+ "name": "callback",
+ "type": "function",
+ "description": "Callback on success, or on failure (in which case $(ref:runtime.lastError) will be set).",
+ "parameters": [],
+ "optional": true
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "onChanged",
+ "type": "function",
+ "description": "Fired when one or more items change.",
+ "parameters": [
+ {
+ "name": "changes",
+ "type": "object",
+ "additionalProperties": { "$ref": "StorageChange" },
+ "description": "Object mapping each key that changed to its corresponding $(ref:storage.StorageChange) for that item."
+ },
+ {
+ "name": "areaName",
+ "type": "string",
+ "description": "The name of the storage area (<code>\"sync\"</code>, <code>\"local\"</code> or <code>\"managed\"</code>) the changes are for."
+ }
+ ]
+ }
+ ],
+ "properties": {
+ "sync": {
+ "$ref": "StorageArea",
+ "description": "Items in the <code>sync</code> storage area are synced using Chrome Sync.",
+ "value": [ "sync" ],
+ "properties": {
+ "QUOTA_BYTES": {
+ "value": 102400,
+ "description": "The maximum total amount (in bytes) of data that can be stored in sync storage, as measured by the JSON stringification of every value plus every key's length. Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError)."
+ },
+ "QUOTA_BYTES_PER_ITEM": {
+ "value": 8192,
+ "description": "The maximum size (in bytes) of each individual item in sync storage, as measured by the JSON stringification of its value plus its key length. Updates containing items larger than this limit will fail immediately and set $(ref:runtime.lastError)."
+ },
+ "MAX_ITEMS": {
+ "value": 512,
+ "description": "The maximum number of items that can be stored in sync storage. Updates that would cause this limit to be exceeded will fail immediately and set $(ref:runtime.lastError)."
+ },
+ "MAX_WRITE_OPERATIONS_PER_HOUR": {
+ "value": 1800,
+ "description": "<p>The maximum number of <code>set</code>, <code>remove</code>, or <code>clear</code> operations that can be performed each hour. This is 1 every 2 seconds, a lower ceiling than the short term higher writes-per-minute limit.</p><p>Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError).</p>"
+ },
+ "MAX_WRITE_OPERATIONS_PER_MINUTE": {
+ "value": 120,
+ "description": "<p>The maximum number of <code>set</code>, <code>remove</code>, or <code>clear</code> operations that can be performed each minute. This is 2 per second, providing higher throughput than writes-per-hour over a shorter period of time.</p><p>Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError).</p>"
+ },
+ "MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE": {
+ "value": 1000000,
+ "deprecated": "The storage.sync API no longer has a sustained write operation quota.",
+ "description": ""
+ }
+ }
+ },
+ "local": {
+ "$ref": "StorageArea",
+ "description": "Items in the <code>local</code> storage area are local to each machine.",
+ "value": [ "local" ],
+ "properties": {
+ "QUOTA_BYTES": {
+ "value": 5242880,
+ "description": "The maximum amount (in bytes) of data that can be stored in local storage, as measured by the JSON stringification of every value plus every key's length. This value will be ignored if the extension has the <code>unlimitedStorage</code> permission. Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError)."
+ }
+ }
+ },
+ "managed": {
+ "$ref": "StorageArea",
+ "description": "Items in the <code>managed</code> storage area are set by the domain administrator, and are read-only for the extension; trying to modify this namespace results in an error.",
+ "value": [ "managed" ]
+ }
+ }
+ }
+]
diff --git a/chromium/extensions/common/api/system_cpu.idl b/chromium/extensions/common/api/system_cpu.idl
new file mode 100644
index 00000000000..bb3ac857f0d
--- /dev/null
+++ b/chromium/extensions/common/api/system_cpu.idl
@@ -0,0 +1,55 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>system.cpu</code> API to query CPU metadata.
+namespace system.cpu {
+
+ // Counters for assessing CPU utilization. Each field is monotonically
+ // increasing while the processor is powered on. Values are in milliseconds.
+ dictionary CpuTime {
+ // The cumulative time used by userspace programs on this processor.
+ double user;
+
+ // The cumulative time used by kernel programs on this processor.
+ double kernel;
+
+ // The cumulative time spent idle by this processor.
+ double idle;
+
+ // The total cumulative time for this processor. This value is equal to
+ // user + kernel + idle.
+ double total;
+ };
+
+ dictionary ProcessorInfo {
+ // Cumulative usage info for this logical processor.
+ CpuTime usage;
+ };
+
+ dictionary CpuInfo {
+ // The number of logical processors.
+ long numOfProcessors;
+
+ // The architecture name of the processors.
+ DOMString archName;
+
+ // The model name of the processors.
+ DOMString modelName;
+
+ // A set of feature codes indicating some of the processor's capabilities.
+ // The currently supported codes are "mmx", "sse", "sse2", "sse3", "ssse3",
+ // "sse4_1", "sse4_2", and "avx".
+ DOMString[] features;
+
+ // Information about each logical processor.
+ ProcessorInfo[] processors;
+ };
+
+ callback CpuInfoCallback = void (CpuInfo info);
+
+ interface Functions {
+ // Queries basic CPU information of the system.
+ static void getInfo(CpuInfoCallback callback);
+ };
+};
diff --git a/chromium/extensions/common/api/system_display.idl b/chromium/extensions/common/api/system_display.idl
new file mode 100644
index 00000000000..4e3c9e5b63a
--- /dev/null
+++ b/chromium/extensions/common/api/system_display.idl
@@ -0,0 +1,155 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>system.display</code> API to query display metadata.
+namespace system.display {
+
+ dictionary Bounds {
+ // The x-coordinate of the upper-left corner.
+ long left;
+
+ // The y-coordinate of the upper-left corner.
+ long top;
+
+ // The width of the display in pixels.
+ long width;
+
+ // The height of the display in pixels.
+ long height;
+ };
+
+ dictionary Insets {
+ // The x-axis distance from the left bound.
+ long left;
+
+ // The y-axis distance from the top bound.
+ long top;
+
+ // The x-axis distance from the right bound.
+ long right;
+
+ // The y-axis distance from the bottom bound.
+ long bottom;
+ };
+
+ dictionary DisplayUnitInfo {
+ // The unique identifier of the display.
+ DOMString id;
+
+ // The user-friendly name (e.g. "HP LCD monitor").
+ DOMString name;
+
+ // Identifier of the display that is being mirrored on the display unit.
+ // If mirroring is not in progress, set to an empty string.
+ // Currently exposed only on ChromeOS. Will be empty string on other
+ // platforms.
+ DOMString mirroringSourceId;
+
+ // True if this is the primary display.
+ boolean isPrimary;
+
+ // True if this is an internal display.
+ boolean isInternal;
+
+ // True if this display is enabled.
+ boolean isEnabled;
+
+ // The number of pixels per inch along the x-axis.
+ double dpiX;
+
+ // The number of pixels per inch along the y-axis.
+ double dpiY;
+
+ // The display's clockwise rotation in degrees relative to the vertical
+ // position.
+ // Currently exposed only on ChromeOS. Will be set to 0 on other platforms.
+ long rotation;
+
+ // The display's logical bounds.
+ Bounds bounds;
+
+ // The display's insets within its screen's bounds.
+ // Currently exposed only on ChromeOS. Will be set to empty insets on
+ // other platforms.
+ Insets overscan;
+
+ // The usable work area of the display within the display bounds. The work
+ // area excludes areas of the display reserved for OS, for example taskbar
+ // and launcher.
+ Bounds workArea;
+ };
+
+ dictionary DisplayProperties {
+ // If set and not empty, starts mirroring between this and the display with
+ // the provided id (the system will determine which of the displays is
+ // actually mirrored).
+ // If set and not empty, stops mirroring between this and the display with
+ // the specified id (if mirroring is in progress).
+ // If set, no other parameter may be set.
+ DOMString? mirroringSourceId;
+
+ // If set to true, makes the display primary. No-op if set to false.
+ boolean? isPrimary;
+
+ // If set, sets the display's overscan insets to the provided values. Note
+ // that overscan values may not be negative or larger than a half of the
+ // screen's size. Overscan cannot be changed on the internal monitor.
+ // It's applied after <code>isPrimary</code> parameter.
+ Insets? overscan;
+
+ // If set, updates the display's rotation.
+ // Legal values are [0, 90, 180, 270]. The rotation is set clockwise,
+ // relative to the display's vertical position.
+ // It's applied after <code>overscan</code> paramter.
+ long? rotation;
+
+ // If set, updates the display's logical bounds origin along x-axis. Applied
+ // together with <code>boundsOriginY</code>, if <code>boundsOriginY</code>
+ // is set. Note that, when updating the display origin, some constraints
+ // will be applied, so the final bounds origin may be different than the one
+ // set. The final bounds can be retrieved using $(ref:getInfo).
+ // The bounds origin is applied after <code>rotation</code>.
+ // The bounds origin cannot be changed on the primary display. Note that is
+ // also invalid to set bounds origin values if <code>isPrimary</code> is
+ // also set (as <code>isPrimary</code> parameter is applied first).
+ long? boundsOriginX;
+
+ // If set, updates the display's logical bounds origin along y-axis.
+ // See documentation for <code>boundsOriginX</code> parameter.
+ long? boundsOriginY;
+ };
+
+ callback DisplayInfoCallback = void (DisplayUnitInfo[] displayInfo);
+ callback SetDisplayUnitInfoCallback = void();
+
+ interface Functions {
+ // Get the information of all attached display devices.
+ static void getInfo(DisplayInfoCallback callback);
+
+ // Updates the properties for the display specified by |id|, according to
+ // the information provided in |info|. On failure, $(ref:runtime.lastError)
+ // will be set.
+ // |id|: The display's unique identifier.
+ // |info|: The information about display properties that should be changed.
+ // A property will be changed only if a new value for it is specified in
+ // |info|.
+ // |callback|: Empty function called when the function finishes. To find out
+ // whether the function succeeded, $(ref:runtime.lastError) should be
+ // queried.
+ static void setDisplayProperties(
+ DOMString id,
+ DisplayProperties info,
+ optional SetDisplayUnitInfoCallback callback);
+
+ // Enables/disables the unified desktop feature. Note that this simply
+ // enables the feature, but will not change the actual desktop mode.
+ // (That is, if the desktop is in mirror mode, it will stay in mirror mode)
+ static void enableUnifiedDesktop(boolean enabled);
+ };
+
+ interface Events {
+ // Fired when anything changes to the display configuration.
+ static void onDisplayChanged();
+ };
+};
diff --git a/chromium/extensions/common/api/system_memory.idl b/chromium/extensions/common/api/system_memory.idl
new file mode 100644
index 00000000000..64af2148903
--- /dev/null
+++ b/chromium/extensions/common/api/system_memory.idl
@@ -0,0 +1,21 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The <code>chrome.system.memory</code> API.
+namespace system.memory {
+
+ dictionary MemoryInfo {
+ // The total amount of physical memory capacity, in bytes.
+ double capacity;
+ // The amount of available capacity, in bytes.
+ double availableCapacity;
+ };
+
+ callback MemoryInfoCallback = void (MemoryInfo info);
+
+ interface Functions {
+ // Get physical memory information.
+ static void getInfo(MemoryInfoCallback callback);
+ };
+};
diff --git a/chromium/extensions/common/api/system_network.idl b/chromium/extensions/common/api/system_network.idl
new file mode 100644
index 00000000000..bbc28ae9dd5
--- /dev/null
+++ b/chromium/extensions/common/api/system_network.idl
@@ -0,0 +1,30 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.system.network</code> API.
+namespace system.network {
+ dictionary NetworkInterface {
+ // The underlying name of the adapter. On *nix, this will typically be
+ // "eth0", "wlan0", etc.
+ DOMString name;
+
+ // The available IPv4/6 address.
+ DOMString address;
+
+ // The prefix length
+ long prefixLength;
+ };
+
+ // Callback from the <code>getNetworkInterfaces</code> method.
+ // |networkInterfaces| : Array of object containing network interfaces
+ // information.
+ callback GetNetworkInterfacesCallback =
+ void (NetworkInterface[] networkInterfaces);
+
+ interface Functions {
+ // Retrieves information about local adapters on this system.
+ // |callback| : Called when local adapter information is available.
+ static void getNetworkInterfaces(GetNetworkInterfacesCallback callback);
+ };
+};
diff --git a/chromium/extensions/common/api/system_storage.idl b/chromium/extensions/common/api/system_storage.idl
new file mode 100644
index 00000000000..f5ca4d56a9c
--- /dev/null
+++ b/chromium/extensions/common/api/system_storage.idl
@@ -0,0 +1,83 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.system.storage</code> API to query storage device
+// information and be notified when a removable storage device is attached and
+// detached.
+namespace system.storage {
+
+ enum StorageUnitType {
+ // The storage has fixed media, e.g. hard disk or SSD.
+ fixed,
+ // The storage is removable, e.g. USB flash drive.
+ removable,
+ // The storage type is unknown.
+ unknown
+ };
+
+ dictionary StorageUnitInfo {
+ // The transient ID that uniquely identifies the storage device.
+ // This ID will be persistent within the same run of a single application.
+ // It will not be a persistent identifier between different runs of an
+ // application, or between different applications.
+ DOMString id;
+ // The name of the storage unit.
+ DOMString name;
+ // The media type of the storage unit.
+ StorageUnitType type;
+ // The total amount of the storage space, in bytes.
+ double capacity;
+ };
+
+ dictionary StorageAvailableCapacityInfo {
+ // A copied |id| of getAvailableCapacity function parameter |id|.
+ DOMString id;
+ // The available capacity of the storage device, in bytes.
+ double availableCapacity;
+ };
+
+ [inline_doc] enum EjectDeviceResultCode {
+ // The ejection command is successful -- the application can prompt the user
+ // to remove the device.
+ success,
+ // The device is in use by another application. The ejection did not
+ // succeed; the user should not remove the device until the other
+ // application is done with the device.
+ in_use,
+ // There is no such device known.
+ no_such_device,
+ // The ejection command failed.
+ failure
+ };
+
+ callback EjectDeviceCallback = void (EjectDeviceResultCode result);
+
+ callback StorageInfoCallback = void (StorageUnitInfo[] info);
+
+ callback GetAvailableCapacityCallback = void (
+ StorageAvailableCapacityInfo info);
+
+ interface Functions {
+ // Get the storage information from the system. The argument passed to the
+ // callback is an array of StorageUnitInfo objects.
+ static void getInfo(StorageInfoCallback callback);
+
+ // Ejects a removable storage device.
+ static void ejectDevice(DOMString id, EjectDeviceCallback callback);
+
+ // Get the available capacity of a specified |id| storage device.
+ // The |id| is the transient device ID from StorageUnitInfo.
+ static void getAvailableCapacity(DOMString id,
+ GetAvailableCapacityCallback callback);
+ };
+
+ interface Events {
+ // Fired when a new removable storage is attached to the system.
+ static void onAttached(StorageUnitInfo info);
+
+ // Fired when a removable storage is detached from the system.
+ static void onDetached(DOMString id);
+ };
+
+};
diff --git a/chromium/extensions/common/api/test.json b/chromium/extensions/common/api/test.json
new file mode 100644
index 00000000000..42a7a26b6fc
--- /dev/null
+++ b/chromium/extensions/common/api/test.json
@@ -0,0 +1,437 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "test",
+ "description": "none",
+ "functions": [
+ {
+ "name": "getConfig",
+ "type": "function",
+ "description": "Gives configuration options set by the test.",
+ "parameters": [
+ {
+ "type": "function", "name": "callback", "parameters": [
+ {
+ "type": "object",
+ "name": "testConfig",
+ "properties": {
+ "customArg": {
+ "type": "string",
+ "optional": true,
+ "description": "Additional string argument to pass to test."
+ },
+ "ftpServer": {
+ "type": "object",
+ "optional": true,
+ "description": "Details on the FTP server used to mock network responses. Will be set only if test calls ExtensionApiTest::StartFTPServer().",
+ "properties": {
+ "port": {
+ "type": "integer",
+ "description": "The port on which the FTP server is listening.",
+ "minimum": 1024,
+ "maximum": 65535
+ }
+ }
+ },
+ "testServer": {
+ "type": "object",
+ "optional": true,
+ "description": "Details on the test server used to mock network responses. Will be set only if test calls ExtensionApiTest::StartEmbeddedTestServer().",
+ "properties": {
+ "port": {
+ "type": "integer",
+ "description": "The port on which the test server is listening.",
+ "minimum": 1024,
+ "maximum": 65535
+ }
+ }
+ },
+ "testDataDirectory": {
+ "type": "string",
+ "description": "file:/// URL for the API test data directory."
+ },
+ "testWebSocketPort": {
+ "type": "integer",
+ "description": "The port on which the test WebSocket server is listening.",
+ "minimum": 0,
+ "maximum": 65535
+ },
+ "isolateExtensions": {
+ "type": "boolean",
+ "description": "Whether or not extensions are running in site isolation mode."
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "notifyFail",
+ "type": "function",
+ "description": "Notifies the browser process that test code running in the extension failed. This is only used for internal unit testing.",
+ "parameters": [
+ {"type": "string", "name": "message"}
+ ]
+ },
+ {
+ "name": "notifyPass",
+ "type": "function",
+ "description": "Notifies the browser process that test code running in the extension passed. This is only used for internal unit testing.",
+ "parameters": [
+ {"type": "string", "name": "message", "optional": true}
+ ]
+ },
+ {
+ "name": "log",
+ "type": "function",
+ "description": "Logs a message during internal unit testing.",
+ "parameters": [
+ {"type": "string", "name": "message"}
+ ]
+ },
+ {
+ "name": "sendMessage",
+ "type": "function",
+ "description": "Sends a string message to the browser process, generating a Notification that C++ test code can wait for.",
+ "parameters": [
+ {"type": "string", "name": "message"},
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {"type": "string", "name": "response"}
+ ]
+ }
+ ]
+ },
+ {
+ "name": "callbackAdded",
+ "type": "function",
+ "nocompile": true,
+ "parameters": []
+ },
+ {
+ "name": "runNextTest",
+ "type": "function",
+ "nocompile": true,
+ "parameters": []
+ },
+ {
+ "name": "fail",
+ "type": "function",
+ "nocompile": true,
+ "parameters": [
+ {"type": "any", "name": "message", "optional": true}
+ ]
+ },
+ {
+ "name": "succeed",
+ "type": "function",
+ "nocompile": true,
+ "parameters": [
+ {"type": "any", "name": "message", "optional": true}
+ ]
+ },
+ {
+ "name": "runWithNativesEnabled",
+ "type": "function",
+ "nocompile": true,
+ "description": "Runs the given function with access to native methods enabled.",
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback"
+ }
+ ]
+ },
+ {
+ "name": "getModuleSystem",
+ "type": "function",
+ "nocompile": true,
+ "description": "Returns an instance of the module system for the given context.",
+ "parameters": [
+ {
+ "type": "any",
+ "name": "context"
+ }
+ ],
+ "returns": {
+ "type": "any",
+ "description": "The module system",
+ "optional": true
+ }
+ },
+ {
+ "name": "assertTrue",
+ "type": "function",
+ "nocompile": true,
+ "parameters": [
+ {
+ "name": "test",
+ "choices": [
+ {"type": "string"},
+ {"type": "boolean"}
+ ]
+ },
+ {"type": "string", "name": "message", "optional": true}
+ ]
+ },
+ {
+ "name": "assertFalse",
+ "type": "function",
+ "nocompile": true,
+ "parameters": [
+ {
+ "name": "test",
+ "choices": [
+ {"type": "string"},
+ {"type": "boolean"}
+ ]
+ },
+ {"type": "string", "name": "message", "optional": true}
+ ]
+ },
+ {
+ "name": "assertBool",
+ "type": "function",
+ "nocompile": true,
+ "parameters": [
+ {
+ "name": "test",
+ "choices": [
+ {"type": "string"},
+ {"type": "boolean"}
+ ]
+ },
+ {"type": "boolean", "name": "expected"},
+ {"type": "string", "name": "message", "optional": true}
+ ]
+ },
+ {
+ "name": "checkDeepEq",
+ "type": "function",
+ "nocompile": true,
+ "allowAmbiguousOptionalArguments": true,
+ "parameters": [
+ // These need to be optional because they can be null.
+ {"type": "any", "name": "expected", "optional": true},
+ {"type": "any", "name": "actual", "optional": true}
+ ]
+ },
+ {
+ "name": "assertEq",
+ "type": "function",
+ "nocompile": true,
+ "allowAmbiguousOptionalArguments": true,
+ "parameters": [
+ // These need to be optional because they can be null.
+ {"type": "any", "name": "expected", "optional": true},
+ {"type": "any", "name": "actual", "optional": true},
+ {"type": "string", "name": "message", "optional": true}
+ ]
+ },
+ {
+ "name": "assertNoLastError",
+ "type": "function",
+ "nocompile": true,
+ "parameters": []
+ },
+ {
+ "name": "assertLastError",
+ "type": "function",
+ "nocompile": true,
+ "parameters": [
+ {"type": "string", "name": "expectedError"}
+ ]
+ },
+ {
+ "name": "assertThrows",
+ "type": "function",
+ "nocompile": true,
+ "parameters": [
+ {"type": "function", "name": "fn"},
+ {
+ "type": "object",
+ "name": "self",
+ "additionalProperties": {"type": "any"},
+ "optional": true
+ },
+ {"type": "array", "items": {"type": "any"}, "name": "args"},
+ {"choices": [ {"type": "string"}, {"type": "object", "isInstanceOf": "RegExp"} ], "name": "message", "optional": true}
+ ]
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "nocompile": true,
+ "parameters": [
+ {"type": "function", "name": "func", "optional": true},
+ {"type": "string", "name": "expectedError", "optional": true}
+ ]
+ },
+ {
+ "name": "listenOnce",
+ "type": "function",
+ "nocompile": true,
+ "parameters": [
+ // TODO(cduvall): Make this a $ref to events.Event.
+ {"type": "any", "name": "event"},
+ {"type": "function", "name": "func"}
+ ]
+ },
+ {
+ "name": "listenForever",
+ "type": "function",
+ "nocompile": true,
+ "parameters": [
+ // TODO(cduvall): Make this a $ref to events.Event.
+ {"type": "any", "name": "event"},
+ {"type": "function", "name": "func"}
+ ]
+ },
+ {
+ "name": "callbackPass",
+ "type": "function",
+ "nocompile": true,
+ "parameters": [
+ {"type": "function", "name": "func", "optional": true}
+ ]
+ },
+ {
+ "name": "callbackFail",
+ "type": "function",
+ "nocompile": true,
+ "parameters": [
+ {"type": "string", "name": "expectedError"},
+ {"type": "function", "name": "func", "optional": true}
+ ]
+ },
+ {
+ "name": "runTests",
+ "type": "function",
+ "nocompile": true,
+ "parameters": [
+ {
+ "type": "array",
+ "name": "tests",
+ "items": {"type": "function"}
+ }
+ ]
+ },
+ {
+ "name": "getApiFeatures",
+ "type": "function",
+ "nocompile": true,
+ "parameters": []
+ },
+ {
+ "name": "getApiDefinitions",
+ "type": "function",
+ "nocompile": true,
+ "parameters": [
+ {
+ "type": "array",
+ "name": "apiNames",
+ "optional": true,
+ "items": {"type": "string"}
+ }
+ ]
+ },
+ {
+ "name": "isProcessingUserGesture",
+ "type": "function",
+ "nocompile": true,
+ "parameters": []
+ },
+ {
+ "name": "runWithUserGesture",
+ "type": "function",
+ "description": "Runs the callback in the context of a user gesture.",
+ "nocompile": true,
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "runWithoutUserGesture",
+ "type": "function",
+ "nocompile": true,
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "waitForRoundTrip",
+ "type": "function",
+ "description": "Sends a string message one round trip from the renderer to the browser process and back.",
+ "parameters": [
+ {"type": "string", "name": "message"},
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {"type": "string", "name": "message"}
+ ]
+ }
+ ]
+ },
+ {
+ "name": "setExceptionHandler",
+ "type": "function",
+ "description": "Sets the function to be called when an exception occurs. By default this is a function which fails the test. This is reset for every test run through $ref:test.runTests.",
+ "nocompile": true,
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {"type": "string", "name": "message"},
+ {"type": "any", "name": "exception"}
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getWakeEventPage",
+ "type": "function",
+ "description": "Returns the wake-event-page API function, which can be called to wake up the extension's event page.",
+ "nocompile": true,
+ "parameters": [],
+ "returns": {
+ "type": "function",
+ "description": "The API function which wakes the extension's event page"
+ }
+ }
+ ],
+ "events": [
+ {
+ "name": "onMessage",
+ "type": "function",
+ "description": "Used to test sending messages to extensions.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "info",
+ "properties": {
+ "data": { "type": "string", "description": "Additional information." },
+ "lastMessage": { "type": "boolean", "description": "True if this was the last message for this test" }
+ }
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/chromium/extensions/common/api/usb.idl b/chromium/extensions/common/api/usb.idl
new file mode 100644
index 00000000000..f0d42eb8de5
--- /dev/null
+++ b/chromium/extensions/common/api/usb.idl
@@ -0,0 +1,403 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.usb</code> API to interact with connected USB
+// devices. This API provides access to USB operations from within the context
+// of an app. Using this API, apps can function as drivers for hardware devices.
+//
+// Errors generated by this API are reported by setting
+// $(ref:runtime.lastError) and executing the function's regular callback. The
+// callback's regular parameters will be undefined in this case.
+namespace usb {
+
+ // Direction, Recipient, RequestType, and TransferType all map to their
+ // namesakes within the USB specification.
+ enum Direction {in, out};
+ enum Recipient {device, _interface, endpoint, other};
+ enum RequestType {standard, class, vendor, reserved};
+ enum TransferType {control, interrupt, isochronous, bulk};
+
+ // For isochronous mode, SynchronizationType and UsageType map to their
+ // namesakes within the USB specification.
+ enum SynchronizationType {asynchronous, adaptive, synchronous};
+ enum UsageType {data, feedback, explicitFeedback};
+
+ dictionary Device {
+ // An opaque ID for the USB device. It remains unchanged until the device is
+ // unplugged.
+ long device;
+ // The device vendor ID.
+ long vendorId;
+ // The product ID.
+ long productId;
+ // The device version (bcdDevice field).
+ long version;
+ // The iProduct string read from the device, if available.
+ DOMString productName;
+ // The iManufacturer string read from the device, if available.
+ DOMString manufacturerName;
+ // The iSerialNumber string read from the device, if available.
+ DOMString serialNumber;
+ };
+
+ dictionary ConnectionHandle {
+ // An opaque handle representing this connection to the USB device and all
+ // associated claimed interfaces and pending transfers. A new handle is
+ // created each time the device is opened. The connection handle is
+ // different from $(ref:Device.device).
+ long handle;
+ // The device vendor ID.
+ long vendorId;
+ // The product ID.
+ long productId;
+ };
+
+ [noinline_doc] dictionary EndpointDescriptor {
+ // Endpoint address.
+ long address;
+ // Transfer type.
+ TransferType type;
+ // Transfer direction.
+ Direction direction;
+ // Maximum packet size.
+ long maximumPacketSize;
+ // Transfer synchronization mode (isochronous only).
+ SynchronizationType? synchronization;
+ // Endpoint usage hint.
+ UsageType? usage;
+ // Polling interval (interrupt and isochronous only).
+ long? pollingInterval;
+ // Extra descriptor data associated with this endpoint.
+ ArrayBuffer extra_data;
+ };
+
+ [noinline_doc] dictionary InterfaceDescriptor {
+ // The interface number.
+ long interfaceNumber;
+ // The interface alternate setting number (defaults to <code>0</code).
+ long alternateSetting;
+ // The USB interface class.
+ long interfaceClass;
+ // The USB interface sub-class.
+ long interfaceSubclass;
+ // The USB interface protocol.
+ long interfaceProtocol;
+ // Description of the interface.
+ DOMString? description;
+ // Available endpoints.
+ EndpointDescriptor[] endpoints;
+ // Extra descriptor data associated with this interface.
+ ArrayBuffer extra_data;
+ };
+
+ [noinline_doc] dictionary ConfigDescriptor {
+ // Is this the active configuration?
+ boolean active;
+ // The configuration number.
+ long configurationValue;
+ // Description of the configuration.
+ DOMString? description;
+ // The device is self-powered.
+ boolean selfPowered;
+ // The device supports remote wakeup.
+ boolean remoteWakeup;
+ // The maximum power needed by this device in milliamps (mA).
+ long maxPower;
+ // Available interfaces.
+ InterfaceDescriptor[] interfaces;
+ // Extra descriptor data associated with this configuration.
+ ArrayBuffer extra_data;
+ };
+
+ dictionary ControlTransferInfo {
+ // The transfer direction (<code>"in"</code> or <code>"out"</code>).
+ Direction direction;
+
+ // The transfer target. The target given by <code>index</code> must be
+ // claimed if <code>"interface"</code> or <code>"endpoint"</code>.
+ Recipient recipient;
+
+ // The request type.
+ RequestType requestType;
+
+ // The <code>bRequest</code> field, see <i>Universal Serial Bus
+ // Specification Revision 1.1</i> &sect; 9.3.
+ long request;
+ // The <code>wValue</code> field, see <i>Ibid</i>.
+ long value;
+ // The <code>wIndex</code> field, see <i>Ibid</i>.
+ long index;
+
+ // The maximum number of bytes to receive (required only by input
+ // transfers).
+ long? length;
+
+ // The data to transmit (required only by output transfers).
+ ArrayBuffer? data;
+
+ // Request timeout (in milliseconds). The default value <code>0</code>
+ // indicates no timeout.
+ long? timeout;
+ };
+
+ dictionary GenericTransferInfo {
+ // The transfer direction (<code>"in"</code> or <code>"out"</code>).
+ Direction direction;
+
+ // The target endpoint address. The interface containing this endpoint must
+ // be claimed.
+ long endpoint;
+
+ // The maximum number of bytes to receive (required only by input
+ // transfers).
+ long? length;
+
+ // The data to transmit (required only by output transfers).
+ ArrayBuffer? data;
+
+ // Request timeout (in milliseconds). The default value <code>0</code>
+ // indicates no timeout.
+ long? timeout;
+ };
+
+ dictionary IsochronousTransferInfo {
+ // Transfer parameters. The transfer length or data buffer specified in this
+ // parameter block is split along <code>packetLength</code> boundaries to
+ // form the individual packets of the transfer.
+ GenericTransferInfo transferInfo;
+
+ // The total number of packets in this transfer.
+ long packets;
+
+ // The length of each of the packets in this transfer.
+ long packetLength;
+ };
+
+ dictionary TransferResultInfo {
+ // A value of <code>0</code> indicates that the transfer was a success.
+ // Other values indicate failure.
+ long? resultCode;
+
+ // The data returned by an input transfer. <code>undefined</code> for output
+ // transfers.
+ ArrayBuffer? data;
+ };
+
+ [noinline_doc] dictionary DeviceFilter {
+ // Device vendor ID.
+ long? vendorId;
+ // Device product ID, checked only if the vendor ID matches.
+ long? productId;
+ // USB interface class, matches any interface on the device.
+ long? interfaceClass;
+ // USB interface sub-class, checked only if the interface class matches.
+ long? interfaceSubclass;
+ // USB interface protocol, checked only if the interface sub-class matches.
+ long? interfaceProtocol;
+ };
+
+ dictionary EnumerateDevicesOptions {
+ [deprecated="Equivalent to setting $(ref:DeviceFilter.vendorId)."]
+ long? vendorId;
+ [deprecated="Equivalent to setting $(ref:DeviceFilter.productId)."]
+ long? productId;
+ // A device matching any given filter will be returned. An empty filter list
+ // will return all devices the app has permission for.
+ DeviceFilter[]? filters;
+ };
+
+ dictionary EnumerateDevicesAndRequestAccessOptions {
+ // The device vendor ID.
+ long vendorId;
+ // The product ID.
+ long productId;
+ // The interface ID to request access to.
+ // Only available on Chrome OS. It has no effect on other platforms.
+ long? interfaceId;
+ };
+
+ dictionary DevicePromptOptions {
+ // Allow the user to select multiple devices.
+ boolean? multiple;
+ // Filter the list of devices presented to the user. If multiple filters are
+ // provided devices matching any filter will be displayed.
+ DeviceFilter[]? filters;
+ };
+
+ callback VoidCallback = void ();
+ callback GetDevicesCallback = void (Device[] devices);
+ callback GetConfigurationsCallback = void (ConfigDescriptor[] configs);
+ callback RequestAccessCallback = void (boolean success);
+ callback OpenDeviceCallback = void (ConnectionHandle handle);
+ callback FindDevicesCallback = void (ConnectionHandle[] handles);
+ callback GetConfigurationCallback = void (ConfigDescriptor config);
+ callback ListInterfacesCallback = void (InterfaceDescriptor[] descriptors);
+ callback CloseDeviceCallback = void ();
+ callback TransferCallback = void (TransferResultInfo info);
+ callback ResetDeviceCallback = void(boolean success);
+
+ interface Functions {
+ // Enumerates connected USB devices.
+ // |options|: The properties to search for on target devices.
+ static void getDevices(EnumerateDevicesOptions options,
+ GetDevicesCallback callback);
+
+ // Presents a device picker to the user and returns the $(ref:Device)s
+ // selected.
+ // If the user cancels the picker devices will be empty. A user gesture
+ // is required for the dialog to display. Without a user gesture, the
+ // callback will run as though the user cancelled.
+ // |options|: Configuration of the device picker dialog box.
+ // |callback|: Invoked with a list of chosen $(ref:Device)s.
+ static void getUserSelectedDevices(DevicePromptOptions options,
+ GetDevicesCallback callback);
+
+ // Returns the full set of device configuration descriptors.
+ // |device|: The $(ref:Device) to fetch descriptors from.
+ static void getConfigurations(Device device,
+ GetConfigurationsCallback callback);
+
+ // Requests access from the permission broker to a device claimed by
+ // Chrome OS if the given interface on the device is not claimed.
+ //
+ // |device|: The $(ref:Device) to request access to.
+ // |interfaceId|: The particular interface requested.
+ [deprecated="This function was Chrome OS specific and calling it on other
+ platforms would fail. This operation is now implicitly performed as part of
+ $(ref:openDevice) and this function will return <code>true</code> on all
+ platforms."]
+ static void requestAccess(Device device,
+ long interfaceId,
+ RequestAccessCallback callback);
+
+ // Opens a USB device returned by $(ref:getDevices).
+ // |device|: The $(ref:Device) to open.
+ static void openDevice(Device device, OpenDeviceCallback callback);
+
+ // Finds USB devices specified by the vendor, product and (optionally)
+ // interface IDs and if permissions allow opens them for use.
+ //
+ // If the access request is rejected or the device fails to be opened a
+ // connection handle will not be created or returned.
+ //
+ // Calling this method is equivalent to calling $(ref:getDevices) followed
+ // by $(ref:openDevice) for each device.
+ //
+ // |options|: The properties to search for on target devices.
+ static void findDevices(EnumerateDevicesAndRequestAccessOptions options,
+ FindDevicesCallback callback);
+
+ // Closes a connection handle. Invoking operations on a handle after it
+ // has been closed is a safe operation but causes no action to be taken.
+ // |handle|: The $(ref:ConnectionHandle) to close.
+ static void closeDevice(ConnectionHandle handle,
+ optional CloseDeviceCallback callback);
+
+ // Select a device configuration.
+ //
+ // This function effectively resets the device by selecting one of the
+ // device's available configurations. Only configuration values greater
+ // than <code>0</code> are valid however some buggy devices have a working
+ // configuration <code>0</code> and so this value is allowed.
+ // |handle|: An open connection to the device.
+ static void setConfiguration(ConnectionHandle handle,
+ long configurationValue,
+ VoidCallback callback);
+
+ // Gets the configuration descriptor for the currently selected
+ // configuration.
+ // |handle|: An open connection to the device.
+ static void getConfiguration(ConnectionHandle handle,
+ GetConfigurationCallback callback);
+
+ // Lists all interfaces on a USB device.
+ // |handle|: An open connection to the device.
+ static void listInterfaces(ConnectionHandle handle,
+ ListInterfacesCallback callback);
+
+ // Claims an interface on a USB device.
+ // Before data can be transfered to an interface or associated endpoints the
+ // interface must be claimed. Only one connection handle can claim an
+ // interface at any given time. If the interface is already claimed, this
+ // call will fail.
+ //
+ // $(ref:releaseInterface) should be called when the interface is no longer
+ // needed.
+ //
+ // |handle|: An open connection to the device.
+ // |interfaceNumber|: The interface to be claimed.
+ static void claimInterface(ConnectionHandle handle, long interfaceNumber,
+ VoidCallback callback);
+
+ // Releases a claimed interface.
+ // |handle|: An open connection to the device.
+ // |interfaceNumber|: The interface to be released.
+ static void releaseInterface(ConnectionHandle handle, long interfaceNumber,
+ VoidCallback callback);
+
+ // Selects an alternate setting on a previously claimed interface.
+ // |handle|: An open connection to the device where this interface has been
+ // claimed.
+ // |interfaceNumber|: The interface to configure.
+ // |alternateSetting|: The alternate setting to configure.
+ static void setInterfaceAlternateSetting(ConnectionHandle handle,
+ long interfaceNumber,
+ long alternateSetting,
+ VoidCallback callback);
+
+ // Performs a control transfer on the specified device.
+ //
+ // Control transfers refer to either the device, an interface or an
+ // endpoint. Transfers to an interface or endpoint require the interface to
+ // be claimed.
+ //
+ // |handle|: An open connection to the device.
+ static void controlTransfer(ConnectionHandle handle,
+ ControlTransferInfo transferInfo,
+ TransferCallback callback);
+
+ // Performs a bulk transfer on the specified device.
+ // |handle|: An open connection to the device.
+ // |transferInfo|: The transfer parameters.
+ static void bulkTransfer(ConnectionHandle handle,
+ GenericTransferInfo transferInfo,
+ TransferCallback callback);
+
+ // Performs an interrupt transfer on the specified device.
+ // |handle|: An open connection to the device.
+ // |transferInfo|: The transfer parameters.
+ static void interruptTransfer(ConnectionHandle handle,
+ GenericTransferInfo transferInfo,
+ TransferCallback callback);
+
+ // Performs an isochronous transfer on the specific device.
+ // |handle|: An open connection to the device.
+ static void isochronousTransfer(ConnectionHandle handle,
+ IsochronousTransferInfo transferInfo,
+ TransferCallback callback);
+
+ // Tries to reset the USB device.
+ // If the reset fails, the given connection handle will be closed and the
+ // USB device will appear to be disconnected then reconnected.
+ // In this case $(ref:getDevices) or $(ref:findDevices) must be called again
+ // to acquire the device.
+ //
+ // |handle|: A connection handle to reset.
+ static void resetDevice(ConnectionHandle handle,
+ ResetDeviceCallback callback);
+ };
+
+ interface Events {
+ // Event generated when a device is added to the system. Events are only
+ // broadcast to apps and extensions that have permission to access the
+ // device. Permission may have been granted at install time, when the user
+ // accepted an optional permission (see $(ref:permissions.request)), or
+ // through $(ref:getUserSelectedDevices).
+ static void onDeviceAdded(Device device);
+
+ // Event generated when a device is removed from the system. See
+ // $(ref:onDeviceAdded) for which events are delivered.
+ static void onDeviceRemoved(Device device);
+ };
+};
diff --git a/chromium/extensions/common/api/virtual_keyboard_private.json b/chromium/extensions/common/api/virtual_keyboard_private.json
new file mode 100644
index 00000000000..5b335fb2a55
--- /dev/null
+++ b/chromium/extensions/common/api/virtual_keyboard_private.json
@@ -0,0 +1,244 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "virtualKeyboardPrivate",
+ "compiler_options": {
+ "implemented_in": "extensions/browser/api/virtual_keyboard_private/virtual_keyboard_private_api.h"
+ },
+ "platforms": ["chromeos"],
+ "description": "none",
+ "types": [
+ {
+ "id": "VirtualKeyboardEventType",
+ "type": "string",
+ "description": "One of keyup or keydown.",
+ "enum": ["keyup", "keydown"]
+ },
+ {
+ "id": "VirtualKeyboardEvent",
+ "type": "object",
+ "properties": {
+ "type": {"$ref": "VirtualKeyboardEventType"},
+ "charValue": {"type": "integer", "description": "Unicode value of the key."},
+ "keyCode": {"type": "integer", "description": "Virtual key code, which is independent of the keyboard layout or modifier state."},
+ "keyName": {"type": "string", "description": "Name of the key, which is independent of modifier state."},
+ "modifiers": {"type": "integer", "optional": true, "description": "Flag for modifiers that are active. None = 0, Shift = 2, Control = 4, Alt = 8."}
+ }
+ },
+ {
+ "id": "KeyboardMode",
+ "type": "string",
+ "enum": [ "FULL_WIDTH", "FLOATING" ],
+ "description": "The value of the virtual keyboard mode to set to."
+ },
+ {
+ "id": "KeyboardState",
+ "type": "string",
+ "enum": [ "ENABLED", "DISABLED", "AUTO"],
+ "description": "The value of the virtual keyboard state to change to."
+ },
+ {
+ "id": "OnTextInputBoxFocusedType",
+ "type": "string",
+ "description": "The value of type attribute of the focused text input box.",
+ "enum": ["text", "number", "password", "date", "url", "tel", "email"]
+ },
+ {
+ "id": "Bounds",
+ "type": "object",
+ "properties": {
+ "left": {"type": "integer", "description": "The position of the virtual keyboard window's left edge."},
+ "top": {"type": "integer", "description": "The position of the virtual keyboard window's top edge."},
+ "width": {"type": "integer", "description": "The width of the virtual keyboard window."},
+ "height": {"type": "integer", "description": "The height of the virtual keyboard window."}
+ }
+ }
+ ],
+ "functions": [
+ {
+ "name": "insertText",
+ "type": "function",
+ "description": "Inserts text into the currently focused text field.",
+ "parameters": [
+ { "name": "text",
+ "type": "string",
+ "description": "The text that will be inserted."
+ },
+ { "type": "function",
+ "name": "callback",
+ "optional": true,
+ "description": "Called when the insertion is completed.",
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "sendKeyEvent",
+ "type": "function",
+ "description": "Sends a fabricated key event to the focused input field.",
+ "parameters": [
+ { "name": "keyEvent",
+ "$ref": "VirtualKeyboardEvent",
+ "description": ""
+ },
+ { "name": "callback",
+ "type": "function",
+ "optional": true,
+ "description": "Called after processing the event.",
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "hideKeyboard",
+ "type": "function",
+ "description": "Hides the virtual keyboard.",
+ "parameters": [
+ { "type": "function",
+ "name": "callback",
+ "optional": true,
+ "description": "Called when the keyboard is hidden.",
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "setHotrodKeyboard",
+ "type": "function",
+ "description": "Sets the state of the hotrod virtual keyboard. This API should only be used by hotrod.",
+ "parameters": [
+ {
+ "type": "boolean",
+ "name": "enable"
+ }
+ ]
+ },
+ {
+ "name": "lockKeyboard",
+ "type": "function",
+ "description": "Sets the lock state of the virtual keyboard. A locked keyboard remains visible even after a text area loses input focus.",
+ "parameters": [
+ {
+ "type": "boolean",
+ "name": "lock"
+ }
+ ]
+ },
+ {
+ "name": "keyboardLoaded",
+ "type": "function",
+ "description": "Inform the system that the keyboard has loaded.",
+ "parameters": [
+ { "type": "function",
+ "name": "callback",
+ "optional": true,
+ "description": "Called when load acknowledgement is complete.",
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "getKeyboardConfig",
+ "type": "function",
+ "description": "Gets the virtual keyboard configuration.",
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "description": "Called when querying virtual keyboard configuration is complete.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "config",
+ "properties": {
+ "layout": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Virtual keyboard layout string."
+ },
+ "a11ymode": {
+ "type": "boolean",
+ "description": "True if accessibility virtual keyboard is enabled."
+ },
+ "features": {
+ "type": "array",
+ "items": { "type": "string" },
+ "description": "List of experimental feature flags."
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "openSettings",
+ "type": "function",
+ "description": "Opens chrome://settings/languages page.",
+ "parameters": [
+ ]
+ },
+ {
+ "name": "setMode",
+ "type": "function",
+ "description": "Sets the virtual keyboard mode.",
+ "parameters": [
+ {
+ "$ref": "KeyboardMode",
+ "name": "mode",
+ "description": "The value of the virtual keyboard mode to set to."
+ }
+ ]
+ },
+ {
+ "name": "setKeyboardState",
+ "type": "function",
+ "description": "Requests the virtual keyboard to change state.",
+ "parameters": [
+ {
+ "$ref": "KeyboardState",
+ "name": "state",
+ "description": "The value of the virtual keyboard state to change to."
+ }
+ ]
+ }
+
+ ],
+ "events": [
+ {
+ "name": "onTextInputBoxFocused",
+ "type": "function",
+ "description": "This event is sent when focus enters a text input box.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "context",
+ "description": "Describes the text input box that has acquired focus. Note only the type of text input box is passed. This API is intended to be used by non-ime virtual keyboard only. Normal ime virtual keyboard should use chrome.input.ime.onFocus to get the more detailed InputContext.",
+ "properties": {
+ "type": {
+ "$ref": "OnTextInputBoxFocusedType",
+ "description": "The value of type attribute of the focused text input box."
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "onBoundsChanged",
+ "type": "function",
+ "description": "This event is sent when virtual keyboard bounds changed and overscroll/resize is enabled.",
+ "parameters": [
+ {
+ "name": "bounds",
+ "description": "The virtual keyboard bounds",
+ "$ref": "Bounds"
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/chromium/extensions/common/api/vpn_provider.idl b/chromium/extensions/common/api/vpn_provider.idl
new file mode 100644
index 00000000000..9399f48e1ba
--- /dev/null
+++ b/chromium/extensions/common/api/vpn_provider.idl
@@ -0,0 +1,193 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Use the <code>chrome.vpnProvider</code> API to implement a VPN
+// client.
+namespace vpnProvider {
+ // A parameters class for the VPN interface.
+ dictionary Parameters {
+ // IP address for the VPN interface in CIDR notation.
+ // IPv4 is currently the only supported mode.
+ DOMString address;
+ // Broadcast address for the VPN interface. (default: deduced
+ // from IP address and mask)
+ DOMString? broadcastAddress;
+ // MTU setting for the VPN interface. (default: 1500 bytes)
+ DOMString? mtu;
+ // Exclude network traffic to the list of IP blocks in CIDR notation from
+ // the tunnel. This can be used to bypass traffic to and from the VPN
+ // server.
+ // When many rules match a destination, the rule with the longest matching
+ // prefix wins.
+ // Entries that correspond to the same CIDR block are treated as duplicates.
+ // Such duplicates in the collated (exclusionList + inclusionList) list are
+ // eliminated and the exact duplicate entry that will be eliminated is
+ // undefined.
+ DOMString[] exclusionList;
+ // Include network traffic to the list of IP blocks in CIDR notation to the
+ // tunnel. This parameter can be used to set up a split tunnel. By default
+ // no traffic is directed to the tunnel. Adding the entry "0.0.0.0/0" to
+ // this list gets all the user traffic redirected to the tunnel.
+ // When many rules match a destination, the rule with the longest matching
+ // prefix wins.
+ // Entries that correspond to the same CIDR block are treated as duplicates.
+ // Such duplicates in the collated (exclusionList + inclusionList) list are
+ // eliminated and the exact duplicate entry that will be eliminated is
+ // undefined.
+ DOMString[] inclusionList;
+ // A list of search domains. (default: no search domain)
+ DOMString[]? domainSearch;
+ // A list of IPs for the DNS servers.
+ DOMString[] dnsServers;
+ // Whether or not the VPN extension implements auto-reconnection.
+ //
+ // If true, the <code>linkDown</code>, <code>linkUp</code>,
+ // <code>linkChanged</code>, <code>suspend</code>, and <code>resume</code>
+ // platform messages will be used to signal the respective events.
+ // If false, the system will forcibly disconnect the VPN if the network
+ // topology changes, and the user will need to reconnect manually.
+ // (default: false)
+ //
+ // This property is new in Chrome 51; it will generate an exception in
+ // earlier versions. try/catch can be used to conditionally enable the
+ // feature based on browser support.
+ DOMString? reconnect;
+ };
+
+ // The enum is used by the platform to notify the client of the VPN session
+ // status.
+ enum PlatformMessage {
+ // VPN configuration connected.
+ connected,
+ // VPN configuration disconnected.
+ disconnected,
+ // An error occurred in VPN connection, for example a timeout. A description
+ // of the error is given as the <a href="#property-onPlatformMessage-error">
+ // error argument to onPlatformMessage</a>.
+ error,
+ // The default physical network connection is down.
+ linkDown,
+ // The default physical network connection is back up.
+ linkUp,
+ // The default physical network connection changed, e.g. wifi-&gt;mobile.
+ linkChanged,
+ // The OS is preparing to suspend, so the VPN should drop its connection.
+ // The extension is not guaranteed to receive this event prior to
+ // suspending.
+ suspend,
+ // The OS has resumed and the user has logged back in, so the VPN should
+ // try to reconnect.
+ resume
+ };
+
+ // The enum is used by the VPN client to inform the platform
+ // of its current state. This helps provide meaningful messages
+ // to the user.
+ enum VpnConnectionState {
+ // VPN connection was successful.
+ connected,
+ // VPN connection failed.
+ failure
+ };
+
+ // The enum is used by the platform to indicate the event that triggered
+ // <code>onUIEvent</code>.
+ enum UIEvent {
+ // Request the VPN client to show add configuration dialog to the user.
+ showAddDialog,
+ // Request the VPN client to show configuration settings dialog to the user.
+ showConfigureDialog
+ };
+
+ // The callback is used by <code>setParameters, sendPacket</code>
+ // to signal completion. The callback is called with
+ // <code>chrome.runtime.lastError</code> set to error code if
+ // there is an error.
+ [inline_doc] callback CallCompleteCallback = void ();
+
+ // The callback is used by <code>createConfig</code> to signal completion.
+ // The callback is called with <code>chrome.runtime.lastError</code> set to
+ // an error code if there is an error.
+ // |id|: A unique ID for the created configuration, or <code>undefined</code>
+ // on failure.
+ [inline_doc] callback CreateConfigCompleteCallback = void (DOMString id);
+
+ interface Functions {
+ // Creates a new VPN configuration that persists across multiple login
+ // sessions of the user.
+ // |name|: The name of the VPN configuration.
+ // |callback|: Called when the configuration is created or if there is an
+ // error.
+ static void createConfig(DOMString name,
+ CreateConfigCompleteCallback callback);
+
+ // Destroys a VPN configuration created by the extension.
+ // |id|: ID of the VPN configuration to destroy.
+ // |callback|: Called when the configuration is destroyed or if there is an
+ // error.
+ static void destroyConfig(DOMString id,
+ optional CallCompleteCallback callback);
+
+ // Sets the parameters for the VPN session. This should be called
+ // immediately after <code>"connected"</code> is received from the platform.
+ // This will succeed only when the VPN session is owned by the extension.
+ // |parameters|: The parameters for the VPN session.
+ // |callback|: Called when the parameters are set or if there is an error.
+ static void setParameters(Parameters parameters,
+ CallCompleteCallback callback);
+
+ // Sends an IP packet through the tunnel created for the VPN session.
+ // This will succeed only when the VPN session is owned by the extension.
+ // |data|: The IP packet to be sent to the platform.
+ // |callback|: Called when the packet is sent or if there is an error.
+ static void sendPacket(ArrayBuffer data,
+ optional CallCompleteCallback callback);
+
+ // Notifies the VPN session state to the platform.
+ // This will succeed only when the VPN session is owned by the extension.
+ // |state|: The VPN session state of the VPN client.
+ // |callback|: Called when the notification is complete or if there is an
+ // error.
+ static void notifyConnectionStateChanged(
+ VpnConnectionState state,
+ optional CallCompleteCallback callback);
+ };
+
+ interface Events {
+ // Triggered when a message is received from the platform for a
+ // VPN configuration owned by the extension.
+ // |id|: ID of the configuration the message is intended for.
+ // |message|: The message received from the platform. Note that new
+ // message types may be added in future Chrome versions to support new
+ // features.
+ // |error|: Error message when there is an error.
+ static void onPlatformMessage(DOMString id,
+ PlatformMessage message,
+ DOMString error);
+
+ // Triggered when an IP packet is received via the tunnel for the VPN
+ // session owned by the extension.
+ // |data|: The IP packet received from the platform.
+ static void onPacketReceived(ArrayBuffer data);
+
+ // Triggered when a configuration created by the extension is removed by the
+ // platform.
+ // |id|: ID of the removed configuration.
+ static void onConfigRemoved(DOMString id);
+
+ // Triggered when a configuration is created by the platform for the
+ // extension.
+ // |id|: ID of the configuration created.
+ // |name|: Name of the configuration created.
+ // |data|: Configuration data provided by the administrator.
+ static void onConfigCreated(DOMString id, DOMString name, object data);
+
+ // Triggered when there is a UI event for the extension. UI events are
+ // signals from the platform that indicate to the app that a UI dialog
+ // needs to be shown to the user.
+ // |event|: The UI event that is triggered.
+ // |id|: ID of the configuration for which the UI event was triggered.
+ static void onUIEvent(UIEvent event, optional DOMString id);
+ };
+};
diff --git a/chromium/extensions/common/api/web_request.json b/chromium/extensions/common/api/web_request.json
new file mode 100644
index 00000000000..4e314eb6c07
--- /dev/null
+++ b/chromium/extensions/common/api/web_request.json
@@ -0,0 +1,582 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "webRequest",
+ "description": "Use the <code>chrome.webRequest</code> API to observe and analyze traffic and to intercept, block, or modify requests in-flight.",
+ "properties": {
+ "MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES": {
+ "value": 20,
+ "description": "The maximum number of times that <code>handlerBehaviorChanged</code> can be called per 10 minute sustained interval. <code>handlerBehaviorChanged</code> is an expensive function call that shouldn't be called often."
+ }
+ },
+ "types": [
+ {
+ "id": "ResourceType",
+ "type": "string",
+ "enum": ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "object", "xmlhttprequest", "ping", "other"]
+ },
+ {
+ "id": "OnBeforeRequestOptions",
+ "type": "string",
+ "enum": ["blocking", "requestBody"]
+ },
+ {
+ "id": "OnBeforeSendHeadersOptions",
+ "type": "string",
+ "enum": ["requestHeaders", "blocking"]
+ },
+ {
+ "id": "OnSendHeadersOptions",
+ "type": "string",
+ "enum": ["requestHeaders"]
+ },
+ {
+ "id": "OnHeadersReceivedOptions",
+ "type": "string",
+ "enum": ["blocking", "responseHeaders"]
+ },
+ {
+ "id": "OnAuthRequiredOptions",
+ "type": "string",
+ "enum": ["responseHeaders", "blocking", "asyncBlocking"]
+ },
+ {
+ "id": "OnResponseStartedOptions",
+ "type": "string",
+ "enum": ["responseHeaders"]
+ },
+ {
+ "id": "OnBeforeRedirectOptions",
+ "type": "string",
+ "enum": ["responseHeaders"]
+ },
+ {
+ "id": "OnCompletedOptions",
+ "type": "string",
+ "enum": ["responseHeaders"]
+ },
+ {
+ "id": "RequestFilter",
+ "type": "object",
+ "description": "An object describing filters to apply to webRequest events.",
+ "properties": {
+ "urls": {
+ "type": "array",
+ "description": "A list of URLs or URL patterns. Requests that cannot match any of the URLs will be filtered out.",
+ "items": { "type": "string" }
+ },
+ "types": {
+ "type": "array",
+ "optional": true,
+ "description": "A list of request types. Requests that cannot match any of the types will be filtered out.",
+ "items": { "$ref": "ResourceType" }
+ },
+ "tabId": { "type": "integer", "optional": true },
+ "windowId": { "type": "integer", "optional": true }
+ }
+ },
+ {
+ "id": "HttpHeaders",
+ "nocompile": true,
+ "type": "array",
+ "description": "An array of HTTP headers. Each header is represented as a dictionary containing the keys <code>name</code> and either <code>value</code> or <code>binaryValue</code>.",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {"type": "string", "description": "Name of the HTTP header."},
+ "value": {"type": "string", "optional": true, "description": "Value of the HTTP header if it can be represented by UTF-8."},
+ "binaryValue": {
+ "type": "array",
+ "optional": true,
+ "description": "Value of the HTTP header if it cannot be represented by UTF-8, stored as individual byte values (0..255).",
+ "items": {"type": "integer"}
+ }
+ }
+ }
+ },
+ {
+ "id": "BlockingResponse",
+ "nocompile": true,
+ "type": "object",
+ "description": "Returns value for event handlers that have the 'blocking' extraInfoSpec applied. Allows the event handler to modify network requests.",
+ "properties": {
+ "cancel": {
+ "type": "boolean",
+ "optional": true,
+ "description": "If true, the request is cancelled. Used in onBeforeRequest, this prevents the request from being sent."
+ },
+ "redirectUrl": {
+ "type": "string",
+ "optional": true,
+ "description": "Only used as a response to the onBeforeRequest and onHeadersReceived events. If set, the original request is prevented from being sent/completed and is instead redirected to the given URL. Redirections to non-HTTP schemes such as data: are allowed. Redirects initiated by a redirect action use the original request method for the redirect, with one exception: If the redirect is initiated at the onHeadersReceived stage, then the redirect will be issued using the GET method."
+ },
+ "requestHeaders": {
+ "$ref": "HttpHeaders",
+ "optional": true,
+ "description": "Only used as a response to the onBeforeSendHeaders event. If set, the request is made with these request headers instead."
+ },
+ "responseHeaders": {
+ "$ref": "HttpHeaders",
+ "optional": true,
+ "description": "Only used as a response to the onHeadersReceived event. If set, the server is assumed to have responded with these response headers instead. Only return <code>responseHeaders</code> if you really want to modify the headers in order to limit the number of conflicts (only one extension may modify <code>responseHeaders</code> for each request)."
+ },
+ "authCredentials": {
+ "type": "object",
+ "description": "Only used as a response to the onAuthRequired event. If set, the request is made using the supplied credentials.",
+ "optional": true,
+ "properties": {
+ "username": {"type": "string"},
+ "password": {"type": "string"}
+ }
+ }
+ }
+ },
+ {
+ "id": "UploadData",
+ "type": "object",
+ "properties": {
+ "bytes": {
+ "type": "any",
+ "optional": true,
+ "description": "An ArrayBuffer with a copy of the data."
+ },
+ "file": {
+ "type": "string",
+ "optional": true,
+ "description": "A string with the file's path and name."
+ }
+ },
+ "description": "Contains data uploaded in a URL request."
+ }
+ ],
+ "functions": [
+ {
+ "name": "handlerBehaviorChanged",
+ "type": "function",
+ "description": "Needs to be called when the behavior of the webRequest handlers has changed to prevent incorrect handling due to caching. This function call is expensive. Don't call it often.",
+ "parameters": [
+ {"type": "function", "name": "callback", "optional": true, "parameters": []}
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "onBeforeRequest",
+ "type": "function",
+ "description": "Fired when a request is about to occur.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "details",
+ "properties": {
+ "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
+ "url": {"type": "string"},
+ "method": {"type": "string", "description": "Standard HTTP method."},
+ "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
+ "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+ "requestBody": {
+ "type": "object",
+ "optional": true,
+ "description": "Contains the HTTP request body data. Only provided if extraInfoSpec contains 'requestBody'.",
+ "properties": {
+ "error": {"type": "string", "optional": true, "description": "Errors when obtaining request body data."},
+ "formData": {
+ "type": "object",
+ "optional": true,
+ "description": "If the request method is POST and the body is a sequence of key-value pairs encoded in UTF8, encoded as either multipart/form-data, or application/x-www-form-urlencoded, this dictionary is present and for each key contains the list of all values for that key. If the data is of another media type, or if it is malformed, the dictionary is not present. An example value of this dictionary is {'key': ['value1', 'value2']}.",
+ "properties": {},
+ "additionalProperties": {
+ "type": "array",
+ "items": { "type": "string" }
+ }
+ },
+ "raw" : {
+ "type": "array",
+ "optional": true,
+ "items": {"$ref": "UploadData"},
+ "description": "If the request method is PUT or POST, and the body is not already parsed in formData, then the unparsed request body elements are contained in this array."
+ }
+ }
+ },
+ "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
+ "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
+ "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}
+ }
+ }
+ ],
+ "extraParameters": [
+ {
+ "$ref": "RequestFilter",
+ "name": "filter",
+ "description": "A set of filters that restricts the events that will be sent to this listener."
+ },
+ {
+ "type": "array",
+ "optional": true,
+ "name": "extraInfoSpec",
+ "description": "Array of extra information that should be passed to the listener function.",
+ "items": {
+ "$ref": "OnBeforeRequestOptions"
+ }
+ }
+ ],
+ "returns": {
+ "$ref": "BlockingResponse",
+ "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.",
+ "optional": true
+ }
+ },
+ {
+ "name": "onBeforeSendHeaders",
+ "nocompile": true,
+ "type": "function",
+ "description": "Fired before sending an HTTP request, once the request headers are available. This may occur after a TCP connection is made to the server, but before any HTTP data is sent. ",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "details",
+ "properties": {
+ "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
+ "url": {"type": "string"},
+ "method": {"type": "string", "description": "Standard HTTP method."},
+ "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
+ "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+ "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
+ "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
+ "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
+ "requestHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP request headers that are going to be sent out with this request."}
+ }
+ }
+ ],
+ "extraParameters": [
+ {
+ "$ref": "RequestFilter",
+ "name": "filter",
+ "description": "A set of filters that restricts the events that will be sent to this listener."
+ },
+ {
+ "type": "array",
+ "optional": true,
+ "name": "extraInfoSpec",
+ "description": "Array of extra information that should be passed to the listener function.",
+ "items": {
+ "$ref": "OnBeforeSendHeadersOptions"
+ }
+ }
+ ],
+ "returns": {
+ "$ref": "BlockingResponse",
+ "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.",
+ "optional": true
+ }
+ },
+ {
+ "name": "onSendHeaders",
+ "nocompile": true,
+ "type": "function",
+ "description": "Fired just before a request is going to be sent to the server (modifications of previous onBeforeSendHeaders callbacks are visible by the time onSendHeaders is fired).",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "details",
+ "properties": {
+ "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
+ "url": {"type": "string"},
+ "method": {"type": "string", "description": "Standard HTTP method."},
+ "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
+ "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+ "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
+ "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
+ "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
+ "requestHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP request headers that have been sent out with this request."}
+ }
+ }
+ ],
+ "extraParameters": [
+ {
+ "$ref": "RequestFilter",
+ "name": "filter",
+ "description": "A set of filters that restricts the events that will be sent to this listener."
+ },
+ {
+ "type": "array",
+ "optional": true,
+ "name": "extraInfoSpec",
+ "description": "Array of extra information that should be passed to the listener function.",
+ "items": {
+ "$ref": "OnSendHeadersOptions"
+ }
+ }
+ ]
+ },
+ {
+ "name": "onHeadersReceived",
+ "nocompile": true,
+ "type": "function",
+ "description": "Fired when HTTP response headers of a request have been received.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "details",
+ "properties": {
+ "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
+ "url": {"type": "string"},
+ "method": {"type": "string", "description": "Standard HTTP method."},
+ "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
+ "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+ "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
+ "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
+ "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
+ "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line)."},
+ "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that have been received with this response."},
+ "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."}
+ }
+ }
+ ],
+ "extraParameters": [
+ {
+ "$ref": "RequestFilter",
+ "name": "filter",
+ "description": "A set of filters that restricts the events that will be sent to this listener."
+ },
+ {
+ "type": "array",
+ "optional": true,
+ "name": "extraInfoSpec",
+ "description": "Array of extra information that should be passed to the listener function.",
+ "items": {
+ "$ref": "OnHeadersReceivedOptions"
+ }
+ }
+ ],
+ "returns": {
+ "$ref": "BlockingResponse",
+ "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.",
+ "optional": true
+ }
+ },
+ {
+ "name": "onAuthRequired",
+ "nocompile": true,
+ "type": "function",
+ "description": "Fired when an authentication failure is received. The listener has three options: it can provide authentication credentials, it can cancel the request and display the error page, or it can take no action on the challenge. If bad user credentials are provided, this may be called multiple times for the same request.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "details",
+ "properties": {
+ "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
+ "url": {"type": "string"},
+ "method": {"type": "string", "description": "Standard HTTP method."},
+ "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
+ "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+ "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
+ "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
+ "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
+ "scheme": {"type": "string", "description": "The authentication scheme, e.g. Basic or Digest."},
+ "realm": {"type": "string", "description": "The authentication realm provided by the server, if there is one.", "optional": true},
+ "challenger": {"type": "object", "description": "The server requesting authentication.", "properties": {"host": {"type": "string"}, "port": {"type": "integer"}}},
+ "isProxy": {"type": "boolean", "description": "True for Proxy-Authenticate, false for WWW-Authenticate."},
+ "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this response."},
+ "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line) or an empty string if there are no headers."},
+ "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."}
+ }
+ },
+ {
+ "type": "function",
+ "optional": true,
+ "name": "callback",
+ "parameters": [
+ {"name": "response", "$ref": "BlockingResponse"}
+ ]
+ }
+ ],
+ "extraParameters": [
+ {
+ "$ref": "RequestFilter",
+ "name": "filter",
+ "description": "A set of filters that restricts the events that will be sent to this listener."
+ },
+ {
+ "type": "array",
+ "optional": true,
+ "name": "extraInfoSpec",
+ "description": "Array of extra information that should be passed to the listener function.",
+ "items": {
+ "$ref": "OnAuthRequiredOptions"
+ }
+ }
+ ],
+ "returns": {
+ "$ref": "BlockingResponse",
+ "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.",
+ "optional": true
+ }
+ },
+ {
+ "name": "onResponseStarted",
+ "nocompile": true,
+ "type": "function",
+ "description": "Fired when the first byte of the response body is received. For HTTP requests, this means that the status line and response headers are available.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "details",
+ "properties": {
+ "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
+ "url": {"type": "string"},
+ "method": {"type": "string", "description": "Standard HTTP method."},
+ "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
+ "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+ "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
+ "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
+ "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
+ "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
+ "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
+ "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."},
+ "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this response."},
+ "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line) or an empty string if there are no headers."}
+ }
+ }
+ ],
+ "extraParameters": [
+ {
+ "$ref": "RequestFilter",
+ "name": "filter",
+ "description": "A set of filters that restricts the events that will be sent to this listener."
+ },
+ {
+ "type": "array",
+ "optional": true,
+ "name": "extraInfoSpec",
+ "description": "Array of extra information that should be passed to the listener function.",
+ "items": {
+ "$ref": "OnResponseStartedOptions"
+ }
+ }
+ ]
+ },
+ {
+ "name": "onBeforeRedirect",
+ "type": "function",
+ "nocompile": true,
+ "description": "Fired when a server-initiated redirect is about to occur.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "details",
+ "properties": {
+ "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
+ "url": {"type": "string"},
+ "method": {"type": "string", "description": "Standard HTTP method."},
+ "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
+ "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+ "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
+ "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
+ "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
+ "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
+ "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
+ "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."},
+ "redirectUrl": {"type": "string", "description": "The new URL."},
+ "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this redirect."},
+ "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line) or an empty string if there are no headers."}
+ }
+ }
+ ],
+ "extraParameters": [
+ {
+ "$ref": "RequestFilter",
+ "name": "filter",
+ "description": "A set of filters that restricts the events that will be sent to this listener."
+ },
+ {
+ "type": "array",
+ "optional": true,
+ "name": "extraInfoSpec",
+ "description": "Array of extra information that should be passed to the listener function.",
+ "items": {
+ "$ref": "OnBeforeRedirectOptions"
+ }
+ }
+ ]
+ },
+ {
+ "name": "onCompleted",
+ "type": "function",
+ "nocompile": true,
+ "description": "Fired when a request is completed.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "details",
+ "properties": {
+ "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
+ "url": {"type": "string"},
+ "method": {"type": "string", "description": "Standard HTTP method."},
+ "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
+ "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+ "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
+ "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
+ "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
+ "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
+ "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
+ "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."},
+ "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this response."},
+ "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line) or an empty string if there are no headers."}
+ }
+ }
+ ],
+ "extraParameters": [
+ {
+ "$ref": "RequestFilter",
+ "name": "filter",
+ "description": "A set of filters that restricts the events that will be sent to this listener."
+ },
+ {
+ "type": "array",
+ "optional": true,
+ "name": "extraInfoSpec",
+ "description": "Array of extra information that should be passed to the listener function.",
+ "items": {
+ "$ref": "OnCompletedOptions"
+ }
+ }
+ ]
+ },
+ {
+ "name": "onErrorOccurred",
+ "type": "function",
+ "description": "Fired when an error occurs.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "details",
+ "properties": {
+ "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
+ "url": {"type": "string"},
+ "method": {"type": "string", "description": "Standard HTTP method."},
+ "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
+ "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+ "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
+ "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
+ "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
+ "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
+ "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
+ "error": {"type": "string", "description": "The error description. This string is <em>not</em> guaranteed to remain backwards compatible between releases. You must not parse and act based upon its content."}
+ }
+ }
+ ],
+ "extraParameters": [
+ {
+ "$ref": "RequestFilter",
+ "name": "filter",
+ "description": "A set of filters that restricts the events that will be sent to this listener."
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/chromium/extensions/common/api/web_request_internal.json b/chromium/extensions/common/api/web_request_internal.json
new file mode 100644
index 00000000000..4adcd06dacb
--- /dev/null
+++ b/chromium/extensions/common/api/web_request_internal.json
@@ -0,0 +1,62 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "webRequestInternal",
+ "description": "none",
+ "compiler_options": {
+ "implemented_in": "extensions/browser/api/web_request/web_request_api.h"
+ },
+ "types": [
+ {
+ "id": "AddEventListenerOptions",
+ "type": "string",
+ "enum": ["requestHeaders", "responseHeaders", "blocking", "asyncBlocking", "requestBody"]
+ }
+ ],
+ "functions": [
+ {
+ "name": "addEventListener",
+ "type": "function",
+ "description": "Used internally to implement the special form of addListener for the webRequest events.",
+ "parameters": [
+ {"type": "function", "name": "callback"},
+ {
+ "$ref": "webRequest.RequestFilter",
+ "name": "filter",
+ "description": "A set of filters that restricts the events that will be sent to this listener."
+ },
+ {
+ "type": "array",
+ "optional": true,
+ "name": "extraInfoSpec",
+ "description": "Array of extra information that should be passed to the listener function.",
+ "items": {
+ "$ref": "AddEventListenerOptions"
+ }
+ },
+ {"type": "string", "name": "eventName"},
+ {"type": "string", "name": "subEventName"},
+ {"type": "integer", "name": "webViewInstanceId"}
+ ]
+ },
+ {
+ "name": "eventHandled",
+ "type": "function",
+ "description": "Used internally to send a response for a blocked event.",
+ "parameters": [
+ {"type": "string", "name": "eventName"},
+ {"type": "string", "name": "subEventName"},
+ {"type": "string", "name": "requestId"},
+ {
+ "$ref": "webRequest.BlockingResponse",
+ "optional": true,
+ "name": "response"
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/chromium/extensions/common/api/web_view_internal.json b/chromium/extensions/common/api/web_view_internal.json
new file mode 100644
index 00000000000..94a6eb9820b
--- /dev/null
+++ b/chromium/extensions/common/api/web_view_internal.json
@@ -0,0 +1,729 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "webViewInternal",
+ "description": "none",
+ "compiler_options": {
+ "implemented_in": "extensions/browser/api/guest_view/web_view/web_view_internal_api.h"
+ },
+ "types": [
+ {
+ "id": "DataTypeSet",
+ "type": "object",
+ "description": "A set of data types. Missing data types are interpreted as <code>false</code>.",
+ "properties": {
+ "appcache": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Websites' appcaches."
+ },
+ "cookies": {
+ "type": "boolean",
+ "optional": true,
+ "description": "The browser's cookies."
+ },
+ "fileSystems": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Websites' file systems."
+ },
+ "indexedDB": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Websites' IndexedDB data."
+ },
+ "localStorage": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Websites' local storage data."
+ },
+ "webSQL": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Websites' WebSQL data."
+ },
+ "cache": {
+ "type": "boolean",
+ "optional": true,
+ "description": "The Websites' cache data. Note: when removing data, this clears the <em>entire</em> cache: it is not limited to the range you specify."
+ }
+ }
+ },
+ {
+ "id": "RemovalOptions",
+ "type": "object",
+ "description": "Options that determine exactly what data will be removed.",
+ "properties": {
+ "since": {
+ "type": "number",
+ "optional": true,
+ "description": "Remove data accumulated on or after this date, represented in milliseconds since the epoch (accessible via the <code>getTime</code> method of the JavaScript <code>Date</code> object). If absent, defaults to 0 (which would remove all browsing data)."
+ }
+ }
+ },
+ {
+ "id": "ZoomMode",
+ "type": "string",
+ "description": "Defines the how zooming is handled in the webview.",
+ "enum": [
+ {
+ "name": "per-origin",
+ "description": "Zoom changes will persist in the zoomed page's origin, i.e. all other webviews in the same partition that are navigated to that same origin will be zoomed as well. Moreover, <code>per-origin</code> zoom changes are saved with the origin, meaning that when navigating to other pages in the same origin, they will all be zoomed to the same zoom factor."
+ },
+ {
+ "name": "per-view",
+ "description": "Zoom changes only take effect in this webview, and zoom changes in other webviews will not affect the zooming of this webview. Also, <code>per-view</code> zoom changes are reset on navigation; navigating a webview will always load pages with their per-origin zoom factors (within the scope of the partition)."
+ },
+ {
+ "name": "disabled",
+ "description": "Disables all zooming in the webview. The content will revert to the default zoom level, and all attempted zoom changes will be ignored."
+ }
+ ]
+ },
+ {
+ "id": "StopFindingAction",
+ "type": "string",
+ "description": "Determines what to do with the active match after the find session has ended. 'clear' will clear the highlighting over the active match; 'keep' will keep the active match highlighted; 'activate' will keep the active match highlighted and simulate a user click on that match.",
+ "enum": ["clear", "keep", "activate"]
+ },
+ {
+ "id": "SetPermissionAction",
+ "type": "string",
+ "enum": ["allow", "deny", "default"]
+ },
+ {
+ "id": "InjectionItems",
+ "type": "object",
+ "description": "The type of injection item: code or a set of files.",
+ "properties": {
+ "code": {
+ "type": "string",
+ "optional": true,
+ "description": "JavaScript code or CSS to be injected into matching pages."
+ },
+ "files": {
+ "type": "array",
+ "items": { "type": "string"},
+ "optional": true,
+ "description": "The list of JavaScript or CSS files to be injected into matching pages. These are injected in the order they appear in this array."
+ }
+ }
+ },
+ {
+ "id": "ContentScriptDetails",
+ "type": "object",
+ "description": "Details of the content script to inject.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "The name of the content script to inject."
+ },
+ "matches": {
+ "type": "array",
+ "items": { "type": "string"},
+ "description": "Specifies which pages this content script will be injected into."
+ },
+ "exclude_matches": {
+ "type": "array",
+ "items": { "type": "string"},
+ "optional": true,
+ "description": "Excludes pages that this content script would otherwise be injected into."
+ },
+ "match_about_blank": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether to insert the content script on about:blank and about:srcdoc. Content scripts will only be injected on pages when their inherit URL is matched by one of the declared patterns in the matches field. The inherit URL is the URL of the document that created the frame or window. Content scripts cannot be inserted in sandboxed frames."
+ },
+ "css": {
+ "$ref": "InjectionItems",
+ "optional": true,
+ "description": "The CSS code or a list of CSS files to be injected into matching pages. These are injected in the order they appear, before any DOM is constructed or displayed for the page."
+ },
+ "js": {
+ "$ref": "InjectionItems",
+ "optional": true,
+ "description": "The JavaScript code or a list of JavaScript files to be injected into matching pages. These are injected in the order they appear."
+ },
+ "run_at": {
+ "$ref": "extensionTypes.RunAt",
+ "optional": true,
+ "description": "The soonest that the JavaScript or CSS will be injected into the tab. Defaults to \"document_idle\"."
+ },
+ "all_frames": {
+ "type": "boolean",
+ "optional": true,
+ "description": "If allFrames is <code>true</code>, implies that the JavaScript or CSS should be injected into all frames of current page. By default, it's <code>false</code> and is only injected into the top frame."
+ },
+ "include_globs": {
+ "type": "array",
+ "items": { "type": "string"},
+ "optional": true,
+ "description": "Applied after matches to include only those URLs that also match this glob. Intended to emulate the @include Greasemonkey keyword."
+ },
+ "exclude_globs": {
+ "type": "array",
+ "items": { "type": "string"},
+ "optional": true,
+ "description": "Applied after matches to exclude URLs that match this glob. Intended to emulate the @exclude Greasemonkey keyword."
+ }
+ },
+ "required": ["name", "matches"]
+ }
+ ],
+ "functions": [
+ {
+ "name": "executeScript",
+ "type": "function",
+ "description": "Injects JavaScript code into a <webview> page.",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId",
+ "description": "The instance ID of the guest <webview> process."
+ },
+ {
+ "type": "string",
+ "name": "src",
+ "description": "The src of the guest <webview> tag."
+ },
+ {
+ "$ref": "extensionTypes.InjectDetails",
+ "name": "details",
+ "description": "Details of the script to run."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "description": "Called after all the JavaScript has been executed.",
+ "parameters": [
+ {
+ "name": "result",
+ "optional": true,
+ "type": "array",
+ "items": {"type": "any", "minimum": 0},
+ "description": "The result of the script in every injected frame."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "insertCSS",
+ "type": "function",
+ "description": "Injects CSS into a <webview> page. For details, see the <a href='/extensions/content_scripts#pi'>programmatic injection</a> section of the content scripts doc.",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId",
+ "description": "The instance ID of the guest <webview> process."
+ },
+ {
+ "type": "string",
+ "name": "src",
+ "description": "The src of the guest <webview> tag."
+ },
+ {
+ "$ref": "extensionTypes.InjectDetails",
+ "name": "details",
+ "description": "Details of the CSS text to insert."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "description": "Called when all the CSS has been inserted.",
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "addContentScripts",
+ "type": "function",
+ "description": "Adds content scripts into a <webview> page. For details, see the <a href='/extensions/content_scripts#pi'>programmatic injection</a> section of the content scripts doc.",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId",
+ "description": "The instance ID of the guest <webview> process."
+ },
+ {
+ "type": "array",
+ "name": "contentScriptList",
+ "items": {
+ "$ref": "ContentScriptDetails",
+ "name": "contentScriptDetails"
+ },
+ "description": "Details of the content scripts to add.",
+ "minItems": 1
+ }
+ ]
+ },
+ {
+ "name": "removeContentScripts",
+ "type": "function",
+ "description": "Removes specified content scripts from a <webview> page. For details, see the <a href='/extensions/content_scripts#pi'>programmatic injection</a> section of the content scripts doc.",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId",
+ "description": "The instance ID of the guest <webview> process."
+ },
+ {
+ "type": "array",
+ "name": "scriptNameList",
+ "items": {
+ "type": "string",
+ "description": "The name of a content script that will be removed."
+ },
+ "optional": true,
+ "description": "A list of names of content scripts that will be removed. If the list is empty, all the content scripts added to the <webview> page will be removed."
+ }
+ ]
+ },
+ {
+ "name": "setZoom",
+ "type": "function",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId",
+ "description": "The instance ID of the guest <webview> process."
+ },
+ {
+ "type": "number",
+ "name": "zoomFactor",
+ "description" : "The new zoom factor."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "description": "Called after the zoom message has been sent to the guest process.",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "getZoom",
+ "type": "function",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId",
+ "description": "The instance ID of the guest <webview> process."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "description": "Called after the current zoom factor is retrieved.",
+ "parameters": [
+ {
+ "type": "number",
+ "name": "zoomFactor",
+ "description": "The current zoom factor."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "setZoomMode",
+ "type": "function",
+ "description": "Sets the zoom mode of the webview.",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId",
+ "description": "The instance ID of the guest <webview> process."
+ },
+ {
+ "$ref": "ZoomMode",
+ "name": "ZoomMode",
+ "description": "Defines how zooming is handled in the webview."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "description": "Called after the zoom mode has been changed.",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "getZoomMode",
+ "type": "function",
+ "description": "Gets the current zoom mode.",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId",
+ "description": "The instance ID of the guest <webview> process."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "description": "Called with the webview's current zoom mode.",
+ "parameters": [
+ {
+ "$ref": "ZoomMode",
+ "name": "ZoomMode",
+ "description": "The webview's current zoom mode."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "find",
+ "type": "function",
+ "description": "Initiates a find-in-page request.",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId",
+ "description": "The instance ID of the guest <webview> process."
+ },
+ {
+ "type": "string",
+ "name": "searchText",
+ "description": "The string to find in the page."
+ },
+ {
+ "type": "object",
+ "name": "options",
+ "optional": true,
+ "properties": {
+ "backward": {
+ "type": "boolean",
+ "description": "Flag to find matches in reverse order.",
+ "optional": true
+ },
+ "matchCase": {
+ "type": "boolean",
+ "description": "Flag to match |searchText| with case-sensitivity.",
+ "optional": true
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "description": "Called after all find results have been returned for this find request.",
+ "optional": true,
+ "parameters": [
+ {
+ "type": "object",
+ "name": "results",
+ "optional": true,
+ "properties": {
+ "numberOfMatches": {
+ "type": "integer",
+ "description": "The number of times |searchText| was matched on the page."
+ },
+ "activeMatchOrdinal": {
+ "type": "integer",
+ "description": "The ordinal number of the current match."
+ },
+ "selectionRect": {
+ "type": "object",
+ "description": "Describes a rectangle around the active match.",
+ "properties": {
+ "left": {
+ "type": "integer"
+ },
+ "top": {
+ "type": "integer"
+ },
+ "width": {
+ "type": "integer"
+ },
+ "height": {
+ "type": "integer"
+ }
+ }
+ },
+ "canceled": {
+ "type": "boolean",
+ "description": "Indicates whether this find request was canceled."
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "stopFinding",
+ "type": "function",
+ "description": "Ends the current find session (clearing all highlighting) and cancels all find requests in progress.",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId",
+ "description": "The instance ID of the guest <webview> process."
+ },
+ {
+ "$ref": "StopFindingAction",
+ "name": "action",
+ "description": "Determines what to do with the active match after the find session has ended.",
+ "optional": true
+ }
+ ]
+ },
+ {
+ "name": "loadDataWithBaseUrl",
+ "type": "function",
+ "description": "Loads a data URL with a specified base URL used for relative links. Optionally, a virtual URL can be provided to be shown to the user instead of the data URL.",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId",
+ "description": "The instance ID of the guest <webview> process."
+ },
+ {
+ "type": "string",
+ "name": "dataUrl",
+ "description" : "The data URL to load."
+ },
+ {
+ "type": "string",
+ "name": "baseUrl",
+ "description": "The base URL that will be used for relative links."
+ },
+ {
+ "type": "string",
+ "name": "virtualUrl",
+ "description": "The URL that will be displayed to the user.",
+ "optional": true
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "description": "Called internally for the purpose of reporting errors to console.error().",
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "go",
+ "type": "function",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId"
+ },
+ {
+ "type": "integer",
+ "name": "relativeIndex"
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "success",
+ "type": "boolean",
+ "description": "Indicates whether the navigation was successful."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "overrideUserAgent",
+ "type": "function",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId"
+ },
+ {
+ "type": "string",
+ "name": "userAgentOverride"
+ }
+ ]
+ },
+ {
+ "name": "reload",
+ "type": "function",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId"
+ }
+ ]
+ },
+ {
+ "name": "setAllowTransparency",
+ "type": "function",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId"
+ },
+ {
+ "type": "boolean",
+ "name": "allow"
+ }
+ ]
+ },
+ {
+ "name": "setAllowScaling",
+ "type": "function",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId"
+ },
+ {
+ "type": "boolean",
+ "name": "allow"
+ }
+ ]
+ },
+ {
+ "name": "setName",
+ "type": "function",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId"
+ },
+ {
+ "type": "string",
+ "name": "frameName"
+ }
+ ]
+ },
+ {
+ "name": "setPermission",
+ "type": "function",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId"
+ },
+ {
+ "type": "integer",
+ "name": "requestId"
+ },
+ {
+ "$ref": "SetPermissionAction",
+ "name": "action"
+ },
+ {
+ "type": "string",
+ "name": "userInput",
+ "optional": true
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "allowed",
+ "type": "boolean"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "navigate",
+ "type": "function",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId"
+ },
+ {
+ "type": "string",
+ "name": "src"
+ }
+ ]
+ },
+ {
+ "name": "stop",
+ "type": "function",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId"
+ }
+ ]
+ },
+ {
+ "name": "terminate",
+ "type": "function",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId"
+ }
+ ]
+ },
+ {
+ "name": "captureVisibleRegion",
+ "type": "function",
+ "description": "foo",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId",
+ "description": "The instance ID of the guest <webview> process."
+ },
+ {
+ "$ref": "extensionTypes.ImageDetails",
+ "name": "options",
+ "optional": true
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {"type": "string",
+ "name": "dataUrl",
+ "description": "A data URL which encodes an image of the visible area of the captured tab. May be assigned to the 'src' property of an HTML Image element for display."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "clearData",
+ "type": "function",
+ "description": "Clears various types of browsing data stored in a storage partition of a <webview>.",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "instanceId",
+ "description": "The instance ID of the guest <webview> process."
+ },
+ {
+ "$ref": "RemovalOptions",
+ "name": "options"
+ },
+ {
+ "name": "dataToRemove",
+ "$ref": "DataTypeSet",
+ "description": "The set of data types to remove."
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "description": "Called when deletion has completed.",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/chromium/extensions/common/api/web_view_request.json b/chromium/extensions/common/api/web_view_request.json
new file mode 100644
index 00000000000..826cfa3d2e1
--- /dev/null
+++ b/chromium/extensions/common/api/web_view_request.json
@@ -0,0 +1,10 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "webViewRequest",
+ "description": "Use the <code>chrome.webViewRequest</code> API to intercept, block, or modify requests in-flight. It is potentially faster than the <a href='webRequest'><code>chrome.webRequest</code> API</a> because you can register rules that are evaluated in the browser rather than the JavaScript engine with reduces roundtrip latencies and allows higher efficiency."
+ }
+]
diff --git a/chromium/extensions/common/api/webcam_private.idl b/chromium/extensions/common/api/webcam_private.idl
new file mode 100644
index 00000000000..8095699cd21
--- /dev/null
+++ b/chromium/extensions/common/api/webcam_private.idl
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Webcam Private API.
+namespace webcamPrivate {
+ enum PanDirection { stop, right, left };
+ enum TiltDirection { stop, up, down };
+ enum Protocol { visca };
+
+ dictionary ProtocolConfiguration {
+ Protocol? protocol;
+ };
+
+ dictionary WebcamConfiguration {
+ double? pan;
+ double? panSpeed;
+ PanDirection? panDirection;
+ double? tilt;
+ double? tiltSpeed;
+ TiltDirection? tiltDirection;
+ double? zoom;
+ };
+
+ callback WebcamIdCallback = void(DOMString webcamId);
+ callback WebcamConfigurationCallback =
+ void(WebcamConfiguration configuration);
+
+ interface Functions {
+ // Open a serial port that controls a webcam.
+ static void openSerialWebcam(DOMString path, ProtocolConfiguration protocol,
+ WebcamIdCallback callback);
+
+ // Close a serial port connection to a webcam.
+ static void closeWebcam(DOMString webcamId);
+
+ static void get(DOMString webcamId, WebcamConfigurationCallback callback);
+ static void set(DOMString webcamId, WebcamConfiguration config);
+
+ // Reset a webcam. Note: the value of the parameter have no effect, it's the
+ // presence of the parameter that matters. E.g.: reset(webcamId, {pan: 0,
+ // tilt: 1}); will reset pan & tilt, but not zoom.
+ static void reset(DOMString webcamId, WebcamConfiguration config);
+ };
+};
+
diff --git a/chromium/extensions/common/cast/cast_cert_validator.cc b/chromium/extensions/common/cast/cast_cert_validator.cc
new file mode 100644
index 00000000000..2211bb653cf
--- /dev/null
+++ b/chromium/extensions/common/cast/cast_cert_validator.cc
@@ -0,0 +1,383 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/cast/cast_cert_validator.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <algorithm>
+#include <utility>
+
+#include "net/cert/internal/certificate_policies.h"
+#include "net/cert/internal/extended_key_usage.h"
+#include "net/cert/internal/parse_certificate.h"
+#include "net/cert/internal/parse_name.h"
+#include "net/cert/internal/signature_algorithm.h"
+#include "net/cert/internal/signature_policy.h"
+#include "net/cert/internal/verify_certificate_chain.h"
+#include "net/cert/internal/verify_signed_data.h"
+#include "net/der/input.h"
+
+namespace extensions {
+namespace api {
+namespace cast_crypto {
+namespace {
+
+// -------------------------------------------------------------------------
+// Cast trust anchors.
+// -------------------------------------------------------------------------
+
+// There are two trusted roots for Cast certificate chains:
+//
+// (1) CN=Cast Root CA
+// (2) CN=Eureka Root CA
+//
+// Note that only the subject/spki are saved here, not the full certificate.
+// See the TODO in CreateCastTrustStore().
+
+unsigned char kCastRootCaSubjectDer[119] = {
+ 0x30, 0x75, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08,
+ 0x0C, 0x0A, 0x43, 0x61, 0x6C, 0x69, 0x66, 0x6F, 0x72, 0x6E, 0x69, 0x61,
+ 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0C, 0x0D, 0x4D,
+ 0x6F, 0x75, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x20, 0x56, 0x69, 0x65, 0x77,
+ 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x0C, 0x0A, 0x47,
+ 0x6F, 0x6F, 0x67, 0x6C, 0x65, 0x20, 0x49, 0x6E, 0x63, 0x31, 0x0D, 0x30,
+ 0x0B, 0x06, 0x03, 0x55, 0x04, 0x0B, 0x0C, 0x04, 0x43, 0x61, 0x73, 0x74,
+ 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x0C, 0x43,
+ 0x61, 0x73, 0x74, 0x20, 0x52, 0x6F, 0x6F, 0x74, 0x20, 0x43, 0x41,
+};
+
+unsigned char kCastRootCaSpkiDer[294] = {
+ 0x30, 0x82, 0x01, 0x22, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86,
+ 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0F, 0x00,
+ 0x30, 0x82, 0x01, 0x0A, 0x02, 0x82, 0x01, 0x01, 0x00, 0xBA, 0xD9, 0x65,
+ 0x9D, 0xDA, 0x39, 0xD3, 0xC1, 0x77, 0xF6, 0xD4, 0xD0, 0xAE, 0x8F, 0x58,
+ 0x08, 0x68, 0x39, 0x4A, 0x95, 0xED, 0x70, 0xCF, 0xFD, 0x79, 0x08, 0xA9,
+ 0xAA, 0xE5, 0xE9, 0xB8, 0xA7, 0x2D, 0xA0, 0x67, 0x47, 0x8A, 0x9E, 0xC9,
+ 0xCF, 0x70, 0xB3, 0x05, 0x87, 0x69, 0x11, 0xEC, 0x70, 0x98, 0x97, 0xC3,
+ 0xE6, 0xC3, 0xC3, 0xEB, 0xBD, 0xC6, 0xB0, 0x3D, 0xFC, 0x4F, 0xC1, 0x5E,
+ 0x38, 0x9F, 0xDA, 0xCF, 0x73, 0x30, 0x06, 0x5B, 0x79, 0x37, 0xC1, 0x5E,
+ 0x8C, 0x87, 0x47, 0x94, 0x9A, 0x41, 0x92, 0x2A, 0xD6, 0x95, 0xC4, 0x71,
+ 0x5C, 0x27, 0x5D, 0x08, 0xB1, 0x80, 0xC6, 0x92, 0xBD, 0x1B, 0xE3, 0x41,
+ 0x97, 0xA1, 0xEC, 0x75, 0x9F, 0x55, 0x9E, 0x3E, 0x9F, 0x8F, 0x1C, 0xC7,
+ 0x65, 0x64, 0x07, 0xD3, 0xB3, 0x96, 0xA1, 0x04, 0x9F, 0x91, 0xC4, 0xDE,
+ 0x0A, 0x7B, 0x6C, 0xD9, 0xC8, 0xC0, 0x78, 0x31, 0xA0, 0x19, 0x42, 0xA9,
+ 0xE8, 0x83, 0xE3, 0xCE, 0xFC, 0xF1, 0xCE, 0xC2, 0x2E, 0x24, 0x46, 0x95,
+ 0x09, 0x19, 0xCA, 0xC0, 0x46, 0xB2, 0xE5, 0x01, 0xBA, 0xD7, 0x4F, 0xF3,
+ 0xBF, 0xF6, 0x69, 0xAD, 0x99, 0x04, 0xFA, 0xA0, 0x07, 0x39, 0x0E, 0xE6,
+ 0xDF, 0x51, 0x47, 0x07, 0xC0, 0xE4, 0xA9, 0x5C, 0x4B, 0x94, 0xC5, 0x2F,
+ 0xB3, 0xA0, 0x30, 0x7F, 0xE7, 0x95, 0x6B, 0xB2, 0xAF, 0x32, 0x0D, 0xF1,
+ 0x8C, 0xD5, 0x6D, 0xCB, 0x7B, 0x47, 0xA7, 0x08, 0xAB, 0xCB, 0x27, 0xA3,
+ 0x4D, 0xCF, 0x4A, 0x5A, 0xF1, 0x05, 0xD1, 0xF8, 0x62, 0xC5, 0x10, 0x2A,
+ 0x74, 0x69, 0xAA, 0xE6, 0x4B, 0x96, 0xFB, 0x9B, 0xD8, 0x63, 0xE4, 0x58,
+ 0x66, 0xD3, 0xAD, 0x8A, 0x6E, 0xFF, 0x7B, 0x5E, 0xF9, 0xA5, 0x56, 0x1E,
+ 0x2D, 0x82, 0x31, 0x5B, 0xF0, 0xE2, 0x24, 0xE6, 0x41, 0x4A, 0x1F, 0xAE,
+ 0x13, 0x02, 0x03, 0x01, 0x00, 0x01,
+};
+
+unsigned char kEurekaRootCaSubjectDer[126] = {
+ 0x30, 0x7C, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08,
+ 0x0C, 0x0A, 0x43, 0x61, 0x6C, 0x69, 0x66, 0x6F, 0x72, 0x6E, 0x69, 0x61,
+ 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0C, 0x0D, 0x4D,
+ 0x6F, 0x75, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x20, 0x56, 0x69, 0x65, 0x77,
+ 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x0C, 0x0A, 0x47,
+ 0x6F, 0x6F, 0x67, 0x6C, 0x65, 0x20, 0x49, 0x6E, 0x63, 0x31, 0x12, 0x30,
+ 0x10, 0x06, 0x03, 0x55, 0x04, 0x0B, 0x0C, 0x09, 0x47, 0x6F, 0x6F, 0x67,
+ 0x6C, 0x65, 0x20, 0x54, 0x56, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x0C, 0x0E, 0x45, 0x75, 0x72, 0x65, 0x6B, 0x61, 0x20, 0x52,
+ 0x6F, 0x6F, 0x74, 0x20, 0x43, 0x41,
+};
+
+unsigned char kEurekaRootCaSpkiDer[294] = {
+ 0x30, 0x82, 0x01, 0x22, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86,
+ 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0F, 0x00,
+ 0x30, 0x82, 0x01, 0x0A, 0x02, 0x82, 0x01, 0x01, 0x00, 0xB9, 0x11, 0xD0,
+ 0xEA, 0x12, 0xDC, 0x32, 0xE1, 0xDF, 0x5C, 0x33, 0x6B, 0x19, 0x73, 0x1D,
+ 0x9D, 0x9E, 0xD0, 0x39, 0x76, 0xBF, 0xA5, 0x84, 0x09, 0xA6, 0xFD, 0x6E,
+ 0x6D, 0xE9, 0xDC, 0x8F, 0x36, 0x4E, 0xE9, 0x88, 0x02, 0xBD, 0x9F, 0xF4,
+ 0xE8, 0x44, 0xFD, 0x4C, 0xF5, 0x9A, 0x02, 0x56, 0x6A, 0x47, 0x2A, 0x63,
+ 0x6C, 0x58, 0x45, 0xCC, 0x7C, 0x66, 0x24, 0xDC, 0x79, 0x79, 0xC3, 0x2A,
+ 0xA4, 0xB2, 0x8B, 0xA0, 0xF7, 0xA2, 0xB5, 0xCD, 0x06, 0x7E, 0xDB, 0xBE,
+ 0xEC, 0x0C, 0x86, 0xF2, 0x0D, 0x24, 0x60, 0x74, 0x84, 0xCA, 0x29, 0x23,
+ 0x84, 0x02, 0xD8, 0xA7, 0xED, 0x3B, 0xF1, 0xEC, 0x26, 0x47, 0x54, 0xE3,
+ 0xB1, 0x2D, 0xE6, 0x64, 0x0F, 0xF6, 0x72, 0xC5, 0xE9, 0x98, 0x52, 0x17,
+ 0xC0, 0xFC, 0xF2, 0x2C, 0x20, 0xC8, 0x40, 0xF8, 0x47, 0xC9, 0x32, 0x9E,
+ 0x3B, 0x97, 0xB1, 0x8B, 0xF5, 0x98, 0x24, 0x70, 0x63, 0x66, 0x19, 0xC1,
+ 0x52, 0xE8, 0x04, 0x05, 0x3D, 0x5F, 0x8D, 0xBC, 0xD8, 0x4B, 0xAF, 0x77,
+ 0x98, 0x6F, 0x1F, 0x78, 0xD1, 0xB6, 0x50, 0x27, 0x4D, 0xE4, 0xEC, 0x14,
+ 0x69, 0x67, 0x1F, 0x58, 0xAF, 0xA9, 0xA0, 0x11, 0x26, 0x3C, 0x94, 0x32,
+ 0x07, 0x7F, 0xD7, 0xE9, 0x69, 0x1F, 0xAE, 0x3F, 0x4F, 0x63, 0x8A, 0x8F,
+ 0x89, 0xD6, 0xF2, 0x19, 0x78, 0x5C, 0x21, 0x8E, 0xB1, 0xB6, 0x57, 0xD8,
+ 0xC0, 0xE1, 0xEE, 0x7D, 0x6E, 0xDD, 0xF1, 0x3A, 0x0A, 0x6A, 0xF1, 0xBA,
+ 0xFF, 0xF9, 0x83, 0x2F, 0xDC, 0xB5, 0xA4, 0x20, 0x17, 0x63, 0x36, 0xEF,
+ 0xC8, 0x62, 0x19, 0xCC, 0x56, 0xCE, 0xB2, 0xEA, 0x31, 0x89, 0x4B, 0x78,
+ 0x58, 0xC1, 0xBF, 0x03, 0x13, 0x99, 0xE0, 0x12, 0xF2, 0x88, 0xAA, 0x9B,
+ 0x94, 0xDA, 0xDD, 0x76, 0x79, 0x17, 0x1E, 0x34, 0xD1, 0x0A, 0xC4, 0x07,
+ 0x45, 0x02, 0x03, 0x01, 0x00, 0x01,
+};
+
+// Helper function that creates and initializes a TrustAnchor struct given
+// arrays for the subject's DER and the SPKI's DER.
+template <size_t SubjectSize, size_t SpkiSize>
+net::TrustAnchor CreateTrustAnchor(const uint8_t (&subject)[SubjectSize],
+ const uint8_t (&spki)[SpkiSize]) {
+ net::TrustAnchor anchor;
+ anchor.name = std::string(subject, subject + SubjectSize);
+ anchor.spki = std::string(spki, spki + SpkiSize);
+ return anchor;
+}
+
+// Creates a trust store with the two Cast roots.
+//
+// TODO(eroman): The root certificates themselves are not included in the trust
+// store (just their subject/SPKI). The problem with this approach is any
+// restrictions encoded in their (like path length, or policy) are not known
+// when verifying, and hence not enforced.
+net::TrustStore CreateCastTrustStore() {
+ net::TrustStore store;
+ store.anchors.push_back(
+ CreateTrustAnchor(kEurekaRootCaSubjectDer, kEurekaRootCaSpkiDer));
+ store.anchors.push_back(
+ CreateTrustAnchor(kCastRootCaSubjectDer, kCastRootCaSpkiDer));
+ return store;
+}
+
+using ExtensionsMap = std::map<net::der::Input, net::ParsedExtension>;
+
+// Helper that looks up an extension by OID given a map of extensions.
+bool GetExtensionValue(const ExtensionsMap& extensions,
+ const net::der::Input& oid,
+ net::der::Input* value) {
+ auto it = extensions.find(oid);
+ if (it == extensions.end())
+ return false;
+ *value = it->second.value;
+ return true;
+}
+
+// Returns the OID for the Audio-Only Cast policy
+// (1.3.6.1.4.1.11129.2.5.2) in DER form.
+net::der::Input AudioOnlyPolicyOid() {
+ static const uint8_t kAudioOnlyPolicy[] = {0x2B, 0x06, 0x01, 0x04, 0x01,
+ 0xD6, 0x79, 0x02, 0x05, 0x02};
+ return net::der::Input(kAudioOnlyPolicy);
+}
+
+// Cast certificates rely on RSASSA-PKCS#1 v1.5 with SHA-1 for signatures.
+//
+// The following signature policy specifies which signature algorithms (and key
+// sizes) are acceptable. It is used when verifying a chain of certificates, as
+// well as when verifying digital signature using the target certificate's
+// SPKI.
+//
+// This particular policy allows for:
+// * ECDSA, RSA-SSA, and RSA-PSS
+// * Supported EC curves: P-256, P-384, P-521.
+// * Hashes: All SHA hashes including SHA-1 (despite being known weak).
+// * RSA keys must have a modulus at least 2048-bits long.
+scoped_ptr<net::SignaturePolicy> CreateCastSignaturePolicy() {
+ return make_scoped_ptr(new net::SimpleSignaturePolicy(2048));
+}
+
+class CertVerificationContextImpl : public CertVerificationContext {
+ public:
+ // Save a copy of the passed in public key (DER) and common name (text).
+ CertVerificationContextImpl(const net::der::Input& spki,
+ const base::StringPiece& common_name)
+ : spki_(spki.AsString()), common_name_(common_name.as_string()) {}
+
+ bool VerifySignatureOverData(const base::StringPiece& signature,
+ const base::StringPiece& data) const override {
+ // This code assumes the signature algorithm was RSASSA PKCS#1 v1.5 with
+ // SHA-1.
+ // TODO(eroman): Is it possible to use other hash algorithms?
+ auto signature_algorithm =
+ net::SignatureAlgorithm::CreateRsaPkcs1(net::DigestAlgorithm::Sha1);
+
+ // Use the same policy as was used for verifying signatures in
+ // certificates. This will ensure for instance that the key used is at
+ // least 2048-bits long.
+ auto signature_policy = CreateCastSignaturePolicy();
+
+ return net::VerifySignedData(
+ *signature_algorithm, net::der::Input(data),
+ net::der::BitString(net::der::Input(signature), 0),
+ net::der::Input(&spki_), signature_policy.get());
+ }
+
+ std::string GetCommonName() const override { return common_name_; }
+
+ private:
+ std::string spki_;
+ std::string common_name_;
+};
+
+// Helper that extracts the Common Name from a certificate's subject field. On
+// success |common_name| contains the text for the attribute (unescaped, so
+// will depend on the encoding used, but for Cast device certs it should
+// be ASCII).
+bool GetCommonNameFromSubject(const net::der::Input& subject_tlv,
+ std::string* common_name) {
+ net::RDNSequence rdn_sequence;
+ if (!net::ParseName(subject_tlv, &rdn_sequence))
+ return false;
+
+ for (const net::RelativeDistinguishedName& rdn : rdn_sequence) {
+ for (const auto& atv : rdn) {
+ if (atv.type == net::TypeCommonNameOid()) {
+ return atv.ValueAsStringUnsafe(common_name);
+ }
+ }
+ }
+ return false;
+}
+
+// Returns true if the extended key usage list |ekus| contains client auth.
+bool HasClientAuth(const std::vector<net::der::Input>& ekus) {
+ for (const auto& oid : ekus) {
+ if (oid == net::ClientAuth())
+ return true;
+ }
+ return false;
+}
+
+// Checks properties on the target certificate.
+//
+// * The Key Usage must include Digital Signature
+// * THe Extended Key Usage must includ TLS Client Auth
+// * May have the policy 1.3.6.1.4.1.11129.2.5.2 to indicate it
+// is an audio-only device.
+WARN_UNUSED_RESULT bool CheckTargetCertificate(
+ const net::der::Input& cert_der,
+ scoped_ptr<CertVerificationContext>* context,
+ CastDeviceCertPolicy* policy) {
+ // TODO(eroman): Simplify this. The certificate chain verification
+ // function already parses this stuff, awkward to re-do it here.
+
+ net::ParsedCertificate cert;
+ if (!net::ParseCertificate(cert_der, &cert))
+ return false;
+
+ net::ParsedTbsCertificate tbs;
+ if (!net::ParseTbsCertificate(cert.tbs_certificate_tlv, &tbs))
+ return false;
+
+ // Get the extensions.
+ if (!tbs.has_extensions)
+ return false;
+ ExtensionsMap extensions;
+ if (!net::ParseExtensions(tbs.extensions_tlv, &extensions))
+ return false;
+
+ net::der::Input extension_value;
+
+ // Get the Key Usage extension.
+ if (!GetExtensionValue(extensions, net::KeyUsageOid(), &extension_value))
+ return false;
+ net::der::BitString key_usage;
+ if (!net::ParseKeyUsage(extension_value, &key_usage))
+ return false;
+
+ // Ensure Key Usage contains digitalSignature.
+ if (!key_usage.AssertsBit(net::KEY_USAGE_BIT_DIGITAL_SIGNATURE))
+ return false;
+
+ // Get the Extended Key Usage extension.
+ if (!GetExtensionValue(extensions, net::ExtKeyUsageOid(), &extension_value))
+ return false;
+ std::vector<net::der::Input> ekus;
+ if (!net::ParseEKUExtension(extension_value, &ekus))
+ return false;
+
+ // Ensure Extended Key Usage contains client auth.
+ if (!HasClientAuth(ekus))
+ return false;
+
+ // Check for an optional audio-only policy extension.
+ *policy = CastDeviceCertPolicy::NONE;
+ if (GetExtensionValue(extensions, net::CertificatePoliciesOid(),
+ &extension_value)) {
+ std::vector<net::der::Input> policies;
+ if (!net::ParseCertificatePoliciesExtension(extension_value, &policies))
+ return false;
+
+ // Look for an audio-only policy. Disregard any other policy found.
+ if (std::find(policies.begin(), policies.end(), AudioOnlyPolicyOid()) !=
+ policies.end()) {
+ *policy = CastDeviceCertPolicy::AUDIO_ONLY;
+ }
+ }
+
+ // Get the Common Name for the certificate.
+ std::string common_name;
+ if (!GetCommonNameFromSubject(tbs.subject_tlv, &common_name))
+ return false;
+
+ context->reset(new CertVerificationContextImpl(tbs.spki_tlv, common_name));
+ return true;
+}
+
+
+// Converts a base::Time::Exploded to a net::der::GeneralizedTime.
+net::der::GeneralizedTime ConvertExplodedTime(
+ const base::Time::Exploded& exploded) {
+ net::der::GeneralizedTime result;
+ result.year = exploded.year;
+ result.month = exploded.month;
+ result.day = exploded.day_of_month;
+ result.hours = exploded.hour;
+ result.minutes = exploded.minute;
+ result.seconds = exploded.second;
+ return result;
+}
+
+} // namespace
+
+bool VerifyDeviceCert(const std::vector<std::string>& certs,
+ const base::Time::Exploded& time,
+ scoped_ptr<CertVerificationContext>* context,
+ CastDeviceCertPolicy* policy) {
+ // Initialize the trust store used for verifying Cast
+ // device certificates.
+ //
+ // Performance: This code is re-building a TrustStore object each
+ // time a chain needs to be verified rather than caching it, to
+ // avoid memory bloat.
+ auto trust_store = CreateCastTrustStore();
+
+ // The underlying verification function expects a sequence of
+ // der::Input, so wrap the data in it (cheap).
+ std::vector<net::der::Input> input_chain;
+ for (const auto& cert : certs)
+ input_chain.push_back(net::der::Input(&cert));
+
+ // Use a signature policy compatible with Cast's PKI.
+ auto signature_policy = CreateCastSignaturePolicy();
+
+ // Do RFC 5280 compatible certificate verification using the two Cast
+ // trust anchors and Cast signature policy.
+ if (!net::VerifyCertificateChain(input_chain, trust_store,
+ signature_policy.get(),
+ ConvertExplodedTime(time))) {
+ return false;
+ }
+
+ // Check properties of the leaf certificate (key usage, policy), and construct
+ // a CertVerificationContext that uses its public key.
+ return CheckTargetCertificate(input_chain[0], context, policy);
+}
+
+scoped_ptr<CertVerificationContext> CertVerificationContextImplForTest(
+ const base::StringPiece& spki) {
+ // Use a bogus CommonName, since this is just exposed for testing signature
+ // verification by unittests.
+ return make_scoped_ptr(
+ new CertVerificationContextImpl(net::der::Input(spki), "CommonName"));
+}
+
+} // namespace cast_crypto
+} // namespace api
+} // namespace extensions
diff --git a/chromium/extensions/common/cast/cast_cert_validator.h b/chromium/extensions/common/cast/cast_cert_validator.h
new file mode 100644
index 00000000000..c435ebb8776
--- /dev/null
+++ b/chromium/extensions/common/cast/cast_cert_validator.h
@@ -0,0 +1,94 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_CAST_CAST_CERT_VALIDATOR_H_
+#define EXTENSIONS_COMMON_CAST_CAST_CERT_VALIDATOR_H_
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "base/time/time.h"
+
+namespace extensions {
+namespace api {
+namespace cast_crypto {
+
+// Describes the policy for a Device certificate.
+enum class CastDeviceCertPolicy {
+ // The device certificate is unrestricted.
+ NONE,
+
+ // The device certificate is for an audio-only device.
+ AUDIO_ONLY,
+};
+
+// An object of this type is returned by the VerifyDeviceCert function, and can
+// be used for additional certificate-related operations, using the verified
+// certificate.
+class CertVerificationContext {
+ public:
+ CertVerificationContext() {}
+ virtual ~CertVerificationContext() {}
+
+ // Use the public key from the verified certificate to verify a
+ // sha1WithRSAEncryption |signature| over arbitrary |data|. Both |signature|
+ // and |data| hold raw binary data. Returns true if the signature was
+ // correct.
+ virtual bool VerifySignatureOverData(const base::StringPiece& signature,
+ const base::StringPiece& data) const = 0;
+
+ // Retrieve the Common Name attribute of the subject's distinguished name from
+ // the verified certificate, if present. Returns an empty string if no Common
+ // Name is found.
+ virtual std::string GetCommonName() const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CertVerificationContext);
+};
+
+// Verifies a cast device certficate given a chain of DER-encoded certificates.
+//
+// Inputs:
+//
+// * |certs| is a chain of DER-encoded certificates:
+// * |certs[0]| is the target certificate (i.e. the device certificate)
+// * |certs[i]| is the certificate that issued certs[i-1]
+// * |certs.back()| must be signed by a trust anchor
+//
+// * |time| is the UTC time to use for determining if the certificate
+// is expired.
+//
+// Outputs:
+//
+// Returns true on success, false on failure. On success the output
+// parameters are filled with more details:
+//
+// * |context| is filled with an object that can be used to verify signatures
+// using the device certificate's public key, as well as to extract other
+// properties from the device certificate (Common Name).
+// * |policy| is filled with an indication of the device certificate's policy
+// (i.e. is it for audio-only devices or is it unrestricted?)
+bool VerifyDeviceCert(const std::vector<std::string>& certs,
+ const base::Time::Exploded& time,
+ scoped_ptr<CertVerificationContext>* context,
+ CastDeviceCertPolicy* policy) WARN_UNUSED_RESULT;
+
+// Exposed only for unit-tests, not for use in production code.
+// Production code would get a context from VerifyDeviceCert().
+//
+// Constructs a VerificationContext that uses the provided public key.
+// The common name will be hardcoded to some test value.
+scoped_ptr<CertVerificationContext> CertVerificationContextImplForTest(
+ const base::StringPiece& spki);
+
+
+} // namespace cast_crypto
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_CAST_CAST_CERT_VALIDATOR_H_
diff --git a/chromium/extensions/common/cast/cast_cert_validator_unittest.cc b/chromium/extensions/common/cast/cast_cert_validator_unittest.cc
new file mode 100644
index 00000000000..b2bc8ed838c
--- /dev/null
+++ b/chromium/extensions/common/cast/cast_cert_validator_unittest.cc
@@ -0,0 +1,522 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/cast/cast_cert_validator.h"
+
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "extensions/common/extension_paths.h"
+#include "net/cert/pem_tokenizer.h"
+#include "net/der/parse_values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace api {
+
+namespace cast_crypto {
+
+namespace {
+
+// Creates an std::string given a uint8_t array.
+template <size_t N>
+std::string CreateString(const uint8_t (&data)[N]) {
+ return std::string(reinterpret_cast<const char*>(data), N);
+}
+
+// Reads a file from the cast certificates test data directory:
+// src/extensions/test/data/cast_certificates/
+std::string ReadTestFileToString(const std::string& file_name) {
+ base::FilePath filepath;
+ if (!PathService::Get(DIR_TEST_DATA, &filepath)) {
+ ADD_FAILURE() << "Couldn't retrieve test data root";
+ return std::string();
+ }
+ filepath = filepath.AppendASCII("cast_certificates/" + file_name);
+
+ // Read the full contents of the file.
+ std::string file_data;
+ if (!base::ReadFileToString(filepath, &file_data)) {
+ ADD_FAILURE() << "Couldn't read file: " << filepath.value();
+ return std::string();
+ }
+
+ return file_data;
+}
+
+// Indicates the expected result of test verification.
+enum TestResult {
+ RESULT_SUCCESS,
+ RESULT_FAIL,
+};
+
+// Reads a PEM file containing "CERTIFICATE" blocks to a vector of certificate
+// data.
+std::vector<std::string> ReadCertificateChainFromFile(
+ const std::string& file_name) {
+ std::string file_data = ReadTestFileToString(file_name);
+
+ // Read the certificate chain from the test file, which is comprised of PEM
+ // blocks titled "CERTIFICATE".
+ std::vector<std::string> pem_headers;
+ pem_headers.push_back("CERTIFICATE");
+
+ std::vector<std::string> certs;
+ net::PEMTokenizer pem_tokenizer(file_data, pem_headers);
+ while (pem_tokenizer.GetNext())
+ certs.push_back(pem_tokenizer.data());
+
+ return certs;
+}
+
+// Reads a test chain from |file_name|, and asserts that verifying it as a Cast
+// device certificate results in |expected_result|.
+//
+// * |expected_policy| - The policy that should have been identified for the
+// device certificate.
+// * |time| - The timestamp to use when verifying the certificate.
+void RunTest(TestResult expected_result,
+ const std::string& expected_common_name,
+ CastDeviceCertPolicy expected_policy,
+ const std::string& file_name,
+ const base::Time::Exploded& time) {
+ auto certs = ReadCertificateChainFromFile(file_name);
+
+ scoped_ptr<CertVerificationContext> context;
+ CastDeviceCertPolicy policy;
+ bool result = VerifyDeviceCert(certs, time, &context, &policy);
+
+ if (expected_result == RESULT_SUCCESS) {
+ ASSERT_TRUE(result);
+ EXPECT_EQ(expected_policy, policy);
+ ASSERT_TRUE(context.get());
+
+ // Test that the context is good.
+ EXPECT_EQ(expected_common_name, context->GetCommonName());
+
+ // Test that an invalid signature fails.
+
+ EXPECT_FALSE(
+ context->VerifySignatureOverData("bogus signature", "bogus data"));
+
+ // Note that testing that a correct signature succeeds would be a natural
+ // test to follow with, but we don't have signed data for these device
+ // certificates to use. The success case is instead tested separately.
+ } else {
+ ASSERT_FALSE(result);
+ }
+}
+
+// Creates a time in UTC at midnight.
+base::Time::Exploded CreateDate(int year, int month, int day) {
+ base::Time::Exploded time = {0};
+ time.year = year;
+ time.month = month;
+ time.day_of_month = day;
+ return time;
+}
+
+// Returns 2016-04-01 00:00:00 UTC.
+//
+// This is a time when most of the test certificate paths are
+// valid.
+base::Time::Exploded AprilFirst2016() {
+ return CreateDate(2016, 4, 1);
+}
+
+// Returns 2015-01-01 00:00:00 UTC.
+base::Time::Exploded JanuaryFirst2015() {
+ return CreateDate(2015, 1, 1);
+}
+
+// Returns 2040-03-01 00:00:00 UTC.
+//
+// This is so far in the future that the test chains in this unit-test
+// should all be invalid.
+base::Time::Exploded MarchFirst2040() {
+ return CreateDate(2040, 3, 1);
+}
+
+// Tests verifying a valid certificate chain of length 2:
+//
+// 0: 2ZZBG9 FA8FCA3EF91A
+// 1: Eureka Gen1 ICA
+//
+// Chains to trust anchor:
+// Eureka Root CA (not included)
+TEST(VerifyCastDeviceCertTest, ChromecastGen1) {
+ RunTest(RESULT_SUCCESS, "2ZZBG9 FA8FCA3EF91A", CastDeviceCertPolicy::NONE,
+ "chromecast_gen1.pem", AprilFirst2016());
+}
+
+// Tests verifying a valid certificate chain of length 2:
+//
+// 0: 2ZZBG9 FA8FCA3EF91A
+// 1: Eureka Gen1 ICA
+//
+// Chains to trust anchor:
+// Cast Root CA (not included)
+TEST(VerifyCastDeviceCertTest, ChromecastGen1Reissue) {
+ RunTest(RESULT_SUCCESS, "2ZZBG9 FA8FCA3EF91A", CastDeviceCertPolicy::NONE,
+ "chromecast_gen1_reissue.pem", AprilFirst2016());
+}
+
+// Tests verifying a valid certificate chain of length 2:
+//
+// 0: 3ZZAK6 FA8FCA3F0D35
+// 1: Chromecast ICA 3
+//
+// Chains to trust anchor:
+// Cast Root CA (not included)
+TEST(VerifyCastDeviceCertTest, ChromecastGen2) {
+ RunTest(RESULT_SUCCESS, "3ZZAK6 FA8FCA3F0D35", CastDeviceCertPolicy::NONE,
+ "chromecast_gen2.pem", AprilFirst2016());
+}
+
+// Tests verifying a valid certificate chain of length 3:
+//
+// 0: -6394818897508095075
+// 1: Asus fugu Cast ICA
+// 2: Widevine Cast Subroot
+//
+// Chains to trust anchor:
+// Cast Root CA (not included)
+TEST(VerifyCastDeviceCertTest, Fugu) {
+ RunTest(RESULT_SUCCESS, "-6394818897508095075", CastDeviceCertPolicy::NONE,
+ "fugu.pem", AprilFirst2016());
+}
+
+// Tests verifying an invalid certificate chain of length 1:
+//
+// 0: Cast Test Untrusted Device
+//
+// Chains to:
+// Cast Test Untrusted ICA (not included)
+//
+// This is invalid because it does not chain to a trust anchor.
+TEST(VerifyCastDeviceCertTest, Unchained) {
+ RunTest(RESULT_FAIL, "", CastDeviceCertPolicy::NONE, "unchained.pem",
+ AprilFirst2016());
+}
+
+// Tests verifying one of the self-signed trust anchors (chain of length 1):
+//
+// 0: Cast Root CA
+//
+// Chains to trust anchor:
+// Cast Root CA
+//
+// Although this is a valid and trusted certificate (it is one of the
+// trust anchors after all) it fails the test as it is not a *device
+// certificate*.
+TEST(VerifyCastDeviceCertTest, CastRootCa) {
+ RunTest(RESULT_FAIL, "", CastDeviceCertPolicy::NONE, "cast_root_ca.pem",
+ AprilFirst2016());
+}
+
+// Tests verifying a valid certificate chain of length 2:
+//
+// 0: 4ZZDZJ FA8FCA7EFE3C
+// 1: Chromecast ICA 4 (Audio)
+//
+// Chains to trust anchor:
+// Cast Root CA (not included)
+//
+// This device certificate has a policy that means it is valid only for audio
+// devices.
+TEST(VerifyCastDeviceCertTest, ChromecastAudio) {
+ RunTest(RESULT_SUCCESS, "4ZZDZJ FA8FCA7EFE3C",
+ CastDeviceCertPolicy::AUDIO_ONLY, "chromecast_audio.pem",
+ AprilFirst2016());
+}
+
+// Tests verifying a valid certificate chain of length 3:
+//
+// 0: MediaTek Audio Dev Test
+// 1: MediaTek Audio Dev Model
+// 2: Cast Audio Dev Root CA
+//
+// Chains to trust anchor:
+// Cast Root CA (not included)
+//
+// This device certificate has a policy that means it is valid only for audio
+// devices.
+TEST(VerifyCastDeviceCertTest, MtkAudioDev) {
+ RunTest(RESULT_SUCCESS, "MediaTek Audio Dev Test",
+ CastDeviceCertPolicy::AUDIO_ONLY, "mtk_audio_dev.pem",
+ JanuaryFirst2015());
+}
+
+// Tests verifying a valid certificate chain of length 2:
+//
+// 0: 9V0000VB FA8FCA784D01
+// 1: Cast TV ICA (Vizio)
+//
+// Chains to trust anchor:
+// Cast Root CA (not included)
+TEST(VerifyCastDeviceCertTest, Vizio) {
+ RunTest(RESULT_SUCCESS, "9V0000VB FA8FCA784D01", CastDeviceCertPolicy::NONE,
+ "vizio.pem", AprilFirst2016());
+}
+
+// Tests verifying a valid certificate chain of length 2 using a date that
+// precedes its notBefore. Must fail.
+TEST(VerifyCastDeviceCertTest, ChromecastGen2NotValidBefore) {
+ RunTest(RESULT_FAIL, "", CastDeviceCertPolicy::NONE, "chromecast_gen2.pem",
+ JanuaryFirst2015());
+}
+
+// Tests verifying a valid certificate chain of length 2 using a date that
+// is after its notAfter. Must fail.
+TEST(VerifyCastDeviceCertTest, ChromecastGen2NotValidAfter) {
+ RunTest(RESULT_FAIL, "", CastDeviceCertPolicy::NONE, "chromecast_gen2.pem",
+ MarchFirst2040());
+}
+
+// Tests verifying a valid certificate chain of length 3:
+//
+// 0: Audio Reference Dev Test
+// 1: Audio Reference Dev Model
+// 2: Cast Audio Dev Root CA
+//
+// Chains to trust anchor:
+// Cast Root CA (not included)
+//
+// This device certificate has a policy that means it is valid only for audio
+// devices.
+TEST(VerifyCastDeviceCertTest, AudioRefDevTestChain3) {
+ RunTest(RESULT_SUCCESS, "Audio Reference Dev Test",
+ CastDeviceCertPolicy::AUDIO_ONLY, "audio_ref_dev_test_chain_3.pem",
+ AprilFirst2016());
+}
+
+// This duplicates the certificate verification from the test above, but then
+// additionally uses the context to verify some data.
+TEST(VerifyCastDeviceCertTest, AudioRefDevTestChain3VerifySignedData) {
+ auto certs = ReadCertificateChainFromFile("audio_ref_dev_test_chain_3.pem");
+
+ scoped_ptr<CertVerificationContext> context;
+ CastDeviceCertPolicy policy;
+ ASSERT_TRUE(VerifyDeviceCert(certs, AprilFirst2016(), &context, &policy));
+
+ unsigned char kData[] = {
+ 0x5f, 0x76, 0x0d, 0xc8, 0x4b, 0xe7, 0x6e, 0xcb, 0x31, 0x58, 0xca, 0xd3,
+ 0x7d, 0x23, 0x55, 0xbe, 0x8d, 0x52, 0x87, 0x83, 0x27, 0x52, 0x78, 0xfa,
+ 0xa6, 0xdd, 0xdf, 0x13, 0x00, 0x51, 0x57, 0x6a, 0x83, 0x15, 0xcc, 0xc5,
+ 0xb2, 0x5c, 0xdf, 0xe6, 0x81, 0xdc, 0x13, 0x58, 0x7b, 0x94, 0x0f, 0x69,
+ 0xcc, 0xdf, 0x68, 0x41, 0x8a, 0x95, 0xe2, 0xcd, 0xf8, 0xde, 0x0f, 0x2f,
+ 0x30, 0xcf, 0x73, 0xbf, 0x37, 0x52, 0x87, 0x23, 0xd7, 0xbe, 0xba, 0x7c,
+ 0xde, 0x50, 0xd3, 0x77, 0x9c, 0x06, 0x82, 0x28, 0x67, 0xc1, 0x1a, 0xf5,
+ 0x8a, 0xa0, 0xf2, 0x32, 0x09, 0x95, 0x41, 0x41, 0x93, 0x8e, 0x62, 0xaa,
+ 0xf3, 0xe3, 0x22, 0x17, 0x43, 0x94, 0x9b, 0x63, 0xfa, 0x68, 0x20, 0x69,
+ 0x38, 0xf6, 0x75, 0x6c, 0xe0, 0x3b, 0xe0, 0x8d, 0x63, 0xac, 0x7f, 0xe3,
+ 0x09, 0xd8, 0xde, 0x91, 0xc8, 0x1e, 0x07, 0x4a, 0xb2, 0x1e, 0xe1, 0xe3,
+ 0xf4, 0x4d, 0x3e, 0x8a, 0xf4, 0xf8, 0x83, 0x39, 0x2b, 0x50, 0x98, 0x61,
+ 0x91, 0x50, 0x00, 0x34, 0x57, 0xd2, 0x0d, 0xf7, 0xfa, 0xc9, 0xcc, 0xd9,
+ 0x7a, 0x3d, 0x39, 0x7a, 0x1a, 0xbd, 0xf8, 0xbe, 0x65, 0xb6, 0xea, 0x4e,
+ 0x86, 0x74, 0xdd, 0x51, 0x74, 0x6e, 0xa6, 0x7f, 0x14, 0x6c, 0x6a, 0x46,
+ 0xb8, 0xaf, 0xcd, 0x6c, 0x78, 0x43, 0x76, 0x47, 0x5b, 0xdc, 0xb6, 0xf6,
+ 0x4d, 0x1b, 0xe0, 0xb5, 0xf9, 0xa2, 0xb8, 0x26, 0x3f, 0x3f, 0xb8, 0x80,
+ 0xed, 0xce, 0xfd, 0x0e, 0xcb, 0x48, 0x7a, 0x3b, 0xdf, 0x92, 0x44, 0x04,
+ 0x81, 0xe4, 0xd3, 0x1e, 0x07, 0x9b, 0x02, 0xae, 0x05, 0x5a, 0x11, 0xf2,
+ 0xc2, 0x75, 0x85, 0xd5, 0xf1, 0x53, 0x4c, 0x09, 0xd0, 0x99, 0xf8, 0x3e,
+ 0xf6, 0x24, 0x46, 0xae, 0x83, 0x35, 0x3e, 0x6c, 0x8c, 0x2a, 0x9f, 0x1c,
+ 0x5b, 0xfb, 0x89, 0x56};
+
+ unsigned char kSha1Signature[] = {
+ 0x52, 0x56, 0xcd, 0x53, 0xfa, 0xd9, 0x44, 0x31, 0x00, 0x2e, 0x85, 0x18,
+ 0x56, 0xae, 0xf9, 0xf2, 0x70, 0x16, 0xc9, 0x59, 0x53, 0xc0, 0x17, 0xd9,
+ 0x09, 0x65, 0x75, 0xee, 0xba, 0xc8, 0x0d, 0x06, 0x2e, 0xb7, 0x1b, 0xd0,
+ 0x6a, 0x4d, 0x58, 0xde, 0x8e, 0xbe, 0x92, 0x22, 0x53, 0x19, 0xbf, 0x74,
+ 0x8f, 0xb8, 0xfc, 0x3c, 0x9b, 0x42, 0x14, 0x7d, 0xe1, 0xfc, 0xa3, 0x71,
+ 0x91, 0x6c, 0x5d, 0x28, 0x69, 0x8d, 0xd2, 0xde, 0xd1, 0x8f, 0xac, 0x6d,
+ 0xf6, 0x48, 0xd8, 0x6f, 0x0e, 0xc9, 0x0a, 0xfa, 0xde, 0x20, 0xe0, 0x9d,
+ 0x7a, 0xf8, 0x30, 0xa8, 0xd4, 0x79, 0x15, 0x63, 0xfb, 0x97, 0xa9, 0xef,
+ 0x9f, 0x9c, 0xac, 0x16, 0xba, 0x1b, 0x2c, 0x14, 0xb4, 0xa4, 0x54, 0x5e,
+ 0xec, 0x04, 0x10, 0x84, 0xc2, 0xa0, 0xd9, 0x6f, 0x05, 0xd4, 0x09, 0x8c,
+ 0x85, 0xe9, 0x7a, 0xd1, 0x5a, 0xa3, 0x70, 0x00, 0x30, 0x9b, 0x19, 0x44,
+ 0x2a, 0x90, 0x7a, 0xcd, 0x91, 0x94, 0x90, 0x66, 0xf9, 0x2e, 0x5e, 0x43,
+ 0x27, 0x33, 0x2c, 0x45, 0xa7, 0xe2, 0x3a, 0x6d, 0xc9, 0x44, 0x58, 0x39,
+ 0x45, 0xcb, 0xbd, 0x2f, 0xc5, 0xb4, 0x08, 0x41, 0x4d, 0x45, 0x67, 0x55,
+ 0x0d, 0x43, 0x3c, 0xb6, 0x81, 0xbb, 0xb4, 0x34, 0x07, 0x10, 0x28, 0x17,
+ 0xc2, 0xad, 0x40, 0x3b, 0xaf, 0xcb, 0xc0, 0xf6, 0x9d, 0x0e, 0x9b, 0xca,
+ 0x2b, 0x20, 0xdf, 0xd0, 0xa3, 0xbe, 0xea, 0x3e, 0xe0, 0x82, 0x7b, 0x93,
+ 0xfd, 0x9c, 0xaf, 0x97, 0x00, 0x05, 0x44, 0x91, 0x73, 0x68, 0x92, 0x3a,
+ 0x8b, 0xbc, 0x0e, 0x96, 0x5e, 0x92, 0x98, 0x70, 0xab, 0xaa, 0x6e, 0x9a,
+ 0x8e, 0xb0, 0xf4, 0x92, 0xc5, 0xa0, 0xa0, 0x4b, 0xb3, 0xd5, 0x44, 0x99,
+ 0x8e, 0xa1, 0xd1, 0x8f, 0xe3, 0xac, 0x71, 0x1e, 0x3f, 0xc2, 0xfd, 0x0a,
+ 0x57, 0xed, 0xea, 0x04};
+
+ unsigned char kSha256Signature[] = {
+ 0x8d, 0x4b, 0x45, 0xdc, 0x13, 0x0a, 0x79, 0xae, 0x4b, 0x83, 0x99, 0x39,
+ 0x2e, 0x58, 0x04, 0x98, 0x7f, 0x0d, 0xc6, 0x57, 0x7f, 0x6c, 0xd7, 0xf8,
+ 0x6f, 0x47, 0xd8, 0xb7, 0xaa, 0x07, 0x29, 0x69, 0x0e, 0x05, 0x3b, 0x8c,
+ 0x94, 0x53, 0xb2, 0x76, 0x51, 0x23, 0xdc, 0x85, 0xd2, 0x13, 0x37, 0x5e,
+ 0x43, 0x9a, 0x4f, 0x38, 0xdd, 0xfc, 0xbb, 0xdf, 0xf0, 0x74, 0xf5, 0x42,
+ 0xa1, 0xaa, 0x60, 0x16, 0x24, 0xc3, 0xcd, 0xf3, 0xd2, 0x8e, 0xa9, 0x39,
+ 0xc5, 0x85, 0x99, 0xa2, 0x1b, 0xf5, 0x6d, 0xbd, 0x29, 0x77, 0xad, 0xd4,
+ 0x3e, 0xbd, 0xa8, 0x34, 0xb3, 0x0a, 0x4f, 0x61, 0xc0, 0x39, 0x9e, 0x50,
+ 0x42, 0x22, 0x58, 0xce, 0xb7, 0x74, 0x15, 0x1e, 0xdf, 0x55, 0x8b, 0x9b,
+ 0x64, 0x07, 0x4a, 0xc6, 0x71, 0x34, 0x57, 0x17, 0x9a, 0x96, 0xdc, 0x87,
+ 0x38, 0x24, 0xb6, 0x48, 0xde, 0x20, 0xa3, 0xb9, 0xae, 0x5f, 0x4e, 0xb4,
+ 0x69, 0xe8, 0x24, 0x0f, 0xca, 0xa4, 0x94, 0x14, 0x97, 0x7e, 0xba, 0x2c,
+ 0x18, 0x59, 0x13, 0xca, 0x7e, 0x0f, 0x8f, 0x83, 0xbf, 0x29, 0x63, 0x15,
+ 0x20, 0xd5, 0x9a, 0xf7, 0xb8, 0x3f, 0xbf, 0x1d, 0x5b, 0xad, 0x13, 0x88,
+ 0x29, 0x8f, 0x5f, 0x31, 0x42, 0x5d, 0x8e, 0x69, 0xc0, 0xc7, 0x76, 0xe4,
+ 0xee, 0x04, 0x22, 0x23, 0x73, 0xac, 0x14, 0xb4, 0xc1, 0x20, 0x44, 0x80,
+ 0x43, 0x41, 0x58, 0x24, 0x1e, 0x2e, 0xcb, 0xa6, 0x97, 0x41, 0x94, 0xaa,
+ 0x6a, 0xbe, 0x55, 0x28, 0x8b, 0xe1, 0x97, 0xd5, 0x1b, 0xb8, 0x9b, 0x4b,
+ 0xd6, 0xfd, 0x2c, 0x59, 0xcd, 0x8b, 0x6c, 0xf2, 0x1e, 0x31, 0xef, 0xe8,
+ 0xb2, 0xcb, 0xaf, 0x4c, 0xfe, 0xea, 0xec, 0x63, 0xb7, 0xf3, 0x3c, 0x2a,
+ 0x15, 0x0e, 0xf0, 0x4e, 0x4a, 0x10, 0x99, 0x62, 0xdd, 0xf4, 0x32, 0x6b,
+ 0xf6, 0x23, 0x12, 0x90};
+
+ EXPECT_TRUE(context->VerifySignatureOverData(CreateString(kSha1Signature),
+ CreateString(kData)));
+
+ // Verify using a VALID SHA-256 signature. This only fails because it is
+ // expecting a SHA-1 signature not a SHA-256 signature.
+ EXPECT_FALSE(context->VerifySignatureOverData(CreateString(kSha256Signature),
+ CreateString(kData)));
+}
+
+// ------------------------------------------------------
+// Valid signature using 1024-bit RSA key
+// ------------------------------------------------------
+
+// This test vector comes from the NIST test vectors (pkcs1v15sign-vectors.txt),
+// PKCS#1 v1.5 Signature Example 1.2.
+//
+// It is a valid signature using a 1024 bit key and SHA-1.
+
+const uint8_t kEx1Message[] = {
+ 0x85, 0x13, 0x84, 0xcd, 0xfe, 0x81, 0x9c, 0x22, 0xed, 0x6c, 0x4c,
+ 0xcb, 0x30, 0xda, 0xeb, 0x5c, 0xf0, 0x59, 0xbc, 0x8e, 0x11, 0x66,
+ 0xb7, 0xe3, 0x53, 0x0c, 0x4c, 0x23, 0x3e, 0x2b, 0x5f, 0x8f, 0x71,
+ 0xa1, 0xcc, 0xa5, 0x82, 0xd4, 0x3e, 0xcc, 0x72, 0xb1, 0xbc, 0xa1,
+ 0x6d, 0xfc, 0x70, 0x13, 0x22, 0x6b, 0x9e,
+};
+
+const uint8_t kEx1Signature[] = {
+ 0x84, 0xfd, 0x2c, 0xe7, 0x34, 0xec, 0x1d, 0xa8, 0x28, 0xd0, 0xf1, 0x5b,
+ 0xf4, 0x9a, 0x87, 0x07, 0xc1, 0x5d, 0x05, 0x94, 0x81, 0x36, 0xde, 0x53,
+ 0x7a, 0x3d, 0xb4, 0x21, 0x38, 0x41, 0x67, 0xc8, 0x6f, 0xae, 0x02, 0x25,
+ 0x87, 0xee, 0x9e, 0x13, 0x7d, 0xae, 0xe7, 0x54, 0x73, 0x82, 0x62, 0x93,
+ 0x2d, 0x27, 0x1c, 0x74, 0x4c, 0x6d, 0x3a, 0x18, 0x9a, 0xd4, 0x31, 0x1b,
+ 0xdb, 0x02, 0x04, 0x92, 0xe3, 0x22, 0xfb, 0xdd, 0xc4, 0x04, 0x06, 0xea,
+ 0x86, 0x0d, 0x4e, 0x8e, 0xa2, 0xa4, 0x08, 0x4a, 0xa9, 0x8b, 0x96, 0x22,
+ 0xa4, 0x46, 0x75, 0x6f, 0xdb, 0x74, 0x0d, 0xdb, 0x3d, 0x91, 0xdb, 0x76,
+ 0x70, 0xe2, 0x11, 0x66, 0x1b, 0xbf, 0x87, 0x09, 0xb1, 0x1c, 0x08, 0xa7,
+ 0x07, 0x71, 0x42, 0x2d, 0x1a, 0x12, 0xde, 0xf2, 0x9f, 0x06, 0x88, 0xa1,
+ 0x92, 0xae, 0xbd, 0x89, 0xe0, 0xf8, 0x96, 0xf8,
+};
+
+const uint8_t kEx1PublicKeySpki[] = {
+ 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81,
+ 0x89, 0x02, 0x81, 0x81, 0x00, 0xa5, 0x6e, 0x4a, 0x0e, 0x70, 0x10, 0x17,
+ 0x58, 0x9a, 0x51, 0x87, 0xdc, 0x7e, 0xa8, 0x41, 0xd1, 0x56, 0xf2, 0xec,
+ 0x0e, 0x36, 0xad, 0x52, 0xa4, 0x4d, 0xfe, 0xb1, 0xe6, 0x1f, 0x7a, 0xd9,
+ 0x91, 0xd8, 0xc5, 0x10, 0x56, 0xff, 0xed, 0xb1, 0x62, 0xb4, 0xc0, 0xf2,
+ 0x83, 0xa1, 0x2a, 0x88, 0xa3, 0x94, 0xdf, 0xf5, 0x26, 0xab, 0x72, 0x91,
+ 0xcb, 0xb3, 0x07, 0xce, 0xab, 0xfc, 0xe0, 0xb1, 0xdf, 0xd5, 0xcd, 0x95,
+ 0x08, 0x09, 0x6d, 0x5b, 0x2b, 0x8b, 0x6d, 0xf5, 0xd6, 0x71, 0xef, 0x63,
+ 0x77, 0xc0, 0x92, 0x1c, 0xb2, 0x3c, 0x27, 0x0a, 0x70, 0xe2, 0x59, 0x8e,
+ 0x6f, 0xf8, 0x9d, 0x19, 0xf1, 0x05, 0xac, 0xc2, 0xd3, 0xf0, 0xcb, 0x35,
+ 0xf2, 0x92, 0x80, 0xe1, 0x38, 0x6b, 0x6f, 0x64, 0xc4, 0xef, 0x22, 0xe1,
+ 0xe1, 0xf2, 0x0d, 0x0c, 0xe8, 0xcf, 0xfb, 0x22, 0x49, 0xbd, 0x9a, 0x21,
+ 0x37, 0x02, 0x03, 0x01, 0x00, 0x01,
+};
+
+// Tests that a valid signature fails, because it uses a 1024-bit RSA key (too
+// weak).
+TEST(VerifyCastDeviceCertTest, VerifySignature1024BitRsa) {
+ auto context =
+ CertVerificationContextImplForTest(CreateString(kEx1PublicKeySpki));
+
+ EXPECT_FALSE(context->VerifySignatureOverData(CreateString(kEx1Signature),
+ CreateString(kEx1Message)));
+}
+
+// ------------------------------------------------------
+// Valid signature using 2048-bit RSA key
+// ------------------------------------------------------
+
+// This test vector was generated (using WebCrypto). It is a valid signature
+// using a 2048-bit RSA key, RSASSA PKCS#1 v1.5 with SHA-1.
+
+const uint8_t kEx2Message[] = {
+ // "hello"
+ 0x68, 0x65, 0x6c, 0x6c, 0x6f,
+};
+
+const uint8_t kEx2Signature[] = {
+ 0xc1, 0x21, 0x84, 0xe1, 0x62, 0x0e, 0x59, 0x52, 0x5b, 0xa4, 0x10, 0x1e,
+ 0x11, 0x80, 0x5b, 0x9e, 0xcb, 0xa0, 0x20, 0x78, 0x29, 0xfc, 0xc0, 0x9a,
+ 0xd9, 0x48, 0x90, 0x81, 0x03, 0xa9, 0xc0, 0x2f, 0x0a, 0xc4, 0x20, 0x34,
+ 0xb5, 0xdb, 0x19, 0x04, 0xec, 0x94, 0x9b, 0xba, 0x48, 0x43, 0xf3, 0x5a,
+ 0x15, 0x56, 0xfc, 0x4a, 0x87, 0x79, 0xf8, 0x50, 0xff, 0x5d, 0x66, 0x25,
+ 0xdc, 0xa5, 0xd8, 0xe8, 0x9f, 0x5a, 0x73, 0x79, 0x6f, 0x5d, 0x99, 0xe0,
+ 0xd5, 0xa5, 0x84, 0x49, 0x20, 0x3c, 0xe2, 0xa3, 0xd0, 0x69, 0x31, 0x2c,
+ 0x13, 0xaf, 0x15, 0xd9, 0x10, 0x0d, 0x6f, 0xdd, 0x9d, 0x62, 0x5d, 0x7b,
+ 0xe1, 0x1a, 0x48, 0x59, 0xaf, 0xf7, 0xbe, 0x87, 0x92, 0x60, 0x5d, 0x1a,
+ 0xb5, 0xfe, 0x27, 0x38, 0x02, 0x20, 0xe9, 0xaf, 0x04, 0x57, 0xd3, 0x3b,
+ 0x70, 0x04, 0x63, 0x5b, 0xc6, 0x5d, 0x83, 0xe2, 0xaf, 0x02, 0xb4, 0xef,
+ 0x1c, 0x33, 0x54, 0x38, 0xf8, 0xb5, 0x19, 0xa8, 0x88, 0xdd, 0x1d, 0x96,
+ 0x1c, 0x5e, 0x54, 0x80, 0xde, 0x7b, 0xb6, 0x29, 0xb8, 0x6b, 0xea, 0x47,
+ 0xe5, 0xf1, 0x7e, 0xed, 0xe1, 0x91, 0xc8, 0xb8, 0x54, 0xd9, 0x1e, 0xfd,
+ 0x07, 0x10, 0xbd, 0xa9, 0xd4, 0x93, 0x5e, 0x65, 0x8b, 0x6b, 0x46, 0x93,
+ 0x4b, 0x60, 0x2a, 0x26, 0xf0, 0x1b, 0x4e, 0xca, 0x04, 0x82, 0xc0, 0x8d,
+ 0xb1, 0xa5, 0xa8, 0x70, 0xdd, 0x66, 0x68, 0x95, 0x09, 0xb4, 0x85, 0x62,
+ 0xf5, 0x17, 0x04, 0x48, 0xb4, 0x9d, 0x66, 0x2b, 0x25, 0x82, 0x7e, 0x99,
+ 0x3e, 0xa1, 0x11, 0x63, 0xc3, 0xdf, 0x10, 0x20, 0x52, 0x56, 0x32, 0x35,
+ 0xa9, 0x36, 0xde, 0x2a, 0xac, 0x10, 0x0d, 0x75, 0x21, 0xed, 0x5b, 0x38,
+ 0xb6, 0xb5, 0x1e, 0xb5, 0x5b, 0x9a, 0x72, 0xd5, 0xf8, 0x1a, 0xd3, 0x91,
+ 0xb8, 0x29, 0x0e, 0x58,
+};
+
+const uint8_t kEx2PublicKeySpki[] = {
+ 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
+ 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xcf, 0xde, 0xa5,
+ 0x2e, 0x9d, 0x38, 0x62, 0x72, 0x47, 0x84, 0x8f, 0x2e, 0xa5, 0xe3, 0xd6,
+ 0x34, 0xb0, 0xf9, 0x79, 0xa9, 0x10, 0x63, 0xa9, 0x93, 0x5a, 0xa1, 0xb9,
+ 0xa3, 0x03, 0xd3, 0xcd, 0x9d, 0x84, 0x7d, 0xb6, 0x92, 0x47, 0xb4, 0x7d,
+ 0x4a, 0xe8, 0x3a, 0x4b, 0xc5, 0xf6, 0x35, 0x6f, 0x18, 0x72, 0xf3, 0xbc,
+ 0xd2, 0x1c, 0x7a, 0xd2, 0xe5, 0xdf, 0xcf, 0xb9, 0xac, 0x28, 0xd3, 0x49,
+ 0x2a, 0x4f, 0x08, 0x62, 0xb9, 0xf1, 0xaa, 0x3d, 0x76, 0xe3, 0xa9, 0x96,
+ 0x32, 0x24, 0x94, 0x9e, 0x88, 0xf8, 0x5e, 0xc3, 0x3c, 0x14, 0x32, 0x86,
+ 0x72, 0xa2, 0x34, 0x3d, 0x41, 0xd0, 0xb2, 0x01, 0x99, 0x01, 0xf3, 0x93,
+ 0xa3, 0x76, 0x5a, 0xff, 0x42, 0x28, 0x54, 0xe0, 0xcc, 0x4c, 0xcd, 0x2d,
+ 0x3b, 0x0b, 0x47, 0xcc, 0xc2, 0x75, 0x02, 0xc1, 0xb7, 0x0b, 0x37, 0x65,
+ 0xe6, 0x0d, 0xe4, 0xc3, 0x85, 0x86, 0x29, 0x3c, 0x77, 0xce, 0xb0, 0x34,
+ 0xa9, 0x03, 0xe9, 0x13, 0xbe, 0x97, 0x1e, 0xfd, 0xeb, 0x0d, 0x60, 0xc2,
+ 0xb3, 0x19, 0xa1, 0x75, 0x72, 0x57, 0x3f, 0x5d, 0x0e, 0x75, 0xac, 0x10,
+ 0x96, 0xad, 0x95, 0x67, 0x9f, 0xa2, 0x84, 0x15, 0x6a, 0x61, 0xb1, 0x47,
+ 0xd1, 0x24, 0x78, 0xb4, 0x40, 0x2b, 0xc3, 0x5c, 0x73, 0xd4, 0xc1, 0x8d,
+ 0x12, 0xf1, 0x3f, 0xb4, 0x93, 0x17, 0xfe, 0x5d, 0xbf, 0x39, 0xf2, 0x45,
+ 0xf9, 0xcf, 0x38, 0x44, 0x40, 0x5b, 0x47, 0x2a, 0xbf, 0xb9, 0xac, 0xa6,
+ 0x14, 0xb6, 0x1b, 0xe3, 0xa8, 0x14, 0xf8, 0xfe, 0x47, 0x67, 0xea, 0x90,
+ 0x51, 0x12, 0xcf, 0x5e, 0x28, 0xec, 0x92, 0x83, 0x7c, 0xc6, 0x29, 0x9f,
+ 0x12, 0x29, 0x88, 0x49, 0xf7, 0xb7, 0xed, 0x5e, 0x3a, 0x78, 0xd6, 0x8a,
+ 0xba, 0x42, 0x6e, 0x0a, 0xf4, 0x0d, 0xc1, 0xc0, 0x8f, 0xdb, 0x26, 0x41,
+ 0x57, 0x02, 0x03, 0x01, 0x00, 0x01,
+};
+
+// Tests that a valid signature using 2048-bit key succeeds.
+TEST(VerifyCastDeviceCertTest, VerifySignature2048BitRsa) {
+ auto context =
+ CertVerificationContextImplForTest(CreateString(kEx2PublicKeySpki));
+
+ EXPECT_TRUE(context->VerifySignatureOverData(CreateString(kEx2Signature),
+ CreateString(kEx2Message)));
+}
+
+} // namespace
+
+} // namespace cast_crypto
+
+} // namespace api
+
+} // namespace extensions
diff --git a/chromium/extensions/common/common_manifest_handlers.cc b/chromium/extensions/common/common_manifest_handlers.cc
new file mode 100644
index 00000000000..95293215947
--- /dev/null
+++ b/chromium/extensions/common/common_manifest_handlers.cc
@@ -0,0 +1,62 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/common_manifest_handlers.h"
+
+#include "extensions/common/api/bluetooth/bluetooth_manifest_handler.h"
+#include "extensions/common/api/declarative/declarative_manifest_handler.h"
+#include "extensions/common/api/printer_provider/usb_printer_manifest_handler.h"
+#include "extensions/common/api/sockets/sockets_manifest_handler.h"
+#include "extensions/common/manifest_handler.h"
+#include "extensions/common/manifest_handlers/background_info.h"
+#include "extensions/common/manifest_handlers/content_capabilities_handler.h"
+#include "extensions/common/manifest_handlers/csp_info.h"
+#include "extensions/common/manifest_handlers/default_locale_handler.h"
+#include "extensions/common/manifest_handlers/externally_connectable.h"
+#include "extensions/common/manifest_handlers/file_handler_info.h"
+#include "extensions/common/manifest_handlers/icons_handler.h"
+#include "extensions/common/manifest_handlers/incognito_info.h"
+#include "extensions/common/manifest_handlers/kiosk_mode_info.h"
+#include "extensions/common/manifest_handlers/launcher_page_info.h"
+#include "extensions/common/manifest_handlers/mime_types_handler.h"
+#include "extensions/common/manifest_handlers/nacl_modules_handler.h"
+#include "extensions/common/manifest_handlers/oauth2_manifest_handler.h"
+#include "extensions/common/manifest_handlers/offline_enabled_info.h"
+#include "extensions/common/manifest_handlers/sandboxed_page_info.h"
+#include "extensions/common/manifest_handlers/shared_module_info.h"
+#include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
+#include "extensions/common/manifest_handlers/webview_info.h"
+
+namespace extensions {
+
+void RegisterCommonManifestHandlers() {
+ DCHECK(!ManifestHandler::IsRegistrationFinalized());
+ (new BackgroundManifestHandler)->Register();
+ (new BluetoothManifestHandler)->Register();
+ (new ContentCapabilitiesHandler)->Register();
+ (new CSPHandler(false))->Register();
+ (new CSPHandler(true))->Register();
+ (new DeclarativeManifestHandler)->Register();
+ (new DefaultLocaleHandler)->Register();
+ (new ExternallyConnectableHandler)->Register();
+ (new FileHandlersParser)->Register();
+ (new IconsHandler)->Register();
+ (new IncognitoHandler)->Register();
+ (new KioskModeHandler)->Register();
+ (new LauncherPageHandler)->Register();
+ (new MimeTypesHandlerParser)->Register();
+#if !defined(DISABLE_NACL)
+ (new NaClModulesHandler)->Register();
+#endif
+ (new OAuth2ManifestHandler)->Register();
+ (new OfflineEnabledHandler)->Register();
+ (new SandboxedPageHandler)->Register();
+ (new SharedModuleHandler)->Register();
+ (new SocketsManifestHandler)->Register();
+ (new UsbPrinterManifestHandler)->Register();
+ (new WebAccessibleResourcesHandler)->Register();
+ (new WebviewHandler)->Register();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/common_manifest_handlers.h b/chromium/extensions/common/common_manifest_handlers.h
new file mode 100644
index 00000000000..4f1d7cc8f6b
--- /dev/null
+++ b/chromium/extensions/common/common_manifest_handlers.h
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_COMMON_MANIFEST_HANDLERS_H_
+#define EXTENSIONS_COMMON_COMMON_MANIFEST_HANDLERS_H_
+
+namespace extensions {
+
+// Registers manifest handlers used by all embedders of the extensions system.
+// Should be called once in each process. Embedders may also wish to register
+// their own set of manifest handlers, such as chrome_manifest_handlers.cc.
+void RegisterCommonManifestHandlers();
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_COMMON_MANIFEST_HANDLERS_H_
diff --git a/chromium/extensions/common/csp_validator.cc b/chromium/extensions/common/csp_validator.cc
new file mode 100644
index 00000000000..22a1325396d
--- /dev/null
+++ b/chromium/extensions/common/csp_validator.cc
@@ -0,0 +1,378 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/csp_validator.h"
+
+#include <stddef.h>
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "content/public/common/url_constants.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/install_warning.h"
+#include "extensions/common/manifest_constants.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+
+namespace extensions {
+
+namespace csp_validator {
+
+namespace {
+
+const char kDefaultSrc[] = "default-src";
+const char kScriptSrc[] = "script-src";
+const char kObjectSrc[] = "object-src";
+const char kPluginTypes[] = "plugin-types";
+
+const char kObjectSrcDefaultDirective[] = "object-src 'self';";
+const char kScriptSrcDefaultDirective[] =
+ "script-src 'self' chrome-extension-resource:;";
+
+const char kSandboxDirectiveName[] = "sandbox";
+const char kAllowSameOriginToken[] = "allow-same-origin";
+const char kAllowTopNavigation[] = "allow-top-navigation";
+
+// This is the list of plugin types which are fully sandboxed and are safe to
+// load up in an extension, regardless of the URL they are navigated to.
+const char* const kSandboxedPluginTypes[] = {
+ "application/pdf",
+ "application/x-google-chrome-pdf",
+ "application/x-pnacl"
+};
+
+// List of CSP hash-source prefixes that are accepted. Blink is a bit more
+// lenient, but we only accept standard hashes to be forward-compatible.
+// http://www.w3.org/TR/2015/CR-CSP2-20150721/#hash_algo
+const char* const kHashSourcePrefixes[] = {
+ "'sha256-",
+ "'sha384-",
+ "'sha512-"
+};
+
+struct DirectiveStatus {
+ explicit DirectiveStatus(const char* name)
+ : directive_name(name), seen_in_policy(false) {}
+
+ const char* directive_name;
+ bool seen_in_policy;
+};
+
+// Returns whether |url| starts with |scheme_and_separator| and does not have a
+// too permissive wildcard host name. If |should_check_rcd| is true, then the
+// Public suffix list is used to exclude wildcard TLDs such as "https://*.org".
+bool isNonWildcardTLD(const std::string& url,
+ const std::string& scheme_and_separator,
+ bool should_check_rcd) {
+ if (!base::StartsWith(url, scheme_and_separator,
+ base::CompareCase::SENSITIVE))
+ return false;
+
+ size_t start_of_host = scheme_and_separator.length();
+
+ size_t end_of_host = url.find("/", start_of_host);
+ if (end_of_host == std::string::npos)
+ end_of_host = url.size();
+
+ // Note: It is sufficient to only compare the first character against '*'
+ // because the CSP only allows wildcards at the start of a directive, see
+ // host-source and host-part at http://www.w3.org/TR/CSP2/#source-list-syntax
+ bool is_wildcard_subdomain = end_of_host > start_of_host + 2 &&
+ url[start_of_host] == '*' && url[start_of_host + 1] == '.';
+ if (is_wildcard_subdomain)
+ start_of_host += 2;
+
+ size_t start_of_port = url.rfind(":", end_of_host);
+ // The ":" check at the end of the following condition is used to avoid
+ // treating the last part of an IPv6 address as a port.
+ if (start_of_port > start_of_host && url[start_of_port - 1] != ':') {
+ bool is_valid_port = false;
+ // Do a quick sanity check. The following check could mistakenly flag
+ // ":123456" or ":****" as valid, but that does not matter because the
+ // relaxing CSP directive will just be ignored by Blink.
+ for (size_t i = start_of_port + 1; i < end_of_host; ++i) {
+ is_valid_port = base::IsAsciiDigit(url[i]) || url[i] == '*';
+ if (!is_valid_port)
+ break;
+ }
+ if (is_valid_port)
+ end_of_host = start_of_port;
+ }
+
+ std::string host(url, start_of_host, end_of_host - start_of_host);
+ // Global wildcards are not allowed.
+ if (host.empty() || host.find("*") != std::string::npos)
+ return false;
+
+ if (!is_wildcard_subdomain || !should_check_rcd)
+ return true;
+
+ // Allow *.googleapis.com to be whitelisted for backwards-compatibility.
+ // (crbug.com/409952)
+ if (host == "googleapis.com")
+ return true;
+
+ // Wildcards on subdomains of a TLD are not allowed.
+ size_t registry_length = net::registry_controlled_domains::GetRegistryLength(
+ host,
+ net::registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES,
+ net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+ return registry_length != 0;
+}
+
+// Checks whether the source is a syntactically valid hash.
+bool IsHashSource(const std::string& source) {
+ size_t hash_end = source.length() - 1;
+ if (source.empty() || source[hash_end] != '\'') {
+ return false;
+ }
+
+ for (const char* prefix : kHashSourcePrefixes) {
+ if (base::StartsWith(source, prefix,
+ base::CompareCase::INSENSITIVE_ASCII)) {
+ for (size_t i = strlen(prefix); i < hash_end; ++i) {
+ const char c = source[i];
+ // The hash must be base64-encoded. Do not allow any other characters.
+ if (!base::IsAsciiAlpha(c) && !base::IsAsciiDigit(c) && c != '+' &&
+ c != '/' && c != '=') {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+InstallWarning CSPInstallWarning(const std::string& csp_warning) {
+ return InstallWarning(csp_warning, manifest_keys::kContentSecurityPolicy);
+}
+
+void GetSecureDirectiveValues(const std::string& directive_name,
+ base::StringTokenizer* tokenizer,
+ int options,
+ std::vector<std::string>* sane_csp_parts,
+ std::vector<InstallWarning>* warnings) {
+ sane_csp_parts->push_back(directive_name);
+ while (tokenizer->GetNext()) {
+ std::string source_literal = tokenizer->token();
+ std::string source_lower = base::ToLowerASCII(source_literal);
+ bool is_secure_csp_token = false;
+
+ // We might need to relax this whitelist over time.
+ if (source_lower == "'self'" || source_lower == "'none'" ||
+ source_lower == "http://127.0.0.1" || source_lower == "blob:" ||
+ source_lower == "filesystem:" || source_lower == "http://localhost" ||
+ base::StartsWith(source_lower, "http://127.0.0.1:",
+ base::CompareCase::SENSITIVE) ||
+ base::StartsWith(source_lower, "http://localhost:",
+ base::CompareCase::SENSITIVE) ||
+ isNonWildcardTLD(source_lower, "https://", true) ||
+ isNonWildcardTLD(source_lower, "chrome://", false) ||
+ isNonWildcardTLD(source_lower,
+ std::string(extensions::kExtensionScheme) +
+ url::kStandardSchemeSeparator,
+ false) ||
+ IsHashSource(source_literal) ||
+ base::StartsWith(source_lower, "chrome-extension-resource:",
+ base::CompareCase::SENSITIVE)) {
+ is_secure_csp_token = true;
+ } else if ((options & OPTIONS_ALLOW_UNSAFE_EVAL) &&
+ source_lower == "'unsafe-eval'") {
+ is_secure_csp_token = true;
+ }
+
+ if (is_secure_csp_token) {
+ sane_csp_parts->push_back(source_literal);
+ } else if (warnings) {
+ warnings->push_back(CSPInstallWarning(ErrorUtils::FormatErrorMessage(
+ manifest_errors::kInvalidCSPInsecureValue, source_literal,
+ directive_name)));
+ }
+ }
+ // End of CSP directive that was started at the beginning of this method. If
+ // none of the values are secure, the policy will be empty and default to
+ // 'none', which is secure.
+ sane_csp_parts->back().push_back(';');
+}
+
+// Returns true if |directive_name| matches |status.directive_name|.
+bool UpdateStatus(const std::string& directive_name,
+ base::StringTokenizer* tokenizer,
+ DirectiveStatus* status,
+ int options,
+ std::vector<std::string>* sane_csp_parts,
+ std::vector<InstallWarning>* warnings) {
+ if (directive_name != status->directive_name)
+ return false;
+
+ if (!status->seen_in_policy) {
+ status->seen_in_policy = true;
+ GetSecureDirectiveValues(directive_name, tokenizer, options, sane_csp_parts,
+ warnings);
+ } else {
+ // Don't show any errors for duplicate CSP directives, because it will be
+ // ignored by the CSP parser (http://www.w3.org/TR/CSP2/#policy-parsing).
+ GetSecureDirectiveValues(directive_name, tokenizer, options, sane_csp_parts,
+ NULL);
+ }
+ return true;
+}
+
+// Returns true if the |plugin_type| is one of the fully sandboxed plugin types.
+bool PluginTypeAllowed(const std::string& plugin_type) {
+ for (size_t i = 0; i < arraysize(kSandboxedPluginTypes); ++i) {
+ if (plugin_type == kSandboxedPluginTypes[i])
+ return true;
+ }
+ return false;
+}
+
+// Returns true if the policy is allowed to contain an insecure object-src
+// directive. This requires OPTIONS_ALLOW_INSECURE_OBJECT_SRC to be specified
+// as an option and the plugin-types that can be loaded must be restricted to
+// the set specified in kSandboxedPluginTypes.
+bool AllowedToHaveInsecureObjectSrc(
+ int options,
+ const std::vector<std::string>& directives) {
+ if (!(options & OPTIONS_ALLOW_INSECURE_OBJECT_SRC))
+ return false;
+
+ for (size_t i = 0; i < directives.size(); ++i) {
+ const std::string& input = directives[i];
+ base::StringTokenizer tokenizer(input, " \t\r\n");
+ if (!tokenizer.GetNext())
+ continue;
+ if (!base::LowerCaseEqualsASCII(tokenizer.token(), kPluginTypes))
+ continue;
+ while (tokenizer.GetNext()) {
+ if (!PluginTypeAllowed(tokenizer.token()))
+ return false;
+ }
+ // All listed plugin types are whitelisted.
+ return true;
+ }
+ // plugin-types not specified.
+ return false;
+}
+
+} // namespace
+
+bool ContentSecurityPolicyIsLegal(const std::string& policy) {
+ // We block these characters to prevent HTTP header injection when
+ // representing the content security policy as an HTTP header.
+ const char kBadChars[] = {',', '\r', '\n', '\0'};
+
+ return policy.find_first_of(kBadChars, 0, arraysize(kBadChars)) ==
+ std::string::npos;
+}
+
+std::string SanitizeContentSecurityPolicy(
+ const std::string& policy,
+ int options,
+ std::vector<InstallWarning>* warnings) {
+ // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm.
+ std::vector<std::string> directives = base::SplitString(
+ policy, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+
+ DirectiveStatus default_src_status(kDefaultSrc);
+ DirectiveStatus script_src_status(kScriptSrc);
+ DirectiveStatus object_src_status(kObjectSrc);
+
+ bool allow_insecure_object_src =
+ AllowedToHaveInsecureObjectSrc(options, directives);
+
+ std::vector<std::string> sane_csp_parts;
+ std::vector<InstallWarning> default_src_csp_warnings;
+ for (size_t i = 0; i < directives.size(); ++i) {
+ std::string& input = directives[i];
+ base::StringTokenizer tokenizer(input, " \t\r\n");
+ if (!tokenizer.GetNext())
+ continue;
+
+ std::string directive_name = base::ToLowerASCII(tokenizer.token_piece());
+ if (UpdateStatus(directive_name, &tokenizer, &default_src_status, options,
+ &sane_csp_parts, &default_src_csp_warnings))
+ continue;
+ if (UpdateStatus(directive_name, &tokenizer, &script_src_status, options,
+ &sane_csp_parts, warnings))
+ continue;
+ if (!allow_insecure_object_src &&
+ UpdateStatus(directive_name, &tokenizer, &object_src_status, options,
+ &sane_csp_parts, warnings))
+ continue;
+
+ // Pass the other CSP directives as-is without further validation.
+ sane_csp_parts.push_back(input + ";");
+ }
+
+ if (default_src_status.seen_in_policy) {
+ if (!script_src_status.seen_in_policy ||
+ !object_src_status.seen_in_policy) {
+ // Insecure values in default-src are only relevant if either script-src
+ // or object-src is omitted.
+ if (warnings)
+ warnings->insert(warnings->end(),
+ default_src_csp_warnings.begin(),
+ default_src_csp_warnings.end());
+ }
+ } else {
+ if (!script_src_status.seen_in_policy) {
+ sane_csp_parts.push_back(kScriptSrcDefaultDirective);
+ if (warnings)
+ warnings->push_back(CSPInstallWarning(ErrorUtils::FormatErrorMessage(
+ manifest_errors::kInvalidCSPMissingSecureSrc, kScriptSrc)));
+ }
+ if (!object_src_status.seen_in_policy && !allow_insecure_object_src) {
+ sane_csp_parts.push_back(kObjectSrcDefaultDirective);
+ if (warnings)
+ warnings->push_back(CSPInstallWarning(ErrorUtils::FormatErrorMessage(
+ manifest_errors::kInvalidCSPMissingSecureSrc, kObjectSrc)));
+ }
+ }
+
+ return base::JoinString(sane_csp_parts, " ");
+}
+
+bool ContentSecurityPolicyIsSandboxed(
+ const std::string& policy, Manifest::Type type) {
+ // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm.
+ bool seen_sandbox = false;
+ for (const std::string& input : base::SplitString(
+ policy, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+ base::StringTokenizer tokenizer(input, " \t\r\n");
+ if (!tokenizer.GetNext())
+ continue;
+
+ std::string directive_name = base::ToLowerASCII(tokenizer.token_piece());
+ if (directive_name != kSandboxDirectiveName)
+ continue;
+
+ seen_sandbox = true;
+
+ while (tokenizer.GetNext()) {
+ std::string token = base::ToLowerASCII(tokenizer.token_piece());
+
+ // The same origin token negates the sandboxing.
+ if (token == kAllowSameOriginToken)
+ return false;
+
+ // Platform apps don't allow navigation.
+ if (type == Manifest::TYPE_PLATFORM_APP) {
+ if (token == kAllowTopNavigation)
+ return false;
+ }
+ }
+ }
+
+ return seen_sandbox;
+}
+
+} // namespace csp_validator
+
+} // namespace extensions
diff --git a/chromium/extensions/common/csp_validator.h b/chromium/extensions/common/csp_validator.h
new file mode 100644
index 00000000000..93676b0b8e6
--- /dev/null
+++ b/chromium/extensions/common/csp_validator.h
@@ -0,0 +1,67 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_CSP_VALIDATOR_H_
+#define EXTENSIONS_COMMON_CSP_VALIDATOR_H_
+
+#include <string>
+
+#include "extensions/common/manifest.h"
+
+namespace extensions {
+
+namespace csp_validator {
+
+// Checks whether the given |policy| is legal for use in the extension system.
+// This check just ensures that the policy doesn't contain any characters that
+// will cause problems when we transmit the policy in an HTTP header.
+bool ContentSecurityPolicyIsLegal(const std::string& policy);
+
+// This specifies options for configuring which CSP directives are permitted in
+// extensions.
+enum Options {
+ OPTIONS_NONE = 0,
+ // Allows 'unsafe-eval' to be specified as a source in a directive.
+ OPTIONS_ALLOW_UNSAFE_EVAL = 1 << 0,
+ // Allow an object-src to be specified with any sources (i.e. it may contain
+ // wildcards or http sources). Specifying this requires the CSP to contain
+ // a plugin-types directive which restricts the plugins that can be loaded
+ // to those which are fully sandboxed.
+ OPTIONS_ALLOW_INSECURE_OBJECT_SRC = 1 << 1,
+};
+
+// Checks whether the given |policy| meets the minimum security requirements
+// for use in the extension system.
+//
+// Ideally, we would like to say that an XSS vulnerability in the extension
+// should not be able to execute script, even in the precense of an active
+// network attacker.
+//
+// However, we found that it broke too many deployed extensions to limit
+// 'unsafe-eval' in the script-src directive, so that is allowed as a special
+// case for extensions. Platform apps disallow it.
+//
+// |options| is a bitmask of Options.
+//
+// If |warnings| is not NULL, any validation errors are appended to |warnings|.
+// Returns the sanitized policy.
+std::string SanitizeContentSecurityPolicy(
+ const std::string& policy,
+ int options,
+ std::vector<InstallWarning>* warnings);
+
+// Checks whether the given |policy| enforces a unique origin sandbox as
+// defined by http://www.whatwg.org/specs/web-apps/current-work/multipage/
+// the-iframe-element.html#attr-iframe-sandbox. The policy must have the
+// "sandbox" directive, and the sandbox tokens must not include
+// "allow-same-origin". Additional restrictions may be imposed depending on
+// |type|.
+bool ContentSecurityPolicyIsSandboxed(
+ const std::string& policy, Manifest::Type type);
+
+} // namespace csp_validator
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_CSP_VALIDATOR_H_
diff --git a/chromium/extensions/common/csp_validator_unittest.cc b/chromium/extensions/common/csp_validator_unittest.cc
new file mode 100644
index 00000000000..d9887412a00
--- /dev/null
+++ b/chromium/extensions/common/csp_validator_unittest.cc
@@ -0,0 +1,456 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "extensions/common/csp_validator.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/install_warning.h"
+#include "extensions/common/manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::csp_validator::ContentSecurityPolicyIsLegal;
+using extensions::csp_validator::SanitizeContentSecurityPolicy;
+using extensions::csp_validator::ContentSecurityPolicyIsSandboxed;
+using extensions::csp_validator::OPTIONS_NONE;
+using extensions::csp_validator::OPTIONS_ALLOW_UNSAFE_EVAL;
+using extensions::csp_validator::OPTIONS_ALLOW_INSECURE_OBJECT_SRC;
+using extensions::ErrorUtils;
+using extensions::InstallWarning;
+using extensions::Manifest;
+
+namespace {
+
+std::string InsecureValueWarning(const std::string& directive,
+ const std::string& value) {
+ return ErrorUtils::FormatErrorMessage(
+ extensions::manifest_errors::kInvalidCSPInsecureValue, value, directive);
+}
+
+std::string MissingSecureSrcWarning(const std::string& directive) {
+ return ErrorUtils::FormatErrorMessage(
+ extensions::manifest_errors::kInvalidCSPMissingSecureSrc, directive);
+}
+
+testing::AssertionResult CheckSanitizeCSP(
+ const std::string& policy,
+ int options,
+ const std::string& expected_csp,
+ const std::vector<std::string>& expected_warnings) {
+ std::vector<InstallWarning> actual_warnings;
+ std::string actual_csp = SanitizeContentSecurityPolicy(policy,
+ options,
+ &actual_warnings);
+ if (actual_csp != expected_csp)
+ return testing::AssertionFailure()
+ << "SanitizeContentSecurityPolicy returned an unexpected CSP.\n"
+ << "Expected CSP: " << expected_csp << "\n"
+ << " Actual CSP: " << actual_csp;
+
+ if (expected_warnings.size() != actual_warnings.size()) {
+ testing::Message msg;
+ msg << "Expected " << expected_warnings.size()
+ << " warnings, but got " << actual_warnings.size();
+ for (size_t i = 0; i < actual_warnings.size(); ++i)
+ msg << "\nWarning " << i << " " << actual_warnings[i].message;
+ return testing::AssertionFailure() << msg;
+ }
+
+ for (size_t i = 0; i < expected_warnings.size(); ++i) {
+ if (expected_warnings[i] != actual_warnings[i].message)
+ return testing::AssertionFailure()
+ << "Unexpected warning from SanitizeContentSecurityPolicy.\n"
+ << "Expected warning[" << i << "]: " << expected_warnings[i]
+ << " Actual warning[" << i << "]: " << actual_warnings[i].message;
+ }
+ return testing::AssertionSuccess();
+}
+
+testing::AssertionResult CheckSanitizeCSP(const std::string& policy,
+ int options) {
+ return CheckSanitizeCSP(policy, options, policy, std::vector<std::string>());
+}
+
+testing::AssertionResult CheckSanitizeCSP(const std::string& policy,
+ int options,
+ const std::string& expected_csp) {
+ std::vector<std::string> expected_warnings;
+ return CheckSanitizeCSP(policy, options, expected_csp, expected_warnings);
+}
+
+testing::AssertionResult CheckSanitizeCSP(const std::string& policy,
+ int options,
+ const std::string& expected_csp,
+ const std::string& warning1) {
+ std::vector<std::string> expected_warnings(1, warning1);
+ return CheckSanitizeCSP(policy, options, expected_csp, expected_warnings);
+}
+
+testing::AssertionResult CheckSanitizeCSP(const std::string& policy,
+ int options,
+ const std::string& expected_csp,
+ const std::string& warning1,
+ const std::string& warning2) {
+ std::vector<std::string> expected_warnings(1, warning1);
+ expected_warnings.push_back(warning2);
+ return CheckSanitizeCSP(policy, options, expected_csp, expected_warnings);
+}
+
+testing::AssertionResult CheckSanitizeCSP(const std::string& policy,
+ int options,
+ const std::string& expected_csp,
+ const std::string& warning1,
+ const std::string& warning2,
+ const std::string& warning3) {
+ std::vector<std::string> expected_warnings(1, warning1);
+ expected_warnings.push_back(warning2);
+ expected_warnings.push_back(warning3);
+ return CheckSanitizeCSP(policy, options, expected_csp, expected_warnings);
+}
+
+}; // namespace
+
+TEST(ExtensionCSPValidator, IsLegal) {
+ EXPECT_TRUE(ContentSecurityPolicyIsLegal("foo"));
+ EXPECT_TRUE(ContentSecurityPolicyIsLegal(
+ "default-src 'self'; script-src http://www.google.com"));
+ EXPECT_FALSE(ContentSecurityPolicyIsLegal(
+ "default-src 'self';\nscript-src http://www.google.com"));
+ EXPECT_FALSE(ContentSecurityPolicyIsLegal(
+ "default-src 'self';\rscript-src http://www.google.com"));
+ EXPECT_FALSE(ContentSecurityPolicyIsLegal(
+ "default-src 'self';,script-src http://www.google.com"));
+}
+
+TEST(ExtensionCSPValidator, IsSecure) {
+ EXPECT_TRUE(CheckSanitizeCSP(
+ std::string(), OPTIONS_ALLOW_UNSAFE_EVAL,
+ "script-src 'self' chrome-extension-resource:; object-src 'self';",
+ MissingSecureSrcWarning("script-src"),
+ MissingSecureSrcWarning("object-src")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "img-src https://google.com", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "img-src https://google.com; script-src 'self'"
+ " chrome-extension-resource:; object-src 'self';",
+ MissingSecureSrcWarning("script-src"),
+ MissingSecureSrcWarning("object-src")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "script-src a b", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "script-src; object-src 'self';",
+ InsecureValueWarning("script-src", "a"),
+ InsecureValueWarning("script-src", "b"),
+ MissingSecureSrcWarning("object-src")));
+
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src *", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src;",
+ InsecureValueWarning("default-src", "*")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self';", OPTIONS_ALLOW_UNSAFE_EVAL));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'none';", OPTIONS_ALLOW_UNSAFE_EVAL));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' ftp://google.com", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "ftp://google.com")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' https://google.com;", OPTIONS_ALLOW_UNSAFE_EVAL));
+
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src *; default-src 'self'", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src; default-src 'self';",
+ InsecureValueWarning("default-src", "*")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self'; default-src *;", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self'; default-src;"));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self'; default-src *; script-src *; script-src 'self'",
+ OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self'; default-src; script-src; script-src 'self';",
+ InsecureValueWarning("script-src", "*")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self'; default-src *; script-src 'self'; script-src *;",
+ OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self'; default-src; script-src 'self'; script-src;"));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src *; script-src 'self'", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src; script-src 'self';",
+ InsecureValueWarning("default-src", "*")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src *; script-src 'self'; img-src 'self'",
+ OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src; script-src 'self'; img-src 'self';",
+ InsecureValueWarning("default-src", "*")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src *; script-src 'self'; object-src 'self';",
+ OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src; script-src 'self'; object-src 'self';"));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "script-src 'self'; object-src 'self';", OPTIONS_ALLOW_UNSAFE_EVAL));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'unsafe-eval';", OPTIONS_ALLOW_UNSAFE_EVAL));
+
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'unsafe-eval'", OPTIONS_NONE,
+ "default-src;",
+ InsecureValueWarning("default-src", "'unsafe-eval'")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'unsafe-inline'", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src;",
+ InsecureValueWarning("default-src", "'unsafe-inline'")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'unsafe-inline' 'none'", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'none';",
+ InsecureValueWarning("default-src", "'unsafe-inline'")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' http://google.com", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "http://google.com")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' https://google.com;", OPTIONS_ALLOW_UNSAFE_EVAL));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' chrome://resources;", OPTIONS_ALLOW_UNSAFE_EVAL));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' chrome-extension://aabbcc;",
+ OPTIONS_ALLOW_UNSAFE_EVAL));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' chrome-extension-resource://aabbcc;",
+ OPTIONS_ALLOW_UNSAFE_EVAL));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' https:", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "https:")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' http:", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "http:")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' google.com", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "google.com")));
+
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' *", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "*")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' *:*", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "*:*")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' *:*/", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "*:*/")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' *:*/path", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "*:*/path")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' https://", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "https://")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' https://*:*", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "https://*:*")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' https://*:*/", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "https://*:*/")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' https://*:*/path", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "https://*:*/path")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' https://*.com", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "https://*.com")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' https://*.*.google.com/", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "https://*.*.google.com/")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' https://*.*.google.com:*/", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "https://*.*.google.com:*/")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' https://www.*.google.com/", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "https://www.*.google.com/")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' https://www.*.google.com:*/",
+ OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "https://www.*.google.com:*/")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' chrome://*", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "chrome://*")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' chrome-extension://*", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "chrome-extension://*")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' chrome-extension://", OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "chrome-extension://")));
+
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' https://*.google.com;", OPTIONS_ALLOW_UNSAFE_EVAL));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' https://*.google.com:1;", OPTIONS_ALLOW_UNSAFE_EVAL));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' https://*.google.com:*;", OPTIONS_ALLOW_UNSAFE_EVAL));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' https://*.google.com:1/;",
+ OPTIONS_ALLOW_UNSAFE_EVAL));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' https://*.google.com:*/;",
+ OPTIONS_ALLOW_UNSAFE_EVAL));
+
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' http://127.0.0.1;", OPTIONS_ALLOW_UNSAFE_EVAL));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' http://localhost;", OPTIONS_ALLOW_UNSAFE_EVAL));
+ EXPECT_TRUE(CheckSanitizeCSP("default-src 'self' http://lOcAlHoSt;",
+ OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self' http://lOcAlHoSt;"));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' http://127.0.0.1:9999;", OPTIONS_ALLOW_UNSAFE_EVAL));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' http://localhost:8888;", OPTIONS_ALLOW_UNSAFE_EVAL));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' http://127.0.0.1.example.com",
+ OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "http://127.0.0.1.example.com")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' http://localhost.example.com",
+ OPTIONS_ALLOW_UNSAFE_EVAL,
+ "default-src 'self';",
+ InsecureValueWarning("default-src", "http://localhost.example.com")));
+
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' blob:;", OPTIONS_ALLOW_UNSAFE_EVAL));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' blob:http://example.com/XXX",
+ OPTIONS_ALLOW_UNSAFE_EVAL, "default-src 'self';",
+ InsecureValueWarning("default-src", "blob:http://example.com/XXX")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' filesystem:;", OPTIONS_ALLOW_UNSAFE_EVAL));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' filesystem:http://example.com/XX",
+ OPTIONS_ALLOW_UNSAFE_EVAL, "default-src 'self';",
+ InsecureValueWarning("default-src", "filesystem:http://example.com/XX")));
+
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' https://*.googleapis.com;",
+ OPTIONS_ALLOW_UNSAFE_EVAL));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src 'self' https://x.googleapis.com;",
+ OPTIONS_ALLOW_UNSAFE_EVAL));
+
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "script-src 'self'; object-src *", OPTIONS_NONE,
+ "script-src 'self'; object-src;",
+ InsecureValueWarning("object-src", "*")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "script-src 'self'; object-src *", OPTIONS_ALLOW_INSECURE_OBJECT_SRC,
+ "script-src 'self'; object-src;",
+ InsecureValueWarning("object-src", "*")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "script-src 'self'; object-src *; plugin-types application/pdf;",
+ OPTIONS_ALLOW_INSECURE_OBJECT_SRC));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "script-src 'self'; object-src *; "
+ "plugin-types application/x-shockwave-flash",
+ OPTIONS_ALLOW_INSECURE_OBJECT_SRC,
+ "script-src 'self'; object-src; "
+ "plugin-types application/x-shockwave-flash;",
+ InsecureValueWarning("object-src", "*")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "script-src 'self'; object-src *; "
+ "plugin-types application/x-shockwave-flash application/pdf;",
+ OPTIONS_ALLOW_INSECURE_OBJECT_SRC,
+ "script-src 'self'; object-src; "
+ "plugin-types application/x-shockwave-flash application/pdf;",
+ InsecureValueWarning("object-src", "*")));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "script-src 'self'; object-src http://www.example.com; "
+ "plugin-types application/pdf;",
+ OPTIONS_ALLOW_INSECURE_OBJECT_SRC));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "object-src http://www.example.com blob:; script-src 'self'; "
+ "plugin-types application/pdf;",
+ OPTIONS_ALLOW_INSECURE_OBJECT_SRC));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "script-src 'self'; object-src http://*.example.com; "
+ "plugin-types application/pdf;",
+ OPTIONS_ALLOW_INSECURE_OBJECT_SRC));
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "script-src *; object-src *; plugin-types application/pdf;",
+ OPTIONS_ALLOW_INSECURE_OBJECT_SRC,
+ "script-src; object-src *; plugin-types application/pdf;",
+ InsecureValueWarning("script-src", "*")));
+
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src; script-src"
+ " 'sha256-hndjYvzUzy2Ykuad81Cwsl1FOXX/qYs/aDVyUyNZwBw='"
+ " 'sha384-bSVm1i3sjPBRM4TwZtYTDjk9JxZMExYHWbFmP1SxDhJH4ue0Wu9OPOkY5hcqRcS"
+ "t'"
+ " 'sha512-440MmBLtj9Kp5Bqloogn9BqGDylY8vFsv5/zXL1zH2fJVssCoskRig4gyM+9Kqw"
+ "vCSapSz5CVoUGHQcxv43UQg==';",
+ OPTIONS_NONE));
+
+ // Reject non-standard algorithms, even if they are still supported by Blink.
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src; script-src 'sha1-eYyYGmKWdhpUewohaXk9o8IaLSw=';",
+ OPTIONS_NONE, "default-src; script-src;",
+ InsecureValueWarning("script-src",
+ "'sha1-eYyYGmKWdhpUewohaXk9o8IaLSw='")));
+
+ EXPECT_TRUE(CheckSanitizeCSP(
+ "default-src; script-src 'sha256-hndjYvzUzy2Ykuad81Cwsl1FOXX/qYs/aDVyUyNZ"
+ "wBw= sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng=';",
+ OPTIONS_NONE, "default-src; script-src;",
+ InsecureValueWarning(
+ "script-src", "'sha256-hndjYvzUzy2Ykuad81Cwsl1FOXX/qYs/aDVyUyNZwBw="),
+ InsecureValueWarning(
+ "script-src",
+ "sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng='")));
+}
+
+TEST(ExtensionCSPValidator, IsSandboxed) {
+ EXPECT_FALSE(ContentSecurityPolicyIsSandboxed(std::string(),
+ Manifest::TYPE_EXTENSION));
+ EXPECT_FALSE(ContentSecurityPolicyIsSandboxed("img-src https://google.com",
+ Manifest::TYPE_EXTENSION));
+
+ // Sandbox directive is required.
+ EXPECT_TRUE(ContentSecurityPolicyIsSandboxed(
+ "sandbox", Manifest::TYPE_EXTENSION));
+
+ // Additional sandbox tokens are OK.
+ EXPECT_TRUE(ContentSecurityPolicyIsSandboxed(
+ "sandbox allow-scripts", Manifest::TYPE_EXTENSION));
+ // Except for allow-same-origin.
+ EXPECT_FALSE(ContentSecurityPolicyIsSandboxed(
+ "sandbox allow-same-origin", Manifest::TYPE_EXTENSION));
+
+ // Additional directives are OK.
+ EXPECT_TRUE(ContentSecurityPolicyIsSandboxed(
+ "sandbox; img-src https://google.com", Manifest::TYPE_EXTENSION));
+
+ // Extensions allow navigation, platform apps don't.
+ EXPECT_TRUE(ContentSecurityPolicyIsSandboxed(
+ "sandbox allow-top-navigation", Manifest::TYPE_EXTENSION));
+ EXPECT_FALSE(ContentSecurityPolicyIsSandboxed(
+ "sandbox allow-top-navigation", Manifest::TYPE_PLATFORM_APP));
+
+ // Popups are OK.
+ EXPECT_TRUE(ContentSecurityPolicyIsSandboxed(
+ "sandbox allow-popups", Manifest::TYPE_EXTENSION));
+ EXPECT_TRUE(ContentSecurityPolicyIsSandboxed(
+ "sandbox allow-popups", Manifest::TYPE_PLATFORM_APP));
+}
diff --git a/chromium/extensions/common/dom_action_types.h b/chromium/extensions/common/dom_action_types.h
new file mode 100644
index 00000000000..10bab3ebd5c
--- /dev/null
+++ b/chromium/extensions/common/dom_action_types.h
@@ -0,0 +1,26 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_DOM_ACTION_TYPES_H_
+#define EXTENSIONS_COMMON_DOM_ACTION_TYPES_H_
+
+namespace extensions {
+
+struct DomActionType {
+ // These values should not be changed. Append any additional values to the
+ // end with sequential numbers.
+ enum Type {
+ GETTER = 0, // For Content Script DOM manipulations
+ SETTER = 1, // For Content Script DOM manipulations
+ METHOD = 2, // For Content Script DOM manipulations
+ INSERTED = 3, // For when Content Scripts are added to pages
+ XHR = 4, // When an extension core sends an XHR
+ WEBREQUEST = 5, // When a page request is modified with the WebRequest API
+ MODIFIED = 6, // For legacy, also used as a catch-all
+ };
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_DOM_ACTION_TYPES_H_
diff --git a/chromium/extensions/common/draggable_region.cc b/chromium/extensions/common/draggable_region.cc
new file mode 100644
index 00000000000..baee6eecf5c
--- /dev/null
+++ b/chromium/extensions/common/draggable_region.cc
@@ -0,0 +1,13 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/draggable_region.h"
+
+namespace extensions {
+
+DraggableRegion::DraggableRegion()
+ : draggable(false) {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/draggable_region.h b/chromium/extensions/common/draggable_region.h
new file mode 100644
index 00000000000..328a05d7b3e
--- /dev/null
+++ b/chromium/extensions/common/draggable_region.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_DRAGGABLE_REGION_H_
+#define EXTENSIONS_COMMON_DRAGGABLE_REGION_H_
+
+#include "ui/gfx/geometry/rect.h"
+
+namespace extensions {
+
+struct DraggableRegion {
+ bool draggable;
+ gfx::Rect bounds;
+
+ DraggableRegion();
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_DRAGGABLE_REGION_H_
diff --git a/chromium/extensions/common/error_utils.cc b/chromium/extensions/common/error_utils.cc
new file mode 100644
index 00000000000..14ca2a96c2f
--- /dev/null
+++ b/chromium/extensions/common/error_utils.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/error_utils.h"
+
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace extensions {
+
+std::string ErrorUtils::FormatErrorMessage(const std::string& format,
+ const std::string& s1) {
+ std::string ret_val = format;
+ base::ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
+ return ret_val;
+}
+
+std::string ErrorUtils::FormatErrorMessage(const std::string& format,
+ const std::string& s1,
+ const std::string& s2) {
+ std::string ret_val = format;
+ base::ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
+ base::ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2);
+ return ret_val;
+}
+
+std::string ErrorUtils::FormatErrorMessage(const std::string& format,
+ const std::string& s1,
+ const std::string& s2,
+ const std::string& s3) {
+ std::string ret_val = format;
+ base::ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
+ base::ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2);
+ base::ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s3);
+ return ret_val;
+}
+
+base::string16 ErrorUtils::FormatErrorMessageUTF16(const std::string& format,
+ const std::string& s1) {
+ return base::UTF8ToUTF16(FormatErrorMessage(format, s1));
+}
+
+base::string16 ErrorUtils::FormatErrorMessageUTF16(const std::string& format,
+ const std::string& s1,
+ const std::string& s2) {
+ return base::UTF8ToUTF16(FormatErrorMessage(format, s1, s2));
+}
+
+base::string16 ErrorUtils::FormatErrorMessageUTF16(const std::string& format,
+ const std::string& s1,
+ const std::string& s2,
+ const std::string& s3) {
+ return base::UTF8ToUTF16(FormatErrorMessage(format, s1, s2, s3));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/error_utils.h b/chromium/extensions/common/error_utils.h
new file mode 100644
index 00000000000..fedbd2887ba
--- /dev/null
+++ b/chromium/extensions/common/error_utils.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_ERROR_UTILS_H_
+#define EXTENSIONS_COMMON_ERROR_UTILS_H_
+
+#include <string>
+
+#include "base/strings/string16.h"
+
+namespace extensions {
+
+class ErrorUtils {
+ public:
+ // Creates an error messages from a pattern.
+ static std::string FormatErrorMessage(const std::string& format,
+ const std::string& s1);
+
+ static std::string FormatErrorMessage(const std::string& format,
+ const std::string& s1,
+ const std::string& s2);
+
+ static std::string FormatErrorMessage(const std::string& format,
+ const std::string& s1,
+ const std::string& s2,
+ const std::string& s3);
+
+ static base::string16 FormatErrorMessageUTF16(const std::string& format,
+ const std::string& s1);
+
+ static base::string16 FormatErrorMessageUTF16(const std::string& format,
+ const std::string& s1,
+ const std::string& s2);
+
+ static base::string16 FormatErrorMessageUTF16(const std::string& format,
+ const std::string& s1,
+ const std::string& s2,
+ const std::string& s3);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_ERROR_UTILS_H_
diff --git a/chromium/extensions/common/event_filter.cc b/chromium/extensions/common/event_filter.cc
new file mode 100644
index 00000000000..955671c20c4
--- /dev/null
+++ b/chromium/extensions/common/event_filter.cc
@@ -0,0 +1,187 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/event_filter.h"
+
+#include <string>
+#include <utility>
+
+#include "components/url_matcher/url_matcher_factory.h"
+#include "ipc/ipc_message.h"
+
+using url_matcher::URLMatcher;
+using url_matcher::URLMatcherConditionSet;
+using url_matcher::URLMatcherFactory;
+
+namespace extensions {
+
+EventFilter::EventMatcherEntry::EventMatcherEntry(
+ scoped_ptr<EventMatcher> event_matcher,
+ URLMatcher* url_matcher,
+ const URLMatcherConditionSet::Vector& condition_sets)
+ : event_matcher_(std::move(event_matcher)), url_matcher_(url_matcher) {
+ for (URLMatcherConditionSet::Vector::const_iterator it =
+ condition_sets.begin(); it != condition_sets.end(); it++)
+ condition_set_ids_.push_back((*it)->id());
+ url_matcher_->AddConditionSets(condition_sets);
+}
+
+EventFilter::EventMatcherEntry::~EventMatcherEntry() {
+ url_matcher_->RemoveConditionSets(condition_set_ids_);
+}
+
+void EventFilter::EventMatcherEntry::DontRemoveConditionSetsInDestructor() {
+ condition_set_ids_.clear();
+}
+
+EventFilter::EventFilter()
+ : next_id_(0),
+ next_condition_set_id_(0) {
+}
+
+EventFilter::~EventFilter() {
+ // Normally when an event matcher entry is removed from event_matchers_ it
+ // will remove its condition sets from url_matcher_, but as url_matcher_ is
+ // being destroyed anyway there is no need to do that step here.
+ for (EventMatcherMultiMap::iterator it = event_matchers_.begin();
+ it != event_matchers_.end(); it++) {
+ for (EventMatcherMap::iterator it2 = it->second.begin();
+ it2 != it->second.end(); it2++) {
+ it2->second->DontRemoveConditionSetsInDestructor();
+ }
+ }
+}
+
+EventFilter::MatcherID
+EventFilter::AddEventMatcher(const std::string& event_name,
+ scoped_ptr<EventMatcher> matcher) {
+ MatcherID id = next_id_++;
+ URLMatcherConditionSet::Vector condition_sets;
+ if (!CreateConditionSets(id, matcher.get(), &condition_sets))
+ return -1;
+
+ for (URLMatcherConditionSet::Vector::iterator it = condition_sets.begin();
+ it != condition_sets.end(); it++) {
+ condition_set_id_to_event_matcher_id_.insert(
+ std::make_pair((*it)->id(), id));
+ }
+ id_to_event_name_[id] = event_name;
+ event_matchers_[event_name][id] = linked_ptr<EventMatcherEntry>(
+ new EventMatcherEntry(std::move(matcher), &url_matcher_, condition_sets));
+ return id;
+}
+
+EventMatcher* EventFilter::GetEventMatcher(MatcherID id) {
+ DCHECK(id_to_event_name_.find(id) != id_to_event_name_.end());
+ const std::string& event_name = id_to_event_name_[id];
+ return event_matchers_[event_name][id]->event_matcher();
+}
+
+const std::string& EventFilter::GetEventName(MatcherID id) {
+ DCHECK(id_to_event_name_.find(id) != id_to_event_name_.end());
+ return id_to_event_name_[id];
+}
+
+bool EventFilter::CreateConditionSets(
+ MatcherID id,
+ EventMatcher* matcher,
+ URLMatcherConditionSet::Vector* condition_sets) {
+ if (matcher->GetURLFilterCount() == 0) {
+ // If there are no URL filters then we want to match all events, so create a
+ // URLFilter from an empty dictionary.
+ base::DictionaryValue empty_dict;
+ return AddDictionaryAsConditionSet(&empty_dict, condition_sets);
+ }
+ for (int i = 0; i < matcher->GetURLFilterCount(); i++) {
+ base::DictionaryValue* url_filter;
+ if (!matcher->GetURLFilter(i, &url_filter))
+ return false;
+ if (!AddDictionaryAsConditionSet(url_filter, condition_sets))
+ return false;
+ }
+ return true;
+}
+
+bool EventFilter::AddDictionaryAsConditionSet(
+ base::DictionaryValue* url_filter,
+ URLMatcherConditionSet::Vector* condition_sets) {
+ std::string error;
+ URLMatcherConditionSet::ID condition_set_id = next_condition_set_id_++;
+ condition_sets->push_back(URLMatcherFactory::CreateFromURLFilterDictionary(
+ url_matcher_.condition_factory(),
+ url_filter,
+ condition_set_id,
+ &error));
+ if (!error.empty()) {
+ LOG(ERROR) << "CreateFromURLFilterDictionary failed: " << error;
+ url_matcher_.ClearUnusedConditionSets();
+ condition_sets->clear();
+ return false;
+ }
+ return true;
+}
+
+std::string EventFilter::RemoveEventMatcher(MatcherID id) {
+ std::map<MatcherID, std::string>::iterator it = id_to_event_name_.find(id);
+ std::string event_name = it->second;
+ // EventMatcherEntry's destructor causes the condition set ids to be removed
+ // from url_matcher_.
+ event_matchers_[event_name].erase(id);
+ id_to_event_name_.erase(it);
+ return event_name;
+}
+
+std::set<EventFilter::MatcherID> EventFilter::MatchEvent(
+ const std::string& event_name, const EventFilteringInfo& event_info,
+ int routing_id) {
+ std::set<MatcherID> matchers;
+
+ EventMatcherMultiMap::iterator it = event_matchers_.find(event_name);
+ if (it == event_matchers_.end())
+ return matchers;
+
+ EventMatcherMap& matcher_map = it->second;
+ GURL url_to_match_against = event_info.has_url() ? event_info.url() : GURL();
+ std::set<URLMatcherConditionSet::ID> matching_condition_set_ids =
+ url_matcher_.MatchURL(url_to_match_against);
+ for (std::set<URLMatcherConditionSet::ID>::iterator it =
+ matching_condition_set_ids.begin();
+ it != matching_condition_set_ids.end(); it++) {
+ std::map<URLMatcherConditionSet::ID, MatcherID>::iterator matcher_id =
+ condition_set_id_to_event_matcher_id_.find(*it);
+ if (matcher_id == condition_set_id_to_event_matcher_id_.end()) {
+ NOTREACHED() << "id not found in condition set map (" << (*it) << ")";
+ continue;
+ }
+ MatcherID id = matcher_id->second;
+ EventMatcherMap::iterator matcher_entry = matcher_map.find(id);
+ if (matcher_entry == matcher_map.end()) {
+ // Matcher must be for a different event.
+ continue;
+ }
+ const EventMatcher* event_matcher = matcher_entry->second->event_matcher();
+ // The context that installed the event listener should be the same context
+ // as the one where the event listener is called.
+ if ((routing_id != MSG_ROUTING_NONE) &&
+ (event_matcher->GetRoutingID() != routing_id)) {
+ continue;
+ }
+ if (event_matcher->MatchNonURLCriteria(event_info)) {
+ CHECK(!event_matcher->HasURLFilters() || event_info.has_url());
+ matchers.insert(id);
+ }
+ }
+
+ return matchers;
+}
+
+int EventFilter::GetMatcherCountForEvent(const std::string& name) {
+ EventMatcherMultiMap::const_iterator it = event_matchers_.find(name);
+ if (it == event_matchers_.end())
+ return 0;
+
+ return it->second.size();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/event_filter.h b/chromium/extensions/common/event_filter.h
new file mode 100644
index 00000000000..8f6d6040420
--- /dev/null
+++ b/chromium/extensions/common/event_filter.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_EVENT_FILTER_H_
+#define EXTENSIONS_COMMON_EVENT_FILTER_H_
+
+#include <map>
+#include <set>
+
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "components/url_matcher/url_matcher.h"
+#include "extensions/common/event_filtering_info.h"
+#include "extensions/common/event_matcher.h"
+
+namespace extensions {
+
+// Matches incoming events against a collection of EventMatchers. Each added
+// EventMatcher is given an id which is returned by MatchEvent() when it is
+// passed a matching event.
+class EventFilter {
+ public:
+ typedef int MatcherID;
+ EventFilter();
+ ~EventFilter();
+
+ // Adds an event matcher that will be used in calls to MatchEvent(). Returns
+ // the id of the matcher, or -1 if there was an error.
+ MatcherID AddEventMatcher(const std::string& event_name,
+ scoped_ptr<EventMatcher> matcher);
+
+ // Retrieve the EventMatcher with the given id.
+ EventMatcher* GetEventMatcher(MatcherID id);
+
+ // Retrieve the name of the event that the EventMatcher specified by |id| is
+ // referring to.
+ const std::string& GetEventName(MatcherID id);
+
+ // Removes an event matcher, returning the name of the event that it was for.
+ std::string RemoveEventMatcher(MatcherID id);
+
+ // Match an event named |event_name| with filtering info |event_info| against
+ // our set of event matchers. Returns a set of ids that correspond to the
+ // event matchers that matched the event.
+ // TODO(koz): Add a std::string* parameter for retrieving error messages.
+ std::set<MatcherID> MatchEvent(const std::string& event_name,
+ const EventFilteringInfo& event_info,
+ int routing_id);
+
+ int GetMatcherCountForEvent(const std::string& event_name);
+
+ // For testing.
+ bool IsURLMatcherEmpty() const {
+ return url_matcher_.IsEmpty();
+ }
+
+ private:
+ class EventMatcherEntry {
+ public:
+ // Adds |condition_sets| to |url_matcher| on construction and removes them
+ // again on destruction. |condition_sets| should be the
+ // URLMatcherConditionSets that match the URL constraints specified by
+ // |event_matcher|.
+ EventMatcherEntry(
+ scoped_ptr<EventMatcher> event_matcher,
+ url_matcher::URLMatcher* url_matcher,
+ const url_matcher::URLMatcherConditionSet::Vector& condition_sets);
+ ~EventMatcherEntry();
+
+ // Prevents the removal of condition sets when this class is destroyed. We
+ // call this in EventFilter's destructor so that we don't do the costly
+ // removal of condition sets when the URLMatcher is going to be destroyed
+ // and clean them up anyway.
+ void DontRemoveConditionSetsInDestructor();
+
+ EventMatcher* event_matcher() {
+ return event_matcher_.get();
+ }
+
+ private:
+ scoped_ptr<EventMatcher> event_matcher_;
+ // The id sets in url_matcher_ that this EventMatcher owns.
+ std::vector<url_matcher::URLMatcherConditionSet::ID> condition_set_ids_;
+ url_matcher::URLMatcher* url_matcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventMatcherEntry);
+ };
+
+ // Maps from a matcher id to an event matcher entry.
+ typedef std::map<MatcherID, linked_ptr<EventMatcherEntry> > EventMatcherMap;
+
+ // Maps from event name to the map of matchers that are registered for it.
+ typedef std::map<std::string, EventMatcherMap> EventMatcherMultiMap;
+
+ // Adds the list of URL filters in |matcher| to the URL matcher, having
+ // matches for those URLs map to |id|.
+ bool CreateConditionSets(
+ MatcherID id,
+ EventMatcher* matcher,
+ url_matcher::URLMatcherConditionSet::Vector* condition_sets);
+
+ bool AddDictionaryAsConditionSet(
+ base::DictionaryValue* url_filter,
+ url_matcher::URLMatcherConditionSet::Vector* condition_sets);
+
+ url_matcher::URLMatcher url_matcher_;
+ EventMatcherMultiMap event_matchers_;
+
+ // The next id to assign to an EventMatcher.
+ MatcherID next_id_;
+
+ // The next id to assign to a condition set passed to URLMatcher.
+ url_matcher::URLMatcherConditionSet::ID next_condition_set_id_;
+
+ // Maps condition set ids, which URLMatcher operates in, to event matcher
+ // ids, which the interface to this class operates in. As each EventFilter
+ // can specify many condition sets this is a many to one relationship.
+ std::map<url_matcher::URLMatcherConditionSet::ID, MatcherID>
+ condition_set_id_to_event_matcher_id_;
+
+ // Maps from event matcher ids to the name of the event they match on.
+ std::map<MatcherID, std::string> id_to_event_name_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventFilter);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_EVENT_FILTER_H_
diff --git a/chromium/extensions/common/event_filter_unittest.cc b/chromium/extensions/common/event_filter_unittest.cc
new file mode 100644
index 00000000000..bb5fe048fd7
--- /dev/null
+++ b/chromium/extensions/common/event_filter_unittest.cc
@@ -0,0 +1,262 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/event_filter.h"
+
+#include <string>
+#include <utility>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "extensions/common/event_filtering_info.h"
+#include "extensions/common/event_matcher.h"
+#include "ipc/ipc_message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::DictionaryValue;
+using base::ListValue;
+using base::Value;
+
+namespace extensions {
+
+class EventFilterUnittest : public testing::Test {
+ public:
+ EventFilterUnittest() {
+ google_event_.SetURL(GURL("http://google.com"));
+ yahoo_event_.SetURL(GURL("http://yahoo.com"));
+ random_url_event_.SetURL(GURL("http://www.something-else.com"));
+ empty_url_event_.SetURL(GURL());
+ }
+
+ protected:
+ scoped_ptr<base::Value> HostSuffixDict(const std::string& host_suffix) {
+ scoped_ptr<base::DictionaryValue> dict(new DictionaryValue());
+ dict->Set("hostSuffix", new base::StringValue(host_suffix));
+ return scoped_ptr<base::Value>(dict.release());
+ }
+
+ scoped_ptr<base::ListValue> ValueAsList(scoped_ptr<base::Value> value) {
+ scoped_ptr<base::ListValue> result(new base::ListValue());
+ result->Append(value.release());
+ return result;
+ }
+
+ scoped_ptr<EventMatcher> AllURLs() {
+ return scoped_ptr<EventMatcher>(new EventMatcher(
+ scoped_ptr<DictionaryValue>(new DictionaryValue), MSG_ROUTING_NONE));
+ }
+
+ scoped_ptr<EventMatcher> HostSuffixMatcher(const std::string& host_suffix) {
+ return MatcherFromURLFilterList(ValueAsList(HostSuffixDict(host_suffix)));
+ }
+
+ scoped_ptr<EventMatcher> MatcherFromURLFilterList(
+ scoped_ptr<ListValue> url_filter_list) {
+ scoped_ptr<DictionaryValue> filter_dict(new DictionaryValue);
+ filter_dict->Set("url", url_filter_list.release());
+ return scoped_ptr<EventMatcher>(
+ new EventMatcher(std::move(filter_dict), MSG_ROUTING_NONE));
+ }
+
+ EventFilter event_filter_;
+ EventFilteringInfo empty_event_;
+ EventFilteringInfo google_event_;
+ EventFilteringInfo yahoo_event_;
+ EventFilteringInfo random_url_event_;
+ EventFilteringInfo empty_url_event_;
+};
+
+TEST_F(EventFilterUnittest, NoMatchersMatchIfEmpty) {
+ std::set<int> matches = event_filter_.MatchEvent("some-event",
+ empty_event_,
+ MSG_ROUTING_NONE);
+ ASSERT_EQ(0u, matches.size());
+}
+
+TEST_F(EventFilterUnittest, AddingEventMatcherDoesntCrash) {
+ event_filter_.AddEventMatcher("event1", AllURLs());
+}
+
+TEST_F(EventFilterUnittest,
+ DontMatchAgainstMatchersForDifferentEvents) {
+ event_filter_.AddEventMatcher("event1", AllURLs());
+ std::set<int> matches = event_filter_.MatchEvent("event2",
+ empty_event_,
+ MSG_ROUTING_NONE);
+ ASSERT_EQ(0u, matches.size());
+}
+
+TEST_F(EventFilterUnittest, DoMatchAgainstMatchersForSameEvent) {
+ int id = event_filter_.AddEventMatcher("event1", AllURLs());
+ std::set<int> matches = event_filter_.MatchEvent("event1",
+ google_event_, MSG_ROUTING_NONE);
+ ASSERT_EQ(1u, matches.size());
+ ASSERT_EQ(1u, matches.count(id));
+}
+
+TEST_F(EventFilterUnittest, DontMatchUnlessMatcherMatches) {
+ EventFilteringInfo info;
+ info.SetURL(GURL("http://www.yahoo.com"));
+ event_filter_.AddEventMatcher("event1", HostSuffixMatcher("google.com"));
+ std::set<int> matches = event_filter_.MatchEvent(
+ "event1", info, MSG_ROUTING_NONE);
+ ASSERT_TRUE(matches.empty());
+}
+
+TEST_F(EventFilterUnittest, RemovingAnEventMatcherStopsItMatching) {
+ int id = event_filter_.AddEventMatcher("event1", AllURLs());
+ event_filter_.RemoveEventMatcher(id);
+ std::set<int> matches = event_filter_.MatchEvent("event1",
+ empty_event_,
+ MSG_ROUTING_NONE);
+ ASSERT_TRUE(matches.empty());
+}
+
+TEST_F(EventFilterUnittest, MultipleEventMatches) {
+ int id1 = event_filter_.AddEventMatcher("event1", AllURLs());
+ int id2 = event_filter_.AddEventMatcher("event1", AllURLs());
+ std::set<int> matches = event_filter_.MatchEvent("event1",
+ google_event_, MSG_ROUTING_NONE);
+ ASSERT_EQ(2u, matches.size());
+ ASSERT_EQ(1u, matches.count(id1));
+ ASSERT_EQ(1u, matches.count(id2));
+}
+
+TEST_F(EventFilterUnittest, TestURLMatching) {
+ EventFilteringInfo info;
+ info.SetURL(GURL("http://www.google.com"));
+ int id = event_filter_.AddEventMatcher("event1",
+ HostSuffixMatcher("google.com"));
+ std::set<int> matches = event_filter_.MatchEvent(
+ "event1", info, MSG_ROUTING_NONE);
+ ASSERT_EQ(1u, matches.size());
+ ASSERT_EQ(1u, matches.count(id));
+}
+
+TEST_F(EventFilterUnittest, TestMultipleURLFiltersMatchOnAny) {
+ scoped_ptr<base::ListValue> filters(new base::ListValue());
+ filters->Append(HostSuffixDict("google.com").release());
+ filters->Append(HostSuffixDict("yahoo.com").release());
+
+ scoped_ptr<EventMatcher> matcher(
+ MatcherFromURLFilterList(std::move(filters)));
+ int id = event_filter_.AddEventMatcher("event1", std::move(matcher));
+
+ {
+ std::set<int> matches = event_filter_.MatchEvent("event1",
+ google_event_, MSG_ROUTING_NONE);
+ ASSERT_EQ(1u, matches.size());
+ ASSERT_EQ(1u, matches.count(id));
+ }
+ {
+ std::set<int> matches = event_filter_.MatchEvent("event1",
+ yahoo_event_, MSG_ROUTING_NONE);
+ ASSERT_EQ(1u, matches.size());
+ ASSERT_EQ(1u, matches.count(id));
+ }
+ {
+ std::set<int> matches = event_filter_.MatchEvent("event1",
+ random_url_event_, MSG_ROUTING_NONE);
+ ASSERT_EQ(0u, matches.size());
+ }
+}
+
+TEST_F(EventFilterUnittest, TestStillMatchesAfterRemoval) {
+ int id1 = event_filter_.AddEventMatcher("event1", AllURLs());
+ int id2 = event_filter_.AddEventMatcher("event1", AllURLs());
+
+ event_filter_.RemoveEventMatcher(id1);
+ {
+ std::set<int> matches = event_filter_.MatchEvent("event1",
+ google_event_, MSG_ROUTING_NONE);
+ ASSERT_EQ(1u, matches.size());
+ ASSERT_EQ(1u, matches.count(id2));
+ }
+}
+
+TEST_F(EventFilterUnittest, TestMatchesOnlyAgainstPatternsForCorrectEvent) {
+ int id1 = event_filter_.AddEventMatcher("event1", AllURLs());
+ event_filter_.AddEventMatcher("event2", AllURLs());
+
+ {
+ std::set<int> matches = event_filter_.MatchEvent("event1",
+ google_event_, MSG_ROUTING_NONE);
+ ASSERT_EQ(1u, matches.size());
+ ASSERT_EQ(1u, matches.count(id1));
+ }
+}
+
+TEST_F(EventFilterUnittest, TestGetMatcherCountForEvent) {
+ ASSERT_EQ(0, event_filter_.GetMatcherCountForEvent("event1"));
+ int id1 = event_filter_.AddEventMatcher("event1", AllURLs());
+ ASSERT_EQ(1, event_filter_.GetMatcherCountForEvent("event1"));
+ int id2 = event_filter_.AddEventMatcher("event1", AllURLs());
+ ASSERT_EQ(2, event_filter_.GetMatcherCountForEvent("event1"));
+ event_filter_.RemoveEventMatcher(id1);
+ ASSERT_EQ(1, event_filter_.GetMatcherCountForEvent("event1"));
+ event_filter_.RemoveEventMatcher(id2);
+ ASSERT_EQ(0, event_filter_.GetMatcherCountForEvent("event1"));
+}
+
+TEST_F(EventFilterUnittest, RemoveEventMatcherReturnsEventName) {
+ int id1 = event_filter_.AddEventMatcher("event1", AllURLs());
+ int id2 = event_filter_.AddEventMatcher("event1", AllURLs());
+ int id3 = event_filter_.AddEventMatcher("event2", AllURLs());
+
+ ASSERT_EQ("event1", event_filter_.RemoveEventMatcher(id1));
+ ASSERT_EQ("event1", event_filter_.RemoveEventMatcher(id2));
+ ASSERT_EQ("event2", event_filter_.RemoveEventMatcher(id3));
+}
+
+TEST_F(EventFilterUnittest, InvalidURLFilterCantBeAdded) {
+ scoped_ptr<base::ListValue> filter_list(new base::ListValue());
+ filter_list->Append(new base::ListValue()); // Should be a dict.
+ scoped_ptr<EventMatcher> matcher(
+ MatcherFromURLFilterList(std::move(filter_list)));
+ int id1 = event_filter_.AddEventMatcher("event1", std::move(matcher));
+ EXPECT_TRUE(event_filter_.IsURLMatcherEmpty());
+ ASSERT_EQ(-1, id1);
+}
+
+TEST_F(EventFilterUnittest, EmptyListOfURLFiltersMatchesAllURLs) {
+ scoped_ptr<base::ListValue> filter_list(new base::ListValue());
+ scoped_ptr<EventMatcher> matcher(MatcherFromURLFilterList(
+ scoped_ptr<ListValue>(new ListValue)));
+ int id = event_filter_.AddEventMatcher("event1", std::move(matcher));
+ std::set<int> matches = event_filter_.MatchEvent("event1",
+ google_event_, MSG_ROUTING_NONE);
+ ASSERT_EQ(1u, matches.size());
+ ASSERT_EQ(1u, matches.count(id));
+}
+
+TEST_F(EventFilterUnittest,
+ InternalURLMatcherShouldBeEmptyWhenThereAreNoEventMatchers) {
+ ASSERT_TRUE(event_filter_.IsURLMatcherEmpty());
+ int id = event_filter_.AddEventMatcher("event1",
+ HostSuffixMatcher("google.com"));
+ ASSERT_FALSE(event_filter_.IsURLMatcherEmpty());
+ event_filter_.RemoveEventMatcher(id);
+ ASSERT_TRUE(event_filter_.IsURLMatcherEmpty());
+}
+
+TEST_F(EventFilterUnittest, EmptyURLsShouldBeMatchedByEmptyURLFilters) {
+ int id = event_filter_.AddEventMatcher("event1", AllURLs());
+ std::set<int> matches = event_filter_.MatchEvent(
+ "event1", empty_url_event_, MSG_ROUTING_NONE);
+ ASSERT_EQ(1u, matches.size());
+ ASSERT_EQ(1u, matches.count(id));
+}
+
+TEST_F(EventFilterUnittest,
+ EmptyURLsShouldBeMatchedByEmptyURLFiltersWithAnEmptyItem) {
+ scoped_ptr<EventMatcher> matcher(MatcherFromURLFilterList(ValueAsList(
+ scoped_ptr<Value>(new DictionaryValue()))));
+ int id = event_filter_.AddEventMatcher("event1", std::move(matcher));
+ std::set<int> matches = event_filter_.MatchEvent(
+ "event1", empty_url_event_, MSG_ROUTING_NONE);
+ ASSERT_EQ(1u, matches.size());
+ ASSERT_EQ(1u, matches.count(id));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/event_filtering_info.cc b/chromium/extensions/common/event_filtering_info.cc
new file mode 100644
index 00000000000..a426e1bb98d
--- /dev/null
+++ b/chromium/extensions/common/event_filtering_info.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/event_filtering_info.h"
+
+#include <utility>
+
+#include "base/json/json_writer.h"
+#include "base/values.h"
+
+namespace extensions {
+
+EventFilteringInfo::EventFilteringInfo()
+ : has_url_(false),
+ has_instance_id_(false),
+ instance_id_(0),
+ has_window_type_(false),
+ has_window_exposed_by_default_(false) {}
+
+EventFilteringInfo::EventFilteringInfo(const EventFilteringInfo& other) =
+ default;
+
+EventFilteringInfo::~EventFilteringInfo() {
+}
+
+void EventFilteringInfo::SetWindowType(const std::string& window_type) {
+ window_type_ = window_type;
+ has_window_type_ = true;
+}
+
+void EventFilteringInfo::SetWindowExposedByDefault(const bool exposed) {
+ window_exposed_by_default_ = exposed;
+ has_window_exposed_by_default_ = true;
+}
+
+void EventFilteringInfo::SetURL(const GURL& url) {
+ url_ = url;
+ has_url_ = true;
+}
+
+void EventFilteringInfo::SetInstanceID(int instance_id) {
+ instance_id_ = instance_id;
+ has_instance_id_ = true;
+}
+
+scoped_ptr<base::Value> EventFilteringInfo::AsValue() const {
+ if (IsEmpty())
+ return base::Value::CreateNullValue();
+
+ scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue);
+ if (has_url_)
+ result->SetString("url", url_.spec());
+
+ if (has_instance_id_)
+ result->SetInteger("instanceId", instance_id_);
+
+ if (!service_type_.empty())
+ result->SetString("serviceType", service_type_);
+
+ if (has_window_type_)
+ result->SetString("windowType", window_type_);
+
+ if (has_window_exposed_by_default_)
+ result->SetBoolean("windowExposedByDefault", window_exposed_by_default_);
+
+ return std::move(result);
+}
+
+bool EventFilteringInfo::IsEmpty() const {
+ return !has_window_type_ && !has_url_ && service_type_.empty() &&
+ !has_instance_id_ && !has_window_exposed_by_default_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/event_filtering_info.h b/chromium/extensions/common/event_filtering_info.h
new file mode 100644
index 00000000000..aaa03a4200c
--- /dev/null
+++ b/chromium/extensions/common/event_filtering_info.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_EVENT_FILTERING_INFO_H_
+#define EXTENSIONS_COMMON_EVENT_FILTERING_INFO_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "url/gurl.h"
+
+namespace base {
+class Value;
+}
+
+namespace extensions {
+
+// Extra information about an event that is used in event filtering.
+//
+// This is the information that is matched against criteria specified in JS
+// extension event listeners. Eg:
+//
+// chrome.someApi.onSomeEvent.addListener(cb,
+// {url: [{hostSuffix: 'google.com'}],
+// tabId: 1});
+class EventFilteringInfo {
+ public:
+ EventFilteringInfo();
+ EventFilteringInfo(const EventFilteringInfo& other);
+ ~EventFilteringInfo();
+ void SetWindowExposedByDefault(bool exposed);
+ void SetWindowType(const std::string& window_type);
+ void SetURL(const GURL& url);
+ void SetInstanceID(int instance_id);
+ void SetServiceType(const std::string& service_type) {
+ service_type_ = service_type;
+ }
+
+ // Note: window type & visible are Chrome concepts, so arguably
+ // doesn't belong in the extensions module. If the number of Chrome
+ // concept grows, consider a delegation model with a
+ // ChromeEventFilteringInfo class.
+ bool has_window_type() const { return has_window_type_; }
+ const std::string& window_type() const { return window_type_; }
+
+ // By default events related to windows are filtered based on the
+ // listener's extension. This parameter will be set if the listener
+ // didn't set any filter on window types.
+ bool has_window_exposed_by_default() const {
+ return has_window_exposed_by_default_;
+ }
+ bool window_exposed_by_default() const { return window_exposed_by_default_; }
+
+ bool has_url() const { return has_url_; }
+ const GURL& url() const { return url_; }
+
+ bool has_instance_id() const { return has_instance_id_; }
+ int instance_id() const { return instance_id_; }
+
+ bool has_service_type() const { return !service_type_.empty(); }
+ const std::string& service_type() const { return service_type_; }
+
+ scoped_ptr<base::Value> AsValue() const;
+ bool IsEmpty() const;
+
+ private:
+ bool has_url_;
+ GURL url_;
+ std::string service_type_;
+
+ bool has_instance_id_;
+ int instance_id_;
+
+ bool has_window_type_;
+ std::string window_type_;
+
+ bool has_window_exposed_by_default_;
+ bool window_exposed_by_default_;
+
+ // Allow implicit copy and assignment.
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_EVENT_FILTERING_INFO_H_
diff --git a/chromium/extensions/common/event_matcher.cc b/chromium/extensions/common/event_matcher.cc
new file mode 100644
index 00000000000..05dfdd8df42
--- /dev/null
+++ b/chromium/extensions/common/event_matcher.cc
@@ -0,0 +1,111 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/event_matcher.h"
+
+#include <utility>
+
+#include "base/callback.h"
+#include "extensions/common/event_filtering_info.h"
+
+namespace {
+const char kUrlFiltersKey[] = "url";
+const char kWindowTypesKey[] = "windowTypes";
+}
+
+namespace extensions {
+
+const char kEventFilterServiceTypeKey[] = "serviceType";
+
+EventMatcher::EventMatcher(scoped_ptr<base::DictionaryValue> filter,
+ int routing_id)
+ : filter_(std::move(filter)), routing_id_(routing_id) {}
+
+EventMatcher::~EventMatcher() {
+}
+
+bool EventMatcher::MatchNonURLCriteria(
+ const EventFilteringInfo& event_info) const {
+ if (event_info.has_instance_id()) {
+ return event_info.instance_id() == GetInstanceID();
+ }
+
+ if (event_info.has_window_type()) {
+ for (int i = 0; i < GetWindowTypeCount(); i++) {
+ std::string window_type;
+ if (GetWindowType(i, &window_type) &&
+ window_type == event_info.window_type())
+ return true;
+ }
+ return false;
+ }
+
+ if (event_info.has_window_exposed_by_default()) {
+ // An event with a |window_exposed_by_default| set is only
+ // relevant to the listener if no window type filter is set.
+ if (HasWindowTypes())
+ return false;
+ return event_info.window_exposed_by_default();
+ }
+
+ const std::string& service_type_filter = GetServiceTypeFilter();
+ return service_type_filter.empty() ||
+ service_type_filter == event_info.service_type();
+}
+
+int EventMatcher::GetURLFilterCount() const {
+ base::ListValue* url_filters = nullptr;
+ if (filter_->GetList(kUrlFiltersKey, &url_filters))
+ return url_filters->GetSize();
+ return 0;
+}
+
+bool EventMatcher::GetURLFilter(int i, base::DictionaryValue** url_filter_out) {
+ base::ListValue* url_filters = nullptr;
+ if (filter_->GetList(kUrlFiltersKey, &url_filters)) {
+ return url_filters->GetDictionary(i, url_filter_out);
+ }
+ return false;
+}
+
+bool EventMatcher::HasURLFilters() const {
+ return GetURLFilterCount() != 0;
+}
+
+std::string EventMatcher::GetServiceTypeFilter() const {
+ std::string service_type_filter;
+ filter_->GetStringASCII(kEventFilterServiceTypeKey, &service_type_filter);
+ return service_type_filter;
+}
+
+int EventMatcher::GetInstanceID() const {
+ int instance_id = 0;
+ filter_->GetInteger("instanceId", &instance_id);
+ return instance_id;
+}
+
+int EventMatcher::GetWindowTypeCount() const {
+ base::ListValue* window_type_filters = nullptr;
+ if (filter_->GetList(kWindowTypesKey, &window_type_filters))
+ return window_type_filters->GetSize();
+ return 0;
+}
+
+bool EventMatcher::GetWindowType(int i, std::string* window_type_out) const {
+ base::ListValue* window_types = nullptr;
+ if (filter_->GetList(kWindowTypesKey, &window_types)) {
+ return window_types->GetString(i, window_type_out);
+ }
+ return false;
+}
+
+bool EventMatcher::HasWindowTypes() const {
+ return GetWindowTypeCount() != 0;
+}
+
+int EventMatcher::GetRoutingID() const {
+ return routing_id_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/event_matcher.h b/chromium/extensions/common/event_matcher.h
new file mode 100644
index 00000000000..3bffd241a54
--- /dev/null
+++ b/chromium/extensions/common/event_matcher.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_EVENT_MATCHER_H_
+#define EXTENSIONS_COMMON_EVENT_MATCHER_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+
+namespace extensions {
+
+class EventFilteringInfo;
+
+extern const char kEventFilterServiceTypeKey[];
+
+// Matches EventFilteringInfos against a set of criteria. This is intended to
+// be used by EventFilter which performs efficient URL matching across
+// potentially many EventMatchers itself. This is why this class only exposes
+// MatchNonURLCriteria() - URL matching is handled by EventFilter.
+class EventMatcher {
+ public:
+ EventMatcher(scoped_ptr<base::DictionaryValue> filter,
+ int routing_id);
+ ~EventMatcher();
+
+ // Returns true if |event_info| satisfies this matcher's criteria, not taking
+ // into consideration any URL criteria.
+ bool MatchNonURLCriteria(const EventFilteringInfo& event_info) const;
+
+ int GetURLFilterCount() const;
+ bool GetURLFilter(int i, base::DictionaryValue** url_filter_out);
+
+ int GetWindowTypeCount() const;
+ bool GetWindowType(int i, std::string* window_type_out) const;
+
+ std::string GetServiceTypeFilter() const;
+
+ bool HasURLFilters() const;
+
+ bool HasWindowTypes() const;
+
+ int GetInstanceID() const;
+
+ int GetRoutingID() const;
+
+ base::DictionaryValue* value() const {
+ return filter_.get();
+ }
+
+ private:
+ // Contains a dictionary that corresponds to a single event filter, eg:
+ //
+ // {url: [{hostSuffix: 'google.com'}]}
+ //
+ // The valid filter keys are event-specific.
+ scoped_ptr<base::DictionaryValue> filter_;
+
+ int routing_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventMatcher);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_EVENT_MATCHER_H_
diff --git a/chromium/extensions/common/extension.cc b/chromium/extensions/common/extension.cc
new file mode 100644
index 00000000000..227cb7e515b
--- /dev/null
+++ b/chromium/extensions/common/extension.cc
@@ -0,0 +1,786 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/extension.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <utility>
+
+#include "base/base64.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/i18n/rtl.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/stl_util.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "components/crx_file/id_util.h"
+#include "content/public/common/url_constants.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/feature_switch.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handler.h"
+#include "extensions/common/manifest_handlers/incognito_info.h"
+#include "extensions/common/manifest_handlers/permissions_parser.h"
+#include "extensions/common/permissions/permission_set.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/permissions/permissions_info.h"
+#include "extensions/common/switches.h"
+#include "extensions/common/url_pattern.h"
+#include "net/base/filename_util.h"
+#include "url/url_util.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace values = manifest_values;
+namespace errors = manifest_errors;
+
+namespace {
+
+const int kModernManifestVersion = 2;
+const int kPEMOutputColumns = 64;
+
+// KEY MARKERS
+const char kKeyBeginHeaderMarker[] = "-----BEGIN";
+const char kKeyBeginFooterMarker[] = "-----END";
+const char kKeyInfoEndMarker[] = "KEY-----";
+const char kPublic[] = "PUBLIC";
+const char kPrivate[] = "PRIVATE";
+
+bool ContainsReservedCharacters(const base::FilePath& path) {
+ // We should disallow backslash '\\' as file path separator even on Windows,
+ // because the backslash is not regarded as file path separator on Linux/Mac.
+ // Extensions are cross-platform.
+ // Since FilePath uses backslash '\\' as file path separator on Windows, so we
+ // need to check manually.
+ if (path.value().find('\\') != path.value().npos)
+ return true;
+ return !net::IsSafePortableRelativePath(path);
+}
+
+} // namespace
+
+const int Extension::kInitFromValueFlagBits = 13;
+
+const char Extension::kMimeType[] = "application/x-chrome-extension";
+
+const int Extension::kValidWebExtentSchemes =
+ URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS;
+
+const int Extension::kValidBookmarkAppSchemes = URLPattern::SCHEME_HTTP |
+ URLPattern::SCHEME_HTTPS |
+ URLPattern::SCHEME_EXTENSION;
+
+const int Extension::kValidHostPermissionSchemes = URLPattern::SCHEME_CHROMEUI |
+ URLPattern::SCHEME_HTTP |
+ URLPattern::SCHEME_HTTPS |
+ URLPattern::SCHEME_FILE |
+ URLPattern::SCHEME_FTP;
+
+//
+// Extension
+//
+
+// static
+scoped_refptr<Extension> Extension::Create(const base::FilePath& path,
+ Manifest::Location location,
+ const base::DictionaryValue& value,
+ int flags,
+ std::string* utf8_error) {
+ return Extension::Create(path,
+ location,
+ value,
+ flags,
+ std::string(), // ID is ignored if empty.
+ utf8_error);
+}
+
+// TODO(sungguk): Continue removing std::string errors and replacing
+// with base::string16. See http://crbug.com/71980.
+scoped_refptr<Extension> Extension::Create(const base::FilePath& path,
+ Manifest::Location location,
+ const base::DictionaryValue& value,
+ int flags,
+ const std::string& explicit_id,
+ std::string* utf8_error) {
+ DCHECK(utf8_error);
+ base::string16 error;
+ scoped_ptr<extensions::Manifest> manifest(
+ new extensions::Manifest(
+ location, scoped_ptr<base::DictionaryValue>(value.DeepCopy())));
+
+ if (!InitExtensionID(manifest.get(), path, explicit_id, flags, &error)) {
+ *utf8_error = base::UTF16ToUTF8(error);
+ return NULL;
+ }
+
+ std::vector<InstallWarning> install_warnings;
+ if (!manifest->ValidateManifest(utf8_error, &install_warnings)) {
+ return NULL;
+ }
+
+ scoped_refptr<Extension> extension = new Extension(path, std::move(manifest));
+ extension->install_warnings_.swap(install_warnings);
+
+ if (!extension->InitFromValue(flags, &error)) {
+ *utf8_error = base::UTF16ToUTF8(error);
+ return NULL;
+ }
+
+ return extension;
+}
+
+Manifest::Type Extension::GetType() const {
+ return converted_from_user_script() ?
+ Manifest::TYPE_USER_SCRIPT : manifest_->type();
+}
+
+// static
+GURL Extension::GetResourceURL(const GURL& extension_url,
+ const std::string& relative_path) {
+ DCHECK(extension_url.SchemeIs(extensions::kExtensionScheme));
+ DCHECK_EQ("/", extension_url.path());
+
+ std::string path = relative_path;
+
+ // If the relative path starts with "/", it is "absolute" relative to the
+ // extension base directory, but extension_url is already specified to refer
+ // to that base directory, so strip the leading "/" if present.
+ if (relative_path.size() > 0 && relative_path[0] == '/')
+ path = relative_path.substr(1);
+
+ GURL ret_val = GURL(extension_url.spec() + path);
+ DCHECK(base::StartsWith(ret_val.spec(), extension_url.spec(),
+ base::CompareCase::INSENSITIVE_ASCII));
+
+ return ret_val;
+}
+
+bool Extension::ResourceMatches(const URLPatternSet& pattern_set,
+ const std::string& resource) const {
+ return pattern_set.MatchesURL(extension_url_.Resolve(resource));
+}
+
+ExtensionResource Extension::GetResource(
+ const std::string& relative_path) const {
+ std::string new_path = relative_path;
+ // We have some legacy data where resources have leading slashes.
+ // See: http://crbug.com/121164
+ if (!new_path.empty() && new_path.at(0) == '/')
+ new_path.erase(0, 1);
+ base::FilePath relative_file_path = base::FilePath::FromUTF8Unsafe(new_path);
+ if (ContainsReservedCharacters(relative_file_path))
+ return ExtensionResource();
+ ExtensionResource r(id(), path(), relative_file_path);
+ if ((creation_flags() & Extension::FOLLOW_SYMLINKS_ANYWHERE)) {
+ r.set_follow_symlinks_anywhere();
+ }
+ return r;
+}
+
+ExtensionResource Extension::GetResource(
+ const base::FilePath& relative_file_path) const {
+ if (ContainsReservedCharacters(relative_file_path))
+ return ExtensionResource();
+ ExtensionResource r(id(), path(), relative_file_path);
+ if ((creation_flags() & Extension::FOLLOW_SYMLINKS_ANYWHERE)) {
+ r.set_follow_symlinks_anywhere();
+ }
+ return r;
+}
+
+// TODO(rafaelw): Move ParsePEMKeyBytes, ProducePEM & FormatPEMForOutput to a
+// util class in base:
+// http://code.google.com/p/chromium/issues/detail?id=13572
+// static
+bool Extension::ParsePEMKeyBytes(const std::string& input,
+ std::string* output) {
+ DCHECK(output);
+ if (!output)
+ return false;
+ if (input.length() == 0)
+ return false;
+
+ std::string working = input;
+ if (base::StartsWith(working, kKeyBeginHeaderMarker,
+ base::CompareCase::SENSITIVE)) {
+ working = base::CollapseWhitespaceASCII(working, true);
+ size_t header_pos = working.find(kKeyInfoEndMarker,
+ sizeof(kKeyBeginHeaderMarker) - 1);
+ if (header_pos == std::string::npos)
+ return false;
+ size_t start_pos = header_pos + sizeof(kKeyInfoEndMarker) - 1;
+ size_t end_pos = working.rfind(kKeyBeginFooterMarker);
+ if (end_pos == std::string::npos)
+ return false;
+ if (start_pos >= end_pos)
+ return false;
+
+ working = working.substr(start_pos, end_pos - start_pos);
+ if (working.length() == 0)
+ return false;
+ }
+
+ return base::Base64Decode(working, output);
+}
+
+// static
+bool Extension::ProducePEM(const std::string& input, std::string* output) {
+ DCHECK(output);
+ if (input.empty())
+ return false;
+ base::Base64Encode(input, output);
+ return true;
+}
+
+// static
+bool Extension::FormatPEMForFileOutput(const std::string& input,
+ std::string* output,
+ bool is_public) {
+ DCHECK(output);
+ if (input.length() == 0)
+ return false;
+ *output = "";
+ output->append(kKeyBeginHeaderMarker);
+ output->append(" ");
+ output->append(is_public ? kPublic : kPrivate);
+ output->append(" ");
+ output->append(kKeyInfoEndMarker);
+ output->append("\n");
+ for (size_t i = 0; i < input.length(); ) {
+ int slice = std::min<int>(input.length() - i, kPEMOutputColumns);
+ output->append(input.substr(i, slice));
+ output->append("\n");
+ i += slice;
+ }
+ output->append(kKeyBeginFooterMarker);
+ output->append(" ");
+ output->append(is_public ? kPublic : kPrivate);
+ output->append(" ");
+ output->append(kKeyInfoEndMarker);
+ output->append("\n");
+
+ return true;
+}
+
+// static
+GURL Extension::GetBaseURLFromExtensionId(const std::string& extension_id) {
+ return GURL(std::string(extensions::kExtensionScheme) +
+ url::kStandardSchemeSeparator + extension_id + "/");
+}
+
+bool Extension::ShowConfigureContextMenus() const {
+ // Normally we don't show a context menu for component actions, but when
+ // re-design is enabled we show them in the toolbar (if they have an action),
+ // and it is weird to have a random button that has no context menu when the
+ // rest do.
+ if (location() == Manifest::COMPONENT ||
+ location() == Manifest::EXTERNAL_COMPONENT)
+ return FeatureSwitch::extension_action_redesign()->IsEnabled();
+
+ return true;
+}
+
+bool Extension::OverlapsWithOrigin(const GURL& origin) const {
+ if (url() == origin)
+ return true;
+
+ if (web_extent().is_empty())
+ return false;
+
+ // Note: patterns and extents ignore port numbers.
+ URLPattern origin_only_pattern(kValidWebExtentSchemes);
+ if (!origin_only_pattern.SetScheme(origin.scheme()))
+ return false;
+ origin_only_pattern.SetHost(origin.host());
+ origin_only_pattern.SetPath("/*");
+
+ URLPatternSet origin_only_pattern_list;
+ origin_only_pattern_list.AddPattern(origin_only_pattern);
+
+ return web_extent().OverlapsWith(origin_only_pattern_list);
+}
+
+bool Extension::RequiresSortOrdinal() const {
+ return is_app() && (display_in_launcher_ || display_in_new_tab_page_);
+}
+
+bool Extension::ShouldDisplayInAppLauncher() const {
+ // Only apps should be displayed in the launcher.
+ return is_app() && display_in_launcher_;
+}
+
+bool Extension::ShouldDisplayInNewTabPage() const {
+ // Only apps should be displayed on the NTP.
+ return is_app() && display_in_new_tab_page_;
+}
+
+bool Extension::ShouldDisplayInExtensionSettings() const {
+ // Don't show for themes since the settings UI isn't really useful for them.
+ if (is_theme())
+ return false;
+
+ // Don't show component extensions and invisible apps.
+ if (ShouldNotBeVisible())
+ return false;
+
+ // Always show unpacked extensions and apps.
+ if (Manifest::IsUnpackedLocation(location()))
+ return true;
+
+ // Unless they are unpacked, never show hosted apps. Note: We intentionally
+ // show packaged apps and platform apps because there are some pieces of
+ // functionality that are only available in chrome://extensions/ but which
+ // are needed for packaged and platform apps. For example, inspecting
+ // background pages. See http://crbug.com/116134.
+ if (is_hosted_app())
+ return false;
+
+ return true;
+}
+
+bool Extension::ShouldNotBeVisible() const {
+ // Don't show component extensions because they are only extensions as an
+ // implementation detail of Chrome.
+ if (extensions::Manifest::IsComponentLocation(location()) &&
+ !base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kShowComponentExtensionOptions)) {
+ return true;
+ }
+
+ // Always show unpacked extensions and apps.
+ if (Manifest::IsUnpackedLocation(location()))
+ return false;
+
+ // Don't show apps that aren't visible in either launcher or ntp.
+ if (is_app() && !ShouldDisplayInAppLauncher() && !ShouldDisplayInNewTabPage())
+ return true;
+
+ return false;
+}
+
+Extension::ManifestData* Extension::GetManifestData(const std::string& key)
+ const {
+ DCHECK(finished_parsing_manifest_ || thread_checker_.CalledOnValidThread());
+ ManifestDataMap::const_iterator iter = manifest_data_.find(key);
+ if (iter != manifest_data_.end())
+ return iter->second.get();
+ return NULL;
+}
+
+void Extension::SetManifestData(const std::string& key,
+ Extension::ManifestData* data) {
+ DCHECK(!finished_parsing_manifest_ && thread_checker_.CalledOnValidThread());
+ manifest_data_[key] = scoped_ptr<ManifestData>(data);
+}
+
+Manifest::Location Extension::location() const {
+ return manifest_->location();
+}
+
+const std::string& Extension::id() const {
+ return manifest_->extension_id();
+}
+
+const std::string Extension::VersionString() const {
+ return version()->GetString();
+}
+
+const std::string Extension::GetVersionForDisplay() const {
+ if (version_name_.size() > 0)
+ return version_name_;
+ return VersionString();
+}
+
+void Extension::AddInstallWarning(const InstallWarning& new_warning) {
+ install_warnings_.push_back(new_warning);
+}
+
+void Extension::AddInstallWarnings(
+ const std::vector<InstallWarning>& new_warnings) {
+ install_warnings_.insert(install_warnings_.end(),
+ new_warnings.begin(), new_warnings.end());
+}
+
+bool Extension::is_app() const {
+ return manifest()->is_app();
+}
+
+bool Extension::is_platform_app() const {
+ return manifest()->is_platform_app();
+}
+
+bool Extension::is_hosted_app() const {
+ return manifest()->is_hosted_app();
+}
+
+bool Extension::is_legacy_packaged_app() const {
+ return manifest()->is_legacy_packaged_app();
+}
+
+bool Extension::is_extension() const {
+ return manifest()->is_extension();
+}
+
+bool Extension::is_shared_module() const {
+ return manifest()->is_shared_module();
+}
+
+bool Extension::is_theme() const {
+ return manifest()->is_theme();
+}
+
+void Extension::AddWebExtentPattern(const URLPattern& pattern) {
+ // Bookmark apps are permissionless.
+ if (from_bookmark())
+ return;
+
+ extent_.AddPattern(pattern);
+}
+
+// static
+bool Extension::InitExtensionID(extensions::Manifest* manifest,
+ const base::FilePath& path,
+ const std::string& explicit_id,
+ int creation_flags,
+ base::string16* error) {
+ if (!explicit_id.empty()) {
+ manifest->set_extension_id(explicit_id);
+ return true;
+ }
+
+ if (manifest->HasKey(keys::kPublicKey)) {
+ std::string public_key;
+ std::string public_key_bytes;
+ if (!manifest->GetString(keys::kPublicKey, &public_key) ||
+ !ParsePEMKeyBytes(public_key, &public_key_bytes)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidKey);
+ return false;
+ }
+ std::string extension_id = crx_file::id_util::GenerateId(public_key_bytes);
+ manifest->set_extension_id(extension_id);
+ return true;
+ }
+
+ if (creation_flags & REQUIRE_KEY) {
+ *error = base::ASCIIToUTF16(errors::kInvalidKey);
+ return false;
+ } else {
+ // If there is a path, we generate the ID from it. This is useful for
+ // development mode, because it keeps the ID stable across restarts and
+ // reloading the extension.
+ std::string extension_id = crx_file::id_util::GenerateIdForPath(path);
+ if (extension_id.empty()) {
+ NOTREACHED() << "Could not create ID from path.";
+ return false;
+ }
+ manifest->set_extension_id(extension_id);
+ return true;
+ }
+}
+
+Extension::Extension(const base::FilePath& path,
+ scoped_ptr<extensions::Manifest> manifest)
+ : manifest_version_(0),
+ converted_from_user_script_(false),
+ manifest_(manifest.release()),
+ finished_parsing_manifest_(false),
+ display_in_launcher_(true),
+ display_in_new_tab_page_(true),
+ wants_file_access_(false),
+ creation_flags_(0) {
+ DCHECK(path.empty() || path.IsAbsolute());
+ path_ = crx_file::id_util::MaybeNormalizePath(path);
+}
+
+Extension::~Extension() {
+}
+
+bool Extension::InitFromValue(int flags, base::string16* error) {
+ DCHECK(error);
+
+ creation_flags_ = flags;
+
+ // Important to load manifest version first because many other features
+ // depend on its value.
+ if (!LoadManifestVersion(error))
+ return false;
+
+ if (!LoadRequiredFeatures(error))
+ return false;
+
+ // We don't need to validate because InitExtensionID already did that.
+ manifest_->GetString(keys::kPublicKey, &public_key_);
+
+ extension_url_ = Extension::GetBaseURLFromExtensionId(id());
+
+ // Load App settings. LoadExtent at least has to be done before
+ // ParsePermissions(), because the valid permissions depend on what type of
+ // package this is.
+ if (is_app() && !LoadAppFeatures(error))
+ return false;
+
+ permissions_parser_.reset(new PermissionsParser());
+ if (!permissions_parser_->Parse(this, error))
+ return false;
+
+ if (manifest_->HasKey(keys::kConvertedFromUserScript)) {
+ manifest_->GetBoolean(keys::kConvertedFromUserScript,
+ &converted_from_user_script_);
+ }
+
+ if (!LoadSharedFeatures(error))
+ return false;
+
+ permissions_parser_->Finalize(this);
+ permissions_parser_.reset();
+
+ finished_parsing_manifest_ = true;
+
+ permissions_data_.reset(new PermissionsData(this));
+
+ return true;
+}
+
+bool Extension::LoadRequiredFeatures(base::string16* error) {
+ if (!LoadName(error) ||
+ !LoadVersion(error))
+ return false;
+ return true;
+}
+
+bool Extension::LoadName(base::string16* error) {
+ base::string16 localized_name;
+ if (!manifest_->GetString(keys::kName, &localized_name)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidName);
+ return false;
+ }
+ non_localized_name_ = base::UTF16ToUTF8(localized_name);
+ base::i18n::AdjustStringForLocaleDirection(&localized_name);
+ name_ = base::UTF16ToUTF8(localized_name);
+ return true;
+}
+
+bool Extension::LoadVersion(base::string16* error) {
+ std::string version_str;
+ if (!manifest_->GetString(keys::kVersion, &version_str)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidVersion);
+ return false;
+ }
+ version_.reset(new Version(version_str));
+ if (!version_->IsValid() || version_->components().size() > 4) {
+ *error = base::ASCIIToUTF16(errors::kInvalidVersion);
+ return false;
+ }
+ if (manifest_->HasKey(keys::kVersionName)) {
+ if (!manifest_->GetString(keys::kVersionName, &version_name_)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidVersionName);
+ return false;
+ }
+ }
+ return true;
+}
+
+bool Extension::LoadAppFeatures(base::string16* error) {
+ if (!LoadExtent(keys::kWebURLs, &extent_,
+ errors::kInvalidWebURLs, errors::kInvalidWebURL, error)) {
+ return false;
+ }
+ if (manifest_->HasKey(keys::kDisplayInLauncher) &&
+ !manifest_->GetBoolean(keys::kDisplayInLauncher, &display_in_launcher_)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidDisplayInLauncher);
+ return false;
+ }
+ if (manifest_->HasKey(keys::kDisplayInNewTabPage)) {
+ if (!manifest_->GetBoolean(keys::kDisplayInNewTabPage,
+ &display_in_new_tab_page_)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidDisplayInNewTabPage);
+ return false;
+ }
+ } else {
+ // Inherit default from display_in_launcher property.
+ display_in_new_tab_page_ = display_in_launcher_;
+ }
+ return true;
+}
+
+bool Extension::LoadExtent(const char* key,
+ URLPatternSet* extent,
+ const char* list_error,
+ const char* value_error,
+ base::string16* error) {
+ const base::Value* temp_pattern_value = NULL;
+ if (!manifest_->Get(key, &temp_pattern_value))
+ return true;
+
+ const base::ListValue* pattern_list = NULL;
+ if (!temp_pattern_value->GetAsList(&pattern_list)) {
+ *error = base::ASCIIToUTF16(list_error);
+ return false;
+ }
+
+ for (size_t i = 0; i < pattern_list->GetSize(); ++i) {
+ std::string pattern_string;
+ if (!pattern_list->GetString(i, &pattern_string)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ value_error, base::SizeTToString(i), errors::kExpectString);
+ return false;
+ }
+
+ URLPattern pattern(kValidWebExtentSchemes);
+ URLPattern::ParseResult parse_result = pattern.Parse(pattern_string);
+ if (parse_result == URLPattern::PARSE_ERROR_EMPTY_PATH) {
+ pattern_string += "/";
+ parse_result = pattern.Parse(pattern_string);
+ }
+
+ if (parse_result != URLPattern::PARSE_SUCCESS) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ value_error, base::SizeTToString(i),
+ URLPattern::GetParseResultString(parse_result));
+ return false;
+ }
+
+ // Do not allow authors to claim "<all_urls>".
+ if (pattern.match_all_urls()) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ value_error, base::SizeTToString(i),
+ errors::kCannotClaimAllURLsInExtent);
+ return false;
+ }
+
+ // Do not allow authors to claim "*" for host.
+ if (pattern.host().empty()) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ value_error, base::SizeTToString(i),
+ errors::kCannotClaimAllHostsInExtent);
+ return false;
+ }
+
+ // We do not allow authors to put wildcards in their paths. Instead, we
+ // imply one at the end.
+ if (pattern.path().find('*') != std::string::npos) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ value_error, base::SizeTToString(i), errors::kNoWildCardsInPaths);
+ return false;
+ }
+ pattern.SetPath(pattern.path() + '*');
+
+ extent->AddPattern(pattern);
+ }
+
+ return true;
+}
+
+bool Extension::LoadSharedFeatures(base::string16* error) {
+ if (!LoadDescription(error) ||
+ !ManifestHandler::ParseExtension(this, error) ||
+ !LoadShortName(error))
+ return false;
+
+ return true;
+}
+
+bool Extension::LoadDescription(base::string16* error) {
+ if (manifest_->HasKey(keys::kDescription) &&
+ !manifest_->GetString(keys::kDescription, &description_)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidDescription);
+ return false;
+ }
+ return true;
+}
+
+bool Extension::LoadManifestVersion(base::string16* error) {
+ // Get the original value out of the dictionary so that we can validate it
+ // more strictly.
+ if (manifest_->value()->HasKey(keys::kManifestVersion)) {
+ int manifest_version = 1;
+ if (!manifest_->GetInteger(keys::kManifestVersion, &manifest_version) ||
+ manifest_version < 1) {
+ *error = base::ASCIIToUTF16(errors::kInvalidManifestVersion);
+ return false;
+ }
+ }
+
+ manifest_version_ = manifest_->GetManifestVersion();
+ if (manifest_version_ < kModernManifestVersion &&
+ ((creation_flags_ & REQUIRE_MODERN_MANIFEST_VERSION &&
+ !base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAllowLegacyExtensionManifests)) ||
+ GetType() == Manifest::TYPE_PLATFORM_APP)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidManifestVersionOld,
+ base::IntToString(kModernManifestVersion),
+ is_platform_app() ? "apps" : "extensions");
+ return false;
+ }
+
+ return true;
+}
+
+bool Extension::LoadShortName(base::string16* error) {
+ if (manifest_->HasKey(keys::kShortName)) {
+ base::string16 localized_short_name;
+ if (!manifest_->GetString(keys::kShortName, &localized_short_name) ||
+ localized_short_name.empty()) {
+ *error = base::ASCIIToUTF16(errors::kInvalidShortName);
+ return false;
+ }
+
+ base::i18n::AdjustStringForLocaleDirection(&localized_short_name);
+ short_name_ = base::UTF16ToUTF8(localized_short_name);
+ } else {
+ short_name_ = name_;
+ }
+ return true;
+}
+
+ExtensionInfo::ExtensionInfo(const base::DictionaryValue* manifest,
+ const std::string& id,
+ const base::FilePath& path,
+ Manifest::Location location)
+ : extension_id(id),
+ extension_path(path),
+ extension_location(location) {
+ if (manifest)
+ extension_manifest.reset(manifest->DeepCopy());
+}
+
+ExtensionInfo::~ExtensionInfo() {}
+
+InstalledExtensionInfo::InstalledExtensionInfo(
+ const Extension* extension,
+ bool is_update,
+ bool from_ephemeral,
+ const std::string& old_name)
+ : extension(extension),
+ is_update(is_update),
+ from_ephemeral(from_ephemeral),
+ old_name(old_name) {}
+
+UnloadedExtensionInfo::UnloadedExtensionInfo(
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason)
+ : reason(reason),
+ extension(extension) {}
+
+UpdatedExtensionPermissionsInfo::UpdatedExtensionPermissionsInfo(
+ const Extension* extension,
+ const PermissionSet& permissions,
+ Reason reason)
+ : reason(reason), extension(extension), permissions(permissions) {}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/extension.h b/chromium/extensions/common/extension.h
new file mode 100644
index 00000000000..13a86b74e56
--- /dev/null
+++ b/chromium/extensions/common/extension.h
@@ -0,0 +1,575 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_EXTENSION_H_
+#define EXTENSIONS_COMMON_EXTENSION_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "extensions/common/extension_resource.h"
+#include "extensions/common/install_warning.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/url_pattern_set.h"
+#include "url/gurl.h"
+
+#if !defined(ENABLE_EXTENSIONS)
+#error "Extensions must be enabled"
+#endif
+
+namespace base {
+class DictionaryValue;
+class Version;
+}
+
+namespace extensions {
+class PermissionSet;
+class PermissionsData;
+class PermissionsParser;
+
+// Uniquely identifies an Extension, using 32 characters from the alphabet
+// 'a'-'p'. An empty string represents "no extension".
+//
+// Note: If this gets used heavily in files that don't otherwise need to include
+// extension.h, we should pull it into a dedicated header.
+typedef std::string ExtensionId;
+
+// Represents a Chrome extension.
+// Once created, an Extension object is immutable, with the exception of its
+// RuntimeData. This makes it safe to use on any thread, since access to the
+// RuntimeData is protected by a lock.
+class Extension : public base::RefCountedThreadSafe<Extension> {
+ public:
+ enum State {
+ DISABLED = 0,
+ ENABLED,
+ // An external extension that the user uninstalled. We should not reinstall
+ // such extensions on startup.
+ EXTERNAL_EXTENSION_UNINSTALLED,
+ // DEPRECATED: Special state for component extensions.
+ // Maintained as a placeholder since states may be stored to disk.
+ ENABLED_COMPONENT_DEPRECATED,
+ // Add new states here as this enum is stored in prefs.
+ NUM_STATES
+ };
+
+ // Reasons an extension may be disabled. These are used in histograms, so do
+ // not remove/reorder entries - only add at the end just before
+ // DISABLE_REASON_LAST (and update the shift value for it). Also remember to
+ // update the enum listing in tools/metrics/histograms.xml.
+ // Also carefully consider if your reason should sync to other devices, and if
+ // so, add it to kKnownSyncableDisableReasons in extension_sync_service.cc.
+ enum DisableReason {
+ DISABLE_NONE = 0,
+ DISABLE_USER_ACTION = 1 << 0,
+ DISABLE_PERMISSIONS_INCREASE = 1 << 1,
+ DISABLE_RELOAD = 1 << 2,
+ DISABLE_UNSUPPORTED_REQUIREMENT = 1 << 3,
+ DISABLE_SIDELOAD_WIPEOUT = 1 << 4,
+ DEPRECATED_DISABLE_UNKNOWN_FROM_SYNC = 1 << 5,
+ // DISABLE_PERMISSIONS_CONSENT = 1 << 6, // Deprecated.
+ // DISABLE_KNOWN_DISABLED = 1 << 7, // Deprecated.
+ DISABLE_NOT_VERIFIED = 1 << 8, // Disabled because we could not verify
+ // the install.
+ DISABLE_GREYLIST = 1 << 9,
+ DISABLE_CORRUPTED = 1 << 10,
+ DISABLE_REMOTE_INSTALL = 1 << 11,
+ // DISABLE_INACTIVE_EPHEMERAL_APP = 1 << 12, // Deprecated.
+ DISABLE_EXTERNAL_EXTENSION = 1 << 13, // External extensions might be
+ // disabled for user prompting.
+ DISABLE_UPDATE_REQUIRED_BY_POLICY = 1 << 14, // Doesn't meet minimum
+ // version requirement.
+ DISABLE_REASON_LAST = 1 << 15, // This should always be the last value
+ };
+
+ // A base class for parsed manifest data that APIs want to store on
+ // the extension. Related to base::SupportsUserData, but with an immutable
+ // thread-safe interface to match Extension.
+ struct ManifestData {
+ virtual ~ManifestData() {}
+ };
+
+ // Do not change the order of entries or remove entries in this list
+ // as this is used in UMA_HISTOGRAM_ENUMERATIONs about extensions.
+ enum InitFromValueFlags {
+ NO_FLAGS = 0,
+
+ // Usually, the id of an extension is generated by the "key" property of
+ // its manifest, but if |REQUIRE_KEY| is not set, a temporary ID will be
+ // generated based on the path.
+ REQUIRE_KEY = 1 << 0,
+
+ // Requires the extension to have an up-to-date manifest version.
+ // Typically, we'll support multiple manifest versions during a version
+ // transition. This flag signals that we want to require the most modern
+ // manifest version that Chrome understands.
+ REQUIRE_MODERN_MANIFEST_VERSION = 1 << 1,
+
+ // |ALLOW_FILE_ACCESS| indicates that the user is allowing this extension
+ // to have file access. If it's not present, then permissions and content
+ // scripts that match file:/// URLs will be filtered out.
+ ALLOW_FILE_ACCESS = 1 << 2,
+
+ // |FROM_WEBSTORE| indicates that the extension was installed from the
+ // Chrome Web Store.
+ FROM_WEBSTORE = 1 << 3,
+
+ // |FROM_BOOKMARK| indicates the extension is a bookmark app which has been
+ // generated from a web page. Bookmark apps have no permissions or extent
+ // and launch the web page they are created from when run.
+ FROM_BOOKMARK = 1 << 4,
+
+ // |FOLLOW_SYMLINKS_ANYWHERE| means that resources can be symlinks to
+ // anywhere in the filesystem, rather than being restricted to the
+ // extension directory.
+ FOLLOW_SYMLINKS_ANYWHERE = 1 << 5,
+
+ // |ERROR_ON_PRIVATE_KEY| means that private keys inside an
+ // extension should be errors rather than warnings.
+ ERROR_ON_PRIVATE_KEY = 1 << 6,
+
+ // |WAS_INSTALLED_BY_DEFAULT| installed by default when the profile was
+ // created.
+ WAS_INSTALLED_BY_DEFAULT = 1 << 7,
+
+ // Unused - was part of an abandoned experiment.
+ REQUIRE_PERMISSIONS_CONSENT = 1 << 8,
+
+ // Unused - this flag has been moved to ExtensionPrefs.
+ IS_EPHEMERAL = 1 << 9,
+
+ // |WAS_INSTALLED_BY_OEM| installed by an OEM (e.g on Chrome OS) and should
+ // be placed in a special OEM folder in the App Launcher. Note: OEM apps are
+ // also installed by Default (i.e. WAS_INSTALLED_BY_DEFAULT is also true).
+ WAS_INSTALLED_BY_OEM = 1 << 10,
+
+ // |WAS_INSTALLED_BY_CUSTODIAN| means this extension was installed by the
+ // custodian of a supervised user.
+ WAS_INSTALLED_BY_CUSTODIAN = 1 << 11,
+
+ // |MAY_BE_UNTRUSTED| indicates that this extension came from a potentially
+ // unsafe source (e.g., sideloaded from a local CRX file via the Windows
+ // registry). Such extensions may be subjected to additional constraints
+ // before they are fully installed and enabled.
+ MAY_BE_UNTRUSTED = 1 << 12,
+
+ // When adding new flags, make sure to update kInitFromValueFlagBits.
+ };
+
+ // This is the highest bit index of the flags defined above.
+ static const int kInitFromValueFlagBits;
+
+ static scoped_refptr<Extension> Create(const base::FilePath& path,
+ Manifest::Location location,
+ const base::DictionaryValue& value,
+ int flags,
+ std::string* error);
+
+ // In a few special circumstances, we want to create an Extension and give it
+ // an explicit id. Most consumers should just use the other Create() method.
+ static scoped_refptr<Extension> Create(const base::FilePath& path,
+ Manifest::Location location,
+ const base::DictionaryValue& value,
+ int flags,
+ const ExtensionId& explicit_id,
+ std::string* error);
+
+ // Valid schemes for web extent URLPatterns.
+ static const int kValidWebExtentSchemes;
+
+ // Valid schemes for bookmark app installs.
+ static const int kValidBookmarkAppSchemes;
+
+ // Valid schemes for host permission URLPatterns.
+ static const int kValidHostPermissionSchemes;
+
+ // The mimetype used for extensions.
+ static const char kMimeType[];
+
+ // See Type definition in Manifest.
+ Manifest::Type GetType() const;
+
+ // Returns an absolute url to a resource inside of an extension. The
+ // |extension_url| argument should be the url() from an Extension object. The
+ // |relative_path| can be untrusted user input. The returned URL will either
+ // be invalid() or a child of |extension_url|.
+ // NOTE: Static so that it can be used from multiple threads.
+ static GURL GetResourceURL(const GURL& extension_url,
+ const std::string& relative_path);
+ GURL GetResourceURL(const std::string& relative_path) const {
+ return GetResourceURL(url(), relative_path);
+ }
+
+ // Returns true if the resource matches a pattern in the pattern_set.
+ bool ResourceMatches(const URLPatternSet& pattern_set,
+ const std::string& resource) const;
+
+ // Returns an extension resource object. |relative_path| should be UTF8
+ // encoded.
+ ExtensionResource GetResource(const std::string& relative_path) const;
+
+ // As above, but with |relative_path| following the file system's encoding.
+ ExtensionResource GetResource(const base::FilePath& relative_path) const;
+
+ // |input| is expected to be the text of an rsa public or private key. It
+ // tolerates the presence or absence of bracking header/footer like this:
+ // -----(BEGIN|END) [RSA PUBLIC/PRIVATE] KEY-----
+ // and may contain newlines.
+ static bool ParsePEMKeyBytes(const std::string& input, std::string* output);
+
+ // Does a simple base64 encoding of |input| into |output|.
+ static bool ProducePEM(const std::string& input, std::string* output);
+
+ // Expects base64 encoded |input| and formats into |output| including
+ // the appropriate header & footer.
+ static bool FormatPEMForFileOutput(const std::string& input,
+ std::string* output,
+ bool is_public);
+
+ // Returns the base extension url for a given |extension_id|.
+ static GURL GetBaseURLFromExtensionId(const ExtensionId& extension_id);
+
+ // Whether context menu should be shown for page and browser actions.
+ bool ShowConfigureContextMenus() const;
+
+ // Returns true if this extension or app includes areas within |origin|.
+ bool OverlapsWithOrigin(const GURL& origin) const;
+
+ // Returns true if the extension requires a valid ordinal for sorting, e.g.,
+ // for displaying in a launcher or new tab page.
+ bool RequiresSortOrdinal() const;
+
+ // Returns true if the extension should be displayed in the app launcher.
+ bool ShouldDisplayInAppLauncher() const;
+
+ // Returns true if the extension should be displayed in the browser NTP.
+ bool ShouldDisplayInNewTabPage() const;
+
+ // Returns true if the extension should be displayed in the extension
+ // settings page (i.e. chrome://extensions).
+ bool ShouldDisplayInExtensionSettings() const;
+
+ // Returns true if the extension should not be shown anywhere. This is
+ // mostly the same as the extension being a component extension, but also
+ // includes non-component apps that are hidden from the app launcher and ntp.
+ bool ShouldNotBeVisible() const;
+
+ // Get the manifest data associated with the key, or NULL if there is none.
+ // Can only be called after InitValue is finished.
+ ManifestData* GetManifestData(const std::string& key) const;
+
+ // Sets |data| to be associated with the key. Takes ownership of |data|.
+ // Can only be called before InitValue is finished. Not thread-safe;
+ // all SetManifestData calls should be on only one thread.
+ void SetManifestData(const std::string& key, ManifestData* data);
+
+ // Accessors:
+
+ const base::FilePath& path() const { return path_; }
+ const GURL& url() const { return extension_url_; }
+ Manifest::Location location() const;
+ const ExtensionId& id() const;
+ const base::Version* version() const { return version_.get(); }
+ const std::string& version_name() const { return version_name_; }
+ const std::string VersionString() const;
+ const std::string GetVersionForDisplay() const;
+ const std::string& name() const { return name_; }
+ const std::string& short_name() const { return short_name_; }
+ const std::string& non_localized_name() const { return non_localized_name_; }
+ // Base64-encoded version of the key used to sign this extension.
+ // In pseudocode, returns
+ // base::Base64Encode(RSAPrivateKey(pem_file).ExportPublicKey()).
+ const std::string& public_key() const { return public_key_; }
+ const std::string& description() const { return description_; }
+ int manifest_version() const { return manifest_version_; }
+ bool converted_from_user_script() const {
+ return converted_from_user_script_;
+ }
+ PermissionsParser* permissions_parser() { return permissions_parser_.get(); }
+ const PermissionsParser* permissions_parser() const {
+ return permissions_parser_.get();
+ }
+
+ const PermissionsData* permissions_data() const {
+ return permissions_data_.get();
+ }
+
+ // Appends |new_warning[s]| to install_warnings_.
+ void AddInstallWarning(const InstallWarning& new_warning);
+ void AddInstallWarnings(const std::vector<InstallWarning>& new_warnings);
+ const std::vector<InstallWarning>& install_warnings() const {
+ return install_warnings_;
+ }
+ const extensions::Manifest* manifest() const {
+ return manifest_.get();
+ }
+ bool wants_file_access() const { return wants_file_access_; }
+ // TODO(rdevlin.cronin): This is needed for ContentScriptsHandler, and should
+ // be moved out as part of crbug.com/159265. This should not be used anywhere
+ // else.
+ void set_wants_file_access(bool wants_file_access) {
+ wants_file_access_ = wants_file_access;
+ }
+ int creation_flags() const { return creation_flags_; }
+ bool from_webstore() const { return (creation_flags_ & FROM_WEBSTORE) != 0; }
+ bool from_bookmark() const { return (creation_flags_ & FROM_BOOKMARK) != 0; }
+ bool may_be_untrusted() const {
+ return (creation_flags_ & MAY_BE_UNTRUSTED) != 0;
+ }
+ bool was_installed_by_default() const {
+ return (creation_flags_ & WAS_INSTALLED_BY_DEFAULT) != 0;
+ }
+ bool was_installed_by_oem() const {
+ return (creation_flags_ & WAS_INSTALLED_BY_OEM) != 0;
+ }
+ bool was_installed_by_custodian() const {
+ return (creation_flags_ & WAS_INSTALLED_BY_CUSTODIAN) != 0;
+ }
+
+ // Type-related queries.
+ bool is_app() const;
+ bool is_platform_app() const;
+ bool is_hosted_app() const;
+ bool is_legacy_packaged_app() const;
+ bool is_extension() const;
+ bool is_shared_module() const;
+ bool is_theme() const;
+
+ void AddWebExtentPattern(const URLPattern& pattern);
+ const URLPatternSet& web_extent() const { return extent_; }
+
+ private:
+ friend class base::RefCountedThreadSafe<Extension>;
+
+ // Chooses the extension ID for an extension based on a variety of criteria.
+ // The chosen ID will be set in |manifest|.
+ static bool InitExtensionID(extensions::Manifest* manifest,
+ const base::FilePath& path,
+ const ExtensionId& explicit_id,
+ int creation_flags,
+ base::string16* error);
+
+ Extension(const base::FilePath& path,
+ scoped_ptr<extensions::Manifest> manifest);
+ virtual ~Extension();
+
+ // Initialize the extension from a parsed manifest.
+ // TODO(aa): Rename to just Init()? There's no Value here anymore.
+ // TODO(aa): It is really weird the way this class essentially contains a copy
+ // of the underlying DictionaryValue in its members. We should decide to
+ // either wrap the DictionaryValue and go with that only, or we should parse
+ // into strong types and discard the value. But doing both is bad.
+ bool InitFromValue(int flags, base::string16* error);
+
+ // The following are helpers for InitFromValue to load various features of the
+ // extension from the manifest.
+
+ bool LoadRequiredFeatures(base::string16* error);
+ bool LoadName(base::string16* error);
+ bool LoadVersion(base::string16* error);
+
+ bool LoadAppFeatures(base::string16* error);
+ bool LoadExtent(const char* key,
+ URLPatternSet* extent,
+ const char* list_error,
+ const char* value_error,
+ base::string16* error);
+
+ bool LoadSharedFeatures(base::string16* error);
+ bool LoadDescription(base::string16* error);
+ bool LoadManifestVersion(base::string16* error);
+ bool LoadShortName(base::string16* error);
+
+ bool CheckMinimumChromeVersion(base::string16* error) const;
+
+ // The extension's human-readable name. Name is used for display purpose. It
+ // might be wrapped with unicode bidi control characters so that it is
+ // displayed correctly in RTL context.
+ // NOTE: Name is UTF-8 and may contain non-ascii characters.
+ std::string name_;
+
+ // A non-localized version of the extension's name. This is useful for
+ // debug output.
+ std::string non_localized_name_;
+
+ // A short version of the extension's name. This can be used as an alternative
+ // to the name where there is insufficient space to display the full name. If
+ // an extension has not explicitly specified a short name, the value of this
+ // member variable will be the full name rather than an empty string.
+ std::string short_name_;
+
+ // The version of this extension's manifest. We increase the manifest
+ // version when making breaking changes to the extension system.
+ // Version 1 was the first manifest version (implied by a lack of a
+ // manifest_version attribute in the extension's manifest). We initialize
+ // this member variable to 0 to distinguish the "uninitialized" case from
+ // the case when we know the manifest version actually is 1.
+ int manifest_version_;
+
+ // The absolute path to the directory the extension is stored in.
+ base::FilePath path_;
+
+ // Defines the set of URLs in the extension's web content.
+ URLPatternSet extent_;
+
+ // The parser for the manifest's permissions. This is NULL anytime not during
+ // initialization.
+ // TODO(rdevlin.cronin): This doesn't really belong here.
+ scoped_ptr<PermissionsParser> permissions_parser_;
+
+ // The active permissions for the extension.
+ scoped_ptr<PermissionsData> permissions_data_;
+
+ // Any warnings that occurred when trying to create/parse the extension.
+ std::vector<InstallWarning> install_warnings_;
+
+ // The base extension url for the extension.
+ GURL extension_url_;
+
+ // The extension's version.
+ scoped_ptr<base::Version> version_;
+
+ // The extension's user visible version name.
+ std::string version_name_;
+
+ // An optional longer description of the extension.
+ std::string description_;
+
+ // True if the extension was generated from a user script. (We show slightly
+ // different UI if so).
+ bool converted_from_user_script_;
+
+ // The public key used to sign the contents of the crx package.
+ std::string public_key_;
+
+ // The manifest from which this extension was created.
+ scoped_ptr<Manifest> manifest_;
+
+ // Stored parsed manifest data.
+ using ManifestDataMap = std::map<std::string, scoped_ptr<ManifestData>>;
+ ManifestDataMap manifest_data_;
+
+ // Set to true at the end of InitValue when initialization is finished.
+ bool finished_parsing_manifest_;
+
+ // Ensures that any call to GetManifestData() prior to finishing
+ // initialization happens from the same thread (this can happen when certain
+ // parts of the initialization process need information from previous parts).
+ base::ThreadChecker thread_checker_;
+
+ // Should this app be shown in the app launcher.
+ bool display_in_launcher_;
+
+ // Should this app be shown in the browser New Tab Page.
+ bool display_in_new_tab_page_;
+
+ // Whether the extension has host permissions or user script patterns that
+ // imply access to file:/// scheme URLs (the user may not have actually
+ // granted it that access).
+ bool wants_file_access_;
+
+ // The flags that were passed to InitFromValue.
+ int creation_flags_;
+
+ DISALLOW_COPY_AND_ASSIGN(Extension);
+};
+
+typedef std::vector<scoped_refptr<const Extension> > ExtensionList;
+typedef std::set<ExtensionId> ExtensionIdSet;
+typedef std::vector<ExtensionId> ExtensionIdList;
+
+// Handy struct to pass core extension info around.
+struct ExtensionInfo {
+ ExtensionInfo(const base::DictionaryValue* manifest,
+ const ExtensionId& id,
+ const base::FilePath& path,
+ Manifest::Location location);
+ ~ExtensionInfo();
+
+ scoped_ptr<base::DictionaryValue> extension_manifest;
+ ExtensionId extension_id;
+ base::FilePath extension_path;
+ Manifest::Location extension_location;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ExtensionInfo);
+};
+
+struct InstalledExtensionInfo {
+ // The extension being installed - this should always be non-NULL.
+ const Extension* extension;
+
+ // True if the extension is being updated; false if it is being installed.
+ bool is_update;
+
+ // True if the extension was previously installed ephemerally and is now
+ // a regular installed extension.
+ bool from_ephemeral;
+
+ // The name of the extension prior to this update. Will be empty if
+ // |is_update| is false.
+ std::string old_name;
+
+ InstalledExtensionInfo(const Extension* extension,
+ bool is_update,
+ bool from_ephemeral,
+ const std::string& old_name);
+};
+
+struct UnloadedExtensionInfo {
+ // TODO(DHNishi): Move this enum to ExtensionRegistryObserver.
+ enum Reason {
+ REASON_UNDEFINED, // Undefined state used to initialize variables.
+ REASON_DISABLE, // Extension is being disabled.
+ REASON_UPDATE, // Extension is being updated to a newer version.
+ REASON_UNINSTALL, // Extension is being uninstalled.
+ REASON_TERMINATE, // Extension has terminated.
+ REASON_BLACKLIST, // Extension has been blacklisted.
+ REASON_PROFILE_SHUTDOWN, // Profile is being shut down.
+ REASON_LOCK_ALL, // All extensions for the profile are blocked.
+ REASON_MIGRATED_TO_COMPONENT, // Extension is being migrated to a component
+ // action.
+ };
+
+ Reason reason;
+
+ // The extension being unloaded - this should always be non-NULL.
+ const Extension* extension;
+
+ UnloadedExtensionInfo(const Extension* extension, Reason reason);
+};
+
+// The details sent for EXTENSION_PERMISSIONS_UPDATED notifications.
+struct UpdatedExtensionPermissionsInfo {
+ enum Reason {
+ ADDED, // The permissions were added to the extension.
+ REMOVED, // The permissions were removed from the extension.
+ };
+
+ Reason reason;
+
+ // The extension who's permissions have changed.
+ const Extension* extension;
+
+ // The permissions that have changed. For Reason::ADDED, this would contain
+ // only the permissions that have added, and for Reason::REMOVED, this would
+ // only contain the removed permissions.
+ const PermissionSet& permissions;
+
+ UpdatedExtensionPermissionsInfo(const Extension* extension,
+ const PermissionSet& permissions,
+ Reason reason);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_EXTENSION_H_
diff --git a/chromium/extensions/common/extension_api.cc b/chromium/extensions/common/extension_api.cc
new file mode 100644
index 00000000000..b30af5f28d1
--- /dev/null
+++ b/chromium/extensions/common/extension_api.cc
@@ -0,0 +1,397 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/extension_api.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/features/feature_provider.h"
+#include "extensions/common/features/simple_feature.h"
+#include "extensions/common/permissions/permission_set.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/grit/extensions_resources.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+namespace {
+
+const char* kChildKinds[] = {
+ "functions",
+ "events"
+};
+
+base::StringPiece ReadFromResource(int resource_id) {
+ return ResourceBundle::GetSharedInstance().GetRawDataResource(
+ resource_id);
+}
+
+scoped_ptr<base::ListValue> LoadSchemaList(const std::string& name,
+ const base::StringPiece& schema) {
+ std::string error_message;
+ scoped_ptr<base::Value> result(
+ base::JSONReader::ReadAndReturnError(
+ schema,
+ base::JSON_PARSE_RFC | base::JSON_DETACHABLE_CHILDREN, // options
+ NULL, // error code
+ &error_message));
+
+ // Tracking down http://crbug.com/121424
+ char buf[128];
+ base::snprintf(buf, arraysize(buf), "%s: (%d) '%s'",
+ name.c_str(),
+ result.get() ? result->GetType() : -1,
+ error_message.c_str());
+
+ CHECK(result.get()) << error_message << " for schema " << schema;
+ CHECK(result->IsType(base::Value::TYPE_LIST)) << " for schema " << schema;
+ return base::ListValue::From(std::move(result));
+}
+
+const base::DictionaryValue* FindListItem(const base::ListValue* list,
+ const std::string& property_name,
+ const std::string& property_value) {
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ const base::DictionaryValue* item = NULL;
+ CHECK(list->GetDictionary(i, &item))
+ << property_value << "/" << property_name;
+ std::string value;
+ if (item->GetString(property_name, &value) && value == property_value)
+ return item;
+ }
+
+ return NULL;
+}
+
+const base::DictionaryValue* GetSchemaChild(
+ const base::DictionaryValue* schema_node,
+ const std::string& child_name) {
+ const base::DictionaryValue* child_node = NULL;
+ for (size_t i = 0; i < arraysize(kChildKinds); ++i) {
+ const base::ListValue* list_node = NULL;
+ if (!schema_node->GetList(kChildKinds[i], &list_node))
+ continue;
+ child_node = FindListItem(list_node, "name", child_name);
+ if (child_node)
+ return child_node;
+ }
+
+ return NULL;
+}
+
+struct Static {
+ Static()
+ : api(ExtensionAPI::CreateWithDefaultConfiguration()) {
+ }
+ scoped_ptr<ExtensionAPI> api;
+};
+
+base::LazyInstance<Static> g_lazy_instance = LAZY_INSTANCE_INITIALIZER;
+
+// May override |g_lazy_instance| for a test.
+ExtensionAPI* g_shared_instance_for_test = NULL;
+
+// If it exists and does not already specify a namespace, then the value stored
+// with key |key| in |schema| will be updated to |schema_namespace| + "." +
+// |schema[key]|.
+void MaybePrefixFieldWithNamespace(const std::string& schema_namespace,
+ base::DictionaryValue* schema,
+ const std::string& key) {
+ if (!schema->HasKey(key))
+ return;
+
+ std::string old_id;
+ CHECK(schema->GetString(key, &old_id));
+ if (old_id.find(".") == std::string::npos)
+ schema->SetString(key, schema_namespace + "." + old_id);
+}
+
+// Modify all "$ref" keys anywhere in |schema| to be prefxied by
+// |schema_namespace| if they do not already specify a namespace.
+void PrefixRefsWithNamespace(const std::string& schema_namespace,
+ base::Value* value) {
+ base::ListValue* list = NULL;
+ base::DictionaryValue* dict = NULL;
+ if (value->GetAsList(&list)) {
+ for (base::ListValue::iterator i = list->begin(); i != list->end(); ++i) {
+ PrefixRefsWithNamespace(schema_namespace, *i);
+ }
+ } else if (value->GetAsDictionary(&dict)) {
+ MaybePrefixFieldWithNamespace(schema_namespace, dict, "$ref");
+ for (base::DictionaryValue::Iterator i(*dict); !i.IsAtEnd(); i.Advance()) {
+ base::Value* value = NULL;
+ CHECK(dict->GetWithoutPathExpansion(i.key(), &value));
+ PrefixRefsWithNamespace(schema_namespace, value);
+ }
+ }
+}
+
+// Modify all objects in the "types" section of the schema to be prefixed by
+// |schema_namespace| if they do not already specify a namespace.
+void PrefixTypesWithNamespace(const std::string& schema_namespace,
+ base::DictionaryValue* schema) {
+ if (!schema->HasKey("types"))
+ return;
+
+ // Add the namespace to all of the types defined in this schema
+ base::ListValue *types = NULL;
+ CHECK(schema->GetList("types", &types));
+ for (size_t i = 0; i < types->GetSize(); ++i) {
+ base::DictionaryValue *type = NULL;
+ CHECK(types->GetDictionary(i, &type));
+ MaybePrefixFieldWithNamespace(schema_namespace, type, "id");
+ MaybePrefixFieldWithNamespace(schema_namespace, type, "customBindings");
+ }
+}
+
+// Modify the schema so that all types are fully qualified.
+void PrefixWithNamespace(const std::string& schema_namespace,
+ base::DictionaryValue* schema) {
+ PrefixTypesWithNamespace(schema_namespace, schema);
+ PrefixRefsWithNamespace(schema_namespace, schema);
+}
+
+} // namespace
+
+// static
+ExtensionAPI* ExtensionAPI::GetSharedInstance() {
+ return g_shared_instance_for_test ? g_shared_instance_for_test
+ : g_lazy_instance.Get().api.get();
+}
+
+// static
+ExtensionAPI* ExtensionAPI::CreateWithDefaultConfiguration() {
+ ExtensionAPI* api = new ExtensionAPI();
+ api->InitDefaultConfiguration();
+ return api;
+}
+
+// static
+void ExtensionAPI::SplitDependencyName(const std::string& full_name,
+ std::string* feature_type,
+ std::string* feature_name) {
+ size_t colon_index = full_name.find(':');
+ if (colon_index == std::string::npos) {
+ // TODO(aa): Remove this code when all API descriptions have been updated.
+ *feature_type = "api";
+ *feature_name = full_name;
+ return;
+ }
+
+ *feature_type = full_name.substr(0, colon_index);
+ *feature_name = full_name.substr(colon_index + 1);
+}
+
+ExtensionAPI::OverrideSharedInstanceForTest::OverrideSharedInstanceForTest(
+ ExtensionAPI* testing_api)
+ : original_api_(g_shared_instance_for_test) {
+ g_shared_instance_for_test = testing_api;
+}
+
+ExtensionAPI::OverrideSharedInstanceForTest::~OverrideSharedInstanceForTest() {
+ g_shared_instance_for_test = original_api_;
+}
+
+void ExtensionAPI::LoadSchema(const std::string& name,
+ const base::StringPiece& schema) {
+ scoped_ptr<base::ListValue> schema_list(LoadSchemaList(name, schema));
+ std::string schema_namespace;
+ extensions::ExtensionsClient* extensions_client =
+ extensions::ExtensionsClient::Get();
+ DCHECK(extensions_client);
+ while (!schema_list->empty()) {
+ base::DictionaryValue* schema = NULL;
+ {
+ scoped_ptr<base::Value> value;
+ schema_list->Remove(schema_list->GetSize() - 1, &value);
+ CHECK(value.release()->GetAsDictionary(&schema));
+ }
+
+ CHECK(schema->GetString("namespace", &schema_namespace));
+ PrefixWithNamespace(schema_namespace, schema);
+ schemas_[schema_namespace] = make_linked_ptr(schema);
+ if (!extensions_client->IsAPISchemaGenerated(schema_namespace))
+ CHECK_EQ(1u, unloaded_schemas_.erase(schema_namespace));
+ }
+}
+
+ExtensionAPI::ExtensionAPI() : default_configuration_initialized_(false) {
+}
+
+ExtensionAPI::~ExtensionAPI() {
+}
+
+void ExtensionAPI::InitDefaultConfiguration() {
+ const char* names[] = {"api", "manifest", "permission"};
+ for (size_t i = 0; i < arraysize(names); ++i)
+ RegisterDependencyProvider(names[i], FeatureProvider::GetByName(names[i]));
+
+ ExtensionsClient::Get()->RegisterAPISchemaResources(this);
+
+ RegisterSchemaResource("declarativeWebRequest",
+ IDR_EXTENSION_API_JSON_DECLARATIVE_WEBREQUEST);
+ RegisterSchemaResource("webViewRequest",
+ IDR_EXTENSION_API_JSON_WEB_VIEW_REQUEST);
+
+ default_configuration_initialized_ = true;
+}
+
+void ExtensionAPI::RegisterSchemaResource(const std::string& name,
+ int resource_id) {
+ unloaded_schemas_[name] = resource_id;
+}
+
+void ExtensionAPI::RegisterDependencyProvider(const std::string& name,
+ const FeatureProvider* provider) {
+ dependency_providers_[name] = provider;
+}
+
+bool ExtensionAPI::IsAnyFeatureAvailableToContext(const Feature& api,
+ const Extension* extension,
+ Feature::Context context,
+ const GURL& url) {
+ FeatureProviderMap::iterator provider = dependency_providers_.find("api");
+ CHECK(provider != dependency_providers_.end());
+
+ if (api.IsAvailableToContext(extension, context, url).is_available())
+ return true;
+
+ // Check to see if there are any parts of this API that are allowed in this
+ // context.
+ const std::vector<Feature*> features = provider->second->GetChildren(api);
+ for (std::vector<Feature*>::const_iterator it = features.begin();
+ it != features.end();
+ ++it) {
+ if ((*it)->IsAvailableToContext(extension, context, url).is_available())
+ return true;
+ }
+ return false;
+}
+
+Feature::Availability ExtensionAPI::IsAvailable(const std::string& full_name,
+ const Extension* extension,
+ Feature::Context context,
+ const GURL& url) {
+ Feature* feature = GetFeatureDependency(full_name);
+ if (!feature) {
+ return Feature::Availability(Feature::NOT_PRESENT,
+ std::string("Unknown feature: ") + full_name);
+ }
+ return feature->IsAvailableToContext(extension, context, url);
+}
+
+bool ExtensionAPI::IsAvailableToWebUI(const std::string& name,
+ const GURL& url) {
+ return IsAvailable(name, NULL, Feature::WEBUI_CONTEXT, url).is_available();
+}
+
+const base::DictionaryValue* ExtensionAPI::GetSchema(
+ const std::string& full_name) {
+ std::string child_name;
+ std::string api_name = GetAPINameFromFullName(full_name, &child_name);
+
+ const base::DictionaryValue* result = NULL;
+ SchemaMap::iterator maybe_schema = schemas_.find(api_name);
+ if (maybe_schema != schemas_.end()) {
+ result = maybe_schema->second.get();
+ } else {
+ // Might not have loaded yet; or might just not exist.
+ UnloadedSchemaMap::iterator maybe_schema_resource =
+ unloaded_schemas_.find(api_name);
+ extensions::ExtensionsClient* extensions_client =
+ extensions::ExtensionsClient::Get();
+ DCHECK(extensions_client);
+ if (maybe_schema_resource != unloaded_schemas_.end()) {
+ LoadSchema(maybe_schema_resource->first,
+ ReadFromResource(maybe_schema_resource->second));
+ } else if (default_configuration_initialized_ &&
+ extensions_client->IsAPISchemaGenerated(api_name)) {
+ LoadSchema(api_name, extensions_client->GetAPISchema(api_name));
+ } else {
+ return NULL;
+ }
+
+ maybe_schema = schemas_.find(api_name);
+ CHECK(schemas_.end() != maybe_schema);
+ result = maybe_schema->second.get();
+ }
+
+ if (!child_name.empty())
+ result = GetSchemaChild(result, child_name);
+
+ return result;
+}
+
+Feature* ExtensionAPI::GetFeatureDependency(const std::string& full_name) {
+ std::string feature_type;
+ std::string feature_name;
+ SplitDependencyName(full_name, &feature_type, &feature_name);
+
+ FeatureProviderMap::iterator provider =
+ dependency_providers_.find(feature_type);
+ if (provider == dependency_providers_.end())
+ return NULL;
+
+ Feature* feature = provider->second->GetFeature(feature_name);
+ // Try getting the feature for the parent API, if this was a child.
+ if (!feature) {
+ std::string child_name;
+ feature = provider->second->GetFeature(
+ GetAPINameFromFullName(feature_name, &child_name));
+ }
+ return feature;
+}
+
+std::string ExtensionAPI::GetAPINameFromFullName(const std::string& full_name,
+ std::string* child_name) {
+ std::string api_name_candidate = full_name;
+ extensions::ExtensionsClient* extensions_client =
+ extensions::ExtensionsClient::Get();
+ DCHECK(extensions_client);
+ while (true) {
+ if (schemas_.find(api_name_candidate) != schemas_.end() ||
+ extensions_client->IsAPISchemaGenerated(api_name_candidate) ||
+ unloaded_schemas_.find(api_name_candidate) != unloaded_schemas_.end()) {
+ std::string result = api_name_candidate;
+
+ if (child_name) {
+ if (result.length() < full_name.length())
+ *child_name = full_name.substr(result.length() + 1);
+ else
+ *child_name = "";
+ }
+
+ return result;
+ }
+
+ size_t last_dot_index = api_name_candidate.rfind('.');
+ if (last_dot_index == std::string::npos)
+ break;
+
+ api_name_candidate = api_name_candidate.substr(0, last_dot_index);
+ }
+
+ *child_name = "";
+ return std::string();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/extension_api.h b/chromium/extensions/common/extension_api.h
new file mode 100644
index 00000000000..fc3e20cde55
--- /dev/null
+++ b/chromium/extensions/common/extension_api.h
@@ -0,0 +1,161 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_EXTENSION_API_H_
+#define EXTENSIONS_COMMON_EXTENSION_API_H_
+
+#include <map>
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/singleton.h"
+#include "base/strings/string_piece.h"
+#include "base/values.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/features/feature_provider.h"
+#include "extensions/common/url_pattern_set.h"
+
+namespace base {
+class DictionaryValue;
+class Value;
+}
+
+class GURL;
+
+namespace extensions {
+
+class Extension;
+class Feature;
+
+// C++ Wrapper for the JSON API definitions in chrome/common/extensions/api/.
+//
+// WARNING: This class is accessed on multiple threads in the browser process
+// (see ExtensionFunctionDispatcher). No state should be modified after
+// construction.
+class ExtensionAPI {
+ public:
+ // Returns a single shared instance of this class. This is the typical use
+ // case in Chrome.
+ //
+ // TODO(aa): Make this const to enforce thread-safe usage.
+ static ExtensionAPI* GetSharedInstance();
+
+ // Creates a new instance configured the way ExtensionAPI typically is in
+ // Chrome. Use the default constructor to get a clean instance.
+ static ExtensionAPI* CreateWithDefaultConfiguration();
+
+ // Splits a name like "permission:bookmark" into ("permission", "bookmark").
+ // The first part refers to a type of feature, for example "manifest",
+ // "permission", or "api". The second part is the full name of the feature.
+ //
+ // TODO(kalman): ExtensionAPI isn't really the right place for this function.
+ static void SplitDependencyName(const std::string& full_name,
+ std::string* feature_type,
+ std::string* feature_name);
+
+ class OverrideSharedInstanceForTest {
+ public:
+ explicit OverrideSharedInstanceForTest(ExtensionAPI* testing_api);
+ ~OverrideSharedInstanceForTest();
+
+ private:
+ ExtensionAPI* original_api_;
+ };
+
+ // Creates a completely clean instance. Configure using RegisterSchema() and
+ // RegisterDependencyProvider before use.
+ ExtensionAPI();
+ virtual ~ExtensionAPI();
+
+ // Add a (non-generated) API schema resource.
+ void RegisterSchemaResource(const std::string& api_name, int resource_id);
+
+ // Add a FeatureProvider for APIs. The features are used to specify
+ // dependencies and constraints on the availability of APIs.
+ void RegisterDependencyProvider(const std::string& name,
+ const FeatureProvider* provider);
+
+ // Returns true if the API item called |api_full_name| and all of its
+ // dependencies are available in |context|.
+ //
+ // |api_full_name| can be either a namespace name (like "bookmarks") or a
+ // member name (like "bookmarks.create").
+ //
+ // Depending on the configuration of |api| (in _api_features.json), either
+ // |extension| or |url| (or both) may determine its availability, but this is
+ // up to the configuration of the individual feature.
+ //
+ // TODO(kalman): This is just an unnecessary combination of finding a Feature
+ // then calling Feature::IsAvailableToContext(..) on it. Just provide that
+ // FindFeature function and let callers compose if they want.
+ Feature::Availability IsAvailable(const std::string& api_full_name,
+ const Extension* extension,
+ Feature::Context context,
+ const GURL& url);
+
+ // Determines whether an API, or any parts of that API, are available in
+ // |context|.
+ bool IsAnyFeatureAvailableToContext(const Feature& api,
+ const Extension* extension,
+ Feature::Context context,
+ const GURL& url);
+
+ // Returns true if |name| is available to WebUI contexts on |url|.
+ bool IsAvailableToWebUI(const std::string& name, const GURL& url);
+
+ // Gets the schema for the extension API with namespace |full_name|.
+ // Ownership remains with this object.
+ const base::DictionaryValue* GetSchema(const std::string& full_name);
+
+ // Splits a full name from the extension API into its API and child name
+ // parts. Some examples:
+ //
+ // "bookmarks.create" -> ("bookmarks", "create")
+ // "experimental.input.ui.cursorUp" -> ("experimental.input.ui", "cursorUp")
+ // "storage.sync.set" -> ("storage", "sync.get")
+ // "<unknown-api>.monkey" -> ("", "")
+ //
+ // The |child_name| parameter can be be NULL if you don't need that part.
+ std::string GetAPINameFromFullName(const std::string& full_name,
+ std::string* child_name);
+
+ // Gets a feature from any dependency provider registered with ExtensionAPI.
+ // Returns NULL if the feature could not be found.
+ Feature* GetFeatureDependency(const std::string& dependency_name);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ExtensionAPITest, DefaultConfigurationFeatures);
+ FRIEND_TEST_ALL_PREFIXES(ExtensionAPITest, TypesHaveNamespace);
+ friend struct base::DefaultSingletonTraits<ExtensionAPI>;
+
+ void InitDefaultConfiguration();
+
+ bool default_configuration_initialized_;
+
+ // Loads a schema.
+ void LoadSchema(const std::string& name, const base::StringPiece& schema);
+
+ // Map from each API that hasn't been loaded yet to the schema which defines
+ // it. Note that there may be multiple APIs per schema.
+ typedef std::map<std::string, int> UnloadedSchemaMap;
+ UnloadedSchemaMap unloaded_schemas_;
+
+ // Schemas for each namespace.
+ typedef std::map<std::string, linked_ptr<const base::DictionaryValue> >
+ SchemaMap;
+ SchemaMap schemas_;
+
+ // FeatureProviders used for resolving dependencies.
+ typedef std::map<std::string, const FeatureProvider*> FeatureProviderMap;
+ FeatureProviderMap dependency_providers_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionAPI);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_EXTENSION_API_H_
diff --git a/chromium/extensions/common/extension_builder.cc b/chromium/extensions/common/extension_builder.cc
new file mode 100644
index 00000000000..905ba6b2dfa
--- /dev/null
+++ b/chromium/extensions/common/extension_builder.cc
@@ -0,0 +1,80 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/extension_builder.h"
+
+#include <utility>
+
+#include "extensions/common/extension.h"
+
+namespace extensions {
+
+ExtensionBuilder::ExtensionBuilder()
+ : location_(Manifest::UNPACKED),
+ flags_(Extension::NO_FLAGS) {
+}
+ExtensionBuilder::~ExtensionBuilder() {}
+
+ExtensionBuilder::ExtensionBuilder(ExtensionBuilder&& other)
+ : path_(std::move(other.path_)),
+ location_(other.location_),
+ manifest_(std::move(other.manifest_)),
+ flags_(other.flags_),
+ id_(std::move(other.id_)) {}
+
+ExtensionBuilder& ExtensionBuilder::operator=(ExtensionBuilder&& other) {
+ path_ = std::move(other.path_);
+ location_ = other.location_;
+ manifest_ = std::move(other.manifest_);
+ flags_ = other.flags_;
+ id_ = std::move(other.id_);
+ return *this;
+}
+
+scoped_refptr<Extension> ExtensionBuilder::Build() {
+ std::string error;
+ scoped_refptr<Extension> extension = Extension::Create(
+ path_,
+ location_,
+ *manifest_,
+ flags_,
+ id_,
+ &error);
+ CHECK_EQ("", error);
+ return extension;
+}
+
+ExtensionBuilder& ExtensionBuilder::SetPath(const base::FilePath& path) {
+ path_ = path;
+ return *this;
+}
+
+ExtensionBuilder& ExtensionBuilder::SetLocation(Manifest::Location location) {
+ location_ = location;
+ return *this;
+}
+
+ExtensionBuilder& ExtensionBuilder::SetManifest(
+ scoped_ptr<base::DictionaryValue> manifest) {
+ manifest_ = std::move(manifest);
+ return *this;
+}
+
+ExtensionBuilder& ExtensionBuilder::MergeManifest(
+ scoped_ptr<base::DictionaryValue> manifest) {
+ manifest_->MergeDictionary(manifest.get());
+ return *this;
+}
+
+ExtensionBuilder& ExtensionBuilder::AddFlags(int init_from_value_flags) {
+ flags_ |= init_from_value_flags;
+ return *this;
+}
+
+ExtensionBuilder& ExtensionBuilder::SetID(const std::string& id) {
+ id_ = id;
+ return *this;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/extension_builder.h b/chromium/extensions/common/extension_builder.h
new file mode 100644
index 00000000000..5fe9801a602
--- /dev/null
+++ b/chromium/extensions/common/extension_builder.h
@@ -0,0 +1,65 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_EXTENSION_BUILDER_H_
+#define EXTENSIONS_COMMON_EXTENSION_BUILDER_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/value_builder.h"
+
+namespace extensions {
+class Extension;
+
+// An easier way to create extensions than Extension::Create. The
+// constructor sets up some defaults which are customized using the
+// methods. The only method that must be called is SetManifest().
+class ExtensionBuilder {
+ public:
+ ExtensionBuilder();
+ ~ExtensionBuilder();
+
+ // Move constructor and operator=.
+ ExtensionBuilder(ExtensionBuilder&& other);
+ ExtensionBuilder& operator=(ExtensionBuilder&& other);
+
+ // Can only be called once, after which it's invalid to use the builder.
+ // CHECKs that the extension was created successfully.
+ scoped_refptr<Extension> Build();
+
+ // Defaults to FilePath().
+ ExtensionBuilder& SetPath(const base::FilePath& path);
+
+ // Defaults to Manifest::UNPACKED.
+ ExtensionBuilder& SetLocation(Manifest::Location location);
+
+ ExtensionBuilder& SetManifest(scoped_ptr<base::DictionaryValue> manifest);
+
+ // Merge another manifest into the current manifest, with new keys taking
+ // precedence.
+ ExtensionBuilder& MergeManifest(scoped_ptr<base::DictionaryValue> manifest);
+
+ ExtensionBuilder& AddFlags(int init_from_value_flags);
+
+ // Defaults to the default extension ID created in Extension::Create.
+ ExtensionBuilder& SetID(const std::string& id);
+
+ private:
+ base::FilePath path_;
+ Manifest::Location location_;
+ scoped_ptr<base::DictionaryValue> manifest_;
+ int flags_;
+ std::string id_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionBuilder);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_EXTENSION_BUILDER_H_
diff --git a/chromium/extensions/common/extension_icon_set.cc b/chromium/extensions/common/extension_icon_set.cc
new file mode 100644
index 00000000000..421407c789d
--- /dev/null
+++ b/chromium/extensions/common/extension_icon_set.cc
@@ -0,0 +1,80 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/extension_icon_set.h"
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+
+ExtensionIconSet::ExtensionIconSet() {}
+
+ExtensionIconSet::ExtensionIconSet(const ExtensionIconSet& other) = default;
+
+ExtensionIconSet::~ExtensionIconSet() {}
+
+void ExtensionIconSet::Clear() {
+ map_.clear();
+}
+
+void ExtensionIconSet::Add(int size, const std::string& path) {
+ DCHECK(!path.empty() && path[0] != '/');
+ map_[size] = path;
+}
+
+const std::string& ExtensionIconSet::Get(int size, MatchType match_type) const {
+ // The searches for MATCH_BIGGER and MATCH_SMALLER below rely on the fact that
+ // std::map is sorted. This is per the spec, so it should be safe to rely on.
+ if (match_type == MATCH_EXACTLY) {
+ IconMap::const_iterator result = map_.find(size);
+ return result == map_.end() ? base::EmptyString() : result->second;
+ } else if (match_type == MATCH_SMALLER) {
+ IconMap::const_reverse_iterator result = map_.rend();
+ for (IconMap::const_reverse_iterator iter = map_.rbegin();
+ iter != map_.rend(); ++iter) {
+ if (iter->first <= size) {
+ result = iter;
+ break;
+ }
+ }
+ return result == map_.rend() ? base::EmptyString() : result->second;
+ } else {
+ DCHECK(match_type == MATCH_BIGGER);
+ IconMap::const_iterator result = map_.end();
+ for (IconMap::const_iterator iter = map_.begin(); iter != map_.end();
+ ++iter) {
+ if (iter->first >= size) {
+ result = iter;
+ break;
+ }
+ }
+ return result == map_.end() ? base::EmptyString() : result->second;
+ }
+}
+
+bool ExtensionIconSet::ContainsPath(const std::string& path) const {
+ return GetIconSizeFromPath(path) != 0;
+}
+
+int ExtensionIconSet::GetIconSizeFromPath(const std::string& path) const {
+ if (path.empty())
+ return 0;
+
+ DCHECK_NE(path[0], '/') <<
+ "ExtensionIconSet stores icon paths without leading slash.";
+
+ for (IconMap::const_iterator iter = map_.begin(); iter != map_.end();
+ ++iter) {
+ if (iter->second == path)
+ return iter->first;
+ }
+
+ return 0;
+}
+
+void ExtensionIconSet::GetPaths(std::set<base::FilePath>* paths) const {
+ CHECK(paths);
+ for (auto iter : map())
+ paths->insert(base::FilePath::FromUTF8Unsafe(iter.second));
+}
diff --git a/chromium/extensions/common/extension_icon_set.h b/chromium/extensions/common/extension_icon_set.h
new file mode 100644
index 00000000000..91ce0f51646
--- /dev/null
+++ b/chromium/extensions/common/extension_icon_set.h
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_EXTENSION_ICON_SET_H_
+#define EXTENSIONS_COMMON_EXTENSION_ICON_SET_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+namespace base {
+class FilePath;
+}
+
+// Represents the set of icons for an extension.
+class ExtensionIconSet {
+ public:
+ // Get an icon from the set, optionally falling back to a smaller or bigger
+ // size. MatchType is exclusive (do not OR them together).
+ enum MatchType {
+ MATCH_EXACTLY,
+ MATCH_BIGGER,
+ MATCH_SMALLER
+ };
+
+ // Access to the underlying map from icon size->{path, bitmap}.
+ typedef std::map<int, std::string> IconMap;
+
+ ExtensionIconSet();
+ ExtensionIconSet(const ExtensionIconSet& other);
+ ~ExtensionIconSet();
+
+ const IconMap& map() const { return map_; }
+ bool empty() const { return map_.empty(); }
+
+ // Remove all icons from the set.
+ void Clear();
+
+ // Add an icon path to the set. If a path for the specified size is already
+ // present, it is overwritten.
+ void Add(int size, const std::string& path);
+
+ // Gets path value of the icon found when searching for |size| using
+ // |mathc_type|.
+ const std::string& Get(int size, MatchType match_type) const;
+
+ // Returns true iff the set contains the specified path.
+ bool ContainsPath(const std::string& path) const;
+
+ // Returns icon size if the set contains the specified path or 0 if not found.
+ int GetIconSizeFromPath(const std::string& path) const;
+
+ // Add the paths of all icons in this set into |paths|, handling the
+ // conversion of (string) -> (base::FilePath). Note that these paths are not
+ // validated in any way, so they may be invalid paths or reference
+ // nonexistent files.
+ void GetPaths(std::set<base::FilePath>* paths) const;
+
+ private:
+ IconMap map_;
+};
+
+#endif // EXTENSIONS_COMMON_EXTENSION_ICON_SET_H_
diff --git a/chromium/extensions/common/extension_l10n_util.cc b/chromium/extensions/common/extension_l10n_util.cc
new file mode 100644
index 00000000000..a423ba59563
--- /dev/null
+++ b/chromium/extensions/common/extension_l10n_util.cc
@@ -0,0 +1,467 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/extension_l10n_util.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/logging.h"
+#include "base/memory/linked_ptr.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/file_util.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/message_bundle.h"
+#include "third_party/icu/source/common/unicode/uloc.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace errors = extensions::manifest_errors;
+namespace keys = extensions::manifest_keys;
+
+namespace {
+
+// Loads contents of the messages file for given locale. If file is not found,
+// or there was parsing error we return NULL and set |error|.
+// Caller owns the returned object.
+base::DictionaryValue* LoadMessageFile(const base::FilePath& locale_path,
+ const std::string& locale,
+ std::string* error) {
+ base::FilePath file =
+ locale_path.AppendASCII(locale).Append(extensions::kMessagesFilename);
+ JSONFileValueDeserializer messages_deserializer(file);
+ scoped_ptr<base::DictionaryValue> dictionary = base::DictionaryValue::From(
+ messages_deserializer.Deserialize(NULL, error));
+ if (!dictionary) {
+ if (error->empty()) {
+ // JSONFileValueSerializer just returns NULL if file cannot be found. It
+ // doesn't set the error, so we have to do it.
+ *error = base::StringPrintf("Catalog file is missing for locale %s.",
+ locale.c_str());
+ } else {
+ *error = extensions::ErrorUtils::FormatErrorMessage(
+ errors::kLocalesInvalidLocale,
+ base::UTF16ToUTF8(file.LossyDisplayName()),
+ *error);
+ }
+ }
+
+ return dictionary.release();
+}
+
+// Localizes manifest value of string type for a given key.
+bool LocalizeManifestValue(const std::string& key,
+ const extensions::MessageBundle& messages,
+ base::DictionaryValue* manifest,
+ std::string* error) {
+ std::string result;
+ if (!manifest->GetString(key, &result))
+ return true;
+
+ if (!messages.ReplaceMessages(&result, error))
+ return false;
+
+ manifest->SetString(key, result);
+ return true;
+}
+
+// Localizes manifest value of list type for a given key.
+bool LocalizeManifestListValue(const std::string& key,
+ const extensions::MessageBundle& messages,
+ base::DictionaryValue* manifest,
+ std::string* error) {
+ base::ListValue* list = NULL;
+ if (!manifest->GetList(key, &list))
+ return true;
+
+ bool ret = true;
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ std::string result;
+ if (list->GetString(i, &result)) {
+ if (messages.ReplaceMessages(&result, error))
+ list->Set(i, new base::StringValue(result));
+ else
+ ret = false;
+ }
+ }
+ return ret;
+}
+
+std::string& GetProcessLocale() {
+ CR_DEFINE_STATIC_LOCAL(std::string, locale, ());
+ return locale;
+}
+
+} // namespace
+
+namespace extension_l10n_util {
+
+void SetProcessLocale(const std::string& locale) {
+ GetProcessLocale() = locale;
+}
+
+std::string GetDefaultLocaleFromManifest(const base::DictionaryValue& manifest,
+ std::string* error) {
+ std::string default_locale;
+ if (manifest.GetString(keys::kDefaultLocale, &default_locale))
+ return default_locale;
+
+ *error = errors::kInvalidDefaultLocale;
+ return std::string();
+}
+
+bool ShouldRelocalizeManifest(const base::DictionaryValue* manifest) {
+ if (!manifest)
+ return false;
+
+ if (!manifest->HasKey(keys::kDefaultLocale))
+ return false;
+
+ std::string manifest_current_locale;
+ manifest->GetString(keys::kCurrentLocale, &manifest_current_locale);
+ return manifest_current_locale != CurrentLocaleOrDefault();
+}
+
+bool LocalizeManifest(const extensions::MessageBundle& messages,
+ base::DictionaryValue* manifest,
+ std::string* error) {
+ // Initialize name.
+ std::string result;
+ if (!manifest->GetString(keys::kName, &result)) {
+ *error = errors::kInvalidName;
+ return false;
+ }
+ if (!LocalizeManifestValue(keys::kName, messages, manifest, error)) {
+ return false;
+ }
+
+ // Initialize short name.
+ if (!LocalizeManifestValue(keys::kShortName, messages, manifest, error))
+ return false;
+
+ // Initialize description.
+ if (!LocalizeManifestValue(keys::kDescription, messages, manifest, error))
+ return false;
+
+ // Initialize browser_action.default_title
+ std::string key(keys::kBrowserAction);
+ key.append(".");
+ key.append(keys::kPageActionDefaultTitle);
+ if (!LocalizeManifestValue(key, messages, manifest, error))
+ return false;
+
+ // Initialize page_action.default_title
+ key.assign(keys::kPageAction);
+ key.append(".");
+ key.append(keys::kPageActionDefaultTitle);
+ if (!LocalizeManifestValue(key, messages, manifest, error))
+ return false;
+
+ // Initialize omnibox.keyword.
+ if (!LocalizeManifestValue(keys::kOmniboxKeyword, messages, manifest, error))
+ return false;
+
+ base::ListValue* file_handlers = NULL;
+ if (manifest->GetList(keys::kFileBrowserHandlers, &file_handlers)) {
+ key.assign(keys::kFileBrowserHandlers);
+ for (size_t i = 0; i < file_handlers->GetSize(); i++) {
+ base::DictionaryValue* handler = NULL;
+ if (!file_handlers->GetDictionary(i, &handler)) {
+ *error = errors::kInvalidFileBrowserHandler;
+ return false;
+ }
+ if (!LocalizeManifestValue(
+ keys::kPageActionDefaultTitle, messages, handler, error))
+ return false;
+ }
+ }
+
+ // Initialize all input_components
+ base::ListValue* input_components = NULL;
+ if (manifest->GetList(keys::kInputComponents, &input_components)) {
+ for (size_t i = 0; i < input_components->GetSize(); ++i) {
+ base::DictionaryValue* module = NULL;
+ if (!input_components->GetDictionary(i, &module)) {
+ *error = errors::kInvalidInputComponents;
+ return false;
+ }
+ if (!LocalizeManifestValue(keys::kName, messages, module, error))
+ return false;
+ if (!LocalizeManifestValue(keys::kDescription, messages, module, error))
+ return false;
+ }
+ }
+
+ // Initialize app.launch.local_path.
+ if (!LocalizeManifestValue(keys::kLaunchLocalPath, messages, manifest, error))
+ return false;
+
+ // Initialize app.launch.web_url.
+ if (!LocalizeManifestValue(keys::kLaunchWebURL, messages, manifest, error))
+ return false;
+
+ // Initialize description of commmands.
+ base::DictionaryValue* commands_handler = NULL;
+ if (manifest->GetDictionary(keys::kCommands, &commands_handler)) {
+ for (base::DictionaryValue::Iterator iter(*commands_handler);
+ !iter.IsAtEnd();
+ iter.Advance()) {
+ key.assign(
+ base::StringPrintf("commands.%s.description", iter.key().c_str()));
+ if (!LocalizeManifestValue(key, messages, manifest, error))
+ return false;
+ }
+ }
+
+ // Initialize search_provider fields.
+ base::DictionaryValue* search_provider = NULL;
+ if (manifest->GetDictionary(keys::kOverrideSearchProvider,
+ &search_provider)) {
+ for (base::DictionaryValue::Iterator iter(*search_provider);
+ !iter.IsAtEnd();
+ iter.Advance()) {
+ key.assign(base::StringPrintf(
+ "%s.%s", keys::kOverrideSearchProvider, iter.key().c_str()));
+ bool success =
+ (key == keys::kSettingsOverrideAlternateUrls)
+ ? LocalizeManifestListValue(key, messages, manifest, error)
+ : LocalizeManifestValue(key, messages, manifest, error);
+ if (!success)
+ return false;
+ }
+ }
+
+ // Initialize chrome_settings_overrides.homepage.
+ if (!LocalizeManifestValue(
+ keys::kOverrideHomepage, messages, manifest, error))
+ return false;
+
+ // Initialize chrome_settings_overrides.startup_pages.
+ if (!LocalizeManifestListValue(
+ keys::kOverrideStartupPage, messages, manifest, error))
+ return false;
+
+ // Add current locale key to the manifest, so we can overwrite prefs
+ // with new manifest when chrome locale changes.
+ manifest->SetString(keys::kCurrentLocale, CurrentLocaleOrDefault());
+ return true;
+}
+
+bool LocalizeExtension(const base::FilePath& extension_path,
+ base::DictionaryValue* manifest,
+ std::string* error) {
+ DCHECK(manifest);
+
+ std::string default_locale = GetDefaultLocaleFromManifest(*manifest, error);
+
+ scoped_ptr<extensions::MessageBundle> message_bundle(
+ extensions::file_util::LoadMessageBundle(
+ extension_path, default_locale, error));
+
+ if (!message_bundle.get() && !error->empty())
+ return false;
+
+ if (message_bundle.get() &&
+ !LocalizeManifest(*message_bundle, manifest, error))
+ return false;
+
+ return true;
+}
+
+bool AddLocale(const std::set<std::string>& chrome_locales,
+ const base::FilePath& locale_folder,
+ const std::string& locale_name,
+ std::set<std::string>* valid_locales,
+ std::string* error) {
+ // Accept name that starts with a . but don't add it to the list of supported
+ // locales.
+ if (locale_name.find(".") == 0)
+ return true;
+ if (chrome_locales.find(locale_name) == chrome_locales.end()) {
+ // Warn if there is an extension locale that's not in the Chrome list,
+ // but don't fail.
+ DLOG(WARNING) << base::StringPrintf("Supplied locale %s is not supported.",
+ locale_name.c_str());
+ return true;
+ }
+ // Check if messages file is actually present (but don't check content).
+ if (base::PathExists(locale_folder.Append(extensions::kMessagesFilename))) {
+ valid_locales->insert(locale_name);
+ } else {
+ *error = base::StringPrintf("Catalog file is missing for locale %s.",
+ locale_name.c_str());
+ return false;
+ }
+
+ return true;
+}
+
+std::string CurrentLocaleOrDefault() {
+ std::string current_locale = l10n_util::NormalizeLocale(GetProcessLocale());
+ if (current_locale.empty())
+ current_locale = "en";
+
+ return current_locale;
+}
+
+void GetAllLocales(std::set<std::string>* all_locales) {
+ const std::vector<std::string>& available_locales =
+ l10n_util::GetAvailableLocales();
+ // Add all parents of the current locale to the available locales set.
+ // I.e. for sr_Cyrl_RS we add sr_Cyrl_RS, sr_Cyrl and sr.
+ for (size_t i = 0; i < available_locales.size(); ++i) {
+ std::vector<std::string> result;
+ l10n_util::GetParentLocales(available_locales[i], &result);
+ all_locales->insert(result.begin(), result.end());
+ }
+}
+
+void GetAllFallbackLocales(const std::string& application_locale,
+ const std::string& default_locale,
+ std::vector<std::string>* all_fallback_locales) {
+ DCHECK(all_fallback_locales);
+ if (!application_locale.empty() && application_locale != default_locale)
+ l10n_util::GetParentLocales(application_locale, all_fallback_locales);
+ all_fallback_locales->push_back(default_locale);
+}
+
+bool GetValidLocales(const base::FilePath& locale_path,
+ std::set<std::string>* valid_locales,
+ std::string* error) {
+ std::set<std::string> chrome_locales;
+ GetAllLocales(&chrome_locales);
+
+ // Enumerate all supplied locales in the extension.
+ base::FileEnumerator locales(
+ locale_path, false, base::FileEnumerator::DIRECTORIES);
+ base::FilePath locale_folder;
+ while (!(locale_folder = locales.Next()).empty()) {
+ std::string locale_name = locale_folder.BaseName().MaybeAsASCII();
+ if (locale_name.empty()) {
+ NOTREACHED();
+ continue; // Not ASCII.
+ }
+ if (!AddLocale(
+ chrome_locales, locale_folder, locale_name, valid_locales, error)) {
+ valid_locales->clear();
+ return false;
+ }
+ }
+
+ if (valid_locales->empty()) {
+ *error = errors::kLocalesNoValidLocaleNamesListed;
+ return false;
+ }
+
+ return true;
+}
+
+extensions::MessageBundle* LoadMessageCatalogs(
+ const base::FilePath& locale_path,
+ const std::string& default_locale,
+ const std::string& application_locale,
+ std::string* error) {
+ std::vector<std::string> all_fallback_locales;
+ GetAllFallbackLocales(
+ application_locale, default_locale, &all_fallback_locales);
+
+ std::vector<linked_ptr<base::DictionaryValue> > catalogs;
+ for (size_t i = 0; i < all_fallback_locales.size(); ++i) {
+ // Skip all parent locales that are not supplied.
+ base::FilePath this_locale_path =
+ locale_path.AppendASCII(all_fallback_locales[i]);
+ if (!base::PathExists(this_locale_path))
+ continue;
+ linked_ptr<base::DictionaryValue> catalog(
+ LoadMessageFile(locale_path, all_fallback_locales[i], error));
+ if (!catalog.get()) {
+ // If locale is valid, but messages.json is corrupted or missing, return
+ // an error.
+ return NULL;
+ } else {
+ catalogs.push_back(catalog);
+ }
+ }
+
+ return extensions::MessageBundle::Create(catalogs, error);
+}
+
+bool ValidateExtensionLocales(const base::FilePath& extension_path,
+ const base::DictionaryValue* manifest,
+ std::string* error) {
+ std::string default_locale = GetDefaultLocaleFromManifest(*manifest, error);
+
+ if (default_locale.empty())
+ return true;
+
+ base::FilePath locale_path = extension_path.Append(extensions::kLocaleFolder);
+
+ std::set<std::string> valid_locales;
+ if (!GetValidLocales(locale_path, &valid_locales, error))
+ return false;
+
+ for (std::set<std::string>::const_iterator locale = valid_locales.begin();
+ locale != valid_locales.end();
+ ++locale) {
+ std::string locale_error;
+ scoped_ptr<base::DictionaryValue> catalog(
+ LoadMessageFile(locale_path, *locale, &locale_error));
+
+ if (!locale_error.empty()) {
+ if (!error->empty())
+ error->append(" ");
+ error->append(locale_error);
+ }
+ }
+
+ return error->empty();
+}
+
+bool ShouldSkipValidation(const base::FilePath& locales_path,
+ const base::FilePath& locale_path,
+ const std::set<std::string>& all_locales) {
+ // Since we use this string as a key in a DictionaryValue, be paranoid about
+ // skipping any strings with '.'. This happens sometimes, for example with
+ // '.svn' directories.
+ base::FilePath relative_path;
+ if (!locales_path.AppendRelativePath(locale_path, &relative_path)) {
+ NOTREACHED();
+ return true;
+ }
+ std::string subdir = relative_path.MaybeAsASCII();
+ if (subdir.empty())
+ return true; // Non-ASCII.
+
+ if (std::find(subdir.begin(), subdir.end(), '.') != subdir.end())
+ return true;
+
+ if (all_locales.find(subdir) == all_locales.end())
+ return true;
+
+ return false;
+}
+
+ScopedLocaleForTest::ScopedLocaleForTest()
+ : locale_(extension_l10n_util::CurrentLocaleOrDefault()) {}
+
+ScopedLocaleForTest::ScopedLocaleForTest(const std::string& locale)
+ : locale_(extension_l10n_util::CurrentLocaleOrDefault()) {
+ extension_l10n_util::SetProcessLocale(locale);
+}
+
+ScopedLocaleForTest::~ScopedLocaleForTest() {
+ extension_l10n_util::SetProcessLocale(locale_);
+}
+
+} // namespace extension_l10n_util
diff --git a/chromium/extensions/common/extension_l10n_util.h b/chromium/extensions/common/extension_l10n_util.h
new file mode 100644
index 00000000000..0f7f8c953a5
--- /dev/null
+++ b/chromium/extensions/common/extension_l10n_util.h
@@ -0,0 +1,130 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This file declares extension specific l10n utils.
+
+#ifndef EXTENSIONS_COMMON_EXTENSION_L10N_UTIL_H_
+#define EXTENSIONS_COMMON_EXTENSION_L10N_UTIL_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+namespace base {
+class DictionaryValue;
+class FilePath;
+}
+
+namespace extensions {
+struct ExtensionInfo;
+class MessageBundle;
+}
+
+namespace extension_l10n_util {
+
+// Set the locale for this process to a fixed value, rather than using the
+// normal file-based lookup mechanisms. This is used to set the locale inside
+// the sandboxed utility process, where file reading is not allowed.
+void SetProcessLocale(const std::string& locale);
+
+// Returns default locale in form "en-US" or "sr" or empty string if
+// "default_locale" section was not defined in the manifest.json file.
+std::string GetDefaultLocaleFromManifest(const base::DictionaryValue& manifest,
+ std::string* error);
+
+// Returns true iff the extension was localized, and the current locale
+// doesn't match the locale written into info.extension_manifest.
+bool ShouldRelocalizeManifest(const base::DictionaryValue* manifest);
+
+// Localize extension name, description, browser_action and other fields
+// in the manifest.
+bool LocalizeManifest(const extensions::MessageBundle& messages,
+ base::DictionaryValue* manifest,
+ std::string* error);
+
+// Load message catalogs, localize manifest and attach message bundle to the
+// extension.
+bool LocalizeExtension(const base::FilePath& extension_path,
+ base::DictionaryValue* manifest,
+ std::string* error);
+
+// Adds locale_name to the extension if it's in chrome_locales, and
+// if messages file is present (we don't check content of messages file here).
+// Returns false if locale_name was not found in chrome_locales, and sets
+// error with locale_name.
+// If file name starts with . return true (helps testing extensions under svn).
+bool AddLocale(const std::set<std::string>& chrome_locales,
+ const base::FilePath& locale_folder,
+ const std::string& locale_name,
+ std::set<std::string>* valid_locales,
+ std::string* error);
+
+// Returns normalized current locale, or default locale - en_US.
+std::string CurrentLocaleOrDefault();
+
+// Extends list of Chrome locales to them and their parents, so we can do
+// proper fallback.
+void GetAllLocales(std::set<std::string>* all_locales);
+
+// Provides a vector of all fallback locales for message localization.
+// The vector is ordered by priority of locale - |application_locale|,
+// first_parent, ..., |default_locale|.
+void GetAllFallbackLocales(const std::string& application_locale,
+ const std::string& default_locale,
+ std::vector<std::string>* all_fallback_locales);
+
+// Fill |valid_locales| with all valid locales under |locale_path|.
+// |valid_locales| is the intersection of the set of locales supported by
+// Chrome and the set of locales specified by |locale_path|.
+// Returns true if vaild_locales contains at least one locale, false otherwise.
+// |error| contains an error message when a locale is corrupt or missing.
+bool GetValidLocales(const base::FilePath& locale_path,
+ std::set<std::string>* valid_locales,
+ std::string* error);
+
+// Loads messages file for default locale, and application locales (application
+// locales doesn't have to exist). Application locale is current locale and its
+// parents.
+// Returns message bundle if it can load default locale messages file, and all
+// messages are valid, else returns NULL and sets error.
+extensions::MessageBundle* LoadMessageCatalogs(
+ const base::FilePath& locale_path,
+ const std::string& default_locale,
+ const std::string& app_locale,
+ std::string* error);
+
+// Loads message catalogs for all locales to check for validity.
+bool ValidateExtensionLocales(const base::FilePath& extension_path,
+ const base::DictionaryValue* manifest,
+ std::string* error);
+
+// Returns true if directory has "." in the name (for .svn) or if it doesn't
+// belong to Chrome locales.
+// |locales_path| is extension_id/_locales
+// |locale_path| is extension_id/_locales/xx
+// |all_locales| is a set of all valid Chrome locales.
+bool ShouldSkipValidation(const base::FilePath& locales_path,
+ const base::FilePath& locale_path,
+ const std::set<std::string>& all_locales);
+
+// Sets the process locale for the duration of the current scope, then reverts
+// back to whatever the current locale was before constructing this.
+// For testing purposed only!
+class ScopedLocaleForTest {
+ public:
+ // Only revert back to current locale at end of scope, don't set locale.
+ ScopedLocaleForTest();
+
+ // Set temporary locale for the current scope
+ explicit ScopedLocaleForTest(const std::string& locale);
+
+ ~ScopedLocaleForTest();
+
+ private:
+ std::string locale_; // The current locale at ctor time.
+};
+
+} // namespace extension_l10n_util
+
+#endif // EXTENSIONS_COMMON_EXTENSION_L10N_UTIL_H_
diff --git a/chromium/extensions/common/extension_l10n_util_unittest.cc b/chromium/extensions/common/extension_l10n_util_unittest.cc
new file mode 100644
index 00000000000..d269501058f
--- /dev/null
+++ b/chromium/extensions/common/extension_l10n_util_unittest.cc
@@ -0,0 +1,642 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/extension_l10n_util.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension_paths.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/message_bundle.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+namespace keys = manifest_keys;
+
+namespace {
+
+TEST(ExtensionL10nUtil, ValidateLocalesWithBadLocale) {
+ base::ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ base::FilePath src_path = temp.path().Append(kLocaleFolder);
+ base::FilePath locale = src_path.AppendASCII("ms");
+ ASSERT_TRUE(base::CreateDirectory(locale));
+
+ base::FilePath messages_file = locale.Append(kMessagesFilename);
+ std::string data = "{ \"name\":";
+ ASSERT_TRUE(base::WriteFile(messages_file, data.c_str(), data.length()));
+
+ base::DictionaryValue manifest;
+ manifest.SetString(keys::kDefaultLocale, "en");
+ std::string error;
+ EXPECT_FALSE(extension_l10n_util::ValidateExtensionLocales(
+ temp.path(), &manifest, &error));
+ EXPECT_THAT(
+ error,
+ testing::HasSubstr(base::UTF16ToUTF8(messages_file.LossyDisplayName())));
+}
+
+TEST(ExtensionL10nUtil, GetValidLocalesEmptyLocaleFolder) {
+ base::ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ base::FilePath src_path = temp.path().Append(kLocaleFolder);
+ ASSERT_TRUE(base::CreateDirectory(src_path));
+
+ std::string error;
+ std::set<std::string> locales;
+ EXPECT_FALSE(
+ extension_l10n_util::GetValidLocales(src_path, &locales, &error));
+
+ EXPECT_TRUE(locales.empty());
+}
+
+TEST(ExtensionL10nUtil, GetValidLocalesWithValidLocaleNoMessagesFile) {
+ base::ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ base::FilePath src_path = temp.path().Append(kLocaleFolder);
+ ASSERT_TRUE(base::CreateDirectory(src_path));
+ ASSERT_TRUE(base::CreateDirectory(src_path.AppendASCII("sr")));
+
+ std::string error;
+ std::set<std::string> locales;
+ EXPECT_FALSE(
+ extension_l10n_util::GetValidLocales(src_path, &locales, &error));
+
+ EXPECT_TRUE(locales.empty());
+}
+
+TEST(ExtensionL10nUtil, GetValidLocalesWithUnsupportedLocale) {
+ base::ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ base::FilePath src_path = temp.path().Append(kLocaleFolder);
+ ASSERT_TRUE(base::CreateDirectory(src_path));
+ // Supported locale.
+ base::FilePath locale_1 = src_path.AppendASCII("sr");
+ ASSERT_TRUE(base::CreateDirectory(locale_1));
+ std::string data("whatever");
+ ASSERT_TRUE(base::WriteFile(
+ locale_1.Append(kMessagesFilename), data.c_str(), data.length()));
+ // Unsupported locale.
+ ASSERT_TRUE(base::CreateDirectory(src_path.AppendASCII("xxx_yyy")));
+
+ std::string error;
+ std::set<std::string> locales;
+ EXPECT_TRUE(extension_l10n_util::GetValidLocales(src_path, &locales, &error));
+
+ EXPECT_FALSE(locales.empty());
+ EXPECT_TRUE(locales.find("sr") != locales.end());
+ EXPECT_FALSE(locales.find("xxx_yyy") != locales.end());
+}
+
+TEST(ExtensionL10nUtil, GetValidLocalesWithValidLocalesAndMessagesFile) {
+ base::FilePath install_dir;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &install_dir));
+ install_dir =
+ install_dir.AppendASCII("extension_with_locales").Append(kLocaleFolder);
+
+ std::string error;
+ std::set<std::string> locales;
+ EXPECT_TRUE(
+ extension_l10n_util::GetValidLocales(install_dir, &locales, &error));
+ EXPECT_EQ(3U, locales.size());
+ EXPECT_TRUE(locales.find("sr") != locales.end());
+ EXPECT_TRUE(locales.find("en") != locales.end());
+ EXPECT_TRUE(locales.find("en_US") != locales.end());
+}
+
+TEST(ExtensionL10nUtil, LoadMessageCatalogsValidFallback) {
+ base::FilePath install_dir;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &install_dir));
+ install_dir =
+ install_dir.AppendASCII("extension_with_locales").Append(kLocaleFolder);
+
+ std::string error;
+ scoped_ptr<MessageBundle> bundle(extension_l10n_util::LoadMessageCatalogs(
+ install_dir, "sr", "en_US", &error));
+ ASSERT_FALSE(NULL == bundle.get());
+ EXPECT_TRUE(error.empty());
+ EXPECT_EQ("Color", bundle->GetL10nMessage("color"));
+ EXPECT_EQ("Not in the US or GB.", bundle->GetL10nMessage("not_in_US_or_GB"));
+}
+
+TEST(ExtensionL10nUtil, LoadMessageCatalogsMissingFiles) {
+ base::ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ base::FilePath src_path = temp.path().Append(kLocaleFolder);
+ ASSERT_TRUE(base::CreateDirectory(src_path));
+ ASSERT_TRUE(base::CreateDirectory(src_path.AppendASCII("en")));
+ ASSERT_TRUE(base::CreateDirectory(src_path.AppendASCII("sr")));
+
+ std::string error;
+ EXPECT_TRUE(NULL == extension_l10n_util::LoadMessageCatalogs(src_path, "en",
+ "sr", &error));
+ EXPECT_FALSE(error.empty());
+}
+
+TEST(ExtensionL10nUtil, LoadMessageCatalogsBadJSONFormat) {
+ base::ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ base::FilePath src_path = temp.path().Append(kLocaleFolder);
+ ASSERT_TRUE(base::CreateDirectory(src_path));
+
+ base::FilePath locale = src_path.AppendASCII("sr");
+ ASSERT_TRUE(base::CreateDirectory(locale));
+
+ std::string data = "{ \"name\":";
+ base::FilePath messages_file = locale.Append(kMessagesFilename);
+ ASSERT_TRUE(base::WriteFile(messages_file, data.c_str(), data.length()));
+
+ std::string error;
+ EXPECT_TRUE(NULL == extension_l10n_util::LoadMessageCatalogs(
+ src_path, "en_US", "sr", &error));
+ EXPECT_EQ(ErrorUtils::FormatErrorMessage(
+ errors::kLocalesInvalidLocale,
+ base::UTF16ToUTF8(messages_file.LossyDisplayName()),
+ "Line: 1, column: 10, Unexpected token."),
+ error);
+}
+
+TEST(ExtensionL10nUtil, LoadMessageCatalogsDuplicateKeys) {
+ base::ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ base::FilePath src_path = temp.path().Append(kLocaleFolder);
+ ASSERT_TRUE(base::CreateDirectory(src_path));
+
+ base::FilePath locale_1 = src_path.AppendASCII("en");
+ ASSERT_TRUE(base::CreateDirectory(locale_1));
+
+ std::string data =
+ "{ \"name\": { \"message\": \"something\" }, "
+ "\"name\": { \"message\": \"something else\" } }";
+ ASSERT_TRUE(base::WriteFile(
+ locale_1.Append(kMessagesFilename), data.c_str(), data.length()));
+
+ base::FilePath locale_2 = src_path.AppendASCII("sr");
+ ASSERT_TRUE(base::CreateDirectory(locale_2));
+
+ ASSERT_TRUE(base::WriteFile(
+ locale_2.Append(kMessagesFilename), data.c_str(), data.length()));
+
+ std::string error;
+ // JSON parser hides duplicates. We are going to get only one key/value
+ // pair at the end.
+ scoped_ptr<MessageBundle> message_bundle(
+ extension_l10n_util::LoadMessageCatalogs(src_path, "en", "sr", &error));
+ EXPECT_TRUE(NULL != message_bundle.get());
+ EXPECT_TRUE(error.empty());
+}
+
+// Caller owns the returned object.
+MessageBundle* CreateManifestBundle() {
+ linked_ptr<base::DictionaryValue> catalog(new base::DictionaryValue);
+
+ base::DictionaryValue* name_tree = new base::DictionaryValue();
+ name_tree->SetString("message", "name");
+ catalog->Set("name", name_tree);
+
+ base::DictionaryValue* short_name_tree = new base::DictionaryValue();
+ short_name_tree->SetString("message", "short_name");
+ catalog->Set("short_name", short_name_tree);
+
+ base::DictionaryValue* description_tree = new base::DictionaryValue();
+ description_tree->SetString("message", "description");
+ catalog->Set("description", description_tree);
+
+ base::DictionaryValue* action_title_tree = new base::DictionaryValue();
+ action_title_tree->SetString("message", "action title");
+ catalog->Set("title", action_title_tree);
+
+ base::DictionaryValue* omnibox_keyword_tree = new base::DictionaryValue();
+ omnibox_keyword_tree->SetString("message", "omnibox keyword");
+ catalog->Set("omnibox_keyword", omnibox_keyword_tree);
+
+ base::DictionaryValue* file_handler_title_tree = new base::DictionaryValue();
+ file_handler_title_tree->SetString("message", "file handler title");
+ catalog->Set("file_handler_title", file_handler_title_tree);
+
+ base::DictionaryValue* launch_local_path_tree = new base::DictionaryValue();
+ launch_local_path_tree->SetString("message", "main.html");
+ catalog->Set("launch_local_path", launch_local_path_tree);
+
+ base::DictionaryValue* launch_web_url_tree = new base::DictionaryValue();
+ launch_web_url_tree->SetString("message", "http://www.google.com/");
+ catalog->Set("launch_web_url", launch_web_url_tree);
+
+ base::DictionaryValue* first_command_description_tree =
+ new base::DictionaryValue();
+ first_command_description_tree->SetString("message", "first command");
+ catalog->Set("first_command_description", first_command_description_tree);
+
+ base::DictionaryValue* second_command_description_tree =
+ new base::DictionaryValue();
+ second_command_description_tree->SetString("message", "second command");
+ catalog->Set("second_command_description", second_command_description_tree);
+
+ base::DictionaryValue* url_country_tree = new base::DictionaryValue();
+ url_country_tree->SetString("message", "de");
+ catalog->Set("country", url_country_tree);
+
+ std::vector<linked_ptr<base::DictionaryValue> > catalogs;
+ catalogs.push_back(catalog);
+
+ std::string error;
+ MessageBundle* bundle = MessageBundle::Create(catalogs, &error);
+ EXPECT_TRUE(bundle);
+ EXPECT_TRUE(error.empty());
+
+ return bundle;
+}
+
+TEST(ExtensionL10nUtil, LocalizeEmptyManifest) {
+ base::DictionaryValue manifest;
+ std::string error;
+ scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+ EXPECT_FALSE(
+ extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+ EXPECT_EQ(std::string(errors::kInvalidName), error);
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithoutNameMsgAndEmptyDescription) {
+ base::DictionaryValue manifest;
+ manifest.SetString(keys::kName, "no __MSG");
+ std::string error;
+ scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+ EXPECT_TRUE(
+ extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+ std::string result;
+ ASSERT_TRUE(manifest.GetString(keys::kName, &result));
+ EXPECT_EQ("no __MSG", result);
+
+ EXPECT_FALSE(manifest.HasKey(keys::kDescription));
+
+ EXPECT_TRUE(error.empty());
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithNameMsgAndEmptyDescription) {
+ base::DictionaryValue manifest;
+ manifest.SetString(keys::kName, "__MSG_name__");
+ std::string error;
+ scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+ EXPECT_TRUE(
+ extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+ std::string result;
+ ASSERT_TRUE(manifest.GetString(keys::kName, &result));
+ EXPECT_EQ("name", result);
+
+ EXPECT_FALSE(manifest.HasKey(keys::kDescription));
+
+ EXPECT_TRUE(error.empty());
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithLocalLaunchURL) {
+ base::DictionaryValue manifest;
+ manifest.SetString(keys::kName, "name");
+ manifest.SetString(keys::kLaunchLocalPath, "__MSG_launch_local_path__");
+ std::string error;
+ scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+ EXPECT_TRUE(
+ extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+ std::string result;
+ ASSERT_TRUE(manifest.GetString(keys::kLaunchLocalPath, &result));
+ EXPECT_EQ("main.html", result);
+
+ EXPECT_TRUE(error.empty());
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithHostedLaunchURL) {
+ base::DictionaryValue manifest;
+ manifest.SetString(keys::kName, "name");
+ manifest.SetString(keys::kLaunchWebURL, "__MSG_launch_web_url__");
+ std::string error;
+ scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+ EXPECT_TRUE(
+ extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+ std::string result;
+ ASSERT_TRUE(manifest.GetString(keys::kLaunchWebURL, &result));
+ EXPECT_EQ("http://www.google.com/", result);
+
+ EXPECT_TRUE(error.empty());
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithBadNameMsg) {
+ base::DictionaryValue manifest;
+ manifest.SetString(keys::kName, "__MSG_name_is_bad__");
+ manifest.SetString(keys::kDescription, "__MSG_description__");
+ std::string error;
+ scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+ EXPECT_FALSE(
+ extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+ std::string result;
+ ASSERT_TRUE(manifest.GetString(keys::kName, &result));
+ EXPECT_EQ("__MSG_name_is_bad__", result);
+
+ ASSERT_TRUE(manifest.GetString(keys::kDescription, &result));
+ EXPECT_EQ("__MSG_description__", result);
+
+ EXPECT_EQ("Variable __MSG_name_is_bad__ used but not defined.", error);
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithNameDescriptionDefaultTitleMsgs) {
+ base::DictionaryValue manifest;
+ manifest.SetString(keys::kName, "__MSG_name__");
+ manifest.SetString(keys::kDescription, "__MSG_description__");
+ std::string action_title(keys::kBrowserAction);
+ action_title.append(".");
+ action_title.append(keys::kPageActionDefaultTitle);
+ manifest.SetString(action_title, "__MSG_title__");
+
+ std::string error;
+ scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+ EXPECT_TRUE(
+ extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+ std::string result;
+ ASSERT_TRUE(manifest.GetString(keys::kName, &result));
+ EXPECT_EQ("name", result);
+
+ ASSERT_TRUE(manifest.GetString(keys::kDescription, &result));
+ EXPECT_EQ("description", result);
+
+ ASSERT_TRUE(manifest.GetString(action_title, &result));
+ EXPECT_EQ("action title", result);
+
+ EXPECT_TRUE(error.empty());
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithNameDescriptionOmniboxMsgs) {
+ base::DictionaryValue manifest;
+ manifest.SetString(keys::kName, "__MSG_name__");
+ manifest.SetString(keys::kDescription, "__MSG_description__");
+ manifest.SetString(keys::kOmniboxKeyword, "__MSG_omnibox_keyword__");
+
+ std::string error;
+ scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+ EXPECT_TRUE(
+ extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+ std::string result;
+ ASSERT_TRUE(manifest.GetString(keys::kName, &result));
+ EXPECT_EQ("name", result);
+
+ ASSERT_TRUE(manifest.GetString(keys::kDescription, &result));
+ EXPECT_EQ("description", result);
+
+ ASSERT_TRUE(manifest.GetString(keys::kOmniboxKeyword, &result));
+ EXPECT_EQ("omnibox keyword", result);
+
+ EXPECT_TRUE(error.empty());
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithNameDescriptionFileHandlerTitle) {
+ base::DictionaryValue manifest;
+ manifest.SetString(keys::kName, "__MSG_name__");
+ manifest.SetString(keys::kDescription, "__MSG_description__");
+ base::ListValue* handlers = new base::ListValue();
+ manifest.Set(keys::kFileBrowserHandlers, handlers);
+ base::DictionaryValue* handler = new base::DictionaryValue();
+ handlers->Append(handler);
+ handler->SetString(keys::kPageActionDefaultTitle,
+ "__MSG_file_handler_title__");
+
+ std::string error;
+ scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+ EXPECT_TRUE(
+ extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+ std::string result;
+ ASSERT_TRUE(manifest.GetString(keys::kName, &result));
+ EXPECT_EQ("name", result);
+
+ ASSERT_TRUE(manifest.GetString(keys::kDescription, &result));
+ EXPECT_EQ("description", result);
+
+ ASSERT_TRUE(handler->GetString(keys::kPageActionDefaultTitle, &result));
+ EXPECT_EQ("file handler title", result);
+
+ EXPECT_TRUE(error.empty());
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithNameDescriptionCommandDescription) {
+ base::DictionaryValue manifest;
+ manifest.SetString(keys::kName, "__MSG_name__");
+ manifest.SetString(keys::kDescription, "__MSG_description__");
+ base::DictionaryValue* commands = new base::DictionaryValue();
+ std::string commands_title(keys::kCommands);
+ manifest.Set(commands_title, commands);
+
+ base::DictionaryValue* first_command = new base::DictionaryValue();
+ commands->Set("first_command", first_command);
+ first_command->SetString(keys::kDescription,
+ "__MSG_first_command_description__");
+
+ base::DictionaryValue* second_command = new base::DictionaryValue();
+ commands->Set("second_command", second_command);
+ second_command->SetString(keys::kDescription,
+ "__MSG_second_command_description__");
+
+ std::string error;
+ scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+ EXPECT_TRUE(
+ extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+ std::string result;
+ ASSERT_TRUE(manifest.GetString(keys::kName, &result));
+ EXPECT_EQ("name", result);
+
+ ASSERT_TRUE(manifest.GetString(keys::kDescription, &result));
+ EXPECT_EQ("description", result);
+
+ ASSERT_TRUE(
+ manifest.GetString("commands.first_command.description", &result));
+ EXPECT_EQ("first command", result);
+
+ ASSERT_TRUE(
+ manifest.GetString("commands.second_command.description", &result));
+ EXPECT_EQ("second command", result);
+
+ EXPECT_TRUE(error.empty());
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithShortName) {
+ base::DictionaryValue manifest;
+ manifest.SetString(keys::kName, "extension name");
+ manifest.SetString(keys::kShortName, "__MSG_short_name__");
+
+ std::string error;
+ scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+ EXPECT_TRUE(
+ extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+ EXPECT_TRUE(error.empty());
+
+ std::string result;
+ ASSERT_TRUE(manifest.GetString(keys::kShortName, &result));
+ EXPECT_EQ("short_name", result);
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithBadShortName) {
+ base::DictionaryValue manifest;
+ manifest.SetString(keys::kName, "extension name");
+ manifest.SetString(keys::kShortName, "__MSG_short_name_bad__");
+
+ std::string error;
+ scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+ EXPECT_FALSE(
+ extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+ EXPECT_FALSE(error.empty());
+
+ std::string result;
+ ASSERT_TRUE(manifest.GetString(keys::kShortName, &result));
+ EXPECT_EQ("__MSG_short_name_bad__", result);
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithSearchProviderMsgs) {
+ base::DictionaryValue manifest;
+ manifest.SetString(keys::kName, "__MSG_name__");
+ manifest.SetString(keys::kDescription, "__MSG_description__");
+
+ base::DictionaryValue* search_provider = new base::DictionaryValue;
+ search_provider->SetString("name", "__MSG_country__");
+ search_provider->SetString("keyword", "__MSG_omnibox_keyword__");
+ search_provider->SetString("search_url", "http://www.foo.__MSG_country__");
+ search_provider->SetString("favicon_url", "http://www.foo.__MSG_country__");
+ search_provider->SetString("suggest_url", "http://www.foo.__MSG_country__");
+ manifest.Set(keys::kOverrideSearchProvider, search_provider);
+
+ manifest.SetString(keys::kOverrideHomepage, "http://www.foo.__MSG_country__");
+
+ base::ListValue* startup_pages = new base::ListValue;
+ startup_pages->AppendString("http://www.foo.__MSG_country__");
+ manifest.Set(keys::kOverrideStartupPage, startup_pages);
+
+ std::string error;
+ scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+ EXPECT_TRUE(
+ extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+ std::string result;
+ ASSERT_TRUE(manifest.GetString(keys::kName, &result));
+ EXPECT_EQ("name", result);
+
+ ASSERT_TRUE(manifest.GetString(keys::kDescription, &result));
+ EXPECT_EQ("description", result);
+
+ std::string key_prefix(keys::kOverrideSearchProvider);
+ key_prefix += '.';
+ ASSERT_TRUE(manifest.GetString(key_prefix + "name", &result));
+ EXPECT_EQ("de", result);
+
+ ASSERT_TRUE(manifest.GetString(key_prefix + "keyword", &result));
+ EXPECT_EQ("omnibox keyword", result);
+
+ ASSERT_TRUE(manifest.GetString(key_prefix + "search_url", &result));
+ EXPECT_EQ("http://www.foo.de", result);
+
+ ASSERT_TRUE(manifest.GetString(key_prefix + "favicon_url", &result));
+ EXPECT_EQ("http://www.foo.de", result);
+
+ ASSERT_TRUE(manifest.GetString(key_prefix + "suggest_url", &result));
+ EXPECT_EQ("http://www.foo.de", result);
+
+ ASSERT_TRUE(manifest.GetString(keys::kOverrideHomepage, &result));
+ EXPECT_EQ("http://www.foo.de", result);
+
+ ASSERT_TRUE(manifest.GetList(keys::kOverrideStartupPage, &startup_pages));
+ ASSERT_TRUE(startup_pages->GetString(0, &result));
+ EXPECT_EQ("http://www.foo.de", result);
+
+ EXPECT_TRUE(error.empty());
+}
+
+// Try with NULL manifest.
+TEST(ExtensionL10nUtil, ShouldRelocalizeManifestWithNullManifest) {
+ EXPECT_FALSE(extension_l10n_util::ShouldRelocalizeManifest(NULL));
+}
+
+// Try with default and current locales missing.
+TEST(ExtensionL10nUtil, ShouldRelocalizeManifestEmptyManifest) {
+ base::DictionaryValue manifest;
+ EXPECT_FALSE(extension_l10n_util::ShouldRelocalizeManifest(&manifest));
+}
+
+// Try with missing current_locale.
+TEST(ExtensionL10nUtil, ShouldRelocalizeManifestWithDefaultLocale) {
+ base::DictionaryValue manifest;
+ manifest.SetString(keys::kDefaultLocale, "en_US");
+ EXPECT_TRUE(extension_l10n_util::ShouldRelocalizeManifest(&manifest));
+}
+
+// Try with missing default_locale.
+TEST(ExtensionL10nUtil, ShouldRelocalizeManifestWithCurrentLocale) {
+ base::DictionaryValue manifest;
+ manifest.SetString(keys::kCurrentLocale,
+ extension_l10n_util::CurrentLocaleOrDefault());
+ EXPECT_FALSE(extension_l10n_util::ShouldRelocalizeManifest(&manifest));
+}
+
+// Try with all data present, but with same current_locale as system locale.
+TEST(ExtensionL10nUtil, ShouldRelocalizeManifestSameCurrentLocale) {
+ base::DictionaryValue manifest;
+ manifest.SetString(keys::kDefaultLocale, "en_US");
+ manifest.SetString(keys::kCurrentLocale,
+ extension_l10n_util::CurrentLocaleOrDefault());
+ EXPECT_FALSE(extension_l10n_util::ShouldRelocalizeManifest(&manifest));
+}
+
+// Try with all data present, but with different current_locale.
+TEST(ExtensionL10nUtil, ShouldRelocalizeManifestDifferentCurrentLocale) {
+ base::DictionaryValue manifest;
+ manifest.SetString(keys::kDefaultLocale, "en_US");
+ manifest.SetString(keys::kCurrentLocale, "sr");
+ EXPECT_TRUE(extension_l10n_util::ShouldRelocalizeManifest(&manifest));
+}
+
+TEST(ExtensionL10nUtil, GetAllFallbackLocales) {
+ std::vector<std::string> fallback_locales;
+ extension_l10n_util::GetAllFallbackLocales("en_US", "all", &fallback_locales);
+ ASSERT_EQ(3U, fallback_locales.size());
+
+ CHECK_EQ("en_US", fallback_locales[0]);
+ CHECK_EQ("en", fallback_locales[1]);
+ CHECK_EQ("all", fallback_locales[2]);
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/common/extension_message_generator.cc b/chromium/extensions/common/extension_message_generator.cc
new file mode 100644
index 00000000000..b066c4f6cb8
--- /dev/null
+++ b/chromium/extensions/common/extension_message_generator.cc
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Get basic type definitions.
+#define IPC_MESSAGE_IMPL
+#include "extensions/common/extension_message_generator.h"
+
+// Generate constructors.
+#include "ipc/struct_constructor_macros.h"
+#include "extensions/common/extension_message_generator.h"
+
+// Generate destructors.
+#include "ipc/struct_destructor_macros.h"
+#include "extensions/common/extension_message_generator.h"
+
+// Generate param traits write methods.
+#include "ipc/param_traits_write_macros.h"
+namespace IPC {
+#include "extensions/common/extension_message_generator.h"
+} // namespace IPC
+
+// Generate param traits read methods.
+#include "ipc/param_traits_read_macros.h"
+namespace IPC {
+#include "extensions/common/extension_message_generator.h"
+} // namespace IPC
+
+// Generate param traits log methods.
+#include "ipc/param_traits_log_macros.h"
+namespace IPC {
+#include "extensions/common/extension_message_generator.h"
+} // namespace IPC
+
diff --git a/chromium/extensions/common/extension_message_generator.h b/chromium/extensions/common/extension_message_generator.h
new file mode 100644
index 00000000000..b2c61f18826
--- /dev/null
+++ b/chromium/extensions/common/extension_message_generator.h
@@ -0,0 +1,9 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Multiply-included file, hence no include guard.
+
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/extension_utility_messages.h"
+#include "extensions/common/guest_view/extensions_guest_view_messages.h"
diff --git a/chromium/extensions/common/extension_messages.cc b/chromium/extensions/common/extension_messages.cc
new file mode 100644
index 00000000000..c648b28a8db
--- /dev/null
+++ b/chromium/extensions/common/extension_messages.cc
@@ -0,0 +1,342 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/extension_messages.h"
+
+#include <stddef.h>
+
+#include "content/public/common/common_param_traits.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_handler.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/permissions/permissions_info.h"
+
+using extensions::APIPermission;
+using extensions::APIPermissionInfo;
+using extensions::APIPermissionSet;
+using extensions::Extension;
+using extensions::Manifest;
+using extensions::ManifestHandler;
+using extensions::ManifestPermission;
+using extensions::ManifestPermissionSet;
+using extensions::PermissionSet;
+using extensions::URLPatternSet;
+
+ExtensionMsg_PermissionSetStruct::ExtensionMsg_PermissionSetStruct() {
+}
+
+ExtensionMsg_PermissionSetStruct::ExtensionMsg_PermissionSetStruct(
+ const PermissionSet& permissions)
+ : apis(permissions.apis()),
+ manifest_permissions(permissions.manifest_permissions()),
+ explicit_hosts(permissions.explicit_hosts()),
+ scriptable_hosts(permissions.scriptable_hosts()) {
+}
+
+ExtensionMsg_PermissionSetStruct::ExtensionMsg_PermissionSetStruct(
+ const ExtensionMsg_PermissionSetStruct& other) = default;
+
+ExtensionMsg_PermissionSetStruct::~ExtensionMsg_PermissionSetStruct() {
+}
+
+scoped_ptr<const PermissionSet>
+ExtensionMsg_PermissionSetStruct::ToPermissionSet() const {
+ return make_scoped_ptr(new PermissionSet(apis, manifest_permissions,
+ explicit_hosts, scriptable_hosts));
+}
+
+ExtensionMsg_Loaded_Params::ExtensionMsg_Loaded_Params()
+ : location(Manifest::INVALID_LOCATION),
+ creation_flags(Extension::NO_FLAGS) {}
+
+ExtensionMsg_Loaded_Params::~ExtensionMsg_Loaded_Params() {}
+
+ExtensionMsg_Loaded_Params::ExtensionMsg_Loaded_Params(
+ const Extension* extension,
+ bool include_tab_permissions)
+ : manifest(extension->manifest()->value()->DeepCopy()),
+ location(extension->location()),
+ path(extension->path()),
+ active_permissions(extension->permissions_data()->active_permissions()),
+ withheld_permissions(extension->permissions_data()
+ ->withheld_permissions()),
+ id(extension->id()),
+ creation_flags(extension->creation_flags()) {
+ if (include_tab_permissions) {
+ for (const auto& pair :
+ extension->permissions_data()->tab_specific_permissions()) {
+ tab_specific_permissions[pair.first] =
+ ExtensionMsg_PermissionSetStruct(*pair.second);
+ }
+ }
+}
+
+ExtensionMsg_Loaded_Params::ExtensionMsg_Loaded_Params(
+ const ExtensionMsg_Loaded_Params& other) = default;
+
+scoped_refptr<Extension> ExtensionMsg_Loaded_Params::ConvertToExtension(
+ std::string* error) const {
+ scoped_refptr<Extension> extension =
+ Extension::Create(path, location, *manifest, creation_flags, error);
+ if (extension.get()) {
+ const extensions::PermissionsData* permissions_data =
+ extension->permissions_data();
+ permissions_data->SetPermissions(active_permissions.ToPermissionSet(),
+ withheld_permissions.ToPermissionSet());
+ for (const auto& pair : tab_specific_permissions) {
+ permissions_data->UpdateTabSpecificPermissions(
+ pair.first, *pair.second.ToPermissionSet());
+ }
+ }
+ return extension;
+}
+
+namespace IPC {
+
+template <>
+struct ParamTraits<Manifest::Location> {
+ typedef Manifest::Location param_type;
+ static void Write(base::Pickle* m, const param_type& p) {
+ int val = static_cast<int>(p);
+ WriteParam(m, val);
+ }
+ static bool Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* p) {
+ int val = 0;
+ if (!ReadParam(m, iter, &val) ||
+ val < Manifest::INVALID_LOCATION ||
+ val >= Manifest::NUM_LOCATIONS)
+ return false;
+ *p = static_cast<param_type>(val);
+ return true;
+ }
+ static void Log(const param_type& p, std::string* l) {
+ ParamTraits<int>::Log(static_cast<int>(p), l);
+ }
+};
+
+void ParamTraits<URLPattern>::Write(base::Pickle* m, const param_type& p) {
+ WriteParam(m, p.valid_schemes());
+ WriteParam(m, p.GetAsString());
+}
+
+bool ParamTraits<URLPattern>::Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* p) {
+ int valid_schemes;
+ std::string spec;
+ if (!ReadParam(m, iter, &valid_schemes) ||
+ !ReadParam(m, iter, &spec))
+ return false;
+
+ // TODO(jstritar): We don't want the URLPattern to fail parsing when the
+ // scheme is invalid. Instead, the pattern should parse but it should not
+ // match the invalid patterns. We get around this by setting the valid
+ // schemes after parsing the pattern. Update these method calls once we can
+ // ignore scheme validation with URLPattern parse options. crbug.com/90544
+ p->SetValidSchemes(URLPattern::SCHEME_ALL);
+ URLPattern::ParseResult result = p->Parse(spec);
+ p->SetValidSchemes(valid_schemes);
+ return URLPattern::PARSE_SUCCESS == result;
+}
+
+void ParamTraits<URLPattern>::Log(const param_type& p, std::string* l) {
+ LogParam(p.GetAsString(), l);
+}
+
+void ParamTraits<URLPatternSet>::Write(base::Pickle* m, const param_type& p) {
+ WriteParam(m, p.patterns());
+}
+
+bool ParamTraits<URLPatternSet>::Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* p) {
+ std::set<URLPattern> patterns;
+ if (!ReadParam(m, iter, &patterns))
+ return false;
+
+ for (std::set<URLPattern>::iterator i = patterns.begin();
+ i != patterns.end(); ++i)
+ p->AddPattern(*i);
+ return true;
+}
+
+void ParamTraits<URLPatternSet>::Log(const param_type& p, std::string* l) {
+ LogParam(p.patterns(), l);
+}
+
+void ParamTraits<APIPermission::ID>::Write(base::Pickle* m,
+ const param_type& p) {
+ WriteParam(m, static_cast<int>(p));
+}
+
+bool ParamTraits<APIPermission::ID>::Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* p) {
+ int api_id = -2;
+ if (!ReadParam(m, iter, &api_id))
+ return false;
+
+ *p = static_cast<APIPermission::ID>(api_id);
+ return true;
+}
+
+void ParamTraits<APIPermission::ID>::Log(
+ const param_type& p, std::string* l) {
+ LogParam(static_cast<int>(p), l);
+}
+
+void ParamTraits<APIPermissionSet>::Write(base::Pickle* m,
+ const param_type& p) {
+ APIPermissionSet::const_iterator it = p.begin();
+ const APIPermissionSet::const_iterator end = p.end();
+ WriteParam(m, static_cast<uint32_t>(p.size()));
+ for (; it != end; ++it) {
+ WriteParam(m, it->id());
+ it->Write(m);
+ }
+}
+
+bool ParamTraits<APIPermissionSet>::Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r) {
+ uint32_t size;
+ if (!ReadParam(m, iter, &size))
+ return false;
+ for (uint32_t i = 0; i < size; ++i) {
+ APIPermission::ID id;
+ if (!ReadParam(m, iter, &id))
+ return false;
+ const APIPermissionInfo* permission_info =
+ extensions::PermissionsInfo::GetInstance()->GetByID(id);
+ if (!permission_info)
+ return false;
+ scoped_ptr<APIPermission> p(permission_info->CreateAPIPermission());
+ if (!p->Read(m, iter))
+ return false;
+ r->insert(p.release());
+ }
+ return true;
+}
+
+void ParamTraits<APIPermissionSet>::Log(
+ const param_type& p, std::string* l) {
+ LogParam(p.map(), l);
+}
+
+void ParamTraits<ManifestPermissionSet>::Write(base::Pickle* m,
+ const param_type& p) {
+ ManifestPermissionSet::const_iterator it = p.begin();
+ const ManifestPermissionSet::const_iterator end = p.end();
+ WriteParam(m, static_cast<uint32_t>(p.size()));
+ for (; it != end; ++it) {
+ WriteParam(m, it->name());
+ it->Write(m);
+ }
+}
+
+bool ParamTraits<ManifestPermissionSet>::Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r) {
+ uint32_t size;
+ if (!ReadParam(m, iter, &size))
+ return false;
+ for (uint32_t i = 0; i < size; ++i) {
+ std::string name;
+ if (!ReadParam(m, iter, &name))
+ return false;
+ scoped_ptr<ManifestPermission> p(ManifestHandler::CreatePermission(name));
+ if (!p)
+ return false;
+ if (!p->Read(m, iter))
+ return false;
+ r->insert(p.release());
+ }
+ return true;
+}
+
+void ParamTraits<ManifestPermissionSet>::Log(
+ const param_type& p, std::string* l) {
+ LogParam(p.map(), l);
+}
+
+void ParamTraits<HostID>::Write(base::Pickle* m, const param_type& p) {
+ WriteParam(m, p.type());
+ WriteParam(m, p.id());
+}
+
+bool ParamTraits<HostID>::Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r) {
+ HostID::HostType type;
+ std::string id;
+ if (!ReadParam(m, iter, &type))
+ return false;
+ if (!ReadParam(m, iter, &id))
+ return false;
+ *r = HostID(type, id);
+ return true;
+}
+
+void ParamTraits<HostID>::Log(
+ const param_type& p, std::string* l) {
+ LogParam(p.type(), l);
+ LogParam(p.id(), l);
+}
+
+void ParamTraits<ExtensionMsg_PermissionSetStruct>::Write(base::Pickle* m,
+ const param_type& p) {
+ WriteParam(m, p.apis);
+ WriteParam(m, p.manifest_permissions);
+ WriteParam(m, p.explicit_hosts);
+ WriteParam(m, p.scriptable_hosts);
+}
+
+bool ParamTraits<ExtensionMsg_PermissionSetStruct>::Read(
+ const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* p) {
+ return ReadParam(m, iter, &p->apis) &&
+ ReadParam(m, iter, &p->manifest_permissions) &&
+ ReadParam(m, iter, &p->explicit_hosts) &&
+ ReadParam(m, iter, &p->scriptable_hosts);
+}
+
+void ParamTraits<ExtensionMsg_PermissionSetStruct>::Log(const param_type& p,
+ std::string* l) {
+ LogParam(p.apis, l);
+ LogParam(p.manifest_permissions, l);
+ LogParam(p.explicit_hosts, l);
+ LogParam(p.scriptable_hosts, l);
+}
+
+void ParamTraits<ExtensionMsg_Loaded_Params>::Write(base::Pickle* m,
+ const param_type& p) {
+ WriteParam(m, p.location);
+ WriteParam(m, p.path);
+ WriteParam(m, *(p.manifest));
+ WriteParam(m, p.creation_flags);
+ WriteParam(m, p.active_permissions);
+ WriteParam(m, p.withheld_permissions);
+}
+
+bool ParamTraits<ExtensionMsg_Loaded_Params>::Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* p) {
+ p->manifest.reset(new base::DictionaryValue());
+ return ReadParam(m, iter, &p->location) && ReadParam(m, iter, &p->path) &&
+ ReadParam(m, iter, p->manifest.get()) &&
+ ReadParam(m, iter, &p->creation_flags) &&
+ ReadParam(m, iter, &p->active_permissions) &&
+ ReadParam(m, iter, &p->withheld_permissions);
+}
+
+void ParamTraits<ExtensionMsg_Loaded_Params>::Log(const param_type& p,
+ std::string* l) {
+ l->append(p.id);
+}
+
+} // namespace IPC
diff --git a/chromium/extensions/common/extension_messages.h b/chromium/extensions/common/extension_messages.h
new file mode 100644
index 00000000000..904c2f98b42
--- /dev/null
+++ b/chromium/extensions/common/extension_messages.h
@@ -0,0 +1,833 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// IPC messages for extensions.
+// Multiply-included message file, hence no include guard.
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/memory/shared_memory.h"
+#include "base/values.h"
+#include "content/public/common/common_param_traits.h"
+#include "content/public/common/socket_permission_request.h"
+#include "extensions/common/api/messaging/message.h"
+#include "extensions/common/draggable_region.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/host_id.h"
+#include "extensions/common/permissions/media_galleries_permission_data.h"
+#include "extensions/common/permissions/permission_set.h"
+#include "extensions/common/permissions/socket_permission_data.h"
+#include "extensions/common/permissions/usb_device_permission_data.h"
+#include "extensions/common/stack_frame.h"
+#include "extensions/common/url_pattern.h"
+#include "extensions/common/url_pattern_set.h"
+#include "extensions/common/user_script.h"
+#include "extensions/common/view_type.h"
+#include "ipc/ipc_message_macros.h"
+#include "url/gurl.h"
+
+#define IPC_MESSAGE_START ExtensionMsgStart
+
+IPC_ENUM_TRAITS_MAX_VALUE(extensions::ViewType, extensions::VIEW_TYPE_LAST)
+IPC_ENUM_TRAITS_MAX_VALUE(content::SocketPermissionRequest::OperationType,
+ content::SocketPermissionRequest::OPERATION_TYPE_LAST)
+
+IPC_ENUM_TRAITS_MAX_VALUE(extensions::UserScript::InjectionType,
+ extensions::UserScript::INJECTION_TYPE_LAST)
+
+IPC_ENUM_TRAITS_MAX_VALUE(extensions::UserScript::RunLocation,
+ extensions::UserScript::RUN_LOCATION_LAST - 1)
+
+IPC_ENUM_TRAITS_MAX_VALUE(HostID::HostType, HostID::HOST_TYPE_LAST)
+
+// Parameters structure for ExtensionHostMsg_AddAPIActionToActivityLog and
+// ExtensionHostMsg_AddEventToActivityLog.
+IPC_STRUCT_BEGIN(ExtensionHostMsg_APIActionOrEvent_Params)
+ // API name.
+ IPC_STRUCT_MEMBER(std::string, api_call)
+
+ // List of arguments.
+ IPC_STRUCT_MEMBER(base::ListValue, arguments)
+
+ // Extra logging information.
+ IPC_STRUCT_MEMBER(std::string, extra)
+IPC_STRUCT_END()
+
+// Parameters structure for ExtensionHostMsg_AddDOMActionToActivityLog.
+IPC_STRUCT_BEGIN(ExtensionHostMsg_DOMAction_Params)
+ // URL of the page.
+ IPC_STRUCT_MEMBER(GURL, url)
+
+ // Title of the page.
+ IPC_STRUCT_MEMBER(base::string16, url_title)
+
+ // API name.
+ IPC_STRUCT_MEMBER(std::string, api_call)
+
+ // List of arguments.
+ IPC_STRUCT_MEMBER(base::ListValue, arguments)
+
+ // Type of DOM API call.
+ IPC_STRUCT_MEMBER(int, call_type)
+IPC_STRUCT_END()
+
+// Parameters structure for ExtensionHostMsg_Request.
+IPC_STRUCT_BEGIN(ExtensionHostMsg_Request_Params)
+ // Message name.
+ IPC_STRUCT_MEMBER(std::string, name)
+
+ // List of message arguments.
+ IPC_STRUCT_MEMBER(base::ListValue, arguments)
+
+ // Extension ID this request was sent from. This can be empty, in the case
+ // where we expose APIs to normal web pages using the extension function
+ // system.
+ IPC_STRUCT_MEMBER(std::string, extension_id)
+
+ // URL of the frame the request was sent from. This isn't necessarily an
+ // extension url. Extension requests can also originate from content scripts,
+ // in which case extension_id will indicate the ID of the associated
+ // extension. Or, they can originate from hosted apps or normal web pages.
+ IPC_STRUCT_MEMBER(GURL, source_url)
+
+ // The id of the tab that sent this request, or -1 if there is no source tab.
+ IPC_STRUCT_MEMBER(int, source_tab_id)
+
+ // Unique request id to match requests and responses.
+ IPC_STRUCT_MEMBER(int, request_id)
+
+ // True if request has a callback specified.
+ IPC_STRUCT_MEMBER(bool, has_callback)
+
+ // True if request is executed in response to an explicit user gesture.
+ IPC_STRUCT_MEMBER(bool, user_gesture)
+IPC_STRUCT_END()
+
+// Allows an extension to execute code in a tab.
+IPC_STRUCT_BEGIN(ExtensionMsg_ExecuteCode_Params)
+ // The extension API request id, for responding.
+ IPC_STRUCT_MEMBER(int, request_id)
+
+ // The ID of the requesting injection host.
+ IPC_STRUCT_MEMBER(HostID, host_id)
+
+ // Whether the code is JavaScript or CSS.
+ IPC_STRUCT_MEMBER(bool, is_javascript)
+
+ // String of code to execute.
+ IPC_STRUCT_MEMBER(std::string, code)
+
+ // The webview guest source who calls to execute code.
+ IPC_STRUCT_MEMBER(GURL, webview_src)
+
+ // Whether to inject into about:blank (sub)frames.
+ IPC_STRUCT_MEMBER(bool, match_about_blank)
+
+ // When to inject the code.
+ IPC_STRUCT_MEMBER(int, run_at)
+
+ // Whether to execute code in the main world (as opposed to an isolated
+ // world).
+ IPC_STRUCT_MEMBER(bool, in_main_world)
+
+ // Whether the request is coming from a <webview>.
+ IPC_STRUCT_MEMBER(bool, is_web_view)
+
+ // Whether the caller is interested in the result value. Manifest-declared
+ // content scripts and executeScript() calls without a response callback
+ // are examples of when this will be false.
+ IPC_STRUCT_MEMBER(bool, wants_result)
+
+ // The URL of the file that was injected, if any.
+ IPC_STRUCT_MEMBER(GURL, file_url)
+
+ // Whether the code to be executed should be associated with a user gesture.
+ IPC_STRUCT_MEMBER(bool, user_gesture)
+IPC_STRUCT_END()
+
+// Struct containing information about the sender of connect() calls that
+// originate from a tab.
+IPC_STRUCT_BEGIN(ExtensionMsg_TabConnectionInfo)
+ // The tab from where the connection was created.
+ IPC_STRUCT_MEMBER(base::DictionaryValue, tab)
+
+ // The ID of the frame that initiated the connection.
+ // 0 if main frame, positive otherwise. -1 if not initiated from a frame.
+ IPC_STRUCT_MEMBER(int, frame_id)
+IPC_STRUCT_END()
+
+// Struct containing information about the destination of tab.connect().
+IPC_STRUCT_BEGIN(ExtensionMsg_TabTargetConnectionInfo)
+ // The destination tab's ID.
+ IPC_STRUCT_MEMBER(int, tab_id)
+
+ // Frame ID of the destination. -1 for all frames, 0 for main frame and
+ // positive if the destination is a specific child frame.
+ IPC_STRUCT_MEMBER(int, frame_id)
+IPC_STRUCT_END()
+
+// Struct containing the data for external connections to extensions. Used to
+// handle the IPCs initiated by both connect() and onConnect().
+IPC_STRUCT_BEGIN(ExtensionMsg_ExternalConnectionInfo)
+ // The ID of the extension that is the target of the request.
+ IPC_STRUCT_MEMBER(std::string, target_id)
+
+ // The ID of the extension that initiated the request. May be empty if it
+ // wasn't initiated by an extension.
+ IPC_STRUCT_MEMBER(std::string, source_id)
+
+ // The URL of the frame that initiated the request.
+ IPC_STRUCT_MEMBER(GURL, source_url)
+
+ // The process ID of the webview that initiated the request.
+ IPC_STRUCT_MEMBER(int, guest_process_id)
+
+ // The render frame routing ID of the webview that initiated the request.
+ IPC_STRUCT_MEMBER(int, guest_render_frame_routing_id)
+IPC_STRUCT_END()
+
+IPC_STRUCT_TRAITS_BEGIN(extensions::DraggableRegion)
+ IPC_STRUCT_TRAITS_MEMBER(draggable)
+ IPC_STRUCT_TRAITS_MEMBER(bounds)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(content::SocketPermissionRequest)
+ IPC_STRUCT_TRAITS_MEMBER(type)
+ IPC_STRUCT_TRAITS_MEMBER(host)
+ IPC_STRUCT_TRAITS_MEMBER(port)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(extensions::SocketPermissionEntry)
+ IPC_STRUCT_TRAITS_MEMBER(pattern_)
+ IPC_STRUCT_TRAITS_MEMBER(match_subdomains_)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(extensions::SocketPermissionData)
+ IPC_STRUCT_TRAITS_MEMBER(entry())
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(extensions::StackFrame)
+ IPC_STRUCT_TRAITS_MEMBER(line_number)
+ IPC_STRUCT_TRAITS_MEMBER(column_number)
+ IPC_STRUCT_TRAITS_MEMBER(source)
+ IPC_STRUCT_TRAITS_MEMBER(function)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(extensions::UsbDevicePermissionData)
+ IPC_STRUCT_TRAITS_MEMBER(vendor_id())
+ IPC_STRUCT_TRAITS_MEMBER(product_id())
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(extensions::MediaGalleriesPermissionData)
+ IPC_STRUCT_TRAITS_MEMBER(permission())
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(extensions::Message)
+ IPC_STRUCT_TRAITS_MEMBER(data)
+ IPC_STRUCT_TRAITS_MEMBER(user_gesture)
+IPC_STRUCT_TRAITS_END()
+
+// Singly-included section for custom IPC traits.
+#ifndef EXTENSIONS_COMMON_EXTENSION_MESSAGES_H_
+#define EXTENSIONS_COMMON_EXTENSION_MESSAGES_H_
+
+// IPC_MESSAGE macros choke on extra , in the std::map, when expanding. We need
+// to typedef it to avoid that.
+// Substitution map for l10n messages.
+typedef std::map<std::string, std::string> SubstitutionMap;
+
+// Map of extensions IDs to the executing script paths.
+typedef std::map<std::string, std::set<std::string> > ExecutingScriptsMap;
+
+struct ExtensionMsg_PermissionSetStruct {
+ ExtensionMsg_PermissionSetStruct();
+ explicit ExtensionMsg_PermissionSetStruct(
+ const extensions::PermissionSet& permissions);
+ ExtensionMsg_PermissionSetStruct(
+ const ExtensionMsg_PermissionSetStruct& other);
+ ~ExtensionMsg_PermissionSetStruct();
+
+ scoped_ptr<const extensions::PermissionSet> ToPermissionSet() const;
+
+ extensions::APIPermissionSet apis;
+ extensions::ManifestPermissionSet manifest_permissions;
+ extensions::URLPatternSet explicit_hosts;
+ extensions::URLPatternSet scriptable_hosts;
+};
+
+struct ExtensionMsg_Loaded_Params {
+ ExtensionMsg_Loaded_Params();
+ ~ExtensionMsg_Loaded_Params();
+ ExtensionMsg_Loaded_Params(const extensions::Extension* extension,
+ bool include_tab_permissions);
+ ExtensionMsg_Loaded_Params(const ExtensionMsg_Loaded_Params& other);
+
+ // Creates a new extension from the data in this object.
+ scoped_refptr<extensions::Extension> ConvertToExtension(
+ std::string* error) const;
+
+ // The subset of the extension manifest data we send to renderers.
+ linked_ptr<base::DictionaryValue> manifest;
+
+ // The location the extension was installed from.
+ extensions::Manifest::Location location;
+
+ // The path the extension was loaded from. This is used in the renderer only
+ // to generate the extension ID for extensions that are loaded unpacked.
+ base::FilePath path;
+
+ // The extension's active and withheld permissions.
+ ExtensionMsg_PermissionSetStruct active_permissions;
+ ExtensionMsg_PermissionSetStruct withheld_permissions;
+ std::map<int, ExtensionMsg_PermissionSetStruct> tab_specific_permissions;
+
+ // We keep this separate so that it can be used in logging.
+ std::string id;
+
+ // Send creation flags so extension is initialized identically.
+ int creation_flags;
+};
+
+struct ExtensionHostMsg_AutomationQuerySelector_Error {
+ enum Value { kNone, kNoMainFrame, kNoDocument, kNodeDestroyed };
+
+ ExtensionHostMsg_AutomationQuerySelector_Error() : value(kNone) {}
+
+ Value value;
+};
+
+namespace IPC {
+
+template <>
+struct ParamTraits<URLPattern> {
+ typedef URLPattern param_type;
+ static void Write(base::Pickle* m, const param_type& p);
+ static bool Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* p);
+ static void Log(const param_type& p, std::string* l);
+};
+
+template <>
+struct ParamTraits<extensions::URLPatternSet> {
+ typedef extensions::URLPatternSet param_type;
+ static void Write(base::Pickle* m, const param_type& p);
+ static bool Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* p);
+ static void Log(const param_type& p, std::string* l);
+};
+
+template <>
+struct ParamTraits<extensions::APIPermission::ID> {
+ typedef extensions::APIPermission::ID param_type;
+ static void Write(base::Pickle* m, const param_type& p);
+ static bool Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* p);
+ static void Log(const param_type& p, std::string* l);
+};
+
+template <>
+struct ParamTraits<extensions::APIPermissionSet> {
+ typedef extensions::APIPermissionSet param_type;
+ static void Write(base::Pickle* m, const param_type& p);
+ static bool Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r);
+ static void Log(const param_type& p, std::string* l);
+};
+
+template <>
+struct ParamTraits<extensions::ManifestPermissionSet> {
+ typedef extensions::ManifestPermissionSet param_type;
+ static void Write(base::Pickle* m, const param_type& p);
+ static bool Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r);
+ static void Log(const param_type& p, std::string* l);
+};
+
+template <>
+struct ParamTraits<HostID> {
+ typedef HostID param_type;
+ static void Write(base::Pickle* m, const param_type& p);
+ static bool Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r);
+ static void Log(const param_type& p, std::string* l);
+};
+
+template <>
+struct ParamTraits<ExtensionMsg_PermissionSetStruct> {
+ typedef ExtensionMsg_PermissionSetStruct param_type;
+ static void Write(base::Pickle* m, const param_type& p);
+ static bool Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* p);
+ static void Log(const param_type& p, std::string* l);
+};
+
+template <>
+struct ParamTraits<ExtensionMsg_Loaded_Params> {
+ typedef ExtensionMsg_Loaded_Params param_type;
+ static void Write(base::Pickle* m, const param_type& p);
+ static bool Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* p);
+ static void Log(const param_type& p, std::string* l);
+};
+
+} // namespace IPC
+
+#endif // EXTENSIONS_COMMON_EXTENSION_MESSAGES_H_
+
+IPC_ENUM_TRAITS_MAX_VALUE(
+ ExtensionHostMsg_AutomationQuerySelector_Error::Value,
+ ExtensionHostMsg_AutomationQuerySelector_Error::kNodeDestroyed)
+
+IPC_STRUCT_TRAITS_BEGIN(ExtensionHostMsg_AutomationQuerySelector_Error)
+IPC_STRUCT_TRAITS_MEMBER(value)
+IPC_STRUCT_TRAITS_END()
+
+// Parameters structure for ExtensionMsg_UpdatePermissions.
+IPC_STRUCT_BEGIN(ExtensionMsg_UpdatePermissions_Params)
+ IPC_STRUCT_MEMBER(std::string, extension_id)
+ IPC_STRUCT_MEMBER(ExtensionMsg_PermissionSetStruct, active_permissions)
+ IPC_STRUCT_MEMBER(ExtensionMsg_PermissionSetStruct, withheld_permissions)
+IPC_STRUCT_END()
+
+// Messages sent from the browser to the renderer:
+
+// The browser sends this message in response to all extension api calls. The
+// response data (if any) is one of the base::Value subclasses, wrapped as the
+// first element in a ListValue.
+IPC_MESSAGE_ROUTED4(ExtensionMsg_Response,
+ int /* request_id */,
+ bool /* success */,
+ base::ListValue /* response wrapper (see comment above) */,
+ std::string /* error */)
+
+// This message is optionally routed. If used as a control message, it will
+// call a javascript function |function_name| from module |module_name| in
+// every registered context in the target process. If routed, it will be
+// restricted to the contexts that are part of the target RenderView.
+//
+// If |extension_id| is non-empty, the function will be invoked only in
+// contexts owned by the extension. |args| is a list of primitive Value types
+// that are passed to the function.
+IPC_MESSAGE_ROUTED5(ExtensionMsg_MessageInvoke,
+ std::string /* extension_id */,
+ std::string /* module_name */,
+ std::string /* function_name */,
+ base::ListValue /* args */,
+ bool /* delivered as part of a user gesture */)
+
+// Set the top-level frame to the provided name.
+IPC_MESSAGE_ROUTED1(ExtensionMsg_SetFrameName,
+ std::string /* frame_name */)
+
+// Tell the renderer process the platforms system font.
+IPC_MESSAGE_CONTROL2(ExtensionMsg_SetSystemFont,
+ std::string /* font_family */,
+ std::string /* font_size */)
+
+// Marks an extension as 'active' in an extension process. 'Active' extensions
+// have more privileges than other extension content that might end up running
+// in the process (e.g. because of iframes or content scripts).
+IPC_MESSAGE_CONTROL1(ExtensionMsg_ActivateExtension,
+ std::string /* extension_id */)
+
+// Notifies the renderer that extensions were loaded in the browser.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_Loaded,
+ std::vector<ExtensionMsg_Loaded_Params>)
+
+// Notifies the renderer that an extension was unloaded in the browser.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_Unloaded,
+ std::string)
+
+// Updates the scripting whitelist for extensions in the render process. This is
+// only used for testing.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_SetScriptingWhitelist,
+ // extension ids
+ extensions::ExtensionsClient::ScriptingWhitelist)
+
+// Notification that renderer should run some JavaScript code.
+IPC_MESSAGE_ROUTED1(ExtensionMsg_ExecuteCode,
+ ExtensionMsg_ExecuteCode_Params)
+
+// Notification that the user scripts have been updated. It has one
+// SharedMemoryHandle argument consisting of the pickled script data. This
+// handle is valid in the context of the renderer.
+// If |owner| is not empty, then the shared memory handle refers to |owner|'s
+// programmatically-defined scripts. Otherwise, the handle refers to all
+// hosts' statically defined scripts. So far, only extension-hosts support
+// statically defined scripts; WebUI-hosts don't.
+// If |changed_hosts| is not empty, only the host in that set will
+// be updated. Otherwise, all hosts that have scripts in the shared memory
+// region will be updated. Note that the empty set => all hosts case is not
+// supported for per-extension programmatically-defined script regions; in such
+// regions, the owner is expected to list itself as the only changed host.
+// If |whitelisted_only| is true, this process should only run whitelisted
+// scripts and not all user scripts.
+IPC_MESSAGE_CONTROL4(ExtensionMsg_UpdateUserScripts,
+ base::SharedMemoryHandle,
+ HostID /* owner */,
+ std::set<HostID> /* changed hosts */,
+ bool /* whitelisted_only */)
+
+// Trigger to execute declarative content script under browser control.
+IPC_MESSAGE_ROUTED4(ExtensionMsg_ExecuteDeclarativeScript,
+ int /* tab identifier */,
+ extensions::ExtensionId /* extension identifier */,
+ int /* script identifier */,
+ GURL /* page URL where script should be injected */)
+
+// Tell the render view which browser window it's being attached to.
+IPC_MESSAGE_ROUTED1(ExtensionMsg_UpdateBrowserWindowId,
+ int /* id of browser window */)
+
+// Tell the render view what its tab ID is.
+IPC_MESSAGE_ROUTED1(ExtensionMsg_SetTabId,
+ int /* id of tab */)
+
+// Tell the renderer to update an extension's permission set.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_UpdatePermissions,
+ ExtensionMsg_UpdatePermissions_Params)
+
+// Tell the render view about new tab-specific permissions for an extension.
+IPC_MESSAGE_CONTROL5(ExtensionMsg_UpdateTabSpecificPermissions,
+ GURL /* url */,
+ std::string /* extension_id */,
+ extensions::URLPatternSet /* hosts */,
+ bool /* update origin whitelist */,
+ int /* tab_id */)
+
+// Tell the render view to clear tab-specific permissions for some extensions.
+IPC_MESSAGE_CONTROL3(ExtensionMsg_ClearTabSpecificPermissions,
+ std::vector<std::string> /* extension_ids */,
+ bool /* update origin whitelist */,
+ int /* tab_id */)
+
+// Tell the renderer which type this view is.
+IPC_MESSAGE_ROUTED1(ExtensionMsg_NotifyRenderViewType,
+ extensions::ViewType /* view_type */)
+
+// Deliver a message sent with ExtensionHostMsg_PostMessage.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_UsingWebRequestAPI,
+ bool /* webrequest_used */)
+
+// The browser's response to the ExtensionMsg_WakeEventPage IPC.
+IPC_MESSAGE_CONTROL2(ExtensionMsg_WakeEventPageResponse,
+ int /* request_id */,
+ bool /* success */)
+
+// Ask the lazy background page if it is ready to be suspended. This is sent
+// when the page is considered idle. The renderer will reply with the same
+// sequence_id so that we can tell which message it is responding to.
+IPC_MESSAGE_CONTROL2(ExtensionMsg_ShouldSuspend,
+ std::string /* extension_id */,
+ uint64_t /* sequence_id */)
+
+// If we complete a round of ShouldSuspend->ShouldSuspendAck messages without
+// the lazy background page becoming active again, we are ready to unload. This
+// message tells the page to dispatch the suspend event.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_Suspend,
+ std::string /* extension_id */)
+
+// The browser changed its mind about suspending this extension.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_CancelSuspend,
+ std::string /* extension_id */)
+
+// Response to the renderer for ExtensionHostMsg_GetAppInstallState.
+IPC_MESSAGE_ROUTED2(ExtensionMsg_GetAppInstallStateResponse,
+ std::string /* state */,
+ int32_t /* callback_id */)
+
+// Dispatch the Port.onConnect event for message channels.
+IPC_MESSAGE_ROUTED5(ExtensionMsg_DispatchOnConnect,
+ int /* target_port_id */,
+ std::string /* channel_name */,
+ ExtensionMsg_TabConnectionInfo /* source */,
+ ExtensionMsg_ExternalConnectionInfo,
+ std::string /* tls_channel_id */)
+
+// Deliver a message sent with ExtensionHostMsg_PostMessage.
+IPC_MESSAGE_ROUTED2(ExtensionMsg_DeliverMessage,
+ int /* target_port_id */,
+ extensions::Message)
+
+// Dispatch the Port.onDisconnect event for message channels.
+IPC_MESSAGE_ROUTED2(ExtensionMsg_DispatchOnDisconnect,
+ int /* port_id */,
+ std::string /* error_message */)
+
+// Informs the renderer what channel (dev, beta, stable, etc) is running.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_SetChannel,
+ int /* channel */)
+
+// Notify the renderer that its window has closed.
+IPC_MESSAGE_ROUTED0(ExtensionMsg_AppWindowClosed)
+
+// Notify the renderer that an extension wants notifications when certain
+// searches match the active page. This message replaces the old set of
+// searches, and triggers ExtensionHostMsg_OnWatchedPageChange messages from
+// each tab to keep the browser updated about changes.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_WatchPages,
+ std::vector<std::string> /* CSS selectors */)
+
+// Send by the browser to indicate a Blob handle has been transferred to the
+// renderer. This is sent after the actual extension response, and depends on
+// the sequential nature of IPCs so that the blob has already been caught.
+// This is a separate control message, so that the renderer process will send
+// an acknowledgement even if the RenderView has closed or navigated away.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_TransferBlobs,
+ std::vector<std::string> /* blob_uuids */)
+
+// Report the WebView partition ID to the WebView guest renderer process.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_SetWebViewPartitionID,
+ std::string /* webview_partition_id */)
+
+// Messages sent from the renderer to the browser:
+
+// A renderer sends this message when an extension process starts an API
+// request. The browser will always respond with a ExtensionMsg_Response.
+IPC_MESSAGE_ROUTED1(ExtensionHostMsg_Request,
+ ExtensionHostMsg_Request_Params)
+
+// A renderer sends this message when an extension process starts an API
+// request. The browser will always respond with a ExtensionMsg_Response.
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_RequestForIOThread,
+ int /* routing_id */,
+ ExtensionHostMsg_Request_Params)
+
+// Notify the browser that the given extension added a listener to an event.
+IPC_MESSAGE_CONTROL3(ExtensionHostMsg_AddListener,
+ std::string /* extension_id */,
+ GURL /* listener_url */,
+ std::string /* name */)
+
+// Notify the browser that the given extension removed a listener from an
+// event.
+IPC_MESSAGE_CONTROL3(ExtensionHostMsg_RemoveListener,
+ std::string /* extension_id */,
+ GURL /* listener_url */,
+ std::string /* name */)
+
+// Notify the browser that the given extension added a listener to an event from
+// a lazy background page.
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_AddLazyListener,
+ std::string /* extension_id */,
+ std::string /* name */)
+
+// Notify the browser that the given extension is no longer interested in
+// receiving the given event from a lazy background page.
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_RemoveLazyListener,
+ std::string /* extension_id */,
+ std::string /* name */)
+
+// Notify the browser that the given extension added a listener to instances of
+// the named event that satisfy the filter.
+IPC_MESSAGE_CONTROL4(ExtensionHostMsg_AddFilteredListener,
+ std::string /* extension_id */,
+ std::string /* name */,
+ base::DictionaryValue /* filter */,
+ bool /* lazy */)
+
+// Notify the browser that the given extension is no longer interested in
+// instances of the named event that satisfy the filter.
+IPC_MESSAGE_CONTROL4(ExtensionHostMsg_RemoveFilteredListener,
+ std::string /* extension_id */,
+ std::string /* name */,
+ base::DictionaryValue /* filter */,
+ bool /* lazy */)
+
+// Notify the browser that an event has finished being dispatched.
+IPC_MESSAGE_ROUTED1(ExtensionHostMsg_EventAck, int /* message_id */)
+
+// Open a channel to all listening contexts owned by the extension with
+// the given ID. This always returns a valid port ID which can be used for
+// sending messages. If an error occurred, the opener will be notified
+// asynchronously.
+IPC_SYNC_MESSAGE_CONTROL4_1(ExtensionHostMsg_OpenChannelToExtension,
+ int /* frame_routing_id */,
+ ExtensionMsg_ExternalConnectionInfo,
+ std::string /* channel_name */,
+ bool /* include_tls_channel_id */,
+ int /* port_id */)
+
+IPC_SYNC_MESSAGE_CONTROL3_1(ExtensionHostMsg_OpenChannelToNativeApp,
+ int /* frame_routing_id */,
+ std::string /* source_extension_id */,
+ std::string /* native_app_name */,
+ int /* port_id */)
+
+// Get a port handle to the given tab. The handle can be used for sending
+// messages to the extension.
+IPC_SYNC_MESSAGE_CONTROL4_1(ExtensionHostMsg_OpenChannelToTab,
+ int /* frame_routing_id */,
+ ExtensionMsg_TabTargetConnectionInfo,
+ std::string /* extension_id */,
+ std::string /* channel_name */,
+ int /* port_id */)
+
+// Sent in response to ExtensionMsg_DispatchOnConnect when the port is accepted.
+// The handle is the value returned by ExtensionHostMsg_OpenChannelTo*.
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_OpenMessagePort,
+ int /* frame_routing_id */,
+ int /* port_id */)
+
+// Sent in response to ExtensionMsg_DispatchOnConnect and whenever the port is
+// closed. The handle is the value returned by ExtensionHostMsg_OpenChannelTo*.
+IPC_MESSAGE_CONTROL3(ExtensionHostMsg_CloseMessagePort,
+ int /* frame_routing_id */,
+ int /* port_id */,
+ bool /* force_close */)
+
+// Send a message to an extension process. The handle is the value returned
+// by ExtensionHostMsg_OpenChannelTo*.
+IPC_MESSAGE_ROUTED2(ExtensionHostMsg_PostMessage,
+ int /* port_id */,
+ extensions::Message)
+
+// Used to get the extension message bundle.
+IPC_SYNC_MESSAGE_CONTROL1_1(ExtensionHostMsg_GetMessageBundle,
+ std::string /* extension id */,
+ SubstitutionMap /* message bundle */)
+
+// Sent from the renderer to the browser to return the script running result.
+IPC_MESSAGE_ROUTED4(
+ ExtensionHostMsg_ExecuteCodeFinished,
+ int /* request id */,
+ std::string /* error; empty implies success */,
+ GURL /* URL of the code executed on. May be empty if unsuccessful. */,
+ base::ListValue /* result of the script */)
+
+// Sent from the renderer to the browser to notify that content scripts are
+// running in the renderer that the IPC originated from.
+IPC_MESSAGE_ROUTED2(ExtensionHostMsg_ContentScriptsExecuting,
+ ExecutingScriptsMap,
+ GURL /* url of the _topmost_ frame */)
+
+// Sent from the renderer to the browser to request permission for a script
+// injection.
+// If request id is -1, this signals that the request has already ran, and this
+// merely serves as a notification. This happens when the feature to disable
+// scripts running without user consent is not enabled.
+IPC_MESSAGE_ROUTED4(ExtensionHostMsg_RequestScriptInjectionPermission,
+ std::string /* extension id */,
+ extensions::UserScript::InjectionType /* script type */,
+ extensions::UserScript::RunLocation /* run location */,
+ int64_t /* request id */)
+
+// Sent from the browser to the renderer in reply to a
+// RequestScriptInjectionPermission message, granting permission for a script
+// script to run.
+IPC_MESSAGE_ROUTED1(ExtensionMsg_PermitScriptInjection,
+ int64_t /* request id */)
+
+// Sent by the renderer when a web page is checking if its app is installed.
+IPC_MESSAGE_ROUTED3(ExtensionHostMsg_GetAppInstallState,
+ GURL /* requestor_url */,
+ int32_t /* return_route_id */,
+ int32_t /* callback_id */)
+
+// Optional Ack message sent to the browser to notify that the response to a
+// function has been processed.
+IPC_MESSAGE_ROUTED1(ExtensionHostMsg_ResponseAck,
+ int /* request_id */)
+
+// Response to ExtensionMsg_ShouldSuspend.
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_ShouldSuspendAck,
+ std::string /* extension_id */,
+ uint64_t /* sequence_id */)
+
+// Response to ExtensionMsg_Suspend, after we dispatch the suspend event.
+IPC_MESSAGE_CONTROL1(ExtensionHostMsg_SuspendAck,
+ std::string /* extension_id */)
+
+// Informs the browser to increment the keepalive count for the lazy background
+// page, keeping it alive.
+IPC_MESSAGE_ROUTED0(ExtensionHostMsg_IncrementLazyKeepaliveCount)
+
+// Informs the browser there is one less thing keeping the lazy background page
+// alive.
+IPC_MESSAGE_ROUTED0(ExtensionHostMsg_DecrementLazyKeepaliveCount)
+
+// Fetches a globally unique ID (for the lifetime of the browser) from the
+// browser process.
+IPC_SYNC_MESSAGE_CONTROL0_1(ExtensionHostMsg_GenerateUniqueID,
+ int /* unique_id */)
+
+// Notify the browser that an app window is ready and can resume resource
+// requests.
+IPC_MESSAGE_CONTROL1(ExtensionHostMsg_AppWindowReady, int /* route_id */)
+
+// Sent by the renderer when the draggable regions are updated.
+IPC_MESSAGE_ROUTED1(ExtensionHostMsg_UpdateDraggableRegions,
+ std::vector<extensions::DraggableRegion> /* regions */)
+
+// Sent by the renderer to log an API action to the extension activity log.
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_AddAPIActionToActivityLog,
+ std::string /* extension_id */,
+ ExtensionHostMsg_APIActionOrEvent_Params)
+
+// Sent by the renderer to log an event to the extension activity log.
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_AddEventToActivityLog,
+ std::string /* extension_id */,
+ ExtensionHostMsg_APIActionOrEvent_Params)
+
+// Sent by the renderer to log a DOM action to the extension activity log.
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_AddDOMActionToActivityLog,
+ std::string /* extension_id */,
+ ExtensionHostMsg_DOMAction_Params)
+
+// Notifies the browser process that a tab has started or stopped matching
+// certain conditions. This message is sent in response to several events:
+//
+// * ExtensionMsg_WatchPages was received, updating the set of conditions.
+// * A new page is loaded. This will be sent after
+// FrameHostMsg_DidCommitProvisionalLoad. Currently this only fires for the
+// main frame.
+// * Something changed on an existing frame causing the set of matching searches
+// to change.
+IPC_MESSAGE_ROUTED1(ExtensionHostMsg_OnWatchedPageChange,
+ std::vector<std::string> /* Matching CSS selectors */)
+
+// Sent by the renderer when it has received a Blob handle from the browser.
+IPC_MESSAGE_CONTROL1(ExtensionHostMsg_TransferBlobsAck,
+ std::vector<std::string> /* blob_uuids */)
+
+// Asks the browser to wake the event page of an extension.
+// The browser will reply with ExtensionHostMsg_WakeEventPageResponse.
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_WakeEventPage,
+ int /* request_id */,
+ std::string /* extension_id */)
+
+// Tells listeners that a detailed message was reported to the console by
+// WebKit.
+IPC_MESSAGE_ROUTED4(ExtensionHostMsg_DetailedConsoleMessageAdded,
+ base::string16 /* message */,
+ base::string16 /* source */,
+ extensions::StackTrace /* stack trace */,
+ int32_t /* severity level */)
+
+// Sent when a query selector request is made from the automation API.
+// acc_obj_id is the accessibility tree ID of the starting element.
+IPC_MESSAGE_ROUTED3(ExtensionMsg_AutomationQuerySelector,
+ int /* request_id */,
+ int /* acc_obj_id */,
+ base::string16 /* selector */)
+
+// Result of a query selector request.
+// result_acc_obj_id is the accessibility tree ID of the result element; 0
+// indicates no result.
+IPC_MESSAGE_ROUTED3(ExtensionHostMsg_AutomationQuerySelector_Result,
+ int /* request_id */,
+ ExtensionHostMsg_AutomationQuerySelector_Error /* error */,
+ int /* result_acc_obj_id */)
diff --git a/chromium/extensions/common/extension_paths.cc b/chromium/extensions/common/extension_paths.cc
new file mode 100644
index 00000000000..636c266907e
--- /dev/null
+++ b/chromium/extensions/common/extension_paths.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/extension_paths.h"
+
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+
+namespace extensions {
+
+bool PathProvider(int key, base::FilePath* result) {
+ if (key != DIR_TEST_DATA)
+ return false;
+ base::FilePath cur;
+ if (!PathService::Get(base::DIR_SOURCE_ROOT, &cur))
+ return false;
+ cur = cur.Append(FILE_PATH_LITERAL("extensions"));
+ cur = cur.Append(FILE_PATH_LITERAL("test"));
+ cur = cur.Append(FILE_PATH_LITERAL("data"));
+ if (!base::PathExists(cur)) // we don't want to create this
+ return false;
+ *result = cur;
+ return true;
+}
+
+// This cannot be done as a static initializer sadly since Visual Studio will
+// eliminate this object file if there is no direct entry point into it.
+void RegisterPathProvider() {
+ PathService::RegisterProvider(PathProvider, PATH_START, PATH_END);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/extension_paths.h b/chromium/extensions/common/extension_paths.h
new file mode 100644
index 00000000000..bbeff6acefd
--- /dev/null
+++ b/chromium/extensions/common/extension_paths.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_EXTENSION_PATHS_H_
+#define EXTENSIONS_COMMON_EXTENSION_PATHS_H_
+
+// This file declares path keys for extensions. These can be used with
+// the PathService to access various special directories and files.
+
+namespace extensions {
+
+enum {
+ PATH_START = 6000,
+
+ // Valid only in development environment
+ DIR_TEST_DATA,
+
+ PATH_END
+};
+
+// Call once to register the provider for the path keys defined above.
+void RegisterPathProvider();
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_EXTENSION_PATHS_H_
diff --git a/chromium/extensions/common/extension_resource.cc b/chromium/extensions/common/extension_resource.cc
new file mode 100644
index 00000000000..fea9286cfb2
--- /dev/null
+++ b/chromium/extensions/common/extension_resource.cc
@@ -0,0 +1,132 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/extension_resource.h"
+
+#include <stddef.h>
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/threading/thread_restrictions.h"
+
+namespace extensions {
+
+ExtensionResource::ExtensionResource() : follow_symlinks_anywhere_(false) {
+}
+
+ExtensionResource::ExtensionResource(const std::string& extension_id,
+ const base::FilePath& extension_root,
+ const base::FilePath& relative_path)
+ : extension_id_(extension_id),
+ extension_root_(extension_root),
+ relative_path_(relative_path),
+ follow_symlinks_anywhere_(false) {
+}
+
+ExtensionResource::ExtensionResource(const ExtensionResource& other) = default;
+
+ExtensionResource::~ExtensionResource() {}
+
+void ExtensionResource::set_follow_symlinks_anywhere() {
+ follow_symlinks_anywhere_ = true;
+}
+
+const base::FilePath& ExtensionResource::GetFilePath() const {
+ if (extension_root_.empty() || relative_path_.empty()) {
+ DCHECK(full_resource_path_.empty());
+ return full_resource_path_;
+ }
+
+ // We've already checked, just return last value.
+ if (!full_resource_path_.empty())
+ return full_resource_path_;
+
+ full_resource_path_ = GetFilePath(
+ extension_root_, relative_path_,
+ follow_symlinks_anywhere_ ?
+ FOLLOW_SYMLINKS_ANYWHERE : SYMLINKS_MUST_RESOLVE_WITHIN_ROOT);
+ return full_resource_path_;
+}
+
+// static
+base::FilePath ExtensionResource::GetFilePath(
+ const base::FilePath& extension_root,
+ const base::FilePath& relative_path,
+ SymlinkPolicy symlink_policy) {
+ // We need to resolve the parent references in the extension_root
+ // path on its own because IsParent doesn't like parent references.
+ base::FilePath clean_extension_root(
+ base::MakeAbsoluteFilePath(extension_root));
+ if (clean_extension_root.empty())
+ return base::FilePath();
+
+ base::FilePath full_path = clean_extension_root.Append(relative_path);
+
+ // If we are allowing the file to be a symlink outside of the root, then the
+ // path before resolving the symlink must still be within it.
+ if (symlink_policy == FOLLOW_SYMLINKS_ANYWHERE) {
+ std::vector<base::FilePath::StringType> components;
+ relative_path.GetComponents(&components);
+ int depth = 0;
+
+ for (std::vector<base::FilePath::StringType>::const_iterator
+ i = components.begin(); i != components.end(); i++) {
+ if (*i == base::FilePath::kParentDirectory) {
+ depth--;
+ } else if (*i != base::FilePath::kCurrentDirectory) {
+ depth++;
+ }
+ if (depth < 0) {
+ return base::FilePath();
+ }
+ }
+ }
+
+ // We must resolve the absolute path of the combined path when
+ // the relative path contains references to a parent folder (i.e., '..').
+ // We also check if the path exists because the posix version of
+ // MakeAbsoluteFilePath will fail if the path doesn't exist, and we want the
+ // same behavior on Windows... So until the posix and Windows version of
+ // MakeAbsoluteFilePath are unified, we need an extra call to PathExists,
+ // unfortunately.
+ // TODO(mad): Fix this once MakeAbsoluteFilePath is unified.
+ full_path = base::MakeAbsoluteFilePath(full_path);
+ if (base::PathExists(full_path) &&
+ (symlink_policy == FOLLOW_SYMLINKS_ANYWHERE ||
+ clean_extension_root.IsParent(full_path))) {
+ return full_path;
+ }
+
+ return base::FilePath();
+}
+
+// Unit-testing helpers.
+base::FilePath::StringType ExtensionResource::NormalizeSeperators(
+ const base::FilePath::StringType& path) const {
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+ base::FilePath::StringType win_path = path;
+ for (size_t i = 0; i < win_path.length(); i++) {
+ if (base::FilePath::IsSeparator(win_path[i]))
+ win_path[i] = base::FilePath::kSeparators[0];
+ }
+ return win_path;
+#else
+ return path;
+#endif // FILE_PATH_USES_WIN_SEPARATORS
+}
+
+bool ExtensionResource::ComparePathWithDefault(
+ const base::FilePath& path) const {
+ // Make sure we have a cached value to test against...
+ if (full_resource_path_.empty())
+ GetFilePath();
+ if (NormalizeSeperators(path.value()) ==
+ NormalizeSeperators(full_resource_path_.value())) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/extension_resource.h b/chromium/extensions/common/extension_resource.h
new file mode 100644
index 00000000000..43281cc87cb
--- /dev/null
+++ b/chromium/extensions/common/extension_resource.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_EXTENSION_RESOURCE_H_
+#define EXTENSIONS_COMMON_EXTENSION_RESOURCE_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+
+namespace extensions {
+
+// Represents a resource inside an extension. For example, an image, or a
+// JavaScript file. This is more complicated than just a simple FilePath
+// because extension resources can come from multiple physical file locations
+// depending on locale.
+class ExtensionResource {
+ public:
+ // SymlinkPolicy decides whether we'll allow resources to be a symlink to
+ // anywhere, or whether they must end up within the extension root.
+ enum SymlinkPolicy {
+ SYMLINKS_MUST_RESOLVE_WITHIN_ROOT,
+ FOLLOW_SYMLINKS_ANYWHERE,
+ };
+
+ ExtensionResource();
+
+ ExtensionResource(const std::string& extension_id,
+ const base::FilePath& extension_root,
+ const base::FilePath& relative_path);
+
+ ExtensionResource(const ExtensionResource& other);
+
+ ~ExtensionResource();
+
+ // set_follow_symlinks_anywhere allows the resource to be a symlink to
+ // anywhere in the filesystem. By default, resources have to be within
+ // |extension_root| after resolving symlinks.
+ void set_follow_symlinks_anywhere();
+
+ // Returns actual path to the resource (default or locale specific). In the
+ // browser process, this will DCHECK if not called on the file thread. To
+ // easily load extension images on the UI thread, see ImageLoader.
+ const base::FilePath& GetFilePath() const;
+
+ // Gets the physical file path for the extension resource, taking into account
+ // localization. In the browser process, this will DCHECK if not called on the
+ // file thread. To easily load extension images on the UI thread, see
+ // ImageLoader.
+ //
+ // The relative path must not resolve to a location outside of
+ // |extension_root|. Iff |file_can_symlink_outside_root| is true, then the
+ // file can be a symlink that links outside of |extension_root|.
+ static base::FilePath GetFilePath(const base::FilePath& extension_root,
+ const base::FilePath& relative_path,
+ SymlinkPolicy symlink_policy);
+
+ // Getters
+ const std::string& extension_id() const { return extension_id_; }
+ const base::FilePath& extension_root() const { return extension_root_; }
+ const base::FilePath& relative_path() const { return relative_path_; }
+
+ bool empty() const { return extension_root().empty(); }
+
+ // Unit test helpers.
+ base::FilePath::StringType NormalizeSeperators(
+ const base::FilePath::StringType& path) const;
+ bool ComparePathWithDefault(const base::FilePath& path) const;
+
+ private:
+ // The id of the extension that this resource is associated with.
+ std::string extension_id_;
+
+ // Extension root.
+ base::FilePath extension_root_;
+
+ // Relative path to resource.
+ base::FilePath relative_path_;
+
+ // If |follow_symlinks_anywhere_| is true then the resource itself must be
+ // within |extension_root|, but it can be a symlink to a file that is not.
+ bool follow_symlinks_anywhere_;
+
+ // Full path to extension resource. Starts empty.
+ mutable base::FilePath full_resource_path_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_EXTENSION_RESOURCE_H_
diff --git a/chromium/extensions/common/extension_resource_unittest.cc b/chromium/extensions/common/extension_resource_unittest.cc
new file mode 100644
index 00000000000..fb974d9f627
--- /dev/null
+++ b/chromium/extensions/common/extension_resource_unittest.cc
@@ -0,0 +1,167 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <algorithm>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/path_service.h"
+#include "build/build_config.h"
+#include "components/crx_file/id_util.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension_paths.h"
+#include "extensions/common/extension_resource.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+TEST(ExtensionResourceTest, CreateEmptyResource) {
+ ExtensionResource resource;
+
+ EXPECT_TRUE(resource.extension_root().empty());
+ EXPECT_TRUE(resource.relative_path().empty());
+ EXPECT_TRUE(resource.GetFilePath().empty());
+}
+
+const base::FilePath::StringType ToLower(
+ const base::FilePath::StringType& in_str) {
+ base::FilePath::StringType str(in_str);
+ std::transform(str.begin(), str.end(), str.begin(), tolower);
+ return str;
+}
+
+TEST(ExtensionResourceTest, CreateWithMissingResourceOnDisk) {
+ base::FilePath root_path;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &root_path));
+ base::FilePath relative_path;
+ relative_path = relative_path.AppendASCII("cira.js");
+ std::string extension_id = crx_file::id_util::GenerateId("test");
+ ExtensionResource resource(extension_id, root_path, relative_path);
+
+ // The path doesn't exist on disk, we will be returned an empty path.
+ EXPECT_EQ(root_path.value(), resource.extension_root().value());
+ EXPECT_EQ(relative_path.value(), resource.relative_path().value());
+ EXPECT_TRUE(resource.GetFilePath().empty());
+}
+
+TEST(ExtensionResourceTest, ResourcesOutsideOfPath) {
+ base::ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ base::FilePath inner_dir = temp.path().AppendASCII("directory");
+ ASSERT_TRUE(base::CreateDirectory(inner_dir));
+ base::FilePath sub_dir = inner_dir.AppendASCII("subdir");
+ ASSERT_TRUE(base::CreateDirectory(sub_dir));
+ base::FilePath inner_file = inner_dir.AppendASCII("inner");
+ base::FilePath outer_file = temp.path().AppendASCII("outer");
+ ASSERT_TRUE(base::WriteFile(outer_file, "X", 1));
+ ASSERT_TRUE(base::WriteFile(inner_file, "X", 1));
+ std::string extension_id = crx_file::id_util::GenerateId("test");
+
+#if defined(OS_POSIX)
+ base::FilePath symlink_file = inner_dir.AppendASCII("symlink");
+ base::CreateSymbolicLink(
+ base::FilePath().AppendASCII("..").AppendASCII("outer"),
+ symlink_file);
+#endif
+
+ // A non-packing extension should be able to access the file within the
+ // directory.
+ ExtensionResource r1(extension_id, inner_dir,
+ base::FilePath().AppendASCII("inner"));
+ EXPECT_FALSE(r1.GetFilePath().empty());
+
+ // ... but not a relative path that walks out of |inner_dir|.
+ ExtensionResource r2(extension_id, inner_dir,
+ base::FilePath().AppendASCII("..").AppendASCII("outer"));
+ EXPECT_TRUE(r2.GetFilePath().empty());
+
+ // A packing extension should also be able to access the file within the
+ // directory.
+ ExtensionResource r3(extension_id, inner_dir,
+ base::FilePath().AppendASCII("inner"));
+ r3.set_follow_symlinks_anywhere();
+ EXPECT_FALSE(r3.GetFilePath().empty());
+
+ // ... but, again, not a relative path that walks out of |inner_dir|.
+ ExtensionResource r4(extension_id, inner_dir,
+ base::FilePath().AppendASCII("..").AppendASCII("outer"));
+ r4.set_follow_symlinks_anywhere();
+ EXPECT_TRUE(r4.GetFilePath().empty());
+
+ // ... and not even when clever current-directory syntax is present. Note
+ // that the path for this test case can't start with the current directory
+ // component due to quirks in FilePath::Append(), and the path must exist.
+ ExtensionResource r4a(
+ extension_id, inner_dir,
+ base::FilePath().AppendASCII("subdir").AppendASCII(".").AppendASCII("..").
+ AppendASCII("..").AppendASCII("outer"));
+ r4a.set_follow_symlinks_anywhere();
+ EXPECT_TRUE(r4a.GetFilePath().empty());
+
+#if defined(OS_POSIX)
+ // The non-packing extension should also not be able to access a resource that
+ // symlinks out of the directory.
+ ExtensionResource r5(extension_id, inner_dir,
+ base::FilePath().AppendASCII("symlink"));
+ EXPECT_TRUE(r5.GetFilePath().empty());
+
+ // ... but a packing extension can.
+ ExtensionResource r6(extension_id, inner_dir,
+ base::FilePath().AppendASCII("symlink"));
+ r6.set_follow_symlinks_anywhere();
+ EXPECT_FALSE(r6.GetFilePath().empty());
+#endif
+}
+
+TEST(ExtensionResourceTest, CreateWithAllResourcesOnDisk) {
+ base::ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ // Create resource in the extension root.
+ const char* filename = "res.ico";
+ base::FilePath root_resource = temp.path().AppendASCII(filename);
+ std::string data = "some foo";
+ ASSERT_TRUE(base::WriteFile(root_resource, data.c_str(), data.length()));
+
+ // Create l10n resources (for current locale and its parents).
+ base::FilePath l10n_path =
+ temp.path().Append(kLocaleFolder);
+ ASSERT_TRUE(base::CreateDirectory(l10n_path));
+
+ std::vector<std::string> locales;
+ l10n_util::GetParentLocales(l10n_util::GetApplicationLocale(std::string()),
+ &locales);
+ ASSERT_FALSE(locales.empty());
+ for (size_t i = 0; i < locales.size(); i++) {
+ base::FilePath make_path;
+ make_path = l10n_path.AppendASCII(locales[i]);
+ ASSERT_TRUE(base::CreateDirectory(make_path));
+ ASSERT_TRUE(base::WriteFile(make_path.AppendASCII(filename),
+ data.c_str(), data.length()));
+ }
+
+ base::FilePath path;
+ std::string extension_id = crx_file::id_util::GenerateId("test");
+ ExtensionResource resource(extension_id, temp.path(),
+ base::FilePath().AppendASCII(filename));
+ base::FilePath resolved_path = resource.GetFilePath();
+
+ base::FilePath expected_path;
+ // Expect default path only, since fallback logic is disabled.
+ // See http://crbug.com/27359.
+ expected_path = base::MakeAbsoluteFilePath(root_resource);
+ ASSERT_FALSE(expected_path.empty());
+
+ EXPECT_EQ(ToLower(expected_path.value()), ToLower(resolved_path.value()));
+ EXPECT_EQ(ToLower(temp.path().value()),
+ ToLower(resource.extension_root().value()));
+ EXPECT_EQ(ToLower(base::FilePath().AppendASCII(filename).value()),
+ ToLower(resource.relative_path().value()));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/extension_set.cc b/chromium/extensions/common/extension_set.cc
new file mode 100644
index 00000000000..c4c15d4e1f2
--- /dev/null
+++ b/chromium/extensions/common/extension_set.cc
@@ -0,0 +1,155 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/extension_set.h"
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handlers/sandboxed_page_info.h"
+
+namespace extensions {
+
+ExtensionSet::const_iterator::const_iterator() {}
+
+ExtensionSet::const_iterator::const_iterator(const const_iterator& other)
+ : it_(other.it_) {
+}
+
+ExtensionSet::const_iterator::const_iterator(ExtensionMap::const_iterator it)
+ : it_(it) {
+}
+
+ExtensionSet::const_iterator::~const_iterator() {}
+
+ExtensionSet::ExtensionSet() {
+}
+
+ExtensionSet::~ExtensionSet() {
+}
+
+size_t ExtensionSet::size() const {
+ return extensions_.size();
+}
+
+bool ExtensionSet::is_empty() const {
+ return extensions_.empty();
+}
+
+bool ExtensionSet::Contains(const std::string& extension_id) const {
+ return extensions_.find(extension_id) != extensions_.end();
+}
+
+bool ExtensionSet::Insert(const scoped_refptr<const Extension>& extension) {
+ bool was_present = ContainsKey(extensions_, extension->id());
+ extensions_[extension->id()] = extension;
+ if (!was_present && !modification_callback_.is_null())
+ modification_callback_.Run(GetIDs());
+ return !was_present;
+}
+
+bool ExtensionSet::InsertAll(const ExtensionSet& extensions) {
+ size_t before = size();
+ for (ExtensionSet::const_iterator iter = extensions.begin();
+ iter != extensions.end(); ++iter) {
+ Insert(*iter);
+ }
+ return size() != before;
+}
+
+bool ExtensionSet::Remove(const std::string& id) {
+ bool was_present = extensions_.erase(id) > 0;
+ if (was_present && !modification_callback_.is_null())
+ modification_callback_.Run(GetIDs());
+ return was_present;
+}
+
+void ExtensionSet::Clear() {
+ extensions_.clear();
+}
+
+std::string ExtensionSet::GetExtensionOrAppIDByURL(const GURL& url) const {
+ if (url.SchemeIs(kExtensionScheme))
+ return url.host();
+
+ const Extension* extension = GetHostedAppByURL(url);
+ if (!extension)
+ return std::string();
+
+ return extension->id();
+}
+
+const Extension* ExtensionSet::GetExtensionOrAppByURL(const GURL& url) const {
+ if (url.SchemeIs(kExtensionScheme))
+ return GetByID(url.host());
+
+ return GetHostedAppByURL(url);
+}
+
+const Extension* ExtensionSet::GetAppByURL(const GURL& url) const {
+ const Extension* extension = GetExtensionOrAppByURL(url);
+ return (extension && extension->is_app()) ? extension : NULL;
+}
+
+const Extension* ExtensionSet::GetHostedAppByURL(const GURL& url) const {
+ for (ExtensionMap::const_iterator iter = extensions_.begin();
+ iter != extensions_.end(); ++iter) {
+ if (iter->second->web_extent().MatchesURL(url))
+ return iter->second.get();
+ }
+
+ return NULL;
+}
+
+const Extension* ExtensionSet::GetHostedAppByOverlappingWebExtent(
+ const URLPatternSet& extent) const {
+ for (ExtensionMap::const_iterator iter = extensions_.begin();
+ iter != extensions_.end(); ++iter) {
+ if (iter->second->web_extent().OverlapsWith(extent))
+ return iter->second.get();
+ }
+
+ return NULL;
+}
+
+bool ExtensionSet::InSameExtent(const GURL& old_url,
+ const GURL& new_url) const {
+ return GetExtensionOrAppByURL(old_url) ==
+ GetExtensionOrAppByURL(new_url);
+}
+
+const Extension* ExtensionSet::GetByID(const std::string& id) const {
+ ExtensionMap::const_iterator i = extensions_.find(id);
+ if (i != extensions_.end())
+ return i->second.get();
+ else
+ return NULL;
+}
+
+ExtensionIdSet ExtensionSet::GetIDs() const {
+ ExtensionIdSet ids;
+ for (ExtensionMap::const_iterator it = extensions_.begin();
+ it != extensions_.end(); ++it) {
+ ids.insert(it->first);
+ }
+ return ids;
+}
+
+bool ExtensionSet::ExtensionBindingsAllowed(const GURL& url) const {
+ if (url.SchemeIs(kExtensionScheme))
+ return true;
+
+ for (ExtensionMap::const_iterator it = extensions_.begin();
+ it != extensions_.end(); ++it) {
+ if (it->second->location() == Manifest::COMPONENT &&
+ it->second->web_extent().MatchesURL(url))
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/extension_set.h b/chromium/extensions/common/extension_set.h
new file mode 100644
index 00000000000..3bcc1f35fef
--- /dev/null
+++ b/chromium/extensions/common/extension_set.h
@@ -0,0 +1,156 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_EXTENSION_SET_H_
+#define EXTENSIONS_COMMON_EXTENSION_SET_H_
+
+#include <stddef.h>
+
+#include <iterator>
+#include <map>
+#include <string>
+
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "extensions/common/extension.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+// The one true extension container. Extensions are identified by their id.
+// Only one extension can be in the set with a given ID.
+class ExtensionSet {
+ public:
+ typedef std::pair<base::FilePath, std::string> ExtensionPathAndDefaultLocale;
+ typedef std::map<std::string, scoped_refptr<const Extension> > ExtensionMap;
+ typedef base::Callback<void(const ExtensionIdSet&)>
+ ModificationCallback;
+
+ // Iteration over the values of the map (given that it's an ExtensionSet,
+ // it should iterate like a set iterator).
+ class const_iterator : public std::iterator<std::input_iterator_tag,
+ scoped_refptr<const Extension> > {
+ public:
+ const_iterator();
+ const_iterator(const const_iterator& other);
+ explicit const_iterator(ExtensionMap::const_iterator it);
+ ~const_iterator();
+ const_iterator& operator++() {
+ ++it_;
+ return *this;
+ }
+ const_iterator operator++(int) {
+ const const_iterator old(*this);
+ ++it_;
+ return old;
+ }
+ const scoped_refptr<const Extension>& operator*() const {
+ return it_->second;
+ }
+ const scoped_refptr<const Extension>* operator->() const {
+ return &it_->second;
+ }
+ bool operator!=(const const_iterator& other) const {
+ return it_ != other.it_;
+ }
+ bool operator==(const const_iterator& other) const {
+ return it_ == other.it_;
+ }
+
+ private:
+ ExtensionMap::const_iterator it_;
+ };
+
+ ExtensionSet();
+ ~ExtensionSet();
+
+ size_t size() const;
+ bool is_empty() const;
+
+ // Iteration support.
+ const_iterator begin() const { return const_iterator(extensions_.begin()); }
+ const_iterator end() const { return const_iterator(extensions_.end()); }
+
+ // Returns true if the set contains the specified extension.
+ bool Contains(const std::string& id) const;
+
+ // Adds the specified extension to the set. The set becomes an owner. Any
+ // previous extension with the same ID is removed.
+ // Returns true if there is no previous extension.
+ bool Insert(const scoped_refptr<const Extension>& extension);
+
+ // Copies different items from |extensions| to the current set and returns
+ // whether anything changed.
+ bool InsertAll(const ExtensionSet& extensions);
+
+ // Removes the specified extension.
+ // Returns true if the set contained the specified extnesion.
+ bool Remove(const std::string& id);
+
+ // Removes all extensions.
+ void Clear();
+
+ // Returns the extension ID, or empty if none. This includes web URLs that
+ // are part of an extension's web extent.
+ std::string GetExtensionOrAppIDByURL(const GURL& url) const;
+
+ // Returns the Extension, or NULL if none. This includes web URLs that are
+ // part of an extension's web extent.
+ // NOTE: This can return NULL if called before UpdateExtensions receives
+ // bulk extension data (e.g. if called from
+ // EventBindings::HandleContextCreated)
+ const Extension* GetExtensionOrAppByURL(const GURL& url) const;
+
+ // Returns the app specified by the given |url|, if one exists. This will
+ // return NULL if there is no entry with |url|, or if the extension with
+ // |url| is not an app.
+ const Extension* GetAppByURL(const GURL& url) const;
+
+ // Returns the hosted app whose web extent contains the URL.
+ const Extension* GetHostedAppByURL(const GURL& url) const;
+
+ // Returns a hosted app that contains any URL that overlaps with the given
+ // extent, if one exists.
+ const Extension* GetHostedAppByOverlappingWebExtent(
+ const URLPatternSet& extent) const;
+
+ // Returns true if |new_url| is in the extent of the same extension as
+ // |old_url|. Also returns true if neither URL is in an app.
+ bool InSameExtent(const GURL& old_url, const GURL& new_url) const;
+
+ // Look up an Extension object by id.
+ const Extension* GetByID(const std::string& id) const;
+
+ // Gets the IDs of all extensions in the set.
+ ExtensionIdSet GetIDs() const;
+
+ // Returns true if |info| should get extension api bindings and be permitted
+ // to make api calls. Note that this is independent of what extension
+ // permissions the given extension has been granted.
+ bool ExtensionBindingsAllowed(const GURL& url) const;
+
+ void set_modification_callback(
+ const ModificationCallback& modification_callback) {
+ modification_callback_ = modification_callback;
+ }
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ExtensionSetTest, ExtensionSet);
+
+ ExtensionMap extensions_;
+
+ // If non-null, called with the extension ids in this set after a modification
+ // occurred. This is not called on Clear() which is typically used when
+ // discarding the set (e.g., on shutdown) and we do not want to track that as
+ // a real modification.
+ ModificationCallback modification_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionSet);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_EXTENSION_SET_H_
diff --git a/chromium/extensions/common/extension_set_unittest.cc b/chromium/extensions/common/extension_set_unittest.cc
new file mode 100644
index 00000000000..8674cc7b550
--- /dev/null
+++ b/chromium/extensions/common/extension_set_unittest.cc
@@ -0,0 +1,148 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_set.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+scoped_refptr<Extension> CreateTestExtension(const std::string& name,
+ const std::string& launch_url,
+ const std::string& extent) {
+#if defined(OS_WIN)
+ base::FilePath path(FILE_PATH_LITERAL("c:\\"));
+#else
+ base::FilePath path(FILE_PATH_LITERAL("/"));
+#endif
+ path = path.AppendASCII(name);
+
+ base::DictionaryValue manifest;
+ manifest.SetString("name", name);
+ manifest.SetString("version", "1");
+
+ if (!launch_url.empty())
+ manifest.SetString("app.launch.web_url", launch_url);
+
+ if (!extent.empty()) {
+ base::ListValue* urls = new base::ListValue();
+ manifest.Set("app.urls", urls);
+ urls->Append(new base::StringValue(extent));
+ }
+
+ std::string error;
+ scoped_refptr<Extension> extension(
+ Extension::Create(path, Manifest::INTERNAL, manifest,
+ Extension::NO_FLAGS, &error));
+ EXPECT_TRUE(extension.get()) << error;
+ return extension;
+}
+
+} // namespace
+
+TEST(ExtensionSetTest, ExtensionSet) {
+ scoped_refptr<Extension> ext1(CreateTestExtension(
+ "a", "https://chrome.google.com/launch", "https://chrome.google.com/"));
+
+ scoped_refptr<Extension> ext2(CreateTestExtension(
+ "a", "http://code.google.com/p/chromium",
+ "http://code.google.com/p/chromium/"));
+
+ scoped_refptr<Extension> ext3(CreateTestExtension(
+ "b", "http://dev.chromium.org/", "http://dev.chromium.org/"));
+
+ scoped_refptr<Extension> ext4(
+ CreateTestExtension("c", std::string(), std::string()));
+
+ ASSERT_TRUE(ext1.get() && ext2.get() && ext3.get() && ext4.get());
+
+ ExtensionSet extensions;
+
+ // Add an extension.
+ EXPECT_TRUE(extensions.Insert(ext1));
+ EXPECT_EQ(1u, extensions.size());
+ EXPECT_EQ(ext1.get(), extensions.GetByID(ext1->id()));
+
+ // Since extension2 has same ID, it should overwrite extension1.
+ EXPECT_FALSE(extensions.Insert(ext2));
+ EXPECT_EQ(1u, extensions.size());
+ EXPECT_EQ(ext2.get(), extensions.GetByID(ext1->id()));
+
+ // Add the other extensions.
+ EXPECT_TRUE(extensions.Insert(ext3));
+ EXPECT_TRUE(extensions.Insert(ext4));
+ EXPECT_EQ(3u, extensions.size());
+
+ // Get extension by its chrome-extension:// URL
+ EXPECT_EQ(
+ ext2.get(),
+ extensions.GetExtensionOrAppByURL(ext2->GetResourceURL("test.html")));
+ EXPECT_EQ(
+ ext3.get(),
+ extensions.GetExtensionOrAppByURL(ext3->GetResourceURL("test.html")));
+ EXPECT_EQ(
+ ext4.get(),
+ extensions.GetExtensionOrAppByURL(ext4->GetResourceURL("test.html")));
+
+ // Get extension by web extent.
+ EXPECT_EQ(ext2.get(),
+ extensions.GetExtensionOrAppByURL(
+ GURL("http://code.google.com/p/chromium/monkey")));
+ EXPECT_EQ(ext3.get(),
+ extensions.GetExtensionOrAppByURL(
+ GURL("http://dev.chromium.org/design-docs/")));
+ EXPECT_FALSE(extensions.GetExtensionOrAppByURL(
+ GURL("http://blog.chromium.org/")));
+
+ // Test InSameExtent().
+ EXPECT_TRUE(extensions.InSameExtent(
+ GURL("http://code.google.com/p/chromium/monkey/"),
+ GURL("http://code.google.com/p/chromium/")));
+ EXPECT_FALSE(extensions.InSameExtent(
+ GURL("http://code.google.com/p/chromium/"),
+ GURL("https://code.google.com/p/chromium/")));
+ EXPECT_FALSE(extensions.InSameExtent(
+ GURL("http://code.google.com/p/chromium/"),
+ GURL("http://dev.chromium.org/design-docs/")));
+
+ // Both of these should be NULL, which mean true for InSameExtent.
+ EXPECT_TRUE(extensions.InSameExtent(
+ GURL("http://www.google.com/"),
+ GURL("http://blog.chromium.org/")));
+
+ // Remove one of the extensions.
+ EXPECT_TRUE(extensions.Remove(ext2->id()));
+ EXPECT_EQ(2u, extensions.size());
+ EXPECT_FALSE(extensions.GetByID(ext2->id()));
+
+ // Make a union of a set with 3 more extensions (only 2 are new).
+ scoped_refptr<Extension> ext5(
+ CreateTestExtension("d", std::string(), std::string()));
+ scoped_refptr<Extension> ext6(
+ CreateTestExtension("e", std::string(), std::string()));
+ ASSERT_TRUE(ext5.get() && ext6.get());
+
+ scoped_ptr<ExtensionSet> to_add(new ExtensionSet());
+ // |ext3| is already in |extensions|, should not affect size.
+ EXPECT_TRUE(to_add->Insert(ext3));
+ EXPECT_TRUE(to_add->Insert(ext5));
+ EXPECT_TRUE(to_add->Insert(ext6));
+
+ ASSERT_TRUE(extensions.Contains(ext3->id()));
+ ASSERT_TRUE(extensions.InsertAll(*to_add));
+ EXPECT_EQ(4u, extensions.size());
+
+ ASSERT_FALSE(extensions.InsertAll(*to_add)); // Re-adding same set no-ops.
+ EXPECT_EQ(4u, extensions.size());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/extension_urls.cc b/chromium/extensions/common/extension_urls.cc
new file mode 100644
index 00000000000..08fb32d6230
--- /dev/null
+++ b/chromium/extensions/common/extension_urls.cc
@@ -0,0 +1,99 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/extension_urls.h"
+
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extensions_client.h"
+#include "net/base/escape.h"
+#include "net/base/url_util.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+const char kEventBindings[] = "event_bindings";
+
+const char kSchemaUtils[] = "schemaUtils";
+
+bool IsSourceFromAnExtension(const base::string16& source) {
+ return GURL(source).SchemeIs(kExtensionScheme) ||
+ base::StartsWith(source, base::ASCIIToUTF16("extensions::"),
+ base::CompareCase::SENSITIVE);
+}
+
+} // namespace extensions
+
+namespace extension_urls {
+
+const char kChromeWebstoreBaseURL[] = "https://chrome.google.com/webstore";
+const char kChromeWebstoreUpdateURL[] =
+ "https://clients2.google.com/service/update2/crx";
+
+std::string GetWebstoreLaunchURL() {
+ extensions::ExtensionsClient* client = extensions::ExtensionsClient::Get();
+ if (client)
+ return client->GetWebstoreBaseURL();
+ return kChromeWebstoreBaseURL;
+}
+
+std::string GetWebstoreExtensionsCategoryURL() {
+ return GetWebstoreLaunchURL() + "/category/extensions";
+}
+
+std::string GetWebstoreItemDetailURLPrefix() {
+ return GetWebstoreLaunchURL() + "/detail/";
+}
+
+GURL GetWebstoreItemJsonDataURL(const std::string& extension_id) {
+ return GURL(GetWebstoreLaunchURL() + "/inlineinstall/detail/" + extension_id);
+}
+
+GURL GetWebstoreJsonSearchUrl(const std::string& query,
+ const std::string& host_language_code) {
+ GURL url(GetWebstoreLaunchURL() + "/jsonsearch");
+ url = net::AppendQueryParameter(url, "q", query);
+ url = net::AppendQueryParameter(url, "hl", host_language_code);
+ return url;
+}
+
+GURL GetWebstoreSearchPageUrl(const std::string& query) {
+ return GURL(GetWebstoreLaunchURL() + "/search/" +
+ net::EscapeQueryParamValue(query, false));
+}
+
+GURL GetWebstoreUpdateUrl() {
+ extensions::ExtensionsClient* client = extensions::ExtensionsClient::Get();
+ if (client)
+ return GURL(client->GetWebstoreUpdateURL());
+ return GURL(kChromeWebstoreUpdateURL);
+}
+
+GURL GetWebstoreReportAbuseUrl(const std::string& extension_id,
+ const std::string& referrer_id) {
+ return GURL(base::StringPrintf("%s/report/%s?utm_source=%s",
+ GetWebstoreLaunchURL().c_str(),
+ extension_id.c_str(), referrer_id.c_str()));
+}
+
+bool IsWebstoreUpdateUrl(const GURL& update_url) {
+ GURL store_url = GetWebstoreUpdateUrl();
+ if (update_url == store_url) {
+ return true;
+ } else {
+ return (update_url.host() == store_url.host() &&
+ update_url.path() == store_url.path());
+ }
+}
+
+bool IsBlacklistUpdateUrl(const GURL& url) {
+ extensions::ExtensionsClient* client = extensions::ExtensionsClient::Get();
+ if (client)
+ return client->IsBlacklistUpdateURL(url);
+ return false;
+}
+
+} // namespace extension_urls
diff --git a/chromium/extensions/common/extension_urls.h b/chromium/extensions/common/extension_urls.h
new file mode 100644
index 00000000000..2fd72c8cf3c
--- /dev/null
+++ b/chromium/extensions/common/extension_urls.h
@@ -0,0 +1,87 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_EXTENSION_URLS_H_
+#define EXTENSIONS_COMMON_EXTENSION_URLS_H_
+
+#include <string>
+
+#include "base/strings/string16.h"
+
+class GURL;
+
+namespace extensions {
+
+// The name of the event_bindings module.
+extern const char kEventBindings[];
+
+// The name of the schemaUtils module.
+extern const char kSchemaUtils[];
+
+// Determine whether or not a source came from an extension. |source| can link
+// to a page or a script, and can be external (e.g., "http://www.google.com"),
+// extension-related (e.g., "chrome-extension://<extension_id>/background.js"),
+// or internal (e.g., "event_bindings" or "schemaUtils").
+bool IsSourceFromAnExtension(const base::string16& source);
+
+} // namespace extensions
+
+namespace extension_urls {
+
+// Canonical URLs for the Chrome Webstore. You probably want to use one of
+// the calls below rather than using one of these constants directly, since
+// the active extensions embedder may provide its own webstore URLs.
+extern const char kChromeWebstoreBaseURL[];
+extern const char kChromeWebstoreUpdateURL[];
+
+// Returns the URL prefix for the extension/apps gallery. Can be set via the
+// --apps-gallery-url switch. The URL returned will not contain a trailing
+// slash. Do not use this as a prefix/extent for the store.
+std::string GetWebstoreLaunchURL();
+
+// Returns the URL to the extensions category on the Web Store. This is
+// derived from GetWebstoreLaunchURL().
+std::string GetWebstoreExtensionsCategoryURL();
+
+// Returns the URL prefix for an item in the extension/app gallery. This URL
+// will contain a trailing slash and should be concatenated with an item ID
+// to get the item detail URL.
+std::string GetWebstoreItemDetailURLPrefix();
+
+// Returns the URL used to get webstore data (ratings, manifest, icon URL,
+// etc.) about an extension from the webstore as JSON.
+GURL GetWebstoreItemJsonDataURL(const std::string& extension_id);
+
+// Returns the URL used to get webstore search results in JSON format. The URL
+// returns a JSON dictionary that has the search results (under "results").
+// Each entry in the array is a dictionary as the data returned for
+// GetWebstoreItemJsonDataURL above. |query| is the user typed query string.
+// |host_language_code| is the host language code, e.g. en_US. Both arguments
+// will be escaped and added as a query parameter to the returned web store
+// json search URL.
+GURL GetWebstoreJsonSearchUrl(const std::string& query,
+ const std::string& host_language_code);
+
+// Returns the URL of the web store search results page for |query|.
+GURL GetWebstoreSearchPageUrl(const std::string& query);
+
+// Return the update URL used by gallery/webstore extensions/apps. This may
+// have been overridden by a command line flag for testing purposes.
+GURL GetWebstoreUpdateUrl();
+
+// Returns the url to visit to report abuse for the given |extension_id|
+// and |referrer_id|.
+GURL GetWebstoreReportAbuseUrl(const std::string& extension_id,
+ const std::string& referrer_id);
+
+// Returns whether the URL is the webstore update URL (just considering host
+// and path, not scheme, query, etc.)
+bool IsWebstoreUpdateUrl(const GURL& update_url);
+
+// Returns true if the URL points to an extension blacklist.
+bool IsBlacklistUpdateUrl(const GURL& url);
+
+} // namespace extension_urls
+
+#endif // EXTENSIONS_COMMON_EXTENSION_URLS_H_
diff --git a/chromium/extensions/common/extension_utility_messages.h b/chromium/extensions/common/extension_utility_messages.h
new file mode 100644
index 00000000000..363392f729f
--- /dev/null
+++ b/chromium/extensions/common/extension_utility_messages.h
@@ -0,0 +1,97 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Multiply-included message file, so no include guard.
+
+#include <string>
+#include <tuple>
+
+#include "extensions/common/update_manifest.h"
+#include "ipc/ipc_message_macros.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/ipc/skia/gfx_skia_param_traits.h"
+#include "url/ipc/url_param_traits.h"
+
+#define IPC_MESSAGE_START ExtensionUtilityMsgStart
+
+#ifndef EXTENSIONS_COMMON_EXTENSION_UTILITY_MESSAGES_H_
+#define EXTENSIONS_COMMON_EXTENSION_UTILITY_MESSAGES_H_
+
+using DecodedImages = std::vector<std::tuple<SkBitmap, base::FilePath>>;
+
+#endif // EXTENSIONS_COMMON_EXTENSION_UTILITY_MESSAGES_H_
+
+IPC_STRUCT_TRAITS_BEGIN(UpdateManifest::Result)
+ IPC_STRUCT_TRAITS_MEMBER(extension_id)
+ IPC_STRUCT_TRAITS_MEMBER(version)
+ IPC_STRUCT_TRAITS_MEMBER(browser_min_version)
+ IPC_STRUCT_TRAITS_MEMBER(package_hash)
+ IPC_STRUCT_TRAITS_MEMBER(crx_url)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(UpdateManifest::Results)
+ IPC_STRUCT_TRAITS_MEMBER(list)
+ IPC_STRUCT_TRAITS_MEMBER(daystart_elapsed_seconds)
+IPC_STRUCT_TRAITS_END()
+
+//------------------------------------------------------------------------------
+// Utility process messages:
+// These are messages from the browser to the utility process.
+
+// Tell the utility process to parse the given xml document.
+IPC_MESSAGE_CONTROL1(ExtensionUtilityMsg_ParseUpdateManifest,
+ std::string /* xml document contents */)
+
+// Tell the utility process to unzip the zipfile at a given path into a
+// directory at the second given path.
+IPC_MESSAGE_CONTROL2(ExtensionUtilityMsg_UnzipToDir,
+ base::FilePath /* zip_file */,
+ base::FilePath /* dir */)
+
+// Tells the utility process to validate and sanitize the extension in a
+// directory.
+IPC_MESSAGE_CONTROL4(ExtensionUtilityMsg_UnpackExtension,
+ base::FilePath /* directory_path */,
+ std::string /* extension_id */,
+ int /* Manifest::Location */,
+ int /* InitFromValue flags */)
+
+//------------------------------------------------------------------------------
+// Utility process host messages:
+// These are messages from the utility process to the browser.
+
+// Reply when the utility process has succeeded in parsing an update manifest
+// xml document.
+IPC_MESSAGE_CONTROL1(ExtensionUtilityHostMsg_ParseUpdateManifest_Succeeded,
+ UpdateManifest::Results /* updates */)
+
+// Reply when an error occurred parsing the update manifest. |error_message|
+// is a description of what went wrong suitable for logging.
+IPC_MESSAGE_CONTROL1(ExtensionUtilityHostMsg_ParseUpdateManifest_Failed,
+ std::string /* error_message, if any */)
+
+// Reply when the utility process is done unzipping a file. |unpacked_path|
+// is the directory which contains the unzipped contents.
+IPC_MESSAGE_CONTROL1(ExtensionUtilityHostMsg_UnzipToDir_Succeeded,
+ base::FilePath /* unpacked_path */)
+
+// Reply when the utility process failed to unzip a file. |error| contains
+// an error string to be reported to the user.
+IPC_MESSAGE_CONTROL1(ExtensionUtilityHostMsg_UnzipToDir_Failed,
+ std::string /* error */)
+
+// Reply when the utility process is done unpacking an extension. |manifest|
+// is the parsed manifest.json file.
+// The unpacker should also have written out files containing the decoded
+// images and message catalogs from the extension. The data is written into a
+// DecodedImages struct into a file named kDecodedImagesFilename in the
+// directory that was passed in. This is done because the data is too large to
+// pass over IPC.
+IPC_MESSAGE_CONTROL1(ExtensionUtilityHostMsg_UnpackExtension_Succeeded,
+ base::DictionaryValue /* manifest */)
+
+// Reply when the utility process has failed while unpacking an extension.
+// |error_message| is a user-displayable explanation of what went wrong.
+IPC_MESSAGE_CONTROL1(ExtensionUtilityHostMsg_UnpackExtension_Failed,
+ base::string16 /* error_message, if any */)
diff --git a/chromium/extensions/common/extensions_client.cc b/chromium/extensions/common/extensions_client.cc
new file mode 100644
index 00000000000..9f453ef0224
--- /dev/null
+++ b/chromium/extensions/common/extensions_client.cc
@@ -0,0 +1,39 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/extensions_client.h"
+
+#include "base/logging.h"
+#include "extensions/common/extension_icon_set.h"
+#include "extensions/common/manifest_handlers/icons_handler.h"
+
+namespace extensions {
+
+namespace {
+
+ExtensionsClient* g_client = NULL;
+
+} // namespace
+
+ExtensionsClient* ExtensionsClient::Get() {
+ DCHECK(g_client);
+ return g_client;
+}
+
+std::set<base::FilePath> ExtensionsClient::GetBrowserImagePaths(
+ const Extension* extension) {
+ std::set<base::FilePath> paths;
+ extensions::IconsInfo::GetIcons(extension).GetPaths(&paths);
+ return paths;
+}
+
+void ExtensionsClient::Set(ExtensionsClient* client) {
+ // This can happen in unit tests, where the utility thread runs in-process.
+ if (g_client)
+ return;
+ g_client = client;
+ g_client->Initialize();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/extensions_client.h b/chromium/extensions/common/extensions_client.h
new file mode 100644
index 00000000000..03876dc80ae
--- /dev/null
+++ b/chromium/extensions/common/extensions_client.h
@@ -0,0 +1,143 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_EXTENSIONS_CLIENT_H_
+#define EXTENSIONS_COMMON_EXTENSIONS_CLIENT_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "extensions/common/permissions/api_permission_set.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+}
+
+namespace extensions {
+
+class APIPermissionSet;
+class Extension;
+class ExtensionAPI;
+class FeatureProvider;
+class JSONFeatureProviderSource;
+class ManifestPermissionSet;
+class PermissionMessage;
+class PermissionMessageProvider;
+class SimpleFeature;
+class URLPatternSet;
+
+// Sets up global state for the extensions system. Should be Set() once in each
+// process. This should be implemented by the client of the extensions system.
+class ExtensionsClient {
+ public:
+ typedef std::vector<std::string> ScriptingWhitelist;
+
+ virtual ~ExtensionsClient() {}
+
+ // Initializes global state. Not done in the constructor because unit tests
+ // can create additional ExtensionsClients because the utility thread runs
+ // in-process.
+ virtual void Initialize() = 0;
+
+ // Returns the global PermissionMessageProvider to use to provide permission
+ // warning strings.
+ virtual const PermissionMessageProvider& GetPermissionMessageProvider()
+ const = 0;
+
+ // Returns the application name. For example, "Chromium" or "app_shell".
+ virtual const std::string GetProductName() = 0;
+
+ // Create a FeatureProvider for a specific feature type, e.g. "permission".
+ virtual scoped_ptr<FeatureProvider> CreateFeatureProvider(
+ const std::string& name) const = 0;
+
+ // Create a JSONFeatureProviderSource for a specific feature type,
+ // e.g. "permission". Currently, all features are loaded from
+ // JSONFeatureProviderSources.
+ // This is used primarily in CreateFeatureProvider, above.
+ virtual scoped_ptr<JSONFeatureProviderSource> CreateFeatureProviderSource(
+ const std::string& name) const = 0;
+
+ // Takes the list of all hosts and filters out those with special
+ // permission strings. Adds the regular hosts to |new_hosts|,
+ // and adds any additional permissions to |permissions|.
+ // TODO(sashab): Split this function in two: One to filter out ignored host
+ // permissions, and one to get permissions for the given hosts.
+ virtual void FilterHostPermissions(const URLPatternSet& hosts,
+ URLPatternSet* new_hosts,
+ PermissionIDSet* permissions) const = 0;
+
+ // Replaces the scripting whitelist with |whitelist|. Used in the renderer;
+ // only used for testing in the browser process.
+ virtual void SetScriptingWhitelist(const ScriptingWhitelist& whitelist) = 0;
+
+ // Return the whitelist of extensions that can run content scripts on
+ // any origin.
+ virtual const ScriptingWhitelist& GetScriptingWhitelist() const = 0;
+
+ // Get the set of chrome:// hosts that |extension| can run content scripts on.
+ virtual URLPatternSet GetPermittedChromeSchemeHosts(
+ const Extension* extension,
+ const APIPermissionSet& api_permissions) const = 0;
+
+ // Returns false if content scripts are forbidden from running on |url|.
+ virtual bool IsScriptableURL(const GURL& url, std::string* error) const = 0;
+
+ // Returns true iff a schema named |name| is generated.
+ virtual bool IsAPISchemaGenerated(const std::string& name) const = 0;
+
+ // Gets the generated API schema named |name|.
+ virtual base::StringPiece GetAPISchema(const std::string& name) const = 0;
+
+ // Register non-generated API schema resources with the global ExtensionAPI.
+ // Called when the ExtensionAPI is lazily initialized.
+ virtual void RegisterAPISchemaResources(ExtensionAPI* api) const = 0;
+
+ // Determines if certain fatal extensions errors should be surpressed
+ // (i.e., only logged) or allowed (i.e., logged before crashing).
+ virtual bool ShouldSuppressFatalErrors() const = 0;
+
+ // Records that a fatal error was caught and suppressed. It is expected that
+ // embedders will only do so if ShouldSuppressFatalErrors at some point
+ // returned true.
+ virtual void RecordDidSuppressFatalError() = 0;
+
+ // Returns the base webstore URL prefix.
+ virtual std::string GetWebstoreBaseURL() const = 0;
+
+ // Returns the URL to use for update manifest queries.
+ virtual std::string GetWebstoreUpdateURL() const = 0;
+
+ // Returns a flag indicating whether or not a given URL is a valid
+ // extension blacklist URL.
+ virtual bool IsBlacklistUpdateURL(const GURL& url) const = 0;
+
+ // Returns the set of file paths corresponding to any images within an
+ // extension's contents that may be displayed directly within the browser UI
+ // or WebUI, such as icons or theme images. This set of paths is used by the
+ // extension unpacker to determine which assets should be transcoded safely
+ // within the utility sandbox.
+ //
+ // The default implementation returns the images used as icons for the
+ // extension itself, so implementors of ExtensionsClient overriding this may
+ // want to call the base class version and then add additional paths to that
+ // result.
+ virtual std::set<base::FilePath> GetBrowserImagePaths(
+ const Extension* extension);
+
+ // Return the extensions client.
+ static ExtensionsClient* Get();
+
+ // Initialize the extensions system with this extensions client.
+ static void Set(ExtensionsClient* client);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_EXTENSIONS_CLIENT_H_
diff --git a/chromium/extensions/common/feature_switch.cc b/chromium/extensions/common/feature_switch.cc
new file mode 100644
index 00000000000..36fb385709f
--- /dev/null
+++ b/chromium/extensions/common/feature_switch.cc
@@ -0,0 +1,275 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/feature_switch.h"
+
+#include "base/command_line.h"
+#include "base/lazy_instance.h"
+#include "base/metrics/field_trial.h"
+#include "base/strings/string_util.h"
+#include "build/build_config.h"
+#include "extensions/common/switches.h"
+
+namespace extensions {
+
+namespace {
+
+// The switch media-router is defined in chrome/common/chrome_switches.cc, but
+// we can't depend on chrome here.
+const char kMediaRouterFlag[] = "media-router";
+
+const char kEnableMediaRouterExperiment[] = "EnableMediaRouter";
+const char kEnableMediaRouterWithCastExtensionExperiment[] =
+ "EnableMediaRouterWithCastExtension";
+const char kExtensionActionRedesignExperiment[] = "ExtensionActionRedesign";
+
+const char* kMediaRouterRequiredExperiments[] = {
+ kEnableMediaRouterExperiment, kExtensionActionRedesignExperiment};
+const char* kMediaRouterWithCastExtensionRequiredExperiments[] = {
+ kEnableMediaRouterWithCastExtensionExperiment,
+ kExtensionActionRedesignExperiment};
+
+class CommonSwitches {
+ public:
+ CommonSwitches()
+ : easy_off_store_install(nullptr, FeatureSwitch::DEFAULT_DISABLED),
+ force_dev_mode_highlighting(switches::kForceDevModeHighlighting,
+ FeatureSwitch::DEFAULT_DISABLED),
+ prompt_for_external_extensions(
+#if defined(CHROMIUM_BUILD)
+ switches::kPromptForExternalExtensions,
+#else
+ nullptr,
+#endif
+#if defined(OS_WIN)
+ FeatureSwitch::DEFAULT_ENABLED),
+#else
+ FeatureSwitch::DEFAULT_DISABLED),
+#endif
+ error_console(switches::kErrorConsole, FeatureSwitch::DEFAULT_DISABLED),
+ enable_override_bookmarks_ui(switches::kEnableOverrideBookmarksUI,
+ FeatureSwitch::DEFAULT_DISABLED),
+ extension_action_redesign(switches::kExtensionActionRedesign,
+ kExtensionActionRedesignExperiment,
+ FeatureSwitch::DEFAULT_ENABLED),
+ extension_action_redesign_override(switches::kExtensionActionRedesign,
+ FeatureSwitch::DEFAULT_ENABLED),
+ scripts_require_action(switches::kScriptsRequireAction,
+ FeatureSwitch::DEFAULT_DISABLED),
+ embedded_extension_options(switches::kEmbeddedExtensionOptions,
+ FeatureSwitch::DEFAULT_DISABLED),
+ trace_app_source(switches::kTraceAppSource,
+ FeatureSwitch::DEFAULT_ENABLED),
+ media_router(kMediaRouterFlag,
+ std::vector<std::string>(
+ kMediaRouterRequiredExperiments,
+ kMediaRouterRequiredExperiments +
+ arraysize(kMediaRouterRequiredExperiments)),
+ FeatureSwitch::DEFAULT_DISABLED),
+ media_router_with_cast_extension(
+ kMediaRouterFlag,
+ std::vector<std::string>(
+ kMediaRouterWithCastExtensionRequiredExperiments,
+ kMediaRouterWithCastExtensionRequiredExperiments +
+ arraysize(
+ kMediaRouterWithCastExtensionRequiredExperiments)),
+ FeatureSwitch::DEFAULT_DISABLED) {
+ }
+
+ // Enables extensions to be easily installed from sites other than the web
+ // store.
+ FeatureSwitch easy_off_store_install;
+
+ FeatureSwitch force_dev_mode_highlighting;
+
+ // Should we prompt the user before allowing external extensions to install?
+ // Default is yes.
+ FeatureSwitch prompt_for_external_extensions;
+
+ FeatureSwitch error_console;
+ FeatureSwitch enable_override_bookmarks_ui;
+ FeatureSwitch extension_action_redesign;
+ FeatureSwitch extension_action_redesign_override;
+ FeatureSwitch scripts_require_action;
+ FeatureSwitch embedded_extension_options;
+ FeatureSwitch trace_app_source;
+ FeatureSwitch media_router;
+ FeatureSwitch media_router_with_cast_extension;
+};
+
+base::LazyInstance<CommonSwitches> g_common_switches =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+FeatureSwitch* FeatureSwitch::force_dev_mode_highlighting() {
+ return &g_common_switches.Get().force_dev_mode_highlighting;
+}
+FeatureSwitch* FeatureSwitch::easy_off_store_install() {
+ return &g_common_switches.Get().easy_off_store_install;
+}
+FeatureSwitch* FeatureSwitch::prompt_for_external_extensions() {
+ return &g_common_switches.Get().prompt_for_external_extensions;
+}
+FeatureSwitch* FeatureSwitch::error_console() {
+ return &g_common_switches.Get().error_console;
+}
+FeatureSwitch* FeatureSwitch::enable_override_bookmarks_ui() {
+ return &g_common_switches.Get().enable_override_bookmarks_ui;
+}
+FeatureSwitch* FeatureSwitch::extension_action_redesign() {
+ // Force-enable the redesigned extension action toolbar when the Media Router
+ // is enabled. Should be removed when the toolbar redesign is used by default.
+ // See crbug.com/514694
+ // Note that if Media Router is enabled by experiment, it implies that the
+ // extension action redesign is also enabled by experiment. Thus it is fine
+ // to return the override switch.
+ // TODO(kmarshall): Remove this override.
+ if (media_router()->IsEnabled())
+ return &g_common_switches.Get().extension_action_redesign_override;
+
+ return &g_common_switches.Get().extension_action_redesign;
+}
+FeatureSwitch* FeatureSwitch::scripts_require_action() {
+ return &g_common_switches.Get().scripts_require_action;
+}
+FeatureSwitch* FeatureSwitch::embedded_extension_options() {
+ return &g_common_switches.Get().embedded_extension_options;
+}
+FeatureSwitch* FeatureSwitch::trace_app_source() {
+ return &g_common_switches.Get().trace_app_source;
+}
+FeatureSwitch* FeatureSwitch::media_router() {
+ return &g_common_switches.Get().media_router;
+}
+FeatureSwitch* FeatureSwitch::media_router_with_cast_extension() {
+ return &g_common_switches.Get().media_router_with_cast_extension;
+}
+
+FeatureSwitch::ScopedOverride::ScopedOverride(FeatureSwitch* feature,
+ bool override_value)
+ : feature_(feature),
+ previous_value_(feature->GetOverrideValue()) {
+ feature_->SetOverrideValue(
+ override_value ? OVERRIDE_ENABLED : OVERRIDE_DISABLED);
+}
+
+FeatureSwitch::ScopedOverride::~ScopedOverride() {
+ feature_->SetOverrideValue(previous_value_);
+}
+
+FeatureSwitch::FeatureSwitch(const char* switch_name,
+ DefaultValue default_value)
+ : FeatureSwitch(base::CommandLine::ForCurrentProcess(),
+ switch_name,
+ default_value) {}
+
+FeatureSwitch::FeatureSwitch(const char* switch_name,
+ const char* field_trial_name,
+ DefaultValue default_value)
+ : FeatureSwitch(base::CommandLine::ForCurrentProcess(),
+ switch_name,
+ std::vector<std::string>(1, field_trial_name),
+ default_value) {}
+
+FeatureSwitch::FeatureSwitch(
+ const char* switch_name,
+ const std::vector<std::string>& required_field_trials,
+ DefaultValue default_value)
+ : FeatureSwitch(base::CommandLine::ForCurrentProcess(),
+ switch_name,
+ required_field_trials,
+ default_value) {}
+
+FeatureSwitch::FeatureSwitch(const base::CommandLine* command_line,
+ const char* switch_name,
+ DefaultValue default_value)
+ : FeatureSwitch(command_line,
+ switch_name,
+ std::vector<std::string>(),
+ default_value) {}
+
+FeatureSwitch::FeatureSwitch(
+ const base::CommandLine* command_line,
+ const char* switch_name,
+ const std::vector<std::string>& required_field_trials,
+ DefaultValue default_value)
+ : command_line_(command_line),
+ switch_name_(switch_name),
+ required_field_trials_(required_field_trials),
+ default_value_(default_value == DEFAULT_ENABLED),
+ override_value_(OVERRIDE_NONE) {}
+
+FeatureSwitch::~FeatureSwitch(){};
+
+bool FeatureSwitch::IsEnabled() const {
+ if (override_value_ != OVERRIDE_NONE)
+ return override_value_ == OVERRIDE_ENABLED;
+
+ if (!switch_name_)
+ return default_value_;
+
+ std::string temp = command_line_->GetSwitchValueASCII(switch_name_);
+ std::string switch_value;
+ base::TrimWhitespaceASCII(temp, base::TRIM_ALL, &switch_value);
+
+ if (switch_value == "1")
+ return true;
+
+ if (switch_value == "0")
+ return false;
+
+ // TODO(imcheng): Don't check |default_value_|. Otherwise, we could improperly
+ // ignore an enable/disable switch if there is a field trial active.
+ // crbug.com/585569
+ if (!default_value_ && command_line_->HasSwitch(GetLegacyEnableFlag()))
+ return true;
+
+ if (default_value_ && command_line_->HasSwitch(GetLegacyDisableFlag()))
+ return false;
+
+ if (!required_field_trials_.empty()) {
+ bool enabled_by_field_trial = true;
+ bool disabled_by_field_trial = false;
+ for (const std::string& field_trial_name : required_field_trials_) {
+ std::string group_name =
+ base::FieldTrialList::FindFullName(field_trial_name);
+ if (!base::StartsWith(group_name, "Enabled",
+ base::CompareCase::SENSITIVE)) {
+ enabled_by_field_trial = false;
+ if (base::StartsWith(group_name, "Disabled",
+ base::CompareCase::SENSITIVE)) {
+ disabled_by_field_trial = true;
+ break;
+ }
+ }
+ }
+ if (disabled_by_field_trial)
+ return false;
+ if (enabled_by_field_trial)
+ return true;
+ }
+
+ return default_value_;
+}
+
+std::string FeatureSwitch::GetLegacyEnableFlag() const {
+ DCHECK(switch_name_);
+ return std::string("enable-") + switch_name_;
+}
+
+std::string FeatureSwitch::GetLegacyDisableFlag() const {
+ DCHECK(switch_name_);
+ return std::string("disable-") + switch_name_;
+}
+
+void FeatureSwitch::SetOverrideValue(OverrideValue override_value) {
+ override_value_ = override_value;
+}
+
+FeatureSwitch::OverrideValue FeatureSwitch::GetOverrideValue() const {
+ return override_value_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/feature_switch.h b/chromium/extensions/common/feature_switch.h
new file mode 100644
index 00000000000..b39cc8c3cf0
--- /dev/null
+++ b/chromium/extensions/common/feature_switch.h
@@ -0,0 +1,111 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_FEATURE_SWITCH_H_
+#define EXTENSIONS_COMMON_FEATURE_SWITCH_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+
+namespace base {
+class CommandLine;
+}
+
+namespace extensions {
+
+// A switch that can turn a feature on or off. Typically controlled via
+// command-line switches but can be overridden, e.g., for testing.
+// Can also integrate with Finch's field trials.
+// A note about priority:
+// 1. If an override is present, the override state will be used.
+// 2. If there is no switch name, the default value will be used. This is
+// because certain features are specifically designed *not* to be able to
+// be turned off via command-line, so we can't consult it (or, by extension,
+// the finch config).
+// 3. If there is a switch name, and the switch is present in the command line,
+// the command line value will be used.
+// 4. If there are field trials associated with the feature, and the machine
+// is in the "Enabled" group for all field trials, then the feature is
+// enabled. If the machine is in the "Disabled" group for any field trials,
+// the feature is disabled.
+// 5. Otherwise, the default value is used.
+class FeatureSwitch {
+ public:
+ static FeatureSwitch* easy_off_store_install();
+ static FeatureSwitch* force_dev_mode_highlighting();
+ static FeatureSwitch* prompt_for_external_extensions();
+ static FeatureSwitch* error_console();
+ static FeatureSwitch* enable_override_bookmarks_ui();
+ static FeatureSwitch* extension_action_redesign();
+ static FeatureSwitch* scripts_require_action();
+ static FeatureSwitch* embedded_extension_options();
+ static FeatureSwitch* trace_app_source();
+ static FeatureSwitch* media_router();
+ static FeatureSwitch* media_router_with_cast_extension();
+
+ enum DefaultValue {
+ DEFAULT_ENABLED,
+ DEFAULT_DISABLED
+ };
+
+ enum OverrideValue {
+ OVERRIDE_NONE,
+ OVERRIDE_ENABLED,
+ OVERRIDE_DISABLED
+ };
+
+ // A temporary override for the switch value.
+ class ScopedOverride {
+ public:
+ ScopedOverride(FeatureSwitch* feature, bool override_value);
+ ~ScopedOverride();
+ private:
+ FeatureSwitch* feature_;
+ FeatureSwitch::OverrideValue previous_value_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedOverride);
+ };
+
+ // |switch_name| can be null, in which case the feature is controlled solely
+ // by the default and override values.
+ FeatureSwitch(const char* switch_name,
+ DefaultValue default_value);
+ FeatureSwitch(const char* switch_name,
+ const char* field_trial_name,
+ DefaultValue default_value);
+ FeatureSwitch(const char* switch_name,
+ const std::vector<std::string>& required_field_trials,
+ DefaultValue default_value);
+ FeatureSwitch(const base::CommandLine* command_line,
+ const char* switch_name,
+ DefaultValue default_value);
+ FeatureSwitch(const base::CommandLine* command_line,
+ const char* switch_name,
+ const std::vector<std::string>& required_field_trials,
+ DefaultValue default_value);
+ ~FeatureSwitch();
+
+ // Consider using ScopedOverride instead.
+ void SetOverrideValue(OverrideValue value);
+ OverrideValue GetOverrideValue() const;
+
+ bool IsEnabled() const;
+
+ private:
+ std::string GetLegacyEnableFlag() const;
+ std::string GetLegacyDisableFlag() const;
+
+ const base::CommandLine* command_line_;
+ const char* switch_name_;
+ std::vector<std::string> required_field_trials_;
+ bool default_value_;
+ OverrideValue override_value_;
+
+ DISALLOW_COPY_AND_ASSIGN(FeatureSwitch);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_FEATURE_SWITCH_H_
diff --git a/chromium/extensions/common/features/api_feature.cc b/chromium/extensions/common/features/api_feature.cc
new file mode 100644
index 00000000000..e3e4c91b6db
--- /dev/null
+++ b/chromium/extensions/common/features/api_feature.cc
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/features/api_feature.h"
+
+namespace extensions {
+
+APIFeature::APIFeature()
+ : internal_(false) {}
+
+APIFeature::~APIFeature() {
+}
+
+bool APIFeature::IsInternal() const {
+ return internal_;
+}
+
+std::string APIFeature::Parse(const base::DictionaryValue* value) {
+ std::string error = SimpleFeature::Parse(value);
+ if (!error.empty())
+ return error;
+
+ value->GetBoolean("internal", &internal_);
+
+ if (contexts()->empty())
+ return name() + ": API features must specify at least one context.";
+
+ return std::string();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/features/api_feature.h b/chromium/extensions/common/features/api_feature.h
new file mode 100644
index 00000000000..7a30cb432d0
--- /dev/null
+++ b/chromium/extensions/common/features/api_feature.h
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_FEATURES_API_FEATURE_H_
+#define EXTENSIONS_COMMON_FEATURES_API_FEATURE_H_
+
+#include <string>
+
+#include "extensions/common/features/simple_feature.h"
+
+namespace extensions {
+
+class APIFeature : public SimpleFeature {
+ public:
+ APIFeature();
+ ~APIFeature() override;
+
+ // extensions::Feature:
+ bool IsInternal() const override;
+
+ std::string Parse(const base::DictionaryValue* value) override;
+
+ private:
+ bool internal_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_FEATURES_API_FEATURE_H_
diff --git a/chromium/extensions/common/features/base_feature_provider.cc b/chromium/extensions/common/features/base_feature_provider.cc
new file mode 100644
index 00000000000..069de389158
--- /dev/null
+++ b/chromium/extensions/common/features/base_feature_provider.cc
@@ -0,0 +1,198 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/features/base_feature_provider.h"
+
+#include <stddef.h>
+
+#include <stack>
+#include <utility>
+
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/features/complex_feature.h"
+#include "extensions/common/features/simple_feature.h"
+
+namespace extensions {
+
+namespace {
+
+bool IsNocompile(const base::Value& value) {
+ bool nocompile = false;
+ const base::DictionaryValue* as_dict = nullptr;
+ if (value.GetAsDictionary(&as_dict)) {
+ as_dict->GetBoolean("nocompile", &nocompile);
+ } else {
+ // "nocompile" is not supported for any other feature type.
+ }
+ return nocompile;
+}
+
+bool ParseFeature(const base::DictionaryValue* value,
+ const std::string& name,
+ SimpleFeature* feature) {
+ feature->set_name(name);
+ std::string error = feature->Parse(value);
+ if (!error.empty())
+ LOG(ERROR) << error;
+ return error.empty();
+}
+
+} // namespace
+
+BaseFeatureProvider::BaseFeatureProvider(const base::DictionaryValue& root,
+ FeatureFactory factory)
+ : factory_(factory) {
+ for (base::DictionaryValue::Iterator iter(root); !iter.IsAtEnd();
+ iter.Advance()) {
+ if (IsNocompile(iter.value())) {
+ continue;
+ }
+
+ if (iter.value().GetType() == base::Value::TYPE_DICTIONARY) {
+ scoped_ptr<SimpleFeature> feature((*factory_)());
+
+ std::vector<std::string> split = base::SplitString(
+ iter.key(), ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+
+ // Push parent features on the stack, starting with the current feature.
+ // If one of the features has "noparent" set, stop pushing features on
+ // the stack. The features will then be parsed in order, starting with
+ // the farthest parent that is either top level or has "noparent" set.
+ std::stack<std::pair<std::string, const base::DictionaryValue*> >
+ parse_stack;
+ while (!split.empty()) {
+ std::string parent_name = base::JoinString(split, ".");
+ split.pop_back();
+ if (root.HasKey(parent_name)) {
+ const base::DictionaryValue* parent = nullptr;
+ if (!root.GetDictionaryWithoutPathExpansion(parent_name, &parent)) {
+ // If the parent is a complex feature, find the parent with the
+ // 'default_parent' flag.
+ const base::ListValue* parent_list = nullptr;
+ CHECK(root.GetListWithoutPathExpansion(parent_name, &parent_list));
+ for (size_t i = 0; i < parent_list->GetSize(); ++i) {
+ CHECK(parent_list->GetDictionary(i, &parent));
+ if (parent->HasKey("default_parent"))
+ break;
+ parent = nullptr;
+ }
+ CHECK(parent) << parent_name << " must declare one of its features"
+ << " the default parent, with {\"default_parent\": true}.";
+ }
+ parse_stack.push(std::make_pair(parent_name, parent));
+ bool no_parent = false;
+ parent->GetBoolean("noparent", &no_parent);
+ if (no_parent)
+ break;
+ }
+ }
+
+ CHECK(!parse_stack.empty());
+ // Parse all parent features.
+ bool parse_error = false;
+ while (!parse_stack.empty()) {
+ if (!ParseFeature(parse_stack.top().second,
+ parse_stack.top().first,
+ feature.get())) {
+ parse_error = true;
+ break;
+ }
+ parse_stack.pop();
+ }
+
+ if (parse_error)
+ continue;
+
+ features_[iter.key()] = std::move(feature);
+ } else if (iter.value().GetType() == base::Value::TYPE_LIST) {
+ // This is a complex feature.
+ const base::ListValue* list =
+ static_cast<const base::ListValue*>(&iter.value());
+ CHECK_GT(list->GetSize(), 0UL);
+
+ scoped_ptr<ComplexFeature::FeatureList> features(
+ new ComplexFeature::FeatureList());
+
+ // Parse and add all SimpleFeatures from the list.
+ for (base::ListValue::const_iterator list_iter = list->begin();
+ list_iter != list->end(); ++list_iter) {
+ if ((*list_iter)->GetType() != base::Value::TYPE_DICTIONARY) {
+ LOG(ERROR) << iter.key() << ": Feature rules must be dictionaries.";
+ continue;
+ }
+
+ scoped_ptr<SimpleFeature> feature((*factory_)());
+ if (!ParseFeature(static_cast<const base::DictionaryValue*>(*list_iter),
+ iter.key(),
+ feature.get()))
+ continue;
+
+ features->push_back(std::move(feature));
+ }
+
+ scoped_ptr<ComplexFeature> feature(
+ new ComplexFeature(std::move(features)));
+ feature->set_name(iter.key());
+
+ features_[iter.key()] = std::move(feature);
+ } else {
+ LOG(ERROR) << iter.key() << ": Feature description must be dictionary or"
+ << " list of dictionaries.";
+ }
+ }
+}
+
+BaseFeatureProvider::~BaseFeatureProvider() {
+}
+
+const FeatureMap& BaseFeatureProvider::GetAllFeatures() const {
+ return features_;
+}
+
+Feature* BaseFeatureProvider::GetFeature(const std::string& name) const {
+ FeatureMap::const_iterator iter = features_.find(name);
+ if (iter != features_.end())
+ return iter->second.get();
+ else
+ return nullptr;
+}
+
+Feature* BaseFeatureProvider::GetParent(Feature* feature) const {
+ CHECK(feature);
+ if (feature->no_parent())
+ return nullptr;
+
+ std::vector<std::string> split = base::SplitString(
+ feature->name(), ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ if (split.size() < 2)
+ return nullptr;
+ split.pop_back();
+ return GetFeature(base::JoinString(split, "."));
+}
+
+// Children of a given API are named starting with parent.name()+".", which
+// means they'll be contiguous in the features_ std::map.
+std::vector<Feature*> BaseFeatureProvider::GetChildren(const Feature& parent)
+ const {
+ std::string prefix = parent.name() + ".";
+ const FeatureMap::const_iterator first_child = features_.lower_bound(prefix);
+
+ // All children have names before (parent.name() + ('.'+1)).
+ ++prefix[prefix.size() - 1];
+ const FeatureMap::const_iterator after_children =
+ features_.lower_bound(prefix);
+
+ std::vector<Feature*> result;
+ result.reserve(std::distance(first_child, after_children));
+ for (FeatureMap::const_iterator it = first_child; it != after_children;
+ ++it) {
+ result.push_back(it->second.get());
+ }
+ return result;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/features/base_feature_provider.h b/chromium/extensions/common/features/base_feature_provider.h
new file mode 100644
index 00000000000..0d6129234c1
--- /dev/null
+++ b/chromium/extensions/common/features/base_feature_provider.h
@@ -0,0 +1,51 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_FEATURES_BASE_FEATURE_PROVIDER_H_
+#define EXTENSIONS_COMMON_FEATURES_BASE_FEATURE_PROVIDER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/features/feature_provider.h"
+#include "extensions/common/features/simple_feature.h"
+
+namespace Base {
+class DictionaryValue;
+}
+
+namespace extensions {
+
+// Reads Features out of a simple JSON file description.
+class BaseFeatureProvider : public FeatureProvider {
+ public:
+ typedef SimpleFeature*(*FeatureFactory)();
+
+ // Creates a new BaseFeatureProvider. Pass null to |factory| to have the
+ // provider create plain old Feature instances.
+ BaseFeatureProvider(const base::DictionaryValue& root,
+ FeatureFactory factory);
+ ~BaseFeatureProvider() override;
+
+ // Gets the feature |feature_name|, if it exists.
+ Feature* GetFeature(const std::string& feature_name) const override;
+ Feature* GetParent(Feature* feature) const override;
+ std::vector<Feature*> GetChildren(const Feature& parent) const override;
+
+ const FeatureMap& GetAllFeatures() const override;
+
+ private:
+ std::map<std::string, scoped_ptr<Feature>> features_;
+
+ // Populated on first use.
+ mutable std::vector<std::string> feature_names_;
+
+ FeatureFactory factory_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_FEATURES_BASE_FEATURE_PROVIDER_H_
diff --git a/chromium/extensions/common/features/base_feature_provider_unittest.cc b/chromium/extensions/common/features/base_feature_provider_unittest.cc
new file mode 100644
index 00000000000..e601cfa57f3
--- /dev/null
+++ b/chromium/extensions/common/features/base_feature_provider_unittest.cc
@@ -0,0 +1,148 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/features/base_feature_provider.h"
+
+#include <algorithm>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "base/stl_util.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/features/simple_feature.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/value_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+// Tests that a real manifest feature is available for the correct types of
+// extensions and apps.
+TEST(BaseFeatureProviderTest, ManifestFeatureTypes) {
+ // NOTE: This feature cannot have multiple rules, otherwise it is not a
+ // SimpleFeature.
+ const SimpleFeature* feature = static_cast<const SimpleFeature*>(
+ FeatureProvider::GetManifestFeature("description"));
+ ASSERT_TRUE(feature);
+ const std::vector<Manifest::Type>* extension_types =
+ feature->extension_types();
+ EXPECT_EQ(6u, extension_types->size());
+ EXPECT_EQ(1, STLCount(*(extension_types), Manifest::TYPE_EXTENSION));
+ EXPECT_EQ(1,
+ STLCount(*(extension_types), Manifest::TYPE_LEGACY_PACKAGED_APP));
+ EXPECT_EQ(1, STLCount(*(extension_types), Manifest::TYPE_PLATFORM_APP));
+ EXPECT_EQ(1, STLCount(*(extension_types), Manifest::TYPE_HOSTED_APP));
+ EXPECT_EQ(1, STLCount(*(extension_types), Manifest::TYPE_THEME));
+ EXPECT_EQ(1, STLCount(*(extension_types), Manifest::TYPE_SHARED_MODULE));
+}
+
+// Tests that real manifest features have the correct availability for an
+// extension.
+TEST(BaseFeatureProviderTest, ManifestFeatureAvailability) {
+ const FeatureProvider* provider = BaseFeatureProvider::GetByName("manifest");
+
+ scoped_refptr<const Extension> extension =
+ ExtensionBuilder()
+ .SetManifest(DictionaryBuilder()
+ .Set("name", "test extension")
+ .Set("version", "1")
+ .Set("description", "hello there")
+ .Build())
+ .Build();
+ ASSERT_TRUE(extension.get());
+
+ Feature* feature = provider->GetFeature("description");
+ EXPECT_EQ(Feature::IS_AVAILABLE,
+ feature->IsAvailableToContext(extension.get(),
+ Feature::UNSPECIFIED_CONTEXT,
+ GURL()).result());
+
+ // This is a generic extension, so an app-only feature isn't allowed.
+ feature = provider->GetFeature("app.background");
+ ASSERT_TRUE(feature);
+ EXPECT_EQ(Feature::INVALID_TYPE,
+ feature->IsAvailableToContext(extension.get(),
+ Feature::UNSPECIFIED_CONTEXT,
+ GURL()).result());
+
+ // A feature not listed in the manifest isn't allowed.
+ feature = provider->GetFeature("background");
+ ASSERT_TRUE(feature);
+ EXPECT_EQ(Feature::NOT_PRESENT,
+ feature->IsAvailableToContext(extension.get(),
+ Feature::UNSPECIFIED_CONTEXT,
+ GURL()).result());
+}
+
+// Tests that a real permission feature is available for the correct types of
+// extensions and apps.
+TEST(BaseFeatureProviderTest, PermissionFeatureTypes) {
+ // NOTE: This feature cannot have multiple rules, otherwise it is not a
+ // SimpleFeature.
+ const SimpleFeature* feature = static_cast<const SimpleFeature*>(
+ BaseFeatureProvider::GetPermissionFeature("power"));
+ ASSERT_TRUE(feature);
+ const std::vector<Manifest::Type>* extension_types =
+ feature->extension_types();
+ EXPECT_EQ(3u, extension_types->size());
+ EXPECT_EQ(1, STLCount(*(extension_types), Manifest::TYPE_EXTENSION));
+ EXPECT_EQ(1,
+ STLCount(*(extension_types), Manifest::TYPE_LEGACY_PACKAGED_APP));
+ EXPECT_EQ(1, STLCount(*(extension_types), Manifest::TYPE_PLATFORM_APP));
+}
+
+// Tests that real permission features have the correct availability for an app.
+TEST(BaseFeatureProviderTest, PermissionFeatureAvailability) {
+ const FeatureProvider* provider =
+ BaseFeatureProvider::GetByName("permission");
+
+ scoped_refptr<const Extension> app =
+ ExtensionBuilder()
+ .SetManifest(
+ DictionaryBuilder()
+ .Set("name", "test app")
+ .Set("version", "1")
+ .Set("app",
+ DictionaryBuilder()
+ .Set("background",
+ DictionaryBuilder()
+ .Set("scripts", ListBuilder()
+ .Append("background.js")
+ .Build())
+ .Build())
+ .Build())
+ .Set("permissions", ListBuilder().Append("power").Build())
+ .Build())
+ .Build();
+ ASSERT_TRUE(app.get());
+ ASSERT_TRUE(app->is_platform_app());
+
+ // A permission requested in the manifest is available.
+ Feature* feature = provider->GetFeature("power");
+ EXPECT_EQ(
+ Feature::IS_AVAILABLE,
+ feature->IsAvailableToContext(
+ app.get(), Feature::UNSPECIFIED_CONTEXT, GURL()).result());
+
+ // A permission only available to whitelisted extensions returns availability
+ // NOT_FOUND_IN_WHITELIST.
+ feature = provider->GetFeature("bluetoothPrivate");
+ ASSERT_TRUE(feature);
+ EXPECT_EQ(
+ Feature::NOT_FOUND_IN_WHITELIST,
+ feature->IsAvailableToContext(
+ app.get(), Feature::UNSPECIFIED_CONTEXT, GURL()).result());
+
+ // A permission that isn't part of the manifest returns NOT_PRESENT.
+ feature = provider->GetFeature("serial");
+ ASSERT_TRUE(feature);
+ EXPECT_EQ(
+ Feature::NOT_PRESENT,
+ feature->IsAvailableToContext(
+ app.get(), Feature::UNSPECIFIED_CONTEXT, GURL()).result());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/features/behavior_feature.cc b/chromium/extensions/common/features/behavior_feature.cc
new file mode 100644
index 00000000000..b43dc2e85e1
--- /dev/null
+++ b/chromium/extensions/common/features/behavior_feature.cc
@@ -0,0 +1,18 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/features/behavior_feature.h"
+
+namespace extensions {
+
+const char BehaviorFeature::kServiceWorker[] = "service_worker";
+
+const char BehaviorFeature::kWhitelistedForIncognito[] =
+ "whitelisted_for_incognito";
+
+const char BehaviorFeature::kDoNotSync[] = "do_not_sync";
+
+const char BehaviorFeature::kZoomWithoutBubble[] = "zoom_without_bubble";
+
+} // namespace extensions
diff --git a/chromium/extensions/common/features/behavior_feature.h b/chromium/extensions/common/features/behavior_feature.h
new file mode 100644
index 00000000000..7e9a5f19bb9
--- /dev/null
+++ b/chromium/extensions/common/features/behavior_feature.h
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_FEATURES_BEHAVIOR_FEATURE_H_
+#define EXTENSIONS_COMMON_FEATURES_BEHAVIOR_FEATURE_H_
+
+#include <string>
+
+#include "extensions/common/features/simple_feature.h"
+
+namespace extensions {
+
+// Implementation of the features in _behavior_features.json.
+//
+// For now, this is just constants + a vacuous implementation of SimpleFeature,
+// for consistency with the other Feature types. One day we may add some
+// additional functionality. One day we may also generate the feature names.
+class BehaviorFeature : public SimpleFeature {
+ public:
+ // Constants corresponding to keys in _behavior_features.json.
+ static const char kServiceWorker[];
+ static const char kWhitelistedForIncognito[];
+ static const char kDoNotSync[];
+ static const char kZoomWithoutBubble[];
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_FEATURES_BEHAVIOR_FEATURE_H_
diff --git a/chromium/extensions/common/features/complex_feature.cc b/chromium/extensions/common/features/complex_feature.cc
new file mode 100644
index 00000000000..674b4eea741
--- /dev/null
+++ b/chromium/extensions/common/features/complex_feature.cc
@@ -0,0 +1,117 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/features/complex_feature.h"
+
+namespace extensions {
+
+ComplexFeature::ComplexFeature(scoped_ptr<FeatureList> features) {
+ DCHECK_GT(features->size(), 0UL);
+ features_.swap(*features);
+ no_parent_ = features_[0]->no_parent();
+
+#if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)
+ // Verify IsInternal and no_parent is consistent across all features.
+ bool first_is_internal = features_[0]->IsInternal();
+ for (FeatureList::const_iterator it = features_.begin() + 1;
+ it != features_.end();
+ ++it) {
+ DCHECK(first_is_internal == (*it)->IsInternal())
+ << "Complex feature must have consistent values of "
+ "internal across all sub features.";
+ DCHECK(no_parent_ == (*it)->no_parent())
+ << "Complex feature must have consistent values of "
+ "no_parent across all sub features.";
+ }
+#endif
+}
+
+ComplexFeature::~ComplexFeature() {
+}
+
+Feature::Availability ComplexFeature::IsAvailableToManifest(
+ const std::string& extension_id,
+ Manifest::Type type,
+ Manifest::Location location,
+ int manifest_version,
+ Platform platform) const {
+ Feature::Availability first_availability =
+ features_[0]->IsAvailableToManifest(
+ extension_id, type, location, manifest_version, platform);
+ if (first_availability.is_available())
+ return first_availability;
+
+ for (FeatureList::const_iterator it = features_.begin() + 1;
+ it != features_.end(); ++it) {
+ Availability availability = (*it)->IsAvailableToManifest(
+ extension_id, type, location, manifest_version, platform);
+ if (availability.is_available())
+ return availability;
+ }
+ // If none of the SimpleFeatures are available, we return the availability
+ // info of the first SimpleFeature that was not available.
+ return first_availability;
+}
+
+Feature::Availability ComplexFeature::IsAvailableToContext(
+ const Extension* extension,
+ Context context,
+ const GURL& url,
+ Platform platform) const {
+ Feature::Availability first_availability =
+ features_[0]->IsAvailableToContext(extension, context, url, platform);
+ if (first_availability.is_available())
+ return first_availability;
+
+ for (FeatureList::const_iterator it = features_.begin() + 1;
+ it != features_.end(); ++it) {
+ Availability availability =
+ (*it)->IsAvailableToContext(extension, context, url, platform);
+ if (availability.is_available())
+ return availability;
+ }
+ // If none of the SimpleFeatures are available, we return the availability
+ // info of the first SimpleFeature that was not available.
+ return first_availability;
+}
+
+bool ComplexFeature::IsIdInBlacklist(const std::string& extension_id) const {
+ for (FeatureList::const_iterator it = features_.begin();
+ it != features_.end();
+ ++it) {
+ if ((*it)->IsIdInBlacklist(extension_id))
+ return true;
+ }
+ return false;
+}
+
+bool ComplexFeature::IsIdInWhitelist(const std::string& extension_id) const {
+ for (FeatureList::const_iterator it = features_.begin();
+ it != features_.end();
+ ++it) {
+ if ((*it)->IsIdInWhitelist(extension_id))
+ return true;
+ }
+ return false;
+}
+
+bool ComplexFeature::IsInternal() const {
+ // Constructor verifies that composed features are consistent, thus we can
+ // return just the first feature's value.
+ return features_[0]->IsInternal();
+}
+
+std::string ComplexFeature::GetAvailabilityMessage(AvailabilityResult result,
+ Manifest::Type type,
+ const GURL& url,
+ Context context) const {
+ if (result == IS_AVAILABLE)
+ return std::string();
+
+ // TODO(justinlin): Form some kind of combined availabilities/messages from
+ // SimpleFeatures.
+ return features_[0]->GetAvailabilityMessage(result, type, url, context);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/features/complex_feature.h b/chromium/extensions/common/features/complex_feature.h
new file mode 100644
index 00000000000..a1a0f67a698
--- /dev/null
+++ b/chromium/extensions/common/features/complex_feature.h
@@ -0,0 +1,62 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_FEATURES_COMPLEX_FEATURE_H_
+#define EXTENSIONS_COMMON_FEATURES_COMPLEX_FEATURE_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/manifest.h"
+
+namespace extensions {
+
+// A ComplexFeature is composed of one or many Features. A ComplexFeature
+// is available if any Feature (i.e. permission rule) that composes it is
+// available, but not if only some combination of Features is available.
+class ComplexFeature : public Feature {
+ public:
+ using FeatureList = std::vector<scoped_ptr<Feature>>;
+
+ explicit ComplexFeature(scoped_ptr<FeatureList> features);
+ ~ComplexFeature() override;
+
+ // extensions::Feature:
+ Availability IsAvailableToManifest(const std::string& extension_id,
+ Manifest::Type type,
+ Manifest::Location location,
+ int manifest_version,
+ Platform platform) const override;
+
+ Availability IsAvailableToContext(const Extension* extension,
+ Context context,
+ const GURL& url,
+ Platform platform) const override;
+
+ bool IsIdInBlacklist(const std::string& extension_id) const override;
+ bool IsIdInWhitelist(const std::string& extension_id) const override;
+
+ protected:
+ // extensions::Feature:
+ std::string GetAvailabilityMessage(AvailabilityResult result,
+ Manifest::Type type,
+ const GURL& url,
+ Context context) const override;
+
+ bool IsInternal() const override;
+
+ private:
+ FeatureList features_;
+
+ DISALLOW_COPY_AND_ASSIGN(ComplexFeature);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_FEATURES_COMPLEX_FEATURE_H_
diff --git a/chromium/extensions/common/features/complex_feature_unittest.cc b/chromium/extensions/common/features/complex_feature_unittest.cc
new file mode 100644
index 00000000000..f60c90026a9
--- /dev/null
+++ b/chromium/extensions/common/features/complex_feature_unittest.cc
@@ -0,0 +1,134 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/features/complex_feature.h"
+
+#include <string>
+#include <utility>
+
+#include "extensions/common/features/simple_feature.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/value_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+TEST(ComplexFeatureTest, MultipleRulesWhitelist) {
+ const std::string kIdFoo("fooabbbbccccddddeeeeffffgggghhhh");
+ const std::string kIdBar("barabbbbccccddddeeeeffffgggghhhh");
+ scoped_ptr<ComplexFeature::FeatureList> features(
+ new ComplexFeature::FeatureList());
+
+ // Rule: "extension", whitelist "foo".
+ scoped_ptr<SimpleFeature> simple_feature(new SimpleFeature);
+ scoped_ptr<base::DictionaryValue> rule(
+ DictionaryBuilder()
+ .Set("whitelist", ListBuilder().Append(kIdFoo).Build())
+ .Set("extension_types", ListBuilder().Append("extension").Build())
+ .Build());
+ simple_feature->Parse(rule.get());
+ features->push_back(std::move(simple_feature));
+
+ // Rule: "legacy_packaged_app", whitelist "bar".
+ simple_feature.reset(new SimpleFeature);
+ rule = DictionaryBuilder()
+ .Set("whitelist", ListBuilder().Append(kIdBar).Build())
+ .Set("extension_types",
+ ListBuilder().Append("legacy_packaged_app").Build())
+ .Build();
+ simple_feature->Parse(rule.get());
+ features->push_back(std::move(simple_feature));
+
+ scoped_ptr<ComplexFeature> feature(new ComplexFeature(std::move(features)));
+
+ // Test match 1st rule.
+ EXPECT_EQ(
+ Feature::IS_AVAILABLE,
+ feature->IsAvailableToManifest(kIdFoo,
+ Manifest::TYPE_EXTENSION,
+ Manifest::INVALID_LOCATION,
+ Feature::UNSPECIFIED_PLATFORM,
+ Feature::GetCurrentPlatform()).result());
+
+ // Test match 2nd rule.
+ EXPECT_EQ(
+ Feature::IS_AVAILABLE,
+ feature->IsAvailableToManifest(kIdBar,
+ Manifest::TYPE_LEGACY_PACKAGED_APP,
+ Manifest::INVALID_LOCATION,
+ Feature::UNSPECIFIED_PLATFORM,
+ Feature::GetCurrentPlatform()).result());
+
+ // Test whitelist with wrong extension type.
+ EXPECT_NE(
+ Feature::IS_AVAILABLE,
+ feature->IsAvailableToManifest(kIdBar,
+ Manifest::TYPE_EXTENSION,
+ Manifest::INVALID_LOCATION,
+ Feature::UNSPECIFIED_PLATFORM,
+ Feature::GetCurrentPlatform()).result());
+ EXPECT_NE(
+ Feature::IS_AVAILABLE,
+ feature->IsAvailableToManifest(kIdFoo,
+ Manifest::TYPE_LEGACY_PACKAGED_APP,
+ Manifest::INVALID_LOCATION,
+ Feature::UNSPECIFIED_PLATFORM,
+ Feature::GetCurrentPlatform()).result());
+}
+
+// Tests that dependencies are correctly checked.
+TEST(ComplexFeatureTest, Dependencies) {
+ scoped_ptr<ComplexFeature::FeatureList> features(
+ new ComplexFeature::FeatureList());
+
+ // Rule which depends on an extension-only feature (content_security_policy).
+ scoped_ptr<SimpleFeature> simple_feature(new SimpleFeature);
+ scoped_ptr<base::DictionaryValue> rule =
+ DictionaryBuilder()
+ .Set("dependencies",
+ ListBuilder().Append("manifest:content_security_policy").Build())
+ .Build();
+ simple_feature->Parse(rule.get());
+ features->push_back(std::move(simple_feature));
+
+ // Rule which depends on an platform-app-only feature (serial).
+ simple_feature.reset(new SimpleFeature);
+ rule = DictionaryBuilder()
+ .Set("dependencies",
+ ListBuilder().Append("permission:serial").Build())
+ .Build();
+ simple_feature->Parse(rule.get());
+ features->push_back(std::move(simple_feature));
+
+ scoped_ptr<ComplexFeature> feature(new ComplexFeature(std::move(features)));
+
+ // Available to extensions because of the content_security_policy rule.
+ EXPECT_EQ(
+ Feature::IS_AVAILABLE,
+ feature->IsAvailableToManifest("extensionid",
+ Manifest::TYPE_EXTENSION,
+ Manifest::INVALID_LOCATION,
+ Feature::UNSPECIFIED_PLATFORM,
+ Feature::GetCurrentPlatform()).result());
+
+ // Available to platform apps because of the serial rule.
+ EXPECT_EQ(
+ Feature::IS_AVAILABLE,
+ feature->IsAvailableToManifest("platformappid",
+ Manifest::TYPE_PLATFORM_APP,
+ Manifest::INVALID_LOCATION,
+ Feature::UNSPECIFIED_PLATFORM,
+ Feature::GetCurrentPlatform()).result());
+
+ // Not available to hosted apps.
+ EXPECT_EQ(
+ Feature::INVALID_TYPE,
+ feature->IsAvailableToManifest("hostedappid",
+ Manifest::TYPE_HOSTED_APP,
+ Manifest::INVALID_LOCATION,
+ Feature::UNSPECIFIED_PLATFORM,
+ Feature::GetCurrentPlatform()).result());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/features/feature.cc b/chromium/extensions/common/features/feature.cc
new file mode 100644
index 00000000000..dc28d6dff8f
--- /dev/null
+++ b/chromium/extensions/common/features/feature.cc
@@ -0,0 +1,53 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/features/feature.h"
+
+#include <map>
+
+#include "base/command_line.h"
+#include "base/lazy_instance.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest.h"
+
+namespace extensions {
+
+// static
+Feature::Platform Feature::GetCurrentPlatform() {
+#if defined(OS_CHROMEOS)
+ return CHROMEOS_PLATFORM;
+#elif defined(OS_LINUX)
+ return LINUX_PLATFORM;
+#elif defined(OS_MACOSX)
+ return MACOSX_PLATFORM;
+#elif defined(OS_WIN)
+ return WIN_PLATFORM;
+#else
+ return UNSPECIFIED_PLATFORM;
+#endif
+}
+
+Feature::Availability Feature::IsAvailableToExtension(
+ const Extension* extension) const {
+ return IsAvailableToManifest(extension->id(),
+ extension->GetType(),
+ extension->location(),
+ extension->manifest_version());
+}
+
+Feature::Availability Feature::IsAvailableToEnvironment() const {
+ return IsAvailableToManifest("", // extension_id
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1); // manifest_version
+}
+
+Feature::Feature() : no_parent_(false) {}
+
+Feature::~Feature() {}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/features/feature.h b/chromium/extensions/common/features/feature.h
new file mode 100644
index 00000000000..b9f8073a020
--- /dev/null
+++ b/chromium/extensions/common/features/feature.h
@@ -0,0 +1,170 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_FEATURES_FEATURE_H_
+#define EXTENSIONS_COMMON_FEATURES_FEATURE_H_
+
+#include <set>
+#include <string>
+
+#include "base/values.h"
+#include "extensions/common/manifest.h"
+
+class GURL;
+
+namespace extensions {
+
+class Extension;
+
+// Represents a single feature accessible to an extension developer, such as a
+// top-level manifest key, a permission, or a programmatic API. A feature can
+// express requirements for where it can be accessed, and supports testing
+// support for those requirements. If platforms are not specified, then feature
+// is available on all platforms.
+class Feature {
+ public:
+ // The JavaScript contexts the feature is supported in.
+ enum Context {
+ UNSPECIFIED_CONTEXT,
+
+ // A context in a privileged extension process.
+ BLESSED_EXTENSION_CONTEXT,
+
+ // A context in an unprivileged extension process.
+ UNBLESSED_EXTENSION_CONTEXT,
+
+ // A context from a content script.
+ CONTENT_SCRIPT_CONTEXT,
+
+ // A normal web page. This should have an associated URL matching pattern.
+ WEB_PAGE_CONTEXT,
+
+ // A web page context which has been blessed by the user. Typically this
+ // will be via the installation of a hosted app, so this may host an
+ // extension. This is not affected by the URL matching pattern.
+ BLESSED_WEB_PAGE_CONTEXT,
+
+ // A page within webui.
+ WEBUI_CONTEXT,
+
+ // A context belonging to a service worker.
+ SERVICE_WORKER_CONTEXT,
+ };
+
+ // The platforms the feature is supported in.
+ enum Platform {
+ UNSPECIFIED_PLATFORM,
+ CHROMEOS_PLATFORM,
+ LINUX_PLATFORM,
+ MACOSX_PLATFORM,
+ WIN_PLATFORM
+ };
+
+ // Whether a feature is available in a given situation or not, and if not,
+ // why not.
+ enum AvailabilityResult {
+ IS_AVAILABLE,
+ NOT_FOUND_IN_WHITELIST,
+ INVALID_URL,
+ INVALID_TYPE,
+ INVALID_CONTEXT,
+ INVALID_LOCATION,
+ INVALID_PLATFORM,
+ INVALID_MIN_MANIFEST_VERSION,
+ INVALID_MAX_MANIFEST_VERSION,
+ NOT_PRESENT,
+ UNSUPPORTED_CHANNEL,
+ FOUND_IN_BLACKLIST,
+ MISSING_COMMAND_LINE_SWITCH,
+ };
+
+ // Container for AvailabiltyResult that also exposes a user-visible error
+ // message in cases where the feature is not available.
+ class Availability {
+ public:
+ Availability(AvailabilityResult result, const std::string& message)
+ : result_(result), message_(message) {}
+
+ AvailabilityResult result() const { return result_; }
+ bool is_available() const { return result_ == IS_AVAILABLE; }
+ const std::string& message() const { return message_; }
+
+ private:
+ friend class SimpleFeature;
+ friend class Feature;
+
+ const AvailabilityResult result_;
+ const std::string message_;
+ };
+
+ Feature();
+ virtual ~Feature();
+
+ const std::string& name() const { return name_; }
+ void set_name(const std::string& name) { name_ = name; }
+ bool no_parent() const { return no_parent_; }
+
+ // Gets the platform the code is currently running on.
+ static Platform GetCurrentPlatform();
+
+ // Tests whether this is an internal API or not.
+ virtual bool IsInternal() const = 0;
+
+ // Returns true if the feature is available to be parsed into a new extension
+ // manifest.
+ Availability IsAvailableToManifest(const std::string& extension_id,
+ Manifest::Type type,
+ Manifest::Location location,
+ int manifest_version) const {
+ return IsAvailableToManifest(extension_id, type, location, manifest_version,
+ GetCurrentPlatform());
+ }
+ virtual Availability IsAvailableToManifest(const std::string& extension_id,
+ Manifest::Type type,
+ Manifest::Location location,
+ int manifest_version,
+ Platform platform) const = 0;
+
+ // Returns true if the feature is available to |extension|.
+ Availability IsAvailableToExtension(const Extension* extension) const;
+
+ // Returns true if the feature is available to be used in the specified
+ // extension and context.
+ Availability IsAvailableToContext(const Extension* extension,
+ Context context,
+ const GURL& url) const {
+ return IsAvailableToContext(extension, context, url, GetCurrentPlatform());
+ }
+ virtual Availability IsAvailableToContext(const Extension* extension,
+ Context context,
+ const GURL& url,
+ Platform platform) const = 0;
+
+ virtual std::string GetAvailabilityMessage(AvailabilityResult result,
+ Manifest::Type type,
+ const GURL& url,
+ Context context) const = 0;
+
+ // Returns true if the feature is available to the current environment,
+ // without needing to know information about an Extension or any other
+ // contextual information. Typically used when the Feature is purely
+ // configured by command line flags and/or Chrome channel.
+ //
+ // Generally try not to use this function. Even if you don't think a Feature
+ // relies on an Extension now - maybe it will, one day, so if there's an
+ // Extension available (or a runtime context, etc) then use the more targeted
+ // method instead.
+ Availability IsAvailableToEnvironment() const;
+
+ virtual bool IsIdInBlacklist(const std::string& extension_id) const = 0;
+ virtual bool IsIdInWhitelist(const std::string& extension_id) const = 0;
+
+ protected:
+ std::string name_;
+ bool no_parent_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_FEATURES_FEATURE_H_
diff --git a/chromium/extensions/common/features/feature_provider.cc b/chromium/extensions/common/features/feature_provider.cc
new file mode 100644
index 00000000000..e420094ae77
--- /dev/null
+++ b/chromium/extensions/common/features/feature_provider.cc
@@ -0,0 +1,124 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/features/feature_provider.h"
+
+#include <map>
+
+#include "base/command_line.h"
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/trace_event/trace_event.h"
+#include "content/public/common/content_switches.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/features/feature_util.h"
+#include "extensions/common/switches.h"
+
+namespace extensions {
+
+namespace {
+
+class Static {
+ public:
+ FeatureProvider* GetFeatures(const std::string& name) const {
+ auto it = feature_providers_.find(name);
+ if (it == feature_providers_.end())
+ CRASH_WITH_MINIDUMP("FeatureProvider \"" + name + "\" not found");
+ return it->second.get();
+ }
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<Static>;
+
+ Static() {
+ TRACE_EVENT0("startup", "extensions::FeatureProvider::Static");
+ base::Time begin_time = base::Time::Now();
+
+ ExtensionsClient* client = ExtensionsClient::Get();
+ feature_providers_["api"] = client->CreateFeatureProvider("api");
+ feature_providers_["manifest"] = client->CreateFeatureProvider("manifest");
+ feature_providers_["permission"] =
+ client->CreateFeatureProvider("permission");
+ feature_providers_["behavior"] = client->CreateFeatureProvider("behavior");
+
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ std::string process_type =
+ command_line->GetSwitchValueASCII(::switches::kProcessType);
+
+ // Measure time only for browser process. This method gets called by the
+ // browser process on startup, as well as on renderer and extension
+ // processes throughout the execution of the browser. We are more
+ // interested in how long this takes as a startup cost, so we are
+ // just measuring the time in the browser process.
+ if (process_type == std::string()) {
+ UMA_HISTOGRAM_TIMES("Extensions.FeatureProviderStaticInitTime",
+ base::Time::Now() - begin_time);
+ }
+ }
+
+ std::map<std::string, scoped_ptr<FeatureProvider>> feature_providers_;
+};
+
+base::LazyInstance<Static> g_static = LAZY_INSTANCE_INITIALIZER;
+
+const Feature* GetFeatureFromProviderByName(const std::string& provider_name,
+ const std::string& feature_name) {
+ const Feature* feature =
+ FeatureProvider::GetByName(provider_name)->GetFeature(feature_name);
+ if (!feature) {
+ CRASH_WITH_MINIDUMP("Feature \"" + feature_name + "\" not found in " +
+ "FeatureProvider \"" + provider_name + "\"");
+ }
+ return feature;
+}
+
+} // namespace
+
+// static
+const FeatureProvider* FeatureProvider::GetByName(const std::string& name) {
+ return g_static.Get().GetFeatures(name);
+}
+
+// static
+const FeatureProvider* FeatureProvider::GetAPIFeatures() {
+ return GetByName("api");
+}
+
+// static
+const FeatureProvider* FeatureProvider::GetManifestFeatures() {
+ return GetByName("manifest");
+}
+
+// static
+const FeatureProvider* FeatureProvider::GetPermissionFeatures() {
+ return GetByName("permission");
+}
+
+// static
+const FeatureProvider* FeatureProvider::GetBehaviorFeatures() {
+ return GetByName("behavior");
+}
+
+// static
+const Feature* FeatureProvider::GetAPIFeature(const std::string& name) {
+ return GetFeatureFromProviderByName("api", name);
+}
+
+// static
+const Feature* FeatureProvider::GetManifestFeature(const std::string& name) {
+ return GetFeatureFromProviderByName("manifest", name);
+}
+
+// static
+const Feature* FeatureProvider::GetPermissionFeature(const std::string& name) {
+ return GetFeatureFromProviderByName("permission", name);
+}
+
+// static
+const Feature* FeatureProvider::GetBehaviorFeature(const std::string& name) {
+ return GetFeatureFromProviderByName("behavior", name);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/features/feature_provider.h b/chromium/extensions/common/features/feature_provider.h
new file mode 100644
index 00000000000..ae975137b50
--- /dev/null
+++ b/chromium/extensions/common/features/feature_provider.h
@@ -0,0 +1,66 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_FEATURES_FEATURE_PROVIDER_H_
+#define EXTENSIONS_COMMON_FEATURES_FEATURE_PROVIDER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+
+namespace extensions {
+
+class Feature;
+
+using FeatureMap = std::map<std::string, scoped_ptr<Feature>>;
+
+// Implemented by classes that can vend features.
+class FeatureProvider {
+ public:
+ FeatureProvider() {}
+ virtual ~FeatureProvider() {}
+
+ //
+ // Static helpers.
+ //
+
+ // Gets a FeatureProvider for a specific type, like "permission".
+ static const FeatureProvider* GetByName(const std::string& name);
+
+ // Directly access the common FeatureProvider types.
+ // Each is equivalent to GetByName('featuretype').
+ static const FeatureProvider* GetAPIFeatures();
+ static const FeatureProvider* GetManifestFeatures();
+ static const FeatureProvider* GetPermissionFeatures();
+ static const FeatureProvider* GetBehaviorFeatures();
+
+ // Directly get Features from the common FeatureProvider types.
+ // Each is equivalent to GetByName('featuretype')->GetFeature(name).
+ static const Feature* GetAPIFeature(const std::string& name);
+ static const Feature* GetManifestFeature(const std::string& name);
+ static const Feature* GetPermissionFeature(const std::string& name);
+ static const Feature* GetBehaviorFeature(const std::string& name);
+
+ //
+ // Instance methods.
+ //
+
+ // Returns the feature with the specified name.
+ virtual Feature* GetFeature(const std::string& name) const = 0;
+
+ // Returns the parent feature of |feature|, or NULL if there isn't one.
+ virtual Feature* GetParent(Feature* feature) const = 0;
+
+ // Returns the features inside the |parent| namespace, recursively.
+ virtual std::vector<Feature*> GetChildren(const Feature& parent) const = 0;
+
+ // Returns a map containing all features described by this instance.
+ virtual const FeatureMap& GetAllFeatures() const = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_FEATURES_FEATURE_PROVIDER_H_
diff --git a/chromium/extensions/common/features/feature_util.h b/chromium/extensions/common/features/feature_util.h
new file mode 100644
index 00000000000..f8d60061e62
--- /dev/null
+++ b/chromium/extensions/common/features/feature_util.h
@@ -0,0 +1,31 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_FEATURES_FEATURE_UTIL_H_
+#define EXTENSIONS_COMMON_FEATURES_FEATURE_UTIL_H_
+
+#include "base/debug/alias.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+
+// Writes |message| to the stack so that it shows up in the minidump, then
+// crashes the current process.
+//
+// The prefix "e::" is used so that the crash can be quickly located.
+//
+// This is provided in feature_util because for some reason features are prone
+// to mysterious crashes in named map lookups. For example see crbug.com/365192
+// and crbug.com/461915.
+#define CRASH_WITH_MINIDUMP(message) \
+ { \
+ std::string message_copy(message); \
+ char minidump[BUFSIZ]; \
+ base::debug::Alias(&minidump); \
+ base::snprintf(minidump, arraysize(minidump), "e::%s:%d:\"%s\"", __FILE__, \
+ __LINE__, message_copy.c_str()); \
+ LOG(FATAL) << message_copy; \
+ }
+
+#endif // EXTENSIONS_COMMON_FEATURES_FEATURE_UTIL_H_
diff --git a/chromium/extensions/common/features/json_feature_provider_source.cc b/chromium/extensions/common/features/json_feature_provider_source.cc
new file mode 100644
index 00000000000..4d2f8343a69
--- /dev/null
+++ b/chromium/extensions/common/features/json_feature_provider_source.cc
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/features/json_feature_provider_source.h"
+
+#include <utility>
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace extensions {
+
+JSONFeatureProviderSource::JSONFeatureProviderSource(const std::string& name)
+ : name_(name) {
+}
+
+JSONFeatureProviderSource::~JSONFeatureProviderSource() {
+}
+
+void JSONFeatureProviderSource::LoadJSON(int resource_id) {
+ const base::StringPiece features_file =
+ ResourceBundle::GetSharedInstance().GetRawDataResource(resource_id);
+ int error_code = 0;
+ std::string error_message;
+ scoped_ptr<base::Value> value(base::JSONReader::ReadAndReturnError(
+ features_file, base::JSON_PARSE_RFC, &error_code, &error_message));
+ DCHECK(value) << "Could not load features: " << name_ << " " << error_message;
+
+ scoped_ptr<base::DictionaryValue> value_as_dict;
+ if (value) {
+ CHECK(value->IsType(base::Value::TYPE_DICTIONARY)) << name_;
+ value_as_dict = base::DictionaryValue::From(std::move(value));
+ } else {
+ // There was some error loading the features file.
+ // http://crbug.com/176381
+ value_as_dict.reset(new base::DictionaryValue());
+ }
+
+ // Ensure there are no key collisions.
+ for (base::DictionaryValue::Iterator iter(*value_as_dict); !iter.IsAtEnd();
+ iter.Advance()) {
+ if (dictionary_.GetWithoutPathExpansion(iter.key(), NULL))
+ LOG(FATAL) << "Key " << iter.key() << " is defined in " << name_
+ << " JSON feature files more than once.";
+ }
+
+ // Merge.
+ dictionary_.MergeDictionary(value_as_dict.get());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/features/json_feature_provider_source.h b/chromium/extensions/common/features/json_feature_provider_source.h
new file mode 100644
index 00000000000..11a4fef884e
--- /dev/null
+++ b/chromium/extensions/common/features/json_feature_provider_source.h
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_FEATURES_JSON_FEATURE_PROVIDER_SOURCE_H_
+#define EXTENSIONS_COMMON_FEATURES_JSON_FEATURE_PROVIDER_SOURCE_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/values.h"
+
+namespace extensions {
+
+// A JSONFeatureProviderSource loads JSON dictionary files that
+// define features.
+class JSONFeatureProviderSource {
+ public:
+ explicit JSONFeatureProviderSource(const std::string& name);
+ ~JSONFeatureProviderSource();
+
+ // Adds the JSON dictionary file to this provider, merging its values with
+ // the current dictionary. Key collisions are treated as errors.
+ void LoadJSON(int resource_id);
+
+ // Returns the parsed dictionary.
+ const base::DictionaryValue& dictionary() { return dictionary_; }
+
+ private:
+ // The name of this feature type; only used for debugging.
+ const std::string name_;
+
+ base::DictionaryValue dictionary_;
+
+ DISALLOW_COPY_AND_ASSIGN(JSONFeatureProviderSource);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_FEATURES_JSON_FEATURE_PROVIDER_SOURCE_H_
diff --git a/chromium/extensions/common/features/manifest_feature.cc b/chromium/extensions/common/features/manifest_feature.cc
new file mode 100644
index 00000000000..f169c6a8ac8
--- /dev/null
+++ b/chromium/extensions/common/features/manifest_feature.cc
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/features/manifest_feature.h"
+
+#include "extensions/common/manifest.h"
+
+namespace extensions {
+
+ManifestFeature::ManifestFeature() {
+}
+
+ManifestFeature::~ManifestFeature() {
+}
+
+Feature::Availability ManifestFeature::IsAvailableToContext(
+ const Extension* extension,
+ Feature::Context context,
+ const GURL& url,
+ Feature::Platform platform) const {
+ Availability availability = SimpleFeature::IsAvailableToContext(extension,
+ context,
+ url,
+ platform);
+ if (!availability.is_available())
+ return availability;
+
+ // We know we can skip manifest()->GetKey() here because we just did the same
+ // validation it would do above.
+ if (extension && !extension->manifest()->value()->HasKey(name()))
+ return CreateAvailability(NOT_PRESENT, extension->GetType());
+
+ return CreateAvailability(IS_AVAILABLE);
+}
+
+std::string ManifestFeature::Parse(const base::DictionaryValue* value) {
+ std::string error = SimpleFeature::Parse(value);
+ if (!error.empty())
+ return error;
+
+ if (extension_types()->empty()) {
+ return name() + ": Manifest features must specify at least one " +
+ "value for extension_types.";
+ }
+
+ if (value->HasKey("contexts"))
+ return name() + ": Manifest features do not support contexts.";
+
+ return std::string();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/features/manifest_feature.h b/chromium/extensions/common/features/manifest_feature.h
new file mode 100644
index 00000000000..ab77d028df8
--- /dev/null
+++ b/chromium/extensions/common/features/manifest_feature.h
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_FEATURES_MANIFEST_FEATURE_H_
+#define EXTENSIONS_COMMON_FEATURES_MANIFEST_FEATURE_H_
+
+#include <string>
+
+#include "extensions/common/features/simple_feature.h"
+
+namespace extensions {
+
+class ManifestFeature : public SimpleFeature {
+ public:
+ ManifestFeature();
+ ~ManifestFeature() override;
+
+ Feature::Availability IsAvailableToContext(
+ const Extension* extension,
+ Feature::Context context,
+ const GURL& url,
+ Feature::Platform platform) const override;
+
+ std::string Parse(const base::DictionaryValue* value) override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_FEATURES_MANIFEST_FEATURE_H_
diff --git a/chromium/extensions/common/features/permission_feature.cc b/chromium/extensions/common/features/permission_feature.cc
new file mode 100644
index 00000000000..529ecd1188a
--- /dev/null
+++ b/chromium/extensions/common/features/permission_feature.cc
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/features/permission_feature.h"
+
+#include "extensions/common/permissions/permission_set.h"
+#include "extensions/common/permissions/permissions_data.h"
+
+namespace extensions {
+
+PermissionFeature::PermissionFeature() {
+}
+
+PermissionFeature::~PermissionFeature() {
+}
+
+Feature::Availability PermissionFeature::IsAvailableToContext(
+ const Extension* extension,
+ Feature::Context context,
+ const GURL& url,
+ Feature::Platform platform) const {
+ Availability availability = SimpleFeature::IsAvailableToContext(extension,
+ context,
+ url,
+ platform);
+ if (!availability.is_available())
+ return availability;
+
+ if (extension && !extension->permissions_data()->HasAPIPermission(name()))
+ return CreateAvailability(NOT_PRESENT, extension->GetType());
+
+ return CreateAvailability(IS_AVAILABLE);
+}
+
+std::string PermissionFeature::Parse(const base::DictionaryValue* value) {
+ std::string error = SimpleFeature::Parse(value);
+ if (!error.empty())
+ return error;
+
+ if (extension_types()->empty()) {
+ return name() + ": Permission features must specify at least one " +
+ "value for extension_types.";
+ }
+
+ if (value->HasKey("contexts"))
+ return name() + ": Permission features do not support contexts.";
+
+ return std::string();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/features/permission_feature.h b/chromium/extensions/common/features/permission_feature.h
new file mode 100644
index 00000000000..ff4a0ad9c08
--- /dev/null
+++ b/chromium/extensions/common/features/permission_feature.h
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_FEATURES_PERMISSION_FEATURE_H_
+#define EXTENSIONS_COMMON_FEATURES_PERMISSION_FEATURE_H_
+
+#include <string>
+
+#include "extensions/common/features/simple_feature.h"
+
+namespace extensions {
+
+class PermissionFeature : public SimpleFeature {
+ public:
+ PermissionFeature();
+ ~PermissionFeature() override;
+
+ Feature::Availability IsAvailableToContext(
+ const Extension* extension,
+ Feature::Context context,
+ const GURL& url,
+ Feature::Platform platform) const override;
+
+ std::string Parse(const base::DictionaryValue* value) override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_FEATURES_PERMISSION_FEATURE_H_
diff --git a/chromium/extensions/common/features/simple_feature.cc b/chromium/extensions/common/features/simple_feature.cc
new file mode 100644
index 00000000000..68c8dfa175f
--- /dev/null
+++ b/chromium/extensions/common/features/simple_feature.cc
@@ -0,0 +1,638 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/features/simple_feature.h"
+
+#include <algorithm>
+#include <map>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "base/sha1.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "components/crx_file/id_util.h"
+#include "extensions/common/extension_api.h"
+#include "extensions/common/features/feature_provider.h"
+#include "extensions/common/features/feature_util.h"
+#include "extensions/common/switches.h"
+
+using crx_file::id_util::HashedIdInHex;
+
+namespace extensions {
+
+namespace {
+
+// A singleton copy of the --whitelisted-extension-id so that we don't need to
+// copy it from the CommandLine each time.
+std::string* g_whitelisted_extension_id = NULL;
+
+Feature::Availability IsAvailableToManifestForBind(
+ const std::string& extension_id,
+ Manifest::Type type,
+ Manifest::Location location,
+ int manifest_version,
+ Feature::Platform platform,
+ const Feature* feature) {
+ return feature->IsAvailableToManifest(
+ extension_id, type, location, manifest_version, platform);
+}
+
+Feature::Availability IsAvailableToContextForBind(const Extension* extension,
+ Feature::Context context,
+ const GURL& url,
+ Feature::Platform platform,
+ const Feature* feature) {
+ return feature->IsAvailableToContext(extension, context, url, platform);
+}
+
+// TODO(aa): Can we replace all this manual parsing with JSON schema stuff?
+
+void ParseVector(const base::Value* value,
+ std::vector<std::string>* vector) {
+ const base::ListValue* list_value = NULL;
+ if (!value->GetAsList(&list_value))
+ return;
+
+ vector->clear();
+ size_t list_size = list_value->GetSize();
+ vector->reserve(list_size);
+ for (size_t i = 0; i < list_size; ++i) {
+ std::string str_val;
+ CHECK(list_value->GetString(i, &str_val));
+ vector->push_back(str_val);
+ }
+ std::sort(vector->begin(), vector->end());
+}
+
+template<typename T>
+void ParseEnum(const std::string& string_value,
+ T* enum_value,
+ const std::map<std::string, T>& mapping) {
+ const auto& iter = mapping.find(string_value);
+ if (iter == mapping.end())
+ CRASH_WITH_MINIDUMP("Enum value not found: " + string_value);
+ *enum_value = iter->second;
+}
+
+template<typename T>
+void ParseEnum(const base::DictionaryValue* value,
+ const std::string& property,
+ T* enum_value,
+ const std::map<std::string, T>& mapping) {
+ std::string string_value;
+ if (!value->GetString(property, &string_value))
+ return;
+
+ ParseEnum(string_value, enum_value, mapping);
+}
+
+template<typename T>
+void ParseEnumVector(const base::Value* value,
+ std::vector<T>* enum_vector,
+ const std::map<std::string, T>& mapping) {
+ enum_vector->clear();
+ std::string property_string;
+ if (value->GetAsString(&property_string)) {
+ if (property_string == "all") {
+ enum_vector->reserve(mapping.size());
+ for (const auto& it : mapping)
+ enum_vector->push_back(it.second);
+ }
+ std::sort(enum_vector->begin(), enum_vector->end());
+ return;
+ }
+
+ std::vector<std::string> string_vector;
+ ParseVector(value, &string_vector);
+ enum_vector->reserve(string_vector.size());
+ for (const auto& str : string_vector) {
+ T enum_value = static_cast<T>(0);
+ ParseEnum(str, &enum_value, mapping);
+ enum_vector->push_back(enum_value);
+ }
+ std::sort(enum_vector->begin(), enum_vector->end());
+}
+
+void ParseURLPatterns(const base::DictionaryValue* value,
+ const std::string& key,
+ URLPatternSet* set) {
+ const base::ListValue* matches = NULL;
+ if (value->GetList(key, &matches)) {
+ set->ClearPatterns();
+ for (size_t i = 0; i < matches->GetSize(); ++i) {
+ std::string pattern;
+ CHECK(matches->GetString(i, &pattern));
+ set->AddPattern(URLPattern(URLPattern::SCHEME_ALL, pattern));
+ }
+ }
+}
+
+// Gets a human-readable name for the given extension type, suitable for giving
+// to developers in an error message.
+std::string GetDisplayName(Manifest::Type type) {
+ switch (type) {
+ case Manifest::TYPE_UNKNOWN:
+ return "unknown";
+ case Manifest::TYPE_EXTENSION:
+ return "extension";
+ case Manifest::TYPE_HOSTED_APP:
+ return "hosted app";
+ case Manifest::TYPE_LEGACY_PACKAGED_APP:
+ return "legacy packaged app";
+ case Manifest::TYPE_PLATFORM_APP:
+ return "packaged app";
+ case Manifest::TYPE_THEME:
+ return "theme";
+ case Manifest::TYPE_USER_SCRIPT:
+ return "user script";
+ case Manifest::TYPE_SHARED_MODULE:
+ return "shared module";
+ case Manifest::NUM_LOAD_TYPES:
+ NOTREACHED();
+ }
+ NOTREACHED();
+ return "";
+}
+
+// Gets a human-readable name for the given context type, suitable for giving
+// to developers in an error message.
+std::string GetDisplayName(Feature::Context context) {
+ switch (context) {
+ case Feature::UNSPECIFIED_CONTEXT:
+ return "unknown";
+ case Feature::BLESSED_EXTENSION_CONTEXT:
+ // "privileged" is vague but hopefully the developer will understand that
+ // means background or app window.
+ return "privileged page";
+ case Feature::UNBLESSED_EXTENSION_CONTEXT:
+ // "iframe" is a bit of a lie/oversimplification, but that's the most
+ // common unblessed context.
+ return "extension iframe";
+ case Feature::CONTENT_SCRIPT_CONTEXT:
+ return "content script";
+ case Feature::WEB_PAGE_CONTEXT:
+ return "web page";
+ case Feature::BLESSED_WEB_PAGE_CONTEXT:
+ return "hosted app";
+ case Feature::WEBUI_CONTEXT:
+ return "webui";
+ case Feature::SERVICE_WORKER_CONTEXT:
+ return "service worker";
+ }
+ NOTREACHED();
+ return "";
+}
+
+// Gets a human-readable list of the display names (pluralized, comma separated
+// with the "and" in the correct place) for each of |enum_types|.
+template <typename EnumType>
+std::string ListDisplayNames(const std::vector<EnumType>& enum_types) {
+ std::string display_name_list;
+ for (size_t i = 0; i < enum_types.size(); ++i) {
+ // Pluralize type name.
+ display_name_list += GetDisplayName(enum_types[i]) + "s";
+ // Comma-separate entries, with an Oxford comma if there is more than 2
+ // total entries.
+ if (enum_types.size() > 2) {
+ if (i < enum_types.size() - 2)
+ display_name_list += ", ";
+ else if (i == enum_types.size() - 2)
+ display_name_list += ", and ";
+ } else if (enum_types.size() == 2 && i == 0) {
+ display_name_list += " and ";
+ }
+ }
+ return display_name_list;
+}
+
+bool IsCommandLineSwitchEnabled(const std::string& switch_name) {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switch_name + "=1"))
+ return true;
+ if (command_line->HasSwitch(std::string("enable-") + switch_name))
+ return true;
+ return false;
+}
+
+bool IsWhitelistedForTest(const std::string& extension_id) {
+ // TODO(jackhou): Delete the commandline whitelisting mechanism.
+ // Since it is only used it tests, ideally it should not be set via the
+ // commandline. At the moment the commandline is used as a mechanism to pass
+ // the id to the renderer process.
+ if (!g_whitelisted_extension_id) {
+ g_whitelisted_extension_id = new std::string(
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kWhitelistedExtensionID));
+ }
+ return !g_whitelisted_extension_id->empty() &&
+ *g_whitelisted_extension_id == extension_id;
+}
+
+} // namespace
+
+SimpleFeature::ScopedWhitelistForTest::ScopedWhitelistForTest(
+ const std::string& id)
+ : previous_id_(g_whitelisted_extension_id) {
+ g_whitelisted_extension_id = new std::string(id);
+}
+
+SimpleFeature::ScopedWhitelistForTest::~ScopedWhitelistForTest() {
+ delete g_whitelisted_extension_id;
+ g_whitelisted_extension_id = previous_id_;
+}
+
+struct SimpleFeature::Mappings {
+ Mappings() {
+ extension_types["extension"] = Manifest::TYPE_EXTENSION;
+ extension_types["theme"] = Manifest::TYPE_THEME;
+ extension_types["legacy_packaged_app"] = Manifest::TYPE_LEGACY_PACKAGED_APP;
+ extension_types["hosted_app"] = Manifest::TYPE_HOSTED_APP;
+ extension_types["platform_app"] = Manifest::TYPE_PLATFORM_APP;
+ extension_types["shared_module"] = Manifest::TYPE_SHARED_MODULE;
+
+ contexts["blessed_extension"] = Feature::BLESSED_EXTENSION_CONTEXT;
+ contexts["unblessed_extension"] = Feature::UNBLESSED_EXTENSION_CONTEXT;
+ contexts["content_script"] = Feature::CONTENT_SCRIPT_CONTEXT;
+ contexts["web_page"] = Feature::WEB_PAGE_CONTEXT;
+ contexts["blessed_web_page"] = Feature::BLESSED_WEB_PAGE_CONTEXT;
+ contexts["webui"] = Feature::WEBUI_CONTEXT;
+
+ locations["component"] = SimpleFeature::COMPONENT_LOCATION;
+ locations["external_component"] =
+ SimpleFeature::EXTERNAL_COMPONENT_LOCATION;
+ locations["policy"] = SimpleFeature::POLICY_LOCATION;
+
+ platforms["chromeos"] = Feature::CHROMEOS_PLATFORM;
+ platforms["linux"] = Feature::LINUX_PLATFORM;
+ platforms["mac"] = Feature::MACOSX_PLATFORM;
+ platforms["win"] = Feature::WIN_PLATFORM;
+ }
+
+ std::map<std::string, Manifest::Type> extension_types;
+ std::map<std::string, Feature::Context> contexts;
+ std::map<std::string, SimpleFeature::Location> locations;
+ std::map<std::string, Feature::Platform> platforms;
+};
+
+SimpleFeature::SimpleFeature()
+ : location_(UNSPECIFIED_LOCATION),
+ min_manifest_version_(0),
+ max_manifest_version_(0),
+ component_extensions_auto_granted_(true) {}
+
+SimpleFeature::~SimpleFeature() {}
+
+bool SimpleFeature::HasDependencies() const {
+ return !dependencies_.empty();
+}
+
+void SimpleFeature::AddFilter(scoped_ptr<SimpleFeatureFilter> filter) {
+ filters_.push_back(std::move(filter));
+}
+
+std::string SimpleFeature::Parse(const base::DictionaryValue* dictionary) {
+ static base::LazyInstance<SimpleFeature::Mappings> mappings =
+ LAZY_INSTANCE_INITIALIZER;
+
+ no_parent_ = false;
+ for (base::DictionaryValue::Iterator it(*dictionary);
+ !it.IsAtEnd();
+ it.Advance()) {
+ std::string key = it.key();
+ const base::Value* value = &it.value();
+ if (key == "matches") {
+ ParseURLPatterns(dictionary, "matches", &matches_);
+ } else if (key == "blacklist") {
+ ParseVector(value, &blacklist_);
+ } else if (key == "whitelist") {
+ ParseVector(value, &whitelist_);
+ } else if (key == "dependencies") {
+ ParseVector(value, &dependencies_);
+ } else if (key == "extension_types") {
+ ParseEnumVector<Manifest::Type>(value, &extension_types_,
+ mappings.Get().extension_types);
+ } else if (key == "contexts") {
+ ParseEnumVector<Context>(value, &contexts_,
+ mappings.Get().contexts);
+ } else if (key == "location") {
+ ParseEnum<Location>(dictionary, "location", &location_,
+ mappings.Get().locations);
+ } else if (key == "platforms") {
+ ParseEnumVector<Platform>(value, &platforms_,
+ mappings.Get().platforms);
+ } else if (key == "min_manifest_version") {
+ dictionary->GetInteger("min_manifest_version", &min_manifest_version_);
+ } else if (key == "max_manifest_version") {
+ dictionary->GetInteger("max_manifest_version", &max_manifest_version_);
+ } else if (key == "noparent") {
+ dictionary->GetBoolean("noparent", &no_parent_);
+ } else if (key == "component_extensions_auto_granted") {
+ dictionary->GetBoolean("component_extensions_auto_granted",
+ &component_extensions_auto_granted_);
+ } else if (key == "command_line_switch") {
+ dictionary->GetString("command_line_switch", &command_line_switch_);
+ }
+ }
+
+ // NOTE: ideally we'd sanity check that "matches" can be specified if and
+ // only if there's a "web_page" or "webui" context, but without
+ // (Simple)Features being aware of their own heirarchy this is impossible.
+ //
+ // For example, we might have feature "foo" available to "web_page" context
+ // and "matches" google.com/*. Then a sub-feature "foo.bar" might override
+ // "matches" to be chromium.org/*. That sub-feature doesn't need to specify
+ // "web_page" context because it's inherited, but we don't know that here.
+
+ std::string result;
+ for (const auto& filter : filters_) {
+ result = filter->Parse(dictionary);
+ if (!result.empty())
+ break;
+ }
+
+ return result;
+}
+
+Feature::Availability SimpleFeature::IsAvailableToManifest(
+ const std::string& extension_id,
+ Manifest::Type type,
+ Manifest::Location location,
+ int manifest_version,
+ Platform platform) const {
+ // Check extension type first to avoid granting platform app permissions
+ // to component extensions.
+ // HACK(kalman): user script -> extension. Solve this in a more generic way
+ // when we compile feature files.
+ Manifest::Type type_to_check = (type == Manifest::TYPE_USER_SCRIPT) ?
+ Manifest::TYPE_EXTENSION : type;
+ if (!extension_types_.empty() &&
+ !ContainsValue(extension_types_, type_to_check)) {
+ return CreateAvailability(INVALID_TYPE, type);
+ }
+
+ if (IsIdInBlacklist(extension_id))
+ return CreateAvailability(FOUND_IN_BLACKLIST, type);
+
+ // TODO(benwells): don't grant all component extensions.
+ // See http://crbug.com/370375 for more details.
+ // Component extensions can access any feature.
+ // NOTE: Deliberately does not match EXTERNAL_COMPONENT.
+ if (component_extensions_auto_granted_ && location == Manifest::COMPONENT)
+ return CreateAvailability(IS_AVAILABLE, type);
+
+ if (!whitelist_.empty() && !IsIdInWhitelist(extension_id) &&
+ !IsWhitelistedForTest(extension_id)) {
+ return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
+ }
+
+ if (!MatchesManifestLocation(location))
+ return CreateAvailability(INVALID_LOCATION, type);
+
+ if (!platforms_.empty() && !ContainsValue(platforms_, platform))
+ return CreateAvailability(INVALID_PLATFORM, type);
+
+ if (min_manifest_version_ != 0 && manifest_version < min_manifest_version_)
+ return CreateAvailability(INVALID_MIN_MANIFEST_VERSION, type);
+
+ if (max_manifest_version_ != 0 && manifest_version > max_manifest_version_)
+ return CreateAvailability(INVALID_MAX_MANIFEST_VERSION, type);
+
+ if (!command_line_switch_.empty() &&
+ !IsCommandLineSwitchEnabled(command_line_switch_)) {
+ return CreateAvailability(MISSING_COMMAND_LINE_SWITCH, type);
+ }
+
+ for (const auto& filter : filters_) {
+ Availability availability = filter->IsAvailableToManifest(
+ extension_id, type, location, manifest_version, platform);
+ if (!availability.is_available())
+ return availability;
+ }
+
+ return CheckDependencies(base::Bind(&IsAvailableToManifestForBind,
+ extension_id,
+ type,
+ location,
+ manifest_version,
+ platform));
+}
+
+Feature::Availability SimpleFeature::IsAvailableToContext(
+ const Extension* extension,
+ SimpleFeature::Context context,
+ const GURL& url,
+ SimpleFeature::Platform platform) const {
+ if (extension) {
+ Availability result = IsAvailableToManifest(extension->id(),
+ extension->GetType(),
+ extension->location(),
+ extension->manifest_version(),
+ platform);
+ if (!result.is_available())
+ return result;
+ }
+
+ if (!contexts_.empty() && !ContainsValue(contexts_, context))
+ return CreateAvailability(INVALID_CONTEXT, context);
+
+ // TODO(kalman): Consider checking |matches_| regardless of context type.
+ // Fewer surprises, and if the feature configuration wants to isolate
+ // "matches" from say "blessed_extension" then they can use complex features.
+ if ((context == WEB_PAGE_CONTEXT || context == WEBUI_CONTEXT) &&
+ !matches_.MatchesURL(url)) {
+ return CreateAvailability(INVALID_URL, url);
+ }
+
+ for (const auto& filter : filters_) {
+ Availability availability =
+ filter->IsAvailableToContext(extension, context, url, platform);
+ if (!availability.is_available())
+ return availability;
+ }
+
+ // TODO(kalman): Assert that if the context was a webpage or WebUI context
+ // then at some point a "matches" restriction was checked.
+ return CheckDependencies(base::Bind(
+ &IsAvailableToContextForBind, extension, context, url, platform));
+}
+
+std::string SimpleFeature::GetAvailabilityMessage(
+ AvailabilityResult result,
+ Manifest::Type type,
+ const GURL& url,
+ Context context) const {
+ switch (result) {
+ case IS_AVAILABLE:
+ return std::string();
+ case NOT_FOUND_IN_WHITELIST:
+ case FOUND_IN_BLACKLIST:
+ return base::StringPrintf(
+ "'%s' is not allowed for specified extension ID.",
+ name().c_str());
+ case INVALID_URL:
+ return base::StringPrintf("'%s' is not allowed on %s.",
+ name().c_str(), url.spec().c_str());
+ case INVALID_TYPE:
+ return base::StringPrintf(
+ "'%s' is only allowed for %s, but this is a %s.",
+ name().c_str(),
+ ListDisplayNames(std::vector<Manifest::Type>(
+ extension_types_.begin(), extension_types_.end())).c_str(),
+ GetDisplayName(type).c_str());
+ case INVALID_CONTEXT:
+ return base::StringPrintf(
+ "'%s' is only allowed to run in %s, but this is a %s",
+ name().c_str(),
+ ListDisplayNames(std::vector<Context>(
+ contexts_.begin(), contexts_.end())).c_str(),
+ GetDisplayName(context).c_str());
+ case INVALID_LOCATION:
+ return base::StringPrintf(
+ "'%s' is not allowed for specified install location.",
+ name().c_str());
+ case INVALID_PLATFORM:
+ return base::StringPrintf(
+ "'%s' is not allowed for specified platform.",
+ name().c_str());
+ case INVALID_MIN_MANIFEST_VERSION:
+ return base::StringPrintf(
+ "'%s' requires manifest version of at least %d.",
+ name().c_str(),
+ min_manifest_version_);
+ case INVALID_MAX_MANIFEST_VERSION:
+ return base::StringPrintf(
+ "'%s' requires manifest version of %d or lower.",
+ name().c_str(),
+ max_manifest_version_);
+ case NOT_PRESENT:
+ return base::StringPrintf(
+ "'%s' requires a different Feature that is not present.",
+ name().c_str());
+ case UNSUPPORTED_CHANNEL:
+ return base::StringPrintf(
+ "'%s' is unsupported in this version of the platform.",
+ name().c_str());
+ case MISSING_COMMAND_LINE_SWITCH:
+ return base::StringPrintf(
+ "'%s' requires the '%s' command line switch to be enabled.",
+ name().c_str(), command_line_switch_.c_str());
+ }
+
+ NOTREACHED();
+ return std::string();
+}
+
+Feature::Availability SimpleFeature::CreateAvailability(
+ AvailabilityResult result) const {
+ return Availability(
+ result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
+ UNSPECIFIED_CONTEXT));
+}
+
+Feature::Availability SimpleFeature::CreateAvailability(
+ AvailabilityResult result, Manifest::Type type) const {
+ return Availability(result, GetAvailabilityMessage(result, type, GURL(),
+ UNSPECIFIED_CONTEXT));
+}
+
+Feature::Availability SimpleFeature::CreateAvailability(
+ AvailabilityResult result,
+ const GURL& url) const {
+ return Availability(
+ result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, url,
+ UNSPECIFIED_CONTEXT));
+}
+
+Feature::Availability SimpleFeature::CreateAvailability(
+ AvailabilityResult result,
+ Context context) const {
+ return Availability(
+ result, GetAvailabilityMessage(result, Manifest::TYPE_UNKNOWN, GURL(),
+ context));
+}
+
+bool SimpleFeature::IsInternal() const {
+ return false;
+}
+
+bool SimpleFeature::IsIdInBlacklist(const std::string& extension_id) const {
+ return IsIdInList(extension_id, blacklist_);
+}
+
+bool SimpleFeature::IsIdInWhitelist(const std::string& extension_id) const {
+ return IsIdInList(extension_id, whitelist_);
+}
+
+// static
+bool SimpleFeature::IsIdInArray(const std::string& extension_id,
+ const char* const array[],
+ size_t array_length) {
+ if (!IsValidExtensionId(extension_id))
+ return false;
+
+ const char* const* start = array;
+ const char* const* end = array + array_length;
+
+ return ((std::find(start, end, extension_id) != end) ||
+ (std::find(start, end, HashedIdInHex(extension_id)) != end));
+}
+
+// static
+bool SimpleFeature::IsIdInList(const std::string& extension_id,
+ const std::vector<std::string>& list) {
+ if (!IsValidExtensionId(extension_id))
+ return false;
+
+ return (ContainsValue(list, extension_id) ||
+ ContainsValue(list, HashedIdInHex(extension_id)));
+}
+
+bool SimpleFeature::MatchesManifestLocation(
+ Manifest::Location manifest_location) const {
+ switch (location_) {
+ case SimpleFeature::UNSPECIFIED_LOCATION:
+ return true;
+ case SimpleFeature::COMPONENT_LOCATION:
+ return manifest_location == Manifest::COMPONENT;
+ case SimpleFeature::EXTERNAL_COMPONENT_LOCATION:
+ return manifest_location == Manifest::EXTERNAL_COMPONENT;
+ case SimpleFeature::POLICY_LOCATION:
+ return manifest_location == Manifest::EXTERNAL_POLICY ||
+ manifest_location == Manifest::EXTERNAL_POLICY_DOWNLOAD;
+ }
+ NOTREACHED();
+ return false;
+}
+
+Feature::Availability SimpleFeature::CheckDependencies(
+ const base::Callback<Availability(const Feature*)>& checker) const {
+ for (const auto& dep_name : dependencies_) {
+ Feature* dependency =
+ ExtensionAPI::GetSharedInstance()->GetFeatureDependency(dep_name);
+ if (!dependency)
+ return CreateAvailability(NOT_PRESENT);
+ Availability dependency_availability = checker.Run(dependency);
+ if (!dependency_availability.is_available())
+ return dependency_availability;
+ }
+ return CreateAvailability(IS_AVAILABLE);
+}
+
+// static
+bool SimpleFeature::IsValidExtensionId(const std::string& extension_id) {
+ // Belt-and-suspenders philosophy here. We should be pretty confident by this
+ // point that we've validated the extension ID format, but in case something
+ // slips through, we avoid a class of attack where creative ID manipulation
+ // leads to hash collisions.
+ // 128 bits / 4 = 32 mpdecimal characters
+ return (extension_id.length() == 32);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/features/simple_feature.h b/chromium/extensions/common/features/simple_feature.h
new file mode 100644
index 00000000000..4ff2c736651
--- /dev/null
+++ b/chromium/extensions/common/features/simple_feature.h
@@ -0,0 +1,215 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_FEATURES_SIMPLE_FEATURE_H_
+#define EXTENSIONS_COMMON_FEATURES_SIMPLE_FEATURE_H_
+
+#include <stddef.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/gtest_prod_util.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/features/simple_feature_filter.h"
+#include "extensions/common/manifest.h"
+
+namespace extensions {
+
+class BaseFeatureProviderTest;
+class ExtensionAPITest;
+class ManifestUnitTest;
+class SimpleFeatureTest;
+
+class SimpleFeature : public Feature {
+ public:
+ // Used by tests to override the cached --whitelisted-extension-id.
+ class ScopedWhitelistForTest {
+ public:
+ explicit ScopedWhitelistForTest(const std::string& id);
+ ~ScopedWhitelistForTest();
+
+ private:
+ std::string* previous_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedWhitelistForTest);
+ };
+
+ SimpleFeature();
+ ~SimpleFeature() override;
+
+ // Dependency resolution is a property of Features that is preferrably
+ // handled internally to avoid temptation, but FeatureFilters may need
+ // to know if there are any at all.
+ bool HasDependencies() const;
+
+ // Adds a filter to this feature. The feature takes ownership of the filter.
+ void AddFilter(scoped_ptr<SimpleFeatureFilter> filter);
+
+ // Parses the JSON representation of a feature into the fields of this object.
+ // Unspecified values in the JSON are not modified in the object. This allows
+ // us to implement inheritance by parsing one value after another. Returns
+ // the error found, or an empty string on success.
+ virtual std::string Parse(const base::DictionaryValue* dictionary);
+
+ Availability IsAvailableToContext(const Extension* extension,
+ Context context) const {
+ return IsAvailableToContext(extension, context, GURL());
+ }
+ Availability IsAvailableToContext(const Extension* extension,
+ Context context,
+ Platform platform) const {
+ return IsAvailableToContext(extension, context, GURL(), platform);
+ }
+ Availability IsAvailableToContext(const Extension* extension,
+ Context context,
+ const GURL& url) const {
+ return IsAvailableToContext(extension, context, url, GetCurrentPlatform());
+ }
+
+ // extension::Feature:
+ Availability IsAvailableToManifest(const std::string& extension_id,
+ Manifest::Type type,
+ Manifest::Location location,
+ int manifest_version,
+ Platform platform) const override;
+
+ Availability IsAvailableToContext(const Extension* extension,
+ Context context,
+ const GURL& url,
+ Platform platform) const override;
+
+ std::string GetAvailabilityMessage(AvailabilityResult result,
+ Manifest::Type type,
+ const GURL& url,
+ Context context) const override;
+
+ bool IsInternal() const override;
+
+ bool IsIdInBlacklist(const std::string& extension_id) const override;
+ bool IsIdInWhitelist(const std::string& extension_id) const override;
+
+ static bool IsIdInArray(const std::string& extension_id,
+ const char* const array[],
+ size_t array_length);
+
+ protected:
+ // Similar to Manifest::Location, these are the classes of locations
+ // supported in feature files. Production code should never directly access
+ // these.
+ enum Location {
+ UNSPECIFIED_LOCATION,
+ COMPONENT_LOCATION,
+ EXTERNAL_COMPONENT_LOCATION,
+ POLICY_LOCATION,
+ };
+
+ // Accessors defined for testing.
+ std::vector<std::string>* blacklist() { return &blacklist_; }
+ const std::vector<std::string>* blacklist() const { return &blacklist_; }
+ std::vector<std::string>* whitelist() { return &whitelist_; }
+ const std::vector<std::string>* whitelist() const { return &whitelist_; }
+ std::vector<Manifest::Type>* extension_types() { return &extension_types_; }
+ const std::vector<Manifest::Type>* extension_types() const {
+ return &extension_types_;
+ }
+ std::vector<Context>* contexts() { return &contexts_; }
+ const std::vector<Context>* contexts() const { return &contexts_; }
+ std::vector<Platform>* platforms() { return &platforms_; }
+ Location location() const { return location_; }
+ void set_location(Location location) { location_ = location; }
+ int min_manifest_version() const { return min_manifest_version_; }
+ void set_min_manifest_version(int min_manifest_version) {
+ min_manifest_version_ = min_manifest_version;
+ }
+ int max_manifest_version() const { return max_manifest_version_; }
+ void set_max_manifest_version(int max_manifest_version) {
+ max_manifest_version_ = max_manifest_version;
+ }
+ const std::string& command_line_switch() const {
+ return command_line_switch_;
+ }
+ void set_command_line_switch(const std::string& command_line_switch) {
+ command_line_switch_ = command_line_switch;
+ }
+
+ // Handy utilities which construct the correct availability message.
+ Availability CreateAvailability(AvailabilityResult result) const;
+ Availability CreateAvailability(AvailabilityResult result,
+ Manifest::Type type) const;
+ Availability CreateAvailability(AvailabilityResult result,
+ const GURL& url) const;
+ Availability CreateAvailability(AvailabilityResult result,
+ Context context) const;
+
+ private:
+ friend class SimpleFeatureTest;
+ FRIEND_TEST_ALL_PREFIXES(BaseFeatureProviderTest, ManifestFeatureTypes);
+ FRIEND_TEST_ALL_PREFIXES(BaseFeatureProviderTest, PermissionFeatureTypes);
+ FRIEND_TEST_ALL_PREFIXES(ExtensionAPITest, DefaultConfigurationFeatures);
+ FRIEND_TEST_ALL_PREFIXES(ManifestUnitTest, Extension);
+ FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, Blacklist);
+ FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, CommandLineSwitch);
+ FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, Context);
+ FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, HashedIdBlacklist);
+ FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, HashedIdWhitelist);
+ FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, Inheritance);
+ FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, Location);
+ FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, ManifestVersion);
+ FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, PackageType);
+ FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, ParseContexts);
+ FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, ParseLocation);
+ FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, ParseManifestVersion);
+ FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, ParseNull);
+ FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, ParsePackageTypes);
+ FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, ParsePlatforms);
+ FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, ParseWhitelist);
+ FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, Platform);
+ FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, Whitelist);
+
+ // Holds String to Enum value mappings.
+ struct Mappings;
+
+ static bool IsIdInList(const std::string& extension_id,
+ const std::vector<std::string>& list);
+
+ bool MatchesManifestLocation(Manifest::Location manifest_location) const;
+
+ Availability CheckDependencies(
+ const base::Callback<Availability(const Feature*)>& checker) const;
+
+ static bool IsValidExtensionId(const std::string& extension_id);
+
+ // For clarity and consistency, we handle the default value of each of these
+ // members the same way: it matches everything. It is up to the higher level
+ // code that reads Features out of static data to validate that data and set
+ // sensible defaults.
+ std::vector<std::string> blacklist_;
+ std::vector<std::string> whitelist_;
+ std::vector<std::string> dependencies_;
+ std::vector<Manifest::Type> extension_types_;
+ std::vector<Context> contexts_;
+ std::vector<Platform> platforms_;
+ URLPatternSet matches_;
+ Location location_;
+ int min_manifest_version_;
+ int max_manifest_version_;
+ bool component_extensions_auto_granted_;
+ std::string command_line_switch_;
+
+ std::vector<scoped_ptr<SimpleFeatureFilter>> filters_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleFeature);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_FEATURES_SIMPLE_FEATURE_H_
diff --git a/chromium/extensions/common/features/simple_feature_filter.cc b/chromium/extensions/common/features/simple_feature_filter.cc
new file mode 100644
index 00000000000..623c49cca8e
--- /dev/null
+++ b/chromium/extensions/common/features/simple_feature_filter.cc
@@ -0,0 +1,37 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/features/simple_feature_filter.h"
+
+#include "extensions/common/features/simple_feature.h"
+
+namespace extensions {
+
+SimpleFeatureFilter::SimpleFeatureFilter(SimpleFeature* feature)
+ : feature_(feature) {}
+
+SimpleFeatureFilter::~SimpleFeatureFilter() {}
+
+std::string SimpleFeatureFilter::Parse(const base::DictionaryValue* value) {
+ return std::string();
+}
+
+Feature::Availability SimpleFeatureFilter::IsAvailableToContext(
+ const Extension* extension,
+ Feature::Context context,
+ const GURL& url,
+ Feature::Platform platform) const {
+ return Feature::Availability(Feature::IS_AVAILABLE, std::string());
+}
+
+Feature::Availability SimpleFeatureFilter::IsAvailableToManifest(
+ const std::string& extension_id,
+ Manifest::Type type,
+ Manifest::Location location,
+ int manifest_version,
+ Feature::Platform platform) const {
+ return Feature::Availability(Feature::IS_AVAILABLE, std::string());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/features/simple_feature_filter.h b/chromium/extensions/common/features/simple_feature_filter.h
new file mode 100644
index 00000000000..4f8a8c2e7bd
--- /dev/null
+++ b/chromium/extensions/common/features/simple_feature_filter.h
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_FEATURES_SIMPLE_FEATURE_FILTER_H_
+#define EXTENSIONS_COMMON_FEATURES_SIMPLE_FEATURE_FILTER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/manifest.h"
+
+class GURL;
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace extensions {
+
+class SimpleFeature;
+
+// A SimpleFeatureFilter can be attached to SimpleFeature objects to provide
+// additional parsing and availability filtering behavior.
+class SimpleFeatureFilter {
+ public:
+ explicit SimpleFeatureFilter(SimpleFeature* feature);
+ virtual ~SimpleFeatureFilter();
+
+ SimpleFeature* feature() const { return feature_; }
+
+ // Parses any additional feature data that may be used by this filter.
+ // Returns an error string on failure or the empty string on success.
+ // The default implementation simply returns the empty string.
+ virtual std::string Parse(const base::DictionaryValue* value);
+
+ // Indicates whether or not the owning feature is available within a given
+ // extensions context. The default implementation only affirms availability.
+ virtual Feature::Availability IsAvailableToContext(
+ const Extension* extension,
+ Feature::Context context,
+ const GURL& url,
+ Feature::Platform platform) const;
+
+ // Indicates whether or not the owning feature is available to a given
+ // extension manifest. The default implementation only affirms availability.
+ virtual Feature::Availability IsAvailableToManifest(
+ const std::string& extension_id,
+ Manifest::Type type,
+ Manifest::Location location,
+ int manifest_version,
+ Feature::Platform platform) const;
+
+ private:
+ // The feature which owns this filter.
+ SimpleFeature* feature_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleFeatureFilter);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_FEATURES_SIMPLE_FEATURE_FILTER_H_
diff --git a/chromium/extensions/common/features/simple_feature_unittest.cc b/chromium/extensions/common/features/simple_feature_unittest.cc
new file mode 100644
index 00000000000..afe8d7cef5d
--- /dev/null
+++ b/chromium/extensions/common/features/simple_feature_unittest.cc
@@ -0,0 +1,752 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/features/simple_feature.h"
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "base/stl_util.h"
+#include "base/values.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/value_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+struct IsAvailableTestData {
+ std::string extension_id;
+ Manifest::Type extension_type;
+ Manifest::Location location;
+ Feature::Platform platform;
+ int manifest_version;
+ Feature::AvailabilityResult expected_result;
+};
+
+class ScopedCommandLineSwitch {
+ public:
+ explicit ScopedCommandLineSwitch(const std::string& arg)
+ : original_command_line_(*base::CommandLine::ForCurrentProcess()) {
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(arg);
+ }
+
+ ~ScopedCommandLineSwitch() {
+ *base::CommandLine::ForCurrentProcess() = original_command_line_;
+ }
+
+ private:
+ base::CommandLine original_command_line_;
+};
+
+} // namespace
+
+class SimpleFeatureTest : public testing::Test {
+ protected:
+ bool LocationIsAvailable(SimpleFeature::Location feature_location,
+ Manifest::Location manifest_location) {
+ SimpleFeature feature;
+ feature.set_location(feature_location);
+ Feature::AvailabilityResult availability_result =
+ feature.IsAvailableToManifest(std::string(),
+ Manifest::TYPE_UNKNOWN,
+ manifest_location,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result();
+ return availability_result == Feature::IS_AVAILABLE;
+ }
+};
+
+TEST_F(SimpleFeatureTest, IsAvailableNullCase) {
+ const IsAvailableTestData tests[] = {
+ {"", Manifest::TYPE_UNKNOWN, Manifest::INVALID_LOCATION,
+ Feature::UNSPECIFIED_PLATFORM, -1, Feature::IS_AVAILABLE},
+ {"random-extension", Manifest::TYPE_UNKNOWN, Manifest::INVALID_LOCATION,
+ Feature::UNSPECIFIED_PLATFORM, -1, Feature::IS_AVAILABLE},
+ {"", Manifest::TYPE_LEGACY_PACKAGED_APP, Manifest::INVALID_LOCATION,
+ Feature::UNSPECIFIED_PLATFORM, -1, Feature::IS_AVAILABLE},
+ {"", Manifest::TYPE_UNKNOWN, Manifest::INVALID_LOCATION,
+ Feature::UNSPECIFIED_PLATFORM, -1, Feature::IS_AVAILABLE},
+ {"", Manifest::TYPE_UNKNOWN, Manifest::COMPONENT,
+ Feature::UNSPECIFIED_PLATFORM, -1, Feature::IS_AVAILABLE},
+ {"", Manifest::TYPE_UNKNOWN, Manifest::INVALID_LOCATION,
+ Feature::CHROMEOS_PLATFORM, -1, Feature::IS_AVAILABLE},
+ {"", Manifest::TYPE_UNKNOWN, Manifest::INVALID_LOCATION,
+ Feature::UNSPECIFIED_PLATFORM, 25, Feature::IS_AVAILABLE}};
+
+ SimpleFeature feature;
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ const IsAvailableTestData& test = tests[i];
+ EXPECT_EQ(test.expected_result,
+ feature.IsAvailableToManifest(test.extension_id,
+ test.extension_type,
+ test.location,
+ test.manifest_version,
+ test.platform).result());
+ }
+}
+
+TEST_F(SimpleFeatureTest, Whitelist) {
+ const std::string kIdFoo("fooabbbbccccddddeeeeffffgggghhhh");
+ const std::string kIdBar("barabbbbccccddddeeeeffffgggghhhh");
+ const std::string kIdBaz("bazabbbbccccddddeeeeffffgggghhhh");
+ SimpleFeature feature;
+ feature.whitelist()->push_back(kIdFoo);
+ feature.whitelist()->push_back(kIdBar);
+
+ EXPECT_EQ(
+ Feature::IS_AVAILABLE,
+ feature.IsAvailableToManifest(kIdFoo,
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+ EXPECT_EQ(
+ Feature::IS_AVAILABLE,
+ feature.IsAvailableToManifest(kIdBar,
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+
+ EXPECT_EQ(
+ Feature::NOT_FOUND_IN_WHITELIST,
+ feature.IsAvailableToManifest(kIdBaz,
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+ EXPECT_EQ(
+ Feature::NOT_FOUND_IN_WHITELIST,
+ feature.IsAvailableToManifest(std::string(),
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+
+ feature.extension_types()->push_back(Manifest::TYPE_LEGACY_PACKAGED_APP);
+ EXPECT_EQ(
+ Feature::NOT_FOUND_IN_WHITELIST,
+ feature.IsAvailableToManifest(kIdBaz,
+ Manifest::TYPE_LEGACY_PACKAGED_APP,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+}
+
+TEST_F(SimpleFeatureTest, HashedIdWhitelist) {
+ // echo -n "fooabbbbccccddddeeeeffffgggghhhh" |
+ // sha1sum | tr '[:lower:]' '[:upper:]'
+ const std::string kIdFoo("fooabbbbccccddddeeeeffffgggghhhh");
+ const std::string kIdFooHashed("55BC7228A0D502A2A48C9BB16B07062A01E62897");
+ SimpleFeature feature;
+
+ feature.whitelist()->push_back(kIdFooHashed);
+
+ EXPECT_EQ(
+ Feature::IS_AVAILABLE,
+ feature.IsAvailableToManifest(kIdFoo,
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+ EXPECT_NE(
+ Feature::IS_AVAILABLE,
+ feature.IsAvailableToManifest(kIdFooHashed,
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+ EXPECT_EQ(
+ Feature::NOT_FOUND_IN_WHITELIST,
+ feature.IsAvailableToManifest("slightlytoooolongforanextensionid",
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+ EXPECT_EQ(
+ Feature::NOT_FOUND_IN_WHITELIST,
+ feature.IsAvailableToManifest("tooshortforanextensionid",
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+}
+
+TEST_F(SimpleFeatureTest, Blacklist) {
+ const std::string kIdFoo("fooabbbbccccddddeeeeffffgggghhhh");
+ const std::string kIdBar("barabbbbccccddddeeeeffffgggghhhh");
+ const std::string kIdBaz("bazabbbbccccddddeeeeffffgggghhhh");
+ SimpleFeature feature;
+ feature.blacklist()->push_back(kIdFoo);
+ feature.blacklist()->push_back(kIdBar);
+
+ EXPECT_EQ(
+ Feature::FOUND_IN_BLACKLIST,
+ feature.IsAvailableToManifest(kIdFoo,
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+ EXPECT_EQ(
+ Feature::FOUND_IN_BLACKLIST,
+ feature.IsAvailableToManifest(kIdBar,
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+
+ EXPECT_EQ(
+ Feature::IS_AVAILABLE,
+ feature.IsAvailableToManifest(kIdBaz,
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+ EXPECT_EQ(
+ Feature::IS_AVAILABLE,
+ feature.IsAvailableToManifest(std::string(),
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+}
+
+TEST_F(SimpleFeatureTest, HashedIdBlacklist) {
+ // echo -n "fooabbbbccccddddeeeeffffgggghhhh" |
+ // sha1sum | tr '[:lower:]' '[:upper:]'
+ const std::string kIdFoo("fooabbbbccccddddeeeeffffgggghhhh");
+ const std::string kIdFooHashed("55BC7228A0D502A2A48C9BB16B07062A01E62897");
+ SimpleFeature feature;
+
+ feature.blacklist()->push_back(kIdFooHashed);
+
+ EXPECT_EQ(
+ Feature::FOUND_IN_BLACKLIST,
+ feature.IsAvailableToManifest(kIdFoo,
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+ EXPECT_NE(
+ Feature::FOUND_IN_BLACKLIST,
+ feature.IsAvailableToManifest(kIdFooHashed,
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+ EXPECT_EQ(
+ Feature::IS_AVAILABLE,
+ feature.IsAvailableToManifest("slightlytoooolongforanextensionid",
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+ EXPECT_EQ(
+ Feature::IS_AVAILABLE,
+ feature.IsAvailableToManifest("tooshortforanextensionid",
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+}
+
+TEST_F(SimpleFeatureTest, PackageType) {
+ SimpleFeature feature;
+ feature.extension_types()->push_back(Manifest::TYPE_EXTENSION);
+ feature.extension_types()->push_back(Manifest::TYPE_LEGACY_PACKAGED_APP);
+
+ EXPECT_EQ(
+ Feature::IS_AVAILABLE,
+ feature.IsAvailableToManifest(std::string(),
+ Manifest::TYPE_EXTENSION,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+ EXPECT_EQ(
+ Feature::IS_AVAILABLE,
+ feature.IsAvailableToManifest(std::string(),
+ Manifest::TYPE_LEGACY_PACKAGED_APP,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+
+ EXPECT_EQ(
+ Feature::INVALID_TYPE,
+ feature.IsAvailableToManifest(std::string(),
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+ EXPECT_EQ(
+ Feature::INVALID_TYPE,
+ feature.IsAvailableToManifest(std::string(),
+ Manifest::TYPE_THEME,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+}
+
+TEST_F(SimpleFeatureTest, Context) {
+ SimpleFeature feature;
+ feature.set_name("somefeature");
+ feature.contexts()->push_back(Feature::BLESSED_EXTENSION_CONTEXT);
+ feature.extension_types()->push_back(Manifest::TYPE_LEGACY_PACKAGED_APP);
+ feature.platforms()->push_back(Feature::CHROMEOS_PLATFORM);
+ feature.set_min_manifest_version(21);
+ feature.set_max_manifest_version(25);
+
+ base::DictionaryValue manifest;
+ manifest.SetString("name", "test");
+ manifest.SetString("version", "1");
+ manifest.SetInteger("manifest_version", 21);
+ manifest.SetString("app.launch.local_path", "foo.html");
+
+ std::string error;
+ scoped_refptr<const Extension> extension(Extension::Create(
+ base::FilePath(), Manifest::INTERNAL, manifest, Extension::NO_FLAGS,
+ &error));
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(extension.get());
+
+ feature.whitelist()->push_back("monkey");
+ EXPECT_EQ(Feature::NOT_FOUND_IN_WHITELIST, feature.IsAvailableToContext(
+ extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+ Feature::CHROMEOS_PLATFORM).result());
+ feature.whitelist()->clear();
+
+ feature.extension_types()->clear();
+ feature.extension_types()->push_back(Manifest::TYPE_THEME);
+ {
+ Feature::Availability availability = feature.IsAvailableToContext(
+ extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+ Feature::CHROMEOS_PLATFORM);
+ EXPECT_EQ(Feature::INVALID_TYPE, availability.result());
+ EXPECT_EQ("'somefeature' is only allowed for themes, "
+ "but this is a legacy packaged app.",
+ availability.message());
+ }
+
+ feature.extension_types()->clear();
+ feature.extension_types()->push_back(Manifest::TYPE_LEGACY_PACKAGED_APP);
+ feature.contexts()->clear();
+ feature.contexts()->push_back(Feature::UNBLESSED_EXTENSION_CONTEXT);
+ feature.contexts()->push_back(Feature::CONTENT_SCRIPT_CONTEXT);
+ {
+ Feature::Availability availability = feature.IsAvailableToContext(
+ extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+ Feature::CHROMEOS_PLATFORM);
+ EXPECT_EQ(Feature::INVALID_CONTEXT, availability.result());
+ EXPECT_EQ("'somefeature' is only allowed to run in extension iframes and "
+ "content scripts, but this is a privileged page",
+ availability.message());
+ }
+
+ feature.contexts()->push_back(Feature::WEB_PAGE_CONTEXT);
+ {
+ Feature::Availability availability = feature.IsAvailableToContext(
+ extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+ Feature::CHROMEOS_PLATFORM);
+ EXPECT_EQ(Feature::INVALID_CONTEXT, availability.result());
+ EXPECT_EQ("'somefeature' is only allowed to run in extension iframes, "
+ "content scripts, and web pages, but this is a privileged page",
+ availability.message());
+ }
+
+ feature.contexts()->clear();
+ feature.contexts()->push_back(Feature::BLESSED_EXTENSION_CONTEXT);
+ feature.set_location(SimpleFeature::COMPONENT_LOCATION);
+ EXPECT_EQ(Feature::INVALID_LOCATION, feature.IsAvailableToContext(
+ extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+ Feature::CHROMEOS_PLATFORM).result());
+ feature.set_location(SimpleFeature::UNSPECIFIED_LOCATION);
+
+ EXPECT_EQ(Feature::INVALID_PLATFORM, feature.IsAvailableToContext(
+ extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+ Feature::UNSPECIFIED_PLATFORM).result());
+
+ feature.set_min_manifest_version(22);
+ EXPECT_EQ(Feature::INVALID_MIN_MANIFEST_VERSION, feature.IsAvailableToContext(
+ extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+ Feature::CHROMEOS_PLATFORM).result());
+ feature.set_min_manifest_version(21);
+
+ feature.set_max_manifest_version(18);
+ EXPECT_EQ(Feature::INVALID_MAX_MANIFEST_VERSION, feature.IsAvailableToContext(
+ extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+ Feature::CHROMEOS_PLATFORM).result());
+ feature.set_max_manifest_version(25);
+}
+
+TEST_F(SimpleFeatureTest, Location) {
+ // Component extensions can access any location.
+ EXPECT_TRUE(LocationIsAvailable(SimpleFeature::COMPONENT_LOCATION,
+ Manifest::COMPONENT));
+ EXPECT_TRUE(LocationIsAvailable(SimpleFeature::EXTERNAL_COMPONENT_LOCATION,
+ Manifest::COMPONENT));
+ EXPECT_TRUE(
+ LocationIsAvailable(SimpleFeature::POLICY_LOCATION, Manifest::COMPONENT));
+ EXPECT_TRUE(LocationIsAvailable(SimpleFeature::UNSPECIFIED_LOCATION,
+ Manifest::COMPONENT));
+
+ // Only component extensions can access the "component" location.
+ EXPECT_FALSE(LocationIsAvailable(SimpleFeature::COMPONENT_LOCATION,
+ Manifest::INVALID_LOCATION));
+ EXPECT_FALSE(LocationIsAvailable(SimpleFeature::COMPONENT_LOCATION,
+ Manifest::UNPACKED));
+ EXPECT_FALSE(LocationIsAvailable(SimpleFeature::COMPONENT_LOCATION,
+ Manifest::EXTERNAL_COMPONENT));
+ EXPECT_FALSE(LocationIsAvailable(SimpleFeature::COMPONENT_LOCATION,
+ Manifest::EXTERNAL_PREF_DOWNLOAD));
+ EXPECT_FALSE(LocationIsAvailable(SimpleFeature::COMPONENT_LOCATION,
+ Manifest::EXTERNAL_POLICY));
+ EXPECT_FALSE(LocationIsAvailable(SimpleFeature::COMPONENT_LOCATION,
+ Manifest::EXTERNAL_POLICY_DOWNLOAD));
+
+ // Policy extensions can access the "policy" location.
+ EXPECT_TRUE(LocationIsAvailable(SimpleFeature::POLICY_LOCATION,
+ Manifest::EXTERNAL_POLICY));
+ EXPECT_TRUE(LocationIsAvailable(SimpleFeature::POLICY_LOCATION,
+ Manifest::EXTERNAL_POLICY_DOWNLOAD));
+
+ // Non-policy (except component) extensions cannot access policy.
+ EXPECT_FALSE(LocationIsAvailable(SimpleFeature::POLICY_LOCATION,
+ Manifest::EXTERNAL_COMPONENT));
+ EXPECT_FALSE(LocationIsAvailable(SimpleFeature::POLICY_LOCATION,
+ Manifest::INVALID_LOCATION));
+ EXPECT_FALSE(
+ LocationIsAvailable(SimpleFeature::POLICY_LOCATION, Manifest::UNPACKED));
+ EXPECT_FALSE(LocationIsAvailable(SimpleFeature::POLICY_LOCATION,
+ Manifest::EXTERNAL_PREF_DOWNLOAD));
+
+ // External component extensions can access the "external_component"
+ // location.
+ EXPECT_TRUE(LocationIsAvailable(SimpleFeature::EXTERNAL_COMPONENT_LOCATION,
+ Manifest::EXTERNAL_COMPONENT));
+}
+
+TEST_F(SimpleFeatureTest, Platform) {
+ SimpleFeature feature;
+ feature.platforms()->push_back(Feature::CHROMEOS_PLATFORM);
+ EXPECT_EQ(Feature::IS_AVAILABLE,
+ feature.IsAvailableToManifest(std::string(),
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::CHROMEOS_PLATFORM).result());
+ EXPECT_EQ(
+ Feature::INVALID_PLATFORM,
+ feature.IsAvailableToManifest(std::string(),
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ -1,
+ Feature::UNSPECIFIED_PLATFORM).result());
+}
+
+TEST_F(SimpleFeatureTest, ManifestVersion) {
+ SimpleFeature feature;
+ feature.set_min_manifest_version(5);
+
+ EXPECT_EQ(
+ Feature::INVALID_MIN_MANIFEST_VERSION,
+ feature.IsAvailableToManifest(std::string(),
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ 0,
+ Feature::UNSPECIFIED_PLATFORM).result());
+ EXPECT_EQ(
+ Feature::INVALID_MIN_MANIFEST_VERSION,
+ feature.IsAvailableToManifest(std::string(),
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ 4,
+ Feature::UNSPECIFIED_PLATFORM).result());
+
+ EXPECT_EQ(
+ Feature::IS_AVAILABLE,
+ feature.IsAvailableToManifest(std::string(),
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ 5,
+ Feature::UNSPECIFIED_PLATFORM).result());
+ EXPECT_EQ(
+ Feature::IS_AVAILABLE,
+ feature.IsAvailableToManifest(std::string(),
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ 10,
+ Feature::UNSPECIFIED_PLATFORM).result());
+
+ feature.set_max_manifest_version(8);
+
+ EXPECT_EQ(
+ Feature::INVALID_MAX_MANIFEST_VERSION,
+ feature.IsAvailableToManifest(std::string(),
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ 10,
+ Feature::UNSPECIFIED_PLATFORM).result());
+ EXPECT_EQ(
+ Feature::IS_AVAILABLE,
+ feature.IsAvailableToManifest(std::string(),
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ 8,
+ Feature::UNSPECIFIED_PLATFORM).result());
+ EXPECT_EQ(
+ Feature::IS_AVAILABLE,
+ feature.IsAvailableToManifest(std::string(),
+ Manifest::TYPE_UNKNOWN,
+ Manifest::INVALID_LOCATION,
+ 7,
+ Feature::UNSPECIFIED_PLATFORM).result());
+}
+
+TEST_F(SimpleFeatureTest, ParseNull) {
+ scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
+ scoped_ptr<SimpleFeature> feature(new SimpleFeature());
+ feature->Parse(value.get());
+ EXPECT_TRUE(feature->whitelist()->empty());
+ EXPECT_TRUE(feature->extension_types()->empty());
+ EXPECT_TRUE(feature->contexts()->empty());
+ EXPECT_EQ(SimpleFeature::UNSPECIFIED_LOCATION, feature->location());
+ EXPECT_TRUE(feature->platforms()->empty());
+ EXPECT_EQ(0, feature->min_manifest_version());
+ EXPECT_EQ(0, feature->max_manifest_version());
+}
+
+TEST_F(SimpleFeatureTest, ParseWhitelist) {
+ scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
+ base::ListValue* whitelist = new base::ListValue();
+ whitelist->Append(new base::StringValue("foo"));
+ whitelist->Append(new base::StringValue("bar"));
+ value->Set("whitelist", whitelist);
+ scoped_ptr<SimpleFeature> feature(new SimpleFeature());
+ feature->Parse(value.get());
+ EXPECT_EQ(2u, feature->whitelist()->size());
+ EXPECT_TRUE(STLCount(*(feature->whitelist()), "foo"));
+ EXPECT_TRUE(STLCount(*(feature->whitelist()), "bar"));
+}
+
+TEST_F(SimpleFeatureTest, ParsePackageTypes) {
+ scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
+ base::ListValue* extension_types = new base::ListValue();
+ extension_types->Append(new base::StringValue("extension"));
+ extension_types->Append(new base::StringValue("theme"));
+ extension_types->Append(new base::StringValue("legacy_packaged_app"));
+ extension_types->Append(new base::StringValue("hosted_app"));
+ extension_types->Append(new base::StringValue("platform_app"));
+ extension_types->Append(new base::StringValue("shared_module"));
+ value->Set("extension_types", extension_types);
+ scoped_ptr<SimpleFeature> feature(new SimpleFeature());
+ feature->Parse(value.get());
+ EXPECT_EQ(6u, feature->extension_types()->size());
+ EXPECT_TRUE(
+ STLCount(*(feature->extension_types()), Manifest::TYPE_EXTENSION));
+ EXPECT_TRUE(
+ STLCount(*(feature->extension_types()), Manifest::TYPE_THEME));
+ EXPECT_TRUE(
+ STLCount(
+ *(feature->extension_types()), Manifest::TYPE_LEGACY_PACKAGED_APP));
+ EXPECT_TRUE(
+ STLCount(*(feature->extension_types()), Manifest::TYPE_HOSTED_APP));
+ EXPECT_TRUE(
+ STLCount(*(feature->extension_types()), Manifest::TYPE_PLATFORM_APP));
+ EXPECT_TRUE(
+ STLCount(*(feature->extension_types()), Manifest::TYPE_SHARED_MODULE));
+
+ value->SetString("extension_types", "all");
+ scoped_ptr<SimpleFeature> feature2(new SimpleFeature());
+ feature2->Parse(value.get());
+ EXPECT_EQ(*(feature->extension_types()), *(feature2->extension_types()));
+}
+
+TEST_F(SimpleFeatureTest, ParseContexts) {
+ scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
+ base::ListValue* contexts = new base::ListValue();
+ contexts->Append(new base::StringValue("blessed_extension"));
+ contexts->Append(new base::StringValue("unblessed_extension"));
+ contexts->Append(new base::StringValue("content_script"));
+ contexts->Append(new base::StringValue("web_page"));
+ contexts->Append(new base::StringValue("blessed_web_page"));
+ contexts->Append(new base::StringValue("webui"));
+ value->Set("contexts", contexts);
+ scoped_ptr<SimpleFeature> feature(new SimpleFeature());
+ feature->Parse(value.get());
+ EXPECT_EQ(6u, feature->contexts()->size());
+ EXPECT_TRUE(
+ STLCount(*(feature->contexts()), Feature::BLESSED_EXTENSION_CONTEXT));
+ EXPECT_TRUE(
+ STLCount(*(feature->contexts()), Feature::UNBLESSED_EXTENSION_CONTEXT));
+ EXPECT_TRUE(
+ STLCount(*(feature->contexts()), Feature::CONTENT_SCRIPT_CONTEXT));
+ EXPECT_TRUE(
+ STLCount(*(feature->contexts()), Feature::WEB_PAGE_CONTEXT));
+ EXPECT_TRUE(
+ STLCount(*(feature->contexts()), Feature::BLESSED_WEB_PAGE_CONTEXT));
+
+ value->SetString("contexts", "all");
+ scoped_ptr<SimpleFeature> feature2(new SimpleFeature());
+ feature2->Parse(value.get());
+ EXPECT_EQ(*(feature->contexts()), *(feature2->contexts()));
+}
+
+TEST_F(SimpleFeatureTest, ParseLocation) {
+ scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
+ value->SetString("location", "component");
+ scoped_ptr<SimpleFeature> feature(new SimpleFeature());
+ feature->Parse(value.get());
+ EXPECT_EQ(SimpleFeature::COMPONENT_LOCATION, feature->location());
+}
+
+TEST_F(SimpleFeatureTest, ParsePlatforms) {
+ scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
+ scoped_ptr<SimpleFeature> feature(new SimpleFeature());
+ base::ListValue* platforms = new base::ListValue();
+ value->Set("platforms", platforms);
+ feature->Parse(value.get());
+ EXPECT_TRUE(feature->platforms()->empty());
+
+ platforms->AppendString("chromeos");
+ feature->Parse(value.get());
+ EXPECT_FALSE(feature->platforms()->empty());
+ EXPECT_EQ(Feature::CHROMEOS_PLATFORM, *feature->platforms()->begin());
+
+ platforms->Clear();
+ platforms->AppendString("win");
+ feature->Parse(value.get());
+ EXPECT_FALSE(feature->platforms()->empty());
+ EXPECT_EQ(Feature::WIN_PLATFORM, *feature->platforms()->begin());
+
+ platforms->Clear();
+ platforms->AppendString("win");
+ platforms->AppendString("chromeos");
+ feature->Parse(value.get());
+ std::vector<Feature::Platform> expected_platforms;
+ expected_platforms.push_back(Feature::CHROMEOS_PLATFORM);
+ expected_platforms.push_back(Feature::WIN_PLATFORM);
+
+ EXPECT_FALSE(feature->platforms()->empty());
+ EXPECT_EQ(expected_platforms, *feature->platforms());
+}
+
+TEST_F(SimpleFeatureTest, ParseManifestVersion) {
+ scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
+ value->SetInteger("min_manifest_version", 1);
+ value->SetInteger("max_manifest_version", 5);
+ scoped_ptr<SimpleFeature> feature(new SimpleFeature());
+ feature->Parse(value.get());
+ EXPECT_EQ(1, feature->min_manifest_version());
+ EXPECT_EQ(5, feature->max_manifest_version());
+}
+
+TEST_F(SimpleFeatureTest, Inheritance) {
+ SimpleFeature feature;
+ feature.whitelist()->push_back("foo");
+ feature.extension_types()->push_back(Manifest::TYPE_THEME);
+ feature.contexts()->push_back(Feature::BLESSED_EXTENSION_CONTEXT);
+ feature.set_location(SimpleFeature::COMPONENT_LOCATION);
+ feature.platforms()->push_back(Feature::CHROMEOS_PLATFORM);
+ feature.set_min_manifest_version(1);
+ feature.set_max_manifest_version(2);
+
+ // Test additive parsing. Parsing an empty dictionary should result in no
+ // changes to a SimpleFeature.
+ base::DictionaryValue definition;
+ feature.Parse(&definition);
+ EXPECT_EQ(1u, feature.whitelist()->size());
+ EXPECT_EQ(1u, feature.extension_types()->size());
+ EXPECT_EQ(1u, feature.contexts()->size());
+ EXPECT_EQ(1, STLCount(*(feature.whitelist()), "foo"));
+ EXPECT_EQ(SimpleFeature::COMPONENT_LOCATION, feature.location());
+ EXPECT_EQ(1u, feature.platforms()->size());
+ EXPECT_EQ(1, STLCount(*(feature.platforms()), Feature::CHROMEOS_PLATFORM));
+ EXPECT_EQ(1, feature.min_manifest_version());
+ EXPECT_EQ(2, feature.max_manifest_version());
+
+ base::ListValue* whitelist = new base::ListValue();
+ base::ListValue* extension_types = new base::ListValue();
+ base::ListValue* contexts = new base::ListValue();
+ whitelist->Append(new base::StringValue("bar"));
+ extension_types->Append(new base::StringValue("extension"));
+ contexts->Append(new base::StringValue("unblessed_extension"));
+ definition.Set("whitelist", whitelist);
+ definition.Set("extension_types", extension_types);
+ definition.Set("contexts", contexts);
+ // Can't test location or platform because we only have one value so far.
+ definition.Set("min_manifest_version", new base::FundamentalValue(2));
+ definition.Set("max_manifest_version", new base::FundamentalValue(3));
+
+ feature.Parse(&definition);
+ EXPECT_EQ(1u, feature.whitelist()->size());
+ EXPECT_EQ(1u, feature.extension_types()->size());
+ EXPECT_EQ(1u, feature.contexts()->size());
+ EXPECT_EQ(1, STLCount(*(feature.whitelist()), "bar"));
+ EXPECT_EQ(1,
+ STLCount(*(feature.extension_types()), Manifest::TYPE_EXTENSION));
+ EXPECT_EQ(1,
+ STLCount(
+ *(feature.contexts()), Feature::UNBLESSED_EXTENSION_CONTEXT));
+ EXPECT_EQ(2, feature.min_manifest_version());
+ EXPECT_EQ(3, feature.max_manifest_version());
+}
+
+TEST_F(SimpleFeatureTest, CommandLineSwitch) {
+ SimpleFeature feature;
+ feature.set_command_line_switch("laser-beams");
+ {
+ EXPECT_EQ(Feature::MISSING_COMMAND_LINE_SWITCH,
+ feature.IsAvailableToEnvironment().result());
+ }
+ {
+ ScopedCommandLineSwitch scoped_switch("laser-beams");
+ EXPECT_EQ(Feature::MISSING_COMMAND_LINE_SWITCH,
+ feature.IsAvailableToEnvironment().result());
+ }
+ {
+ ScopedCommandLineSwitch scoped_switch("enable-laser-beams");
+ EXPECT_EQ(Feature::IS_AVAILABLE,
+ feature.IsAvailableToEnvironment().result());
+ }
+ {
+ ScopedCommandLineSwitch scoped_switch("disable-laser-beams");
+ EXPECT_EQ(Feature::MISSING_COMMAND_LINE_SWITCH,
+ feature.IsAvailableToEnvironment().result());
+ }
+ {
+ ScopedCommandLineSwitch scoped_switch("laser-beams=1");
+ EXPECT_EQ(Feature::IS_AVAILABLE,
+ feature.IsAvailableToEnvironment().result());
+ }
+ {
+ ScopedCommandLineSwitch scoped_switch("laser-beams=0");
+ EXPECT_EQ(Feature::MISSING_COMMAND_LINE_SWITCH,
+ feature.IsAvailableToEnvironment().result());
+ }
+}
+
+TEST_F(SimpleFeatureTest, IsIdInArray) {
+ EXPECT_FALSE(SimpleFeature::IsIdInArray("", {}, 0));
+ EXPECT_FALSE(SimpleFeature::IsIdInArray(
+ "bbbbccccdddddddddeeeeeeffffgghhh", {}, 0));
+
+ const char* const kIdArray[] = {
+ "bbbbccccdddddddddeeeeeeffffgghhh",
+ // aaaabbbbccccddddeeeeffffgggghhhh
+ "9A0417016F345C934A1A88F55CA17C05014EEEBA"
+ };
+ EXPECT_FALSE(SimpleFeature::IsIdInArray("", kIdArray, arraysize(kIdArray)));
+ EXPECT_FALSE(SimpleFeature::IsIdInArray(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", kIdArray, arraysize(kIdArray)));
+ EXPECT_TRUE(SimpleFeature::IsIdInArray(
+ "bbbbccccdddddddddeeeeeeffffgghhh", kIdArray, arraysize(kIdArray)));
+ EXPECT_TRUE(SimpleFeature::IsIdInArray(
+ "aaaabbbbccccddddeeeeffffgggghhhh", kIdArray, arraysize(kIdArray)));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/file_util.cc b/chromium/extensions/common/file_util.cc
new file mode 100644
index 00000000000..830cc480cf1
--- /dev/null
+++ b/chromium/extensions/common/file_util.cc
@@ -0,0 +1,629 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/file_util.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_icon_set.h"
+#include "extensions/common/extension_l10n_util.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/install_warning.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handler.h"
+#include "extensions/common/manifest_handlers/default_locale_handler.h"
+#include "extensions/common/manifest_handlers/icons_handler.h"
+#include "extensions/common/manifest_handlers/shared_module_info.h"
+#include "grit/extensions_strings.h"
+#include "net/base/escape.h"
+#include "net/base/filename_util.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
+
+namespace extensions {
+namespace file_util {
+namespace {
+
+enum SafeInstallationFlag {
+ DEFAULT, // Default case, controlled by a field trial.
+ DISABLED, // Safe installation is disabled.
+ ENABLED, // Safe installation is enabled.
+};
+SafeInstallationFlag g_use_safe_installation = DEFAULT;
+
+// Returns true if the given file path exists and is not zero-length.
+bool ValidateFilePath(const base::FilePath& path) {
+ int64_t size = 0;
+ return base::PathExists(path) && base::GetFileSize(path, &size) && size != 0;
+}
+
+// Returns true if the extension installation should flush all files and the
+// directory.
+bool UseSafeInstallation() {
+ if (g_use_safe_installation == DEFAULT) {
+ const char kFieldTrialName[] = "ExtensionUseSafeInstallation";
+ const char kEnable[] = "Enable";
+ return base::FieldTrialList::FindFullName(kFieldTrialName) == kEnable;
+ }
+
+ return g_use_safe_installation == ENABLED;
+}
+
+enum FlushOneOrAllFiles {
+ ONE_FILE_ONLY,
+ ALL_FILES
+};
+
+// Flush all files in a directory or just one. When flushing all files, it
+// makes sure every file is on disk. When flushing one file only, it ensures
+// all parent directories are on disk.
+void FlushFilesInDir(const base::FilePath& path,
+ FlushOneOrAllFiles one_or_all_files) {
+ if (!UseSafeInstallation()) {
+ return;
+ }
+ base::FileEnumerator temp_traversal(path,
+ true, // recursive
+ base::FileEnumerator::FILES);
+ for (base::FilePath current = temp_traversal.Next(); !current.empty();
+ current = temp_traversal.Next()) {
+ base::File currentFile(current,
+ base::File::FLAG_OPEN | base::File::FLAG_WRITE);
+ currentFile.Flush();
+ currentFile.Close();
+ if (one_or_all_files == ONE_FILE_ONLY) {
+ break;
+ }
+ }
+}
+
+} // namespace
+
+const base::FilePath::CharType kTempDirectoryName[] = FILE_PATH_LITERAL("Temp");
+
+void SetUseSafeInstallation(bool use_safe_installation) {
+ g_use_safe_installation = use_safe_installation ? ENABLED : DISABLED;
+}
+
+base::FilePath InstallExtension(const base::FilePath& unpacked_source_dir,
+ const std::string& id,
+ const std::string& version,
+ const base::FilePath& extensions_dir) {
+ base::FilePath extension_dir = extensions_dir.AppendASCII(id);
+ base::FilePath version_dir;
+
+ // Create the extension directory if it doesn't exist already.
+ if (!base::PathExists(extension_dir)) {
+ if (!base::CreateDirectory(extension_dir))
+ return base::FilePath();
+ }
+
+ // Get a temp directory on the same file system as the profile.
+ base::FilePath install_temp_dir = GetInstallTempDir(extensions_dir);
+ base::ScopedTempDir extension_temp_dir;
+ if (install_temp_dir.empty() ||
+ !extension_temp_dir.CreateUniqueTempDirUnderPath(install_temp_dir)) {
+ LOG(ERROR) << "Creating of temp dir under in the profile failed.";
+ return base::FilePath();
+ }
+ base::FilePath crx_temp_source =
+ extension_temp_dir.path().Append(unpacked_source_dir.BaseName());
+ if (!base::Move(unpacked_source_dir, crx_temp_source)) {
+ LOG(ERROR) << "Moving extension from : " << unpacked_source_dir.value()
+ << " to : " << crx_temp_source.value() << " failed.";
+ return base::FilePath();
+ }
+
+ // Try to find a free directory. There can be legitimate conflicts in the case
+ // of overinstallation of the same version.
+ const int kMaxAttempts = 100;
+ for (int i = 0; i < kMaxAttempts; ++i) {
+ base::FilePath candidate = extension_dir.AppendASCII(
+ base::StringPrintf("%s_%u", version.c_str(), i));
+ if (!base::PathExists(candidate)) {
+ version_dir = candidate;
+ break;
+ }
+ }
+
+ if (version_dir.empty()) {
+ LOG(ERROR) << "Could not find a home for extension " << id << " with "
+ << "version " << version << ".";
+ return base::FilePath();
+ }
+
+ base::TimeTicks start_time = base::TimeTicks::Now();
+
+ // Flush the source dir completely before moving to make sure everything is
+ // on disk. Otherwise a sudden power loss could cause the newly installed
+ // extension to be in a corrupted state. Note that empty sub-directories
+ // may still be lost.
+ FlushFilesInDir(crx_temp_source, ALL_FILES);
+
+ // The target version_dir does not exists yet, so base::Move() is using
+ // rename() on POSIX systems. It is atomic in the sense that it will
+ // either complete successfully or in the event of data loss be reverted.
+ if (!base::Move(crx_temp_source, version_dir)) {
+ LOG(ERROR) << "Installing extension from : " << crx_temp_source.value()
+ << " into : " << version_dir.value() << " failed.";
+ return base::FilePath();
+ }
+
+ // Flush one file in the new version_dir to make sure the dir move above is
+ // persisted on disk. This is guaranteed on POSIX systems. ExtensionPrefs
+ // is going to be updated with the new version_dir later. In the event of
+ // data loss ExtensionPrefs should be pointing to the previous version which
+ // is still fine.
+ FlushFilesInDir(version_dir, ONE_FILE_ONLY);
+
+ UMA_HISTOGRAM_TIMES("Extensions.FileInstallation",
+ base::TimeTicks::Now() - start_time);
+
+ return version_dir;
+}
+
+void UninstallExtension(const base::FilePath& extensions_dir,
+ const std::string& id) {
+ // We don't care about the return value. If this fails (and it can, due to
+ // plugins that aren't unloaded yet), it will get cleaned up by
+ // ExtensionGarbageCollector::GarbageCollectExtensions.
+ base::DeleteFile(extensions_dir.AppendASCII(id), true); // recursive.
+}
+
+scoped_refptr<Extension> LoadExtension(const base::FilePath& extension_path,
+ Manifest::Location location,
+ int flags,
+ std::string* error) {
+ return LoadExtension(extension_path, std::string(), location, flags, error);
+}
+
+scoped_refptr<Extension> LoadExtension(const base::FilePath& extension_path,
+ const std::string& extension_id,
+ Manifest::Location location,
+ int flags,
+ std::string* error) {
+ scoped_ptr<base::DictionaryValue> manifest =
+ LoadManifest(extension_path, error);
+ if (!manifest.get())
+ return NULL;
+ if (!extension_l10n_util::LocalizeExtension(
+ extension_path, manifest.get(), error)) {
+ return NULL;
+ }
+
+ scoped_refptr<Extension> extension(Extension::Create(
+ extension_path, location, *manifest, flags, extension_id, error));
+ if (!extension.get())
+ return NULL;
+
+ std::vector<InstallWarning> warnings;
+ if (!ValidateExtension(extension.get(), error, &warnings))
+ return NULL;
+ extension->AddInstallWarnings(warnings);
+
+ return extension;
+}
+
+scoped_ptr<base::DictionaryValue> LoadManifest(
+ const base::FilePath& extension_path,
+ std::string* error) {
+ return LoadManifest(extension_path, kManifestFilename, error);
+}
+
+scoped_ptr<base::DictionaryValue> LoadManifest(
+ const base::FilePath& extension_path,
+ const base::FilePath::CharType* manifest_filename,
+ std::string* error) {
+ base::FilePath manifest_path = extension_path.Append(manifest_filename);
+ if (!base::PathExists(manifest_path)) {
+ *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE);
+ return NULL;
+ }
+
+ JSONFileValueDeserializer deserializer(manifest_path);
+ scoped_ptr<base::Value> root(deserializer.Deserialize(NULL, error));
+ if (!root.get()) {
+ if (error->empty()) {
+ // If |error| is empty, than the file could not be read.
+ // It would be cleaner to have the JSON reader give a specific error
+ // in this case, but other code tests for a file error with
+ // error->empty(). For now, be consistent.
+ *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE);
+ } else {
+ *error = base::StringPrintf(
+ "%s %s", manifest_errors::kManifestParseError, error->c_str());
+ }
+ return NULL;
+ }
+
+ if (!root->IsType(base::Value::TYPE_DICTIONARY)) {
+ *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_INVALID);
+ return NULL;
+ }
+
+ return base::DictionaryValue::From(std::move(root));
+}
+
+bool ValidateExtension(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) {
+ // Ask registered manifest handlers to validate their paths.
+ if (!ManifestHandler::ValidateExtension(extension, error, warnings))
+ return false;
+
+ // Check children of extension root to see if any of them start with _ and is
+ // not on the reserved list. We only warn, and do not block the loading of the
+ // extension.
+ std::string warning;
+ if (!CheckForIllegalFilenames(extension->path(), &warning))
+ warnings->push_back(InstallWarning(warning));
+
+ // Check that the extension does not include any Windows reserved filenames.
+ std::string windows_reserved_warning;
+ if (!CheckForWindowsReservedFilenames(extension->path(),
+ &windows_reserved_warning)) {
+ warnings->push_back(InstallWarning(windows_reserved_warning));
+ }
+
+ // Check that extensions don't include private key files.
+ std::vector<base::FilePath> private_keys =
+ FindPrivateKeyFiles(extension->path());
+ if (extension->creation_flags() & Extension::ERROR_ON_PRIVATE_KEY) {
+ if (!private_keys.empty()) {
+ // Only print one of the private keys because l10n_util doesn't have a way
+ // to translate a list of strings.
+ *error =
+ l10n_util::GetStringFUTF8(IDS_EXTENSION_CONTAINS_PRIVATE_KEY,
+ private_keys.front().LossyDisplayName());
+ return false;
+ }
+ } else {
+ for (size_t i = 0; i < private_keys.size(); ++i) {
+ warnings->push_back(InstallWarning(
+ l10n_util::GetStringFUTF8(IDS_EXTENSION_CONTAINS_PRIVATE_KEY,
+ private_keys[i].LossyDisplayName())));
+ }
+ // Only warn; don't block loading the extension.
+ }
+ return true;
+}
+
+std::vector<base::FilePath> FindPrivateKeyFiles(
+ const base::FilePath& extension_dir) {
+ std::vector<base::FilePath> result;
+ // Pattern matching only works at the root level, so filter manually.
+ base::FileEnumerator traversal(
+ extension_dir, /*recursive=*/true, base::FileEnumerator::FILES);
+ for (base::FilePath current = traversal.Next(); !current.empty();
+ current = traversal.Next()) {
+ if (!current.MatchesExtension(kExtensionKeyFileExtension))
+ continue;
+
+ std::string key_contents;
+ if (!base::ReadFileToString(current, &key_contents)) {
+ // If we can't read the file, assume it's not a private key.
+ continue;
+ }
+ std::string key_bytes;
+ if (!Extension::ParsePEMKeyBytes(key_contents, &key_bytes)) {
+ // If we can't parse the key, assume it's ok too.
+ continue;
+ }
+
+ result.push_back(current);
+ }
+ return result;
+}
+
+bool CheckForIllegalFilenames(const base::FilePath& extension_path,
+ std::string* error) {
+ // Reserved underscore names.
+ static const base::FilePath::CharType* reserved_names[] = {
+ kLocaleFolder, kPlatformSpecificFolder, FILE_PATH_LITERAL("__MACOSX"), };
+ CR_DEFINE_STATIC_LOCAL(
+ std::set<base::FilePath::StringType>,
+ reserved_underscore_names,
+ (reserved_names, reserved_names + arraysize(reserved_names)));
+
+ // Enumerate all files and directories in the extension root.
+ // There is a problem when using pattern "_*" with FileEnumerator, so we have
+ // to cheat with find_first_of and match all.
+ const int kFilesAndDirectories =
+ base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES;
+ base::FileEnumerator all_files(extension_path, false, kFilesAndDirectories);
+
+ base::FilePath file;
+ while (!(file = all_files.Next()).empty()) {
+ base::FilePath::StringType filename = file.BaseName().value();
+
+ // Skip all that don't start with "_".
+ if (filename.find_first_of(FILE_PATH_LITERAL("_")) != 0)
+ continue;
+ if (reserved_underscore_names.find(filename) ==
+ reserved_underscore_names.end()) {
+ *error = base::StringPrintf(
+ "Cannot load extension with file or directory name %s. "
+ "Filenames starting with \"_\" are reserved for use by the system.",
+ file.BaseName().AsUTF8Unsafe().c_str());
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool CheckForWindowsReservedFilenames(const base::FilePath& extension_dir,
+ std::string* error) {
+ const int kFilesAndDirectories =
+ base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES;
+ base::FileEnumerator traversal(extension_dir, true, kFilesAndDirectories);
+
+ for (base::FilePath current = traversal.Next(); !current.empty();
+ current = traversal.Next()) {
+ base::FilePath::StringType filename = current.BaseName().value();
+ bool is_reserved_filename = net::IsReservedNameOnWindows(filename);
+ if (is_reserved_filename) {
+ *error = base::StringPrintf(
+ "Cannot load extension with file or directory name %s. "
+ "The filename is illegal.",
+ current.BaseName().AsUTF8Unsafe().c_str());
+ return false;
+ }
+ }
+
+ return true;
+}
+
+base::FilePath GetInstallTempDir(const base::FilePath& extensions_dir) {
+ // We do file IO in this function, but only when the current profile's
+ // Temp directory has never been used before, or in a rare error case.
+ // Developers are not likely to see these situations often, so do an
+ // explicit thread check.
+ base::ThreadRestrictions::AssertIOAllowed();
+
+ // Create the temp directory as a sub-directory of the Extensions directory.
+ // This guarantees it is on the same file system as the extension's eventual
+ // install target.
+ base::FilePath temp_path = extensions_dir.Append(kTempDirectoryName);
+ if (base::PathExists(temp_path)) {
+ if (!base::DirectoryExists(temp_path)) {
+ DLOG(WARNING) << "Not a directory: " << temp_path.value();
+ return base::FilePath();
+ }
+ if (!base::PathIsWritable(temp_path)) {
+ DLOG(WARNING) << "Can't write to path: " << temp_path.value();
+ return base::FilePath();
+ }
+ // This is a directory we can write to.
+ return temp_path;
+ }
+
+ // Directory doesn't exist, so create it.
+ if (!base::CreateDirectory(temp_path)) {
+ DLOG(WARNING) << "Couldn't create directory: " << temp_path.value();
+ return base::FilePath();
+ }
+ return temp_path;
+}
+
+void DeleteFile(const base::FilePath& path, bool recursive) {
+ base::DeleteFile(path, recursive);
+}
+
+base::FilePath ExtensionURLToRelativeFilePath(const GURL& url) {
+ std::string url_path = url.path();
+ if (url_path.empty() || url_path[0] != '/')
+ return base::FilePath();
+
+ // Drop the leading slashes and convert %-encoded UTF8 to regular UTF8.
+ std::string file_path = net::UnescapeURLComponent(
+ url_path,
+ net::UnescapeRule::SPACES |
+ net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS);
+ size_t skip = file_path.find_first_not_of("/\\");
+ if (skip != file_path.npos)
+ file_path = file_path.substr(skip);
+
+ base::FilePath path = base::FilePath::FromUTF8Unsafe(file_path);
+
+ // It's still possible for someone to construct an annoying URL whose path
+ // would still wind up not being considered relative at this point.
+ // For example: chrome-extension://id/c:////foo.html
+ if (path.IsAbsolute())
+ return base::FilePath();
+
+ return path;
+}
+
+base::FilePath ExtensionResourceURLToFilePath(const GURL& url,
+ const base::FilePath& root) {
+ std::string host = net::UnescapeURLComponent(
+ url.host(),
+ net::UnescapeRule::SPACES |
+ net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS);
+ if (host.empty())
+ return base::FilePath();
+
+ base::FilePath relative_path = ExtensionURLToRelativeFilePath(url);
+ if (relative_path.empty())
+ return base::FilePath();
+
+ base::FilePath path = root.AppendASCII(host).Append(relative_path);
+ if (!base::PathExists(path))
+ return base::FilePath();
+ path = base::MakeAbsoluteFilePath(path);
+ if (path.empty() || !root.IsParent(path))
+ return base::FilePath();
+ return path;
+}
+
+bool ValidateExtensionIconSet(const ExtensionIconSet& icon_set,
+ const Extension* extension,
+ int error_message_id,
+ std::string* error) {
+ for (ExtensionIconSet::IconMap::const_iterator iter = icon_set.map().begin();
+ iter != icon_set.map().end();
+ ++iter) {
+ const base::FilePath path =
+ extension->GetResource(iter->second).GetFilePath();
+ if (!ValidateFilePath(path)) {
+ *error = l10n_util::GetStringFUTF8(error_message_id,
+ base::UTF8ToUTF16(iter->second));
+ return false;
+ }
+ }
+ return true;
+}
+
+MessageBundle* LoadMessageBundle(
+ const base::FilePath& extension_path,
+ const std::string& default_locale,
+ std::string* error) {
+ error->clear();
+ // Load locale information if available.
+ base::FilePath locale_path = extension_path.Append(kLocaleFolder);
+ if (!base::PathExists(locale_path))
+ return NULL;
+
+ std::set<std::string> chrome_locales;
+ extension_l10n_util::GetAllLocales(&chrome_locales);
+
+ base::FilePath default_locale_path = locale_path.AppendASCII(default_locale);
+ if (default_locale.empty() ||
+ chrome_locales.find(default_locale) == chrome_locales.end() ||
+ !base::PathExists(default_locale_path)) {
+ *error = l10n_util::GetStringUTF8(
+ IDS_EXTENSION_LOCALES_NO_DEFAULT_LOCALE_SPECIFIED);
+ return NULL;
+ }
+
+ MessageBundle* message_bundle =
+ extension_l10n_util::LoadMessageCatalogs(
+ locale_path,
+ default_locale,
+ extension_l10n_util::CurrentLocaleOrDefault(),
+ error);
+
+ return message_bundle;
+}
+
+MessageBundle::SubstitutionMap* LoadMessageBundleSubstitutionMap(
+ const base::FilePath& extension_path,
+ const std::string& extension_id,
+ const std::string& default_locale) {
+ MessageBundle::SubstitutionMap* return_value =
+ new MessageBundle::SubstitutionMap();
+ if (!default_locale.empty()) {
+ // Touch disk only if extension is localized.
+ std::string error;
+ scoped_ptr<MessageBundle> bundle(
+ LoadMessageBundle(extension_path, default_locale, &error));
+
+ if (bundle.get())
+ *return_value = *bundle->dictionary();
+ }
+
+ // Add @@extension_id reserved message here, so it's available to
+ // non-localized extensions too.
+ return_value->insert(
+ std::make_pair(MessageBundle::kExtensionIdKey, extension_id));
+
+ return return_value;
+}
+
+MessageBundle::SubstitutionMap* LoadMessageBundleSubstitutionMapWithImports(
+ const std::string& extension_id,
+ const ExtensionSet& extension_set) {
+ const Extension* extension = extension_set.GetByID(extension_id);
+ MessageBundle::SubstitutionMap* return_value =
+ new MessageBundle::SubstitutionMap();
+
+ // Add @@extension_id reserved message here, so it's available to
+ // non-localized extensions too.
+ return_value->insert(
+ std::make_pair(MessageBundle::kExtensionIdKey, extension_id));
+
+ base::FilePath extension_path;
+ std::string default_locale;
+ if (!extension) {
+ NOTREACHED() << "Missing extension " << extension_id;
+ return return_value;
+ }
+
+ // Touch disk only if extension is localized.
+ default_locale = LocaleInfo::GetDefaultLocale(extension);
+ if (default_locale.empty()) {
+ return return_value;
+ }
+
+ std::string error;
+ scoped_ptr<MessageBundle> bundle(
+ LoadMessageBundle(extension->path(), default_locale, &error));
+
+ if (bundle.get()) {
+ for (auto iter : *bundle->dictionary()) {
+ return_value->insert(std::make_pair(iter.first, iter.second));
+ }
+ }
+
+ auto imports = extensions::SharedModuleInfo::GetImports(extension);
+ // Iterate through the imports in reverse. This will allow later imported
+ // modules to override earlier imported modules, as the list order is
+ // maintained from the definition in manifest.json of the imports.
+ for (auto it = imports.rbegin(); it != imports.rend(); ++it) {
+ const extensions::Extension* imported_extension =
+ extension_set.GetByID(it->extension_id);
+ if (!imported_extension) {
+ NOTREACHED() << "Missing shared module " << it->extension_id;
+ continue;
+ }
+ scoped_ptr<MessageBundle> imported_bundle(
+ LoadMessageBundle(imported_extension->path(), default_locale, &error));
+
+ if (imported_bundle.get()) {
+ for (auto iter : *imported_bundle->dictionary()) {
+ // |insert| only adds new entries, and does not replace entries in
+ // the main extension or previously processed imports.
+ return_value->insert(std::make_pair(iter.first, iter.second));
+ }
+ }
+ }
+
+ return return_value;
+}
+
+base::FilePath GetVerifiedContentsPath(const base::FilePath& extension_path) {
+ return extension_path.Append(kMetadataFolder)
+ .Append(kVerifiedContentsFilename);
+}
+base::FilePath GetComputedHashesPath(const base::FilePath& extension_path) {
+ return extension_path.Append(kMetadataFolder).Append(kComputedHashesFilename);
+}
+
+} // namespace file_util
+} // namespace extensions
diff --git a/chromium/extensions/common/file_util.h b/chromium/extensions/common/file_util.h
new file mode 100644
index 00000000000..3fda56ad8f0
--- /dev/null
+++ b/chromium/extensions/common/file_util.h
@@ -0,0 +1,157 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_FILE_UTIL_H_
+#define EXTENSIONS_COMMON_FILE_UTIL_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/message_bundle.h"
+
+class ExtensionIconSet;
+class GURL;
+
+namespace base {
+class FilePath;
+}
+
+namespace extensions {
+class Extension;
+class ExtensionSet;
+struct InstallWarning;
+
+// Utilities for manipulating the on-disk storage of extensions.
+namespace file_util {
+
+extern const base::FilePath::CharType kTempDirectoryName[];
+
+// Sets the flag to enable safe installation (i.e. flush all installed files).
+void SetUseSafeInstallation(bool use_safe_installation);
+
+// Copies |unpacked_source_dir| into the right location under |extensions_dir|.
+// The destination directory is returned on success, or empty path is returned
+// on failure.
+base::FilePath InstallExtension(const base::FilePath& unpacked_source_dir,
+ const std::string& id,
+ const std::string& version,
+ const base::FilePath& extensions_dir);
+
+// Removes all versions of the extension with |id| from |extensions_dir|.
+void UninstallExtension(const base::FilePath& extensions_dir,
+ const std::string& id);
+
+// Loads and validates an extension from the specified directory. Returns NULL
+// on failure, with a description of the error in |error|.
+scoped_refptr<Extension> LoadExtension(const base::FilePath& extension_root,
+ Manifest::Location location,
+ int flags,
+ std::string* error);
+
+// The same as LoadExtension except use the provided |extension_id|.
+scoped_refptr<Extension> LoadExtension(const base::FilePath& extension_root,
+ const std::string& extension_id,
+ Manifest::Location location,
+ int flags,
+ std::string* error);
+
+// Loads an extension manifest from the specified directory. Returns NULL
+// on failure, with a description of the error in |error|.
+scoped_ptr<base::DictionaryValue> LoadManifest(
+ const base::FilePath& extension_root,
+ std::string* error);
+
+// Convenience overload for specifying a manifest filename.
+scoped_ptr<base::DictionaryValue> LoadManifest(
+ const base::FilePath& extension_root,
+ const base::FilePath::CharType* manifest_filename,
+ std::string* error);
+
+// Returns true if the given extension object is valid and consistent.
+// May also append a series of warning messages to |warnings|, but they
+// should not prevent the extension from running.
+//
+// Otherwise, returns false, and a description of the error is
+// returned in |error|.
+bool ValidateExtension(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings);
+
+// Returns a list of files that contain private keys inside |extension_dir|.
+std::vector<base::FilePath> FindPrivateKeyFiles(
+ const base::FilePath& extension_dir);
+
+// We need to reserve the namespace of entries that start with "_" for future
+// use by Chrome.
+// If any files or directories are found using "_" prefix and are not on
+// reserved list we return false, and set error message.
+bool CheckForIllegalFilenames(const base::FilePath& extension_path,
+ std::string* error);
+
+// We need to reserve the names of special Windows filenames, such as
+// "com2.zip."
+// If any files or directories are found to be using a reserved Windows
+// filename, we return false, and set error message.
+bool CheckForWindowsReservedFilenames(const base::FilePath& extension_dir,
+ std::string* error);
+
+// Returns a path to a temporary directory for unpacking an extension that will
+// be installed into |extensions_dir|. Creates the directory if necessary.
+// The directory will be on the same file system as |extensions_dir| so
+// that the extension directory can be efficiently renamed into place. Returns
+// an empty file path on failure.
+base::FilePath GetInstallTempDir(const base::FilePath& extensions_dir);
+
+// Helper function to delete files. This is used to avoid ugly casts which
+// would be necessary with PostMessage since base::Delete is overloaded.
+// TODO(skerner): Make a version of Delete that is not overloaded in file_util.
+void DeleteFile(const base::FilePath& path, bool recursive);
+
+// Get a relative file path from a chrome-extension:// URL.
+base::FilePath ExtensionURLToRelativeFilePath(const GURL& url);
+
+// Get a full file path from a chrome-extension-resource:// URL, If the URL
+// points a file outside of root, this function will return empty FilePath.
+base::FilePath ExtensionResourceURLToFilePath(const GURL& url,
+ const base::FilePath& root);
+
+// Returns true if the icons in the icon set exist. Oherwise, populates
+// |error| with the |error_message_id| for an invalid file.
+bool ValidateExtensionIconSet(const ExtensionIconSet& icon_set,
+ const Extension* extension,
+ int error_message_id,
+ std::string* error);
+
+// Loads extension message catalogs and returns message bundle.
+// Returns NULL on error or if the extension is not localized.
+MessageBundle* LoadMessageBundle(const base::FilePath& extension_path,
+ const std::string& default_locale,
+ std::string* error);
+
+// Loads the extension message bundle substitution map. Contains at least
+// the extension_id item.
+MessageBundle::SubstitutionMap* LoadMessageBundleSubstitutionMap(
+ const base::FilePath& extension_path,
+ const std::string& extension_id,
+ const std::string& default_locale);
+
+// Loads the extension message bundle substitution map, including messages from
+// Shared Modules that the given extension imports. Contains at least the
+// extension_id item.
+MessageBundle::SubstitutionMap* LoadMessageBundleSubstitutionMapWithImports(
+ const std::string& extension_id,
+ const ExtensionSet& extension_set);
+
+// Helper functions for getting paths for files used in content verification.
+base::FilePath GetVerifiedContentsPath(const base::FilePath& extension_path);
+base::FilePath GetComputedHashesPath(const base::FilePath& extension_path);
+
+} // namespace file_util
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_FILE_UTIL_H_
diff --git a/chromium/extensions/common/file_util_unittest.cc b/chromium/extensions/common/file_util_unittest.cc
new file mode 100644
index 00000000000..e63856c3069
--- /dev/null
+++ b/chromium/extensions/common/file_util_unittest.cc
@@ -0,0 +1,552 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/file_util.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_paths.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+#include "grit/extensions_strings.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+namespace {
+
+scoped_refptr<Extension> LoadExtensionManifest(
+ const base::DictionaryValue& manifest,
+ const base::FilePath& manifest_dir,
+ Manifest::Location location,
+ int extra_flags,
+ std::string* error) {
+ scoped_refptr<Extension> extension =
+ Extension::Create(manifest_dir, location, manifest, extra_flags, error);
+ return extension;
+}
+
+scoped_refptr<Extension> LoadExtensionManifest(
+ const std::string& manifest_value,
+ const base::FilePath& manifest_dir,
+ Manifest::Location location,
+ int extra_flags,
+ std::string* error) {
+ JSONStringValueDeserializer deserializer(manifest_value);
+ scoped_ptr<base::Value> result = deserializer.Deserialize(NULL, error);
+ if (!result.get())
+ return NULL;
+ CHECK_EQ(base::Value::TYPE_DICTIONARY, result->GetType());
+ return LoadExtensionManifest(
+ *base::DictionaryValue::From(std::move(result)).get(), manifest_dir,
+ location, extra_flags, error);
+}
+
+} // namespace
+
+typedef testing::Test FileUtilTest;
+
+TEST_F(FileUtilTest, InstallUninstallGarbageCollect) {
+ base::ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ // Create a source extension.
+ std::string extension_id("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
+ std::string version("1.0");
+ base::FilePath src = temp.path().AppendASCII(extension_id);
+ ASSERT_TRUE(base::CreateDirectory(src));
+
+ base::FilePath extension_content;
+ base::CreateTemporaryFileInDir(src, &extension_content);
+ ASSERT_TRUE(base::PathExists(extension_content));
+
+ // Create a extensions tree.
+ base::FilePath all_extensions = temp.path().AppendASCII("extensions");
+ ASSERT_TRUE(base::CreateDirectory(all_extensions));
+
+ // Install in empty directory. Should create parent directories as needed.
+ base::FilePath version_1 =
+ file_util::InstallExtension(src, extension_id, version, all_extensions);
+ ASSERT_EQ(
+ version_1.value(),
+ all_extensions.AppendASCII(extension_id).AppendASCII("1.0_0").value());
+ ASSERT_TRUE(base::DirectoryExists(version_1));
+ ASSERT_TRUE(base::PathExists(version_1.Append(extension_content.BaseName())));
+
+ // Should have moved the source.
+ ASSERT_FALSE(base::DirectoryExists(src));
+
+ // Install again. Should create a new one with different name.
+ ASSERT_TRUE(base::CreateDirectory(src));
+ base::FilePath version_2 =
+ file_util::InstallExtension(src, extension_id, version, all_extensions);
+ ASSERT_EQ(
+ version_2.value(),
+ all_extensions.AppendASCII(extension_id).AppendASCII("1.0_1").value());
+ ASSERT_TRUE(base::DirectoryExists(version_2));
+
+ // Should have moved the source.
+ ASSERT_FALSE(base::DirectoryExists(src));
+
+ // Install yet again. Should create a new one with a different name.
+ ASSERT_TRUE(base::CreateDirectory(src));
+ base::FilePath version_3 =
+ file_util::InstallExtension(src, extension_id, version, all_extensions);
+ ASSERT_EQ(
+ version_3.value(),
+ all_extensions.AppendASCII(extension_id).AppendASCII("1.0_2").value());
+ ASSERT_TRUE(base::DirectoryExists(version_3));
+
+ // Uninstall. Should remove entire extension subtree.
+ file_util::UninstallExtension(all_extensions, extension_id);
+ ASSERT_FALSE(base::DirectoryExists(version_1.DirName()));
+ ASSERT_FALSE(base::DirectoryExists(version_2.DirName()));
+ ASSERT_FALSE(base::DirectoryExists(version_3.DirName()));
+ ASSERT_TRUE(base::DirectoryExists(all_extensions));
+}
+
+TEST_F(FileUtilTest, LoadExtensionWithValidLocales) {
+ base::FilePath install_dir;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &install_dir));
+ install_dir = install_dir.AppendASCII("extension_with_locales");
+
+ std::string error;
+ scoped_refptr<Extension> extension(file_util::LoadExtension(
+ install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
+ ASSERT_TRUE(extension.get() != NULL);
+ EXPECT_EQ("The first extension that I made.", extension->description());
+}
+
+TEST_F(FileUtilTest, LoadExtensionWithoutLocalesFolder) {
+ base::FilePath install_dir;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &install_dir));
+ install_dir = install_dir.AppendASCII("extension_without_locales");
+
+ std::string error;
+ scoped_refptr<Extension> extension(file_util::LoadExtension(
+ install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
+ ASSERT_FALSE(extension.get() == NULL);
+ EXPECT_TRUE(error.empty());
+}
+
+TEST_F(FileUtilTest, CheckIllegalFilenamesNoUnderscores) {
+ base::ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ base::FilePath src_path = temp.path().AppendASCII("some_dir");
+ ASSERT_TRUE(base::CreateDirectory(src_path));
+
+ std::string data = "{ \"name\": { \"message\": \"foobar\" } }";
+ ASSERT_TRUE(base::WriteFile(
+ src_path.AppendASCII("some_file.txt"), data.c_str(), data.length()));
+ std::string error;
+ EXPECT_TRUE(file_util::CheckForIllegalFilenames(temp.path(), &error));
+}
+
+TEST_F(FileUtilTest, CheckIllegalFilenamesOnlyReserved) {
+ base::ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ const base::FilePath::CharType* folders[] = {
+ extensions::kLocaleFolder, extensions::kPlatformSpecificFolder};
+
+ for (size_t i = 0; i < arraysize(folders); i++) {
+ base::FilePath src_path = temp.path().Append(folders[i]);
+ ASSERT_TRUE(base::CreateDirectory(src_path));
+ }
+
+ std::string error;
+ EXPECT_TRUE(file_util::CheckForIllegalFilenames(temp.path(), &error));
+}
+
+TEST_F(FileUtilTest, CheckIllegalFilenamesReservedAndIllegal) {
+ base::ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ base::FilePath src_path = temp.path().Append(extensions::kLocaleFolder);
+ ASSERT_TRUE(base::CreateDirectory(src_path));
+
+ src_path = temp.path().AppendASCII("_some_dir");
+ ASSERT_TRUE(base::CreateDirectory(src_path));
+
+ std::string error;
+ EXPECT_FALSE(file_util::CheckForIllegalFilenames(temp.path(), &error));
+}
+
+// These tests do not work on Windows, because it is illegal to create a
+// file/directory with a Windows reserved name. Because we cannot create a
+// file that will cause the test to fail, let's skip the test.
+#if !defined(OS_WIN)
+TEST_F(FileUtilTest, CheckIllegalFilenamesDirectoryWindowsReserved) {
+ base::ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ base::FilePath src_path = temp.path().AppendASCII("aux");
+ ASSERT_TRUE(base::CreateDirectory(src_path));
+
+ std::string error;
+ EXPECT_FALSE(
+ file_util::CheckForWindowsReservedFilenames(temp.path(), &error));
+}
+
+TEST_F(FileUtilTest,
+ CheckIllegalFilenamesWindowsReservedFilenameWithExtension) {
+ base::ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ base::FilePath src_path = temp.path().AppendASCII("some_dir");
+ ASSERT_TRUE(base::CreateDirectory(src_path));
+
+ std::string data = "{ \"name\": { \"message\": \"foobar\" } }";
+ ASSERT_TRUE(base::WriteFile(src_path.AppendASCII("lpt1.txt"), data.c_str(),
+ data.length()));
+
+ std::string error;
+ EXPECT_FALSE(
+ file_util::CheckForWindowsReservedFilenames(temp.path(), &error));
+}
+#endif
+
+TEST_F(FileUtilTest, LoadExtensionGivesHelpfullErrorOnMissingManifest) {
+ base::FilePath install_dir;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &install_dir));
+ install_dir =
+ install_dir.AppendASCII("file_util").AppendASCII("missing_manifest");
+
+ std::string error;
+ scoped_refptr<Extension> extension(file_util::LoadExtension(
+ install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
+ ASSERT_TRUE(extension.get() == NULL);
+ ASSERT_FALSE(error.empty());
+ ASSERT_STREQ("Manifest file is missing or unreadable.", error.c_str());
+}
+
+TEST_F(FileUtilTest, LoadExtensionGivesHelpfullErrorOnBadManifest) {
+ base::FilePath install_dir;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &install_dir));
+ install_dir =
+ install_dir.AppendASCII("file_util").AppendASCII("bad_manifest");
+
+ std::string error;
+ scoped_refptr<Extension> extension(file_util::LoadExtension(
+ install_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
+ ASSERT_TRUE(extension.get() == NULL);
+ ASSERT_FALSE(error.empty());
+ ASSERT_STREQ(
+ "Manifest is not valid JSON. "
+ "Line: 2, column: 16, Syntax error.",
+ error.c_str());
+}
+
+TEST_F(FileUtilTest, ValidateThemeUTF8) {
+ base::ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ // "aeo" with accents. Use http://0xcc.net/jsescape/ to decode them.
+ std::string non_ascii_file = "\xC3\xA0\xC3\xA8\xC3\xB2.png";
+ base::FilePath non_ascii_path =
+ temp.path().Append(base::FilePath::FromUTF8Unsafe(non_ascii_file));
+ base::WriteFile(non_ascii_path, "", 0);
+
+ std::string kManifest = base::StringPrintf(
+ "{ \"name\": \"Test\", \"version\": \"1.0\", "
+ " \"theme\": { \"images\": { \"theme_frame\": \"%s\" } }"
+ "}",
+ non_ascii_file.c_str());
+ std::string error;
+ scoped_refptr<Extension> extension = LoadExtensionManifest(
+ kManifest, temp.path(), Manifest::UNPACKED, 0, &error);
+ ASSERT_TRUE(extension.get()) << error;
+
+ std::vector<extensions::InstallWarning> warnings;
+ EXPECT_TRUE(file_util::ValidateExtension(extension.get(), &error, &warnings))
+ << error;
+ EXPECT_EQ(0U, warnings.size());
+}
+
+TEST_F(FileUtilTest, BackgroundScriptsMustExist) {
+ base::ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
+ value->SetString("name", "test");
+ value->SetString("version", "1");
+ value->SetInteger("manifest_version", 1);
+
+ base::ListValue* scripts = new base::ListValue();
+ scripts->Append(new base::StringValue("foo.js"));
+ value->Set("background.scripts", scripts);
+
+ std::string error;
+ std::vector<extensions::InstallWarning> warnings;
+ scoped_refptr<Extension> extension = LoadExtensionManifest(
+ *value.get(), temp.path(), Manifest::UNPACKED, 0, &error);
+ ASSERT_TRUE(extension.get()) << error;
+
+ EXPECT_FALSE(
+ file_util::ValidateExtension(extension.get(), &error, &warnings));
+ EXPECT_EQ(
+ l10n_util::GetStringFUTF8(IDS_EXTENSION_LOAD_BACKGROUND_SCRIPT_FAILED,
+ base::ASCIIToUTF16("foo.js")),
+ error);
+ EXPECT_EQ(0U, warnings.size());
+
+ scripts->Clear();
+ scripts->Append(new base::StringValue("http://google.com/foo.js"));
+
+ extension = LoadExtensionManifest(*value.get(), temp.path(),
+ Manifest::UNPACKED, 0, &error);
+ ASSERT_TRUE(extension.get()) << error;
+
+ warnings.clear();
+ EXPECT_FALSE(
+ file_util::ValidateExtension(extension.get(), &error, &warnings));
+ EXPECT_EQ(
+ l10n_util::GetStringFUTF8(IDS_EXTENSION_LOAD_BACKGROUND_SCRIPT_FAILED,
+ base::ASCIIToUTF16("http://google.com/foo.js")),
+ error);
+ EXPECT_EQ(0U, warnings.size());
+}
+
+// Private key, generated by Chrome specifically for this test, and
+// never used elsewhere.
+const char private_key[] =
+ "-----BEGIN PRIVATE KEY-----\n"
+ "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKt02SR0FYaYy6fpW\n"
+ "MAA+kU1BgK3d+OmmWfdr+JATIjhRkyeSF4lTd/71JQsyKqPzYkQPi3EeROWM+goTv\n"
+ "EhJqq07q63BolpsFmlV+S4ny+sBA2B4aWwRYXlBWikdrQSA0mJMzvEHc6nKzBgXik\n"
+ "QSVbyyBNAsxlDB9WaCxRVOpK3AgMBAAECgYBGvSPlrVtAOAQ2V8j9FqorKZA8SLPX\n"
+ "IeJC/yzU3RB2nPMjI17aMOvrUHxJUhzMeh4jwabVvSzzDtKFozPGupW3xaI8sQdi2\n"
+ "WWMTQIk/Q9HHDWoQ9qA6SwX2qWCc5SyjCKqVp78ye+000kqTJYjBsDgXeAlzKcx2B\n"
+ "4GAAeWonDdkQJBANNb8wrqNWFn7DqyQTfELzcRTRnqQ/r1pdeJo6obzbnwGnlqe3t\n"
+ "KhLjtJNIGrQg5iC0OVLWFuvPJs0t3z62A1ckCQQDPq2JZuwTwu5Pl4DJ0r9O1FdqN\n"
+ "JgqPZyMptokCDQ3khLLGakIu+TqB9YtrzI69rJMSG2Egb+6McaDX+dh3XmR/AkB9t\n"
+ "xJf6qDnmA2td/tMtTc0NOk8Qdg/fD8xbZ/YfYMnVoYYs9pQoilBaWRePDRNURMLYZ\n"
+ "vHAI0Llmw7tj7jv17pAkEAz44uXRpjRKtllUIvi5pUENAHwDz+HvdpGH68jpU3hmb\n"
+ "uOwrmnQYxaMReFV68Z2w9DcLZn07f7/R9Wn72z89CxwJAFsDoNaDes4h48bX7plct\n"
+ "s9ACjmTwcCigZjN2K7AGv7ntCLF3DnV5dK0dTHNaAdD3SbY3jl29Rk2CwiURSX6Ee\n"
+ "g==\n"
+ "-----END PRIVATE KEY-----\n";
+
+TEST_F(FileUtilTest, FindPrivateKeyFiles) {
+ base::ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ base::FilePath src_path = temp.path().AppendASCII("some_dir");
+ ASSERT_TRUE(base::CreateDirectory(src_path));
+
+ ASSERT_TRUE(base::WriteFile(
+ src_path.AppendASCII("a_key.pem"), private_key, arraysize(private_key)));
+ ASSERT_TRUE(base::WriteFile(src_path.AppendASCII("second_key.pem"),
+ private_key,
+ arraysize(private_key)));
+ // Shouldn't find a key with a different extension.
+ ASSERT_TRUE(base::WriteFile(src_path.AppendASCII("key.diff_ext"),
+ private_key,
+ arraysize(private_key)));
+ // Shouldn't find a key that isn't parsable.
+ ASSERT_TRUE(base::WriteFile(src_path.AppendASCII("unparsable_key.pem"),
+ private_key,
+ arraysize(private_key) - 30));
+ std::vector<base::FilePath> private_keys =
+ file_util::FindPrivateKeyFiles(temp.path());
+ EXPECT_EQ(2U, private_keys.size());
+ EXPECT_THAT(private_keys,
+ testing::Contains(src_path.AppendASCII("a_key.pem")));
+ EXPECT_THAT(private_keys,
+ testing::Contains(src_path.AppendASCII("second_key.pem")));
+}
+
+TEST_F(FileUtilTest, WarnOnPrivateKey) {
+ base::ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ base::FilePath ext_path = temp.path().AppendASCII("ext_root");
+ ASSERT_TRUE(base::CreateDirectory(ext_path));
+
+ const char manifest[] =
+ "{\n"
+ " \"name\": \"Test Extension\",\n"
+ " \"version\": \"1.0\",\n"
+ " \"manifest_version\": 2,\n"
+ " \"description\": \"The first extension that I made.\"\n"
+ "}\n";
+ ASSERT_TRUE(base::WriteFile(
+ ext_path.AppendASCII("manifest.json"), manifest, strlen(manifest)));
+ ASSERT_TRUE(base::WriteFile(
+ ext_path.AppendASCII("a_key.pem"), private_key, strlen(private_key)));
+
+ std::string error;
+ scoped_refptr<Extension> extension(
+ file_util::LoadExtension(ext_path,
+ "the_id",
+ Manifest::EXTERNAL_PREF,
+ Extension::NO_FLAGS,
+ &error));
+ ASSERT_TRUE(extension.get()) << error;
+ ASSERT_EQ(1u, extension->install_warnings().size());
+ EXPECT_THAT(extension->install_warnings(),
+ testing::ElementsAre(testing::Field(
+ &extensions::InstallWarning::message,
+ testing::ContainsRegex(
+ "extension includes the key file.*ext_root.a_key.pem"))));
+
+ // Turn the warning into an error with ERROR_ON_PRIVATE_KEY.
+ extension = file_util::LoadExtension(ext_path,
+ "the_id",
+ Manifest::EXTERNAL_PREF,
+ Extension::ERROR_ON_PRIVATE_KEY,
+ &error);
+ EXPECT_FALSE(extension.get());
+ EXPECT_THAT(error,
+ testing::ContainsRegex(
+ "extension includes the key file.*ext_root.a_key.pem"));
+}
+
+// Try to install an extension with a zero-length icon file.
+TEST_F(FileUtilTest, CheckZeroLengthAndMissingIconFile) {
+ base::FilePath install_dir;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &install_dir));
+
+ base::FilePath ext_dir =
+ install_dir.AppendASCII("file_util").AppendASCII("bad_icon");
+
+ std::string error;
+ scoped_refptr<Extension> extension(file_util::LoadExtension(
+ ext_dir, Manifest::INTERNAL, Extension::NO_FLAGS, &error));
+ ASSERT_FALSE(extension);
+}
+
+// Try to install an unpacked extension with a zero-length icon file.
+TEST_F(FileUtilTest, CheckZeroLengthAndMissingIconFileUnpacked) {
+ base::FilePath install_dir;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &install_dir));
+
+ base::FilePath ext_dir =
+ install_dir.AppendASCII("file_util").AppendASCII("bad_icon");
+
+ std::string error;
+ scoped_refptr<Extension> extension(file_util::LoadExtension(
+ ext_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
+ EXPECT_FALSE(extension);
+ EXPECT_EQ("Could not load extension icon 'missing-icon.png'.", error);
+}
+
+TEST_F(FileUtilTest, ExtensionURLToRelativeFilePath) {
+#define URL_PREFIX "chrome-extension://extension-id/"
+ struct TestCase {
+ const char* url;
+ const char* expected_relative_path;
+ } test_cases[] = {
+ {URL_PREFIX "simple.html", "simple.html"},
+ {URL_PREFIX "directory/to/file.html", "directory/to/file.html"},
+ {URL_PREFIX "escape%20spaces.html", "escape spaces.html"},
+ {URL_PREFIX "%C3%9Cber.html",
+ "\xC3\x9C"
+ "ber.html"},
+#if defined(OS_WIN)
+ {URL_PREFIX "C%3A/simple.html", ""},
+#endif
+ {URL_PREFIX "////simple.html", "simple.html"},
+ {URL_PREFIX "/simple.html", "simple.html"},
+ {URL_PREFIX "\\simple.html", "simple.html"},
+ {URL_PREFIX "\\\\foo\\simple.html", "foo/simple.html"},
+ {URL_PREFIX "..%2f..%2fsimple.html", "..%2f..%2fsimple.html"},
+ };
+#undef URL_PREFIX
+
+ for (size_t i = 0; i < arraysize(test_cases); ++i) {
+ GURL url(test_cases[i].url);
+ base::FilePath expected_path =
+ base::FilePath::FromUTF8Unsafe(test_cases[i].expected_relative_path);
+ base::FilePath actual_path =
+ extensions::file_util::ExtensionURLToRelativeFilePath(url);
+ EXPECT_FALSE(actual_path.IsAbsolute()) <<
+ " For the path " << actual_path.value();
+ EXPECT_EQ(expected_path.value(), actual_path.value()) <<
+ " For the path " << url;
+ }
+}
+
+TEST_F(FileUtilTest, ExtensionResourceURLToFilePath) {
+ // Setup filesystem for testing.
+ base::FilePath root_path;
+ ASSERT_TRUE(base::CreateNewTempDirectory(
+ base::FilePath::StringType(), &root_path));
+ root_path = base::MakeAbsoluteFilePath(root_path);
+ ASSERT_FALSE(root_path.empty());
+
+ base::FilePath api_path = root_path.Append(FILE_PATH_LITERAL("apiname"));
+ ASSERT_TRUE(base::CreateDirectory(api_path));
+
+ const char data[] = "Test Data";
+ base::FilePath resource_path = api_path.Append(FILE_PATH_LITERAL("test.js"));
+ ASSERT_TRUE(base::WriteFile(resource_path, data, sizeof(data)));
+ resource_path = api_path.Append(FILE_PATH_LITERAL("escape spaces.js"));
+ ASSERT_TRUE(base::WriteFile(resource_path, data, sizeof(data)));
+ resource_path = api_path.Append(FILE_PATH_LITERAL("escape spaces.js"));
+ ASSERT_TRUE(base::WriteFile(resource_path, data, sizeof(data)));
+ resource_path = api_path.Append(FILE_PATH_LITERAL("..%2f..%2fsimple.html"));
+ ASSERT_TRUE(base::WriteFile(resource_path, data, sizeof(data)));
+
+#ifdef FILE_PATH_USES_WIN_SEPARATORS
+#define SEP "\\"
+#else
+#define SEP "/"
+#endif
+#define URL_PREFIX "chrome-extension-resource://"
+ struct TestCase {
+ const char* url;
+ const base::FilePath::CharType* expected_path;
+ } test_cases[] = {
+ {URL_PREFIX "apiname/test.js", FILE_PATH_LITERAL("test.js")},
+ {URL_PREFIX "/apiname/test.js", FILE_PATH_LITERAL("test.js")},
+ // Test % escape
+ {URL_PREFIX "apiname/%74%65st.js", FILE_PATH_LITERAL("test.js")},
+ {URL_PREFIX "apiname/escape%20spaces.js",
+ FILE_PATH_LITERAL("escape spaces.js")},
+ // Test file does not exist.
+ {URL_PREFIX "apiname/directory/to/file.js", NULL},
+ // Test apiname/../../test.js
+ {URL_PREFIX "apiname/../../test.js", FILE_PATH_LITERAL("test.js")},
+ {URL_PREFIX "apiname/..%2F../test.js", NULL},
+ {URL_PREFIX "apiname/f/../../../test.js", FILE_PATH_LITERAL("test.js")},
+ {URL_PREFIX "apiname/f%2F..%2F..%2F../test.js", NULL},
+ {URL_PREFIX "apiname/..%2f..%2fsimple.html",
+ FILE_PATH_LITERAL("..%2f..%2fsimple.html")},
+ };
+#undef SEP
+#undef URL_PREFIX
+
+ for (size_t i = 0; i < arraysize(test_cases); ++i) {
+ GURL url(test_cases[i].url);
+ base::FilePath expected_path;
+ if (test_cases[i].expected_path)
+ expected_path = root_path.Append(FILE_PATH_LITERAL("apiname")).Append(
+ test_cases[i].expected_path);
+ base::FilePath actual_path =
+ extensions::file_util::ExtensionResourceURLToFilePath(url, root_path);
+ EXPECT_EQ(expected_path.value(), actual_path.value()) <<
+ " For the path " << url;
+ }
+ // Remove temp files.
+ ASSERT_TRUE(base::DeleteFile(root_path, true));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/guest_view/OWNERS b/chromium/extensions/common/guest_view/OWNERS
new file mode 100644
index 00000000000..1e6d75dcf89
--- /dev/null
+++ b/chromium/extensions/common/guest_view/OWNERS
@@ -0,0 +1,16 @@
+fsamuel@chromium.org
+lazyboy@chromium.org
+wjmaclean@chromium.org
+
+# For security review of IPC message files.
+per-file *_messages*.h=set noparent
+per-file *_messages*.h=dcheng@chromium.org
+per-file *_messages*.h=inferno@chromium.org
+per-file *_messages*.h=jln@chromium.org
+per-file *_messages*.h=jschuh@chromium.org
+per-file *_messages*.h=kenrb@chromium.org
+per-file *_messages*.h=mkwst@chromium.org
+per-file *_messages*.h=nasko@chromium.org
+per-file *_messages*.h=palmer@chromium.org
+per-file *_messages*.h=tsepez@chromium.org
+per-file *_messages*.h=wfh@chromium.org
diff --git a/chromium/extensions/common/guest_view/extensions_guest_view_messages.h b/chromium/extensions/common/guest_view/extensions_guest_view_messages.h
new file mode 100644
index 00000000000..61da7fcec0a
--- /dev/null
+++ b/chromium/extensions/common/guest_view/extensions_guest_view_messages.h
@@ -0,0 +1,47 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// IPC messages for extensions GuestViews.
+// Multiply-included message file, hence no include guard.
+
+#include <string>
+
+#include "ipc/ipc_message_macros.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/ipc/gfx_param_traits.h"
+
+#define IPC_MESSAGE_START ExtensionsGuestViewMsgStart
+// Messages sent from the browser to the renderer.
+
+// The ACK for GuestViewHostMsg_CreateMimeHandlerViewGuest.
+IPC_MESSAGE_CONTROL1(ExtensionsGuestViewMsg_CreateMimeHandlerViewGuestACK,
+ int /* element_instance_id */)
+
+// Once a MimeHandlerView guest's JavaScript onload function has been called,
+// this IPC is sent to the container to notify it.
+IPC_MESSAGE_CONTROL1(ExtensionsGuestViewMsg_MimeHandlerViewGuestOnLoadCompleted,
+ int /* element_instance_id */)
+
+// Messages sent from the renderer to the browser.
+
+// Queries whether the RenderView of the provided |routing_id| is allowed to
+// inject the script with the provided |script_id|.
+IPC_SYNC_MESSAGE_CONTROL2_1(
+ ExtensionsGuestViewHostMsg_CanExecuteContentScriptSync,
+ int /* routing_id */,
+ int /* script_id */,
+ bool /* allowed */)
+
+// Tells the browser to create a mime handler guest view for a plugin.
+IPC_MESSAGE_CONTROL4(ExtensionsGuestViewHostMsg_CreateMimeHandlerViewGuest,
+ int /* render_frame_id */,
+ std::string /* view_id */,
+ int /* element_instance_id */,
+ gfx::Size /* element_size */)
+
+// A renderer sends this message when it wants to resize a guest.
+IPC_MESSAGE_CONTROL3(ExtensionsGuestViewHostMsg_ResizeGuest,
+ int /* routing_id */,
+ int /* element_instance_id*/,
+ gfx::Size /* new_size */)
diff --git a/chromium/extensions/common/host_id.cc b/chromium/extensions/common/host_id.cc
new file mode 100644
index 00000000000..e7d474fefc5
--- /dev/null
+++ b/chromium/extensions/common/host_id.cc
@@ -0,0 +1,33 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/host_id.h"
+
+HostID::HostID()
+ : type_(HostType::EXTENSIONS) {
+}
+
+HostID::HostID(HostType type, const std::string& id)
+ : type_(type), id_(id) {
+}
+
+HostID::HostID(const HostID& host_id)
+ : type_(host_id.type()),
+ id_(host_id.id()) {
+}
+
+HostID::~HostID() {
+}
+
+bool HostID::operator<(const HostID& host_id) const {
+ if (type_ != host_id.type())
+ return type_ < host_id.type();
+ else if (id_ != host_id.id())
+ return id_ < host_id.id();
+ return false;
+}
+
+bool HostID::operator==(const HostID& host_id) const {
+ return type_ == host_id.type() && id_ == host_id.id();
+}
diff --git a/chromium/extensions/common/host_id.h b/chromium/extensions/common/host_id.h
new file mode 100644
index 00000000000..176ea49b6e5
--- /dev/null
+++ b/chromium/extensions/common/host_id.h
@@ -0,0 +1,35 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_HOST_ID_H_
+#define EXTENSIONS_COMMON_HOST_ID_H_
+
+#include <string>
+
+// IDs of hosts who own user scripts.
+// A HostID is immutable after creation.
+struct HostID {
+ enum HostType { EXTENSIONS, WEBUI, HOST_TYPE_LAST = WEBUI };
+
+ HostID();
+ HostID(HostType type, const std::string& id);
+ HostID(const HostID& host_id);
+ ~HostID();
+
+ bool operator<(const HostID& host_id) const;
+ bool operator==(const HostID& host_id) const;
+
+ HostType type() const { return type_; }
+ const std::string& id() const { return id_; }
+
+ private:
+ // The type of the host.
+ HostType type_;
+
+ // Similar to extension_id, host_id is a unique indentifier for a host,
+ // e.g., an Extension or WebUI.
+ std::string id_;
+};
+
+#endif // EXTENSIONS_COMMON_HOST_ID_H_
diff --git a/chromium/extensions/common/image_util.cc b/chromium/extensions/common/image_util.cc
new file mode 100644
index 00000000000..6a8e13a67de
--- /dev/null
+++ b/chromium/extensions/common/image_util.cc
@@ -0,0 +1,109 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/image_util.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <vector>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "third_party/re2/src/re2/re2.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/utils/SkParse.h"
+#include "ui/gfx/color_utils.h"
+
+namespace extensions {
+namespace image_util {
+
+bool ParseCssColorString(const std::string& color_string, SkColor* result) {
+ if (color_string.empty())
+ return false;
+ if (color_string[0] == '#')
+ return ParseHexColorString(color_string, result);
+ if (base::StartsWith(color_string, "hsl", base::CompareCase::SENSITIVE))
+ return ParseHslColorString(color_string, result);
+ if (base::StartsWith(color_string, "rgb", base::CompareCase::SENSITIVE)) {
+ NOTIMPLEMENTED();
+ return false;
+ }
+ if (SkParse::FindNamedColor(color_string.c_str(), color_string.size(),
+ result) != nullptr) {
+ return true;
+ }
+
+ return false;
+}
+
+bool ParseHexColorString(const std::string& color_string, SkColor* result) {
+ std::string formatted_color;
+ // Check the string for incorrect formatting.
+ if (color_string.empty() || color_string[0] != '#')
+ return false;
+
+ // Convert the string from #FFF format to #FFFFFF format.
+ if (color_string.length() == 4) {
+ for (size_t i = 1; i < 4; ++i) {
+ formatted_color += color_string[i];
+ formatted_color += color_string[i];
+ }
+ } else if (color_string.length() == 7) {
+ formatted_color = color_string.substr(1, 6);
+ } else {
+ return false;
+ }
+
+ // Convert the string to an integer and make sure it is in the correct value
+ // range.
+ std::vector<uint8_t> color_bytes;
+ if (!base::HexStringToBytes(formatted_color, &color_bytes))
+ return false;
+
+ *result = SkColorSetARGB(255, color_bytes[0], color_bytes[1], color_bytes[2]);
+ return true;
+}
+
+std::string GenerateHexColorString(SkColor color) {
+ return base::StringPrintf("#%02X%02X%02X", SkColorGetR(color),
+ SkColorGetG(color), SkColorGetB(color));
+}
+
+bool ParseHslColorString(const std::string& color_string, SkColor* result) {
+ // http://www.w3.org/wiki/CSS/Properties/color/HSL#The_format_of_the_HSL_Value
+ // The CSS3 specification defines the format of a HSL color as
+ // hsl(<number>, <percent>, <percent>)
+ // and with alpha, the format is
+ // hsla(<number>, <percent>, <percent>, <number>)
+ // e.g.: hsl(120, 100%, 50%), hsla(120, 100%, 50%, 0.5);
+ double hue = 0.0;
+ double saturation = 0.0;
+ double lightness = 0.0;
+ // 'hsl()' has '1' alpha value implicitly.
+ double alpha = 1.0;
+ if (!re2::RE2::FullMatch(color_string,
+ "hsl\\((-?[\\d.]+),\\s*([\\d.]+)%,\\s*([\\d.]+)%\\)",
+ &hue, &saturation, &lightness) &&
+ !re2::RE2::FullMatch(
+ color_string,
+ "hsla\\((-?[\\d.]+),\\s*([\\d.]+)%,\\s*([\\d.]+)%,\\s*([\\d.]+)\\)",
+ &hue, &saturation, &lightness, &alpha)) {
+ return false;
+ }
+
+ color_utils::HSL hsl;
+ // Normalize the value between 0.0 and 1.0.
+ hsl.h = (((static_cast<int>(hue) % 360) + 360) % 360) / 360.0;
+ hsl.s = std::max(0.0, std::min(100.0, saturation)) / 100.0;
+ hsl.l = std::max(0.0, std::min(100.0, lightness)) / 100.0;
+
+ SkAlpha sk_alpha = std::max(0.0, std::min(1.0, alpha)) * 255;
+
+ *result = color_utils::HSLToSkColor(hsl, sk_alpha);
+ return true;
+}
+
+} // namespace image_util
+} // namespace extensions
diff --git a/chromium/extensions/common/image_util.h b/chromium/extensions/common/image_util.h
new file mode 100644
index 00000000000..2efdafd0ad1
--- /dev/null
+++ b/chromium/extensions/common/image_util.h
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_IMAGE_UTIL_H_
+#define EXTENSIONS_COMMON_IMAGE_UTIL_H_
+
+#include <string>
+
+typedef unsigned int SkColor;
+
+// This file contains various utility functions for extension images and colors.
+namespace extensions {
+namespace image_util {
+
+// Parses a CSS-style color string from hex (3- or 6-digit) or HSL(A) format.
+// Returns true on success.
+bool ParseCssColorString(const std::string& color_string, SkColor* result);
+
+// Parses a string like #FF9982 or #EEE to a color. Returns true for success.
+bool ParseHexColorString(const std::string& color_string, SkColor* result);
+
+// Creates a string like #FF9982 from a color.
+std::string GenerateHexColorString(SkColor color);
+
+// Parses hsl() or hsla() string to a SkColor. Returns true for success.
+bool ParseHslColorString(const std::string& color_string, SkColor* result);
+
+} // namespace image_util
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_IMAGE_UTIL_H__
diff --git a/chromium/extensions/common/image_util_unittest.cc b/chromium/extensions/common/image_util_unittest.cc
new file mode 100644
index 00000000000..e4a81bb4192
--- /dev/null
+++ b/chromium/extensions/common/image_util_unittest.cc
@@ -0,0 +1,122 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "extensions/common/image_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/color_utils.h"
+
+namespace extensions {
+
+void RunPassHexTest(const std::string& css_string, SkColor expected_result) {
+ SkColor color = 0;
+ EXPECT_TRUE(image_util::ParseHexColorString(css_string, &color));
+ EXPECT_EQ(color, expected_result);
+}
+
+void RunFailHexTest(const std::string& css_string) {
+ SkColor color = 0;
+ EXPECT_FALSE(image_util::ParseHexColorString(css_string, &color));
+}
+
+void RunPassHslTest(const std::string& hsl_string, SkColor expected) {
+ SkColor color = 0;
+ EXPECT_TRUE(image_util::ParseHslColorString(hsl_string, &color));
+ EXPECT_EQ(color, expected);
+}
+
+void RunFailHslTest(const std::string& hsl_string) {
+ SkColor color = 0;
+ EXPECT_FALSE(image_util::ParseHslColorString(hsl_string, &color));
+}
+
+TEST(ImageUtilTest, ChangeBadgeBackgroundNormalCSS) {
+ RunPassHexTest("#34006A", SkColorSetARGB(0xFF, 0x34, 0, 0x6A));
+}
+
+TEST(ImageUtilTest, ChangeBadgeBackgroundShortCSS) {
+ RunPassHexTest("#A1E", SkColorSetARGB(0xFF, 0xAA, 0x11, 0xEE));
+}
+
+TEST(ImageUtilTest, ChangeBadgeBackgroundCSSNoHash) {
+ RunFailHexTest("11FF22");
+}
+
+TEST(ImageUtilTest, ChangeBadgeBackgroundCSSTooShort) {
+ RunFailHexTest("#FF22");
+}
+
+TEST(ImageUtilTest, ChangeBadgeBackgroundCSSTooLong) {
+ RunFailHexTest("#FF22128");
+}
+
+TEST(ImageUtilTest, ChangeBadgeBackgroundCSSInvalid) {
+ RunFailHexTest("#-22128");
+}
+
+TEST(ImageUtilTest, ChangeBadgeBackgroundCSSInvalidWithPlus) {
+ RunFailHexTest("#+22128");
+}
+
+TEST(ImageUtilTest, AcceptHsl) {
+ // Run basic color tests.
+ RunPassHslTest("hsl(0, 100%, 50%)", SK_ColorRED);
+ RunPassHslTest("hsl(120, 100%, 50%)", SK_ColorGREEN);
+ RunPassHslTest("hsl(240, 100%, 50%)", SK_ColorBLUE);
+ RunPassHslTest("hsl(180, 100%, 50%)", SK_ColorCYAN);
+
+ // Passing in >100% saturation should be equivalent to 100%.
+ RunPassHslTest("hsl(120, 200%, 50%)", SK_ColorGREEN);
+
+ // Passing in the same degree +/- full rotations should be equivalent.
+ RunPassHslTest("hsl(480, 100%, 50%)", SK_ColorGREEN);
+ RunPassHslTest("hsl(-240, 100%, 50%)", SK_ColorGREEN);
+
+ // We should be able to parse doubles
+ RunPassHslTest("hsl(120.0, 100.0%, 50.0%)", SK_ColorGREEN);
+}
+
+TEST(ImageUtilTest, InvalidHsl) {
+ RunFailHslTest("(0,100%,50%)");
+ RunFailHslTest("[0, 100, 50]");
+ RunFailHslTest("hs l(0,100%,50%)");
+ RunFailHslTest("rgb(0,100%,50%)");
+ RunFailHslTest("hsl(0,100%)");
+ RunFailHslTest("hsl(100%,50%)");
+ RunFailHslTest("hsl(120, 100, 50)");
+ RunFailHslTest("hsl[120, 100%, 50%]");
+ RunFailHslTest("hsl(120, 100%, 50%, 1.0)");
+ RunFailHslTest("hsla(120, 100%, 50%)");
+}
+
+TEST(ImageUtilTest, AcceptHsla) {
+ // Run basic color tests.
+ RunPassHslTest("hsla(0, 100%, 50%, 1.0)", SK_ColorRED);
+ RunPassHslTest("hsla(0, 100%, 50%, 0.0)",
+ SkColorSetARGB(0x00, 0xFF, 0x00, 0x00));
+ RunPassHslTest("hsla(0, 100%, 50%, 0.5)",
+ SkColorSetARGB(0x7F, 0xFF, 0x00, 0x00));
+ RunPassHslTest("hsla(0, 100%, 50%, 0.25)",
+ SkColorSetARGB(0x3F, 0xFF, 0x00, 0x00));
+ RunPassHslTest("hsla(0, 100%, 50%, 0.75)",
+ SkColorSetARGB(0xBF, 0xFF, 0x00, 0x00));
+
+ // We should able to parse integer alpha value.
+ RunPassHslTest("hsla(0, 100%, 50%, 1)", SK_ColorRED);
+}
+
+TEST(ImageUtilTest, BasicColorKeyword) {
+ SkColor color = 0;
+ EXPECT_TRUE(image_util::ParseCssColorString("red", &color));
+ EXPECT_EQ(color, SK_ColorRED);
+
+ EXPECT_TRUE(image_util::ParseCssColorString("blue", &color));
+ EXPECT_EQ(color, SK_ColorBLUE);
+
+ EXPECT_FALSE(image_util::ParseCssColorString("my_red", &color));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/install_warning.cc b/chromium/extensions/common/install_warning.cc
new file mode 100644
index 00000000000..3db5a5be077
--- /dev/null
+++ b/chromium/extensions/common/install_warning.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/install_warning.h"
+
+namespace extensions {
+
+InstallWarning::InstallWarning(const std::string& message) : message(message) {
+}
+
+InstallWarning::InstallWarning(const std::string& message,
+ const std::string& key)
+ : message(message), key(key) {
+}
+
+InstallWarning::InstallWarning(const std::string& message,
+ const std::string& key,
+ const std::string& specific)
+ : message(message), key(key), specific(specific) {
+}
+
+InstallWarning::~InstallWarning() {
+}
+
+void PrintTo(const InstallWarning& warning, ::std::ostream* os) {
+ // This is just for test error messages, so no need to escape '"'
+ // characters inside the message.
+ *os << "InstallWarning(\"" << warning.message << "\")";
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/install_warning.h b/chromium/extensions/common/install_warning.h
new file mode 100644
index 00000000000..ca79477ef03
--- /dev/null
+++ b/chromium/extensions/common/install_warning.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_INSTALL_WARNING_H_
+#define EXTENSIONS_COMMON_INSTALL_WARNING_H_
+
+#include <ostream>
+#include <string>
+
+namespace extensions {
+
+// A struct to describe a non-fatal issue discovered in the installation of an
+// extension.
+struct InstallWarning {
+ explicit InstallWarning(const std::string& message);
+ InstallWarning(const std::string& message,
+ const std::string& key);
+ InstallWarning(const std::string& message,
+ const std::string& key,
+ const std::string& specific);
+ ~InstallWarning();
+
+ bool operator==(const InstallWarning& other) const {
+ // We don't have to look at |key| or |specific| here, because they are each
+ // used in the the message itself.
+ // For example, a full message would be "Permission 'foo' is unknown or URL
+ // pattern is malformed." |key| here is "permissions", and |specific| is
+ // "foo", but these are redundant with the message.
+ return message == other.message;
+ }
+
+ // The warning's message (human-friendly).
+ std::string message;
+ // Optional - for specifying the incorrect key in the manifest (e.g.,
+ // "permissions").
+ std::string key;
+ // Optional - for specifying the incorrect portion of a key in the manifest
+ // (e.g., an unrecognized permission "foo" in "permissions").
+ std::string specific;
+};
+
+// Let gtest print InstallWarnings.
+void PrintTo(const InstallWarning&, ::std::ostream* os);
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_INSTALL_WARNING_H_
diff --git a/chromium/extensions/common/manifest.cc b/chromium/extensions/common/manifest.cc
new file mode 100644
index 00000000000..8bc2e28ea85
--- /dev/null
+++ b/chromium/extensions/common/manifest.cc
@@ -0,0 +1,258 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest.h"
+
+#include <utility>
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/features/feature_provider.h"
+#include "extensions/common/install_warning.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+
+namespace {
+
+// Rank extension locations in a way that allows
+// Manifest::GetHigherPriorityLocation() to compare locations.
+// An extension installed from two locations will have the location
+// with the higher rank, as returned by this function. The actual
+// integer values may change, and should never be persisted.
+int GetLocationRank(Manifest::Location location) {
+ const int kInvalidRank = -1;
+ int rank = kInvalidRank; // Will CHECK that rank is not kInvalidRank.
+
+ switch (location) {
+ // Component extensions can not be overriden by any other type.
+ case Manifest::COMPONENT:
+ rank = 9;
+ break;
+
+ case Manifest::EXTERNAL_COMPONENT:
+ rank = 8;
+ break;
+
+ // Policy controlled extensions may not be overridden by any type
+ // that is not part of chrome.
+ case Manifest::EXTERNAL_POLICY:
+ rank = 7;
+ break;
+
+ case Manifest::EXTERNAL_POLICY_DOWNLOAD:
+ rank = 6;
+ break;
+
+ // A developer-loaded extension should override any installed type
+ // that a user can disable. Anything specified on the command-line should
+ // override one loaded via the extensions UI.
+ case Manifest::COMMAND_LINE:
+ rank = 5;
+ break;
+
+ case Manifest::UNPACKED:
+ rank = 4;
+ break;
+
+ // The relative priority of various external sources is not important,
+ // but having some order ensures deterministic behavior.
+ case Manifest::EXTERNAL_REGISTRY:
+ rank = 3;
+ break;
+
+ case Manifest::EXTERNAL_PREF:
+ rank = 2;
+ break;
+
+ case Manifest::EXTERNAL_PREF_DOWNLOAD:
+ rank = 1;
+ break;
+
+ // User installed extensions are overridden by any external type.
+ case Manifest::INTERNAL:
+ rank = 0;
+ break;
+
+ default:
+ NOTREACHED() << "Need to add new extension location " << location;
+ }
+
+ CHECK(rank != kInvalidRank);
+ return rank;
+}
+
+} // namespace
+
+// static
+Manifest::Location Manifest::GetHigherPriorityLocation(
+ Location loc1, Location loc2) {
+ if (loc1 == loc2)
+ return loc1;
+
+ int loc1_rank = GetLocationRank(loc1);
+ int loc2_rank = GetLocationRank(loc2);
+
+ // If two different locations have the same rank, then we can not
+ // deterministicly choose a location.
+ CHECK(loc1_rank != loc2_rank);
+
+ // Highest rank has highest priority.
+ return (loc1_rank > loc2_rank ? loc1 : loc2 );
+}
+
+Manifest::Manifest(Location location, scoped_ptr<base::DictionaryValue> value)
+ : location_(location), value_(std::move(value)), type_(TYPE_UNKNOWN) {
+ if (value_->HasKey(keys::kTheme)) {
+ type_ = TYPE_THEME;
+ } else if (value_->HasKey(keys::kExport)) {
+ type_ = TYPE_SHARED_MODULE;
+ } else if (value_->HasKey(keys::kApp)) {
+ if (value_->Get(keys::kWebURLs, NULL) ||
+ value_->Get(keys::kLaunchWebURL, NULL)) {
+ type_ = TYPE_HOSTED_APP;
+ } else if (value_->Get(keys::kPlatformAppBackground, NULL)) {
+ type_ = TYPE_PLATFORM_APP;
+ } else {
+ type_ = TYPE_LEGACY_PACKAGED_APP;
+ }
+ } else {
+ type_ = TYPE_EXTENSION;
+ }
+ CHECK_NE(type_, TYPE_UNKNOWN);
+}
+
+Manifest::~Manifest() {
+}
+
+bool Manifest::ValidateManifest(
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const {
+ *error = "";
+
+ // Check every feature to see if its in the manifest. Note that this means
+ // we will ignore keys that are not features; we do this for forward
+ // compatibility.
+ // TODO(aa): Consider having an error here in the case of strict error
+ // checking to let developers know when they screw up.
+
+ const FeatureProvider* manifest_feature_provider =
+ FeatureProvider::GetManifestFeatures();
+ for (const auto& map_entry : manifest_feature_provider->GetAllFeatures()) {
+ // Use Get instead of HasKey because the former uses path expansion.
+ if (!value_->Get(map_entry.first, nullptr))
+ continue;
+
+ Feature::Availability result = map_entry.second->IsAvailableToManifest(
+ extension_id_, type_, location_, GetManifestVersion());
+ if (!result.is_available())
+ warnings->push_back(InstallWarning(result.message(), map_entry.first));
+ }
+
+ // Also generate warnings for keys that are not features.
+ for (base::DictionaryValue::Iterator it(*value_); !it.IsAtEnd();
+ it.Advance()) {
+ if (!manifest_feature_provider->GetFeature(it.key())) {
+ warnings->push_back(InstallWarning(
+ ErrorUtils::FormatErrorMessage(
+ manifest_errors::kUnrecognizedManifestKey, it.key()),
+ it.key()));
+ }
+ }
+ return true;
+}
+
+bool Manifest::HasKey(const std::string& key) const {
+ return CanAccessKey(key) && value_->HasKey(key);
+}
+
+bool Manifest::HasPath(const std::string& path) const {
+ base::Value* ignored = NULL;
+ return CanAccessPath(path) && value_->Get(path, &ignored);
+}
+
+bool Manifest::Get(
+ const std::string& path, const base::Value** out_value) const {
+ return CanAccessPath(path) && value_->Get(path, out_value);
+}
+
+bool Manifest::GetBoolean(
+ const std::string& path, bool* out_value) const {
+ return CanAccessPath(path) && value_->GetBoolean(path, out_value);
+}
+
+bool Manifest::GetInteger(
+ const std::string& path, int* out_value) const {
+ return CanAccessPath(path) && value_->GetInteger(path, out_value);
+}
+
+bool Manifest::GetString(
+ const std::string& path, std::string* out_value) const {
+ return CanAccessPath(path) && value_->GetString(path, out_value);
+}
+
+bool Manifest::GetString(
+ const std::string& path, base::string16* out_value) const {
+ return CanAccessPath(path) && value_->GetString(path, out_value);
+}
+
+bool Manifest::GetDictionary(
+ const std::string& path, const base::DictionaryValue** out_value) const {
+ return CanAccessPath(path) && value_->GetDictionary(path, out_value);
+}
+
+bool Manifest::GetList(
+ const std::string& path, const base::ListValue** out_value) const {
+ return CanAccessPath(path) && value_->GetList(path, out_value);
+}
+
+Manifest* Manifest::DeepCopy() const {
+ Manifest* manifest = new Manifest(
+ location_, scoped_ptr<base::DictionaryValue>(value_->DeepCopy()));
+ manifest->set_extension_id(extension_id_);
+ return manifest;
+}
+
+bool Manifest::Equals(const Manifest* other) const {
+ return other && value_->Equals(other->value());
+}
+
+int Manifest::GetManifestVersion() const {
+ // Platform apps were launched after manifest version 2 was the preferred
+ // version, so they default to that.
+ int manifest_version = type_ == TYPE_PLATFORM_APP ? 2 : 1;
+ value_->GetInteger(keys::kManifestVersion, &manifest_version);
+ return manifest_version;
+}
+
+bool Manifest::CanAccessPath(const std::string& path) const {
+ std::string key;
+ for (const base::StringPiece& component : base::SplitStringPiece(
+ path, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+ component.AppendToString(&key);
+ if (!CanAccessKey(key))
+ return false;
+ key += '.';
+ }
+ return true;
+}
+
+bool Manifest::CanAccessKey(const std::string& key) const {
+ Feature* feature = FeatureProvider::GetManifestFeatures()->GetFeature(key);
+ if (!feature)
+ return true;
+
+ return feature->IsAvailableToManifest(
+ extension_id_, type_, location_, GetManifestVersion())
+ .is_available();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest.h b/chromium/extensions/common/manifest.h
new file mode 100644
index 00000000000..4ae38d3e247
--- /dev/null
+++ b/chromium/extensions/common/manifest.h
@@ -0,0 +1,203 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_H_
+#define EXTENSIONS_COMMON_MANIFEST_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "base/values.h"
+
+namespace extensions {
+struct InstallWarning;
+
+// Wraps the DictionaryValue form of extension's manifest. Enforces access to
+// properties of the manifest using ManifestFeatureProvider.
+class Manifest {
+ public:
+ // What an extension was loaded from.
+ // NOTE: These values are stored as integers in the preferences and used
+ // in histograms so don't remove or reorder existing items. Just append
+ // to the end.
+ enum Location {
+ INVALID_LOCATION,
+ INTERNAL, // A crx file from the internal Extensions directory.
+ EXTERNAL_PREF, // A crx file from an external directory (via prefs).
+ EXTERNAL_REGISTRY, // A crx file from an external directory (via eg the
+ // registry on Windows).
+ UNPACKED, // From loading an unpacked extension from the
+ // extensions settings page.
+ COMPONENT, // An integral component of Chrome itself, which
+ // happens to be implemented as an extension. We don't
+ // show these in the management UI.
+ EXTERNAL_PREF_DOWNLOAD, // A crx file from an external directory (via
+ // prefs), installed from an update URL.
+ EXTERNAL_POLICY_DOWNLOAD, // A crx file from an external directory (via
+ // admin policies), installed from an update URL.
+ COMMAND_LINE, // --load-extension.
+ EXTERNAL_POLICY, // A crx file from an external directory (via admin
+ // policies), cached locally and installed from the
+ // cache.
+ EXTERNAL_COMPONENT, // Similar to COMPONENT in that it's considered an
+ // internal implementation detail of chrome, but
+ // installed from an update URL like the *DOWNLOAD ones.
+
+ // New enum values must go above here.
+ NUM_LOCATIONS
+ };
+
+ // Do not change the order of entries or remove entries in this list
+ // as this is used in UMA_HISTOGRAM_ENUMERATIONs about extensions.
+ enum Type {
+ TYPE_UNKNOWN = 0,
+ TYPE_EXTENSION,
+ TYPE_THEME,
+ TYPE_USER_SCRIPT,
+ TYPE_HOSTED_APP,
+ // This is marked legacy because platform apps are preferred. For
+ // backwards compatibility, we can't remove support for packaged apps
+ TYPE_LEGACY_PACKAGED_APP,
+ TYPE_PLATFORM_APP,
+ TYPE_SHARED_MODULE,
+
+ // New enum values must go above here.
+ NUM_LOAD_TYPES
+ };
+
+ // Given two install sources, return the one which should take priority
+ // over the other. If an extension is installed from two sources A and B,
+ // its install source should be set to GetHigherPriorityLocation(A, B).
+ static Location GetHigherPriorityLocation(Location loc1, Location loc2);
+
+ // Whether the |location| is external or not.
+ static inline bool IsExternalLocation(Location location) {
+ return location == EXTERNAL_PREF ||
+ location == EXTERNAL_REGISTRY ||
+ location == EXTERNAL_PREF_DOWNLOAD ||
+ location == EXTERNAL_POLICY ||
+ location == EXTERNAL_POLICY_DOWNLOAD ||
+ location == EXTERNAL_COMPONENT;
+ }
+
+ // Whether the |location| is unpacked (no CRX) or not.
+ static inline bool IsUnpackedLocation(Location location) {
+ return location == UNPACKED || location == COMMAND_LINE;
+ }
+
+ // Whether extensions with |location| are auto-updatable or not.
+ static inline bool IsAutoUpdateableLocation(Location location) {
+ // Only internal and external extensions can be autoupdated.
+ return location == INTERNAL ||
+ IsExternalLocation(location);
+ }
+
+ // Whether the |location| is a source of extensions force-installed through
+ // policy.
+ static inline bool IsPolicyLocation(Location location) {
+ return location == EXTERNAL_POLICY ||
+ location == EXTERNAL_POLICY_DOWNLOAD;
+ }
+
+ // Whether the |location| is an extension intended to be an internal part of
+ // Chrome.
+ static inline bool IsComponentLocation(Location location) {
+ return location == COMPONENT || location == EXTERNAL_COMPONENT;
+ }
+
+ // Unpacked extensions start off with file access since they are a developer
+ // feature.
+ static inline bool ShouldAlwaysAllowFileAccess(Location location) {
+ return IsUnpackedLocation(location);
+ }
+
+ Manifest(Location location, scoped_ptr<base::DictionaryValue> value);
+ virtual ~Manifest();
+
+ const std::string& extension_id() const { return extension_id_; }
+ void set_extension_id(const std::string& id) { extension_id_ = id; }
+
+ Location location() const { return location_; }
+
+ // Returns false and |error| will be non-empty if the manifest is malformed.
+ // |warnings| will be populated if there are keys in the manifest that cannot
+ // be specified by the extension type.
+ bool ValidateManifest(std::string* error,
+ std::vector<InstallWarning>* warnings) const;
+
+ // The version of this extension's manifest. We increase the manifest
+ // version when making breaking changes to the extension system. If the
+ // manifest contains no explicit manifest version, this returns the current
+ // system default.
+ int GetManifestVersion() const;
+
+ // Returns the manifest type.
+ Type type() const { return type_; }
+
+ bool is_theme() const { return type_ == TYPE_THEME; }
+ bool is_app() const {
+ return is_legacy_packaged_app() || is_hosted_app() || is_platform_app();
+ }
+ bool is_platform_app() const { return type_ == TYPE_PLATFORM_APP; }
+ bool is_hosted_app() const { return type_ == TYPE_HOSTED_APP; }
+ bool is_legacy_packaged_app() const {
+ return type_ == TYPE_LEGACY_PACKAGED_APP;
+ }
+ bool is_extension() const { return type_ == TYPE_EXTENSION; }
+ bool is_shared_module() const { return type_ == TYPE_SHARED_MODULE; }
+
+ // These access the wrapped manifest value, returning false when the property
+ // does not exist or if the manifest type can't access it.
+ bool HasKey(const std::string& key) const;
+ bool HasPath(const std::string& path) const;
+ bool Get(const std::string& path, const base::Value** out_value) const;
+ bool GetBoolean(const std::string& path, bool* out_value) const;
+ bool GetInteger(const std::string& path, int* out_value) const;
+ bool GetString(const std::string& path, std::string* out_value) const;
+ bool GetString(const std::string& path, base::string16* out_value) const;
+ bool GetDictionary(const std::string& path,
+ const base::DictionaryValue** out_value) const;
+ bool GetList(const std::string& path,
+ const base::ListValue** out_value) const;
+
+ // Returns a new Manifest equal to this one, passing ownership to
+ // the caller.
+ Manifest* DeepCopy() const;
+
+ // Returns true if this equals the |other| manifest.
+ bool Equals(const Manifest* other) const;
+
+ // Gets the underlying DictionaryValue representing the manifest.
+ // Note: only use this when you KNOW you don't need the validation.
+ const base::DictionaryValue* value() const { return value_.get(); }
+
+ private:
+ // Returns true if the extension can specify the given |path|.
+ bool CanAccessPath(const std::string& path) const;
+ bool CanAccessKey(const std::string& key) const;
+
+ // A persistent, globally unique ID. An extension's ID is used in things
+ // like directory structures and URLs, and is expected to not change across
+ // versions. It is generated as a SHA-256 hash of the extension's public
+ // key, or as a hash of the path in the case of unpacked extensions.
+ std::string extension_id_;
+
+ // The location the extension was loaded from.
+ Location location_;
+
+ // The underlying dictionary representation of the manifest.
+ scoped_ptr<base::DictionaryValue> value_;
+
+ Type type_;
+
+ DISALLOW_COPY_AND_ASSIGN(Manifest);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_H_
diff --git a/chromium/extensions/common/manifest_constants.cc b/chromium/extensions/common/manifest_constants.cc
new file mode 100644
index 00000000000..51b5570889c
--- /dev/null
+++ b/chromium/extensions/common/manifest_constants.cc
@@ -0,0 +1,750 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "build/build_config.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+namespace manifest_keys {
+
+const char kAboutPage[] = "about_page";
+const char kAllFrames[] = "all_frames";
+const char kAltKey[] = "altKey";
+const char kApp[] = "app";
+const char kAppIconColor[] = "app.icon_color";
+const char kAutomation[] = "automation";
+const char kBackgroundAllowJsAccess[] = "background.allow_js_access";
+const char kBackgroundPage[] = "background.page";
+const char kBackgroundPageLegacy[] = "background_page";
+const char kBackgroundPersistent[] = "background.persistent";
+const char kBackgroundScripts[] = "background.scripts";
+const char kBluetooth[] = "bluetooth";
+const char kBookmarkUI[] = "bookmarks_ui";
+const char kBrowserAction[] = "browser_action";
+const char kChromeURLOverrides[] = "chrome_url_overrides";
+const char kCommands[] = "commands";
+const char kContentCapabilities[] = "content_capabilities";
+const char kContentScripts[] = "content_scripts";
+const char kContentSecurityPolicy[] = "content_security_policy";
+const char kConvertedFromUserScript[] = "converted_from_user_script";
+const char kCopresence[] = "copresence";
+const char kCss[] = "css";
+const char kCtrlKey[] = "ctrlKey";
+const char kCurrentLocale[] = "current_locale";
+const char kDefaultLocale[] = "default_locale";
+const char kDescription[] = "description";
+const char kDevToolsPage[] = "devtools_page";
+const char kDisplayInLauncher[] = "display_in_launcher";
+const char kDisplayInNewTabPage[] = "display_in_new_tab_page";
+const char kEventName[] = "event_name";
+const char kExcludeGlobs[] = "exclude_globs";
+const char kExcludeMatches[] = "exclude_matches";
+const char kExport[] = "export";
+const char kExternallyConnectable[] = "externally_connectable";
+const char kEventRules[] = "event_rules";
+const char kFileAccessList[] = "file_access";
+const char kFileFilters[] = "file_filters";
+const char kFileBrowserHandlers[] = "file_browser_handlers";
+const char kFileHandlers[] = "file_handlers";
+const char kFileHandlerExtensions[] = "extensions";
+const char kFileHandlerTypes[] = "types";
+const char kGlobal[] = "global";
+const char kHideBookmarkButton[] = "hide_bookmark_button";
+const char kHomepageURL[] = "homepage_url";
+const char kIcons[] = "icons";
+const char kId[] = "id";
+const char kImeOptionsPage[] = "options_page";
+const char kImport[] = "import";
+const char kIncognito[] = "incognito";
+const char kIncludeGlobs[] = "include_globs";
+const char kIndicator[] = "indicator";
+const char kInputComponents[] = "input_components";
+const char kInputView[] = "input_view";
+const char kIsolation[] = "app.isolation";
+const char kJs[] = "js";
+const char kKey[] = "key";
+const char kKeycode[] = "keyCode";
+const char kKiosk[] = "kiosk";
+const char kKioskEnabled[] = "kiosk_enabled";
+const char kKioskOnly[] = "kiosk_only";
+const char kKioskMode[] = "kiosk_mode";
+const char kKioskRequiredPlatformVersion[] = "kiosk.required_platform_version";
+const char kKioskSecondaryApps[] = "kiosk_secondary_apps";
+const char kLanguage[] = "language";
+const char kLaunch[] = "app.launch";
+const char kLaunchContainer[] = "app.launch.container";
+const char kLauncherPage[] = "launcher_page";
+const char kLauncherPagePage[] = "launcher_page.page";
+const char kLaunchHeight[] = "app.launch.height";
+const char kLaunchLocalPath[] = "app.launch.local_path";
+const char kLaunchWebURL[] = "app.launch.web_url";
+const char kLaunchWidth[] = "app.launch.width";
+const char kLayouts[] = "layouts";
+const char kLinkedAppIcons[] = "app.linked_icons";
+const char kLinkedAppIconURL[] = "url";
+const char kLinkedAppIconSize[] = "size";
+const char kManifestVersion[] = "manifest_version";
+const char kMatchAboutBlank[] = "match_about_blank";
+const char kMatches[] = "matches";
+const char kMinimumChromeVersion[] = "minimum_chrome_version";
+const char kMinimumVersion[] = "minimum_version";
+const char kMIMETypes[] = "mime_types";
+const char kMimeTypesHandler[] = "mime_types_handler";
+const char kName[] = "name";
+const char kNaClModules[] = "nacl_modules";
+const char kNaClModulesMIMEType[] = "mime_type";
+const char kNaClModulesPath[] = "path";
+const char kOAuth2[] = "oauth2";
+const char kOAuth2AutoApprove[] = "oauth2.auto_approve";
+const char kOAuth2ClientId[] = "oauth2.client_id";
+const char kOAuth2Scopes[] = "oauth2.scopes";
+const char kOfflineEnabled[] = "offline_enabled";
+const char kOmnibox[] = "omnibox";
+const char kOmniboxKeyword[] = "omnibox.keyword";
+const char kOptionalPermissions[] = "optional_permissions";
+const char kOptionsPage[] = "options_page";
+const char kOptionsUI[] = "options_ui";
+const char kOverrideHomepage[] = "chrome_settings_overrides.homepage";
+const char kOverrideSearchProvider[] =
+ "chrome_settings_overrides.search_provider";
+const char kOverrideStartupPage[] = "chrome_settings_overrides.startup_pages";
+const char kPageAction[] = "page_action";
+const char kPageActionDefaultIcon[] = "default_icon";
+const char kPageActionDefaultPopup[] = "default_popup";
+const char kPageActionDefaultTitle[] = "default_title";
+const char kPageActionIcons[] = "icons";
+const char kPageActionId[] = "id";
+const char kPageActionPopup[] = "popup";
+const char kPageActionPopupPath[] = "path";
+const char kPermissions[] = "permissions";
+const char kPlatformAppBackground[] = "app.background";
+const char kPlatformAppBackgroundPage[] = "app.background.page";
+const char kPlatformAppBackgroundScripts[] = "app.background.scripts";
+const char kPlatformAppContentSecurityPolicy[] = "app.content_security_policy";
+const char kPlugins[] = "plugins";
+const char kPluginsPath[] = "path";
+const char kPluginsPublic[] = "public";
+const char kPublicKey[] = "key";
+const char kRemoveButton[] = "remove_button";
+const char kRequirements[] = "requirements";
+const char kRunAt[] = "run_at";
+const char kSandboxedPages[] = "sandbox.pages";
+const char kSandboxedPagesCSP[] = "sandbox.content_security_policy";
+const char kSettingsOverride[] = "chrome_settings_overrides";
+const char kSettingsOverrideAlternateUrls[] =
+ "chrome_settings_overrides.search_provider.alternate_urls";
+const char kShiftKey[] = "shiftKey";
+const char kShortcutKey[] = "shortcutKey";
+const char kShortName[] = "short_name";
+const char kSignature[] = "signature";
+const char kSockets[] = "sockets";
+const char kSpellcheck[] = "spellcheck";
+const char kSpellcheckDictionaryFormat[] = "dictionary_format";
+const char kSpellcheckDictionaryLanguage[] = "dictionary_language";
+const char kSpellcheckDictionaryLocale[] = "dictionary_locale";
+const char kSpellcheckDictionaryPath[] = "dictionary_path";
+const char kStorageManagedSchema[] = "storage.managed_schema";
+const char kSuggestedKey[] = "suggested_key";
+const char kSynthesizeExtensionAction[] = "_synthesize_extension_action";
+const char kSystemIndicator[] = "system_indicator";
+const char kTheme[] = "theme";
+const char kThemeColors[] = "colors";
+const char kThemeDisplayProperties[] = "properties";
+const char kThemeImages[] = "images";
+const char kThemeTints[] = "tints";
+const char kTtsEngine[] = "tts_engine";
+const char kTtsGenderFemale[] = "female";
+const char kTtsGenderMale[] = "male";
+const char kTtsVoices[] = "voices";
+const char kTtsVoicesEventTypeEnd[] = "end";
+const char kTtsVoicesEventTypeError[] = "error";
+const char kTtsVoicesEventTypeMarker[] = "marker";
+const char kTtsVoicesEventTypeSentence[] = "sentence";
+const char kTtsVoicesEventTypeStart[] = "start";
+const char kTtsVoicesEventTypeWord[] = "word";
+const char kTtsVoicesEventTypes[] = "event_types";
+const char kTtsVoicesGender[] = "gender";
+const char kTtsVoicesLang[] = "lang";
+const char kTtsVoicesRemote[] = "remote";
+const char kTtsVoicesVoiceName[] = "voice_name";
+const char kType[] = "type";
+const char kUIOverride[] = "chrome_ui_overrides";
+const char kUpdateURL[] = "update_url";
+const char kUrlHandlers[] = "url_handlers";
+const char kUrlHandlerTitle[] = "title";
+const char kUsbPrinters[] = "usb_printers";
+const char kVersion[] = "version";
+const char kVersionName[] = "version_name";
+const char kWebAccessibleResources[] = "web_accessible_resources";
+const char kWebURLs[] = "app.urls";
+const char kWebview[] = "webview";
+const char kWebviewAccessibleResources[] = "accessible_resources";
+const char kWebviewName[] = "name";
+const char kWebviewPartitions[] = "partitions";
+const char kWhitelist[] = "whitelist";
+#if defined(OS_CHROMEOS)
+const char kFileSystemProviderCapabilities[] =
+ "file_system_provider_capabilities";
+#endif
+
+} // namespace manifest_keys
+
+namespace manifest_values {
+
+const char kApiKey[] = "api_key";
+const char kBrowserActionCommandEvent[] = "_execute_browser_action";
+const char kIncognitoNotAllowed[] = "not_allowed";
+const char kIncognitoSplit[] = "split";
+const char kIncognitoSpanning[] = "spanning";
+const char kIsolatedStorage[] = "storage";
+const char kKeybindingPlatformChromeOs[] = "chromeos";
+const char kKeybindingPlatformDefault[] = "default";
+const char kKeybindingPlatformLinux[] = "linux";
+const char kKeybindingPlatformMac[] = "mac";
+const char kKeybindingPlatformWin[] = "windows";
+const char kKeyAlt[] = "Alt";
+const char kKeyComma[] = "Comma";
+const char kKeyCommand[] = "Command";
+const char kKeyCtrl[] = "Ctrl";
+const char kKeyDel[] = "Delete";
+const char kKeyDown[] = "Down";
+const char kKeyEnd[] = "End";
+const char kKeyHome[] = "Home";
+const char kKeyIns[] = "Insert";
+const char kKeyLeft[] = "Left";
+const char kKeyMacCtrl[] = "MacCtrl";
+const char kKeyMediaNextTrack[] = "MediaNextTrack";
+const char kKeyMediaPlayPause[] = "MediaPlayPause";
+const char kKeyMediaPrevTrack[] = "MediaPrevTrack";
+const char kKeyMediaStop[] = "MediaStop";
+const char kKeyPgDwn[] = "PageDown";
+const char kKeyPgUp[] = "PageUp";
+const char kKeyPeriod[] = "Period";
+const char kKeyRight[] = "Right";
+const char kKeySearch[] = "Search";
+const char kKeySeparator[] = "+";
+const char kKeyShift[] = "Shift";
+const char kKeySpace[] = "Space";
+const char kKeyTab[] = "Tab";
+const char kKeyUp[] = "Up";
+const char kRunAtDocumentStart[] = "document_start";
+const char kRunAtDocumentEnd[] = "document_end";
+const char kRunAtDocumentIdle[] = "document_idle";
+const char kPageActionCommandEvent[] = "_execute_page_action";
+const char kPageActionTypeTab[] = "tab";
+const char kPageActionTypePermanent[] = "permanent";
+const char kLaunchContainerPanel[] = "panel";
+const char kLaunchContainerTab[] = "tab";
+const char kLaunchContainerWindow[] = "window";
+
+} // namespace manifest_values
+
+// Extension-related error messages. Some of these are simple patterns, where a
+// '*' is replaced at runtime with a specific value. This is used instead of
+// printf because we want to unit test them and scanf is hard to make
+// cross-platform.
+namespace manifest_errors {
+
+const char kActiveTabPermissionNotGranted[] =
+ "The 'activeTab' permission is not in effect because this extension has "
+ "not been in invoked.";
+const char kAllURLOrActiveTabNeeded[] =
+ "Either the '<all_urls>' or 'activeTab' permission is required.";
+const char kAppsNotEnabled[] =
+ "Apps are not enabled.";
+const char kBackgroundPermissionNeeded[] =
+ "Hosted apps that use 'background_page' must have the 'background' "
+ "permission.";
+const char kBackgroundPersistentInvalidForPlatformApps[] =
+ "The key 'background.persistent' is not supported for packaged apps.";
+const char kBackgroundRequiredForPlatformApps[] =
+ "Packaged apps must have a background page or background scripts.";
+const char kCannotAccessAboutUrl[] =
+ "Cannot access \"*\" at origin \"*\". Extension must have permission to "
+ "access the frame's origin, and matchAboutBlank must be true.";
+const char kCannotAccessChromeUrl[] = "Cannot access a chrome:// URL";
+const char kCannotAccessExtensionUrl[] =
+ "Cannot access a chrome-extension:// URL of different extension";
+// This deliberately does not contain a URL. Otherwise an extension can parse
+// error messages and determine the URLs of open tabs without having appropriate
+// permissions to see these URLs.
+const char kCannotAccessPage[] =
+ "Cannot access contents of the page. "
+ "Extension manifest must request permission to access the respective host.";
+// Use this error message with caution and only if the extension triggering it
+// has tabs permission. Otherwise, URLs may be leaked to extensions.
+const char kCannotAccessPageWithUrl[] =
+ "Cannot access contents of url \"*\". "
+ "Extension manifest must request permission to access this host.";
+const char kCannotChangeExtensionID[] =
+ "Installed extensions cannot change their IDs.";
+const char kCannotClaimAllHostsInExtent[] =
+ "Cannot claim all hosts ('*') in an extent.";
+const char kCannotClaimAllURLsInExtent[] =
+ "Cannot claim all URLs in an extent.";
+const char kCannotScriptGallery[] =
+ "The extensions gallery cannot be scripted.";
+const char kCannotScriptSigninPage[] =
+ "The sign-in page cannot be scripted.";
+const char kChromeVersionTooLow[] =
+ "This extension requires * version * or greater.";
+const char kDisabledByPolicy[] =
+ "This extension has been disabled by your administrator.";
+const char kExpectString[] = "Expect string value.";
+const char kInvalidAboutPage[] = "Invalid value for 'about_page'.";
+const char kInvalidAboutPageExpectRelativePath[] =
+ "Invalid value for 'about_page'. Value must be a relative path.";
+const char kInvalidAllFrames[] =
+ "Invalid value for 'content_scripts[*].all_frames'.";
+const char kInvalidAppIconColor[] = "Invalid value for app.icon_color.";
+const char kInvalidBackground[] =
+ "Invalid value for 'background_page'.";
+const char kInvalidBackgroundAllowJsAccess[] =
+ "Invalid value for 'background.allow_js_access'.";
+const char kInvalidBackgroundCombination[] =
+ "The background.page and background.scripts properties cannot be used at "
+ "the same time.";
+const char kInvalidBackgroundScript[] =
+ "Invalid value for 'background.scripts[*]'.";
+const char kInvalidBackgroundScripts[] =
+ "Invalid value for 'background.scripts'.";
+const char kInvalidBackgroundInHostedApp[] =
+ "Invalid value for 'background_page'. Hosted apps must specify an "
+ "absolute HTTPS URL for the background page.";
+const char kInvalidBackgroundPersistent[] =
+ "Invalid value for 'background.persistent'.";
+const char kInvalidBackgroundPersistentInPlatformApp[] =
+ "Invalid value for 'app.background.persistent'. Packaged apps do not "
+ "support persistent background pages and must use event pages.";
+const char kInvalidBackgroundPersistentNoPage[] =
+ "Must specify one of background.page or background.scripts to use"
+ " background.persistent.";
+const char kInvalidBrowserAction[] =
+ "Invalid value for 'browser_action'.";
+const char kInvalidChromeURLOverrides[] =
+ "Invalid value for 'chrome_url_overrides'.";
+const char kInvalidCommandsKey[] =
+ "Invalid value for 'commands'.";
+const char kInvalidContentCapabilities[] =
+ "Invalid value for 'content_capabilities'.";
+const char kInvalidContentCapabilitiesMatch[] =
+ "Invalid content_capabilities URL pattern: *";
+const char kInvalidContentCapabilitiesMatchOrigin[] =
+ "Domain wildcards are not allowed for content_capabilities URL patterns.";
+const char kInvalidContentCapabilitiesPermission[] =
+ "Invalid content_capabilities permission: *.";
+const char kInvalidContentScript[] =
+ "Invalid value for 'content_scripts[*]'.";
+const char kInvalidContentScriptsList[] =
+ "Invalid value for 'content_scripts'.";
+const char kInvalidContentSecurityPolicy[] =
+ "Invalid value for 'content_security_policy'.";
+const char kInvalidCopresenceConfig[] = "Invalid value for 'copresence'.";
+const char kInvalidCopresenceApiKey[] =
+ "copresence.api_key must not be empty.";
+const char kInvalidCSPInsecureValue[] =
+ "Ignored insecure CSP value \"*\" in directive '*'.";
+const char kInvalidCSPMissingSecureSrc[] =
+ "CSP directive '*' must be specified (either explicitly, or implicitly via"
+ " 'default-src') and must whitelist only secure resources.";
+const char kInvalidCss[] =
+ "Invalid value for 'content_scripts[*].css[*]'.";
+const char kInvalidCssList[] =
+ "Required value 'content_scripts[*].css' is invalid.";
+const char kInvalidDefaultLocale[] =
+ "Invalid value for default locale - locale name must be a string.";
+const char kInvalidDescription[] =
+ "Invalid value for 'description'.";
+const char kInvalidDevToolsPage[] =
+ "Invalid value for 'devtools_page'.";
+const char kInvalidDisplayInLauncher[] =
+ "Invalid value for 'display_in_launcher'.";
+const char kInvalidDisplayInNewTabPage[] =
+ "Invalid value for 'display_in_new_tab_page'.";
+const char kInvalidEmptyDictionary[] = "Empty dictionary for '*'.";
+const char kInvalidExcludeMatch[] =
+ "Invalid value for 'content_scripts[*].exclude_matches[*]': *";
+const char kInvalidExcludeMatches[] =
+ "Invalid value for 'content_scripts[*].exclude_matches'.";
+const char kInvalidExport[] =
+ "Invalid value for 'export'.";
+const char kInvalidExportPermissions[] =
+ "Permissions are not allowed for extensions that export resources.";
+const char kInvalidExportWhitelist[] =
+ "Invalid value for 'export.whitelist'.";
+const char kInvalidExportWhitelistString[] =
+ "Invalid value for 'export.whitelist[*]'.";
+const char kInvalidFileAccessList[] =
+ "Invalid value for 'file_access'.";
+const char kInvalidFileAccessValue[] =
+ "Invalid value for 'file_access[*]'.";
+const char kInvalidFileBrowserHandler[] =
+ "Invalid value for 'file_browser_handlers'.";
+const char kInvalidFileBrowserHandlerMissingPermission[] =
+ "Declaring file browser handlers requires the fileBrowserHandler manifest "
+ "permission.";
+const char kInvalidFileFiltersList[] =
+ "Invalid value for 'file_filters'.";
+const char kInvalidFileFilterValue[] =
+ "Invalid value for 'file_filters[*]'.";
+const char kInvalidFileHandlers[] =
+ "Invalid value for 'file_handlers'.";
+const char kInvalidFileHandlersTooManyTypesAndExtensions[] =
+ "Too many MIME and extension file_handlers have been declared.";
+const char kInvalidFileHandlerExtension[] =
+ "Invalid value for 'file_handlers[*].extensions'.";
+const char kInvalidFileHandlerExtensionElement[] =
+ "Invalid value for 'file_handlers[*].extensions[*]'.";
+const char kInvalidFileHandlerIncludeDirectories[] =
+ "Invalid value for 'include_directories'.";
+const char kInvalidFileHandlerNoTypeOrExtension[] =
+ "'file_handlers[*]' must contain a non-empty 'types', 'extensions' "
+ "or 'include_directories'.";
+const char kInvalidFileHandlerType[] =
+ "Invalid value for 'file_handlers[*].types'.";
+const char kInvalidFileHandlerTypeElement[] =
+ "Invalid value for 'file_handlers[*].types[*]'.";
+const char kInvalidGlob[] =
+ "Invalid value for 'content_scripts[*].*[*]'.";
+const char kInvalidGlobList[] =
+ "Invalid value for 'content_scripts[*].*'.";
+const char kInvalidHomepageOverrideURL[] =
+ "Invalid value for overriding homepage url: '[*]'.";
+const char kInvalidHomepageURL[] =
+ "Invalid value for homepage url: '[*]'.";
+const char kInvalidIconKey[] = "Invalid key in icons: \"*\".";
+const char kInvalidIconPath[] =
+ "Invalid value for 'icons[\"*\"]'.";
+const char kInvalidIcons[] =
+ "Invalid value for 'icons'.";
+const char kInvalidImport[] =
+ "Invalid value for 'import'.";
+const char kInvalidImportAndExport[] =
+ "Simultaneous 'import' and 'export' are not allowed.";
+const char kInvalidImportId[] =
+ "Invalid value for 'import[*].id'.";
+const char kInvalidImportVersion[] =
+ "Invalid value for 'import[*].minimum_version'.";
+const char kInvalidIncognitoBehavior[] =
+ "Invalid value for 'incognito'.";
+const char kInvalidInputComponents[] =
+ "Invalid value for 'input_components'";
+const char kInvalidInputComponentDescription[] =
+ "Invalid value for 'input_components[*].description";
+const char kInvalidInputComponentLayoutName[] =
+ "Invalid value for 'input_components[*].layouts[*]";
+const char kInvalidInputComponentName[] =
+ "Invalid value for 'input_components[*].name";
+const char kInvalidInputComponentShortcutKey[] =
+ "Invalid value for 'input_components[*].shortcutKey";
+const char kInvalidInputComponentShortcutKeycode[] =
+ "Invalid value for 'input_components[*].shortcutKey.keyCode";
+const char kInvalidInputComponentType[] =
+ "Invalid value for 'input_components[*].type";
+const char kInvalidInputView[] =
+ "Invalid value for 'input_view'.";
+const char kInvalidIsolation[] =
+ "Invalid value for 'app.isolation'.";
+const char kInvalidIsolationValue[] =
+ "Invalid value for 'app.isolation[*]'.";
+const char kInvalidJs[] =
+ "Invalid value for 'content_scripts[*].js[*]'.";
+const char kInvalidJsList[] =
+ "Required value 'content_scripts[*].js' is invalid.";
+const char kInvalidKey[] =
+ "Value 'key' is missing or invalid.";
+const char kInvalidKeyBinding[] =
+ "Invalid value for 'commands[*].*': *.";
+const char kInvalidKeyBindingDescription[] =
+ "Invalid value for 'commands[*].description'.";
+const char kInvalidKeyBindingDictionary[] =
+ "Contents of 'commands[*]' invalid.";
+const char kInvalidKeyBindingMediaKeyWithModifier[] =
+ "Media key cannot have any modifier for 'commands[*].*': *.";
+const char kInvalidKeyBindingMissingPlatform[] =
+ "Could not find key specification for 'command[*].*': Either specify a key "
+ "for '*', or specify a default key.";
+const char kInvalidKeyBindingTooMany[] =
+ "Too many shortcuts specified for 'commands': The maximum is *.";
+const char kInvalidKeyBindingUnknownPlatform[] =
+ "Unknown platform for 'command[*]': *. Valid values are: 'windows', 'mac'"
+ " 'chromeos', 'linux' and 'default'.";
+const char kInvalidKioskEnabled[] =
+ "Invalid value for 'kiosk_enabled'.";
+const char kInvalidKioskOnly[] =
+ "Invalid value for 'kiosk_only'.";
+const char kInvalidKioskOnlyButNotEnabled[] =
+ "The 'kiosk_only' key is set, but 'kiosk_enabled' is not set.";
+const char kInvalidKioskRequiredPlatformVersion[] =
+ "Invalid value for 'kiosk.required_platform_version'";
+const char kInvalidKioskSecondaryApps[] =
+ "Invalid value for 'kiosk_secondary_apps'";
+const char kInvalidKioskSecondaryAppsBadAppEntry[] =
+ "Invalid app id item for 'kiosk_secondary_apps'";
+const char kInvalidKioskSecondaryAppsBadAppId[] =
+ "Invalid app id value for 'kiosk_secondary_apps'";
+const char kInvalidLauncherPage[] = "Invalid value for 'launcher_page'.";
+const char kInvalidLauncherPagePage[] =
+ "Invalid value for 'launcher_page.page'.";
+const char kInvalidLaunchContainer[] =
+ "Invalid value for 'app.launch.container'.";
+const char kInvalidLaunchValue[] =
+ "Invalid value for '*'.";
+const char kInvalidLaunchValueContainer[] =
+ "Invalid container type for '*'.";
+const char kInvalidLinkedAppIcon[] =
+ "Invalid linked app icon. Must be a dictionary";
+const char kInvalidLinkedAppIconSize[] =
+ "Invalid 'size' for linked app icon. Must be an integer";
+const char kInvalidLinkedAppIconURL[] =
+ "Invalid 'url' for linked app icon. Must be a string that is a valid URL";
+const char kInvalidLinkedAppIcons[] =
+ "Invalid 'app.linked_icons'. Must be an array";
+const char kInvalidManifest[] =
+ "Manifest file is invalid.";
+const char kInvalidManifestVersion[] =
+ "Invalid value for 'manifest_version'. Must be an integer greater than "
+ "zero.";
+const char kInvalidManifestVersionOld[] =
+ "The 'manifest_version' key must be present and set to * (without quotes). "
+ "See developer.chrome.com/*/manifestVersion.html for details.";
+const char kInvalidMatch[] =
+ "Invalid value for 'content_scripts[*].matches[*]': *";
+const char kInvalidMatchAboutBlank[] =
+ "Invalid value for 'content_scripts[*].match_about_blank'.";
+const char kInvalidMatchCount[] =
+ "Invalid value for 'content_scripts[*].matches'. There must be at least "
+ "one match specified.";
+const char kInvalidMatches[] =
+ "Required value 'content_scripts[*].matches' is missing or invalid.";
+const char kInvalidMIMETypes[] =
+ "Invalid value for 'mime_types'";
+const char kInvalidMimeTypesHandler[] =
+ "Invalid value for 'mime_types'.";
+const char kInvalidMinimumChromeVersion[] =
+ "Invalid value for 'minimum_chrome_version'.";
+const char kInvalidName[] =
+ "Required value 'name' is missing or invalid.";
+const char kInvalidNaClModules[] =
+ "Invalid value for 'nacl_modules'.";
+const char kInvalidNaClModulesPath[] =
+ "Invalid value for 'nacl_modules[*].path'.";
+const char kInvalidNaClModulesMIMEType[] =
+ "Invalid value for 'nacl_modules[*].mime_type'.";
+const char kInvalidOAuth2AutoApprove[] =
+ "Invalid value for 'oauth2.auto_approve'. Value must be true or false.";
+const char kInvalidOAuth2ClientId[] =
+ "Invalid value for 'oauth2.client_id'.";
+const char kInvalidOAuth2Scopes[] =
+ "Invalid value for 'oauth2.scopes'.";
+const char kInvalidOfflineEnabled[] =
+ "Invalid value for 'offline_enabled'.";
+const char kInvalidOmniboxKeyword[] =
+ "Invalid value for 'omnibox.keyword'.";
+const char kInvalidOptionsPage[] = "Invalid value for '*'.";
+const char kInvalidOptionsPageExpectUrlInPackage[] =
+ "Invalid value for 'options_page'. Value must be a relative path.";
+const char kInvalidOptionsPageInHostedApp[] =
+ "Invalid value for 'options_page'. Hosted apps must specify an "
+ "absolute URL.";
+const char kInvalidOptionsUIChromeStyle[] =
+ "Invalid value for 'options_ui.chrome_style'.";
+const char kInvalidOptionsUIOpenInTab[] =
+ "Invalid value for 'options_ui.open_in_tab'.";
+const char kInvalidPageAction[] =
+ "Invalid value for 'page_action'.";
+const char kInvalidPageActionDefaultTitle[] =
+ "Invalid value for 'default_title'.";
+const char kInvalidPageActionIconPath[] =
+ "Invalid value for 'page_action.default_icon'.";
+const char kInvalidPageActionId[] =
+ "Required value 'id' is missing or invalid.";
+const char kInvalidPageActionName[] =
+ "Invalid value for 'page_action.name'.";
+const char kInvalidPageActionOldAndNewKeys[] =
+ "Key \"*\" is deprecated. Key \"*\" has the same meaning. You can not "
+ "use both.";
+const char kInvalidPageActionPopup[] =
+ "Invalid type for page action popup.";
+const char kInvalidPageActionPopupPath[] =
+ "Invalid value for page action popup path [*].";
+const char kInvalidPermissionWithDetail[] =
+ "Invalid value for 'permissions[*]': *.";
+const char kInvalidPermission[] =
+ "Invalid value for 'permissions[*]'.";
+const char kInvalidPermissions[] =
+ "Invalid value for 'permissions'.";
+const char kInvalidPermissionScheme[] =
+ "Invalid scheme for 'permissions[*]'.";
+const char kInvalidPlugins[] =
+ "Invalid value for 'plugins'.";
+const char kInvalidPluginsPath[] =
+ "Invalid value for 'plugins[*].path'.";
+const char kInvalidPluginsPublic[] =
+ "Invalid value for 'plugins[*].public'.";
+const char kInvalidRequirement[] =
+ "Invalid value for requirement \"*\"";
+const char kInvalidRequirements[] =
+ "Invalid value for 'requirements'";
+const char kInvalidRunAt[] =
+ "Invalid value for 'content_scripts[*].run_at'.";
+const char kInvalidSandboxedPagesList[] =
+ "Invalid value for 'sandbox.pages'.";
+const char kInvalidSandboxedPage[] =
+ "Invalid value for 'sandbox.pages[*]'.";
+const char kInvalidSandboxedPagesCSP[] =
+ "Invalid value for 'sandbox.content_security_policy'.";
+const char kInvalidSearchEngineMissingKeys[] =
+ "Missing mandatory parameters for "
+ "'chrome_settings_overrides.search_provider'.";
+const char kInvalidSearchEngineURL[] =
+ "Invalid URL [*] for 'chrome_settings_overrides.search_provider'.";
+const char kInvalidShortName[] =
+ "Invalid value for 'short_name'.";
+const char kInvalidSignature[] =
+ "Value 'signature' is missing or invalid.";
+const char kInvalidSpellcheck[] =
+ "Invalid value for 'spellcheck'.";
+const char kInvalidSpellcheckDictionaryFormat[] =
+ "Invalid value for spellcheck dictionary format.";
+const char kInvalidSpellcheckDictionaryLanguage[] =
+ "Invalid value for spellcheck dictionary language.";
+const char kInvalidSpellcheckDictionaryLocale[] =
+ "Invalid value for spellcheck dictionary locale.";
+const char kInvalidSpellcheckDictionaryPath[] =
+ "Invalid value for spellcheck dictionary path.";
+const char kInvalidStartupOverrideURL[] =
+ "Invalid value for overriding startup URL: '[*]'.";
+const char kInvalidSystemIndicator[] =
+ "Invalid value for 'system_indicator'.";
+const char kInvalidTheme[] =
+ "Invalid value for 'theme'.";
+const char kInvalidThemeColors[] =
+ "Invalid value for theme colors - colors must be integers";
+const char kInvalidThemeImages[] =
+ "Invalid value for theme images - images must be strings.";
+const char kInvalidThemeImagesMissing[] =
+ "An image specified in the theme is missing.";
+const char kInvalidThemeTints[] =
+ "Invalid value for theme images - tints must be decimal numbers.";
+const char kInvalidTts[] =
+ "Invalid value for 'tts_engine'.";
+const char kInvalidTtsVoices[] =
+ "Invalid value for 'tts_engine.voices'.";
+const char kInvalidTtsVoicesEventTypes[] =
+ "Invalid value for 'tts_engine.voices[*].event_types'.";
+const char kInvalidTtsVoicesGender[] =
+ "Invalid value for 'tts_engine.voices[*].gender'.";
+const char kInvalidTtsVoicesLang[] =
+ "Invalid value for 'tts_engine.voices[*].lang'.";
+const char kInvalidTtsVoicesRemote[] =
+ "Invalid value for 'tts_engine.voices[*].remote'.";
+const char kInvalidTtsVoicesVoiceName[] =
+ "Invalid value for 'tts_engine.voices[*].voice_name'.";
+const char kInvalidUpdateURL[] =
+ "Invalid value for update url: '[*]'.";
+const char kInvalidURLHandlers[] =
+ "Invalid value for 'url_handlers'.";
+const char kInvalidURLHandlerPatternElement[] =
+ "Invalid value for 'url_handlers[*]'.";
+const char kInvalidURLHandlerTitle[] =
+ "Invalid value for 'url_handlers[*].title'.";
+const char kInvalidURLHandlerPattern[] =
+ "Invalid value for 'url_handlers[*].matches[*]'.";
+const char kInvalidURLPatternError[] =
+ "Invalid url pattern '*'";
+const char kInvalidVersion[] =
+ "Required value 'version' is missing or invalid. It must be between 1-4 "
+ "dot-separated integers each between 0 and 65536.";
+const char kInvalidVersionName[] = "Invalid value for 'version_name'.";
+const char kInvalidWebAccessibleResourcesList[] =
+ "Invalid value for 'web_accessible_resources'.";
+const char kInvalidWebAccessibleResource[] =
+ "Invalid value for 'web_accessible_resources[*]'.";
+const char kInvalidWebview[] =
+ "Invalid value for 'webview'.";
+const char kInvalidWebviewAccessibleResourcesList[] =
+ "Invalid value for'webview.accessible_resources'.";
+const char kInvalidWebviewAccessibleResource[] =
+ "Invalid value for 'webview.accessible_resources[*]'.";
+const char kInvalidWebviewPartition[] =
+ "Invalid value for 'webview.partitions[*]'.";
+const char kInvalidWebviewPartitionName[] =
+ "Invalid value for 'webview.partitions[*].name'.";
+const char kInvalidWebviewPartitionsList[] =
+ "Invalid value for 'webview.partitions'.";
+const char kInvalidWebURL[] =
+ "Invalid value for 'app.urls[*]': *";
+const char kInvalidWebURLs[] =
+ "Invalid value for 'app.urls'.";
+const char kInvalidZipHash[] =
+ "Required key 'zip_hash' is missing or invalid.";
+const char kKeyIsDeprecatedWithReplacement[] =
+ "Key \"*\" is deprecated. Key \"*\" should be used instead.";
+const char kLauncherPagePageRequired[] =
+ "The 'launcher_page.page' key is required.";
+const char kLaunchPathAndExtentAreExclusive[] =
+ "The 'app.launch.local_path' and 'app.urls' keys cannot both be set.";
+const char kLaunchPathAndURLAreExclusive[] =
+ "The 'app.launch.local_path' and 'app.launch.web_url' keys cannot "
+ "both be set.";
+const char kLaunchURLRequired[] =
+ "Either 'app.launch.local_path' or 'app.launch.web_url' is required.";
+const char kLocalesInvalidLocale[] =
+ "Invalid locale file '*': *";
+const char kLocalesMessagesFileMissing[] =
+ "Messages file is missing for locale.";
+const char kLocalesNoDefaultLocaleSpecified[] =
+ "Localization used, but default_locale wasn't specified in the manifest.";
+const char kLocalesNoDefaultMessages[] =
+ "Default locale is defined but default data couldn't be loaded.";
+const char kLocalesNoValidLocaleNamesListed[] =
+ "No valid locale name could be found in _locales directory.";
+const char kLocalesTreeMissing[] =
+ "Default locale was specified, but _locales subtree is missing.";
+const char kManifestParseError[] =
+ "Manifest is not valid JSON.";
+const char kManifestUnreadable[] =
+ "Manifest file is missing or unreadable.";
+const char kMissingFile[] =
+ "At least one js or css file is required for 'content_scripts[*]'.";
+const char kMultipleOverrides[] =
+ "An extension cannot override more than one page.";
+const char kNoPermissionForMIMETypes[] =
+ "The extension is not allowed to use mime_types key.";
+const char kNoWildCardsInPaths[] =
+ "Wildcards are not allowed in extent URL pattern paths.";
+const char kOneUISurfaceOnly[] =
+ "Only one of 'browser_action', 'page_action', and 'app' can be specified.";
+const char kPermissionMustBeOptional[] =
+ "Permission '*' must be specified in the optional section of the manifest.";
+const char kPermissionNotAllowed[] =
+ "Access to permission '*' denied.";
+const char kPermissionNotAllowedInManifest[] =
+ "Permission '*' cannot be specified in the manifest.";
+const char kPermissionUnknownOrMalformed[] =
+ "Permission '*' is unknown or URL pattern is malformed.";
+const char kReservedMessageFound[] =
+ "Reserved key * found in message catalog.";
+const char kUnrecognizedManifestKey[] = "Unrecognized manifest key '*'.";
+const char kUnrecognizedManifestProperty[] =
+ "Unrecognized property '*' of manifest key '*'.";
+const char kWebRequestConflictsWithLazyBackground[] =
+ "The 'webRequest' API cannot be used with event pages.";
+#if defined(OS_CHROMEOS)
+const char kIllegalPlugins[] =
+ "Extensions cannot install plugins on Chrome OS.";
+const char kInvalidFileSystemProviderMissingCapabilities[] =
+ "The 'fileSystemProvider' permission requires the "
+ "'file_system_provider_capabilities' section to be specified in the "
+ "manifest.";
+const char kInvalidFileSystemProviderMissingPermission[] =
+ "The 'file_system_provider_capabilities' section requires the "
+ "'fileSystemProvider' permission to be specified in the manifest.";
+#endif
+
+} // namespace manifest_errors
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_constants.h b/chromium/extensions/common/manifest_constants.h
new file mode 100644
index 00000000000..da7562227a9
--- /dev/null
+++ b/chromium/extensions/common/manifest_constants.h
@@ -0,0 +1,500 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_CONSTANTS_H_
+#define EXTENSIONS_COMMON_MANIFEST_CONSTANTS_H_
+
+#include "build/build_config.h"
+
+namespace extensions {
+
+// Keys used in JSON representation of extensions.
+namespace manifest_keys {
+
+extern const char kAboutPage[];
+extern const char kAllFrames[];
+extern const char kAltKey[];
+extern const char kApp[];
+extern const char kAppIconColor[];
+extern const char kAutomation[];
+extern const char kBackgroundAllowJsAccess[];
+extern const char kBackgroundPage[];
+extern const char kBackgroundPageLegacy[];
+extern const char kBackgroundPersistent[];
+extern const char kBackgroundScripts[];
+extern const char kBluetooth[];
+extern const char kBookmarkUI[];
+extern const char kBrowserAction[];
+extern const char kBrowseURLs[];
+extern const char kChromeURLOverrides[];
+extern const char kCommands[];
+extern const char kContentCapabilities[];
+extern const char kContentScripts[];
+extern const char kContentSecurityPolicy[];
+extern const char kConvertedFromUserScript[];
+extern const char kCopresence[];
+extern const char kCss[];
+extern const char kCtrlKey[];
+extern const char kCurrentLocale[];
+extern const char kDefaultLocale[];
+extern const char kDescription[];
+extern const char kDevToolsPage[];
+extern const char kDisplayInLauncher[];
+extern const char kDisplayInNewTabPage[];
+extern const char kEventName[];
+extern const char kExcludeGlobs[];
+extern const char kExcludeMatches[];
+extern const char kExport[];
+extern const char kExternallyConnectable[];
+extern const char kEventRules[];
+extern const char kFileAccessList[];
+extern const char kFileHandlers[];
+extern const char kFileHandlerExtensions[];
+extern const char kFileHandlerTypes[];
+extern const char kFileFilters[];
+extern const char kFileBrowserHandlers[];
+extern const char kGlobal[];
+extern const char kHideBookmarkButton[];
+extern const char kHomepageURL[];
+extern const char kIcons[];
+extern const char kId[];
+extern const char kImeOptionsPage[];
+extern const char kImport[];
+extern const char kIncognito[];
+extern const char kIncludeGlobs[];
+extern const char kIndicator[];
+extern const char kInputComponents[];
+extern const char kInputView[];
+extern const char kIsolation[];
+extern const char kJs[];
+extern const char kKey[];
+extern const char kKeycode[];
+extern const char kKiosk[];
+extern const char kKioskEnabled[];
+extern const char kKioskOnly[];
+extern const char kKioskMode[];
+extern const char kKioskRequiredPlatformVersion[];
+extern const char kKioskSecondaryApps[];
+extern const char kLanguage[];
+extern const char kLaunch[];
+extern const char kLaunchContainer[];
+extern const char kLauncherPage[];
+extern const char kLauncherPagePage[];
+extern const char kLaunchHeight[];
+extern const char kLaunchLocalPath[];
+extern const char kLaunchWebURL[];
+extern const char kLaunchWidth[];
+extern const char kLayouts[];
+extern const char kLinkedAppIcons[];
+extern const char kLinkedAppIconURL[];
+extern const char kLinkedAppIconSize[];
+extern const char kManifestVersion[];
+extern const char kMatchAboutBlank[];
+extern const char kMatches[];
+extern const char kMIMETypes[];
+extern const char kMimeTypesHandler[];
+extern const char kMinimumChromeVersion[];
+extern const char kMinimumVersion[];
+extern const char kNaClModules[];
+extern const char kNaClModulesMIMEType[];
+extern const char kNaClModulesPath[];
+extern const char kName[];
+extern const char kOAuth2[];
+extern const char kOAuth2AutoApprove[];
+extern const char kOAuth2ClientId[];
+extern const char kOAuth2Scopes[];
+extern const char kOfflineEnabled[];
+extern const char kOmnibox[];
+extern const char kOmniboxKeyword[];
+extern const char kOptionalPermissions[];
+extern const char kOptionsPage[];
+extern const char kOptionsUI[];
+extern const char kOverrideHomepage[];
+extern const char kOverrideSearchProvider[];
+extern const char kOverrideStartupPage[];
+extern const char kPageAction[];
+extern const char kPageActionDefaultIcon[];
+extern const char kPageActionDefaultPopup[];
+extern const char kPageActionDefaultTitle[];
+extern const char kPageActionIcons[];
+extern const char kPageActionId[];
+extern const char kPageActionPopup[];
+extern const char kPageActionPopupPath[];
+extern const char kPermissions[];
+extern const char kPlatformAppBackground[];
+extern const char kPlatformAppBackgroundPage[];
+extern const char kPlatformAppBackgroundScripts[];
+extern const char kPlatformAppContentSecurityPolicy[];
+extern const char kPlugins[];
+extern const char kPluginsPath[];
+extern const char kPluginsPublic[];
+extern const char kPublicKey[];
+extern const char kRemoveButton[];
+extern const char kRequiredPlatformVersion[];
+extern const char kRequirements[];
+extern const char kRunAt[];
+extern const char kSandboxedPages[];
+extern const char kSandboxedPagesCSP[];
+extern const char kSettingsOverride[];
+extern const char kSettingsOverrideAlternateUrls[];
+extern const char kShiftKey[];
+extern const char kShortcutKey[];
+extern const char kShortName[];
+extern const char kSignature[];
+extern const char kSockets[];
+extern const char kSpellcheck[];
+extern const char kSpellcheckDictionaryFormat[];
+extern const char kSpellcheckDictionaryLanguage[];
+extern const char kSpellcheckDictionaryLocale[];
+extern const char kSpellcheckDictionaryPath[];
+extern const char kStorageManagedSchema[];
+extern const char kSuggestedKey[];
+extern const char kSynthesizeExtensionAction[];
+extern const char kSystemIndicator[];
+extern const char kTheme[];
+extern const char kThemeColors[];
+extern const char kThemeDisplayProperties[];
+extern const char kThemeImages[];
+extern const char kThemeTints[];
+extern const char kTtsEngine[];
+extern const char kTtsGenderFemale[];
+extern const char kTtsGenderMale[];
+extern const char kTtsVoices[];
+extern const char kTtsVoicesEventTypeEnd[];
+extern const char kTtsVoicesEventTypeError[];
+extern const char kTtsVoicesEventTypeMarker[];
+extern const char kTtsVoicesEventTypeSentence[];
+extern const char kTtsVoicesEventTypeStart[];
+extern const char kTtsVoicesEventTypeWord[];
+extern const char kTtsVoicesEventTypes[];
+extern const char kTtsVoicesGender[];
+extern const char kTtsVoicesLang[];
+extern const char kTtsVoicesRemote[];
+extern const char kTtsVoicesVoiceName[];
+extern const char kType[];
+extern const char kUIOverride[];
+extern const char kUpdateURL[];
+extern const char kUrlHandlers[];
+extern const char kUrlHandlerTitle[];
+extern const char kUsbPrinters[];
+extern const char kVersion[];
+extern const char kVersionName[];
+extern const char kWebAccessibleResources[];
+extern const char kWebURLs[];
+extern const char kWebview[];
+extern const char kWebviewName[];
+extern const char kWebviewAccessibleResources[];
+extern const char kWebviewPartitions[];
+extern const char kWhitelist[];
+#if defined(OS_CHROMEOS)
+extern const char kFileSystemProviderCapabilities[];
+#endif
+} // namespace manifest_keys
+
+// Some values expected in manifests.
+namespace manifest_values {
+
+extern const char kApiKey[];
+extern const char kBrowserActionCommandEvent[];
+extern const char kIncognitoNotAllowed[];
+extern const char kIncognitoSplit[];
+extern const char kIncognitoSpanning[];
+extern const char kIsolatedStorage[];
+extern const char kKeybindingPlatformChromeOs[];
+extern const char kKeybindingPlatformDefault[];
+extern const char kKeybindingPlatformLinux[];
+extern const char kKeybindingPlatformMac[];
+extern const char kKeybindingPlatformWin[];
+extern const char kKeyAlt[];
+extern const char kKeyShift[];
+extern const char kKeyCommand[];
+extern const char kKeyCtrl[];
+extern const char kKeyComma[];
+extern const char kKeyDel[];
+extern const char kKeyDown[];
+extern const char kKeyHome[];
+extern const char kKeyEnd[];
+extern const char kKeyIns[];
+extern const char kKeyLeft[];
+extern const char kKeyMacCtrl[];
+extern const char kKeyMediaNextTrack[];
+extern const char kKeyMediaPlayPause[];
+extern const char kKeyMediaPrevTrack[];
+extern const char kKeyMediaStop[];
+extern const char kKeyPgDwn[];
+extern const char kKeyPgUp[];
+extern const char kKeyPeriod[];
+extern const char kKeyRight[];
+extern const char kKeySearch[];
+extern const char kKeySeparator[];
+extern const char kKeySpace[];
+extern const char kKeyTab[];
+extern const char kKeyUp[];
+extern const char kLaunchContainerPanel[];
+extern const char kLaunchContainerTab[];
+extern const char kLaunchContainerWindow[];
+extern const char kPageActionCommandEvent[];
+extern const char kPageActionTypePermanent[];
+extern const char kPageActionTypeTab[];
+extern const char kRunAtDocumentEnd[];
+extern const char kRunAtDocumentIdle[];
+extern const char kRunAtDocumentStart[];
+
+} // namespace manifest_values
+
+// Error messages returned from extension installation.
+namespace manifest_errors {
+
+extern const char kActiveTabPermissionNotGranted[];
+extern const char kAllURLOrActiveTabNeeded[];
+extern const char kAppsNotEnabled[];
+extern const char kBackgroundPermissionNeeded[];
+extern const char kBackgroundPersistentInvalidForPlatformApps[];
+extern const char kBackgroundRequiredForPlatformApps[];
+extern const char kCannotAccessAboutUrl[];
+extern const char kCannotAccessChromeUrl[];
+extern const char kCannotAccessExtensionUrl[];
+extern const char kCannotAccessPage[];
+extern const char kCannotAccessPageWithUrl[];
+extern const char kCannotChangeExtensionID[];
+extern const char kCannotClaimAllHostsInExtent[];
+extern const char kCannotClaimAllURLsInExtent[];
+extern const char kCannotScriptGallery[];
+extern const char kCannotScriptSigninPage[];
+extern const char kCannotUninstallManagedExtension[];
+extern const char kChromeVersionTooLow[];
+extern const char kDevToolsExperimental[];
+extern const char kDisabledByPolicy[];
+extern const char kExpectString[];
+extern const char kInvalidAboutPage[];
+extern const char kInvalidAboutPageExpectRelativePath[];
+extern const char kInvalidAllFrames[];
+extern const char kInvalidAppIconColor[];
+extern const char kInvalidBackground[];
+extern const char kInvalidBackgroundAllowJsAccess[];
+extern const char kInvalidBackgroundCombination[];
+extern const char kInvalidBackgroundScript[];
+extern const char kInvalidBackgroundScripts[];
+extern const char kInvalidBackgroundInHostedApp[];
+extern const char kInvalidBackgroundPersistent[];
+extern const char kInvalidBackgroundPersistentInPlatformApp[];
+extern const char kInvalidBackgroundPersistentNoPage[];
+extern const char kInvalidBrowserAction[];
+extern const char kInvalidBrowseURL[];
+extern const char kInvalidBrowseURLs[];
+extern const char kInvalidChromeURLOverrides[];
+extern const char kInvalidCommandsKey[];
+extern const char kInvalidContentCapabilities[];
+extern const char kInvalidContentCapabilitiesMatch[];
+extern const char kInvalidContentCapabilitiesMatchOrigin[];
+extern const char kInvalidContentCapabilitiesPermission[];
+extern const char kInvalidContentScript[];
+extern const char kInvalidContentScriptsList[];
+extern const char kInvalidContentSecurityPolicy[];
+extern const char kInvalidCopresenceConfig[];
+extern const char kInvalidCopresenceApiKey[];
+extern const char kInvalidCSPInsecureValue[];
+extern const char kInvalidCSPMissingSecureSrc[];
+extern const char kInvalidCss[];
+extern const char kInvalidCssList[];
+extern const char kInvalidDefaultLocale[];
+extern const char kInvalidDescription[];
+extern const char kInvalidDevToolsPage[];
+extern const char kInvalidDisplayInLauncher[];
+extern const char kInvalidDisplayInNewTabPage[];
+extern const char kInvalidEmptyDictionary[];
+extern const char kInvalidExcludeMatch[];
+extern const char kInvalidExcludeMatches[];
+extern const char kInvalidExport[];
+extern const char kInvalidExportPermissions[];
+extern const char kInvalidExportWhitelist[];
+extern const char kInvalidExportWhitelistString[];
+extern const char kInvalidFileAccessList[];
+extern const char kInvalidFileAccessValue[];
+extern const char kInvalidFileBrowserHandler[];
+extern const char kInvalidFileBrowserHandlerMissingPermission[];
+extern const char kInvalidFileFiltersList[];
+extern const char kInvalidFileFilterValue[];
+extern const char kInvalidFileHandlers[];
+extern const char kInvalidFileHandlersTooManyTypesAndExtensions[];
+extern const char kInvalidFileHandlerExtension[];
+extern const char kInvalidFileHandlerExtensionElement[];
+extern const char kInvalidFileHandlerIncludeDirectories[];
+extern const char kInvalidFileHandlerNoTypeOrExtension[];
+extern const char kInvalidFileHandlerType[];
+extern const char kInvalidFileHandlerTypeElement[];
+extern const char kInvalidGlob[];
+extern const char kInvalidGlobList[];
+extern const char kInvalidHomepageOverrideURL[];
+extern const char kInvalidHomepageURL[];
+extern const char kInvalidIconKey[];
+extern const char kInvalidIconPath[];
+extern const char kInvalidIcons[];
+extern const char kInvalidImport[];
+extern const char kInvalidImportAndExport[];
+extern const char kInvalidImportId[];
+extern const char kInvalidImportVersion[];
+extern const char kInvalidIncognitoBehavior[];
+extern const char kInvalidInputComponents[];
+extern const char kInvalidInputComponentDescription[];
+extern const char kInvalidInputComponentLayoutName[];
+extern const char kInvalidInputComponentName[];
+extern const char kInvalidInputComponentShortcutKey[];
+extern const char kInvalidInputComponentShortcutKeycode[];
+extern const char kInvalidInputComponentType[];
+extern const char kInvalidInputView[];
+extern const char kInvalidIsolation[];
+extern const char kInvalidIsolationValue[];
+extern const char kInvalidJs[];
+extern const char kInvalidJsList[];
+extern const char kInvalidKey[];
+extern const char kInvalidKeyBinding[];
+extern const char kInvalidKeyBindingDescription[];
+extern const char kInvalidKeyBindingDictionary[];
+extern const char kInvalidKeyBindingMediaKeyWithModifier[];
+extern const char kInvalidKeyBindingMissingPlatform[];
+extern const char kInvalidKeyBindingTooMany[];
+extern const char kInvalidKeyBindingUnknownPlatform[];
+extern const char kInvalidKioskEnabled[];
+extern const char kInvalidKioskOnly[];
+extern const char kInvalidKioskOnlyButNotEnabled[];
+extern const char kInvalidKioskRequiredPlatformVersion[];
+extern const char kInvalidKioskSecondaryApps[];
+extern const char kInvalidKioskSecondaryAppsBadAppEntry[];
+extern const char kInvalidKioskSecondaryAppsBadAppId[];
+extern const char kInvalidLauncherPage[];
+extern const char kInvalidLauncherPagePage[];
+extern const char kInvalidLaunchContainer[];
+extern const char kInvalidLaunchValue[];
+extern const char kInvalidLaunchValueContainer[];
+extern const char kInvalidLinkedAppIcon[];
+extern const char kInvalidLinkedAppIconSize[];
+extern const char kInvalidLinkedAppIconURL[];
+extern const char kInvalidLinkedAppIcons[];
+extern const char kInvalidManifest[];
+extern const char kInvalidManifestVersion[];
+extern const char kInvalidManifestVersionOld[];
+extern const char kInvalidMatch[];
+extern const char kInvalidMatchAboutBlank[];
+extern const char kInvalidMatchCount[];
+extern const char kInvalidMatches[];
+extern const char kInvalidMIMETypes[];
+extern const char kInvalidMimeTypesHandler[];
+extern const char kInvalidMinimumChromeVersion[];
+extern const char kInvalidNaClModules[];
+extern const char kInvalidNaClModulesMIMEType[];
+extern const char kInvalidNaClModulesPath[];
+extern const char kInvalidName[];
+extern const char kInvalidOAuth2AutoApprove[];
+extern const char kInvalidOAuth2ClientId[];
+extern const char kInvalidOAuth2Scopes[];
+extern const char kInvalidOfflineEnabled[];
+extern const char kInvalidOmniboxKeyword[];
+extern const char kInvalidOptionsUIChromeStyle[];
+extern const char kInvalidOptionsUIOpenInTab[];
+extern const char kInvalidOptionsPage[];
+extern const char kInvalidOptionsPageExpectUrlInPackage[];
+extern const char kInvalidOptionsPageInHostedApp[];
+extern const char kInvalidPageAction[];
+extern const char kInvalidPageActionDefaultTitle[];
+extern const char kInvalidPageActionIconPath[];
+extern const char kInvalidPageActionId[];
+extern const char kInvalidPageActionName[];
+extern const char kInvalidPageActionOldAndNewKeys[];
+extern const char kInvalidPageActionPopup[];
+extern const char kInvalidPageActionPopupHeight[];
+extern const char kInvalidPageActionPopupPath[];
+extern const char kInvalidPermissionWithDetail[];
+extern const char kInvalidPermission[];
+extern const char kInvalidPermissions[];
+extern const char kInvalidPermissionScheme[];
+extern const char kInvalidPlugins[];
+extern const char kInvalidPluginsPath[];
+extern const char kInvalidPluginsPublic[];
+extern const char kInvalidRequirement[];
+extern const char kInvalidRequirements[];
+extern const char kInvalidRunAt[];
+extern const char kInvalidSandboxedPagesList[];
+extern const char kInvalidSandboxedPage[];
+extern const char kInvalidSandboxedPagesCSP[];
+extern const char kInvalidSearchEngineMissingKeys[];
+extern const char kInvalidSearchEngineURL[];
+extern const char kInvalidShortName[];
+extern const char kInvalidSignature[];
+extern const char kInvalidSpellcheck[];
+extern const char kInvalidSpellcheckDictionaryFormat[];
+extern const char kInvalidSpellcheckDictionaryLanguage[];
+extern const char kInvalidSpellcheckDictionaryLocale[];
+extern const char kInvalidSpellcheckDictionaryPath[];
+extern const char kInvalidStartupOverrideURL[];
+extern const char kInvalidSystemIndicator[];
+extern const char kInvalidTheme[];
+extern const char kInvalidThemeColors[];
+extern const char kInvalidThemeImages[];
+extern const char kInvalidThemeImagesMissing[];
+extern const char kInvalidThemeTints[];
+extern const char kInvalidTts[];
+extern const char kInvalidTtsVoices[];
+extern const char kInvalidTtsVoicesEventTypes[];
+extern const char kInvalidTtsVoicesGender[];
+extern const char kInvalidTtsVoicesLang[];
+extern const char kInvalidTtsVoicesRemote[];
+extern const char kInvalidTtsVoicesVoiceName[];
+extern const char kInvalidUpdateURL[];
+extern const char kInvalidURLPatternError[];
+extern const char kInvalidURLHandlers[];
+extern const char kInvalidURLHandlerPatternElement[];
+extern const char kInvalidURLHandlerTitle[];
+extern const char kInvalidURLHandlerPattern[];
+extern const char kInvalidVersion[];
+extern const char kInvalidVersionName[];
+extern const char kInvalidWebAccessibleResourcesList[];
+extern const char kInvalidWebAccessibleResource[];
+extern const char kInvalidWebview[];
+extern const char kInvalidWebviewAccessibleResourcesList[];
+extern const char kInvalidWebviewAccessibleResource[];
+extern const char kInvalidWebviewPartition[];
+extern const char kInvalidWebviewPartitionName[];
+extern const char kInvalidWebviewPartitionsList[];
+extern const char kInvalidWebURL[];
+extern const char kInvalidWebURLs[];
+extern const char kInvalidZipHash[];
+extern const char kInsecureContentSecurityPolicy[];
+extern const char kKeyIsDeprecatedWithReplacement[];
+extern const char kLauncherPagePageRequired[];
+extern const char kLaunchPathAndExtentAreExclusive[];
+extern const char kLaunchPathAndURLAreExclusive[];
+extern const char kLaunchURLRequired[];
+extern const char kLocalesInvalidLocale[];
+extern const char kLocalesMessagesFileMissing[];
+extern const char kLocalesNoDefaultLocaleSpecified[];
+extern const char kLocalesNoDefaultMessages[];
+extern const char kLocalesNoValidLocaleNamesListed[];
+extern const char kLocalesTreeMissing[];
+extern const char kManifestParseError[];
+extern const char kManifestUnreadable[];
+extern const char kMissingFile[];
+extern const char kMultipleOverrides[];
+extern const char kNoPermissionForMIMETypes[];
+extern const char kNoWildCardsInPaths[];
+extern const char kOneUISurfaceOnly[];
+extern const char kPermissionMustBeOptional[];
+extern const char kPermissionNotAllowed[];
+extern const char kPermissionNotAllowedInManifest[];
+extern const char kPermissionUnknownOrMalformed[];
+extern const char kReservedMessageFound[];
+extern const char kUnrecognizedManifestKey[];
+extern const char kUnrecognizedManifestProperty[];
+extern const char kWebRequestConflictsWithLazyBackground[];
+#if defined(OS_CHROMEOS)
+extern const char kIllegalPlugins[];
+extern const char kInvalidFileSystemProviderMissingCapabilities[];
+extern const char kInvalidFileSystemProviderMissingPermission[];
+#endif
+
+} // namespace manifest_errors
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_CONSTANTS_H_
diff --git a/chromium/extensions/common/manifest_handler.cc b/chromium/extensions/common/manifest_handler.cc
new file mode 100644
index 00000000000..815146e30e1
--- /dev/null
+++ b/chromium/extensions/common/manifest_handler.cc
@@ -0,0 +1,252 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handler.h"
+
+#include <stddef.h>
+
+#include <map>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/permissions/manifest_permission.h"
+#include "extensions/common/permissions/manifest_permission_set.h"
+
+namespace extensions {
+
+namespace {
+
+static base::LazyInstance<ManifestHandlerRegistry> g_registry =
+ LAZY_INSTANCE_INITIALIZER;
+static ManifestHandlerRegistry* g_registry_override = NULL;
+
+ManifestHandlerRegistry* GetRegistry() {
+ if (!g_registry_override)
+ return g_registry.Pointer();
+ return g_registry_override;
+}
+
+} // namespace
+
+ManifestHandler::ManifestHandler() {
+}
+
+ManifestHandler::~ManifestHandler() {
+}
+
+bool ManifestHandler::Validate(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const {
+ return true;
+}
+
+bool ManifestHandler::AlwaysParseForType(Manifest::Type type) const {
+ return false;
+}
+
+bool ManifestHandler::AlwaysValidateForType(Manifest::Type type) const {
+ return false;
+}
+
+const std::vector<std::string> ManifestHandler::PrerequisiteKeys() const {
+ return std::vector<std::string>();
+}
+
+void ManifestHandler::Register() {
+ linked_ptr<ManifestHandler> this_linked(this);
+ const std::vector<std::string> keys = Keys();
+ for (size_t i = 0; i < keys.size(); ++i)
+ GetRegistry()->RegisterManifestHandler(keys[i], this_linked);
+}
+
+ManifestPermission* ManifestHandler::CreatePermission() {
+ return NULL;
+}
+
+ManifestPermission* ManifestHandler::CreateInitialRequiredPermission(
+ const Extension* extension) {
+ return NULL;
+}
+
+// static
+void ManifestHandler::FinalizeRegistration() {
+ GetRegistry()->Finalize();
+}
+
+// static
+bool ManifestHandler::IsRegistrationFinalized() {
+ return GetRegistry()->is_finalized_;
+}
+
+// static
+bool ManifestHandler::ParseExtension(Extension* extension,
+ base::string16* error) {
+ return GetRegistry()->ParseExtension(extension, error);
+}
+
+// static
+bool ManifestHandler::ValidateExtension(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) {
+ return GetRegistry()->ValidateExtension(extension, error, warnings);
+}
+
+// static
+ManifestPermission* ManifestHandler::CreatePermission(const std::string& name) {
+ return GetRegistry()->CreatePermission(name);
+}
+
+// static
+void ManifestHandler::AddExtensionInitialRequiredPermissions(
+ const Extension* extension, ManifestPermissionSet* permission_set) {
+ return GetRegistry()->AddExtensionInitialRequiredPermissions(extension,
+ permission_set);
+}
+
+// static
+const std::vector<std::string> ManifestHandler::SingleKey(
+ const std::string& key) {
+ return std::vector<std::string>(1, key);
+}
+
+ManifestHandlerRegistry::ManifestHandlerRegistry() : is_finalized_(false) {
+}
+
+ManifestHandlerRegistry::~ManifestHandlerRegistry() {
+}
+
+void ManifestHandlerRegistry::Finalize() {
+ CHECK(!is_finalized_);
+ SortManifestHandlers();
+ is_finalized_ = true;
+}
+
+void ManifestHandlerRegistry::RegisterManifestHandler(
+ const std::string& key, linked_ptr<ManifestHandler> handler) {
+ CHECK(!is_finalized_);
+ handlers_[key] = handler;
+}
+
+bool ManifestHandlerRegistry::ParseExtension(Extension* extension,
+ base::string16* error) {
+ std::map<int, ManifestHandler*> handlers_by_priority;
+ for (ManifestHandlerMap::iterator iter = handlers_.begin();
+ iter != handlers_.end(); ++iter) {
+ ManifestHandler* handler = iter->second.get();
+ if (extension->manifest()->HasPath(iter->first) ||
+ handler->AlwaysParseForType(extension->GetType())) {
+ handlers_by_priority[priority_map_[handler]] = handler;
+ }
+ }
+ for (std::map<int, ManifestHandler*>::iterator iter =
+ handlers_by_priority.begin();
+ iter != handlers_by_priority.end(); ++iter) {
+ if (!(iter->second)->Parse(extension, error))
+ return false;
+ }
+ return true;
+}
+
+bool ManifestHandlerRegistry::ValidateExtension(
+ const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) {
+ std::set<ManifestHandler*> handlers;
+ for (ManifestHandlerMap::iterator iter = handlers_.begin();
+ iter != handlers_.end(); ++iter) {
+ ManifestHandler* handler = iter->second.get();
+ if (extension->manifest()->HasPath(iter->first) ||
+ handler->AlwaysValidateForType(extension->GetType())) {
+ handlers.insert(handler);
+ }
+ }
+ for (std::set<ManifestHandler*>::iterator iter = handlers.begin();
+ iter != handlers.end(); ++iter) {
+ if (!(*iter)->Validate(extension, error, warnings))
+ return false;
+ }
+ return true;
+}
+
+ManifestPermission* ManifestHandlerRegistry::CreatePermission(
+ const std::string& name) {
+ ManifestHandlerMap::const_iterator it = handlers_.find(name);
+ if (it == handlers_.end())
+ return NULL;
+
+ return it->second->CreatePermission();
+}
+
+void ManifestHandlerRegistry::AddExtensionInitialRequiredPermissions(
+ const Extension* extension, ManifestPermissionSet* permission_set) {
+ for (ManifestHandlerMap::const_iterator it = handlers_.begin();
+ it != handlers_.end(); ++it) {
+ ManifestPermission* permission =
+ it->second->CreateInitialRequiredPermission(extension);
+ if (permission) {
+ permission_set->insert(permission);
+ }
+ }
+}
+
+// static
+ManifestHandlerRegistry* ManifestHandlerRegistry::SetForTesting(
+ ManifestHandlerRegistry* new_registry) {
+ ManifestHandlerRegistry* old_registry = GetRegistry();
+ if (new_registry != g_registry.Pointer())
+ g_registry_override = new_registry;
+ else
+ g_registry_override = NULL;
+ return old_registry;
+}
+
+void ManifestHandlerRegistry::SortManifestHandlers() {
+ std::set<ManifestHandler*> unsorted_handlers;
+ for (ManifestHandlerMap::const_iterator iter = handlers_.begin();
+ iter != handlers_.end(); ++iter) {
+ unsorted_handlers.insert(iter->second.get());
+ }
+
+ int priority = 0;
+ while (true) {
+ std::set<ManifestHandler*> next_unsorted_handlers;
+ for (std::set<ManifestHandler*>::const_iterator iter =
+ unsorted_handlers.begin();
+ iter != unsorted_handlers.end(); ++iter) {
+ ManifestHandler* handler = *iter;
+ const std::vector<std::string>& prerequisites =
+ handler->PrerequisiteKeys();
+ int unsatisfied = prerequisites.size();
+ for (size_t i = 0; i < prerequisites.size(); ++i) {
+ ManifestHandlerMap::const_iterator prereq_iter =
+ handlers_.find(prerequisites[i]);
+ // If the prerequisite does not exist, crash.
+ CHECK(prereq_iter != handlers_.end())
+ << "Extension manifest handler depends on unrecognized key "
+ << prerequisites[i];
+ // Prerequisite is in our map.
+ if (ContainsKey(priority_map_, prereq_iter->second.get()))
+ unsatisfied--;
+ }
+ if (unsatisfied == 0) {
+ priority_map_[handler] = priority;
+ priority++;
+ } else {
+ // Put in the list for next time.
+ next_unsorted_handlers.insert(handler);
+ }
+ }
+ if (next_unsorted_handlers.size() == unsorted_handlers.size())
+ break;
+ unsorted_handlers.swap(next_unsorted_handlers);
+ }
+
+ // If there are any leftover unsorted handlers, they must have had
+ // circular dependencies.
+ CHECK_EQ(unsorted_handlers.size(), std::set<ManifestHandler*>::size_type(0))
+ << "Extension manifest handlers have circular dependencies!";
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handler.h b/chromium/extensions/common/manifest_handler.h
new file mode 100644
index 00000000000..4c7da31eee0
--- /dev/null
+++ b/chromium/extensions/common/manifest_handler.h
@@ -0,0 +1,173 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLER_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLER_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/lazy_instance.h"
+#include "base/memory/linked_ptr.h"
+#include "base/strings/string16.h"
+#include "extensions/common/manifest.h"
+
+namespace extensions {
+class Extension;
+class ManifestPermission;
+class ManifestPermissionSet;
+
+// An interface for clients that recognize and parse keys in extension
+// manifests.
+class ManifestHandler {
+ public:
+ ManifestHandler();
+ virtual ~ManifestHandler();
+
+ // Attempts to parse the extension's manifest.
+ // Returns true on success or false on failure; if false, |error| will
+ // be set to a failure message.
+ virtual bool Parse(Extension* extension, base::string16* error) = 0;
+
+ // Validate that files associated with this manifest key exist.
+ // Validation takes place after parsing. May also append a series of
+ // warning messages to |warnings|.
+ //
+ // Otherwise, returns false, and a description of the error is
+ // returned in |error|.
+ // TODO(yoz): Change error to base::string16. See crbug.com/71980.
+ virtual bool Validate(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const;
+
+ // If false (the default), only parse the manifest if a registered
+ // key is present in the manifest. If true, always attempt to parse
+ // the manifest for this extension type, even if no registered keys
+ // are present. This allows specifying a default parsed value for
+ // extensions that don't declare our key in the manifest.
+ // TODO(yoz): Use Feature availability instead.
+ virtual bool AlwaysParseForType(Manifest::Type type) const;
+
+ // Same as AlwaysParseForType, but for Validate instead of Parse.
+ virtual bool AlwaysValidateForType(Manifest::Type type) const;
+
+ // The list of keys that, if present, should be parsed before calling our
+ // Parse (typically, because our Parse needs to read those keys).
+ // Defaults to empty.
+ virtual const std::vector<std::string> PrerequisiteKeys() const;
+
+ // Associate us with our keys() in the manifest. A handler can register
+ // for multiple keys. The global registry takes ownership of this;
+ // if it has an existing handler for |key|, it replaces it with this.
+ // Manifest handlers must be registered at process startup in
+ // common_manifest_handlers.cc or chrome_manifest_handlers.cc:
+ // (new MyManifestHandler)->Register();
+ void Register();
+
+ // Creates a |ManifestPermission| instance for the given manifest key |name|.
+ // The returned permission does not contain any permission data, so this
+ // method is usually used before calling |FromValue| or |Read|. Returns
+ // |NULL| if the manifest handler does not support custom permissions.
+ virtual ManifestPermission* CreatePermission();
+
+ // Creates a |ManifestPermission| instance containing the initial set of
+ // required manifest permissions for the given |extension|. Returns |NULL| if
+ // the manifest handler does not support custom permissions or if there was
+ // no manifest key in the extension manifest for this handler.
+ virtual ManifestPermission* CreateInitialRequiredPermission(
+ const Extension* extension);
+
+ // Calling FinalizeRegistration indicates that there are no more
+ // manifest handlers to be registered.
+ static void FinalizeRegistration();
+
+ static bool IsRegistrationFinalized();
+
+ // Call Parse on all registered manifest handlers that should parse
+ // this extension.
+ static bool ParseExtension(Extension* extension, base::string16* error);
+
+ // Call Validate on all registered manifest handlers for this extension.
+ static bool ValidateExtension(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings);
+
+ // Calls |CreatePermission| on the manifest handler for |key|. Returns |NULL|
+ // if there is no manifest handler for |key| or if the manifest handler for
+ // |key| does not support custom permissions.
+ static ManifestPermission* CreatePermission(const std::string& key);
+
+ // Calls |CreateInitialRequiredPermission| on all registered manifest handlers
+ // and adds the returned permissions to |permission_set|. Note this should be
+ // called after all manifest data elements have been read, parsed and stored
+ // in the manifest data property of |extension|, as manifest handlers need
+ // access to their manifest data to initialize their required manifest
+ // permission.
+ static void AddExtensionInitialRequiredPermissions(
+ const Extension* extension, ManifestPermissionSet* permission_set);
+
+ protected:
+ // A convenience method for handlers that only register for 1 key,
+ // so that they can define keys() { return SingleKey(kKey); }
+ static const std::vector<std::string> SingleKey(const std::string& key);
+
+ private:
+ // The keys to register us for (in Register).
+ virtual const std::vector<std::string> Keys() const = 0;
+};
+
+// The global registry for manifest handlers.
+class ManifestHandlerRegistry {
+ private:
+ friend class ManifestHandler;
+ friend class ScopedTestingManifestHandlerRegistry;
+ friend struct base::DefaultLazyInstanceTraits<ManifestHandlerRegistry>;
+
+ ManifestHandlerRegistry();
+ ~ManifestHandlerRegistry();
+
+ void Finalize();
+
+ void RegisterManifestHandler(const std::string& key,
+ linked_ptr<ManifestHandler> handler);
+ bool ParseExtension(Extension* extension, base::string16* error);
+ bool ValidateExtension(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings);
+
+ ManifestPermission* CreatePermission(const std::string& key);
+
+ void AddExtensionInitialRequiredPermissions(
+ const Extension* extension,
+ ManifestPermissionSet* permission_set);
+
+ // Overrides the current global ManifestHandlerRegistry with
+ // |registry|, returning the current one.
+ static ManifestHandlerRegistry* SetForTesting(
+ ManifestHandlerRegistry* new_registry);
+
+ typedef std::map<std::string, linked_ptr<ManifestHandler> >
+ ManifestHandlerMap;
+ typedef std::map<ManifestHandler*, int> ManifestHandlerPriorityMap;
+
+ // Puts the manifest handlers in order such that each handler comes after
+ // any handlers for their PrerequisiteKeys. If there is no handler for
+ // a prerequisite key, that dependency is simply ignored.
+ // CHECKs that there are no manifest handlers with circular dependencies.
+ void SortManifestHandlers();
+
+ // All registered manifest handlers.
+ ManifestHandlerMap handlers_;
+
+ // The priority for each manifest handler. Handlers with lower priority
+ // values are evaluated first.
+ ManifestHandlerPriorityMap priority_map_;
+
+ bool is_finalized_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLER_H_
diff --git a/chromium/extensions/common/manifest_handler_helpers.cc b/chromium/extensions/common/manifest_handler_helpers.cc
new file mode 100644
index 00000000000..4a644b819ff
--- /dev/null
+++ b/chromium/extensions/common/manifest_handler_helpers.cc
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handler_helpers.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_icon_set.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+
+namespace manifest_handler_helpers {
+
+bool NormalizeAndValidatePath(std::string* path) {
+ size_t first_non_slash = path->find_first_not_of('/');
+ if (first_non_slash == std::string::npos) {
+ *path = "";
+ return false;
+ }
+
+ *path = path->substr(first_non_slash);
+ return true;
+}
+
+bool LoadIconsFromDictionary(const base::DictionaryValue* icons_value,
+ ExtensionIconSet* icons,
+ base::string16* error) {
+ DCHECK(icons);
+ DCHECK(error);
+ for (base::DictionaryValue::Iterator iterator(*icons_value);
+ !iterator.IsAtEnd(); iterator.Advance()) {
+ int size = 0;
+ std::string icon_path;
+ if (!base::StringToInt(iterator.key(), &size) || size <= 0 ||
+ size > extension_misc::EXTENSION_ICON_GIGANTOR * 4) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidIconKey,
+ iterator.key());
+ return false;
+ }
+ if (!iterator.value().GetAsString(&icon_path) ||
+ !NormalizeAndValidatePath(&icon_path)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidIconPath,
+ iterator.key());
+ return false;
+ }
+
+ icons->Add(size, icon_path);
+ }
+ return true;
+}
+
+} // namespace manifest_handler_helpers
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handler_helpers.h b/chromium/extensions/common/manifest_handler_helpers.h
new file mode 100644
index 00000000000..b0249c30c96
--- /dev/null
+++ b/chromium/extensions/common/manifest_handler_helpers.h
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLER_HELPERS_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLER_HELPERS_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+
+class ExtensionIconSet;
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace extensions {
+namespace manifest_handler_helpers {
+
+// Strips leading slashes from the file path. Returns true iff the final path is
+// non empty.
+bool NormalizeAndValidatePath(std::string* path);
+
+// Loads icon paths defined in dictionary |icons_value| into ExtensionIconSet
+// |icons|. |icons_value| is a dictionary value {icon size -> icon path}.
+// Returns success. If load fails, |error| will be set.
+bool LoadIconsFromDictionary(const base::DictionaryValue* icons_value,
+ ExtensionIconSet* icons,
+ base::string16* error);
+
+} // namespace manifest_handler_helpers
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLER_HELPERS_H_
diff --git a/chromium/extensions/common/manifest_handler_unittest.cc b/chromium/extensions/common/manifest_handler_unittest.cc
new file mode 100644
index 00000000000..cbccf1204b2
--- /dev/null
+++ b/chromium/extensions/common/manifest_handler_unittest.cc
@@ -0,0 +1,281 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/install_warning.h"
+#include "extensions/common/manifest_handler.h"
+#include "extensions/common/value_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+std::vector<std::string> SingleKey(const std::string& key) {
+ return std::vector<std::string>(1, key);
+}
+
+} // namespace
+
+class ScopedTestingManifestHandlerRegistry {
+ public:
+ ScopedTestingManifestHandlerRegistry() {
+ old_registry_ = ManifestHandlerRegistry::SetForTesting(&registry_);
+ }
+
+ ~ScopedTestingManifestHandlerRegistry() {
+ ManifestHandlerRegistry::SetForTesting(old_registry_);
+ }
+
+ ManifestHandlerRegistry registry_;
+ ManifestHandlerRegistry* old_registry_;
+};
+
+class ManifestHandlerTest : public testing::Test {
+ public:
+ class ParsingWatcher {
+ public:
+ // Called when a manifest handler parses.
+ void Record(const std::string& name) {
+ parsed_names_.push_back(name);
+ }
+
+ const std::vector<std::string>& parsed_names() {
+ return parsed_names_;
+ }
+
+ // Returns true if |name_before| was parsed before |name_after|.
+ bool ParsedBefore(const std::string& name_before,
+ const std::string& name_after) {
+ size_t i_before = parsed_names_.size();
+ size_t i_after = 0;
+ for (size_t i = 0; i < parsed_names_.size(); ++i) {
+ if (parsed_names_[i] == name_before)
+ i_before = i;
+ if (parsed_names_[i] == name_after)
+ i_after = i;
+ }
+ if (i_before < i_after)
+ return true;
+ return false;
+ }
+
+ private:
+ // The order of manifest handlers that we watched parsing.
+ std::vector<std::string> parsed_names_;
+ };
+
+ class TestManifestHandler : public ManifestHandler {
+ public:
+ TestManifestHandler(const std::string& name,
+ const std::vector<std::string>& keys,
+ const std::vector<std::string>& prereqs,
+ ParsingWatcher* watcher)
+ : name_(name), keys_(keys), prereqs_(prereqs), watcher_(watcher) {
+ }
+
+ bool Parse(Extension* extension, base::string16* error) override {
+ watcher_->Record(name_);
+ return true;
+ }
+
+ const std::vector<std::string> PrerequisiteKeys() const override {
+ return prereqs_;
+ }
+
+ protected:
+ std::string name_;
+ std::vector<std::string> keys_;
+ std::vector<std::string> prereqs_;
+ ParsingWatcher* watcher_;
+
+ const std::vector<std::string> Keys() const override { return keys_; }
+ };
+
+ class FailingTestManifestHandler : public TestManifestHandler {
+ public:
+ FailingTestManifestHandler(const std::string& name,
+ const std::vector<std::string>& keys,
+ const std::vector<std::string>& prereqs,
+ ParsingWatcher* watcher)
+ : TestManifestHandler(name, keys, prereqs, watcher) {
+ }
+ bool Parse(Extension* extension, base::string16* error) override {
+ *error = base::ASCIIToUTF16(name_);
+ return false;
+ }
+ };
+
+ class AlwaysParseTestManifestHandler : public TestManifestHandler {
+ public:
+ AlwaysParseTestManifestHandler(const std::string& name,
+ const std::vector<std::string>& keys,
+ const std::vector<std::string>& prereqs,
+ ParsingWatcher* watcher)
+ : TestManifestHandler(name, keys, prereqs, watcher) {
+ }
+
+ bool AlwaysParseForType(Manifest::Type type) const override { return true; }
+ };
+
+ class TestManifestValidator : public ManifestHandler {
+ public:
+ TestManifestValidator(bool return_value,
+ bool always_validate,
+ std::vector<std::string> keys)
+ : return_value_(return_value),
+ always_validate_(always_validate),
+ keys_(keys) {
+ }
+
+ bool Parse(Extension* extension, base::string16* error) override {
+ return true;
+ }
+
+ bool Validate(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const override {
+ return return_value_;
+ }
+
+ bool AlwaysValidateForType(Manifest::Type type) const override {
+ return always_validate_;
+ }
+
+ private:
+ const std::vector<std::string> Keys() const override { return keys_; }
+
+ protected:
+ bool return_value_;
+ bool always_validate_;
+ std::vector<std::string> keys_;
+ };
+};
+
+TEST_F(ManifestHandlerTest, DependentHandlers) {
+ ScopedTestingManifestHandlerRegistry registry;
+ ParsingWatcher watcher;
+ std::vector<std::string> prereqs;
+ (new TestManifestHandler("A", SingleKey("a"), prereqs, &watcher))->Register();
+ (new TestManifestHandler("B", SingleKey("b"), prereqs, &watcher))->Register();
+ (new TestManifestHandler("J", SingleKey("j"), prereqs, &watcher))->Register();
+ (new AlwaysParseTestManifestHandler("K", SingleKey("k"), prereqs, &watcher))->
+ Register();
+ prereqs.push_back("c.d");
+ std::vector<std::string> keys;
+ keys.push_back("c.e");
+ keys.push_back("c.z");
+ (new TestManifestHandler("C.EZ", keys, prereqs, &watcher))->Register();
+ prereqs.clear();
+ prereqs.push_back("b");
+ prereqs.push_back("k");
+ (new TestManifestHandler("C.D", SingleKey("c.d"), prereqs, &watcher))->
+ Register();
+ ManifestHandler::FinalizeRegistration();
+
+ scoped_refptr<Extension> extension =
+ ExtensionBuilder()
+ .SetManifest(DictionaryBuilder()
+ .Set("name", "no name")
+ .Set("version", "0")
+ .Set("manifest_version", 2)
+ .Set("a", 1)
+ .Set("b", 2)
+ .Set("c", DictionaryBuilder()
+ .Set("d", 3)
+ .Set("e", 4)
+ .Set("f", 5)
+ .Build())
+ .Set("g", 6)
+ .Build())
+ .Build();
+
+ // A, B, C.EZ, C.D, K
+ EXPECT_EQ(5u, watcher.parsed_names().size());
+ EXPECT_TRUE(watcher.ParsedBefore("B", "C.D"));
+ EXPECT_TRUE(watcher.ParsedBefore("K", "C.D"));
+ EXPECT_TRUE(watcher.ParsedBefore("C.D", "C.EZ"));
+}
+
+TEST_F(ManifestHandlerTest, FailingHandlers) {
+ ScopedTestingManifestHandlerRegistry registry;
+ // Can't use ExtensionBuilder, because this extension will fail to
+ // be parsed.
+ scoped_ptr<base::DictionaryValue> manifest_a(
+ DictionaryBuilder()
+ .Set("name", "no name")
+ .Set("version", "0")
+ .Set("manifest_version", 2)
+ .Set("a", 1)
+ .Build());
+
+ // Succeeds when "a" is not recognized.
+ std::string error;
+ scoped_refptr<Extension> extension = Extension::Create(
+ base::FilePath(),
+ Manifest::INVALID_LOCATION,
+ *manifest_a,
+ Extension::NO_FLAGS,
+ &error);
+ EXPECT_TRUE(extension.get());
+
+ // Register a handler for "a" that fails.
+ ParsingWatcher watcher;
+ (new FailingTestManifestHandler(
+ "A", SingleKey("a"), std::vector<std::string>(), &watcher))->Register();
+ ManifestHandler::FinalizeRegistration();
+
+ extension = Extension::Create(
+ base::FilePath(),
+ Manifest::INVALID_LOCATION,
+ *manifest_a,
+ Extension::NO_FLAGS,
+ &error);
+ EXPECT_FALSE(extension.get());
+ EXPECT_EQ("A", error);
+}
+
+TEST_F(ManifestHandlerTest, Validate) {
+ ScopedTestingManifestHandlerRegistry registry;
+ scoped_refptr<Extension> extension =
+ ExtensionBuilder()
+ .SetManifest(DictionaryBuilder()
+ .Set("name", "no name")
+ .Set("version", "0")
+ .Set("manifest_version", 2)
+ .Set("a", 1)
+ .Set("b", 2)
+ .Build())
+ .Build();
+ EXPECT_TRUE(extension.get());
+
+ std::string error;
+ std::vector<InstallWarning> warnings;
+ // Always validates and fails.
+ (new TestManifestValidator(false, true, SingleKey("c")))->Register();
+ EXPECT_FALSE(
+ ManifestHandler::ValidateExtension(extension.get(), &error, &warnings));
+
+ // This overrides the registered handler for "c".
+ (new TestManifestValidator(false, false, SingleKey("c")))->Register();
+ EXPECT_TRUE(
+ ManifestHandler::ValidateExtension(extension.get(), &error, &warnings));
+
+ // Validates "a" and fails.
+ (new TestManifestValidator(false, true, SingleKey("a")))->Register();
+ EXPECT_FALSE(
+ ManifestHandler::ValidateExtension(extension.get(), &error, &warnings));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/app_isolation_info.cc b/chromium/extensions/common/manifest_handlers/app_isolation_info.cc
new file mode 100644
index 00000000000..16bd3755f25
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/app_isolation_info.cc
@@ -0,0 +1,99 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/app_isolation_info.h"
+
+#include <stddef.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/permissions_parser.h"
+#include "extensions/common/permissions/api_permission_set.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+
+AppIsolationInfo::AppIsolationInfo(bool isolated_storage)
+ : has_isolated_storage(isolated_storage) {
+}
+
+AppIsolationInfo::~AppIsolationInfo() {
+}
+
+// static
+bool AppIsolationInfo::HasIsolatedStorage(const Extension* extension) {
+ AppIsolationInfo* info = static_cast<AppIsolationInfo*>(
+ extension->GetManifestData(keys::kIsolation));
+ return info ? info->has_isolated_storage : false;
+}
+
+AppIsolationHandler::AppIsolationHandler() {
+}
+
+AppIsolationHandler::~AppIsolationHandler() {
+}
+
+bool AppIsolationHandler::Parse(Extension* extension, base::string16* error) {
+ // Platform apps always get isolated storage.
+ if (extension->is_platform_app()) {
+ extension->SetManifestData(keys::kIsolation, new AppIsolationInfo(true));
+ return true;
+ }
+
+ // Other apps only get it if it is requested _and_ experimental APIs are
+ // enabled.
+ if (!extension->is_app() ||
+ !PermissionsParser::HasAPIPermission(extension,
+ APIPermission::kExperimental)) {
+ return true;
+ }
+
+ // We should only be parsing if the extension has the key in the manifest,
+ // or is a platform app (which we already handled).
+ DCHECK(extension->manifest()->HasPath(keys::kIsolation));
+
+ const base::ListValue* isolation_list = NULL;
+ if (!extension->manifest()->GetList(keys::kIsolation, &isolation_list)) {
+ *error = base::ASCIIToUTF16(manifest_errors::kInvalidIsolation);
+ return false;
+ }
+
+ bool has_isolated_storage = false;
+ for (size_t i = 0; i < isolation_list->GetSize(); ++i) {
+ std::string isolation_string;
+ if (!isolation_list->GetString(i, &isolation_string)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ manifest_errors::kInvalidIsolationValue, base::SizeTToString(i));
+ return false;
+ }
+
+ // Check for isolated storage.
+ if (isolation_string == manifest_values::kIsolatedStorage) {
+ has_isolated_storage = true;
+ } else {
+ DLOG(WARNING) << "Did not recognize isolation type: " << isolation_string;
+ }
+ }
+
+ if (has_isolated_storage)
+ extension->SetManifestData(keys::kIsolation, new AppIsolationInfo(true));
+
+ return true;
+}
+
+bool AppIsolationHandler::AlwaysParseForType(Manifest::Type type) const {
+ return type == Manifest::TYPE_PLATFORM_APP;
+}
+
+const std::vector<std::string> AppIsolationHandler::Keys() const {
+ return SingleKey(keys::kIsolation);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/app_isolation_info.h b/chromium/extensions/common/manifest_handlers/app_isolation_info.h
new file mode 100644
index 00000000000..dcbb475123d
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/app_isolation_info.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_APP_ISOLATION_INFO_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_APP_ISOLATION_INFO_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+struct AppIsolationInfo : public Extension::ManifestData {
+ explicit AppIsolationInfo(bool isolated_storage);
+ ~AppIsolationInfo() override;
+
+ static bool HasIsolatedStorage(const Extension* extension);
+
+ // Whether this extension requests isolated storage.
+ bool has_isolated_storage;
+};
+
+// Parses the "isolation" manifest key.
+class AppIsolationHandler : public ManifestHandler {
+ public:
+ AppIsolationHandler();
+ ~AppIsolationHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+ bool AlwaysParseForType(Manifest::Type type) const override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(AppIsolationHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_APP_ISOLATION_INFO_H_
diff --git a/chromium/extensions/common/manifest_handlers/background_info.cc b/chromium/extensions/common/manifest_handlers/background_info.cc
new file mode 100644
index 00000000000..c33d22c2436
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/background_info.cc
@@ -0,0 +1,342 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/background_info.h"
+
+#include <stddef.h>
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/file_util.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/permissions_parser.h"
+#include "extensions/common/permissions/api_permission_set.h"
+#include "extensions/common/switches.h"
+#include "grit/extensions_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using base::ASCIIToUTF16;
+using base::DictionaryValue;
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace values = manifest_values;
+namespace errors = manifest_errors;
+
+namespace {
+
+const char kBackground[] = "background";
+
+static base::LazyInstance<BackgroundInfo> g_empty_background_info =
+ LAZY_INSTANCE_INITIALIZER;
+
+const BackgroundInfo& GetBackgroundInfo(const Extension* extension) {
+ BackgroundInfo* info = static_cast<BackgroundInfo*>(
+ extension->GetManifestData(kBackground));
+ if (!info)
+ return g_empty_background_info.Get();
+ return *info;
+}
+
+} // namespace
+
+BackgroundInfo::BackgroundInfo()
+ : is_persistent_(true),
+ allow_js_access_(true) {
+}
+
+BackgroundInfo::~BackgroundInfo() {
+}
+
+// static
+GURL BackgroundInfo::GetBackgroundURL(const Extension* extension) {
+ const BackgroundInfo& info = GetBackgroundInfo(extension);
+ if (info.background_scripts_.empty())
+ return info.background_url_;
+ return extension->GetResourceURL(kGeneratedBackgroundPageFilename);
+}
+
+// static
+const std::vector<std::string>& BackgroundInfo::GetBackgroundScripts(
+ const Extension* extension) {
+ return GetBackgroundInfo(extension).background_scripts_;
+}
+
+// static
+bool BackgroundInfo::HasBackgroundPage(const Extension* extension) {
+ return GetBackgroundInfo(extension).has_background_page();
+}
+
+// static
+bool BackgroundInfo::HasPersistentBackgroundPage(const Extension* extension) {
+ return GetBackgroundInfo(extension).has_persistent_background_page();
+}
+
+// static
+bool BackgroundInfo::HasLazyBackgroundPage(const Extension* extension) {
+ return GetBackgroundInfo(extension).has_lazy_background_page();
+}
+
+// static
+bool BackgroundInfo::HasGeneratedBackgroundPage(const Extension* extension) {
+ const BackgroundInfo& info = GetBackgroundInfo(extension);
+ return !info.background_scripts_.empty();
+}
+
+// static
+bool BackgroundInfo::AllowJSAccess(const Extension* extension) {
+ return GetBackgroundInfo(extension).allow_js_access_;
+}
+
+bool BackgroundInfo::Parse(const Extension* extension, base::string16* error) {
+ const std::string& bg_scripts_key = extension->is_platform_app() ?
+ keys::kPlatformAppBackgroundScripts : keys::kBackgroundScripts;
+ if (!LoadBackgroundScripts(extension, bg_scripts_key, error) ||
+ !LoadBackgroundPage(extension, error) ||
+ !LoadBackgroundPersistent(extension, error) ||
+ !LoadAllowJSAccess(extension, error)) {
+ return false;
+ }
+
+ int background_solution_sum = (background_url_.is_valid() ? 1 : 0) +
+ (!background_scripts_.empty() ? 1 : 0);
+ if (background_solution_sum > 1) {
+ *error = ASCIIToUTF16(errors::kInvalidBackgroundCombination);
+ return false;
+ }
+
+ return true;
+}
+
+bool BackgroundInfo::LoadBackgroundScripts(const Extension* extension,
+ const std::string& key,
+ base::string16* error) {
+ const base::Value* background_scripts_value = NULL;
+ if (!extension->manifest()->Get(key, &background_scripts_value))
+ return true;
+
+ CHECK(background_scripts_value);
+ if (background_scripts_value->GetType() != base::Value::TYPE_LIST) {
+ *error = ASCIIToUTF16(errors::kInvalidBackgroundScripts);
+ return false;
+ }
+
+ const base::ListValue* background_scripts = NULL;
+ background_scripts_value->GetAsList(&background_scripts);
+ for (size_t i = 0; i < background_scripts->GetSize(); ++i) {
+ std::string script;
+ if (!background_scripts->GetString(i, &script)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidBackgroundScript, base::SizeTToString(i));
+ return false;
+ }
+ background_scripts_.push_back(script);
+ }
+
+ return true;
+}
+
+bool BackgroundInfo::LoadBackgroundPage(const Extension* extension,
+ const std::string& key,
+ base::string16* error) {
+ const base::Value* background_page_value = NULL;
+ if (!extension->manifest()->Get(key, &background_page_value))
+ return true;
+
+ std::string background_str;
+ if (!background_page_value->GetAsString(&background_str)) {
+ *error = ASCIIToUTF16(errors::kInvalidBackground);
+ return false;
+ }
+
+ if (extension->is_hosted_app()) {
+ background_url_ = GURL(background_str);
+
+ if (!PermissionsParser::HasAPIPermission(extension,
+ APIPermission::kBackground)) {
+ *error = ASCIIToUTF16(errors::kBackgroundPermissionNeeded);
+ return false;
+ }
+ // Hosted apps require an absolute URL.
+ if (!background_url_.is_valid()) {
+ *error = ASCIIToUTF16(errors::kInvalidBackgroundInHostedApp);
+ return false;
+ }
+
+ if (!(background_url_.SchemeIs("https") ||
+ (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAllowHTTPBackgroundPage) &&
+ background_url_.SchemeIs("http")))) {
+ *error = ASCIIToUTF16(errors::kInvalidBackgroundInHostedApp);
+ return false;
+ }
+ } else {
+ background_url_ = extension->GetResourceURL(background_str);
+ }
+
+ return true;
+}
+
+bool BackgroundInfo::LoadBackgroundPage(const Extension* extension,
+ base::string16* error) {
+ if (extension->is_platform_app()) {
+ return LoadBackgroundPage(
+ extension, keys::kPlatformAppBackgroundPage, error);
+ }
+
+ if (!LoadBackgroundPage(extension, keys::kBackgroundPage, error))
+ return false;
+ if (background_url_.is_empty())
+ return LoadBackgroundPage(extension, keys::kBackgroundPageLegacy, error);
+ return true;
+}
+
+bool BackgroundInfo::LoadBackgroundPersistent(const Extension* extension,
+ base::string16* error) {
+ if (extension->is_platform_app()) {
+ is_persistent_ = false;
+ return true;
+ }
+
+ const base::Value* background_persistent = NULL;
+ if (!extension->manifest()->Get(keys::kBackgroundPersistent,
+ &background_persistent))
+ return true;
+
+ if (!background_persistent->GetAsBoolean(&is_persistent_)) {
+ *error = ASCIIToUTF16(errors::kInvalidBackgroundPersistent);
+ return false;
+ }
+
+ if (!has_background_page()) {
+ *error = ASCIIToUTF16(errors::kInvalidBackgroundPersistentNoPage);
+ return false;
+ }
+
+ return true;
+}
+
+bool BackgroundInfo::LoadAllowJSAccess(const Extension* extension,
+ base::string16* error) {
+ const base::Value* allow_js_access = NULL;
+ if (!extension->manifest()->Get(keys::kBackgroundAllowJsAccess,
+ &allow_js_access))
+ return true;
+
+ if (!allow_js_access->IsType(base::Value::TYPE_BOOLEAN) ||
+ !allow_js_access->GetAsBoolean(&allow_js_access_)) {
+ *error = ASCIIToUTF16(errors::kInvalidBackgroundAllowJsAccess);
+ return false;
+ }
+
+ return true;
+}
+
+BackgroundManifestHandler::BackgroundManifestHandler() {
+}
+
+BackgroundManifestHandler::~BackgroundManifestHandler() {
+}
+
+bool BackgroundManifestHandler::Parse(Extension* extension,
+ base::string16* error) {
+ scoped_ptr<BackgroundInfo> info(new BackgroundInfo);
+ if (!info->Parse(extension, error))
+ return false;
+
+ // Platform apps must have background pages.
+ if (extension->is_platform_app() && !info->has_background_page()) {
+ *error = ASCIIToUTF16(errors::kBackgroundRequiredForPlatformApps);
+ return false;
+ }
+ // Lazy background pages are incompatible with the webRequest API.
+ if (info->has_lazy_background_page() &&
+ PermissionsParser::HasAPIPermission(extension,
+ APIPermission::kWebRequest)) {
+ *error = ASCIIToUTF16(errors::kWebRequestConflictsWithLazyBackground);
+ return false;
+ }
+
+ extension->SetManifestData(kBackground, info.release());
+ return true;
+}
+
+bool BackgroundManifestHandler::Validate(
+ const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const {
+ // Validate that background scripts exist.
+ const std::vector<std::string>& background_scripts =
+ BackgroundInfo::GetBackgroundScripts(extension);
+ for (size_t i = 0; i < background_scripts.size(); ++i) {
+ if (!base::PathExists(
+ extension->GetResource(background_scripts[i]).GetFilePath())) {
+ *error = l10n_util::GetStringFUTF8(
+ IDS_EXTENSION_LOAD_BACKGROUND_SCRIPT_FAILED,
+ base::UTF8ToUTF16(background_scripts[i]));
+ return false;
+ }
+ }
+
+ // Validate background page location, except for hosted apps, which should use
+ // an external URL. Background page for hosted apps are verified when the
+ // extension is created (in Extension::InitFromValue)
+ if (BackgroundInfo::HasBackgroundPage(extension) &&
+ !extension->is_hosted_app() && background_scripts.empty()) {
+ base::FilePath page_path = file_util::ExtensionURLToRelativeFilePath(
+ BackgroundInfo::GetBackgroundURL(extension));
+ const base::FilePath path = extension->GetResource(page_path).GetFilePath();
+ if (path.empty() || !base::PathExists(path)) {
+ *error =
+ l10n_util::GetStringFUTF8(IDS_EXTENSION_LOAD_BACKGROUND_PAGE_FAILED,
+ page_path.LossyDisplayName());
+ return false;
+ }
+ }
+
+ if (extension->is_platform_app()) {
+ const std::string manifest_key =
+ std::string(keys::kPlatformAppBackground) + ".persistent";
+ bool is_persistent = false;
+ // Validate that packaged apps do not use a persistent background page.
+ if (extension->manifest()->GetBoolean(manifest_key, &is_persistent) &&
+ is_persistent) {
+ warnings->push_back(
+ InstallWarning(errors::kInvalidBackgroundPersistentInPlatformApp));
+ }
+ // Validate that packaged apps do not use the key 'background.persistent'.
+ // Use the dictionary directly to prevent an access check as
+ // 'background.persistent' is not available for packaged apps.
+ if (extension->manifest()->value()->Get(keys::kBackgroundPersistent,
+ NULL)) {
+ warnings->push_back(
+ InstallWarning(errors::kBackgroundPersistentInvalidForPlatformApps));
+ }
+ }
+
+ return true;
+}
+
+bool BackgroundManifestHandler::AlwaysParseForType(Manifest::Type type) const {
+ return type == Manifest::TYPE_PLATFORM_APP;
+}
+
+const std::vector<std::string> BackgroundManifestHandler::Keys() const {
+ static const char* keys[] = {
+ keys::kBackgroundAllowJsAccess, keys::kBackgroundPage,
+ keys::kBackgroundPageLegacy, keys::kBackgroundPersistent,
+ keys::kBackgroundScripts, keys::kPlatformAppBackgroundPage,
+ keys::kPlatformAppBackgroundScripts};
+ return std::vector<std::string>(keys, keys + arraysize(keys));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/background_info.h b/chromium/extensions/common/manifest_handlers/background_info.h
new file mode 100644
index 00000000000..66ed985ec68
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/background_info.h
@@ -0,0 +1,101 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_BACKGROUND_INFO_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_BACKGROUND_INFO_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/values.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+class BackgroundInfo : public Extension::ManifestData {
+ public:
+ BackgroundInfo();
+ ~BackgroundInfo() override;
+
+ static GURL GetBackgroundURL(const Extension* extension);
+ static const std::vector<std::string>& GetBackgroundScripts(
+ const Extension* extension);
+ static bool HasBackgroundPage(const Extension* extension);
+ static bool HasPersistentBackgroundPage(const Extension* extension);
+ static bool HasLazyBackgroundPage(const Extension* extension);
+ static bool HasGeneratedBackgroundPage(const Extension* extension);
+ static bool AllowJSAccess(const Extension* extension);
+
+ bool has_background_page() const {
+ return background_url_.is_valid() || !background_scripts_.empty();
+ }
+
+ bool has_persistent_background_page() const {
+ return has_background_page() && is_persistent_;
+ }
+
+ bool has_lazy_background_page() const {
+ return has_background_page() && !is_persistent_;
+ }
+
+ bool Parse(const Extension* extension, base::string16* error);
+
+ private:
+ bool LoadBackgroundScripts(const Extension* extension,
+ const std::string& key,
+ base::string16* error);
+ bool LoadBackgroundPage(const Extension* extension,
+ const std::string& key,
+ base::string16* error);
+ bool LoadBackgroundPage(const Extension* extension, base::string16* error);
+ bool LoadBackgroundPersistent(const Extension* extension,
+ base::string16* error);
+ bool LoadAllowJSAccess(const Extension* extension, base::string16* error);
+
+ // Optional URL to a master page of which a single instance should be always
+ // loaded in the background.
+ GURL background_url_;
+
+ // Optional list of scripts to use to generate a background page. If this is
+ // present, background_url_ will be empty and generated by GetBackgroundURL().
+ std::vector<std::string> background_scripts_;
+
+ // True if the background page should stay loaded forever; false if it should
+ // load on-demand (when it needs to handle an event). Defaults to true.
+ bool is_persistent_;
+
+ // True if the background page can be scripted by pages of the app or
+ // extension, in which case all such pages must run in the same process.
+ // False if such pages are not permitted to script the background page,
+ // allowing them to run in different processes.
+ // Defaults to true.
+ bool allow_js_access_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackgroundInfo);
+};
+
+// Parses all background/event page-related keys in the manifest.
+class BackgroundManifestHandler : public ManifestHandler {
+ public:
+ BackgroundManifestHandler();
+ ~BackgroundManifestHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+ bool Validate(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const override;
+ bool AlwaysParseForType(Manifest::Type type) const override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(BackgroundManifestHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_BACKGROUND_INFO_H_
diff --git a/chromium/extensions/common/manifest_handlers/content_capabilities_handler.cc b/chromium/extensions/common/manifest_handlers/content_capabilities_handler.cc
new file mode 100644
index 00000000000..cfae3120c74
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/content_capabilities_handler.cc
@@ -0,0 +1,117 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/content_capabilities_handler.h"
+
+#include "base/command_line.h"
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "content/public/common/content_switches.h"
+#include "extensions/common/api/extensions_manifest_types.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/install_warning.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/permissions/permissions_info.h"
+#include "extensions/common/url_pattern.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+using api::extensions_manifest_types::ContentCapabilities;
+
+ContentCapabilitiesInfo::ContentCapabilitiesInfo() {
+}
+
+ContentCapabilitiesInfo::~ContentCapabilitiesInfo() {
+}
+
+static base::LazyInstance<ContentCapabilitiesInfo>
+g_empty_content_capabilities_info = LAZY_INSTANCE_INITIALIZER;
+
+// static
+const ContentCapabilitiesInfo& ContentCapabilitiesInfo::Get(
+ const Extension* extension) {
+ ContentCapabilitiesInfo* info = static_cast<ContentCapabilitiesInfo*>(
+ extension->GetManifestData(keys::kContentCapabilities));
+ return info ? *info : g_empty_content_capabilities_info.Get();
+}
+
+ContentCapabilitiesHandler::ContentCapabilitiesHandler() {
+}
+
+ContentCapabilitiesHandler::~ContentCapabilitiesHandler() {
+}
+
+bool ContentCapabilitiesHandler::Parse(Extension* extension,
+ base::string16* error) {
+ scoped_ptr<ContentCapabilitiesInfo> info(new ContentCapabilitiesInfo);
+
+ const base::Value* value = NULL;
+ if (!extension->manifest()->Get(keys::kContentCapabilities, &value)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidContentCapabilities);
+ return false;
+ }
+
+ scoped_ptr<ContentCapabilities> capabilities(ContentCapabilities::FromValue(
+ *value, error));
+ if (!capabilities)
+ return false;
+
+ int supported_schemes = URLPattern::SCHEME_HTTPS;
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)) {
+ // We don't have a suitable HTTPS test server, so this will have to do.
+ supported_schemes |= URLPattern::SCHEME_HTTP;
+ }
+
+ std::string url_error;
+ URLPatternSet potential_url_patterns;
+ if (!potential_url_patterns.Populate(capabilities->matches, supported_schemes,
+ false /* allow_file_access */,
+ &url_error)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidContentCapabilitiesMatch, url_error);
+ return false;
+ }
+
+ // Filter wildcard URL patterns and emit warnings for them.
+ std::set<URLPattern> valid_url_patterns;
+ for (const URLPattern& pattern : potential_url_patterns) {
+ if (pattern.match_subdomains() || pattern.ImpliesAllHosts()) {
+ extension->AddInstallWarning(InstallWarning(
+ errors::kInvalidContentCapabilitiesMatchOrigin));
+ } else {
+ valid_url_patterns.insert(pattern);
+ }
+ }
+ info->url_patterns = URLPatternSet(valid_url_patterns);
+
+ // Filter invalid permissions and emit warnings for them.
+ for (const std::string& permission_name : capabilities->permissions) {
+ const APIPermissionInfo* permission_info = PermissionsInfo::GetInstance()
+ ->GetByName(permission_name);
+ if (!permission_info || !permission_info->supports_content_capabilities()) {
+ extension->AddInstallWarning(InstallWarning(
+ errors::kInvalidContentCapabilitiesPermission,
+ keys::kContentCapabilities,
+ permission_name));
+ } else {
+ info->permissions.insert(permission_info->CreateAPIPermission());
+ }
+ }
+
+ extension->SetManifestData(keys::kContentCapabilities, info.release());
+ return true;
+}
+
+const std::vector<std::string> ContentCapabilitiesHandler::Keys()
+ const {
+ return SingleKey(keys::kContentCapabilities);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/content_capabilities_handler.h b/chromium/extensions/common/manifest_handlers/content_capabilities_handler.h
new file mode 100644
index 00000000000..17c7dd1858d
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/content_capabilities_handler.h
@@ -0,0 +1,51 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_CONTENT_CAPABILITIES_HANDLER_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_CONTENT_CAPABILITIES_HANDLER_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+#include "extensions/common/permissions/permission_set.h"
+#include "extensions/common/url_pattern_set.h"
+
+namespace extensions {
+
+// Manifest data describing an extension's set of granted content capabilities.
+struct ContentCapabilitiesInfo : public Extension::ManifestData {
+ // The set of API permissions to be granted to web content.
+ APIPermissionSet permissions;
+
+ // The URL pattern set which should be used to decide which content is granted
+ // these capabilities.
+ URLPatternSet url_patterns;
+
+ ContentCapabilitiesInfo();
+ ~ContentCapabilitiesInfo() override;
+
+ static const ContentCapabilitiesInfo& Get(const Extension* extension);
+};
+
+// Parses the "content_capabilities" manifest key.
+class ContentCapabilitiesHandler : public ManifestHandler {
+ public:
+ ContentCapabilitiesHandler();
+ ~ContentCapabilitiesHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentCapabilitiesHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_CONTENT_CAPABILITIES_HANDLER_H_
diff --git a/chromium/extensions/common/manifest_handlers/content_capabilities_manifest_unittest.cc b/chromium/extensions/common/manifest_handlers/content_capabilities_manifest_unittest.cc
new file mode 100644
index 00000000000..e9f78c3a546
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/content_capabilities_manifest_unittest.cc
@@ -0,0 +1,90 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/content_capabilities_handler.h"
+#include "extensions/common/manifest_test.h"
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/extensions_api_permissions.h"
+#include "extensions/common/permissions/permissions_info.h"
+#include "extensions/common/switches.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+using extensions::APIPermission;
+
+class ContentCapabilitiesManifestTest : public ManifestTest {
+ std::string GetTestExtensionID() const override {
+ return std::string("apdfllckaahabafndbhieahigkjlhalf");
+ }
+};
+
+TEST_F(ContentCapabilitiesManifestTest, RejectDomainWildcards) {
+ scoped_refptr<Extension> extension(LoadAndExpectWarning(
+ "content_capabilities_domain_wildcard.json",
+ manifest_errors::kInvalidContentCapabilitiesMatchOrigin));
+ const ContentCapabilitiesInfo& info = ContentCapabilitiesInfo::Get(
+ extension.get());
+ // Make sure the wildcard is not included in the pattern set.
+ EXPECT_FALSE(info.url_patterns.MatchesURL(GURL("https://bar.example.com/")));
+ EXPECT_TRUE(info.url_patterns.MatchesURL(GURL("https://foo.example.com/")));
+}
+
+TEST_F(ContentCapabilitiesManifestTest, RejectedAllHosts) {
+ scoped_refptr<Extension> extension(LoadAndExpectWarning(
+ "content_capabilities_all_hosts.json",
+ manifest_errors::kInvalidContentCapabilitiesMatchOrigin));
+ const ContentCapabilitiesInfo& info = ContentCapabilitiesInfo::Get(
+ extension.get());
+ // Make sure the wildcard is not included in the pattern set.
+ EXPECT_FALSE(info.url_patterns.MatchesURL(GURL("https://nonspecific.com/")));
+ EXPECT_TRUE(info.url_patterns.MatchesURL(GURL("https://example.com/")));
+}
+
+TEST_F(ContentCapabilitiesManifestTest, InvalidPermission) {
+ scoped_refptr<Extension> extension(LoadAndExpectWarning(
+ "content_capabilities_invalid_permission.json",
+ manifest_errors::kInvalidContentCapabilitiesPermission));
+ const ContentCapabilitiesInfo& info = ContentCapabilitiesInfo::Get(
+ extension.get());
+ // Make sure the invalid permission is not included in the permission set.
+ EXPECT_EQ(3u, info.permissions.size());
+ EXPECT_EQ(1u, info.permissions.count(APIPermission::kClipboardRead));
+ EXPECT_EQ(1u, info.permissions.count(APIPermission::kClipboardWrite));
+ EXPECT_EQ(1u, info.permissions.count(APIPermission::kUnlimitedStorage));
+ EXPECT_EQ(0u, info.permissions.count(APIPermission::kUsb));
+}
+
+TEST_F(ContentCapabilitiesManifestTest, InvalidValue) {
+ LoadAndExpectError("content_capabilities_invalid_value.json",
+ "expected dictionary, got list");
+}
+
+TEST_F(ContentCapabilitiesManifestTest, RejectNonHttpsUrlPatterns) {
+ LoadAndExpectError("content_capabilities_non_https_matches.json",
+ manifest_errors::kInvalidContentCapabilitiesMatch);
+}
+
+TEST_F(ContentCapabilitiesManifestTest, Valid) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("content_capabilities_valid.json"));
+ const ContentCapabilitiesInfo& info = ContentCapabilitiesInfo::Get(
+ extension.get());
+ EXPECT_EQ(1u, info.url_patterns.size());
+ EXPECT_FALSE(info.url_patterns.MatchesURL(GURL("http://valid.example.com/")));
+ EXPECT_FALSE(info.url_patterns.MatchesURL(GURL("https://foo.example.com/")));
+ EXPECT_FALSE(info.url_patterns.MatchesURL(GURL("https://example.com/")));
+ EXPECT_TRUE(info.url_patterns.MatchesURL(GURL("https://valid.example.com/")));
+ EXPECT_EQ(3u, info.permissions.size());
+ EXPECT_EQ(1u, info.permissions.count(APIPermission::kClipboardRead));
+ EXPECT_EQ(1u, info.permissions.count(APIPermission::kClipboardWrite));
+ EXPECT_EQ(1u, info.permissions.count(APIPermission::kUnlimitedStorage));
+ EXPECT_EQ(0u, info.permissions.count(APIPermission::kUsb));
+}
+
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/csp_info.cc b/chromium/extensions/common/manifest_handlers/csp_info.cc
new file mode 100644
index 00000000000..de984301432
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/csp_info.cc
@@ -0,0 +1,162 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/csp_info.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/csp_validator.h"
+#include "extensions/common/install_warning.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/sandboxed_page_info.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+using csp_validator::ContentSecurityPolicyIsLegal;
+using csp_validator::SanitizeContentSecurityPolicy;
+
+namespace {
+
+const char kDefaultContentSecurityPolicy[] =
+ "script-src 'self' blob: filesystem: chrome-extension-resource:; "
+ "object-src 'self' blob: filesystem:;";
+
+#define PLATFORM_APP_LOCAL_CSP_SOURCES \
+ "'self' blob: filesystem: data: chrome-extension-resource:"
+
+const char kDefaultPlatformAppContentSecurityPolicy[] =
+ // Platform apps can only use local resources by default.
+ "default-src 'self' blob: filesystem: chrome-extension-resource:;"
+ // For remote resources, they can fetch them via XMLHttpRequest.
+ " connect-src * data: blob: filesystem:;"
+ // And serve them via data: or same-origin (blob:, filesystem:) URLs
+ " style-src " PLATFORM_APP_LOCAL_CSP_SOURCES " 'unsafe-inline';"
+ " img-src " PLATFORM_APP_LOCAL_CSP_SOURCES ";"
+ " frame-src " PLATFORM_APP_LOCAL_CSP_SOURCES ";"
+ " font-src " PLATFORM_APP_LOCAL_CSP_SOURCES ";"
+ // Media can be loaded from remote resources since:
+ // 1. <video> and <audio> have good fallback behavior when offline or under
+ // spotty connectivity.
+ // 2. Fetching via XHR and serving via blob: URLs currently does not allow
+ // streaming or partial buffering.
+ " media-src * data: blob: filesystem:;";
+
+int GetValidatorOptions(Extension* extension) {
+ int options = csp_validator::OPTIONS_NONE;
+
+ // crbug.com/146487
+ if (extension->GetType() == Manifest::TYPE_EXTENSION ||
+ extension->GetType() == Manifest::TYPE_LEGACY_PACKAGED_APP) {
+ options |= csp_validator::OPTIONS_ALLOW_UNSAFE_EVAL;
+ }
+
+ // Component extensions can specify an insecure object-src directive. This
+ // should be safe because non-NPAPI plugins should load in a sandboxed process
+ // and only allow communication via postMessage. Flash is an exception since
+ // it allows scripting into the embedder page, but even then it should
+ // disallow cross-origin scripting. At some point we may want to consider
+ // allowing this publicly.
+ if (extensions::Manifest::IsComponentLocation(extension->location()))
+ options |= csp_validator::OPTIONS_ALLOW_INSECURE_OBJECT_SRC;
+
+ return options;
+}
+
+} // namespace
+
+CSPInfo::CSPInfo(const std::string& security_policy)
+ : content_security_policy(security_policy) {
+}
+
+CSPInfo::~CSPInfo() {
+}
+
+// static
+const std::string& CSPInfo::GetContentSecurityPolicy(
+ const Extension* extension) {
+ CSPInfo* csp_info = static_cast<CSPInfo*>(
+ extension->GetManifestData(keys::kContentSecurityPolicy));
+ return csp_info ? csp_info->content_security_policy : base::EmptyString();
+}
+
+// static
+const std::string& CSPInfo::GetResourceContentSecurityPolicy(
+ const Extension* extension,
+ const std::string& relative_path) {
+ return SandboxedPageInfo::IsSandboxedPage(extension, relative_path) ?
+ SandboxedPageInfo::GetContentSecurityPolicy(extension) :
+ GetContentSecurityPolicy(extension);
+}
+
+CSPHandler::CSPHandler(bool is_platform_app)
+ : is_platform_app_(is_platform_app) {
+}
+
+CSPHandler::~CSPHandler() {
+}
+
+bool CSPHandler::Parse(Extension* extension, base::string16* error) {
+ const std::string key = Keys()[0];
+ if (!extension->manifest()->HasPath(key)) {
+ if (extension->manifest_version() >= 2) {
+ // TODO(abarth): Should we continue to let extensions override the
+ // default Content-Security-Policy?
+ std::string content_security_policy = is_platform_app_ ?
+ kDefaultPlatformAppContentSecurityPolicy :
+ kDefaultContentSecurityPolicy;
+
+ CHECK_EQ(content_security_policy,
+ SanitizeContentSecurityPolicy(content_security_policy,
+ GetValidatorOptions(extension),
+ NULL));
+ extension->SetManifestData(keys::kContentSecurityPolicy,
+ new CSPInfo(content_security_policy));
+ }
+ return true;
+ }
+
+ std::string content_security_policy;
+ if (!extension->manifest()->GetString(key, &content_security_policy)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidContentSecurityPolicy);
+ return false;
+ }
+ if (!ContentSecurityPolicyIsLegal(content_security_policy)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidContentSecurityPolicy);
+ return false;
+ }
+ std::string sanitized_csp;
+ if (extension->manifest_version() >= 2) {
+ std::vector<InstallWarning> warnings;
+ content_security_policy =
+ SanitizeContentSecurityPolicy(content_security_policy,
+ GetValidatorOptions(extension),
+ &warnings);
+ extension->AddInstallWarnings(warnings);
+ }
+
+ extension->SetManifestData(keys::kContentSecurityPolicy,
+ new CSPInfo(content_security_policy));
+ return true;
+}
+
+bool CSPHandler::AlwaysParseForType(Manifest::Type type) const {
+ if (is_platform_app_)
+ return type == Manifest::TYPE_PLATFORM_APP;
+ else
+ return type == Manifest::TYPE_EXTENSION ||
+ type == Manifest::TYPE_LEGACY_PACKAGED_APP;
+}
+
+const std::vector<std::string> CSPHandler::Keys() const {
+ const std::string& key = is_platform_app_ ?
+ keys::kPlatformAppContentSecurityPolicy : keys::kContentSecurityPolicy;
+ return SingleKey(key);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/csp_info.h b/chromium/extensions/common/manifest_handlers/csp_info.h
new file mode 100644
index 00000000000..a9b78d46d35
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/csp_info.h
@@ -0,0 +1,55 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_CSP_INFO_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_CSP_INFO_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+// A structure to hold the Content-Security-Policy information.
+struct CSPInfo : public Extension::ManifestData {
+ explicit CSPInfo(const std::string& security_policy);
+ ~CSPInfo() override;
+
+ // The Content-Security-Policy for an extension. Extensions can use
+ // Content-Security-Policies to mitigate cross-site scripting and other
+ // vulnerabilities.
+ std::string content_security_policy;
+
+ static const std::string& GetContentSecurityPolicy(
+ const Extension* extension);
+
+ // Returns the Content Security Policy that the specified resource should be
+ // served with.
+ static const std::string& GetResourceContentSecurityPolicy(
+ const Extension* extension,
+ const std::string& relative_path);
+};
+
+// Parses "content_security_policy" and "app.content_security_policy" keys.
+class CSPHandler : public ManifestHandler {
+ public:
+ explicit CSPHandler(bool is_platform_app);
+ ~CSPHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+ bool AlwaysParseForType(Manifest::Type type) const override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+
+ bool is_platform_app_;
+
+ DISALLOW_COPY_AND_ASSIGN(CSPHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_CSP_INFO_H_
diff --git a/chromium/extensions/common/manifest_handlers/default_locale_handler.cc b/chromium/extensions/common/manifest_handlers/default_locale_handler.cc
new file mode 100644
index 00000000000..debb664ef6c
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/default_locale_handler.cc
@@ -0,0 +1,119 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/default_locale_handler.h"
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension_l10n_util.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+#include "grit/extensions_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+// static
+const std::string& LocaleInfo::GetDefaultLocale(const Extension* extension) {
+ LocaleInfo* info = static_cast<LocaleInfo*>(
+ extension->GetManifestData(keys::kDefaultLocale));
+ return info ? info->default_locale : base::EmptyString();
+}
+
+DefaultLocaleHandler::DefaultLocaleHandler() {
+}
+
+DefaultLocaleHandler::~DefaultLocaleHandler() {
+}
+
+bool DefaultLocaleHandler::Parse(Extension* extension, base::string16* error) {
+ scoped_ptr<LocaleInfo> info(new LocaleInfo);
+ if (!extension->manifest()->GetString(keys::kDefaultLocale,
+ &info->default_locale) ||
+ !l10n_util::IsValidLocaleSyntax(info->default_locale)) {
+ *error = base::ASCIIToUTF16(manifest_errors::kInvalidDefaultLocale);
+ return false;
+ }
+ extension->SetManifestData(keys::kDefaultLocale, info.release());
+ return true;
+}
+
+bool DefaultLocaleHandler::Validate(
+ const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const {
+ // default_locale and _locales have to be both present or both missing.
+ const base::FilePath path = extension->path().Append(kLocaleFolder);
+ bool path_exists = base::PathExists(path);
+ std::string default_locale =
+ extensions::LocaleInfo::GetDefaultLocale(extension);
+
+ // If both default locale and _locales folder are empty, skip verification.
+ if (default_locale.empty() && !path_exists)
+ return true;
+
+ if (default_locale.empty() && path_exists) {
+ *error = l10n_util::GetStringUTF8(
+ IDS_EXTENSION_LOCALES_NO_DEFAULT_LOCALE_SPECIFIED);
+ return false;
+ } else if (!default_locale.empty() && !path_exists) {
+ *error = errors::kLocalesTreeMissing;
+ return false;
+ }
+
+ // Treat all folders under _locales as valid locales.
+ base::FileEnumerator locales(path, false, base::FileEnumerator::DIRECTORIES);
+
+ std::set<std::string> all_locales;
+ extension_l10n_util::GetAllLocales(&all_locales);
+ const base::FilePath default_locale_path = path.AppendASCII(default_locale);
+ bool has_default_locale_message_file = false;
+
+ base::FilePath locale_path;
+ while (!(locale_path = locales.Next()).empty()) {
+ if (extension_l10n_util::ShouldSkipValidation(path, locale_path,
+ all_locales))
+ continue;
+
+ base::FilePath messages_path = locale_path.Append(kMessagesFilename);
+
+ if (!base::PathExists(messages_path)) {
+ *error = base::StringPrintf(
+ "%s %s", errors::kLocalesMessagesFileMissing,
+ base::UTF16ToUTF8(messages_path.LossyDisplayName()).c_str());
+ return false;
+ }
+
+ if (locale_path == default_locale_path)
+ has_default_locale_message_file = true;
+ }
+
+ // Only message file for default locale has to exist.
+ if (!has_default_locale_message_file) {
+ *error = errors::kLocalesNoDefaultMessages;
+ return false;
+ }
+
+ return true;
+}
+
+bool DefaultLocaleHandler::AlwaysValidateForType(Manifest::Type type) const {
+ // Required to validate _locales directory; see Validate.
+ return true;
+}
+
+const std::vector<std::string> DefaultLocaleHandler::Keys() const {
+ return SingleKey(keys::kDefaultLocale);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/default_locale_handler.h b/chromium/extensions/common/manifest_handlers/default_locale_handler.h
new file mode 100644
index 00000000000..7ca44579abe
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/default_locale_handler.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_DEFAULT_LOCALE_HANDLER_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_DEFAULT_LOCALE_HANDLER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+// A structure to hold the locale information for an extension.
+struct LocaleInfo : public Extension::ManifestData {
+ // Default locale for fall back. Can be empty if extension is not localized.
+ std::string default_locale;
+
+ static const std::string& GetDefaultLocale(const Extension* extension);
+};
+
+// Parses the "default_locale" manifest key.
+class DefaultLocaleHandler : public ManifestHandler {
+ public:
+ DefaultLocaleHandler();
+ ~DefaultLocaleHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ // Validates locale info. Doesn't check if messages.json files are valid.
+ bool Validate(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const override;
+
+ bool AlwaysValidateForType(Manifest::Type type) const override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(DefaultLocaleHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_DEFAULT_LOCALE_HANDLER_H_
diff --git a/chromium/extensions/common/manifest_handlers/default_locale_manifest_unittest.cc b/chromium/extensions/common/manifest_handlers/default_locale_manifest_unittest.cc
new file mode 100644
index 00000000000..fd70029087d
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/default_locale_manifest_unittest.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/default_locale_handler.h"
+#include "extensions/common/manifest_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+using DefaultLocaleManifestTest = ManifestTest;
+
+TEST_F(DefaultLocaleManifestTest, DefaultLocale) {
+ LoadAndExpectError("default_locale_invalid.json",
+ manifest_errors::kInvalidDefaultLocale);
+
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("default_locale_valid.json"));
+ EXPECT_EQ("de-AT", LocaleInfo::GetDefaultLocale(extension.get()));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/externally_connectable.cc b/chromium/extensions/common/manifest_handlers/externally_connectable.cc
new file mode 100644
index 00000000000..31f96e7222a
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/externally_connectable.cc
@@ -0,0 +1,229 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/externally_connectable.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+
+#include "base/stl_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/crx_file/id_util.h"
+#include "extensions/common/api/extensions_manifest_types.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/permissions_parser.h"
+#include "extensions/common/permissions/api_permission_set.h"
+#include "extensions/common/url_pattern.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "url/gurl.h"
+
+namespace rcd = net::registry_controlled_domains;
+
+namespace extensions {
+
+namespace externally_connectable_errors {
+const char kErrorInvalidMatchPattern[] = "Invalid match pattern '*'";
+const char kErrorInvalidId[] = "Invalid ID '*'";
+const char kErrorNothingSpecified[] =
+ "'externally_connectable' specifies neither 'matches' nor 'ids'; "
+ "nothing will be able to connect";
+const char kErrorTopLevelDomainsNotAllowed[] =
+ "\"*\" is an effective top level domain for which wildcard subdomains such "
+ "as \"*\" are not allowed";
+const char kErrorWildcardHostsNotAllowed[] =
+ "Wildcard domain patterns such as \"*\" are not allowed";
+} // namespace externally_connectable_errors
+
+namespace keys = extensions::manifest_keys;
+namespace errors = externally_connectable_errors;
+using api::extensions_manifest_types::ExternallyConnectable;
+
+namespace {
+
+const char kAllIds[] = "*";
+
+template <typename T>
+std::vector<T> Sorted(const std::vector<T>& in) {
+ std::vector<T> out = in;
+ std::sort(out.begin(), out.end());
+ return out;
+}
+
+} // namespace
+
+ExternallyConnectableHandler::ExternallyConnectableHandler() {
+}
+
+ExternallyConnectableHandler::~ExternallyConnectableHandler() {
+}
+
+bool ExternallyConnectableHandler::Parse(Extension* extension,
+ base::string16* error) {
+ const base::Value* externally_connectable = NULL;
+ CHECK(extension->manifest()->Get(keys::kExternallyConnectable,
+ &externally_connectable));
+ bool allow_all_urls = PermissionsParser::HasAPIPermission(
+ extension, APIPermission::kExternallyConnectableAllUrls);
+
+ std::vector<InstallWarning> install_warnings;
+ scoped_ptr<ExternallyConnectableInfo> info =
+ ExternallyConnectableInfo::FromValue(
+ *externally_connectable, allow_all_urls, &install_warnings, error);
+ if (!info)
+ return false;
+ if (!info->matches.is_empty()) {
+ PermissionsParser::AddAPIPermission(extension,
+ APIPermission::kWebConnectable);
+ }
+ extension->AddInstallWarnings(install_warnings);
+ extension->SetManifestData(keys::kExternallyConnectable, info.release());
+ return true;
+}
+
+const std::vector<std::string> ExternallyConnectableHandler::Keys() const {
+ return SingleKey(keys::kExternallyConnectable);
+}
+
+// static
+ExternallyConnectableInfo* ExternallyConnectableInfo::Get(
+ const Extension* extension) {
+ return static_cast<ExternallyConnectableInfo*>(
+ extension->GetManifestData(keys::kExternallyConnectable));
+}
+
+// static
+scoped_ptr<ExternallyConnectableInfo> ExternallyConnectableInfo::FromValue(
+ const base::Value& value,
+ bool allow_all_urls,
+ std::vector<InstallWarning>* install_warnings,
+ base::string16* error) {
+ scoped_ptr<ExternallyConnectable> externally_connectable =
+ ExternallyConnectable::FromValue(value, error);
+ if (!externally_connectable)
+ return scoped_ptr<ExternallyConnectableInfo>();
+
+ URLPatternSet matches;
+
+ if (externally_connectable->matches) {
+ for (std::vector<std::string>::iterator it =
+ externally_connectable->matches->begin();
+ it != externally_connectable->matches->end();
+ ++it) {
+ // Safe to use SCHEME_ALL here; externally_connectable gives a page ->
+ // extension communication path, not the other way.
+ URLPattern pattern(URLPattern::SCHEME_ALL);
+ if (pattern.Parse(*it) != URLPattern::PARSE_SUCCESS) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kErrorInvalidMatchPattern, *it);
+ return scoped_ptr<ExternallyConnectableInfo>();
+ }
+
+ if (allow_all_urls && pattern.match_all_urls()) {
+ matches.AddPattern(pattern);
+ continue;
+ }
+
+ // Wildcard hosts are not allowed.
+ if (pattern.host().empty()) {
+ // Warning not error for forwards compatibility.
+ install_warnings->push_back(
+ InstallWarning(ErrorUtils::FormatErrorMessage(
+ errors::kErrorWildcardHostsNotAllowed, *it),
+ keys::kExternallyConnectable,
+ *it));
+ continue;
+ }
+
+ // Wildcards on subdomains of a TLD are not allowed.
+ size_t registry_length = rcd::GetRegistryLength(
+ pattern.host(),
+ // This means that things that look like TLDs - the foobar in
+ // http://google.foobar - count as TLDs.
+ rcd::INCLUDE_UNKNOWN_REGISTRIES,
+ // This means that effective TLDs like appspot.com count as TLDs;
+ // codereview.appspot.com and evil.appspot.com are different.
+ rcd::INCLUDE_PRIVATE_REGISTRIES);
+
+ if (registry_length == std::string::npos) {
+ // The URL parsing combined with host().empty() should have caught this.
+ NOTREACHED() << *it;
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kErrorInvalidMatchPattern, *it);
+ return scoped_ptr<ExternallyConnectableInfo>();
+ }
+
+ // Broad match patterns like "*.com", "*.co.uk", and even "*.appspot.com"
+ // are not allowed. However just "appspot.com" is ok.
+ if (registry_length == 0 && pattern.match_subdomains()) {
+ // Warning not error for forwards compatibility.
+ install_warnings->push_back(
+ InstallWarning(ErrorUtils::FormatErrorMessage(
+ errors::kErrorTopLevelDomainsNotAllowed,
+ pattern.host().c_str(),
+ *it),
+ keys::kExternallyConnectable,
+ *it));
+ continue;
+ }
+
+ matches.AddPattern(pattern);
+ }
+ }
+
+ std::vector<std::string> ids;
+ bool all_ids = false;
+
+ if (externally_connectable->ids) {
+ for (std::vector<std::string>::iterator it =
+ externally_connectable->ids->begin();
+ it != externally_connectable->ids->end();
+ ++it) {
+ if (*it == kAllIds) {
+ all_ids = true;
+ } else if (crx_file::id_util::IdIsValid(*it)) {
+ ids.push_back(*it);
+ } else {
+ *error =
+ ErrorUtils::FormatErrorMessageUTF16(errors::kErrorInvalidId, *it);
+ return scoped_ptr<ExternallyConnectableInfo>();
+ }
+ }
+ }
+
+ if (!externally_connectable->matches && !externally_connectable->ids) {
+ install_warnings->push_back(InstallWarning(errors::kErrorNothingSpecified,
+ keys::kExternallyConnectable));
+ }
+
+ bool accepts_tls_channel_id =
+ externally_connectable->accepts_tls_channel_id.get() &&
+ *externally_connectable->accepts_tls_channel_id;
+ return make_scoped_ptr(new ExternallyConnectableInfo(
+ matches, ids, all_ids, accepts_tls_channel_id));
+}
+
+ExternallyConnectableInfo::~ExternallyConnectableInfo() {
+}
+
+ExternallyConnectableInfo::ExternallyConnectableInfo(
+ const URLPatternSet& matches,
+ const std::vector<std::string>& ids,
+ bool all_ids,
+ bool accepts_tls_channel_id)
+ : matches(matches),
+ ids(Sorted(ids)),
+ all_ids(all_ids),
+ accepts_tls_channel_id(accepts_tls_channel_id) {
+}
+
+bool ExternallyConnectableInfo::IdCanConnect(const std::string& id) {
+ if (all_ids)
+ return true;
+ DCHECK(base::STLIsSorted(ids));
+ return std::binary_search(ids.begin(), ids.end(), id);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/externally_connectable.h b/chromium/extensions/common/manifest_handlers/externally_connectable.h
new file mode 100644
index 00000000000..ab3b2b5fb02
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/externally_connectable.h
@@ -0,0 +1,99 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_EXTERNALLY_CONNECTABLE_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_EXTERNALLY_CONNECTABLE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/install_warning.h"
+#include "extensions/common/manifest_handler.h"
+#include "extensions/common/url_pattern_set.h"
+
+class GURL;
+
+namespace base {
+class Value;
+}
+
+namespace extensions {
+
+// Error constants used when parsing the externally_connectable manifest entry.
+namespace externally_connectable_errors {
+extern const char kErrorInvalid[];
+extern const char kErrorInvalidMatchPattern[];
+extern const char kErrorInvalidId[];
+extern const char kErrorNothingSpecified[];
+extern const char kErrorTopLevelDomainsNotAllowed[];
+extern const char kErrorWildcardHostsNotAllowed[];
+} // namespace externally_connectable_errors
+
+// Parses the externally_connectable manifest entry.
+class ExternallyConnectableHandler : public ManifestHandler {
+ public:
+ ExternallyConnectableHandler();
+ ~ExternallyConnectableHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(ExternallyConnectableHandler);
+};
+
+// The parsed form of the externally_connectable manifest entry.
+struct ExternallyConnectableInfo : public Extension::ManifestData {
+ public:
+ // Gets the ExternallyConnectableInfo for |extension|, or NULL if none was
+ // specified.
+ static ExternallyConnectableInfo* Get(const Extension* extension);
+
+ // Tries to construct the info based on |value|, as it would have appeared in
+ // the manifest. Sets |error| and returns an empty scoped_ptr on failure.
+ static scoped_ptr<ExternallyConnectableInfo> FromValue(
+ const base::Value& value,
+ bool allow_all_urls,
+ std::vector<InstallWarning>* install_warnings,
+ base::string16* error);
+
+ ~ExternallyConnectableInfo() override;
+
+ // The URL patterns that are allowed to connect/sendMessage.
+ const URLPatternSet matches;
+
+ // The extension IDs that are allowed to connect/sendMessage. Sorted.
+ const std::vector<std::string> ids;
+
+ // True if any extension is allowed to connect. This would have corresponded
+ // to an ID of "*" in |ids|.
+ const bool all_ids;
+
+ // True if extension accepts the TLS channel ID, when requested by the
+ // connecting origin.
+ const bool accepts_tls_channel_id;
+
+ // Returns true if |ids| contains |id| or if |all_ids| is true.
+ //
+ // More convenient for callers than checking each individually, and it makes
+ // use of the sortedness of |ids|.
+ bool IdCanConnect(const std::string& id);
+
+ // Public only for testing. Use FromValue in production.
+ ExternallyConnectableInfo(const URLPatternSet& matches,
+ const std::vector<std::string>& ids,
+ bool all_ids,
+ bool accepts_tls_channel_id);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ExternallyConnectableInfo);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_EXTERNALLY_CONNECTABLE_H_
diff --git a/chromium/extensions/common/manifest_handlers/externally_connectable_unittest.cc b/chromium/extensions/common/manifest_handlers/externally_connectable_unittest.cc
new file mode 100644
index 00000000000..251958801bf
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/externally_connectable_unittest.cc
@@ -0,0 +1,340 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <algorithm>
+
+#include "base/macros.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/externally_connectable.h"
+#include "extensions/common/manifest_test.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::ElementsAre;
+
+namespace extensions {
+
+namespace errors = externally_connectable_errors;
+
+class ExternallyConnectableTest : public ManifestTest {
+ public:
+ ExternallyConnectableTest() {}
+ ~ExternallyConnectableTest() override {}
+
+ protected:
+ ExternallyConnectableInfo* GetExternallyConnectableInfo(
+ scoped_refptr<Extension> extension) {
+ return static_cast<ExternallyConnectableInfo*>(
+ extension->GetManifestData(manifest_keys::kExternallyConnectable));
+ }
+};
+
+TEST_F(ExternallyConnectableTest, IDsAndMatches) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("externally_connectable_ids_and_matches.json");
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(extension->permissions_data()->HasAPIPermission(
+ APIPermission::kWebConnectable));
+
+ ExternallyConnectableInfo* info =
+ ExternallyConnectableInfo::Get(extension.get());
+ ASSERT_TRUE(info);
+
+ EXPECT_THAT(info->ids,
+ ElementsAre("abcdefghijklmnopabcdefghijklmnop",
+ "ponmlkjihgfedcbaponmlkjihgfedcba"));
+
+ EXPECT_FALSE(info->all_ids);
+
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://example.com")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://example.com/")));
+ EXPECT_FALSE(info->matches.MatchesURL(GURL("http://example.com/index.html")));
+
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://google.com")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://google.com/")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://google.com/index.html")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.google.com")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.google.com/")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("https://google.com")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("https://google.com/")));
+
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://build.chromium.org")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://build.chromium.org/")));
+ EXPECT_TRUE(
+ info->matches.MatchesURL(GURL("http://build.chromium.org/index.html")));
+ EXPECT_FALSE(info->matches.MatchesURL(GURL("https://build.chromium.org")));
+ EXPECT_FALSE(info->matches.MatchesURL(GURL("https://build.chromium.org/")));
+ EXPECT_FALSE(
+ info->matches.MatchesURL(GURL("http://foo.chromium.org/index.html")));
+
+ EXPECT_FALSE(info->matches.MatchesURL(GURL("http://yahoo.com")));
+ EXPECT_FALSE(info->matches.MatchesURL(GURL("http://yahoo.com/")));
+
+ // TLD-style patterns should match just the TLD.
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://appspot.com/foo.html")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://com")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://go/here")));
+
+ // TLD-style patterns should *not* match any subdomains of the TLD.
+ EXPECT_FALSE(
+ info->matches.MatchesURL(GURL("http://codereview.appspot.com/foo.html")));
+ EXPECT_FALSE(
+ info->matches.MatchesURL(GURL("http://chromium.com/index.html")));
+ EXPECT_FALSE(info->matches.MatchesURL(GURL("http://here.go/somewhere")));
+
+ // Paths that don't have any wildcards should match the exact domain, but
+ // ignore the trailing slash. This is kind of a corner case, so let's test it.
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://no.wildcard.path")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://no.wildcard.path/")));
+ EXPECT_FALSE(
+ info->matches.MatchesURL(GURL("http://no.wildcard.path/index.html")));
+}
+
+TEST_F(ExternallyConnectableTest, IDs) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("externally_connectable_ids.json");
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_FALSE(extension->permissions_data()->HasAPIPermission(
+ APIPermission::kWebConnectable));
+
+ ExternallyConnectableInfo* info =
+ ExternallyConnectableInfo::Get(extension.get());
+ ASSERT_TRUE(info);
+
+ EXPECT_THAT(info->ids,
+ ElementsAre("abcdefghijklmnopabcdefghijklmnop",
+ "ponmlkjihgfedcbaponmlkjihgfedcba"));
+
+ EXPECT_FALSE(info->all_ids);
+
+ EXPECT_FALSE(info->matches.MatchesURL(GURL("http://google.com/index.html")));
+}
+
+TEST_F(ExternallyConnectableTest, Matches) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("externally_connectable_matches.json");
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(extension->permissions_data()->HasAPIPermission(
+ APIPermission::kWebConnectable));
+
+ ExternallyConnectableInfo* info =
+ ExternallyConnectableInfo::Get(extension.get());
+ ASSERT_TRUE(info);
+
+ EXPECT_THAT(info->ids, ElementsAre());
+
+ EXPECT_FALSE(info->all_ids);
+
+ EXPECT_FALSE(info->accepts_tls_channel_id);
+
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://example.com")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://example.com/")));
+ EXPECT_FALSE(info->matches.MatchesURL(GURL("http://example.com/index.html")));
+
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://google.com")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://google.com/")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://google.com/index.html")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.google.com")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.google.com/")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("https://google.com")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("https://google.com/")));
+
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://build.chromium.org")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://build.chromium.org/")));
+ EXPECT_TRUE(
+ info->matches.MatchesURL(GURL("http://build.chromium.org/index.html")));
+ EXPECT_FALSE(info->matches.MatchesURL(GURL("https://build.chromium.org")));
+ EXPECT_FALSE(info->matches.MatchesURL(GURL("https://build.chromium.org/")));
+ EXPECT_FALSE(
+ info->matches.MatchesURL(GURL("http://foo.chromium.org/index.html")));
+
+ EXPECT_FALSE(info->matches.MatchesURL(GURL("http://yahoo.com")));
+ EXPECT_FALSE(info->matches.MatchesURL(GURL("http://yahoo.com/")));
+}
+
+TEST_F(ExternallyConnectableTest, MatchesWithTlsChannelId) {
+ scoped_refptr<Extension> extension = LoadAndExpectSuccess(
+ "externally_connectable_matches_tls_channel_id.json");
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_TRUE(extension->permissions_data()->HasAPIPermission(
+ APIPermission::kWebConnectable));
+
+ ExternallyConnectableInfo* info =
+ ExternallyConnectableInfo::Get(extension.get());
+ ASSERT_TRUE(info);
+
+ EXPECT_THAT(info->ids, ElementsAre());
+
+ EXPECT_FALSE(info->all_ids);
+
+ EXPECT_TRUE(info->accepts_tls_channel_id);
+
+ // The matches portion of the manifest is identical to those in
+ // externally_connectable_matches, so only a subset of the Matches tests is
+ // repeated here.
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://example.com")));
+ EXPECT_FALSE(info->matches.MatchesURL(GURL("http://example.com/index.html")));
+}
+
+TEST_F(ExternallyConnectableTest, AllIDs) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("externally_connectable_all_ids.json");
+ ASSERT_TRUE(extension.get());
+
+ EXPECT_FALSE(extension->permissions_data()->HasAPIPermission(
+ APIPermission::kWebConnectable));
+
+ ExternallyConnectableInfo* info =
+ ExternallyConnectableInfo::Get(extension.get());
+ ASSERT_TRUE(info);
+
+ EXPECT_THAT(info->ids,
+ ElementsAre("abcdefghijklmnopabcdefghijklmnop",
+ "ponmlkjihgfedcbaponmlkjihgfedcba"));
+
+ EXPECT_TRUE(info->all_ids);
+
+ EXPECT_FALSE(info->matches.MatchesURL(GURL("http://google.com/index.html")));
+}
+
+TEST_F(ExternallyConnectableTest, IdCanConnect) {
+ // Not in order to test that ExternallyConnectableInfo sorts it.
+ std::string matches_ids_array[] = {"g", "h", "c", "i", "a", "z", "b"};
+ std::vector<std::string> matches_ids(
+ matches_ids_array, matches_ids_array + arraysize(matches_ids_array));
+
+ std::string nomatches_ids_array[] = {"2", "3", "1"};
+
+ // all_ids = false.
+ {
+ ExternallyConnectableInfo info(URLPatternSet(), matches_ids, false, false);
+ for (size_t i = 0; i < matches_ids.size(); ++i)
+ EXPECT_TRUE(info.IdCanConnect(matches_ids[i]));
+ for (size_t i = 0; i < arraysize(nomatches_ids_array); ++i)
+ EXPECT_FALSE(info.IdCanConnect(nomatches_ids_array[i]));
+ }
+
+ // all_ids = true.
+ {
+ ExternallyConnectableInfo info(URLPatternSet(), matches_ids, true, false);
+ for (size_t i = 0; i < matches_ids.size(); ++i)
+ EXPECT_TRUE(info.IdCanConnect(matches_ids[i]));
+ for (size_t i = 0; i < arraysize(nomatches_ids_array); ++i)
+ EXPECT_TRUE(info.IdCanConnect(nomatches_ids_array[i]));
+ }
+}
+
+TEST_F(ExternallyConnectableTest, ErrorWrongFormat) {
+ LoadAndExpectError("externally_connectable_error_wrong_format.json",
+ "expected dictionary, got string");
+}
+
+TEST_F(ExternallyConnectableTest, ErrorBadID) {
+ LoadAndExpectError(
+ "externally_connectable_bad_id.json",
+ ErrorUtils::FormatErrorMessage(errors::kErrorInvalidId, "badid"));
+}
+
+TEST_F(ExternallyConnectableTest, ErrorBadMatches) {
+ LoadAndExpectError("externally_connectable_error_bad_matches.json",
+ ErrorUtils::FormatErrorMessage(
+ errors::kErrorInvalidMatchPattern, "www.yahoo.com"));
+}
+
+TEST_F(ExternallyConnectableTest, WarningNoAllURLs) {
+ scoped_refptr<Extension> extension = LoadAndExpectWarning(
+ "externally_connectable_error_all_urls.json",
+ ErrorUtils::FormatErrorMessage(errors::kErrorWildcardHostsNotAllowed,
+ "<all_urls>"));
+ ExternallyConnectableInfo* info = GetExternallyConnectableInfo(extension);
+ EXPECT_FALSE(info->matches.MatchesAllURLs());
+ EXPECT_FALSE(info->matches.ContainsPattern(
+ URLPattern(URLPattern::SCHEME_ALL, "<all_urls>")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("https://example.com")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://build.chromium.org")));
+}
+
+TEST_F(ExternallyConnectableTest, AllURLsNotWhitelisted) {
+ scoped_refptr<Extension> extension = LoadAndExpectSuccess(
+ "externally_connectable_all_urls_not_whitelisted.json");
+ ExternallyConnectableInfo* info = GetExternallyConnectableInfo(extension);
+ EXPECT_FALSE(info->matches.MatchesAllURLs());
+}
+
+TEST_F(ExternallyConnectableTest, AllURLsWhitelisted) {
+ scoped_refptr<Extension> extension =
+ LoadAndExpectSuccess("externally_connectable_all_urls_whitelisted.json");
+ ExternallyConnectableInfo* info = GetExternallyConnectableInfo(extension);
+ EXPECT_TRUE(info->matches.MatchesAllURLs());
+ URLPattern pattern(URLPattern::SCHEME_ALL, "<all_urls>");
+ EXPECT_TRUE(info->matches.ContainsPattern(pattern));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("https://example.com")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://build.chromium.org")));
+}
+
+TEST_F(ExternallyConnectableTest, WarningWildcardHost) {
+ scoped_refptr<Extension> extension = LoadAndExpectWarning(
+ "externally_connectable_error_wildcard_host.json",
+ ErrorUtils::FormatErrorMessage(errors::kErrorWildcardHostsNotAllowed,
+ "http://*/*"));
+ ExternallyConnectableInfo* info = GetExternallyConnectableInfo(extension);
+ EXPECT_FALSE(info->matches.ContainsPattern(
+ URLPattern(URLPattern::SCHEME_ALL, "http://*/*")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("https://example.com")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://build.chromium.org")));
+}
+
+TEST_F(ExternallyConnectableTest, WarningNoTLD) {
+ scoped_refptr<Extension> extension = LoadAndExpectWarning(
+ "externally_connectable_error_tld.json",
+ ErrorUtils::FormatErrorMessage(errors::kErrorTopLevelDomainsNotAllowed,
+ "co.uk",
+ "http://*.co.uk/*"));
+ ExternallyConnectableInfo* info = GetExternallyConnectableInfo(extension);
+ EXPECT_FALSE(info->matches.ContainsPattern(
+ URLPattern(URLPattern::SCHEME_ALL, "http://*.co.uk/*")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("https://example.com")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://build.chromium.org")));
+}
+
+TEST_F(ExternallyConnectableTest, WarningNoEffectiveTLD) {
+ scoped_refptr<Extension> extension = LoadAndExpectWarning(
+ "externally_connectable_error_effective_tld.json",
+ ErrorUtils::FormatErrorMessage(errors::kErrorTopLevelDomainsNotAllowed,
+ "appspot.com",
+ "http://*.appspot.com/*"));
+ ExternallyConnectableInfo* info = GetExternallyConnectableInfo(extension);
+ EXPECT_FALSE(info->matches.ContainsPattern(
+ URLPattern(URLPattern::SCHEME_ALL, "http://*.appspot.com/*")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("https://example.com")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://build.chromium.org")));
+}
+
+TEST_F(ExternallyConnectableTest, WarningUnknownTLD) {
+ scoped_refptr<Extension> extension = LoadAndExpectWarning(
+ "externally_connectable_error_unknown_tld.json",
+ ErrorUtils::FormatErrorMessage(errors::kErrorTopLevelDomainsNotAllowed,
+ "notatld",
+ "http://*.notatld/*"));
+ ExternallyConnectableInfo* info = GetExternallyConnectableInfo(extension);
+ EXPECT_FALSE(info->matches.ContainsPattern(
+ URLPattern(URLPattern::SCHEME_ALL, "http://*.notatld/*")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("https://example.com")));
+ EXPECT_TRUE(info->matches.MatchesURL(GURL("http://build.chromium.org")));
+}
+
+TEST_F(ExternallyConnectableTest, WarningNothingSpecified) {
+ LoadAndExpectWarning("externally_connectable_nothing_specified.json",
+ errors::kErrorNothingSpecified);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/file_handler_info.cc b/chromium/extensions/common/manifest_handlers/file_handler_info.cc
new file mode 100644
index 00000000000..c95a5e46874
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/file_handler_info.cc
@@ -0,0 +1,186 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/file_handler_info.h"
+
+#include <stddef.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+namespace {
+const int kMaxTypeAndExtensionHandlers = 200;
+const char kNotRecognized[] = "'%s' is not a recognized file handler property.";
+}
+
+FileHandlerInfo::FileHandlerInfo() : include_directories(false) {}
+FileHandlerInfo::FileHandlerInfo(const FileHandlerInfo& other) = default;
+FileHandlerInfo::~FileHandlerInfo() {}
+
+FileHandlers::FileHandlers() {}
+FileHandlers::~FileHandlers() {}
+
+// static
+const FileHandlersInfo* FileHandlers::GetFileHandlers(
+ const Extension* extension) {
+ FileHandlers* info = static_cast<FileHandlers*>(
+ extension->GetManifestData(keys::kFileHandlers));
+ return info ? &info->file_handlers : NULL;
+}
+
+FileHandlersParser::FileHandlersParser() {
+}
+
+FileHandlersParser::~FileHandlersParser() {
+}
+
+bool LoadFileHandler(const std::string& handler_id,
+ const base::DictionaryValue& handler_info,
+ FileHandlersInfo* file_handlers,
+ base::string16* error,
+ std::vector<InstallWarning>* install_warnings) {
+ DCHECK(error);
+ FileHandlerInfo handler;
+
+ handler.id = handler_id;
+
+ const base::ListValue* mime_types = NULL;
+ if (handler_info.HasKey(keys::kFileHandlerTypes) &&
+ !handler_info.GetList(keys::kFileHandlerTypes, &mime_types)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidFileHandlerType, handler_id);
+ return false;
+ }
+
+ const base::ListValue* file_extensions = NULL;
+ if (handler_info.HasKey(keys::kFileHandlerExtensions) &&
+ !handler_info.GetList(keys::kFileHandlerExtensions, &file_extensions)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidFileHandlerExtension, handler_id);
+ return false;
+ }
+
+ handler.include_directories = false;
+ if (handler_info.HasKey("include_directories") &&
+ !handler_info.GetBoolean("include_directories",
+ &handler.include_directories)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidFileHandlerIncludeDirectories, handler_id);
+ return false;
+ }
+
+ if ((!mime_types || mime_types->empty()) &&
+ (!file_extensions || file_extensions->empty()) &&
+ !handler.include_directories) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidFileHandlerNoTypeOrExtension,
+ handler_id);
+ return false;
+ }
+
+ if (mime_types) {
+ std::string type;
+ for (size_t i = 0; i < mime_types->GetSize(); ++i) {
+ if (!mime_types->GetString(i, &type)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidFileHandlerTypeElement, handler_id,
+ base::SizeTToString(i));
+ return false;
+ }
+ handler.types.insert(type);
+ }
+ }
+
+ if (file_extensions) {
+ std::string file_extension;
+ for (size_t i = 0; i < file_extensions->GetSize(); ++i) {
+ if (!file_extensions->GetString(i, &file_extension)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidFileHandlerExtensionElement, handler_id,
+ base::SizeTToString(i));
+ return false;
+ }
+ handler.extensions.insert(file_extension);
+ }
+ }
+
+ file_handlers->push_back(handler);
+
+ // Check for unknown keys.
+ for (base::DictionaryValue::Iterator it(handler_info); !it.IsAtEnd();
+ it.Advance()) {
+ if (it.key() != keys::kFileHandlerExtensions &&
+ it.key() != keys::kFileHandlerTypes) {
+ install_warnings->push_back(
+ InstallWarning(base::StringPrintf(kNotRecognized, it.key().c_str()),
+ keys::kFileHandlers,
+ it.key()));
+ }
+ }
+
+ return true;
+}
+
+bool FileHandlersParser::Parse(Extension* extension, base::string16* error) {
+ scoped_ptr<FileHandlers> info(new FileHandlers);
+ const base::DictionaryValue* all_handlers = NULL;
+ if (!extension->manifest()->GetDictionary(keys::kFileHandlers,
+ &all_handlers)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidFileHandlers);
+ return false;
+ }
+
+ std::vector<InstallWarning> install_warnings;
+ for (base::DictionaryValue::Iterator iter(*all_handlers);
+ !iter.IsAtEnd();
+ iter.Advance()) {
+ const base::DictionaryValue* handler = NULL;
+ if (iter.value().GetAsDictionary(&handler)) {
+ if (!LoadFileHandler(iter.key(),
+ *handler,
+ &info->file_handlers,
+ error,
+ &install_warnings))
+ return false;
+ } else {
+ *error = base::ASCIIToUTF16(errors::kInvalidFileHandlers);
+ return false;
+ }
+ }
+
+ int filter_count = 0;
+ for (FileHandlersInfo::const_iterator iter = info->file_handlers.begin();
+ iter != info->file_handlers.end();
+ iter++) {
+ filter_count += iter->types.size();
+ filter_count += iter->extensions.size();
+ }
+
+ if (filter_count > kMaxTypeAndExtensionHandlers) {
+ *error = base::ASCIIToUTF16(
+ errors::kInvalidFileHandlersTooManyTypesAndExtensions);
+ return false;
+ }
+
+ extension->SetManifestData(keys::kFileHandlers, info.release());
+ extension->AddInstallWarnings(install_warnings);
+ return true;
+}
+
+const std::vector<std::string> FileHandlersParser::Keys() const {
+ return SingleKey(keys::kFileHandlers);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/file_handler_info.h b/chromium/extensions/common/manifest_handlers/file_handler_info.h
new file mode 100644
index 00000000000..90de31c3069
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/file_handler_info.h
@@ -0,0 +1,63 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_FILE_HANDLER_INFO_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_FILE_HANDLER_INFO_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+struct FileHandlerInfo {
+ FileHandlerInfo();
+ FileHandlerInfo(const FileHandlerInfo& other);
+ ~FileHandlerInfo();
+
+ // The id of this handler.
+ std::string id;
+
+ // File extensions associated with this handler.
+ std::set<std::string> extensions;
+
+ // MIME types associated with this handler.
+ std::set<std::string> types;
+
+ // True if the handler can manage directories.
+ bool include_directories;
+};
+
+typedef std::vector<FileHandlerInfo> FileHandlersInfo;
+
+struct FileHandlers : public Extension::ManifestData {
+ FileHandlers();
+ ~FileHandlers() override;
+
+ FileHandlersInfo file_handlers;
+
+ static const FileHandlersInfo* GetFileHandlers(const Extension* extension);
+};
+
+// Parses the "file_handlers" manifest key.
+class FileHandlersParser : public ManifestHandler {
+ public:
+ FileHandlersParser();
+ ~FileHandlersParser() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(FileHandlersParser);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_FILE_HANDLER_INFO_H_
diff --git a/chromium/extensions/common/manifest_handlers/file_handler_manifest_unittest.cc b/chromium/extensions/common/manifest_handlers/file_handler_manifest_unittest.cc
new file mode 100644
index 00000000000..d55c14848be
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/file_handler_manifest_unittest.cc
@@ -0,0 +1,73 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/macros.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/file_handler_info.h"
+#include "extensions/common/manifest_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+
+typedef ManifestTest FileHandlersManifestTest;
+
+TEST_F(FileHandlersManifestTest, InvalidFileHandlers) {
+ Testcase testcases[] = {
+ Testcase("file_handlers_invalid_handlers.json",
+ errors::kInvalidFileHandlers),
+ Testcase("file_handlers_invalid_type.json",
+ errors::kInvalidFileHandlerType),
+ Testcase("file_handlers_invalid_extension.json",
+ errors::kInvalidFileHandlerExtension),
+ Testcase("file_handlers_invalid_no_type_or_extension.json",
+ errors::kInvalidFileHandlerNoTypeOrExtension),
+ Testcase("file_handlers_invalid_type_element.json",
+ errors::kInvalidFileHandlerTypeElement),
+ Testcase("file_handlers_invalid_extension_element.json",
+ errors::kInvalidFileHandlerExtensionElement),
+ Testcase("file_handlers_invalid_too_many.json",
+ errors::kInvalidFileHandlersTooManyTypesAndExtensions),
+ };
+ RunTestcases(testcases, arraysize(testcases), EXPECT_TYPE_ERROR);
+}
+
+TEST_F(FileHandlersManifestTest, ValidFileHandlers) {
+ scoped_refptr<const Extension> extension =
+ LoadAndExpectSuccess("file_handlers_valid.json");
+
+ ASSERT_TRUE(extension.get());
+ const FileHandlersInfo* handlers =
+ FileHandlers::GetFileHandlers(extension.get());
+ ASSERT_TRUE(handlers != NULL);
+ ASSERT_EQ(2U, handlers->size());
+
+ FileHandlerInfo handler = handlers->at(0);
+ EXPECT_EQ("image", handler.id);
+ EXPECT_EQ(1U, handler.types.size());
+ EXPECT_EQ(1U, handler.types.count("image/*"));
+ EXPECT_EQ(2U, handler.extensions.size());
+ EXPECT_EQ(1U, handler.extensions.count(".png"));
+ EXPECT_EQ(1U, handler.extensions.count(".gif"));
+
+ handler = handlers->at(1);
+ EXPECT_EQ("text", handler.id);
+ EXPECT_EQ(1U, handler.types.size());
+ EXPECT_EQ(1U, handler.types.count("text/*"));
+ EXPECT_EQ(0U, handler.extensions.size());
+}
+
+TEST_F(FileHandlersManifestTest, NotPlatformApp) {
+ // This should load successfully but have the file handlers ignored.
+ scoped_refptr<const Extension> extension =
+ LoadAndExpectSuccess("file_handlers_invalid_not_app.json");
+
+ ASSERT_TRUE(extension.get());
+ const FileHandlersInfo* handlers =
+ FileHandlers::GetFileHandlers(extension.get());
+ ASSERT_TRUE(handlers == NULL);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/icons_handler.cc b/chromium/extensions/common/manifest_handlers/icons_handler.cc
new file mode 100644
index 00000000000..20ad025b25b
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/icons_handler.cc
@@ -0,0 +1,88 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/icons_handler.h"
+
+#include "base/files/file_util.h"
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/file_util.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handler_helpers.h"
+#include "grit/extensions_strings.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+
+static base::LazyInstance<ExtensionIconSet> g_empty_icon_set =
+ LAZY_INSTANCE_INITIALIZER;
+
+// static
+const ExtensionIconSet& IconsInfo::GetIcons(const Extension* extension) {
+ IconsInfo* info = static_cast<IconsInfo*>(
+ extension->GetManifestData(keys::kIcons));
+ return info ? info->icons : g_empty_icon_set.Get();
+}
+
+// static
+ExtensionResource IconsInfo::GetIconResource(
+ const Extension* extension,
+ int size,
+ ExtensionIconSet::MatchType match_type) {
+ const std::string& path = GetIcons(extension).Get(size, match_type);
+ return path.empty() ? ExtensionResource() : extension->GetResource(path);
+}
+
+// static
+GURL IconsInfo::GetIconURL(const Extension* extension,
+ int size,
+ ExtensionIconSet::MatchType match_type) {
+ const std::string& path = GetIcons(extension).Get(size, match_type);
+ return path.empty() ? GURL() : extension->GetResourceURL(path);
+}
+
+IconsHandler::IconsHandler() {
+}
+
+IconsHandler::~IconsHandler() {
+}
+
+bool IconsHandler::Parse(Extension* extension, base::string16* error) {
+ scoped_ptr<IconsInfo> icons_info(new IconsInfo);
+ const base::DictionaryValue* icons_dict = NULL;
+ if (!extension->manifest()->GetDictionary(keys::kIcons, &icons_dict)) {
+ *error = base::ASCIIToUTF16(manifest_errors::kInvalidIcons);
+ return false;
+ }
+
+ if (!manifest_handler_helpers::LoadIconsFromDictionary(
+ icons_dict, &icons_info->icons, error)) {
+ return false;
+ }
+
+ extension->SetManifestData(keys::kIcons, icons_info.release());
+ return true;
+}
+
+bool IconsHandler::Validate(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const {
+ return file_util::ValidateExtensionIconSet(IconsInfo::GetIcons(extension),
+ extension,
+ IDS_EXTENSION_LOAD_ICON_FAILED,
+ error);
+}
+
+const std::vector<std::string> IconsHandler::Keys() const {
+ return SingleKey(keys::kIcons);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/icons_handler.h b/chromium/extensions/common/manifest_handlers/icons_handler.h
new file mode 100644
index 00000000000..5c9186d0c15
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/icons_handler.h
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_ICONS_HANDLER_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_ICONS_HANDLER_H_
+
+#include <string>
+
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_icon_set.h"
+#include "extensions/common/extension_resource.h"
+#include "extensions/common/manifest_handler.h"
+
+class GURL;
+
+namespace extensions {
+
+struct IconsInfo : public Extension::ManifestData {
+ // The icons for the extension.
+ ExtensionIconSet icons;
+
+ // Return the icon set for the given |extension|.
+ static const ExtensionIconSet& GetIcons(const Extension* extension);
+
+ // Get an extension icon as a resource or URL.
+ static ExtensionResource GetIconResource(
+ const Extension* extension,
+ int size,
+ ExtensionIconSet::MatchType match_type);
+ static GURL GetIconURL(const Extension* extension,
+ int size,
+ ExtensionIconSet::MatchType match_type);
+};
+
+// Parses the "icons" manifest key.
+class IconsHandler : public ManifestHandler {
+ public:
+ IconsHandler();
+ ~IconsHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+ bool Validate(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_ICONS_HANDLER_H_
diff --git a/chromium/extensions/common/manifest_handlers/icons_handler_unittest.cc b/chromium/extensions/common/manifest_handlers/icons_handler_unittest.cc
new file mode 100644
index 00000000000..fa644a940ce
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/icons_handler_unittest.cc
@@ -0,0 +1,69 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/values_test_util.h"
+#include "extensions/common/manifest_handlers/icons_handler.h"
+#include "extensions/common/manifest_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+class ProductIconManifestTest : public ManifestTest {
+ public:
+ ProductIconManifestTest() {}
+
+ protected:
+ scoped_ptr<base::DictionaryValue> CreateManifest(
+ const std::string& extra_icons) {
+ scoped_ptr<base::DictionaryValue> manifest = base::DictionaryValue::From(
+ base::test::ParseJson("{ \n"
+ " \"name\": \"test\", \n"
+ " \"version\": \"0.1\", \n"
+ " \"manifest_version\": 2, \n"
+ " \"icons\": { \n" +
+ extra_icons + " \"16\": \"icon1.png\", \n"
+ " \"32\": \"icon2.png\" \n"
+ " } \n"
+ "} \n"));
+ EXPECT_TRUE(manifest);
+ return manifest;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ProductIconManifestTest);
+};
+
+TEST_F(ProductIconManifestTest, Sizes) {
+ // Too big.
+ {
+ scoped_ptr<base::DictionaryValue> ext_manifest =
+ CreateManifest("\"100000\": \"icon3.png\", \n");
+ ManifestData manifest(std::move(ext_manifest), "test");
+ LoadAndExpectError(manifest, "Invalid key in icons: \"100000\".");
+ }
+ // Too small.
+ {
+ scoped_ptr<base::DictionaryValue> ext_manifest =
+ CreateManifest("\"0\": \"icon3.png\", \n");
+ ManifestData manifest(std::move(ext_manifest), "test");
+ LoadAndExpectError(manifest, "Invalid key in icons: \"0\".");
+ }
+ // NaN.
+ {
+ scoped_ptr<base::DictionaryValue> ext_manifest =
+ CreateManifest("\"sixteen\": \"icon3.png\", \n");
+ ManifestData manifest(std::move(ext_manifest), "test");
+ LoadAndExpectError(manifest, "Invalid key in icons: \"sixteen\".");
+ }
+ // Just right.
+ {
+ scoped_ptr<base::DictionaryValue> ext_manifest =
+ CreateManifest("\"512\": \"icon3.png\", \n");
+ ManifestData manifest(std::move(ext_manifest), "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/incognito_info.cc b/chromium/extensions/common/manifest_handlers/incognito_info.cc
new file mode 100644
index 00000000000..b7dc8b28138
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/incognito_info.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/incognito_info.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+
+IncognitoInfo::IncognitoInfo(Mode mode) : mode(mode) {}
+
+IncognitoInfo::~IncognitoInfo() {
+}
+
+// static
+bool IncognitoInfo::IsSplitMode(const Extension* extension) {
+ IncognitoInfo* info = static_cast<IncognitoInfo*>(
+ extension->GetManifestData(keys::kIncognito));
+ return info ? info->mode == Mode::SPLIT : false;
+}
+
+// static
+bool IncognitoInfo::IsIncognitoAllowed(const Extension* extension) {
+ IncognitoInfo* info =
+ static_cast<IncognitoInfo*>(extension->GetManifestData(keys::kIncognito));
+ return info ? info->mode != Mode::NOT_ALLOWED : true;
+}
+
+IncognitoHandler::IncognitoHandler() {
+}
+
+IncognitoHandler::~IncognitoHandler() {
+}
+
+bool IncognitoHandler::Parse(Extension* extension, base::string16* error) {
+ // Extensions and Chrome apps default to spanning mode.
+ // Hosted and legacy packaged apps default to split mode.
+ IncognitoInfo::Mode mode =
+ extension->is_hosted_app() || extension->is_legacy_packaged_app()
+ ? IncognitoInfo::Mode::SPLIT
+ : IncognitoInfo::Mode::SPANNING;
+ if (!extension->manifest()->HasKey(keys::kIncognito)) {
+ extension->SetManifestData(keys::kIncognito, new IncognitoInfo(mode));
+ return true;
+ }
+
+ std::string incognito_string;
+ if (!extension->manifest()->GetString(keys::kIncognito, &incognito_string)) {
+ *error = base::ASCIIToUTF16(manifest_errors::kInvalidIncognitoBehavior);
+ return false;
+ }
+
+ if (incognito_string == manifest_values::kIncognitoSplit) {
+ mode = IncognitoInfo::Mode::SPLIT;
+ } else if (incognito_string == manifest_values::kIncognitoSpanning) {
+ mode = IncognitoInfo::Mode::SPANNING;
+ } else if (incognito_string == manifest_values::kIncognitoNotAllowed) {
+ mode = IncognitoInfo::Mode::NOT_ALLOWED;
+ } else {
+ *error = base::ASCIIToUTF16(manifest_errors::kInvalidIncognitoBehavior);
+ return false;
+ }
+
+ extension->SetManifestData(keys::kIncognito, new IncognitoInfo(mode));
+ return true;
+}
+
+bool IncognitoHandler::AlwaysParseForType(Manifest::Type type) const {
+ return true;
+}
+
+const std::vector<std::string> IncognitoHandler::Keys() const {
+ return SingleKey(keys::kIncognito);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/incognito_info.h b/chromium/extensions/common/manifest_handlers/incognito_info.h
new file mode 100644
index 00000000000..35df2ddf9e6
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/incognito_info.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_INCOGNITO_INFO_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_INCOGNITO_INFO_H_
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+struct IncognitoInfo : public Extension::ManifestData {
+ enum Mode { SPLIT, SPANNING, NOT_ALLOWED };
+
+ explicit IncognitoInfo(Mode mode);
+
+ ~IncognitoInfo() override;
+
+ // If true, a separate process will be used for the extension in incognito
+ // mode.
+ Mode mode;
+
+ // Return the incognito mode information for the given |extension|.
+ static bool IsSplitMode(const Extension* extension);
+
+ // Return whether this extension can be run in incognito mode as specified
+ // in its manifest.
+ static bool IsIncognitoAllowed(const Extension* extension);
+};
+
+// Parses the "incognito" manifest key.
+class IncognitoHandler : public ManifestHandler {
+ public:
+ IncognitoHandler();
+ ~IncognitoHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+ bool AlwaysParseForType(Manifest::Type type) const override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(IncognitoHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_INCOGNITO_INFO_H_
diff --git a/chromium/extensions/common/manifest_handlers/incognito_manifest_unittest.cc b/chromium/extensions/common/manifest_handlers/incognito_manifest_unittest.cc
new file mode 100644
index 00000000000..05511627906
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/incognito_manifest_unittest.cc
@@ -0,0 +1,45 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/extension_util.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handlers/incognito_info.h"
+#include "extensions/common/manifest_test.h"
+
+namespace extensions {
+
+using IncognitoManifestTest = ManifestTest;
+
+TEST_F(IncognitoManifestTest, IncognitoNotAllowed) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("incognito_not_allowed.json"));
+ EXPECT_FALSE(IncognitoInfo::IsIncognitoAllowed(extension.get()));
+ EXPECT_FALSE(IncognitoInfo::IsSplitMode(extension.get()));
+ EXPECT_FALSE(util::CanBeIncognitoEnabled(extension.get()));
+}
+
+TEST_F(IncognitoManifestTest, IncognitoSpanning) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("incognito_spanning.json"));
+ EXPECT_TRUE(IncognitoInfo::IsIncognitoAllowed(extension.get()));
+ EXPECT_FALSE(IncognitoInfo::IsSplitMode(extension.get()));
+ EXPECT_TRUE(util::CanBeIncognitoEnabled(extension.get()));
+}
+
+TEST_F(IncognitoManifestTest, IncognitoSplit) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("incognito_split.json"));
+ EXPECT_TRUE(IncognitoInfo::IsIncognitoAllowed(extension.get()));
+ EXPECT_TRUE(IncognitoInfo::IsSplitMode(extension.get()));
+ EXPECT_TRUE(util::CanBeIncognitoEnabled(extension.get()));
+}
+
+TEST_F(IncognitoManifestTest, IncognitoUnspecified) {
+ scoped_refptr<Extension> extension(LoadAndExpectSuccess("minimal.json"));
+ EXPECT_TRUE(IncognitoInfo::IsIncognitoAllowed(extension.get()));
+ EXPECT_FALSE(IncognitoInfo::IsSplitMode(extension.get()));
+ EXPECT_TRUE(util::CanBeIncognitoEnabled(extension.get()));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/kiosk_mode_info.cc b/chromium/extensions/common/manifest_handlers/kiosk_mode_info.cc
new file mode 100644
index 00000000000..d625bfd7d36
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/kiosk_mode_info.cc
@@ -0,0 +1,150 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/kiosk_mode_info.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "extensions/common/api/extensions_manifest_types.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+
+using api::extensions_manifest_types::KioskSecondaryAppsType;
+
+KioskModeInfo::KioskModeInfo(KioskStatus kiosk_status,
+ const std::vector<std::string>& secondary_app_ids,
+ const std::string& required_platform_version)
+ : kiosk_status(kiosk_status),
+ secondary_app_ids(secondary_app_ids),
+ required_platform_version(required_platform_version) {}
+
+KioskModeInfo::~KioskModeInfo() {
+}
+
+// static
+KioskModeInfo* KioskModeInfo::Get(const Extension* extension) {
+ return static_cast<KioskModeInfo*>(
+ extension->GetManifestData(keys::kKioskMode));
+}
+
+// static
+bool KioskModeInfo::IsKioskEnabled(const Extension* extension) {
+ KioskModeInfo* info = Get(extension);
+ return info ? info->kiosk_status != NONE : false;
+}
+
+// static
+bool KioskModeInfo::IsKioskOnly(const Extension* extension) {
+ KioskModeInfo* info = Get(extension);
+ return info ? info->kiosk_status == ONLY : false;
+}
+
+// static
+bool KioskModeInfo::HasSecondaryApps(const Extension* extension) {
+ KioskModeInfo* info = Get(extension);
+ return info && !info->secondary_app_ids.empty();
+}
+
+// static
+bool KioskModeInfo::IsValidPlatformVersion(const std::string& version_string) {
+ const base::Version version(version_string);
+ return version.IsValid() && version.components().size() <= 3u;
+}
+
+KioskModeHandler::KioskModeHandler() {
+ supported_keys_.push_back(keys::kKiosk);
+ supported_keys_.push_back(keys::kKioskEnabled);
+ supported_keys_.push_back(keys::kKioskOnly);
+ supported_keys_.push_back(keys::kKioskSecondaryApps);
+}
+
+KioskModeHandler::~KioskModeHandler() {
+}
+
+bool KioskModeHandler::Parse(Extension* extension, base::string16* error) {
+ const Manifest* manifest = extension->manifest();
+ DCHECK(manifest->HasKey(keys::kKioskEnabled) ||
+ manifest->HasKey(keys::kKioskOnly));
+
+ bool kiosk_enabled = false;
+ if (manifest->HasKey(keys::kKioskEnabled) &&
+ !manifest->GetBoolean(keys::kKioskEnabled, &kiosk_enabled)) {
+ *error = base::ASCIIToUTF16(manifest_errors::kInvalidKioskEnabled);
+ return false;
+ }
+
+ bool kiosk_only = false;
+ if (manifest->HasKey(keys::kKioskOnly) &&
+ !manifest->GetBoolean(keys::kKioskOnly, &kiosk_only)) {
+ *error = base::ASCIIToUTF16(manifest_errors::kInvalidKioskOnly);
+ return false;
+ }
+
+ if (kiosk_only && !kiosk_enabled) {
+ *error = base::ASCIIToUTF16(
+ manifest_errors::kInvalidKioskOnlyButNotEnabled);
+ return false;
+ }
+
+ // All other use cases should be already filtered out by manifest feature
+ // checks.
+ DCHECK(extension->is_platform_app());
+
+ KioskModeInfo::KioskStatus kiosk_status = KioskModeInfo::NONE;
+ if (kiosk_enabled)
+ kiosk_status = kiosk_only ? KioskModeInfo::ONLY : KioskModeInfo::ENABLED;
+
+ // Kiosk secondary apps key is optional.
+ std::vector<std::string> ids;
+ if (manifest->HasKey(keys::kKioskSecondaryApps)) {
+ const base::Value* secondary_apps = nullptr;
+ const base::ListValue* list = nullptr;
+ if (!manifest->Get(keys::kKioskSecondaryApps, &secondary_apps) ||
+ !secondary_apps->GetAsList(&list)) {
+ *error = base::ASCIIToUTF16(manifest_errors::kInvalidKioskSecondaryApps);
+ return false;
+ }
+
+ for (const base::Value* value : *list) {
+ scoped_ptr<KioskSecondaryAppsType> app =
+ KioskSecondaryAppsType::FromValue(*value, error);
+ if (!app) {
+ *error = base::ASCIIToUTF16(
+ manifest_errors::kInvalidKioskSecondaryAppsBadAppId);
+ return false;
+ }
+ ids.push_back(app->id);
+ }
+ }
+
+ // Optional kiosk.required_platform_version key.
+ std::string required_platform_version;
+ if (manifest->HasPath(keys::kKioskRequiredPlatformVersion) &&
+ (!manifest->GetString(keys::kKioskRequiredPlatformVersion,
+ &required_platform_version) ||
+ !KioskModeInfo::IsValidPlatformVersion(required_platform_version))) {
+ *error = base::ASCIIToUTF16(
+ manifest_errors::kInvalidKioskRequiredPlatformVersion);
+ return false;
+ }
+
+ extension->SetManifestData(
+ keys::kKioskMode,
+ new KioskModeInfo(kiosk_status, ids, required_platform_version));
+
+ return true;
+}
+
+const std::vector<std::string> KioskModeHandler::Keys() const {
+ return supported_keys_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/kiosk_mode_info.h b/chromium/extensions/common/manifest_handlers/kiosk_mode_info.h
new file mode 100644
index 00000000000..bd37f969c65
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/kiosk_mode_info.h
@@ -0,0 +1,74 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_KIOSK_MODE_INFO_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_KIOSK_MODE_INFO_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+struct KioskModeInfo : public Extension::ManifestData {
+ public:
+ enum KioskStatus {
+ NONE,
+ ENABLED,
+ ONLY
+ };
+
+ KioskModeInfo(KioskStatus kiosk_status,
+ const std::vector<std::string>& secondary_app_ids,
+ const std::string& required_platform_version);
+ ~KioskModeInfo() override;
+
+ // Gets the KioskModeInfo for |extension|, or NULL if none was
+ // specified.
+ static KioskModeInfo* Get(const Extension* extension);
+
+ // Whether the extension or app is enabled for app kiosk mode.
+ static bool IsKioskEnabled(const Extension* extension);
+
+ // Whether the extension or app should only be available in kiosk mode.
+ static bool IsKioskOnly(const Extension* extension);
+
+ // Returns true if |extension| declares kiosk secondary apps.
+ static bool HasSecondaryApps(const Extension* extension);
+
+ // Whether the given |version_string| is a valid ChromeOS platform version.
+ // The acceptable format is major[.minor[.micro]].
+ static bool IsValidPlatformVersion(const std::string& version_string);
+
+ KioskStatus kiosk_status;
+
+ // The IDs of the kiosk secondary apps.
+ const std::vector<std::string> secondary_app_ids;
+
+ const std::string required_platform_version;
+};
+
+// Parses the "kiosk_enabled" and "kiosk_only" manifest keys.
+class KioskModeHandler : public ManifestHandler {
+ public:
+ KioskModeHandler();
+ ~KioskModeHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+
+ std::vector<std::string> supported_keys_;
+
+ DISALLOW_COPY_AND_ASSIGN(KioskModeHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_KIOSK_MODE_INFO_H_
diff --git a/chromium/extensions/common/manifest_handlers/kiosk_mode_info_unittest.cc b/chromium/extensions/common/manifest_handlers/kiosk_mode_info_unittest.cc
new file mode 100644
index 00000000000..444af6380b7
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/kiosk_mode_info_unittest.cc
@@ -0,0 +1,56 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/kiosk_mode_info.h"
+#include "extensions/common/manifest_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+using KioskModeInfoManifestTest = ManifestTest;
+
+TEST_F(KioskModeInfoManifestTest, NoSecondaryApps) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("kiosk_secondary_app_no_secondary_app.json"));
+ EXPECT_FALSE(KioskModeInfo::HasSecondaryApps(extension.get()));
+}
+
+TEST_F(KioskModeInfoManifestTest, MultipleSecondaryApps) {
+ const std::string expected_ids[] = {
+ "fiehokkcgaojmbhfhlpiheggjhaedjoc",
+ "ihplaomghjbeafnpnjkhppmfpnmdihgd"};
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("kiosk_secondary_app_multi_apps.json"));
+ EXPECT_TRUE(KioskModeInfo::HasSecondaryApps(extension.get()));
+ KioskModeInfo* info = KioskModeInfo::Get(extension.get());
+ EXPECT_NE(nullptr, info);
+ std::vector<std::string> parsed_ids(info->secondary_app_ids);
+ std::sort(parsed_ids.begin(), parsed_ids.end());
+ EXPECT_TRUE(
+ std::equal(parsed_ids.begin(), parsed_ids.end(), expected_ids));
+}
+
+TEST_F(KioskModeInfoManifestTest, RequiredPlatformVersionOptional) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("kiosk_required_platform_version_not_present.json"));
+ KioskModeInfo* info = KioskModeInfo::Get(extension.get());
+ EXPECT_TRUE(info->required_platform_version.empty());
+}
+
+TEST_F(KioskModeInfoManifestTest, RequiredPlatformVersion) {
+ scoped_refptr<Extension> extension(
+ LoadAndExpectSuccess("kiosk_required_platform_version.json"));
+ KioskModeInfo* info = KioskModeInfo::Get(extension.get());
+ EXPECT_EQ("1234.0.0", info->required_platform_version);
+}
+
+TEST_F(KioskModeInfoManifestTest, RequiredPlatformVersionInvalid) {
+ LoadAndExpectError("kiosk_required_platform_version_empty.json",
+ manifest_errors::kInvalidKioskRequiredPlatformVersion);
+ LoadAndExpectError("kiosk_required_platform_version_invalid.json",
+ manifest_errors::kInvalidKioskRequiredPlatformVersion);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/launcher_page_info.cc b/chromium/extensions/common/manifest_handlers/launcher_page_info.cc
new file mode 100644
index 00000000000..0e1afea72b7
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/launcher_page_info.cc
@@ -0,0 +1,76 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/launcher_page_info.h"
+
+#include "base/files/file_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+#include "grit/extensions_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+LauncherPageHandler::LauncherPageHandler() {
+}
+
+LauncherPageHandler::~LauncherPageHandler() {
+}
+
+// static
+LauncherPageInfo* LauncherPageHandler::GetInfo(const Extension* extension) {
+ return static_cast<LauncherPageInfo*>(
+ extension->GetManifestData(manifest_keys::kLauncherPage));
+}
+
+bool LauncherPageHandler::Parse(Extension* extension, base::string16* error) {
+ const extensions::Manifest* manifest = extension->manifest();
+ scoped_ptr<LauncherPageInfo> launcher_page_info(new LauncherPageInfo);
+ const base::DictionaryValue* launcher_page_dict = NULL;
+ if (!manifest->GetDictionary(manifest_keys::kLauncherPage,
+ &launcher_page_dict)) {
+ *error = base::ASCIIToUTF16(manifest_errors::kInvalidLauncherPage);
+ return false;
+ }
+
+ if (!manifest->HasPath(extensions::manifest_keys::kLauncherPagePage)) {
+ *error = base::ASCIIToUTF16(manifest_errors::kLauncherPagePageRequired);
+ return false;
+ }
+
+ std::string launcher_page_page;
+ if (!manifest->GetString(extensions::manifest_keys::kLauncherPagePage,
+ &launcher_page_page)) {
+ *error = base::ASCIIToUTF16(manifest_errors::kInvalidLauncherPagePage);
+ return false;
+ }
+
+ launcher_page_info->page = launcher_page_page;
+
+ extension->SetManifestData(manifest_keys::kLauncherPage,
+ launcher_page_info.release());
+ return true;
+}
+
+bool LauncherPageHandler::Validate(
+ const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const {
+ LauncherPageInfo* info = GetInfo(extension);
+ const base::FilePath path = extension->GetResource(info->page).GetFilePath();
+ if (!base::PathExists(path)) {
+ *error = l10n_util::GetStringFUTF8(IDS_EXTENSION_LOAD_LAUNCHER_PAGE_FAILED,
+ base::UTF8ToUTF16(info->page));
+ return false;
+ }
+
+ return true;
+}
+
+const std::vector<std::string> LauncherPageHandler::Keys() const {
+ return SingleKey(manifest_keys::kLauncherPage);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/launcher_page_info.h b/chromium/extensions/common/manifest_handlers/launcher_page_info.h
new file mode 100644
index 00000000000..7337658c3f8
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/launcher_page_info.h
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_LAUNCHER_PAGE_INFO_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_LAUNCHER_PAGE_INFO_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+struct LauncherPageInfo : public Extension::ManifestData {
+ // The page URL.
+ std::string page;
+};
+
+// Parses the "launcher_page" manifest key.
+class LauncherPageHandler : public ManifestHandler {
+ public:
+ LauncherPageHandler();
+ ~LauncherPageHandler() override;
+
+ // Gets the LauncherPageInfo for a given |extension|.
+ static LauncherPageInfo* GetInfo(const Extension* extension);
+
+ // ManifestHandler overrides:
+ bool Parse(Extension* extension, base::string16* error) override;
+ bool Validate(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(LauncherPageHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_LAUNCHER_PAGE_INFO_H_
diff --git a/chromium/extensions/common/manifest_handlers/mime_types_handler.cc b/chromium/extensions/common/manifest_handlers/mime_types_handler.cc
new file mode 100644
index 00000000000..3c90b609500
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/mime_types_handler.cc
@@ -0,0 +1,132 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/mime_types_handler.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace keys = extensions::manifest_keys;
+namespace errors = extensions::manifest_errors;
+
+namespace {
+
+const char* const kMIMETypeHandlersWhitelist[] = {
+ extension_misc::kPdfExtensionId,
+ extension_misc::kQuickOfficeComponentExtensionId,
+ extension_misc::kQuickOfficeInternalExtensionId,
+ extension_misc::kQuickOfficeExtensionId,
+ extension_misc::kMimeHandlerPrivateTestExtensionId};
+
+// Stored on the Extension.
+struct MimeTypesHandlerInfo : public extensions::Extension::ManifestData {
+ MimeTypesHandler handler_;
+
+ MimeTypesHandlerInfo();
+ ~MimeTypesHandlerInfo() override;
+};
+
+MimeTypesHandlerInfo::MimeTypesHandlerInfo() {
+}
+
+MimeTypesHandlerInfo::~MimeTypesHandlerInfo() {
+}
+
+} // namespace
+
+// static
+std::vector<std::string> MimeTypesHandler::GetMIMETypeWhitelist() {
+ std::vector<std::string> whitelist;
+ for (size_t i = 0; i < arraysize(kMIMETypeHandlersWhitelist); ++i)
+ whitelist.push_back(kMIMETypeHandlersWhitelist[i]);
+ return whitelist;
+}
+
+MimeTypesHandler::MimeTypesHandler() {
+}
+
+MimeTypesHandler::~MimeTypesHandler() {
+}
+
+void MimeTypesHandler::AddMIMEType(const std::string& mime_type) {
+ mime_type_set_.insert(mime_type);
+}
+
+bool MimeTypesHandler::CanHandleMIMEType(const std::string& mime_type) const {
+ return mime_type_set_.find(mime_type) != mime_type_set_.end();
+}
+
+bool MimeTypesHandler::HasPlugin() const {
+ return !handler_url_.empty();
+}
+
+base::FilePath MimeTypesHandler::GetPluginPath() const {
+ // TODO(raymes): Storing the extension URL in a base::FilePath is really
+ // nasty. We should probably just use the extension ID as the placeholder path
+ // instead.
+ return base::FilePath::FromUTF8Unsafe(
+ std::string(extensions::kExtensionScheme) + "://" + extension_id_ + "/");
+}
+
+// static
+MimeTypesHandler* MimeTypesHandler::GetHandler(
+ const extensions::Extension* extension) {
+ MimeTypesHandlerInfo* info = static_cast<MimeTypesHandlerInfo*>(
+ extension->GetManifestData(keys::kMimeTypesHandler));
+ if (info)
+ return &info->handler_;
+ return NULL;
+}
+
+MimeTypesHandlerParser::MimeTypesHandlerParser() {
+}
+
+MimeTypesHandlerParser::~MimeTypesHandlerParser() {
+}
+
+bool MimeTypesHandlerParser::Parse(extensions::Extension* extension,
+ base::string16* error) {
+ const base::ListValue* mime_types_value = NULL;
+ if (!extension->manifest()->GetList(keys::kMIMETypes,
+ &mime_types_value)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidMimeTypesHandler);
+ return false;
+ }
+
+ scoped_ptr<MimeTypesHandlerInfo> info(new MimeTypesHandlerInfo);
+ info->handler_.set_extension_id(extension->id());
+ for (size_t i = 0; i < mime_types_value->GetSize(); ++i) {
+ std::string filter;
+ if (!mime_types_value->GetString(i, &filter)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidMIMETypes);
+ return false;
+ }
+ info->handler_.AddMIMEType(filter);
+ }
+
+ std::string mime_types_handler;
+ if (extension->manifest()->GetString(keys::kMimeTypesHandler,
+ &mime_types_handler)) {
+ info->handler_.set_handler_url(mime_types_handler);
+ }
+
+ extension->SetManifestData(keys::kMimeTypesHandler, info.release());
+ return true;
+}
+
+const std::vector<std::string> MimeTypesHandlerParser::Keys() const {
+ std::vector<std::string> keys;
+ keys.push_back(keys::kMIMETypes);
+ keys.push_back(keys::kMimeTypesHandler);
+ return keys;
+}
diff --git a/chromium/extensions/common/manifest_handlers/mime_types_handler.h b/chromium/extensions/common/manifest_handlers/mime_types_handler.h
new file mode 100644
index 00000000000..1041a8de124
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/mime_types_handler.h
@@ -0,0 +1,76 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_MIME_TYPES_HANDLER_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_MIME_TYPES_HANDLER_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+class MimeTypesHandler {
+ public:
+ // Returns list of extensions' ids that are allowed to use MIME type filters.
+ static std::vector<std::string> GetMIMETypeWhitelist();
+
+ static MimeTypesHandler* GetHandler(const extensions::Extension* extension);
+
+ MimeTypesHandler();
+ ~MimeTypesHandler();
+
+ // extension id
+ std::string extension_id() const { return extension_id_; }
+ void set_extension_id(const std::string& extension_id) {
+ extension_id_ = extension_id;
+ }
+
+ // Adds a MIME type filter to the handler.
+ void AddMIMEType(const std::string& mime_type);
+ // Tests if the handler has registered a filter for the MIME type.
+ bool CanHandleMIMEType(const std::string& mime_type) const;
+
+ // Set the URL that will be used to handle MIME type requests.
+ void set_handler_url(const std::string& handler_url) {
+ handler_url_ = handler_url;
+ }
+ // The URL that will be used to handle MIME type requests.
+ const std::string& handler_url() const { return handler_url_; }
+
+ const std::set<std::string>& mime_type_set() const { return mime_type_set_; }
+
+ // Returns true if this MimeTypesHandler has a plugin associated with it (for
+ // the mimeHandlerPrivate API). Returns false if the MimeTypesHandler is for
+ // the streamsPrivate API.
+ bool HasPlugin() const;
+
+ // If HasPlugin() returns true, this will return the plugin path for the
+ // plugin associated with this MimeTypesHandler.
+ base::FilePath GetPluginPath() const;
+
+ private:
+ // The id for the extension this action belongs to (as defined in the
+ // extension manifest).
+ std::string extension_id_;
+
+ // A list of MIME type filters.
+ std::set<std::string> mime_type_set_;
+
+ std::string handler_url_;
+};
+
+class MimeTypesHandlerParser : public extensions::ManifestHandler {
+ public:
+ MimeTypesHandlerParser();
+ ~MimeTypesHandlerParser() override;
+
+ bool Parse(extensions::Extension* extension, base::string16* error) override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+};
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_MIME_TYPES_HANDLER_H_
diff --git a/chromium/extensions/common/manifest_handlers/nacl_modules_handler.cc b/chromium/extensions/common/manifest_handlers/nacl_modules_handler.cc
new file mode 100644
index 00000000000..27df6cf4297
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/nacl_modules_handler.cc
@@ -0,0 +1,91 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/nacl_modules_handler.h"
+
+#include <stddef.h>
+
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+namespace {
+
+struct NaClModuleData : Extension::ManifestData {
+ // Optional list of NaCl modules and associated properties.
+ NaClModuleInfo::List nacl_modules_;
+};
+
+} // namespace
+
+// static
+const NaClModuleInfo::List* NaClModuleInfo::GetNaClModules(
+ const Extension* extension) {
+ NaClModuleData* data = static_cast<NaClModuleData*>(
+ extension->GetManifestData(keys::kNaClModules));
+ return data ? &data->nacl_modules_ : NULL;
+}
+
+NaClModulesHandler::NaClModulesHandler() {
+}
+
+NaClModulesHandler::~NaClModulesHandler() {
+}
+
+bool NaClModulesHandler::Parse(Extension* extension, base::string16* error) {
+ const base::ListValue* list_value = NULL;
+ if (!extension->manifest()->GetList(keys::kNaClModules, &list_value)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidNaClModules);
+ return false;
+ }
+
+ scoped_ptr<NaClModuleData> nacl_module_data(new NaClModuleData);
+
+ for (size_t i = 0; i < list_value->GetSize(); ++i) {
+ const base::DictionaryValue* module_value = NULL;
+ if (!list_value->GetDictionary(i, &module_value)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidNaClModules);
+ return false;
+ }
+
+ // Get nacl_modules[i].path.
+ std::string path_str;
+ if (!module_value->GetString(keys::kNaClModulesPath, &path_str)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidNaClModulesPath, base::SizeTToString(i));
+ return false;
+ }
+
+ // Get nacl_modules[i].mime_type.
+ std::string mime_type;
+ if (!module_value->GetString(keys::kNaClModulesMIMEType, &mime_type)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidNaClModulesMIMEType, base::SizeTToString(i));
+ return false;
+ }
+
+ nacl_module_data->nacl_modules_.push_back(NaClModuleInfo());
+ nacl_module_data->nacl_modules_.back().url =
+ extension->GetResourceURL(path_str);
+ nacl_module_data->nacl_modules_.back().mime_type = mime_type;
+ }
+
+ extension->SetManifestData(keys::kNaClModules, nacl_module_data.release());
+ return true;
+}
+
+const std::vector<std::string> NaClModulesHandler::Keys() const {
+ return SingleKey(keys::kNaClModules);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/nacl_modules_handler.h b/chromium/extensions/common/manifest_handlers/nacl_modules_handler.h
new file mode 100644
index 00000000000..4830bfab7d7
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/nacl_modules_handler.h
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_NACL_MODULES_HANDLER_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_NACL_MODULES_HANDLER_H_
+
+#include <list>
+#include <string>
+
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+// An NaCl module included in the extension.
+struct NaClModuleInfo {
+ typedef std::list<NaClModuleInfo> List;
+ // Returns the NaCl modules for the extensions, or NULL if none exist.
+ static const NaClModuleInfo::List* GetNaClModules(
+ const Extension* extension);
+
+ GURL url;
+ std::string mime_type;
+};
+
+// Parses the "nacl_modules" manifest key.
+class NaClModulesHandler : public ManifestHandler {
+ public:
+ NaClModulesHandler();
+ ~NaClModulesHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_NACL_MODULES_HANDLER_H_
diff --git a/chromium/extensions/common/manifest_handlers/oauth2_manifest_handler.cc b/chromium/extensions/common/manifest_handlers/oauth2_manifest_handler.cc
new file mode 100644
index 00000000000..73a1320158b
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/oauth2_manifest_handler.cc
@@ -0,0 +1,100 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/oauth2_manifest_handler.h"
+
+#include <stddef.h>
+
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace {
+
+// Manifest keys.
+const char kClientId[] = "client_id";
+const char kScopes[] = "scopes";
+const char kAutoApprove[] = "auto_approve";
+
+} // namespace
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+OAuth2Info::OAuth2Info() : auto_approve(false) {}
+OAuth2Info::~OAuth2Info() {}
+
+static base::LazyInstance<OAuth2Info> g_empty_oauth2_info =
+ LAZY_INSTANCE_INITIALIZER;
+
+// static
+const OAuth2Info& OAuth2Info::GetOAuth2Info(const Extension* extension) {
+ OAuth2Info* info = static_cast<OAuth2Info*>(
+ extension->GetManifestData(keys::kOAuth2));
+ return info ? *info : g_empty_oauth2_info.Get();
+}
+
+OAuth2ManifestHandler::OAuth2ManifestHandler() {
+}
+
+OAuth2ManifestHandler::~OAuth2ManifestHandler() {
+}
+
+bool OAuth2ManifestHandler::Parse(Extension* extension,
+ base::string16* error) {
+ scoped_ptr<OAuth2Info> info(new OAuth2Info);
+ const base::DictionaryValue* dict = NULL;
+ if (!extension->manifest()->GetDictionary(keys::kOAuth2, &dict)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidOAuth2ClientId);
+ return false;
+ }
+
+ // HasPath checks for whether the manifest is allowed to have
+ // oauth2.auto_approve based on whitelist, and if it is present.
+ // GetBoolean reads the value of auto_approve directly from dict to prevent
+ // duplicate checking.
+ if (extension->manifest()->HasPath(keys::kOAuth2AutoApprove) &&
+ !dict->GetBoolean(kAutoApprove, &info->auto_approve)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidOAuth2AutoApprove);
+ return false;
+ }
+
+ // Component apps using auto_approve may use Chrome's client ID by
+ // omitting the field.
+ if ((!dict->GetString(kClientId, &info->client_id) ||
+ info->client_id.empty()) &&
+ (extension->location() != Manifest::COMPONENT || !info->auto_approve)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidOAuth2ClientId);
+ return false;
+ }
+
+ const base::ListValue* list = NULL;
+ if (!dict->GetList(kScopes, &list)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidOAuth2Scopes);
+ return false;
+ }
+
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ std::string scope;
+ if (!list->GetString(i, &scope)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidOAuth2Scopes);
+ return false;
+ }
+ info->scopes.push_back(scope);
+ }
+
+ extension->SetManifestData(keys::kOAuth2, info.release());
+ return true;
+}
+
+const std::vector<std::string> OAuth2ManifestHandler::Keys() const {
+ return SingleKey(keys::kOAuth2);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/oauth2_manifest_handler.h b/chromium/extensions/common/manifest_handlers/oauth2_manifest_handler.h
new file mode 100644
index 00000000000..20276e7f834
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/oauth2_manifest_handler.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_OAUTH2_MANIFEST_HANDLER_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_OAUTH2_MANIFEST_HANDLER_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+// OAuth2 info included in the extension.
+struct OAuth2Info : public Extension::ManifestData {
+ OAuth2Info();
+ ~OAuth2Info() override;
+
+ std::string client_id;
+ std::vector<std::string> scopes;
+
+ // Indicates that approval UI can be skipped for a set of whitelisted apps.
+ bool auto_approve;
+
+ static const OAuth2Info& GetOAuth2Info(const Extension* extension);
+};
+
+// Parses the "oauth2" manifest key.
+class OAuth2ManifestHandler : public ManifestHandler {
+ public:
+ OAuth2ManifestHandler();
+ ~OAuth2ManifestHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(OAuth2ManifestHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_OAUTH2_MANIFEST_HANDLER_H_
diff --git a/chromium/extensions/common/manifest_handlers/oauth2_manifest_unittest.cc b/chromium/extensions/common/manifest_handlers/oauth2_manifest_unittest.cc
new file mode 100644
index 00000000000..f09c97143b1
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/oauth2_manifest_unittest.cc
@@ -0,0 +1,319 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <utility>
+
+#include "base/test/values_test_util.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/oauth2_manifest_handler.h"
+#include "extensions/common/manifest_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+namespace {
+
+// Produces extension ID = "mdbihdcgjmagbcapkhhkjbbdlkflmbfo".
+const char kExtensionKey[] =
+ "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCV9PlZjcTIXfnlB3HXo50OlM/CnIq0y7jm"
+ "KfPVyStaWsmFB7NaVnqUXoGb9swBDfVnZ6BrupwnxL76TWEJPo+KQMJ6uz0PPdJWi2jQfZiG"
+ "iheDiKH5Gv+dVd67qf7ly8QWW0o8qmFpqBZQpksm1hOGbfsupv9W4c42tMEIicDMLQIDAQAB";
+const char kAutoApproveNotAllowedWarning[] =
+ "'oauth2.auto_approve' is not allowed for specified extension ID.";
+
+} // namespace
+
+class OAuth2ManifestTest : public ManifestTest {
+ protected:
+ enum AutoApproveValue {
+ AUTO_APPROVE_NOT_SET,
+ AUTO_APPROVE_FALSE,
+ AUTO_APPROVE_TRUE,
+ AUTO_APPROVE_INVALID
+ };
+
+ enum ClientIdValue {
+ CLIENT_ID_DEFAULT,
+ CLIENT_ID_NOT_SET,
+ CLIENT_ID_EMPTY
+ };
+
+ scoped_ptr<base::DictionaryValue> CreateManifest(
+ AutoApproveValue auto_approve,
+ bool extension_id_whitelisted,
+ ClientIdValue client_id) {
+ scoped_ptr<base::DictionaryValue> manifest = base::DictionaryValue::From(
+ base::test::ParseJson("{ \n"
+ " \"name\": \"test\", \n"
+ " \"version\": \"0.1\", \n"
+ " \"manifest_version\": 2, \n"
+ " \"oauth2\": { \n"
+ " \"scopes\": [ \"scope1\" ], \n"
+ " }, \n"
+ "} \n"));
+ EXPECT_TRUE(manifest);
+ switch (auto_approve) {
+ case AUTO_APPROVE_NOT_SET:
+ break;
+ case AUTO_APPROVE_FALSE:
+ manifest->SetBoolean(keys::kOAuth2AutoApprove, false);
+ break;
+ case AUTO_APPROVE_TRUE:
+ manifest->SetBoolean(keys::kOAuth2AutoApprove, true);
+ break;
+ case AUTO_APPROVE_INVALID:
+ manifest->SetString(keys::kOAuth2AutoApprove, "incorrect value");
+ break;
+ }
+ switch (client_id) {
+ case CLIENT_ID_DEFAULT:
+ manifest->SetString(keys::kOAuth2ClientId, "client1");
+ break;
+ case CLIENT_ID_NOT_SET:
+ break;
+ case CLIENT_ID_EMPTY:
+ manifest->SetString(keys::kOAuth2ClientId, "");
+ }
+ if (extension_id_whitelisted)
+ manifest->SetString(keys::kKey, kExtensionKey);
+ return manifest;
+ }
+
+};
+
+TEST_F(OAuth2ManifestTest, OAuth2SectionParsing) {
+ base::DictionaryValue base_manifest;
+
+ base_manifest.SetString(keys::kName, "test");
+ base_manifest.SetString(keys::kVersion, "0.1");
+ base_manifest.SetInteger(keys::kManifestVersion, 2);
+ base_manifest.SetString(keys::kOAuth2ClientId, "client1");
+ base::ListValue* scopes = new base::ListValue();
+ scopes->Append(new base::StringValue("scope1"));
+ scopes->Append(new base::StringValue("scope2"));
+ base_manifest.Set(keys::kOAuth2Scopes, scopes);
+
+ // OAuth2 section should be parsed for an extension.
+ {
+ base::DictionaryValue ext_manifest;
+ // Lack of "app" section representa an extension. So the base manifest
+ // itself represents an extension.
+ ext_manifest.MergeDictionary(&base_manifest);
+ ext_manifest.SetString(keys::kKey, kExtensionKey);
+ ext_manifest.SetBoolean(keys::kOAuth2AutoApprove, true);
+
+ ManifestData manifest(&ext_manifest, "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_TRUE(extension->install_warnings().empty());
+ EXPECT_EQ("client1", OAuth2Info::GetOAuth2Info(extension.get()).client_id);
+ EXPECT_EQ(2U, OAuth2Info::GetOAuth2Info(extension.get()).scopes.size());
+ EXPECT_EQ("scope1", OAuth2Info::GetOAuth2Info(extension.get()).scopes[0]);
+ EXPECT_EQ("scope2", OAuth2Info::GetOAuth2Info(extension.get()).scopes[1]);
+ EXPECT_TRUE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+ }
+
+ // OAuth2 section should be parsed for a packaged app.
+ {
+ base::DictionaryValue app_manifest;
+ app_manifest.SetString(keys::kLaunchLocalPath, "launch.html");
+ app_manifest.MergeDictionary(&base_manifest);
+
+ ManifestData manifest(&app_manifest, "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_TRUE(extension->install_warnings().empty());
+ EXPECT_EQ("client1", OAuth2Info::GetOAuth2Info(extension.get()).client_id);
+ EXPECT_EQ(2U, OAuth2Info::GetOAuth2Info(extension.get()).scopes.size());
+ EXPECT_EQ("scope1", OAuth2Info::GetOAuth2Info(extension.get()).scopes[0]);
+ EXPECT_EQ("scope2", OAuth2Info::GetOAuth2Info(extension.get()).scopes[1]);
+ EXPECT_FALSE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+ }
+
+ // OAuth2 section should NOT be parsed for a hosted app.
+ {
+ base::DictionaryValue app_manifest;
+ app_manifest.SetString(keys::kLaunchWebURL, "http://www.google.com");
+ app_manifest.MergeDictionary(&base_manifest);
+
+ ManifestData manifest(&app_manifest, "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_EQ(1U, extension->install_warnings().size());
+ const extensions::InstallWarning& warning =
+ extension->install_warnings()[0];
+ EXPECT_EQ("'oauth2' is only allowed for extensions, legacy packaged apps, "
+ "and packaged apps, but this is a hosted app.",
+ warning.message);
+ EXPECT_EQ("", OAuth2Info::GetOAuth2Info(extension.get()).client_id);
+ EXPECT_TRUE(OAuth2Info::GetOAuth2Info(extension.get()).scopes.empty());
+ EXPECT_FALSE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+ }
+}
+
+TEST_F(OAuth2ManifestTest, AutoApproveNotSetExtensionNotOnWhitelist) {
+ scoped_ptr<base::DictionaryValue> ext_manifest =
+ CreateManifest(AUTO_APPROVE_NOT_SET, false, CLIENT_ID_DEFAULT);
+ ManifestData manifest(std::move(ext_manifest), "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_TRUE(extension->install_warnings().empty());
+ EXPECT_FALSE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+}
+
+TEST_F(OAuth2ManifestTest, AutoApproveFalseExtensionNotOnWhitelist) {
+ scoped_ptr<base::DictionaryValue> ext_manifest =
+ CreateManifest(AUTO_APPROVE_FALSE, false, CLIENT_ID_DEFAULT);
+ ManifestData manifest(std::move(ext_manifest), "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_EQ(1U, extension->install_warnings().size());
+ const extensions::InstallWarning& warning =
+ extension->install_warnings()[0];
+ EXPECT_EQ(kAutoApproveNotAllowedWarning, warning.message);
+ EXPECT_FALSE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+}
+
+TEST_F(OAuth2ManifestTest, AutoApproveTrueExtensionNotOnWhitelist) {
+ scoped_ptr<base::DictionaryValue> ext_manifest =
+ CreateManifest(AUTO_APPROVE_TRUE, false, CLIENT_ID_DEFAULT);
+ ManifestData manifest(std::move(ext_manifest), "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_EQ(1U, extension->install_warnings().size());
+ const extensions::InstallWarning& warning =
+ extension->install_warnings()[0];
+ EXPECT_EQ(kAutoApproveNotAllowedWarning, warning.message);
+ EXPECT_FALSE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+}
+
+TEST_F(OAuth2ManifestTest, AutoApproveInvalidExtensionNotOnWhitelist) {
+ scoped_ptr<base::DictionaryValue> ext_manifest =
+ CreateManifest(AUTO_APPROVE_INVALID, false, CLIENT_ID_DEFAULT);
+ ManifestData manifest(std::move(ext_manifest), "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_EQ(1U, extension->install_warnings().size());
+ const extensions::InstallWarning& warning =
+ extension->install_warnings()[0];
+ EXPECT_EQ(kAutoApproveNotAllowedWarning, warning.message);
+ EXPECT_FALSE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+}
+
+TEST_F(OAuth2ManifestTest, AutoApproveNotSetExtensionOnWhitelist) {
+ scoped_ptr<base::DictionaryValue> ext_manifest =
+ CreateManifest(AUTO_APPROVE_NOT_SET, true, CLIENT_ID_DEFAULT);
+ ManifestData manifest(std::move(ext_manifest), "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_TRUE(extension->install_warnings().empty());
+ EXPECT_FALSE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+}
+
+TEST_F(OAuth2ManifestTest, AutoApproveFalseExtensionOnWhitelist) {
+ scoped_ptr<base::DictionaryValue> ext_manifest =
+ CreateManifest(AUTO_APPROVE_FALSE, true, CLIENT_ID_DEFAULT);
+ ManifestData manifest(std::move(ext_manifest), "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_TRUE(extension->install_warnings().empty());
+ EXPECT_FALSE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+}
+
+TEST_F(OAuth2ManifestTest, AutoApproveTrueExtensionOnWhitelist) {
+ scoped_ptr<base::DictionaryValue> ext_manifest =
+ CreateManifest(AUTO_APPROVE_TRUE, true, CLIENT_ID_DEFAULT);
+ ManifestData manifest(std::move(ext_manifest), "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_TRUE(extension->install_warnings().empty());
+ EXPECT_TRUE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+}
+
+TEST_F(OAuth2ManifestTest, AutoApproveInvalidExtensionOnWhitelist) {
+ scoped_ptr<base::DictionaryValue> ext_manifest =
+ CreateManifest(AUTO_APPROVE_INVALID, true, CLIENT_ID_DEFAULT);
+ ManifestData manifest(std::move(ext_manifest), "test");
+ std::string error;
+ scoped_refptr<extensions::Extension> extension =
+ LoadExtension(manifest, &error);
+ EXPECT_EQ(
+ "Invalid value for 'oauth2.auto_approve'. Value must be true or false.",
+ error);
+}
+
+TEST_F(OAuth2ManifestTest, InvalidClientId) {
+ {
+ scoped_ptr<base::DictionaryValue> ext_manifest =
+ CreateManifest(AUTO_APPROVE_NOT_SET, false, CLIENT_ID_NOT_SET);
+ ManifestData manifest(std::move(ext_manifest), "test");
+ std::string error;
+ LoadAndExpectError(manifest, errors::kInvalidOAuth2ClientId);
+ }
+
+ {
+ scoped_ptr<base::DictionaryValue> ext_manifest =
+ CreateManifest(AUTO_APPROVE_NOT_SET, false, CLIENT_ID_EMPTY);
+ ManifestData manifest(std::move(ext_manifest), "test");
+ std::string error;
+ LoadAndExpectError(manifest, errors::kInvalidOAuth2ClientId);
+ }
+}
+
+TEST_F(OAuth2ManifestTest, ComponentInvalidClientId) {
+ // Component Apps without auto_approve must include a client ID.
+ {
+ scoped_ptr<base::DictionaryValue> ext_manifest =
+ CreateManifest(AUTO_APPROVE_NOT_SET, false, CLIENT_ID_NOT_SET);
+ ManifestData manifest(std::move(ext_manifest), "test");
+ std::string error;
+ LoadAndExpectError(manifest,
+ errors::kInvalidOAuth2ClientId,
+ extensions::Manifest::COMPONENT);
+ }
+
+ {
+ scoped_ptr<base::DictionaryValue> ext_manifest =
+ CreateManifest(AUTO_APPROVE_NOT_SET, false, CLIENT_ID_EMPTY);
+ ManifestData manifest(std::move(ext_manifest), "test");
+ std::string error;
+ LoadAndExpectError(manifest,
+ errors::kInvalidOAuth2ClientId,
+ extensions::Manifest::COMPONENT);
+ }
+}
+
+TEST_F(OAuth2ManifestTest, ComponentWithChromeClientId) {
+ {
+ scoped_ptr<base::DictionaryValue> ext_manifest =
+ CreateManifest(AUTO_APPROVE_TRUE, true, CLIENT_ID_NOT_SET);
+ ManifestData manifest(std::move(ext_manifest), "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest, extensions::Manifest::COMPONENT);
+ EXPECT_TRUE(OAuth2Info::GetOAuth2Info(extension.get()).client_id.empty());
+ }
+
+ {
+ scoped_ptr<base::DictionaryValue> ext_manifest =
+ CreateManifest(AUTO_APPROVE_TRUE, true, CLIENT_ID_EMPTY);
+ ManifestData manifest(std::move(ext_manifest), "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest, extensions::Manifest::COMPONENT);
+ EXPECT_TRUE(OAuth2Info::GetOAuth2Info(extension.get()).client_id.empty());
+ }
+}
+
+TEST_F(OAuth2ManifestTest, ComponentWithStandardClientId) {
+ scoped_ptr<base::DictionaryValue> ext_manifest =
+ CreateManifest(AUTO_APPROVE_TRUE, true, CLIENT_ID_DEFAULT);
+ ManifestData manifest(std::move(ext_manifest), "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest, extensions::Manifest::COMPONENT);
+ EXPECT_EQ("client1", OAuth2Info::GetOAuth2Info(extension.get()).client_id);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/offline_enabled_info.cc b/chromium/extensions/common/manifest_handlers/offline_enabled_info.cc
new file mode 100644
index 00000000000..4ce6637f874
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/offline_enabled_info.cc
@@ -0,0 +1,76 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/offline_enabled_info.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/permissions_parser.h"
+#include "extensions/common/permissions/api_permission_set.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+
+OfflineEnabledInfo::OfflineEnabledInfo(bool is_offline_enabled)
+ : offline_enabled(is_offline_enabled) {
+}
+
+OfflineEnabledInfo::~OfflineEnabledInfo() {
+}
+
+// static
+bool OfflineEnabledInfo::IsOfflineEnabled(const Extension* extension) {
+ OfflineEnabledInfo* info = static_cast<OfflineEnabledInfo*>(
+ extension->GetManifestData(keys::kOfflineEnabled));
+ return info ? info->offline_enabled : false;
+}
+
+OfflineEnabledHandler::OfflineEnabledHandler() {
+}
+
+OfflineEnabledHandler::~OfflineEnabledHandler() {
+}
+
+bool OfflineEnabledHandler::Parse(Extension* extension, base::string16* error) {
+ if (!extension->manifest()->HasKey(keys::kOfflineEnabled)) {
+ // Only platform apps are provided with a default offline enabled value.
+ // A platform app is offline enabled unless it requests the webview
+ // permission. That is, offline_enabled is true when there is NO webview
+ // permission requested and false when webview permission is present.
+ DCHECK(extension->is_platform_app());
+
+ const bool has_webview_permission =
+ PermissionsParser::HasAPIPermission(extension, APIPermission::kWebView);
+ extension->SetManifestData(keys::kOfflineEnabled,
+ new OfflineEnabledInfo(!has_webview_permission));
+ return true;
+ }
+
+ bool offline_enabled = false;
+
+ if (!extension->manifest()->GetBoolean(keys::kOfflineEnabled,
+ &offline_enabled)) {
+ *error = base::ASCIIToUTF16(manifest_errors::kInvalidOfflineEnabled);
+ return false;
+ }
+
+ extension->SetManifestData(keys::kOfflineEnabled,
+ new OfflineEnabledInfo(offline_enabled));
+ return true;
+}
+
+bool OfflineEnabledHandler::AlwaysParseForType(Manifest::Type type) const {
+ return type == Manifest::TYPE_PLATFORM_APP;
+}
+
+const std::vector<std::string> OfflineEnabledHandler::Keys() const {
+ return SingleKey(keys::kOfflineEnabled);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/offline_enabled_info.h b/chromium/extensions/common/manifest_handlers/offline_enabled_info.h
new file mode 100644
index 00000000000..99b9383e960
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/offline_enabled_info.h
@@ -0,0 +1,46 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_OFFLINE_ENABLED_INFO_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_OFFLINE_ENABLED_INFO_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+struct OfflineEnabledInfo : public Extension::ManifestData {
+ explicit OfflineEnabledInfo(bool offline_enabled);
+ ~OfflineEnabledInfo() override;
+
+ // Whether the extension or app should be enabled when offline.
+ // Defaults to false, except for platform apps which are offline by default.
+ bool offline_enabled;
+
+ static bool IsOfflineEnabled(const Extension* extension);
+};
+
+// Parses the "offline_enabled" manifest key.
+class OfflineEnabledHandler : public ManifestHandler {
+ public:
+ OfflineEnabledHandler();
+ ~OfflineEnabledHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+ bool AlwaysParseForType(Manifest::Type type) const override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(OfflineEnabledHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_OFFLINE_ENABLED_INFO_H_
diff --git a/chromium/extensions/common/manifest_handlers/options_page_info.cc b/chromium/extensions/common/manifest_handlers/options_page_info.cc
new file mode 100644
index 00000000000..c46a9c1eac3
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/options_page_info.cc
@@ -0,0 +1,231 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/options_page_info.h"
+
+#include "base/files/file_util.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/common/api/extensions_manifest_types.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/feature_switch.h"
+#include "extensions/common/file_util.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/strings/grit/extensions_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using base::ASCIIToUTF16;
+using base::DictionaryValue;
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+using api::extensions_manifest_types::OptionsUI;
+
+namespace {
+
+OptionsPageInfo* GetOptionsPageInfo(const Extension* extension) {
+ return static_cast<OptionsPageInfo*>(
+ extension->GetManifestData(keys::kOptionsUI));
+}
+
+// Parses |url_string| into a GURL |result| if it is a valid options page for
+// this app/extension. If not, it returns the reason in |error|. Because this
+// handles URLs for both "options_page" and "options_ui.page", the name of the
+// manifest field must be provided in |manifest_field_name|.
+bool ParseOptionsUrl(Extension* extension,
+ const std::string& url_string,
+ const std::string& manifest_field_name,
+ base::string16* error,
+ GURL* result) {
+ if (extension->is_hosted_app()) {
+ // Hosted apps require an absolute URL.
+ GURL options_url(url_string);
+ if (!options_url.is_valid() || !options_url.SchemeIsHTTPOrHTTPS()) {
+ *error = base::ASCIIToUTF16(errors::kInvalidOptionsPageInHostedApp);
+ return false;
+ }
+ *result = options_url;
+ return true;
+ }
+
+ // Otherwise the options URL should be inside the extension.
+ if (GURL(url_string).is_valid()) {
+ *error = base::ASCIIToUTF16(errors::kInvalidOptionsPageExpectUrlInPackage);
+ return false;
+ }
+
+ GURL resource_url = extension->GetResourceURL(url_string);
+ if (!resource_url.is_valid()) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidOptionsPage,
+ manifest_field_name);
+ return false;
+ }
+ *result = resource_url;
+ return true;
+}
+
+} // namespace
+
+OptionsPageInfo::OptionsPageInfo(const GURL& options_page,
+ bool chrome_styles,
+ bool open_in_tab)
+ : options_page_(options_page),
+ chrome_styles_(chrome_styles),
+ open_in_tab_(open_in_tab) {
+}
+
+OptionsPageInfo::~OptionsPageInfo() {
+}
+
+// static
+const GURL& OptionsPageInfo::GetOptionsPage(const Extension* extension) {
+ OptionsPageInfo* info = GetOptionsPageInfo(extension);
+ return info ? info->options_page_ : GURL::EmptyGURL();
+}
+
+// static
+bool OptionsPageInfo::HasOptionsPage(const Extension* extension) {
+ return !OptionsPageInfo::GetOptionsPage(extension).is_empty();
+}
+
+// static
+bool OptionsPageInfo::ShouldUseChromeStyle(const Extension* extension) {
+ OptionsPageInfo* info = GetOptionsPageInfo(extension);
+ return info && info->chrome_styles_;
+}
+
+// static
+bool OptionsPageInfo::ShouldOpenInTab(const Extension* extension) {
+ OptionsPageInfo* info = GetOptionsPageInfo(extension);
+ return info && info->open_in_tab_;
+}
+
+scoped_ptr<OptionsPageInfo> OptionsPageInfo::Create(
+ Extension* extension,
+ const base::Value* options_ui_value,
+ const std::string& options_page_string,
+ std::vector<InstallWarning>* install_warnings,
+ base::string16* error) {
+ GURL options_page;
+ // Chrome styling is always opt-in.
+ bool chrome_style = false;
+ // Extensions can opt in or out to opening in a tab, and users can choose via
+ // the --embedded-extension-options flag which should be the default.
+ bool open_in_tab = !FeatureSwitch::embedded_extension_options()->IsEnabled();
+
+ // Parse the options_ui object.
+ if (options_ui_value) {
+ base::string16 options_ui_error;
+
+ scoped_ptr<OptionsUI> options_ui =
+ OptionsUI::FromValue(*options_ui_value, &options_ui_error);
+ if (!options_ui_error.empty()) {
+ // OptionsUI::FromValue populates |error| both when there are
+ // errors (in which case |options_ui| will be NULL) and warnings
+ // (in which case |options_ui| will be valid). Either way, show it
+ // as an install warning.
+ install_warnings->push_back(
+ InstallWarning(base::UTF16ToASCII(options_ui_error)));
+ }
+
+ if (options_ui) {
+ base::string16 options_parse_error;
+ if (!ParseOptionsUrl(extension,
+ options_ui->page,
+ keys::kOptionsUI,
+ &options_parse_error,
+ &options_page)) {
+ install_warnings->push_back(
+ InstallWarning(base::UTF16ToASCII(options_parse_error)));
+ }
+ if (options_ui->chrome_style.get())
+ chrome_style = *options_ui->chrome_style;
+ open_in_tab = false;
+ if (options_ui->open_in_tab.get())
+ open_in_tab = *options_ui->open_in_tab;
+ }
+ }
+
+ // Parse the legacy options_page entry if there was no entry for
+ // options_ui.page.
+ if (!options_page_string.empty() && !options_page.is_valid()) {
+ if (!ParseOptionsUrl(extension,
+ options_page_string,
+ keys::kOptionsPage,
+ error,
+ &options_page)) {
+ return scoped_ptr<OptionsPageInfo>();
+ }
+ }
+
+ return make_scoped_ptr(
+ new OptionsPageInfo(options_page, chrome_style, open_in_tab));
+}
+
+OptionsPageManifestHandler::OptionsPageManifestHandler() {
+}
+
+OptionsPageManifestHandler::~OptionsPageManifestHandler() {
+}
+
+bool OptionsPageManifestHandler::Parse(Extension* extension,
+ base::string16* error) {
+ std::vector<InstallWarning> install_warnings;
+ const Manifest* manifest = extension->manifest();
+
+ std::string options_page_string;
+ if (manifest->HasPath(keys::kOptionsPage) &&
+ !manifest->GetString(keys::kOptionsPage, &options_page_string)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidOptionsPage,
+ keys::kOptionsPage);
+ return false;
+ }
+
+ const base::Value* options_ui_value = NULL;
+ ignore_result(manifest->Get(keys::kOptionsUI, &options_ui_value));
+
+ scoped_ptr<OptionsPageInfo> info =
+ OptionsPageInfo::Create(extension,
+ options_ui_value,
+ options_page_string,
+ &install_warnings,
+ error);
+ if (!info)
+ return false;
+
+ extension->AddInstallWarnings(install_warnings);
+ extension->SetManifestData(keys::kOptionsUI, info.release());
+ return true;
+}
+
+bool OptionsPageManifestHandler::Validate(
+ const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const {
+ // Validate path to the options page. Don't check the URL for hosted apps,
+ // because they are expected to refer to an external URL.
+ if (!OptionsPageInfo::HasOptionsPage(extension) || extension->is_hosted_app())
+ return true;
+
+ base::FilePath options_path = file_util::ExtensionURLToRelativeFilePath(
+ OptionsPageInfo::GetOptionsPage(extension));
+ base::FilePath path = extension->GetResource(options_path).GetFilePath();
+ if (path.empty() || !base::PathExists(path)) {
+ *error = l10n_util::GetStringFUTF8(IDS_EXTENSION_LOAD_OPTIONS_PAGE_FAILED,
+ options_path.LossyDisplayName());
+ return false;
+ }
+ return true;
+}
+
+const std::vector<std::string> OptionsPageManifestHandler::Keys() const {
+ static const char* keys[] = {keys::kOptionsPage, keys::kOptionsUI};
+ return std::vector<std::string>(keys, keys + arraysize(keys));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/options_page_info.h b/chromium/extensions/common/manifest_handlers/options_page_info.h
new file mode 100644
index 00000000000..fcde3e22e81
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/options_page_info.h
@@ -0,0 +1,88 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_OPTIONS_PAGE_INFO_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_OPTIONS_PAGE_INFO_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/values.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+#include "url/gurl.h"
+
+namespace base {
+class Value;
+}
+
+namespace extensions {
+
+// A class to provide options page configuration settings from the manifest.
+class OptionsPageInfo : public Extension::ManifestData {
+ public:
+ OptionsPageInfo(const GURL& options_page,
+ bool chrome_styles,
+ bool open_in_tab);
+ ~OptionsPageInfo() override;
+
+ // Returns the URL to the given extension's options page. This method supports
+ // both the "options_ui.page" field and the legacy "options_page" field. If
+ // both are present, it will return the value of "options_ui.page".
+ static const GURL& GetOptionsPage(const Extension* extension);
+
+ // Returns true if the given extension has an options page. An extension has
+ // an options page if one or both of "options_ui.page" and "options_page"
+ // specify a valid options page.
+ static bool HasOptionsPage(const Extension* extension);
+
+ // Returns whether the Chrome user agent stylesheet should be applied to the
+ // given extension's options page.
+ static bool ShouldUseChromeStyle(const Extension* extension);
+
+ // Returns whether the given extension's options page should be opened in a
+ // new tab instead of an embedded popup.
+ static bool ShouldOpenInTab(const Extension* extension);
+
+ static scoped_ptr<OptionsPageInfo> Create(
+ Extension* extension,
+ const base::Value* options_ui_value,
+ const std::string& options_page_string,
+ std::vector<InstallWarning>* install_warnings,
+ base::string16* error);
+
+ private:
+ // The URL to the options page of this extension. We only store one options
+ // URL, either options_page or options_ui.page. options_ui.page is preferred
+ // if both are present.
+ GURL options_page_;
+
+ bool chrome_styles_;
+
+ bool open_in_tab_;
+
+ DISALLOW_COPY_AND_ASSIGN(OptionsPageInfo);
+};
+
+// Parses the "options_ui" manifest key and the legacy "options_page" key.
+class OptionsPageManifestHandler : public ManifestHandler {
+ public:
+ OptionsPageManifestHandler();
+ ~OptionsPageManifestHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+ bool Validate(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(OptionsPageManifestHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_OPTIONS_PAGE_INFO_H_
diff --git a/chromium/extensions/common/manifest_handlers/permissions_parser.cc b/chromium/extensions/common/manifest_handlers/permissions_parser.cc
new file mode 100644
index 00000000000..2afdf722fdc
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/permissions_parser.cc
@@ -0,0 +1,341 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/permissions_parser.h"
+
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "content/public/common/url_constants.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/features/feature_provider.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handler.h"
+#include "extensions/common/permissions/api_permission_set.h"
+#include "extensions/common/permissions/permission_set.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/switches.h"
+#include "extensions/common/url_pattern_set.h"
+#include "url/url_constants.h"
+
+namespace extensions {
+
+namespace {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+struct ManifestPermissions : public Extension::ManifestData {
+ ManifestPermissions(scoped_ptr<const PermissionSet> permissions);
+ ~ManifestPermissions() override;
+
+ scoped_ptr<const PermissionSet> permissions;
+};
+
+ManifestPermissions::ManifestPermissions(
+ scoped_ptr<const PermissionSet> permissions)
+ : permissions(std::move(permissions)) {}
+
+ManifestPermissions::~ManifestPermissions() {
+}
+
+// Checks whether the host |pattern| is allowed for the given |extension|,
+// given API permissions |permissions|.
+bool CanSpecifyHostPermission(const Extension* extension,
+ const URLPattern& pattern,
+ const APIPermissionSet& permissions) {
+ if (!pattern.match_all_urls() &&
+ pattern.MatchesScheme(content::kChromeUIScheme)) {
+ URLPatternSet chrome_scheme_hosts =
+ ExtensionsClient::Get()->GetPermittedChromeSchemeHosts(extension,
+ permissions);
+ if (chrome_scheme_hosts.ContainsPattern(pattern))
+ return true;
+
+ // Component extensions can have access to all of chrome://*.
+ if (PermissionsData::CanExecuteScriptEverywhere(extension))
+ return true;
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kExtensionsOnChromeURLs)) {
+ return true;
+ }
+
+ // TODO(aboxhall): return from_webstore() when webstore handles blocking
+ // extensions which request chrome:// urls
+ return false;
+ }
+
+ // Otherwise, the valid schemes were handled by URLPattern.
+ return true;
+}
+
+// Parses the host and api permissions from the specified permission |key|
+// from |extension|'s manifest.
+bool ParseHelper(Extension* extension,
+ const char* key,
+ APIPermissionSet* api_permissions,
+ URLPatternSet* host_permissions,
+ base::string16* error) {
+ if (!extension->manifest()->HasKey(key))
+ return true;
+
+ const base::ListValue* permissions = NULL;
+ if (!extension->manifest()->GetList(key, &permissions)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidPermissions,
+ std::string());
+ return false;
+ }
+
+ // NOTE: We need to get the APIPermission before we check if features
+ // associated with them are available because the feature system does not
+ // know about aliases.
+
+ std::vector<std::string> host_data;
+ if (!APIPermissionSet::ParseFromJSON(
+ permissions,
+ APIPermissionSet::kDisallowInternalPermissions,
+ api_permissions,
+ error,
+ &host_data)) {
+ return false;
+ }
+
+ // Verify feature availability of permissions.
+ std::vector<APIPermission::ID> to_remove;
+ const FeatureProvider* permission_features =
+ FeatureProvider::GetPermissionFeatures();
+ for (APIPermissionSet::const_iterator iter = api_permissions->begin();
+ iter != api_permissions->end();
+ ++iter) {
+ Feature* feature = permission_features->GetFeature(iter->name());
+
+ // The feature should exist since we just got an APIPermission for it. The
+ // two systems should be updated together whenever a permission is added.
+ DCHECK(feature) << "Could not find feature for " << iter->name();
+ // http://crbug.com/176381
+ if (!feature) {
+ to_remove.push_back(iter->id());
+ continue;
+ }
+
+ // Sneaky check for "experimental", which we always allow for extensions
+ // installed from the Webstore. This way we can whitelist extensions to
+ // have access to experimental in just the store, and not have to push a
+ // new version of the client. Otherwise, experimental goes through the
+ // usual features check.
+ if (iter->id() == APIPermission::kExperimental &&
+ extension->from_webstore()) {
+ continue;
+ }
+
+ Feature::Availability availability =
+ feature->IsAvailableToExtension(extension);
+ if (!availability.is_available()) {
+ // Don't fail, but warn the developer that the manifest contains
+ // unrecognized permissions. This may happen legitimately if the
+ // extensions requests platform- or channel-specific permissions.
+ extension->AddInstallWarning(
+ InstallWarning(availability.message(), feature->name()));
+ to_remove.push_back(iter->id());
+ continue;
+ }
+ }
+
+ // Remove permissions that are not available to this extension.
+ for (std::vector<APIPermission::ID>::const_iterator iter = to_remove.begin();
+ iter != to_remove.end();
+ ++iter) {
+ api_permissions->erase(*iter);
+ }
+
+ // Parse host pattern permissions.
+ const int kAllowedSchemes =
+ PermissionsData::CanExecuteScriptEverywhere(extension)
+ ? URLPattern::SCHEME_ALL
+ : Extension::kValidHostPermissionSchemes;
+
+ for (std::vector<std::string>::const_iterator iter = host_data.begin();
+ iter != host_data.end();
+ ++iter) {
+ const std::string& permission_str = *iter;
+
+ // Check if it's a host pattern permission.
+ URLPattern pattern = URLPattern(kAllowedSchemes);
+ URLPattern::ParseResult parse_result = pattern.Parse(permission_str);
+ if (parse_result == URLPattern::PARSE_SUCCESS) {
+ // The path component is not used for host permissions, so we force it
+ // to match all paths.
+ pattern.SetPath("/*");
+ int valid_schemes = pattern.valid_schemes();
+ if (pattern.MatchesScheme(url::kFileScheme) &&
+ !PermissionsData::CanExecuteScriptEverywhere(extension)) {
+ extension->set_wants_file_access(true);
+ if (!(extension->creation_flags() & Extension::ALLOW_FILE_ACCESS))
+ valid_schemes &= ~URLPattern::SCHEME_FILE;
+ }
+
+ if (pattern.scheme() != content::kChromeUIScheme &&
+ !PermissionsData::CanExecuteScriptEverywhere(extension)) {
+ // Keep chrome:// in allowed schemes only if it's explicitly requested
+ // or CanExecuteScriptEverywhere is true. If the
+ // extensions_on_chrome_urls flag is not set, CanSpecifyHostPermission
+ // will fail, so don't check the flag here.
+ valid_schemes &= ~URLPattern::SCHEME_CHROMEUI;
+ }
+ pattern.SetValidSchemes(valid_schemes);
+
+ if (!CanSpecifyHostPermission(extension, pattern, *api_permissions)) {
+ // TODO(aboxhall): make a warning (see pattern.match_all_urls() block
+ // below).
+ extension->AddInstallWarning(InstallWarning(
+ ErrorUtils::FormatErrorMessage(errors::kInvalidPermissionScheme,
+ permission_str),
+ key,
+ permission_str));
+ continue;
+ }
+
+ host_permissions->AddPattern(pattern);
+ // We need to make sure all_urls matches chrome://favicon and (maybe)
+ // chrome://thumbnail, so add them back in to host_permissions separately.
+ if (pattern.match_all_urls()) {
+ host_permissions->AddPatterns(
+ ExtensionsClient::Get()->GetPermittedChromeSchemeHosts(
+ extension, *api_permissions));
+ }
+ continue;
+ }
+
+ // It's probably an unknown API permission. Do not throw an error so
+ // extensions can retain backwards compatability (http://crbug.com/42742).
+ extension->AddInstallWarning(InstallWarning(
+ ErrorUtils::FormatErrorMessage(
+ manifest_errors::kPermissionUnknownOrMalformed, permission_str),
+ key,
+ permission_str));
+ }
+
+ return true;
+}
+
+} // namespace
+
+struct PermissionsParser::InitialPermissions {
+ APIPermissionSet api_permissions;
+ ManifestPermissionSet manifest_permissions;
+ URLPatternSet host_permissions;
+ URLPatternSet scriptable_hosts;
+};
+
+PermissionsParser::PermissionsParser() {
+}
+
+PermissionsParser::~PermissionsParser() {
+}
+
+bool PermissionsParser::Parse(Extension* extension, base::string16* error) {
+ initial_required_permissions_.reset(new InitialPermissions);
+ if (!ParseHelper(extension,
+ keys::kPermissions,
+ &initial_required_permissions_->api_permissions,
+ &initial_required_permissions_->host_permissions,
+ error)) {
+ return false;
+ }
+
+ initial_optional_permissions_.reset(new InitialPermissions);
+ if (!ParseHelper(extension,
+ keys::kOptionalPermissions,
+ &initial_optional_permissions_->api_permissions,
+ &initial_optional_permissions_->host_permissions,
+ error)) {
+ return false;
+ }
+
+ return true;
+}
+
+void PermissionsParser::Finalize(Extension* extension) {
+ ManifestHandler::AddExtensionInitialRequiredPermissions(
+ extension, &initial_required_permissions_->manifest_permissions);
+
+ scoped_ptr<const PermissionSet> required_permissions(
+ new PermissionSet(initial_required_permissions_->api_permissions,
+ initial_required_permissions_->manifest_permissions,
+ initial_required_permissions_->host_permissions,
+ initial_required_permissions_->scriptable_hosts));
+ extension->SetManifestData(
+ keys::kPermissions,
+ new ManifestPermissions(std::move(required_permissions)));
+
+ scoped_ptr<const PermissionSet> optional_permissions(new PermissionSet(
+ initial_optional_permissions_->api_permissions,
+ initial_optional_permissions_->manifest_permissions,
+ initial_optional_permissions_->host_permissions, URLPatternSet()));
+ extension->SetManifestData(
+ keys::kOptionalPermissions,
+ new ManifestPermissions(std::move(optional_permissions)));
+}
+
+// static
+void PermissionsParser::AddAPIPermission(Extension* extension,
+ APIPermission::ID permission) {
+ DCHECK(extension->permissions_parser());
+ extension->permissions_parser()
+ ->initial_required_permissions_->api_permissions.insert(permission);
+}
+
+// static
+void PermissionsParser::AddAPIPermission(Extension* extension,
+ APIPermission* permission) {
+ DCHECK(extension->permissions_parser());
+ extension->permissions_parser()
+ ->initial_required_permissions_->api_permissions.insert(permission);
+}
+
+// static
+bool PermissionsParser::HasAPIPermission(const Extension* extension,
+ APIPermission::ID permission) {
+ DCHECK(extension->permissions_parser());
+ return extension->permissions_parser()
+ ->initial_required_permissions_->api_permissions.count(
+ permission) > 0;
+}
+
+// static
+void PermissionsParser::SetScriptableHosts(
+ Extension* extension,
+ const URLPatternSet& scriptable_hosts) {
+ DCHECK(extension->permissions_parser());
+ extension->permissions_parser()
+ ->initial_required_permissions_->scriptable_hosts = scriptable_hosts;
+}
+
+// static
+const PermissionSet& PermissionsParser::GetRequiredPermissions(
+ const Extension* extension) {
+ DCHECK(extension->GetManifestData(keys::kPermissions));
+ return *static_cast<const ManifestPermissions*>(
+ extension->GetManifestData(keys::kPermissions))
+ ->permissions;
+}
+
+// static
+const PermissionSet& PermissionsParser::GetOptionalPermissions(
+ const Extension* extension) {
+ DCHECK(extension->GetManifestData(keys::kOptionalPermissions));
+ return *static_cast<const ManifestPermissions*>(
+ extension->GetManifestData(keys::kOptionalPermissions))
+ ->permissions;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/permissions_parser.h b/chromium/extensions/common/manifest_handlers/permissions_parser.h
new file mode 100644
index 00000000000..2844d2ae875
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/permissions_parser.h
@@ -0,0 +1,63 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_PERMISSIONS_PARSER_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_PERMISSIONS_PARSER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/permission_set.h"
+
+namespace extensions {
+
+class Extension;
+class URLPatternSet;
+
+// The class for parsing the kPermissions and kOptionalPermissions keys in the
+// manifest. Because permissions are slightly different than other keys (they
+// are used in many different handlers and need to be the first and last key
+// touched), this is not an actual ManifestHandler (hence the difference in
+// name).
+class PermissionsParser {
+ public:
+ PermissionsParser();
+ ~PermissionsParser();
+
+ // Parse the manifest-specified permissions.
+ bool Parse(Extension* extension, base::string16* error);
+
+ // Finalize the permissions, setting the related manifest data on the
+ // extension.
+ void Finalize(Extension* extension);
+
+ // Modify the manifest permissions. These methods should only be used
+ // during initialization and will DCHECK() for safety.
+ static void AddAPIPermission(Extension* extension,
+ APIPermission::ID permission);
+ static void AddAPIPermission(Extension* extension, APIPermission* permission);
+ static bool HasAPIPermission(const Extension* extension,
+ APIPermission::ID permission);
+ static void SetScriptableHosts(Extension* extension,
+ const URLPatternSet& scriptable_hosts);
+
+ // Return the extension's manifest-specified permissions. In no cases should
+ // these permissions be used to determine if an action is allowed. Instead,
+ // use PermissionsData.
+ static const PermissionSet& GetRequiredPermissions(
+ const Extension* extension);
+ static const PermissionSet& GetOptionalPermissions(
+ const Extension* extension);
+
+ private:
+ struct InitialPermissions;
+
+ // The initial permissions for the extension, which can still be modified.
+ scoped_ptr<InitialPermissions> initial_required_permissions_;
+ scoped_ptr<InitialPermissions> initial_optional_permissions_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_PERMISSIONS_PARSER_H_
diff --git a/chromium/extensions/common/manifest_handlers/requirements_info.cc b/chromium/extensions/common/manifest_handlers/requirements_info.cc
new file mode 100644
index 00000000000..9a2a22b92aa
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/requirements_info.cc
@@ -0,0 +1,159 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/requirements_info.h"
+
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+RequirementsInfo::RequirementsInfo(const Manifest* manifest)
+ : webgl(false),
+ npapi(false),
+ window_shape(false) {
+ // Before parsing requirements from the manifest, automatically default the
+ // NPAPI plugin requirement based on whether it includes NPAPI plugins.
+ const base::ListValue* list_value = NULL;
+ npapi = manifest->GetList(keys::kPlugins, &list_value) &&
+ !list_value->empty();
+}
+
+RequirementsInfo::~RequirementsInfo() {
+}
+
+// static
+const RequirementsInfo& RequirementsInfo::GetRequirements(
+ const Extension* extension) {
+ RequirementsInfo* info = static_cast<RequirementsInfo*>(
+ extension->GetManifestData(keys::kRequirements));
+
+ // We should be guaranteed to have requirements, since they are parsed for all
+ // extension types.
+ CHECK(info);
+ return *info;
+}
+
+RequirementsHandler::RequirementsHandler() {
+}
+
+RequirementsHandler::~RequirementsHandler() {
+}
+
+const std::vector<std::string> RequirementsHandler::PrerequisiteKeys() const {
+ return SingleKey(keys::kPlugins);
+}
+
+const std::vector<std::string> RequirementsHandler::Keys() const {
+ return SingleKey(keys::kRequirements);
+}
+
+bool RequirementsHandler::AlwaysParseForType(Manifest::Type type) const {
+ return true;
+}
+
+bool RequirementsHandler::Parse(Extension* extension, base::string16* error) {
+ scoped_ptr<RequirementsInfo> requirements(
+ new RequirementsInfo(extension->manifest()));
+
+ if (!extension->manifest()->HasKey(keys::kRequirements)) {
+ extension->SetManifestData(keys::kRequirements, requirements.release());
+ return true;
+ }
+
+ const base::DictionaryValue* requirements_value = NULL;
+ if (!extension->manifest()->GetDictionary(keys::kRequirements,
+ &requirements_value)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidRequirements);
+ return false;
+ }
+
+ for (base::DictionaryValue::Iterator iter(*requirements_value);
+ !iter.IsAtEnd();
+ iter.Advance()) {
+ const base::DictionaryValue* requirement_value;
+ if (!iter.value().GetAsDictionary(&requirement_value)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidRequirement, iter.key());
+ return false;
+ }
+
+ if (iter.key() == "plugins") {
+ for (base::DictionaryValue::Iterator plugin_iter(*requirement_value);
+ !plugin_iter.IsAtEnd(); plugin_iter.Advance()) {
+ bool plugin_required = false;
+ if (!plugin_iter.value().GetAsBoolean(&plugin_required)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidRequirement, iter.key());
+ return false;
+ }
+ if (plugin_iter.key() == "npapi") {
+ requirements->npapi = plugin_required;
+ } else {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidRequirement, iter.key());
+ return false;
+ }
+ }
+ } else if (iter.key() == "3D") {
+ const base::ListValue* features = NULL;
+ if (!requirement_value->GetListWithoutPathExpansion("features",
+ &features) ||
+ !features) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidRequirement, iter.key());
+ return false;
+ }
+
+ for (base::ListValue::const_iterator feature_iter = features->begin();
+ feature_iter != features->end(); ++feature_iter) {
+ std::string feature;
+ if ((*feature_iter)->GetAsString(&feature)) {
+ if (feature == "webgl") {
+ requirements->webgl = true;
+ } else if (feature == "css3d") {
+ // css3d is always available, so no check is needed, but no error is
+ // generated.
+ } else {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidRequirement, iter.key());
+ return false;
+ }
+ }
+ }
+ } else if (iter.key() == "window") {
+ for (base::DictionaryValue::Iterator feature_iter(*requirement_value);
+ !feature_iter.IsAtEnd(); feature_iter.Advance()) {
+ bool feature_required = false;
+ if (!feature_iter.value().GetAsBoolean(&feature_required)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidRequirement, iter.key());
+ return false;
+ }
+ if (feature_iter.key() == "shape") {
+ requirements->window_shape = feature_required;
+ } else {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidRequirement, iter.key());
+ return false;
+ }
+ }
+ } else {
+ *error = base::ASCIIToUTF16(errors::kInvalidRequirements);
+ return false;
+ }
+ }
+
+ extension->SetManifestData(keys::kRequirements, requirements.release());
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/requirements_info.h b/chromium/extensions/common/manifest_handlers/requirements_info.h
new file mode 100644
index 00000000000..9c87bca8cbf
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/requirements_info.h
@@ -0,0 +1,50 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_REQUIREMENTS_INFO_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_REQUIREMENTS_INFO_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+// Declared requirements for the extension.
+struct RequirementsInfo : public Extension::ManifestData {
+ explicit RequirementsInfo(const Manifest* manifest);
+ ~RequirementsInfo() override;
+
+ bool webgl;
+ bool npapi;
+ bool window_shape;
+
+ static const RequirementsInfo& GetRequirements(const Extension* extension);
+};
+
+// Parses the "requirements" manifest key.
+class RequirementsHandler : public ManifestHandler {
+ public:
+ RequirementsHandler();
+ ~RequirementsHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ bool AlwaysParseForType(Manifest::Type type) const override;
+
+ const std::vector<std::string> PrerequisiteKeys() const override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(RequirementsHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_REQUIREMENTS_INFO_H_
diff --git a/chromium/extensions/common/manifest_handlers/sandboxed_page_info.cc b/chromium/extensions/common/manifest_handlers/sandboxed_page_info.cc
new file mode 100644
index 00000000000..bc44851935b
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/sandboxed_page_info.cc
@@ -0,0 +1,124 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/sandboxed_page_info.h"
+
+#include <stddef.h>
+
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/csp_validator.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/url_pattern.h"
+
+namespace extensions {
+
+namespace {
+
+namespace keys = extensions::manifest_keys;
+namespace errors = manifest_errors;
+
+const char kDefaultSandboxedPageContentSecurityPolicy[] =
+ "sandbox allow-scripts allow-forms allow-popups allow-modals";
+
+static base::LazyInstance<SandboxedPageInfo> g_empty_sandboxed_info =
+ LAZY_INSTANCE_INITIALIZER;
+
+const SandboxedPageInfo& GetSandboxedPageInfo(const Extension* extension) {
+ SandboxedPageInfo* info = static_cast<SandboxedPageInfo*>(
+ extension->GetManifestData(keys::kSandboxedPages));
+ return info ? *info : g_empty_sandboxed_info.Get();
+}
+
+} // namespace
+
+SandboxedPageInfo::SandboxedPageInfo() {
+}
+
+SandboxedPageInfo::~SandboxedPageInfo() {
+}
+
+const std::string& SandboxedPageInfo::GetContentSecurityPolicy(
+ const Extension* extension) {
+ return GetSandboxedPageInfo(extension).content_security_policy;
+}
+
+const URLPatternSet& SandboxedPageInfo::GetPages(const Extension* extension) {
+ return GetSandboxedPageInfo(extension).pages;
+}
+
+bool SandboxedPageInfo::IsSandboxedPage(const Extension* extension,
+ const std::string& relative_path) {
+ return extension->ResourceMatches(GetPages(extension), relative_path);
+}
+
+SandboxedPageHandler::SandboxedPageHandler() {
+}
+
+SandboxedPageHandler::~SandboxedPageHandler() {
+}
+
+bool SandboxedPageHandler::Parse(Extension* extension, base::string16* error) {
+ scoped_ptr<SandboxedPageInfo> sandboxed_info(new SandboxedPageInfo);
+
+ const base::ListValue* list_value = NULL;
+ if (!extension->manifest()->GetList(keys::kSandboxedPages, &list_value)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidSandboxedPagesList);
+ return false;
+ }
+
+ for (size_t i = 0; i < list_value->GetSize(); ++i) {
+ std::string relative_path;
+ if (!list_value->GetString(i, &relative_path)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidSandboxedPage, base::SizeTToString(i));
+ return false;
+ }
+ URLPattern pattern(URLPattern::SCHEME_EXTENSION);
+ if (pattern.Parse(extension->url().spec()) != URLPattern::PARSE_SUCCESS) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidURLPatternError, extension->url().spec());
+ return false;
+ }
+ while (relative_path[0] == '/')
+ relative_path = relative_path.substr(1, relative_path.length() - 1);
+ pattern.SetPath(pattern.path() + relative_path);
+ sandboxed_info->pages.AddPattern(pattern);
+ }
+
+ if (extension->manifest()->HasPath(keys::kSandboxedPagesCSP)) {
+ if (!extension->manifest()->GetString(
+ keys::kSandboxedPagesCSP,
+ &sandboxed_info->content_security_policy)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidSandboxedPagesCSP);
+ return false;
+ }
+
+ if (!csp_validator::ContentSecurityPolicyIsLegal(
+ sandboxed_info->content_security_policy) ||
+ !csp_validator::ContentSecurityPolicyIsSandboxed(
+ sandboxed_info->content_security_policy, extension->GetType())) {
+ *error = base::ASCIIToUTF16(errors::kInvalidSandboxedPagesCSP);
+ return false;
+ }
+ } else {
+ sandboxed_info->content_security_policy =
+ kDefaultSandboxedPageContentSecurityPolicy;
+ CHECK(csp_validator::ContentSecurityPolicyIsSandboxed(
+ sandboxed_info->content_security_policy, extension->GetType()));
+ }
+
+ extension->SetManifestData(keys::kSandboxedPages, sandboxed_info.release());
+ return true;
+}
+
+const std::vector<std::string> SandboxedPageHandler::Keys() const {
+ return SingleKey(keys::kSandboxedPages);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/sandboxed_page_info.h b/chromium/extensions/common/manifest_handlers/sandboxed_page_info.h
new file mode 100644
index 00000000000..3dd0ae7242c
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/sandboxed_page_info.h
@@ -0,0 +1,55 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_SANDBOXED_PAGE_INFO_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_SANDBOXED_PAGE_INFO_H_
+
+#include <string>
+
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+#include "extensions/common/url_pattern_set.h"
+
+namespace extensions {
+
+struct SandboxedPageInfo : public Extension::ManifestData {
+ public:
+ SandboxedPageInfo();
+ ~SandboxedPageInfo() override;
+
+ // Returns the extension's Content Security Policy for the sandboxed pages.
+ static const std::string& GetContentSecurityPolicy(
+ const Extension* extension);
+
+ // Returns the extension's sandboxed pages.
+ static const URLPatternSet& GetPages(const Extension* extension);
+
+ // Returns true if the specified page is sandboxed.
+ static bool IsSandboxedPage(const Extension* extension,
+ const std::string& relative_path);
+
+ // Optional list of extension pages that are sandboxed (served from a unique
+ // origin with a different Content Security Policy).
+ URLPatternSet pages;
+
+ // Content Security Policy that should be used to enforce the sandbox used
+ // by sandboxed pages (guaranteed to have the "sandbox" directive without the
+ // "allow-same-origin" token).
+ std::string content_security_policy;
+};
+
+class SandboxedPageHandler : public ManifestHandler {
+ public:
+ SandboxedPageHandler();
+ ~SandboxedPageHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_SANDBOXED_PAGE_INFO_H_
diff --git a/chromium/extensions/common/manifest_handlers/shared_module_info.cc b/chromium/extensions/common/manifest_handlers/shared_module_info.cc
new file mode 100644
index 00000000000..87c8e879d5e
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/shared_module_info.cc
@@ -0,0 +1,239 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/shared_module_info.h"
+
+#include <stddef.h>
+
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/version.h"
+#include "components/crx_file/id_util.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/permissions/permission_set.h"
+#include "extensions/common/permissions/permissions_data.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace values = manifest_values;
+namespace errors = manifest_errors;
+
+namespace {
+
+const char kSharedModule[] = "shared_module";
+
+static base::LazyInstance<SharedModuleInfo> g_empty_shared_module_info =
+ LAZY_INSTANCE_INITIALIZER;
+
+const SharedModuleInfo& GetSharedModuleInfo(const Extension* extension) {
+ SharedModuleInfo* info = static_cast<SharedModuleInfo*>(
+ extension->GetManifestData(kSharedModule));
+ if (!info)
+ return g_empty_shared_module_info.Get();
+ return *info;
+}
+
+} // namespace
+
+SharedModuleInfo::SharedModuleInfo() {
+}
+
+SharedModuleInfo::~SharedModuleInfo() {
+}
+
+// static
+void SharedModuleInfo::ParseImportedPath(const std::string& path,
+ std::string* import_id,
+ std::string* import_relative_path) {
+ std::vector<std::string> tokens = base::SplitString(
+ path, "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+ if (tokens.size() > 2 && tokens[0] == kModulesDir &&
+ crx_file::id_util::IdIsValid(tokens[1])) {
+ *import_id = tokens[1];
+ *import_relative_path = tokens[2];
+ for (size_t i = 3; i < tokens.size(); ++i)
+ *import_relative_path += "/" + tokens[i];
+ }
+}
+
+// static
+bool SharedModuleInfo::IsImportedPath(const std::string& path) {
+ std::vector<std::string> tokens = base::SplitString(
+ path, "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+ if (tokens.size() > 2 && tokens[0] == kModulesDir &&
+ crx_file::id_util::IdIsValid(tokens[1])) {
+ return true;
+ }
+ return false;
+}
+
+// static
+bool SharedModuleInfo::IsSharedModule(const Extension* extension) {
+ CHECK(extension);
+ return extension->manifest()->is_shared_module();
+}
+
+// static
+bool SharedModuleInfo::IsExportAllowedByWhitelist(const Extension* extension,
+ const std::string& other_id) {
+ // Sanity check. In case the caller did not check |extension| to make sure it
+ // is a shared module, we do not want it to appear that the extension with
+ // |other_id| importing |extension| is valid.
+ if (!SharedModuleInfo::IsSharedModule(extension))
+ return false;
+ const SharedModuleInfo& info = GetSharedModuleInfo(extension);
+ if (info.export_whitelist_.empty())
+ return true;
+ if (info.export_whitelist_.find(other_id) != info.export_whitelist_.end())
+ return true;
+ return false;
+}
+
+// static
+bool SharedModuleInfo::ImportsExtensionById(const Extension* extension,
+ const std::string& other_id) {
+ const SharedModuleInfo& info = GetSharedModuleInfo(extension);
+ for (size_t i = 0; i < info.imports_.size(); i++) {
+ if (info.imports_[i].extension_id == other_id)
+ return true;
+ }
+ return false;
+}
+
+// static
+bool SharedModuleInfo::ImportsModules(const Extension* extension) {
+ return GetSharedModuleInfo(extension).imports_.size() > 0;
+}
+
+// static
+const std::vector<SharedModuleInfo::ImportInfo>& SharedModuleInfo::GetImports(
+ const Extension* extension) {
+ return GetSharedModuleInfo(extension).imports_;
+}
+
+bool SharedModuleInfo::Parse(const Extension* extension,
+ base::string16* error) {
+ bool has_import = extension->manifest()->HasKey(keys::kImport);
+ bool has_export = extension->manifest()->HasKey(keys::kExport);
+ if (!has_import && !has_export)
+ return true;
+
+ if (has_import && has_export) {
+ *error = base::ASCIIToUTF16(errors::kInvalidImportAndExport);
+ return false;
+ }
+
+ if (has_export) {
+ const base::DictionaryValue* export_value = NULL;
+ if (!extension->manifest()->GetDictionary(keys::kExport, &export_value)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidExport);
+ return false;
+ }
+ if (export_value->HasKey(keys::kWhitelist)) {
+ const base::ListValue* whitelist = NULL;
+ if (!export_value->GetList(keys::kWhitelist, &whitelist)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidExportWhitelist);
+ return false;
+ }
+ for (size_t i = 0; i < whitelist->GetSize(); ++i) {
+ std::string extension_id;
+ if (!whitelist->GetString(i, &extension_id) ||
+ !crx_file::id_util::IdIsValid(extension_id)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidExportWhitelistString, base::SizeTToString(i));
+ return false;
+ }
+ export_whitelist_.insert(extension_id);
+ }
+ }
+ }
+
+ if (has_import) {
+ const base::ListValue* import_list = NULL;
+ if (!extension->manifest()->GetList(keys::kImport, &import_list)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidImport);
+ return false;
+ }
+ for (size_t i = 0; i < import_list->GetSize(); ++i) {
+ const base::DictionaryValue* import_entry = NULL;
+ if (!import_list->GetDictionary(i, &import_entry)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidImport);
+ return false;
+ }
+ std::string extension_id;
+ imports_.push_back(ImportInfo());
+ if (!import_entry->GetString(keys::kId, &extension_id) ||
+ !crx_file::id_util::IdIsValid(extension_id)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidImportId,
+ base::SizeTToString(i));
+ return false;
+ }
+ imports_.back().extension_id = extension_id;
+ if (import_entry->HasKey(keys::kMinimumVersion)) {
+ std::string min_version;
+ if (!import_entry->GetString(keys::kMinimumVersion, &min_version)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidImportVersion, base::SizeTToString(i));
+ return false;
+ }
+ imports_.back().minimum_version = min_version;
+ Version v(min_version);
+ if (!v.IsValid()) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidImportVersion, base::SizeTToString(i));
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+
+SharedModuleHandler::SharedModuleHandler() {
+}
+
+SharedModuleHandler::~SharedModuleHandler() {
+}
+
+bool SharedModuleHandler::Parse(Extension* extension, base::string16* error) {
+ scoped_ptr<SharedModuleInfo> info(new SharedModuleInfo);
+ if (!info->Parse(extension, error))
+ return false;
+ extension->SetManifestData(kSharedModule, info.release());
+ return true;
+}
+
+bool SharedModuleHandler::Validate(
+ const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const {
+ // Extensions that export resources should not have any permissions of their
+ // own, instead they rely on the permissions of the extensions which import
+ // them.
+ if (SharedModuleInfo::IsSharedModule(extension) &&
+ !extension->permissions_data()->active_permissions().IsEmpty()) {
+ *error = errors::kInvalidExportPermissions;
+ return false;
+ }
+ return true;
+}
+
+const std::vector<std::string> SharedModuleHandler::Keys() const {
+ static const char* keys[] = {
+ keys::kExport,
+ keys::kImport
+ };
+ return std::vector<std::string>(keys, keys + arraysize(keys));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/shared_module_info.h b/chromium/extensions/common/manifest_handlers/shared_module_info.h
new file mode 100644
index 00000000000..14403cf4d15
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/shared_module_info.h
@@ -0,0 +1,74 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_SHARED_MODULE_INFO_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_SHARED_MODULE_INFO_H_
+
+#include <string>
+#include <vector>
+
+#include "base/values.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+class SharedModuleInfo : public Extension::ManifestData {
+ public:
+ SharedModuleInfo();
+ ~SharedModuleInfo() override;
+
+ bool Parse(const Extension* extension, base::string16* error);
+
+ struct ImportInfo {
+ std::string extension_id;
+ std::string minimum_version;
+ };
+
+ // Utility functions.
+ static void ParseImportedPath(const std::string& path,
+ std::string* import_id,
+ std::string* import_relative_path);
+ static bool IsImportedPath(const std::string& path);
+
+ // Functions relating to exporting resources.
+ static bool IsSharedModule(const Extension* extension);
+ // Check against the shared module's whitelist to see if |other_id| can import
+ // its resources. If no whitelist is specified, all extensions can import this
+ // extension.
+ static bool IsExportAllowedByWhitelist(const Extension* extension,
+ const std::string& other_id);
+
+ // Functions relating to importing resources.
+ static bool ImportsExtensionById(const Extension* extension,
+ const std::string& other_id);
+ static bool ImportsModules(const Extension* extension);
+ static const std::vector<ImportInfo>& GetImports(const Extension* extension);
+
+ private:
+ // Optional list of extensions from which importing is allowed.
+ std::set<std::string> export_whitelist_;
+
+ // Optional list of module imports of other extensions.
+ std::vector<ImportInfo> imports_;
+};
+
+// Parses all import/export keys in the manifest.
+class SharedModuleHandler : public ManifestHandler {
+ public:
+ SharedModuleHandler();
+ ~SharedModuleHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+ bool Validate(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_SHARED_MODULE_INFO_H_
diff --git a/chromium/extensions/common/manifest_handlers/shared_module_manifest_unittest.cc b/chromium/extensions/common/manifest_handlers/shared_module_manifest_unittest.cc
new file mode 100644
index 00000000000..63ff88e3816
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/shared_module_manifest_unittest.cc
@@ -0,0 +1,124 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/macros.h"
+#include "base/version.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handlers/shared_module_info.h"
+#include "extensions/common/manifest_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char* kValidImportPath =
+ "_modules/abcdefghijklmnopabcdefghijklmnop/foo/bar.html";
+const char* kValidImportPathID = "abcdefghijklmnopabcdefghijklmnop";
+const char* kValidImportPathRelative = "foo/bar.html";
+const char* kInvalidImportPath = "_modules/abc/foo.html";
+const char* kImportId1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+const char* kImportId2 = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
+const char* kNoImport = "cccccccccccccccccccccccccccccccc";
+
+} // namespace
+
+namespace extensions {
+
+class SharedModuleManifestTest : public ManifestTest {
+};
+
+TEST_F(SharedModuleManifestTest, ExportsAll) {
+ ManifestData manifest("shared_module_export.json");
+
+ scoped_refptr<Extension> extension = LoadAndExpectSuccess(manifest);
+
+ EXPECT_TRUE(SharedModuleInfo::IsSharedModule(extension.get()))
+ << manifest.name();
+ EXPECT_FALSE(SharedModuleInfo::ImportsModules(extension.get()))
+ << manifest.name();
+
+ EXPECT_TRUE(SharedModuleInfo::IsExportAllowedByWhitelist(extension.get(),
+ kImportId1)) << manifest.name();
+ EXPECT_TRUE(SharedModuleInfo::IsExportAllowedByWhitelist(extension.get(),
+ kImportId2)) << manifest.name();
+ EXPECT_FALSE(SharedModuleInfo::IsExportAllowedByWhitelist(extension.get(),
+ kNoImport)) << manifest.name();
+}
+
+TEST_F(SharedModuleManifestTest, ExportWhitelistAll) {
+ ManifestData manifest("shared_module_export_no_whitelist.json");
+
+ scoped_refptr<Extension> extension = LoadAndExpectSuccess(manifest);
+
+ EXPECT_TRUE(SharedModuleInfo::IsExportAllowedByWhitelist(extension.get(),
+ kImportId1)) << manifest.name();
+ EXPECT_TRUE(SharedModuleInfo::IsExportAllowedByWhitelist(extension.get(),
+ kImportId2)) << manifest.name();
+ EXPECT_TRUE(SharedModuleInfo::IsExportAllowedByWhitelist(extension.get(),
+ kNoImport)) << manifest.name();
+}
+
+TEST_F(SharedModuleManifestTest, ExportParseErrors) {
+ Testcase testcases[] = {
+ Testcase("shared_module_export_and_import.json",
+ "Simultaneous 'import' and 'export' are not allowed."),
+ Testcase("shared_module_export_not_dict.json",
+ "Invalid value for 'export'."),
+ Testcase("shared_module_export_whitelist_item_not_id.json",
+ "Invalid value for 'export.whitelist[0]'."),
+ Testcase("shared_module_export_whitelist_item_not_string.json",
+ "Invalid value for 'export.whitelist[0]'."),
+ Testcase("shared_module_export_whitelist_not_list.json",
+ "Invalid value for 'export.whitelist'."),
+ };
+ RunTestcases(testcases, arraysize(testcases), EXPECT_TYPE_ERROR);
+}
+
+TEST_F(SharedModuleManifestTest, SharedModuleStaticFunctions) {
+ EXPECT_TRUE(SharedModuleInfo::IsImportedPath(kValidImportPath));
+ EXPECT_FALSE(SharedModuleInfo::IsImportedPath(kInvalidImportPath));
+
+ std::string id;
+ std::string relative;
+ SharedModuleInfo::ParseImportedPath(kValidImportPath, &id, &relative);
+ EXPECT_EQ(id, kValidImportPathID);
+ EXPECT_EQ(relative, kValidImportPathRelative);
+}
+
+TEST_F(SharedModuleManifestTest, Import) {
+ ManifestData manifest("shared_module_import.json");
+
+ scoped_refptr<Extension> extension = LoadAndExpectSuccess(manifest);
+
+ EXPECT_FALSE(SharedModuleInfo::IsSharedModule(extension.get()))
+ << manifest.name();
+ EXPECT_TRUE(SharedModuleInfo::ImportsModules(extension.get()))
+ << manifest.name();
+ const std::vector<SharedModuleInfo::ImportInfo>& imports =
+ SharedModuleInfo::GetImports(extension.get());
+ ASSERT_EQ(2U, imports.size());
+ EXPECT_EQ(imports[0].extension_id, kImportId1);
+ EXPECT_EQ(imports[0].minimum_version, "");
+ EXPECT_EQ(imports[1].extension_id, kImportId2);
+ EXPECT_TRUE(base::Version(imports[1].minimum_version).IsValid());
+ EXPECT_TRUE(
+ SharedModuleInfo::ImportsExtensionById(extension.get(), kImportId1));
+ EXPECT_TRUE(
+ SharedModuleInfo::ImportsExtensionById(extension.get(), kImportId2));
+ EXPECT_FALSE(
+ SharedModuleInfo::ImportsExtensionById(extension.get(), kNoImport));
+}
+
+TEST_F(SharedModuleManifestTest, ImportParseErrors) {
+ Testcase testcases[] = {
+ Testcase("shared_module_import_not_list.json",
+ "Invalid value for 'import'."),
+ Testcase("shared_module_import_invalid_id.json",
+ "Invalid value for 'import[0].id'."),
+ Testcase("shared_module_import_invalid_version.json",
+ "Invalid value for 'import[0].minimum_version'."),
+ };
+ RunTestcases(testcases, arraysize(testcases), EXPECT_TYPE_ERROR);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/web_accessible_resources_info.cc b/chromium/extensions/common/manifest_handlers/web_accessible_resources_info.cc
new file mode 100644
index 00000000000..2aa7b2b66c4
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/web_accessible_resources_info.cc
@@ -0,0 +1,101 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
+
+#include <stddef.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+namespace {
+
+const WebAccessibleResourcesInfo* GetResourcesInfo(const Extension* extension) {
+ return static_cast<WebAccessibleResourcesInfo*>(
+ extension->GetManifestData(keys::kWebAccessibleResources));
+}
+
+} // namespace
+
+WebAccessibleResourcesInfo::WebAccessibleResourcesInfo() {
+}
+
+WebAccessibleResourcesInfo::~WebAccessibleResourcesInfo() {
+}
+
+// static
+bool WebAccessibleResourcesInfo::IsResourceWebAccessible(
+ const Extension* extension,
+ const std::string& relative_path) {
+ // For old manifest versions which do not specify web_accessible_resources
+ // we always allow resource loads.
+ if (extension->manifest_version() < 2 &&
+ !WebAccessibleResourcesInfo::HasWebAccessibleResources(extension))
+ return true;
+
+ const WebAccessibleResourcesInfo* info = GetResourcesInfo(extension);
+ return info &&
+ extension->ResourceMatches(
+ info->web_accessible_resources_, relative_path);
+}
+
+// static
+bool WebAccessibleResourcesInfo::HasWebAccessibleResources(
+ const Extension* extension) {
+ const WebAccessibleResourcesInfo* info = GetResourcesInfo(extension);
+ return info && info->web_accessible_resources_.size() > 0;
+}
+
+WebAccessibleResourcesHandler::WebAccessibleResourcesHandler() {
+}
+
+WebAccessibleResourcesHandler::~WebAccessibleResourcesHandler() {
+}
+
+bool WebAccessibleResourcesHandler::Parse(Extension* extension,
+ base::string16* error) {
+ scoped_ptr<WebAccessibleResourcesInfo> info(new WebAccessibleResourcesInfo);
+ const base::ListValue* list_value = NULL;
+ if (!extension->manifest()->GetList(keys::kWebAccessibleResources,
+ &list_value)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidWebAccessibleResourcesList);
+ return false;
+ }
+ for (size_t i = 0; i < list_value->GetSize(); ++i) {
+ std::string relative_path;
+ if (!list_value->GetString(i, &relative_path)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidWebAccessibleResource, base::SizeTToString(i));
+ return false;
+ }
+ URLPattern pattern(URLPattern::SCHEME_EXTENSION);
+ if (pattern.Parse(extension->url().spec()) != URLPattern::PARSE_SUCCESS) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidURLPatternError, extension->url().spec());
+ return false;
+ }
+ while (relative_path[0] == '/')
+ relative_path = relative_path.substr(1, relative_path.length() - 1);
+ pattern.SetPath(pattern.path() + relative_path);
+ info->web_accessible_resources_.AddPattern(pattern);
+ }
+ extension->SetManifestData(keys::kWebAccessibleResources, info.release());
+ return true;
+}
+
+const std::vector<std::string> WebAccessibleResourcesHandler::Keys() const {
+ return SingleKey(keys::kWebAccessibleResources);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/web_accessible_resources_info.h b/chromium/extensions/common/manifest_handlers/web_accessible_resources_info.h
new file mode 100644
index 00000000000..9246ca1d6ea
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/web_accessible_resources_info.h
@@ -0,0 +1,51 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_WEB_ACCESSIBLE_RESOURCES_INFO_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_WEB_ACCESSIBLE_RESOURCES_INFO_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+// A structure to hold the web accessible extension resources
+// that may be specified in the manifest of an extension using
+// "web_accessible_resources" key.
+struct WebAccessibleResourcesInfo : public Extension::ManifestData {
+ // Define out of line constructor/destructor to please Clang.
+ WebAccessibleResourcesInfo();
+ ~WebAccessibleResourcesInfo() override;
+
+ // Returns true if the specified resource is web accessible.
+ static bool IsResourceWebAccessible(const Extension* extension,
+ const std::string& relative_path);
+
+ // Returns true when 'web_accessible_resources' are defined for the extension.
+ static bool HasWebAccessibleResources(const Extension* extension);
+
+ // Optional list of web accessible extension resources.
+ URLPatternSet web_accessible_resources_;
+};
+
+// Parses the "web_accessible_resources" manifest key.
+class WebAccessibleResourcesHandler : public ManifestHandler {
+ public:
+ WebAccessibleResourcesHandler();
+ ~WebAccessibleResourcesHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebAccessibleResourcesHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_WEB_ACCESSIBLE_RESOURCES_INFO_H_
diff --git a/chromium/extensions/common/manifest_handlers/webview_info.cc b/chromium/extensions/common/manifest_handlers/webview_info.cc
new file mode 100644
index 00000000000..6775c754dbc
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/webview_info.cc
@@ -0,0 +1,177 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/webview_info.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/pattern.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+namespace keys = extensions::manifest_keys;
+namespace errors = extensions::manifest_errors;
+
+// A PartitionItem represents a set of accessible resources given a partition
+// ID pattern.
+class PartitionItem {
+ public:
+ explicit PartitionItem(const std::string& partition_pattern)
+ : partition_pattern_(partition_pattern) {
+ }
+
+ virtual ~PartitionItem() {
+ }
+
+ bool Matches(const std::string& partition_id) const {
+ return base::MatchPattern(partition_id, partition_pattern_);
+ }
+
+ // Adds a pattern to the set. Returns true if a new pattern was inserted,
+ // false if the pattern was already in the set.
+ bool AddPattern(const URLPattern& pattern) {
+ return accessible_resources_.AddPattern(pattern);
+ }
+
+ const URLPatternSet& accessible_resources() const {
+ return accessible_resources_;
+ }
+ private:
+ // A pattern string that matches partition IDs.
+ const std::string partition_pattern_;
+ // A URL pattern set of resources accessible to the given
+ // |partition_pattern_|.
+ URLPatternSet accessible_resources_;
+};
+
+WebviewInfo::WebviewInfo(const std::string& extension_id)
+ : extension_id_(extension_id) {
+}
+
+WebviewInfo::~WebviewInfo() {
+}
+
+// static
+bool WebviewInfo::IsResourceWebviewAccessible(
+ const Extension* extension,
+ const std::string& partition_id,
+ const std::string& relative_path) {
+ if (!extension)
+ return false;
+
+ const WebviewInfo* webview_info = static_cast<const WebviewInfo*>(
+ extension->GetManifestData(keys::kWebviewAccessibleResources));
+ if (!webview_info)
+ return false;
+
+ for (const auto& item : webview_info->partition_items_) {
+ if (item->Matches(partition_id) &&
+ extension->ResourceMatches(item->accessible_resources(),
+ relative_path)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void WebviewInfo::AddPartitionItem(scoped_ptr<PartitionItem> item) {
+ partition_items_.push_back(std::move(item));
+}
+
+WebviewHandler::WebviewHandler() {
+}
+
+WebviewHandler::~WebviewHandler() {
+}
+
+bool WebviewHandler::Parse(Extension* extension, base::string16* error) {
+ scoped_ptr<WebviewInfo> info(new WebviewInfo(extension->id()));
+
+ const base::DictionaryValue* dict_value = NULL;
+ if (!extension->manifest()->GetDictionary(keys::kWebview,
+ &dict_value)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidWebview);
+ return false;
+ }
+
+ const base::ListValue* partition_list = NULL;
+ if (!dict_value->GetList(keys::kWebviewPartitions, &partition_list)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidWebviewPartitionsList);
+ return false;
+ }
+
+ // The partition list must have at least one entry.
+ if (partition_list->GetSize() == 0) {
+ *error = base::ASCIIToUTF16(errors::kInvalidWebviewPartitionsList);
+ return false;
+ }
+
+ for (size_t i = 0; i < partition_list->GetSize(); ++i) {
+ const base::DictionaryValue* partition = NULL;
+ if (!partition_list->GetDictionary(i, &partition)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidWebviewPartition, base::SizeTToString(i));
+ return false;
+ }
+
+ std::string partition_pattern;
+ if (!partition->GetString(keys::kWebviewName, &partition_pattern)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidWebviewPartitionName, base::SizeTToString(i));
+ return false;
+ }
+
+ const base::ListValue* url_list = NULL;
+ if (!partition->GetList(keys::kWebviewAccessibleResources,
+ &url_list)) {
+ *error = base::ASCIIToUTF16(
+ errors::kInvalidWebviewAccessibleResourcesList);
+ return false;
+ }
+
+ // The URL list should have at least one entry.
+ if (url_list->GetSize() == 0) {
+ *error = base::ASCIIToUTF16(
+ errors::kInvalidWebviewAccessibleResourcesList);
+ return false;
+ }
+
+ scoped_ptr<PartitionItem> partition_item(
+ new PartitionItem(partition_pattern));
+
+ for (size_t i = 0; i < url_list->GetSize(); ++i) {
+ std::string relative_path;
+ if (!url_list->GetString(i, &relative_path)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidWebviewAccessibleResource, base::SizeTToString(i));
+ return false;
+ }
+ URLPattern pattern(URLPattern::SCHEME_EXTENSION,
+ Extension::GetResourceURL(extension->url(),
+ relative_path).spec());
+ partition_item->AddPattern(pattern);
+ }
+ info->AddPartitionItem(std::move(partition_item));
+ }
+
+ extension->SetManifestData(keys::kWebviewAccessibleResources, info.release());
+ return true;
+}
+
+const std::vector<std::string> WebviewHandler::Keys() const {
+ return SingleKey(keys::kWebview);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_handlers/webview_info.h b/chromium/extensions/common/manifest_handlers/webview_info.h
new file mode 100644
index 00000000000..887394d2dd7
--- /dev/null
+++ b/chromium/extensions/common/manifest_handlers/webview_info.h
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_WEBVIEW_INFO_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_WEBVIEW_INFO_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+class PartitionItem;
+
+// A class to hold the <webview> accessible extension resources
+// that may be specified in the manifest of an extension using the
+// "webview" key.
+class WebviewInfo : public Extension::ManifestData {
+ public:
+ // Returns true if |extension|'s resource at |relative_path| is accessible
+ // from the WebView partition with ID |partition_id|.
+ static bool IsResourceWebviewAccessible(const Extension* extension,
+ const std::string& partition_id,
+ const std::string& relative_path);
+
+ // Define out of line constructor/destructor to please Clang.
+ WebviewInfo(const std::string& extension_id);
+ ~WebviewInfo() override;
+
+ void AddPartitionItem(scoped_ptr<PartitionItem> item);
+
+ private:
+ std::string extension_id_;
+ std::vector<scoped_ptr<PartitionItem>> partition_items_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebviewInfo);
+};
+
+// Parses the "webview" manifest key.
+class WebviewHandler : public ManifestHandler {
+ public:
+ WebviewHandler();
+ ~WebviewHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(WebviewHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_WEBVIEW_INFO_H_
diff --git a/chromium/extensions/common/manifest_test.cc b/chromium/extensions/common/manifest_test.cc
new file mode 100644
index 00000000000..771d4b3c4ad
--- /dev/null
+++ b/chromium/extensions/common/manifest_test.cc
@@ -0,0 +1,274 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_test.h"
+
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/path_service.h"
+#include "base/strings/pattern.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "extensions/common/extension_l10n_util.h"
+#include "extensions/common/extension_paths.h"
+#include "extensions/common/test_util.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+namespace {
+
+// |manifest_path| is an absolute path to a manifest file.
+scoped_ptr<base::DictionaryValue> LoadManifestFile(
+ const base::FilePath& manifest_path,
+ std::string* error) {
+ base::FilePath extension_path = manifest_path.DirName();
+
+ EXPECT_TRUE(base::PathExists(manifest_path)) <<
+ "Couldn't find " << manifest_path.value();
+
+ JSONFileValueDeserializer deserializer(manifest_path);
+ scoped_ptr<base::DictionaryValue> manifest =
+ base::DictionaryValue::From(deserializer.Deserialize(NULL, error));
+
+ // Most unit tests don't need localization, and they'll fail if we try to
+ // localize them, since their manifests don't have a default_locale key.
+ // Only localize manifests that indicate they want to be localized.
+ // Calling LocalizeExtension at this point mirrors file_util::LoadExtension.
+ if (manifest &&
+ manifest_path.value().find(FILE_PATH_LITERAL("localized")) !=
+ std::string::npos)
+ extension_l10n_util::LocalizeExtension(extension_path, manifest.get(),
+ error);
+
+ return manifest;
+}
+
+} // namespace
+
+ManifestTest::ManifestTest()
+ : enable_apps_(true) {
+}
+
+ManifestTest::~ManifestTest() {
+}
+
+// Helper class that simplifies creating methods that take either a filename
+// to a manifest or the manifest itself.
+ManifestTest::ManifestData::ManifestData(const char* name)
+ : name_(name), manifest_(nullptr) {}
+
+// This does not take ownership of |manifest|.
+ManifestTest::ManifestData::ManifestData(base::DictionaryValue* manifest,
+ const char* name)
+ : name_(name), manifest_(manifest) {
+ CHECK(manifest_) << "Manifest NULL";
+}
+
+ManifestTest::ManifestData::ManifestData(
+ scoped_ptr<base::DictionaryValue> manifest)
+ : manifest_(manifest.get()), manifest_holder_(std::move(manifest)) {
+ CHECK(manifest_) << "Manifest NULL";
+}
+
+ManifestTest::ManifestData::ManifestData(
+ scoped_ptr<base::DictionaryValue> manifest,
+ const char* name)
+ : name_(name),
+ manifest_(manifest.get()),
+ manifest_holder_(std::move(manifest)) {
+ CHECK(manifest_) << "Manifest NULL";
+}
+
+ManifestTest::ManifestData::ManifestData(const ManifestData& m) {
+ NOTREACHED();
+}
+
+ManifestTest::ManifestData::~ManifestData() {
+}
+
+base::DictionaryValue* ManifestTest::ManifestData::GetManifest(
+ base::FilePath test_data_dir, std::string* error) const {
+ if (manifest_)
+ return manifest_;
+
+ base::FilePath manifest_path = test_data_dir.AppendASCII(name_);
+ manifest_holder_ = LoadManifestFile(manifest_path, error);
+ manifest_ = manifest_holder_.get();
+ return manifest_;
+}
+
+std::string ManifestTest::GetTestExtensionID() const {
+ return std::string();
+}
+
+base::FilePath ManifestTest::GetTestDataDir() {
+ base::FilePath path;
+ PathService::Get(DIR_TEST_DATA, &path);
+ return path.AppendASCII("manifest_tests");
+}
+
+scoped_ptr<base::DictionaryValue> ManifestTest::LoadManifest(
+ char const* manifest_name, std::string* error) {
+ base::FilePath manifest_path = GetTestDataDir().AppendASCII(manifest_name);
+ return LoadManifestFile(manifest_path, error);
+}
+
+scoped_refptr<Extension> ManifestTest::LoadExtension(
+ const ManifestData& manifest,
+ std::string* error,
+ extensions::Manifest::Location location,
+ int flags) {
+ base::FilePath test_data_dir = GetTestDataDir();
+ base::DictionaryValue* value = manifest.GetManifest(test_data_dir, error);
+ if (!value)
+ return NULL;
+ return Extension::Create(test_data_dir.DirName(), location, *value, flags,
+ GetTestExtensionID(), error);
+}
+
+scoped_refptr<Extension> ManifestTest::LoadAndExpectSuccess(
+ const ManifestData& manifest,
+ extensions::Manifest::Location location,
+ int flags) {
+ std::string error;
+ scoped_refptr<Extension> extension =
+ LoadExtension(manifest, &error, location, flags);
+ EXPECT_TRUE(extension.get()) << manifest.name();
+ EXPECT_EQ("", error) << manifest.name();
+ return extension;
+}
+
+scoped_refptr<Extension> ManifestTest::LoadAndExpectSuccess(
+ char const* manifest_name,
+ extensions::Manifest::Location location,
+ int flags) {
+ return LoadAndExpectSuccess(ManifestData(manifest_name), location, flags);
+}
+
+scoped_refptr<Extension> ManifestTest::LoadAndExpectWarning(
+ const ManifestData& manifest,
+ const std::string& expected_warning,
+ extensions::Manifest::Location location,
+ int flags) {
+ std::string error;
+ scoped_refptr<Extension> extension =
+ LoadExtension(manifest, &error, location, flags);
+ EXPECT_TRUE(extension.get()) << manifest.name();
+ EXPECT_EQ("", error) << manifest.name();
+ EXPECT_EQ(1u, extension->install_warnings().size());
+ EXPECT_EQ(expected_warning, extension->install_warnings()[0].message);
+ return extension;
+}
+
+scoped_refptr<Extension> ManifestTest::LoadAndExpectWarning(
+ char const* manifest_name,
+ const std::string& expected_warning,
+ extensions::Manifest::Location location,
+ int flags) {
+ return LoadAndExpectWarning(
+ ManifestData(manifest_name), expected_warning, location, flags);
+}
+
+void ManifestTest::VerifyExpectedError(
+ Extension* extension,
+ const std::string& name,
+ const std::string& error,
+ const std::string& expected_error) {
+ EXPECT_FALSE(extension) <<
+ "Expected failure loading extension '" << name <<
+ "', but didn't get one.";
+ EXPECT_TRUE(base::MatchPattern(error, expected_error))
+ << name << " expected '" << expected_error << "' but got '" << error
+ << "'";
+}
+
+void ManifestTest::LoadAndExpectError(
+ const ManifestData& manifest,
+ const std::string& expected_error,
+ extensions::Manifest::Location location,
+ int flags) {
+ std::string error;
+ scoped_refptr<Extension> extension(
+ LoadExtension(manifest, &error, location, flags));
+ VerifyExpectedError(extension.get(), manifest.name(), error,
+ expected_error);
+}
+
+void ManifestTest::LoadAndExpectError(
+ char const* manifest_name,
+ const std::string& expected_error,
+ extensions::Manifest::Location location,
+ int flags) {
+ return LoadAndExpectError(
+ ManifestData(manifest_name), expected_error, location, flags);
+}
+
+void ManifestTest::AddPattern(extensions::URLPatternSet* extent,
+ const std::string& pattern) {
+ int schemes = URLPattern::SCHEME_ALL;
+ extent->AddPattern(URLPattern(schemes, pattern));
+}
+
+ManifestTest::Testcase::Testcase(const std::string& manifest_filename,
+ const std::string& expected_error,
+ extensions::Manifest::Location location,
+ int flags)
+ : manifest_filename_(manifest_filename),
+ expected_error_(expected_error),
+ location_(location),
+ flags_(flags) {}
+
+ManifestTest::Testcase::Testcase(const std::string& manifest_filename,
+ const std::string& expected_error)
+ : manifest_filename_(manifest_filename),
+ expected_error_(expected_error),
+ location_(extensions::Manifest::INTERNAL),
+ flags_(Extension::NO_FLAGS) {}
+
+ManifestTest::Testcase::Testcase(const std::string& manifest_filename)
+ : manifest_filename_(manifest_filename),
+ location_(extensions::Manifest::INTERNAL),
+ flags_(Extension::NO_FLAGS) {}
+
+ManifestTest::Testcase::Testcase(const std::string& manifest_filename,
+ extensions::Manifest::Location location,
+ int flags)
+ : manifest_filename_(manifest_filename),
+ location_(location),
+ flags_(flags) {}
+
+void ManifestTest::RunTestcases(const Testcase* testcases,
+ size_t num_testcases,
+ ExpectType type) {
+ for (size_t i = 0; i < num_testcases; ++i)
+ RunTestcase(testcases[i], type);
+}
+
+void ManifestTest::RunTestcase(const Testcase& testcase,
+ ExpectType type) {
+ switch (type) {
+ case EXPECT_TYPE_ERROR:
+ LoadAndExpectError(testcase.manifest_filename_.c_str(),
+ testcase.expected_error_,
+ testcase.location_,
+ testcase.flags_);
+ break;
+ case EXPECT_TYPE_WARNING:
+ LoadAndExpectWarning(testcase.manifest_filename_.c_str(),
+ testcase.expected_error_,
+ testcase.location_,
+ testcase.flags_);
+ break;
+ case EXPECT_TYPE_SUCCESS:
+ LoadAndExpectSuccess(testcase.manifest_filename_.c_str(),
+ testcase.location_,
+ testcase.flags_);
+ break;
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_test.h b/chromium/extensions/common/manifest_test.h
new file mode 100644
index 00000000000..32a5fa53151
--- /dev/null
+++ b/chromium/extensions/common/manifest_test.h
@@ -0,0 +1,172 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_TEST_H_
+#define EXTENSIONS_COMMON_MANIFEST_TEST_H_
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace extensions {
+
+// Base class for tests that parse a manifest file.
+class ManifestTest : public testing::Test {
+ public:
+ ManifestTest();
+ ~ManifestTest() override;
+
+ protected:
+ // Helper class that simplifies creating methods that take either a filename
+ // to a manifest or the manifest itself.
+ class ManifestData {
+ public:
+ explicit ManifestData(const char* name);
+ ManifestData(base::DictionaryValue* manifest, const char* name);
+ explicit ManifestData(scoped_ptr<base::DictionaryValue> manifest);
+ explicit ManifestData(scoped_ptr<base::DictionaryValue> manifest,
+ const char* name);
+ // C++98 requires the copy constructor for a type to be visible if you
+ // take a const-ref of a temporary for that type. Since Manifest
+ // contains a scoped_ptr, its implicit copy constructor is declared
+ // Manifest(Manifest&) according to spec 12.8.5. This breaks the first
+ // requirement and thus you cannot use it with LoadAndExpectError() or
+ // LoadAndExpectSuccess() easily.
+ //
+ // To get around this spec pedantry, we declare the copy constructor
+ // explicitly. It will never get invoked.
+ ManifestData(const ManifestData& m);
+
+ ~ManifestData();
+
+ const std::string& name() const { return name_; };
+
+ base::DictionaryValue* GetManifest(base::FilePath manifest_path,
+ std::string* error) const;
+
+ private:
+ const std::string name_;
+ mutable base::DictionaryValue* manifest_;
+ mutable scoped_ptr<base::DictionaryValue> manifest_holder_;
+ };
+
+ // Allows the test implementation to override a loaded test manifest's
+ // extension ID. Useful for testing features behind a whitelist.
+ virtual std::string GetTestExtensionID() const;
+
+ // Returns the path in which to find test manifest data files, for example
+ // extensions/test/data/manifest_tests.
+ virtual base::FilePath GetTestDataDir();
+
+ scoped_ptr<base::DictionaryValue> LoadManifest(
+ char const* manifest_name,
+ std::string* error);
+
+ scoped_refptr<extensions::Extension> LoadExtension(
+ const ManifestData& manifest,
+ std::string* error,
+ extensions::Manifest::Location location =
+ extensions::Manifest::INTERNAL,
+ int flags = extensions::Extension::NO_FLAGS);
+
+ scoped_refptr<extensions::Extension> LoadAndExpectSuccess(
+ const ManifestData& manifest,
+ extensions::Manifest::Location location =
+ extensions::Manifest::INTERNAL,
+ int flags = extensions::Extension::NO_FLAGS);
+
+ scoped_refptr<extensions::Extension> LoadAndExpectSuccess(
+ char const* manifest_name,
+ extensions::Manifest::Location location =
+ extensions::Manifest::INTERNAL,
+ int flags = extensions::Extension::NO_FLAGS);
+
+ scoped_refptr<extensions::Extension> LoadAndExpectWarning(
+ const ManifestData& manifest,
+ const std::string& expected_error,
+ extensions::Manifest::Location location =
+ extensions::Manifest::INTERNAL,
+ int flags = extensions::Extension::NO_FLAGS);
+
+ scoped_refptr<extensions::Extension> LoadAndExpectWarning(
+ char const* manifest_name,
+ const std::string& expected_error,
+ extensions::Manifest::Location location =
+ extensions::Manifest::INTERNAL,
+ int flags = extensions::Extension::NO_FLAGS);
+
+ void VerifyExpectedError(extensions::Extension* extension,
+ const std::string& name,
+ const std::string& error,
+ const std::string& expected_error);
+
+ void LoadAndExpectError(char const* manifest_name,
+ const std::string& expected_error,
+ extensions::Manifest::Location location =
+ extensions::Manifest::INTERNAL,
+ int flags = extensions::Extension::NO_FLAGS);
+
+ void LoadAndExpectError(const ManifestData& manifest,
+ const std::string& expected_error,
+ extensions::Manifest::Location location =
+ extensions::Manifest::INTERNAL,
+ int flags = extensions::Extension::NO_FLAGS);
+
+ void AddPattern(extensions::URLPatternSet* extent,
+ const std::string& pattern);
+
+ // used to differentiate between calls to LoadAndExpectError,
+ // LoadAndExpectWarning and LoadAndExpectSuccess via function RunTestcases.
+ enum ExpectType {
+ EXPECT_TYPE_ERROR,
+ EXPECT_TYPE_WARNING,
+ EXPECT_TYPE_SUCCESS
+ };
+
+ struct Testcase {
+ const std::string manifest_filename_;
+ std::string expected_error_; // only used for ExpectedError tests
+ extensions::Manifest::Location location_;
+ int flags_;
+
+ Testcase(const std::string& manifest_filename,
+ const std::string& expected_error,
+ extensions::Manifest::Location location,
+ int flags);
+
+ Testcase(const std::string& manifest_filename,
+ const std::string& expected_error);
+
+ explicit Testcase(const std::string& manifest_filename);
+
+ Testcase(const std::string& manifest_filename,
+ extensions::Manifest::Location location,
+ int flags);
+ };
+
+ void RunTestcases(const Testcase* testcases,
+ size_t num_testcases,
+ ExpectType type);
+
+ void RunTestcase(const Testcase& testcase, ExpectType type);
+
+ bool enable_apps_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ManifestTest);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_TEST_H_
diff --git a/chromium/extensions/common/manifest_url_handlers.cc b/chromium/extensions/common/manifest_url_handlers.cc
new file mode 100644
index 00000000000..7d1487344b5
--- /dev/null
+++ b/chromium/extensions/common/manifest_url_handlers.cc
@@ -0,0 +1,196 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_url_handlers.h"
+
+#include "base/files/file_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension_urls.h"
+#include "extensions/common/file_util.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/shared_module_info.h"
+#include "extensions/strings/grit/extensions_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+// static
+const GURL& ManifestURL::Get(const Extension* extension,
+ const std::string& key) {
+ ManifestURL* manifest_url =
+ static_cast<ManifestURL*>(extension->GetManifestData(key));
+ return manifest_url ? manifest_url->url_ : GURL::EmptyGURL();
+}
+
+// static
+const GURL ManifestURL::GetHomepageURL(const Extension* extension) {
+ const GURL& homepage_url = Get(extension, keys::kHomepageURL);
+ if (homepage_url.is_valid())
+ return homepage_url;
+ bool use_webstore_url = UpdatesFromGallery(extension) &&
+ !SharedModuleInfo::IsSharedModule(extension);
+ return use_webstore_url
+ ? GURL(extension_urls::GetWebstoreItemDetailURLPrefix() +
+ extension->id())
+ : GURL::EmptyGURL();
+}
+
+// static
+bool ManifestURL::SpecifiedHomepageURL(const Extension* extension) {
+ return Get(extension, keys::kHomepageURL).is_valid();
+}
+
+// static
+const GURL& ManifestURL::GetUpdateURL(const Extension* extension) {
+ return Get(extension, keys::kUpdateURL);
+}
+
+// static
+bool ManifestURL::UpdatesFromGallery(const Extension* extension) {
+ return extension_urls::IsWebstoreUpdateUrl(GetUpdateURL(extension));
+}
+
+// static
+bool ManifestURL::UpdatesFromGallery(const base::DictionaryValue* manifest) {
+ std::string url;
+ if (!manifest->GetString(keys::kUpdateURL, &url))
+ return false;
+ return extension_urls::IsWebstoreUpdateUrl(GURL(url));
+}
+
+// static
+const GURL& ManifestURL::GetAboutPage(const Extension* extension) {
+ return Get(extension, keys::kAboutPage);
+}
+
+// static
+const GURL ManifestURL::GetDetailsURL(const Extension* extension) {
+ return extension->from_webstore() ?
+ GURL(extension_urls::GetWebstoreItemDetailURLPrefix() + extension->id()) :
+ GURL::EmptyGURL();
+}
+
+HomepageURLHandler::HomepageURLHandler() {
+}
+
+HomepageURLHandler::~HomepageURLHandler() {
+}
+
+bool HomepageURLHandler::Parse(Extension* extension, base::string16* error) {
+ scoped_ptr<ManifestURL> manifest_url(new ManifestURL);
+ std::string homepage_url_str;
+ if (!extension->manifest()->GetString(keys::kHomepageURL,
+ &homepage_url_str)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidHomepageURL,
+ std::string());
+ return false;
+ }
+ manifest_url->url_ = GURL(homepage_url_str);
+ if (!manifest_url->url_.is_valid() ||
+ !manifest_url->url_.SchemeIsHTTPOrHTTPS()) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidHomepageURL, homepage_url_str);
+ return false;
+ }
+ extension->SetManifestData(keys::kHomepageURL, manifest_url.release());
+ return true;
+}
+
+const std::vector<std::string> HomepageURLHandler::Keys() const {
+ return SingleKey(keys::kHomepageURL);
+}
+
+UpdateURLHandler::UpdateURLHandler() {
+}
+
+UpdateURLHandler::~UpdateURLHandler() {
+}
+
+bool UpdateURLHandler::Parse(Extension* extension, base::string16* error) {
+ scoped_ptr<ManifestURL> manifest_url(new ManifestURL);
+ std::string tmp_update_url;
+
+ if (!extension->manifest()->GetString(keys::kUpdateURL, &tmp_update_url)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidUpdateURL,
+ std::string());
+ return false;
+ }
+
+ manifest_url->url_ = GURL(tmp_update_url);
+ if (!manifest_url->url_.is_valid() ||
+ manifest_url->url_.has_ref()) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidUpdateURL, tmp_update_url);
+ return false;
+ }
+
+ extension->SetManifestData(keys::kUpdateURL, manifest_url.release());
+ return true;
+}
+
+const std::vector<std::string> UpdateURLHandler::Keys() const {
+ return SingleKey(keys::kUpdateURL);
+}
+
+AboutPageHandler::AboutPageHandler() {
+}
+
+AboutPageHandler::~AboutPageHandler() {
+}
+
+bool AboutPageHandler::Parse(Extension* extension, base::string16* error) {
+ scoped_ptr<ManifestURL> manifest_url(new ManifestURL);
+ std::string about_str;
+ if (!extension->manifest()->GetString(keys::kAboutPage, &about_str)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidAboutPage);
+ return false;
+ }
+
+ GURL absolute(about_str);
+ if (absolute.is_valid()) {
+ *error = base::ASCIIToUTF16(errors::kInvalidAboutPageExpectRelativePath);
+ return false;
+ }
+ manifest_url->url_ = extension->GetResourceURL(about_str);
+ if (!manifest_url->url_.is_valid()) {
+ *error = base::ASCIIToUTF16(errors::kInvalidAboutPage);
+ return false;
+ }
+ extension->SetManifestData(keys::kAboutPage, manifest_url.release());
+ return true;
+}
+
+bool AboutPageHandler::Validate(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const {
+ // Validate path to the options page.
+ if (!extensions::ManifestURL::GetAboutPage(extension).is_empty()) {
+ const base::FilePath about_path =
+ extensions::file_util::ExtensionURLToRelativeFilePath(
+ extensions::ManifestURL::GetAboutPage(extension));
+ const base::FilePath path =
+ extension->GetResource(about_path).GetFilePath();
+ if (path.empty() || !base::PathExists(path)) {
+ *error = l10n_util::GetStringFUTF8(IDS_EXTENSION_LOAD_ABOUT_PAGE_FAILED,
+ about_path.LossyDisplayName());
+ return false;
+ }
+ }
+ return true;
+}
+
+const std::vector<std::string> AboutPageHandler::Keys() const {
+ return SingleKey(keys::kAboutPage);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/manifest_url_handlers.h b/chromium/extensions/common/manifest_url_handlers.h
new file mode 100644
index 00000000000..fb1b6c754e0
--- /dev/null
+++ b/chromium/extensions/common/manifest_url_handlers.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_URL_HANDLERS_H_
+#define EXTENSIONS_COMMON_MANIFEST_URL_HANDLERS_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace extensions {
+
+// A structure to hold various URLs like devtools_page, homepage_url, etc
+// that may be specified in the manifest of an extension.
+struct ManifestURL : public Extension::ManifestData {
+ GURL url_;
+
+ // Returns the value of a URL key for an extension, or an empty URL if unset.
+ static const GURL& Get(const Extension* extension, const std::string& key);
+
+ // Returns the Homepage URL for this extension.
+ // If homepage_url was not specified in the manifest,
+ // this returns the Google Gallery URL. For third-party extensions,
+ // this returns a blank GURL.
+ static const GURL GetHomepageURL(const Extension* extension);
+
+ // Returns true if the extension specified a valid home page url in the
+ // manifest.
+ static bool SpecifiedHomepageURL(const Extension* extension);
+
+ // Returns the Update URL for this extension.
+ static const GURL& GetUpdateURL(const Extension* extension);
+
+ // Returns true if this extension's update URL is the extension gallery.
+ static bool UpdatesFromGallery(const Extension* extension);
+ static bool UpdatesFromGallery(const base::DictionaryValue* manifest);
+
+ // Returns the About Page for this extension.
+ static const GURL& GetAboutPage(const Extension* extension);
+
+ // Returns the webstore page URL for this extension.
+ static const GURL GetDetailsURL(const Extension* extension);
+};
+
+// Parses the "homepage_url" manifest key.
+class HomepageURLHandler : public ManifestHandler {
+ public:
+ HomepageURLHandler();
+ ~HomepageURLHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(HomepageURLHandler);
+};
+
+// Parses the "update_url" manifest key.
+class UpdateURLHandler : public ManifestHandler {
+ public:
+ UpdateURLHandler();
+ ~UpdateURLHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateURLHandler);
+};
+
+// Parses the "about_page" manifest key.
+// TODO(sashab): Make this and any other similar handlers extend from the same
+// abstract class, URLManifestHandler, which has pure virtual methods for
+// detecting the required URL type (relative or absolute) and abstracts the
+// URL parsing logic away.
+class AboutPageHandler : public ManifestHandler {
+ public:
+ AboutPageHandler();
+ ~AboutPageHandler() override;
+
+ bool Parse(Extension* extension, base::string16* error) override;
+ bool Validate(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const override;
+
+ private:
+ const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(AboutPageHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_URL_HANDLERS_H_
diff --git a/chromium/extensions/common/message_bundle.cc b/chromium/extensions/common/message_bundle.cc
new file mode 100644
index 00000000000..ef78a0a7d5c
--- /dev/null
+++ b/chromium/extensions/common/message_bundle.cc
@@ -0,0 +1,346 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/message_bundle.h"
+
+#include <string>
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "base/i18n/rtl.h"
+#include "base/lazy_instance.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension_l10n_util.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+
+const char MessageBundle::kContentKey[] = "content";
+const char MessageBundle::kMessageKey[] = "message";
+const char MessageBundle::kPlaceholdersKey[] = "placeholders";
+
+const char MessageBundle::kPlaceholderBegin[] = "$";
+const char MessageBundle::kPlaceholderEnd[] = "$";
+const char MessageBundle::kMessageBegin[] = "__MSG_";
+const char MessageBundle::kMessageEnd[] = "__";
+
+// Reserved messages names.
+const char MessageBundle::kUILocaleKey[] = "@@ui_locale";
+const char MessageBundle::kBidiDirectionKey[] = "@@bidi_dir";
+const char MessageBundle::kBidiReversedDirectionKey[] = "@@bidi_reversed_dir";
+const char MessageBundle::kBidiStartEdgeKey[] = "@@bidi_start_edge";
+const char MessageBundle::kBidiEndEdgeKey[] = "@@bidi_end_edge";
+const char MessageBundle::kExtensionIdKey[] = "@@extension_id";
+
+// Reserved messages values.
+const char MessageBundle::kBidiLeftEdgeValue[] = "left";
+const char MessageBundle::kBidiRightEdgeValue[] = "right";
+
+// Formats message in case we encounter a bad formed key in the JSON object.
+// Returns false and sets |error| to actual error message.
+static bool BadKeyMessage(const std::string& name, std::string* error) {
+ *error = base::StringPrintf(
+ "Name of a key \"%s\" is invalid. Only ASCII [a-z], "
+ "[A-Z], [0-9] and \"_\" are allowed.",
+ name.c_str());
+ return false;
+}
+
+// static
+MessageBundle* MessageBundle::Create(const CatalogVector& locale_catalogs,
+ std::string* error) {
+ scoped_ptr<MessageBundle> message_bundle(new MessageBundle);
+ if (!message_bundle->Init(locale_catalogs, error))
+ return NULL;
+
+ return message_bundle.release();
+}
+
+bool MessageBundle::Init(const CatalogVector& locale_catalogs,
+ std::string* error) {
+ dictionary_.clear();
+
+ for (CatalogVector::const_reverse_iterator it = locale_catalogs.rbegin();
+ it != locale_catalogs.rend(); ++it) {
+ base::DictionaryValue* catalog = (*it).get();
+ for (base::DictionaryValue::Iterator message_it(*catalog);
+ !message_it.IsAtEnd(); message_it.Advance()) {
+ std::string key(base::ToLowerASCII(message_it.key()));
+ if (!IsValidName(message_it.key()))
+ return BadKeyMessage(key, error);
+ std::string value;
+ if (!GetMessageValue(message_it.key(), message_it.value(), &value, error))
+ return false;
+ // Keys are not case-sensitive.
+ dictionary_[key] = value;
+ }
+ }
+
+ if (!AppendReservedMessagesForLocale(
+ extension_l10n_util::CurrentLocaleOrDefault(), error))
+ return false;
+
+ return true;
+}
+
+bool MessageBundle::AppendReservedMessagesForLocale(
+ const std::string& app_locale, std::string* error) {
+ SubstitutionMap append_messages;
+ append_messages[kUILocaleKey] = app_locale;
+
+ // Calling base::i18n::GetTextDirection on non-UI threads doesn't seems safe,
+ // so we use GetTextDirectionForLocale instead.
+ if (base::i18n::GetTextDirectionForLocale(app_locale.c_str()) ==
+ base::i18n::RIGHT_TO_LEFT) {
+ append_messages[kBidiDirectionKey] = "rtl";
+ append_messages[kBidiReversedDirectionKey] = "ltr";
+ append_messages[kBidiStartEdgeKey] = kBidiRightEdgeValue;
+ append_messages[kBidiEndEdgeKey] = kBidiLeftEdgeValue;
+ } else {
+ append_messages[kBidiDirectionKey] = "ltr";
+ append_messages[kBidiReversedDirectionKey] = "rtl";
+ append_messages[kBidiStartEdgeKey] = kBidiLeftEdgeValue;
+ append_messages[kBidiEndEdgeKey] = kBidiRightEdgeValue;
+ }
+
+ // Add all reserved messages to the dictionary, but check for collisions.
+ SubstitutionMap::iterator it = append_messages.begin();
+ for (; it != append_messages.end(); ++it) {
+ if (ContainsKey(dictionary_, it->first)) {
+ *error = ErrorUtils::FormatErrorMessage(
+ errors::kReservedMessageFound, it->first);
+ return false;
+ } else {
+ dictionary_[it->first] = it->second;
+ }
+ }
+
+ return true;
+}
+
+bool MessageBundle::GetMessageValue(const std::string& key,
+ const base::Value& name_value,
+ std::string* value,
+ std::string* error) const {
+ // Get the top level tree for given key (name part).
+ const base::DictionaryValue* name_tree;
+ if (!name_value.GetAsDictionary(&name_tree)) {
+ *error = base::StringPrintf("Not a valid tree for key %s.", key.c_str());
+ return false;
+ }
+ // Extract message from it.
+ if (!name_tree->GetString(kMessageKey, value)) {
+ *error = base::StringPrintf(
+ "There is no \"%s\" element for key %s.", kMessageKey, key.c_str());
+ return false;
+ }
+
+ SubstitutionMap placeholders;
+ if (!GetPlaceholders(*name_tree, key, &placeholders, error))
+ return false;
+
+ if (!ReplacePlaceholders(placeholders, value, error))
+ return false;
+
+ return true;
+}
+
+MessageBundle::MessageBundle() {
+}
+
+bool MessageBundle::GetPlaceholders(const base::DictionaryValue& name_tree,
+ const std::string& name_key,
+ SubstitutionMap* placeholders,
+ std::string* error) const {
+ if (!name_tree.HasKey(kPlaceholdersKey))
+ return true;
+
+ const base::DictionaryValue* placeholders_tree;
+ if (!name_tree.GetDictionary(kPlaceholdersKey, &placeholders_tree)) {
+ *error = base::StringPrintf("Not a valid \"%s\" element for key %s.",
+ kPlaceholdersKey, name_key.c_str());
+ return false;
+ }
+
+ for (base::DictionaryValue::Iterator it(*placeholders_tree); !it.IsAtEnd();
+ it.Advance()) {
+ const base::DictionaryValue* placeholder;
+ const std::string& content_key(it.key());
+ if (!IsValidName(content_key))
+ return BadKeyMessage(content_key, error);
+ if (!it.value().GetAsDictionary(&placeholder)) {
+ *error = base::StringPrintf("Invalid placeholder %s for key %s",
+ content_key.c_str(),
+ name_key.c_str());
+ return false;
+ }
+ std::string content;
+ if (!placeholder->GetString(kContentKey, &content)) {
+ *error = base::StringPrintf("Invalid \"%s\" element for key %s.",
+ kContentKey, name_key.c_str());
+ return false;
+ }
+ (*placeholders)[base::ToLowerASCII(content_key)] = content;
+ }
+
+ return true;
+}
+
+bool MessageBundle::ReplacePlaceholders(const SubstitutionMap& placeholders,
+ std::string* message,
+ std::string* error) const {
+ return ReplaceVariables(placeholders,
+ kPlaceholderBegin,
+ kPlaceholderEnd,
+ message,
+ error);
+}
+
+bool MessageBundle::ReplaceMessages(std::string* text,
+ std::string* error) const {
+ return ReplaceMessagesWithExternalDictionary(dictionary_, text, error);
+}
+
+MessageBundle::~MessageBundle() {
+}
+
+// static
+bool MessageBundle::ReplaceMessagesWithExternalDictionary(
+ const SubstitutionMap& dictionary, std::string* text, std::string* error) {
+ return ReplaceVariables(dictionary, kMessageBegin, kMessageEnd, text, error);
+}
+
+// static
+bool MessageBundle::ReplaceVariables(const SubstitutionMap& variables,
+ const std::string& var_begin_delimiter,
+ const std::string& var_end_delimiter,
+ std::string* message,
+ std::string* error) {
+ std::string::size_type beg_index = 0;
+ const std::string::size_type var_begin_delimiter_size =
+ var_begin_delimiter.size();
+ while (true) {
+ beg_index = message->find(var_begin_delimiter, beg_index);
+ if (beg_index == message->npos)
+ return true;
+
+ // Advance it immediately to the begining of possible variable name.
+ beg_index += var_begin_delimiter_size;
+ if (beg_index >= message->size())
+ return true;
+ std::string::size_type end_index =
+ message->find(var_end_delimiter, beg_index);
+ if (end_index == message->npos)
+ return true;
+
+ // Looking for 1 in substring of ...$1$....
+ const std::string& var_name =
+ message->substr(beg_index, end_index - beg_index);
+ if (!IsValidName(var_name))
+ continue;
+ auto it = variables.find(base::ToLowerASCII(var_name));
+ if (it == variables.end()) {
+ *error = base::StringPrintf("Variable %s%s%s used but not defined.",
+ var_begin_delimiter.c_str(),
+ var_name.c_str(),
+ var_end_delimiter.c_str());
+ return false;
+ }
+
+ // Replace variable with its value.
+ std::string value = it->second;
+ message->replace(beg_index - var_begin_delimiter_size,
+ end_index - beg_index + var_begin_delimiter_size +
+ var_end_delimiter.size(),
+ value);
+
+ // And position pointer to after the replacement.
+ beg_index += value.size() - var_begin_delimiter_size;
+ }
+
+ return true;
+}
+
+// static
+bool MessageBundle::IsValidName(const std::string& name) {
+ if (name.empty())
+ return false;
+
+ std::string::const_iterator it = name.begin();
+ for (; it != name.end(); ++it) {
+ // Allow only ascii 0-9, a-z, A-Z, and _ in the name.
+ if (!base::IsAsciiAlpha(*it) && !base::IsAsciiDigit(*it) && *it != '_' &&
+ *it != '@')
+ return false;
+ }
+
+ return true;
+}
+
+// Dictionary interface.
+
+std::string MessageBundle::GetL10nMessage(const std::string& name) const {
+ return GetL10nMessage(name, dictionary_);
+}
+
+// static
+std::string MessageBundle::GetL10nMessage(const std::string& name,
+ const SubstitutionMap& dictionary) {
+ auto it = dictionary.find(base::ToLowerASCII(name));
+ if (it != dictionary.end()) {
+ return it->second;
+ }
+
+ return std::string();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Renderer helper functions.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+// Unique class for Singleton.
+struct ExtensionToMessagesMap {
+ ExtensionToMessagesMap();
+ ~ExtensionToMessagesMap();
+
+ // Maps extension ID to message map.
+ ExtensionToL10nMessagesMap messages_map;
+};
+
+static base::LazyInstance<ExtensionToMessagesMap> g_extension_to_messages_map =
+ LAZY_INSTANCE_INITIALIZER;
+
+ExtensionToMessagesMap::ExtensionToMessagesMap() {}
+
+ExtensionToMessagesMap::~ExtensionToMessagesMap() {}
+
+ExtensionToL10nMessagesMap* GetExtensionToL10nMessagesMap() {
+ return &g_extension_to_messages_map.Get().messages_map;
+}
+
+L10nMessagesMap* GetL10nMessagesMap(const std::string& extension_id) {
+ ExtensionToL10nMessagesMap::iterator it =
+ g_extension_to_messages_map.Get().messages_map.find(extension_id);
+ if (it != g_extension_to_messages_map.Get().messages_map.end())
+ return &(it->second);
+
+ return NULL;
+}
+
+void EraseL10nMessagesMap(const std::string& extension_id) {
+ g_extension_to_messages_map.Get().messages_map.erase(extension_id);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/message_bundle.h b/chromium/extensions/common/message_bundle.h
new file mode 100644
index 00000000000..9c0bde79a30
--- /dev/null
+++ b/chromium/extensions/common/message_bundle.h
@@ -0,0 +1,175 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MESSAGE_BUNDLE_H_
+#define EXTENSIONS_COMMON_MESSAGE_BUNDLE_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/memory/linked_ptr.h"
+
+namespace base {
+class DictionaryValue;
+class Value;
+}
+
+namespace extensions {
+
+// Contains localized extension messages for one locale. Any messages that the
+// locale does not provide are pulled from the default locale.
+class MessageBundle {
+ public:
+ typedef std::map<std::string, std::string> SubstitutionMap;
+ typedef std::vector<linked_ptr<base::DictionaryValue> > CatalogVector;
+
+ // JSON keys of interest for messages file.
+ static const char kContentKey[];
+ static const char kMessageKey[];
+ static const char kPlaceholdersKey[];
+
+ // Begin/end markers for placeholders and messages
+ static const char kPlaceholderBegin[];
+ static const char kPlaceholderEnd[];
+ static const char kMessageBegin[];
+ static const char kMessageEnd[];
+
+ // Reserved message names in the dictionary.
+ // Update i18n documentation when adding new reserved value.
+ static const char kUILocaleKey[];
+ // See http://code.google.com/apis/gadgets/docs/i18n.html#BIDI for
+ // description.
+ // TODO(cira): point to chrome docs once they are out.
+ static const char kBidiDirectionKey[];
+ static const char kBidiReversedDirectionKey[];
+ static const char kBidiStartEdgeKey[];
+ static const char kBidiEndEdgeKey[];
+ // Extension id gets added in the
+ // browser/renderer_host/resource_message_filter.cc to enable message
+ // replacement for non-localized extensions.
+ static const char kExtensionIdKey[];
+
+ // Values for some of the reserved messages.
+ static const char kBidiLeftEdgeValue[];
+ static const char kBidiRightEdgeValue[];
+
+ // Creates MessageBundle or returns NULL if there was an error. Expects
+ // locale_catalogs to be sorted from more specific to less specific, with
+ // default catalog at the end.
+ static MessageBundle* Create(const CatalogVector& locale_catalogs,
+ std::string* error);
+
+ // Get message from the catalog with given key.
+ // Returned message has all of the internal placeholders resolved to their
+ // value (content).
+ // Returns empty string if it can't find a message.
+ // We don't use simple GetMessage name, since there is a global
+ // #define GetMessage GetMessageW override in Chrome code.
+ std::string GetL10nMessage(const std::string& name) const;
+
+ // Get message from the given catalog with given key.
+ static std::string GetL10nMessage(const std::string& name,
+ const SubstitutionMap& dictionary);
+
+ // Number of messages in the catalog.
+ // Used for unittesting only.
+ size_t size() const { return dictionary_.size(); }
+
+ // Replaces all __MSG_message__ with values from the catalog.
+ // Returns false if there is a message in text that's not defined in the
+ // dictionary.
+ bool ReplaceMessages(std::string* text, std::string* error) const;
+ // Static version that accepts dictionary.
+ static bool ReplaceMessagesWithExternalDictionary(
+ const SubstitutionMap& dictionary, std::string* text, std::string* error);
+
+ // Replaces each occurance of variable placeholder with its value.
+ // I.e. replaces __MSG_name__ with value from the catalog with the key "name".
+ // Returns false if for a valid message/placeholder name there is no matching
+ // replacement.
+ // Public for easier unittesting.
+ static bool ReplaceVariables(const SubstitutionMap& variables,
+ const std::string& var_begin,
+ const std::string& var_end,
+ std::string* message,
+ std::string* error);
+
+ // Allow only ascii 0-9, a-z, A-Z, and _ in the variable name.
+ // Returns false if the input is empty or if it has illegal characters.
+ static bool IsValidName(const std::string& name);
+
+ // Getter for dictionary_.
+ const SubstitutionMap* dictionary() const { return &dictionary_; }
+
+ ~MessageBundle();
+
+ private:
+ // Testing friend.
+ friend class MessageBundleTest;
+
+ // Use Create to create MessageBundle instance.
+ MessageBundle();
+
+ // Initializes the instance from the contents of vector of catalogs.
+ // If the key is not present in more specific catalog we fall back to next one
+ // (less specific).
+ // Returns false on error.
+ bool Init(const CatalogVector& locale_catalogs, std::string* error);
+
+ // Appends locale specific reserved messages to the dictionary.
+ // Returns false if there was a conflict with user defined messages.
+ bool AppendReservedMessagesForLocale(const std::string& application_locale,
+ std::string* error);
+
+ // Helper methods that navigate JSON tree and return simplified message.
+ // They replace all $PLACEHOLDERS$ with their value, and return just key/value
+ // of the message.
+ bool GetMessageValue(const std::string& key,
+ const base::Value& name_value,
+ std::string* value,
+ std::string* error) const;
+
+ // Get all placeholders for a given message from JSON subtree.
+ bool GetPlaceholders(const base::DictionaryValue& name_tree,
+ const std::string& name_key,
+ SubstitutionMap* placeholders,
+ std::string* error) const;
+
+ // For a given message, replaces all placeholders with their actual value.
+ // Returns false if replacement failed (see ReplaceVariables).
+ bool ReplacePlaceholders(const SubstitutionMap& placeholders,
+ std::string* message,
+ std::string* error) const;
+
+ // Holds all messages for application locale.
+ SubstitutionMap dictionary_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Renderer helper typedefs and functions.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+// A map of message name to message.
+typedef std::map<std::string, std::string> L10nMessagesMap;
+
+// A map of extension ID to l10n message map.
+typedef std::map<std::string, L10nMessagesMap > ExtensionToL10nMessagesMap;
+
+// Returns the extension_id to messages map.
+ExtensionToL10nMessagesMap* GetExtensionToL10nMessagesMap();
+
+// Returns message map that matches given extension_id, or NULL.
+L10nMessagesMap* GetL10nMessagesMap(const std::string& extension_id);
+
+// Erases the L10nMessagesMap for the given |extension_id|.
+void EraseL10nMessagesMap(const std::string& extension_id);
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MESSAGE_BUNDLE_H_
diff --git a/chromium/extensions/common/message_bundle_unittest.cc b/chromium/extensions/common/message_bundle_unittest.cc
new file mode 100644
index 00000000000..f80473bfcdb
--- /dev/null
+++ b/chromium/extensions/common/message_bundle_unittest.cc
@@ -0,0 +1,437 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/message_bundle.h"
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "base/i18n/rtl.h"
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension_l10n_util.h"
+#include "extensions/common/manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+
+class MessageBundleTest : public testing::Test {
+ protected:
+ enum BadDictionary {
+ INVALID_NAME,
+ NAME_NOT_A_TREE,
+ EMPTY_NAME_TREE,
+ MISSING_MESSAGE,
+ PLACEHOLDER_NOT_A_TREE,
+ EMPTY_PLACEHOLDER_TREE,
+ CONTENT_MISSING,
+ MESSAGE_PLACEHOLDER_DOESNT_MATCH,
+ };
+
+ // Helper method for dictionary building.
+ void SetDictionary(const std::string& name,
+ base::DictionaryValue* subtree,
+ base::DictionaryValue* target) {
+ target->Set(name, static_cast<base::Value*>(subtree));
+ }
+
+ void CreateContentTree(const std::string& name,
+ const std::string& content,
+ base::DictionaryValue* dict) {
+ base::DictionaryValue* content_tree = new base::DictionaryValue;
+ content_tree->SetString(MessageBundle::kContentKey, content);
+ SetDictionary(name, content_tree, dict);
+ }
+
+ void CreatePlaceholdersTree(base::DictionaryValue* dict) {
+ base::DictionaryValue* placeholders_tree = new base::DictionaryValue;
+ CreateContentTree("a", "A", placeholders_tree);
+ CreateContentTree("b", "B", placeholders_tree);
+ CreateContentTree("c", "C", placeholders_tree);
+ SetDictionary(MessageBundle::kPlaceholdersKey,
+ placeholders_tree,
+ dict);
+ }
+
+ void CreateMessageTree(const std::string& name,
+ const std::string& message,
+ bool create_placeholder_subtree,
+ base::DictionaryValue* dict) {
+ base::DictionaryValue* message_tree = new base::DictionaryValue;
+ if (create_placeholder_subtree)
+ CreatePlaceholdersTree(message_tree);
+ message_tree->SetString(MessageBundle::kMessageKey, message);
+ SetDictionary(name, message_tree, dict);
+ }
+
+ // Caller owns the memory.
+ base::DictionaryValue* CreateGoodDictionary() {
+ base::DictionaryValue* dict = new base::DictionaryValue;
+ CreateMessageTree("n1", "message1 $a$ $b$", true, dict);
+ CreateMessageTree("n2", "message2 $c$", true, dict);
+ CreateMessageTree("n3", "message3", false, dict);
+ return dict;
+ }
+
+ // Caller owns the memory.
+ base::DictionaryValue* CreateBadDictionary(enum BadDictionary what_is_bad) {
+ base::DictionaryValue* dict = CreateGoodDictionary();
+ // Now remove/break things.
+ switch (what_is_bad) {
+ case INVALID_NAME:
+ CreateMessageTree("n 5", "nevermind", false, dict);
+ break;
+ case NAME_NOT_A_TREE:
+ dict->SetString("n4", "whatever");
+ break;
+ case EMPTY_NAME_TREE: {
+ base::DictionaryValue* empty_tree = new base::DictionaryValue;
+ SetDictionary("n4", empty_tree, dict);
+ }
+ break;
+ case MISSING_MESSAGE:
+ dict->Remove("n1.message", NULL);
+ break;
+ case PLACEHOLDER_NOT_A_TREE:
+ dict->SetString("n1.placeholders", "whatever");
+ break;
+ case EMPTY_PLACEHOLDER_TREE: {
+ base::DictionaryValue* empty_tree = new base::DictionaryValue;
+ SetDictionary("n1.placeholders", empty_tree, dict);
+ }
+ break;
+ case CONTENT_MISSING:
+ dict->Remove("n1.placeholders.a.content", NULL);
+ break;
+ case MESSAGE_PLACEHOLDER_DOESNT_MATCH:
+ base::DictionaryValue* value;
+ dict->Remove("n1.placeholders.a", NULL);
+ dict->GetDictionary("n1.placeholders", &value);
+ CreateContentTree("x", "X", value);
+ break;
+ }
+
+ return dict;
+ }
+
+ unsigned int ReservedMessagesCount() {
+ // Update when adding new reserved messages.
+ return 5U;
+ }
+
+ void CheckReservedMessages(MessageBundle* handler) {
+ std::string ui_locale = extension_l10n_util::CurrentLocaleOrDefault();
+ EXPECT_EQ(ui_locale,
+ handler->GetL10nMessage(MessageBundle::kUILocaleKey));
+
+ std::string text_dir = "ltr";
+ if (base::i18n::GetTextDirectionForLocale(ui_locale.c_str()) ==
+ base::i18n::RIGHT_TO_LEFT)
+ text_dir = "rtl";
+
+ EXPECT_EQ(text_dir, handler->GetL10nMessage(
+ MessageBundle::kBidiDirectionKey));
+ }
+
+ bool AppendReservedMessages(const std::string& application_locale) {
+ std::string error;
+ return handler_->AppendReservedMessagesForLocale(
+ application_locale, &error);
+ }
+
+ std::string CreateMessageBundle() {
+ std::string error;
+ handler_.reset(MessageBundle::Create(catalogs_, &error));
+
+ return error;
+ }
+
+ void ClearDictionary() {
+ handler_->dictionary_.clear();
+ }
+
+ scoped_ptr<MessageBundle> handler_;
+ std::vector<linked_ptr<base::DictionaryValue> > catalogs_;
+};
+
+TEST_F(MessageBundleTest, ReservedMessagesCount) {
+ ASSERT_EQ(5U, ReservedMessagesCount());
+}
+
+TEST_F(MessageBundleTest, InitEmptyDictionaries) {
+ CreateMessageBundle();
+ EXPECT_TRUE(handler_.get() != NULL);
+ EXPECT_EQ(0U + ReservedMessagesCount(), handler_->size());
+ CheckReservedMessages(handler_.get());
+}
+
+TEST_F(MessageBundleTest, InitGoodDefaultDict) {
+ catalogs_.push_back(
+ linked_ptr<base::DictionaryValue>(CreateGoodDictionary()));
+ CreateMessageBundle();
+
+ EXPECT_TRUE(handler_.get() != NULL);
+ EXPECT_EQ(3U + ReservedMessagesCount(), handler_->size());
+
+ EXPECT_EQ("message1 A B", handler_->GetL10nMessage("n1"));
+ EXPECT_EQ("message2 C", handler_->GetL10nMessage("n2"));
+ EXPECT_EQ("message3", handler_->GetL10nMessage("n3"));
+ CheckReservedMessages(handler_.get());
+}
+
+TEST_F(MessageBundleTest, InitAppDictConsultedFirst) {
+ catalogs_.push_back(
+ linked_ptr<base::DictionaryValue>(CreateGoodDictionary()));
+ catalogs_.push_back(
+ linked_ptr<base::DictionaryValue>(CreateGoodDictionary()));
+
+ base::DictionaryValue* app_dict = catalogs_[0].get();
+ // Flip placeholders in message of n1 tree.
+ app_dict->SetString("n1.message", "message1 $b$ $a$");
+ // Remove one message from app dict.
+ app_dict->Remove("n2", NULL);
+ // Replace n3 with N3.
+ app_dict->Remove("n3", NULL);
+ CreateMessageTree("N3", "message3_app_dict", false, app_dict);
+
+ CreateMessageBundle();
+
+ EXPECT_TRUE(handler_.get() != NULL);
+ EXPECT_EQ(3U + ReservedMessagesCount(), handler_->size());
+
+ EXPECT_EQ("message1 B A", handler_->GetL10nMessage("n1"));
+ EXPECT_EQ("message2 C", handler_->GetL10nMessage("n2"));
+ EXPECT_EQ("message3_app_dict", handler_->GetL10nMessage("n3"));
+ CheckReservedMessages(handler_.get());
+}
+
+TEST_F(MessageBundleTest, InitBadAppDict) {
+ catalogs_.push_back(
+ linked_ptr<base::DictionaryValue>(CreateBadDictionary(INVALID_NAME)));
+ catalogs_.push_back(
+ linked_ptr<base::DictionaryValue>(CreateGoodDictionary()));
+
+ std::string error = CreateMessageBundle();
+
+ EXPECT_TRUE(handler_.get() == NULL);
+ EXPECT_EQ("Name of a key \"n 5\" is invalid. Only ASCII [a-z], "
+ "[A-Z], [0-9] and \"_\" are allowed.", error);
+
+ catalogs_[0].reset(CreateBadDictionary(NAME_NOT_A_TREE));
+ handler_.reset(MessageBundle::Create(catalogs_, &error));
+ EXPECT_TRUE(handler_.get() == NULL);
+ EXPECT_EQ("Not a valid tree for key n4.", error);
+
+ catalogs_[0].reset(CreateBadDictionary(EMPTY_NAME_TREE));
+ handler_.reset(MessageBundle::Create(catalogs_, &error));
+ EXPECT_TRUE(handler_.get() == NULL);
+ EXPECT_EQ("There is no \"message\" element for key n4.", error);
+
+ catalogs_[0].reset(CreateBadDictionary(MISSING_MESSAGE));
+ handler_.reset(MessageBundle::Create(catalogs_, &error));
+ EXPECT_TRUE(handler_.get() == NULL);
+ EXPECT_EQ("There is no \"message\" element for key n1.", error);
+
+ catalogs_[0].reset(CreateBadDictionary(PLACEHOLDER_NOT_A_TREE));
+ handler_.reset(MessageBundle::Create(catalogs_, &error));
+ EXPECT_TRUE(handler_.get() == NULL);
+ EXPECT_EQ("Not a valid \"placeholders\" element for key n1.", error);
+
+ catalogs_[0].reset(CreateBadDictionary(EMPTY_PLACEHOLDER_TREE));
+ handler_.reset(MessageBundle::Create(catalogs_, &error));
+ EXPECT_TRUE(handler_.get() == NULL);
+ EXPECT_EQ("Variable $a$ used but not defined.", error);
+
+ catalogs_[0].reset(CreateBadDictionary(CONTENT_MISSING));
+ handler_.reset(MessageBundle::Create(catalogs_, &error));
+ EXPECT_TRUE(handler_.get() == NULL);
+ EXPECT_EQ("Invalid \"content\" element for key n1.", error);
+
+ catalogs_[0].reset(CreateBadDictionary(MESSAGE_PLACEHOLDER_DOESNT_MATCH));
+ handler_.reset(MessageBundle::Create(catalogs_, &error));
+ EXPECT_TRUE(handler_.get() == NULL);
+ EXPECT_EQ("Variable $a$ used but not defined.", error);
+}
+
+TEST_F(MessageBundleTest, ReservedMessagesOverrideDeveloperMessages) {
+ catalogs_.push_back(
+ linked_ptr<base::DictionaryValue>(CreateGoodDictionary()));
+
+ base::DictionaryValue* dict = catalogs_[0].get();
+ CreateMessageTree(MessageBundle::kUILocaleKey, "x", false, dict);
+
+ std::string error = CreateMessageBundle();
+
+ EXPECT_TRUE(handler_.get() == NULL);
+ std::string expected_error = ErrorUtils::FormatErrorMessage(
+ errors::kReservedMessageFound, MessageBundle::kUILocaleKey);
+ EXPECT_EQ(expected_error, error);
+}
+
+TEST_F(MessageBundleTest, AppendReservedMessagesForLTR) {
+ CreateMessageBundle();
+
+ ASSERT_TRUE(handler_.get() != NULL);
+ ClearDictionary();
+ ASSERT_TRUE(AppendReservedMessages("en_US"));
+
+ EXPECT_EQ("en_US",
+ handler_->GetL10nMessage(MessageBundle::kUILocaleKey));
+ EXPECT_EQ("ltr", handler_->GetL10nMessage(
+ MessageBundle::kBidiDirectionKey));
+ EXPECT_EQ("rtl", handler_->GetL10nMessage(
+ MessageBundle::kBidiReversedDirectionKey));
+ EXPECT_EQ("left", handler_->GetL10nMessage(
+ MessageBundle::kBidiStartEdgeKey));
+ EXPECT_EQ("right", handler_->GetL10nMessage(
+ MessageBundle::kBidiEndEdgeKey));
+}
+
+TEST_F(MessageBundleTest, AppendReservedMessagesForRTL) {
+ CreateMessageBundle();
+
+ ASSERT_TRUE(handler_.get() != NULL);
+ ClearDictionary();
+ ASSERT_TRUE(AppendReservedMessages("he"));
+
+ EXPECT_EQ("he",
+ handler_->GetL10nMessage(MessageBundle::kUILocaleKey));
+ EXPECT_EQ("rtl", handler_->GetL10nMessage(
+ MessageBundle::kBidiDirectionKey));
+ EXPECT_EQ("ltr", handler_->GetL10nMessage(
+ MessageBundle::kBidiReversedDirectionKey));
+ EXPECT_EQ("right", handler_->GetL10nMessage(
+ MessageBundle::kBidiStartEdgeKey));
+ EXPECT_EQ("left", handler_->GetL10nMessage(
+ MessageBundle::kBidiEndEdgeKey));
+}
+
+TEST_F(MessageBundleTest, IsValidNameCheckValidCharacters) {
+ EXPECT_TRUE(MessageBundle::IsValidName(std::string("a__BV_9")));
+ EXPECT_TRUE(MessageBundle::IsValidName(std::string("@@a__BV_9")));
+ EXPECT_FALSE(MessageBundle::IsValidName(std::string("$a__BV_9$")));
+ EXPECT_FALSE(MessageBundle::IsValidName(std::string("a-BV-9")));
+ EXPECT_FALSE(MessageBundle::IsValidName(std::string("a#BV!9")));
+ EXPECT_FALSE(MessageBundle::IsValidName(std::string("a<b")));
+}
+
+struct ReplaceVariables {
+ const char* original;
+ const char* result;
+ const char* error;
+ const char* begin_delimiter;
+ const char* end_delimiter;
+ bool pass;
+};
+
+TEST(MessageBundle, ReplaceMessagesInText) {
+ const char* kMessageBegin = MessageBundle::kMessageBegin;
+ const char* kMessageEnd = MessageBundle::kMessageEnd;
+ const char* kPlaceholderBegin = MessageBundle::kPlaceholderBegin;
+ const char* kPlaceholderEnd = MessageBundle::kPlaceholderEnd;
+
+ static ReplaceVariables test_cases[] = {
+ // Message replacement.
+ { "This is __MSG_siMPle__ message", "This is simple message",
+ "", kMessageBegin, kMessageEnd, true },
+ { "This is __MSG_", "This is __MSG_",
+ "", kMessageBegin, kMessageEnd, true },
+ { "This is __MSG__simple__ message", "This is __MSG__simple__ message",
+ "Variable __MSG__simple__ used but not defined.",
+ kMessageBegin, kMessageEnd, false },
+ { "__MSG_LoNg__", "A pretty long replacement",
+ "", kMessageBegin, kMessageEnd, true },
+ { "A __MSG_SimpLE__MSG_ a", "A simpleMSG_ a",
+ "", kMessageBegin, kMessageEnd, true },
+ { "A __MSG_simple__MSG_long__", "A simpleMSG_long__",
+ "", kMessageBegin, kMessageEnd, true },
+ { "A __MSG_simple____MSG_long__", "A simpleA pretty long replacement",
+ "", kMessageBegin, kMessageEnd, true },
+ { "__MSG_d1g1ts_are_ok__", "I are d1g1t",
+ "", kMessageBegin, kMessageEnd, true },
+ // Placeholder replacement.
+ { "This is $sImpLe$ message", "This is simple message",
+ "", kPlaceholderBegin, kPlaceholderEnd, true },
+ { "This is $", "This is $",
+ "", kPlaceholderBegin, kPlaceholderEnd, true },
+ { "This is $$sIMPle$ message", "This is $simple message",
+ "", kPlaceholderBegin, kPlaceholderEnd, true },
+ { "$LONG_V$", "A pretty long replacement",
+ "", kPlaceholderBegin, kPlaceholderEnd, true },
+ { "A $simple$$ a", "A simple$ a",
+ "", kPlaceholderBegin, kPlaceholderEnd, true },
+ { "A $simple$long_v$", "A simplelong_v$",
+ "", kPlaceholderBegin, kPlaceholderEnd, true },
+ { "A $simple$$long_v$", "A simpleA pretty long replacement",
+ "", kPlaceholderBegin, kPlaceholderEnd, true },
+ { "This is $bad name$", "This is $bad name$",
+ "", kPlaceholderBegin, kPlaceholderEnd, true },
+ { "This is $missing$", "This is $missing$",
+ "Variable $missing$ used but not defined.",
+ kPlaceholderBegin, kPlaceholderEnd, false },
+ };
+
+ MessageBundle::SubstitutionMap messages;
+ messages.insert(std::make_pair("simple", "simple"));
+ messages.insert(std::make_pair("long", "A pretty long replacement"));
+ messages.insert(std::make_pair("long_v", "A pretty long replacement"));
+ messages.insert(std::make_pair("bad name", "Doesn't matter"));
+ messages.insert(std::make_pair("d1g1ts_are_ok", "I are d1g1t"));
+
+ for (size_t i = 0; i < arraysize(test_cases); ++i) {
+ std::string text = test_cases[i].original;
+ std::string error;
+ EXPECT_EQ(test_cases[i].pass,
+ MessageBundle::ReplaceVariables(messages,
+ test_cases[i].begin_delimiter,
+ test_cases[i].end_delimiter,
+ &text,
+ &error));
+ EXPECT_EQ(test_cases[i].result, text);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Renderer helper functions test.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(GetExtensionToL10nMessagesMapTest, ReturnsTheSameObject) {
+ ExtensionToL10nMessagesMap* map1 = GetExtensionToL10nMessagesMap();
+ ASSERT_TRUE(NULL != map1);
+
+ ExtensionToL10nMessagesMap* map2 = GetExtensionToL10nMessagesMap();
+ ASSERT_EQ(map1, map2);
+}
+
+TEST(GetExtensionToL10nMessagesMapTest, ReturnsNullForUnknownExtensionId) {
+ const std::string extension_id("some_unique_12334212314234_id");
+ L10nMessagesMap* map = GetL10nMessagesMap(extension_id);
+ EXPECT_TRUE(NULL == map);
+}
+
+TEST(GetExtensionToL10nMessagesMapTest, ReturnsMapForKnownExtensionId) {
+ const std::string extension_id("some_unique_121212121212121_id");
+ // Store a map for given id.
+ L10nMessagesMap messages;
+ messages.insert(std::make_pair("message_name", "message_value"));
+ (*GetExtensionToL10nMessagesMap())[extension_id] = messages;
+
+ L10nMessagesMap* map = GetL10nMessagesMap(extension_id);
+ ASSERT_TRUE(NULL != map);
+ EXPECT_EQ(1U, map->size());
+ EXPECT_EQ("message_value", (*map)["message_name"]);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/mojo/keep_alive.mojom b/chromium/extensions/common/mojo/keep_alive.mojom
new file mode 100644
index 00000000000..9f36ae6c26e
--- /dev/null
+++ b/chromium/extensions/common/mojo/keep_alive.mojom
@@ -0,0 +1,10 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module extensions;
+
+// An RAII service for keep alives. While KeepAlive services for an extension
+// remain, the background page for that extension will remain alive.
+interface KeepAlive {
+};
diff --git a/chromium/extensions/common/mojo/stash.mojom b/chromium/extensions/common/mojo/stash.mojom
new file mode 100644
index 00000000000..5c1e68bd169
--- /dev/null
+++ b/chromium/extensions/common/mojo/stash.mojom
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module extensions;
+
+// A stashed client object. This contains the serialized bytes and handles,
+// stored in |data| and |handles|, respectively, of a mojo struct of the
+// serialized representation of the client object.
+struct StashedObject {
+ // A client identifier for this stashed object. This is to allow the client to
+ // reconstitute the correct serialization struct from this StashedObject.
+ string id;
+
+ // The serialized data of the serialization struct.
+ array<uint8> data;
+
+ // The handles contained within the serialization struct.
+ array<handle> stashed_handles;
+
+ // Whether to monitor |stashed_handles| and relaunch the client when a handle
+ // becomes readable.
+ bool monitor_handles = false;
+};
+
+interface StashService {
+ // Adds |stashed_objects| to the stash. StashedObjects are stored in memory in
+ // the browser process for as long as the extension that owns them remains
+ // enabled and the browser process runs.
+ AddToStash(array<StashedObject> stashed_objects);
+
+ // Returns all stashed objects.
+ RetrieveStash() => (array<StashedObject> stash);
+};
diff --git a/chromium/extensions/common/mojo/wifi_display_session_service.mojom b/chromium/extensions/common/mojo/wifi_display_session_service.mojom
new file mode 100644
index 00000000000..f664a5fb8db
--- /dev/null
+++ b/chromium/extensions/common/mojo/wifi_display_session_service.mojom
@@ -0,0 +1,45 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module extensions;
+
+// WiFiDisplaySessionService class provides access to the network for
+// the render-hosted Wi-Fi Display session.
+interface WiFiDisplaySessionService {
+ SetClient(WiFiDisplaySessionServiceClient client);
+
+ // Requires connection to a sink using the given authentication information.
+ // Note: 'auth_method' values must correspond to 'enum AuthenticationMethod'
+ // from display_source.idl
+ Connect(int32 sink_id, int32 auth_method, string auth_data);
+
+ // Drops the established connection to the connected sink.
+ Disconnect();
+
+ // Sends a controlling mesage to the connected sink.
+ SendMessage(string message);
+};
+
+interface WiFiDisplaySessionServiceClient {
+ // Notification of a successfull connection to a sink.
+ OnConnected(string ip_address);
+
+ // Notification of a handled connection request.
+ OnConnectRequestHandled(bool success, string error_message);
+
+ // Notification of a session termination.
+ OnTerminated();
+
+ // Notification of a handled termination request.
+ OnDisconnectRequestHandled(bool success, string error_message);
+
+ // Notification of an error occurred during the session.
+ // Note: 'type' values must correspond to 'enum ErrorType'
+ // from display_source.idl
+ OnError(int32 type, string description);
+
+ // Invoked to transmit a controlling message from
+ // the connected sink.
+ OnMessage(string data);
+};
diff --git a/chromium/extensions/common/one_shot_event.cc b/chromium/extensions/common/one_shot_event.cc
new file mode 100644
index 00000000000..a61cac8a218
--- /dev/null
+++ b/chromium/extensions/common/one_shot_event.cc
@@ -0,0 +1,102 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/one_shot_event.h"
+
+#include <stddef.h>
+
+#include "base/callback.h"
+#include "base/lazy_instance.h"
+#include "base/location.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/time/time.h"
+
+using base::SingleThreadTaskRunner;
+
+namespace extensions {
+
+struct OneShotEvent::TaskInfo {
+ TaskInfo() {}
+ TaskInfo(const tracked_objects::Location& from_here,
+ const scoped_refptr<SingleThreadTaskRunner>& runner,
+ const base::Closure& task,
+ const base::TimeDelta& delay)
+ : from_here(from_here), runner(runner), task(task), delay(delay) {
+ CHECK(runner.get()); // Detect mistakes with a decent stack frame.
+ }
+ tracked_objects::Location from_here;
+ scoped_refptr<SingleThreadTaskRunner> runner;
+ base::Closure task;
+ base::TimeDelta delay;
+};
+
+OneShotEvent::OneShotEvent() : signaled_(false) {
+ // It's acceptable to construct the OneShotEvent on one thread, but
+ // immediately move it to another thread.
+ thread_checker_.DetachFromThread();
+}
+OneShotEvent::OneShotEvent(bool signaled) : signaled_(signaled) {
+ thread_checker_.DetachFromThread();
+}
+OneShotEvent::~OneShotEvent() {}
+
+void OneShotEvent::Post(const tracked_objects::Location& from_here,
+ const base::Closure& task) const {
+ PostImpl(from_here, task, base::ThreadTaskRunnerHandle::Get(),
+ base::TimeDelta());
+}
+
+void OneShotEvent::Post(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ const scoped_refptr<SingleThreadTaskRunner>& runner) const {
+ PostImpl(from_here, task, runner, base::TimeDelta());
+}
+
+void OneShotEvent::PostDelayed(const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ const base::TimeDelta& delay) const {
+ PostImpl(from_here, task, base::ThreadTaskRunnerHandle::Get(), delay);
+}
+
+void OneShotEvent::Signal() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ CHECK(!signaled_) << "Only call Signal once.";
+
+ signaled_ = true;
+ // After this point, a call to Post() from one of the queued tasks
+ // could proceed immediately, but the fact that this object is
+ // single-threaded prevents that from being relevant.
+
+ // We could randomize tasks_ in debug mode in order to check that
+ // the order doesn't matter...
+ for (size_t i = 0; i < tasks_.size(); ++i) {
+ const TaskInfo& task = tasks_[i];
+ if (task.delay != base::TimeDelta())
+ task.runner->PostDelayedTask(task.from_here, task.task, task.delay);
+ else
+ task.runner->PostTask(task.from_here, task.task);
+ }
+}
+
+void OneShotEvent::PostImpl(const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ const scoped_refptr<SingleThreadTaskRunner>& runner,
+ const base::TimeDelta& delay) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (is_signaled()) {
+ if (delay != base::TimeDelta())
+ runner->PostDelayedTask(from_here, task, delay);
+ else
+ runner->PostTask(from_here, task);
+ } else {
+ tasks_.push_back(TaskInfo(from_here, runner, task, delay));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/one_shot_event.h b/chromium/extensions/common/one_shot_event.h
new file mode 100644
index 00000000000..af985384ed7
--- /dev/null
+++ b/chromium/extensions/common/one_shot_event.h
@@ -0,0 +1,114 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_ONE_SHOT_EVENT_H_
+#define EXTENSIONS_COMMON_ONE_SHOT_EVENT_H_
+
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+class TimeDelta;
+}
+
+namespace tracked_objects {
+class Location;
+}
+
+namespace extensions {
+
+// This class represents an event that's expected to happen once. It
+// allows clients to guarantee that code is run after the OneShotEvent
+// is signaled. If the OneShotEvent is destroyed before it's
+// signaled, the delayed closures are destroyed without being run.
+//
+// This class is similar to a WaitableEvent combined with several
+// WaitableEventWatchers, but using it is simpler.
+//
+// This class is not thread-safe, and must be used from a single thread.
+class OneShotEvent {
+ public:
+ OneShotEvent();
+ // Use the following constructor to create an already signaled event. This is
+ // useful if you construct the event on a different thread from where it is
+ // used, in which case it is not possible to call Signal() just after
+ // construction.
+ explicit OneShotEvent(bool signaled);
+ ~OneShotEvent();
+
+ // True if Signal has been called. This function is mostly for
+ // migrating old code; usually calling Post() unconditionally will
+ // result in more readable code.
+ bool is_signaled() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return signaled_;
+ }
+
+ // Causes is_signaled() to return true and all queued tasks to be
+ // run in an arbitrary order. This method must only be called once.
+ void Signal();
+
+ // Scheduled |task| to be called on |runner| after is_signaled()
+ // becomes true. If called with |delay|, then the task will happen
+ // (roughly) |delay| after is_signaled(), *not* |delay| after the
+ // post. Inside |task|, if this OneShotEvent is still alive,
+ // CHECK(is_signaled()) will never fail (which implies that
+ // OneShotEvent::Reset() doesn't exist).
+ //
+ // If |*this| is destroyed before being released, none of these
+ // tasks will be executed.
+ //
+ // Omitting the |runner| argument indicates that |task| should run
+ // on current thread's TaskRunner.
+ //
+ // Tasks may be run in an arbitrary order, not just FIFO. Tasks
+ // will never be called on the current thread before this function
+ // returns. Beware that there's no simple way to wait for all tasks
+ // on a OneShotEvent to complete, so it's almost never safe to use
+ // base::Unretained() when creating one.
+ //
+ // Const because Post() doesn't modify the logical state of this
+ // object (which is just the is_signaled() bit).
+ void Post(const tracked_objects::Location& from_here,
+ const base::Closure& task) const;
+ void Post(const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ const scoped_refptr<base::SingleThreadTaskRunner>& runner) const;
+ void PostDelayed(const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ const base::TimeDelta& delay) const;
+
+ private:
+ struct TaskInfo;
+
+ void PostImpl(const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ const scoped_refptr<base::SingleThreadTaskRunner>& runner,
+ const base::TimeDelta& delay) const;
+
+ base::ThreadChecker thread_checker_;
+
+ bool signaled_;
+
+ // The task list is mutable because it's not part of the logical
+ // state of the object. This lets us return const references to the
+ // OneShotEvent to clients that just want to run tasks through it
+ // without worrying that they'll signal the event.
+ //
+ // Optimization note: We could reduce the size of this class to a
+ // single pointer by storing |signaled_| in the low bit of a
+ // pointer, and storing the size and capacity of the array (if any)
+ // on the far end of the pointer.
+ mutable std::vector<TaskInfo> tasks_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_ONE_SHOT_EVENT_H_
diff --git a/chromium/extensions/common/one_shot_event_unittest.cc b/chromium/extensions/common/one_shot_event_unittest.cc
new file mode 100644
index 00000000000..c19b036609e
--- /dev/null
+++ b/chromium/extensions/common/one_shot_event_unittest.cc
@@ -0,0 +1,112 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/one_shot_event.h"
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/test/test_simple_task_runner.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+void Increment(int* i) { ++*i; }
+
+TEST(OneShotEventTest, RecordsSignal) {
+ OneShotEvent event;
+ EXPECT_FALSE(event.is_signaled());
+ event.Signal();
+ EXPECT_TRUE(event.is_signaled());
+}
+
+TEST(OneShotEventTest, CallsQueue) {
+ OneShotEvent event;
+ scoped_refptr<base::TestSimpleTaskRunner> runner(
+ new base::TestSimpleTaskRunner);
+ int i = 0;
+ event.Post(FROM_HERE, base::Bind(&Increment, &i), runner);
+ event.Post(FROM_HERE, base::Bind(&Increment, &i), runner);
+ EXPECT_EQ(0U, runner->GetPendingTasks().size());
+ event.Signal();
+ ASSERT_EQ(2U, runner->GetPendingTasks().size());
+ EXPECT_NE(runner->GetPendingTasks()[0].location.line_number(),
+ runner->GetPendingTasks()[1].location.line_number())
+ << "Make sure FROM_HERE is propagated.";
+ EXPECT_EQ(0, i);
+ runner->RunPendingTasks();
+ EXPECT_EQ(2, i);
+}
+
+TEST(OneShotEventTest, CallsAfterSignalDontRunInline) {
+ OneShotEvent event;
+ scoped_refptr<base::TestSimpleTaskRunner> runner(
+ new base::TestSimpleTaskRunner);
+ int i = 0;
+
+ event.Signal();
+ event.Post(FROM_HERE, base::Bind(&Increment, &i), runner);
+ EXPECT_EQ(1U, runner->GetPendingTasks().size());
+ EXPECT_EQ(0, i);
+ runner->RunPendingTasks();
+ EXPECT_EQ(1, i);
+}
+
+TEST(OneShotEventTest, PostDefaultsToCurrentMessageLoop) {
+ OneShotEvent event;
+ scoped_refptr<base::TestSimpleTaskRunner> runner(
+ new base::TestSimpleTaskRunner);
+ base::MessageLoop loop;
+ int runner_i = 0;
+ int loop_i = 0;
+
+ event.Post(FROM_HERE, base::Bind(&Increment, &runner_i), runner);
+ event.Post(FROM_HERE, base::Bind(&Increment, &loop_i));
+ event.Signal();
+ EXPECT_EQ(1U, runner->GetPendingTasks().size());
+ EXPECT_EQ(0, runner_i);
+ runner->RunPendingTasks();
+ EXPECT_EQ(1, runner_i);
+ EXPECT_EQ(0, loop_i);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, loop_i);
+}
+
+void CheckSignaledAndPostIncrement(
+ OneShotEvent* event,
+ const scoped_refptr<base::SingleThreadTaskRunner>& runner,
+ int* i) {
+ EXPECT_TRUE(event->is_signaled());
+ event->Post(FROM_HERE, base::Bind(&Increment, i), runner);
+}
+
+TEST(OneShotEventTest, IsSignaledAndPostsFromCallbackWork) {
+ OneShotEvent event;
+ scoped_refptr<base::TestSimpleTaskRunner> runner(
+ new base::TestSimpleTaskRunner);
+ int i = 0;
+
+ event.Post(FROM_HERE,
+ base::Bind(&CheckSignaledAndPostIncrement, &event, runner, &i),
+ runner);
+ EXPECT_EQ(0, i);
+ event.Signal();
+
+ // CheckSignaledAndPostIncrement is queued on |runner|.
+ EXPECT_EQ(1U, runner->GetPendingTasks().size());
+ EXPECT_EQ(0, i);
+ runner->RunPendingTasks();
+ // Increment is queued on |runner|.
+ EXPECT_EQ(1U, runner->GetPendingTasks().size());
+ EXPECT_EQ(0, i);
+ runner->RunPendingTasks();
+ // Increment has run.
+ EXPECT_EQ(0U, runner->GetPendingTasks().size());
+ EXPECT_EQ(1, i);
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/OWNERS b/chromium/extensions/common/permissions/OWNERS
new file mode 100644
index 00000000000..1b2e1c51680
--- /dev/null
+++ b/chromium/extensions/common/permissions/OWNERS
@@ -0,0 +1,2 @@
+rdevlin.cronin@chromium.org
+treib@chromium.org
diff --git a/chromium/extensions/common/permissions/PRESUBMIT.py b/chromium/extensions/common/permissions/PRESUBMIT.py
new file mode 100644
index 00000000000..73eaaae820b
--- /dev/null
+++ b/chromium/extensions/common/permissions/PRESUBMIT.py
@@ -0,0 +1,29 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Chromium presubmit script for src/extensions/common/permissions.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details on the presubmit API built into depot_tools.
+"""
+import sys
+
+def _CreateAPIPermissionIDChecker(input_api, output_api):
+ original_sys_path = sys.path
+
+ try:
+ sys.path.append(input_api.os_path.join(
+ input_api.PresubmitLocalPath(), '..', '..', '..', 'tools',
+ 'strict_enum_value_checker'))
+ from strict_enum_value_checker import StrictEnumValueChecker
+ finally:
+ sys.path = original_sys_path
+
+ return StrictEnumValueChecker(input_api, output_api,
+ start_marker=' enum ID {', end_marker=' // Last entry:',
+ path='extensions/common/permissions/api_permission.h')
+
+def CheckChangeOnUpload(input_api, output_api):
+ return _CreateAPIPermissionIDChecker(input_api, output_api).Run()
+
diff --git a/chromium/extensions/common/permissions/api_permission.cc b/chromium/extensions/common/permissions/api_permission.cc
new file mode 100644
index 00000000000..77a13172cdc
--- /dev/null
+++ b/chromium/extensions/common/permissions/api_permission.cc
@@ -0,0 +1,118 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/api_permission.h"
+
+#include "extensions/common/permissions/api_permission_set.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+
+using extensions::APIPermission;
+using extensions::APIPermissionInfo;
+
+class SimpleAPIPermission : public APIPermission {
+ public:
+ explicit SimpleAPIPermission(const APIPermissionInfo* permission)
+ : APIPermission(permission) { }
+
+ ~SimpleAPIPermission() override {}
+
+ extensions::PermissionIDSet GetPermissions() const override {
+ extensions::PermissionIDSet permissions;
+ permissions.insert(id());
+ return permissions;
+ }
+
+ bool Check(const APIPermission::CheckParam* param) const override {
+ return !param;
+ }
+
+ bool Contains(const APIPermission* rhs) const override {
+ CHECK_EQ(info(), rhs->info());
+ return true;
+ }
+
+ bool Equal(const APIPermission* rhs) const override {
+ if (this != rhs)
+ CHECK_EQ(info(), rhs->info());
+ return true;
+ }
+
+ bool FromValue(const base::Value* value,
+ std::string* /*error*/,
+ std::vector<std::string>* /*unhandled_permissions*/) override {
+ return (value == NULL);
+ }
+
+ scoped_ptr<base::Value> ToValue() const override {
+ return scoped_ptr<base::Value>();
+ }
+
+ APIPermission* Clone() const override {
+ return new SimpleAPIPermission(info());
+ }
+
+ APIPermission* Diff(const APIPermission* rhs) const override {
+ CHECK_EQ(info(), rhs->info());
+ return NULL;
+ }
+
+ APIPermission* Union(const APIPermission* rhs) const override {
+ CHECK_EQ(info(), rhs->info());
+ return new SimpleAPIPermission(info());
+ }
+
+ APIPermission* Intersect(const APIPermission* rhs) const override {
+ CHECK_EQ(info(), rhs->info());
+ return new SimpleAPIPermission(info());
+ }
+
+ void Write(base::Pickle* m) const override {}
+
+ bool Read(const base::Pickle* m, base::PickleIterator* iter) override {
+ return true;
+ }
+
+ void Log(std::string* log) const override {}
+};
+
+} // namespace
+
+namespace extensions {
+
+APIPermission::APIPermission(const APIPermissionInfo* info)
+ : info_(info) {
+ DCHECK(info_);
+}
+
+APIPermission::~APIPermission() { }
+
+APIPermission::ID APIPermission::id() const {
+ return info()->id();
+}
+
+const char* APIPermission::name() const {
+ return info()->name();
+}
+
+//
+// APIPermissionInfo
+//
+
+APIPermissionInfo::APIPermissionInfo(const APIPermissionInfo::InitInfo& info)
+ : id_(info.id),
+ name_(info.name),
+ flags_(info.flags),
+ api_permission_constructor_(info.constructor) {
+}
+
+APIPermissionInfo::~APIPermissionInfo() { }
+
+APIPermission* APIPermissionInfo::CreateAPIPermission() const {
+ return api_permission_constructor_ ?
+ api_permission_constructor_(this) : new SimpleAPIPermission(this);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/api_permission.h b/chromium/extensions/common/permissions/api_permission.h
new file mode 100644
index 00000000000..f6903626f4a
--- /dev/null
+++ b/chromium/extensions/common/permissions/api_permission.h
@@ -0,0 +1,439 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_API_PERMISSION_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_API_PERMISSION_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/pickle.h"
+#include "base/values.h"
+
+namespace IPC {
+class Message;
+}
+
+namespace extensions {
+
+class PermissionIDSet;
+class APIPermissionInfo;
+class ChromeAPIPermissions;
+
+// APIPermission is for handling some complex permissions. Please refer to
+// extensions::SocketPermission as an example.
+// There is one instance per permission per loaded extension.
+class APIPermission {
+ public:
+ // The IDs of all permissions available to apps. Add as many permissions here
+ // as needed to generate meaningful permission messages. Add the rules for the
+ // messages to ChromePermissionMessageProvider.
+ // Do not reorder this enumeration or remove any entries. If you need to add a
+ // new entry, add it just prior to kEnumBoundary, and ensure to update the
+ // "ExtensionPermission3" enum in tools/metrics/histograms/histograms.xml
+ // (by running update_extension_permission.py).
+ // TODO(sashab): Move this to a more central location, and rename it to
+ // PermissionID.
+ enum ID {
+ // Error codes.
+ kInvalid,
+ kUnknown,
+
+ // Actual permission IDs. Not all of these are valid permissions on their
+ // own; some are just needed by various manifest permissions to represent
+ // their permission message rule combinations.
+ kAccessibilityFeaturesModify,
+ kAccessibilityFeaturesRead,
+ kAccessibilityPrivate,
+ kActiveTab,
+ kActivityLogPrivate,
+ kAlarms,
+ kAlphaEnabled,
+ kAlwaysOnTopWindows,
+ kAppView,
+ kAudio,
+ kAudioCapture,
+ kAudioModem,
+ kAutofillPrivate,
+ kAutomation,
+ kAutoTestPrivate,
+ kBackground,
+ kBluetoothPrivate,
+ kBookmark,
+ kBookmarkManagerPrivate,
+ kBrailleDisplayPrivate,
+ kBrowser,
+ kBrowsingData,
+ kCast,
+ kCastStreaming,
+ kChromeosInfoPrivate,
+ kClipboardRead,
+ kClipboardWrite,
+ kCloudPrintPrivate,
+ kCommandLinePrivate,
+ kCommandsAccessibility,
+ kContentSettings,
+ kContextMenus,
+ kCookie,
+ kCopresence,
+ kCopresencePrivate,
+ kCryptotokenPrivate,
+ kDataReductionProxy,
+ kDiagnostics,
+ kDial,
+ kDebugger,
+ kDeclarative,
+ kDeclarativeContent,
+ kDeclarativeWebRequest,
+ kDesktopCapture,
+ kDesktopCapturePrivate,
+ kDeveloperPrivate,
+ kDevtools,
+ kDns,
+ kDocumentScan,
+ kDownloads,
+ kDownloadsInternal,
+ kDownloadsOpen,
+ kDownloadsShelf,
+ kEasyUnlockPrivate,
+ kEchoPrivate,
+ kEmbeddedExtensionOptions,
+ kEnterprisePlatformKeys,
+ kEnterprisePlatformKeysPrivate,
+ kExperienceSamplingPrivate,
+ kExperimental,
+ kExtensionView,
+ kExternallyConnectableAllUrls,
+ kFeedbackPrivate,
+ kFileBrowserHandler,
+ kFileBrowserHandlerInternal,
+ kFileManagerPrivate,
+ kFileSystem,
+ kFileSystemDirectory,
+ kFileSystemProvider,
+ kFileSystemRequestFileSystem,
+ kFileSystemRetainEntries,
+ kFileSystemWrite,
+ kDeleted_FileSystemWriteDirectory,
+ kFirstRunPrivate,
+ kFontSettings,
+ kFullscreen,
+ kGcdPrivate,
+ kGcm,
+ kGeolocation,
+ kHid,
+ kHistory,
+ kHomepage,
+ kHotwordPrivate,
+ kIdentity,
+ kIdentityEmail,
+ kIdentityPrivate,
+ kIdltest,
+ kIdle,
+ kImeWindowEnabled,
+ kInlineInstallPrivate,
+ kInput,
+ kInputMethodPrivate,
+ kDeleted_InterceptAllKeys,
+ kLauncherSearchProvider,
+ kLocation,
+ kLogPrivate,
+ kManagement,
+ kMediaGalleries,
+ kMediaPlayerPrivate,
+ kMediaRouterPrivate,
+ kMetricsPrivate,
+ kMDns,
+ kMusicManagerPrivate,
+ kNativeMessaging,
+ kNetworkingConfig,
+ kNetworkingPrivate,
+ kNotificationProvider,
+ kNotifications,
+ kOverrideEscFullscreen,
+ kPageCapture,
+ kPointerLock,
+ kPlatformKeys,
+ kPlugin,
+ kPower,
+ kPreferencesPrivate,
+ kDeleted_PrincipalsPrivate,
+ kPrinterProvider,
+ kPrivacy,
+ kProcesses,
+ kProxy,
+ kImageWriterPrivate,
+ kDeleted_ReadingListPrivate,
+ kRtcPrivate,
+ kSearchProvider,
+ kSearchEnginesPrivate,
+ kSerial,
+ kSessions,
+ kSettingsPrivate,
+ kSignedInDevices,
+ kSocket,
+ kStartupPages,
+ kStorage,
+ kStreamsPrivate,
+ kSyncFileSystem,
+ kSystemPrivate,
+ kSystemDisplay,
+ kSystemStorage,
+ kTab,
+ kTabCapture,
+ kTabCaptureForTab,
+ kTerminalPrivate,
+ kTopSites,
+ kTts,
+ kTtsEngine,
+ kUnlimitedStorage,
+ kU2fDevices,
+ kUsb,
+ kUsbDevice,
+ kVideoCapture,
+ kVirtualKeyboardPrivate,
+ kVpnProvider,
+ kWallpaper,
+ kWallpaperPrivate,
+ kWebcamPrivate,
+ kWebConnectable, // for externally_connectable manifest key
+ kWebNavigation,
+ kWebRequest,
+ kWebRequestBlocking,
+ kWebrtcAudioPrivate,
+ kWebrtcDesktopCapturePrivate,
+ kWebrtcLoggingPrivate,
+ kWebstorePrivate,
+ kWebstoreWidgetPrivate,
+ kWebView,
+ kWindowShape,
+ kScreenlockPrivate,
+ kSystemCpu,
+ kSystemMemory,
+ kSystemNetwork,
+ kSystemInfoCpu,
+ kSystemInfoMemory,
+ kBluetooth,
+ kBluetoothDevices,
+ kFavicon,
+ kFullAccess,
+ kHostReadOnly,
+ kHostReadWrite,
+ kHostsAll,
+ kHostsAllReadOnly,
+ kMediaGalleriesAllGalleriesCopyTo,
+ kMediaGalleriesAllGalleriesDelete,
+ kMediaGalleriesAllGalleriesRead,
+ kNetworkState,
+ kOverrideBookmarksUI,
+ kShouldWarnAllHosts,
+ kSocketAnyHost,
+ kSocketDomainHosts,
+ kSocketSpecificHosts,
+ kDeleted_UsbDeviceList,
+ kUsbDeviceUnknownProduct,
+ kUsbDeviceUnknownVendor,
+ kUsersPrivate,
+ kPasswordsPrivate,
+ kLanguageSettingsPrivate,
+ kEnterpriseDeviceAttributes,
+ kCertificateProvider,
+ kResourcesPrivate,
+ kDisplaySource,
+ // Last entry: Add new entries above and ensure to update the
+ // "ExtensionPermission3" enum in tools/metrics/histograms/histograms.xml
+ // (by running update_extension_permission.py).
+ kEnumBoundary
+ };
+
+ struct CheckParam {
+ };
+
+ explicit APIPermission(const APIPermissionInfo* info);
+
+ virtual ~APIPermission();
+
+ // Returns the id of this permission.
+ ID id() const;
+
+ // Returns the name of this permission.
+ const char* name() const;
+
+ // Returns the APIPermission of this permission.
+ const APIPermissionInfo* info() const {
+ return info_;
+ }
+
+ // The set of permissions an app/extension with this API permission has. These
+ // permissions are used by PermissionMessageProvider to generate meaningful
+ // permission messages for the app/extension.
+ //
+ // For simple API permissions, this will return a set containing only the ID
+ // of the permission. More complex permissions might have multiple IDs, one
+ // for each of the capabilities the API permission has (e.g. read, write and
+ // copy, in the case of the media gallery permission). Permissions that
+ // require parameters may also contain a parameter string (along with the
+ // permission's ID) which can be substituted into the permission message if a
+ // rule is defined to do so.
+ //
+ // Permissions with multiple values, such as host permissions, are represented
+ // by multiple entries in this set. Each permission in the subset has the same
+ // ID (e.g. kHostReadOnly) but a different parameter (e.g. google.com). These
+ // are grouped to form different kinds of permission messages (e.g. 'Access to
+ // 2 hosts') depending on the number that are in the set. The rules that
+ // define the grouping of related permissions with the same ID is defined in
+ // ChromePermissionMessageProvider.
+ virtual PermissionIDSet GetPermissions() const = 0;
+
+ // Returns true if the given permission is allowed.
+ virtual bool Check(const CheckParam* param) const = 0;
+
+ // Returns true if |rhs| is a subset of this.
+ virtual bool Contains(const APIPermission* rhs) const = 0;
+
+ // Returns true if |rhs| is equal to this.
+ virtual bool Equal(const APIPermission* rhs) const = 0;
+
+ // Parses the APIPermission from |value|. Returns false if an error happens
+ // and optionally set |error| if |error| is not NULL. If |value| represents
+ // multiple permissions, some are invalid, and |unhandled_permissions| is
+ // not NULL, the invalid ones are put into |unhandled_permissions| and the
+ // function returns true.
+ virtual bool FromValue(const base::Value* value,
+ std::string* error,
+ std::vector<std::string>* unhandled_permissions) = 0;
+
+ // Stores this into a new created |value|.
+ virtual scoped_ptr<base::Value> ToValue() const = 0;
+
+ // Clones this.
+ virtual APIPermission* Clone() const = 0;
+
+ // Returns a new API permission which equals this - |rhs|.
+ virtual APIPermission* Diff(const APIPermission* rhs) const = 0;
+
+ // Returns a new API permission which equals the union of this and |rhs|.
+ virtual APIPermission* Union(const APIPermission* rhs) const = 0;
+
+ // Returns a new API permission which equals the intersect of this and |rhs|.
+ virtual APIPermission* Intersect(const APIPermission* rhs) const = 0;
+
+ // IPC functions
+ // Writes this into the given IPC message |m|.
+ virtual void Write(base::Pickle* m) const = 0;
+
+ // Reads from the given IPC message |m|.
+ virtual bool Read(const base::Pickle* m, base::PickleIterator* iter) = 0;
+
+ // Logs this permission.
+ virtual void Log(std::string* log) const = 0;
+
+ private:
+ const APIPermissionInfo* const info_;
+};
+
+
+// The APIPermissionInfo is an immutable class that describes a single
+// named permission (API permission).
+// There is one instance per permission.
+class APIPermissionInfo {
+ public:
+ enum Flag {
+ kFlagNone = 0,
+
+ // Indicates if the permission implies full access (native code).
+ kFlagImpliesFullAccess = 1 << 0,
+
+ // Indicates if the permission implies full URL access.
+ kFlagImpliesFullURLAccess = 1 << 1,
+
+ // Indicates that extensions cannot specify the permission as optional.
+ kFlagCannotBeOptional = 1 << 3,
+
+ // Indicates that the permission is internal to the extensions
+ // system and cannot be specified in the "permissions" list.
+ kFlagInternal = 1 << 4,
+
+ // Indicates that the permission may be granted to web contents by
+ // extensions using the content_capabilities manifest feature.
+ kFlagSupportsContentCapabilities = 1 << 5,
+ };
+
+ typedef APIPermission* (*APIPermissionConstructor)(const APIPermissionInfo*);
+
+ typedef std::set<APIPermission::ID> IDSet;
+
+ ~APIPermissionInfo();
+
+ // Creates a APIPermission instance.
+ APIPermission* CreateAPIPermission() const;
+
+ int flags() const { return flags_; }
+
+ APIPermission::ID id() const { return id_; }
+
+ // Returns the name of this permission.
+ const char* name() const { return name_; }
+
+ // Returns true if this permission implies full access (e.g., native code).
+ bool implies_full_access() const {
+ return (flags_ & kFlagImpliesFullAccess) != 0;
+ }
+
+ // Returns true if this permission implies full URL access.
+ bool implies_full_url_access() const {
+ return (flags_ & kFlagImpliesFullURLAccess) != 0;
+ }
+
+ // Returns true if this permission can be added and removed via the
+ // optional permissions extension API.
+ bool supports_optional() const {
+ return (flags_ & kFlagCannotBeOptional) == 0;
+ }
+
+ // Returns true if this permission is internal rather than a
+ // "permissions" list entry.
+ bool is_internal() const {
+ return (flags_ & kFlagInternal) != 0;
+ }
+
+ // Returns true if this permission can be granted to web contents by an
+ // extension through the content_capabilities manifest feature.
+ bool supports_content_capabilities() const {
+ return (flags_ & kFlagSupportsContentCapabilities) != 0;
+ }
+
+ private:
+ // Instances should only be constructed from within a PermissionsProvider.
+ friend class ChromeAPIPermissions;
+ friend class ExtensionsAPIPermissions;
+ // Implementations of APIPermission will want to get the permission message,
+ // but this class's implementation should be hidden from everyone else.
+ friend class APIPermission;
+
+ // This exists to allow aggregate initialization, so that default values
+ // for flags, etc. can be omitted.
+ // TODO(yoz): Simplify the way initialization is done. APIPermissionInfo
+ // should be the simple data struct.
+ struct InitInfo {
+ APIPermission::ID id;
+ const char* name;
+ int flags;
+ APIPermissionInfo::APIPermissionConstructor constructor;
+ };
+
+ explicit APIPermissionInfo(const InitInfo& info);
+
+ const APIPermission::ID id_;
+ const char* const name_;
+ const int flags_;
+ const APIPermissionConstructor api_permission_constructor_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_API_PERMISSION_H_
diff --git a/chromium/extensions/common/permissions/api_permission_set.cc b/chromium/extensions/common/permissions/api_permission_set.cc
new file mode 100644
index 00000000000..0e48d2f4933
--- /dev/null
+++ b/chromium/extensions/common/permissions/api_permission_set.cc
@@ -0,0 +1,312 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/api_permission_set.h"
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/permissions/permissions_info.h"
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+
+namespace {
+
+// Helper object that is implicitly constructible from both a PermissionID and
+// from an APIPermission::ID.
+struct PermissionIDCompareHelper {
+ PermissionIDCompareHelper(const PermissionID& id) : id(id.id()) {}
+ PermissionIDCompareHelper(const APIPermission::ID id) : id(id) {}
+
+ APIPermission::ID id;
+};
+
+bool CreateAPIPermission(
+ const std::string& permission_str,
+ const base::Value* permission_value,
+ APIPermissionSet::ParseSource source,
+ APIPermissionSet* api_permissions,
+ base::string16* error,
+ std::vector<std::string>* unhandled_permissions) {
+
+ const APIPermissionInfo* permission_info =
+ PermissionsInfo::GetInstance()->GetByName(permission_str);
+ if (permission_info) {
+ scoped_ptr<APIPermission> permission(
+ permission_info->CreateAPIPermission());
+ if (source != APIPermissionSet::kAllowInternalPermissions &&
+ permission_info->is_internal()) {
+ // An internal permission specified in permissions list is an error.
+ if (error) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kPermissionNotAllowedInManifest, permission_str);
+ }
+ return false;
+ }
+
+ std::string error_details;
+ if (!permission->FromValue(permission_value, &error_details,
+ unhandled_permissions)) {
+ if (error) {
+ if (error_details.empty()) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidPermission,
+ permission_info->name());
+ } else {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidPermissionWithDetail,
+ permission_info->name(),
+ error_details);
+ }
+ return false;
+ }
+ LOG(WARNING) << "Parse permission failed.";
+ } else {
+ api_permissions->insert(permission.release());
+ }
+ return true;
+ }
+
+ if (unhandled_permissions)
+ unhandled_permissions->push_back(permission_str);
+ else
+ LOG(WARNING) << "Unknown permission[" << permission_str << "].";
+
+ return true;
+}
+
+bool ParseChildPermissions(const std::string& base_name,
+ const base::Value* permission_value,
+ APIPermissionSet::ParseSource source,
+ APIPermissionSet* api_permissions,
+ base::string16* error,
+ std::vector<std::string>* unhandled_permissions) {
+ if (permission_value) {
+ const base::ListValue* permissions;
+ if (!permission_value->GetAsList(&permissions)) {
+ if (error) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidPermission, base_name);
+ return false;
+ }
+ LOG(WARNING) << "Permission value is not a list.";
+ // Failed to parse, but since error is NULL, failures are not fatal so
+ // return true here anyway.
+ return true;
+ }
+
+ for (size_t i = 0; i < permissions->GetSize(); ++i) {
+ std::string permission_str;
+ if (!permissions->GetString(i, &permission_str)) {
+ // permission should be a string
+ if (error) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidPermission,
+ base_name + '.' + base::SizeTToString(i));
+ return false;
+ }
+ LOG(WARNING) << "Permission is not a string.";
+ continue;
+ }
+
+ if (!CreateAPIPermission(
+ base_name + '.' + permission_str, NULL, source,
+ api_permissions, error, unhandled_permissions))
+ return false;
+ }
+ }
+
+ return CreateAPIPermission(base_name, NULL, source,
+ api_permissions, error, NULL);
+}
+
+} // namespace
+
+void APIPermissionSet::insert(APIPermission::ID id) {
+ const APIPermissionInfo* permission_info =
+ PermissionsInfo::GetInstance()->GetByID(id);
+ DCHECK(permission_info);
+ insert(permission_info->CreateAPIPermission());
+}
+
+void APIPermissionSet::insert(APIPermission* permission) {
+ BaseSetOperators<APIPermissionSet>::insert(permission);
+}
+
+// static
+bool APIPermissionSet::ParseFromJSON(
+ const base::ListValue* permissions,
+ APIPermissionSet::ParseSource source,
+ APIPermissionSet* api_permissions,
+ base::string16* error,
+ std::vector<std::string>* unhandled_permissions) {
+ for (size_t i = 0; i < permissions->GetSize(); ++i) {
+ std::string permission_str;
+ const base::Value* permission_value = NULL;
+ if (!permissions->GetString(i, &permission_str)) {
+ const base::DictionaryValue* dict = NULL;
+ // permission should be a string or a single key dict.
+ if (!permissions->GetDictionary(i, &dict) || dict->size() != 1) {
+ if (error) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidPermission, base::SizeTToString(i));
+ return false;
+ }
+ LOG(WARNING) << "Permission is not a string or single key dict.";
+ continue;
+ }
+ base::DictionaryValue::Iterator it(*dict);
+ permission_str = it.key();
+ permission_value = &it.value();
+ }
+
+ // Check if this permission is a special case where its value should
+ // be treated as a list of child permissions.
+ if (PermissionsInfo::GetInstance()->HasChildPermissions(permission_str)) {
+ if (!ParseChildPermissions(permission_str, permission_value, source,
+ api_permissions, error, unhandled_permissions))
+ return false;
+ continue;
+ }
+
+ if (!CreateAPIPermission(permission_str, permission_value, source,
+ api_permissions, error, unhandled_permissions))
+ return false;
+ }
+ return true;
+}
+
+PermissionID::PermissionID(APIPermission::ID id)
+ : std::pair<APIPermission::ID, base::string16>(id, base::string16()) {
+}
+
+PermissionID::PermissionID(APIPermission::ID id,
+ const base::string16& parameter)
+ : std::pair<APIPermission::ID, base::string16>(id, parameter) {
+}
+
+PermissionID::~PermissionID() {
+}
+
+PermissionIDSet::PermissionIDSet() {
+}
+
+PermissionIDSet::PermissionIDSet(const PermissionIDSet& other) = default;
+
+PermissionIDSet::~PermissionIDSet() {
+}
+
+void PermissionIDSet::insert(APIPermission::ID permission_id) {
+ insert(permission_id, base::string16());
+}
+
+void PermissionIDSet::insert(APIPermission::ID permission_id,
+ const base::string16& permission_detail) {
+ permissions_.insert(PermissionID(permission_id, permission_detail));
+}
+
+void PermissionIDSet::InsertAll(const PermissionIDSet& permission_set) {
+ for (const auto& permission : permission_set.permissions_) {
+ permissions_.insert(permission);
+ }
+}
+
+void PermissionIDSet::erase(APIPermission::ID permission_id) {
+ auto lower_bound = permissions_.lower_bound(PermissionID(permission_id));
+ auto upper_bound = lower_bound;
+ while (upper_bound != permissions_.end() &&
+ upper_bound->id() == permission_id) {
+ ++upper_bound;
+ }
+ permissions_.erase(lower_bound, upper_bound);
+}
+
+std::vector<base::string16> PermissionIDSet::GetAllPermissionParameters()
+ const {
+ std::vector<base::string16> params;
+ for (const auto& permission : permissions_) {
+ params.push_back(permission.parameter());
+ }
+ return params;
+}
+
+bool PermissionIDSet::ContainsID(APIPermission::ID permission_id) const {
+ auto it = permissions_.lower_bound(PermissionID(permission_id));
+ return it != permissions_.end() && it->id() == permission_id;
+}
+
+bool PermissionIDSet::ContainsAllIDs(
+ const std::set<APIPermission::ID>& permission_ids) const {
+ return std::includes(permissions_.begin(), permissions_.end(),
+ permission_ids.begin(), permission_ids.end(),
+ [] (const PermissionIDCompareHelper& lhs,
+ const PermissionIDCompareHelper& rhs) {
+ return lhs.id < rhs.id;
+ });
+}
+
+bool PermissionIDSet::ContainsAnyID(
+ const std::set<APIPermission::ID>& permission_ids) const {
+ for (APIPermission::ID id : permission_ids) {
+ if (ContainsID(id))
+ return true;
+ }
+ return false;
+}
+
+PermissionIDSet PermissionIDSet::GetAllPermissionsWithID(
+ APIPermission::ID permission_id) const {
+ PermissionIDSet subset;
+ auto it = permissions_.lower_bound(PermissionID(permission_id));
+ while (it != permissions_.end() && it->id() == permission_id) {
+ subset.permissions_.insert(*it);
+ ++it;
+ }
+ return subset;
+}
+
+PermissionIDSet PermissionIDSet::GetAllPermissionsWithIDs(
+ const std::set<APIPermission::ID>& permission_ids) const {
+ PermissionIDSet subset;
+ for (const auto& permission : permissions_) {
+ if (ContainsKey(permission_ids, permission.id())) {
+ subset.permissions_.insert(permission);
+ }
+ }
+ return subset;
+}
+
+bool PermissionIDSet::Includes(const PermissionIDSet& subset) const {
+ return base::STLIncludes(permissions_, subset.permissions_);
+}
+
+bool PermissionIDSet::Equals(const PermissionIDSet& set) const {
+ return permissions_ == set.permissions_;
+}
+
+// static
+PermissionIDSet PermissionIDSet::Difference(const PermissionIDSet& set_1,
+ const PermissionIDSet& set_2) {
+ return PermissionIDSet(base::STLSetDifference<std::set<PermissionID>>(
+ set_1.permissions_, set_2.permissions_));
+}
+
+size_t PermissionIDSet::size() const {
+ return permissions_.size();
+}
+
+bool PermissionIDSet::empty() const {
+ return permissions_.empty();
+}
+
+PermissionIDSet::PermissionIDSet(const std::set<PermissionID>& permissions)
+ : permissions_(permissions) {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/api_permission_set.h b/chromium/extensions/common/permissions/api_permission_set.h
new file mode 100644
index 00000000000..e64d519c005
--- /dev/null
+++ b/chromium/extensions/common/permissions/api_permission_set.h
@@ -0,0 +1,172 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_API_PERMISSION_SET_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_API_PERMISSION_SET_H_
+
+#include <stddef.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/base_set_operators.h"
+
+namespace base {
+class ListValue;
+} // namespace base
+
+namespace extensions {
+
+class APIPermissionSet;
+class Extension;
+
+template<>
+struct BaseSetOperatorsTraits<APIPermissionSet> {
+ typedef APIPermission ElementType;
+ typedef APIPermission::ID ElementIDType;
+};
+
+class APIPermissionSet : public BaseSetOperators<APIPermissionSet> {
+ public:
+ enum ParseSource {
+ // Don't allow internal permissions to be parsed (e.g. entries in the
+ // "permissions" list in a manifest).
+ kDisallowInternalPermissions,
+
+ // Allow internal permissions to be parsed (e.g. from the "api" field of a
+ // permissions list in the prefs).
+ kAllowInternalPermissions,
+ };
+
+ void insert(APIPermission::ID id);
+
+ // Insert |permission| into the APIPermissionSet. The APIPermissionSet will
+ // take the ownership of |permission|,
+ void insert(APIPermission* permission);
+
+ // Parses permissions from |permissions| and adds the parsed permissions to
+ // |api_permissions|. If |source| is kDisallowInternalPermissions, treat
+ // permissions with kFlagInternal as errors. If |unhandled_permissions| is
+ // not NULL, the names of all permissions that couldn't be parsed will be
+ // added to this vector. If |error| is NULL, parsing will continue with the
+ // next permission if invalid data is detected. If |error| is not NULL, it
+ // will be set to an error message and false is returned when an invalid
+ // permission is found.
+ static bool ParseFromJSON(
+ const base::ListValue* permissions,
+ ParseSource source,
+ APIPermissionSet* api_permissions,
+ base::string16* error,
+ std::vector<std::string>* unhandled_permissions);
+};
+
+// An ID representing a single permission that belongs to an app or extension.
+//
+// Each PermissionID has a required ID to identify the permission. For most
+// permissions, this is all they have.
+//
+// Some more complex permissions have a parameter, which acts like an argument
+// for the permission. For example, host permissions might have the ID
+// kReadOnlyHost and the argument 'www.google.com' (the host which is
+// read-only). Parameters are passed to the permission message rules for this
+// permission, so they can affect the displayed message.
+//
+// Note: Inheriting from std::pair automatically gives us an operator<
+// (required for putting these into an std::set).
+//
+// TODO(sashab): Move this to the same file as PermissionIDSet once that moves
+// to its own file.
+class PermissionID : public std::pair<APIPermission::ID, base::string16> {
+ public:
+ explicit PermissionID(APIPermission::ID id);
+ PermissionID(APIPermission::ID id, const base::string16& parameter);
+ virtual ~PermissionID();
+
+ const APIPermission::ID& id() const { return this->first; }
+ const base::string16& parameter() const { return this->second; }
+};
+
+// A set of permissions for an app or extension. Used for passing around groups
+// of permissions, such as required or optional permissions.
+//
+// Each permission can also store a string, such as a hostname or device number,
+// as a parameter that helps identify the permission. This parameter can then
+// be used when the permission message is generated. For example, the permission
+// kHostReadOnly might have the parameter "google.com", which means that the app
+// or extension has the permission to read the host google.com. This parameter
+// may then be included in the permission message when it is generated later.
+//
+// Example:
+// // Create an empty PermissionIDSet.
+// PermissionIDSet p;
+// // Add a permission to the set.
+// p.insert(APIPermission::kNetworkState);
+// // Add a permission with a parameter to the set.
+// p.insert(APIPermission::kHostReadOnly,
+// base::ASCIIToUTF16("http://www.google.com"));
+//
+// TODO(sashab): Move this to its own file and rename it to PermissionSet after
+// APIPermission is removed, the current PermissionSet is no longer used, and
+// APIPermission::ID is the only type of Permission ID.
+class PermissionIDSet {
+ public:
+ using const_iterator = std::set<PermissionID>::const_iterator;
+
+ PermissionIDSet();
+ PermissionIDSet(const PermissionIDSet& other);
+ virtual ~PermissionIDSet();
+
+ // Adds the given permission, and an optional parameter, to the set.
+ void insert(APIPermission::ID permission_id);
+ void insert(APIPermission::ID permission_id,
+ const base::string16& permission_parameter);
+ void InsertAll(const PermissionIDSet& permission_set);
+
+ // Erases all permissions with the given id.
+ void erase(APIPermission::ID permission_id);
+
+ // Returns the parameters for all PermissionIDs in this set.
+ std::vector<base::string16> GetAllPermissionParameters() const;
+
+ // Check if the set contains a permission with the given ID.
+ bool ContainsID(APIPermission::ID permission_id) const;
+
+ // Check if the set contains permissions with all the given IDs.
+ bool ContainsAllIDs(const std::set<APIPermission::ID>& permission_ids) const;
+
+ // Check if the set contains any permission with one of the given IDs.
+ bool ContainsAnyID(const std::set<APIPermission::ID>& permission_ids) const;
+
+ // Returns all the permissions in this set with the given ID.
+ PermissionIDSet GetAllPermissionsWithID(
+ APIPermission::ID permission_id) const;
+
+ // Returns all the permissions in this set with one of the given IDs.
+ PermissionIDSet GetAllPermissionsWithIDs(
+ const std::set<APIPermission::ID>& permission_ids) const;
+
+ // Convenience functions for common set operations.
+ bool Includes(const PermissionIDSet& subset) const;
+ bool Equals(const PermissionIDSet& set) const;
+ static PermissionIDSet Difference(const PermissionIDSet& set_1,
+ const PermissionIDSet& set_2);
+
+ size_t size() const;
+ bool empty() const;
+
+ const_iterator begin() const { return permissions_.begin(); }
+ const_iterator end() const { return permissions_.end(); }
+
+ private:
+ PermissionIDSet(const std::set<PermissionID>& permissions);
+
+ std::set<PermissionID> permissions_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_API_PERMISSION_SET_H_
diff --git a/chromium/extensions/common/permissions/api_permission_set_unittest.cc b/chromium/extensions/common/permissions/api_permission_set_unittest.cc
new file mode 100644
index 00000000000..78a0161b27b
--- /dev/null
+++ b/chromium/extensions/common/permissions/api_permission_set_unittest.cc
@@ -0,0 +1,288 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/pickle.h"
+#include "base/values.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/permissions/api_permission_set.h"
+#include "extensions/common/permissions/permissions_info.h"
+#include "ipc/ipc_message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+TEST(APIPermissionSetTest, General) {
+ APIPermissionSet apis;
+ apis.insert(APIPermission::kAudioCapture);
+ apis.insert(APIPermission::kDns);
+ apis.insert(APIPermission::kHid);
+ apis.insert(APIPermission::kPower);
+ apis.insert(APIPermission::kSerial);
+
+ EXPECT_EQ(apis.find(APIPermission::kPower)->id(), APIPermission::kPower);
+ EXPECT_TRUE(apis.find(APIPermission::kSocket) == apis.end());
+
+ EXPECT_EQ(apis.size(), 5u);
+
+ EXPECT_EQ(apis.erase(APIPermission::kAudioCapture), 1u);
+ EXPECT_EQ(apis.size(), 4u);
+
+ EXPECT_EQ(apis.erase(APIPermission::kAudioCapture), 0u);
+ EXPECT_EQ(apis.size(), 4u);
+}
+
+TEST(APIPermissionSetTest, CreateUnion) {
+ APIPermission* permission = NULL;
+
+ APIPermissionSet apis1;
+ APIPermissionSet apis2;
+ APIPermissionSet expected_apis;
+ APIPermissionSet result;
+
+ const APIPermissionInfo* permission_info =
+ PermissionsInfo::GetInstance()->GetByID(APIPermission::kSocket);
+ permission = permission_info->CreateAPIPermission();
+ {
+ scoped_ptr<base::ListValue> value(new base::ListValue());
+ value->Append(new base::StringValue("tcp-connect:*.example.com:80"));
+ value->Append(new base::StringValue("udp-bind::8080"));
+ value->Append(new base::StringValue("udp-send-to::8888"));
+ ASSERT_TRUE(permission->FromValue(value.get(), NULL, NULL));
+ }
+
+ // Union with an empty set.
+ apis1.insert(APIPermission::kAudioCapture);
+ apis1.insert(APIPermission::kDns);
+ apis1.insert(permission->Clone());
+ expected_apis.insert(APIPermission::kAudioCapture);
+ expected_apis.insert(APIPermission::kDns);
+ expected_apis.insert(permission);
+
+ ASSERT_TRUE(apis2.empty());
+ APIPermissionSet::Union(apis1, apis2, &result);
+
+ EXPECT_TRUE(apis1.Contains(apis2));
+ EXPECT_TRUE(apis1.Contains(result));
+ EXPECT_FALSE(apis2.Contains(apis1));
+ EXPECT_FALSE(apis2.Contains(result));
+ EXPECT_TRUE(result.Contains(apis1));
+ EXPECT_TRUE(result.Contains(apis2));
+
+ EXPECT_EQ(expected_apis, result);
+
+ // Now use a real second set.
+ apis2.insert(APIPermission::kAudioCapture);
+ apis2.insert(APIPermission::kHid);
+ apis2.insert(APIPermission::kPower);
+ apis2.insert(APIPermission::kSerial);
+
+ permission = permission_info->CreateAPIPermission();
+ {
+ scoped_ptr<base::ListValue> value(new base::ListValue());
+ value->Append(new base::StringValue("tcp-connect:*.example.com:80"));
+ value->Append(new base::StringValue("udp-send-to::8899"));
+ ASSERT_TRUE(permission->FromValue(value.get(), NULL, NULL));
+ }
+ apis2.insert(permission);
+
+ expected_apis.insert(APIPermission::kAudioCapture);
+ expected_apis.insert(APIPermission::kHid);
+ expected_apis.insert(APIPermission::kPower);
+ expected_apis.insert(APIPermission::kSerial);
+
+ permission = permission_info->CreateAPIPermission();
+ {
+ scoped_ptr<base::ListValue> value(new base::ListValue());
+ value->Append(new base::StringValue("tcp-connect:*.example.com:80"));
+ value->Append(new base::StringValue("udp-bind::8080"));
+ value->Append(new base::StringValue("udp-send-to::8888"));
+ value->Append(new base::StringValue("udp-send-to::8899"));
+ ASSERT_TRUE(permission->FromValue(value.get(), NULL, NULL));
+ }
+ // Insert a new socket permission which will replace the old one.
+ expected_apis.insert(permission);
+
+ APIPermissionSet::Union(apis1, apis2, &result);
+
+ EXPECT_FALSE(apis1.Contains(apis2));
+ EXPECT_FALSE(apis1.Contains(result));
+ EXPECT_FALSE(apis2.Contains(apis1));
+ EXPECT_FALSE(apis2.Contains(result));
+ EXPECT_TRUE(result.Contains(apis1));
+ EXPECT_TRUE(result.Contains(apis2));
+
+ EXPECT_EQ(expected_apis, result);
+}
+
+TEST(APIPermissionSetTest, CreateIntersection) {
+ APIPermission* permission = NULL;
+
+ APIPermissionSet apis1;
+ APIPermissionSet apis2;
+ APIPermissionSet expected_apis;
+ APIPermissionSet result;
+
+ const APIPermissionInfo* permission_info =
+ PermissionsInfo::GetInstance()->GetByID(APIPermission::kSocket);
+
+ // Intersection with an empty set.
+ apis1.insert(APIPermission::kAudioCapture);
+ apis1.insert(APIPermission::kDns);
+ permission = permission_info->CreateAPIPermission();
+ {
+ scoped_ptr<base::ListValue> value(new base::ListValue());
+ value->Append(new base::StringValue("tcp-connect:*.example.com:80"));
+ value->Append(new base::StringValue("udp-bind::8080"));
+ value->Append(new base::StringValue("udp-send-to::8888"));
+ ASSERT_TRUE(permission->FromValue(value.get(), NULL, NULL));
+ }
+ apis1.insert(permission);
+
+ ASSERT_TRUE(apis2.empty());
+ APIPermissionSet::Intersection(apis1, apis2, &result);
+
+ EXPECT_TRUE(apis1.Contains(result));
+ EXPECT_TRUE(apis2.Contains(result));
+ EXPECT_TRUE(apis1.Contains(apis2));
+ EXPECT_FALSE(apis2.Contains(apis1));
+ EXPECT_FALSE(result.Contains(apis1));
+ EXPECT_TRUE(result.Contains(apis2));
+
+ EXPECT_TRUE(result.empty());
+ EXPECT_EQ(expected_apis, result);
+
+ // Now use a real second set.
+ apis2.insert(APIPermission::kAudioCapture);
+ apis2.insert(APIPermission::kHid);
+ apis2.insert(APIPermission::kPower);
+ apis2.insert(APIPermission::kSerial);
+ permission = permission_info->CreateAPIPermission();
+ {
+ scoped_ptr<base::ListValue> value(new base::ListValue());
+ value->Append(new base::StringValue("udp-bind::8080"));
+ value->Append(new base::StringValue("udp-send-to::8888"));
+ value->Append(new base::StringValue("udp-send-to::8899"));
+ ASSERT_TRUE(permission->FromValue(value.get(), NULL, NULL));
+ }
+ apis2.insert(permission);
+
+ expected_apis.insert(APIPermission::kAudioCapture);
+ permission = permission_info->CreateAPIPermission();
+ {
+ scoped_ptr<base::ListValue> value(new base::ListValue());
+ value->Append(new base::StringValue("udp-bind::8080"));
+ value->Append(new base::StringValue("udp-send-to::8888"));
+ ASSERT_TRUE(permission->FromValue(value.get(), NULL, NULL));
+ }
+ expected_apis.insert(permission);
+
+ APIPermissionSet::Intersection(apis1, apis2, &result);
+
+ EXPECT_TRUE(apis1.Contains(result));
+ EXPECT_TRUE(apis2.Contains(result));
+ EXPECT_FALSE(apis1.Contains(apis2));
+ EXPECT_FALSE(apis2.Contains(apis1));
+ EXPECT_FALSE(result.Contains(apis1));
+ EXPECT_FALSE(result.Contains(apis2));
+
+ EXPECT_EQ(expected_apis, result);
+}
+
+TEST(APIPermissionSetTest, CreateDifference) {
+ APIPermission* permission = NULL;
+
+ APIPermissionSet apis1;
+ APIPermissionSet apis2;
+ APIPermissionSet expected_apis;
+ APIPermissionSet result;
+
+ const APIPermissionInfo* permission_info =
+ PermissionsInfo::GetInstance()->GetByID(APIPermission::kSocket);
+
+ // Difference with an empty set.
+ apis1.insert(APIPermission::kAudioCapture);
+ apis1.insert(APIPermission::kDns);
+ permission = permission_info->CreateAPIPermission();
+ {
+ scoped_ptr<base::ListValue> value(new base::ListValue());
+ value->Append(new base::StringValue("tcp-connect:*.example.com:80"));
+ value->Append(new base::StringValue("udp-bind::8080"));
+ value->Append(new base::StringValue("udp-send-to::8888"));
+ ASSERT_TRUE(permission->FromValue(value.get(), NULL, NULL));
+ }
+ apis1.insert(permission);
+
+ ASSERT_TRUE(apis2.empty());
+ APIPermissionSet::Difference(apis1, apis2, &result);
+
+ EXPECT_EQ(apis1, result);
+
+ // Now use a real second set.
+ apis2.insert(APIPermission::kAudioCapture);
+ apis2.insert(APIPermission::kHid);
+ apis2.insert(APIPermission::kPower);
+ apis2.insert(APIPermission::kSerial);
+ permission = permission_info->CreateAPIPermission();
+ {
+ scoped_ptr<base::ListValue> value(new base::ListValue());
+ value->Append(new base::StringValue("tcp-connect:*.example.com:80"));
+ value->Append(new base::StringValue("udp-send-to::8899"));
+ ASSERT_TRUE(permission->FromValue(value.get(), NULL, NULL));
+ }
+ apis2.insert(permission);
+
+ expected_apis.insert(APIPermission::kDns);
+ permission = permission_info->CreateAPIPermission();
+ {
+ scoped_ptr<base::ListValue> value(new base::ListValue());
+ value->Append(new base::StringValue("udp-bind::8080"));
+ value->Append(new base::StringValue("udp-send-to::8888"));
+ ASSERT_TRUE(permission->FromValue(value.get(), NULL, NULL));
+ }
+ expected_apis.insert(permission);
+
+ APIPermissionSet::Difference(apis1, apis2, &result);
+
+ EXPECT_TRUE(apis1.Contains(result));
+ EXPECT_FALSE(apis2.Contains(result));
+
+ EXPECT_EQ(expected_apis, result);
+
+ // |result| = |apis1| - |apis2| --> |result| intersect |apis2| == empty_set
+ APIPermissionSet result2;
+ APIPermissionSet::Intersection(result, apis2, &result2);
+ EXPECT_TRUE(result2.empty());
+}
+
+TEST(APIPermissionSetTest, IPC) {
+ APIPermission* permission = NULL;
+
+ APIPermissionSet apis;
+ APIPermissionSet expected_apis;
+
+ const APIPermissionInfo* permission_info =
+ PermissionsInfo::GetInstance()->GetByID(APIPermission::kSocket);
+
+ apis.insert(APIPermission::kAudioCapture);
+ apis.insert(APIPermission::kDns);
+ permission = permission_info->CreateAPIPermission();
+ {
+ scoped_ptr<base::ListValue> value(new base::ListValue());
+ value->Append(new base::StringValue("tcp-connect:*.example.com:80"));
+ value->Append(new base::StringValue("udp-bind::8080"));
+ value->Append(new base::StringValue("udp-send-to::8888"));
+ ASSERT_TRUE(permission->FromValue(value.get(), NULL, NULL));
+ }
+ apis.insert(permission);
+
+ EXPECT_NE(apis, expected_apis);
+
+ IPC::Message m;
+ WriteParam(&m, apis);
+ base::PickleIterator iter(m);
+ CHECK(ReadParam(&m, &iter, &expected_apis));
+ EXPECT_EQ(apis, expected_apis);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/base_set_operators.h b/chromium/extensions/common/permissions/base_set_operators.h
new file mode 100644
index 00000000000..ffe0323da22
--- /dev/null
+++ b/chromium/extensions/common/permissions/base_set_operators.h
@@ -0,0 +1,289 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_BASE_SET_OPERATORS_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_BASE_SET_OPERATORS_H_
+
+#include <stddef.h>
+
+#include <iterator>
+#include <map>
+
+#include "base/memory/linked_ptr.h"
+
+namespace extensions {
+
+// Traits for template paramater of |BaseSetOperators<T>|. Specializations
+// should define |ElementType| for the type of elements to store in the set,
+// and |EmementIDType| for the type of element identifiers.
+template <typename T>
+struct BaseSetOperatorsTraits {};
+
+// Set operations shared by |APIPermissionSet| and |ManifestPermissionSet|.
+//
+// TODO(rpaquay): It would be nice to remove the need for the sub-classes and
+// instead directly use this class where needed.
+template <typename T>
+class BaseSetOperators {
+ public:
+ typedef typename BaseSetOperatorsTraits<T>::ElementType ElementType;
+ typedef typename BaseSetOperatorsTraits<T>::ElementIDType ElementIDType;
+ typedef std::map<ElementIDType, linked_ptr<ElementType> > Map;
+
+ class const_iterator :
+ public std::iterator<std::input_iterator_tag, const ElementType*> {
+ public:
+ const_iterator(const typename Map::const_iterator& it) : it_(it) {}
+ const_iterator(const const_iterator& ids_it) : it_(ids_it.it_) {}
+
+ const_iterator& operator++() {
+ ++it_;
+ return *this;
+ }
+
+ const_iterator operator++(int) {
+ const_iterator tmp(it_++);
+ return tmp;
+ }
+
+ bool operator==(const const_iterator& rhs) const {
+ return it_ == rhs.it_;
+ }
+
+ bool operator!=(const const_iterator& rhs) const {
+ return it_ != rhs.it_;
+ }
+
+ const ElementType* operator*() const {
+ return it_->second.get();
+ }
+
+ const ElementType* operator->() const {
+ return it_->second.get();
+ }
+
+ private:
+ typename Map::const_iterator it_;
+ };
+
+ BaseSetOperators() {
+ // Ensure |T| is convertible to us, so we can safely downcast when calling
+ // methods that must exist in |T|.
+ static_assert(std::is_convertible<T*, BaseSetOperators<T>*>::value,
+ "U ptr must implicitly convert to T ptr");
+ }
+
+ BaseSetOperators(const T& set) {
+ this->operator=(set);
+ }
+
+ ~BaseSetOperators() {}
+
+ T& operator=(const T& rhs) {
+ return Assign(rhs);
+ }
+
+ bool operator==(const T& rhs) const {
+ return Equal(rhs);
+ }
+
+ bool operator!=(const T& rhs) const {
+ return !operator==(rhs);
+ }
+
+ T& Assign(const T& rhs) {
+ const_iterator it = rhs.begin();
+ const const_iterator end = rhs.end();
+ while (it != end) {
+ insert(it->Clone());
+ ++it;
+ }
+ return *static_cast<T*>(this);
+ }
+
+ bool Equal(const T& rhs) const {
+ const_iterator it = begin();
+ const_iterator rhs_it = rhs.begin();
+ const_iterator it_end = end();
+ const_iterator rhs_it_end = rhs.end();
+
+ while (it != it_end && rhs_it != rhs_it_end) {
+ if (it->id() > rhs_it->id())
+ return false;
+ else if (it->id() < rhs_it->id())
+ return false;
+ else if (!it->Equal(*rhs_it))
+ return false;
+ ++it;
+ ++rhs_it;
+ }
+ return it == it_end && rhs_it == rhs_it_end;
+ }
+
+ bool Contains(const T& rhs) const {
+ const_iterator it1 = begin();
+ const_iterator it2 = rhs.begin();
+ const_iterator end1 = end();
+ const_iterator end2 = rhs.end();
+
+ while (it1 != end1 && it2 != end2) {
+ if (it1->id() > it2->id()) {
+ return false;
+ } else if (it1->id() < it2->id()) {
+ ++it1;
+ } else {
+ if (!it1->Contains(*it2))
+ return false;
+ ++it1;
+ ++it2;
+ }
+ }
+
+ return it2 == end2;
+ }
+
+ static void Difference(const T& set1, const T& set2, T* set3) {
+ CHECK(set3);
+ set3->clear();
+
+ const_iterator it1 = set1.begin();
+ const_iterator it2 = set2.begin();
+ const const_iterator end1 = set1.end();
+ const const_iterator end2 = set2.end();
+
+ while (it1 != end1 && it2 != end2) {
+ if (it1->id() < it2->id()) {
+ set3->insert(it1->Clone());
+ ++it1;
+ } else if (it1->id() > it2->id()) {
+ ++it2;
+ } else {
+ ElementType* p = it1->Diff(*it2);
+ if (p)
+ set3->insert(p);
+ ++it1;
+ ++it2;
+ }
+ }
+
+ while (it1 != end1) {
+ set3->insert(it1->Clone());
+ ++it1;
+ }
+ }
+
+ static void Intersection(const T& set1, const T& set2, T* set3) {
+ DCHECK(set3);
+ set3->clear();
+
+ const_iterator it1 = set1.begin();
+ const_iterator it2 = set2.begin();
+ const const_iterator end1 = set1.end();
+ const const_iterator end2 = set2.end();
+
+ while (it1 != end1 && it2 != end2) {
+ if (it1->id() < it2->id()) {
+ ++it1;
+ } else if (it1->id() > it2->id()) {
+ ++it2;
+ } else {
+ ElementType* p = it1->Intersect(*it2);
+ if (p)
+ set3->insert(p);
+ ++it1;
+ ++it2;
+ }
+ }
+ }
+
+ static void Union(const T& set1, const T& set2, T* set3) {
+ DCHECK(set3);
+ set3->clear();
+
+ const_iterator it1 = set1.begin();
+ const_iterator it2 = set2.begin();
+ const const_iterator end1 = set1.end();
+ const const_iterator end2 = set2.end();
+
+ while (true) {
+ if (it1 == end1) {
+ while (it2 != end2) {
+ set3->insert(it2->Clone());
+ ++it2;
+ }
+ break;
+ }
+ if (it2 == end2) {
+ while (it1 != end1) {
+ set3->insert(it1->Clone());
+ ++it1;
+ }
+ break;
+ }
+ if (it1->id() < it2->id()) {
+ set3->insert(it1->Clone());
+ ++it1;
+ } else if (it1->id() > it2->id()) {
+ set3->insert(it2->Clone());
+ ++it2;
+ } else {
+ set3->insert(it1->Union(*it2));
+ ++it1;
+ ++it2;
+ }
+ }
+ }
+
+ const_iterator begin() const {
+ return const_iterator(map().begin());
+ }
+
+ const_iterator end() const {
+ return map().end();
+ }
+
+ const_iterator find(ElementIDType id) const {
+ return map().find(id);
+ }
+
+ size_t count(ElementIDType id) const {
+ return map().count(id);
+ }
+
+ bool empty() const {
+ return map().empty();
+ }
+
+ size_t erase(ElementIDType id) {
+ return map().erase(id);
+ }
+
+ // Take ownership and insert |item| into the set.
+ void insert(ElementType* item) {
+ map_[item->id()].reset(item);
+ }
+
+ size_t size() const {
+ return map().size();
+ }
+
+ const Map& map() const {
+ return map_;
+ }
+
+ Map& map() {
+ return map_;
+ }
+
+ void clear() {
+ map_.clear();
+ }
+
+ private:
+ Map map_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_BASE_SET_OPERATORS_H_
diff --git a/chromium/extensions/common/permissions/extensions_api_permissions.cc b/chromium/extensions/common/permissions/extensions_api_permissions.cc
new file mode 100644
index 00000000000..9f9127c7bba
--- /dev/null
+++ b/chromium/extensions/common/permissions/extensions_api_permissions.cc
@@ -0,0 +1,141 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/extensions_api_permissions.h"
+
+#include <stddef.h>
+
+#include <vector>
+
+#include "base/macros.h"
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/socket_permission.h"
+#include "extensions/common/permissions/usb_device_permission.h"
+#include "grit/extensions_strings.h"
+
+namespace extensions {
+
+namespace {
+
+const char kOldAlwaysOnTopWindowsPermission[] = "alwaysOnTopWindows";
+const char kOldFullscreenPermission[] = "fullscreen";
+const char kOldOverrideEscFullscreenPermission[] = "overrideEscFullscreen";
+const char kOldUnlimitedStoragePermission[] = "unlimited_storage";
+
+template <typename T>
+APIPermission* CreateAPIPermission(const APIPermissionInfo* permission) {
+ return new T(permission);
+}
+
+} // namespace
+
+std::vector<APIPermissionInfo*> ExtensionsAPIPermissions::GetAllPermissions()
+ const {
+ // WARNING: If you are modifying a permission message in this list, be sure to
+ // add the corresponding permission message rule to
+ // ChromePermissionMessageProvider::GetCoalescedPermissionMessages as well.
+ APIPermissionInfo::InitInfo permissions_to_register[] = {
+ {APIPermission::kAlarms, "alarms"},
+ {APIPermission::kAlphaEnabled, "app.window.alpha"},
+ {APIPermission::kAlwaysOnTopWindows, "app.window.alwaysOnTop"},
+ {APIPermission::kAppView,
+ "appview",
+ APIPermissionInfo::kFlagCannotBeOptional},
+ {APIPermission::kAudio, "audio"},
+ {APIPermission::kAudioCapture, "audioCapture"},
+ {APIPermission::kBluetoothPrivate,
+ "bluetoothPrivate",
+ APIPermissionInfo::kFlagCannotBeOptional},
+ {APIPermission::kClipboardRead,
+ "clipboardRead",
+ APIPermissionInfo::kFlagSupportsContentCapabilities},
+ {APIPermission::kClipboardWrite,
+ "clipboardWrite",
+ APIPermissionInfo::kFlagSupportsContentCapabilities},
+ {APIPermission::kDeclarativeWebRequest, "declarativeWebRequest"},
+ {APIPermission::kDiagnostics,
+ "diagnostics",
+ APIPermissionInfo::kFlagCannotBeOptional},
+ {APIPermission::kDisplaySource, "displaySource"},
+ {APIPermission::kDns, "dns"},
+ {APIPermission::kDocumentScan, "documentScan"},
+ {APIPermission::kExtensionView,
+ "extensionview",
+ APIPermissionInfo::kFlagCannotBeOptional},
+ {APIPermission::kExternallyConnectableAllUrls,
+ "externally_connectable.all_urls"},
+ {APIPermission::kFullscreen, "app.window.fullscreen"},
+ {APIPermission::kHid, "hid"},
+ {APIPermission::kImeWindowEnabled, "app.window.ime"},
+ {APIPermission::kOverrideEscFullscreen,
+ "app.window.fullscreen.overrideEsc"},
+ {APIPermission::kIdle, "idle"},
+ {APIPermission::kNetworkingConfig, "networking.config"},
+ {APIPermission::kNetworkingPrivate,
+ "networkingPrivate",
+ APIPermissionInfo::kFlagCannotBeOptional},
+ {APIPermission::kPower, "power"},
+ {APIPermission::kPrinterProvider, "printerProvider"},
+ {APIPermission::kSerial, "serial"},
+ {APIPermission::kSocket,
+ "socket",
+ APIPermissionInfo::kFlagCannotBeOptional,
+ &CreateAPIPermission<SocketPermission>},
+ {APIPermission::kStorage, "storage"},
+ {APIPermission::kSystemCpu, "system.cpu"},
+ {APIPermission::kSystemMemory, "system.memory"},
+ {APIPermission::kSystemNetwork, "system.network"},
+ {APIPermission::kSystemDisplay, "system.display"},
+ {APIPermission::kSystemStorage, "system.storage"},
+ {APIPermission::kU2fDevices, "u2fDevices"},
+ {APIPermission::kUnlimitedStorage,
+ "unlimitedStorage",
+ APIPermissionInfo::kFlagCannotBeOptional |
+ APIPermissionInfo::kFlagSupportsContentCapabilities},
+ {APIPermission::kUsb, "usb", APIPermissionInfo::kFlagNone},
+ {APIPermission::kUsbDevice,
+ "usbDevices",
+ APIPermissionInfo::kFlagNone,
+ &CreateAPIPermission<UsbDevicePermission>},
+ {APIPermission::kVideoCapture, "videoCapture"},
+ {APIPermission::kVpnProvider,
+ "vpnProvider",
+ APIPermissionInfo::kFlagCannotBeOptional},
+ // NOTE(kalman): This is provided by a manifest property but needs to
+ // appear in the install permission dialogue, so we need a fake
+ // permission for it. See http://crbug.com/247857.
+ {APIPermission::kWebConnectable,
+ "webConnectable",
+ APIPermissionInfo::kFlagCannotBeOptional |
+ APIPermissionInfo::kFlagInternal},
+ {APIPermission::kWebRequest, "webRequest"},
+ {APIPermission::kWebRequestBlocking, "webRequestBlocking"},
+ {APIPermission::kWebView,
+ "webview",
+ APIPermissionInfo::kFlagCannotBeOptional},
+ {APIPermission::kWindowShape, "app.window.shape"},
+ };
+
+ std::vector<APIPermissionInfo*> permissions;
+ for (size_t i = 0; i < arraysize(permissions_to_register); ++i)
+ permissions.push_back(new APIPermissionInfo(permissions_to_register[i]));
+ return permissions;
+}
+
+std::vector<PermissionsProvider::AliasInfo>
+ExtensionsAPIPermissions::GetAllAliases() const {
+ std::vector<PermissionsProvider::AliasInfo> aliases;
+ aliases.push_back(PermissionsProvider::AliasInfo(
+ "app.window.alwaysOnTop", kOldAlwaysOnTopWindowsPermission));
+ aliases.push_back(PermissionsProvider::AliasInfo("app.window.fullscreen",
+ kOldFullscreenPermission));
+ aliases.push_back(
+ PermissionsProvider::AliasInfo("app.window.fullscreen.overrideEsc",
+ kOldOverrideEscFullscreenPermission));
+ aliases.push_back(PermissionsProvider::AliasInfo(
+ "unlimitedStorage", kOldUnlimitedStoragePermission));
+ return aliases;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/extensions_api_permissions.h b/chromium/extensions/common/permissions/extensions_api_permissions.h
new file mode 100644
index 00000000000..2509f9d112e
--- /dev/null
+++ b/chromium/extensions/common/permissions/extensions_api_permissions.h
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_EXTENSIONS_API_PERMISSIONS_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_EXTENSIONS_API_PERMISSIONS_H_
+
+#include "base/compiler_specific.h"
+#include "extensions/common/permissions/permissions_provider.h"
+
+namespace extensions {
+
+class ExtensionsAPIPermissions : public PermissionsProvider {
+ public:
+ std::vector<APIPermissionInfo*> GetAllPermissions() const override;
+ std::vector<PermissionsProvider::AliasInfo> GetAllAliases() const override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_EXTENSIONS_API_PERMISSIONS_H_
diff --git a/chromium/extensions/common/permissions/manifest_permission.cc b/chromium/extensions/common/permissions/manifest_permission.cc
new file mode 100644
index 00000000000..97421385169
--- /dev/null
+++ b/chromium/extensions/common/permissions/manifest_permission.cc
@@ -0,0 +1,55 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/manifest_permission.h"
+
+#include "base/json/json_writer.h"
+#include "extensions/common/manifest_handler.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_utils.h"
+
+namespace extensions {
+
+ManifestPermission::ManifestPermission() {}
+
+ManifestPermission::~ManifestPermission() { }
+
+ManifestPermission* ManifestPermission::Clone() const {
+ return Union(this);
+}
+
+bool ManifestPermission::Contains(const ManifestPermission* rhs) const {
+ return scoped_ptr<ManifestPermission>(Intersect(rhs))->Equal(rhs);
+}
+
+bool ManifestPermission::Equal(const ManifestPermission* rhs) const {
+ return ToValue()->Equals(rhs->ToValue().get());
+}
+
+void ManifestPermission::Write(base::Pickle* m) const {
+ base::ListValue singleton;
+ base::Value* value = ToValue().release();
+ singleton.Append(value);
+ IPC::WriteParam(m, singleton);
+}
+
+bool ManifestPermission::Read(const base::Pickle* m,
+ base::PickleIterator* iter) {
+ base::ListValue singleton;
+ if (!IPC::ReadParam(m, iter, &singleton))
+ return false;
+ if (singleton.GetSize() != 1)
+ return false;
+ base::Value* value = NULL;
+ if (!singleton.Get(0, &value))
+ return false;
+ return FromValue(value);
+}
+
+void ManifestPermission::Log(std::string* log) const {
+ base::JSONWriter::WriteWithOptions(
+ *ToValue(), base::JSONWriter::OPTIONS_PRETTY_PRINT, log);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/manifest_permission.h b/chromium/extensions/common/permissions/manifest_permission.h
new file mode 100644
index 00000000000..db8f07c2594
--- /dev/null
+++ b/chromium/extensions/common/permissions/manifest_permission.h
@@ -0,0 +1,86 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_MANIFEST_PERMISSION_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_MANIFEST_PERMISSION_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/pickle.h"
+#include "extensions/common/permissions/api_permission_set.h"
+
+namespace base {
+class PickleIterator;
+class Value;
+}
+
+namespace IPC {
+class Message;
+}
+
+namespace extensions {
+
+// Represent the custom behavior of a top-level manifest entry contributing to
+// permission messages and storage.
+class ManifestPermission {
+ public:
+ ManifestPermission();
+ virtual ~ManifestPermission();
+
+ // The manifest key this permission applies to.
+ virtual std::string name() const = 0;
+
+ // Same as name(), needed for compatibility with APIPermission.
+ virtual std::string id() const = 0;
+
+ // The set of permissions this manifest entry has. These permissions are used
+ // by PermissionMessageProvider to generate meaningful permission messages
+ // for the app.
+ virtual PermissionIDSet GetPermissions() const = 0;
+
+ // Parses the ManifestPermission from |value|. Returns false if error happens.
+ virtual bool FromValue(const base::Value* value) = 0;
+
+ // Stores this into a new created Value.
+ virtual scoped_ptr<base::Value> ToValue() const = 0;
+
+ // Clones this.
+ ManifestPermission* Clone() const;
+
+ // Returns a new manifest permission which equals this - |rhs|.
+ virtual ManifestPermission* Diff(const ManifestPermission* rhs) const = 0;
+
+ // Returns a new manifest permission which equals the union of this and |rhs|.
+ virtual ManifestPermission* Union(const ManifestPermission* rhs) const = 0;
+
+ // Returns a new manifest permission which equals the intersect of this and
+ // |rhs|.
+ virtual ManifestPermission* Intersect(const ManifestPermission* rhs)
+ const = 0;
+
+ // Returns true if |rhs| is a subset of this.
+ bool Contains(const ManifestPermission* rhs) const;
+
+ // Returns true if |rhs| is equal to this.
+ bool Equal(const ManifestPermission* rhs) const;
+
+ // IPC functions
+ // Writes this into the given IPC message |m|.
+ void Write(base::Pickle* m) const;
+
+ // Reads from the given IPC message |m|.
+ bool Read(const base::Pickle* m, base::PickleIterator* iter);
+
+ // Logs this permission.
+ void Log(std::string* log) const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ManifestPermission);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_MANIFEST_PERMISSION_H_
diff --git a/chromium/extensions/common/permissions/manifest_permission_set.cc b/chromium/extensions/common/permissions/manifest_permission_set.cc
new file mode 100644
index 00000000000..39e2ba673d7
--- /dev/null
+++ b/chromium/extensions/common/permissions/manifest_permission_set.cc
@@ -0,0 +1,95 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/manifest_permission_set.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handler.h"
+#include "extensions/common/permissions/manifest_permission.h"
+
+namespace {
+
+using extensions::ErrorUtils;
+using extensions::ManifestPermission;
+using extensions::ManifestPermissionSet;
+using extensions::ManifestHandler;
+namespace errors = extensions::manifest_errors;
+
+bool CreateManifestPermission(
+ const std::string& permission_name,
+ const base::Value* permission_value,
+ ManifestPermissionSet* manifest_permissions,
+ base::string16* error,
+ std::vector<std::string>* unhandled_permissions) {
+
+ scoped_ptr<ManifestPermission> permission(
+ ManifestHandler::CreatePermission(permission_name));
+
+ if (!permission) {
+ if (unhandled_permissions)
+ unhandled_permissions->push_back(permission_name);
+ else
+ LOG(WARNING) << "Unknown permission[" << permission_name << "].";
+ return true;
+ }
+
+ if (!permission->FromValue(permission_value)) {
+ if (error) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidPermission, permission_name);
+ return false;
+ }
+ LOG(WARNING) << "Parse permission failed.";
+ return true;
+ } else {
+ manifest_permissions->insert(permission.release());
+ return true;
+ }
+}
+
+} // namespace
+
+namespace extensions {
+
+// static
+bool ManifestPermissionSet::ParseFromJSON(
+ const base::ListValue* permissions,
+ ManifestPermissionSet* manifest_permissions,
+ base::string16* error,
+ std::vector<std::string>* unhandled_permissions) {
+ for (size_t i = 0; i < permissions->GetSize(); ++i) {
+ std::string permission_name;
+ const base::Value* permission_value = NULL;
+ if (!permissions->GetString(i, &permission_name)) {
+ const base::DictionaryValue* dict = NULL;
+ // permission should be a string or a single key dict.
+ if (!permissions->GetDictionary(i, &dict) || dict->size() != 1) {
+ if (error) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidPermission, base::SizeTToString(i));
+ return false;
+ }
+ LOG(WARNING) << "Permission is not a string or single key dict.";
+ continue;
+ }
+ base::DictionaryValue::Iterator it(*dict);
+ permission_name = it.key();
+ permission_value = &it.value();
+ }
+
+ if (!CreateManifestPermission(permission_name, permission_value,
+ manifest_permissions, error,
+ unhandled_permissions))
+ return false;
+ }
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/manifest_permission_set.h b/chromium/extensions/common/permissions/manifest_permission_set.h
new file mode 100644
index 00000000000..4b1ffe02389
--- /dev/null
+++ b/chromium/extensions/common/permissions/manifest_permission_set.h
@@ -0,0 +1,47 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_MANIFEST_PERMISSION_SET_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_MANIFEST_PERMISSION_SET_H_
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "extensions/common/permissions/base_set_operators.h"
+
+namespace base {
+class ListValue;
+} // namespace base
+
+namespace extensions {
+
+class Extension;
+class ManifestPermission;
+class ManifestPermissionSet;
+
+template<>
+struct BaseSetOperatorsTraits<ManifestPermissionSet> {
+ typedef ManifestPermission ElementType;
+ typedef std::string ElementIDType;
+};
+
+class ManifestPermissionSet : public BaseSetOperators<ManifestPermissionSet> {
+ public:
+ // Parses permissions from |permissions| and adds the parsed permissions to
+ // |manifest_permissions|. If |unhandled_permissions| is not NULL, the names
+ // of all permissions that couldn't be parsed will be added to this vector.
+ // If |error| is NULL, parsing will continue with the next permission if
+ // invalid data is detected. If |error| is not NULL, it will be set to an
+ // error message and false is returned when an invalid permission is found.
+ static bool ParseFromJSON(
+ const base::ListValue* permissions,
+ ManifestPermissionSet* manifest_permissions,
+ base::string16* error,
+ std::vector<std::string>* unhandled_permissions);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_MANIFEST_PERMISSION_SET_H_
diff --git a/chromium/extensions/common/permissions/manifest_permission_set_unittest.cc b/chromium/extensions/common/permissions/manifest_permission_set_unittest.cc
new file mode 100644
index 00000000000..f3660363f9a
--- /dev/null
+++ b/chromium/extensions/common/permissions/manifest_permission_set_unittest.cc
@@ -0,0 +1,222 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/pickle.h"
+#include "base/values.h"
+#include "extensions/common/permissions/manifest_permission.h"
+#include "extensions/common/permissions/manifest_permission_set.h"
+#include "ipc/ipc_message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+class MockManifestPermission : public ManifestPermission {
+ public:
+ MockManifestPermission(const std::string& name)
+ : name_(name) {
+ }
+
+ std::string name() const override { return name_; }
+
+ std::string id() const override { return name(); }
+
+ PermissionIDSet GetPermissions() const override { return PermissionIDSet(); }
+
+ bool FromValue(const base::Value* value) override { return true; }
+
+ scoped_ptr<base::Value> ToValue() const override {
+ return base::Value::CreateNullValue();
+ }
+
+ ManifestPermission* Diff(const ManifestPermission* rhs) const override {
+ const MockManifestPermission* other =
+ static_cast<const MockManifestPermission*>(rhs);
+ EXPECT_EQ(name_, other->name_);
+ return NULL;
+ }
+
+ ManifestPermission* Union(const ManifestPermission* rhs) const override {
+ const MockManifestPermission* other =
+ static_cast<const MockManifestPermission*>(rhs);
+ EXPECT_EQ(name_, other->name_);
+ return new MockManifestPermission(name_);
+ }
+
+ ManifestPermission* Intersect(const ManifestPermission* rhs) const override {
+ const MockManifestPermission* other =
+ static_cast<const MockManifestPermission*>(rhs);
+ EXPECT_EQ(name_, other->name_);
+ return new MockManifestPermission(name_);
+ }
+
+ private:
+ std::string name_;
+};
+
+TEST(ManifestPermissionSetTest, General) {
+ ManifestPermissionSet set;
+ set.insert(new MockManifestPermission("p1"));
+ set.insert(new MockManifestPermission("p2"));
+ set.insert(new MockManifestPermission("p3"));
+ set.insert(new MockManifestPermission("p4"));
+ set.insert(new MockManifestPermission("p5"));
+
+ EXPECT_EQ(set.find("p1")->id(), "p1");
+ EXPECT_TRUE(set.find("p10") == set.end());
+
+ EXPECT_EQ(set.size(), 5u);
+
+ EXPECT_EQ(set.erase("p1"), 1u);
+ EXPECT_EQ(set.size(), 4u);
+
+ EXPECT_EQ(set.erase("p1"), 0u);
+ EXPECT_EQ(set.size(), 4u);
+}
+
+TEST(ManifestPermissionSetTest, CreateUnion) {
+ ManifestPermissionSet permissions1;
+ ManifestPermissionSet permissions2;
+ ManifestPermissionSet expected_permissions;
+ ManifestPermissionSet result;
+
+ ManifestPermission* permission = new MockManifestPermission("p3");
+
+ // Union with an empty set.
+ permissions1.insert(new MockManifestPermission("p1"));
+ permissions1.insert(new MockManifestPermission("p2"));
+ permissions1.insert(permission->Clone());
+ expected_permissions.insert(new MockManifestPermission("p1"));
+ expected_permissions.insert(new MockManifestPermission("p2"));
+ expected_permissions.insert(permission);
+
+ ManifestPermissionSet::Union(permissions1, permissions2, &result);
+
+ EXPECT_TRUE(permissions1.Contains(permissions2));
+ EXPECT_TRUE(permissions1.Contains(result));
+ EXPECT_FALSE(permissions2.Contains(permissions1));
+ EXPECT_FALSE(permissions2.Contains(result));
+ EXPECT_TRUE(result.Contains(permissions1));
+ EXPECT_TRUE(result.Contains(permissions2));
+
+ EXPECT_EQ(expected_permissions, result);
+
+ // Now use a real second set.
+ permissions2.insert(new MockManifestPermission("p1"));
+ permissions2.insert(new MockManifestPermission("p2"));
+ permissions2.insert(new MockManifestPermission("p33"));
+ permissions2.insert(new MockManifestPermission("p4"));
+ permissions2.insert(new MockManifestPermission("p5"));
+
+ expected_permissions.insert(new MockManifestPermission("p1"));
+ expected_permissions.insert(new MockManifestPermission("p2"));
+ expected_permissions.insert(new MockManifestPermission("p3"));
+ expected_permissions.insert(new MockManifestPermission("p4"));
+ expected_permissions.insert(new MockManifestPermission("p5"));
+ expected_permissions.insert(new MockManifestPermission("p33"));
+
+ ManifestPermissionSet::Union(permissions1, permissions2, &result);
+
+ {
+ ManifestPermissionSet set1;
+ set1.insert(new MockManifestPermission("p1"));
+ set1.insert(new MockManifestPermission("p2"));
+ ManifestPermissionSet set2;
+ set2.insert(new MockManifestPermission("p3"));
+
+ EXPECT_FALSE(set1.Contains(set2));
+ EXPECT_FALSE(set2.Contains(set1));
+ }
+
+ EXPECT_FALSE(permissions1.Contains(permissions2));
+ EXPECT_FALSE(permissions1.Contains(result));
+ EXPECT_FALSE(permissions2.Contains(permissions1));
+ EXPECT_FALSE(permissions2.Contains(result));
+ EXPECT_TRUE(result.Contains(permissions1));
+ EXPECT_TRUE(result.Contains(permissions2));
+
+ EXPECT_EQ(expected_permissions, result);
+}
+
+TEST(ManifestPermissionSetTest, CreateIntersection) {
+ ManifestPermissionSet permissions1;
+ ManifestPermissionSet permissions2;
+ ManifestPermissionSet expected_permissions;
+ ManifestPermissionSet result;
+
+ // Intersection with an empty set.
+ permissions1.insert(new MockManifestPermission("p1"));
+ permissions1.insert(new MockManifestPermission("p2"));
+ permissions1.insert(new MockManifestPermission("p3"));
+
+ ManifestPermissionSet::Intersection(permissions1, permissions2, &result);
+ EXPECT_TRUE(permissions1.Contains(result));
+ EXPECT_TRUE(permissions2.Contains(result));
+ EXPECT_TRUE(permissions1.Contains(permissions2));
+ EXPECT_FALSE(permissions2.Contains(permissions1));
+ EXPECT_FALSE(result.Contains(permissions1));
+ EXPECT_TRUE(result.Contains(permissions2));
+
+ EXPECT_TRUE(result.empty());
+ EXPECT_EQ(expected_permissions, result);
+
+ // Now use a real second set.
+ permissions2.insert(new MockManifestPermission("p1"));
+ permissions2.insert(new MockManifestPermission("p3"));
+ permissions2.insert(new MockManifestPermission("p4"));
+ permissions2.insert(new MockManifestPermission("p5"));
+
+ expected_permissions.insert(new MockManifestPermission("p1"));
+ expected_permissions.insert(new MockManifestPermission("p3"));
+
+ ManifestPermissionSet::Intersection(permissions1, permissions2, &result);
+
+ EXPECT_TRUE(permissions1.Contains(result));
+ EXPECT_TRUE(permissions2.Contains(result));
+ EXPECT_FALSE(permissions1.Contains(permissions2));
+ EXPECT_FALSE(permissions2.Contains(permissions1));
+ EXPECT_FALSE(result.Contains(permissions1));
+ EXPECT_FALSE(result.Contains(permissions2));
+
+ EXPECT_EQ(expected_permissions, result);
+}
+
+TEST(ManifestPermissionSetTest, CreateDifference) {
+ ManifestPermissionSet permissions1;
+ ManifestPermissionSet permissions2;
+ ManifestPermissionSet expected_permissions;
+ ManifestPermissionSet result;
+
+ // Difference with an empty set.
+ permissions1.insert(new MockManifestPermission("p1"));
+ permissions1.insert(new MockManifestPermission("p2"));
+ permissions1.insert(new MockManifestPermission("p3"));
+
+ ManifestPermissionSet::Difference(permissions1, permissions2, &result);
+
+ EXPECT_EQ(permissions1, result);
+
+ // Now use a real second set.
+ permissions2.insert(new MockManifestPermission("p1"));
+ permissions2.insert(new MockManifestPermission("p2"));
+ permissions2.insert(new MockManifestPermission("p4"));
+ permissions2.insert(new MockManifestPermission("p5"));
+ permissions2.insert(new MockManifestPermission("p6"));
+
+ expected_permissions.insert(new MockManifestPermission("p3"));
+
+ ManifestPermissionSet::Difference(permissions1, permissions2, &result);
+
+ EXPECT_TRUE(permissions1.Contains(result));
+ EXPECT_FALSE(permissions2.Contains(result));
+
+ EXPECT_EQ(expected_permissions, result);
+
+ // |result| = |permissions1| - |permissions2| -->
+ // |result| intersect |permissions2| == empty_set
+ ManifestPermissionSet result2;
+ ManifestPermissionSet::Intersection(result, permissions2, &result2);
+ EXPECT_TRUE(result2.empty());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/media_galleries_permission.cc b/chromium/extensions/common/permissions/media_galleries_permission.cc
new file mode 100644
index 00000000000..4ac5ae6c0c9
--- /dev/null
+++ b/chromium/extensions/common/permissions/media_galleries_permission.cc
@@ -0,0 +1,171 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/media_galleries_permission.h"
+
+#include <stddef.h>
+
+#include <set>
+#include <string>
+
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/common/permissions/permissions_info.h"
+#include "grit/extensions_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+namespace {
+
+// copyTo permission requires delete permission as a prerequisite.
+// delete permission requires read permission as a prerequisite.
+bool IsValidPermissionSet(bool has_read, bool has_copy_to, bool has_delete,
+ std::string* error) {
+ if (has_copy_to) {
+ if (has_read && has_delete)
+ return true;
+ if (error)
+ *error = "copyTo permission requires read and delete permissions";
+ return false;
+ }
+ if (has_delete) {
+ if (has_read)
+ return true;
+ if (error)
+ *error = "delete permission requires read permission";
+ return false;
+ }
+ return true;
+}
+
+// Adds the permissions from the |data_set| to |ids|.
+void AddPermissionsToLists(
+ const std::set<MediaGalleriesPermissionData>& data_set,
+ PermissionIDSet* ids) {
+ // TODO(sashab): Once GetMessages() is deprecated, move this logic back into
+ // GetPermissions().
+ bool has_all_auto_detected = false;
+ bool has_read = false;
+ bool has_copy_to = false;
+ bool has_delete = false;
+
+ for (std::set<MediaGalleriesPermissionData>::const_iterator it =
+ data_set.begin();
+ it != data_set.end(); ++it) {
+ if (it->permission() ==
+ MediaGalleriesPermission::kAllAutoDetectedPermission)
+ has_all_auto_detected = true;
+ else if (it->permission() == MediaGalleriesPermission::kReadPermission)
+ has_read = true;
+ else if (it->permission() == MediaGalleriesPermission::kCopyToPermission)
+ has_copy_to = true;
+ else if (it->permission() == MediaGalleriesPermission::kDeletePermission)
+ has_delete = true;
+ }
+
+ if (!IsValidPermissionSet(has_read, has_copy_to, has_delete, NULL)) {
+ NOTREACHED();
+ return;
+ }
+
+ // If |has_all_auto_detected| is false, then Chrome will prompt the user at
+ // runtime when the extension call the getMediaGalleries API.
+ if (!has_all_auto_detected)
+ return;
+ // No access permission case.
+ if (!has_read)
+ return;
+
+ // Separate PermissionMessage IDs for read, copyTo, and delete. Otherwise an
+ // extension can silently gain new access capabilities.
+ ids->insert(APIPermission::kMediaGalleriesAllGalleriesRead);
+
+ // For copyTo and delete, the proper combined permission message will be
+ // derived in ChromePermissionMessageProvider::GetWarningMessages(), such
+ // that the user get 1 entry for all media galleries access permissions,
+ // rather than several separate entries.
+ if (has_copy_to)
+ ids->insert(APIPermission::kMediaGalleriesAllGalleriesCopyTo);
+ if (has_delete)
+ ids->insert(APIPermission::kMediaGalleriesAllGalleriesDelete);
+}
+
+} // namespace
+
+const char MediaGalleriesPermission::kAllAutoDetectedPermission[] =
+ "allAutoDetected";
+const char MediaGalleriesPermission::kReadPermission[] = "read";
+const char MediaGalleriesPermission::kCopyToPermission[] = "copyTo";
+const char MediaGalleriesPermission::kDeletePermission[] = "delete";
+
+MediaGalleriesPermission::MediaGalleriesPermission(
+ const APIPermissionInfo* info)
+ : SetDisjunctionPermission<MediaGalleriesPermissionData,
+ MediaGalleriesPermission>(info) {
+}
+
+MediaGalleriesPermission::~MediaGalleriesPermission() {
+}
+
+bool MediaGalleriesPermission::FromValue(
+ const base::Value* value,
+ std::string* error,
+ std::vector<std::string>* unhandled_permissions) {
+ size_t unhandled_permissions_count = 0;
+ if (unhandled_permissions)
+ unhandled_permissions_count = unhandled_permissions->size();
+ bool parsed_ok =
+ SetDisjunctionPermission<MediaGalleriesPermissionData,
+ MediaGalleriesPermission>::FromValue(
+ value, error, unhandled_permissions);
+ if (unhandled_permissions) {
+ for (size_t i = unhandled_permissions_count;
+ i < unhandled_permissions->size();
+ i++) {
+ (*unhandled_permissions)[i] =
+ "{\"mediaGalleries\": [" + (*unhandled_permissions)[i] + "]}";
+ }
+ }
+ if (!parsed_ok)
+ return false;
+
+ bool has_read = false;
+ bool has_copy_to = false;
+ bool has_delete = false;
+ for (std::set<MediaGalleriesPermissionData>::const_iterator it =
+ data_set_.begin(); it != data_set_.end(); ++it) {
+ if (it->permission() == kAllAutoDetectedPermission) {
+ continue;
+ }
+ if (it->permission() == kReadPermission) {
+ has_read = true;
+ continue;
+ }
+ if (it->permission() == kCopyToPermission) {
+ has_copy_to = true;
+ continue;
+ }
+ if (it->permission() == kDeletePermission) {
+ has_delete = true;
+ continue;
+ }
+
+ // No other permissions, so reaching this means
+ // MediaGalleriesPermissionData is probably out of sync in some way.
+ // Fail so developers notice this.
+ NOTREACHED();
+ return false;
+ }
+
+ return IsValidPermissionSet(has_read, has_copy_to, has_delete, error);
+}
+
+PermissionIDSet MediaGalleriesPermission::GetPermissions() const {
+ PermissionIDSet result;
+ AddPermissionsToLists(data_set_, &result);
+ return result;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/media_galleries_permission.h b/chromium/extensions/common/permissions/media_galleries_permission.h
new file mode 100644
index 00000000000..40a2277b40d
--- /dev/null
+++ b/chromium/extensions/common/permissions/media_galleries_permission.h
@@ -0,0 +1,61 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_MEDIA_GALLERIES_PERMISSION_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_MEDIA_GALLERIES_PERMISSION_H_
+
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/media_galleries_permission_data.h"
+#include "extensions/common/permissions/set_disjunction_permission.h"
+
+namespace extensions {
+
+// Media Galleries permissions are as follows:
+// <media-galleries-permission-pattern>
+// := <access> | <access> 'allAutoDetected' | 'allAutoDetected' |
+// <access> 'scan' | 'scan'
+// <access> := 'read' | 'read' <access> | 'read' <secondary-access>
+// <secondary-access>
+// := 'delete' | 'delete' <secondary-access> |
+// 'delete' <tertiary-access>
+// <tertiary-access>
+// := 'copyTo' | 'copyTo' <tertiary-access>
+// An example of a line for mediaGalleries permissions in a manifest file:
+// {"mediaGalleries": "read delete"},
+// We also allow a permission without any sub-permissions:
+// "mediaGalleries",
+class MediaGalleriesPermission
+ : public SetDisjunctionPermission<MediaGalleriesPermissionData,
+ MediaGalleriesPermission> {
+ public:
+ struct CheckParam : public APIPermission::CheckParam {
+ explicit CheckParam(const std::string& permission)
+ : permission(permission) {}
+ const std::string permission;
+ };
+
+ explicit MediaGalleriesPermission(const APIPermissionInfo* info);
+ ~MediaGalleriesPermission() override;
+
+ // SetDisjunctionPermission overrides.
+ // MediaGalleriesPermission does additional checks to make sure the
+ // permissions do not contain unknown values.
+ bool FromValue(const base::Value* value,
+ std::string* error,
+ std::vector<std::string>* unhandled_permissions) override;
+
+ // APIPermission overrides.
+ PermissionIDSet GetPermissions() const override;
+
+ // Permission strings.
+ static const char kAllAutoDetectedPermission[];
+ static const char kScanPermission[];
+ static const char kReadPermission[];
+ static const char kCopyToPermission[];
+ static const char kDeletePermission[];
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_MEDIA_GALLERIES_PERMISSION_H_
diff --git a/chromium/extensions/common/permissions/media_galleries_permission_data.cc b/chromium/extensions/common/permissions/media_galleries_permission_data.cc
new file mode 100644
index 00000000000..495c1f917eb
--- /dev/null
+++ b/chromium/extensions/common/permissions/media_galleries_permission_data.cc
@@ -0,0 +1,61 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/media_galleries_permission_data.h"
+
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "extensions/common/permissions/media_galleries_permission.h"
+
+namespace extensions {
+
+MediaGalleriesPermissionData::MediaGalleriesPermissionData() {
+}
+
+bool MediaGalleriesPermissionData::Check(
+ const APIPermission::CheckParam* param) const {
+ if (!param)
+ return false;
+
+ const MediaGalleriesPermission::CheckParam& specific_param =
+ *static_cast<const MediaGalleriesPermission::CheckParam*>(param);
+ return permission_ == specific_param.permission;
+}
+
+scoped_ptr<base::Value> MediaGalleriesPermissionData::ToValue() const {
+ return scoped_ptr<base::Value>(new base::StringValue(permission_));
+}
+
+bool MediaGalleriesPermissionData::FromValue(const base::Value* value) {
+ if (!value)
+ return false;
+
+ std::string raw_permission;
+ if (!value->GetAsString(&raw_permission))
+ return false;
+
+ std::string permission;
+ base::TrimWhitespaceASCII(raw_permission, base::TRIM_ALL, &permission);
+
+ if (permission == MediaGalleriesPermission::kAllAutoDetectedPermission ||
+ permission == MediaGalleriesPermission::kReadPermission ||
+ permission == MediaGalleriesPermission::kCopyToPermission ||
+ permission == MediaGalleriesPermission::kDeletePermission) {
+ permission_ = permission;
+ return true;
+ }
+ return false;
+}
+
+bool MediaGalleriesPermissionData::operator<(
+ const MediaGalleriesPermissionData& rhs) const {
+ return permission_ < rhs.permission_;
+}
+
+bool MediaGalleriesPermissionData::operator==(
+ const MediaGalleriesPermissionData& rhs) const {
+ return permission_ == rhs.permission_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/media_galleries_permission_data.h b/chromium/extensions/common/permissions/media_galleries_permission_data.h
new file mode 100644
index 00000000000..05ae5789416
--- /dev/null
+++ b/chromium/extensions/common/permissions/media_galleries_permission_data.h
@@ -0,0 +1,50 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_MEDIA_GALLERIES_PERMISSION_DATA_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_MEDIA_GALLERIES_PERMISSION_DATA_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/permissions/api_permission.h"
+
+namespace base {
+class Value;
+}
+
+namespace extensions {
+
+// A MediaGalleriesPermissionData instance represents a single part of the
+// MediaGalleriesPermission. e.g. "read" or "allAutoDetected".
+class MediaGalleriesPermissionData {
+ public:
+ MediaGalleriesPermissionData();
+
+ // Check if |param| (which must be a MediaGalleriesPermission::CheckParam)
+ // matches the encapsulated attribute.
+ bool Check(const APIPermission::CheckParam* param) const;
+
+ // Convert |this| into a base::Value.
+ scoped_ptr<base::Value> ToValue() const;
+
+ // Populate |this| from a base::Value.
+ bool FromValue(const base::Value* value);
+
+ bool operator<(const MediaGalleriesPermissionData& rhs) const;
+ bool operator==(const MediaGalleriesPermissionData& rhs) const;
+
+ std::string permission() const { return permission_; }
+
+ // This accessor is provided for IPC_STRUCT_TRAITS_MEMBER. Please think
+ // twice before using it for anything else.
+ std::string& permission() { return permission_; }
+
+ private:
+ std::string permission_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_MEDIA_GALLERIES_PERMISSION_DATA_H_
diff --git a/chromium/extensions/common/permissions/permission_message.cc b/chromium/extensions/common/permissions/permission_message.cc
new file mode 100644
index 00000000000..83553e66dd3
--- /dev/null
+++ b/chromium/extensions/common/permissions/permission_message.cc
@@ -0,0 +1,23 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/permission_message.h"
+
+namespace extensions {
+
+PermissionMessage::PermissionMessage(const base::string16& message,
+ const PermissionIDSet& permissions)
+ : message_(message), permissions_(permissions) {}
+
+PermissionMessage::PermissionMessage(
+ const base::string16& message,
+ const PermissionIDSet& permissions,
+ const std::vector<base::string16>& submessages)
+ : message_(message), permissions_(permissions), submessages_(submessages) {}
+
+PermissionMessage::PermissionMessage(const PermissionMessage& other) = default;
+
+PermissionMessage::~PermissionMessage() {}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/permission_message.h b/chromium/extensions/common/permissions/permission_message.h
new file mode 100644
index 00000000000..28f1f1571ef
--- /dev/null
+++ b/chromium/extensions/common/permissions/permission_message.h
@@ -0,0 +1,68 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_PERMISSION_MESSAGE_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_PERMISSION_MESSAGE_H_
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include "extensions/common/permissions/api_permission_set.h"
+
+namespace extensions {
+
+// The new kind of Chrome app/extension permission messages.
+//
+// A PermissionMessage is an immutable object that represents a single bullet
+// in the list of an app or extension's permissions. It contains the localized
+// permission message to display, as well as the set of permissions that
+// contributed to that message (and should be revoked if this permission is
+// revoked). It can also optionally contain a list of sub-messages which should
+// appear as nested bullet points below the main one.
+//
+// |permissions| contains the permissions that are 'represented' by this
+// message and should be revoked if this permission message is revoked. Note
+// that other permissions could have contributed to the message, but these are
+// the ones 'contained' in this message - if this set is taken for all
+// PermissionMessages, each permission will only be in at most one
+// PermissionMessage.
+//
+// Some permissions may contain nested messages, stored in |submessages|. These
+// are appropriate to show as nested bullet points below the permission,
+// collapsed if needed. For example, host permission messages may list all the
+// sites the app has access to in |submessages|, with a summary message in
+// |message|.
+//
+// TODO(sashab): Add a custom revoke action for each permission and nested
+// permission message, registerable as a callback.
+class PermissionMessage {
+ public:
+ PermissionMessage(const base::string16& message,
+ const PermissionIDSet& permissions);
+ PermissionMessage(const base::string16& message,
+ const PermissionIDSet& permissions,
+ const std::vector<base::string16>& submessages);
+ PermissionMessage(const PermissionMessage& other);
+ virtual ~PermissionMessage();
+
+ const base::string16& message() const { return message_; }
+ const PermissionIDSet& permissions() const { return permissions_; }
+ const std::vector<base::string16>& submessages() const {
+ return submessages_;
+ }
+
+ private:
+ const base::string16 message_;
+ const PermissionIDSet permissions_;
+ const std::vector<base::string16> submessages_;
+};
+
+// TODO(treib): Make this an std::vector when we have C++11 library support on
+// all platforms. (In C++03, std::vector's elements must be copy-assignable...)
+typedef std::list<PermissionMessage> PermissionMessages;
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_PERMISSION_MESSAGE_H_
diff --git a/chromium/extensions/common/permissions/permission_message_provider.cc b/chromium/extensions/common/permissions/permission_message_provider.cc
new file mode 100644
index 00000000000..6f63133a7a5
--- /dev/null
+++ b/chromium/extensions/common/permissions/permission_message_provider.cc
@@ -0,0 +1,18 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/permission_message_provider.h"
+
+#include "base/metrics/field_trial.h"
+#include "base/strings/string_split.h"
+#include "extensions/common/extensions_client.h"
+
+namespace extensions {
+
+// static
+const PermissionMessageProvider* PermissionMessageProvider::Get() {
+ return &(ExtensionsClient::Get()->GetPermissionMessageProvider());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/permission_message_provider.h b/chromium/extensions/common/permissions/permission_message_provider.h
new file mode 100644
index 00000000000..a75b46eed4a
--- /dev/null
+++ b/chromium/extensions/common/permissions/permission_message_provider.h
@@ -0,0 +1,58 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_PERMISSION_MESSAGE_PROVIDER_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_PERMISSION_MESSAGE_PROVIDER_H_
+
+#include <vector>
+
+#include "extensions/common/manifest.h"
+#include "extensions/common/permissions/permission_message.h"
+
+namespace extensions {
+
+class PermissionIDSet;
+class PermissionSet;
+
+// The PermissionMessageProvider interprets permissions, translating them
+// into warning messages to show to the user. It also determines whether
+// a new set of permissions entails showing new warning messages.
+class PermissionMessageProvider {
+ public:
+ PermissionMessageProvider() {}
+ virtual ~PermissionMessageProvider() {}
+
+ // Return the global permission message provider.
+ static const PermissionMessageProvider* Get();
+
+ // Calculates and returns the full list of permission messages for the given
+ // |permissions|. This involves converting the given PermissionIDs into
+ // localized messages, as well as coalescing and parameterizing any messages
+ // that require the permission ID's argument in their message.
+ virtual PermissionMessages GetPermissionMessages(
+ const PermissionIDSet& permissions) const = 0;
+
+ // Returns true if |new_permissions| has a greater privilege level than
+ // |old_permissions|.
+ // Whether certain permissions are considered varies by extension type.
+ // TODO(sashab): Add an implementation of this method that uses
+ // PermissionIDSet instead, then deprecate this one.
+ virtual bool IsPrivilegeIncrease(const PermissionSet& old_permissions,
+ const PermissionSet& new_permissions,
+ Manifest::Type extension_type) const = 0;
+
+ // Given the permissions for an extension, finds the IDs of all the
+ // permissions for that extension (including API, manifest and host
+ // permissions).
+ // TODO(sashab): This uses the legacy PermissionSet type. Deprecate or rename
+ // this type, and make this take as little as is needed to work out the
+ // PermissionIDSet.
+ virtual PermissionIDSet GetAllPermissionIDs(
+ const PermissionSet& permissions,
+ Manifest::Type extension_type) const = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_PERMISSION_MESSAGE_PROVIDER_H_
diff --git a/chromium/extensions/common/permissions/permission_message_test_util.cc b/chromium/extensions/common/permissions/permission_message_test_util.cc
new file mode 100644
index 00000000000..d4ff6395e34
--- /dev/null
+++ b/chromium/extensions/common/permissions/permission_message_test_util.cc
@@ -0,0 +1,322 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/permission_message_test_util.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <iterator>
+
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/common/permissions/permission_message_provider.h"
+#include "extensions/common/permissions/permissions_data.h"
+
+namespace extensions {
+
+namespace {
+
+PermissionMessages GetMessages(const PermissionSet& permissions,
+ Manifest::Type extension_type) {
+ const PermissionMessageProvider* provider = PermissionMessageProvider::Get();
+ return provider->GetPermissionMessages(
+ provider->GetAllPermissionIDs(permissions, extension_type));
+}
+
+std::vector<base::string16> MakeVectorString16(const base::string16& str) {
+ return std::vector<base::string16>(1, str);
+}
+
+std::vector<base::string16> MakeVectorString16(const base::string16& str1,
+ const base::string16& str2) {
+ std::vector<base::string16> result;
+ result.push_back(str1);
+ result.push_back(str2);
+ return result;
+}
+
+std::vector<base::string16> MakeVectorString16(const std::string& str1,
+ const std::string& str2) {
+ return MakeVectorString16(base::UTF8ToUTF16(str1), base::UTF8ToUTF16(str2));
+}
+
+std::vector<base::string16> MakeVectorString16(
+ const std::vector<std::string>& vec) {
+ std::vector<base::string16> result;
+ for (const std::string& msg : vec)
+ result.push_back(base::UTF8ToUTF16(msg));
+ return result;
+}
+
+std::vector<std::vector<base::string16>> MakeVectorVectorString16(
+ const std::vector<base::string16>& vec) {
+ return std::vector<std::vector<base::string16>>(1, vec);
+}
+
+std::vector<std::vector<base::string16>> MakeVectorVectorString16(
+ const std::vector<std::vector<std::string>>& vecs) {
+ std::vector<std::vector<base::string16>> result;
+ for (const std::vector<std::string>& vec : vecs)
+ result.push_back(MakeVectorString16(vec));
+ return result;
+}
+
+// Returns the vector of messages concatenated into a single string, separated
+// by newlines, e.g.: "Bar"\n"Baz"\n
+base::string16 MessagesVectorToString(
+ const std::vector<base::string16>& messages) {
+ if (messages.empty())
+ return base::ASCIIToUTF16("\n");
+ return base::ASCIIToUTF16("\"") +
+ base::JoinString(messages, base::ASCIIToUTF16("\"\n\"")) +
+ base::ASCIIToUTF16("\"\n");
+}
+
+base::string16 MessagesToString(const PermissionMessages& messages) {
+ std::vector<base::string16> messages_vec;
+ for (const PermissionMessage& msg : messages)
+ messages_vec.push_back(msg.message());
+ return MessagesVectorToString(messages_vec);
+}
+
+bool CheckThatSubmessagesMatch(
+ const base::string16& message,
+ const std::vector<base::string16>& expected_submessages,
+ const std::vector<base::string16>& actual_submessages) {
+ bool result = true;
+
+ std::vector<base::string16> expected_sorted(expected_submessages);
+ std::sort(expected_sorted.begin(), expected_sorted.end());
+
+ std::vector<base::string16> actual_sorted(actual_submessages);
+ std::sort(actual_sorted.begin(), actual_sorted.end());
+ if (expected_sorted != actual_sorted) {
+ // This is always a failure, even within an EXPECT_FALSE.
+ // Message: Expected submessages for "Message" to be { "Foo" }, but got
+ // { "Bar", "Baz" }
+ ADD_FAILURE() << "Expected submessages for \"" << message << "\" to be:\n"
+ << MessagesVectorToString(expected_sorted) << "But got:\n"
+ << MessagesVectorToString(actual_sorted);
+ result = false;
+ }
+
+ return result;
+}
+
+testing::AssertionResult VerifyHasPermissionMessageImpl(
+ const base::string16& expected_message,
+ const std::vector<base::string16>& expected_submessages,
+ const PermissionMessages& actual_messages) {
+ auto message_it =
+ std::find_if(actual_messages.begin(), actual_messages.end(),
+ [&expected_message](const PermissionMessage& msg) {
+ return msg.message() == expected_message;
+ });
+ bool found = message_it != actual_messages.end();
+ if (!found) {
+ // Message: Expected messages to contain "Foo", but got { "Bar", "Baz" }
+ return testing::AssertionFailure() << "Expected messages to contain \""
+ << expected_message << "\", but got "
+ << MessagesToString(actual_messages);
+ }
+
+ if (!CheckThatSubmessagesMatch(expected_message, expected_submessages,
+ message_it->submessages())) {
+ return testing::AssertionFailure();
+ }
+
+ // Message: Expected messages NOT to contain "Foo", but got { "Bar", "Baz" }
+ return testing::AssertionSuccess() << "Expected messages NOT to contain \""
+ << expected_message << "\", but got "
+ << MessagesToString(actual_messages);
+}
+
+testing::AssertionResult VerifyPermissionMessagesWithSubmessagesImpl(
+ const std::vector<base::string16>& expected_messages,
+ const std::vector<std::vector<base::string16>>& expected_submessages,
+ const PermissionMessages& actual_messages,
+ bool check_order) {
+ CHECK_EQ(expected_messages.size(), expected_submessages.size());
+ if (expected_messages.size() != actual_messages.size()) {
+ // Message: Expected 2 messages { "Bar", "Baz" }, but got 0 {}
+ return testing::AssertionFailure()
+ << "Expected " << expected_messages.size() << " messages:\n"
+ << MessagesVectorToString(expected_messages) << "But got "
+ << actual_messages.size() << " messages:\n"
+ << MessagesToString(actual_messages);
+ }
+
+ if (check_order) {
+ auto it = actual_messages.begin();
+ for (size_t i = 0; i < expected_messages.size(); i++, ++it) {
+ const PermissionMessage& actual_message = *it;
+ if (expected_messages[i] != actual_message.message()) {
+ // Message: Expected messages to be { "Foo" }, but got { "Bar", "Baz" }
+ return testing::AssertionFailure()
+ << "Expected messages to be:\n"
+ << MessagesVectorToString(expected_messages) << "But got:\n"
+ << MessagesToString(actual_messages);
+ }
+
+ if (!CheckThatSubmessagesMatch(expected_messages[i],
+ expected_submessages[i],
+ actual_message.submessages())) {
+ return testing::AssertionFailure();
+ }
+ }
+ } else {
+ for (size_t i = 0; i < expected_messages.size(); i++) {
+ testing::AssertionResult result = VerifyHasPermissionMessageImpl(
+ expected_messages[i], expected_submessages[i], actual_messages);
+ if (!result)
+ return result;
+ }
+ }
+ return testing::AssertionSuccess();
+}
+
+} // namespace
+
+testing::AssertionResult VerifyHasPermissionMessage(
+ const PermissionsData* permissions_data,
+ const std::string& expected_message) {
+ return VerifyHasPermissionMessage(permissions_data,
+ base::UTF8ToUTF16(expected_message));
+}
+
+testing::AssertionResult VerifyHasPermissionMessage(
+ const PermissionsData* permissions_data,
+ const base::string16& expected_message) {
+ return VerifyHasPermissionMessageImpl(
+ expected_message, std::vector<base::string16>(),
+ permissions_data->GetPermissionMessages());
+}
+
+testing::AssertionResult VerifyHasPermissionMessage(
+ const PermissionSet& permissions,
+ Manifest::Type extension_type,
+ const std::string& expected_message) {
+ return VerifyHasPermissionMessage(permissions, extension_type,
+ base::UTF8ToUTF16(expected_message));
+}
+
+testing::AssertionResult VerifyHasPermissionMessage(
+ const PermissionSet& permissions,
+ Manifest::Type extension_type,
+ const base::string16& expected_message) {
+ return VerifyHasPermissionMessageImpl(
+ expected_message, std::vector<base::string16>(),
+ GetMessages(permissions, extension_type));
+}
+
+testing::AssertionResult VerifyNoPermissionMessages(
+ const PermissionsData* permissions_data) {
+ return VerifyPermissionMessages(permissions_data,
+ std::vector<base::string16>(), true);
+}
+
+testing::AssertionResult VerifyOnePermissionMessage(
+ const PermissionsData* permissions_data,
+ const std::string& expected_message) {
+ return VerifyOnePermissionMessage(permissions_data,
+ base::UTF8ToUTF16(expected_message));
+}
+
+testing::AssertionResult VerifyOnePermissionMessage(
+ const PermissionsData* permissions_data,
+ const base::string16& expected_message) {
+ return VerifyPermissionMessages(permissions_data,
+ MakeVectorString16(expected_message), true);
+}
+
+testing::AssertionResult VerifyOnePermissionMessage(
+ const PermissionSet& permissions,
+ Manifest::Type extension_type,
+ const base::string16& expected_message) {
+ return VerifyPermissionMessagesWithSubmessagesImpl(
+ MakeVectorString16(expected_message),
+ std::vector<std::vector<base::string16>>(1),
+ GetMessages(permissions, extension_type), true);
+}
+
+testing::AssertionResult VerifyOnePermissionMessageWithSubmessages(
+ const PermissionsData* permissions_data,
+ const std::string& expected_message,
+ const std::vector<std::string>& expected_submessages) {
+ return VerifyOnePermissionMessageWithSubmessages(
+ permissions_data, base::UTF8ToUTF16(expected_message),
+ MakeVectorString16(expected_submessages));
+}
+
+testing::AssertionResult VerifyOnePermissionMessageWithSubmessages(
+ const PermissionsData* permissions_data,
+ const base::string16& expected_message,
+ const std::vector<base::string16>& expected_submessages) {
+ return VerifyPermissionMessagesWithSubmessages(
+ permissions_data, MakeVectorString16(expected_message),
+ MakeVectorVectorString16(expected_submessages), true);
+}
+
+testing::AssertionResult VerifyTwoPermissionMessages(
+ const PermissionsData* permissions_data,
+ const std::string& expected_message_1,
+ const std::string& expected_message_2,
+ bool check_order) {
+ return VerifyPermissionMessages(
+ permissions_data,
+ MakeVectorString16(expected_message_1, expected_message_2), check_order);
+}
+
+testing::AssertionResult VerifyTwoPermissionMessages(
+ const PermissionsData* permissions_data,
+ const base::string16& expected_message_1,
+ const base::string16& expected_message_2,
+ bool check_order) {
+ return VerifyPermissionMessages(
+ permissions_data,
+ MakeVectorString16(expected_message_1, expected_message_2), check_order);
+}
+
+testing::AssertionResult VerifyPermissionMessages(
+ const PermissionsData* permissions_data,
+ const std::vector<std::string>& expected_messages,
+ bool check_order) {
+ return VerifyPermissionMessages(
+ permissions_data, MakeVectorString16(expected_messages), check_order);
+}
+
+testing::AssertionResult VerifyPermissionMessages(
+ const PermissionsData* permissions_data,
+ const std::vector<base::string16>& expected_messages,
+ bool check_order) {
+ return VerifyPermissionMessagesWithSubmessages(
+ permissions_data, expected_messages,
+ std::vector<std::vector<base::string16>>(expected_messages.size()),
+ check_order);
+}
+
+testing::AssertionResult VerifyPermissionMessagesWithSubmessages(
+ const PermissionsData* permissions_data,
+ const std::vector<std::string>& expected_messages,
+ const std::vector<std::vector<std::string>>& expected_submessages,
+ bool check_order) {
+ return VerifyPermissionMessagesWithSubmessages(
+ permissions_data, MakeVectorString16(expected_messages),
+ MakeVectorVectorString16(expected_submessages), check_order);
+}
+
+testing::AssertionResult VerifyPermissionMessagesWithSubmessages(
+ const PermissionsData* permissions_data,
+ const std::vector<base::string16>& expected_messages,
+ const std::vector<std::vector<base::string16>>& expected_submessages,
+ bool check_order) {
+ CHECK_EQ(expected_messages.size(), expected_submessages.size());
+ return VerifyPermissionMessagesWithSubmessagesImpl(
+ expected_messages, expected_submessages,
+ permissions_data->GetPermissionMessages(), check_order);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/permission_message_test_util.h b/chromium/extensions/common/permissions/permission_message_test_util.h
new file mode 100644
index 00000000000..34e36f33e62
--- /dev/null
+++ b/chromium/extensions/common/permissions/permission_message_test_util.h
@@ -0,0 +1,91 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_PERMISSION_MESSAGE_TEST_UTIL_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_PERMISSION_MESSAGE_TEST_UTIL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "extensions/common/manifest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+class PermissionsData;
+class PermissionSet;
+
+testing::AssertionResult VerifyHasPermissionMessage(
+ const PermissionsData* permissions_data,
+ const std::string& expected_message);
+testing::AssertionResult VerifyHasPermissionMessage(
+ const PermissionsData* permissions_data,
+ const base::string16& expected_message);
+testing::AssertionResult VerifyHasPermissionMessage(
+ const PermissionSet& permissions,
+ Manifest::Type extension_type,
+ const std::string& expected_message);
+testing::AssertionResult VerifyHasPermissionMessage(
+ const PermissionSet& permissions,
+ Manifest::Type extension_type,
+ const base::string16& expected_message);
+
+testing::AssertionResult VerifyNoPermissionMessages(
+ const PermissionsData* permissions_data);
+
+testing::AssertionResult VerifyOnePermissionMessage(
+ const PermissionsData* permissions_data,
+ const std::string& expected_message);
+testing::AssertionResult VerifyOnePermissionMessage(
+ const PermissionsData* permissions_data,
+ const base::string16& expected_message);
+testing::AssertionResult VerifyOnePermissionMessage(
+ const PermissionSet& permissions,
+ Manifest::Type extension_type,
+ const base::string16& expected_message);
+
+testing::AssertionResult VerifyOnePermissionMessageWithSubmessages(
+ const PermissionsData* permissions_data,
+ const std::string& expected_message,
+ const std::vector<std::string>& expected_submessages);
+testing::AssertionResult VerifyOnePermissionMessageWithSubmessages(
+ const PermissionsData* permissions_data,
+ const base::string16& expected_message,
+ const std::vector<base::string16>& expected_submessages);
+
+testing::AssertionResult VerifyTwoPermissionMessages(
+ const PermissionsData* permissions_data,
+ const std::string& expected_message_1,
+ const std::string& expected_message_2,
+ bool check_order);
+testing::AssertionResult VerifyTwoPermissionMessages(
+ const PermissionsData* permissions_data,
+ const base::string16& expected_message_1,
+ const base::string16& expected_message_2,
+ bool check_order);
+
+testing::AssertionResult VerifyPermissionMessages(
+ const PermissionsData* permissions_data,
+ const std::vector<std::string>& expected_messages,
+ bool check_order);
+testing::AssertionResult VerifyPermissionMessages(
+ const PermissionsData* permissions_data,
+ const std::vector<base::string16>& expected_messages,
+ bool check_order);
+
+testing::AssertionResult VerifyPermissionMessagesWithSubmessages(
+ const PermissionsData* permissions_data,
+ const std::vector<std::string>& expected_messages,
+ const std::vector<std::vector<std::string>>& expected_submessages,
+ bool check_order);
+testing::AssertionResult VerifyPermissionMessagesWithSubmessages(
+ const PermissionsData* permissions_data,
+ const std::vector<base::string16>& expected_messages,
+ const std::vector<std::vector<base::string16>>& expected_submessages,
+ bool check_order);
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_PERMISSION_MESSAGE_TEST_UTIL_H_
diff --git a/chromium/extensions/common/permissions/permission_message_util.cc b/chromium/extensions/common/permissions/permission_message_util.cc
new file mode 100644
index 00000000000..0ee3abf27de
--- /dev/null
+++ b/chromium/extensions/common/permissions/permission_message_util.cc
@@ -0,0 +1,87 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/permission_message_util.h"
+
+#include <stddef.h>
+#include <vector>
+
+#include "base/strings/string_split.h"
+#include "extensions/common/url_pattern_set.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "url/url_constants.h"
+
+using extensions::URLPatternSet;
+
+namespace {
+
+// Helper for GetDistinctHosts(): com > net > org > everything else.
+bool RcdBetterThan(const std::string& a, const std::string& b) {
+ if (a == b)
+ return false;
+ if (a == "com")
+ return true;
+ if (a == "net")
+ return b != "com";
+ if (a == "org")
+ return b != "com" && b != "net";
+ return false;
+}
+
+} // namespace
+
+namespace permission_message_util {
+
+std::set<std::string> GetDistinctHosts(const URLPatternSet& host_patterns,
+ bool include_rcd,
+ bool exclude_file_scheme) {
+ // Each item is a host split into two parts: host without RCDs and
+ // current best RCD.
+ typedef base::StringPairs HostVector;
+ HostVector hosts_best_rcd;
+ for (const URLPattern& pattern : host_patterns) {
+ if (exclude_file_scheme && pattern.scheme() == url::kFileScheme)
+ continue;
+
+ std::string host = pattern.host();
+
+ // Add the subdomain wildcard back to the host, if necessary.
+ if (pattern.match_subdomains())
+ host = "*." + host;
+
+ // If the host has an RCD, split it off so we can detect duplicates.
+ std::string rcd;
+ size_t reg_len = net::registry_controlled_domains::GetRegistryLength(
+ host,
+ net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
+ net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
+ if (reg_len && reg_len != std::string::npos) {
+ if (include_rcd) // else leave rcd empty
+ rcd = host.substr(host.size() - reg_len);
+ host = host.substr(0, host.size() - reg_len);
+ }
+
+ // Check if we've already seen this host.
+ HostVector::iterator it = hosts_best_rcd.begin();
+ for (; it != hosts_best_rcd.end(); ++it) {
+ if (it->first == host)
+ break;
+ }
+ // If this host was found, replace the RCD if this one is better.
+ if (it != hosts_best_rcd.end()) {
+ if (include_rcd && RcdBetterThan(rcd, it->second))
+ it->second = rcd;
+ } else { // Previously unseen host, append it.
+ hosts_best_rcd.push_back(std::make_pair(host, rcd));
+ }
+ }
+
+ // Build up the result by concatenating hosts and RCDs.
+ std::set<std::string> distinct_hosts;
+ for (const auto& host_rcd : hosts_best_rcd)
+ distinct_hosts.insert(host_rcd.first + host_rcd.second);
+ return distinct_hosts;
+}
+
+} // namespace permission_message_util
diff --git a/chromium/extensions/common/permissions/permission_message_util.h b/chromium/extensions/common/permissions/permission_message_util.h
new file mode 100644
index 00000000000..66dd01d7680
--- /dev/null
+++ b/chromium/extensions/common/permissions/permission_message_util.h
@@ -0,0 +1,24 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_PERMISSION_MESSAGE_UTIL_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_PERMISSION_MESSAGE_UTIL_H_
+
+#include <set>
+#include <string>
+
+namespace extensions {
+class URLPatternSet;
+}
+
+namespace permission_message_util {
+
+std::set<std::string> GetDistinctHosts(
+ const extensions::URLPatternSet& host_patterns,
+ bool include_rcd,
+ bool exclude_file_scheme);
+
+} // namespace permission_message_util
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_PERMISSION_MESSAGE_UTIL_H_
diff --git a/chromium/extensions/common/permissions/permission_set.cc b/chromium/extensions/common/permissions/permission_set.cc
new file mode 100644
index 00000000000..a3e3f4144f6
--- /dev/null
+++ b/chromium/extensions/common/permissions/permission_set.cc
@@ -0,0 +1,268 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/permission_set.h"
+
+#include "extensions/common/permissions/permissions_info.h"
+#include "extensions/common/url_pattern.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+namespace {
+
+void AddPatternsAndRemovePaths(const URLPatternSet& set, URLPatternSet* out) {
+ DCHECK(out);
+ for (URLPatternSet::const_iterator i = set.begin(); i != set.end(); ++i) {
+ URLPattern p = *i;
+ p.SetPath("/*");
+ out->AddPattern(p);
+ }
+}
+
+} // namespace
+
+//
+// PermissionSet
+//
+
+PermissionSet::PermissionSet() : should_warn_all_hosts_(UNINITIALIZED) {}
+
+PermissionSet::PermissionSet(
+ const APIPermissionSet& apis,
+ const ManifestPermissionSet& manifest_permissions,
+ const URLPatternSet& explicit_hosts,
+ const URLPatternSet& scriptable_hosts)
+ : apis_(apis),
+ manifest_permissions_(manifest_permissions),
+ scriptable_hosts_(scriptable_hosts),
+ should_warn_all_hosts_(UNINITIALIZED) {
+ AddPatternsAndRemovePaths(explicit_hosts, &explicit_hosts_);
+ InitImplicitPermissions();
+ InitEffectiveHosts();
+}
+
+PermissionSet::~PermissionSet() {}
+
+// static
+scoped_ptr<const PermissionSet> PermissionSet::CreateDifference(
+ const PermissionSet& set1,
+ const PermissionSet& set2) {
+ APIPermissionSet apis;
+ APIPermissionSet::Difference(set1.apis(), set2.apis(), &apis);
+
+ ManifestPermissionSet manifest_permissions;
+ ManifestPermissionSet::Difference(set1.manifest_permissions(),
+ set2.manifest_permissions(),
+ &manifest_permissions);
+
+ URLPatternSet explicit_hosts = URLPatternSet::CreateDifference(
+ set1.explicit_hosts(), set2.explicit_hosts());
+
+ URLPatternSet scriptable_hosts = URLPatternSet::CreateDifference(
+ set1.scriptable_hosts(), set2.scriptable_hosts());
+
+ return make_scoped_ptr(new PermissionSet(apis, manifest_permissions,
+ explicit_hosts, scriptable_hosts));
+}
+
+// static
+scoped_ptr<const PermissionSet> PermissionSet::CreateIntersection(
+ const PermissionSet& set1,
+ const PermissionSet& set2) {
+ APIPermissionSet apis;
+ APIPermissionSet::Intersection(set1.apis(), set2.apis(), &apis);
+
+ ManifestPermissionSet manifest_permissions;
+ ManifestPermissionSet::Intersection(set1.manifest_permissions(),
+ set2.manifest_permissions(),
+ &manifest_permissions);
+
+ URLPatternSet explicit_hosts = URLPatternSet::CreateSemanticIntersection(
+ set1.explicit_hosts(), set2.explicit_hosts());
+ URLPatternSet scriptable_hosts = URLPatternSet::CreateSemanticIntersection(
+ set1.scriptable_hosts(), set2.scriptable_hosts());
+
+ return make_scoped_ptr(new PermissionSet(apis, manifest_permissions,
+ explicit_hosts, scriptable_hosts));
+}
+
+// static
+scoped_ptr<const PermissionSet> PermissionSet::CreateUnion(
+ const PermissionSet& set1,
+ const PermissionSet& set2) {
+ APIPermissionSet apis;
+ APIPermissionSet::Union(set1.apis(), set2.apis(), &apis);
+
+ ManifestPermissionSet manifest_permissions;
+ ManifestPermissionSet::Union(set1.manifest_permissions(),
+ set2.manifest_permissions(),
+ &manifest_permissions);
+
+ URLPatternSet explicit_hosts =
+ URLPatternSet::CreateUnion(set1.explicit_hosts(), set2.explicit_hosts());
+
+ URLPatternSet scriptable_hosts = URLPatternSet::CreateUnion(
+ set1.scriptable_hosts(), set2.scriptable_hosts());
+
+ return make_scoped_ptr(new PermissionSet(apis, manifest_permissions,
+ explicit_hosts, scriptable_hosts));
+}
+
+bool PermissionSet::operator==(
+ const PermissionSet& rhs) const {
+ return apis_ == rhs.apis_ &&
+ manifest_permissions_ == rhs.manifest_permissions_ &&
+ scriptable_hosts_ == rhs.scriptable_hosts_ &&
+ explicit_hosts_ == rhs.explicit_hosts_;
+}
+
+bool PermissionSet::operator!=(const PermissionSet& rhs) const {
+ return !(*this == rhs);
+}
+
+scoped_ptr<const PermissionSet> PermissionSet::Clone() const {
+ return make_scoped_ptr(new PermissionSet(*this));
+}
+
+bool PermissionSet::Contains(const PermissionSet& set) const {
+ return apis_.Contains(set.apis()) &&
+ manifest_permissions_.Contains(set.manifest_permissions()) &&
+ explicit_hosts().Contains(set.explicit_hosts()) &&
+ scriptable_hosts().Contains(set.scriptable_hosts());
+}
+
+std::set<std::string> PermissionSet::GetAPIsAsStrings() const {
+ std::set<std::string> apis_str;
+ for (APIPermissionSet::const_iterator i = apis_.begin();
+ i != apis_.end(); ++i) {
+ apis_str.insert(i->name());
+ }
+ return apis_str;
+}
+
+bool PermissionSet::IsEmpty() const {
+ // Not default if any host permissions are present.
+ if (!(explicit_hosts().is_empty() && scriptable_hosts().is_empty()))
+ return false;
+
+ // Or if it has no api permissions.
+ return apis().empty() && manifest_permissions().empty();
+}
+
+bool PermissionSet::HasAPIPermission(
+ APIPermission::ID id) const {
+ return apis().find(id) != apis().end();
+}
+
+bool PermissionSet::HasAPIPermission(const std::string& permission_name) const {
+ const APIPermissionInfo* permission =
+ PermissionsInfo::GetInstance()->GetByName(permission_name);
+ // Ensure our PermissionsProvider is aware of this permission.
+ CHECK(permission) << permission_name;
+ return (permission && apis_.count(permission->id()));
+}
+
+bool PermissionSet::CheckAPIPermission(APIPermission::ID permission) const {
+ return CheckAPIPermissionWithParam(permission, NULL);
+}
+
+bool PermissionSet::CheckAPIPermissionWithParam(
+ APIPermission::ID permission,
+ const APIPermission::CheckParam* param) const {
+ APIPermissionSet::const_iterator iter = apis().find(permission);
+ if (iter == apis().end())
+ return false;
+ return iter->Check(param);
+}
+
+bool PermissionSet::HasExplicitAccessToOrigin(
+ const GURL& origin) const {
+ return explicit_hosts().MatchesURL(origin);
+}
+
+bool PermissionSet::HasScriptableAccessToURL(
+ const GURL& origin) const {
+ // We only need to check our host list to verify access. The host list should
+ // already reflect any special rules (such as chrome://favicon, all hosts
+ // access, etc.).
+ return scriptable_hosts().MatchesURL(origin);
+}
+
+bool PermissionSet::HasEffectiveAccessToAllHosts() const {
+ // There are two ways this set can have effective access to all hosts:
+ // 1) it has an <all_urls> URL pattern.
+ // 2) it has a named permission with implied full URL access.
+ if (effective_hosts().MatchesAllURLs())
+ return true;
+
+ for (APIPermissionSet::const_iterator i = apis().begin();
+ i != apis().end(); ++i) {
+ if (i->info()->implies_full_url_access())
+ return true;
+ }
+ return false;
+}
+
+bool PermissionSet::ShouldWarnAllHosts() const {
+ if (should_warn_all_hosts_ == UNINITIALIZED)
+ InitShouldWarnAllHosts();
+ return should_warn_all_hosts_ == WARN_ALL_HOSTS;
+}
+
+bool PermissionSet::HasEffectiveAccessToURL(const GURL& url) const {
+ return effective_hosts().MatchesURL(url);
+}
+
+bool PermissionSet::HasEffectiveFullAccess() const {
+ for (APIPermissionSet::const_iterator i = apis().begin();
+ i != apis().end(); ++i) {
+ if (i->info()->implies_full_access())
+ return true;
+ }
+ return false;
+}
+
+PermissionSet::PermissionSet(const PermissionSet& permissions)
+ : apis_(permissions.apis_),
+ manifest_permissions_(permissions.manifest_permissions_),
+ explicit_hosts_(permissions.explicit_hosts_),
+ scriptable_hosts_(permissions.scriptable_hosts_),
+ effective_hosts_(permissions.effective_hosts_),
+ should_warn_all_hosts_(permissions.should_warn_all_hosts_) {}
+
+void PermissionSet::InitImplicitPermissions() {
+ // The downloads permission implies the internal version as well.
+ if (apis_.find(APIPermission::kDownloads) != apis_.end())
+ apis_.insert(APIPermission::kDownloadsInternal);
+
+ // The fileBrowserHandler permission implies the internal version as well.
+ if (apis_.find(APIPermission::kFileBrowserHandler) != apis_.end())
+ apis_.insert(APIPermission::kFileBrowserHandlerInternal);
+}
+
+void PermissionSet::InitEffectiveHosts() {
+ effective_hosts_ =
+ URLPatternSet::CreateUnion(explicit_hosts(), scriptable_hosts());
+}
+
+void PermissionSet::InitShouldWarnAllHosts() const {
+ if (HasEffectiveAccessToAllHosts()) {
+ should_warn_all_hosts_ = WARN_ALL_HOSTS;
+ return;
+ }
+
+ for (URLPatternSet::const_iterator iter = effective_hosts_.begin();
+ iter != effective_hosts_.end();
+ ++iter) {
+ if (iter->ImpliesAllHosts()) {
+ should_warn_all_hosts_ = WARN_ALL_HOSTS;
+ return;
+ }
+ }
+
+ should_warn_all_hosts_ = DONT_WARN_ALL_HOSTS;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/permission_set.h b/chromium/extensions/common/permissions/permission_set.h
new file mode 100644
index 00000000000..b03a8697f8d
--- /dev/null
+++ b/chromium/extensions/common/permissions/permission_set.h
@@ -0,0 +1,175 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_PERMISSION_SET_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_PERMISSION_SET_H_
+
+#include <set>
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/api_permission_set.h"
+#include "extensions/common/permissions/manifest_permission.h"
+#include "extensions/common/permissions/manifest_permission_set.h"
+#include "extensions/common/url_pattern_set.h"
+
+namespace extensions {
+
+// The PermissionSet is an immutable class that encapsulates an
+// extension's permissions. The class exposes set operations for combining and
+// manipulating the permissions.
+// TODO(sashab): PermissionIDSet should be called PermissionSet. Once
+// PermissionMessageProvider::GetCoalescedPermissionMessages() is the only
+// method used for generating permission messages, find the other users of this
+// class and deprecate or rename it as appropriate.
+class PermissionSet {
+ public:
+ // Creates an empty permission set (e.g. default permissions).
+ PermissionSet();
+
+ // Creates a new permission set based on the specified data: the API
+ // permissions, manifest key permissions, host permissions, and scriptable
+ // hosts. The effective hosts of the newly created permission set will be
+ // inferred from the given host permissions.
+ PermissionSet(const APIPermissionSet& apis,
+ const ManifestPermissionSet& manifest_permissions,
+ const URLPatternSet& explicit_hosts,
+ const URLPatternSet& scriptable_hosts);
+ ~PermissionSet();
+
+ // Creates a new permission set equal to |set1| - |set2|.
+ static scoped_ptr<const PermissionSet> CreateDifference(
+ const PermissionSet& set1,
+ const PermissionSet& set2);
+
+ // Creates a new permission set equal to the intersection of |set1| and
+ // |set2|.
+ static scoped_ptr<const PermissionSet> CreateIntersection(
+ const PermissionSet& set1,
+ const PermissionSet& set2);
+
+ // Creates a new permission set equal to the union of |set1| and |set2|.
+ static scoped_ptr<const PermissionSet> CreateUnion(const PermissionSet& set1,
+ const PermissionSet& set2);
+
+ bool operator==(const PermissionSet& rhs) const;
+ bool operator!=(const PermissionSet& rhs) const;
+
+ // Returns a copy of this PermissionSet.
+ scoped_ptr<const PermissionSet> Clone() const;
+
+ // Returns true if every API or host permission available to |set| is also
+ // available to this. In other words, if the API permissions of |set| are a
+ // subset of this, and the host permissions in this encompass those in |set|.
+ bool Contains(const PermissionSet& set) const;
+
+ // Gets the API permissions in this set as a set of strings.
+ std::set<std::string> GetAPIsAsStrings() const;
+
+ // Returns true if this is an empty set (e.g., the default permission set).
+ bool IsEmpty() const;
+
+ // Returns true if the set has the specified API permission.
+ bool HasAPIPermission(APIPermission::ID permission) const;
+
+ // Returns true if the |extension| explicitly requests access to the given
+ // |permission_name|. Note this does not include APIs without no corresponding
+ // permission, like "runtime" or "browserAction".
+ bool HasAPIPermission(const std::string& permission_name) const;
+
+ // Returns true if the set allows the given permission with the default
+ // permission detal.
+ bool CheckAPIPermission(APIPermission::ID permission) const;
+
+ // Returns true if the set allows the given permission and permission param.
+ bool CheckAPIPermissionWithParam(APIPermission::ID permission,
+ const APIPermission::CheckParam* param) const;
+
+ // Returns true if this includes permission to access |origin|.
+ bool HasExplicitAccessToOrigin(const GURL& origin) const;
+
+ // Returns true if this permission set includes access to script |url|.
+ bool HasScriptableAccessToURL(const GURL& url) const;
+
+ // Returns true if this permission set includes effective access to all
+ // origins.
+ bool HasEffectiveAccessToAllHosts() const;
+
+ // Returns true if this permission set has access to so many hosts, that we
+ // should treat it as all hosts for warning purposes.
+ // For example, '*://*.com/*'.
+ bool ShouldWarnAllHosts() const;
+
+ // Returns true if this permission set includes effective access to |url|.
+ bool HasEffectiveAccessToURL(const GURL& url) const;
+
+ // Returns true if this permission set effectively represents full access
+ // (e.g. native code).
+ bool HasEffectiveFullAccess() const;
+
+ const APIPermissionSet& apis() const { return apis_; }
+
+ const ManifestPermissionSet& manifest_permissions() const {
+ return manifest_permissions_;
+ }
+
+ const URLPatternSet& effective_hosts() const { return effective_hosts_; }
+
+ const URLPatternSet& explicit_hosts() const { return explicit_hosts_; }
+
+ const URLPatternSet& scriptable_hosts() const { return scriptable_hosts_; }
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(PermissionsTest, GetWarningMessages_AudioVideo);
+ FRIEND_TEST_ALL_PREFIXES(PermissionsTest, AccessToDevicesMessages);
+
+ // Deliberate copy constructor for cloning the set.
+ PermissionSet(const PermissionSet& permission_set);
+
+ // Adds permissions implied independently of other context.
+ void InitImplicitPermissions();
+
+ // Initializes the effective host permission based on the data in this set.
+ void InitEffectiveHosts();
+
+ // Initializes |has_access_to_most_hosts_|.
+ void InitShouldWarnAllHosts() const;
+
+ // The api list is used when deciding if an extension can access certain
+ // extension APIs and features.
+ APIPermissionSet apis_;
+
+ // The manifest key permission list is used when deciding if an extension
+ // can access certain extension APIs and features.
+ ManifestPermissionSet manifest_permissions_;
+
+ // The list of hosts that can be accessed directly from the extension.
+ // TODO(jstritar): Rename to "hosts_"?
+ URLPatternSet explicit_hosts_;
+
+ // The list of hosts that can be scripted by content scripts.
+ // TODO(jstritar): Rename to "user_script_hosts_"?
+ URLPatternSet scriptable_hosts_;
+
+ // The list of hosts this effectively grants access to.
+ URLPatternSet effective_hosts_;
+
+ enum ShouldWarnAllHostsType {
+ UNINITIALIZED = 0,
+ WARN_ALL_HOSTS,
+ DONT_WARN_ALL_HOSTS
+ };
+ // Cache whether this set implies access to all hosts, because it's
+ // non-trivial to compute (lazily initialized).
+ mutable ShouldWarnAllHostsType should_warn_all_hosts_;
+
+ DISALLOW_ASSIGN(PermissionSet);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_PERMISSION_SET_H_
diff --git a/chromium/extensions/common/permissions/permissions_data.cc b/chromium/extensions/common/permissions/permissions_data.cc
new file mode 100644
index 00000000000..c77c4ee511b
--- /dev/null
+++ b/chromium/extensions/common/permissions/permissions_data.cc
@@ -0,0 +1,368 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/permissions_data.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "content/public/common/url_constants.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/permissions_parser.h"
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/permission_message_provider.h"
+#include "extensions/common/switches.h"
+#include "extensions/common/url_pattern_set.h"
+#include "url/gurl.h"
+#include "url/url_constants.h"
+
+namespace extensions {
+
+namespace {
+
+PermissionsData::PolicyDelegate* g_policy_delegate = nullptr;
+
+class AutoLockOnValidThread {
+ public:
+ AutoLockOnValidThread(base::Lock& lock, base::ThreadChecker* thread_checker)
+ : auto_lock_(lock) {
+ DCHECK(!thread_checker || thread_checker->CalledOnValidThread());
+ }
+
+ private:
+ base::AutoLock auto_lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(AutoLockOnValidThread);
+};
+
+} // namespace
+
+PermissionsData::PermissionsData(const Extension* extension)
+ : extension_id_(extension->id()), manifest_type_(extension->GetType()) {
+ const PermissionSet& required_permissions =
+ PermissionsParser::GetRequiredPermissions(extension);
+ active_permissions_unsafe_.reset(new PermissionSet(
+ required_permissions.apis(), required_permissions.manifest_permissions(),
+ required_permissions.explicit_hosts(),
+ required_permissions.scriptable_hosts()));
+ withheld_permissions_unsafe_.reset(new PermissionSet());
+}
+
+PermissionsData::~PermissionsData() {
+}
+
+// static
+void PermissionsData::SetPolicyDelegate(PolicyDelegate* delegate) {
+ g_policy_delegate = delegate;
+}
+
+// static
+bool PermissionsData::CanExecuteScriptEverywhere(const Extension* extension) {
+ if (extension->location() == Manifest::COMPONENT)
+ return true;
+
+ const ExtensionsClient::ScriptingWhitelist& whitelist =
+ ExtensionsClient::Get()->GetScriptingWhitelist();
+
+ return std::find(whitelist.begin(), whitelist.end(), extension->id()) !=
+ whitelist.end();
+}
+
+// static
+bool PermissionsData::ShouldSkipPermissionWarnings(
+ const std::string& extension_id) {
+ // See http://b/4946060 for more details.
+ return extension_id == extension_misc::kProdHangoutsExtensionId;
+}
+
+// static
+bool PermissionsData::IsRestrictedUrl(const GURL& document_url,
+ const Extension* extension,
+ std::string* error) {
+ if (extension && CanExecuteScriptEverywhere(extension))
+ return false;
+
+ // Check if the scheme is valid for extensions. If not, return.
+ if (!URLPattern::IsValidSchemeForExtensions(document_url.scheme()) &&
+ document_url.spec() != url::kAboutBlankURL) {
+ if (error) {
+ if (extension->permissions_data()->active_permissions().HasAPIPermission(
+ APIPermission::kTab)) {
+ *error = ErrorUtils::FormatErrorMessage(
+ manifest_errors::kCannotAccessPageWithUrl, document_url.spec());
+ } else {
+ *error = manifest_errors::kCannotAccessPage;
+ }
+ }
+ return true;
+ }
+
+ if (!ExtensionsClient::Get()->IsScriptableURL(document_url, error))
+ return true;
+
+ bool allow_on_chrome_urls = base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kExtensionsOnChromeURLs);
+ if (document_url.SchemeIs(content::kChromeUIScheme) &&
+ !allow_on_chrome_urls) {
+ if (error)
+ *error = manifest_errors::kCannotAccessChromeUrl;
+ return true;
+ }
+
+ if (extension && document_url.SchemeIs(kExtensionScheme) &&
+ document_url.host() != extension->id() && !allow_on_chrome_urls) {
+ if (error)
+ *error = manifest_errors::kCannotAccessExtensionUrl;
+ return true;
+ }
+
+ return false;
+}
+
+void PermissionsData::BindToCurrentThread() const {
+ DCHECK(!thread_checker_);
+ thread_checker_.reset(new base::ThreadChecker());
+}
+
+void PermissionsData::SetPermissions(
+ scoped_ptr<const PermissionSet> active,
+ scoped_ptr<const PermissionSet> withheld) const {
+ AutoLockOnValidThread lock(runtime_lock_, thread_checker_.get());
+ active_permissions_unsafe_ = std::move(active);
+ withheld_permissions_unsafe_ = std::move(withheld);
+}
+
+void PermissionsData::SetActivePermissions(
+ scoped_ptr<const PermissionSet> active) const {
+ AutoLockOnValidThread lock(runtime_lock_, thread_checker_.get());
+ active_permissions_unsafe_ = std::move(active);
+}
+
+void PermissionsData::UpdateTabSpecificPermissions(
+ int tab_id,
+ const PermissionSet& permissions) const {
+ AutoLockOnValidThread lock(runtime_lock_, thread_checker_.get());
+ CHECK_GE(tab_id, 0);
+ TabPermissionsMap::const_iterator iter =
+ tab_specific_permissions_.find(tab_id);
+ scoped_ptr<const PermissionSet> new_permissions = PermissionSet::CreateUnion(
+ iter == tab_specific_permissions_.end()
+ ? static_cast<const PermissionSet&>(PermissionSet())
+ : *iter->second,
+ permissions);
+ tab_specific_permissions_[tab_id] = std::move(new_permissions);
+}
+
+void PermissionsData::ClearTabSpecificPermissions(int tab_id) const {
+ AutoLockOnValidThread lock(runtime_lock_, thread_checker_.get());
+ CHECK_GE(tab_id, 0);
+ tab_specific_permissions_.erase(tab_id);
+}
+
+bool PermissionsData::HasAPIPermission(APIPermission::ID permission) const {
+ base::AutoLock auto_lock(runtime_lock_);
+ return active_permissions_unsafe_->HasAPIPermission(permission);
+}
+
+bool PermissionsData::HasAPIPermission(
+ const std::string& permission_name) const {
+ base::AutoLock auto_lock(runtime_lock_);
+ return active_permissions_unsafe_->HasAPIPermission(permission_name);
+}
+
+bool PermissionsData::HasAPIPermissionForTab(
+ int tab_id,
+ APIPermission::ID permission) const {
+ base::AutoLock auto_lock(runtime_lock_);
+ if (active_permissions_unsafe_->HasAPIPermission(permission))
+ return true;
+
+ const PermissionSet* tab_permissions = GetTabSpecificPermissions(tab_id);
+ return tab_permissions && tab_permissions->HasAPIPermission(permission);
+}
+
+bool PermissionsData::CheckAPIPermissionWithParam(
+ APIPermission::ID permission,
+ const APIPermission::CheckParam* param) const {
+ base::AutoLock auto_lock(runtime_lock_);
+ return active_permissions_unsafe_->CheckAPIPermissionWithParam(permission,
+ param);
+}
+
+URLPatternSet PermissionsData::GetEffectiveHostPermissions() const {
+ base::AutoLock auto_lock(runtime_lock_);
+ URLPatternSet effective_hosts = active_permissions_unsafe_->effective_hosts();
+ for (const auto& val : tab_specific_permissions_)
+ effective_hosts.AddPatterns(val.second->effective_hosts());
+ return effective_hosts;
+}
+
+bool PermissionsData::HasHostPermission(const GURL& url) const {
+ base::AutoLock auto_lock(runtime_lock_);
+ return active_permissions_unsafe_->HasExplicitAccessToOrigin(url);
+}
+
+bool PermissionsData::HasEffectiveAccessToAllHosts() const {
+ base::AutoLock auto_lock(runtime_lock_);
+ return active_permissions_unsafe_->HasEffectiveAccessToAllHosts();
+}
+
+PermissionMessages PermissionsData::GetPermissionMessages() const {
+ base::AutoLock auto_lock(runtime_lock_);
+ return PermissionMessageProvider::Get()->GetPermissionMessages(
+ PermissionMessageProvider::Get()->GetAllPermissionIDs(
+ *active_permissions_unsafe_, manifest_type_));
+}
+
+bool PermissionsData::HasWithheldImpliedAllHosts() const {
+ base::AutoLock auto_lock(runtime_lock_);
+ // Since we currently only withhold all_hosts, it's sufficient to check
+ // that either set is not empty.
+ return !withheld_permissions_unsafe_->explicit_hosts().is_empty() ||
+ !withheld_permissions_unsafe_->scriptable_hosts().is_empty();
+}
+
+bool PermissionsData::CanAccessPage(const Extension* extension,
+ const GURL& document_url,
+ int tab_id,
+ std::string* error) const {
+ base::AutoLock auto_lock(runtime_lock_);
+ AccessType result =
+ CanRunOnPage(extension, document_url, tab_id,
+ active_permissions_unsafe_->explicit_hosts(),
+ withheld_permissions_unsafe_->explicit_hosts(), error);
+ // TODO(rdevlin.cronin) Update callers so that they only need ACCESS_ALLOWED.
+ return result == ACCESS_ALLOWED || result == ACCESS_WITHHELD;
+}
+
+PermissionsData::AccessType PermissionsData::GetPageAccess(
+ const Extension* extension,
+ const GURL& document_url,
+ int tab_id,
+ std::string* error) const {
+ base::AutoLock auto_lock(runtime_lock_);
+ return CanRunOnPage(extension, document_url, tab_id,
+ active_permissions_unsafe_->explicit_hosts(),
+ withheld_permissions_unsafe_->explicit_hosts(), error);
+}
+
+bool PermissionsData::CanRunContentScriptOnPage(const Extension* extension,
+ const GURL& document_url,
+ int tab_id,
+ std::string* error) const {
+ base::AutoLock auto_lock(runtime_lock_);
+ AccessType result =
+ CanRunOnPage(extension, document_url, tab_id,
+ active_permissions_unsafe_->scriptable_hosts(),
+ withheld_permissions_unsafe_->scriptable_hosts(), error);
+ // TODO(rdevlin.cronin) Update callers so that they only need ACCESS_ALLOWED.
+ return result == ACCESS_ALLOWED || result == ACCESS_WITHHELD;
+}
+
+PermissionsData::AccessType PermissionsData::GetContentScriptAccess(
+ const Extension* extension,
+ const GURL& document_url,
+ int tab_id,
+ std::string* error) const {
+ base::AutoLock auto_lock(runtime_lock_);
+ return CanRunOnPage(extension, document_url, tab_id,
+ active_permissions_unsafe_->scriptable_hosts(),
+ withheld_permissions_unsafe_->scriptable_hosts(), error);
+}
+
+bool PermissionsData::CanCaptureVisiblePage(int tab_id,
+ std::string* error) const {
+ const URLPattern all_urls(URLPattern::SCHEME_ALL,
+ URLPattern::kAllUrlsPattern);
+
+ base::AutoLock auto_lock(runtime_lock_);
+ if (active_permissions_unsafe_->explicit_hosts().ContainsPattern(all_urls))
+ return true;
+
+ if (tab_id >= 0) {
+ const PermissionSet* tab_permissions = GetTabSpecificPermissions(tab_id);
+ if (tab_permissions &&
+ tab_permissions->HasAPIPermission(APIPermission::kTab)) {
+ return true;
+ }
+ if (error)
+ *error = manifest_errors::kActiveTabPermissionNotGranted;
+ return false;
+ }
+
+ if (error)
+ *error = manifest_errors::kAllURLOrActiveTabNeeded;
+ return false;
+}
+
+const PermissionSet* PermissionsData::GetTabSpecificPermissions(
+ int tab_id) const {
+ CHECK_GE(tab_id, 0);
+ runtime_lock_.AssertAcquired();
+ TabPermissionsMap::const_iterator iter =
+ tab_specific_permissions_.find(tab_id);
+ return iter != tab_specific_permissions_.end() ? iter->second.get() : nullptr;
+}
+
+bool PermissionsData::HasTabSpecificPermissionToExecuteScript(
+ int tab_id,
+ const GURL& url) const {
+ runtime_lock_.AssertAcquired();
+ if (tab_id >= 0) {
+ const PermissionSet* tab_permissions = GetTabSpecificPermissions(tab_id);
+ if (tab_permissions &&
+ tab_permissions->explicit_hosts().MatchesSecurityOrigin(url)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+PermissionsData::AccessType PermissionsData::CanRunOnPage(
+ const Extension* extension,
+ const GURL& document_url,
+ int tab_id,
+ const URLPatternSet& permitted_url_patterns,
+ const URLPatternSet& withheld_url_patterns,
+ std::string* error) const {
+ runtime_lock_.AssertAcquired();
+ if (g_policy_delegate &&
+ !g_policy_delegate->CanExecuteScriptOnPage(extension, document_url,
+ tab_id, error)) {
+ return ACCESS_DENIED;
+ }
+
+ if (IsRestrictedUrl(document_url, extension, error))
+ return ACCESS_DENIED;
+
+ if (HasTabSpecificPermissionToExecuteScript(tab_id, document_url))
+ return ACCESS_ALLOWED;
+
+ if (permitted_url_patterns.MatchesURL(document_url))
+ return ACCESS_ALLOWED;
+
+ if (withheld_url_patterns.MatchesURL(document_url))
+ return ACCESS_WITHHELD;
+
+ if (error) {
+ if (extension->permissions_data()->active_permissions().HasAPIPermission(
+ APIPermission::kTab)) {
+ *error = ErrorUtils::FormatErrorMessage(
+ manifest_errors::kCannotAccessPageWithUrl, document_url.spec());
+ } else {
+ *error = manifest_errors::kCannotAccessPage;
+ }
+ }
+
+ return ACCESS_DENIED;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/permissions_data.h b/chromium/extensions/common/permissions/permissions_data.h
new file mode 100644
index 00000000000..bb8e51e9b78
--- /dev/null
+++ b/chromium/extensions/common/permissions/permissions_data.h
@@ -0,0 +1,267 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_PERMISSIONS_DATA_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_PERMISSIONS_DATA_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_checker.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/permission_message.h"
+#include "extensions/common/permissions/permission_set.h"
+
+class GURL;
+
+namespace extensions {
+class Extension;
+class URLPatternSet;
+
+// A container for the permissions state of an extension, including active,
+// withheld, and tab-specific permissions.
+// Thread-Safety: Since this is an object on the Extension object, *some* thread
+// safety is provided. All utility functions for checking if a permission is
+// present or an operation is allowed are thread-safe. However, permissions can
+// only be set (or updated) on the thread to which this object is bound.
+// Permissions may be accessed synchronously on that same thread.
+// Accessing on an improper thread will DCHECK().
+// This is necessary to prevent a scenario in which one thread will access
+// permissions while another thread changes them.
+class PermissionsData {
+ public:
+ // The possible types of access for a given frame.
+ enum AccessType {
+ ACCESS_DENIED, // The extension is not allowed to access the given page.
+ ACCESS_ALLOWED, // The extension is allowed to access the given page.
+ ACCESS_WITHHELD // The browser must determine if the extension can access
+ // the given page.
+ };
+
+ using TabPermissionsMap = std::map<int, scoped_ptr<const PermissionSet>>;
+
+ // Delegate class to allow different contexts (e.g. browser vs renderer) to
+ // have control over policy decisions.
+ class PolicyDelegate {
+ public:
+ virtual ~PolicyDelegate() {}
+
+ // Returns false if script access should be blocked on this page.
+ // Otherwise, default policy should decide.
+ virtual bool CanExecuteScriptOnPage(const Extension* extension,
+ const GURL& document_url,
+ int tab_id,
+ std::string* error) = 0;
+ };
+
+ static void SetPolicyDelegate(PolicyDelegate* delegate);
+
+ PermissionsData(const Extension* extension);
+ virtual ~PermissionsData();
+
+ // Returns true if the extension is a COMPONENT extension or is on the
+ // whitelist of extensions that can script all pages.
+ static bool CanExecuteScriptEverywhere(const Extension* extension);
+
+ // Returns true if we should skip the permissions warning for the extension
+ // with the given |extension_id|.
+ static bool ShouldSkipPermissionWarnings(const std::string& extension_id);
+
+ // Returns true if the given |url| is restricted for the given |extension|,
+ // as is commonly the case for chrome:// urls.
+ // NOTE: You probably want to use CanAccessPage().
+ static bool IsRestrictedUrl(const GURL& document_url,
+ const Extension* extension,
+ std::string* error);
+
+ // Locks the permissions data to the current thread. We don't do this on
+ // construction, since extensions are initialized across multiple threads.
+ void BindToCurrentThread() const;
+
+ // Sets the runtime permissions of the given |extension| to |active| and
+ // |withheld|.
+ void SetPermissions(scoped_ptr<const PermissionSet> active,
+ scoped_ptr<const PermissionSet> withheld) const;
+
+ // Sets the active permissions, leaving withheld the same.
+ void SetActivePermissions(scoped_ptr<const PermissionSet> active) const;
+
+ // Updates the tab-specific permissions of |tab_id| to include those from
+ // |permissions|.
+ void UpdateTabSpecificPermissions(int tab_id,
+ const PermissionSet& permissions) const;
+
+ // Clears the tab-specific permissions of |tab_id|.
+ void ClearTabSpecificPermissions(int tab_id) const;
+
+ // Returns true if the |extension| has the given |permission|. Prefer
+ // IsExtensionWithPermissionOrSuggestInConsole when developers may be using an
+ // api that requires a permission they didn't know about, e.g. open web apis.
+ // Note this does not include APIs with no corresponding permission, like
+ // "runtime" or "browserAction".
+ // TODO(mpcomplete): drop the "API" from these names, it's confusing.
+ bool HasAPIPermission(APIPermission::ID permission) const;
+ bool HasAPIPermission(const std::string& permission_name) const;
+ bool HasAPIPermissionForTab(int tab_id, APIPermission::ID permission) const;
+ bool CheckAPIPermissionWithParam(
+ APIPermission::ID permission,
+ const APIPermission::CheckParam* param) const;
+
+ // Returns the hosts this extension effectively has access to, including
+ // explicit and scriptable hosts, and any hosts on tabs the extension has
+ // active tab permissions for.
+ URLPatternSet GetEffectiveHostPermissions() const;
+
+ // TODO(rdevlin.cronin): HasHostPermission() and
+ // HasEffectiveAccessToAllHosts() are just forwards for the active
+ // permissions. We should either get rid of these, and have callers use
+ // active_permissions(), or should get rid of active_permissions(), and make
+ // callers use PermissionsData for everything. We should not do both.
+
+ // Whether the extension has access to the given |url|.
+ bool HasHostPermission(const GURL& url) const;
+
+ // Whether the extension has effective access to all hosts. This is true if
+ // there is a content script that matches all hosts, if there is a host
+ // permission grants access to all hosts (like <all_urls>) or an api
+ // permission that effectively grants access to all hosts (e.g. proxy,
+ // network, etc.)
+ bool HasEffectiveAccessToAllHosts() const;
+
+ // Returns the full list of permission details for messages that should
+ // display at install time, in a nested format ready for display.
+ PermissionMessages GetPermissionMessages() const;
+
+ // Returns true if the extension has requested all-hosts permissions (or
+ // something close to it), but has had it withheld.
+ bool HasWithheldImpliedAllHosts() const;
+
+ // Returns true if the |extension| has permission to access and interact with
+ // the specified page, in order to do things like inject scripts or modify
+ // the content.
+ // If this returns false and |error| is non-NULL, |error| will be popualted
+ // with the reason the extension cannot access the page.
+ bool CanAccessPage(const Extension* extension,
+ const GURL& document_url,
+ int tab_id,
+ std::string* error) const;
+ // Like CanAccessPage, but also takes withheld permissions into account.
+ // TODO(rdevlin.cronin) We shouldn't have two functions, but not all callers
+ // know how to wait for permission.
+ AccessType GetPageAccess(const Extension* extension,
+ const GURL& document_url,
+ int tab_id,
+ std::string* error) const;
+
+ // Returns true if the |extension| has permission to inject a content script
+ // on the page.
+ // If this returns false and |error| is non-NULL, |error| will be popualted
+ // with the reason the extension cannot script the page.
+ // NOTE: You almost certainly want to use CanAccessPage() instead of this
+ // method.
+ bool CanRunContentScriptOnPage(const Extension* extension,
+ const GURL& document_url,
+ int tab_id,
+ std::string* error) const;
+ // Like CanRunContentScriptOnPage, but also takes withheld permissions into
+ // account.
+ // TODO(rdevlin.cronin) We shouldn't have two functions, but not all callers
+ // know how to wait for permission.
+ AccessType GetContentScriptAccess(const Extension* extension,
+ const GURL& document_url,
+ int tab_id,
+ std::string* error) const;
+
+ // Returns true if extension is allowed to obtain the contents of a page as
+ // an image. Since a page may contain sensitive information, this is
+ // restricted to the extension's host permissions as well as the extension
+ // page itself.
+ bool CanCaptureVisiblePage(int tab_id, std::string* error) const;
+
+ const TabPermissionsMap& tab_specific_permissions() const {
+ DCHECK(!thread_checker_ || thread_checker_->CalledOnValidThread());
+ return tab_specific_permissions_;
+ }
+
+ const PermissionSet& active_permissions() const {
+ DCHECK(!thread_checker_ || thread_checker_->CalledOnValidThread());
+ return *active_permissions_unsafe_;
+ }
+
+ const PermissionSet& withheld_permissions() const {
+ DCHECK(!thread_checker_ || thread_checker_->CalledOnValidThread());
+ return *withheld_permissions_unsafe_;
+ }
+
+#if defined(UNIT_TEST)
+ const PermissionSet* GetTabSpecificPermissionsForTesting(int tab_id) const {
+ base::AutoLock auto_lock(runtime_lock_);
+ return GetTabSpecificPermissions(tab_id);
+ }
+#endif
+
+ private:
+ // Gets the tab-specific host permissions of |tab_id|, or NULL if there
+ // aren't any.
+ // Must be called with |runtime_lock_| acquired.
+ const PermissionSet* GetTabSpecificPermissions(int tab_id) const;
+
+ // Returns true if the |extension| has tab-specific permission to operate on
+ // the tab specified by |tab_id| with the given |url|.
+ // Note that if this returns false, it doesn't mean the extension can't run on
+ // the given tab, only that it does not have tab-specific permission to do so.
+ // Must be called with |runtime_lock_| acquired.
+ bool HasTabSpecificPermissionToExecuteScript(int tab_id,
+ const GURL& url) const;
+
+ // Returns whether or not the extension is permitted to run on the given page,
+ // checking against |permitted_url_patterns| in addition to blocking special
+ // sites (like the webstore or chrome:// urls).
+ // Must be called with |runtime_lock_| acquired.
+ AccessType CanRunOnPage(const Extension* extension,
+ const GURL& document_url,
+ int tab_id,
+ const URLPatternSet& permitted_url_patterns,
+ const URLPatternSet& withheld_url_patterns,
+ std::string* error) const;
+
+ // The associated extension's id.
+ std::string extension_id_;
+
+ // The associated extension's manifest type.
+ Manifest::Type manifest_type_;
+
+ mutable base::Lock runtime_lock_;
+
+ // The permission's which are currently active on the extension during
+ // runtime.
+ // Unsafe indicates that we must lock anytime this is directly accessed.
+ // Unless you need to change |active_permissions_unsafe_|, use the (safe)
+ // active_permissions() accessor.
+ mutable scoped_ptr<const PermissionSet> active_permissions_unsafe_;
+
+ // The permissions the extension requested, but was not granted due because
+ // they are too powerful. This includes things like all_hosts.
+ // Unsafe indicates that we must lock anytime this is directly accessed.
+ // Unless you need to change |withheld_permissions_unsafe_|, use the (safe)
+ // withheld_permissions() accessor.
+ mutable scoped_ptr<const PermissionSet> withheld_permissions_unsafe_;
+
+ mutable TabPermissionsMap tab_specific_permissions_;
+
+ mutable scoped_ptr<base::ThreadChecker> thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(PermissionsData);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_PERMISSIONS_DATA_H_
diff --git a/chromium/extensions/common/permissions/permissions_info.cc b/chromium/extensions/common/permissions/permissions_info.cc
new file mode 100644
index 00000000000..e83fb2e3a78
--- /dev/null
+++ b/chromium/extensions/common/permissions/permissions_info.cc
@@ -0,0 +1,96 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/permissions_info.h"
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+
+namespace extensions {
+
+static base::LazyInstance<PermissionsInfo> g_permissions_info =
+ LAZY_INSTANCE_INITIALIZER;
+
+// static
+PermissionsInfo* PermissionsInfo::GetInstance() {
+ return g_permissions_info.Pointer();
+}
+
+void PermissionsInfo::AddProvider(const PermissionsProvider& provider) {
+ std::vector<APIPermissionInfo*> permissions = provider.GetAllPermissions();
+ std::vector<PermissionsProvider::AliasInfo> aliases =
+ provider.GetAllAliases();
+
+ for (size_t i = 0; i < permissions.size(); ++i)
+ RegisterPermission(permissions[i]);
+ for (size_t i = 0; i < aliases.size(); ++i)
+ RegisterAlias(aliases[i].name, aliases[i].alias);
+}
+
+const APIPermissionInfo* PermissionsInfo::GetByID(
+ APIPermission::ID id) const {
+ IDMap::const_iterator i = id_map_.find(id);
+ return (i == id_map_.end()) ? NULL : i->second;
+}
+
+const APIPermissionInfo* PermissionsInfo::GetByName(
+ const std::string& name) const {
+ NameMap::const_iterator i = name_map_.find(name);
+ return (i == name_map_.end()) ? NULL : i->second;
+}
+
+APIPermissionSet PermissionsInfo::GetAll() const {
+ APIPermissionSet permissions;
+ for (IDMap::const_iterator i = id_map_.begin(); i != id_map_.end(); ++i)
+ permissions.insert(i->second->id());
+ return permissions;
+}
+
+APIPermissionSet PermissionsInfo::GetAllByName(
+ const std::set<std::string>& permission_names) const {
+ APIPermissionSet permissions;
+ for (std::set<std::string>::const_iterator i = permission_names.begin();
+ i != permission_names.end(); ++i) {
+ const APIPermissionInfo* permission_info = GetByName(*i);
+ if (permission_info)
+ permissions.insert(permission_info->id());
+ }
+ return permissions;
+}
+
+bool PermissionsInfo::HasChildPermissions(const std::string& name) const {
+ NameMap::const_iterator i = name_map_.lower_bound(name + '.');
+ if (i == name_map_.end()) return false;
+ return base::StartsWith(i->first, name + '.', base::CompareCase::SENSITIVE);
+}
+
+PermissionsInfo::PermissionsInfo()
+ : permission_count_(0) {
+}
+
+PermissionsInfo::~PermissionsInfo() {
+ STLDeleteContainerPairSecondPointers(id_map_.begin(), id_map_.end());
+}
+
+void PermissionsInfo::RegisterAlias(
+ const char* name,
+ const char* alias) {
+ DCHECK(ContainsKey(name_map_, name));
+ DCHECK(!ContainsKey(name_map_, alias));
+ name_map_[alias] = name_map_[name];
+}
+
+void PermissionsInfo::RegisterPermission(APIPermissionInfo* permission) {
+ DCHECK(!ContainsKey(id_map_, permission->id()));
+ DCHECK(!ContainsKey(name_map_, permission->name()));
+
+ id_map_[permission->id()] = permission;
+ name_map_[permission->name()] = permission;
+
+ permission_count_++;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/permissions_info.h b/chromium/extensions/common/permissions/permissions_info.h
new file mode 100644
index 00000000000..d834c41da14
--- /dev/null
+++ b/chromium/extensions/common/permissions/permissions_info.h
@@ -0,0 +1,82 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_PERMISSIONS_INFO_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_PERMISSIONS_INFO_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/callback.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/api_permission_set.h"
+#include "extensions/common/permissions/permissions_provider.h"
+
+namespace extensions {
+
+// A global object that holds the extension permission instances and provides
+// methods for accessing them.
+class PermissionsInfo {
+ public:
+ static PermissionsInfo* GetInstance();
+
+ // Initializes the permissions from the provider.
+ void AddProvider(const PermissionsProvider& provider);
+
+ // Returns the permission with the given |id|, and NULL if it doesn't exist.
+ const APIPermissionInfo* GetByID(APIPermission::ID id) const;
+
+ // Returns the permission with the given |name|, and NULL if none
+ // exists.
+ const APIPermissionInfo* GetByName(const std::string& name) const;
+
+ // Returns a set containing all valid api permission ids.
+ APIPermissionSet GetAll() const;
+
+ // Converts all the permission names in |permission_names| to permission ids.
+ APIPermissionSet GetAllByName(
+ const std::set<std::string>& permission_names) const;
+
+ // Checks if any permissions have names that start with |name| followed by a
+ // period.
+ bool HasChildPermissions(const std::string& name) const;
+
+ // Gets the total number of API permissions.
+ size_t get_permission_count() const { return permission_count_; }
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<PermissionsInfo>;
+
+ PermissionsInfo();
+
+ virtual ~PermissionsInfo();
+
+ // Registers an |alias| for a given permission |name|.
+ void RegisterAlias(const char* name, const char* alias);
+
+ // Registers a permission with the specified attributes and flags.
+ void RegisterPermission(APIPermissionInfo* permission);
+
+ // Maps permission ids to permissions.
+ typedef std::map<APIPermission::ID, APIPermissionInfo*> IDMap;
+
+ // Maps names and aliases to permissions.
+ typedef std::map<std::string, APIPermissionInfo*> NameMap;
+
+ IDMap id_map_;
+ NameMap name_map_;
+
+ size_t permission_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(PermissionsInfo);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_PERMISSIONS_INFO_H_
diff --git a/chromium/extensions/common/permissions/permissions_provider.h b/chromium/extensions/common/permissions/permissions_provider.h
new file mode 100644
index 00000000000..5d7046d0db4
--- /dev/null
+++ b/chromium/extensions/common/permissions/permissions_provider.h
@@ -0,0 +1,40 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_PERMISSIONS_PROVIDER_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_PERMISSIONS_PROVIDER_H_
+
+#include <vector>
+
+namespace extensions {
+
+class APIPermissionInfo;
+
+// The PermissionsProvider creates APIPermissions instances. It is only
+// needed at startup time. Typically, ExtensionsClient will register
+// its PermissionsProviders with the global PermissionsInfo at startup.
+// TODO(sashab): Remove all permission messages from this class, moving the
+// permission message rules into ChromePermissionMessageProvider.
+class PermissionsProvider {
+ public:
+ // An alias for a given permission |name|.
+ struct AliasInfo {
+ const char* name;
+ const char* alias;
+
+ AliasInfo(const char* name, const char* alias)
+ : name(name), alias(alias) {
+ }
+ };
+ // Returns all the known permissions. The caller, PermissionsInfo,
+ // takes ownership of the APIPermissionInfos.
+ virtual std::vector<APIPermissionInfo*> GetAllPermissions() const = 0;
+
+ // Returns all the known permission aliases.
+ virtual std::vector<AliasInfo> GetAllAliases() const = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_PERMISSIONS_PROVIDER_H_
diff --git a/chromium/extensions/common/permissions/set_disjunction_permission.h b/chromium/extensions/common/permissions/set_disjunction_permission.h
new file mode 100644
index 00000000000..ce1c6ee1dd1
--- /dev/null
+++ b/chromium/extensions/common/permissions/set_disjunction_permission.h
@@ -0,0 +1,171 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_SET_DISJUNCTION_PERMISSION_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_SET_DISJUNCTION_PERMISSION_H_
+
+#include <stddef.h>
+
+#include <set>
+#include <string>
+
+#include "base/json/json_writer.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/permissions/api_permission.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_utils.h"
+
+namespace extensions {
+
+// An abstract base class for permissions that are represented by the
+// disjunction of a set of conditions. Each condition is represented by a
+// |PermissionDataType| (e.g. SocketPermissionData). If an
+// APIPermission::CheckParam matches any of the conditions in the set, the
+// permission is granted.
+//
+// For an example of how to use this class, see SocketPermission.
+template <class PermissionDataType, class DerivedType>
+class SetDisjunctionPermission : public APIPermission {
+ public:
+ explicit SetDisjunctionPermission(const APIPermissionInfo* info)
+ : APIPermission(info) {}
+
+ ~SetDisjunctionPermission() override {}
+
+ // APIPermission overrides
+ bool Check(const APIPermission::CheckParam* param) const override {
+ for (typename std::set<PermissionDataType>::const_iterator i =
+ data_set_.begin();
+ i != data_set_.end();
+ ++i) {
+ if (i->Check(param))
+ return true;
+ }
+ return false;
+ }
+
+ bool Contains(const APIPermission* rhs) const override {
+ CHECK(rhs->info() == info());
+ const SetDisjunctionPermission* perm =
+ static_cast<const SetDisjunctionPermission*>(rhs);
+ return base::STLIncludes<std::set<PermissionDataType> >(
+ data_set_, perm->data_set_);
+ }
+
+ bool Equal(const APIPermission* rhs) const override {
+ CHECK(rhs->info() == info());
+ const SetDisjunctionPermission* perm =
+ static_cast<const SetDisjunctionPermission*>(rhs);
+ return data_set_ == perm->data_set_;
+ }
+
+ APIPermission* Clone() const override {
+ SetDisjunctionPermission* result = new DerivedType(info());
+ result->data_set_ = data_set_;
+ return result;
+ }
+
+ APIPermission* Diff(const APIPermission* rhs) const override {
+ CHECK(rhs->info() == info());
+ const SetDisjunctionPermission* perm =
+ static_cast<const SetDisjunctionPermission*>(rhs);
+ scoped_ptr<SetDisjunctionPermission> result(new DerivedType(info()));
+ result->data_set_ = base::STLSetDifference<std::set<PermissionDataType> >(
+ data_set_, perm->data_set_);
+ return result->data_set_.empty() ? NULL : result.release();
+ }
+
+ APIPermission* Union(const APIPermission* rhs) const override {
+ CHECK(rhs->info() == info());
+ const SetDisjunctionPermission* perm =
+ static_cast<const SetDisjunctionPermission*>(rhs);
+ scoped_ptr<SetDisjunctionPermission> result(new DerivedType(info()));
+ result->data_set_ = base::STLSetUnion<std::set<PermissionDataType> >(
+ data_set_, perm->data_set_);
+ return result.release();
+ }
+
+ APIPermission* Intersect(const APIPermission* rhs) const override {
+ CHECK(rhs->info() == info());
+ const SetDisjunctionPermission* perm =
+ static_cast<const SetDisjunctionPermission*>(rhs);
+ scoped_ptr<SetDisjunctionPermission> result(new DerivedType(info()));
+ result->data_set_ = base::STLSetIntersection<std::set<PermissionDataType> >(
+ data_set_, perm->data_set_);
+ return result->data_set_.empty() ? NULL : result.release();
+ }
+
+ bool FromValue(
+ const base::Value* value,
+ std::string* error,
+ std::vector<std::string>* unhandled_permissions) override {
+ data_set_.clear();
+ const base::ListValue* list = NULL;
+
+ if (!value) {
+ // treat null as an empty list.
+ return true;
+ }
+
+ if (!value->GetAsList(&list)) {
+ if (error)
+ *error = "Cannot parse the permission list. It's not a list.";
+ return false;
+ }
+
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ const base::Value* item_value = NULL;
+ bool got_item = list->Get(i, &item_value);
+ DCHECK(got_item);
+ DCHECK(item_value);
+
+ PermissionDataType data;
+ if (data.FromValue(item_value)) {
+ data_set_.insert(data);
+ } else {
+ std::string unknown_permission;
+ base::JSONWriter::Write(*item_value, &unknown_permission);
+ if (unhandled_permissions) {
+ unhandled_permissions->push_back(unknown_permission);
+ } else {
+ if (error) {
+ *error = "Cannot parse an item from the permission list: " +
+ unknown_permission;
+ }
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ scoped_ptr<base::Value> ToValue() const override {
+ base::ListValue* list = new base::ListValue();
+ typename std::set<PermissionDataType>::const_iterator i;
+ for (i = data_set_.begin(); i != data_set_.end(); ++i) {
+ scoped_ptr<base::Value> item_value(i->ToValue());
+ list->Append(item_value.release());
+ }
+ return scoped_ptr<base::Value>(list);
+ }
+
+ void Write(base::Pickle* m) const override { IPC::WriteParam(m, data_set_); }
+
+ bool Read(const base::Pickle* m, base::PickleIterator* iter) override {
+ return IPC::ReadParam(m, iter, &data_set_);
+ }
+
+ void Log(std::string* log) const override {
+ IPC::LogParam(data_set_, log);
+ }
+
+ protected:
+ std::set<PermissionDataType> data_set_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_SET_DISJUNCTION_PERMISSION_H_
diff --git a/chromium/extensions/common/permissions/settings_override_permission.cc b/chromium/extensions/common/permissions/settings_override_permission.cc
new file mode 100644
index 00000000000..69318d82939
--- /dev/null
+++ b/chromium/extensions/common/permissions/settings_override_permission.cc
@@ -0,0 +1,91 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/settings_override_permission.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/common/permissions/api_permission_set.h"
+#include "grit/extensions_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+SettingsOverrideAPIPermission::SettingsOverrideAPIPermission(
+ const APIPermissionInfo* permission)
+ : APIPermission(permission) {}
+
+SettingsOverrideAPIPermission::SettingsOverrideAPIPermission(
+ const APIPermissionInfo* permission,
+ const std::string& setting_value)
+ : APIPermission(permission), setting_value_(setting_value) {}
+
+SettingsOverrideAPIPermission::~SettingsOverrideAPIPermission() {}
+
+PermissionIDSet SettingsOverrideAPIPermission::GetPermissions() const {
+ PermissionIDSet permissions;
+ permissions.insert(info()->id(), base::UTF8ToUTF16(setting_value_));
+ return permissions;
+}
+
+bool SettingsOverrideAPIPermission::Check(
+ const APIPermission::CheckParam* param) const {
+ return (param == NULL);
+}
+
+bool SettingsOverrideAPIPermission::Contains(const APIPermission* rhs) const {
+ CHECK_EQ(info(), rhs->info());
+ return true;
+}
+
+bool SettingsOverrideAPIPermission::Equal(const APIPermission* rhs) const {
+ if (this != rhs)
+ CHECK_EQ(info(), rhs->info());
+ return true;
+}
+
+bool SettingsOverrideAPIPermission::FromValue(
+ const base::Value* value,
+ std::string* /*error*/,
+ std::vector<std::string>* unhandled_permissions) {
+ return value && value->GetAsString(&setting_value_);
+}
+
+scoped_ptr<base::Value> SettingsOverrideAPIPermission::ToValue() const {
+ return make_scoped_ptr(new base::StringValue(setting_value_));
+}
+
+APIPermission* SettingsOverrideAPIPermission::Clone() const {
+ return new SettingsOverrideAPIPermission(info(), setting_value_);
+}
+
+APIPermission* SettingsOverrideAPIPermission::Diff(
+ const APIPermission* rhs) const {
+ CHECK_EQ(info(), rhs->info());
+ return NULL;
+}
+
+APIPermission* SettingsOverrideAPIPermission::Union(
+ const APIPermission* rhs) const {
+ CHECK_EQ(info(), rhs->info());
+ return new SettingsOverrideAPIPermission(info(), setting_value_);
+}
+
+APIPermission* SettingsOverrideAPIPermission::Intersect(
+ const APIPermission* rhs) const {
+ CHECK_EQ(info(), rhs->info());
+ return new SettingsOverrideAPIPermission(info(), setting_value_);
+}
+
+void SettingsOverrideAPIPermission::Write(base::Pickle* m) const {}
+
+bool SettingsOverrideAPIPermission::Read(const base::Pickle* m,
+ base::PickleIterator* iter) {
+ return true;
+}
+
+void SettingsOverrideAPIPermission::Log(std::string* log) const {
+ *log = setting_value_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/settings_override_permission.h b/chromium/extensions/common/permissions/settings_override_permission.h
new file mode 100644
index 00000000000..81a27305177
--- /dev/null
+++ b/chromium/extensions/common/permissions/settings_override_permission.h
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_SETTINGS_OVERRIDE_PERMISSION_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_SETTINGS_OVERRIDE_PERMISSION_H_
+
+#include <string>
+
+#include "extensions/common/permissions/api_permission.h"
+
+namespace extensions {
+
+// Takes care of creating custom permission messages for extensions that
+// override settings.
+class SettingsOverrideAPIPermission : public APIPermission {
+ public:
+ explicit SettingsOverrideAPIPermission(const APIPermissionInfo* permission);
+ SettingsOverrideAPIPermission(const APIPermissionInfo* permission,
+ const std::string& setting_value);
+ ~SettingsOverrideAPIPermission() override;
+
+ // APIPermission overrides.
+ PermissionIDSet GetPermissions() const override;
+ bool Check(const APIPermission::CheckParam* param) const override;
+ bool Contains(const APIPermission* rhs) const override;
+ bool Equal(const APIPermission* rhs) const override;
+ bool FromValue(const base::Value* value,
+ std::string* error,
+ std::vector<std::string>* unhandled_permissions) override;
+ scoped_ptr<base::Value> ToValue() const override;
+ APIPermission* Clone() const override;
+ APIPermission* Diff(const APIPermission* rhs) const override;
+ APIPermission* Union(const APIPermission* rhs) const override;
+ APIPermission* Intersect(const APIPermission* rhs) const override;
+ void Write(base::Pickle* m) const override;
+ bool Read(const base::Pickle* m, base::PickleIterator* iter) override;
+ void Log(std::string* log) const override;
+
+ private:
+ std::string setting_value_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_SETTINGS_OVERRIDE_PERMISSION_H_
diff --git a/chromium/extensions/common/permissions/socket_permission.cc b/chromium/extensions/common/permissions/socket_permission.cc
new file mode 100644
index 00000000000..a8e5c18f5ec
--- /dev/null
+++ b/chromium/extensions/common/permissions/socket_permission.cc
@@ -0,0 +1,63 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/socket_permission.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/common/api/sockets/sockets_manifest_permission.h"
+#include "extensions/common/permissions/permissions_info.h"
+#include "extensions/common/permissions/set_disjunction_permission.h"
+#include "grit/extensions_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+namespace {
+
+// Extracts the SocketPermissionEntry fields from a set of SocketPermissionData,
+// and places them in their own set. Useful for converting the
+// std::set<SocketPermissionEntry> field from SocketPermission into a parameter
+// that can be passed to SocketsManifestPermission::AddSocketHostPermissions().
+SocketPermissionEntrySet ExtractSocketEntries(
+ const std::set<SocketPermissionData>& data_set) {
+ SocketPermissionEntrySet entries;
+ for (const auto& data : data_set)
+ entries.insert(data.entry());
+ return entries;
+}
+
+} // namespace
+
+SocketPermission::SocketPermission(const APIPermissionInfo* info)
+ : SetDisjunctionPermission<SocketPermissionData, SocketPermission>(info) {}
+
+SocketPermission::~SocketPermission() {}
+
+bool SocketPermission::FromValue(
+ const base::Value* value,
+ std::string* error,
+ std::vector<std::string>* unhandled_permissions) {
+ bool parsed_ok = SetDisjunctionPermission<
+ SocketPermissionData, SocketPermission>::FromValue(value, error,
+ unhandled_permissions);
+ if (parsed_ok && data_set_.empty()) {
+ if (error)
+ *error = "NULL or empty permission list";
+ return false;
+ }
+ return parsed_ok;
+}
+
+PermissionIDSet SocketPermission::GetPermissions() const {
+ PermissionIDSet ids;
+ SocketPermissionEntrySet entries = ExtractSocketEntries(data_set_);
+ SocketsManifestPermission::AddSocketHostPermissions(entries, &ids);
+ return ids;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/socket_permission.h b/chromium/extensions/common/permissions/socket_permission.h
new file mode 100644
index 00000000000..590ed5db8d5
--- /dev/null
+++ b/chromium/extensions/common/permissions/socket_permission.h
@@ -0,0 +1,44 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_SOCKET_PERMISSION_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_SOCKET_PERMISSION_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/set_disjunction_permission.h"
+#include "extensions/common/permissions/socket_permission_data.h"
+
+namespace extensions {
+
+class SocketPermission
+ : public SetDisjunctionPermission<SocketPermissionData, SocketPermission> {
+ public:
+ struct CheckParam : APIPermission::CheckParam {
+ CheckParam(content::SocketPermissionRequest::OperationType type,
+ const std::string& host,
+ uint16_t port)
+ : request(type, host, port) {}
+ content::SocketPermissionRequest request;
+ };
+
+ explicit SocketPermission(const APIPermissionInfo* info);
+
+ ~SocketPermission() override;
+
+ // SetDisjunctionPermission overrides.
+ bool FromValue(const base::Value* value,
+ std::string* error,
+ std::vector<std::string>* unhandled_permissions) override;
+
+ // APIPermission overrides
+ PermissionIDSet GetPermissions() const override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_SOCKET_PERMISSION_H_
diff --git a/chromium/extensions/common/permissions/socket_permission_data.cc b/chromium/extensions/common/permissions/socket_permission_data.cc
new file mode 100644
index 00000000000..f22f14a1eb5
--- /dev/null
+++ b/chromium/extensions/common/permissions/socket_permission_data.cc
@@ -0,0 +1,159 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/socket_permission_data.h"
+
+#include <cstdlib>
+#include <sstream>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/socket_permission.h"
+#include "url/url_canon.h"
+
+namespace {
+
+using content::SocketPermissionRequest;
+using extensions::SocketPermissionData;
+
+const char kColon = ':';
+const char kInvalid[] = "invalid";
+const char kTCPConnect[] = "tcp-connect";
+const char kTCPListen[] = "tcp-listen";
+const char kUDPBind[] = "udp-bind";
+const char kUDPSendTo[] = "udp-send-to";
+const char kUDPMulticastMembership[] = "udp-multicast-membership";
+const char kResolveHost[] = "resolve-host";
+const char kResolveProxy[] = "resolve-proxy";
+const char kNetworkState[] = "network-state";
+
+SocketPermissionRequest::OperationType StringToType(const std::string& s) {
+ if (s == kTCPConnect)
+ return SocketPermissionRequest::TCP_CONNECT;
+ if (s == kTCPListen)
+ return SocketPermissionRequest::TCP_LISTEN;
+ if (s == kUDPBind)
+ return SocketPermissionRequest::UDP_BIND;
+ if (s == kUDPSendTo)
+ return SocketPermissionRequest::UDP_SEND_TO;
+ if (s == kUDPMulticastMembership)
+ return SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP;
+ if (s == kResolveHost)
+ return SocketPermissionRequest::RESOLVE_HOST;
+ if (s == kResolveProxy)
+ return SocketPermissionRequest::RESOLVE_PROXY;
+ if (s == kNetworkState)
+ return SocketPermissionRequest::NETWORK_STATE;
+ return SocketPermissionRequest::NONE;
+}
+
+const char* TypeToString(SocketPermissionRequest::OperationType type) {
+ switch (type) {
+ case SocketPermissionRequest::TCP_CONNECT:
+ return kTCPConnect;
+ case SocketPermissionRequest::TCP_LISTEN:
+ return kTCPListen;
+ case SocketPermissionRequest::UDP_BIND:
+ return kUDPBind;
+ case SocketPermissionRequest::UDP_SEND_TO:
+ return kUDPSendTo;
+ case SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP:
+ return kUDPMulticastMembership;
+ case SocketPermissionRequest::RESOLVE_HOST:
+ return kResolveHost;
+ case SocketPermissionRequest::RESOLVE_PROXY:
+ return kResolveProxy;
+ case SocketPermissionRequest::NETWORK_STATE:
+ return kNetworkState;
+ default:
+ return kInvalid;
+ }
+}
+
+} // namespace
+
+namespace extensions {
+
+SocketPermissionData::SocketPermissionData() {}
+
+SocketPermissionData::~SocketPermissionData() {}
+
+bool SocketPermissionData::operator<(const SocketPermissionData& rhs) const {
+ return entry_ < rhs.entry_;
+}
+
+bool SocketPermissionData::operator==(const SocketPermissionData& rhs) const {
+ return entry_ == rhs.entry_;
+}
+
+bool SocketPermissionData::Check(const APIPermission::CheckParam* param) const {
+ if (!param)
+ return false;
+ const SocketPermission::CheckParam& specific_param =
+ *static_cast<const SocketPermission::CheckParam*>(param);
+ const SocketPermissionRequest& request = specific_param.request;
+
+ return entry_.Check(request);
+}
+
+scoped_ptr<base::Value> SocketPermissionData::ToValue() const {
+ return scoped_ptr<base::Value>(new base::StringValue(GetAsString()));
+}
+
+bool SocketPermissionData::FromValue(const base::Value* value) {
+ std::string spec;
+ if (!value->GetAsString(&spec))
+ return false;
+
+ return Parse(spec);
+}
+
+SocketPermissionEntry& SocketPermissionData::entry() {
+ // Clear the spec because the caller could mutate |this|.
+ spec_.clear();
+ return entry_;
+}
+
+// TODO(reillyg): Rewrite this method to support IPv6.
+bool SocketPermissionData::Parse(const std::string& permission) {
+ Reset();
+
+ std::vector<std::string> tokens =
+ base::SplitString(permission, std::string(1, kColon),
+ base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+ if (tokens.empty())
+ return false;
+
+ SocketPermissionRequest::OperationType type = StringToType(tokens[0]);
+ if (type == SocketPermissionRequest::NONE)
+ return false;
+
+ tokens.erase(tokens.begin());
+ return SocketPermissionEntry::ParseHostPattern(type, tokens, &entry_);
+}
+
+const std::string& SocketPermissionData::GetAsString() const {
+ if (!spec_.empty())
+ return spec_;
+
+ spec_.reserve(64);
+ spec_.append(TypeToString(entry_.pattern().type));
+ std::string pattern = entry_.GetHostPatternAsString();
+ if (!pattern.empty()) {
+ spec_.append(1, kColon).append(pattern);
+ }
+ return spec_;
+}
+
+void SocketPermissionData::Reset() {
+ entry_ = SocketPermissionEntry();
+ spec_.clear();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/socket_permission_data.h b/chromium/extensions/common/permissions/socket_permission_data.h
new file mode 100644
index 00000000000..f227f8d4c28
--- /dev/null
+++ b/chromium/extensions/common/permissions/socket_permission_data.h
@@ -0,0 +1,89 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_SOCKET_PERMISSION_DATA_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_SOCKET_PERMISSION_DATA_H_
+
+#include <string>
+
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/socket_permission_entry.h"
+#include "ipc/ipc_param_traits.h"
+
+namespace ipc_fuzzer {
+template <class T>
+struct FuzzTraits;
+template <class T>
+struct GenerateTraits;
+} // namespace ipc_fuzzer
+
+namespace extensions {
+
+// A pattern that can be used to match socket permission.
+// <socket-permission-pattern>
+// := <op> |
+// <op> ':' <host> |
+// <op> ':' ':' <port> |
+// <op> ':' <host> ':' <port> |
+// 'udp-multicast-membership'
+// <op> := 'tcp-connect' |
+// 'tcp-listen' |
+// 'udp-bind' |
+// 'udp-send-to' |
+// 'udp-multicast-membership' |
+// 'resolve-host' |
+// 'resolve-proxy' |
+// 'network-state'
+// <host> := '*' |
+// '*.' <anychar except '/' and '*'>+ |
+// <anychar except '/' and '*'>+
+// <port> := '*' |
+// <port number between 0 and 65535>)
+// The multicast membership permission implies a permission to any address.
+class SocketPermissionData {
+ public:
+ SocketPermissionData();
+ ~SocketPermissionData();
+
+ // operators <, == are needed by container std::set and algorithms
+ // std::set_includes and std::set_differences.
+ bool operator<(const SocketPermissionData& rhs) const;
+ bool operator==(const SocketPermissionData& rhs) const;
+
+ // Check if |param| (which must be a SocketPermissionData::CheckParam)
+ // matches the spec of |this|.
+ bool Check(const APIPermission::CheckParam* param) const;
+
+ // Convert |this| into a base::Value.
+ scoped_ptr<base::Value> ToValue() const;
+
+ // Populate |this| from a base::Value.
+ bool FromValue(const base::Value* value);
+
+ // TODO(bryeung): SocketPermissionData should be encoded as a base::Value
+ // instead of a string. Until that is done, expose these methods for
+ // testing.
+ bool ParseForTest(const std::string& permission) { return Parse(permission); }
+ const std::string& GetAsStringForTest() const { return GetAsString(); }
+
+ const SocketPermissionEntry& entry() const { return entry_; }
+
+ private:
+ // Friend so ParamTraits can serialize us.
+ friend struct IPC::ParamTraits<SocketPermissionData>;
+ friend struct ipc_fuzzer::FuzzTraits<SocketPermissionData>;
+ friend struct ipc_fuzzer::GenerateTraits<SocketPermissionData>;
+
+ SocketPermissionEntry& entry();
+
+ bool Parse(const std::string& permission);
+ const std::string& GetAsString() const;
+ void Reset();
+
+ SocketPermissionEntry entry_;
+ mutable std::string spec_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_SOCKET_PERMISSION_DATA_H_
diff --git a/chromium/extensions/common/permissions/socket_permission_entry.cc b/chromium/extensions/common/permissions/socket_permission_entry.cc
new file mode 100644
index 00000000000..a6fbb3b5b11
--- /dev/null
+++ b/chromium/extensions/common/permissions/socket_permission_entry.cc
@@ -0,0 +1,215 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/socket_permission_entry.h"
+
+#include <stdint.h>
+
+#include <cstdlib>
+#include <sstream>
+#include <tuple>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/socket_permission.h"
+#include "url/url_canon.h"
+
+namespace {
+
+using content::SocketPermissionRequest;
+
+const char kColon = ':';
+const char kDot = '.';
+const char kWildcard[] = "*";
+const uint16_t kWildcardPortNumber = 0;
+const uint16_t kInvalidPort = 65535;
+
+bool StartsOrEndsWithWhitespace(const std::string& str) {
+ return !str.empty() && (base::IsUnicodeWhitespace(str.front()) ||
+ base::IsUnicodeWhitespace(str.back()));
+}
+
+} // namespace
+
+namespace extensions {
+
+SocketPermissionEntry::SocketPermissionEntry()
+ : pattern_(SocketPermissionRequest::NONE, std::string(), kInvalidPort),
+ match_subdomains_(false) {}
+
+SocketPermissionEntry::~SocketPermissionEntry() {}
+
+bool SocketPermissionEntry::operator<(const SocketPermissionEntry& rhs) const {
+ return std::tie(pattern_.type, pattern_.host, match_subdomains_,
+ pattern_.port) <
+ std::tie(rhs.pattern_.type, rhs.pattern_.host, rhs.match_subdomains_,
+ rhs.pattern_.port);
+}
+
+bool SocketPermissionEntry::operator==(const SocketPermissionEntry& rhs) const {
+ return (pattern_.type == rhs.pattern_.type) &&
+ (pattern_.host == rhs.pattern_.host) &&
+ (match_subdomains_ == rhs.match_subdomains_) &&
+ (pattern_.port == rhs.pattern_.port);
+}
+
+bool SocketPermissionEntry::Check(
+ const content::SocketPermissionRequest& request) const {
+ if (pattern_.type != request.type)
+ return false;
+
+ std::string lhost = base::ToLowerASCII(request.host);
+ if (pattern_.host != lhost) {
+ if (!match_subdomains_)
+ return false;
+
+ if (!pattern_.host.empty()) {
+ // Do not wildcard part of IP address.
+ url::Component component(0, lhost.length());
+ url::RawCanonOutputT<char, 128> ignored_output;
+ url::CanonHostInfo host_info;
+ url::CanonicalizeIPAddress(
+ lhost.c_str(), component, &ignored_output, &host_info);
+ if (host_info.IsIPAddress())
+ return false;
+
+ // host should equal one or more chars + "." + host_.
+ int i = lhost.length() - pattern_.host.length();
+ if (i < 2)
+ return false;
+
+ if (lhost.compare(i, pattern_.host.length(), pattern_.host) != 0)
+ return false;
+
+ if (lhost[i - 1] != kDot)
+ return false;
+ }
+ }
+
+ if (pattern_.port != request.port && pattern_.port != kWildcardPortNumber)
+ return false;
+
+ return true;
+}
+
+SocketPermissionEntry::HostType SocketPermissionEntry::GetHostType() const {
+ return pattern_.host.empty()
+ ? SocketPermissionEntry::ANY_HOST
+ : match_subdomains_ ? SocketPermissionEntry::HOSTS_IN_DOMAINS
+ : SocketPermissionEntry::SPECIFIC_HOSTS;
+}
+
+bool SocketPermissionEntry::IsAddressBoundType() const {
+ return pattern_.type == SocketPermissionRequest::TCP_CONNECT ||
+ pattern_.type == SocketPermissionRequest::TCP_LISTEN ||
+ pattern_.type == SocketPermissionRequest::UDP_BIND ||
+ pattern_.type == SocketPermissionRequest::UDP_SEND_TO;
+}
+
+// static
+bool SocketPermissionEntry::ParseHostPattern(
+ SocketPermissionRequest::OperationType type,
+ const std::string& pattern,
+ SocketPermissionEntry* entry) {
+ std::vector<std::string> tokens =
+ base::SplitString(pattern, std::string(1, kColon), base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_ALL);
+ return ParseHostPattern(type, tokens, entry);
+}
+
+// static
+bool SocketPermissionEntry::ParseHostPattern(
+ SocketPermissionRequest::OperationType type,
+ const std::vector<std::string>& pattern_tokens,
+ SocketPermissionEntry* entry) {
+
+ SocketPermissionEntry result;
+
+ if (type == SocketPermissionRequest::NONE)
+ return false;
+
+ if (pattern_tokens.size() > 2)
+ return false;
+
+ result.pattern_.type = type;
+ result.pattern_.port = kWildcardPortNumber;
+ result.match_subdomains_ = true;
+
+ if (pattern_tokens.size() == 0) {
+ *entry = result;
+ return true;
+ }
+
+ // Return an error if address is specified for permissions that don't
+ // need it (such as 'resolve-host').
+ if (!result.IsAddressBoundType())
+ return false;
+
+ result.pattern_.host = pattern_tokens[0];
+ if (!result.pattern_.host.empty()) {
+ if (StartsOrEndsWithWhitespace(result.pattern_.host))
+ return false;
+ result.pattern_.host = base::ToLowerASCII(result.pattern_.host);
+
+ // The first component can optionally be '*' to match all subdomains.
+ std::vector<std::string> host_components =
+ base::SplitString(result.pattern_.host, std::string(1, kDot),
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ DCHECK(!host_components.empty());
+
+ if (host_components[0] == kWildcard || host_components[0].empty()) {
+ host_components.erase(host_components.begin(),
+ host_components.begin() + 1);
+ } else {
+ result.match_subdomains_ = false;
+ }
+ result.pattern_.host = base::JoinString(host_components, ".");
+ }
+
+ if (pattern_tokens.size() == 1 || pattern_tokens[1].empty() ||
+ pattern_tokens[1] == kWildcard) {
+ *entry = result;
+ return true;
+ }
+
+ if (StartsOrEndsWithWhitespace(pattern_tokens[1]))
+ return false;
+
+ int port;
+ if (!base::StringToInt(pattern_tokens[1], &port) || port < 1 || port > 65535)
+ return false;
+ result.pattern_.port = static_cast<uint16_t>(port);
+
+ *entry = result;
+ return true;
+}
+
+std::string SocketPermissionEntry::GetHostPatternAsString() const {
+ std::string result;
+
+ if (!IsAddressBoundType())
+ return result;
+
+ if (match_subdomains()) {
+ result.append(kWildcard);
+ if (!pattern_.host.empty())
+ result.append(1, kDot).append(pattern_.host);
+ } else {
+ result.append(pattern_.host);
+ }
+
+ if (pattern_.port == kWildcardPortNumber)
+ result.append(1, kColon).append(kWildcard);
+ else
+ result.append(1, kColon).append(base::UintToString(pattern_.port));
+
+ return result;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/socket_permission_entry.h b/chromium/extensions/common/permissions/socket_permission_entry.h
new file mode 100644
index 00000000000..ee4e2122940
--- /dev/null
+++ b/chromium/extensions/common/permissions/socket_permission_entry.h
@@ -0,0 +1,84 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_SOCKET_PERMISSION_ENTRY_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_SOCKET_PERMISSION_ENTRY_H_
+
+#include <string>
+#include <vector>
+
+#include "content/public/common/socket_permission_request.h"
+#include "ipc/ipc_param_traits.h"
+
+namespace ipc_fuzzer {
+template <class T>
+struct FuzzTraits;
+template <class T>
+struct GenerateTraits;
+} // namespace ipc_fuzzer
+
+namespace extensions {
+
+// Internal representation of a socket permission for a specific operation, such
+// as UDP "bind", host 127.0.0.1, port *.
+class SocketPermissionEntry {
+ public:
+ enum HostType { ANY_HOST, HOSTS_IN_DOMAINS, SPECIFIC_HOSTS, };
+
+ SocketPermissionEntry();
+ ~SocketPermissionEntry();
+
+ // operators <, == are needed by container std::set and algorithms
+ // std::set_includes and std::set_differences.
+ bool operator<(const SocketPermissionEntry& rhs) const;
+ bool operator==(const SocketPermissionEntry& rhs) const;
+
+ bool Check(const content::SocketPermissionRequest& request) const;
+
+ // Parse a host:port pattern for a given operation type.
+ // <pattern> := '' |
+ // <host> |
+ // ':' <port> |
+ // <host> ':' <port> |
+ //
+ // <host> := '*' |
+ // '*.' <anychar except '/' and '*'>+ |
+ // <anychar except '/' and '*'>+
+ //
+ // <port> := '*' |
+ // <port number between 0 and 65535>)
+ static bool ParseHostPattern(
+ content::SocketPermissionRequest::OperationType type,
+ const std::string& pattern,
+ SocketPermissionEntry* entry);
+
+ static bool ParseHostPattern(
+ content::SocketPermissionRequest::OperationType type,
+ const std::vector<std::string>& pattern_tokens,
+ SocketPermissionEntry* entry);
+
+ // Returns true if the permission type can be bound to a host or port.
+ bool IsAddressBoundType() const;
+
+ std::string GetHostPatternAsString() const;
+ HostType GetHostType() const;
+
+ const content::SocketPermissionRequest& pattern() const { return pattern_; }
+ bool match_subdomains() const { return match_subdomains_; }
+
+ private:
+ // Friend so ParamTraits can serialize us.
+ friend struct IPC::ParamTraits<SocketPermissionEntry>;
+ friend struct ipc_fuzzer::FuzzTraits<SocketPermissionEntry>;
+ friend struct ipc_fuzzer::GenerateTraits<SocketPermissionEntry>;
+
+ // The permission type, host and port.
+ content::SocketPermissionRequest pattern_;
+
+ // True if there was a wildcard in the host name.
+ bool match_subdomains_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_SOCKET_PERMISSION_ENTRY_H_
diff --git a/chromium/extensions/common/permissions/socket_permission_unittest.cc b/chromium/extensions/common/permissions/socket_permission_unittest.cc
new file mode 100644
index 00000000000..1ed207e814c
--- /dev/null
+++ b/chromium/extensions/common/permissions/socket_permission_unittest.cc
@@ -0,0 +1,330 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/pickle.h"
+#include "base/values.h"
+#include "extensions/common/permissions/permissions_info.h"
+#include "extensions/common/permissions/socket_permission.h"
+#include "extensions/common/permissions/socket_permission_data.h"
+#include "ipc/ipc_message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+using content::SocketPermissionRequest;
+
+void ParseTest(const std::string& permission,
+ const std::string& expected_result) {
+ SocketPermissionData data;
+ ASSERT_TRUE(data.ParseForTest(permission)) << "Parse permission \""
+ << permission << "\" failed.";
+ EXPECT_EQ(expected_result, data.GetAsStringForTest());
+}
+
+TEST(SocketPermissionTest, General) {
+ SocketPermissionData data1, data2;
+
+ CHECK(data1.ParseForTest("tcp-connect"));
+ CHECK(data2.ParseForTest("tcp-connect"));
+
+ EXPECT_TRUE(data1 == data2);
+ EXPECT_FALSE(data1 < data2);
+
+ CHECK(data1.ParseForTest("tcp-connect"));
+ CHECK(data2.ParseForTest("tcp-connect:www.example.com"));
+
+ EXPECT_FALSE(data1 == data2);
+ EXPECT_TRUE(data1 < data2);
+}
+
+TEST(SocketPermissionTest, Parse) {
+ SocketPermissionData data;
+
+ EXPECT_FALSE(data.ParseForTest(std::string()));
+ EXPECT_FALSE(data.ParseForTest("*"));
+ EXPECT_FALSE(data.ParseForTest("\00\00*"));
+ EXPECT_FALSE(data.ParseForTest("\01*"));
+ EXPECT_FALSE(data.ParseForTest("tcp-connect:www.example.com:-1"));
+ EXPECT_FALSE(data.ParseForTest("tcp-connect:www.example.com:65536"));
+ EXPECT_FALSE(data.ParseForTest("tcp-connect:::"));
+ EXPECT_FALSE(data.ParseForTest("tcp-connect::0"));
+ EXPECT_FALSE(data.ParseForTest("tcp-connect: www.exmaple.com: 99 "));
+ EXPECT_FALSE(data.ParseForTest("tcp-connect:*.exmaple.com :99"));
+ EXPECT_FALSE(data.ParseForTest("tcp-connect:*.exmaple.com: 99"));
+ EXPECT_FALSE(data.ParseForTest("tcp-connect:*.exmaple.com:99 "));
+ EXPECT_FALSE(data.ParseForTest("tcp-connect:\t*.exmaple.com:99"));
+ EXPECT_FALSE(data.ParseForTest("tcp-connect:\n*.exmaple.com:99"));
+ EXPECT_FALSE(data.ParseForTest("resolve-host:exmaple.com:99"));
+ EXPECT_FALSE(data.ParseForTest("resolve-host:127.0.0.1"));
+ EXPECT_FALSE(data.ParseForTest("resolve-host:"));
+ EXPECT_FALSE(data.ParseForTest("resolve-proxy:exmaple.com:99"));
+ EXPECT_FALSE(data.ParseForTest("resolve-proxy:exmaple.com"));
+
+ ParseTest("tcp-connect", "tcp-connect:*:*");
+ ParseTest("tcp-listen", "tcp-listen:*:*");
+ ParseTest("udp-bind", "udp-bind:*:*");
+ ParseTest("udp-send-to", "udp-send-to:*:*");
+ ParseTest("resolve-host", "resolve-host");
+ ParseTest("resolve-proxy", "resolve-proxy");
+
+ ParseTest("tcp-connect:", "tcp-connect:*:*");
+ ParseTest("tcp-listen:", "tcp-listen:*:*");
+ ParseTest("udp-bind:", "udp-bind:*:*");
+ ParseTest("udp-send-to:", "udp-send-to:*:*");
+
+ ParseTest("tcp-connect::", "tcp-connect:*:*");
+ ParseTest("tcp-listen::", "tcp-listen:*:*");
+ ParseTest("udp-bind::", "udp-bind:*:*");
+ ParseTest("udp-send-to::", "udp-send-to:*:*");
+
+ ParseTest("tcp-connect:*", "tcp-connect:*:*");
+ ParseTest("tcp-listen:*", "tcp-listen:*:*");
+ ParseTest("udp-bind:*", "udp-bind:*:*");
+ ParseTest("udp-send-to:*", "udp-send-to:*:*");
+
+ ParseTest("tcp-connect:*:", "tcp-connect:*:*");
+ ParseTest("tcp-listen:*:", "tcp-listen:*:*");
+ ParseTest("udp-bind:*:", "udp-bind:*:*");
+ ParseTest("udp-send-to:*:", "udp-send-to:*:*");
+
+ ParseTest("tcp-connect::*", "tcp-connect:*:*");
+ ParseTest("tcp-listen::*", "tcp-listen:*:*");
+ ParseTest("udp-bind::*", "udp-bind:*:*");
+ ParseTest("udp-send-to::*", "udp-send-to:*:*");
+
+ ParseTest("tcp-connect:www.example.com", "tcp-connect:www.example.com:*");
+ ParseTest("tcp-listen:www.example.com", "tcp-listen:www.example.com:*");
+ ParseTest("udp-bind:www.example.com", "udp-bind:www.example.com:*");
+ ParseTest("udp-send-to:www.example.com", "udp-send-to:www.example.com:*");
+ ParseTest("udp-send-to:wWW.ExAmPlE.cOm", "udp-send-to:www.example.com:*");
+
+ ParseTest("tcp-connect:.example.com", "tcp-connect:*.example.com:*");
+ ParseTest("tcp-listen:.example.com", "tcp-listen:*.example.com:*");
+ ParseTest("udp-bind:.example.com", "udp-bind:*.example.com:*");
+ ParseTest("udp-send-to:.example.com", "udp-send-to:*.example.com:*");
+
+ ParseTest("tcp-connect:*.example.com", "tcp-connect:*.example.com:*");
+ ParseTest("tcp-listen:*.example.com", "tcp-listen:*.example.com:*");
+ ParseTest("udp-bind:*.example.com", "udp-bind:*.example.com:*");
+ ParseTest("udp-send-to:*.example.com", "udp-send-to:*.example.com:*");
+
+ ParseTest("tcp-connect::99", "tcp-connect:*:99");
+ ParseTest("tcp-listen::99", "tcp-listen:*:99");
+ ParseTest("udp-bind::99", "udp-bind:*:99");
+ ParseTest("udp-send-to::99", "udp-send-to:*:99");
+
+ ParseTest("tcp-connect:www.example.com", "tcp-connect:www.example.com:*");
+
+ ParseTest("tcp-connect:*.example.com:99", "tcp-connect:*.example.com:99");
+}
+
+TEST(SocketPermissionTest, Match) {
+ SocketPermissionData data;
+ scoped_ptr<SocketPermission::CheckParam> param;
+
+ CHECK(data.ParseForTest("tcp-connect"));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80));
+ EXPECT_TRUE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_SEND_TO, "www.example.com", 80));
+ EXPECT_FALSE(data.Check(param.get()));
+
+ CHECK(data.ParseForTest("udp-send-to::8800"));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_SEND_TO, "www.example.com", 8800));
+ EXPECT_TRUE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_SEND_TO, "smtp.example.com", 8800));
+ EXPECT_TRUE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80));
+ EXPECT_FALSE(data.Check(param.get()));
+
+ CHECK(data.ParseForTest("udp-send-to:*.example.com:8800"));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_SEND_TO, "www.example.com", 8800));
+ EXPECT_TRUE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_SEND_TO, "smtp.example.com", 8800));
+ EXPECT_TRUE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_SEND_TO, "SMTP.example.com", 8800));
+ EXPECT_TRUE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80));
+ EXPECT_FALSE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_SEND_TO, "www.google.com", 8800));
+ EXPECT_FALSE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_SEND_TO, "wwwexample.com", 8800));
+ EXPECT_FALSE(data.Check(param.get()));
+
+ CHECK(data.ParseForTest("udp-send-to:*.ExAmPlE.cOm:8800"));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_SEND_TO, "www.example.com", 8800));
+ EXPECT_TRUE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_SEND_TO, "smtp.example.com", 8800));
+ EXPECT_TRUE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_SEND_TO, "SMTP.example.com", 8800));
+ EXPECT_TRUE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80));
+ EXPECT_FALSE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_SEND_TO, "www.google.com", 8800));
+ EXPECT_FALSE(data.Check(param.get()));
+
+ ASSERT_TRUE(data.ParseForTest("udp-bind::8800"));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_BIND, "127.0.0.1", 8800));
+ EXPECT_TRUE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_BIND, "127.0.0.1", 8888));
+ EXPECT_FALSE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80));
+ EXPECT_FALSE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_SEND_TO, "www.google.com", 8800));
+ EXPECT_FALSE(data.Check(param.get()));
+
+ // Do not wildcard part of ip address.
+ ASSERT_TRUE(data.ParseForTest("tcp-connect:*.168.0.1:8800"));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::TCP_CONNECT, "192.168.0.1", 8800));
+ EXPECT_FALSE(data.Check(param.get()));
+
+ ASSERT_FALSE(data.ParseForTest("udp-multicast-membership:*"));
+ ASSERT_FALSE(data.ParseForTest("udp-multicast-membership:*:*"));
+ ASSERT_TRUE(data.ParseForTest("udp-multicast-membership"));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_BIND, "127.0.0.1", 8800));
+ EXPECT_FALSE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_BIND, "127.0.0.1", 8888));
+ EXPECT_FALSE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80));
+ EXPECT_FALSE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_SEND_TO, "www.google.com", 8800));
+ EXPECT_FALSE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP, "127.0.0.1", 35));
+ EXPECT_TRUE(data.Check(param.get()));
+
+ ASSERT_TRUE(data.ParseForTest("resolve-host"));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::RESOLVE_HOST, "www.example.com", 80));
+ EXPECT_TRUE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::RESOLVE_HOST, "www.example.com", 8080));
+ EXPECT_TRUE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_BIND, "127.0.0.1", 8800));
+ EXPECT_FALSE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::TCP_CONNECT, "127.0.0.1", 8800));
+ EXPECT_FALSE(data.Check(param.get()));
+
+ ASSERT_TRUE(data.ParseForTest("resolve-proxy"));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::RESOLVE_PROXY, "www.example.com", 80));
+ EXPECT_TRUE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::RESOLVE_PROXY, "www.example.com", 8080));
+ EXPECT_TRUE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_BIND, "127.0.0.1", 8800));
+ EXPECT_FALSE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::TCP_CONNECT, "127.0.0.1", 8800));
+ EXPECT_FALSE(data.Check(param.get()));
+
+ ASSERT_TRUE(data.ParseForTest("network-state"));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::NETWORK_STATE, std::string(), 0));
+ EXPECT_TRUE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_BIND, "127.0.0.1", 8800));
+ EXPECT_FALSE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::TCP_CONNECT, "127.0.0.1", 8800));
+ EXPECT_FALSE(data.Check(param.get()));
+}
+
+TEST(SocketPermissionTest, IPC) {
+ const APIPermissionInfo* permission_info =
+ PermissionsInfo::GetInstance()->GetByID(APIPermission::kSocket);
+
+ {
+ IPC::Message m;
+
+ scoped_ptr<APIPermission> permission1(
+ permission_info->CreateAPIPermission());
+ scoped_ptr<APIPermission> permission2(
+ permission_info->CreateAPIPermission());
+
+ permission1->Write(&m);
+ base::PickleIterator iter(m);
+ permission2->Read(&m, &iter);
+
+ EXPECT_TRUE(permission1->Equal(permission2.get()));
+ }
+
+ {
+ IPC::Message m;
+
+ scoped_ptr<APIPermission> permission1(
+ permission_info->CreateAPIPermission());
+ scoped_ptr<APIPermission> permission2(
+ permission_info->CreateAPIPermission());
+
+ scoped_ptr<base::ListValue> value(new base::ListValue());
+ value->AppendString("tcp-connect:*.example.com:80");
+ value->AppendString("udp-bind::8080");
+ value->AppendString("udp-send-to::8888");
+ ASSERT_TRUE(permission1->FromValue(value.get(), NULL, NULL));
+
+ EXPECT_FALSE(permission1->Equal(permission2.get()));
+
+ permission1->Write(&m);
+ base::PickleIterator iter(m);
+ permission2->Read(&m, &iter);
+ EXPECT_TRUE(permission1->Equal(permission2.get()));
+ }
+}
+
+TEST(SocketPermissionTest, Value) {
+ const APIPermissionInfo* permission_info =
+ PermissionsInfo::GetInstance()->GetByID(APIPermission::kSocket);
+
+ scoped_ptr<APIPermission> permission1(permission_info->CreateAPIPermission());
+ scoped_ptr<APIPermission> permission2(permission_info->CreateAPIPermission());
+
+ scoped_ptr<base::ListValue> value(new base::ListValue());
+ value->AppendString("tcp-connect:*.example.com:80");
+ value->AppendString("udp-bind::8080");
+ value->AppendString("udp-send-to::8888");
+ ASSERT_TRUE(permission1->FromValue(value.get(), NULL, NULL));
+
+ EXPECT_FALSE(permission1->Equal(permission2.get()));
+
+ scoped_ptr<base::Value> vtmp(permission1->ToValue());
+ ASSERT_TRUE(vtmp);
+ ASSERT_TRUE(permission2->FromValue(vtmp.get(), NULL, NULL));
+ EXPECT_TRUE(permission1->Equal(permission2.get()));
+}
+
+} // namespace
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/usb_device_permission.cc b/chromium/extensions/common/permissions/usb_device_permission.cc
new file mode 100644
index 00000000000..fd94783b784
--- /dev/null
+++ b/chromium/extensions/common/permissions/usb_device_permission.cc
@@ -0,0 +1,80 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/usb_device_permission.h"
+
+#include <set>
+#include <string>
+
+#include "base/logging.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "device/usb/usb_ids.h"
+#include "extensions/common/permissions/permissions_info.h"
+#include "grit/extensions_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+UsbDevicePermission::UsbDevicePermission(const APIPermissionInfo* info)
+ : SetDisjunctionPermission<UsbDevicePermissionData, UsbDevicePermission>(
+ info) {}
+
+UsbDevicePermission::~UsbDevicePermission() {}
+
+bool UsbDevicePermission::FromValue(
+ const base::Value* value,
+ std::string* error,
+ std::vector<std::string>* unhandled_permissions) {
+ bool parsed_ok =
+ SetDisjunctionPermission<UsbDevicePermissionData, UsbDevicePermission>::
+ FromValue(value, error, unhandled_permissions);
+ if (parsed_ok && data_set_.empty()) {
+ if (error)
+ *error = "NULL or empty permission list";
+ return false;
+ }
+ return parsed_ok;
+}
+
+PermissionIDSet UsbDevicePermission::GetPermissions() const {
+ PermissionIDSet ids;
+
+ std::set<uint16_t> unknown_product_vendors;
+ bool found_unknown_vendor = false;
+
+ for (const UsbDevicePermissionData& data : data_set_) {
+ const char* vendor = device::UsbIds::GetVendorName(data.vendor_id());
+ if (vendor) {
+ const char* product =
+ device::UsbIds::GetProductName(data.vendor_id(), data.product_id());
+ if (product) {
+ base::string16 product_name_and_vendor = l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_USB_DEVICE_PRODUCT_NAME_AND_VENDOR,
+ base::UTF8ToUTF16(product), base::UTF8ToUTF16(vendor));
+ ids.insert(APIPermission::kUsbDevice, product_name_and_vendor);
+ } else {
+ unknown_product_vendors.insert(data.vendor_id());
+ }
+ } else {
+ found_unknown_vendor = true;
+ }
+ }
+
+ for (uint16_t vendor_id : unknown_product_vendors) {
+ const char* vendor = device::UsbIds::GetVendorName(vendor_id);
+ DCHECK(vendor);
+ ids.insert(APIPermission::kUsbDeviceUnknownProduct,
+ base::UTF8ToUTF16(vendor));
+ }
+
+ if (found_unknown_vendor)
+ ids.insert(APIPermission::kUsbDeviceUnknownVendor);
+
+ return ids;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/usb_device_permission.h b/chromium/extensions/common/permissions/usb_device_permission.h
new file mode 100644
index 00000000000..2658011861d
--- /dev/null
+++ b/chromium/extensions/common/permissions/usb_device_permission.h
@@ -0,0 +1,44 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_USB_DEVICE_PERMISSION_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_USB_DEVICE_PERMISSION_H_
+
+#include <stdint.h>
+
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/set_disjunction_permission.h"
+#include "extensions/common/permissions/usb_device_permission_data.h"
+
+namespace extensions {
+
+class UsbDevicePermission
+ : public SetDisjunctionPermission<UsbDevicePermissionData,
+ UsbDevicePermission> {
+ public:
+ struct CheckParam : public APIPermission::CheckParam {
+ CheckParam(uint16_t vendor_id, uint16_t product_id, int interface_id)
+ : vendor_id(vendor_id),
+ product_id(product_id),
+ interface_id(interface_id) {}
+ const uint16_t vendor_id;
+ const uint16_t product_id;
+ const int interface_id;
+ };
+
+ explicit UsbDevicePermission(const APIPermissionInfo* info);
+ ~UsbDevicePermission() override;
+
+ // SetDisjunctionPermission overrides.
+ bool FromValue(const base::Value* value,
+ std::string* error,
+ std::vector<std::string>* unhandled_permissions) override;
+
+ // APIPermission overrides
+ PermissionIDSet GetPermissions() const override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_USB_DEVICE_PERMISSION_H_
diff --git a/chromium/extensions/common/permissions/usb_device_permission_data.cc b/chromium/extensions/common/permissions/usb_device_permission_data.cc
new file mode 100644
index 00000000000..3d6817b72ca
--- /dev/null
+++ b/chromium/extensions/common/permissions/usb_device_permission_data.cc
@@ -0,0 +1,104 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/usb_device_permission_data.h"
+
+#include <limits>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/values.h"
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/usb_device_permission.h"
+
+namespace {
+
+const char* kProductIdKey = "productId";
+const char* kVendorIdKey = "vendorId";
+const char* kInterfaceIdKey = "interfaceId";
+
+} // namespace
+
+namespace extensions {
+
+UsbDevicePermissionData::UsbDevicePermissionData()
+ : vendor_id_(0), product_id_(0), interface_id_(ANY_INTERFACE) {
+}
+
+UsbDevicePermissionData::UsbDevicePermissionData(uint16_t vendor_id,
+ uint16_t product_id,
+ int interface_id)
+ : vendor_id_(vendor_id),
+ product_id_(product_id),
+ interface_id_(interface_id) {}
+
+bool UsbDevicePermissionData::Check(
+ const APIPermission::CheckParam* param) const {
+ if (!param)
+ return false;
+ const UsbDevicePermission::CheckParam& specific_param =
+ *static_cast<const UsbDevicePermission::CheckParam*>(param);
+ return vendor_id_ == specific_param.vendor_id &&
+ product_id_ == specific_param.product_id &&
+ (specific_param.interface_id == UNSPECIFIED_INTERFACE ||
+ interface_id_ == specific_param.interface_id);
+}
+
+scoped_ptr<base::Value> UsbDevicePermissionData::ToValue() const {
+ base::DictionaryValue* result = new base::DictionaryValue();
+ result->SetInteger(kVendorIdKey, vendor_id_);
+ result->SetInteger(kProductIdKey, product_id_);
+ result->SetInteger(kInterfaceIdKey, interface_id_);
+ return scoped_ptr<base::Value>(result);
+}
+
+bool UsbDevicePermissionData::FromValue(const base::Value* value) {
+ if (!value)
+ return false;
+
+ const base::DictionaryValue* dict_value;
+ if (!value->GetAsDictionary(&dict_value))
+ return false;
+
+ int temp;
+ if (!dict_value->GetInteger(kVendorIdKey, &temp))
+ return false;
+ if (temp < 0 || temp > std::numeric_limits<uint16_t>::max())
+ return false;
+ vendor_id_ = temp;
+
+ if (!dict_value->GetInteger(kProductIdKey, &temp))
+ return false;
+ if (temp < 0 || temp > std::numeric_limits<uint16_t>::max())
+ return false;
+ product_id_ = temp;
+
+ if (!dict_value->GetInteger(kInterfaceIdKey, &temp))
+ interface_id_ = ANY_INTERFACE;
+ else if (temp < ANY_INTERFACE || temp > std::numeric_limits<uint8_t>::max())
+ return false;
+ else
+ interface_id_ = temp;
+
+ return true;
+}
+
+bool UsbDevicePermissionData::operator<(
+ const UsbDevicePermissionData& rhs) const {
+ return std::tie(vendor_id_, product_id_, interface_id_) <
+ std::tie(rhs.vendor_id_, rhs.product_id_, rhs.interface_id_);
+}
+
+bool UsbDevicePermissionData::operator==(
+ const UsbDevicePermissionData& rhs) const {
+ return vendor_id_ == rhs.vendor_id_ &&
+ product_id_ == rhs.product_id_ &&
+ interface_id_ == rhs.interface_id_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/permissions/usb_device_permission_data.h b/chromium/extensions/common/permissions/usb_device_permission_data.h
new file mode 100644
index 00000000000..e72cc6c2b2a
--- /dev/null
+++ b/chromium/extensions/common/permissions/usb_device_permission_data.h
@@ -0,0 +1,72 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef EXTENSIONS_COMMON_PERMISSIONS_USB_DEVICE_PERMISSION_DATA_H_
+#define EXTENSIONS_COMMON_PERMISSIONS_USB_DEVICE_PERMISSION_DATA_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/permissions/api_permission.h"
+
+namespace base {
+
+class Value;
+
+} // namespace base
+
+namespace extensions {
+
+// A pattern that can be used to match a USB device permission.
+// Should be of the format: vendorId:productId, where both vendorId and
+// productId are decimal strings representing uint16_t values.
+class UsbDevicePermissionData {
+ public:
+ enum SpecialInterfaces {
+ // A special interface id for stating permissions for an entire USB device,
+ // no specific interface. This value must match value of Rule::ANY_INTERFACE
+ // from ChromeOS permission_broker project.
+ ANY_INTERFACE = -1,
+
+ // A special interface id for |Check| to indicate that interface field is
+ // not to be checked. Not used in manifest file.
+ UNSPECIFIED_INTERFACE = -2
+ };
+
+ UsbDevicePermissionData();
+ UsbDevicePermissionData(uint16_t vendor_id,
+ uint16_t product_id,
+ int interface_id);
+
+ // Check if |param| (which must be a UsbDevicePermissionData::CheckParam)
+ // matches the vendor and product IDs associated with |this|.
+ bool Check(const APIPermission::CheckParam* param) const;
+
+ // Convert |this| into a base::Value.
+ scoped_ptr<base::Value> ToValue() const;
+
+ // Populate |this| from a base::Value.
+ bool FromValue(const base::Value* value);
+
+ bool operator<(const UsbDevicePermissionData& rhs) const;
+ bool operator==(const UsbDevicePermissionData& rhs) const;
+
+ const uint16_t& vendor_id() const { return vendor_id_; }
+ const uint16_t& product_id() const { return product_id_; }
+
+ // These accessors are provided for IPC_STRUCT_TRAITS_MEMBER. Please
+ // think twice before using them for anything else.
+ uint16_t& vendor_id() { return vendor_id_; }
+ uint16_t& product_id() { return product_id_; }
+
+ private:
+ uint16_t vendor_id_;
+ uint16_t product_id_;
+ int interface_id_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_PERMISSIONS_USB_DEVICE_PERMISSION_DATA_H_
diff --git a/chromium/extensions/common/permissions/usb_device_permission_unittest.cc b/chromium/extensions/common/permissions/usb_device_permission_unittest.cc
new file mode 100644
index 00000000000..73971964dec
--- /dev/null
+++ b/chromium/extensions/common/permissions/usb_device_permission_unittest.cc
@@ -0,0 +1,19 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/permissions/usb_device_permission_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+TEST(USBDevicePermissionTest, PermissionDataOrder) {
+ EXPECT_LT(UsbDevicePermissionData(0x02ad, 0x138c, -1),
+ UsbDevicePermissionData(0x02ad, 0x138d, -1));
+ ASSERT_LT(UsbDevicePermissionData(0x02ad, 0x138d, -1),
+ UsbDevicePermissionData(0x02ae, 0x138c, -1));
+ EXPECT_LT(UsbDevicePermissionData(0x02ad, 0x138c, -1),
+ UsbDevicePermissionData(0x02ad, 0x138c, 0));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/stack_frame.cc b/chromium/extensions/common/stack_frame.cc
new file mode 100644
index 00000000000..046a2652250
--- /dev/null
+++ b/chromium/extensions/common/stack_frame.cc
@@ -0,0 +1,79 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/stack_frame.h"
+
+#include <string>
+
+#include "base/strings/utf_string_conversions.h"
+#include "third_party/re2/src/re2/re2.h"
+
+namespace extensions {
+
+namespace {
+const char kAnonymousFunction[] = "(anonymous function)";
+}
+
+StackFrame::StackFrame() : line_number(1), column_number(1) {
+}
+
+StackFrame::StackFrame(const StackFrame& frame)
+ : line_number(frame.line_number),
+ column_number(frame.column_number),
+ source(frame.source),
+ function(frame.function) {
+}
+
+StackFrame::StackFrame(uint32_t line_number,
+ uint32_t column_number,
+ const base::string16& source,
+ const base::string16& function)
+ : line_number(line_number),
+ column_number(column_number),
+ source(source),
+ function(function.empty() ? base::UTF8ToUTF16(kAnonymousFunction)
+ : function) {}
+
+StackFrame::~StackFrame() {
+}
+
+// Create a stack frame from the passed text. The text must follow one of two
+// formats:
+// - "function_name (source:line_number:column_number)"
+// - "source:line_number:column_number"
+// (We have to recognize two formats because V8 will report stack traces in
+// both ways. If we reconcile this, we can clean this up.)
+// static
+scoped_ptr<StackFrame> StackFrame::CreateFromText(
+ const base::string16& frame_text) {
+ // We need to use utf8 for re2 matching.
+ std::string text = base::UTF16ToUTF8(frame_text);
+
+ size_t line = 1;
+ size_t column = 1;
+ std::string source;
+ std::string function;
+ if (!re2::RE2::FullMatch(text,
+ "(.+) \\(([^\\(\\)]+):(\\d+):(\\d+)\\)",
+ &function, &source, &line, &column) &&
+ !re2::RE2::FullMatch(text,
+ "([^\\(\\)]+):(\\d+):(\\d+)",
+ &source, &line, &column)) {
+ return scoped_ptr<StackFrame>();
+ }
+
+ return scoped_ptr<StackFrame>(new StackFrame(line,
+ column,
+ base::UTF8ToUTF16(source),
+ base::UTF8ToUTF16(function)));
+}
+
+bool StackFrame::operator==(const StackFrame& rhs) const {
+ return line_number == rhs.line_number &&
+ column_number == rhs.column_number &&
+ source == rhs.source &&
+ function == rhs.function;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/stack_frame.h b/chromium/extensions/common/stack_frame.h
new file mode 100644
index 00000000000..a55b8d82863
--- /dev/null
+++ b/chromium/extensions/common/stack_frame.h
@@ -0,0 +1,46 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_STACK_FRAME_H_
+#define EXTENSIONS_COMMON_STACK_FRAME_H_
+
+#include <stddef.h>
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+
+namespace extensions {
+
+struct StackFrame {
+ StackFrame();
+ StackFrame(const StackFrame& frame);
+ StackFrame(uint32_t line_number,
+ uint32_t column_number,
+ const base::string16& source,
+ const base::string16& function);
+ ~StackFrame();
+
+ // Construct a stack frame from a reported plain-text frame.
+ static scoped_ptr<StackFrame> CreateFromText(
+ const base::string16& frame_text);
+
+ bool operator==(const StackFrame& rhs) const;
+
+ // Note: we use uint32_t instead of size_t because this struct is sent over
+ // IPC which could span 32 & 64 bit processes. This is fine since line numbers
+ // and column numbers shouldn't exceed UINT32_MAX even on 64 bit builds.
+ uint32_t line_number;
+ uint32_t column_number;
+ base::string16 source;
+ base::string16 function; // optional
+};
+
+typedef std::vector<StackFrame> StackTrace;
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_STACK_FRAME_H_
+
diff --git a/chromium/extensions/common/stack_frame_unittest.cc b/chromium/extensions/common/stack_frame_unittest.cc
new file mode 100644
index 00000000000..51f886774e5
--- /dev/null
+++ b/chromium/extensions/common/stack_frame_unittest.cc
@@ -0,0 +1,87 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/stack_frame.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::UTF8ToUTF16;
+
+namespace extensions {
+
+namespace {
+
+void AssertStackFrameValid(const std::string& text,
+ size_t line,
+ size_t column,
+ const std::string& source,
+ const std::string& function) {
+ base::string16 utf16_text = base::UTF8ToUTF16(text);
+ scoped_ptr<StackFrame> frame = StackFrame::CreateFromText(utf16_text);
+
+ ASSERT_TRUE(frame.get()) << "Failed to create frame from '" << text << "'";
+ EXPECT_EQ(line, frame->line_number);
+ EXPECT_EQ(column, frame->column_number);
+ EXPECT_EQ(base::UTF8ToUTF16(source), frame->source);
+ EXPECT_EQ(base::UTF8ToUTF16(function), frame->function);
+}
+
+void AssertStackFrameInvalid(const std::string& text) {
+ base::string16 utf16_text = base::UTF8ToUTF16(text);
+ scoped_ptr<StackFrame> frame = StackFrame::CreateFromText(utf16_text);
+ ASSERT_FALSE(frame.get()) << "Errantly created frame from '" << text << "'";
+}
+
+} // namespace
+
+TEST(StackFrameUnitTest, ParseStackFramesFromText) {
+ AssertStackFrameValid(
+ "function_name (https://www.url.com/foo.html:100:201)",
+ 100u, 201u, "https://www.url.com/foo.html", "function_name");
+ AssertStackFrameValid(
+ "(anonymous function) (https://www.url.com/foo.html:100:201)",
+ 100u, 201u, "https://www.url.com/foo.html", "(anonymous function)");
+ AssertStackFrameValid(
+ "Function.target.(anonymous function) (internals::SafeBuiltins:19:14)",
+ 19u, 14u, "internals::SafeBuiltins",
+ "Function.target.(anonymous function)");
+ AssertStackFrameValid(
+ "internal-item:://fpgohbggpmcpeedljibghijiclejiklo/script.js:6:12",
+ 6u, 12u, "internal-item:://fpgohbggpmcpeedljibghijiclejiklo/script.js",
+ "(anonymous function)");
+
+ // No delimiting ':' between line/column numbers.
+ AssertStackFrameInvalid(
+ "function_name (https://www.url.com/foo.html:100201)");
+ // No line number.
+ AssertStackFrameInvalid("function_name (https://www.url.com/foo.html::201)");
+ // No line number or delimiting ':'.
+ AssertStackFrameInvalid("function_name (https://www.url.com/foo.html201)");
+ // No leading '(' around url, line, column.
+ AssertStackFrameInvalid(
+ "function_name https://www.url.com/foo.html:100:201)");
+ // No trailing ')'.
+ AssertStackFrameInvalid(
+ "function_name (https://www.url.com/foo.html:100:201");
+ // Trailing ' '.
+ AssertStackFrameInvalid(
+ "function_name (https://www.url.com/foo.html:100:201) ");
+ // Invalid column number.
+ AssertStackFrameInvalid(
+ "function_name (https://www.url.com/foo.html:100:201a)");
+ // Negative column number.
+ AssertStackFrameInvalid(
+ "function_name (https://www.url.com/foo.html:100:-201)");
+ // Extra trailing ')'
+ AssertStackFrameInvalid(
+ "function_name (https://www.url.com/foo.html:100:201))");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/switches.cc b/chromium/extensions/common/switches.cc
new file mode 100644
index 00000000000..9f60f9619f6
--- /dev/null
+++ b/chromium/extensions/common/switches.cc
@@ -0,0 +1,119 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/switches.h"
+
+namespace extensions {
+
+namespace switches {
+
+// Allows non-https URL for background_page for hosted apps.
+const char kAllowHTTPBackgroundPage[] = "allow-http-background-page";
+
+// Allows the browser to load extensions that lack a modern manifest when that
+// would otherwise be forbidden.
+const char kAllowLegacyExtensionManifests[] =
+ "allow-legacy-extension-manifests";
+
+// Enables extension options to be embedded in chrome://extensions rather than
+// a new tab.
+const char kEmbeddedExtensionOptions[] = "embedded-extension-options";
+
+// Show apps windows after the first paint. Windows will be shown significantly
+// later for heavy apps loading resources synchronously but it will be
+// insignificant for apps that load most of their resources asynchronously.
+const char kEnableAppsShowOnFirstPaint[] = "enable-apps-show-on-first-paint";
+
+// Enables the <window-controls> tag in platform apps.
+const char kEnableAppWindowControls[] = "enable-app-window-controls";
+
+// Enable BLE Advertisiing in apps.
+const char kEnableBLEAdvertising[] = "enable-ble-advertising-in-apps";
+
+const char kDisableDesktopCaptureAudio[] =
+ "disable-audio-support-for-desktop-share";
+
+// Hack so that feature switch can work with about_flags. See
+// kEnableScriptsRequireAction.
+const char kEnableEmbeddedExtensionOptions[] =
+ "enable-embedded-extension-options";
+
+// Enables extension APIs that are in development.
+const char kEnableExperimentalExtensionApis[] =
+ "enable-experimental-extension-apis";
+
+// Hack so that feature switch can work with about_flags. See
+// kEnableScriptsRequireAction.
+const char kEnableExtensionActionRedesign[] =
+ "enable-extension-action-redesign";
+
+// Enables the mojo implementation of the serial API.
+const char kEnableMojoSerialService[] = "enable-mojo-serial-service";
+
+// Enables extensions to hide bookmarks UI elements.
+const char kEnableOverrideBookmarksUI[] = "enable-override-bookmarks-ui";
+
+// Enables tab for desktop sharing.
+const char kDisableTabForDesktopShare[] = "disable-tab-for-desktop-share";
+const char kEnableTabForDesktopShare[] = "enable-tab-for-desktop-share";
+
+// Allows the ErrorConsole to collect runtime and manifest errors, and display
+// them in the chrome:extensions page.
+const char kErrorConsole[] = "error-console";
+
+// Whether to switch to extension action redesign mode (experimental).
+const char kExtensionActionRedesign[] = "extension-action-redesign";
+
+// Marks a renderer as extension process.
+const char kExtensionProcess[] = "extension-process";
+
+// Enables extensions running scripts on chrome:// URLs.
+// Extensions still need to explicitly request access to chrome:// URLs in the
+// manifest.
+const char kExtensionsOnChromeURLs[] = "extensions-on-chrome-urls";
+
+// Whether to force developer mode extensions highlighting.
+const char kForceDevModeHighlighting[] = "force-dev-mode-highlighting";
+
+// Enables site isolation for all chrome-extension:// urls.
+const char kIsolateExtensions[] = "isolate-extensions";
+
+// Path to a comma-separated list of apps to load at startup. The first app in
+// the list will be launched.
+const char kLoadApps[] = "load-apps";
+
+// Notify the user and require consent for extensions running scripts.
+// Appending --scripts-require-action=1 has the same effect as
+// --enable-scripts-require-action (see below).
+const char kScriptsRequireAction[] = "scripts-require-action";
+// FeatureSwitch and about_flags don't play nice. Feature switch expects either
+// --enable-<feature> or --<feature>=1, but about_flags expects the command
+// line argument to enable it (or a selection). Hack this in, so enabling it
+// in about_flags enables the feature. Appending this flag has the same effect
+// as --scripts-require-action=1.
+const char kEnableScriptsRequireAction[] = "enable-scripts-require-action";
+
+#if defined(CHROMIUM_BUILD)
+// Should we prompt the user before allowing external extensions to install?
+// This flag is available on Chromium for testing purposes.
+const char kPromptForExternalExtensions[] = "prompt-for-external-extensions";
+#endif
+
+// Makes component extensions appear in chrome://settings/extensions.
+const char kShowComponentExtensionOptions[] =
+ "show-component-extension-options";
+
+// Adds the given extension ID to all the permission whitelists.
+const char kWhitelistedExtensionID[] = "whitelisted-extension-id";
+
+// Pass launch source to platform apps.
+const char kTraceAppSource[] = "enable-trace-app-source";
+
+// Enable package hash check: the .crx file sha256 hash sum should be equal to
+// the one received from update manifest.
+const char kEnableCrxHashCheck[] = "enable-crx-hash-check";
+
+} // namespace switches
+
+} // namespace extensions
diff --git a/chromium/extensions/common/switches.h b/chromium/extensions/common/switches.h
new file mode 100644
index 00000000000..a812c12d62f
--- /dev/null
+++ b/chromium/extensions/common/switches.h
@@ -0,0 +1,49 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_SWITCHES_H_
+#define EXTENSIONS_COMMON_SWITCHES_H_
+
+// All switches in alphabetical order. The switches should be documented
+// alongside the definition of their values in the .cc file.
+namespace extensions {
+
+namespace switches {
+
+extern const char kAllowHTTPBackgroundPage[];
+extern const char kAllowLegacyExtensionManifests[];
+extern const char kDisableDesktopCaptureAudio[];
+extern const char kDisableTabForDesktopShare[];
+extern const char kEmbeddedExtensionOptions[];
+extern const char kEnableAppsShowOnFirstPaint[];
+extern const char kEnableAppWindowControls[];
+extern const char kEnableEmbeddedExtensionOptions[];
+extern const char kEnableExperimentalExtensionApis[];
+extern const char kEnableExtensionActionRedesign[];
+extern const char kEnableMojoSerialService[];
+extern const char kEnableOverrideBookmarksUI[];
+extern const char kEnableBLEAdvertising[];
+extern const char kEnableTabForDesktopShare[];
+extern const char kErrorConsole[];
+extern const char kExtensionActionRedesign[];
+extern const char kExtensionProcess[];
+extern const char kExtensionsOnChromeURLs[];
+extern const char kForceDevModeHighlighting[];
+extern const char kIsolateExtensions[];
+extern const char kLoadApps[];
+extern const char kScriptsRequireAction[];
+extern const char kEnableScriptsRequireAction[];
+#if defined(CHROMIUM_BUILD)
+extern const char kPromptForExternalExtensions[];
+#endif
+extern const char kShowComponentExtensionOptions[];
+extern const char kTraceAppSource[];
+extern const char kWhitelistedExtensionID[];
+extern const char kEnableCrxHashCheck[];
+
+} // namespace switches
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_SWITCHES_H_
diff --git a/chromium/extensions/common/test_util.cc b/chromium/extensions/common/test_util.cc
new file mode 100644
index 00000000000..c065c291ec8
--- /dev/null
+++ b/chromium/extensions/common/test_util.cc
@@ -0,0 +1,59 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/test_util.h"
+
+#include <utility>
+
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/value_builder.h"
+
+namespace extensions {
+namespace test_util {
+
+ExtensionBuilder BuildExtension(ExtensionBuilder builder) {
+ builder.SetManifest(DictionaryBuilder()
+ .Set("name", "Test extension")
+ .Set("version", "1.0")
+ .Set("manifest_version", 2)
+ .Build());
+ return builder;
+}
+
+ExtensionBuilder BuildApp(ExtensionBuilder builder) {
+ builder.SetManifest(
+ DictionaryBuilder()
+ .Set("name", "Test extension")
+ .Set("version", "1.0")
+ .Set("manifest_version", 2)
+ .Set("app", extensions::DictionaryBuilder()
+ .Set("background",
+ extensions::DictionaryBuilder()
+ .Set("scripts", extensions::ListBuilder()
+ .Append("background.js")
+ .Build())
+ .Build())
+ .Build())
+ .Build());
+ return builder;
+}
+
+scoped_refptr<Extension> CreateEmptyExtension() {
+ return ExtensionBuilder()
+ .SetManifest(
+ DictionaryBuilder().Set("name", "Test").Set("version", "1.0").Build())
+ .Build();
+}
+
+scoped_refptr<Extension> CreateEmptyExtension(const std::string& id) {
+ return ExtensionBuilder()
+ .SetManifest(
+ DictionaryBuilder().Set("name", "test").Set("version", "0.1").Build())
+ .SetID(id)
+ .Build();
+}
+
+} // namespace test_util
+} // namespace extensions
diff --git a/chromium/extensions/common/test_util.h b/chromium/extensions/common/test_util.h
new file mode 100644
index 00000000000..72203994419
--- /dev/null
+++ b/chromium/extensions/common/test_util.h
@@ -0,0 +1,35 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_TEST_UTIL_H_
+#define EXTENSIONS_COMMON_TEST_UTIL_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+
+namespace extensions {
+class Extension;
+class ExtensionBuilder;
+
+namespace test_util {
+
+// Adds an extension manifest to a builder.
+ExtensionBuilder BuildExtension(ExtensionBuilder builder);
+
+// Adds an app manifest to a builder.
+ExtensionBuilder BuildApp(ExtensionBuilder builder);
+
+// Creates an extension instance that can be attached to an ExtensionFunction
+// before running it.
+scoped_refptr<Extension> CreateEmptyExtension();
+
+// Create an extension with a variable |id|, for tests that require multiple
+// extensions side-by-side having distinct IDs.
+scoped_refptr<Extension> CreateEmptyExtension(const std::string& id);
+
+} // namespace test_util
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_TEST_UTIL_H_
diff --git a/chromium/extensions/common/update_manifest.cc b/chromium/extensions/common/update_manifest.cc
new file mode 100644
index 00000000000..c8ebdf7961b
--- /dev/null
+++ b/chromium/extensions/common/update_manifest.cc
@@ -0,0 +1,287 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/update_manifest.h"
+
+#include <algorithm>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/version.h"
+#include "libxml/tree.h"
+#include "third_party/libxml/chromium/libxml_utils.h"
+
+static const char* kExpectedGupdateProtocol = "2.0";
+static const char* kExpectedGupdateXmlns =
+ "http://www.google.com/update2/response";
+
+UpdateManifest::Result::Result()
+ : size(0),
+ diff_size(0) {}
+
+UpdateManifest::Result::Result(const Result& other) = default;
+
+UpdateManifest::Result::~Result() {}
+
+UpdateManifest::Results::Results() : daystart_elapsed_seconds(kNoDaystart) {}
+
+UpdateManifest::Results::~Results() {}
+
+UpdateManifest::UpdateManifest() {
+}
+
+UpdateManifest::~UpdateManifest() {}
+
+void UpdateManifest::ParseError(const char* details, ...) {
+ va_list args;
+ va_start(args, details);
+
+ if (errors_.length() > 0) {
+ // TODO(asargent) make a platform abstracted newline?
+ errors_ += "\r\n";
+ }
+ base::StringAppendV(&errors_, details, args);
+ va_end(args);
+}
+
+// Checks whether a given node's name matches |expected_name| and
+// |expected_namespace|.
+static bool TagNameEquals(const xmlNode* node, const char* expected_name,
+ const xmlNs* expected_namespace) {
+ if (node->ns != expected_namespace) {
+ return false;
+ }
+ return 0 == strcmp(expected_name, reinterpret_cast<const char*>(node->name));
+}
+
+// Returns child nodes of |root| with name |name| in namespace |xml_namespace|.
+static std::vector<xmlNode*> GetChildren(xmlNode* root, xmlNs* xml_namespace,
+ const char* name) {
+ std::vector<xmlNode*> result;
+ for (xmlNode* child = root->children; child != NULL; child = child->next) {
+ if (!TagNameEquals(child, name, xml_namespace)) {
+ continue;
+ }
+ result.push_back(child);
+ }
+ return result;
+}
+
+// Returns the value of a named attribute, or the empty string.
+static std::string GetAttribute(xmlNode* node, const char* attribute_name) {
+ const xmlChar* name = reinterpret_cast<const xmlChar*>(attribute_name);
+ for (xmlAttr* attr = node->properties; attr != NULL; attr = attr->next) {
+ if (!xmlStrcmp(attr->name, name) && attr->children &&
+ attr->children->content) {
+ return std::string(reinterpret_cast<const char*>(
+ attr->children->content));
+ }
+ }
+ return std::string();
+}
+
+// This is used for the xml parser to report errors. This assumes the context
+// is a pointer to a std::string where the error message should be appended.
+static void XmlErrorFunc(void *context, const char *message, ...) {
+ va_list args;
+ va_start(args, message);
+ std::string* error = static_cast<std::string*>(context);
+ base::StringAppendV(error, message, args);
+ va_end(args);
+}
+
+// Utility class for cleaning up the xml document when leaving a scope.
+class ScopedXmlDocument {
+ public:
+ explicit ScopedXmlDocument(xmlDocPtr document) : document_(document) {}
+ ~ScopedXmlDocument() {
+ if (document_)
+ xmlFreeDoc(document_);
+ }
+
+ xmlDocPtr get() {
+ return document_;
+ }
+
+ private:
+ xmlDocPtr document_;
+};
+
+// Returns a pointer to the xmlNs on |node| with the |expected_href|, or
+// NULL if there isn't one with that href.
+static xmlNs* GetNamespace(xmlNode* node, const char* expected_href) {
+ const xmlChar* href = reinterpret_cast<const xmlChar*>(expected_href);
+ for (xmlNs* ns = node->ns; ns != NULL; ns = ns->next) {
+ if (ns->href && !xmlStrcmp(ns->href, href)) {
+ return ns;
+ }
+ }
+ return NULL;
+}
+
+
+// Helper function that reads in values for a single <app> tag. It returns a
+// boolean indicating success or failure. On failure, it writes a error message
+// into |error_detail|.
+static bool ParseSingleAppTag(xmlNode* app_node, xmlNs* xml_namespace,
+ UpdateManifest::Result* result,
+ std::string *error_detail) {
+ // Read the extension id.
+ result->extension_id = GetAttribute(app_node, "appid");
+ if (result->extension_id.length() == 0) {
+ *error_detail = "Missing appid on app node";
+ return false;
+ }
+
+ // Get the updatecheck node.
+ std::vector<xmlNode*> updates = GetChildren(app_node, xml_namespace,
+ "updatecheck");
+ if (updates.size() > 1) {
+ *error_detail = "Too many updatecheck tags on app (expecting only 1).";
+ return false;
+ }
+ if (updates.empty()) {
+ *error_detail = "Missing updatecheck on app.";
+ return false;
+ }
+ xmlNode *updatecheck = updates[0];
+
+ if (GetAttribute(updatecheck, "status") == "noupdate") {
+ return true;
+ }
+
+ // Find the url to the crx file.
+ result->crx_url = GURL(GetAttribute(updatecheck, "codebase"));
+ if (!result->crx_url.is_valid()) {
+ *error_detail = "Invalid codebase url: '";
+ *error_detail += result->crx_url.possibly_invalid_spec();
+ *error_detail += "'.";
+ return false;
+ }
+
+ // Get the version.
+ result->version = GetAttribute(updatecheck, "version");
+ if (result->version.length() == 0) {
+ *error_detail = "Missing version for updatecheck.";
+ return false;
+ }
+ Version version(result->version);
+ if (!version.IsValid()) {
+ *error_detail = "Invalid version: '";
+ *error_detail += result->version;
+ *error_detail += "'.";
+ return false;
+ }
+
+ // Get the minimum browser version (not required).
+ result->browser_min_version = GetAttribute(updatecheck, "prodversionmin");
+ if (result->browser_min_version.length()) {
+ Version browser_min_version(result->browser_min_version);
+ if (!browser_min_version.IsValid()) {
+ *error_detail = "Invalid prodversionmin: '";
+ *error_detail += result->browser_min_version;
+ *error_detail += "'.";
+ return false;
+ }
+ }
+
+ // package_hash is optional. It is a sha256 hash of the package in hex
+ // format.
+ result->package_hash = GetAttribute(updatecheck, "hash_sha256");
+
+ int size = 0;
+ if (base::StringToInt(GetAttribute(updatecheck, "size"), &size)) {
+ result->size = size;
+ }
+
+ // package_fingerprint is optional. It identifies the package, preferably
+ // with a modified sha256 hash of the package in hex format.
+ result->package_fingerprint = GetAttribute(updatecheck, "fp");
+
+ // Differential update information is optional.
+ result->diff_crx_url = GURL(GetAttribute(updatecheck, "codebasediff"));
+ result->diff_package_hash = GetAttribute(updatecheck, "hashdiff");
+ int sizediff = 0;
+ if (base::StringToInt(GetAttribute(updatecheck, "sizediff"), &sizediff)) {
+ result->diff_size = sizediff;
+ }
+
+ return true;
+}
+
+
+bool UpdateManifest::Parse(const std::string& manifest_xml) {
+ results_.list.resize(0);
+ results_.daystart_elapsed_seconds = kNoDaystart;
+ errors_ = "";
+
+ if (manifest_xml.length() < 1) {
+ ParseError("Empty xml");
+ return false;
+ }
+
+ std::string xml_errors;
+ ScopedXmlErrorFunc error_func(&xml_errors, &XmlErrorFunc);
+
+ // Start up the xml parser with the manifest_xml contents.
+ ScopedXmlDocument document(xmlParseDoc(
+ reinterpret_cast<const xmlChar*>(manifest_xml.c_str())));
+ if (!document.get()) {
+ ParseError("%s", xml_errors.c_str());
+ return false;
+ }
+
+ xmlNode *root = xmlDocGetRootElement(document.get());
+ if (!root) {
+ ParseError("Missing root node");
+ return false;
+ }
+
+ // Look for the required namespace declaration.
+ xmlNs* gupdate_ns = GetNamespace(root, kExpectedGupdateXmlns);
+ if (!gupdate_ns) {
+ ParseError("Missing or incorrect xmlns on gupdate tag");
+ return false;
+ }
+
+ if (!TagNameEquals(root, "gupdate", gupdate_ns)) {
+ ParseError("Missing gupdate tag");
+ return false;
+ }
+
+ // Check for the gupdate "protocol" attribute.
+ if (GetAttribute(root, "protocol") != kExpectedGupdateProtocol) {
+ ParseError("Missing/incorrect protocol on gupdate tag "
+ "(expected '%s')", kExpectedGupdateProtocol);
+ return false;
+ }
+
+ // Parse the first <daystart> if it's present.
+ std::vector<xmlNode*> daystarts = GetChildren(root, gupdate_ns, "daystart");
+ if (!daystarts.empty()) {
+ xmlNode* first = daystarts[0];
+ std::string elapsed_seconds = GetAttribute(first, "elapsed_seconds");
+ int parsed_elapsed = kNoDaystart;
+ if (base::StringToInt(elapsed_seconds, &parsed_elapsed)) {
+ results_.daystart_elapsed_seconds = parsed_elapsed;
+ }
+ }
+
+ // Parse each of the <app> tags.
+ std::vector<xmlNode*> apps = GetChildren(root, gupdate_ns, "app");
+ for (unsigned int i = 0; i < apps.size(); i++) {
+ Result current;
+ std::string error;
+ if (!ParseSingleAppTag(apps[i], gupdate_ns, &current, &error)) {
+ ParseError("%s", error.c_str());
+ } else {
+ results_.list.push_back(current);
+ }
+ }
+
+ return true;
+}
diff --git a/chromium/extensions/common/update_manifest.h b/chromium/extensions/common/update_manifest.h
new file mode 100644
index 00000000000..6761a399e8d
--- /dev/null
+++ b/chromium/extensions/common/update_manifest.h
@@ -0,0 +1,96 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_UPDATE_MANIFEST_H_
+#define EXTENSIONS_COMMON_UPDATE_MANIFEST_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "url/gurl.h"
+
+class UpdateManifest {
+ public:
+ // An update manifest looks like this:
+ //
+ // <?xml version="1.0" encoding="UTF-8"?>
+ // <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
+ // <daystart elapsed_seconds="300" />
+ // <app appid="12345" status="ok">
+ // <updatecheck codebase="http://example.com/extension_1.2.3.4.crx"
+ // hash="12345" size="9854" status="ok" version="1.2.3.4"
+ // prodversionmin="2.0.143.0"
+ // codebasediff="http://example.com/diff_1.2.3.4.crx"
+ // hashdiff="123" sizediff="101"
+ // fp="1.123" />
+ // </app>
+ // </gupdate>
+ //
+ // The <daystart> tag contains a "elapsed_seconds" attribute which refers to
+ // the server's notion of how many seconds it has been since midnight.
+ //
+ // The "appid" attribute of the <app> tag refers to the unique id of the
+ // extension. The "codebase" attribute of the <updatecheck> tag is the url to
+ // fetch the updated crx file, and the "prodversionmin" attribute refers to
+ // the minimum version of the chrome browser that the update applies to.
+
+ // The diff data members correspond to the differential update package, if
+ // a differential update is specified in the response.
+
+ // The result of parsing one <app> tag in an xml update check manifest.
+ struct Result {
+ Result();
+ Result(const Result& other);
+ ~Result();
+
+ std::string extension_id;
+ std::string version;
+ std::string browser_min_version;
+
+ // Attributes for the full update.
+ GURL crx_url;
+ std::string package_hash;
+ int size;
+ std::string package_fingerprint;
+
+ // Attributes for the differential update.
+ GURL diff_crx_url;
+ std::string diff_package_hash;
+ int diff_size;
+ };
+
+ static const int kNoDaystart = -1;
+ struct Results {
+ Results();
+ ~Results();
+
+ std::vector<Result> list;
+ // This will be >= 0, or kNoDaystart if the <daystart> tag was not present.
+ int daystart_elapsed_seconds;
+ };
+
+ UpdateManifest();
+ ~UpdateManifest();
+
+ // Parses an update manifest xml string into Result data. Returns a bool
+ // indicating success or failure. On success, the results are available by
+ // calling results(). The details for any failures are available by calling
+ // errors().
+ bool Parse(const std::string& manifest_xml);
+
+ const Results& results() { return results_; }
+ const std::string& errors() { return errors_; }
+
+ private:
+ Results results_;
+ std::string errors_;
+
+ // Helper function that adds parse error details to our errors_ string.
+ void ParseError(const char* details, ...);
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateManifest);
+};
+
+#endif // EXTENSIONS_COMMON_UPDATE_MANIFEST_H_
diff --git a/chromium/extensions/common/update_manifest_unittest.cc b/chromium/extensions/common/update_manifest_unittest.cc
new file mode 100644
index 00000000000..02beb677450
--- /dev/null
+++ b/chromium/extensions/common/update_manifest_unittest.cc
@@ -0,0 +1,185 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/update_manifest.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+static const char kValidXml[] =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345'>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' prodversionmin='2.0.143.0' />"
+" </app>"
+"</gupdate>";
+
+static const char valid_xml_with_hash[] =
+ "<?xml version='1.0' encoding='UTF-8'?>"
+ "<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+ " <app appid='12345'>"
+ " <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+ " version='1.2.3.4' prodversionmin='2.0.143.0' "
+ " hash_sha256='1234'/>"
+ " </app>"
+ "</gupdate>";
+
+static const char kMissingAppId[] =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' />"
+" </app>"
+"</gupdate>";
+
+static const char kInvalidCodebase[] =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345' status='ok'>"
+" <updatecheck codebase='example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' />"
+" </app>"
+"</gupdate>";
+
+static const char kMissingVersion[] =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345' status='ok'>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' />"
+" </app>"
+"</gupdate>";
+
+static const char kInvalidVersion[] =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345' status='ok'>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' "
+" version='1.2.3.a'/>"
+" </app>"
+"</gupdate>";
+
+static const char kUsesNamespacePrefix[] =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<g:gupdate xmlns:g='http://www.google.com/update2/response' protocol='2.0'>"
+" <g:app appid='12345'>"
+" <g:updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' prodversionmin='2.0.143.0' />"
+" </g:app>"
+"</g:gupdate>";
+
+// Includes unrelated <app> tags from other xml namespaces - this should
+// not cause problems.
+static const char kSimilarTagnames[] =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<gupdate xmlns='http://www.google.com/update2/response'"
+" xmlns:a='http://a' protocol='2.0'>"
+" <a:app/>"
+" <b:app xmlns:b='http://b' />"
+" <app appid='12345'>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' prodversionmin='2.0.143.0' />"
+" </app>"
+"</gupdate>";
+
+// Includes a <daystart> tag.
+static const char kWithDaystart[] =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <daystart elapsed_seconds='456' />"
+" <app appid='12345'>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' prodversionmin='2.0.143.0' />"
+" </app>"
+"</gupdate>";
+
+// Indicates no updates available - this should not be a parse error.
+static const char kNoUpdate[] =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345'>"
+" <updatecheck status='noupdate' />"
+" </app>"
+"</gupdate>";
+
+// Includes two <app> tags, one with an error.
+static const char kTwoAppsOneError[] =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='aaaaaaaa' status='error-unknownApplication'>"
+" <updatecheck status='error-unknownapplication'/>"
+" </app>"
+" <app appid='bbbbbbbb'>"
+" <updatecheck codebase='http://example.com/b_3.1.crx' version='3.1'/>"
+" </app>"
+"</gupdate>";
+
+TEST(ExtensionUpdateManifestTest, TestUpdateManifest) {
+ UpdateManifest parser;
+
+ // Test parsing of a number of invalid xml cases
+ EXPECT_FALSE(parser.Parse(std::string()));
+ EXPECT_FALSE(parser.errors().empty());
+
+ EXPECT_TRUE(parser.Parse(kMissingAppId));
+ EXPECT_TRUE(parser.results().list.empty());
+ EXPECT_FALSE(parser.errors().empty());
+
+ EXPECT_TRUE(parser.Parse(kInvalidCodebase));
+ EXPECT_TRUE(parser.results().list.empty());
+ EXPECT_FALSE(parser.errors().empty());
+
+ EXPECT_TRUE(parser.Parse(kMissingVersion));
+ EXPECT_TRUE(parser.results().list.empty());
+ EXPECT_FALSE(parser.errors().empty());
+
+ EXPECT_TRUE(parser.Parse(kInvalidVersion));
+ EXPECT_TRUE(parser.results().list.empty());
+ EXPECT_FALSE(parser.errors().empty());
+
+ // Parse some valid XML, and check that all params came out as expected
+ EXPECT_TRUE(parser.Parse(kValidXml));
+ EXPECT_TRUE(parser.errors().empty());
+ EXPECT_FALSE(parser.results().list.empty());
+ const UpdateManifest::Result* firstResult = &parser.results().list.at(0);
+ EXPECT_EQ(GURL("http://example.com/extension_1.2.3.4.crx"),
+ firstResult->crx_url);
+
+ EXPECT_EQ("1.2.3.4", firstResult->version);
+ EXPECT_EQ("2.0.143.0", firstResult->browser_min_version);
+
+ // Parse some xml that uses namespace prefixes.
+ EXPECT_TRUE(parser.Parse(kUsesNamespacePrefix));
+ EXPECT_TRUE(parser.errors().empty());
+ EXPECT_TRUE(parser.Parse(kSimilarTagnames));
+ EXPECT_TRUE(parser.errors().empty());
+
+ // Parse xml with hash value
+ EXPECT_TRUE(parser.Parse(valid_xml_with_hash));
+ EXPECT_TRUE(parser.errors().empty());
+ EXPECT_FALSE(parser.results().list.empty());
+ firstResult = &parser.results().list.at(0);
+ EXPECT_EQ("1234", firstResult->package_hash);
+
+ // Parse xml with a <daystart> element.
+ EXPECT_TRUE(parser.Parse(kWithDaystart));
+ EXPECT_TRUE(parser.errors().empty());
+ EXPECT_FALSE(parser.results().list.empty());
+ EXPECT_EQ(parser.results().daystart_elapsed_seconds, 456);
+
+ // Parse a no-update response.
+ EXPECT_TRUE(parser.Parse(kNoUpdate));
+ EXPECT_TRUE(parser.errors().empty());
+ EXPECT_FALSE(parser.results().list.empty());
+ firstResult = &parser.results().list.at(0);
+ EXPECT_EQ(firstResult->extension_id, "12345");
+ EXPECT_EQ(firstResult->version, "");
+
+ // Parse xml with one error and one success <app> tag.
+ EXPECT_TRUE(parser.Parse(kTwoAppsOneError));
+ EXPECT_FALSE(parser.errors().empty());
+ EXPECT_EQ(1u, parser.results().list.size());
+ firstResult = &parser.results().list.at(0);
+ EXPECT_EQ(firstResult->extension_id, "bbbbbbbb");
+}
diff --git a/chromium/extensions/common/url_pattern_set.cc b/chromium/extensions/common/url_pattern_set.cc
new file mode 100644
index 00000000000..5f09564f9f4
--- /dev/null
+++ b/chromium/extensions/common/url_pattern_set.cc
@@ -0,0 +1,289 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/url_pattern_set.h"
+
+#include <iterator>
+#include <ostream>
+
+#include "base/logging.h"
+#include "base/memory/linked_ptr.h"
+#include "base/stl_util.h"
+#include "base/values.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/url_pattern.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+#include "url/url_constants.h"
+
+namespace extensions {
+
+namespace {
+
+const char kInvalidURLPatternError[] = "Invalid url pattern '*'";
+
+} // namespace
+
+// static
+URLPatternSet URLPatternSet::CreateDifference(const URLPatternSet& set1,
+ const URLPatternSet& set2) {
+ return URLPatternSet(base::STLSetDifference<std::set<URLPattern>>(
+ set1.patterns_, set2.patterns_));
+}
+
+// static
+URLPatternSet URLPatternSet::CreateIntersection(const URLPatternSet& set1,
+ const URLPatternSet& set2) {
+ return URLPatternSet(base::STLSetIntersection<std::set<URLPattern>>(
+ set1.patterns_, set2.patterns_));
+}
+
+URLPatternSet URLPatternSet::CreateSemanticIntersection(
+ const URLPatternSet& set1,
+ const URLPatternSet& set2) {
+ URLPatternSet result;
+ for (const URLPattern& pattern : set1) {
+ if (set2.ContainsPattern(pattern))
+ result.patterns_.insert(pattern);
+ }
+ for (const URLPattern& pattern : set2) {
+ if (set1.ContainsPattern(pattern))
+ result.patterns_.insert(pattern);
+ }
+ return result;
+}
+
+// static
+URLPatternSet URLPatternSet::CreateUnion(const URLPatternSet& set1,
+ const URLPatternSet& set2) {
+ return URLPatternSet(
+ base::STLSetUnion<std::set<URLPattern>>(set1.patterns_, set2.patterns_));
+}
+
+// static
+URLPatternSet URLPatternSet::CreateUnion(
+ const std::vector<URLPatternSet>& sets) {
+ URLPatternSet result;
+ if (sets.empty())
+ return result;
+
+ // N-way union algorithm is basic O(nlog(n)) merge algorithm.
+ //
+ // Do the first merge step into a working set so that we don't mutate any of
+ // the input.
+ std::vector<URLPatternSet> working;
+ for (size_t i = 0; i < sets.size(); i += 2) {
+ if (i + 1 < sets.size())
+ working.push_back(CreateUnion(sets[i], sets[i + 1]));
+ else
+ working.push_back(sets[i]);
+ }
+
+ for (size_t skip = 1; skip < working.size(); skip *= 2) {
+ for (size_t i = 0; i < (working.size() - skip); i += skip) {
+ URLPatternSet u = CreateUnion(working[i], working[i + skip]);
+ working[i].patterns_.swap(u.patterns_);
+ }
+ }
+
+ result.patterns_.swap(working[0].patterns_);
+ return result;
+}
+
+URLPatternSet::URLPatternSet() {}
+
+URLPatternSet::URLPatternSet(const URLPatternSet& rhs)
+ : patterns_(rhs.patterns_) {}
+
+URLPatternSet::URLPatternSet(const std::set<URLPattern>& patterns)
+ : patterns_(patterns) {}
+
+URLPatternSet::~URLPatternSet() {}
+
+URLPatternSet& URLPatternSet::operator=(const URLPatternSet& rhs) {
+ patterns_ = rhs.patterns_;
+ return *this;
+}
+
+bool URLPatternSet::operator==(const URLPatternSet& other) const {
+ return patterns_ == other.patterns_;
+}
+
+std::ostream& operator<<(std::ostream& out,
+ const URLPatternSet& url_pattern_set) {
+ out << "{ ";
+
+ std::set<URLPattern>::const_iterator iter =
+ url_pattern_set.patterns().begin();
+ if (!url_pattern_set.patterns().empty()) {
+ out << *iter;
+ ++iter;
+ }
+
+ for (;iter != url_pattern_set.patterns().end(); ++iter)
+ out << ", " << *iter;
+
+ if (!url_pattern_set.patterns().empty())
+ out << " ";
+
+ out << "}";
+ return out;
+}
+
+bool URLPatternSet::is_empty() const {
+ return patterns_.empty();
+}
+
+size_t URLPatternSet::size() const {
+ return patterns_.size();
+}
+
+bool URLPatternSet::AddPattern(const URLPattern& pattern) {
+ return patterns_.insert(pattern).second;
+}
+
+void URLPatternSet::AddPatterns(const URLPatternSet& set) {
+ patterns_.insert(set.patterns().begin(),
+ set.patterns().end());
+}
+
+void URLPatternSet::ClearPatterns() {
+ patterns_.clear();
+}
+
+bool URLPatternSet::AddOrigin(int valid_schemes, const GURL& origin) {
+ if (origin.is_empty())
+ return false;
+ const url::Origin real_origin(origin);
+ DCHECK(real_origin.IsSameOriginWith(url::Origin(origin.GetOrigin())));
+ URLPattern origin_pattern(valid_schemes);
+ // Origin adding could fail if |origin| does not match |valid_schemes|.
+ if (origin_pattern.Parse(origin.spec()) != URLPattern::PARSE_SUCCESS) {
+ return false;
+ }
+ origin_pattern.SetPath("/*");
+ return AddPattern(origin_pattern);
+}
+
+bool URLPatternSet::Contains(const URLPatternSet& other) const {
+ for (URLPatternSet::const_iterator it = other.begin();
+ it != other.end(); ++it) {
+ if (!ContainsPattern(*it))
+ return false;
+ }
+
+ return true;
+}
+
+bool URLPatternSet::ContainsPattern(const URLPattern& pattern) const {
+ for (URLPatternSet::const_iterator it = begin();
+ it != end(); ++it) {
+ if (it->Contains(pattern))
+ return true;
+ }
+ return false;
+}
+
+bool URLPatternSet::MatchesURL(const GURL& url) const {
+ for (URLPatternSet::const_iterator pattern = patterns_.begin();
+ pattern != patterns_.end(); ++pattern) {
+ if (pattern->MatchesURL(url))
+ return true;
+ }
+
+ return false;
+}
+
+bool URLPatternSet::MatchesAllURLs() const {
+ for (URLPatternSet::const_iterator host = begin(); host != end(); ++host) {
+ if (host->match_all_urls() ||
+ (host->match_subdomains() && host->host().empty()))
+ return true;
+ }
+ return false;
+}
+
+bool URLPatternSet::MatchesSecurityOrigin(const GURL& origin) const {
+ for (URLPatternSet::const_iterator pattern = patterns_.begin();
+ pattern != patterns_.end(); ++pattern) {
+ if (pattern->MatchesSecurityOrigin(origin))
+ return true;
+ }
+
+ return false;
+}
+
+bool URLPatternSet::OverlapsWith(const URLPatternSet& other) const {
+ // Two extension extents overlap if there is any one URL that would match at
+ // least one pattern in each of the extents.
+ for (URLPatternSet::const_iterator i = patterns_.begin();
+ i != patterns_.end(); ++i) {
+ for (URLPatternSet::const_iterator j = other.patterns().begin();
+ j != other.patterns().end(); ++j) {
+ if (i->OverlapsWith(*j))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+scoped_ptr<base::ListValue> URLPatternSet::ToValue() const {
+ scoped_ptr<base::ListValue> value(new base::ListValue);
+ for (URLPatternSet::const_iterator i = patterns_.begin();
+ i != patterns_.end(); ++i)
+ value->AppendIfNotPresent(new base::StringValue(i->GetAsString()));
+ return value;
+}
+
+bool URLPatternSet::Populate(const std::vector<std::string>& patterns,
+ int valid_schemes,
+ bool allow_file_access,
+ std::string* error) {
+ ClearPatterns();
+ for (size_t i = 0; i < patterns.size(); ++i) {
+ URLPattern pattern(valid_schemes);
+ if (pattern.Parse(patterns[i]) != URLPattern::PARSE_SUCCESS) {
+ if (error) {
+ *error = ErrorUtils::FormatErrorMessage(kInvalidURLPatternError,
+ patterns[i]);
+ } else {
+ LOG(ERROR) << "Invalid url pattern: " << patterns[i];
+ }
+ return false;
+ }
+ if (!allow_file_access && pattern.MatchesScheme(url::kFileScheme)) {
+ pattern.SetValidSchemes(
+ pattern.valid_schemes() & ~URLPattern::SCHEME_FILE);
+ }
+ AddPattern(pattern);
+ }
+ return true;
+}
+
+scoped_ptr<std::vector<std::string> > URLPatternSet::ToStringVector() const {
+ scoped_ptr<std::vector<std::string> > value(new std::vector<std::string>);
+ for (URLPatternSet::const_iterator i = patterns_.begin();
+ i != patterns_.end();
+ ++i) {
+ value->push_back(i->GetAsString());
+ }
+ return value;
+}
+
+bool URLPatternSet::Populate(const base::ListValue& value,
+ int valid_schemes,
+ bool allow_file_access,
+ std::string* error) {
+ std::vector<std::string> patterns;
+ for (size_t i = 0; i < value.GetSize(); ++i) {
+ std::string item;
+ if (!value.GetString(i, &item))
+ return false;
+ patterns.push_back(item);
+ }
+ return Populate(patterns, valid_schemes, allow_file_access, error);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/url_pattern_set.h b/chromium/extensions/common/url_pattern_set.h
new file mode 100644
index 00000000000..717779ced5d
--- /dev/null
+++ b/chromium/extensions/common/url_pattern_set.h
@@ -0,0 +1,125 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_URL_PATTERN_SET_H_
+#define EXTENSIONS_COMMON_URL_PATTERN_SET_H_
+
+#include <stddef.h>
+
+#include <iosfwd>
+#include <set>
+
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/url_pattern.h"
+
+class GURL;
+
+namespace base {
+class ListValue;
+class Value;
+}
+
+namespace extensions {
+
+// Represents the set of URLs an extension uses for web content.
+class URLPatternSet {
+ public:
+ typedef std::set<URLPattern>::const_iterator const_iterator;
+ typedef std::set<URLPattern>::iterator iterator;
+
+ // Returns |set1| - |set2|.
+ static URLPatternSet CreateDifference(const URLPatternSet& set1,
+ const URLPatternSet& set2);
+
+ // Returns the intersection of |set1| and |set2|.
+ static URLPatternSet CreateIntersection(const URLPatternSet& set1,
+ const URLPatternSet& set2);
+
+ // Creates an intersection result where result has every element that is
+ // contained by both |set1| and |set2|. This is different than
+ // CreateIntersection(), which does string comparisons. For example, the
+ // semantic intersection of ("http://*.google.com/*") and
+ // ("http://google.com/*") is ("http://google.com/*"), but the result from
+ // CreateIntersection() would be ().
+ // TODO(devlin): This is weird. We probably want to use mostly
+ // CreateSemanticIntersection().
+ static URLPatternSet CreateSemanticIntersection(const URLPatternSet& set1,
+ const URLPatternSet& set2);
+
+ // Returns the union of |set1| and |set2|.
+ static URLPatternSet CreateUnion(const URLPatternSet& set1,
+ const URLPatternSet& set2);
+
+ // Returns the union of all sets in |sets|.
+ static URLPatternSet CreateUnion(const std::vector<URLPatternSet>& sets);
+
+ URLPatternSet();
+ URLPatternSet(const URLPatternSet& rhs);
+ explicit URLPatternSet(const std::set<URLPattern>& patterns);
+ ~URLPatternSet();
+
+ URLPatternSet& operator=(const URLPatternSet& rhs);
+ bool operator==(const URLPatternSet& rhs) const;
+
+ bool is_empty() const;
+ size_t size() const;
+ const std::set<URLPattern>& patterns() const { return patterns_; }
+ const_iterator begin() const { return patterns_.begin(); }
+ const_iterator end() const { return patterns_.end(); }
+
+ // Adds a pattern to the set. Returns true if a new pattern was inserted,
+ // false if the pattern was already in the set.
+ bool AddPattern(const URLPattern& pattern);
+
+ // Adds all patterns from |set| into this.
+ void AddPatterns(const URLPatternSet& set);
+
+ void ClearPatterns();
+
+ // Adds a pattern based on |origin| to the set.
+ bool AddOrigin(int valid_schemes, const GURL& origin);
+
+ // Returns true if every URL that matches |set| is matched by this. In other
+ // words, if every pattern in |set| is encompassed by a pattern in this.
+ bool Contains(const URLPatternSet& set) const;
+
+ // Returns true if any pattern in this set encompasses |pattern|.
+ bool ContainsPattern(const URLPattern& pattern) const;
+
+ // Test if the extent contains a URL.
+ bool MatchesURL(const GURL& url) const;
+
+ // Test if the extent matches all URLs (for example, <all_urls>).
+ bool MatchesAllURLs() const;
+
+ bool MatchesSecurityOrigin(const GURL& origin) const;
+
+ // Returns true if there is a single URL that would be in two extents.
+ bool OverlapsWith(const URLPatternSet& other) const;
+
+ // Converts to and from Value for serialization to preferences.
+ scoped_ptr<base::ListValue> ToValue() const;
+ bool Populate(const base::ListValue& value,
+ int valid_schemes,
+ bool allow_file_access,
+ std::string* error);
+
+ // Converts to and from a vector of strings.
+ scoped_ptr<std::vector<std::string> > ToStringVector() const;
+ bool Populate(const std::vector<std::string>& patterns,
+ int valid_schemes,
+ bool allow_file_access,
+ std::string* error);
+
+ private:
+ // The list of URL patterns that comprise the extent.
+ std::set<URLPattern> patterns_;
+};
+
+std::ostream& operator<<(std::ostream& out,
+ const URLPatternSet& url_pattern_set);
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_URL_PATTERN_SET_H_
diff --git a/chromium/extensions/common/url_pattern_set_unittest.cc b/chromium/extensions/common/url_pattern_set_unittest.cc
new file mode 100644
index 00000000000..4d3af98bf68
--- /dev/null
+++ b/chromium/extensions/common/url_pattern_set_unittest.cc
@@ -0,0 +1,479 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/url_pattern_set.h"
+
+#include <stddef.h>
+
+#include <sstream>
+
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+namespace {
+
+void AddPattern(URLPatternSet* set, const std::string& pattern) {
+ int schemes = URLPattern::SCHEME_ALL;
+ set->AddPattern(URLPattern(schemes, pattern));
+}
+
+URLPatternSet Patterns(const std::string& pattern) {
+ URLPatternSet set;
+ AddPattern(&set, pattern);
+ return set;
+}
+
+URLPatternSet Patterns(const std::string& pattern1,
+ const std::string& pattern2) {
+ URLPatternSet set;
+ AddPattern(&set, pattern1);
+ AddPattern(&set, pattern2);
+ return set;
+}
+
+} // namespace
+
+TEST(URLPatternSetTest, Empty) {
+ URLPatternSet set;
+ EXPECT_FALSE(set.MatchesURL(GURL("http://www.foo.com/bar")));
+ EXPECT_FALSE(set.MatchesURL(GURL()));
+ EXPECT_FALSE(set.MatchesURL(GURL("invalid")));
+}
+
+TEST(URLPatternSetTest, One) {
+ URLPatternSet set;
+ AddPattern(&set, "http://www.google.com/*");
+
+ EXPECT_TRUE(set.MatchesURL(GURL("http://www.google.com/")));
+ EXPECT_TRUE(set.MatchesURL(GURL("http://www.google.com/monkey")));
+ EXPECT_FALSE(set.MatchesURL(GURL("https://www.google.com/")));
+ EXPECT_FALSE(set.MatchesURL(GURL("https://www.microsoft.com/")));
+}
+
+TEST(URLPatternSetTest, Two) {
+ URLPatternSet set;
+ AddPattern(&set, "http://www.google.com/*");
+ AddPattern(&set, "http://www.yahoo.com/*");
+
+ EXPECT_TRUE(set.MatchesURL(GURL("http://www.google.com/monkey")));
+ EXPECT_TRUE(set.MatchesURL(GURL("http://www.yahoo.com/monkey")));
+ EXPECT_FALSE(set.MatchesURL(GURL("https://www.apple.com/monkey")));
+}
+
+TEST(URLPatternSetTest, StreamOperatorEmpty) {
+ URLPatternSet set;
+
+ std::ostringstream stream;
+ stream << set;
+ EXPECT_EQ("{ }", stream.str());
+}
+
+TEST(URLPatternSetTest, StreamOperatorOne) {
+ URLPatternSet set;
+ AddPattern(&set, "http://www.google.com/*");
+
+ std::ostringstream stream;
+ stream << set;
+ EXPECT_EQ("{ \"http://www.google.com/*\" }", stream.str());
+}
+
+TEST(URLPatternSetTest, StreamOperatorTwo) {
+ URLPatternSet set;
+ AddPattern(&set, "http://www.google.com/*");
+ AddPattern(&set, "http://www.yahoo.com/*");
+
+ std::ostringstream stream;
+ stream << set;
+ EXPECT_EQ("{ \"http://www.google.com/*\", \"http://www.yahoo.com/*\" }",
+ stream.str());
+}
+
+TEST(URLPatternSetTest, OverlapsWith) {
+ URLPatternSet set1;
+ AddPattern(&set1, "http://www.google.com/f*");
+ AddPattern(&set1, "http://www.yahoo.com/b*");
+
+ URLPatternSet set2;
+ AddPattern(&set2, "http://www.reddit.com/f*");
+ AddPattern(&set2, "http://www.yahoo.com/z*");
+
+ URLPatternSet set3;
+ AddPattern(&set3, "http://www.google.com/q/*");
+ AddPattern(&set3, "http://www.yahoo.com/b/*");
+
+ EXPECT_FALSE(set1.OverlapsWith(set2));
+ EXPECT_FALSE(set2.OverlapsWith(set1));
+
+ EXPECT_TRUE(set1.OverlapsWith(set3));
+ EXPECT_TRUE(set3.OverlapsWith(set1));
+}
+
+TEST(URLPatternSetTest, CreateDifference) {
+ URLPatternSet expected;
+ URLPatternSet set1;
+ URLPatternSet set2;
+ AddPattern(&set1, "http://www.google.com/f*");
+ AddPattern(&set1, "http://www.yahoo.com/b*");
+
+ // Subtract an empty set.
+ URLPatternSet result = URLPatternSet::CreateDifference(set1, set2);
+ EXPECT_EQ(set1, result);
+
+ // Subtract a real set.
+ AddPattern(&set2, "http://www.reddit.com/f*");
+ AddPattern(&set2, "http://www.yahoo.com/z*");
+ AddPattern(&set2, "http://www.google.com/f*");
+
+ AddPattern(&expected, "http://www.yahoo.com/b*");
+
+ result = URLPatternSet::CreateDifference(set1, set2);
+ EXPECT_EQ(expected, result);
+ EXPECT_FALSE(result.is_empty());
+ EXPECT_TRUE(set1.Contains(result));
+ EXPECT_FALSE(result.Contains(set2));
+ EXPECT_FALSE(set2.Contains(result));
+
+ URLPatternSet intersection = URLPatternSet::CreateIntersection(result, set2);
+ EXPECT_TRUE(intersection.is_empty());
+}
+
+TEST(URLPatternSetTest, CreateIntersection) {
+ URLPatternSet empty_set;
+ URLPatternSet expected;
+ URLPatternSet set1;
+ AddPattern(&set1, "http://www.google.com/f*");
+ AddPattern(&set1, "http://www.yahoo.com/b*");
+
+ // Intersection with an empty set.
+ URLPatternSet result = URLPatternSet::CreateIntersection(set1, empty_set);
+ EXPECT_EQ(expected, result);
+ EXPECT_TRUE(result.is_empty());
+ EXPECT_TRUE(empty_set.Contains(result));
+ EXPECT_TRUE(result.Contains(empty_set));
+ EXPECT_TRUE(set1.Contains(result));
+
+ // Intersection with a real set.
+ URLPatternSet set2;
+ AddPattern(&set2, "http://www.reddit.com/f*");
+ AddPattern(&set2, "http://www.yahoo.com/z*");
+ AddPattern(&set2, "http://www.google.com/f*");
+
+ AddPattern(&expected, "http://www.google.com/f*");
+
+ result = URLPatternSet::CreateIntersection(set1, set2);
+ EXPECT_EQ(expected, result);
+ EXPECT_FALSE(result.is_empty());
+ EXPECT_TRUE(set1.Contains(result));
+ EXPECT_TRUE(set2.Contains(result));
+}
+
+TEST(URLPatternSetTest, CreateSemanticIntersection) {
+ {
+ URLPatternSet set1;
+ AddPattern(&set1, "http://*.google.com/*");
+ AddPattern(&set1, "http://*.yahoo.com/*");
+
+ URLPatternSet set2;
+ AddPattern(&set2, "http://google.com/*");
+
+ // The semantic intersection should contain only those patterns that are in
+ // both set 1 and set 2, or "http://google.com/*".
+ URLPatternSet intersection =
+ URLPatternSet::CreateSemanticIntersection(set1, set2);
+ ASSERT_EQ(1u, intersection.size());
+ EXPECT_EQ("http://google.com/*", intersection.begin()->GetAsString());
+ }
+
+ {
+ // We don't handle funny intersections, where the resultant pattern is
+ // neither of the two compared patterns. So the intersection of these two
+ // is not http://www.google.com/*, but rather nothing.
+ URLPatternSet set1;
+ AddPattern(&set1, "http://*/*");
+ URLPatternSet set2;
+ AddPattern(&set2, "*://www.google.com/*");
+ EXPECT_TRUE(
+ URLPatternSet::CreateSemanticIntersection(set1, set2).is_empty());
+ }
+}
+
+TEST(URLPatternSetTest, CreateUnion) {
+ URLPatternSet empty_set;
+
+ URLPatternSet set1;
+ AddPattern(&set1, "http://www.google.com/f*");
+ AddPattern(&set1, "http://www.yahoo.com/b*");
+
+ URLPatternSet expected;
+ AddPattern(&expected, "http://www.google.com/f*");
+ AddPattern(&expected, "http://www.yahoo.com/b*");
+
+ // Union with an empty set.
+ URLPatternSet result = URLPatternSet::CreateUnion(set1, empty_set);
+ EXPECT_EQ(expected, result);
+
+ // Union with a real set.
+ URLPatternSet set2;
+ AddPattern(&set2, "http://www.reddit.com/f*");
+ AddPattern(&set2, "http://www.yahoo.com/z*");
+ AddPattern(&set2, "http://www.google.com/f*");
+
+ AddPattern(&expected, "http://www.reddit.com/f*");
+ AddPattern(&expected, "http://www.yahoo.com/z*");
+
+ result = URLPatternSet::CreateUnion(set1, set2);
+ EXPECT_EQ(expected, result);
+}
+
+TEST(URLPatternSetTest, Contains) {
+ URLPatternSet set1;
+ URLPatternSet set2;
+ URLPatternSet empty_set;
+
+ AddPattern(&set1, "http://www.google.com/*");
+ AddPattern(&set1, "http://www.yahoo.com/*");
+
+ AddPattern(&set2, "http://www.reddit.com/*");
+
+ EXPECT_FALSE(set1.Contains(set2));
+ EXPECT_TRUE(set1.Contains(empty_set));
+ EXPECT_FALSE(empty_set.Contains(set1));
+
+ AddPattern(&set2, "http://www.yahoo.com/*");
+
+ EXPECT_FALSE(set1.Contains(set2));
+ EXPECT_FALSE(set2.Contains(set1));
+
+ AddPattern(&set2, "http://www.google.com/*");
+
+ EXPECT_FALSE(set1.Contains(set2));
+ EXPECT_TRUE(set2.Contains(set1));
+
+ // Note that this checks if individual patterns contain other patterns, not
+ // just equality. For example:
+ AddPattern(&set1, "http://*.reddit.com/*");
+ EXPECT_TRUE(set1.Contains(set2));
+ EXPECT_FALSE(set2.Contains(set1));
+}
+
+TEST(URLPatternSetTest, Duplicates) {
+ URLPatternSet set1;
+ URLPatternSet set2;
+
+ AddPattern(&set1, "http://www.google.com/*");
+ AddPattern(&set2, "http://www.google.com/*");
+
+ AddPattern(&set1, "http://www.google.com/*");
+
+ // The sets should still be equal after adding a duplicate.
+ EXPECT_EQ(set2, set1);
+}
+
+TEST(URLPatternSetTest, ToValueAndPopulate) {
+ URLPatternSet set1;
+ URLPatternSet set2;
+
+ std::vector<std::string> patterns;
+ patterns.push_back("http://www.google.com/*");
+ patterns.push_back("http://www.yahoo.com/*");
+
+ for (size_t i = 0; i < patterns.size(); ++i)
+ AddPattern(&set1, patterns[i]);
+
+ std::string error;
+ bool allow_file_access = false;
+ scoped_ptr<base::ListValue> value(set1.ToValue());
+ set2.Populate(*value, URLPattern::SCHEME_ALL, allow_file_access, &error);
+ EXPECT_EQ(set1, set2);
+
+ set2.ClearPatterns();
+ set2.Populate(patterns, URLPattern::SCHEME_ALL, allow_file_access, &error);
+ EXPECT_EQ(set1, set2);
+}
+
+TEST(URLPatternSetTest, NwayUnion) {
+ std::string google_a = "http://www.google.com/a*";
+ std::string google_b = "http://www.google.com/b*";
+ std::string google_c = "http://www.google.com/c*";
+ std::string yahoo_a = "http://www.yahoo.com/a*";
+ std::string yahoo_b = "http://www.yahoo.com/b*";
+ std::string yahoo_c = "http://www.yahoo.com/c*";
+ std::string reddit_a = "http://www.reddit.com/a*";
+ std::string reddit_b = "http://www.reddit.com/b*";
+ std::string reddit_c = "http://www.reddit.com/c*";
+
+ // Empty list.
+ {
+ std::vector<URLPatternSet> empty;
+
+ URLPatternSet result = URLPatternSet::CreateUnion(empty);
+
+ URLPatternSet expected;
+ EXPECT_EQ(expected, result);
+ }
+
+ // Singleton list.
+ {
+ std::vector<URLPatternSet> test;
+ test.push_back(Patterns(google_a));
+
+ URLPatternSet result = URLPatternSet::CreateUnion(test);
+
+ URLPatternSet expected = Patterns(google_a);
+ EXPECT_EQ(expected, result);
+ }
+
+ // List with 2 elements.
+ {
+ std::vector<URLPatternSet> test;
+ test.push_back(Patterns(google_a, google_b));
+ test.push_back(Patterns(google_b, google_c));
+
+ URLPatternSet result = URLPatternSet::CreateUnion(test);
+
+ URLPatternSet expected;
+ AddPattern(&expected, google_a);
+ AddPattern(&expected, google_b);
+ AddPattern(&expected, google_c);
+ EXPECT_EQ(expected, result);
+ }
+
+ // List with 3 elements.
+ {
+ std::vector<URLPatternSet> test;
+ test.push_back(Patterns(google_a, google_b));
+ test.push_back(Patterns(google_b, google_c));
+ test.push_back(Patterns(yahoo_a, yahoo_b));
+
+ URLPatternSet result = URLPatternSet::CreateUnion(test);
+
+ URLPatternSet expected;
+ AddPattern(&expected, google_a);
+ AddPattern(&expected, google_b);
+ AddPattern(&expected, google_c);
+ AddPattern(&expected, yahoo_a);
+ AddPattern(&expected, yahoo_b);
+ EXPECT_EQ(expected, result);
+ }
+
+ // List with 7 elements.
+ {
+ std::vector<URLPatternSet> test;
+ test.push_back(Patterns(google_a));
+ test.push_back(Patterns(google_b));
+ test.push_back(Patterns(google_c));
+ test.push_back(Patterns(yahoo_a));
+ test.push_back(Patterns(yahoo_b));
+ test.push_back(Patterns(yahoo_c));
+ test.push_back(Patterns(reddit_a));
+
+ URLPatternSet result = URLPatternSet::CreateUnion(test);
+
+ URLPatternSet expected;
+ AddPattern(&expected, google_a);
+ AddPattern(&expected, google_b);
+ AddPattern(&expected, google_c);
+ AddPattern(&expected, yahoo_a);
+ AddPattern(&expected, yahoo_b);
+ AddPattern(&expected, yahoo_c);
+ AddPattern(&expected, reddit_a);
+ EXPECT_EQ(expected, result);
+ }
+
+ // List with 8 elements.
+ {
+ std::vector<URLPatternSet> test;
+ test.push_back(Patterns(google_a));
+ test.push_back(Patterns(google_b));
+ test.push_back(Patterns(google_c));
+ test.push_back(Patterns(yahoo_a));
+ test.push_back(Patterns(yahoo_b));
+ test.push_back(Patterns(yahoo_c));
+ test.push_back(Patterns(reddit_a));
+ test.push_back(Patterns(reddit_b));
+
+ URLPatternSet result = URLPatternSet::CreateUnion(test);
+
+ URLPatternSet expected;
+ AddPattern(&expected, google_a);
+ AddPattern(&expected, google_b);
+ AddPattern(&expected, google_c);
+ AddPattern(&expected, yahoo_a);
+ AddPattern(&expected, yahoo_b);
+ AddPattern(&expected, yahoo_c);
+ AddPattern(&expected, reddit_a);
+ AddPattern(&expected, reddit_b);
+ EXPECT_EQ(expected, result);
+ }
+
+ // List with 9 elements.
+ {
+ std::vector<URLPatternSet> test;
+ test.push_back(Patterns(google_a));
+ test.push_back(Patterns(google_b));
+ test.push_back(Patterns(google_c));
+ test.push_back(Patterns(yahoo_a));
+ test.push_back(Patterns(yahoo_b));
+ test.push_back(Patterns(yahoo_c));
+ test.push_back(Patterns(reddit_a));
+ test.push_back(Patterns(reddit_b));
+ test.push_back(Patterns(reddit_c));
+
+ URLPatternSet result = URLPatternSet::CreateUnion(test);
+
+ URLPatternSet expected;
+ AddPattern(&expected, google_a);
+ AddPattern(&expected, google_b);
+ AddPattern(&expected, google_c);
+ AddPattern(&expected, yahoo_a);
+ AddPattern(&expected, yahoo_b);
+ AddPattern(&expected, yahoo_c);
+ AddPattern(&expected, reddit_a);
+ AddPattern(&expected, reddit_b);
+ AddPattern(&expected, reddit_c);
+ EXPECT_EQ(expected, result);
+ }
+}
+
+TEST(URLPatternSetTest, AddOrigin) {
+ URLPatternSet set;
+ EXPECT_TRUE(set.AddOrigin(
+ URLPattern::SCHEME_ALL, GURL("https://www.google.com/")));
+ EXPECT_TRUE(set.MatchesURL(GURL("https://www.google.com/foo/bar")));
+ EXPECT_FALSE(set.MatchesURL(GURL("http://www.google.com/foo/bar")));
+ EXPECT_FALSE(set.MatchesURL(GURL("https://en.google.com/foo/bar")));
+ set.ClearPatterns();
+
+ EXPECT_TRUE(set.AddOrigin(
+ URLPattern::SCHEME_ALL, GURL("https://google.com/")));
+ EXPECT_FALSE(set.MatchesURL(GURL("https://www.google.com/foo/bar")));
+ EXPECT_TRUE(set.MatchesURL(GURL("https://google.com/foo/bar")));
+
+ EXPECT_FALSE(set.AddOrigin(
+ URLPattern::SCHEME_HTTP, GURL("https://google.com/")));
+}
+
+TEST(URLPatternSetTest, ToStringVector) {
+ URLPatternSet set;
+ AddPattern(&set, "https://google.com/");
+ AddPattern(&set, "https://google.com/");
+ AddPattern(&set, "https://yahoo.com/");
+
+ scoped_ptr<std::vector<std::string>> string_vector(set.ToStringVector());
+
+ EXPECT_EQ(2UL, string_vector->size());
+
+ const auto begin = string_vector->begin();
+ const auto end = string_vector->end();
+
+ auto it = std::find(begin, end, "https://google.com/");
+ EXPECT_NE(it, end);
+ it = std::find(begin, end, "https://yahoo.com/");
+ EXPECT_NE(it, end);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/url_pattern_unittest.cc b/chromium/extensions/common/url_pattern_unittest.cc
new file mode 100644
index 00000000000..f0965cace3a
--- /dev/null
+++ b/chromium/extensions/common/url_pattern_unittest.cc
@@ -0,0 +1,850 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/url_pattern.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace {
+
+// See url_pattern.h for examples of valid and invalid patterns.
+
+static const int kAllSchemes =
+ URLPattern::SCHEME_HTTP |
+ URLPattern::SCHEME_HTTPS |
+ URLPattern::SCHEME_FILE |
+ URLPattern::SCHEME_FTP |
+ URLPattern::SCHEME_CHROMEUI |
+ URLPattern::SCHEME_EXTENSION |
+ URLPattern::SCHEME_FILESYSTEM;
+
+TEST(ExtensionURLPatternTest, ParseInvalid) {
+ const struct {
+ const char* pattern;
+ URLPattern::ParseResult expected_result;
+ } kInvalidPatterns[] = {
+ { "http", URLPattern::PARSE_ERROR_MISSING_SCHEME_SEPARATOR },
+ { "http:", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR },
+ { "http:/", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR },
+ { "about://", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR },
+ { "http://", URLPattern::PARSE_ERROR_EMPTY_HOST },
+ { "http:///", URLPattern::PARSE_ERROR_EMPTY_HOST },
+ { "http:// /", URLPattern::PARSE_ERROR_EMPTY_HOST },
+ { "http://*foo/bar", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD },
+ { "http://foo.*.bar/baz", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD },
+ { "http://fo.*.ba:123/baz", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD },
+ { "http:/bar", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR },
+ { "http://bar", URLPattern::PARSE_ERROR_EMPTY_PATH },
+ };
+
+ for (size_t i = 0; i < arraysize(kInvalidPatterns); ++i) {
+ URLPattern pattern(URLPattern::SCHEME_ALL);
+ EXPECT_EQ(kInvalidPatterns[i].expected_result,
+ pattern.Parse(kInvalidPatterns[i].pattern))
+ << kInvalidPatterns[i].pattern;
+ }
+
+ {
+ // Cannot use a C string, because this contains a null byte.
+ std::string null_host("http://\0www/", 12);
+ URLPattern pattern(URLPattern::SCHEME_ALL);
+ EXPECT_EQ(URLPattern::PARSE_ERROR_INVALID_HOST,
+ pattern.Parse(null_host))
+ << null_host;
+ }
+}
+
+TEST(ExtensionURLPatternTest, Ports) {
+ const struct {
+ const char* pattern;
+ URLPattern::ParseResult expected_result;
+ const char* expected_port;
+ } kTestPatterns[] = {
+ { "http://foo:1234/", URLPattern::PARSE_SUCCESS, "1234" },
+ { "http://foo:1234/bar", URLPattern::PARSE_SUCCESS, "1234" },
+ { "http://*.foo:1234/", URLPattern::PARSE_SUCCESS, "1234" },
+ { "http://*.foo:1234/bar", URLPattern::PARSE_SUCCESS, "1234" },
+ { "http://:1234/", URLPattern::PARSE_SUCCESS, "1234" },
+ { "http://foo:/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
+ { "http://foo:*/", URLPattern::PARSE_SUCCESS, "*" },
+ { "http://*.foo:/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
+ { "http://foo:com/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
+ { "http://foo:123456/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
+ { "http://foo:80:80/monkey", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
+ { "file://foo:1234/bar", URLPattern::PARSE_SUCCESS, "*" },
+ { "chrome://foo:1234/bar", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
+
+ // Port-like strings in the path should not trigger a warning.
+ { "http://*/:1234", URLPattern::PARSE_SUCCESS, "*" },
+ { "http://*.foo/bar:1234", URLPattern::PARSE_SUCCESS, "*" },
+ { "http://foo/bar:1234/path", URLPattern::PARSE_SUCCESS, "*" },
+ };
+
+ for (size_t i = 0; i < arraysize(kTestPatterns); ++i) {
+ URLPattern pattern(URLPattern::SCHEME_ALL);
+ EXPECT_EQ(kTestPatterns[i].expected_result,
+ pattern.Parse(kTestPatterns[i].pattern))
+ << "Got unexpected result for URL pattern: "
+ << kTestPatterns[i].pattern;
+ EXPECT_EQ(kTestPatterns[i].expected_port, pattern.port())
+ << "Got unexpected port for URL pattern: " << kTestPatterns[i].pattern;
+ }
+}
+
+// all pages for a given scheme
+TEST(ExtensionURLPatternTest, Match1) {
+ URLPattern pattern(kAllSchemes);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://*/*"));
+ EXPECT_EQ("http", pattern.scheme());
+ EXPECT_EQ("", pattern.host());
+ EXPECT_TRUE(pattern.match_subdomains());
+ EXPECT_FALSE(pattern.match_all_urls());
+ EXPECT_EQ("/*", pattern.path());
+ EXPECT_TRUE(pattern.MatchesURL(GURL("http://google.com")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("http://yahoo.com")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("http://google.com/foo")));
+ EXPECT_FALSE(pattern.MatchesURL(GURL("https://google.com")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("http://74.125.127.100/search")));
+}
+
+// all domains
+TEST(ExtensionURLPatternTest, Match2) {
+ URLPattern pattern(kAllSchemes);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("https://*/foo*"));
+ EXPECT_EQ("https", pattern.scheme());
+ EXPECT_EQ("", pattern.host());
+ EXPECT_TRUE(pattern.match_subdomains());
+ EXPECT_FALSE(pattern.match_all_urls());
+ EXPECT_EQ("/foo*", pattern.path());
+ EXPECT_TRUE(pattern.MatchesURL(GURL("https://www.google.com/foo")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("https://www.google.com/foobar")));
+ EXPECT_FALSE(pattern.MatchesURL(GURL("http://www.google.com/foo")));
+ EXPECT_FALSE(pattern.MatchesURL(GURL("https://www.google.com/")));
+ EXPECT_TRUE(pattern.MatchesURL(
+ GURL("filesystem:https://www.google.com/foobar/")));
+}
+
+// subdomains
+TEST(URLPatternTest, Match3) {
+ URLPattern pattern(kAllSchemes);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS,
+ pattern.Parse("http://*.google.com/foo*bar"));
+ EXPECT_EQ("http", pattern.scheme());
+ EXPECT_EQ("google.com", pattern.host());
+ EXPECT_TRUE(pattern.match_subdomains());
+ EXPECT_FALSE(pattern.match_all_urls());
+ EXPECT_EQ("/foo*bar", pattern.path());
+ EXPECT_TRUE(pattern.MatchesURL(GURL("http://google.com/foobar")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.google.com/foo?bar")));
+ EXPECT_TRUE(pattern.MatchesURL(
+ GURL("http://monkey.images.google.com/foooobar")));
+ EXPECT_FALSE(pattern.MatchesURL(GURL("http://yahoo.com/foobar")));
+ EXPECT_TRUE(pattern.MatchesURL(
+ GURL("filesystem:http://google.com/foo/bar")));
+ EXPECT_FALSE(pattern.MatchesURL(
+ GURL("filesystem:http://google.com/temporary/foobar")));
+}
+
+// glob escaping
+TEST(ExtensionURLPatternTest, Match5) {
+ URLPattern pattern(kAllSchemes);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("file:///foo?bar\\*baz"));
+ EXPECT_EQ("file", pattern.scheme());
+ EXPECT_EQ("", pattern.host());
+ EXPECT_FALSE(pattern.match_subdomains());
+ EXPECT_FALSE(pattern.match_all_urls());
+ EXPECT_EQ("/foo?bar\\*baz", pattern.path());
+ EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo?bar\\hellobaz")));
+ EXPECT_FALSE(pattern.MatchesURL(GURL("file:///fooXbar\\hellobaz")));
+}
+
+// ip addresses
+TEST(ExtensionURLPatternTest, Match6) {
+ URLPattern pattern(kAllSchemes);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://127.0.0.1/*"));
+ EXPECT_EQ("http", pattern.scheme());
+ EXPECT_EQ("127.0.0.1", pattern.host());
+ EXPECT_FALSE(pattern.match_subdomains());
+ EXPECT_FALSE(pattern.match_all_urls());
+ EXPECT_EQ("/*", pattern.path());
+ EXPECT_TRUE(pattern.MatchesURL(GURL("http://127.0.0.1")));
+}
+
+// subdomain matching with ip addresses
+TEST(ExtensionURLPatternTest, Match7) {
+ URLPattern pattern(kAllSchemes);
+ // allowed, but useless
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://*.0.0.1/*"));
+ EXPECT_EQ("http", pattern.scheme());
+ EXPECT_EQ("0.0.1", pattern.host());
+ EXPECT_TRUE(pattern.match_subdomains());
+ EXPECT_FALSE(pattern.match_all_urls());
+ EXPECT_EQ("/*", pattern.path());
+ // Subdomain matching is never done if the argument has an IP address host.
+ EXPECT_FALSE(pattern.MatchesURL(GURL("http://127.0.0.1")));
+}
+
+// unicode
+TEST(ExtensionURLPatternTest, Match8) {
+ URLPattern pattern(kAllSchemes);
+ // The below is the ASCII encoding of the following URL:
+ // http://*.\xe1\x80\xbf/a\xc2\x81\xe1*
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS,
+ pattern.Parse("http://*.xn--gkd/a%C2%81%E1*"));
+ EXPECT_EQ("http", pattern.scheme());
+ EXPECT_EQ("xn--gkd", pattern.host());
+ EXPECT_TRUE(pattern.match_subdomains());
+ EXPECT_FALSE(pattern.match_all_urls());
+ EXPECT_EQ("/a%C2%81%E1*", pattern.path());
+ EXPECT_TRUE(pattern.MatchesURL(
+ GURL("http://abc.\xe1\x80\xbf/a\xc2\x81\xe1xyz")));
+ EXPECT_TRUE(pattern.MatchesURL(
+ GURL("http://\xe1\x80\xbf/a\xc2\x81\xe1\xe1")));
+}
+
+// chrome://
+TEST(ExtensionURLPatternTest, Match9) {
+ URLPattern pattern(kAllSchemes);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("chrome://favicon/*"));
+ EXPECT_EQ("chrome", pattern.scheme());
+ EXPECT_EQ("favicon", pattern.host());
+ EXPECT_FALSE(pattern.match_subdomains());
+ EXPECT_FALSE(pattern.match_all_urls());
+ EXPECT_EQ("/*", pattern.path());
+ EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/https://google.com")));
+ EXPECT_FALSE(pattern.MatchesURL(GURL("chrome://history")));
+}
+
+// *://
+TEST(ExtensionURLPatternTest, Match10) {
+ URLPattern pattern(kAllSchemes);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("*://*/*"));
+ EXPECT_TRUE(pattern.MatchesScheme("http"));
+ EXPECT_TRUE(pattern.MatchesScheme("https"));
+ EXPECT_FALSE(pattern.MatchesScheme("chrome"));
+ EXPECT_FALSE(pattern.MatchesScheme("file"));
+ EXPECT_FALSE(pattern.MatchesScheme("ftp"));
+ EXPECT_TRUE(pattern.match_subdomains());
+ EXPECT_FALSE(pattern.match_all_urls());
+ EXPECT_EQ("/*", pattern.path());
+ EXPECT_TRUE(pattern.MatchesURL(GURL("http://127.0.0.1")));
+ EXPECT_FALSE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com")));
+ EXPECT_FALSE(pattern.MatchesURL(GURL("file:///foo/bar")));
+ EXPECT_FALSE(pattern.MatchesURL(GURL("file://localhost/foo/bar")));
+}
+
+// <all_urls>
+TEST(ExtensionURLPatternTest, Match11) {
+ URLPattern pattern(kAllSchemes);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("<all_urls>"));
+ EXPECT_TRUE(pattern.MatchesScheme("chrome"));
+ EXPECT_TRUE(pattern.MatchesScheme("http"));
+ EXPECT_TRUE(pattern.MatchesScheme("https"));
+ EXPECT_TRUE(pattern.MatchesScheme("file"));
+ EXPECT_TRUE(pattern.MatchesScheme("filesystem"));
+ EXPECT_TRUE(pattern.MatchesScheme("chrome-extension"));
+ EXPECT_TRUE(pattern.match_subdomains());
+ EXPECT_TRUE(pattern.match_all_urls());
+ EXPECT_EQ("/*", pattern.path());
+ EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("http://127.0.0.1")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo/bar")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("file://localhost/foo/bar")));
+
+ // Make sure the properties are the same when creating an <all_urls> pattern
+ // via SetMatchAllURLs and by parsing <all_urls>.
+ URLPattern pattern2(kAllSchemes);
+ pattern2.SetMatchAllURLs(true);
+
+ EXPECT_EQ(pattern.valid_schemes(), pattern2.valid_schemes());
+ EXPECT_EQ(pattern.match_subdomains(), pattern2.match_subdomains());
+ EXPECT_EQ(pattern.path(), pattern2.path());
+ EXPECT_EQ(pattern.match_all_urls(), pattern2.match_all_urls());
+ EXPECT_EQ(pattern.scheme(), pattern2.scheme());
+ EXPECT_EQ(pattern.port(), pattern2.port());
+ EXPECT_EQ(pattern.GetAsString(), pattern2.GetAsString());
+}
+
+// SCHEME_ALL matches all schemes.
+TEST(ExtensionURLPatternTest, Match12) {
+ URLPattern pattern(URLPattern::SCHEME_ALL);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("<all_urls>"));
+ EXPECT_TRUE(pattern.MatchesScheme("chrome"));
+ EXPECT_TRUE(pattern.MatchesScheme("http"));
+ EXPECT_TRUE(pattern.MatchesScheme("https"));
+ EXPECT_TRUE(pattern.MatchesScheme("file"));
+ EXPECT_TRUE(pattern.MatchesScheme("filesystem"));
+ EXPECT_TRUE(pattern.MatchesScheme("javascript"));
+ EXPECT_TRUE(pattern.MatchesScheme("data"));
+ EXPECT_TRUE(pattern.MatchesScheme("about"));
+ EXPECT_TRUE(pattern.MatchesScheme("chrome-extension"));
+ EXPECT_TRUE(pattern.match_subdomains());
+ EXPECT_TRUE(pattern.match_all_urls());
+ EXPECT_EQ("/*", pattern.path());
+ EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("http://127.0.0.1")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo/bar")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("file://localhost/foo/bar")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://newtab")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("about:blank")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("about:version")));
+ EXPECT_TRUE(pattern.MatchesURL(
+ GURL("data:text/html;charset=utf-8,<html>asdf</html>")));
+}
+
+static const struct MatchPatterns {
+ const char* pattern;
+ const char* matches;
+} kMatch13UrlPatternTestCases[] = {
+ {"about:*", "about:blank"},
+ {"about:blank", "about:blank"},
+ {"about:*", "about:version"},
+ {"chrome-extension://*/*", "chrome-extension://FTW"},
+ {"data:*", "data:monkey"},
+ {"javascript:*", "javascript:atemyhomework"},
+};
+
+// SCHEME_ALL and specific schemes.
+TEST(ExtensionURLPatternTest, Match13) {
+ for (size_t i = 0; i < arraysize(kMatch13UrlPatternTestCases); ++i) {
+ URLPattern pattern(URLPattern::SCHEME_ALL);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS,
+ pattern.Parse(kMatch13UrlPatternTestCases[i].pattern))
+ << " while parsing " << kMatch13UrlPatternTestCases[i].pattern;
+ EXPECT_TRUE(pattern.MatchesURL(
+ GURL(kMatch13UrlPatternTestCases[i].matches)))
+ << " while matching " << kMatch13UrlPatternTestCases[i].matches;
+ }
+
+ // Negative test.
+ URLPattern pattern(URLPattern::SCHEME_ALL);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("data:*"));
+ EXPECT_FALSE(pattern.MatchesURL(GURL("about:blank")));
+}
+
+// file scheme with empty hostname
+TEST(ExtensionURLPatternTest, Match14) {
+ URLPattern pattern(kAllSchemes);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("file:///foo*"));
+ EXPECT_EQ("file", pattern.scheme());
+ EXPECT_EQ("", pattern.host());
+ EXPECT_FALSE(pattern.match_subdomains());
+ EXPECT_FALSE(pattern.match_all_urls());
+ EXPECT_EQ("/foo*", pattern.path());
+ EXPECT_FALSE(pattern.MatchesURL(GURL("file://foo")));
+ EXPECT_FALSE(pattern.MatchesURL(GURL("file://foobar")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foobar")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("file://localhost/foo")));
+}
+
+// file scheme without hostname part
+TEST(ExtensionURLPatternTest, Match15) {
+ URLPattern pattern(kAllSchemes);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("file://foo*"));
+ EXPECT_EQ("file", pattern.scheme());
+ EXPECT_EQ("", pattern.host());
+ EXPECT_FALSE(pattern.match_subdomains());
+ EXPECT_FALSE(pattern.match_all_urls());
+ EXPECT_EQ("/foo*", pattern.path());
+ EXPECT_FALSE(pattern.MatchesURL(GURL("file://foo")));
+ EXPECT_FALSE(pattern.MatchesURL(GURL("file://foobar")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foobar")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("file://localhost/foo")));
+}
+
+// file scheme with hostname
+TEST(ExtensionURLPatternTest, Match16) {
+ URLPattern pattern(kAllSchemes);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("file://localhost/foo*"));
+ EXPECT_EQ("file", pattern.scheme());
+ // Since hostname is ignored for file://.
+ EXPECT_EQ("", pattern.host());
+ EXPECT_FALSE(pattern.match_subdomains());
+ EXPECT_FALSE(pattern.match_all_urls());
+ EXPECT_EQ("/foo*", pattern.path());
+ EXPECT_FALSE(pattern.MatchesURL(GURL("file://foo")));
+ EXPECT_FALSE(pattern.MatchesURL(GURL("file://foobar")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foobar")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("file://localhost/foo")));
+}
+
+// Specific port
+TEST(ExtensionURLPatternTest, Match17) {
+ URLPattern pattern(kAllSchemes);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS,
+ pattern.Parse("http://www.example.com:80/foo"));
+ EXPECT_EQ("http", pattern.scheme());
+ EXPECT_EQ("www.example.com", pattern.host());
+ EXPECT_FALSE(pattern.match_subdomains());
+ EXPECT_FALSE(pattern.match_all_urls());
+ EXPECT_EQ("/foo", pattern.path());
+ EXPECT_EQ("80", pattern.port());
+ EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.example.com:80/foo")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.example.com/foo")));
+ EXPECT_FALSE(pattern.MatchesURL(GURL("http://www.example.com:8080/foo")));
+ EXPECT_FALSE(pattern.MatchesURL(
+ GURL("filesystem:http://www.example.com:8080/foo/")));
+ EXPECT_FALSE(pattern.MatchesURL(
+ GURL("filesystem:http://www.example.com/f/foo")));
+}
+
+// Explicit port wildcard
+TEST(ExtensionURLPatternTest, Match18) {
+ URLPattern pattern(kAllSchemes);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS,
+ pattern.Parse("http://www.example.com:*/foo"));
+ EXPECT_EQ("http", pattern.scheme());
+ EXPECT_EQ("www.example.com", pattern.host());
+ EXPECT_FALSE(pattern.match_subdomains());
+ EXPECT_FALSE(pattern.match_all_urls());
+ EXPECT_EQ("/foo", pattern.path());
+ EXPECT_EQ("*", pattern.port());
+ EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.example.com:80/foo")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.example.com/foo")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.example.com:8080/foo")));
+ EXPECT_FALSE(pattern.MatchesURL(
+ GURL("filesystem:http://www.example.com:8080/foo/")));
+}
+
+// chrome-extension://
+TEST(ExtensionURLPatternTest, Match19) {
+ URLPattern pattern(URLPattern::SCHEME_EXTENSION);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS,
+ pattern.Parse("chrome-extension://ftw/*"));
+ EXPECT_EQ("chrome-extension", pattern.scheme());
+ EXPECT_EQ("ftw", pattern.host());
+ EXPECT_FALSE(pattern.match_subdomains());
+ EXPECT_FALSE(pattern.match_all_urls());
+ EXPECT_EQ("/*", pattern.path());
+ EXPECT_TRUE(pattern.MatchesURL(GURL("chrome-extension://ftw")));
+ EXPECT_TRUE(pattern.MatchesURL(
+ GURL("chrome-extension://ftw/http://google.com")));
+ EXPECT_TRUE(pattern.MatchesURL(
+ GURL("chrome-extension://ftw/https://google.com")));
+ EXPECT_FALSE(pattern.MatchesURL(GURL("chrome-extension://foobar")));
+ EXPECT_TRUE(pattern.MatchesURL(
+ GURL("filesystem:chrome-extension://ftw/t/file.txt")));
+}
+
+static const struct GetAsStringPatterns {
+ const char* pattern;
+} kGetAsStringTestCases[] = {
+ { "http://www/" },
+ { "http://*/*" },
+ { "chrome://*/*" },
+ { "chrome://newtab/" },
+ { "about:*" },
+ { "about:blank" },
+ { "chrome-extension://*/*" },
+ { "chrome-extension://FTW/" },
+ { "data:*" },
+ { "data:monkey" },
+ { "javascript:*" },
+ { "javascript:atemyhomework" },
+ { "http://www.example.com:8080/foo" },
+};
+
+TEST(ExtensionURLPatternTest, GetAsString) {
+ for (size_t i = 0; i < arraysize(kGetAsStringTestCases); ++i) {
+ URLPattern pattern(URLPattern::SCHEME_ALL);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS,
+ pattern.Parse(kGetAsStringTestCases[i].pattern))
+ << "Error parsing " << kGetAsStringTestCases[i].pattern;
+ EXPECT_EQ(kGetAsStringTestCases[i].pattern,
+ pattern.GetAsString());
+ }
+}
+
+testing::AssertionResult Overlaps(const URLPattern& pattern1,
+ const URLPattern& pattern2) {
+ if (!pattern1.OverlapsWith(pattern2)) {
+ return testing::AssertionFailure()
+ << pattern1.GetAsString() << " does not overlap " <<
+ pattern2.GetAsString();
+ }
+ if (!pattern2.OverlapsWith(pattern1)) {
+ return testing::AssertionFailure()
+ << pattern2.GetAsString() << " does not overlap " <<
+ pattern1.GetAsString();
+ }
+ return testing::AssertionSuccess()
+ << pattern1.GetAsString() << " overlaps with " << pattern2.GetAsString();
+}
+
+TEST(ExtensionURLPatternTest, Overlaps) {
+ URLPattern pattern1(kAllSchemes, "http://www.google.com/foo/*");
+ URLPattern pattern2(kAllSchemes, "https://www.google.com/foo/*");
+ URLPattern pattern3(kAllSchemes, "http://*.google.com/foo/*");
+ URLPattern pattern4(kAllSchemes, "http://*.yahooo.com/foo/*");
+ URLPattern pattern5(kAllSchemes, "http://www.yahooo.com/bar/*");
+ URLPattern pattern6(kAllSchemes,
+ "http://www.yahooo.com/bar/baz/*");
+ URLPattern pattern7(kAllSchemes, "file:///*");
+ URLPattern pattern8(kAllSchemes, "*://*/*");
+ URLPattern pattern9(URLPattern::SCHEME_HTTPS, "*://*/*");
+ URLPattern pattern10(kAllSchemes, "<all_urls>");
+
+ EXPECT_TRUE(Overlaps(pattern1, pattern1));
+ EXPECT_FALSE(Overlaps(pattern1, pattern2));
+ EXPECT_TRUE(Overlaps(pattern1, pattern3));
+ EXPECT_FALSE(Overlaps(pattern1, pattern4));
+ EXPECT_FALSE(Overlaps(pattern3, pattern4));
+ EXPECT_FALSE(Overlaps(pattern4, pattern5));
+ EXPECT_TRUE(Overlaps(pattern5, pattern6));
+
+ // Test that scheme restrictions work.
+ EXPECT_TRUE(Overlaps(pattern1, pattern8));
+ EXPECT_FALSE(Overlaps(pattern1, pattern9));
+ EXPECT_TRUE(Overlaps(pattern1, pattern10));
+
+ // Test that '<all_urls>' includes file URLs, while scheme '*' does not.
+ EXPECT_FALSE(Overlaps(pattern7, pattern8));
+ EXPECT_TRUE(Overlaps(pattern7, pattern10));
+
+ // Test that wildcard schemes are handled correctly, especially when compared
+ // to each-other.
+ URLPattern pattern11(kAllSchemes, "http://example.com/*");
+ URLPattern pattern12(kAllSchemes, "*://example.com/*");
+ URLPattern pattern13(kAllSchemes, "*://example.com/foo/*");
+ URLPattern pattern14(kAllSchemes, "*://google.com/*");
+ EXPECT_TRUE(Overlaps(pattern8, pattern12));
+ EXPECT_TRUE(Overlaps(pattern9, pattern12));
+ EXPECT_TRUE(Overlaps(pattern10, pattern12));
+ EXPECT_TRUE(Overlaps(pattern11, pattern12));
+ EXPECT_TRUE(Overlaps(pattern12, pattern13));
+ EXPECT_TRUE(Overlaps(pattern11, pattern13));
+ EXPECT_FALSE(Overlaps(pattern14, pattern12));
+ EXPECT_FALSE(Overlaps(pattern14, pattern13));
+}
+
+TEST(ExtensionURLPatternTest, ConvertToExplicitSchemes) {
+ URLPatternList all_urls(URLPattern(
+ kAllSchemes,
+ "<all_urls>").ConvertToExplicitSchemes());
+
+ URLPatternList all_schemes(URLPattern(
+ kAllSchemes,
+ "*://google.com/foo").ConvertToExplicitSchemes());
+
+ URLPatternList monkey(URLPattern(
+ URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS |
+ URLPattern::SCHEME_FTP,
+ "http://google.com/monkey").ConvertToExplicitSchemes());
+
+ ASSERT_EQ(7u, all_urls.size());
+ ASSERT_EQ(2u, all_schemes.size());
+ ASSERT_EQ(1u, monkey.size());
+
+ EXPECT_EQ("http://*/*", all_urls[0].GetAsString());
+ EXPECT_EQ("https://*/*", all_urls[1].GetAsString());
+ EXPECT_EQ("file:///*", all_urls[2].GetAsString());
+ EXPECT_EQ("ftp://*/*", all_urls[3].GetAsString());
+ EXPECT_EQ("chrome://*/*", all_urls[4].GetAsString());
+
+ EXPECT_EQ("http://google.com/foo", all_schemes[0].GetAsString());
+ EXPECT_EQ("https://google.com/foo", all_schemes[1].GetAsString());
+
+ EXPECT_EQ("http://google.com/monkey", monkey[0].GetAsString());
+}
+
+TEST(ExtensionURLPatternTest, IgnorePorts) {
+ std::string pattern_str = "http://www.example.com:8080/foo";
+ GURL url("http://www.example.com:1234/foo");
+
+ URLPattern pattern(kAllSchemes);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse(pattern_str));
+
+ EXPECT_EQ(pattern_str, pattern.GetAsString());
+ EXPECT_FALSE(pattern.MatchesURL(url));
+}
+
+TEST(ExtensionURLPatternTest, IgnoreMissingBackslashes) {
+ std::string pattern_str1 = "http://www.example.com/example";
+ std::string pattern_str2 = "http://www.example.com/example/*";
+ GURL url1("http://www.example.com/example");
+ GURL url2("http://www.example.com/example/");
+
+ URLPattern pattern1(kAllSchemes);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern1.Parse(pattern_str1));
+ URLPattern pattern2(kAllSchemes);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern2.Parse(pattern_str2));
+
+ // Same patterns should match same urls.
+ EXPECT_TRUE(pattern1.MatchesURL(url1));
+ EXPECT_TRUE(pattern2.MatchesURL(url2));
+ // The not terminated path should match the terminated pattern.
+ EXPECT_TRUE(pattern2.MatchesURL(url1));
+ // The terminated path however should not match the unterminated pattern.
+ EXPECT_FALSE(pattern1.MatchesURL(url2));
+}
+
+TEST(ExtensionURLPatternTest, Equals) {
+ const struct {
+ const char* pattern1;
+ const char* pattern2;
+ bool expected_equal;
+ } kEqualsTestCases[] = {
+ // schemes
+ { "http://en.google.com/blah/*/foo",
+ "https://en.google.com/blah/*/foo",
+ false
+ },
+ { "https://en.google.com/blah/*/foo",
+ "https://en.google.com/blah/*/foo",
+ true
+ },
+ { "https://en.google.com/blah/*/foo",
+ "ftp://en.google.com/blah/*/foo",
+ false
+ },
+
+ // subdomains
+ { "https://en.google.com/blah/*/foo",
+ "https://fr.google.com/blah/*/foo",
+ false
+ },
+ { "https://www.google.com/blah/*/foo",
+ "https://*.google.com/blah/*/foo",
+ false
+ },
+ { "https://*.google.com/blah/*/foo",
+ "https://*.google.com/blah/*/foo",
+ true
+ },
+
+ // domains
+ { "http://en.example.com/blah/*/foo",
+ "http://en.google.com/blah/*/foo",
+ false
+ },
+
+ // ports
+ { "http://en.google.com:8000/blah/*/foo",
+ "http://en.google.com/blah/*/foo",
+ false
+ },
+ { "http://fr.google.com:8000/blah/*/foo",
+ "http://fr.google.com:8000/blah/*/foo",
+ true
+ },
+ { "http://en.google.com:8000/blah/*/foo",
+ "http://en.google.com:8080/blah/*/foo",
+ false
+ },
+
+ // paths
+ { "http://en.google.com/blah/*/foo",
+ "http://en.google.com/blah/*",
+ false
+ },
+ { "http://en.google.com/*",
+ "http://en.google.com/",
+ false
+ },
+ { "http://en.google.com/*",
+ "http://en.google.com/*",
+ true
+ },
+
+ // all_urls
+ { "<all_urls>",
+ "<all_urls>",
+ true
+ },
+ { "<all_urls>",
+ "http://*/*",
+ false
+ }
+ };
+
+ for (size_t i = 0; i < arraysize(kEqualsTestCases); ++i) {
+ std::string message = kEqualsTestCases[i].pattern1;
+ message += " ";
+ message += kEqualsTestCases[i].pattern2;
+
+ URLPattern pattern1(URLPattern::SCHEME_ALL);
+ URLPattern pattern2(URLPattern::SCHEME_ALL);
+
+ pattern1.Parse(kEqualsTestCases[i].pattern1);
+ pattern2.Parse(kEqualsTestCases[i].pattern2);
+ EXPECT_EQ(kEqualsTestCases[i].expected_equal, pattern1 == pattern2)
+ << message;
+ }
+}
+
+TEST(ExtensionURLPatternTest, CanReusePatternWithParse) {
+ URLPattern pattern1(URLPattern::SCHEME_ALL);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern1.Parse("http://aa.com/*"));
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern1.Parse("http://bb.com/*"));
+
+ EXPECT_TRUE(pattern1.MatchesURL(GURL("http://bb.com/path")));
+ EXPECT_FALSE(pattern1.MatchesURL(GURL("http://aa.com/path")));
+
+ URLPattern pattern2(URLPattern::SCHEME_ALL, URLPattern::kAllUrlsPattern);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern2.Parse("http://aa.com/*"));
+
+ EXPECT_FALSE(pattern2.MatchesURL(GURL("http://bb.com/path")));
+ EXPECT_TRUE(pattern2.MatchesURL(GURL("http://aa.com/path")));
+ EXPECT_FALSE(pattern2.MatchesURL(GURL("http://sub.aa.com/path")));
+
+ URLPattern pattern3(URLPattern::SCHEME_ALL, "http://aa.com/*");
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern3.Parse("http://aa.com:88/*"));
+ EXPECT_FALSE(pattern3.MatchesURL(GURL("http://aa.com/path")));
+ EXPECT_TRUE(pattern3.MatchesURL(GURL("http://aa.com:88/path")));
+}
+
+// Returns success if neither |a| nor |b| encompasses the other.
+testing::AssertionResult NeitherContains(const URLPattern& a,
+ const URLPattern& b) {
+ if (a.Contains(b))
+ return testing::AssertionFailure() << a.GetAsString() << " encompasses " <<
+ b.GetAsString();
+ if (b.Contains(a))
+ return testing::AssertionFailure() << b.GetAsString() << " encompasses " <<
+ a.GetAsString();
+ return testing::AssertionSuccess() <<
+ "Neither " << a.GetAsString() << " nor " << b.GetAsString() <<
+ " encompass the other";
+}
+
+// Returns success if |a| encompasses |b| but not the other way around.
+testing::AssertionResult StrictlyContains(const URLPattern& a,
+ const URLPattern& b) {
+ if (!a.Contains(b))
+ return testing::AssertionFailure() << a.GetAsString() <<
+ " does not encompass " <<
+ b.GetAsString();
+ if (b.Contains(a))
+ return testing::AssertionFailure() << b.GetAsString() << " encompasses " <<
+ a.GetAsString();
+ return testing::AssertionSuccess() << a.GetAsString() <<
+ " strictly encompasses " <<
+ b.GetAsString();
+}
+
+TEST(ExtensionURLPatternTest, Subset) {
+ URLPattern pattern1(kAllSchemes, "http://www.google.com/foo/*");
+ URLPattern pattern2(kAllSchemes, "https://www.google.com/foo/*");
+ URLPattern pattern3(kAllSchemes, "http://*.google.com/foo/*");
+ URLPattern pattern4(kAllSchemes, "http://*.yahooo.com/foo/*");
+ URLPattern pattern5(kAllSchemes, "http://www.yahooo.com/bar/*");
+ URLPattern pattern6(kAllSchemes, "http://www.yahooo.com/bar/baz/*");
+ URLPattern pattern7(kAllSchemes, "file:///*");
+ URLPattern pattern8(kAllSchemes, "*://*/*");
+ URLPattern pattern9(URLPattern::SCHEME_HTTPS, "*://*/*");
+ URLPattern pattern10(kAllSchemes, "<all_urls>");
+ URLPattern pattern11(kAllSchemes, "http://example.com/*");
+ URLPattern pattern12(kAllSchemes, "*://example.com/*");
+ URLPattern pattern13(kAllSchemes, "*://example.com/foo/*");
+ URLPattern pattern14(kAllSchemes, "http://yahoo.com/*");
+ URLPattern pattern15(kAllSchemes, "http://*.yahoo.com/*");
+
+ // All patterns should encompass themselves.
+ EXPECT_TRUE(pattern1.Contains(pattern1));
+ EXPECT_TRUE(pattern2.Contains(pattern2));
+ EXPECT_TRUE(pattern3.Contains(pattern3));
+ EXPECT_TRUE(pattern4.Contains(pattern4));
+ EXPECT_TRUE(pattern5.Contains(pattern5));
+ EXPECT_TRUE(pattern6.Contains(pattern6));
+ EXPECT_TRUE(pattern7.Contains(pattern7));
+ EXPECT_TRUE(pattern8.Contains(pattern8));
+ EXPECT_TRUE(pattern9.Contains(pattern9));
+ EXPECT_TRUE(pattern10.Contains(pattern10));
+ EXPECT_TRUE(pattern11.Contains(pattern11));
+ EXPECT_TRUE(pattern12.Contains(pattern12));
+ EXPECT_TRUE(pattern13.Contains(pattern13));
+
+ // pattern1's relationship to the other patterns.
+ EXPECT_TRUE(NeitherContains(pattern1, pattern2));
+ EXPECT_TRUE(StrictlyContains(pattern3, pattern1));
+ EXPECT_TRUE(NeitherContains(pattern1, pattern4));
+ EXPECT_TRUE(NeitherContains(pattern1, pattern5));
+ EXPECT_TRUE(NeitherContains(pattern1, pattern6));
+ EXPECT_TRUE(NeitherContains(pattern1, pattern7));
+ EXPECT_TRUE(StrictlyContains(pattern8, pattern1));
+ EXPECT_TRUE(NeitherContains(pattern1, pattern9));
+ EXPECT_TRUE(StrictlyContains(pattern10, pattern1));
+ EXPECT_TRUE(NeitherContains(pattern1, pattern11));
+ EXPECT_TRUE(NeitherContains(pattern1, pattern12));
+ EXPECT_TRUE(NeitherContains(pattern1, pattern13));
+
+ // pattern2's relationship to the other patterns.
+ EXPECT_TRUE(NeitherContains(pattern2, pattern3));
+ EXPECT_TRUE(NeitherContains(pattern2, pattern4));
+ EXPECT_TRUE(NeitherContains(pattern2, pattern5));
+ EXPECT_TRUE(NeitherContains(pattern2, pattern6));
+ EXPECT_TRUE(NeitherContains(pattern2, pattern7));
+ EXPECT_TRUE(StrictlyContains(pattern8, pattern2));
+ EXPECT_TRUE(StrictlyContains(pattern9, pattern2));
+ EXPECT_TRUE(StrictlyContains(pattern10, pattern2));
+ EXPECT_TRUE(NeitherContains(pattern2, pattern11));
+ EXPECT_TRUE(NeitherContains(pattern2, pattern12));
+ EXPECT_TRUE(NeitherContains(pattern2, pattern13));
+
+ // Specifically test file:// URLs.
+ EXPECT_TRUE(NeitherContains(pattern7, pattern8));
+ EXPECT_TRUE(NeitherContains(pattern7, pattern9));
+ EXPECT_TRUE(StrictlyContains(pattern10, pattern7));
+
+ // <all_urls> encompasses everything.
+ EXPECT_TRUE(StrictlyContains(pattern10, pattern1));
+ EXPECT_TRUE(StrictlyContains(pattern10, pattern2));
+ EXPECT_TRUE(StrictlyContains(pattern10, pattern3));
+ EXPECT_TRUE(StrictlyContains(pattern10, pattern4));
+ EXPECT_TRUE(StrictlyContains(pattern10, pattern5));
+ EXPECT_TRUE(StrictlyContains(pattern10, pattern6));
+ EXPECT_TRUE(StrictlyContains(pattern10, pattern7));
+ EXPECT_TRUE(StrictlyContains(pattern10, pattern8));
+ EXPECT_TRUE(StrictlyContains(pattern10, pattern9));
+ EXPECT_TRUE(StrictlyContains(pattern10, pattern11));
+ EXPECT_TRUE(StrictlyContains(pattern10, pattern12));
+ EXPECT_TRUE(StrictlyContains(pattern10, pattern13));
+
+ // More...
+ EXPECT_TRUE(StrictlyContains(pattern12, pattern11));
+ EXPECT_TRUE(NeitherContains(pattern11, pattern13));
+ EXPECT_TRUE(StrictlyContains(pattern12, pattern13));
+ EXPECT_TRUE(StrictlyContains(pattern15, pattern14));
+}
+
+TEST(ExtensionURLPatternTest, MatchesSingleOrigin) {
+ EXPECT_FALSE(
+ URLPattern(URLPattern::SCHEME_ALL, "http://*/").MatchesSingleOrigin());
+ EXPECT_FALSE(URLPattern(URLPattern::SCHEME_ALL, "https://*.google.com/*")
+ .MatchesSingleOrigin());
+ EXPECT_TRUE(URLPattern(URLPattern::SCHEME_ALL, "http://google.com/")
+ .MatchesSingleOrigin());
+ EXPECT_TRUE(URLPattern(URLPattern::SCHEME_ALL, "http://google.com/*")
+ .MatchesSingleOrigin());
+ EXPECT_TRUE(URLPattern(URLPattern::SCHEME_ALL, "http://www.google.com/")
+ .MatchesSingleOrigin());
+ EXPECT_FALSE(URLPattern(URLPattern::SCHEME_ALL, "*://www.google.com/")
+ .MatchesSingleOrigin());
+ EXPECT_FALSE(URLPattern(URLPattern::SCHEME_ALL, "http://*.com/")
+ .MatchesSingleOrigin());
+ EXPECT_FALSE(URLPattern(URLPattern::SCHEME_ALL, "http://*.google.com/foo/bar")
+ .MatchesSingleOrigin());
+ EXPECT_TRUE(
+ URLPattern(URLPattern::SCHEME_ALL, "http://www.google.com/foo/bar")
+ .MatchesSingleOrigin());
+ EXPECT_FALSE(URLPattern(URLPattern::SCHEME_HTTPS, "*://*.google.com/foo/bar")
+ .MatchesSingleOrigin());
+ EXPECT_TRUE(URLPattern(URLPattern::SCHEME_HTTPS, "https://www.google.com/")
+ .MatchesSingleOrigin());
+ EXPECT_FALSE(URLPattern(URLPattern::SCHEME_HTTP,
+ "http://*.google.com/foo/bar").MatchesSingleOrigin());
+ EXPECT_TRUE(
+ URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/foo/bar")
+ .MatchesSingleOrigin());
+}
+
+} // namespace
diff --git a/chromium/extensions/common/user_script.cc b/chromium/extensions/common/user_script.cc
new file mode 100644
index 00000000000..20bff9f159c
--- /dev/null
+++ b/chromium/extensions/common/user_script.cc
@@ -0,0 +1,298 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/user_script.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/atomic_sequence_num.h"
+#include "base/command_line.h"
+#include "base/pickle.h"
+#include "base/strings/pattern.h"
+#include "base/strings/string_util.h"
+#include "extensions/common/switches.h"
+
+namespace {
+
+// This cannot be a plain int or int64_t because we need to generate unique IDs
+// from multiple threads.
+base::StaticAtomicSequenceNumber g_user_script_id_generator;
+
+bool UrlMatchesGlobs(const std::vector<std::string>* globs,
+ const GURL& url) {
+ for (std::vector<std::string>::const_iterator glob = globs->begin();
+ glob != globs->end(); ++glob) {
+ if (base::MatchPattern(url.spec(), *glob))
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace
+
+namespace extensions {
+
+// The bitmask for valid user script injectable schemes used by URLPattern.
+enum {
+ kValidUserScriptSchemes = URLPattern::SCHEME_CHROMEUI |
+ URLPattern::SCHEME_HTTP |
+ URLPattern::SCHEME_HTTPS |
+ URLPattern::SCHEME_FILE |
+ URLPattern::SCHEME_FTP
+};
+
+// static
+const char UserScript::kFileExtension[] = ".user.js";
+
+
+// static
+int UserScript::GenerateUserScriptID() {
+ return g_user_script_id_generator.GetNext();
+}
+
+bool UserScript::IsURLUserScript(const GURL& url,
+ const std::string& mime_type) {
+ return base::EndsWith(url.ExtractFileName(), kFileExtension,
+ base::CompareCase::INSENSITIVE_ASCII) &&
+ mime_type != "text/html";
+}
+
+// static
+int UserScript::ValidUserScriptSchemes(bool canExecuteScriptEverywhere) {
+ if (canExecuteScriptEverywhere)
+ return URLPattern::SCHEME_ALL;
+ int valid_schemes = kValidUserScriptSchemes;
+ if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kExtensionsOnChromeURLs)) {
+ valid_schemes &= ~URLPattern::SCHEME_CHROMEUI;
+ }
+ return valid_schemes;
+}
+
+UserScript::File::File(const base::FilePath& extension_root,
+ const base::FilePath& relative_path,
+ const GURL& url)
+ : extension_root_(extension_root),
+ relative_path_(relative_path),
+ url_(url) {
+}
+
+UserScript::File::File() {}
+
+UserScript::File::File(const File& other) = default;
+
+UserScript::File::~File() {}
+
+UserScript::UserScript()
+ : run_location_(DOCUMENT_IDLE),
+ consumer_instance_type_(TAB),
+ user_script_id_(-1),
+ emulate_greasemonkey_(false),
+ match_all_frames_(false),
+ match_about_blank_(false),
+ incognito_enabled_(false) {}
+
+UserScript::UserScript(const UserScript& other) = default;
+
+UserScript::~UserScript() {
+}
+
+void UserScript::add_url_pattern(const URLPattern& pattern) {
+ url_set_.AddPattern(pattern);
+}
+
+void UserScript::add_exclude_url_pattern(const URLPattern& pattern) {
+ exclude_url_set_.AddPattern(pattern);
+}
+
+bool UserScript::MatchesURL(const GURL& url) const {
+ if (!url_set_.is_empty()) {
+ if (!url_set_.MatchesURL(url))
+ return false;
+ }
+
+ if (!exclude_url_set_.is_empty()) {
+ if (exclude_url_set_.MatchesURL(url))
+ return false;
+ }
+
+ if (!globs_.empty()) {
+ if (!UrlMatchesGlobs(&globs_, url))
+ return false;
+ }
+
+ if (!exclude_globs_.empty()) {
+ if (UrlMatchesGlobs(&exclude_globs_, url))
+ return false;
+ }
+
+ return true;
+}
+
+void UserScript::File::Pickle(base::Pickle* pickle) const {
+ pickle->WriteString(url_.spec());
+ // Do not write path. It's not needed in the renderer.
+ // Do not write content. It will be serialized by other means.
+}
+
+void UserScript::File::Unpickle(const base::Pickle& pickle,
+ base::PickleIterator* iter) {
+ // Read the url from the pickle.
+ std::string url;
+ CHECK(iter->ReadString(&url));
+ set_url(GURL(url));
+}
+
+void UserScript::Pickle(base::Pickle* pickle) const {
+ // Write the simple types to the pickle.
+ pickle->WriteInt(run_location());
+ pickle->WriteInt(user_script_id_);
+ pickle->WriteBool(emulate_greasemonkey());
+ pickle->WriteBool(match_all_frames());
+ pickle->WriteBool(match_about_blank());
+ pickle->WriteBool(is_incognito_enabled());
+
+ PickleHostID(pickle, host_id_);
+ pickle->WriteInt(consumer_instance_type());
+ PickleGlobs(pickle, globs_);
+ PickleGlobs(pickle, exclude_globs_);
+ PickleURLPatternSet(pickle, url_set_);
+ PickleURLPatternSet(pickle, exclude_url_set_);
+ PickleScripts(pickle, js_scripts_);
+ PickleScripts(pickle, css_scripts_);
+}
+
+void UserScript::PickleGlobs(base::Pickle* pickle,
+ const std::vector<std::string>& globs) const {
+ pickle->WriteUInt32(globs.size());
+ for (std::vector<std::string>::const_iterator glob = globs.begin();
+ glob != globs.end(); ++glob) {
+ pickle->WriteString(*glob);
+ }
+}
+
+void UserScript::PickleHostID(base::Pickle* pickle,
+ const HostID& host_id) const {
+ pickle->WriteInt(host_id.type());
+ pickle->WriteString(host_id.id());
+}
+
+void UserScript::PickleURLPatternSet(base::Pickle* pickle,
+ const URLPatternSet& pattern_list) const {
+ pickle->WriteUInt32(pattern_list.patterns().size());
+ for (URLPatternSet::const_iterator pattern = pattern_list.begin();
+ pattern != pattern_list.end(); ++pattern) {
+ pickle->WriteInt(pattern->valid_schemes());
+ pickle->WriteString(pattern->GetAsString());
+ }
+}
+
+void UserScript::PickleScripts(base::Pickle* pickle,
+ const FileList& scripts) const {
+ pickle->WriteUInt32(scripts.size());
+ for (FileList::const_iterator file = scripts.begin();
+ file != scripts.end(); ++file) {
+ file->Pickle(pickle);
+ }
+}
+
+void UserScript::Unpickle(const base::Pickle& pickle,
+ base::PickleIterator* iter) {
+ // Read the run location.
+ int run_location = 0;
+ CHECK(iter->ReadInt(&run_location));
+ CHECK(run_location >= 0 && run_location < RUN_LOCATION_LAST);
+ run_location_ = static_cast<RunLocation>(run_location);
+
+ CHECK(iter->ReadInt(&user_script_id_));
+ CHECK(iter->ReadBool(&emulate_greasemonkey_));
+ CHECK(iter->ReadBool(&match_all_frames_));
+ CHECK(iter->ReadBool(&match_about_blank_));
+ CHECK(iter->ReadBool(&incognito_enabled_));
+
+ UnpickleHostID(pickle, iter, &host_id_);
+
+ int consumer_instance_type = 0;
+ CHECK(iter->ReadInt(&consumer_instance_type));
+ consumer_instance_type_ =
+ static_cast<ConsumerInstanceType>(consumer_instance_type);
+
+ UnpickleGlobs(pickle, iter, &globs_);
+ UnpickleGlobs(pickle, iter, &exclude_globs_);
+ UnpickleURLPatternSet(pickle, iter, &url_set_);
+ UnpickleURLPatternSet(pickle, iter, &exclude_url_set_);
+ UnpickleScripts(pickle, iter, &js_scripts_);
+ UnpickleScripts(pickle, iter, &css_scripts_);
+}
+
+void UserScript::UnpickleGlobs(const base::Pickle& pickle,
+ base::PickleIterator* iter,
+ std::vector<std::string>* globs) {
+ uint32_t num_globs = 0;
+ CHECK(iter->ReadUInt32(&num_globs));
+ globs->clear();
+ for (uint32_t i = 0; i < num_globs; ++i) {
+ std::string glob;
+ CHECK(iter->ReadString(&glob));
+ globs->push_back(glob);
+ }
+}
+
+void UserScript::UnpickleHostID(const base::Pickle& pickle,
+ base::PickleIterator* iter,
+ HostID* host_id) {
+ int type = 0;
+ std::string id;
+ CHECK(iter->ReadInt(&type));
+ CHECK(iter->ReadString(&id));
+ *host_id = HostID(static_cast<HostID::HostType>(type), id);
+}
+
+void UserScript::UnpickleURLPatternSet(const base::Pickle& pickle,
+ base::PickleIterator* iter,
+ URLPatternSet* pattern_list) {
+ uint32_t num_patterns = 0;
+ CHECK(iter->ReadUInt32(&num_patterns));
+
+ pattern_list->ClearPatterns();
+ for (uint32_t i = 0; i < num_patterns; ++i) {
+ int valid_schemes;
+ CHECK(iter->ReadInt(&valid_schemes));
+
+ std::string pattern_str;
+ CHECK(iter->ReadString(&pattern_str));
+
+ URLPattern pattern(kValidUserScriptSchemes);
+ URLPattern::ParseResult result = pattern.Parse(pattern_str);
+ CHECK(URLPattern::PARSE_SUCCESS == result) <<
+ URLPattern::GetParseResultString(result) << " " << pattern_str.c_str();
+
+ pattern.SetValidSchemes(valid_schemes);
+ pattern_list->AddPattern(pattern);
+ }
+}
+
+void UserScript::UnpickleScripts(const base::Pickle& pickle,
+ base::PickleIterator* iter,
+ FileList* scripts) {
+ uint32_t num_files = 0;
+ CHECK(iter->ReadUInt32(&num_files));
+ scripts->clear();
+ for (uint32_t i = 0; i < num_files; ++i) {
+ File file;
+ file.Unpickle(pickle, iter);
+ scripts->push_back(file);
+ }
+}
+
+bool operator<(const UserScript& script1, const UserScript& script2) {
+ // The only kind of script that should be compared is the kind that has its
+ // IDs initialized to a meaningful value.
+ DCHECK(script1.id() != -1 && script2.id() != -1);
+ return script1.id() < script2.id();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/user_script.h b/chromium/extensions/common/user_script.h
new file mode 100644
index 00000000000..40ad3303a4e
--- /dev/null
+++ b/chromium/extensions/common/user_script.h
@@ -0,0 +1,329 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_USER_SCRIPT_H_
+#define EXTENSIONS_COMMON_USER_SCRIPT_H_
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/strings/string_piece.h"
+#include "extensions/common/host_id.h"
+#include "extensions/common/url_pattern.h"
+#include "extensions/common/url_pattern_set.h"
+#include "url/gurl.h"
+
+namespace base {
+class Pickle;
+class PickleIterator;
+}
+
+namespace extensions {
+
+// Represents a user script, either a standalone one, or one that is part of an
+// extension.
+class UserScript {
+ public:
+ // The file extension for standalone user scripts.
+ static const char kFileExtension[];
+
+ static int GenerateUserScriptID();
+
+ // Check if a URL should be treated as a user script and converted to an
+ // extension.
+ static bool IsURLUserScript(const GURL& url, const std::string& mime_type);
+
+ // Get the valid user script schemes for the current process. If
+ // canExecuteScriptEverywhere is true, this will return ALL_SCHEMES.
+ static int ValidUserScriptSchemes(bool canExecuteScriptEverywhere = false);
+
+ // TODO(rdevlin.cronin) This and RunLocataion don't really belong here, since
+ // they are used for more than UserScripts (e.g., tabs.executeScript()).
+ // The type of injected script.
+ enum InjectionType {
+ // A content script specified in the extension's manifest.
+ CONTENT_SCRIPT,
+ // A script injected via, e.g. tabs.executeScript().
+ PROGRAMMATIC_SCRIPT
+ };
+ // The last type of injected script; used for enum verification in IPC.
+ // Update this if you add more injected script types!
+ static const InjectionType INJECTION_TYPE_LAST = PROGRAMMATIC_SCRIPT;
+
+ // Locations that user scripts can be run inside the document.
+ // The three run locations must strictly follow each other in both load order
+ // (i.e., start *always* comes before end) and numerically, as we use
+ // arithmetic checking (e.g., curr == last + 1). So, no bitmasks here!!
+ enum RunLocation {
+ UNDEFINED,
+ DOCUMENT_START, // After the documentElement is created, but before
+ // anything else happens.
+ DOCUMENT_END, // After the entire document is parsed. Same as
+ // DOMContentLoaded.
+ DOCUMENT_IDLE, // Sometime after DOMContentLoaded, as soon as the document
+ // is "idle". Currently this uses the simple heuristic of:
+ // min(DOM_CONTENT_LOADED + TIMEOUT, ONLOAD), but no
+ // particular injection point is guaranteed.
+ RUN_DEFERRED, // The user script's injection was deferred for permissions
+ // reasons, and was executed at a later time.
+ BROWSER_DRIVEN, // The user script will be injected when triggered by an
+ // IPC in the browser process.
+ RUN_LOCATION_LAST // Leave this as the last item.
+ };
+
+ // Holds actual script file info.
+ class File {
+ public:
+ File(const base::FilePath& extension_root,
+ const base::FilePath& relative_path,
+ const GURL& url);
+ File();
+ File(const File& other);
+ ~File();
+
+ const base::FilePath& extension_root() const { return extension_root_; }
+ const base::FilePath& relative_path() const { return relative_path_; }
+
+ const GURL& url() const { return url_; }
+ void set_url(const GURL& url) { url_ = url; }
+
+ // If external_content_ is set returns it as content otherwise it returns
+ // content_
+ const base::StringPiece GetContent() const {
+ if (external_content_.data())
+ return external_content_;
+ else
+ return content_;
+ }
+ void set_external_content(const base::StringPiece& content) {
+ external_content_ = content;
+ }
+ void set_content(const base::StringPiece& content) {
+ content_.assign(content.begin(), content.end());
+ }
+
+ // Serialization support. The content and FilePath members will not be
+ // serialized!
+ void Pickle(base::Pickle* pickle) const;
+ void Unpickle(const base::Pickle& pickle, base::PickleIterator* iter);
+
+ private:
+ // Where the script file lives on the disk. We keep the path split so that
+ // it can be localized at will.
+ base::FilePath extension_root_;
+ base::FilePath relative_path_;
+
+ // The url to this scipt file.
+ GURL url_;
+
+ // The script content. It can be set to either loaded_content_ or
+ // externally allocated string.
+ base::StringPiece external_content_;
+
+ // Set when the content is loaded by LoadContent
+ std::string content_;
+ };
+
+ typedef std::vector<File> FileList;
+
+ // Type of a API consumer instance that user scripts will be injected on.
+ enum ConsumerInstanceType { TAB, WEBVIEW };
+
+ // Constructor. Default the run location to document end, which is like
+ // Greasemonkey and probably more useful for typical scripts.
+ UserScript();
+ UserScript(const UserScript& other);
+ ~UserScript();
+
+ const std::string& name_space() const { return name_space_; }
+ void set_name_space(const std::string& name_space) {
+ name_space_ = name_space;
+ }
+
+ const std::string& name() const { return name_; }
+ void set_name(const std::string& name) { name_ = name; }
+
+ const std::string& version() const { return version_; }
+ void set_version(const std::string& version) {
+ version_ = version;
+ }
+
+ const std::string& description() const { return description_; }
+ void set_description(const std::string& description) {
+ description_ = description;
+ }
+
+ // The place in the document to run the script.
+ RunLocation run_location() const { return run_location_; }
+ void set_run_location(RunLocation location) { run_location_ = location; }
+
+ // Whether to emulate greasemonkey when running this script.
+ bool emulate_greasemonkey() const { return emulate_greasemonkey_; }
+ void set_emulate_greasemonkey(bool val) { emulate_greasemonkey_ = val; }
+
+ // Whether to match all frames, or only the top one.
+ bool match_all_frames() const { return match_all_frames_; }
+ void set_match_all_frames(bool val) { match_all_frames_ = val; }
+
+ // Whether to match about:blank and about:srcdoc.
+ bool match_about_blank() const { return match_about_blank_; }
+ void set_match_about_blank(bool val) { match_about_blank_ = val; }
+
+ // The globs, if any, that determine which pages this script runs against.
+ // These are only used with "standalone" Greasemonkey-like user scripts.
+ const std::vector<std::string>& globs() const { return globs_; }
+ void add_glob(const std::string& glob) { globs_.push_back(glob); }
+ void clear_globs() { globs_.clear(); }
+ const std::vector<std::string>& exclude_globs() const {
+ return exclude_globs_;
+ }
+ void add_exclude_glob(const std::string& glob) {
+ exclude_globs_.push_back(glob);
+ }
+ void clear_exclude_globs() { exclude_globs_.clear(); }
+
+ // The URLPatterns, if any, that determine which pages this script runs
+ // against.
+ const URLPatternSet& url_patterns() const { return url_set_; }
+ void add_url_pattern(const URLPattern& pattern);
+ const URLPatternSet& exclude_url_patterns() const {
+ return exclude_url_set_;
+ }
+ void add_exclude_url_pattern(const URLPattern& pattern);
+
+ // List of js scripts for this user script
+ FileList& js_scripts() { return js_scripts_; }
+ const FileList& js_scripts() const { return js_scripts_; }
+
+ // List of css scripts for this user script
+ FileList& css_scripts() { return css_scripts_; }
+ const FileList& css_scripts() const { return css_scripts_; }
+
+ const std::string& extension_id() const { return host_id_.id(); }
+
+ const HostID& host_id() const { return host_id_; }
+ void set_host_id(const HostID& host_id) { host_id_ = host_id; }
+
+ const ConsumerInstanceType& consumer_instance_type() const {
+ return consumer_instance_type_;
+ }
+ void set_consumer_instance_type(
+ const ConsumerInstanceType& consumer_instance_type) {
+ consumer_instance_type_ = consumer_instance_type;
+ }
+
+ int id() const { return user_script_id_; }
+ void set_id(int id) { user_script_id_ = id; }
+
+ bool is_incognito_enabled() const { return incognito_enabled_; }
+ void set_incognito_enabled(bool enabled) { incognito_enabled_ = enabled; }
+
+ bool is_standalone() const { return extension_id().empty(); }
+
+ // Returns true if the script should be applied to the specified URL, false
+ // otherwise.
+ bool MatchesURL(const GURL& url) const;
+
+ // Serialize the UserScript into a pickle. The content of the scripts and
+ // paths to UserScript::Files will not be serialized!
+ void Pickle(base::Pickle* pickle) const;
+
+ // Deserialize the script from a pickle. Note that this always succeeds
+ // because presumably we were the one that pickled it, and we did it
+ // correctly.
+ void Unpickle(const base::Pickle& pickle, base::PickleIterator* iter);
+
+ private:
+ // base::Pickle helper functions used to pickle the individual types of
+ // components.
+ void PickleGlobs(base::Pickle* pickle,
+ const std::vector<std::string>& globs) const;
+ void PickleHostID(base::Pickle* pickle, const HostID& host_id) const;
+ void PickleURLPatternSet(base::Pickle* pickle,
+ const URLPatternSet& pattern_list) const;
+ void PickleScripts(base::Pickle* pickle, const FileList& scripts) const;
+
+ // Unpickle helper functions used to unpickle individual types of components.
+ void UnpickleGlobs(const base::Pickle& pickle,
+ base::PickleIterator* iter,
+ std::vector<std::string>* globs);
+ void UnpickleHostID(const base::Pickle& pickle,
+ base::PickleIterator* iter,
+ HostID* host_id);
+ void UnpickleURLPatternSet(const base::Pickle& pickle,
+ base::PickleIterator* iter,
+ URLPatternSet* pattern_list);
+ void UnpickleScripts(const base::Pickle& pickle,
+ base::PickleIterator* iter,
+ FileList* scripts);
+
+ // The location to run the script inside the document.
+ RunLocation run_location_;
+
+ // The namespace of the script. This is used by Greasemonkey in the same way
+ // as XML namespaces. Only used when parsing Greasemonkey-style scripts.
+ std::string name_space_;
+
+ // The script's name. Only used when parsing Greasemonkey-style scripts.
+ std::string name_;
+
+ // A longer description. Only used when parsing Greasemonkey-style scripts.
+ std::string description_;
+
+ // A version number of the script. Only used when parsing Greasemonkey-style
+ // scripts.
+ std::string version_;
+
+ // Greasemonkey-style globs that determine pages to inject the script into.
+ // These are only used with standalone scripts.
+ std::vector<std::string> globs_;
+ std::vector<std::string> exclude_globs_;
+
+ // URLPatterns that determine pages to inject the script into. These are
+ // only used with scripts that are part of extensions.
+ URLPatternSet url_set_;
+ URLPatternSet exclude_url_set_;
+
+ // List of js scripts defined in content_scripts
+ FileList js_scripts_;
+
+ // List of css scripts defined in content_scripts
+ FileList css_scripts_;
+
+ // The ID of the host this script is a part of. The |ID| of the
+ // |host_id| can be empty if the script is a "standlone" user script.
+ HostID host_id_;
+
+ // The type of the consumer instance that the script will be injected.
+ ConsumerInstanceType consumer_instance_type_;
+
+ // The globally-unique id associated with this user script. Defaults to
+ // -1 for invalid.
+ int user_script_id_;
+
+ // Whether we should try to emulate Greasemonkey's APIs when running this
+ // script.
+ bool emulate_greasemonkey_;
+
+ // Whether the user script should run in all frames, or only just the top one.
+ // Defaults to false.
+ bool match_all_frames_;
+
+ // Whether the user script should run in about:blank and about:srcdoc as well.
+ // Defaults to false.
+ bool match_about_blank_;
+
+ // True if the script should be injected into an incognito tab.
+ bool incognito_enabled_;
+};
+
+// For storing UserScripts with unique IDs in sets.
+bool operator<(const UserScript& script1, const UserScript& script2);
+
+typedef std::vector<UserScript> UserScriptList;
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_USER_SCRIPT_H_
diff --git a/chromium/extensions/common/user_script_unittest.cc b/chromium/extensions/common/user_script_unittest.cc
new file mode 100644
index 00000000000..5923f4c003c
--- /dev/null
+++ b/chromium/extensions/common/user_script_unittest.cc
@@ -0,0 +1,233 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/files/file_path.h"
+#include "base/pickle.h"
+#include "extensions/common/user_script.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+static const int kAllSchemes =
+ URLPattern::SCHEME_HTTP |
+ URLPattern::SCHEME_HTTPS |
+ URLPattern::SCHEME_FILE |
+ URLPattern::SCHEME_FTP |
+ URLPattern::SCHEME_CHROMEUI;
+
+TEST(ExtensionUserScriptTest, Glob_HostString) {
+ UserScript script;
+ script.add_glob("*mail.google.com*");
+ script.add_glob("*mail.yahoo.com*");
+ script.add_glob("*mail.msn.com*");
+ EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com")));
+ EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com/foo")));
+ EXPECT_TRUE(script.MatchesURL(GURL("https://mail.google.com/foo")));
+ EXPECT_TRUE(script.MatchesURL(GURL("ftp://mail.google.com/foo")));
+ EXPECT_TRUE(script.MatchesURL(GURL("http://woo.mail.google.com/foo")));
+ EXPECT_TRUE(script.MatchesURL(GURL("http://mail.yahoo.com/bar")));
+ EXPECT_TRUE(script.MatchesURL(GURL("http://mail.msn.com/baz")));
+ EXPECT_FALSE(script.MatchesURL(GURL("http://www.hotmail.com")));
+
+ script.add_exclude_glob("*foo*");
+ EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com")));
+ EXPECT_FALSE(script.MatchesURL(GURL("http://mail.google.com/foo")));
+}
+
+TEST(ExtensionUserScriptTest, Glob_TrailingSlash) {
+ UserScript script;
+ script.add_glob("*mail.google.com/");
+ // GURL normalizes the URL to have a trailing "/"
+ EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com")));
+ EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com/")));
+ EXPECT_FALSE(script.MatchesURL(GURL("http://mail.google.com/foo")));
+}
+
+TEST(ExtensionUserScriptTest, Glob_TrailingSlashStar) {
+ UserScript script;
+ script.add_glob("http://mail.google.com/*");
+ // GURL normalizes the URL to have a trailing "/"
+ EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com")));
+ EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com/foo")));
+ EXPECT_FALSE(script.MatchesURL(GURL("https://mail.google.com/foo")));
+}
+
+TEST(ExtensionUserScriptTest, Glob_Star) {
+ UserScript script;
+ script.add_glob("*");
+ EXPECT_TRUE(script.MatchesURL(GURL("http://foo.com/bar")));
+ EXPECT_TRUE(script.MatchesURL(GURL("http://hot.com/dog")));
+ EXPECT_TRUE(script.MatchesURL(GURL("https://hot.com/dog")));
+ EXPECT_TRUE(script.MatchesURL(GURL("file:///foo/bar")));
+ EXPECT_TRUE(script.MatchesURL(GURL("file://localhost/foo/bar")));
+}
+
+TEST(ExtensionUserScriptTest, Glob_StringAnywhere) {
+ UserScript script;
+ script.add_glob("*foo*");
+ EXPECT_TRUE(script.MatchesURL(GURL("http://foo.com/bar")));
+ EXPECT_TRUE(script.MatchesURL(GURL("http://baz.org/foo/bar")));
+ EXPECT_FALSE(script.MatchesURL(GURL("http://baz.org")));
+}
+
+TEST(ExtensionUserScriptTest, UrlPattern) {
+ URLPattern pattern(kAllSchemes);
+ ASSERT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://*/foo*"));
+
+ UserScript script;
+ script.add_url_pattern(pattern);
+ EXPECT_TRUE(script.MatchesURL(GURL("http://monkey.com/foobar")));
+ EXPECT_FALSE(script.MatchesURL(GURL("http://monkey.com/hotdog")));
+
+ // NOTE: URLPattern is tested more extensively in url_pattern_unittest.cc.
+}
+
+TEST(ExtensionUserScriptTest, ExcludeUrlPattern) {
+ UserScript script;
+
+ URLPattern pattern(kAllSchemes);
+ ASSERT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://*.nytimes.com/*"));
+ script.add_url_pattern(pattern);
+
+ URLPattern exclude(kAllSchemes);
+ ASSERT_EQ(URLPattern::PARSE_SUCCESS, exclude.Parse("*://*/*business*"));
+ script.add_exclude_url_pattern(exclude);
+
+ EXPECT_TRUE(script.MatchesURL(GURL("http://www.nytimes.com/health")));
+ EXPECT_FALSE(script.MatchesURL(GURL("http://www.nytimes.com/business")));
+ EXPECT_TRUE(script.MatchesURL(GURL("http://business.nytimes.com")));
+}
+
+TEST(ExtensionUserScriptTest, UrlPatternAndIncludeGlobs) {
+ UserScript script;
+
+ URLPattern pattern(kAllSchemes);
+ ASSERT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://*.nytimes.com/*"));
+ script.add_url_pattern(pattern);
+
+ script.add_glob("*nytimes.com/???s/*");
+
+ EXPECT_TRUE(script.MatchesURL(GURL("http://www.nytimes.com/arts/1.html")));
+ EXPECT_TRUE(script.MatchesURL(GURL("http://www.nytimes.com/jobs/1.html")));
+ EXPECT_FALSE(script.MatchesURL(GURL("http://www.nytimes.com/sports/1.html")));
+}
+
+TEST(ExtensionUserScriptTest, UrlPatternAndExcludeGlobs) {
+ UserScript script;
+
+ URLPattern pattern(kAllSchemes);
+ ASSERT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://*.nytimes.com/*"));
+ script.add_url_pattern(pattern);
+
+ script.add_exclude_glob("*science*");
+
+ EXPECT_TRUE(script.MatchesURL(GURL("http://www.nytimes.com")));
+ EXPECT_FALSE(script.MatchesURL(GURL("http://science.nytimes.com")));
+ EXPECT_FALSE(script.MatchesURL(GURL("http://www.nytimes.com/science")));
+}
+
+TEST(ExtensionUserScriptTest, UrlPatternGlobInteraction) {
+ // If there are both, match intersection(union(globs), union(urlpatterns)).
+ UserScript script;
+
+ URLPattern pattern(kAllSchemes);
+ ASSERT_EQ(URLPattern::PARSE_SUCCESS,
+ pattern.Parse("http://www.google.com/*"));
+ script.add_url_pattern(pattern);
+
+ script.add_glob("*bar*");
+
+ // No match, because it doesn't match the glob.
+ EXPECT_FALSE(script.MatchesURL(GURL("http://www.google.com/foo")));
+
+ script.add_exclude_glob("*baz*");
+
+ // No match, because it matches the exclude glob.
+ EXPECT_FALSE(script.MatchesURL(GURL("http://www.google.com/baz")));
+
+ // Match, because it matches the glob, doesn't match the exclude glob.
+ EXPECT_TRUE(script.MatchesURL(GURL("http://www.google.com/bar")));
+
+ // Try with just a single exclude glob.
+ script.clear_globs();
+ EXPECT_TRUE(script.MatchesURL(GURL("http://www.google.com/foo")));
+
+ // Try with no globs or exclude globs.
+ script.clear_exclude_globs();
+ EXPECT_TRUE(script.MatchesURL(GURL("http://www.google.com/foo")));
+}
+
+TEST(ExtensionUserScriptTest, Pickle) {
+ URLPattern pattern1(kAllSchemes);
+ URLPattern pattern2(kAllSchemes);
+ URLPattern exclude1(kAllSchemes);
+ URLPattern exclude2(kAllSchemes);
+ ASSERT_EQ(URLPattern::PARSE_SUCCESS, pattern1.Parse("http://*/foo*"));
+ ASSERT_EQ(URLPattern::PARSE_SUCCESS, pattern2.Parse("http://bar/baz*"));
+ ASSERT_EQ(URLPattern::PARSE_SUCCESS, exclude1.Parse("*://*/*bar"));
+ ASSERT_EQ(URLPattern::PARSE_SUCCESS, exclude2.Parse("https://*/*"));
+
+ UserScript script1;
+ script1.js_scripts().push_back(UserScript::File(
+ base::FilePath(FILE_PATH_LITERAL("c:\\foo\\")),
+ base::FilePath(FILE_PATH_LITERAL("foo.user.js")),
+ GURL("chrome-extension://abc/foo.user.js")));
+ script1.css_scripts().push_back(UserScript::File(
+ base::FilePath(FILE_PATH_LITERAL("c:\\foo\\")),
+ base::FilePath(FILE_PATH_LITERAL("foo.user.css")),
+ GURL("chrome-extension://abc/foo.user.css")));
+ script1.css_scripts().push_back(UserScript::File(
+ base::FilePath(FILE_PATH_LITERAL("c:\\foo\\")),
+ base::FilePath(FILE_PATH_LITERAL("foo2.user.css")),
+ GURL("chrome-extension://abc/foo2.user.css")));
+ script1.set_run_location(UserScript::DOCUMENT_START);
+
+ script1.add_url_pattern(pattern1);
+ script1.add_url_pattern(pattern2);
+ script1.add_exclude_url_pattern(exclude1);
+ script1.add_exclude_url_pattern(exclude2);
+
+ const int64_t kId = 12;
+ script1.set_id(kId);
+ const std::string kExtensionId = "foo";
+ HostID id(HostID::EXTENSIONS, kExtensionId);
+ script1.set_host_id(id);
+
+ base::Pickle pickle;
+ script1.Pickle(&pickle);
+
+ base::PickleIterator iter(pickle);
+ UserScript script2;
+ script2.Unpickle(pickle, &iter);
+
+ EXPECT_EQ(1U, script2.js_scripts().size());
+ EXPECT_EQ(script1.js_scripts()[0].url(), script2.js_scripts()[0].url());
+
+ EXPECT_EQ(2U, script2.css_scripts().size());
+ for (size_t i = 0; i < script2.js_scripts().size(); ++i) {
+ EXPECT_EQ(script1.css_scripts()[i].url(), script2.css_scripts()[i].url());
+ }
+
+ ASSERT_EQ(script1.globs().size(), script2.globs().size());
+ for (size_t i = 0; i < script1.globs().size(); ++i) {
+ EXPECT_EQ(script1.globs()[i], script2.globs()[i]);
+ }
+
+ ASSERT_EQ(script1.url_patterns(), script2.url_patterns());
+ ASSERT_EQ(script1.exclude_url_patterns(), script2.exclude_url_patterns());
+
+ EXPECT_EQ(kExtensionId, script2.extension_id());
+ EXPECT_EQ(kId, script2.id());
+}
+
+TEST(ExtensionUserScriptTest, Defaults) {
+ UserScript script;
+ ASSERT_EQ(UserScript::DOCUMENT_IDLE, script.run_location());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/value_builder.cc b/chromium/extensions/common/value_builder.cc
new file mode 100644
index 00000000000..efc2e44fad3
--- /dev/null
+++ b/chromium/extensions/common/value_builder.cc
@@ -0,0 +1,103 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/value_builder.h"
+
+#include <utility>
+
+#include "base/json/json_writer.h"
+#include "base/values.h"
+
+namespace extensions {
+
+// DictionaryBuilder
+
+DictionaryBuilder::DictionaryBuilder() : dict_(new base::DictionaryValue) {}
+
+DictionaryBuilder::DictionaryBuilder(const base::DictionaryValue& init)
+ : dict_(init.DeepCopy()) {}
+
+DictionaryBuilder::~DictionaryBuilder() {}
+
+std::string DictionaryBuilder::ToJSON() const {
+ std::string json;
+ base::JSONWriter::WriteWithOptions(
+ *dict_, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
+ return json;
+}
+
+DictionaryBuilder& DictionaryBuilder::Set(const std::string& path,
+ int in_value) {
+ dict_->SetWithoutPathExpansion(path, new base::FundamentalValue(in_value));
+ return *this;
+}
+
+DictionaryBuilder& DictionaryBuilder::Set(const std::string& path,
+ double in_value) {
+ dict_->SetWithoutPathExpansion(path, new base::FundamentalValue(in_value));
+ return *this;
+}
+
+DictionaryBuilder& DictionaryBuilder::Set(const std::string& path,
+ const std::string& in_value) {
+ dict_->SetWithoutPathExpansion(path, new base::StringValue(in_value));
+ return *this;
+}
+
+DictionaryBuilder& DictionaryBuilder::Set(const std::string& path,
+ const base::string16& in_value) {
+ dict_->SetWithoutPathExpansion(path, new base::StringValue(in_value));
+ return *this;
+}
+
+DictionaryBuilder& DictionaryBuilder::Set(const std::string& path,
+ scoped_ptr<base::Value> in_value) {
+ dict_->SetWithoutPathExpansion(path, std::move(in_value));
+ return *this;
+}
+
+DictionaryBuilder& DictionaryBuilder::SetBoolean(
+ const std::string& path, bool in_value) {
+ dict_->SetWithoutPathExpansion(path, new base::FundamentalValue(in_value));
+ return *this;
+}
+
+// ListBuilder
+
+ListBuilder::ListBuilder() : list_(new base::ListValue) {}
+ListBuilder::ListBuilder(const base::ListValue& init) : list_(init.DeepCopy()) {
+}
+ListBuilder::~ListBuilder() {}
+
+ListBuilder& ListBuilder::Append(int in_value) {
+ list_->Append(new base::FundamentalValue(in_value));
+ return *this;
+}
+
+ListBuilder& ListBuilder::Append(double in_value) {
+ list_->Append(new base::FundamentalValue(in_value));
+ return *this;
+}
+
+ListBuilder& ListBuilder::Append(const std::string& in_value) {
+ list_->Append(new base::StringValue(in_value));
+ return *this;
+}
+
+ListBuilder& ListBuilder::Append(const base::string16& in_value) {
+ list_->Append(new base::StringValue(in_value));
+ return *this;
+}
+
+ListBuilder& ListBuilder::Append(scoped_ptr<base::Value> in_value) {
+ list_->Append(std::move(in_value));
+ return *this;
+}
+
+ListBuilder& ListBuilder::AppendBoolean(bool in_value) {
+ list_->Append(new base::FundamentalValue(in_value));
+ return *this;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/value_builder.h b/chromium/extensions/common/value_builder.h
new file mode 100644
index 00000000000..27c4e0165c6
--- /dev/null
+++ b/chromium/extensions/common/value_builder.h
@@ -0,0 +1,99 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file provides a builders for DictionaryValue and ListValue. These
+// aren't specific to extensions and could move up to base/ if there's interest
+// from other sub-projects.
+//
+// The pattern is to write:
+//
+// scoped_ptr<BuiltType> result(FooBuilder()
+// .Set(args)
+// .Set(args)
+// .Build());
+//
+// The Build() method invalidates its builder, and returns ownership of the
+// built value.
+//
+// These objects are intended to be used as temporaries rather than stored
+// anywhere, so the use of non-const reference parameters is likely to cause
+// less confusion than usual.
+
+#ifndef EXTENSIONS_COMMON_VALUE_BUILDER_H_
+#define EXTENSIONS_COMMON_VALUE_BUILDER_H_
+
+#include <string>
+#include <utility>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+class Value;
+}
+
+namespace extensions {
+
+class ListBuilder;
+
+class DictionaryBuilder {
+ public:
+ DictionaryBuilder();
+ explicit DictionaryBuilder(const base::DictionaryValue& init);
+ ~DictionaryBuilder();
+
+ // Can only be called once, after which it's invalid to use the builder.
+ scoped_ptr<base::DictionaryValue> Build() { return std::move(dict_); }
+
+ // Immediately serializes the current state to JSON. Can be called as many
+ // times as you like.
+ std::string ToJSON() const;
+
+ DictionaryBuilder& Set(const std::string& path, int in_value);
+ DictionaryBuilder& Set(const std::string& path, double in_value);
+ DictionaryBuilder& Set(const std::string& path, const std::string& in_value);
+ DictionaryBuilder& Set(const std::string& path,
+ const base::string16& in_value);
+ DictionaryBuilder& Set(const std::string& path,
+ scoped_ptr<base::Value> in_value);
+
+ // Named differently because overload resolution is too eager to
+ // convert implicitly to bool.
+ DictionaryBuilder& SetBoolean(const std::string& path, bool in_value);
+
+ private:
+ scoped_ptr<base::DictionaryValue> dict_;
+};
+
+class ListBuilder {
+ public:
+ ListBuilder();
+ explicit ListBuilder(const base::ListValue& init);
+ ~ListBuilder();
+
+ // Can only be called once, after which it's invalid to use the builder.
+ scoped_ptr<base::ListValue> Build() { return std::move(list_); }
+
+ ListBuilder& Append(int in_value);
+ ListBuilder& Append(double in_value);
+ ListBuilder& Append(const std::string& in_value);
+ ListBuilder& Append(const base::string16& in_value);
+ ListBuilder& Append(scoped_ptr<base::Value> in_value);
+
+ // Named differently because overload resolution is too eager to
+ // convert implicitly to bool.
+ ListBuilder& AppendBoolean(bool in_value);
+
+ private:
+ scoped_ptr<base::ListValue> list_;
+
+ DISALLOW_COPY_AND_ASSIGN(ListBuilder);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_VALUE_BUILDER_H_
diff --git a/chromium/extensions/common/value_builder_unittest.cc b/chromium/extensions/common/value_builder_unittest.cc
new file mode 100644
index 00000000000..1f1c14e1abd
--- /dev/null
+++ b/chromium/extensions/common/value_builder_unittest.cc
@@ -0,0 +1,36 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <utility>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "extensions/common/value_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ValueBuilderTest = testing::Test;
+
+namespace extensions {
+
+TEST(ValueBuilderTest, Basic) {
+ ListBuilder permission_list;
+ permission_list.Append("tabs").Append("history");
+
+ scoped_ptr<base::DictionaryValue> settings(new base::DictionaryValue);
+
+ ASSERT_FALSE(settings->GetList("permissions", nullptr));
+ settings =
+ DictionaryBuilder().Set("permissions", permission_list.Build()).Build();
+ base::ListValue* list_value;
+ ASSERT_TRUE(settings->GetList("permissions", &list_value));
+
+ ASSERT_EQ(2U, list_value->GetSize());
+ std::string permission;
+ ASSERT_TRUE(list_value->GetString(0, &permission));
+ ASSERT_EQ(permission, "tabs");
+ ASSERT_TRUE(list_value->GetString(1, &permission));
+ ASSERT_EQ(permission, "history");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/value_counter.cc b/chromium/extensions/common/value_counter.cc
new file mode 100644
index 00000000000..a4e49e0aab1
--- /dev/null
+++ b/chromium/extensions/common/value_counter.cc
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/value_counter.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/values.h"
+
+namespace extensions {
+
+struct ValueCounter::Entry {
+ explicit Entry(scoped_ptr<base::Value> value)
+ : value(std::move(value)), count(1) {}
+
+ scoped_ptr<base::Value> value;
+ int count;
+};
+
+ValueCounter::ValueCounter() {
+}
+
+ValueCounter::~ValueCounter() {
+}
+
+bool ValueCounter::Add(const base::Value& value) {
+ for (const auto& entry : entries_) {
+ if (entry->value->Equals(&value)) {
+ ++entry->count;
+ return false;
+ }
+ }
+ entries_.push_back(make_scoped_ptr(new Entry(value.CreateDeepCopy())));
+ return true;
+}
+
+bool ValueCounter::Remove(const base::Value& value) {
+ for (auto it = entries_.begin(); it != entries_.end(); ++it) {
+ if ((*it)->value->Equals(&value)) {
+ if (--(*it)->count == 0) {
+ std::swap(*it, entries_.back());
+ entries_.pop_back();
+ return true; // Removed the last entry.
+ }
+ return false; // Removed, but no the last entry.
+ }
+ }
+ return false; // Nothing to remove.
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/common/value_counter.h b/chromium/extensions/common/value_counter.h
new file mode 100644
index 00000000000..b1137013fa9
--- /dev/null
+++ b/chromium/extensions/common/value_counter.h
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_VALUE_COUNTER_H_
+#define EXTENSIONS_COMMON_VALUE_COUNTER_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace base {
+class Value;
+}
+
+namespace extensions {
+
+// Keeps a running count of Values, like map<Value, int>. Adding/removing
+// values increments/decrements the count associated with a given Value.
+//
+// Add() and Remove() are linear in the number of Values in the ValueCounter,
+// because there is no operator<() defined on Value, so we must iterate to find
+// whether a Value is equal to an existing one.
+class ValueCounter {
+ public:
+ ValueCounter();
+ ~ValueCounter();
+
+ // Adds |value| to the set. In the case where a Value equal to |value|
+ // doesn't already exist in this map, this function makes a copy of |value|
+ // and returns true. Otherwise, it returns false.
+ bool Add(const base::Value& value);
+
+ // Removes |value| from the set, and returns true if it removed the last
+ // value equal to |value|. If there are more equal values, or if there
+ // weren't any in the first place, returns false.
+ bool Remove(const base::Value& value);
+
+ // Returns true if there are no values of any type being counted.
+ bool is_empty() const { return entries_.empty(); }
+
+ private:
+ struct Entry;
+ std::vector<scoped_ptr<Entry>> entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(ValueCounter);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_VALUE_COUNTER_H_
diff --git a/chromium/extensions/common/view_type.cc b/chromium/extensions/common/view_type.cc
new file mode 100644
index 00000000000..cf9b0c1b91f
--- /dev/null
+++ b/chromium/extensions/common/view_type.cc
@@ -0,0 +1,18 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/view_type.h"
+
+namespace extensions {
+
+const char kViewTypeTabContents[] = "TAB";
+const char kViewTypeBackgroundPage[] = "BACKGROUND";
+const char kViewTypePopup[] = "POPUP";
+const char kViewTypePanel[] = "PANEL";
+const char kViewTypeExtensionDialog[] = "EXTENSION_DIALOG";
+const char kViewTypeAppWindow[] = "APP_WINDOW";
+const char kViewTypeLauncherPage[] = "LAUNCHER_PAGE";
+const char kViewTypeAll[] = "ALL";
+
+} // namespace extensions
diff --git a/chromium/extensions/common/view_type.h b/chromium/extensions/common/view_type.h
new file mode 100644
index 00000000000..5fc7c273194
--- /dev/null
+++ b/chromium/extensions/common/view_type.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_VIEW_TYPE_H_
+#define EXTENSIONS_COMMON_VIEW_TYPE_H_
+
+namespace extensions {
+
+// Icky RTTI used by a few systems to distinguish the host type of a given
+// WebContents.
+//
+// TODO(aa): Remove this and teach those systems to keep track of their own
+// data.
+enum ViewType {
+ VIEW_TYPE_INVALID,
+ VIEW_TYPE_APP_WINDOW,
+ VIEW_TYPE_BACKGROUND_CONTENTS,
+ VIEW_TYPE_COMPONENT, // For custom parts of Chrome if no other type applies.
+ VIEW_TYPE_EXTENSION_BACKGROUND_PAGE,
+ VIEW_TYPE_EXTENSION_DIALOG,
+ VIEW_TYPE_EXTENSION_GUEST,
+ VIEW_TYPE_EXTENSION_POPUP,
+ VIEW_TYPE_LAUNCHER_PAGE,
+ VIEW_TYPE_PANEL,
+ VIEW_TYPE_TAB_CONTENTS,
+ VIEW_TYPE_LAST = VIEW_TYPE_TAB_CONTENTS
+};
+
+// Constant strings corresponding to the Type enumeration values. Used
+// when converting JS arguments.
+extern const char kViewTypeAll[];
+extern const char kViewTypeAppWindow[];
+extern const char kViewTypeBackgroundPage[];
+extern const char kViewTypeExtensionDialog[];
+extern const char kViewTypeLauncherPage[];
+extern const char kViewTypePanel[];
+extern const char kViewTypePopup[];
+extern const char kViewTypeTabContents[];
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_VIEW_TYPE_H_
diff --git a/chromium/extensions/components/DEPS b/chromium/extensions/components/DEPS
new file mode 100644
index 00000000000..0a33fe40ab1
--- /dev/null
+++ b/chromium/extensions/components/DEPS
@@ -0,0 +1,7 @@
+include_rules = [
+ # Things in extensions/components can depend on extensions, but not other
+ # extensions/components in a way that could make a cycle in the dependency
+ # graph. Individual components must explicitly declare their dependencies
+ # on other components.
+ "-extensions/components",
+]
diff --git a/chromium/extensions/components/README b/chromium/extensions/components/README
new file mode 100644
index 00000000000..4f0474d893a
--- /dev/null
+++ b/chromium/extensions/components/README
@@ -0,0 +1,11 @@
+This directory holds components reused in multiple embedders which themselves
+have extensions dependencies. Components such as these do not belong in the root
+src/components, because src/extensions already depends on src/components. A
+component in src/components can not have an extensions dependency.
+
+Code in an extensions/component should be placed in a namespace corresponding to
+the name of the component (ignoring extensions); e.g. for a component living in
+extensions/components/foo, code in that component should be in the foo::
+namespace.
+
+See src/components/README for additional notes.
diff --git a/chromium/extensions/components/javascript_dialog_extensions_client/BUILD.gn b/chromium/extensions/components/javascript_dialog_extensions_client/BUILD.gn
new file mode 100644
index 00000000000..7e05ed7eee5
--- /dev/null
+++ b/chromium/extensions/components/javascript_dialog_extensions_client/BUILD.gn
@@ -0,0 +1,18 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+static_library("javascript_dialog_extensions_client") {
+ sources = [
+ "javascript_dialog_extension_client_impl.cc",
+ "javascript_dialog_extension_client_impl.h",
+ ]
+
+ deps = [
+ "//components/app_modal",
+ "//content/public/browser",
+ "//extensions/browser",
+ "//extensions/common",
+ "//skia",
+ ]
+}
diff --git a/chromium/extensions/components/javascript_dialog_extensions_client/DEPS b/chromium/extensions/components/javascript_dialog_extensions_client/DEPS
new file mode 100644
index 00000000000..fa862bdaf26
--- /dev/null
+++ b/chromium/extensions/components/javascript_dialog_extensions_client/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+components/app_modal",
+ "+content/public/browser",
+ "+extensions/browser",
+ "+extensions/common",
+]
diff --git a/chromium/extensions/components/javascript_dialog_extensions_client/OWNERS b/chromium/extensions/components/javascript_dialog_extensions_client/OWNERS
new file mode 100644
index 00000000000..ac7a12e41d1
--- /dev/null
+++ b/chromium/extensions/components/javascript_dialog_extensions_client/OWNERS
@@ -0,0 +1,2 @@
+avi@chromium.org
+oshima@chromium.org
diff --git a/chromium/extensions/components/javascript_dialog_extensions_client/javascript_dialog_extension_client_impl.cc b/chromium/extensions/components/javascript_dialog_extensions_client/javascript_dialog_extension_client_impl.cc
new file mode 100644
index 00000000000..d2f5d50518a
--- /dev/null
+++ b/chromium/extensions/components/javascript_dialog_extensions_client/javascript_dialog_extension_client_impl.cc
@@ -0,0 +1,86 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/components/javascript_dialog_extensions_client/javascript_dialog_extension_client_impl.h"
+
+#include "base/macros.h"
+#include "components/app_modal/javascript_dialog_extensions_client.h"
+#include "components/app_modal/javascript_dialog_manager.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/common/extension.h"
+#include "ui/gfx/native_widget_types.h"
+#include "url/origin.h"
+
+namespace javascript_dialog_extensions_client {
+namespace {
+
+using extensions::Extension;
+
+// Returns the ProcessManager for the browser context from |web_contents|.
+extensions::ProcessManager* GetProcessManager(
+ content::WebContents* web_contents) {
+ return extensions::ProcessManager::Get(web_contents->GetBrowserContext());
+}
+
+// Returns the extension associated with |web_contents| or NULL if there is no
+// associated extension (or extensions are not supported).
+const Extension* GetExtensionForWebContents(
+ content::WebContents* web_contents) {
+ return GetProcessManager(web_contents)->GetExtensionForWebContents(
+ web_contents);
+}
+
+class JavaScriptDialogExtensionsClientImpl
+ : public app_modal::JavaScriptDialogExtensionsClient {
+ public:
+ JavaScriptDialogExtensionsClientImpl() {}
+ ~JavaScriptDialogExtensionsClientImpl() override {}
+
+ // JavaScriptDialogExtensionsClient:
+ void OnDialogOpened(content::WebContents* web_contents) override {
+ const Extension* extension = GetExtensionForWebContents(web_contents);
+ if (extension == nullptr)
+ return;
+
+ DCHECK(web_contents);
+ extensions::ProcessManager* pm = GetProcessManager(web_contents);
+ if (pm)
+ pm->IncrementLazyKeepaliveCount(extension);
+ }
+ void OnDialogClosed(content::WebContents* web_contents) override {
+ const Extension* extension = GetExtensionForWebContents(web_contents);
+ if (extension == nullptr)
+ return;
+
+ DCHECK(web_contents);
+ extensions::ProcessManager* pm = GetProcessManager(web_contents);
+ if (pm)
+ pm->DecrementLazyKeepaliveCount(extension);
+ }
+ bool GetExtensionName(content::WebContents* web_contents,
+ const GURL& origin_url,
+ std::string* name_out) override {
+ const Extension* extension = GetExtensionForWebContents(web_contents);
+ if (extension && url::IsSameOriginWith(
+ origin_url, web_contents->GetLastCommittedURL())) {
+ *name_out = extension->name();
+ return true;
+ }
+ return false;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(JavaScriptDialogExtensionsClientImpl);
+};
+
+} // namespace
+
+void InstallClient() {
+ app_modal::JavaScriptDialogManager::GetInstance()->
+ SetExtensionsClient(
+ make_scoped_ptr(new JavaScriptDialogExtensionsClientImpl));
+}
+
+} // namespace javascript_dialog_extensions_client
diff --git a/chromium/extensions/components/javascript_dialog_extensions_client/javascript_dialog_extension_client_impl.h b/chromium/extensions/components/javascript_dialog_extensions_client/javascript_dialog_extension_client_impl.h
new file mode 100644
index 00000000000..b94fff219b0
--- /dev/null
+++ b/chromium/extensions/components/javascript_dialog_extensions_client/javascript_dialog_extension_client_impl.h
@@ -0,0 +1,15 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMPONENTS_JAVASCRIPT_DIALOG_EXTENSIONS_CLIENT_JAVASCRIPT_DIALOG_EXTENSION_CLIENT_IMPL_H_
+#define EXTENSIONS_COMPONENTS_JAVASCRIPT_DIALOG_EXTENSIONS_CLIENT_JAVASCRIPT_DIALOG_EXTENSION_CLIENT_IMPL_H_
+
+namespace javascript_dialog_extensions_client {
+
+void InstallClient();
+
+} // namespace javascript_dialog_extensions_client
+
+#endif // EXTENSIONS_COMPONENTS_JAVASCRIPT_DIALOG_EXTENSIONS_CLIENT_JAVASCRIPT_DIALOG_EXTENSION_CLIENT_IMPL_H_
+
diff --git a/chromium/extensions/components/native_app_window/BUILD.gn b/chromium/extensions/components/native_app_window/BUILD.gn
new file mode 100644
index 00000000000..5136e2a008b
--- /dev/null
+++ b/chromium/extensions/components/native_app_window/BUILD.gn
@@ -0,0 +1,20 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+static_library("native_app_window") {
+ sources = [
+ "native_app_window_views.cc",
+ "native_app_window_views.h",
+ ]
+
+ deps = [
+ "//base",
+ "//content/public/browser",
+ "//extensions/browser",
+ "//extensions/common",
+ "//skia",
+ "//ui/views",
+ "//ui/views/controls/webview",
+ ]
+}
diff --git a/chromium/extensions/components/native_app_window/DEPS b/chromium/extensions/components/native_app_window/DEPS
new file mode 100644
index 00000000000..a3755f9b875
--- /dev/null
+++ b/chromium/extensions/components/native_app_window/DEPS
@@ -0,0 +1,7 @@
+include_rules = [
+ "+content/public/browser",
+ "+third_party/skia/include/core/SkRegion.h",
+
+ "+ui/aura",
+ "+ui/views",
+]
diff --git a/chromium/extensions/components/native_app_window/OWNERS b/chromium/extensions/components/native_app_window/OWNERS
new file mode 100644
index 00000000000..aac10b5bd43
--- /dev/null
+++ b/chromium/extensions/components/native_app_window/OWNERS
@@ -0,0 +1,2 @@
+benwells@chromium.org
+tapted@chromium.org
diff --git a/chromium/extensions/components/native_app_window/README b/chromium/extensions/components/native_app_window/README
new file mode 100644
index 00000000000..bec84bb6143
--- /dev/null
+++ b/chromium/extensions/components/native_app_window/README
@@ -0,0 +1,2 @@
+The native_app_window extensions component contains UI-specific implementations
+of extensions::NativeAppWindow.
diff --git a/chromium/extensions/components/native_app_window/native_app_window_views.cc b/chromium/extensions/components/native_app_window/native_app_window_views.cc
new file mode 100644
index 00000000000..1b6fa9e13aa
--- /dev/null
+++ b/chromium/extensions/components/native_app_window/native_app_window_views.cc
@@ -0,0 +1,448 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/components/native_app_window/native_app_window_views.h"
+
+#include "base/threading/sequenced_worker_pool.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/common/draggable_region.h"
+#include "third_party/skia/include/core/SkRegion.h"
+#include "ui/gfx/path.h"
+#include "ui/views/controls/webview/webview.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/window/non_client_view.h"
+
+#if defined(USE_AURA)
+#include "ui/aura/window.h"
+#endif
+
+using extensions::AppWindow;
+
+namespace native_app_window {
+
+NativeAppWindowViews::NativeAppWindowViews()
+ : app_window_(NULL),
+ web_view_(NULL),
+ widget_(NULL),
+ frameless_(false),
+ resizable_(false) {
+}
+
+void NativeAppWindowViews::Init(AppWindow* app_window,
+ const AppWindow::CreateParams& create_params) {
+ app_window_ = app_window;
+ frameless_ = create_params.frame == AppWindow::FRAME_NONE;
+ resizable_ = create_params.resizable;
+ size_constraints_.set_minimum_size(
+ create_params.GetContentMinimumSize(gfx::Insets()));
+ size_constraints_.set_maximum_size(
+ create_params.GetContentMaximumSize(gfx::Insets()));
+ Observe(app_window_->web_contents());
+
+ widget_ = new views::Widget;
+ widget_->AddObserver(this);
+ InitializeWindow(app_window, create_params);
+
+ OnViewWasResized();
+}
+
+NativeAppWindowViews::~NativeAppWindowViews() {
+ web_view_->SetWebContents(NULL);
+}
+
+void NativeAppWindowViews::OnCanHaveAlphaEnabledChanged() {
+ app_window_->OnNativeWindowChanged();
+}
+
+void NativeAppWindowViews::InitializeWindow(
+ AppWindow* app_window,
+ const AppWindow::CreateParams& create_params) {
+ // Stub implementation. See also ChromeNativeAppWindowViews.
+ views::Widget::InitParams init_params(views::Widget::InitParams::TYPE_WINDOW);
+ init_params.delegate = this;
+ init_params.keep_on_top = create_params.always_on_top;
+ widget_->Init(init_params);
+ widget_->CenterWindow(
+ create_params.GetInitialWindowBounds(gfx::Insets()).size());
+}
+
+// ui::BaseWindow implementation.
+
+bool NativeAppWindowViews::IsActive() const {
+ return widget_->IsActive();
+}
+
+bool NativeAppWindowViews::IsMaximized() const {
+ return widget_->IsMaximized();
+}
+
+bool NativeAppWindowViews::IsMinimized() const {
+ return widget_->IsMinimized();
+}
+
+bool NativeAppWindowViews::IsFullscreen() const {
+ return widget_->IsFullscreen();
+}
+
+gfx::NativeWindow NativeAppWindowViews::GetNativeWindow() const {
+ return widget_->GetNativeWindow();
+}
+
+gfx::Rect NativeAppWindowViews::GetRestoredBounds() const {
+ return widget_->GetRestoredBounds();
+}
+
+ui::WindowShowState NativeAppWindowViews::GetRestoredState() const {
+ // Stub implementation. See also ChromeNativeAppWindowViews.
+ if (IsMaximized())
+ return ui::SHOW_STATE_MAXIMIZED;
+ if (IsFullscreen())
+ return ui::SHOW_STATE_FULLSCREEN;
+ return ui::SHOW_STATE_NORMAL;
+}
+
+gfx::Rect NativeAppWindowViews::GetBounds() const {
+ return widget_->GetWindowBoundsInScreen();
+}
+
+void NativeAppWindowViews::Show() {
+ if (widget_->IsVisible()) {
+ widget_->Activate();
+ return;
+ }
+ widget_->Show();
+}
+
+void NativeAppWindowViews::ShowInactive() {
+ if (widget_->IsVisible())
+ return;
+
+ widget_->ShowInactive();
+}
+
+void NativeAppWindowViews::Hide() {
+ widget_->Hide();
+}
+
+void NativeAppWindowViews::Close() {
+ widget_->Close();
+}
+
+void NativeAppWindowViews::Activate() {
+ widget_->Activate();
+}
+
+void NativeAppWindowViews::Deactivate() {
+ widget_->Deactivate();
+}
+
+void NativeAppWindowViews::Maximize() {
+ widget_->Maximize();
+}
+
+void NativeAppWindowViews::Minimize() {
+ widget_->Minimize();
+}
+
+void NativeAppWindowViews::Restore() {
+ widget_->Restore();
+}
+
+void NativeAppWindowViews::SetBounds(const gfx::Rect& bounds) {
+ widget_->SetBounds(bounds);
+}
+
+void NativeAppWindowViews::FlashFrame(bool flash) {
+ widget_->FlashFrame(flash);
+}
+
+bool NativeAppWindowViews::IsAlwaysOnTop() const {
+ // Stub implementation. See also ChromeNativeAppWindowViews.
+ return widget_->IsAlwaysOnTop();
+}
+
+void NativeAppWindowViews::SetAlwaysOnTop(bool always_on_top) {
+ widget_->SetAlwaysOnTop(always_on_top);
+}
+
+gfx::NativeView NativeAppWindowViews::GetHostView() const {
+ return widget_->GetNativeView();
+}
+
+gfx::Point NativeAppWindowViews::GetDialogPosition(const gfx::Size& size) {
+ gfx::Size app_window_size = widget_->GetWindowBoundsInScreen().size();
+ return gfx::Point(app_window_size.width() / 2 - size.width() / 2,
+ app_window_size.height() / 2 - size.height() / 2);
+}
+
+gfx::Size NativeAppWindowViews::GetMaximumDialogSize() {
+ return widget_->GetWindowBoundsInScreen().size();
+}
+
+void NativeAppWindowViews::AddObserver(
+ web_modal::ModalDialogHostObserver* observer) {
+ observer_list_.AddObserver(observer);
+}
+void NativeAppWindowViews::RemoveObserver(
+ web_modal::ModalDialogHostObserver* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+void NativeAppWindowViews::OnViewWasResized() {
+ FOR_EACH_OBSERVER(web_modal::ModalDialogHostObserver,
+ observer_list_,
+ OnPositionRequiresUpdate());
+}
+
+// WidgetDelegate implementation.
+
+void NativeAppWindowViews::OnWidgetMove() {
+ app_window_->OnNativeWindowChanged();
+}
+
+views::View* NativeAppWindowViews::GetInitiallyFocusedView() {
+ return web_view_;
+}
+
+bool NativeAppWindowViews::CanResize() const {
+ return resizable_ && !size_constraints_.HasFixedSize() &&
+ !WidgetHasHitTestMask();
+}
+
+bool NativeAppWindowViews::CanMaximize() const {
+ return resizable_ && !size_constraints_.HasMaximumSize() &&
+ !app_window_->window_type_is_panel() && !WidgetHasHitTestMask();
+}
+
+bool NativeAppWindowViews::CanMinimize() const {
+ return true;
+}
+
+base::string16 NativeAppWindowViews::GetWindowTitle() const {
+ return app_window_->GetTitle();
+}
+
+bool NativeAppWindowViews::ShouldShowWindowTitle() const {
+ return app_window_->window_type() == AppWindow::WINDOW_TYPE_V1_PANEL;
+}
+
+bool NativeAppWindowViews::ShouldShowWindowIcon() const {
+ return app_window_->window_type() == AppWindow::WINDOW_TYPE_V1_PANEL;
+}
+
+void NativeAppWindowViews::SaveWindowPlacement(const gfx::Rect& bounds,
+ ui::WindowShowState show_state) {
+ views::WidgetDelegate::SaveWindowPlacement(bounds, show_state);
+ app_window_->OnNativeWindowChanged();
+}
+
+void NativeAppWindowViews::DeleteDelegate() {
+ widget_->RemoveObserver(this);
+ app_window_->OnNativeClose();
+}
+
+views::Widget* NativeAppWindowViews::GetWidget() {
+ return widget_;
+}
+
+const views::Widget* NativeAppWindowViews::GetWidget() const {
+ return widget_;
+}
+
+views::View* NativeAppWindowViews::GetContentsView() {
+ return this;
+}
+
+bool NativeAppWindowViews::ShouldDescendIntoChildForEventHandling(
+ gfx::NativeView child,
+ const gfx::Point& location) {
+#if defined(USE_AURA)
+ if (child->Contains(web_view_->web_contents()->GetNativeView())) {
+ // App window should claim mouse events that fall within the draggable
+ // region.
+ return !draggable_region_.get() ||
+ !draggable_region_->contains(location.x(), location.y());
+ }
+#endif
+
+ return true;
+}
+
+// WidgetObserver implementation.
+
+void NativeAppWindowViews::OnWidgetDestroying(views::Widget* widget) {
+ FOR_EACH_OBSERVER(web_modal::ModalDialogHostObserver,
+ observer_list_,
+ OnHostDestroying());
+}
+
+void NativeAppWindowViews::OnWidgetVisibilityChanged(views::Widget* widget,
+ bool visible) {
+ app_window_->OnNativeWindowChanged();
+}
+
+void NativeAppWindowViews::OnWidgetActivationChanged(views::Widget* widget,
+ bool active) {
+ app_window_->OnNativeWindowChanged();
+ if (active)
+ app_window_->OnNativeWindowActivated();
+}
+
+// WebContentsObserver implementation.
+
+void NativeAppWindowViews::RenderViewCreated(
+ content::RenderViewHost* render_view_host) {
+ if (app_window_->requested_alpha_enabled() && CanHaveAlphaEnabled()) {
+ content::RenderWidgetHostView* view =
+ render_view_host->GetWidget()->GetView();
+ DCHECK(view);
+ view->SetBackgroundColor(SK_ColorTRANSPARENT);
+ }
+}
+
+void NativeAppWindowViews::RenderViewHostChanged(
+ content::RenderViewHost* old_host,
+ content::RenderViewHost* new_host) {
+ OnViewWasResized();
+}
+
+// views::View implementation.
+
+void NativeAppWindowViews::Layout() {
+ DCHECK(web_view_);
+ web_view_->SetBounds(0, 0, width(), height());
+ OnViewWasResized();
+}
+
+void NativeAppWindowViews::ViewHierarchyChanged(
+ const ViewHierarchyChangedDetails& details) {
+ if (details.is_add && details.child == this) {
+ web_view_ = new views::WebView(NULL);
+ AddChildView(web_view_);
+ web_view_->SetWebContents(app_window_->web_contents());
+ }
+}
+
+gfx::Size NativeAppWindowViews::GetMinimumSize() const {
+ return size_constraints_.GetMinimumSize();
+}
+
+gfx::Size NativeAppWindowViews::GetMaximumSize() const {
+ return size_constraints_.GetMaximumSize();
+}
+
+void NativeAppWindowViews::OnFocus() {
+ web_view_->RequestFocus();
+}
+
+// NativeAppWindow implementation.
+
+void NativeAppWindowViews::SetFullscreen(int fullscreen_types) {
+ // Stub implementation. See also ChromeNativeAppWindowViews.
+ widget_->SetFullscreen(fullscreen_types != AppWindow::FULLSCREEN_TYPE_NONE);
+}
+
+bool NativeAppWindowViews::IsFullscreenOrPending() const {
+ // Stub implementation. See also ChromeNativeAppWindowViews.
+ return widget_->IsFullscreen();
+}
+
+void NativeAppWindowViews::UpdateWindowIcon() {
+ widget_->UpdateWindowIcon();
+}
+
+void NativeAppWindowViews::UpdateWindowTitle() {
+ widget_->UpdateWindowTitle();
+}
+
+void NativeAppWindowViews::UpdateDraggableRegions(
+ const std::vector<extensions::DraggableRegion>& regions) {
+ // Draggable region is not supported for non-frameless window.
+ if (!frameless_)
+ return;
+
+ draggable_region_.reset(AppWindow::RawDraggableRegionsToSkRegion(regions));
+ OnViewWasResized();
+}
+
+SkRegion* NativeAppWindowViews::GetDraggableRegion() {
+ return draggable_region_.get();
+}
+
+void NativeAppWindowViews::UpdateShape(scoped_ptr<SkRegion> region) {
+ // Stub implementation. See also ChromeNativeAppWindowViews.
+}
+
+void NativeAppWindowViews::HandleKeyboardEvent(
+ const content::NativeWebKeyboardEvent& event) {
+ unhandled_keyboard_event_handler_.HandleKeyboardEvent(event,
+ GetFocusManager());
+}
+
+bool NativeAppWindowViews::IsFrameless() const {
+ return frameless_;
+}
+
+bool NativeAppWindowViews::HasFrameColor() const {
+ return false;
+}
+
+SkColor NativeAppWindowViews::ActiveFrameColor() const {
+ return SK_ColorBLACK;
+}
+
+SkColor NativeAppWindowViews::InactiveFrameColor() const {
+ return SK_ColorBLACK;
+}
+
+gfx::Insets NativeAppWindowViews::GetFrameInsets() const {
+ if (frameless_)
+ return gfx::Insets();
+
+ // The pretend client_bounds passed in need to be large enough to ensure that
+ // GetWindowBoundsForClientBounds() doesn't decide that it needs more than
+ // the specified amount of space to fit the window controls in, and return a
+ // number larger than the real frame insets. Most window controls are smaller
+ // than 1000x1000px, so this should be big enough.
+ gfx::Rect client_bounds = gfx::Rect(1000, 1000);
+ gfx::Rect window_bounds =
+ widget_->non_client_view()->GetWindowBoundsForClientBounds(client_bounds);
+ return window_bounds.InsetsFrom(client_bounds);
+}
+
+void NativeAppWindowViews::HideWithApp() {
+}
+
+void NativeAppWindowViews::ShowWithApp() {
+}
+
+gfx::Size NativeAppWindowViews::GetContentMinimumSize() const {
+ return size_constraints_.GetMinimumSize();
+}
+
+gfx::Size NativeAppWindowViews::GetContentMaximumSize() const {
+ return size_constraints_.GetMaximumSize();
+}
+
+void NativeAppWindowViews::SetContentSizeConstraints(
+ const gfx::Size& min_size,
+ const gfx::Size& max_size) {
+ size_constraints_.set_minimum_size(min_size);
+ size_constraints_.set_maximum_size(max_size);
+ widget_->OnSizeConstraintsChanged();
+}
+
+bool NativeAppWindowViews::CanHaveAlphaEnabled() const {
+ return widget_->IsTranslucentWindowOpacitySupported();
+}
+
+void NativeAppWindowViews::SetVisibleOnAllWorkspaces(bool always_visible) {
+ widget_->SetVisibleOnAllWorkspaces(always_visible);
+}
+
+} // namespace native_app_window
diff --git a/chromium/extensions/components/native_app_window/native_app_window_views.h b/chromium/extensions/components/native_app_window/native_app_window_views.h
new file mode 100644
index 00000000000..0fa319653d1
--- /dev/null
+++ b/chromium/extensions/components/native_app_window/native_app_window_views.h
@@ -0,0 +1,191 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMPONENTS_NATIVE_APP_WINDOW_NATIVE_APP_WINDOW_VIEWS_H_
+#define EXTENSIONS_COMPONENTS_NATIVE_APP_WINDOW_NATIVE_APP_WINDOW_VIEWS_H_
+
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/native_app_window.h"
+#include "extensions/browser/app_window/size_constraints.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/views/controls/webview/unhandled_keyboard_event_handler.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+#include "ui/views/widget/widget_observer.h"
+
+class SkRegion;
+
+namespace content {
+class BrowserContext;
+class RenderViewHost;
+class WebContents;
+}
+
+namespace extensions {
+class Extension;
+}
+
+namespace ui {
+class MenuModel;
+}
+
+namespace views {
+class MenuRunner;
+class WebView;
+}
+
+namespace native_app_window {
+
+// A NativeAppWindow backed by a views::Widget. This class may be used alone
+// as a stub or subclassed (for example, ChromeNativeAppWindowViews).
+class NativeAppWindowViews : public extensions::NativeAppWindow,
+ public content::WebContentsObserver,
+ public views::WidgetDelegateView,
+ public views::WidgetObserver {
+ public:
+ NativeAppWindowViews();
+ ~NativeAppWindowViews() override;
+ void Init(extensions::AppWindow* app_window,
+ const extensions::AppWindow::CreateParams& create_params);
+
+ // Signal that CanHaveTransparentBackground has changed.
+ void OnCanHaveAlphaEnabledChanged();
+
+ extensions::AppWindow* app_window() { return app_window_; }
+ const extensions::AppWindow* app_window() const { return app_window_; }
+
+ views::WebView* web_view() { return web_view_; }
+ const views::WebView* web_view() const { return web_view_; }
+
+ views::Widget* widget() { return widget_; }
+ const views::Widget* widget() const { return widget_; }
+
+ void set_window_for_testing(views::Widget* window) { widget_ = window; }
+ void set_web_view_for_testing(views::WebView* view) { web_view_ = view; }
+
+ protected:
+ // Initializes |widget_| for |app_window|.
+ virtual void InitializeWindow(
+ extensions::AppWindow* app_window,
+ const extensions::AppWindow::CreateParams& create_params);
+
+ // ui::BaseWindow implementation.
+ bool IsActive() const override;
+ bool IsMaximized() const override;
+ bool IsMinimized() const override;
+ bool IsFullscreen() const override;
+ gfx::NativeWindow GetNativeWindow() const override;
+ gfx::Rect GetRestoredBounds() const override;
+ ui::WindowShowState GetRestoredState() const override;
+ gfx::Rect GetBounds() const override;
+ void Show() override;
+ void ShowInactive() override;
+ void Hide() override;
+ void Close() override;
+ void Activate() override;
+ void Deactivate() override;
+ void Maximize() override;
+ void Minimize() override;
+ void Restore() override;
+ void SetBounds(const gfx::Rect& bounds) override;
+ void FlashFrame(bool flash) override;
+ bool IsAlwaysOnTop() const override;
+ void SetAlwaysOnTop(bool always_on_top) override;
+
+ // WidgetDelegate implementation.
+ void OnWidgetMove() override;
+ views::View* GetInitiallyFocusedView() override;
+ bool CanResize() const override;
+ bool CanMaximize() const override;
+ bool CanMinimize() const override;
+ base::string16 GetWindowTitle() const override;
+ bool ShouldShowWindowTitle() const override;
+ bool ShouldShowWindowIcon() const override;
+ void SaveWindowPlacement(const gfx::Rect& bounds,
+ ui::WindowShowState show_state) override;
+ void DeleteDelegate() override;
+ views::Widget* GetWidget() override;
+ const views::Widget* GetWidget() const override;
+ views::View* GetContentsView() override;
+ bool ShouldDescendIntoChildForEventHandling(
+ gfx::NativeView child,
+ const gfx::Point& location) override;
+
+ // WidgetObserver implementation.
+ void OnWidgetDestroying(views::Widget* widget) override;
+ void OnWidgetVisibilityChanged(views::Widget* widget, bool visible) override;
+ void OnWidgetActivationChanged(views::Widget* widget, bool active) override;
+
+ // WebContentsObserver implementation.
+ void RenderViewCreated(content::RenderViewHost* render_view_host) override;
+ void RenderViewHostChanged(content::RenderViewHost* old_host,
+ content::RenderViewHost* new_host) override;
+
+ // views::View implementation.
+ void Layout() override;
+ void ViewHierarchyChanged(
+ const ViewHierarchyChangedDetails& details) override;
+ gfx::Size GetMinimumSize() const override;
+ gfx::Size GetMaximumSize() const override;
+ void OnFocus() override;
+
+ // NativeAppWindow implementation.
+ void SetFullscreen(int fullscreen_types) override;
+ bool IsFullscreenOrPending() const override;
+ void UpdateWindowIcon() override;
+ void UpdateWindowTitle() override;
+ void UpdateDraggableRegions(
+ const std::vector<extensions::DraggableRegion>& regions) override;
+ SkRegion* GetDraggableRegion() override;
+ void UpdateShape(scoped_ptr<SkRegion> region) override;
+ void HandleKeyboardEvent(
+ const content::NativeWebKeyboardEvent& event) override;
+ bool IsFrameless() const override;
+ bool HasFrameColor() const override;
+ SkColor ActiveFrameColor() const override;
+ SkColor InactiveFrameColor() const override;
+ gfx::Insets GetFrameInsets() const override;
+ void HideWithApp() override;
+ void ShowWithApp() override;
+ gfx::Size GetContentMinimumSize() const override;
+ gfx::Size GetContentMaximumSize() const override;
+ void SetContentSizeConstraints(const gfx::Size& min_size,
+ const gfx::Size& max_size) override;
+ bool CanHaveAlphaEnabled() const override;
+ void SetVisibleOnAllWorkspaces(bool always_visible) override;
+
+ // web_modal::WebContentsModalDialogHost implementation.
+ gfx::NativeView GetHostView() const override;
+ gfx::Point GetDialogPosition(const gfx::Size& size) override;
+ gfx::Size GetMaximumDialogSize() override;
+ void AddObserver(web_modal::ModalDialogHostObserver* observer) override;
+ void RemoveObserver(web_modal::ModalDialogHostObserver* observer) override;
+
+ private:
+ // Informs modal dialogs that they need to update their positions.
+ void OnViewWasResized();
+
+ extensions::AppWindow* app_window_; // Not owned.
+ views::WebView* web_view_;
+ views::Widget* widget_;
+
+ scoped_ptr<SkRegion> draggable_region_;
+
+ bool frameless_;
+ bool resizable_;
+ extensions::SizeConstraints size_constraints_;
+
+ views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_;
+
+ base::ObserverList<web_modal::ModalDialogHostObserver> observer_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeAppWindowViews);
+};
+
+} // namespace native_app_window
+
+#endif // EXTENSIONS_COMPONENTS_NATIVE_APP_WINDOW_NATIVE_APP_WINDOW_VIEWS_H_
diff --git a/chromium/extensions/extensions.gni b/chromium/extensions/extensions.gni
new file mode 100644
index 00000000000..05f11085834
--- /dev/null
+++ b/chromium/extensions/extensions.gni
@@ -0,0 +1,25 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# This file defines the extension gypi values. This file is read once and
+# cached, which is a performance optimization that allows us to share the
+# results of parsing the .gypi file between all extensions BUILD.gn files.
+# It also saves us from duplicating this exec_script call.
+extensions_gypi_values =
+ exec_script("//build/gypi_to_gn.py",
+ [
+ rebase_path("extensions.gypi"),
+ "--replace=<(SHARED_INTERMEDIATE_DIR)=$root_gen_dir",
+ ],
+ "scope",
+ [ "extensions.gypi" ])
+
+extensions_tests_gypi_values =
+ exec_script("//build/gypi_to_gn.py",
+ [
+ rebase_path("extensions_tests.gypi"),
+ "--replace=<(SHARED_INTERMEDIATE_DIR)=$root_gen_dir",
+ ],
+ "scope",
+ [ "extensions_tests.gypi" ])
diff --git a/chromium/extensions/extensions_resources.grd b/chromium/extensions/extensions_resources.grd
new file mode 100644
index 00000000000..6406ce90ea0
--- /dev/null
+++ b/chromium/extensions/extensions_resources.grd
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1">
+ <outputs>
+ <output filename="grit/extensions_resources.h" type="rc_header">
+ <emit emit_type='prepend'></emit>
+ </output>
+ <output filename="extensions_resources.pak" type="data_package" />
+ </outputs>
+ <release seq="1">
+ <includes>
+ <include name="IDR_EXTENSION_API_FEATURES" file="common\api\_api_features.json" type="BINDATA" />
+ <include name="IDR_EXTENSION_API_JSON_DECLARATIVE_WEBREQUEST" file="common\api\declarative_web_request.json" type="BINDATA" />
+ <include name="IDR_EXTENSION_API_JSON_WEB_VIEW_REQUEST" file="common\api\web_view_request.json" type="BINDATA" />
+ <include name="IDR_EXTENSION_MANIFEST_FEATURES" file="common\api\_manifest_features.json" type="BINDATA" />
+ <include name="IDR_EXTENSION_PERMISSION_FEATURES" file="common\api\_permission_features.json" type="BINDATA" />
+ <include name="IDR_EXTENSION_BEHAVIOR_FEATURES" file="common\api\_behavior_features.json" type="BINDATA" />
+ </includes>
+ </release>
+</grit>
diff --git a/chromium/extensions/extensions_strings.grd b/chromium/extensions/extensions_strings.grd
new file mode 100644
index 00000000000..ecdc0faba62
--- /dev/null
+++ b/chromium/extensions/extensions_strings.grd
@@ -0,0 +1,344 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Strings for the extensions module. Used mostly for low-level error messages.
+ Where possible new strings should be kept in Chrome and the extensions module
+ should return an error code, message flag, etc.
+-->
+
+<grit latest_public_release="0" current_release="1"
+ source_lang_id="en" enc_check="möl">
+ <outputs>
+ <output filename="grit/extensions_strings.h" type="rc_header">
+ <emit emit_type='prepend'></emit>
+ </output>
+ <output filename="extensions_strings_am.pak" type="data_package" lang="am" />
+ <output filename="extensions_strings_ar.pak" type="data_package" lang="ar" />
+ <output filename="extensions_strings_bg.pak" type="data_package" lang="bg" />
+ <output filename="extensions_strings_bn.pak" type="data_package" lang="bn" />
+ <output filename="extensions_strings_ca.pak" type="data_package" lang="ca" />
+ <output filename="extensions_strings_cs.pak" type="data_package" lang="cs" />
+ <output filename="extensions_strings_da.pak" type="data_package" lang="da" />
+ <output filename="extensions_strings_de.pak" type="data_package" lang="de" />
+ <output filename="extensions_strings_el.pak" type="data_package" lang="el" />
+ <output filename="extensions_strings_en-GB.pak" type="data_package" lang="en-GB" />
+ <output filename="extensions_strings_en-US.pak" type="data_package" lang="en" />
+ <output filename="extensions_strings_es.pak" type="data_package" lang="es" />
+ <if expr="is_ios">
+ <!-- iOS uses es-MX for es-419 -->
+ <output filename="extensions_strings_es-MX.pak" type="data_package" lang="es-419" />
+ </if>
+ <if expr="not is_ios">
+ <output filename="extensions_strings_es-419.pak" type="data_package" lang="es-419" />
+ </if>
+ <output filename="extensions_strings_et.pak" type="data_package" lang="et" />
+ <output filename="extensions_strings_fa.pak" type="data_package" lang="fa" />
+ <output filename="extensions_strings_fake-bidi.pak" type="data_package" lang="fake-bidi" />
+ <output filename="extensions_strings_fi.pak" type="data_package" lang="fi" />
+ <output filename="extensions_strings_fil.pak" type="data_package" lang="fil" />
+ <output filename="extensions_strings_fr.pak" type="data_package" lang="fr" />
+ <output filename="extensions_strings_gu.pak" type="data_package" lang="gu" />
+ <output filename="extensions_strings_he.pak" type="data_package" lang="he" />
+ <output filename="extensions_strings_hi.pak" type="data_package" lang="hi" />
+ <output filename="extensions_strings_hr.pak" type="data_package" lang="hr" />
+ <output filename="extensions_strings_hu.pak" type="data_package" lang="hu" />
+ <output filename="extensions_strings_id.pak" type="data_package" lang="id" />
+ <output filename="extensions_strings_it.pak" type="data_package" lang="it" />
+ <output filename="extensions_strings_ja.pak" type="data_package" lang="ja" />
+ <output filename="extensions_strings_kn.pak" type="data_package" lang="kn" />
+ <output filename="extensions_strings_ko.pak" type="data_package" lang="ko" />
+ <output filename="extensions_strings_lt.pak" type="data_package" lang="lt" />
+ <output filename="extensions_strings_lv.pak" type="data_package" lang="lv" />
+ <output filename="extensions_strings_ml.pak" type="data_package" lang="ml" />
+ <output filename="extensions_strings_mr.pak" type="data_package" lang="mr" />
+ <output filename="extensions_strings_ms.pak" type="data_package" lang="ms" />
+ <output filename="extensions_strings_nl.pak" type="data_package" lang="nl" />
+ <!-- The translation console uses 'no' for Norwegian Bokmål. It should
+ be 'nb'. -->
+ <output filename="extensions_strings_nb.pak" type="data_package" lang="no" />
+ <output filename="extensions_strings_pl.pak" type="data_package" lang="pl" />
+ <if expr="is_ios">
+ <!-- iOS uses pt for pt-BR -->
+ <output filename="extensions_strings_pt.pak" type="data_package" lang="pt-BR" />
+ </if>
+ <if expr="not is_ios">
+ <output filename="extensions_strings_pt-BR.pak" type="data_package" lang="pt-BR" />
+ </if>
+ <output filename="extensions_strings_pt-PT.pak" type="data_package" lang="pt-PT" />
+ <output filename="extensions_strings_ro.pak" type="data_package" lang="ro" />
+ <output filename="extensions_strings_ru.pak" type="data_package" lang="ru" />
+ <output filename="extensions_strings_sk.pak" type="data_package" lang="sk" />
+ <output filename="extensions_strings_sl.pak" type="data_package" lang="sl" />
+ <output filename="extensions_strings_sr.pak" type="data_package" lang="sr" />
+ <output filename="extensions_strings_sv.pak" type="data_package" lang="sv" />
+ <output filename="extensions_strings_sw.pak" type="data_package" lang="sw" />
+ <output filename="extensions_strings_ta.pak" type="data_package" lang="ta" />
+ <output filename="extensions_strings_te.pak" type="data_package" lang="te" />
+ <output filename="extensions_strings_th.pak" type="data_package" lang="th" />
+ <output filename="extensions_strings_tr.pak" type="data_package" lang="tr" />
+ <output filename="extensions_strings_uk.pak" type="data_package" lang="uk" />
+ <output filename="extensions_strings_vi.pak" type="data_package" lang="vi" />
+ <output filename="extensions_strings_zh-CN.pak" type="data_package" lang="zh-CN" />
+ <output filename="extensions_strings_zh-TW.pak" type="data_package" lang="zh-TW" />
+ </outputs>
+ <translations>
+ <file path="strings/extensions_strings_am.xtb" lang="am" />
+ <file path="strings/extensions_strings_ar.xtb" lang="ar" />
+ <file path="strings/extensions_strings_bg.xtb" lang="bg" />
+ <file path="strings/extensions_strings_bn.xtb" lang="bn" />
+ <file path="strings/extensions_strings_ca.xtb" lang="ca" />
+ <file path="strings/extensions_strings_cs.xtb" lang="cs" />
+ <file path="strings/extensions_strings_da.xtb" lang="da" />
+ <file path="strings/extensions_strings_de.xtb" lang="de" />
+ <file path="strings/extensions_strings_el.xtb" lang="el" />
+ <file path="strings/extensions_strings_en-GB.xtb" lang="en-GB" />
+ <file path="strings/extensions_strings_es.xtb" lang="es" />
+ <file path="strings/extensions_strings_es-419.xtb" lang="es-419" />
+ <file path="strings/extensions_strings_et.xtb" lang="et" />
+ <file path="strings/extensions_strings_fa.xtb" lang="fa" />
+ <file path="strings/extensions_strings_fi.xtb" lang="fi" />
+ <file path="strings/extensions_strings_fil.xtb" lang="fil" />
+ <file path="strings/extensions_strings_fr.xtb" lang="fr" />
+ <file path="strings/extensions_strings_gu.xtb" lang="gu" />
+ <file path="strings/extensions_strings_hi.xtb" lang="hi" />
+ <file path="strings/extensions_strings_hr.xtb" lang="hr" />
+ <file path="strings/extensions_strings_hu.xtb" lang="hu" />
+ <file path="strings/extensions_strings_id.xtb" lang="id" />
+ <file path="strings/extensions_strings_it.xtb" lang="it" />
+ <!-- The translation console uses 'iw' for Hebrew, but we use 'he'. -->
+ <file path="strings/extensions_strings_iw.xtb" lang="he" />
+ <file path="strings/extensions_strings_ja.xtb" lang="ja" />
+ <file path="strings/extensions_strings_kn.xtb" lang="kn" />
+ <file path="strings/extensions_strings_ko.xtb" lang="ko" />
+ <file path="strings/extensions_strings_lt.xtb" lang="lt" />
+ <file path="strings/extensions_strings_lv.xtb" lang="lv" />
+ <file path="strings/extensions_strings_ml.xtb" lang="ml" />
+ <file path="strings/extensions_strings_mr.xtb" lang="mr" />
+ <file path="strings/extensions_strings_ms.xtb" lang="ms" />
+ <file path="strings/extensions_strings_nl.xtb" lang="nl" />
+ <file path="strings/extensions_strings_no.xtb" lang="no" />
+ <file path="strings/extensions_strings_pl.xtb" lang="pl" />
+ <file path="strings/extensions_strings_pt-BR.xtb" lang="pt-BR" />
+ <file path="strings/extensions_strings_pt-PT.xtb" lang="pt-PT" />
+ <file path="strings/extensions_strings_ro.xtb" lang="ro" />
+ <file path="strings/extensions_strings_ru.xtb" lang="ru" />
+ <file path="strings/extensions_strings_sk.xtb" lang="sk" />
+ <file path="strings/extensions_strings_sl.xtb" lang="sl" />
+ <file path="strings/extensions_strings_sr.xtb" lang="sr" />
+ <file path="strings/extensions_strings_sv.xtb" lang="sv" />
+ <file path="strings/extensions_strings_sw.xtb" lang="sw" />
+ <file path="strings/extensions_strings_ta.xtb" lang="ta" />
+ <file path="strings/extensions_strings_te.xtb" lang="te" />
+ <file path="strings/extensions_strings_th.xtb" lang="th" />
+ <file path="strings/extensions_strings_tr.xtb" lang="tr" />
+ <file path="strings/extensions_strings_uk.xtb" lang="uk" />
+ <file path="strings/extensions_strings_vi.xtb" lang="vi" />
+ <file path="strings/extensions_strings_zh-CN.xtb" lang="zh-CN" />
+ <file path="strings/extensions_strings_zh-TW.xtb" lang="zh-TW" />
+ </translations>
+ <release seq="1" allow_pseudo="false">
+ <messages fallback_to_english="true">
+
+ <!-- Document Scan API strings. Please keep alphabetized. -->
+
+ <!-- General extensions strings. Please keep alphabetized. -->
+ <message name="IDS_EXTENSION_CONTAINS_PRIVATE_KEY" desc="Error message when an extension includes a file containing a private key.">
+ This extension includes the key file '<ph name="KEY_PATH">$1<ex>relative/path/to/file.pem</ex></ph>'. You probably don't want to do that.
+ </message>
+ <message name="IDS_EXTENSION_LOAD_ABOUT_PAGE_FAILED" desc="">
+ Could not load about page '<ph name="ABOUT_PAGE">$1<ex>page.html</ex></ph>'.
+ </message>
+ <message name="IDS_EXTENSION_LOAD_BACKGROUND_SCRIPT_FAILED" desc="">
+ Could not load background script '<ph name="BACKGROUND_SCRIPT">$1<ex>script.js</ex></ph>'.
+ </message>
+ <message name="IDS_EXTENSION_LOAD_BACKGROUND_PAGE_FAILED" desc="">
+ Could not load background page '<ph name="BACKGROUND_PAGE">$1<ex>page.html</ex></ph>'.
+ </message>
+ <message name="IDS_EXTENSION_LOAD_ICON_FAILED" desc="">
+ Could not load extension icon '<ph name="ICON">$1<ex>icon.png</ex></ph>'.
+ </message>
+ <message name="IDS_EXTENSION_LOAD_LAUNCHER_PAGE_FAILED" desc="">
+ Could not load launcher page '<ph name="PAGE">$1<ex>index.html</ex></ph>'.
+ </message>
+ <message name="IDS_EXTENSION_LOAD_OPTIONS_PAGE_FAILED" desc="">
+ Could not load options page '<ph name="OPTIONS_PAGE">$1<ex>page.html</ex></ph>'.
+ </message>
+ <message name="IDS_EXTENSION_LOCALES_NO_DEFAULT_LOCALE_SPECIFIED" desc="">
+ Localization used, but default_locale wasn't specified in the manifest.
+ </message>
+ <message name="IDS_EXTENSION_MANIFEST_UNREADABLE" desc="">
+ Manifest file is missing or unreadable.
+ </message>
+ <message name="IDS_EXTENSION_MANIFEST_INVALID" desc="">
+ Manifest file is invalid.
+ </message>
+ <message name="IDS_EXTENSION_PACKAGE_DIRECTORY_ERROR" desc="Message for when an error occurs while trying to create the temporary directory needed to unzip a packaged extension or app.">
+ Could not create directory for unzipping: '<ph name="DIRECTORY_PATH">$1<ex>profile/Extensions/CRX_INSTALL</ex></ph>'
+ </message>
+ <message name="IDS_EXTENSION_PACKAGE_IMAGE_PATH_ERROR" desc="Error message for when a packaged extension or app contains a reference to an image that could be outside the package.">
+ Illegal path (absolute or relative with '..'): '<ph name="IMAGE_PATH">$1<ex>../image.png</ex></ph>'
+ </message>
+ <message name="IDS_EXTENSION_PACKAGE_IMAGE_ERROR" desc="Message for when an error occurs while trying to decode an image found within a packaged extension or app.">
+ Could not decode image: '<ph name="IMAGE_NAME">$1<ex>image.png</ex></ph>'
+ </message>
+ <message name="IDS_EXTENSION_PACKAGE_UNZIP_ERROR" desc="Message for when an error occurs while unzipping a packaged extension or app.">
+ Could not unzip extension
+ </message>
+
+ <message name="IDS_LOAD_STATE_PARAMETER_EXTENSION" desc="Parameter to IDS_LOAD_STATE_WAITING_FOR_DELEGATE when we are waiting for an extension. The variable is the extension name.">
+ extension <ph name="EXTENSION_NAME">$1<ex>Adblock</ex></ph>
+ </message>
+
+ <!-- Policy strings. Please keep alphabetized. -->
+ <message name="IDS_EXTENSION_CANT_INSTALL_POLICY_BLOCKED" desc="Error message when a user tries to install an extension that is blocked by administrator policy.">
+ <ph name="EXTENSION_NAME">$1<ex>Google Talk</ex></ph> (extension ID "<ph name="EXTENSION_ID">$2<ex>nckgahadagoaajjgafhacjanaoiihapd</ex></ph>") is blocked by the administrator.
+ </message>
+ <message name="IDS_EXTENSION_CANT_MODIFY_POLICY_REQUIRED" desc="Error message when a user tries to remove or change an extension that is required by administrator policy.">
+ The administrator of this machine requires <ph name="EXTENSION_NAME">$1<ex>Google Talk</ex></ph> to be installed. It cannot be removed or modified.
+ </message>
+ <message name="IDS_EXTENSION_CANT_UNINSTALL_POLICY_REQUIRED" desc="Error message when a user tries to uninstall an extension that is required by administrator policy.">
+ The administrator of this machine requires <ph name="EXTENSION_NAME">$1<ex>Google Talk</ex></ph> to be installed. It cannot be uninstalled.
+ </message>
+ <message name="IDS_EXTENSION_DISABLED_UPDATE_REQUIRED_BY_POLICY" desc="Error message when an extension doesn't meet minimum version required by administrator policy.">
+ The administrator of this machine requires <ph name="EXTENSION_NAME">$1<ex>Google Talk</ex></ph> to have a minimum version of <ph name="EXTENSION_VERSION">$2<ex>1.1.0</ex></ph>. It cannot be enabled until it has updated to that version (or higher).
+ </message>
+
+ <!--
+ Device API strings. Please keep alphabetized.
+
+ To check UI that displays these strings please use one of the sample
+ applications such as:
+
+ https://chrome.google.com/webstore/detail/igkmggljimacfdfalpeelenjeicmfnll
+ https://chrome.google.com/webstore/detail/ohndmecdhlgohpibepbboddcoecomnpc
+
+ IDS_EXTENSION_PROMPT_WARNING_* messages appear on the extension details
+ page along with other permissions. IDS_*PERMISSIONS_PROMPT* messages
+ appear in the device picker displayed by the
+ chrome.usb.getUserSelectedDevices and chrome.hid.getUserSelectedDevices
+ APIs.
+ -->
+ <message name="IDS_DEVICE_NAME_WITH_PRODUCT_SERIAL" desc="String describing a device by its product name and serial number.">
+ <ph name="PRODUCT_NAME">$1<ex>Power Knob</ex></ph> (serial number <ph name="SERIAL_NUMBER">$2<ex>00001</ex></ph>)
+ </message>
+ <message name="IDS_DEVICE_NAME_WITH_PRODUCT_UNKNOWN_VENDOR" desc="String describing a device by its product name when only the numeric vendor ID is available.">
+ <ph name="PRODUCT_NAME">$1<ex>Power Knob</ex></ph> from vendor <ph name="VENDOR_ID">$2<ex>abcd</ex></ph>
+ </message>
+ <message name="IDS_DEVICE_NAME_WITH_PRODUCT_UNKNOWN_VENDOR_SERIAL" desc="String describing a device by its product name and serial number when the numeric vendor ID is available.">
+ <ph name="PRODUCT_NAME">$1<ex>Power Knob</ex></ph> from vendor <ph name="VENDOR_ID">$2<ex>abcd</ex></ph> (serial number <ph name="SERIAL_NUMBER">$3<ex>00001</ex></ph>)
+ </message>
+ <message name="IDS_DEVICE_NAME_WITH_PRODUCT_VENDOR" desc="String describing a device by its product name and vendor.">
+ <ph name="PRODUCT_NAME">$1<ex>Power Knob</ex></ph> from <ph name="VENDOR_NAME">$2<ex>Griffin Technology</ex></ph>
+ </message>
+ <message name="IDS_DEVICE_NAME_WITH_PRODUCT_VENDOR_SERIAL" desc="String describing a device by its product name and vendor and serial number.">
+ <ph name="PRODUCT_NAME">$1<ex>Power Knob</ex></ph> from <ph name="VENDOR_NAME">$2<ex>Griffin Technology</ex></ph> (serial number <ph name="SERIAL_NUMBER">$3<ex>00001</ex></ph>)
+ </message>
+ <message name="IDS_DEVICE_NAME_WITH_UNKNOWN_PRODUCT_UNKNOWN_VENDOR" desc="String describing a device when only numeric vendor and product IDs are available.">
+ Unknown product <ph name="PRODUCT_ID">$1<ex>1234</ex></ph> from vendor <ph name="VENDOR_ID">$2<ex>abcd</ex></ph>
+ </message>
+ <message name="IDS_DEVICE_NAME_WITH_UNKNOWN_PRODUCT_UNKNOWN_VENDOR_SERIAL" desc="String describing a device by its serial number when only numeric vendor and product IDs are available.">
+ Unknown product <ph name="PRODUCT_ID">$1<ex>1234</ex></ph> from vendor <ph name="VENDOR_ID">$2<ex>abcd</ex></ph> (serial number <ph name="SERIAL_NUMBER">$3<ex>00001</ex></ph>)
+ </message>
+ <message name="IDS_DEVICE_NAME_WITH_UNKNOWN_PRODUCT_VENDOR" desc="String describing a device by its vendor name when only the numeric product ID is available.">
+ Unknown product <ph name="PRODUCT_ID">$1<ex>b33f</ex></ph> from <ph name="VENDOR_NAME">$2<ex>Griffin Technology</ex></ph>
+ </message>
+ <message name="IDS_DEVICE_NAME_WITH_UNKNOWN_PRODUCT_VENDOR_SERIAL" desc="String describing a device by its vendor name and serial number when only the numeric product ID is available.">
+ Unknown product <ph name="PRODUCT_ID">$1<ex>1234</ex></ph> from <ph name="VENDOR_NAME">$2<ex>Griffin Technology</ex></ph> (serial number <ph name="SERIAL_NUMBER">$3<ex>00001</ex></ph>)
+ </message>
+ <message name="IDS_DEVICE_PERMISSIONS_PROMPT" desc="Instructions asking the user to select one or more devices for use with an app. [ICU Syntax]">
+ {0, select,
+ single {The application "<ph name="APP_NAME">{1}<ex>Chrome Dev Editor</ex></ph>" is requesting access to one of your devices.}
+ multiple {The application "<ph name="APP_NAME">{1}<ex>Chrome Dev Editor</ex></ph>" is requesting access to one or more of your devices.}
+ other {UNUSED}}
+ </message>
+ <message name="IDS_EXTENSION_USB_DEVICE_PRODUCT_NAME_AND_VENDOR" desc="USB device name and vendor string, used in various permission messages for USB devices.">
+ <ph name="PRODUCT_NAME">$1<ex>SoundKnob</ex></ph> from <ph name="VENDOR_NAME">$2<ex>Griffin Technology</ex></ph>
+ </message>
+ <message name="IDS_HID_DEVICE_PERMISSIONS_PROMPT_TITLE" desc="Title of the HID device selection dialog when the app will accept one or more selected devices. [ICU Syntax]">
+ {0, select,
+ single {Select a HID device} multiple {Select HID devices}
+ other {UNUSED}}
+ </message>
+ <message name="IDS_USB_DEVICE_PERMISSIONS_PROMPT_TITLE" desc="Title of the USB device selection dialog when the app will accept one or more selected devices. [ICU Syntax]">
+ {0, select,
+ single {Select a USB device} multiple {Select USB devices}
+ other {UNUSED}}
+ </message>
+
+ <!-- Extension task manager strings. Please keep alphabetized. -->
+ <message name="IDS_EXTENSION_TASK_MANAGER_APPVIEW_TAG_PREFIX" desc="The prefix for a guest page loaded in an appview tag in the Task Manager">
+ Appview: <ph name="APPVIEW_TAG_NAME">$1<ex>Google Hangouts</ex></ph>
+ </message>
+ <message name="IDS_EXTENSION_TASK_MANAGER_EXTENSIONOPTIONS_TAG_PREFIX" desc="The prefix for a guest page loaded in an extensionoptions tag in the Task Manager">
+ Options: <ph name="EXTENSIONOPTIONS_TAG_NAME">$1<ex>Google Hangouts</ex></ph>
+ </message>
+ <message name="IDS_EXTENSION_TASK_MANAGER_EXTENSIONVIEW_TAG_PREFIX" desc="The prefix for a guest page loaded in an extensionview tag in the Task Manager">
+ ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME">$1<ex>Google Hangouts</ex></ph>
+ </message>
+ <message name="IDS_EXTENSION_TASK_MANAGER_MIMEHANDLERVIEW_TAG_PREFIX" desc="The prefix for a guest page loaded in a mime handler view page in the task manager">
+ Mimehandler: <ph name="MIMEHANDLERVIEW_TAG_NAME">$1<ex>My mime handler view</ex></ph>
+ </message>
+ <message name="IDS_EXTENSION_TASK_MANAGER_WEBVIEW_TAG_PREFIX" desc="The prefix for a guest page loaded in a webview tag in the Task Manager">
+ Webview: <ph name="WEBVIEW_TAG_NAME">$1<ex>Google</ex></ph>
+ </message>
+
+ <!-- Global error messages for extensions. Please keep alphabetized. -->
+ <message name="IDS_EXTENSION_WARNINGS_NETWORK_DELAY" desc="Warning message indicating that an extension caused excessive network delays for web requests">
+ This extension is slowing down <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>. You should disable it to restore <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>'s performance.
+ </message>
+ <message name="IDS_EXTENSION_WARNINGS_NETWORK_CONFLICT" desc="Warning message which indicates that two or more extensions tried to modify a network request in a conflicting way and the modification of this extension was ignored">
+ This extension failed to modify a network request because the modification conflicted with another extension.
+ </message>
+ <message name="IDS_EXTENSION_WARNINGS_REDIRECT_CONFLICT" desc="Warning message indicating that two extensions tried to redirect to different destinations">
+ This extension failed to redirect a network request to <ph name="ATTEMPTED_REDIRECT_DESTINATION">$1<ex>http://www.google.com</ex></ph> because another extension (<ph name="EXTENSION_NAME">$2<ex>My Cool Extension</ex></ph>) redirected it to <ph name="ACTUAL_REDIRECT_DESTINATION">$3<ex>https://www.google.com</ex></ph>.
+ </message>
+ <message name="IDS_EXTENSION_WARNINGS_REQUEST_HEADER_CONFLICT" desc="Warning message which indicates that two or more extensions tried to modify a network request in a conflicting way and the modification of this extension was ignored">
+ This extension failed to modify the request header "<ph name="HEADER_NAME">$1<ex>User-Agent</ex></ph>" of a network request because the modification conflicted with another extension (<ph name="EXTENSION_NAME">$2<ex>My Cool Extension</ex></ph>).
+ </message>
+ <message name="IDS_EXTENSION_WARNINGS_RESPONSE_HEADER_CONFLICT" desc="Warning message which indicates that two or more extensions tried to modify a network request in a conflicting way and the modification of this extension was ignored">
+ This extension failed to modify the response header "<ph name="HEADER_NAME">$1<ex>User-Agents</ex></ph>" of a network request because the modification conflicted with another extension (<ph name="EXTENSION_NAME">$2<ex>My Cool Extension</ex></ph>).
+ </message>
+ <message name="IDS_EXTENSION_WARNINGS_CREDENTIALS_CONFLICT" desc="Warning message which indicates that two or more extensions tried to modify a network request in a conflicting way and the modification of this extension was ignored">
+ This extension failed to provide credentials to a network request because another extension (<ph name="EXTENSION_NAME">$1<ex>My Cool Extension</ex></ph>) provided different credentials.
+ </message>
+ <message name="IDS_EXTENSION_WARNINGS_DOWNLOAD_FILENAME_CONFLICT" desc="Warning message which indicates that two or more extensions tried to determine the filename of a downloaded file in a conflicting way and the modification of this extension was ignored">
+ This extension failed to name the download "<ph name="ATTEMPTED_FILENAME">$1<ex>apple.png</ex></ph>" because another extension (<ph name="EXTENSION_NAME">$2<ex>My Cool Extension</ex></ph>) determined a different filename "<ph name="ACTUAL_FILENAME">$3<ex>banana.png</ex></ph>".
+ </message>
+ <message name="IDS_EXTENSION_WARNING_RELOAD_TOO_FREQUENT" desc="Warning message which indates that an extension got stuck in a reload loop.">
+ This extension reloaded itself too frequently.
+ </message>
+
+ <!-- Install related messages. Please keep alphabetized. -->
+ <message name="IDS_EXTENSION_INSTALL_PROCESS_CRASHED" desc="Error message in case package fails to install because a utility process crashed.">
+ Could not install package because a utility process crashed. Try restarting Chrome and trying again.
+ </message>
+ <message name="IDS_EXTENSION_PACKAGE_ERROR_CODE" desc="Error message in cases where we fail to install the extension because the crx file is invalid. For example, because the crx header or signature is invalid.">
+ Package is invalid: '<ph name="ERROR_CODE">$1<ex>error</ex></ph>'.
+ </message>
+ <message name="IDS_EXTENSION_PACKAGE_ERROR_MESSAGE" desc="The cases where we get an error message string back from some other component. The message might be in English, but these are typically developer errors, and the extension SDK is English.">
+ Package is invalid. Details: '<ph name="ERROR_MESSAGE">$1<ex>error</ex></ph>'.
+ </message>
+ <message name="IDS_EXTENSION_PACKAGE_INSTALL_ERROR" desc="Error message in case package fails to install because of some problem with the filesystem.">
+ Could not install package: '<ph name="ERROR_CODE">$1<ex>error</ex></ph>'
+ </message>
+ <if expr="is_win">
+ <message name="IDS_EXTENSION_UNPACK_FAILED" desc="On windows, it is possible to mount a disk without the root of that disk having a drive letter. The sandbox does not support this. See crbug/49530 .">
+ Can not unpack extension. To safely unpack an extension, there must be a path to your profile directory that starts with a drive letter and does not contain a junction, mount point, or symlink. No such path exists for your profile.
+ </message>
+ </if>
+ <if expr="not is_win">
+ <message name="IDS_EXTENSION_UNPACK_FAILED" desc="">
+ Can not unpack extension. To safely unpack an extension, there must be a path to your profile directory that does not contain a symlink. No such path exists for your profile.
+ </message>
+ </if>
+
+ <!-- Utility process names. Please keep alphabetized. -->
+ <message name="IDS_UTILITY_PROCESS_EXTENSION_UNPACKER_NAME" desc="The name of the utility process used for unpacking extensions.">
+ Extension Unpacker
+ </message>
+ <message name="IDS_UTILITY_PROCESS_MANIFEST_PARSER_NAME" desc="The name of the utility process used for parsing extension manifests.">
+ Extension Manifest Parser
+ </message>
+ </messages>
+ </release>
+</grit>
diff --git a/chromium/extensions/renderer/BUILD.gn b/chromium/extensions/renderer/BUILD.gn
new file mode 100644
index 00000000000..1b8f7d0efbe
--- /dev/null
+++ b/chromium/extensions/renderer/BUILD.gn
@@ -0,0 +1,44 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/features.gni")
+import("//extensions/extensions.gni")
+
+assert(enable_extensions)
+
+# GYP version: extensions/extensions.gyp:extensions_renderer
+source_set("renderer") {
+ sources = rebase_path(extensions_gypi_values.extensions_renderer_sources,
+ ".",
+ "//extensions")
+
+ configs += [
+ "//build/config:precompiled_headers",
+
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ "//build/config/compiler:no_size_t_to_int_warning",
+ ]
+
+ deps = [
+ "//chrome:resources",
+ "//components/guest_view/renderer",
+ "//content:resources",
+ "//extensions:extensions_resources",
+ "//gin",
+ "//mojo/edk/js",
+ "//mojo/public/js",
+ "//skia",
+ "//third_party/WebKit/public:blink",
+ ]
+
+ if (enable_wifi_display) {
+ wifi_display_sources = rebase_path(
+ extensions_gypi_values.extensions_renderer_sources_wifi_display,
+ ".",
+ "//extensions")
+ sources += wifi_display_sources
+
+ deps += [ "//third_party/wds:libwds" ]
+ }
+}
diff --git a/chromium/extensions/renderer/DEPS b/chromium/extensions/renderer/DEPS
new file mode 100644
index 00000000000..6d1446295a6
--- /dev/null
+++ b/chromium/extensions/renderer/DEPS
@@ -0,0 +1,25 @@
+include_rules = [
+ "+components/translate/core/language_detection",
+ "+content/public/child",
+ "+content/public/renderer",
+
+ "+gin",
+ "+mojo/edk/js",
+
+ "+third_party/skia/include/core",
+ "+third_party/cld_2/src/public/compact_lang_det.h",
+ "+third_party/cld_2/src/public/encodings.h",
+
+ "+third_party/WebKit/public/platform",
+ "+third_party/WebKit/public/web",
+
+ "+third_party/wds/src/libwds/public",
+
+ "+tools/json_schema_compiler",
+
+ "-v8",
+ "+v8/include",
+
+ "+storage/common/fileapi",
+]
+
diff --git a/chromium/extensions/renderer/OWNERS b/chromium/extensions/renderer/OWNERS
new file mode 100644
index 00000000000..899a192f3bf
--- /dev/null
+++ b/chromium/extensions/renderer/OWNERS
@@ -0,0 +1,2 @@
+# For V8 related changes, please consider also adding
+jochen@chromium.org
diff --git a/chromium/extensions/renderer/activity_log_converter_strategy.cc b/chromium/extensions/renderer/activity_log_converter_strategy.cc
new file mode 100644
index 00000000000..f76f9558e84
--- /dev/null
+++ b/chromium/extensions/renderer/activity_log_converter_strategy.cc
@@ -0,0 +1,83 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/activity_log_converter_strategy.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+namespace {
+
+// Summarize a V8 value. This performs a shallow conversion in all cases, and
+// returns only a string with a description of the value (e.g.,
+// "[HTMLElement]").
+scoped_ptr<base::Value> SummarizeV8Value(v8::Isolate* isolate,
+ v8::Local<v8::Object> object) {
+ v8::TryCatch try_catch(isolate);
+ v8::Isolate::DisallowJavascriptExecutionScope scope(
+ isolate, v8::Isolate::DisallowJavascriptExecutionScope::THROW_ON_FAILURE);
+ v8::Local<v8::String> name = v8::String::NewFromUtf8(isolate, "[");
+ if (object->IsFunction()) {
+ name =
+ v8::String::Concat(name, v8::String::NewFromUtf8(isolate, "Function"));
+ v8::Local<v8::Value> fname =
+ v8::Local<v8::Function>::Cast(object)->GetName();
+ if (fname->IsString() && v8::Local<v8::String>::Cast(fname)->Length()) {
+ name = v8::String::Concat(name, v8::String::NewFromUtf8(isolate, " "));
+ name = v8::String::Concat(name, v8::Local<v8::String>::Cast(fname));
+ name = v8::String::Concat(name, v8::String::NewFromUtf8(isolate, "()"));
+ }
+ } else {
+ name = v8::String::Concat(name, object->GetConstructorName());
+ }
+ name = v8::String::Concat(name, v8::String::NewFromUtf8(isolate, "]"));
+
+ if (try_catch.HasCaught()) {
+ return scoped_ptr<base::Value>(
+ new base::StringValue("[JS Execution Exception]"));
+ }
+
+ return scoped_ptr<base::Value>(
+ new base::StringValue(std::string(*v8::String::Utf8Value(name))));
+}
+
+} // namespace
+
+ActivityLogConverterStrategy::ActivityLogConverterStrategy() {}
+
+ActivityLogConverterStrategy::~ActivityLogConverterStrategy() {}
+
+bool ActivityLogConverterStrategy::FromV8Object(
+ v8::Local<v8::Object> value,
+ base::Value** out,
+ v8::Isolate* isolate,
+ const FromV8ValueCallback& callback) const {
+ return FromV8Internal(value, out, isolate, callback);
+}
+
+bool ActivityLogConverterStrategy::FromV8Array(
+ v8::Local<v8::Array> value,
+ base::Value** out,
+ v8::Isolate* isolate,
+ const FromV8ValueCallback& callback) const {
+ return FromV8Internal(value, out, isolate, callback);
+}
+
+bool ActivityLogConverterStrategy::FromV8Internal(
+ v8::Local<v8::Object> value,
+ base::Value** out,
+ v8::Isolate* isolate,
+ const FromV8ValueCallback& callback) const {
+ scoped_ptr<base::Value> parsed_value;
+ parsed_value = SummarizeV8Value(isolate, value);
+ *out = parsed_value.release();
+
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/activity_log_converter_strategy.h b/chromium/extensions/renderer/activity_log_converter_strategy.h
new file mode 100644
index 00000000000..fd7a1d2babf
--- /dev/null
+++ b/chromium/extensions/renderer/activity_log_converter_strategy.h
@@ -0,0 +1,50 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_ACTIVITY_LOG_CONVERTER_STRATEGY_H_
+#define EXTENSIONS_RENDERER_ACTIVITY_LOG_CONVERTER_STRATEGY_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "content/public/child/v8_value_converter.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+// This class is used by activity logger and extends behavior of
+// V8ValueConverter. It overwrites conversion of V8 arrays and objects. When
+// converting arrays and objects, we must not invoke any JS code which may
+// result in triggering activity logger. In such case, the log entries will be
+// generated due to V8 object conversion rather than extension activity.
+class ActivityLogConverterStrategy
+ : public content::V8ValueConverter::Strategy {
+ public:
+ typedef content::V8ValueConverter::Strategy::FromV8ValueCallback
+ FromV8ValueCallback;
+
+ ActivityLogConverterStrategy();
+ ~ActivityLogConverterStrategy() override;
+
+ // content::V8ValueConverter::Strategy implementation.
+ bool FromV8Object(v8::Local<v8::Object> value,
+ base::Value** out,
+ v8::Isolate* isolate,
+ const FromV8ValueCallback& callback) const override;
+ bool FromV8Array(v8::Local<v8::Array> value,
+ base::Value** out,
+ v8::Isolate* isolate,
+ const FromV8ValueCallback& callback) const override;
+
+ private:
+ bool FromV8Internal(v8::Local<v8::Object> value,
+ base::Value** out,
+ v8::Isolate* isolate,
+ const FromV8ValueCallback& callback) const;
+
+ DISALLOW_COPY_AND_ASSIGN(ActivityLogConverterStrategy);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_ACTIVITY_LOG_CONVERTER_STRATEGY_H_
diff --git a/chromium/extensions/renderer/activity_log_converter_strategy_unittest.cc b/chromium/extensions/renderer/activity_log_converter_strategy_unittest.cc
new file mode 100644
index 00000000000..fbb9597d661
--- /dev/null
+++ b/chromium/extensions/renderer/activity_log_converter_strategy_unittest.cc
@@ -0,0 +1,172 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "extensions/renderer/activity_log_converter_strategy.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "v8/include/v8.h"
+
+using content::V8ValueConverter;
+
+namespace extensions {
+
+class ActivityLogConverterStrategyTest : public testing::Test {
+ public:
+ ActivityLogConverterStrategyTest()
+ : isolate_(v8::Isolate::GetCurrent()),
+ handle_scope_(isolate_),
+ context_(isolate_, v8::Context::New(isolate_)),
+ context_scope_(context()) {}
+
+ protected:
+ void SetUp() override {
+ converter_.reset(V8ValueConverter::create());
+ strategy_.reset(new ActivityLogConverterStrategy());
+ converter_->SetFunctionAllowed(true);
+ converter_->SetStrategy(strategy_.get());
+ }
+
+ testing::AssertionResult VerifyNull(v8::Local<v8::Value> v8_value) {
+ scoped_ptr<base::Value> value(
+ converter_->FromV8Value(v8_value, context()));
+ if (value->IsType(base::Value::TYPE_NULL))
+ return testing::AssertionSuccess();
+ return testing::AssertionFailure();
+ }
+
+ testing::AssertionResult VerifyBoolean(v8::Local<v8::Value> v8_value,
+ bool expected) {
+ bool out;
+ scoped_ptr<base::Value> value(
+ converter_->FromV8Value(v8_value, context()));
+ if (value->IsType(base::Value::TYPE_BOOLEAN)
+ && value->GetAsBoolean(&out)
+ && out == expected)
+ return testing::AssertionSuccess();
+ return testing::AssertionFailure();
+ }
+
+ testing::AssertionResult VerifyInteger(v8::Local<v8::Value> v8_value,
+ int expected) {
+ int out;
+ scoped_ptr<base::Value> value(
+ converter_->FromV8Value(v8_value, context()));
+ if (value->IsType(base::Value::TYPE_INTEGER)
+ && value->GetAsInteger(&out)
+ && out == expected)
+ return testing::AssertionSuccess();
+ return testing::AssertionFailure();
+ }
+
+ testing::AssertionResult VerifyDouble(v8::Local<v8::Value> v8_value,
+ double expected) {
+ double out;
+ scoped_ptr<base::Value> value(
+ converter_->FromV8Value(v8_value, context()));
+ if (value->IsType(base::Value::TYPE_DOUBLE)
+ && value->GetAsDouble(&out)
+ && out == expected)
+ return testing::AssertionSuccess();
+ return testing::AssertionFailure();
+ }
+
+ testing::AssertionResult VerifyString(v8::Local<v8::Value> v8_value,
+ const std::string& expected) {
+ std::string out;
+ scoped_ptr<base::Value> value(
+ converter_->FromV8Value(v8_value, context()));
+ if (value->IsType(base::Value::TYPE_STRING)
+ && value->GetAsString(&out)
+ && out == expected)
+ return testing::AssertionSuccess();
+ return testing::AssertionFailure();
+ }
+
+ v8::Local<v8::Context> context() const {
+ return v8::Local<v8::Context>::New(isolate_, context_);
+ }
+
+ v8::Isolate* isolate_;
+ v8::HandleScope handle_scope_;
+ v8::Global<v8::Context> context_;
+ v8::Context::Scope context_scope_;
+ scoped_ptr<V8ValueConverter> converter_;
+ scoped_ptr<ActivityLogConverterStrategy> strategy_;
+};
+
+TEST_F(ActivityLogConverterStrategyTest, ConversionTest) {
+ const char* source = "(function() {"
+ "function foo() {}"
+ "return {"
+ "null: null,"
+ "true: true,"
+ "false: false,"
+ "positive_int: 42,"
+ "negative_int: -42,"
+ "zero: 0,"
+ "double: 88.8,"
+ "big_integral_double: 9007199254740992.0," // 2.0^53
+ "string: \"foobar\","
+ "empty_string: \"\","
+ "dictionary: {"
+ "foo: \"bar\","
+ "hot: \"dog\","
+ "},"
+ "empty_dictionary: {},"
+ "list: [ \"monkey\", \"balls\" ],"
+ "empty_list: [],"
+ "function: (0, function() {})," // ensure function is anonymous
+ "named_function: foo"
+ "};"
+ "})();";
+
+ v8::MicrotasksScope microtasks(
+ isolate_, v8::MicrotasksScope::kDoNotRunMicrotasks);
+ v8::Local<v8::Script> script(
+ v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source)));
+ v8::Local<v8::Object> v8_object = script->Run().As<v8::Object>();
+
+ EXPECT_TRUE(VerifyString(v8_object, "[Object]"));
+ EXPECT_TRUE(
+ VerifyNull(v8_object->Get(v8::String::NewFromUtf8(isolate_, "null"))));
+ EXPECT_TRUE(VerifyBoolean(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "true")), true));
+ EXPECT_TRUE(VerifyBoolean(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "false")), false));
+ EXPECT_TRUE(VerifyInteger(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "positive_int")), 42));
+ EXPECT_TRUE(VerifyInteger(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "negative_int")), -42));
+ EXPECT_TRUE(VerifyInteger(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "zero")), 0));
+ EXPECT_TRUE(VerifyDouble(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "double")), 88.8));
+ EXPECT_TRUE(VerifyDouble(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "big_integral_double")),
+ 9007199254740992.0));
+ EXPECT_TRUE(VerifyString(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "string")), "foobar"));
+ EXPECT_TRUE(VerifyString(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "empty_string")), ""));
+ EXPECT_TRUE(VerifyString(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "dictionary")),
+ "[Object]"));
+ EXPECT_TRUE(VerifyString(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "empty_dictionary")),
+ "[Object]"));
+ EXPECT_TRUE(VerifyString(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "list")), "[Array]"));
+ EXPECT_TRUE(VerifyString(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "empty_list")),
+ "[Array]"));
+ EXPECT_TRUE(VerifyString(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "function")),
+ "[Function]"));
+ EXPECT_TRUE(VerifyString(
+ v8_object->Get(v8::String::NewFromUtf8(isolate_, "named_function")),
+ "[Function foo()]"));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/automation/automation_api_helper.cc b/chromium/extensions/renderer/api/automation/automation_api_helper.cc
new file mode 100644
index 00000000000..ab10682f6db
--- /dev/null
+++ b/chromium/extensions/renderer/api/automation/automation_api_helper.cc
@@ -0,0 +1,90 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/api/automation/automation_api_helper.h"
+
+#include "content/public/renderer/render_view.h"
+#include "extensions/common/extension_messages.h"
+#include "third_party/WebKit/public/web/WebAXObject.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebElement.h"
+#include "third_party/WebKit/public/web/WebExceptionCode.h"
+#include "third_party/WebKit/public/web/WebFrame.h"
+#include "third_party/WebKit/public/web/WebNode.h"
+#include "third_party/WebKit/public/web/WebView.h"
+
+namespace extensions {
+
+AutomationApiHelper::AutomationApiHelper(content::RenderView* render_view)
+ : content::RenderViewObserver(render_view) {
+}
+
+AutomationApiHelper::~AutomationApiHelper() {
+}
+
+bool AutomationApiHelper::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(AutomationApiHelper, message)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_AutomationQuerySelector, OnQuerySelector)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void AutomationApiHelper::OnQuerySelector(int request_id,
+ int acc_obj_id,
+ const base::string16& selector) {
+ ExtensionHostMsg_AutomationQuerySelector_Error error;
+ if (!render_view() || !render_view()->GetWebView() ||
+ !render_view()->GetWebView()->mainFrame()) {
+ error.value = ExtensionHostMsg_AutomationQuerySelector_Error::kNoMainFrame;
+ Send(new ExtensionHostMsg_AutomationQuerySelector_Result(
+ routing_id(), request_id, error, 0));
+ return;
+ }
+ blink::WebDocument document =
+ render_view()->GetWebView()->mainFrame()->document();
+ if (document.isNull()) {
+ error.value =
+ ExtensionHostMsg_AutomationQuerySelector_Error::kNoDocument;
+ Send(new ExtensionHostMsg_AutomationQuerySelector_Result(
+ routing_id(), request_id, error, 0));
+ return;
+ }
+ blink::WebNode start_node = document;
+ if (acc_obj_id > 0) {
+ blink::WebAXObject start_acc_obj =
+ document.accessibilityObjectFromID(acc_obj_id);
+ if (start_acc_obj.isNull()) {
+ error.value =
+ ExtensionHostMsg_AutomationQuerySelector_Error::kNodeDestroyed;
+ Send(new ExtensionHostMsg_AutomationQuerySelector_Result(
+ routing_id(), request_id, error, 0));
+ return;
+ }
+
+ start_node = start_acc_obj.node();
+ while (start_node.isNull()) {
+ start_acc_obj = start_acc_obj.parentObject();
+ start_node = start_acc_obj.node();
+ }
+ }
+ blink::WebString web_selector(selector);
+ blink::WebExceptionCode ec = 0;
+ blink::WebElement result_element = start_node.querySelector(web_selector, ec);
+ int result_acc_obj_id = 0;
+ if (!ec && !result_element.isNull()) {
+ blink::WebAXObject result_acc_obj = result_element.accessibilityObject();
+ if (!result_acc_obj.isDetached()) {
+ while (result_acc_obj.accessibilityIsIgnored())
+ result_acc_obj = result_acc_obj.parentObject();
+
+ result_acc_obj_id = result_acc_obj.axID();
+ }
+ }
+ Send(new ExtensionHostMsg_AutomationQuerySelector_Result(
+ routing_id(), request_id, error, result_acc_obj_id));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/automation/automation_api_helper.h b/chromium/extensions/renderer/api/automation/automation_api_helper.h
new file mode 100644
index 00000000000..32aa2ef0dff
--- /dev/null
+++ b/chromium/extensions/renderer/api/automation/automation_api_helper.h
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_API_AUTOMATION_AUTOMATION_API_HELPER_H_
+#define EXTENSIONS_RENDERER_API_AUTOMATION_AUTOMATION_API_HELPER_H_
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "content/public/renderer/render_view_observer.h"
+
+namespace extensions {
+
+// Renderer-side implementation for chrome.automation API (for the few pieces
+// which aren't built in to the existing accessibility system).
+class AutomationApiHelper : public content::RenderViewObserver {
+ public:
+ explicit AutomationApiHelper(content::RenderView* render_view);
+ ~AutomationApiHelper() override;
+
+ private:
+ // RenderViewObserver implementation.
+ bool OnMessageReceived(const IPC::Message& message) override;
+
+ void OnQuerySelector(int acc_obj_id,
+ int request_id,
+ const base::string16& selector);
+
+ DISALLOW_COPY_AND_ASSIGN(AutomationApiHelper);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_AUTOMATION_AUTOMATION_API_HELPER_H_
diff --git a/chromium/extensions/renderer/api/display_source/OWNERS b/chromium/extensions/renderer/api/display_source/OWNERS
new file mode 100644
index 00000000000..f7e4f6f8751
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/OWNERS
@@ -0,0 +1,2 @@
+alexander.shalamov@intel.com
+mikhail.pozdnyakov@intel.com
diff --git a/chromium/extensions/renderer/api/display_source/display_source_session.cc b/chromium/extensions/renderer/api/display_source/display_source_session.cc
new file mode 100644
index 00000000000..20b14420dd9
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/display_source_session.cc
@@ -0,0 +1,43 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/api/display_source/display_source_session.h"
+
+#if defined(ENABLE_WIFI_DISPLAY)
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_session.h"
+#endif
+
+namespace extensions {
+
+DisplaySourceSessionParams::DisplaySourceSessionParams()
+ : auth_method(api::display_source::AUTHENTICATION_METHOD_NONE) {
+}
+
+DisplaySourceSessionParams::~DisplaySourceSessionParams() = default;
+
+DisplaySourceSession::DisplaySourceSession()
+ : state_(Idle) {
+}
+
+DisplaySourceSession::~DisplaySourceSession() = default;
+
+void DisplaySourceSession::SetNotificationCallbacks(
+ const base::Closure& terminated_callback,
+ const ErrorCallback& error_callback) {
+ DCHECK(terminated_callback_.is_null());
+ DCHECK(error_callback_.is_null());
+
+ terminated_callback_ = terminated_callback;
+ error_callback_ = error_callback;
+}
+
+scoped_ptr<DisplaySourceSession> DisplaySourceSessionFactory::CreateSession(
+ const DisplaySourceSessionParams& params) {
+#if defined(ENABLE_WIFI_DISPLAY)
+ return scoped_ptr<DisplaySourceSession>(new WiFiDisplaySession(params));
+#endif
+ return nullptr;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/display_source_session.h b/chromium/extensions/renderer/api/display_source/display_source_session.h
new file mode 100644
index 00000000000..922126d994b
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/display_source_session.h
@@ -0,0 +1,116 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_SESSION_H_
+#define EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_SESSION_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/api/display_source.h"
+#include "third_party/WebKit/public/web/WebDOMMediaStreamTrack.h"
+
+namespace content {
+class RenderFrame;
+}
+
+namespace extensions {
+
+using DisplaySourceAuthInfo = api::display_source::AuthenticationInfo;
+using DisplaySourceAuthMethod = api::display_source::AuthenticationMethod;
+using DisplaySourceErrorType = api::display_source::ErrorType;
+
+// This class represents a generic display source session interface.
+class DisplaySourceSession {
+ public:
+ using CompletionCallback =
+ base::Callback<void(bool success, const std::string& error_description)>;
+ using ErrorCallback =
+ base::Callback<void(DisplaySourceErrorType error_type,
+ const std::string& error_description)>;
+
+ // State flow is ether:
+ // 'Idle' -> 'Establishing' -> 'Established' -> 'Terminating' -> 'Idle'
+ // (terminated by Terminate() call)
+ // or
+ // 'Idle' -> 'Establishing' -> 'Established' -> 'Idle'
+ // (terminated from sink device or due to an error)
+ enum State {
+ Idle,
+ Establishing,
+ Established,
+ Terminating
+ };
+
+ virtual ~DisplaySourceSession();
+
+ // Starts the session.
+ // The session state should be set to 'Establishing' immediately after this
+ // method is called.
+ // |callback| : Called with 'success' flag set to 'true' if the session is
+ // successfully started (state should be set to 'Established')
+ //
+ // Called with 'success' flag set to 'false' if the session
+ // has failed to start (state should be set back to 'Idle').
+ // The 'error_description' argument contains description of
+ // an error that had caused the call falure.
+ virtual void Start(const CompletionCallback& callback) = 0;
+
+ // Terminates the session.
+ // The session state should be set to 'Terminating' immediately after this
+ // method is called.
+ // |callback| : Called with 'success' flag set to 'true' if the session is
+ // successfully terminated (state should be set to 'Idle')
+ //
+ // Called with 'success' flag set to 'false' if the session
+ // could not terminate (state should not be modified).
+ // The 'error_description' argument contains description of
+ // an error that had caused the call falure.
+ virtual void Terminate(const CompletionCallback& callback) = 0;
+
+ State state() const { return state_; }
+
+ // Sets the callbacks invoked to inform about the session's state changes.
+ // It is required to set the callbacks before the session is started.
+ // |terminated_callback| : Called when session was terminated (state
+ // should be set to 'Idle')
+ // |error_callback| : Called if a fatal error has occured and the session
+ // will be terminated soon for emergency reasons.
+ void SetNotificationCallbacks(const base::Closure& terminated_callback,
+ const ErrorCallback& error_callback);
+
+ protected:
+ DisplaySourceSession();
+
+ State state_;
+ base::Closure terminated_callback_;
+ ErrorCallback error_callback_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DisplaySourceSession);
+};
+
+struct DisplaySourceSessionParams {
+ DisplaySourceSessionParams();
+ ~DisplaySourceSessionParams();
+
+ int sink_id;
+ blink::WebMediaStreamTrack video_track;
+ blink::WebMediaStreamTrack audio_track;
+ DisplaySourceAuthMethod auth_method;
+ std::string auth_data;
+ content::RenderFrame* render_frame;
+};
+
+class DisplaySourceSessionFactory {
+ public:
+ static scoped_ptr<DisplaySourceSession> CreateSession(
+ const DisplaySourceSessionParams& params);
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DisplaySourceSessionFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_DISPLAY_SOURCE_SESSION_H_
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.cc b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.cc
new file mode 100644
index 00000000000..d17ca85056b
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.cc
@@ -0,0 +1,166 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/logging.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h"
+
+#include <cstring>
+
+namespace extensions {
+
+WiFiDisplayElementaryStreamDescriptor::WiFiDisplayElementaryStreamDescriptor(
+ const WiFiDisplayElementaryStreamDescriptor& other) {
+ if (!other.empty()) {
+ data_.reset(new uint8_t[other.size()]);
+ std::memcpy(data(), other.data(), other.size());
+ }
+}
+
+WiFiDisplayElementaryStreamDescriptor::WiFiDisplayElementaryStreamDescriptor(
+ WiFiDisplayElementaryStreamDescriptor&&) = default;
+
+WiFiDisplayElementaryStreamDescriptor::WiFiDisplayElementaryStreamDescriptor(
+ DescriptorTag tag,
+ uint8_t length)
+ : data_(new uint8_t[kHeaderSize + length]) {
+ uint8_t* p = data();
+ *p++ = tag;
+ *p++ = length;
+ DCHECK_EQ(private_data(), p);
+}
+
+WiFiDisplayElementaryStreamDescriptor::
+ ~WiFiDisplayElementaryStreamDescriptor() {}
+
+WiFiDisplayElementaryStreamDescriptor& WiFiDisplayElementaryStreamDescriptor::
+operator=(WiFiDisplayElementaryStreamDescriptor&&) = default;
+
+const uint8_t* WiFiDisplayElementaryStreamDescriptor::data() const {
+ return data_.get();
+}
+
+uint8_t* WiFiDisplayElementaryStreamDescriptor::data() {
+ return data_.get();
+}
+
+size_t WiFiDisplayElementaryStreamDescriptor::size() const {
+ if (empty())
+ return 0u;
+ return kHeaderSize + length();
+}
+
+WiFiDisplayElementaryStreamDescriptor
+WiFiDisplayElementaryStreamDescriptor::AVCTimingAndHRD::Create() {
+ WiFiDisplayElementaryStreamDescriptor descriptor(
+ DESCRIPTOR_TAG_AVC_TIMING_AND_HRD, 2u);
+ uint8_t* p = descriptor.private_data();
+ *p++ = (false << 7) | // hrd_management_valid_flag
+ (0x3Fu << 1) | // reserved (all six bits on)
+ (false << 0); // picture_and_timing_info_present
+ // No picture nor timing info bits.
+ *p++ = (false << 7) | // fixed_frame_rate_flag
+ (false << 6) | // temporal_poc_flag
+ (false << 5) | // picture_to_display_conversion_flag
+ (0x1Fu << 0); // reserved (all five bits on)
+ DCHECK_EQ(descriptor.end(), p);
+ return descriptor;
+}
+
+WiFiDisplayElementaryStreamDescriptor
+WiFiDisplayElementaryStreamDescriptor::AVCVideo::Create(
+ uint8_t profile_idc,
+ bool constraint_set0_flag,
+ bool constraint_set1_flag,
+ bool constraint_set2_flag,
+ uint8_t avc_compatible_flags,
+ uint8_t level_idc,
+ bool avc_still_present) {
+ const bool avc_24_hour_picture_flag = false;
+ WiFiDisplayElementaryStreamDescriptor descriptor(DESCRIPTOR_TAG_AVC_VIDEO,
+ 4u);
+ uint8_t* p = descriptor.private_data();
+ *p++ = profile_idc;
+ *p++ = (constraint_set0_flag << 7) | (constraint_set1_flag << 6) |
+ (constraint_set2_flag << 5) | (avc_compatible_flags << 0);
+ *p++ = level_idc;
+ *p++ = (avc_still_present << 7) | (avc_24_hour_picture_flag << 6) |
+ (0x3Fu << 0); // Reserved (all 6 bits on)
+ DCHECK_EQ(descriptor.end(), p);
+ return descriptor;
+}
+
+namespace {
+struct LPCMAudioStreamByte0 {
+ enum : uint8_t {
+ kBitsPerSampleShift = 3u,
+ kBitsPerSampleMask = ((1u << 2) - 1u) << kBitsPerSampleShift,
+ kEmphasisFlagShift = 0u,
+ kEmphasisFlagMask = 1u << kEmphasisFlagShift,
+ kReservedOnBitsShift = 1u,
+ kReservedOnBitsMask = ((1u << 2) - 1u) << kReservedOnBitsShift,
+ kSamplingFrequencyShift = 5u,
+ kSamplingFrequencyMask = ((1u << 3) - 1u) << kSamplingFrequencyShift,
+ };
+};
+
+struct LPCMAudioStreamByte1 {
+ enum : uint8_t {
+ kNumberOfChannelsShift = 5u,
+ kNumberOfChannelsMask = ((1u << 3) - 1u) << kNumberOfChannelsShift,
+ kReservedOnBitsShift = 0u,
+ kReservedOnBitsMask = ((1u << 4) - 1u) << kReservedOnBitsShift,
+ // The bit not listed above having a shift 4u is a reserved off bit.
+ };
+};
+} // namespace
+
+WiFiDisplayElementaryStreamDescriptor
+WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream::Create(
+ SamplingFrequency sampling_frequency,
+ BitsPerSample bits_per_sample,
+ bool emphasis_flag,
+ NumberOfChannels number_of_channels) {
+ WiFiDisplayElementaryStreamDescriptor descriptor(
+ DESCRIPTOR_TAG_LPCM_AUDIO_STREAM, 2u);
+ uint8_t* p = descriptor.private_data();
+ *p++ = (sampling_frequency << LPCMAudioStreamByte0::kSamplingFrequencyShift) |
+ (bits_per_sample << LPCMAudioStreamByte0::kBitsPerSampleShift) |
+ LPCMAudioStreamByte0::kReservedOnBitsMask |
+ (emphasis_flag << LPCMAudioStreamByte0::kEmphasisFlagShift);
+ *p++ = (number_of_channels << LPCMAudioStreamByte1::kNumberOfChannelsShift) |
+ LPCMAudioStreamByte1::kReservedOnBitsMask;
+ DCHECK_EQ(descriptor.end(), p);
+ return descriptor;
+}
+
+WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream::BitsPerSample
+WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream::bits_per_sample()
+ const {
+ return static_cast<BitsPerSample>(
+ (private_data()[0] & LPCMAudioStreamByte0::kBitsPerSampleMask) >>
+ LPCMAudioStreamByte0::kBitsPerSampleShift);
+}
+
+bool WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream::emphasis_flag()
+ const {
+ return (private_data()[0] & LPCMAudioStreamByte0::kEmphasisFlagMask) != 0u;
+}
+
+WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream::NumberOfChannels
+WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream::number_of_channels()
+ const {
+ return static_cast<NumberOfChannels>(
+ (private_data()[1] & LPCMAudioStreamByte1::kNumberOfChannelsMask) >>
+ LPCMAudioStreamByte1::kNumberOfChannelsShift);
+}
+
+WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream::SamplingFrequency
+WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream::sampling_frequency()
+ const {
+ return static_cast<SamplingFrequency>(
+ (private_data()[0] & LPCMAudioStreamByte0::kSamplingFrequencyMask) >>
+ LPCMAudioStreamByte0::kSamplingFrequencyShift);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h
new file mode 100644
index 00000000000..c149af851f8
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h
@@ -0,0 +1,122 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_DESCRIPTOR_H_
+#define EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_DESCRIPTOR_H_
+
+#include <stdint.h>
+#include <type_traits>
+
+#include "base/memory/scoped_ptr.h"
+
+namespace extensions {
+
+// WiFi Display elementary stream descriptors are used for passing descriptive
+// information about elementary streams to WiFiDisplayTransportStreamPacketizer
+// which then packetizes that information so that it can be passed to a remote
+// sink.
+class WiFiDisplayElementaryStreamDescriptor {
+ public:
+ enum { kHeaderSize = 2u };
+
+ enum DescriptorTag : uint8_t {
+ DESCRIPTOR_TAG_AVC_VIDEO = 0x28u,
+ DESCRIPTOR_TAG_AVC_TIMING_AND_HRD = 0x2A,
+ DESCRIPTOR_TAG_LPCM_AUDIO_STREAM = 0x83u,
+ };
+
+ // Make Google Test treat this class as a container.
+ using const_iterator = const uint8_t*;
+ using iterator = const uint8_t*;
+
+ WiFiDisplayElementaryStreamDescriptor(
+ const WiFiDisplayElementaryStreamDescriptor&);
+ WiFiDisplayElementaryStreamDescriptor(
+ WiFiDisplayElementaryStreamDescriptor&&);
+ ~WiFiDisplayElementaryStreamDescriptor();
+
+ WiFiDisplayElementaryStreamDescriptor& operator=(
+ WiFiDisplayElementaryStreamDescriptor&&);
+
+ const uint8_t* begin() const { return data(); }
+ const uint8_t* data() const;
+ bool empty() const { return !data_; }
+ const uint8_t* end() const { return data() + size(); }
+ size_t size() const;
+
+ DescriptorTag tag() const { return static_cast<DescriptorTag>(data()[0]); }
+ uint8_t length() const { return data()[1]; }
+
+ // AVC (Advanced Video Coding) timing and HRD (Hypothetical Reference
+ // Decoder) descriptor provides timing and HRD parameters for a video stream.
+ struct AVCTimingAndHRD {
+ static WiFiDisplayElementaryStreamDescriptor Create();
+ };
+
+ // AVC (Advanced Video Coding) video descriptor provides basic coding
+ // parameters for a video stream.
+ struct AVCVideo {
+ static WiFiDisplayElementaryStreamDescriptor Create(
+ uint8_t profile_idc,
+ bool constraint_set0_flag,
+ bool constraint_set1_flag,
+ bool constraint_set2_flag,
+ uint8_t avc_compatible_flags,
+ uint8_t level_idc,
+ bool avc_still_present);
+ };
+
+ class LPCMAudioStream;
+
+ protected:
+ WiFiDisplayElementaryStreamDescriptor(DescriptorTag tag, uint8_t length);
+
+ uint8_t* data();
+ const uint8_t* private_data() const { return data() + kHeaderSize; }
+ uint8_t* private_data() { return data() + kHeaderSize; }
+
+ private:
+ scoped_ptr<uint8_t[]> data_;
+};
+
+// LPCM (Linear pulse-code modulation) audio stream descriptor provides basic
+// coding parameters for a private WiFi Display LPCM audio stream.
+class WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream
+ : public WiFiDisplayElementaryStreamDescriptor {
+ public:
+ enum { kTag = DESCRIPTOR_TAG_LPCM_AUDIO_STREAM };
+ enum BitsPerSample : uint8_t { BITS_PER_SAMPLE_16 = 0u };
+ enum NumberOfChannels : uint8_t {
+ NUMBER_OF_CHANNELS_DUAL_MONO = 0u,
+ NUMBER_OF_CHANNELS_STEREO = 1u
+ };
+ enum SamplingFrequency : uint8_t {
+ SAMPLING_FREQUENCY_44_1K = 1u,
+ SAMPLING_FREQUENCY_48K = 2u
+ };
+
+ static WiFiDisplayElementaryStreamDescriptor Create(
+ SamplingFrequency sampling_frequency,
+ BitsPerSample bits_per_sample,
+ bool emphasis_flag,
+ NumberOfChannels number_of_channels);
+
+ BitsPerSample bits_per_sample() const;
+ bool emphasis_flag() const;
+ NumberOfChannels number_of_channels() const;
+ SamplingFrequency sampling_frequency() const;
+};
+
+// Subclasses of WiFiDisplayElementaryStreamDescriptor MUST NOT define new
+// member variables but only new non-virtual member functions which parse
+// the inherited data. This allows WiFiDisplayElementaryStreamDescriptor
+// pointers to be cast to subclass pointers.
+static_assert(
+ std::is_standard_layout<
+ WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream>::value,
+ "Forbidden memory layout for an elementary stream descriptor");
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_DESCRIPTOR_H_
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor_unittest.cc b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor_unittest.cc
new file mode 100644
index 00000000000..4e049db9ba7
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor_unittest.cc
@@ -0,0 +1,151 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+using LPCMAudioStreamDescriptor =
+ extensions::WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream;
+
+namespace extensions {
+
+namespace {
+
+// Copy constructors cannot always be tested by calling them directly as
+// a compiler is allowed to optimize copy constructor calls away in certain
+// cases (that is called return value optimization). Therefore, this helper
+// function is needed to really create a copy of an object.
+template <typename T>
+T Copy(const T& t) {
+ return t;
+}
+
+class Data : public std::vector<uint8_t> {
+ public:
+ template <size_t N>
+ explicit Data(const char (&str)[N]) {
+ EXPECT_EQ('\0', str[N - 1]);
+ insert(end(), str, str + N - 1);
+ }
+
+ bool operator==(const WiFiDisplayElementaryStreamDescriptor& rhs) const {
+ return size() == rhs.size() && std::equal(begin(), end(), rhs.begin());
+ }
+};
+
+TEST(WiFiDisplayElementaryStreamDescriptorTest, AVCTimingAndHRDDescriptor) {
+ using AVCTimingAndHRDDescriptor =
+ WiFiDisplayElementaryStreamDescriptor::AVCTimingAndHRD;
+ EXPECT_EQ(Data("\x2A\x02\x7E\x1F"), AVCTimingAndHRDDescriptor::Create());
+ EXPECT_EQ(Data("\x2A\x02\x7E\x1F"),
+ Copy(AVCTimingAndHRDDescriptor::Create()));
+}
+
+TEST(WiFiDisplayElementaryStreamDescriptorTest, AVCVideoDescriptor) {
+ using AVCVideoDescriptor = WiFiDisplayElementaryStreamDescriptor::AVCVideo;
+ EXPECT_EQ(
+ Data("\x28\x04\x00\x00\x00\x3F"),
+ AVCVideoDescriptor::Create(0x0u, false, false, false, 0x0u, 0x0u, false));
+ EXPECT_EQ(Data("\x28\x04\x00\x00\x00\x3F"),
+ Copy(AVCVideoDescriptor::Create(0x0u, false, false, false, 0x0u,
+ 0x0u, false)));
+ EXPECT_EQ(Data("\x28\x04\xFF\x00\x00\x3F"),
+ AVCVideoDescriptor::Create(0xFFu, false, false, false, 0x0u, 0x0u,
+ false));
+ EXPECT_EQ(
+ Data("\x28\x04\x00\x80\x00\x3F"),
+ AVCVideoDescriptor::Create(0x0u, true, false, false, 0x0u, 0x0u, false));
+ EXPECT_EQ(
+ Data("\x28\x04\x00\x40\x00\x3F"),
+ AVCVideoDescriptor::Create(0x0u, false, true, false, 0x0u, 0x0u, false));
+ EXPECT_EQ(
+ Data("\x28\x04\x00\x20\x00\x3F"),
+ AVCVideoDescriptor::Create(0x0u, false, false, true, 0x0u, 0x0u, false));
+ EXPECT_EQ(Data("\x28\x04\x00\x1F\x00\x3F"),
+ AVCVideoDescriptor::Create(0x0u, false, false, false, 0x1Fu, 0x0u,
+ false));
+ EXPECT_EQ(Data("\x28\x04\x00\x00\xFF\x3F"),
+ AVCVideoDescriptor::Create(0x0u, false, false, false, 0x0u, 0xFFu,
+ false));
+ EXPECT_EQ(
+ Data("\x28\x04\x00\x00\x00\xBF"),
+ AVCVideoDescriptor::Create(0x0u, false, false, false, 0x0u, 0x0u, true));
+}
+
+class LPCMAudioStreamDescriptorTest
+ : public testing::TestWithParam<
+ testing::tuple<LPCMAudioStreamDescriptor::SamplingFrequency,
+ LPCMAudioStreamDescriptor::BitsPerSample,
+ bool,
+ LPCMAudioStreamDescriptor::NumberOfChannels,
+ Data>> {
+ protected:
+ LPCMAudioStreamDescriptorTest()
+ : sampling_frequency_(testing::get<0>(GetParam())),
+ bits_per_sample_(testing::get<1>(GetParam())),
+ emphasis_flag_(testing::get<2>(GetParam())),
+ number_of_channels_(testing::get<3>(GetParam())),
+ expected_data_(testing::get<4>(GetParam())),
+ descriptor_(LPCMAudioStreamDescriptor::Create(sampling_frequency_,
+ bits_per_sample_,
+ emphasis_flag_,
+ number_of_channels_)) {}
+
+ const LPCMAudioStreamDescriptor::SamplingFrequency sampling_frequency_;
+ const LPCMAudioStreamDescriptor::BitsPerSample bits_per_sample_;
+ const bool emphasis_flag_;
+ const LPCMAudioStreamDescriptor::NumberOfChannels number_of_channels_;
+ const Data expected_data_;
+ const WiFiDisplayElementaryStreamDescriptor descriptor_;
+};
+
+TEST_P(LPCMAudioStreamDescriptorTest, Create) {
+ EXPECT_EQ(expected_data_, descriptor_);
+ EXPECT_EQ(expected_data_, Copy(descriptor_));
+}
+
+TEST_P(LPCMAudioStreamDescriptorTest, Accessors) {
+ ASSERT_EQ(LPCMAudioStreamDescriptor::kTag, descriptor_.tag());
+ const LPCMAudioStreamDescriptor& descriptor =
+ *static_cast<const LPCMAudioStreamDescriptor*>(&descriptor_);
+ EXPECT_EQ(sampling_frequency_, descriptor.sampling_frequency());
+ EXPECT_EQ(bits_per_sample_, descriptor.bits_per_sample());
+ EXPECT_EQ(emphasis_flag_, descriptor.emphasis_flag());
+ EXPECT_EQ(number_of_channels_, descriptor.number_of_channels());
+}
+
+INSTANTIATE_TEST_CASE_P(
+ WiFiDisplayElementaryStreamDescriptorTests,
+ LPCMAudioStreamDescriptorTest,
+ testing::Values(testing::make_tuple(
+ LPCMAudioStreamDescriptor::SAMPLING_FREQUENCY_44_1K,
+ LPCMAudioStreamDescriptor::BITS_PER_SAMPLE_16,
+ false,
+ LPCMAudioStreamDescriptor::NUMBER_OF_CHANNELS_DUAL_MONO,
+ Data("\x83\x02\x26\x0F")),
+ testing::make_tuple(
+ LPCMAudioStreamDescriptor::SAMPLING_FREQUENCY_48K,
+ LPCMAudioStreamDescriptor::BITS_PER_SAMPLE_16,
+ false,
+ LPCMAudioStreamDescriptor::NUMBER_OF_CHANNELS_DUAL_MONO,
+ Data("\x83\x02\x46\x0F")),
+ testing::make_tuple(
+ LPCMAudioStreamDescriptor::SAMPLING_FREQUENCY_44_1K,
+ LPCMAudioStreamDescriptor::BITS_PER_SAMPLE_16,
+ true,
+ LPCMAudioStreamDescriptor::NUMBER_OF_CHANNELS_DUAL_MONO,
+ Data("\x83\x02\x27\x0F")),
+ testing::make_tuple(
+ LPCMAudioStreamDescriptor::SAMPLING_FREQUENCY_44_1K,
+ LPCMAudioStreamDescriptor::BITS_PER_SAMPLE_16,
+ false,
+ LPCMAudioStreamDescriptor::NUMBER_OF_CHANNELS_STEREO,
+ Data("\x83\x02\x26\x2F"))));
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.cc b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.cc
new file mode 100644
index 00000000000..2c84885db87
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.cc
@@ -0,0 +1,46 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h"
+
+#include <utility>
+
+namespace extensions {
+
+WiFiDisplayElementaryStreamInfo::WiFiDisplayElementaryStreamInfo(
+ ElementaryStreamType type)
+ : type_(type) {}
+
+WiFiDisplayElementaryStreamInfo::WiFiDisplayElementaryStreamInfo(
+ ElementaryStreamType type,
+ DescriptorVector descriptors)
+ : descriptors_(std::move(descriptors)), type_(type) {}
+
+WiFiDisplayElementaryStreamInfo::WiFiDisplayElementaryStreamInfo(
+ const WiFiDisplayElementaryStreamInfo&) = default;
+
+WiFiDisplayElementaryStreamInfo::WiFiDisplayElementaryStreamInfo(
+ WiFiDisplayElementaryStreamInfo&&) = default;
+
+WiFiDisplayElementaryStreamInfo::~WiFiDisplayElementaryStreamInfo() {}
+
+WiFiDisplayElementaryStreamInfo& WiFiDisplayElementaryStreamInfo::operator=(
+ WiFiDisplayElementaryStreamInfo&&) = default;
+
+void WiFiDisplayElementaryStreamInfo::AddDescriptor(
+ WiFiDisplayElementaryStreamDescriptor descriptor) {
+ descriptors_.emplace_back(std::move(descriptor));
+}
+
+const WiFiDisplayElementaryStreamDescriptor*
+WiFiDisplayElementaryStreamInfo::FindDescriptor(
+ DescriptorTag descriptor_tag) const {
+ for (const auto& descriptor : descriptors()) {
+ if (descriptor.tag() == descriptor_tag)
+ return &descriptor;
+ }
+ return nullptr;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h
new file mode 100644
index 00000000000..a0e63a82b01
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h
@@ -0,0 +1,58 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_INFO_H_
+#define EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_INFO_H_
+
+#include <stdint.h>
+#include <vector>
+
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h"
+
+namespace extensions {
+
+// WiFi Display elementary stream info is a container for elementary stream
+// information and is used for passing that information to a WiFi Display
+// transport stream packetizer.
+class WiFiDisplayElementaryStreamInfo {
+ public:
+ using DescriptorVector = std::vector<WiFiDisplayElementaryStreamDescriptor>;
+ using DescriptorTag = WiFiDisplayElementaryStreamDescriptor::DescriptorTag;
+
+ enum ElementaryStreamType : uint8_t {
+ AUDIO_AAC = 0x0Fu,
+ AUDIO_AC3 = 0x81u,
+ AUDIO_LPCM = 0x83u,
+ VIDEO_H264 = 0x1Bu,
+ };
+
+ explicit WiFiDisplayElementaryStreamInfo(ElementaryStreamType type);
+ WiFiDisplayElementaryStreamInfo(ElementaryStreamType type,
+ DescriptorVector descriptors);
+ WiFiDisplayElementaryStreamInfo(const WiFiDisplayElementaryStreamInfo&);
+ WiFiDisplayElementaryStreamInfo(WiFiDisplayElementaryStreamInfo&&);
+ ~WiFiDisplayElementaryStreamInfo();
+
+ WiFiDisplayElementaryStreamInfo& operator=(WiFiDisplayElementaryStreamInfo&&);
+
+ const DescriptorVector& descriptors() const { return descriptors_; }
+ ElementaryStreamType type() const { return type_; }
+
+ void AddDescriptor(WiFiDisplayElementaryStreamDescriptor descriptor);
+ const WiFiDisplayElementaryStreamDescriptor* FindDescriptor(
+ DescriptorTag descriptor_tag) const;
+ template <typename Descriptor>
+ const Descriptor* FindDescriptor() const {
+ return static_cast<const Descriptor*>(
+ FindDescriptor(static_cast<DescriptorTag>(Descriptor::kTag)));
+ }
+
+ private:
+ DescriptorVector descriptors_;
+ ElementaryStreamType type_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_INFO_H_
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.cc b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.cc
new file mode 100644
index 00000000000..39a6e3fcaf8
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.cc
@@ -0,0 +1,188 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.h"
+
+#include <cstring>
+
+#include "base/logging.h"
+
+namespace extensions {
+namespace {
+
+// Code and parameters related to the Packetized Elementary Stream (PES)
+// specification.
+namespace pes {
+
+const size_t kOptionalHeaderBaseSize = 3u;
+const size_t kPacketHeaderBaseSize = 6u;
+const size_t kTimeStampSize = 5u;
+const size_t kPacketHeaderMaxSize =
+ kPacketHeaderBaseSize + kOptionalHeaderBaseSize + 2u * kTimeStampSize;
+
+const size_t kUnitDataAlignment = 4u;
+
+size_t FillInTimeStamp(uint8_t* dst,
+ uint8_t pts_dts_indicator,
+ const base::TimeTicks& ts) {
+ // Convert to the number of 90 kHz ticks since some epoch.
+ // Always round up so that the number of ticks is never smaller than
+ // the number of program clock reference base ticks (which is not rounded
+ // because program clock reference is encoded with higher precision).
+ const uint64_t us =
+ static_cast<uint64_t>((ts - base::TimeTicks()).InMicroseconds());
+ const uint64_t n = (us * 90u + 999u) / 1000u;
+
+ // Expand PTS DTS indicator and a 33 bit time stamp to 40 bits:
+ // * 4 PTS DTS indicator bits, 3 time stamp bits, 1 on bit
+ // * 15 time stamp bits, 1 on bit
+ // * 15 time stamp bits, 1 on bit
+ size_t i = 0u;
+ dst[i++] = (pts_dts_indicator << 4) | (((n >> 30) & 0x7u) << 1) | (0x1u << 0);
+ dst[i++] = (n >> 22) & 0xFFu;
+ dst[i++] = (((n >> 15) & 0x7Fu) << 1) | (0x1u << 0);
+ dst[i++] = (n >> 7) & 0xFFu;
+ dst[i++] = (((n >> 0) & 0x7Fu) << 1) | (0x1u << 0);
+ DCHECK_EQ(i, kTimeStampSize);
+ return i;
+}
+
+size_t FillInOptionalHeader(uint8_t* dst,
+ const base::TimeTicks& pts,
+ const base::TimeTicks& dts,
+ size_t unit_header_size) {
+ size_t i = 0u;
+ dst[i++] = (0x2u << 6) | // Marker bits (0b10)
+ (0x0u << 4) | // Scrambling control (0b00 for not)
+ (0x0u << 3) | // Priority
+ (0x0u << 2) | // Data alignment indicator (0b0 for not)
+ (0x0u << 1) | // Copyright (0b0 for not)
+ (0x0u << 0); // Original (0b0 for copy)
+ const uint8_t pts_dts_indicator =
+ !pts.is_null() ? (!dts.is_null() ? 0x3u : 0x2u) : 0x0u;
+ dst[i++] = (pts_dts_indicator << 6) | // PTS DTS indicator
+ (0x0u << 5) | // ESCR flag
+ (0x0u << 4) | // ES rate flag
+ (0x0u << 3) | // DSM trick mode flag
+ (0x0u << 2) | // Additional copy info flag
+ (0x0u << 1) | // CRC flag
+ (0x0u << 0); // Extension flag
+ const size_t header_length_index = i++;
+ const size_t optional_header_base_end_index = i;
+ DCHECK_EQ(i, kOptionalHeaderBaseSize);
+
+ // Optional fields:
+ // PTS and DTS.
+ if (!pts.is_null()) {
+ i += FillInTimeStamp(&dst[i], pts_dts_indicator, pts);
+ if (!dts.is_null())
+ i += FillInTimeStamp(&dst[i], 0x1u, dts);
+ }
+
+ // Stuffing bytes (for unit data alignment).
+ const size_t remainder =
+ (kPacketHeaderBaseSize + i + unit_header_size) % kUnitDataAlignment;
+ if (remainder) {
+ const size_t n = kUnitDataAlignment - remainder;
+ std::memset(&dst[i], 0xFF, n);
+ i += n;
+ }
+
+ dst[header_length_index] = i - optional_header_base_end_index;
+ return i;
+}
+
+size_t FillInPacketHeader(uint8_t* dst,
+ uint8_t stream_id,
+ const base::TimeTicks& pts,
+ const base::TimeTicks& dts,
+ size_t unit_header_size,
+ size_t unit_size) {
+ // Reserve space for packet header base.
+ size_t i = kPacketHeaderBaseSize;
+ const size_t header_base_end_index = i;
+
+ // Fill in optional header.
+ i += FillInOptionalHeader(&dst[i], pts, dts, unit_header_size);
+
+ // Compute packet length.
+ size_t packet_length =
+ (i - header_base_end_index) + unit_header_size + unit_size;
+ if (packet_length >> 16) {
+ // The packet length is too large to be represented. That should only
+ // happen for video frames for which the packet length is not mandatory
+ // but may be set to 0, too.
+ DCHECK_GE(static_cast<unsigned>(stream_id),
+ WiFiDisplayElementaryStreamPacketizer::kFirstVideoStreamId);
+ DCHECK_LE(static_cast<unsigned>(stream_id),
+ WiFiDisplayElementaryStreamPacketizer::kLastVideoStreamId);
+ packet_length = 0u;
+ }
+
+ // Fill in packet header base.
+ size_t j = 0u;
+ dst[j++] = 0x00u; // Packet start code prefix (0x000001 in three bytes).
+ dst[j++] = 0x00u; //
+ dst[j++] = 0x01u; //
+ dst[j++] = stream_id;
+ dst[j++] = packet_length >> 8;
+ dst[j++] = packet_length & 0xFFu;
+ DCHECK_EQ(j, kPacketHeaderBaseSize);
+
+ return i;
+}
+
+} // namespace pes
+
+} // namespace
+
+WiFiDisplayElementaryStreamPacket::WiFiDisplayElementaryStreamPacket(
+ const HeaderBuffer& header_data,
+ size_t header_size,
+ const uint8_t* unit_header_data,
+ size_t unit_header_size,
+ const uint8_t* unit_data,
+ size_t unit_size)
+ : header_(header_buffer_, header_size),
+ unit_header_(unit_header_data, unit_header_size),
+ unit_(unit_data, unit_size) {
+ // Copy the actual header data bytes from the |header_data| argument to
+ // the |header_buffer_| member buffer used in the member initialization list.
+ std::memcpy(header_buffer_, header_data, header_.size());
+}
+
+WiFiDisplayElementaryStreamPacket::WiFiDisplayElementaryStreamPacket(
+ WiFiDisplayElementaryStreamPacket&& other)
+ : header_(header_buffer_, other.header().size()),
+ unit_header_(other.unit_header().data(), other.unit_header().size()),
+ unit_(other.unit().data(), other.unit().size()) {
+ // Copy the actual header data bytes from |other.header().data()| to
+ // the |header_buffer_| member buffer used in the member initialization list.
+ std::memcpy(header_buffer_, other.header().data(), header_.size());
+}
+
+// static
+WiFiDisplayElementaryStreamPacket
+WiFiDisplayElementaryStreamPacketizer::EncodeElementaryStreamUnit(
+ uint8_t stream_id,
+ const uint8_t* unit_header_data,
+ size_t unit_header_size,
+ const uint8_t* unit_data,
+ size_t unit_size,
+ const base::TimeTicks& pts,
+ const base::TimeTicks& dts) {
+ if (!unit_header_data) {
+ DCHECK_EQ(0u, unit_header_size);
+ unit_header_data = unit_data;
+ }
+
+ uint8_t header_data[pes::kPacketHeaderMaxSize];
+ size_t header_size = pes::FillInPacketHeader(header_data, stream_id, pts, dts,
+ unit_header_size, unit_size);
+ return WiFiDisplayElementaryStreamPacket(header_data, header_size,
+ unit_header_data, unit_header_size,
+ unit_data, unit_size);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.h b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.h
new file mode 100644
index 00000000000..4201a58287d
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.h
@@ -0,0 +1,72 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_PACKETIZER_H_
+#define EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_PACKETIZER_H_
+
+#include "base/time/time.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_stream_packet_part.h"
+
+namespace extensions {
+
+// WiFi Display elementary stream packet represents a Packetized Elementary
+// Stream (PES) packet containing WiFi Display elementary stream unit data.
+class WiFiDisplayElementaryStreamPacket {
+ public:
+ using HeaderBuffer = uint8_t[19];
+
+ WiFiDisplayElementaryStreamPacket(const HeaderBuffer& header_data,
+ size_t header_size,
+ const uint8_t* unit_header_data,
+ size_t unit_header_size,
+ const uint8_t* unit_data,
+ size_t unit_size);
+ // WiFiDisplayElementaryStreamPacketizer::EncodeElementaryStreamUnit returns
+ // WiFiDisplayElementaryStreamPacket so WiFiDisplayElementaryStreamPacket
+ // must be move constructible (as it is not copy constructible).
+ // A compiler should however use return value optimization and elide each
+ // call to this move constructor.
+ WiFiDisplayElementaryStreamPacket(WiFiDisplayElementaryStreamPacket&& other);
+
+ const WiFiDisplayStreamPacketPart& header() const { return header_; }
+ const WiFiDisplayStreamPacketPart& unit_header() const {
+ return unit_header_;
+ }
+ const WiFiDisplayStreamPacketPart& unit() const { return unit_; }
+
+ private:
+ HeaderBuffer header_buffer_;
+ WiFiDisplayStreamPacketPart header_;
+ WiFiDisplayStreamPacketPart unit_header_;
+ WiFiDisplayStreamPacketPart unit_;
+
+ DISALLOW_COPY_AND_ASSIGN(WiFiDisplayElementaryStreamPacket);
+};
+
+// The WiFi Display elementary stream packetizer packetizes unit buffers to
+// Packetized Elementary Stream (PES) packets.
+// It is used internally by a WiFi Display transport stream packetizer.
+class WiFiDisplayElementaryStreamPacketizer {
+ public:
+ enum : uint8_t {
+ kPrivateStream1Id = 0xBDu,
+ kFirstAudioStreamId = 0xC0u,
+ kLastAudioStreamId = 0xDFu,
+ kFirstVideoStreamId = 0xE0u,
+ kLastVideoStreamId = 0xEFu,
+ };
+
+ static WiFiDisplayElementaryStreamPacket EncodeElementaryStreamUnit(
+ uint8_t stream_id,
+ const uint8_t* unit_header_data,
+ size_t unit_header_size,
+ const uint8_t* unit_data,
+ size_t unit_size,
+ const base::TimeTicks& pts,
+ const base::TimeTicks& dts);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_PACKETIZER_H_
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.cc b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.cc
new file mode 100644
index 00000000000..f77571d58a2
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.cc
@@ -0,0 +1,247 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.h"
+
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "content/public/renderer/media_stream_utils.h"
+
+namespace extensions {
+
+namespace {
+
+const char kErrorNoVideoFormatData[] =
+ "Failed to get video format data from the given MediaStreamTrack object";
+const char kErrorSinkCannotPlayVideo[] =
+ "The sink cannot play video from the given MediaStreamTrack object";
+const char kErrorSinkCannotPlayAudio[] =
+ "The sink cannot play audio from the given MediaStreamTrack object";
+} // namespace
+
+WiFiDisplayMediaManager::WiFiDisplayMediaManager(
+ const blink::WebMediaStreamTrack& video_track,
+ const blink::WebMediaStreamTrack& audio_track,
+ const ErrorCallback& error_callback)
+ : video_track_(video_track),
+ audio_track_(audio_track),
+ error_callback_(error_callback) {
+ DCHECK(!video_track.isNull() || !audio_track.isNull());
+ DCHECK(!error_callback_.is_null());
+}
+
+WiFiDisplayMediaManager::~WiFiDisplayMediaManager() {
+}
+
+void WiFiDisplayMediaManager::Play() {
+ NOTIMPLEMENTED();
+}
+
+void WiFiDisplayMediaManager::Teardown() {
+ NOTIMPLEMENTED();
+}
+
+void WiFiDisplayMediaManager::Pause() {
+ NOTIMPLEMENTED();
+}
+
+bool WiFiDisplayMediaManager::IsPaused() const {
+ NOTIMPLEMENTED();
+ return true;
+}
+
+wds::SessionType WiFiDisplayMediaManager::GetSessionType() const {
+ uint16_t session_type = 0;
+ if (!video_track_.isNull())
+ session_type |= wds::VideoSession;
+
+ if (!audio_track_.isNull())
+ session_type |= wds::AudioSession;
+
+ return static_cast<wds::SessionType>(session_type);
+}
+
+void WiFiDisplayMediaManager::SetSinkRtpPorts(int port1, int port2) {
+ sink_rtp_ports_ = std::pair<int, int>(port1, port2);
+}
+
+std::pair<int, int> WiFiDisplayMediaManager::GetSinkRtpPorts() const {
+ return sink_rtp_ports_;
+}
+
+int WiFiDisplayMediaManager::GetLocalRtpPort() const {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+namespace {
+struct VideoFormat {
+ wds::RateAndResolution rr;
+ int width;
+ int height;
+ int frame_rate;
+};
+
+const VideoFormat cea_table[] = {
+ {wds::CEA640x480p60, 640, 480, 60},
+ {wds::CEA720x480p60, 720, 480, 60},
+ {wds::CEA720x576p50, 720, 576, 50},
+ {wds::CEA1280x720p30, 1280, 720, 30},
+ {wds::CEA1280x720p60, 1280, 720, 60},
+ {wds::CEA1920x1080p30, 1920, 1080, 30},
+ {wds::CEA1920x1080p60, 1920, 1080, 60},
+ {wds::CEA1280x720p25, 1280, 720, 25},
+ {wds::CEA1280x720p50, 1280, 720, 50},
+ {wds::CEA1920x1080p25, 1920, 1080, 25},
+ {wds::CEA1920x1080p50, 1920, 1080, 50},
+ {wds::CEA1280x720p24, 1280, 720, 24},
+ {wds::CEA1920x1080p24, 1920, 1080, 24}
+};
+
+const VideoFormat vesa_table[] = {
+ {wds::VESA800x600p30, 800, 600, 30},
+ {wds::VESA800x600p60, 800, 600, 60},
+ {wds::VESA1024x768p30, 1024, 768, 30},
+ {wds::VESA1024x768p60, 1024, 768, 60},
+ {wds::VESA1152x864p30, 1152, 864, 30},
+ {wds::VESA1152x864p60, 1152, 864, 60},
+ {wds::VESA1280x768p30, 1280, 768, 30},
+ {wds::VESA1280x768p60, 1280, 768, 60},
+ {wds::VESA1280x800p30, 1280, 800, 30},
+ {wds::VESA1280x800p60, 1280, 800, 60},
+ {wds::VESA1360x768p30, 1360, 768, 30},
+ {wds::VESA1360x768p60, 1360, 768, 60},
+ {wds::VESA1366x768p30, 1366, 768, 30},
+ {wds::VESA1366x768p60, 1366, 768, 60},
+ {wds::VESA1280x1024p30, 1280, 1024, 30},
+ {wds::VESA1280x1024p60, 1280, 1024, 60},
+ {wds::VESA1400x1050p30, 1400, 1050, 30},
+ {wds::VESA1400x1050p60, 1400, 1050, 60},
+ {wds::VESA1440x900p30, 1440, 900, 30},
+ {wds::VESA1440x900p60, 1440, 900, 60},
+ {wds::VESA1600x900p30, 1600, 900, 30},
+ {wds::VESA1600x900p60, 1600, 900, 60},
+ {wds::VESA1600x1200p30, 1600, 1200, 30},
+ {wds::VESA1600x1200p60, 1600, 1200, 60},
+ {wds::VESA1680x1024p30, 1680, 1024, 30},
+ {wds::VESA1680x1024p60, 1680, 1024, 60},
+ {wds::VESA1680x1050p30, 1680, 1050, 30},
+ {wds::VESA1680x1050p60, 1680, 1050, 60},
+ {wds::VESA1920x1200p30, 1920, 1200, 30}
+};
+
+const VideoFormat hh_table[] = {
+ {wds::HH800x480p30, 800, 480, 30},
+ {wds::HH800x480p60, 800, 480, 60},
+ {wds::HH854x480p30, 854, 480, 30},
+ {wds::HH854x480p60, 854, 480, 60},
+ {wds::HH864x480p30, 864, 480, 30},
+ {wds::HH864x480p60, 864, 480, 60},
+ {wds::HH640x360p30, 640, 360, 30},
+ {wds::HH640x360p60, 640, 360, 60},
+ {wds::HH960x540p30, 960, 540, 30},
+ {wds::HH960x540p60, 960, 540, 60},
+ {wds::HH848x480p30, 848, 480, 30},
+ {wds::HH848x480p60, 848, 480, 60}
+};
+
+template <wds::ResolutionType type, unsigned N>
+bool FindRateResolution(const media::VideoCaptureFormat* format,
+ const wds::RateAndResolutionsBitmap& bitmap,
+ const VideoFormat (&table)[N],
+ wds::H264VideoFormat* result /*out*/) {
+ for (unsigned i = 0; i < N; ++i) {
+ if (bitmap.test(table[i].rr)) {
+ if (format->frame_size.width() == table[i].width &&
+ format->frame_size.height() == table[i].height &&
+ format->frame_rate == table[i].frame_rate) {
+ result->rate_resolution = table[i].rr;
+ result->type = type;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool FindOptimalFormat(
+ const media::VideoCaptureFormat* capture_format,
+ const std::vector<wds::H264VideoCodec>& sink_supported_codecs,
+ wds::H264VideoFormat* result /*out*/) {
+ DCHECK(result);
+ for (const wds::H264VideoCodec& codec : sink_supported_codecs) {
+ bool found =
+ FindRateResolution<wds::CEA>(
+ capture_format, codec.cea_rr, cea_table, result) ||
+ FindRateResolution<wds::VESA>(
+ capture_format, codec.vesa_rr, vesa_table, result) ||
+ FindRateResolution<wds::HH>(
+ capture_format, codec.hh_rr, hh_table, result);
+ if (found) {
+ result->profile = codec.profile;
+ result->level = codec.level;
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace
+
+wds::H264VideoFormat WiFiDisplayMediaManager::GetOptimalVideoFormat() const {
+ return optimal_video_format_;
+}
+
+void WiFiDisplayMediaManager::SendIDRPicture() {
+ NOTIMPLEMENTED();
+}
+
+std::string WiFiDisplayMediaManager::GetSessionId() const {
+ return base::RandBytesAsString(8);
+}
+
+bool WiFiDisplayMediaManager::InitOptimalVideoFormat(
+ const wds::NativeVideoFormat& sink_native_format,
+ const std::vector<wds::H264VideoCodec>& sink_supported_codecs) {
+ const media::VideoCaptureFormat* capture_format =
+ content::GetCurrentVideoTrackFormat(video_track_);
+ if (!capture_format) {
+ error_callback_.Run(kErrorNoVideoFormatData);
+ return false;
+ }
+
+ if (!FindOptimalFormat(
+ capture_format, sink_supported_codecs, &optimal_video_format_)) {
+ error_callback_.Run(kErrorSinkCannotPlayVideo);
+ return false;
+ }
+
+ return true;
+}
+
+bool WiFiDisplayMediaManager::InitOptimalAudioFormat(
+ const std::vector<wds::AudioCodec>& sink_codecs) {
+ for (const wds::AudioCodec& codec : sink_codecs) {
+ // MediaStreamTrack contains LPCM audio.
+ if (codec.format == wds::LPCM) {
+ optimal_audio_codec_ = codec;
+ // Picking a single mode.
+ wds::AudioModes optimal_mode;
+ if (codec.modes.test(wds::LPCM_44_1K_16B_2CH))
+ optimal_mode.set(wds::LPCM_44_1K_16B_2CH);
+ else
+ optimal_mode.set(wds::LPCM_48K_16B_2CH);
+ optimal_audio_codec_.modes = optimal_mode;
+ return true;
+ }
+ }
+ error_callback_.Run(kErrorSinkCannotPlayAudio);
+ return false;
+}
+
+wds::AudioCodec WiFiDisplayMediaManager::GetOptimalAudioFormat() const {
+ return optimal_audio_codec_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.h b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.h
new file mode 100644
index 00000000000..277771cb2bc
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.h
@@ -0,0 +1,69 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_MEDIA_MANAGER_H_
+#define EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_MEDIA_MANAGER_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "third_party/WebKit/public/web/WebDOMMediaStreamTrack.h"
+#include "third_party/wds/src/libwds/public/media_manager.h"
+
+namespace extensions {
+
+class WiFiDisplayMediaManager : public wds::SourceMediaManager {
+ public:
+ using ErrorCallback = base::Callback<void(const std::string&)>;
+
+ WiFiDisplayMediaManager(
+ const blink::WebMediaStreamTrack& video_track,
+ const blink::WebMediaStreamTrack& audio_track,
+ const ErrorCallback& error_callback);
+
+ ~WiFiDisplayMediaManager() override;
+
+ private:
+ // wds::SourceMediaManager overrides.
+ void Play() override;
+
+ void Pause() override;
+ void Teardown() override;
+ bool IsPaused() const override;
+ wds::SessionType GetSessionType() const override;
+ void SetSinkRtpPorts(int port1, int port2) override;
+ std::pair<int, int> GetSinkRtpPorts() const override;
+ int GetLocalRtpPort() const override;
+
+ bool InitOptimalVideoFormat(
+ const wds::NativeVideoFormat& sink_native_format,
+ const std::vector<wds::H264VideoCodec>& sink_supported_codecs) override;
+ wds::H264VideoFormat GetOptimalVideoFormat() const override;
+ bool InitOptimalAudioFormat(
+ const std::vector<wds::AudioCodec>& sink_supported_codecs) override;
+ wds::AudioCodec GetOptimalAudioFormat() const override;
+
+ void SendIDRPicture() override;
+
+ std::string GetSessionId() const override;
+
+ private:
+ blink::WebMediaStreamTrack video_track_;
+ blink::WebMediaStreamTrack audio_track_;
+
+ std::pair<int, int> sink_rtp_ports_;
+ wds::H264VideoFormat optimal_video_format_;
+ wds::AudioCodec optimal_audio_codec_;
+
+ ErrorCallback error_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(WiFiDisplayMediaManager);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_MEDIA_MANAGER_H_
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.cc b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.cc
new file mode 100644
index 00000000000..f8f9cf23b5d
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.cc
@@ -0,0 +1,110 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "crypto/random.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h"
+
+namespace extensions {
+namespace {
+const size_t kMaxTransportStreamPacketCount = 7u;
+const uint8_t kProtocolPayloadTypeMP2T = 33u;
+const uint8_t kProtocolVersion = 2u;
+} // namespace
+
+WiFiDisplayMediaDatagramPacket::WiFiDisplayMediaDatagramPacket() = default;
+
+WiFiDisplayMediaDatagramPacket::WiFiDisplayMediaDatagramPacket(
+ WiFiDisplayMediaDatagramPacket&&) = default;
+
+WiFiDisplayMediaPacketizer::WiFiDisplayMediaPacketizer(
+ const base::TimeDelta& delay_for_unit_time_stamps,
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos,
+ const PacketizedCallback& on_packetized)
+ : WiFiDisplayTransportStreamPacketizer(delay_for_unit_time_stamps,
+ std::move(stream_infos)),
+ on_packetized_media_datagram_packet_(on_packetized) {
+ // Sequence numbers are mainly used for detecting lossed packets within one
+ // RTP session. The initial value SHOULD be random (unpredictable) to make
+ // known-plaintext attacks on encryption more difficult, in case the packets
+ // flow through a translator that encrypts them.
+ crypto::RandBytes(&sequence_number_, sizeof(sequence_number_));
+ base::RandBytes(&synchronization_source_identifier_,
+ sizeof(synchronization_source_identifier_));
+}
+
+WiFiDisplayMediaPacketizer::~WiFiDisplayMediaPacketizer() {}
+
+bool WiFiDisplayMediaPacketizer::OnPacketizedTransportStreamPacket(
+ const WiFiDisplayTransportStreamPacket& transport_stream_packet,
+ bool flush) {
+ DCHECK(CalledOnValidThread());
+
+ if (media_datagram_packet_.empty()) {
+ // Convert time to the number of 90 kHz ticks since some epoch.
+ const uint64_t us = static_cast<uint64_t>(
+ (base::TimeTicks::Now() - base::TimeTicks()).InMicroseconds());
+ const uint64_t time_stamp = (us * 90u + 500u) / 1000u;
+ const uint8_t header_without_identifiers[] = {
+ (kProtocolVersion << 6 | // Version (2 bits)
+ 0x0u << 5 | // Padding (no)
+ 0x0u << 4 | // Extension (no)
+ 0u << 0), // CSRC count (4 bits)
+ (0x0u << 7 | // Marker (no)
+ kProtocolPayloadTypeMP2T << 0), // Payload type (7 bits)
+ sequence_number_ >> 8, // Sequence number (16 bits)
+ sequence_number_ & 0xFFu, //
+ (time_stamp >> 24) & 0xFFu, // Time stamp (32 bits)
+ (time_stamp >> 16) & 0xFFu, //
+ (time_stamp >> 8) & 0xFFu, //
+ time_stamp & 0xFFu}; //
+ ++sequence_number_;
+ media_datagram_packet_.reserve(
+ sizeof(header_without_identifiers) +
+ sizeof(synchronization_source_identifier_) +
+ kMaxTransportStreamPacketCount *
+ WiFiDisplayTransportStreamPacket::kPacketSize);
+ media_datagram_packet_.insert(media_datagram_packet_.end(),
+ std::begin(header_without_identifiers),
+ std::end(header_without_identifiers));
+ media_datagram_packet_.insert(
+ media_datagram_packet_.end(),
+ std::begin(synchronization_source_identifier_),
+ std::end(synchronization_source_identifier_));
+ DCHECK_EQ(0u, media_datagram_packet_.size() /
+ WiFiDisplayTransportStreamPacket::kPacketSize);
+ }
+
+ // Append header and payload data.
+ media_datagram_packet_.insert(media_datagram_packet_.end(),
+ transport_stream_packet.header().begin(),
+ transport_stream_packet.header().end());
+ media_datagram_packet_.insert(media_datagram_packet_.end(),
+ transport_stream_packet.payload().begin(),
+ transport_stream_packet.payload().end());
+ media_datagram_packet_.insert(media_datagram_packet_.end(),
+ transport_stream_packet.filler().size(),
+ transport_stream_packet.filler().value());
+
+ // Combine multiple transport stream packets into one datagram packet
+ // by delaying delegation whenever possible.
+ if (!flush) {
+ const size_t transport_stream_packet_count =
+ media_datagram_packet_.size() /
+ WiFiDisplayTransportStreamPacket::kPacketSize;
+ if (transport_stream_packet_count < kMaxTransportStreamPacketCount)
+ return true;
+ }
+
+ WiFiDisplayMediaDatagramPacket packet;
+ packet.swap(media_datagram_packet_);
+ return on_packetized_media_datagram_packet_.Run(std::move(packet));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.h b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.h
new file mode 100644
index 00000000000..23c07e08119
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.h
@@ -0,0 +1,61 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_MEDIA_PACKETIZER_H_
+#define EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_MEDIA_PACKETIZER_H_
+
+#include <vector>
+
+#include "base/callback.h"
+#include "base/move.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h"
+
+namespace extensions {
+
+// This class represents an RTP datagram packet containing MPEG Transport
+// Stream (MPEG-TS) packets as a payload.
+class WiFiDisplayMediaDatagramPacket : public std::vector<uint8_t> {
+ public:
+ WiFiDisplayMediaDatagramPacket();
+ WiFiDisplayMediaDatagramPacket(WiFiDisplayMediaDatagramPacket&&);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN_WITH_MOVE_FOR_BIND(WiFiDisplayMediaDatagramPacket);
+};
+
+// The WiFi Display media packetizer packetizes unit buffers to media datagram
+// packets containing MPEG Transport Stream (MPEG-TS) packets containing either
+// meta information or Packetized Elementary Stream (PES) packets containing
+// unit data.
+//
+// Whenever a media datagram packet is fully created and thus ready for further
+// processing, a callback is called.
+class WiFiDisplayMediaPacketizer : public WiFiDisplayTransportStreamPacketizer {
+ public:
+ using PacketizedCallback =
+ base::Callback<bool(WiFiDisplayMediaDatagramPacket)>;
+
+ WiFiDisplayMediaPacketizer(
+ const base::TimeDelta& delay_for_unit_time_stamps,
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos,
+ const PacketizedCallback& on_packetized);
+ ~WiFiDisplayMediaPacketizer() override;
+
+ protected:
+ bool OnPacketizedTransportStreamPacket(
+ const WiFiDisplayTransportStreamPacket& transport_stream_packet,
+ bool flush) override;
+
+ private:
+ using SourceIdentifier = uint8_t[4];
+
+ WiFiDisplayMediaDatagramPacket media_datagram_packet_;
+ PacketizedCallback on_packetized_media_datagram_packet_;
+ uint16_t sequence_number_;
+ SourceIdentifier synchronization_source_identifier_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_MEDIA_PACKETIZER_H_
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer_unittest.cc b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer_unittest.cc
new file mode 100644
index 00000000000..5253a1baa76
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer_unittest.cc
@@ -0,0 +1,905 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <array>
+#include <list>
+
+#include "base/big_endian.h"
+#include "base/bind.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using PacketPart = extensions::WiFiDisplayStreamPacketPart;
+
+namespace extensions {
+
+std::ostream& operator<<(std::ostream& os, const PacketPart& part) {
+ const auto flags = os.flags();
+ os << "{" << std::hex << std::noshowbase;
+ for (const auto& item : part) {
+ if (&item != &*part.begin())
+ os << ", ";
+ os << "0x" << static_cast<unsigned>(item);
+ }
+ os.setf(flags, std::ios::basefield | std::ios::showbase);
+ return os << "}";
+}
+
+bool operator==(const PacketPart& a, const PacketPart& b) {
+ if (a.size() != b.size())
+ return false;
+ return std::equal(a.begin(), a.end(), b.begin());
+}
+
+namespace {
+
+namespace pes {
+const unsigned kDtsFlag = 0x0040u;
+const unsigned kMarkerFlag = 0x8000u;
+const unsigned kPtsFlag = 0x0080u;
+const size_t kUnitDataAlignment = sizeof(uint32_t);
+}
+
+namespace rtp {
+const unsigned kVersionMask = 0xC000u;
+const unsigned kVersion2 = 0x8000u;
+const unsigned kPaddingFlag = 0x2000u;
+const unsigned kExtensionFlag = 0x1000u;
+const unsigned kContributingSourceCountMask = 0x0F00u;
+const unsigned kMarkerFlag = 0x0010u;
+const unsigned kPayloadTypeMask = 0x007Fu;
+const unsigned kPayloadTypeMP2T = 0x0021u;
+} // namespace rtp
+
+namespace ts {
+const uint64_t kTimeStampMask = (static_cast<uint64_t>(1u) << 33) - 1u;
+const uint64_t kTimeStampSecond = 90000u; // 90 kHz
+const uint64_t kProgramClockReferenceSecond =
+ 300u * kTimeStampSecond; // 27 MHz
+
+// Packet header:
+const size_t kPacketHeaderSize = 4u;
+const unsigned kSyncByte = 0x47u;
+const uint32_t kSyncByteMask = 0xFF000000u;
+const uint32_t kTransportErrorIndicator = 0x00800000u;
+const uint32_t kPayloadUnitStartIndicator = 0x00400000u;
+const uint32_t kTransportPriority = 0x00200000u;
+const uint32_t kScramblingControlMask = 0x000000C0u;
+const uint32_t kAdaptationFieldFlag = 0x00000020u;
+const uint32_t kPayloadFlag = 0x00000010u;
+
+// Adaptation field:
+const unsigned kRandomAccessFlag = 0x40u;
+const unsigned kPcrFlag = 0x10u;
+} // namespace ts
+
+namespace widi {
+const unsigned kProgramAssociationTablePacketId = 0x0000u;
+const unsigned kProgramMapTablePacketId = 0x0100u;
+const unsigned kProgramClockReferencePacketId = 0x1000u;
+const unsigned kVideoStreamPacketId = 0x1011u;
+const unsigned kFirstAudioStreamPacketId = 0x1100u;
+const size_t kMaxTransportStreamPacketCountPerDatagramPacket = 7u;
+} // namespace widi
+
+template <typename PacketContainer>
+class PacketCollector {
+ public:
+ PacketContainer FetchPackets() {
+ PacketContainer container;
+ container.swap(packets_);
+ return container;
+ }
+
+ protected:
+ PacketContainer packets_;
+};
+
+class FakeMediaPacketizer
+ : public WiFiDisplayMediaPacketizer,
+ public PacketCollector<std::vector<std::vector<uint8_t>>> {
+ public:
+ FakeMediaPacketizer(const base::TimeDelta& delay_for_unit_time_stamps,
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos)
+ : WiFiDisplayMediaPacketizer(
+ delay_for_unit_time_stamps,
+ std::move(stream_infos),
+ base::Bind(&FakeMediaPacketizer::OnPacketizedMediaDatagramPacket,
+ base::Unretained(this))) {}
+
+ // Extend the interface in order to allow to bypass packetization of units to
+ // Packetized Elementary Stream (PES) packets and further to Transport Stream
+ // (TS) packets and to test only packetization of TS packets to media
+ // datagram packets.
+ bool EncodeTransportStreamPacket(
+ const WiFiDisplayTransportStreamPacket& transport_stream_packet,
+ bool flush) {
+ return OnPacketizedTransportStreamPacket(transport_stream_packet, flush);
+ }
+
+ private:
+ bool OnPacketizedMediaDatagramPacket(
+ WiFiDisplayMediaDatagramPacket media_datagram_packet) {
+ packets_.emplace_back(std::move(media_datagram_packet));
+ return true;
+ }
+};
+
+class FakeTransportStreamPacketizer
+ : public WiFiDisplayTransportStreamPacketizer,
+ public PacketCollector<std::list<WiFiDisplayTransportStreamPacket>> {
+ public:
+ FakeTransportStreamPacketizer(
+ const base::TimeDelta& delay_for_unit_time_stamps,
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos)
+ : WiFiDisplayTransportStreamPacketizer(delay_for_unit_time_stamps,
+ std::move(stream_infos)) {}
+
+ using WiFiDisplayTransportStreamPacketizer::NormalizeUnitTimeStamps;
+
+ protected:
+ bool OnPacketizedTransportStreamPacket(
+ const WiFiDisplayTransportStreamPacket& transport_stream_packet,
+ bool flush) override {
+ // Make a copy of header bytes as they are in stack.
+ headers_.emplace_back(transport_stream_packet.header().begin(),
+ transport_stream_packet.header().end());
+ const auto& header = headers_.back();
+ if (transport_stream_packet.payload().empty()) {
+ packets_.emplace_back(header.data(), header.size());
+ } else {
+ packets_.emplace_back(header.data(), header.size(),
+ transport_stream_packet.payload().begin());
+ }
+ EXPECT_EQ(transport_stream_packet.header().size(),
+ packets_.back().header().size());
+ EXPECT_EQ(transport_stream_packet.payload().size(),
+ packets_.back().payload().size());
+ EXPECT_EQ(transport_stream_packet.filler().size(),
+ packets_.back().filler().size());
+ return true;
+ }
+
+ private:
+ std::vector<std::vector<uint8_t>> headers_;
+};
+
+struct ProgramClockReference {
+ enum { kInvalidBase = ~static_cast<uint64_t>(0u) };
+ uint64_t base;
+ uint16_t extension;
+};
+
+ProgramClockReference ParseProgramClockReference(const uint8_t pcr_bytes[6]) {
+ const uint8_t reserved_pcr_bits = pcr_bytes[4] & 0x7Eu;
+ EXPECT_EQ(0x7Eu, reserved_pcr_bits);
+ ProgramClockReference pcr;
+ pcr.base = pcr_bytes[0];
+ pcr.base = (pcr.base << 8) | pcr_bytes[1];
+ pcr.base = (pcr.base << 8) | pcr_bytes[2];
+ pcr.base = (pcr.base << 8) | pcr_bytes[3];
+ pcr.base = (pcr.base << 1) | ((pcr_bytes[4] & 0x80u) >> 7);
+ pcr.extension = pcr_bytes[4] & 0x01u;
+ pcr.extension = (pcr.extension << 8) | pcr_bytes[5];
+ return pcr;
+}
+
+uint64_t ParseTimeStamp(const uint8_t ts_bytes[5], uint8_t pts_dts_indicator) {
+ EXPECT_EQ(pts_dts_indicator, (ts_bytes[0] & 0xF0u) >> 4);
+ EXPECT_EQ(0x01u, ts_bytes[0] & 0x01u);
+ EXPECT_EQ(0x01u, ts_bytes[2] & 0x01u);
+ EXPECT_EQ(0x01u, ts_bytes[4] & 0x01u);
+ uint64_t ts = 0u;
+ ts = (ts_bytes[0] & 0x0Eu) >> 1;
+ ts = (ts << 8) | ts_bytes[1];
+ ts = (ts << 7) | ((ts_bytes[2] & 0xFEu) >> 1);
+ ts = (ts << 8) | ts_bytes[3];
+ ts = (ts << 7) | ((ts_bytes[4] & 0xFEu) >> 1);
+ return ts;
+}
+
+unsigned ParseTransportStreamPacketId(
+ const WiFiDisplayTransportStreamPacket& packet) {
+ if (packet.header().size() < ts::kPacketHeaderSize)
+ return ~0u;
+ return (((packet.header().begin()[1] & 0x001Fu) << 8) |
+ packet.header().begin()[2]);
+}
+
+class WiFiDisplayElementaryStreamUnitPacketizationTest
+ : public testing::TestWithParam<
+ testing::tuple<unsigned, base::TimeDelta, base::TimeDelta>> {
+ protected:
+ static base::TimeTicks SumOrNull(const base::TimeTicks& base,
+ const base::TimeDelta& delta) {
+ return delta.is_max() ? base::TimeTicks() : base + delta;
+ }
+
+ WiFiDisplayElementaryStreamUnitPacketizationTest()
+ : unit_(testing::get<0>(GetParam())),
+ now_(base::TimeTicks::Now()),
+ dts_(SumOrNull(now_, testing::get<1>(GetParam()))),
+ pts_(SumOrNull(now_, testing::get<2>(GetParam()))) {}
+
+ void CheckElementaryStreamPacketHeader(
+ const WiFiDisplayElementaryStreamPacket& packet,
+ uint8_t stream_id) {
+ base::BigEndianReader header_reader(
+ reinterpret_cast<const char*>(packet.header().begin()),
+ packet.header().size());
+ uint8_t parsed_packet_start_code_prefix[3];
+ EXPECT_TRUE(
+ header_reader.ReadBytes(parsed_packet_start_code_prefix,
+ sizeof(parsed_packet_start_code_prefix)));
+ EXPECT_EQ(0x00u, parsed_packet_start_code_prefix[0]);
+ EXPECT_EQ(0x00u, parsed_packet_start_code_prefix[1]);
+ EXPECT_EQ(0x01u, parsed_packet_start_code_prefix[2]);
+ uint8_t parsed_stream_id;
+ EXPECT_TRUE(header_reader.ReadU8(&parsed_stream_id));
+ EXPECT_EQ(stream_id, parsed_stream_id);
+ uint16_t parsed_packet_length;
+ EXPECT_TRUE(header_reader.ReadU16(&parsed_packet_length));
+ size_t packet_length = static_cast<size_t>(header_reader.remaining()) +
+ packet.unit_header().size() + packet.unit().size();
+ if (packet_length >> 16)
+ packet_length = 0u;
+ EXPECT_EQ(packet_length, parsed_packet_length);
+ uint16_t parsed_flags;
+ EXPECT_TRUE(header_reader.ReadU16(&parsed_flags));
+ EXPECT_EQ(
+ 0u, parsed_flags & ~(pes::kMarkerFlag | pes::kPtsFlag | pes::kDtsFlag));
+ const bool parsed_pts_flag = (parsed_flags & pes::kPtsFlag) != 0u;
+ const bool parsed_dts_flag = (parsed_flags & pes::kDtsFlag) != 0u;
+ EXPECT_EQ(!pts_.is_null(), parsed_pts_flag);
+ EXPECT_EQ(!pts_.is_null() && !dts_.is_null(), parsed_dts_flag);
+ uint8_t parsed_header_length;
+ EXPECT_TRUE(header_reader.ReadU8(&parsed_header_length));
+ EXPECT_EQ(header_reader.remaining(), parsed_header_length);
+ if (parsed_pts_flag) {
+ uint8_t parsed_pts_bytes[5];
+ EXPECT_TRUE(
+ header_reader.ReadBytes(parsed_pts_bytes, sizeof(parsed_pts_bytes)));
+ const uint64_t parsed_pts =
+ ParseTimeStamp(parsed_pts_bytes, parsed_dts_flag ? 0x3u : 0x2u);
+ if (parsed_dts_flag) {
+ uint8_t parsed_dts_bytes[5];
+ EXPECT_TRUE(header_reader.ReadBytes(parsed_dts_bytes,
+ sizeof(parsed_dts_bytes)));
+ const uint64_t parsed_dts = ParseTimeStamp(parsed_dts_bytes, 0x1u);
+ EXPECT_EQ(
+ static_cast<uint64_t>(90 * (pts_ - dts_).InMicroseconds() / 1000),
+ (parsed_pts - parsed_dts) & UINT64_C(0x1FFFFFFFF));
+ }
+ }
+ while (header_reader.remaining() > 0) {
+ uint8_t parsed_stuffing_byte;
+ EXPECT_TRUE(header_reader.ReadU8(&parsed_stuffing_byte));
+ EXPECT_EQ(0xFFu, parsed_stuffing_byte);
+ }
+ EXPECT_EQ(0, header_reader.remaining());
+ }
+
+ void CheckElementaryStreamPacketUnitHeader(
+ const WiFiDisplayElementaryStreamPacket& packet,
+ const uint8_t* unit_header_data,
+ size_t unit_header_size) {
+ EXPECT_EQ(unit_header_data, packet.unit_header().begin());
+ EXPECT_EQ(unit_header_size, packet.unit_header().size());
+ }
+
+ void CheckElementaryStreamPacketUnit(
+ const WiFiDisplayElementaryStreamPacket& packet) {
+ EXPECT_EQ(0u, (packet.header().size() + packet.unit_header().size()) %
+ pes::kUnitDataAlignment);
+ EXPECT_EQ(unit_.data(), packet.unit().begin());
+ EXPECT_EQ(unit_.size(), packet.unit().size());
+ }
+
+ void CheckTransportStreamPacketHeader(
+ base::BigEndianReader* header_reader,
+ bool expected_payload_unit_start_indicator,
+ unsigned expected_packet_id,
+ bool* adaptation_field_flag,
+ uint8_t expected_continuity_counter) {
+ uint32_t parsed_u32;
+ EXPECT_TRUE(header_reader->ReadU32(&parsed_u32));
+ EXPECT_EQ(ts::kSyncByte << 24u, parsed_u32 & ts::kSyncByteMask);
+ EXPECT_EQ(0x0u, parsed_u32 & ts::kTransportErrorIndicator);
+ EXPECT_EQ(expected_payload_unit_start_indicator,
+ (parsed_u32 & ts::kPayloadUnitStartIndicator) != 0u);
+ EXPECT_EQ(0x0u, parsed_u32 & ts::kTransportPriority);
+ EXPECT_EQ(expected_packet_id, (parsed_u32 & 0x001FFF00) >> 8);
+ EXPECT_EQ(0x0u, parsed_u32 & ts::kScramblingControlMask);
+ if (!adaptation_field_flag) {
+ EXPECT_EQ(0x0u, parsed_u32 & ts::kAdaptationFieldFlag);
+ } else {
+ *adaptation_field_flag = (parsed_u32 & ts::kAdaptationFieldFlag) != 0u;
+ }
+ EXPECT_EQ(ts::kPayloadFlag, parsed_u32 & ts::kPayloadFlag);
+ EXPECT_EQ(expected_continuity_counter & 0xFu, parsed_u32 & 0x0000000Fu);
+ }
+
+ void CheckTransportStreamAdaptationField(
+ base::BigEndianReader* header_reader,
+ const WiFiDisplayTransportStreamPacket& packet,
+ uint8_t* adaptation_field_flags) {
+ uint8_t parsed_adaptation_field_length;
+ EXPECT_TRUE(header_reader->ReadU8(&parsed_adaptation_field_length));
+ if (parsed_adaptation_field_length > 0u) {
+ const int initial_remaining = header_reader->remaining();
+ uint8_t parsed_adaptation_field_flags;
+ EXPECT_TRUE(header_reader->ReadU8(&parsed_adaptation_field_flags));
+ if (!adaptation_field_flags) {
+ EXPECT_EQ(0x0u, parsed_adaptation_field_flags);
+ } else {
+ *adaptation_field_flags = parsed_adaptation_field_flags;
+ if (parsed_adaptation_field_flags & ts::kPcrFlag) {
+ uint8_t parsed_pcr_bytes[6];
+ EXPECT_TRUE(header_reader->ReadBytes(parsed_pcr_bytes,
+ sizeof(parsed_pcr_bytes)));
+ parsed_pcr_ = ParseProgramClockReference(parsed_pcr_bytes);
+ }
+ }
+ size_t remaining_stuffing_length =
+ parsed_adaptation_field_length -
+ static_cast<size_t>(initial_remaining - header_reader->remaining());
+ while (remaining_stuffing_length > 0u && header_reader->remaining() > 0) {
+ // Adaptation field stuffing byte in header_reader.
+ uint8_t parsed_stuffing_byte;
+ EXPECT_TRUE(header_reader->ReadU8(&parsed_stuffing_byte));
+ EXPECT_EQ(0xFFu, parsed_stuffing_byte);
+ --remaining_stuffing_length;
+ }
+ if (packet.payload().empty()) {
+ // Adaptation field stuffing bytes in packet.filler().
+ EXPECT_EQ(remaining_stuffing_length, packet.filler().size());
+ EXPECT_EQ(0xFFu, packet.filler().value());
+ } else {
+ EXPECT_EQ(0u, remaining_stuffing_length);
+ }
+ }
+ }
+
+ void CheckTransportStreamProgramAssociationTablePacket(
+ const WiFiDisplayTransportStreamPacket& packet) {
+ static const uint8_t kProgramAssicationTable[4u + 13u] = {
+ // Pointer:
+ 0u, // Pointer field
+ // Table header:
+ 0x00u, // Table ID (PAT)
+ 0x80u | // Section syntax indicator (0b1 for PAT)
+ 0x00u | // Private bit (0b0 for PAT)
+ 0x30u | // Reserved bits (0b11)
+ 0x00u | // Section length unused bits (0b00)
+ 0u, // Section length (10 bits)
+ 13u, //
+ // Table syntax:
+ 0x00u, // Table ID extension (transport stream ID)
+ 0x01u, //
+ 0xC0u | // Reserved bits (0b11)
+ 0x00u | // Version (0b00000)
+ 0x01u, // Current indicator (0b1)
+ 0u, // Section number
+ 0u, // Last section number
+ // Program association table specific data:
+ 0x00u, // Program number
+ 0x01u, //
+ 0xE0 | // Reserved bits (0b111)
+ 0x01u, // Program map packet ID (13 bits)
+ 0x00, //
+ // CRC:
+ 0xE8u,
+ 0xF9u, 0x5Eu, 0x7Du};
+
+ base::BigEndianReader header_reader(
+ reinterpret_cast<const char*>(packet.header().begin()),
+ packet.header().size());
+
+ CheckTransportStreamPacketHeader(
+ &header_reader, true, widi::kProgramAssociationTablePacketId, nullptr,
+ continuity_.program_assication_table++);
+
+ EXPECT_EQ(PacketPart(kProgramAssicationTable),
+ PacketPart(packet.header().end() - header_reader.remaining(),
+ static_cast<size_t>(header_reader.remaining())));
+ EXPECT_TRUE(header_reader.Skip(header_reader.remaining()));
+
+ EXPECT_EQ(0, header_reader.remaining());
+ EXPECT_EQ(0u, packet.payload().size());
+ }
+
+ void CheckTransportStreamProgramMapTablePacket(
+ const WiFiDisplayTransportStreamPacket& packet,
+ const PacketPart& program_map_table) {
+ base::BigEndianReader header_reader(
+ reinterpret_cast<const char*>(packet.header().begin()),
+ packet.header().size());
+
+ CheckTransportStreamPacketHeader(&header_reader, true,
+ widi::kProgramMapTablePacketId, nullptr,
+ continuity_.program_map_table++);
+
+ EXPECT_EQ(program_map_table,
+ PacketPart(packet.header().end() - header_reader.remaining(),
+ static_cast<size_t>(header_reader.remaining())));
+ EXPECT_TRUE(header_reader.Skip(header_reader.remaining()));
+
+ EXPECT_EQ(0, header_reader.remaining());
+ EXPECT_EQ(0u, packet.payload().size());
+ }
+
+ void CheckTransportStreamProgramClockReferencePacket(
+ const WiFiDisplayTransportStreamPacket& packet) {
+ base::BigEndianReader header_reader(
+ reinterpret_cast<const char*>(packet.header().begin()),
+ packet.header().size());
+
+ bool parsed_adaptation_field_flag;
+ CheckTransportStreamPacketHeader(
+ &header_reader, true, widi::kProgramClockReferencePacketId,
+ &parsed_adaptation_field_flag, continuity_.program_clock_reference++);
+ EXPECT_TRUE(parsed_adaptation_field_flag);
+
+ uint8_t parsed_adaptation_field_flags;
+ CheckTransportStreamAdaptationField(&header_reader, packet,
+ &parsed_adaptation_field_flags);
+ EXPECT_EQ(ts::kPcrFlag, parsed_adaptation_field_flags);
+
+ EXPECT_EQ(0, header_reader.remaining());
+ EXPECT_EQ(0u, packet.payload().size());
+ }
+
+ void CheckTransportStreamElementaryStreamPacket(
+ const WiFiDisplayTransportStreamPacket& packet,
+ const WiFiDisplayElementaryStreamPacket& elementary_stream_packet,
+ unsigned stream_index,
+ unsigned expected_packet_id,
+ bool expected_random_access,
+ const uint8_t** unit_data_pos) {
+ const bool first_transport_stream_packet_for_current_unit =
+ packet.payload().begin() == unit_.data();
+ const bool last_transport_stream_packet_for_current_unit =
+ packet.payload().end() == unit_.data() + unit_.size();
+ base::BigEndianReader header_reader(
+ reinterpret_cast<const char*>(packet.header().begin()),
+ packet.header().size());
+
+ bool parsed_adaptation_field_flag;
+ CheckTransportStreamPacketHeader(
+ &header_reader, first_transport_stream_packet_for_current_unit,
+ expected_packet_id, &parsed_adaptation_field_flag,
+ continuity_.elementary_streams[stream_index]++);
+
+ if (first_transport_stream_packet_for_current_unit) {
+ // Random access can only be signified by adaptation field.
+ if (expected_random_access)
+ EXPECT_TRUE(parsed_adaptation_field_flag);
+ // If there is no need for padding nor for a random access indicator,
+ // then there is no need for an adaptation field, either.
+ if (!last_transport_stream_packet_for_current_unit &&
+ !expected_random_access) {
+ EXPECT_FALSE(parsed_adaptation_field_flag);
+ }
+ if (parsed_adaptation_field_flag) {
+ uint8_t parsed_adaptation_field_flags;
+ CheckTransportStreamAdaptationField(&header_reader, packet,
+ &parsed_adaptation_field_flags);
+ EXPECT_EQ(expected_random_access ? ts::kRandomAccessFlag : 0u,
+ parsed_adaptation_field_flags);
+ }
+
+ // Elementary stream header.
+ PacketPart parsed_elementary_stream_packet_header(
+ packet.header().end() - header_reader.remaining(),
+ std::min(elementary_stream_packet.header().size(),
+ static_cast<size_t>(header_reader.remaining())));
+ EXPECT_EQ(elementary_stream_packet.header(),
+ parsed_elementary_stream_packet_header);
+ EXPECT_TRUE(
+ header_reader.Skip(parsed_elementary_stream_packet_header.size()));
+
+ // Elementary stream unit header.
+ PacketPart parsed_unit_header(
+ packet.header().end() - header_reader.remaining(),
+ std::min(elementary_stream_packet.unit_header().size(),
+ static_cast<size_t>(header_reader.remaining())));
+ EXPECT_EQ(elementary_stream_packet.unit_header(), parsed_unit_header);
+ EXPECT_TRUE(header_reader.Skip(parsed_unit_header.size()));
+
+ // Time stamps.
+ if (parsed_elementary_stream_packet_header.size() >= 19u) {
+ uint64_t parsed_dts = ParseTimeStamp(
+ &parsed_elementary_stream_packet_header.begin()[14], 0x1u);
+ // Check that
+ // 0 <= 300 * parsed_dts - parsed_pcr_value <=
+ // kProgramClockReferenceSecond
+ // where
+ // parsed_pcr_value = 300 * parsed_pcr_.base + parsed_pcr_.extension
+ // but allow parsed_pcr_.base and parsed_dts to wrap around in 33 bits.
+ EXPECT_NE(ProgramClockReference::kInvalidBase, parsed_pcr_.base);
+ EXPECT_LE(
+ 300u * ((parsed_dts - parsed_pcr_.base) & ts::kTimeStampMask) -
+ parsed_pcr_.extension,
+ ts::kProgramClockReferenceSecond)
+ << " DTS must be not smaller than PCR!";
+ }
+ } else {
+ // If there is no need for padding, then there is no need for
+ // an adaptation field, either.
+ if (!last_transport_stream_packet_for_current_unit)
+ EXPECT_FALSE(parsed_adaptation_field_flag);
+ if (parsed_adaptation_field_flag) {
+ CheckTransportStreamAdaptationField(&header_reader, packet, nullptr);
+ }
+ }
+ EXPECT_EQ(0, header_reader.remaining());
+
+ // Transport stream packet payload.
+ EXPECT_EQ(*unit_data_pos, packet.payload().begin());
+ if (*unit_data_pos == packet.payload().begin())
+ *unit_data_pos += packet.payload().size();
+
+ // Transport stream packet filler.
+ EXPECT_EQ(0u, packet.filler().size());
+ }
+
+ enum { kVideoOnlyUnitSize = 0x8000u }; // Not exact. Be on the safe side.
+
+ const std::vector<uint8_t> unit_;
+ const base::TimeTicks now_;
+ const base::TimeTicks dts_;
+ const base::TimeTicks pts_;
+
+ struct {
+ size_t program_assication_table;
+ size_t program_map_table;
+ size_t program_clock_reference;
+ size_t elementary_streams[3];
+ } continuity_ = {0u, 0u, 0u, {0u, 0u, 0u}};
+ ProgramClockReference parsed_pcr_ = {ProgramClockReference::kInvalidBase, 0u};
+};
+
+TEST_P(WiFiDisplayElementaryStreamUnitPacketizationTest,
+ EncodeToElementaryStreamPacket) {
+ const size_t kMaxUnitHeaderSize = 4u;
+
+ const uint8_t stream_id =
+ unit_.size() >= kVideoOnlyUnitSize
+ ? WiFiDisplayElementaryStreamPacketizer::kFirstVideoStreamId
+ : WiFiDisplayElementaryStreamPacketizer::kFirstAudioStreamId;
+
+ WiFiDisplayElementaryStreamPacketizer packetizer;
+ uint8_t unit_header_data[kMaxUnitHeaderSize];
+ for (size_t unit_header_size = 0u; unit_header_size <= kMaxUnitHeaderSize;
+ ++unit_header_size) {
+ WiFiDisplayElementaryStreamPacket packet =
+ packetizer.EncodeElementaryStreamUnit(stream_id, unit_header_data,
+ unit_header_size, unit_.data(),
+ unit_.size(), pts_, dts_);
+ CheckElementaryStreamPacketHeader(packet, stream_id);
+ CheckElementaryStreamPacketUnitHeader(packet, unit_header_data,
+ unit_header_size);
+ CheckElementaryStreamPacketUnit(packet);
+ }
+}
+
+TEST_P(WiFiDisplayElementaryStreamUnitPacketizationTest,
+ EncodeToTransportStreamPackets) {
+ enum { kStreamCount = 3u };
+ static const bool kBoolValues[] = {false, true};
+ static const unsigned kPacketIds[kStreamCount] = {
+ widi::kVideoStreamPacketId, widi::kFirstAudioStreamPacketId + 0u,
+ widi::kFirstAudioStreamPacketId + 1u};
+ static const uint8_t kProgramMapTable[4u + 42u] = {
+ // Pointer:
+ 0u, // Pointer field
+ // Table header:
+ 0x02u, // Table ID (PMT)
+ 0x80u | // Section syntax indicator (0b1 for PMT)
+ 0x00u | // Private bit (0b0 for PMT)
+ 0x30u | // Reserved bits (0b11)
+ 0x00u | // Section length unused bits (0b00)
+ 0u, // Section length (10 bits)
+ 42u, //
+ // Table syntax:
+ 0x00u, // Table ID extension (program number)
+ 0x01u, //
+ 0xC0u | // Reserved bits (0b11)
+ 0x00u | // Version (0b00000)
+ 0x01u, // Current indicator (0b1)
+ 0u, // Section number
+ 0u, // Last section number
+ // Program map table specific data:
+ 0xE0u | // Reserved bits (0b111)
+ 0x10u, // Program clock reference packet ID (13 bits)
+ 0x00u, //
+ 0xF0u | // Reserved bits (0b11)
+ 0x00u | // Program info length unused bits
+ 0u, // Program info length (10 bits)
+ 0u, //
+ // Elementary stream specific data:
+ 0x1Bu, // Stream type (H.264 in a packetized stream)
+ 0xE0u | // Reserved bits (0b111)
+ 0x10u, // Elementary packet ID (13 bits)
+ 0x11u, //
+ 0xF0u | // Reserved bits (0b1111)
+ 0x00u | // Elementary stream info length unused bits
+ 0u, // Elementary stream info length (10 bits)
+ 10u, //
+ 0x28u, // AVC video descriptor tag
+ 4u, // Descriptor length
+ 0xA5u,
+ 0xF5u, 0xBDu, 0xBFu,
+ 0x2Au, // AVC timing and HRD descriptor tag
+ 2u, // Descriptor length
+ 0x7Eu, 0x1Fu,
+ // Elementary stream specific data:
+ 0x83u, // Stream type (lossless audio in a packetized stream)
+ 0xE0u | // Reserved bits (0b111)
+ 0x11u, // Elementary packet ID (13 bits)
+ 0x00u, //
+ 0xF0u | // Reserved bits (0b1111)
+ 0x00u | // Elementary stream info length unused bits
+ 0u, // Elementary stream info length (10 bits)
+ 4u, //
+ 0x83u, // LPCM audio stream descriptor tag
+ 2u, // Descriptor length
+ 0x26u,
+ 0x2Fu,
+ // Elementary stream specific data:
+ 0x0Fu, // Stream type (AAC in a packetized stream)
+ 0xE0u | // Reserved bits (0b111)
+ 0x11u, // Elementary packet ID (13 bits)
+ 0x01u, //
+ 0xF0u | // Reserved bits (0b1111)
+ 0x00u | // Elementary stream info length unused bits
+ 0u, // Elementary stream info length (10 bits)
+ 0u, //
+ // CRC:
+ 0x4Fu,
+ 0x63u, 0xABu, 0x6Eu};
+ static const uint8_t kStreamIds[] = {
+ WiFiDisplayElementaryStreamPacketizer::kFirstVideoStreamId,
+ WiFiDisplayElementaryStreamPacketizer::kPrivateStream1Id,
+ WiFiDisplayElementaryStreamPacketizer::kFirstAudioStreamId};
+
+ using ESDescriptor = WiFiDisplayElementaryStreamDescriptor;
+ std::vector<ESDescriptor> lpcm_descriptors;
+ lpcm_descriptors.emplace_back(ESDescriptor::LPCMAudioStream::Create(
+ ESDescriptor::LPCMAudioStream::SAMPLING_FREQUENCY_44_1K,
+ ESDescriptor::LPCMAudioStream::BITS_PER_SAMPLE_16, false,
+ ESDescriptor::LPCMAudioStream::NUMBER_OF_CHANNELS_STEREO));
+ std::vector<ESDescriptor> video_desciptors;
+ video_desciptors.emplace_back(ESDescriptor::AVCVideo::Create(
+ 0xA5u, true, true, true, 0x15u, 0xBDu, true));
+ video_desciptors.emplace_back(ESDescriptor::AVCTimingAndHRD::Create());
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos;
+ stream_infos.emplace_back(WiFiDisplayElementaryStreamInfo::VIDEO_H264,
+ std::move(video_desciptors));
+ stream_infos.emplace_back(WiFiDisplayElementaryStreamInfo::AUDIO_LPCM,
+ std::move(lpcm_descriptors));
+ stream_infos.emplace_back(WiFiDisplayElementaryStreamInfo::AUDIO_AAC);
+ WiFiDisplayElementaryStreamPacketizer elementary_stream_packetizer;
+ FakeTransportStreamPacketizer packetizer(
+ base::TimeDelta::FromMilliseconds(200), std::move(stream_infos));
+
+ size_t packet_index = 0u;
+ for (unsigned stream_index = 0; stream_index < kStreamCount; ++stream_index) {
+ const uint8_t* unit_header_data = nullptr;
+ size_t unit_header_size = 0u;
+ if (stream_index > 0u) { // Audio stream.
+ if (unit_.size() >= kVideoOnlyUnitSize)
+ continue;
+ if (stream_index == 1u) { // LPCM
+ unit_header_data = reinterpret_cast<const uint8_t*>("\xA0\x06\x00\x09");
+ unit_header_size = 4u;
+ }
+ }
+ for (const bool random_access : kBoolValues) {
+ EXPECT_TRUE(packetizer.EncodeElementaryStreamUnit(
+ stream_index, unit_.data(), unit_.size(), random_access, pts_, dts_,
+ true));
+ auto normalized_pts = pts_;
+ auto normalized_dts = dts_;
+ packetizer.NormalizeUnitTimeStamps(&normalized_pts, &normalized_dts);
+ WiFiDisplayElementaryStreamPacket elementary_stream_packet =
+ elementary_stream_packetizer.EncodeElementaryStreamUnit(
+ kStreamIds[stream_index], unit_header_data, unit_header_size,
+ unit_.data(), unit_.size(), normalized_pts, normalized_dts);
+
+ const uint8_t* unit_data_pos = unit_.data();
+ for (const auto& packet : packetizer.FetchPackets()) {
+ switch (ParseTransportStreamPacketId(packet)) {
+ case widi::kProgramAssociationTablePacketId:
+ if (packet_index < 4u)
+ EXPECT_EQ(0u, packet_index);
+ CheckTransportStreamProgramAssociationTablePacket(packet);
+ break;
+ case widi::kProgramMapTablePacketId:
+ if (packet_index < 4u)
+ EXPECT_EQ(1u, packet_index);
+ CheckTransportStreamProgramMapTablePacket(
+ packet, PacketPart(kProgramMapTable));
+ break;
+ case widi::kProgramClockReferencePacketId:
+ if (packet_index < 4u)
+ EXPECT_EQ(2u, packet_index);
+ CheckTransportStreamProgramClockReferencePacket(packet);
+ break;
+ default:
+ if (packet_index < 4u)
+ EXPECT_EQ(3u, packet_index);
+ CheckTransportStreamElementaryStreamPacket(
+ packet, elementary_stream_packet, stream_index,
+ kPacketIds[stream_index], random_access, &unit_data_pos);
+ }
+ ++packet_index;
+ }
+ EXPECT_EQ(unit_.data() + unit_.size(), unit_data_pos);
+ }
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(
+ WiFiDisplayElementaryStreamUnitPacketizationTests,
+ WiFiDisplayElementaryStreamUnitPacketizationTest,
+ testing::Combine(testing::Values(123u, 180u, 0x10000u),
+ testing::Values(base::TimeDelta::Max(),
+ base::TimeDelta::FromMicroseconds(0)),
+ testing::Values(base::TimeDelta::Max(),
+ base::TimeDelta::FromMicroseconds(
+ 1000 * INT64_C(0x123456789) / 90))));
+
+TEST(WiFiDisplayTransportStreamPacketizationTest, EncodeToMediaDatagramPacket) {
+ const size_t kPacketHeaderSize = 12u;
+
+ // Create fake units.
+ const size_t kUnitCount = 12u;
+ const size_t kUnitSize =
+ WiFiDisplayTransportStreamPacket::kPacketSize - 4u - 12u;
+ std::vector<std::array<uint8_t, kUnitSize>> units(kUnitCount);
+ for (auto& unit : units)
+ unit.fill(static_cast<uint8_t>(&unit - units.data()));
+
+ // Create transport stream packets.
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos;
+ stream_infos.emplace_back(WiFiDisplayElementaryStreamInfo::VIDEO_H264);
+ FakeTransportStreamPacketizer transport_stream_packetizer(
+ base::TimeDelta::FromMilliseconds(0), std::move(stream_infos));
+ for (const auto& unit : units) {
+ EXPECT_TRUE(transport_stream_packetizer.EncodeElementaryStreamUnit(
+ 0u, unit.data(), unit.size(), false, base::TimeTicks(),
+ base::TimeTicks(), &unit == &units.back()));
+ }
+ auto transport_stream_packets = transport_stream_packetizer.FetchPackets();
+ // There should be exactly one transport stream payload packet for each unit.
+ // There should also be some but not too many transport stream meta
+ // information packets.
+ EXPECT_EQ(1u, transport_stream_packets.size() / kUnitCount);
+
+ // Encode transport stream packets to datagram packets.
+ FakeMediaPacketizer packetizer(
+ base::TimeDelta::FromMilliseconds(0),
+ std::vector<WiFiDisplayElementaryStreamInfo>());
+ for (const auto& transport_stream_packet : transport_stream_packets) {
+ EXPECT_TRUE(packetizer.EncodeTransportStreamPacket(
+ transport_stream_packet,
+ &transport_stream_packet == &transport_stream_packets.back()));
+ }
+ auto packets = packetizer.FetchPackets();
+
+ // Check datagram packets.
+ ProgramClockReference pcr = {ProgramClockReference::kInvalidBase, 0u};
+ uint16_t sequence_number;
+ uint32_t synchronization_source_identifier;
+ auto transport_stream_packet_it = transport_stream_packets.cbegin();
+ for (const auto& packet : packets) {
+ base::BigEndianReader header_reader(
+ reinterpret_cast<const char*>(packet.data()),
+ std::min(kPacketHeaderSize, packet.size()));
+
+ // Packet flags.
+ uint16_t parsed_u16;
+ EXPECT_TRUE(header_reader.ReadU16(&parsed_u16));
+ EXPECT_EQ(rtp::kVersion2, parsed_u16 & rtp::kVersionMask);
+ EXPECT_FALSE(parsed_u16 & rtp::kPaddingFlag);
+ EXPECT_FALSE(parsed_u16 & rtp::kExtensionFlag);
+ EXPECT_EQ(0u, parsed_u16 & rtp::kContributingSourceCountMask);
+ EXPECT_FALSE(parsed_u16 & rtp::kMarkerFlag);
+ EXPECT_EQ(rtp::kPayloadTypeMP2T, parsed_u16 & rtp::kPayloadTypeMask);
+
+ // Packet sequence number.
+ uint16_t parsed_sequence_number;
+ EXPECT_TRUE(header_reader.ReadU16(&parsed_sequence_number));
+ if (&packet == &packets.front())
+ sequence_number = parsed_sequence_number;
+ EXPECT_EQ(sequence_number++, parsed_sequence_number);
+
+ // Packet time stamp.
+ uint32_t parsed_time_stamp;
+ EXPECT_TRUE(header_reader.ReadU32(&parsed_time_stamp));
+ if (pcr.base == ProgramClockReference::kInvalidBase) {
+ // This happens only for the first datagram packet.
+ EXPECT_TRUE(&packet == &packets.front());
+ // Ensure that the next datagram packet reaches the else branch.
+ EXPECT_FALSE(&packet == &packets.back());
+ } else {
+ // Check that
+ // 0 <= parsed_time_stamp - pcr.base <= kTimeStampSecond
+ // but allow pcr.base and parsed_time_stamp to wrap around in 32 bits.
+ EXPECT_LE((parsed_time_stamp - pcr.base) & 0xFFFFFFFFu,
+ ts::kTimeStampSecond)
+ << " Time stamp must not be smaller than PCR!";
+ }
+
+ // Packet synchronization source identifier.
+ uint32_t parsed_synchronization_source_identifier;
+ EXPECT_TRUE(
+ header_reader.ReadU32(&parsed_synchronization_source_identifier));
+ if (&packet == &packets.front()) {
+ synchronization_source_identifier =
+ parsed_synchronization_source_identifier;
+ }
+ EXPECT_EQ(synchronization_source_identifier,
+ parsed_synchronization_source_identifier);
+
+ EXPECT_EQ(0, header_reader.remaining());
+
+ // Packet payload.
+ size_t offset = kPacketHeaderSize;
+ while (offset + WiFiDisplayTransportStreamPacket::kPacketSize <=
+ packet.size() &&
+ transport_stream_packet_it != transport_stream_packets.end()) {
+ const auto& transport_stream_packet = *transport_stream_packet_it++;
+ const PacketPart parsed_transport_stream_packet_header(
+ packet.data() + offset, transport_stream_packet.header().size());
+ const PacketPart parsed_transport_stream_packet_payload(
+ parsed_transport_stream_packet_header.end(),
+ transport_stream_packet.payload().size());
+ const PacketPart parsed_transport_stream_packet_filler(
+ parsed_transport_stream_packet_payload.end(),
+ transport_stream_packet.filler().size());
+ offset += WiFiDisplayTransportStreamPacket::kPacketSize;
+
+ // Check bytes.
+ EXPECT_EQ(transport_stream_packet.header(),
+ parsed_transport_stream_packet_header);
+ EXPECT_EQ(transport_stream_packet.payload(),
+ parsed_transport_stream_packet_payload);
+ EXPECT_EQ(transport_stream_packet.filler().size(),
+ std::count(parsed_transport_stream_packet_filler.begin(),
+ parsed_transport_stream_packet_filler.end(),
+ transport_stream_packet.filler().value()));
+
+ if (ParseTransportStreamPacketId(transport_stream_packet) ==
+ widi::kProgramClockReferencePacketId) {
+ pcr = ParseProgramClockReference(
+ &transport_stream_packet.header().begin()[6]);
+ }
+ }
+ EXPECT_EQ(offset, packet.size()) << " Extra packet payload bytes.";
+
+ // Check that the payload contains a correct number of transport stream
+ // packets.
+ const size_t transport_stream_packet_count_in_datagram_packet =
+ packet.size() / WiFiDisplayTransportStreamPacket::kPacketSize;
+ if (&packet == &packets.back()) {
+ EXPECT_GE(transport_stream_packet_count_in_datagram_packet, 1u);
+ EXPECT_LE(transport_stream_packet_count_in_datagram_packet,
+ widi::kMaxTransportStreamPacketCountPerDatagramPacket);
+ } else {
+ EXPECT_EQ(widi::kMaxTransportStreamPacketCountPerDatagramPacket,
+ transport_stream_packet_count_in_datagram_packet);
+ }
+ }
+ EXPECT_EQ(transport_stream_packets.end(), transport_stream_packet_it);
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_session.cc b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_session.cc
new file mode 100644
index 00000000000..f1bfe4cbea7
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_session.cc
@@ -0,0 +1,225 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_session.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/timer/timer.h"
+#include "content/public/common/service_registry.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_media_manager.h"
+#include "third_party/wds/src/libwds/public/logging.h"
+#include "third_party/wds/src/libwds/public/media_manager.h"
+
+namespace {
+const char kErrorInternal[] = "An internal error has occurred";
+const char kErrorTimeout[] = "Sink became unresponsive";
+
+static void LogWDSError(const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ char buffer[256];
+ vsnprintf(buffer, 256, format, args);
+ va_end(args);
+ DVLOG(1) << "[WDS] " << buffer;
+}
+
+} // namespace
+
+namespace extensions {
+
+using api::display_source::ErrorType;
+
+WiFiDisplaySession::WiFiDisplaySession(
+ const DisplaySourceSessionParams& params)
+ : binding_(this),
+ params_(params),
+ cseq_(0),
+ timer_id_(0),
+ weak_factory_(this) {
+ DCHECK(params_.render_frame);
+ wds::LogSystem::set_error_func(&LogWDSError);
+ params.render_frame->GetServiceRegistry()->ConnectToRemoteService(
+ mojo::GetProxy(&service_));
+ service_.set_connection_error_handler(base::Bind(
+ &WiFiDisplaySession::OnIPCConnectionError,
+ weak_factory_.GetWeakPtr()));
+
+ service_->SetClient(binding_.CreateInterfacePtrAndBind());
+ binding_.set_connection_error_handler(base::Bind(
+ &WiFiDisplaySession::OnIPCConnectionError,
+ weak_factory_.GetWeakPtr()));
+}
+
+WiFiDisplaySession::~WiFiDisplaySession() {
+}
+
+void WiFiDisplaySession::Start(const CompletionCallback& callback) {
+ DCHECK_EQ(DisplaySourceSession::Idle, state_);
+ DCHECK(!terminated_callback_.is_null())
+ << "Should be set with 'SetNotificationCallbacks'";
+ DCHECK(!error_callback_.is_null())
+ << "Should be set with 'SetNotificationCallbacks'";
+
+ service_->Connect(params_.sink_id, params_.auth_method, params_.auth_data);
+ state_ = DisplaySourceSession::Establishing;
+ start_completion_callback_ = callback;
+}
+
+void WiFiDisplaySession::Terminate(const CompletionCallback& callback) {
+ DCHECK_EQ(DisplaySourceSession::Established, state_);
+ Terminate();
+ teminate_completion_callback_ = callback;
+}
+
+void WiFiDisplaySession::OnConnected(const mojo::String& ip_address) {
+ DCHECK_EQ(DisplaySourceSession::Established, state_);
+ ip_address_ = ip_address;
+ media_manager_.reset(
+ new WiFiDisplayMediaManager(
+ params_.video_track,
+ params_.audio_track,
+ base::Bind(
+ &WiFiDisplaySession::OnMediaError,
+ weak_factory_.GetWeakPtr())));
+ wfd_source_.reset(wds::Source::Create(this, media_manager_.get(), this));
+ wfd_source_->Start();
+}
+
+void WiFiDisplaySession::OnConnectRequestHandled(bool success,
+ const mojo::String& error) {
+ DCHECK_EQ(DisplaySourceSession::Establishing, state_);
+ state_ =
+ success ? DisplaySourceSession::Established : DisplaySourceSession::Idle;
+ RunStartCallback(success, error);
+}
+
+void WiFiDisplaySession::OnTerminated() {
+ DCHECK_NE(DisplaySourceSession::Idle, state_);
+ state_ = DisplaySourceSession::Idle;
+ media_manager_.reset();
+ wfd_source_.reset();
+ terminated_callback_.Run();
+}
+
+void WiFiDisplaySession::OnDisconnectRequestHandled(bool success,
+ const mojo::String& error) {
+ RunTerminateCallback(success, error);
+}
+
+void WiFiDisplaySession::OnError(int32_t type,
+ const mojo::String& description) {
+ DCHECK(type > api::display_source::ERROR_TYPE_NONE
+ && type <= api::display_source::ERROR_TYPE_LAST);
+ DCHECK_EQ(DisplaySourceSession::Established, state_);
+ error_callback_.Run(static_cast<ErrorType>(type), description);
+}
+
+void WiFiDisplaySession::OnMessage(const mojo::String& data) {
+ DCHECK_EQ(DisplaySourceSession::Established, state_);
+ DCHECK(wfd_source_);
+ wfd_source_->RTSPDataReceived(data);
+}
+
+std::string WiFiDisplaySession::GetLocalIPAddress() const {
+ return ip_address_;
+}
+
+int WiFiDisplaySession::GetNextCSeq(int* initial_peer_cseq) const {
+ return ++cseq_;
+}
+
+void WiFiDisplaySession::SendRTSPData(const std::string& message) {
+ service_->SendMessage(message);
+}
+
+unsigned WiFiDisplaySession::CreateTimer(int seconds) {
+ scoped_ptr<base::Timer> timer(new base::Timer(true, true));
+ auto insert_ret = timers_.insert(
+ std::pair<int, scoped_ptr<base::Timer>>(
+ ++timer_id_, std::move(timer)));
+ DCHECK(insert_ret.second);
+ insert_ret.first->second->Start(FROM_HERE,
+ base::TimeDelta::FromSeconds(seconds),
+ base::Bind(&wds::Source::OnTimerEvent,
+ base::Unretained(wfd_source_.get()),
+ timer_id_));
+ return static_cast<unsigned>(timer_id_);
+}
+
+void WiFiDisplaySession::ReleaseTimer(unsigned timer_id) {
+ auto it = timers_.find(static_cast<int>(timer_id));
+ if (it != timers_.end())
+ timers_.erase(it);
+}
+
+void WiFiDisplaySession::ErrorOccurred(wds::ErrorType error) {
+ DCHECK_NE(DisplaySourceSession::Idle, state_);
+ if (error == wds::TimeoutError) {
+ error_callback_.Run(api::display_source::ERROR_TYPE_TIMEOUT_ERROR,
+ kErrorTimeout);
+ } else {
+ error_callback_.Run(api::display_source::ERROR_TYPE_UNKNOWN_ERROR,
+ kErrorInternal);
+ }
+ // The session cannot continue.
+ Terminate();
+}
+
+void WiFiDisplaySession::SessionCompleted() {
+ DCHECK_NE(DisplaySourceSession::Idle, state_);
+ // The session has finished normally.
+ Terminate();
+}
+
+void WiFiDisplaySession::OnIPCConnectionError() {
+ // We must explicitly notify the session termination as it will never
+ // arrive from browser process (IPC is broken).
+ switch (state_) {
+ case DisplaySourceSession::Idle:
+ case DisplaySourceSession::Establishing:
+ RunStartCallback(false, kErrorInternal);
+ break;
+ case DisplaySourceSession::Terminating:
+ case DisplaySourceSession::Established:
+ error_callback_.Run(api::display_source::ERROR_TYPE_UNKNOWN_ERROR,
+ kErrorInternal);
+ state_ = DisplaySourceSession::Idle;
+ terminated_callback_.Run();
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+void WiFiDisplaySession::OnMediaError(const std::string& error) {
+ DCHECK_NE(DisplaySourceSession::Idle, state_);
+ error_callback_.Run(api::display_source::ERROR_TYPE_MEDIA_PIPELINE_ERROR,
+ error);
+ Terminate();
+}
+
+void WiFiDisplaySession::Terminate() {
+ if (state_ == DisplaySourceSession::Established) {
+ service_->Disconnect();
+ state_ = DisplaySourceSession::Terminating;
+ }
+}
+
+void WiFiDisplaySession::RunStartCallback(bool success,
+ const std::string& error_message) {
+ if (!start_completion_callback_.is_null())
+ start_completion_callback_.Run(success, error_message);
+}
+
+void WiFiDisplaySession::RunTerminateCallback(
+ bool success,
+ const std::string& error_message) {
+ if (!teminate_completion_callback_.is_null())
+ teminate_completion_callback_.Run(success, error_message);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_session.h b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_session.h
new file mode 100644
index 00000000000..bacb875dbac
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_session.h
@@ -0,0 +1,95 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_SESSION_H_
+#define EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_SESSION_H_
+
+#include <map>
+#include <string>
+
+#include "extensions/common/mojo/wifi_display_session_service.mojom.h"
+#include "extensions/renderer/api/display_source/display_source_session.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "third_party/wds/src/libwds/public/source.h"
+
+namespace base {
+class Timer;
+} // namespace base
+
+namespace extensions {
+
+class WiFiDisplayMediaManager;
+
+// This class represents a single Wi-Fi Display session.
+// It manages life-cycle of the session and it is also responsible for
+// exchange of session controlling (RTSP) messages with the sink.
+class WiFiDisplaySession: public DisplaySourceSession,
+ public WiFiDisplaySessionServiceClient,
+ public wds::Peer::Delegate,
+ public wds::Peer::Observer {
+ public:
+ explicit WiFiDisplaySession(
+ const DisplaySourceSessionParams& params);
+ ~WiFiDisplaySession() override;
+
+ private:
+ using DisplaySourceSession::CompletionCallback;
+ // DisplaySourceSession overrides.
+ void Start(const CompletionCallback& callback) override;
+ void Terminate(const CompletionCallback& callback) override;
+
+ // WiFiDisplaySessionServiceClient overrides.
+ void OnConnected(const mojo::String& ip_address) override;
+ void OnConnectRequestHandled(bool success,
+ const mojo::String& error) override;
+ void OnTerminated() override;
+ void OnDisconnectRequestHandled(bool success,
+ const mojo::String& error) override;
+ void OnError(int32_t type, const mojo::String& description) override;
+ void OnMessage(const mojo::String& data) override;
+
+ // wds::Peer::Delegate overrides.
+ unsigned CreateTimer(int seconds) override;
+ void ReleaseTimer(unsigned timer_id) override;
+ void SendRTSPData(const std::string& message) override;
+ std::string GetLocalIPAddress() const override;
+ int GetNextCSeq(int* initial_peer_cseq = nullptr) const override;
+
+ // wds::Peer::Observer overrides.
+ void ErrorOccurred(wds::ErrorType error) override;
+ void SessionCompleted() override;
+
+ // A connection error handler for the mojo objects used in this class.
+ void OnIPCConnectionError();
+
+ // An error handler for media pipeline error.
+ void OnMediaError(const std::string& error);
+
+ void Terminate();
+
+ void RunStartCallback(bool success, const std::string& error = "");
+ void RunTerminateCallback(bool success, const std::string& error = "");
+
+ private:
+ scoped_ptr<wds::Source> wfd_source_;
+ scoped_ptr<WiFiDisplayMediaManager> media_manager_;
+ WiFiDisplaySessionServicePtr service_;
+ mojo::Binding<WiFiDisplaySessionServiceClient> binding_;
+ std::string ip_address_;
+ std::map<int, scoped_ptr<base::Timer>> timers_;
+
+ DisplaySourceSessionParams params_;
+ CompletionCallback start_completion_callback_;
+ CompletionCallback teminate_completion_callback_;
+ // Holds sequence number for the following RTSP request-response pair.
+ mutable int cseq_;
+ int timer_id_;
+ base::WeakPtrFactory<WiFiDisplaySession> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WiFiDisplaySession);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_SESSION_H_
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_stream_packet_part.h b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_stream_packet_part.h
new file mode 100644
index 00000000000..152b4a7ea23
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_stream_packet_part.h
@@ -0,0 +1,43 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_STREAM_PACKET_PART_H_
+#define EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_STREAM_PACKET_PART_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/macros.h"
+
+namespace extensions {
+
+// WiFi Display elementary stream unit packetization consists of multiple
+// packetization phases. During those phases, unit buffer bytes are not
+// modified but only wrapped in packets.
+// This class allows different kind of WiFi Display stream packets to refer to
+// unit buffer bytes without copying them.
+class WiFiDisplayStreamPacketPart {
+ public:
+ WiFiDisplayStreamPacketPart(const uint8_t* data, size_t size)
+ : data_(data), size_(size) {}
+ template <size_t N>
+ explicit WiFiDisplayStreamPacketPart(const uint8_t (&data)[N])
+ : WiFiDisplayStreamPacketPart(data, N) {}
+
+ const uint8_t* begin() const { return data(); }
+ const uint8_t* data() const { return data_; }
+ bool empty() const { return size() == 0u; }
+ const uint8_t* end() const { return data() + size(); }
+ size_t size() const { return size_; }
+
+ private:
+ const uint8_t* const data_;
+ const size_t size_;
+
+ DISALLOW_COPY_AND_ASSIGN(WiFiDisplayStreamPacketPart);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_STREAM_PACKET_BASE_H_
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.cc b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.cc
new file mode 100644
index 00000000000..eee0a52c55a
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.cc
@@ -0,0 +1,727 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h"
+
+#include <algorithm>
+#include <cstring>
+#include <utility>
+
+#include "base/logging.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.h"
+
+namespace extensions {
+namespace {
+
+uint32_t crc32(uint32_t crc, const uint8_t* data, size_t len) {
+ static const uint32_t table[256] = {
+ 0x00000000, 0xb71dc104, 0x6e3b8209, 0xd926430d, 0xdc760413, 0x6b6bc517,
+ 0xb24d861a, 0x0550471e, 0xb8ed0826, 0x0ff0c922, 0xd6d68a2f, 0x61cb4b2b,
+ 0x649b0c35, 0xd386cd31, 0x0aa08e3c, 0xbdbd4f38, 0x70db114c, 0xc7c6d048,
+ 0x1ee09345, 0xa9fd5241, 0xacad155f, 0x1bb0d45b, 0xc2969756, 0x758b5652,
+ 0xc836196a, 0x7f2bd86e, 0xa60d9b63, 0x11105a67, 0x14401d79, 0xa35ddc7d,
+ 0x7a7b9f70, 0xcd665e74, 0xe0b62398, 0x57abe29c, 0x8e8da191, 0x39906095,
+ 0x3cc0278b, 0x8bdde68f, 0x52fba582, 0xe5e66486, 0x585b2bbe, 0xef46eaba,
+ 0x3660a9b7, 0x817d68b3, 0x842d2fad, 0x3330eea9, 0xea16ada4, 0x5d0b6ca0,
+ 0x906d32d4, 0x2770f3d0, 0xfe56b0dd, 0x494b71d9, 0x4c1b36c7, 0xfb06f7c3,
+ 0x2220b4ce, 0x953d75ca, 0x28803af2, 0x9f9dfbf6, 0x46bbb8fb, 0xf1a679ff,
+ 0xf4f63ee1, 0x43ebffe5, 0x9acdbce8, 0x2dd07dec, 0x77708634, 0xc06d4730,
+ 0x194b043d, 0xae56c539, 0xab068227, 0x1c1b4323, 0xc53d002e, 0x7220c12a,
+ 0xcf9d8e12, 0x78804f16, 0xa1a60c1b, 0x16bbcd1f, 0x13eb8a01, 0xa4f64b05,
+ 0x7dd00808, 0xcacdc90c, 0x07ab9778, 0xb0b6567c, 0x69901571, 0xde8dd475,
+ 0xdbdd936b, 0x6cc0526f, 0xb5e61162, 0x02fbd066, 0xbf469f5e, 0x085b5e5a,
+ 0xd17d1d57, 0x6660dc53, 0x63309b4d, 0xd42d5a49, 0x0d0b1944, 0xba16d840,
+ 0x97c6a5ac, 0x20db64a8, 0xf9fd27a5, 0x4ee0e6a1, 0x4bb0a1bf, 0xfcad60bb,
+ 0x258b23b6, 0x9296e2b2, 0x2f2bad8a, 0x98366c8e, 0x41102f83, 0xf60dee87,
+ 0xf35da999, 0x4440689d, 0x9d662b90, 0x2a7bea94, 0xe71db4e0, 0x500075e4,
+ 0x892636e9, 0x3e3bf7ed, 0x3b6bb0f3, 0x8c7671f7, 0x555032fa, 0xe24df3fe,
+ 0x5ff0bcc6, 0xe8ed7dc2, 0x31cb3ecf, 0x86d6ffcb, 0x8386b8d5, 0x349b79d1,
+ 0xedbd3adc, 0x5aa0fbd8, 0xeee00c69, 0x59fdcd6d, 0x80db8e60, 0x37c64f64,
+ 0x3296087a, 0x858bc97e, 0x5cad8a73, 0xebb04b77, 0x560d044f, 0xe110c54b,
+ 0x38368646, 0x8f2b4742, 0x8a7b005c, 0x3d66c158, 0xe4408255, 0x535d4351,
+ 0x9e3b1d25, 0x2926dc21, 0xf0009f2c, 0x471d5e28, 0x424d1936, 0xf550d832,
+ 0x2c769b3f, 0x9b6b5a3b, 0x26d61503, 0x91cbd407, 0x48ed970a, 0xfff0560e,
+ 0xfaa01110, 0x4dbdd014, 0x949b9319, 0x2386521d, 0x0e562ff1, 0xb94beef5,
+ 0x606dadf8, 0xd7706cfc, 0xd2202be2, 0x653deae6, 0xbc1ba9eb, 0x0b0668ef,
+ 0xb6bb27d7, 0x01a6e6d3, 0xd880a5de, 0x6f9d64da, 0x6acd23c4, 0xddd0e2c0,
+ 0x04f6a1cd, 0xb3eb60c9, 0x7e8d3ebd, 0xc990ffb9, 0x10b6bcb4, 0xa7ab7db0,
+ 0xa2fb3aae, 0x15e6fbaa, 0xccc0b8a7, 0x7bdd79a3, 0xc660369b, 0x717df79f,
+ 0xa85bb492, 0x1f467596, 0x1a163288, 0xad0bf38c, 0x742db081, 0xc3307185,
+ 0x99908a5d, 0x2e8d4b59, 0xf7ab0854, 0x40b6c950, 0x45e68e4e, 0xf2fb4f4a,
+ 0x2bdd0c47, 0x9cc0cd43, 0x217d827b, 0x9660437f, 0x4f460072, 0xf85bc176,
+ 0xfd0b8668, 0x4a16476c, 0x93300461, 0x242dc565, 0xe94b9b11, 0x5e565a15,
+ 0x87701918, 0x306dd81c, 0x353d9f02, 0x82205e06, 0x5b061d0b, 0xec1bdc0f,
+ 0x51a69337, 0xe6bb5233, 0x3f9d113e, 0x8880d03a, 0x8dd09724, 0x3acd5620,
+ 0xe3eb152d, 0x54f6d429, 0x7926a9c5, 0xce3b68c1, 0x171d2bcc, 0xa000eac8,
+ 0xa550add6, 0x124d6cd2, 0xcb6b2fdf, 0x7c76eedb, 0xc1cba1e3, 0x76d660e7,
+ 0xaff023ea, 0x18ede2ee, 0x1dbda5f0, 0xaaa064f4, 0x738627f9, 0xc49be6fd,
+ 0x09fdb889, 0xbee0798d, 0x67c63a80, 0xd0dbfb84, 0xd58bbc9a, 0x62967d9e,
+ 0xbbb03e93, 0x0cadff97, 0xb110b0af, 0x060d71ab, 0xdf2b32a6, 0x6836f3a2,
+ 0x6d66b4bc, 0xda7b75b8, 0x035d36b5, 0xb440f7b1};
+ for (; len; ++data, --len)
+ crc = (crc >> 8) ^ table[(crc & 0xFFu) ^ *data];
+ return crc;
+}
+
+// Code and parameters related to the Program Specific Information (PSI)
+// specification.
+namespace psi {
+
+const uint8_t kProgramAssociationTableId = 0x00u;
+const uint8_t kProgramMapTableId = 0x02u;
+
+const uint16_t kFirstProgramNumber = 0x0001u;
+
+const size_t kCrcSize = 4u;
+const size_t kProgramMapTableElementaryStreamEntryBaseSize = 5u;
+const size_t kTableHeaderSize = 3u;
+
+size_t FillInTablePointer(uint8_t* dst, size_t min_size) {
+ size_t i = 1u;
+ if (i < min_size) {
+ std::memset(&dst[i], 0xFF, min_size - i); // Pointer filler bytes
+ i = min_size;
+ }
+ dst[0] = i - 1u; // Pointer field
+ return i;
+}
+
+size_t FillInTableHeaderAndCrc(uint8_t* header_dst,
+ uint8_t* crc_dst,
+ uint8_t table_id) {
+ size_t i;
+ const uint8_t* const header_end = header_dst + kTableHeaderSize;
+ const uint8_t* const crc_end = crc_dst + kCrcSize;
+ const size_t section_length = static_cast<size_t>(crc_end - header_end);
+ DCHECK_LE(section_length, 1021u);
+
+ // Table header.
+ i = 0u;
+ header_dst[i++] = table_id;
+ header_dst[i++] =
+ (0x1u << 7) | // Section syntax indicator (1 for PAT and PMT)
+ (0x0u << 6) | // Private bit (0 for PAT and PMT)
+ (0x3u << 4) | // Reserved bits (both bits on)
+ (0x0u << 2) | // Section length unused bits (both bits off)
+ ((section_length >> 8) & 0x03u); // Section length (10 bits)
+ header_dst[i++] = section_length & 0xFFu; //
+ DCHECK_EQ(kTableHeaderSize, i);
+
+ // CRC.
+ uint32_t crc =
+ crc32(0xFFFFFFFFu, header_dst, static_cast<size_t>(crc_dst - header_dst));
+ i = 0u;
+ // Avoid swapping the crc by reversing write order.
+ crc_dst[i++] = crc & 0xFFu;
+ crc_dst[i++] = (crc >> 8) & 0xFFu;
+ crc_dst[i++] = (crc >> 16) & 0xFFu;
+ crc_dst[i++] = (crc >> 24) & 0xFFu;
+ DCHECK_EQ(kCrcSize, i);
+ return i;
+}
+
+size_t FillInTableSyntax(uint8_t* dst,
+ uint16_t table_id_extension,
+ uint8_t version_number) {
+ size_t i = 0u;
+ dst[i++] = table_id_extension >> 8;
+ dst[i++] = table_id_extension & 0xFFu;
+ dst[i++] = (0x3u << 6) | // Reserved bits (both bits on)
+ ((version_number & 0x1Fu) << 1) | // Version number (5 bits)
+ (0x1u << 0); // Current indicator
+ dst[i++] = 0u; // Section number
+ dst[i++] = 0u; // Last section number
+ return i;
+}
+
+size_t FillInProgramAssociationTableEntry(uint8_t* dst,
+ uint16_t program_number,
+ unsigned pmt_packet_id) {
+ size_t i = 0u;
+ dst[i++] = program_number >> 8;
+ dst[i++] = program_number & 0xFFu;
+ dst[i++] = (0x7u << 5) | // Reserved bits (all 3 bits on)
+ ((pmt_packet_id >> 8) & 0x1Fu); // Packet identifier (13 bits)
+ dst[i++] = pmt_packet_id & 0xFFu; //
+ return i;
+}
+
+size_t FillInProgramMapTableData(uint8_t* dst, unsigned pcr_packet_id) {
+ size_t i = 0u;
+ dst[i++] = (0x7u << 5) | // Reserved bits (all 3 bits on)
+ ((pcr_packet_id >> 8) & 0x1Fu); // Packet identifier (13 bits)
+ dst[i++] = pcr_packet_id & 0xFFu; //
+ dst[i++] = (0xFu << 4) | // Reserved bits (all 4 bits on)
+ (0x0u << 2) | // Program info length unused bits (both bits off)
+ ((0u >> 8) & 0x3u); // Program info length (10 bits)
+ dst[i++] = 0u & 0xFFu; //
+ // No program descriptors
+ return i;
+}
+
+size_t CalculateElementaryStreamInfoLength(
+ const std::vector<WiFiDisplayElementaryStreamDescriptor>& es_descriptors) {
+ size_t es_info_length = 0u;
+ for (const auto& es_descriptor : es_descriptors)
+ es_info_length += es_descriptor.size();
+ DCHECK_EQ(0u, es_info_length >> 8);
+ return es_info_length;
+}
+
+size_t FillInProgramMapTableElementaryStreamEntry(
+ uint8_t* dst,
+ uint8_t stream_type,
+ unsigned es_packet_id,
+ size_t es_info_length,
+ const std::vector<WiFiDisplayElementaryStreamDescriptor>& es_descriptors) {
+ DCHECK_EQ(CalculateElementaryStreamInfoLength(es_descriptors),
+ es_info_length);
+ DCHECK_EQ(0u, es_info_length >> 10);
+ size_t i = 0u;
+ dst[i++] = stream_type;
+ dst[i++] = (0x7u << 5) | // Reserved bits (all 3 bits on)
+ ((es_packet_id >> 8) & 0x1Fu); // Packet identifier (13 bits)
+ dst[i++] = es_packet_id & 0xFFu; //
+ dst[i++] = (0xFu << 4) | // Reserved bits (all 4 bits on)
+ (0x0u << 2) | // ES info length unused bits (both bits off)
+ ((es_info_length >> 8) & 0x3u); // ES info length (10 bits)
+ dst[i++] = es_info_length & 0xFFu; //
+ for (const auto& es_descriptor : es_descriptors) {
+ std::memcpy(&dst[i], es_descriptor.data(), es_descriptor.size());
+ i += es_descriptor.size();
+ }
+ return i;
+}
+
+} // namespace psi
+
+// Code and parameters related to the MPEG Transport Stream (MPEG-TS)
+// specification.
+namespace ts {
+
+const size_t kAdaptationFieldLengthSize = 1u;
+const size_t kAdaptationFieldFlagsSize = 1u;
+const size_t kPacketHeaderSize = 4u;
+const size_t kProgramClockReferenceSize = 6u;
+
+size_t FillInProgramClockReference(uint8_t* dst, const base::TimeTicks& pcr) {
+ // Convert to the number of 27 MHz ticks since some epoch.
+ const uint64_t us =
+ static_cast<uint64_t>((pcr - base::TimeTicks()).InMicroseconds());
+ const uint64_t n = 27u * us;
+ const uint64_t base = n / 300u; // 90 kHz
+ const uint64_t extension = n % 300u;
+
+ size_t i = 0u;
+ dst[i++] = (base >> 25) & 0xFFu; // Base (33 bits)
+ dst[i++] = (base >> 17) & 0xFFu; //
+ dst[i++] = (base >> 9) & 0xFFu; //
+ dst[i++] = (base >> 1) & 0xFFu; //
+ dst[i++] = ((base & 0x01u) << 7) | //
+ (0x3Fu << 1) | // Reserved bits (all 6 bits on)
+ ((extension >> 8) & 0x1u); // Extension (9 bits)
+ dst[i++] = extension & 0xFFu; //
+ DCHECK_EQ(kProgramClockReferenceSize, i);
+ return i;
+}
+
+size_t FillInAdaptationFieldLengthFromSize(uint8_t* dst, size_t size) {
+ size_t i = 0u;
+ dst[i++] = size - 1u;
+ DCHECK_EQ(kAdaptationFieldLengthSize, i);
+ return i;
+}
+
+size_t FillInAdaptationFieldFlags(uint8_t* dst,
+ bool random_access_indicator,
+ const base::TimeTicks& pcr) {
+ size_t i = 0u;
+ dst[i++] = (0x0u << 7) | // Discontinuity indicator
+ (random_access_indicator << 6) | // Random access indicator
+ (0x0u << 5) | // Elementary stream priority indicator
+ ((!pcr.is_null()) << 4) | // PCR flag
+ (0x0u << 3) | // OPCR flag
+ (0x0u << 2) | // Splicing point flag
+ (0x0u << 1) | // Transport private data flag
+ (0x0u << 0); // Adaptation field extension flag
+ DCHECK_EQ(kAdaptationFieldFlagsSize, i);
+ return i;
+}
+
+size_t FillInAdaptationField(uint8_t* dst,
+ bool random_access_indicator,
+ size_t min_size) {
+ // Reserve space for a length.
+ size_t i = kAdaptationFieldLengthSize;
+
+ if (random_access_indicator || i < min_size) {
+ const base::TimeTicks pcr;
+ i += FillInAdaptationFieldFlags(&dst[i], random_access_indicator, pcr);
+
+ if (i < min_size) {
+ std::memset(&dst[i], 0xFF, min_size - i); // Stuffing bytes.
+ i = min_size;
+ }
+ }
+
+ // Fill in a length now that the size is known.
+ FillInAdaptationFieldLengthFromSize(dst, i);
+
+ return i;
+}
+
+size_t FillInPacketHeader(uint8_t* dst,
+ bool payload_unit_start_indicator,
+ unsigned packet_id,
+ bool adaptation_field_flag,
+ unsigned continuity_counter) {
+ size_t i = 0u;
+ dst[i++] = 0x47; // Sync byte ('G')
+ dst[i++] =
+ (0x0u << 7) | // Transport error indicator
+ (payload_unit_start_indicator << 6) | // Payload unit start indicator
+ (0x0 << 5) | // Transport priority
+ ((packet_id >> 8) & 0x1Fu); // Packet identifier (13 bits)
+ dst[i++] = packet_id & 0xFFu; //
+ dst[i++] = (0x0u << 6) | // Scrambling control (0b00 for not)
+ (adaptation_field_flag << 5) | // Adaptation field flag
+ (0x1u << 4) | // Payload flag
+ (continuity_counter & 0xFu); // Continuity counter
+ DCHECK_EQ(kPacketHeaderSize, i);
+ return i;
+}
+
+} // namespace ts
+
+// Code and parameters related to the WiFi Display specification.
+namespace widi {
+
+const size_t kUnitHeaderMaxSize = 4u;
+
+// Maximum interval between meta information which includes:
+// * Program Association Table (PAT)
+// * Program Map Table (PMT)
+// * Program Clock Reference (PCR)
+const int kMaxMillisecondsBetweenMetaInformation = 100u;
+
+const unsigned kProgramAssociationTablePacketId = 0x0000u;
+const unsigned kProgramMapTablePacketId = 0x0100u;
+const unsigned kProgramClockReferencePacketId = 0x1000u;
+const unsigned kVideoStreamPacketId = 0x1011u;
+const unsigned kFirstAudioStreamPacketId = 0x1100u;
+
+size_t FillInUnitHeader(uint8_t* dst,
+ const WiFiDisplayElementaryStreamInfo& stream_info) {
+ size_t i = 0u;
+
+ if (stream_info.type() == WiFiDisplayElementaryStreamInfo::AUDIO_LPCM) {
+ // Convert an LPCM audio stream descriptor to an LPCM unit header.
+ if (const auto* lpcm_descriptor =
+ stream_info.FindDescriptor<
+ WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream>()) {
+ dst[i++] = 0xA0u; // Sub stream ID (0th sub stream)
+ dst[i++] = WiFiDisplayTransportStreamPacketizer::LPCM::kFramesPerUnit;
+ dst[i++] = ((0x00u << 1) | // Reserved (all 7 bits off)
+ (lpcm_descriptor->emphasis_flag() << 0));
+ dst[i++] = ((lpcm_descriptor->bits_per_sample() << 6) |
+ (lpcm_descriptor->sampling_frequency() << 3) |
+ (lpcm_descriptor->number_of_channels() << 0));
+ }
+ }
+
+ DCHECK_LE(i, kUnitHeaderMaxSize);
+ return i;
+}
+
+} // namespace widi
+
+} // namespace
+
+WiFiDisplayTransportStreamPacket::WiFiDisplayTransportStreamPacket(
+ const uint8_t* header_data,
+ size_t header_size)
+ : header_(header_data, header_size),
+ payload_(header_.end(), 0u),
+ filler_(kPacketSize - header_size) {}
+
+WiFiDisplayTransportStreamPacket::WiFiDisplayTransportStreamPacket(
+ const uint8_t* header_data,
+ size_t header_size,
+ const uint8_t* payload_data)
+ : header_(header_data, header_size),
+ payload_(payload_data, kPacketSize - header_size),
+ filler_(0u) {}
+
+struct WiFiDisplayTransportStreamPacketizer::ElementaryStreamState {
+ ElementaryStreamState(WiFiDisplayElementaryStreamInfo info,
+ uint16_t packet_id,
+ uint8_t stream_id)
+ : info(std::move(info)),
+ info_length(
+ psi::CalculateElementaryStreamInfoLength(this->info.descriptors())),
+ packet_id(packet_id),
+ stream_id(stream_id) {}
+
+ WiFiDisplayElementaryStreamInfo info;
+ uint8_t info_length;
+ struct {
+ uint8_t continuity = 0u;
+ } counters;
+ uint16_t packet_id;
+ uint8_t stream_id;
+};
+
+WiFiDisplayTransportStreamPacketizer::WiFiDisplayTransportStreamPacketizer(
+ const base::TimeDelta& delay_for_unit_time_stamps,
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos)
+ : delay_for_unit_time_stamps_(delay_for_unit_time_stamps) {
+ std::memset(&counters_, 0x00, sizeof(counters_));
+ if (!stream_infos.empty())
+ CHECK(SetElementaryStreams(std::move(stream_infos)));
+}
+
+WiFiDisplayTransportStreamPacketizer::~WiFiDisplayTransportStreamPacketizer() {}
+
+bool WiFiDisplayTransportStreamPacketizer::EncodeElementaryStreamUnit(
+ unsigned stream_index,
+ const uint8_t* unit_data,
+ size_t unit_size,
+ bool random_access,
+ base::TimeTicks pts,
+ base::TimeTicks dts,
+ bool flush) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_LT(stream_index, stream_states_.size());
+ ElementaryStreamState& stream_state = stream_states_[stream_index];
+
+ if (program_clock_reference_.is_null() ||
+ base::TimeTicks::Now() - program_clock_reference_ >
+ base::TimeDelta::FromMilliseconds(
+ widi::kMaxMillisecondsBetweenMetaInformation) /
+ 2) {
+ if (!EncodeMetaInformation(false))
+ return false;
+ }
+
+ uint8_t unit_header_data[widi::kUnitHeaderMaxSize];
+ const size_t unit_header_size =
+ widi::FillInUnitHeader(unit_header_data, stream_state.info);
+
+ UpdateDelayForUnitTimeStamps(pts, dts);
+ NormalizeUnitTimeStamps(&pts, &dts);
+
+ WiFiDisplayElementaryStreamPacketizer elementary_stream_packetizer;
+ WiFiDisplayElementaryStreamPacket elementary_stream_packet =
+ elementary_stream_packetizer.EncodeElementaryStreamUnit(
+ stream_state.stream_id, unit_header_data, unit_header_size, unit_data,
+ unit_size, pts, dts);
+
+ size_t adaptation_field_min_size = 0u;
+ uint8_t header_data[WiFiDisplayTransportStreamPacket::kPacketSize];
+ bool is_payload_unit_end;
+ bool is_payload_unit_start = true;
+ size_t remaining_unit_size = elementary_stream_packet.unit().size();
+ do {
+ // Fill in headers and an adaptation field:
+ // * Transport stream packet header
+ // * Transport stream adaptation field
+ // (only for the first and/or the last packet):
+ // - for the first packet to hold flags
+ // - for the last packet to hold padding
+ // * PES packet header (only for the first packet):
+ // - PES packet header base
+ // - Optional PES header base
+ // - Optional PES header optional fields:
+ // - Presentation time stamp
+ // - Decoding time stamp
+ bool adaptation_field_flag = false;
+ size_t header_min_size;
+ if (is_payload_unit_start || is_payload_unit_end) {
+ header_min_size = ts::kPacketHeaderSize;
+ if (is_payload_unit_start) {
+ header_min_size += elementary_stream_packet.header().size() +
+ elementary_stream_packet.unit_header().size();
+ }
+ adaptation_field_min_size =
+ std::max(
+ WiFiDisplayTransportStreamPacket::kPacketSize - header_min_size,
+ remaining_unit_size) -
+ remaining_unit_size;
+ adaptation_field_flag = adaptation_field_min_size > 0 ||
+ (is_payload_unit_start && random_access);
+ }
+ size_t i = 0u;
+ i += ts::FillInPacketHeader(&header_data[i], is_payload_unit_start,
+ stream_state.packet_id, adaptation_field_flag,
+ stream_state.counters.continuity++);
+ if (is_payload_unit_start) {
+ size_t adaptation_field_size = adaptation_field_min_size;
+ if (adaptation_field_flag) {
+ adaptation_field_size = ts::FillInAdaptationField(
+ &header_data[i], random_access, adaptation_field_min_size);
+ i += adaptation_field_size;
+ DCHECK_GE(adaptation_field_size, adaptation_field_min_size);
+ }
+ std::memcpy(&header_data[i], elementary_stream_packet.header().data(),
+ elementary_stream_packet.header().size());
+ i += elementary_stream_packet.header().size();
+ std::memcpy(&header_data[i],
+ elementary_stream_packet.unit_header().data(),
+ elementary_stream_packet.unit_header().size());
+ i += elementary_stream_packet.unit_header().size();
+ DCHECK_EQ(header_min_size + adaptation_field_size, i);
+ } else if (is_payload_unit_end) {
+ if (adaptation_field_flag) {
+ // Fill in an adaptation field only for padding.
+ i += ts::FillInAdaptationField(&header_data[i], false,
+ adaptation_field_min_size);
+ }
+ DCHECK_EQ(header_min_size + adaptation_field_min_size, i);
+ }
+
+ // Delegate the packet.
+ WiFiDisplayTransportStreamPacket packet(
+ header_data, i,
+ elementary_stream_packet.unit().end() - remaining_unit_size);
+ DCHECK_LE(packet.payload().size(), remaining_unit_size);
+ remaining_unit_size -= packet.payload().size();
+ if (!OnPacketizedTransportStreamPacket(
+ packet, flush && remaining_unit_size == 0u)) {
+ return false;
+ }
+
+ // Prepare for the next packet.
+ is_payload_unit_end =
+ remaining_unit_size <=
+ WiFiDisplayTransportStreamPacket::kPacketSize - ts::kPacketHeaderSize;
+ is_payload_unit_start = false;
+ } while (remaining_unit_size > 0u);
+
+ DCHECK_EQ(0u, remaining_unit_size);
+
+ return true;
+}
+
+bool WiFiDisplayTransportStreamPacketizer::EncodeMetaInformation(bool flush) {
+ DCHECK(CalledOnValidThread());
+
+ return (EncodeProgramAssociationTable(false) &&
+ EncodeProgramMapTables(false) && EncodeProgramClockReference(flush));
+}
+
+bool WiFiDisplayTransportStreamPacketizer::EncodeProgramAssociationTable(
+ bool flush) {
+ DCHECK(CalledOnValidThread());
+
+ const uint16_t transport_stream_id = 0x0001u;
+
+ uint8_t header_data[WiFiDisplayTransportStreamPacket::kPacketSize];
+ size_t i = 0u;
+
+ // Fill in a packet header.
+ i += ts::FillInPacketHeader(&header_data[i], true,
+ widi::kProgramAssociationTablePacketId, false,
+ counters_.program_association_table_continuity++);
+
+ // Fill in a minimal table pointer.
+ i += psi::FillInTablePointer(&header_data[i], 0u);
+
+ // Reserve space for a table header.
+ const size_t table_header_index = i;
+ i += psi::kTableHeaderSize;
+
+ // Fill in a table syntax.
+ const uint8_t version_number = 0u;
+ i += psi::FillInTableSyntax(&header_data[i], transport_stream_id,
+ version_number);
+
+ // Fill in program association table data.
+ i += psi::FillInProgramAssociationTableEntry(&header_data[i],
+ psi::kFirstProgramNumber,
+ widi::kProgramMapTablePacketId);
+
+ // Fill in a table header and a CRC now that the table size is known.
+ i += psi::FillInTableHeaderAndCrc(&header_data[table_header_index],
+ &header_data[i],
+ psi::kProgramAssociationTableId);
+
+ // Delegate the packet.
+ return OnPacketizedTransportStreamPacket(
+ WiFiDisplayTransportStreamPacket(header_data, i), flush);
+}
+
+bool WiFiDisplayTransportStreamPacketizer::EncodeProgramClockReference(
+ bool flush) {
+ DCHECK(CalledOnValidThread());
+
+ program_clock_reference_ = base::TimeTicks::Now();
+
+ uint8_t header_data[ts::kPacketHeaderSize + ts::kAdaptationFieldLengthSize +
+ ts::kAdaptationFieldFlagsSize +
+ ts::kProgramClockReferenceSize];
+ size_t i = 0u;
+
+ // Fill in a packet header.
+ i += ts::FillInPacketHeader(&header_data[i], true,
+ widi::kProgramClockReferencePacketId, true,
+ counters_.program_clock_reference_continuity++);
+
+ // Fill in an adaptation field.
+ i += ts::FillInAdaptationFieldLengthFromSize(
+ &header_data[i], WiFiDisplayTransportStreamPacket::kPacketSize - i);
+ i += ts::FillInAdaptationFieldFlags(&header_data[i], false,
+ program_clock_reference_);
+ i += ts::FillInProgramClockReference(&header_data[i],
+ program_clock_reference_);
+
+ DCHECK_EQ(std::end(header_data), header_data + i);
+
+ // Delegate the packet.
+ return OnPacketizedTransportStreamPacket(
+ WiFiDisplayTransportStreamPacket(header_data, i), flush);
+}
+
+bool WiFiDisplayTransportStreamPacketizer::EncodeProgramMapTables(bool flush) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!stream_states_.empty());
+
+ const uint16_t program_number = psi::kFirstProgramNumber;
+
+ uint8_t header_data[WiFiDisplayTransportStreamPacket::kPacketSize];
+ size_t i = 0u;
+
+ // Fill in a packet header.
+ i += ts::FillInPacketHeader(&header_data[i], true,
+ widi::kProgramMapTablePacketId, false,
+ counters_.program_map_table_continuity++);
+
+ // Fill in a minimal table pointer.
+ i += psi::FillInTablePointer(&header_data[i], 0u);
+
+ // Reserve space for a table header.
+ const size_t table_header_index = i;
+ i += psi::kTableHeaderSize;
+
+ // Fill in a table syntax.
+ i += psi::FillInTableSyntax(&header_data[i], program_number,
+ counters_.program_map_table_version);
+
+ // Fill in program map table data.
+ i += psi::FillInProgramMapTableData(&header_data[i],
+ widi::kProgramClockReferencePacketId);
+ for (const auto& stream_state : stream_states_) {
+ DCHECK_LE(i + psi::kProgramMapTableElementaryStreamEntryBaseSize +
+ stream_state.info_length + psi::kCrcSize,
+ WiFiDisplayTransportStreamPacket::kPacketSize);
+ i += psi::FillInProgramMapTableElementaryStreamEntry(
+ &header_data[i], stream_state.info.type(), stream_state.packet_id,
+ stream_state.info_length, stream_state.info.descriptors());
+ }
+
+ // Fill in a table header and a CRC now that the table size is known.
+ i += psi::FillInTableHeaderAndCrc(&header_data[table_header_index],
+ &header_data[i], psi::kProgramMapTableId);
+
+ // Delegate the packet.
+ return OnPacketizedTransportStreamPacket(
+ WiFiDisplayTransportStreamPacket(header_data, i), flush);
+}
+
+void WiFiDisplayTransportStreamPacketizer::NormalizeUnitTimeStamps(
+ base::TimeTicks* pts,
+ base::TimeTicks* dts) const {
+ DCHECK(CalledOnValidThread());
+
+ // Normalize a presentation time stamp.
+ if (!pts || pts->is_null())
+ return;
+ *pts += delay_for_unit_time_stamps_;
+ DCHECK_LE(program_clock_reference_, *pts);
+
+ // Normalize a decoding time stamp.
+ if (!dts || dts->is_null())
+ return;
+ *dts += delay_for_unit_time_stamps_;
+ DCHECK_LE(program_clock_reference_, *dts);
+ DCHECK_LE(*dts, *pts);
+}
+
+bool WiFiDisplayTransportStreamPacketizer::SetElementaryStreams(
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos) {
+ DCHECK(CalledOnValidThread());
+
+ std::vector<ElementaryStreamState> new_stream_states;
+ new_stream_states.reserve(stream_infos.size());
+
+ uint8_t audio_stream_id =
+ WiFiDisplayElementaryStreamPacketizer::kFirstAudioStreamId;
+ uint16_t audio_stream_packet_id = widi::kFirstAudioStreamPacketId;
+ uint8_t private_stream_1_id =
+ WiFiDisplayElementaryStreamPacketizer::kPrivateStream1Id;
+ uint16_t video_stream_packet_id = widi::kVideoStreamPacketId;
+
+ for (auto& stream_info : stream_infos) {
+ uint16_t packet_id;
+ uint8_t stream_id;
+
+ switch (stream_info.type()) {
+ case AUDIO_AAC:
+ packet_id = audio_stream_packet_id++;
+ stream_id = audio_stream_id++;
+ break;
+ case AUDIO_AC3:
+ case AUDIO_LPCM:
+ if (private_stream_1_id !=
+ WiFiDisplayElementaryStreamPacketizer::kPrivateStream1Id) {
+ return false;
+ }
+ packet_id = audio_stream_packet_id++;
+ stream_id = private_stream_1_id++;
+ break;
+ case VIDEO_H264:
+ if (video_stream_packet_id != widi::kVideoStreamPacketId)
+ return false;
+ packet_id = video_stream_packet_id++;
+ stream_id = WiFiDisplayElementaryStreamPacketizer::kFirstVideoStreamId;
+ break;
+ }
+
+ new_stream_states.emplace_back(std::move(stream_info), packet_id,
+ stream_id);
+ }
+
+ // If there are no previous states, there is no previous program map table
+ // to change, either. This ensures that the first encoded program map table
+ // has version 0.
+ if (!stream_states_.empty())
+ ++counters_.program_map_table_version;
+
+ stream_states_.swap(new_stream_states);
+
+ return true;
+}
+
+void WiFiDisplayTransportStreamPacketizer::UpdateDelayForUnitTimeStamps(
+ const base::TimeTicks& pts,
+ const base::TimeTicks& dts) {
+ DCHECK(CalledOnValidThread());
+
+ if (pts.is_null())
+ return;
+
+ const base::TimeTicks now = base::TimeTicks::Now();
+ DCHECK_LE(program_clock_reference_, now);
+
+ // Ensure that delayed time stamps are greater than or equal to now.
+ const base::TimeTicks ts_min =
+ (dts.is_null() ? pts : dts) + delay_for_unit_time_stamps_;
+ if (now > ts_min) {
+ const base::TimeDelta error = now - ts_min;
+ delay_for_unit_time_stamps_ += 2 * error;
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h
new file mode 100644
index 00000000000..0cdeb6a750d
--- /dev/null
+++ b/chromium/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h
@@ -0,0 +1,176 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_TRANSPORT_STREAM_PACKETIZER_H_
+#define EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_TRANSPORT_STREAM_PACKETIZER_H_
+
+#include <vector>
+
+#include "base/threading/non_thread_safe.h"
+#include "base/time/time.h"
+#include "extensions/renderer/api/display_source/wifi_display/wifi_display_stream_packet_part.h"
+
+namespace extensions {
+
+class WiFiDisplayElementaryStreamInfo;
+class WiFiDisplayElementaryStreamPacket;
+
+// This class represents an MPEG Transport Stream (MPEG-TS) packet containing
+// WiFi Display elementary stream unit data or related meta information.
+class WiFiDisplayTransportStreamPacket {
+ public:
+ enum { kPacketSize = 188u };
+
+ using Part = WiFiDisplayStreamPacketPart;
+
+ // This class represents a possibly empty padding part in the end of
+ // a transport stream packet. The padding part consists of repeated bytes
+ // having the same value.
+ class PaddingPart {
+ public:
+ explicit PaddingPart(unsigned size) : size_(size) {}
+
+ unsigned size() const { return size_; }
+ uint8_t value() const { return 0xFFu; }
+
+ private:
+ const unsigned size_;
+
+ DISALLOW_COPY_AND_ASSIGN(PaddingPart);
+ };
+
+ WiFiDisplayTransportStreamPacket(const uint8_t* header_data,
+ size_t header_size);
+ WiFiDisplayTransportStreamPacket(const uint8_t* header_data,
+ size_t header_size,
+ const uint8_t* payload_data);
+
+ const Part& header() const { return header_; }
+ const Part& payload() const { return payload_; }
+ const PaddingPart& filler() const { return filler_; }
+
+ private:
+ const Part header_;
+ const Part payload_;
+ const PaddingPart filler_;
+
+ DISALLOW_COPY_AND_ASSIGN(WiFiDisplayTransportStreamPacket);
+};
+
+// The WiFi Display transport stream packetizer packetizes unit buffers to
+// MPEG Transport Stream (MPEG-TS) packets containing either meta information
+// or Packetized Elementary Stream (PES) packets containing unit data.
+//
+// Whenever a Transport Stream (TS) packet is fully created and thus ready for
+// further processing, a pure virtual member function
+// |OnPacketizedTransportStreamPacket| is called.
+class WiFiDisplayTransportStreamPacketizer : public base::NonThreadSafe {
+ public:
+ enum ElementaryStreamType : uint8_t {
+ AUDIO_AAC = 0x0Fu,
+ AUDIO_AC3 = 0x81u,
+ AUDIO_LPCM = 0x83u,
+ VIDEO_H264 = 0x1Bu,
+ };
+
+ // Fixed coding parameters for Linear Pulse-Code Modulation (LPCM) audio
+ // streams. See |WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream| for
+ // variable ones.
+ struct LPCM {
+ enum {
+ kFramesPerUnit = 6u,
+ kChannelSamplesPerFrame = 80u,
+ kChannelSamplesPerUnit = kChannelSamplesPerFrame * kFramesPerUnit
+ };
+ };
+
+ WiFiDisplayTransportStreamPacketizer(
+ const base::TimeDelta& delay_for_unit_time_stamps,
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos);
+ virtual ~WiFiDisplayTransportStreamPacketizer();
+
+ // Encodes one elementary stream unit buffer (such as one video frame or
+ // 2 * |LPCM::kChannelSamplesPerUnit| two-channel LPCM audio samples) into
+ // packets:
+ // 1) Encodes meta information into meta information packets (by calling
+ // |EncodeMetaInformation|) if needed.
+ // 2) Normalizes unit time stamps (|pts| and |dts|) so that they are never
+ // smaller than a program clock reference.
+ // 3) Encodes the elementary stream unit buffer to unit data packets.
+ // Returns false in the case of an error in which case the caller should stop
+ // encoding.
+ //
+ // In order to minimize encoding delays, |flush| should be true unless
+ // the caller is about to continue encoding immediately.
+ //
+ // Precondition: Elementary streams are configured either using a constructor
+ // or using the |SetElementaryStreams| member function.
+ bool EncodeElementaryStreamUnit(unsigned stream_index,
+ const uint8_t* unit_data,
+ size_t unit_size,
+ bool random_access,
+ base::TimeTicks pts,
+ base::TimeTicks dts,
+ bool flush);
+
+ // Encodes meta information (program association table, program map table and
+ // program clock reference). Returns false in the case of an error in which
+ // case the caller should stop encoding.
+ //
+ // The |EncodeElementaryStreamUnit| member function calls this member function
+ // when needed, thus the caller is responsible for calling this member
+ // function explicitly only if the caller does silence suppression and does
+ // thus not encode all elementary stream units by calling
+ // the |EncodeElementaryStreamUnit| member function.
+ //
+ // In order to minimize encoding delays, |flush| should be true unless
+ // the caller is about to continue encoding immediately.
+ //
+ // Precondition: Elementary streams are configured either using a constructor
+ // or using the |SetElementaryStreams| member function.
+ bool EncodeMetaInformation(bool flush);
+
+ bool SetElementaryStreams(
+ std::vector<WiFiDisplayElementaryStreamInfo> stream_infos);
+
+ void DetachFromThread() { base::NonThreadSafe::DetachFromThread(); }
+
+ protected:
+ bool EncodeProgramAssociationTable(bool flush);
+ bool EncodeProgramClockReference(bool flush);
+ bool EncodeProgramMapTables(bool flush);
+
+ // Normalizes unit time stamps by delaying them in order to ensure that unit
+ // time stamps are never smaller than a program clock reference.
+ // Precondition: The |UpdateDelayForUnitTimeStamps| member function is called.
+ void NormalizeUnitTimeStamps(base::TimeTicks* pts,
+ base::TimeTicks* dts) const;
+ // Update unit time stamp delay in order to ensure that normalized unit time
+ // stamps are never smaller than a program clock reference.
+ void UpdateDelayForUnitTimeStamps(const base::TimeTicks& pts,
+ const base::TimeTicks& dts);
+
+ // Called whenever a Transport Stream (TS) packet is fully created and thus
+ // ready for further processing.
+ virtual bool OnPacketizedTransportStreamPacket(
+ const WiFiDisplayTransportStreamPacket& transport_stream_packet,
+ bool flush) = 0;
+
+ private:
+ struct ElementaryStreamState;
+
+ struct {
+ uint8_t program_association_table_continuity;
+ uint8_t program_map_table_continuity;
+ uint8_t program_map_table_version;
+ uint8_t program_clock_reference_continuity;
+ } counters_;
+ base::TimeDelta delay_for_unit_time_stamps_;
+ base::TimeTicks program_clock_reference_;
+ std::vector<ElementaryStreamState> stream_states_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_TRANSPORT_STREAM_PACKETIZER_H_
diff --git a/chromium/extensions/renderer/api/mojo_private/mojo_private_unittest.cc b/chromium/extensions/renderer/api/mojo_private/mojo_private_unittest.cc
new file mode 100644
index 00000000000..344cbe9da6c
--- /dev/null
+++ b/chromium/extensions/renderer/api/mojo_private/mojo_private_unittest.cc
@@ -0,0 +1,59 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/api_test_base.h"
+
+#include "base/macros.h"
+#include "gin/modules/module_registry.h"
+
+// A test launcher for tests for the mojoPrivate API defined in
+// extensions/test/data/mojo_private_unittest.js.
+
+namespace extensions {
+
+class MojoPrivateApiTest : public ApiTestBase {
+ public:
+ MojoPrivateApiTest() = default;
+
+ gin::ModuleRegistry* module_registry() {
+ return gin::ModuleRegistry::From(env()->context()->v8_context());
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MojoPrivateApiTest);
+};
+
+TEST_F(MojoPrivateApiTest, Define) {
+ ASSERT_NO_FATAL_FAILURE(RunTest("mojo_private_unittest.js", "testDefine"));
+ EXPECT_EQ(1u, module_registry()->available_modules().count("testModule"));
+}
+
+TEST_F(MojoPrivateApiTest, DefineRegistersModule) {
+ ASSERT_NO_FATAL_FAILURE(
+ RunTest("mojo_private_unittest.js", "testDefineRegistersModule"));
+ EXPECT_EQ(1u, module_registry()->available_modules().count("testModule"));
+ EXPECT_EQ(1u, module_registry()->available_modules().count("dependency"));
+}
+
+TEST_F(MojoPrivateApiTest, DefineModuleDoesNotExist) {
+ ASSERT_NO_FATAL_FAILURE(
+ RunTest("mojo_private_unittest.js", "testDefineModuleDoesNotExist"));
+ EXPECT_EQ(0u, module_registry()->available_modules().count("testModule"));
+ EXPECT_EQ(0u,
+ module_registry()->available_modules().count("does not exist!"));
+ EXPECT_EQ(1u, module_registry()->unsatisfied_dependencies().count(
+ "does not exist!"));
+}
+
+TEST_F(MojoPrivateApiTest, RequireAsync) {
+ ASSERT_NO_FATAL_FAILURE(
+ RunTest("mojo_private_unittest.js", "testRequireAsync"));
+}
+
+TEST_F(MojoPrivateApiTest, DefineAndRequire) {
+ ASSERT_NO_FATAL_FAILURE(
+ RunTest("mojo_private_unittest.js", "testDefineAndRequire"));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/serial/DEPS b/chromium/extensions/renderer/api/serial/DEPS
new file mode 100644
index 00000000000..e273c393ba2
--- /dev/null
+++ b/chromium/extensions/renderer/api/serial/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+device/serial",
+]
diff --git a/chromium/extensions/renderer/api/serial/data_receiver_unittest.cc b/chromium/extensions/renderer/api/serial/data_receiver_unittest.cc
new file mode 100644
index 00000000000..0c7dc000d8a
--- /dev/null
+++ b/chromium/extensions/renderer/api/serial/data_receiver_unittest.cc
@@ -0,0 +1,202 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+
+#include <queue>
+#include <utility>
+
+#include "base/macros.h"
+#include "device/serial/data_source_sender.h"
+#include "device/serial/data_stream.mojom.h"
+#include "extensions/renderer/api_test_base.h"
+#include "gin/dictionary.h"
+#include "gin/wrappable.h"
+#include "grit/extensions_renderer_resources.h"
+#include "mojo/edk/js/handle.h"
+
+namespace extensions {
+
+class DataReceiverFactory : public gin::Wrappable<DataReceiverFactory> {
+ public:
+ using Callback = base::Callback<void(
+ mojo::InterfaceRequest<device::serial::DataSource>,
+ mojo::InterfacePtr<device::serial::DataSourceClient>)>;
+ static gin::Handle<DataReceiverFactory> Create(v8::Isolate* isolate,
+ const Callback& callback) {
+ return gin::CreateHandle(isolate,
+ new DataReceiverFactory(callback, isolate));
+ }
+
+ gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
+ v8::Isolate* isolate) override {
+ return Wrappable<DataReceiverFactory>::GetObjectTemplateBuilder(isolate)
+ .SetMethod("create", &DataReceiverFactory::CreateReceiver);
+ }
+
+ gin::Dictionary CreateReceiver() {
+ mojo::InterfacePtr<device::serial::DataSource> sink;
+ mojo::InterfacePtr<device::serial::DataSourceClient> client;
+ mojo::InterfaceRequest<device::serial::DataSourceClient> client_request =
+ mojo::GetProxy(&client);
+ callback_.Run(mojo::GetProxy(&sink), std::move(client));
+
+ gin::Dictionary result = gin::Dictionary::CreateEmpty(isolate_);
+ result.Set("source", sink.PassInterface().PassHandle().release());
+ result.Set("client", client_request.PassMessagePipe().release());
+ return result;
+ }
+
+ static gin::WrapperInfo kWrapperInfo;
+
+ private:
+ DataReceiverFactory(const Callback& callback, v8::Isolate* isolate)
+ : callback_(callback), isolate_(isolate) {}
+
+ base::Callback<void(mojo::InterfaceRequest<device::serial::DataSource>,
+ mojo::InterfacePtr<device::serial::DataSourceClient>)>
+ callback_;
+ v8::Isolate* isolate_;
+};
+
+gin::WrapperInfo DataReceiverFactory::kWrapperInfo = {gin::kEmbedderNativeGin};
+
+// Runs tests defined in extensions/test/data/data_receiver_unittest.js
+class DataReceiverTest : public ApiTestBase {
+ public:
+ DataReceiverTest() {}
+
+ void SetUp() override {
+ ApiTestBase::SetUp();
+ gin::ModuleRegistry::From(env()->context()->v8_context())
+ ->AddBuiltinModule(env()->isolate(),
+ "device/serial/data_receiver_test_factory",
+ DataReceiverFactory::Create(
+ env()->isolate(),
+ base::Bind(&DataReceiverTest::CreateDataSource,
+ base::Unretained(this))).ToV8());
+ }
+
+ void TearDown() override {
+ if (sender_.get()) {
+ sender_->ShutDown();
+ sender_ = NULL;
+ }
+ ApiTestBase::TearDown();
+ }
+
+ std::queue<int32_t> error_to_send_;
+ std::queue<std::string> data_to_send_;
+
+ private:
+ void CreateDataSource(
+ mojo::InterfaceRequest<device::serial::DataSource> request,
+ mojo::InterfacePtr<device::serial::DataSourceClient> client) {
+ sender_ = new device::DataSourceSender(
+ std::move(request), std::move(client),
+ base::Bind(&DataReceiverTest::ReadyToSend, base::Unretained(this)),
+ base::Bind(base::DoNothing));
+ }
+
+ void ReadyToSend(scoped_ptr<device::WritableBuffer> buffer) {
+ if (data_to_send_.empty() && error_to_send_.empty())
+ return;
+
+ std::string data;
+ int32_t error = 0;
+ if (!data_to_send_.empty()) {
+ data = data_to_send_.front();
+ data_to_send_.pop();
+ }
+ if (!error_to_send_.empty()) {
+ error = error_to_send_.front();
+ error_to_send_.pop();
+ }
+ DCHECK(buffer->GetSize() >= static_cast<uint32_t>(data.size()));
+ memcpy(buffer->GetData(), data.c_str(), data.size());
+ if (error)
+ buffer->DoneWithError(data.size(), error);
+ else
+ buffer->Done(data.size());
+ }
+
+ scoped_refptr<device::DataSourceSender> sender_;
+
+ DISALLOW_COPY_AND_ASSIGN(DataReceiverTest);
+};
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_Receive DISABLED_Receive
+#else
+#define MAYBE_Receive Receive
+#endif
+TEST_F(DataReceiverTest, MAYBE_Receive) {
+ data_to_send_.push("a");
+ RunTest("data_receiver_unittest.js", "testReceive");
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_ReceiveError DISABLED_ReceiveError
+#else
+#define MAYBE_ReceiveError ReceiveError
+#endif
+TEST_F(DataReceiverTest, MAYBE_ReceiveError) {
+ error_to_send_.push(1);
+ RunTest("data_receiver_unittest.js", "testReceiveError");
+}
+
+TEST_F(DataReceiverTest, ReceiveDataAndError) {
+ data_to_send_.push("a");
+ data_to_send_.push("b");
+ error_to_send_.push(1);
+ RunTest("data_receiver_unittest.js", "testReceiveDataAndError");
+}
+
+TEST_F(DataReceiverTest, ReceiveErrorThenData) {
+ data_to_send_.push("");
+ data_to_send_.push("a");
+ error_to_send_.push(1);
+ RunTest("data_receiver_unittest.js", "testReceiveErrorThenData");
+}
+
+TEST_F(DataReceiverTest, ReceiveBeforeAndAfterSerialization) {
+ data_to_send_.push("a");
+ data_to_send_.push("b");
+ RunTest("data_receiver_unittest.js",
+ "testReceiveBeforeAndAfterSerialization");
+}
+
+TEST_F(DataReceiverTest, ReceiveErrorSerialization) {
+ error_to_send_.push(1);
+ error_to_send_.push(3);
+ RunTest("data_receiver_unittest.js", "testReceiveErrorSerialization");
+}
+
+TEST_F(DataReceiverTest, ReceiveDataAndErrorSerialization) {
+ data_to_send_.push("a");
+ data_to_send_.push("b");
+ error_to_send_.push(1);
+ error_to_send_.push(3);
+ RunTest("data_receiver_unittest.js", "testReceiveDataAndErrorSerialization");
+}
+
+TEST_F(DataReceiverTest, SerializeDuringReceive) {
+ data_to_send_.push("a");
+ RunTest("data_receiver_unittest.js", "testSerializeDuringReceive");
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_SerializeAfterClose DISABLED_SerializeAfterClose
+#else
+#define MAYBE_SerializeAfterClose SerializeAfterClose
+#endif
+TEST_F(DataReceiverTest, MAYBE_SerializeAfterClose) {
+ data_to_send_.push("a");
+ RunTest("data_receiver_unittest.js", "testSerializeAfterClose");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/serial/data_sender_unittest.cc b/chromium/extensions/renderer/api/serial/data_sender_unittest.cc
new file mode 100644
index 00000000000..a38dd10f201
--- /dev/null
+++ b/chromium/extensions/renderer/api/serial/data_sender_unittest.cc
@@ -0,0 +1,213 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+
+#include <queue>
+#include <utility>
+
+#include "base/macros.h"
+#include "device/serial/data_sink_receiver.h"
+#include "device/serial/data_stream.mojom.h"
+#include "extensions/renderer/api_test_base.h"
+#include "grit/extensions_renderer_resources.h"
+
+namespace extensions {
+
+// Runs tests defined in extensions/test/data/data_sender_unittest.js
+class DataSenderTest : public ApiTestBase {
+ public:
+ DataSenderTest() {}
+
+ void SetUp() override {
+ ApiTestBase::SetUp();
+ service_provider()->AddService(
+ base::Bind(&DataSenderTest::CreateDataSink, base::Unretained(this)));
+ }
+
+ void TearDown() override {
+ if (receiver_.get()) {
+ receiver_->ShutDown();
+ receiver_ = NULL;
+ }
+ EXPECT_FALSE(buffer_);
+ buffer_.reset();
+ ApiTestBase::TearDown();
+ }
+
+ std::queue<int32_t> error_to_report_;
+ std::queue<std::string> expected_data_;
+
+ private:
+ void CreateDataSink(
+ mojo::InterfaceRequest<device::serial::DataSink> request) {
+ receiver_ = new device::DataSinkReceiver(
+ std::move(request),
+ base::Bind(&DataSenderTest::ReadyToReceive, base::Unretained(this)),
+ base::Bind(&DataSenderTest::OnCancel, base::Unretained(this)),
+ base::Bind(base::DoNothing));
+ }
+
+ void ReadyToReceive(scoped_ptr<device::ReadOnlyBuffer> buffer) {
+ std::string data(buffer->GetData(), buffer->GetSize());
+ if (expected_data_.empty()) {
+ buffer_ = std::move(buffer);
+ return;
+ }
+
+ std::string& expected = expected_data_.front();
+ if (expected.size() > buffer->GetSize()) {
+ EXPECT_EQ(expected.substr(0, buffer->GetSize()), data);
+ expected = expected.substr(buffer->GetSize());
+ buffer->Done(buffer->GetSize());
+ return;
+ }
+ if (expected.size() < buffer->GetSize())
+ data = data.substr(0, expected.size());
+ EXPECT_EQ(expected, data);
+ expected_data_.pop();
+ int32_t error = 0;
+ if (!error_to_report_.empty()) {
+ error = error_to_report_.front();
+ error_to_report_.pop();
+ }
+ if (error)
+ buffer->DoneWithError(data.size(), error);
+ else
+ buffer->Done(data.size());
+ }
+
+ void OnCancel(int32_t error) {
+ ASSERT_TRUE(buffer_);
+ buffer_->DoneWithError(0, error);
+ buffer_.reset();
+ }
+
+ scoped_refptr<device::DataSinkReceiver> receiver_;
+ scoped_ptr<device::ReadOnlyBuffer> buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(DataSenderTest);
+};
+
+TEST_F(DataSenderTest, Send) {
+ expected_data_.push("aa");
+ RunTest("data_sender_unittest.js", "testSend");
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_LargeSend DISABLED_LargeSend
+#else
+#define MAYBE_LargeSend LargeSend
+#endif
+TEST_F(DataSenderTest, MAYBE_LargeSend) {
+ std::string pattern = "123";
+ std::string expected_data;
+ for (int i = 0; i < 11; i++)
+ expected_data += pattern;
+ expected_data_.push(expected_data);
+ RunTest("data_sender_unittest.js", "testLargeSend");
+}
+
+TEST_F(DataSenderTest, SendError) {
+ expected_data_.push("");
+ expected_data_.push("a");
+ error_to_report_.push(1);
+ RunTest("data_sender_unittest.js", "testSendError");
+}
+
+TEST_F(DataSenderTest, SendErrorPartialSuccess) {
+ expected_data_.push(std::string(5, 'b'));
+ expected_data_.push("a");
+ error_to_report_.push(1);
+ RunTest("data_sender_unittest.js", "testSendErrorPartialSuccess");
+}
+
+TEST_F(DataSenderTest, SendErrorBetweenPackets) {
+ expected_data_.push(std::string(2, 'b'));
+ expected_data_.push("a");
+ error_to_report_.push(1);
+ RunTest("data_sender_unittest.js", "testSendErrorBetweenPackets");
+}
+
+TEST_F(DataSenderTest, SendErrorInSecondPacket) {
+ expected_data_.push(std::string(3, 'b'));
+ expected_data_.push("a");
+ error_to_report_.push(1);
+ RunTest("data_sender_unittest.js", "testSendErrorInSecondPacket");
+}
+
+TEST_F(DataSenderTest, SendErrorInLargeSend) {
+ expected_data_.push("123456789012");
+ expected_data_.push("a");
+ error_to_report_.push(1);
+ RunTest("data_sender_unittest.js", "testSendErrorInLargeSend");
+}
+
+TEST_F(DataSenderTest, SendErrorBeforeLargeSend) {
+ expected_data_.push(std::string(2, 'b'));
+ expected_data_.push("a");
+ error_to_report_.push(1);
+ RunTest("data_sender_unittest.js", "testSendErrorBeforeLargeSend");
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_CancelWithoutSend DISABLED_CancelWithoutSend
+#else
+#define MAYBE_CancelWithoutSend CancelWithoutSend
+#endif
+TEST_F(DataSenderTest, MAYBE_CancelWithoutSend) {
+ RunTest("data_sender_unittest.js", "testCancelWithoutSend");
+}
+
+TEST_F(DataSenderTest, Cancel) {
+ RunTest("data_sender_unittest.js", "testCancel");
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_Close DISABLED_Close
+#else
+#define MAYBE_Close Close
+#endif
+TEST_F(DataSenderTest, MAYBE_Close) {
+ RunTest("data_sender_unittest.js", "testClose");
+}
+
+TEST_F(DataSenderTest, SendAfterSerialization) {
+ expected_data_.push("aa");
+ RunTest("data_sender_unittest.js", "testSendAfterSerialization");
+}
+
+TEST_F(DataSenderTest, SendErrorAfterSerialization) {
+ expected_data_.push("");
+ expected_data_.push("a");
+ error_to_report_.push(1);
+ RunTest("data_sender_unittest.js", "testSendErrorAfterSerialization");
+}
+
+TEST_F(DataSenderTest, CancelAfterSerialization) {
+ RunTest("data_sender_unittest.js", "testCancelAfterSerialization");
+}
+
+TEST_F(DataSenderTest, SerializeCancelsSendsInProgress) {
+ RunTest("data_sender_unittest.js", "testSerializeCancelsSendsInProgress");
+}
+
+TEST_F(DataSenderTest, SerializeWaitsForCancel) {
+ RunTest("data_sender_unittest.js", "testSerializeWaitsForCancel");
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_SerializeAfterClose DISABLED_SerializeAfterClose
+#else
+#define MAYBE_SerializeAfterClose SerializeAfterClose
+#endif
+TEST_F(DataSenderTest, MAYBE_SerializeAfterClose) {
+ RunTest("data_sender_unittest.js", "testSerializeAfterClose");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api/serial/serial_api_unittest.cc b/chromium/extensions/renderer/api/serial/serial_api_unittest.cc
new file mode 100644
index 00000000000..82f8f2a36f1
--- /dev/null
+++ b/chromium/extensions/renderer/api/serial/serial_api_unittest.cc
@@ -0,0 +1,738 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/macros.h"
+#include "base/thread_task_runner_handle.h"
+#include "device/serial/serial_device_enumerator.h"
+#include "device/serial/serial_service_impl.h"
+#include "device/serial/test_serial_io_handler.h"
+#include "extensions/browser/mojo/stash_backend.h"
+#include "extensions/common/mojo/keep_alive.mojom.h"
+#include "extensions/renderer/api_test_base.h"
+#include "grit/extensions_renderer_resources.h"
+
+// A test launcher for tests for the serial API defined in
+// extensions/test/data/serial_unittest.js. Each C++ test function sets up a
+// fake DeviceEnumerator or SerialIoHandler expecting or returning particular
+// values for that test.
+
+namespace extensions {
+
+namespace {
+
+class FakeSerialDeviceEnumerator : public device::SerialDeviceEnumerator {
+ mojo::Array<device::serial::DeviceInfoPtr> GetDevices() override {
+ mojo::Array<device::serial::DeviceInfoPtr> result(3);
+ result[0] = device::serial::DeviceInfo::New();
+ result[0]->path = "device";
+ result[0]->vendor_id = 1234;
+ result[0]->has_vendor_id = true;
+ result[0]->product_id = 5678;
+ result[0]->has_product_id = true;
+ result[0]->display_name = "foo";
+ result[1] = device::serial::DeviceInfo::New();
+ result[1]->path = "another_device";
+ // These IDs should be ignored.
+ result[1]->vendor_id = 1234;
+ result[1]->product_id = 5678;
+ result[2] = device::serial::DeviceInfo::New();
+ result[2]->path = "";
+ result[2]->display_name = "";
+ return result;
+ }
+};
+
+enum OptionalValue {
+ OPTIONAL_VALUE_UNSET,
+ OPTIONAL_VALUE_FALSE,
+ OPTIONAL_VALUE_TRUE,
+};
+
+device::serial::HostControlSignals GenerateControlSignals(OptionalValue dtr,
+ OptionalValue rts) {
+ device::serial::HostControlSignals result;
+ switch (dtr) {
+ case OPTIONAL_VALUE_UNSET:
+ break;
+ case OPTIONAL_VALUE_FALSE:
+ result.dtr = false;
+ result.has_dtr = true;
+ break;
+ case OPTIONAL_VALUE_TRUE:
+ result.dtr = true;
+ result.has_dtr = true;
+ break;
+ }
+ switch (rts) {
+ case OPTIONAL_VALUE_UNSET:
+ break;
+ case OPTIONAL_VALUE_FALSE:
+ result.rts = false;
+ result.has_rts = true;
+ break;
+ case OPTIONAL_VALUE_TRUE:
+ result.rts = true;
+ result.has_rts = true;
+ break;
+ }
+ return result;
+}
+
+device::serial::ConnectionOptions GenerateConnectionOptions(
+ int bitrate,
+ device::serial::DataBits data_bits,
+ device::serial::ParityBit parity_bit,
+ device::serial::StopBits stop_bits,
+ OptionalValue cts_flow_control) {
+ device::serial::ConnectionOptions result;
+ result.bitrate = bitrate;
+ result.data_bits = data_bits;
+ result.parity_bit = parity_bit;
+ result.stop_bits = stop_bits;
+ switch (cts_flow_control) {
+ case OPTIONAL_VALUE_UNSET:
+ break;
+ case OPTIONAL_VALUE_FALSE:
+ result.cts_flow_control = false;
+ result.has_cts_flow_control = true;
+ break;
+ case OPTIONAL_VALUE_TRUE:
+ result.cts_flow_control = true;
+ result.has_cts_flow_control = true;
+ break;
+ }
+ return result;
+}
+
+class TestIoHandlerBase : public device::TestSerialIoHandler {
+ public:
+ TestIoHandlerBase() : calls_(0) {}
+
+ size_t num_calls() const { return calls_; }
+
+ protected:
+ ~TestIoHandlerBase() override {}
+ void record_call() const { calls_++; }
+
+ private:
+ mutable size_t calls_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestIoHandlerBase);
+};
+
+class SetControlSignalsTestIoHandler : public TestIoHandlerBase {
+ public:
+ SetControlSignalsTestIoHandler() {}
+
+ bool SetControlSignals(
+ const device::serial::HostControlSignals& signals) override {
+ static const device::serial::HostControlSignals expected_signals[] = {
+ GenerateControlSignals(OPTIONAL_VALUE_UNSET, OPTIONAL_VALUE_UNSET),
+ GenerateControlSignals(OPTIONAL_VALUE_FALSE, OPTIONAL_VALUE_UNSET),
+ GenerateControlSignals(OPTIONAL_VALUE_TRUE, OPTIONAL_VALUE_UNSET),
+ GenerateControlSignals(OPTIONAL_VALUE_UNSET, OPTIONAL_VALUE_FALSE),
+ GenerateControlSignals(OPTIONAL_VALUE_FALSE, OPTIONAL_VALUE_FALSE),
+ GenerateControlSignals(OPTIONAL_VALUE_TRUE, OPTIONAL_VALUE_FALSE),
+ GenerateControlSignals(OPTIONAL_VALUE_UNSET, OPTIONAL_VALUE_TRUE),
+ GenerateControlSignals(OPTIONAL_VALUE_FALSE, OPTIONAL_VALUE_TRUE),
+ GenerateControlSignals(OPTIONAL_VALUE_TRUE, OPTIONAL_VALUE_TRUE),
+ };
+ if (num_calls() >= arraysize(expected_signals))
+ return false;
+
+ EXPECT_EQ(expected_signals[num_calls()].has_dtr, signals.has_dtr);
+ EXPECT_EQ(expected_signals[num_calls()].dtr, signals.dtr);
+ EXPECT_EQ(expected_signals[num_calls()].has_rts, signals.has_rts);
+ EXPECT_EQ(expected_signals[num_calls()].rts, signals.rts);
+ record_call();
+ return true;
+ }
+
+ private:
+ ~SetControlSignalsTestIoHandler() override {}
+
+ DISALLOW_COPY_AND_ASSIGN(SetControlSignalsTestIoHandler);
+};
+
+class GetControlSignalsTestIoHandler : public TestIoHandlerBase {
+ public:
+ GetControlSignalsTestIoHandler() {}
+
+ device::serial::DeviceControlSignalsPtr GetControlSignals() const override {
+ device::serial::DeviceControlSignalsPtr signals(
+ device::serial::DeviceControlSignals::New());
+ signals->dcd = num_calls() & 1;
+ signals->cts = num_calls() & 2;
+ signals->ri = num_calls() & 4;
+ signals->dsr = num_calls() & 8;
+ record_call();
+ return signals;
+ }
+
+ private:
+ ~GetControlSignalsTestIoHandler() override {}
+
+ DISALLOW_COPY_AND_ASSIGN(GetControlSignalsTestIoHandler);
+};
+
+class ConfigurePortTestIoHandler : public TestIoHandlerBase {
+ public:
+ ConfigurePortTestIoHandler() {}
+ bool ConfigurePortImpl() override {
+ static const device::serial::ConnectionOptions expected_options[] = {
+ // Each JavaScript call to chrome.serial.update only modifies a single
+ // property of the connection however this function can only check the
+ // final value of all options. The modified option is marked with "set".
+ GenerateConnectionOptions(9600, device::serial::DataBits::EIGHT,
+ device::serial::ParityBit::NO,
+ device::serial::StopBits::ONE,
+ OPTIONAL_VALUE_FALSE),
+ GenerateConnectionOptions(
+ 57600, // set
+ device::serial::DataBits::EIGHT, device::serial::ParityBit::NO,
+ device::serial::StopBits::ONE, OPTIONAL_VALUE_FALSE),
+ GenerateConnectionOptions(57600,
+ device::serial::DataBits::SEVEN, // set
+ device::serial::ParityBit::NO,
+ device::serial::StopBits::ONE,
+ OPTIONAL_VALUE_FALSE),
+ GenerateConnectionOptions(57600,
+ device::serial::DataBits::EIGHT, // set
+ device::serial::ParityBit::NO,
+ device::serial::StopBits::ONE,
+ OPTIONAL_VALUE_FALSE),
+ GenerateConnectionOptions(57600, device::serial::DataBits::EIGHT,
+ device::serial::ParityBit::NO, // set
+ device::serial::StopBits::ONE,
+ OPTIONAL_VALUE_FALSE),
+ GenerateConnectionOptions(57600, device::serial::DataBits::EIGHT,
+ device::serial::ParityBit::ODD, // set
+ device::serial::StopBits::ONE,
+ OPTIONAL_VALUE_FALSE),
+ GenerateConnectionOptions(57600, device::serial::DataBits::EIGHT,
+ device::serial::ParityBit::EVEN, // set
+ device::serial::StopBits::ONE,
+ OPTIONAL_VALUE_FALSE),
+ GenerateConnectionOptions(57600, device::serial::DataBits::EIGHT,
+ device::serial::ParityBit::EVEN,
+ device::serial::StopBits::ONE, // set
+ OPTIONAL_VALUE_FALSE),
+ GenerateConnectionOptions(57600, device::serial::DataBits::EIGHT,
+ device::serial::ParityBit::EVEN,
+ device::serial::StopBits::TWO, // set
+ OPTIONAL_VALUE_FALSE),
+ GenerateConnectionOptions(57600, device::serial::DataBits::EIGHT,
+ device::serial::ParityBit::EVEN,
+ device::serial::StopBits::TWO,
+ OPTIONAL_VALUE_FALSE), // set
+ GenerateConnectionOptions(57600, device::serial::DataBits::EIGHT,
+ device::serial::ParityBit::EVEN,
+ device::serial::StopBits::TWO,
+ OPTIONAL_VALUE_TRUE), // set
+ };
+
+ if (!TestIoHandlerBase::ConfigurePortImpl()) {
+ return false;
+ }
+
+ if (num_calls() >= arraysize(expected_options)) {
+ return false;
+ }
+
+ EXPECT_EQ(expected_options[num_calls()].bitrate, options().bitrate);
+ EXPECT_EQ(expected_options[num_calls()].data_bits, options().data_bits);
+ EXPECT_EQ(expected_options[num_calls()].parity_bit, options().parity_bit);
+ EXPECT_EQ(expected_options[num_calls()].stop_bits, options().stop_bits);
+ EXPECT_EQ(expected_options[num_calls()].has_cts_flow_control,
+ options().has_cts_flow_control);
+ EXPECT_EQ(expected_options[num_calls()].cts_flow_control,
+ options().cts_flow_control);
+ record_call();
+ return true;
+ }
+
+ private:
+ ~ConfigurePortTestIoHandler() override {}
+
+ DISALLOW_COPY_AND_ASSIGN(ConfigurePortTestIoHandler);
+};
+
+class FlushTestIoHandler : public TestIoHandlerBase {
+ public:
+ FlushTestIoHandler() {}
+
+ bool Flush() const override {
+ record_call();
+ return true;
+ }
+
+ private:
+ ~FlushTestIoHandler() override {}
+
+ DISALLOW_COPY_AND_ASSIGN(FlushTestIoHandler);
+};
+
+class FailToConnectTestIoHandler : public TestIoHandlerBase {
+ public:
+ FailToConnectTestIoHandler() {}
+ void Open(const std::string& port,
+ const device::serial::ConnectionOptions& options,
+ const OpenCompleteCallback& callback) override {
+ callback.Run(false);
+ return;
+ }
+
+ private:
+ ~FailToConnectTestIoHandler() override {}
+
+ DISALLOW_COPY_AND_ASSIGN(FailToConnectTestIoHandler);
+};
+
+class FailToGetInfoTestIoHandler : public TestIoHandlerBase {
+ public:
+ explicit FailToGetInfoTestIoHandler(int times_to_succeed)
+ : times_to_succeed_(times_to_succeed) {}
+ device::serial::ConnectionInfoPtr GetPortInfo() const override {
+ if (times_to_succeed_-- > 0)
+ return device::TestSerialIoHandler::GetPortInfo();
+ return device::serial::ConnectionInfoPtr();
+ }
+
+ private:
+ ~FailToGetInfoTestIoHandler() override {}
+
+ mutable int times_to_succeed_;
+
+ DISALLOW_COPY_AND_ASSIGN(FailToGetInfoTestIoHandler);
+};
+
+class SendErrorTestIoHandler : public TestIoHandlerBase {
+ public:
+ explicit SendErrorTestIoHandler(device::serial::SendError error)
+ : error_(error) {}
+
+ void WriteImpl() override { QueueWriteCompleted(0, error_); }
+
+ private:
+ ~SendErrorTestIoHandler() override {}
+
+ device::serial::SendError error_;
+
+ DISALLOW_COPY_AND_ASSIGN(SendErrorTestIoHandler);
+};
+
+class FixedDataReceiveTestIoHandler : public TestIoHandlerBase {
+ public:
+ explicit FixedDataReceiveTestIoHandler(const std::string& data)
+ : data_(data) {}
+
+ void ReadImpl() override {
+ if (pending_read_buffer_len() < data_.size())
+ return;
+ memcpy(pending_read_buffer(), data_.c_str(), data_.size());
+ QueueReadCompleted(static_cast<uint32_t>(data_.size()),
+ device::serial::ReceiveError::NONE);
+ }
+
+ private:
+ ~FixedDataReceiveTestIoHandler() override {}
+
+ const std::string data_;
+
+ DISALLOW_COPY_AND_ASSIGN(FixedDataReceiveTestIoHandler);
+};
+
+class ReceiveErrorTestIoHandler : public TestIoHandlerBase {
+ public:
+ explicit ReceiveErrorTestIoHandler(device::serial::ReceiveError error)
+ : error_(error) {}
+
+ void ReadImpl() override { QueueReadCompleted(0, error_); }
+
+ private:
+ ~ReceiveErrorTestIoHandler() override {}
+
+ device::serial::ReceiveError error_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReceiveErrorTestIoHandler);
+};
+
+class SendDataWithErrorIoHandler : public TestIoHandlerBase {
+ public:
+ SendDataWithErrorIoHandler() : sent_error_(false) {}
+ void WriteImpl() override {
+ if (sent_error_) {
+ WriteCompleted(pending_write_buffer_len(),
+ device::serial::SendError::NONE);
+ return;
+ }
+ sent_error_ = true;
+ // We expect the JS test code to send a 4 byte buffer.
+ ASSERT_LT(2u, pending_write_buffer_len());
+ WriteCompleted(2, device::serial::SendError::SYSTEM_ERROR);
+ }
+
+ private:
+ ~SendDataWithErrorIoHandler() override {}
+
+ bool sent_error_;
+
+ DISALLOW_COPY_AND_ASSIGN(SendDataWithErrorIoHandler);
+};
+
+class BlockSendsForeverSendIoHandler : public TestIoHandlerBase {
+ public:
+ BlockSendsForeverSendIoHandler() {}
+ void WriteImpl() override {}
+
+ private:
+ ~BlockSendsForeverSendIoHandler() override {}
+
+ DISALLOW_COPY_AND_ASSIGN(BlockSendsForeverSendIoHandler);
+};
+
+} // namespace
+
+class SerialApiTest : public ApiTestBase {
+ public:
+ SerialApiTest() {}
+
+ void SetUp() override {
+ ApiTestBase::SetUp();
+ stash_backend_.reset(new StashBackend(base::Closure()));
+ PrepareEnvironment(api_test_env(), stash_backend_.get());
+ }
+
+ void PrepareEnvironment(ApiTestEnvironment* environment,
+ StashBackend* stash_backend) {
+ environment->env()->RegisterModule("serial", IDR_SERIAL_CUSTOM_BINDINGS_JS);
+ environment->service_provider()->AddService<device::serial::SerialService>(
+ base::Bind(&SerialApiTest::CreateSerialService,
+ base::Unretained(this)));
+ environment->service_provider()->AddService(base::Bind(
+ &StashBackend::BindToRequest, base::Unretained(stash_backend)));
+ environment->service_provider()->IgnoreServiceRequests<KeepAlive>();
+ }
+
+ scoped_refptr<TestIoHandlerBase> io_handler_;
+
+ scoped_ptr<StashBackend> stash_backend_;
+
+ private:
+ scoped_refptr<device::SerialIoHandler> GetIoHandler() {
+ if (!io_handler_.get())
+ io_handler_ = new TestIoHandlerBase;
+ return io_handler_;
+ }
+
+ void CreateSerialService(
+ mojo::InterfaceRequest<device::serial::SerialService> request) {
+ new device::SerialServiceImpl(
+ new device::SerialConnectionFactory(
+ base::Bind(&SerialApiTest::GetIoHandler, base::Unretained(this)),
+ base::ThreadTaskRunnerHandle::Get()),
+ scoped_ptr<device::SerialDeviceEnumerator>(
+ new FakeSerialDeviceEnumerator),
+ std::move(request));
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(SerialApiTest);
+};
+
+TEST_F(SerialApiTest, GetDevices) {
+ RunTest("serial_unittest.js", "testGetDevices");
+}
+
+TEST_F(SerialApiTest, ConnectFail) {
+ io_handler_ = new FailToConnectTestIoHandler;
+ RunTest("serial_unittest.js", "testConnectFail");
+}
+
+TEST_F(SerialApiTest, GetInfoFailOnConnect) {
+ io_handler_ = new FailToGetInfoTestIoHandler(0);
+ RunTest("serial_unittest.js", "testGetInfoFailOnConnect");
+}
+
+TEST_F(SerialApiTest, Connect) {
+ RunTest("serial_unittest.js", "testConnect");
+}
+
+TEST_F(SerialApiTest, ConnectDefaultOptions) {
+ RunTest("serial_unittest.js", "testConnectDefaultOptions");
+}
+
+TEST_F(SerialApiTest, ConnectInvalidBitrate) {
+ RunTest("serial_unittest.js", "testConnectInvalidBitrate");
+}
+
+TEST_F(SerialApiTest, GetInfo) {
+ RunTest("serial_unittest.js", "testGetInfo");
+}
+
+TEST_F(SerialApiTest, GetInfoAfterSerialization) {
+ RunTest("serial_unittest.js", "testGetInfoAfterSerialization");
+}
+
+TEST_F(SerialApiTest, GetInfoFailToGetPortInfo) {
+ io_handler_ = new FailToGetInfoTestIoHandler(1);
+ RunTest("serial_unittest.js", "testGetInfoFailToGetPortInfo");
+}
+
+TEST_F(SerialApiTest, GetConnections) {
+ RunTest("serial_unittest.js", "testGetConnections");
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_GetControlSignals DISABLED_GetControlSignals
+#else
+#define MAYBE_GetControlSignals GetControlSignals
+#endif
+TEST_F(SerialApiTest, MAYBE_GetControlSignals) {
+ io_handler_ = new GetControlSignalsTestIoHandler;
+ RunTest("serial_unittest.js", "testGetControlSignals");
+ EXPECT_EQ(16u, io_handler_->num_calls());
+}
+
+TEST_F(SerialApiTest, SetControlSignals) {
+ io_handler_ = new SetControlSignalsTestIoHandler;
+ RunTest("serial_unittest.js", "testSetControlSignals");
+ EXPECT_EQ(9u, io_handler_->num_calls());
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_Update DISABLED_Update
+#else
+#define MAYBE_Update Update
+#endif
+TEST_F(SerialApiTest, MAYBE_Update) {
+ io_handler_ = new ConfigurePortTestIoHandler;
+ RunTest("serial_unittest.js", "testUpdate");
+ EXPECT_EQ(11u, io_handler_->num_calls());
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_UpdateAcrossSerialization DISABLED_UpdateAcrossSerialization
+#else
+#define MAYBE_UpdateAcrossSerialization UpdateAcrossSerialization
+#endif
+TEST_F(SerialApiTest, MAYBE_UpdateAcrossSerialization) {
+ io_handler_ = new ConfigurePortTestIoHandler;
+ RunTest("serial_unittest.js", "testUpdateAcrossSerialization");
+ EXPECT_EQ(11u, io_handler_->num_calls());
+}
+
+TEST_F(SerialApiTest, UpdateInvalidBitrate) {
+ io_handler_ = new ConfigurePortTestIoHandler;
+ RunTest("serial_unittest.js", "testUpdateInvalidBitrate");
+ EXPECT_EQ(1u, io_handler_->num_calls());
+}
+
+TEST_F(SerialApiTest, Flush) {
+ io_handler_ = new FlushTestIoHandler;
+ RunTest("serial_unittest.js", "testFlush");
+ EXPECT_EQ(1u, io_handler_->num_calls());
+}
+
+TEST_F(SerialApiTest, SetPaused) {
+ RunTest("serial_unittest.js", "testSetPaused");
+}
+
+TEST_F(SerialApiTest, Echo) {
+ RunTest("serial_unittest.js", "testEcho");
+}
+
+TEST_F(SerialApiTest, EchoAfterSerialization) {
+ RunTest("serial_unittest.js", "testEchoAfterSerialization");
+}
+
+TEST_F(SerialApiTest, SendDuringExistingSend) {
+ RunTest("serial_unittest.js", "testSendDuringExistingSend");
+}
+
+TEST_F(SerialApiTest, SendAfterSuccessfulSend) {
+ RunTest("serial_unittest.js", "testSendAfterSuccessfulSend");
+}
+
+TEST_F(SerialApiTest, SendPartialSuccessWithError) {
+ io_handler_ = new SendDataWithErrorIoHandler();
+ RunTest("serial_unittest.js", "testSendPartialSuccessWithError");
+}
+
+TEST_F(SerialApiTest, SendTimeout) {
+ io_handler_ = new BlockSendsForeverSendIoHandler();
+ RunTest("serial_unittest.js", "testSendTimeout");
+}
+
+TEST_F(SerialApiTest, SendTimeoutAfterSerialization) {
+ io_handler_ = new BlockSendsForeverSendIoHandler();
+ RunTest("serial_unittest.js", "testSendTimeoutAfterSerialization");
+}
+
+TEST_F(SerialApiTest, DisableSendTimeout) {
+ io_handler_ = new BlockSendsForeverSendIoHandler();
+ RunTest("serial_unittest.js", "testDisableSendTimeout");
+}
+
+TEST_F(SerialApiTest, PausedReceive) {
+ io_handler_ = new FixedDataReceiveTestIoHandler("data");
+ RunTest("serial_unittest.js", "testPausedReceive");
+}
+
+TEST_F(SerialApiTest, PausedReceiveError) {
+ io_handler_ =
+ new ReceiveErrorTestIoHandler(device::serial::ReceiveError::DEVICE_LOST);
+ RunTest("serial_unittest.js", "testPausedReceiveError");
+}
+
+TEST_F(SerialApiTest, ReceiveTimeout) {
+ RunTest("serial_unittest.js", "testReceiveTimeout");
+}
+
+TEST_F(SerialApiTest, ReceiveTimeoutAfterSerialization) {
+ RunTest("serial_unittest.js", "testReceiveTimeoutAfterSerialization");
+}
+
+TEST_F(SerialApiTest, DisableReceiveTimeout) {
+ RunTest("serial_unittest.js", "testDisableReceiveTimeout");
+}
+
+TEST_F(SerialApiTest, ReceiveErrorDisconnected) {
+ io_handler_ =
+ new ReceiveErrorTestIoHandler(device::serial::ReceiveError::DISCONNECTED);
+ RunTest("serial_unittest.js", "testReceiveErrorDisconnected");
+}
+
+TEST_F(SerialApiTest, ReceiveErrorDeviceLost) {
+ io_handler_ =
+ new ReceiveErrorTestIoHandler(device::serial::ReceiveError::DEVICE_LOST);
+ RunTest("serial_unittest.js", "testReceiveErrorDeviceLost");
+}
+
+TEST_F(SerialApiTest, ReceiveErrorBreak) {
+ io_handler_ =
+ new ReceiveErrorTestIoHandler(device::serial::ReceiveError::BREAK);
+ RunTest("serial_unittest.js", "testReceiveErrorBreak");
+}
+
+TEST_F(SerialApiTest, ReceiveErrorFrameError) {
+ io_handler_ =
+ new ReceiveErrorTestIoHandler(device::serial::ReceiveError::FRAME_ERROR);
+ RunTest("serial_unittest.js", "testReceiveErrorFrameError");
+}
+
+TEST_F(SerialApiTest, ReceiveErrorOverrun) {
+ io_handler_ =
+ new ReceiveErrorTestIoHandler(device::serial::ReceiveError::OVERRUN);
+ RunTest("serial_unittest.js", "testReceiveErrorOverrun");
+}
+
+TEST_F(SerialApiTest, ReceiveErrorBufferOverflow) {
+ io_handler_ = new ReceiveErrorTestIoHandler(
+ device::serial::ReceiveError::BUFFER_OVERFLOW);
+ RunTest("serial_unittest.js", "testReceiveErrorBufferOverflow");
+}
+
+TEST_F(SerialApiTest, ReceiveErrorParityError) {
+ io_handler_ =
+ new ReceiveErrorTestIoHandler(device::serial::ReceiveError::PARITY_ERROR);
+ RunTest("serial_unittest.js", "testReceiveErrorParityError");
+}
+
+TEST_F(SerialApiTest, ReceiveErrorSystemError) {
+ io_handler_ =
+ new ReceiveErrorTestIoHandler(device::serial::ReceiveError::SYSTEM_ERROR);
+ RunTest("serial_unittest.js", "testReceiveErrorSystemError");
+}
+
+TEST_F(SerialApiTest, SendErrorDisconnected) {
+ io_handler_ =
+ new SendErrorTestIoHandler(device::serial::SendError::DISCONNECTED);
+ RunTest("serial_unittest.js", "testSendErrorDisconnected");
+}
+
+TEST_F(SerialApiTest, SendErrorSystemError) {
+ io_handler_ =
+ new SendErrorTestIoHandler(device::serial::SendError::SYSTEM_ERROR);
+ RunTest("serial_unittest.js", "testSendErrorSystemError");
+}
+
+TEST_F(SerialApiTest, DisconnectUnknownConnectionId) {
+ RunTest("serial_unittest.js", "testDisconnectUnknownConnectionId");
+}
+
+TEST_F(SerialApiTest, GetInfoUnknownConnectionId) {
+ RunTest("serial_unittest.js", "testGetInfoUnknownConnectionId");
+}
+
+TEST_F(SerialApiTest, UpdateUnknownConnectionId) {
+ RunTest("serial_unittest.js", "testUpdateUnknownConnectionId");
+}
+
+TEST_F(SerialApiTest, SetControlSignalsUnknownConnectionId) {
+ RunTest("serial_unittest.js", "testSetControlSignalsUnknownConnectionId");
+}
+
+TEST_F(SerialApiTest, GetControlSignalsUnknownConnectionId) {
+ RunTest("serial_unittest.js", "testGetControlSignalsUnknownConnectionId");
+}
+
+TEST_F(SerialApiTest, FlushUnknownConnectionId) {
+ RunTest("serial_unittest.js", "testFlushUnknownConnectionId");
+}
+
+TEST_F(SerialApiTest, SetPausedUnknownConnectionId) {
+ RunTest("serial_unittest.js", "testSetPausedUnknownConnectionId");
+}
+
+TEST_F(SerialApiTest, SendUnknownConnectionId) {
+ RunTest("serial_unittest.js", "testSendUnknownConnectionId");
+}
+
+// Note: these tests are disabled, since there is no good story for persisting
+// the stashed handles when an extension process is shut down. See
+// https://crbug.com/538774
+TEST_F(SerialApiTest, DISABLED_StashAndRestoreDuringEcho) {
+ ASSERT_NO_FATAL_FAILURE(RunTest("serial_unittest.js", "testSendAndStash"));
+ scoped_ptr<ModuleSystemTestEnvironment> new_env(CreateEnvironment());
+ ApiTestEnvironment new_api_test_env(new_env.get());
+ PrepareEnvironment(&new_api_test_env, stash_backend_.get());
+ new_api_test_env.RunTest("serial_unittest.js", "testRestoreAndReceive");
+}
+
+TEST_F(SerialApiTest, DISABLED_StashAndRestoreDuringEchoError) {
+ io_handler_ =
+ new ReceiveErrorTestIoHandler(device::serial::ReceiveError::DEVICE_LOST);
+ ASSERT_NO_FATAL_FAILURE(
+ RunTest("serial_unittest.js", "testRestoreAndReceiveErrorSetUp"));
+ scoped_ptr<ModuleSystemTestEnvironment> new_env(CreateEnvironment());
+ ApiTestEnvironment new_api_test_env(new_env.get());
+ PrepareEnvironment(&new_api_test_env, stash_backend_.get());
+ new_api_test_env.RunTest("serial_unittest.js", "testRestoreAndReceiveError");
+}
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_StashAndRestoreNoConnections DISABLED_StashAndRestoreNoConnections
+#else
+#define MAYBE_StashAndRestoreNoConnections StashAndRestoreNoConnections
+#endif
+TEST_F(SerialApiTest, MAYBE_StashAndRestoreNoConnections) {
+ ASSERT_NO_FATAL_FAILURE(
+ RunTest("serial_unittest.js", "testStashNoConnections"));
+ io_handler_ = nullptr;
+ scoped_ptr<ModuleSystemTestEnvironment> new_env(CreateEnvironment());
+ ApiTestEnvironment new_api_test_env(new_env.get());
+ PrepareEnvironment(&new_api_test_env, stash_backend_.get());
+ new_api_test_env.RunTest("serial_unittest.js", "testRestoreNoConnections");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api_activity_logger.cc b/chromium/extensions/renderer/api_activity_logger.cc
new file mode 100644
index 00000000000..e78c27452f1
--- /dev/null
+++ b/chromium/extensions/renderer/api_activity_logger.cc
@@ -0,0 +1,82 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/bind.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/renderer/activity_log_converter_strategy.h"
+#include "extensions/renderer/api_activity_logger.h"
+#include "extensions/renderer/script_context.h"
+
+using content::V8ValueConverter;
+
+namespace extensions {
+
+APIActivityLogger::APIActivityLogger(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("LogEvent", base::Bind(&APIActivityLogger::LogEvent));
+ RouteFunction("LogAPICall", base::Bind(&APIActivityLogger::LogAPICall));
+}
+
+// static
+void APIActivityLogger::LogAPICall(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ LogInternal(APICALL, args);
+}
+
+// static
+void APIActivityLogger::LogEvent(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ LogInternal(EVENT, args);
+}
+
+// static
+void APIActivityLogger::LogInternal(
+ const CallType call_type,
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_GT(args.Length(), 2);
+ CHECK(args[0]->IsString());
+ CHECK(args[1]->IsString());
+ CHECK(args[2]->IsArray());
+
+ std::string ext_id = *v8::String::Utf8Value(args[0]);
+ ExtensionHostMsg_APIActionOrEvent_Params params;
+ params.api_call = *v8::String::Utf8Value(args[1]);
+ if (args.Length() == 4) // Extras are optional.
+ params.extra = *v8::String::Utf8Value(args[3]);
+ else
+ params.extra = "";
+
+ // Get the array of api call arguments.
+ v8::Local<v8::Array> arg_array = v8::Local<v8::Array>::Cast(args[2]);
+ if (arg_array->Length() > 0) {
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+ ActivityLogConverterStrategy strategy;
+ converter->SetFunctionAllowed(true);
+ converter->SetStrategy(&strategy);
+ scoped_ptr<base::ListValue> arg_list(new base::ListValue());
+ for (size_t i = 0; i < arg_array->Length(); ++i) {
+ arg_list->Set(
+ i,
+ converter->FromV8Value(arg_array->Get(i),
+ args.GetIsolate()->GetCurrentContext()));
+ }
+ params.arguments.Swap(arg_list.get());
+ }
+
+ if (call_type == APICALL) {
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_AddAPIActionToActivityLog(ext_id, params));
+ } else if (call_type == EVENT) {
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_AddEventToActivityLog(ext_id, params));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api_activity_logger.h b/chromium/extensions/renderer/api_activity_logger.h
new file mode 100644
index 00000000000..63a4699a88d
--- /dev/null
+++ b/chromium/extensions/renderer/api_activity_logger.h
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_API_ACTIVITY_LOGGER_H_
+#define EXTENSIONS_RENDERER_API_ACTIVITY_LOGGER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+// Used to log extension API calls and events that are implemented with custom
+// bindings.The actions are sent via IPC to extensions::ActivityLog for
+// recording and display.
+class APIActivityLogger : public ObjectBackedNativeHandler {
+ public:
+ explicit APIActivityLogger(ScriptContext* context);
+
+ private:
+ // Used to distinguish API calls & events from each other in LogInternal.
+ enum CallType { APICALL, EVENT };
+
+ // This is ultimately invoked in bindings.js with JavaScript arguments.
+ // arg0 - extension ID as a string
+ // arg1 - API call name as a string
+ // arg2 - arguments to the API call
+ // arg3 - any extra logging info as a string (optional)
+ static void LogAPICall(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // This is ultimately invoked in bindings.js with JavaScript arguments.
+ // arg0 - extension ID as a string
+ // arg1 - Event name as a string
+ // arg2 - Event arguments
+ // arg3 - any extra logging info as a string (optional)
+ static void LogEvent(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // LogAPICall and LogEvent are really the same underneath except for
+ // how they are ultimately dispatched to the log.
+ static void LogInternal(const CallType call_type,
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ DISALLOW_COPY_AND_ASSIGN(APIActivityLogger);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_ACTIVITY_LOGGER_H_
diff --git a/chromium/extensions/renderer/api_definitions_natives.cc b/chromium/extensions/renderer/api_definitions_natives.cc
new file mode 100644
index 00000000000..96f1f2c6eb8
--- /dev/null
+++ b/chromium/extensions/renderer/api_definitions_natives.cc
@@ -0,0 +1,37 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/api_definitions_natives.h"
+
+#include "extensions/common/features/feature.h"
+#include "extensions/common/features/feature_provider.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+ApiDefinitionsNatives::ApiDefinitionsNatives(Dispatcher* dispatcher,
+ ScriptContext* context)
+ : ObjectBackedNativeHandler(context), dispatcher_(dispatcher) {
+ RouteFunction(
+ "GetExtensionAPIDefinitionsForTest",
+ base::Bind(&ApiDefinitionsNatives::GetExtensionAPIDefinitionsForTest,
+ base::Unretained(this)));
+}
+
+void ApiDefinitionsNatives::GetExtensionAPIDefinitionsForTest(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ std::vector<std::string> apis;
+ const FeatureProvider* feature_provider = FeatureProvider::GetAPIFeatures();
+ for (const auto& map_entry : feature_provider->GetAllFeatures()) {
+ if (!feature_provider->GetParent(map_entry.second.get()) &&
+ context()->GetAvailability(map_entry.first).is_available()) {
+ apis.push_back(map_entry.first);
+ }
+ }
+ args.GetReturnValue().Set(
+ dispatcher_->v8_schema_registry()->GetSchemas(apis));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api_definitions_natives.h b/chromium/extensions/renderer/api_definitions_natives.h
new file mode 100644
index 00000000000..dcc1b732feb
--- /dev/null
+++ b/chromium/extensions/renderer/api_definitions_natives.h
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_API_DEFINITIONS_NATIVES_H_
+#define EXTENSIONS_RENDERER_API_DEFINITIONS_NATIVES_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+class Dispatcher;
+class ScriptContext;
+
+// Native functions for JS to get access to the schemas for extension APIs.
+class ApiDefinitionsNatives : public ObjectBackedNativeHandler {
+ public:
+ ApiDefinitionsNatives(Dispatcher* dispatcher, ScriptContext* context);
+
+ private:
+ // Returns the list of all schemas that are available to the calling context.
+ void GetExtensionAPIDefinitionsForTest(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Not owned.
+ Dispatcher* dispatcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(ApiDefinitionsNatives);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_DEFINITIONS_NATIVES_H_
diff --git a/chromium/extensions/renderer/api_test_base.cc b/chromium/extensions/renderer/api_test_base.cc
new file mode 100644
index 00000000000..81ec07f18d6
--- /dev/null
+++ b/chromium/extensions/renderer/api_test_base.cc
@@ -0,0 +1,240 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/api_test_base.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/run_loop.h"
+#include "extensions/common/extension_urls.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/process_info_native_handler.h"
+#include "gin/converter.h"
+#include "gin/dictionary.h"
+#include "mojo/edk/js/core.h"
+#include "mojo/edk/js/handle.h"
+#include "mojo/edk/js/support.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace extensions {
+namespace {
+
+// Natives for the implementation of the unit test version of chrome.test. Calls
+// the provided |quit_closure| when either notifyPass or notifyFail is called.
+class TestNatives : public gin::Wrappable<TestNatives> {
+ public:
+ static gin::Handle<TestNatives> Create(v8::Isolate* isolate,
+ const base::Closure& quit_closure) {
+ return gin::CreateHandle(isolate, new TestNatives(quit_closure));
+ }
+
+ gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
+ v8::Isolate* isolate) override {
+ return Wrappable<TestNatives>::GetObjectTemplateBuilder(isolate)
+ .SetMethod("Log", &TestNatives::Log)
+ .SetMethod("NotifyPass", &TestNatives::NotifyPass)
+ .SetMethod("NotifyFail", &TestNatives::NotifyFail);
+ }
+
+ void Log(const std::string& value) { logs_ += value + "\n"; }
+ void NotifyPass() { FinishTesting(); }
+
+ void NotifyFail(const std::string& message) {
+ FinishTesting();
+ FAIL() << logs_ << message;
+ }
+
+ void FinishTesting() {
+ base::MessageLoop::current()->PostTask(FROM_HERE, quit_closure_);
+ }
+
+ static gin::WrapperInfo kWrapperInfo;
+
+ private:
+ explicit TestNatives(const base::Closure& quit_closure)
+ : quit_closure_(quit_closure) {}
+
+ const base::Closure quit_closure_;
+ std::string logs_;
+};
+
+gin::WrapperInfo TestNatives::kWrapperInfo = {gin::kEmbedderNativeGin};
+
+} // namespace
+
+gin::WrapperInfo TestServiceProvider::kWrapperInfo = {gin::kEmbedderNativeGin};
+
+gin::Handle<TestServiceProvider> TestServiceProvider::Create(
+ v8::Isolate* isolate) {
+ return gin::CreateHandle(isolate, new TestServiceProvider());
+}
+
+TestServiceProvider::~TestServiceProvider() {
+}
+
+gin::ObjectTemplateBuilder TestServiceProvider::GetObjectTemplateBuilder(
+ v8::Isolate* isolate) {
+ return Wrappable<TestServiceProvider>::GetObjectTemplateBuilder(isolate)
+ .SetMethod("connectToService", &TestServiceProvider::ConnectToService);
+}
+
+mojo::Handle TestServiceProvider::ConnectToService(
+ const std::string& service_name) {
+ EXPECT_EQ(1u, service_factories_.count(service_name))
+ << "Unregistered service " << service_name << " requested.";
+ mojo::MessagePipe pipe;
+ std::map<std::string,
+ base::Callback<void(mojo::ScopedMessagePipeHandle)> >::iterator it =
+ service_factories_.find(service_name);
+ if (it != service_factories_.end())
+ it->second.Run(std::move(pipe.handle0));
+ return pipe.handle1.release();
+}
+
+TestServiceProvider::TestServiceProvider() {
+}
+
+// static
+void TestServiceProvider::IgnoreHandle(mojo::ScopedMessagePipeHandle handle) {
+}
+
+ApiTestEnvironment::ApiTestEnvironment(
+ ModuleSystemTestEnvironment* environment) {
+ env_ = environment;
+ InitializeEnvironment();
+ RegisterModules();
+}
+
+ApiTestEnvironment::~ApiTestEnvironment() {
+}
+
+void ApiTestEnvironment::RegisterModules() {
+ v8_schema_registry_.reset(new V8SchemaRegistry);
+ const std::vector<std::pair<std::string, int> > resources =
+ Dispatcher::GetJsResources();
+ for (std::vector<std::pair<std::string, int> >::const_iterator resource =
+ resources.begin();
+ resource != resources.end();
+ ++resource) {
+ if (resource->first != "test_environment_specific_bindings")
+ env()->RegisterModule(resource->first, resource->second);
+ }
+ Dispatcher::RegisterNativeHandlers(env()->module_system(),
+ env()->context(),
+ NULL,
+ NULL,
+ v8_schema_registry_.get());
+ env()->module_system()->RegisterNativeHandler(
+ "process",
+ scoped_ptr<NativeHandler>(new ProcessInfoNativeHandler(
+ env()->context(),
+ env()->context()->GetExtensionID(),
+ env()->context()->GetContextTypeDescription(),
+ false,
+ false,
+ 2,
+ false)));
+ env()->RegisterTestFile("test_environment_specific_bindings",
+ "unit_test_environment_specific_bindings.js");
+
+ env()->OverrideNativeHandler("activityLogger",
+ "exports.$set('LogAPICall', function() {});");
+ env()->OverrideNativeHandler(
+ "apiDefinitions",
+ "exports.$set('GetExtensionAPIDefinitionsForTest',"
+ "function() { return [] });");
+ env()->OverrideNativeHandler(
+ "event_natives",
+ "exports.$set('AttachEvent', function() {});"
+ "exports.$set('DetachEvent', function() {});"
+ "exports.$set('AttachFilteredEvent', function() {});"
+ "exports.$set('AttachFilteredEvent', function() {});"
+ "exports.$set('MatchAgainstEventFilter', function() { return [] });");
+
+ gin::ModuleRegistry::From(env()->context()->v8_context())
+ ->AddBuiltinModule(env()->isolate(), mojo::edk::js::Core::kModuleName,
+ mojo::edk::js::Core::GetModule(env()->isolate()));
+ gin::ModuleRegistry::From(env()->context()->v8_context())
+ ->AddBuiltinModule(env()->isolate(), mojo::edk::js::Support::kModuleName,
+ mojo::edk::js::Support::GetModule(env()->isolate()));
+ gin::Handle<TestServiceProvider> service_provider =
+ TestServiceProvider::Create(env()->isolate());
+ service_provider_ = service_provider.get();
+ gin::ModuleRegistry::From(env()->context()->v8_context())
+ ->AddBuiltinModule(env()->isolate(),
+ "content/public/renderer/frame_service_registry",
+ service_provider.ToV8());
+}
+
+void ApiTestEnvironment::InitializeEnvironment() {
+ gin::Dictionary global(env()->isolate(),
+ env()->context()->v8_context()->Global());
+ gin::Dictionary navigator(gin::Dictionary::CreateEmpty(env()->isolate()));
+ navigator.Set("appVersion", base::StringPiece(""));
+ global.Set("navigator", navigator);
+ gin::Dictionary chrome(gin::Dictionary::CreateEmpty(env()->isolate()));
+ global.Set("chrome", chrome);
+ gin::Dictionary extension(gin::Dictionary::CreateEmpty(env()->isolate()));
+ chrome.Set("extension", extension);
+ gin::Dictionary runtime(gin::Dictionary::CreateEmpty(env()->isolate()));
+ chrome.Set("runtime", runtime);
+}
+
+void ApiTestEnvironment::RunTest(const std::string& file_name,
+ const std::string& test_name) {
+ env()->RegisterTestFile("testBody", file_name);
+ base::RunLoop run_loop;
+ gin::ModuleRegistry::From(env()->context()->v8_context())->AddBuiltinModule(
+ env()->isolate(),
+ "testNatives",
+ TestNatives::Create(env()->isolate(), run_loop.QuitClosure()).ToV8());
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ApiTestEnvironment::RunTestInner, base::Unretained(this),
+ test_name, run_loop.QuitClosure()));
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&ApiTestEnvironment::RunPromisesAgain,
+ base::Unretained(this)));
+ run_loop.Run();
+}
+
+void ApiTestEnvironment::RunTestInner(const std::string& test_name,
+ const base::Closure& quit_closure) {
+ v8::HandleScope scope(env()->isolate());
+ ModuleSystem::NativesEnabledScope natives_enabled(env()->module_system());
+ v8::Local<v8::Value> result =
+ env()->module_system()->CallModuleMethod("testBody", test_name);
+ if (!result->IsTrue()) {
+ base::MessageLoop::current()->PostTask(FROM_HERE, quit_closure);
+ FAIL() << "Failed to run test \"" << test_name << "\"";
+ }
+}
+
+void ApiTestEnvironment::RunPromisesAgain() {
+ v8::MicrotasksScope::PerformCheckpoint(env()->isolate());
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&ApiTestEnvironment::RunPromisesAgain,
+ base::Unretained(this)));
+}
+
+ApiTestBase::ApiTestBase() {
+}
+
+ApiTestBase::~ApiTestBase() {
+}
+
+void ApiTestBase::SetUp() {
+ ModuleSystemTest::SetUp();
+ test_env_.reset(new ApiTestEnvironment(env()));
+}
+
+void ApiTestBase::RunTest(const std::string& file_name,
+ const std::string& test_name) {
+ ExpectNoAssertionsMade();
+ test_env_->RunTest(file_name, test_name);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/api_test_base.h b/chromium/extensions/renderer/api_test_base.h
new file mode 100644
index 00000000000..8b62049924c
--- /dev/null
+++ b/chromium/extensions/renderer/api_test_base.h
@@ -0,0 +1,124 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_API_TEST_BASE_H_
+#define EXTENSIONS_RENDERER_API_TEST_BASE_H_
+
+#include <map>
+#include <string>
+#include <utility>
+
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "extensions/renderer/module_system_test.h"
+#include "extensions/renderer/v8_schema_registry.h"
+#include "gin/handle.h"
+#include "gin/modules/module_registry.h"
+#include "gin/object_template_builder.h"
+#include "gin/wrappable.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace extensions {
+
+class V8SchemaRegistry;
+
+// A ServiceProvider that provides access from JS modules to services registered
+// by AddService() calls.
+class TestServiceProvider : public gin::Wrappable<TestServiceProvider> {
+ public:
+ static gin::Handle<TestServiceProvider> Create(v8::Isolate* isolate);
+ ~TestServiceProvider() override;
+
+ gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
+ v8::Isolate* isolate) override;
+
+ template <typename Interface>
+ void AddService(const base::Callback<void(mojo::InterfaceRequest<Interface>)>
+ service_factory) {
+ service_factories_.insert(std::make_pair(
+ Interface::Name_,
+ base::Bind(ForwardToServiceFactory<Interface>, service_factory)));
+ }
+
+ // Ignore requests for the Interface service.
+ template <typename Interface>
+ void IgnoreServiceRequests() {
+ service_factories_.insert(std::make_pair(
+ Interface::Name_, base::Bind(&TestServiceProvider::IgnoreHandle)));
+ }
+
+ static gin::WrapperInfo kWrapperInfo;
+
+ private:
+ TestServiceProvider();
+
+ mojo::Handle ConnectToService(const std::string& service_name);
+
+ template <typename Interface>
+ static void ForwardToServiceFactory(
+ const base::Callback<void(mojo::InterfaceRequest<Interface>)>
+ service_factory,
+ mojo::ScopedMessagePipeHandle handle) {
+ service_factory.Run(mojo::MakeRequest<Interface>(std::move(handle)));
+ }
+
+ static void IgnoreHandle(mojo::ScopedMessagePipeHandle handle);
+
+ std::map<std::string, base::Callback<void(mojo::ScopedMessagePipeHandle)> >
+ service_factories_;
+};
+
+// An environment for unit testing apps/extensions API custom bindings
+// implemented on Mojo services. This augments a ModuleSystemTestEnvironment
+// with a TestServiceProvider and other modules available in a real extensions
+// environment.
+class ApiTestEnvironment {
+ public:
+ explicit ApiTestEnvironment(ModuleSystemTestEnvironment* environment);
+ ~ApiTestEnvironment();
+ void RunTest(const std::string& file_name, const std::string& test_name);
+ TestServiceProvider* service_provider() { return service_provider_; }
+ ModuleSystemTestEnvironment* env() { return env_; }
+
+ private:
+ void RegisterModules();
+ void InitializeEnvironment();
+ void RunTestInner(const std::string& test_name,
+ const base::Closure& quit_closure);
+ void RunPromisesAgain();
+
+ ModuleSystemTestEnvironment* env_;
+ TestServiceProvider* service_provider_;
+ scoped_ptr<V8SchemaRegistry> v8_schema_registry_;
+};
+
+// A base class for unit testing apps/extensions API custom bindings implemented
+// on Mojo services. To use:
+// 1. Register test Mojo service implementations on service_provider().
+// 2. Write JS tests in extensions/test/data/test_file.js.
+// 3. Write one C++ test function for each JS test containing
+// RunTest("test_file.js", "testFunctionName").
+// See extensions/renderer/api_test_base_unittest.cc and
+// extensions/test/data/api_test_base_unittest.js for sample usage.
+class ApiTestBase : public ModuleSystemTest {
+ protected:
+ ApiTestBase();
+ ~ApiTestBase() override;
+ void SetUp() override;
+ void RunTest(const std::string& file_name, const std::string& test_name);
+
+ ApiTestEnvironment* api_test_env() { return test_env_.get(); }
+ TestServiceProvider* service_provider() {
+ return test_env_->service_provider();
+ }
+
+ private:
+ base::MessageLoop message_loop_;
+ scoped_ptr<ApiTestEnvironment> test_env_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_API_TEST_BASE_H_
diff --git a/chromium/extensions/renderer/api_test_base_unittest.cc b/chromium/extensions/renderer/api_test_base_unittest.cc
new file mode 100644
index 00000000000..149e34eb22a
--- /dev/null
+++ b/chromium/extensions/renderer/api_test_base_unittest.cc
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/api_test_base.h"
+
+namespace extensions {
+
+class ApiTestBaseTest : public ApiTestBase {
+ public:
+ void SetUp() override { ApiTestBase::SetUp(); }
+};
+
+TEST_F(ApiTestBaseTest, TestEnvironment) {
+ RunTest("api_test_base_unittest.js", "testEnvironment");
+}
+
+TEST_F(ApiTestBaseTest, TestPromisesRun) {
+ RunTest("api_test_base_unittest.js", "testPromisesRun");
+}
+
+TEST_F(ApiTestBaseTest, TestCommonModulesAreAvailable) {
+ RunTest("api_test_base_unittest.js", "testCommonModulesAreAvailable");
+}
+
+TEST_F(ApiTestBaseTest, TestMojoModulesAreAvailable) {
+ RunTest("api_test_base_unittest.js", "testMojoModulesAreAvailable");
+}
+
+TEST_F(ApiTestBaseTest, TestTestBindings) {
+ RunTest("api_test_base_unittest.js", "testTestBindings");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/app_window_custom_bindings.cc b/chromium/extensions/renderer/app_window_custom_bindings.cc
new file mode 100644
index 00000000000..492731d1a82
--- /dev/null
+++ b/chromium/extensions/renderer/app_window_custom_bindings.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/app_window_custom_bindings.h"
+
+#include "base/command_line.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/render_view.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/switches.h"
+#include "extensions/renderer/script_context.h"
+#include "grit/extensions_renderer_resources.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebView.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+AppWindowCustomBindings::AppWindowCustomBindings(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("GetFrame", base::Bind(&AppWindowCustomBindings::GetFrame,
+ base::Unretained(this)));
+
+ RouteFunction("GetWindowControlsHtmlTemplate",
+ base::Bind(&AppWindowCustomBindings::GetWindowControlsHtmlTemplate,
+ base::Unretained(this)));
+}
+
+void AppWindowCustomBindings::GetFrame(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // TODO(jeremya): convert this to IDL nocompile to get validation, and turn
+ // these argument checks into CHECK().
+ if (args.Length() != 2)
+ return;
+
+ if (!args[0]->IsInt32() || !args[1]->IsBoolean())
+ return;
+
+ int frame_id = args[0]->Int32Value();
+ bool notify_browser = args[1]->BooleanValue();
+
+ if (frame_id == MSG_ROUTING_NONE)
+ return;
+
+ content::RenderFrame* app_frame =
+ content::RenderFrame::FromRoutingID(frame_id);
+ if (!app_frame)
+ return;
+
+ if (notify_browser) {
+ content::RenderThread::Get()->Send(new ExtensionHostMsg_AppWindowReady(
+ app_frame->GetRenderView()->GetRoutingID()));
+ }
+
+ v8::Local<v8::Value> window =
+ app_frame->GetWebFrame()->mainWorldScriptContext()->Global();
+ args.GetReturnValue().Set(window);
+}
+
+void AppWindowCustomBindings::GetWindowControlsHtmlTemplate(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(args.Length(), 0);
+
+ v8::Local<v8::Value> result = v8::String::Empty(args.GetIsolate());
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableAppWindowControls)) {
+ base::StringValue value(
+ ResourceBundle::GetSharedInstance()
+ .GetRawDataResource(IDR_WINDOW_CONTROLS_TEMPLATE_HTML)
+ .as_string());
+ scoped_ptr<content::V8ValueConverter> converter(
+ content::V8ValueConverter::create());
+ result = converter->ToV8Value(&value, context()->v8_context());
+ }
+ args.GetReturnValue().Set(result);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/app_window_custom_bindings.h b/chromium/extensions/renderer/app_window_custom_bindings.h
new file mode 100644
index 00000000000..3fbab04f0fd
--- /dev/null
+++ b/chromium/extensions/renderer/app_window_custom_bindings.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_APP_WINDOW_CUSTOM_BINDINGS_H_
+#define EXTENSIONS_RENDERER_APP_WINDOW_CUSTOM_BINDINGS_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContextSet;
+
+// Implements custom bindings for the app.window API.
+class AppWindowCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ AppWindowCustomBindings(ScriptContext* context);
+
+ private:
+ void GetFrame(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Return string containing the HTML <template> for the <window-controls>
+ // custom element.
+ void GetWindowControlsHtmlTemplate(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ DISALLOW_COPY_AND_ASSIGN(AppWindowCustomBindings);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_APP_WINDOW_CUSTOM_BINDINGS_H_
diff --git a/chromium/extensions/renderer/binding_generating_native_handler.cc b/chromium/extensions/renderer/binding_generating_native_handler.cc
new file mode 100644
index 00000000000..8912017e7f1
--- /dev/null
+++ b/chromium/extensions/renderer/binding_generating_native_handler.cc
@@ -0,0 +1,112 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/binding_generating_native_handler.h"
+
+#include "base/macros.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/v8_helpers.h"
+
+namespace extensions {
+
+using namespace v8_helpers;
+
+BindingGeneratingNativeHandler::BindingGeneratingNativeHandler(
+ ScriptContext* context,
+ const std::string& api_name,
+ const std::string& bind_to)
+ : context_(context), api_name_(api_name), bind_to_(bind_to) {}
+
+v8::Local<v8::Object> BindingGeneratingNativeHandler::NewInstance() {
+ // This long sequence of commands effectively runs the JavaScript code,
+ // such that result[bind_to] is the compiled schema for |api_name|:
+ //
+ // var result = {};
+ // result[bind_to] = require('binding').Binding.create(api_name).generate();
+ // return result;
+ //
+ // Unfortunately using the v8 APIs makes that quite verbose.
+ // Each stage is marked with the code it executes.
+ v8::Isolate* isolate = context_->isolate();
+ v8::EscapableHandleScope scope(isolate);
+
+ // Convert |api_name| and |bind_to| into their v8::Strings to pass
+ // through the v8 APIs.
+ v8::Local<v8::String> v8_api_name;
+ v8::Local<v8::String> v8_bind_to;
+ if (!ToV8String(isolate, api_name_, &v8_api_name) ||
+ !ToV8String(isolate, bind_to_, &v8_bind_to)) {
+ NOTREACHED();
+ return v8::Local<v8::Object>();
+ }
+
+ v8::Local<v8::Context> v8_context = context_->v8_context();
+
+ // require('binding');
+ v8::Local<v8::Object> binding_module;
+ if (!context_->module_system()->Require("binding").ToLocal(&binding_module)) {
+ NOTREACHED();
+ return v8::Local<v8::Object>();
+ }
+
+ // require('binding').Binding;
+ v8::Local<v8::Value> binding_value;
+ v8::Local<v8::Object> binding;
+ if (!GetProperty(v8_context, binding_module, "Binding", &binding_value) ||
+ !binding_value->ToObject(v8_context).ToLocal(&binding)) {
+ NOTREACHED();
+ return v8::Local<v8::Object>();
+ }
+
+ // require('binding').Binding.create;
+ v8::Local<v8::Value> create_binding_value;
+ if (!GetProperty(v8_context, binding, "create", &create_binding_value) ||
+ !create_binding_value->IsFunction()) {
+ NOTREACHED();
+ return v8::Local<v8::Object>();
+ }
+ v8::Local<v8::Function> create_binding =
+ create_binding_value.As<v8::Function>();
+
+ // require('Binding').Binding.create(api_name);
+ v8::Local<v8::Value> argv[] = {v8_api_name};
+ v8::Local<v8::Value> binding_instance_value;
+ v8::Local<v8::Object> binding_instance;
+ if (!CallFunction(v8_context, create_binding, binding, arraysize(argv), argv,
+ &binding_instance_value) ||
+ !binding_instance_value->ToObject(v8_context)
+ .ToLocal(&binding_instance)) {
+ NOTREACHED();
+ return v8::Local<v8::Object>();
+ }
+
+ // require('binding').Binding.create(api_name).generate;
+ v8::Local<v8::Value> generate_value;
+ if (!GetProperty(v8_context, binding_instance, "generate", &generate_value) ||
+ !generate_value->IsFunction()) {
+ NOTREACHED();
+ return v8::Local<v8::Object>();
+ }
+ v8::Local<v8::Function> generate = generate_value.As<v8::Function>();
+
+ // require('binding').Binding.create(api_name).generate();
+ v8::Local<v8::Object> object = v8::Object::New(isolate);
+ v8::Local<v8::Value> compiled_schema;
+ if (!CallFunction(v8_context, generate, binding_instance, 0, nullptr,
+ &compiled_schema)) {
+ NOTREACHED();
+ return v8::Local<v8::Object>();
+ }
+
+ // var result = {};
+ // result[bind_to] = ...;
+ if (!SetProperty(v8_context, object, v8_bind_to, compiled_schema)) {
+ NOTREACHED();
+ return v8::Local<v8::Object>();
+ }
+ // return result;
+ return scope.Escape(object);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/binding_generating_native_handler.h b/chromium/extensions/renderer/binding_generating_native_handler.h
new file mode 100644
index 00000000000..d4e4b0aa722
--- /dev/null
+++ b/chromium/extensions/renderer/binding_generating_native_handler.h
@@ -0,0 +1,38 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_BINDING_GENERATING_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_BINDING_GENERATING_NATIVE_HANDLER_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "extensions/renderer/native_handler.h"
+
+namespace extensions {
+
+class ScriptContext;
+
+// Generates API bindings based on the JSON/IDL schemas. This is done by
+// creating a |Binding| (from binding.js) for the schema and generating the
+// bindings from that.
+class BindingGeneratingNativeHandler : public NativeHandler {
+ public:
+ // Generates binding for |api_name|, and sets the |bind_to| property on the
+ // Object returned by |NewInstance| to the generated binding.
+ BindingGeneratingNativeHandler(ScriptContext* context,
+ const std::string& api_name,
+ const std::string& bind_to);
+
+ v8::Local<v8::Object> NewInstance() override;
+
+ private:
+ ScriptContext* context_;
+ std::string api_name_;
+ std::string bind_to_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_BINDING_GENERATING_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/blob_native_handler.cc b/chromium/extensions/renderer/blob_native_handler.cc
new file mode 100644
index 00000000000..4161fbc2184
--- /dev/null
+++ b/chromium/extensions/renderer/blob_native_handler.cc
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/blob_native_handler.h"
+
+#include "base/bind.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/platform/WebURL.h"
+#include "third_party/WebKit/public/web/WebBlob.h"
+
+namespace {
+
+// Expects a single Blob argument. Returns the Blob's UUID.
+void GetBlobUuid(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ blink::WebBlob blob = blink::WebBlob::fromV8Value(args[0]);
+ args.GetReturnValue().Set(
+ v8::String::NewFromUtf8(args.GetIsolate(), blob.uuid().utf8().data()));
+}
+
+} // namespace
+
+namespace extensions {
+
+BlobNativeHandler::BlobNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("GetBlobUuid", base::Bind(&GetBlobUuid));
+ RouteFunction("TakeBrowserProcessBlob",
+ base::Bind(&BlobNativeHandler::TakeBrowserProcessBlob,
+ base::Unretained(this)));
+}
+
+// Take ownership of a Blob created on the browser process. Expects the Blob's
+// UUID, type, and size as arguments. Returns the Blob we just took to
+// Javascript. The Blob reference in the browser process is dropped through
+// a separate flow to avoid leaking Blobs if the script context is destroyed.
+void BlobNativeHandler::TakeBrowserProcessBlob(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(3, args.Length());
+ CHECK(args[0]->IsString());
+ CHECK(args[1]->IsString());
+ CHECK(args[2]->IsInt32());
+ std::string uuid(*v8::String::Utf8Value(args[0]));
+ std::string type(*v8::String::Utf8Value(args[1]));
+ blink::WebBlob blob =
+ blink::WebBlob::createFromUUID(blink::WebString::fromUTF8(uuid),
+ blink::WebString::fromUTF8(type),
+ args[2]->Int32Value());
+ args.GetReturnValue().Set(blob.toV8Value(
+ context()->v8_context()->Global(), args.GetIsolate()));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/blob_native_handler.h b/chromium/extensions/renderer/blob_native_handler.h
new file mode 100644
index 00000000000..6f986e56647
--- /dev/null
+++ b/chromium/extensions/renderer/blob_native_handler.h
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_BLOB_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_BLOB_NATIVE_HANDLER_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+// This native handler is used to extract Blobs' UUIDs and pass them over to the
+// browser process extension implementation via argument modification. This is
+// necessary to support extension functions that take Blob parameters, as Blobs
+// are not serialized and sent over to the browser process in the normal way.
+//
+// Blobs sent via this method don't have their ref-counts incremented, so the
+// app using this technique must be sure to keep a reference.
+class BlobNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit BlobNativeHandler(ScriptContext* context);
+
+ private:
+ void TakeBrowserProcessBlob(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_BLOB_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/console.cc b/chromium/extensions/renderer/console.cc
new file mode 100644
index 00000000000..64160743f77
--- /dev/null
+++ b/chromium/extensions/renderer/console.cc
@@ -0,0 +1,127 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/console.h"
+
+#include "base/compiler_specific.h"
+#include "base/debug/alias.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "extensions/renderer/v8_helpers.h"
+
+namespace extensions {
+namespace console {
+
+using namespace v8_helpers;
+
+namespace {
+
+// Writes |message| to stack to show up in minidump, then crashes.
+void CheckWithMinidump(const std::string& message) {
+ char minidump[1024];
+ base::debug::Alias(&minidump);
+ base::snprintf(
+ minidump, arraysize(minidump), "e::console: %s", message.c_str());
+ CHECK(false) << message;
+}
+
+typedef void (*LogMethod)(content::RenderFrame* render_frame,
+ const std::string& message);
+
+void BoundLogMethodCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
+ std::string message;
+ for (int i = 0; i < info.Length(); ++i) {
+ if (i > 0)
+ message += " ";
+ message += *v8::String::Utf8Value(info[i]);
+ }
+
+ v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
+ if (context.IsEmpty()) {
+ LOG(WARNING) << "Could not log \"" << message << "\": no context given";
+ return;
+ }
+
+ ScriptContext* script_context =
+ ScriptContextSet::GetContextByV8Context(context);
+ LogMethod log_method =
+ reinterpret_cast<LogMethod>(info.Data().As<v8::External>()->Value());
+ (*log_method)(script_context ? script_context->GetRenderFrame() : nullptr,
+ message);
+}
+
+void BindLogMethod(v8::Isolate* isolate,
+ v8::Local<v8::Object> target,
+ const std::string& name,
+ LogMethod log_method) {
+ v8::Local<v8::FunctionTemplate> tmpl = v8::FunctionTemplate::New(
+ isolate,
+ &BoundLogMethodCallback,
+ v8::External::New(isolate, reinterpret_cast<void*>(log_method)));
+ v8::Local<v8::Function> function;
+ if (!tmpl->GetFunction(isolate->GetCurrentContext()).ToLocal(&function)) {
+ LOG(FATAL) << "Could not create log function \"" << name << "\"";
+ return;
+ }
+ v8::Local<v8::String> v8_name = ToV8StringUnsafe(isolate, name);
+ if (!SetProperty(isolate->GetCurrentContext(), target, v8_name, function)) {
+ LOG(WARNING) << "Could not bind log method \"" << name << "\"";
+ }
+ SetProperty(isolate->GetCurrentContext(), target, v8_name,
+ tmpl->GetFunction());
+}
+
+} // namespace
+
+void Debug(content::RenderFrame* render_frame, const std::string& message) {
+ AddMessage(render_frame, content::CONSOLE_MESSAGE_LEVEL_DEBUG, message);
+}
+
+void Log(content::RenderFrame* render_frame, const std::string& message) {
+ AddMessage(render_frame, content::CONSOLE_MESSAGE_LEVEL_LOG, message);
+}
+
+void Warn(content::RenderFrame* render_frame, const std::string& message) {
+ AddMessage(render_frame, content::CONSOLE_MESSAGE_LEVEL_WARNING, message);
+}
+
+void Error(content::RenderFrame* render_frame, const std::string& message) {
+ AddMessage(render_frame, content::CONSOLE_MESSAGE_LEVEL_ERROR, message);
+}
+
+void Fatal(content::RenderFrame* render_frame, const std::string& message) {
+ Error(render_frame, message);
+ CheckWithMinidump(message);
+}
+
+void AddMessage(content::RenderFrame* render_frame,
+ content::ConsoleMessageLevel level,
+ const std::string& message) {
+ if (!render_frame) {
+ LOG(WARNING) << "Could not log \"" << message
+ << "\": no render frame found";
+ } else {
+ render_frame->AddMessageToConsole(level, message);
+ }
+}
+
+v8::Local<v8::Object> AsV8Object(v8::Isolate* isolate) {
+ v8::EscapableHandleScope handle_scope(isolate);
+ v8::Local<v8::Object> console_object = v8::Object::New(isolate);
+ BindLogMethod(isolate, console_object, "debug", &Debug);
+ BindLogMethod(isolate, console_object, "log", &Log);
+ BindLogMethod(isolate, console_object, "warn", &Warn);
+ BindLogMethod(isolate, console_object, "error", &Error);
+ return handle_scope.Escape(console_object);
+}
+
+} // namespace console
+} // namespace extensions
diff --git a/chromium/extensions/renderer/console.h b/chromium/extensions/renderer/console.h
new file mode 100644
index 00000000000..79d338e773a
--- /dev/null
+++ b/chromium/extensions/renderer/console.h
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_CONSOLE_H_
+#define EXTENSIONS_RENDERER_CONSOLE_H_
+
+#include <string>
+
+#include "content/public/common/console_message_level.h"
+#include "v8/include/v8.h"
+
+namespace content {
+class RenderFrame;
+}
+
+namespace extensions {
+
+// Utility for logging messages to RenderFrames.
+namespace console {
+
+// Adds |message| to the console of |render_frame|. If |render_frame| is null,
+// LOG()s the message instead.
+void Debug(content::RenderFrame* render_frame, const std::string& message);
+void Log(content::RenderFrame* render_frame, const std::string& message);
+void Warn(content::RenderFrame* render_frame, const std::string& message);
+void Error(content::RenderFrame* render_frame, const std::string& message);
+
+// Logs an Error then crashes the current process.
+void Fatal(content::RenderFrame* render_frame, const std::string& message);
+
+void AddMessage(content::RenderFrame* render_frame,
+ content::ConsoleMessageLevel level,
+ const std::string& message);
+
+// Returns a new v8::Object with each standard log method (Debug/Log/Warn/Error)
+// bound to respective debug/log/warn/error methods. This is a direct drop-in
+// replacement for the standard devtools console.* methods usually accessible
+// from JS.
+v8::Local<v8::Object> AsV8Object(v8::Isolate* isolate);
+
+} // namespace console
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_CONSOLE_H_
diff --git a/chromium/extensions/renderer/console_apitest.cc b/chromium/extensions/renderer/console_apitest.cc
new file mode 100644
index 00000000000..de6e3b10a95
--- /dev/null
+++ b/chromium/extensions/renderer/console_apitest.cc
@@ -0,0 +1,16 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/extension_apitest.h"
+
+namespace extensions {
+namespace {
+
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, UncaughtExceptionLogging) {
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(RunExtensionTest("uncaught_exception_logging")) << message_;
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/renderer/content_watcher.cc b/chromium/extensions/renderer/content_watcher.cc
new file mode 100644
index 00000000000..8c99e11364c
--- /dev/null
+++ b/chromium/extensions/renderer/content_watcher.cc
@@ -0,0 +1,125 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/content_watcher.h"
+
+#include <stddef.h>
+
+#include "content/public/renderer/render_view.h"
+#include "content/public/renderer/render_view_visitor.h"
+#include "extensions/common/extension_messages.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebElement.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebView.h"
+
+namespace extensions {
+
+using blink::WebString;
+using blink::WebVector;
+using blink::WebView;
+
+ContentWatcher::ContentWatcher() {}
+ContentWatcher::~ContentWatcher() {}
+
+void ContentWatcher::OnWatchPages(
+ const std::vector<std::string>& new_css_selectors_utf8) {
+ blink::WebVector<blink::WebString> new_css_selectors(
+ new_css_selectors_utf8.size());
+ bool changed = new_css_selectors.size() != css_selectors_.size();
+ for (size_t i = 0; i < new_css_selectors.size(); ++i) {
+ new_css_selectors[i] =
+ blink::WebString::fromUTF8(new_css_selectors_utf8[i]);
+ if (!changed && new_css_selectors[i] != css_selectors_[i])
+ changed = true;
+ }
+
+ if (!changed)
+ return;
+
+ css_selectors_.swap(new_css_selectors);
+
+ // Tell each frame's document about the new set of watched selectors. These
+ // will trigger calls to DidMatchCSS after Blink has a chance to apply the new
+ // style, which will in turn notify the browser about the changes.
+ struct WatchSelectors : public content::RenderViewVisitor {
+ explicit WatchSelectors(const WebVector<WebString>& css_selectors)
+ : css_selectors_(css_selectors) {}
+
+ bool Visit(content::RenderView* view) override {
+ // TODO(dcheng): This should be rewritten to be frame-oriented. It
+ // probably breaks declarative content for OOPI.
+ for (blink::WebFrame* frame = view->GetWebView()->mainFrame(); frame;
+ frame = frame->traverseNext(/*wrap=*/false)) {
+ if (frame->isWebRemoteFrame())
+ continue;
+ frame->toWebLocalFrame()->document().watchCSSSelectors(css_selectors_);
+ }
+
+ return true; // Continue visiting.
+ }
+
+ const WebVector<WebString>& css_selectors_;
+ };
+ WatchSelectors visitor(css_selectors_);
+ content::RenderView::ForEach(&visitor);
+}
+
+void ContentWatcher::DidCreateDocumentElement(blink::WebLocalFrame* frame) {
+ frame->document().watchCSSSelectors(css_selectors_);
+}
+
+void ContentWatcher::DidMatchCSS(
+ blink::WebLocalFrame* frame,
+ const WebVector<WebString>& newly_matching_selectors,
+ const WebVector<WebString>& stopped_matching_selectors) {
+ std::set<std::string>& frame_selectors = matching_selectors_[frame];
+ for (size_t i = 0; i < stopped_matching_selectors.size(); ++i)
+ frame_selectors.erase(stopped_matching_selectors[i].utf8());
+ for (size_t i = 0; i < newly_matching_selectors.size(); ++i)
+ frame_selectors.insert(newly_matching_selectors[i].utf8());
+
+ if (frame_selectors.empty())
+ matching_selectors_.erase(frame);
+
+ NotifyBrowserOfChange(frame);
+}
+
+void ContentWatcher::NotifyBrowserOfChange(
+ blink::WebLocalFrame* changed_frame) const {
+ blink::WebFrame* const top_frame = changed_frame->top();
+ const blink::WebSecurityOrigin top_origin = top_frame->getSecurityOrigin();
+ // Want to aggregate matched selectors from all frames where an
+ // extension with access to top_origin could run on the frame.
+ if (!top_origin.canAccess(changed_frame->document().getSecurityOrigin())) {
+ // If the changed frame can't be accessed by the top frame, then
+ // no change in it could affect the set of selectors we'd send back.
+ return;
+ }
+
+ std::set<base::StringPiece> transitive_selectors;
+ for (blink::WebFrame* frame = top_frame; frame;
+ frame = frame->traverseNext(/*wrap=*/false)) {
+ if (top_origin.canAccess(frame->getSecurityOrigin())) {
+ std::map<blink::WebFrame*, std::set<std::string> >::const_iterator
+ frame_selectors = matching_selectors_.find(frame);
+ if (frame_selectors != matching_selectors_.end()) {
+ transitive_selectors.insert(frame_selectors->second.begin(),
+ frame_selectors->second.end());
+ }
+ }
+ }
+ std::vector<std::string> selector_strings;
+ for (std::set<base::StringPiece>::const_iterator it =
+ transitive_selectors.begin();
+ it != transitive_selectors.end();
+ ++it)
+ selector_strings.push_back(it->as_string());
+ content::RenderView* view =
+ content::RenderView::FromWebView(top_frame->view());
+ view->Send(new ExtensionHostMsg_OnWatchedPageChange(view->GetRoutingID(),
+ selector_strings));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/content_watcher.h b/chromium/extensions/renderer/content_watcher.h
new file mode 100644
index 00000000000..4480ce99cf7
--- /dev/null
+++ b/chromium/extensions/renderer/content_watcher.h
@@ -0,0 +1,73 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_CONTENT_WATCHER_H_
+#define EXTENSIONS_RENDERER_CONTENT_WATCHER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "third_party/WebKit/public/platform/WebVector.h"
+
+namespace blink {
+class WebFrame;
+class WebLocalFrame;
+class WebString;
+}
+
+namespace extensions {
+class Dispatcher;
+class Extension;
+class NativeHandler;
+
+// Watches the content of WebFrames to notify extensions when they match various
+// patterns. This class tracks the set of relevant patterns (set by
+// ExtensionMsg_WatchPages) and the set that match on each WebFrame, and sends a
+// ExtensionHostMsg_OnWatchedPageChange whenever a RenderView's set changes.
+//
+// There's one ContentWatcher per Dispatcher rather than per RenderView because
+// WebFrames can move between RenderViews through adoptNode.
+// TODO(devlin): This class isn't OOPI-safe.
+class ContentWatcher {
+ public:
+ ContentWatcher();
+ ~ContentWatcher();
+
+ // Handler for ExtensionMsg_WatchPages.
+ void OnWatchPages(const std::vector<std::string>& css_selectors);
+
+ // Uses WebDocument::watchCSSSelectors to watch the selectors in
+ // css_selectors_ and get a callback into DidMatchCSS() whenever the set of
+ // matching selectors in |frame| changes.
+ void DidCreateDocumentElement(blink::WebLocalFrame* frame);
+
+ // Records that |newly_matching_selectors| have started matching on |*frame|,
+ // and |stopped_matching_selectors| have stopped matching.
+ void DidMatchCSS(
+ blink::WebLocalFrame* frame,
+ const blink::WebVector<blink::WebString>& newly_matching_selectors,
+ const blink::WebVector<blink::WebString>& stopped_matching_selectors);
+
+ private:
+ // Given that we saw a change in the CSS selectors that |changed_frame|
+ // matched, tell the browser about the new set of matching selectors in its
+ // top-level page. We filter this so that if an extension were to be granted
+ // activeTab permission on that top-level page, we only send CSS selectors for
+ // frames that it could run on.
+ void NotifyBrowserOfChange(blink::WebLocalFrame* changed_frame) const;
+
+ // If any of these selectors match on a page, we need to send an
+ // ExtensionHostMsg_OnWatchedPageChange back to the browser.
+ blink::WebVector<blink::WebString> css_selectors_;
+
+ // Maps live WebFrames to the set of CSS selectors they match. Blink sends
+ // back diffs, which we apply to these sets.
+ std::map<blink::WebFrame*, std::set<std::string> > matching_selectors_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_CONTENT_WATCHER_H_
diff --git a/chromium/extensions/renderer/context_menus_custom_bindings.cc b/chromium/extensions/renderer/context_menus_custom_bindings.cc
new file mode 100644
index 00000000000..40f4705ebf3
--- /dev/null
+++ b/chromium/extensions/renderer/context_menus_custom_bindings.cc
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/context_menus_custom_bindings.h"
+
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/extension_messages.h"
+#include "v8/include/v8.h"
+
+namespace {
+
+void GetNextContextMenuId(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ int context_menu_id = -1;
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_GenerateUniqueID(&context_menu_id));
+ args.GetReturnValue().Set(static_cast<int32_t>(context_menu_id));
+}
+
+} // namespace
+
+namespace extensions {
+
+ContextMenusCustomBindings::ContextMenusCustomBindings(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("GetNextContextMenuId", base::Bind(&GetNextContextMenuId));
+}
+
+} // extensions
diff --git a/chromium/extensions/renderer/context_menus_custom_bindings.h b/chromium/extensions/renderer/context_menus_custom_bindings.h
new file mode 100644
index 00000000000..7c625b48a83
--- /dev/null
+++ b/chromium/extensions/renderer/context_menus_custom_bindings.h
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_CONTEXT_MENUS_CUSTOM_BINDINGS_H_
+#define EXTENSIONS_RENDERER_CONTEXT_MENUS_CUSTOM_BINDINGS_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+// Implements custom bindings for the contextMenus API.
+class ContextMenusCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ ContextMenusCustomBindings(ScriptContext* context);
+};
+
+} // extensions
+
+#endif // EXTENSIONS_RENDERER_CONTEXT_MENUS_CUSTOM_BINDINGS_H_
diff --git a/chromium/extensions/renderer/css_native_handler.cc b/chromium/extensions/renderer/css_native_handler.cc
new file mode 100644
index 00000000000..d7cdc39f164
--- /dev/null
+++ b/chromium/extensions/renderer/css_native_handler.cc
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/css_native_handler.h"
+
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/v8_helpers.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/web/WebSelector.h"
+
+namespace extensions {
+
+using blink::WebString;
+
+CssNativeHandler::CssNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("CanonicalizeCompoundSelector",
+ base::Bind(&CssNativeHandler::CanonicalizeCompoundSelector,
+ base::Unretained(this)));
+}
+
+void CssNativeHandler::CanonicalizeCompoundSelector(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(args.Length(), 1);
+ CHECK(args[0]->IsString());
+ std::string input_selector = *v8::String::Utf8Value(args[0]);
+ // TODO(esprehn): This API shouldn't exist, the extension code should be
+ // moved into blink.
+ WebString output_selector = blink::canonicalizeSelector(
+ WebString::fromUTF8(input_selector), blink::WebSelectorTypeCompound);
+ args.GetReturnValue().Set(v8_helpers::ToV8StringUnsafe(
+ args.GetIsolate(), output_selector.utf8().c_str()));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/css_native_handler.h b/chromium/extensions/renderer/css_native_handler.h
new file mode 100644
index 00000000000..c3d1bc34649
--- /dev/null
+++ b/chromium/extensions/renderer/css_native_handler.h
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_CSS_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_CSS_NATIVE_HANDLER_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+class CssNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit CssNativeHandler(ScriptContext* context);
+
+ private:
+ // Expects one string argument that's a comma-separated list of compound CSS
+ // selectors (http://dev.w3.org/csswg/selectors4/#compound), and returns its
+ // Blink-canonicalized form. If the selector is invalid, returns an empty
+ // string.
+ void CanonicalizeCompoundSelector(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_CSS_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/dispatcher.cc b/chromium/extensions/renderer/dispatcher.cc
new file mode 100644
index 00000000000..60a4a11bfec
--- /dev/null
+++ b/chromium/extensions/renderer/dispatcher.cc
@@ -0,0 +1,1629 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/dispatcher.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/debug/alias.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/user_metrics_action.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "content/grit/content_resources.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/common/browser_plugin_guest_mode.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/api/messaging/message.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension_api.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/extension_urls.h"
+#include "extensions/common/feature_switch.h"
+#include "extensions/common/features/behavior_feature.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/features/feature_provider.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/background_info.h"
+#include "extensions/common/manifest_handlers/content_capabilities_handler.h"
+#include "extensions/common/manifest_handlers/externally_connectable.h"
+#include "extensions/common/manifest_handlers/options_page_info.h"
+#include "extensions/common/message_bundle.h"
+#include "extensions/common/permissions/permission_set.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/switches.h"
+#include "extensions/common/view_type.h"
+#include "extensions/renderer/api_activity_logger.h"
+#include "extensions/renderer/api_definitions_natives.h"
+#include "extensions/renderer/app_window_custom_bindings.h"
+#include "extensions/renderer/binding_generating_native_handler.h"
+#include "extensions/renderer/blob_native_handler.h"
+#include "extensions/renderer/content_watcher.h"
+#include "extensions/renderer/context_menus_custom_bindings.h"
+#include "extensions/renderer/css_native_handler.h"
+#include "extensions/renderer/dispatcher_delegate.h"
+#include "extensions/renderer/display_source_custom_bindings.h"
+#include "extensions/renderer/document_custom_bindings.h"
+#include "extensions/renderer/dom_activity_logger.h"
+#include "extensions/renderer/event_bindings.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/extension_helper.h"
+#include "extensions/renderer/extensions_renderer_client.h"
+#include "extensions/renderer/file_system_natives.h"
+#include "extensions/renderer/guest_view/guest_view_internal_custom_bindings.h"
+#include "extensions/renderer/i18n_custom_bindings.h"
+#include "extensions/renderer/id_generator_custom_bindings.h"
+#include "extensions/renderer/lazy_background_page_native_handler.h"
+#include "extensions/renderer/logging_native_handler.h"
+#include "extensions/renderer/messaging_bindings.h"
+#include "extensions/renderer/module_system.h"
+#include "extensions/renderer/print_native_handler.h"
+#include "extensions/renderer/process_info_native_handler.h"
+#include "extensions/renderer/render_frame_observer_natives.h"
+#include "extensions/renderer/renderer_extension_registry.h"
+#include "extensions/renderer/request_sender.h"
+#include "extensions/renderer/runtime_custom_bindings.h"
+#include "extensions/renderer/safe_builtins.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "extensions/renderer/script_injection.h"
+#include "extensions/renderer/script_injection_manager.h"
+#include "extensions/renderer/send_request_natives.h"
+#include "extensions/renderer/set_icon_natives.h"
+#include "extensions/renderer/static_v8_external_one_byte_string_resource.h"
+#include "extensions/renderer/test_features_native_handler.h"
+#include "extensions/renderer/test_native_handler.h"
+#include "extensions/renderer/user_gestures_native_handler.h"
+#include "extensions/renderer/utils_native_handler.h"
+#include "extensions/renderer/v8_context_native_handler.h"
+#include "extensions/renderer/v8_helpers.h"
+#include "extensions/renderer/wake_event_page.h"
+#include "extensions/renderer/worker_script_context_set.h"
+#include "grit/extensions_renderer_resources.h"
+#include "mojo/public/js/constants.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/platform/WebURLRequest.h"
+#include "third_party/WebKit/public/web/WebCustomElement.h"
+#include "third_party/WebKit/public/web/WebDataSource.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebFrame.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebRuntimeFeatures.h"
+#include "third_party/WebKit/public/web/WebScopedUserGesture.h"
+#include "third_party/WebKit/public/web/WebSecurityPolicy.h"
+#include "third_party/WebKit/public/web/WebView.h"
+#include "ui/base/layout.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "v8/include/v8.h"
+
+using base::UserMetricsAction;
+using blink::WebDataSource;
+using blink::WebDocument;
+using blink::WebScopedUserGesture;
+using blink::WebSecurityPolicy;
+using blink::WebString;
+using blink::WebVector;
+using blink::WebView;
+using content::RenderThread;
+
+namespace extensions {
+
+namespace {
+
+static const int64_t kInitialExtensionIdleHandlerDelayMs = 5 * 1000;
+static const int64_t kMaxExtensionIdleHandlerDelayMs = 5 * 60 * 1000;
+static const char kEventDispatchFunction[] = "dispatchEvent";
+static const char kOnSuspendEvent[] = "runtime.onSuspend";
+static const char kOnSuspendCanceledEvent[] = "runtime.onSuspendCanceled";
+
+void CrashOnException(const v8::TryCatch& trycatch) {
+ NOTREACHED();
+};
+
+// Returns the global value for "chrome" from |context|. If one doesn't exist
+// creates a new object for it.
+//
+// Note that this isn't necessarily an object, since webpages can write, for
+// example, "window.chrome = true".
+v8::Local<v8::Value> GetOrCreateChrome(ScriptContext* context) {
+ v8::Local<v8::String> chrome_string(
+ v8::String::NewFromUtf8(context->isolate(), "chrome"));
+ v8::Local<v8::Object> global(context->v8_context()->Global());
+ v8::Local<v8::Value> chrome(global->Get(chrome_string));
+ if (chrome->IsUndefined()) {
+ chrome = v8::Object::New(context->isolate());
+ global->Set(chrome_string, chrome);
+ }
+ return chrome;
+}
+
+// Returns |value| cast to an object if possible, else an empty handle.
+v8::Local<v8::Object> AsObjectOrEmpty(v8::Local<v8::Value> value) {
+ return value->IsObject() ? value.As<v8::Object>() : v8::Local<v8::Object>();
+}
+
+// Calls a method |method_name| in a module |module_name| belonging to the
+// module system from |context|. Intended as a callback target from
+// ScriptContextSet::ForEach.
+void CallModuleMethod(const std::string& module_name,
+ const std::string& method_name,
+ const base::ListValue* args,
+ ScriptContext* context) {
+ v8::HandleScope handle_scope(context->isolate());
+ v8::Context::Scope context_scope(context->v8_context());
+
+ scoped_ptr<content::V8ValueConverter> converter(
+ content::V8ValueConverter::create());
+
+ std::vector<v8::Local<v8::Value>> arguments;
+ for (base::ListValue::const_iterator it = args->begin(); it != args->end();
+ ++it) {
+ arguments.push_back(converter->ToV8Value(*it, context->v8_context()));
+ }
+
+ context->module_system()->CallModuleMethod(
+ module_name, method_name, &arguments);
+}
+
+// This handles the "chrome." root API object in script contexts.
+class ChromeNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit ChromeNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction(
+ "GetChrome",
+ base::Bind(&ChromeNativeHandler::GetChrome, base::Unretained(this)));
+ }
+
+ void GetChrome(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(GetOrCreateChrome(context()));
+ }
+};
+
+base::LazyInstance<WorkerScriptContextSet> g_worker_script_context_set =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// Note that we can't use Blink public APIs in the constructor becase Blink
+// is not initialized at the point we create Dispatcher.
+Dispatcher::Dispatcher(DispatcherDelegate* delegate)
+ : delegate_(delegate),
+ content_watcher_(new ContentWatcher()),
+ source_map_(&ResourceBundle::GetSharedInstance()),
+ v8_schema_registry_(new V8SchemaRegistry),
+ user_script_set_manager_observer_(this),
+ webrequest_used_(false) {
+ const base::CommandLine& command_line =
+ *(base::CommandLine::ForCurrentProcess());
+ set_idle_notifications_ =
+ command_line.HasSwitch(switches::kExtensionProcess) ||
+ command_line.HasSwitch(::switches::kSingleProcess);
+
+ if (set_idle_notifications_) {
+ RenderThread::Get()->SetIdleNotificationDelayInMs(
+ kInitialExtensionIdleHandlerDelayMs);
+ }
+
+ script_context_set_.reset(new ScriptContextSet(&active_extension_ids_));
+ user_script_set_manager_.reset(new UserScriptSetManager());
+ script_injection_manager_.reset(
+ new ScriptInjectionManager(user_script_set_manager_.get()));
+ user_script_set_manager_observer_.Add(user_script_set_manager_.get());
+ request_sender_.reset(new RequestSender(this));
+ PopulateSourceMap();
+ WakeEventPage::Get()->Init(RenderThread::Get());
+
+ RenderThread::Get()->RegisterExtension(SafeBuiltins::CreateV8Extension());
+
+ // WebSecurityPolicy whitelists. They should be registered for both
+ // chrome-extension: and chrome-extension-resource.
+ using RegisterFunction = void (*)(const WebString&);
+ RegisterFunction register_functions[] = {
+ // Treat as secure because communication with them is entirely in the
+ // browser, so there is no danger of manipulation or eavesdropping on
+ // communication with them by third parties.
+ WebSecurityPolicy::registerURLSchemeAsSecure,
+ // As far as Blink is concerned, they should be allowed to receive CORS
+ // requests. At the Extensions layer, requests will actually be blocked
+ // unless overridden by the web_accessible_resources manifest key.
+ // TODO(kalman): See what happens with a service worker.
+ WebSecurityPolicy::registerURLSchemeAsCORSEnabled,
+ // Resources should bypass Content Security Policy checks when included in
+ // protected resources. TODO(kalman): What are "protected resources"?
+ WebSecurityPolicy::registerURLSchemeAsBypassingContentSecurityPolicy,
+ // Extension resources are HTTP-like and safe to expose to the fetch API.
+ // The rules for the fetch API are consistent with XHR.
+ WebSecurityPolicy::registerURLSchemeAsSupportingFetchAPI,
+ // Extension resources, when loaded as the top-level document, should
+ // bypass Blink's strict first-party origin checks.
+ WebSecurityPolicy::registerURLSchemeAsFirstPartyWhenTopLevel,
+ };
+
+ WebString extension_scheme(base::ASCIIToUTF16(kExtensionScheme));
+ WebString extension_resource_scheme(base::ASCIIToUTF16(
+ kExtensionResourceScheme));
+ for (RegisterFunction func : register_functions) {
+ func(extension_scheme);
+ func(extension_resource_scheme);
+ }
+
+ // For extensions, we want to ensure we call the IdleHandler every so often,
+ // even if the extension keeps up activity.
+ if (set_idle_notifications_) {
+ forced_idle_timer_.reset(new base::RepeatingTimer);
+ forced_idle_timer_->Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kMaxExtensionIdleHandlerDelayMs),
+ RenderThread::Get(),
+ &RenderThread::IdleHandler);
+ }
+
+ // Initialize host permissions for any extensions that were activated before
+ // WebKit was initialized.
+ for (const std::string& extension_id : active_extension_ids_) {
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(extension_id);
+ CHECK(extension);
+ InitOriginPermissions(extension);
+ }
+
+ EnableCustomElementWhiteList();
+}
+
+Dispatcher::~Dispatcher() {
+}
+
+void Dispatcher::OnRenderFrameCreated(content::RenderFrame* render_frame) {
+ script_injection_manager_->OnRenderFrameCreated(render_frame);
+}
+
+bool Dispatcher::IsExtensionActive(const std::string& extension_id) const {
+ bool is_active =
+ active_extension_ids_.find(extension_id) != active_extension_ids_.end();
+ if (is_active)
+ CHECK(RendererExtensionRegistry::Get()->Contains(extension_id));
+ return is_active;
+}
+
+void Dispatcher::DidCreateScriptContext(
+ blink::WebLocalFrame* frame,
+ const v8::Local<v8::Context>& v8_context,
+ int extension_group,
+ int world_id) {
+ const base::TimeTicks start_time = base::TimeTicks::Now();
+
+ ScriptContext* context = script_context_set_->Register(
+ frame, v8_context, extension_group, world_id);
+
+ // Initialize origin permissions for content scripts, which can't be
+ // initialized in |OnActivateExtension|.
+ if (context->context_type() == Feature::CONTENT_SCRIPT_CONTEXT)
+ InitOriginPermissions(context->extension());
+
+ {
+ scoped_ptr<ModuleSystem> module_system(
+ new ModuleSystem(context, &source_map_));
+ context->set_module_system(std::move(module_system));
+ }
+ ModuleSystem* module_system = context->module_system();
+
+ // Enable natives in startup.
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(module_system);
+
+ RegisterNativeHandlers(module_system, context);
+
+ // chrome.Event is part of the public API (although undocumented). Make it
+ // lazily evalulate to Event from event_bindings.js. For extensions only
+ // though, not all webpages!
+ if (context->extension()) {
+ v8::Local<v8::Object> chrome = AsObjectOrEmpty(GetOrCreateChrome(context));
+ if (!chrome.IsEmpty())
+ module_system->SetLazyField(chrome, "Event", kEventBindings, "Event");
+ }
+
+ UpdateBindingsForContext(context);
+
+ bool is_within_platform_app = IsWithinPlatformApp();
+ // Inject custom JS into the platform app context.
+ if (is_within_platform_app) {
+ module_system->Require("platformApp");
+ }
+
+ RequireGuestViewModules(context);
+ delegate_->RequireAdditionalModules(context, is_within_platform_app);
+
+ const base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
+ switch (context->context_type()) {
+ case Feature::UNSPECIFIED_CONTEXT:
+ UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_Unspecified",
+ elapsed);
+ break;
+ case Feature::BLESSED_EXTENSION_CONTEXT:
+ UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_Blessed", elapsed);
+ break;
+ case Feature::UNBLESSED_EXTENSION_CONTEXT:
+ UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_Unblessed",
+ elapsed);
+ break;
+ case Feature::CONTENT_SCRIPT_CONTEXT:
+ UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_ContentScript",
+ elapsed);
+ break;
+ case Feature::WEB_PAGE_CONTEXT:
+ UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_WebPage", elapsed);
+ break;
+ case Feature::BLESSED_WEB_PAGE_CONTEXT:
+ UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_BlessedWebPage",
+ elapsed);
+ break;
+ case Feature::WEBUI_CONTEXT:
+ UMA_HISTOGRAM_TIMES("Extensions.DidCreateScriptContext_WebUI", elapsed);
+ break;
+ case Feature::SERVICE_WORKER_CONTEXT:
+ // Handled in DidInitializeServiceWorkerContextOnWorkerThread().
+ NOTREACHED();
+ break;
+ }
+
+ VLOG(1) << "Num tracked contexts: " << script_context_set_->size();
+}
+
+// static
+void Dispatcher::DidInitializeServiceWorkerContextOnWorkerThread(
+ v8::Local<v8::Context> v8_context,
+ const GURL& url) {
+ const base::TimeTicks start_time = base::TimeTicks::Now();
+
+ if (!url.SchemeIs(kExtensionScheme) &&
+ !url.SchemeIs(kExtensionResourceScheme)) {
+ // Early-out if this isn't a chrome-extension:// or resource scheme,
+ // because looking up the extension registry is unnecessary if it's not.
+ // Checking this will also skip over hosted apps, which is the desired
+ // behavior - hosted app service workers are not our concern.
+ return;
+ }
+
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetExtensionOrAppByURL(url);
+
+ if (!extension) {
+ // TODO(kalman): This is no good. Instead we need to either:
+ //
+ // - Hold onto the v8::Context and create the ScriptContext and install
+ // our bindings when this extension is loaded.
+ // - Deal with there being an extension ID (url.host()) but no
+ // extension associated with it, then document that getBackgroundClient
+ // may fail if the extension hasn't loaded yet.
+ //
+ // The former is safer, but is unfriendly to caching (e.g. session restore).
+ // It seems to contradict the service worker idiom.
+ //
+ // The latter is friendly to caching, but running extension code without an
+ // installed extension makes me nervous, and means that we won't be able to
+ // expose arbitrary (i.e. capability-checked) extension APIs to service
+ // workers. We will probably need to relax some assertions - we just need
+ // to find them.
+ //
+ // Perhaps this could be solved with our own event on the service worker
+ // saying that an extension is ready, and documenting that extension APIs
+ // won't work before that event has fired?
+ return;
+ }
+
+ ScriptContext* context = new ScriptContext(
+ v8_context, nullptr, extension, Feature::SERVICE_WORKER_CONTEXT,
+ extension, Feature::SERVICE_WORKER_CONTEXT);
+ context->set_url(url);
+
+ g_worker_script_context_set.Get().Insert(make_scoped_ptr(context));
+
+ v8::Isolate* isolate = context->isolate();
+
+ // Fetch the source code for service_worker_bindings.js.
+ base::StringPiece script_resource =
+ ResourceBundle::GetSharedInstance().GetRawDataResource(
+ IDR_SERVICE_WORKER_BINDINGS_JS);
+ v8::Local<v8::String> script = v8::String::NewExternal(
+ isolate, new StaticV8ExternalOneByteStringResource(script_resource));
+
+ // Run service_worker.js to get the main function.
+ v8::Local<v8::Function> main_function;
+ {
+ v8::Local<v8::Value> result = context->RunScript(
+ v8_helpers::ToV8StringUnsafe(isolate, "service_worker"), script,
+ base::Bind(&CrashOnException));
+ CHECK(result->IsFunction());
+ main_function = result.As<v8::Function>();
+ }
+
+ // Expose CHECK/DCHECK/NOTREACHED to the main function with a
+ // LoggingNativeHandler. Admire the neat base::Bind trick to both Invalidate
+ // and delete the native handler.
+ LoggingNativeHandler* logging = new LoggingNativeHandler(context);
+ context->AddInvalidationObserver(
+ base::Bind(&NativeHandler::Invalidate, base::Owned(logging)));
+
+ // Execute the main function with its dependencies passed in as arguments.
+ v8::Local<v8::Value> args[] = {
+ // The extension's background URL.
+ v8_helpers::ToV8StringUnsafe(
+ isolate, BackgroundInfo::GetBackgroundURL(extension).spec()),
+ // The wake-event-page native function.
+ WakeEventPage::Get()->GetForContext(context),
+ // The logging module.
+ logging->NewInstance(),
+ };
+ context->CallFunction(main_function, arraysize(args), args);
+
+ const base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
+ UMA_HISTOGRAM_TIMES(
+ "Extensions.DidInitializeServiceWorkerContextOnWorkerThread", elapsed);
+}
+
+void Dispatcher::WillReleaseScriptContext(
+ blink::WebLocalFrame* frame,
+ const v8::Local<v8::Context>& v8_context,
+ int world_id) {
+ ScriptContext* context = script_context_set_->GetByV8Context(v8_context);
+ if (!context)
+ return;
+
+ // TODO(kalman): Make |request_sender| use |context->AddInvalidationObserver|.
+ // In fact |request_sender_| should really be owned by ScriptContext.
+ request_sender_->InvalidateSource(context);
+
+ script_context_set_->Remove(context);
+ VLOG(1) << "Num tracked contexts: " << script_context_set_->size();
+}
+
+// static
+void Dispatcher::WillDestroyServiceWorkerContextOnWorkerThread(
+ v8::Local<v8::Context> v8_context,
+ const GURL& url) {
+ if (url.SchemeIs(kExtensionScheme) ||
+ url.SchemeIs(kExtensionResourceScheme)) {
+ // See comment in DidInitializeServiceWorkerContextOnWorkerThread.
+ g_worker_script_context_set.Get().Remove(v8_context, url);
+ }
+}
+
+void Dispatcher::DidCreateDocumentElement(blink::WebLocalFrame* frame) {
+ // Note: use GetEffectiveDocumentURL not just frame->document()->url()
+ // so that this also injects the stylesheet on about:blank frames that
+ // are hosted in the extension process.
+ GURL effective_document_url = ScriptContext::GetEffectiveDocumentURL(
+ frame, frame->document().url(), true /* match_about_blank */);
+
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetExtensionOrAppByURL(
+ effective_document_url);
+
+ if (extension &&
+ (extension->is_extension() || extension->is_platform_app())) {
+ int resource_id = extension->is_platform_app() ? IDR_PLATFORM_APP_CSS
+ : IDR_EXTENSION_FONTS_CSS;
+ std::string stylesheet = ResourceBundle::GetSharedInstance()
+ .GetRawDataResource(resource_id)
+ .as_string();
+ base::ReplaceFirstSubstringAfterOffset(
+ &stylesheet, 0, "$FONTFAMILY", system_font_family_);
+ base::ReplaceFirstSubstringAfterOffset(
+ &stylesheet, 0, "$FONTSIZE", system_font_size_);
+
+ // Blink doesn't let us define an additional user agent stylesheet, so
+ // we insert the default platform app or extension stylesheet into all
+ // documents that are loaded in each app or extension.
+ frame->document().insertStyleSheet(WebString::fromUTF8(stylesheet));
+ }
+
+ // If this is an extension options page, and the extension has opted into
+ // using Chrome styles, then insert the Chrome extension stylesheet.
+ if (extension && extension->is_extension() &&
+ OptionsPageInfo::ShouldUseChromeStyle(extension) &&
+ effective_document_url == OptionsPageInfo::GetOptionsPage(extension)) {
+ frame->document().insertStyleSheet(
+ WebString::fromUTF8(ResourceBundle::GetSharedInstance()
+ .GetRawDataResource(IDR_EXTENSION_CSS)
+ .as_string()));
+ }
+
+ // In testing, the document lifetime events can happen after the render
+ // process shutdown event.
+ // See: http://crbug.com/21508 and http://crbug.com/500851
+ if (content_watcher_) {
+ content_watcher_->DidCreateDocumentElement(frame);
+ }
+}
+
+void Dispatcher::RunScriptsAtDocumentStart(content::RenderFrame* render_frame) {
+ ExtensionFrameHelper* frame_helper = ExtensionFrameHelper::Get(render_frame);
+ if (!frame_helper)
+ return; // The frame is invisible to extensions.
+
+ frame_helper->RunScriptsAtDocumentStart();
+ // |frame_helper| and |render_frame| might be dead by now.
+}
+
+void Dispatcher::RunScriptsAtDocumentEnd(content::RenderFrame* render_frame) {
+ ExtensionFrameHelper* frame_helper = ExtensionFrameHelper::Get(render_frame);
+ if (!frame_helper)
+ return; // The frame is invisible to extensions.
+
+ frame_helper->RunScriptsAtDocumentEnd();
+ // |frame_helper| and |render_frame| might be dead by now.
+}
+
+void Dispatcher::OnExtensionResponse(int request_id,
+ bool success,
+ const base::ListValue& response,
+ const std::string& error) {
+ request_sender_->HandleResponse(request_id, success, response, error);
+}
+
+void Dispatcher::DispatchEvent(const std::string& extension_id,
+ const std::string& event_name) const {
+ base::ListValue args;
+ args.Set(0, new base::StringValue(event_name));
+ args.Set(1, new base::ListValue());
+
+ // Needed for Windows compilation, since kEventBindings is declared extern.
+ const char* local_event_bindings = kEventBindings;
+ script_context_set_->ForEach(
+ extension_id, base::Bind(&CallModuleMethod, local_event_bindings,
+ kEventDispatchFunction, &args));
+}
+
+void Dispatcher::InvokeModuleSystemMethod(content::RenderFrame* render_frame,
+ const std::string& extension_id,
+ const std::string& module_name,
+ const std::string& function_name,
+ const base::ListValue& args,
+ bool user_gesture) {
+ scoped_ptr<WebScopedUserGesture> web_user_gesture;
+ if (user_gesture)
+ web_user_gesture.reset(new WebScopedUserGesture);
+
+ script_context_set_->ForEach(
+ extension_id, render_frame,
+ base::Bind(&CallModuleMethod, module_name, function_name, &args));
+
+ // Reset the idle handler each time there's any activity like event or message
+ // dispatch, for which Invoke is the chokepoint.
+ if (set_idle_notifications_) {
+ RenderThread::Get()->ScheduleIdleHandler(
+ kInitialExtensionIdleHandlerDelayMs);
+ }
+
+ // Tell the browser process when an event has been dispatched with a lazy
+ // background page active.
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(extension_id);
+ if (extension && BackgroundInfo::HasLazyBackgroundPage(extension) &&
+ module_name == kEventBindings &&
+ function_name == kEventDispatchFunction) {
+ content::RenderFrame* background_frame =
+ ExtensionFrameHelper::GetBackgroundPageFrame(extension_id);
+ if (background_frame) {
+ int message_id;
+ args.GetInteger(3, &message_id);
+ background_frame->Send(new ExtensionHostMsg_EventAck(
+ background_frame->GetRoutingID(), message_id));
+ }
+ }
+}
+
+void Dispatcher::ClearPortData(int port_id) {
+ // Only the target port side has entries in |port_to_tab_id_map_|. If
+ // |port_id| is a source port, std::map::erase() will just silently fail
+ // here as a no-op.
+ port_to_tab_id_map_.erase(port_id);
+}
+
+// static
+std::vector<std::pair<std::string, int> > Dispatcher::GetJsResources() {
+ std::vector<std::pair<std::string, int> > resources;
+
+ // Libraries.
+ resources.push_back(std::make_pair("appView", IDR_APP_VIEW_JS));
+ resources.push_back(std::make_pair("entryIdManager", IDR_ENTRY_ID_MANAGER));
+ resources.push_back(std::make_pair(kEventBindings, IDR_EVENT_BINDINGS_JS));
+ resources.push_back(std::make_pair("extensionOptions",
+ IDR_EXTENSION_OPTIONS_JS));
+ resources.push_back(std::make_pair("extensionOptionsAttributes",
+ IDR_EXTENSION_OPTIONS_ATTRIBUTES_JS));
+ resources.push_back(std::make_pair("extensionOptionsConstants",
+ IDR_EXTENSION_OPTIONS_CONSTANTS_JS));
+ resources.push_back(std::make_pair("extensionOptionsEvents",
+ IDR_EXTENSION_OPTIONS_EVENTS_JS));
+ resources.push_back(std::make_pair("extensionView", IDR_EXTENSION_VIEW_JS));
+ resources.push_back(std::make_pair("extensionViewApiMethods",
+ IDR_EXTENSION_VIEW_API_METHODS_JS));
+ resources.push_back(std::make_pair("extensionViewAttributes",
+ IDR_EXTENSION_VIEW_ATTRIBUTES_JS));
+ resources.push_back(std::make_pair("extensionViewConstants",
+ IDR_EXTENSION_VIEW_CONSTANTS_JS));
+ resources.push_back(std::make_pair("extensionViewEvents",
+ IDR_EXTENSION_VIEW_EVENTS_JS));
+ resources.push_back(std::make_pair(
+ "extensionViewInternal", IDR_EXTENSION_VIEW_INTERNAL_CUSTOM_BINDINGS_JS));
+ resources.push_back(std::make_pair("guestView", IDR_GUEST_VIEW_JS));
+ resources.push_back(std::make_pair("guestViewAttributes",
+ IDR_GUEST_VIEW_ATTRIBUTES_JS));
+ resources.push_back(std::make_pair("guestViewContainer",
+ IDR_GUEST_VIEW_CONTAINER_JS));
+ resources.push_back(std::make_pair("guestViewDeny", IDR_GUEST_VIEW_DENY_JS));
+ resources.push_back(std::make_pair("guestViewEvents",
+ IDR_GUEST_VIEW_EVENTS_JS));
+
+ if (content::BrowserPluginGuestMode::UseCrossProcessFramesForGuests()) {
+ resources.push_back(std::make_pair("guestViewIframe",
+ IDR_GUEST_VIEW_IFRAME_JS));
+ resources.push_back(std::make_pair("guestViewIframeContainer",
+ IDR_GUEST_VIEW_IFRAME_CONTAINER_JS));
+ }
+
+ resources.push_back(std::make_pair("imageUtil", IDR_IMAGE_UTIL_JS));
+ resources.push_back(std::make_pair("json_schema", IDR_JSON_SCHEMA_JS));
+ resources.push_back(std::make_pair("lastError", IDR_LAST_ERROR_JS));
+ resources.push_back(std::make_pair("messaging", IDR_MESSAGING_JS));
+ resources.push_back(std::make_pair("messaging_utils",
+ IDR_MESSAGING_UTILS_JS));
+ resources.push_back(std::make_pair(kSchemaUtils, IDR_SCHEMA_UTILS_JS));
+ resources.push_back(std::make_pair("sendRequest", IDR_SEND_REQUEST_JS));
+ resources.push_back(std::make_pair("setIcon", IDR_SET_ICON_JS));
+ resources.push_back(std::make_pair("test", IDR_TEST_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("test_environment_specific_bindings",
+ IDR_BROWSER_TEST_ENVIRONMENT_SPECIFIC_BINDINGS_JS));
+ resources.push_back(std::make_pair("uncaught_exception_handler",
+ IDR_UNCAUGHT_EXCEPTION_HANDLER_JS));
+ resources.push_back(std::make_pair("utils", IDR_UTILS_JS));
+ resources.push_back(std::make_pair("webRequest",
+ IDR_WEB_REQUEST_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("webRequestInternal",
+ IDR_WEB_REQUEST_INTERNAL_CUSTOM_BINDINGS_JS));
+ // Note: webView not webview so that this doesn't interfere with the
+ // chrome.webview API bindings.
+ resources.push_back(std::make_pair("webView", IDR_WEB_VIEW_JS));
+ resources.push_back(std::make_pair("webViewActionRequests",
+ IDR_WEB_VIEW_ACTION_REQUESTS_JS));
+ resources.push_back(std::make_pair("webViewApiMethods",
+ IDR_WEB_VIEW_API_METHODS_JS));
+ resources.push_back(std::make_pair("webViewAttributes",
+ IDR_WEB_VIEW_ATTRIBUTES_JS));
+ resources.push_back(std::make_pair("webViewConstants",
+ IDR_WEB_VIEW_CONSTANTS_JS));
+ resources.push_back(std::make_pair("webViewEvents", IDR_WEB_VIEW_EVENTS_JS));
+ resources.push_back(std::make_pair("webViewInternal",
+ IDR_WEB_VIEW_INTERNAL_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("webViewExperimental", IDR_WEB_VIEW_EXPERIMENTAL_JS));
+ resources.push_back(
+ std::make_pair(mojo::kBindingsModuleName, IDR_MOJO_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair(mojo::kBufferModuleName, IDR_MOJO_BUFFER_JS));
+ resources.push_back(
+ std::make_pair(mojo::kCodecModuleName, IDR_MOJO_CODEC_JS));
+ resources.push_back(
+ std::make_pair(mojo::kConnectionModuleName, IDR_MOJO_CONNECTION_JS));
+ resources.push_back(
+ std::make_pair(mojo::kConnectorModuleName, IDR_MOJO_CONNECTOR_JS));
+ resources.push_back(
+ std::make_pair(mojo::kRouterModuleName, IDR_MOJO_ROUTER_JS));
+ resources.push_back(
+ std::make_pair(mojo::kUnicodeModuleName, IDR_MOJO_UNICODE_JS));
+ resources.push_back(
+ std::make_pair(mojo::kValidatorModuleName, IDR_MOJO_VALIDATOR_JS));
+ resources.push_back(std::make_pair("async_waiter", IDR_ASYNC_WAITER_JS));
+ resources.push_back(std::make_pair("data_receiver", IDR_DATA_RECEIVER_JS));
+ resources.push_back(std::make_pair("data_sender", IDR_DATA_SENDER_JS));
+ resources.push_back(std::make_pair("keep_alive", IDR_KEEP_ALIVE_JS));
+ resources.push_back(std::make_pair("extensions/common/mojo/keep_alive.mojom",
+ IDR_KEEP_ALIVE_MOJOM_JS));
+ resources.push_back(std::make_pair("device/serial/data_stream.mojom",
+ IDR_DATA_STREAM_MOJOM_JS));
+ resources.push_back(
+ std::make_pair("device/serial/data_stream_serialization.mojom",
+ IDR_DATA_STREAM_SERIALIZATION_MOJOM_JS));
+ resources.push_back(std::make_pair("stash_client", IDR_STASH_CLIENT_JS));
+ resources.push_back(
+ std::make_pair("extensions/common/mojo/stash.mojom", IDR_STASH_MOJOM_JS));
+
+ // Custom bindings.
+ resources.push_back(
+ std::make_pair("app.runtime", IDR_APP_RUNTIME_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("app.window", IDR_APP_WINDOW_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("declarativeWebRequest",
+ IDR_DECLARATIVE_WEBREQUEST_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("displaySource",
+ IDR_DISPLAY_SOURCE_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("contextMenus", IDR_CONTEXT_MENUS_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("contextMenusHandlers", IDR_CONTEXT_MENUS_HANDLERS_JS));
+ resources.push_back(
+ std::make_pair("extension", IDR_EXTENSION_CUSTOM_BINDINGS_JS));
+ resources.push_back(std::make_pair("i18n", IDR_I18N_CUSTOM_BINDINGS_JS));
+ resources.push_back(std::make_pair(
+ "mimeHandlerPrivate", IDR_MIME_HANDLER_PRIVATE_CUSTOM_BINDINGS_JS));
+ resources.push_back(std::make_pair("extensions/common/api/mime_handler.mojom",
+ IDR_MIME_HANDLER_MOJOM_JS));
+ resources.push_back(
+ std::make_pair("mojoPrivate", IDR_MOJO_PRIVATE_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("permissions", IDR_PERMISSIONS_CUSTOM_BINDINGS_JS));
+ resources.push_back(std::make_pair("printerProvider",
+ IDR_PRINTER_PROVIDER_CUSTOM_BINDINGS_JS));
+ resources.push_back(
+ std::make_pair("runtime", IDR_RUNTIME_CUSTOM_BINDINGS_JS));
+ resources.push_back(std::make_pair("windowControls", IDR_WINDOW_CONTROLS_JS));
+ resources.push_back(
+ std::make_pair("webViewRequest",
+ IDR_WEB_VIEW_REQUEST_CUSTOM_BINDINGS_JS));
+ resources.push_back(std::make_pair("binding", IDR_BINDING_JS));
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableMojoSerialService)) {
+ resources.push_back(
+ std::make_pair("serial", IDR_SERIAL_CUSTOM_BINDINGS_JS));
+ }
+ resources.push_back(std::make_pair("serial_service", IDR_SERIAL_SERVICE_JS));
+ resources.push_back(
+ std::make_pair("device/serial/serial.mojom", IDR_SERIAL_MOJOM_JS));
+ resources.push_back(std::make_pair("device/serial/serial_serialization.mojom",
+ IDR_SERIAL_SERIALIZATION_MOJOM_JS));
+
+ // Custom types sources.
+ resources.push_back(std::make_pair("StorageArea", IDR_STORAGE_AREA_JS));
+
+ // Platform app sources that are not API-specific..
+ resources.push_back(std::make_pair("platformApp", IDR_PLATFORM_APP_JS));
+
+#if defined(ENABLE_MEDIA_ROUTER)
+ resources.push_back(
+ std::make_pair("chrome/browser/media/router/mojo/media_router.mojom",
+ IDR_MEDIA_ROUTER_MOJOM_JS));
+ resources.push_back(
+ std::make_pair("media_router_bindings", IDR_MEDIA_ROUTER_BINDINGS_JS));
+#endif // defined(ENABLE_MEDIA_ROUTER)
+
+ return resources;
+}
+
+// NOTE: please use the naming convention "foo_natives" for these.
+// static
+void Dispatcher::RegisterNativeHandlers(ModuleSystem* module_system,
+ ScriptContext* context,
+ Dispatcher* dispatcher,
+ RequestSender* request_sender,
+ V8SchemaRegistry* v8_schema_registry) {
+ module_system->RegisterNativeHandler(
+ "chrome", scoped_ptr<NativeHandler>(new ChromeNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "lazy_background_page",
+ scoped_ptr<NativeHandler>(new LazyBackgroundPageNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "logging", scoped_ptr<NativeHandler>(new LoggingNativeHandler(context)));
+ module_system->RegisterNativeHandler("schema_registry",
+ v8_schema_registry->AsNativeHandler());
+ module_system->RegisterNativeHandler(
+ "print", scoped_ptr<NativeHandler>(new PrintNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "test_features",
+ scoped_ptr<NativeHandler>(new TestFeaturesNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "test_native_handler",
+ scoped_ptr<NativeHandler>(new TestNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "user_gestures",
+ scoped_ptr<NativeHandler>(new UserGesturesNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "utils", scoped_ptr<NativeHandler>(new UtilsNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "v8_context",
+ scoped_ptr<NativeHandler>(new V8ContextNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "event_natives", scoped_ptr<NativeHandler>(new EventBindings(context)));
+ module_system->RegisterNativeHandler(
+ "messaging_natives",
+ scoped_ptr<NativeHandler>(MessagingBindings::Get(dispatcher, context)));
+ module_system->RegisterNativeHandler(
+ "apiDefinitions",
+ scoped_ptr<NativeHandler>(
+ new ApiDefinitionsNatives(dispatcher, context)));
+ module_system->RegisterNativeHandler(
+ "sendRequest",
+ scoped_ptr<NativeHandler>(
+ new SendRequestNatives(request_sender, context)));
+ module_system->RegisterNativeHandler(
+ "setIcon",
+ scoped_ptr<NativeHandler>(new SetIconNatives(context)));
+ module_system->RegisterNativeHandler(
+ "activityLogger",
+ scoped_ptr<NativeHandler>(new APIActivityLogger(context)));
+ module_system->RegisterNativeHandler(
+ "renderFrameObserverNatives",
+ scoped_ptr<NativeHandler>(new RenderFrameObserverNatives(context)));
+
+ // Natives used by multiple APIs.
+ module_system->RegisterNativeHandler(
+ "file_system_natives",
+ scoped_ptr<NativeHandler>(new FileSystemNatives(context)));
+
+ // Custom bindings.
+ module_system->RegisterNativeHandler(
+ "app_window_natives",
+ scoped_ptr<NativeHandler>(new AppWindowCustomBindings(context)));
+ module_system->RegisterNativeHandler(
+ "blob_natives",
+ scoped_ptr<NativeHandler>(new BlobNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "context_menus",
+ scoped_ptr<NativeHandler>(new ContextMenusCustomBindings(context)));
+ module_system->RegisterNativeHandler(
+ "css_natives", scoped_ptr<NativeHandler>(new CssNativeHandler(context)));
+ module_system->RegisterNativeHandler(
+ "document_natives",
+ scoped_ptr<NativeHandler>(new DocumentCustomBindings(context)));
+ module_system->RegisterNativeHandler(
+ "guest_view_internal",
+ scoped_ptr<NativeHandler>(
+ new GuestViewInternalCustomBindings(context)));
+ module_system->RegisterNativeHandler(
+ "i18n", scoped_ptr<NativeHandler>(new I18NCustomBindings(context)));
+ module_system->RegisterNativeHandler(
+ "id_generator",
+ scoped_ptr<NativeHandler>(new IdGeneratorCustomBindings(context)));
+ module_system->RegisterNativeHandler(
+ "runtime", scoped_ptr<NativeHandler>(new RuntimeCustomBindings(context)));
+ module_system->RegisterNativeHandler(
+ "display_source",
+ scoped_ptr<NativeHandler>(new DisplaySourceCustomBindings(context)));
+}
+
+bool Dispatcher::OnControlMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(Dispatcher, message)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_ActivateExtension, OnActivateExtension)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_CancelSuspend, OnCancelSuspend)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_DeliverMessage, OnDeliverMessage)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnConnect, OnDispatchOnConnect)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnDisconnect, OnDispatchOnDisconnect)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_Loaded, OnLoaded)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_MessageInvoke, OnMessageInvoke)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_SetChannel, OnSetChannel)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_SetScriptingWhitelist,
+ OnSetScriptingWhitelist)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_SetSystemFont, OnSetSystemFont)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_SetWebViewPartitionID,
+ OnSetWebViewPartitionID)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_ShouldSuspend, OnShouldSuspend)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_Suspend, OnSuspend)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_TransferBlobs, OnTransferBlobs)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_Unloaded, OnUnloaded)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_UpdatePermissions, OnUpdatePermissions)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateTabSpecificPermissions,
+ OnUpdateTabSpecificPermissions)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_ClearTabSpecificPermissions,
+ OnClearTabSpecificPermissions)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_UsingWebRequestAPI, OnUsingWebRequestAPI)
+ IPC_MESSAGE_FORWARD(ExtensionMsg_WatchPages,
+ content_watcher_.get(),
+ ContentWatcher::OnWatchPages)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+
+ return handled;
+}
+void Dispatcher::IdleNotification() {
+ if (set_idle_notifications_ && forced_idle_timer_) {
+ // Dampen the forced delay as well if the extension stays idle for long
+ // periods of time. (forced_idle_timer_ can be NULL after
+ // OnRenderProcessShutdown has been called.)
+ int64_t forced_delay_ms =
+ std::max(RenderThread::Get()->GetIdleNotificationDelayInMs(),
+ kMaxExtensionIdleHandlerDelayMs);
+ forced_idle_timer_->Stop();
+ forced_idle_timer_->Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(forced_delay_ms),
+ RenderThread::Get(),
+ &RenderThread::IdleHandler);
+ }
+}
+
+void Dispatcher::OnRenderProcessShutdown() {
+ v8_schema_registry_.reset();
+ forced_idle_timer_.reset();
+ content_watcher_.reset();
+ script_context_set_->ForEach(
+ std::string(), nullptr,
+ base::Bind(&ScriptContextSet::Remove,
+ base::Unretained(script_context_set_.get())));
+}
+
+void Dispatcher::OnActivateExtension(const std::string& extension_id) {
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(extension_id);
+ if (!extension) {
+ // Extension was activated but was never loaded. This probably means that
+ // the renderer failed to load it (or the browser failed to tell us when it
+ // did). Failures shouldn't happen, but instead of crashing there (which
+ // executes on all renderers) be conservative and only crash in the renderer
+ // of the extension which failed to load; this one.
+ std::string& error = extension_load_errors_[extension_id];
+ char minidump[256];
+ base::debug::Alias(&minidump);
+ base::snprintf(minidump,
+ arraysize(minidump),
+ "e::dispatcher:%s:%s",
+ extension_id.c_str(),
+ error.c_str());
+ LOG(FATAL) << extension_id << " was never loaded: " << error;
+ }
+
+ active_extension_ids_.insert(extension_id);
+
+ // This is called when starting a new extension page, so start the idle
+ // handler ticking.
+ RenderThread::Get()->ScheduleIdleHandler(kInitialExtensionIdleHandlerDelayMs);
+
+ DOMActivityLogger::AttachToWorld(
+ DOMActivityLogger::kMainWorldId, extension_id);
+
+ InitOriginPermissions(extension);
+
+ UpdateActiveExtensions();
+}
+
+void Dispatcher::OnCancelSuspend(const std::string& extension_id) {
+ DispatchEvent(extension_id, kOnSuspendCanceledEvent);
+}
+
+void Dispatcher::OnDeliverMessage(int target_port_id, const Message& message) {
+ scoped_ptr<RequestSender::ScopedTabID> scoped_tab_id;
+ std::map<int, int>::const_iterator it =
+ port_to_tab_id_map_.find(target_port_id);
+ if (it != port_to_tab_id_map_.end()) {
+ scoped_tab_id.reset(
+ new RequestSender::ScopedTabID(request_sender(), it->second));
+ }
+
+ MessagingBindings::DeliverMessage(*script_context_set_, target_port_id,
+ message,
+ NULL); // All render frames.
+}
+
+void Dispatcher::OnDispatchOnConnect(
+ int target_port_id,
+ const std::string& channel_name,
+ const ExtensionMsg_TabConnectionInfo& source,
+ const ExtensionMsg_ExternalConnectionInfo& info,
+ const std::string& tls_channel_id) {
+ DCHECK(!ContainsKey(port_to_tab_id_map_, target_port_id));
+ DCHECK_EQ(1, target_port_id % 2); // target renderer ports have odd IDs.
+ int sender_tab_id = -1;
+ source.tab.GetInteger("id", &sender_tab_id);
+ port_to_tab_id_map_[target_port_id] = sender_tab_id;
+
+ MessagingBindings::DispatchOnConnect(*script_context_set_, target_port_id,
+ channel_name, source, info,
+ tls_channel_id,
+ NULL); // All render frames.
+}
+
+void Dispatcher::OnDispatchOnDisconnect(int port_id,
+ const std::string& error_message) {
+ MessagingBindings::DispatchOnDisconnect(*script_context_set_, port_id,
+ error_message,
+ NULL); // All render frames.
+}
+
+void Dispatcher::OnLoaded(
+ const std::vector<ExtensionMsg_Loaded_Params>& loaded_extensions) {
+ for (const auto& param : loaded_extensions) {
+ std::string error;
+ scoped_refptr<const Extension> extension = param.ConvertToExtension(&error);
+ if (!extension.get()) {
+ NOTREACHED() << error;
+ // Note: in tests |param.id| has been observed to be empty (see comment
+ // just below) so this isn't all that reliable.
+ extension_load_errors_[param.id] = error;
+ continue;
+ }
+
+ RendererExtensionRegistry* extension_registry =
+ RendererExtensionRegistry::Get();
+ // TODO(kalman): This test is deliberately not a CHECK (though I wish it
+ // could be) and uses extension->id() not params.id:
+ // 1. For some reason params.id can be empty. I've only seen it with
+ // the webstore extension, in tests, and I've spent some time trying to
+ // figure out why - but cost/benefit won.
+ // 2. The browser only sends this IPC to RenderProcessHosts once, but the
+ // Dispatcher is attached to a RenderThread. Presumably there is a
+ // mismatch there. In theory one would think it's possible for the
+ // browser to figure this out itself - but again, cost/benefit.
+ if (!extension_registry->Contains(extension->id()))
+ extension_registry->Insert(extension);
+ }
+
+ // Update the available bindings for all contexts. These may have changed if
+ // an externally_connectable extension was loaded that can connect to an
+ // open webpage.
+ UpdateBindings("");
+}
+
+void Dispatcher::OnMessageInvoke(const std::string& extension_id,
+ const std::string& module_name,
+ const std::string& function_name,
+ const base::ListValue& args,
+ bool user_gesture) {
+ InvokeModuleSystemMethod(
+ NULL, extension_id, module_name, function_name, args, user_gesture);
+}
+
+void Dispatcher::OnSetChannel(int channel) {
+ delegate_->SetChannel(channel);
+}
+
+void Dispatcher::OnSetScriptingWhitelist(
+ const ExtensionsClient::ScriptingWhitelist& extension_ids) {
+ ExtensionsClient::Get()->SetScriptingWhitelist(extension_ids);
+}
+
+void Dispatcher::OnSetSystemFont(const std::string& font_family,
+ const std::string& font_size) {
+ system_font_family_ = font_family;
+ system_font_size_ = font_size;
+}
+
+void Dispatcher::OnSetWebViewPartitionID(const std::string& partition_id) {
+ // |webview_partition_id_| cannot be changed once set.
+ CHECK(webview_partition_id_.empty() || webview_partition_id_ == partition_id);
+ webview_partition_id_ = partition_id;
+}
+
+void Dispatcher::OnShouldSuspend(const std::string& extension_id,
+ uint64_t sequence_id) {
+ RenderThread::Get()->Send(
+ new ExtensionHostMsg_ShouldSuspendAck(extension_id, sequence_id));
+}
+
+void Dispatcher::OnSuspend(const std::string& extension_id) {
+ // Dispatch the suspend event. This doesn't go through the standard event
+ // dispatch machinery because it requires special handling. We need to let
+ // the browser know when we are starting and stopping the event dispatch, so
+ // that it still considers the extension idle despite any activity the suspend
+ // event creates.
+ DispatchEvent(extension_id, kOnSuspendEvent);
+ RenderThread::Get()->Send(new ExtensionHostMsg_SuspendAck(extension_id));
+}
+
+void Dispatcher::OnTransferBlobs(const std::vector<std::string>& blob_uuids) {
+ RenderThread::Get()->Send(new ExtensionHostMsg_TransferBlobsAck(blob_uuids));
+}
+
+void Dispatcher::OnUnloaded(const std::string& id) {
+ // See comment in OnLoaded for why it would be nice, but perhaps incorrect,
+ // to CHECK here rather than guarding.
+ if (!RendererExtensionRegistry::Get()->Remove(id))
+ return;
+
+ active_extension_ids_.erase(id);
+
+ script_injection_manager_->OnExtensionUnloaded(id);
+
+ // If the extension is later reloaded with a different set of permissions,
+ // we'd like it to get a new isolated world ID, so that it can pick up the
+ // changed origin whitelist.
+ ScriptInjection::RemoveIsolatedWorld(id);
+
+ // Invalidate all of the contexts that were removed.
+ // TODO(kalman): add an invalidation observer interface to ScriptContext.
+ std::set<ScriptContext*> removed_contexts =
+ script_context_set_->OnExtensionUnloaded(id);
+ for (ScriptContext* context : removed_contexts) {
+ request_sender_->InvalidateSource(context);
+ }
+
+ // Update the available bindings for the remaining contexts. These may have
+ // changed if an externally_connectable extension is unloaded and a webpage
+ // is no longer accessible.
+ UpdateBindings("");
+
+ // Invalidates the messages map for the extension in case the extension is
+ // reloaded with a new messages map.
+ EraseL10nMessagesMap(id);
+
+ // We don't do anything with existing platform-app stylesheets. They will
+ // stay resident, but the URL pattern corresponding to the unloaded
+ // extension's URL just won't match anything anymore.
+}
+
+void Dispatcher::OnUpdatePermissions(
+ const ExtensionMsg_UpdatePermissions_Params& params) {
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(params.extension_id);
+ if (!extension)
+ return;
+
+ scoped_ptr<const PermissionSet> active =
+ params.active_permissions.ToPermissionSet();
+ scoped_ptr<const PermissionSet> withheld =
+ params.withheld_permissions.ToPermissionSet();
+
+ UpdateOriginPermissions(
+ extension->url(),
+ extension->permissions_data()->GetEffectiveHostPermissions(),
+ active->effective_hosts());
+
+ extension->permissions_data()->SetPermissions(std::move(active),
+ std::move(withheld));
+ UpdateBindings(extension->id());
+}
+
+void Dispatcher::OnUpdateTabSpecificPermissions(const GURL& visible_url,
+ const std::string& extension_id,
+ const URLPatternSet& new_hosts,
+ bool update_origin_whitelist,
+ int tab_id) {
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(extension_id);
+ if (!extension)
+ return;
+
+ URLPatternSet old_effective =
+ extension->permissions_data()->GetEffectiveHostPermissions();
+ extension->permissions_data()->UpdateTabSpecificPermissions(
+ tab_id,
+ extensions::PermissionSet(extensions::APIPermissionSet(),
+ extensions::ManifestPermissionSet(), new_hosts,
+ extensions::URLPatternSet()));
+
+ if (update_origin_whitelist) {
+ UpdateOriginPermissions(
+ extension->url(),
+ old_effective,
+ extension->permissions_data()->GetEffectiveHostPermissions());
+ }
+}
+
+void Dispatcher::OnClearTabSpecificPermissions(
+ const std::vector<std::string>& extension_ids,
+ bool update_origin_whitelist,
+ int tab_id) {
+ for (const std::string& id : extension_ids) {
+ const Extension* extension = RendererExtensionRegistry::Get()->GetByID(id);
+ if (extension) {
+ URLPatternSet old_effective =
+ extension->permissions_data()->GetEffectiveHostPermissions();
+ extension->permissions_data()->ClearTabSpecificPermissions(tab_id);
+ if (update_origin_whitelist) {
+ UpdateOriginPermissions(
+ extension->url(),
+ old_effective,
+ extension->permissions_data()->GetEffectiveHostPermissions());
+ }
+ }
+ }
+}
+
+void Dispatcher::OnUsingWebRequestAPI(bool webrequest_used) {
+ webrequest_used_ = webrequest_used;
+}
+
+void Dispatcher::OnUserScriptsUpdated(const std::set<HostID>& changed_hosts,
+ const std::vector<UserScript*>& scripts) {
+ UpdateActiveExtensions();
+}
+
+void Dispatcher::UpdateActiveExtensions() {
+ std::set<std::string> active_extensions = active_extension_ids_;
+ user_script_set_manager_->GetAllActiveExtensionIds(&active_extensions);
+ delegate_->OnActiveExtensionsUpdated(active_extensions);
+}
+
+void Dispatcher::InitOriginPermissions(const Extension* extension) {
+ delegate_->InitOriginPermissions(extension,
+ IsExtensionActive(extension->id()));
+ UpdateOriginPermissions(
+ extension->url(),
+ URLPatternSet(), // No old permissions.
+ extension->permissions_data()->GetEffectiveHostPermissions());
+}
+
+void Dispatcher::UpdateOriginPermissions(const GURL& extension_url,
+ const URLPatternSet& old_patterns,
+ const URLPatternSet& new_patterns) {
+ static const char* kSchemes[] = {
+ url::kHttpScheme,
+ url::kHttpsScheme,
+ url::kFileScheme,
+ content::kChromeUIScheme,
+ url::kFtpScheme,
+#if defined(OS_CHROMEOS)
+ content::kExternalFileScheme,
+#endif
+ extensions::kExtensionScheme,
+ };
+ for (size_t i = 0; i < arraysize(kSchemes); ++i) {
+ const char* scheme = kSchemes[i];
+ // Remove all old patterns...
+ for (URLPatternSet::const_iterator pattern = old_patterns.begin();
+ pattern != old_patterns.end(); ++pattern) {
+ if (pattern->MatchesScheme(scheme)) {
+ WebSecurityPolicy::removeOriginAccessWhitelistEntry(
+ extension_url,
+ WebString::fromUTF8(scheme),
+ WebString::fromUTF8(pattern->host()),
+ pattern->match_subdomains());
+ }
+ }
+ // ...And add the new ones.
+ for (URLPatternSet::const_iterator pattern = new_patterns.begin();
+ pattern != new_patterns.end(); ++pattern) {
+ if (pattern->MatchesScheme(scheme)) {
+ WebSecurityPolicy::addOriginAccessWhitelistEntry(
+ extension_url,
+ WebString::fromUTF8(scheme),
+ WebString::fromUTF8(pattern->host()),
+ pattern->match_subdomains());
+ }
+ }
+ }
+}
+
+void Dispatcher::EnableCustomElementWhiteList() {
+ blink::WebCustomElement::addEmbedderCustomElementName("appview");
+ blink::WebCustomElement::addEmbedderCustomElementName("appviewbrowserplugin");
+ blink::WebCustomElement::addEmbedderCustomElementName("extensionoptions");
+ blink::WebCustomElement::addEmbedderCustomElementName(
+ "extensionoptionsbrowserplugin");
+ blink::WebCustomElement::addEmbedderCustomElementName("extensionview");
+ blink::WebCustomElement::addEmbedderCustomElementName(
+ "extensionviewbrowserplugin");
+ blink::WebCustomElement::addEmbedderCustomElementName("webview");
+ blink::WebCustomElement::addEmbedderCustomElementName("webviewbrowserplugin");
+}
+
+void Dispatcher::UpdateBindings(const std::string& extension_id) {
+ script_context_set().ForEach(extension_id,
+ base::Bind(&Dispatcher::UpdateBindingsForContext,
+ base::Unretained(this)));
+}
+
+void Dispatcher::UpdateBindingsForContext(ScriptContext* context) {
+ v8::HandleScope handle_scope(context->isolate());
+ v8::Context::Scope context_scope(context->v8_context());
+
+ // TODO(kalman): Make the bindings registration have zero overhead then run
+ // the same code regardless of context type.
+ switch (context->context_type()) {
+ case Feature::UNSPECIFIED_CONTEXT:
+ case Feature::WEB_PAGE_CONTEXT:
+ case Feature::BLESSED_WEB_PAGE_CONTEXT:
+ // Hard-code registration of any APIs that are exposed to webpage-like
+ // contexts, because it's too expensive to run the full bindings code.
+ // All of the same permission checks will still apply.
+ if (context->GetAvailability("app").is_available())
+ RegisterBinding("app", context);
+ if (context->GetAvailability("webstore").is_available())
+ RegisterBinding("webstore", context);
+ if (context->GetAvailability("dashboardPrivate").is_available())
+ RegisterBinding("dashboardPrivate", context);
+ if (IsRuntimeAvailableToContext(context))
+ RegisterBinding("runtime", context);
+ UpdateContentCapabilities(context);
+ break;
+
+ case Feature::BLESSED_EXTENSION_CONTEXT:
+ case Feature::UNBLESSED_EXTENSION_CONTEXT:
+ case Feature::CONTENT_SCRIPT_CONTEXT:
+ case Feature::WEBUI_CONTEXT: {
+ // Extension context; iterate through all the APIs and bind the available
+ // ones.
+ const FeatureProvider* api_feature_provider =
+ FeatureProvider::GetAPIFeatures();
+ for (const auto& map_entry : api_feature_provider->GetAllFeatures()) {
+ // Internal APIs are included via require(api_name) from internal code
+ // rather than chrome[api_name].
+ if (map_entry.second->IsInternal())
+ continue;
+
+ // If this API has a parent feature (and isn't marked 'noparent'),
+ // then this must be a function or event, so we should not register.
+ if (api_feature_provider->GetParent(map_entry.second.get()) != nullptr)
+ continue;
+
+ // Skip chrome.test if this isn't a test.
+ if (map_entry.first == "test" &&
+ !base::CommandLine::ForCurrentProcess()->HasSwitch(
+ ::switches::kTestType)) {
+ continue;
+ }
+
+ if (context->IsAnyFeatureAvailableToContext(*map_entry.second.get()))
+ RegisterBinding(map_entry.first, context);
+ }
+ break;
+ }
+ case Feature::SERVICE_WORKER_CONTEXT:
+ // Handled in DidInitializeServiceWorkerContextOnWorkerThread().
+ NOTREACHED();
+ break;
+ }
+}
+
+void Dispatcher::RegisterBinding(const std::string& api_name,
+ ScriptContext* context) {
+ std::string bind_name;
+ v8::Local<v8::Object> bind_object =
+ GetOrCreateBindObjectIfAvailable(api_name, &bind_name, context);
+
+ // Empty if the bind object failed to be created, probably because the
+ // extension overrode chrome with a non-object, e.g. window.chrome = true.
+ if (bind_object.IsEmpty())
+ return;
+
+ v8::Local<v8::String> v8_bind_name =
+ v8::String::NewFromUtf8(context->isolate(), bind_name.c_str());
+ if (bind_object->HasRealNamedProperty(v8_bind_name)) {
+ // The bind object may already have the property if the API has been
+ // registered before (or if the extension has put something there already,
+ // but, whatevs).
+ //
+ // In the former case, we need to re-register the bindings for the APIs
+ // which the extension now has permissions for (if any), but not touch any
+ // others so that we don't destroy state such as event listeners.
+ //
+ // TODO(kalman): Only register available APIs to make this all moot.
+ if (bind_object->HasRealNamedCallbackProperty(v8_bind_name))
+ return; // lazy binding still there, nothing to do
+ if (bind_object->Get(v8_bind_name)->IsObject())
+ return; // binding has already been fully installed
+ }
+
+ ModuleSystem* module_system = context->module_system();
+ if (!source_map_.Contains(api_name)) {
+ module_system->RegisterNativeHandler(
+ api_name,
+ scoped_ptr<NativeHandler>(new BindingGeneratingNativeHandler(
+ context, api_name, "binding")));
+ module_system->SetNativeLazyField(
+ bind_object, bind_name, api_name, "binding");
+ } else {
+ module_system->SetLazyField(bind_object, bind_name, api_name, "binding");
+ }
+}
+
+// NOTE: please use the naming convention "foo_natives" for these.
+void Dispatcher::RegisterNativeHandlers(ModuleSystem* module_system,
+ ScriptContext* context) {
+ RegisterNativeHandlers(module_system,
+ context,
+ this,
+ request_sender_.get(),
+ v8_schema_registry_.get());
+ const Extension* extension = context->extension();
+ int manifest_version = extension ? extension->manifest_version() : 1;
+ bool is_component_extension =
+ extension && Manifest::IsComponentLocation(extension->location());
+ bool send_request_disabled =
+ (extension && Manifest::IsUnpackedLocation(extension->location()) &&
+ BackgroundInfo::HasLazyBackgroundPage(extension));
+ module_system->RegisterNativeHandler(
+ "process",
+ scoped_ptr<NativeHandler>(new ProcessInfoNativeHandler(
+ context,
+ context->GetExtensionID(),
+ context->GetContextTypeDescription(),
+ ExtensionsRendererClient::Get()->IsIncognitoProcess(),
+ is_component_extension,
+ manifest_version,
+ send_request_disabled)));
+
+ delegate_->RegisterNativeHandlers(this, module_system, context);
+}
+
+bool Dispatcher::IsRuntimeAvailableToContext(ScriptContext* context) {
+ for (const auto& extension :
+ *RendererExtensionRegistry::Get()->GetMainThreadExtensionSet()) {
+ ExternallyConnectableInfo* info = static_cast<ExternallyConnectableInfo*>(
+ extension->GetManifestData(manifest_keys::kExternallyConnectable));
+ if (info && info->matches.MatchesURL(context->url()))
+ return true;
+ }
+ return false;
+}
+
+void Dispatcher::UpdateContentCapabilities(ScriptContext* context) {
+ APIPermissionSet permissions;
+ for (const auto& extension :
+ *RendererExtensionRegistry::Get()->GetMainThreadExtensionSet()) {
+ const ContentCapabilitiesInfo& info =
+ ContentCapabilitiesInfo::Get(extension.get());
+ if (info.url_patterns.MatchesURL(context->url())) {
+ APIPermissionSet new_permissions;
+ APIPermissionSet::Union(permissions, info.permissions, &new_permissions);
+ permissions = new_permissions;
+ }
+ }
+ context->set_content_capabilities(permissions);
+}
+
+void Dispatcher::PopulateSourceMap() {
+ const std::vector<std::pair<std::string, int> > resources = GetJsResources();
+ for (std::vector<std::pair<std::string, int> >::const_iterator resource =
+ resources.begin();
+ resource != resources.end();
+ ++resource) {
+ source_map_.RegisterSource(resource->first, resource->second);
+ }
+ delegate_->PopulateSourceMap(&source_map_);
+}
+
+bool Dispatcher::IsWithinPlatformApp() {
+ for (std::set<std::string>::iterator iter = active_extension_ids_.begin();
+ iter != active_extension_ids_.end();
+ ++iter) {
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(*iter);
+ if (extension && extension->is_platform_app())
+ return true;
+ }
+ return false;
+}
+
+v8::Local<v8::Object> Dispatcher::GetOrCreateObject(
+ const v8::Local<v8::Object>& object,
+ const std::string& field,
+ v8::Isolate* isolate) {
+ v8::Local<v8::String> key = v8::String::NewFromUtf8(isolate, field.c_str());
+ // If the object has a callback property, it is assumed it is an unavailable
+ // API, so it is safe to delete. This is checked before GetOrCreateObject is
+ // called.
+ if (object->HasRealNamedCallbackProperty(key)) {
+ object->Delete(key);
+ } else if (object->HasRealNamedProperty(key)) {
+ v8::Local<v8::Value> value = object->Get(key);
+ CHECK(value->IsObject());
+ return v8::Local<v8::Object>::Cast(value);
+ }
+
+ v8::Local<v8::Object> new_object = v8::Object::New(isolate);
+ object->Set(key, new_object);
+ return new_object;
+}
+
+v8::Local<v8::Object> Dispatcher::GetOrCreateBindObjectIfAvailable(
+ const std::string& api_name,
+ std::string* bind_name,
+ ScriptContext* context) {
+ std::vector<std::string> split = base::SplitString(
+ api_name, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+
+ v8::Local<v8::Object> bind_object;
+
+ // Check if this API has an ancestor. If the API's ancestor is available and
+ // the API is not available, don't install the bindings for this API. If
+ // the API is available and its ancestor is not, delete the ancestor and
+ // install the bindings for the API. This is to prevent loading the ancestor
+ // API schema if it will not be needed.
+ //
+ // For example:
+ // If app is available and app.window is not, just install app.
+ // If app.window is available and app is not, delete app and install
+ // app.window on a new object so app does not have to be loaded.
+ const FeatureProvider* api_feature_provider =
+ FeatureProvider::GetAPIFeatures();
+ std::string ancestor_name;
+ bool only_ancestor_available = false;
+
+ for (size_t i = 0; i < split.size() - 1; ++i) {
+ ancestor_name += (i ? "." : "") + split[i];
+ if (api_feature_provider->GetFeature(ancestor_name) &&
+ context->GetAvailability(ancestor_name).is_available() &&
+ !context->GetAvailability(api_name).is_available()) {
+ only_ancestor_available = true;
+ break;
+ }
+
+ if (bind_object.IsEmpty()) {
+ bind_object = AsObjectOrEmpty(GetOrCreateChrome(context));
+ if (bind_object.IsEmpty())
+ return v8::Local<v8::Object>();
+ }
+ bind_object = GetOrCreateObject(bind_object, split[i], context->isolate());
+ }
+
+ if (only_ancestor_available)
+ return v8::Local<v8::Object>();
+
+ if (bind_name)
+ *bind_name = split.back();
+
+ return bind_object.IsEmpty() ? AsObjectOrEmpty(GetOrCreateChrome(context))
+ : bind_object;
+}
+
+void Dispatcher::RequireGuestViewModules(ScriptContext* context) {
+ Feature::Context context_type = context->context_type();
+ ModuleSystem* module_system = context->module_system();
+
+ // Only set if |context| is capable of running guests in OOPIF. Used to
+ // require additional module overrides.
+ bool guest_view_required = false;
+
+ // Require AppView.
+ if (context->GetAvailability("appViewEmbedderInternal").is_available()) {
+ module_system->Require("appView");
+ }
+
+ // Require ExtensionOptions.
+ if (context->GetAvailability("extensionOptionsInternal").is_available()) {
+ module_system->Require("extensionOptions");
+ module_system->Require("extensionOptionsAttributes");
+
+ guest_view_required = true;
+ }
+
+ // Require ExtensionView.
+ if (context->GetAvailability("extensionViewInternal").is_available()) {
+ module_system->Require("extensionView");
+ module_system->Require("extensionViewApiMethods");
+ module_system->Require("extensionViewAttributes");
+ }
+
+ // Require WebView.
+ if (context->GetAvailability("webViewInternal").is_available()) {
+ module_system->Require("webView");
+ module_system->Require("webViewApiMethods");
+ module_system->Require("webViewAttributes");
+ if (context->GetAvailability("webViewExperimentalInternal")
+ .is_available()) {
+ module_system->Require("webViewExperimental");
+ }
+
+ guest_view_required = true;
+ }
+
+ if (guest_view_required &&
+ content::BrowserPluginGuestMode::UseCrossProcessFramesForGuests()) {
+ module_system->Require("guestViewIframe");
+ module_system->Require("guestViewIframeContainer");
+ }
+
+ // The "guestViewDeny" module must always be loaded last. It registers
+ // error-providing custom elements for the GuestView types that are not
+ // available, and thus all of those types must have been checked and loaded
+ // (or not loaded) beforehand.
+ if (context_type == Feature::BLESSED_EXTENSION_CONTEXT) {
+ module_system->Require("guestViewDeny");
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/dispatcher.h b/chromium/extensions/renderer/dispatcher.h
new file mode 100644
index 00000000000..37ab0a312dc
--- /dev/null
+++ b/chromium/extensions/renderer/dispatcher.h
@@ -0,0 +1,318 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_DISPATCHER_H_
+#define EXTENSIONS_RENDERER_DISPATCHER_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/scoped_observer.h"
+#include "base/timer/timer.h"
+#include "content/public/renderer/render_process_observer.h"
+#include "extensions/common/event_filter.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/renderer/resource_bundle_source_map.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "extensions/renderer/user_script_set_manager.h"
+#include "extensions/renderer/v8_schema_registry.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/platform/WebVector.h"
+#include "v8/include/v8.h"
+
+class ChromeRenderViewTest;
+class GURL;
+class ModuleSystem;
+class URLPattern;
+struct ExtensionMsg_ExternalConnectionInfo;
+struct ExtensionMsg_Loaded_Params;
+struct ExtensionMsg_TabConnectionInfo;
+struct ExtensionMsg_UpdatePermissions_Params;
+
+namespace blink {
+class WebFrame;
+class WebLocalFrame;
+class WebSecurityOrigin;
+}
+
+namespace base {
+class ListValue;
+}
+
+namespace content {
+class RenderThread;
+}
+
+namespace extensions {
+class ContentWatcher;
+class DispatcherDelegate;
+class FilteredEventRouter;
+class ManifestPermissionSet;
+class RequestSender;
+class ScriptContext;
+class ScriptInjectionManager;
+struct Message;
+
+// Dispatches extension control messages sent to the renderer and stores
+// renderer extension related state.
+class Dispatcher : public content::RenderProcessObserver,
+ public UserScriptSetManager::Observer {
+ public:
+ explicit Dispatcher(DispatcherDelegate* delegate);
+ ~Dispatcher() override;
+
+ const ScriptContextSet& script_context_set() const {
+ return *script_context_set_;
+ }
+
+ V8SchemaRegistry* v8_schema_registry() { return v8_schema_registry_.get(); }
+
+ ContentWatcher* content_watcher() { return content_watcher_.get(); }
+
+ RequestSender* request_sender() { return request_sender_.get(); }
+
+ const std::string& webview_partition_id() { return webview_partition_id_; }
+
+ void OnRenderFrameCreated(content::RenderFrame* render_frame);
+
+ bool IsExtensionActive(const std::string& extension_id) const;
+
+ void DidCreateScriptContext(blink::WebLocalFrame* frame,
+ const v8::Local<v8::Context>& context,
+ int extension_group,
+ int world_id);
+
+ // Runs on a different thread and should not use any member variables.
+ static void DidInitializeServiceWorkerContextOnWorkerThread(
+ v8::Local<v8::Context> v8_context,
+ const GURL& url);
+
+ void WillReleaseScriptContext(blink::WebLocalFrame* frame,
+ const v8::Local<v8::Context>& context,
+ int world_id);
+
+ // Runs on a different thread and should not use any member variables.
+ static void WillDestroyServiceWorkerContextOnWorkerThread(
+ v8::Local<v8::Context> v8_context,
+ const GURL& url);
+
+ // This method is not allowed to run JavaScript code in the frame.
+ void DidCreateDocumentElement(blink::WebLocalFrame* frame);
+
+ // These methods may run (untrusted) JavaScript code in the frame, and
+ // cause |render_frame| to become invalid.
+ void RunScriptsAtDocumentStart(content::RenderFrame* render_frame);
+ void RunScriptsAtDocumentEnd(content::RenderFrame* render_frame);
+
+ void OnExtensionResponse(int request_id,
+ bool success,
+ const base::ListValue& response,
+ const std::string& error);
+
+ // Dispatches the event named |event_name| to all render views.
+ void DispatchEvent(const std::string& extension_id,
+ const std::string& event_name) const;
+
+ // Shared implementation of the various MessageInvoke IPCs.
+ void InvokeModuleSystemMethod(content::RenderFrame* render_frame,
+ const std::string& extension_id,
+ const std::string& module_name,
+ const std::string& function_name,
+ const base::ListValue& args,
+ bool user_gesture);
+
+ void ClearPortData(int port_id);
+
+ // Returns a list of (module name, resource id) pairs for the JS modules to
+ // add to the source map.
+ static std::vector<std::pair<std::string, int> > GetJsResources();
+ static void RegisterNativeHandlers(ModuleSystem* module_system,
+ ScriptContext* context,
+ Dispatcher* dispatcher,
+ RequestSender* request_sender,
+ V8SchemaRegistry* v8_schema_registry);
+
+ bool WasWebRequestUsedBySomeExtensions() const { return webrequest_used_; }
+
+ private:
+ // The RendererPermissionsPolicyDelegateTest.CannotScriptWebstore test needs
+ // to call the OnActivateExtension IPCs.
+ friend class ::ChromeRenderViewTest;
+ FRIEND_TEST_ALL_PREFIXES(RendererPermissionsPolicyDelegateTest,
+ CannotScriptWebstore);
+
+ // RenderProcessObserver implementation:
+ bool OnControlMessageReceived(const IPC::Message& message) override;
+ void IdleNotification() override;
+ void OnRenderProcessShutdown() override;
+
+ void OnActivateExtension(const std::string& extension_id);
+ void OnCancelSuspend(const std::string& extension_id);
+ void OnDeliverMessage(int target_port_id, const Message& message);
+ void OnDispatchOnConnect(int target_port_id,
+ const std::string& channel_name,
+ const ExtensionMsg_TabConnectionInfo& source,
+ const ExtensionMsg_ExternalConnectionInfo& info,
+ const std::string& tls_channel_id);
+ void OnDispatchOnDisconnect(int port_id, const std::string& error_message);
+ void OnLoaded(
+ const std::vector<ExtensionMsg_Loaded_Params>& loaded_extensions);
+ void OnMessageInvoke(const std::string& extension_id,
+ const std::string& module_name,
+ const std::string& function_name,
+ const base::ListValue& args,
+ bool user_gesture);
+ void OnSetChannel(int channel);
+ void OnSetScriptingWhitelist(
+ const ExtensionsClient::ScriptingWhitelist& extension_ids);
+ void OnSetSystemFont(const std::string& font_family,
+ const std::string& font_size);
+ void OnSetWebViewPartitionID(const std::string& partition_id);
+ void OnShouldSuspend(const std::string& extension_id, uint64_t sequence_id);
+ void OnSuspend(const std::string& extension_id);
+ void OnTransferBlobs(const std::vector<std::string>& blob_uuids);
+ void OnUnloaded(const std::string& id);
+ void OnUpdatePermissions(const ExtensionMsg_UpdatePermissions_Params& params);
+ void OnUpdateTabSpecificPermissions(const GURL& visible_url,
+ const std::string& extension_id,
+ const URLPatternSet& new_hosts,
+ bool update_origin_whitelist,
+ int tab_id);
+ void OnClearTabSpecificPermissions(
+ const std::vector<std::string>& extension_ids,
+ bool update_origin_whitelist,
+ int tab_id);
+ void OnUsingWebRequestAPI(bool webrequest_used);
+
+ // UserScriptSetManager::Observer implementation.
+ void OnUserScriptsUpdated(const std::set<HostID>& changed_hosts,
+ const std::vector<UserScript*>& scripts) override;
+
+ void UpdateActiveExtensions();
+
+ // Sets up the host permissions for |extension|.
+ void InitOriginPermissions(const Extension* extension);
+
+ // Updates the host permissions for the extension url to include only those in
+ // |new_patterns|, and remove from |old_patterns| that are no longer allowed.
+ void UpdateOriginPermissions(const GURL& extension_url,
+ const URLPatternSet& old_patterns,
+ const URLPatternSet& new_patterns);
+
+ // Enable custom element whitelist in Apps.
+ void EnableCustomElementWhiteList();
+
+ // Adds or removes bindings for every context belonging to |extension_id|, or
+ // or all contexts if |extension_id| is empty.
+ void UpdateBindings(const std::string& extension_id);
+
+ void UpdateBindingsForContext(ScriptContext* context);
+
+ void RegisterBinding(const std::string& api_name, ScriptContext* context);
+
+ void RegisterNativeHandlers(ModuleSystem* module_system,
+ ScriptContext* context);
+
+ // Determines if a ScriptContext can connect to any externally_connectable-
+ // enabled extension.
+ bool IsRuntimeAvailableToContext(ScriptContext* context);
+
+ // Updates a web page context with any content capabilities granted by active
+ // extensions.
+ void UpdateContentCapabilities(ScriptContext* context);
+
+ // Inserts static source code into |source_map_|.
+ void PopulateSourceMap();
+
+ // Returns whether the current renderer hosts a platform app.
+ bool IsWithinPlatformApp();
+
+ // Gets |field| from |object| or creates it as an empty object if it doesn't
+ // exist.
+ v8::Local<v8::Object> GetOrCreateObject(const v8::Local<v8::Object>& object,
+ const std::string& field,
+ v8::Isolate* isolate);
+
+ v8::Local<v8::Object> GetOrCreateBindObjectIfAvailable(
+ const std::string& api_name,
+ std::string* bind_name,
+ ScriptContext* context);
+
+ // Requires the GuestView modules in the module system of the ScriptContext
+ // |context|.
+ void RequireGuestViewModules(ScriptContext* context);
+
+ // The delegate for this dispatcher. Not owned, but must extend beyond the
+ // Dispatcher's own lifetime.
+ DispatcherDelegate* delegate_;
+
+ // True if the IdleNotification timer should be set.
+ bool set_idle_notifications_;
+
+ // The IDs of extensions that failed to load, mapped to the error message
+ // generated on failure.
+ std::map<std::string, std::string> extension_load_errors_;
+
+ // All the bindings contexts that are currently loaded for this renderer.
+ // There is zero or one for each v8 context.
+ scoped_ptr<ScriptContextSet> script_context_set_;
+
+ scoped_ptr<ContentWatcher> content_watcher_;
+
+ scoped_ptr<UserScriptSetManager> user_script_set_manager_;
+
+ scoped_ptr<ScriptInjectionManager> script_injection_manager_;
+
+ // Same as above, but on a longer timer and will run even if the process is
+ // not idle, to ensure that IdleHandle gets called eventually.
+ scoped_ptr<base::RepeatingTimer> forced_idle_timer_;
+
+ // The extensions and apps that are active in this process.
+ ExtensionIdSet active_extension_ids_;
+
+ ResourceBundleSourceMap source_map_;
+
+ // Cache for the v8 representation of extension API schemas.
+ scoped_ptr<V8SchemaRegistry> v8_schema_registry_;
+
+ // Sends API requests to the extension host.
+ scoped_ptr<RequestSender> request_sender_;
+
+ // The platforms system font family and size;
+ std::string system_font_family_;
+ std::string system_font_size_;
+
+ // Mapping of port IDs to tabs. If there is no tab, the value would be -1.
+ std::map<int, int> port_to_tab_id_map_;
+
+ // It is important for this to come after the ScriptInjectionManager, so that
+ // the observer is destroyed before the UserScriptSet.
+ ScopedObserver<UserScriptSetManager, UserScriptSetManager::Observer>
+ user_script_set_manager_observer_;
+
+ // Status of webrequest usage.
+ bool webrequest_used_;
+
+ // The WebView partition ID associated with this process's storage partition,
+ // if this renderer is a WebView guest render process. Otherwise, this will be
+ // empty.
+ std::string webview_partition_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(Dispatcher);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_DISPATCHER_H_
diff --git a/chromium/extensions/renderer/dispatcher_delegate.h b/chromium/extensions/renderer/dispatcher_delegate.h
new file mode 100644
index 00000000000..f00c357b7ef
--- /dev/null
+++ b/chromium/extensions/renderer/dispatcher_delegate.h
@@ -0,0 +1,59 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_DISPATCHER_DELEGATE_H_
+#define EXTENSIONS_RENDERER_DISPATCHER_DELEGATE_H_
+
+#include <set>
+#include <string>
+
+namespace blink {
+class WebFrame;
+}
+
+namespace extensions {
+class Dispatcher;
+class Extension;
+class ModuleSystem;
+class ResourceBundleSourceMap;
+class ScriptContext;
+class URLPatternSet;
+
+// Base class and default implementation for an extensions::Dispacher delegate.
+// DispatcherDelegate can be used to override and extend the behavior of the
+// extensions system's renderer side.
+class DispatcherDelegate {
+ public:
+ virtual ~DispatcherDelegate() {}
+
+ // Initializes origin permissions for a newly created extension context.
+ virtual void InitOriginPermissions(const Extension* extension,
+ bool is_extension_active) {}
+
+ // Includes additional native handlers in a ScriptContext's ModuleSystem.
+ virtual void RegisterNativeHandlers(Dispatcher* dispatcher,
+ ModuleSystem* module_system,
+ ScriptContext* context) {}
+
+ // Includes additional source resources into the resource map.
+ virtual void PopulateSourceMap(ResourceBundleSourceMap* source_map) {}
+
+ // Requires additional modules within an extension context's module system.
+ virtual void RequireAdditionalModules(ScriptContext* context,
+ bool is_within_platform_app) {}
+
+ // Allows the delegate to respond to an updated set of active extensions in
+ // the Dispatcher.
+ virtual void OnActiveExtensionsUpdated(
+ const std::set<std::string>& extension_ids) {}
+
+ // Sets the current Chrome channel.
+ // TODO(rockot): This doesn't belong in a generic extensions system interface.
+ // See http://crbug.com/368431.
+ virtual void SetChannel(int channel) {}
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_DISPATCHER_DELEGATE_H_
diff --git a/chromium/extensions/renderer/display_source_custom_bindings.cc b/chromium/extensions/renderer/display_source_custom_bindings.cc
new file mode 100644
index 00000000000..1a021c157b3
--- /dev/null
+++ b/chromium/extensions/renderer/display_source_custom_bindings.cc
@@ -0,0 +1,306 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/display_source_custom_bindings.h"
+
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "content/public/child/v8_value_converter.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/platform/WebMediaStream.h"
+#include "third_party/WebKit/public/platform/WebMediaStreamTrack.h"
+#include "third_party/WebKit/public/web/WebDOMMediaStreamTrack.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+using content::V8ValueConverter;
+
+namespace {
+const char kErrorNotSupported[] = "Not supported";
+const char kInvalidStreamArgs[] = "Invalid stream arguments";
+const char kSessionAlreadyStarted[] = "The session has been already started";
+const char kSessionAlreadyTerminating[] = "The session is already terminating";
+const char kSessionNotFound[] = "Session not found";
+} // namespace
+
+DisplaySourceCustomBindings::DisplaySourceCustomBindings(ScriptContext* context)
+ : ObjectBackedNativeHandler(context),
+ weak_factory_(this) {
+ RouteFunction("StartSession",
+ base::Bind(&DisplaySourceCustomBindings::StartSession,
+ weak_factory_.GetWeakPtr()));
+ RouteFunction("TerminateSession",
+ base::Bind(&DisplaySourceCustomBindings::TerminateSession,
+ weak_factory_.GetWeakPtr()));
+}
+
+DisplaySourceCustomBindings::~DisplaySourceCustomBindings() {
+}
+
+void DisplaySourceCustomBindings::Invalidate() {
+ session_map_.clear();
+ weak_factory_.InvalidateWeakPtrs();
+ ObjectBackedNativeHandler::Invalidate();
+}
+
+namespace {
+
+v8::Local<v8::Value> GetChildValue(v8::Local<v8::Object> value,
+ const std::string& key_name,
+ v8::Isolate* isolate) {
+ v8::Local<v8::Array> property_names(value->GetOwnPropertyNames());
+ for (uint32_t i = 0; i < property_names->Length(); ++i) {
+ v8::Local<v8::Value> key(property_names->Get(i));
+ if (key_name == *v8::String::Utf8Value(key)) {
+ v8::TryCatch try_catch(isolate);
+ v8::Local<v8::Value> child_v8 = value->Get(key);
+ if (try_catch.HasCaught()) {
+ return v8::Null(isolate);
+ }
+ return child_v8;
+ }
+ }
+
+ return v8::Null(isolate);
+}
+
+int32_t GetCallbackId() {
+ static int32_t sCallId = 0;
+ return ++sCallId;
+}
+
+} // namespace
+
+void DisplaySourceCustomBindings::StartSession(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsObject());
+
+ v8::Isolate* isolate = context()->isolate();
+ v8::Local<v8::Object> start_info = args[0].As<v8::Object>();
+
+ v8::Local<v8::Value> sink_id_val =
+ GetChildValue(start_info, "sinkId", isolate);
+ CHECK(sink_id_val->IsInt32());
+ const int sink_id = sink_id_val->ToInt32(isolate)->Value();
+ if (GetDisplaySession(sink_id)) {
+ isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(
+ isolate, kSessionAlreadyStarted)));
+ return;
+ }
+
+ v8::Local<v8::Value> video_stream_val =
+ GetChildValue(start_info, "videoTrack", isolate);
+ v8::Local<v8::Value> audio_stream_val =
+ GetChildValue(start_info, "audioTrack", isolate);
+
+ if ((video_stream_val->IsNull() || video_stream_val->IsUndefined()) &&
+ (audio_stream_val->IsNull() || audio_stream_val->IsUndefined())) {
+ isolate->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate, kInvalidStreamArgs)));
+ return;
+ }
+
+ blink::WebMediaStreamTrack audio_track, video_track;
+
+ if (!video_stream_val->IsNull() && !video_stream_val->IsUndefined()) {
+ CHECK(video_stream_val->IsObject());
+ video_track =
+ blink::WebDOMMediaStreamTrack::fromV8Value(
+ video_stream_val).component();
+ if (video_track.isNull()) {
+ isolate->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate, kInvalidStreamArgs)));
+ return;
+ }
+ }
+ if (!audio_stream_val->IsNull() && !audio_stream_val->IsUndefined()) {
+ CHECK(audio_stream_val->IsObject());
+ audio_track =
+ blink::WebDOMMediaStreamTrack::fromV8Value(
+ audio_stream_val).component();
+ if (audio_track.isNull()) {
+ isolate->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate, kInvalidStreamArgs)));
+ return;
+ }
+ }
+
+ scoped_ptr<DisplaySourceAuthInfo> auth_info;
+ v8::Local<v8::Value> auth_info_v8_val =
+ GetChildValue(start_info, "authenticationInfo", isolate);
+ if (!auth_info_v8_val->IsNull()) {
+ CHECK(auth_info_v8_val->IsObject());
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+ scoped_ptr<base::Value> auth_info_val(
+ converter->FromV8Value(auth_info_v8_val, context()->v8_context()));
+ CHECK(auth_info_val);
+ auth_info = DisplaySourceAuthInfo::FromValue(*auth_info_val);
+ }
+
+ DisplaySourceSessionParams session_params;
+ session_params.sink_id = sink_id;
+ session_params.video_track = video_track;
+ session_params.audio_track = audio_track;
+ session_params.render_frame = context()->GetRenderFrame();
+ if (auth_info) {
+ session_params.auth_method = auth_info->method;
+ session_params.auth_data = auth_info->data ? *auth_info->data : "";
+ }
+ scoped_ptr<DisplaySourceSession> session =
+ DisplaySourceSessionFactory::CreateSession(session_params);
+ if (!session) {
+ isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(
+ isolate, kErrorNotSupported)));
+ return;
+ }
+
+ auto on_terminated_callback =
+ base::Bind(&DisplaySourceCustomBindings::OnSessionTerminated,
+ weak_factory_.GetWeakPtr(), sink_id);
+ auto on_error_callback =
+ base::Bind(&DisplaySourceCustomBindings::OnSessionError,
+ weak_factory_.GetWeakPtr(), sink_id);
+ session->SetNotificationCallbacks(on_terminated_callback, on_error_callback);
+
+ int32_t call_id = GetCallbackId();
+ args.GetReturnValue().Set(call_id);
+
+ auto on_call_completed =
+ base::Bind(&DisplaySourceCustomBindings::OnSessionStarted,
+ weak_factory_.GetWeakPtr(), sink_id, call_id);
+ session->Start(on_call_completed);
+ session_map_.insert(std::make_pair(sink_id, std::move(session)));
+}
+
+void DisplaySourceCustomBindings::TerminateSession(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsInt32());
+
+ v8::Isolate* isolate = context()->isolate();
+ int sink_id = args[0]->ToInt32(args.GetIsolate())->Value();
+ DisplaySourceSession* session = GetDisplaySession(sink_id);
+ if (!session) {
+ isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(
+ isolate, kSessionNotFound)));
+ return;
+ }
+
+ DisplaySourceSession::State state = session->state();
+ DCHECK_NE(state, DisplaySourceSession::Idle);
+ if (state == DisplaySourceSession::Establishing) {
+ // 'session started' callback has not yet been invoked.
+ // This session is not existing for the user.
+ isolate->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate, kSessionNotFound)));
+ return;
+ }
+
+ if (state == DisplaySourceSession::Terminating) {
+ isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(
+ isolate, kSessionAlreadyTerminating)));
+ return;
+ }
+
+ int32_t call_id = GetCallbackId();
+ args.GetReturnValue().Set(call_id);
+
+ auto on_call_completed =
+ base::Bind(&DisplaySourceCustomBindings::OnCallCompleted,
+ weak_factory_.GetWeakPtr(), call_id);
+ // The session will get removed from session_map_ in OnSessionTerminated.
+ session->Terminate(on_call_completed);
+}
+
+void DisplaySourceCustomBindings::OnCallCompleted(
+ int call_id,
+ bool success,
+ const std::string& error_message) {
+ v8::Isolate* isolate = context()->isolate();
+ ModuleSystem* module_system = context()->module_system();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context()->v8_context());
+
+ v8::Local<v8::Value> callback_args[2];
+ callback_args[0] = v8::Integer::New(isolate, call_id);
+ if (success)
+ callback_args[1] = v8::Null(isolate);
+ else
+ callback_args[1] = v8::String::NewFromUtf8(isolate, error_message.c_str());
+
+ module_system->CallModuleMethod("displaySource", "callCompletionCallback", 2,
+ callback_args);
+}
+
+void DisplaySourceCustomBindings::OnSessionStarted(
+ int sink_id,
+ int call_id,
+ bool success,
+ const std::string& error_message) {
+ CHECK(GetDisplaySession(sink_id));
+ if (!success) {
+ // Session has failed to start, removing it.
+ session_map_.erase(sink_id);
+ }
+ OnCallCompleted(call_id, success, error_message);
+}
+
+void DisplaySourceCustomBindings::DispatchSessionTerminated(int sink_id) const {
+ v8::Isolate* isolate = context()->isolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context()->v8_context());
+ v8::Local<v8::Array> event_args = v8::Array::New(isolate, 1);
+ event_args->Set(0, v8::Integer::New(isolate, sink_id));
+ context()->DispatchEvent("displaySource.onSessionTerminated", event_args);
+}
+
+void DisplaySourceCustomBindings::DispatchSessionError(
+ int sink_id,
+ DisplaySourceErrorType type,
+ const std::string& message) const {
+ v8::Isolate* isolate = context()->isolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context()->v8_context());
+
+ api::display_source::ErrorInfo error_info;
+ error_info.type = type;
+ if (!message.empty())
+ error_info.description.reset(new std::string(message));
+
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+ v8::Local<v8::Value> info_arg =
+ converter->ToV8Value(error_info.ToValue().get(),
+ context()->v8_context());
+
+ v8::Local<v8::Array> event_args = v8::Array::New(isolate, 2);
+ event_args->Set(0, v8::Integer::New(isolate, sink_id));
+ event_args->Set(1, info_arg);
+ context()->DispatchEvent("displaySource.onSessionErrorOccured", event_args);
+}
+
+DisplaySourceSession* DisplaySourceCustomBindings::GetDisplaySession(
+ int sink_id) const {
+ auto iter = session_map_.find(sink_id);
+ if (iter != session_map_.end())
+ return iter->second.get();
+ return nullptr;
+}
+
+void DisplaySourceCustomBindings::OnSessionTerminated(int sink_id) {
+ CHECK(GetDisplaySession(sink_id));
+ session_map_.erase(sink_id);
+ DispatchSessionTerminated(sink_id);
+}
+
+void DisplaySourceCustomBindings::OnSessionError(int sink_id,
+ DisplaySourceErrorType type,
+ const std::string& message) {
+ CHECK(GetDisplaySession(sink_id));
+ DispatchSessionError(sink_id, type, message);
+}
+
+} // extensions
diff --git a/chromium/extensions/renderer/display_source_custom_bindings.h b/chromium/extensions/renderer/display_source_custom_bindings.h
new file mode 100644
index 00000000000..b7e5e967ac7
--- /dev/null
+++ b/chromium/extensions/renderer/display_source_custom_bindings.h
@@ -0,0 +1,63 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_DISPLAY_SOURCE_CUSTOM_BINDINGS_H_
+#define EXTENSIONS_RENDERER_DISPLAY_SOURCE_CUSTOM_BINDINGS_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/api/display_source.h"
+#include "extensions/renderer/api/display_source/display_source_session.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+class ScriptContext;
+
+// Implements custom bindings for the displaySource API.
+class DisplaySourceCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ explicit DisplaySourceCustomBindings(ScriptContext* context);
+
+ ~DisplaySourceCustomBindings() override;
+
+ private:
+ // ObjectBackedNativeHandler override.
+ void Invalidate() override;
+
+ void StartSession(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ void TerminateSession(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ // Call completion callbacks.
+ void OnCallCompleted(int call_id,
+ bool success,
+ const std::string& error_message);
+ void OnSessionStarted(int sink_id,
+ int call_id,
+ bool success,
+ const std::string& error_message);
+ // Dispatch events
+ void DispatchSessionTerminated(int sink_id) const;
+ void DispatchSessionError(int sink_id,
+ DisplaySourceErrorType type,
+ const std::string& message) const;
+
+ // DisplaySession notification callbacks.
+ void OnSessionTerminated(int sink_id);
+ void OnSessionError(int sink_id,
+ DisplaySourceErrorType type,
+ const std::string& message);
+
+ DisplaySourceSession* GetDisplaySession(int sink_id) const;
+
+ std::map<int, scoped_ptr<DisplaySourceSession>> session_map_;
+ base::WeakPtrFactory<DisplaySourceCustomBindings> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplaySourceCustomBindings);
+};
+
+} // extensions
+
+#endif // EXTENSIONS_RENDERER_DISPLAY_SOURCE_CUSTOM_BINDINGS_H_
diff --git a/chromium/extensions/renderer/document_custom_bindings.cc b/chromium/extensions/renderer/document_custom_bindings.cc
new file mode 100644
index 00000000000..5f81ee8e1b2
--- /dev/null
+++ b/chromium/extensions/renderer/document_custom_bindings.cc
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/document_custom_bindings.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+DocumentCustomBindings::DocumentCustomBindings(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("RegisterElement",
+ base::Bind(&DocumentCustomBindings::RegisterElement,
+ base::Unretained(this)));
+}
+
+// Attach an event name to an object.
+void DocumentCustomBindings::RegisterElement(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() != 2 || !args[0]->IsString() || !args[1]->IsObject()) {
+ NOTREACHED();
+ return;
+ }
+
+ std::string element_name(*v8::String::Utf8Value(args[0]));
+ v8::Local<v8::Object> options = v8::Local<v8::Object>::Cast(args[1]);
+
+ blink::WebExceptionCode ec = 0;
+ blink::WebDocument document = context()->web_frame()->document();
+ v8::Local<v8::Value> constructor = document.registerEmbedderCustomElement(
+ blink::WebString::fromUTF8(element_name), options, ec);
+ args.GetReturnValue().Set(constructor);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/document_custom_bindings.h b/chromium/extensions/renderer/document_custom_bindings.h
new file mode 100644
index 00000000000..6c07eab0fa2
--- /dev/null
+++ b/chromium/extensions/renderer/document_custom_bindings.h
@@ -0,0 +1,25 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_DOCUMENT_CUSTOM_BINDINGS_H_
+#define EXTENSIONS_RENDERER_DOCUMENT_CUSTOM_BINDINGS_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+// Implements custom bindings for document-level operations.
+class DocumentCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ DocumentCustomBindings(ScriptContext* context);
+
+ private:
+ // Registers the provided element as a custom element in Blink.
+ void RegisterElement(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_DOCUMENT_CUSTOM_BINDINGS_H_
diff --git a/chromium/extensions/renderer/dom_activity_logger.cc b/chromium/extensions/renderer/dom_activity_logger.cc
new file mode 100644
index 00000000000..92055232c71
--- /dev/null
+++ b/chromium/extensions/renderer/dom_activity_logger.cc
@@ -0,0 +1,133 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/dom_activity_logger.h"
+
+#include <utility>
+
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/dom_action_types.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/renderer/activity_log_converter_strategy.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/platform/WebURL.h"
+
+using content::V8ValueConverter;
+using blink::WebString;
+using blink::WebURL;
+
+namespace extensions {
+
+namespace {
+
+// Converts the given |v8_value| and appends it to the given |list|, if the
+// conversion succeeds.
+void AppendV8Value(const std::string& api_name,
+ const v8::Local<v8::Value>& v8_value,
+ base::ListValue* list) {
+ DCHECK(list);
+ std::unique_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+ ActivityLogConverterStrategy strategy;
+ converter->SetFunctionAllowed(true);
+ converter->SetStrategy(&strategy);
+ std::unique_ptr<base::Value> value(converter->FromV8Value(
+ v8_value, v8::Isolate::GetCurrent()->GetCurrentContext()));
+
+ if (value.get())
+ list->Append(value.release());
+}
+
+} // namespace
+
+DOMActivityLogger::DOMActivityLogger(const std::string& extension_id)
+ : extension_id_(extension_id) {
+}
+
+DOMActivityLogger::~DOMActivityLogger() {}
+
+void DOMActivityLogger::AttachToWorld(int world_id,
+ const std::string& extension_id) {
+ // If there is no logger registered for world_id, construct a new logger
+ // and register it with world_id.
+ if (!blink::hasDOMActivityLogger(world_id,
+ WebString::fromUTF8(extension_id))) {
+ DOMActivityLogger* logger = new DOMActivityLogger(extension_id);
+ blink::setDOMActivityLogger(world_id,
+ WebString::fromUTF8(extension_id),
+ logger);
+ }
+}
+
+void DOMActivityLogger::logGetter(const WebString& api_name,
+ const WebURL& url,
+ const WebString& title) {
+ SendDomActionMessage(api_name.utf8(), url, title, DomActionType::GETTER,
+ std::unique_ptr<base::ListValue>(new base::ListValue()));
+}
+
+void DOMActivityLogger::logSetter(const WebString& api_name,
+ const v8::Local<v8::Value>& new_value,
+ const WebURL& url,
+ const WebString& title) {
+ logSetter(api_name, new_value, v8::Local<v8::Value>(), url, title);
+}
+
+void DOMActivityLogger::logSetter(const WebString& api_name,
+ const v8::Local<v8::Value>& new_value,
+ const v8::Local<v8::Value>& old_value,
+ const WebURL& url,
+ const WebString& title) {
+ std::unique_ptr<base::ListValue> args(new base::ListValue);
+ std::string api_name_utf8 = api_name.utf8();
+ AppendV8Value(api_name_utf8, new_value, args.get());
+ if (!old_value.IsEmpty())
+ AppendV8Value(api_name_utf8, old_value, args.get());
+ SendDomActionMessage(api_name_utf8, url, title, DomActionType::SETTER,
+ std::move(args));
+}
+
+void DOMActivityLogger::logMethod(const WebString& api_name,
+ int argc,
+ const v8::Local<v8::Value>* argv,
+ const WebURL& url,
+ const WebString& title) {
+ std::unique_ptr<base::ListValue> args(new base::ListValue);
+ std::string api_name_utf8 = api_name.utf8();
+ for (int i = 0; i < argc; ++i)
+ AppendV8Value(api_name_utf8, argv[i], args.get());
+ SendDomActionMessage(api_name_utf8, url, title, DomActionType::METHOD,
+ std::move(args));
+}
+
+void DOMActivityLogger::logEvent(const WebString& event_name,
+ int argc,
+ const WebString* argv,
+ const WebURL& url,
+ const WebString& title) {
+ std::unique_ptr<base::ListValue> args(new base::ListValue);
+ std::string event_name_utf8 = event_name.utf8();
+ for (int i = 0; i < argc; ++i)
+ args->Append(new base::StringValue(argv[i]));
+ SendDomActionMessage(event_name_utf8, url, title, DomActionType::METHOD,
+ std::move(args));
+}
+
+void DOMActivityLogger::SendDomActionMessage(
+ const std::string& api_call,
+ const GURL& url,
+ const base::string16& url_title,
+ DomActionType::Type call_type,
+ std::unique_ptr<base::ListValue> args) {
+ ExtensionHostMsg_DOMAction_Params params;
+ params.api_call = api_call;
+ params.url = url;
+ params.url_title = url_title;
+ params.call_type = call_type;
+ params.arguments.Swap(args.get());
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_AddDOMActionToActivityLog(extension_id_, params));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/dom_activity_logger.h b/chromium/extensions/renderer/dom_activity_logger.h
new file mode 100644
index 00000000000..3956c4847b4
--- /dev/null
+++ b/chromium/extensions/renderer/dom_activity_logger.h
@@ -0,0 +1,91 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_DOM_ACTIVITY_LOGGER_H_
+#define EXTENSIONS_RENDERER_DOM_ACTIVITY_LOGGER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "extensions/common/dom_action_types.h"
+#include "third_party/WebKit/public/web/WebDOMActivityLogger.h"
+#include "v8/include/v8.h"
+
+namespace base {
+class ListValue;
+}
+
+namespace blink {
+class WebString;
+class WebURL;
+}
+
+namespace content {
+class V8ValueConverter;
+}
+
+namespace extensions {
+
+// Used to log DOM API calls from within WebKit. The events are sent via IPC to
+// extensions::ActivityLog for recording and display.
+class DOMActivityLogger: public blink::WebDOMActivityLogger {
+ public:
+ static const int kMainWorldId = 0;
+ explicit DOMActivityLogger(const std::string& extension_id);
+ ~DOMActivityLogger() override;
+
+ // Check (using the WebKit API) if there is no logger attached to the world
+ // corresponding to world_id, and if so, construct a new logger and attach it.
+ // world_id = 0 indicates the main world.
+ static void AttachToWorld(int world_id,
+ const std::string& extension_id);
+
+ private:
+ // blink::WebDOMActivityLogger implementation.
+ // Marshals the arguments into an ExtensionHostMsg_DOMAction_Params and sends
+ // it over to the browser (via IPC) for appending it to the extension activity
+ // log.
+ // These methods don't have the override keyword due to the complexities it
+ // introduces when changes blink apis.
+ void logGetter(const blink::WebString& api_name,
+ const blink::WebURL& url,
+ const blink::WebString& title) override;
+ void logSetter(const blink::WebString& api_name,
+ const v8::Local<v8::Value>& new_value,
+ const blink::WebURL& url,
+ const blink::WebString& title) override;
+ virtual void logSetter(const blink::WebString& api_name,
+ const v8::Local<v8::Value>& new_value,
+ const v8::Local<v8::Value>& old_value,
+ const blink::WebURL& url,
+ const blink::WebString& title);
+ void logMethod(const blink::WebString& api_name,
+ int argc,
+ const v8::Local<v8::Value>* argv,
+ const blink::WebURL& url,
+ const blink::WebString& title) override;
+ void logEvent(const blink::WebString& event_name,
+ int argc,
+ const blink::WebString* argv,
+ const blink::WebURL& url,
+ const blink::WebString& title) override;
+
+ // Helper function to actually send the message across IPC.
+ void SendDomActionMessage(const std::string& api_call,
+ const GURL& url,
+ const base::string16& url_title,
+ DomActionType::Type call_type,
+ std::unique_ptr<base::ListValue> args);
+
+ // The id of the extension with which this logger is associated.
+ std::string extension_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(DOMActivityLogger);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_DOM_ACTIVITY_LOGGER_H_
+
diff --git a/chromium/extensions/renderer/event_bindings.cc b/chromium/extensions/renderer/event_bindings.cc
new file mode 100644
index 00000000000..87b8349d098
--- /dev/null
+++ b/chromium/extensions/renderer/event_bindings.cc
@@ -0,0 +1,367 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/event_bindings.h"
+
+#include <stdint.h>
+
+#include <map>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/crx_file/id_util.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/render_view.h"
+#include "extensions/common/event_filter.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/value_counter.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/script_context.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+namespace {
+
+// A map of event names to the number of contexts listening to that event.
+// We notify the browser about event listeners when we transition between 0
+// and 1.
+typedef std::map<std::string, int> EventListenerCounts;
+
+// A map of extension IDs to listener counts for that extension.
+base::LazyInstance<std::map<std::string, EventListenerCounts>>
+ g_listener_counts = LAZY_INSTANCE_INITIALIZER;
+
+// A map of (extension ID, event name) pairs to the filtered listener counts
+// for that pair. The map is used to keep track of which filters are in effect
+// for which events. We notify the browser about filtered event listeners when
+// we transition between 0 and 1.
+using FilteredEventListenerKey = std::pair<std::string, std::string>;
+using FilteredEventListenerCounts =
+ std::map<FilteredEventListenerKey, scoped_ptr<ValueCounter>>;
+base::LazyInstance<FilteredEventListenerCounts> g_filtered_listener_counts =
+ LAZY_INSTANCE_INITIALIZER;
+
+base::LazyInstance<EventFilter> g_event_filter = LAZY_INSTANCE_INITIALIZER;
+
+// Gets a unique string key identifier for a ScriptContext.
+// TODO(kalman): Just use pointer equality...?
+std::string GetKeyForScriptContext(ScriptContext* script_context) {
+ const std::string& extension_id = script_context->GetExtensionID();
+ CHECK(crx_file::id_util::IdIsValid(extension_id) ||
+ script_context->url().is_valid());
+ return crx_file::id_util::IdIsValid(extension_id)
+ ? extension_id
+ : script_context->url().spec();
+}
+
+// Increments the number of event-listeners for the given |event_name| and
+// ScriptContext. Returns the count after the increment.
+int IncrementEventListenerCount(ScriptContext* script_context,
+ const std::string& event_name) {
+ return ++g_listener_counts
+ .Get()[GetKeyForScriptContext(script_context)][event_name];
+}
+
+// Decrements the number of event-listeners for the given |event_name| and
+// ScriptContext. Returns the count after the increment.
+int DecrementEventListenerCount(ScriptContext* script_context,
+ const std::string& event_name) {
+ return --g_listener_counts
+ .Get()[GetKeyForScriptContext(script_context)][event_name];
+}
+
+EventFilteringInfo ParseFromObject(v8::Local<v8::Object> object,
+ v8::Isolate* isolate) {
+ EventFilteringInfo info;
+ v8::Local<v8::String> url(v8::String::NewFromUtf8(isolate, "url"));
+ if (object->Has(url)) {
+ v8::Local<v8::Value> url_value(object->Get(url));
+ info.SetURL(GURL(*v8::String::Utf8Value(url_value)));
+ }
+ v8::Local<v8::String> instance_id(
+ v8::String::NewFromUtf8(isolate, "instanceId"));
+ if (object->Has(instance_id)) {
+ v8::Local<v8::Value> instance_id_value(object->Get(instance_id));
+ info.SetInstanceID(instance_id_value->IntegerValue());
+ }
+ v8::Local<v8::String> service_type(
+ v8::String::NewFromUtf8(isolate, "serviceType"));
+ if (object->Has(service_type)) {
+ v8::Local<v8::Value> service_type_value(object->Get(service_type));
+ info.SetServiceType(*v8::String::Utf8Value(service_type_value));
+ }
+ v8::Local<v8::String> window_types(
+ v8::String::NewFromUtf8(isolate, "windowType"));
+ if (object->Has(window_types)) {
+ v8::Local<v8::Value> window_types_value(object->Get(window_types));
+ info.SetWindowType(*v8::String::Utf8Value(window_types_value));
+ }
+
+ v8::Local<v8::String> window_exposed(
+ v8::String::NewFromUtf8(isolate, "windowExposedByDefault"));
+ if (object->Has(window_exposed)) {
+ v8::Local<v8::Value> window_exposed_value(object->Get(window_exposed));
+ info.SetWindowExposedByDefault(
+ window_exposed_value.As<v8::Boolean>()->Value());
+ }
+
+ return info;
+}
+
+// Add a filter to |event_name| in |extension_id|, returning true if it
+// was the first filter for that event in that extension.
+bool AddFilter(const std::string& event_name,
+ const std::string& extension_id,
+ const base::DictionaryValue& filter) {
+ FilteredEventListenerKey key(extension_id, event_name);
+ FilteredEventListenerCounts& all_counts = g_filtered_listener_counts.Get();
+ FilteredEventListenerCounts::const_iterator counts = all_counts.find(key);
+ if (counts == all_counts.end()) {
+ counts = all_counts.insert(std::make_pair(
+ key, make_scoped_ptr(new ValueCounter())))
+ .first;
+ }
+ return counts->second->Add(filter);
+}
+
+// Remove a filter from |event_name| in |extension_id|, returning true if it
+// was the last filter for that event in that extension.
+bool RemoveFilter(const std::string& event_name,
+ const std::string& extension_id,
+ base::DictionaryValue* filter) {
+ FilteredEventListenerKey key(extension_id, event_name);
+ FilteredEventListenerCounts& all_counts = g_filtered_listener_counts.Get();
+ FilteredEventListenerCounts::const_iterator counts = all_counts.find(key);
+ if (counts == all_counts.end())
+ return false;
+ // Note: Remove() returns true if it removed the last filter equivalent to
+ // |filter|. If there are more equivalent filters, or if there weren't any in
+ // the first place, it returns false.
+ if (counts->second->Remove(*filter)) {
+ if (counts->second->is_empty())
+ all_counts.erase(counts); // Clean up if there are no more filters.
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
+EventBindings::EventBindings(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("AttachEvent", base::Bind(&EventBindings::AttachEventHandler,
+ base::Unretained(this)));
+ RouteFunction("DetachEvent", base::Bind(&EventBindings::DetachEventHandler,
+ base::Unretained(this)));
+ RouteFunction(
+ "AttachFilteredEvent",
+ base::Bind(&EventBindings::AttachFilteredEvent, base::Unretained(this)));
+ RouteFunction("DetachFilteredEvent",
+ base::Bind(&EventBindings::DetachFilteredEventHandler,
+ base::Unretained(this)));
+ RouteFunction("MatchAgainstEventFilter",
+ base::Bind(&EventBindings::MatchAgainstEventFilter,
+ base::Unretained(this)));
+
+ // It's safe to use base::Unretained here because |context| will always
+ // outlive us.
+ context->AddInvalidationObserver(
+ base::Bind(&EventBindings::OnInvalidated, base::Unretained(this)));
+}
+
+EventBindings::~EventBindings() {}
+
+void EventBindings::AttachEventHandler(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsString());
+ AttachEvent(*v8::String::Utf8Value(args[0]));
+}
+
+void EventBindings::AttachEvent(const std::string& event_name) {
+ if (!context()->HasAccessOrThrowError(event_name))
+ return;
+
+ // Record the attachment for this context so that events can be detached when
+ // the context is destroyed.
+ //
+ // Ideally we'd CHECK that it's not already attached, however that's not
+ // possible because extensions can create and attach events themselves. Very
+ // silly, but that's the way it is. For an example of this, see
+ // chrome/test/data/extensions/api_test/events/background.js.
+ attached_event_names_.insert(event_name);
+
+ const std::string& extension_id = context()->GetExtensionID();
+ if (IncrementEventListenerCount(context(), event_name) == 1) {
+ content::RenderThread::Get()->Send(new ExtensionHostMsg_AddListener(
+ extension_id, context()->url(), event_name));
+ }
+
+ // This is called the first time the page has added a listener. Since
+ // the background page is the only lazy page, we know this is the first
+ // time this listener has been registered.
+ if (ExtensionFrameHelper::IsContextForEventPage(context())) {
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_AddLazyListener(extension_id, event_name));
+ }
+}
+
+void EventBindings::DetachEventHandler(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(2, args.Length());
+ CHECK(args[0]->IsString());
+ CHECK(args[1]->IsBoolean());
+ DetachEvent(*v8::String::Utf8Value(args[0]), args[1]->BooleanValue());
+}
+
+void EventBindings::DetachEvent(const std::string& event_name, bool is_manual) {
+ // See comment in AttachEvent().
+ attached_event_names_.erase(event_name);
+
+ const std::string& extension_id = context()->GetExtensionID();
+
+ if (DecrementEventListenerCount(context(), event_name) == 0) {
+ content::RenderThread::Get()->Send(new ExtensionHostMsg_RemoveListener(
+ extension_id, context()->url(), event_name));
+ }
+
+ // DetachEvent is called when the last listener for the context is
+ // removed. If the context is the background page, and it removes the
+ // last listener manually, then we assume that it is no longer interested
+ // in being awakened for this event.
+ if (is_manual && ExtensionFrameHelper::IsContextForEventPage(context())) {
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_RemoveLazyListener(extension_id, event_name));
+ }
+}
+
+// MatcherID AttachFilteredEvent(string event_name, object filter)
+// event_name - Name of the event to attach.
+// filter - Which instances of the named event are we interested in.
+// returns the id assigned to the listener, which will be returned from calls
+// to MatchAgainstEventFilter where this listener matches.
+void EventBindings::AttachFilteredEvent(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(2, args.Length());
+ CHECK(args[0]->IsString());
+ CHECK(args[1]->IsObject());
+
+ std::string event_name = *v8::String::Utf8Value(args[0]);
+ if (!context()->HasAccessOrThrowError(event_name))
+ return;
+
+ scoped_ptr<base::DictionaryValue> filter;
+ {
+ scoped_ptr<content::V8ValueConverter> converter(
+ content::V8ValueConverter::create());
+ scoped_ptr<base::Value> filter_value(converter->FromV8Value(
+ v8::Local<v8::Object>::Cast(args[1]), context()->v8_context()));
+ if (!filter_value || !filter_value->IsType(base::Value::TYPE_DICTIONARY)) {
+ args.GetReturnValue().Set(static_cast<int32_t>(-1));
+ return;
+ }
+ filter = base::DictionaryValue::From(std::move(filter_value));
+ }
+
+ // Hold onto a weak reference to |filter| so that it can be used after passing
+ // ownership to |event_filter|.
+ base::DictionaryValue* filter_weak = filter.get();
+ int id = g_event_filter.Get().AddEventMatcher(
+ event_name, ParseEventMatcher(std::move(filter)));
+ attached_matcher_ids_.insert(id);
+
+ // Only send IPCs the first time a filter gets added.
+ std::string extension_id = context()->GetExtensionID();
+ if (AddFilter(event_name, extension_id, *filter_weak)) {
+ bool lazy = ExtensionFrameHelper::IsContextForEventPage(context());
+ content::RenderThread::Get()->Send(new ExtensionHostMsg_AddFilteredListener(
+ extension_id, event_name, *filter_weak, lazy));
+ }
+
+ args.GetReturnValue().Set(static_cast<int32_t>(id));
+}
+
+void EventBindings::DetachFilteredEventHandler(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(2, args.Length());
+ CHECK(args[0]->IsInt32());
+ CHECK(args[1]->IsBoolean());
+ DetachFilteredEvent(args[0]->Int32Value(), args[1]->BooleanValue());
+}
+
+void EventBindings::DetachFilteredEvent(int matcher_id, bool is_manual) {
+ EventFilter& event_filter = g_event_filter.Get();
+ EventMatcher* event_matcher = event_filter.GetEventMatcher(matcher_id);
+
+ const std::string& event_name = event_filter.GetEventName(matcher_id);
+
+ // Only send IPCs the last time a filter gets removed.
+ std::string extension_id = context()->GetExtensionID();
+ if (RemoveFilter(event_name, extension_id, event_matcher->value())) {
+ bool remove_lazy =
+ is_manual && ExtensionFrameHelper::IsContextForEventPage(context());
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_RemoveFilteredListener(
+ extension_id, event_name, *event_matcher->value(), remove_lazy));
+ }
+
+ event_filter.RemoveEventMatcher(matcher_id);
+ attached_matcher_ids_.erase(matcher_id);
+}
+
+void EventBindings::MatchAgainstEventFilter(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ v8::Isolate* isolate = args.GetIsolate();
+ typedef std::set<EventFilter::MatcherID> MatcherIDs;
+ EventFilter& event_filter = g_event_filter.Get();
+ std::string event_name = *v8::String::Utf8Value(args[0]);
+ EventFilteringInfo info =
+ ParseFromObject(args[1]->ToObject(isolate), isolate);
+ // Only match events routed to this context's RenderFrame or ones that don't
+ // have a routingId in their filter.
+ MatcherIDs matched_event_filters = event_filter.MatchEvent(
+ event_name, info, context()->GetRenderFrame()->GetRoutingID());
+ v8::Local<v8::Array> array(
+ v8::Array::New(isolate, matched_event_filters.size()));
+ int i = 0;
+ for (MatcherIDs::iterator it = matched_event_filters.begin();
+ it != matched_event_filters.end();
+ ++it) {
+ array->Set(v8::Integer::New(isolate, i++), v8::Integer::New(isolate, *it));
+ }
+ args.GetReturnValue().Set(array);
+}
+
+scoped_ptr<EventMatcher> EventBindings::ParseEventMatcher(
+ scoped_ptr<base::DictionaryValue> filter) {
+ return make_scoped_ptr(new EventMatcher(
+ std::move(filter), context()->GetRenderFrame()->GetRoutingID()));
+}
+
+void EventBindings::OnInvalidated() {
+ // Detach all attached events that weren't attached. Iterate over a copy
+ // because it will be mutated.
+ std::set<std::string> attached_event_names_safe = attached_event_names_;
+ for (const std::string& event_name : attached_event_names_safe) {
+ DetachEvent(event_name, false /* is_manual */);
+ }
+ DCHECK(attached_event_names_.empty())
+ << "Events cannot be attached during invalidation";
+
+ // Same for filtered events.
+ std::set<int> attached_matcher_ids_safe = attached_matcher_ids_;
+ for (int matcher_id : attached_matcher_ids_safe) {
+ DetachFilteredEvent(matcher_id, false /* is_manual */);
+ }
+ DCHECK(attached_matcher_ids_.empty())
+ << "Filtered events cannot be attached during invalidation";
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/event_bindings.h b/chromium/extensions/renderer/event_bindings.h
new file mode 100644
index 00000000000..8c179983717
--- /dev/null
+++ b/chromium/extensions/renderer/event_bindings.h
@@ -0,0 +1,89 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_EVENT_BINDINGS_H_
+#define EXTENSIONS_RENDERER_EVENT_BINDINGS_H_
+
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace extensions {
+class EventMatcher;
+
+// This class deals with the javascript bindings related to Event objects.
+class EventBindings : public ObjectBackedNativeHandler {
+ public:
+ explicit EventBindings(ScriptContext* context);
+ ~EventBindings() override;
+
+ private:
+ // JavaScript handler which forwards to AttachEvent().
+ // args[0] forwards to |event_name|.
+ void AttachEventHandler(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Attach an event name to an object.
+ // |event_name| The name of the event to attach.
+ void AttachEvent(const std::string& event_name);
+
+ // JavaScript handler which forwards to DetachEvent().
+ // args[0] forwards to |event_name|.
+ // args[1] forwards to |is_manual|.
+ void DetachEventHandler(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Detaches an event name from an object.
+ // |event_name| The name of the event to stop listening to.
+ // |is_manual| True if this detach was done by the user via removeListener()
+ // as opposed to automatically during shutdown, in which case we should inform
+ // the browser we are no longer interested in that event.
+ void DetachEvent(const std::string& event_name, bool is_manual);
+
+ // MatcherID AttachFilteredEvent(string event_name, object filter)
+ // |event_name| Name of the event to attach.
+ // |filter| Which instances of the named event are we interested in.
+ // returns the id assigned to the listener, which will be returned from calls
+ // to MatchAgainstEventFilter where this listener matches.
+ void AttachFilteredEvent(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // JavaScript handler which forwards to DetachFilteredEvent.
+ // void DetachFilteredEvent(int id, bool manual)
+ // args[0] forwards to |matcher_id|
+ // args[1] forwards to |is_manual|
+ void DetachFilteredEventHandler(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Detaches a filtered event. Unlike a normal event, a filtered event is
+ // identified by a unique ID per filter, not its name.
+ // |matcher_id| The ID of the filtered event.
+ // |is_manual| false if this is part of the extension unload process where all
+ // listeners are automatically detached.
+ void DetachFilteredEvent(int matcher_id, bool is_manual);
+
+ void MatchAgainstEventFilter(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ scoped_ptr<EventMatcher> ParseEventMatcher(
+ scoped_ptr<base::DictionaryValue> filter);
+
+ // Called when our context, and therefore us, is invalidated. Run any cleanup.
+ void OnInvalidated();
+
+ // The set of attached events and filtered events. Maintain these so that we
+ // can detch them on unload.
+ std::set<std::string> attached_event_names_;
+ std::set<int> attached_matcher_ids_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventBindings);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_EVENT_BINDINGS_H_
diff --git a/chromium/extensions/renderer/event_unittest.cc b/chromium/extensions/renderer/event_unittest.cc
new file mode 100644
index 00000000000..59694623dca
--- /dev/null
+++ b/chromium/extensions/renderer/event_unittest.cc
@@ -0,0 +1,259 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/extension_urls.h"
+#include "extensions/renderer/module_system_test.h"
+#include "grit/extensions_renderer_resources.h"
+
+namespace extensions {
+namespace {
+
+class EventUnittest : public ModuleSystemTest {
+ void SetUp() override {
+ ModuleSystemTest::SetUp();
+
+ env()->RegisterModule(kEventBindings, IDR_EVENT_BINDINGS_JS);
+ env()->RegisterModule("json_schema", IDR_JSON_SCHEMA_JS);
+ env()->RegisterModule(kSchemaUtils, IDR_SCHEMA_UTILS_JS);
+ env()->RegisterModule("uncaught_exception_handler",
+ IDR_UNCAUGHT_EXCEPTION_HANDLER_JS);
+ env()->RegisterModule("utils", IDR_UTILS_JS);
+
+ // Mock out the native handler for event_bindings. These mocks will fail if
+ // any invariants maintained by the real event_bindings are broken.
+ env()->OverrideNativeHandler(
+ "event_natives",
+ "var assert = requireNative('assert');"
+ "exports.$set('attachedListeners', {});"
+ "var attachedListeners = exports.attachedListeners;"
+ "exports.$set('attachedFilteredListeners', {});"
+ "var attachedFilteredListeners = exports.attachedFilteredListeners;"
+ "var nextId = 0;"
+ "var idToName = {};"
+ "exports.$set('AttachEvent', function(eventName) {"
+ " assert.AssertFalse(!!attachedListeners[eventName]);"
+ " attachedListeners[eventName] = 1;"
+ "});"
+ "exports.$set('DetachEvent', function(eventName) {"
+ " assert.AssertTrue(!!attachedListeners[eventName]);"
+ " delete attachedListeners[eventName];"
+ "});"
+ "exports.$set('IsEventAttached', function(eventName) {"
+ " return !!attachedListeners[eventName];"
+ "});"
+ "exports.$set('AttachFilteredEvent', function(name, filters) {"
+ " var id = nextId++;"
+ " idToName[id] = name;"
+ " attachedFilteredListeners[name] ="
+ " attachedFilteredListeners[name] || [];"
+ " attachedFilteredListeners[name][id] = filters;"
+ " return id;"
+ "});"
+ "exports.$set('DetachFilteredEvent', function(id, manual) {"
+ " var i = attachedFilteredListeners[idToName[id]].indexOf(id);"
+ " attachedFilteredListeners[idToName[id]].splice(i, 1);"
+ "});"
+ "exports.$set('HasFilteredListener', function(name) {"
+ " return attachedFilteredListeners[name].length;"
+ "});");
+ env()->OverrideNativeHandler("sendRequest",
+ "exports.$set('sendRequest', function() {});");
+ env()->OverrideNativeHandler(
+ "apiDefinitions",
+ "exports.$set('GetExtensionAPIDefinitionsForTest', function() {});");
+ env()->OverrideNativeHandler("logging",
+ "exports.$set('DCHECK', function() {});");
+ env()->OverrideNativeHandler("schema_registry",
+ "exports.$set('GetSchema', function() {});");
+ }
+};
+
+TEST_F(EventUnittest, TestNothing) {
+ ExpectNoAssertionsMade();
+}
+
+TEST_F(EventUnittest, AddRemoveTwoListeners) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var assert = requireNative('assert');"
+ "var Event = require('event_bindings').Event;"
+ "var eventNatives = requireNative('event_natives');"
+ "var myEvent = new Event('named-event');"
+ "var cb1 = function() {};"
+ "var cb2 = function() {};"
+ "myEvent.addListener(cb1);"
+ "myEvent.addListener(cb2);"
+ "myEvent.removeListener(cb1);"
+ "assert.AssertTrue(!!eventNatives.attachedListeners['named-event']);"
+ "myEvent.removeListener(cb2);"
+ "assert.AssertFalse(!!eventNatives.attachedListeners['named-event']);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(EventUnittest, EventsThatSupportRulesMustHaveAName) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var Event = require('event_bindings').Event;"
+ "var eventOpts = {supportsRules: true};"
+ "var assert = requireNative('assert');"
+ "var caught = false;"
+ "try {"
+ " var myEvent = new Event(undefined, undefined, eventOpts);"
+ "} catch (e) {"
+ " caught = true;"
+ "}"
+ "assert.AssertTrue(caught);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(EventUnittest, NamedEventDispatch) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var Event = require('event_bindings').Event;"
+ "var dispatchEvent = require('event_bindings').dispatchEvent;"
+ "var assert = requireNative('assert');"
+ "var e = new Event('myevent');"
+ "var called = false;"
+ "e.addListener(function() { called = true; });"
+ "dispatchEvent('myevent', []);"
+ "assert.AssertTrue(called);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(EventUnittest, AddListenerWithFiltersThrowsErrorByDefault) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("test",
+ "var Event = require('event_bindings').Event;"
+ "var assert = requireNative('assert');"
+ "var e = new Event('myevent');"
+ "var filter = [{"
+ " url: {hostSuffix: 'google.com'},"
+ "}];"
+ "var caught = false;"
+ "try {"
+ " e.addListener(function() {}, filter);"
+ "} catch (e) {"
+ " caught = true;"
+ "}"
+ "assert.AssertTrue(caught);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(EventUnittest, FilteredEventsAttachment) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var Event = require('event_bindings').Event;"
+ "var assert = requireNative('assert');"
+ "var bindings = requireNative('event_natives');"
+ "var eventOpts = {supportsListeners: true, supportsFilters: true};"
+ "var e = new Event('myevent', undefined, eventOpts);"
+ "var cb = function() {};"
+ "var filters = {url: [{hostSuffix: 'google.com'}]};"
+ "e.addListener(cb, filters);"
+ "assert.AssertTrue(bindings.HasFilteredListener('myevent'));"
+ "e.removeListener(cb);"
+ "assert.AssertFalse(bindings.HasFilteredListener('myevent'));");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(EventUnittest, DetachFilteredEvent) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var Event = require('event_bindings').Event;"
+ "var assert = requireNative('assert');"
+ "var bindings = requireNative('event_natives');"
+ "var eventOpts = {supportsListeners: true, supportsFilters: true};"
+ "var e = new Event('myevent', undefined, eventOpts);"
+ "var cb1 = function() {};"
+ "var cb2 = function() {};"
+ "var filters = {url: [{hostSuffix: 'google.com'}]};"
+ "e.addListener(cb1, filters);"
+ "e.addListener(cb2, filters);"
+ "privates(e).impl.detach_();"
+ "assert.AssertFalse(bindings.HasFilteredListener('myevent'));");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(EventUnittest, AttachAndRemoveSameFilteredEventListener) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var Event = require('event_bindings').Event;"
+ "var assert = requireNative('assert');"
+ "var bindings = requireNative('event_natives');"
+ "var eventOpts = {supportsListeners: true, supportsFilters: true};"
+ "var e = new Event('myevent', undefined, eventOpts);"
+ "var cb = function() {};"
+ "var filters = {url: [{hostSuffix: 'google.com'}]};"
+ "e.addListener(cb, filters);"
+ "e.addListener(cb, filters);"
+ "assert.AssertTrue(bindings.HasFilteredListener('myevent'));"
+ "e.removeListener(cb);"
+ "assert.AssertTrue(bindings.HasFilteredListener('myevent'));"
+ "e.removeListener(cb);"
+ "assert.AssertFalse(bindings.HasFilteredListener('myevent'));");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(EventUnittest, AddingFilterWithUrlFieldNotAListThrowsException) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var Event = require('event_bindings').Event;"
+ "var assert = requireNative('assert');"
+ "var eventOpts = {supportsListeners: true, supportsFilters: true};"
+ "var e = new Event('myevent', undefined, eventOpts);"
+ "var cb = function() {};"
+ "var filters = {url: {hostSuffix: 'google.com'}};"
+ "var caught = false;"
+ "try {"
+ " e.addListener(cb, filters);"
+ "} catch (e) {"
+ " caught = true;"
+ "}"
+ "assert.AssertTrue(caught);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(EventUnittest, MaxListeners) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var Event = require('event_bindings').Event;"
+ "var assert = requireNative('assert');"
+ "var eventOpts = {supportsListeners: true, maxListeners: 1};"
+ "var e = new Event('myevent', undefined, eventOpts);"
+ "var cb = function() {};"
+ "var caught = false;"
+ "try {"
+ " e.addListener(cb);"
+ "} catch (e) {"
+ " caught = true;"
+ "}"
+ "assert.AssertTrue(!caught);"
+ "try {"
+ " e.addListener(cb);"
+ "} catch (e) {"
+ " caught = true;"
+ "}"
+ "assert.AssertTrue(caught);");
+ env()->module_system()->Require("test");
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/renderer/extension_frame_helper.cc b/chromium/extensions/renderer/extension_frame_helper.cc
new file mode 100644
index 00000000000..ba47a7c7488
--- /dev/null
+++ b/chromium/extensions/renderer/extension_frame_helper.cc
@@ -0,0 +1,277 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/extension_frame_helper.h"
+
+#include "base/strings/string_util.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/api/messaging/message.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/manifest_handlers/background_info.h"
+#include "extensions/renderer/console.h"
+#include "extensions/renderer/content_watcher.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/messaging_bindings.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/platform/WebSecurityOrigin.h"
+#include "third_party/WebKit/public/web/WebConsoleMessage.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+
+namespace extensions {
+
+namespace {
+
+base::LazyInstance<std::set<const ExtensionFrameHelper*>> g_frame_helpers =
+ LAZY_INSTANCE_INITIALIZER;
+
+// Returns true if the render frame corresponding with |frame_helper| matches
+// the given criteria.
+bool RenderFrameMatches(const ExtensionFrameHelper* frame_helper,
+ ViewType match_view_type,
+ int match_window_id,
+ const std::string& match_extension_id) {
+ if (match_view_type != VIEW_TYPE_INVALID &&
+ frame_helper->view_type() != match_view_type)
+ return false;
+
+ // Not all frames have a valid ViewType, e.g. devtools, most GuestViews, and
+ // unclassified detached WebContents.
+ if (frame_helper->view_type() == VIEW_TYPE_INVALID)
+ return false;
+
+ // This logic matches ExtensionWebContentsObserver::GetExtensionFromFrame.
+ blink::WebSecurityOrigin origin =
+ frame_helper->render_frame()->GetWebFrame()->getSecurityOrigin();
+ if (origin.isUnique() ||
+ !base::EqualsASCII(base::StringPiece16(origin.protocol()),
+ kExtensionScheme) ||
+ !base::EqualsASCII(base::StringPiece16(origin.host()),
+ match_extension_id.c_str()))
+ return false;
+
+ if (match_window_id != extension_misc::kUnknownWindowId &&
+ frame_helper->browser_window_id() != match_window_id)
+ return false;
+ return true;
+}
+
+// Runs every callback in |callbacks_to_be_run_and_cleared| while |frame_helper|
+// is valid, and clears |callbacks_to_be_run_and_cleared|.
+void RunCallbacksWhileFrameIsValid(
+ base::WeakPtr<ExtensionFrameHelper> frame_helper,
+ std::vector<base::Closure>* callbacks_to_be_run_and_cleared) {
+ // The JavaScript code can cause re-entrancy. To avoid a deadlock, don't run
+ // callbacks that are added during the iteration.
+ std::vector<base::Closure> callbacks;
+ callbacks_to_be_run_and_cleared->swap(callbacks);
+ for (auto& callback : callbacks) {
+ callback.Run();
+ if (!frame_helper.get())
+ return; // Frame and ExtensionFrameHelper invalidated by callback.
+ }
+}
+
+} // namespace
+
+ExtensionFrameHelper::ExtensionFrameHelper(content::RenderFrame* render_frame,
+ Dispatcher* extension_dispatcher)
+ : content::RenderFrameObserver(render_frame),
+ content::RenderFrameObserverTracker<ExtensionFrameHelper>(render_frame),
+ view_type_(VIEW_TYPE_INVALID),
+ tab_id_(-1),
+ browser_window_id_(-1),
+ extension_dispatcher_(extension_dispatcher),
+ did_create_current_document_element_(false),
+ weak_ptr_factory_(this) {
+ g_frame_helpers.Get().insert(this);
+}
+
+ExtensionFrameHelper::~ExtensionFrameHelper() {
+ g_frame_helpers.Get().erase(this);
+}
+
+// static
+std::vector<content::RenderFrame*> ExtensionFrameHelper::GetExtensionFrames(
+ const std::string& extension_id,
+ int browser_window_id,
+ ViewType view_type) {
+ std::vector<content::RenderFrame*> render_frames;
+ for (const ExtensionFrameHelper* helper : g_frame_helpers.Get()) {
+ if (RenderFrameMatches(helper, view_type, browser_window_id, extension_id))
+ render_frames.push_back(helper->render_frame());
+ }
+ return render_frames;
+}
+
+// static
+content::RenderFrame* ExtensionFrameHelper::GetBackgroundPageFrame(
+ const std::string& extension_id) {
+ for (const ExtensionFrameHelper* helper : g_frame_helpers.Get()) {
+ if (RenderFrameMatches(helper, VIEW_TYPE_EXTENSION_BACKGROUND_PAGE,
+ extension_misc::kUnknownWindowId, extension_id)) {
+ blink::WebLocalFrame* web_frame = helper->render_frame()->GetWebFrame();
+ // Check if this is the top frame.
+ if (web_frame->top() == web_frame)
+ return helper->render_frame();
+ }
+ }
+ return nullptr;
+}
+
+// static
+bool ExtensionFrameHelper::IsContextForEventPage(const ScriptContext* context) {
+ content::RenderFrame* render_frame = context->GetRenderFrame();
+ return context->extension() && render_frame &&
+ BackgroundInfo::HasLazyBackgroundPage(context->extension()) &&
+ ExtensionFrameHelper::Get(render_frame)->view_type() ==
+ VIEW_TYPE_EXTENSION_BACKGROUND_PAGE;
+}
+
+void ExtensionFrameHelper::DidCreateDocumentElement() {
+ did_create_current_document_element_ = true;
+ extension_dispatcher_->DidCreateDocumentElement(
+ render_frame()->GetWebFrame());
+}
+
+void ExtensionFrameHelper::DidCreateNewDocument() {
+ did_create_current_document_element_ = false;
+}
+
+void ExtensionFrameHelper::RunScriptsAtDocumentStart() {
+ DCHECK(did_create_current_document_element_);
+ RunCallbacksWhileFrameIsValid(weak_ptr_factory_.GetWeakPtr(),
+ &document_element_created_callbacks_);
+ // |this| might be dead by now.
+}
+
+void ExtensionFrameHelper::RunScriptsAtDocumentEnd() {
+ RunCallbacksWhileFrameIsValid(weak_ptr_factory_.GetWeakPtr(),
+ &document_load_finished_callbacks_);
+ // |this| might be dead by now.
+}
+
+void ExtensionFrameHelper::ScheduleAtDocumentStart(
+ const base::Closure& callback) {
+ document_element_created_callbacks_.push_back(callback);
+}
+
+void ExtensionFrameHelper::ScheduleAtDocumentEnd(
+ const base::Closure& callback) {
+ document_load_finished_callbacks_.push_back(callback);
+}
+
+void ExtensionFrameHelper::DidMatchCSS(
+ const blink::WebVector<blink::WebString>& newly_matching_selectors,
+ const blink::WebVector<blink::WebString>& stopped_matching_selectors) {
+ extension_dispatcher_->content_watcher()->DidMatchCSS(
+ render_frame()->GetWebFrame(), newly_matching_selectors,
+ stopped_matching_selectors);
+}
+
+void ExtensionFrameHelper::DidCreateScriptContext(
+ v8::Local<v8::Context> context,
+ int extension_group,
+ int world_id) {
+ extension_dispatcher_->DidCreateScriptContext(
+ render_frame()->GetWebFrame(), context, extension_group, world_id);
+}
+
+void ExtensionFrameHelper::WillReleaseScriptContext(
+ v8::Local<v8::Context> context,
+ int world_id) {
+ extension_dispatcher_->WillReleaseScriptContext(
+ render_frame()->GetWebFrame(), context, world_id);
+}
+
+bool ExtensionFrameHelper::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(ExtensionFrameHelper, message)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnConnect,
+ OnExtensionDispatchOnConnect)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_DeliverMessage, OnExtensionDeliverMessage)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnDisconnect,
+ OnExtensionDispatchOnDisconnect)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_SetTabId, OnExtensionSetTabId)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateBrowserWindowId,
+ OnUpdateBrowserWindowId)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_NotifyRenderViewType,
+ OnNotifyRendererViewType)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_Response, OnExtensionResponse)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_MessageInvoke, OnExtensionMessageInvoke)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void ExtensionFrameHelper::OnExtensionDispatchOnConnect(
+ int target_port_id,
+ const std::string& channel_name,
+ const ExtensionMsg_TabConnectionInfo& source,
+ const ExtensionMsg_ExternalConnectionInfo& info,
+ const std::string& tls_channel_id) {
+ MessagingBindings::DispatchOnConnect(
+ extension_dispatcher_->script_context_set(),
+ target_port_id,
+ channel_name,
+ source,
+ info,
+ tls_channel_id,
+ render_frame());
+}
+
+void ExtensionFrameHelper::OnExtensionDeliverMessage(int target_id,
+ const Message& message) {
+ MessagingBindings::DeliverMessage(
+ extension_dispatcher_->script_context_set(), target_id, message,
+ render_frame());
+}
+
+void ExtensionFrameHelper::OnExtensionDispatchOnDisconnect(
+ int port_id,
+ const std::string& error_message) {
+ MessagingBindings::DispatchOnDisconnect(
+ extension_dispatcher_->script_context_set(), port_id, error_message,
+ render_frame());
+}
+
+void ExtensionFrameHelper::OnExtensionSetTabId(int tab_id) {
+ CHECK_EQ(tab_id_, -1);
+ CHECK_GE(tab_id, 0);
+ tab_id_ = tab_id;
+}
+
+void ExtensionFrameHelper::OnUpdateBrowserWindowId(int browser_window_id) {
+ browser_window_id_ = browser_window_id;
+}
+
+void ExtensionFrameHelper::OnNotifyRendererViewType(ViewType type) {
+ // TODO(devlin): It'd be really nice to be able to
+ // DCHECK_EQ(VIEW_TYPE_INVALID, view_type_) here.
+ view_type_ = type;
+}
+
+void ExtensionFrameHelper::OnExtensionResponse(int request_id,
+ bool success,
+ const base::ListValue& response,
+ const std::string& error) {
+ extension_dispatcher_->OnExtensionResponse(request_id,
+ success,
+ response,
+ error);
+}
+
+void ExtensionFrameHelper::OnExtensionMessageInvoke(
+ const std::string& extension_id,
+ const std::string& module_name,
+ const std::string& function_name,
+ const base::ListValue& args,
+ bool user_gesture) {
+ extension_dispatcher_->InvokeModuleSystemMethod(render_frame(), extension_id,
+ module_name, function_name,
+ args, user_gesture);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/extension_frame_helper.h b/chromium/extensions/renderer/extension_frame_helper.h
new file mode 100644
index 00000000000..57ac3b66b23
--- /dev/null
+++ b/chromium/extensions/renderer/extension_frame_helper.h
@@ -0,0 +1,148 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_EXTENSION_FRAME_HELPER_H_
+#define EXTENSIONS_RENDERER_EXTENSION_FRAME_HELPER_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "content/public/common/console_message_level.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "content/public/renderer/render_frame_observer_tracker.h"
+#include "extensions/common/view_type.h"
+
+struct ExtensionMsg_ExternalConnectionInfo;
+struct ExtensionMsg_TabConnectionInfo;
+
+namespace base {
+class ListValue;
+}
+
+namespace extensions {
+
+class Dispatcher;
+struct Message;
+class ScriptContext;
+
+// RenderFrame-level plumbing for extension features.
+class ExtensionFrameHelper
+ : public content::RenderFrameObserver,
+ public content::RenderFrameObserverTracker<ExtensionFrameHelper> {
+ public:
+ ExtensionFrameHelper(content::RenderFrame* render_frame,
+ Dispatcher* extension_dispatcher);
+ ~ExtensionFrameHelper() override;
+
+ // Returns a list of extension RenderFrames that match the given filter
+ // criteria. A |browser_window_id| of extension_misc::kUnknownWindowId
+ // specifies "all", as does a |view_type| of VIEW_TYPE_INVALID.
+ static std::vector<content::RenderFrame*> GetExtensionFrames(
+ const std::string& extension_id,
+ int browser_window_id,
+ ViewType view_type);
+
+ // Returns the main frame of the extension's background page, or null if there
+ // isn't one in this process.
+ static content::RenderFrame* GetBackgroundPageFrame(
+ const std::string& extension_id);
+
+ // Returns true if the given |context| is for any frame in the extension's
+ // event page.
+ // TODO(devlin): This isn't really used properly, and should probably be
+ // deleted.
+ static bool IsContextForEventPage(const ScriptContext* context);
+
+ ViewType view_type() const { return view_type_; }
+ int tab_id() const { return tab_id_; }
+ int browser_window_id() const { return browser_window_id_; }
+ bool did_create_current_document_element() const {
+ return did_create_current_document_element_;
+ }
+
+ // Called when the document element has been inserted in this frame. This
+ // method may invoke untrusted JavaScript code that invalidate the frame and
+ // this ExtensionFrameHelper.
+ void RunScriptsAtDocumentStart();
+
+ // Called after the DOMContentLoaded event has fired.
+ void RunScriptsAtDocumentEnd();
+
+ // Schedule a callback, to be run at the next RunScriptsAtDocumentStart
+ // notification. Only call this when you are certain that there will be such a
+ // notification, e.g. from RenderFrameObserver::DidCreateDocumentElement.
+ // Otherwise the callback is never invoked, or invoked for a document that you
+ // were not expecting.
+ void ScheduleAtDocumentStart(const base::Closure& callback);
+
+ // Schedule a callback, to be run at the next RunScriptsAtDocumentEnd call.
+ void ScheduleAtDocumentEnd(const base::Closure& callback);
+
+ private:
+ // RenderFrameObserver implementation.
+ void DidCreateDocumentElement() override;
+ void DidCreateNewDocument() override;
+ void DidMatchCSS(
+ const blink::WebVector<blink::WebString>& newly_matching_selectors,
+ const blink::WebVector<blink::WebString>& stopped_matching_selectors)
+ override;
+ void DidCreateScriptContext(v8::Local<v8::Context>,
+ int extension_group,
+ int world_id) override;
+ void WillReleaseScriptContext(v8::Local<v8::Context>, int world_id) override;
+ bool OnMessageReceived(const IPC::Message& message) override;
+
+ // IPC handlers.
+ void OnExtensionDispatchOnConnect(
+ int target_port_id,
+ const std::string& channel_name,
+ const ExtensionMsg_TabConnectionInfo& source,
+ const ExtensionMsg_ExternalConnectionInfo& info,
+ const std::string& tls_channel_id);
+ void OnExtensionDeliverMessage(int target_port_id,
+ const Message& message);
+ void OnExtensionDispatchOnDisconnect(int port_id,
+ const std::string& error_message);
+ void OnExtensionSetTabId(int tab_id);
+ void OnUpdateBrowserWindowId(int browser_window_id);
+ void OnNotifyRendererViewType(ViewType view_type);
+ void OnExtensionResponse(int request_id,
+ bool success,
+ const base::ListValue& response,
+ const std::string& error);
+ void OnExtensionMessageInvoke(const std::string& extension_id,
+ const std::string& module_name,
+ const std::string& function_name,
+ const base::ListValue& args,
+ bool user_gesture);
+
+ // Type of view associated with the RenderFrame.
+ ViewType view_type_;
+
+ // The id of the tab the render frame is attached to.
+ int tab_id_;
+
+ // The id of the browser window the render frame is attached to.
+ int browser_window_id_;
+
+ Dispatcher* extension_dispatcher_;
+
+ // Whether or not the current document element has been created.
+ bool did_create_current_document_element_;
+
+ // Callbacks to be run at the next RunScriptsAtDocumentStart notification.
+ std::vector<base::Closure> document_element_created_callbacks_;
+
+ // Callbacks to be run at the next RunScriptsAtDocumentEnd notification.
+ std::vector<base::Closure> document_load_finished_callbacks_;
+
+ base::WeakPtrFactory<ExtensionFrameHelper> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionFrameHelper);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_EXTENSION_FRAME_HELPER_H_
diff --git a/chromium/extensions/renderer/extension_groups.h b/chromium/extensions/renderer/extension_groups.h
new file mode 100644
index 00000000000..9766fa4ed45
--- /dev/null
+++ b/chromium/extensions/renderer/extension_groups.h
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_EXTENSION_GROUPS_H_
+#define EXTENSIONS_RENDERER_EXTENSION_GROUPS_H_
+
+namespace extensions {
+
+// A set of extension groups for use with blink::registerExtension and
+// WebFrame::ExecuteScriptInNewWorld to control which extensions get loaded
+// into which contexts.
+// TODO(kalman): Remove this when https://crbug.com/481699 is fixed.
+enum ExtensionGroups {
+ // Use this to mark extensions to be loaded into content scripts only.
+ EXTENSION_GROUP_CONTENT_SCRIPTS = 1,
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_EXTENSION_GROUPS_H_
diff --git a/chromium/extensions/renderer/extension_helper.cc b/chromium/extensions/renderer/extension_helper.cc
new file mode 100644
index 00000000000..4d3bd2711dc
--- /dev/null
+++ b/chromium/extensions/renderer/extension_helper.cc
@@ -0,0 +1,76 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/extension_helper.h"
+
+#include <stddef.h>
+
+#include "content/public/renderer/render_view.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/url_pattern_set.h"
+#include "extensions/renderer/api/automation/automation_api_helper.h"
+#include "extensions/renderer/dispatcher.h"
+#include "third_party/WebKit/public/platform/WebURLRequest.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebView.h"
+
+namespace extensions {
+
+ExtensionHelper::ExtensionHelper(content::RenderView* render_view,
+ Dispatcher* dispatcher)
+ : content::RenderViewObserver(render_view),
+ dispatcher_(dispatcher) {
+ // Lifecycle managed by RenderViewObserver.
+ new AutomationApiHelper(render_view);
+}
+
+ExtensionHelper::~ExtensionHelper() {
+}
+
+bool ExtensionHelper::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(ExtensionHelper, message)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_SetFrameName, OnSetFrameName)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_AppWindowClosed,
+ OnAppWindowClosed)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void ExtensionHelper::DraggableRegionsChanged(blink::WebFrame* frame) {
+ blink::WebVector<blink::WebDraggableRegion> webregions =
+ frame->document().draggableRegions();
+ std::vector<DraggableRegion> regions;
+ for (size_t i = 0; i < webregions.size(); ++i) {
+ DraggableRegion region;
+ render_view()->ConvertViewportToWindowViaWidget(&webregions[i].bounds);
+ region.bounds = webregions[i].bounds;
+ region.draggable = webregions[i].draggable;
+ regions.push_back(region);
+ }
+ Send(new ExtensionHostMsg_UpdateDraggableRegions(routing_id(), regions));
+}
+
+void ExtensionHelper::OnSetFrameName(const std::string& name) {
+ blink::WebView* web_view = render_view()->GetWebView();
+ if (web_view)
+ web_view->mainFrame()->setName(blink::WebString::fromUTF8(name));
+}
+
+void ExtensionHelper::OnAppWindowClosed() {
+ v8::HandleScope scope(v8::Isolate::GetCurrent());
+ v8::Local<v8::Context> v8_context =
+ render_view()->GetWebView()->mainFrame()->mainWorldScriptContext();
+ ScriptContext* script_context =
+ dispatcher_->script_context_set().GetByV8Context(v8_context);
+ if (!script_context)
+ return;
+ script_context->module_system()->CallModuleMethod("app.window",
+ "onAppWindowClosed");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/extension_helper.h b/chromium/extensions/renderer/extension_helper.h
new file mode 100644
index 00000000000..a25a0a3f4ca
--- /dev/null
+++ b/chromium/extensions/renderer/extension_helper.h
@@ -0,0 +1,37 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_EXTENSION_HELPER_H_
+#define EXTENSIONS_RENDERER_EXTENSION_HELPER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "content/public/renderer/render_view_observer.h"
+
+namespace extensions {
+class Dispatcher;
+
+// RenderView-level plumbing for extension features.
+class ExtensionHelper : public content::RenderViewObserver {
+ public:
+ ExtensionHelper(content::RenderView* render_view, Dispatcher* dispatcher);
+ ~ExtensionHelper() override;
+
+ private:
+ // RenderViewObserver implementation.
+ bool OnMessageReceived(const IPC::Message& message) override;
+ void DraggableRegionsChanged(blink::WebFrame* frame) override;
+
+ void OnAppWindowClosed();
+ void OnSetFrameName(const std::string& name);
+
+ Dispatcher* dispatcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionHelper);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_EXTENSION_HELPER_H_
diff --git a/chromium/extensions/renderer/extension_injection_host.cc b/chromium/extensions/renderer/extension_injection_host.cc
new file mode 100644
index 00000000000..6e5b0422871
--- /dev/null
+++ b/chromium/extensions/renderer/extension_injection_host.cc
@@ -0,0 +1,88 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/extension_injection_host.h"
+
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/manifest_handlers/csp_info.h"
+#include "extensions/renderer/renderer_extension_registry.h"
+#include "third_party/WebKit/public/platform/WebSecurityOrigin.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+
+namespace extensions {
+
+ExtensionInjectionHost::ExtensionInjectionHost(
+ const Extension* extension)
+ : InjectionHost(HostID(HostID::EXTENSIONS, extension->id())),
+ extension_(extension) {
+}
+
+ExtensionInjectionHost::~ExtensionInjectionHost() {
+}
+
+// static
+scoped_ptr<const InjectionHost> ExtensionInjectionHost::Create(
+ const std::string& extension_id) {
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(extension_id);
+ if (!extension)
+ return scoped_ptr<const ExtensionInjectionHost>();
+ return scoped_ptr<const ExtensionInjectionHost>(
+ new ExtensionInjectionHost(extension));
+}
+
+std::string ExtensionInjectionHost::GetContentSecurityPolicy() const {
+ return CSPInfo::GetContentSecurityPolicy(extension_);
+}
+
+const GURL& ExtensionInjectionHost::url() const {
+ return extension_->url();
+}
+
+const std::string& ExtensionInjectionHost::name() const {
+ return extension_->name();
+}
+
+PermissionsData::AccessType ExtensionInjectionHost::CanExecuteOnFrame(
+ const GURL& document_url,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ bool is_declarative) const {
+ blink::WebSecurityOrigin top_frame_security_origin =
+ render_frame->GetWebFrame()->top()->getSecurityOrigin();
+ // Only whitelisted extensions may run scripts on another extension's page.
+ if (top_frame_security_origin.protocol().utf8() == kExtensionScheme &&
+ top_frame_security_origin.host().utf8() != extension_->id() &&
+ !PermissionsData::CanExecuteScriptEverywhere(extension_))
+ return PermissionsData::ACCESS_DENIED;
+
+ // Declarative user scripts use "page access" (from "permissions" section in
+ // manifest) whereas non-declarative user scripts use custom
+ // "content script access" logic.
+ PermissionsData::AccessType access = PermissionsData::ACCESS_ALLOWED;
+ if (is_declarative) {
+ access = extension_->permissions_data()->GetPageAccess(
+ extension_,
+ document_url,
+ tab_id,
+ nullptr /* ignore error */);
+ } else {
+ access = extension_->permissions_data()->GetContentScriptAccess(
+ extension_,
+ document_url,
+ tab_id,
+ nullptr /* ignore error */);
+ }
+ if (access == PermissionsData::ACCESS_WITHHELD &&
+ (tab_id == -1 || render_frame->GetWebFrame()->parent())) {
+ // Note: we don't consider ACCESS_WITHHELD for child frames or for frames
+ // outside of tabs because there is nowhere to surface a request.
+ // TODO(devlin): We should ask for permission somehow. crbug.com/491402.
+ access = PermissionsData::ACCESS_DENIED;
+ }
+ return access;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/extension_injection_host.h b/chromium/extensions/renderer/extension_injection_host.h
new file mode 100644
index 00000000000..a3c9d320dfd
--- /dev/null
+++ b/chromium/extensions/renderer/extension_injection_host.h
@@ -0,0 +1,45 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_EXTENSION_INJECTION_HOST_H_
+#define EXTENSIONS_RENDERER_EXTENSION_INJECTION_HOST_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "extensions/common/extension.h"
+#include "extensions/renderer/injection_host.h"
+
+namespace extensions {
+
+// A wrapper class that holds an extension and implements the InjectionHost
+// interface.
+class ExtensionInjectionHost : public InjectionHost {
+ public:
+ ExtensionInjectionHost(const Extension* extension);
+ ~ExtensionInjectionHost() override;
+
+ // Create an ExtensionInjectionHost object. If the extension is gone, returns
+ // a null scoped ptr.
+ static scoped_ptr<const InjectionHost> Create(
+ const std::string& extension_id);
+
+ private:
+ // InjectionHost:
+ std::string GetContentSecurityPolicy() const override;
+ const GURL& url() const override;
+ const std::string& name() const override;
+ PermissionsData::AccessType CanExecuteOnFrame(
+ const GURL& document_url,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ bool is_declarative) const override;
+
+ const Extension* extension_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionInjectionHost);
+};
+
+} // namespace extesions
+
+#endif // EXTENSIONS_RENDERER_EXTENSION_INJECTION_HOST_H_
diff --git a/chromium/extensions/renderer/extensions_render_frame_observer.cc b/chromium/extensions/renderer/extensions_render_frame_observer.cc
new file mode 100644
index 00000000000..3f66f46256a
--- /dev/null
+++ b/chromium/extensions/renderer/extensions_render_frame_observer.cc
@@ -0,0 +1,100 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/extensions_render_frame_observer.h"
+
+#include <stddef.h>
+
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/stack_frame.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+
+namespace extensions {
+
+namespace {
+
+// The delimiter for a stack trace provided by WebKit.
+const char kStackFrameDelimiter[] = "\n at ";
+
+// Get a stack trace from a WebKit console message.
+// There are three possible scenarios:
+// 1. WebKit gives us a stack trace in |stack_trace|.
+// 2. The stack trace is embedded in the error |message| by an internal
+// script. This will be more useful than |stack_trace|, since |stack_trace|
+// will include the internal bindings trace, instead of a developer's code.
+// 3. No stack trace is included. In this case, we should mock one up from
+// the given line number and source.
+// |message| will be populated with the error message only (i.e., will not
+// include any stack trace).
+StackTrace GetStackTraceFromMessage(base::string16* message,
+ const base::string16& source,
+ const base::string16& stack_trace,
+ int32_t line_number) {
+ StackTrace result;
+ std::vector<base::string16> pieces;
+ size_t index = 0;
+
+ if (message->find(base::UTF8ToUTF16(kStackFrameDelimiter)) !=
+ base::string16::npos) {
+ base::SplitStringUsingSubstr(*message,
+ base::UTF8ToUTF16(kStackFrameDelimiter),
+ &pieces);
+ *message = pieces[0];
+ index = 1;
+ } else if (!stack_trace.empty()) {
+ base::SplitStringUsingSubstr(stack_trace,
+ base::UTF8ToUTF16(kStackFrameDelimiter),
+ &pieces);
+ }
+
+ // If we got a stack trace, parse each frame from the text.
+ if (index < pieces.size()) {
+ for (; index < pieces.size(); ++index) {
+ scoped_ptr<StackFrame> frame = StackFrame::CreateFromText(pieces[index]);
+ if (frame.get())
+ result.push_back(*frame);
+ }
+ }
+
+ if (result.empty()) { // If we don't have a stack trace, mock one up.
+ result.push_back(
+ StackFrame(line_number,
+ 1u, // column number
+ source,
+ base::string16() /* no function name */ ));
+ }
+
+ return result;
+}
+
+} // namespace
+
+ExtensionsRenderFrameObserver::ExtensionsRenderFrameObserver(
+ content::RenderFrame* render_frame)
+ : content::RenderFrameObserver(render_frame) {
+}
+
+ExtensionsRenderFrameObserver::~ExtensionsRenderFrameObserver() {
+}
+
+void ExtensionsRenderFrameObserver::DetailedConsoleMessageAdded(
+ const base::string16& message,
+ const base::string16& source,
+ const base::string16& stack_trace_string,
+ uint32_t line_number,
+ int32_t severity_level) {
+ base::string16 trimmed_message = message;
+ StackTrace stack_trace = GetStackTraceFromMessage(
+ &trimmed_message,
+ source,
+ stack_trace_string,
+ line_number);
+ Send(new ExtensionHostMsg_DetailedConsoleMessageAdded(
+ routing_id(), trimmed_message, source, stack_trace, severity_level));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/extensions_render_frame_observer.h b/chromium/extensions/renderer/extensions_render_frame_observer.h
new file mode 100644
index 00000000000..55521b2b7b5
--- /dev/null
+++ b/chromium/extensions/renderer/extensions_render_frame_observer.h
@@ -0,0 +1,38 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_EXTENSIONS_RENDER_FRAME_OBSERVER_H_
+#define EXTENSIONS_RENDERER_EXTENSIONS_RENDER_FRAME_OBSERVER_H_
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "content/public/renderer/render_frame_observer.h"
+
+namespace extensions {
+
+// This class holds the extensions specific parts of RenderFrame, and has the
+// same lifetime.
+class ExtensionsRenderFrameObserver
+ : public content::RenderFrameObserver {
+ public:
+ explicit ExtensionsRenderFrameObserver(
+ content::RenderFrame* render_frame);
+ ~ExtensionsRenderFrameObserver() override;
+
+ private:
+ // RenderFrameObserver implementation.
+ void DetailedConsoleMessageAdded(const base::string16& message,
+ const base::string16& source,
+ const base::string16& stack_trace,
+ uint32_t line_number,
+ int32_t severity_level) override;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionsRenderFrameObserver);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_EXTENSIONS_RENDER_FRAME_OBSERVER_H_
+
diff --git a/chromium/extensions/renderer/extensions_renderer_client.cc b/chromium/extensions/renderer/extensions_renderer_client.cc
new file mode 100644
index 00000000000..26c46ed70ed
--- /dev/null
+++ b/chromium/extensions/renderer/extensions_renderer_client.cc
@@ -0,0 +1,26 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/extensions_renderer_client.h"
+
+#include "base/logging.h"
+
+namespace extensions {
+
+namespace {
+
+ExtensionsRendererClient* g_client = NULL;
+
+} // namespace
+
+ExtensionsRendererClient* ExtensionsRendererClient::Get() {
+ CHECK(g_client);
+ return g_client;
+}
+
+void ExtensionsRendererClient::Set(ExtensionsRendererClient* client) {
+ g_client = client;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/extensions_renderer_client.h b/chromium/extensions/renderer/extensions_renderer_client.h
new file mode 100644
index 00000000000..ae4a31ba689
--- /dev/null
+++ b/chromium/extensions/renderer/extensions_renderer_client.h
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_EXTENSIONS_RENDERER_CLIENT_H_
+#define EXTENSIONS_RENDERER_EXTENSIONS_RENDERER_CLIENT_H_
+
+class ResourceBundleSourceMap;
+
+namespace extensions {
+
+// Interface to allow the extensions module to make render-process-specific
+// queries of the embedder. Should be Set() once in the render process.
+//
+// NOTE: Methods that do not require knowledge of renderer concepts should be
+// added in ExtensionsClient (extensions/common/extensions_client.h) even if
+// they are only used in the renderer process.
+class ExtensionsRendererClient {
+ public:
+ virtual ~ExtensionsRendererClient() {}
+
+ // Returns true if the current render process was launched incognito.
+ virtual bool IsIncognitoProcess() const = 0;
+
+ // Returns the lowest isolated world ID available to extensions.
+ // Must be greater than 0. See blink::WebFrame::executeScriptInIsolatedWorld
+ // (third_party/WebKit/public/web/WebFrame.h) for additional context.
+ virtual int GetLowestIsolatedWorldId() const = 0;
+
+ // Returns the single instance of |this|.
+ static ExtensionsRendererClient* Get();
+
+ // Initialize the single instance.
+ static void Set(ExtensionsRendererClient* client);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_EXTENSIONS_RENDERER_CLIENT_H_
diff --git a/chromium/extensions/renderer/file_system_natives.cc b/chromium/extensions/renderer/file_system_natives.cc
new file mode 100644
index 00000000000..2b7006299a2
--- /dev/null
+++ b/chromium/extensions/renderer/file_system_natives.cc
@@ -0,0 +1,124 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/file_system_natives.h"
+
+#include <string>
+
+#include "extensions/common/constants.h"
+#include "extensions/renderer/script_context.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/web/WebDOMFileSystem.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "url/origin.h"
+
+namespace extensions {
+
+FileSystemNatives::FileSystemNatives(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction(
+ "GetFileEntry",
+ base::Bind(&FileSystemNatives::GetFileEntry, base::Unretained(this)));
+ RouteFunction("GetIsolatedFileSystem",
+ base::Bind(&FileSystemNatives::GetIsolatedFileSystem,
+ base::Unretained(this)));
+ RouteFunction("CrackIsolatedFileSystemName",
+ base::Bind(&FileSystemNatives::CrackIsolatedFileSystemName,
+ base::Unretained(this)));
+}
+
+void FileSystemNatives::GetIsolatedFileSystem(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK(args.Length() == 1 || args.Length() == 2);
+ CHECK(args[0]->IsString());
+ std::string file_system_id(*v8::String::Utf8Value(args[0]));
+ blink::WebLocalFrame* webframe =
+ blink::WebLocalFrame::frameForContext(context()->v8_context());
+ DCHECK(webframe);
+
+ GURL context_url =
+ extensions::ScriptContext::GetDataSourceURLForFrame(webframe);
+ CHECK(context_url.SchemeIs(extensions::kExtensionScheme));
+
+ const GURL origin(url::Origin(context_url).Serialize());
+ std::string name(storage::GetIsolatedFileSystemName(origin, file_system_id));
+
+ // The optional second argument is the subfolder within the isolated file
+ // system at which to root the DOMFileSystem we're returning to the caller.
+ std::string optional_root_name;
+ if (args.Length() == 2) {
+ CHECK(args[1]->IsString());
+ optional_root_name = *v8::String::Utf8Value(args[1]);
+ }
+
+ GURL root_url(storage::GetIsolatedFileSystemRootURIString(
+ origin, file_system_id, optional_root_name));
+
+ args.GetReturnValue().Set(
+ blink::WebDOMFileSystem::create(webframe,
+ blink::WebFileSystemTypeIsolated,
+ blink::WebString::fromUTF8(name),
+ root_url)
+ .toV8Value(context()->v8_context()->Global(), args.GetIsolate()));
+}
+
+void FileSystemNatives::GetFileEntry(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(5, args.Length());
+ CHECK(args[0]->IsString());
+ std::string type_string = *v8::String::Utf8Value(args[0]);
+ blink::WebFileSystemType type;
+ bool is_valid_type = storage::GetFileSystemPublicType(type_string, &type);
+ DCHECK(is_valid_type);
+ if (is_valid_type == false) {
+ return;
+ }
+
+ CHECK(args[1]->IsString());
+ CHECK(args[2]->IsString());
+ CHECK(args[3]->IsString());
+ std::string file_system_name(*v8::String::Utf8Value(args[1]));
+ GURL file_system_root_url(*v8::String::Utf8Value(args[2]));
+ std::string file_path_string(*v8::String::Utf8Value(args[3]));
+ base::FilePath file_path = base::FilePath::FromUTF8Unsafe(file_path_string);
+ DCHECK(storage::VirtualPath::IsAbsolute(file_path.value()));
+
+ CHECK(args[4]->IsBoolean());
+ blink::WebDOMFileSystem::EntryType entry_type =
+ args[4]->BooleanValue() ? blink::WebDOMFileSystem::EntryTypeDirectory
+ : blink::WebDOMFileSystem::EntryTypeFile;
+
+ blink::WebLocalFrame* webframe =
+ blink::WebLocalFrame::frameForContext(context()->v8_context());
+ DCHECK(webframe);
+ args.GetReturnValue().Set(
+ blink::WebDOMFileSystem::create(
+ webframe,
+ type,
+ blink::WebString::fromUTF8(file_system_name),
+ file_system_root_url)
+ .createV8Entry(blink::WebString::fromUTF8(file_path_string),
+ entry_type,
+ context()->v8_context()->Global(),
+ args.GetIsolate()));
+}
+
+void FileSystemNatives::CrackIsolatedFileSystemName(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ DCHECK_EQ(args.Length(), 1);
+ DCHECK(args[0]->IsString());
+ std::string filesystem_name = *v8::String::Utf8Value(args[0]);
+ std::string filesystem_id;
+ if (!storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id))
+ return;
+
+ args.GetReturnValue().Set(v8::String::NewFromUtf8(args.GetIsolate(),
+ filesystem_id.c_str(),
+ v8::String::kNormalString,
+ filesystem_id.size()));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/file_system_natives.h b/chromium/extensions/renderer/file_system_natives.h
new file mode 100644
index 00000000000..102f2200644
--- /dev/null
+++ b/chromium/extensions/renderer/file_system_natives.h
@@ -0,0 +1,31 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_FILE_SYSTEM_NATIVES_H_
+#define EXTENSIONS_RENDERER_FILE_SYSTEM_NATIVES_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+// Custom bindings for the nativeFileSystem API.
+class FileSystemNatives : public ObjectBackedNativeHandler {
+ public:
+ explicit FileSystemNatives(ScriptContext* context);
+
+ private:
+ void GetFileEntry(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void GetIsolatedFileSystem(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void CrackIsolatedFileSystemName(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemNatives);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_FILE_SYSTEM_NATIVES_H_
diff --git a/chromium/extensions/renderer/gc_callback.cc b/chromium/extensions/renderer/gc_callback.cc
new file mode 100644
index 00000000000..46b7f8acdf1
--- /dev/null
+++ b/chromium/extensions/renderer/gc_callback.cc
@@ -0,0 +1,58 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/gc_callback.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+GCCallback::GCCallback(ScriptContext* context,
+ const v8::Local<v8::Object>& object,
+ const v8::Local<v8::Function>& callback,
+ const base::Closure& fallback)
+ : context_(context),
+ object_(context->isolate(), object),
+ callback_(context->isolate(), callback),
+ fallback_(fallback),
+ weak_ptr_factory_(this) {
+ object_.SetWeak(this, OnObjectGC, v8::WeakCallbackType::kParameter);
+ context->AddInvalidationObserver(base::Bind(&GCCallback::OnContextInvalidated,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+GCCallback::~GCCallback() {}
+
+// static
+void GCCallback::OnObjectGC(const v8::WeakCallbackInfo<GCCallback>& data) {
+ // Usually FirstWeakCallback should do nothing other than reset |object_|
+ // and then set a second weak callback to run later. We can sidestep that,
+ // because posting a task to the current message loop is all but free - but
+ // DO NOT add any more work to this method. The only acceptable place to add
+ // code is RunCallback.
+ GCCallback* self = data.GetParameter();
+ self->object_.Reset();
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&GCCallback::RunCallback,
+ self->weak_ptr_factory_.GetWeakPtr()));
+}
+
+void GCCallback::RunCallback() {
+ fallback_.Reset();
+ v8::Isolate* isolate = context_->isolate();
+ v8::HandleScope handle_scope(isolate);
+ context_->CallFunction(v8::Local<v8::Function>::New(isolate, callback_));
+ delete this;
+}
+
+void GCCallback::OnContextInvalidated() {
+ if (!fallback_.is_null()) {
+ fallback_.Run();
+ delete this;
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/gc_callback.h b/chromium/extensions/renderer/gc_callback.h
new file mode 100644
index 00000000000..96dc2441aed
--- /dev/null
+++ b/chromium/extensions/renderer/gc_callback.h
@@ -0,0 +1,56 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_GC_CALLBACK_H_
+#define EXTENSIONS_RENDERER_GC_CALLBACK_H_
+
+#include <map>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+class ScriptContext;
+
+// Runs |callback| when v8 garbage collects |object|, or |fallback| if
+// |context| is invalidated first. Exactly one of |callback| or |fallback| will
+// be called, after which it deletes itself.
+class GCCallback {
+ public:
+ GCCallback(ScriptContext* context,
+ const v8::Local<v8::Object>& object,
+ const v8::Local<v8::Function>& callback,
+ const base::Closure& fallback);
+ ~GCCallback();
+
+ private:
+ static void OnObjectGC(const v8::WeakCallbackInfo<GCCallback>& data);
+ void RunCallback();
+ void OnContextInvalidated();
+
+ // The context which owns |object_|.
+ ScriptContext* context_;
+
+ // The object this GCCallback is bound to.
+ v8::Global<v8::Object> object_;
+
+ // The function to run when |object_| is garbage collected.
+ v8::Global<v8::Function> callback_;
+
+ // The function to run if |context_| is invalidated before we have a chance
+ // to execute |callback_|.
+ base::Closure fallback_;
+
+ base::WeakPtrFactory<GCCallback> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(GCCallback);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_GC_CALLBACK_H_
diff --git a/chromium/extensions/renderer/gc_callback_unittest.cc b/chromium/extensions/renderer/gc_callback_unittest.cc
new file mode 100644
index 00000000000..f1b70ba3e24
--- /dev/null
+++ b/chromium/extensions/renderer/gc_callback_unittest.cc
@@ -0,0 +1,161 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/renderer/gc_callback.h"
+#include "extensions/renderer/scoped_web_frame.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "gin/function_template.h"
+#include "gin/public/context_holder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/WebKit/public/web/WebFrame.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+namespace {
+
+void SetToTrue(bool* value) {
+ if (*value)
+ ADD_FAILURE() << "Value is already true";
+ *value = true;
+}
+
+class GCCallbackTest : public testing::Test {
+ public:
+ GCCallbackTest() : script_context_set_(&active_extensions_) {}
+
+ protected:
+ base::MessageLoop& message_loop() { return message_loop_; }
+
+ ScriptContextSet& script_context_set() { return script_context_set_; }
+
+ v8::Local<v8::Context> v8_context() {
+ return v8::Local<v8::Context>::New(v8::Isolate::GetCurrent(), v8_context_);
+ }
+
+ ScriptContext* RegisterScriptContext() {
+ // No extension group or world ID.
+ return script_context_set_.Register(
+ web_frame_.frame(),
+ v8::Local<v8::Context>::New(v8::Isolate::GetCurrent(), v8_context_), 0,
+ 0);
+ }
+
+ void RequestGarbageCollection() {
+ v8::Isolate::GetCurrent()->RequestGarbageCollectionForTesting(
+ v8::Isolate::kFullGarbageCollection);
+ }
+
+ private:
+ void SetUp() override {
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Context> local_v8_context = v8::Context::New(isolate);
+ v8_context_.Reset(isolate, local_v8_context);
+ // ScriptContexts rely on gin.
+ gin_context_holder_.reset(new gin::ContextHolder(isolate));
+ gin_context_holder_->SetContext(local_v8_context);
+ }
+
+ void TearDown() override {
+ gin_context_holder_.reset();
+ v8_context_.Reset();
+ RequestGarbageCollection();
+ }
+
+ base::MessageLoop message_loop_;
+ ScopedWebFrame web_frame_; // (this will construct the v8::Isolate)
+ ExtensionIdSet active_extensions_;
+ ScriptContextSet script_context_set_;
+ v8::Global<v8::Context> v8_context_;
+ scoped_ptr<gin::ContextHolder> gin_context_holder_;
+
+ DISALLOW_COPY_AND_ASSIGN(GCCallbackTest);
+};
+
+TEST_F(GCCallbackTest, GCBeforeContextInvalidated) {
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(v8_context());
+
+ ScriptContext* script_context = RegisterScriptContext();
+
+ bool callback_invoked = false;
+ bool fallback_invoked = false;
+
+ {
+ // Nest another HandleScope so that |object| and |unreachable_function|'s
+ // handles will be garbage collected.
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Object> object = v8::Object::New(isolate);
+ v8::Local<v8::FunctionTemplate> unreachable_function =
+ gin::CreateFunctionTemplate(isolate,
+ base::Bind(SetToTrue, &callback_invoked));
+ // The GCCallback will delete itself, or memory tests will complain.
+ new GCCallback(script_context, object, unreachable_function->GetFunction(),
+ base::Bind(SetToTrue, &fallback_invoked));
+ }
+
+ // Trigger a GC. Only the callback should be invoked.
+ RequestGarbageCollection();
+ message_loop().RunUntilIdle();
+
+ EXPECT_TRUE(callback_invoked);
+ EXPECT_FALSE(fallback_invoked);
+
+ // Invalidate the context. The fallback should not be invoked because the
+ // callback was already invoked.
+ script_context_set().Remove(script_context);
+ message_loop().RunUntilIdle();
+
+ EXPECT_FALSE(fallback_invoked);
+}
+
+TEST_F(GCCallbackTest, ContextInvalidatedBeforeGC) {
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(v8_context());
+
+ ScriptContext* script_context = RegisterScriptContext();
+
+ bool callback_invoked = false;
+ bool fallback_invoked = false;
+
+ {
+ // Nest another HandleScope so that |object| and |unreachable_function|'s
+ // handles will be garbage collected.
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Object> object = v8::Object::New(isolate);
+ v8::Local<v8::FunctionTemplate> unreachable_function =
+ gin::CreateFunctionTemplate(isolate,
+ base::Bind(SetToTrue, &callback_invoked));
+ // The GCCallback will delete itself, or memory tests will complain.
+ new GCCallback(script_context, object, unreachable_function->GetFunction(),
+ base::Bind(SetToTrue, &fallback_invoked));
+ }
+
+ // Invalidate the context. Only the fallback should be invoked.
+ script_context_set().Remove(script_context);
+ message_loop().RunUntilIdle();
+
+ EXPECT_FALSE(callback_invoked);
+ EXPECT_TRUE(fallback_invoked);
+
+ // Trigger a GC. The callback should not be invoked because the fallback was
+ // already invoked.
+ RequestGarbageCollection();
+ message_loop().RunUntilIdle();
+
+ EXPECT_FALSE(callback_invoked);
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/renderer/guest_view/OWNERS b/chromium/extensions/renderer/guest_view/OWNERS
new file mode 100644
index 00000000000..51bd1721001
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/OWNERS
@@ -0,0 +1,5 @@
+fsamuel@chromium.org
+lazyboy@chromium.org
+lfg@chromium.org
+hanxi@chromium.org
+wjmaclean@chromium.org
diff --git a/chromium/extensions/renderer/guest_view/extensions_guest_view_container.cc b/chromium/extensions/renderer/guest_view/extensions_guest_view_container.cc
new file mode 100644
index 00000000000..ffa3580e3fe
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/extensions_guest_view_container.cc
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/guest_view/extensions_guest_view_container.h"
+
+#include "content/public/renderer/render_frame.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace extensions {
+
+ExtensionsGuestViewContainer::ExtensionsGuestViewContainer(
+ content::RenderFrame* render_frame)
+ : GuestViewContainer(render_frame),
+ element_resize_isolate_(nullptr),
+ weak_ptr_factory_(this) {
+}
+
+ExtensionsGuestViewContainer::~ExtensionsGuestViewContainer() {
+}
+
+void ExtensionsGuestViewContainer::OnDestroy(bool embedder_frame_destroyed) {
+}
+
+void ExtensionsGuestViewContainer::RegisterElementResizeCallback(
+ v8::Local<v8::Function> callback,
+ v8::Isolate* isolate) {
+ element_resize_callback_.Reset(isolate, callback);
+ element_resize_isolate_ = isolate;
+}
+
+void ExtensionsGuestViewContainer::DidResizeElement(const gfx::Size& new_size) {
+ // Call the element resize callback, if one is registered.
+ if (element_resize_callback_.IsEmpty())
+ return;
+
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ExtensionsGuestViewContainer::CallElementResizeCallback,
+ weak_ptr_factory_.GetWeakPtr(), new_size));
+}
+
+void ExtensionsGuestViewContainer::CallElementResizeCallback(
+ const gfx::Size& new_size) {
+ v8::HandleScope handle_scope(element_resize_isolate_);
+ v8::Local<v8::Function> callback = v8::Local<v8::Function>::New(
+ element_resize_isolate_, element_resize_callback_);
+ v8::Local<v8::Context> context = callback->CreationContext();
+ if (context.IsEmpty())
+ return;
+
+ const int argc = 2;
+ v8::Local<v8::Value> argv[argc] = {
+ v8::Integer::New(element_resize_isolate_, new_size.width()),
+ v8::Integer::New(element_resize_isolate_, new_size.height())};
+
+ v8::Context::Scope context_scope(context);
+ v8::MicrotasksScope microtasks(
+ element_resize_isolate_, v8::MicrotasksScope::kDoNotRunMicrotasks);
+
+ callback->Call(context->Global(), argc, argv);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/guest_view/extensions_guest_view_container.h b/chromium/extensions/renderer/guest_view/extensions_guest_view_container.h
new file mode 100644
index 00000000000..bc95b187f80
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/extensions_guest_view_container.h
@@ -0,0 +1,50 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_GUEST_VIEW_EXTENSIONS_GUEST_VIEW_CONTAINER_H_
+#define EXTENSIONS_RENDERER_GUEST_VIEW_EXTENSIONS_GUEST_VIEW_CONTAINER_H_
+
+#include <queue>
+
+#include "base/macros.h"
+#include "components/guest_view/renderer/guest_view_container.h"
+#include "v8/include/v8.h"
+
+namespace gfx {
+class Size;
+}
+
+namespace extensions {
+
+class ExtensionsGuestViewContainer : public guest_view::GuestViewContainer {
+ public:
+ explicit ExtensionsGuestViewContainer(content::RenderFrame* render_frame);
+
+ void RegisterElementResizeCallback(v8::Local<v8::Function> callback,
+ v8::Isolate* isolate);
+
+ // BrowserPluginDelegate implementation.
+ void DidResizeElement(const gfx::Size& new_size) override;
+
+ protected:
+ ~ExtensionsGuestViewContainer() override;
+
+ private:
+ void CallElementResizeCallback(const gfx::Size& new_size);
+
+ // GuestViewContainer implementation.
+ void OnDestroy(bool embedder_frame_destroyed) override;
+
+ v8::Global<v8::Function> element_resize_callback_;
+ v8::Isolate* element_resize_isolate_;
+
+ // Weak pointer factory used for calling the element resize callback.
+ base::WeakPtrFactory<ExtensionsGuestViewContainer> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionsGuestViewContainer);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_GUEST_VIEW_EXTENSIONS_GUEST_VIEW_CONTAINER_H_
diff --git a/chromium/extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.cc b/chromium/extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.cc
new file mode 100644
index 00000000000..16f1f4771e4
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.cc
@@ -0,0 +1,26 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.h"
+
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_macros.h"
+
+namespace extensions {
+
+ExtensionsGuestViewContainerDispatcher::
+ ExtensionsGuestViewContainerDispatcher() {
+}
+
+ExtensionsGuestViewContainerDispatcher::
+ ~ExtensionsGuestViewContainerDispatcher() {
+}
+
+bool ExtensionsGuestViewContainerDispatcher::HandlesMessage(
+ const IPC::Message& message) {
+ return GuestViewContainerDispatcher::HandlesMessage(message) ||
+ (IPC_MESSAGE_CLASS(message) == ExtensionsGuestViewMsgStart);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.h b/chromium/extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.h
new file mode 100644
index 00000000000..b04ee04f1e0
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.h
@@ -0,0 +1,23 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/macros.h"
+#include "components/guest_view/renderer/guest_view_container_dispatcher.h"
+
+namespace extensions {
+
+class ExtensionsGuestViewContainerDispatcher
+ : public guest_view::GuestViewContainerDispatcher {
+ public:
+ ExtensionsGuestViewContainerDispatcher();
+ ~ExtensionsGuestViewContainerDispatcher() override;
+
+ private:
+ // guest_view::GuestViewContainerDispatcher implementation.
+ bool HandlesMessage(const IPC::Message& message) override;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionsGuestViewContainerDispatcher);
+};
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/guest_view/guest_view_internal_custom_bindings.cc b/chromium/extensions/renderer/guest_view/guest_view_internal_custom_bindings.cc
new file mode 100644
index 00000000000..f54c87891c7
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/guest_view_internal_custom_bindings.cc
@@ -0,0 +1,438 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/guest_view/guest_view_internal_custom_bindings.h"
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "components/guest_view/common/guest_view_constants.h"
+#include "components/guest_view/common/guest_view_messages.h"
+#include "components/guest_view/renderer/guest_view_request.h"
+#include "components/guest_view/renderer/iframe_guest_view_container.h"
+#include "components/guest_view/renderer/iframe_guest_view_request.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/render_view.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/guest_view/extensions_guest_view_messages.h"
+#include "extensions/renderer/guest_view/extensions_guest_view_container.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/web/WebFrame.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebRemoteFrame.h"
+#include "third_party/WebKit/public/web/WebScopedUserGesture.h"
+#include "third_party/WebKit/public/web/WebView.h"
+#include "v8/include/v8.h"
+
+using content::V8ValueConverter;
+
+namespace {
+
+// A map from view instance ID to view object (stored via weak V8 reference).
+// Views are registered into this map via
+// GuestViewInternalCustomBindings::RegisterView(), and accessed via
+// GuestViewInternalCustomBindings::GetViewFromID().
+using ViewMap = std::map<int, v8::Global<v8::Object>*>;
+static base::LazyInstance<ViewMap> weak_view_map = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+namespace extensions {
+
+namespace {
+
+content::RenderFrame* GetRenderFrame(v8::Handle<v8::Value> value) {
+ v8::Local<v8::Context> context =
+ v8::Local<v8::Object>::Cast(value)->CreationContext();
+ if (context.IsEmpty())
+ return nullptr;
+ blink::WebLocalFrame* frame = blink::WebLocalFrame::frameForContext(context);
+ if (!frame)
+ return nullptr;
+ return content::RenderFrame::FromWebFrame(frame);
+}
+
+} // namespace
+
+GuestViewInternalCustomBindings::GuestViewInternalCustomBindings(
+ ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("AttachGuest",
+ base::Bind(&GuestViewInternalCustomBindings::AttachGuest,
+ base::Unretained(this)));
+ RouteFunction("DetachGuest",
+ base::Bind(&GuestViewInternalCustomBindings::DetachGuest,
+ base::Unretained(this)));
+ RouteFunction("AttachIframeGuest",
+ base::Bind(&GuestViewInternalCustomBindings::AttachIframeGuest,
+ base::Unretained(this)));
+ RouteFunction("DestroyContainer",
+ base::Bind(&GuestViewInternalCustomBindings::DestroyContainer,
+ base::Unretained(this)));
+ RouteFunction("GetContentWindow",
+ base::Bind(&GuestViewInternalCustomBindings::GetContentWindow,
+ base::Unretained(this)));
+ RouteFunction("GetViewFromID",
+ base::Bind(&GuestViewInternalCustomBindings::GetViewFromID,
+ base::Unretained(this)));
+ RouteFunction(
+ "RegisterDestructionCallback",
+ base::Bind(&GuestViewInternalCustomBindings::RegisterDestructionCallback,
+ base::Unretained(this)));
+ RouteFunction(
+ "RegisterElementResizeCallback",
+ base::Bind(
+ &GuestViewInternalCustomBindings::RegisterElementResizeCallback,
+ base::Unretained(this)));
+ RouteFunction("RegisterView",
+ base::Bind(&GuestViewInternalCustomBindings::RegisterView,
+ base::Unretained(this)));
+ RouteFunction(
+ "RunWithGesture",
+ base::Bind(&GuestViewInternalCustomBindings::RunWithGesture,
+ base::Unretained(this)));
+}
+
+GuestViewInternalCustomBindings::~GuestViewInternalCustomBindings() {}
+
+// static
+void GuestViewInternalCustomBindings::ResetMapEntry(
+ const v8::WeakCallbackInfo<int>& data) {
+ int* param = data.GetParameter();
+ int view_instance_id = *param;
+ delete param;
+ ViewMap& view_map = weak_view_map.Get();
+ auto entry = view_map.find(view_instance_id);
+ if (entry == view_map.end())
+ return;
+
+ // V8 says we need to explicitly reset weak handles from their callbacks.
+ // It is not implicit as one might expect.
+ entry->second->Reset();
+ delete entry->second;
+ view_map.erase(entry);
+
+ // Let the GuestViewManager know that a GuestView has been garbage collected.
+ content::RenderThread::Get()->Send(
+ new GuestViewHostMsg_ViewGarbageCollected(view_instance_id));
+}
+
+void GuestViewInternalCustomBindings::AttachGuest(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Allow for an optional callback parameter.
+ CHECK(args.Length() >= 3 && args.Length() <= 4);
+ // Element Instance ID.
+ CHECK(args[0]->IsInt32());
+ // Guest Instance ID.
+ CHECK(args[1]->IsInt32());
+ // Attach Parameters.
+ CHECK(args[2]->IsObject());
+ // Optional Callback Function.
+ CHECK(args.Length() < 4 || args[3]->IsFunction());
+
+ int element_instance_id = args[0]->Int32Value();
+ // An element instance ID uniquely identifies a GuestViewContainer.
+ auto guest_view_container =
+ guest_view::GuestViewContainer::FromID(element_instance_id);
+
+ // TODO(fsamuel): Should we be reporting an error if the element instance ID
+ // is invalid?
+ if (!guest_view_container)
+ return;
+
+ int guest_instance_id = args[1]->Int32Value();
+
+ scoped_ptr<base::DictionaryValue> params;
+ {
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+ scoped_ptr<base::Value> params_as_value(
+ converter->FromV8Value(args[2], context()->v8_context()));
+ params = base::DictionaryValue::From(std::move(params_as_value));
+ CHECK(params);
+ }
+
+ // Add flag to |params| to indicate that the element size is specified in
+ // logical units.
+ params->SetBoolean(guest_view::kElementSizeIsLogical, true);
+
+ linked_ptr<guest_view::GuestViewRequest> request(
+ new guest_view::GuestViewAttachRequest(
+ guest_view_container, guest_instance_id, std::move(params),
+ args.Length() == 4 ? args[3].As<v8::Function>()
+ : v8::Local<v8::Function>(),
+ args.GetIsolate()));
+ guest_view_container->IssueRequest(request);
+
+ args.GetReturnValue().Set(v8::Boolean::New(context()->isolate(), true));
+}
+
+void GuestViewInternalCustomBindings::DetachGuest(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Allow for an optional callback parameter.
+ CHECK(args.Length() >= 1 && args.Length() <= 2);
+ // Element Instance ID.
+ CHECK(args[0]->IsInt32());
+ // Optional Callback Function.
+ CHECK(args.Length() < 2 || args[1]->IsFunction());
+
+ int element_instance_id = args[0]->Int32Value();
+ // An element instance ID uniquely identifies a GuestViewContainer.
+ auto guest_view_container =
+ guest_view::GuestViewContainer::FromID(element_instance_id);
+
+ // TODO(fsamuel): Should we be reporting an error if the element instance ID
+ // is invalid?
+ if (!guest_view_container)
+ return;
+
+ linked_ptr<guest_view::GuestViewRequest> request(
+ new guest_view::GuestViewDetachRequest(
+ guest_view_container, args.Length() == 2 ? args[1].As<v8::Function>()
+ : v8::Local<v8::Function>(),
+ args.GetIsolate()));
+ guest_view_container->IssueRequest(request);
+
+ args.GetReturnValue().Set(v8::Boolean::New(context()->isolate(), true));
+}
+
+void GuestViewInternalCustomBindings::AttachIframeGuest(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Allow for an optional callback parameter.
+ const int num_required_params = 4;
+ CHECK(args.Length() >= num_required_params &&
+ args.Length() <= (num_required_params + 1));
+ // Element Instance ID.
+ CHECK(args[0]->IsInt32());
+ // Guest Instance ID.
+ CHECK(args[1]->IsInt32());
+ // Attach Parameters.
+ CHECK(args[2]->IsObject());
+ // <iframe>.contentWindow.
+ CHECK(args[3]->IsObject());
+ // Optional Callback Function.
+ CHECK(args.Length() <= num_required_params ||
+ args[num_required_params]->IsFunction());
+
+ int element_instance_id = args[0]->Int32Value();
+ int guest_instance_id = args[1]->Int32Value();
+
+ scoped_ptr<base::DictionaryValue> params;
+ {
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+ scoped_ptr<base::Value> params_as_value(
+ converter->FromV8Value(args[2], context()->v8_context()));
+ params = base::DictionaryValue::From(std::move(params_as_value));
+ CHECK(params);
+ }
+
+ // Add flag to |params| to indicate that the element size is specified in
+ // logical units.
+ params->SetBoolean(guest_view::kElementSizeIsLogical, true);
+
+ content::RenderFrame* render_frame = GetRenderFrame(args[3]);
+ blink::WebLocalFrame* frame = render_frame->GetWebFrame();
+
+ // Parent must exist.
+ blink::WebFrame* parent_frame = frame->parent();
+ DCHECK(parent_frame);
+ DCHECK(parent_frame->isWebLocalFrame());
+
+ content::RenderFrame* embedder_parent_frame =
+ content::RenderFrame::FromWebFrame(parent_frame);
+
+ // Create a GuestViewContainer if it does not exist.
+ // An element instance ID uniquely identifies an IframeGuestViewContainer
+ // within a RenderView.
+ auto* guest_view_container =
+ static_cast<guest_view::IframeGuestViewContainer*>(
+ guest_view::GuestViewContainer::FromID(element_instance_id));
+ // This is the first time we hear about the |element_instance_id|.
+ DCHECK(!guest_view_container);
+ // The <webview> element's GC takes ownership of |guest_view_container|.
+ guest_view_container =
+ new guest_view::IframeGuestViewContainer(embedder_parent_frame);
+ guest_view_container->SetElementInstanceID(element_instance_id);
+
+ linked_ptr<guest_view::GuestViewRequest> request(
+ new guest_view::GuestViewAttachIframeRequest(
+ guest_view_container, render_frame->GetRoutingID(), guest_instance_id,
+ std::move(params), args.Length() == (num_required_params + 1)
+ ? args[num_required_params].As<v8::Function>()
+ : v8::Local<v8::Function>(),
+ args.GetIsolate()));
+ guest_view_container->IssueRequest(request);
+
+ args.GetReturnValue().Set(v8::Boolean::New(context()->isolate(), true));
+}
+
+void GuestViewInternalCustomBindings::DestroyContainer(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().SetNull();
+
+ if (args.Length() != 1)
+ return;
+
+ // Element Instance ID.
+ if (!args[0]->IsInt32())
+ return;
+
+ int element_instance_id = args[0]->Int32Value();
+ auto* guest_view_container =
+ guest_view::GuestViewContainer::FromID(element_instance_id);
+ if (!guest_view_container)
+ return;
+
+ // Note: |guest_view_container| is deleted.
+ // GuestViewContainer::DidDestroyElement() currently also destroys
+ // a GuestViewContainer. That won't be necessary once GuestViewContainer
+ // always runs w/o plugin.
+ guest_view_container->Destroy(false /* embedder_frame_destroyed */);
+}
+
+void GuestViewInternalCustomBindings::GetContentWindow(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Default to returning null.
+ args.GetReturnValue().SetNull();
+
+ if (args.Length() != 1)
+ return;
+
+ // The routing ID for the RenderView.
+ if (!args[0]->IsInt32())
+ return;
+
+ int view_id = args[0]->Int32Value();
+ if (view_id == MSG_ROUTING_NONE)
+ return;
+
+ content::RenderView* view = content::RenderView::FromRoutingID(view_id);
+ if (!view)
+ return;
+
+ blink::WebFrame* frame = view->GetWebView()->mainFrame();
+ // TODO(lazyboy,nasko): The WebLocalFrame branch is not used when running
+ // on top of out-of-process iframes. Remove it once the code is converted.
+ v8::Local<v8::Value> window;
+ if (frame->isWebLocalFrame()) {
+ window = frame->mainWorldScriptContext()->Global();
+ } else {
+ window =
+ frame->toWebRemoteFrame()->deprecatedMainWorldScriptContext()->Global();
+ }
+ args.GetReturnValue().Set(window);
+}
+
+void GuestViewInternalCustomBindings::GetViewFromID(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Default to returning null.
+ args.GetReturnValue().SetNull();
+ // There is one argument.
+ CHECK(args.Length() == 1);
+ // The view ID.
+ CHECK(args[0]->IsInt32());
+ int view_id = args[0]->Int32Value();
+
+ ViewMap& view_map = weak_view_map.Get();
+ auto map_entry = view_map.find(view_id);
+ if (map_entry == view_map.end())
+ return;
+
+ auto return_object = v8::Handle<v8::Object>::New(args.GetIsolate(),
+ *map_entry->second);
+ args.GetReturnValue().Set(return_object);
+}
+
+void GuestViewInternalCustomBindings::RegisterDestructionCallback(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // There are two parameters.
+ CHECK(args.Length() == 2);
+ // Element Instance ID.
+ CHECK(args[0]->IsInt32());
+ // Callback function.
+ CHECK(args[1]->IsFunction());
+
+ int element_instance_id = args[0]->Int32Value();
+ // An element instance ID uniquely identifies a GuestViewContainer within a
+ // RenderView.
+ auto* guest_view_container =
+ guest_view::GuestViewContainer::FromID(element_instance_id);
+ if (!guest_view_container)
+ return;
+
+ guest_view_container->RegisterDestructionCallback(args[1].As<v8::Function>(),
+ args.GetIsolate());
+
+ args.GetReturnValue().Set(v8::Boolean::New(context()->isolate(), true));
+}
+
+void GuestViewInternalCustomBindings::RegisterElementResizeCallback(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // There are two parameters.
+ CHECK(args.Length() == 2);
+ // Element Instance ID.
+ CHECK(args[0]->IsInt32());
+ // Callback function.
+ CHECK(args[1]->IsFunction());
+
+ int element_instance_id = args[0]->Int32Value();
+ // An element instance ID uniquely identifies a ExtensionsGuestViewContainer
+ // within a RenderView.
+ auto guest_view_container = static_cast<ExtensionsGuestViewContainer*>(
+ guest_view::GuestViewContainer::FromID(element_instance_id));
+ if (!guest_view_container)
+ return;
+
+ guest_view_container->RegisterElementResizeCallback(
+ args[1].As<v8::Function>(), args.GetIsolate());
+
+ args.GetReturnValue().Set(v8::Boolean::New(context()->isolate(), true));
+}
+
+void GuestViewInternalCustomBindings::RegisterView(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // There are three parameters.
+ CHECK(args.Length() == 3);
+ // View Instance ID.
+ CHECK(args[0]->IsInt32());
+ // View element.
+ CHECK(args[1]->IsObject());
+ // View type (e.g. "webview").
+ CHECK(args[2]->IsString());
+
+ // A reference to the view object is stored in |weak_view_map| using its view
+ // ID as the key. The reference is made weak so that it will not extend the
+ // lifetime of the object.
+ int view_instance_id = args[0]->Int32Value();
+ auto object =
+ new v8::Global<v8::Object>(args.GetIsolate(), args[1].As<v8::Object>());
+ weak_view_map.Get().insert(std::make_pair(view_instance_id, object));
+
+ // The |view_instance_id| is given to the SetWeak callback so that that view's
+ // entry in |weak_view_map| can be cleared when the view object is garbage
+ // collected.
+ object->SetWeak(new int(view_instance_id),
+ &GuestViewInternalCustomBindings::ResetMapEntry,
+ v8::WeakCallbackType::kParameter);
+
+ // Let the GuestViewManager know that a GuestView has been created.
+ const std::string& view_type = *v8::String::Utf8Value(args[2]);
+ content::RenderThread::Get()->Send(
+ new GuestViewHostMsg_ViewCreated(view_instance_id, view_type));
+}
+
+void GuestViewInternalCustomBindings::RunWithGesture(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Gesture is required to request fullscreen.
+ blink::WebScopedUserGesture user_gesture;
+ CHECK_EQ(args.Length(), 1);
+ CHECK(args[0]->IsFunction());
+ v8::Local<v8::Value> no_args;
+ context()->CallFunction(v8::Local<v8::Function>::Cast(args[0]), 0, &no_args);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/guest_view/guest_view_internal_custom_bindings.h b/chromium/extensions/renderer/guest_view/guest_view_internal_custom_bindings.h
new file mode 100644
index 00000000000..f18e15cd7ab
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/guest_view_internal_custom_bindings.h
@@ -0,0 +1,98 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_GUEST_VIEW_GUEST_VIEW_INTERNAL_CUSTOM_BINDINGS_H_
+#define EXTENSIONS_RENDERER_GUEST_VIEW_GUEST_VIEW_INTERNAL_CUSTOM_BINDINGS_H_
+
+#include <map>
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class Dispatcher;
+
+// Implements custom bindings for the guestViewInternal API.
+class GuestViewInternalCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ explicit GuestViewInternalCustomBindings(ScriptContext* context);
+ ~GuestViewInternalCustomBindings() override;
+
+ private:
+ // ResetMapEntry is called as a callback to SetWeak(). It resets the
+ // weak view reference held in |view_map_|.
+ static void ResetMapEntry(const v8::WeakCallbackInfo<int>& data);
+
+ // AttachGuest attaches a GuestView to a provided container element. Once
+ // attached, the GuestView will participate in layout of the container page
+ // and become visible on screen.
+ // AttachGuest takes four parameters:
+ // |element_instance_id| uniquely identifies a container within the content
+ // module is able to host GuestViews.
+ // |guest_instance_id| uniquely identifies an unattached GuestView.
+ // |attach_params| is typically used to convey the current state of the
+ // container element at the time of attachment. These parameters are passed
+ // down to the GuestView. The GuestView may use these parameters to update the
+ // state of the guest hosted in another process.
+ // |callback| is an optional callback that is called once attachment is
+ // complete. The callback takes in a parameter for the WindowProxy of the
+ // guest identified by |guest_instance_id|.
+ void AttachGuest(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // DetachGuest detaches the container container specified from the associated
+ // GuestViewBase. DetachGuest takes two parameters:
+ // |element_instance_id| uniquely identifies a container within the content
+ // module is able to host GuestViews.
+ // |callback| is an optional callback that is called once the container has
+ // been detached.
+ void DetachGuest(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // AttachIframeGuest is --site-per-process variant of AttachGuest().
+ //
+ // AttachIframeGuest takes a |contentWindow| parameter in addition to the
+ // parameters to AttachGuest. That parameter is used to identify the
+ // RenderFrame of the <iframe> container element.
+ void AttachIframeGuest(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // GetContentWindow takes in a RenderView routing ID and returns the
+ // Window JavaScript object for that RenderView.
+ void GetContentWindow(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Destroys the GuestViewContainer given an element instance ID in |args|.
+ void DestroyContainer(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // GetViewFromID takes a view ID, and returns the GuestView element associated
+ // with that ID, if one exists. Otherwise, null is returned.
+ void GetViewFromID(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // RegisterDestructionCallback registers a JavaScript callback function to be
+ // called when the guestview's container is destroyed.
+ // RegisterDestructionCallback takes in a single paramater, |callback|.
+ void RegisterDestructionCallback(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // RegisterElementResizeCallback registers a JavaScript callback function to
+ // be called when the element is resized. RegisterElementResizeCallback takes
+ // a single parameter, |callback|.
+ void RegisterElementResizeCallback(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // RegisterView takes in a view ID and a GuestView element, and stores the
+ // pair as an entry in |view_map_|. The view can then be retrieved using
+ // GetViewFromID.
+ void RegisterView(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Runs a JavaScript function with user gesture.
+ //
+ // This is used to request webview element to enter fullscreen (from the
+ // embedder).
+ // Note that the guest requesting fullscreen means it has already been
+ // triggered by a user gesture and we get to this point if embedder allows
+ // the fullscreen request to proceed.
+ void RunWithGesture(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_GUEST_VIEW_GUEST_VIEW_INTERNAL_CUSTOM_BINDINGS_H_
diff --git a/chromium/extensions/renderer/guest_view/mime_handler_view/OWNERS b/chromium/extensions/renderer/guest_view/mime_handler_view/OWNERS
new file mode 100644
index 00000000000..db781ac5adc
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/mime_handler_view/OWNERS
@@ -0,0 +1 @@
+raymes@chromium.org
diff --git a/chromium/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.cc b/chromium/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.cc
new file mode 100644
index 00000000000..91f8197e853
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.cc
@@ -0,0 +1,340 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.h"
+
+#include <map>
+#include <set>
+
+#include "base/macros.h"
+#include "components/guest_view/common/guest_view_constants.h"
+#include "components/guest_view/common/guest_view_messages.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_constants.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/guest_view/extensions_guest_view_messages.h"
+#include "gin/arguments.h"
+#include "gin/dictionary.h"
+#include "gin/handle.h"
+#include "gin/interceptor.h"
+#include "gin/object_template_builder.h"
+#include "gin/wrappable.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebRemoteFrame.h"
+#include "third_party/WebKit/public/web/WebView.h"
+
+namespace extensions {
+
+namespace {
+
+const char kPostMessageName[] = "postMessage";
+
+// The gin-backed scriptable object which is exposed by the BrowserPlugin for
+// MimeHandlerViewContainer. This currently only implements "postMessage".
+class ScriptableObject : public gin::Wrappable<ScriptableObject>,
+ public gin::NamedPropertyInterceptor {
+ public:
+ static gin::WrapperInfo kWrapperInfo;
+
+ static v8::Local<v8::Object> Create(
+ v8::Isolate* isolate,
+ base::WeakPtr<MimeHandlerViewContainer> container) {
+ ScriptableObject* scriptable_object =
+ new ScriptableObject(isolate, container);
+ return gin::CreateHandle(isolate, scriptable_object)
+ .ToV8()
+ .As<v8::Object>();
+ }
+
+ // gin::NamedPropertyInterceptor
+ v8::Local<v8::Value> GetNamedProperty(
+ v8::Isolate* isolate,
+ const std::string& identifier) override {
+ if (identifier == kPostMessageName) {
+ if (post_message_function_template_.IsEmpty()) {
+ post_message_function_template_.Reset(
+ isolate,
+ gin::CreateFunctionTemplate(
+ isolate, base::Bind(&MimeHandlerViewContainer::PostMessage,
+ container_, isolate)));
+ }
+ v8::Local<v8::FunctionTemplate> function_template =
+ v8::Local<v8::FunctionTemplate>::New(isolate,
+ post_message_function_template_);
+ v8::Local<v8::Function> function;
+ if (function_template->GetFunction(isolate->GetCurrentContext())
+ .ToLocal(&function))
+ return function;
+ }
+ return v8::Local<v8::Value>();
+ }
+
+ private:
+ ScriptableObject(v8::Isolate* isolate,
+ base::WeakPtr<MimeHandlerViewContainer> container)
+ : gin::NamedPropertyInterceptor(isolate, this),
+ container_(container) {}
+
+ // gin::Wrappable
+ gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
+ v8::Isolate* isolate) override {
+ return gin::Wrappable<ScriptableObject>::GetObjectTemplateBuilder(isolate)
+ .AddNamedPropertyInterceptor();
+ }
+
+ base::WeakPtr<MimeHandlerViewContainer> container_;
+ v8::Persistent<v8::FunctionTemplate> post_message_function_template_;
+};
+
+// static
+gin::WrapperInfo ScriptableObject::kWrapperInfo = { gin::kEmbedderNativeGin };
+
+// Maps from content::RenderFrame to the set of MimeHandlerViewContainers within
+// it.
+base::LazyInstance<
+ std::map<content::RenderFrame*, std::set<MimeHandlerViewContainer*>>>
+ g_mime_handler_view_container_map = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+MimeHandlerViewContainer::MimeHandlerViewContainer(
+ content::RenderFrame* render_frame,
+ const std::string& mime_type,
+ const GURL& original_url)
+ : GuestViewContainer(render_frame),
+ mime_type_(mime_type),
+ original_url_(original_url),
+ guest_proxy_routing_id_(-1),
+ guest_loaded_(false),
+ weak_factory_(this) {
+ DCHECK(!mime_type_.empty());
+ is_embedded_ = !render_frame->GetWebFrame()->document().isPluginDocument();
+ g_mime_handler_view_container_map.Get()[render_frame].insert(this);
+}
+
+MimeHandlerViewContainer::~MimeHandlerViewContainer() {
+ if (loader_)
+ loader_->cancel();
+
+ if (render_frame()) {
+ g_mime_handler_view_container_map.Get()[render_frame()].erase(this);
+ if (g_mime_handler_view_container_map.Get()[render_frame()].empty())
+ g_mime_handler_view_container_map.Get().erase(render_frame());
+ }
+}
+
+// static
+std::vector<MimeHandlerViewContainer*>
+MimeHandlerViewContainer::FromRenderFrame(content::RenderFrame* render_frame) {
+ auto it = g_mime_handler_view_container_map.Get().find(render_frame);
+ if (it == g_mime_handler_view_container_map.Get().end())
+ return std::vector<MimeHandlerViewContainer*>();
+
+ return std::vector<MimeHandlerViewContainer*>(it->second.begin(),
+ it->second.end());
+}
+
+void MimeHandlerViewContainer::OnReady() {
+ if (!render_frame())
+ return;
+
+ blink::WebFrame* frame = render_frame()->GetWebFrame();
+ blink::WebURLLoaderOptions options;
+ // The embedded plugin is allowed to be cross-origin and we should always
+ // send credentials/cookies with the request.
+ options.crossOriginRequestPolicy =
+ blink::WebURLLoaderOptions::CrossOriginRequestPolicyAllow;
+ options.allowCredentials = true;
+ DCHECK(!loader_);
+ loader_.reset(frame->createAssociatedURLLoader(options));
+
+ blink::WebURLRequest request(original_url_);
+ request.setRequestContext(blink::WebURLRequest::RequestContextObject);
+ loader_->loadAsynchronously(request, this);
+}
+
+bool MimeHandlerViewContainer::OnMessage(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(MimeHandlerViewContainer, message)
+ IPC_MESSAGE_HANDLER(ExtensionsGuestViewMsg_CreateMimeHandlerViewGuestACK,
+ OnCreateMimeHandlerViewGuestACK)
+ IPC_MESSAGE_HANDLER(
+ ExtensionsGuestViewMsg_MimeHandlerViewGuestOnLoadCompleted,
+ OnMimeHandlerViewGuestOnLoadCompleted)
+ IPC_MESSAGE_HANDLER(GuestViewMsg_GuestAttached, OnGuestAttached)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void MimeHandlerViewContainer::DidFinishLoading() {
+ DCHECK(!is_embedded_);
+ CreateMimeHandlerViewGuest();
+}
+
+void MimeHandlerViewContainer::OnRenderFrameDestroyed() {
+ g_mime_handler_view_container_map.Get().erase(render_frame());
+}
+
+void MimeHandlerViewContainer::DidReceiveData(const char* data,
+ int data_length) {
+ view_id_ += std::string(data, data_length);
+}
+
+
+void MimeHandlerViewContainer::DidResizeElement(const gfx::Size& new_size) {
+ element_size_ = new_size;
+ render_frame()->Send(new ExtensionsGuestViewHostMsg_ResizeGuest(
+ render_frame()->GetRoutingID(), element_instance_id(), new_size));
+}
+
+v8::Local<v8::Object> MimeHandlerViewContainer::V8ScriptableObject(
+ v8::Isolate* isolate) {
+ if (scriptable_object_.IsEmpty()) {
+ v8::Local<v8::Object> object =
+ ScriptableObject::Create(isolate, weak_factory_.GetWeakPtr());
+ scriptable_object_.Reset(isolate, object);
+ }
+ return v8::Local<v8::Object>::New(isolate, scriptable_object_);
+}
+
+void MimeHandlerViewContainer::didReceiveData(blink::WebURLLoader* /* unused */,
+ const char* data,
+ int data_length,
+ int /* unused */) {
+ view_id_ += std::string(data, data_length);
+}
+
+void MimeHandlerViewContainer::didFinishLoading(
+ blink::WebURLLoader* /* unused */,
+ double /* unused */,
+ int64_t /* unused */) {
+ DCHECK(is_embedded_);
+ CreateMimeHandlerViewGuest();
+}
+
+void MimeHandlerViewContainer::PostMessage(v8::Isolate* isolate,
+ v8::Local<v8::Value> message) {
+ if (!guest_loaded_) {
+ linked_ptr<v8::Global<v8::Value>> global(
+ new v8::Global<v8::Value>(isolate, message));
+ pending_messages_.push_back(global);
+ return;
+ }
+
+ content::RenderView* guest_proxy_render_view =
+ content::RenderView::FromRoutingID(guest_proxy_routing_id_);
+ if (!guest_proxy_render_view)
+ return;
+ blink::WebFrame* guest_proxy_frame =
+ guest_proxy_render_view->GetWebView()->mainFrame();
+ if (!guest_proxy_frame)
+ return;
+
+ v8::Context::Scope context_scope(
+ render_frame()->GetWebFrame()->mainWorldScriptContext());
+
+ // TODO(lazyboy,nasko): The WebLocalFrame branch is not used when running
+ // on top of out-of-process iframes. Remove it once the code is converted.
+ v8::Local<v8::Object> guest_proxy_window;
+ if (guest_proxy_frame->isWebLocalFrame()) {
+ guest_proxy_window =
+ guest_proxy_frame->mainWorldScriptContext()->Global();
+ } else {
+ guest_proxy_window = guest_proxy_frame->toWebRemoteFrame()
+ ->deprecatedMainWorldScriptContext()
+ ->Global();
+ }
+ gin::Dictionary window_object(isolate, guest_proxy_window);
+ v8::Local<v8::Function> post_message;
+ if (!window_object.Get(std::string(kPostMessageName), &post_message))
+ return;
+
+ v8::Local<v8::Value> args[] = {
+ message,
+ // Post the message to any domain inside the browser plugin. The embedder
+ // should already know what is embedded.
+ gin::StringToV8(isolate, "*")};
+ render_frame()->GetWebFrame()->callFunctionEvenIfScriptDisabled(
+ post_message.As<v8::Function>(),
+ guest_proxy_window,
+ arraysize(args),
+ args);
+}
+
+void MimeHandlerViewContainer::PostMessageFromValue(
+ const base::Value& message) {
+ blink::WebFrame* frame = render_frame()->GetWebFrame();
+ if (!frame)
+ return;
+
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(frame->mainWorldScriptContext());
+ scoped_ptr<content::V8ValueConverter> converter(
+ content::V8ValueConverter::create());
+ PostMessage(isolate,
+ converter->ToV8Value(&message, frame->mainWorldScriptContext()));
+}
+
+void MimeHandlerViewContainer::OnCreateMimeHandlerViewGuestACK(
+ int element_instance_id) {
+ DCHECK_NE(this->element_instance_id(), guest_view::kInstanceIDNone);
+ DCHECK_EQ(this->element_instance_id(), element_instance_id);
+
+ if (!render_frame())
+ return;
+
+ render_frame()->AttachGuest(element_instance_id);
+}
+
+void MimeHandlerViewContainer::OnGuestAttached(int /* unused */,
+ int guest_proxy_routing_id) {
+ // Save the RenderView routing ID of the guest here so it can be used to route
+ // PostMessage calls.
+ guest_proxy_routing_id_ = guest_proxy_routing_id;
+}
+
+void MimeHandlerViewContainer::OnMimeHandlerViewGuestOnLoadCompleted(
+ int /* unused */) {
+ if (!render_frame())
+ return;
+
+ guest_loaded_ = true;
+ if (pending_messages_.empty())
+ return;
+
+ // Now that the guest has loaded, flush any unsent messages.
+ blink::WebFrame* frame = render_frame()->GetWebFrame();
+ if (!frame)
+ return;
+
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(frame->mainWorldScriptContext());
+ for (const auto& pending_message : pending_messages_)
+ PostMessage(isolate, v8::Local<v8::Value>::New(isolate, *pending_message));
+
+ pending_messages_.clear();
+}
+
+void MimeHandlerViewContainer::CreateMimeHandlerViewGuest() {
+ // The loader has completed loading |view_id_| so we can dispose it.
+ loader_.reset();
+
+ DCHECK_NE(element_instance_id(), guest_view::kInstanceIDNone);
+
+ if (!render_frame())
+ return;
+
+ render_frame()->Send(
+ new ExtensionsGuestViewHostMsg_CreateMimeHandlerViewGuest(
+ render_frame()->GetRoutingID(), view_id_, element_instance_id(),
+ element_size_));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.h b/chromium/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.h
new file mode 100644
index 00000000000..6c98abefabf
--- /dev/null
+++ b/chromium/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.h
@@ -0,0 +1,132 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_GUEST_VIEW_MIME_HANDLER_VIEW_MIME_HANDLER_VIEW_CONTAINER_H_
+#define EXTENSIONS_RENDERER_GUEST_VIEW_MIME_HANDLER_VIEW_MIME_HANDLER_VIEW_CONTAINER_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "components/guest_view/renderer/guest_view_container.h"
+#include "third_party/WebKit/public/platform/WebURLLoader.h"
+#include "third_party/WebKit/public/platform/WebURLLoaderClient.h"
+#include "ui/gfx/geometry/size.h"
+#include "url/gurl.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+// A container for loading up an extension inside a BrowserPlugin to handle a
+// MIME type. A request for the URL of the data to load inside the container is
+// made and a url is sent back in response which points to the URL which the
+// container should be navigated to. There are two cases for making this URL
+// request, the case where the plugin is embedded and the case where it is top
+// level:
+// 1) In the top level case a URL request for the data to load has already been
+// made by the renderer on behalf of the plugin. The |DidReceiveData| and
+// |DidFinishLoading| callbacks (from BrowserPluginDelegate) will be called
+// when data is received and when it has finished being received,
+// respectively.
+// 2) In the embedded case, no URL request is automatically made by the
+// renderer. We make a URL request for the data inside the container using
+// a WebURLLoader. In this case, the |didReceiveData| and |didFinishLoading|
+// (from WebURLLoaderClient) when data is received and when it has finished
+// being received.
+class MimeHandlerViewContainer : public guest_view::GuestViewContainer,
+ public blink::WebURLLoaderClient {
+ public:
+ MimeHandlerViewContainer(content::RenderFrame* render_frame,
+ const std::string& mime_type,
+ const GURL& original_url);
+
+ static std::vector<MimeHandlerViewContainer*> FromRenderFrame(
+ content::RenderFrame* render_frame);
+
+ // GuestViewContainer implementation.
+ bool OnMessage(const IPC::Message& message) override;
+ void OnReady() override;
+
+ // BrowserPluginDelegate implementation.
+ void DidFinishLoading() override;
+ void DidReceiveData(const char* data, int data_length) override;
+ void DidResizeElement(const gfx::Size& new_size) override;
+ v8::Local<v8::Object> V8ScriptableObject(v8::Isolate*) override;
+
+ // WebURLLoaderClient overrides.
+ void didReceiveData(blink::WebURLLoader* loader,
+ const char* data,
+ int data_length,
+ int encoded_data_length) override;
+ void didFinishLoading(blink::WebURLLoader* loader,
+ double finish_time,
+ int64_t total_encoded_data_length) override;
+
+ // GuestViewContainer overrides.
+ void OnRenderFrameDestroyed() override;
+
+ // Post a JavaScript message to the guest.
+ void PostMessage(v8::Isolate* isolate, v8::Local<v8::Value> message);
+
+ // Post |message| to the guest.
+ void PostMessageFromValue(const base::Value& message);
+
+ protected:
+ ~MimeHandlerViewContainer() override;
+
+ private:
+ // Message handlers.
+ void OnCreateMimeHandlerViewGuestACK(int element_instance_id);
+ void OnGuestAttached(int element_instance_id,
+ int guest_proxy_routing_id);
+ void OnMimeHandlerViewGuestOnLoadCompleted(int element_instance_id);
+
+ void CreateMimeHandlerViewGuest();
+
+ // The MIME type of the plugin.
+ const std::string mime_type_;
+
+ // The URL of the extension to navigate to.
+ std::string view_id_;
+
+ // Whether the plugin is embedded or not.
+ bool is_embedded_;
+
+ // The original URL of the plugin.
+ GURL original_url_;
+
+ // The RenderView routing ID of the guest.
+ int guest_proxy_routing_id_;
+
+ // A URL loader to load the |original_url_| when the plugin is embedded. In
+ // the embedded case, no URL request is made automatically.
+ scoped_ptr<blink::WebURLLoader> loader_;
+
+ // The scriptable object that backs the plugin.
+ v8::Global<v8::Object> scriptable_object_;
+
+ // Pending postMessage messages that need to be sent to the guest. These are
+ // queued while the guest is loading and once it is fully loaded they are
+ // delivered so that messages aren't lost.
+ std::vector<linked_ptr<v8::Global<v8::Value>>> pending_messages_;
+
+ // True if the guest page has fully loaded and its JavaScript onload function
+ // has been called.
+ bool guest_loaded_;
+
+ // The size of the element.
+ gfx::Size element_size_;
+
+ base::WeakPtrFactory<MimeHandlerViewContainer> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(MimeHandlerViewContainer);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_GUEST_VIEW_MIME_HANDLER_VIEW_MIME_HANDLER_VIEW_CONTAINER_H_
diff --git a/chromium/extensions/renderer/i18n_custom_bindings.cc b/chromium/extensions/renderer/i18n_custom_bindings.cc
new file mode 100644
index 00000000000..08d9e75aac3
--- /dev/null
+++ b/chromium/extensions/renderer/i18n_custom_bindings.cc
@@ -0,0 +1,245 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/i18n_custom_bindings.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/message_bundle.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/v8_helpers.h"
+#include "third_party/cld_2/src/public/compact_lang_det.h"
+#include "third_party/cld_2/src/public/encodings.h"
+
+namespace extensions {
+
+using namespace v8_helpers;
+
+namespace {
+
+// Max number of languages detected by CLD2.
+const int kCldNumLangs = 3;
+
+struct DetectedLanguage {
+ DetectedLanguage(const std::string& language, int percentage)
+ : language(language), percentage(percentage) {}
+ ~DetectedLanguage() {}
+
+ // Returns a new v8::Local<v8::Value> representing the serialized form of
+ // this DetectedLanguage object.
+ scoped_ptr<base::DictionaryValue> ToDictionary() const;
+
+ std::string language;
+ int percentage;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DetectedLanguage);
+};
+
+// LanguageDetectionResult object that holds detected langugae reliability and
+// array of DetectedLanguage
+struct LanguageDetectionResult {
+ explicit LanguageDetectionResult(bool is_reliable)
+ : is_reliable(is_reliable) {}
+ ~LanguageDetectionResult() {}
+
+ // Returns a new v8::Local<v8::Value> representing the serialized form of
+ // this Result object.
+ v8::Local<v8::Value> ToValue(ScriptContext* context);
+
+ // CLD detected language reliability
+ bool is_reliable;
+
+ // Array of detectedLanguage of size 1-3. The null is returned if
+ // there were no languages detected
+ std::vector<scoped_ptr<DetectedLanguage>> languages;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LanguageDetectionResult);
+};
+
+scoped_ptr<base::DictionaryValue> DetectedLanguage::ToDictionary() const {
+ scoped_ptr<base::DictionaryValue> dict_value(new base::DictionaryValue());
+ dict_value->SetString("language", language.c_str());
+ dict_value->SetInteger("percentage", percentage);
+ return dict_value;
+}
+
+v8::Local<v8::Value> LanguageDetectionResult::ToValue(ScriptContext* context) {
+ base::DictionaryValue dict_value;
+ dict_value.SetBoolean("isReliable", is_reliable);
+ scoped_ptr<base::ListValue> languages_list(new base::ListValue());
+ for (const auto& language : languages)
+ languages_list->Append(language->ToDictionary());
+ dict_value.Set("languages", std::move(languages_list));
+
+ v8::Local<v8::Context> v8_context = context->v8_context();
+ v8::Isolate* isolate = v8_context->GetIsolate();
+ v8::EscapableHandleScope handle_scope(isolate);
+
+ scoped_ptr<content::V8ValueConverter> converter(
+ content::V8ValueConverter::create());
+ v8::Local<v8::Value> result = converter->ToV8Value(&dict_value, v8_context);
+ return handle_scope.Escape(result);
+}
+
+void InitDetectedLanguages(
+ CLD2::Language* languages,
+ int* percents,
+ std::vector<scoped_ptr<DetectedLanguage>>* detected_languages) {
+ for (int i = 0; i < kCldNumLangs; i++) {
+ std::string language_code;
+ // Convert LanguageCode 'zh' to 'zh-CN' and 'zh-Hant' to 'zh-TW' for
+ // Translate server usage. see DetermineTextLanguage in
+ // components/translate/core/language_detection/language_detection_util.cc
+ if (languages[i] == CLD2::UNKNOWN_LANGUAGE) {
+ // Break from the loop since there is no need to save
+ // unknown languages
+ break;
+ } else {
+ language_code =
+ CLD2::LanguageCode(static_cast<CLD2::Language>(languages[i]));
+ }
+ detected_languages->push_back(
+ make_scoped_ptr(new DetectedLanguage(language_code, percents[i])));
+ }
+}
+
+} // namespace
+
+I18NCustomBindings::I18NCustomBindings(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction(
+ "GetL10nMessage",
+ base::Bind(&I18NCustomBindings::GetL10nMessage, base::Unretained(this)));
+ RouteFunction("GetL10nUILanguage",
+ base::Bind(&I18NCustomBindings::GetL10nUILanguage,
+ base::Unretained(this)));
+ RouteFunction("DetectTextLanguage",
+ base::Bind(&I18NCustomBindings::DetectTextLanguage,
+ base::Unretained(this)));
+}
+
+void I18NCustomBindings::GetL10nMessage(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() != 3 || !args[0]->IsString()) {
+ NOTREACHED() << "Bad arguments";
+ return;
+ }
+
+ std::string extension_id;
+ if (args[2]->IsNull() || !args[2]->IsString()) {
+ return;
+ } else {
+ extension_id = *v8::String::Utf8Value(args[2]);
+ if (extension_id.empty())
+ return;
+ }
+
+ L10nMessagesMap* l10n_messages = GetL10nMessagesMap(extension_id);
+ if (!l10n_messages) {
+ content::RenderFrame* render_frame = context()->GetRenderFrame();
+ if (!render_frame)
+ return;
+
+ L10nMessagesMap messages;
+ // A sync call to load message catalogs for current extension.
+ render_frame->Send(
+ new ExtensionHostMsg_GetMessageBundle(extension_id, &messages));
+
+ // Save messages we got.
+ ExtensionToL10nMessagesMap& l10n_messages_map =
+ *GetExtensionToL10nMessagesMap();
+ l10n_messages_map[extension_id] = messages;
+
+ l10n_messages = GetL10nMessagesMap(extension_id);
+ }
+
+ std::string message_name = *v8::String::Utf8Value(args[0]);
+ std::string message =
+ MessageBundle::GetL10nMessage(message_name, *l10n_messages);
+
+ v8::Isolate* isolate = args.GetIsolate();
+ std::vector<std::string> substitutions;
+ if (args[1]->IsArray()) {
+ // chrome.i18n.getMessage("message_name", ["more", "params"]);
+ v8::Local<v8::Array> placeholders = v8::Local<v8::Array>::Cast(args[1]);
+ uint32_t count = placeholders->Length();
+ if (count > 9)
+ return;
+ for (uint32_t i = 0; i < count; ++i) {
+ substitutions.push_back(*v8::String::Utf8Value(placeholders->Get(
+ v8::Integer::New(isolate, i))));
+ }
+ } else if (args[1]->IsString()) {
+ // chrome.i18n.getMessage("message_name", "one param");
+ substitutions.push_back(*v8::String::Utf8Value(args[1]));
+ }
+
+ args.GetReturnValue().Set(v8::String::NewFromUtf8(
+ isolate,
+ base::ReplaceStringPlaceholders(message, substitutions, NULL).c_str()));
+}
+
+void I18NCustomBindings::GetL10nUILanguage(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(v8::String::NewFromUtf8(
+ args.GetIsolate(), content::RenderThread::Get()->GetLocale().c_str()));
+}
+
+void I18NCustomBindings::DetectTextLanguage(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK(args.Length() == 1);
+ CHECK(args[0]->IsString());
+
+ std::string text = *v8::String::Utf8Value(args[0]);
+ CLD2::CLDHints cldhints = {nullptr, "", CLD2::UNKNOWN_ENCODING,
+ CLD2::UNKNOWN_LANGUAGE};
+
+ bool is_plain_text = true; // assume the text is a plain text
+ int flags = 0; // no flags, see compact_lang_det.h for details
+ int text_bytes; // amount of non-tag/letters-only text (assumed 0)
+ int valid_prefix_bytes; // amount of valid UTF8 character in the string
+ double normalized_score[kCldNumLangs];
+
+ CLD2::Language languages[kCldNumLangs];
+ int percents[kCldNumLangs];
+ bool is_reliable = false;
+
+ // populating languages and percents
+ int cld_language = CLD2::ExtDetectLanguageSummaryCheckUTF8(
+ text.c_str(), static_cast<int>(text.size()), is_plain_text, &cldhints,
+ flags, languages, percents, normalized_score,
+ nullptr, // assumed no ResultChunkVector is used
+ &text_bytes, &is_reliable, &valid_prefix_bytes);
+
+ // Check if non-UTF8 character is encountered
+ // See bug http://crbug.com/444258.
+ if (valid_prefix_bytes < static_cast<int>(text.size()) &&
+ cld_language == CLD2::UNKNOWN_LANGUAGE) {
+ // Detect Language upto before the first non-UTF8 character
+ CLD2::ExtDetectLanguageSummary(
+ text.c_str(), valid_prefix_bytes, is_plain_text, &cldhints, flags,
+ languages, percents, normalized_score,
+ nullptr, // assumed no ResultChunkVector is used
+ &text_bytes, &is_reliable);
+ }
+
+ LanguageDetectionResult result(is_reliable);
+ // populate LanguageDetectionResult with languages and percents
+ InitDetectedLanguages(languages, percents, &result.languages);
+
+ args.GetReturnValue().Set(result.ToValue(context()));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/i18n_custom_bindings.h b/chromium/extensions/renderer/i18n_custom_bindings.h
new file mode 100644
index 00000000000..0ad2ffcdee9
--- /dev/null
+++ b/chromium/extensions/renderer/i18n_custom_bindings.h
@@ -0,0 +1,26 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_I18N_CUSTOM_BINDINGS_H_
+#define EXTENSIONS_RENDERER_I18N_CUSTOM_BINDINGS_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+// Implements custom bindings for the i18n API.
+class I18NCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ explicit I18NCustomBindings(ScriptContext* context);
+
+ private:
+ void GetL10nMessage(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void GetL10nUILanguage(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void DetectTextLanguage(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_I18N_CUSTOM_BINDINGS_H_
diff --git a/chromium/extensions/renderer/id_generator_custom_bindings.cc b/chromium/extensions/renderer/id_generator_custom_bindings.cc
new file mode 100644
index 00000000000..182c4540d16
--- /dev/null
+++ b/chromium/extensions/renderer/id_generator_custom_bindings.cc
@@ -0,0 +1,31 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/id_generator_custom_bindings.h"
+
+#include <stdint.h>
+
+#include "base/bind.h"
+
+namespace extensions {
+
+IdGeneratorCustomBindings::IdGeneratorCustomBindings(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("GetNextId",
+ base::Bind(&IdGeneratorCustomBindings::GetNextId,
+ base::Unretained(this)));
+}
+
+void IdGeneratorCustomBindings::GetNextId(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ static int32_t next_id = 0;
+ ++next_id;
+ // Make sure 0 is never returned because some APIs (particularly WebRequest)
+ // have special meaning for 0 IDs.
+ if (next_id == 0)
+ next_id = 1;
+ args.GetReturnValue().Set(next_id);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/id_generator_custom_bindings.h b/chromium/extensions/renderer/id_generator_custom_bindings.h
new file mode 100644
index 00000000000..b8d79f6c6be
--- /dev/null
+++ b/chromium/extensions/renderer/id_generator_custom_bindings.h
@@ -0,0 +1,25 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_ID_GENERATOR_CUSTOM_BINDINGS_H_
+#define EXTENSIONS_RENDERER_ID_GENERATOR_CUSTOM_BINDINGS_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+// Implements function that can be used by JS layer to generate unique integer
+// identifiers.
+class IdGeneratorCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ IdGeneratorCustomBindings(ScriptContext* context);
+
+ private:
+ void GetNextId(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_ID_GENERATOR_CUSTOM_BINDINGS_H_
diff --git a/chromium/extensions/renderer/injection_host.cc b/chromium/extensions/renderer/injection_host.cc
new file mode 100644
index 00000000000..30e8679de88
--- /dev/null
+++ b/chromium/extensions/renderer/injection_host.cc
@@ -0,0 +1,12 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/injection_host.h"
+
+InjectionHost::InjectionHost(const HostID& host_id) :
+ id_(host_id) {
+}
+
+InjectionHost::~InjectionHost() {
+}
diff --git a/chromium/extensions/renderer/injection_host.h b/chromium/extensions/renderer/injection_host.h
new file mode 100644
index 00000000000..21a4c0ed61a
--- /dev/null
+++ b/chromium/extensions/renderer/injection_host.h
@@ -0,0 +1,47 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_INJECTION_HOST_H
+#define EXTENSIONS_RENDERER_INJECTION_HOST_H
+
+#include "base/macros.h"
+#include "extensions/common/host_id.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "url/gurl.h"
+
+namespace content {
+class RenderFrame;
+}
+
+// An interface for all kinds of hosts who own user scripts.
+class InjectionHost {
+ public:
+ InjectionHost(const HostID& host_id);
+ virtual ~InjectionHost();
+
+ virtual std::string GetContentSecurityPolicy() const = 0;
+
+ // The base url for the host.
+ virtual const GURL& url() const = 0;
+
+ // The human-readable name of the host.
+ virtual const std::string& name() const = 0;
+
+ // Returns true if the script should execute.
+ virtual extensions::PermissionsData::AccessType CanExecuteOnFrame(
+ const GURL& document_url,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ bool is_declarative) const = 0;
+
+ const HostID& id() const { return id_; }
+
+ private:
+ // The ID of the host.
+ HostID id_;
+
+ DISALLOW_COPY_AND_ASSIGN(InjectionHost);
+};
+
+#endif // EXTENSIONS_RENDERER_INJECTION_HOST_H
diff --git a/chromium/extensions/renderer/json_schema_unittest.cc b/chromium/extensions/renderer/json_schema_unittest.cc
new file mode 100644
index 00000000000..3a2191cad0f
--- /dev/null
+++ b/chromium/extensions/renderer/json_schema_unittest.cc
@@ -0,0 +1,111 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/module_system_test.h"
+#include "extensions/renderer/v8_schema_registry.h"
+#include "gin/dictionary.h"
+#include "grit/extensions_renderer_resources.h"
+
+namespace extensions {
+
+class JsonSchemaTest : public ModuleSystemTest {
+ public:
+ void SetUp() override {
+ ModuleSystemTest::SetUp();
+
+ env()->RegisterModule("json_schema", IDR_JSON_SCHEMA_JS);
+ env()->RegisterModule("utils", IDR_UTILS_JS);
+
+ env()->module_system()->RegisterNativeHandler(
+ "schema_registry", schema_registry_.AsNativeHandler());
+
+ env()->RegisterTestFile("json_schema_test", "json_schema_test.js");
+ }
+
+ protected:
+ void TestFunction(const std::string& test_name) {
+ env()->module_system()->CallModuleMethod("json_schema_test", test_name);
+ }
+
+ private:
+ V8SchemaRegistry schema_registry_;
+};
+
+TEST_F(JsonSchemaTest, TestFormatError) {
+ TestFunction("testFormatError");
+}
+
+TEST_F(JsonSchemaTest, TestComplex) {
+ TestFunction("testComplex");
+}
+
+TEST_F(JsonSchemaTest, TestEnum) {
+ TestFunction("testEnum");
+}
+
+TEST_F(JsonSchemaTest, TestExtends) {
+ TestFunction("testExtends");
+}
+
+TEST_F(JsonSchemaTest, TestObject) {
+ TestFunction("testObject");
+}
+
+TEST_F(JsonSchemaTest, TestArrayTuple) {
+ TestFunction("testArrayTuple");
+}
+
+TEST_F(JsonSchemaTest, TestArrayNonTuple) {
+ TestFunction("testArrayNonTuple");
+}
+
+TEST_F(JsonSchemaTest, TestString) {
+ TestFunction("testString");
+}
+
+TEST_F(JsonSchemaTest, TestNumber) {
+ TestFunction("testNumber");
+}
+
+TEST_F(JsonSchemaTest, TestIntegerBounds) {
+ TestFunction("testIntegerBounds");
+}
+
+TEST_F(JsonSchemaTest, TestType) {
+ gin::Dictionary array_buffer_container(
+ env()->isolate(),
+ env()->CreateGlobal("otherContextArrayBufferContainer"));
+ {
+ // Create an ArrayBuffer in another v8 context and pass it to the test
+ // through a global.
+ scoped_ptr<ModuleSystemTestEnvironment> other_env(CreateEnvironment());
+ v8::Context::Scope scope(other_env->context()->v8_context());
+ v8::Local<v8::ArrayBuffer> array_buffer(
+ v8::ArrayBuffer::New(env()->isolate(), 1));
+ array_buffer_container.Set("value", array_buffer);
+ }
+ TestFunction("testType");
+}
+
+TEST_F(JsonSchemaTest, TestTypeReference) {
+ TestFunction("testTypeReference");
+}
+
+TEST_F(JsonSchemaTest, TestGetAllTypesForSchema) {
+ TestFunction("testGetAllTypesForSchema");
+}
+
+TEST_F(JsonSchemaTest, TestIsValidSchemaType) {
+ TestFunction("testIsValidSchemaType");
+}
+
+TEST_F(JsonSchemaTest, TestCheckSchemaOverlap) {
+ TestFunction("testCheckSchemaOverlap");
+}
+
+TEST_F(JsonSchemaTest, TestInstanceOf) {
+ TestFunction("testInstanceOf");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/lazy_background_page_native_handler.cc b/chromium/extensions/renderer/lazy_background_page_native_handler.cc
new file mode 100644
index 00000000000..f09beec7a7f
--- /dev/null
+++ b/chromium/extensions/renderer/lazy_background_page_native_handler.cc
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/lazy_background_page_native_handler.h"
+
+#include "base/bind.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+LazyBackgroundPageNativeHandler::LazyBackgroundPageNativeHandler(
+ ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction(
+ "IncrementKeepaliveCount",
+ base::Bind(&LazyBackgroundPageNativeHandler::IncrementKeepaliveCount,
+ base::Unretained(this)));
+ RouteFunction(
+ "DecrementKeepaliveCount",
+ base::Bind(&LazyBackgroundPageNativeHandler::DecrementKeepaliveCount,
+ base::Unretained(this)));
+}
+
+void LazyBackgroundPageNativeHandler::IncrementKeepaliveCount(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (context() && ExtensionFrameHelper::IsContextForEventPage(context())) {
+ content::RenderFrame* render_frame = context()->GetRenderFrame();
+ render_frame->Send(new ExtensionHostMsg_IncrementLazyKeepaliveCount(
+ render_frame->GetRoutingID()));
+ }
+}
+
+void LazyBackgroundPageNativeHandler::DecrementKeepaliveCount(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (context() && ExtensionFrameHelper::IsContextForEventPage(context())) {
+ content::RenderFrame* render_frame = context()->GetRenderFrame();
+ render_frame->Send(new ExtensionHostMsg_DecrementLazyKeepaliveCount(
+ render_frame->GetRoutingID()));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/lazy_background_page_native_handler.h b/chromium/extensions/renderer/lazy_background_page_native_handler.h
new file mode 100644
index 00000000000..88eaf0ffb1f
--- /dev/null
+++ b/chromium/extensions/renderer/lazy_background_page_native_handler.h
@@ -0,0 +1,23 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_LAZY_BACKGROUND_PAGE_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_LAZY_BACKGROUND_PAGE_NATIVE_HANDLER_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+
+class Extension;
+
+class LazyBackgroundPageNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit LazyBackgroundPageNativeHandler(ScriptContext* context);
+ void IncrementKeepaliveCount(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void DecrementKeepaliveCount(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_LAZY_BACKGROUND_PAGE_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/logging_native_handler.cc b/chromium/extensions/renderer/logging_native_handler.cc
new file mode 100644
index 00000000000..86fbc9c9fbe
--- /dev/null
+++ b/chromium/extensions/renderer/logging_native_handler.cc
@@ -0,0 +1,78 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "extensions/renderer/logging_native_handler.h"
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+LoggingNativeHandler::LoggingNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction(
+ "DCHECK",
+ base::Bind(&LoggingNativeHandler::Dcheck, base::Unretained(this)));
+ RouteFunction(
+ "CHECK",
+ base::Bind(&LoggingNativeHandler::Check, base::Unretained(this)));
+ RouteFunction(
+ "DCHECK_IS_ON",
+ base::Bind(&LoggingNativeHandler::DcheckIsOn, base::Unretained(this)));
+ RouteFunction("LOG",
+ base::Bind(&LoggingNativeHandler::Log, base::Unretained(this)));
+ RouteFunction(
+ "WARNING",
+ base::Bind(&LoggingNativeHandler::Warning, base::Unretained(this)));
+}
+
+LoggingNativeHandler::~LoggingNativeHandler() {}
+
+void LoggingNativeHandler::Check(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ bool check_value;
+ std::string error_message;
+ ParseArgs(args, &check_value, &error_message);
+ CHECK(check_value) << error_message;
+}
+
+void LoggingNativeHandler::Dcheck(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ bool check_value;
+ std::string error_message;
+ ParseArgs(args, &check_value, &error_message);
+ DCHECK(check_value) << error_message;
+}
+
+void LoggingNativeHandler::DcheckIsOn(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(DCHECK_IS_ON());
+}
+
+void LoggingNativeHandler::Log(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ LOG(INFO) << *v8::String::Utf8Value(args[0]);
+}
+
+void LoggingNativeHandler::Warning(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ LOG(WARNING) << *v8::String::Utf8Value(args[0]);
+}
+
+void LoggingNativeHandler::ParseArgs(
+ const v8::FunctionCallbackInfo<v8::Value>& args,
+ bool* check_value,
+ std::string* error_message) {
+ CHECK_LE(args.Length(), 2);
+ *check_value = args[0]->BooleanValue();
+ if (args.Length() == 2) {
+ *error_message = "Error: " + std::string(*v8::String::Utf8Value(args[1]));
+ }
+
+ *error_message += "\n" + context()->GetStackTraceAsString();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/logging_native_handler.h b/chromium/extensions/renderer/logging_native_handler.h
new file mode 100644
index 00000000000..ca9938c2760
--- /dev/null
+++ b/chromium/extensions/renderer/logging_native_handler.h
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_LOGGING_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_LOGGING_NATIVE_HANDLER_H_
+
+#include <string>
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+// Exposes logging.h macros to JavaScript bindings.
+class LoggingNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit LoggingNativeHandler(ScriptContext* context);
+ ~LoggingNativeHandler() override;
+
+ // Equivalent to CHECK(predicate) << message.
+ //
+ // void(predicate, message?)
+ void Check(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Equivalent to DCHECK(predicate) << message.
+ //
+ // void(predicate, message?)
+ void Dcheck(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Equivalent to DCHECK_IS_ON().
+ //
+ // bool()
+ void DcheckIsOn(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Equivalent to LOG(INFO) << message.
+ //
+ // void(message)
+ void Log(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Equivalent to LOG(WARNING) << message.
+ //
+ // void(message)
+ void Warning(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ void ParseArgs(const v8::FunctionCallbackInfo<v8::Value>& args,
+ bool* check_value,
+ std::string* error_message);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_LOGGING_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/messaging_bindings.cc b/chromium/extensions/renderer/messaging_bindings.cc
new file mode 100644
index 00000000000..95bf59a4b67
--- /dev/null
+++ b/chromium/extensions/renderer/messaging_bindings.cc
@@ -0,0 +1,492 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/messaging_bindings.h"
+
+#include <stdint.h>
+
+#include <map>
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/values.h"
+#include "components/guest_view/common/guest_view_constants.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/common/child_process_host.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/api/messaging/message.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/manifest_handlers/externally_connectable.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/event_bindings.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/gc_callback.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "extensions/renderer/v8_helpers.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebScopedUserGesture.h"
+#include "third_party/WebKit/public/web/WebScopedWindowFocusAllowedIndicator.h"
+#include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
+#include "v8/include/v8.h"
+
+// Message passing API example (in a content script):
+// var extension =
+// new chrome.Extension('00123456789abcdef0123456789abcdef0123456');
+// var port = runtime.connect();
+// port.postMessage('Can you hear me now?');
+// port.onmessage.addListener(function(msg, port) {
+// alert('response=' + msg);
+// port.postMessage('I got your reponse');
+// });
+
+using content::RenderThread;
+using content::V8ValueConverter;
+
+namespace extensions {
+
+using v8_helpers::ToV8String;
+using v8_helpers::ToV8StringUnsafe;
+using v8_helpers::IsEmptyOrUndefied;
+
+namespace {
+
+// Tracks every reference between ScriptContexts and Ports, by ID.
+class PortTracker {
+ public:
+ PortTracker() {}
+ ~PortTracker() {}
+
+ // Returns true if |context| references |port_id|.
+ bool HasReference(ScriptContext* context, int port_id) const {
+ auto ports = contexts_to_ports_.find(context);
+ return ports != contexts_to_ports_.end() &&
+ ports->second.count(port_id) > 0;
+ }
+
+ // Marks |context| and |port_id| as referencing each other.
+ void AddReference(ScriptContext* context, int port_id) {
+ contexts_to_ports_[context].insert(port_id);
+ }
+
+ // Removes the references between |context| and |port_id|.
+ // Returns true if a reference was removed, false if the reference didn't
+ // exist to be removed.
+ bool RemoveReference(ScriptContext* context, int port_id) {
+ auto ports = contexts_to_ports_.find(context);
+ if (ports == contexts_to_ports_.end() ||
+ ports->second.erase(port_id) == 0) {
+ return false;
+ }
+ if (ports->second.empty())
+ contexts_to_ports_.erase(context);
+ return true;
+ }
+
+ // Returns true if this tracker has any reference to |port_id|.
+ bool HasPort(int port_id) const {
+ for (auto it : contexts_to_ports_) {
+ if (it.second.count(port_id) > 0)
+ return true;
+ }
+ return false;
+ }
+
+ // Deletes all references to |port_id|.
+ void DeletePort(int port_id) {
+ for (auto it = contexts_to_ports_.begin();
+ it != contexts_to_ports_.end();) {
+ if (it->second.erase(port_id) > 0 && it->second.empty())
+ contexts_to_ports_.erase(it++);
+ else
+ ++it;
+ }
+ }
+
+ // Gets every port ID that has a reference to |context|.
+ std::set<int> GetPortsForContext(ScriptContext* context) const {
+ auto ports = contexts_to_ports_.find(context);
+ return ports == contexts_to_ports_.end() ? std::set<int>() : ports->second;
+ }
+
+ private:
+ // Maps ScriptContexts to the port IDs that have a reference to it.
+ std::map<ScriptContext*, std::set<int>> contexts_to_ports_;
+
+ DISALLOW_COPY_AND_ASSIGN(PortTracker);
+};
+
+base::LazyInstance<PortTracker> g_port_tracker = LAZY_INSTANCE_INITIALIZER;
+
+const char kPortClosedError[] = "Attempting to use a disconnected port object";
+
+class ExtensionImpl : public ObjectBackedNativeHandler {
+ public:
+ ExtensionImpl(Dispatcher* dispatcher, ScriptContext* context)
+ : ObjectBackedNativeHandler(context),
+ dispatcher_(dispatcher),
+ weak_ptr_factory_(this) {
+ RouteFunction(
+ "CloseChannel",
+ base::Bind(&ExtensionImpl::CloseChannel, base::Unretained(this)));
+ RouteFunction(
+ "PortAddRef",
+ base::Bind(&ExtensionImpl::PortAddRef, base::Unretained(this)));
+ RouteFunction(
+ "PortRelease",
+ base::Bind(&ExtensionImpl::PortRelease, base::Unretained(this)));
+ RouteFunction(
+ "PostMessage",
+ base::Bind(&ExtensionImpl::PostMessage, base::Unretained(this)));
+ // TODO(fsamuel, kalman): Move BindToGC out of messaging natives.
+ RouteFunction("BindToGC",
+ base::Bind(&ExtensionImpl::BindToGC, base::Unretained(this)));
+
+ // Observe |context| so that port references to it can be cleared.
+ context->AddInvalidationObserver(base::Bind(
+ &ExtensionImpl::OnContextInvalidated, weak_ptr_factory_.GetWeakPtr()));
+ }
+
+ ~ExtensionImpl() override {}
+
+ private:
+ void OnContextInvalidated() {
+ for (int port_id : g_port_tracker.Get().GetPortsForContext(context()))
+ ReleasePort(port_id);
+ }
+
+ void ClearPortDataAndNotifyDispatcher(int port_id) {
+ g_port_tracker.Get().DeletePort(port_id);
+ dispatcher_->ClearPortData(port_id);
+ }
+
+ // Sends a message along the given channel.
+ void PostMessage(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ content::RenderFrame* render_frame = context()->GetRenderFrame();
+ if (!render_frame)
+ return;
+
+ // Arguments are (int32_t port_id, string message).
+ CHECK(args.Length() == 2 && args[0]->IsInt32() && args[1]->IsString());
+
+ int port_id = args[0].As<v8::Int32>()->Value();
+ if (!g_port_tracker.Get().HasPort(port_id)) {
+ v8::Local<v8::String> error_message =
+ ToV8StringUnsafe(args.GetIsolate(), kPortClosedError);
+ args.GetIsolate()->ThrowException(v8::Exception::Error(error_message));
+ return;
+ }
+
+ render_frame->Send(new ExtensionHostMsg_PostMessage(
+ render_frame->GetRoutingID(), port_id,
+ Message(*v8::String::Utf8Value(args[1]),
+ blink::WebUserGestureIndicator::isProcessingUserGesture())));
+ }
+
+ // Forcefully disconnects a port.
+ void CloseChannel(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Arguments are (int32_t port_id, boolean notify_browser).
+ CHECK_EQ(2, args.Length());
+ CHECK(args[0]->IsInt32());
+ CHECK(args[1]->IsBoolean());
+
+ int port_id = args[0].As<v8::Int32>()->Value();
+ if (!g_port_tracker.Get().HasPort(port_id))
+ return;
+
+ // Send via the RenderThread because the RenderFrame might be closing.
+ bool notify_browser = args[1].As<v8::Boolean>()->Value();
+ content::RenderFrame* render_frame = context()->GetRenderFrame();
+ if (notify_browser && render_frame) {
+ render_frame->Send(new ExtensionHostMsg_CloseMessagePort(
+ render_frame->GetRoutingID(), port_id, true));
+ }
+
+ ClearPortDataAndNotifyDispatcher(port_id);
+ }
+
+ // A new port has been created for a context. This occurs both when script
+ // opens a connection, and when a connection is opened to this script.
+ void PortAddRef(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Arguments are (int32_t port_id).
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsInt32());
+
+ int port_id = args[0].As<v8::Int32>()->Value();
+ g_port_tracker.Get().AddReference(context(), port_id);
+ }
+
+ // The frame a port lived in has been destroyed. When there are no more
+ // frames with a reference to a given port, we will disconnect it and notify
+ // the other end of the channel.
+ // TODO(robwu): Port lifetime management has moved to the browser, this is no
+ // longer needed. See .destroy_() inmessaging.js for more details.
+ void PortRelease(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Arguments are (int32_t port_id).
+ CHECK(args.Length() == 1 && args[0]->IsInt32());
+ ReleasePort(args[0].As<v8::Int32>()->Value());
+ }
+
+ // Releases the reference to |port_id| for this context, and clears all port
+ // data if there are no more references.
+ void ReleasePort(int port_id) {
+ content::RenderFrame* render_frame = context()->GetRenderFrame();
+ if (g_port_tracker.Get().RemoveReference(context(), port_id) &&
+ !g_port_tracker.Get().HasPort(port_id) && render_frame) {
+ render_frame->Send(new ExtensionHostMsg_CloseMessagePort(
+ render_frame->GetRoutingID(), port_id, false));
+ }
+ }
+
+ // void BindToGC(object, callback, port_id)
+ //
+ // Binds |callback| to be invoked *sometime after* |object| is garbage
+ // collected. We don't call the method re-entrantly so as to avoid executing
+ // JS in some bizarro undefined mid-GC state, nor do we then call into the
+ // script context if it's been invalidated.
+ //
+ // If the script context *is* invalidated in the meantime, as a slight hack,
+ // release the port with ID |port_id| if it's >= 0.
+ void BindToGC(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK(args.Length() == 3 && args[0]->IsObject() && args[1]->IsFunction() &&
+ args[2]->IsInt32());
+ int port_id = args[2].As<v8::Int32>()->Value();
+ base::Closure fallback = base::Bind(&base::DoNothing);
+ if (port_id >= 0) {
+ fallback = base::Bind(&ExtensionImpl::ReleasePort,
+ weak_ptr_factory_.GetWeakPtr(), port_id);
+ }
+ // Destroys itself when the object is GC'd or context is invalidated.
+ new GCCallback(context(), args[0].As<v8::Object>(),
+ args[1].As<v8::Function>(), fallback);
+ }
+
+ // Dispatcher handle. Not owned.
+ Dispatcher* dispatcher_;
+
+ base::WeakPtrFactory<ExtensionImpl> weak_ptr_factory_;
+};
+
+void DispatchOnConnectToScriptContext(
+ int target_port_id,
+ const std::string& channel_name,
+ const ExtensionMsg_TabConnectionInfo* source,
+ const ExtensionMsg_ExternalConnectionInfo& info,
+ const std::string& tls_channel_id,
+ bool* port_created,
+ ScriptContext* script_context) {
+ v8::Isolate* isolate = script_context->isolate();
+ v8::HandleScope handle_scope(isolate);
+
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+
+ const std::string& source_url_spec = info.source_url.spec();
+ std::string target_extension_id = script_context->GetExtensionID();
+ const Extension* extension = script_context->extension();
+
+ v8::Local<v8::Value> tab = v8::Null(isolate);
+ v8::Local<v8::Value> tls_channel_id_value = v8::Undefined(isolate);
+ v8::Local<v8::Value> guest_process_id = v8::Undefined(isolate);
+ v8::Local<v8::Value> guest_render_frame_routing_id = v8::Undefined(isolate);
+
+ if (extension) {
+ if (!source->tab.empty() && !extension->is_platform_app())
+ tab = converter->ToV8Value(&source->tab, script_context->v8_context());
+
+ ExternallyConnectableInfo* externally_connectable =
+ ExternallyConnectableInfo::Get(extension);
+ if (externally_connectable &&
+ externally_connectable->accepts_tls_channel_id) {
+ v8::Local<v8::String> v8_tls_channel_id;
+ if (ToV8String(isolate, tls_channel_id.c_str(), &v8_tls_channel_id))
+ tls_channel_id_value = v8_tls_channel_id;
+ }
+
+ if (info.guest_process_id != content::ChildProcessHost::kInvalidUniqueID) {
+ guest_process_id = v8::Integer::New(isolate, info.guest_process_id);
+ guest_render_frame_routing_id =
+ v8::Integer::New(isolate, info.guest_render_frame_routing_id);
+ }
+ }
+
+ v8::Local<v8::String> v8_channel_name;
+ v8::Local<v8::String> v8_source_id;
+ v8::Local<v8::String> v8_target_extension_id;
+ v8::Local<v8::String> v8_source_url_spec;
+ if (!ToV8String(isolate, channel_name.c_str(), &v8_channel_name) ||
+ !ToV8String(isolate, info.source_id.c_str(), &v8_source_id) ||
+ !ToV8String(isolate, target_extension_id.c_str(),
+ &v8_target_extension_id) ||
+ !ToV8String(isolate, source_url_spec.c_str(), &v8_source_url_spec)) {
+ NOTREACHED() << "dispatchOnConnect() passed non-string argument";
+ return;
+ }
+
+ v8::Local<v8::Value> arguments[] = {
+ // portId
+ v8::Integer::New(isolate, target_port_id),
+ // channelName
+ v8_channel_name,
+ // sourceTab
+ tab,
+ // source_frame_id
+ v8::Integer::New(isolate, source->frame_id),
+ // guestProcessId
+ guest_process_id,
+ // guestRenderFrameRoutingId
+ guest_render_frame_routing_id,
+ // sourceExtensionId
+ v8_source_id,
+ // targetExtensionId
+ v8_target_extension_id,
+ // sourceUrl
+ v8_source_url_spec,
+ // tlsChannelId
+ tls_channel_id_value,
+ };
+
+ v8::Local<v8::Value> retval =
+ script_context->module_system()->CallModuleMethod(
+ "messaging", "dispatchOnConnect", arraysize(arguments), arguments);
+
+ if (!IsEmptyOrUndefied(retval)) {
+ CHECK(retval->IsBoolean());
+ *port_created |= retval.As<v8::Boolean>()->Value();
+ } else {
+ LOG(ERROR) << "Empty return value from dispatchOnConnect.";
+ }
+}
+
+void DeliverMessageToScriptContext(const Message& message,
+ int target_port_id,
+ ScriptContext* script_context) {
+ v8::Isolate* isolate = script_context->isolate();
+ v8::HandleScope handle_scope(isolate);
+
+ // Check to see whether the context has this port before bothering to create
+ // the message.
+ v8::Local<v8::Value> port_id_handle =
+ v8::Integer::New(isolate, target_port_id);
+ v8::Local<v8::Value> has_port =
+ script_context->module_system()->CallModuleMethod("messaging", "hasPort",
+ 1, &port_id_handle);
+ // Could be empty/undefined if an exception was thrown.
+ // TODO(kalman): Should this be built into CallModuleMethod?
+ if (IsEmptyOrUndefied(has_port))
+ return;
+ CHECK(has_port->IsBoolean());
+ if (!has_port.As<v8::Boolean>()->Value())
+ return;
+
+ v8::Local<v8::String> v8_data;
+ if (!ToV8String(isolate, message.data.c_str(), &v8_data))
+ return;
+ std::vector<v8::Local<v8::Value>> arguments;
+ arguments.push_back(v8_data);
+ arguments.push_back(port_id_handle);
+
+ scoped_ptr<blink::WebScopedUserGesture> web_user_gesture;
+ scoped_ptr<blink::WebScopedWindowFocusAllowedIndicator> allow_window_focus;
+ if (message.user_gesture) {
+ web_user_gesture.reset(new blink::WebScopedUserGesture);
+
+ if (script_context->web_frame()) {
+ blink::WebDocument document = script_context->web_frame()->document();
+ allow_window_focus.reset(new blink::WebScopedWindowFocusAllowedIndicator(
+ &document));
+ }
+ }
+
+ script_context->module_system()->CallModuleMethod(
+ "messaging", "dispatchOnMessage", &arguments);
+}
+
+void DispatchOnDisconnectToScriptContext(int port_id,
+ const std::string& error_message,
+ ScriptContext* script_context) {
+ v8::Isolate* isolate = script_context->isolate();
+ v8::HandleScope handle_scope(isolate);
+
+ std::vector<v8::Local<v8::Value>> arguments;
+ arguments.push_back(v8::Integer::New(isolate, port_id));
+ v8::Local<v8::String> v8_error_message;
+ if (!error_message.empty())
+ ToV8String(isolate, error_message.c_str(), &v8_error_message);
+ if (!v8_error_message.IsEmpty()) {
+ arguments.push_back(v8_error_message);
+ } else {
+ arguments.push_back(v8::Null(isolate));
+ }
+
+ script_context->module_system()->CallModuleMethod(
+ "messaging", "dispatchOnDisconnect", &arguments);
+}
+
+} // namespace
+
+ObjectBackedNativeHandler* MessagingBindings::Get(Dispatcher* dispatcher,
+ ScriptContext* context) {
+ return new ExtensionImpl(dispatcher, context);
+}
+
+// static
+void MessagingBindings::DispatchOnConnect(
+ const ScriptContextSet& context_set,
+ int target_port_id,
+ const std::string& channel_name,
+ const ExtensionMsg_TabConnectionInfo& source,
+ const ExtensionMsg_ExternalConnectionInfo& info,
+ const std::string& tls_channel_id,
+ content::RenderFrame* restrict_to_render_frame) {
+ int routing_id = restrict_to_render_frame
+ ? restrict_to_render_frame->GetRoutingID()
+ : MSG_ROUTING_NONE;
+ bool port_created = false;
+ context_set.ForEach(
+ info.target_id, restrict_to_render_frame,
+ base::Bind(&DispatchOnConnectToScriptContext, target_port_id,
+ channel_name, &source, info, tls_channel_id, &port_created));
+ // Note: |restrict_to_render_frame| may have been deleted at this point!
+
+ if (port_created) {
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_OpenMessagePort(routing_id, target_port_id));
+ } else {
+ content::RenderThread::Get()->Send(new ExtensionHostMsg_CloseMessagePort(
+ routing_id, target_port_id, false));
+ }
+}
+
+// static
+void MessagingBindings::DeliverMessage(
+ const ScriptContextSet& context_set,
+ int target_port_id,
+ const Message& message,
+ content::RenderFrame* restrict_to_render_frame) {
+ context_set.ForEach(
+ restrict_to_render_frame,
+ base::Bind(&DeliverMessageToScriptContext, message, target_port_id));
+}
+
+// static
+void MessagingBindings::DispatchOnDisconnect(
+ const ScriptContextSet& context_set,
+ int port_id,
+ const std::string& error_message,
+ content::RenderFrame* restrict_to_render_frame) {
+ context_set.ForEach(
+ restrict_to_render_frame,
+ base::Bind(&DispatchOnDisconnectToScriptContext, port_id, error_message));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/messaging_bindings.h b/chromium/extensions/renderer/messaging_bindings.h
new file mode 100644
index 00000000000..a7a1c6fef80
--- /dev/null
+++ b/chromium/extensions/renderer/messaging_bindings.h
@@ -0,0 +1,73 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_MESSAGING_BINDINGS_H_
+#define EXTENSIONS_RENDERER_MESSAGING_BINDINGS_H_
+
+#include <string>
+
+#include "extensions/renderer/script_context_set.h"
+
+struct ExtensionMsg_ExternalConnectionInfo;
+struct ExtensionMsg_TabConnectionInfo;
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace content {
+class RenderFrame;
+}
+
+namespace v8 {
+class Extension;
+}
+
+namespace extensions {
+class Dispatcher;
+struct Message;
+class ObjectBackedNativeHandler;
+class ScriptContextSet;
+
+// Manually implements JavaScript bindings for extension messaging.
+//
+// TODO(aa): This should all get re-implemented using SchemaGeneratedBindings.
+// If anything needs to be manual for some reason, it should be implemented in
+// its own class.
+class MessagingBindings {
+ public:
+ // Creates an instance of the extension.
+ static ObjectBackedNativeHandler* Get(Dispatcher* dispatcher,
+ ScriptContext* context);
+
+ // Dispatches the onConnect content script messaging event to some contexts
+ // in |context_set|. If |restrict_to_render_frame| is specified, only contexts
+ // in that render frame will receive the message.
+ static void DispatchOnConnect(const ScriptContextSet& context_set,
+ int target_port_id,
+ const std::string& channel_name,
+ const ExtensionMsg_TabConnectionInfo& source,
+ const ExtensionMsg_ExternalConnectionInfo& info,
+ const std::string& tls_channel_id,
+ content::RenderFrame* restrict_to_render_frame);
+
+ // Delivers a message sent using content script messaging to some of the
+ // contexts in |bindings_context_set|. If |restrict_to_render_frame| is
+ // specified, only contexts in that render view will receive the message.
+ static void DeliverMessage(const ScriptContextSet& context_set,
+ int target_port_id,
+ const Message& message,
+ content::RenderFrame* restrict_to_render_frame);
+
+ // Dispatches the onDisconnect event in response to the channel being closed.
+ static void DispatchOnDisconnect(
+ const ScriptContextSet& context_set,
+ int port_id,
+ const std::string& error_message,
+ content::RenderFrame* restrict_to_render_frame);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_MESSAGING_BINDINGS_H_
diff --git a/chromium/extensions/renderer/messaging_utils_unittest.cc b/chromium/extensions/renderer/messaging_utils_unittest.cc
new file mode 100644
index 00000000000..fbb2dec93c8
--- /dev/null
+++ b/chromium/extensions/renderer/messaging_utils_unittest.cc
@@ -0,0 +1,196 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/stringprintf.h"
+#include "extensions/renderer/module_system_test.h"
+#include "grit/extensions_renderer_resources.h"
+
+namespace extensions {
+namespace {
+
+class MessagingUtilsUnittest : public ModuleSystemTest {
+ protected:
+ void RegisterTestModule(const char* code) {
+ env()->RegisterModule(
+ "test",
+ base::StringPrintf(
+ "var assert = requireNative('assert');\n"
+ "var AssertTrue = assert.AssertTrue;\n"
+ "var AssertFalse = assert.AssertFalse;\n"
+ "var messagingUtils = require('messaging_utils');\n"
+ "%s",
+ code));
+ }
+
+ private:
+ void SetUp() override {
+ ModuleSystemTest::SetUp();
+
+ env()->RegisterModule("messaging_utils", IDR_MESSAGING_UTILS_JS);
+ }
+};
+
+TEST_F(MessagingUtilsUnittest, TestNothing) {
+ ExpectNoAssertionsMade();
+}
+
+TEST_F(MessagingUtilsUnittest, NoArguments) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ RegisterTestModule(
+ "var args = messagingUtils.alignSendMessageArguments();\n"
+ "AssertTrue(args === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, ZeroArguments) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ RegisterTestModule(
+ "var args = messagingUtils.alignSendMessageArguments([]);"
+ "AssertTrue(args === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, TooManyArgumentsNoOptions) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ RegisterTestModule(
+ "var args = messagingUtils.alignSendMessageArguments(\n"
+ " ['a', 'b', 'c', 'd']);\n"
+ "AssertTrue(args === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, TooManyArgumentsWithOptions) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ RegisterTestModule(
+ "var args = messagingUtils.alignSendMessageArguments(\n"
+ " ['a', 'b', 'c', 'd', 'e'], true);\n"
+ "AssertTrue(args === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, FinalArgumentIsNotAFunctionNoOptions) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ RegisterTestModule(
+ "var args = messagingUtils.alignSendMessageArguments(\n"
+ " ['a', 'b', 'c']);\n"
+ "AssertTrue(args === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, FinalArgumentIsNotAFunctionWithOptions) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ RegisterTestModule(
+ "var args = messagingUtils.alignSendMessageArguments(\n"
+ " ['a', 'b', 'c', 'd'], true);\n"
+ "AssertTrue(args === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, OneStringArgument) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ // Because the request argument is required, a single argument must get
+ // mapped to it rather than to the optional targetId argument.
+ RegisterTestModule(
+ "var args = messagingUtils.alignSendMessageArguments(['a']);\n"
+ "AssertTrue(args.length == 3);\n"
+ "AssertTrue(args[0] === null);\n"
+ "AssertTrue(args[1] == 'a');\n"
+ "AssertTrue(args[2] === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, OneStringAndOneNullArgument) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ // Explicitly specifying null as the request is allowed.
+ RegisterTestModule(
+ "var args = messagingUtils.alignSendMessageArguments(['a', null]);\n"
+ "AssertTrue(args.length == 3);\n"
+ "AssertTrue(args[0] == 'a');\n"
+ "AssertTrue(args[1] === null);\n"
+ "AssertTrue(args[2] === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, OneNullAndOneStringArgument) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ RegisterTestModule(
+ "var args = messagingUtils.alignSendMessageArguments([null, 'a']);\n"
+ "AssertTrue(args.length == 3);\n"
+ "AssertTrue(args[0] === null);\n"
+ "AssertTrue(args[1] == 'a');\n"
+ "AssertTrue(args[2] === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, OneStringAndOneFunctionArgument) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ // When the arguments are a string and a function, the function is
+ // unambiguously the responseCallback. Because the request argument is
+ // required, the remaining argument must get mapped to it rather than to the
+ // optional targetId argument.
+ RegisterTestModule(
+ "var cb = function() {};\n"
+ "var args = messagingUtils.alignSendMessageArguments(['a', cb]);\n"
+ "AssertTrue(args.length == 3);\n"
+ "AssertTrue(args[0] === null);\n"
+ "AssertTrue(args[1] == 'a');\n"
+ "AssertTrue(args[2] == cb);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, OneStringAndOneObjectArgument) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ // This tests an ambiguous set of arguments when options are present:
+ // chrome.runtime.sendMessage('target', {'msg': 'this is a message'});
+ // vs.
+ // chrome.runtime.sendMessage('request', {'includeTlsChannelId': true});
+ //
+ // The question is whether the string should map to the target and the
+ // dictionary to the message, or whether the string should map to the message
+ // and the dictionary to the options. Because the target and message arguments
+ // predate the options argument, we bind the string in this case to the
+ // targetId.
+ RegisterTestModule(
+ "var obj = {'b': true};\n"
+ "var args = messagingUtils.alignSendMessageArguments(['a', obj], true);\n"
+ "AssertTrue(args.length == 4);\n"
+ "AssertTrue(args[0] == 'a');\n"
+ "AssertTrue(args[1] == obj);\n"
+ "AssertTrue(args[2] === null);\n"
+ "AssertTrue(args[3] === null);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(MessagingUtilsUnittest, TwoObjectArguments) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ // When two non-string arguments are provided and options are present, the
+ // two arguments must match request and options, respectively, because
+ // targetId must be a string.
+ RegisterTestModule(
+ "var obj1 = {'a': 'foo'};\n"
+ "var obj2 = {'b': 'bar'};\n"
+ "var args = messagingUtils.alignSendMessageArguments(\n"
+ " [obj1, obj2], true);\n"
+ "AssertTrue(args.length == 4);\n"
+ "AssertTrue(args[0] === null);\n"
+ "AssertTrue(args[1] == obj1);\n"
+ "AssertTrue(args[2] == obj2);\n"
+ "AssertTrue(args[3] === null);");
+ env()->module_system()->Require("test");
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/renderer/module_system.cc b/chromium/extensions/renderer/module_system.cc
new file mode 100644
index 00000000000..4ff9b5c5f51
--- /dev/null
+++ b/chromium/extensions/renderer/module_system.cc
@@ -0,0 +1,757 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/module_system.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/trace_event/trace_event.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/renderer/console.h"
+#include "extensions/renderer/safe_builtins.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "extensions/renderer/v8_helpers.h"
+#include "gin/modules/module_registry.h"
+#include "third_party/WebKit/public/web/WebFrame.h"
+
+namespace extensions {
+
+using namespace v8_helpers;
+
+namespace {
+
+const char* kModuleSystem = "module_system";
+const char* kModuleName = "module_name";
+const char* kModuleField = "module_field";
+const char* kModulesField = "modules";
+
+// Logs an error for the calling context in preparation for potentially
+// crashing the renderer, with some added metadata about the context:
+// - Its type (blessed, unblessed, etc).
+// - Whether it's valid.
+// - The extension ID, if one exists.
+// Crashing won't happen in stable/beta releases, but is encouraged to happen
+// in the less stable released to catch errors early.
+void Fatal(ScriptContext* context, const std::string& message) {
+ // Prepend some context metadata.
+ std::string full_message = "(";
+ if (!context->is_valid())
+ full_message += "Invalid ";
+ full_message += context->GetContextTypeDescription();
+ full_message += " context";
+ if (context->extension()) {
+ full_message += " for ";
+ full_message += context->extension()->id();
+ }
+ full_message += ") ";
+ full_message += message;
+
+ ExtensionsClient* client = ExtensionsClient::Get();
+ if (client->ShouldSuppressFatalErrors()) {
+ console::Error(context->GetRenderFrame(), full_message);
+ client->RecordDidSuppressFatalError();
+ } else {
+ console::Fatal(context->GetRenderFrame(), full_message);
+ }
+}
+
+void Warn(v8::Isolate* isolate, const std::string& message) {
+ ScriptContext* script_context =
+ ScriptContextSet::GetContextByV8Context(isolate->GetCurrentContext());
+ console::Warn(script_context ? script_context->GetRenderFrame() : nullptr,
+ message);
+}
+
+// Default exception handler which logs the exception.
+class DefaultExceptionHandler : public ModuleSystem::ExceptionHandler {
+ public:
+ explicit DefaultExceptionHandler(ScriptContext* context)
+ : ModuleSystem::ExceptionHandler(context) {}
+
+ // Fatally dumps the debug info from |try_catch| to the console.
+ // Make sure this is never used for exceptions that originate in external
+ // code!
+ void HandleUncaughtException(const v8::TryCatch& try_catch) override {
+ v8::HandleScope handle_scope(context_->isolate());
+ std::string stack_trace = "<stack trace unavailable>";
+ v8::Local<v8::Value> v8_stack_trace;
+ if (try_catch.StackTrace(context_->v8_context()).ToLocal(&v8_stack_trace)) {
+ v8::String::Utf8Value stack_value(v8_stack_trace);
+ if (*stack_value)
+ stack_trace.assign(*stack_value, stack_value.length());
+ else
+ stack_trace = "<could not convert stack trace to string>";
+ }
+ Fatal(context_, CreateExceptionString(try_catch) + "{" + stack_trace + "}");
+ }
+};
+
+// Sets a property on the "exports" object for bindings. Called by JS with
+// exports.$set(<key>, <value>).
+void SetExportsProperty(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ v8::Local<v8::Object> obj = args.This();
+ CHECK_EQ(2, args.Length());
+ CHECK(args[0]->IsString());
+ v8::Maybe<bool> result =
+ obj->DefineOwnProperty(args.GetIsolate()->GetCurrentContext(),
+ args[0]->ToString(), args[1], v8::ReadOnly);
+ if (!result.FromMaybe(false))
+ LOG(ERROR) << "Failed to set private property on the export.";
+}
+
+} // namespace
+
+std::string ModuleSystem::ExceptionHandler::CreateExceptionString(
+ const v8::TryCatch& try_catch) {
+ v8::Local<v8::Message> message(try_catch.Message());
+ if (message.IsEmpty()) {
+ return "try_catch has no message";
+ }
+
+ std::string resource_name = "<unknown resource>";
+ if (!message->GetScriptOrigin().ResourceName().IsEmpty()) {
+ v8::String::Utf8Value resource_name_v8(
+ message->GetScriptOrigin().ResourceName());
+ resource_name.assign(*resource_name_v8, resource_name_v8.length());
+ }
+
+ std::string error_message = "<no error message>";
+ if (!message->Get().IsEmpty()) {
+ v8::String::Utf8Value error_message_v8(message->Get());
+ error_message.assign(*error_message_v8, error_message_v8.length());
+ }
+
+ auto maybe = message->GetLineNumber(context_->v8_context());
+ int line_number = maybe.IsJust() ? maybe.FromJust() : 0;
+ return base::StringPrintf("%s:%d: %s",
+ resource_name.c_str(),
+ line_number,
+ error_message.c_str());
+}
+
+ModuleSystem::ModuleSystem(ScriptContext* context, SourceMap* source_map)
+ : ObjectBackedNativeHandler(context),
+ context_(context),
+ source_map_(source_map),
+ natives_enabled_(0),
+ exception_handler_(new DefaultExceptionHandler(context)),
+ weak_factory_(this) {
+ RouteFunction(
+ "require",
+ base::Bind(&ModuleSystem::RequireForJs, base::Unretained(this)));
+ RouteFunction(
+ "requireNative",
+ base::Bind(&ModuleSystem::RequireNative, base::Unretained(this)));
+ RouteFunction(
+ "requireAsync",
+ base::Bind(&ModuleSystem::RequireAsync, base::Unretained(this)));
+ RouteFunction("privates",
+ base::Bind(&ModuleSystem::Private, base::Unretained(this)));
+
+ v8::Local<v8::Object> global(context->v8_context()->Global());
+ v8::Isolate* isolate = context->isolate();
+ SetPrivate(global, kModulesField, v8::Object::New(isolate));
+ SetPrivate(global, kModuleSystem, v8::External::New(isolate, this));
+
+ gin::ModuleRegistry::From(context->v8_context())->AddObserver(this);
+ if (context_->GetRenderFrame()) {
+ context_->GetRenderFrame()->EnsureMojoBuiltinsAreAvailable(
+ context->isolate(), context->v8_context());
+ }
+}
+
+ModuleSystem::~ModuleSystem() {
+}
+
+void ModuleSystem::Invalidate() {
+ // Clear the module system properties from the global context. It's polite,
+ // and we use this as a signal in lazy handlers that we no longer exist.
+ {
+ v8::HandleScope scope(GetIsolate());
+ v8::Local<v8::Object> global = context()->v8_context()->Global();
+ DeletePrivate(global, kModulesField);
+ DeletePrivate(global, kModuleSystem);
+ }
+
+ // Invalidate all active and clobbered NativeHandlers we own.
+ for (const auto& handler : native_handler_map_)
+ handler.second->Invalidate();
+ for (const auto& clobbered_handler : clobbered_native_handlers_)
+ clobbered_handler->Invalidate();
+
+ ObjectBackedNativeHandler::Invalidate();
+}
+
+ModuleSystem::NativesEnabledScope::NativesEnabledScope(
+ ModuleSystem* module_system)
+ : module_system_(module_system) {
+ module_system_->natives_enabled_++;
+}
+
+ModuleSystem::NativesEnabledScope::~NativesEnabledScope() {
+ module_system_->natives_enabled_--;
+ CHECK_GE(module_system_->natives_enabled_, 0);
+}
+
+void ModuleSystem::HandleException(const v8::TryCatch& try_catch) {
+ exception_handler_->HandleUncaughtException(try_catch);
+}
+
+v8::MaybeLocal<v8::Object> ModuleSystem::Require(
+ const std::string& module_name) {
+ v8::Local<v8::String> v8_module_name;
+ if (!ToV8String(GetIsolate(), module_name, &v8_module_name))
+ return v8::MaybeLocal<v8::Object>();
+ v8::EscapableHandleScope handle_scope(GetIsolate());
+ v8::Local<v8::Value> value = RequireForJsInner(
+ v8_module_name);
+ if (value.IsEmpty() || !value->IsObject())
+ return v8::MaybeLocal<v8::Object>();
+ return handle_scope.Escape(value.As<v8::Object>());
+}
+
+void ModuleSystem::RequireForJs(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (!args[0]->IsString()) {
+ NOTREACHED() << "require() called with a non-string argument";
+ return;
+ }
+ v8::Local<v8::String> module_name = args[0].As<v8::String>();
+ args.GetReturnValue().Set(RequireForJsInner(module_name));
+}
+
+v8::Local<v8::Value> ModuleSystem::RequireForJsInner(
+ v8::Local<v8::String> module_name) {
+ v8::EscapableHandleScope handle_scope(GetIsolate());
+ v8::Local<v8::Context> v8_context = context()->v8_context();
+ v8::Context::Scope context_scope(v8_context);
+
+ v8::Local<v8::Object> global(context()->v8_context()->Global());
+
+ // The module system might have been deleted. This can happen if a different
+ // context keeps a reference to us, but our frame is destroyed (e.g.
+ // background page keeps reference to chrome object in a closed popup).
+ v8::Local<v8::Value> modules_value;
+ if (!GetPrivate(global, kModulesField, &modules_value) ||
+ modules_value->IsUndefined()) {
+ Warn(GetIsolate(), "Extension view no longer exists");
+ return v8::Undefined(GetIsolate());
+ }
+
+ v8::Local<v8::Object> modules(v8::Local<v8::Object>::Cast(modules_value));
+ v8::Local<v8::Value> exports;
+ if (!GetPrivateProperty(v8_context, modules, module_name, &exports) ||
+ !exports->IsUndefined())
+ return handle_scope.Escape(exports);
+
+ exports = LoadModule(*v8::String::Utf8Value(module_name));
+ SetPrivateProperty(v8_context, modules, module_name, exports);
+ return handle_scope.Escape(exports);
+}
+
+v8::Local<v8::Value> ModuleSystem::CallModuleMethod(
+ const std::string& module_name,
+ const std::string& method_name) {
+ v8::EscapableHandleScope handle_scope(GetIsolate());
+ v8::Local<v8::Value> no_args;
+ return handle_scope.Escape(
+ CallModuleMethod(module_name, method_name, 0, &no_args));
+}
+
+v8::Local<v8::Value> ModuleSystem::CallModuleMethod(
+ const std::string& module_name,
+ const std::string& method_name,
+ std::vector<v8::Local<v8::Value>>* args) {
+ return CallModuleMethod(module_name, method_name, args->size(), args->data());
+}
+
+v8::Local<v8::Value> ModuleSystem::CallModuleMethod(
+ const std::string& module_name,
+ const std::string& method_name,
+ int argc,
+ v8::Local<v8::Value> argv[]) {
+ TRACE_EVENT2("v8",
+ "v8.callModuleMethod",
+ "module_name",
+ module_name,
+ "method_name",
+ method_name);
+
+ v8::EscapableHandleScope handle_scope(GetIsolate());
+ v8::Local<v8::Context> v8_context = context()->v8_context();
+ v8::Context::Scope context_scope(v8_context);
+
+ v8::Local<v8::String> v8_module_name;
+ v8::Local<v8::String> v8_method_name;
+ if (!ToV8String(GetIsolate(), module_name.c_str(), &v8_module_name) ||
+ !ToV8String(GetIsolate(), method_name.c_str(), &v8_method_name)) {
+ return handle_scope.Escape(v8::Undefined(GetIsolate()));
+ }
+
+ v8::Local<v8::Value> module;
+ {
+ NativesEnabledScope natives_enabled(this);
+ module = RequireForJsInner(v8_module_name);
+ }
+
+ if (module.IsEmpty() || !module->IsObject()) {
+ Fatal(context_,
+ "Failed to get module " + module_name + " to call " + method_name);
+ return handle_scope.Escape(v8::Undefined(GetIsolate()));
+ }
+
+ v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(module);
+ v8::Local<v8::Value> value;
+ if (!GetProperty(v8_context, object, v8_method_name, &value) ||
+ !value->IsFunction()) {
+ Fatal(context_, module_name + "." + method_name + " is not a function");
+ return handle_scope.Escape(v8::Undefined(GetIsolate()));
+ }
+
+ v8::Local<v8::Function> func = v8::Local<v8::Function>::Cast(value);
+ v8::Local<v8::Value> result;
+ {
+ v8::TryCatch try_catch(GetIsolate());
+ try_catch.SetCaptureMessage(true);
+ result = context_->CallFunction(func, argc, argv);
+ if (try_catch.HasCaught()) {
+ HandleException(try_catch);
+ result = v8::Undefined(GetIsolate());
+ }
+ }
+ return handle_scope.Escape(result);
+}
+
+void ModuleSystem::RegisterNativeHandler(
+ const std::string& name,
+ scoped_ptr<NativeHandler> native_handler) {
+ ClobberExistingNativeHandler(name);
+ native_handler_map_[name] = std::move(native_handler);
+}
+
+void ModuleSystem::OverrideNativeHandlerForTest(const std::string& name) {
+ ClobberExistingNativeHandler(name);
+ overridden_native_handlers_.insert(name);
+}
+
+void ModuleSystem::RunString(const std::string& code, const std::string& name) {
+ v8::HandleScope handle_scope(GetIsolate());
+ v8::Local<v8::String> v8_code;
+ v8::Local<v8::String> v8_name;
+ if (!ToV8String(GetIsolate(), code.c_str(), &v8_code) ||
+ !ToV8String(GetIsolate(), name.c_str(), &v8_name)) {
+ Warn(GetIsolate(), "Too long code or name.");
+ return;
+ }
+ RunString(v8_code, v8_name);
+}
+
+// static
+void ModuleSystem::NativeLazyFieldGetter(
+ v8::Local<v8::Name> property,
+ const v8::PropertyCallbackInfo<v8::Value>& info) {
+ LazyFieldGetterInner(property.As<v8::String>(), info,
+ &ModuleSystem::RequireNativeFromString);
+}
+
+// static
+void ModuleSystem::LazyFieldGetter(
+ v8::Local<v8::Name> property,
+ const v8::PropertyCallbackInfo<v8::Value>& info) {
+ LazyFieldGetterInner(property.As<v8::String>(), info, &ModuleSystem::Require);
+}
+
+// static
+void ModuleSystem::LazyFieldGetterInner(
+ v8::Local<v8::String> property,
+ const v8::PropertyCallbackInfo<v8::Value>& info,
+ RequireFunction require_function) {
+ CHECK(!info.Data().IsEmpty());
+ CHECK(info.Data()->IsObject());
+ v8::Isolate* isolate = info.GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Object> parameters = v8::Local<v8::Object>::Cast(info.Data());
+ // This context should be the same as context()->v8_context().
+ v8::Local<v8::Context> context = parameters->CreationContext();
+ v8::Local<v8::Object> global(context->Global());
+ v8::Local<v8::Value> module_system_value;
+ if (!GetPrivate(context, global, kModuleSystem, &module_system_value) ||
+ !module_system_value->IsExternal()) {
+ // ModuleSystem has been deleted.
+ // TODO(kalman): See comment in header file.
+ Warn(isolate,
+ "Module system has been deleted, does extension view exist?");
+ return;
+ }
+
+ ModuleSystem* module_system = static_cast<ModuleSystem*>(
+ v8::Local<v8::External>::Cast(module_system_value)->Value());
+
+ v8::Local<v8::Value> v8_module_name;
+ if (!GetPrivateProperty(context, parameters, kModuleName, &v8_module_name)) {
+ Warn(isolate, "Cannot find module.");
+ return;
+ }
+ std::string name = *v8::String::Utf8Value(v8_module_name);
+
+ // Switch to our v8 context because we need functions created while running
+ // the require()d module to belong to our context, not the current one.
+ v8::Context::Scope context_scope(context);
+ NativesEnabledScope natives_enabled_scope(module_system);
+
+ v8::TryCatch try_catch(isolate);
+ v8::Local<v8::Value> module_value;
+ if (!(module_system->*require_function)(name).ToLocal(&module_value)) {
+ module_system->HandleException(try_catch);
+ return;
+ }
+
+ v8::Local<v8::Object> module = v8::Local<v8::Object>::Cast(module_value);
+ v8::Local<v8::Value> field_value;
+ if (!GetPrivateProperty(context, parameters, kModuleField, &field_value)) {
+ module_system->HandleException(try_catch);
+ return;
+ }
+ v8::Local<v8::String> field;
+ if (!field_value->ToString(context).ToLocal(&field)) {
+ module_system->HandleException(try_catch);
+ return;
+ }
+
+ if (!IsTrue(module->Has(context, field))) {
+ std::string field_str = *v8::String::Utf8Value(field);
+ Fatal(module_system->context_,
+ "Lazy require of " + name + "." + field_str + " did not set the " +
+ field_str + " field");
+ return;
+ }
+
+ v8::Local<v8::Value> new_field;
+ if (!GetProperty(context, module, field, &new_field)) {
+ module_system->HandleException(try_catch);
+ return;
+ }
+
+ // Ok for it to be undefined, among other things it's how bindings signify
+ // that the extension doesn't have permission to use them.
+ CHECK(!new_field.IsEmpty());
+
+ // Delete the getter and set this field to |new_field| so the same object is
+ // returned every time a certain API is accessed.
+ v8::Local<v8::Value> val = info.This();
+ if (val->IsObject()) {
+ v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(val);
+ object->Delete(context, property);
+ SetProperty(context, object, property, new_field);
+ } else {
+ NOTREACHED();
+ }
+ info.GetReturnValue().Set(new_field);
+}
+
+void ModuleSystem::SetLazyField(v8::Local<v8::Object> object,
+ const std::string& field,
+ const std::string& module_name,
+ const std::string& module_field) {
+ SetLazyField(
+ object, field, module_name, module_field, &ModuleSystem::LazyFieldGetter);
+}
+
+void ModuleSystem::SetLazyField(v8::Local<v8::Object> object,
+ const std::string& field,
+ const std::string& module_name,
+ const std::string& module_field,
+ v8::AccessorNameGetterCallback getter) {
+ CHECK(field.size() < v8::String::kMaxLength);
+ CHECK(module_name.size() < v8::String::kMaxLength);
+ CHECK(module_field.size() < v8::String::kMaxLength);
+ v8::HandleScope handle_scope(GetIsolate());
+ v8::Local<v8::Object> parameters = v8::Object::New(GetIsolate());
+ v8::Local<v8::Context> context = context_->v8_context();
+ SetPrivateProperty(context, parameters, kModuleName,
+ ToV8StringUnsafe(GetIsolate(), module_name.c_str()));
+ SetPrivateProperty(context, parameters, kModuleField,
+ ToV8StringUnsafe(GetIsolate(), module_field.c_str()));
+ auto maybe = object->SetAccessor(
+ context, ToV8StringUnsafe(GetIsolate(), field.c_str()), getter, NULL,
+ parameters);
+ CHECK(IsTrue(maybe));
+}
+
+void ModuleSystem::SetNativeLazyField(v8::Local<v8::Object> object,
+ const std::string& field,
+ const std::string& module_name,
+ const std::string& module_field) {
+ SetLazyField(object,
+ field,
+ module_name,
+ module_field,
+ &ModuleSystem::NativeLazyFieldGetter);
+}
+
+v8::Local<v8::Value> ModuleSystem::RunString(v8::Local<v8::String> code,
+ v8::Local<v8::String> name) {
+ return context_->RunScript(
+ name, code, base::Bind(&ExceptionHandler::HandleUncaughtException,
+ base::Unretained(exception_handler_.get())));
+}
+
+v8::Local<v8::Value> ModuleSystem::GetSource(const std::string& module_name) {
+ v8::EscapableHandleScope handle_scope(GetIsolate());
+ if (!source_map_->Contains(module_name))
+ return v8::Undefined(GetIsolate());
+ return handle_scope.Escape(
+ v8::Local<v8::Value>(source_map_->GetSource(GetIsolate(), module_name)));
+}
+
+void ModuleSystem::RequireNative(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ std::string native_name = *v8::String::Utf8Value(args[0]);
+ v8::Local<v8::Object> object;
+ if (RequireNativeFromString(native_name).ToLocal(&object))
+ args.GetReturnValue().Set(object);
+}
+
+v8::MaybeLocal<v8::Object> ModuleSystem::RequireNativeFromString(
+ const std::string& native_name) {
+ if (natives_enabled_ == 0) {
+ // HACK: if in test throw exception so that we can test the natives-disabled
+ // logic; however, under normal circumstances, this is programmer error so
+ // we could crash.
+ if (exception_handler_) {
+ GetIsolate()->ThrowException(
+ ToV8StringUnsafe(GetIsolate(), "Natives disabled"));
+ return v8::MaybeLocal<v8::Object>();
+ }
+ Fatal(context_, "Natives disabled for requireNative(" + native_name + ")");
+ return v8::MaybeLocal<v8::Object>();
+ }
+
+ if (overridden_native_handlers_.count(native_name) > 0u) {
+ v8::Local<v8::Value> value = RequireForJsInner(
+ ToV8StringUnsafe(GetIsolate(), native_name.c_str()));
+ if (value.IsEmpty() || !value->IsObject())
+ return v8::MaybeLocal<v8::Object>();
+ return value.As<v8::Object>();
+ }
+
+ NativeHandlerMap::iterator i = native_handler_map_.find(native_name);
+ if (i == native_handler_map_.end()) {
+ Fatal(context_,
+ "Couldn't find native for requireNative(" + native_name + ")");
+ return v8::MaybeLocal<v8::Object>();
+ }
+ return i->second->NewInstance();
+}
+
+void ModuleSystem::RequireAsync(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ std::string module_name = *v8::String::Utf8Value(args[0]);
+ v8::Local<v8::Context> v8_context = context_->v8_context();
+ v8::Local<v8::Promise::Resolver> resolver(
+ v8::Promise::Resolver::New(v8_context).ToLocalChecked());
+ args.GetReturnValue().Set(resolver->GetPromise());
+ scoped_ptr<v8::Global<v8::Promise::Resolver>> global_resolver(
+ new v8::Global<v8::Promise::Resolver>(GetIsolate(), resolver));
+ gin::ModuleRegistry* module_registry =
+ gin::ModuleRegistry::From(v8_context);
+ if (!module_registry) {
+ Warn(GetIsolate(), "Extension view no longer exists");
+ resolver->Reject(v8_context, v8::Exception::Error(ToV8StringUnsafe(
+ GetIsolate(), "Extension view no longer exists")));
+ return;
+ }
+ module_registry->LoadModule(
+ GetIsolate(), module_name,
+ base::Bind(&ModuleSystem::OnModuleLoaded, weak_factory_.GetWeakPtr(),
+ base::Passed(&global_resolver)));
+ if (module_registry->available_modules().count(module_name) == 0)
+ LoadModule(module_name);
+}
+
+v8::Local<v8::String> ModuleSystem::WrapSource(v8::Local<v8::String> source) {
+ v8::EscapableHandleScope handle_scope(GetIsolate());
+ // Keep in order with the arguments in RequireForJsInner.
+ v8::Local<v8::String> left = ToV8StringUnsafe(
+ GetIsolate(),
+ "(function(define, require, requireNative, requireAsync, exports, "
+ "console, privates,"
+ "$Array, $Function, $JSON, $Object, $RegExp, $String, $Error) {"
+ "'use strict';");
+ v8::Local<v8::String> right = ToV8StringUnsafe(GetIsolate(), "\n})");
+ return handle_scope.Escape(v8::Local<v8::String>(
+ v8::String::Concat(left, v8::String::Concat(source, right))));
+}
+
+void ModuleSystem::Private(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ if (!args[0]->IsObject() || args[0]->IsNull()) {
+ GetIsolate()->ThrowException(
+ v8::Exception::TypeError(ToV8StringUnsafe(GetIsolate(),
+ args[0]->IsUndefined()
+ ? "Method called without a valid receiver (this). "
+ "Did you forget to call .bind()?"
+ : "Invalid invocation: receiver is not an object!")));
+ return;
+ }
+ v8::Local<v8::Object> obj = args[0].As<v8::Object>();
+ v8::Local<v8::Value> privates;
+ if (!GetPrivate(obj, "privates", &privates) || !privates->IsObject()) {
+ privates = v8::Object::New(args.GetIsolate());
+ if (privates.IsEmpty()) {
+ GetIsolate()->ThrowException(
+ ToV8StringUnsafe(GetIsolate(), "Failed to create privates"));
+ return;
+ }
+ v8::Maybe<bool> maybe =
+ privates.As<v8::Object>()->SetPrototype(context()->v8_context(),
+ v8::Null(args.GetIsolate()));
+ CHECK(maybe.IsJust() && maybe.FromJust());
+ SetPrivate(obj, "privates", privates);
+ }
+ args.GetReturnValue().Set(privates);
+}
+
+v8::Local<v8::Value> ModuleSystem::LoadModule(const std::string& module_name) {
+ v8::EscapableHandleScope handle_scope(GetIsolate());
+ v8::Local<v8::Context> v8_context = context()->v8_context();
+ v8::Context::Scope context_scope(v8_context);
+
+ v8::Local<v8::Value> source(GetSource(module_name));
+ if (source.IsEmpty() || source->IsUndefined()) {
+ Fatal(context_, "No source for require(" + module_name + ")");
+ return v8::Undefined(GetIsolate());
+ }
+ v8::Local<v8::String> wrapped_source(
+ WrapSource(v8::Local<v8::String>::Cast(source)));
+ v8::Local<v8::String> v8_module_name;
+ if (!ToV8String(GetIsolate(), module_name.c_str(), &v8_module_name)) {
+ NOTREACHED() << "module_name is too long";
+ return v8::Undefined(GetIsolate());
+ }
+ // Modules are wrapped in (function(){...}) so they always return functions.
+ v8::Local<v8::Value> func_as_value =
+ RunString(wrapped_source, v8_module_name);
+ if (func_as_value.IsEmpty() || func_as_value->IsUndefined()) {
+ Fatal(context_, "Bad source for require(" + module_name + ")");
+ return v8::Undefined(GetIsolate());
+ }
+
+ v8::Local<v8::Function> func = v8::Local<v8::Function>::Cast(func_as_value);
+
+ v8::Local<v8::Object> define_object = v8::Object::New(GetIsolate());
+ gin::ModuleRegistry::InstallGlobals(GetIsolate(), define_object);
+
+ v8::Local<v8::Object> exports = v8::Object::New(GetIsolate());
+
+ v8::Local<v8::FunctionTemplate> tmpl = v8::FunctionTemplate::New(
+ GetIsolate(),
+ &SetExportsProperty);
+ v8::Local<v8::String> v8_key;
+ if (!v8_helpers::ToV8String(GetIsolate(), "$set", &v8_key)) {
+ NOTREACHED();
+ return v8::Undefined(GetIsolate());
+ }
+
+ v8::Local<v8::Function> function;
+ if (!tmpl->GetFunction(v8_context).ToLocal(&function)) {
+ NOTREACHED();
+ return v8::Undefined(GetIsolate());
+ }
+
+ exports->DefineOwnProperty(v8_context, v8_key, function, v8::ReadOnly)
+ .FromJust();
+
+ v8::Local<v8::Object> natives(NewInstance());
+ CHECK(!natives.IsEmpty()); // this can fail if v8 has issues
+
+ // These must match the argument order in WrapSource.
+ v8::Local<v8::Value> args[] = {
+ // AMD.
+ GetPropertyUnsafe(v8_context, define_object, "define"),
+ // CommonJS.
+ GetPropertyUnsafe(v8_context, natives, "require",
+ v8::NewStringType::kInternalized),
+ GetPropertyUnsafe(v8_context, natives, "requireNative",
+ v8::NewStringType::kInternalized),
+ GetPropertyUnsafe(v8_context, natives, "requireAsync",
+ v8::NewStringType::kInternalized),
+ exports,
+ // Libraries that we magically expose to every module.
+ console::AsV8Object(GetIsolate()),
+ GetPropertyUnsafe(v8_context, natives, "privates",
+ v8::NewStringType::kInternalized),
+ // Each safe builtin. Keep in order with the arguments in WrapSource.
+ context_->safe_builtins()->GetArray(),
+ context_->safe_builtins()->GetFunction(),
+ context_->safe_builtins()->GetJSON(),
+ context_->safe_builtins()->GetObjekt(),
+ context_->safe_builtins()->GetRegExp(),
+ context_->safe_builtins()->GetString(),
+ context_->safe_builtins()->GetError(),
+ };
+ {
+ v8::TryCatch try_catch(GetIsolate());
+ try_catch.SetCaptureMessage(true);
+ context_->CallFunction(func, arraysize(args), args);
+ if (try_catch.HasCaught()) {
+ HandleException(try_catch);
+ return v8::Undefined(GetIsolate());
+ }
+ }
+ return handle_scope.Escape(exports);
+}
+
+void ModuleSystem::OnDidAddPendingModule(
+ const std::string& id,
+ const std::vector<std::string>& dependencies) {
+ bool module_system_managed = source_map_->Contains(id);
+
+ gin::ModuleRegistry* registry =
+ gin::ModuleRegistry::From(context_->v8_context());
+ DCHECK(registry);
+ for (const auto& dependency : dependencies) {
+ // If a dependency is not available, and either the module or this
+ // dependency is managed by ModuleSystem, attempt to load it. Other
+ // gin::ModuleRegistry users (WebUI and users of the mojoPrivate API) are
+ // responsible for loading their module dependencies when required.
+ if (registry->available_modules().count(dependency) == 0 &&
+ (module_system_managed || source_map_->Contains(dependency))) {
+ LoadModule(dependency);
+ }
+ }
+ registry->AttemptToLoadMoreModules(GetIsolate());
+}
+
+void ModuleSystem::OnModuleLoaded(
+ scoped_ptr<v8::Global<v8::Promise::Resolver>> resolver,
+ v8::Local<v8::Value> value) {
+ if (!is_valid())
+ return;
+ v8::HandleScope handle_scope(GetIsolate());
+ v8::Local<v8::Promise::Resolver> resolver_local(
+ v8::Local<v8::Promise::Resolver>::New(GetIsolate(), *resolver));
+ resolver_local->Resolve(context()->v8_context(), value);
+}
+
+void ModuleSystem::ClobberExistingNativeHandler(const std::string& name) {
+ NativeHandlerMap::iterator existing_handler = native_handler_map_.find(name);
+ if (existing_handler != native_handler_map_.end()) {
+ clobbered_native_handlers_.push_back(std::move(existing_handler->second));
+ native_handler_map_.erase(existing_handler);
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/module_system.h b/chromium/extensions/renderer/module_system.h
new file mode 100644
index 00000000000..134d2baa05f
--- /dev/null
+++ b/chromium/extensions/renderer/module_system.h
@@ -0,0 +1,253 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_MODULE_SYSTEM_H_
+#define EXTENSIONS_RENDERER_MODULE_SYSTEM_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/renderer/native_handler.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "gin/modules/module_registry_observer.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+class ScriptContext;
+
+// A module system for JS similar to node.js' require() function.
+// Each module has three variables in the global scope:
+// - exports, an object returned to dependencies who require() this
+// module.
+// - require, a function that takes a module name as an argument and returns
+// that module's exports object.
+// - requireNative, a function that takes the name of a registered
+// NativeHandler and returns an object that contains the functions the
+// NativeHandler defines.
+//
+// Each module in a ModuleSystem is executed at most once and its exports
+// object cached.
+//
+// Note that a ModuleSystem must be used only in conjunction with a single
+// v8::Context.
+// TODO(koz): Rename this to JavaScriptModuleSystem.
+class ModuleSystem : public ObjectBackedNativeHandler,
+ public gin::ModuleRegistryObserver {
+ public:
+ class SourceMap {
+ public:
+ virtual ~SourceMap() {}
+ virtual v8::Local<v8::Value> GetSource(v8::Isolate* isolate,
+ const std::string& name) = 0;
+ virtual bool Contains(const std::string& name) = 0;
+ };
+
+ class ExceptionHandler {
+ public:
+ explicit ExceptionHandler(ScriptContext* context) : context_(context) {}
+ virtual ~ExceptionHandler() {}
+ virtual void HandleUncaughtException(const v8::TryCatch& try_catch) = 0;
+
+ protected:
+ // Formats |try_catch| as a nice string.
+ std::string CreateExceptionString(const v8::TryCatch& try_catch);
+ // A script context associated with this handler. Owned by the module
+ // system.
+ ScriptContext* context_;
+ };
+
+ // Enables native bindings for the duration of its lifetime.
+ class NativesEnabledScope {
+ public:
+ explicit NativesEnabledScope(ModuleSystem* module_system);
+ ~NativesEnabledScope();
+
+ private:
+ ModuleSystem* module_system_;
+ DISALLOW_COPY_AND_ASSIGN(NativesEnabledScope);
+ };
+
+ // |source_map| is a weak pointer.
+ ModuleSystem(ScriptContext* context, SourceMap* source_map);
+ ~ModuleSystem() override;
+
+ // Require the specified module. This is the equivalent of calling
+ // require('module_name') from the loaded JS files.
+ v8::MaybeLocal<v8::Object> Require(const std::string& module_name);
+ void Require(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Run |code| in the current context with the name |name| used for stack
+ // traces.
+ v8::Local<v8::Value> RunString(v8::Local<v8::String> code,
+ v8::Local<v8::String> name);
+
+ // Calls the specified method exported by the specified module. This is
+ // equivalent to calling require('module_name').method_name() from JS.
+ v8::Local<v8::Value> CallModuleMethod(const std::string& module_name,
+ const std::string& method_name);
+ v8::Local<v8::Value> CallModuleMethod(
+ const std::string& module_name,
+ const std::string& method_name,
+ std::vector<v8::Local<v8::Value>>* args);
+ v8::Local<v8::Value> CallModuleMethod(const std::string& module_name,
+ const std::string& method_name,
+ int argc,
+ v8::Local<v8::Value> argv[]);
+
+ // Register |native_handler| as a potential target for requireNative(), so
+ // calls to requireNative(|name|) from JS will return a new object created by
+ // |native_handler|.
+ void RegisterNativeHandler(const std::string& name,
+ scoped_ptr<NativeHandler> native_handler);
+
+ // Causes requireNative(|name|) to look for its module in |source_map_|
+ // instead of using a registered native handler. This can be used in unit
+ // tests to mock out native modules.
+ void OverrideNativeHandlerForTest(const std::string& name);
+
+ // Executes |code| in the current context with |name| as the filename.
+ void RunString(const std::string& code, const std::string& name);
+
+ // Make |object|.|field| lazily evaluate to the result of
+ // require(|module_name|)[|module_field|].
+ //
+ // TODO(kalman): All targets for this method are ObjectBackedNativeHandlers,
+ // move this logic into those classes (in fact, the chrome
+ // object is the only client, only that needs to implement it).
+ void SetLazyField(v8::Local<v8::Object> object,
+ const std::string& field,
+ const std::string& module_name,
+ const std::string& module_field);
+
+ void SetLazyField(v8::Local<v8::Object> object,
+ const std::string& field,
+ const std::string& module_name,
+ const std::string& module_field,
+ v8::AccessorNameGetterCallback getter);
+
+ // Make |object|.|field| lazily evaluate to the result of
+ // requireNative(|module_name|)[|module_field|].
+ // TODO(kalman): Same as above.
+ void SetNativeLazyField(v8::Local<v8::Object> object,
+ const std::string& field,
+ const std::string& module_name,
+ const std::string& module_field);
+
+ // Passes exceptions to |handler| rather than console::Fatal.
+ void SetExceptionHandlerForTest(scoped_ptr<ExceptionHandler> handler) {
+ exception_handler_ = std::move(handler);
+ }
+
+ protected:
+ friend class ModuleSystemTestEnvironment;
+ friend class ScriptContext;
+ void Invalidate() override;
+
+ private:
+ typedef std::map<std::string, scoped_ptr<NativeHandler>> NativeHandlerMap;
+
+ // Retrieves the lazily defined field specified by |property|.
+ static void LazyFieldGetter(v8::Local<v8::Name> property,
+ const v8::PropertyCallbackInfo<v8::Value>& info);
+ // Retrieves the lazily defined field specified by |property| on a native
+ // object.
+ static void NativeLazyFieldGetter(
+ v8::Local<v8::Name> property,
+ const v8::PropertyCallbackInfo<v8::Value>& info);
+
+ // Called when an exception is thrown but not caught.
+ void HandleException(const v8::TryCatch& try_catch);
+
+ void RequireForJs(const v8::FunctionCallbackInfo<v8::Value>& args);
+ v8::Local<v8::Value> RequireForJsInner(v8::Local<v8::String> module_name);
+
+ typedef v8::MaybeLocal<v8::Object>(ModuleSystem::*RequireFunction)(
+ const std::string&);
+ // Base implementation of a LazyFieldGetter which uses |require_fn| to require
+ // modules.
+ static void LazyFieldGetterInner(
+ v8::Local<v8::String> property,
+ const v8::PropertyCallbackInfo<v8::Value>& info,
+ RequireFunction require_function);
+
+ // Return the named source file stored in the source map.
+ // |args[0]| - the name of a source file in source_map_.
+ v8::Local<v8::Value> GetSource(const std::string& module_name);
+
+ // Return an object that contains the native methods defined by the named
+ // NativeHandler.
+ // |args[0]| - the name of a native handler object.
+ v8::MaybeLocal<v8::Object> RequireNativeFromString(
+ const std::string& native_name);
+ void RequireNative(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Return a promise for a requested module.
+ // |args[0]| - the name of a module.
+ void RequireAsync(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Wraps |source| in a (function(define, require, requireNative, ...) {...}).
+ v8::Local<v8::String> WrapSource(v8::Local<v8::String> source);
+
+ // NativeHandler implementation which returns the private area of an Object.
+ void Private(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Loads and runs a Javascript module.
+ v8::Local<v8::Value> LoadModule(const std::string& module_name);
+
+ // Invoked when a module is loaded in response to a requireAsync call.
+ // Resolves |resolver| with |value|.
+ void OnModuleLoaded(scoped_ptr<v8::Global<v8::Promise::Resolver>> resolver,
+ v8::Local<v8::Value> value);
+
+ // gin::ModuleRegistryObserver overrides.
+ void OnDidAddPendingModule(
+ const std::string& id,
+ const std::vector<std::string>& dependencies) override;
+
+ // Marks any existing NativeHandler named |name| as clobbered.
+ // See |clobbered_native_handlers_|.
+ void ClobberExistingNativeHandler(const std::string& name);
+
+ ScriptContext* context_;
+
+ // A map from module names to the JS source for that module. GetSource()
+ // performs a lookup on this map.
+ SourceMap* source_map_;
+
+ // A map from native handler names to native handlers.
+ NativeHandlerMap native_handler_map_;
+
+ // When 0, natives are disabled, otherwise indicates how many callers have
+ // pinned natives as enabled.
+ int natives_enabled_;
+
+ // Called when an exception is thrown but not caught in JS. Overridable by
+ // tests.
+ scoped_ptr<ExceptionHandler> exception_handler_;
+
+ // A set of native handlers that should actually be require()d as non-native
+ // handlers. This is used for tests to mock out native handlers in JS.
+ std::set<std::string> overridden_native_handlers_;
+
+ // A list of NativeHandlers that have been clobbered, either due to
+ // registering a NativeHandler when one was already registered with the same
+ // name, or due to OverrideNativeHandlerForTest. This is needed so that they
+ // can be later Invalidated. It should only happen in tests.
+ std::vector<scoped_ptr<NativeHandler>> clobbered_native_handlers_;
+
+ base::WeakPtrFactory<ModuleSystem> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ModuleSystem);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_MODULE_SYSTEM_H_
diff --git a/chromium/extensions/renderer/module_system_test.cc b/chromium/extensions/renderer/module_system_test.cc
new file mode 100644
index 00000000000..3386ff010c1
--- /dev/null
+++ b/chromium/extensions/renderer/module_system_test.cc
@@ -0,0 +1,261 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/module_system_test.h"
+
+#include <stddef.h>
+
+#include <map>
+#include <string>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "base/strings/string_piece.h"
+#include "extensions/common/extension_paths.h"
+#include "extensions/renderer/logging_native_handler.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "extensions/renderer/safe_builtins.h"
+#include "extensions/renderer/utils_native_handler.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace extensions {
+namespace {
+
+class FailsOnException : public ModuleSystem::ExceptionHandler {
+ public:
+ FailsOnException() : ModuleSystem::ExceptionHandler(nullptr) {}
+ void HandleUncaughtException(const v8::TryCatch& try_catch) override {
+ FAIL() << "Uncaught exception: " << CreateExceptionString(try_catch);
+ }
+};
+
+class V8ExtensionConfigurator {
+ public:
+ V8ExtensionConfigurator()
+ : safe_builtins_(SafeBuiltins::CreateV8Extension()),
+ names_(1, safe_builtins_->name()),
+ configuration_(
+ new v8::ExtensionConfiguration(static_cast<int>(names_.size()),
+ names_.data())) {
+ v8::RegisterExtension(safe_builtins_.get());
+ }
+
+ v8::ExtensionConfiguration* GetConfiguration() {
+ return configuration_.get();
+ }
+
+ private:
+ scoped_ptr<v8::Extension> safe_builtins_;
+ std::vector<const char*> names_;
+ scoped_ptr<v8::ExtensionConfiguration> configuration_;
+};
+
+base::LazyInstance<V8ExtensionConfigurator>::Leaky g_v8_extension_configurator =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// Native JS functions for doing asserts.
+class ModuleSystemTestEnvironment::AssertNatives
+ : public ObjectBackedNativeHandler {
+ public:
+ explicit AssertNatives(ScriptContext* context)
+ : ObjectBackedNativeHandler(context),
+ assertion_made_(false),
+ failed_(false) {
+ RouteFunction(
+ "AssertTrue",
+ base::Bind(&AssertNatives::AssertTrue, base::Unretained(this)));
+ RouteFunction(
+ "AssertFalse",
+ base::Bind(&AssertNatives::AssertFalse, base::Unretained(this)));
+ }
+
+ bool assertion_made() { return assertion_made_; }
+ bool failed() { return failed_; }
+
+ void AssertTrue(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ assertion_made_ = true;
+ failed_ = failed_ || !args[0]->ToBoolean(args.GetIsolate())->Value();
+ }
+
+ void AssertFalse(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ assertion_made_ = true;
+ failed_ = failed_ || args[0]->ToBoolean(args.GetIsolate())->Value();
+ }
+
+ private:
+ bool assertion_made_;
+ bool failed_;
+};
+
+// Source map that operates on std::strings.
+class ModuleSystemTestEnvironment::StringSourceMap
+ : public ModuleSystem::SourceMap {
+ public:
+ StringSourceMap() {}
+ ~StringSourceMap() override {}
+
+ v8::Local<v8::Value> GetSource(v8::Isolate* isolate,
+ const std::string& name) override {
+ if (source_map_.count(name) == 0)
+ return v8::Undefined(isolate);
+ return v8::String::NewFromUtf8(isolate, source_map_[name].c_str());
+ }
+
+ bool Contains(const std::string& name) override {
+ return source_map_.count(name);
+ }
+
+ void RegisterModule(const std::string& name, const std::string& source) {
+ CHECK_EQ(0u, source_map_.count(name)) << "Module " << name << " not found";
+ source_map_[name] = source;
+ }
+
+ private:
+ std::map<std::string, std::string> source_map_;
+};
+
+ModuleSystemTestEnvironment::ModuleSystemTestEnvironment(v8::Isolate* isolate)
+ : isolate_(isolate),
+ context_holder_(new gin::ContextHolder(isolate_)),
+ handle_scope_(isolate_),
+ source_map_(new StringSourceMap()) {
+ context_holder_->SetContext(v8::Context::New(
+ isolate, g_v8_extension_configurator.Get().GetConfiguration()));
+ context_.reset(new ScriptContext(context_holder_->context(),
+ nullptr, // WebFrame
+ nullptr, // Extension
+ Feature::BLESSED_EXTENSION_CONTEXT,
+ nullptr, // Effective Extension
+ Feature::BLESSED_EXTENSION_CONTEXT));
+ context_->v8_context()->Enter();
+ assert_natives_ = new AssertNatives(context_.get());
+
+ {
+ scoped_ptr<ModuleSystem> module_system(
+ new ModuleSystem(context_.get(), source_map_.get()));
+ context_->set_module_system(std::move(module_system));
+ }
+ ModuleSystem* module_system = context_->module_system();
+ module_system->RegisterNativeHandler(
+ "assert", scoped_ptr<NativeHandler>(assert_natives_));
+ module_system->RegisterNativeHandler(
+ "logging",
+ scoped_ptr<NativeHandler>(new LoggingNativeHandler(context_.get())));
+ module_system->RegisterNativeHandler(
+ "utils",
+ scoped_ptr<NativeHandler>(new UtilsNativeHandler(context_.get())));
+ module_system->SetExceptionHandlerForTest(
+ scoped_ptr<ModuleSystem::ExceptionHandler>(new FailsOnException));
+}
+
+ModuleSystemTestEnvironment::~ModuleSystemTestEnvironment() {
+ if (context_->is_valid())
+ ShutdownModuleSystem();
+}
+
+void ModuleSystemTestEnvironment::RegisterModule(const std::string& name,
+ const std::string& code) {
+ source_map_->RegisterModule(name, code);
+}
+
+void ModuleSystemTestEnvironment::RegisterModule(const std::string& name,
+ int resource_id) {
+ const std::string& code = ResourceBundle::GetSharedInstance()
+ .GetRawDataResource(resource_id)
+ .as_string();
+ source_map_->RegisterModule(name, code);
+}
+
+void ModuleSystemTestEnvironment::OverrideNativeHandler(
+ const std::string& name,
+ const std::string& code) {
+ RegisterModule(name, code);
+ context_->module_system()->OverrideNativeHandlerForTest(name);
+}
+
+void ModuleSystemTestEnvironment::RegisterTestFile(
+ const std::string& module_name,
+ const std::string& file_name) {
+ base::FilePath test_js_file_path;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &test_js_file_path));
+ test_js_file_path = test_js_file_path.AppendASCII(file_name);
+ std::string test_js;
+ ASSERT_TRUE(base::ReadFileToString(test_js_file_path, &test_js));
+ source_map_->RegisterModule(module_name, test_js);
+}
+
+void ModuleSystemTestEnvironment::ShutdownGin() {
+ context_holder_.reset();
+}
+
+void ModuleSystemTestEnvironment::ShutdownModuleSystem() {
+ CHECK(context_->is_valid());
+ context_->v8_context()->Exit();
+ context_->Invalidate();
+}
+
+v8::Local<v8::Object> ModuleSystemTestEnvironment::CreateGlobal(
+ const std::string& name) {
+ v8::EscapableHandleScope handle_scope(isolate_);
+ v8::Local<v8::Object> object = v8::Object::New(isolate_);
+ isolate_->GetCurrentContext()->Global()->Set(
+ v8::String::NewFromUtf8(isolate_, name.c_str()), object);
+ return handle_scope.Escape(object);
+}
+
+ModuleSystemTest::ModuleSystemTest()
+ : isolate_(v8::Isolate::GetCurrent()),
+ should_assertions_be_made_(true) {
+}
+
+ModuleSystemTest::~ModuleSystemTest() {
+}
+
+void ModuleSystemTest::SetUp() {
+ env_ = CreateEnvironment();
+ base::CommandLine::ForCurrentProcess()->AppendSwitch("test-type");
+}
+
+void ModuleSystemTest::TearDown() {
+ // All tests must assert at least once unless otherwise specified.
+ EXPECT_EQ(should_assertions_be_made_,
+ env_->assert_natives()->assertion_made());
+ EXPECT_FALSE(env_->assert_natives()->failed());
+ env_.reset();
+ v8::HeapStatistics stats;
+ isolate_->GetHeapStatistics(&stats);
+ size_t old_heap_size = 0;
+ // Run the GC until the heap size reaches a steady state to ensure that
+ // all the garbage is collected.
+ while (stats.used_heap_size() != old_heap_size) {
+ old_heap_size = stats.used_heap_size();
+ isolate_->RequestGarbageCollectionForTesting(
+ v8::Isolate::kFullGarbageCollection);
+ isolate_->GetHeapStatistics(&stats);
+ }
+}
+
+scoped_ptr<ModuleSystemTestEnvironment> ModuleSystemTest::CreateEnvironment() {
+ return make_scoped_ptr(new ModuleSystemTestEnvironment(isolate_));
+}
+
+void ModuleSystemTest::ExpectNoAssertionsMade() {
+ should_assertions_be_made_ = false;
+}
+
+void ModuleSystemTest::RunResolvedPromises() {
+ v8::MicrotasksScope::PerformCheckpoint(isolate_);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/module_system_test.h b/chromium/extensions/renderer/module_system_test.h
new file mode 100644
index 00000000000..517c3080c00
--- /dev/null
+++ b/chromium/extensions/renderer/module_system_test.h
@@ -0,0 +1,111 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_MODULE_SYSTEM_TEST_H_
+#define EXTENSIONS_RENDERER_MODULE_SYSTEM_TEST_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/module_system.h"
+#include "extensions/renderer/script_context.h"
+#include "gin/public/context_holder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+class ModuleSystemTestEnvironment {
+ public:
+ class AssertNatives;
+ class StringSourceMap;
+
+ explicit ModuleSystemTestEnvironment(v8::Isolate* isolate);
+ ~ModuleSystemTestEnvironment();
+
+ // Register a named JS module in the module system.
+ void RegisterModule(const std::string& name, const std::string& code);
+
+ // Register a named JS module with source retrieved from a ResourceBundle.
+ void RegisterModule(const std::string& name, int resource_id);
+
+ // Register a named JS module in the module system and tell the module system
+ // to use it to handle any requireNative() calls for native modules with that
+ // name.
+ void OverrideNativeHandler(const std::string& name, const std::string& code);
+
+ // Registers |file_name| from chrome/test/data/extensions as a module name
+ // |module_name|.
+ void RegisterTestFile(const std::string& module_name,
+ const std::string& file_name);
+
+ // Create an empty object in the global scope with name |name|.
+ v8::Local<v8::Object> CreateGlobal(const std::string& name);
+
+ void ShutdownGin();
+
+ void ShutdownModuleSystem();
+
+ ModuleSystem* module_system() { return context_->module_system(); }
+
+ ScriptContext* context() { return context_.get(); }
+
+ v8::Isolate* isolate() { return isolate_; }
+
+ AssertNatives* assert_natives() { return assert_natives_; }
+
+ private:
+ v8::Isolate* isolate_;
+ scoped_ptr<gin::ContextHolder> context_holder_;
+ v8::HandleScope handle_scope_;
+ scoped_ptr<ScriptContext> context_;
+ AssertNatives* assert_natives_;
+ scoped_ptr<StringSourceMap> source_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(ModuleSystemTestEnvironment);
+};
+
+// Test fixture for testing JS that makes use of the module system.
+//
+// Typically tests will look like:
+//
+// TEST_F(MyModuleSystemTest, TestStuff) {
+// ModuleSystem::NativesEnabledScope natives_enabled(module_system_.get());
+// RegisterModule("test", "requireNative('assert').AssertTrue(true);");
+// module_system_->Require("test");
+// }
+//
+// By default a test will fail if no method in the native module 'assert' is
+// called. This behaviour can be overridden by calling ExpectNoAssertionsMade().
+class ModuleSystemTest : public testing::Test {
+ public:
+ ModuleSystemTest();
+ ~ModuleSystemTest() override;
+
+ void SetUp() override;
+ void TearDown() override;
+
+ protected:
+ ModuleSystemTestEnvironment* env() { return env_.get(); }
+
+ scoped_ptr<ModuleSystemTestEnvironment> CreateEnvironment();
+
+ // Make the test fail if any asserts are called. By default a test will fail
+ // if no asserts are called.
+ void ExpectNoAssertionsMade();
+
+ // Runs promises that have been resolved. Resolved promises will not run
+ // until this is called.
+ void RunResolvedPromises();
+
+ private:
+ v8::Isolate* isolate_;
+ scoped_ptr<ModuleSystemTestEnvironment> env_;
+ bool should_assertions_be_made_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ModuleSystemTest);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_MODULE_SYSTEM_TEST_H_
diff --git a/chromium/extensions/renderer/module_system_unittest.cc b/chromium/extensions/renderer/module_system_unittest.cc
new file mode 100644
index 00000000000..5803ad0d694
--- /dev/null
+++ b/chromium/extensions/renderer/module_system_unittest.cc
@@ -0,0 +1,518 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/module_system.h"
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/memory/scoped_ptr.h"
+#include "extensions/renderer/module_system_test.h"
+#include "gin/modules/module_registry.h"
+
+namespace extensions {
+
+class CounterNatives : public ObjectBackedNativeHandler {
+ public:
+ explicit CounterNatives(ScriptContext* context)
+ : ObjectBackedNativeHandler(context), counter_(0) {
+ RouteFunction("Get",
+ base::Bind(&CounterNatives::Get, base::Unretained(this)));
+ RouteFunction(
+ "Increment",
+ base::Bind(&CounterNatives::Increment, base::Unretained(this)));
+ }
+
+ void Get(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(static_cast<int32_t>(counter_));
+ }
+
+ void Increment(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ counter_++;
+ }
+
+ private:
+ int counter_;
+};
+
+class TestExceptionHandler : public ModuleSystem::ExceptionHandler {
+ public:
+ TestExceptionHandler()
+ : ModuleSystem::ExceptionHandler(nullptr), handled_exception_(false) {}
+
+ void HandleUncaughtException(const v8::TryCatch& try_catch) override {
+ handled_exception_ = true;
+ }
+
+ bool handled_exception() const { return handled_exception_; }
+
+ private:
+ bool handled_exception_;
+};
+
+TEST_F(ModuleSystemTest, TestExceptionHandling) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ TestExceptionHandler* handler = new TestExceptionHandler;
+ scoped_ptr<ModuleSystem::ExceptionHandler> scoped_handler(handler);
+ ASSERT_FALSE(handler->handled_exception());
+ env()->module_system()->SetExceptionHandlerForTest(std::move(scoped_handler));
+
+ env()->RegisterModule("test", "throw 'hi';");
+ env()->module_system()->Require("test");
+ ASSERT_TRUE(handler->handled_exception());
+
+ ExpectNoAssertionsMade();
+}
+
+TEST_F(ModuleSystemTest, TestRequire) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("add",
+ "exports.$set('Add',"
+ "function(x, y) { return x + y; });");
+ env()->RegisterModule("test",
+ "var Add = require('add').Add;"
+ "requireNative('assert').AssertTrue(Add(3, 5) == 8);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestNestedRequire) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("add",
+ "exports.$set('Add',"
+ "function(x, y) { return x + y; });");
+ env()->RegisterModule("double",
+ "var Add = require('add').Add;"
+ "exports.$set('Double',"
+ "function(x) { return Add(x, x); });");
+ env()->RegisterModule("test",
+ "var Double = require('double').Double;"
+ "requireNative('assert').AssertTrue(Double(3) == 6);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestModuleInsulation) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("x",
+ "var x = 10;"
+ "exports.$set('X', function() { return x; });");
+ env()->RegisterModule("y",
+ "var x = 15;"
+ "require('x');"
+ "exports.$set('Y', function() { return x; });");
+ env()->RegisterModule("test",
+ "var Y = require('y').Y;"
+ "var X = require('x').X;"
+ "var assert = requireNative('assert');"
+ "assert.AssertTrue(!this.hasOwnProperty('x'));"
+ "assert.AssertTrue(Y() == 15);"
+ "assert.AssertTrue(X() == 10);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestNativesAreDisabledOutsideANativesEnabledScope) {
+ env()->RegisterModule("test",
+ "var assert;"
+ "try {"
+ " assert = requireNative('assert');"
+ "} catch (e) {"
+ " var caught = true;"
+ "}"
+ "if (assert) {"
+ " assert.AssertTrue(true);"
+ "}");
+ env()->module_system()->Require("test");
+ ExpectNoAssertionsMade();
+}
+
+TEST_F(ModuleSystemTest, TestNativesAreEnabledWithinANativesEnabledScope) {
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');"
+ "assert.AssertTrue(true);");
+
+ {
+ ModuleSystem::NativesEnabledScope natives_enabled(env()->module_system());
+ {
+ ModuleSystem::NativesEnabledScope natives_enabled_inner(
+ env()->module_system());
+ }
+ env()->module_system()->Require("test");
+ }
+}
+
+TEST_F(ModuleSystemTest, TestLazyField) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("lazy", "exports.$set('x', 5);");
+
+ v8::Local<v8::Object> object = env()->CreateGlobal("object");
+
+ env()->module_system()->SetLazyField(object, "blah", "lazy", "x");
+
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');"
+ "assert.AssertTrue(object.blah == 5);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestLazyFieldYieldingObject) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "lazy",
+ "var object = {};"
+ "object.__defineGetter__('z', function() { return 1; });"
+ "object.x = 5;"
+ "object.y = function() { return 10; };"
+ "exports.$set('object', object);");
+
+ v8::Local<v8::Object> object = env()->CreateGlobal("object");
+
+ env()->module_system()->SetLazyField(object, "thing", "lazy", "object");
+
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');"
+ "assert.AssertTrue(object.thing.x == 5);"
+ "assert.AssertTrue(object.thing.y() == 10);"
+ "assert.AssertTrue(object.thing.z == 1);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestLazyFieldIsOnlyEvaledOnce) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->module_system()->RegisterNativeHandler(
+ "counter",
+ scoped_ptr<NativeHandler>(new CounterNatives(env()->context())));
+ env()->RegisterModule("lazy",
+ "requireNative('counter').Increment();"
+ "exports.$set('x', 5);");
+
+ v8::Local<v8::Object> object = env()->CreateGlobal("object");
+
+ env()->module_system()->SetLazyField(object, "x", "lazy", "x");
+
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');"
+ "var counter = requireNative('counter');"
+ "assert.AssertTrue(counter.Get() == 0);"
+ "object.x;"
+ "assert.AssertTrue(counter.Get() == 1);"
+ "object.x;"
+ "assert.AssertTrue(counter.Get() == 1);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestRequireNativesAfterLazyEvaluation) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("lazy", "exports.$set('x', 5);");
+ v8::Local<v8::Object> object = env()->CreateGlobal("object");
+
+ env()->module_system()->SetLazyField(object, "x", "lazy", "x");
+ env()->RegisterModule("test",
+ "object.x;"
+ "requireNative('assert').AssertTrue(true);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestTransitiveRequire) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("dependency", "exports.$set('x', 5);");
+ env()->RegisterModule("lazy",
+ "exports.$set('output', require('dependency'));");
+
+ v8::Local<v8::Object> object = env()->CreateGlobal("object");
+
+ env()->module_system()->SetLazyField(object, "thing", "lazy", "output");
+
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');"
+ "assert.AssertTrue(object.thing.x == 5);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestModulesOnlyGetEvaledOnce) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->module_system()->RegisterNativeHandler(
+ "counter",
+ scoped_ptr<NativeHandler>(new CounterNatives(env()->context())));
+
+ env()->RegisterModule("incrementsWhenEvaled",
+ "requireNative('counter').Increment();");
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');"
+ "var counter = requireNative('counter');"
+ "assert.AssertTrue(counter.Get() == 0);"
+ "require('incrementsWhenEvaled');"
+ "assert.AssertTrue(counter.Get() == 1);"
+ "require('incrementsWhenEvaled');"
+ "assert.AssertTrue(counter.Get() == 1);");
+
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestOverrideNativeHandler) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->OverrideNativeHandler("assert",
+ "exports.$set('AssertTrue', function() {});");
+ env()->RegisterModule("test", "requireNative('assert').AssertTrue(true);");
+ ExpectNoAssertionsMade();
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestOverrideNonExistentNativeHandler) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->OverrideNativeHandler("thing", "exports.$set('x', 5);");
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');"
+ "assert.AssertTrue(requireNative('thing').x == 5);");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(ModuleSystemTest, TestRequireAsync) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("add",
+ "define('add', [], function() {"
+ " return { Add: function(x, y) { return x + y; } };"
+ "});");
+ env()->RegisterModule("math",
+ "define('math', ['add'], function(add) {"
+ " return { Add: add.Add };"
+ "});");
+ env()->RegisterModule(
+ "test",
+ "requireAsync('math').then(function(math) {"
+ " requireNative('assert').AssertTrue(math.Add(3, 5) == 8);"
+ "});");
+ env()->module_system()->Require("test");
+ RunResolvedPromises();
+}
+
+TEST_F(ModuleSystemTest, TestRequireAsyncInParallel) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("add",
+ "define('add', [], function() {"
+ " return { Add: function(x, y) { return x + y; } };"
+ "});");
+ env()->RegisterModule(
+ "subtract",
+ "define('subtract', [], function() {"
+ " return { Subtract: function(x, y) { return x - y; } };"
+ "});");
+ env()->RegisterModule(
+ "math",
+ "exports.$set('AddAndSubtract', function(x, y, z) {"
+ " return Promise.all([requireAsync('add'),"
+ " requireAsync('subtract')"
+ " ]).then(function(modules) {"
+ " return modules[1].Subtract(modules[0].Add(x, y), z);"
+ " });"
+ "});");
+ env()->RegisterModule("test",
+ "var AddAndSubtract = require('math').AddAndSubtract;"
+ "AddAndSubtract(3, 5, 2).then(function(result) {"
+ " requireNative('assert').AssertTrue(result == 6);"
+ "});");
+ env()->module_system()->Require("test");
+ RunResolvedPromises();
+}
+
+TEST_F(ModuleSystemTest, TestNestedRequireAsyncs) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("first",
+ "define('first', [], function() {"
+ " return { next: 'second' };"
+ "});");
+ env()->RegisterModule("second",
+ "define('second', [], function() {"
+ " return { next: '' };"
+ "});");
+ env()->RegisterModule(
+ "test",
+ "requireAsync('first').then(function(module) {"
+ " return requireAsync(module.next)"
+ "}).then(function(module) {"
+ " requireNative('assert').AssertTrue(module.next === '');"
+ "});");
+ env()->module_system()->Require("test");
+ RunResolvedPromises();
+}
+
+TEST_F(ModuleSystemTest, TestRequireFromAMDModule) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "add", "exports.$set('Add', function(x, y) { return x + y; });");
+ env()->RegisterModule("math",
+ "define('math', [], function() {"
+ " var add = require('add');"
+ " return { Add: add.Add };"
+ "});");
+ env()->RegisterModule(
+ "test",
+ "requireAsync('math').then(function(math) {"
+ " requireNative('assert').AssertTrue(math.Add(3, 5) == 8);"
+ "});");
+ env()->module_system()->Require("test");
+ RunResolvedPromises();
+}
+
+TEST_F(ModuleSystemTest, TestRequireAsyncFromAMDModule) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("add",
+ "define('add', [], function() {"
+ " return { Add: function(x, y) { return x + y; } };"
+ "});");
+ env()->RegisterModule("math",
+ "define('math', [], function() {"
+ " function Add(x, y) {"
+ " return requireAsync('add').then(function(add) {"
+ " return add.Add(x, y);"
+ " });"
+ " }"
+ " return { Add: Add };"
+ "});");
+ env()->RegisterModule("test",
+ "requireAsync('math').then(function(math) {"
+ " return math.Add(3, 6);"
+ "}).then(function(result) {"
+ " requireNative('assert').AssertTrue(result == 9);"
+ "});");
+ env()->module_system()->Require("test");
+ RunResolvedPromises();
+}
+
+TEST_F(ModuleSystemTest, TestRequireAsyncFromAnotherContext) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "requireAsync('natives').then(function(natives) {"
+ " natives.requireAsync('ping').then(function(ping) {"
+ " return ping();"
+ " }).then(function(result) {"
+ " requireNative('assert').AssertTrue(result == 'pong');"
+ " });"
+ "});");
+ scoped_ptr<ModuleSystemTestEnvironment> other_env = CreateEnvironment();
+ other_env->RegisterModule("ping",
+ "define('ping', ['natives'], function(natives) {"
+ " return function() {"
+ " return 'pong';"
+ " }"
+ "});");
+ gin::ModuleRegistry::From(env()->context()->v8_context())
+ ->AddBuiltinModule(
+ env()->isolate(), "natives",
+ other_env->module_system()->NewInstance());
+ gin::ModuleRegistry::From(other_env->context()->v8_context())
+ ->AddBuiltinModule(
+ env()->isolate(), "natives",
+ env()->module_system()->NewInstance());
+ env()->module_system()->Require("test");
+ RunResolvedPromises();
+}
+
+TEST_F(ModuleSystemTest, TestRequireAsyncBetweenContexts) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("pong",
+ "define('pong', [], function() {"
+ " return function() { return 'done'; };"
+ "});");
+ env()->RegisterModule(
+ "test",
+ "requireAsync('natives').then(function(natives) {"
+ " natives.requireAsync('ping').then(function(ping) {"
+ " return ping();"
+ " }).then(function(pong) {"
+ " return pong();"
+ " }).then(function(result) {"
+ " requireNative('assert').AssertTrue(result == 'done');"
+ " });"
+ "});");
+ scoped_ptr<ModuleSystemTestEnvironment> other_env = CreateEnvironment();
+ other_env->RegisterModule("ping",
+ "define('ping', ['natives'], function(natives) {"
+ " return function() {"
+ " return natives.requireAsync('pong');"
+ " }"
+ "});");
+ gin::ModuleRegistry::From(env()->context()->v8_context())
+ ->AddBuiltinModule(
+ env()->isolate(), "natives",
+ other_env->module_system()->NewInstance());
+ gin::ModuleRegistry::From(other_env->context()->v8_context())
+ ->AddBuiltinModule(
+ env()->isolate(), "natives",
+ env()->module_system()->NewInstance());
+ env()->module_system()->Require("test");
+ RunResolvedPromises();
+}
+
+TEST_F(ModuleSystemTest, TestRequireAsyncFromContextWithNoModuleRegistry) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("test",
+ "requireAsync('natives').then(function(natives) {"
+ " var AssertTrue = requireNative('assert').AssertTrue;"
+ " natives.requireAsync('foo').then(function() {"
+ " AssertTrue(false);"
+ " }).catch(function(error) {"
+ " AssertTrue(error.message == "
+ " 'Extension view no longer exists');"
+ " });"
+ "});");
+ scoped_ptr<ModuleSystemTestEnvironment> other_env = CreateEnvironment();
+ gin::ModuleRegistry::From(env()->context()->v8_context())
+ ->AddBuiltinModule(
+ env()->isolate(), "natives",
+ other_env->module_system()->NewInstance());
+ other_env->ShutdownGin();
+ env()->module_system()->Require("test");
+ RunResolvedPromises();
+}
+
+TEST_F(ModuleSystemTest, TestRequireAsyncFromContextWithNoModuleSystem) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("test",
+ "requireAsync('natives').then(function(natives) {"
+ " requireNative('assert').AssertTrue("
+ " natives.requireAsync('foo') === undefined);"
+ "});");
+ scoped_ptr<ModuleSystemTestEnvironment> other_env = CreateEnvironment();
+ gin::ModuleRegistry::From(env()->context()->v8_context())
+ ->AddBuiltinModule(
+ env()->isolate(), "natives",
+ other_env->module_system()->NewInstance());
+ other_env->ShutdownModuleSystem();
+ env()->module_system()->Require("test");
+ RunResolvedPromises();
+}
+
+TEST_F(ModuleSystemTest, TestPrivatesIsPrivate) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var v = privates({});"
+ "requireNative('assert').AssertFalse(v instanceof Object);");
+ env()->module_system()->Require("test");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/mojo/keep_alive_client_unittest.cc b/chromium/extensions/renderer/mojo/keep_alive_client_unittest.cc
new file mode 100644
index 00000000000..022d2d2ee54
--- /dev/null
+++ b/chromium/extensions/renderer/mojo/keep_alive_client_unittest.cc
@@ -0,0 +1,100 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <utility>
+
+#include "base/macros.h"
+#include "extensions/common/mojo/keep_alive.mojom.h"
+#include "extensions/renderer/api_test_base.h"
+#include "grit/extensions_renderer_resources.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+
+// A test launcher for tests for the stash client defined in
+// extensions/test/data/keep_alive_client_unittest.js.
+
+namespace extensions {
+namespace {
+
+// A KeepAlive implementation that calls provided callbacks on creation and
+// destruction.
+class TestKeepAlive : public KeepAlive {
+ public:
+ TestKeepAlive(const base::Closure& on_destruction,
+ mojo::InterfaceRequest<KeepAlive> keep_alive)
+ : on_destruction_(on_destruction),
+ binding_(this, std::move(keep_alive)) {}
+
+ ~TestKeepAlive() override { on_destruction_.Run(); }
+
+ static void Create(const base::Closure& on_creation,
+ const base::Closure& on_destruction,
+ mojo::InterfaceRequest<KeepAlive> keep_alive) {
+ new TestKeepAlive(on_destruction, std::move(keep_alive));
+ on_creation.Run();
+ }
+
+ private:
+ const base::Closure on_destruction_;
+ mojo::StrongBinding<KeepAlive> binding_;
+};
+
+} // namespace
+
+class KeepAliveClientTest : public ApiTestBase {
+ public:
+ KeepAliveClientTest() {}
+
+ void SetUp() override {
+ ApiTestBase::SetUp();
+ service_provider()->AddService(
+ base::Bind(&TestKeepAlive::Create,
+ base::Bind(&KeepAliveClientTest::KeepAliveCreated,
+ base::Unretained(this)),
+ base::Bind(&KeepAliveClientTest::KeepAliveDestroyed,
+ base::Unretained(this))));
+ created_keep_alive_ = false;
+ destroyed_keep_alive_ = false;
+ }
+
+ void WaitForKeepAlive() {
+ // Wait for a keep-alive to be created and destroyed.
+ while (!created_keep_alive_ || !destroyed_keep_alive_) {
+ base::RunLoop run_loop;
+ stop_run_loop_ = run_loop.QuitClosure();
+ run_loop.Run();
+ }
+ EXPECT_TRUE(created_keep_alive_);
+ EXPECT_TRUE(destroyed_keep_alive_);
+ }
+
+ private:
+ void KeepAliveCreated() {
+ created_keep_alive_ = true;
+ if (!stop_run_loop_.is_null())
+ stop_run_loop_.Run();
+ }
+ void KeepAliveDestroyed() {
+ destroyed_keep_alive_ = true;
+ if (!stop_run_loop_.is_null())
+ stop_run_loop_.Run();
+ }
+
+ bool created_keep_alive_;
+ bool destroyed_keep_alive_;
+ base::Closure stop_run_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeepAliveClientTest);
+};
+
+TEST_F(KeepAliveClientTest, KeepAliveWithSuccessfulCall) {
+ RunTest("keep_alive_client_unittest.js", "testKeepAliveWithSuccessfulCall");
+ WaitForKeepAlive();
+}
+
+TEST_F(KeepAliveClientTest, KeepAliveWithError) {
+ RunTest("keep_alive_client_unittest.js", "testKeepAliveWithError");
+ WaitForKeepAlive();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/mojo/stash_client_unittest.cc b/chromium/extensions/renderer/mojo/stash_client_unittest.cc
new file mode 100644
index 00000000000..3f361cc2535
--- /dev/null
+++ b/chromium/extensions/renderer/mojo/stash_client_unittest.cc
@@ -0,0 +1,55 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <vector>
+
+#include "base/macros.h"
+#include "extensions/browser/mojo/stash_backend.h"
+#include "extensions/common/mojo/stash.mojom.h"
+#include "extensions/renderer/api_test_base.h"
+#include "gin/dictionary.h"
+#include "grit/extensions_renderer_resources.h"
+#include "mojo/public/cpp/bindings/lib/message_builder.h"
+
+// A test launcher for tests for the stash client defined in
+// extensions/test/data/stash_client_unittest.js.
+
+namespace extensions {
+class StashClientTest : public ApiTestBase {
+ public:
+ StashClientTest() {}
+
+ void SetUp() override {
+ ApiTestBase::SetUp();
+ stash_backend_.reset(new StashBackend(base::Closure()));
+ PrepareEnvironment(api_test_env());
+ }
+
+ void PrepareEnvironment(ApiTestEnvironment* env) {
+ env->service_provider()->AddService(base::Bind(
+ &StashBackend::BindToRequest, base::Unretained(stash_backend_.get())));
+ }
+
+ scoped_ptr<StashBackend> stash_backend_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StashClientTest);
+};
+
+// https://crbug.com/599898
+#if defined(LEAK_SANITIZER)
+#define MAYBE_StashAndRestore DISABLED_StashAndRestore
+#else
+#define MAYBE_StashAndRestore StashAndRestore
+#endif
+// Test that stashing and restoring work correctly.
+TEST_F(StashClientTest, MAYBE_StashAndRestore) {
+ ASSERT_NO_FATAL_FAILURE(RunTest("stash_client_unittest.js", "testStash"));
+ scoped_ptr<ModuleSystemTestEnvironment> restore_test_env(CreateEnvironment());
+ ApiTestEnvironment restore_environment(restore_test_env.get());
+ PrepareEnvironment(&restore_environment);
+ restore_environment.RunTest("stash_client_unittest.js", "testRetrieve");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/native_handler.cc b/chromium/extensions/renderer/native_handler.cc
new file mode 100644
index 00000000000..a8b343ad589
--- /dev/null
+++ b/chromium/extensions/renderer/native_handler.cc
@@ -0,0 +1,22 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/native_handler.h"
+
+#include "base/logging.h"
+
+namespace extensions {
+
+NativeHandler::NativeHandler() : is_valid_(true) {}
+
+NativeHandler::~NativeHandler() {
+ CHECK(!is_valid_) << "NativeHandlers must be invalidated before destruction";
+}
+
+void NativeHandler::Invalidate() {
+ CHECK(is_valid_);
+ is_valid_ = false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/native_handler.h b/chromium/extensions/renderer/native_handler.h
new file mode 100644
index 00000000000..5b3d3f73f5e
--- /dev/null
+++ b/chromium/extensions/renderer/native_handler.h
@@ -0,0 +1,49 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_NATIVE_HANDLER_H_
+
+#include "base/macros.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+// NativeHandlers are intended to be used with a ModuleSystem. The ModuleSystem
+// will assume ownership of the NativeHandler, and as a ModuleSystem is tied to
+// a single v8::Context, this implies that NativeHandlers will also be tied to
+// a single v8::Context.
+// TODO(koz): Rename this to NativeJavaScriptModule.
+class NativeHandler {
+ public:
+ NativeHandler();
+ virtual ~NativeHandler();
+
+ // Create a new instance of the object this handler specifies.
+ virtual v8::Local<v8::Object> NewInstance() = 0;
+
+ // Invalidate this object so it cannot be used any more. This is needed
+ // because it's possible for this to outlive its owner context. Invalidate
+ // must be called before this happens.
+ //
+ // Subclasses should override to invalidate their own V8 state. If they do
+ // they must call their superclass' Invalidate().
+ //
+ // Invalidate() will be called on destruction, if it hasn't already been.
+ // Subclasses don't need to do it themselves.
+ virtual void Invalidate();
+
+ protected:
+ // Allow subclasses to query valid state.
+ bool is_valid() { return is_valid_; }
+
+ private:
+ bool is_valid_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/object_backed_native_handler.cc b/chromium/extensions/renderer/object_backed_native_handler.cc
new file mode 100644
index 00000000000..1ffc355cc48
--- /dev/null
+++ b/chromium/extensions/renderer/object_backed_native_handler.cc
@@ -0,0 +1,222 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/memory/linked_ptr.h"
+#include "content/public/child/worker_thread.h"
+#include "extensions/common/extension_api.h"
+#include "extensions/renderer/console.h"
+#include "extensions/renderer/module_system.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "extensions/renderer/v8_helpers.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+namespace {
+// Key for the base::Bound routed function.
+const char* kHandlerFunction = "handler_function";
+const char* kFeatureName = "feature_name";
+} // namespace
+
+ObjectBackedNativeHandler::ObjectBackedNativeHandler(ScriptContext* context)
+ : router_data_(context->isolate()),
+ context_(context),
+ object_template_(context->isolate(),
+ v8::ObjectTemplate::New(context->isolate())) {
+}
+
+ObjectBackedNativeHandler::~ObjectBackedNativeHandler() {
+}
+
+v8::Local<v8::Object> ObjectBackedNativeHandler::NewInstance() {
+ return v8::Local<v8::ObjectTemplate>::New(GetIsolate(), object_template_)
+ ->NewInstance();
+}
+
+// static
+void ObjectBackedNativeHandler::Router(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ v8::Isolate* isolate = args.GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Object> data = args.Data().As<v8::Object>();
+ v8::Local<v8::Context> context = isolate->GetCurrentContext();
+
+ v8::Local<v8::Value> handler_function_value;
+ v8::Local<v8::Value> feature_name_value;
+ // See comment in header file for why we do this.
+ if (!GetPrivate(context, data, kHandlerFunction, &handler_function_value) ||
+ handler_function_value->IsUndefined() ||
+ !GetPrivate(context, data, kFeatureName, &feature_name_value) ||
+ !feature_name_value->IsString()) {
+ ScriptContext* script_context =
+ ScriptContextSet::GetContextByV8Context(context);
+ console::Error(script_context ? script_context->GetRenderFrame() : nullptr,
+ "Extension view no longer exists");
+ return;
+ }
+
+ // We can't access the ScriptContextSet on a worker thread. Luckily, we also
+ // don't inject many bindings into worker threads.
+ // TODO(devlin): Figure out a way around this.
+ if (content::WorkerThread::GetCurrentId() == 0) {
+ ScriptContext* script_context =
+ ScriptContextSet::GetContextByV8Context(context);
+ v8::Local<v8::String> feature_name_string =
+ feature_name_value->ToString(context).ToLocalChecked();
+ std::string feature_name = *v8::String::Utf8Value(feature_name_string);
+ // TODO(devlin): Eventually, we should fail if either script_context is null
+ // or feature_name is empty.
+ if (script_context &&
+ !feature_name.empty() &&
+ !script_context->GetAvailability(feature_name).is_available()) {
+ return;
+ }
+ }
+ // This CHECK is *important*. Otherwise, we'll go around happily executing
+ // something random. See crbug.com/548273.
+ CHECK(handler_function_value->IsExternal());
+ static_cast<HandlerFunction*>(
+ handler_function_value.As<v8::External>()->Value())->Run(args);
+
+ // Verify that the return value, if any, is accessible by the context.
+ v8::ReturnValue<v8::Value> ret = args.GetReturnValue();
+ v8::Local<v8::Value> ret_value = ret.Get();
+ if (ret_value->IsObject() && !ret_value->IsNull() &&
+ !ContextCanAccessObject(context, v8::Local<v8::Object>::Cast(ret_value),
+ true)) {
+ NOTREACHED() << "Insecure return value";
+ ret.SetUndefined();
+ }
+}
+
+void ObjectBackedNativeHandler::RouteFunction(
+ const std::string& name,
+ const HandlerFunction& handler_function) {
+ RouteFunction(name, "", handler_function);
+}
+
+void ObjectBackedNativeHandler::RouteFunction(
+ const std::string& name,
+ const std::string& feature_name,
+ const HandlerFunction& handler_function) {
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context_->v8_context());
+
+ v8::Local<v8::Object> data = v8::Object::New(isolate);
+ SetPrivate(data, kHandlerFunction,
+ v8::External::New(isolate, new HandlerFunction(handler_function)));
+ DCHECK(feature_name.empty() ||
+ ExtensionAPI::GetSharedInstance()->GetFeatureDependency(feature_name))
+ << feature_name;
+ SetPrivate(data, kFeatureName,
+ v8_helpers::ToV8StringUnsafe(isolate, feature_name));
+ v8::Local<v8::FunctionTemplate> function_template =
+ v8::FunctionTemplate::New(isolate, Router, data);
+ v8::Local<v8::ObjectTemplate>::New(isolate, object_template_)
+ ->Set(isolate, name.c_str(), function_template);
+ router_data_.Append(data);
+}
+
+v8::Isolate* ObjectBackedNativeHandler::GetIsolate() const {
+ return context_->isolate();
+}
+
+void ObjectBackedNativeHandler::Invalidate() {
+ v8::Isolate* isolate = GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context_->v8_context());
+
+ for (size_t i = 0; i < router_data_.Size(); i++) {
+ v8::Local<v8::Object> data = router_data_.Get(i);
+ v8::Local<v8::Value> handler_function_value;
+ CHECK(GetPrivate(data, kHandlerFunction, &handler_function_value));
+ delete static_cast<HandlerFunction*>(
+ handler_function_value.As<v8::External>()->Value());
+ DeletePrivate(data, kHandlerFunction);
+ }
+
+ router_data_.Clear();
+ object_template_.Reset();
+
+ NativeHandler::Invalidate();
+}
+
+// static
+bool ObjectBackedNativeHandler::ContextCanAccessObject(
+ const v8::Local<v8::Context>& context,
+ const v8::Local<v8::Object>& object,
+ bool allow_null_context) {
+ if (object->IsNull())
+ return true;
+ if (context == object->CreationContext())
+ return true;
+ ScriptContext* other_script_context =
+ ScriptContextSet::GetContextByObject(object);
+ if (!other_script_context || !other_script_context->web_frame())
+ return allow_null_context;
+
+ return blink::WebFrame::scriptCanAccess(other_script_context->web_frame());
+}
+
+void ObjectBackedNativeHandler::SetPrivate(v8::Local<v8::Object> obj,
+ const char* key,
+ v8::Local<v8::Value> value) {
+ SetPrivate(context_->v8_context(), obj, key, value);
+}
+
+// static
+void ObjectBackedNativeHandler::SetPrivate(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> obj,
+ const char* key,
+ v8::Local<v8::Value> value) {
+ obj->SetPrivate(context, v8::Private::ForApi(context->GetIsolate(),
+ v8::String::NewFromUtf8(
+ context->GetIsolate(), key)),
+ value)
+ .FromJust();
+}
+
+bool ObjectBackedNativeHandler::GetPrivate(v8::Local<v8::Object> obj,
+ const char* key,
+ v8::Local<v8::Value>* result) {
+ return GetPrivate(context_->v8_context(), obj, key, result);
+}
+
+// static
+bool ObjectBackedNativeHandler::GetPrivate(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> obj,
+ const char* key,
+ v8::Local<v8::Value>* result) {
+ return obj->GetPrivate(context,
+ v8::Private::ForApi(context->GetIsolate(),
+ v8::String::NewFromUtf8(
+ context->GetIsolate(), key)))
+ .ToLocal(result);
+}
+
+void ObjectBackedNativeHandler::DeletePrivate(v8::Local<v8::Object> obj,
+ const char* key) {
+ DeletePrivate(context_->v8_context(), obj, key);
+}
+
+// static
+void ObjectBackedNativeHandler::DeletePrivate(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> obj,
+ const char* key) {
+ obj->DeletePrivate(context,
+ v8::Private::ForApi(
+ context->GetIsolate(),
+ v8::String::NewFromUtf8(context->GetIsolate(), key)))
+ .FromJust();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/object_backed_native_handler.h b/chromium/extensions/renderer/object_backed_native_handler.h
new file mode 100644
index 00000000000..974abe18600
--- /dev/null
+++ b/chromium/extensions/renderer/object_backed_native_handler.h
@@ -0,0 +1,123 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_OBJECT_BACKED_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_OBJECT_BACKED_NATIVE_HANDLER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "extensions/renderer/native_handler.h"
+#include "v8/include/v8-util.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+class ScriptContext;
+
+// An ObjectBackedNativeHandler is a factory for JS objects with functions on
+// them that map to native C++ functions. Subclasses should call RouteFunction()
+// in their constructor to define functions on the created JS objects.
+class ObjectBackedNativeHandler : public NativeHandler {
+ public:
+ explicit ObjectBackedNativeHandler(ScriptContext* context);
+ ~ObjectBackedNativeHandler() override;
+
+ // Create an object with bindings to the native functions defined through
+ // RouteFunction().
+ v8::Local<v8::Object> NewInstance() override;
+
+ v8::Isolate* GetIsolate() const;
+
+ protected:
+ typedef base::Callback<void(const v8::FunctionCallbackInfo<v8::Value>&)>
+ HandlerFunction;
+
+ // Installs a new 'route' from |name| to |handler_function|. This means that
+ // NewInstance()s of this ObjectBackedNativeHandler will have a property
+ // |name| which will be handled by |handler_function|.
+ //
+ // Routed functions are destroyed along with the destruction of this class,
+ // and are never called back into, therefore it's safe for |handler_function|
+ // to bind to base::Unretained.
+ //
+ // |feature_name| corresponds to the api feature the native handler is used
+ // for. If the associated ScriptContext does not have access to that feature,
+ // the |handler_function| is not invoked.
+ // TODO(devlin): Deprecate the version that doesn't take a |feature_name|.
+ void RouteFunction(const std::string& name,
+ const HandlerFunction& handler_function);
+ void RouteFunction(const std::string& name,
+ const std::string& feature_name,
+ const HandlerFunction& handler_function);
+
+ ScriptContext* context() const { return context_; }
+
+ void Invalidate() override;
+
+ // Returns true if the given |context| is allowed to access the given
+ // |object|. This should be checked before returning any objects from another
+ // context.
+ // |allow_null_context| indicates that if there is no ScriptContext associated
+ // with the |object|, it should be allowed.
+ // TODO(devlin): It'd be nice to track down when when there's no ScriptContext
+ // and remove |allow_null_context|.
+ static bool ContextCanAccessObject(const v8::Local<v8::Context>& context,
+ const v8::Local<v8::Object>& object,
+ bool allow_null_context);
+
+ // The following methods are convenience wrappers for methods on v8::Object
+ // with the corresponding names.
+ void SetPrivate(v8::Local<v8::Object> obj,
+ const char* key,
+ v8::Local<v8::Value> value);
+ static void SetPrivate(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> obj,
+ const char* key,
+ v8::Local<v8::Value> value);
+ bool GetPrivate(v8::Local<v8::Object> obj,
+ const char* key,
+ v8::Local<v8::Value>* result);
+ static bool GetPrivate(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> obj,
+ const char* key,
+ v8::Local<v8::Value>* result);
+ void DeletePrivate(v8::Local<v8::Object> obj, const char* key);
+ static void DeletePrivate(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> obj,
+ const char* key);
+
+ private:
+ // Callback for RouteFunction which routes the V8 call to the correct
+ // base::Bound callback.
+ static void Router(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // When RouteFunction is called we create a v8::Object to hold the data we
+ // need when handling it in Router() - this is the base::Bound function to
+ // route to.
+ //
+ // We need a v8::Object because it's possible for v8 to outlive the
+ // base::Bound function; the lifetime of an ObjectBackedNativeHandler is the
+ // lifetime of webkit's involvement with it, not the life of the v8 context.
+ // A scenario when v8 will outlive us is if a frame holds onto the
+ // contentWindow of an iframe after it's removed.
+ //
+ // So, we use v8::Objects here to hold that data, effectively refcounting
+ // the data. When |this| is destroyed we remove the base::Bound function from
+ // the object to indicate that it shoudn't be called.
+ typedef v8::PersistentValueVector<v8::Object> RouterData;
+ RouterData router_data_;
+
+ ScriptContext* context_;
+
+ v8::Global<v8::ObjectTemplate> object_template_;
+
+ DISALLOW_COPY_AND_ASSIGN(ObjectBackedNativeHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_OBJECT_BACKED_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/print_native_handler.cc b/chromium/extensions/renderer/print_native_handler.cc
new file mode 100644
index 00000000000..b6ee63a7e59
--- /dev/null
+++ b/chromium/extensions/renderer/print_native_handler.cc
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/print_native_handler.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/strings/string_util.h"
+
+namespace extensions {
+
+PrintNativeHandler::PrintNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("Print",
+ base::Bind(&PrintNativeHandler::Print, base::Unretained(this)));
+}
+
+void PrintNativeHandler::Print(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() < 1)
+ return;
+
+ std::vector<std::string> components;
+ for (int i = 0; i < args.Length(); ++i)
+ components.push_back(*v8::String::Utf8Value(args[i]));
+
+ LOG(ERROR) << base::JoinString(components, ",");
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/print_native_handler.h b/chromium/extensions/renderer/print_native_handler.h
new file mode 100644
index 00000000000..b2e5d375d7f
--- /dev/null
+++ b/chromium/extensions/renderer/print_native_handler.h
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_PRINT_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_PRINT_NATIVE_HANDLER_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+
+class PrintNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit PrintNativeHandler(ScriptContext* context);
+
+ void Print(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_PRINT_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/process_info_native_handler.cc b/chromium/extensions/renderer/process_info_native_handler.cc
new file mode 100644
index 00000000000..545a21274dc
--- /dev/null
+++ b/chromium/extensions/renderer/process_info_native_handler.cc
@@ -0,0 +1,98 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/process_info_native_handler.h"
+
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+ProcessInfoNativeHandler::ProcessInfoNativeHandler(
+ ScriptContext* context,
+ const std::string& extension_id,
+ const std::string& context_type,
+ bool is_incognito_context,
+ bool is_component_extension,
+ int manifest_version,
+ bool send_request_disabled)
+ : ObjectBackedNativeHandler(context),
+ extension_id_(extension_id),
+ context_type_(context_type),
+ is_incognito_context_(is_incognito_context),
+ is_component_extension_(is_component_extension),
+ manifest_version_(manifest_version),
+ send_request_disabled_(send_request_disabled) {
+ RouteFunction("GetExtensionId",
+ base::Bind(&ProcessInfoNativeHandler::GetExtensionId,
+ base::Unretained(this)));
+ RouteFunction("GetContextType",
+ base::Bind(&ProcessInfoNativeHandler::GetContextType,
+ base::Unretained(this)));
+ RouteFunction("InIncognitoContext",
+ base::Bind(&ProcessInfoNativeHandler::InIncognitoContext,
+ base::Unretained(this)));
+ RouteFunction("IsComponentExtension",
+ base::Bind(&ProcessInfoNativeHandler::IsComponentExtension,
+ base::Unretained(this)));
+ RouteFunction("GetManifestVersion",
+ base::Bind(&ProcessInfoNativeHandler::GetManifestVersion,
+ base::Unretained(this)));
+ RouteFunction("IsSendRequestDisabled",
+ base::Bind(&ProcessInfoNativeHandler::IsSendRequestDisabled,
+ base::Unretained(this)));
+ RouteFunction(
+ "HasSwitch",
+ base::Bind(&ProcessInfoNativeHandler::HasSwitch, base::Unretained(this)));
+}
+
+void ProcessInfoNativeHandler::GetExtensionId(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(
+ v8::String::NewFromUtf8(args.GetIsolate(), extension_id_.c_str()));
+}
+
+void ProcessInfoNativeHandler::GetContextType(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(
+ v8::String::NewFromUtf8(args.GetIsolate(), context_type_.c_str()));
+}
+
+void ProcessInfoNativeHandler::InIncognitoContext(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(is_incognito_context_);
+}
+
+void ProcessInfoNativeHandler::IsComponentExtension(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(is_component_extension_);
+}
+
+void ProcessInfoNativeHandler::GetManifestVersion(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(static_cast<int32_t>(manifest_version_));
+}
+
+void ProcessInfoNativeHandler::IsSendRequestDisabled(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (send_request_disabled_) {
+ args.GetReturnValue().Set(v8::String::NewFromUtf8(
+ args.GetIsolate(),
+ "sendRequest and onRequest are obsolete."
+ " Please use sendMessage and onMessage instead."));
+ }
+}
+
+void ProcessInfoNativeHandler::HasSwitch(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK(args.Length() == 1 && args[0]->IsString());
+ bool has_switch = base::CommandLine::ForCurrentProcess()->HasSwitch(
+ *v8::String::Utf8Value(args[0]));
+ args.GetReturnValue().Set(v8::Boolean::New(args.GetIsolate(), has_switch));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/process_info_native_handler.h b/chromium/extensions/renderer/process_info_native_handler.h
new file mode 100644
index 00000000000..dcde4300807
--- /dev/null
+++ b/chromium/extensions/renderer/process_info_native_handler.h
@@ -0,0 +1,43 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_PROCESS_INFO_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_PROCESS_INFO_NATIVE_HANDLER_H_
+
+#include <string>
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+
+class ProcessInfoNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ ProcessInfoNativeHandler(ScriptContext* context,
+ const std::string& extension_id,
+ const std::string& context_type,
+ bool is_incognito_context,
+ bool is_component_extension,
+ int manifest_version,
+ bool send_request_disabled);
+
+ private:
+ void GetExtensionId(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void GetContextType(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void InIncognitoContext(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void IsComponentExtension(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void GetManifestVersion(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void IsSendRequestDisabled(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void HasSwitch(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ std::string extension_id_;
+ std::string context_type_;
+ bool is_incognito_context_;
+ bool is_component_extension_;
+ int manifest_version_;
+ bool send_request_disabled_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_PROCESS_INFO_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/programmatic_script_injector.cc b/chromium/extensions/renderer/programmatic_script_injector.cc
new file mode 100644
index 00000000000..eea394b0aa8
--- /dev/null
+++ b/chromium/extensions/renderer/programmatic_script_injector.cc
@@ -0,0 +1,185 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/programmatic_script_injector.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/values.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/permissions/api_permission.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/renderer/injection_host.h"
+#include "extensions/renderer/renderer_extension_registry.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebScriptSource.h"
+
+namespace extensions {
+
+ProgrammaticScriptInjector::ProgrammaticScriptInjector(
+ const ExtensionMsg_ExecuteCode_Params& params,
+ content::RenderFrame* render_frame)
+ : params_(new ExtensionMsg_ExecuteCode_Params(params)),
+ url_(
+ ScriptContext::GetDataSourceURLForFrame(render_frame->GetWebFrame())),
+ finished_(false) {
+ if (url_.SchemeIs(url::kAboutScheme)) {
+ origin_for_about_error_ =
+ render_frame->GetWebFrame()->getSecurityOrigin().toString().utf8();
+ }
+}
+
+ProgrammaticScriptInjector::~ProgrammaticScriptInjector() {
+}
+
+UserScript::InjectionType ProgrammaticScriptInjector::script_type()
+ const {
+ return UserScript::PROGRAMMATIC_SCRIPT;
+}
+
+bool ProgrammaticScriptInjector::ShouldExecuteInMainWorld() const {
+ return params_->in_main_world;
+}
+
+bool ProgrammaticScriptInjector::IsUserGesture() const {
+ return params_->user_gesture;
+}
+
+bool ProgrammaticScriptInjector::ExpectsResults() const {
+ return params_->wants_result;
+}
+
+bool ProgrammaticScriptInjector::ShouldInjectJs(
+ UserScript::RunLocation run_location) const {
+ return GetRunLocation() == run_location && params_->is_javascript;
+}
+
+bool ProgrammaticScriptInjector::ShouldInjectCss(
+ UserScript::RunLocation run_location) const {
+ return GetRunLocation() == run_location && !params_->is_javascript;
+}
+
+PermissionsData::AccessType ProgrammaticScriptInjector::CanExecuteOnFrame(
+ const InjectionHost* injection_host,
+ blink::WebLocalFrame* frame,
+ int tab_id) const {
+ GURL effective_document_url = ScriptContext::GetEffectiveDocumentURL(
+ frame, frame->document().url(), params_->match_about_blank);
+ if (params_->is_web_view) {
+ if (frame->parent()) {
+ // This is a subframe inside <webview>, so allow it.
+ return PermissionsData::ACCESS_ALLOWED;
+ }
+
+ return effective_document_url == params_->webview_src
+ ? PermissionsData::ACCESS_ALLOWED
+ : PermissionsData::ACCESS_DENIED;
+ }
+ DCHECK_EQ(injection_host->id().type(), HostID::EXTENSIONS);
+
+ return injection_host->CanExecuteOnFrame(
+ effective_document_url,
+ content::RenderFrame::FromWebFrame(frame),
+ tab_id,
+ true /* is_declarative */);
+}
+
+std::vector<blink::WebScriptSource> ProgrammaticScriptInjector::GetJsSources(
+ UserScript::RunLocation run_location) const {
+ DCHECK_EQ(GetRunLocation(), run_location);
+ DCHECK(params_->is_javascript);
+
+ return std::vector<blink::WebScriptSource>(
+ 1,
+ blink::WebScriptSource(
+ blink::WebString::fromUTF8(params_->code), params_->file_url));
+}
+
+std::vector<std::string> ProgrammaticScriptInjector::GetCssSources(
+ UserScript::RunLocation run_location) const {
+ DCHECK_EQ(GetRunLocation(), run_location);
+ DCHECK(!params_->is_javascript);
+
+ return std::vector<std::string>(1, params_->code);
+}
+
+void ProgrammaticScriptInjector::GetRunInfo(
+ ScriptsRunInfo* scripts_run_info,
+ UserScript::RunLocation run_location) const {
+}
+
+void ProgrammaticScriptInjector::OnInjectionComplete(
+ scoped_ptr<base::Value> execution_result,
+ UserScript::RunLocation run_location,
+ content::RenderFrame* render_frame) {
+ DCHECK(results_.empty());
+ if (execution_result)
+ results_.Append(std::move(execution_result));
+ Finish(std::string(), render_frame);
+}
+
+void ProgrammaticScriptInjector::OnWillNotInject(
+ InjectFailureReason reason,
+ content::RenderFrame* render_frame) {
+ std::string error;
+ switch (reason) {
+ case NOT_ALLOWED:
+ if (!CanShowUrlInError()) {
+ error = manifest_errors::kCannotAccessPage;
+ } else if (!origin_for_about_error_.empty()) {
+ error = ErrorUtils::FormatErrorMessage(
+ manifest_errors::kCannotAccessAboutUrl, url_.spec(),
+ origin_for_about_error_);
+ } else {
+ error = ErrorUtils::FormatErrorMessage(
+ manifest_errors::kCannotAccessPageWithUrl, url_.spec());
+ }
+ break;
+ case EXTENSION_REMOVED: // no special error here.
+ case WONT_INJECT:
+ break;
+ }
+ Finish(error, render_frame);
+}
+
+bool ProgrammaticScriptInjector::CanShowUrlInError() const {
+ if (params_->host_id.type() != HostID::EXTENSIONS)
+ return false;
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(params_->host_id.id());
+ if (!extension)
+ return false;
+ return extension->permissions_data()->active_permissions().HasAPIPermission(
+ APIPermission::kTab);
+}
+
+UserScript::RunLocation ProgrammaticScriptInjector::GetRunLocation() const {
+ return static_cast<UserScript::RunLocation>(params_->run_at);
+}
+
+void ProgrammaticScriptInjector::Finish(const std::string& error,
+ content::RenderFrame* render_frame) {
+ DCHECK(!finished_);
+ finished_ = true;
+
+ // It's possible that the render frame was destroyed in the course of
+ // injecting scripts. Don't respond if it was (the browser side watches for
+ // frame deletions so nothing is left hanging).
+ if (render_frame) {
+ render_frame->Send(
+ new ExtensionHostMsg_ExecuteCodeFinished(
+ render_frame->GetRoutingID(), params_->request_id,
+ error, url_, results_));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/programmatic_script_injector.h b/chromium/extensions/renderer/programmatic_script_injector.h
new file mode 100644
index 00000000000..a7af9ff3818
--- /dev/null
+++ b/chromium/extensions/renderer/programmatic_script_injector.h
@@ -0,0 +1,84 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_PROGRAMMATIC_SCRIPT_INJECTOR_H_
+#define EXTENSIONS_RENDERER_PROGRAMMATIC_SCRIPT_INJECTOR_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "extensions/renderer/script_injection.h"
+#include "url/gurl.h"
+
+struct ExtensionMsg_ExecuteCode_Params;
+
+namespace content {
+class RenderFrame;
+}
+
+namespace extensions {
+
+// A ScriptInjector to handle tabs.executeScript().
+class ProgrammaticScriptInjector : public ScriptInjector {
+ public:
+ ProgrammaticScriptInjector(const ExtensionMsg_ExecuteCode_Params& params,
+ content::RenderFrame* render_frame);
+ ~ProgrammaticScriptInjector() override;
+
+ private:
+ // ScriptInjector implementation.
+ UserScript::InjectionType script_type() const override;
+ bool ShouldExecuteInMainWorld() const override;
+ bool IsUserGesture() const override;
+ bool ExpectsResults() const override;
+ bool ShouldInjectJs(UserScript::RunLocation run_location) const override;
+ bool ShouldInjectCss(UserScript::RunLocation run_location) const override;
+ PermissionsData::AccessType CanExecuteOnFrame(
+ const InjectionHost* injection_host,
+ blink::WebLocalFrame* web_frame,
+ int tab_id) const override;
+ std::vector<blink::WebScriptSource> GetJsSources(
+ UserScript::RunLocation run_location) const override;
+ std::vector<std::string> GetCssSources(
+ UserScript::RunLocation run_location) const override;
+ void GetRunInfo(ScriptsRunInfo* scripts_run_info,
+ UserScript::RunLocation run_location) const override;
+ void OnInjectionComplete(scoped_ptr<base::Value> execution_result,
+ UserScript::RunLocation run_location,
+ content::RenderFrame* render_frame) override;
+ void OnWillNotInject(InjectFailureReason reason,
+ content::RenderFrame* render_frame) override;
+
+ // Whether it is safe to include information about the URL in error messages.
+ bool CanShowUrlInError() const;
+
+ // Return the run location for this injector.
+ UserScript::RunLocation GetRunLocation() const;
+
+ // Notify the browser that the script was injected (or never will be), and
+ // send along any results or errors.
+ void Finish(const std::string& error, content::RenderFrame* render_frame);
+
+ // The parameters for injecting the script.
+ scoped_ptr<ExtensionMsg_ExecuteCode_Params> params_;
+
+ // The url of the frame into which we are injecting.
+ GURL url_;
+
+ // The serialization of the frame's origin if the frame is an about:-URL. This
+ // is used to provide user-friendly messages.
+ std::string origin_for_about_error_;
+
+ // The results of the script execution.
+ base::ListValue results_;
+
+ // Whether or not this script injection has finished.
+ bool finished_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProgrammaticScriptInjector);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_PROGRAMMATIC_SCRIPT_INJECTOR_H_
diff --git a/chromium/extensions/renderer/render_frame_observer_natives.cc b/chromium/extensions/renderer/render_frame_observer_natives.cc
new file mode 100644
index 00000000000..7c507b37993
--- /dev/null
+++ b/chromium/extensions/renderer/render_frame_observer_natives.cc
@@ -0,0 +1,107 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/render_frame_observer_natives.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+namespace {
+
+// Deletes itself when done.
+class LoadWatcher : public content::RenderFrameObserver {
+ public:
+ LoadWatcher(content::RenderFrame* frame,
+ const base::Callback<void(bool)>& callback)
+ : content::RenderFrameObserver(frame), callback_(callback) {}
+
+ void DidCreateDocumentElement() override {
+ // Defer the callback instead of running it now to avoid re-entrancy caused
+ // by the JavaScript callback.
+ ExtensionFrameHelper::Get(render_frame())
+ ->ScheduleAtDocumentStart(base::Bind(callback_, true));
+ delete this;
+ }
+
+ void DidFailProvisionalLoad(const blink::WebURLError& error) override {
+ // Use PostTask to avoid running user scripts while handling this
+ // DidFailProvisionalLoad notification.
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(callback_, false));
+ delete this;
+ }
+
+ private:
+ base::Callback<void(bool)> callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(LoadWatcher);
+};
+
+} // namespace
+
+RenderFrameObserverNatives::RenderFrameObserverNatives(ScriptContext* context)
+ : ObjectBackedNativeHandler(context), weak_ptr_factory_(this) {
+ RouteFunction(
+ "OnDocumentElementCreated",
+ base::Bind(&RenderFrameObserverNatives::OnDocumentElementCreated,
+ base::Unretained(this)));
+}
+
+RenderFrameObserverNatives::~RenderFrameObserverNatives() {}
+
+void RenderFrameObserverNatives::Invalidate() {
+ weak_ptr_factory_.InvalidateWeakPtrs();
+ ObjectBackedNativeHandler::Invalidate();
+}
+
+void RenderFrameObserverNatives::OnDocumentElementCreated(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK(args.Length() == 2);
+ CHECK(args[0]->IsInt32());
+ CHECK(args[1]->IsFunction());
+
+ int frame_id = args[0]->Int32Value();
+
+ content::RenderFrame* frame = content::RenderFrame::FromRoutingID(frame_id);
+ if (!frame) {
+ LOG(WARNING) << "No render frame found to register LoadWatcher.";
+ return;
+ }
+
+ v8::Global<v8::Function> v8_callback(context()->isolate(),
+ args[1].As<v8::Function>());
+ base::Callback<void(bool)> callback(
+ base::Bind(&RenderFrameObserverNatives::InvokeCallback,
+ weak_ptr_factory_.GetWeakPtr(), base::Passed(&v8_callback)));
+ if (ExtensionFrameHelper::Get(frame)->did_create_current_document_element()) {
+ // If the document element is already created, then we can call the callback
+ // immediately (though use PostTask to ensure that the callback is called
+ // asynchronously).
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(callback, true));
+ } else {
+ new LoadWatcher(frame, callback);
+ }
+
+ args.GetReturnValue().Set(true);
+}
+
+void RenderFrameObserverNatives::InvokeCallback(
+ v8::Global<v8::Function> callback,
+ bool succeeded) {
+ v8::Isolate* isolate = context()->isolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Value> args[] = {v8::Boolean::New(isolate, succeeded)};
+ context()->CallFunction(v8::Local<v8::Function>::New(isolate, callback),
+ arraysize(args), args);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/render_frame_observer_natives.h b/chromium/extensions/renderer/render_frame_observer_natives.h
new file mode 100644
index 00000000000..4e81e3bc8b7
--- /dev/null
+++ b/chromium/extensions/renderer/render_frame_observer_natives.h
@@ -0,0 +1,38 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_RENDER_FRAME_OBSERVER_NATIVES_H_
+#define EXTENSIONS_RENDERER_RENDER_FRAME_OBSERVER_NATIVES_H_
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+// Native functions for JS to run callbacks upon RenderFrame events.
+class RenderFrameObserverNatives : public ObjectBackedNativeHandler {
+ public:
+ explicit RenderFrameObserverNatives(ScriptContext* context);
+ ~RenderFrameObserverNatives() override;
+
+ private:
+ void Invalidate() override;
+
+ // Runs a callback upon creation of new document element inside a render frame
+ // (document.documentElement).
+ void OnDocumentElementCreated(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ void InvokeCallback(v8::Global<v8::Function> callback, bool succeeded);
+
+ base::WeakPtrFactory<RenderFrameObserverNatives> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderFrameObserverNatives);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_RENDER_FRAME_OBSERVER_NATIVES_H_
diff --git a/chromium/extensions/renderer/renderer_extension_registry.cc b/chromium/extensions/renderer/renderer_extension_registry.cc
new file mode 100644
index 00000000000..ddc3a1b89f3
--- /dev/null
+++ b/chromium/extensions/renderer/renderer_extension_registry.cc
@@ -0,0 +1,105 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/renderer_extension_registry.h"
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "content/public/renderer/render_thread.h"
+
+namespace extensions {
+
+namespace {
+
+base::LazyInstance<RendererExtensionRegistry> g_renderer_extension_registry =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+RendererExtensionRegistry::RendererExtensionRegistry() {}
+
+RendererExtensionRegistry::~RendererExtensionRegistry() {}
+
+// static
+RendererExtensionRegistry* RendererExtensionRegistry::Get() {
+ return g_renderer_extension_registry.Pointer();
+}
+
+const ExtensionSet* RendererExtensionRegistry::GetMainThreadExtensionSet()
+ const {
+ // This can only be modified on the RenderThread, because
+ // GetMainThreadExtensionSet is inherently thread unsafe.
+ // Enforcing single-thread modification at least mitigates this.
+ // TODO(annekao): Remove this restriction once GetMainThreadExtensionSet is
+ // fixed.
+ DCHECK(content::RenderThread::Get());
+ base::AutoLock lock(lock_);
+ return &extensions_;
+}
+
+size_t RendererExtensionRegistry::size() const {
+ base::AutoLock lock(lock_);
+ return extensions_.size();
+}
+
+bool RendererExtensionRegistry::is_empty() const {
+ base::AutoLock lock(lock_);
+ return extensions_.is_empty();
+}
+
+bool RendererExtensionRegistry::Contains(
+ const std::string& extension_id) const {
+ base::AutoLock lock(lock_);
+ return extensions_.Contains(extension_id);
+}
+
+bool RendererExtensionRegistry::Insert(
+ const scoped_refptr<const Extension>& extension) {
+ DCHECK(content::RenderThread::Get());
+ base::AutoLock lock(lock_);
+ return extensions_.Insert(extension);
+}
+
+bool RendererExtensionRegistry::Remove(const std::string& id) {
+ DCHECK(content::RenderThread::Get());
+ base::AutoLock lock(lock_);
+ return extensions_.Remove(id);
+}
+
+std::string RendererExtensionRegistry::GetExtensionOrAppIDByURL(
+ const GURL& url) const {
+ base::AutoLock lock(lock_);
+ return extensions_.GetExtensionOrAppIDByURL(url);
+}
+
+const Extension* RendererExtensionRegistry::GetExtensionOrAppByURL(
+ const GURL& url) const {
+ base::AutoLock lock(lock_);
+ return extensions_.GetExtensionOrAppByURL(url);
+}
+
+const Extension* RendererExtensionRegistry::GetHostedAppByURL(
+ const GURL& url) const {
+ base::AutoLock lock(lock_);
+ return extensions_.GetHostedAppByURL(url);
+}
+
+const Extension* RendererExtensionRegistry::GetByID(
+ const std::string& id) const {
+ base::AutoLock lock(lock_);
+ return extensions_.GetByID(id);
+}
+
+ExtensionIdSet RendererExtensionRegistry::GetIDs() const {
+ base::AutoLock lock(lock_);
+ return extensions_.GetIDs();
+}
+
+bool RendererExtensionRegistry::ExtensionBindingsAllowed(
+ const GURL& url) const {
+ base::AutoLock lock(lock_);
+ return extensions_.ExtensionBindingsAllowed(url);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/renderer_extension_registry.h b/chromium/extensions/renderer/renderer_extension_registry.h
new file mode 100644
index 00000000000..97c24bbf535
--- /dev/null
+++ b/chromium/extensions/renderer/renderer_extension_registry.h
@@ -0,0 +1,62 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_RENDERER_EXTENSION_REGISTRY_H_
+#define EXTENSIONS_RENDERER_RENDERER_EXTENSION_REGISTRY_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/synchronization/lock.h"
+#include "extensions/common/extension_set.h"
+
+class GURL;
+
+namespace extensions {
+
+// Thread safe container for all loaded extensions in this process,
+// essentially the renderer counterpart to ExtensionRegistry.
+class RendererExtensionRegistry {
+ public:
+ RendererExtensionRegistry();
+ ~RendererExtensionRegistry();
+
+ static RendererExtensionRegistry* Get();
+
+ // Returns the ExtensionSet that underlies this RenderExtensionRegistry.
+ //
+ // This is not thread-safe and must only be called on the RenderThread, but
+ // even so, it's not thread safe because other threads may decide to
+ // modify this. Don't persist a reference to this.
+ //
+ // TODO(annekao): remove or make thread-safe and callback-based.
+ const ExtensionSet* GetMainThreadExtensionSet() const;
+
+ size_t size() const;
+ bool is_empty() const;
+
+ // Forwards to the ExtensionSet methods by the same name.
+ bool Contains(const std::string& id) const;
+ bool Insert(const scoped_refptr<const Extension>& extension);
+ bool Remove(const std::string& id);
+ std::string GetExtensionOrAppIDByURL(const GURL& url) const;
+ const Extension* GetExtensionOrAppByURL(const GURL& url) const;
+ const Extension* GetHostedAppByURL(const GURL& url) const;
+ const Extension* GetByID(const std::string& id) const;
+ ExtensionIdSet GetIDs() const;
+ bool ExtensionBindingsAllowed(const GURL& url) const;
+
+ private:
+ ExtensionSet extensions_;
+
+ mutable base::Lock lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(RendererExtensionRegistry);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_RENDERER_EXTENSION_REGISTRY_H_
diff --git a/chromium/extensions/renderer/request_sender.cc b/chromium/extensions/renderer/request_sender.cc
new file mode 100644
index 00000000000..6f3cf51a8f1
--- /dev/null
+++ b/chromium/extensions/renderer/request_sender.cc
@@ -0,0 +1,143 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/request_sender.h"
+
+#include "base/values.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebScopedUserGesture.h"
+#include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
+#include "third_party/WebKit/public/web/WebUserGestureToken.h"
+
+namespace extensions {
+
+// Contains info relevant to a pending API request.
+struct PendingRequest {
+ public:
+ PendingRequest(const std::string& name,
+ RequestSender::Source* source,
+ blink::WebUserGestureToken token)
+ : name(name), source(source), token(token) {}
+
+ std::string name;
+ RequestSender::Source* source;
+ blink::WebUserGestureToken token;
+};
+
+RequestSender::ScopedTabID::ScopedTabID(RequestSender* request_sender,
+ int tab_id)
+ : request_sender_(request_sender),
+ tab_id_(tab_id),
+ previous_tab_id_(request_sender->source_tab_id_) {
+ request_sender_->source_tab_id_ = tab_id;
+}
+
+RequestSender::ScopedTabID::~ScopedTabID() {
+ DCHECK_EQ(tab_id_, request_sender_->source_tab_id_);
+ request_sender_->source_tab_id_ = previous_tab_id_;
+}
+
+RequestSender::RequestSender(Dispatcher* dispatcher)
+ : dispatcher_(dispatcher), source_tab_id_(-1) {}
+
+RequestSender::~RequestSender() {}
+
+void RequestSender::InsertRequest(int request_id,
+ PendingRequest* pending_request) {
+ DCHECK_EQ(0u, pending_requests_.count(request_id));
+ pending_requests_[request_id].reset(pending_request);
+}
+
+linked_ptr<PendingRequest> RequestSender::RemoveRequest(int request_id) {
+ PendingRequestMap::iterator i = pending_requests_.find(request_id);
+ if (i == pending_requests_.end())
+ return linked_ptr<PendingRequest>();
+ linked_ptr<PendingRequest> result = i->second;
+ pending_requests_.erase(i);
+ return result;
+}
+
+int RequestSender::GetNextRequestId() const {
+ static int next_request_id = 0;
+ return next_request_id++;
+}
+
+void RequestSender::StartRequest(Source* source,
+ const std::string& name,
+ int request_id,
+ bool has_callback,
+ bool for_io_thread,
+ base::ListValue* value_args) {
+ ScriptContext* context = source->GetContext();
+ if (!context)
+ return;
+
+ // Get the current RenderFrame so that we can send a routed IPC message from
+ // the correct source.
+ content::RenderFrame* render_frame = context->GetRenderFrame();
+ if (!render_frame)
+ return;
+
+ // TODO(koz): See if we can make this a CHECK.
+ if (!context->HasAccessOrThrowError(name))
+ return;
+
+ GURL source_url;
+ if (blink::WebLocalFrame* webframe = context->web_frame())
+ source_url = webframe->document().url();
+
+ InsertRequest(request_id, new PendingRequest(name, source,
+ blink::WebUserGestureIndicator::currentUserGestureToken()));
+
+ ExtensionHostMsg_Request_Params params;
+ params.name = name;
+ params.arguments.Swap(value_args);
+ params.extension_id = context->GetExtensionID();
+ params.source_url = source_url;
+ params.source_tab_id = source_tab_id_;
+ params.request_id = request_id;
+ params.has_callback = has_callback;
+ params.user_gesture =
+ blink::WebUserGestureIndicator::isProcessingUserGesture();
+ if (for_io_thread) {
+ render_frame->Send(new ExtensionHostMsg_RequestForIOThread(
+ render_frame->GetRoutingID(), params));
+ } else {
+ render_frame->Send(
+ new ExtensionHostMsg_Request(render_frame->GetRoutingID(), params));
+ }
+}
+
+void RequestSender::HandleResponse(int request_id,
+ bool success,
+ const base::ListValue& response,
+ const std::string& error) {
+ linked_ptr<PendingRequest> request = RemoveRequest(request_id);
+
+ if (!request.get()) {
+ // This can happen if a context is destroyed while a request is in flight.
+ return;
+ }
+
+ blink::WebScopedUserGesture gesture(request->token);
+ request->source->OnResponseReceived(
+ request->name, request_id, success, response, error);
+}
+
+void RequestSender::InvalidateSource(Source* source) {
+ for (PendingRequestMap::iterator it = pending_requests_.begin();
+ it != pending_requests_.end();) {
+ if (it->second->source == source)
+ pending_requests_.erase(it++);
+ else
+ ++it;
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/request_sender.h b/chromium/extensions/renderer/request_sender.h
new file mode 100644
index 00000000000..245c0feb67e
--- /dev/null
+++ b/chromium/extensions/renderer/request_sender.h
@@ -0,0 +1,107 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_REQUEST_SENDER_H_
+#define EXTENSIONS_RENDERER_REQUEST_SENDER_H_
+
+#include <map>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "v8/include/v8.h"
+
+namespace base {
+class ListValue;
+}
+
+namespace extensions {
+class Dispatcher;
+class ScriptContext;
+
+struct PendingRequest;
+
+// Responsible for sending requests for named extension API functions to the
+// extension host and routing the responses back to the caller.
+class RequestSender {
+ public:
+ // Source represents a user of RequestSender. Every request is associated with
+ // a Source object, which will be notified when the corresponding response
+ // arrives. When a Source object is going away and there are pending requests,
+ // it should call InvalidateSource() to make sure no notifications are sent to
+ // it later.
+ class Source {
+ public:
+ virtual ~Source() {}
+
+ virtual ScriptContext* GetContext() = 0;
+ virtual void OnResponseReceived(const std::string& name,
+ int request_id,
+ bool success,
+ const base::ListValue& response,
+ const std::string& error) = 0;
+ };
+
+ // Helper class to (re)set the |source_tab_id_| below.
+ class ScopedTabID {
+ public:
+ ScopedTabID(RequestSender* request_sender, int tab_id);
+ ~ScopedTabID();
+
+ private:
+ RequestSender* const request_sender_;
+ const int tab_id_;
+ const int previous_tab_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedTabID);
+ };
+
+ explicit RequestSender(Dispatcher* dispatcher);
+ ~RequestSender();
+
+ // In order to avoid collision, all |request_id|s passed into StartRequest()
+ // should be generated by this method.
+ int GetNextRequestId() const;
+
+ // Makes a call to the API function |name| that is to be handled by the
+ // extension host. The response to this request will be received in
+ // HandleResponse().
+ // TODO(koz): Remove |request_id| and generate that internally.
+ // There are multiple of these per render view though, so we'll
+ // need to vend the IDs centrally.
+ void StartRequest(Source* source,
+ const std::string& name,
+ int request_id,
+ bool has_callback,
+ bool for_io_thread,
+ base::ListValue* value_args);
+
+ // Handles responses from the extension host to calls made by StartRequest().
+ void HandleResponse(int request_id,
+ bool success,
+ const base::ListValue& response,
+ const std::string& error);
+
+ // Notifies this that a request source is no longer valid.
+ // TODO(kalman): Do this in a generic/safe way.
+ void InvalidateSource(Source* source);
+
+ private:
+ friend class ScopedTabID;
+ typedef std::map<int, linked_ptr<PendingRequest> > PendingRequestMap;
+
+ void InsertRequest(int request_id, PendingRequest* pending_request);
+ linked_ptr<PendingRequest> RemoveRequest(int request_id);
+
+ Dispatcher* dispatcher_;
+ PendingRequestMap pending_requests_;
+
+ int source_tab_id_; // Id of the tab sending the request, or -1 if no tab.
+
+ DISALLOW_COPY_AND_ASSIGN(RequestSender);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_REQUEST_SENDER_H_
diff --git a/chromium/extensions/renderer/resource_bundle_source_map.cc b/chromium/extensions/renderer/resource_bundle_source_map.cc
new file mode 100644
index 00000000000..88fc5372185
--- /dev/null
+++ b/chromium/extensions/renderer/resource_bundle_source_map.cc
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/resource_bundle_source_map.h"
+
+#include "base/logging.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace extensions {
+
+ResourceBundleSourceMap::ResourceBundleSourceMap(
+ const ui::ResourceBundle* resource_bundle)
+ : resource_bundle_(resource_bundle) {
+}
+
+ResourceBundleSourceMap::~ResourceBundleSourceMap() {
+}
+
+void ResourceBundleSourceMap::RegisterSource(const std::string& name,
+ int resource_id) {
+ resource_id_map_[name] = resource_id;
+}
+
+v8::Local<v8::Value> ResourceBundleSourceMap::GetSource(
+ v8::Isolate* isolate,
+ const std::string& name) {
+ if (!Contains(name)) {
+ NOTREACHED() << "No module is registered with name \"" << name << "\"";
+ return v8::Undefined(isolate);
+ }
+ base::StringPiece resource =
+ resource_bundle_->GetRawDataResource(resource_id_map_[name]);
+ if (resource.empty()) {
+ NOTREACHED()
+ << "Module resource registered as \"" << name << "\" not found";
+ return v8::Undefined(isolate);
+ }
+ return ConvertString(isolate, resource);
+}
+
+bool ResourceBundleSourceMap::Contains(const std::string& name) {
+ return !!resource_id_map_.count(name);
+}
+
+v8::Local<v8::String> ResourceBundleSourceMap::ConvertString(
+ v8::Isolate* isolate,
+ const base::StringPiece& string) {
+ // v8 takes ownership of the StaticV8ExternalOneByteStringResource (see
+ // v8::String::NewExternal()).
+ return v8::String::NewExternal(
+ isolate, new StaticV8ExternalOneByteStringResource(string));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/resource_bundle_source_map.h b/chromium/extensions/renderer/resource_bundle_source_map.h
new file mode 100644
index 00000000000..13f8f216697
--- /dev/null
+++ b/chromium/extensions/renderer/resource_bundle_source_map.h
@@ -0,0 +1,45 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_RESOURCE_BUNDLE_SOURCE_MAP_H_
+#define EXTENSIONS_RENDERER_RESOURCE_BUNDLE_SOURCE_MAP_H_
+
+#include <map>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/linked_ptr.h"
+#include "base/strings/string_piece.h"
+#include "extensions/renderer/module_system.h"
+#include "extensions/renderer/static_v8_external_one_byte_string_resource.h"
+#include "v8/include/v8.h"
+
+namespace ui {
+class ResourceBundle;
+}
+
+namespace extensions {
+
+class ResourceBundleSourceMap : public extensions::ModuleSystem::SourceMap {
+ public:
+ explicit ResourceBundleSourceMap(const ui::ResourceBundle* resource_bundle);
+ ~ResourceBundleSourceMap() override;
+
+ v8::Local<v8::Value> GetSource(v8::Isolate* isolate,
+ const std::string& name) override;
+ bool Contains(const std::string& name) override;
+
+ void RegisterSource(const std::string& name, int resource_id);
+
+ private:
+ v8::Local<v8::String> ConvertString(v8::Isolate* isolate,
+ const base::StringPiece& string);
+
+ const ui::ResourceBundle* resource_bundle_;
+ std::map<std::string, int> resource_id_map_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_RESOURCE_BUNDLE_SOURCE_MAP_H_
diff --git a/chromium/extensions/renderer/resources/OWNERS b/chromium/extensions/renderer/resources/OWNERS
new file mode 100644
index 00000000000..33637287bcf
--- /dev/null
+++ b/chromium/extensions/renderer/resources/OWNERS
@@ -0,0 +1,4 @@
+per-file media_router_bindings.js=imcheng@chromium.org
+per-file media_router_bindings.js=kmarshall@chromium.org
+per-file media_router_bindings.js=mfoltz@chromium.org
+
diff --git a/chromium/extensions/renderer/resources/app_runtime_custom_bindings.js b/chromium/extensions/renderer/resources/app_runtime_custom_bindings.js
new file mode 100644
index 00000000000..3f0dbd27272
--- /dev/null
+++ b/chromium/extensions/renderer/resources/app_runtime_custom_bindings.js
@@ -0,0 +1,83 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Custom binding for the chrome.app.runtime API.
+
+var binding = require('binding').Binding.create('app.runtime');
+
+var AppViewGuestInternal =
+ require('binding').Binding.create('appViewGuestInternal').generate();
+var eventBindings = require('event_bindings');
+var fileSystemHelpers = requireNative('file_system_natives');
+var GetIsolatedFileSystem = fileSystemHelpers.GetIsolatedFileSystem;
+var entryIdManager = require('entryIdManager');
+
+eventBindings.registerArgumentMassager('app.runtime.onEmbedRequested',
+ function(args, dispatch) {
+ var appEmbeddingRequest = args[0];
+ var id = appEmbeddingRequest.guestInstanceId;
+ delete appEmbeddingRequest.guestInstanceId;
+ appEmbeddingRequest.allow = function(url) {
+ AppViewGuestInternal.attachFrame(url, id);
+ };
+
+ appEmbeddingRequest.deny = function() {
+ AppViewGuestInternal.denyRequest(id);
+ };
+
+ dispatch([appEmbeddingRequest]);
+});
+
+eventBindings.registerArgumentMassager('app.runtime.onLaunched',
+ function(args, dispatch) {
+ var launchData = args[0];
+ if (launchData.items) {
+ // An onLaunched corresponding to file_handlers in the app's manifest.
+ var items = [];
+ var numItems = launchData.items.length;
+ var itemLoaded = function(err, item) {
+ if (err) {
+ console.error('Error getting fileEntry, code: ' + err.code);
+ } else {
+ $Array.push(items, item);
+ }
+ if (--numItems === 0) {
+ var data = {
+ isKioskSession: launchData.isKioskSession,
+ isPublicSession: launchData.isPublicSession,
+ source: launchData.source
+ };
+ if (items.length !== 0) {
+ data.id = launchData.id;
+ data.items = items;
+ }
+ dispatch([data]);
+ }
+ };
+ $Array.forEach(launchData.items, function(item) {
+ var fs = GetIsolatedFileSystem(item.fileSystemId);
+ if (item.isDirectory) {
+ fs.root.getDirectory(item.baseName, {}, function(dirEntry) {
+ entryIdManager.registerEntry(item.entryId, dirEntry);
+ itemLoaded(null, {entry: dirEntry});
+ }, function(fileError) {
+ itemLoaded(fileError);
+ });
+ } else {
+ fs.root.getFile(item.baseName, {}, function(fileEntry) {
+ entryIdManager.registerEntry(item.entryId, fileEntry);
+ itemLoaded(null, {entry: fileEntry, type: item.mimeType});
+ }, function(fileError) {
+ itemLoaded(fileError);
+ });
+ }
+ });
+ } else {
+ // Default case. This currently covers an onLaunched corresponding to
+ // url_handlers in the app's manifest.
+ dispatch([launchData]);
+ }
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/app_window_custom_bindings.js b/chromium/extensions/renderer/resources/app_window_custom_bindings.js
new file mode 100644
index 00000000000..b0ae54f4a7b
--- /dev/null
+++ b/chromium/extensions/renderer/resources/app_window_custom_bindings.js
@@ -0,0 +1,410 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Custom binding for the app_window API.
+
+var appWindowNatives = requireNative('app_window_natives');
+var runtimeNatives = requireNative('runtime');
+var Binding = require('binding').Binding;
+var Event = require('event_bindings').Event;
+var forEach = require('utils').forEach;
+var renderFrameObserverNatives = requireNative('renderFrameObserverNatives');
+
+var appWindowData = null;
+var currentAppWindow = null;
+var currentWindowInternal = null;
+
+var kSetBoundsFunction = 'setBounds';
+var kSetSizeConstraintsFunction = 'setSizeConstraints';
+
+// Bounds class definition.
+var Bounds = function(boundsKey) {
+ privates(this).boundsKey_ = boundsKey;
+};
+Object.defineProperty(Bounds.prototype, 'left', {
+ get: function() {
+ return appWindowData[privates(this).boundsKey_].left;
+ },
+ set: function(left) {
+ this.setPosition(left, null);
+ },
+ enumerable: true
+});
+Object.defineProperty(Bounds.prototype, 'top', {
+ get: function() {
+ return appWindowData[privates(this).boundsKey_].top;
+ },
+ set: function(top) {
+ this.setPosition(null, top);
+ },
+ enumerable: true
+});
+Object.defineProperty(Bounds.prototype, 'width', {
+ get: function() {
+ return appWindowData[privates(this).boundsKey_].width;
+ },
+ set: function(width) {
+ this.setSize(width, null);
+ },
+ enumerable: true
+});
+Object.defineProperty(Bounds.prototype, 'height', {
+ get: function() {
+ return appWindowData[privates(this).boundsKey_].height;
+ },
+ set: function(height) {
+ this.setSize(null, height);
+ },
+ enumerable: true
+});
+Object.defineProperty(Bounds.prototype, 'minWidth', {
+ get: function() {
+ return appWindowData[privates(this).boundsKey_].minWidth;
+ },
+ set: function(minWidth) {
+ updateSizeConstraints(privates(this).boundsKey_, { minWidth: minWidth });
+ },
+ enumerable: true
+});
+Object.defineProperty(Bounds.prototype, 'maxWidth', {
+ get: function() {
+ return appWindowData[privates(this).boundsKey_].maxWidth;
+ },
+ set: function(maxWidth) {
+ updateSizeConstraints(privates(this).boundsKey_, { maxWidth: maxWidth });
+ },
+ enumerable: true
+});
+Object.defineProperty(Bounds.prototype, 'minHeight', {
+ get: function() {
+ return appWindowData[privates(this).boundsKey_].minHeight;
+ },
+ set: function(minHeight) {
+ updateSizeConstraints(privates(this).boundsKey_, { minHeight: minHeight });
+ },
+ enumerable: true
+});
+Object.defineProperty(Bounds.prototype, 'maxHeight', {
+ get: function() {
+ return appWindowData[privates(this).boundsKey_].maxHeight;
+ },
+ set: function(maxHeight) {
+ updateSizeConstraints(privates(this).boundsKey_, { maxHeight: maxHeight });
+ },
+ enumerable: true
+});
+Bounds.prototype.setPosition = function(left, top) {
+ updateBounds(privates(this).boundsKey_, { left: left, top: top });
+};
+Bounds.prototype.setSize = function(width, height) {
+ updateBounds(privates(this).boundsKey_, { width: width, height: height });
+};
+Bounds.prototype.setMinimumSize = function(minWidth, minHeight) {
+ updateSizeConstraints(privates(this).boundsKey_,
+ { minWidth: minWidth, minHeight: minHeight });
+};
+Bounds.prototype.setMaximumSize = function(maxWidth, maxHeight) {
+ updateSizeConstraints(privates(this).boundsKey_,
+ { maxWidth: maxWidth, maxHeight: maxHeight });
+};
+
+var appWindow = Binding.create('app.window');
+appWindow.registerCustomHook(function(bindingsAPI) {
+ var apiFunctions = bindingsAPI.apiFunctions;
+
+ apiFunctions.setCustomCallback('create',
+ function(name, request, callback, windowParams) {
+ var view = null;
+
+ // When window creation fails, |windowParams| will be undefined.
+ if (windowParams && windowParams.frameId) {
+ view = appWindowNatives.GetFrame(
+ windowParams.frameId, true /* notifyBrowser */);
+ }
+
+ if (!view) {
+ // No route to created window. If given a callback, trigger it with an
+ // undefined object.
+ if (callback)
+ callback();
+ return;
+ }
+
+ if (windowParams.existingWindow) {
+ // Not creating a new window, but activating an existing one, so trigger
+ // callback with existing window and don't do anything else.
+ if (callback)
+ callback(view.chrome.app.window.current());
+ return;
+ }
+
+ // Initialize appWindowData in the newly created JS context
+ if (view.chrome.app) {
+ view.chrome.app.window.initializeAppWindow(windowParams);
+ } else {
+ var sandbox_window_message = 'Creating sandboxed window, it doesn\'t ' +
+ 'have access to the chrome.app API.';
+ if (callback) {
+ sandbox_window_message = sandbox_window_message +
+ ' The chrome.app.window.create callback will be called, but ' +
+ 'there will be no object provided for the sandboxed window.';
+ }
+ console.warn(sandbox_window_message);
+ }
+
+ if (callback) {
+ if (!view || !view.chrome.app /* sandboxed window */) {
+ callback(undefined);
+ return;
+ }
+
+ var willCallback =
+ renderFrameObserverNatives.OnDocumentElementCreated(
+ windowParams.frameId,
+ function(success) {
+ if (success) {
+ callback(view.chrome.app.window.current());
+ } else {
+ callback(undefined);
+ }
+ });
+ if (!willCallback) {
+ callback(undefined);
+ }
+ }
+ });
+
+ apiFunctions.setHandleRequest('current', function() {
+ if (!currentAppWindow) {
+ console.error('The JavaScript context calling ' +
+ 'chrome.app.window.current() has no associated AppWindow.');
+ return null;
+ }
+ return currentAppWindow;
+ });
+
+ apiFunctions.setHandleRequest('getAll', function() {
+ var views = runtimeNatives.GetExtensionViews(-1, 'APP_WINDOW');
+ return $Array.map(views, function(win) {
+ return win.chrome.app.window.current();
+ });
+ });
+
+ apiFunctions.setHandleRequest('get', function(id) {
+ var windows = $Array.filter(chrome.app.window.getAll(), function(win) {
+ return win.id == id;
+ });
+ return windows.length > 0 ? windows[0] : null;
+ });
+
+ apiFunctions.setHandleRequest('canSetVisibleOnAllWorkspaces', function() {
+ return /Mac/.test(navigator.platform) || /Linux/.test(navigator.userAgent);
+ });
+
+ // This is an internal function, but needs to be bound into a closure
+ // so the correct JS context is used for global variables such as
+ // currentWindowInternal, appWindowData, etc.
+ apiFunctions.setHandleRequest('initializeAppWindow', function(params) {
+ currentWindowInternal =
+ Binding.create('app.currentWindowInternal').generate();
+ var AppWindow = function() {
+ this.innerBounds = new Bounds('innerBounds');
+ this.outerBounds = new Bounds('outerBounds');
+ };
+ forEach(currentWindowInternal, function(key, value) {
+ // Do not add internal functions that should not appear in the AppWindow
+ // interface. They are called by Bounds mutators.
+ if (key !== kSetBoundsFunction && key !== kSetSizeConstraintsFunction)
+ AppWindow.prototype[key] = value;
+ });
+ AppWindow.prototype.moveTo = $Function.bind(window.moveTo, window);
+ AppWindow.prototype.resizeTo = $Function.bind(window.resizeTo, window);
+ AppWindow.prototype.contentWindow = window;
+ AppWindow.prototype.onClosed = new Event();
+ AppWindow.prototype.onWindowFirstShownForTests = new Event();
+ AppWindow.prototype.close = function() {
+ this.contentWindow.close();
+ };
+ AppWindow.prototype.getBounds = function() {
+ // This is to maintain backcompatibility with a bug on Windows and
+ // ChromeOS, which returns the position of the window but the size of
+ // the content.
+ var innerBounds = appWindowData.innerBounds;
+ var outerBounds = appWindowData.outerBounds;
+ return { left: outerBounds.left, top: outerBounds.top,
+ width: innerBounds.width, height: innerBounds.height };
+ };
+ AppWindow.prototype.setBounds = function(bounds) {
+ updateBounds('bounds', bounds);
+ };
+ AppWindow.prototype.isFullscreen = function() {
+ return appWindowData.fullscreen;
+ };
+ AppWindow.prototype.isMinimized = function() {
+ return appWindowData.minimized;
+ };
+ AppWindow.prototype.isMaximized = function() {
+ return appWindowData.maximized;
+ };
+ AppWindow.prototype.isAlwaysOnTop = function() {
+ return appWindowData.alwaysOnTop;
+ };
+ AppWindow.prototype.alphaEnabled = function() {
+ return appWindowData.alphaEnabled;
+ };
+ AppWindow.prototype.handleWindowFirstShownForTests = function(callback) {
+ // This allows test apps to get have their callback run even if they
+ // call this after the first show has happened.
+ if (this.firstShowHasHappened) {
+ callback();
+ return;
+ }
+ this.onWindowFirstShownForTests.addListener(callback);
+ }
+
+ Object.defineProperty(AppWindow.prototype, 'id', {get: function() {
+ return appWindowData.id;
+ }});
+
+ // These properties are for testing.
+ Object.defineProperty(
+ AppWindow.prototype, 'hasFrameColor', {get: function() {
+ return appWindowData.hasFrameColor;
+ }});
+
+ Object.defineProperty(AppWindow.prototype, 'activeFrameColor',
+ {get: function() {
+ return appWindowData.activeFrameColor;
+ }});
+
+ Object.defineProperty(AppWindow.prototype, 'inactiveFrameColor',
+ {get: function() {
+ return appWindowData.inactiveFrameColor;
+ }});
+
+ appWindowData = {
+ id: params.id || '',
+ innerBounds: {
+ left: params.innerBounds.left,
+ top: params.innerBounds.top,
+ width: params.innerBounds.width,
+ height: params.innerBounds.height,
+
+ minWidth: params.innerBounds.minWidth,
+ minHeight: params.innerBounds.minHeight,
+ maxWidth: params.innerBounds.maxWidth,
+ maxHeight: params.innerBounds.maxHeight
+ },
+ outerBounds: {
+ left: params.outerBounds.left,
+ top: params.outerBounds.top,
+ width: params.outerBounds.width,
+ height: params.outerBounds.height,
+
+ minWidth: params.outerBounds.minWidth,
+ minHeight: params.outerBounds.minHeight,
+ maxWidth: params.outerBounds.maxWidth,
+ maxHeight: params.outerBounds.maxHeight
+ },
+ fullscreen: params.fullscreen,
+ minimized: params.minimized,
+ maximized: params.maximized,
+ alwaysOnTop: params.alwaysOnTop,
+ hasFrameColor: params.hasFrameColor,
+ activeFrameColor: params.activeFrameColor,
+ inactiveFrameColor: params.inactiveFrameColor,
+ alphaEnabled: params.alphaEnabled
+ };
+ currentAppWindow = new AppWindow;
+ });
+});
+
+function boundsEqual(bounds1, bounds2) {
+ if (!bounds1 || !bounds2)
+ return false;
+ return (bounds1.left == bounds2.left && bounds1.top == bounds2.top &&
+ bounds1.width == bounds2.width && bounds1.height == bounds2.height);
+}
+
+function dispatchEventIfExists(target, name) {
+ // Sometimes apps like to put their own properties on the window which
+ // break our assumptions.
+ var event = target[name];
+ if (event && (typeof event.dispatch == 'function'))
+ event.dispatch();
+ else
+ console.warn('Could not dispatch ' + name + ', event has been clobbered');
+}
+
+function updateAppWindowProperties(update) {
+ if (!appWindowData)
+ return;
+
+ var oldData = appWindowData;
+ update.id = oldData.id;
+ appWindowData = update;
+
+ var currentWindow = currentAppWindow;
+
+ if (!boundsEqual(oldData.innerBounds, update.innerBounds))
+ dispatchEventIfExists(currentWindow, "onBoundsChanged");
+
+ if (!oldData.fullscreen && update.fullscreen)
+ dispatchEventIfExists(currentWindow, "onFullscreened");
+ if (!oldData.minimized && update.minimized)
+ dispatchEventIfExists(currentWindow, "onMinimized");
+ if (!oldData.maximized && update.maximized)
+ dispatchEventIfExists(currentWindow, "onMaximized");
+
+ if ((oldData.fullscreen && !update.fullscreen) ||
+ (oldData.minimized && !update.minimized) ||
+ (oldData.maximized && !update.maximized))
+ dispatchEventIfExists(currentWindow, "onRestored");
+
+ if (oldData.alphaEnabled !== update.alphaEnabled)
+ dispatchEventIfExists(currentWindow, "onAlphaEnabledChanged");
+};
+
+function onAppWindowShownForTests() {
+ if (!currentAppWindow)
+ return;
+
+ if (!currentAppWindow.firstShowHasHappened)
+ dispatchEventIfExists(currentAppWindow, "onWindowFirstShownForTests");
+
+ currentAppWindow.firstShowHasHappened = true;
+}
+
+function onAppWindowClosed() {
+ if (!currentAppWindow)
+ return;
+ dispatchEventIfExists(currentAppWindow, "onClosed");
+}
+
+function updateBounds(boundsType, bounds) {
+ if (!currentWindowInternal)
+ return;
+
+ currentWindowInternal.setBounds(boundsType, bounds);
+}
+
+function updateSizeConstraints(boundsType, constraints) {
+ if (!currentWindowInternal)
+ return;
+
+ forEach(constraints, function(key, value) {
+ // From the perspective of the API, null is used to reset constraints.
+ // We need to convert this to 0 because a value of null is interpreted
+ // the same as undefined in the browser and leaves the constraint unchanged.
+ if (value === null)
+ constraints[key] = 0;
+ });
+
+ currentWindowInternal.setSizeConstraints(boundsType, constraints);
+}
+
+exports.$set('binding', appWindow.generate());
+exports.$set('onAppWindowClosed', onAppWindowClosed);
+exports.$set('updateAppWindowProperties', updateAppWindowProperties);
+exports.$set('appWindowShownForTests', onAppWindowShownForTests);
diff --git a/chromium/extensions/renderer/resources/async_waiter.js b/chromium/extensions/renderer/resources/async_waiter.js
new file mode 100644
index 00000000000..6470f64b4d5
--- /dev/null
+++ b/chromium/extensions/renderer/resources/async_waiter.js
@@ -0,0 +1,93 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define('async_waiter', [
+ 'mojo/public/js/support',
+], function(supportModule) {
+ /**
+ * @module async_waiter
+ */
+
+ /**
+ * @callback module:async_waiter.AsyncWaiter.Callback
+ * @param {number} result The result of waiting.
+ */
+
+ /**
+ * A waiter that waits for a handle to be ready for either reading or writing.
+ * @param {!MojoHandle} handle The handle to wait on.
+ * @param {number} signals The signals to wait for handle to be ready for.
+ * @param {module:async_waiter.AsyncWaiter.Callback} callback The callback to
+ * call when handle is ready.
+ * @constructor
+ * @alias module:async_waiter.AsyncWaiter
+ */
+ function AsyncWaiter(handle, signals, callback) {
+ /**
+ * The handle to wait on.
+ * @type {!MojoHandle}
+ * @private
+ */
+ this.handle_ = handle;
+
+ /**
+ * The signals to wait for.
+ * @type {number}
+ * @private
+ */
+ this.signals_ = signals;
+
+ /**
+ * The callback to invoke when
+ * |[handle_]{@link module:async_waiter.AsyncWaiter#handle_}| is ready.
+ * @type {module:async_waiter.AsyncWaiter.Callback}
+ * @private
+ */
+ this.callback_ = callback;
+ this.id_ = null;
+ }
+
+ /**
+ * Start waiting for the handle to be ready.
+ * @throws Will throw if this is already waiting.
+ */
+ AsyncWaiter.prototype.start = function() {
+ if (this.id_)
+ throw new Error('Already started');
+ this.id_ = supportModule.asyncWait(
+ this.handle_, this.signals_, this.onHandleReady_.bind(this));
+ };
+
+ /**
+ * Stop waiting for the handle to be ready.
+ */
+ AsyncWaiter.prototype.stop = function() {
+ if (!this.id_)
+ return;
+
+ supportModule.cancelWait(this.id_);
+ this.id_ = null;
+ };
+
+ /**
+ * Returns whether this {@link AsyncWaiter} is waiting.
+ * @return {boolean} Whether this AsyncWaiter is waiting.
+ */
+ AsyncWaiter.prototype.isWaiting = function() {
+ return !!this.id_;
+ };
+
+ /**
+ * Invoked when |[handle_]{@link module:async_waiter.AsyncWaiter#handle_}| is
+ * ready.
+ * @param {number} result The result of the wait.
+ * @private
+ */
+ AsyncWaiter.prototype.onHandleReady_ = function(result) {
+ this.id_ = null;
+ this.callback_(result);
+ };
+
+ return {AsyncWaiter: AsyncWaiter};
+});
diff --git a/chromium/extensions/renderer/resources/binding.js b/chromium/extensions/renderer/resources/binding.js
new file mode 100644
index 00000000000..c5690907f40
--- /dev/null
+++ b/chromium/extensions/renderer/resources/binding.js
@@ -0,0 +1,574 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var Event = require('event_bindings').Event;
+var forEach = require('utils').forEach;
+// Note: Beware sneaky getters/setters when using GetAvailbility(). Use safe/raw
+// variables as arguments.
+var GetAvailability = requireNative('v8_context').GetAvailability;
+var exceptionHandler = require('uncaught_exception_handler');
+var lastError = require('lastError');
+var logActivity = requireNative('activityLogger');
+var logging = requireNative('logging');
+var process = requireNative('process');
+var schemaRegistry = requireNative('schema_registry');
+var schemaUtils = require('schemaUtils');
+var utils = require('utils');
+var sendRequestHandler = require('sendRequest');
+
+var contextType = process.GetContextType();
+var extensionId = process.GetExtensionId();
+var manifestVersion = process.GetManifestVersion();
+var sendRequest = sendRequestHandler.sendRequest;
+
+// Stores the name and definition of each API function, with methods to
+// modify their behaviour (such as a custom way to handle requests to the
+// API, a custom callback, etc).
+function APIFunctions(namespace) {
+ this.apiFunctions_ = {};
+ this.unavailableApiFunctions_ = {};
+ this.namespace = namespace;
+}
+
+APIFunctions.prototype.register = function(apiName, apiFunction) {
+ this.apiFunctions_[apiName] = apiFunction;
+};
+
+// Registers a function as existing but not available, meaning that calls to
+// the set* methods that reference this function should be ignored rather
+// than throwing Errors.
+APIFunctions.prototype.registerUnavailable = function(apiName) {
+ this.unavailableApiFunctions_[apiName] = apiName;
+};
+
+APIFunctions.prototype.setHook_ =
+ function(apiName, propertyName, customizedFunction) {
+ if ($Object.hasOwnProperty(this.unavailableApiFunctions_, apiName))
+ return;
+ if (!$Object.hasOwnProperty(this.apiFunctions_, apiName))
+ throw new Error('Tried to set hook for unknown API "' + apiName + '"');
+ this.apiFunctions_[apiName][propertyName] = customizedFunction;
+};
+
+APIFunctions.prototype.setHandleRequest =
+ function(apiName, customizedFunction) {
+ var prefix = this.namespace;
+ return this.setHook_(apiName, 'handleRequest',
+ function() {
+ var ret = $Function.apply(customizedFunction, this, arguments);
+ // Logs API calls to the Activity Log if it doesn't go through an
+ // ExtensionFunction.
+ if (!sendRequestHandler.getCalledSendRequest())
+ logActivity.LogAPICall(extensionId, prefix + "." + apiName,
+ $Array.slice(arguments));
+ return ret;
+ });
+};
+
+APIFunctions.prototype.setHandleRequestWithPromise =
+ function(apiName, customizedFunction) {
+ var prefix = this.namespace;
+ return this.setHook_(apiName, 'handleRequest', function() {
+ var name = prefix + '.' + apiName;
+ logActivity.LogAPICall(extensionId, name, $Array.slice(arguments));
+ var stack = exceptionHandler.getExtensionStackTrace();
+ var callback = arguments[arguments.length - 1];
+ var args = $Array.slice(arguments, 0, arguments.length - 1);
+ var keepAlivePromise = requireAsync('keep_alive').then(function(module) {
+ return module.createKeepAlive();
+ });
+ $Function.apply(customizedFunction, this, args).then(function(result) {
+ if (callback) {
+ sendRequestHandler.safeCallbackApply(name, {'stack': stack}, callback,
+ [result]);
+ }
+ }).catch(function(error) {
+ if (callback) {
+ var message = exceptionHandler.safeErrorToString(error, true);
+ lastError.run(name, message, stack, callback);
+ }
+ }).then(function() {
+ keepAlivePromise.then(function(keepAlive) {
+ keepAlive.close();
+ });
+ });
+ });
+};
+
+APIFunctions.prototype.setUpdateArgumentsPostValidate =
+ function(apiName, customizedFunction) {
+ return this.setHook_(
+ apiName, 'updateArgumentsPostValidate', customizedFunction);
+};
+
+APIFunctions.prototype.setUpdateArgumentsPreValidate =
+ function(apiName, customizedFunction) {
+ return this.setHook_(
+ apiName, 'updateArgumentsPreValidate', customizedFunction);
+};
+
+APIFunctions.prototype.setCustomCallback =
+ function(apiName, customizedFunction) {
+ return this.setHook_(apiName, 'customCallback', customizedFunction);
+};
+
+function CustomBindingsObject() {
+}
+
+CustomBindingsObject.prototype.setSchema = function(schema) {
+ // The functions in the schema are in list form, so we move them into a
+ // dictionary for easier access.
+ var self = this;
+ self.functionSchemas = {};
+ $Array.forEach(schema.functions, function(f) {
+ self.functionSchemas[f.name] = {
+ name: f.name,
+ definition: f
+ }
+ });
+};
+
+// Get the platform from navigator.appVersion.
+function getPlatform() {
+ var platforms = [
+ [/CrOS Touch/, "chromeos touch"],
+ [/CrOS/, "chromeos"],
+ [/Linux/, "linux"],
+ [/Mac/, "mac"],
+ [/Win/, "win"],
+ ];
+
+ for (var i = 0; i < platforms.length; i++) {
+ if ($RegExp.exec(platforms[i][0], navigator.appVersion)) {
+ return platforms[i][1];
+ }
+ }
+ return "unknown";
+}
+
+function isPlatformSupported(schemaNode, platform) {
+ return !schemaNode.platforms ||
+ $Array.indexOf(schemaNode.platforms, platform) > -1;
+}
+
+function isManifestVersionSupported(schemaNode, manifestVersion) {
+ return !schemaNode.maximumManifestVersion ||
+ manifestVersion <= schemaNode.maximumManifestVersion;
+}
+
+function isSchemaNodeSupported(schemaNode, platform, manifestVersion) {
+ return isPlatformSupported(schemaNode, platform) &&
+ isManifestVersionSupported(schemaNode, manifestVersion);
+}
+
+function createCustomType(type) {
+ var jsModuleName = type.js_module;
+ logging.CHECK(jsModuleName, 'Custom type ' + type.id +
+ ' has no "js_module" property.');
+ // This list contains all types that has a js_module property. It is ugly to
+ // hard-code them here, but the number of APIs that use js_module has not
+ // changed since the introduction of js_modules in crbug.com/222156.
+ // This whitelist serves as an extra line of defence to avoid exposing
+ // arbitrary extension modules when the |type| definition is poisoned.
+ var whitelistedModules = [
+ 'ChromeDirectSetting',
+ 'ChromeSetting',
+ 'ContentSetting',
+ 'StorageArea',
+ ];
+ logging.CHECK($Array.indexOf(whitelistedModules, jsModuleName) !== -1,
+ 'Module ' + jsModuleName + ' does not define a custom type.');
+ var jsModule = require(jsModuleName);
+ logging.CHECK(jsModule, 'No module ' + jsModuleName + ' found for ' +
+ type.id + '.');
+ var customType = jsModule[jsModuleName];
+ logging.CHECK(customType, jsModuleName + ' must export itself.');
+ customType.prototype = new CustomBindingsObject();
+ customType.prototype.setSchema(type);
+ return customType;
+}
+
+var platform = getPlatform();
+
+function Binding(apiName) {
+ this.apiName_ = apiName;
+ this.apiFunctions_ = new APIFunctions(apiName);
+ this.customEvent_ = null;
+ this.customHooks_ = [];
+};
+
+Binding.create = function(apiName) {
+ return new Binding(apiName);
+};
+
+Binding.prototype = {
+ // The API through which the ${api_name}_custom_bindings.js files customize
+ // their API bindings beyond what can be generated.
+ //
+ // There are 2 types of customizations available: those which are required in
+ // order to do the schema generation (registerCustomEvent and
+ // registerCustomType), and those which can only run after the bindings have
+ // been generated (registerCustomHook).
+
+ // Registers a custom event type for the API identified by |namespace|.
+ // |event| is the event's constructor.
+ registerCustomEvent: function(event) {
+ this.customEvent_ = event;
+ },
+
+ // Registers a function |hook| to run after the schema for all APIs has been
+ // generated. The hook is passed as its first argument an "API" object to
+ // interact with, and second the current extension ID. See where
+ // |customHooks| is used.
+ registerCustomHook: function(fn) {
+ $Array.push(this.customHooks_, fn);
+ },
+
+ // TODO(kalman/cduvall): Refactor this so |runHooks_| is not needed.
+ runHooks_: function(api, schema) {
+ $Array.forEach(this.customHooks_, function(hook) {
+ if (!isSchemaNodeSupported(schema, platform, manifestVersion))
+ return;
+
+ if (!hook)
+ return;
+
+ hook({
+ apiFunctions: this.apiFunctions_,
+ schema: schema,
+ compiledApi: api
+ }, extensionId, contextType);
+ }, this);
+ },
+
+ // Generates the bindings from the schema for |this.apiName_| and integrates
+ // any custom bindings that might be present.
+ generate: function() {
+ // NB: It's important to load the schema during generation rather than
+ // setting it beforehand so that we're more confident the schema we're
+ // loading is real, and not one that was injected by a page intercepting
+ // Binding.generate.
+ // Additionally, since the schema is an object returned from a native
+ // handler, its properties don't have the custom getters/setters that a page
+ // may have put on Object.prototype, and the object is frozen by v8.
+ var schema = schemaRegistry.GetSchema(this.apiName_);
+
+ function shouldCheckUnprivileged() {
+ var shouldCheck = 'unprivileged' in schema;
+ if (shouldCheck)
+ return shouldCheck;
+
+ $Array.forEach(['functions', 'events'], function(type) {
+ if ($Object.hasOwnProperty(schema, type)) {
+ $Array.forEach(schema[type], function(node) {
+ if ('unprivileged' in node)
+ shouldCheck = true;
+ });
+ }
+ });
+ if (shouldCheck)
+ return shouldCheck;
+
+ for (var property in schema.properties) {
+ if ($Object.hasOwnProperty(schema, property) &&
+ 'unprivileged' in schema.properties[property]) {
+ shouldCheck = true;
+ break;
+ }
+ }
+ return shouldCheck;
+ }
+ var checkUnprivileged = shouldCheckUnprivileged();
+
+ // TODO(kalman/cduvall): Make GetAvailability handle this, then delete the
+ // supporting code.
+ if (!isSchemaNodeSupported(schema, platform, manifestVersion)) {
+ console.error('chrome.' + schema.namespace + ' is not supported on ' +
+ 'this platform or manifest version');
+ return undefined;
+ }
+
+ var mod = {};
+
+ var namespaces = $String.split(schema.namespace, '.');
+ for (var index = 0, name; name = namespaces[index]; index++) {
+ mod[name] = mod[name] || {};
+ mod = mod[name];
+ }
+
+ if (schema.types) {
+ $Array.forEach(schema.types, function(t) {
+ if (!isSchemaNodeSupported(t, platform, manifestVersion))
+ return;
+
+ // Add types to global schemaValidator; the types we depend on from
+ // other namespaces will be added as needed.
+ schemaUtils.schemaValidator.addTypes(t);
+
+ // Generate symbols for enums.
+ var enumValues = t['enum'];
+ if (enumValues) {
+ // Type IDs are qualified with the namespace during compilation,
+ // unfortunately, so remove it here.
+ logging.DCHECK($String.substr(t.id, 0, schema.namespace.length) ==
+ schema.namespace);
+ // Note: + 1 because it ends in a '.', e.g., 'fooApi.Type'.
+ var id = $String.substr(t.id, schema.namespace.length + 1);
+ mod[id] = {};
+ $Array.forEach(enumValues, function(enumValue) {
+ // Note: enums can be declared either as a list of strings
+ // ['foo', 'bar'] or as a list of objects
+ // [{'name': 'foo'}, {'name': 'bar'}].
+ enumValue = $Object.hasOwnProperty(enumValue, 'name') ?
+ enumValue.name : enumValue;
+ if (enumValue) { // Avoid setting any empty enums.
+ // Make all properties in ALL_CAPS_STYLE.
+ //
+ // The built-in versions of $String.replace call other built-ins,
+ // which may be clobbered. Instead, manually build the property
+ // name.
+ //
+ // If the first character is a digit (we know it must be one of
+ // a digit, a letter, or an underscore), precede it with an
+ // underscore.
+ var propertyName = ($RegExp.exec(/\d/, enumValue[0])) ? '_' : '';
+ for (var i = 0; i < enumValue.length; ++i) {
+ var next;
+ if (i > 0 && $RegExp.exec(/[a-z]/, enumValue[i-1]) &&
+ $RegExp.exec(/[A-Z]/, enumValue[i])) {
+ // Replace myEnum-Foo with my_Enum-Foo:
+ next = '_' + enumValue[i];
+ } else if ($RegExp.exec(/\W/, enumValue[i])) {
+ // Replace my_Enum-Foo with my_Enum_Foo:
+ next = '_';
+ } else {
+ next = enumValue[i];
+ }
+ propertyName += next;
+ }
+ // Uppercase (replace my_Enum_Foo with MY_ENUM_FOO):
+ propertyName = $String.toUpperCase(propertyName);
+ mod[id][propertyName] = enumValue;
+ }
+ });
+ }
+ }, this);
+ }
+
+ // TODO(cduvall): Take out when all APIs have been converted to features.
+ // Returns whether access to the content of a schema should be denied,
+ // based on the presence of "unprivileged" and whether this is an
+ // extension process (versus e.g. a content script).
+ function isSchemaAccessAllowed(itemSchema) {
+ return (contextType == 'BLESSED_EXTENSION') ||
+ schema.unprivileged ||
+ itemSchema.unprivileged;
+ };
+
+ // Setup Functions.
+ if (schema.functions) {
+ $Array.forEach(schema.functions, function(functionDef) {
+ if (functionDef.name in mod) {
+ throw new Error('Function ' + functionDef.name +
+ ' already defined in ' + schema.namespace);
+ }
+
+ if (!isSchemaNodeSupported(functionDef, platform, manifestVersion)) {
+ this.apiFunctions_.registerUnavailable(functionDef.name);
+ return;
+ }
+
+ var apiFunction = {};
+ apiFunction.definition = functionDef;
+ var apiFunctionName = schema.namespace + '.' + functionDef.name;
+ apiFunction.name = apiFunctionName;
+
+ if (!GetAvailability(apiFunctionName).is_available ||
+ (checkUnprivileged && !isSchemaAccessAllowed(functionDef))) {
+ this.apiFunctions_.registerUnavailable(functionDef.name);
+ return;
+ }
+
+ // TODO(aa): It would be best to run this in a unit test, but in order
+ // to do that we would need to better factor this code so that it
+ // doesn't depend on so much v8::Extension machinery.
+ if (logging.DCHECK_IS_ON() &&
+ schemaUtils.isFunctionSignatureAmbiguous(apiFunction.definition)) {
+ throw new Error(
+ apiFunction.name + ' has ambiguous optional arguments. ' +
+ 'To implement custom disambiguation logic, add ' +
+ '"allowAmbiguousOptionalArguments" to the function\'s schema.');
+ }
+
+ this.apiFunctions_.register(functionDef.name, apiFunction);
+
+ mod[functionDef.name] = $Function.bind(function() {
+ var args = $Array.slice(arguments);
+ if (this.updateArgumentsPreValidate)
+ args = $Function.apply(this.updateArgumentsPreValidate, this, args);
+
+ args = schemaUtils.normalizeArgumentsAndValidate(args, this);
+ if (this.updateArgumentsPostValidate) {
+ args = $Function.apply(this.updateArgumentsPostValidate,
+ this,
+ args);
+ }
+
+ sendRequestHandler.clearCalledSendRequest();
+
+ var retval;
+ if (this.handleRequest) {
+ retval = $Function.apply(this.handleRequest, this, args);
+ } else {
+ var optArgs = {
+ customCallback: this.customCallback
+ };
+ retval = sendRequest(this.name, args,
+ this.definition.parameters,
+ optArgs);
+ }
+ sendRequestHandler.clearCalledSendRequest();
+
+ // Validate return value if in sanity check mode.
+ if (logging.DCHECK_IS_ON() && this.definition.returns)
+ schemaUtils.validate([retval], [this.definition.returns]);
+ return retval;
+ }, apiFunction);
+ }, this);
+ }
+
+ // Setup Events
+ if (schema.events) {
+ $Array.forEach(schema.events, function(eventDef) {
+ if (eventDef.name in mod) {
+ throw new Error('Event ' + eventDef.name +
+ ' already defined in ' + schema.namespace);
+ }
+ if (!isSchemaNodeSupported(eventDef, platform, manifestVersion))
+ return;
+
+ var eventName = schema.namespace + "." + eventDef.name;
+ if (!GetAvailability(eventName).is_available ||
+ (checkUnprivileged && !isSchemaAccessAllowed(eventDef))) {
+ return;
+ }
+
+ var options = eventDef.options || {};
+ if (eventDef.filters && eventDef.filters.length > 0)
+ options.supportsFilters = true;
+
+ var parameters = eventDef.parameters;
+ if (this.customEvent_) {
+ mod[eventDef.name] = new this.customEvent_(
+ eventName, parameters, eventDef.extraParameters, options);
+ } else {
+ mod[eventDef.name] = new Event(eventName, parameters, options);
+ }
+ }, this);
+ }
+
+ function addProperties(m, parentDef) {
+ var properties = parentDef.properties;
+ if (!properties)
+ return;
+
+ forEach(properties, function(propertyName, propertyDef) {
+ if (propertyName in m)
+ return; // TODO(kalman): be strict like functions/events somehow.
+ if (!isSchemaNodeSupported(propertyDef, platform, manifestVersion))
+ return;
+ if (!GetAvailability(schema.namespace + "." +
+ propertyName).is_available ||
+ (checkUnprivileged && !isSchemaAccessAllowed(propertyDef))) {
+ return;
+ }
+
+ // |value| is eventually added to |m|, the exposed API. Make copies
+ // of everything from the schema. (The schema is also frozen, so as long
+ // as we don't make any modifications, shallow copies are fine.)
+ var value;
+ if ($Array.isArray(propertyDef.value))
+ value = $Array.slice(propertyDef.value);
+ else if (typeof propertyDef.value === 'object')
+ value = $Object.assign({}, propertyDef.value);
+ else
+ value = propertyDef.value;
+
+ if (value) {
+ // Values may just have raw types as defined in the JSON, such
+ // as "WINDOW_ID_NONE": { "value": -1 }. We handle this here.
+ // TODO(kalman): enforce that things with a "value" property can't
+ // define their own types.
+ var type = propertyDef.type || typeof(value);
+ if (type === 'integer' || type === 'number') {
+ value = parseInt(value);
+ } else if (type === 'boolean') {
+ value = value === 'true';
+ } else if (propertyDef['$ref']) {
+ var ref = propertyDef['$ref'];
+ var type = utils.loadTypeSchema(propertyDef['$ref'], schema);
+ logging.CHECK(type, 'Schema for $ref type ' + ref + ' not found');
+ var constructor = createCustomType(type);
+ var args = value;
+ // For an object propertyDef, |value| is an array of constructor
+ // arguments, but we want to pass the arguments directly (i.e.
+ // not as an array), so we have to fake calling |new| on the
+ // constructor.
+ value = { __proto__: constructor.prototype };
+ $Function.apply(constructor, value, args);
+ // Recursively add properties.
+ addProperties(value, propertyDef);
+ } else if (type === 'object') {
+ // Recursively add properties.
+ addProperties(value, propertyDef);
+ } else if (type !== 'string') {
+ throw new Error('NOT IMPLEMENTED (extension_api.json error): ' +
+ 'Cannot parse values for type "' + type + '"');
+ }
+ m[propertyName] = value;
+ }
+ });
+ };
+
+ addProperties(mod, schema);
+
+ // This generate() call is considered successful if any functions,
+ // properties, or events were created.
+ var success = ($Object.keys(mod).length > 0);
+
+ // Special case: webViewRequest is a vacuous API which just copies its
+ // implementation from declarativeWebRequest.
+ //
+ // TODO(kalman): This would be unnecessary if we did these checks after the
+ // hooks (i.e. this.runHooks_(mod)). The reason we don't is to be very
+ // conservative with running any JS which might actually be for an API
+ // which isn't available, but this is probably overly cautious given the
+ // C++ is only giving us APIs which are available. FIXME.
+ if (schema.namespace == 'webViewRequest') {
+ success = true;
+ }
+
+ // Special case: runtime.lastError is only occasionally set, so
+ // specifically check its availability.
+ if (schema.namespace == 'runtime' &&
+ GetAvailability('runtime.lastError').is_available) {
+ success = true;
+ }
+
+ if (!success) {
+ var availability = GetAvailability(schema.namespace);
+ // If an API was available it should have been successfully generated.
+ logging.DCHECK(!availability.is_available,
+ schema.namespace + ' was available but not generated');
+ console.error('chrome.' + schema.namespace + ' is not available: ' +
+ availability.message);
+ return;
+ }
+
+ this.runHooks_(mod, schema);
+ return mod;
+ }
+};
+
+exports.$set('Binding', Binding);
diff --git a/chromium/extensions/renderer/resources/browser_test_environment_specific_bindings.js b/chromium/extensions/renderer/resources/browser_test_environment_specific_bindings.js
new file mode 100644
index 00000000000..8574068656d
--- /dev/null
+++ b/chromium/extensions/renderer/resources/browser_test_environment_specific_bindings.js
@@ -0,0 +1,15 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+function registerHooks(api) {
+}
+
+function testDone(runNextTest) {
+ // Use setTimeout here to allow previous test contexts to be
+ // eligible for garbage collection.
+ setTimeout(runNextTest, 0);
+}
+
+exports.$set('registerHooks', registerHooks);
+exports.$set('testDone', testDone);
diff --git a/chromium/extensions/renderer/resources/context_menus_custom_bindings.js b/chromium/extensions/renderer/resources/context_menus_custom_bindings.js
new file mode 100644
index 00000000000..ec8080f5294
--- /dev/null
+++ b/chromium/extensions/renderer/resources/context_menus_custom_bindings.js
@@ -0,0 +1,26 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Custom binding for the contextMenus API.
+
+var binding = require('binding').Binding.create('contextMenus');
+var contextMenusHandlers = require('contextMenusHandlers');
+
+binding.registerCustomHook(function(bindingsAPI) {
+ var apiFunctions = bindingsAPI.apiFunctions;
+
+ var handlers = contextMenusHandlers.create(false /* isWebview */);
+
+ apiFunctions.setHandleRequest('create', handlers.requestHandlers.create);
+
+ apiFunctions.setCustomCallback('create', handlers.callbacks.create);
+
+ apiFunctions.setCustomCallback('remove', handlers.callbacks.remove);
+
+ apiFunctions.setCustomCallback('update', handlers.callbacks.update);
+
+ apiFunctions.setCustomCallback('removeAll', handlers.callbacks.removeAll);
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/context_menus_handlers.js b/chromium/extensions/renderer/resources/context_menus_handlers.js
new file mode 100644
index 00000000000..aef6889d7a3
--- /dev/null
+++ b/chromium/extensions/renderer/resources/context_menus_handlers.js
@@ -0,0 +1,141 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Implementation of custom bindings for the contextMenus API.
+// This is used to implement the contextMenus API for extensions and for the
+// <webview> tag (see chrome_web_view_experimental.js).
+
+var contextMenuNatives = requireNative('context_menus');
+var sendRequest = require('sendRequest').sendRequest;
+var Event = require('event_bindings').Event;
+var lastError = require('lastError');
+
+// Add the bindings to the contextMenus API.
+function createContextMenusHandlers(isWebview) {
+ var eventName = isWebview ? 'webViewInternal.contextMenus' : 'contextMenus';
+ // Some dummy value for chrome.contextMenus instances.
+ // Webviews use positive integers, and 0 to denote an invalid webview ID.
+ // The following constant is -1 to avoid any conflicts between webview IDs and
+ // extensions.
+ var INSTANCEID_NON_WEBVIEW = -1;
+
+ // Generates a customCallback for a given method. |handleCallback| will be
+ // invoked with |request.args| as parameters.
+ function createCustomCallback(handleCallback) {
+ return function(name, request, callback) {
+ if (lastError.hasError(chrome)) {
+ if (callback)
+ callback();
+ return;
+ }
+ var args = request.args;
+ if (!isWebview) {
+ // <webview>s have an extra item in front of the parameter list, which
+ // specifies the viewInstanceId of the webview. This is used to hide
+ // context menu events in one webview from another.
+ // The non-webview chrome.contextMenus API is not called with such an
+ // ID, so we prepend an ID to match the function signature.
+ args = $Array.concat([INSTANCEID_NON_WEBVIEW], args);
+ }
+ $Function.apply(handleCallback, null, args);
+ if (callback)
+ callback();
+ };
+ }
+
+ var contextMenus = {};
+ contextMenus.handlers = {};
+ contextMenus.event = new Event(eventName);
+
+ contextMenus.getIdFromCreateProperties = function(createProperties) {
+ if (typeof createProperties.id !== 'undefined')
+ return createProperties.id;
+ return createProperties.generatedId;
+ };
+
+ contextMenus.handlersForId = function(instanceId, id) {
+ if (!contextMenus.handlers[instanceId]) {
+ contextMenus.handlers[instanceId] = {
+ generated: {},
+ string: {}
+ };
+ }
+ if (typeof id === 'number')
+ return contextMenus.handlers[instanceId].generated;
+ return contextMenus.handlers[instanceId].string;
+ };
+
+ contextMenus.ensureListenerSetup = function() {
+ if (contextMenus.listening) {
+ return;
+ }
+ contextMenus.listening = true;
+ contextMenus.event.addListener(function(info) {
+ var instanceId = INSTANCEID_NON_WEBVIEW;
+ if (isWebview) {
+ instanceId = info.webviewInstanceId;
+ // Don't expose |webviewInstanceId| via the public API.
+ delete info.webviewInstanceId;
+ }
+
+ var id = info.menuItemId;
+ var onclick = contextMenus.handlersForId(instanceId, id)[id];
+ if (onclick) {
+ $Function.apply(onclick, null, arguments);
+ }
+ });
+ };
+
+ // To be used with apiFunctions.setHandleRequest
+ var requestHandlers = {};
+ // To be used with apiFunctions.setCustomCallback
+ var callbacks = {};
+
+ requestHandlers.create = function() {
+ var createProperties = isWebview ? arguments[1] : arguments[0];
+ createProperties.generatedId = contextMenuNatives.GetNextContextMenuId();
+ var optArgs = {
+ customCallback: this.customCallback,
+ };
+ sendRequest(this.name, arguments, this.definition.parameters, optArgs);
+ return contextMenus.getIdFromCreateProperties(createProperties);
+ };
+
+ callbacks.create =
+ createCustomCallback(function(instanceId, createProperties) {
+ var id = contextMenus.getIdFromCreateProperties(createProperties);
+ var onclick = createProperties.onclick;
+ if (onclick) {
+ contextMenus.ensureListenerSetup();
+ contextMenus.handlersForId(instanceId, id)[id] = onclick;
+ }
+ });
+
+ callbacks.remove = createCustomCallback(function(instanceId, id) {
+ delete contextMenus.handlersForId(instanceId, id)[id];
+ });
+
+ callbacks.update =
+ createCustomCallback(function(instanceId, id, updateProperties) {
+ var onclick = updateProperties.onclick;
+ if (onclick) {
+ contextMenus.ensureListenerSetup();
+ contextMenus.handlersForId(instanceId, id)[id] = onclick;
+ } else if (onclick === null) {
+ // When onclick is explicitly set to null, remove the event listener.
+ delete contextMenus.handlersForId(instanceId, id)[id];
+ }
+ });
+
+ callbacks.removeAll = createCustomCallback(function(instanceId) {
+ delete contextMenus.handlers[instanceId];
+ });
+
+ return {
+ requestHandlers: requestHandlers,
+ callbacks: callbacks
+ };
+}
+
+exports.$set('create', createContextMenusHandlers);
diff --git a/chromium/extensions/renderer/resources/data_receiver.js b/chromium/extensions/renderer/resources/data_receiver.js
new file mode 100644
index 00000000000..a248ffbf4be
--- /dev/null
+++ b/chromium/extensions/renderer/resources/data_receiver.js
@@ -0,0 +1,336 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define('data_receiver', [
+ 'device/serial/data_stream.mojom',
+ 'device/serial/data_stream_serialization.mojom',
+ 'mojo/public/js/core',
+ 'mojo/public/js/router',
+], function(dataStream, serialization, core, router) {
+ /**
+ * @module data_receiver
+ */
+
+ /**
+ * A pending receive operation.
+ * @constructor
+ * @alias module:data_receiver~PendingReceive
+ * @private
+ */
+ function PendingReceive() {
+ /**
+ * The promise that will be resolved or rejected when this receive completes
+ * or fails, respectively.
+ * @type {!Promise<ArrayBuffer>}
+ * @private
+ */
+ this.promise_ = new Promise(function(resolve, reject) {
+ /**
+ * The callback to call with the data received on success.
+ * @type {Function}
+ * @private
+ */
+ this.dataCallback_ = resolve;
+ /**
+ * The callback to call with the error on failure.
+ * @type {Function}
+ * @private
+ */
+ this.errorCallback_ = reject;
+ }.bind(this));
+ }
+
+ /**
+ * Returns the promise that will be resolved when this operation completes or
+ * rejected if an error occurs.
+ * @return {Promise<ArrayBuffer>} A promise to the data received.
+ */
+ PendingReceive.prototype.getPromise = function() {
+ return this.promise_;
+ };
+
+ /**
+ * Dispatches received data to the promise returned by
+ * [getPromise]{@link module:data_receiver.PendingReceive#getPromise}.
+ * @param {!ArrayBuffer} data The data to dispatch.
+ */
+ PendingReceive.prototype.dispatchData = function(data) {
+ this.dataCallback_(data);
+ };
+
+ /**
+ * Dispatches an error if the offset of the error has been reached.
+ * @param {!PendingReceiveError} error The error to dispatch.
+ * @param {number} bytesReceived The number of bytes that have been received.
+ */
+ PendingReceive.prototype.dispatchError = function(error) {
+ if (error.queuePosition > 0)
+ return false;
+
+ var e = new Error();
+ e.error = error.error;
+ this.errorCallback_(e);
+ return true;
+ };
+
+ /**
+ * Unconditionally dispatches an error.
+ * @param {number} error The error to dispatch.
+ */
+ PendingReceive.prototype.dispatchFatalError = function(error) {
+ var e = new Error();
+ e.error = error;
+ this.errorCallback_(e);
+ };
+
+ /**
+ * A DataReceiver that receives data from a DataSource.
+ * @param {!MojoHandle} source The handle to the DataSource.
+ * @param {!MojoHandle} client The handle to the DataSourceClient.
+ * @param {number} bufferSize How large a buffer to use.
+ * @param {number} fatalErrorValue The receive error value to report in the
+ * event of a fatal error.
+ * @constructor
+ * @alias module:data_receiver.DataReceiver
+ */
+ function DataReceiver(source, client, bufferSize, fatalErrorValue) {
+ this.init_(source, client, fatalErrorValue, 0, null, [], false);
+ this.source_.init(bufferSize);
+ }
+
+ DataReceiver.prototype =
+ $Object.create(dataStream.DataSourceClient.stubClass.prototype);
+
+ /**
+ * Closes this DataReceiver.
+ */
+ DataReceiver.prototype.close = function() {
+ if (this.shutDown_)
+ return;
+ this.shutDown_ = true;
+ this.router_.close();
+ this.clientRouter_.close();
+ if (this.receive_) {
+ this.receive_.dispatchFatalError(this.fatalErrorValue_);
+ this.receive_ = null;
+ }
+ };
+
+ /**
+ * Initialize this DataReceiver.
+ * @param {!MojoHandle} source A handle to the DataSource.
+ * @param {!MojoHandle} client A handle to the DataSourceClient.
+ * @param {number} fatalErrorValue The error to dispatch in the event of a
+ * fatal error.
+ * @param {number} bytesReceived The number of bytes already received.
+ * @param {PendingReceiveError} pendingError The pending error if there is
+ * one.
+ * @param {!Array<!ArrayBuffer>} pendingData Data received from the
+ * DataSource not yet requested by the client.
+ * @param {boolean} paused Whether the DataSource is paused.
+ * @private
+ */
+ DataReceiver.prototype.init_ = function(source, client, fatalErrorValue,
+ bytesReceived, pendingError,
+ pendingData, paused) {
+ /**
+ * The [Router]{@link module:mojo/public/js/router.Router} for the
+ * connection to the DataSource.
+ * @private
+ */
+ this.router_ = new router.Router(source);
+ /**
+ * The [Router]{@link module:mojo/public/js/router.Router} for the
+ * connection to the DataSource.
+ * @private
+ */
+ this.clientRouter_ = new router.Router(client);
+ /**
+ * The connection to the DataSource.
+ * @private
+ */
+ this.source_ = new dataStream.DataSource.proxyClass(this.router_);
+ this.client_ = new dataStream.DataSourceClient.stubClass(this);
+ this.clientRouter_.setIncomingReceiver(this.client_);
+ /**
+ * The current receive operation.
+ * @type {module:data_receiver~PendingReceive}
+ * @private
+ */
+ this.receive_ = null;
+ /**
+ * The error to be dispatched in the event of a fatal error.
+ * @const {number}
+ * @private
+ */
+ this.fatalErrorValue_ = fatalErrorValue;
+ /**
+ * The pending error if there is one.
+ * @type {PendingReceiveError}
+ * @private
+ */
+ this.pendingError_ = pendingError;
+ /**
+ * Whether the DataSource is paused.
+ * @type {boolean}
+ * @private
+ */
+ this.paused_ = paused;
+ /**
+ * A queue of data that has been received from the DataSource, but not
+ * consumed by the client.
+ * @type {module:data_receiver~PendingData[]}
+ * @private
+ */
+ this.pendingDataBuffers_ = pendingData;
+ /**
+ * Whether this DataReceiver has shut down.
+ * @type {boolean}
+ * @private
+ */
+ this.shutDown_ = false;
+ };
+
+ /**
+ * Serializes this DataReceiver.
+ * This will cancel a receive if one is in progress.
+ * @return {!Promise<SerializedDataReceiver>} A promise that will resolve to
+ * the serialization of this DataReceiver. If this DataReceiver has shut
+ * down, the promise will resolve to null.
+ */
+ DataReceiver.prototype.serialize = function() {
+ if (this.shutDown_)
+ return Promise.resolve(null);
+
+ if (this.receive_) {
+ this.receive_.dispatchFatalError(this.fatalErrorValue_);
+ this.receive_ = null;
+ }
+ var serialized = new serialization.SerializedDataReceiver();
+ serialized.source = this.router_.connector_.handle_;
+ serialized.client = this.clientRouter_.connector_.handle_;
+ serialized.fatal_error_value = this.fatalErrorValue_;
+ serialized.paused = this.paused_;
+ serialized.pending_error = this.pendingError_;
+ serialized.pending_data = [];
+ $Array.forEach(this.pendingDataBuffers_, function(buffer) {
+ serialized.pending_data.push(new Uint8Array(buffer));
+ });
+ this.router_.connector_.handle_ = null;
+ this.router_.close();
+ this.clientRouter_.connector_.handle_ = null;
+ this.clientRouter_.close();
+ this.shutDown_ = true;
+ return Promise.resolve(serialized);
+ };
+
+ /**
+ * Deserializes a SerializedDataReceiver.
+ * @param {SerializedDataReceiver} serialized The serialized DataReceiver.
+ * @return {!DataReceiver} The deserialized DataReceiver.
+ */
+ DataReceiver.deserialize = function(serialized) {
+ var receiver = $Object.create(DataReceiver.prototype);
+ receiver.deserialize_(serialized);
+ return receiver;
+ };
+
+ /**
+ * Deserializes a SerializedDataReceiver into this DataReceiver.
+ * @param {SerializedDataReceiver} serialized The serialized DataReceiver.
+ * @private
+ */
+ DataReceiver.prototype.deserialize_ = function(serialized) {
+ if (!serialized) {
+ this.shutDown_ = true;
+ return;
+ }
+ var pendingData = [];
+ $Array.forEach(serialized.pending_data, function(data) {
+ var buffer = new Uint8Array(data.length);
+ buffer.set(data);
+ pendingData.push(buffer.buffer);
+ });
+ this.init_(serialized.source, serialized.client,
+ serialized.fatal_error_value, serialized.bytes_received,
+ serialized.pending_error, pendingData, serialized.paused);
+ };
+
+ /**
+ * Receive data from the DataSource.
+ * @return {Promise<ArrayBuffer>} A promise to the received data. If an error
+ * occurs, the promise will reject with an Error object with a property
+ * error containing the error code.
+ * @throws Will throw if this has encountered a fatal error or another receive
+ * is in progress.
+ */
+ DataReceiver.prototype.receive = function() {
+ if (this.shutDown_)
+ throw new Error('DataReceiver has been closed');
+ if (this.receive_)
+ throw new Error('Receive already in progress.');
+ var receive = new PendingReceive();
+ var promise = receive.getPromise();
+ if (this.pendingError_ &&
+ receive.dispatchError(this.pendingError_)) {
+ this.pendingError_ = null;
+ this.paused_ = true;
+ return promise;
+ }
+ if (this.paused_) {
+ this.source_.resume();
+ this.paused_ = false;
+ }
+ this.receive_ = receive;
+ this.dispatchData_();
+ return promise;
+ };
+
+ DataReceiver.prototype.dispatchData_ = function() {
+ if (!this.receive_) {
+ this.close();
+ return;
+ }
+ if (this.pendingDataBuffers_.length) {
+ this.receive_.dispatchData(this.pendingDataBuffers_[0]);
+ this.source_.reportBytesReceived(this.pendingDataBuffers_[0].byteLength);
+ this.receive_ = null;
+ this.pendingDataBuffers_.shift();
+ if (this.pendingError_)
+ this.pendingError_.queuePosition--;
+ }
+ };
+
+ /**
+ * Invoked by the DataSource when an error is encountered.
+ * @param {number} offset The location at which the error occurred.
+ * @param {number} error The error that occurred.
+ * @private
+ */
+ DataReceiver.prototype.onError = function(error) {
+ if (this.shutDown_)
+ return;
+
+ var pendingError = new serialization.PendingReceiveError();
+ pendingError.error = error;
+ pendingError.queuePosition = this.pendingDataBuffers_.length;
+ if (this.receive_ && this.receive_.dispatchError(pendingError)) {
+ this.receive_ = null;
+ this.paused_ = true;
+ return;
+ }
+ this.pendingError_ = pendingError;
+ };
+
+ DataReceiver.prototype.onData = function(data) {
+ var buffer = new ArrayBuffer(data.length);
+ var uintView = new Uint8Array(buffer);
+ uintView.set(data);
+ this.pendingDataBuffers_.push(buffer);
+ if (this.receive_)
+ this.dispatchData_();
+ };
+
+ return {DataReceiver: DataReceiver};
+});
diff --git a/chromium/extensions/renderer/resources/data_sender.js b/chromium/extensions/renderer/resources/data_sender.js
new file mode 100644
index 00000000000..5fa8708ba33
--- /dev/null
+++ b/chromium/extensions/renderer/resources/data_sender.js
@@ -0,0 +1,335 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define('data_sender', [
+ 'device/serial/data_stream.mojom',
+ 'device/serial/data_stream_serialization.mojom',
+ 'mojo/public/js/core',
+ 'mojo/public/js/router',
+], function(dataStreamMojom, serialization, core, routerModule) {
+ /**
+ * @module data_sender
+ */
+
+ /**
+ * A pending send operation.
+ * @param {!ArrayBuffer} data The data to be sent.
+ * @constructor
+ * @alias module:data_sender~PendingSend
+ * @private
+ */
+ function PendingSend(data) {
+ /**
+ * The data to be sent.
+ * @type {ArrayBuffer}
+ * @private
+ */
+ this.data_ = data;
+ /**
+ * The total length of data to be sent.
+ * @type {number}
+ * @private
+ */
+ this.length_ = data.byteLength;
+ /**
+ * The promise that will be resolved or rejected when this send completes
+ * or fails, respectively.
+ * @type {!Promise<number>}
+ * @private
+ */
+ this.promise_ = new Promise(function(resolve, reject) {
+ /**
+ * The callback to call on success.
+ * @type {Function}
+ * @private
+ */
+ this.successCallback_ = resolve;
+ /**
+ * The callback to call with the error on failure.
+ * @type {Function}
+ * @private
+ */
+ this.errorCallback_ = reject;
+ }.bind(this));
+ }
+
+ /**
+ * Returns the promise that will be resolved when this operation completes or
+ * rejected if an error occurs.
+ * @return {!Promise<number>} A promise to the number of bytes sent.
+ */
+ PendingSend.prototype.getPromise = function() {
+ return this.promise_;
+ };
+
+ /**
+ * Invoked when the DataSink reports that bytes have been sent. Resolves the
+ * promise returned by
+ * [getPromise()]{@link module:data_sender~PendingSend#getPromise} once all
+ * bytes have been reported as sent.
+ */
+ PendingSend.prototype.reportBytesSent = function() {
+ this.successCallback_(this.length_);
+ };
+
+ /**
+ * Invoked when the DataSink reports an error. Rejects the promise returned by
+ * [getPromise()]{@link module:data_sender~PendingSend#getPromise} unless the
+ * error occurred after this send, that is, unless numBytes is greater than
+ * the nubmer of outstanding bytes.
+ * @param {number} numBytes The number of bytes sent.
+ * @param {number} error The error reported by the DataSink.
+ */
+ PendingSend.prototype.reportBytesSentAndError = function(numBytes, error) {
+ var e = new Error();
+ e.error = error;
+ e.bytesSent = numBytes;
+ this.errorCallback_(e);
+ };
+
+ /**
+ * Writes pending data into the data pipe.
+ * @param {!DataSink} sink The DataSink to receive the data.
+ * @return {!Object} result The send result.
+ * @return {boolean} result.completed Whether all of the pending data was
+ * sent.
+ */
+ PendingSend.prototype.sendData = function(sink) {
+ var dataSent = sink.onData(new Uint8Array(this.data_));
+ this.data_ = null;
+ return dataSent;
+ };
+
+ /**
+ * A DataSender that sends data to a DataSink.
+ * @param {!MojoHandle} sink The handle to the DataSink.
+ * @param {number} bufferSize How large a buffer to use for data.
+ * @param {number} fatalErrorValue The send error value to report in the
+ * event of a fatal error.
+ * @constructor
+ * @alias module:data_sender.DataSender
+ */
+ function DataSender(sink, bufferSize, fatalErrorValue) {
+ this.init_(sink, fatalErrorValue);
+ }
+
+ /**
+ * Closes this DataSender.
+ */
+ DataSender.prototype.close = function() {
+ if (this.shutDown_)
+ return;
+ this.shutDown_ = true;
+ this.router_.close();
+ while (this.sendsAwaitingAck_.length) {
+ this.sendsAwaitingAck_.pop().reportBytesSentAndError(
+ 0, this.fatalErrorValue_);
+ }
+ this.callCancelCallback_();
+ };
+
+ /**
+ * Initialize this DataSender.
+ * @param {!MojoHandle} sink A handle to the DataSink.
+ * @param {number} fatalErrorValue The error to dispatch in the event of a
+ * fatal error.
+ * @private
+ */
+ DataSender.prototype.init_ = function(sink, fatalErrorValue) {
+ /**
+ * The error to be dispatched in the event of a fatal error.
+ * @const {number}
+ * @private
+ */
+ this.fatalErrorValue_ = fatalErrorValue;
+ /**
+ * Whether this DataSender has shut down.
+ * @type {boolean}
+ * @private
+ */
+ this.shutDown_ = false;
+ /**
+ * The [Router]{@link module:mojo/public/js/router.Router} for the
+ * connection to the DataSink.
+ * @private
+ */
+ this.router_ = new routerModule.Router(sink);
+ /**
+ * The connection to the DataSink.
+ * @private
+ */
+ this.sink_ = new dataStreamMojom.DataSink.proxyClass(this.router_);
+ /**
+ * A queue of sends that have sent their data to the DataSink, but have not
+ * been received by the DataSink.
+ * @type {!module:data_sender~PendingSend[]}
+ * @private
+ */
+ this.sendsAwaitingAck_ = [];
+
+ /**
+ * The callback that will resolve a pending cancel if one is in progress.
+ * @type {?Function}
+ * @private
+ */
+ this.pendingCancel_ = null;
+
+ /**
+ * The promise that will be resolved when a pending cancel completes if one
+ * is in progress.
+ * @type {Promise}
+ * @private
+ */
+ this.cancelPromise_ = null;
+ };
+
+ /**
+ * Serializes this DataSender.
+ * This will cancel any sends in progress before the returned promise
+ * resolves.
+ * @return {!Promise<SerializedDataSender>} A promise that will resolve to
+ * the serialization of this DataSender. If this DataSender has shut down,
+ * the promise will resolve to null.
+ */
+ DataSender.prototype.serialize = function() {
+ if (this.shutDown_)
+ return Promise.resolve(null);
+
+ var readyToSerialize = Promise.resolve();
+ if (this.sendsAwaitingAck_.length) {
+ if (this.pendingCancel_)
+ readyToSerialize = this.cancelPromise_;
+ else
+ readyToSerialize = this.cancel(this.fatalErrorValue_);
+ }
+ return readyToSerialize.then(function() {
+ var serialized = new serialization.SerializedDataSender();
+ serialized.sink = this.router_.connector_.handle_;
+ serialized.fatal_error_value = this.fatalErrorValue_;
+ this.router_.connector_.handle_ = null;
+ this.router_.close();
+ this.shutDown_ = true;
+ return serialized;
+ }.bind(this));
+ };
+
+ /**
+ * Deserializes a SerializedDataSender.
+ * @param {SerializedDataSender} serialized The serialized DataSender.
+ * @return {!DataSender} The deserialized DataSender.
+ */
+ DataSender.deserialize = function(serialized) {
+ var sender = $Object.create(DataSender.prototype);
+ sender.deserialize_(serialized);
+ return sender;
+ };
+
+ /**
+ * Deserializes a SerializedDataSender into this DataSender.
+ * @param {SerializedDataSender} serialized The serialized DataSender.
+ * @private
+ */
+ DataSender.prototype.deserialize_ = function(serialized) {
+ if (!serialized) {
+ this.shutDown_ = true;
+ return;
+ }
+ this.init_(serialized.sink, serialized.fatal_error_value,
+ serialized.buffer_size);
+ };
+
+ /**
+ * Sends data to the DataSink.
+ * @return {!Promise<number>} A promise to the number of bytes sent. If an
+ * error occurs, the promise will reject with an Error object with a
+ * property error containing the error code.
+ * @throws Will throw if this has encountered a fatal error or a cancel is in
+ * progress.
+ */
+ DataSender.prototype.send = function(data) {
+ if (this.shutDown_)
+ throw new Error('DataSender has been closed');
+ if (this.pendingCancel_)
+ throw new Error('Cancel in progress');
+ var send = new PendingSend(data);
+ this.sendsAwaitingAck_.push(send);
+ send.sendData(this.sink_).then(this.reportBytesSentAndError.bind(this));
+ return send.getPromise();
+ };
+
+ /**
+ * Requests the cancellation of any in-progress sends. Calls to
+ * [send()]{@link module:data_sender.DataSender#send} will fail until the
+ * cancel has completed.
+ * @param {number} error The error to report for cancelled sends.
+ * @return {!Promise} A promise that will resolve when the cancel completes.
+ * @throws Will throw if this has encountered a fatal error or another cancel
+ * is in progress.
+ */
+ DataSender.prototype.cancel = function(error) {
+ if (this.shutDown_)
+ throw new Error('DataSender has been closed');
+ if (this.pendingCancel_)
+ throw new Error('Cancel already in progress');
+ if (this.sendsAwaitingAck_.length == 0)
+ return Promise.resolve();
+
+ this.sink_.cancel(error);
+ this.cancelPromise_ = new Promise(function(resolve) {
+ this.pendingCancel_ = resolve;
+ }.bind(this));
+ return this.cancelPromise_;
+ };
+
+ /**
+ * Calls and clears the pending cancel callback if one is pending.
+ * @private
+ */
+ DataSender.prototype.callCancelCallback_ = function() {
+ if (this.pendingCancel_) {
+ this.cancelPromise_ = null;
+ this.pendingCancel_();
+ this.pendingCancel_ = null;
+ }
+ };
+
+ /**
+ * Invoked by the DataSink to report that data has been successfully sent.
+ * @private
+ */
+ DataSender.prototype.reportBytesSent = function() {
+ var result = this.sendsAwaitingAck_[0].reportBytesSent();
+ this.sendsAwaitingAck_.shift();
+
+ // A cancel is completed when all of the sends that were in progress have
+ // completed or failed. This is the case where all sends complete
+ // successfully.
+ if (this.sendsAwaitingAck_.length == 0)
+ this.callCancelCallback_();
+ };
+
+ /**
+ * Invoked by the DataSink to report an error in sending data.
+ * @param {number} numBytes The number of bytes sent.
+ * @param {number} error The error reported by the DataSink.
+ * @private
+ */
+ DataSender.prototype.reportBytesSentAndError = function(result) {
+ var numBytes = result.bytes_sent;
+ var error = result.error;
+ if (!error) {
+ this.reportBytesSent();
+ return;
+ }
+ var result =
+ this.sendsAwaitingAck_[0].reportBytesSentAndError(numBytes, error);
+ this.sendsAwaitingAck_.shift();
+ if (this.sendsAwaitingAck_.length)
+ return;
+ this.callCancelCallback_();
+ this.sink_.clearError();
+ };
+
+ return {DataSender: DataSender};
+});
diff --git a/chromium/extensions/renderer/resources/declarative_webrequest_custom_bindings.js b/chromium/extensions/renderer/resources/declarative_webrequest_custom_bindings.js
new file mode 100644
index 00000000000..e9da12b7077
--- /dev/null
+++ b/chromium/extensions/renderer/resources/declarative_webrequest_custom_bindings.js
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Custom binding for the declarativeWebRequest API.
+
+var binding = require('binding').Binding.create('declarativeWebRequest');
+
+var utils = require('utils');
+var validate = require('schemaUtils').validate;
+
+binding.registerCustomHook(function(api) {
+ var declarativeWebRequest = api.compiledApi;
+
+ // Returns the schema definition of type |typeId| defined in |namespace|.
+ function getSchema(typeId) {
+ return utils.lookup(api.schema.types,
+ 'id',
+ 'declarativeWebRequest.' + typeId);
+ }
+
+ // Helper function for the constructor of concrete datatypes of the
+ // declarative webRequest API.
+ // Makes sure that |this| contains the union of parameters and
+ // {'instanceType': 'declarativeWebRequest.' + typeId} and validates the
+ // generated union dictionary against the schema for |typeId|.
+ function setupInstance(instance, parameters, typeId) {
+ for (var key in parameters) {
+ if ($Object.hasOwnProperty(parameters, key)) {
+ instance[key] = parameters[key];
+ }
+ }
+ instance.instanceType = 'declarativeWebRequest.' + typeId;
+ var schema = getSchema(typeId);
+ validate([instance], [schema]);
+ }
+
+ // Setup all data types for the declarative webRequest API.
+ declarativeWebRequest.RequestMatcher = function(parameters) {
+ setupInstance(this, parameters, 'RequestMatcher');
+ };
+ declarativeWebRequest.CancelRequest = function(parameters) {
+ setupInstance(this, parameters, 'CancelRequest');
+ };
+ declarativeWebRequest.RedirectRequest = function(parameters) {
+ setupInstance(this, parameters, 'RedirectRequest');
+ };
+ declarativeWebRequest.SetRequestHeader = function(parameters) {
+ setupInstance(this, parameters, 'SetRequestHeader');
+ };
+ declarativeWebRequest.RemoveRequestHeader = function(parameters) {
+ setupInstance(this, parameters, 'RemoveRequestHeader');
+ };
+ declarativeWebRequest.AddResponseHeader = function(parameters) {
+ setupInstance(this, parameters, 'AddResponseHeader');
+ };
+ declarativeWebRequest.RemoveResponseHeader = function(parameters) {
+ setupInstance(this, parameters, 'RemoveResponseHeader');
+ };
+ declarativeWebRequest.RedirectToTransparentImage =
+ function(parameters) {
+ setupInstance(this, parameters, 'RedirectToTransparentImage');
+ };
+ declarativeWebRequest.RedirectToEmptyDocument = function(parameters) {
+ setupInstance(this, parameters, 'RedirectToEmptyDocument');
+ };
+ declarativeWebRequest.RedirectByRegEx = function(parameters) {
+ setupInstance(this, parameters, 'RedirectByRegEx');
+ };
+ declarativeWebRequest.IgnoreRules = function(parameters) {
+ setupInstance(this, parameters, 'IgnoreRules');
+ };
+ declarativeWebRequest.AddRequestCookie = function(parameters) {
+ setupInstance(this, parameters, 'AddRequestCookie');
+ };
+ declarativeWebRequest.AddResponseCookie = function(parameters) {
+ setupInstance(this, parameters, 'AddResponseCookie');
+ };
+ declarativeWebRequest.EditRequestCookie = function(parameters) {
+ setupInstance(this, parameters, 'EditRequestCookie');
+ };
+ declarativeWebRequest.EditResponseCookie = function(parameters) {
+ setupInstance(this, parameters, 'EditResponseCookie');
+ };
+ declarativeWebRequest.RemoveRequestCookie = function(parameters) {
+ setupInstance(this, parameters, 'RemoveRequestCookie');
+ };
+ declarativeWebRequest.RemoveResponseCookie = function(parameters) {
+ setupInstance(this, parameters, 'RemoveResponseCookie');
+ };
+ declarativeWebRequest.SendMessageToExtension = function(parameters) {
+ setupInstance(this, parameters, 'SendMessageToExtension');
+ };
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/display_source_custom_bindings.js b/chromium/extensions/renderer/resources/display_source_custom_bindings.js
new file mode 100644
index 00000000000..38d7e3c791e
--- /dev/null
+++ b/chromium/extensions/renderer/resources/display_source_custom_bindings.js
@@ -0,0 +1,69 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Custom binding for the Display Source API.
+
+var binding = require('binding').Binding.create('displaySource');
+var chrome = requireNative('chrome').GetChrome();
+var lastError = require('lastError');
+var natives = requireNative('display_source');
+var logging = requireNative('logging');
+
+var callbacksInfo = {};
+
+function callbackWrapper(callback, method, message) {
+ if (callback == undefined)
+ return;
+
+ try {
+ if (message !== null)
+ lastError.set(method, message, null, chrome);
+ callback();
+ } finally {
+ lastError.clear(chrome);
+ }
+}
+
+function callCompletionCallback(callbackId, error_message) {
+ try {
+ var callbackInfo = callbacksInfo[callbackId];
+ logging.DCHECK(callbackInfo != null);
+ callbackWrapper(callbackInfo.callback, callbackInfo.method, error_message);
+ } finally {
+ delete callbacksInfo[callbackId];
+ }
+}
+
+binding.registerCustomHook(function(bindingsAPI, extensionId) {
+ var apiFunctions = bindingsAPI.apiFunctions;
+ apiFunctions.setHandleRequest(
+ 'startSession', function(sessionInfo, callback) {
+ try {
+ var callId = natives.StartSession(sessionInfo);
+ callbacksInfo[callId] = {
+ callback: callback,
+ method: 'displaySource.startSession'
+ };
+ } catch (e) {
+ callbackWrapper(callback, 'displaySource.startSession', e.message);
+ }
+ });
+ apiFunctions.setHandleRequest(
+ 'terminateSession', function(sink_id, callback) {
+ try {
+ var callId = natives.TerminateSession(sink_id);
+ callbacksInfo[callId] = {
+ callback: callback,
+ method: 'displaySource.terminateSession'
+ };
+ } catch (e) {
+ callbackWrapper(
+ callback, 'displaySource.terminateSession', e.message);
+ }
+ });
+});
+
+exports.$set('binding', binding.generate());
+// Called by C++.
+exports.$set('callCompletionCallback', callCompletionCallback);
diff --git a/chromium/extensions/renderer/resources/entry_id_manager.js b/chromium/extensions/renderer/resources/entry_id_manager.js
new file mode 100644
index 00000000000..8e23942f93a
--- /dev/null
+++ b/chromium/extensions/renderer/resources/entry_id_manager.js
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var fileSystemNatives = requireNative('file_system_natives');
+
+var nameToIds = {};
+var idsToEntries = {};
+
+function computeName(entry) {
+ return entry.filesystem.name + ':' + entry.fullPath;
+}
+
+function computeId(entry) {
+ var fileSystemId = fileSystemNatives.CrackIsolatedFileSystemName(
+ entry.filesystem.name);
+ if (!fileSystemId)
+ return null;
+ // Strip the leading '/' from the path.
+ return fileSystemId + ':' + $String.slice(entry.fullPath, 1);
+}
+
+function registerEntry(id, entry) {
+ var name = computeName(entry);
+ nameToIds[name] = id;
+ idsToEntries[id] = entry;
+}
+
+function getEntryId(entry) {
+ var name = null;
+ try {
+ name = computeName(entry);
+ } catch(e) {
+ return null;
+ }
+ var id = nameToIds[name];
+ if (id != null)
+ return id;
+
+ // If an entry has not been registered, compute its id and register it.
+ id = computeId(entry);
+ registerEntry(id, entry);
+ return id;
+}
+
+function getEntryById(id) {
+ return idsToEntries[id];
+}
+
+exports.$set('registerEntry', registerEntry);
+exports.$set('getEntryId', getEntryId);
+exports.$set('getEntryById', getEntryById);
diff --git a/chromium/extensions/renderer/resources/event.js b/chromium/extensions/renderer/resources/event.js
new file mode 100644
index 00000000000..2f9aa3c7a55
--- /dev/null
+++ b/chromium/extensions/renderer/resources/event.js
@@ -0,0 +1,520 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TODO(robwu): Fix indentation.
+
+ var exceptionHandler = require('uncaught_exception_handler');
+ var eventNatives = requireNative('event_natives');
+ var logging = requireNative('logging');
+ var schemaRegistry = requireNative('schema_registry');
+ var sendRequest = require('sendRequest').sendRequest;
+ var utils = require('utils');
+ var validate = require('schemaUtils').validate;
+
+ // Schemas for the rule-style functions on the events API that
+ // only need to be generated occasionally, so populate them lazily.
+ var ruleFunctionSchemas = {
+ __proto__: null,
+ // These values are set lazily:
+ // addRules: {},
+ // getRules: {},
+ // removeRules: {}
+ };
+
+ // This function ensures that |ruleFunctionSchemas| is populated.
+ function ensureRuleSchemasLoaded() {
+ if (ruleFunctionSchemas.addRules)
+ return;
+ var eventsSchema = schemaRegistry.GetSchema("events");
+ var eventType = utils.lookup(eventsSchema.types, 'id', 'events.Event');
+
+ ruleFunctionSchemas.addRules =
+ utils.lookup(eventType.functions, 'name', 'addRules');
+ ruleFunctionSchemas.getRules =
+ utils.lookup(eventType.functions, 'name', 'getRules');
+ ruleFunctionSchemas.removeRules =
+ utils.lookup(eventType.functions, 'name', 'removeRules');
+ }
+
+ // A map of event names to the event object that is registered to that name.
+ var attachedNamedEvents = {__proto__: null};
+
+ // A map of functions that massage event arguments before they are dispatched.
+ // Key is event name, value is function.
+ var eventArgumentMassagers = {__proto__: null};
+
+ // An attachment strategy for events that aren't attached to the browser.
+ // This applies to events with the "unmanaged" option and events without
+ // names.
+ function NullAttachmentStrategy(event) {
+ this.event_ = event;
+ }
+ $Object.setPrototypeOf(NullAttachmentStrategy.prototype, null);
+
+ NullAttachmentStrategy.prototype.onAddedListener =
+ function(listener) {
+ };
+ NullAttachmentStrategy.prototype.onRemovedListener =
+ function(listener) {
+ };
+ NullAttachmentStrategy.prototype.detach = function(manual) {
+ };
+ NullAttachmentStrategy.prototype.getListenersByIDs = function(ids) {
+ // |ids| is for filtered events only.
+ return this.event_.listeners;
+ };
+
+ // Handles adding/removing/dispatching listeners for unfiltered events.
+ function UnfilteredAttachmentStrategy(event) {
+ this.event_ = event;
+ }
+ $Object.setPrototypeOf(UnfilteredAttachmentStrategy.prototype, null);
+
+ UnfilteredAttachmentStrategy.prototype.onAddedListener =
+ function(listener) {
+ // Only attach / detach on the first / last listener removed.
+ if (this.event_.listeners.length == 0)
+ eventNatives.AttachEvent(this.event_.eventName);
+ };
+
+ UnfilteredAttachmentStrategy.prototype.onRemovedListener =
+ function(listener) {
+ if (this.event_.listeners.length == 0)
+ this.detach(true);
+ };
+
+ UnfilteredAttachmentStrategy.prototype.detach = function(manual) {
+ eventNatives.DetachEvent(this.event_.eventName, manual);
+ };
+
+ UnfilteredAttachmentStrategy.prototype.getListenersByIDs = function(ids) {
+ // |ids| is for filtered events only.
+ return this.event_.listeners;
+ };
+
+ function FilteredAttachmentStrategy(event) {
+ this.event_ = event;
+ this.listenerMap_ = {__proto__: null};
+ }
+ $Object.setPrototypeOf(FilteredAttachmentStrategy.prototype, null);
+
+ utils.defineProperty(FilteredAttachmentStrategy, 'idToEventMap',
+ {__proto__: null});
+
+ FilteredAttachmentStrategy.prototype.onAddedListener = function(listener) {
+ var id = eventNatives.AttachFilteredEvent(this.event_.eventName,
+ listener.filters || {});
+ if (id == -1)
+ throw new Error("Can't add listener");
+ listener.id = id;
+ this.listenerMap_[id] = listener;
+ FilteredAttachmentStrategy.idToEventMap[id] = this.event_;
+ };
+
+ FilteredAttachmentStrategy.prototype.onRemovedListener = function(listener) {
+ this.detachListener(listener, true);
+ };
+
+ FilteredAttachmentStrategy.prototype.detachListener =
+ function(listener, manual) {
+ if (listener.id == undefined)
+ throw new Error("listener.id undefined - '" + listener + "'");
+ var id = listener.id;
+ delete this.listenerMap_[id];
+ delete FilteredAttachmentStrategy.idToEventMap[id];
+ eventNatives.DetachFilteredEvent(id, manual);
+ };
+
+ FilteredAttachmentStrategy.prototype.detach = function(manual) {
+ for (var i in this.listenerMap_)
+ this.detachListener(this.listenerMap_[i], manual);
+ };
+
+ FilteredAttachmentStrategy.prototype.getListenersByIDs = function(ids) {
+ var result = [];
+ for (var i = 0; i < ids.length; i++)
+ $Array.push(result, this.listenerMap_[ids[i]]);
+ return result;
+ };
+
+ function parseEventOptions(opt_eventOptions) {
+ return $Object.assign({
+ __proto__: null,
+ }, {
+ // Event supports adding listeners with filters ("filtered events"), for
+ // example as used in the webNavigation API.
+ //
+ // event.addListener(listener, [filter1, filter2]);
+ supportsFilters: false,
+
+ // Events supports vanilla events. Most APIs use these.
+ //
+ // event.addListener(listener);
+ supportsListeners: true,
+
+ // Event supports adding rules ("declarative events") rather than
+ // listeners, for example as used in the declarativeWebRequest API.
+ //
+ // event.addRules([rule1, rule2]);
+ supportsRules: false,
+
+ // Event is unmanaged in that the browser has no knowledge of its
+ // existence; it's never invoked, doesn't keep the renderer alive, and
+ // the bindings system has no knowledge of it.
+ //
+ // Both events created by user code (new chrome.Event()) and messaging
+ // events are unmanaged, though in the latter case the browser *does*
+ // interact indirectly with them via IPCs written by hand.
+ unmanaged: false,
+ }, opt_eventOptions);
+ }
+
+ // Event object. If opt_eventName is provided, this object represents
+ // the unique instance of that named event, and dispatching an event
+ // with that name will route through this object's listeners. Note that
+ // opt_eventName is required for events that support rules.
+ //
+ // Example:
+ // var Event = require('event_bindings').Event;
+ // chrome.tabs.onChanged = new Event("tab-changed");
+ // chrome.tabs.onChanged.addListener(function(data) { alert(data); });
+ // Event.dispatch("tab-changed", "hi");
+ // will result in an alert dialog that says 'hi'.
+ //
+ // If opt_eventOptions exists, it is a dictionary that contains the boolean
+ // entries "supportsListeners" and "supportsRules".
+ // If opt_webViewInstanceId exists, it is an integer uniquely identifying a
+ // <webview> tag within the embedder. If it does not exist, then this is an
+ // extension event rather than a <webview> event.
+ function EventImpl(opt_eventName, opt_argSchemas, opt_eventOptions,
+ opt_webViewInstanceId) {
+ this.eventName = opt_eventName;
+ this.argSchemas = opt_argSchemas;
+ this.listeners = [];
+ this.eventOptions = parseEventOptions(opt_eventOptions);
+ this.webViewInstanceId = opt_webViewInstanceId || 0;
+
+ if (!this.eventName) {
+ if (this.eventOptions.supportsRules)
+ throw new Error("Events that support rules require an event name.");
+ // Events without names cannot be managed by the browser by definition
+ // (the browser has no way of identifying them).
+ this.eventOptions.unmanaged = true;
+ }
+
+ // Track whether the event has been destroyed to help track down the cause
+ // of http://crbug.com/258526.
+ // This variable will eventually hold the stack trace of the destroy call.
+ // TODO(kalman): Delete this and replace with more sound logic that catches
+ // when events are used without being *attached*.
+ this.destroyed = null;
+
+ if (this.eventOptions.unmanaged)
+ this.attachmentStrategy = new NullAttachmentStrategy(this);
+ else if (this.eventOptions.supportsFilters)
+ this.attachmentStrategy = new FilteredAttachmentStrategy(this);
+ else
+ this.attachmentStrategy = new UnfilteredAttachmentStrategy(this);
+ }
+ $Object.setPrototypeOf(EventImpl.prototype, null);
+
+ // callback is a function(args, dispatch). args are the args we receive from
+ // dispatchEvent(), and dispatch is a function(args) that dispatches args to
+ // its listeners.
+ function registerArgumentMassager(name, callback) {
+ if (eventArgumentMassagers[name])
+ throw new Error("Massager already registered for event: " + name);
+ eventArgumentMassagers[name] = callback;
+ }
+
+ // Dispatches a named event with the given argument array. The args array is
+ // the list of arguments that will be sent to the event callback.
+ function dispatchEvent(name, args, filteringInfo) {
+ var listenerIDs = [];
+
+ if (filteringInfo)
+ listenerIDs = eventNatives.MatchAgainstEventFilter(name, filteringInfo);
+
+ var event = attachedNamedEvents[name];
+ if (!event)
+ return;
+
+ var dispatchArgs = function(args) {
+ var result = event.dispatch_(args, listenerIDs);
+ if (result)
+ logging.DCHECK(!result.validationErrors, result.validationErrors);
+ return result;
+ };
+
+ if (eventArgumentMassagers[name])
+ eventArgumentMassagers[name](args, dispatchArgs);
+ else
+ dispatchArgs(args);
+ }
+
+ // Registers a callback to be called when this event is dispatched.
+ EventImpl.prototype.addListener = function(cb, filters) {
+ if (!this.eventOptions.supportsListeners)
+ throw new Error("This event does not support listeners.");
+ if (this.eventOptions.maxListeners &&
+ this.getListenerCount_() >= this.eventOptions.maxListeners) {
+ throw new Error("Too many listeners for " + this.eventName);
+ }
+ if (filters) {
+ if (!this.eventOptions.supportsFilters)
+ throw new Error("This event does not support filters.");
+ if (filters.url && !(filters.url instanceof Array))
+ throw new Error("filters.url should be an array.");
+ if (filters.serviceType &&
+ !(typeof filters.serviceType === 'string')) {
+ throw new Error("filters.serviceType should be a string.")
+ }
+ }
+ var listener = {callback: cb, filters: filters};
+ this.attach_(listener);
+ $Array.push(this.listeners, listener);
+ };
+
+ EventImpl.prototype.attach_ = function(listener) {
+ this.attachmentStrategy.onAddedListener(listener);
+
+ if (this.listeners.length == 0) {
+ if (this.eventName) {
+ if (attachedNamedEvents[this.eventName]) {
+ throw new Error("Event '" + this.eventName +
+ "' is already attached.");
+ }
+ attachedNamedEvents[this.eventName] = this;
+ }
+ }
+ };
+
+ // Unregisters a callback.
+ EventImpl.prototype.removeListener = function(cb) {
+ if (!this.eventOptions.supportsListeners)
+ throw new Error("This event does not support listeners.");
+
+ var idx = this.findListener_(cb);
+ if (idx == -1)
+ return;
+
+ var removedListener = $Array.splice(this.listeners, idx, 1)[0];
+ this.attachmentStrategy.onRemovedListener(removedListener);
+
+ if (this.listeners.length == 0) {
+ if (this.eventName) {
+ if (!attachedNamedEvents[this.eventName]) {
+ throw new Error(
+ "Event '" + this.eventName + "' is not attached.");
+ }
+ delete attachedNamedEvents[this.eventName];
+ }
+ }
+ };
+
+ // Test if the given callback is registered for this event.
+ EventImpl.prototype.hasListener = function(cb) {
+ if (!this.eventOptions.supportsListeners)
+ throw new Error("This event does not support listeners.");
+ return this.findListener_(cb) > -1;
+ };
+
+ // Test if any callbacks are registered for this event.
+ EventImpl.prototype.hasListeners = function() {
+ return this.getListenerCount_() > 0;
+ };
+
+ // Returns the number of listeners on this event.
+ EventImpl.prototype.getListenerCount_ = function() {
+ if (!this.eventOptions.supportsListeners)
+ throw new Error("This event does not support listeners.");
+ return this.listeners.length;
+ };
+
+ // Returns the index of the given callback if registered, or -1 if not
+ // found.
+ EventImpl.prototype.findListener_ = function(cb) {
+ for (var i = 0; i < this.listeners.length; i++) {
+ if (this.listeners[i].callback == cb) {
+ return i;
+ }
+ }
+
+ return -1;
+ };
+
+ EventImpl.prototype.dispatch_ = function(args, listenerIDs) {
+ if (this.destroyed) {
+ throw new Error(this.eventName + ' was already destroyed at: ' +
+ this.destroyed);
+ }
+ if (!this.eventOptions.supportsListeners)
+ throw new Error("This event does not support listeners.");
+
+ if (this.argSchemas && logging.DCHECK_IS_ON()) {
+ try {
+ validate(args, this.argSchemas);
+ } catch (e) {
+ e.message += ' in ' + this.eventName;
+ throw e;
+ }
+ }
+
+ // Make a copy of the listeners in case the listener list is modified
+ // while dispatching the event.
+ var listeners = $Array.slice(
+ this.attachmentStrategy.getListenersByIDs(listenerIDs));
+
+ var results = [];
+ for (var i = 0; i < listeners.length; i++) {
+ try {
+ var result = this.wrapper.dispatchToListener(listeners[i].callback,
+ args);
+ if (result !== undefined)
+ $Array.push(results, result);
+ } catch (e) {
+ exceptionHandler.handle('Error in event handler for ' +
+ (this.eventName ? this.eventName : '(unknown)'),
+ e);
+ }
+ }
+ if (results.length)
+ return {results: results};
+ }
+
+ // Can be overridden to support custom dispatching.
+ EventImpl.prototype.dispatchToListener = function(callback, args) {
+ return $Function.apply(callback, null, args);
+ }
+
+ // Dispatches this event object to all listeners, passing all supplied
+ // arguments to this function each listener.
+ EventImpl.prototype.dispatch = function(varargs) {
+ return this.dispatch_($Array.slice(arguments), undefined);
+ };
+
+ // Detaches this event object from its name.
+ EventImpl.prototype.detach_ = function() {
+ this.attachmentStrategy.detach(false);
+ };
+
+ EventImpl.prototype.destroy_ = function() {
+ this.listeners.length = 0;
+ this.detach_();
+ this.destroyed = exceptionHandler.getStackTrace();
+ };
+
+ EventImpl.prototype.addRules = function(rules, opt_cb) {
+ if (!this.eventOptions.supportsRules)
+ throw new Error("This event does not support rules.");
+
+ // Takes a list of JSON datatype identifiers and returns a schema fragment
+ // that verifies that a JSON object corresponds to an array of only these
+ // data types.
+ function buildArrayOfChoicesSchema(typesList) {
+ return {
+ __proto__: null,
+ 'type': 'array',
+ 'items': {
+ __proto__: null,
+ 'choices': $Array.map(typesList, function(el) {
+ return {
+ __proto__: null,
+ '$ref': el,
+ };
+ }),
+ }
+ };
+ }
+
+ // Validate conditions and actions against specific schemas of this
+ // event object type.
+ // |rules| is an array of JSON objects that follow the Rule type of the
+ // declarative extension APIs. |conditions| is an array of JSON type
+ // identifiers that are allowed to occur in the conditions attribute of each
+ // rule. Likewise, |actions| is an array of JSON type identifiers that are
+ // allowed to occur in the actions attribute of each rule.
+ function validateRules(rules, conditions, actions) {
+ var conditionsSchema = buildArrayOfChoicesSchema(conditions);
+ var actionsSchema = buildArrayOfChoicesSchema(actions);
+ $Array.forEach(rules, function(rule) {
+ validate([rule.conditions], [conditionsSchema]);
+ validate([rule.actions], [actionsSchema]);
+ });
+ };
+
+ if (!this.eventOptions.conditions || !this.eventOptions.actions) {
+ throw new Error('Event ' + this.eventName + ' misses ' +
+ 'conditions or actions in the API specification.');
+ }
+
+ validateRules(rules,
+ this.eventOptions.conditions,
+ this.eventOptions.actions);
+
+ ensureRuleSchemasLoaded();
+ // We remove the first parameter from the validation to give the user more
+ // meaningful error messages.
+ validate([this.webViewInstanceId, rules, opt_cb],
+ $Array.slice(ruleFunctionSchemas.addRules.parameters, 1));
+ sendRequest(
+ "events.addRules",
+ [this.eventName, this.webViewInstanceId, rules, opt_cb],
+ ruleFunctionSchemas.addRules.parameters);
+ }
+
+ EventImpl.prototype.removeRules = function(ruleIdentifiers, opt_cb) {
+ if (!this.eventOptions.supportsRules)
+ throw new Error("This event does not support rules.");
+ ensureRuleSchemasLoaded();
+ // We remove the first parameter from the validation to give the user more
+ // meaningful error messages.
+ validate([this.webViewInstanceId, ruleIdentifiers, opt_cb],
+ $Array.slice(ruleFunctionSchemas.removeRules.parameters, 1));
+ sendRequest("events.removeRules",
+ [this.eventName,
+ this.webViewInstanceId,
+ ruleIdentifiers,
+ opt_cb],
+ ruleFunctionSchemas.removeRules.parameters);
+ }
+
+ EventImpl.prototype.getRules = function(ruleIdentifiers, cb) {
+ if (!this.eventOptions.supportsRules)
+ throw new Error("This event does not support rules.");
+ ensureRuleSchemasLoaded();
+ // We remove the first parameter from the validation to give the user more
+ // meaningful error messages.
+ validate([this.webViewInstanceId, ruleIdentifiers, cb],
+ $Array.slice(ruleFunctionSchemas.getRules.parameters, 1));
+
+ sendRequest(
+ "events.getRules",
+ [this.eventName, this.webViewInstanceId, ruleIdentifiers, cb],
+ ruleFunctionSchemas.getRules.parameters);
+ }
+
+ function Event() {
+ privates(Event).constructPrivate(this, arguments);
+ }
+ utils.expose(Event, EventImpl, {
+ functions: [
+ 'addListener',
+ 'removeListener',
+ 'hasListener',
+ 'hasListeners',
+ 'dispatchToListener',
+ 'dispatch',
+ 'addRules',
+ 'removeRules',
+ 'getRules',
+ ],
+ });
+
+ // NOTE: Event is (lazily) exposed as chrome.Event from dispatcher.cc.
+ exports.$set('Event', Event);
+
+ exports.$set('dispatchEvent', dispatchEvent);
+ exports.$set('parseEventOptions', parseEventOptions);
+ exports.$set('registerArgumentMassager', registerArgumentMassager);
diff --git a/chromium/extensions/renderer/resources/extension.css b/chromium/extensions/renderer/resources/extension.css
new file mode 100644
index 00000000000..808a08ce151
--- /dev/null
+++ b/chromium/extensions/renderer/resources/extension.css
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2014 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * This stylesheet is used to apply Chrome styles to extension pages that opt in
+ * to using them.
+ *
+ * These styles have been copied from ui/webui/resources/css/chrome_shared.css
+ * and ui/webui/resources/css/widgets.css *with CSS class logic removed*, so
+ * that it's as close to a user-agent stylesheet as possible.
+ *
+ * For example, extensions shouldn't be able to set a .link-button class and
+ * have it do anything.
+ *
+ * Other than that, keep this file and chrome_shared.css/widgets.cc in sync as
+ * much as possible.
+ */
+
+body {
+ color: #333;
+ cursor: default;
+ /* Note that the correct font-family and font-size are set in
+ * extension_fonts.css. */
+ /* This top margin of 14px matches the top padding on the h1 element on
+ * overlays (see the ".overlay .page h1" selector in overlay.css), which
+ * every dialogue has.
+ *
+ * Similarly, the bottom 14px margin matches the bottom padding of the area
+ * which hosts the buttons (see the ".overlay .page * .action-area" selector
+ * in overlay.css).
+ *
+ * Both have a padding left/right of 17px.
+ *
+ * Note that we're putting this here in the Extension content, rather than
+ * the WebUI element which contains the content, so that scrollbars in the
+ * Extension content don't get a 6px margin, which looks quite odd.
+ */
+ margin: 14px 17px;
+}
+
+p {
+ line-height: 1.8em;
+}
+
+h1,
+h2,
+h3 {
+ -webkit-user-select: none;
+ font-weight: normal;
+ /* Makes the vertical size of the text the same for all fonts. */
+ line-height: 1;
+}
+
+h1 {
+ font-size: 1.5em;
+}
+
+h2 {
+ font-size: 1.3em;
+ margin-bottom: 0.4em;
+}
+
+h3 {
+ color: black;
+ font-size: 1.2em;
+ margin-bottom: 0.8em;
+}
+
+a {
+ color: rgb(17, 85, 204);
+ text-decoration: underline;
+}
+
+a:active {
+ color: rgb(5, 37, 119);
+}
+
+/* Default state **************************************************************/
+
+:-webkit-any(button,
+ input[type='button'],
+ input[type='submit']),
+select,
+input[type='checkbox'],
+input[type='radio'] {
+ -webkit-appearance: none;
+ -webkit-user-select: none;
+ background-image: linear-gradient(#ededed, #ededed 38%, #dedede);
+ border: 1px solid rgba(0, 0, 0, 0.25);
+ border-radius: 2px;
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08),
+ inset 0 1px 2px rgba(255, 255, 255, 0.75);
+ color: #444;
+ font: inherit;
+ margin: 0 1px 0 0;
+ outline: none;
+ text-shadow: 0 1px 0 rgb(240, 240, 240);
+}
+
+:-webkit-any(button,
+ input[type='button'],
+ input[type='submit']),
+select {
+ min-height: 2em;
+ min-width: 4em;
+<if expr="is_win">
+ /* The following platform-specific rule is necessary to get adjacent
+ * buttons, text inputs, and so forth to align on their borders while also
+ * aligning on the text's baselines. */
+ padding-bottom: 1px;
+</if>
+}
+
+:-webkit-any(button,
+ input[type='button'],
+ input[type='submit']) {
+ -webkit-padding-end: 10px;
+ -webkit-padding-start: 10px;
+}
+
+select {
+ -webkit-appearance: none;
+ -webkit-padding-end: 20px;
+ -webkit-padding-start: 6px;
+ /* OVERRIDE */
+ background-image: url(../../../ui/webui/resources/images/select.png),
+ linear-gradient(#ededed, #ededed 38%, #dedede);
+ background-position: right center;
+ background-repeat: no-repeat;
+}
+
+html[dir='rtl'] select {
+ background-position: center left;
+}
+
+input[type='checkbox'] {
+ height: 13px;
+ position: relative;
+ vertical-align: middle;
+ width: 13px;
+}
+
+input[type='radio'] {
+ /* OVERRIDE */
+ border-radius: 100%;
+ height: 15px;
+ position: relative;
+ vertical-align: middle;
+ width: 15px;
+}
+
+/* TODO(estade): add more types here? */
+input[type='number'],
+input[type='password'],
+input[type='search'],
+input[type='text'],
+input[type='url'],
+input:not([type]),
+textarea {
+ border: 1px solid #bfbfbf;
+ border-radius: 2px;
+ box-sizing: border-box;
+ color: #444;
+ font: inherit;
+ margin: 0;
+ /* Use min-height to accommodate addditional padding for touch as needed. */
+ min-height: 2em;
+ padding: 3px;
+ outline: none;
+<if expr="is_win or is_macosx or is_ios">
+ /* For better alignment between adjacent buttons and inputs. */
+ padding-bottom: 4px;
+</if>
+}
+
+input[type='search'] {
+ -webkit-appearance: textfield;
+ /* NOTE: Keep a relatively high min-width for this so we don't obscure the end
+ * of the default text in relatively spacious languages (i.e. German). */
+ min-width: 160px;
+}
+
+/* Remove when https://bugs.webkit.org/show_bug.cgi?id=51499 is fixed.
+ * TODO(dbeam): are there more types that would benefit from this? */
+input[type='search']::-webkit-textfield-decoration-container {
+ direction: inherit;
+}
+
+/* Checked ********************************************************************/
+
+input[type='checkbox']:checked::before {
+ -webkit-user-select: none;
+ background-image: url(../../../ui/webui/resources/images/check.png);
+ background-size: 100% 100%;
+ content: '';
+ display: block;
+ height: 100%;
+ width: 100%;
+}
+
+input[type='radio']:checked::before {
+ background-color: #666;
+ border-radius: 100%;
+ bottom: 3px;
+ content: '';
+ display: block;
+ left: 3px;
+ position: absolute;
+ right: 3px;
+ top: 3px;
+}
+
+/* Hover **********************************************************************/
+
+:enabled:hover:-webkit-any(
+ select,
+ input[type='checkbox'],
+ input[type='radio'],
+ :-webkit-any(
+ button,
+ input[type='button'],
+ input[type='submit'])) {
+ background-image: linear-gradient(#f0f0f0, #f0f0f0 38%, #e0e0e0);
+ border-color: rgba(0, 0, 0, 0.3);
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.12),
+ inset 0 1px 2px rgba(255, 255, 255, 0.95);
+ color: black;
+}
+
+:enabled:hover:-webkit-any(select) {
+ /* OVERRIDE */
+ background-image: url(../../../ui/webui/resources/images/select.png),
+ linear-gradient(#f0f0f0, #f0f0f0 38%, #e0e0e0);
+}
+
+/* Active *********************************************************************/
+
+:enabled:active:-webkit-any(
+ select,
+ input[type='checkbox'],
+ input[type='radio'],
+ :-webkit-any(
+ button,
+ input[type='button'],
+ input[type='submit'])) {
+ background-image: linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7);
+ box-shadow: none;
+ text-shadow: none;
+}
+
+:enabled:active:-webkit-any(select) {
+ /* OVERRIDE */
+ background-image: url(../../../ui/webui/resources/images/select.png),
+ linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7);
+}
+
+/* Disabled *******************************************************************/
+
+:disabled:-webkit-any(
+ button,
+ input[type='button'],
+ input[type='submit']),
+select:disabled {
+ background-image: linear-gradient(#f1f1f1, #f1f1f1 38%, #e6e6e6);
+ border-color: rgba(80, 80, 80, 0.2);
+ box-shadow: 0 1px 0 rgba(80, 80, 80, 0.08),
+ inset 0 1px 2px rgba(255, 255, 255, 0.75);
+ color: #aaa;
+}
+
+select:disabled {
+ /* OVERRIDE */
+ background-image: url(../../../ui/webui/resources/images/disabled_select.png),
+ linear-gradient(#f1f1f1, #f1f1f1 38%, #e6e6e6);
+}
+
+input:disabled:-webkit-any([type='checkbox'],
+ [type='radio']) {
+ opacity: .75;
+}
+
+input:disabled:-webkit-any([type='password'],
+ [type='search'],
+ [type='text'],
+ [type='url'],
+ :not([type])) {
+ color: #999;
+}
+
+/* Focus **********************************************************************/
+
+:enabled:focus:-webkit-any(
+ select,
+ input[type='checkbox'],
+ input[type='number'],
+ input[type='password'],
+ input[type='radio'],
+ input[type='search'],
+ input[type='text'],
+ input[type='url'],
+ input:not([type]),
+ :-webkit-any(
+ button,
+ input[type='button'],
+ input[type='submit'])) {
+ /* OVERRIDE */
+ -webkit-transition: border-color 200ms;
+ /* We use border color because it follows the border radius (unlike outline).
+ * This is particularly noticeable on mac. */
+ border-color: rgb(77, 144, 254);
+ outline: none;
+}
+
+/* Checkbox/radio helpers ******************************************************
+ *
+ * .checkbox and .radio classes wrap labels. Checkboxes and radios should use
+ * these classes with the markup structure:
+ *
+ * <div class="checkbox">
+ * <label>
+ * <input type="checkbox"></input>
+ * <span>
+ * </label>
+ * </div>
+ */
+
+:-webkit-any(.checkbox, .radio) label {
+ /* Don't expand horizontally: <http://crbug.com/112091>. */
+ align-items: center;
+ display: inline-flex;
+ padding-bottom: 7px;
+ padding-top: 7px;
+}
+
+:-webkit-any(.checkbox, .radio) label input {
+ flex-shrink: 0;
+}
+
+:-webkit-any(.checkbox, .radio) label input ~ span {
+ -webkit-margin-start: 0.6em;
+ /* Make sure long spans wrap at the same horizontal position they start. */
+ display: block;
+}
+
+:-webkit-any(.checkbox, .radio) label:hover {
+ color: black;
+}
+
+label > input:disabled:-webkit-any([type='checkbox'], [type='radio']) ~ span {
+ color: #999;
+}
diff --git a/chromium/extensions/renderer/resources/extension_custom_bindings.js b/chromium/extensions/renderer/resources/extension_custom_bindings.js
new file mode 100644
index 00000000000..68450d56b3a
--- /dev/null
+++ b/chromium/extensions/renderer/resources/extension_custom_bindings.js
@@ -0,0 +1,111 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Custom binding for the extension API.
+
+var binding = require('binding').Binding.create('extension');
+
+var messaging = require('messaging');
+var runtimeNatives = requireNative('runtime');
+var GetExtensionViews = runtimeNatives.GetExtensionViews;
+var chrome = requireNative('chrome').GetChrome();
+
+var inIncognitoContext = requireNative('process').InIncognitoContext();
+var sendRequestIsDisabled = requireNative('process').IsSendRequestDisabled();
+var contextType = requireNative('process').GetContextType();
+var manifestVersion = requireNative('process').GetManifestVersion();
+
+// This should match chrome.windows.WINDOW_ID_NONE.
+//
+// We can't use chrome.windows.WINDOW_ID_NONE directly because the
+// chrome.windows API won't exist unless this extension has permission for it;
+// which may not be the case.
+var WINDOW_ID_NONE = -1;
+
+binding.registerCustomHook(function(bindingsAPI, extensionId) {
+ var extension = bindingsAPI.compiledApi;
+ if (manifestVersion < 2) {
+ chrome.self = extension;
+ extension.inIncognitoTab = inIncognitoContext;
+ }
+ extension.inIncognitoContext = inIncognitoContext;
+
+ var apiFunctions = bindingsAPI.apiFunctions;
+
+ apiFunctions.setHandleRequest('getViews', function(properties) {
+ var windowId = WINDOW_ID_NONE;
+ var type = 'ALL';
+ if (properties) {
+ if (properties.type != null) {
+ type = properties.type;
+ }
+ if (properties.windowId != null) {
+ windowId = properties.windowId;
+ }
+ }
+ return GetExtensionViews(windowId, type);
+ });
+
+ apiFunctions.setHandleRequest('getBackgroundPage', function() {
+ return GetExtensionViews(-1, 'BACKGROUND')[0] || null;
+ });
+
+ apiFunctions.setHandleRequest('getExtensionTabs', function(windowId) {
+ if (windowId == null)
+ windowId = WINDOW_ID_NONE;
+ return GetExtensionViews(windowId, 'TAB');
+ });
+
+ apiFunctions.setHandleRequest('getURL', function(path) {
+ path = String(path);
+ if (!path.length || path[0] != '/')
+ path = '/' + path;
+ return 'chrome-extension://' + extensionId + path;
+ });
+
+ // Alias several messaging deprecated APIs to their runtime counterparts.
+ var mayNeedAlias = [
+ // Types
+ 'Port',
+ // Functions
+ 'connect', 'sendMessage', 'connectNative', 'sendNativeMessage',
+ // Events
+ 'onConnect', 'onConnectExternal', 'onMessage', 'onMessageExternal'
+ ];
+ $Array.forEach(mayNeedAlias, function(alias) {
+ // Checking existence isn't enough since some functions are disabled via
+ // getters that throw exceptions. Assume that any getter is such a function.
+ if (chrome.runtime &&
+ $Object.hasOwnProperty(chrome.runtime, alias) &&
+ chrome.runtime.__lookupGetter__(alias) === undefined) {
+ extension[alias] = chrome.runtime[alias];
+ }
+ });
+
+ apiFunctions.setUpdateArgumentsPreValidate('sendRequest',
+ $Function.bind(messaging.sendMessageUpdateArguments,
+ null, 'sendRequest', false /* hasOptionsArgument */));
+
+ apiFunctions.setHandleRequest('sendRequest',
+ function(targetId, request, responseCallback) {
+ if (sendRequestIsDisabled)
+ throw new Error(sendRequestIsDisabled);
+ var port = chrome.runtime.connect(targetId || extensionId,
+ {name: messaging.kRequestChannel});
+ messaging.sendMessageImpl(port, request, responseCallback);
+ });
+
+ if (sendRequestIsDisabled) {
+ extension.onRequest.addListener = function() {
+ throw new Error(sendRequestIsDisabled);
+ };
+ if (contextType == 'BLESSED_EXTENSION') {
+ extension.onRequestExternal.addListener = function() {
+ throw new Error(sendRequestIsDisabled);
+ };
+ }
+ }
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/extension_fonts.css b/chromium/extensions/renderer/resources/extension_fonts.css
new file mode 100644
index 00000000000..464f8ebfc99
--- /dev/null
+++ b/chromium/extensions/renderer/resources/extension_fonts.css
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2014 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * This stylesheet is used to apply Chrome system fonts to all extension pages.
+ */
+
+body {
+ font-family: $FONTFAMILY;
+ font-size: $FONTSIZE;
+}
diff --git a/chromium/extensions/renderer/resources/extensions_renderer_resources.grd b/chromium/extensions/renderer/resources/extensions_renderer_resources.grd
new file mode 100644
index 00000000000..808b931d542
--- /dev/null
+++ b/chromium/extensions/renderer/resources/extensions_renderer_resources.grd
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1">
+ <outputs>
+ <output filename="grit/extensions_renderer_resources.h" type="rc_header">
+ <emit emit_type='prepend'></emit>
+ </output>
+ <output filename="extensions_renderer_resources.pak" type="data_package" />
+ </outputs>
+ <release seq="1">
+ <includes>
+ <!-- Extension libraries. -->
+ <include name="IDR_APP_VIEW_JS" file="guest_view/app_view/app_view.js" type="BINDATA" />
+ <include name="IDR_ASYNC_WAITER_JS" file="async_waiter.js" type="BINDATA" />
+ <include name="IDR_BROWSER_TEST_ENVIRONMENT_SPECIFIC_BINDINGS_JS" file="browser_test_environment_specific_bindings.js" type="BINDATA" />
+ <include name="IDR_DATA_RECEIVER_JS" file="data_receiver.js" type="BINDATA" />
+ <include name="IDR_DATA_SENDER_JS" file="data_sender.js" type="BINDATA" />
+ <include name="IDR_DATA_STREAM_MOJOM_JS" file="${mojom_root}\device\serial\data_stream.mojom.js" use_base_dir="false" type="BINDATA" />
+ <include name="IDR_DATA_STREAM_SERIALIZATION_MOJOM_JS" file="${mojom_root}\device\serial\data_stream_serialization.mojom.js" use_base_dir="false" type="BINDATA" />
+ <include name="IDR_ENTRY_ID_MANAGER" file="entry_id_manager.js" type="BINDATA" />
+ <include name="IDR_EVENT_BINDINGS_JS" file="event.js" type="BINDATA" />
+ <include name="IDR_EXTENSION_OPTIONS_JS" file="guest_view/extension_options/extension_options.js" type="BINDATA"/>
+ <include name="IDR_EXTENSION_OPTIONS_ATTRIBUTES_JS" file="guest_view/extension_options/extension_options_attributes.js" type="BINDATA"/>
+ <include name="IDR_EXTENSION_OPTIONS_CONSTANTS_JS" file="guest_view/extension_options/extension_options_constants.js" type="BINDATA"/>
+ <include name="IDR_EXTENSION_OPTIONS_EVENTS_JS" file="guest_view/extension_options/extension_options_events.js" type="BINDATA"/>
+ <include name="IDR_EXTENSION_VIEW_JS" file="guest_view/extension_view/extension_view.js" type="BINDATA" />
+ <include name="IDR_EXTENSION_VIEW_API_METHODS_JS" file="guest_view/extension_view/extension_view_api_methods.js" type="BINDATA" />
+ <include name="IDR_EXTENSION_VIEW_ATTRIBUTES_JS" file="guest_view/extension_view/extension_view_attributes.js" type="BINDATA" />
+ <include name="IDR_EXTENSION_VIEW_CONSTANTS_JS" file="guest_view/extension_view/extension_view_constants.js" type="BINDATA" />
+ <include name="IDR_EXTENSION_VIEW_EVENTS_JS" file="guest_view/extension_view/extension_view_events.js" type="BINDATA" />
+ <include name="IDR_EXTENSION_VIEW_INTERNAL_CUSTOM_BINDINGS_JS" file="guest_view/extension_view/extension_view_internal.js" type="BINDATA" />
+ <include name="IDR_GUEST_VIEW_ATTRIBUTES_JS" file="guest_view/guest_view_attributes.js" type="BINDATA" />
+ <include name="IDR_GUEST_VIEW_CONTAINER_JS" file="guest_view/guest_view_container.js" type="BINDATA" />
+ <include name="IDR_GUEST_VIEW_DENY_JS" file="guest_view/guest_view_deny.js" type="BINDATA" />
+ <include name="IDR_GUEST_VIEW_EVENTS_JS" file="guest_view/guest_view_events.js" type="BINDATA" />
+ <include name="IDR_GUEST_VIEW_IFRAME_CONTAINER_JS" file="guest_view/guest_view_iframe_container.js" type="BINDATA" />
+ <include name="IDR_GUEST_VIEW_IFRAME_JS" file="guest_view/guest_view_iframe.js" type="BINDATA" />
+ <include name="IDR_GUEST_VIEW_JS" file="guest_view/guest_view.js" type="BINDATA" />
+ <include name="IDR_IMAGE_UTIL_JS" file="image_util.js" type="BINDATA" />
+ <include name="IDR_JSON_SCHEMA_JS" file="json_schema.js" type="BINDATA" />
+ <include name="IDR_KEEP_ALIVE_JS" file="keep_alive.js" type="BINDATA" />
+ <include name="IDR_KEEP_ALIVE_MOJOM_JS" file="${mojom_root}\extensions\common\mojo\keep_alive.mojom.js" use_base_dir="false" type="BINDATA" />
+ <include name="IDR_LAST_ERROR_JS" file="last_error.js" type="BINDATA" />
+ <include name="IDR_MESSAGING_JS" file="messaging.js" type="BINDATA" />
+ <include name="IDR_MESSAGING_UTILS_JS" file="messaging_utils.js" type="BINDATA" />
+ <include name="IDR_MIME_HANDLER_PRIVATE_CUSTOM_BINDINGS_JS" file="mime_handler_private_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_MIME_HANDLER_MOJOM_JS" file="${mojom_root}\extensions\common\api\mime_handler.mojom.js" use_base_dir="false" type="BINDATA" />
+ <include name="IDR_SCHEMA_UTILS_JS" file="schema_utils.js" type="BINDATA" />
+ <include name="IDR_SEND_REQUEST_JS" file="send_request.js" type="BINDATA" />
+ <include name="IDR_SERIAL_CUSTOM_BINDINGS_JS" file="serial_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_SERIAL_MOJOM_JS" file="${mojom_root}\device\serial\serial.mojom.js" use_base_dir="false" type="BINDATA" />
+ <include name="IDR_SERIAL_SERIALIZATION_MOJOM_JS" file="${mojom_root}\device\serial\serial_serialization.mojom.js" use_base_dir="false" type="BINDATA" />
+ <include name="IDR_SERIAL_SERVICE_JS" file="serial_service.js" type="BINDATA" />
+ <include name="IDR_SET_ICON_JS" file="set_icon.js" type="BINDATA" />
+ <include name="IDR_STASH_CLIENT_JS" file="stash_client.js" type="BINDATA" />
+ <include name="IDR_STASH_MOJOM_JS" file="${mojom_root}\extensions\common\mojo\stash.mojom.js" use_base_dir="false" type="BINDATA" />
+ <include name="IDR_TEST_CUSTOM_BINDINGS_JS" file="test_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_UNCAUGHT_EXCEPTION_HANDLER_JS" file="uncaught_exception_handler.js" type="BINDATA" />
+ <include name="IDR_UTILS_JS" file="utils.js" type="BINDATA" />
+ <include name="IDR_WEB_VIEW_ACTION_REQUESTS_JS" file="guest_view/web_view/web_view_action_requests.js" type="BINDATA" />
+ <include name="IDR_WEB_VIEW_API_METHODS_JS" file="guest_view/web_view/web_view_api_methods.js" type="BINDATA" />
+ <include name="IDR_WEB_VIEW_ATTRIBUTES_JS" file="guest_view/web_view/web_view_attributes.js" type="BINDATA" />
+ <include name="IDR_WEB_VIEW_CONSTANTS_JS" file="guest_view/web_view/web_view_constants.js" type="BINDATA" />
+ <include name="IDR_WEB_VIEW_EVENTS_JS" file="guest_view/web_view/web_view_events.js" type="BINDATA" />
+ <include name="IDR_WEB_VIEW_EXPERIMENTAL_JS" file="guest_view/web_view/web_view_experimental.js" type="BINDATA" />
+ <include name="IDR_WEB_VIEW_INTERNAL_CUSTOM_BINDINGS_JS" file="guest_view/web_view/web_view_internal.js" type="BINDATA" />
+ <include name="IDR_WEB_VIEW_JS" file="guest_view/web_view/web_view.js" type="BINDATA" />
+
+ <!-- Custom bindings for APIs. -->
+ <include name="IDR_APP_RUNTIME_CUSTOM_BINDINGS_JS" file="app_runtime_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_APP_WINDOW_CUSTOM_BINDINGS_JS" file="app_window_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_BINDING_JS" file="binding.js" type="BINDATA" />
+ <include name="IDR_CONTEXT_MENUS_CUSTOM_BINDINGS_JS" file="context_menus_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_CONTEXT_MENUS_HANDLERS_JS" file="context_menus_handlers.js" type="BINDATA" />
+ <include name="IDR_DECLARATIVE_WEBREQUEST_CUSTOM_BINDINGS_JS" file="declarative_webrequest_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_DISPLAY_SOURCE_CUSTOM_BINDINGS_JS" file="display_source_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_EXTENSION_CUSTOM_BINDINGS_JS" file="extension_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_GREASEMONKEY_API_JS" file="greasemonkey_api.js" type="BINDATA" />
+ <include name="IDR_I18N_CUSTOM_BINDINGS_JS" file="i18n_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_MOJO_PRIVATE_CUSTOM_BINDINGS_JS" file="mojo_private_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_PERMISSIONS_CUSTOM_BINDINGS_JS" file="permissions_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_PRINTER_PROVIDER_CUSTOM_BINDINGS_JS" file="printer_provider_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_RUNTIME_CUSTOM_BINDINGS_JS" file="runtime_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_SERVICE_WORKER_BINDINGS_JS" file="service_worker_bindings.js" type="BINDATA" />
+ <include name="IDR_WEB_REQUEST_CUSTOM_BINDINGS_JS" file="web_request_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_WEB_REQUEST_INTERNAL_CUSTOM_BINDINGS_JS" file="web_request_internal_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_WINDOW_CONTROLS_JS" file="window_controls.js" type="BINDATA" />
+ <include name="IDR_WINDOW_CONTROLS_TEMPLATE_HTML" file="window_controls_template.html" type="BINDATA" />
+ <include name="IDR_WEB_VIEW_REQUEST_CUSTOM_BINDINGS_JS" file="guest_view/web_view/web_view_request_custom_bindings.js" type="BINDATA" />
+
+ <!-- Custom types for APIs. -->
+ <include name="IDR_STORAGE_AREA_JS" file="storage_area.js" type="BINDATA" />
+
+ <!-- Platform app support. -->
+ <include name="IDR_PLATFORM_APP_CSS" file="platform_app.css" type="BINDATA" />
+ <include name="IDR_PLATFORM_APP_JS" file="platform_app.js" type="BINDATA" />
+
+ <!-- Extension styles. -->
+ <include name="IDR_EXTENSION_FONTS_CSS" file="extension_fonts.css" type="BINDATA"/>
+
+ <!-- Media Router Mojo service and bindings. -->
+ <if expr="enable_media_router">
+ <include name="IDR_MEDIA_ROUTER_MOJOM_JS" file="${mojom_root}\chrome\browser\media\router\mojo\media_router.mojom.js" use_base_dir="false" type="BINDATA" />
+ <include name="IDR_MEDIA_ROUTER_BINDINGS_JS" file="media_router_bindings.js" type="BINDATA" />
+ </if>
+ </includes>
+ <structures>
+ <!-- Extension styles. -->
+ <structure name="IDR_EXTENSION_CSS" file="extension.css" type="chrome_html" flattenhtml="true" />
+ </structures>
+ </release>
+</grit>
diff --git a/chromium/extensions/renderer/resources/greasemonkey_api.js b/chromium/extensions/renderer/resources/greasemonkey_api.js
new file mode 100644
index 00000000000..bc09911571a
--- /dev/null
+++ b/chromium/extensions/renderer/resources/greasemonkey_api.js
@@ -0,0 +1,82 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// -----------------------------------------------------------------------------
+// NOTE: If you change this file you need to touch renderer_resources.grd to
+// have your change take effect.
+// -----------------------------------------------------------------------------
+
+// Partial implementation of the Greasemonkey API, see:
+// http://wiki.greasespot.net/Greasemonkey_Manual:APIs
+
+function GM_addStyle(css) {
+ var parent = document.getElementsByTagName("head")[0];
+ if (!parent) {
+ parent = document.documentElement;
+ }
+ var style = document.createElement("style");
+ style.type = "text/css";
+ var textNode = document.createTextNode(css);
+ style.appendChild(textNode);
+ parent.appendChild(style);
+}
+
+function GM_xmlhttpRequest(details) {
+ function setupEvent(xhr, url, eventName, callback) {
+ xhr[eventName] = function () {
+ var isComplete = xhr.readyState == 4;
+ var responseState = {
+ responseText: xhr.responseText,
+ readyState: xhr.readyState,
+ responseHeaders: isComplete ? xhr.getAllResponseHeaders() : "",
+ status: isComplete ? xhr.status : 0,
+ statusText: isComplete ? xhr.statusText : "",
+ finalUrl: isComplete ? url : ""
+ };
+ callback(responseState);
+ };
+ }
+
+ var xhr = new XMLHttpRequest();
+ var eventNames = ["onload", "onerror", "onreadystatechange"];
+ for (var i = 0; i < eventNames.length; i++ ) {
+ var eventName = eventNames[i];
+ if (eventName in details) {
+ setupEvent(xhr, details.url, eventName, details[eventName]);
+ }
+ }
+
+ xhr.open(details.method, details.url);
+
+ if (details.overrideMimeType) {
+ xhr.overrideMimeType(details.overrideMimeType);
+ }
+ if (details.headers) {
+ for (var header in details.headers) {
+ xhr.setRequestHeader(header, details.headers[header]);
+ }
+ }
+ xhr.send(details.data ? details.data : null);
+}
+
+function GM_openInTab(url) {
+ window.open(url, "");
+}
+
+function GM_log(message) {
+ window.console.log(message);
+}
+
+(function() {
+ function generateGreasemonkeyStub(name) {
+ return function() {
+ console.log("%s is not supported.", name);
+ };
+ }
+
+ var apis = ["GM_getValue", "GM_setValue", "GM_registerMenuCommand"];
+ for (var i = 0, api; api = apis[i]; i++) {
+ window[api] = generateGreasemonkeyStub(api);
+ }
+})();
diff --git a/chromium/extensions/renderer/resources/guest_view/OWNERS b/chromium/extensions/renderer/resources/guest_view/OWNERS
new file mode 100644
index 00000000000..c7f0051d52f
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/OWNERS
@@ -0,0 +1,4 @@
+paulmeyer@chromium.org
+fsamuel@chromium.org
+lazyboy@chromium.org
+wjmaclean@chromium.org
diff --git a/chromium/extensions/renderer/resources/guest_view/app_view/app_view.js b/chromium/extensions/renderer/resources/guest_view/app_view/app_view.js
new file mode 100644
index 00000000000..a164fab1865
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/app_view/app_view.js
@@ -0,0 +1,80 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var DocumentNatives = requireNative('document_natives');
+var GuestViewContainer = require('guestViewContainer').GuestViewContainer;
+var IdGenerator = requireNative('id_generator');
+
+function AppViewImpl(appviewElement) {
+ GuestViewContainer.call(this, appviewElement, 'appview');
+
+ this.app = '';
+ this.data = '';
+}
+
+AppViewImpl.prototype.__proto__ = GuestViewContainer.prototype;
+
+AppViewImpl.VIEW_TYPE = 'AppView';
+
+// Add extra functionality to |this.element|.
+AppViewImpl.setupElement = function(proto) {
+ var apiMethods = [
+ 'connect'
+ ];
+
+ // Forward proto.foo* method calls to AppViewImpl.foo*.
+ GuestViewContainer.forwardApiMethods(proto, apiMethods);
+}
+
+AppViewImpl.prototype.getErrorNode = function() {
+ if (!this.errorNode) {
+ this.errorNode = document.createElement('div');
+ this.errorNode.innerText = 'Unable to connect to app.';
+ this.errorNode.style.position = 'absolute';
+ this.errorNode.style.left = '0px';
+ this.errorNode.style.top = '0px';
+ this.errorNode.style.width = '100%';
+ this.errorNode.style.height = '100%';
+ this.element.shadowRoot.appendChild(this.errorNode);
+ }
+ return this.errorNode;
+};
+
+AppViewImpl.prototype.buildContainerParams = function() {
+ return {
+ 'appId': this.app,
+ 'data': this.data || {}
+ };
+};
+
+AppViewImpl.prototype.connect = function(app, data, callback) {
+ if (!this.elementAttached) {
+ if (callback) {
+ callback(false);
+ }
+ return;
+ }
+
+ this.app = app;
+ this.data = data;
+
+ this.guest.destroy();
+ this.guest.create(this.buildParams(), function() {
+ if (!this.guest.getId()) {
+ var errorMsg = 'Unable to connect to app "' + app + '".';
+ window.console.warn(errorMsg);
+ this.getErrorNode().innerText = errorMsg;
+ if (callback) {
+ callback(false);
+ }
+ return;
+ }
+ this.attachWindow$();
+ if (callback) {
+ callback(true);
+ }
+ }.bind(this));
+};
+
+GuestViewContainer.registerElement(AppViewImpl);
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options.js b/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options.js
new file mode 100644
index 00000000000..70e1158d403
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options.js
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var ExtensionOptionsConstants =
+ require('extensionOptionsConstants').ExtensionOptionsConstants;
+var ExtensionOptionsEvents =
+ require('extensionOptionsEvents').ExtensionOptionsEvents;
+var GuestViewContainer = require('guestViewContainer').GuestViewContainer;
+
+function ExtensionOptionsImpl(extensionoptionsElement) {
+ GuestViewContainer.call(this, extensionoptionsElement, 'extensionoptions');
+
+ new ExtensionOptionsEvents(this);
+};
+
+ExtensionOptionsImpl.prototype.__proto__ = GuestViewContainer.prototype;
+
+ExtensionOptionsImpl.VIEW_TYPE = 'ExtensionOptions';
+
+ExtensionOptionsImpl.prototype.onElementAttached = function() {
+ this.createGuest();
+}
+
+ExtensionOptionsImpl.prototype.buildContainerParams = function() {
+ var params = {};
+ for (var i in this.attributes) {
+ params[i] = this.attributes[i].getValue();
+ }
+ return params;
+};
+
+ExtensionOptionsImpl.prototype.createGuest = function() {
+ // Destroy the old guest if one exists.
+ this.guest.destroy();
+
+ this.guest.create(this.buildParams(), function() {
+ if (!this.guest.getId()) {
+ // Fire a createfailed event here rather than in ExtensionOptionsGuest
+ // because the guest will not be created, and cannot fire an event.
+ var createFailedEvent = new Event('createfailed', { bubbles: true });
+ this.dispatchEvent(createFailedEvent);
+ } else {
+ this.attachWindow$();
+ }
+ }.bind(this));
+};
+
+GuestViewContainer.registerElement(ExtensionOptionsImpl);
+
+// Exports.
+exports.$set('ExtensionOptionsImpl', ExtensionOptionsImpl);
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_attributes.js b/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_attributes.js
new file mode 100644
index 00000000000..5206f1dc713
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_attributes.js
@@ -0,0 +1,43 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This module implements the attributes of the <extensionoptions> tag.
+
+var GuestViewAttributes = require('guestViewAttributes').GuestViewAttributes;
+var ExtensionOptionsConstants =
+ require('extensionOptionsConstants').ExtensionOptionsConstants;
+var ExtensionOptionsImpl = require('extensionOptions').ExtensionOptionsImpl;
+
+// -----------------------------------------------------------------------------
+// ExtensionAttribute object.
+
+// Attribute that handles extension binded to the extensionoptions.
+function ExtensionAttribute(view) {
+ GuestViewAttributes.Attribute.call(
+ this, ExtensionOptionsConstants.ATTRIBUTE_EXTENSION, view);
+}
+
+ExtensionAttribute.prototype.__proto__ =
+ GuestViewAttributes.Attribute.prototype;
+
+ExtensionAttribute.prototype.handleMutation = function(oldValue, newValue) {
+ // Once this attribute has been set, it cannot be unset.
+ if (!newValue && oldValue) {
+ this.setValueIgnoreMutation(oldValue);
+ return;
+ }
+
+ if (!newValue || !this.elementAttached)
+ return;
+
+ this.view.createGuest();
+};
+
+// -----------------------------------------------------------------------------
+
+// Sets up all of the extensionoptions attributes.
+ExtensionOptionsImpl.prototype.setupAttributes = function() {
+ this.attributes[ExtensionOptionsConstants.ATTRIBUTE_EXTENSION] =
+ new ExtensionAttribute(this);
+};
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_constants.js b/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_constants.js
new file mode 100644
index 00000000000..c4d9692aff2
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_constants.js
@@ -0,0 +1,14 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This module contains constants used in extensionoptions.
+
+// Container for the extensionview constants.
+var ExtensionOptionsConstants = {
+ // Attributes.
+ ATTRIBUTE_EXTENSION: 'extension'
+};
+
+exports.$set('ExtensionOptionsConstants',
+ $Object.freeze(ExtensionOptionsConstants));
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_events.js b/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_events.js
new file mode 100644
index 00000000000..20a2f1f8a6e
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_options/extension_options_events.js
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var CreateEvent = require('guestViewEvents').CreateEvent;
+var GuestViewEvents = require('guestViewEvents').GuestViewEvents;
+
+function ExtensionOptionsEvents(extensionOptionsImpl) {
+ GuestViewEvents.call(this, extensionOptionsImpl);
+
+ // |setupEventProperty| is normally called automatically, but the
+ // 'createfailed' event is registered here because the event is fired from
+ // ExtensionOptionsImpl instead of in response to an extension event.
+ this.setupEventProperty('createfailed');
+}
+
+ExtensionOptionsEvents.prototype.__proto__ = GuestViewEvents.prototype;
+
+// A dictionary of <extensionoptions> extension events to be listened for. This
+// dictionary augments |GuestViewEvents.EVENTS| in guest_view_events.js. See the
+// documentation there for details.
+ExtensionOptionsEvents.EVENTS = {
+ 'close': {
+ evt: CreateEvent('extensionOptionsInternal.onClose')
+ },
+ 'load': {
+ evt: CreateEvent('extensionOptionsInternal.onLoad')
+ },
+ 'preferredsizechanged': {
+ evt: CreateEvent('extensionOptionsInternal.onPreferredSizeChanged'),
+ fields:['width', 'height']
+ }
+}
+
+ExtensionOptionsEvents.prototype.getEvents = function() {
+ return ExtensionOptionsEvents.EVENTS;
+};
+
+// Exports.
+exports.$set('ExtensionOptionsEvents', ExtensionOptionsEvents);
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view.js b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view.js
new file mode 100644
index 00000000000..763b5541446
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view.js
@@ -0,0 +1,139 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This module implements the ExtensionView <extensionview>.
+
+var GuestViewContainer = require('guestViewContainer').GuestViewContainer;
+var ExtensionViewConstants =
+ require('extensionViewConstants').ExtensionViewConstants;
+var ExtensionViewEvents = require('extensionViewEvents').ExtensionViewEvents;
+var ExtensionViewInternal =
+ require('extensionViewInternal').ExtensionViewInternal;
+
+function ExtensionViewImpl(extensionviewElement) {
+ GuestViewContainer.call(this, extensionviewElement, 'extensionview');
+
+ // A queue of objects in the order they should be loaded.
+ // Every load call will add the given src, as well as the resolve and reject
+ // functions. Each src will be loaded in the order they were called.
+ this.loadQueue = [];
+
+ // The current src that is loading.
+ // @type {Object<!string, function, function>}
+ this.pendingLoad = null;
+
+ new ExtensionViewEvents(this, this.viewInstanceId);
+}
+
+ExtensionViewImpl.prototype.__proto__ = GuestViewContainer.prototype;
+
+ExtensionViewImpl.VIEW_TYPE = 'ExtensionView';
+
+ExtensionViewImpl.setupElement = function(proto) {
+ var apiMethods = ExtensionViewImpl.getApiMethods();
+
+ GuestViewContainer.forwardApiMethods(proto, apiMethods);
+};
+
+ExtensionViewImpl.prototype.createGuest = function(callback) {
+ this.guest.create(this.buildParams(), function() {
+ this.attachWindow$();
+ callback();
+ }.bind(this));
+};
+
+ExtensionViewImpl.prototype.buildContainerParams = function() {
+ var params = {};
+ for (var i in this.attributes) {
+ params[i] = this.attributes[i].getValue();
+ }
+ return params;
+};
+
+ExtensionViewImpl.prototype.onElementDetached = function() {
+ this.guest.destroy();
+
+ // Reset all attributes.
+ for (var i in this.attributes) {
+ this.attributes[i].setValueIgnoreMutation();
+ }
+};
+
+// Updates src upon loadcommit.
+ExtensionViewImpl.prototype.onLoadCommit = function(url) {
+ this.attributes[ExtensionViewConstants.ATTRIBUTE_SRC].
+ setValueIgnoreMutation(url);
+};
+
+// Loads the next pending src from |loadQueue| to the extensionview.
+ExtensionViewImpl.prototype.loadNextSrc = function() {
+ // If extensionview isn't currently loading a src, load the next src
+ // in |loadQueue|. Otherwise, do nothing.
+ if (!this.pendingLoad && this.loadQueue.length) {
+ this.pendingLoad = this.loadQueue.shift();
+ var src = this.pendingLoad.src;
+ var resolve = this.pendingLoad.resolve;
+ var reject = this.pendingLoad.reject;
+
+ // The extensionview validates the |src| twice, once in |parseSrc| and then
+ // in |loadSrc|. The |src| isn't checked directly in |loadNextSrc| for
+ // validity since the sending renderer (WebUI) is trusted.
+ ExtensionViewInternal.parseSrc(src, function(isSrcValid, extensionId) {
+ // Check if the src is valid.
+ if (!isSrcValid) {
+ reject('Failed to load: src is not valid.');
+ return;
+ }
+
+ // Destroy the current guest and create a new one if extension ID
+ // is different.
+ //
+ // This may happen if the extensionview is loads an extension page, and
+ // is then intended to load a page served from a different extension in
+ // the same part of the WebUI.
+ //
+ // The two calls may look like the following:
+ // extensionview.load('chrome-extension://firstId/page.html');
+ // extensionview.load('chrome-extension://secondId/page.html');
+ // The second time load is called, we destroy the current guest since
+ // we will be loading content from a different extension.
+ if (extensionId !=
+ this.attributes[ExtensionViewConstants.ATTRIBUTE_EXTENSION]
+ .getValue()) {
+ this.guest.destroy();
+
+ // Update the extension and src attributes.
+ this.attributes[ExtensionViewConstants.ATTRIBUTE_EXTENSION]
+ .setValueIgnoreMutation(extensionId);
+ this.attributes[ExtensionViewConstants.ATTRIBUTE_SRC]
+ .setValueIgnoreMutation(src);
+
+ this.createGuest(function() {
+ if (this.guest.getId() <= 0) {
+ reject('Failed to load: guest creation failed.');
+ } else {
+ resolve('Successful load.');
+ }
+ }.bind(this));
+ } else {
+ ExtensionViewInternal.loadSrc(this.guest.getId(), src,
+ function(hasLoadSucceeded) {
+ if (!hasLoadSucceeded) {
+ reject('Failed to load.');
+ } else {
+ // Update the src attribute.
+ this.attributes[ExtensionViewConstants.ATTRIBUTE_SRC]
+ .setValueIgnoreMutation(src);
+ resolve('Successful load.');
+ }
+ }.bind(this));
+ }
+ }.bind(this));
+ }
+};
+
+GuestViewContainer.registerElement(ExtensionViewImpl);
+
+// Exports.
+exports.$set('ExtensionViewImpl', ExtensionViewImpl);
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_api_methods.js b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_api_methods.js
new file mode 100644
index 00000000000..d495973072a
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_api_methods.js
@@ -0,0 +1,45 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This module implements the public-facing API functions for the
+// <extensionview> tag.
+
+var ExtensionViewInternal =
+ require('extensionViewInternal').ExtensionViewInternal;
+var ExtensionViewImpl = require('extensionView').ExtensionViewImpl;
+var ExtensionViewConstants =
+ require('extensionViewConstants').ExtensionViewConstants;
+
+// An array of <extensionview>'s public-facing API methods.
+var EXTENSION_VIEW_API_METHODS = [
+ // Loads the given src into extensionview. Must be called every time the
+ // the extensionview should load a new page. This is the only way to set
+ // the extension and src attributes. Returns a promise indicating whether
+ // or not load was successful.
+ 'load'
+];
+
+// -----------------------------------------------------------------------------
+// Custom API method implementations.
+
+ExtensionViewImpl.prototype.load = function(src) {
+ return new Promise(function(resolve, reject) {
+ this.loadQueue.push({src: src, resolve: resolve, reject: reject});
+ this.loadNextSrc();
+ }.bind(this))
+ .then(function onLoadResolved() {
+ this.pendingLoad = null;
+ this.loadNextSrc();
+ }.bind(this), function onLoadRejected() {
+ this.pendingLoad = null;
+ this.loadNextSrc();
+ reject('Failed to load.');
+ }.bind(this));
+};
+
+// -----------------------------------------------------------------------------
+
+ExtensionViewImpl.getApiMethods = function() {
+ return EXTENSION_VIEW_API_METHODS;
+};
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_attributes.js b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_attributes.js
new file mode 100644
index 00000000000..550fc0f5f3f
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_attributes.js
@@ -0,0 +1,55 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This module implements the attributes of the <extensionview> tag.
+
+var GuestViewAttributes = require('guestViewAttributes').GuestViewAttributes;
+var ExtensionViewConstants =
+ require('extensionViewConstants').ExtensionViewConstants;
+var ExtensionViewImpl = require('extensionView').ExtensionViewImpl;
+var ExtensionViewInternal =
+ require('extensionViewInternal').ExtensionViewInternal;
+
+// -----------------------------------------------------------------------------
+// ExtensionAttribute object.
+
+// Attribute that handles the extension associated with the extensionview.
+function ExtensionAttribute(view) {
+ GuestViewAttributes.ReadOnlyAttribute.call(
+ this, ExtensionViewConstants.ATTRIBUTE_EXTENSION, view);
+}
+
+ExtensionAttribute.prototype.__proto__ =
+ GuestViewAttributes.ReadOnlyAttribute.prototype;
+
+// -----------------------------------------------------------------------------
+// SrcAttribute object.
+
+// Attribute that handles the location and navigation of the extensionview.
+// This is read only because we only want to be able to navigate to a src
+// through the load API call, which checks for URL validity and the extension
+// ID of the new src.
+function SrcAttribute(view) {
+ GuestViewAttributes.ReadOnlyAttribute.call(
+ this, ExtensionViewConstants.ATTRIBUTE_SRC, view);
+}
+
+SrcAttribute.prototype.__proto__ =
+ GuestViewAttributes.ReadOnlyAttribute.prototype;
+
+SrcAttribute.prototype.handleMutation = function(oldValue, newValue) {
+ console.log('src is read only. Use .load(url) to navigate to a new ' +
+ 'extension page.');
+ this.setValueIgnoreMutation(oldValue);
+}
+
+// -----------------------------------------------------------------------------
+
+// Sets up all of the extensionview attributes.
+ExtensionViewImpl.prototype.setupAttributes = function() {
+ this.attributes[ExtensionViewConstants.ATTRIBUTE_EXTENSION] =
+ new ExtensionAttribute(this);
+ this.attributes[ExtensionViewConstants.ATTRIBUTE_SRC] =
+ new SrcAttribute(this);
+};
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_constants.js b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_constants.js
new file mode 100644
index 00000000000..80934cf31e7
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_constants.js
@@ -0,0 +1,14 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This module contains constants used in extensionview.
+
+// Container for the extensionview constants.
+var ExtensionViewConstants = {
+ // Attributes.
+ ATTRIBUTE_EXTENSION: 'extension',
+ ATTRIBUTE_SRC: 'src',
+};
+
+exports.$set('ExtensionViewConstants', $Object.freeze(ExtensionViewConstants));
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_events.js b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_events.js
new file mode 100644
index 00000000000..db674180166
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_events.js
@@ -0,0 +1,32 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Event management for ExtensionView.
+
+var CreateEvent = require('guestViewEvents').CreateEvent;
+var GuestViewEvents = require('guestViewEvents').GuestViewEvents;
+
+function ExtensionViewEvents(extensionViewImpl) {
+ GuestViewEvents.call(this, extensionViewImpl);
+}
+
+ExtensionViewEvents.prototype.__proto__ = GuestViewEvents.prototype;
+
+ExtensionViewEvents.EVENTS = {
+ 'loadcommit': {
+ evt: CreateEvent('extensionViewInternal.onLoadCommit'),
+ handler: 'handleLoadCommitEvent',
+ internal: true
+ }
+};
+
+ExtensionViewEvents.prototype.getEvents = function() {
+ return ExtensionViewEvents.EVENTS;
+};
+
+ExtensionViewEvents.prototype.handleLoadCommitEvent = function(event) {
+ this.view.onLoadCommit(event.url);
+};
+
+exports.$set('ExtensionViewEvents', ExtensionViewEvents);
diff --git a/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_internal.js b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_internal.js
new file mode 100644
index 00000000000..07c6dde8c9b
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/extension_view/extension_view_internal.js
@@ -0,0 +1,7 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+exports.$set(
+ 'ExtensionViewInternal',
+ require('binding').Binding.create('extensionViewInternal').generate());
diff --git a/chromium/extensions/renderer/resources/guest_view/guest_view.js b/chromium/extensions/renderer/resources/guest_view/guest_view.js
new file mode 100644
index 00000000000..1f887a746b3
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/guest_view.js
@@ -0,0 +1,355 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This module implements a wrapper for a guestview that manages its
+// creation, attaching, and destruction.
+
+var CreateEvent = require('guestViewEvents').CreateEvent;
+var EventBindings = require('event_bindings');
+var GuestViewInternal =
+ require('binding').Binding.create('guestViewInternal').generate();
+var GuestViewInternalNatives = requireNative('guest_view_internal');
+
+// Events.
+var ResizeEvent = CreateEvent('guestViewInternal.onResize');
+
+// Error messages.
+var ERROR_MSG_ALREADY_ATTACHED = 'The guest has already been attached.';
+var ERROR_MSG_ALREADY_CREATED = 'The guest has already been created.';
+var ERROR_MSG_INVALID_STATE = 'The guest is in an invalid state.';
+var ERROR_MSG_NOT_ATTACHED = 'The guest is not attached.';
+var ERROR_MSG_NOT_CREATED = 'The guest has not been created.';
+
+// Properties.
+var PROPERTY_ON_RESIZE = 'onresize';
+
+// Contains and hides the internal implementation details of |GuestView|,
+// including maintaining its state and enforcing the proper usage of its API
+// fucntions.
+function GuestViewImpl(guestView, viewType, guestInstanceId) {
+ if (guestInstanceId) {
+ this.id = guestInstanceId;
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
+ } else {
+ this.id = 0;
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_START;
+ }
+ this.actionQueue = [];
+ this.contentWindow = null;
+ this.guestView = guestView;
+ this.pendingAction = null;
+ this.viewType = viewType;
+ this.internalInstanceId = 0;
+
+ this.setupOnResize();
+}
+
+// Possible states.
+GuestViewImpl.GuestState = {
+ GUEST_STATE_START: 0,
+ GUEST_STATE_CREATED: 1,
+ GUEST_STATE_ATTACHED: 2
+};
+
+// Sets up the onResize property on the GuestView.
+GuestViewImpl.prototype.setupOnResize = function() {
+ $Object.defineProperty(this.guestView, PROPERTY_ON_RESIZE, {
+ get: function() {
+ return this[PROPERTY_ON_RESIZE];
+ }.bind(this),
+ set: function(value) {
+ this[PROPERTY_ON_RESIZE] = value;
+ }.bind(this),
+ enumerable: true
+ });
+
+ this.callOnResize = function(e) {
+ if (!this[PROPERTY_ON_RESIZE]) {
+ return;
+ }
+ this[PROPERTY_ON_RESIZE](e);
+ }.bind(this);
+};
+
+// Callback wrapper that is used to call the callback of the pending action (if
+// one exists), and then performs the next action in the queue.
+GuestViewImpl.prototype.handleCallback = function(callback) {
+ if (callback) {
+ callback();
+ }
+ this.pendingAction = null;
+ this.performNextAction();
+};
+
+// Perform the next action in the queue, if one exists.
+GuestViewImpl.prototype.performNextAction = function() {
+ // Make sure that there is not already an action in progress, and that there
+ // exists a queued action to perform.
+ if (!this.pendingAction && this.actionQueue.length) {
+ this.pendingAction = this.actionQueue.shift();
+ this.pendingAction();
+ }
+};
+
+// Check the current state to see if the proposed action is valid. Returns false
+// if invalid.
+GuestViewImpl.prototype.checkState = function(action) {
+ // Create an error prefix based on the proposed action.
+ var errorPrefix = 'Error calling ' + action + ': ';
+
+ // Check that the current state is valid.
+ if (!(this.state >= 0 && this.state <= 2)) {
+ window.console.error(errorPrefix + ERROR_MSG_INVALID_STATE);
+ return false;
+ }
+
+ // Map of possible errors for each action. For each action, the errors are
+ // listed for states in the order: GUEST_STATE_START, GUEST_STATE_CREATED,
+ // GUEST_STATE_ATTACHED.
+ var errors = {
+ 'attach': [ERROR_MSG_NOT_CREATED, null, ERROR_MSG_ALREADY_ATTACHED],
+ 'create': [null, ERROR_MSG_ALREADY_CREATED, ERROR_MSG_ALREADY_CREATED],
+ 'destroy': [null, null, null],
+ 'detach': [ERROR_MSG_NOT_ATTACHED, ERROR_MSG_NOT_ATTACHED, null],
+ 'setSize': [ERROR_MSG_NOT_CREATED, null, null]
+ };
+
+ // Check that the proposed action is a real action.
+ if (errors[action] == undefined) {
+ window.console.error(errorPrefix + ERROR_MSG_INVALID_ACTION);
+ return false;
+ }
+
+ // Report the error if the proposed action is found to be invalid for the
+ // current state.
+ var error;
+ if (error = errors[action][this.state]) {
+ window.console.error(errorPrefix + error);
+ return false;
+ }
+
+ return true;
+};
+
+// Returns a wrapper function for |func| with a weak reference to |this|. This
+// implementation of weakWrapper() requires a provided |viewInstanceId| since
+// GuestViewImpl does not store this ID.
+GuestViewImpl.prototype.weakWrapper = function(func, viewInstanceId) {
+ return function() {
+ var view = GuestViewInternalNatives.GetViewFromID(viewInstanceId);
+ if (view && view.guest) {
+ return $Function.apply(func,
+ privates(view.guest).internal,
+ $Array.slice(arguments));
+ }
+ };
+};
+
+// Internal implementation of attach().
+GuestViewImpl.prototype.attachImpl$ = function(
+ internalInstanceId, viewInstanceId, attachParams, callback) {
+ // Check the current state.
+ if (!this.checkState('attach')) {
+ this.handleCallback(callback);
+ return;
+ }
+
+ // Callback wrapper function to store the contentWindow from the attachGuest()
+ // callback, handle potential attaching failure, register an automatic detach,
+ // and advance the queue.
+ var callbackWrapper = function(callback, contentWindow) {
+ // Check if attaching failed.
+ if (!contentWindow) {
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
+ this.internalInstanceId = 0;
+ } else {
+ // Only update the contentWindow if attaching is successful.
+ this.contentWindow = contentWindow;
+ }
+
+ this.handleCallback(callback);
+ };
+
+ attachParams['instanceId'] = viewInstanceId;
+ GuestViewInternalNatives.AttachGuest(internalInstanceId,
+ this.id,
+ attachParams,
+ callbackWrapper.bind(this, callback));
+
+ this.internalInstanceId = internalInstanceId;
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_ATTACHED;
+
+ // Detach automatically when the container is destroyed.
+ GuestViewInternalNatives.RegisterDestructionCallback(
+ internalInstanceId, this.weakWrapper(function() {
+ if (this.state != GuestViewImpl.GuestState.GUEST_STATE_ATTACHED ||
+ this.internalInstanceId != internalInstanceId) {
+ return;
+ }
+
+ this.internalInstanceId = 0;
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
+ }, viewInstanceId));
+};
+
+// Internal implementation of create().
+GuestViewImpl.prototype.createImpl$ = function(createParams, callback) {
+ // Check the current state.
+ if (!this.checkState('create')) {
+ this.handleCallback(callback);
+ return;
+ }
+
+ // Callback wrapper function to store the guestInstanceId from the
+ // createGuest() callback, handle potential creation failure, and advance the
+ // queue.
+ var callbackWrapper = function(callback, guestInfo) {
+ this.id = guestInfo.id;
+ this.contentWindow =
+ GuestViewInternalNatives.GetContentWindow(guestInfo.contentWindowId);
+
+ // Check if creation failed.
+ if (this.id === 0) {
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_START;
+ this.contentWindow = null;
+ }
+
+ ResizeEvent.addListener(this.callOnResize, {instanceId: this.id});
+ this.handleCallback(callback);
+ };
+
+ this.sendCreateRequest(createParams, callbackWrapper.bind(this, callback));
+
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
+};
+
+GuestViewImpl.prototype.sendCreateRequest = function(
+ createParams, boundCallback) {
+ GuestViewInternal.createGuest(this.viewType, createParams, boundCallback);
+};
+
+// Internal implementation of destroy().
+GuestViewImpl.prototype.destroyImpl = function(callback) {
+ // Check the current state.
+ if (!this.checkState('destroy')) {
+ this.handleCallback(callback);
+ return;
+ }
+
+ if (this.state == GuestViewImpl.GuestState.GUEST_STATE_START) {
+ // destroy() does nothing in this case.
+ this.handleCallback(callback);
+ return;
+ }
+
+ // If this guest is attached, then detach it first.
+ if (!!this.internalInstanceId) {
+ GuestViewInternalNatives.DetachGuest(this.internalInstanceId);
+ }
+
+ GuestViewInternal.destroyGuest(this.id,
+ this.handleCallback.bind(this, callback));
+
+ // Reset the state of the destroyed guest;
+ this.contentWindow = null;
+ this.id = 0;
+ this.internalInstanceId = 0;
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_START;
+ if (ResizeEvent.hasListener(this.callOnResize)) {
+ ResizeEvent.removeListener(this.callOnResize);
+ }
+};
+
+// Internal implementation of detach().
+GuestViewImpl.prototype.detachImpl = function(callback) {
+ // Check the current state.
+ if (!this.checkState('detach')) {
+ this.handleCallback(callback);
+ return;
+ }
+
+ GuestViewInternalNatives.DetachGuest(
+ this.internalInstanceId,
+ this.handleCallback.bind(this, callback));
+
+ this.internalInstanceId = 0;
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
+};
+
+// Internal implementation of setSize().
+GuestViewImpl.prototype.setSizeImpl = function(sizeParams, callback) {
+ // Check the current state.
+ if (!this.checkState('setSize')) {
+ this.handleCallback(callback);
+ return;
+ }
+
+ GuestViewInternal.setSize(this.id, sizeParams,
+ this.handleCallback.bind(this, callback));
+};
+
+// The exposed interface to a guestview. Exposes in its API the functions
+// attach(), create(), destroy(), and getId(). All other implementation details
+// are hidden.
+function GuestView(viewType, guestInstanceId) {
+ privates(this).internal = new GuestViewImpl(this, viewType, guestInstanceId);
+}
+
+// Attaches the guestview to the container with ID |internalInstanceId|.
+GuestView.prototype.attach = function(
+ internalInstanceId, viewInstanceId, attachParams, callback) {
+ var internal = privates(this).internal;
+ internal.actionQueue.push(internal.attachImpl$.bind(
+ internal, internalInstanceId, viewInstanceId, attachParams, callback));
+ internal.performNextAction();
+};
+
+// Creates the guestview.
+GuestView.prototype.create = function(createParams, callback) {
+ var internal = privates(this).internal;
+ internal.actionQueue.push(internal.createImpl$.bind(
+ internal, createParams, callback));
+ internal.performNextAction();
+};
+
+// Destroys the guestview. Nothing can be done with the guestview after it has
+// been destroyed.
+GuestView.prototype.destroy = function(callback) {
+ var internal = privates(this).internal;
+ internal.actionQueue.push(internal.destroyImpl.bind(internal, callback));
+ internal.performNextAction();
+};
+
+// Detaches the guestview from its container.
+// Note: This is not currently used.
+GuestView.prototype.detach = function(callback) {
+ var internal = privates(this).internal;
+ internal.actionQueue.push(internal.detachImpl.bind(internal, callback));
+ internal.performNextAction();
+};
+
+// Adjusts the guestview's sizing parameters.
+GuestView.prototype.setSize = function(sizeParams, callback) {
+ var internal = privates(this).internal;
+ internal.actionQueue.push(internal.setSizeImpl.bind(
+ internal, sizeParams, callback));
+ internal.performNextAction();
+};
+
+// Returns the contentWindow for this guestview.
+GuestView.prototype.getContentWindow = function() {
+ var internal = privates(this).internal;
+ return internal.contentWindow;
+};
+
+// Returns the ID for this guestview.
+GuestView.prototype.getId = function() {
+ var internal = privates(this).internal;
+ return internal.id;
+};
+
+// Exports
+exports.$set('GuestView', GuestView);
+exports.$set('GuestViewImpl', GuestViewImpl);
+exports.$set('ResizeEvent', ResizeEvent);
diff --git a/chromium/extensions/renderer/resources/guest_view/guest_view_attributes.js b/chromium/extensions/renderer/resources/guest_view/guest_view_attributes.js
new file mode 100644
index 00000000000..6c7f711ef6c
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/guest_view_attributes.js
@@ -0,0 +1,142 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This module implements the base attributes of the GuestView tags.
+
+// -----------------------------------------------------------------------------
+// Attribute object.
+
+// Default implementation of a GuestView attribute.
+function Attribute(name, view) {
+ this.dirty = false;
+ this.ignoreMutation = false;
+ this.name = name;
+ this.view = view;
+
+ this.defineProperty();
+}
+
+// Retrieves and returns the attribute's value.
+Attribute.prototype.getValue = function() {
+ return this.view.element.getAttribute(this.name) || '';
+};
+
+// Retrieves and returns the attribute's value if it has been dirtied since
+// the last time this method was called. Returns null otherwise.
+Attribute.prototype.getValueIfDirty = function() {
+ if (!this.dirty)
+ return null;
+ this.dirty = false;
+ return this.getValue();
+};
+
+// Sets the attribute's value.
+Attribute.prototype.setValue = function(value) {
+ this.view.element.setAttribute(this.name, value || '');
+};
+
+// Changes the attribute's value without triggering its mutation handler.
+Attribute.prototype.setValueIgnoreMutation = function(value) {
+ this.ignoreMutation = true;
+ this.setValue(value);
+ this.ignoreMutation = false;
+};
+
+// Defines this attribute as a property on the view's element.
+Attribute.prototype.defineProperty = function() {
+ $Object.defineProperty(this.view.element, this.name, {
+ get: function() {
+ return this.getValue();
+ }.bind(this),
+ set: function(value) {
+ this.setValue(value);
+ }.bind(this),
+ enumerable: true
+ });
+};
+
+// Called when the attribute's value changes.
+Attribute.prototype.maybeHandleMutation = function(oldValue, newValue) {
+ if (this.ignoreMutation)
+ return;
+
+ this.dirty = true;
+ this.handleMutation(oldValue, newValue);
+};
+
+// Called when a change that isn't ignored occurs to the attribute's value.
+Attribute.prototype.handleMutation = function(oldValue, newValue) {};
+
+// Called when the view's element is attached to the DOM tree.
+Attribute.prototype.attach = function() {};
+
+// Called when the view's element is detached from the DOM tree.
+Attribute.prototype.detach = function() {};
+
+// -----------------------------------------------------------------------------
+// BooleanAttribute object.
+
+// An attribute that is treated as a Boolean.
+function BooleanAttribute(name, view) {
+ Attribute.call(this, name, view);
+}
+
+BooleanAttribute.prototype.__proto__ = Attribute.prototype;
+
+BooleanAttribute.prototype.getValue = function() {
+ return this.view.element.hasAttribute(this.name);
+};
+
+BooleanAttribute.prototype.setValue = function(value) {
+ if (!value) {
+ this.view.element.removeAttribute(this.name);
+ } else {
+ this.view.element.setAttribute(this.name, '');
+ }
+};
+
+// -----------------------------------------------------------------------------
+// IntegerAttribute object.
+
+// An attribute that is treated as an integer.
+function IntegerAttribute(name, view) {
+ Attribute.call(this, name, view);
+}
+
+IntegerAttribute.prototype.__proto__ = Attribute.prototype;
+
+IntegerAttribute.prototype.getValue = function() {
+ return parseInt(this.view.element.getAttribute(this.name)) || 0;
+};
+
+IntegerAttribute.prototype.setValue = function(value) {
+ this.view.element.setAttribute(this.name, parseInt(value) || 0);
+};
+
+// -----------------------------------------------------------------------------
+// ReadOnlyAttribute object.
+
+// An attribute that cannot be changed (externally). The only way to set it
+// internally is via |setValueIgnoreMutation|.
+function ReadOnlyAttribute(name, view) {
+ Attribute.call(this, name, view);
+}
+
+ReadOnlyAttribute.prototype.__proto__ = Attribute.prototype;
+
+ReadOnlyAttribute.prototype.handleMutation = function(oldValue, newValue) {
+ this.setValueIgnoreMutation(oldValue);
+}
+
+// -----------------------------------------------------------------------------
+
+var GuestViewAttributes = {
+ Attribute: Attribute,
+ BooleanAttribute: BooleanAttribute,
+ IntegerAttribute: IntegerAttribute,
+ ReadOnlyAttribute: ReadOnlyAttribute
+};
+
+// Exports.
+exports.$set('GuestViewAttributes', GuestViewAttributes);
diff --git a/chromium/extensions/renderer/resources/guest_view/guest_view_container.js b/chromium/extensions/renderer/resources/guest_view/guest_view_container.js
new file mode 100644
index 00000000000..acba9fb7b0f
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/guest_view_container.js
@@ -0,0 +1,311 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This module implements the shared functionality for different guestview
+// containers, such as web_view, app_view, etc.
+
+var DocumentNatives = requireNative('document_natives');
+var GuestView = require('guestView').GuestView;
+var GuestViewInternalNatives = requireNative('guest_view_internal');
+var IdGenerator = requireNative('id_generator');
+var MessagingNatives = requireNative('messaging_natives');
+
+function GuestViewContainer(element, viewType) {
+ privates(element).internal = this;
+ this.attributes = {};
+ this.element = element;
+ this.elementAttached = false;
+ this.viewInstanceId = IdGenerator.GetNextId();
+ this.viewType = viewType;
+
+ this.setupGuestProperty();
+ this.guest = new GuestView(viewType);
+ this.setupAttributes();
+
+ privates(this).internalElement = this.createInternalElement$();
+ this.setupFocusPropagation();
+ var shadowRoot = this.element.createShadowRoot();
+ shadowRoot.appendChild(privates(this).internalElement);
+
+ GuestViewInternalNatives.RegisterView(this.viewInstanceId, this, viewType);
+}
+
+// Forward public API methods from |proto| to their internal implementations.
+GuestViewContainer.forwardApiMethods = function(proto, apiMethods) {
+ var createProtoHandler = function(m) {
+ return function(var_args) {
+ var internal = privates(this).internal;
+ return $Function.apply(internal[m], internal, arguments);
+ };
+ };
+ for (var i = 0; apiMethods[i]; ++i) {
+ proto[apiMethods[i]] = createProtoHandler(apiMethods[i]);
+ }
+};
+
+// Registers the browserplugin and guestview as custom elements once the
+// document has loaded.
+GuestViewContainer.registerElement = function(guestViewContainerType) {
+ var useCapture = true;
+ window.addEventListener('readystatechange', function listener(event) {
+ if (document.readyState == 'loading')
+ return;
+
+ registerInternalElement(guestViewContainerType.VIEW_TYPE.toLowerCase());
+ registerGuestViewElement(guestViewContainerType);
+ window.removeEventListener(event.type, listener, useCapture);
+ }, useCapture);
+};
+
+// Create the 'guest' property to track new GuestViews and always listen for
+// their resizes.
+GuestViewContainer.prototype.setupGuestProperty = function() {
+ $Object.defineProperty(this, 'guest', {
+ get: function() {
+ return privates(this).guest;
+ }.bind(this),
+ set: function(value) {
+ privates(this).guest = value;
+ if (!value) {
+ return;
+ }
+ privates(this).guest.onresize = function(e) {
+ // Dispatch the 'contentresize' event.
+ var contentResizeEvent = new Event('contentresize', { bubbles: true });
+ contentResizeEvent.oldWidth = e.oldWidth;
+ contentResizeEvent.oldHeight = e.oldHeight;
+ contentResizeEvent.newWidth = e.newWidth;
+ contentResizeEvent.newHeight = e.newHeight;
+ this.dispatchEvent(contentResizeEvent);
+ }.bind(this);
+ }.bind(this),
+ enumerable: true
+ });
+};
+
+GuestViewContainer.prototype.createInternalElement$ = function() {
+ // We create BrowserPlugin as a custom element in order to observe changes
+ // to attributes synchronously.
+ var browserPluginElement =
+ new GuestViewContainer[this.viewType + 'BrowserPlugin']();
+ privates(browserPluginElement).internal = this;
+ return browserPluginElement;
+};
+
+GuestViewContainer.prototype.setupFocusPropagation = function() {
+ if (!this.element.hasAttribute('tabIndex')) {
+ // GuestViewContainer needs a tabIndex in order to be focusable.
+ // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute
+ // to allow GuestViewContainer to be focusable.
+ // See http://crbug.com/231664.
+ this.element.setAttribute('tabIndex', -1);
+ }
+ this.element.addEventListener('focus', this.weakWrapper(function(e) {
+ // Focus the BrowserPlugin when the GuestViewContainer takes focus.
+ privates(this).internalElement.focus();
+ }));
+ this.element.addEventListener('blur', this.weakWrapper(function(e) {
+ // Blur the BrowserPlugin when the GuestViewContainer loses focus.
+ privates(this).internalElement.blur();
+ }));
+};
+
+GuestViewContainer.prototype.focus = function() {
+ // Focus the internal element when focus() is called on the GuestView element.
+ privates(this).internalElement.focus();
+}
+
+GuestViewContainer.prototype.attachWindow$ = function() {
+ if (!this.internalInstanceId) {
+ return true;
+ }
+
+ this.guest.attach(this.internalInstanceId,
+ this.viewInstanceId,
+ this.buildParams());
+ return true;
+};
+
+GuestViewContainer.prototype.makeGCOwnContainer = function(internalInstanceId) {
+ MessagingNatives.BindToGC(this, function() {
+ GuestViewInternalNatives.DestroyContainer(internalInstanceId);
+ }, -1);
+};
+
+GuestViewContainer.prototype.onInternalInstanceId = function(
+ internalInstanceId) {
+ this.internalInstanceId = internalInstanceId;
+ this.makeGCOwnContainer(this.internalInstanceId);
+
+ // Track when the element resizes using the element resize callback.
+ GuestViewInternalNatives.RegisterElementResizeCallback(
+ this.internalInstanceId, this.weakWrapper(this.onElementResize));
+
+ if (!this.guest.getId()) {
+ return;
+ }
+ this.guest.attach(this.internalInstanceId,
+ this.viewInstanceId,
+ this.buildParams());
+};
+
+GuestViewContainer.prototype.handleInternalElementAttributeMutation =
+ function(name, oldValue, newValue) {
+ if (name == 'internalinstanceid' && !oldValue && !!newValue) {
+ privates(this).internalElement.removeAttribute('internalinstanceid');
+ this.onInternalInstanceId(parseInt(newValue));
+ }
+};
+
+GuestViewContainer.prototype.onElementResize = function(newWidth, newHeight) {
+ if (!this.guest.getId())
+ return;
+ this.guest.setSize({normal: {width: newWidth, height: newHeight}});
+};
+
+GuestViewContainer.prototype.buildParams = function() {
+ var params = this.buildContainerParams();
+ params['instanceId'] = this.viewInstanceId;
+ // When the GuestViewContainer is not participating in layout (display:none)
+ // then getBoundingClientRect() would report a width and height of 0.
+ // However, in the case where the GuestViewContainer has a fixed size we can
+ // use that value to initially size the guest so as to avoid a relayout of the
+ // on display:block.
+ var css = window.getComputedStyle(this.element, null);
+ var elementRect = this.element.getBoundingClientRect();
+ params['elementWidth'] = parseInt(elementRect.width) ||
+ parseInt(css.getPropertyValue('width'));
+ params['elementHeight'] = parseInt(elementRect.height) ||
+ parseInt(css.getPropertyValue('height'));
+ return params;
+};
+
+GuestViewContainer.prototype.dispatchEvent = function(event) {
+ return this.element.dispatchEvent(event);
+}
+
+// Returns a wrapper function for |func| with a weak reference to |this|.
+GuestViewContainer.prototype.weakWrapper = function(func) {
+ var viewInstanceId = this.viewInstanceId;
+ return function() {
+ var view = GuestViewInternalNatives.GetViewFromID(viewInstanceId);
+ if (view) {
+ return $Function.apply(func, view, $Array.slice(arguments));
+ }
+ };
+};
+
+// Implemented by the specific view type, if needed.
+GuestViewContainer.prototype.buildContainerParams = function() { return {}; };
+GuestViewContainer.prototype.willAttachElement = function() {};
+GuestViewContainer.prototype.onElementAttached = function() {};
+GuestViewContainer.prototype.onElementDetached = function() {};
+GuestViewContainer.prototype.setupAttributes = function() {};
+
+// Registers the browser plugin <object> custom element. |viewType| is the
+// name of the specific guestview container (e.g. 'webview').
+function registerInternalElement(viewType) {
+ var proto = $Object.create(HTMLElement.prototype);
+
+ proto.createdCallback = function() {
+ this.setAttribute('type', 'application/browser-plugin');
+ this.setAttribute('id', 'browser-plugin-' + IdGenerator.GetNextId());
+ this.style.width = '100%';
+ this.style.height = '100%';
+ };
+
+ proto.attachedCallback = function() {
+ // Load the plugin immediately.
+ var unused = this.nonExistentAttribute;
+ };
+
+ proto.attributeChangedCallback = function(name, oldValue, newValue) {
+ var internal = privates(this).internal;
+ if (!internal) {
+ return;
+ }
+ internal.handleInternalElementAttributeMutation(name, oldValue, newValue);
+ };
+
+ GuestViewContainer[viewType + 'BrowserPlugin'] =
+ DocumentNatives.RegisterElement(viewType + 'browserplugin',
+ {extends: 'object', prototype: proto});
+
+ delete proto.createdCallback;
+ delete proto.attachedCallback;
+ delete proto.detachedCallback;
+ delete proto.attributeChangedCallback;
+};
+
+// Registers the guestview container as a custom element.
+// |guestViewContainerType| is the type of guestview container
+// (e.g. WebViewImpl).
+function registerGuestViewElement(guestViewContainerType) {
+ var proto = $Object.create(HTMLElement.prototype);
+
+ proto.createdCallback = function() {
+ new guestViewContainerType(this);
+ };
+
+ proto.attachedCallback = function() {
+ var internal = privates(this).internal;
+ if (!internal) {
+ return;
+ }
+ internal.elementAttached = true;
+ internal.willAttachElement();
+ internal.onElementAttached();
+ };
+
+ proto.attributeChangedCallback = function(name, oldValue, newValue) {
+ var internal = privates(this).internal;
+ if (!internal || !internal.attributes[name]) {
+ return;
+ }
+
+ // Let the changed attribute handle its own mutation.
+ internal.attributes[name].maybeHandleMutation(oldValue, newValue);
+ };
+
+ proto.detachedCallback = function() {
+ var internal = privates(this).internal;
+ if (!internal) {
+ return;
+ }
+ internal.elementAttached = false;
+ internal.internalInstanceId = 0;
+ internal.guest.destroy();
+ internal.onElementDetached();
+ };
+
+ // Override |focus| to let |internal| handle it.
+ proto.focus = function() {
+ var internal = privates(this).internal;
+ if (!internal) {
+ return;
+ }
+ internal.focus();
+ };
+
+ // Let the specific view type add extra functionality to its custom element
+ // through |proto|.
+ if (guestViewContainerType.setupElement) {
+ guestViewContainerType.setupElement(proto);
+ }
+
+ window[guestViewContainerType.VIEW_TYPE] =
+ DocumentNatives.RegisterElement(
+ guestViewContainerType.VIEW_TYPE.toLowerCase(),
+ {prototype: proto});
+
+ // Delete the callbacks so developers cannot call them and produce unexpected
+ // behavior.
+ delete proto.createdCallback;
+ delete proto.attachedCallback;
+ delete proto.detachedCallback;
+ delete proto.attributeChangedCallback;
+}
+
+// Exports.
+exports.$set('GuestViewContainer', GuestViewContainer);
diff --git a/chromium/extensions/renderer/resources/guest_view/guest_view_deny.js b/chromium/extensions/renderer/resources/guest_view/guest_view_deny.js
new file mode 100644
index 00000000000..395594e77ae
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/guest_view_deny.js
@@ -0,0 +1,58 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This module implements the registration of guestview elements when
+// permissions are not available. These elements exist only to provide a useful
+// error message when developers attempt to use them.
+
+var DocumentNatives = requireNative('document_natives');
+var GuestViewContainer = require('guestViewContainer').GuestViewContainer;
+
+var ERROR_MESSAGE = 'You do not have permission to use the %1 element.' +
+ ' Be sure to declare the "%1" permission in your manifest file.';
+
+// A list of view types that will have custom elements registered if they are
+// not already registered by the time this module is loaded.
+var VIEW_TYPES = [
+ 'AppView',
+ 'ExtensionOptions',
+ 'ExtensionView',
+ 'WebView'
+];
+
+// Registers a GuestView custom element.
+function registerGuestViewElement(viewType) {
+ var proto = Object.create(HTMLElement.prototype);
+
+ proto.createdCallback = function() {
+ window.console.error(ERROR_MESSAGE.replace(/%1/g, viewType.toLowerCase()));
+ };
+
+ window[viewType] = DocumentNatives.RegisterElement(viewType.toLowerCase(),
+ {prototype: proto});
+
+ // Delete the callbacks so developers cannot call them and produce unexpected
+ // behavior.
+ delete proto.createdCallback;
+ delete proto.attachedCallback;
+ delete proto.detachedCallback;
+ delete proto.attributeChangedCallback;
+}
+
+var useCapture = true;
+window.addEventListener('readystatechange', function listener(event) {
+ if (document.readyState == 'loading')
+ return;
+
+ for (var i = 0; i != VIEW_TYPES.length; ++i) {
+ // Register the error-providing custom element only for those view types
+ // that have not already been registered. Since this module is always loaded
+ // last, all the view types that are available (i.e. have the proper
+ // permissions) will have already been registered on |window|.
+ if (!window[VIEW_TYPES[i]])
+ registerGuestViewElement(VIEW_TYPES[i]);
+ }
+
+ window.removeEventListener(event.type, listener, useCapture);
+}, useCapture);
diff --git a/chromium/extensions/renderer/resources/guest_view/guest_view_events.js b/chromium/extensions/renderer/resources/guest_view/guest_view_events.js
new file mode 100644
index 00000000000..e3ccde1a6c6
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/guest_view_events.js
@@ -0,0 +1,178 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Event management for GuestViewContainers.
+
+var EventBindings = require('event_bindings');
+var GuestViewInternalNatives = requireNative('guest_view_internal');
+var MessagingNatives = requireNative('messaging_natives');
+
+var CreateEvent = function(name) {
+ var eventOpts = {supportsListeners: true, supportsFilters: true};
+ return new EventBindings.Event(name, undefined, eventOpts);
+};
+
+function GuestViewEvents(view) {
+ view.events = this;
+
+ this.view = view;
+ this.on = {};
+
+ // |setupEventProperty| is normally called automatically, but these events are
+ // are registered here because they are dispatched from GuestViewContainer
+ // instead of in response to extension events.
+ this.setupEventProperty('contentresize');
+ this.setupEventProperty('resize');
+ this.setupEvents();
+}
+
+// |GuestViewEvents.EVENTS| is a dictionary of extension events to be listened
+// for, which specifies how each event should be handled. The events are
+// organized by name, and by default will be dispatched as DOM events with
+// the same name.
+// |cancelable| (default: false) specifies whether the DOM event's default
+// behavior can be canceled. If the default action associated with the event
+// is prevented, then its dispatch function will return false in its event
+// handler. The event must have a specified |handler| for this to be
+// meaningful.
+// |evt| specifies a descriptor object for the extension event. An event
+// listener will be attached to this descriptor.
+// |fields| (default: none) specifies the public-facing fields in the DOM event
+// that are accessible to developers.
+// |handler| specifies the name of a handler function to be called each time
+// that extension event is caught by its event listener. The DOM event
+// should be dispatched within this handler function (if desired). With no
+// handler function, the DOM event will be dispatched by default each time
+// the extension event is caught.
+// |internal| (default: false) specifies that the event will not be dispatched
+// as a DOM event, and will also not appear as an on* property on the view’s
+// element. A |handler| should be specified for all internal events, and
+// |fields| and |cancelable| should be left unspecified (as they are only
+// meaningful for DOM events).
+GuestViewEvents.EVENTS = {};
+
+// Attaches |listener| onto the event descriptor object |evt|, and registers it
+// to be removed once this GuestViewEvents object is garbage collected.
+GuestViewEvents.prototype.addScopedListener = function(
+ evt, listener, listenerOpts) {
+ this.listenersToBeRemoved.push({ 'evt': evt, 'listener': listener });
+ evt.addListener(listener, listenerOpts);
+};
+
+// Sets up the handling of events.
+GuestViewEvents.prototype.setupEvents = function() {
+ // An array of registerd event listeners that should be removed when this
+ // GuestViewEvents is garbage collected.
+ this.listenersToBeRemoved = [];
+ MessagingNatives.BindToGC(this, function(listenersToBeRemoved) {
+ for (var i = 0; i != listenersToBeRemoved.length; ++i) {
+ listenersToBeRemoved[i].evt.removeListener(
+ listenersToBeRemoved[i].listener);
+ listenersToBeRemoved[i] = null;
+ }
+ }.bind(undefined, this.listenersToBeRemoved), -1 /* portId */);
+
+ // Set up the GuestView events.
+ for (var eventName in GuestViewEvents.EVENTS) {
+ this.setupEvent(eventName, GuestViewEvents.EVENTS[eventName]);
+ }
+
+ // Set up the derived view's events.
+ var events = this.getEvents();
+ for (var eventName in events) {
+ this.setupEvent(eventName, events[eventName]);
+ }
+};
+
+// Sets up the handling of the |eventName| event.
+GuestViewEvents.prototype.setupEvent = function(eventName, eventInfo) {
+ if (!eventInfo.internal) {
+ this.setupEventProperty(eventName);
+ }
+
+ var listenerOpts = { instanceId: this.view.viewInstanceId };
+ if (eventInfo.handler) {
+ this.addScopedListener(eventInfo.evt, this.weakWrapper(function(e) {
+ this[eventInfo.handler](e, eventName);
+ }), listenerOpts);
+ return;
+ }
+
+ // Internal events are not dispatched as DOM events.
+ if (eventInfo.internal) {
+ return;
+ }
+
+ this.addScopedListener(eventInfo.evt, this.weakWrapper(function(e) {
+ var domEvent = this.makeDomEvent(e, eventName);
+ this.view.dispatchEvent(domEvent);
+ }), listenerOpts);
+};
+
+// Constructs a DOM event based on the info for the |eventName| event provided
+// in either |GuestViewEvents.EVENTS| or getEvents().
+GuestViewEvents.prototype.makeDomEvent = function(event, eventName) {
+ var eventInfo =
+ GuestViewEvents.EVENTS[eventName] || this.getEvents()[eventName];
+
+ // Internal events are not dispatched as DOM events.
+ if (eventInfo.internal) {
+ return null;
+ }
+
+ var details = { bubbles: true };
+ if (eventInfo.cancelable) {
+ details.cancelable = true;
+ }
+ var domEvent = new Event(eventName, details);
+ if (eventInfo.fields) {
+ $Array.forEach(eventInfo.fields, function(field) {
+ if (event[field] !== undefined) {
+ domEvent[field] = event[field];
+ }
+ }.bind(this));
+ }
+
+ return domEvent;
+};
+
+// Adds an 'on<event>' property on the view, which can be used to set/unset
+// an event handler.
+GuestViewEvents.prototype.setupEventProperty = function(eventName) {
+ var propertyName = 'on' + eventName.toLowerCase();
+ $Object.defineProperty(this.view.element, propertyName, {
+ get: function() {
+ return this.on[propertyName];
+ }.bind(this),
+ set: function(value) {
+ if (this.on[propertyName]) {
+ this.view.element.removeEventListener(eventName, this.on[propertyName]);
+ }
+ this.on[propertyName] = value;
+ if (value) {
+ this.view.element.addEventListener(eventName, value);
+ }
+ }.bind(this),
+ enumerable: true
+ });
+};
+
+// returns a wrapper for |func| with a weak reference to |this|.
+GuestViewEvents.prototype.weakWrapper = function(func) {
+ var viewInstanceId = this.view.viewInstanceId;
+ return function() {
+ var view = GuestViewInternalNatives.GetViewFromID(viewInstanceId);
+ if (!view) {
+ return;
+ }
+ return $Function.apply(func, view.events, $Array.slice(arguments));
+ };
+};
+
+// Implemented by the derived event manager, if one exists.
+GuestViewEvents.prototype.getEvents = function() { return {}; };
+
+// Exports.
+exports.$set('GuestViewEvents', GuestViewEvents);
+exports.$set('CreateEvent', CreateEvent);
diff --git a/chromium/extensions/renderer/resources/guest_view/guest_view_iframe.js b/chromium/extensions/renderer/resources/guest_view/guest_view_iframe.js
new file mode 100644
index 00000000000..a73de791a98
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/guest_view_iframe.js
@@ -0,0 +1,108 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// --site-per-process overrides for guest_view.js.
+
+var GuestView = require('guestView').GuestView;
+var GuestViewImpl = require('guestView').GuestViewImpl;
+var GuestViewInternalNatives = requireNative('guest_view_internal');
+var ResizeEvent = require('guestView').ResizeEvent;
+
+var getIframeContentWindow = function(viewInstanceId) {
+ var view = GuestViewInternalNatives.GetViewFromID(viewInstanceId);
+ if (!view)
+ return null;
+
+ var internalIframeElement = privates(view).internalElement;
+ if (internalIframeElement)
+ return internalIframeElement.contentWindow;
+
+ return null;
+};
+
+// Internal implementation of attach().
+GuestViewImpl.prototype.attachImpl$ = function(
+ internalInstanceId, viewInstanceId, attachParams, callback) {
+ var view = GuestViewInternalNatives.GetViewFromID(viewInstanceId);
+ if (!view.elementAttached) {
+ // Defer the attachment until the <webview> element is attached.
+ view.deferredAttachCallback = this.attachImpl$.bind(
+ this, internalInstanceId, viewInstanceId, attachParams, callback);
+ return;
+ };
+
+ // Check the current state.
+ if (!this.checkState('attach')) {
+ this.handleCallback(callback);
+ return;
+ }
+
+ // Callback wrapper function to store the contentWindow from the attachGuest()
+ // callback, handle potential attaching failure, register an automatic detach,
+ // and advance the queue.
+ var callbackWrapper = function(callback, contentWindow) {
+ // Check if attaching failed.
+ contentWindow = getIframeContentWindow(viewInstanceId);
+ if (!contentWindow) {
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
+ this.internalInstanceId = 0;
+ } else {
+ // Only update the contentWindow if attaching is successful.
+ this.contentWindow = contentWindow;
+ }
+
+ this.handleCallback(callback);
+ };
+
+ attachParams['instanceId'] = viewInstanceId;
+ var contentWindow = getIframeContentWindow(viewInstanceId);
+ // |contentWindow| is used to retrieve the RenderFrame in cpp.
+ GuestViewInternalNatives.AttachIframeGuest(
+ internalInstanceId, this.id, attachParams, contentWindow,
+ callbackWrapper.bind(this, callback));
+
+ this.internalInstanceId = internalInstanceId;
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_ATTACHED;
+
+ // Detach automatically when the container is destroyed.
+ GuestViewInternalNatives.RegisterDestructionCallback(
+ internalInstanceId, this.weakWrapper(function() {
+ if (this.state != GuestViewImpl.GuestState.GUEST_STATE_ATTACHED ||
+ this.internalInstanceId != internalInstanceId) {
+ return;
+ }
+
+ this.internalInstanceId = 0;
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
+ }, viewInstanceId));
+};
+
+// Internal implementation of create().
+GuestViewImpl.prototype.createImpl$ = function(createParams, callback) {
+ // Check the current state.
+ if (!this.checkState('create')) {
+ this.handleCallback(callback);
+ return;
+ }
+
+ // Callback wrapper function to store the guestInstanceId from the
+ // createGuest() callback, handle potential creation failure, and advance the
+ // queue.
+ var callbackWrapper = function(callback, guestInfo) {
+ this.id = guestInfo.id;
+
+ // Check if creation failed.
+ if (this.id === 0) {
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_START;
+ this.contentWindow = null;
+ }
+
+ ResizeEvent.addListener(this.callOnResize, {instanceId: this.id});
+ this.handleCallback(callback);
+ };
+
+ this.sendCreateRequest(createParams, callbackWrapper.bind(this, callback));
+
+ this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
+};
diff --git a/chromium/extensions/renderer/resources/guest_view/guest_view_iframe_container.js b/chromium/extensions/renderer/resources/guest_view/guest_view_iframe_container.js
new file mode 100644
index 00000000000..4cd628be8f7
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/guest_view_iframe_container.js
@@ -0,0 +1,30 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// --site-per-process overrides for guest_view_container.js
+
+var GuestViewContainer = require('guestViewContainer').GuestViewContainer;
+var IdGenerator = requireNative('id_generator');
+
+GuestViewContainer.prototype.createInternalElement$ = function() {
+ var iframeElement = document.createElement('iframe');
+ iframeElement.style.width = '100%';
+ iframeElement.style.height = '100%';
+ privates(iframeElement).internal = this;
+ return iframeElement;
+};
+
+GuestViewContainer.prototype.attachWindow$ = function() {
+ var generatedId = IdGenerator.GetNextId();
+ // Generate an instance id for the container.
+ this.onInternalInstanceId(generatedId);
+ return true;
+};
+
+GuestViewContainer.prototype.willAttachElement = function () {
+ if (this.deferredAttachCallback) {
+ this.deferredAttachCallback();
+ this.deferredAttachCallback = null;
+ }
+};
diff --git a/chromium/extensions/renderer/resources/guest_view/web_view/web_view.js b/chromium/extensions/renderer/resources/guest_view/web_view/web_view.js
new file mode 100644
index 00000000000..b5d08c19ad5
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/web_view/web_view.js
@@ -0,0 +1,235 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This module implements WebView (<webview>) as a custom element that wraps a
+// BrowserPlugin object element. The object element is hidden within
+// the shadow DOM of the WebView element.
+
+var DocumentNatives = requireNative('document_natives');
+var GuestView = require('guestView').GuestView;
+var GuestViewContainer = require('guestViewContainer').GuestViewContainer;
+var GuestViewInternalNatives = requireNative('guest_view_internal');
+var WebViewConstants = require('webViewConstants').WebViewConstants;
+var WebViewEvents = require('webViewEvents').WebViewEvents;
+var WebViewInternal = require('webViewInternal').WebViewInternal;
+
+// Represents the internal state of <webview>.
+function WebViewImpl(webviewElement) {
+ GuestViewContainer.call(this, webviewElement, 'webview');
+ this.cachedZoom = 1;
+ this.setupElementProperties();
+ new WebViewEvents(this, this.viewInstanceId);
+}
+
+WebViewImpl.prototype.__proto__ = GuestViewContainer.prototype;
+
+WebViewImpl.VIEW_TYPE = 'WebView';
+
+// Add extra functionality to |this.element|.
+WebViewImpl.setupElement = function(proto) {
+ // Public-facing API methods.
+ var apiMethods = WebViewImpl.getApiMethods();
+
+ // Add the experimental API methods, if available.
+ var experimentalApiMethods = WebViewImpl.maybeGetExperimentalApiMethods();
+ apiMethods = $Array.concat(apiMethods, experimentalApiMethods);
+
+ // Create default implementations for undefined API methods.
+ var createDefaultApiMethod = function(m) {
+ return function(var_args) {
+ if (!this.guest.getId()) {
+ return false;
+ }
+ var args = $Array.concat([this.guest.getId()], $Array.slice(arguments));
+ $Function.apply(WebViewInternal[m], null, args);
+ return true;
+ };
+ };
+ for (var i = 0; i != apiMethods.length; ++i) {
+ if (WebViewImpl.prototype[apiMethods[i]] == undefined) {
+ WebViewImpl.prototype[apiMethods[i]] =
+ createDefaultApiMethod(apiMethods[i]);
+ }
+ }
+
+ // Forward proto.foo* method calls to WebViewImpl.foo*.
+ GuestViewContainer.forwardApiMethods(proto, apiMethods);
+};
+
+// Initiates navigation once the <webview> element is attached to the DOM.
+WebViewImpl.prototype.onElementAttached = function() {
+ // Mark all attributes as dirty on attachment.
+ for (var i in this.attributes) {
+ this.attributes[i].dirty = true;
+ }
+ for (var i in this.attributes) {
+ this.attributes[i].attach();
+ }
+};
+
+// Resets some state upon detaching <webview> element from the DOM.
+WebViewImpl.prototype.onElementDetached = function() {
+ this.guest.destroy();
+ for (var i in this.attributes) {
+ this.attributes[i].dirty = false;
+ }
+ for (var i in this.attributes) {
+ this.attributes[i].detach();
+ }
+};
+
+// Sets the <webview>.request property.
+WebViewImpl.prototype.setRequestPropertyOnWebViewElement = function(request) {
+ Object.defineProperty(
+ this.element,
+ 'request',
+ {
+ value: request,
+ enumerable: true
+ }
+ );
+};
+
+WebViewImpl.prototype.setupElementProperties = function() {
+ // We cannot use {writable: true} property descriptor because we want a
+ // dynamic getter value.
+ Object.defineProperty(this.element, 'contentWindow', {
+ get: function() {
+ return this.guest.getContentWindow();
+ }.bind(this),
+ // No setter.
+ enumerable: true
+ });
+};
+
+WebViewImpl.prototype.onSizeChanged = function(webViewEvent) {
+ var newWidth = webViewEvent.newWidth;
+ var newHeight = webViewEvent.newHeight;
+
+ var element = this.element;
+
+ var width = element.offsetWidth;
+ var height = element.offsetHeight;
+
+ // Check the current bounds to make sure we do not resize <webview>
+ // outside of current constraints.
+ var maxWidth = this.attributes[
+ WebViewConstants.ATTRIBUTE_MAXWIDTH].getValue() || width;
+ var minWidth = this.attributes[
+ WebViewConstants.ATTRIBUTE_MINWIDTH].getValue() || width;
+ var maxHeight = this.attributes[
+ WebViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() || height;
+ var minHeight = this.attributes[
+ WebViewConstants.ATTRIBUTE_MINHEIGHT].getValue() || height;
+
+ minWidth = Math.min(minWidth, maxWidth);
+ minHeight = Math.min(minHeight, maxHeight);
+
+ if (!this.attributes[WebViewConstants.ATTRIBUTE_AUTOSIZE].getValue() ||
+ (newWidth >= minWidth &&
+ newWidth <= maxWidth &&
+ newHeight >= minHeight &&
+ newHeight <= maxHeight)) {
+ element.style.width = newWidth + 'px';
+ element.style.height = newHeight + 'px';
+ // Only fire the DOM event if the size of the <webview> has actually
+ // changed.
+ this.dispatchEvent(webViewEvent);
+ }
+};
+
+WebViewImpl.prototype.createGuest = function() {
+ this.guest.create(this.buildParams(), function() {
+ this.attachWindow$();
+ }.bind(this));
+};
+
+WebViewImpl.prototype.onFrameNameChanged = function(name) {
+ this.attributes[WebViewConstants.ATTRIBUTE_NAME].setValueIgnoreMutation(name);
+};
+
+// Updates state upon loadcommit.
+WebViewImpl.prototype.onLoadCommit = function(
+ baseUrlForDataUrl, currentEntryIndex, entryCount,
+ processId, url, isTopLevel) {
+ this.baseUrlForDataUrl = baseUrlForDataUrl;
+ this.currentEntryIndex = currentEntryIndex;
+ this.entryCount = entryCount;
+ this.processId = processId;
+ if (isTopLevel) {
+ // Touching the src attribute triggers a navigation. To avoid
+ // triggering a page reload on every guest-initiated navigation,
+ // we do not handle this mutation.
+ this.attributes[
+ WebViewConstants.ATTRIBUTE_SRC].setValueIgnoreMutation(url);
+ }
+};
+
+WebViewImpl.prototype.onAttach = function(storagePartitionId) {
+ this.attributes[WebViewConstants.ATTRIBUTE_PARTITION].setValueIgnoreMutation(
+ storagePartitionId);
+};
+
+WebViewImpl.prototype.buildContainerParams = function() {
+ var params = { 'initialZoomFactor': this.cachedZoomFactor,
+ 'userAgentOverride': this.userAgentOverride };
+ for (var i in this.attributes) {
+ var value = this.attributes[i].getValueIfDirty();
+ if (value)
+ params[i] = value;
+ }
+ return params;
+};
+
+WebViewImpl.prototype.attachWindow$ = function(opt_guestInstanceId) {
+ // If |opt_guestInstanceId| was provided, then a different existing guest is
+ // being attached to this webview, and the current one will get destroyed.
+ if (opt_guestInstanceId) {
+ if (this.guest.getId() == opt_guestInstanceId) {
+ return true;
+ }
+ this.guest.destroy();
+ this.guest = new GuestView('webview', opt_guestInstanceId);
+ }
+
+ return GuestViewContainer.prototype.attachWindow$.call(this);
+};
+
+// Shared implementation of executeScript() and insertCSS().
+WebViewImpl.prototype.executeCode = function(func, args) {
+ if (!this.guest.getId()) {
+ window.console.error(WebViewConstants.ERROR_MSG_CANNOT_INJECT_SCRIPT);
+ return false;
+ }
+
+ var webviewSrc = this.attributes[WebViewConstants.ATTRIBUTE_SRC].getValue();
+ if (this.baseUrlForDataUrl) {
+ webviewSrc = this.baseUrlForDataUrl;
+ }
+
+ args = $Array.concat([this.guest.getId(), webviewSrc],
+ $Array.slice(args));
+ $Function.apply(func, null, args);
+ return true;
+}
+
+// Requests the <webview> element wihtin the embedder to enter fullscreen.
+WebViewImpl.prototype.makeElementFullscreen = function() {
+ GuestViewInternalNatives.RunWithGesture(function() {
+ this.element.webkitRequestFullScreen();
+ }.bind(this));
+};
+
+// Implemented when the ChromeWebView API is available.
+WebViewImpl.prototype.maybeSetupContextMenus = function() {};
+
+// Implemented when the experimental WebView API is available.
+WebViewImpl.maybeGetExperimentalApiMethods = function() {
+ return [];
+};
+
+GuestViewContainer.registerElement(WebViewImpl);
+
+// Exports.
+exports.$set('WebViewImpl', WebViewImpl);
diff --git a/chromium/extensions/renderer/resources/guest_view/web_view/web_view_action_requests.js b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_action_requests.js
new file mode 100644
index 00000000000..5f4bf2c0607
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_action_requests.js
@@ -0,0 +1,296 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This module implements helper objects for the dialog, newwindow, and
+// permissionrequest <webview> events.
+
+var MessagingNatives = requireNative('messaging_natives');
+var WebViewConstants = require('webViewConstants').WebViewConstants;
+var WebViewInternal = require('webViewInternal').WebViewInternal;
+
+var PERMISSION_TYPES = ['media',
+ 'geolocation',
+ 'pointerLock',
+ 'download',
+ 'loadplugin',
+ 'filesystem',
+ 'fullscreen'];
+
+// -----------------------------------------------------------------------------
+// WebViewActionRequest object.
+
+// Default partial implementation of a webview action request.
+function WebViewActionRequest(webViewImpl, event, webViewEvent, interfaceName) {
+ this.webViewImpl = webViewImpl;
+ this.event = event;
+ this.webViewEvent = webViewEvent;
+ this.interfaceName = interfaceName;
+ this.guestInstanceId = this.webViewImpl.guest.getId();
+ this.requestId = event.requestId;
+ this.actionTaken = false;
+
+ // Add on the request information specific to the request type.
+ for (var infoName in this.event.requestInfo) {
+ this.event[infoName] = this.event.requestInfo[infoName];
+ this.webViewEvent[infoName] = this.event.requestInfo[infoName];
+ }
+}
+
+// Performs the default action for the request.
+WebViewActionRequest.prototype.defaultAction = function() {
+ // Do nothing if the action has already been taken or the requester is
+ // already gone (in which case its guestInstanceId will be stale).
+ if (this.actionTaken ||
+ this.guestInstanceId != this.webViewImpl.guest.getId()) {
+ return;
+ }
+
+ this.actionTaken = true;
+ WebViewInternal.setPermission(this.guestInstanceId, this.requestId,
+ 'default', '', function(allowed) {
+ if (allowed) {
+ return;
+ }
+ this.showWarningMessage();
+ }.bind(this));
+};
+
+// Called to handle the action request's event.
+WebViewActionRequest.prototype.handleActionRequestEvent = function() {
+ // Construct the interface object and attach it to |webViewEvent|.
+ var request = this.getInterfaceObject();
+ this.webViewEvent[this.interfaceName] = request;
+
+ var defaultPrevented = !this.webViewImpl.dispatchEvent(this.webViewEvent);
+ // Set |webViewEvent| to null to break the circular reference to |request| so
+ // that the garbage collector can eventually collect it.
+ this.webViewEvent = null;
+ if (this.actionTaken) {
+ return;
+ }
+
+ if (defaultPrevented) {
+ // Track the lifetime of |request| with the garbage collector.
+ var portId = -1; // (hack) there is no Extension Port to release
+ MessagingNatives.BindToGC(request, this.defaultAction.bind(this), portId);
+ } else {
+ this.defaultAction();
+ }
+};
+
+// Displays a warning message when an action request is blocked by default.
+WebViewActionRequest.prototype.showWarningMessage = function() {
+ window.console.warn(this.WARNING_MSG_REQUEST_BLOCKED);
+};
+
+// This function ensures that each action is taken at most once.
+WebViewActionRequest.prototype.validateCall = function() {
+ if (this.actionTaken) {
+ throw new Error(this.ERROR_MSG_ACTION_ALREADY_TAKEN);
+ }
+ this.actionTaken = true;
+};
+
+// The following are implemented by the specific action request.
+
+// Returns the interface object for this action request.
+WebViewActionRequest.prototype.getInterfaceObject = undefined;
+
+// Error/warning messages.
+WebViewActionRequest.prototype.ERROR_MSG_ACTION_ALREADY_TAKEN = undefined;
+WebViewActionRequest.prototype.WARNING_MSG_REQUEST_BLOCKED = undefined;
+
+// -----------------------------------------------------------------------------
+// Dialog object.
+
+// Represents a dialog box request (e.g. alert()).
+function Dialog(webViewImpl, event, webViewEvent) {
+ WebViewActionRequest.call(this, webViewImpl, event, webViewEvent, 'dialog');
+
+ this.handleActionRequestEvent();
+}
+
+Dialog.prototype.__proto__ = WebViewActionRequest.prototype;
+
+Dialog.prototype.getInterfaceObject = function() {
+ return {
+ ok: function(user_input) {
+ this.validateCall();
+ user_input = user_input || '';
+ WebViewInternal.setPermission(
+ this.guestInstanceId, this.requestId, 'allow', user_input);
+ }.bind(this),
+ cancel: function() {
+ this.validateCall();
+ WebViewInternal.setPermission(
+ this.guestInstanceId, this.requestId, 'deny');
+ }.bind(this)
+ };
+};
+
+Dialog.prototype.showWarningMessage = function() {
+ var VOWELS = ['a', 'e', 'i', 'o', 'u'];
+ var dialogType = this.event.messageType;
+ var article = (VOWELS.indexOf(dialogType.charAt(0)) >= 0) ? 'An' : 'A';
+ this.WARNING_MSG_REQUEST_BLOCKED = this.WARNING_MSG_REQUEST_BLOCKED.
+ replace('%1', article).replace('%2', dialogType);
+ window.console.warn(this.WARNING_MSG_REQUEST_BLOCKED);
+};
+
+Dialog.prototype.ERROR_MSG_ACTION_ALREADY_TAKEN =
+ WebViewConstants.ERROR_MSG_DIALOG_ACTION_ALREADY_TAKEN;
+Dialog.prototype.WARNING_MSG_REQUEST_BLOCKED =
+ WebViewConstants.WARNING_MSG_DIALOG_REQUEST_BLOCKED;
+
+// -----------------------------------------------------------------------------
+// NewWindow object.
+
+// Represents a new window request.
+function NewWindow(webViewImpl, event, webViewEvent) {
+ WebViewActionRequest.call(this, webViewImpl, event, webViewEvent, 'window');
+
+ this.handleActionRequestEvent();
+}
+
+NewWindow.prototype.__proto__ = WebViewActionRequest.prototype;
+
+NewWindow.prototype.getInterfaceObject = function() {
+ return {
+ attach: function(webview) {
+ this.validateCall();
+ if (!webview || !webview.tagName || webview.tagName != 'WEBVIEW') {
+ throw new Error(ERROR_MSG_WEBVIEW_EXPECTED);
+ }
+
+ var webViewImpl = privates(webview).internal;
+ // Update the partition.
+ if (this.event.partition) {
+ webViewImpl.onAttach(this.event.partition);
+ }
+
+ var attached = webViewImpl.attachWindow$(this.event.windowId);
+ if (!attached) {
+ window.console.error(ERROR_MSG_NEWWINDOW_UNABLE_TO_ATTACH);
+ }
+
+ if (this.guestInstanceId != this.webViewImpl.guest.getId()) {
+ // If the opener is already gone, then its guestInstanceId will be
+ // stale.
+ return;
+ }
+
+ // If the object being passed into attach is not a valid <webview>
+ // then we will fail and it will be treated as if the new window
+ // was rejected. The permission API plumbing is used here to clean
+ // up the state created for the new window if attaching fails.
+ WebViewInternal.setPermission(this.guestInstanceId, this.requestId,
+ attached ? 'allow' : 'deny');
+ }.bind(this),
+ discard: function() {
+ this.validateCall();
+ if (!this.guestInstanceId) {
+ // If the opener is already gone, then we won't have its
+ // guestInstanceId.
+ return;
+ }
+ WebViewInternal.setPermission(
+ this.guestInstanceId, this.requestId, 'deny');
+ }.bind(this)
+ };
+};
+
+NewWindow.prototype.ERROR_MSG_ACTION_ALREADY_TAKEN =
+ WebViewConstants.ERROR_MSG_NEWWINDOW_ACTION_ALREADY_TAKEN;
+NewWindow.prototype.WARNING_MSG_REQUEST_BLOCKED =
+ WebViewConstants.WARNING_MSG_NEWWINDOW_REQUEST_BLOCKED;
+
+// -----------------------------------------------------------------------------
+// PermissionRequest object.
+
+// Represents a permission request (e.g. to access the filesystem).
+function PermissionRequest(webViewImpl, event, webViewEvent) {
+ WebViewActionRequest.call(this, webViewImpl, event, webViewEvent, 'request');
+
+ if (!this.validPermissionCheck()) {
+ return;
+ }
+
+ this.handleActionRequestEvent();
+}
+
+PermissionRequest.prototype.__proto__ = WebViewActionRequest.prototype;
+
+PermissionRequest.prototype.allow = function() {
+ this.validateCall();
+ WebViewInternal.setPermission(this.guestInstanceId, this.requestId, 'allow');
+};
+
+PermissionRequest.prototype.deny = function() {
+ this.validateCall();
+ WebViewInternal.setPermission(this.guestInstanceId, this.requestId, 'deny');
+};
+
+PermissionRequest.prototype.getInterfaceObject = function() {
+ var request = {
+ allow: this.allow.bind(this),
+ deny: this.deny.bind(this)
+ };
+
+ // Add on the request information specific to the request type.
+ for (var infoName in this.event.requestInfo) {
+ request[infoName] = this.event.requestInfo[infoName];
+ }
+
+ return $Object.freeze(request);
+};
+
+PermissionRequest.prototype.showWarningMessage = function() {
+ window.console.warn(
+ this.WARNING_MSG_REQUEST_BLOCKED.replace('%1', this.event.permission));
+};
+
+// Checks that the requested permission is valid. Returns true if valid.
+PermissionRequest.prototype.validPermissionCheck = function() {
+ if (PERMISSION_TYPES.indexOf(this.event.permission) < 0) {
+ // The permission type is not allowed. Trigger the default response.
+ this.defaultAction();
+ return false;
+ }
+ return true;
+};
+
+PermissionRequest.prototype.ERROR_MSG_ACTION_ALREADY_TAKEN =
+ WebViewConstants.ERROR_MSG_PERMISSION_ACTION_ALREADY_TAKEN;
+PermissionRequest.prototype.WARNING_MSG_REQUEST_BLOCKED =
+ WebViewConstants.WARNING_MSG_PERMISSION_REQUEST_BLOCKED;
+
+// -----------------------------------------------------------------------------
+
+// FullscreenPermissionRequest object.
+
+// Represents a fullscreen permission request.
+function FullscreenPermissionRequest(webViewImpl, event, webViewEvent) {
+ PermissionRequest.call(this, webViewImpl, event, webViewEvent);
+}
+
+FullscreenPermissionRequest.prototype.__proto__ = PermissionRequest.prototype;
+
+FullscreenPermissionRequest.prototype.allow = function() {
+ PermissionRequest.prototype.allow.call(this);
+ // Now make the <webview> element go fullscreen.
+ this.webViewImpl.makeElementFullscreen();
+};
+
+// -----------------------------------------------------------------------------
+
+var WebViewActionRequests = {
+ WebViewActionRequest: WebViewActionRequest,
+ Dialog: Dialog,
+ NewWindow: NewWindow,
+ PermissionRequest: PermissionRequest,
+ FullscreenPermissionRequest: FullscreenPermissionRequest
+};
+
+// Exports.
+exports.$set('WebViewActionRequests', WebViewActionRequests);
diff --git a/chromium/extensions/renderer/resources/guest_view/web_view/web_view_api_methods.js b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_api_methods.js
new file mode 100644
index 00000000000..a28741b7430
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_api_methods.js
@@ -0,0 +1,204 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This module implements the public-facing API functions for the <webview> tag.
+
+var WebViewInternal = require('webViewInternal').WebViewInternal;
+var WebViewImpl = require('webView').WebViewImpl;
+
+// An array of <webview>'s public-facing API methods. Methods without custom
+// implementations will be given default implementations that call into the
+// internal API method with the same name in |WebViewInternal|. For example, a
+// method called 'someApiMethod' would be given the following default
+// implementation:
+//
+// WebViewImpl.prototype.someApiMethod = function(var_args) {
+// if (!this.guest.getId()) {
+// return false;
+// }
+// var args = $Array.concat([this.guest.getId()], $Array.slice(arguments));
+// $Function.apply(WebViewInternal.someApiMethod, null, args);
+// return true;
+// };
+//
+// These default implementations come from createDefaultApiMethod() in
+// web_view.js.
+var WEB_VIEW_API_METHODS = [
+ // Add content scripts for the guest page.
+ 'addContentScripts',
+
+ // Navigates to the previous history entry.
+ 'back',
+
+ // Returns whether there is a previous history entry to navigate to.
+ 'canGoBack',
+
+ // Returns whether there is a subsequent history entry to navigate to.
+ 'canGoForward',
+
+ // Clears browsing data for the WebView partition.
+ 'clearData',
+
+ // Injects JavaScript code into the guest page.
+ 'executeScript',
+
+ // Initiates a find-in-page request.
+ 'find',
+
+ // Navigates to the subsequent history entry.
+ 'forward',
+
+ // Returns Chrome's internal process ID for the guest web page's current
+ // process.
+ 'getProcessId',
+
+ // Returns the user agent string used by the webview for guest page requests.
+ 'getUserAgent',
+
+ // Gets the current zoom factor.
+ 'getZoom',
+
+ // Gets the current zoom mode of the webview.
+ 'getZoomMode',
+
+ // Navigates to a history entry using a history index relative to the current
+ // navigation.
+ 'go',
+
+ // Injects CSS into the guest page.
+ 'insertCSS',
+
+ // Indicates whether or not the webview's user agent string has been
+ // overridden.
+ 'isUserAgentOverridden',
+
+ // Loads a data URL with a specified base URL used for relative links.
+ // Optionally, a virtual URL can be provided to be shown to the user instead
+ // of the data URL.
+ 'loadDataWithBaseUrl',
+
+ // Prints the contents of the webview.
+ 'print',
+
+ // Removes content scripts for the guest page.
+ 'removeContentScripts',
+
+ // Reloads the current top-level page.
+ 'reload',
+
+ // Override the user agent string used by the webview for guest page requests.
+ 'setUserAgentOverride',
+
+ // Changes the zoom factor of the page.
+ 'setZoom',
+
+ // Changes the zoom mode of the webview.
+ 'setZoomMode',
+
+ // Stops loading the current navigation if one is in progress.
+ 'stop',
+
+ // Ends the current find session.
+ 'stopFinding',
+
+ // Forcibly kills the guest web page's renderer process.
+ 'terminate'
+];
+
+// -----------------------------------------------------------------------------
+// Custom API method implementations.
+
+WebViewImpl.prototype.addContentScripts = function(rules) {
+ return WebViewInternal.addContentScripts(this.viewInstanceId, rules);
+};
+
+WebViewImpl.prototype.back = function(callback) {
+ return this.go(-1, callback);
+};
+
+WebViewImpl.prototype.canGoBack = function() {
+ return this.entryCount > 1 && this.currentEntryIndex > 0;
+};
+
+WebViewImpl.prototype.canGoForward = function() {
+ return this.currentEntryIndex >= 0 &&
+ this.currentEntryIndex < (this.entryCount - 1);
+};
+
+WebViewImpl.prototype.executeScript = function(var_args) {
+ return this.executeCode(WebViewInternal.executeScript,
+ $Array.slice(arguments));
+};
+
+WebViewImpl.prototype.forward = function(callback) {
+ return this.go(1, callback);
+};
+
+WebViewImpl.prototype.getProcessId = function() {
+ return this.processId;
+};
+
+WebViewImpl.prototype.getUserAgent = function() {
+ return this.userAgentOverride || navigator.userAgent;
+};
+
+WebViewImpl.prototype.insertCSS = function(var_args) {
+ return this.executeCode(WebViewInternal.insertCSS, $Array.slice(arguments));
+};
+
+WebViewImpl.prototype.isUserAgentOverridden = function() {
+ return !!this.userAgentOverride &&
+ this.userAgentOverride != navigator.userAgent;
+};
+
+WebViewImpl.prototype.loadDataWithBaseUrl = function(
+ dataUrl, baseUrl, virtualUrl) {
+ if (!this.guest.getId()) {
+ return;
+ }
+ WebViewInternal.loadDataWithBaseUrl(
+ this.guest.getId(), dataUrl, baseUrl, virtualUrl, function() {
+ // Report any errors.
+ if (chrome.runtime.lastError != undefined) {
+ window.console.error(
+ 'Error while running webview.loadDataWithBaseUrl: ' +
+ chrome.runtime.lastError.message);
+ }
+ });
+};
+
+WebViewImpl.prototype.print = function() {
+ return this.executeScript({code: 'window.print();'});
+};
+
+WebViewImpl.prototype.removeContentScripts = function(names) {
+ return WebViewInternal.removeContentScripts(this.viewInstanceId, names);
+};
+
+WebViewImpl.prototype.setUserAgentOverride = function(userAgentOverride) {
+ this.userAgentOverride = userAgentOverride;
+ if (!this.guest.getId()) {
+ // If we are not attached yet, then we will pick up the user agent on
+ // attachment.
+ return false;
+ }
+ WebViewInternal.overrideUserAgent(this.guest.getId(), userAgentOverride);
+ return true;
+};
+
+WebViewImpl.prototype.setZoom = function(zoomFactor, callback) {
+ if (!this.guest.getId()) {
+ this.cachedZoomFactor = zoomFactor;
+ return false;
+ }
+ this.cachedZoomFactor = 1;
+ WebViewInternal.setZoom(this.guest.getId(), zoomFactor, callback);
+ return true;
+};
+
+// -----------------------------------------------------------------------------
+
+WebViewImpl.getApiMethods = function() {
+ return WEB_VIEW_API_METHODS;
+};
diff --git a/chromium/extensions/renderer/resources/guest_view/web_view/web_view_attributes.js b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_attributes.js
new file mode 100644
index 00000000000..ce538fdc3df
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_attributes.js
@@ -0,0 +1,277 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This module implements the attributes of the <webview> tag.
+
+var GuestViewAttributes = require('guestViewAttributes').GuestViewAttributes;
+var WebViewConstants = require('webViewConstants').WebViewConstants;
+var WebViewImpl = require('webView').WebViewImpl;
+var WebViewInternal = require('webViewInternal').WebViewInternal;
+
+// -----------------------------------------------------------------------------
+// AllowScalingAttribute object.
+
+// Attribute that specifies whether scaling is allowed in the webview.
+function AllowScalingAttribute(view) {
+ GuestViewAttributes.BooleanAttribute.call(
+ this, WebViewConstants.ATTRIBUTE_ALLOWSCALING, view);
+}
+
+AllowScalingAttribute.prototype.__proto__ =
+ GuestViewAttributes.BooleanAttribute.prototype;
+
+AllowScalingAttribute.prototype.handleMutation = function(oldValue, newValue) {
+ if (!this.view.guest.getId())
+ return;
+
+ WebViewInternal.setAllowScaling(this.view.guest.getId(), this.getValue());
+};
+
+// -----------------------------------------------------------------------------
+// AllowTransparencyAttribute object.
+
+// Attribute that specifies whether transparency is allowed in the webview.
+function AllowTransparencyAttribute(view) {
+ GuestViewAttributes.BooleanAttribute.call(
+ this, WebViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY, view);
+}
+
+AllowTransparencyAttribute.prototype.__proto__ =
+ GuestViewAttributes.BooleanAttribute.prototype;
+
+AllowTransparencyAttribute.prototype.handleMutation = function(oldValue,
+ newValue) {
+ if (!this.view.guest.getId())
+ return;
+
+ WebViewInternal.setAllowTransparency(this.view.guest.getId(),
+ this.getValue());
+};
+
+// -----------------------------------------------------------------------------
+// AutosizeDimensionAttribute object.
+
+// Attribute used to define the demension limits of autosizing.
+function AutosizeDimensionAttribute(name, view) {
+ GuestViewAttributes.IntegerAttribute.call(this, name, view);
+}
+
+AutosizeDimensionAttribute.prototype.__proto__ =
+ GuestViewAttributes.IntegerAttribute.prototype;
+
+AutosizeDimensionAttribute.prototype.handleMutation = function(
+ oldValue, newValue) {
+ if (!this.view.guest.getId())
+ return;
+
+ this.view.guest.setSize({
+ 'enableAutoSize': this.view.attributes[
+ WebViewConstants.ATTRIBUTE_AUTOSIZE].getValue(),
+ 'min': {
+ 'width': this.view.attributes[
+ WebViewConstants.ATTRIBUTE_MINWIDTH].getValue(),
+ 'height': this.view.attributes[
+ WebViewConstants.ATTRIBUTE_MINHEIGHT].getValue()
+ },
+ 'max': {
+ 'width': this.view.attributes[
+ WebViewConstants.ATTRIBUTE_MAXWIDTH].getValue(),
+ 'height': this.view.attributes[
+ WebViewConstants.ATTRIBUTE_MAXHEIGHT].getValue()
+ }
+ });
+ return;
+};
+
+// -----------------------------------------------------------------------------
+// AutosizeAttribute object.
+
+// Attribute that specifies whether the webview should be autosized.
+function AutosizeAttribute(view) {
+ GuestViewAttributes.BooleanAttribute.call(
+ this, WebViewConstants.ATTRIBUTE_AUTOSIZE, view);
+}
+
+AutosizeAttribute.prototype.__proto__ =
+ GuestViewAttributes.BooleanAttribute.prototype;
+
+AutosizeAttribute.prototype.handleMutation =
+ AutosizeDimensionAttribute.prototype.handleMutation;
+
+// -----------------------------------------------------------------------------
+// NameAttribute object.
+
+// Attribute that sets the guest content's window.name object.
+function NameAttribute(view) {
+ GuestViewAttributes.Attribute.call(
+ this, WebViewConstants.ATTRIBUTE_NAME, view);
+}
+
+NameAttribute.prototype.__proto__ = GuestViewAttributes.Attribute.prototype
+
+NameAttribute.prototype.handleMutation = function(oldValue, newValue) {
+ oldValue = oldValue || '';
+ newValue = newValue || '';
+ if (oldValue === newValue || !this.view.guest.getId())
+ return;
+
+ WebViewInternal.setName(this.view.guest.getId(), newValue);
+};
+
+NameAttribute.prototype.setValue = function(value) {
+ value = value || '';
+ if (value === '')
+ this.view.element.removeAttribute(this.name);
+ else
+ this.view.element.setAttribute(this.name, value);
+};
+
+// -----------------------------------------------------------------------------
+// PartitionAttribute object.
+
+// Attribute representing the state of the storage partition.
+function PartitionAttribute(view) {
+ GuestViewAttributes.Attribute.call(
+ this, WebViewConstants.ATTRIBUTE_PARTITION, view);
+ this.validPartitionId = true;
+}
+
+PartitionAttribute.prototype.__proto__ =
+ GuestViewAttributes.Attribute.prototype;
+
+PartitionAttribute.prototype.handleMutation = function(oldValue, newValue) {
+ newValue = newValue || '';
+
+ // The partition cannot change if the webview has already navigated.
+ if (!this.view.attributes[
+ WebViewConstants.ATTRIBUTE_SRC].beforeFirstNavigation) {
+ window.console.error(WebViewConstants.ERROR_MSG_ALREADY_NAVIGATED);
+ this.setValueIgnoreMutation(oldValue);
+ return;
+ }
+ if (newValue == 'persist:') {
+ this.validPartitionId = false;
+ window.console.error(
+ WebViewConstants.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE);
+ }
+};
+
+PartitionAttribute.prototype.detach = function() {
+ this.validPartitionId = true;
+};
+
+// -----------------------------------------------------------------------------
+// SrcAttribute object.
+
+// Attribute that handles the location and navigation of the webview.
+function SrcAttribute(view) {
+ GuestViewAttributes.Attribute.call(
+ this, WebViewConstants.ATTRIBUTE_SRC, view);
+ this.setupMutationObserver();
+ this.beforeFirstNavigation = true;
+}
+
+SrcAttribute.prototype.__proto__ = GuestViewAttributes.Attribute.prototype;
+
+SrcAttribute.prototype.setValueIgnoreMutation = function(value) {
+ GuestViewAttributes.Attribute.prototype.setValueIgnoreMutation.call(
+ this, value);
+ // takeRecords() is needed to clear queued up src mutations. Without it, it is
+ // possible for this change to get picked up asyncronously by src's mutation
+ // observer |observer|, and then get handled even though we do not want to
+ // handle this mutation.
+ this.observer.takeRecords();
+}
+
+SrcAttribute.prototype.handleMutation = function(oldValue, newValue) {
+ // Once we have navigated, we don't allow clearing the src attribute.
+ // Once <webview> enters a navigated state, it cannot return to a
+ // placeholder state.
+ if (!newValue && oldValue) {
+ // src attribute changes normally initiate a navigation. We suppress
+ // the next src attribute handler call to avoid reloading the page
+ // on every guest-initiated navigation.
+ this.setValueIgnoreMutation(oldValue);
+ return;
+ }
+ this.parse();
+};
+
+SrcAttribute.prototype.attach = function() {
+ this.parse();
+};
+
+SrcAttribute.prototype.detach = function() {
+ this.beforeFirstNavigation = true;
+};
+
+// The purpose of this mutation observer is to catch assignment to the src
+// attribute without any changes to its value. This is useful in the case
+// where the webview guest has crashed and navigating to the same address
+// spawns off a new process.
+SrcAttribute.prototype.setupMutationObserver =
+ function() {
+ this.observer = new MutationObserver(function(mutations) {
+ $Array.forEach(mutations, function(mutation) {
+ var oldValue = mutation.oldValue;
+ var newValue = this.getValue();
+ if (oldValue != newValue) {
+ return;
+ }
+ this.handleMutation(oldValue, newValue);
+ }.bind(this));
+ }.bind(this));
+ var params = {
+ attributes: true,
+ attributeOldValue: true,
+ attributeFilter: [this.name]
+ };
+ this.observer.observe(this.view.element, params);
+};
+
+SrcAttribute.prototype.parse = function() {
+ if (!this.view.elementAttached ||
+ !this.view.attributes[
+ WebViewConstants.ATTRIBUTE_PARTITION].validPartitionId ||
+ !this.getValue()) {
+ return;
+ }
+
+ if (!this.view.guest.getId()) {
+ if (this.beforeFirstNavigation) {
+ this.beforeFirstNavigation = false;
+ this.view.createGuest();
+ }
+ return;
+ }
+
+ WebViewInternal.navigate(this.view.guest.getId(), this.getValue());
+};
+
+// -----------------------------------------------------------------------------
+
+// Sets up all of the webview attributes.
+WebViewImpl.prototype.setupAttributes = function() {
+ this.attributes[WebViewConstants.ATTRIBUTE_ALLOWSCALING] =
+ new AllowScalingAttribute(this);
+ this.attributes[WebViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY] =
+ new AllowTransparencyAttribute(this);
+ this.attributes[WebViewConstants.ATTRIBUTE_AUTOSIZE] =
+ new AutosizeAttribute(this);
+ this.attributes[WebViewConstants.ATTRIBUTE_NAME] =
+ new NameAttribute(this);
+ this.attributes[WebViewConstants.ATTRIBUTE_PARTITION] =
+ new PartitionAttribute(this);
+ this.attributes[WebViewConstants.ATTRIBUTE_SRC] =
+ new SrcAttribute(this);
+
+ var autosizeAttributes = [WebViewConstants.ATTRIBUTE_MAXHEIGHT,
+ WebViewConstants.ATTRIBUTE_MAXWIDTH,
+ WebViewConstants.ATTRIBUTE_MINHEIGHT,
+ WebViewConstants.ATTRIBUTE_MINWIDTH];
+ for (var i = 0; autosizeAttributes[i]; ++i) {
+ this.attributes[autosizeAttributes[i]] =
+ new AutosizeDimensionAttribute(autosizeAttributes[i], this);
+ }
+};
diff --git a/chromium/extensions/renderer/resources/guest_view/web_view/web_view_constants.js b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_constants.js
new file mode 100644
index 00000000000..09110e14749
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_constants.js
@@ -0,0 +1,40 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This module contains constants used in webview.
+
+// Container for the webview constants.
+var WebViewConstants = {
+ // Attributes.
+ ATTRIBUTE_ALLOWTRANSPARENCY: 'allowtransparency',
+ ATTRIBUTE_ALLOWSCALING: 'allowscaling',
+ ATTRIBUTE_AUTOSIZE: 'autosize',
+ ATTRIBUTE_MAXHEIGHT: 'maxheight',
+ ATTRIBUTE_MAXWIDTH: 'maxwidth',
+ ATTRIBUTE_MINHEIGHT: 'minheight',
+ ATTRIBUTE_MINWIDTH: 'minwidth',
+ ATTRIBUTE_NAME: 'name',
+ ATTRIBUTE_PARTITION: 'partition',
+ ATTRIBUTE_SRC: 'src',
+
+ // Error/warning messages.
+ ERROR_MSG_ALREADY_NAVIGATED: '<webview>: ' +
+ 'The object has already navigated, so its partition cannot be changed.',
+ ERROR_MSG_CANNOT_INJECT_SCRIPT: '<webview>: ' +
+ 'Script cannot be injected into content until the page has loaded.',
+ ERROR_MSG_DIALOG_ACTION_ALREADY_TAKEN: '<webview>: ' +
+ 'An action has already been taken for this "dialog" event.',
+ ERROR_MSG_NEWWINDOW_ACTION_ALREADY_TAKEN: '<webview>: ' +
+ 'An action has already been taken for this "newwindow" event.',
+ ERROR_MSG_PERMISSION_ACTION_ALREADY_TAKEN: '<webview>: ' +
+ 'Permission has already been decided for this "permissionrequest" event.',
+ ERROR_MSG_INVALID_PARTITION_ATTRIBUTE: '<webview>: ' +
+ 'Invalid partition attribute.',
+ WARNING_MSG_DIALOG_REQUEST_BLOCKED: '<webview>: %1 %2 dialog was blocked.',
+ WARNING_MSG_NEWWINDOW_REQUEST_BLOCKED: '<webview>: A new window was blocked.',
+ WARNING_MSG_PERMISSION_REQUEST_BLOCKED: '<webview>: ' +
+ 'The permission request for "%1" has been denied.'
+};
+
+exports.$set('WebViewConstants', $Object.freeze(WebViewConstants));
diff --git a/chromium/extensions/renderer/resources/guest_view/web_view/web_view_events.js b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_events.js
new file mode 100644
index 00000000000..1077684de00
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_events.js
@@ -0,0 +1,301 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Event management for WebView.
+
+var CreateEvent = require('guestViewEvents').CreateEvent;
+var DeclarativeWebRequestSchema =
+ requireNative('schema_registry').GetSchema('declarativeWebRequest');
+var EventBindings = require('event_bindings');
+var GuestViewEvents = require('guestViewEvents').GuestViewEvents;
+var GuestViewInternalNatives = requireNative('guest_view_internal');
+var IdGenerator = requireNative('id_generator');
+var WebRequestEvent = require('webRequestInternal').WebRequestEvent;
+var WebRequestSchema =
+ requireNative('schema_registry').GetSchema('webRequest');
+var WebViewActionRequests =
+ require('webViewActionRequests').WebViewActionRequests;
+
+var WebRequestMessageEvent = CreateEvent('webViewInternal.onMessage');
+
+function WebViewEvents(webViewImpl) {
+ GuestViewEvents.call(this, webViewImpl);
+
+ this.setupWebRequestEvents();
+ this.view.maybeSetupContextMenus();
+}
+
+WebViewEvents.prototype.__proto__ = GuestViewEvents.prototype;
+
+// A dictionary of <webview> extension events to be listened for. This
+// dictionary augments |GuestViewEvents.EVENTS| in guest_view_events.js. See the
+// documentation there for details.
+WebViewEvents.EVENTS = {
+ 'close': {
+ evt: CreateEvent('webViewInternal.onClose')
+ },
+ 'consolemessage': {
+ evt: CreateEvent('webViewInternal.onConsoleMessage'),
+ fields: ['level', 'message', 'line', 'sourceId']
+ },
+ 'contentload': {
+ evt: CreateEvent('webViewInternal.onContentLoad')
+ },
+ 'dialog': {
+ cancelable: true,
+ evt: CreateEvent('webViewInternal.onDialog'),
+ fields: ['defaultPromptText', 'messageText', 'messageType', 'url'],
+ handler: 'handleDialogEvent'
+ },
+ 'droplink': {
+ evt: CreateEvent('webViewInternal.onDropLink'),
+ fields: ['url']
+ },
+ 'exit': {
+ evt: CreateEvent('webViewInternal.onExit'),
+ fields: ['processId', 'reason']
+ },
+ 'exitfullscreen': {
+ evt: CreateEvent('webViewInternal.onExitFullscreen'),
+ fields: ['url'],
+ handler: 'handleFullscreenExitEvent',
+ internal: true
+ },
+ 'findupdate': {
+ evt: CreateEvent('webViewInternal.onFindReply'),
+ fields: [
+ 'searchText',
+ 'numberOfMatches',
+ 'activeMatchOrdinal',
+ 'selectionRect',
+ 'canceled',
+ 'finalUpdate'
+ ]
+ },
+ 'framenamechanged': {
+ evt: CreateEvent('webViewInternal.onFrameNameChanged'),
+ handler: 'handleFrameNameChangedEvent',
+ internal: true
+ },
+ 'loadabort': {
+ cancelable: true,
+ evt: CreateEvent('webViewInternal.onLoadAbort'),
+ fields: ['url', 'isTopLevel', 'code', 'reason'],
+ handler: 'handleLoadAbortEvent'
+ },
+ 'loadcommit': {
+ evt: CreateEvent('webViewInternal.onLoadCommit'),
+ fields: ['url', 'isTopLevel'],
+ handler: 'handleLoadCommitEvent'
+ },
+ 'loadprogress': {
+ evt: CreateEvent('webViewInternal.onLoadProgress'),
+ fields: ['url', 'progress']
+ },
+ 'loadredirect': {
+ evt: CreateEvent('webViewInternal.onLoadRedirect'),
+ fields: ['isTopLevel', 'oldUrl', 'newUrl']
+ },
+ 'loadstart': {
+ evt: CreateEvent('webViewInternal.onLoadStart'),
+ fields: ['url', 'isTopLevel']
+ },
+ 'loadstop': {
+ evt: CreateEvent('webViewInternal.onLoadStop')
+ },
+ 'newwindow': {
+ cancelable: true,
+ evt: CreateEvent('webViewInternal.onNewWindow'),
+ fields: [
+ 'initialHeight',
+ 'initialWidth',
+ 'targetUrl',
+ 'windowOpenDisposition',
+ 'name'
+ ],
+ handler: 'handleNewWindowEvent'
+ },
+ 'permissionrequest': {
+ cancelable: true,
+ evt: CreateEvent('webViewInternal.onPermissionRequest'),
+ fields: [
+ 'identifier',
+ 'lastUnlockedBySelf',
+ 'name',
+ 'permission',
+ 'requestMethod',
+ 'url',
+ 'userGesture'
+ ],
+ handler: 'handlePermissionEvent'
+ },
+ 'responsive': {
+ evt: CreateEvent('webViewInternal.onResponsive'),
+ fields: ['processId']
+ },
+ 'sizechanged': {
+ evt: CreateEvent('webViewInternal.onSizeChanged'),
+ fields: ['oldHeight', 'oldWidth', 'newHeight', 'newWidth'],
+ handler: 'handleSizeChangedEvent'
+ },
+ 'unresponsive': {
+ evt: CreateEvent('webViewInternal.onUnresponsive'),
+ fields: ['processId']
+ },
+ 'zoomchange': {
+ evt: CreateEvent('webViewInternal.onZoomChange'),
+ fields: ['oldZoomFactor', 'newZoomFactor']
+ }
+};
+
+WebViewEvents.prototype.setupWebRequestEvents = function() {
+ var request = {};
+ var createWebRequestEvent = function(webRequestEvent) {
+ return this.weakWrapper(function() {
+ if (!this[webRequestEvent.name]) {
+ this[webRequestEvent.name] =
+ new WebRequestEvent(
+ 'webViewInternal.' + webRequestEvent.name,
+ webRequestEvent.parameters,
+ webRequestEvent.extraParameters, webRequestEvent.options,
+ this.view.viewInstanceId);
+ }
+ return this[webRequestEvent.name];
+ });
+ }.bind(this);
+
+ var createDeclarativeWebRequestEvent = function(webRequestEvent) {
+ return this.weakWrapper(function() {
+ if (!this[webRequestEvent.name]) {
+ // The onMessage event gets a special event type because we want
+ // the listener to fire only for messages targeted for this particular
+ // <webview>.
+ var EventClass = webRequestEvent.name === 'onMessage' ?
+ DeclarativeWebRequestEvent : EventBindings.Event;
+ this[webRequestEvent.name] =
+ new EventClass(
+ 'webViewInternal.declarativeWebRequest.' + webRequestEvent.name,
+ webRequestEvent.parameters,
+ webRequestEvent.options,
+ this.view.viewInstanceId);
+ }
+ return this[webRequestEvent.name];
+ });
+ }.bind(this);
+
+ for (var i = 0; i < DeclarativeWebRequestSchema.events.length; ++i) {
+ var eventSchema = DeclarativeWebRequestSchema.events[i];
+ var webRequestEvent = createDeclarativeWebRequestEvent(eventSchema);
+ Object.defineProperty(
+ request,
+ eventSchema.name,
+ {
+ get: webRequestEvent,
+ enumerable: true
+ }
+ );
+ }
+
+ // Populate the WebRequest events from the API definition.
+ for (var i = 0; i < WebRequestSchema.events.length; ++i) {
+ var webRequestEvent = createWebRequestEvent(WebRequestSchema.events[i]);
+ Object.defineProperty(
+ request,
+ WebRequestSchema.events[i].name,
+ {
+ get: webRequestEvent,
+ enumerable: true
+ }
+ );
+ }
+
+ this.view.setRequestPropertyOnWebViewElement(request);
+};
+
+WebViewEvents.prototype.getEvents = function() {
+ return WebViewEvents.EVENTS;
+};
+
+WebViewEvents.prototype.handleDialogEvent = function(event, eventName) {
+ var webViewEvent = this.makeDomEvent(event, eventName);
+ new WebViewActionRequests.Dialog(this.view, event, webViewEvent);
+};
+
+WebViewEvents.prototype.handleFrameNameChangedEvent = function(event) {
+ this.view.onFrameNameChanged(event.name);
+};
+
+WebViewEvents.prototype.handleFullscreenExitEvent = function(event, eventName) {
+ document.webkitCancelFullScreen();
+};
+
+WebViewEvents.prototype.handleLoadAbortEvent = function(event, eventName) {
+ var showWarningMessage = function(code, reason) {
+ var WARNING_MSG_LOAD_ABORTED = '<webview>: ' +
+ 'The load has aborted with error %1: %2.';
+ window.console.warn(
+ WARNING_MSG_LOAD_ABORTED.replace('%1', code).replace('%2', reason));
+ };
+ var webViewEvent = this.makeDomEvent(event, eventName);
+ if (this.view.dispatchEvent(webViewEvent)) {
+ showWarningMessage(event.code, event.reason);
+ }
+};
+
+WebViewEvents.prototype.handleLoadCommitEvent = function(event, eventName) {
+ this.view.onLoadCommit(event.baseUrlForDataUrl,
+ event.currentEntryIndex,
+ event.entryCount,
+ event.processId,
+ event.url,
+ event.isTopLevel);
+ var webViewEvent = this.makeDomEvent(event, eventName);
+ this.view.dispatchEvent(webViewEvent);
+};
+
+WebViewEvents.prototype.handleNewWindowEvent = function(event, eventName) {
+ var webViewEvent = this.makeDomEvent(event, eventName);
+ new WebViewActionRequests.NewWindow(this.view, event, webViewEvent);
+};
+
+WebViewEvents.prototype.handlePermissionEvent = function(event, eventName) {
+ var webViewEvent = this.makeDomEvent(event, eventName);
+ if (event.permission === 'fullscreen') {
+ new WebViewActionRequests.FullscreenPermissionRequest(
+ this.view, event, webViewEvent);
+ } else {
+ new WebViewActionRequests.PermissionRequest(this.view, event, webViewEvent);
+ }
+};
+
+WebViewEvents.prototype.handleSizeChangedEvent = function(event, eventName) {
+ var webViewEvent = this.makeDomEvent(event, eventName);
+ this.view.onSizeChanged(webViewEvent);
+};
+
+function DeclarativeWebRequestEvent(opt_eventName,
+ opt_argSchemas,
+ opt_eventOptions,
+ opt_webViewInstanceId) {
+ var subEventName = opt_eventName + '/' + IdGenerator.GetNextId();
+ EventBindings.Event.call(this,
+ subEventName,
+ opt_argSchemas,
+ opt_eventOptions,
+ opt_webViewInstanceId);
+
+ var view = GuestViewInternalNatives.GetViewFromID(opt_webViewInstanceId || 0);
+ if (!view) {
+ return;
+ }
+ view.events.addScopedListener(WebRequestMessageEvent, function() {
+ // Re-dispatch to subEvent's listeners.
+ $Function.apply(this.dispatch, this, $Array.slice(arguments));
+ }.bind(this), {instanceId: opt_webViewInstanceId || 0});
+}
+
+DeclarativeWebRequestEvent.prototype.__proto__ = EventBindings.Event.prototype;
+
+// Exports.
+exports.$set('WebViewEvents', WebViewEvents);
diff --git a/chromium/extensions/renderer/resources/guest_view/web_view/web_view_experimental.js b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_experimental.js
new file mode 100644
index 00000000000..96331df2a89
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_experimental.js
@@ -0,0 +1,24 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This module implements experimental API for <webview>.
+// See web_view.js and web_view_api_methods.js for details.
+//
+// <webview> Experimental API is only available on canary and channels of
+// Chrome.
+
+var WebViewImpl = require('webView').WebViewImpl;
+var WebViewInternal = require('webViewInternal').WebViewInternal;
+
+// An array of <webview>'s experimental API methods. See |WEB_VIEW_API_METHODS|
+// in web_view_api_methods.js for more details.
+var WEB_VIEW_EXPERIMENTAL_API_METHODS = [
+ // Captures the visible region of the WebView contents into a bitmap.
+ 'captureVisibleRegion'
+];
+
+// Registers the experimantal WebVIew API when available.
+WebViewImpl.maybeGetExperimentalApiMethods = function() {
+ return WEB_VIEW_EXPERIMENTAL_API_METHODS;
+};
diff --git a/chromium/extensions/renderer/resources/guest_view/web_view/web_view_internal.js b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_internal.js
new file mode 100644
index 00000000000..4d2c36c2dad
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_internal.js
@@ -0,0 +1,7 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+exports.$set(
+ 'WebViewInternal',
+ require('binding').Binding.create('webViewInternal').generate());
diff --git a/chromium/extensions/renderer/resources/guest_view/web_view/web_view_request_custom_bindings.js b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_request_custom_bindings.js
new file mode 100644
index 00000000000..61b553b693f
--- /dev/null
+++ b/chromium/extensions/renderer/resources/guest_view/web_view/web_view_request_custom_bindings.js
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Custom binding for the webViewRequest API.
+
+var binding = require('binding').Binding.create('webViewRequest');
+
+var declarativeWebRequestSchema =
+ requireNative('schema_registry').GetSchema('declarativeWebRequest');
+var utils = require('utils');
+var validate = require('schemaUtils').validate;
+
+binding.registerCustomHook(function(api) {
+ var webViewRequest = api.compiledApi;
+
+ // Returns the schema definition of type |typeId| defined in
+ // |declarativeWebRequestScheme.types|.
+ function getSchema(typeId) {
+ return utils.lookup(declarativeWebRequestSchema.types,
+ 'id',
+ 'declarativeWebRequest.' + typeId);
+ }
+
+ // Helper function for the constructor of concrete datatypes of the
+ // declarative webRequest API.
+ // Makes sure that |this| contains the union of parameters and
+ // {'instanceType': 'declarativeWebRequest.' + typeId} and validates the
+ // generated union dictionary against the schema for |typeId|.
+ function setupInstance(instance, parameters, typeId) {
+ for (var key in parameters) {
+ if ($Object.hasOwnProperty(parameters, key)) {
+ instance[key] = parameters[key];
+ }
+ }
+
+ instance.instanceType = 'declarativeWebRequest.' + typeId;
+ var schema = getSchema(typeId);
+ validate([instance], [schema]);
+ }
+
+ // Setup all data types for the declarative webRequest API from the schema.
+ for (var i = 0; i < declarativeWebRequestSchema.types.length; ++i) {
+ var typeSchema = declarativeWebRequestSchema.types[i];
+ var typeId = typeSchema.id.replace('declarativeWebRequest.', '');
+ var action = function(typeId) {
+ return function(parameters) {
+ setupInstance(this, parameters, typeId);
+ };
+ }(typeId);
+ webViewRequest[typeId] = action;
+ }
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/i18n_custom_bindings.js b/chromium/extensions/renderer/resources/i18n_custom_bindings.js
new file mode 100644
index 00000000000..de69fa2ebe0
--- /dev/null
+++ b/chromium/extensions/renderer/resources/i18n_custom_bindings.js
@@ -0,0 +1,49 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Custom binding for the i18n API.
+
+var binding = require('binding').Binding.create('i18n');
+
+var i18nNatives = requireNative('i18n');
+var GetL10nMessage = i18nNatives.GetL10nMessage;
+var GetL10nUILanguage = i18nNatives.GetL10nUILanguage;
+var DetectTextLanguage = i18nNatives.DetectTextLanguage;
+
+binding.registerCustomHook(function(bindingsAPI, extensionId) {
+ var apiFunctions = bindingsAPI.apiFunctions;
+
+ apiFunctions.setUpdateArgumentsPreValidate('getMessage', function() {
+ var args = $Array.slice(arguments);
+
+ // The first argument is the message, and should be a string.
+ var message = args[0];
+ if (typeof(message) !== 'string') {
+ console.warn(extensionId + ': the first argument to getMessage should ' +
+ 'be type "string", was ' + message +
+ ' (type "' + typeof(message) + '")');
+ args[0] = String(message);
+ }
+
+ return args;
+ });
+
+ apiFunctions.setHandleRequest('getMessage',
+ function(messageName, substitutions) {
+ return GetL10nMessage(messageName, substitutions, extensionId);
+ });
+
+ apiFunctions.setHandleRequest('getUILanguage', function() {
+ return GetL10nUILanguage();
+ });
+
+ apiFunctions.setHandleRequest('detectLanguage', function(text, callback) {
+ window.setTimeout(function() {
+ var response = DetectTextLanguage(text);
+ callback(response);
+ }, 0);
+ });
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/image_util.js b/chromium/extensions/renderer/resources/image_util.js
new file mode 100644
index 00000000000..a7a27a64a66
--- /dev/null
+++ b/chromium/extensions/renderer/resources/image_util.js
@@ -0,0 +1,82 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This function takes an object |imageSpec| with the key |path| -
+// corresponding to the internet URL to be translated - and optionally
+// |width| and |height| which are the maximum dimensions to be used when
+// converting the image.
+function loadImageData(imageSpec, callbacks) {
+ var path = imageSpec.path;
+ var img = new Image();
+ if (typeof callbacks.onerror === 'function') {
+ img.onerror = function() {
+ callbacks.onerror({ problem: 'could_not_load', path: path });
+ };
+ }
+ img.onload = function() {
+ var canvas = document.createElement('canvas');
+
+ if (img.width <= 0 || img.height <= 0) {
+ callbacks.onerror({ problem: 'image_size_invalid', path: path});
+ return;
+ }
+
+ var scaleFactor = 1;
+ if (imageSpec.width && imageSpec.width < img.width)
+ scaleFactor = imageSpec.width / img.width;
+
+ if (imageSpec.height && imageSpec.height < img.height) {
+ var heightScale = imageSpec.height / img.height;
+ if (heightScale < scaleFactor)
+ scaleFactor = heightScale;
+ }
+
+ canvas.width = img.width * scaleFactor;
+ canvas.height = img.height * scaleFactor;
+
+ var canvas_context = canvas.getContext('2d');
+ canvas_context.clearRect(0, 0, canvas.width, canvas.height);
+ canvas_context.drawImage(img, 0, 0, canvas.width, canvas.height);
+ try {
+ var imageData = canvas_context.getImageData(
+ 0, 0, canvas.width, canvas.height);
+ if (typeof callbacks.oncomplete === 'function') {
+ callbacks.oncomplete(
+ imageData.width, imageData.height, imageData.data.buffer);
+ }
+ } catch (e) {
+ if (typeof callbacks.onerror === 'function') {
+ callbacks.onerror({ problem: 'data_url_unavailable', path: path });
+ }
+ }
+ }
+ img.src = path;
+}
+
+function on_complete_index(index, err, loading, finished, callbacks) {
+ return function(width, height, imageData) {
+ delete loading[index];
+ finished[index] = { width: width, height: height, data: imageData };
+ if (err)
+ callbacks.onerror(index);
+ if ($Object.keys(loading).length == 0)
+ callbacks.oncomplete(finished);
+ }
+}
+
+function loadAllImages(imageSpecs, callbacks) {
+ var loading = {}, finished = [],
+ index, pathname;
+
+ for (var index = 0; index < imageSpecs.length; index++) {
+ loading[index] = imageSpecs[index];
+ loadImageData(imageSpecs[index], {
+ oncomplete: on_complete_index(index, false, loading, finished, callbacks),
+ onerror: on_complete_index(index, true, loading, finished, callbacks)
+ });
+ }
+}
+
+exports.$set('loadImageData', loadImageData);
+exports.$set('loadAllImages', loadAllImages);
diff --git a/chromium/extensions/renderer/resources/json_schema.js b/chromium/extensions/renderer/resources/json_schema.js
new file mode 100644
index 00000000000..447d1ea21c2
--- /dev/null
+++ b/chromium/extensions/renderer/resources/json_schema.js
@@ -0,0 +1,525 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// -----------------------------------------------------------------------------
+// NOTE: If you change this file you need to touch
+// extension_renderer_resources.grd to have your change take effect.
+// -----------------------------------------------------------------------------
+
+//==============================================================================
+// This file contains a class that implements a subset of JSON Schema.
+// See: http://www.json.com/json-schema-proposal/ for more details.
+//
+// The following features of JSON Schema are not implemented:
+// - requires
+// - unique
+// - disallow
+// - union types (but replaced with 'choices')
+//
+// The following properties are not applicable to the interface exposed by
+// this class:
+// - options
+// - readonly
+// - title
+// - description
+// - format
+// - default
+// - transient
+// - hidden
+//
+// There are also these departures from the JSON Schema proposal:
+// - function and undefined types are supported
+// - null counts as 'unspecified' for optional values
+// - added the 'choices' property, to allow specifying a list of possible types
+// for a value
+// - by default an "object" typed schema does not allow additional properties.
+// if present, "additionalProperties" is to be a schema against which all
+// additional properties will be validated.
+//==============================================================================
+
+var loadTypeSchema = require('utils').loadTypeSchema;
+var CHECK = requireNative('logging').CHECK;
+
+function isInstanceOfClass(instance, className) {
+ while ((instance = instance.__proto__)) {
+ if (instance.constructor.name == className)
+ return true;
+ }
+ return false;
+}
+
+function isOptionalValue(value) {
+ return typeof(value) === 'undefined' || value === null;
+}
+
+function enumToString(enumValue) {
+ if (enumValue.name === undefined)
+ return enumValue;
+
+ return enumValue.name;
+}
+
+/**
+ * Validates an instance against a schema and accumulates errors. Usage:
+ *
+ * var validator = new JSONSchemaValidator();
+ * validator.validate(inst, schema);
+ * if (validator.errors.length == 0)
+ * console.log("Valid!");
+ * else
+ * console.log(validator.errors);
+ *
+ * The errors property contains a list of objects. Each object has two
+ * properties: "path" and "message". The "path" property contains the path to
+ * the key that had the problem, and the "message" property contains a sentence
+ * describing the error.
+ */
+function JSONSchemaValidator() {
+ this.errors = [];
+ this.types = [];
+}
+
+JSONSchemaValidator.messages = {
+ invalidEnum: "Value must be one of: [*].",
+ propertyRequired: "Property is required.",
+ unexpectedProperty: "Unexpected property.",
+ arrayMinItems: "Array must have at least * items.",
+ arrayMaxItems: "Array must not have more than * items.",
+ itemRequired: "Item is required.",
+ stringMinLength: "String must be at least * characters long.",
+ stringMaxLength: "String must not be more than * characters long.",
+ stringPattern: "String must match the pattern: *.",
+ numberFiniteNotNan: "Value must not be *.",
+ numberMinValue: "Value must not be less than *.",
+ numberMaxValue: "Value must not be greater than *.",
+ numberIntValue: "Value must fit in a 32-bit signed integer.",
+ numberMaxDecimal: "Value must not have more than * decimal places.",
+ invalidType: "Expected '*' but got '*'.",
+ invalidTypeIntegerNumber:
+ "Expected 'integer' but got 'number', consider using Math.round().",
+ invalidChoice: "Value does not match any valid type choices.",
+ invalidPropertyType: "Missing property type.",
+ schemaRequired: "Schema value required.",
+ unknownSchemaReference: "Unknown schema reference: *.",
+ notInstance: "Object must be an instance of *."
+};
+
+/**
+ * Builds an error message. Key is the property in the |errors| object, and
+ * |opt_replacements| is an array of values to replace "*" characters with.
+ */
+JSONSchemaValidator.formatError = function(key, opt_replacements) {
+ var message = this.messages[key];
+ if (opt_replacements) {
+ for (var i = 0; i < opt_replacements.length; i++) {
+ message = message.replace("*", opt_replacements[i]);
+ }
+ }
+ return message;
+};
+
+/**
+ * Classifies a value as one of the JSON schema primitive types. Note that we
+ * don't explicitly disallow 'function', because we want to allow functions in
+ * the input values.
+ */
+JSONSchemaValidator.getType = function(value) {
+ var s = typeof value;
+
+ if (s == "object") {
+ if (value === null) {
+ return "null";
+ } else if (Object.prototype.toString.call(value) == "[object Array]") {
+ return "array";
+ } else if (Object.prototype.toString.call(value) ==
+ "[object ArrayBuffer]") {
+ return "binary";
+ }
+ } else if (s == "number") {
+ if (value % 1 == 0) {
+ return "integer";
+ }
+ }
+
+ return s;
+};
+
+/**
+ * Add types that may be referenced by validated schemas that reference them
+ * with "$ref": <typeId>. Each type must be a valid schema and define an
+ * "id" property.
+ */
+JSONSchemaValidator.prototype.addTypes = function(typeOrTypeList) {
+ function addType(validator, type) {
+ if (!type.id)
+ throw new Error("Attempt to addType with missing 'id' property");
+ validator.types[type.id] = type;
+ }
+
+ if (typeOrTypeList instanceof Array) {
+ for (var i = 0; i < typeOrTypeList.length; i++) {
+ addType(this, typeOrTypeList[i]);
+ }
+ } else {
+ addType(this, typeOrTypeList);
+ }
+}
+
+/**
+ * Returns a list of strings of the types that this schema accepts.
+ */
+JSONSchemaValidator.prototype.getAllTypesForSchema = function(schema) {
+ var schemaTypes = [];
+ if (schema.type)
+ $Array.push(schemaTypes, schema.type);
+ if (schema.choices) {
+ for (var i = 0; i < schema.choices.length; i++) {
+ var choiceTypes = this.getAllTypesForSchema(schema.choices[i]);
+ schemaTypes = $Array.concat(schemaTypes, choiceTypes);
+ }
+ }
+ var ref = schema['$ref'];
+ if (ref) {
+ var type = this.getOrAddType(ref);
+ CHECK(type, 'Could not find type ' + ref);
+ schemaTypes = $Array.concat(schemaTypes, this.getAllTypesForSchema(type));
+ }
+ return schemaTypes;
+};
+
+JSONSchemaValidator.prototype.getOrAddType = function(typeName) {
+ if (!this.types[typeName])
+ this.types[typeName] = loadTypeSchema(typeName);
+ return this.types[typeName];
+};
+
+/**
+ * Returns true if |schema| would accept an argument of type |type|.
+ */
+JSONSchemaValidator.prototype.isValidSchemaType = function(type, schema) {
+ if (type == 'any')
+ return true;
+
+ // TODO(kalman): I don't understand this code. How can type be "null"?
+ if (schema.optional && (type == "null" || type == "undefined"))
+ return true;
+
+ var schemaTypes = this.getAllTypesForSchema(schema);
+ for (var i = 0; i < schemaTypes.length; i++) {
+ if (schemaTypes[i] == "any" || type == schemaTypes[i] ||
+ (type == "integer" && schemaTypes[i] == "number"))
+ return true;
+ }
+
+ return false;
+};
+
+/**
+ * Returns true if there is a non-null argument that both |schema1| and
+ * |schema2| would accept.
+ */
+JSONSchemaValidator.prototype.checkSchemaOverlap = function(schema1, schema2) {
+ var schema1Types = this.getAllTypesForSchema(schema1);
+ for (var i = 0; i < schema1Types.length; i++) {
+ if (this.isValidSchemaType(schema1Types[i], schema2))
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Validates an instance against a schema. The instance can be any JavaScript
+ * value and will be validated recursively. When this method returns, the
+ * |errors| property will contain a list of errors, if any.
+ */
+JSONSchemaValidator.prototype.validate = function(instance, schema, opt_path) {
+ var path = opt_path || "";
+
+ if (!schema) {
+ this.addError(path, "schemaRequired");
+ return;
+ }
+
+ // If this schema defines itself as reference type, save it in this.types.
+ if (schema.id)
+ this.types[schema.id] = schema;
+
+ // If the schema has an extends property, the instance must validate against
+ // that schema too.
+ if (schema.extends)
+ this.validate(instance, schema.extends, path);
+
+ // If the schema has a $ref property, the instance must validate against
+ // that schema too. It must be present in this.types to be referenced.
+ var ref = schema["$ref"];
+ if (ref) {
+ if (!this.getOrAddType(ref))
+ this.addError(path, "unknownSchemaReference", [ ref ]);
+ else
+ this.validate(instance, this.getOrAddType(ref), path)
+ }
+
+ // If the schema has a choices property, the instance must validate against at
+ // least one of the items in that array.
+ if (schema.choices) {
+ this.validateChoices(instance, schema, path);
+ return;
+ }
+
+ // If the schema has an enum property, the instance must be one of those
+ // values.
+ if (schema.enum) {
+ if (!this.validateEnum(instance, schema, path))
+ return;
+ }
+
+ if (schema.type && schema.type != "any") {
+ if (!this.validateType(instance, schema, path))
+ return;
+
+ // Type-specific validation.
+ switch (schema.type) {
+ case "object":
+ this.validateObject(instance, schema, path);
+ break;
+ case "array":
+ this.validateArray(instance, schema, path);
+ break;
+ case "string":
+ this.validateString(instance, schema, path);
+ break;
+ case "number":
+ case "integer":
+ this.validateNumber(instance, schema, path);
+ break;
+ }
+ }
+};
+
+/**
+ * Validates an instance against a choices schema. The instance must match at
+ * least one of the provided choices.
+ */
+JSONSchemaValidator.prototype.validateChoices =
+ function(instance, schema, path) {
+ var originalErrors = this.errors;
+
+ for (var i = 0; i < schema.choices.length; i++) {
+ this.errors = [];
+ this.validate(instance, schema.choices[i], path);
+ if (this.errors.length == 0) {
+ this.errors = originalErrors;
+ return;
+ }
+ }
+
+ this.errors = originalErrors;
+ this.addError(path, "invalidChoice");
+};
+
+/**
+ * Validates an instance against a schema with an enum type. Populates the
+ * |errors| property, and returns a boolean indicating whether the instance
+ * validates.
+ */
+JSONSchemaValidator.prototype.validateEnum = function(instance, schema, path) {
+ for (var i = 0; i < schema.enum.length; i++) {
+ if (instance === enumToString(schema.enum[i]))
+ return true;
+ }
+
+ this.addError(path, "invalidEnum",
+ [$Array.join($Array.map(schema.enum, enumToString), ", ")]);
+ return false;
+};
+
+/**
+ * Validates an instance against an object schema and populates the errors
+ * property.
+ */
+JSONSchemaValidator.prototype.validateObject =
+ function(instance, schema, path) {
+ if (schema.properties) {
+ for (var prop in schema.properties) {
+ // It is common in JavaScript to add properties to Object.prototype. This
+ // check prevents such additions from being interpreted as required
+ // schema properties.
+ // TODO(aa): If it ever turns out that we actually want this to work,
+ // there are other checks we could put here, like requiring that schema
+ // properties be objects that have a 'type' property.
+ if (!$Object.hasOwnProperty(schema.properties, prop))
+ continue;
+
+ var propPath = path ? path + "." + prop : prop;
+ if (schema.properties[prop] == undefined) {
+ this.addError(propPath, "invalidPropertyType");
+ } else if (prop in instance && !isOptionalValue(instance[prop])) {
+ this.validate(instance[prop], schema.properties[prop], propPath);
+ } else if (!schema.properties[prop].optional) {
+ this.addError(propPath, "propertyRequired");
+ }
+ }
+ }
+
+ // If "instanceof" property is set, check that this object inherits from
+ // the specified constructor (function).
+ if (schema.isInstanceOf) {
+ if (!isInstanceOfClass(instance, schema.isInstanceOf))
+ this.addError(propPath, "notInstance", [schema.isInstanceOf]);
+ }
+
+ // Exit early from additional property check if "type":"any" is defined.
+ if (schema.additionalProperties &&
+ schema.additionalProperties.type &&
+ schema.additionalProperties.type == "any") {
+ return;
+ }
+
+ // By default, additional properties are not allowed on instance objects. This
+ // can be overridden by setting the additionalProperties property to a schema
+ // which any additional properties must validate against.
+ for (var prop in instance) {
+ if (schema.properties && prop in schema.properties)
+ continue;
+
+ // Any properties inherited through the prototype are ignored.
+ if (!$Object.hasOwnProperty(instance, prop))
+ continue;
+
+ var propPath = path ? path + "." + prop : prop;
+ if (schema.additionalProperties)
+ this.validate(instance[prop], schema.additionalProperties, propPath);
+ else
+ this.addError(propPath, "unexpectedProperty");
+ }
+};
+
+/**
+ * Validates an instance against an array schema and populates the errors
+ * property.
+ */
+JSONSchemaValidator.prototype.validateArray = function(instance, schema, path) {
+ var typeOfItems = JSONSchemaValidator.getType(schema.items);
+
+ if (typeOfItems == 'object') {
+ if (schema.minItems && instance.length < schema.minItems) {
+ this.addError(path, "arrayMinItems", [schema.minItems]);
+ }
+
+ if (typeof schema.maxItems != "undefined" &&
+ instance.length > schema.maxItems) {
+ this.addError(path, "arrayMaxItems", [schema.maxItems]);
+ }
+
+ // If the items property is a single schema, each item in the array must
+ // have that schema.
+ for (var i = 0; i < instance.length; i++) {
+ this.validate(instance[i], schema.items, path + "." + i);
+ }
+ } else if (typeOfItems == 'array') {
+ // If the items property is an array of schemas, each item in the array must
+ // validate against the corresponding schema.
+ for (var i = 0; i < schema.items.length; i++) {
+ var itemPath = path ? path + "." + i : String(i);
+ if (i in instance && !isOptionalValue(instance[i])) {
+ this.validate(instance[i], schema.items[i], itemPath);
+ } else if (!schema.items[i].optional) {
+ this.addError(itemPath, "itemRequired");
+ }
+ }
+
+ if (schema.additionalProperties) {
+ for (var i = schema.items.length; i < instance.length; i++) {
+ var itemPath = path ? path + "." + i : String(i);
+ this.validate(instance[i], schema.additionalProperties, itemPath);
+ }
+ } else {
+ if (instance.length > schema.items.length) {
+ this.addError(path, "arrayMaxItems", [schema.items.length]);
+ }
+ }
+ }
+};
+
+/**
+ * Validates a string and populates the errors property.
+ */
+JSONSchemaValidator.prototype.validateString =
+ function(instance, schema, path) {
+ if (schema.minLength && instance.length < schema.minLength)
+ this.addError(path, "stringMinLength", [schema.minLength]);
+
+ if (schema.maxLength && instance.length > schema.maxLength)
+ this.addError(path, "stringMaxLength", [schema.maxLength]);
+
+ if (schema.pattern && !schema.pattern.test(instance))
+ this.addError(path, "stringPattern", [schema.pattern]);
+};
+
+/**
+ * Validates a number and populates the errors property. The instance is
+ * assumed to be a number.
+ */
+JSONSchemaValidator.prototype.validateNumber =
+ function(instance, schema, path) {
+ // Forbid NaN, +Infinity, and -Infinity. Our APIs don't use them, and
+ // JSON serialization encodes them as 'null'. Re-evaluate supporting
+ // them if we add an API that could reasonably take them as a parameter.
+ if (isNaN(instance) ||
+ instance == Number.POSITIVE_INFINITY ||
+ instance == Number.NEGATIVE_INFINITY )
+ this.addError(path, "numberFiniteNotNan", [instance]);
+
+ if (schema.minimum !== undefined && instance < schema.minimum)
+ this.addError(path, "numberMinValue", [schema.minimum]);
+
+ if (schema.maximum !== undefined && instance > schema.maximum)
+ this.addError(path, "numberMaxValue", [schema.maximum]);
+
+ // Check for integer values outside of -2^31..2^31-1.
+ if (schema.type === "integer" && (instance | 0) !== instance)
+ this.addError(path, "numberIntValue", []);
+
+ if (schema.maxDecimal && instance * Math.pow(10, schema.maxDecimal) % 1)
+ this.addError(path, "numberMaxDecimal", [schema.maxDecimal]);
+};
+
+/**
+ * Validates the primitive type of an instance and populates the errors
+ * property. Returns true if the instance validates, false otherwise.
+ */
+JSONSchemaValidator.prototype.validateType = function(instance, schema, path) {
+ var actualType = JSONSchemaValidator.getType(instance);
+ if (schema.type == actualType ||
+ (schema.type == "number" && actualType == "integer")) {
+ return true;
+ } else if (schema.type == "integer" && actualType == "number") {
+ this.addError(path, "invalidTypeIntegerNumber");
+ return false;
+ } else {
+ this.addError(path, "invalidType", [schema.type, actualType]);
+ return false;
+ }
+};
+
+/**
+ * Adds an error message. |key| is an index into the |messages| object.
+ * |replacements| is an array of values to replace '*' characters in the
+ * message.
+ */
+JSONSchemaValidator.prototype.addError = function(path, key, replacements) {
+ $Array.push(this.errors, {
+ path: path,
+ message: JSONSchemaValidator.formatError(key, replacements)
+ });
+};
+
+/**
+ * Resets errors to an empty list so you can call 'validate' again.
+ */
+JSONSchemaValidator.prototype.resetErrors = function() {
+ this.errors = [];
+};
+
+exports.$set('JSONSchemaValidator', JSONSchemaValidator);
diff --git a/chromium/extensions/renderer/resources/keep_alive.js b/chromium/extensions/renderer/resources/keep_alive.js
new file mode 100644
index 00000000000..5269c30285f
--- /dev/null
+++ b/chromium/extensions/renderer/resources/keep_alive.js
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define('keep_alive', [
+ 'content/public/renderer/frame_service_registry',
+ 'extensions/common/mojo/keep_alive.mojom',
+ 'mojo/public/js/core',
+], function(serviceProvider, mojom, core) {
+
+ /**
+ * An object that keeps the background page alive until closed.
+ * @constructor
+ * @alias module:keep_alive~KeepAlive
+ */
+ function KeepAlive() {
+ /**
+ * The handle to the keep-alive object in the browser.
+ * @type {!MojoHandle}
+ * @private
+ */
+ this.handle_ = serviceProvider.connectToService(mojom.KeepAlive.name);
+ }
+
+ /**
+ * Removes this keep-alive.
+ */
+ KeepAlive.prototype.close = function() {
+ core.close(this.handle_);
+ };
+
+ var exports = {};
+
+ return {
+ /**
+ * Creates a keep-alive.
+ * @return {!module:keep_alive~KeepAlive} A new keep-alive.
+ */
+ createKeepAlive: function() { return new KeepAlive(); }
+ };
+});
diff --git a/chromium/extensions/renderer/resources/last_error.js b/chromium/extensions/renderer/resources/last_error.js
new file mode 100644
index 00000000000..fc0c7b5e4d3
--- /dev/null
+++ b/chromium/extensions/renderer/resources/last_error.js
@@ -0,0 +1,143 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var GetAvailability = requireNative('v8_context').GetAvailability;
+var GetGlobal = requireNative('sendRequest').GetGlobal;
+
+// Utility for setting chrome.*.lastError.
+//
+// A utility here is useful for two reasons:
+// 1. For backwards compatibility we need to set chrome.extension.lastError,
+// but not all contexts actually have access to the extension namespace.
+// 2. When calling across contexts, the global object that gets lastError set
+// needs to be that of the caller. We force callers to explicitly specify
+// the chrome object to try to prevent bugs here.
+
+/**
+ * Sets the last error for |name| on |targetChrome| to |message| with an
+ * optional |stack|.
+ */
+function set(name, message, stack, targetChrome) {
+ if (!targetChrome) {
+ var errorMessage = name + ': ' + message;
+ if (stack != null && stack != '')
+ errorMessage += '\n' + stack;
+ throw new Error('No chrome object to set error: ' + errorMessage);
+ }
+ clear(targetChrome); // in case somebody has set a sneaky getter/setter
+
+ var errorObject = { message: message };
+ if (GetAvailability('extension.lastError').is_available)
+ targetChrome.extension.lastError = errorObject;
+
+ assertRuntimeIsAvailable();
+
+ // We check to see if developers access runtime.lastError in order to decide
+ // whether or not to log it in the (error) console.
+ privates(targetChrome.runtime).accessedLastError = false;
+ $Object.defineProperty(targetChrome.runtime, 'lastError', {
+ configurable: true,
+ get: function() {
+ privates(targetChrome.runtime).accessedLastError = true;
+ return errorObject;
+ },
+ set: function(error) {
+ errorObject = errorObject;
+ }});
+};
+
+/**
+ * Check if anyone has checked chrome.runtime.lastError since it was set.
+ * @param {Object} targetChrome the Chrome object to check.
+ * @return boolean True if the lastError property was set.
+ */
+function hasAccessed(targetChrome) {
+ assertRuntimeIsAvailable();
+ return privates(targetChrome.runtime).accessedLastError === true;
+}
+
+/**
+ * Check whether there is an error set on |targetChrome| without setting
+ * |accessedLastError|.
+ * @param {Object} targetChrome the Chrome object to check.
+ * @return boolean Whether lastError has been set.
+ */
+function hasError(targetChrome) {
+ if (!targetChrome)
+ throw new Error('No target chrome to check');
+
+ assertRuntimeIsAvailable();
+ if ('lastError' in targetChrome.runtime)
+ return true;
+
+ return false;
+};
+
+/**
+ * Clears the last error on |targetChrome|.
+ */
+function clear(targetChrome) {
+ if (!targetChrome)
+ throw new Error('No target chrome to clear error');
+
+ if (GetAvailability('extension.lastError').is_available)
+ delete targetChrome.extension.lastError;
+
+ assertRuntimeIsAvailable();
+ delete targetChrome.runtime.lastError;
+ delete privates(targetChrome.runtime).accessedLastError;
+};
+
+function assertRuntimeIsAvailable() {
+ // chrome.runtime should always be available, but maybe it's disappeared for
+ // some reason? Add debugging for http://crbug.com/258526.
+ var runtimeAvailability = GetAvailability('runtime.lastError');
+ if (!runtimeAvailability.is_available) {
+ throw new Error('runtime.lastError is not available: ' +
+ runtimeAvailability.message);
+ }
+ if (!chrome.runtime)
+ throw new Error('runtime namespace is null or undefined');
+}
+
+/**
+ * Runs |callback(args)| with last error args as in set().
+ *
+ * The target chrome object is the global object's of the callback, so this
+ * method won't work if the real callback has been wrapped (etc).
+ */
+function run(name, message, stack, callback, args) {
+ var global = GetGlobal(callback);
+ var targetChrome = global && global.chrome;
+ set(name, message, stack, targetChrome);
+ try {
+ $Function.apply(callback, undefined, args);
+ } finally {
+ reportIfUnchecked(name, targetChrome, stack);
+ clear(targetChrome);
+ }
+}
+
+/**
+ * Checks whether chrome.runtime.lastError has been accessed if set.
+ * If it was set but not accessed, the error is reported to the console.
+ *
+ * @param {string=} name - name of API.
+ * @param {Object} targetChrome - the Chrome object to check.
+ * @param {string=} stack - Stack trace of the call up to the error.
+ */
+function reportIfUnchecked(name, targetChrome, stack) {
+ if (hasAccessed(targetChrome) || !hasError(targetChrome))
+ return;
+ var message = targetChrome.runtime.lastError.message;
+ console.error("Unchecked runtime.lastError while running " +
+ (name || "unknown") + ": " + message + (stack ? "\n" + stack : ""));
+}
+
+exports.$set('clear', clear);
+exports.$set('hasAccessed', hasAccessed);
+exports.$set('hasError', hasError);
+exports.$set('set', set);
+exports.$set('run', run);
+exports.$set('reportIfUnchecked', reportIfUnchecked);
diff --git a/chromium/extensions/renderer/resources/media_router_bindings.js b/chromium/extensions/renderer/resources/media_router_bindings.js
new file mode 100644
index 00000000000..4eaee9f0286
--- /dev/null
+++ b/chromium/extensions/renderer/resources/media_router_bindings.js
@@ -0,0 +1,777 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var mediaRouter;
+
+define('media_router_bindings', [
+ 'mojo/public/js/bindings',
+ 'mojo/public/js/core',
+ 'content/public/renderer/frame_service_registry',
+ 'chrome/browser/media/router/mojo/media_router.mojom',
+ 'extensions/common/mojo/keep_alive.mojom',
+ 'mojo/public/js/connection',
+ 'mojo/public/js/router',
+], function(bindings,
+ core,
+ serviceProvider,
+ mediaRouterMojom,
+ keepAliveMojom,
+ connector,
+ routerModule) {
+ 'use strict';
+
+ /**
+ * Converts a media sink to a MediaSink Mojo object.
+ * @param {!MediaSink} sink A media sink.
+ * @return {!mediaRouterMojom.MediaSink} A Mojo MediaSink object.
+ */
+ function sinkToMojo_(sink) {
+ return new mediaRouterMojom.MediaSink({
+ 'name': sink.friendlyName,
+ 'description': sink.description,
+ 'domain': sink.domain,
+ 'sink_id': sink.id,
+ 'icon_type': sinkIconTypeToMojo(sink.iconType),
+ });
+ }
+
+ /**
+ * Converts a media sink's icon type to a MediaSink.IconType Mojo object.
+ * @param {!MediaSink.IconType} type A media sink's icon type.
+ * @return {!mediaRouterMojom.MediaSink.IconType} A Mojo MediaSink.IconType
+ * object.
+ */
+ function sinkIconTypeToMojo(type) {
+ switch (type) {
+ case 'cast':
+ return mediaRouterMojom.MediaSink.IconType.CAST;
+ case 'cast_audio':
+ return mediaRouterMojom.MediaSink.IconType.CAST_AUDIO;
+ case 'cast_audio_group':
+ return mediaRouterMojom.MediaSink.IconType.CAST_AUDIO_GROUP;
+ case 'generic':
+ return mediaRouterMojom.MediaSink.IconType.GENERIC;
+ case 'hangout':
+ return mediaRouterMojom.MediaSink.IconType.HANGOUT;
+ default:
+ console.error('Unknown sink icon type : ' + type);
+ return mediaRouterMojom.MediaSink.IconType.GENERIC;
+ }
+ }
+
+ /**
+ * Returns a Mojo MediaRoute object given a MediaRoute and a
+ * media sink name.
+ * @param {!MediaRoute} route
+ * @return {!mojo.MediaRoute}
+ */
+ function routeToMojo_(route) {
+ return new mediaRouterMojom.MediaRoute({
+ 'media_route_id': route.id,
+ 'media_source': route.mediaSource,
+ 'media_sink_id': route.sinkId,
+ 'description': route.description,
+ 'icon_url': route.iconUrl,
+ 'is_local': route.isLocal,
+ 'custom_controller_path': route.customControllerPath,
+ // Begin newly added properties, followed by the milestone they were
+ // added. The guard should be safe to remove N+2 milestones later.
+ 'for_display': route.forDisplay, // M47
+ 'off_the_record': !!route.offTheRecord // M50
+ });
+ }
+
+ /**
+ * Converts a route message to a RouteMessage Mojo object.
+ * @param {!RouteMessage} message
+ * @return {!mediaRouterMojom.RouteMessage} A Mojo RouteMessage object.
+ */
+ function messageToMojo_(message) {
+ if ("string" == typeof message.message) {
+ return new mediaRouterMojom.RouteMessage({
+ 'type': mediaRouterMojom.RouteMessage.Type.TEXT,
+ 'message': message.message,
+ });
+ } else {
+ return new mediaRouterMojom.RouteMessage({
+ 'type': mediaRouterMojom.RouteMessage.Type.BINARY,
+ 'data': message.message,
+ });
+ }
+ }
+
+ /**
+ * Converts presentation connection state to Mojo enum value.
+ * @param {!string} state
+ * @return {!mediaRouterMojom.MediaRouter.PresentationConnectionState}
+ */
+ function presentationConnectionStateToMojo_(state) {
+ var PresentationConnectionState =
+ mediaRouterMojom.MediaRouter.PresentationConnectionState;
+ switch (state) {
+ case 'connecting':
+ return PresentationConnectionState.CONNECTING;
+ case 'connected':
+ return PresentationConnectionState.CONNECTED;
+ case 'closed':
+ return PresentationConnectionState.CLOSED;
+ case 'terminated':
+ return PresentationConnectionState.TERMINATED;
+ default:
+ console.error('Unknown presentation connection state: ' + state);
+ return PresentationConnectionState.TERMINATED;
+ }
+ }
+
+ /**
+ * Converts presentation connection close reason to Mojo enum value.
+ * @param {!string} reason
+ * @return {!mediaRouterMojom.MediaRouter.PresentationConnectionCloseReason}
+ */
+ function presentationConnectionCloseReasonToMojo_(reason) {
+ var PresentationConnectionCloseReason =
+ mediaRouterMojom.MediaRouter.PresentationConnectionCloseReason;
+ switch (reason) {
+ case 'error':
+ return PresentationConnectionCloseReason.CONNECTION_ERROR;
+ case 'closed':
+ return PresentationConnectionCloseReason.CLOSED;
+ case 'went_away':
+ return PresentationConnectionCloseReason.WENT_AWAY;
+ default:
+ console.error('Unknown presentation connection close reason : ' +
+ reason);
+ return PresentationConnectionCloseReason.CONNECTION_ERROR;
+ }
+ }
+
+ /**
+ * Parses the given route request Error object and converts it to the
+ * corresponding result code.
+ * @param {!Error} error
+ * @return {!mediaRouterMojom.RouteRequestResultCode}
+ */
+ function getRouteRequestResultCode_(error) {
+ if (error.message.startsWith('timeout'))
+ return mediaRouterMojom.RouteRequestResultCode.TIMED_OUT;
+ else
+ return mediaRouterMojom.RouteRequestResultCode.UNKNOWN_ERROR;
+ }
+
+ /**
+ * Creates and returns a successful route response from given route.
+ * @param {!MediaRoute} route
+ * @return {!Object}
+ */
+ function toSuccessRouteResponse_(route) {
+ return {
+ route: routeToMojo_(route),
+ result_code: mediaRouterMojom.RouteRequestResultCode.OK
+ };
+ }
+
+ /**
+ * Creates and returns a error route response from given Error object
+ * @param {!Error} error
+ * @return {!Object}
+ */
+ function toErrorRouteResponse_(error) {
+ return {
+ error_text: 'Error creating route: ' + error.message,
+ result_code: getRouteRequestResultCode_(error)
+ };
+ }
+
+ /**
+ * Creates a new MediaRouter.
+ * Converts a route struct to its Mojo form.
+ * @param {!MediaRouterService} service
+ * @constructor
+ */
+ function MediaRouter(service) {
+ /**
+ * The Mojo service proxy. Allows extension code to call methods that reside
+ * in the browser.
+ * @type {!MediaRouterService}
+ */
+ this.service_ = service;
+
+ /**
+ * The provider manager service delegate. Its methods are called by the
+ * browser-resident Mojo service.
+ * @type {!MediaRouter}
+ */
+ this.mrpm_ = new MediaRouteProvider(this);
+
+ /**
+ * The message pipe that connects the Media Router to mrpm_ across
+ * browser/renderer IPC boundaries. Object must remain in scope for the
+ * lifetime of the connection to prevent the connection from closing
+ * automatically.
+ * @type {!mojo.MessagePipe}
+ */
+ this.pipe_ = core.createMessagePipe();
+
+ /**
+ * Handle to a KeepAlive service object, which prevents the extension from
+ * being suspended as long as it remains in scope.
+ * @type {boolean}
+ */
+ this.keepAlive_ = null;
+
+ /**
+ * The stub used to bind the service delegate to the Mojo interface.
+ * Object must remain in scope for the lifetime of the connection to
+ * prevent the connection from closing automatically.
+ * @type {!mojom.MediaRouter}
+ */
+ this.mediaRouteProviderStub_ = connector.bindHandleToStub(
+ this.pipe_.handle0, mediaRouterMojom.MediaRouteProvider);
+
+ // Link mediaRouteProviderStub_ to the provider manager delegate.
+ bindings.StubBindings(this.mediaRouteProviderStub_).delegate = this.mrpm_;
+ }
+
+ /**
+ * Registers the Media Router Provider Manager with the Media Router.
+ * @return {!Promise<string>} Instance ID for the Media Router.
+ */
+ MediaRouter.prototype.start = function() {
+ return this.service_.registerMediaRouteProvider(this.pipe_.handle1).then(
+ function(result) {
+ return result.instance_id;
+ }.bind(this));
+ }
+
+ /**
+ * Sets the service delegate methods.
+ * @param {Object} handlers
+ */
+ MediaRouter.prototype.setHandlers = function(handlers) {
+ this.mrpm_.setHandlers(handlers);
+ }
+
+ /**
+ * The keep alive status.
+ * @return {boolean}
+ */
+ MediaRouter.prototype.getKeepAlive = function() {
+ return this.keepAlive_ != null;
+ };
+
+ /**
+ * Called by the provider manager when a sink list for a given source is
+ * updated.
+ * @param {!string} sourceUrn
+ * @param {!Array<!MediaSink>} sinks
+ * @param {Array<string>=} opt_origins
+ */
+ MediaRouter.prototype.onSinksReceived = function(sourceUrn, sinks,
+ opt_origins) {
+ // TODO(imcheng): Make origins required in M52+.
+ this.service_.onSinksReceived(sourceUrn, sinks.map(sinkToMojo_),
+ opt_origins || []);
+ };
+
+ /**
+ * Called by the provider manager to keep the extension from suspending
+ * if it enters a state where suspension is undesirable (e.g. there is an
+ * active MediaRoute.)
+ * If keepAlive is true, the extension is kept alive.
+ * If keepAlive is false, the extension is allowed to suspend.
+ * @param {boolean} keepAlive
+ */
+ MediaRouter.prototype.setKeepAlive = function(keepAlive) {
+ if (keepAlive === false && this.keepAlive_) {
+ this.keepAlive_.close();
+ this.keepAlive_ = null;
+ } else if (keepAlive === true && !this.keepAlive_) {
+ this.keepAlive_ = new routerModule.Router(
+ serviceProvider.connectToService(
+ keepAliveMojom.KeepAlive.name));
+ }
+ };
+
+ /**
+ * Called by the provider manager to send an issue from a media route
+ * provider to the Media Router, to show the user.
+ * @param {!Object} issue The issue object.
+ */
+ MediaRouter.prototype.onIssue = function(issue) {
+ function issueSeverityToMojo_(severity) {
+ switch (severity) {
+ case 'fatal':
+ return mediaRouterMojom.Issue.Severity.FATAL;
+ case 'warning':
+ return mediaRouterMojom.Issue.Severity.WARNING;
+ case 'notification':
+ return mediaRouterMojom.Issue.Severity.NOTIFICATION;
+ default:
+ console.error('Unknown issue severity: ' + severity);
+ return mediaRouterMojom.Issue.Severity.NOTIFICATION;
+ }
+ }
+
+ function issueActionToMojo_(action) {
+ switch (action) {
+ case 'ok':
+ return mediaRouterMojom.Issue.ActionType.OK;
+ case 'cancel':
+ return mediaRouterMojom.Issue.ActionType.CANCEL;
+ case 'dismiss':
+ return mediaRouterMojom.Issue.ActionType.DISMISS;
+ case 'learn_more':
+ return mediaRouterMojom.Issue.ActionType.LEARN_MORE;
+ default:
+ console.error('Unknown issue action type : ' + action);
+ return mediaRouterMojom.Issue.ActionType.OK;
+ }
+ }
+
+ var secondaryActions = (issue.secondaryActions || []).map(function(e) {
+ return issueActionToMojo_(e);
+ });
+ this.service_.onIssue(new mediaRouterMojom.Issue({
+ 'route_id': issue.routeId,
+ 'severity': issueSeverityToMojo_(issue.severity),
+ 'title': issue.title,
+ 'message': issue.message,
+ 'default_action': issueActionToMojo_(issue.defaultAction),
+ 'secondary_actions': secondaryActions,
+ 'help_url': issue.helpUrl,
+ 'is_blocking': issue.isBlocking
+ }));
+ };
+
+ /**
+ * Called by the provider manager when the set of active routes
+ * has been updated.
+ * @param {!Array<MediaRoute>} routes The active set of media routes.
+ * @param {string=} opt_sourceUrn The sourceUrn associated with this route
+ * query. This parameter is optional and can be empty.
+ * @param {Array<string>=} opt_joinableRouteIds The active set of joinable
+ * media routes. This parameter is optional and can be empty.
+ */
+ MediaRouter.prototype.onRoutesUpdated =
+ function(routes, opt_sourceUrn, opt_joinableRouteIds) {
+ // TODO(boetger): This check allows backward compatibility with the Cast SDK
+ // and can be removed when the Cast SDK is updated.
+ if (typeof(opt_sourceUrn) != 'string') {
+ opt_sourceUrn = '';
+ }
+
+ this.service_.onRoutesUpdated(
+ routes.map(routeToMojo_),
+ opt_sourceUrn || '',
+ opt_joinableRouteIds || []);
+ };
+
+ /**
+ * Called by the provider manager when sink availability has been updated.
+ * @param {!mediaRouterMojom.MediaRouter.SinkAvailability} availability
+ * The new sink availability.
+ */
+ MediaRouter.prototype.onSinkAvailabilityUpdated = function(availability) {
+ this.service_.onSinkAvailabilityUpdated(availability);
+ };
+
+ /**
+ * Called by the provider manager when the state of a presentation connected
+ * to a route has changed.
+ * @param {string} routeId
+ * @param {string} state
+ */
+ MediaRouter.prototype.onPresentationConnectionStateChanged =
+ function(routeId, state) {
+ this.service_.onPresentationConnectionStateChanged(
+ routeId, presentationConnectionStateToMojo_(state));
+ };
+
+ /**
+ * Called by the provider manager when the state of a presentation connected
+ * to a route has closed.
+ * @param {string} routeId
+ * @param {string} reason
+ * @param {string} message
+ */
+ MediaRouter.prototype.onPresentationConnectionClosed =
+ function(routeId, reason, message) {
+ this.service_.onPresentationConnectionClosed(
+ routeId, presentationConnectionCloseReasonToMojo_(reason), message);
+ };
+
+ /**
+ * Object containing callbacks set by the provider manager.
+ *
+ * @constructor
+ * @struct
+ */
+ function MediaRouterHandlers() {
+ /**
+ * @type {function(!string, !string, !string, !string, !number}
+ */
+ this.createRoute = null;
+
+ /**
+ * @type {function(!string, !string, !string, !number)}
+ */
+ this.joinRoute = null;
+
+ /**
+ * @type {function(string)}
+ */
+ this.terminateRoute = null;
+
+ /**
+ * @type {function(string)}
+ */
+ this.startObservingMediaSinks = null;
+
+ /**
+ * @type {function(string)}
+ */
+ this.stopObservingMediaSinks = null;
+
+ /**
+ * @type {function(string, string): Promise}
+ */
+ this.sendRouteMessage = null;
+
+ /**
+ * @type {function(string, Uint8Array): Promise}
+ */
+ this.sendRouteBinaryMessage = null;
+
+ /**
+ * @type {function(string):
+ * Promise.<{messages: Array.<RouteMessage>, error: boolean}>}
+ */
+ this.listenForRouteMessages = null;
+
+ /**
+ * @type {function(string)}
+ */
+ this.stopListeningForRouteMessages = null;
+
+ /**
+ * @type {function(string)}
+ */
+ this.detachRoute = null;
+
+ /**
+ * @type {function()}
+ */
+ this.startObservingMediaRoutes = null;
+
+ /**
+ * @type {function()}
+ */
+ this.stopObservingMediaRoutes = null;
+
+ /**
+ * @type {function()}
+ */
+ this.connectRouteByRouteId = null;
+
+ /**
+ * @type {function()}
+ */
+ this.enableMdnsDiscovery = null;
+
+ /**
+ * @type {function()}
+ */
+ this.updateMediaSinks = null;
+ };
+
+ /**
+ * Routes calls from Media Router to the provider manager extension.
+ * Registered with the MediaRouter stub.
+ * @param {!MediaRouter} MediaRouter proxy to call into the
+ * Media Router mojo interface.
+ * @constructor
+ */
+ function MediaRouteProvider(mediaRouter) {
+ mediaRouterMojom.MediaRouteProvider.stubClass.call(this);
+
+ /**
+ * Object containing JS callbacks into Provider Manager code.
+ * @type {!MediaRouterHandlers}
+ */
+ this.handlers_ = new MediaRouterHandlers();
+
+ /**
+ * Proxy class to the browser's Media Router Mojo service.
+ * @type {!MediaRouter}
+ */
+ this.mediaRouter_ = mediaRouter;
+ }
+ MediaRouteProvider.prototype = Object.create(
+ mediaRouterMojom.MediaRouteProvider.stubClass.prototype);
+
+ /*
+ * Sets the callback handler used to invoke methods in the provider manager.
+ *
+ * @param {!MediaRouterHandlers} handlers
+ */
+ MediaRouteProvider.prototype.setHandlers = function(handlers) {
+ this.handlers_ = handlers;
+ var requiredHandlers = [
+ 'stopObservingMediaRoutes',
+ 'startObservingMediaRoutes',
+ 'sendRouteMessage',
+ 'sendRouteBinaryMessage',
+ 'listenForRouteMessages',
+ 'stopListeningForRouteMessages',
+ 'detachRoute',
+ 'terminateRoute',
+ 'joinRoute',
+ 'createRoute',
+ 'stopObservingMediaSinks',
+ 'startObservingMediaRoutes',
+ 'connectRouteByRouteId',
+ 'enableMdnsDiscovery',
+ 'updateMediaSinks',
+ ];
+ requiredHandlers.forEach(function(nextHandler) {
+ if (handlers[nextHandler] === undefined) {
+ console.error(nextHandler + ' handler not registered.');
+ }
+ });
+ }
+
+ /**
+ * Starts querying for sinks capable of displaying the media source
+ * designated by |sourceUrn|. Results are returned by calling
+ * OnSinksReceived.
+ * @param {!string} sourceUrn
+ */
+ MediaRouteProvider.prototype.startObservingMediaSinks =
+ function(sourceUrn) {
+ this.handlers_.startObservingMediaSinks(sourceUrn);
+ };
+
+ /**
+ * Stops querying for sinks capable of displaying |sourceUrn|.
+ * @param {!string} sourceUrn
+ */
+ MediaRouteProvider.prototype.stopObservingMediaSinks =
+ function(sourceUrn) {
+ this.handlers_.stopObservingMediaSinks(sourceUrn);
+ };
+
+ /**
+ * Requests that |sinkId| render the media referenced by |sourceUrn|. If the
+ * request is from the Presentation API, then origin and tabId will
+ * be populated.
+ * @param {!string} sourceUrn Media source to render.
+ * @param {!string} sinkId Media sink ID.
+ * @param {!string} presentationId Presentation ID from the site
+ * requesting presentation. TODO(mfoltz): Remove.
+ * @param {!string} origin Origin of site requesting presentation.
+ * @param {!number} tabId ID of tab requesting presentation.
+ * @param {!number} timeoutMillis If positive, the timeout duration for the
+ * request, measured in seconds. Otherwise, the default duration will be
+ * used.
+ * @param {!boolean} offTheRecord If true, the route is being requested by
+ * an off the record (incognito) profile.
+ * @return {!Promise.<!Object>} A Promise resolving to an object describing
+ * the newly created media route, or rejecting with an error message on
+ * failure.
+ */
+ MediaRouteProvider.prototype.createRoute =
+ function(sourceUrn, sinkId, presentationId, origin, tabId,
+ timeoutMillis, offTheRecord) {
+ return this.handlers_.createRoute(
+ sourceUrn, sinkId, presentationId, origin, tabId, timeoutMillis,
+ offTheRecord)
+ .then(function(route) {
+ return toSuccessRouteResponse_(route);
+ },
+ function(err) {
+ return toErrorRouteResponse_(err);
+ });
+ };
+
+ /**
+ * Handles a request via the Presentation API to join an existing route given
+ * by |sourceUrn| and |presentationId|. |origin| and |tabId| are used for
+ * validating same-origin/tab scope.
+ * @param {!string} sourceUrn Media source to render.
+ * @param {!string} presentationId Presentation ID to join.
+ * @param {!string} origin Origin of site requesting join.
+ * @param {!number} tabId ID of tab requesting join.
+ * @param {!number} timeoutMillis If positive, the timeout duration for the
+ * request, measured in seconds. Otherwise, the default duration will be
+ * used.
+ * @param {!boolean} offTheRecord If true, the route is being requested by
+ * an off the record (incognito) profile.
+ * @return {!Promise.<!Object>} A Promise resolving to an object describing
+ * the newly created media route, or rejecting with an error message on
+ * failure.
+ */
+ MediaRouteProvider.prototype.joinRoute =
+ function(sourceUrn, presentationId, origin, tabId, timeoutMillis,
+ offTheRecord) {
+ return this.handlers_.joinRoute(
+ sourceUrn, presentationId, origin, tabId, timeoutMillis, offTheRecord)
+ .then(function(route) {
+ return toSuccessRouteResponse_(route);
+ },
+ function(err) {
+ return toErrorRouteResponse_(err);
+ });
+ };
+
+ /**
+ * Handles a request via the Presentation API to join an existing route given
+ * by |sourceUrn| and |routeId|. |origin| and |tabId| are used for
+ * validating same-origin/tab scope.
+ * @param {!string} sourceUrn Media source to render.
+ * @param {!string} routeId Route ID to join.
+ * @param {!string} presentationId Presentation ID to join.
+ * @param {!string} origin Origin of site requesting join.
+ * @param {!number} tabId ID of tab requesting join.
+ * @param {!number} timeoutMillis If positive, the timeout duration for the
+ * request, measured in seconds. Otherwise, the default duration will be
+ * used.
+ * @param {!boolean} offTheRecord If true, the route is being requested by
+ * an off the record (incognito) profile.
+ * @return {!Promise.<!Object>} A Promise resolving to an object describing
+ * the newly created media route, or rejecting with an error message on
+ * failure.
+ */
+ MediaRouteProvider.prototype.connectRouteByRouteId =
+ function(sourceUrn, routeId, presentationId, origin, tabId,
+ timeoutMillis, offTheRecord) {
+ return this.handlers_.connectRouteByRouteId(
+ sourceUrn, routeId, presentationId, origin, tabId, timeoutMillis,
+ offTheRecord)
+ .then(function(route) {
+ return toSuccessRouteResponse_(route);
+ },
+ function(err) {
+ return toErrorRouteResponse_(err);
+ });
+ };
+
+ /**
+ * Terminates the route specified by |routeId|.
+ * @param {!string} routeId
+ */
+ MediaRouteProvider.prototype.terminateRoute = function(routeId) {
+ this.handlers_.terminateRoute(routeId);
+ };
+
+ /**
+ * Posts a message to the route designated by |routeId|.
+ * @param {!string} routeId
+ * @param {!string} message
+ * @return {!Promise.<boolean>} Resolved with true if the message was sent,
+ * or false on failure.
+ */
+ MediaRouteProvider.prototype.sendRouteMessage = function(
+ routeId, message) {
+ return this.handlers_.sendRouteMessage(routeId, message)
+ .then(function() {
+ return {'sent': true};
+ }, function() {
+ return {'sent': false};
+ });
+ };
+
+ /**
+ * Sends a binary message to the route designated by |routeId|.
+ * @param {!string} routeId
+ * @param {!Uint8Array} data
+ * @return {!Promise.<boolean>} Resolved with true if the data was sent,
+ * or false on failure.
+ */
+ MediaRouteProvider.prototype.sendRouteBinaryMessage = function(
+ routeId, data) {
+ return this.handlers_.sendRouteBinaryMessage(routeId, data)
+ .then(function() {
+ return {'sent': true};
+ }, function() {
+ return {'sent': false};
+ });
+ };
+
+ /**
+ * Listen for next batch of messages from one of the routeIds.
+ * @param {!string} routeId
+ * @return {!Promise.<{messages: Array.<RouteMessage>, error: boolean}>}
+ * Resolved with a list of messages, and a boolean indicating if an error
+ * occurred.
+ */
+ MediaRouteProvider.prototype.listenForRouteMessages = function(routeId) {
+ return this.handlers_.listenForRouteMessages(routeId)
+ .then(function(messages) {
+ return {'messages': messages.map(messageToMojo_), 'error': false};
+ }, function() {
+ return {'messages': [], 'error': true};
+ });
+ };
+
+ /**
+ * If there is an outstanding |listenForRouteMessages| promise for
+ * |routeId|, resolve that promise with an empty array.
+ * @param {!string} routeId
+ */
+ MediaRouteProvider.prototype.stopListeningForRouteMessages = function(
+ routeId) {
+ return this.handlers_.stopListeningForRouteMessages(routeId);
+ };
+
+ /**
+ * Indicates that the presentation connection that was connected to |routeId|
+ * is no longer connected to it.
+ * @param {!string} routeId
+ */
+ MediaRouteProvider.prototype.detachRoute = function(
+ routeId) {
+ this.handlers_.detachRoute(routeId);
+ };
+
+ /**
+ * Requests that the provider manager start sending information about active
+ * media routes to the Media Router.
+ * @param {!string} sourceUrn
+ */
+ MediaRouteProvider.prototype.startObservingMediaRoutes = function(sourceUrn) {
+ this.handlers_.startObservingMediaRoutes(sourceUrn);
+ };
+
+ /**
+ * Requests that the provider manager stop sending information about active
+ * media routes to the Media Router.
+ * @param {!string} sourceUrn
+ */
+ MediaRouteProvider.prototype.stopObservingMediaRoutes = function(sourceUrn) {
+ this.handlers_.stopObservingMediaRoutes(sourceUrn);
+ };
+
+ /**
+ * Enables mDNS device discovery.
+ */
+ MediaRouteProvider.prototype.enableMdnsDiscovery = function() {
+ this.handlers_.enableMdnsDiscovery();
+ };
+
+ /**
+ * Requests that the provider manager update media sinks.
+ * @param {!string} sourceUrn
+ */
+ MediaRouteProvider.prototype.updateMediaSinks = function(sourceUrn) {
+ this.handlers_.updateMediaSinks(sourceUrn);
+ };
+
+ mediaRouter = new MediaRouter(connector.bindHandleToProxy(
+ serviceProvider.connectToService(
+ mediaRouterMojom.MediaRouter.name),
+ mediaRouterMojom.MediaRouter));
+
+ return mediaRouter;
+});
+
diff --git a/chromium/extensions/renderer/resources/messaging.js b/chromium/extensions/renderer/resources/messaging.js
new file mode 100644
index 00000000000..8ca84ea4655
--- /dev/null
+++ b/chromium/extensions/renderer/resources/messaging.js
@@ -0,0 +1,446 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// chrome.runtime.messaging API implementation.
+// TODO(robwu): Fix this indentation.
+
+ // TODO(kalman): factor requiring chrome out of here.
+ var chrome = requireNative('chrome').GetChrome();
+ var Event = require('event_bindings').Event;
+ var lastError = require('lastError');
+ var logActivity = requireNative('activityLogger');
+ var logging = requireNative('logging');
+ var messagingNatives = requireNative('messaging_natives');
+ var processNatives = requireNative('process');
+ var utils = require('utils');
+ var messagingUtils = require('messaging_utils');
+
+ // The reserved channel name for the sendRequest/send(Native)Message APIs.
+ // Note: sendRequest is deprecated.
+ var kRequestChannel = "chrome.extension.sendRequest";
+ var kMessageChannel = "chrome.runtime.sendMessage";
+ var kNativeMessageChannel = "chrome.runtime.sendNativeMessage";
+
+ // Map of port IDs to port object.
+ var ports = {__proto__: null};
+
+ // Change even to odd and vice versa, to get the other side of a given
+ // channel.
+ function getOppositePortId(portId) { return portId ^ 1; }
+
+ // Port object. Represents a connection to another script context through
+ // which messages can be passed.
+ function PortImpl(portId, opt_name) {
+ this.portId_ = portId;
+ this.name = opt_name;
+
+ // Note: Keep these schemas in sync with the documentation in runtime.json
+ var portSchema = {
+ __proto__: null,
+ name: 'port',
+ $ref: 'runtime.Port',
+ };
+ var messageSchema = {
+ __proto__: null,
+ name: 'message',
+ type: 'any',
+ optional: true,
+ };
+ var options = {
+ __proto__: null,
+ unmanaged: true,
+ };
+ this.onDisconnect = new Event(null, [portSchema], options);
+ this.onMessage = new Event(null, [messageSchema, portSchema], options);
+ this.onDestroy_ = null;
+ }
+ $Object.setPrototypeOf(PortImpl.prototype, null);
+
+ // Sends a message asynchronously to the context on the other end of this
+ // port.
+ PortImpl.prototype.postMessage = function(msg) {
+ // JSON.stringify doesn't support a root object which is undefined.
+ if (msg === undefined)
+ msg = null;
+ msg = $JSON.stringify(msg);
+ if (msg === undefined) {
+ // JSON.stringify can fail with unserializable objects. Log an error and
+ // drop the message.
+ //
+ // TODO(kalman/mpcomplete): it would be better to do the same validation
+ // here that we do for runtime.sendMessage (and variants), i.e. throw an
+ // schema validation Error, but just maintain the old behaviour until
+ // there's a good reason not to (http://crbug.com/263077).
+ console.error('Illegal argument to Port.postMessage');
+ return;
+ }
+ messagingNatives.PostMessage(this.portId_, msg);
+ };
+
+ // Disconnects the port from the other end.
+ PortImpl.prototype.disconnect = function() {
+ messagingNatives.CloseChannel(this.portId_, true);
+ this.destroy_();
+ };
+
+ PortImpl.prototype.destroy_ = function() {
+ if (this.onDestroy_) {
+ this.onDestroy_();
+ this.onDestroy_ = null;
+ }
+ privates(this.onDisconnect).impl.destroy_();
+ privates(this.onMessage).impl.destroy_();
+ // TODO(robwu): Remove port lifetime management because it is completely
+ // handled in the browser. The renderer's only roles are
+ // 1) rejecting ports so that the browser knows that the renderer is not
+ // interested in the port (this is merely an optimization)
+ // 2) acknowledging port creations, so that the browser knows that the port
+ // was successfully created (from the perspective of the extension), but
+ // then closed for some non-fatal reason.
+ // 3) notifying the browser of explicit port closure via .disconnect().
+ // In other cases (navigations), the browser automatically cleans up the
+ // port.
+ messagingNatives.PortRelease(this.portId_);
+ delete ports[this.portId_];
+ };
+
+ // Returns true if the specified port id is in this context. This is used by
+ // the C++ to avoid creating the javascript message for all the contexts that
+ // don't care about a particular message.
+ function hasPort(portId) {
+ return portId in ports;
+ };
+
+ // Hidden port creation function. We don't want to expose an API that lets
+ // people add arbitrary port IDs to the port list.
+ function createPort(portId, opt_name) {
+ if (ports[portId])
+ throw new Error("Port '" + portId + "' already exists.");
+ var port = new Port(portId, opt_name);
+ ports[portId] = port;
+ messagingNatives.PortAddRef(portId);
+ return port;
+ };
+
+ // Helper function for dispatchOnRequest.
+ function handleSendRequestError(isSendMessage,
+ responseCallbackPreserved,
+ sourceExtensionId,
+ targetExtensionId,
+ sourceUrl) {
+ var errorMsg;
+ var eventName = isSendMessage ? 'runtime.onMessage' : 'extension.onRequest';
+ if (isSendMessage && !responseCallbackPreserved) {
+ errorMsg =
+ 'The chrome.' + eventName + ' listener must return true if you ' +
+ 'want to send a response after the listener returns';
+ } else {
+ errorMsg =
+ 'Cannot send a response more than once per chrome.' + eventName +
+ ' listener per document';
+ }
+ errorMsg += ' (message was sent by extension' + sourceExtensionId;
+ if (sourceExtensionId && sourceExtensionId !== targetExtensionId)
+ errorMsg += ' for extension ' + targetExtensionId;
+ if (sourceUrl)
+ errorMsg += ' for URL ' + sourceUrl;
+ errorMsg += ').';
+ lastError.set(eventName, errorMsg, null, chrome);
+ }
+
+ // Helper function for dispatchOnConnect
+ function dispatchOnRequest(portId, channelName, sender,
+ sourceExtensionId, targetExtensionId, sourceUrl,
+ isExternal) {
+ var isSendMessage = channelName == kMessageChannel;
+ var requestEvent = null;
+ if (isSendMessage) {
+ if (chrome.runtime) {
+ requestEvent = isExternal ? chrome.runtime.onMessageExternal
+ : chrome.runtime.onMessage;
+ }
+ } else {
+ if (chrome.extension) {
+ requestEvent = isExternal ? chrome.extension.onRequestExternal
+ : chrome.extension.onRequest;
+ }
+ }
+ if (!requestEvent)
+ return false;
+ if (!requestEvent.hasListeners())
+ return false;
+ var port = createPort(portId, channelName);
+
+ function messageListener(request) {
+ var responseCallbackPreserved = false;
+ var responseCallback = function(response) {
+ if (port) {
+ port.postMessage(response);
+ privates(port).impl.destroy_();
+ port = null;
+ } else {
+ // We nulled out port when sending the response, and now the page
+ // is trying to send another response for the same request.
+ handleSendRequestError(isSendMessage, responseCallbackPreserved,
+ sourceExtensionId, targetExtensionId);
+ }
+ };
+ // In case the extension never invokes the responseCallback, and also
+ // doesn't keep a reference to it, we need to clean up the port. Do
+ // so by attaching to the garbage collection of the responseCallback
+ // using some native hackery.
+ //
+ // If the context is destroyed before this has a chance to execute,
+ // BindToGC knows to release |portId| (important for updating C++ state
+ // both in this renderer and on the other end). We don't need to clear
+ // any JavaScript state, as calling destroy_() would usually do - but
+ // the context has been destroyed, so there isn't any JS state to clear.
+ messagingNatives.BindToGC(responseCallback, function() {
+ if (port) {
+ privates(port).impl.destroy_();
+ port = null;
+ }
+ }, portId);
+ var rv = requestEvent.dispatch(request, sender, responseCallback);
+ if (isSendMessage) {
+ responseCallbackPreserved =
+ rv && rv.results && $Array.indexOf(rv.results, true) > -1;
+ if (!responseCallbackPreserved && port) {
+ // If they didn't access the response callback, they're not
+ // going to send a response, so clean up the port immediately.
+ privates(port).impl.destroy_();
+ port = null;
+ }
+ }
+ }
+
+ privates(port).impl.onDestroy_ = function() {
+ port.onMessage.removeListener(messageListener);
+ };
+ port.onMessage.addListener(messageListener);
+
+ var eventName = isSendMessage ? "runtime.onMessage" : "extension.onRequest";
+ if (isExternal)
+ eventName += "External";
+ logActivity.LogEvent(targetExtensionId,
+ eventName,
+ [sourceExtensionId, sourceUrl]);
+ return true;
+ }
+
+ // Called by native code when a channel has been opened to this context.
+ function dispatchOnConnect(portId,
+ channelName,
+ sourceTab,
+ sourceFrameId,
+ guestProcessId,
+ guestRenderFrameRoutingId,
+ sourceExtensionId,
+ targetExtensionId,
+ sourceUrl,
+ tlsChannelId) {
+ // Only create a new Port if someone is actually listening for a connection.
+ // In addition to being an optimization, this also fixes a bug where if 2
+ // channels were opened to and from the same process, closing one would
+ // close both.
+ var extensionId = processNatives.GetExtensionId();
+
+ // messaging_bindings.cc should ensure that this method only gets called for
+ // the right extension.
+ logging.CHECK(targetExtensionId == extensionId);
+
+ if (ports[getOppositePortId(portId)])
+ return false; // this channel was opened by us, so ignore it
+
+ // Determine whether this is coming from another extension, so we can use
+ // the right event.
+ var isExternal = sourceExtensionId != extensionId;
+
+ var sender = {};
+ if (sourceExtensionId != '')
+ sender.id = sourceExtensionId;
+ if (sourceUrl)
+ sender.url = sourceUrl;
+ if (sourceTab)
+ sender.tab = sourceTab;
+ if (sourceFrameId >= 0)
+ sender.frameId = sourceFrameId;
+ if (typeof guestProcessId !== 'undefined' &&
+ typeof guestRenderFrameRoutingId !== 'undefined') {
+ // Note that |guestProcessId| and |guestRenderFrameRoutingId| are not
+ // standard fields on MessageSender and should not be exposed to drive-by
+ // extensions; it is only exposed to component extensions.
+ logging.CHECK(processNatives.IsComponentExtension(),
+ "GuestProcessId can only be exposed to component extensions.");
+ sender.guestProcessId = guestProcessId;
+ sender.guestRenderFrameRoutingId = guestRenderFrameRoutingId;
+ }
+ if (typeof tlsChannelId != 'undefined')
+ sender.tlsChannelId = tlsChannelId;
+
+ // Special case for sendRequest/onRequest and sendMessage/onMessage.
+ if (channelName == kRequestChannel || channelName == kMessageChannel) {
+ return dispatchOnRequest(portId, channelName, sender,
+ sourceExtensionId, targetExtensionId, sourceUrl,
+ isExternal);
+ }
+
+ var connectEvent = null;
+ if (chrome.runtime) {
+ connectEvent = isExternal ? chrome.runtime.onConnectExternal
+ : chrome.runtime.onConnect;
+ }
+ if (!connectEvent)
+ return false;
+ if (!connectEvent.hasListeners())
+ return false;
+
+ var port = createPort(portId, channelName);
+ port.sender = sender;
+ if (processNatives.manifestVersion < 2)
+ port.tab = port.sender.tab;
+
+ var eventName = (isExternal ?
+ "runtime.onConnectExternal" : "runtime.onConnect");
+ connectEvent.dispatch(port);
+ logActivity.LogEvent(targetExtensionId,
+ eventName,
+ [sourceExtensionId]);
+ return true;
+ };
+
+ // Called by native code when a channel has been closed.
+ function dispatchOnDisconnect(portId, errorMessage) {
+ var port = ports[portId];
+ if (port) {
+ // Update the renderer's port bookkeeping, without notifying the browser.
+ messagingNatives.CloseChannel(portId, false);
+ if (errorMessage)
+ lastError.set('Port', errorMessage, null, chrome);
+ try {
+ port.onDisconnect.dispatch(port);
+ } finally {
+ privates(port).impl.destroy_();
+ lastError.clear(chrome);
+ }
+ }
+ };
+
+ // Called by native code when a message has been sent to the given port.
+ function dispatchOnMessage(msg, portId) {
+ var port = ports[portId];
+ if (port) {
+ if (msg)
+ msg = $JSON.parse(msg);
+ port.onMessage.dispatch(msg, port);
+ }
+ };
+
+ // Shared implementation used by tabs.sendMessage and runtime.sendMessage.
+ function sendMessageImpl(port, request, responseCallback) {
+ if (port.name != kNativeMessageChannel)
+ port.postMessage(request);
+
+ if (port.name == kMessageChannel && !responseCallback) {
+ // TODO(mpcomplete): Do this for the old sendRequest API too, after
+ // verifying it doesn't break anything.
+ // Go ahead and disconnect immediately if the sender is not expecting
+ // a response.
+ port.disconnect();
+ return;
+ }
+
+ function sendResponseAndClearCallback(response) {
+ // Save a reference so that we don't re-entrantly call responseCallback.
+ var sendResponse = responseCallback;
+ responseCallback = null;
+ if (arguments.length === 0) {
+ // According to the documentation of chrome.runtime.sendMessage, the
+ // callback is invoked without any arguments when an error occurs.
+ sendResponse();
+ } else {
+ sendResponse(response);
+ }
+ }
+
+
+ // Note: make sure to manually remove the onMessage/onDisconnect listeners
+ // that we added before destroying the Port, a workaround to a bug in Port
+ // where any onMessage/onDisconnect listeners added but not removed will
+ // be leaked when the Port is destroyed.
+ // http://crbug.com/320723 tracks a sustainable fix.
+
+ function disconnectListener() {
+ if (!responseCallback)
+ return;
+
+ if (lastError.hasError(chrome)) {
+ sendResponseAndClearCallback();
+ } else {
+ lastError.set(
+ port.name, 'The message port closed before a reponse was received.',
+ null, chrome);
+ try {
+ sendResponseAndClearCallback();
+ } finally {
+ lastError.clear(chrome);
+ }
+ }
+ }
+
+ function messageListener(response) {
+ try {
+ if (responseCallback)
+ sendResponseAndClearCallback(response);
+ } finally {
+ port.disconnect();
+ }
+ }
+
+ privates(port).impl.onDestroy_ = function() {
+ port.onDisconnect.removeListener(disconnectListener);
+ port.onMessage.removeListener(messageListener);
+ };
+ port.onDisconnect.addListener(disconnectListener);
+ port.onMessage.addListener(messageListener);
+ };
+
+ function sendMessageUpdateArguments(functionName, hasOptionsArgument) {
+ // skip functionName and hasOptionsArgument
+ var args = $Array.slice(arguments, 2);
+ var alignedArgs = messagingUtils.alignSendMessageArguments(args,
+ hasOptionsArgument);
+ if (!alignedArgs)
+ throw new Error('Invalid arguments to ' + functionName + '.');
+ return alignedArgs;
+ }
+
+ function Port() {
+ privates(Port).constructPrivate(this, arguments);
+ }
+ utils.expose(Port, PortImpl, {
+ functions: [
+ 'disconnect',
+ 'postMessage',
+ ],
+ properties: [
+ 'name',
+ 'onDisconnect',
+ 'onMessage',
+ ],
+ });
+
+exports.$set('kRequestChannel', kRequestChannel);
+exports.$set('kMessageChannel', kMessageChannel);
+exports.$set('kNativeMessageChannel', kNativeMessageChannel);
+exports.$set('Port', Port);
+exports.$set('createPort', createPort);
+exports.$set('sendMessageImpl', sendMessageImpl);
+exports.$set('sendMessageUpdateArguments', sendMessageUpdateArguments);
+
+// For C++ code to call.
+exports.$set('hasPort', hasPort);
+exports.$set('dispatchOnConnect', dispatchOnConnect);
+exports.$set('dispatchOnDisconnect', dispatchOnDisconnect);
+exports.$set('dispatchOnMessage', dispatchOnMessage);
diff --git a/chromium/extensions/renderer/resources/messaging_utils.js b/chromium/extensions/renderer/resources/messaging_utils.js
new file mode 100644
index 00000000000..381fbeb572b
--- /dev/null
+++ b/chromium/extensions/renderer/resources/messaging_utils.js
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Routines used to normalize arguments to messaging functions.
+
+function alignSendMessageArguments(args, hasOptionsArgument) {
+ // Align missing (optional) function arguments with the arguments that
+ // schema validation is expecting, e.g.
+ // extension.sendRequest(req) -> extension.sendRequest(null, req)
+ // extension.sendRequest(req, cb) -> extension.sendRequest(null, req, cb)
+ if (!args || !args.length)
+ return null;
+ var lastArg = args.length - 1;
+
+ // responseCallback (last argument) is optional.
+ var responseCallback = null;
+ if (typeof args[lastArg] == 'function')
+ responseCallback = args[lastArg--];
+
+ var options = null;
+ if (hasOptionsArgument && lastArg >= 1) {
+ // options (third argument) is optional. It can also be ambiguous which
+ // argument it should match. If there are more than two arguments remaining,
+ // options is definitely present:
+ if (lastArg > 1) {
+ options = args[lastArg--];
+ } else {
+ // Exactly two arguments remaining. If the first argument is a string,
+ // it should bind to targetId, and the second argument should bind to
+ // request, which is required. In other words, when two arguments remain,
+ // only bind options when the first argument cannot bind to targetId.
+ if (!(args[0] === null || typeof args[0] == 'string'))
+ options = args[lastArg--];
+ }
+ }
+
+ // request (second argument) is required.
+ var request = args[lastArg--];
+
+ // targetId (first argument, extensionId in the manifest) is optional.
+ var targetId = null;
+ if (lastArg >= 0)
+ targetId = args[lastArg--];
+
+ if (lastArg != -1)
+ return null;
+ if (hasOptionsArgument)
+ return [targetId, request, options, responseCallback];
+ return [targetId, request, responseCallback];
+}
+
+exports.$set('alignSendMessageArguments', alignSendMessageArguments);
diff --git a/chromium/extensions/renderer/resources/mime_handler_private_custom_bindings.js b/chromium/extensions/renderer/resources/mime_handler_private_custom_bindings.js
new file mode 100644
index 00000000000..8e9eb20ebe5
--- /dev/null
+++ b/chromium/extensions/renderer/resources/mime_handler_private_custom_bindings.js
@@ -0,0 +1,75 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * Custom bindings for the mime handler API.
+ */
+
+var binding = require('binding').Binding.create('mimeHandlerPrivate');
+
+var NO_STREAM_ERROR =
+ 'Streams are only available from a mime handler view guest.';
+var STREAM_ABORTED_ERROR = 'Stream has been aborted.';
+
+var servicePromise = Promise.all([
+ requireAsync('content/public/renderer/frame_service_registry'),
+ requireAsync('extensions/common/api/mime_handler.mojom'),
+ requireAsync('mojo/public/js/router'),
+]).then(function(modules) {
+ var serviceProvider = modules[0];
+ var mojom = modules[1];
+ var routerModule = modules[2];
+ return new mojom.MimeHandlerService.proxyClass(new routerModule.Router(
+ serviceProvider.connectToService(mojom.MimeHandlerService.name)));
+});
+
+// Stores a promise to the GetStreamInfo() result to avoid making additional
+// calls in response to getStreamInfo() calls.
+var streamInfoPromise;
+
+function throwNoStreamError() {
+ throw new Error(NO_STREAM_ERROR);
+}
+
+function createStreamInfoPromise() {
+ return servicePromise.then(function(service) {
+ return service.getStreamInfo();
+ }).then(function(result) {
+ if (!result.stream_info)
+ throw new Error(STREAM_ABORTED_ERROR);
+ return result.stream_info;
+ }, throwNoStreamError);
+}
+
+function constructStreamInfoDict(streamInfo) {
+ var headers = {};
+ for (var header of streamInfo.response_headers) {
+ headers[header[0]] = header[1];
+ }
+ return {
+ mimeType: streamInfo.mime_type,
+ originalUrl: streamInfo.original_url,
+ streamUrl: streamInfo.stream_url,
+ tabId: streamInfo.tab_id,
+ embedded: !!streamInfo.embedded,
+ responseHeaders: headers,
+ };
+}
+
+binding.registerCustomHook(function(bindingsAPI) {
+ var apiFunctions = bindingsAPI.apiFunctions;
+ apiFunctions.setHandleRequestWithPromise('getStreamInfo', function() {
+ if (!streamInfoPromise)
+ streamInfoPromise = createStreamInfoPromise();
+ return streamInfoPromise.then(constructStreamInfoDict);
+ });
+
+ apiFunctions.setHandleRequestWithPromise('abortStream', function() {
+ return servicePromise.then(function(service) {
+ return service.abortStream().then(function() {});
+ }).catch(throwNoStreamError);
+ });
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/mojo_private_custom_bindings.js b/chromium/extensions/renderer/resources/mojo_private_custom_bindings.js
new file mode 100644
index 00000000000..18d6016ba3b
--- /dev/null
+++ b/chromium/extensions/renderer/resources/mojo_private_custom_bindings.js
@@ -0,0 +1,23 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * Custom bindings for the mojoPrivate API.
+ */
+
+let binding = require('binding').Binding.create('mojoPrivate');
+
+binding.registerCustomHook(function(bindingsAPI) {
+ let apiFunctions = bindingsAPI.apiFunctions;
+
+ apiFunctions.setHandleRequest('define', function(name, deps, factory) {
+ define(name, deps || [], factory);
+ });
+
+ apiFunctions.setHandleRequest('requireAsync', function(moduleName) {
+ return requireAsync(moduleName);
+ });
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/permissions_custom_bindings.js b/chromium/extensions/renderer/resources/permissions_custom_bindings.js
new file mode 100644
index 00000000000..492360a9ef1
--- /dev/null
+++ b/chromium/extensions/renderer/resources/permissions_custom_bindings.js
@@ -0,0 +1,92 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Custom binding for the Permissions API.
+
+var binding = require('binding').Binding.create('permissions');
+
+var Event = require('event_bindings').Event;
+
+// These custom binding are only necessary because it is not currently
+// possible to have a union of types as the type of the items in an array.
+// Once that is fixed, this entire file should go away.
+// See,
+// https://code.google.com/p/chromium/issues/detail?id=162044
+// https://code.google.com/p/chromium/issues/detail?id=162042
+// TODO(bryeung): delete this file.
+binding.registerCustomHook(function(api) {
+ var apiFunctions = api.apiFunctions;
+ var permissions = api.compiledApi;
+
+ function maybeConvertToObject(str) {
+ var parts = $String.split(str, '|');
+ if (parts.length != 2)
+ return str;
+
+ var ret = {};
+ ret[parts[0]] = JSON.parse(parts[1]);
+ return ret;
+ }
+
+ function convertObjectPermissionsToStrings() {
+ if (arguments.length < 1)
+ return arguments;
+
+ var args = arguments[0].permissions;
+ if (!args)
+ return arguments;
+
+ for (var i = 0; i < args.length; i += 1) {
+ if (typeof(args[i]) == 'object') {
+ var a = args[i];
+ var keys = $Object.keys(a);
+ if (keys.length != 1) {
+ throw new Error("Too many keys in object-style permission.");
+ }
+ arguments[0].permissions[i] = keys[0] + '|' +
+ JSON.stringify(a[keys[0]]);
+ }
+ }
+
+ return arguments;
+ }
+
+ // Convert complex permissions to strings so they validate against the schema
+ apiFunctions.setUpdateArgumentsPreValidate(
+ 'contains', convertObjectPermissionsToStrings);
+ apiFunctions.setUpdateArgumentsPreValidate(
+ 'remove', convertObjectPermissionsToStrings);
+ apiFunctions.setUpdateArgumentsPreValidate(
+ 'request', convertObjectPermissionsToStrings);
+
+ // Convert complex permissions back to objects
+ apiFunctions.setCustomCallback('getAll',
+ function(name, request, callback, response) {
+ for (var i = 0; i < response.permissions.length; i += 1) {
+ response.permissions[i] =
+ maybeConvertToObject(response.permissions[i]);
+ }
+
+ // Since the schema says Permissions.permissions contains strings and
+ // not objects, validation will fail after the for-loop above. This
+ // skips validation and calls the callback directly.
+ if (callback)
+ callback(response);
+ });
+
+ // Also convert complex permissions back to objects for events. The
+ // dispatchToListener call happens after argument validation, which works
+ // around the problem that Permissions.permissions is supposed to be a list
+ // of strings.
+ permissions.onAdded.dispatchToListener = function(callback, args) {
+ for (var i = 0; i < args[0].permissions.length; i += 1) {
+ args[0].permissions[i] = maybeConvertToObject(args[0].permissions[i]);
+ }
+ $Function.call(Event.prototype.dispatchToListener, this, callback, args);
+ };
+ permissions.onRemoved.dispatchToListener =
+ permissions.onAdded.dispatchToListener;
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/platform_app.css b/chromium/extensions/renderer/resources/platform_app.css
new file mode 100644
index 00000000000..fa811310bd9
--- /dev/null
+++ b/chromium/extensions/renderer/resources/platform_app.css
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * A style sheet for Chrome apps.
+ */
+
+@namespace "http://www.w3.org/1999/xhtml";
+
+body {
+ -webkit-user-select: none;
+ cursor: default;
+ font-family: $FONTFAMILY;
+ font-size: $FONTSIZE;
+}
+
+webview, appview {
+ display: inline-block;
+ width: 300px;
+ height: 300px;
+}
+
+html, body {
+ overflow: hidden;
+}
+
+img, a {
+ -webkit-user-drag: none;
+}
+
+[contenteditable], input {
+ -webkit-user-select: auto;
+}
+
diff --git a/chromium/extensions/renderer/resources/platform_app.js b/chromium/extensions/renderer/resources/platform_app.js
new file mode 100644
index 00000000000..9f386d790d2
--- /dev/null
+++ b/chromium/extensions/renderer/resources/platform_app.js
@@ -0,0 +1,232 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var logging = requireNative('logging');
+
+/**
+ * Returns a function that logs a 'not available' error to the console and
+ * returns undefined.
+ *
+ * @param {string} messagePrefix text to prepend to the exception message.
+ */
+function generateDisabledMethodStub(messagePrefix, opt_messageSuffix) {
+ var message = messagePrefix + ' is not available in packaged apps.';
+ if (opt_messageSuffix) message = message + ' ' + opt_messageSuffix;
+ return function() {
+ console.error(message);
+ return;
+ };
+}
+
+/**
+ * Returns a function that throws a 'not available' error.
+ *
+ * @param {string} messagePrefix text to prepend to the exception message.
+ */
+function generateThrowingMethodStub(messagePrefix, opt_messageSuffix) {
+ var message = messagePrefix + ' is not available in packaged apps.';
+ if (opt_messageSuffix) message = message + ' ' + opt_messageSuffix;
+ return function() {
+ throw new Error(message);
+ };
+}
+
+/**
+ * Replaces the given methods of the passed in object with stubs that log
+ * 'not available' errors to the console and return undefined.
+ *
+ * This should be used on methods attached via non-configurable properties,
+ * such as window.alert. disableGetters should be used when possible, because
+ * it is friendlier towards feature detection.
+ *
+ * In most cases, the useThrowingStubs should be false, so the stubs used to
+ * replace the methods log an error to the console, but allow the calling code
+ * to continue. We shouldn't break library code that uses feature detection
+ * responsibly, such as:
+ * if(window.confirm) {
+ * var result = window.confirm('Are you sure you want to delete ...?');
+ * ...
+ * }
+ *
+ * useThrowingStubs should only be true for methods that are deprecated in the
+ * Web platform, and should not be used by a responsible library, even in
+ * conjunction with feature detection. A great example is document.write(), as
+ * the HTML5 specification recommends against using it, and says that its
+ * behavior is unreliable. No reasonable library code should ever use it.
+ * HTML5 spec: http://www.w3.org/TR/html5/dom.html#dom-document-write
+ *
+ * @param {Object} object The object with methods to disable. The prototype is
+ * preferred.
+ * @param {string} objectName The display name to use in the error message
+ * thrown by the stub (this is the name that the object is commonly referred
+ * to by web developers, e.g. "document" instead of "HTMLDocument").
+ * @param {Array<string>} methodNames names of methods to disable.
+ * @param {Boolean} useThrowingStubs if true, the replaced methods will throw
+ * an error instead of silently returning undefined
+ */
+function disableMethods(object, objectName, methodNames, useThrowingStubs) {
+ $Array.forEach(methodNames, function(methodName) {
+ logging.DCHECK($Object.getOwnPropertyDescriptor(object, methodName),
+ objectName + ': ' + methodName);
+ var messagePrefix = objectName + '.' + methodName + '()';
+ $Object.defineProperty(object, methodName, {
+ configurable: false,
+ enumerable: false,
+ value: useThrowingStubs ?
+ generateThrowingMethodStub(messagePrefix) :
+ generateDisabledMethodStub(messagePrefix)
+ });
+ });
+}
+
+/**
+ * Replaces the given properties of the passed in object with stubs that log
+ * 'not available' warnings to the console and return undefined when gotten. If
+ * a property's setter is later invoked, the getter and setter are restored to
+ * default behaviors.
+ *
+ * @param {Object} object The object with properties to disable. The prototype
+ * is preferred.
+ * @param {string} objectName The display name to use in the error message
+ * thrown by the getter stub (this is the name that the object is commonly
+ * referred to by web developers, e.g. "document" instead of
+ * "HTMLDocument").
+ * @param {Array<string>} propertyNames names of properties to disable.
+ * @param {?string=} opt_messageSuffix An optional suffix for the message.
+ * @param {boolean=} opt_ignoreMissingProperty True if we allow disabling
+ * getters for non-existent properties.
+ */
+function disableGetters(object, objectName, propertyNames, opt_messageSuffix,
+ opt_ignoreMissingProperty) {
+ $Array.forEach(propertyNames, function(propertyName) {
+ logging.DCHECK(opt_ignoreMissingProperty ||
+ $Object.getOwnPropertyDescriptor(object, propertyName),
+ objectName + ': ' + propertyName);
+ var stub = generateDisabledMethodStub(objectName + '.' + propertyName,
+ opt_messageSuffix);
+ stub._is_platform_app_disabled_getter = true;
+ $Object.defineProperty(object, propertyName, {
+ configurable: true,
+ enumerable: false,
+ get: stub,
+ set: function(value) {
+ var descriptor = $Object.getOwnPropertyDescriptor(this, propertyName);
+ if (!descriptor || !descriptor.get ||
+ descriptor.get._is_platform_app_disabled_getter) {
+ // The stub getter is still defined. Blow-away the property to
+ // restore default getter/setter behaviors and re-create it with the
+ // given value.
+ delete this[propertyName];
+ this[propertyName] = value;
+ } else {
+ // Do nothing. If some custom getter (not ours) has been defined,
+ // there would be no way to read back the value stored by a default
+ // setter. Also, the only way to clear a custom getter is to first
+ // delete the property. Therefore, the value we have here should
+ // just go into a black hole.
+ }
+ }
+ });
+ });
+}
+
+/**
+ * Replaces the given properties of the passed in object with stubs that log
+ * 'not available' warnings to the console when set.
+ *
+ * @param {Object} object The object with properties to disable. The prototype
+ * is preferred.
+ * @param {string} objectName The display name to use in the error message
+ * thrown by the setter stub (this is the name that the object is commonly
+ * referred to by web developers, e.g. "document" instead of
+ * "HTMLDocument").
+ * @param {Array<string>} propertyNames names of properties to disable.
+ */
+function disableSetters(object, objectName, propertyNames, opt_messageSuffix) {
+ $Array.forEach(propertyNames, function(propertyName) {
+ logging.DCHECK($Object.getOwnPropertyDescriptor(object, propertyName),
+ objectName + ': ' + propertyName);
+ var stub = generateDisabledMethodStub(objectName + '.' + propertyName,
+ opt_messageSuffix);
+ $Object.defineProperty(object, propertyName, {
+ configurable: false,
+ enumerable: false,
+ get: function() {
+ return;
+ },
+ set: stub
+ });
+ });
+}
+
+// Disable benign Document methods.
+disableMethods(Document.prototype, 'document', ['open', 'close']);
+disableMethods(HTMLDocument.prototype, 'document', ['clear']);
+
+// Replace evil Document methods with exception-throwing stubs.
+disableMethods(Document.prototype, 'document', ['write', 'writeln'], true);
+
+// Disable history.
+Object.defineProperty(window, "history", { value: {} });
+// Note: we just blew away the history object, so we need to ignore the fact
+// that these properties aren't defined on the object.
+disableGetters(window.history, 'history',
+ ['back', 'forward', 'go', 'length', 'pushState', 'replaceState', 'state'],
+ null, true);
+
+// Disable find.
+disableMethods(window, 'window', ['find']);
+
+// Disable modal dialogs. Shell windows disable these anyway, but it's nice to
+// warn.
+disableMethods(window, 'window', ['alert', 'confirm', 'prompt']);
+
+// Disable window.*bar.
+disableGetters(window, 'window',
+ ['locationbar', 'menubar', 'personalbar', 'scrollbars', 'statusbar',
+ 'toolbar']);
+
+// Disable window.localStorage.
+// Sometimes DOM security policy prevents us from doing this (e.g. for data:
+// URLs) so wrap in try-catch.
+try {
+ disableGetters(window, 'window',
+ ['localStorage'],
+ 'Use chrome.storage.local instead.');
+} catch (e) {}
+
+// Document instance properties that we wish to disable need to be set when
+// the document begins loading, since only then will the "document" reference
+// point to the page's document (it will be reset between now and then).
+// We can't listen for the "readystatechange" event on the document (because
+// the object that it's dispatched on doesn't exist yet), but we can instead
+// do it at the window level in the capturing phase.
+window.addEventListener('readystatechange', function(event) {
+ if (document.readyState != 'loading')
+ return;
+
+ // Deprecated document properties from
+ // https://developer.mozilla.org/en/DOM/document.
+ // To deprecate document.all, simply changing its getter and setter would
+ // activate its cache mechanism, and degrade the performance. Here we assign
+ // it first to 'undefined' to avoid this.
+ document.all = undefined;
+ disableGetters(document, 'document',
+ ['alinkColor', 'all', 'bgColor', 'fgColor', 'linkColor', 'vlinkColor'],
+ null, true);
+}, true);
+
+// Disable onunload, onbeforeunload.
+disableSetters(window, 'window', ['onbeforeunload', 'onunload']);
+var eventTargetAddEventListener = EventTarget.prototype.addEventListener;
+EventTarget.prototype.addEventListener = function(type) {
+ var args = $Array.slice(arguments);
+ // Note: Force conversion to a string in order to catch any funny attempts
+ // to pass in something that evals to 'unload' but wouldn't === 'unload'.
+ var type = (args[0] += '');
+ if (type === 'unload' || type === 'beforeunload')
+ generateDisabledMethodStub(type)();
+ else
+ return $Function.apply(eventTargetAddEventListener, this, args);
+};
diff --git a/chromium/extensions/renderer/resources/printer_provider_custom_bindings.js b/chromium/extensions/renderer/resources/printer_provider_custom_bindings.js
new file mode 100644
index 00000000000..4bf6f12f760
--- /dev/null
+++ b/chromium/extensions/renderer/resources/printer_provider_custom_bindings.js
@@ -0,0 +1,123 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var binding = require('binding').Binding.create('printerProvider');
+var printerProviderInternal = require('binding').Binding.create(
+ 'printerProviderInternal').generate();
+var eventBindings = require('event_bindings');
+var blobNatives = requireNative('blob_natives');
+
+var printerProviderSchema =
+ requireNative('schema_registry').GetSchema('printerProvider')
+var utils = require('utils');
+var validate = require('schemaUtils').validate;
+
+// Custom bindings for chrome.printerProvider API.
+// The bindings are used to implement callbacks for the API events. Internally
+// each event is passed requestId argument used to identify the callback
+// associated with the event. This argument is massaged out from the event
+// arguments before dispatching the event to consumers. A callback is appended
+// to the event arguments. The callback wraps an appropriate
+// chrome.printerProviderInternal API function that is used to report the event
+// result from the extension. The function is passed requestId and values
+// provided by the extension. It validates that the values provided by the
+// extension match chrome.printerProvider event callback schemas. It also
+// ensures that a callback is run at most once. In case there is an exception
+// during event dispatching, the chrome.printerProviderInternal function
+// is called with a default error value.
+//
+
+// Handles a chrome.printerProvider event as described in the file comment.
+// |eventName|: The event name.
+// |prepareArgsForDispatch|: Function called before dispatching the event to
+// the extension. It's called with original event |args| list and callback
+// that should be called when the |args| are ready for dispatch. The
+// callbacks should report whether the argument preparation was successful.
+// The function should not change the first argument, which contains the
+// request id.
+// |resultreporter|: The function that should be called to report event result.
+// One of chrome.printerProviderInternal API functions.
+function handleEvent(eventName, prepareArgsForDispatch, resultReporter) {
+ eventBindings.registerArgumentMassager(
+ 'printerProvider.' + eventName,
+ function(args, dispatch) {
+ var responded = false;
+
+ // Validates that the result passed by the extension to the event
+ // callback matches the callback schema. Throws an exception in case of
+ // an error.
+ var validateResult = function(result) {
+ var eventSchema =
+ utils.lookup(printerProviderSchema.events, 'name', eventName);
+ var callbackSchema =
+ utils.lookup(eventSchema.parameters, 'type', 'function');
+
+ validate([result], callbackSchema.parameters);
+ };
+
+ // Function provided to the extension as the event callback argument.
+ // It makes sure that the event result hasn't previously been returned
+ // and that the provided result matches the callback schema. In case of
+ // an error it throws an exception.
+ var reportResult = function(result) {
+ if (responded) {
+ throw new Error(
+ 'Event callback must not be called more than once.');
+ }
+
+ var finalResult = null;
+ try {
+ validateResult(result); // throws on failure
+ finalResult = result;
+ } finally {
+ responded = true;
+ resultReporter(args[0] /* requestId */, finalResult);
+ }
+ };
+
+ prepareArgsForDispatch(args, function(success) {
+ if (!success) {
+ // Do not throw an exception since the extension should not yet be
+ // aware of the event.
+ resultReporter(args[0] /* requestId */, null);
+ return;
+ }
+ dispatch(args.slice(1).concat(reportResult));
+ });
+ });
+}
+
+// Sets up printJob.document property for a print request.
+function createPrintRequestBlobArguments(args, callback) {
+ printerProviderInternal.getPrintData(args[0] /* requestId */,
+ function(blobInfo) {
+ if (chrome.runtime.lastError) {
+ callback(false);
+ return;
+ }
+
+ // |args[1]| is printJob.
+ args[1].document = blobNatives.TakeBrowserProcessBlob(
+ blobInfo.blobUuid, blobInfo.type, blobInfo.size);
+ callback(true);
+ });
+}
+
+handleEvent('onGetPrintersRequested',
+ function(args, callback) { callback(true); },
+ printerProviderInternal.reportPrinters);
+
+handleEvent('onGetCapabilityRequested',
+ function(args, callback) { callback(true); },
+ printerProviderInternal.reportPrinterCapability);
+
+handleEvent('onPrintRequested',
+ createPrintRequestBlobArguments,
+ printerProviderInternal.reportPrintResult);
+
+handleEvent('onGetUsbPrinterInfoRequested',
+ function(args, callback) { callback(true); },
+ printerProviderInternal.reportUsbPrinterInfo);
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/runtime_custom_bindings.js b/chromium/extensions/renderer/resources/runtime_custom_bindings.js
new file mode 100644
index 00000000000..1f829d4deb1
--- /dev/null
+++ b/chromium/extensions/renderer/resources/runtime_custom_bindings.js
@@ -0,0 +1,206 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Custom binding for the runtime API.
+
+var binding = require('binding').Binding.create('runtime');
+
+var messaging = require('messaging');
+var runtimeNatives = requireNative('runtime');
+var process = requireNative('process');
+var forEach = require('utils').forEach;
+
+var backgroundPage = window;
+var backgroundRequire = require;
+var contextType = process.GetContextType();
+if (contextType == 'BLESSED_EXTENSION' ||
+ contextType == 'UNBLESSED_EXTENSION') {
+ var manifest = runtimeNatives.GetManifest();
+ if (manifest.app && manifest.app.background) {
+ // Get the background page if one exists. Otherwise, default to the current
+ // window.
+ backgroundPage = runtimeNatives.GetExtensionViews(-1, 'BACKGROUND')[0];
+ if (backgroundPage) {
+ var GetModuleSystem = requireNative('v8_context').GetModuleSystem;
+ backgroundRequire = GetModuleSystem(backgroundPage).require;
+ } else {
+ backgroundPage = window;
+ }
+ }
+}
+
+// For packaged apps, all windows use the bindFileEntryCallback from the
+// background page so their FileEntry objects have the background page's context
+// as their own. This allows them to be used from other windows (including the
+// background page) after the original window is closed.
+if (window == backgroundPage) {
+ var lastError = require('lastError');
+ var fileSystemNatives = requireNative('file_system_natives');
+ var GetIsolatedFileSystem = fileSystemNatives.GetIsolatedFileSystem;
+ var bindDirectoryEntryCallback = function(functionName, apiFunctions) {
+ apiFunctions.setCustomCallback(functionName,
+ function(name, request, callback, response) {
+ if (callback) {
+ if (!response) {
+ callback();
+ return;
+ }
+ var fileSystemId = response.fileSystemId;
+ var baseName = response.baseName;
+ var fs = GetIsolatedFileSystem(fileSystemId);
+
+ try {
+ fs.root.getDirectory(baseName, {}, callback, function(fileError) {
+ lastError.run('runtime.' + functionName,
+ 'Error getting Entry, code: ' + fileError.code,
+ request.stack,
+ callback);
+ });
+ } catch (e) {
+ lastError.run('runtime.' + functionName,
+ 'Error: ' + e.stack,
+ request.stack,
+ callback);
+ }
+ }
+ });
+ };
+} else {
+ // Force the runtime API to be loaded in the background page. Using
+ // backgroundPageModuleSystem.require('runtime') is insufficient as
+ // requireNative is only allowed while lazily loading an API.
+ backgroundPage.chrome.runtime;
+ var bindDirectoryEntryCallback = backgroundRequire(
+ 'runtime').bindDirectoryEntryCallback;
+}
+
+binding.registerCustomHook(function(binding, id, contextType) {
+ var apiFunctions = binding.apiFunctions;
+ var runtime = binding.compiledApi;
+
+ //
+ // Unprivileged APIs.
+ //
+
+ if (id != '')
+ runtime.id = id;
+
+ apiFunctions.setHandleRequest('getManifest', function() {
+ return runtimeNatives.GetManifest();
+ });
+
+ apiFunctions.setHandleRequest('getURL', function(path) {
+ path = String(path);
+ if (!path.length || path[0] != '/')
+ path = '/' + path;
+ return 'chrome-extension://' + id + path;
+ });
+
+ var sendMessageUpdateArguments = messaging.sendMessageUpdateArguments;
+ apiFunctions.setUpdateArgumentsPreValidate('sendMessage',
+ $Function.bind(sendMessageUpdateArguments, null, 'sendMessage',
+ true /* hasOptionsArgument */));
+ apiFunctions.setUpdateArgumentsPreValidate('sendNativeMessage',
+ $Function.bind(sendMessageUpdateArguments, null, 'sendNativeMessage',
+ false /* hasOptionsArgument */));
+
+ apiFunctions.setHandleRequest('sendMessage',
+ function(targetId, message, options, responseCallback) {
+ var connectOptions = {name: messaging.kMessageChannel};
+ forEach(options, function(k, v) {
+ connectOptions[k] = v;
+ });
+ var port = runtime.connect(targetId || runtime.id, connectOptions);
+ messaging.sendMessageImpl(port, message, responseCallback);
+ });
+
+ apiFunctions.setHandleRequest('sendNativeMessage',
+ function(targetId, message, responseCallback) {
+ var port = runtime.connectNative(targetId);
+ messaging.sendMessageImpl(port, message, responseCallback);
+ });
+
+ apiFunctions.setUpdateArgumentsPreValidate('connect', function() {
+ // Align missing (optional) function arguments with the arguments that
+ // schema validation is expecting, e.g.
+ // runtime.connect() -> runtime.connect(null, null)
+ // runtime.connect({}) -> runtime.connect(null, {})
+ var nextArg = 0;
+
+ // targetId (first argument) is optional.
+ var targetId = null;
+ if (typeof(arguments[nextArg]) == 'string')
+ targetId = arguments[nextArg++];
+
+ // connectInfo (second argument) is optional.
+ var connectInfo = null;
+ if (typeof(arguments[nextArg]) == 'object')
+ connectInfo = arguments[nextArg++];
+
+ if (nextArg != arguments.length)
+ throw new Error('Invalid arguments to connect.');
+ return [targetId, connectInfo];
+ });
+
+ apiFunctions.setUpdateArgumentsPreValidate('connectNative',
+ function(appName) {
+ if (typeof(appName) !== 'string') {
+ throw new Error('Invalid arguments to connectNative.');
+ }
+ return [appName];
+ });
+
+ apiFunctions.setHandleRequest('connect', function(targetId, connectInfo) {
+ if (!targetId) {
+ // runtime.id is only defined inside extensions. If we're in a webpage,
+ // the best we can do at this point is to fail.
+ if (!runtime.id) {
+ throw new Error('chrome.runtime.connect() called from a webpage must ' +
+ 'specify an Extension ID (string) for its first ' +
+ 'argument');
+ }
+ targetId = runtime.id;
+ }
+
+ var name = '';
+ if (connectInfo && connectInfo.name)
+ name = connectInfo.name;
+
+ var includeTlsChannelId =
+ !!(connectInfo && connectInfo.includeTlsChannelId);
+
+ var portId = runtimeNatives.OpenChannelToExtension(targetId, name,
+ includeTlsChannelId);
+ if (portId >= 0)
+ return messaging.createPort(portId, name);
+ });
+
+ //
+ // Privileged APIs.
+ //
+ if (contextType != 'BLESSED_EXTENSION')
+ return;
+
+ apiFunctions.setHandleRequest('connectNative',
+ function(nativeAppName) {
+ var portId = runtimeNatives.OpenChannelToNativeApp(runtime.id,
+ nativeAppName);
+ if (portId >= 0)
+ return messaging.createPort(portId, '');
+ throw new Error('Error connecting to native app: ' + nativeAppName);
+ });
+
+ apiFunctions.setCustomCallback('getBackgroundPage',
+ function(name, request, callback, response) {
+ if (callback) {
+ var bg = runtimeNatives.GetExtensionViews(-1, 'BACKGROUND')[0] || null;
+ callback(bg);
+ }
+ });
+
+ bindDirectoryEntryCallback('getPackageDirectoryEntry', apiFunctions);
+});
+
+exports.$set('bindDirectoryEntryCallback', bindDirectoryEntryCallback);
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/schema_utils.js b/chromium/extensions/renderer/resources/schema_utils.js
new file mode 100644
index 00000000000..b14d2eb548d
--- /dev/null
+++ b/chromium/extensions/renderer/resources/schema_utils.js
@@ -0,0 +1,159 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Routines used to validate and normalize arguments.
+// TODO(benwells): unit test this file.
+
+var JSONSchemaValidator = require('json_schema').JSONSchemaValidator;
+
+var schemaValidator = new JSONSchemaValidator();
+
+// Validate arguments.
+function validate(args, parameterSchemas) {
+ if (args.length > parameterSchemas.length)
+ throw new Error("Too many arguments.");
+ for (var i = 0; i < parameterSchemas.length; i++) {
+ if (i in args && args[i] !== null && args[i] !== undefined) {
+ schemaValidator.resetErrors();
+ schemaValidator.validate(args[i], parameterSchemas[i]);
+ if (schemaValidator.errors.length == 0)
+ continue;
+ var message = "Invalid value for argument " + (i + 1) + ". ";
+ for (var i = 0, err;
+ err = schemaValidator.errors[i]; i++) {
+ if (err.path) {
+ message += "Property '" + err.path + "': ";
+ }
+ message += err.message;
+ message = message.substring(0, message.length - 1);
+ message += ", ";
+ }
+ message = message.substring(0, message.length - 2);
+ message += ".";
+ throw new Error(message);
+ } else if (!parameterSchemas[i].optional) {
+ throw new Error("Parameter " + (i + 1) + " (" +
+ parameterSchemas[i].name + ") is required.");
+ }
+ }
+}
+
+// Generate all possible signatures for a given API function.
+function getSignatures(parameterSchemas) {
+ if (parameterSchemas.length === 0)
+ return [[]];
+ var signatures = [];
+ var remaining = getSignatures($Array.slice(parameterSchemas, 1));
+ for (var i = 0; i < remaining.length; i++)
+ $Array.push(signatures, $Array.concat([parameterSchemas[0]], remaining[i]))
+ if (parameterSchemas[0].optional)
+ return $Array.concat(signatures, remaining);
+ return signatures;
+};
+
+// Return true if arguments match a given signature's schema.
+function argumentsMatchSignature(args, candidateSignature) {
+ if (args.length != candidateSignature.length)
+ return false;
+ for (var i = 0; i < candidateSignature.length; i++) {
+ var argType = JSONSchemaValidator.getType(args[i]);
+ if (!schemaValidator.isValidSchemaType(argType,
+ candidateSignature[i]))
+ return false;
+ }
+ return true;
+};
+
+// Finds the function signature for the given arguments.
+function resolveSignature(args, definedSignature) {
+ var candidateSignatures = getSignatures(definedSignature);
+ for (var i = 0; i < candidateSignatures.length; i++) {
+ if (argumentsMatchSignature(args, candidateSignatures[i]))
+ return candidateSignatures[i];
+ }
+ return null;
+};
+
+// Returns a string representing the defined signature of the API function.
+// Example return value for chrome.windows.getCurrent:
+// "windows.getCurrent(optional object populate, function callback)"
+function getParameterSignatureString(name, definedSignature) {
+ var getSchemaTypeString = function(schema) {
+ var schemaTypes = schemaValidator.getAllTypesForSchema(schema);
+ var typeName = schemaTypes.join(" or ") + " " + schema.name;
+ if (schema.optional)
+ return "optional " + typeName;
+ return typeName;
+ };
+ var typeNames = $Array.map(definedSignature, getSchemaTypeString);
+ return name + "(" + typeNames.join(", ") + ")";
+};
+
+// Returns a string representing a call to an API function.
+// Example return value for call: chrome.windows.get(1, callback) is:
+// "windows.get(int, function)"
+function getArgumentSignatureString(name, args) {
+ var typeNames = $Array.map(args, JSONSchemaValidator.getType);
+ return name + "(" + typeNames.join(", ") + ")";
+};
+
+// Finds the correct signature for the given arguments, then validates the
+// arguments against that signature. Returns a 'normalized' arguments list
+// where nulls are inserted where optional parameters were omitted.
+// |args| is expected to be an array.
+function normalizeArgumentsAndValidate(args, funDef) {
+ if (funDef.allowAmbiguousOptionalArguments) {
+ validate(args, funDef.definition.parameters);
+ return args;
+ }
+ var definedSignature = funDef.definition.parameters;
+ var resolvedSignature = resolveSignature(args, definedSignature);
+ if (!resolvedSignature)
+ throw new Error("Invocation of form " +
+ getArgumentSignatureString(funDef.name, args) +
+ " doesn't match definition " +
+ getParameterSignatureString(funDef.name, definedSignature));
+ validate(args, resolvedSignature);
+ var normalizedArgs = [];
+ var ai = 0;
+ for (var si = 0; si < definedSignature.length; si++) {
+ // Handle integer -0 as 0.
+ if (JSONSchemaValidator.getType(args[ai]) === "integer" && args[ai] === 0)
+ args[ai] = 0;
+ if (definedSignature[si] === resolvedSignature[ai])
+ $Array.push(normalizedArgs, args[ai++]);
+ else
+ $Array.push(normalizedArgs, null);
+ }
+ return normalizedArgs;
+};
+
+// Validates that a given schema for an API function is not ambiguous.
+function isFunctionSignatureAmbiguous(functionDef) {
+ if (functionDef.allowAmbiguousOptionalArguments)
+ return false;
+ var signaturesAmbiguous = function(signature1, signature2) {
+ if (signature1.length != signature2.length)
+ return false;
+ for (var i = 0; i < signature1.length; i++) {
+ if (!schemaValidator.checkSchemaOverlap(
+ signature1[i], signature2[i]))
+ return false;
+ }
+ return true;
+ };
+ var candidateSignatures = getSignatures(functionDef.parameters);
+ for (var i = 0; i < candidateSignatures.length; i++) {
+ for (var j = i + 1; j < candidateSignatures.length; j++) {
+ if (signaturesAmbiguous(candidateSignatures[i], candidateSignatures[j]))
+ return true;
+ }
+ }
+ return false;
+};
+
+exports.$set('isFunctionSignatureAmbiguous', isFunctionSignatureAmbiguous);
+exports.$set('normalizeArgumentsAndValidate', normalizeArgumentsAndValidate);
+exports.$set('schemaValidator', schemaValidator);
+exports.$set('validate', validate);
diff --git a/chromium/extensions/renderer/resources/send_request.js b/chromium/extensions/renderer/resources/send_request.js
new file mode 100644
index 00000000000..9fed10e7f42
--- /dev/null
+++ b/chromium/extensions/renderer/resources/send_request.js
@@ -0,0 +1,151 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var exceptionHandler = require('uncaught_exception_handler');
+var lastError = require('lastError');
+var logging = requireNative('logging');
+var natives = requireNative('sendRequest');
+var validate = require('schemaUtils').validate;
+
+// All outstanding requests from sendRequest().
+var requests = {};
+
+// Used to prevent double Activity Logging for API calls that use both custom
+// bindings and ExtensionFunctions (via sendRequest).
+var calledSendRequest = false;
+
+// Runs a user-supplied callback safely.
+function safeCallbackApply(name, request, callback, args) {
+ try {
+ $Function.apply(callback, request, args);
+ } catch (e) {
+ exceptionHandler.handle('Error in response to ' + name, e, request.stack);
+ }
+}
+
+// Callback handling.
+function handleResponse(requestId, name, success, responseList, error) {
+ // The chrome objects we will set lastError on. Really we should only be
+ // setting this on the callback's chrome object, but set on ours too since
+ // it's conceivable that something relies on that.
+ var callerChrome = chrome;
+
+ try {
+ var request = requests[requestId];
+ logging.DCHECK(request != null);
+
+ // lastError needs to be set on the caller's chrome object no matter what,
+ // though chances are it's the same as ours (it will be different when
+ // calling API methods on other contexts).
+ if (request.callback) {
+ var global = natives.GetGlobal(request.callback);
+ callerChrome = global ? global.chrome : callerChrome;
+ }
+
+ lastError.clear(chrome);
+ if (callerChrome !== chrome)
+ lastError.clear(callerChrome);
+
+ if (!success) {
+ if (!error)
+ error = "Unknown error.";
+ lastError.set(name, error, request.stack, chrome);
+ if (callerChrome !== chrome)
+ lastError.set(name, error, request.stack, callerChrome);
+ }
+
+ if (request.customCallback) {
+ safeCallbackApply(name,
+ request,
+ request.customCallback,
+ $Array.concat([name, request, request.callback],
+ responseList));
+ } else if (request.callback) {
+ // Validate callback in debug only -- and only when the
+ // caller has provided a callback. Implementations of api
+ // calls may not return data if they observe the caller
+ // has not provided a callback.
+ if (logging.DCHECK_IS_ON() && !error) {
+ if (!request.callbackSchema.parameters)
+ throw new Error(name + ": no callback schema defined");
+ validate(responseList, request.callbackSchema.parameters);
+ }
+ safeCallbackApply(name, request, request.callback, responseList);
+ }
+
+ if (error && !lastError.hasAccessed(chrome)) {
+ // The native call caused an error, but the developer might not have
+ // checked runtime.lastError.
+ lastError.reportIfUnchecked(name, callerChrome, request.stack);
+ }
+ } finally {
+ delete requests[requestId];
+ lastError.clear(chrome);
+ if (callerChrome !== chrome)
+ lastError.clear(callerChrome);
+ }
+}
+
+function prepareRequest(args, argSchemas) {
+ var request = {};
+ var argCount = args.length;
+
+ // Look for callback param.
+ if (argSchemas.length > 0 &&
+ argSchemas[argSchemas.length - 1].type == "function") {
+ request.callback = args[args.length - 1];
+ request.callbackSchema = argSchemas[argSchemas.length - 1];
+ --argCount;
+ }
+
+ request.args = [];
+ for (var k = 0; k < argCount; k++) {
+ request.args[k] = args[k];
+ }
+
+ return request;
+}
+
+// Send an API request and optionally register a callback.
+// |optArgs| is an object with optional parameters as follows:
+// - customCallback: a callback that should be called instead of the standard
+// callback.
+// - forIOThread: true if this function should be handled on the browser IO
+// thread.
+// - preserveNullInObjects: true if it is safe for null to be in objects.
+// - stack: An optional string that contains the stack trace, to be displayed
+// to the user if an error occurs.
+function sendRequest(functionName, args, argSchemas, optArgs) {
+ calledSendRequest = true;
+ if (!optArgs)
+ optArgs = {};
+ var request = prepareRequest(args, argSchemas);
+ request.stack = optArgs.stack || exceptionHandler.getExtensionStackTrace();
+ if (optArgs.customCallback) {
+ request.customCallback = optArgs.customCallback;
+ }
+
+ var hasCallback = request.callback || optArgs.customCallback;
+ var requestId =
+ natives.StartRequest(functionName, request.args, hasCallback,
+ optArgs.forIOThread, optArgs.preserveNullInObjects);
+ request.id = requestId;
+ requests[requestId] = request;
+}
+
+function getCalledSendRequest() {
+ return calledSendRequest;
+}
+
+function clearCalledSendRequest() {
+ calledSendRequest = false;
+}
+
+exports.$set('sendRequest', sendRequest);
+exports.$set('getCalledSendRequest', getCalledSendRequest);
+exports.$set('clearCalledSendRequest', clearCalledSendRequest);
+exports.$set('safeCallbackApply', safeCallbackApply);
+
+// Called by C++.
+exports.$set('handleResponse', handleResponse);
diff --git a/chromium/extensions/renderer/resources/serial_custom_bindings.js b/chromium/extensions/renderer/resources/serial_custom_bindings.js
new file mode 100644
index 00000000000..d77631cf645
--- /dev/null
+++ b/chromium/extensions/renderer/resources/serial_custom_bindings.js
@@ -0,0 +1,117 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * Custom bindings for the Serial API.
+ *
+ * The bindings are implemented by asynchronously delegating to the
+ * serial_service module. The functions that apply to a particular connection
+ * are delegated to the appropriate method on the Connection object specified by
+ * the ID parameter.
+ */
+
+var binding = require('binding').Binding.create('serial');
+var context = requireNative('v8_context');
+var eventBindings = require('event_bindings');
+var utils = require('utils');
+
+var serialServicePromise = function() {
+ // getBackgroundPage is not available in unit tests so fall back to the
+ // current page's serial_service module.
+ if (!chrome.runtime.getBackgroundPage)
+ return requireAsync('serial_service');
+
+ // Load the serial_service module from the background page if one exists. This
+ // is necessary for serial connections created in one window to be usable
+ // after that window is closed. This is necessary because the Mojo async
+ // waiter only functions while the v8 context remains.
+ return utils.promise(chrome.runtime.getBackgroundPage).then(function(bgPage) {
+ return context.GetModuleSystem(bgPage).requireAsync('serial_service');
+ }).catch(function() {
+ return requireAsync('serial_service');
+ });
+}();
+
+function forwardToConnection(methodName) {
+ return function(connectionId) {
+ var args = $Array.slice(arguments, 1);
+ return serialServicePromise.then(function(serialService) {
+ return serialService.getConnection(connectionId);
+ }).then(function(connection) {
+ return $Function.apply(connection[methodName], connection, args);
+ });
+ };
+}
+
+function addEventListeners(connection, id) {
+ connection.onData = function(data) {
+ eventBindings.dispatchEvent(
+ 'serial.onReceive', [{connectionId: id, data: data}]);
+ };
+ connection.onError = function(error) {
+ eventBindings.dispatchEvent(
+ 'serial.onReceiveError', [{connectionId: id, error: error}]);
+ };
+}
+
+serialServicePromise.then(function(serialService) {
+ return serialService.getConnections().then(function(connections) {
+ for (var entry of connections) {
+ var connection = entry[1];
+ addEventListeners(connection, entry[0]);
+ connection.resumeReceives();
+ };
+ });
+});
+
+binding.registerCustomHook(function(bindingsAPI) {
+ var apiFunctions = bindingsAPI.apiFunctions;
+ apiFunctions.setHandleRequestWithPromise('getDevices', function() {
+ return serialServicePromise.then(function(serialService) {
+ return serialService.getDevices();
+ });
+ });
+
+ apiFunctions.setHandleRequestWithPromise('connect', function(path, options) {
+ return serialServicePromise.then(function(serialService) {
+ return serialService.createConnection(path, options);
+ }).then(function(result) {
+ addEventListeners(result.connection, result.info.connectionId);
+ return result.info;
+ }).catch (function(e) {
+ throw new Error('Failed to connect to the port.');
+ });
+ });
+
+ apiFunctions.setHandleRequestWithPromise(
+ 'disconnect', forwardToConnection('close'));
+ apiFunctions.setHandleRequestWithPromise(
+ 'getInfo', forwardToConnection('getInfo'));
+ apiFunctions.setHandleRequestWithPromise(
+ 'update', forwardToConnection('setOptions'));
+ apiFunctions.setHandleRequestWithPromise(
+ 'getControlSignals', forwardToConnection('getControlSignals'));
+ apiFunctions.setHandleRequestWithPromise(
+ 'setControlSignals', forwardToConnection('setControlSignals'));
+ apiFunctions.setHandleRequestWithPromise(
+ 'flush', forwardToConnection('flush'));
+ apiFunctions.setHandleRequestWithPromise(
+ 'setPaused', forwardToConnection('setPaused'));
+ apiFunctions.setHandleRequestWithPromise(
+ 'send', forwardToConnection('send'));
+
+ apiFunctions.setHandleRequestWithPromise('getConnections', function() {
+ return serialServicePromise.then(function(serialService) {
+ return serialService.getConnections();
+ }).then(function(connections) {
+ var promises = [];
+ for (var connection of connections.values()) {
+ promises.push(connection.getInfo());
+ }
+ return Promise.all(promises);
+ });
+ });
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/serial_service.js b/chromium/extensions/renderer/resources/serial_service.js
new file mode 100644
index 00000000000..26b990b227e
--- /dev/null
+++ b/chromium/extensions/renderer/resources/serial_service.js
@@ -0,0 +1,554 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define('serial_service', [
+ 'content/public/renderer/frame_service_registry',
+ 'data_receiver',
+ 'data_sender',
+ 'device/serial/serial.mojom',
+ 'device/serial/serial_serialization.mojom',
+ 'mojo/public/js/core',
+ 'mojo/public/js/router',
+ 'stash_client',
+], function(serviceProvider,
+ dataReceiver,
+ dataSender,
+ serialMojom,
+ serialization,
+ core,
+ routerModule,
+ stashClient) {
+ /**
+ * A Javascript client for the serial service and connection Mojo services.
+ *
+ * This provides a thick client around the Mojo services, exposing a JS-style
+ * interface to serial connections and information about serial devices. This
+ * converts parameters and result between the Apps serial API types and the
+ * Mojo types.
+ */
+
+ var service = new serialMojom.SerialService.proxyClass(
+ new routerModule.Router(
+ serviceProvider.connectToService(serialMojom.SerialService.name)));
+
+ function getDevices() {
+ return service.getDevices().then(function(response) {
+ return $Array.map(response.devices, function(device) {
+ var result = {path: device.path};
+ if (device.has_vendor_id)
+ result.vendorId = device.vendor_id;
+ if (device.has_product_id)
+ result.productId = device.product_id;
+ if (device.display_name)
+ result.displayName = device.display_name;
+ return result;
+ });
+ });
+ }
+
+ var DATA_BITS_TO_MOJO = {
+ undefined: serialMojom.DataBits.NONE,
+ 'seven': serialMojom.DataBits.SEVEN,
+ 'eight': serialMojom.DataBits.EIGHT,
+ };
+ var STOP_BITS_TO_MOJO = {
+ undefined: serialMojom.StopBits.NONE,
+ 'one': serialMojom.StopBits.ONE,
+ 'two': serialMojom.StopBits.TWO,
+ };
+ var PARITY_BIT_TO_MOJO = {
+ undefined: serialMojom.ParityBit.NONE,
+ 'no': serialMojom.ParityBit.NO,
+ 'odd': serialMojom.ParityBit.ODD,
+ 'even': serialMojom.ParityBit.EVEN,
+ };
+ var SEND_ERROR_TO_MOJO = {
+ undefined: serialMojom.SendError.NONE,
+ 'disconnected': serialMojom.SendError.DISCONNECTED,
+ 'pending': serialMojom.SendError.PENDING,
+ 'timeout': serialMojom.SendError.TIMEOUT,
+ 'system_error': serialMojom.SendError.SYSTEM_ERROR,
+ };
+ var RECEIVE_ERROR_TO_MOJO = {
+ undefined: serialMojom.ReceiveError.NONE,
+ 'disconnected': serialMojom.ReceiveError.DISCONNECTED,
+ 'device_lost': serialMojom.ReceiveError.DEVICE_LOST,
+ 'timeout': serialMojom.ReceiveError.TIMEOUT,
+ 'break': serialMojom.ReceiveError.BREAK,
+ 'frame_error': serialMojom.ReceiveError.FRAME_ERROR,
+ 'overrun': serialMojom.ReceiveError.OVERRUN,
+ 'buffer_overflow': serialMojom.ReceiveError.BUFFER_OVERFLOW,
+ 'parity_error': serialMojom.ReceiveError.PARITY_ERROR,
+ 'system_error': serialMojom.ReceiveError.SYSTEM_ERROR,
+ };
+
+ function invertMap(input) {
+ var output = {};
+ for (var key in input) {
+ if (key == 'undefined')
+ output[input[key]] = undefined;
+ else
+ output[input[key]] = key;
+ }
+ return output;
+ }
+ var DATA_BITS_FROM_MOJO = invertMap(DATA_BITS_TO_MOJO);
+ var STOP_BITS_FROM_MOJO = invertMap(STOP_BITS_TO_MOJO);
+ var PARITY_BIT_FROM_MOJO = invertMap(PARITY_BIT_TO_MOJO);
+ var SEND_ERROR_FROM_MOJO = invertMap(SEND_ERROR_TO_MOJO);
+ var RECEIVE_ERROR_FROM_MOJO = invertMap(RECEIVE_ERROR_TO_MOJO);
+
+ function getServiceOptions(options) {
+ var out = {};
+ if (options.dataBits)
+ out.data_bits = DATA_BITS_TO_MOJO[options.dataBits];
+ if (options.stopBits)
+ out.stop_bits = STOP_BITS_TO_MOJO[options.stopBits];
+ if (options.parityBit)
+ out.parity_bit = PARITY_BIT_TO_MOJO[options.parityBit];
+ if ('ctsFlowControl' in options) {
+ out.has_cts_flow_control = true;
+ out.cts_flow_control = options.ctsFlowControl;
+ }
+ if ('bitrate' in options)
+ out.bitrate = options.bitrate;
+ return out;
+ }
+
+ function convertServiceInfo(result) {
+ if (!result.info)
+ throw new Error('Failed to get ConnectionInfo.');
+ return {
+ ctsFlowControl: !!result.info.cts_flow_control,
+ bitrate: result.info.bitrate || undefined,
+ dataBits: DATA_BITS_FROM_MOJO[result.info.data_bits],
+ stopBits: STOP_BITS_FROM_MOJO[result.info.stop_bits],
+ parityBit: PARITY_BIT_FROM_MOJO[result.info.parity_bit],
+ };
+ }
+
+ // Update client-side options |clientOptions| from the user-provided
+ // |options|.
+ function updateClientOptions(clientOptions, options) {
+ if ('name' in options)
+ clientOptions.name = options.name;
+ if ('receiveTimeout' in options)
+ clientOptions.receiveTimeout = options.receiveTimeout;
+ if ('sendTimeout' in options)
+ clientOptions.sendTimeout = options.sendTimeout;
+ if ('bufferSize' in options)
+ clientOptions.bufferSize = options.bufferSize;
+ if ('persistent' in options)
+ clientOptions.persistent = options.persistent;
+ };
+
+ function Connection(connection, router, receivePipe, receiveClientPipe,
+ sendPipe, id, options) {
+ var state = new serialization.ConnectionState();
+ state.connectionId = id;
+ updateClientOptions(state, options);
+ var receiver = new dataReceiver.DataReceiver(
+ receivePipe, receiveClientPipe, state.bufferSize,
+ serialMojom.ReceiveError.DISCONNECTED);
+ var sender = new dataSender.DataSender(sendPipe, state.bufferSize,
+ serialMojom.SendError.DISCONNECTED);
+ this.init_(state,
+ connection,
+ router,
+ receiver,
+ sender,
+ null,
+ serialMojom.ReceiveError.NONE);
+ connections_.set(id, this);
+ this.startReceive_();
+ }
+
+ // Initializes this Connection from the provided args.
+ Connection.prototype.init_ = function(state,
+ connection,
+ router,
+ receiver,
+ sender,
+ queuedReceiveData,
+ queuedReceiveError) {
+ this.state_ = state;
+
+ // queuedReceiveData_ or queuedReceiveError_ will store the receive result
+ // or error, respectively, if a receive completes or fails while this
+ // connection is paused. At most one of the the two may be non-null: a
+ // receive completed while paused will only set one of them, no further
+ // receives will be performed while paused and a queued result is dispatched
+ // before any further receives are initiated when unpausing.
+ if (queuedReceiveError != serialMojom.ReceiveError.NONE)
+ this.queuedReceiveError_ = {error: queuedReceiveError};
+ if (queuedReceiveData) {
+ this.queuedReceiveData_ = new ArrayBuffer(queuedReceiveData.length);
+ new Int8Array(this.queuedReceiveData_).set(queuedReceiveData);
+ }
+ this.router_ = router;
+ this.remoteConnection_ = connection;
+ this.receivePipe_ = receiver;
+ this.sendPipe_ = sender;
+ this.sendInProgress_ = false;
+ };
+
+ Connection.create = function(path, options) {
+ options = options || {};
+ var serviceOptions = getServiceOptions(options);
+ var pipe = core.createMessagePipe();
+ var sendPipe = core.createMessagePipe();
+ var receivePipe = core.createMessagePipe();
+ var receivePipeClient = core.createMessagePipe();
+ service.connect(path,
+ serviceOptions,
+ pipe.handle0,
+ sendPipe.handle0,
+ receivePipe.handle0,
+ receivePipeClient.handle0);
+ var router = new routerModule.Router(pipe.handle1);
+ var connection = new serialMojom.Connection.proxyClass(router);
+ return connection.getInfo().then(convertServiceInfo).then(function(info) {
+ return Promise.all([info, allocateConnectionId()]);
+ }).catch(function(e) {
+ router.close();
+ core.close(sendPipe.handle1);
+ core.close(receivePipe.handle1);
+ core.close(receivePipeClient.handle1);
+ throw e;
+ }).then(function(results) {
+ var info = results[0];
+ var id = results[1];
+ var serialConnectionClient = new Connection(connection,
+ router,
+ receivePipe.handle1,
+ receivePipeClient.handle1,
+ sendPipe.handle1,
+ id,
+ options);
+ var clientInfo = serialConnectionClient.getClientInfo_();
+ for (var key in clientInfo) {
+ info[key] = clientInfo[key];
+ }
+ return {
+ connection: serialConnectionClient,
+ info: info,
+ };
+ });
+ };
+
+ Connection.prototype.close = function() {
+ this.router_.close();
+ this.receivePipe_.close();
+ this.sendPipe_.close();
+ clearTimeout(this.receiveTimeoutId_);
+ clearTimeout(this.sendTimeoutId_);
+ connections_.delete(this.state_.connectionId);
+ return true;
+ };
+
+ Connection.prototype.getClientInfo_ = function() {
+ return {
+ connectionId: this.state_.connectionId,
+ paused: this.state_.paused,
+ persistent: this.state_.persistent,
+ name: this.state_.name,
+ receiveTimeout: this.state_.receiveTimeout,
+ sendTimeout: this.state_.sendTimeout,
+ bufferSize: this.state_.bufferSize,
+ };
+ };
+
+ Connection.prototype.getInfo = function() {
+ var info = this.getClientInfo_();
+ return this.remoteConnection_.getInfo().then(convertServiceInfo).then(
+ function(result) {
+ for (var key in result) {
+ info[key] = result[key];
+ }
+ return info;
+ }).catch(function() {
+ return info;
+ });
+ };
+
+ Connection.prototype.setOptions = function(options) {
+ updateClientOptions(this.state_, options);
+ var serviceOptions = getServiceOptions(options);
+ if ($Object.keys(serviceOptions).length == 0)
+ return true;
+ return this.remoteConnection_.setOptions(serviceOptions).then(
+ function(result) {
+ return !!result.success;
+ }).catch(function() {
+ return false;
+ });
+ };
+
+ Connection.prototype.getControlSignals = function() {
+ return this.remoteConnection_.getControlSignals().then(function(result) {
+ if (!result.signals)
+ throw new Error('Failed to get control signals.');
+ var signals = result.signals;
+ return {
+ dcd: !!signals.dcd,
+ cts: !!signals.cts,
+ ri: !!signals.ri,
+ dsr: !!signals.dsr,
+ };
+ });
+ };
+
+ Connection.prototype.setControlSignals = function(signals) {
+ var controlSignals = {};
+ if ('dtr' in signals) {
+ controlSignals.has_dtr = true;
+ controlSignals.dtr = signals.dtr;
+ }
+ if ('rts' in signals) {
+ controlSignals.has_rts = true;
+ controlSignals.rts = signals.rts;
+ }
+ return this.remoteConnection_.setControlSignals(controlSignals).then(
+ function(result) {
+ return !!result.success;
+ });
+ };
+
+ Connection.prototype.flush = function() {
+ return this.remoteConnection_.flush().then(function(result) {
+ return !!result.success;
+ });
+ };
+
+ Connection.prototype.setPaused = function(paused) {
+ this.state_.paused = paused;
+ if (paused) {
+ clearTimeout(this.receiveTimeoutId_);
+ this.receiveTimeoutId_ = null;
+ } else if (!this.receiveInProgress_) {
+ this.startReceive_();
+ }
+ };
+
+ Connection.prototype.send = function(data) {
+ if (this.sendInProgress_)
+ return Promise.resolve({bytesSent: 0, error: 'pending'});
+
+ if (this.state_.sendTimeout) {
+ this.sendTimeoutId_ = setTimeout(function() {
+ this.sendPipe_.cancel(serialMojom.SendError.TIMEOUT);
+ }.bind(this), this.state_.sendTimeout);
+ }
+ this.sendInProgress_ = true;
+ return this.sendPipe_.send(data).then(function(bytesSent) {
+ return {bytesSent: bytesSent};
+ }).catch(function(e) {
+ return {
+ bytesSent: e.bytesSent,
+ error: SEND_ERROR_FROM_MOJO[e.error],
+ };
+ }).then(function(result) {
+ if (this.sendTimeoutId_)
+ clearTimeout(this.sendTimeoutId_);
+ this.sendTimeoutId_ = null;
+ this.sendInProgress_ = false;
+ return result;
+ }.bind(this));
+ };
+
+ Connection.prototype.startReceive_ = function() {
+ this.receiveInProgress_ = true;
+ var receivePromise = null;
+ // If we have a queued receive result, dispatch it immediately instead of
+ // starting a new receive.
+ if (this.queuedReceiveData_) {
+ receivePromise = Promise.resolve(this.queuedReceiveData_);
+ this.queuedReceiveData_ = null;
+ } else if (this.queuedReceiveError_) {
+ receivePromise = Promise.reject(this.queuedReceiveError_);
+ this.queuedReceiveError_ = null;
+ } else {
+ receivePromise = this.receivePipe_.receive();
+ }
+ receivePromise.then(this.onDataReceived_.bind(this)).catch(
+ this.onReceiveError_.bind(this));
+ this.startReceiveTimeoutTimer_();
+ };
+
+ Connection.prototype.onDataReceived_ = function(data) {
+ this.startReceiveTimeoutTimer_();
+ this.receiveInProgress_ = false;
+ if (this.state_.paused) {
+ this.queuedReceiveData_ = data;
+ return;
+ }
+ if (this.onData) {
+ this.onData(data);
+ }
+ if (!this.state_.paused) {
+ this.startReceive_();
+ }
+ };
+
+ Connection.prototype.onReceiveError_ = function(e) {
+ clearTimeout(this.receiveTimeoutId_);
+ this.receiveInProgress_ = false;
+ if (this.state_.paused) {
+ this.queuedReceiveError_ = e;
+ return;
+ }
+ var error = e.error;
+ this.state_.paused = true;
+ if (this.onError)
+ this.onError(RECEIVE_ERROR_FROM_MOJO[error]);
+ };
+
+ Connection.prototype.startReceiveTimeoutTimer_ = function() {
+ clearTimeout(this.receiveTimeoutId_);
+ if (this.state_.receiveTimeout && !this.state_.paused) {
+ this.receiveTimeoutId_ = setTimeout(this.onReceiveTimeout_.bind(this),
+ this.state_.receiveTimeout);
+ }
+ };
+
+ Connection.prototype.onReceiveTimeout_ = function() {
+ if (this.onError)
+ this.onError('timeout');
+ this.startReceiveTimeoutTimer_();
+ };
+
+ Connection.prototype.serialize = function() {
+ connections_.delete(this.state_.connectionId);
+ this.onData = null;
+ this.onError = null;
+ var handle = this.router_.connector_.handle_;
+ this.router_.connector_.handle_ = null;
+ this.router_.close();
+ clearTimeout(this.receiveTimeoutId_);
+ clearTimeout(this.sendTimeoutId_);
+
+ // Serializing receivePipe_ will cancel an in-progress receive, which would
+ // pause the connection, so save it ahead of time.
+ var paused = this.state_.paused;
+ return Promise.all([
+ this.receivePipe_.serialize(),
+ this.sendPipe_.serialize(),
+ ]).then(function(serializedComponents) {
+ var queuedReceiveError = serialMojom.ReceiveError.NONE;
+ if (this.queuedReceiveError_)
+ queuedReceiveError = this.queuedReceiveError_.error;
+ this.state_.paused = paused;
+ var serialized = new serialization.SerializedConnection();
+ serialized.state = this.state_;
+ serialized.queuedReceiveError = queuedReceiveError;
+ serialized.queuedReceiveData =
+ this.queuedReceiveData_ ? new Int8Array(this.queuedReceiveData_) :
+ null;
+ serialized.connection = handle;
+ serialized.receiver = serializedComponents[0];
+ serialized.sender = serializedComponents[1];
+ return serialized;
+ }.bind(this));
+ };
+
+ Connection.deserialize = function(serialized) {
+ var serialConnection = $Object.create(Connection.prototype);
+ var router = new routerModule.Router(serialized.connection);
+ var connection = new serialMojom.Connection.proxyClass(router);
+ var receiver = dataReceiver.DataReceiver.deserialize(serialized.receiver);
+ var sender = dataSender.DataSender.deserialize(serialized.sender);
+
+ // Ensure that paused and persistent are booleans.
+ serialized.state.paused = !!serialized.state.paused;
+ serialized.state.persistent = !!serialized.state.persistent;
+ serialConnection.init_(serialized.state,
+ connection,
+ router,
+ receiver,
+ sender,
+ serialized.queuedReceiveData,
+ serialized.queuedReceiveError);
+ serialConnection.awaitingResume_ = true;
+ var connectionId = serialized.state.connectionId;
+ connections_.set(connectionId, serialConnection);
+ if (connectionId >= nextConnectionId_)
+ nextConnectionId_ = connectionId + 1;
+ return serialConnection;
+ };
+
+ // Resume receives on a deserialized connection.
+ Connection.prototype.resumeReceives = function() {
+ if (!this.awaitingResume_)
+ return;
+ this.awaitingResume_ = false;
+ if (!this.state_.paused)
+ this.startReceive_();
+ };
+
+ // All accesses to connections_ and nextConnectionId_ other than those
+ // involved in deserialization should ensure that
+ // connectionDeserializationComplete_ has resolved first.
+ var connectionDeserializationComplete_ = stashClient.retrieve(
+ 'serial', serialization.SerializedConnection).then(function(decoded) {
+ if (!decoded)
+ return;
+ return Promise.all($Array.map(decoded, Connection.deserialize));
+ });
+
+ // The map of connection ID to connection object.
+ var connections_ = new Map();
+
+ // The next connection ID to be allocated.
+ var nextConnectionId_ = 0;
+
+ function getConnections() {
+ return connectionDeserializationComplete_.then(function() {
+ return new Map(connections_);
+ });
+ }
+
+ function getConnection(id) {
+ return getConnections().then(function(connections) {
+ if (!connections.has(id))
+ throw new Error('Serial connection not found.');
+ return connections.get(id);
+ });
+ }
+
+ function allocateConnectionId() {
+ return connectionDeserializationComplete_.then(function() {
+ return nextConnectionId_++;
+ });
+ }
+
+ stashClient.registerClient(
+ 'serial', serialization.SerializedConnection, function() {
+ return connectionDeserializationComplete_.then(function() {
+ var clientPromises = [];
+ for (var connection of connections_.values()) {
+ if (connection.state_.persistent)
+ clientPromises.push(connection.serialize());
+ else
+ connection.close();
+ }
+ return Promise.all($Array.map(clientPromises, function(promise) {
+ return promise.then(function(serialization) {
+ return {
+ serialization: serialization,
+ monitorHandles: !serialization.paused,
+ };
+ });
+ }));
+ });
+ });
+
+ return {
+ getDevices: getDevices,
+ createConnection: Connection.create,
+ getConnection: getConnection,
+ getConnections: getConnections,
+ // For testing.
+ Connection: Connection,
+ };
+});
diff --git a/chromium/extensions/renderer/resources/service_worker_bindings.js b/chromium/extensions/renderer/resources/service_worker_bindings.js
new file mode 100644
index 00000000000..532f51ddd2b
--- /dev/null
+++ b/chromium/extensions/renderer/resources/service_worker_bindings.js
@@ -0,0 +1,67 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This function is returned to DidInitializeServiceWorkerContextOnWorkerThread
+// then executed, passing in dependencies as function arguments.
+//
+// |backgroundUrl| is the URL of the extension's background page.
+// |wakeEventPage| is a function that wakes up the current extension's event
+// page, then runs its callback on completion or failure.
+// |logging| is an object equivalent to a subset of base/debug/logging.h, with
+// CHECK/DCHECK/etc.
+(function(backgroundUrl, wakeEventPage, logging) {
+ 'use strict';
+ self.chrome = self.chrome || {};
+ self.chrome.runtime = self.chrome.runtime || {};
+
+ // Returns a Promise that resolves to the background page's client, or null
+ // if there is no background client.
+ function findBackgroundClient() {
+ return self.clients.matchAll({
+ includeUncontrolled: true,
+ type: 'window'
+ }).then(function(clients) {
+ return clients.find(function(client) {
+ return client.url == backgroundUrl;
+ });
+ });
+ }
+
+ // Returns a Promise wrapper around wakeEventPage, that resolves on success,
+ // or rejects on failure.
+ function makeWakeEventPagePromise() {
+ return new Promise(function(resolve, reject) {
+ wakeEventPage(function(success) {
+ if (success)
+ resolve();
+ else
+ reject('Failed to start background client "' + backgroundUrl + '"');
+ });
+ });
+ }
+
+ // The chrome.runtime.getBackgroundClient function is documented in
+ // runtime.json. It returns a Promise that resolves to the background page's
+ // client, or is rejected if there is no background client or if the
+ // background client failed to wake.
+ self.chrome.runtime.getBackgroundClient = function() {
+ return findBackgroundClient().then(function(client) {
+ if (client) {
+ // Background client is already awake, or it was persistent.
+ return client;
+ }
+
+ // Event page needs to be woken.
+ return makeWakeEventPagePromise().then(function() {
+ return findBackgroundClient();
+ }).then(function(client) {
+ if (!client) {
+ return Promise.reject(
+ 'Background client "' + backgroundUrl + '" not found');
+ }
+ return client;
+ });
+ });
+ };
+});
diff --git a/chromium/extensions/renderer/resources/set_icon.js b/chromium/extensions/renderer/resources/set_icon.js
new file mode 100644
index 00000000000..b0ed2b5b5fa
--- /dev/null
+++ b/chromium/extensions/renderer/resources/set_icon.js
@@ -0,0 +1,112 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var SetIconCommon = requireNative('setIcon').SetIconCommon;
+var sendRequest = require('sendRequest').sendRequest;
+
+function loadImagePath(path, callback) {
+ var img = new Image();
+ img.onerror = function() {
+ console.error('Could not load action icon \'' + path + '\'.');
+ };
+ img.onload = function() {
+ var canvas = document.createElement('canvas');
+ canvas.width = img.width;
+ canvas.height = img.height;
+
+ var canvas_context = canvas.getContext('2d');
+ canvas_context.clearRect(0, 0, canvas.width, canvas.height);
+ canvas_context.drawImage(img, 0, 0, canvas.width, canvas.height);
+ var imageData = canvas_context.getImageData(0, 0, canvas.width,
+ canvas.height);
+ callback(imageData);
+ };
+ img.src = path;
+}
+
+function smellsLikeImageData(imageData) {
+ // See if this object at least looks like an ImageData element.
+ // Unfortunately, we cannot use instanceof because the ImageData
+ // constructor is not public.
+ //
+ // We do this manually instead of using JSONSchema to avoid having these
+ // properties show up in the doc.
+ return (typeof imageData == 'object') && ('width' in imageData) &&
+ ('height' in imageData) && ('data' in imageData);
+}
+
+function verifyImageData(imageData) {
+ if (!smellsLikeImageData(imageData)) {
+ throw new Error(
+ 'The imageData property must contain an ImageData object or' +
+ ' dictionary of ImageData objects.');
+ }
+}
+
+/**
+ * Normalizes |details| to a format suitable for sending to the browser,
+ * for example converting ImageData to a binary representation.
+ *
+ * @param {ImageDetails} details
+ * The ImageDetails passed into an extension action-style API.
+ * @param {Function} callback
+ * The callback function to pass processed imageData back to. Note that this
+ * callback may be called reentrantly.
+ */
+function setIcon(details, callback) {
+ // Note that iconIndex is actually deprecated, and only available to the
+ // pageAction API.
+ // TODO(kalman): Investigate whether this is for the pageActions API, and if
+ // so, delete it.
+ if ('iconIndex' in details) {
+ callback(details);
+ return;
+ }
+
+ if ('imageData' in details) {
+ if (smellsLikeImageData(details.imageData)) {
+ var imageData = details.imageData;
+ details.imageData = {};
+ details.imageData[imageData.width.toString()] = imageData;
+ } else if (typeof details.imageData == 'object' &&
+ Object.getOwnPropertyNames(details.imageData).length !== 0) {
+ for (var sizeKey in details.imageData) {
+ verifyImageData(details.imageData[sizeKey]);
+ }
+ } else {
+ verifyImageData(false);
+ }
+
+ callback(SetIconCommon(details));
+ return;
+ }
+
+ if ('path' in details) {
+ if (typeof details.path == 'object') {
+ details.imageData = {};
+ var detailKeyCount = 0;
+ for (var iconSize in details.path) {
+ ++detailKeyCount;
+ loadImagePath(details.path[iconSize], function(size, imageData) {
+ details.imageData[size] = imageData;
+ if (--detailKeyCount == 0)
+ callback(SetIconCommon(details));
+ }.bind(null, iconSize));
+ }
+ if (detailKeyCount == 0)
+ throw new Error('The path property must not be empty.');
+ } else if (typeof details.path == 'string') {
+ details.imageData = {};
+ loadImagePath(details.path, function(imageData) {
+ details.imageData[imageData.width.toString()] = imageData;
+ delete details.path;
+ callback(SetIconCommon(details));
+ });
+ }
+ return;
+ }
+ throw new Error('Either the path or imageData property must be specified.');
+}
+
+exports.$set('setIcon', setIcon);
diff --git a/chromium/extensions/renderer/resources/stash_client.js b/chromium/extensions/renderer/resources/stash_client.js
new file mode 100644
index 00000000000..240a676463d
--- /dev/null
+++ b/chromium/extensions/renderer/resources/stash_client.js
@@ -0,0 +1,168 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define('stash_client', [
+ 'async_waiter',
+ 'content/public/renderer/frame_service_registry',
+ 'extensions/common/mojo/stash.mojom',
+ 'mojo/public/js/buffer',
+ 'mojo/public/js/codec',
+ 'mojo/public/js/core',
+ 'mojo/public/js/router',
+], function(asyncWaiter, serviceProvider, stashMojom, bufferModule,
+ codec, core, routerModule) {
+ /**
+ * @module stash_client
+ */
+
+ var service = new stashMojom.StashService.proxyClass(new routerModule.Router(
+ serviceProvider.connectToService(stashMojom.StashService.name)));
+
+ /**
+ * A callback invoked to obtain objects to stash from a particular client.
+ * @callback module:stash_client.StashCallback
+ * @return {!Promise<!Array<!Object>>|!Array<!Object>} An array of objects to
+ * stash or a promise that will resolve to an array of objects to stash.
+ * The exact type of each object should match the type passed alongside
+ * this callback.
+ */
+
+ /**
+ * A stash client registration.
+ * @constructor
+ * @private
+ * @alias module:stash_client~Registration
+ */
+ function Registration(id, type, callback) {
+ /**
+ * The client id.
+ * @type {string}
+ * @private
+ */
+ this.id_ = id;
+
+ /**
+ * The type of the objects to be stashed.
+ * @type {!Object}
+ * @private
+ */
+ this.type_ = type;
+
+ /**
+ * The callback to invoke to obtain the objects to stash.
+ * @type {module:stash_client.StashCallback}
+ * @private
+ */
+ this.callback_ = callback;
+ }
+
+ /**
+ * Serializes and returns this client's stashable objects.
+ * @return
+ * {!Promise<!Array<module:extensions/common/stash.mojom.StashedObject>>} The
+ * serialized stashed objects.
+ */
+ Registration.prototype.serialize = function() {
+ return Promise.resolve(this.callback_()).then($Function.bind(
+ function(stashedObjects) {
+ if (!stashedObjects)
+ return [];
+ return $Array.map(stashedObjects, function(stashed) {
+ var builder = new codec.MessageBuilder(
+ 0, codec.align(this.type_.encodedSize));
+ builder.encodeStruct(this.type_, stashed.serialization);
+ var encoded = builder.finish();
+ return new stashMojom.StashedObject({
+ id: this.id_,
+ data: new Uint8Array(encoded.buffer.arrayBuffer),
+ stashed_handles: encoded.handles,
+ monitor_handles: stashed.monitorHandles,
+ });
+ }, this);
+ }, this)).catch(function(e) { return []; });
+ };
+
+ /**
+ * The registered stash clients.
+ * @type {!Array<!Registration>}
+ */
+ var clients = [];
+
+ /**
+ * Registers a client to provide objects to stash during shut-down.
+ *
+ * @param {string} id The id of the client. This can be passed to retrieve to
+ * retrieve the stashed objects.
+ * @param {!Object} type The type of the objects that callback will return.
+ * @param {module:stash_client.StashCallback} callback The callback that
+ * returns objects to stash.
+ * @alias module:stash_client.registerClient
+ */
+ function registerClient(id, type, callback) {
+ clients.push(new Registration(id, type, callback));
+ }
+
+ var retrievedStash = service.retrieveStash().then(function(result) {
+ if (!result || !result.stash)
+ return {};
+ var stashById = {};
+ $Array.forEach(result.stash, function(stashed) {
+ if (!stashById[stashed.id])
+ stashById[stashed.id] = [];
+ stashById[stashed.id].push(stashed);
+ });
+ return stashById;
+ }, function() {
+ // If the stash is not available, act as if the stash was empty.
+ return {};
+ });
+
+ /**
+ * Retrieves the objects that were stashed with the given |id|, deserializing
+ * them into structs with type |type|.
+ *
+ * @param {string} id The id of the client. This should be unique to this
+ * client and should be passed as the id to registerClient().
+ * @param {!Object} type The mojo struct type that was serialized into the
+ * each stashed object.
+ * @return {!Promise<!Array<!Object>>} The stashed objects. The exact type of
+ * each object is that of the |type| parameter.
+ * @alias module:stash_client.retrieve
+ */
+ function retrieve(id, type) {
+ return retrievedStash.then(function(stash) {
+ var stashedObjects = stash[id];
+ if (!stashedObjects)
+ return Promise.resolve([]);
+
+ return Promise.all($Array.map(stashedObjects, function(stashed) {
+ var encodedData = new ArrayBuffer(stashed.data.length);
+ new Uint8Array(encodedData).set(stashed.data);
+ var reader = new codec.MessageReader(new codec.Message(
+ new bufferModule.Buffer(encodedData), stashed.stashed_handles));
+ var decoded = reader.decodeStruct(type);
+ return decoded;
+ }));
+ });
+ }
+
+ function saveStashForTesting() {
+ Promise.all($Array.map(clients, function(client) {
+ return client.serialize();
+ })).then(function(stashedObjects) {
+ var flattenedObjectsToStash = [];
+ $Array.forEach(stashedObjects, function(stashedObjects) {
+ flattenedObjectsToStash =
+ $Array.concat(flattenedObjectsToStash, stashedObjects);
+ });
+ service.addToStash(flattenedObjectsToStash);
+ });
+ }
+
+ return {
+ registerClient: registerClient,
+ retrieve: retrieve,
+ saveStashForTesting: saveStashForTesting,
+ };
+});
diff --git a/chromium/extensions/renderer/resources/storage_area.js b/chromium/extensions/renderer/resources/storage_area.js
new file mode 100644
index 00000000000..4ff6bbdb6d0
--- /dev/null
+++ b/chromium/extensions/renderer/resources/storage_area.js
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var normalizeArgumentsAndValidate =
+ require('schemaUtils').normalizeArgumentsAndValidate
+var sendRequest = require('sendRequest').sendRequest;
+
+function extendSchema(schema) {
+ var extendedSchema = $Array.slice(schema);
+ $Array.unshift(extendedSchema, {'type': 'string'});
+ return extendedSchema;
+}
+
+function StorageArea(namespace, schema) {
+ // Binds an API function for a namespace to its browser-side call, e.g.
+ // storage.sync.get('foo') -> (binds to) ->
+ // storage.get('sync', 'foo').
+ //
+ // TODO(kalman): Put as a method on CustombindingObject and re-use (or
+ // even generate) for other APIs that need to do this. Same for other
+ // callers of registerCustomType().
+ var self = this;
+ function bindApiFunction(functionName) {
+ self[functionName] = function() {
+ var funSchema = this.functionSchemas[functionName];
+ var args = $Array.slice(arguments);
+ args = normalizeArgumentsAndValidate(args, funSchema);
+ return sendRequest(
+ 'storage.' + functionName,
+ $Array.concat([namespace], args),
+ extendSchema(funSchema.definition.parameters),
+ {preserveNullInObjects: true});
+ };
+ }
+ var apiFunctions = ['get', 'set', 'remove', 'clear', 'getBytesInUse'];
+ $Array.forEach(apiFunctions, bindApiFunction);
+}
+
+exports.$set('StorageArea', StorageArea);
diff --git a/chromium/extensions/renderer/resources/test_custom_bindings.js b/chromium/extensions/renderer/resources/test_custom_bindings.js
new file mode 100644
index 00000000000..6538efac1e3
--- /dev/null
+++ b/chromium/extensions/renderer/resources/test_custom_bindings.js
@@ -0,0 +1,364 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// test_custom_bindings.js
+// mini-framework for ExtensionApiTest browser tests
+
+var binding = require('binding').Binding.create('test');
+
+var environmentSpecificBindings = require('test_environment_specific_bindings');
+var GetExtensionAPIDefinitionsForTest =
+ requireNative('apiDefinitions').GetExtensionAPIDefinitionsForTest;
+var GetAPIFeatures = requireNative('test_features').GetAPIFeatures;
+var natives = requireNative('test_native_handler');
+var uncaughtExceptionHandler = require('uncaught_exception_handler');
+var userGestures = requireNative('user_gestures');
+
+var RunWithNativesEnabled = requireNative('v8_context').RunWithNativesEnabled;
+var GetModuleSystem = requireNative('v8_context').GetModuleSystem;
+
+binding.registerCustomHook(function(api) {
+ var chromeTest = api.compiledApi;
+ var apiFunctions = api.apiFunctions;
+
+ chromeTest.tests = chromeTest.tests || [];
+
+ var currentTest = null;
+ var lastTest = null;
+ var testsFailed = 0;
+ var testCount = 1;
+ var failureException = 'chrome.test.failure';
+
+ // Helper function to get around the fact that function names in javascript
+ // are read-only, and you can't assign one to anonymous functions.
+ function testName(test) {
+ return test ? (test.name || test.generatedName) : "(no test)";
+ }
+
+ function testDone() {
+ environmentSpecificBindings.testDone(chromeTest.runNextTest);
+ }
+
+ function allTestsDone() {
+ if (testsFailed == 0) {
+ chromeTest.notifyPass();
+ } else {
+ chromeTest.notifyFail('Failed ' + testsFailed + ' of ' +
+ testCount + ' tests');
+ }
+ }
+
+ var pendingCallbacks = 0;
+
+ apiFunctions.setHandleRequest('callbackAdded', function() {
+ pendingCallbacks++;
+
+ var called = null;
+ return function() {
+ if (called != null) {
+ var redundantPrefix = 'Error\n';
+ chromeTest.fail(
+ 'Callback has already been run. ' +
+ 'First call:\n' +
+ $String.slice(called, redundantPrefix.length) + '\n' +
+ 'Second call:\n' +
+ $String.slice(new Error().stack, redundantPrefix.length));
+ }
+ called = new Error().stack;
+
+ pendingCallbacks--;
+ if (pendingCallbacks == 0) {
+ chromeTest.succeed();
+ }
+ };
+ });
+
+ apiFunctions.setHandleRequest('runNextTest', function() {
+ // There may have been callbacks which were interrupted by failure
+ // exceptions.
+ pendingCallbacks = 0;
+
+ lastTest = currentTest;
+ currentTest = chromeTest.tests.shift();
+
+ if (!currentTest) {
+ allTestsDone();
+ return;
+ }
+
+ try {
+ chromeTest.log("( RUN ) " + testName(currentTest));
+ uncaughtExceptionHandler.setHandler(function(message, e) {
+ if (e !== failureException)
+ chromeTest.fail('uncaught exception: ' + message);
+ });
+ currentTest.call();
+ } catch (e) {
+ uncaughtExceptionHandler.handle(e.message, e);
+ }
+ });
+
+ apiFunctions.setHandleRequest('fail', function(message) {
+ chromeTest.log("( FAILED ) " + testName(currentTest));
+
+ var stack = {};
+ Error.captureStackTrace(stack, chromeTest.fail);
+
+ if (!message)
+ message = "FAIL (no message)";
+
+ message += "\n" + stack.stack;
+ console.log("[FAIL] " + testName(currentTest) + ": " + message);
+ testsFailed++;
+ testDone();
+
+ // Interrupt the rest of the test.
+ throw failureException;
+ });
+
+ apiFunctions.setHandleRequest('succeed', function() {
+ console.log("[SUCCESS] " + testName(currentTest));
+ chromeTest.log("( SUCCESS )");
+ testDone();
+ });
+
+ apiFunctions.setHandleRequest('runWithNativesEnabled', function(callback) {
+ RunWithNativesEnabled(callback);
+ });
+
+ apiFunctions.setHandleRequest('getModuleSystem', function(context) {
+ return GetModuleSystem(context);
+ });
+
+ apiFunctions.setHandleRequest('assertTrue', function(test, message) {
+ chromeTest.assertBool(test, true, message);
+ });
+
+ apiFunctions.setHandleRequest('assertFalse', function(test, message) {
+ chromeTest.assertBool(test, false, message);
+ });
+
+ apiFunctions.setHandleRequest('assertBool',
+ function(test, expected, message) {
+ if (test !== expected) {
+ if (typeof(test) == "string") {
+ if (message)
+ message = test + "\n" + message;
+ else
+ message = test;
+ }
+ chromeTest.fail(message);
+ }
+ });
+
+ apiFunctions.setHandleRequest('checkDeepEq', function(expected, actual) {
+ if ((expected === null) != (actual === null))
+ return false;
+
+ if (expected === actual)
+ return true;
+
+ if (typeof(expected) !== typeof(actual))
+ return false;
+
+ for (var p in actual) {
+ if ($Object.hasOwnProperty(actual, p) &&
+ !$Object.hasOwnProperty(expected, p)) {
+ return false;
+ }
+ }
+ for (var p in expected) {
+ if ($Object.hasOwnProperty(expected, p) &&
+ !$Object.hasOwnProperty(actual, p)) {
+ return false;
+ }
+ }
+
+ for (var p in expected) {
+ var eq = true;
+ switch (typeof(expected[p])) {
+ case 'object':
+ eq = chromeTest.checkDeepEq(expected[p], actual[p]);
+ break;
+ case 'function':
+ eq = (typeof(actual[p]) != 'undefined' &&
+ expected[p].toString() == actual[p].toString());
+ break;
+ default:
+ eq = (expected[p] == actual[p] &&
+ typeof(expected[p]) == typeof(actual[p]));
+ break;
+ }
+ if (!eq)
+ return false;
+ }
+ return true;
+ });
+
+ apiFunctions.setHandleRequest('assertEq',
+ function(expected, actual, message) {
+ var error_msg = "API Test Error in " + testName(currentTest);
+ if (message)
+ error_msg += ": " + message;
+ if (typeof(expected) == 'object') {
+ if (!chromeTest.checkDeepEq(expected, actual)) {
+ error_msg += "\nActual: " + $JSON.stringify(actual) +
+ "\nExpected: " + $JSON.stringify(expected);
+ chromeTest.fail(error_msg);
+ }
+ return;
+ }
+ if (expected != actual) {
+ chromeTest.fail(error_msg +
+ "\nActual: " + actual + "\nExpected: " + expected);
+ }
+ if (typeof(expected) != typeof(actual)) {
+ chromeTest.fail(error_msg +
+ " (type mismatch)\nActual Type: " + typeof(actual) +
+ "\nExpected Type:" + typeof(expected));
+ }
+ });
+
+ apiFunctions.setHandleRequest('assertNoLastError', function() {
+ if (chrome.runtime.lastError != undefined) {
+ chromeTest.fail("lastError.message == " +
+ chrome.runtime.lastError.message);
+ }
+ });
+
+ apiFunctions.setHandleRequest('assertLastError', function(expectedError) {
+ chromeTest.assertEq(typeof(expectedError), 'string');
+ chromeTest.assertTrue(chrome.runtime.lastError != undefined,
+ "No lastError, but expected " + expectedError);
+ chromeTest.assertEq(expectedError, chrome.runtime.lastError.message);
+ });
+
+ apiFunctions.setHandleRequest('assertThrows',
+ function(fn, self, args, message) {
+ chromeTest.assertTrue(typeof fn == 'function');
+ try {
+ fn.apply(self, args);
+ chromeTest.fail('Did not throw error: ' + fn);
+ } catch (e) {
+ if (e != failureException && message !== undefined) {
+ if (message instanceof RegExp) {
+ chromeTest.assertTrue(message.test(e.message),
+ e.message + ' should match ' + message)
+ } else {
+ chromeTest.assertEq(message, e.message);
+ }
+ }
+ }
+ });
+
+ function safeFunctionApply(func, args) {
+ try {
+ if (func)
+ return $Function.apply(func, undefined, args);
+ } catch (e) {
+ var msg = "uncaught exception " + e;
+ chromeTest.fail(msg);
+ }
+ };
+
+ // Wrapper for generating test functions, that takes care of calling
+ // assertNoLastError() and (optionally) succeed() for you.
+ apiFunctions.setHandleRequest('callback', function(func, expectedError) {
+ if (func) {
+ chromeTest.assertEq(typeof(func), 'function');
+ }
+ var callbackCompleted = chromeTest.callbackAdded();
+
+ return function() {
+ if (expectedError == null) {
+ chromeTest.assertNoLastError();
+ } else {
+ chromeTest.assertLastError(expectedError);
+ }
+
+ var result;
+ if (func) {
+ result = safeFunctionApply(func, arguments);
+ }
+
+ callbackCompleted();
+ return result;
+ };
+ });
+
+ apiFunctions.setHandleRequest('listenOnce', function(event, func) {
+ var callbackCompleted = chromeTest.callbackAdded();
+ var listener = function() {
+ event.removeListener(listener);
+ safeFunctionApply(func, arguments);
+ callbackCompleted();
+ };
+ event.addListener(listener);
+ });
+
+ apiFunctions.setHandleRequest('listenForever', function(event, func) {
+ var callbackCompleted = chromeTest.callbackAdded();
+
+ var listener = function() {
+ safeFunctionApply(func, arguments);
+ };
+
+ var done = function() {
+ event.removeListener(listener);
+ callbackCompleted();
+ };
+
+ event.addListener(listener);
+ return done;
+ });
+
+ apiFunctions.setHandleRequest('callbackPass', function(func) {
+ return chromeTest.callback(func);
+ });
+
+ apiFunctions.setHandleRequest('callbackFail', function(expectedError, func) {
+ return chromeTest.callback(func, expectedError);
+ });
+
+ apiFunctions.setHandleRequest('runTests', function(tests) {
+ chromeTest.tests = tests;
+ testCount = chromeTest.tests.length;
+ chromeTest.runNextTest();
+ });
+
+ apiFunctions.setHandleRequest('getApiDefinitions', function() {
+ return GetExtensionAPIDefinitionsForTest();
+ });
+
+ apiFunctions.setHandleRequest('getApiFeatures', function() {
+ return GetAPIFeatures();
+ });
+
+ apiFunctions.setHandleRequest('isProcessingUserGesture', function() {
+ return userGestures.IsProcessingUserGesture();
+ });
+
+ apiFunctions.setHandleRequest('runWithUserGesture', function(callback) {
+ chromeTest.assertEq(typeof(callback), 'function');
+ return userGestures.RunWithUserGesture(callback);
+ });
+
+ apiFunctions.setHandleRequest('runWithoutUserGesture', function(callback) {
+ chromeTest.assertEq(typeof(callback), 'function');
+ return userGestures.RunWithoutUserGesture(callback);
+ });
+
+ apiFunctions.setHandleRequest('setExceptionHandler', function(callback) {
+ chromeTest.assertEq(typeof(callback), 'function');
+ uncaughtExceptionHandler.setHandler(callback);
+ });
+
+ apiFunctions.setHandleRequest('getWakeEventPage', function() {
+ return natives.GetWakeEventPage();
+ });
+
+ environmentSpecificBindings.registerHooks(api);
+});
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/uncaught_exception_handler.js b/chromium/extensions/renderer/resources/uncaught_exception_handler.js
new file mode 100644
index 00000000000..de4d71e71dc
--- /dev/null
+++ b/chromium/extensions/renderer/resources/uncaught_exception_handler.js
@@ -0,0 +1,110 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Handles uncaught exceptions thrown by extensions. By default this is to
+// log an error message, but tests may override this behaviour.
+var handler = function(message, e) {
+ console.error(message);
+};
+
+/**
+ * Append the error description and stack trace to |message|.
+ *
+ * @param {string} message - The prefix of the error message.
+ * @param {Error|*} e - The thrown error object. This object is potentially
+ * unsafe, because it could be generated by an extension.
+ * @param {string=} priorStackTrace - The stack trace to be appended to the
+ * error message. This stack trace must not include stack frames of |e.stack|,
+ * because both stack traces are concatenated. Overlapping stack traces will
+ * confuse extension developers.
+ * @return {string} The formatted error message.
+ */
+function formatErrorMessage(message, e, priorStackTrace) {
+ if (e)
+ message += ': ' + safeErrorToString(e, false);
+
+ var stack;
+ try {
+ // If the stack was set, use it.
+ // |e.stack| could be void in the following common example:
+ // throw "Error message";
+ stack = $String.self(e && e.stack);
+ } catch (e) {}
+
+ // If a stack is not provided, capture a stack trace.
+ if (!priorStackTrace && !stack)
+ stack = getStackTrace();
+
+ stack = filterExtensionStackTrace(stack);
+ if (stack)
+ message += '\n' + stack;
+
+ // If an asynchronouse stack trace was set, append it.
+ if (priorStackTrace)
+ message += '\n' + priorStackTrace;
+
+ return message;
+}
+
+function filterExtensionStackTrace(stack) {
+ if (!stack)
+ return '';
+ // Remove stack frames in the stack trace that weren't associated with the
+ // extension, to not confuse extension developers with internal details.
+ stack = $String.split(stack, '\n');
+ stack = $Array.filter(stack, function(line) {
+ return $String.indexOf(line, 'chrome-extension://') >= 0;
+ });
+ return $Array.join(stack, '\n');
+}
+
+function getStackTrace() {
+ var e = {};
+ $Error.captureStackTrace(e, getStackTrace);
+ return e.stack;
+}
+
+function getExtensionStackTrace() {
+ return filterExtensionStackTrace(getStackTrace());
+}
+
+/**
+ * Convert an object to a string.
+ *
+ * @param {Error|*} e - A thrown object (possibly user-supplied).
+ * @param {boolean=} omitType - Whether to try to serialize |e.message| instead
+ * of |e.toString()|.
+ * @return {string} The error message.
+ */
+function safeErrorToString(e, omitType) {
+ try {
+ return $String.self(omitType && e.message || e);
+ } catch (e) {
+ // This error is exceptional and could be triggered by
+ // throw {toString: function() { throw 'Haha' } };
+ return '(cannot get error message)';
+ }
+}
+
+/**
+ * Formats the error message and invokes the error handler.
+ *
+ * @param {string} message - Error message prefix.
+ * @param {Error|*} e - Thrown object.
+ * @param {string=} priorStackTrace - Error message suffix.
+ * @see formatErrorMessage
+ */
+exports.$set('handle', function(message, e, priorStackTrace) {
+ message = formatErrorMessage(message, e, priorStackTrace);
+ handler(message, e);
+});
+
+// |newHandler| A function which matches |handler|.
+exports.$set('setHandler', function(newHandler) {
+ handler = newHandler;
+});
+
+exports.$set('getStackTrace', getStackTrace);
+exports.$set('getExtensionStackTrace', getExtensionStackTrace);
+exports.$set('safeErrorToString', safeErrorToString);
diff --git a/chromium/extensions/renderer/resources/utils.js b/chromium/extensions/renderer/resources/utils.js
new file mode 100644
index 00000000000..26aa5e8ed3e
--- /dev/null
+++ b/chromium/extensions/renderer/resources/utils.js
@@ -0,0 +1,240 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var nativeDeepCopy = requireNative('utils').deepCopy;
+var schemaRegistry = requireNative('schema_registry');
+var CHECK = requireNative('logging').CHECK;
+var DCHECK = requireNative('logging').DCHECK;
+var WARNING = requireNative('logging').WARNING;
+
+/**
+ * An object forEach. Calls |f| with each (key, value) pair of |obj|, using
+ * |self| as the target.
+ * @param {Object} obj The object to iterate over.
+ * @param {function} f The function to call in each iteration.
+ * @param {Object} self The object to use as |this| in each function call.
+ */
+function forEach(obj, f, self) {
+ for (var key in obj) {
+ if ($Object.hasOwnProperty(obj, key))
+ $Function.call(f, self, key, obj[key]);
+ }
+}
+
+/**
+ * Assuming |array_of_dictionaries| is structured like this:
+ * [{id: 1, ... }, {id: 2, ...}, ...], you can use
+ * lookup(array_of_dictionaries, 'id', 2) to get the dictionary with id == 2.
+ * @param {Array<Object<?>>} array_of_dictionaries
+ * @param {string} field
+ * @param {?} value
+ */
+function lookup(array_of_dictionaries, field, value) {
+ var filter = function (dict) {return dict[field] == value;};
+ var matches = $Array.filter(array_of_dictionaries, filter);
+ if (matches.length == 0) {
+ return undefined;
+ } else if (matches.length == 1) {
+ return matches[0]
+ } else {
+ throw new Error("Failed lookup of field '" + field + "' with value '" +
+ value + "'");
+ }
+}
+
+function loadTypeSchema(typeName, defaultSchema) {
+ var parts = $String.split(typeName, '.');
+ if (parts.length == 1) {
+ if (defaultSchema == null) {
+ WARNING('Trying to reference "' + typeName + '" ' +
+ 'with neither namespace nor default schema.');
+ return null;
+ }
+ var types = defaultSchema.types;
+ } else {
+ var schemaName = $Array.join($Array.slice(parts, 0, parts.length - 1), '.');
+ var types = schemaRegistry.GetSchema(schemaName).types;
+ }
+ for (var i = 0; i < types.length; ++i) {
+ if (types[i].id == typeName)
+ return types[i];
+ }
+ return null;
+}
+
+/**
+ * Sets a property |value| on |obj| with property name |key|. Like
+ *
+ * obj[key] = value;
+ *
+ * but without triggering setters.
+ */
+function defineProperty(obj, key, value) {
+ $Object.defineProperty(obj, key, {
+ __proto__: null,
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: value,
+ });
+}
+
+/**
+ * Takes a private class implementation |privateClass| and exposes a subset of
+ * its methods |functions| and properties |properties| and |readonly| to a
+ * public wrapper class that should be passed in. Within bindings code, you can
+ * access the implementation from an instance of the wrapper class using
+ * privates(instance).impl, and from the implementation class you can access
+ * the wrapper using this.wrapper (or implInstance.wrapper if you have another
+ * instance of the implementation class).
+ *
+ * |publicClass| should be a constructor that calls constructPrivate() like so:
+ *
+ * privates(publicClass).constructPrivate(this, arguments);
+ *
+ * @param {function} publicClass The publicly exposed wrapper class. This must
+ * be a named function, and the name appears in stack traces.
+ * @param {Object} privateClass The class implementation.
+ * @param {{superclass: ?Function,
+ * functions: ?Array<string>,
+ * properties: ?Array<string>,
+ * readonly: ?Array<string>}} exposed The names of properties on the
+ * implementation class to be exposed. |superclass| represents the
+ * constructor of the class to be used as the superclass of the exposed
+ * class; |functions| represents the names of functions which should be
+ * delegated to the implementation; |properties| are gettable/settable
+ * properties and |readonly| are read-only properties.
+ */
+function expose(publicClass, privateClass, exposed) {
+ DCHECK(!(privateClass.prototype instanceof $Object.self));
+
+ $Object.setPrototypeOf(exposed, null);
+
+ // This should be called by publicClass.
+ privates(publicClass).constructPrivate = function(self, args) {
+ if (!(self instanceof publicClass)) {
+ throw new Error('Please use "new ' + publicClass.name + '"');
+ }
+ // The "instanceof publicClass" check can easily be spoofed, so we check
+ // whether the private impl is already set before continuing.
+ var privateSelf = privates(self);
+ if ('impl' in privateSelf) {
+ throw new Error('Object ' + publicClass.name + ' is already constructed');
+ }
+ var privateObj = $Object.create(privateClass.prototype);
+ $Function.apply(privateClass, privateObj, args);
+ privateObj.wrapper = self;
+ privateSelf.impl = privateObj;
+ };
+
+ function getPrivateImpl(self) {
+ var impl = privates(self).impl;
+ if (!(impl instanceof privateClass)) {
+ // Either the object is not constructed, or the property descriptor is
+ // used on a target that is not an instance of publicClass.
+ throw new Error('impl is not an instance of ' + privateClass.name);
+ }
+ return impl;
+ }
+
+ var publicClassPrototype = {
+ // The final prototype will be assigned at the end of this method.
+ __proto__: null,
+ constructor: publicClass,
+ };
+
+ if ('functions' in exposed) {
+ $Array.forEach(exposed.functions, function(func) {
+ publicClassPrototype[func] = function() {
+ var impl = getPrivateImpl(this);
+ return $Function.apply(impl[func], impl, arguments);
+ };
+ });
+ }
+
+ if ('properties' in exposed) {
+ $Array.forEach(exposed.properties, function(prop) {
+ $Object.defineProperty(publicClassPrototype, prop, {
+ __proto__: null,
+ enumerable: true,
+ get: function() {
+ return getPrivateImpl(this)[prop];
+ },
+ set: function(value) {
+ var impl = getPrivateImpl(this);
+ delete impl[prop];
+ impl[prop] = value;
+ }
+ });
+ });
+ }
+
+ if ('readonly' in exposed) {
+ $Array.forEach(exposed.readonly, function(readonly) {
+ $Object.defineProperty(publicClassPrototype, readonly, {
+ __proto__: null,
+ enumerable: true,
+ get: function() {
+ return getPrivateImpl(this)[readonly];
+ },
+ });
+ });
+ }
+
+ // The prototype properties have been installed. Now we can safely assign an
+ // unsafe prototype and export the class to the public.
+ var superclass = exposed.superclass || $Object.self;
+ $Object.setPrototypeOf(publicClassPrototype, superclass.prototype);
+ publicClass.prototype = publicClassPrototype;
+
+ return publicClass;
+}
+
+/**
+ * Returns a deep copy of |value|. The copy will have no references to nested
+ * values of |value|.
+ */
+function deepCopy(value) {
+ return nativeDeepCopy(value);
+}
+
+/**
+ * Wrap an asynchronous API call to a function |func| in a promise. The
+ * remaining arguments will be passed to |func|. Returns a promise that will be
+ * resolved to the result passed to the callback or rejected if an error occurs
+ * (if chrome.runtime.lastError is set). If there are multiple results, the
+ * promise will be resolved with an array containing those results.
+ *
+ * For example,
+ * promise(chrome.storage.get, 'a').then(function(result) {
+ * // Use result.
+ * }).catch(function(error) {
+ * // Report error.message.
+ * });
+ */
+function promise(func) {
+ var args = $Array.slice(arguments, 1);
+ DCHECK(typeof func == 'function');
+ return new Promise(function(resolve, reject) {
+ args.push(function() {
+ if (chrome.runtime.lastError) {
+ reject(new Error(chrome.runtime.lastError));
+ return;
+ }
+ if (arguments.length <= 1)
+ resolve(arguments[0]);
+ else
+ resolve($Array.slice(arguments));
+ });
+ $Function.apply(func, null, args);
+ });
+}
+
+exports.$set('forEach', forEach);
+exports.$set('loadTypeSchema', loadTypeSchema);
+exports.$set('lookup', lookup);
+exports.$set('defineProperty', defineProperty);
+exports.$set('expose', expose);
+exports.$set('deepCopy', deepCopy);
+exports.$set('promise', promise);
diff --git a/chromium/extensions/renderer/resources/web_request_custom_bindings.js b/chromium/extensions/renderer/resources/web_request_custom_bindings.js
new file mode 100644
index 00000000000..2dd7ce4f048
--- /dev/null
+++ b/chromium/extensions/renderer/resources/web_request_custom_bindings.js
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Custom binding for the webRequest API.
+
+var binding = require('binding').Binding.create('webRequest');
+var sendRequest = require('sendRequest').sendRequest;
+var WebRequestEvent = require('webRequestInternal').WebRequestEvent;
+
+binding.registerCustomHook(function(api) {
+ var apiFunctions = api.apiFunctions;
+
+ apiFunctions.setHandleRequest('handlerBehaviorChanged', function() {
+ var args = $Array.slice(arguments);
+ sendRequest(this.name, args, this.definition.parameters,
+ {forIOThread: true});
+ });
+});
+
+binding.registerCustomEvent(WebRequestEvent);
+
+exports.$set('binding', binding.generate());
diff --git a/chromium/extensions/renderer/resources/web_request_internal_custom_bindings.js b/chromium/extensions/renderer/resources/web_request_internal_custom_bindings.js
new file mode 100644
index 00000000000..32ad33b9b47
--- /dev/null
+++ b/chromium/extensions/renderer/resources/web_request_internal_custom_bindings.js
@@ -0,0 +1,196 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Custom binding for the webRequestInternal API.
+
+var binding = require('binding').Binding.create('webRequestInternal');
+var eventBindings = require('event_bindings');
+var sendRequest = require('sendRequest').sendRequest;
+var validate = require('schemaUtils').validate;
+var utils = require('utils');
+var idGeneratorNatives = requireNative('id_generator');
+
+var webRequestInternal;
+
+function GetUniqueSubEventName(eventName) {
+ return eventName + '/' + idGeneratorNatives.GetNextId();
+}
+
+// WebRequestEventImpl object. This is used for special webRequest events
+// with extra parameters. Each invocation of addListener creates a new named
+// sub-event. That sub-event is associated with the extra parameters in the
+// browser process, so that only it is dispatched when the main event occurs
+// matching the extra parameters.
+//
+// Example:
+// chrome.webRequest.onBeforeRequest.addListener(
+// callback, {urls: 'http://*.google.com/*'});
+// ^ callback will only be called for onBeforeRequests matching the filter.
+function WebRequestEventImpl(eventName, opt_argSchemas, opt_extraArgSchemas,
+ opt_eventOptions, opt_webViewInstanceId) {
+ if (typeof eventName != 'string')
+ throw new Error('chrome.WebRequestEvent requires an event name.');
+
+ this.eventName = eventName;
+ this.argSchemas = opt_argSchemas;
+ this.extraArgSchemas = opt_extraArgSchemas;
+ this.webViewInstanceId = opt_webViewInstanceId || 0;
+ this.subEvents = [];
+ this.eventOptions = eventBindings.parseEventOptions(opt_eventOptions);
+ if (this.eventOptions.supportsRules) {
+ this.eventForRules =
+ new eventBindings.Event(eventName, opt_argSchemas, opt_eventOptions,
+ opt_webViewInstanceId);
+ }
+}
+$Object.setPrototypeOf(WebRequestEventImpl.prototype, null);
+
+// Test if the given callback is registered for this event.
+WebRequestEventImpl.prototype.hasListener = function(cb) {
+ if (!this.eventOptions.supportsListeners)
+ throw new Error('This event does not support listeners.');
+ return this.findListener_(cb) > -1;
+};
+
+// Test if any callbacks are registered fur thus event.
+WebRequestEventImpl.prototype.hasListeners = function() {
+ if (!this.eventOptions.supportsListeners)
+ throw new Error('This event does not support listeners.');
+ return this.subEvents.length > 0;
+};
+
+// Registers a callback to be called when this event is dispatched. If
+// opt_filter is specified, then the callback is only called for events that
+// match the given filters. If opt_extraInfo is specified, the given optional
+// info is sent to the callback.
+WebRequestEventImpl.prototype.addListener =
+ function(cb, opt_filter, opt_extraInfo) {
+ if (!this.eventOptions.supportsListeners)
+ throw new Error('This event does not support listeners.');
+ // NOTE(benjhayden) New APIs should not use this subEventName trick! It does
+ // not play well with event pages. See downloads.onDeterminingFilename and
+ // ExtensionDownloadsEventRouter for an alternative approach.
+ var subEventName = GetUniqueSubEventName(this.eventName);
+ // Note: this could fail to validate, in which case we would not add the
+ // subEvent listener.
+ validate($Array.slice(arguments, 1), this.extraArgSchemas);
+ webRequestInternal.addEventListener(
+ cb, opt_filter, opt_extraInfo, this.eventName, subEventName,
+ this.webViewInstanceId);
+
+ var subEvent = new eventBindings.Event(subEventName, this.argSchemas);
+ var subEventCallback = cb;
+ if (opt_extraInfo && opt_extraInfo.indexOf('blocking') >= 0) {
+ var eventName = this.eventName;
+ subEventCallback = function() {
+ var requestId = arguments[0].requestId;
+ try {
+ var result = $Function.apply(cb, null, arguments);
+ webRequestInternal.eventHandled(
+ eventName, subEventName, requestId, result);
+ } catch (e) {
+ webRequestInternal.eventHandled(
+ eventName, subEventName, requestId);
+ throw e;
+ }
+ };
+ } else if (opt_extraInfo && opt_extraInfo.indexOf('asyncBlocking') >= 0) {
+ var eventName = this.eventName;
+ subEventCallback = function() {
+ var details = arguments[0];
+ var requestId = details.requestId;
+ var handledCallback = function(response) {
+ webRequestInternal.eventHandled(
+ eventName, subEventName, requestId, response);
+ };
+ $Function.apply(cb, null, [details, handledCallback]);
+ };
+ }
+ $Array.push(this.subEvents,
+ {subEvent: subEvent, callback: cb, subEventCallback: subEventCallback});
+ subEvent.addListener(subEventCallback);
+};
+
+// Unregisters a callback.
+WebRequestEventImpl.prototype.removeListener = function(cb) {
+ if (!this.eventOptions.supportsListeners)
+ throw new Error('This event does not support listeners.');
+ var idx;
+ while ((idx = this.findListener_(cb)) >= 0) {
+ var e = this.subEvents[idx];
+ e.subEvent.removeListener(e.subEventCallback);
+ if (e.subEvent.hasListeners()) {
+ console.error(
+ 'Internal error: webRequest subEvent has orphaned listeners.');
+ }
+ $Array.splice(this.subEvents, idx, 1);
+ }
+};
+
+WebRequestEventImpl.prototype.findListener_ = function(cb) {
+ for (var i in this.subEvents) {
+ var e = this.subEvents[i];
+ if (e.callback === cb) {
+ if (e.subEvent.hasListener(e.subEventCallback))
+ return i;
+ console.error('Internal error: webRequest subEvent has no callback.');
+ }
+ }
+
+ return -1;
+};
+
+WebRequestEventImpl.prototype.addRules = function(rules, opt_cb) {
+ if (!this.eventOptions.supportsRules)
+ throw new Error('This event does not support rules.');
+ this.eventForRules.addRules(rules, opt_cb);
+};
+
+WebRequestEventImpl.prototype.removeRules =
+ function(ruleIdentifiers, opt_cb) {
+ if (!this.eventOptions.supportsRules)
+ throw new Error('This event does not support rules.');
+ this.eventForRules.removeRules(ruleIdentifiers, opt_cb);
+};
+
+WebRequestEventImpl.prototype.getRules = function(ruleIdentifiers, cb) {
+ if (!this.eventOptions.supportsRules)
+ throw new Error('This event does not support rules.');
+ this.eventForRules.getRules(ruleIdentifiers, cb);
+};
+
+binding.registerCustomHook(function(api) {
+ var apiFunctions = api.apiFunctions;
+
+ apiFunctions.setHandleRequest('addEventListener', function() {
+ var args = $Array.slice(arguments);
+ sendRequest(this.name, args, this.definition.parameters,
+ {forIOThread: true});
+ });
+
+ apiFunctions.setHandleRequest('eventHandled', function() {
+ var args = $Array.slice(arguments);
+ sendRequest(this.name, args, this.definition.parameters,
+ {forIOThread: true});
+ });
+});
+
+function WebRequestEvent() {
+ privates(WebRequestEvent).constructPrivate(this, arguments);
+}
+utils.expose(WebRequestEvent, WebRequestEventImpl, {
+ functions: [
+ 'hasListener',
+ 'hasListeners',
+ 'addListener',
+ 'removeListener',
+ 'addRules',
+ 'removeRules',
+ 'getRules',
+ ],
+});
+
+webRequestInternal = binding.generate();
+exports.$set('binding', webRequestInternal);
+exports.$set('WebRequestEvent', WebRequestEvent);
diff --git a/chromium/extensions/renderer/resources/window_controls.js b/chromium/extensions/renderer/resources/window_controls.js
new file mode 100644
index 00000000000..75c88e63927
--- /dev/null
+++ b/chromium/extensions/renderer/resources/window_controls.js
@@ -0,0 +1,78 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//
+// <window-controls> shadow element implementation.
+//
+
+var chrome = requireNative('chrome').GetChrome();
+var forEach = require('utils').forEach;
+var addTagWatcher = require('tagWatcher').addTagWatcher;
+var appWindow = require('app.window');
+var getHtmlTemplate =
+ requireNative('app_window_natives').GetWindowControlsHtmlTemplate;
+
+/**
+ * @constructor
+ */
+function WindowControls(node) {
+ this.node_ = node;
+ this.shadowRoot_ = this.createShadowRoot_(node);
+ this.setupWindowControls_();
+}
+
+/**
+ * @private
+ */
+WindowControls.prototype.template_element = null;
+
+/**
+ * @private
+ */
+WindowControls.prototype.createShadowRoot_ = function(node) {
+ // Initialize |template| from HTML template resource and cache result.
+ var template = WindowControls.prototype.template_element;
+ if (!template) {
+ var element = document.createElement('div');
+ element.innerHTML = getHtmlTemplate();
+ WindowControls.prototype.template_element = element.firstChild;
+ template = WindowControls.prototype.template_element;
+ }
+ // Create shadow root element with template clone as first child.
+ var shadowRoot = node.createShadowRoot();
+ shadowRoot.appendChild(template.content.cloneNode(true));
+ return shadowRoot;
+}
+
+/**
+ * @private
+ */
+WindowControls.prototype.setupWindowControls_ = function() {
+ var self = this;
+ this.shadowRoot_.querySelector("#close-control").addEventListener('click',
+ function(e) {
+ chrome.app.window.current().close();
+ });
+
+ this.shadowRoot_.querySelector("#maximize-control").addEventListener('click',
+ function(e) {
+ self.maxRestore_();
+ });
+}
+
+/**
+ * @private
+ * Restore or maximize depending on current state
+ */
+WindowControls.prototype.maxRestore_ = function() {
+ if (chrome.app.window.current().isMaximized()) {
+ chrome.app.window.current().restore();
+ } else {
+ chrome.app.window.current().maximize();
+ }
+}
+
+addTagWatcher('WINDOW-CONTROLS', function(addedNode) {
+ new WindowControls(addedNode);
+});
diff --git a/chromium/extensions/renderer/resources/window_controls_template.html b/chromium/extensions/renderer/resources/window_controls_template.html
new file mode 100644
index 00000000000..6f468bc4b3a
--- /dev/null
+++ b/chromium/extensions/renderer/resources/window_controls_template.html
@@ -0,0 +1,52 @@
+<template id="window-controls-template">
+ <style>
+ .controls {
+ width:32px;
+ height:32px;
+ position:absolute;
+ z-index:200;
+ }
+ #close-control {
+ top:8px;
+ right:10px;
+ }
+ #maximize-control {
+ top:8px;
+ right:52px;
+ }
+ #close {
+ top:0;
+ right:0;
+ -webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAALNJREFUeNrsllEOwyAIhmVXwIu487dXKEdy2PBgWuxwm/VhkPwvSuALAhFyzmGmPcJkcwAHcAAHMAMAQGItLGSFhlB8kpmgrGKL2NbiziIWKqHK2SY+qzluBwBKcg2iTr7fjQBoQZySd1W2E0CD2LSqjAQ4Qqh9YY37zRj+5iPx4RPUZac7n6DVhHRHE74bQxo9hvUiio1FRCMXURKIeNFSKD5Pa1zwX7EDOIAD/D3AS4ABAKWdkCCeGGsrAAAAAElFTkSuQmCC');
+ }
+ .windowbutton {
+ width:32px;
+ height:32px;
+ position:absolute;
+ background-color:rgba(0, 0, 0, 0.49);
+ }
+ .windowbuttonbackground {
+ width:32px;
+ height:32px;
+ position:absolute;
+ }
+ .windowbuttonbackground:hover {
+ background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEBJREFUeNrszrENAEAIw8A8EvU32X9RGsQUaewFfM/2l9TKNBWcX10KBwAAAAAAAAAAAAAAAAAAABxggv9ZAQYAhakDi3I15kgAAAAASUVORK5CYII=');
+
+ }
+ .windowbuttonbackground:active {
+ background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAFtJREFUeNrs1zESgCAMRNEAkSZD5VW8/1k8hFaCCqfY5u9M6v/apIh2mH1hkqXba92uUvxU5Mfoe57xx0Rb7WziAQAAAAAAAAAAAAAAAAAAOcDXkzqvifrvL8AAWBcLpapo5CcAAAAASUVORK5CYII=');
+ }
+ #maximize {
+ -webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAFFJREFUeNrs1jsOACAIREHWeP8royWdnwaDj9pi2OhGubtlTrPkAQAAAIC+e1DSUWXOhlWtBGIYq+W5hAAA1GzC23f+fALiVwwAAIDvAUOAAQAv/Aw+jTHzugAAAABJRU5ErkJggg==');
+ }
+ </style>
+ <div id="close-control" class="controls">
+ <div id="close" class="windowbutton"> </div>
+ <div id="close-background" class="windowbuttonbackground"> </div>
+ </div>
+ <div id="maximize-control" class="controls">
+ <div id="maximize" class="windowbutton"></div>
+ <div id="maximize-background" class="windowbuttonbackground"></div>
+ </div>
+</template>
diff --git a/chromium/extensions/renderer/runtime_custom_bindings.cc b/chromium/extensions/renderer/runtime_custom_bindings.cc
new file mode 100644
index 00000000000..8c6f2705e7e
--- /dev/null
+++ b/chromium/extensions/renderer/runtime_custom_bindings.cc
@@ -0,0 +1,184 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/runtime_custom_bindings.h"
+
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/features/feature_provider.h"
+#include "extensions/common/manifest.h"
+#include "extensions/renderer/api_activity_logger.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebView.h"
+
+using content::V8ValueConverter;
+
+namespace extensions {
+
+RuntimeCustomBindings::RuntimeCustomBindings(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction(
+ "GetManifest",
+ base::Bind(&RuntimeCustomBindings::GetManifest, base::Unretained(this)));
+ RouteFunction("OpenChannelToExtension",
+ base::Bind(&RuntimeCustomBindings::OpenChannelToExtension,
+ base::Unretained(this)));
+ RouteFunction("OpenChannelToNativeApp",
+ base::Bind(&RuntimeCustomBindings::OpenChannelToNativeApp,
+ base::Unretained(this)));
+ RouteFunction("GetExtensionViews",
+ base::Bind(&RuntimeCustomBindings::GetExtensionViews,
+ base::Unretained(this)));
+}
+
+RuntimeCustomBindings::~RuntimeCustomBindings() {
+}
+
+void RuntimeCustomBindings::OpenChannelToExtension(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Get the current RenderFrame so that we can send a routed IPC message from
+ // the correct source.
+ content::RenderFrame* renderframe = context()->GetRenderFrame();
+ if (!renderframe)
+ return;
+
+ // The Javascript code should validate/fill the arguments.
+ CHECK_EQ(args.Length(), 3);
+ CHECK(args[0]->IsString() && args[1]->IsString() && args[2]->IsBoolean());
+
+ ExtensionMsg_ExternalConnectionInfo info;
+
+ // For messaging APIs, hosted apps should be considered a web page so hide
+ // its extension ID.
+ const Extension* extension = context()->extension();
+ if (extension && !extension->is_hosted_app())
+ info.source_id = extension->id();
+
+ info.target_id = *v8::String::Utf8Value(args[0]);
+ info.source_url = context()->url();
+ std::string channel_name = *v8::String::Utf8Value(args[1]);
+ bool include_tls_channel_id =
+ args.Length() > 2 ? args[2]->BooleanValue() : false;
+ int port_id = -1;
+ renderframe->Send(new ExtensionHostMsg_OpenChannelToExtension(
+ renderframe->GetRoutingID(), info, channel_name, include_tls_channel_id,
+ &port_id));
+ args.GetReturnValue().Set(static_cast<int32_t>(port_id));
+}
+
+void RuntimeCustomBindings::OpenChannelToNativeApp(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Verify that the extension has permission to use native messaging.
+ Feature::Availability availability =
+ FeatureProvider::GetPermissionFeatures()
+ ->GetFeature("nativeMessaging")
+ ->IsAvailableToContext(context()->extension(),
+ context()->context_type(), context()->url());
+ if (!availability.is_available())
+ return;
+
+ content::RenderFrame* render_frame = context()->GetRenderFrame();
+ if (!render_frame)
+ return;
+
+ // The Javascript code should validate/fill the arguments.
+ CHECK(args.Length() >= 2 && args[0]->IsString() && args[1]->IsString());
+
+ std::string extension_id = *v8::String::Utf8Value(args[0]);
+ std::string native_app_name = *v8::String::Utf8Value(args[1]);
+
+ int port_id = -1;
+ render_frame->Send(new ExtensionHostMsg_OpenChannelToNativeApp(
+ render_frame->GetRoutingID(), extension_id, native_app_name, &port_id));
+ args.GetReturnValue().Set(static_cast<int32_t>(port_id));
+}
+
+void RuntimeCustomBindings::GetManifest(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK(context()->extension());
+
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+ args.GetReturnValue().Set(converter->ToV8Value(
+ context()->extension()->manifest()->value(), context()->v8_context()));
+}
+
+void RuntimeCustomBindings::GetExtensionViews(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() != 2)
+ return;
+
+ if (!args[0]->IsInt32() || !args[1]->IsString())
+ return;
+
+ // |browser_window_id| == extension_misc::kUnknownWindowId means getting
+ // all views for the current extension.
+ int browser_window_id = args[0]->Int32Value();
+
+ std::string view_type_string =
+ base::ToUpperASCII(*v8::String::Utf8Value(args[1]));
+ // |view_type| == VIEW_TYPE_INVALID means getting any type of
+ // views.
+ ViewType view_type = VIEW_TYPE_INVALID;
+ if (view_type_string == kViewTypeBackgroundPage) {
+ view_type = VIEW_TYPE_EXTENSION_BACKGROUND_PAGE;
+ } else if (view_type_string == kViewTypeTabContents) {
+ view_type = VIEW_TYPE_TAB_CONTENTS;
+ } else if (view_type_string == kViewTypePopup) {
+ view_type = VIEW_TYPE_EXTENSION_POPUP;
+ } else if (view_type_string == kViewTypeExtensionDialog) {
+ view_type = VIEW_TYPE_EXTENSION_DIALOG;
+ } else if (view_type_string == kViewTypeAppWindow) {
+ view_type = VIEW_TYPE_APP_WINDOW;
+ } else if (view_type_string == kViewTypeLauncherPage) {
+ view_type = VIEW_TYPE_LAUNCHER_PAGE;
+ } else if (view_type_string == kViewTypePanel) {
+ view_type = VIEW_TYPE_PANEL;
+ } else if (view_type_string != kViewTypeAll) {
+ return;
+ }
+
+ std::string extension_id = context()->GetExtensionID();
+ if (extension_id.empty())
+ return;
+
+ std::vector<content::RenderFrame*> frames =
+ ExtensionFrameHelper::GetExtensionFrames(extension_id, browser_window_id,
+ view_type);
+ v8::Local<v8::Context> v8_context = args.GetIsolate()->GetCurrentContext();
+ v8::Local<v8::Array> v8_views = v8::Array::New(args.GetIsolate());
+ int v8_index = 0;
+ for (content::RenderFrame* frame : frames) {
+ // We filter out iframes here. GetExtensionViews should only return the
+ // main views, not any subframes. (Returning subframes can cause broken
+ // behavior by treating an app window's iframe as its main frame, and maybe
+ // other nastiness).
+ if (frame->GetWebFrame()->top() != frame->GetWebFrame())
+ continue;
+
+ v8::Local<v8::Context> context =
+ frame->GetWebFrame()->mainWorldScriptContext();
+ if (!context.IsEmpty()) {
+ v8::Local<v8::Value> window = context->Global();
+ DCHECK(!window.IsEmpty());
+ v8::Maybe<bool> maybe =
+ v8_views->CreateDataProperty(v8_context, v8_index++, window);
+ DCHECK(maybe.IsJust() && maybe.FromJust());
+ }
+ }
+
+ args.GetReturnValue().Set(v8_views);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/runtime_custom_bindings.h b/chromium/extensions/renderer/runtime_custom_bindings.h
new file mode 100644
index 00000000000..17e96bbaf79
--- /dev/null
+++ b/chromium/extensions/renderer/runtime_custom_bindings.h
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_RUNTIME_CUSTOM_BINDINGS_H_
+#define EXTENSIONS_RENDERER_RUNTIME_CUSTOM_BINDINGS_H_
+
+#include "base/compiler_specific.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+// The native component of custom bindings for the chrome.runtime API.
+class RuntimeCustomBindings : public ObjectBackedNativeHandler {
+ public:
+ explicit RuntimeCustomBindings(ScriptContext* context);
+
+ ~RuntimeCustomBindings() override;
+
+ // Creates a new messaging channel to the given extension.
+ void OpenChannelToExtension(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Creates a new messaging channels for the specified native application.
+ void OpenChannelToNativeApp(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ private:
+ void GetManifest(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void GetExtensionViews(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_RUNTIME_CUSTOM_BINDINGS_H_
diff --git a/chromium/extensions/renderer/safe_builtins.cc b/chromium/extensions/renderer/safe_builtins.cc
new file mode 100644
index 00000000000..914f4eaf940
--- /dev/null
+++ b/chromium/extensions/renderer/safe_builtins.cc
@@ -0,0 +1,259 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/safe_builtins.h"
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/v8_helpers.h"
+
+namespace extensions {
+
+using namespace v8_helpers;
+
+namespace {
+
+const char kClassName[] = "extensions::SafeBuiltins";
+
+// Documentation for makeCallback in the JavaScript, out here to reduce the
+// (very small) amount of effort that the v8 parser needs to do:
+//
+// Returns a new object with every function on |obj| configured to call()\n"
+// itself with the given arguments.\n"
+// E.g. given\n"
+// var result = makeCallable(Function.prototype)\n"
+// |result| will be a object including 'bind' such that\n"
+// result.bind(foo, 1, 2, 3);\n"
+// is equivalent to Function.prototype.bind.call(foo, 1, 2, 3), and so on.\n"
+// This is a convenient way to save functions that user scripts may clobber.\n"
+const char kScript[] =
+ "(function() {\n"
+ "'use strict';\n"
+ "native function Apply();\n"
+ "native function Save();\n"
+ "\n"
+ "// Used in the callback implementation, could potentially be clobbered.\n"
+ "function makeCallable(obj, target, isStatic, propertyNames) {\n"
+ " propertyNames.forEach(function(propertyName) {\n"
+ " var property = obj[propertyName];\n"
+ " target[propertyName] = function() {\n"
+ " var recv = obj;\n"
+ " var firstArgIndex = 0;\n"
+ " if (!isStatic) {\n"
+ " if (arguments.length == 0)\n"
+ " throw 'There must be at least one argument, the receiver';\n"
+ " recv = arguments[0];\n"
+ " firstArgIndex = 1;\n"
+ " }\n"
+ " return Apply(\n"
+ " property, recv, arguments, firstArgIndex, arguments.length);\n"
+ " };\n"
+ " });\n"
+ "}\n"
+ "\n"
+ "function saveBuiltin(builtin, protoPropertyNames, staticPropertyNames) {\n"
+ " var safe = function() {\n"
+ " throw 'Safe objects cannot be called nor constructed. ' +\n"
+ " 'Use $Foo.self() or new $Foo.self() instead.';\n"
+ " };\n"
+ " safe.self = builtin;\n"
+ " makeCallable(builtin.prototype, safe, false, protoPropertyNames);\n"
+ " if (staticPropertyNames)\n"
+ " makeCallable(builtin, safe, true, staticPropertyNames);\n"
+ " Save(builtin.name, safe);\n"
+ "}\n"
+ "\n"
+ "// Save only what is needed by the extension modules.\n"
+ "saveBuiltin(Object,\n"
+ " ['hasOwnProperty'],\n"
+ " ['create', 'defineProperty', 'freeze',\n"
+ " 'getOwnPropertyDescriptor', 'getPrototypeOf', 'keys',\n"
+ " 'assign', 'setPrototypeOf']);\n"
+ "saveBuiltin(Function,\n"
+ " ['apply', 'bind', 'call']);\n"
+ "saveBuiltin(Array,\n"
+ " ['concat', 'forEach', 'indexOf', 'join', 'push', 'slice',\n"
+ " 'splice', 'map', 'filter', 'unshift', 'pop', 'reverse'],\n"
+ " ['isArray']);\n"
+ "saveBuiltin(String,\n"
+ " ['indexOf', 'slice', 'split', 'substr', 'toUpperCase',\n"
+ " 'replace']);\n"
+ "// Use exec rather than test to defend against clobbering in the\n"
+ "// presence of ES2015 semantics, which read RegExp.prototype.exec.\n"
+ "saveBuiltin(RegExp,\n"
+ " ['exec']);\n"
+ "saveBuiltin(Error,\n"
+ " [],\n"
+ " ['captureStackTrace']);\n"
+ "\n"
+ "// JSON is trickier because extensions can override toJSON in\n"
+ "// incompatible ways, and we need to prevent that.\n"
+ "var builtinTypes = [\n"
+ " Object, Function, Array, String, Boolean, Number, Date, RegExp\n"
+ "];\n"
+ "var builtinToJSONs = builtinTypes.map(function(t) {\n"
+ " return t.toJSON;\n"
+ "});\n"
+ "var builtinArray = Array;\n"
+ "var builtinJSONStringify = JSON.stringify;\n"
+ "Save('JSON', {\n"
+ " parse: JSON.parse,\n"
+ " stringify: function(obj) {\n"
+ " var savedToJSONs = new builtinArray(builtinTypes.length);\n"
+ " try {\n"
+ " for (var i = 0; i < builtinTypes.length; ++i) {\n"
+ " try {\n"
+ " if (builtinTypes[i].prototype.toJSON !==\n"
+ " builtinToJSONs[i]) {\n"
+ " savedToJSONs[i] = builtinTypes[i].prototype.toJSON;\n"
+ " builtinTypes[i].prototype.toJSON = builtinToJSONs[i];\n"
+ " }\n"
+ " } catch (e) {}\n"
+ " }\n"
+ " } catch (e) {}\n"
+ " try {\n"
+ " return builtinJSONStringify(obj);\n"
+ " } finally {\n"
+ " for (var i = 0; i < builtinTypes.length; ++i) {\n"
+ " try {\n"
+ " if (i in savedToJSONs)\n"
+ " builtinTypes[i].prototype.toJSON = savedToJSONs[i];\n"
+ " } catch (e) {}\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "});\n"
+ "\n"
+ "}());\n";
+
+v8::Local<v8::Private> MakeKey(const char* name, v8::Isolate* isolate) {
+ return v8::Private::ForApi(
+ isolate, ToV8StringUnsafe(
+ isolate, base::StringPrintf("%s::%s", kClassName, name)));
+}
+
+void SaveImpl(const char* name,
+ v8::Local<v8::Value> value,
+ v8::Local<v8::Context> context) {
+ CHECK(!value.IsEmpty() && value->IsObject()) << name;
+ context->Global()
+ ->SetPrivate(context, MakeKey(name, context->GetIsolate()), value)
+ .FromJust();
+}
+
+v8::Local<v8::Object> Load(const char* name, v8::Local<v8::Context> context) {
+ v8::Local<v8::Value> value =
+ context->Global()
+ ->GetPrivate(context, MakeKey(name, context->GetIsolate()))
+ .ToLocalChecked();
+ CHECK(value->IsObject()) << name;
+ return v8::Local<v8::Object>::Cast(value);
+}
+
+class ExtensionImpl : public v8::Extension {
+ public:
+ ExtensionImpl() : v8::Extension(kClassName, kScript) {}
+
+ private:
+ v8::Local<v8::FunctionTemplate> GetNativeFunctionTemplate(
+ v8::Isolate* isolate,
+ v8::Local<v8::String> name) override {
+ v8::Local<v8::Context> context = isolate->GetCurrentContext();
+ if (IsTrue(name->Equals(context, ToV8StringUnsafe(isolate, "Apply"))))
+ return v8::FunctionTemplate::New(isolate, Apply);
+ if (IsTrue(name->Equals(context, ToV8StringUnsafe(isolate, "Save"))))
+ return v8::FunctionTemplate::New(isolate, Save);
+ NOTREACHED() << *v8::String::Utf8Value(name);
+ return v8::Local<v8::FunctionTemplate>();
+ }
+
+ static void Apply(const v8::FunctionCallbackInfo<v8::Value>& info) {
+ CHECK(info.Length() == 5 && info[0]->IsFunction() && // function
+ // info[1] could be an object or a string
+ info[2]->IsObject() && // args
+ info[3]->IsInt32() && // first_arg_index
+ info[4]->IsInt32()); // args_length
+ v8::Local<v8::Function> function = info[0].As<v8::Function>();
+ v8::Local<v8::Object> recv;
+ if (info[1]->IsObject()) {
+ recv = v8::Local<v8::Object>::Cast(info[1]);
+ } else if (info[1]->IsString()) {
+ recv = v8::StringObject::New(v8::Local<v8::String>::Cast(info[1]))
+ .As<v8::Object>();
+ } else {
+ info.GetIsolate()->ThrowException(
+ v8::Exception::TypeError(ToV8StringUnsafe(
+ info.GetIsolate(),
+ "The first argument is the receiver and must be an object")));
+ return;
+ }
+ v8::Local<v8::Object> args = v8::Local<v8::Object>::Cast(info[2]);
+ int first_arg_index = info[3].As<v8::Int32>()->Value();
+ int args_length = info[4].As<v8::Int32>()->Value();
+
+ v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
+ int argc = args_length - first_arg_index;
+ scoped_ptr<v8::Local<v8::Value> []> argv(new v8::Local<v8::Value>[argc]);
+ for (int i = 0; i < argc; ++i) {
+ CHECK(IsTrue(args->Has(context, i + first_arg_index)));
+ // Getting a property value could throw an exception.
+ if (!GetProperty(context, args, i + first_arg_index, &argv[i]))
+ return;
+ }
+
+ v8::MicrotasksScope microtasks(
+ info.GetIsolate(), v8::MicrotasksScope::kDoNotRunMicrotasks);
+ v8::Local<v8::Value> return_value;
+ if (function->Call(context, recv, argc, argv.get()).ToLocal(&return_value))
+ info.GetReturnValue().Set(return_value);
+ }
+
+ static void Save(const v8::FunctionCallbackInfo<v8::Value>& info) {
+ CHECK(info.Length() == 2 && info[0]->IsString() && info[1]->IsObject());
+ SaveImpl(*v8::String::Utf8Value(info[0]),
+ info[1],
+ info.GetIsolate()->GetCurrentContext());
+ }
+};
+
+} // namespace
+
+// static
+v8::Extension* SafeBuiltins::CreateV8Extension() { return new ExtensionImpl(); }
+
+SafeBuiltins::SafeBuiltins(ScriptContext* context) : context_(context) {}
+
+SafeBuiltins::~SafeBuiltins() {}
+
+v8::Local<v8::Object> SafeBuiltins::GetArray() const {
+ return Load("Array", context_->v8_context());
+}
+
+v8::Local<v8::Object> SafeBuiltins::GetFunction() const {
+ return Load("Function", context_->v8_context());
+}
+
+v8::Local<v8::Object> SafeBuiltins::GetJSON() const {
+ return Load("JSON", context_->v8_context());
+}
+
+v8::Local<v8::Object> SafeBuiltins::GetObjekt() const {
+ return Load("Object", context_->v8_context());
+}
+
+v8::Local<v8::Object> SafeBuiltins::GetRegExp() const {
+ return Load("RegExp", context_->v8_context());
+}
+
+v8::Local<v8::Object> SafeBuiltins::GetString() const {
+ return Load("String", context_->v8_context());
+}
+
+v8::Local<v8::Object> SafeBuiltins::GetError() const {
+ return Load("Error", context_->v8_context());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/safe_builtins.h b/chromium/extensions/renderer/safe_builtins.h
new file mode 100644
index 00000000000..83aaca9e537
--- /dev/null
+++ b/chromium/extensions/renderer/safe_builtins.h
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_SAFE_BUILTINS_H_
+#define EXTENSIONS_RENDERER_SAFE_BUILTINS_H_
+
+#include "v8/include/v8.h"
+
+namespace extensions {
+class ScriptContext;
+
+// A collection of safe builtin objects, in that they won't be tained by
+// extensions overriding methods on them.
+class SafeBuiltins {
+ public:
+ // Creates the v8::Extension which manages SafeBuiltins instances.
+ static v8::Extension* CreateV8Extension();
+
+ explicit SafeBuiltins(ScriptContext* context);
+
+ virtual ~SafeBuiltins();
+
+ // Each method returns an object with methods taken from their respective
+ // builtin object's prototype, adapted to automatically call() themselves.
+ //
+ // Examples:
+ // Array.prototype.forEach.call(...) becomes Array.forEach(...)
+ // Object.prototype.toString.call(...) becomes Object.toString(...)
+ // Object.keys.call(...) becomes Object.keys(...)
+ v8::Local<v8::Object> GetArray() const;
+ v8::Local<v8::Object> GetFunction() const;
+ v8::Local<v8::Object> GetJSON() const;
+ // NOTE(kalman): VS2010 won't compile "GetObject", it mysteriously renames it
+ // to "GetObjectW" - hence GetObjekt. Sorry.
+ v8::Local<v8::Object> GetObjekt() const;
+ v8::Local<v8::Object> GetRegExp() const;
+ v8::Local<v8::Object> GetString() const;
+ v8::Local<v8::Object> GetError() const;
+
+ private:
+ ScriptContext* context_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SAFE_BUILTINS_H_
diff --git a/chromium/extensions/renderer/safe_builtins_unittest.cc b/chromium/extensions/renderer/safe_builtins_unittest.cc
new file mode 100644
index 00000000000..38910a2c5df
--- /dev/null
+++ b/chromium/extensions/renderer/safe_builtins_unittest.cc
@@ -0,0 +1,66 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/module_system_test.h"
+
+namespace extensions {
+namespace {
+
+class SafeBuiltinsUnittest : public ModuleSystemTest {};
+
+TEST_F(SafeBuiltinsUnittest, TestNotOriginalObject) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');\n"
+ "Array.foo = 10;\n"
+ "assert.AssertTrue(!$Array.hasOwnProperty('foo'));\n");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(SafeBuiltinsUnittest, TestSelf) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');\n"
+ "Array.foo = 10;\n"
+ "assert.AssertTrue($Array.self.foo == 10);\n"
+ "var arr = $Array.self(1);\n"
+ "assert.AssertTrue(arr.length == 1);\n"
+ "assert.AssertTrue(arr[0] === undefined);\n");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(SafeBuiltinsUnittest, TestStaticFunction) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule("test",
+ "var assert = requireNative('assert');\n"
+ "Object.keys = function() {throw new Error()};\n"
+ "var obj = {a: 10};\n"
+ "var keys = $Object.keys(obj);\n"
+ "assert.AssertTrue(keys.length == 1);\n"
+ "assert.AssertTrue(keys[0] == 'a');\n");
+ env()->module_system()->Require("test");
+}
+
+TEST_F(SafeBuiltinsUnittest, TestInstanceMethod) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->RegisterModule(
+ "test",
+ "var assert = requireNative('assert');\n"
+ "Array.prototype.push = function() {throw new Error();}\n"
+ "var arr = []\n"
+ "$Array.push(arr, 1);\n"
+ "assert.AssertTrue(arr.length == 1);\n"
+ "assert.AssertTrue(arr[0] == 1);\n");
+ env()->module_system()->Require("test");
+}
+
+// NOTE: JSON is already tested in ExtensionApiTest.Messaging, via
+// chrome/test/data/extensions/api_test/messaging/connect/page.js.
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/renderer/scoped_web_frame.cc b/chromium/extensions/renderer/scoped_web_frame.cc
new file mode 100644
index 00000000000..88c4098b5c3
--- /dev/null
+++ b/chromium/extensions/renderer/scoped_web_frame.cc
@@ -0,0 +1,24 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/scoped_web_frame.h"
+
+#include "third_party/WebKit/public/web/WebHeap.h"
+
+namespace extensions {
+
+ScopedWebFrame::ScopedWebFrame() : view_(nullptr), frame_(nullptr) {
+ view_ = blink::WebView::create(nullptr);
+ frame_ = blink::WebLocalFrame::create(
+ blink::WebTreeScopeType::Document, nullptr);
+ view_->setMainFrame(frame_);
+}
+
+ScopedWebFrame::~ScopedWebFrame() {
+ view_->close();
+ frame_->close();
+ blink::WebHeap::collectAllGarbageForTesting();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/scoped_web_frame.h b/chromium/extensions/renderer/scoped_web_frame.h
new file mode 100644
index 00000000000..948a289483b
--- /dev/null
+++ b/chromium/extensions/renderer/scoped_web_frame.h
@@ -0,0 +1,34 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SCOPED_WEB_FRAME_H_
+#define SCOPED_WEB_FRAME_H_
+
+#include "base/macros.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebView.h"
+
+namespace extensions {
+
+// ScopedWebFrame is a class to create a dummy webview and frame for testing.
+// The dymmy webview and frame will be destructed when the scope exits.
+class ScopedWebFrame {
+public:
+ ScopedWebFrame();
+ ~ScopedWebFrame();
+
+ blink::WebLocalFrame* frame() { return frame_; }
+
+private:
+ // The webview and the frame are kept alive by the ScopedWebFrame
+ // because they are not destructed unless ~ScopedWebFrame explicitly
+ // closes the webview and the frame.
+ blink::WebView* view_;
+ blink::WebLocalFrame* frame_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedWebFrame);
+};
+
+} // namespace extensions
+
+#endif // SCOPED_WEB_FRAME_H_
diff --git a/chromium/extensions/renderer/script_context.cc b/chromium/extensions/renderer/script_context.cc
new file mode 100644
index 00000000000..a1d8996a5da
--- /dev/null
+++ b/chromium/extensions/renderer/script_context.cc
@@ -0,0 +1,489 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/script_context.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_api.h"
+#include "extensions/common/extension_urls.h"
+#include "extensions/common/features/base_feature_provider.h"
+#include "extensions/common/manifest_handlers/sandboxed_page_info.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/renderer/renderer_extension_registry.h"
+#include "extensions/renderer/v8_helpers.h"
+#include "gin/per_context_data.h"
+#include "third_party/WebKit/public/platform/WebSecurityOrigin.h"
+#include "third_party/WebKit/public/web/WebDataSource.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebFrame.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebView.h"
+#include "v8/include/v8.h"
+
+using content::V8ValueConverter;
+
+namespace extensions {
+
+namespace {
+
+std::string GetContextTypeDescriptionString(Feature::Context context_type) {
+ switch (context_type) {
+ case Feature::UNSPECIFIED_CONTEXT:
+ return "UNSPECIFIED";
+ case Feature::BLESSED_EXTENSION_CONTEXT:
+ return "BLESSED_EXTENSION";
+ case Feature::UNBLESSED_EXTENSION_CONTEXT:
+ return "UNBLESSED_EXTENSION";
+ case Feature::CONTENT_SCRIPT_CONTEXT:
+ return "CONTENT_SCRIPT";
+ case Feature::WEB_PAGE_CONTEXT:
+ return "WEB_PAGE";
+ case Feature::BLESSED_WEB_PAGE_CONTEXT:
+ return "BLESSED_WEB_PAGE";
+ case Feature::WEBUI_CONTEXT:
+ return "WEBUI";
+ case Feature::SERVICE_WORKER_CONTEXT:
+ return "SERVICE_WORKER";
+ }
+ NOTREACHED();
+ return std::string();
+}
+
+static std::string ToStringOrDefault(
+ const v8::Local<v8::String>& v8_string,
+ const std::string& dflt) {
+ if (v8_string.IsEmpty())
+ return dflt;
+ std::string ascii_value = *v8::String::Utf8Value(v8_string);
+ return ascii_value.empty() ? dflt : ascii_value;
+}
+
+} // namespace
+
+// A gin::Runner that delegates to its ScriptContext.
+class ScriptContext::Runner : public gin::Runner {
+ public:
+ explicit Runner(ScriptContext* context);
+
+ // gin::Runner overrides.
+ void Run(const std::string& source,
+ const std::string& resource_name) override;
+ v8::Local<v8::Value> Call(v8::Local<v8::Function> function,
+ v8::Local<v8::Value> receiver,
+ int argc,
+ v8::Local<v8::Value> argv[]) override;
+ gin::ContextHolder* GetContextHolder() override;
+
+ private:
+ ScriptContext* context_;
+};
+
+ScriptContext::ScriptContext(const v8::Local<v8::Context>& v8_context,
+ blink::WebLocalFrame* web_frame,
+ const Extension* extension,
+ Feature::Context context_type,
+ const Extension* effective_extension,
+ Feature::Context effective_context_type)
+ : is_valid_(true),
+ v8_context_(v8_context->GetIsolate(), v8_context),
+ web_frame_(web_frame),
+ extension_(extension),
+ context_type_(context_type),
+ effective_extension_(effective_extension),
+ effective_context_type_(effective_context_type),
+ safe_builtins_(this),
+ isolate_(v8_context->GetIsolate()),
+ url_(web_frame_ ? GetDataSourceURLForFrame(web_frame_) : GURL()),
+ runner_(new Runner(this)) {
+ VLOG(1) << "Created context:\n" << GetDebugString();
+ gin::PerContextData* gin_data = gin::PerContextData::From(v8_context);
+ CHECK(gin_data);
+ gin_data->set_runner(runner_.get());
+}
+
+ScriptContext::~ScriptContext() {
+ VLOG(1) << "Destroyed context for extension\n"
+ << " extension id: " << GetExtensionID() << "\n"
+ << " effective extension id: "
+ << (effective_extension_.get() ? effective_extension_->id() : "");
+ CHECK(!is_valid_) << "ScriptContexts must be invalidated before destruction";
+}
+
+// static
+bool ScriptContext::IsSandboxedPage(const GURL& url) {
+ // TODO(kalman): This is checking the wrong thing. See comment in
+ // HasAccessOrThrowError.
+ if (url.SchemeIs(kExtensionScheme)) {
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(url.host());
+ if (extension) {
+ return SandboxedPageInfo::IsSandboxedPage(extension, url.path());
+ }
+ }
+ return false;
+}
+
+void ScriptContext::Invalidate() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ CHECK(is_valid_);
+ is_valid_ = false;
+
+ // TODO(kalman): Make ModuleSystem use AddInvalidationObserver.
+ // Ownership graph is a bit weird here.
+ if (module_system_)
+ module_system_->Invalidate();
+
+ // Swap |invalidate_observers_| to a local variable to clear it, and to make
+ // sure it's not mutated as we iterate.
+ std::vector<base::Closure> observers;
+ observers.swap(invalidate_observers_);
+ for (const base::Closure& observer : observers) {
+ observer.Run();
+ }
+ DCHECK(invalidate_observers_.empty())
+ << "Invalidation observers cannot be added during invalidation";
+
+ runner_.reset();
+ v8_context_.Reset();
+}
+
+void ScriptContext::AddInvalidationObserver(const base::Closure& observer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ invalidate_observers_.push_back(observer);
+}
+
+const std::string& ScriptContext::GetExtensionID() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return extension_.get() ? extension_->id() : base::EmptyString();
+}
+
+content::RenderFrame* ScriptContext::GetRenderFrame() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (web_frame_)
+ return content::RenderFrame::FromWebFrame(web_frame_);
+ return NULL;
+}
+
+v8::Local<v8::Value> ScriptContext::CallFunction(
+ const v8::Local<v8::Function>& function,
+ int argc,
+ v8::Local<v8::Value> argv[]) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ v8::EscapableHandleScope handle_scope(isolate());
+ v8::Context::Scope scope(v8_context());
+
+ v8::MicrotasksScope microtasks(
+ isolate(), v8::MicrotasksScope::kDoNotRunMicrotasks);
+ if (!is_valid_) {
+ return handle_scope.Escape(
+ v8::Local<v8::Primitive>(v8::Undefined(isolate())));
+ }
+
+ v8::Local<v8::Object> global = v8_context()->Global();
+ if (!web_frame_)
+ return handle_scope.Escape(function->Call(global, argc, argv));
+ return handle_scope.Escape(
+ v8::Local<v8::Value>(web_frame_->callFunctionEvenIfScriptDisabled(
+ function, global, argc, argv)));
+}
+
+v8::Local<v8::Value> ScriptContext::CallFunction(
+ const v8::Local<v8::Function>& function) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return CallFunction(function, 0, nullptr);
+}
+
+Feature::Availability ScriptContext::GetAvailability(
+ const std::string& api_name) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (base::StartsWith(api_name, "test", base::CompareCase::SENSITIVE)) {
+ bool allowed = base::CommandLine::ForCurrentProcess()->
+ HasSwitch(::switches::kTestType);
+ Feature::AvailabilityResult result =
+ allowed ? Feature::IS_AVAILABLE : Feature::MISSING_COMMAND_LINE_SWITCH;
+ return Feature::Availability(result,
+ allowed ? "" : "Only allowed in tests");
+ }
+ // Hack: Hosted apps should have the availability of messaging APIs based on
+ // the URL of the page (which might have access depending on some extension
+ // with externally_connectable), not whether the app has access to messaging
+ // (which it won't).
+ const Extension* extension = extension_.get();
+ if (extension && extension->is_hosted_app() &&
+ (api_name == "runtime.connect" || api_name == "runtime.sendMessage")) {
+ extension = NULL;
+ }
+ return ExtensionAPI::GetSharedInstance()->IsAvailable(api_name, extension,
+ context_type_, url());
+}
+
+void ScriptContext::DispatchEvent(const char* event_name,
+ v8::Local<v8::Array> args) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ v8::HandleScope handle_scope(isolate());
+ v8::Context::Scope context_scope(v8_context());
+
+ v8::Local<v8::Value> argv[] = {v8::String::NewFromUtf8(isolate(), event_name),
+ args};
+ module_system_->CallModuleMethod(
+ kEventBindings, "dispatchEvent", arraysize(argv), argv);
+}
+
+std::string ScriptContext::GetContextTypeDescription() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return GetContextTypeDescriptionString(context_type_);
+}
+
+std::string ScriptContext::GetEffectiveContextTypeDescription() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return GetContextTypeDescriptionString(effective_context_type_);
+}
+
+bool ScriptContext::IsAnyFeatureAvailableToContext(const Feature& api) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return ExtensionAPI::GetSharedInstance()->IsAnyFeatureAvailableToContext(
+ api, extension(), context_type(), GetDataSourceURLForFrame(web_frame()));
+}
+
+// static
+GURL ScriptContext::GetDataSourceURLForFrame(const blink::WebFrame* frame) {
+ // Normally we would use frame->document().url() to determine the document's
+ // URL, but to decide whether to inject a content script, we use the URL from
+ // the data source. This "quirk" helps prevents content scripts from
+ // inadvertently adding DOM elements to the compose iframe in Gmail because
+ // the compose iframe's dataSource URL is about:blank, but the document URL
+ // changes to match the parent document after Gmail document.writes into
+ // it to create the editor.
+ // http://code.google.com/p/chromium/issues/detail?id=86742
+ blink::WebDataSource* data_source = frame->provisionalDataSource()
+ ? frame->provisionalDataSource()
+ : frame->dataSource();
+ return data_source ? GURL(data_source->request().url()) : GURL();
+}
+
+// static
+GURL ScriptContext::GetEffectiveDocumentURL(const blink::WebFrame* frame,
+ const GURL& document_url,
+ bool match_about_blank) {
+ // Common scenario. If |match_about_blank| is false (as is the case in most
+ // extensions), or if the frame is not an about:-page, just return
+ // |document_url| (supposedly the URL of the frame).
+ if (!match_about_blank || !document_url.SchemeIs(url::kAboutScheme))
+ return document_url;
+
+ // Non-sandboxed about:blank and about:srcdoc pages inherit their security
+ // origin from their parent frame/window. So, traverse the frame/window
+ // hierarchy to find the closest non-about:-page and return its URL.
+ const blink::WebFrame* parent = frame;
+ do {
+ if (parent->parent())
+ parent = parent->parent();
+ else if (parent->opener() != parent)
+ parent = parent->opener();
+ else
+ parent = nullptr;
+ } while (parent && !parent->document().isNull() &&
+ GURL(parent->document().url()).SchemeIs(url::kAboutScheme));
+
+ if (parent && !parent->document().isNull()) {
+ // Only return the parent URL if the frame can access it.
+ const blink::WebDocument& parent_document = parent->document();
+ if (frame->document().getSecurityOrigin().canAccess(
+ parent_document.getSecurityOrigin())) {
+ return parent_document.url();
+ }
+ }
+ return document_url;
+}
+
+ScriptContext* ScriptContext::GetContext() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return this;
+}
+
+void ScriptContext::OnResponseReceived(const std::string& name,
+ int request_id,
+ bool success,
+ const base::ListValue& response,
+ const std::string& error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ v8::HandleScope handle_scope(isolate());
+
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+ v8::Local<v8::Value> argv[] = {
+ v8::Integer::New(isolate(), request_id),
+ v8::String::NewFromUtf8(isolate(), name.c_str()),
+ v8::Boolean::New(isolate(), success),
+ converter->ToV8Value(&response,
+ v8::Local<v8::Context>::New(isolate(), v8_context_)),
+ v8::String::NewFromUtf8(isolate(), error.c_str())};
+
+ v8::Local<v8::Value> retval = module_system()->CallModuleMethod(
+ "sendRequest", "handleResponse", arraysize(argv), argv);
+
+ // In debug, the js will validate the callback parameters and return a
+ // string if a validation error has occured.
+ DCHECK(retval.IsEmpty() || retval->IsUndefined())
+ << *v8::String::Utf8Value(retval);
+}
+
+bool ScriptContext::HasAPIPermission(APIPermission::ID permission) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (effective_extension_.get()) {
+ return effective_extension_->permissions_data()->HasAPIPermission(
+ permission);
+ }
+ if (context_type() == Feature::WEB_PAGE_CONTEXT) {
+ // Only web page contexts may be granted content capabilities. Other
+ // contexts are either privileged WebUI or extensions with their own set of
+ // permissions.
+ if (content_capabilities_.find(permission) != content_capabilities_.end())
+ return true;
+ }
+ return false;
+}
+
+bool ScriptContext::HasAccessOrThrowError(const std::string& name) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // Theoretically[1] we could end up with bindings being injected into
+ // sandboxed frames, for example content scripts. Don't let them execute API
+ // functions.
+ //
+ // In any case, this check is silly. The frame's document's security origin
+ // already tells us if it's sandboxed. The only problem is that until
+ // crbug.com/466373 is fixed, we don't know the security origin up-front and
+ // may not know it here, either.
+ //
+ // [1] citation needed. This ScriptContext should already be in a state that
+ // doesn't allow this, from ScriptContextSet::ClassifyJavaScriptContext.
+ if (extension() &&
+ SandboxedPageInfo::IsSandboxedPage(extension(), url_.path())) {
+ static const char kMessage[] =
+ "%s cannot be used within a sandboxed frame.";
+ std::string error_msg = base::StringPrintf(kMessage, name.c_str());
+ isolate()->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate(), error_msg.c_str())));
+ return false;
+ }
+
+ Feature::Availability availability = GetAvailability(name);
+ if (!availability.is_available()) {
+ isolate()->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate(), availability.message().c_str())));
+ return false;
+ }
+
+ return true;
+}
+
+std::string ScriptContext::GetDebugString() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return base::StringPrintf(
+ " extension id: %s\n"
+ " frame: %p\n"
+ " URL: %s\n"
+ " context_type: %s\n"
+ " effective extension id: %s\n"
+ " effective context type: %s",
+ extension_.get() ? extension_->id().c_str() : "(none)", web_frame_,
+ url_.spec().c_str(), GetContextTypeDescription().c_str(),
+ effective_extension_.get() ? effective_extension_->id().c_str()
+ : "(none)",
+ GetEffectiveContextTypeDescription().c_str());
+}
+
+std::string ScriptContext::GetStackTraceAsString() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ v8::Local<v8::StackTrace> stack_trace =
+ v8::StackTrace::CurrentStackTrace(isolate(), 10);
+ if (stack_trace.IsEmpty() || stack_trace->GetFrameCount() <= 0) {
+ return " <no stack trace>";
+ }
+ std::string result;
+ for (int i = 0; i < stack_trace->GetFrameCount(); ++i) {
+ v8::Local<v8::StackFrame> frame = stack_trace->GetFrame(i);
+ CHECK(!frame.IsEmpty());
+ result += base::StringPrintf(
+ "\n at %s (%s:%d:%d)",
+ ToStringOrDefault(frame->GetFunctionName(), "<anonymous>").c_str(),
+ ToStringOrDefault(frame->GetScriptName(), "<anonymous>").c_str(),
+ frame->GetLineNumber(), frame->GetColumn());
+ }
+ return result;
+}
+
+v8::Local<v8::Value> ScriptContext::RunScript(
+ v8::Local<v8::String> name,
+ v8::Local<v8::String> code,
+ const RunScriptExceptionHandler& exception_handler) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ v8::EscapableHandleScope handle_scope(isolate());
+ v8::Context::Scope context_scope(v8_context());
+
+ // Prepend extensions:: to |name| so that internal code can be differentiated
+ // from external code in stack traces. This has no effect on behaviour.
+ std::string internal_name =
+ base::StringPrintf("extensions::%s", *v8::String::Utf8Value(name));
+
+ if (internal_name.size() >= v8::String::kMaxLength) {
+ NOTREACHED() << "internal_name is too long.";
+ return v8::Undefined(isolate());
+ }
+
+ v8::MicrotasksScope microtasks(
+ isolate(), v8::MicrotasksScope::kDoNotRunMicrotasks);
+ v8::TryCatch try_catch(isolate());
+ try_catch.SetCaptureMessage(true);
+ v8::ScriptOrigin origin(
+ v8_helpers::ToV8StringUnsafe(isolate(), internal_name.c_str()));
+ v8::Local<v8::Script> script;
+ if (!v8::Script::Compile(v8_context(), code, &origin).ToLocal(&script)) {
+ exception_handler.Run(try_catch);
+ return v8::Undefined(isolate());
+ }
+
+ v8::Local<v8::Value> result;
+ if (!script->Run(v8_context()).ToLocal(&result)) {
+ exception_handler.Run(try_catch);
+ return v8::Undefined(isolate());
+ }
+
+ return handle_scope.Escape(result);
+}
+
+ScriptContext::Runner::Runner(ScriptContext* context) : context_(context) {
+}
+
+void ScriptContext::Runner::Run(const std::string& source,
+ const std::string& resource_name) {
+ context_->module_system()->RunString(source, resource_name);
+}
+
+v8::Local<v8::Value> ScriptContext::Runner::Call(
+ v8::Local<v8::Function> function,
+ v8::Local<v8::Value> receiver,
+ int argc,
+ v8::Local<v8::Value> argv[]) {
+ return context_->CallFunction(function, argc, argv);
+}
+
+gin::ContextHolder* ScriptContext::Runner::GetContextHolder() {
+ v8::HandleScope handle_scope(context_->isolate());
+ return gin::PerContextData::From(context_->v8_context())->context_holder();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/script_context.h b/chromium/extensions/renderer/script_context.h
new file mode 100644
index 00000000000..48c873ce1fc
--- /dev/null
+++ b/chromium/extensions/renderer/script_context.h
@@ -0,0 +1,259 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_SCRIPT_CONTEXT_H_
+#define EXTENSIONS_RENDERER_SCRIPT_CONTEXT_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/threading/thread_checker.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/permissions/api_permission_set.h"
+#include "extensions/renderer/module_system.h"
+#include "extensions/renderer/request_sender.h"
+#include "extensions/renderer/safe_builtins.h"
+#include "gin/runner.h"
+#include "url/gurl.h"
+#include "v8/include/v8.h"
+
+namespace blink {
+class WebFrame;
+class WebLocalFrame;
+}
+
+namespace content {
+class RenderFrame;
+}
+
+namespace extensions {
+class Extension;
+
+// Extensions wrapper for a v8::Context.
+//
+// v8::Contexts can be constructed on any thread, and must only be accessed or
+// destroyed that thread.
+//
+// Note that ScriptContexts bound to worker threads will not have the full
+// functionality as those bound to the main RenderThread.
+class ScriptContext : public RequestSender::Source {
+ public:
+ using RunScriptExceptionHandler = base::Callback<void(const v8::TryCatch&)>;
+
+ ScriptContext(const v8::Local<v8::Context>& context,
+ blink::WebLocalFrame* frame,
+ const Extension* extension,
+ Feature::Context context_type,
+ const Extension* effective_extension,
+ Feature::Context effective_context_type);
+ ~ScriptContext() override;
+
+ // Returns whether |url| from any Extension in |extension_set| is sandboxed,
+ // as declared in each Extension's manifest.
+ // TODO(kalman): Delete this when crbug.com/466373 is fixed.
+ // See comment in HasAccessOrThrowError.
+ static bool IsSandboxedPage(const GURL& url);
+
+ // Clears the WebFrame for this contexts and invalidates the associated
+ // ModuleSystem.
+ void Invalidate();
+
+ // Registers |observer| to be run when this context is invalidated. Closures
+ // are run immediately when Invalidate() is called, not in a message loop.
+ void AddInvalidationObserver(const base::Closure& observer);
+
+ // Returns true if this context is still valid, false if it isn't.
+ // A context becomes invalid via Invalidate().
+ bool is_valid() const { return is_valid_; }
+
+ v8::Local<v8::Context> v8_context() const {
+ return v8::Local<v8::Context>::New(isolate_, v8_context_);
+ }
+
+ const Extension* extension() const { return extension_.get(); }
+
+ const Extension* effective_extension() const {
+ return effective_extension_.get();
+ }
+
+ blink::WebLocalFrame* web_frame() const { return web_frame_; }
+
+ Feature::Context context_type() const { return context_type_; }
+
+ Feature::Context effective_context_type() const {
+ return effective_context_type_;
+ }
+
+ void set_module_system(scoped_ptr<ModuleSystem> module_system) {
+ module_system_ = std::move(module_system);
+ }
+
+ ModuleSystem* module_system() { return module_system_.get(); }
+
+ SafeBuiltins* safe_builtins() { return &safe_builtins_; }
+
+ const SafeBuiltins* safe_builtins() const { return &safe_builtins_; }
+
+ // Returns the ID of the extension associated with this context, or empty
+ // string if there is no such extension.
+ const std::string& GetExtensionID() const;
+
+ // Returns the RenderFrame associated with this context. Can return NULL if
+ // the context is in the process of being destroyed.
+ content::RenderFrame* GetRenderFrame() const;
+
+ // Runs |function| with appropriate scopes. Doesn't catch exceptions, callers
+ // must do that if they want.
+ //
+ // USE THIS METHOD RATHER THAN v8::Function::Call WHEREVER POSSIBLE.
+ v8::Local<v8::Value> CallFunction(const v8::Local<v8::Function>& function,
+ int argc,
+ v8::Local<v8::Value> argv[]) const;
+ v8::Local<v8::Value> CallFunction(
+ const v8::Local<v8::Function>& function) const;
+
+ void DispatchEvent(const char* event_name, v8::Local<v8::Array> args) const;
+
+ // Returns the availability of the API |api_name|.
+ Feature::Availability GetAvailability(const std::string& api_name);
+
+ // Returns a string description of the type of context this is.
+ std::string GetContextTypeDescription() const;
+
+ // Returns a string description of the effective type of context this is.
+ std::string GetEffectiveContextTypeDescription() const;
+
+ v8::Isolate* isolate() const { return isolate_; }
+
+ // Get the URL of this context's web frame.
+ //
+ // TODO(kalman): Remove this and replace with a GetOrigin() call which reads
+ // of WebDocument::getSecurityOrigin():
+ // - The URL can change (e.g. pushState) but the origin cannot. Luckily it
+ // appears as though callers don't make security decisions based on the
+ // result of url() so it's not a problem... yet.
+ // - Origin is the correct check to be making.
+ // - It might let us remove the about:blank resolving?
+ const GURL& url() const { return url_; }
+
+ // Sets the URL of this ScriptContext. Usually this will automatically be set
+ // on construction, unless this isn't constructed with enough information to
+ // determine the URL (e.g. frame was null).
+ // TODO(kalman): Make this a constructor parameter (as an origin).
+ void set_url(const GURL& url) { url_ = url; }
+
+ // Returns whether the API |api| or any part of the API could be
+ // available in this context without taking into account the context's
+ // extension.
+ bool IsAnyFeatureAvailableToContext(const extensions::Feature& api);
+
+ // Utility to get the URL we will match against for a frame. If the frame has
+ // committed, this is the commited URL. Otherwise it is the provisional URL.
+ // The returned URL may be invalid.
+ static GURL GetDataSourceURLForFrame(const blink::WebFrame* frame);
+
+ // Returns the first non-about:-URL in the document hierarchy above and
+ // including |frame|. The document hierarchy is only traversed if
+ // |document_url| is an about:-URL and if |match_about_blank| is true.
+ static GURL GetEffectiveDocumentURL(const blink::WebFrame* frame,
+ const GURL& document_url,
+ bool match_about_blank);
+
+ // RequestSender::Source implementation.
+ ScriptContext* GetContext() override;
+ void OnResponseReceived(const std::string& name,
+ int request_id,
+ bool success,
+ const base::ListValue& response,
+ const std::string& error) override;
+
+ // Grants a set of content capabilities to this context.
+ void set_content_capabilities(const APIPermissionSet& capabilities) {
+ content_capabilities_ = capabilities;
+ }
+
+ // Indicates if this context has an effective API permission either by being
+ // a context for an extension which has that permission, or by being a web
+ // context which has been granted the corresponding capability by an
+ // extension.
+ bool HasAPIPermission(APIPermission::ID permission) const;
+
+ // Throws an Error in this context's JavaScript context, if this context does
+ // not have access to |name|. Returns true if this context has access (i.e.
+ // no exception thrown), false if it does not (i.e. an exception was thrown).
+ bool HasAccessOrThrowError(const std::string& name);
+
+ // Returns a string representation of this ScriptContext, for debugging.
+ std::string GetDebugString() const;
+
+ // Gets the current stack trace as a multi-line string to be logged.
+ std::string GetStackTraceAsString() const;
+
+ // Runs |code|, labelling the script that gets created as |name| (the name is
+ // used in the devtools and stack traces). |exception_handler| will be called
+ // re-entrantly if an exception is thrown during the script's execution.
+ v8::Local<v8::Value> RunScript(
+ v8::Local<v8::String> name,
+ v8::Local<v8::String> code,
+ const RunScriptExceptionHandler& exception_handler);
+
+ private:
+ class Runner;
+
+ // Whether this context is valid.
+ bool is_valid_;
+
+ // The v8 context the bindings are accessible to.
+ v8::Global<v8::Context> v8_context_;
+
+ // The WebLocalFrame associated with this context. This can be NULL because
+ // this object can outlive is destroyed asynchronously.
+ blink::WebLocalFrame* web_frame_;
+
+ // The extension associated with this context, or NULL if there is none. This
+ // might be a hosted app in the case that this context is hosting a web URL.
+ scoped_refptr<const Extension> extension_;
+
+ // The type of context.
+ Feature::Context context_type_;
+
+ // The effective extension associated with this context, or NULL if there is
+ // none. This is different from the above extension if this context is in an
+ // about:blank iframe for example.
+ scoped_refptr<const Extension> effective_extension_;
+
+ // The type of context.
+ Feature::Context effective_context_type_;
+
+ // Owns and structures the JS that is injected to set up extension bindings.
+ scoped_ptr<ModuleSystem> module_system_;
+
+ // Contains safe copies of builtin objects like Function.prototype.
+ SafeBuiltins safe_builtins_;
+
+ // The set of capabilities granted to this context by extensions.
+ APIPermissionSet content_capabilities_;
+
+ // A list of base::Closure instances as an observer interface for
+ // invalidation.
+ std::vector<base::Closure> invalidate_observers_;
+
+ v8::Isolate* isolate_;
+
+ GURL url_;
+
+ scoped_ptr<Runner> runner_;
+
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScriptContext);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SCRIPT_CONTEXT_H_
diff --git a/chromium/extensions/renderer/script_context_browsertest.cc b/chromium/extensions/renderer/script_context_browsertest.cc
new file mode 100644
index 00000000000..2559a692910
--- /dev/null
+++ b/chromium/extensions/renderer/script_context_browsertest.cc
@@ -0,0 +1,91 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/test/frame_load_waiter.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "url/gurl.h"
+
+using blink::WebFrame;
+
+namespace extensions {
+namespace {
+
+class ScriptContextTest : public ChromeRenderViewTest {
+ protected:
+ GURL GetEffectiveDocumentURL(const WebFrame* frame) {
+ return ScriptContext::GetEffectiveDocumentURL(
+ frame, frame->document().url(), true);
+ }
+};
+
+TEST_F(ScriptContextTest, GetEffectiveDocumentURL) {
+ GURL top_url("http://example.com/");
+ GURL different_url("http://example.net/");
+ GURL blank_url("about:blank");
+ GURL srcdoc_url("about:srcdoc");
+
+ const char frame_html[] =
+ "<iframe name='frame1' srcdoc=\""
+ " <iframe name='frame1_1'></iframe>"
+ " <iframe name='frame1_2' sandbox=''></iframe>"
+ "\"></iframe>"
+ "<iframe name='frame2' sandbox='' srcdoc=\""
+ " <iframe name='frame2_1'></iframe>"
+ "\"></iframe>"
+ "<iframe name='frame3'></iframe>";
+
+ const char frame3_html[] = "<iframe name='frame3_1'></iframe>";
+
+ WebFrame* frame = GetMainFrame();
+ ASSERT_TRUE(frame);
+
+ frame->loadHTMLString(frame_html, top_url);
+ content::FrameLoadWaiter(content::RenderFrame::FromWebFrame(frame)).Wait();
+
+ WebFrame* frame1 = frame->findChildByName("frame1");
+ ASSERT_TRUE(frame1);
+ WebFrame* frame1_1 = frame1->findChildByName("frame1_1");
+ ASSERT_TRUE(frame1_1);
+ WebFrame* frame1_2 = frame1->findChildByName("frame1_2");
+ ASSERT_TRUE(frame1_2);
+ WebFrame* frame2 = frame->findChildByName("frame2");
+ ASSERT_TRUE(frame2);
+ WebFrame* frame2_1 = frame2->findChildByName("frame2_1");
+ ASSERT_TRUE(frame2_1);
+ WebFrame* frame3 = frame->findChildByName("frame3");
+ ASSERT_TRUE(frame3);
+
+ // Load a blank document in a frame from a different origin.
+ frame3->loadHTMLString(frame3_html, different_url);
+ content::FrameLoadWaiter(content::RenderFrame::FromWebFrame(frame3)).Wait();
+
+ WebFrame* frame3_1 = frame->findChildByName("frame3");
+ ASSERT_TRUE(frame3_1);
+
+ // Top-level frame
+ EXPECT_EQ(GetEffectiveDocumentURL(frame), top_url);
+ // top -> srcdoc = inherit
+ EXPECT_EQ(GetEffectiveDocumentURL(frame1), top_url);
+ // top -> srcdoc -> about:blank = inherit
+ EXPECT_EQ(GetEffectiveDocumentURL(frame1_1), top_url);
+ // top -> srcdoc -> about:blank sandboxed = same URL
+ EXPECT_EQ(GetEffectiveDocumentURL(frame1_2), blank_url);
+
+ // top -> srcdoc [sandboxed] = same URL
+ EXPECT_EQ(GetEffectiveDocumentURL(frame2), srcdoc_url);
+ // top -> srcdoc [sandboxed] -> about:blank = same URL
+ EXPECT_EQ(GetEffectiveDocumentURL(frame2_1), blank_url);
+
+ // top -> different origin = different origin
+ EXPECT_EQ(GetEffectiveDocumentURL(frame3), different_url);
+ // top -> different origin -> about:blank = inherit
+ EXPECT_EQ(GetEffectiveDocumentURL(frame3_1), different_url);
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/renderer/script_context_set.cc b/chromium/extensions/renderer/script_context_set.cc
new file mode 100644
index 00000000000..33df67e58d1
--- /dev/null
+++ b/chromium/extensions/renderer/script_context_set.cc
@@ -0,0 +1,223 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/script_context_set.h"
+
+#include "base/message_loop/message_loop.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/extension.h"
+#include "extensions/renderer/extension_groups.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_injection.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+namespace {
+// There is only ever one instance of the ScriptContextSet.
+ScriptContextSet* g_context_set = nullptr;
+}
+
+ScriptContextSet::ScriptContextSet(ExtensionIdSet* active_extension_ids)
+ : active_extension_ids_(active_extension_ids) {
+ DCHECK(!g_context_set);
+ g_context_set = this;
+}
+
+ScriptContextSet::~ScriptContextSet() {
+ g_context_set = nullptr;
+}
+
+ScriptContext* ScriptContextSet::Register(
+ blink::WebLocalFrame* frame,
+ const v8::Local<v8::Context>& v8_context,
+ int extension_group,
+ int world_id) {
+ const Extension* extension =
+ GetExtensionFromFrameAndWorld(frame, world_id, false);
+ const Extension* effective_extension =
+ GetExtensionFromFrameAndWorld(frame, world_id, true);
+
+ GURL frame_url = ScriptContext::GetDataSourceURLForFrame(frame);
+ Feature::Context context_type =
+ ClassifyJavaScriptContext(extension, extension_group, frame_url,
+ frame->document().getSecurityOrigin());
+ Feature::Context effective_context_type = ClassifyJavaScriptContext(
+ effective_extension, extension_group,
+ ScriptContext::GetEffectiveDocumentURL(frame, frame_url, true),
+ frame->document().getSecurityOrigin());
+
+ ScriptContext* context =
+ new ScriptContext(v8_context, frame, extension, context_type,
+ effective_extension, effective_context_type);
+ contexts_.insert(context); // takes ownership
+ return context;
+}
+
+void ScriptContextSet::Remove(ScriptContext* context) {
+ if (contexts_.erase(context)) {
+ context->Invalidate();
+ base::MessageLoop::current()->DeleteSoon(FROM_HERE, context);
+ }
+}
+
+ScriptContext* ScriptContextSet::GetCurrent() const {
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ return isolate->InContext() ? GetByV8Context(isolate->GetCurrentContext())
+ : nullptr;
+}
+
+ScriptContext* ScriptContextSet::GetByV8Context(
+ const v8::Local<v8::Context>& v8_context) const {
+ for (ScriptContext* script_context : contexts_) {
+ if (script_context->v8_context() == v8_context)
+ return script_context;
+ }
+ return nullptr;
+}
+
+ScriptContext* ScriptContextSet::GetContextByObject(
+ const v8::Local<v8::Object>& object) {
+ return GetContextByV8Context(object->CreationContext());
+}
+
+ScriptContext* ScriptContextSet::GetContextByV8Context(
+ const v8::Local<v8::Context>& v8_context) {
+ // g_context_set can be null in unittests.
+ return g_context_set ? g_context_set->GetByV8Context(v8_context) : nullptr;
+}
+
+void ScriptContextSet::ForEach(
+ const std::string& extension_id,
+ content::RenderFrame* render_frame,
+ const base::Callback<void(ScriptContext*)>& callback) const {
+ // We copy the context list, because calling into javascript may modify it
+ // out from under us.
+ std::set<ScriptContext*> contexts_copy = contexts_;
+
+ for (ScriptContext* context : contexts_copy) {
+ // For the same reason as above, contexts may become invalid while we run.
+ if (!context->is_valid())
+ continue;
+
+ if (!extension_id.empty()) {
+ const Extension* extension = context->extension();
+ if (!extension || (extension_id != extension->id()))
+ continue;
+ }
+
+ content::RenderFrame* context_render_frame = context->GetRenderFrame();
+ if (!context_render_frame)
+ continue;
+
+ if (render_frame && render_frame != context_render_frame)
+ continue;
+
+ callback.Run(context);
+ }
+}
+
+std::set<ScriptContext*> ScriptContextSet::OnExtensionUnloaded(
+ const std::string& extension_id) {
+ std::set<ScriptContext*> removed;
+ ForEach(extension_id, base::Bind(&ScriptContextSet::RecordAndRemove,
+ base::Unretained(this), &removed));
+ return removed;
+}
+
+const Extension* ScriptContextSet::GetExtensionFromFrameAndWorld(
+ const blink::WebLocalFrame* frame,
+ int world_id,
+ bool use_effective_url) {
+ std::string extension_id;
+ if (world_id != 0) {
+ // Isolated worlds (content script).
+ extension_id = ScriptInjection::GetHostIdForIsolatedWorld(world_id);
+ } else {
+ // Extension pages (chrome-extension:// URLs).
+ GURL frame_url = ScriptContext::GetDataSourceURLForFrame(frame);
+ frame_url = ScriptContext::GetEffectiveDocumentURL(frame, frame_url,
+ use_effective_url);
+ extension_id =
+ RendererExtensionRegistry::Get()->GetExtensionOrAppIDByURL(frame_url);
+ }
+
+ // There are conditions where despite a context being associated with an
+ // extension, no extension actually gets found. Ignore "invalid" because CSP
+ // blocks extension page loading by switching the extension ID to "invalid".
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(extension_id);
+ if (!extension && !extension_id.empty() && extension_id != "invalid") {
+ // TODO(kalman): Do something here?
+ }
+ return extension;
+}
+
+Feature::Context ScriptContextSet::ClassifyJavaScriptContext(
+ const Extension* extension,
+ int extension_group,
+ const GURL& url,
+ const blink::WebSecurityOrigin& origin) {
+ // WARNING: This logic must match ProcessMap::GetContextType, as much as
+ // possible.
+
+ DCHECK_GE(extension_group, 0);
+ if (extension_group == EXTENSION_GROUP_CONTENT_SCRIPTS) {
+ return extension ? // TODO(kalman): when does this happen?
+ Feature::CONTENT_SCRIPT_CONTEXT
+ : Feature::UNSPECIFIED_CONTEXT;
+ }
+
+ // We have an explicit check for sandboxed pages before checking whether the
+ // extension is active in this process because:
+ // 1. Sandboxed pages run in the same process as regular extension pages, so
+ // the extension is considered active.
+ // 2. ScriptContext creation (which triggers bindings injection) happens
+ // before the SecurityContext is updated with the sandbox flags (after
+ // reading the CSP header), so the caller can't check if the context's
+ // security origin is unique yet.
+ if (ScriptContext::IsSandboxedPage(url))
+ return Feature::WEB_PAGE_CONTEXT;
+
+ if (extension && active_extension_ids_->count(extension->id()) > 0) {
+ // |extension| is active in this process, but it could be either a true
+ // extension process or within the extent of a hosted app. In the latter
+ // case this would usually be considered a (blessed) web page context,
+ // unless the extension in question is a component extension, in which case
+ // we cheat and call it blessed.
+ return (extension->is_hosted_app() &&
+ extension->location() != Manifest::COMPONENT)
+ ? Feature::BLESSED_WEB_PAGE_CONTEXT
+ : Feature::BLESSED_EXTENSION_CONTEXT;
+ }
+
+ // TODO(kalman): This isUnique() check is wrong, it should be performed as
+ // part of ScriptContext::IsSandboxedPage().
+ if (!origin.isUnique() &&
+ RendererExtensionRegistry::Get()->ExtensionBindingsAllowed(url)) {
+ if (!extension) // TODO(kalman): when does this happen?
+ return Feature::UNSPECIFIED_CONTEXT;
+ return extension->is_hosted_app() ? Feature::BLESSED_WEB_PAGE_CONTEXT
+ : Feature::UNBLESSED_EXTENSION_CONTEXT;
+ }
+
+ if (!url.is_valid())
+ return Feature::UNSPECIFIED_CONTEXT;
+
+ if (url.SchemeIs(content::kChromeUIScheme))
+ return Feature::WEBUI_CONTEXT;
+
+ return Feature::WEB_PAGE_CONTEXT;
+}
+
+void ScriptContextSet::RecordAndRemove(std::set<ScriptContext*>* removed,
+ ScriptContext* context) {
+ removed->insert(context);
+ Remove(context); // Note: context deletion is deferred to the message loop.
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/script_context_set.h b/chromium/extensions/renderer/script_context_set.h
new file mode 100644
index 00000000000..355139747a4
--- /dev/null
+++ b/chromium/extensions/renderer/script_context_set.h
@@ -0,0 +1,145 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_SCRIPT_CONTEXT_SET_H_
+#define EXTENSIONS_RENDERER_SCRIPT_CONTEXT_SET_H_
+
+#include <stddef.h>
+
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/renderer/renderer_extension_registry.h"
+#include "url/gurl.h"
+#include "v8/include/v8.h"
+
+class GURL;
+
+namespace base {
+class ListValue;
+}
+
+namespace blink {
+class WebLocalFrame;
+class WebSecurityOrigin;
+}
+
+namespace content {
+class RenderFrame;
+}
+
+namespace extensions {
+class ScriptContext;
+
+// A container of ScriptContexts, responsible for both creating and managing
+// them.
+//
+// Since calling JavaScript within a context can cause any number of contexts
+// to be created or destroyed, this has additional smarts to help with the set
+// changing underneath callers.
+class ScriptContextSet {
+ public:
+ ScriptContextSet(
+ // Set of the IDs of extensions that are active in this process.
+ // Must outlive this. TODO(kalman): Combine this and |extensions|.
+ ExtensionIdSet* active_extension_ids);
+
+ ~ScriptContextSet();
+
+ // Returns the number of contexts being tracked by this set.
+ // This may also include invalid contexts. TODO(kalman): Useful?
+ size_t size() const { return contexts_.size(); }
+
+ // Creates and starts managing a new ScriptContext. Ownership is held.
+ // Returns a weak reference to the new ScriptContext.
+ ScriptContext* Register(blink::WebLocalFrame* frame,
+ const v8::Local<v8::Context>& v8_context,
+ int extension_group,
+ int world_id);
+
+ // If the specified context is contained in this set, remove it, then delete
+ // it asynchronously. After this call returns the context object will still
+ // be valid, but its frame() pointer will be cleared.
+ void Remove(ScriptContext* context);
+
+ // Gets the ScriptContext corresponding to v8::Context::GetCurrent(), or
+ // NULL if no such context exists.
+ ScriptContext* GetCurrent() const;
+
+ // Gets the ScriptContext corresponding to the specified
+ // v8::Context or NULL if no such context exists.
+ ScriptContext* GetByV8Context(const v8::Local<v8::Context>& context) const;
+ // Static equivalent of the above.
+ static ScriptContext* GetContextByV8Context(
+ const v8::Local<v8::Context>& context);
+
+ // Returns the ScriptContext corresponding to the V8 context that created the
+ // given |object|.
+ static ScriptContext* GetContextByObject(const v8::Local<v8::Object>& object);
+
+ // Synchronously runs |callback| with each ScriptContext that belongs to
+ // |extension_id| in |render_frame|.
+ //
+ // An empty |extension_id| will match all extensions, and a null
+ // |render_frame| will match all render views, but try to use the inline
+ // variants of these methods instead.
+ void ForEach(const std::string& extension_id,
+ content::RenderFrame* render_frame,
+ const base::Callback<void(ScriptContext*)>& callback) const;
+ // ForEach which matches all extensions.
+ void ForEach(content::RenderFrame* render_frame,
+ const base::Callback<void(ScriptContext*)>& callback) const {
+ ForEach(std::string(), render_frame, callback);
+ }
+ // ForEach which matches all render views.
+ void ForEach(const std::string& extension_id,
+ const base::Callback<void(ScriptContext*)>& callback) const {
+ ForEach(extension_id, nullptr, callback);
+ }
+
+ // Cleans up contexts belonging to an unloaded extension.
+ //
+ // Returns the set of ScriptContexts that were removed as a result. These
+ // are safe to interact with until the end of the current event loop, since
+ // they're deleted asynchronously.
+ std::set<ScriptContext*> OnExtensionUnloaded(const std::string& extension_id);
+
+ private:
+ // Finds the extension for the JavaScript context associated with the
+ // specified |frame| and isolated world. If |world_id| is zero, finds the
+ // extension ID associated with the main world's JavaScript context. If the
+ // JavaScript context isn't from an extension, returns empty string.
+ const Extension* GetExtensionFromFrameAndWorld(
+ const blink::WebLocalFrame* frame,
+ int world_id,
+ bool use_effective_url);
+
+ // Returns the Feature::Context type of context for a JavaScript context.
+ Feature::Context ClassifyJavaScriptContext(
+ const Extension* extension,
+ int extension_group,
+ const GURL& url,
+ const blink::WebSecurityOrigin& origin);
+
+ // Helper for OnExtensionUnloaded().
+ void RecordAndRemove(std::set<ScriptContext*>* removed,
+ ScriptContext* context);
+
+ // Weak reference to all installed Extensions that are also active in this
+ // process.
+ ExtensionIdSet* active_extension_ids_;
+
+ // The set of all ScriptContexts we own.
+ std::set<ScriptContext*> contexts_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScriptContextSet);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SCRIPT_CONTEXT_SET_H_
diff --git a/chromium/extensions/renderer/script_context_set_unittest.cc b/chromium/extensions/renderer/script_context_set_unittest.cc
new file mode 100644
index 00000000000..756c94290f5
--- /dev/null
+++ b/chromium/extensions/renderer/script_context_set_unittest.cc
@@ -0,0 +1,62 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <vector>
+
+#include "base/message_loop/message_loop.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/renderer/scoped_web_frame.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "gin/public/context_holder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/WebKit/public/web/WebFrame.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+TEST(ScriptContextSetTest, Lifecycle) {
+ base::MessageLoop loop;
+ ScopedWebFrame web_frame;
+
+ // Do this after construction of the webview, since it may construct the
+ // Isolate.
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Context> v8_context = v8::Context::New(isolate);
+ v8::Context::Scope context_scope(v8_context);
+ // ScriptContext relies on gin, it just doesn't look like it from here.
+ gin::ContextHolder context_holder(isolate);
+ context_holder.SetContext(v8_context);
+
+ ExtensionIdSet active_extensions;
+ ScriptContextSet context_set(&active_extensions);
+ ScriptContext* context = context_set.Register(
+ web_frame.frame(), v8_context, 0, 0); // no extension group or world ID
+
+ // Context is valid and resembles correctness.
+ EXPECT_TRUE(context->is_valid());
+ EXPECT_EQ(web_frame.frame(), context->web_frame());
+ EXPECT_EQ(v8_context, context->v8_context());
+
+ // Context has been correctly added.
+ EXPECT_EQ(1u, context_set.size());
+ EXPECT_EQ(context, context_set.GetByV8Context(v8_context));
+
+ // Test context is correctly removed.
+ context_set.Remove(context);
+ EXPECT_EQ(0u, context_set.size());
+ EXPECT_EQ(nullptr, context_set.GetByV8Context(v8_context));
+
+ // After removal, the context should be marked for destruction.
+ EXPECT_FALSE(context->is_valid());
+
+ // Run loop to do the actual deletion.
+ loop.RunUntilIdle();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/script_context_unittest.cc b/chromium/extensions/renderer/script_context_unittest.cc
new file mode 100644
index 00000000000..b0a846c6b66
--- /dev/null
+++ b/chromium/extensions/renderer/script_context_unittest.cc
@@ -0,0 +1,24 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/module_system_test.h"
+#include "extensions/renderer/script_context.h"
+#include "gin/per_context_data.h"
+#include "gin/runner.h"
+
+namespace extensions {
+
+using ScriptContextTest = ModuleSystemTest;
+
+TEST_F(ScriptContextTest, GinRunnerLifetime) {
+ ExpectNoAssertionsMade();
+ base::WeakPtr<gin::Runner> weak_runner =
+ gin::PerContextData::From(env()->context()->v8_context())
+ ->runner()
+ ->GetWeakPtr();
+ env()->ShutdownModuleSystem();
+ EXPECT_FALSE(weak_runner);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/script_injection.cc b/chromium/extensions/renderer/script_injection.cc
new file mode 100644
index 00000000000..70464b646c4
--- /dev/null
+++ b/chromium/extensions/renderer/script_injection.cc
@@ -0,0 +1,319 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/script_injection.h"
+
+#include <map>
+#include <utility>
+
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/metrics/histogram.h"
+#include "base/timer/elapsed_timer.h"
+#include "base/values.h"
+#include "content/public/child/v8_value_converter.h"
+#include "content/public/renderer/render_frame.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/host_id.h"
+#include "extensions/renderer/dom_activity_logger.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/extension_groups.h"
+#include "extensions/renderer/extensions_renderer_client.h"
+#include "extensions/renderer/script_injection_callback.h"
+#include "extensions/renderer/scripts_run_info.h"
+#include "third_party/WebKit/public/platform/WebSecurityOrigin.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebScriptSource.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+namespace {
+
+using IsolatedWorldMap = std::map<std::string, int>;
+base::LazyInstance<IsolatedWorldMap> g_isolated_worlds =
+ LAZY_INSTANCE_INITIALIZER;
+
+const int64_t kInvalidRequestId = -1;
+
+// The id of the next pending injection.
+int64_t g_next_pending_id = 0;
+
+// Gets the isolated world ID to use for the given |injection_host|
+// in the given |frame|. If no isolated world has been created for that
+// |injection_host| one will be created and initialized.
+int GetIsolatedWorldIdForInstance(const InjectionHost* injection_host,
+ blink::WebLocalFrame* frame) {
+ static int g_next_isolated_world_id =
+ ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId();
+
+ IsolatedWorldMap& isolated_worlds = g_isolated_worlds.Get();
+
+ int id = 0;
+ const std::string& key = injection_host->id().id();
+ IsolatedWorldMap::iterator iter = isolated_worlds.find(key);
+ if (iter != isolated_worlds.end()) {
+ id = iter->second;
+ } else {
+ id = g_next_isolated_world_id++;
+ // This map will tend to pile up over time, but realistically, you're never
+ // going to have enough injection hosts for it to matter.
+ isolated_worlds[key] = id;
+ }
+
+ // We need to set the isolated world origin and CSP even if it's not a new
+ // world since these are stored per frame, and we might not have used this
+ // isolated world in this frame before.
+ frame->setIsolatedWorldSecurityOrigin(
+ id, blink::WebSecurityOrigin::create(injection_host->url()));
+ frame->setIsolatedWorldContentSecurityPolicy(
+ id, blink::WebString::fromUTF8(
+ injection_host->GetContentSecurityPolicy()));
+ frame->setIsolatedWorldHumanReadableName(
+ id, blink::WebString::fromUTF8(injection_host->name()));
+
+ return id;
+}
+
+} // namespace
+
+// Watches for the deletion of a RenderFrame, after which is_valid will return
+// false.
+class ScriptInjection::FrameWatcher : public content::RenderFrameObserver {
+ public:
+ FrameWatcher(content::RenderFrame* render_frame,
+ ScriptInjection* injection)
+ : content::RenderFrameObserver(render_frame),
+ injection_(injection) {}
+ ~FrameWatcher() override {}
+
+ private:
+ void FrameDetached() override { injection_->invalidate_render_frame(); }
+ void OnDestruct() override { injection_->invalidate_render_frame(); }
+
+ ScriptInjection* injection_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameWatcher);
+};
+
+// static
+std::string ScriptInjection::GetHostIdForIsolatedWorld(int isolated_world_id) {
+ const IsolatedWorldMap& isolated_worlds = g_isolated_worlds.Get();
+
+ for (const auto& iter : isolated_worlds) {
+ if (iter.second == isolated_world_id)
+ return iter.first;
+ }
+ return std::string();
+}
+
+// static
+void ScriptInjection::RemoveIsolatedWorld(const std::string& host_id) {
+ g_isolated_worlds.Get().erase(host_id);
+}
+
+ScriptInjection::ScriptInjection(scoped_ptr<ScriptInjector> injector,
+ content::RenderFrame* render_frame,
+ scoped_ptr<const InjectionHost> injection_host,
+ UserScript::RunLocation run_location)
+ : injector_(std::move(injector)),
+ render_frame_(render_frame),
+ injection_host_(std::move(injection_host)),
+ run_location_(run_location),
+ request_id_(kInvalidRequestId),
+ complete_(false),
+ did_inject_js_(false),
+ frame_watcher_(new FrameWatcher(render_frame, this)),
+ weak_ptr_factory_(this) {
+ CHECK(injection_host_.get());
+}
+
+ScriptInjection::~ScriptInjection() {
+ if (!complete_)
+ NotifyWillNotInject(ScriptInjector::WONT_INJECT);
+}
+
+ScriptInjection::InjectionResult ScriptInjection::TryToInject(
+ UserScript::RunLocation current_location,
+ ScriptsRunInfo* scripts_run_info,
+ const CompletionCallback& async_completion_callback) {
+ if (current_location < run_location_)
+ return INJECTION_WAITING; // Wait for the right location.
+
+ if (request_id_ != kInvalidRequestId) {
+ // We're waiting for permission right now, try again later.
+ return INJECTION_WAITING;
+ }
+
+ if (!injection_host_) {
+ NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED);
+ return INJECTION_FINISHED; // We're done.
+ }
+
+ blink::WebLocalFrame* web_frame = render_frame_->GetWebFrame();
+ switch (injector_->CanExecuteOnFrame(
+ injection_host_.get(), web_frame,
+ ExtensionFrameHelper::Get(render_frame_)->tab_id())) {
+ case PermissionsData::ACCESS_DENIED:
+ NotifyWillNotInject(ScriptInjector::NOT_ALLOWED);
+ return INJECTION_FINISHED; // We're done.
+ case PermissionsData::ACCESS_WITHHELD:
+ RequestPermissionFromBrowser();
+ return INJECTION_WAITING; // Wait around for permission.
+ case PermissionsData::ACCESS_ALLOWED:
+ InjectionResult result = Inject(scripts_run_info);
+ // If the injection is blocked, we need to set the manager so we can
+ // notify it upon completion.
+ if (result == INJECTION_BLOCKED)
+ async_completion_callback_ = async_completion_callback;
+ return result;
+ }
+
+ NOTREACHED();
+ return INJECTION_FINISHED;
+}
+
+ScriptInjection::InjectionResult ScriptInjection::OnPermissionGranted(
+ ScriptsRunInfo* scripts_run_info) {
+ if (!injection_host_) {
+ NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED);
+ return INJECTION_FINISHED;
+ }
+
+ return Inject(scripts_run_info);
+}
+
+void ScriptInjection::OnHostRemoved() {
+ injection_host_.reset(nullptr);
+}
+
+void ScriptInjection::RequestPermissionFromBrowser() {
+ // If we are just notifying the browser of the injection, then send an
+ // invalid request (which is treated like a notification).
+ request_id_ = g_next_pending_id++;
+ render_frame_->Send(new ExtensionHostMsg_RequestScriptInjectionPermission(
+ render_frame_->GetRoutingID(), host_id().id(), injector_->script_type(),
+ run_location_, request_id_));
+}
+
+void ScriptInjection::NotifyWillNotInject(
+ ScriptInjector::InjectFailureReason reason) {
+ complete_ = true;
+ injector_->OnWillNotInject(reason, render_frame_);
+}
+
+ScriptInjection::InjectionResult ScriptInjection::Inject(
+ ScriptsRunInfo* scripts_run_info) {
+ DCHECK(injection_host_);
+ DCHECK(scripts_run_info);
+ DCHECK(!complete_);
+
+ bool should_inject_js = injector_->ShouldInjectJs(run_location_);
+ bool should_inject_css = injector_->ShouldInjectCss(run_location_);
+ DCHECK(should_inject_js || should_inject_css);
+
+ if (should_inject_js)
+ InjectJs();
+ if (should_inject_css)
+ InjectCss();
+
+ complete_ = did_inject_js_ || !should_inject_js;
+
+ injector_->GetRunInfo(scripts_run_info, run_location_);
+
+ if (complete_) {
+ injector_->OnInjectionComplete(std::move(execution_result_), run_location_,
+ render_frame_);
+ } else {
+ ++scripts_run_info->num_blocking_js;
+ }
+
+ return complete_ ? INJECTION_FINISHED : INJECTION_BLOCKED;
+}
+
+void ScriptInjection::InjectJs() {
+ DCHECK(!did_inject_js_);
+ blink::WebLocalFrame* web_frame = render_frame_->GetWebFrame();
+ std::vector<blink::WebScriptSource> sources =
+ injector_->GetJsSources(run_location_);
+ bool in_main_world = injector_->ShouldExecuteInMainWorld();
+ int world_id = in_main_world
+ ? DOMActivityLogger::kMainWorldId
+ : GetIsolatedWorldIdForInstance(injection_host_.get(),
+ web_frame);
+ bool is_user_gesture = injector_->IsUserGesture();
+
+ scoped_ptr<blink::WebScriptExecutionCallback> callback(
+ new ScriptInjectionCallback(
+ base::Bind(&ScriptInjection::OnJsInjectionCompleted,
+ weak_ptr_factory_.GetWeakPtr())));
+
+ base::ElapsedTimer exec_timer;
+ if (injection_host_->id().type() == HostID::EXTENSIONS)
+ DOMActivityLogger::AttachToWorld(world_id, injection_host_->id().id());
+ if (in_main_world) {
+ // We only inject in the main world for javascript: urls.
+ DCHECK_EQ(1u, sources.size());
+
+ web_frame->requestExecuteScriptAndReturnValue(sources.front(),
+ is_user_gesture,
+ callback.release());
+ } else {
+ web_frame->requestExecuteScriptInIsolatedWorld(
+ world_id,
+ &sources.front(),
+ sources.size(),
+ EXTENSION_GROUP_CONTENT_SCRIPTS,
+ is_user_gesture,
+ callback.release());
+ }
+
+ if (injection_host_->id().type() == HostID::EXTENSIONS)
+ UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer.Elapsed());
+}
+
+void ScriptInjection::OnJsInjectionCompleted(
+ const blink::WebVector<v8::Local<v8::Value> >& results) {
+ DCHECK(!did_inject_js_);
+
+ bool expects_results = injector_->ExpectsResults();
+ if (expects_results) {
+ if (!results.isEmpty() && !results[0].IsEmpty()) {
+ // Right now, we only support returning single results (per frame).
+ scoped_ptr<content::V8ValueConverter> v8_converter(
+ content::V8ValueConverter::create());
+ // It's safe to always use the main world context when converting
+ // here. V8ValueConverterImpl shouldn't actually care about the
+ // context scope, and it switches to v8::Object's creation context
+ // when encountered.
+ v8::Local<v8::Context> context =
+ render_frame_->GetWebFrame()->mainWorldScriptContext();
+ execution_result_.reset(v8_converter->FromV8Value(results[0], context));
+ }
+ if (!execution_result_.get())
+ execution_result_ = base::Value::CreateNullValue();
+ }
+ did_inject_js_ = true;
+
+ // If |async_completion_callback_| is set, it means the script finished
+ // asynchronously, and we should run it.
+ if (!async_completion_callback_.is_null()) {
+ injector_->OnInjectionComplete(std::move(execution_result_), run_location_,
+ render_frame_);
+ // Warning: this object can be destroyed after this line!
+ async_completion_callback_.Run(this);
+ }
+}
+
+void ScriptInjection::InjectCss() {
+ std::vector<std::string> css_sources =
+ injector_->GetCssSources(run_location_);
+ blink::WebLocalFrame* web_frame = render_frame_->GetWebFrame();
+ for (const std::string& css : css_sources)
+ web_frame->document().insertStyleSheet(blink::WebString::fromUTF8(css));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/script_injection.h b/chromium/extensions/renderer/script_injection.h
new file mode 100644
index 00000000000..6b3679573c3
--- /dev/null
+++ b/chromium/extensions/renderer/script_injection.h
@@ -0,0 +1,150 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_SCRIPT_INJECTION_H_
+#define EXTENSIONS_RENDERER_SCRIPT_INJECTION_H_
+
+#include <stdint.h>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "extensions/common/user_script.h"
+#include "extensions/renderer/injection_host.h"
+#include "extensions/renderer/script_injector.h"
+
+struct HostID;
+
+namespace blink {
+template<typename T> class WebVector;
+}
+
+namespace content {
+class RenderFrame;
+}
+
+namespace v8 {
+class Value;
+template <class T> class Local;
+}
+
+namespace extensions {
+struct ScriptsRunInfo;
+
+// A script wrapper which is aware of whether or not it is allowed to execute,
+// and contains the implementation to do so.
+class ScriptInjection {
+ public:
+ enum InjectionResult {
+ INJECTION_FINISHED,
+ INJECTION_BLOCKED,
+ INJECTION_WAITING
+ };
+
+ using CompletionCallback = base::Callback<void(ScriptInjection*)>;
+
+ // Return the id of the injection host associated with the given world.
+ static std::string GetHostIdForIsolatedWorld(int world_id);
+
+ // Remove the isolated world associated with the given injection host.
+ static void RemoveIsolatedWorld(const std::string& host_id);
+
+ ScriptInjection(scoped_ptr<ScriptInjector> injector,
+ content::RenderFrame* render_frame,
+ scoped_ptr<const InjectionHost> injection_host,
+ UserScript::RunLocation run_location);
+ ~ScriptInjection();
+
+ // Try to inject the script at the |current_location|. This returns
+ // INJECTION_FINISHED if injection has injected or will never inject, returns
+ // INJECTION_BLOCKED if injection is running asynchronously and has not
+ // finished yet, returns INJECTION_WAITING if injections is delayed (either
+ // for permission purposes or because |current_location| is not the designated
+ // |run_location_|).
+ // If INJECTION_BLOCKED is returned, |async_completion_callback| will be
+ // called upon completion.
+ InjectionResult TryToInject(
+ UserScript::RunLocation current_location,
+ ScriptsRunInfo* scripts_run_info,
+ const CompletionCallback& async_completion_callback);
+
+ // Called when permission for the given injection has been granted.
+ // Returns INJECTION_FINISHED if injection has injected or will never inject,
+ // returns INJECTION_BLOCKED if injection is ran asynchronously.
+ InjectionResult OnPermissionGranted(ScriptsRunInfo* scripts_run_info);
+
+ // Resets the pointer of the injection host when the host is gone.
+ void OnHostRemoved();
+
+ void invalidate_render_frame() { render_frame_ = nullptr; }
+
+ // Accessors.
+ content::RenderFrame* render_frame() const { return render_frame_; }
+ const HostID& host_id() const { return injection_host_->id(); }
+ int64_t request_id() const { return request_id_; }
+
+ private:
+ class FrameWatcher;
+
+ // Sends a message to the browser to request permission to inject.
+ void RequestPermissionFromBrowser();
+
+ // Injects the script. Returns INJECTION_FINISHED if injection has finished,
+ // otherwise INJECTION_BLOCKED.
+ InjectionResult Inject(ScriptsRunInfo* scripts_run_info);
+
+ // Inject any JS scripts into the frame for the injection.
+ void InjectJs();
+
+ // Called when JS injection for the given frame has been completed.
+ void OnJsInjectionCompleted(
+ const blink::WebVector<v8::Local<v8::Value> >& results);
+
+ // Inject any CSS source into the frame for the injection.
+ void InjectCss();
+
+ // Notify that we will not inject, and mark it as acknowledged.
+ void NotifyWillNotInject(ScriptInjector::InjectFailureReason reason);
+
+ // The injector for this injection.
+ scoped_ptr<ScriptInjector> injector_;
+
+ // The RenderFrame into which this should inject the script.
+ content::RenderFrame* render_frame_;
+
+ // The associated injection host.
+ scoped_ptr<const InjectionHost> injection_host_;
+
+ // The location in the document load at which we inject the script.
+ UserScript::RunLocation run_location_;
+
+ // This injection's request id. This will be -1 unless the injection is
+ // currently waiting on permission.
+ int64_t request_id_;
+
+ // Whether or not the injection is complete, either via injecting the script
+ // or because it will never complete.
+ bool complete_;
+
+ // Whether or not the injection successfully injected JS.
+ bool did_inject_js_;
+
+ // Results storage.
+ scoped_ptr<base::Value> execution_result_;
+
+ // The callback to run upon completing asynchronously.
+ CompletionCallback async_completion_callback_;
+
+ // A helper class to hold the render frame and watch for its deletion.
+ scoped_ptr<FrameWatcher> frame_watcher_;
+
+ base::WeakPtrFactory<ScriptInjection> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScriptInjection);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SCRIPT_INJECTION_H_
diff --git a/chromium/extensions/renderer/script_injection_callback.cc b/chromium/extensions/renderer/script_injection_callback.cc
new file mode 100644
index 00000000000..8b911d1c28c
--- /dev/null
+++ b/chromium/extensions/renderer/script_injection_callback.cc
@@ -0,0 +1,23 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/script_injection_callback.h"
+
+namespace extensions {
+
+ScriptInjectionCallback::ScriptInjectionCallback(
+ const CompleteCallback& injection_completed_callback)
+ : injection_completed_callback_(injection_completed_callback) {
+}
+
+ScriptInjectionCallback::~ScriptInjectionCallback() {
+}
+
+void ScriptInjectionCallback::completed(
+ const blink::WebVector<v8::Local<v8::Value> >& result) {
+ injection_completed_callback_.Run(result);
+ delete this;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/script_injection_callback.h b/chromium/extensions/renderer/script_injection_callback.h
new file mode 100644
index 00000000000..14819277cbd
--- /dev/null
+++ b/chromium/extensions/renderer/script_injection_callback.h
@@ -0,0 +1,42 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_SCRIPT_INJECTION_CALLBACK_H_
+#define EXTENSIONS_RENDERER_SCRIPT_INJECTION_CALLBACK_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "third_party/WebKit/public/web/WebScriptExecutionCallback.h"
+#include "v8/include/v8.h"
+
+namespace blink {
+template<typename T> class WebVector;
+}
+
+namespace extensions {
+
+// A wrapper around a callback to notify a script injection when injection
+// completes.
+// This class manages its own lifetime.
+class ScriptInjectionCallback : public blink::WebScriptExecutionCallback {
+ public:
+ using CompleteCallback =
+ base::Callback<void(
+ const blink::WebVector<v8::Local<v8::Value>>& result)>;
+
+ ScriptInjectionCallback(const CompleteCallback& injection_completed_callback);
+ ~ScriptInjectionCallback() override;
+
+ void completed(
+ const blink::WebVector<v8::Local<v8::Value> >& result) override;
+
+ private:
+ CompleteCallback injection_completed_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScriptInjectionCallback);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SCRIPT_INJECTION_CALLBACK_H_
diff --git a/chromium/extensions/renderer/script_injection_manager.cc b/chromium/extensions/renderer/script_injection_manager.cc
new file mode 100644
index 00000000000..0e234d0906b
--- /dev/null
+++ b/chromium/extensions/renderer/script_injection_manager.cc
@@ -0,0 +1,514 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/script_injection_manager.h"
+
+#include <utility>
+
+#include "base/auto_reset.h"
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/extension_injection_host.h"
+#include "extensions/renderer/programmatic_script_injector.h"
+#include "extensions/renderer/renderer_extension_registry.h"
+#include "extensions/renderer/script_injection.h"
+#include "extensions/renderer/scripts_run_info.h"
+#include "extensions/renderer/web_ui_injection_host.h"
+#include "ipc/ipc_message_macros.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebFrame.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebView.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+namespace {
+
+// The length of time to wait after the DOM is complete to try and run user
+// scripts.
+const int kScriptIdleTimeoutInMs = 200;
+
+// Returns the RunLocation that follows |run_location|.
+UserScript::RunLocation NextRunLocation(UserScript::RunLocation run_location) {
+ switch (run_location) {
+ case UserScript::DOCUMENT_START:
+ return UserScript::DOCUMENT_END;
+ case UserScript::DOCUMENT_END:
+ return UserScript::DOCUMENT_IDLE;
+ case UserScript::DOCUMENT_IDLE:
+ return UserScript::RUN_LOCATION_LAST;
+ case UserScript::UNDEFINED:
+ case UserScript::RUN_DEFERRED:
+ case UserScript::BROWSER_DRIVEN:
+ case UserScript::RUN_LOCATION_LAST:
+ break;
+ }
+ NOTREACHED();
+ return UserScript::RUN_LOCATION_LAST;
+}
+
+} // namespace
+
+class ScriptInjectionManager::RFOHelper : public content::RenderFrameObserver {
+ public:
+ RFOHelper(content::RenderFrame* render_frame,
+ ScriptInjectionManager* manager);
+ ~RFOHelper() override;
+
+ private:
+ // RenderFrameObserver implementation.
+ bool OnMessageReceived(const IPC::Message& message) override;
+ void DidCreateNewDocument() override;
+ void DidCreateDocumentElement() override;
+ void DidFailProvisionalLoad(const blink::WebURLError& error) override;
+ void DidFinishDocumentLoad() override;
+ void DidFinishLoad() override;
+ void FrameDetached() override;
+ void OnDestruct() override;
+
+ virtual void OnExecuteCode(const ExtensionMsg_ExecuteCode_Params& params);
+ virtual void OnExecuteDeclarativeScript(int tab_id,
+ const ExtensionId& extension_id,
+ int script_id,
+ const GURL& url);
+ virtual void OnPermitScriptInjection(int64_t request_id);
+
+ // Tells the ScriptInjectionManager to run tasks associated with
+ // document_idle.
+ void RunIdle();
+
+ void StartInjectScripts(UserScript::RunLocation run_location);
+
+ // Indicate that the frame is no longer valid because it is starting
+ // a new load or closing.
+ void InvalidateAndResetFrame();
+
+ // The owning ScriptInjectionManager.
+ ScriptInjectionManager* manager_;
+
+ bool should_run_idle_;
+
+ base::WeakPtrFactory<RFOHelper> weak_factory_;
+};
+
+ScriptInjectionManager::RFOHelper::RFOHelper(content::RenderFrame* render_frame,
+ ScriptInjectionManager* manager)
+ : content::RenderFrameObserver(render_frame),
+ manager_(manager),
+ should_run_idle_(true),
+ weak_factory_(this) {
+}
+
+ScriptInjectionManager::RFOHelper::~RFOHelper() {
+}
+
+bool ScriptInjectionManager::RFOHelper::OnMessageReceived(
+ const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(ScriptInjectionManager::RFOHelper, message)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteCode, OnExecuteCode)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_PermitScriptInjection,
+ OnPermitScriptInjection)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteDeclarativeScript,
+ OnExecuteDeclarativeScript)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void ScriptInjectionManager::RFOHelper::DidCreateNewDocument() {
+ // A new document is going to be shown, so invalidate the old document state.
+ // Check that the frame's state is known before invalidating the frame,
+ // because it is possible that a script injection was scheduled before the
+ // page was loaded, e.g. by navigating to a javascript: URL before the page
+ // has loaded.
+ if (manager_->frame_statuses_.count(render_frame()) != 0)
+ InvalidateAndResetFrame();
+}
+
+void ScriptInjectionManager::RFOHelper::DidCreateDocumentElement() {
+ ExtensionFrameHelper::Get(render_frame())
+ ->ScheduleAtDocumentStart(
+ base::Bind(&ScriptInjectionManager::RFOHelper::StartInjectScripts,
+ weak_factory_.GetWeakPtr(), UserScript::DOCUMENT_START));
+}
+
+void ScriptInjectionManager::RFOHelper::DidFailProvisionalLoad(
+ const blink::WebURLError& error) {
+ FrameStatusMap::iterator it = manager_->frame_statuses_.find(render_frame());
+ if (it != manager_->frame_statuses_.end() &&
+ it->second == UserScript::DOCUMENT_START) {
+ // Since the provisional load failed, the frame stays at its previous loaded
+ // state and origin (or the parent's origin for new/about:blank frames).
+ // Reset the frame to DOCUMENT_IDLE in order to reflect that the frame is
+ // done loading, and avoid any deadlock in the system.
+ //
+ // We skip injection of DOCUMENT_END and DOCUMENT_IDLE scripts, because the
+ // injections closely follow the DOMContentLoaded (and onload) events, which
+ // are not triggered after a failed provisional load.
+ // This assumption is verified in the checkDOMContentLoadedEvent subtest of
+ // ExecuteScriptApiTest.FrameWithHttp204 (browser_tests).
+ InvalidateAndResetFrame();
+ should_run_idle_ = false;
+ manager_->frame_statuses_[render_frame()] = UserScript::DOCUMENT_IDLE;
+ }
+}
+
+void ScriptInjectionManager::RFOHelper::DidFinishDocumentLoad() {
+ DCHECK(content::RenderThread::Get());
+ ExtensionFrameHelper::Get(render_frame())
+ ->ScheduleAtDocumentEnd(
+ base::Bind(&ScriptInjectionManager::RFOHelper::StartInjectScripts,
+ weak_factory_.GetWeakPtr(), UserScript::DOCUMENT_END));
+
+ // We try to run idle in two places: here and DidFinishLoad.
+ // DidFinishDocumentLoad() corresponds to completing the document's load,
+ // whereas DidFinishLoad corresponds to completing the document and all
+ // subresources' load. We don't want to hold up script injection for a
+ // particularly slow subresource, so we set a delayed task from here - but if
+ // we finish everything before that point (i.e., DidFinishLoad() is
+ // triggered), then there's no reason to keep waiting.
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ScriptInjectionManager::RFOHelper::RunIdle,
+ weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(kScriptIdleTimeoutInMs));
+}
+
+void ScriptInjectionManager::RFOHelper::DidFinishLoad() {
+ DCHECK(content::RenderThread::Get());
+ // Ensure that we don't block any UI progress by running scripts.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&ScriptInjectionManager::RFOHelper::RunIdle,
+ weak_factory_.GetWeakPtr()));
+}
+
+void ScriptInjectionManager::RFOHelper::FrameDetached() {
+ // The frame is closing - invalidate.
+ InvalidateAndResetFrame();
+}
+
+void ScriptInjectionManager::RFOHelper::OnDestruct() {
+ manager_->RemoveObserver(this);
+}
+
+void ScriptInjectionManager::RFOHelper::OnExecuteCode(
+ const ExtensionMsg_ExecuteCode_Params& params) {
+ manager_->HandleExecuteCode(params, render_frame());
+}
+
+void ScriptInjectionManager::RFOHelper::OnExecuteDeclarativeScript(
+ int tab_id,
+ const ExtensionId& extension_id,
+ int script_id,
+ const GURL& url) {
+ // TODO(markdittmer): URL-checking isn't the best security measure.
+ // Begin script injection workflow only if the current URL is identical to
+ // the one that matched declarative conditions in the browser.
+ if (render_frame()->GetWebFrame()->document().url() == url) {
+ manager_->HandleExecuteDeclarativeScript(render_frame(),
+ tab_id,
+ extension_id,
+ script_id,
+ url);
+ }
+}
+
+void ScriptInjectionManager::RFOHelper::OnPermitScriptInjection(
+ int64_t request_id) {
+ manager_->HandlePermitScriptInjection(request_id);
+}
+
+void ScriptInjectionManager::RFOHelper::RunIdle() {
+ // Only notify the manager if the frame hasn't either been removed or already
+ // had idle run since the task to RunIdle() was posted.
+ if (should_run_idle_) {
+ should_run_idle_ = false;
+ manager_->StartInjectScripts(render_frame(), UserScript::DOCUMENT_IDLE);
+ }
+}
+
+void ScriptInjectionManager::RFOHelper::StartInjectScripts(
+ UserScript::RunLocation run_location) {
+ manager_->StartInjectScripts(render_frame(), run_location);
+}
+
+void ScriptInjectionManager::RFOHelper::InvalidateAndResetFrame() {
+ // Invalidate any pending idle injections, and reset the frame inject on idle.
+ weak_factory_.InvalidateWeakPtrs();
+ // We reset to inject on idle, because the frame can be reused (in the case of
+ // navigation).
+ should_run_idle_ = true;
+ manager_->InvalidateForFrame(render_frame());
+}
+
+ScriptInjectionManager::ScriptInjectionManager(
+ UserScriptSetManager* user_script_set_manager)
+ : user_script_set_manager_(user_script_set_manager),
+ user_script_set_manager_observer_(this) {
+ user_script_set_manager_observer_.Add(user_script_set_manager_);
+}
+
+ScriptInjectionManager::~ScriptInjectionManager() {
+ for (const auto& injection : pending_injections_)
+ injection->invalidate_render_frame();
+ for (const auto& injection : running_injections_)
+ injection->invalidate_render_frame();
+}
+
+void ScriptInjectionManager::OnRenderFrameCreated(
+ content::RenderFrame* render_frame) {
+ rfo_helpers_.push_back(make_scoped_ptr(new RFOHelper(render_frame, this)));
+}
+
+void ScriptInjectionManager::OnExtensionUnloaded(
+ const std::string& extension_id) {
+ for (auto iter = pending_injections_.begin();
+ iter != pending_injections_.end();) {
+ if ((*iter)->host_id().id() == extension_id) {
+ (*iter)->OnHostRemoved();
+ iter = pending_injections_.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+}
+
+void ScriptInjectionManager::OnInjectionFinished(
+ ScriptInjection* injection) {
+ auto iter =
+ std::find_if(running_injections_.begin(), running_injections_.end(),
+ [injection](const scoped_ptr<ScriptInjection>& mode) {
+ return injection == mode.get();
+ });
+ if (iter != running_injections_.end())
+ running_injections_.erase(iter);
+}
+
+void ScriptInjectionManager::OnUserScriptsUpdated(
+ const std::set<HostID>& changed_hosts,
+ const std::vector<UserScript*>& scripts) {
+ for (auto iter = pending_injections_.begin();
+ iter != pending_injections_.end();) {
+ if (changed_hosts.count((*iter)->host_id()) > 0)
+ iter = pending_injections_.erase(iter);
+ else
+ ++iter;
+ }
+}
+
+void ScriptInjectionManager::RemoveObserver(RFOHelper* helper) {
+ for (auto iter = rfo_helpers_.begin(); iter != rfo_helpers_.end(); ++iter) {
+ if (iter->get() == helper) {
+ rfo_helpers_.erase(iter);
+ break;
+ }
+ }
+}
+
+void ScriptInjectionManager::InvalidateForFrame(content::RenderFrame* frame) {
+ // If the frame invalidated is the frame being injected into, we need to
+ // note it.
+ active_injection_frames_.erase(frame);
+
+ for (auto iter = pending_injections_.begin();
+ iter != pending_injections_.end();) {
+ if ((*iter)->render_frame() == frame)
+ iter = pending_injections_.erase(iter);
+ else
+ ++iter;
+ }
+
+ frame_statuses_.erase(frame);
+}
+
+void ScriptInjectionManager::StartInjectScripts(
+ content::RenderFrame* frame,
+ UserScript::RunLocation run_location) {
+ FrameStatusMap::iterator iter = frame_statuses_.find(frame);
+ // We also don't execute if we detect that the run location is somehow out of
+ // order. This can happen if:
+ // - The first run location reported for the frame isn't DOCUMENT_START, or
+ // - The run location reported doesn't immediately follow the previous
+ // reported run location.
+ // We don't want to run because extensions may have requirements that scripts
+ // running in an earlier run location have run by the time a later script
+ // runs. Better to just not run.
+ // Note that we check run_location > NextRunLocation() in the second clause
+ // (as opposed to !=) because earlier signals (like DidCreateDocumentElement)
+ // can happen multiple times, so we can receive earlier/equal run locations.
+ if ((iter == frame_statuses_.end() &&
+ run_location != UserScript::DOCUMENT_START) ||
+ (iter != frame_statuses_.end() &&
+ run_location > NextRunLocation(iter->second))) {
+ // We also invalidate the frame, because the run order of pending injections
+ // may also be bad.
+ InvalidateForFrame(frame);
+ return;
+ } else if (iter != frame_statuses_.end() && iter->second >= run_location) {
+ // Certain run location signals (like DidCreateDocumentElement) can happen
+ // multiple times. Ignore the subsequent signals.
+ return;
+ }
+
+ // Otherwise, all is right in the world, and we can get on with the
+ // injections!
+ frame_statuses_[frame] = run_location;
+ InjectScripts(frame, run_location);
+}
+
+void ScriptInjectionManager::InjectScripts(
+ content::RenderFrame* frame,
+ UserScript::RunLocation run_location) {
+ // Find any injections that want to run on the given frame.
+ ScriptInjectionVector frame_injections;
+ for (auto iter = pending_injections_.begin();
+ iter != pending_injections_.end();) {
+ if ((*iter)->render_frame() == frame) {
+ frame_injections.push_back(std::move(*iter));
+ iter = pending_injections_.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+
+ // Add any injections for user scripts.
+ int tab_id = ExtensionFrameHelper::Get(frame)->tab_id();
+ user_script_set_manager_->GetAllInjections(&frame_injections, frame, tab_id,
+ run_location);
+
+ // Note that we are running in |frame|.
+ active_injection_frames_.insert(frame);
+
+ ScriptsRunInfo scripts_run_info(frame, run_location);
+ for (auto iter = frame_injections.begin(); iter != frame_injections.end();) {
+ // It's possible for the frame to be invalidated in the course of injection
+ // (if a script removes its own frame, for example). If this happens, abort.
+ if (!active_injection_frames_.count(frame))
+ break;
+ scoped_ptr<ScriptInjection> injection(std::move(*iter));
+ iter = frame_injections.erase(iter);
+ TryToInject(std::move(injection), run_location, &scripts_run_info);
+ }
+
+ // We are done running in the frame.
+ active_injection_frames_.erase(frame);
+
+ scripts_run_info.LogRun();
+}
+
+void ScriptInjectionManager::TryToInject(
+ scoped_ptr<ScriptInjection> injection,
+ UserScript::RunLocation run_location,
+ ScriptsRunInfo* scripts_run_info) {
+ // Try to inject the script. If the injection is waiting (i.e., for
+ // permission), add it to the list of pending injections. If the injection
+ // has blocked, add it to the list of running injections.
+ // The Unretained below is safe because this object owns all the
+ // ScriptInjections, so is guaranteed to outlive them.
+ switch (injection->TryToInject(
+ run_location,
+ scripts_run_info,
+ base::Bind(&ScriptInjectionManager::OnInjectionFinished,
+ base::Unretained(this)))) {
+ case ScriptInjection::INJECTION_WAITING:
+ pending_injections_.push_back(std::move(injection));
+ break;
+ case ScriptInjection::INJECTION_BLOCKED:
+ running_injections_.push_back(std::move(injection));
+ break;
+ case ScriptInjection::INJECTION_FINISHED:
+ break;
+ }
+}
+
+void ScriptInjectionManager::HandleExecuteCode(
+ const ExtensionMsg_ExecuteCode_Params& params,
+ content::RenderFrame* render_frame) {
+ scoped_ptr<const InjectionHost> injection_host;
+ if (params.host_id.type() == HostID::EXTENSIONS) {
+ injection_host = ExtensionInjectionHost::Create(params.host_id.id());
+ if (!injection_host)
+ return;
+ } else if (params.host_id.type() == HostID::WEBUI) {
+ injection_host.reset(
+ new WebUIInjectionHost(params.host_id));
+ }
+
+ scoped_ptr<ScriptInjection> injection(new ScriptInjection(
+ scoped_ptr<ScriptInjector>(
+ new ProgrammaticScriptInjector(params, render_frame)),
+ render_frame, std::move(injection_host),
+ static_cast<UserScript::RunLocation>(params.run_at)));
+
+ FrameStatusMap::const_iterator iter = frame_statuses_.find(render_frame);
+ UserScript::RunLocation run_location =
+ iter == frame_statuses_.end() ? UserScript::UNDEFINED : iter->second;
+
+ ScriptsRunInfo scripts_run_info(render_frame, run_location);
+ TryToInject(std::move(injection), run_location, &scripts_run_info);
+}
+
+void ScriptInjectionManager::HandleExecuteDeclarativeScript(
+ content::RenderFrame* render_frame,
+ int tab_id,
+ const ExtensionId& extension_id,
+ int script_id,
+ const GURL& url) {
+ scoped_ptr<ScriptInjection> injection =
+ user_script_set_manager_->GetInjectionForDeclarativeScript(
+ script_id,
+ render_frame,
+ tab_id,
+ url,
+ extension_id);
+ if (injection.get()) {
+ ScriptsRunInfo scripts_run_info(render_frame, UserScript::BROWSER_DRIVEN);
+ // TODO(markdittmer): Use return value of TryToInject for error handling.
+ TryToInject(std::move(injection), UserScript::BROWSER_DRIVEN,
+ &scripts_run_info);
+
+ scripts_run_info.LogRun();
+ }
+}
+
+void ScriptInjectionManager::HandlePermitScriptInjection(int64_t request_id) {
+ auto iter = pending_injections_.begin();
+ for (; iter != pending_injections_.end(); ++iter) {
+ if ((*iter)->request_id() == request_id) {
+ DCHECK((*iter)->host_id().type() == HostID::EXTENSIONS);
+ break;
+ }
+ }
+ if (iter == pending_injections_.end())
+ return;
+
+ // At this point, because the request is present in pending_injections_, we
+ // know that this is the same page that issued the request (otherwise,
+ // RFOHelper::InvalidateAndResetFrame would have caused it to be cleared out).
+
+ scoped_ptr<ScriptInjection> injection(std::move(*iter));
+ pending_injections_.erase(iter);
+
+ ScriptsRunInfo scripts_run_info(injection->render_frame(),
+ UserScript::RUN_DEFERRED);
+ ScriptInjection::InjectionResult res = injection->OnPermissionGranted(
+ &scripts_run_info);
+ if (res == ScriptInjection::INJECTION_BLOCKED)
+ running_injections_.push_back(std::move(injection));
+ scripts_run_info.LogRun();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/script_injection_manager.h b/chromium/extensions/renderer/script_injection_manager.h
new file mode 100644
index 00000000000..c0f5da82cce
--- /dev/null
+++ b/chromium/extensions/renderer/script_injection_manager.h
@@ -0,0 +1,126 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_SCRIPT_INJECTION_MANAGER_H_
+#define EXTENSIONS_RENDERER_SCRIPT_INJECTION_MANAGER_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/scoped_observer.h"
+#include "extensions/common/user_script.h"
+#include "extensions/renderer/script_injection.h"
+#include "extensions/renderer/user_script_set_manager.h"
+
+struct ExtensionMsg_ExecuteCode_Params;
+
+namespace content {
+class RenderFrame;
+}
+
+namespace extensions {
+class Extension;
+
+// The ScriptInjectionManager manages extensions injecting scripts into frames
+// via both content/user scripts and tabs.executeScript(). It is responsible for
+// maintaining any pending injections awaiting permission or the appropriate
+// load point, and injecting them when ready.
+class ScriptInjectionManager : public UserScriptSetManager::Observer {
+ public:
+ explicit ScriptInjectionManager(
+ UserScriptSetManager* user_script_set_manager);
+ virtual ~ScriptInjectionManager();
+
+ // Notifies that a new render view has been created.
+ void OnRenderFrameCreated(content::RenderFrame* render_frame);
+
+ // Removes pending injections of the unloaded extension.
+ void OnExtensionUnloaded(const std::string& extension_id);
+
+ private:
+ // A RenderFrameObserver implementation which watches the various render
+ // frames in order to notify the ScriptInjectionManager of different
+ // document load states and IPCs.
+ class RFOHelper;
+
+ using FrameStatusMap =
+ std::map<content::RenderFrame*, UserScript::RunLocation>;
+
+ using ScriptInjectionVector = std::vector<scoped_ptr<ScriptInjection>>;
+
+ // Notifies that an injection has been finished.
+ void OnInjectionFinished(ScriptInjection* injection);
+
+ // UserScriptSetManager::Observer implementation.
+ void OnUserScriptsUpdated(const std::set<HostID>& changed_hosts,
+ const std::vector<UserScript*>& scripts) override;
+
+ // Notifies that an RFOHelper should be removed.
+ void RemoveObserver(RFOHelper* helper);
+
+ // Invalidate any pending tasks associated with |frame|.
+ void InvalidateForFrame(content::RenderFrame* frame);
+
+ // Starts the process to inject appropriate scripts into |frame|.
+ void StartInjectScripts(content::RenderFrame* frame,
+ UserScript::RunLocation run_location);
+
+ // Actually injects the scripts into |frame|.
+ void InjectScripts(content::RenderFrame* frame,
+ UserScript::RunLocation run_location);
+
+ // Try to inject and store injection if it has not finished.
+ void TryToInject(scoped_ptr<ScriptInjection> injection,
+ UserScript::RunLocation run_location,
+ ScriptsRunInfo* scripts_run_info);
+
+ // Handle the ExecuteCode extension message.
+ void HandleExecuteCode(const ExtensionMsg_ExecuteCode_Params& params,
+ content::RenderFrame* render_frame);
+
+ // Handle the ExecuteDeclarativeScript extension message.
+ void HandleExecuteDeclarativeScript(content::RenderFrame* web_frame,
+ int tab_id,
+ const ExtensionId& extension_id,
+ int script_id,
+ const GURL& url);
+
+ // Handle the GrantInjectionPermission extension message.
+ void HandlePermitScriptInjection(int64_t request_id);
+
+ // The map of active web frames to their corresponding statuses. The
+ // RunLocation of the frame corresponds to the last location that has ran.
+ FrameStatusMap frame_statuses_;
+
+ // The frames currently being injected into, so long as that frame is valid.
+ std::set<content::RenderFrame*> active_injection_frames_;
+
+ // The collection of RFOHelpers.
+ std::vector<scoped_ptr<RFOHelper>> rfo_helpers_;
+
+ // The set of UserScripts associated with extensions. Owned by the Dispatcher.
+ UserScriptSetManager* user_script_set_manager_;
+
+ // Pending injections which are waiting for either the proper run location or
+ // user consent.
+ ScriptInjectionVector pending_injections_;
+
+ // Running injections which are waiting for async callbacks from blink.
+ ScriptInjectionVector running_injections_;
+
+ ScopedObserver<UserScriptSetManager, UserScriptSetManager::Observer>
+ user_script_set_manager_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScriptInjectionManager);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SCRIPT_INJECTION_MANAGER_H_
diff --git a/chromium/extensions/renderer/script_injector.h b/chromium/extensions/renderer/script_injector.h
new file mode 100644
index 00000000000..a51e1d5a9a0
--- /dev/null
+++ b/chromium/extensions/renderer/script_injector.h
@@ -0,0 +1,98 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_SCRIPT_INJECTOR_H_
+#define EXTENSIONS_RENDERER_SCRIPT_INJECTOR_H_
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/common/user_script.h"
+#include "third_party/WebKit/public/web/WebScriptSource.h"
+
+class GURL;
+class InjectionHost;
+
+namespace blink {
+class WebLocalFrame;
+}
+
+namespace extensions {
+struct ScriptsRunInfo;
+
+// The pseudo-delegate class for a ScriptInjection that provides all necessary
+// information about how to inject the script, including what code to inject,
+// when (run location), and where (world), but without any injection logic.
+class ScriptInjector {
+ public:
+ // The possible reasons for not injecting the script.
+ enum InjectFailureReason {
+ EXTENSION_REMOVED, // The extension was removed before injection.
+ NOT_ALLOWED, // The script is not allowed to inject.
+ WONT_INJECT // The injection won't inject because the user rejected
+ // (or just did not accept) the injection.
+ };
+
+ virtual ~ScriptInjector() {}
+
+ // Returns the script type of this particular injection.
+ virtual UserScript::InjectionType script_type() const = 0;
+
+ // Returns true if the script should execute in the main world.
+ virtual bool ShouldExecuteInMainWorld() const = 0;
+
+ // Returns true if the script is running inside a user gesture.
+ virtual bool IsUserGesture() const = 0;
+
+ // Returns true if the script expects results.
+ virtual bool ExpectsResults() const = 0;
+
+ // Returns true if the script should inject JS source at the given
+ // |run_location|.
+ virtual bool ShouldInjectJs(UserScript::RunLocation run_location) const = 0;
+
+ // Returns true if the script should inject CSS at the given |run_location|.
+ virtual bool ShouldInjectCss(UserScript::RunLocation run_location) const = 0;
+
+ // Returns true if the script should execute on the given |frame|.
+ virtual PermissionsData::AccessType CanExecuteOnFrame(
+ const InjectionHost* injection_host,
+ blink::WebLocalFrame* web_frame,
+ int tab_id) const = 0;
+
+ // Returns the javascript sources to inject at the given |run_location|.
+ // Only called if ShouldInjectJs() is true.
+ virtual std::vector<blink::WebScriptSource> GetJsSources(
+ UserScript::RunLocation run_location) const = 0;
+
+ // Returns the css to inject at the given |run_location|.
+ // Only called if ShouldInjectCss() is true.
+ virtual std::vector<std::string> GetCssSources(
+ UserScript::RunLocation run_location) const = 0;
+
+ // Fill scriptrs run info based on information about injection.
+ virtual void GetRunInfo(
+ ScriptsRunInfo* scripts_run_info,
+ UserScript::RunLocation run_location) const = 0;
+
+ // Notifies the script that injection has completed, with a possibly-populated
+ // list of results (depending on whether or not ExpectsResults() was true).
+ // |render_frame| contains the render frame, or null if the frame was
+ // invalidated.
+ virtual void OnInjectionComplete(
+ scoped_ptr<base::Value> execution_result,
+ UserScript::RunLocation run_location,
+ content::RenderFrame* render_frame) = 0;
+
+ // Notifies the script that injection will never occur.
+ // |render_frame| contains the render frame, or null if the frame was
+ // invalidated.
+ virtual void OnWillNotInject(InjectFailureReason reason,
+ content::RenderFrame* render_frame) = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SCRIPT_INJECTOR_H_
diff --git a/chromium/extensions/renderer/scripts_run_info.cc b/chromium/extensions/renderer/scripts_run_info.cc
new file mode 100644
index 00000000000..b31bbe07ba1
--- /dev/null
+++ b/chromium/extensions/renderer/scripts_run_info.cc
@@ -0,0 +1,77 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/scripts_run_info.h"
+
+#include "base/metrics/histogram.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+
+namespace extensions {
+
+ScriptsRunInfo::ScriptsRunInfo(content::RenderFrame* render_frame,
+ UserScript::RunLocation location)
+ : num_css(0u),
+ num_js(0u),
+ num_blocking_js(0u),
+ routing_id_(render_frame->GetRoutingID()),
+ run_location_(location),
+ frame_url_(ScriptContext::GetDataSourceURLForFrame(
+ render_frame->GetWebFrame())) {
+}
+
+ScriptsRunInfo::~ScriptsRunInfo() {
+}
+
+void ScriptsRunInfo::LogRun() {
+ // Notify the browser if any extensions are now executing scripts.
+ if (!executing_scripts.empty()) {
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_ContentScriptsExecuting(
+ routing_id_, executing_scripts, frame_url_));
+ }
+
+ switch (run_location_) {
+ case UserScript::DOCUMENT_START:
+ UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount", num_css);
+ UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount", num_js);
+ if (num_blocking_js) {
+ UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_BlockingScriptCount",
+ num_blocking_js);
+ } else if (num_css || num_js) {
+ UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time", timer.Elapsed());
+ }
+ break;
+ case UserScript::DOCUMENT_END:
+ UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", num_js);
+ if (num_blocking_js) {
+ UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_BlockingScriptCount",
+ num_blocking_js);
+ } else if (num_js) {
+ UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", timer.Elapsed());
+ }
+ break;
+ case UserScript::DOCUMENT_IDLE:
+ UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount", num_js);
+ if (num_blocking_js) {
+ UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_BlockingScriptCount",
+ num_blocking_js);
+ } else if (num_js) {
+ UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", timer.Elapsed());
+ }
+ break;
+ case UserScript::RUN_DEFERRED:
+ case UserScript::BROWSER_DRIVEN:
+ // TODO(rdevlin.cronin): Add histograms.
+ break;
+ case UserScript::UNDEFINED:
+ case UserScript::RUN_LOCATION_LAST:
+ NOTREACHED();
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/scripts_run_info.h b/chromium/extensions/renderer/scripts_run_info.h
new file mode 100644
index 00000000000..ebd51dfa43b
--- /dev/null
+++ b/chromium/extensions/renderer/scripts_run_info.h
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_SCRIPTS_RUN_INFO_H_
+#define EXTENSIONS_RENDERER_SCRIPTS_RUN_INFO_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "base/timer/elapsed_timer.h"
+#include "extensions/common/user_script.h"
+
+namespace content {
+class RenderFrame;
+}
+
+namespace extensions {
+
+// A struct containing information about a script run.
+struct ScriptsRunInfo {
+ // Map of extensions IDs to the executing script paths.
+ typedef std::map<std::string, std::set<std::string> > ExecutingScriptsMap;
+
+ ScriptsRunInfo(content::RenderFrame* render_frame,
+ UserScript::RunLocation location);
+ ~ScriptsRunInfo();
+
+ // The number of CSS scripts injected.
+ size_t num_css;
+ // The number of JS scripts injected.
+ size_t num_js;
+ // The number of blocked JS scripts injected.
+ size_t num_blocking_js;
+ // A map of extension ids to executing script paths.
+ ExecutingScriptsMap executing_scripts;
+ // The elapsed time since the ScriptsRunInfo was constructed.
+ base::ElapsedTimer timer;
+
+ // Log information about a given script run.
+ void LogRun();
+
+ private:
+ // The routinig id to use to notify the browser of any injections. Since the
+ // frame may be deleted in injection, we don't hold on to a reference to it
+ // directly.
+ int routing_id_;
+
+ // The run location at which injection is happening.
+ UserScript::RunLocation run_location_;
+
+ // The url of the frame, preserved for the same reason as the routing id.
+ GURL frame_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScriptsRunInfo);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SCRIPTS_RUN_INFO_H_
diff --git a/chromium/extensions/renderer/send_request_natives.cc b/chromium/extensions/renderer/send_request_natives.cc
new file mode 100644
index 00000000000..ef3e93be9d9
--- /dev/null
+++ b/chromium/extensions/renderer/send_request_natives.cc
@@ -0,0 +1,79 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/send_request_natives.h"
+
+#include <stdint.h>
+
+#include "base/json/json_reader.h"
+#include "content/public/child/v8_value_converter.h"
+#include "extensions/renderer/request_sender.h"
+#include "extensions/renderer/script_context.h"
+
+using content::V8ValueConverter;
+
+namespace extensions {
+
+SendRequestNatives::SendRequestNatives(RequestSender* request_sender,
+ ScriptContext* context)
+ : ObjectBackedNativeHandler(context), request_sender_(request_sender) {
+ RouteFunction(
+ "StartRequest",
+ base::Bind(&SendRequestNatives::StartRequest, base::Unretained(this)));
+ RouteFunction(
+ "GetGlobal",
+ base::Bind(&SendRequestNatives::GetGlobal, base::Unretained(this)));
+}
+
+// Starts an API request to the browser, with an optional callback. The
+// callback will be dispatched to EventBindings::HandleResponse.
+void SendRequestNatives::StartRequest(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(5, args.Length());
+ std::string name = *v8::String::Utf8Value(args[0]);
+ bool has_callback = args[2]->BooleanValue();
+ bool for_io_thread = args[3]->BooleanValue();
+ bool preserve_null_in_objects = args[4]->BooleanValue();
+
+ int request_id = request_sender_->GetNextRequestId();
+ args.GetReturnValue().Set(static_cast<int32_t>(request_id));
+
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+
+ // See http://crbug.com/149880. The context menus APIs relies on this, but
+ // we shouldn't really be doing it (e.g. for the sake of the storage API).
+ converter->SetFunctionAllowed(true);
+
+ if (!preserve_null_in_objects)
+ converter->SetStripNullFromObjects(true);
+
+ scoped_ptr<base::Value> value_args(
+ converter->FromV8Value(args[1], context()->v8_context()));
+ if (!value_args.get() || !value_args->IsType(base::Value::TYPE_LIST)) {
+ NOTREACHED() << "Unable to convert args passed to StartRequest";
+ return;
+ }
+
+ request_sender_->StartRequest(
+ context(),
+ name,
+ request_id,
+ has_callback,
+ for_io_thread,
+ static_cast<base::ListValue*>(value_args.get()));
+}
+
+void SendRequestNatives::GetGlobal(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsObject());
+ v8::Local<v8::Context> v8_context =
+ v8::Local<v8::Object>::Cast(args[0])->CreationContext();
+ if (ContextCanAccessObject(context()->v8_context(), v8_context->Global(),
+ false)) {
+ args.GetReturnValue().Set(v8_context->Global());
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/send_request_natives.h b/chromium/extensions/renderer/send_request_natives.h
new file mode 100644
index 00000000000..69212e1dbb1
--- /dev/null
+++ b/chromium/extensions/renderer/send_request_natives.h
@@ -0,0 +1,37 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_SEND_REQUEST_NATIVES_H_
+#define EXTENSIONS_RENDERER_SEND_REQUEST_NATIVES_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+class RequestSender;
+class ScriptContext;
+
+// Native functions exposed to extensions via JS for calling API functions in
+// the browser.
+class SendRequestNatives : public ObjectBackedNativeHandler {
+ public:
+ SendRequestNatives(RequestSender* request_sender, ScriptContext* context);
+
+ private:
+ // Starts an API request to the browser, with an optional callback. The
+ // callback will be dispatched to EventBindings::HandleResponse.
+ void StartRequest(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ // Gets a reference to an object's global object.
+ void GetGlobal(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ RequestSender* request_sender_;
+
+ DISALLOW_COPY_AND_ASSIGN(SendRequestNatives);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SEND_REQUEST_NATIVES_H_
diff --git a/chromium/extensions/renderer/set_icon_natives.cc b/chromium/extensions/renderer/set_icon_natives.cc
new file mode 100644
index 00000000000..0df21459cb4
--- /dev/null
+++ b/chromium/extensions/renderer/set_icon_natives.cc
@@ -0,0 +1,155 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/set_icon_natives.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "content/public/common/common_param_traits.h"
+#include "extensions/renderer/request_sender.h"
+#include "extensions/renderer/script_context.h"
+#include "ipc/ipc_message_utils.h"
+#include "third_party/WebKit/public/web/WebArrayBufferConverter.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace {
+
+const char kInvalidDimensions[] = "ImageData has invalid dimensions.";
+const char kInvalidData[] = "ImageData data length does not match dimensions.";
+const char kNoMemory[] = "Chrome was unable to initialize icon.";
+
+} // namespace
+
+namespace extensions {
+
+SetIconNatives::SetIconNatives(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction(
+ "SetIconCommon",
+ base::Bind(&SetIconNatives::SetIconCommon, base::Unretained(this)));
+}
+
+bool SetIconNatives::ConvertImageDataToBitmapValue(
+ const v8::Local<v8::Object> image_data,
+ v8::Local<v8::Value>* image_data_bitmap) {
+ v8::Isolate* isolate = context()->v8_context()->GetIsolate();
+ v8::Local<v8::Object> data =
+ image_data->Get(v8::String::NewFromUtf8(isolate, "data"))
+ ->ToObject(isolate);
+ int width =
+ image_data->Get(v8::String::NewFromUtf8(isolate, "width"))->Int32Value();
+ int height =
+ image_data->Get(v8::String::NewFromUtf8(isolate, "height"))->Int32Value();
+
+ if (width <= 0 || height <= 0) {
+ isolate->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate, kInvalidDimensions)));
+ return false;
+ }
+
+ // We need to be able to safely check |data_length| == 4 * width * height
+ // without overflowing below.
+ int max_width = (std::numeric_limits<int>::max() / 4) / height;
+ if (width > max_width) {
+ isolate->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate, kInvalidDimensions)));
+ return false;
+ }
+
+ int data_length =
+ data->Get(v8::String::NewFromUtf8(isolate, "length"))->Int32Value();
+ if (data_length != 4 * width * height) {
+ isolate->ThrowException(
+ v8::Exception::Error(v8::String::NewFromUtf8(isolate, kInvalidData)));
+ return false;
+ }
+
+ SkBitmap bitmap;
+ if (!bitmap.tryAllocN32Pixels(width, height)) {
+ isolate->ThrowException(
+ v8::Exception::Error(v8::String::NewFromUtf8(isolate, kNoMemory)));
+ return false;
+ }
+ bitmap.eraseARGB(0, 0, 0, 0);
+
+ uint32_t* pixels = bitmap.getAddr32(0, 0);
+ for (int t = 0; t < width * height; t++) {
+ // |data| is RGBA, pixels is ARGB.
+ pixels[t] = SkPreMultiplyColor(
+ ((data->Get(v8::Integer::New(isolate, 4 * t + 3))->Int32Value() & 0xFF)
+ << 24) |
+ ((data->Get(v8::Integer::New(isolate, 4 * t + 0))->Int32Value() & 0xFF)
+ << 16) |
+ ((data->Get(v8::Integer::New(isolate, 4 * t + 1))->Int32Value() & 0xFF)
+ << 8) |
+ ((data->Get(v8::Integer::New(isolate, 4 * t + 2))->Int32Value() & 0xFF)
+ << 0));
+ }
+
+ // Construct the Value object.
+ IPC::Message bitmap_pickle;
+ IPC::WriteParam(&bitmap_pickle, bitmap);
+ blink::WebArrayBuffer buffer =
+ blink::WebArrayBuffer::create(bitmap_pickle.size(), 1);
+ memcpy(buffer.data(), bitmap_pickle.data(), bitmap_pickle.size());
+ *image_data_bitmap = blink::WebArrayBufferConverter::toV8Value(
+ &buffer, context()->v8_context()->Global(), isolate);
+
+ return true;
+}
+
+bool SetIconNatives::ConvertImageDataSetToBitmapValueSet(
+ v8::Local<v8::Object>& details,
+ v8::Local<v8::Object>* bitmap_set_value) {
+ v8::Isolate* isolate = context()->v8_context()->GetIsolate();
+ v8::Local<v8::Object> image_data_set =
+ details->Get(v8::String::NewFromUtf8(isolate, "imageData"))
+ ->ToObject(isolate);
+
+ DCHECK(bitmap_set_value);
+
+ v8::Local<v8::Array> property_names(image_data_set->GetOwnPropertyNames());
+ for (size_t i = 0; i < property_names->Length(); ++i) {
+ v8::Local<v8::Value> key(property_names->Get(i));
+ v8::String::Utf8Value utf8_key(key);
+ int size;
+ if (!base::StringToInt(std::string(*utf8_key), &size))
+ continue;
+ v8::Local<v8::Object> image_data =
+ image_data_set->Get(key)->ToObject(isolate);
+ v8::Local<v8::Value> image_data_bitmap;
+ if (!ConvertImageDataToBitmapValue(image_data, &image_data_bitmap))
+ return false;
+ (*bitmap_set_value)->Set(key, image_data_bitmap);
+ }
+ return true;
+}
+
+void SetIconNatives::SetIconCommon(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsObject());
+ v8::Local<v8::Object> details = args[0]->ToObject(args.GetIsolate());
+ v8::Local<v8::Object> bitmap_set_value(v8::Object::New(args.GetIsolate()));
+ if (!ConvertImageDataSetToBitmapValueSet(details, &bitmap_set_value))
+ return;
+
+ v8::Local<v8::Object> dict(v8::Object::New(args.GetIsolate()));
+ dict->Set(v8::String::NewFromUtf8(args.GetIsolate(), "imageData"),
+ bitmap_set_value);
+ if (details->Has(v8::String::NewFromUtf8(args.GetIsolate(), "tabId"))) {
+ dict->Set(
+ v8::String::NewFromUtf8(args.GetIsolate(), "tabId"),
+ details->Get(v8::String::NewFromUtf8(args.GetIsolate(), "tabId")));
+ }
+ args.GetReturnValue().Set(dict);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/set_icon_natives.h b/chromium/extensions/renderer/set_icon_natives.h
new file mode 100644
index 00000000000..f42589188de
--- /dev/null
+++ b/chromium/extensions/renderer/set_icon_natives.h
@@ -0,0 +1,33 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_SET_ICON_NATIVES_H_
+#define EXTENSIONS_RENDERER_SET_ICON_NATIVES_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+class ScriptContext;
+
+// Functions exposed to extension JS to implement the setIcon extension API.
+class SetIconNatives : public ObjectBackedNativeHandler {
+ public:
+ explicit SetIconNatives(ScriptContext* context);
+
+ private:
+ bool ConvertImageDataToBitmapValue(const v8::Local<v8::Object> image_data,
+ v8::Local<v8::Value>* image_data_bitmap);
+ bool ConvertImageDataSetToBitmapValueSet(
+ v8::Local<v8::Object>& details,
+ v8::Local<v8::Object>* bitmap_set_value);
+ void SetIconCommon(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ DISALLOW_COPY_AND_ASSIGN(SetIconNatives);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_SET_ICON_NATIVES_H_
diff --git a/chromium/extensions/renderer/static_v8_external_one_byte_string_resource.cc b/chromium/extensions/renderer/static_v8_external_one_byte_string_resource.cc
new file mode 100644
index 00000000000..3f54b5ddfb5
--- /dev/null
+++ b/chromium/extensions/renderer/static_v8_external_one_byte_string_resource.cc
@@ -0,0 +1,26 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/static_v8_external_one_byte_string_resource.h"
+
+namespace extensions {
+
+StaticV8ExternalOneByteStringResource::StaticV8ExternalOneByteStringResource(
+ const base::StringPiece& buffer)
+ : buffer_(buffer) {
+}
+
+StaticV8ExternalOneByteStringResource::
+ ~StaticV8ExternalOneByteStringResource() {
+}
+
+const char* StaticV8ExternalOneByteStringResource::data() const {
+ return buffer_.data();
+}
+
+size_t StaticV8ExternalOneByteStringResource::length() const {
+ return buffer_.length();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/static_v8_external_one_byte_string_resource.h b/chromium/extensions/renderer/static_v8_external_one_byte_string_resource.h
new file mode 100644
index 00000000000..3f569585a7b
--- /dev/null
+++ b/chromium/extensions/renderer/static_v8_external_one_byte_string_resource.h
@@ -0,0 +1,35 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_STATIC_V8_EXTERNAL_ONE_BYTE_STRING_RESOURCE_H_
+#define EXTENSIONS_RENDERER_STATIC_V8_EXTERNAL_ONE_BYTE_STRING_RESOURCE_H_
+
+#include <stddef.h>
+
+#include "base/compiler_specific.h"
+#include "base/strings/string_piece.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+// A very simple implementation of v8::ExternalAsciiStringResource that just
+// wraps a buffer. The buffer must outlive the v8 runtime instance this resource
+// is used in.
+class StaticV8ExternalOneByteStringResource
+ : public v8::String::ExternalOneByteStringResource {
+ public:
+ explicit StaticV8ExternalOneByteStringResource(
+ const base::StringPiece& buffer);
+ ~StaticV8ExternalOneByteStringResource() override;
+
+ const char* data() const override;
+ size_t length() const override;
+
+ private:
+ base::StringPiece buffer_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_STATIC_V8_EXTERNAL_ONE_BYTE_STRING_RESOURCE_H_
diff --git a/chromium/extensions/renderer/test_extensions_renderer_client.cc b/chromium/extensions/renderer/test_extensions_renderer_client.cc
new file mode 100644
index 00000000000..5299878ac8a
--- /dev/null
+++ b/chromium/extensions/renderer/test_extensions_renderer_client.cc
@@ -0,0 +1,22 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/test_extensions_renderer_client.h"
+
+namespace extensions {
+
+TestExtensionsRendererClient::TestExtensionsRendererClient() {}
+
+TestExtensionsRendererClient::~TestExtensionsRendererClient() {}
+
+bool TestExtensionsRendererClient::IsIncognitoProcess() const {
+ return false;
+}
+
+int TestExtensionsRendererClient::GetLowestIsolatedWorldId() const {
+ // Note that 0 is reserved for the global world.
+ return 1;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/test_extensions_renderer_client.h b/chromium/extensions/renderer/test_extensions_renderer_client.h
new file mode 100644
index 00000000000..f79c6a95e35
--- /dev/null
+++ b/chromium/extensions/renderer/test_extensions_renderer_client.h
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_TEST_EXTENSIONS_RENDERER_CLIENT_H_
+#define EXTENSIONS_RENDERER_TEST_EXTENSIONS_RENDERER_CLIENT_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/extensions_renderer_client.h"
+
+namespace extensions {
+
+class TestExtensionsRendererClient : public ExtensionsRendererClient {
+ public:
+ TestExtensionsRendererClient();
+ ~TestExtensionsRendererClient() override;
+
+ // ExtensionsRendererClient implementation.
+ bool IsIncognitoProcess() const override;
+ int GetLowestIsolatedWorldId() const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestExtensionsRendererClient);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_TEST_EXTENSIONS_RENDERER_CLIENT_H_
diff --git a/chromium/extensions/renderer/test_features_native_handler.cc b/chromium/extensions/renderer/test_features_native_handler.cc
new file mode 100644
index 00000000000..0f228555809
--- /dev/null
+++ b/chromium/extensions/renderer/test_features_native_handler.cc
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/test_features_native_handler.h"
+
+#include "base/bind.h"
+#include "content/public/child/v8_value_converter.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/features/json_feature_provider_source.h"
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+TestFeaturesNativeHandler::TestFeaturesNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("GetAPIFeatures",
+ base::Bind(&TestFeaturesNativeHandler::GetAPIFeatures,
+ base::Unretained(this)));
+}
+
+void TestFeaturesNativeHandler::GetAPIFeatures(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ scoped_ptr<JSONFeatureProviderSource> source(
+ ExtensionsClient::Get()->CreateFeatureProviderSource("api"));
+ scoped_ptr<content::V8ValueConverter> converter(
+ content::V8ValueConverter::create());
+ args.GetReturnValue().Set(
+ converter->ToV8Value(&source->dictionary(), context()->v8_context()));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/test_features_native_handler.h b/chromium/extensions/renderer/test_features_native_handler.h
new file mode 100644
index 00000000000..2422178a1e0
--- /dev/null
+++ b/chromium/extensions/renderer/test_features_native_handler.h
@@ -0,0 +1,22 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_TEST_FEATURES_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_TEST_FEATURES_NATIVE_HANDLER_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+
+class TestFeaturesNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit TestFeaturesNativeHandler(ScriptContext* context);
+
+ private:
+ void GetAPIFeatures(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_TEST_FEATURES_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/test_native_handler.cc b/chromium/extensions/renderer/test_native_handler.cc
new file mode 100644
index 00000000000..31e7dcb9d75
--- /dev/null
+++ b/chromium/extensions/renderer/test_native_handler.cc
@@ -0,0 +1,24 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/test_native_handler.h"
+
+#include "extensions/renderer/wake_event_page.h"
+
+namespace extensions {
+
+TestNativeHandler::TestNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction(
+ "GetWakeEventPage",
+ base::Bind(&TestNativeHandler::GetWakeEventPage, base::Unretained(this)));
+}
+
+void TestNativeHandler::GetWakeEventPage(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(0, args.Length());
+ args.GetReturnValue().Set(WakeEventPage::Get()->GetForContext(context()));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/test_native_handler.h b/chromium/extensions/renderer/test_native_handler.h
new file mode 100644
index 00000000000..88e5d239845
--- /dev/null
+++ b/chromium/extensions/renderer/test_native_handler.h
@@ -0,0 +1,29 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_TEST_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_TEST_NATIVE_HANDLER_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+class ScriptContext;
+
+// NativeHandler for the chrome.test API.
+class TestNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit TestNativeHandler(ScriptContext* context);
+
+ private:
+ void GetWakeEventPage(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ DISALLOW_COPY_AND_ASSIGN(TestNativeHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_TEST_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/user_gestures_native_handler.cc b/chromium/extensions/renderer/user_gestures_native_handler.cc
new file mode 100644
index 00000000000..c01a4ae7240
--- /dev/null
+++ b/chromium/extensions/renderer/user_gestures_native_handler.cc
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/user_gestures_native_handler.h"
+
+#include "base/bind.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/web/WebScopedUserGesture.h"
+#include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
+
+namespace extensions {
+
+UserGesturesNativeHandler::UserGesturesNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction("IsProcessingUserGesture",
+ "test",
+ base::Bind(&UserGesturesNativeHandler::IsProcessingUserGesture,
+ base::Unretained(this)));
+ RouteFunction("RunWithUserGesture",
+ "test",
+ base::Bind(&UserGesturesNativeHandler::RunWithUserGesture,
+ base::Unretained(this)));
+ RouteFunction("RunWithoutUserGesture",
+ "test",
+ base::Bind(&UserGesturesNativeHandler::RunWithoutUserGesture,
+ base::Unretained(this)));
+}
+
+void UserGesturesNativeHandler::IsProcessingUserGesture(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(v8::Boolean::New(
+ args.GetIsolate(),
+ blink::WebUserGestureIndicator::isProcessingUserGesture()));
+}
+
+void UserGesturesNativeHandler::RunWithUserGesture(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ blink::WebScopedUserGesture user_gesture;
+ CHECK_EQ(args.Length(), 1);
+ CHECK(args[0]->IsFunction());
+ v8::Local<v8::Value> no_args;
+ context()->CallFunction(v8::Local<v8::Function>::Cast(args[0]), 0, &no_args);
+}
+
+void UserGesturesNativeHandler::RunWithoutUserGesture(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ blink::WebUserGestureIndicator::consumeUserGesture();
+ CHECK_EQ(args.Length(), 1);
+ CHECK(args[0]->IsFunction());
+ v8::Local<v8::Value> no_args;
+ context()->CallFunction(v8::Local<v8::Function>::Cast(args[0]), 0, &no_args);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/user_gestures_native_handler.h b/chromium/extensions/renderer/user_gestures_native_handler.h
new file mode 100644
index 00000000000..fbe15fbebc3
--- /dev/null
+++ b/chromium/extensions/renderer/user_gestures_native_handler.h
@@ -0,0 +1,24 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_USER_GESTURES_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_USER_GESTURES_NATIVE_HANDLER_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+
+class UserGesturesNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit UserGesturesNativeHandler(ScriptContext* context);
+
+ private:
+ void IsProcessingUserGesture(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void RunWithUserGesture(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void RunWithoutUserGesture(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_USER_GESTURES_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/user_script_injector.cc b/chromium/extensions/renderer/user_script_injector.cc
new file mode 100644
index 00000000000..9885b4ea8d3
--- /dev/null
+++ b/chromium/extensions/renderer/user_script_injector.cc
@@ -0,0 +1,268 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/user_script_injector.h"
+
+#include <tuple>
+#include <vector>
+
+#include "base/lazy_instance.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/guest_view/extensions_guest_view_messages.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/renderer/injection_host.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/scripts_run_info.h"
+#include "grit/extensions_renderer_resources.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebScriptSource.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+namespace {
+
+struct RoutingInfoKey {
+ int routing_id;
+ int script_id;
+
+ RoutingInfoKey(int routing_id, int script_id)
+ : routing_id(routing_id), script_id(script_id) {}
+
+ bool operator<(const RoutingInfoKey& other) const {
+ return std::tie(routing_id, script_id) <
+ std::tie(other.routing_id, other.script_id);
+ }
+};
+
+using RoutingInfoMap = std::map<RoutingInfoKey, bool>;
+
+// These two strings are injected before and after the Greasemonkey API and
+// user script to wrap it in an anonymous scope.
+const char kUserScriptHead[] = "(function (unsafeWindow) {\n";
+const char kUserScriptTail[] = "\n})(window);";
+
+// A map records whether a given |script_id| from a webview-added user script
+// is allowed to inject on the render of given |routing_id|.
+// Once a script is added, the decision of whether or not allowed to inject
+// won't be changed.
+// After removed by the webview, the user scipt will also be removed
+// from the render. Therefore, there won't be any query from the same
+// |script_id| and |routing_id| pair.
+base::LazyInstance<RoutingInfoMap> g_routing_info_map =
+ LAZY_INSTANCE_INITIALIZER;
+
+// Greasemonkey API source that is injected with the scripts.
+struct GreasemonkeyApiJsString {
+ GreasemonkeyApiJsString();
+ blink::WebScriptSource GetSource() const;
+
+ private:
+ std::string source_;
+};
+
+// The below constructor, monstrous as it is, just makes a WebScriptSource from
+// the GreasemonkeyApiJs resource.
+GreasemonkeyApiJsString::GreasemonkeyApiJsString()
+ : source_(ResourceBundle::GetSharedInstance()
+ .GetRawDataResource(IDR_GREASEMONKEY_API_JS)
+ .as_string()) {
+}
+
+blink::WebScriptSource GreasemonkeyApiJsString::GetSource() const {
+ return blink::WebScriptSource(blink::WebString::fromUTF8(source_));
+}
+
+base::LazyInstance<GreasemonkeyApiJsString> g_greasemonkey_api =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+UserScriptInjector::UserScriptInjector(const UserScript* script,
+ UserScriptSet* script_list,
+ bool is_declarative)
+ : script_(script),
+ script_id_(script_->id()),
+ host_id_(script_->host_id()),
+ is_declarative_(is_declarative),
+ user_script_set_observer_(this) {
+ user_script_set_observer_.Add(script_list);
+}
+
+UserScriptInjector::~UserScriptInjector() {
+}
+
+void UserScriptInjector::OnUserScriptsUpdated(
+ const std::set<HostID>& changed_hosts,
+ const std::vector<UserScript*>& scripts) {
+ // If the host causing this injection changed, then this injection
+ // will be removed, and there's no guarantee the backing script still exists.
+ if (changed_hosts.count(host_id_) > 0)
+ return;
+
+ for (std::vector<UserScript*>::const_iterator iter = scripts.begin();
+ iter != scripts.end();
+ ++iter) {
+ // We need to compare to |script_id_| (and not to script_->id()) because the
+ // old |script_| may be deleted by now.
+ if ((*iter)->id() == script_id_) {
+ script_ = *iter;
+ break;
+ }
+ }
+}
+
+UserScript::InjectionType UserScriptInjector::script_type() const {
+ return UserScript::CONTENT_SCRIPT;
+}
+
+bool UserScriptInjector::ShouldExecuteInMainWorld() const {
+ return false;
+}
+
+bool UserScriptInjector::IsUserGesture() const {
+ return false;
+}
+
+bool UserScriptInjector::ExpectsResults() const {
+ return false;
+}
+
+bool UserScriptInjector::ShouldInjectJs(
+ UserScript::RunLocation run_location) const {
+ return script_->run_location() == run_location &&
+ !script_->js_scripts().empty();
+}
+
+bool UserScriptInjector::ShouldInjectCss(
+ UserScript::RunLocation run_location) const {
+ return run_location == UserScript::DOCUMENT_START &&
+ !script_->css_scripts().empty();
+}
+
+PermissionsData::AccessType UserScriptInjector::CanExecuteOnFrame(
+ const InjectionHost* injection_host,
+ blink::WebLocalFrame* web_frame,
+ int tab_id) const {
+ if (script_->consumer_instance_type() ==
+ UserScript::ConsumerInstanceType::WEBVIEW) {
+ int routing_id = content::RenderView::FromWebView(web_frame->top()->view())
+ ->GetRoutingID();
+
+ RoutingInfoKey key(routing_id, script_->id());
+
+ RoutingInfoMap& map = g_routing_info_map.Get();
+ auto iter = map.find(key);
+
+ bool allowed = false;
+ if (iter != map.end()) {
+ allowed = iter->second;
+ } else {
+ // Send a SYNC IPC message to the browser to check if this is allowed.
+ // This is not ideal, but is mitigated by the fact that this is only done
+ // for webviews, and then only once per host.
+ // TODO(hanxi): Find a more efficient way to do this.
+ content::RenderThread::Get()->Send(
+ new ExtensionsGuestViewHostMsg_CanExecuteContentScriptSync(
+ routing_id, script_->id(), &allowed));
+ map.insert(std::pair<RoutingInfoKey, bool>(key, allowed));
+ }
+
+ return allowed ? PermissionsData::ACCESS_ALLOWED
+ : PermissionsData::ACCESS_DENIED;
+ }
+
+ GURL effective_document_url = ScriptContext::GetEffectiveDocumentURL(
+ web_frame, web_frame->document().url(), script_->match_about_blank());
+
+ return injection_host->CanExecuteOnFrame(
+ effective_document_url,
+ content::RenderFrame::FromWebFrame(web_frame),
+ tab_id,
+ is_declarative_);
+}
+
+std::vector<blink::WebScriptSource> UserScriptInjector::GetJsSources(
+ UserScript::RunLocation run_location) const {
+ DCHECK_EQ(script_->run_location(), run_location);
+
+ std::vector<blink::WebScriptSource> sources;
+ const UserScript::FileList& js_scripts = script_->js_scripts();
+ bool is_standalone_or_emulate_greasemonkey =
+ script_->is_standalone() || script_->emulate_greasemonkey();
+
+ for (UserScript::FileList::const_iterator iter = js_scripts.begin();
+ iter != js_scripts.end();
+ ++iter) {
+ std::string content = iter->GetContent().as_string();
+
+ // We add this dumb function wrapper for standalone user script to
+ // emulate what Greasemonkey does.
+ // TODO(aa): I think that maybe "is_standalone" scripts don't exist
+ // anymore. Investigate.
+ if (is_standalone_or_emulate_greasemonkey) {
+ content.insert(0, kUserScriptHead);
+ content += kUserScriptTail;
+ }
+ sources.push_back(blink::WebScriptSource(
+ blink::WebString::fromUTF8(content), iter->url()));
+ }
+
+ // Emulate Greasemonkey API for scripts that were converted to extensions
+ // and "standalone" user scripts.
+ if (is_standalone_or_emulate_greasemonkey)
+ sources.insert(sources.begin(), g_greasemonkey_api.Get().GetSource());
+
+ return sources;
+}
+
+std::vector<std::string> UserScriptInjector::GetCssSources(
+ UserScript::RunLocation run_location) const {
+ DCHECK_EQ(UserScript::DOCUMENT_START, run_location);
+
+ std::vector<std::string> sources;
+ const UserScript::FileList& css_scripts = script_->css_scripts();
+ for (UserScript::FileList::const_iterator iter = css_scripts.begin();
+ iter != css_scripts.end();
+ ++iter) {
+ sources.push_back(iter->GetContent().as_string());
+ }
+ return sources;
+}
+
+void UserScriptInjector::GetRunInfo(
+ ScriptsRunInfo* scripts_run_info,
+ UserScript::RunLocation run_location) const {
+ if (ShouldInjectJs(run_location)) {
+ const UserScript::FileList& js_scripts = script_->js_scripts();
+ scripts_run_info->num_js += js_scripts.size();
+ for (UserScript::FileList::const_iterator iter = js_scripts.begin();
+ iter != js_scripts.end();
+ ++iter) {
+ scripts_run_info->executing_scripts[host_id_.id()].insert(
+ iter->url().path());
+ }
+ }
+
+ if (ShouldInjectCss(run_location))
+ scripts_run_info->num_css += script_->css_scripts().size();
+}
+
+void UserScriptInjector::OnInjectionComplete(
+ scoped_ptr<base::Value> execution_result,
+ UserScript::RunLocation run_location,
+ content::RenderFrame* render_frame) {
+}
+
+void UserScriptInjector::OnWillNotInject(InjectFailureReason reason,
+ content::RenderFrame* render_frame) {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/user_script_injector.h b/chromium/extensions/renderer/user_script_injector.h
new file mode 100644
index 00000000000..61909894e31
--- /dev/null
+++ b/chromium/extensions/renderer/user_script_injector.h
@@ -0,0 +1,86 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_USER_SCRIPT_INJECTOR_H_
+#define EXTENSIONS_RENDERER_USER_SCRIPT_INJECTOR_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/scoped_observer.h"
+#include "extensions/common/user_script.h"
+#include "extensions/renderer/script_injection.h"
+#include "extensions/renderer/user_script_set.h"
+
+class InjectionHost;
+
+namespace blink {
+class WebLocalFrame;
+}
+
+namespace extensions {
+
+// A ScriptInjector for UserScripts.
+class UserScriptInjector : public ScriptInjector,
+ public UserScriptSet::Observer {
+ public:
+ UserScriptInjector(const UserScript* user_script,
+ UserScriptSet* user_script_set,
+ bool is_declarative);
+ ~UserScriptInjector() override;
+
+ private:
+ // UserScriptSet::Observer implementation.
+ void OnUserScriptsUpdated(const std::set<HostID>& changed_hosts,
+ const std::vector<UserScript*>& scripts) override;
+
+ // ScriptInjector implementation.
+ UserScript::InjectionType script_type() const override;
+ bool ShouldExecuteInMainWorld() const override;
+ bool IsUserGesture() const override;
+ bool ExpectsResults() const override;
+ bool ShouldInjectJs(UserScript::RunLocation run_location) const override;
+ bool ShouldInjectCss(UserScript::RunLocation run_location) const override;
+ PermissionsData::AccessType CanExecuteOnFrame(
+ const InjectionHost* injection_host,
+ blink::WebLocalFrame* web_frame,
+ int tab_id) const override;
+ std::vector<blink::WebScriptSource> GetJsSources(
+ UserScript::RunLocation run_location) const override;
+ std::vector<std::string> GetCssSources(
+ UserScript::RunLocation run_location) const override;
+ void GetRunInfo(ScriptsRunInfo* scripts_run_info,
+ UserScript::RunLocation run_location) const override;
+ void OnInjectionComplete(scoped_ptr<base::Value> execution_result,
+ UserScript::RunLocation run_location,
+ content::RenderFrame* render_frame) override;
+ void OnWillNotInject(InjectFailureReason reason,
+ content::RenderFrame* render_frame) override;
+
+ // The associated user script. Owned by the UserScriptInjector that created
+ // this object.
+ const UserScript* script_;
+
+ // The id of the associated user script. We cache this because when we update
+ // the |script_| associated with this injection, the old referance may be
+ // deleted.
+ int script_id_;
+
+ // The associated host id, preserved for the same reason as |script_id|.
+ HostID host_id_;
+
+ // Indicates whether or not this script is declarative. This influences which
+ // script permissions are checked before injection.
+ bool is_declarative_;
+
+ ScopedObserver<UserScriptSet, UserScriptSet::Observer>
+ user_script_set_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserScriptInjector);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_USER_SCRIPT_INJECTOR_H_
diff --git a/chromium/extensions/renderer/user_script_set.cc b/chromium/extensions/renderer/user_script_set.cc
new file mode 100644
index 00000000000..e289f3fc56a
--- /dev/null
+++ b/chromium/extensions/renderer/user_script_set.cc
@@ -0,0 +1,235 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/user_script_set.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/memory/ref_counted.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/renderer/extension_injection_host.h"
+#include "extensions/renderer/extensions_renderer_client.h"
+#include "extensions/renderer/injection_host.h"
+#include "extensions/renderer/renderer_extension_registry.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_injection.h"
+#include "extensions/renderer/user_script_injector.h"
+#include "extensions/renderer/web_ui_injection_host.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+namespace {
+
+GURL GetDocumentUrlForFrame(blink::WebLocalFrame* frame) {
+ GURL data_source_url = ScriptContext::GetDataSourceURLForFrame(frame);
+ if (!data_source_url.is_empty() && frame->isViewSourceModeEnabled()) {
+ data_source_url = GURL(content::kViewSourceScheme + std::string(":") +
+ data_source_url.spec());
+ }
+
+ return data_source_url;
+}
+
+} // namespace
+
+UserScriptSet::UserScriptSet() {}
+
+UserScriptSet::~UserScriptSet() {
+}
+
+void UserScriptSet::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void UserScriptSet::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void UserScriptSet::GetActiveExtensionIds(
+ std::set<std::string>* ids) const {
+ for (const UserScript* script : scripts_) {
+ if (script->host_id().type() != HostID::EXTENSIONS)
+ continue;
+ DCHECK(!script->extension_id().empty());
+ ids->insert(script->extension_id());
+ }
+}
+
+void UserScriptSet::GetInjections(
+ std::vector<scoped_ptr<ScriptInjection>>* injections,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location) {
+ GURL document_url = GetDocumentUrlForFrame(render_frame->GetWebFrame());
+ for (const UserScript* script : scripts_) {
+ scoped_ptr<ScriptInjection> injection = GetInjectionForScript(
+ script,
+ render_frame,
+ tab_id,
+ run_location,
+ document_url,
+ false /* is_declarative */);
+ if (injection.get())
+ injections->push_back(std::move(injection));
+ }
+}
+
+bool UserScriptSet::UpdateUserScripts(base::SharedMemoryHandle shared_memory,
+ const std::set<HostID>& changed_hosts,
+ bool whitelisted_only) {
+ bool only_inject_incognito =
+ ExtensionsRendererClient::Get()->IsIncognitoProcess();
+
+ // Create the shared memory object (read only).
+ shared_memory_.reset(new base::SharedMemory(shared_memory, true));
+ if (!shared_memory_.get())
+ return false;
+
+ // First get the size of the memory block.
+ if (!shared_memory_->Map(sizeof(base::Pickle::Header)))
+ return false;
+ base::Pickle::Header* pickle_header =
+ reinterpret_cast<base::Pickle::Header*>(shared_memory_->memory());
+
+ // Now map in the rest of the block.
+ int pickle_size = sizeof(base::Pickle::Header) + pickle_header->payload_size;
+ shared_memory_->Unmap();
+ if (!shared_memory_->Map(pickle_size))
+ return false;
+
+ // Unpickle scripts.
+ uint32_t num_scripts = 0;
+ base::Pickle pickle(reinterpret_cast<char*>(shared_memory_->memory()),
+ pickle_size);
+ base::PickleIterator iter(pickle);
+ CHECK(iter.ReadUInt32(&num_scripts));
+
+ scripts_.clear();
+ scripts_.reserve(num_scripts);
+ for (uint32_t i = 0; i < num_scripts; ++i) {
+ scoped_ptr<UserScript> script(new UserScript());
+ script->Unpickle(pickle, &iter);
+
+ // Note that this is a pointer into shared memory. We don't own it. It gets
+ // cleared up when the last renderer or browser process drops their
+ // reference to the shared memory.
+ for (size_t j = 0; j < script->js_scripts().size(); ++j) {
+ const char* body = NULL;
+ int body_length = 0;
+ CHECK(iter.ReadData(&body, &body_length));
+ script->js_scripts()[j].set_external_content(
+ base::StringPiece(body, body_length));
+ }
+ for (size_t j = 0; j < script->css_scripts().size(); ++j) {
+ const char* body = NULL;
+ int body_length = 0;
+ CHECK(iter.ReadData(&body, &body_length));
+ script->css_scripts()[j].set_external_content(
+ base::StringPiece(body, body_length));
+ }
+
+ if (only_inject_incognito && !script->is_incognito_enabled())
+ continue; // This script shouldn't run in an incognito tab.
+
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(script->extension_id());
+ if (whitelisted_only &&
+ (!extension ||
+ !PermissionsData::CanExecuteScriptEverywhere(extension))) {
+ continue;
+ }
+
+ scripts_.push_back(std::move(script));
+ }
+
+ FOR_EACH_OBSERVER(Observer,
+ observers_,
+ OnUserScriptsUpdated(changed_hosts, scripts_.get()));
+ return true;
+}
+
+scoped_ptr<ScriptInjection> UserScriptSet::GetDeclarativeScriptInjection(
+ int script_id,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location,
+ const GURL& document_url) {
+ for (const UserScript* script : scripts_) {
+ if (script->id() == script_id) {
+ return GetInjectionForScript(script,
+ render_frame,
+ tab_id,
+ run_location,
+ document_url,
+ true /* is_declarative */);
+ }
+ }
+ return scoped_ptr<ScriptInjection>();
+}
+
+scoped_ptr<ScriptInjection> UserScriptSet::GetInjectionForScript(
+ const UserScript* script,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location,
+ const GURL& document_url,
+ bool is_declarative) {
+ scoped_ptr<ScriptInjection> injection;
+ scoped_ptr<const InjectionHost> injection_host;
+ blink::WebLocalFrame* web_frame = render_frame->GetWebFrame();
+
+ const HostID& host_id = script->host_id();
+ if (host_id.type() == HostID::EXTENSIONS) {
+ injection_host = ExtensionInjectionHost::Create(host_id.id());
+ if (!injection_host)
+ return injection;
+ } else {
+ DCHECK_EQ(host_id.type(), HostID::WEBUI);
+ injection_host.reset(new WebUIInjectionHost(host_id));
+ }
+
+ if (web_frame->parent() && !script->match_all_frames())
+ return injection; // Only match subframes if the script declared it.
+
+ GURL effective_document_url = ScriptContext::GetEffectiveDocumentURL(
+ web_frame, document_url, script->match_about_blank());
+
+ if (!script->MatchesURL(effective_document_url))
+ return injection;
+
+ scoped_ptr<ScriptInjector> injector(new UserScriptInjector(script,
+ this,
+ is_declarative));
+
+ if (injector->CanExecuteOnFrame(
+ injection_host.get(),
+ web_frame,
+ tab_id) ==
+ PermissionsData::ACCESS_DENIED) {
+ return injection;
+ }
+
+ bool inject_css = !script->css_scripts().empty() &&
+ run_location == UserScript::DOCUMENT_START;
+ bool inject_js =
+ !script->js_scripts().empty() && script->run_location() == run_location;
+ if (inject_css || inject_js) {
+ injection.reset(new ScriptInjection(std::move(injector), render_frame,
+ std::move(injection_host),
+ run_location));
+ }
+ return injection;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/user_script_set.h b/chromium/extensions/renderer/user_script_set.h
new file mode 100644
index 00000000000..128b33f0be9
--- /dev/null
+++ b/chromium/extensions/renderer/user_script_set.h
@@ -0,0 +1,100 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_USER_SCRIPT_SET_H_
+#define EXTENSIONS_RENDERER_USER_SCRIPT_SET_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/shared_memory.h"
+#include "base/observer_list.h"
+#include "content/public/renderer/render_process_observer.h"
+#include "extensions/common/user_script.h"
+
+class GURL;
+
+namespace content {
+class RenderFrame;
+}
+
+namespace extensions {
+class ScriptInjection;
+
+// The UserScriptSet is a collection of UserScripts which knows how to update
+// itself from SharedMemory and create ScriptInjections for UserScripts to
+// inject on a page.
+class UserScriptSet {
+ public:
+ class Observer {
+ public:
+ virtual void OnUserScriptsUpdated(
+ const std::set<HostID>& changed_hosts,
+ const std::vector<UserScript*>& scripts) = 0;
+ };
+
+ UserScriptSet();
+ ~UserScriptSet();
+
+ // Adds or removes observers.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Appends the ids of the extensions that have user scripts to |ids|.
+ void GetActiveExtensionIds(std::set<std::string>* ids) const;
+
+ // Append any ScriptInjections that should run on the given |render_frame| and
+ // |tab_id|, at the given |run_location|, to |injections|.
+ // |extensions| is passed in to verify the corresponding extension is still
+ // valid.
+ void GetInjections(std::vector<scoped_ptr<ScriptInjection>>* injections,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location);
+
+ scoped_ptr<ScriptInjection> GetDeclarativeScriptInjection(
+ int script_id,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location,
+ const GURL& document_url);
+
+ // Updates scripts given the shared memory region containing user scripts.
+ // Returns true if the scripts were successfully updated.
+ bool UpdateUserScripts(base::SharedMemoryHandle shared_memory,
+ const std::set<HostID>& changed_hosts,
+ bool whitelisted_only);
+
+ const std::vector<UserScript*>& scripts() const { return scripts_.get(); }
+
+ private:
+ // Returns a new ScriptInjection for the given |script| to execute in the
+ // |render_frame|, or NULL if the script should not execute.
+ scoped_ptr<ScriptInjection> GetInjectionForScript(
+ const UserScript* script,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location,
+ const GURL& document_url,
+ bool is_declarative);
+
+ // Shared memory containing raw script data.
+ scoped_ptr<base::SharedMemory> shared_memory_;
+
+ // The UserScripts this injector manages.
+ ScopedVector<UserScript> scripts_;
+
+ // The associated observers.
+ base::ObserverList<Observer> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserScriptSet);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_USER_SCRIPT_SET_H_
diff --git a/chromium/extensions/renderer/user_script_set_manager.cc b/chromium/extensions/renderer/user_script_set_manager.cc
new file mode 100644
index 00000000000..0404e0552b9
--- /dev/null
+++ b/chromium/extensions/renderer/user_script_set_manager.cc
@@ -0,0 +1,159 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/user_script_set_manager.h"
+
+#include "components/crx_file/id_util.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/script_injection.h"
+#include "extensions/renderer/user_script_set.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_macros.h"
+
+namespace extensions {
+
+UserScriptSetManager::UserScriptSetManager() {
+ content::RenderThread::Get()->AddObserver(this);
+}
+
+UserScriptSetManager::~UserScriptSetManager() {
+}
+
+void UserScriptSetManager::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void UserScriptSetManager::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+scoped_ptr<ScriptInjection>
+UserScriptSetManager::GetInjectionForDeclarativeScript(
+ int script_id,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ const GURL& url,
+ const std::string& extension_id) {
+ UserScriptSet* user_script_set =
+ GetProgrammaticScriptsByHostID(HostID(HostID::EXTENSIONS, extension_id));
+ if (!user_script_set)
+ return scoped_ptr<ScriptInjection>();
+
+ return user_script_set->GetDeclarativeScriptInjection(
+ script_id,
+ render_frame,
+ tab_id,
+ UserScript::BROWSER_DRIVEN,
+ url);
+}
+
+bool UserScriptSetManager::OnControlMessageReceived(
+ const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(UserScriptSetManager, message)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateUserScripts, OnUpdateUserScripts)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void UserScriptSetManager::GetAllInjections(
+ std::vector<scoped_ptr<ScriptInjection>>* injections,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location) {
+ static_scripts_.GetInjections(injections, render_frame, tab_id, run_location);
+ for (UserScriptSetMap::iterator it = programmatic_scripts_.begin();
+ it != programmatic_scripts_.end();
+ ++it) {
+ it->second->GetInjections(injections, render_frame, tab_id, run_location);
+ }
+}
+
+void UserScriptSetManager::GetAllActiveExtensionIds(
+ std::set<std::string>* ids) const {
+ DCHECK(ids);
+ static_scripts_.GetActiveExtensionIds(ids);
+ for (UserScriptSetMap::const_iterator it = programmatic_scripts_.begin();
+ it != programmatic_scripts_.end();
+ ++it) {
+ it->second->GetActiveExtensionIds(ids);
+ }
+}
+
+UserScriptSet* UserScriptSetManager::GetProgrammaticScriptsByHostID(
+ const HostID& host_id) {
+ UserScriptSetMap::const_iterator it = programmatic_scripts_.find(host_id);
+ return it != programmatic_scripts_.end() ? it->second.get() : NULL;
+}
+
+void UserScriptSetManager::OnUpdateUserScripts(
+ base::SharedMemoryHandle shared_memory,
+ const HostID& host_id,
+ const std::set<HostID>& changed_hosts,
+ bool whitelisted_only) {
+ if (!base::SharedMemory::IsHandleValid(shared_memory)) {
+ NOTREACHED() << "Bad scripts handle";
+ return;
+ }
+
+ for (const HostID& host_id : changed_hosts) {
+ if (host_id.type() == HostID::EXTENSIONS &&
+ !crx_file::id_util::IdIsValid(host_id.id())) {
+ NOTREACHED() << "Invalid extension id: " << host_id.id();
+ return;
+ }
+ }
+
+ UserScriptSet* scripts = NULL;
+ if (!host_id.id().empty()) {
+ // The expectation when there is a host that "owns" this shared
+ // memory region is that the |changed_hosts| is either the empty list
+ // or just the owner.
+ CHECK(changed_hosts.size() <= 1);
+ if (programmatic_scripts_.find(host_id) == programmatic_scripts_.end()) {
+ scripts = new UserScriptSet();
+ programmatic_scripts_[host_id] = make_linked_ptr(scripts);
+ } else {
+ scripts = programmatic_scripts_[host_id].get();
+ }
+ } else {
+ scripts = &static_scripts_;
+ }
+ DCHECK(scripts);
+
+ // If no hosts are included in the set, that indicates that all
+ // hosts were updated. Add them all to the set so that observers and
+ // individual UserScriptSets don't need to know this detail.
+ const std::set<HostID>* effective_hosts = &changed_hosts;
+ std::set<HostID> all_hosts;
+ if (changed_hosts.empty()) {
+ // The meaning of "all hosts(extensions)" varies, depending on whether some
+ // host "owns" this shared memory region.
+ // No owner => all known hosts.
+ // Owner => just the owner host.
+ if (host_id.id().empty()) {
+ std::set<std::string> extension_ids =
+ RendererExtensionRegistry::Get()->GetIDs();
+ for (const std::string& extension_id : extension_ids)
+ all_hosts.insert(HostID(HostID::EXTENSIONS, extension_id));
+ } else {
+ all_hosts.insert(host_id);
+ }
+ effective_hosts = &all_hosts;
+ }
+
+ if (scripts->UpdateUserScripts(shared_memory,
+ *effective_hosts,
+ whitelisted_only)) {
+ FOR_EACH_OBSERVER(
+ Observer,
+ observers_,
+ OnUserScriptsUpdated(*effective_hosts, scripts->scripts()));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/user_script_set_manager.h b/chromium/extensions/renderer/user_script_set_manager.h
new file mode 100644
index 00000000000..5cba81e7bc2
--- /dev/null
+++ b/chromium/extensions/renderer/user_script_set_manager.h
@@ -0,0 +1,113 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_USER_SCRIPT_SET_MANAGER_H_
+#define EXTENSIONS_RENDERER_USER_SCRIPT_SET_MANAGER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/shared_memory.h"
+#include "base/observer_list.h"
+#include "content/public/renderer/render_process_observer.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/user_script.h"
+#include "extensions/renderer/user_script_set.h"
+
+namespace content {
+class RenderFrame;
+}
+
+namespace IPC {
+class Message;
+}
+
+namespace extensions {
+
+class ScriptInjection;
+
+// Manager for separate UserScriptSets, one for each shared memory region.
+// Regions are organized as follows:
+// static_scripts -- contains all extensions' scripts that are statically
+// declared in the extension manifest.
+// programmatic_scripts -- one region per host (extension or WebUI) containing
+// only programmatically-declared scripts, instantiated
+// when an extension first creates a declarative rule
+// that would, if triggered, request a script injection.
+class UserScriptSetManager : public content::RenderProcessObserver {
+ public:
+ // Like a UserScriptSet::Observer, but automatically subscribes to all sets
+ // associated with the manager.
+ class Observer {
+ public:
+ virtual void OnUserScriptsUpdated(
+ const std::set<HostID>& changed_hosts,
+ const std::vector<UserScript*>& scripts) = 0;
+ };
+
+ UserScriptSetManager();
+
+ ~UserScriptSetManager() override;
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Looks up the script injection associated with |script_id| and
+ // |extension_id| in the context of the given |web_frame|, |tab_id|,
+ // and |url|.
+ scoped_ptr<ScriptInjection> GetInjectionForDeclarativeScript(
+ int script_id,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ const GURL& url,
+ const std::string& extension_id);
+
+ // Append all injections from |static_scripts| and each of
+ // |programmatic_scripts_| to |injections|.
+ void GetAllInjections(std::vector<scoped_ptr<ScriptInjection>>* injections,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location);
+
+ // Get active extension IDs from |static_scripts| and each of
+ // |programmatic_scripts_|.
+ void GetAllActiveExtensionIds(std::set<std::string>* ids) const;
+
+ const UserScriptSet* static_scripts() const { return &static_scripts_; }
+
+ private:
+ // Map for per-extension sets that may be defined programmatically.
+ typedef std::map<HostID, linked_ptr<UserScriptSet> > UserScriptSetMap;
+
+ // content::RenderProcessObserver implementation.
+ bool OnControlMessageReceived(const IPC::Message& message) override;
+
+ UserScriptSet* GetProgrammaticScriptsByHostID(const HostID& host_id);
+
+ // Handle the UpdateUserScripts extension message.
+ void OnUpdateUserScripts(base::SharedMemoryHandle shared_memory,
+ const HostID& host_id,
+ const std::set<HostID>& changed_hosts,
+ bool whitelisted_only);
+
+ // Scripts statically defined in extension manifests.
+ UserScriptSet static_scripts_;
+
+ // Scripts programmatically-defined through API calls (initialized and stored
+ // per-extension).
+ UserScriptSetMap programmatic_scripts_;
+
+ // The associated observers.
+ base::ObserverList<Observer> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserScriptSetManager);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_USER_SCRIPT_SET_MANAGER_H_
diff --git a/chromium/extensions/renderer/utils_native_handler.cc b/chromium/extensions/renderer/utils_native_handler.cc
new file mode 100644
index 00000000000..741a111b779
--- /dev/null
+++ b/chromium/extensions/renderer/utils_native_handler.cc
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/utils_native_handler.h"
+
+#include "base/macros.h"
+#include "extensions/renderer/script_context.h"
+#include "third_party/WebKit/public/web/WebSerializedScriptValue.h"
+
+namespace extensions {
+
+UtilsNativeHandler::UtilsNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context) {
+ RouteFunction(
+ "deepCopy",
+ base::Bind(&UtilsNativeHandler::DeepCopy, base::Unretained(this)));
+}
+
+UtilsNativeHandler::~UtilsNativeHandler() {}
+
+void UtilsNativeHandler::DeepCopy(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ args.GetReturnValue().Set(
+ blink::WebSerializedScriptValue::serialize(args[0]).deserialize());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/utils_native_handler.h b/chromium/extensions/renderer/utils_native_handler.h
new file mode 100644
index 00000000000..6a2ae4da350
--- /dev/null
+++ b/chromium/extensions/renderer/utils_native_handler.h
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_UTILS_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_UTILS_NATIVE_HANDLER_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+class ScriptContext;
+
+class UtilsNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit UtilsNativeHandler(ScriptContext* context);
+ ~UtilsNativeHandler() override;
+
+ private:
+ // |args| consists of one argument: an arbitrary value. Returns a deep copy of
+ // that value. The copy will have no references to nested values of the
+ // argument.
+ void DeepCopy(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ DISALLOW_COPY_AND_ASSIGN(UtilsNativeHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_UTILS_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/utils_unittest.cc b/chromium/extensions/renderer/utils_unittest.cc
new file mode 100644
index 00000000000..485c214ea66
--- /dev/null
+++ b/chromium/extensions/renderer/utils_unittest.cc
@@ -0,0 +1,77 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/stringprintf.h"
+#include "extensions/renderer/module_system_test.h"
+#include "gin/dictionary.h"
+#include "grit/extensions_renderer_resources.h"
+
+namespace extensions {
+namespace {
+
+class UtilsUnittest : public ModuleSystemTest {
+ private:
+ void SetUp() override {
+ ModuleSystemTest::SetUp();
+
+ env()->RegisterModule("utils", IDR_UTILS_JS);
+ env()->RegisterTestFile("utils_unittest", "utils_unittest.js");
+ env()->OverrideNativeHandler("schema_registry",
+ "exports.$set('GetSchema', function() {});");
+ env()->OverrideNativeHandler("logging",
+ "exports.$set('CHECK', function() {});\n"
+ "exports.$set('DCHECK', function() {});\n"
+ "exports.$set('WARNING', function() {});");
+ env()->OverrideNativeHandler("v8_context", "");
+ gin::Dictionary chrome(env()->isolate(), env()->CreateGlobal("chrome"));
+ gin::Dictionary chrome_runtime(
+ gin::Dictionary::CreateEmpty(env()->isolate()));
+ chrome.Set("runtime", chrome_runtime);
+ }
+};
+
+TEST_F(UtilsUnittest, TestNothing) {
+ ExpectNoAssertionsMade();
+}
+
+TEST_F(UtilsUnittest, SuperClass) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->module_system()->CallModuleMethod("utils_unittest", "testSuperClass");
+}
+
+TEST_F(UtilsUnittest, PromiseNoResult) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->module_system()->CallModuleMethod("utils_unittest",
+ "testPromiseNoResult");
+ RunResolvedPromises();
+}
+
+TEST_F(UtilsUnittest, PromiseOneResult) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->module_system()->CallModuleMethod("utils_unittest",
+ "testPromiseOneResult");
+ RunResolvedPromises();
+}
+
+TEST_F(UtilsUnittest, PromiseTwoResults) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->module_system()->CallModuleMethod("utils_unittest",
+ "testPromiseTwoResults");
+ RunResolvedPromises();
+}
+
+TEST_F(UtilsUnittest, PromiseError) {
+ ModuleSystem::NativesEnabledScope natives_enabled_scope(
+ env()->module_system());
+ env()->module_system()->CallModuleMethod("utils_unittest",
+ "testPromiseError");
+ RunResolvedPromises();
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/renderer/v8_context_native_handler.cc b/chromium/extensions/renderer/v8_context_native_handler.cc
new file mode 100644
index 00000000000..1ac53c5be00
--- /dev/null
+++ b/chromium/extensions/renderer/v8_context_native_handler.cc
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/v8_context_native_handler.h"
+
+#include "base/bind.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+
+namespace extensions {
+
+V8ContextNativeHandler::V8ContextNativeHandler(ScriptContext* context)
+ : ObjectBackedNativeHandler(context), context_(context) {
+ RouteFunction("GetAvailability",
+ base::Bind(&V8ContextNativeHandler::GetAvailability,
+ base::Unretained(this)));
+ RouteFunction("GetModuleSystem",
+ base::Bind(&V8ContextNativeHandler::GetModuleSystem,
+ base::Unretained(this)));
+ RouteFunction(
+ "RunWithNativesEnabled",
+ base::Bind(&V8ContextNativeHandler::RunWithNativesEnabled,
+ base::Unretained(this)));
+}
+
+void V8ContextNativeHandler::GetAvailability(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(args.Length(), 1);
+ v8::Isolate* isolate = args.GetIsolate();
+ std::string api_name = *v8::String::Utf8Value(args[0]);
+ Feature::Availability availability = context_->GetAvailability(api_name);
+
+ v8::Local<v8::Object> ret = v8::Object::New(isolate);
+ ret->Set(v8::String::NewFromUtf8(isolate, "is_available"),
+ v8::Boolean::New(isolate, availability.is_available()));
+ ret->Set(v8::String::NewFromUtf8(isolate, "message"),
+ v8::String::NewFromUtf8(isolate, availability.message().c_str()));
+ ret->Set(v8::String::NewFromUtf8(isolate, "result"),
+ v8::Integer::New(isolate, availability.result()));
+ args.GetReturnValue().Set(ret);
+}
+
+void V8ContextNativeHandler::GetModuleSystem(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(args.Length(), 1);
+ CHECK(args[0]->IsObject());
+ ScriptContext* context = ScriptContextSet::GetContextByObject(
+ v8::Local<v8::Object>::Cast(args[0]));
+ if (blink::WebFrame::scriptCanAccess(context->web_frame()))
+ args.GetReturnValue().Set(context->module_system()->NewInstance());
+}
+
+void V8ContextNativeHandler::RunWithNativesEnabled(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(args.Length(), 1);
+ CHECK(args[0]->IsFunction());
+ ModuleSystem::NativesEnabledScope natives_enabled(context()->module_system());
+ context()->CallFunction(v8::Local<v8::Function>::Cast(args[0]));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/v8_context_native_handler.h b/chromium/extensions/renderer/v8_context_native_handler.h
new file mode 100644
index 00000000000..956ef6c68ce
--- /dev/null
+++ b/chromium/extensions/renderer/v8_context_native_handler.h
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_V8_CONTEXT_NATIVE_HANDLER_H_
+#define EXTENSIONS_RENDERER_V8_CONTEXT_NATIVE_HANDLER_H_
+
+#include "extensions/renderer/object_backed_native_handler.h"
+
+namespace extensions {
+
+class Dispatcher;
+
+class V8ContextNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ explicit V8ContextNativeHandler(ScriptContext* context);
+
+ private:
+ void GetAvailability(const v8::FunctionCallbackInfo<v8::Value>& args);
+ void GetModuleSystem(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ void RunWithNativesEnabled(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ ScriptContext* context_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_V8_CONTEXT_NATIVE_HANDLER_H_
diff --git a/chromium/extensions/renderer/v8_helpers.h b/chromium/extensions/renderer/v8_helpers.h
new file mode 100644
index 00000000000..0a3b2ebaad9
--- /dev/null
+++ b/chromium/extensions/renderer/v8_helpers.h
@@ -0,0 +1,166 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_V8_HELPERS_H_
+#define EXTENSIONS_RENDERER_V8_HELPERS_H_
+
+#include <stdint.h>
+#include <string.h>
+
+#include "base/strings/string_number_conversions.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+namespace v8_helpers {
+
+// Helper functions for V8 APIs.
+
+// Converts |str| to a V8 string. Returns true on success.
+inline bool ToV8String(v8::Isolate* isolate,
+ const char* str,
+ v8::Local<v8::String>* out) {
+ return v8::String::NewFromUtf8(isolate, str, v8::NewStringType::kNormal)
+ .ToLocal(out);
+}
+
+inline bool ToV8String(v8::Isolate* isolate,
+ const std::string& str,
+ v8::Local<v8::String>* out) {
+ return ToV8String(isolate, str.c_str(), out);
+}
+
+// Converts |str| to a V8 string.
+// This crashes when strlen(str) > v8::String::kMaxLength.
+inline v8::Local<v8::String> ToV8StringUnsafe(
+ v8::Isolate* isolate,
+ const char* str,
+ v8::NewStringType string_type = v8::NewStringType::kNormal) {
+ DCHECK(strlen(str) <= v8::String::kMaxLength);
+ return v8::String::NewFromUtf8(isolate, str, string_type)
+ .ToLocalChecked();
+}
+
+inline v8::Local<v8::String> ToV8StringUnsafe(
+ v8::Isolate* isolate,
+ const std::string& str,
+ v8::NewStringType string_type = v8::NewStringType::kNormal) {
+ return ToV8StringUnsafe(isolate, str.c_str(), string_type);
+}
+
+// Returns true if |maybe| is both a value, and that value is true.
+inline bool IsTrue(v8::Maybe<bool> maybe) {
+ return maybe.IsJust() && maybe.FromJust();
+}
+
+// Returns true if |value| is empty or undefined.
+inline bool IsEmptyOrUndefied(v8::Local<v8::Value> value) {
+ return value.IsEmpty() || value->IsUndefined();
+}
+
+// SetProperty() family wraps V8::Object::DefineOwnProperty().
+// Returns true on success.
+// NOTE: Think about whether you want this or SetPrivateProperty() below.
+// TODO(devlin): Sort through more of the callers of this and see if we can
+// convert more to be private.
+inline bool SetProperty(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ v8::Local<v8::String> key,
+ v8::Local<v8::Value> value) {
+ return IsTrue(object->DefineOwnProperty(context, key, value));
+}
+
+// Wraps v8::Object::SetPrivate(). When possible, prefer this to SetProperty().
+inline bool SetPrivateProperty(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ v8::Local<v8::String> key,
+ v8::Local<v8::Value> value) {
+ return IsTrue(object->SetPrivate(
+ context, v8::Private::ForApi(context->GetIsolate(), key), value));
+}
+
+inline bool SetPrivateProperty(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ const char* key,
+ v8::Local<v8::Value> value) {
+ v8::Local<v8::String> v8_key;
+ return ToV8String(context->GetIsolate(), key, &v8_key) &&
+ IsTrue(object->SetPrivate(
+ context, v8::Private::ForApi(context->GetIsolate(), v8_key),
+ value));
+}
+
+// GetProperty() family calls V8::Object::Get() and extracts a value from
+// returned MaybeLocal. Returns true on success.
+// NOTE: Think about whether you want this or GetPrivateProperty() below.
+template <typename Key>
+inline bool GetProperty(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ Key key,
+ v8::Local<v8::Value>* out) {
+ return object->Get(context, key).ToLocal(out);
+}
+
+inline bool GetProperty(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ const char* key,
+ v8::Local<v8::Value>* out) {
+ v8::Local<v8::String> v8_key;
+ if (!ToV8String(context->GetIsolate(), key, &v8_key))
+ return false;
+ return GetProperty(context, object, v8_key, out);
+}
+
+// Wraps v8::Object::GetPrivate(). When possible, prefer this to GetProperty().
+inline bool GetPrivateProperty(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ v8::Local<v8::String> key,
+ v8::Local<v8::Value>* out) {
+ return object
+ ->GetPrivate(context, v8::Private::ForApi(context->GetIsolate(), key))
+ .ToLocal(out);
+}
+
+inline bool GetPrivateProperty(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ const char* key,
+ v8::Local<v8::Value>* out) {
+ v8::Local<v8::String> v8_key;
+ return ToV8String(context->GetIsolate(), key, &v8_key) &&
+ GetPrivateProperty(context, object, v8_key, out);
+}
+
+// GetPropertyUnsafe() family wraps v8::Object::Get(). They crash when an
+// exception is thrown.
+inline v8::Local<v8::Value> GetPropertyUnsafe(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ v8::Local<v8::Value> key) {
+ return object->Get(context, key).ToLocalChecked();
+}
+
+inline v8::Local<v8::Value> GetPropertyUnsafe(
+ v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ const char* key,
+ v8::NewStringType string_type = v8::NewStringType::kNormal) {
+ return object->Get(context,
+ ToV8StringUnsafe(context->GetIsolate(), key, string_type))
+ .ToLocalChecked();
+}
+
+// Wraps v8::Function::Call(). Returns true on success.
+inline bool CallFunction(v8::Local<v8::Context> context,
+ v8::Local<v8::Function> function,
+ v8::Local<v8::Value> recv,
+ int argc,
+ v8::Local<v8::Value> argv[],
+ v8::Local<v8::Value>* out) {
+ v8::MicrotasksScope microtasks_scope(
+ context->GetIsolate(), v8::MicrotasksScope::kDoNotRunMicrotasks);
+ return function->Call(context, recv, argc, argv).ToLocal(out);
+}
+
+} // namespace v8_helpers
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_V8_HELPERS_H_
diff --git a/chromium/extensions/renderer/v8_schema_registry.cc b/chromium/extensions/renderer/v8_schema_registry.cc
new file mode 100644
index 00000000000..3bd7c479122
--- /dev/null
+++ b/chromium/extensions/renderer/v8_schema_registry.cc
@@ -0,0 +1,142 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/v8_schema_registry.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "content/public/child/v8_value_converter.h"
+#include "extensions/common/extension_api.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "extensions/renderer/script_context.h"
+
+using content::V8ValueConverter;
+
+namespace extensions {
+
+namespace {
+
+// Recursively freezes every v8 object on |object|.
+void DeepFreeze(const v8::Local<v8::Object>& object,
+ const v8::Local<v8::Context>& context) {
+ // Don't let the object trace upwards via the prototype.
+ v8::Maybe<bool> maybe =
+ object->SetPrototype(context, v8::Null(context->GetIsolate()));
+ CHECK(maybe.IsJust() && maybe.FromJust());
+ v8::Local<v8::Array> property_names = object->GetOwnPropertyNames();
+ for (uint32_t i = 0; i < property_names->Length(); ++i) {
+ v8::Local<v8::Value> child = object->Get(property_names->Get(i));
+ if (child->IsObject())
+ DeepFreeze(v8::Local<v8::Object>::Cast(child), context);
+ }
+ object->SetIntegrityLevel(context, v8::IntegrityLevel::kFrozen);
+}
+
+class SchemaRegistryNativeHandler : public ObjectBackedNativeHandler {
+ public:
+ SchemaRegistryNativeHandler(V8SchemaRegistry* registry,
+ scoped_ptr<ScriptContext> context)
+ : ObjectBackedNativeHandler(context.get()),
+ context_(std::move(context)),
+ registry_(registry) {
+ RouteFunction("GetSchema",
+ base::Bind(&SchemaRegistryNativeHandler::GetSchema,
+ base::Unretained(this)));
+ }
+
+ ~SchemaRegistryNativeHandler() override { context_->Invalidate(); }
+
+ private:
+ void GetSchema(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(
+ registry_->GetSchema(*v8::String::Utf8Value(args[0])));
+ }
+
+ scoped_ptr<ScriptContext> context_;
+ V8SchemaRegistry* registry_;
+};
+
+} // namespace
+
+V8SchemaRegistry::V8SchemaRegistry() {
+}
+
+V8SchemaRegistry::~V8SchemaRegistry() {
+}
+
+scoped_ptr<NativeHandler> V8SchemaRegistry::AsNativeHandler() {
+ scoped_ptr<ScriptContext> context(
+ new ScriptContext(GetOrCreateContext(v8::Isolate::GetCurrent()),
+ NULL, // no frame
+ NULL, // no extension
+ Feature::UNSPECIFIED_CONTEXT,
+ NULL, // no effective extension
+ Feature::UNSPECIFIED_CONTEXT));
+ return scoped_ptr<NativeHandler>(
+ new SchemaRegistryNativeHandler(this, std::move(context)));
+}
+
+v8::Local<v8::Array> V8SchemaRegistry::GetSchemas(
+ const std::vector<std::string>& apis) {
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ v8::EscapableHandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(GetOrCreateContext(isolate));
+
+ v8::Local<v8::Array> v8_apis(v8::Array::New(isolate, apis.size()));
+ size_t api_index = 0;
+ for (std::vector<std::string>::const_iterator i = apis.begin();
+ i != apis.end();
+ ++i) {
+ v8_apis->Set(api_index++, GetSchema(*i));
+ }
+ return handle_scope.Escape(v8_apis);
+}
+
+v8::Local<v8::Object> V8SchemaRegistry::GetSchema(const std::string& api) {
+ if (schema_cache_ != NULL) {
+ v8::Local<v8::Object> cached_schema = schema_cache_->Get(api);
+ if (!cached_schema.IsEmpty()) {
+ return cached_schema;
+ }
+ }
+
+ // Slow path: Need to build schema first.
+
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ v8::EscapableHandleScope handle_scope(isolate);
+ v8::Local<v8::Context> context = GetOrCreateContext(isolate);
+ v8::Context::Scope context_scope(context);
+
+ const base::DictionaryValue* schema =
+ ExtensionAPI::GetSharedInstance()->GetSchema(api);
+ CHECK(schema) << api;
+ scoped_ptr<V8ValueConverter> v8_value_converter(V8ValueConverter::create());
+ v8::Local<v8::Value> value = v8_value_converter->ToV8Value(schema, context);
+ CHECK(!value.IsEmpty());
+
+ v8::Local<v8::Object> v8_schema(v8::Local<v8::Object>::Cast(value));
+ DeepFreeze(v8_schema, context);
+ schema_cache_->Set(api, v8_schema);
+
+ return handle_scope.Escape(v8_schema);
+}
+
+v8::Local<v8::Context> V8SchemaRegistry::GetOrCreateContext(
+ v8::Isolate* isolate) {
+ // It's ok to create local handles in this function, since this is only called
+ // when we have a HandleScope.
+ if (!context_holder_) {
+ context_holder_.reset(new gin::ContextHolder(isolate));
+ context_holder_->SetContext(v8::Context::New(isolate));
+ schema_cache_.reset(new SchemaCache(isolate));
+ return context_holder_->context();
+ }
+ return context_holder_->context();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/v8_schema_registry.h b/chromium/extensions/renderer/v8_schema_registry.h
new file mode 100644
index 00000000000..08f54b62658
--- /dev/null
+++ b/chromium/extensions/renderer/v8_schema_registry.h
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_V8_SCHEMA_REGISTRY_H_
+#define EXTENSIONS_RENDERER_V8_SCHEMA_REGISTRY_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "gin/public/context_holder.h"
+#include "v8/include/v8-util.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+class NativeHandler;
+
+// A registry for the v8::Value representations of extension API schemas.
+// In a way, the v8 counterpart to ExtensionAPI.
+class V8SchemaRegistry {
+ public:
+ V8SchemaRegistry();
+ ~V8SchemaRegistry();
+
+ // Creates a NativeHandler wrapper |this|. Supports GetSchema.
+ scoped_ptr<NativeHandler> AsNativeHandler();
+
+ // Returns a v8::Array with all the schemas for the APIs in |apis|.
+ v8::Local<v8::Array> GetSchemas(const std::vector<std::string>& apis);
+
+ // Returns a v8::Object for the schema for |api|, possibly from the cache.
+ v8::Local<v8::Object> GetSchema(const std::string& api);
+
+ private:
+ // Gets the separate context that backs the registry, creating a new one if
+ // if necessary. Will also initialize schema_cache_.
+ v8::Local<v8::Context> GetOrCreateContext(v8::Isolate* isolate);
+
+ // Cache of schemas. Created lazily by GetOrCreateContext.
+ typedef v8::StdGlobalValueMap<std::string, v8::Object> SchemaCache;
+ scoped_ptr<SchemaCache> schema_cache_;
+
+ // Single per-instance gin::ContextHolder to create v8::Values.
+ // Created lazily via GetOrCreateContext.
+ scoped_ptr<gin::ContextHolder> context_holder_;
+
+ DISALLOW_COPY_AND_ASSIGN(V8SchemaRegistry);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_V8_SCHEMA_REGISTRY_H_
diff --git a/chromium/extensions/renderer/wake_event_page.cc b/chromium/extensions/renderer/wake_event_page.cc
new file mode 100644
index 00000000000..e3e21899bef
--- /dev/null
+++ b/chromium/extensions/renderer/wake_event_page.cc
@@ -0,0 +1,200 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/wake_event_page.h"
+
+#include <utility>
+
+#include "base/atomic_sequence_num.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/public/child/worker_thread.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/v8_helpers.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_macros.h"
+
+namespace extensions {
+
+using namespace v8_helpers;
+
+namespace {
+
+base::LazyInstance<WakeEventPage> g_instance = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+class WakeEventPage::WakeEventPageNativeHandler
+ : public ObjectBackedNativeHandler {
+ public:
+ // Handles own lifetime.
+ WakeEventPageNativeHandler(ScriptContext* context,
+ const std::string& name,
+ const MakeRequestCallback& make_request)
+ : ObjectBackedNativeHandler(context),
+ make_request_(make_request),
+ weak_ptr_factory_(this) {
+ // Use Unretained not a WeakPtr because RouteFunction is tied to the
+ // lifetime of this, so there is no way for DoWakeEventPage to be called
+ // after destruction.
+ RouteFunction(name, base::Bind(&WakeEventPageNativeHandler::DoWakeEventPage,
+ base::Unretained(this)));
+ // Delete self on invalidation. base::Unretained because by definition this
+ // can't be deleted before it's deleted.
+ context->AddInvalidationObserver(base::Bind(
+ &WakeEventPageNativeHandler::DeleteSelf, base::Unretained(this)));
+ };
+
+ ~WakeEventPageNativeHandler() override {}
+
+ private:
+ void DeleteSelf() {
+ Invalidate();
+ delete this;
+ }
+
+ // Called by JavaScript with a single argument, the function to call when the
+ // event page has been woken.
+ void DoWakeEventPage(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsFunction());
+ v8::Global<v8::Function> callback(args.GetIsolate(),
+ args[0].As<v8::Function>());
+
+ const std::string& extension_id = context()->GetExtensionID();
+ CHECK(!extension_id.empty());
+
+ make_request_.Run(
+ extension_id,
+ base::Bind(&WakeEventPageNativeHandler::OnEventPageIsAwake,
+ weak_ptr_factory_.GetWeakPtr(), base::Passed(&callback)));
+ }
+
+ void OnEventPageIsAwake(v8::Global<v8::Function> callback, bool success) {
+ v8::Isolate* isolate = context()->isolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Value> args[] = {
+ v8::Boolean::New(isolate, success),
+ };
+ context()->CallFunction(v8::Local<v8::Function>::New(isolate, callback),
+ arraysize(args), args);
+ }
+
+ MakeRequestCallback make_request_;
+ base::WeakPtrFactory<WakeEventPageNativeHandler> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WakeEventPageNativeHandler);
+};
+
+// static
+WakeEventPage* WakeEventPage::Get() {
+ return g_instance.Pointer();
+}
+
+void WakeEventPage::Init(content::RenderThread* render_thread) {
+ DCHECK(render_thread);
+ DCHECK_EQ(content::RenderThread::Get(), render_thread);
+ DCHECK(!message_filter_);
+
+ message_filter_ = render_thread->GetSyncMessageFilter();
+ render_thread->AddObserver(this);
+}
+
+v8::Local<v8::Function> WakeEventPage::GetForContext(ScriptContext* context) {
+ DCHECK(message_filter_);
+
+ v8::Isolate* isolate = context->isolate();
+ v8::EscapableHandleScope handle_scope(isolate);
+ v8::Handle<v8::Context> v8_context = context->v8_context();
+ v8::Context::Scope context_scope(v8_context);
+
+ // Cache the imported function as a hidden property on the global object of
+ // |v8_context|. Creating it isn't free.
+ v8::Local<v8::Private> kWakeEventPageKey =
+ v8::Private::ForApi(isolate, ToV8StringUnsafe(isolate, "WakeEventPage"));
+ v8::Local<v8::Value> wake_event_page;
+ if (!v8_context->Global()
+ ->GetPrivate(v8_context, kWakeEventPageKey)
+ .ToLocal(&wake_event_page) ||
+ wake_event_page->IsUndefined()) {
+ // Implement this using a NativeHandler, which requires a function name
+ // (arbitrary in this case). Handles own lifetime.
+ const char* kFunctionName = "WakeEventPage";
+ WakeEventPageNativeHandler* native_handler = new WakeEventPageNativeHandler(
+ context, kFunctionName, base::Bind(&WakeEventPage::MakeRequest,
+ // Safe, owned by a LazyInstance.
+ base::Unretained(this)));
+
+ // Extract and cache the wake-event-page function from the native handler.
+ wake_event_page = GetPropertyUnsafe(
+ v8_context, native_handler->NewInstance(), kFunctionName);
+ v8_context->Global()
+ ->SetPrivate(v8_context, kWakeEventPageKey, wake_event_page)
+ .FromJust();
+ }
+
+ CHECK(wake_event_page->IsFunction());
+ return handle_scope.Escape(wake_event_page.As<v8::Function>());
+}
+
+WakeEventPage::RequestData::RequestData(int thread_id,
+ const OnResponseCallback& on_response)
+ : thread_id(thread_id), on_response(on_response) {}
+
+WakeEventPage::RequestData::~RequestData() {}
+
+WakeEventPage::WakeEventPage() {}
+
+WakeEventPage::~WakeEventPage() {}
+
+void WakeEventPage::MakeRequest(const std::string& extension_id,
+ const OnResponseCallback& on_response) {
+ static base::AtomicSequenceNumber sequence_number;
+ int request_id = sequence_number.GetNext();
+ {
+ scoped_ptr<RequestData> request_data(
+ new RequestData(content::WorkerThread::GetCurrentId(), on_response));
+ base::AutoLock lock(requests_lock_);
+ requests_.set(request_id, std::move(request_data));
+ }
+ message_filter_->Send(
+ new ExtensionHostMsg_WakeEventPage(request_id, extension_id));
+}
+
+bool WakeEventPage::OnControlMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(WakeEventPage, message)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_WakeEventPageResponse,
+ OnWakeEventPageResponse)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void WakeEventPage::OnWakeEventPageResponse(int request_id, bool success) {
+ scoped_ptr<RequestData> request_data;
+ {
+ base::AutoLock lock(requests_lock_);
+ request_data = requests_.take(request_id);
+ }
+ CHECK(request_data) << "No request with ID " << request_id;
+ if (request_data->thread_id == 0) {
+ // Thread ID of 0 means it wasn't called on a worker thread, so safe to
+ // call immediately.
+ request_data->on_response.Run(success);
+ } else {
+ content::WorkerThread::PostTask(
+ request_data->thread_id,
+ base::Bind(request_data->on_response, success));
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/wake_event_page.h b/chromium/extensions/renderer/wake_event_page.h
new file mode 100644
index 00000000000..48bfd4bedb4
--- /dev/null
+++ b/chromium/extensions/renderer/wake_event_page.h
@@ -0,0 +1,116 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_WAKE_EVENT_PAGE_H_
+#define EXTENSIONS_RENDERER_WAKE_EVENT_PAGE_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/containers/scoped_ptr_hash_map.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/synchronization/lock.h"
+#include "content/public/renderer/render_process_observer.h"
+#include "ipc/ipc_sync_message_filter.h"
+#include "v8/include/v8.h"
+
+namespace content {
+class RenderThread;
+}
+
+namespace extensions {
+class ScriptContext;
+
+// This class implements the wake-event-page JavaScript function, which wakes
+// an event page and runs a callback when done.
+//
+// Note, the function will do a round trip to the browser even if event page is
+// open. Any optimisation to prevent this must be at the JavaScript level.
+class WakeEventPage : public content::RenderProcessObserver {
+ public:
+ WakeEventPage();
+ ~WakeEventPage() override;
+
+ // Returns the single instance of the WakeEventPage object.
+ //
+ // Thread safe.
+ static WakeEventPage* Get();
+
+ // Initializes the WakeEventPage.
+ //
+ // This must be called before any bindings are installed, and must be called
+ // on the render thread.
+ void Init(content::RenderThread* render_thread);
+
+ // Returns the wake-event-page function bound to a given context. The
+ // function will be cached as a hidden value in the context's global object.
+ //
+ // To mix C++ and JavaScript, example usage might be:
+ //
+ // WakeEventPage::Get().GetForContext(context)(function() {
+ // ...
+ // });
+ //
+ // Thread safe.
+ v8::Local<v8::Function> GetForContext(ScriptContext* context);
+
+ private:
+ class WakeEventPageNativeHandler;
+
+ // The response from an ExtensionHostMsg_WakeEvent call, passed true if the
+ // call was successful, false on failure.
+ using OnResponseCallback = base::Callback<void(bool)>;
+
+ // Makes an ExtensionHostMsg_WakeEvent request for an extension ID. The
+ // second argument is a callback to run when the request has completed.
+ using MakeRequestCallback =
+ base::Callback<void(const std::string&, const OnResponseCallback&)>;
+
+ // For |requests_|.
+ struct RequestData {
+ RequestData(int thread_id, const OnResponseCallback& on_response);
+ ~RequestData();
+
+ // The thread ID the request was made on. |on_response| must be called on
+ // that thread.
+ int thread_id;
+
+ // Callback to run when the response to the request arrives.
+ OnResponseCallback on_response;
+ };
+
+ // Runs |on_response|, passing it |success|.
+ static void RunOnResponseWithResult(const OnResponseCallback& on_response,
+ bool success);
+
+ // Sends the ExtensionHostMsg_WakeEvent IPC for |extension_id|, and
+ // updates |requests_| bookkeeping.
+ void MakeRequest(const std::string& extension_id,
+ const OnResponseCallback& on_response);
+
+ // content::RenderProcessObserver:
+ bool OnControlMessageReceived(const IPC::Message& message) override;
+
+ // OnControlMessageReceived handlers:
+ void OnWakeEventPageResponse(int request_id, bool success);
+
+ // IPC sender. Belongs to the render thread, but thread safe.
+ scoped_refptr<IPC::SyncMessageFilter> message_filter_;
+
+ // All in-flight requests, keyed by request ID. Used on multiple threads, so
+ // must be guarded by |requests_lock_|.
+ base::ScopedPtrHashMap<int, scoped_ptr<RequestData>> requests_;
+
+ // Lock for |requests_|.
+ base::Lock requests_lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(WakeEventPage);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_WAKE_EVENT_PAGE_H_
diff --git a/chromium/extensions/renderer/web_ui_injection_host.cc b/chromium/extensions/renderer/web_ui_injection_host.cc
new file mode 100644
index 00000000000..1fd436452a8
--- /dev/null
+++ b/chromium/extensions/renderer/web_ui_injection_host.cc
@@ -0,0 +1,34 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/web_ui_injection_host.h"
+
+WebUIInjectionHost::WebUIInjectionHost(const HostID& host_id)
+ : InjectionHost(host_id),
+ url_(host_id.id()) {
+}
+
+WebUIInjectionHost::~WebUIInjectionHost() {
+}
+
+std::string WebUIInjectionHost::GetContentSecurityPolicy() const {
+ return std::string();
+}
+
+const GURL& WebUIInjectionHost::url() const {
+ return url_;
+}
+
+const std::string& WebUIInjectionHost::name() const {
+ return id().id();
+}
+
+extensions::PermissionsData::AccessType WebUIInjectionHost::CanExecuteOnFrame(
+ const GURL& document_url,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ bool is_declarative) const {
+ // Content scripts are allowed to inject on webviews created by WebUI.
+ return extensions::PermissionsData::AccessType::ACCESS_ALLOWED;
+}
diff --git a/chromium/extensions/renderer/web_ui_injection_host.h b/chromium/extensions/renderer/web_ui_injection_host.h
new file mode 100644
index 00000000000..a4df5f79fb1
--- /dev/null
+++ b/chromium/extensions/renderer/web_ui_injection_host.h
@@ -0,0 +1,33 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_WEBUI_INJECTION_HOST_H_
+#define EXTENSIONS_RENDERER_WEBUI_INJECTION_HOST_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/injection_host.h"
+
+class WebUIInjectionHost : public InjectionHost {
+ public:
+ WebUIInjectionHost(const HostID& host_id);
+ ~WebUIInjectionHost() override;
+
+ private:
+ // InjectionHost:
+ std::string GetContentSecurityPolicy() const override;
+ const GURL& url() const override;
+ const std::string& name() const override;
+ extensions::PermissionsData::AccessType CanExecuteOnFrame(
+ const GURL& document_url,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ bool is_declarative) const override;
+
+ private:
+ GURL url_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebUIInjectionHost);
+};
+
+#endif // EXTENSIONS_RENDERER_WEBUI_INJECTION_HOST_H_
diff --git a/chromium/extensions/renderer/worker_script_context_set.cc b/chromium/extensions/renderer/worker_script_context_set.cc
new file mode 100644
index 00000000000..a05fdbc77c6
--- /dev/null
+++ b/chromium/extensions/renderer/worker_script_context_set.cc
@@ -0,0 +1,82 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/worker_script_context_set.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "extensions/renderer/script_context.h"
+
+namespace extensions {
+
+using ContextVector = ScopedVector<ScriptContext>;
+
+namespace {
+
+// Returns an iterator to the ScriptContext associated with |v8_context| from
+// |contexts|, or |contexts|->end() if not found.
+ContextVector::iterator FindContext(ContextVector* contexts,
+ v8::Local<v8::Context> v8_context) {
+ auto context_matches = [&v8_context](ScriptContext* context) {
+ v8::HandleScope handle_scope(context->isolate());
+ v8::Context::Scope context_scope(context->v8_context());
+ return context->v8_context() == v8_context;
+ };
+ return std::find_if(contexts->begin(), contexts->end(), context_matches);
+}
+
+} // namespace
+
+WorkerScriptContextSet::WorkerScriptContextSet() {}
+
+WorkerScriptContextSet::~WorkerScriptContextSet() {}
+
+void WorkerScriptContextSet::Insert(scoped_ptr<ScriptContext> context) {
+ DCHECK_GT(content::WorkerThread::GetCurrentId(), 0)
+ << "Must be called on a worker thread";
+ ContextVector* contexts = contexts_tls_.Get();
+ if (!contexts) {
+ // First context added for this thread. Create a new set, then wait for
+ // this thread's shutdown.
+ contexts = new ContextVector();
+ contexts_tls_.Set(contexts);
+ content::WorkerThread::AddObserver(this);
+ }
+ CHECK(FindContext(contexts, context->v8_context()) == contexts->end())
+ << "Worker for " << context->url() << " is already in this set";
+ contexts->push_back(std::move(context));
+}
+
+void WorkerScriptContextSet::Remove(v8::Local<v8::Context> v8_context,
+ const GURL& url) {
+ DCHECK_GT(content::WorkerThread::GetCurrentId(), 0)
+ << "Must be called on a worker thread";
+ ContextVector* contexts = contexts_tls_.Get();
+ if (!contexts) {
+ // Thread has already been torn down, and |v8_context| removed. I'm not
+ // sure this can actually happen (depends on in what order blink fires
+ // events), but SW lifetime has bitten us before, so be cautious.
+ return;
+ }
+ auto context_it = FindContext(contexts, v8_context);
+ CHECK(context_it != contexts->end()) << "Worker for " << url
+ << " is not in this set";
+ ScriptContext* context = *context_it;
+ DCHECK_EQ(url, context->url());
+ context->Invalidate();
+ contexts->erase(context_it);
+}
+
+void WorkerScriptContextSet::WillStopCurrentWorkerThread() {
+ content::WorkerThread::RemoveObserver(this);
+ ContextVector* contexts = contexts_tls_.Get();
+ DCHECK(contexts);
+ for (ScriptContext* context : *contexts)
+ context->Invalidate();
+ contexts_tls_.Set(nullptr);
+ delete contexts;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/renderer/worker_script_context_set.h b/chromium/extensions/renderer/worker_script_context_set.h
new file mode 100644
index 00000000000..dd33a77d6a7
--- /dev/null
+++ b/chromium/extensions/renderer/worker_script_context_set.h
@@ -0,0 +1,46 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_WORKER_SCRIPT_CONTEXT_SET_H_
+#define EXTENSIONS_RENDERER_WORKER_SCRIPT_CONTEXT_SET_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/threading/thread_local.h"
+#include "content/public/child/worker_thread.h"
+#include "url/gurl.h"
+#include "v8/include/v8.h"
+
+namespace extensions {
+
+class ScriptContext;
+
+// A set of ScriptContexts owned by worker threads. Thread safe.
+class WorkerScriptContextSet : public content::WorkerThread::Observer {
+ public:
+ WorkerScriptContextSet();
+
+ ~WorkerScriptContextSet() override;
+
+ // Inserts |context| into the set. Contexts are stored in TLS.
+ void Insert(scoped_ptr<ScriptContext> context);
+
+ // Removes the ScriptContext associated with |v8_context| which was added for
+ // |url| (used for sanity checking).
+ void Remove(v8::Local<v8::Context> v8_context, const GURL& url);
+
+ private:
+ // WorkerThread::Observer:
+ void WillStopCurrentWorkerThread() override;
+
+ // Implement thread safety by storing each ScriptContext in TLS.
+ base::ThreadLocalPointer<ScopedVector<ScriptContext>> contexts_tls_;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkerScriptContextSet);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_RENDERER_WORKER_SCRIPT_CONTEXT_SET_H_
diff --git a/chromium/extensions/shell/BUILD.gn b/chromium/extensions/shell/BUILD.gn
new file mode 100644
index 00000000000..fca3ec3e927
--- /dev/null
+++ b/chromium/extensions/shell/BUILD.gn
@@ -0,0 +1,226 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//extensions/shell/app_shell.gni")
+
+# Technically, this directory should not depend on files from src/chrome, but
+# that's where the VERSION file is. This should probably all be moved to
+# src/build.
+import("//chrome/version.gni")
+import("//testing/test.gni")
+import("//tools/grit/grit_rule.gni")
+
+assert(enable_extensions)
+
+grit("resources") {
+ source = "app_shell_resources.grd"
+ outputs = [
+ "grit/app_shell_resources.h",
+ "app_shell_resources.pak",
+ ]
+}
+
+source_set("app_shell_lib") {
+ # TODO(jamescook): investigate and get rid of test dependencies. This library
+ # is testonly because it depends on testonly libraries, namely
+ # //content/shell:content_shell_lib. See http://crbug.com/438283
+ testonly = true
+ deps = [
+ ":resources",
+ ":version_header",
+ "//base",
+ "//components/devtools_discovery",
+ "//components/devtools_http_handler",
+ "//components/guest_view/browser",
+ "//components/guest_view/common",
+ "//components/guest_view/renderer",
+ "//components/pref_registry",
+ "//components/prefs",
+ "//components/update_client",
+ "//components/user_prefs",
+ "//components/web_cache/renderer",
+ "//content",
+ "//content/shell:content_shell_lib",
+ "//device/core",
+ "//device/hid",
+ "//extensions:extensions_resources",
+ "//extensions:shell_and_test_pak",
+ "//extensions/browser",
+ "//extensions/common",
+ "//extensions/common/api",
+ "//extensions/common/api:api_registration",
+ "//extensions/renderer",
+ "//extensions/shell/common/api",
+ "//extensions/shell/common/api:api_registration",
+ "//extensions/utility",
+ "//mojo/edk/system",
+ "//skia",
+ "//third_party/WebKit/public:blink",
+ "//ui/base",
+ "//ui/base/ime",
+ "//v8",
+ ]
+
+ sources = rebase_path(app_shell_gypi_values.app_shell_lib_sources,
+ ".",
+ "//extensions/shell")
+
+ if (use_aura) {
+ deps += [ "//ui/wm" ]
+
+ aura_sources = rebase_path(app_shell_gypi_values.app_shell_lib_sources_aura,
+ ".",
+ "//extensions/shell")
+ sources += aura_sources
+ }
+
+ if (is_chromeos) {
+ deps += [
+ "//chromeos",
+ "//ui/chromeos:ui_chromeos",
+ "//ui/display",
+ ]
+ chromeos_sources =
+ rebase_path(app_shell_gypi_values.app_shell_lib_sources_chromeos,
+ ".",
+ "//extensions/shell")
+ sources += chromeos_sources
+ }
+
+ if (enable_nacl) {
+ sources += [
+ "browser/shell_nacl_browser_delegate.cc",
+ "browser/shell_nacl_browser_delegate.h",
+ ]
+
+ deps += [
+ "//components/nacl/browser",
+ "//components/nacl/common",
+ "//components/nacl/loader",
+ "//components/nacl/renderer",
+ "//components/nacl/renderer/plugin:nacl_trusted_plugin",
+ ]
+
+ if (is_linux) {
+ deps += [
+ "//components/nacl/loader:helper_nonsfi",
+ "//components/nacl/loader:nacl_helper",
+ ]
+ }
+ }
+
+ if (cld_version == 2) {
+ deps += [ "//third_party/cld_2:cld2_platform_impl" ]
+ }
+}
+
+if (!(is_chromeos && !use_ozone)) {
+ executable("app_shell") {
+ # testonly because :app_shell_lib is testonly. See :app_shell_lib comment.
+ testonly = true
+ sources = rebase_path(app_shell_gypi_values.app_shell_sources,
+ ".",
+ "//extensions/shell")
+ deps = [
+ ":app_shell_lib",
+ "//build/config/sanitizers:deps",
+ "//extensions:shell_and_test_pak",
+ ]
+
+ if (is_win) {
+ configs += [ "//build/config/win:windowed" ]
+ configs -= [ "//build/config/win:console" ]
+ }
+
+ if (is_mac) {
+ # TODO(GYP) bug 546894: Fix GN and toolchains to handle spaces here.
+ #output_name = "App Shell"
+ # TODO(GYP): Mac bundling. See also content_shell which this is basically
+ # a copy-paste of.
+ deps += [ ":app_shell_framework" ]
+ # TODO(GYP): Mac app_shell_helper stuff.
+ }
+ }
+}
+
+test("app_shell_unittests") {
+ sources = rebase_path(app_shell_gypi_values.app_shell_unittests_sources,
+ ".",
+ "//extensions/shell")
+
+ data = [
+ "//extensions/test/data/",
+ "$root_out_dir/extensions_shell_and_test.pak",
+
+ #"$root_out_dir/natives_blob.bin", # move to gin
+ #"$root_out_dir/snapshot_blob.bin",
+ ]
+
+ deps = [
+ ":app_shell_lib",
+ "//base",
+ "//base/test:test_support",
+ "//content/test:test_support",
+ "//extensions:shell_and_test_pak",
+ "//extensions:test_support",
+ "//testing/gtest",
+ ]
+
+ data_deps = [
+ # "//gin", # TODO(dpranke): Either gin or v8 data is needed ...
+ "//third_party/mesa:osmesa",
+ ]
+
+ if (use_aura) {
+ deps += [ "//ui/aura:test_support" ]
+
+ aura_sources =
+ rebase_path(app_shell_gypi_values.app_shell_unittests_sources_aura,
+ ".",
+ "//extensions/shell")
+ sources += aura_sources
+ }
+
+ if (is_chromeos) {
+ deps += [ "//chromeos:test_support_without_gmock" ]
+
+ chromeos_sources =
+ rebase_path(app_shell_gypi_values.app_shell_unittests_sources_chromeos,
+ ".",
+ "//extensions/shell")
+ sources += chromeos_sources
+ }
+
+ # TODO(GYP): Enable this when //components/nacl GN is done.
+ if (false) {
+ if (use_nacl) {
+ nacl_sources =
+ rebase_path(app_shell_gypi_values.app_shell_unittests_sources_nacl,
+ ".",
+ "//extensions/shell")
+ sources += nacl_sources
+ }
+ }
+}
+
+process_version("version_header") {
+ template_file = "common/version.h.in"
+ output = "$target_gen_dir/common/version.h"
+}
+
+if (is_mac) {
+ # TODO(GYP) this should be a bundle. Lots of other stuff in this target.
+ # Should be able to copy content shell framework (this is basically a
+ # copy-paste of that target).
+ shared_library("app_shell_framework") {
+ testonly = true
+ sources = [
+ "app/shell_main_mac.cc",
+ "app/shell_main_mac.h",
+ ]
+ deps = [
+ ":app_shell_lib",
+ ]
+ }
+}
diff --git a/chromium/extensions/shell/DEPS b/chromium/extensions/shell/DEPS
new file mode 100644
index 00000000000..74cb6bba5c0
--- /dev/null
+++ b/chromium/extensions/shell/DEPS
@@ -0,0 +1,25 @@
+include_rules = [
+ # The apps module has dependencies on chrome.
+ "-apps",
+
+ # Individual subdirectories should have their own DEPS that include
+ # their allowed directories.
+ "-extensions/shell",
+ "+extensions/shell/common",
+ "+extensions/shell/test",
+
+ # Do not add dependencies on Chrome. Talk to OWNERS about how to refactor
+ # the code you need to a shared location.
+ "-chrome",
+
+ # The system.storage API expects the StorageMonitor to be initialized by
+ # the embedder before being used by the extension.
+ "+components/storage_monitor",
+
+ # Only allow app_shell and extensions resources, not general Chrome ones.
+ "-grit",
+ "+grit/app_shell_resources.h",
+ "+grit/extensions_resources.h",
+
+ # Real DEPS go in subdirectories, for example extensions/shell/browser/DEPS.
+]
diff --git a/chromium/extensions/shell/OWNERS b/chromium/extensions/shell/OWNERS
new file mode 100644
index 00000000000..32993524f78
--- /dev/null
+++ b/chromium/extensions/shell/OWNERS
@@ -0,0 +1,2 @@
+derat@chromium.org
+jamescook@chromium.org
diff --git a/chromium/extensions/shell/README b/chromium/extensions/shell/README
new file mode 100644
index 00000000000..c0806774bdb
--- /dev/null
+++ b/chromium/extensions/shell/README
@@ -0,0 +1,22 @@
+# Introduction
+
+app_shell is an experimental project to build a minimal environment like
+content_shell.
+
+The goal is to be able to run a v2 app and supply most of the chrome.* extension
+APIs without running the rest of Chrome.
+
+app_shell is only officially supported on Chrome OS.
+
+# How to run
+
+$ app_shell --load-apps=/path/to/extension
+
+# For example, you can try the calculator app:
+
+$ app_shell --load-apps=chrome/common/extensions/docs/examples/apps/calculator/app/
+
+# To load multiple apps, specify them in a comma separated list. The first app
+# will be launched:
+
+$ app_shell --load-apps=/path/to/extension1,/path/to/extension2
diff --git a/chromium/extensions/shell/app/DEPS b/chromium/extensions/shell/app/DEPS
new file mode 100644
index 00000000000..5b7cd9764a3
--- /dev/null
+++ b/chromium/extensions/shell/app/DEPS
@@ -0,0 +1,8 @@
+include_rules = [
+ "+extensions/shell",
+ "+chromeos",
+ "+components/nacl",
+ "+content/public/app",
+ "+content/public/browser",
+ "+sandbox",
+]
diff --git a/chromium/extensions/shell/app/README b/chromium/extensions/shell/app/README
new file mode 100644
index 00000000000..9dbba28e593
--- /dev/null
+++ b/chromium/extensions/shell/app/README
@@ -0,0 +1,5 @@
+"Application support" for the app_shell executable.
+
+This directory has nothing to do with the apps/extensions themselves.
+
+See content/shell/app for a similar example.
diff --git a/chromium/extensions/shell/app/app-Info.plist b/chromium/extensions/shell/app/app-Info.plist
new file mode 100644
index 00000000000..269fd96fa36
--- /dev/null
+++ b/chromium/extensions/shell/app/app-Info.plist
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleDisplayName</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIconFile</key>
+ <string>app.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.chromium.AppShell</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>${MACOSX_DEPLOYMENT_TARGET}.0</string>
+ <key>LSFileQuarantineEnabled</key>
+ <true/>
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+</dict>
+</plist>
diff --git a/chromium/extensions/shell/app/framework-Info.plist b/chromium/extensions/shell/app/framework-Info.plist
new file mode 100644
index 00000000000..ec58d3794c0
--- /dev/null
+++ b/chromium/extensions/shell/app/framework-Info.plist
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.chromium.AppShell.framework</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>FMWK</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+</dict>
+</plist>
diff --git a/chromium/extensions/shell/app/helper-Info.plist b/chromium/extensions/shell/app/helper-Info.plist
new file mode 100644
index 00000000000..dd654f115e4
--- /dev/null
+++ b/chromium/extensions/shell/app/helper-Info.plist
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleDisplayName</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.chromium.AppShell.helper</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>LSFileQuarantineEnabled</key>
+ <true/>
+ <key>LSMinimumSystemVersion</key>
+ <string>${MACOSX_DEPLOYMENT_TARGET}.0</string>
+ <key>LSUIElement</key>
+ <string>1</string>
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+</dict>
+</plist>
diff --git a/chromium/extensions/shell/app/paths_mac.h b/chromium/extensions/shell/app/paths_mac.h
new file mode 100644
index 00000000000..8cdaee9550c
--- /dev/null
+++ b/chromium/extensions/shell/app/paths_mac.h
@@ -0,0 +1,22 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_APP_PATHS_MAC_H_
+#define EXTENSIONS_SHELL_APP_PATHS_MAC_H_
+
+namespace base {
+class FilePath;
+}
+
+namespace extensions {
+
+// Override the framework bundle path.
+void OverrideFrameworkBundlePath();
+
+// Override the helper (child process) path.
+void OverrideChildProcessFilePath();
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_APP_PATHS_MAC_H_
diff --git a/chromium/extensions/shell/app/paths_mac.mm b/chromium/extensions/shell/app/paths_mac.mm
new file mode 100644
index 00000000000..32061e04fc1
--- /dev/null
+++ b/chromium/extensions/shell/app/paths_mac.mm
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/app/paths_mac.h"
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/mac/bundle_locations.h"
+#include "base/mac/foundation_util.h"
+#include "base/path_service.h"
+#include "content/public/common/content_paths.h"
+
+namespace extensions {
+
+namespace {
+
+base::FilePath GetFrameworksPath() {
+ base::FilePath path;
+ PathService::Get(base::FILE_EXE, &path);
+ // We now have a path .../App Shell.app/Contents/MacOS/App Shell, and want to
+ // transform it into
+ // .../App Shell.app/Contents/Frameworks/App Shell Framework.framework.
+ // But if it's App Shell Helper.app (inside Frameworks), we must go deeper.
+ if (base::mac::IsBackgroundOnlyProcess()) {
+ path = path.DirName().DirName().DirName().DirName().DirName();
+ } else {
+ path = path.DirName().DirName();
+ }
+ DCHECK_EQ("Contents", path.BaseName().value());
+ path = path.Append("Frameworks");
+ return path;
+}
+
+} // namespace
+
+void OverrideFrameworkBundlePath() {
+ base::FilePath path =
+ GetFrameworksPath().Append("App Shell Framework.framework");
+ base::mac::SetOverrideFrameworkBundlePath(path);
+}
+
+void OverrideChildProcessFilePath() {
+ base::FilePath path = GetFrameworksPath()
+ .Append("App Shell Helper.app")
+ .Append("Contents")
+ .Append("MacOS")
+ .Append("App Shell Helper");
+ PathService::Override(content::CHILD_PROCESS_EXE, path);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/app/shell_main.cc b/chromium/extensions/shell/app/shell_main.cc
new file mode 100644
index 00000000000..30cd8563197
--- /dev/null
+++ b/chromium/extensions/shell/app/shell_main.cc
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "build/build_config.h"
+#include "content/public/app/content_main.h"
+#include "extensions/shell/app/shell_main_delegate.h"
+
+#if defined(OS_WIN)
+#include "content/public/app/sandbox_helper_win.h"
+#include "sandbox/win/src/sandbox_types.h"
+#endif
+
+#if defined(OS_MACOSX)
+#include "extensions/shell/app/shell_main_mac.h"
+#endif
+
+#if defined(OS_MACOSX)
+int main(int argc, const char** argv) {
+ // Do the delegate work in shell_main_mac to avoid having to export the
+ // delegate types.
+ return ::ContentMain(argc, argv);
+}
+#elif defined(OS_WIN)
+int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t*, int) {
+ extensions::ShellMainDelegate delegate;
+ content::ContentMainParams params(&delegate);
+
+ sandbox::SandboxInterfaceInfo sandbox_info = {0};
+ content::InitializeSandboxInfo(&sandbox_info);
+ params.instance = instance;
+ params.sandbox_info = &sandbox_info;
+
+ return content::ContentMain(params);
+}
+#else // non-Mac POSIX
+int main(int argc, const char** argv) {
+ extensions::ShellMainDelegate delegate;
+ content::ContentMainParams params(&delegate);
+
+ params.argc = argc;
+ params.argv = argv;
+
+ return content::ContentMain(params);
+}
+#endif
diff --git a/chromium/extensions/shell/app/shell_main_delegate.cc b/chromium/extensions/shell/app/shell_main_delegate.cc
new file mode 100644
index 00000000000..d2759270ba1
--- /dev/null
+++ b/chromium/extensions/shell/app/shell_main_delegate.cc
@@ -0,0 +1,175 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/app/shell_main_delegate.h"
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "build/build_config.h"
+#include "content/public/browser/browser_main_runner.h"
+#include "content/public/common/content_switches.h"
+#include "extensions/common/extension_paths.h"
+#include "extensions/shell/browser/default_shell_browser_main_delegate.h"
+#include "extensions/shell/browser/shell_content_browser_client.h"
+#include "extensions/shell/common/shell_content_client.h"
+#include "extensions/shell/renderer/shell_content_renderer_client.h"
+#include "extensions/shell/utility/shell_content_utility_client.h"
+#include "ui/base/resource/resource_bundle.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/chromeos_paths.h"
+#endif
+
+#if defined(OS_MACOSX)
+#include "base/mac/foundation_util.h"
+#include "extensions/shell/app/paths_mac.h"
+#endif
+
+#if !defined(DISABLE_NACL)
+#include "components/nacl/common/nacl_switches.h"
+#if defined(OS_LINUX)
+#include "components/nacl/common/nacl_paths.h"
+#endif // OS_LINUX
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
+#include "components/nacl/zygote/nacl_fork_delegate_linux.h"
+#endif // OS_POSIX && !OS_MACOSX && !OS_ANDROID
+#endif // !DISABLE_NACL
+
+namespace {
+
+void InitLogging() {
+ base::FilePath log_filename;
+ PathService::Get(base::DIR_EXE, &log_filename);
+ log_filename = log_filename.AppendASCII("app_shell.log");
+ logging::LoggingSettings settings;
+ settings.logging_dest = logging::LOG_TO_ALL;
+ settings.log_file = log_filename.value().c_str();
+ settings.delete_old = logging::DELETE_OLD_LOG_FILE;
+ logging::InitLogging(settings);
+ logging::SetLogItems(true, true, true, true);
+}
+
+// Returns the path to the extensions_shell_and_test.pak file.
+base::FilePath GetResourcesPakFilePath() {
+#if defined(OS_MACOSX)
+ return base::mac::PathForFrameworkBundleResource(
+ CFSTR("extensions_shell_and_test.pak"));
+#else
+ base::FilePath extensions_shell_and_test_pak_path;
+ PathService::Get(base::DIR_MODULE, &extensions_shell_and_test_pak_path);
+ extensions_shell_and_test_pak_path =
+ extensions_shell_and_test_pak_path.AppendASCII(
+ "extensions_shell_and_test.pak");
+ return extensions_shell_and_test_pak_path;
+#endif // OS_MACOSX
+}
+
+} // namespace
+
+namespace extensions {
+
+ShellMainDelegate::ShellMainDelegate() {
+}
+
+ShellMainDelegate::~ShellMainDelegate() {
+}
+
+bool ShellMainDelegate::BasicStartupComplete(int* exit_code) {
+ InitLogging();
+ content_client_.reset(CreateContentClient());
+ SetContentClient(content_client_.get());
+
+#if defined(OS_MACOSX)
+ OverrideChildProcessFilePath();
+ // This must happen before InitializeResourceBundle.
+ OverrideFrameworkBundlePath();
+#endif
+
+#if defined(OS_CHROMEOS)
+ chromeos::RegisterPathProvider();
+#endif
+#if !defined(DISABLE_NACL) && defined(OS_LINUX)
+ nacl::RegisterPathProvider();
+#endif
+ extensions::RegisterPathProvider();
+ return false;
+}
+
+void ShellMainDelegate::PreSandboxStartup() {
+ std::string process_type =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kProcessType);
+ if (ProcessNeedsResourceBundle(process_type))
+ InitializeResourceBundle();
+}
+
+content::ContentBrowserClient* ShellMainDelegate::CreateContentBrowserClient() {
+ browser_client_.reset(CreateShellContentBrowserClient());
+ return browser_client_.get();
+}
+
+content::ContentRendererClient*
+ShellMainDelegate::CreateContentRendererClient() {
+ renderer_client_.reset(CreateShellContentRendererClient());
+ return renderer_client_.get();
+}
+
+content::ContentUtilityClient* ShellMainDelegate::CreateContentUtilityClient() {
+ utility_client_.reset(CreateShellContentUtilityClient());
+ return utility_client_.get();
+}
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
+void ShellMainDelegate::ZygoteStarting(
+ ScopedVector<content::ZygoteForkDelegate>* delegates) {
+#if !defined(DISABLE_NACL)
+ nacl::AddNaClZygoteForkDelegates(delegates);
+#endif // DISABLE_NACL
+}
+#endif // OS_POSIX && !OS_MACOSX && !OS_ANDROID
+
+content::ContentClient* ShellMainDelegate::CreateContentClient() {
+ return new ShellContentClient();
+}
+
+content::ContentBrowserClient*
+ShellMainDelegate::CreateShellContentBrowserClient() {
+ return new ShellContentBrowserClient(new DefaultShellBrowserMainDelegate());
+}
+
+content::ContentRendererClient*
+ShellMainDelegate::CreateShellContentRendererClient() {
+ return new ShellContentRendererClient();
+}
+
+content::ContentUtilityClient*
+ShellMainDelegate::CreateShellContentUtilityClient() {
+ return new ShellContentUtilityClient();
+}
+
+void ShellMainDelegate::InitializeResourceBundle() {
+ ui::ResourceBundle::InitSharedInstanceWithPakPath(
+ GetResourcesPakFilePath());
+}
+
+// static
+bool ShellMainDelegate::ProcessNeedsResourceBundle(
+ const std::string& process_type) {
+ // The browser process has no process type flag, but needs resources.
+ // On Linux the zygote process opens the resources for the renderers.
+ return process_type.empty() ||
+ process_type == switches::kZygoteProcess ||
+ process_type == switches::kRendererProcess ||
+#if !defined(DISABLE_NACL)
+ process_type == switches::kNaClLoaderProcess ||
+#endif
+#if defined(OS_MACOSX)
+ process_type == switches::kGpuProcess ||
+#endif
+ process_type == switches::kUtilityProcess;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/app/shell_main_delegate.h b/chromium/extensions/shell/app/shell_main_delegate.h
new file mode 100644
index 00000000000..335307ad45f
--- /dev/null
+++ b/chromium/extensions/shell/app/shell_main_delegate.h
@@ -0,0 +1,66 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_APP_SHELL_MAIN_DELEGATE_H_
+#define EXTENSIONS_SHELL_APP_SHELL_MAIN_DELEGATE_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "build/build_config.h"
+#include "content/public/app/content_main_delegate.h"
+
+namespace content {
+class BrowserContext;
+class ContentBrowserClient;
+class ContentClient;
+class ContentRendererClient;
+}
+
+namespace extensions {
+class ShellBrowserMainDelegate;
+
+class ShellMainDelegate : public content::ContentMainDelegate {
+ public:
+ ShellMainDelegate();
+ ~ShellMainDelegate() override;
+
+ // ContentMainDelegate implementation:
+ bool BasicStartupComplete(int* exit_code) override;
+ void PreSandboxStartup() override;
+ content::ContentBrowserClient* CreateContentBrowserClient() override;
+ content::ContentRendererClient* CreateContentRendererClient() override;
+ content::ContentUtilityClient* CreateContentUtilityClient() override;
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
+ void ZygoteStarting(
+ ScopedVector<content::ZygoteForkDelegate>* delegates) override;
+#endif
+
+ protected:
+ // The created object is owned by this object.
+ virtual content::ContentClient* CreateContentClient();
+ virtual content::ContentBrowserClient* CreateShellContentBrowserClient();
+ virtual content::ContentRendererClient* CreateShellContentRendererClient();
+ virtual content::ContentUtilityClient* CreateShellContentUtilityClient();
+
+ // Initializes the resource bundle and resources.pak.
+ virtual void InitializeResourceBundle();
+
+ private:
+ // |process_type| is zygote, renderer, utility, etc. Returns true if the
+ // process needs data from resources.pak.
+ static bool ProcessNeedsResourceBundle(const std::string& process_type);
+
+ scoped_ptr<content::ContentClient> content_client_;
+ scoped_ptr<content::ContentBrowserClient> browser_client_;
+ scoped_ptr<content::ContentRendererClient> renderer_client_;
+ scoped_ptr<content::ContentUtilityClient> utility_client_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellMainDelegate);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_APP_SHELL_MAIN_DELEGATE_H_
diff --git a/chromium/extensions/shell/app/shell_main_mac.cc b/chromium/extensions/shell/app/shell_main_mac.cc
new file mode 100644
index 00000000000..85195c5a342
--- /dev/null
+++ b/chromium/extensions/shell/app/shell_main_mac.cc
@@ -0,0 +1,16 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/app/shell_main_mac.h"
+
+#include "content/public/app/content_main.h"
+#include "extensions/shell/app/shell_main_delegate.h"
+
+int ContentMain(int argc, const char** argv) {
+ extensions::ShellMainDelegate delegate;
+ content::ContentMainParams params(&delegate);
+ params.argc = argc;
+ params.argv = argv;
+ return content::ContentMain(params);
+}
diff --git a/chromium/extensions/shell/app/shell_main_mac.h b/chromium/extensions/shell/app/shell_main_mac.h
new file mode 100644
index 00000000000..457806fa488
--- /dev/null
+++ b/chromium/extensions/shell/app/shell_main_mac.h
@@ -0,0 +1,16 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_APP_SHELL_MAIN_MAC_H_
+#define EXTENSIONS_SHELL_APP_SHELL_MAIN_MAC_H_
+
+#include "build/build_config.h"
+
+extern "C" {
+__attribute__((visibility("default"))) int ContentMain(int argc,
+ const char** argv);
+} // extern "C"
+
+#endif // EXTENSIONS_SHELL_APP_SHELL_MAIN_MAC_H_
+
diff --git a/chromium/extensions/shell/app_shell.gni b/chromium/extensions/shell/app_shell.gni
new file mode 100644
index 00000000000..3b5e9842b38
--- /dev/null
+++ b/chromium/extensions/shell/app_shell.gni
@@ -0,0 +1,16 @@
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# This file defines the app_shell gypi values. This file is read once and
+# cached, which is a performance optimization that allows us to share the
+# results of parsing the .gypi file between all app_shell BUILD.gn files.
+# It also saves us from duplicating this exec_script call.
+app_shell_gypi_values =
+ exec_script("//build/gypi_to_gn.py",
+ [
+ rebase_path("app_shell.gypi"),
+ "--replace=<(SHARED_INTERMEDIATE_DIR)=$root_gen_dir",
+ ],
+ "scope",
+ [ "app_shell.gypi" ])
diff --git a/chromium/extensions/shell/app_shell_resources.grd b/chromium/extensions/shell/app_shell_resources.grd
new file mode 100644
index 00000000000..99f33da367b
--- /dev/null
+++ b/chromium/extensions/shell/app_shell_resources.grd
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1">
+ <outputs>
+ <output filename="grit/app_shell_resources.h" type="rc_header">
+ <emit emit_type='prepend'></emit>
+ </output>
+ <output filename="app_shell_resources.pak" type="data_package" />
+ </outputs>
+ <release seq="1">
+ <includes>
+ <!-- Features specific to app_shell. -->
+ <include name="IDR_SHELL_EXTENSION_API_FEATURES" file="common\api\_api_features.json" type="BINDATA" />
+ </includes>
+ </release>
+</grit>
diff --git a/chromium/extensions/shell/browser/DEPS b/chromium/extensions/shell/browser/DEPS
new file mode 100644
index 00000000000..bdc975fe5de
--- /dev/null
+++ b/chromium/extensions/shell/browser/DEPS
@@ -0,0 +1,48 @@
+include_rules = [
+ "+chromeos",
+ "+components/devtools_discovery",
+ "+components/devtools_http_handler",
+ "+components/keyed_service",
+ "+components/nacl/browser",
+ "+components/nacl/common",
+ "+components/pref_registry",
+ "+components/update_client",
+ "+components/user_prefs",
+ "+components/web_modal",
+ "+content/public/browser",
+
+ # Pieces of content_shell reused in app_shell.
+ # TODO(jamescook): Eliminate these. http://crbug.com/438283
+ "+content/shell/browser/shell_application_mac.h",
+ "+content/shell/browser/shell_browser_context.h",
+ "+content/shell/browser/shell_devtools_manager_delegate.h",
+ "+content/shell/browser/shell_url_request_context_getter.h",
+
+ # For device backend support.
+ "+device/bluetooth",
+ "+device/core",
+ "+device/hid",
+ "+device/usb",
+
+ "+gin",
+
+ "+google_apis/gaia",
+
+ "+net",
+
+ "+ppapi",
+ "+storage/browser/quota",
+ "+sync/api",
+ "+third_party/skia/include",
+
+ # Additional UI dependencies for app_shell. Note that no particular UI toolkit
+ # is supported; only Aura and some necessary bits to set up display surfaces.
+ "+ui/aura",
+ "+ui/chromeos",
+ "+ui/display",
+ "+ui/wm",
+
+ "+third_party/cros_system_api",
+
+ "-webkit",
+]
diff --git a/chromium/extensions/shell/browser/api/identity/identity_api.cc b/chromium/extensions/shell/browser/api/identity/identity_api.cc
new file mode 100644
index 00000000000..bf8ad1f8fda
--- /dev/null
+++ b/chromium/extensions/shell/browser/api/identity/identity_api.cc
@@ -0,0 +1,160 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/api/identity/identity_api.h"
+
+#include <set>
+#include <string>
+
+#include "base/guid.h"
+#include "content/public/browser/browser_context.h"
+#include "extensions/common/manifest_handlers/oauth2_manifest_handler.h"
+#include "extensions/shell/browser/shell_oauth2_token_service.h"
+#include "extensions/shell/common/api/identity.h"
+#include "google_apis/gaia/gaia_auth_util.h"
+
+namespace extensions {
+namespace shell {
+
+namespace {
+const char kIdentityApiId[] = "identity_api";
+const char kErrorNoUserAccount[] = "No user account.";
+const char kErrorNoRefreshToken[] = "No refresh token.";
+const char kErrorNoScopesInManifest[] = "No scopes in manifest.";
+const char kErrorUserPermissionRequired[] =
+ "User permission required but not available in app_shell";
+} // namespace
+
+IdentityAPI::IdentityAPI(content::BrowserContext* context)
+ : device_id_(base::GenerateGUID()) {
+}
+
+IdentityAPI::~IdentityAPI() {
+}
+
+// static
+IdentityAPI* IdentityAPI::Get(content::BrowserContext* context) {
+ return BrowserContextKeyedAPIFactory<IdentityAPI>::Get(context);
+}
+
+// static
+BrowserContextKeyedAPIFactory<IdentityAPI>* IdentityAPI::GetFactoryInstance() {
+ static base::LazyInstance<BrowserContextKeyedAPIFactory<IdentityAPI>>
+ factory = LAZY_INSTANCE_INITIALIZER;
+ return factory.Pointer();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction()
+ : OAuth2TokenService::Consumer(kIdentityApiId) {
+}
+
+IdentityGetAuthTokenFunction::~IdentityGetAuthTokenFunction() {
+}
+
+void IdentityGetAuthTokenFunction::SetMintTokenFlowForTesting(
+ OAuth2MintTokenFlow* flow) {
+ mint_token_flow_.reset(flow);
+}
+
+ExtensionFunction::ResponseAction IdentityGetAuthTokenFunction::Run() {
+ scoped_ptr<api::identity::GetAuthToken::Params> params(
+ api::identity::GetAuthToken::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ ShellOAuth2TokenService* service = ShellOAuth2TokenService::GetInstance();
+ std::string account_id = service->AccountId();
+ if (account_id.empty())
+ return RespondNow(Error(kErrorNoUserAccount));
+
+ if (!service->RefreshTokenIsAvailable(account_id))
+ return RespondNow(Error(kErrorNoRefreshToken));
+
+ // Verify that we have scopes.
+ const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(extension());
+ if (oauth2_info.scopes.empty())
+ return RespondNow(Error(kErrorNoScopesInManifest));
+
+ // Balanced in OnGetTokenFailure() and in the OAuth2MintTokenFlow callbacks.
+ AddRef();
+
+ // First, fetch a logged-in-user access token for the Chrome project client ID
+ // and client secret. This token is used later to get a second access token
+ // that will be returned to the app.
+ std::set<std::string> no_scopes;
+ access_token_request_ =
+ service->StartRequest(service->AccountId(), no_scopes, this);
+ return RespondLater();
+}
+
+void IdentityGetAuthTokenFunction::OnGetTokenSuccess(
+ const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) {
+ // Tests may override the mint token flow.
+ if (!mint_token_flow_) {
+ const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(extension());
+ DCHECK(!oauth2_info.scopes.empty());
+
+ mint_token_flow_.reset(new OAuth2MintTokenFlow(
+ this,
+ OAuth2MintTokenFlow::Parameters(
+ extension()->id(),
+ oauth2_info.client_id,
+ oauth2_info.scopes,
+ IdentityAPI::Get(browser_context())->device_id(),
+ OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE)));
+ }
+
+ // Use the logging-in-user access token to mint an access token for this app.
+ mint_token_flow_->Start(browser_context()->GetRequestContext(), access_token);
+}
+
+void IdentityGetAuthTokenFunction::OnGetTokenFailure(
+ const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) {
+ Respond(Error(error.ToString()));
+ Release(); // Balanced in Run().
+}
+
+void IdentityGetAuthTokenFunction::OnMintTokenSuccess(
+ const std::string& access_token,
+ int time_to_live) {
+ Respond(OneArgument(new base::StringValue(access_token)));
+ Release(); // Balanced in Run().
+}
+
+void IdentityGetAuthTokenFunction::OnIssueAdviceSuccess(
+ const IssueAdviceInfo& issue_advice) {
+ Respond(Error(kErrorUserPermissionRequired));
+ Release(); // Balanced in Run().
+}
+
+void IdentityGetAuthTokenFunction::OnMintTokenFailure(
+ const GoogleServiceAuthError& error) {
+ Respond(Error(error.ToString()));
+ Release(); // Balanced in Run().
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+IdentityRemoveCachedAuthTokenFunction::IdentityRemoveCachedAuthTokenFunction() {
+}
+
+IdentityRemoveCachedAuthTokenFunction::
+ ~IdentityRemoveCachedAuthTokenFunction() {
+}
+
+ExtensionFunction::ResponseAction IdentityRemoveCachedAuthTokenFunction::Run() {
+ scoped_ptr<api::identity::RemoveCachedAuthToken::Params> params(
+ api::identity::RemoveCachedAuthToken::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ // This stub identity API does not maintain a token cache, so there is nothing
+ // to remove.
+ return RespondNow(NoArguments());
+}
+
+} // namespace shell
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/api/identity/identity_api.h b/chromium/extensions/shell/browser/api/identity/identity_api.h
new file mode 100644
index 00000000000..a3343aded23
--- /dev/null
+++ b/chromium/extensions/shell/browser/api/identity/identity_api.h
@@ -0,0 +1,105 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_API_IDENTITY_IDENTITY_API_H_
+#define EXTENSIONS_SHELL_BROWSER_API_IDENTITY_IDENTITY_API_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_function.h"
+#include "google_apis/gaia/oauth2_mint_token_flow.h"
+#include "google_apis/gaia/oauth2_token_service.h"
+
+namespace extensions {
+namespace shell {
+
+// Storage for data used across identity function invocations.
+class IdentityAPI : public BrowserContextKeyedAPI {
+ public:
+ explicit IdentityAPI(content::BrowserContext* context);
+ ~IdentityAPI() override;
+
+ static IdentityAPI* Get(content::BrowserContext* context);
+
+ const std::string& device_id() const { return device_id_; }
+
+ // BrowserContextKeyedAPI:
+ static BrowserContextKeyedAPIFactory<IdentityAPI>* GetFactoryInstance();
+ static const char* service_name() { return "IdentityAPI"; }
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<IdentityAPI>;
+
+ // A GUID identifying this device.
+ // TODO(jamescook): Make this GUID stable across runs of the app, perhaps by
+ // storing it in a pref.
+ const std::string device_id_;
+};
+
+// Returns an OAuth2 access token for a user. See the IDL file for
+// documentation.
+class IdentityGetAuthTokenFunction : public UIThreadExtensionFunction,
+ public OAuth2TokenService::Consumer,
+ public OAuth2MintTokenFlow::Delegate {
+ public:
+ DECLARE_EXTENSION_FUNCTION("identity.getAuthToken", UNKNOWN);
+
+ IdentityGetAuthTokenFunction();
+
+ // Takes ownership.
+ void SetMintTokenFlowForTesting(OAuth2MintTokenFlow* flow);
+
+ protected:
+ ~IdentityGetAuthTokenFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ // OAuth2TokenService::Consumer:
+ void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) override;
+ void OnGetTokenFailure(const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) override;
+
+ // OAuth2MintTokenFlow::Delegate:
+ void OnMintTokenSuccess(const std::string& access_token,
+ int time_to_live) override;
+ void OnIssueAdviceSuccess(const IssueAdviceInfo& issue_advice) override;
+ void OnMintTokenFailure(const GoogleServiceAuthError& error) override;
+
+ private:
+ // A pending token fetch request to get a login-scoped access token for the
+ // current user for the Chrome project id.
+ scoped_ptr<OAuth2TokenService::Request> access_token_request_;
+
+ // A request for an access token for the current app and its scopes.
+ scoped_ptr<OAuth2MintTokenFlow> mint_token_flow_;
+
+ DISALLOW_COPY_AND_ASSIGN(IdentityGetAuthTokenFunction);
+};
+
+// Stub. See the IDL file for documentation.
+class IdentityRemoveCachedAuthTokenFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("identity.removeCachedAuthToken", UNKNOWN)
+
+ IdentityRemoveCachedAuthTokenFunction();
+
+ protected:
+ ~IdentityRemoveCachedAuthTokenFunction() override;
+
+ // ExtensionFunction:
+ ResponseAction Run() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(IdentityRemoveCachedAuthTokenFunction);
+};
+
+} // namespace shell
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_API_IDENTITY_IDENTITY_API_H_
diff --git a/chromium/extensions/shell/browser/api/identity/identity_api_unittest.cc b/chromium/extensions/shell/browser/api/identity/identity_api_unittest.cc
new file mode 100644
index 00000000000..0af6e4b13bb
--- /dev/null
+++ b/chromium/extensions/shell/browser/api/identity/identity_api_unittest.cc
@@ -0,0 +1,131 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/api/identity/identity_api.h"
+
+#include <string>
+#include <utility>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "extensions/browser/api_unittest.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/shell/browser/shell_oauth2_token_service.h"
+#include "google_apis/gaia/oauth2_mint_token_flow.h"
+
+namespace extensions {
+namespace shell {
+
+// A ShellOAuth2TokenService that does not make network requests.
+class MockShellOAuth2TokenService : public ShellOAuth2TokenService {
+ public:
+ // The service starts with no account id or refresh token.
+ MockShellOAuth2TokenService() : ShellOAuth2TokenService(nullptr, "", "") {}
+ ~MockShellOAuth2TokenService() override {}
+
+ // OAuth2TokenService:
+ scoped_ptr<Request> StartRequest(const std::string& account_id,
+ const ScopeSet& scopes,
+ Consumer* consumer) override {
+ // Immediately return success.
+ consumer->OnGetTokenSuccess(nullptr, "logged-in-user-token", base::Time());
+ return nullptr;
+ }
+};
+
+// A mint token flow that immediately returns a known access token when started.
+class MockOAuth2MintTokenFlow : public OAuth2MintTokenFlow {
+ public:
+ explicit MockOAuth2MintTokenFlow(Delegate* delegate)
+ : OAuth2MintTokenFlow(delegate, Parameters()), delegate_(delegate) {}
+ ~MockOAuth2MintTokenFlow() override {}
+
+ // OAuth2ApiCallFlow:
+ void Start(net::URLRequestContextGetter* context,
+ const std::string& access_token) override {
+ EXPECT_EQ("logged-in-user-token", access_token);
+ delegate_->OnMintTokenSuccess("app-access-token", 12345);
+ }
+
+ private:
+ // Cached here so OAuth2MintTokenFlow does not have to expose its delegate.
+ Delegate* delegate_;
+};
+
+class IdentityApiTest : public ApiUnitTest {
+ public:
+ IdentityApiTest() {}
+ ~IdentityApiTest() override {}
+
+ // testing::Test:
+ void SetUp() override {
+ ApiUnitTest::SetUp();
+ // Create an extension with OAuth2 scopes.
+ set_extension(
+ ExtensionBuilder()
+ .SetManifest(
+ DictionaryBuilder()
+ .Set("name", "Test")
+ .Set("version", "1.0")
+ .Set("oauth2",
+ DictionaryBuilder()
+ .Set("client_id",
+ "123456.apps.googleusercontent.com")
+ .Set("scopes",
+ ListBuilder()
+ .Append("https://www.googleapis.com/"
+ "auth/drive")
+ .Build())
+ .Build())
+ .Build())
+ .SetLocation(Manifest::UNPACKED)
+ .Build());
+ }
+};
+
+// Verifies that the getAuthToken function exists and can be called without
+// crashing.
+TEST_F(IdentityApiTest, GetAuthTokenNoRefreshToken) {
+ MockShellOAuth2TokenService token_service;
+
+ // Calling getAuthToken() before a refresh token is available causes an error.
+ std::string error =
+ RunFunctionAndReturnError(new IdentityGetAuthTokenFunction, "[{}]");
+ EXPECT_FALSE(error.empty());
+}
+
+// Verifies that getAuthToken() returns an app access token.
+TEST_F(IdentityApiTest, GetAuthToken) {
+ MockShellOAuth2TokenService token_service;
+
+ // Simulate a refresh token being set.
+ token_service.SetRefreshToken("larry@google.com", "refresh-token");
+
+ // RunFunctionAndReturnValue takes ownership.
+ IdentityGetAuthTokenFunction* function = new IdentityGetAuthTokenFunction;
+ function->SetMintTokenFlowForTesting(new MockOAuth2MintTokenFlow(function));
+
+ // Function succeeds and returns a token (for its callback).
+ scoped_ptr<base::Value> result = RunFunctionAndReturnValue(function, "[{}]");
+ ASSERT_TRUE(result.get());
+ std::string value;
+ result->GetAsString(&value);
+ EXPECT_NE("logged-in-user-token", value);
+ EXPECT_EQ("app-access-token", value);
+}
+
+// Verifies that the removeCachedAuthToken function exists and can be called
+// without crashing.
+TEST_F(IdentityApiTest, RemoveCachedAuthToken) {
+ MockShellOAuth2TokenService token_service;
+
+ // Function succeeds and returns nothing (for its callback).
+ scoped_ptr<base::Value> result = RunFunctionAndReturnValue(
+ new IdentityRemoveCachedAuthTokenFunction, "[{}]");
+ EXPECT_FALSE(result.get());
+}
+
+} // namespace shell
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/api/vpn_provider/OWNERS b/chromium/extensions/shell/browser/api/vpn_provider/OWNERS
new file mode 100644
index 00000000000..c83fa2113aa
--- /dev/null
+++ b/chromium/extensions/shell/browser/api/vpn_provider/OWNERS
@@ -0,0 +1,3 @@
+kaliamoorthi@chromium.org
+bartfab@chromium.org
+emaxx@chromium.org
diff --git a/chromium/extensions/shell/browser/api/vpn_provider/vpn_service_factory.cc b/chromium/extensions/shell/browser/api/vpn_provider/vpn_service_factory.cc
new file mode 100644
index 00000000000..167f5ec965f
--- /dev/null
+++ b/chromium/extensions/shell/browser/api/vpn_provider/vpn_service_factory.cc
@@ -0,0 +1,59 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/vpn_provider/vpn_service_factory.h"
+
+#include "base/memory/singleton.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/network/network_handler.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "extensions/browser/api/vpn_provider/vpn_service.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_registry.h"
+
+namespace chromeos {
+
+// This file is a dummy stub for use in appshell.
+
+// static
+VpnService* VpnServiceFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<VpnService*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+VpnServiceFactory* VpnServiceFactory::GetInstance() {
+ return base::Singleton<VpnServiceFactory>::get();
+}
+
+VpnServiceFactory::VpnServiceFactory()
+ : BrowserContextKeyedServiceFactory(
+ "VpnService",
+ BrowserContextDependencyManager::GetInstance()) {
+}
+
+VpnServiceFactory::~VpnServiceFactory() {
+}
+
+bool VpnServiceFactory::ServiceIsCreatedWithBrowserContext() const {
+ return true;
+}
+
+bool VpnServiceFactory::ServiceIsNULLWhileTesting() const {
+ return true;
+}
+
+KeyedService* VpnServiceFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ return new VpnService(
+ context, "testuser", extensions::ExtensionRegistry::Get(context),
+ extensions::EventRouter::Get(context),
+ DBusThreadManager::Get()->GetShillThirdPartyVpnDriverClient(),
+ NetworkHandler::Get()->network_configuration_handler(),
+ NetworkHandler::Get()->network_profile_handler(),
+ NetworkHandler::Get()->network_state_handler());
+}
+
+} // namespace chromeos
diff --git a/chromium/extensions/shell/browser/default_shell_browser_main_delegate.cc b/chromium/extensions/shell/browser/default_shell_browser_main_delegate.cc
new file mode 100644
index 00000000000..c73a35f9a32
--- /dev/null
+++ b/chromium/extensions/shell/browser/default_shell_browser_main_delegate.cc
@@ -0,0 +1,81 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/default_shell_browser_main_delegate.h"
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/strings/string_tokenizer.h"
+#include "build/build_config.h"
+#include "extensions/common/switches.h"
+#include "extensions/shell/browser/shell_extension_system.h"
+
+#if defined(USE_AURA)
+#include "extensions/shell/browser/shell_desktop_controller_aura.h"
+#endif
+
+#if defined(OS_MACOSX)
+#include "extensions/shell/browser/shell_desktop_controller_mac.h"
+#endif
+
+namespace extensions {
+
+DefaultShellBrowserMainDelegate::DefaultShellBrowserMainDelegate() {
+}
+
+DefaultShellBrowserMainDelegate::~DefaultShellBrowserMainDelegate() {
+}
+
+void DefaultShellBrowserMainDelegate::Start(
+ content::BrowserContext* browser_context) {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kLoadApps)) {
+ ShellExtensionSystem* extension_system = static_cast<ShellExtensionSystem*>(
+ ExtensionSystem::Get(browser_context));
+ extension_system->Init();
+
+ base::CommandLine::StringType path_list =
+ command_line->GetSwitchValueNative(switches::kLoadApps);
+
+ base::StringTokenizerT<base::CommandLine::StringType,
+ base::CommandLine::StringType::const_iterator>
+ tokenizer(path_list, FILE_PATH_LITERAL(","));
+
+ std::string launch_id;
+ while (tokenizer.GetNext()) {
+ base::FilePath app_absolute_dir =
+ base::MakeAbsoluteFilePath(base::FilePath(tokenizer.token()));
+
+ const Extension* extension = extension_system->LoadApp(app_absolute_dir);
+ if (!extension)
+ continue;
+ if (launch_id.empty())
+ launch_id = extension->id();
+ }
+
+ if (!launch_id.empty())
+ extension_system->LaunchApp(launch_id);
+ else
+ LOG(ERROR) << "Could not load any apps.";
+ } else {
+ LOG(ERROR) << "--" << switches::kLoadApps
+ << " unset; boredom is in your future";
+ }
+}
+
+void DefaultShellBrowserMainDelegate::Shutdown() {
+}
+
+DesktopController* DefaultShellBrowserMainDelegate::CreateDesktopController() {
+#if defined(USE_AURA)
+ return new ShellDesktopControllerAura();
+#elif defined(OS_MACOSX)
+ return new ShellDesktopControllerMac();
+#else
+ return NULL;
+#endif
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/default_shell_browser_main_delegate.h b/chromium/extensions/shell/browser/default_shell_browser_main_delegate.h
new file mode 100644
index 00000000000..64e643fbf51
--- /dev/null
+++ b/chromium/extensions/shell/browser/default_shell_browser_main_delegate.h
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_DEFAULT_SHELL_BROWSER_MAIN_DELEGATE_H_
+#define EXTENSIONS_SHELL_BROWSER_DEFAULT_SHELL_BROWSER_MAIN_DELEGATE_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "extensions/shell/browser/shell_browser_main_delegate.h"
+
+namespace extensions {
+
+// A ShellBrowserMainDelegate that starts an application specified
+// by the "--app" command line. This is used only in the browser process.
+class DefaultShellBrowserMainDelegate : public ShellBrowserMainDelegate {
+ public:
+ DefaultShellBrowserMainDelegate();
+ ~DefaultShellBrowserMainDelegate() override;
+
+ // ShellBrowserMainDelegate:
+ void Start(content::BrowserContext* context) override;
+ void Shutdown() override;
+ DesktopController* CreateDesktopController() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DefaultShellBrowserMainDelegate);
+};
+
+} // namespace extensions
+
+#endif // DEFAULT_SHELL_BROWSER_MAIN_DELEGATE_H_
diff --git a/chromium/extensions/shell/browser/desktop_controller.cc b/chromium/extensions/shell/browser/desktop_controller.cc
new file mode 100644
index 00000000000..7bf6bc52c08
--- /dev/null
+++ b/chromium/extensions/shell/browser/desktop_controller.cc
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/desktop_controller.h"
+
+#include "base/logging.h"
+#include "base/macros.h"
+
+namespace extensions {
+namespace {
+
+DesktopController* g_instance = NULL;
+
+} // namespace
+
+// static
+DesktopController* DesktopController::instance() {
+ return g_instance;
+}
+
+DesktopController::DesktopController() {
+ CHECK(!g_instance) << "DesktopController already exists";
+ g_instance = this;
+}
+
+DesktopController::~DesktopController() {
+ DCHECK(g_instance);
+ g_instance = NULL;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/desktop_controller.h b/chromium/extensions/shell/browser/desktop_controller.h
new file mode 100644
index 00000000000..bdcd8e1b774
--- /dev/null
+++ b/chromium/extensions/shell/browser/desktop_controller.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_DESKTOP_CONTROLLER_H_
+#define EXTENSIONS_SHELL_BROWSER_DESKTOP_CONTROLLER_H_
+
+#include "ui/gfx/native_widget_types.h"
+
+namespace aura {
+class Window;
+class WindowTreeHost;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace gfx {
+class Size;
+}
+
+namespace extensions {
+class AppWindow;
+class Extension;
+class ShellAppWindow;
+
+// DesktopController is an interface to construct the window environment in
+// extensions shell. ShellDesktopControllerAura provides a default
+// implementation for app_shell, and other embedders can provide their own.
+// TODO(jamescook|oshima): Clean up this interface now that there is only one
+// way to create an app window.
+class DesktopController {
+ public:
+ DesktopController();
+ virtual ~DesktopController();
+
+ // Returns the single instance of the desktop. (Stateless functions like
+ // ShellAppWindowCreateFunction need to be able to access the desktop, so
+ // we need a singleton somewhere).
+ static DesktopController* instance();
+
+ // Get the size of the window created by this DesktopController. This should
+ // typically be full-screen.
+ virtual gfx::Size GetWindowSize() = 0;
+
+ // Creates a new app window and adds it to the desktop. The desktop maintains
+ // ownership of the window. The window must be closed before |extension| is
+ // destroyed.
+ virtual AppWindow* CreateAppWindow(content::BrowserContext* context,
+ const Extension* extension) = 0;
+
+ // Attaches the window to our window hierarchy.
+ virtual void AddAppWindow(gfx::NativeWindow window) = 0;
+
+ // Removes the window from the desktop.
+ virtual void RemoveAppWindow(AppWindow* window) = 0;
+
+ // Closes and destroys the app windows.
+ virtual void CloseAppWindows() = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_DESKTOP_CONTROLLER_H_
diff --git a/chromium/extensions/shell/browser/geolocation/geolocation_apitest.cc b/chromium/extensions/shell/browser/geolocation/geolocation_apitest.cc
new file mode 100644
index 00000000000..f44c1ab3b6f
--- /dev/null
+++ b/chromium/extensions/shell/browser/geolocation/geolocation_apitest.cc
@@ -0,0 +1,17 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/test/shell_apitest.h"
+
+namespace extensions {
+
+IN_PROC_BROWSER_TEST_F(ShellApiTest,
+ ExtensionGeolocationShouldReturnPermissionDenied) {
+ // app_shell does not implement CreateAccessTokenStore() and the other
+ // bits for proper Geolocation support. We make sure that clients of this
+ // API will always get "permission denied" and won't crash.
+ ASSERT_TRUE(RunAppTest("geolocation/always_permission_denied")) << message_;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/media_capture_util.cc b/chromium/extensions/shell/browser/media_capture_util.cc
new file mode 100644
index 00000000000..6f4660c84f0
--- /dev/null
+++ b/chromium/extensions/shell/browser/media_capture_util.cc
@@ -0,0 +1,89 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/media_capture_util.h"
+
+#include <string>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "content/public/browser/media_capture_devices.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/permissions/permissions_data.h"
+
+using content::MediaCaptureDevices;
+using content::MediaStreamDevice;
+using content::MediaStreamDevices;
+using content::MediaStreamUI;
+
+namespace extensions {
+
+const MediaStreamDevice* GetRequestedDeviceOrDefault(
+ const MediaStreamDevices& devices,
+ const std::string& requested_device_id) {
+ if (!requested_device_id.empty())
+ return devices.FindById(requested_device_id);
+
+ if (!devices.empty())
+ return &devices[0];
+
+ return NULL;
+}
+
+namespace media_capture_util {
+
+// See also Chrome's MediaCaptureDevicesDispatcher.
+void GrantMediaStreamRequest(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback,
+ const Extension* extension) {
+ // app_shell only supports audio and video capture, not tab or screen capture.
+ DCHECK(request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE ||
+ request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE);
+
+ MediaStreamDevices devices;
+
+ if (request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE) {
+ VerifyMediaAccessPermission(request.audio_type, extension);
+ const MediaStreamDevice* device = GetRequestedDeviceOrDefault(
+ MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices(),
+ request.requested_audio_device_id);
+ if (device)
+ devices.push_back(*device);
+ }
+
+ if (request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE) {
+ VerifyMediaAccessPermission(request.video_type, extension);
+ const MediaStreamDevice* device = GetRequestedDeviceOrDefault(
+ MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices(),
+ request.requested_video_device_id);
+ if (device)
+ devices.push_back(*device);
+ }
+
+ // TODO(jamescook): Should we show a recording icon somewhere? If so, where?
+ scoped_ptr<MediaStreamUI> ui;
+ callback.Run(devices, devices.empty() ? content::MEDIA_DEVICE_INVALID_STATE
+ : content::MEDIA_DEVICE_OK,
+ std::move(ui));
+}
+
+void VerifyMediaAccessPermission(content::MediaStreamType type,
+ const Extension* extension) {
+ const PermissionsData* permissions_data = extension->permissions_data();
+ if (type == content::MEDIA_DEVICE_AUDIO_CAPTURE) {
+ // app_shell has no UI surface to show an error, and on an embedded device
+ // it's better to crash than to have a feature not work.
+ CHECK(permissions_data->HasAPIPermission(APIPermission::kAudioCapture))
+ << "Audio capture request but no audioCapture permission in manifest.";
+ } else {
+ DCHECK(type == content::MEDIA_DEVICE_VIDEO_CAPTURE);
+ CHECK(permissions_data->HasAPIPermission(APIPermission::kVideoCapture))
+ << "Video capture request but no videoCapture permission in manifest.";
+ }
+}
+
+} // namespace media_capture_util
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/media_capture_util.h b/chromium/extensions/shell/browser/media_capture_util.h
new file mode 100644
index 00000000000..b1b15ba98cf
--- /dev/null
+++ b/chromium/extensions/shell/browser/media_capture_util.h
@@ -0,0 +1,38 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_MEDIA_CAPTURE_UTIL_H_
+#define EXTENSIONS_SHELL_BROWSER_MEDIA_CAPTURE_UTIL_H_
+
+#include "base/macros.h"
+#include "content/public/common/media_stream_request.h"
+
+namespace content {
+class WebContents;
+}
+
+namespace extensions {
+
+class Extension;
+
+namespace media_capture_util {
+
+// Grants access to audio and video capture devices.
+// * If the caller requests specific device ids, grants access to those.
+// * If the caller does not request specific ids, grants access to the first
+// available device.
+// Usually used as a helper for media capture ProcessMediaAccessRequest().
+void GrantMediaStreamRequest(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback,
+ const Extension* extension);
+
+// Verifies that the extension has permission for |type|. If not, crash.
+void VerifyMediaAccessPermission(content::MediaStreamType type,
+ const Extension* extension);
+
+} // namespace media_capture_util
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_MEDIA_CAPTURE_UTIL_H_
diff --git a/chromium/extensions/shell/browser/shell_app_delegate.cc b/chromium/extensions/shell/browser/shell_app_delegate.cc
new file mode 100644
index 00000000000..6404964c6da
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_app_delegate.cc
@@ -0,0 +1,104 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_app_delegate.h"
+
+#include "content/public/browser/web_contents.h"
+#include "extensions/common/constants.h"
+#include "extensions/shell/browser/media_capture_util.h"
+#include "extensions/shell/browser/shell_extension_web_contents_observer.h"
+
+namespace extensions {
+
+ShellAppDelegate::ShellAppDelegate() {
+}
+
+ShellAppDelegate::~ShellAppDelegate() {
+}
+
+void ShellAppDelegate::InitWebContents(content::WebContents* web_contents) {
+ ShellExtensionWebContentsObserver::CreateForWebContents(web_contents);
+}
+
+void ShellAppDelegate::RenderViewCreated(
+ content::RenderViewHost* render_view_host) {
+ // The views implementation of AppWindow takes focus via SetInitialFocus()
+ // and views::WebView but app_shell is aura-only and must do it manually.
+ content::WebContents::FromRenderViewHost(render_view_host)->Focus();
+}
+
+void ShellAppDelegate::ResizeWebContents(content::WebContents* web_contents,
+ const gfx::Size& size) {
+ NOTIMPLEMENTED();
+}
+
+content::WebContents* ShellAppDelegate::OpenURLFromTab(
+ content::BrowserContext* context,
+ content::WebContents* source,
+ const content::OpenURLParams& params) {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+void ShellAppDelegate::AddNewContents(content::BrowserContext* context,
+ content::WebContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_rect,
+ bool user_gesture,
+ bool* was_blocked) {
+ NOTIMPLEMENTED();
+}
+
+content::ColorChooser* ShellAppDelegate::ShowColorChooser(
+ content::WebContents* web_contents,
+ SkColor initial_color) {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+void ShellAppDelegate::RunFileChooser(
+ content::WebContents* tab,
+ const content::FileChooserParams& params) {
+ NOTIMPLEMENTED();
+}
+
+void ShellAppDelegate::RequestMediaAccessPermission(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback,
+ const extensions::Extension* extension) {
+ media_capture_util::GrantMediaStreamRequest(
+ web_contents, request, callback, extension);
+}
+
+bool ShellAppDelegate::CheckMediaAccessPermission(
+ content::WebContents* web_contents,
+ const GURL& security_origin,
+ content::MediaStreamType type,
+ const Extension* extension) {
+ media_capture_util::VerifyMediaAccessPermission(type, extension);
+ return true;
+}
+
+int ShellAppDelegate::PreferredIconSize() {
+ return extension_misc::EXTENSION_ICON_SMALL;
+}
+
+void ShellAppDelegate::SetWebContentsBlocked(
+ content::WebContents* web_contents,
+ bool blocked) {
+ NOTIMPLEMENTED();
+}
+
+bool ShellAppDelegate::IsWebContentsVisible(
+ content::WebContents* web_contents) {
+ return true;
+}
+
+void ShellAppDelegate::SetTerminatingCallback(const base::Closure& callback) {
+ // TODO(jamescook): Should app_shell continue to close the app window
+ // manually or should it use a browser termination callback like Chrome?
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_app_delegate.h b/chromium/extensions/shell/browser/shell_app_delegate.h
new file mode 100644
index 00000000000..22b3ec01438
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_app_delegate.h
@@ -0,0 +1,63 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_APP_DELEGATE_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_APP_DELEGATE_H_
+
+#include "base/macros.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "extensions/browser/app_window/app_delegate.h"
+
+namespace extensions {
+
+// AppDelegate implementation for app_shell. Sets focus after the WebContents is
+// created. Ignores most operations that would create a new dialog or window.
+class ShellAppDelegate : public AppDelegate {
+ public:
+ ShellAppDelegate();
+ ~ShellAppDelegate() override;
+
+ // AppDelegate overrides:
+ void InitWebContents(content::WebContents* web_contents) override;
+ void RenderViewCreated(content::RenderViewHost* render_view_host) override;
+ void ResizeWebContents(content::WebContents* web_contents,
+ const gfx::Size& size) override;
+ content::WebContents* OpenURLFromTab(
+ content::BrowserContext* context,
+ content::WebContents* source,
+ const content::OpenURLParams& params) override;
+ void AddNewContents(content::BrowserContext* context,
+ content::WebContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_rect,
+ bool user_gesture,
+ bool* was_blocked) override;
+ content::ColorChooser* ShowColorChooser(content::WebContents* web_contents,
+ SkColor initial_color) override;
+ void RunFileChooser(content::WebContents* tab,
+ const content::FileChooserParams& params) override;
+ void RequestMediaAccessPermission(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback,
+ const Extension* extension) override;
+ bool CheckMediaAccessPermission(content::WebContents* web_contents,
+ const GURL& security_origin,
+ content::MediaStreamType type,
+ const Extension* extension) override;
+ int PreferredIconSize() override;
+ void SetWebContentsBlocked(content::WebContents* web_contents,
+ bool blocked) override;
+ bool IsWebContentsVisible(content::WebContents* web_contents) override;
+ void SetTerminatingCallback(const base::Closure& callback) override;
+ void OnHide() override {}
+ void OnShow() override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShellAppDelegate);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_APP_DELEGATE_H_
diff --git a/chromium/extensions/shell/browser/shell_app_view_guest_delegate.cc b/chromium/extensions/shell/browser/shell_app_view_guest_delegate.cc
new file mode 100644
index 00000000000..4662eba1294
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_app_view_guest_delegate.cc
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_app_view_guest_delegate.h"
+
+#include "extensions/shell/browser/shell_app_delegate.h"
+
+namespace extensions {
+
+ShellAppViewGuestDelegate::ShellAppViewGuestDelegate() {
+}
+
+ShellAppViewGuestDelegate::~ShellAppViewGuestDelegate() {
+}
+
+bool ShellAppViewGuestDelegate::HandleContextMenu(
+ content::WebContents* web_contents,
+ const content::ContextMenuParams& params) {
+ return false;
+}
+
+AppDelegate* ShellAppViewGuestDelegate::CreateAppDelegate() {
+ return new ShellAppDelegate();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_app_view_guest_delegate.h b/chromium/extensions/shell/browser/shell_app_view_guest_delegate.h
new file mode 100644
index 00000000000..f74ff6de3b5
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_app_view_guest_delegate.h
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_APP_VIEW_GUEST_DELEGATE_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_APP_VIEW_GUEST_DELEGATE_H_
+
+#include "base/macros.h"
+#include "content/public/common/context_menu_params.h"
+#include "extensions/browser/guest_view/app_view/app_view_guest_delegate.h"
+
+namespace extensions {
+
+class ShellAppViewGuestDelegate : public AppViewGuestDelegate {
+ public:
+ ShellAppViewGuestDelegate();
+ ~ShellAppViewGuestDelegate() override;
+
+ // AppViewGuestDelegate:
+ bool HandleContextMenu(content::WebContents* web_contents,
+ const content::ContextMenuParams& params) override;
+ AppDelegate* CreateAppDelegate() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShellAppViewGuestDelegate);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_APP_VIEW_GUEST_DELEGATE_H_
diff --git a/chromium/extensions/shell/browser/shell_app_window_client.cc b/chromium/extensions/shell/browser/shell_app_window_client.cc
new file mode 100644
index 00000000000..8ad16707338
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_app_window_client.cc
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_app_window_client.h"
+
+#include <vector>
+
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/shell/browser/desktop_controller.h"
+
+namespace extensions {
+
+ShellAppWindowClient::ShellAppWindowClient() {
+}
+
+ShellAppWindowClient::~ShellAppWindowClient() {
+}
+
+AppWindow* ShellAppWindowClient::CreateAppWindow(
+ content::BrowserContext* context,
+ const Extension* extension) {
+ return DesktopController::instance()->CreateAppWindow(context, extension);
+}
+
+void ShellAppWindowClient::OpenDevToolsWindow(
+ content::WebContents* web_contents,
+ const base::Closure& callback) {
+ NOTIMPLEMENTED();
+}
+
+bool ShellAppWindowClient::IsCurrentChannelOlderThanDev() {
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_app_window_client.h b/chromium/extensions/shell/browser/shell_app_window_client.h
new file mode 100644
index 00000000000..ecb5f0ba385
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_app_window_client.h
@@ -0,0 +1,38 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_APP_WINDOW_CLIENT_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_APP_WINDOW_CLIENT_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "extensions/browser/app_window/app_window_client.h"
+
+namespace extensions {
+
+// app_shell's AppWindowClient implementation.
+class ShellAppWindowClient : public AppWindowClient {
+ public:
+ ShellAppWindowClient();
+ ~ShellAppWindowClient() override;
+
+ // AppWindowClient overrides:
+ AppWindow* CreateAppWindow(content::BrowserContext* context,
+ const Extension* extension) override;
+ // Note that CreateNativeAppWindow is defined in separate (per-framework)
+ // implementation files.
+ NativeAppWindow* CreateNativeAppWindow(
+ AppWindow* window,
+ AppWindow::CreateParams* params) override;
+ void OpenDevToolsWindow(content::WebContents* web_contents,
+ const base::Closure& callback) override;
+ bool IsCurrentChannelOlderThanDev() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShellAppWindowClient);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_APP_WINDOW_CLIENT_H_
diff --git a/chromium/extensions/shell/browser/shell_app_window_client_aura.cc b/chromium/extensions/shell/browser/shell_app_window_client_aura.cc
new file mode 100644
index 00000000000..af63f278b05
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_app_window_client_aura.cc
@@ -0,0 +1,23 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_app_window_client.h"
+
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/shell/browser/desktop_controller.h"
+#include "extensions/shell/browser/shell_native_app_window_aura.h"
+
+namespace extensions {
+
+NativeAppWindow* ShellAppWindowClient::CreateNativeAppWindow(
+ AppWindow* window,
+ AppWindow::CreateParams* params) {
+ ShellNativeAppWindow* native_app_window =
+ new ShellNativeAppWindowAura(window, *params);
+ DesktopController::instance()->AddAppWindow(
+ native_app_window->GetNativeWindow());
+ return native_app_window;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_app_window_client_mac.mm b/chromium/extensions/shell/browser/shell_app_window_client_mac.mm
new file mode 100644
index 00000000000..c5e0c0949ff
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_app_window_client_mac.mm
@@ -0,0 +1,23 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_app_window_client.h"
+
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/shell/browser/desktop_controller.h"
+#import "extensions/shell/browser/shell_native_app_window_mac.h"
+
+namespace extensions {
+
+NativeAppWindow* ShellAppWindowClient::CreateNativeAppWindow(
+ AppWindow* window,
+ AppWindow::CreateParams* params) {
+ ShellNativeAppWindow* native_app_window =
+ new ShellNativeAppWindowMac(window, *params);
+ DesktopController::instance()->AddAppWindow(
+ native_app_window->GetNativeWindow());
+ return native_app_window;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_audio_controller_chromeos.cc b/chromium/extensions/shell/browser/shell_audio_controller_chromeos.cc
new file mode 100644
index 00000000000..1e89065861b
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_audio_controller_chromeos.cc
@@ -0,0 +1,93 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_audio_controller_chromeos.h"
+
+#include <algorithm>
+
+#include "chromeos/audio/audio_device.h"
+
+namespace extensions {
+
+namespace {
+
+// Returns a pointer to the device in |devices| with ID |node_id|, or NULL if it
+// isn't present.
+const chromeos::AudioDevice* GetDevice(const chromeos::AudioDeviceList& devices,
+ uint64_t node_id) {
+ for (chromeos::AudioDeviceList::const_iterator it = devices.begin();
+ it != devices.end(); ++it) {
+ if (it->id == node_id)
+ return &(*it);
+ }
+ return NULL;
+}
+
+} // namespace
+
+ShellAudioController::ShellAudioController() {
+ chromeos::CrasAudioHandler::Get()->AddAudioObserver(this);
+ ActivateDevices();
+}
+
+ShellAudioController::~ShellAudioController() {
+ chromeos::CrasAudioHandler::Get()->RemoveAudioObserver(this);
+}
+
+void ShellAudioController::OnOutputNodeVolumeChanged(uint64_t /* node_id */,
+ int /* volume */) {
+}
+
+void ShellAudioController::OnOutputMuteChanged(bool /* mute_on */,
+ bool /* system_adjust */) {}
+
+void ShellAudioController::OnInputNodeGainChanged(uint64_t /* node_id */,
+ int /* gain */) {
+}
+
+void ShellAudioController::OnInputMuteChanged(bool /* mute_on */) {
+}
+
+void ShellAudioController::OnAudioNodesChanged() {
+ VLOG(1) << "Audio nodes changed";
+ ActivateDevices();
+}
+
+void ShellAudioController::OnActiveOutputNodeChanged() {}
+
+void ShellAudioController::OnActiveInputNodeChanged() {}
+
+void ShellAudioController::ActivateDevices() {
+ chromeos::CrasAudioHandler* handler = chromeos::CrasAudioHandler::Get();
+ chromeos::AudioDeviceList devices;
+ handler->GetAudioDevices(&devices);
+ sort(devices.begin(), devices.end(), chromeos::AudioDeviceCompare());
+
+ uint64_t best_input = 0, best_output = 0;
+ for (chromeos::AudioDeviceList::const_reverse_iterator it = devices.rbegin();
+ it != devices.rend() && (!best_input || !best_output); ++it) {
+ // TODO(derat): Need to check |plugged_time|?
+ if (it->is_input && !best_input)
+ best_input = it->id;
+ else if (!it->is_input && !best_output)
+ best_output = it->id;
+ }
+
+ if (best_input && best_input != handler->GetPrimaryActiveInputNode()) {
+ const chromeos::AudioDevice* device = GetDevice(devices, best_input);
+ DCHECK(device);
+ VLOG(1) << "Activating input device: " << device->ToString();
+ handler->SwitchToDevice(*device, true,
+ chromeos::CrasAudioHandler::ACTIVATE_BY_USER);
+ }
+ if (best_output && best_output != handler->GetPrimaryActiveOutputNode()) {
+ const chromeos::AudioDevice* device = GetDevice(devices, best_output);
+ DCHECK(device);
+ VLOG(1) << "Activating output device: " << device->ToString();
+ handler->SwitchToDevice(*device, true,
+ chromeos::CrasAudioHandler::ACTIVATE_BY_USER);
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_audio_controller_chromeos.h b/chromium/extensions/shell/browser/shell_audio_controller_chromeos.h
new file mode 100644
index 00000000000..c0153df1751
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_audio_controller_chromeos.h
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_AUDIO_CONTROLLER_CHROMEOS_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_AUDIO_CONTROLLER_CHROMEOS_H_
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "chromeos/audio/cras_audio_handler.h"
+
+namespace extensions {
+
+// Ensures that the "best" input and output audio devices are always active.
+class ShellAudioController : public chromeos::CrasAudioHandler::AudioObserver {
+ public:
+ ShellAudioController();
+ ~ShellAudioController() override;
+
+ // chromeos::CrasAudioHandler::Observer implementation:
+ void OnOutputNodeVolumeChanged(uint64_t node_id, int volume) override;
+ void OnOutputMuteChanged(bool mute_on, bool system_adjust) override;
+ void OnInputNodeGainChanged(uint64_t node_id, int gain) override;
+ void OnInputMuteChanged(bool mute_on) override;
+ void OnAudioNodesChanged() override;
+ void OnActiveOutputNodeChanged() override;
+ void OnActiveInputNodeChanged() override;
+
+ private:
+ // Gets the current device list from CRAS, chooses the best input and output
+ // device, and activates them if they aren't already active.
+ void ActivateDevices();
+
+ DISALLOW_COPY_AND_ASSIGN(ShellAudioController);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_AUDIO_CONTROLLER_CHROMEOS_H_
diff --git a/chromium/extensions/shell/browser/shell_audio_controller_chromeos_unittest.cc b/chromium/extensions/shell/browser/shell_audio_controller_chromeos_unittest.cc
new file mode 100644
index 00000000000..15b1e5f006b
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_audio_controller_chromeos_unittest.cc
@@ -0,0 +1,140 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_audio_controller_chromeos.h"
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "chromeos/audio/audio_device.h"
+#include "chromeos/audio/audio_devices_pref_handler.h"
+#include "chromeos/audio/cras_audio_handler.h"
+#include "chromeos/dbus/audio_node.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/fake_cras_audio_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using chromeos::AudioDevice;
+using chromeos::AudioNode;
+using chromeos::AudioNodeList;
+
+namespace extensions {
+
+class ShellAudioControllerTest : public testing::Test {
+ public:
+ ShellAudioControllerTest() : next_node_id_(1) {
+ // This also initializes DBusThreadManager.
+ scoped_ptr<chromeos::DBusThreadManagerSetter> dbus_setter =
+ chromeos::DBusThreadManager::GetSetterForTesting();
+
+ audio_client_ = new chromeos::FakeCrasAudioClient();
+ audio_client_->SetAudioNodesForTesting(AudioNodeList());
+ dbus_setter->SetCrasAudioClient(make_scoped_ptr(audio_client_));
+
+ chromeos::CrasAudioHandler::InitializeForTesting();
+ audio_handler_ = chromeos::CrasAudioHandler::Get();
+
+ controller_.reset(new ShellAudioController());
+ }
+
+ ~ShellAudioControllerTest() override {
+ controller_.reset();
+ chromeos::CrasAudioHandler::Shutdown();
+ chromeos::DBusThreadManager::Shutdown();
+ }
+
+ protected:
+ // Fills a AudioNode for use by tests.
+ AudioNode CreateNode(chromeos::AudioDeviceType type) {
+ AudioNode node;
+ node.is_input =
+ type == chromeos::AUDIO_TYPE_MIC ||
+ type == chromeos::AUDIO_TYPE_INTERNAL_MIC ||
+ type == chromeos::AUDIO_TYPE_KEYBOARD_MIC;
+ node.id = next_node_id_++;
+ node.type = AudioDevice::GetTypeString(type);
+ return node;
+ }
+
+ // Changes the active state of the node with |id| in |nodes|.
+ void SetNodeActive(AudioNodeList* nodes, uint64_t id, bool active) {
+ for (AudioNodeList::iterator it = nodes->begin();
+ it != nodes->end(); ++it) {
+ if (it->id == id) {
+ it->active = active;
+ return;
+ }
+ }
+ ASSERT_TRUE(false) << "Didn't find ID " << id;
+ }
+
+ chromeos::FakeCrasAudioClient* audio_client_; // Not owned.
+ chromeos::CrasAudioHandler* audio_handler_; // Not owned.
+ scoped_ptr<ShellAudioController> controller_;
+
+ // Next audio node ID to be returned by CreateNode().
+ uint64_t next_node_id_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShellAudioControllerTest);
+};
+
+// Tests that higher-priority devices are activated as soon as they're
+// connected.
+TEST_F(ShellAudioControllerTest, SelectBestDevices) {
+ AudioNode internal_speaker =
+ CreateNode(chromeos::AUDIO_TYPE_INTERNAL_SPEAKER);
+ AudioNode internal_mic = CreateNode(chromeos::AUDIO_TYPE_INTERNAL_MIC);
+ AudioNode headphone = CreateNode(chromeos::AUDIO_TYPE_HEADPHONE);
+ AudioNode external_mic = CreateNode(chromeos::AUDIO_TYPE_MIC);
+
+ // AudioDevice gives the headphone jack a higher priority than the internal
+ // speaker and an external mic a higher priority than the internal mic, so we
+ // should start out favoring headphones and the external mic.
+ AudioNodeList all_nodes;
+ all_nodes.push_back(internal_speaker);
+ all_nodes.push_back(internal_mic);
+ all_nodes.push_back(headphone);
+ all_nodes.push_back(external_mic);
+ audio_client_->SetAudioNodesAndNotifyObserversForTesting(all_nodes);
+ EXPECT_EQ(headphone.id, audio_handler_->GetPrimaryActiveOutputNode());
+ EXPECT_EQ(external_mic.id, audio_handler_->GetPrimaryActiveInputNode());
+
+ // Unplug the headphones and mic and check that we switch to the internal
+ // devices.
+ AudioNodeList internal_nodes;
+ internal_nodes.push_back(internal_speaker);
+ internal_nodes.push_back(internal_mic);
+ audio_client_->SetAudioNodesAndNotifyObserversForTesting(internal_nodes);
+ EXPECT_EQ(internal_speaker.id, audio_handler_->GetPrimaryActiveOutputNode());
+ EXPECT_EQ(internal_mic.id, audio_handler_->GetPrimaryActiveInputNode());
+
+ // Switch back to the external devices. Mark the previously-activated internal
+ // devices as being active so CrasAudioHandler doesn't complain.
+ SetNodeActive(&all_nodes, internal_speaker.id, true);
+ SetNodeActive(&all_nodes, internal_mic.id, true);
+ audio_client_->SetAudioNodesAndNotifyObserversForTesting(all_nodes);
+ EXPECT_EQ(headphone.id, audio_handler_->GetPrimaryActiveOutputNode());
+ EXPECT_EQ(external_mic.id, audio_handler_->GetPrimaryActiveInputNode());
+}
+
+// Tests that active audio devices are unmuted and have correct initial volume.
+TEST_F(ShellAudioControllerTest, InitialVolume) {
+ AudioNodeList nodes;
+ nodes.push_back(CreateNode(chromeos::AUDIO_TYPE_INTERNAL_SPEAKER));
+ nodes.push_back(CreateNode(chromeos::AUDIO_TYPE_INTERNAL_MIC));
+ audio_client_->SetAudioNodesAndNotifyObserversForTesting(nodes);
+
+ EXPECT_FALSE(audio_handler_->IsOutputMuted());
+ EXPECT_FALSE(audio_handler_->IsInputMuted());
+ EXPECT_EQ(static_cast<double>(
+ chromeos::AudioDevicesPrefHandler::kDefaultOutputVolumePercent),
+ audio_handler_->GetOutputVolumePercent());
+
+ // TODO(rkc): The default value for gain is wrong. http://crbug.com/442489
+ EXPECT_EQ(75.0, audio_handler_->GetInputGainPercent());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_browser_context.cc b/chromium/extensions/shell/browser/shell_browser_context.cc
new file mode 100644
index 00000000000..4fd5a33ae32
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_browser_context.cc
@@ -0,0 +1,95 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_browser_context.h"
+
+#include <utility>
+
+#include "base/command_line.h"
+#include "components/guest_view/browser/guest_view_manager.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/content_switches.h"
+#include "extensions/browser/extension_protocols.h"
+#include "extensions/common/constants.h"
+#include "extensions/shell/browser/shell_browser_main_parts.h"
+#include "extensions/shell/browser/shell_extension_system.h"
+#include "extensions/shell/browser/shell_network_delegate.h"
+#include "extensions/shell/browser/shell_special_storage_policy.h"
+#include "extensions/shell/browser/shell_url_request_context_getter.h"
+
+namespace extensions {
+
+namespace {
+
+bool IgnoreCertificateErrors() {
+ return base::CommandLine::ForCurrentProcess()->HasSwitch(
+ ::switches::kIgnoreCertificateErrors);
+}
+
+} // namespace
+
+// Create a normal recording browser context. If we used an incognito context
+// then app_shell would also have to create a normal context and manage both.
+ShellBrowserContext::ShellBrowserContext(
+ ShellBrowserMainParts* browser_main_parts)
+ : content::ShellBrowserContext(false /* off_the_record */,
+ nullptr /* net_log */),
+ storage_policy_(new ShellSpecialStoragePolicy),
+ browser_main_parts_(browser_main_parts) {
+}
+
+ShellBrowserContext::~ShellBrowserContext() {
+}
+
+content::BrowserPluginGuestManager* ShellBrowserContext::GetGuestManager() {
+ return guest_view::GuestViewManager::FromBrowserContext(this);
+}
+
+storage::SpecialStoragePolicy* ShellBrowserContext::GetSpecialStoragePolicy() {
+ return storage_policy_.get();
+}
+
+net::URLRequestContextGetter* ShellBrowserContext::CreateRequestContext(
+ content::ProtocolHandlerMap* protocol_handlers,
+ content::URLRequestInterceptorScopedVector request_interceptors) {
+ DCHECK(!url_request_context_getter());
+ // Handle only chrome-extension:// requests. app_shell does not support
+ // chrome-extension-resource:// requests (it does not store shared extension
+ // data in its installation directory).
+ InfoMap* extension_info_map =
+ browser_main_parts_->extension_system()->info_map();
+ (*protocol_handlers)[kExtensionScheme] =
+ linked_ptr<net::URLRequestJobFactory::ProtocolHandler>(
+ CreateExtensionProtocolHandler(false /* is_incognito */,
+ extension_info_map)
+ .release());
+
+ set_url_request_context_getter(new ShellURLRequestContextGetter(
+ this, IgnoreCertificateErrors(), GetPath(),
+ content::BrowserThread::GetMessageLoopProxyForThread(
+ content::BrowserThread::IO),
+ content::BrowserThread::GetMessageLoopProxyForThread(
+ content::BrowserThread::FILE),
+ protocol_handlers, std::move(request_interceptors), nullptr /* net_log */,
+ extension_info_map));
+ resource_context_->set_url_request_context_getter(
+ url_request_context_getter());
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(
+ &ShellBrowserContext::InitURLRequestContextOnIOThread,
+ base::Unretained(this)));
+ return url_request_context_getter();
+}
+
+void ShellBrowserContext::InitURLRequestContextOnIOThread() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+
+ // GetURLRequestContext() will create a URLRequestContext if it isn't
+ // initialized.
+ url_request_context_getter()->GetURLRequestContext();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_browser_context.h b/chromium/extensions/shell/browser/shell_browser_context.h
new file mode 100644
index 00000000000..a98ef1e4391
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_browser_context.h
@@ -0,0 +1,44 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_BROWSER_CONTEXT_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_BROWSER_CONTEXT_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "content/shell/browser/shell_browser_context.h"
+#include "storage/browser/quota/special_storage_policy.h"
+
+namespace extensions {
+
+class InfoMap;
+class ShellBrowserMainParts;
+class ShellSpecialStoragePolicy;
+
+// The BrowserContext used by the content, apps and extensions systems in
+// app_shell.
+class ShellBrowserContext : public content::ShellBrowserContext {
+ public:
+ explicit ShellBrowserContext(ShellBrowserMainParts* browser_main_parts);
+ ~ShellBrowserContext() override;
+
+ // content::BrowserContext implementation.
+ content::BrowserPluginGuestManager* GetGuestManager() override;
+ storage::SpecialStoragePolicy* GetSpecialStoragePolicy() override;
+ net::URLRequestContextGetter* CreateRequestContext(
+ content::ProtocolHandlerMap* protocol_handlers,
+ content::URLRequestInterceptorScopedVector request_interceptors) override;
+
+ private:
+ void InitURLRequestContextOnIOThread();
+
+ scoped_refptr<storage::SpecialStoragePolicy> storage_policy_;
+ ShellBrowserMainParts* browser_main_parts_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellBrowserContext);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_BROWSER_CONTEXT_H_
diff --git a/chromium/extensions/shell/browser/shell_browser_context_keyed_service_factories.cc b/chromium/extensions/shell/browser/shell_browser_context_keyed_service_factories.cc
new file mode 100644
index 00000000000..9d3767f2fae
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_browser_context_keyed_service_factories.cc
@@ -0,0 +1,22 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_browser_context_keyed_service_factories.h"
+
+#include "extensions/browser/updater/update_service_factory.h"
+#include "extensions/shell/browser/api/identity/identity_api.h"
+
+namespace extensions {
+namespace shell {
+
+void EnsureBrowserContextKeyedServiceFactoriesBuilt() {
+ IdentityAPI::GetFactoryInstance();
+
+ // TODO(rockot): Remove this once UpdateService is supported across all
+ // extensions embedders (and namely chrome.)
+ UpdateServiceFactory::GetInstance();
+}
+
+} // namespace shell
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_browser_context_keyed_service_factories.h b/chromium/extensions/shell/browser/shell_browser_context_keyed_service_factories.h
new file mode 100644
index 00000000000..e252181015a
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_browser_context_keyed_service_factories.h
@@ -0,0 +1,18 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_BROWSER_CONTEXT_KEYED_SERVICE_FACTORIES_H_
+#define EXTENSIONS_SHELL_BROWSER_BROWSER_CONTEXT_KEYED_SERVICE_FACTORIES_H_
+
+namespace extensions {
+namespace shell {
+
+// Ensures the existence of any BrowserContextKeyedServiceFactory provided by
+// the core extensions code.
+void EnsureBrowserContextKeyedServiceFactoriesBuilt();
+
+} // namespace shell
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_BROWSER_CONTEXT_KEYED_SERVICE_FACTORIES_H_
diff --git a/chromium/extensions/shell/browser/shell_browser_main_delegate.h b/chromium/extensions/shell/browser/shell_browser_main_delegate.h
new file mode 100644
index 00000000000..a3ddf7f2986
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_browser_main_delegate.h
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_BROWSER_MAIN_DELEGATE_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_BROWSER_MAIN_DELEGATE_H_
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class DesktopController;
+
+class ShellBrowserMainDelegate {
+ public:
+ virtual ~ShellBrowserMainDelegate() {}
+
+ // Called to start an application after all initialization processes that are
+ // necesary to run apps are completed.
+ virtual void Start(content::BrowserContext* context) = 0;
+
+ // Called after the main message looop has stopped, but before
+ // other services such as BrowserContext / extension system are shut down.
+ virtual void Shutdown() = 0;
+
+ // Creates the ShellDesktopControllerAura instance to initialize the root
+ // window and window manager. Subclass may return its subclass to customize
+ // the window manager.
+ virtual DesktopController* CreateDesktopController() = 0;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_BROWSER_MAIN_DELEGATE_H_
diff --git a/chromium/extensions/shell/browser/shell_browser_main_parts.cc b/chromium/extensions/shell/browser/shell_browser_main_parts.cc
new file mode 100644
index 00000000000..e636fa0b904
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_browser_main_parts.cc
@@ -0,0 +1,299 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_browser_main_parts.h"
+
+#include <string>
+
+#include "base/command_line.h"
+#include "base/run_loop.h"
+#include "build/build_config.h"
+#include "components/devtools_http_handler/devtools_http_handler.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/prefs/pref_service.h"
+#include "components/storage_monitor/storage_monitor.h"
+#include "components/update_client/update_query_params.h"
+#include "content/public/browser/child_process_security_policy.h"
+#include "content/public/browser/context_factory.h"
+#include "content/public/common/result_codes.h"
+#include "content/shell/browser/shell_devtools_manager_delegate.h"
+#include "extensions/browser/app_window/app_window_client.h"
+#include "extensions/browser/browser_context_keyed_service_factories.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/updater/update_service.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/switches.h"
+#include "extensions/shell/browser/shell_browser_context.h"
+#include "extensions/shell/browser/shell_browser_context_keyed_service_factories.h"
+#include "extensions/shell/browser/shell_browser_main_delegate.h"
+#include "extensions/shell/browser/shell_desktop_controller_aura.h"
+#include "extensions/shell/browser/shell_device_client.h"
+#include "extensions/shell/browser/shell_extension_system.h"
+#include "extensions/shell/browser/shell_extension_system_factory.h"
+#include "extensions/shell/browser/shell_extensions_browser_client.h"
+#include "extensions/shell/browser/shell_oauth2_token_service.h"
+#include "extensions/shell/browser/shell_prefs.h"
+#include "extensions/shell/browser/shell_update_query_params_delegate.h"
+#include "extensions/shell/common/shell_extensions_client.h"
+#include "extensions/shell/common/switches.h"
+#include "ui/base/ime/input_method_initializer.h"
+#include "ui/base/resource/resource_bundle.h"
+
+#if defined(USE_AURA)
+#include "ui/aura/env.h"
+#endif
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/audio/audio_devices_pref_handler_impl.h"
+#include "chromeos/audio/cras_audio_handler.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/disks/disk_mount_manager.h"
+#include "chromeos/network/network_handler.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/dbus/bluez_dbus_manager.h"
+#include "extensions/shell/browser/shell_audio_controller_chromeos.h"
+#include "extensions/shell/browser/shell_network_controller_chromeos.h"
+#endif
+
+#if defined(OS_MACOSX)
+#include "extensions/shell/browser/shell_browser_main_parts_mac.h"
+#endif
+
+#if !defined(DISABLE_NACL)
+#include "components/nacl/browser/nacl_browser.h"
+#include "components/nacl/browser/nacl_process_host.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/shell/browser/shell_nacl_browser_delegate.h"
+#endif
+
+using base::CommandLine;
+using content::BrowserContext;
+
+#if !defined(DISABLE_NACL)
+using content::BrowserThread;
+#endif
+
+namespace extensions {
+
+ShellBrowserMainParts::ShellBrowserMainParts(
+ const content::MainFunctionParams& parameters,
+ ShellBrowserMainDelegate* browser_main_delegate)
+ : devtools_http_handler_(nullptr),
+ extension_system_(nullptr),
+ parameters_(parameters),
+ run_message_loop_(true),
+ browser_main_delegate_(browser_main_delegate) {
+}
+
+ShellBrowserMainParts::~ShellBrowserMainParts() {
+ DCHECK(!devtools_http_handler_);
+}
+
+void ShellBrowserMainParts::PreMainMessageLoopStart() {
+ // TODO(jamescook): Initialize touch here?
+#if defined(OS_MACOSX)
+ MainPartsPreMainMessageLoopStartMac();
+#endif
+}
+
+void ShellBrowserMainParts::PostMainMessageLoopStart() {
+#if defined(OS_CHROMEOS)
+ // Perform initialization of D-Bus objects here rather than in the below
+ // helper classes so those classes' tests can initialize stub versions of the
+ // D-Bus objects.
+ chromeos::DBusThreadManager::Initialize();
+ chromeos::disks::DiskMountManager::Initialize();
+
+ bluez::BluezDBusManager::Initialize(
+ chromeos::DBusThreadManager::Get()->GetSystemBus(),
+ chromeos::DBusThreadManager::Get()->IsUsingStub(
+ chromeos::DBusClientBundle::BLUETOOTH));
+
+ chromeos::NetworkHandler::Initialize();
+ network_controller_.reset(new ShellNetworkController(
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueNative(
+ switches::kAppShellPreferredNetwork)));
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAppShellAllowRoaming)) {
+ network_controller_->SetCellularAllowRoaming(true);
+ }
+#else
+ // Non-Chrome OS platforms are for developer convenience, so use a test IME.
+ ui::InitializeInputMethodForTesting();
+#endif
+}
+
+void ShellBrowserMainParts::PreEarlyInitialization() {
+}
+
+int ShellBrowserMainParts::PreCreateThreads() {
+ // TODO(jamescook): Initialize chromeos::CrosSettings here?
+
+ content::ChildProcessSecurityPolicy::GetInstance()->RegisterWebSafeScheme(
+ kExtensionScheme);
+ content::ChildProcessSecurityPolicy::GetInstance()->RegisterWebSafeScheme(
+ kExtensionResourceScheme);
+
+ // Return no error.
+ return 0;
+}
+
+void ShellBrowserMainParts::PreMainMessageLoopRun() {
+ // Initialize our "profile" equivalent.
+ browser_context_.reset(new ShellBrowserContext(this));
+
+ // app_shell only supports a single user, so all preferences live in the user
+ // data directory, including the device-wide local state.
+ local_state_ = shell_prefs::CreateLocalState(browser_context_->GetPath());
+ user_pref_service_ =
+ shell_prefs::CreateUserPrefService(browser_context_.get());
+
+#if defined(OS_CHROMEOS)
+ chromeos::CrasAudioHandler::Initialize(
+ new chromeos::AudioDevicesPrefHandlerImpl(local_state_.get()));
+ audio_controller_.reset(new ShellAudioController());
+#endif
+
+#if defined(USE_AURA)
+ aura::Env::GetInstance()->set_context_factory(content::GetContextFactory());
+#endif
+
+ storage_monitor::StorageMonitor::Create();
+
+ desktop_controller_.reset(browser_main_delegate_->CreateDesktopController());
+
+ // TODO(jamescook): Initialize user_manager::UserManager.
+
+ device_client_.reset(new ShellDeviceClient);
+
+ extensions_client_.reset(CreateExtensionsClient());
+ ExtensionsClient::Set(extensions_client_.get());
+
+ extensions_browser_client_.reset(CreateExtensionsBrowserClient(
+ browser_context_.get(), user_pref_service_.get()));
+ ExtensionsBrowserClient::Set(extensions_browser_client_.get());
+
+ update_query_params_delegate_.reset(new ShellUpdateQueryParamsDelegate);
+ update_client::UpdateQueryParams::SetDelegate(
+ update_query_params_delegate_.get());
+
+ // Create our custom ExtensionSystem first because other
+ // KeyedServices depend on it.
+ // TODO(yoz): Move this after EnsureBrowserContextKeyedServiceFactoriesBuilt.
+ CreateExtensionSystem();
+
+ // Register additional KeyedService factories here. See
+ // ChromeBrowserMainExtraPartsProfiles for details.
+ EnsureBrowserContextKeyedServiceFactoriesBuilt();
+ ShellExtensionSystemFactory::GetInstance();
+
+ BrowserContextDependencyManager::GetInstance()->CreateBrowserContextServices(
+ browser_context_.get());
+
+ // Initialize OAuth2 support from command line.
+ base::CommandLine* cmd = base::CommandLine::ForCurrentProcess();
+ oauth2_token_service_.reset(new ShellOAuth2TokenService(
+ browser_context_.get(),
+ cmd->GetSwitchValueASCII(switches::kAppShellUser),
+ cmd->GetSwitchValueASCII(switches::kAppShellRefreshToken)));
+
+#if !defined(DISABLE_NACL)
+ // Takes ownership.
+ nacl::NaClBrowser::SetDelegate(
+ new ShellNaClBrowserDelegate(browser_context_.get()));
+ // Track the task so it can be canceled if app_shell shuts down very quickly,
+ // such as in browser tests.
+ task_tracker_.PostTask(
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO).get(),
+ FROM_HERE,
+ base::Bind(nacl::NaClProcessHost::EarlyStartup));
+#endif
+
+ devtools_http_handler_.reset(
+ content::ShellDevToolsManagerDelegate::CreateHttpHandler(
+ browser_context_.get()));
+ if (parameters_.ui_task) {
+ // For running browser tests.
+ parameters_.ui_task->Run();
+ delete parameters_.ui_task;
+ run_message_loop_ = false;
+ } else {
+ browser_main_delegate_->Start(browser_context_.get());
+ }
+}
+
+bool ShellBrowserMainParts::MainMessageLoopRun(int* result_code) {
+ if (!run_message_loop_)
+ return true;
+ // TODO(yoz): just return false here?
+ base::RunLoop run_loop;
+ run_loop.Run();
+ *result_code = content::RESULT_CODE_NORMAL_EXIT;
+ return true;
+}
+
+void ShellBrowserMainParts::PostMainMessageLoopRun() {
+ // NOTE: Please destroy objects in the reverse order of their creation.
+ browser_main_delegate_->Shutdown();
+ devtools_http_handler_.reset();
+
+#if !defined(DISABLE_NACL)
+ task_tracker_.TryCancelAll();
+ nacl::NaClBrowser::SetDelegate(nullptr);
+#endif
+
+ oauth2_token_service_.reset();
+ BrowserContextDependencyManager::GetInstance()->DestroyBrowserContextServices(
+ browser_context_.get());
+ extension_system_ = NULL;
+ ExtensionsBrowserClient::Set(NULL);
+ extensions_browser_client_.reset();
+
+ desktop_controller_.reset();
+
+ storage_monitor::StorageMonitor::Destroy();
+
+#if defined(OS_CHROMEOS)
+ audio_controller_.reset();
+ chromeos::CrasAudioHandler::Shutdown();
+#endif
+
+ user_pref_service_->CommitPendingWrite();
+ user_pref_service_.reset();
+ local_state_->CommitPendingWrite();
+ local_state_.reset();
+
+ browser_context_.reset();
+}
+
+void ShellBrowserMainParts::PostDestroyThreads() {
+#if defined(OS_CHROMEOS)
+ network_controller_.reset();
+ chromeos::NetworkHandler::Shutdown();
+ chromeos::disks::DiskMountManager::Shutdown();
+ device::BluetoothAdapterFactory::Shutdown();
+ bluez::BluezDBusManager::Shutdown();
+ chromeos::DBusThreadManager::Shutdown();
+#endif
+}
+
+ExtensionsClient* ShellBrowserMainParts::CreateExtensionsClient() {
+ return new ShellExtensionsClient();
+}
+
+ExtensionsBrowserClient* ShellBrowserMainParts::CreateExtensionsBrowserClient(
+ content::BrowserContext* context,
+ PrefService* service) {
+ return new ShellExtensionsBrowserClient(context, service);
+}
+
+void ShellBrowserMainParts::CreateExtensionSystem() {
+ DCHECK(browser_context_);
+ extension_system_ = static_cast<ShellExtensionSystem*>(
+ ExtensionSystem::Get(browser_context_.get()));
+ extension_system_->InitForRegularProfile(true);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_browser_main_parts.h b/chromium/extensions/shell/browser/shell_browser_main_parts.h
new file mode 100644
index 00000000000..0525cbbdaf7
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_browser_main_parts.h
@@ -0,0 +1,120 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_BROWSER_MAIN_PARTS_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_BROWSER_MAIN_PARTS_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/task/cancelable_task_tracker.h"
+#include "build/build_config.h"
+#include "content/public/browser/browser_main_parts.h"
+#include "content/public/common/main_function_params.h"
+#include "ui/aura/window_tree_host_observer.h"
+
+class PrefService;
+
+namespace content {
+class BrowserContext;
+struct MainFunctionParams;
+}
+
+namespace devtools_http_handler {
+class DevToolsHttpHandler;
+}
+
+namespace views {
+class Widget;
+}
+
+namespace extensions {
+
+class AppWindowClient;
+class DesktopController;
+class ExtensionsBrowserClient;
+class ExtensionsClient;
+class ShellBrowserContext;
+class ShellBrowserMainDelegate;
+class ShellDeviceClient;
+class ShellExtensionSystem;
+class ShellOAuth2TokenService;
+class ShellUpdateQueryParamsDelegate;
+
+#if defined(OS_CHROMEOS)
+class ShellAudioController;
+class ShellNetworkController;
+#endif
+
+// Handles initialization of AppShell.
+class ShellBrowserMainParts : public content::BrowserMainParts {
+ public:
+ ShellBrowserMainParts(const content::MainFunctionParams& parameters,
+ ShellBrowserMainDelegate* browser_main_delegate);
+ ~ShellBrowserMainParts() override;
+
+ ShellBrowserContext* browser_context() { return browser_context_.get(); }
+
+ ShellExtensionSystem* extension_system() { return extension_system_; }
+
+ // BrowserMainParts overrides.
+ void PreEarlyInitialization() override;
+ void PreMainMessageLoopStart() override;
+ void PostMainMessageLoopStart() override;
+ int PreCreateThreads() override;
+ void PreMainMessageLoopRun() override;
+ bool MainMessageLoopRun(int* result_code) override;
+ void PostMainMessageLoopRun() override;
+ void PostDestroyThreads() override;
+
+ protected:
+ // app_shell embedders may need custom extensions client interfaces.
+ // This class takes ownership of the returned objects.
+ virtual ExtensionsClient* CreateExtensionsClient();
+ virtual ExtensionsBrowserClient* CreateExtensionsBrowserClient(
+ content::BrowserContext* context,
+ PrefService* service);
+
+ private:
+ // Creates and initializes the ExtensionSystem.
+ void CreateExtensionSystem();
+
+#if defined(OS_CHROMEOS)
+ scoped_ptr<ShellNetworkController> network_controller_;
+ scoped_ptr<ShellAudioController> audio_controller_;
+#endif
+ scoped_ptr<DesktopController> desktop_controller_;
+ scoped_ptr<ShellBrowserContext> browser_context_;
+ scoped_ptr<PrefService> local_state_;
+ scoped_ptr<PrefService> user_pref_service_;
+ scoped_ptr<ShellDeviceClient> device_client_;
+ scoped_ptr<AppWindowClient> app_window_client_;
+ scoped_ptr<ExtensionsClient> extensions_client_;
+ scoped_ptr<ExtensionsBrowserClient> extensions_browser_client_;
+ scoped_ptr<devtools_http_handler::DevToolsHttpHandler> devtools_http_handler_;
+ scoped_ptr<ShellUpdateQueryParamsDelegate> update_query_params_delegate_;
+ scoped_ptr<ShellOAuth2TokenService> oauth2_token_service_;
+
+ // Owned by the KeyedService system.
+ ShellExtensionSystem* extension_system_;
+
+ // For running app browsertests.
+ const content::MainFunctionParams parameters_;
+
+ // If true, indicates the main message loop should be run
+ // in MainMessageLoopRun. If false, it has already been run.
+ bool run_message_loop_;
+
+ scoped_ptr<ShellBrowserMainDelegate> browser_main_delegate_;
+
+#if !defined(DISABLE_NACL)
+ base::CancelableTaskTracker task_tracker_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(ShellBrowserMainParts);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_BROWSER_MAIN_PARTS_H_
diff --git a/chromium/extensions/shell/browser/shell_browser_main_parts_mac.h b/chromium/extensions/shell/browser/shell_browser_main_parts_mac.h
new file mode 100644
index 00000000000..f250a7a48b3
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_browser_main_parts_mac.h
@@ -0,0 +1,15 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_BROWSER_MAIN_PARTS_MAC_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_BROWSER_MAIN_PARTS_MAC_H_
+
+namespace extensions {
+
+// Called from ShellBrowserMainParts::PreMainMessageLoopStart.
+void MainPartsPreMainMessageLoopStartMac();
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_BROWSER_MAIN_PARTS_MAC_H_
diff --git a/chromium/extensions/shell/browser/shell_browser_main_parts_mac.mm b/chromium/extensions/shell/browser/shell_browser_main_parts_mac.mm
new file mode 100644
index 00000000000..9fe902dc3c9
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_browser_main_parts_mac.mm
@@ -0,0 +1,16 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_browser_main_parts.h"
+
+#import "content/shell/browser/shell_application_mac.h"
+
+namespace extensions {
+
+void MainPartsPreMainMessageLoopStartMac() {
+ // Force the NSApplication subclass to be used.
+ [ShellCrApplication sharedApplication];
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_browsertest.cc b/chromium/extensions/shell/browser/shell_browsertest.cc
new file mode 100644
index 00000000000..baf75d9f712
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_browsertest.cc
@@ -0,0 +1,38 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/logging.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/test_utils.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/app_window_registry.h"
+#include "extensions/browser/notification_types.h"
+#include "extensions/shell/test/shell_apitest.h"
+
+#if defined(USE_AURA)
+#include "ui/aura/window.h"
+#endif
+
+namespace extensions {
+
+// Test that we can open an app window and wait for it to load.
+IN_PROC_BROWSER_TEST_F(ShellApiTest, Basic) {
+ ASSERT_TRUE(RunAppTest("platform_app")) << message_;
+
+ // A window was created.
+ AppWindow* app_window =
+ AppWindowRegistry::Get(browser_context())->app_windows().front();
+ ASSERT_TRUE(app_window);
+
+ // TOOD(yoz): Test for focus on Cocoa.
+ // app_window->GetBaseWindow()->IsActive() is possible, although on Mac,
+ // focus changes are asynchronous, so interactive_ui_tests are required.
+#if defined(USE_AURA)
+ // The web contents have focus.
+ EXPECT_TRUE(app_window->web_contents()->GetContentNativeView()->HasFocus());
+#endif
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_content_browser_client.cc b/chromium/extensions/shell/browser/shell_content_browser_client.cc
new file mode 100644
index 00000000000..26298c70b91
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_content_browser_client.cc
@@ -0,0 +1,263 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_content_browser_client.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "components/guest_view/browser/guest_view_message_filter.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/site_instance.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/common/content_descriptors.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+#include "content/shell/browser/shell_browser_context.h"
+#include "content/shell/browser/shell_devtools_manager_delegate.h"
+#include "extensions/browser/extension_message_filter.h"
+#include "extensions/browser/extension_protocols.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/guest_view/extensions_guest_view_message_filter.h"
+#include "extensions/browser/info_map.h"
+#include "extensions/browser/io_thread_extension_message_filter.h"
+#include "extensions/browser/process_map.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/switches.h"
+#include "extensions/shell/browser/shell_browser_context.h"
+#include "extensions/shell/browser/shell_browser_main_parts.h"
+#include "extensions/shell/browser/shell_extension_system.h"
+#include "extensions/shell/browser/shell_speech_recognition_manager_delegate.h"
+#include "url/gurl.h"
+
+#if !defined(DISABLE_NACL)
+#include "components/nacl/browser/nacl_browser.h"
+#include "components/nacl/browser/nacl_host_message_filter.h"
+#include "components/nacl/browser/nacl_process_host.h"
+#include "components/nacl/common/nacl_process_type.h"
+#include "components/nacl/common/nacl_switches.h"
+#include "content/public/browser/browser_child_process_host.h"
+#include "content/public/browser/child_process_data.h"
+#endif
+
+using base::CommandLine;
+using content::BrowserContext;
+using content::BrowserThread;
+
+namespace extensions {
+namespace {
+
+ShellContentBrowserClient* g_instance = nullptr;
+
+} // namespace
+
+ShellContentBrowserClient::ShellContentBrowserClient(
+ ShellBrowserMainDelegate* browser_main_delegate)
+ : browser_main_parts_(nullptr),
+ browser_main_delegate_(browser_main_delegate) {
+ DCHECK(!g_instance);
+ g_instance = this;
+}
+
+ShellContentBrowserClient::~ShellContentBrowserClient() {
+ g_instance = nullptr;
+}
+
+// static
+ShellContentBrowserClient* ShellContentBrowserClient::Get() {
+ return g_instance;
+}
+
+content::BrowserContext* ShellContentBrowserClient::GetBrowserContext() {
+ return browser_main_parts_->browser_context();
+}
+
+content::BrowserMainParts* ShellContentBrowserClient::CreateBrowserMainParts(
+ const content::MainFunctionParams& parameters) {
+ browser_main_parts_ =
+ CreateShellBrowserMainParts(parameters, browser_main_delegate_);
+ return browser_main_parts_;
+}
+
+void ShellContentBrowserClient::RenderProcessWillLaunch(
+ content::RenderProcessHost* host) {
+ int render_process_id = host->GetID();
+ BrowserContext* browser_context = browser_main_parts_->browser_context();
+ host->AddFilter(
+ new ExtensionMessageFilter(render_process_id, browser_context));
+ host->AddFilter(
+ new IOThreadExtensionMessageFilter(render_process_id, browser_context));
+ host->AddFilter(
+ new ExtensionsGuestViewMessageFilter(
+ render_process_id, browser_context));
+ // PluginInfoMessageFilter is not required because app_shell does not have
+ // the concept of disabled plugins.
+#if !defined(DISABLE_NACL)
+ host->AddFilter(new nacl::NaClHostMessageFilter(
+ render_process_id,
+ browser_context->IsOffTheRecord(),
+ browser_context->GetPath(),
+ host->GetStoragePartition()->GetURLRequestContext()));
+#endif
+}
+
+bool ShellContentBrowserClient::ShouldUseProcessPerSite(
+ content::BrowserContext* browser_context,
+ const GURL& effective_url) {
+ // This ensures that all render views created for a single app will use the
+ // same render process (see content::SiteInstance::GetProcess). Otherwise the
+ // default behavior of ContentBrowserClient will lead to separate render
+ // processes for the background page and each app window view.
+ return true;
+}
+
+bool ShellContentBrowserClient::IsHandledURL(const GURL& url) {
+ if (!url.is_valid())
+ return false;
+ // Keep in sync with ProtocolHandlers added in
+ // ShellBrowserContext::CreateRequestContext() and in
+ // content::ShellURLRequestContextGetter::GetURLRequestContext().
+ static const char* const kProtocolList[] = {
+ url::kBlobScheme,
+ content::kChromeDevToolsScheme,
+ content::kChromeUIScheme,
+ url::kDataScheme,
+ url::kFileScheme,
+ url::kFileSystemScheme,
+ kExtensionScheme,
+ kExtensionResourceScheme,
+ };
+ for (size_t i = 0; i < arraysize(kProtocolList); ++i) {
+ if (url.scheme() == kProtocolList[i])
+ return true;
+ }
+ return false;
+}
+
+void ShellContentBrowserClient::SiteInstanceGotProcess(
+ content::SiteInstance* site_instance) {
+ // If this isn't an extension renderer there's nothing to do.
+ const Extension* extension = GetExtension(site_instance);
+ if (!extension)
+ return;
+
+ ProcessMap::Get(browser_main_parts_->browser_context())
+ ->Insert(extension->id(),
+ site_instance->GetProcess()->GetID(),
+ site_instance->GetId());
+
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&InfoMap::RegisterExtensionProcess,
+ browser_main_parts_->extension_system()->info_map(),
+ extension->id(),
+ site_instance->GetProcess()->GetID(),
+ site_instance->GetId()));
+}
+
+void ShellContentBrowserClient::SiteInstanceDeleting(
+ content::SiteInstance* site_instance) {
+ // If this isn't an extension renderer there's nothing to do.
+ const Extension* extension = GetExtension(site_instance);
+ if (!extension)
+ return;
+
+ ProcessMap::Get(browser_main_parts_->browser_context())
+ ->Remove(extension->id(),
+ site_instance->GetProcess()->GetID(),
+ site_instance->GetId());
+
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&InfoMap::UnregisterExtensionProcess,
+ browser_main_parts_->extension_system()->info_map(),
+ extension->id(),
+ site_instance->GetProcess()->GetID(),
+ site_instance->GetId()));
+}
+
+void ShellContentBrowserClient::AppendExtraCommandLineSwitches(
+ base::CommandLine* command_line,
+ int child_process_id) {
+ std::string process_type =
+ command_line->GetSwitchValueASCII(::switches::kProcessType);
+ if (process_type == ::switches::kRendererProcess)
+ AppendRendererSwitches(command_line);
+}
+
+content::SpeechRecognitionManagerDelegate*
+ShellContentBrowserClient::CreateSpeechRecognitionManagerDelegate() {
+ return new speech::ShellSpeechRecognitionManagerDelegate();
+}
+
+content::BrowserPpapiHost*
+ShellContentBrowserClient::GetExternalBrowserPpapiHost(int plugin_process_id) {
+#if !defined(DISABLE_NACL)
+ content::BrowserChildProcessHostIterator iter(PROCESS_TYPE_NACL_LOADER);
+ while (!iter.Done()) {
+ nacl::NaClProcessHost* host = static_cast<nacl::NaClProcessHost*>(
+ iter.GetDelegate());
+ if (host->process() &&
+ host->process()->GetData().id == plugin_process_id) {
+ // Found the plugin.
+ return host->browser_ppapi_host();
+ }
+ ++iter;
+ }
+#endif
+ return nullptr;
+}
+
+void ShellContentBrowserClient::GetAdditionalAllowedSchemesForFileSystem(
+ std::vector<std::string>* additional_allowed_schemes) {
+ ContentBrowserClient::GetAdditionalAllowedSchemesForFileSystem(
+ additional_allowed_schemes);
+ additional_allowed_schemes->push_back(kExtensionScheme);
+}
+
+content::DevToolsManagerDelegate*
+ShellContentBrowserClient::GetDevToolsManagerDelegate() {
+ return new content::ShellDevToolsManagerDelegate();
+}
+
+ShellBrowserMainParts* ShellContentBrowserClient::CreateShellBrowserMainParts(
+ const content::MainFunctionParams& parameters,
+ ShellBrowserMainDelegate* browser_main_delegate) {
+ return new ShellBrowserMainParts(parameters, browser_main_delegate);
+}
+
+void ShellContentBrowserClient::AppendRendererSwitches(
+ base::CommandLine* command_line) {
+ // TODO(jamescook): Should we check here if the process is in the extension
+ // service process map, or can we assume all renderers are extension
+ // renderers?
+ command_line->AppendSwitch(switches::kExtensionProcess);
+
+#if !defined(DISABLE_NACL)
+ // NOTE: app_shell does not support non-SFI mode, so it does not pass through
+ // SFI switches either here or for the zygote process.
+ static const char* const kSwitchNames[] = {
+ ::switches::kEnableNaClDebug,
+ };
+ command_line->CopySwitchesFrom(*base::CommandLine::ForCurrentProcess(),
+ kSwitchNames, arraysize(kSwitchNames));
+#endif // !defined(DISABLE_NACL)
+}
+
+const Extension* ShellContentBrowserClient::GetExtension(
+ content::SiteInstance* site_instance) {
+ ExtensionRegistry* registry =
+ ExtensionRegistry::Get(site_instance->GetBrowserContext());
+ return registry->enabled_extensions().GetExtensionOrAppByURL(
+ site_instance->GetSiteURL());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_content_browser_client.h b/chromium/extensions/shell/browser/shell_content_browser_client.h
new file mode 100644
index 00000000000..a87d36ecacd
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_content_browser_client.h
@@ -0,0 +1,84 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_CONTENT_BROWSER_CLIENT_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_CONTENT_BROWSER_CLIENT_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "content/public/browser/content_browser_client.h"
+
+class GURL;
+
+namespace base {
+class CommandLine;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+class Extension;
+class ShellBrowserMainDelegate;
+class ShellBrowserMainParts;
+
+// Content module browser process support for app_shell.
+class ShellContentBrowserClient : public content::ContentBrowserClient {
+ public:
+ explicit ShellContentBrowserClient(
+ ShellBrowserMainDelegate* browser_main_delegate);
+ ~ShellContentBrowserClient() override;
+
+ // Returns the single instance.
+ static ShellContentBrowserClient* Get();
+
+ // Returns the single browser context for app_shell.
+ content::BrowserContext* GetBrowserContext();
+
+ // content::ContentBrowserClient overrides.
+ content::BrowserMainParts* CreateBrowserMainParts(
+ const content::MainFunctionParams& parameters) override;
+ void RenderProcessWillLaunch(content::RenderProcessHost* host) override;
+ bool ShouldUseProcessPerSite(content::BrowserContext* browser_context,
+ const GURL& effective_url) override;
+ // TODO(jamescook): Quota management?
+ bool IsHandledURL(const GURL& url) override;
+ void SiteInstanceGotProcess(content::SiteInstance* site_instance) override;
+ void SiteInstanceDeleting(content::SiteInstance* site_instance) override;
+ void AppendExtraCommandLineSwitches(base::CommandLine* command_line,
+ int child_process_id) override;
+ content::SpeechRecognitionManagerDelegate*
+ CreateSpeechRecognitionManagerDelegate() override;
+ content::BrowserPpapiHost* GetExternalBrowserPpapiHost(
+ int plugin_process_id) override;
+ void GetAdditionalAllowedSchemesForFileSystem(
+ std::vector<std::string>* additional_schemes) override;
+ content::DevToolsManagerDelegate* GetDevToolsManagerDelegate() override;
+
+ protected:
+ // Subclasses may wish to provide their own ShellBrowserMainParts.
+ virtual ShellBrowserMainParts* CreateShellBrowserMainParts(
+ const content::MainFunctionParams& parameters,
+ ShellBrowserMainDelegate* browser_main_delegate);
+
+ private:
+ // Appends command line switches for a renderer process.
+ void AppendRendererSwitches(base::CommandLine* command_line);
+
+ // Returns the extension or app associated with |site_instance| or NULL.
+ const Extension* GetExtension(content::SiteInstance* site_instance);
+
+ // Owned by content::BrowserMainLoop.
+ ShellBrowserMainParts* browser_main_parts_;
+
+ // Owned by ShellBrowserMainParts.
+ ShellBrowserMainDelegate* browser_main_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellContentBrowserClient);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_CONTENT_BROWSER_CLIENT_H_
diff --git a/chromium/extensions/shell/browser/shell_desktop_controller_aura.cc b/chromium/extensions/shell/browser/shell_desktop_controller_aura.cc
new file mode 100644
index 00000000000..18474de0b0b
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_desktop_controller_aura.cc
@@ -0,0 +1,348 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_desktop_controller_aura.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/native_app_window.h"
+#include "extensions/shell/browser/shell_app_delegate.h"
+#include "extensions/shell/browser/shell_app_window_client.h"
+#include "extensions/shell/browser/shell_screen.h"
+#include "extensions/shell/common/switches.h"
+#include "ui/aura/client/cursor_client.h"
+#include "ui/aura/client/default_capture_client.h"
+#include "ui/aura/layout_manager.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_event_dispatcher.h"
+#include "ui/aura/window_tree_host.h"
+#include "ui/base/cursor/cursor.h"
+#include "ui/base/cursor/image_cursors.h"
+#include "ui/base/ime/input_method_initializer.h"
+#include "ui/base/user_activity/user_activity_detector.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/screen.h"
+#include "ui/wm/core/base_focus_rules.h"
+#include "ui/wm/core/compound_event_filter.h"
+#include "ui/wm/core/cursor_manager.h"
+#include "ui/wm/core/focus_controller.h"
+#include "ui/wm/core/native_cursor_manager.h"
+#include "ui/wm/core/native_cursor_manager_delegate.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "ui/chromeos/user_activity_power_manager_notifier.h"
+#include "ui/display/types/display_mode.h"
+#include "ui/display/types/display_snapshot.h"
+#endif
+
+namespace extensions {
+namespace {
+
+// A simple layout manager that makes each new window fill its parent.
+class FillLayout : public aura::LayoutManager {
+ public:
+ FillLayout() {}
+ ~FillLayout() override {}
+
+ private:
+ // aura::LayoutManager:
+ void OnWindowResized() override {}
+
+ void OnWindowAddedToLayout(aura::Window* child) override {
+ if (!child->parent())
+ return;
+
+ // Create a rect at 0,0 with the size of the parent.
+ gfx::Size parent_size = child->parent()->bounds().size();
+ child->SetBounds(gfx::Rect(parent_size));
+ }
+
+ void OnWillRemoveWindowFromLayout(aura::Window* child) override {}
+
+ void OnWindowRemovedFromLayout(aura::Window* child) override {}
+
+ void OnChildWindowVisibilityChanged(aura::Window* child,
+ bool visible) override {}
+
+ void SetChildBounds(aura::Window* child,
+ const gfx::Rect& requested_bounds) override {
+ SetChildBoundsDirect(child, requested_bounds);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(FillLayout);
+};
+
+// A class that bridges the gap between CursorManager and Aura. It borrows
+// heavily from AshNativeCursorManager.
+class ShellNativeCursorManager : public wm::NativeCursorManager {
+ public:
+ explicit ShellNativeCursorManager(aura::WindowTreeHost* host)
+ : host_(host), image_cursors_(new ui::ImageCursors) {}
+ ~ShellNativeCursorManager() override {}
+
+ // wm::NativeCursorManager overrides.
+ void SetDisplay(const gfx::Display& display,
+ wm::NativeCursorManagerDelegate* delegate) override {
+ if (image_cursors_->SetDisplay(display, display.device_scale_factor()))
+ SetCursor(delegate->GetCursor(), delegate);
+ }
+
+ void SetCursor(gfx::NativeCursor cursor,
+ wm::NativeCursorManagerDelegate* delegate) override {
+ image_cursors_->SetPlatformCursor(&cursor);
+ cursor.set_device_scale_factor(image_cursors_->GetScale());
+ delegate->CommitCursor(cursor);
+
+ if (delegate->IsCursorVisible())
+ ApplyCursor(cursor);
+ }
+
+ void SetVisibility(bool visible,
+ wm::NativeCursorManagerDelegate* delegate) override {
+ delegate->CommitVisibility(visible);
+
+ if (visible) {
+ SetCursor(delegate->GetCursor(), delegate);
+ } else {
+ gfx::NativeCursor invisible_cursor(ui::kCursorNone);
+ image_cursors_->SetPlatformCursor(&invisible_cursor);
+ ApplyCursor(invisible_cursor);
+ }
+ }
+
+ void SetCursorSet(ui::CursorSetType cursor_set,
+ wm::NativeCursorManagerDelegate* delegate) override {
+ image_cursors_->SetCursorSet(cursor_set);
+ delegate->CommitCursorSet(cursor_set);
+ if (delegate->IsCursorVisible())
+ SetCursor(delegate->GetCursor(), delegate);
+ }
+
+ void SetMouseEventsEnabled(
+ bool enabled,
+ wm::NativeCursorManagerDelegate* delegate) override {
+ delegate->CommitMouseEventsEnabled(enabled);
+ SetVisibility(delegate->IsCursorVisible(), delegate);
+ }
+
+ private:
+ // Sets |cursor| as the active cursor within Aura.
+ void ApplyCursor(gfx::NativeCursor cursor) { host_->SetCursor(cursor); }
+
+ aura::WindowTreeHost* host_; // Not owned.
+
+ scoped_ptr<ui::ImageCursors> image_cursors_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellNativeCursorManager);
+};
+
+class AppsFocusRules : public wm::BaseFocusRules {
+ public:
+ AppsFocusRules() {}
+ ~AppsFocusRules() override {}
+
+ bool SupportsChildActivation(aura::Window* window) const override {
+ return true;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AppsFocusRules);
+};
+
+} // namespace
+
+ShellDesktopControllerAura::ShellDesktopControllerAura()
+ : app_window_client_(new ShellAppWindowClient) {
+ extensions::AppWindowClient::Set(app_window_client_.get());
+
+#if defined(OS_CHROMEOS)
+ chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(
+ this);
+ display_configurator_.reset(new ui::DisplayConfigurator);
+ display_configurator_->Init(false);
+ display_configurator_->ForceInitialConfigure(0);
+ display_configurator_->AddObserver(this);
+#endif
+ CreateRootWindow();
+}
+
+ShellDesktopControllerAura::~ShellDesktopControllerAura() {
+ CloseAppWindows();
+ DestroyRootWindow();
+#if defined(OS_CHROMEOS)
+ chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(
+ this);
+#endif
+ extensions::AppWindowClient::Set(NULL);
+}
+
+gfx::Size ShellDesktopControllerAura::GetWindowSize() {
+ return host_->window()->bounds().size();
+}
+
+AppWindow* ShellDesktopControllerAura::CreateAppWindow(
+ content::BrowserContext* context,
+ const Extension* extension) {
+ app_windows_.push_back(
+ new AppWindow(context, new ShellAppDelegate, extension));
+ return app_windows_.back();
+}
+
+void ShellDesktopControllerAura::AddAppWindow(gfx::NativeWindow window) {
+ aura::Window* root_window = host_->window();
+ root_window->AddChild(window);
+}
+
+void ShellDesktopControllerAura::RemoveAppWindow(AppWindow* window) {
+ auto iter = std::find(app_windows_.begin(), app_windows_.end(), window);
+ DCHECK(iter != app_windows_.end());
+ app_windows_.erase(iter);
+}
+
+void ShellDesktopControllerAura::CloseAppWindows() {
+ // Create a copy of the window vector, because closing the windows will
+ // trigger RemoveAppWindow, which will invalidate the iterator.
+ // This vector should be small enough that this should not be an issue.
+ std::vector<AppWindow*> app_windows(app_windows_);
+ for (AppWindow* app_window : app_windows)
+ app_window->GetBaseWindow()->Close(); // Close() deletes |app_window|.
+ app_windows_.clear();
+}
+
+aura::Window* ShellDesktopControllerAura::GetDefaultParent(
+ aura::Window* context,
+ aura::Window* window,
+ const gfx::Rect& bounds) {
+ return host_->window();
+}
+
+#if defined(OS_CHROMEOS)
+void ShellDesktopControllerAura::PowerButtonEventReceived(
+ bool down,
+ const base::TimeTicks& timestamp) {
+ if (down) {
+ chromeos::DBusThreadManager::Get()
+ ->GetPowerManagerClient()
+ ->RequestShutdown();
+ }
+}
+
+void ShellDesktopControllerAura::OnDisplayModeChanged(
+ const ui::DisplayConfigurator::DisplayStateList& displays) {
+ gfx::Size size = GetPrimaryDisplaySize();
+ if (!size.IsEmpty())
+ host_->UpdateRootWindowSize(size);
+}
+#endif
+
+void ShellDesktopControllerAura::OnHostCloseRequested(
+ const aura::WindowTreeHost* host) {
+ DCHECK_EQ(host_.get(), host);
+ CloseAppWindows();
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
+}
+
+void ShellDesktopControllerAura::InitWindowManager() {
+ wm::FocusController* focus_controller =
+ new wm::FocusController(new AppsFocusRules());
+ aura::client::SetFocusClient(host_->window(), focus_controller);
+ host_->window()->AddPreTargetHandler(focus_controller);
+ aura::client::SetActivationClient(host_->window(), focus_controller);
+ focus_client_.reset(focus_controller);
+
+ capture_client_.reset(
+ new aura::client::DefaultCaptureClient(host_->window()));
+
+ // Ensure new windows fill the display.
+ host_->window()->SetLayoutManager(new FillLayout);
+
+ cursor_manager_.reset(
+ new wm::CursorManager(scoped_ptr<wm::NativeCursorManager>(
+ new ShellNativeCursorManager(host_.get()))));
+ cursor_manager_->SetDisplay(gfx::Screen::GetScreen()->GetPrimaryDisplay());
+ cursor_manager_->SetCursor(ui::kCursorPointer);
+ aura::client::SetCursorClient(host_->window(), cursor_manager_.get());
+
+ user_activity_detector_.reset(new ui::UserActivityDetector);
+#if defined(OS_CHROMEOS)
+ user_activity_notifier_.reset(
+ new ui::UserActivityPowerManagerNotifier(user_activity_detector_.get()));
+#endif
+}
+
+void ShellDesktopControllerAura::CreateRootWindow() {
+ // Set up basic pieces of ui::wm.
+ gfx::Size size;
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kAppShellHostWindowSize)) {
+ const std::string size_str =
+ command_line->GetSwitchValueASCII(switches::kAppShellHostWindowSize);
+ int width, height;
+ CHECK_EQ(2, sscanf(size_str.c_str(), "%dx%d", &width, &height));
+ size = gfx::Size(width, height);
+ } else {
+ size = GetPrimaryDisplaySize();
+ }
+ if (size.IsEmpty())
+ size = gfx::Size(1920, 1080);
+
+ screen_.reset(new ShellScreen(size));
+ gfx::Screen::SetScreenInstance(screen_.get());
+ // TODO(mukai): Set up input method.
+
+ host_.reset(screen_->CreateHostForPrimaryDisplay());
+ aura::client::SetWindowTreeClient(host_->window(), this);
+ root_window_event_filter_.reset(new wm::CompoundEventFilter);
+ host_->window()->AddPreTargetHandler(root_window_event_filter_.get());
+ InitWindowManager();
+
+ host_->AddObserver(this);
+
+ // Ensure the X window gets mapped.
+ host_->Show();
+}
+
+void ShellDesktopControllerAura::DestroyRootWindow() {
+ host_->RemoveObserver(this);
+ wm::FocusController* focus_controller =
+ static_cast<wm::FocusController*>(focus_client_.get());
+ if (focus_controller) {
+ host_->window()->RemovePreTargetHandler(focus_controller);
+ aura::client::SetActivationClient(host_->window(), NULL);
+ }
+ root_window_event_filter_.reset();
+ capture_client_.reset();
+ focus_client_.reset();
+ cursor_manager_.reset();
+#if defined(OS_CHROMEOS)
+ user_activity_notifier_.reset();
+#endif
+ user_activity_detector_.reset();
+ host_.reset();
+ screen_.reset();
+}
+
+gfx::Size ShellDesktopControllerAura::GetPrimaryDisplaySize() {
+#if defined(OS_CHROMEOS)
+ const ui::DisplayConfigurator::DisplayStateList& displays =
+ display_configurator_->cached_displays();
+ if (displays.empty())
+ return gfx::Size();
+ const ui::DisplayMode* mode = displays[0]->current_mode();
+ return mode ? mode->size() : gfx::Size();
+#else
+ return gfx::Size();
+#endif
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_desktop_controller_aura.h b/chromium/extensions/shell/browser/shell_desktop_controller_aura.h
new file mode 100644
index 00000000000..74595250bf2
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_desktop_controller_aura.h
@@ -0,0 +1,143 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_DESKTOP_CONTROLLER_AURA_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_DESKTOP_CONTROLLER_AURA_H_
+
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "build/build_config.h"
+#include "extensions/shell/browser/desktop_controller.h"
+#include "ui/aura/client/window_tree_client.h"
+#include "ui/aura/window_tree_host_observer.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/dbus/power_manager_client.h"
+#include "ui/display/chromeos/display_configurator.h"
+#endif
+
+namespace aura {
+class Window;
+class WindowTreeHost;
+namespace client {
+class DefaultCaptureClient;
+class FocusClient;
+}
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace gfx {
+class Size;
+}
+
+namespace ui {
+class UserActivityDetector;
+#if defined(OS_CHROMEOS)
+class UserActivityPowerManagerNotifier;
+#endif
+}
+
+namespace wm {
+class CompoundEventFilter;
+class CursorManager;
+}
+
+namespace extensions {
+class AppWindowClient;
+class Extension;
+class ShellScreen;
+
+// Handles desktop-related tasks for app_shell.
+class ShellDesktopControllerAura
+ : public DesktopController,
+ public aura::client::WindowTreeClient,
+#if defined(OS_CHROMEOS)
+ public chromeos::PowerManagerClient::Observer,
+ public ui::DisplayConfigurator::Observer,
+#endif
+ public aura::WindowTreeHostObserver {
+ public:
+ ShellDesktopControllerAura();
+ ~ShellDesktopControllerAura() override;
+
+ // DesktopController:
+ gfx::Size GetWindowSize() override;
+ AppWindow* CreateAppWindow(content::BrowserContext* context,
+ const Extension* extension) override;
+ void AddAppWindow(gfx::NativeWindow window) override;
+ void RemoveAppWindow(AppWindow* window) override;
+ void CloseAppWindows() override;
+
+ // aura::client::WindowTreeClient overrides:
+ aura::Window* GetDefaultParent(aura::Window* context,
+ aura::Window* window,
+ const gfx::Rect& bounds) override;
+
+#if defined(OS_CHROMEOS)
+ // chromeos::PowerManagerClient::Observer overrides:
+ void PowerButtonEventReceived(bool down,
+ const base::TimeTicks& timestamp) override;
+
+ // ui::DisplayConfigurator::Observer overrides.
+ void OnDisplayModeChanged(
+ const ui::DisplayConfigurator::DisplayStateList& displays) override;
+#endif
+
+ // aura::WindowTreeHostObserver overrides:
+ void OnHostCloseRequested(const aura::WindowTreeHost* host) override;
+
+ protected:
+ // Creates and sets the aura clients and window manager stuff. Subclass may
+ // initialize different sets of the clients.
+ virtual void InitWindowManager();
+
+ private:
+ // Creates the window that hosts the app.
+ void CreateRootWindow();
+
+ // Closes and destroys the root window hosting the app.
+ void DestroyRootWindow();
+
+ // Returns the dimensions (in pixels) of the primary display, or an empty size
+ // if the dimensions can't be determined or no display is connected.
+ gfx::Size GetPrimaryDisplaySize();
+
+#if defined(OS_CHROMEOS)
+ scoped_ptr<ui::DisplayConfigurator> display_configurator_;
+#endif
+
+ scoped_ptr<ShellScreen> screen_;
+
+ scoped_ptr<aura::WindowTreeHost> host_;
+
+ scoped_ptr<wm::CompoundEventFilter> root_window_event_filter_;
+
+ scoped_ptr<aura::client::DefaultCaptureClient> capture_client_;
+
+ scoped_ptr<aura::client::FocusClient> focus_client_;
+
+ scoped_ptr<wm::CursorManager> cursor_manager_;
+
+ scoped_ptr<ui::UserActivityDetector> user_activity_detector_;
+#if defined(OS_CHROMEOS)
+ scoped_ptr<ui::UserActivityPowerManagerNotifier> user_activity_notifier_;
+#endif
+
+ scoped_ptr<AppWindowClient> app_window_client_;
+
+ // NativeAppWindow::Close() deletes the AppWindow.
+ std::vector<AppWindow*> app_windows_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellDesktopControllerAura);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_DESKTOP_CONTROLLER_AURA_H_
diff --git a/chromium/extensions/shell/browser/shell_desktop_controller_aura_unittest.cc b/chromium/extensions/shell/browser/shell_desktop_controller_aura_unittest.cc
new file mode 100644
index 00000000000..8bcfc4ad334
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_desktop_controller_aura_unittest.cc
@@ -0,0 +1,76 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_desktop_controller_aura.h"
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "ui/aura/test/aura_test_base.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/fake_power_manager_client.h"
+#endif
+
+namespace extensions {
+
+class ShellDesktopControllerAuraTest : public aura::test::AuraTestBase {
+ public:
+ ShellDesktopControllerAuraTest()
+#if defined(OS_CHROMEOS)
+ : power_manager_client_(NULL)
+#endif
+ {
+ }
+ ~ShellDesktopControllerAuraTest() override {}
+
+ void SetUp() override {
+#if defined(OS_CHROMEOS)
+ scoped_ptr<chromeos::DBusThreadManagerSetter> dbus_setter =
+ chromeos::DBusThreadManager::GetSetterForTesting();
+ power_manager_client_ = new chromeos::FakePowerManagerClient();
+ dbus_setter->SetPowerManagerClient(make_scoped_ptr(power_manager_client_));
+#endif
+ aura::test::AuraTestBase::SetUp();
+ controller_.reset(new ShellDesktopControllerAura());
+ }
+
+ void TearDown() override {
+ controller_.reset();
+ aura::test::AuraTestBase::TearDown();
+#if defined(OS_CHROMEOS)
+ chromeos::DBusThreadManager::Shutdown();
+#endif
+ }
+
+ protected:
+ scoped_ptr<ShellDesktopControllerAura> controller_;
+
+#if defined(OS_CHROMEOS)
+ chromeos::FakePowerManagerClient* power_manager_client_; // Not owned.
+#endif
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShellDesktopControllerAuraTest);
+};
+
+#if defined(OS_CHROMEOS)
+// Tests that a shutdown request is sent to the power manager when the power
+// button is pressed.
+TEST_F(ShellDesktopControllerAuraTest, PowerButton) {
+ // Ignore button releases.
+ power_manager_client_->SendPowerButtonEvent(false /* down */,
+ base::TimeTicks());
+ EXPECT_EQ(0, power_manager_client_->num_request_shutdown_calls());
+
+ // A button press should trigger a shutdown request.
+ power_manager_client_->SendPowerButtonEvent(true /* down */,
+ base::TimeTicks());
+ EXPECT_EQ(1, power_manager_client_->num_request_shutdown_calls());
+}
+#endif
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_desktop_controller_mac.h b/chromium/extensions/shell/browser/shell_desktop_controller_mac.h
new file mode 100644
index 00000000000..dda610c7fe7
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_desktop_controller_mac.h
@@ -0,0 +1,45 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_DESKTOP_CONTROLLER_MAC_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_DESKTOP_CONTROLLER_MAC_H_
+
+#include "extensions/shell/browser/desktop_controller.h"
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace extensions {
+
+class AppWindow;
+class AppWindowClient;
+
+// A simple implementation of the app_shell DesktopController for Mac Cocoa.
+// Only currently supports one app window (unlike Aura).
+class ShellDesktopControllerMac : public DesktopController {
+ public:
+ ShellDesktopControllerMac();
+ ~ShellDesktopControllerMac() override;
+
+ // DesktopController:
+ gfx::Size GetWindowSize() override;
+ AppWindow* CreateAppWindow(content::BrowserContext* context,
+ const Extension* extension) override;
+ void AddAppWindow(gfx::NativeWindow window) override;
+ void RemoveAppWindow(AppWindow* window) override;
+ void CloseAppWindows() override;
+
+ private:
+ scoped_ptr<AppWindowClient> app_window_client_;
+
+ // The desktop only supports a single app window.
+ // TODO(yoz): Support multiple app windows, as we do in Aura.
+ AppWindow* app_window_; // NativeAppWindow::Close() deletes this.
+
+ DISALLOW_COPY_AND_ASSIGN(ShellDesktopControllerMac);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_DESKTOP_CONTROLLER_MAC_H_
diff --git a/chromium/extensions/shell/browser/shell_desktop_controller_mac.mm b/chromium/extensions/shell/browser/shell_desktop_controller_mac.mm
new file mode 100644
index 00000000000..7731b0a58f8
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_desktop_controller_mac.mm
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_desktop_controller_mac.h"
+
+#include "extensions/browser/app_window/native_app_window.h"
+#include "extensions/shell/browser/shell_app_delegate.h"
+#include "extensions/shell/browser/shell_app_window_client.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/screen.h"
+
+namespace extensions {
+
+ShellDesktopControllerMac::ShellDesktopControllerMac()
+ : app_window_client_(new ShellAppWindowClient), app_window_(NULL) {
+ AppWindowClient::Set(app_window_client_.get());
+}
+
+ShellDesktopControllerMac::~ShellDesktopControllerMac() {
+ // TOOD(yoz): This is actually too late to close app windows (for tests).
+ // Maybe this is useful for non-tests.
+ CloseAppWindows();
+}
+
+gfx::Size ShellDesktopControllerMac::GetWindowSize() {
+ // This is the full screen size.
+ return gfx::Screen::GetScreen()->GetPrimaryDisplay().bounds().size();
+}
+
+AppWindow* ShellDesktopControllerMac::CreateAppWindow(
+ content::BrowserContext* context,
+ const Extension* extension) {
+ app_window_ = new AppWindow(context, new ShellAppDelegate, extension);
+ return app_window_;
+}
+
+void ShellDesktopControllerMac::AddAppWindow(gfx::NativeWindow window) {
+}
+
+void ShellDesktopControllerMac::RemoveAppWindow(AppWindow* window) {
+}
+
+void ShellDesktopControllerMac::CloseAppWindows() {
+ if (app_window_) {
+ ui::BaseWindow* window = app_window_->GetBaseWindow();
+ window->Close(); // Close() deletes |app_window_|.
+ app_window_ = NULL;
+ }
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_device_client.cc b/chromium/extensions/shell/browser/shell_device_client.cc
new file mode 100644
index 00000000000..857e2a4f2fc
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_device_client.cc
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_device_client.h"
+
+#include "base/logging.h"
+#include "content/public/browser/browser_thread.h"
+#include "device/hid/hid_service.h"
+#include "device/usb/usb_service.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+ShellDeviceClient::ShellDeviceClient() {}
+
+ShellDeviceClient::~ShellDeviceClient() {}
+
+device::UsbService* ShellDeviceClient::GetUsbService() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (!usb_service_) {
+ usb_service_ = device::UsbService::Create(
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE));
+ }
+ return usb_service_.get();
+}
+
+device::HidService* ShellDeviceClient::GetHidService() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (!hid_service_) {
+ hid_service_ = device::HidService::Create(
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE));
+ }
+ return hid_service_.get();
+}
+
+}
diff --git a/chromium/extensions/shell/browser/shell_device_client.h b/chromium/extensions/shell/browser/shell_device_client.h
new file mode 100644
index 00000000000..2a5e717c95a
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_device_client.h
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_DEVICE_CLIENT_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_DEVICE_CLIENT_H_
+
+#include "device/core/device_client.h"
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace extensions {
+
+// Implementation of device::DeviceClient that returns //device API service
+// singletons appropriate for use within app shell.
+class ShellDeviceClient : device::DeviceClient {
+ public:
+ ShellDeviceClient();
+ ~ShellDeviceClient() override;
+
+ // device::DeviceClient implementation
+ device::UsbService* GetUsbService() override;
+ device::HidService* GetHidService() override;
+
+ private:
+ scoped_ptr<device::HidService> hid_service_;
+ scoped_ptr<device::UsbService> usb_service_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellDeviceClient);
+};
+
+}
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_DEVICE_CLIENT_H_
diff --git a/chromium/extensions/shell/browser/shell_display_info_provider.cc b/chromium/extensions/shell/browser/shell_display_info_provider.cc
new file mode 100644
index 00000000000..db9cbf53b19
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_display_info_provider.cc
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_display_info_provider.h"
+
+#include "base/logging.h"
+
+namespace extensions {
+
+ShellDisplayInfoProvider::ShellDisplayInfoProvider() {
+}
+
+ShellDisplayInfoProvider::~ShellDisplayInfoProvider() {
+}
+
+bool ShellDisplayInfoProvider::SetInfo(
+ const std::string& display_id,
+ const api::system_display::DisplayProperties& info,
+ std::string* error) {
+ *error = "Not implemented";
+ return false;
+}
+
+void ShellDisplayInfoProvider::UpdateDisplayUnitInfoForPlatform(
+ const gfx::Display& display,
+ extensions::api::system_display::DisplayUnitInfo* unit) {
+ static bool logged_once = false;
+ if (!logged_once) {
+ NOTIMPLEMENTED();
+ logged_once = true;
+ }
+}
+
+// static
+DisplayInfoProvider* DisplayInfoProvider::Create() {
+ return new ShellDisplayInfoProvider();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_display_info_provider.h b/chromium/extensions/shell/browser/shell_display_info_provider.h
new file mode 100644
index 00000000000..65137f80e8e
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_display_info_provider.h
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_DISPLAY_INFO_PROVIDER_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_DISPLAY_INFO_PROVIDER_H_
+
+#include "base/macros.h"
+#include "extensions/browser/api/system_display/display_info_provider.h"
+
+namespace extensions {
+
+class ShellDisplayInfoProvider : public DisplayInfoProvider {
+ public:
+ ShellDisplayInfoProvider();
+ ~ShellDisplayInfoProvider() override;
+
+ // DisplayInfoProvider implementation.
+ bool SetInfo(const std::string& display_id,
+ const api::system_display::DisplayProperties& info,
+ std::string* error) override;
+ void UpdateDisplayUnitInfoForPlatform(
+ const gfx::Display& display,
+ extensions::api::system_display::DisplayUnitInfo* unit) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShellDisplayInfoProvider);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_DISPLAY_INFO_PROVIDER_H_
diff --git a/chromium/extensions/shell/browser/shell_extension_host_delegate.cc b/chromium/extensions/shell/browser/shell_extension_host_delegate.cc
new file mode 100644
index 00000000000..51c73341abf
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_extension_host_delegate.cc
@@ -0,0 +1,73 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_extension_host_delegate.h"
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "extensions/browser/serial_extension_host_queue.h"
+#include "extensions/shell/browser/media_capture_util.h"
+#include "extensions/shell/browser/shell_extension_web_contents_observer.h"
+
+namespace extensions {
+
+ShellExtensionHostDelegate::ShellExtensionHostDelegate() {
+}
+
+ShellExtensionHostDelegate::~ShellExtensionHostDelegate() {
+}
+
+void ShellExtensionHostDelegate::OnExtensionHostCreated(
+ content::WebContents* web_contents) {
+ ShellExtensionWebContentsObserver::CreateForWebContents(web_contents);
+}
+
+void ShellExtensionHostDelegate::OnRenderViewCreatedForBackgroundPage(
+ ExtensionHost* host) {
+}
+
+content::JavaScriptDialogManager*
+ShellExtensionHostDelegate::GetJavaScriptDialogManager() {
+ // TODO(jamescook): Create a JavaScriptDialogManager or reuse the one from
+ // content_shell.
+ NOTREACHED();
+ return NULL;
+}
+
+void ShellExtensionHostDelegate::CreateTab(content::WebContents* web_contents,
+ const std::string& extension_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_rect,
+ bool user_gesture) {
+ // TODO(jamescook): Should app_shell support opening popup windows?
+ NOTREACHED();
+}
+
+void ShellExtensionHostDelegate::ProcessMediaAccessRequest(
+ content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback,
+ const Extension* extension) {
+ // Allow access to the microphone and/or camera.
+ media_capture_util::GrantMediaStreamRequest(
+ web_contents, request, callback, extension);
+}
+
+bool ShellExtensionHostDelegate::CheckMediaAccessPermission(
+ content::WebContents* web_contents,
+ const GURL& security_origin,
+ content::MediaStreamType type,
+ const Extension* extension) {
+ media_capture_util::VerifyMediaAccessPermission(type, extension);
+ return true;
+}
+
+static base::LazyInstance<SerialExtensionHostQueue> g_queue =
+ LAZY_INSTANCE_INITIALIZER;
+
+ExtensionHostQueue* ShellExtensionHostDelegate::GetExtensionHostQueue() const {
+ return g_queue.Pointer();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_extension_host_delegate.h b/chromium/extensions/shell/browser/shell_extension_host_delegate.h
new file mode 100644
index 00000000000..d91b407e381
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_extension_host_delegate.h
@@ -0,0 +1,44 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_HOST_DELEGATE_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_HOST_DELEGATE_H_
+
+#include "base/macros.h"
+#include "extensions/browser/extension_host_delegate.h"
+
+namespace extensions {
+
+// A minimal ExtensionHostDelegate.
+class ShellExtensionHostDelegate : public ExtensionHostDelegate {
+ public:
+ ShellExtensionHostDelegate();
+ ~ShellExtensionHostDelegate() override;
+
+ // ExtensionHostDelegate implementation.
+ void OnExtensionHostCreated(content::WebContents* web_contents) override;
+ void OnRenderViewCreatedForBackgroundPage(ExtensionHost* host) override;
+ content::JavaScriptDialogManager* GetJavaScriptDialogManager() override;
+ void CreateTab(content::WebContents* web_contents,
+ const std::string& extension_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_rect,
+ bool user_gesture) override;
+ void ProcessMediaAccessRequest(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback,
+ const Extension* extension) override;
+ bool CheckMediaAccessPermission(content::WebContents* web_contents,
+ const GURL& security_origin,
+ content::MediaStreamType type,
+ const Extension* extension) override;
+ ExtensionHostQueue* GetExtensionHostQueue() const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShellExtensionHostDelegate);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_HOST_DELEGATE_H_
diff --git a/chromium/extensions/shell/browser/shell_extension_system.cc b/chromium/extensions/shell/browser/shell_extension_system.cc
new file mode 100644
index 00000000000..3fbbf14e42b
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_extension_system.cc
@@ -0,0 +1,199 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_extension_system.h"
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_source.h"
+#include "extensions/browser/api/app_runtime/app_runtime_api.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/info_map.h"
+#include "extensions/browser/notification_types.h"
+#include "extensions/browser/null_app_sorting.h"
+#include "extensions/browser/quota_service.h"
+#include "extensions/browser/runtime_data.h"
+#include "extensions/browser/service_worker_manager.h"
+#include "extensions/browser/value_store/value_store_factory_impl.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/file_util.h"
+
+using content::BrowserContext;
+using content::BrowserThread;
+
+namespace extensions {
+
+ShellExtensionSystem::ShellExtensionSystem(BrowserContext* browser_context)
+ : browser_context_(browser_context),
+ store_factory_(new ValueStoreFactoryImpl(browser_context->GetPath())),
+ weak_factory_(this) {}
+
+ShellExtensionSystem::~ShellExtensionSystem() {
+}
+
+const Extension* ShellExtensionSystem::LoadApp(const base::FilePath& app_dir) {
+ // app_shell only supports unpacked extensions.
+ // NOTE: If you add packed extension support consider removing the flag
+ // FOLLOW_SYMLINKS_ANYWHERE below. Packed extensions should not have symlinks.
+ CHECK(base::DirectoryExists(app_dir)) << app_dir.AsUTF8Unsafe();
+ int load_flags = Extension::FOLLOW_SYMLINKS_ANYWHERE;
+ std::string load_error;
+ scoped_refptr<Extension> extension = file_util::LoadExtension(
+ app_dir, Manifest::COMMAND_LINE, load_flags, &load_error);
+ if (!extension.get()) {
+ LOG(ERROR) << "Loading extension at " << app_dir.value()
+ << " failed with: " << load_error;
+ return nullptr;
+ }
+
+ // TODO(jamescook): We may want to do some of these things here:
+ // * Create a PermissionsUpdater.
+ // * Call PermissionsUpdater::GrantActivePermissions().
+ // * Call ExtensionService::SatisfyImports().
+ // * Call ExtensionPrefs::OnExtensionInstalled().
+ // * Call ExtensionRegistryObserver::OnExtensionWillbeInstalled().
+
+ ExtensionRegistry::Get(browser_context_)->AddEnabled(extension.get());
+
+ RegisterExtensionWithRequestContexts(
+ extension.get(),
+ base::Bind(
+ &ShellExtensionSystem::OnExtensionRegisteredWithRequestContexts,
+ weak_factory_.GetWeakPtr(), extension));
+
+ content::NotificationService::current()->Notify(
+ extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
+ content::Source<BrowserContext>(browser_context_),
+ content::Details<const Extension>(extension.get()));
+
+ return extension.get();
+}
+
+void ShellExtensionSystem::Init() {
+ // Inform the rest of the extensions system to start.
+ ready_.Signal();
+ content::NotificationService::current()->Notify(
+ extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
+ content::Source<BrowserContext>(browser_context_),
+ content::NotificationService::NoDetails());
+}
+
+void ShellExtensionSystem::LaunchApp(const ExtensionId& extension_id) {
+ // Send the onLaunched event.
+ DCHECK(ExtensionRegistry::Get(browser_context_)
+ ->enabled_extensions()
+ .Contains(extension_id));
+ const Extension* extension = ExtensionRegistry::Get(browser_context_)
+ ->enabled_extensions()
+ .GetByID(extension_id);
+ AppRuntimeEventRouter::DispatchOnLaunchedEvent(
+ browser_context_, extension, extensions::SOURCE_UNTRACKED);
+}
+
+void ShellExtensionSystem::Shutdown() {
+}
+
+void ShellExtensionSystem::InitForRegularProfile(bool extensions_enabled) {
+ service_worker_manager_.reset(new ServiceWorkerManager(browser_context_));
+ runtime_data_.reset(
+ new RuntimeData(ExtensionRegistry::Get(browser_context_)));
+ quota_service_.reset(new QuotaService);
+ app_sorting_.reset(new NullAppSorting);
+}
+
+ExtensionService* ShellExtensionSystem::extension_service() {
+ return nullptr;
+}
+
+RuntimeData* ShellExtensionSystem::runtime_data() {
+ return runtime_data_.get();
+}
+
+ManagementPolicy* ShellExtensionSystem::management_policy() {
+ return nullptr;
+}
+
+ServiceWorkerManager* ShellExtensionSystem::service_worker_manager() {
+ return service_worker_manager_.get();
+}
+
+SharedUserScriptMaster* ShellExtensionSystem::shared_user_script_master() {
+ return nullptr;
+}
+
+StateStore* ShellExtensionSystem::state_store() {
+ return nullptr;
+}
+
+StateStore* ShellExtensionSystem::rules_store() {
+ return nullptr;
+}
+
+scoped_refptr<ValueStoreFactory> ShellExtensionSystem::store_factory() {
+ return store_factory_;
+}
+
+InfoMap* ShellExtensionSystem::info_map() {
+ if (!info_map_.get())
+ info_map_ = new InfoMap;
+ return info_map_.get();
+}
+
+QuotaService* ShellExtensionSystem::quota_service() {
+ return quota_service_.get();
+}
+
+AppSorting* ShellExtensionSystem::app_sorting() {
+ return app_sorting_.get();
+}
+
+void ShellExtensionSystem::RegisterExtensionWithRequestContexts(
+ const Extension* extension,
+ const base::Closure& callback) {
+ BrowserThread::PostTaskAndReply(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&InfoMap::AddExtension, info_map(),
+ base::RetainedRef(extension), base::Time::Now(), false, false),
+ callback);
+}
+
+void ShellExtensionSystem::UnregisterExtensionWithRequestContexts(
+ const std::string& extension_id,
+ const UnloadedExtensionInfo::Reason reason) {
+}
+
+const OneShotEvent& ShellExtensionSystem::ready() const {
+ return ready_;
+}
+
+ContentVerifier* ShellExtensionSystem::content_verifier() {
+ return nullptr;
+}
+
+scoped_ptr<ExtensionSet> ShellExtensionSystem::GetDependentExtensions(
+ const Extension* extension) {
+ return make_scoped_ptr(new ExtensionSet());
+}
+
+void ShellExtensionSystem::InstallUpdate(const std::string& extension_id,
+ const base::FilePath& temp_dir) {
+ NOTREACHED();
+ base::DeleteFile(temp_dir, true /* recursive */);
+}
+
+void ShellExtensionSystem::OnExtensionRegisteredWithRequestContexts(
+ scoped_refptr<Extension> extension) {
+ ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_);
+ registry->AddReady(extension);
+ registry->TriggerOnReady(extension.get());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_extension_system.h b/chromium/extensions/shell/browser/shell_extension_system.h
new file mode 100644
index 00000000000..4448fb9385e
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_extension_system.h
@@ -0,0 +1,99 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_SYSTEM_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_SYSTEM_H_
+
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/common/one_shot_event.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class ValueStoreFactory;
+
+// A simplified version of ExtensionSystem for app_shell. Allows
+// app_shell to skip initialization of services it doesn't need.
+class ShellExtensionSystem : public ExtensionSystem {
+ public:
+ explicit ShellExtensionSystem(content::BrowserContext* browser_context);
+ ~ShellExtensionSystem() override;
+
+ // Loads an unpacked application from a directory. Returns the extension on
+ // success, or null otherwise.
+ const Extension* LoadApp(const base::FilePath& app_dir);
+
+ // Initializes the extension system.
+ void Init();
+
+ // Launch the app with id |extension_id|.
+ void LaunchApp(const std::string& extension_id);
+
+ // KeyedService implementation:
+ void Shutdown() override;
+
+ // ExtensionSystem implementation:
+ void InitForRegularProfile(bool extensions_enabled) override;
+ ExtensionService* extension_service() override;
+ RuntimeData* runtime_data() override;
+ ManagementPolicy* management_policy() override;
+ ServiceWorkerManager* service_worker_manager() override;
+ SharedUserScriptMaster* shared_user_script_master() override;
+ StateStore* state_store() override;
+ StateStore* rules_store() override;
+ scoped_refptr<ValueStoreFactory> store_factory() override;
+ InfoMap* info_map() override;
+ QuotaService* quota_service() override;
+ AppSorting* app_sorting() override;
+ void RegisterExtensionWithRequestContexts(
+ const Extension* extension,
+ const base::Closure& callback) override;
+ void UnregisterExtensionWithRequestContexts(
+ const std::string& extension_id,
+ const UnloadedExtensionInfo::Reason reason) override;
+ const OneShotEvent& ready() const override;
+ ContentVerifier* content_verifier() override;
+ scoped_ptr<ExtensionSet> GetDependentExtensions(
+ const Extension* extension) override;
+ void InstallUpdate(const std::string& extension_id,
+ const base::FilePath& temp_dir) override;
+
+ private:
+ void OnExtensionRegisteredWithRequestContexts(
+ scoped_refptr<Extension> extension);
+ content::BrowserContext* browser_context_; // Not owned.
+
+ // Data to be accessed on the IO thread. Must outlive process_manager_.
+ scoped_refptr<InfoMap> info_map_;
+
+ scoped_ptr<ServiceWorkerManager> service_worker_manager_;
+ scoped_ptr<RuntimeData> runtime_data_;
+ scoped_ptr<QuotaService> quota_service_;
+ scoped_ptr<AppSorting> app_sorting_;
+
+ scoped_refptr<ValueStoreFactory> store_factory_;
+
+ // Signaled when the extension system has completed its startup tasks.
+ OneShotEvent ready_;
+
+ base::WeakPtrFactory<ShellExtensionSystem> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellExtensionSystem);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_SYSTEM_H_
diff --git a/chromium/extensions/shell/browser/shell_extension_system_factory.cc b/chromium/extensions/shell/browser/shell_extension_system_factory.cc
new file mode 100644
index 00000000000..2093c5f7c73
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_extension_system_factory.cc
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_extension_system_factory.h"
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "extensions/browser/extension_prefs_factory.h"
+#include "extensions/browser/extension_registry_factory.h"
+#include "extensions/shell/browser/shell_extension_system.h"
+
+using content::BrowserContext;
+
+namespace extensions {
+
+ExtensionSystem* ShellExtensionSystemFactory::GetForBrowserContext(
+ BrowserContext* context) {
+ return static_cast<ShellExtensionSystem*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+ShellExtensionSystemFactory* ShellExtensionSystemFactory::GetInstance() {
+ return base::Singleton<ShellExtensionSystemFactory>::get();
+}
+
+ShellExtensionSystemFactory::ShellExtensionSystemFactory()
+ : ExtensionSystemProvider("ShellExtensionSystem",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(ExtensionPrefsFactory::GetInstance());
+ DependsOn(ExtensionRegistryFactory::GetInstance());
+}
+
+ShellExtensionSystemFactory::~ShellExtensionSystemFactory() {
+}
+
+KeyedService* ShellExtensionSystemFactory::BuildServiceInstanceFor(
+ BrowserContext* context) const {
+ return new ShellExtensionSystem(context);
+}
+
+BrowserContext* ShellExtensionSystemFactory::GetBrowserContextToUse(
+ BrowserContext* context) const {
+ // Use a separate instance for incognito.
+ return context;
+}
+
+bool ShellExtensionSystemFactory::ServiceIsCreatedWithBrowserContext() const {
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_extension_system_factory.h b/chromium/extensions/shell/browser/shell_extension_system_factory.h
new file mode 100644
index 00000000000..ef4fbeb8e62
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_extension_system_factory.h
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_SYSTEM_FACTORY_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_SYSTEM_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "extensions/browser/extension_system_provider.h"
+
+namespace extensions {
+
+// A factory that provides ShellExtensionSystem for app_shell.
+class ShellExtensionSystemFactory : public ExtensionSystemProvider {
+ public:
+ // ExtensionSystemProvider implementation:
+ ExtensionSystem* GetForBrowserContext(
+ content::BrowserContext* context) override;
+
+ static ShellExtensionSystemFactory* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<ShellExtensionSystemFactory>;
+
+ ShellExtensionSystemFactory();
+ ~ShellExtensionSystemFactory() override;
+
+ // BrowserContextKeyedServiceFactory implementation:
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+ bool ServiceIsCreatedWithBrowserContext() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellExtensionSystemFactory);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_SYSTEM_FACTORY_H_
diff --git a/chromium/extensions/shell/browser/shell_extension_web_contents_observer.cc b/chromium/extensions/shell/browser/shell_extension_web_contents_observer.cc
new file mode 100644
index 00000000000..9dc369103dd
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_extension_web_contents_observer.cc
@@ -0,0 +1,20 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_extension_web_contents_observer.h"
+
+DEFINE_WEB_CONTENTS_USER_DATA_KEY(
+ extensions::ShellExtensionWebContentsObserver);
+
+namespace extensions {
+
+ShellExtensionWebContentsObserver::ShellExtensionWebContentsObserver(
+ content::WebContents* web_contents)
+ : ExtensionWebContentsObserver(web_contents) {
+}
+
+ShellExtensionWebContentsObserver::~ShellExtensionWebContentsObserver() {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_extension_web_contents_observer.h b/chromium/extensions/shell/browser/shell_extension_web_contents_observer.h
new file mode 100644
index 00000000000..372b7c2d6ec
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_extension_web_contents_observer.h
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_WEB_CONTENTS_OBSERVER_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_WEB_CONTENTS_OBSERVER_H_
+
+#include "base/macros.h"
+#include "content/public/browser/web_contents_user_data.h"
+#include "extensions/browser/extension_web_contents_observer.h"
+
+namespace extensions {
+
+// The app_shell version of ExtensionWebContentsObserver.
+class ShellExtensionWebContentsObserver
+ : public ExtensionWebContentsObserver,
+ public content::WebContentsUserData<ShellExtensionWebContentsObserver> {
+ private:
+ friend class content::WebContentsUserData<ShellExtensionWebContentsObserver>;
+
+ explicit ShellExtensionWebContentsObserver(
+ content::WebContents* web_contents);
+ ~ShellExtensionWebContentsObserver() override;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellExtensionWebContentsObserver);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSION_WEB_CONTENTS_OBSERVER_H_
diff --git a/chromium/extensions/shell/browser/shell_extensions_api_client.cc b/chromium/extensions/shell/browser/shell_extensions_api_client.cc
new file mode 100644
index 00000000000..527b883ab7f
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_extensions_api_client.cc
@@ -0,0 +1,25 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_extensions_api_client.h"
+
+#include "extensions/shell/browser/shell_app_view_guest_delegate.h"
+#include "extensions/shell/browser/shell_extension_web_contents_observer.h"
+
+namespace extensions {
+
+ShellExtensionsAPIClient::ShellExtensionsAPIClient() {
+}
+
+void ShellExtensionsAPIClient::AttachWebContentsHelpers(
+ content::WebContents* web_contents) const {
+ ShellExtensionWebContentsObserver::CreateForWebContents(web_contents);
+}
+
+AppViewGuestDelegate* ShellExtensionsAPIClient::CreateAppViewGuestDelegate()
+ const {
+ return new ShellAppViewGuestDelegate();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_extensions_api_client.h b/chromium/extensions/shell/browser/shell_extensions_api_client.h
new file mode 100644
index 00000000000..17a26fc78c1
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_extensions_api_client.h
@@ -0,0 +1,24 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSIONS_API_CLIENT_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSIONS_API_CLIENT_H_
+
+#include "extensions/browser/api/extensions_api_client.h"
+
+namespace extensions {
+
+class ShellExtensionsAPIClient : public ExtensionsAPIClient {
+ public:
+ ShellExtensionsAPIClient();
+
+ // ExtensionsAPIClient implementation.
+ void AttachWebContentsHelpers(content::WebContents* web_contents) const
+ override;
+ AppViewGuestDelegate* CreateAppViewGuestDelegate() const override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSIONS_API_CLIENT_H_
diff --git a/chromium/extensions/shell/browser/shell_extensions_browser_client.cc b/chromium/extensions/shell/browser/shell_extensions_browser_client.cc
new file mode 100644
index 00000000000..fd6bfd4c0b2
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_extensions_browser_client.cc
@@ -0,0 +1,252 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_extensions_browser_client.h"
+
+#include <utility>
+
+#include "build/build_config.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/api/generated_api_registration.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_function_registry.h"
+#include "extensions/browser/mojo/service_registration.h"
+#include "extensions/browser/null_app_sorting.h"
+#include "extensions/browser/updater/null_extension_cache.h"
+#include "extensions/browser/url_request_util.h"
+#include "extensions/shell/browser/api/generated_api_registration.h"
+#include "extensions/shell/browser/shell_extension_host_delegate.h"
+#include "extensions/shell/browser/shell_extension_system_factory.h"
+#include "extensions/shell/browser/shell_extension_web_contents_observer.h"
+#include "extensions/shell/browser/shell_extensions_api_client.h"
+#include "extensions/shell/browser/shell_runtime_api_delegate.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/login/login_state.h"
+#endif
+
+using content::BrowserContext;
+using content::BrowserThread;
+
+namespace extensions {
+
+ShellExtensionsBrowserClient::ShellExtensionsBrowserClient(
+ BrowserContext* context,
+ PrefService* pref_service)
+ : browser_context_(context),
+ pref_service_(pref_service),
+ api_client_(new ShellExtensionsAPIClient),
+ extension_cache_(new NullExtensionCache()) {
+}
+
+ShellExtensionsBrowserClient::~ShellExtensionsBrowserClient() {
+}
+
+bool ShellExtensionsBrowserClient::IsShuttingDown() {
+ return false;
+}
+
+bool ShellExtensionsBrowserClient::AreExtensionsDisabled(
+ const base::CommandLine& command_line,
+ BrowserContext* context) {
+ return false;
+}
+
+bool ShellExtensionsBrowserClient::IsValidContext(BrowserContext* context) {
+ return context == browser_context_;
+}
+
+bool ShellExtensionsBrowserClient::IsSameContext(BrowserContext* first,
+ BrowserContext* second) {
+ return first == second;
+}
+
+bool ShellExtensionsBrowserClient::HasOffTheRecordContext(
+ BrowserContext* context) {
+ return false;
+}
+
+BrowserContext* ShellExtensionsBrowserClient::GetOffTheRecordContext(
+ BrowserContext* context) {
+ // app_shell only supports a single context.
+ return NULL;
+}
+
+BrowserContext* ShellExtensionsBrowserClient::GetOriginalContext(
+ BrowserContext* context) {
+ return context;
+}
+
+#if defined(OS_CHROMEOS)
+std::string ShellExtensionsBrowserClient::GetUserIdHashFromContext(
+ content::BrowserContext* context) {
+ return chromeos::LoginState::Get()->primary_user_hash();
+}
+#endif
+
+bool ShellExtensionsBrowserClient::IsGuestSession(
+ BrowserContext* context) const {
+ return false;
+}
+
+bool ShellExtensionsBrowserClient::IsExtensionIncognitoEnabled(
+ const std::string& extension_id,
+ content::BrowserContext* context) const {
+ return false;
+}
+
+bool ShellExtensionsBrowserClient::CanExtensionCrossIncognito(
+ const Extension* extension,
+ content::BrowserContext* context) const {
+ return false;
+}
+
+net::URLRequestJob*
+ShellExtensionsBrowserClient::MaybeCreateResourceBundleRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const base::FilePath& directory_path,
+ const std::string& content_security_policy,
+ bool send_cors_header) {
+ return NULL;
+}
+
+bool ShellExtensionsBrowserClient::AllowCrossRendererResourceLoad(
+ net::URLRequest* request,
+ bool is_incognito,
+ const Extension* extension,
+ InfoMap* extension_info_map) {
+ bool allowed = false;
+ if (url_request_util::AllowCrossRendererResourceLoad(
+ request, is_incognito, extension, extension_info_map, &allowed)) {
+ return allowed;
+ }
+
+ // Couldn't determine if resource is allowed. Block the load.
+ return false;
+}
+
+PrefService* ShellExtensionsBrowserClient::GetPrefServiceForContext(
+ BrowserContext* context) {
+ return pref_service_;
+}
+
+void ShellExtensionsBrowserClient::GetEarlyExtensionPrefsObservers(
+ content::BrowserContext* context,
+ std::vector<ExtensionPrefsObserver*>* observers) const {
+}
+
+ProcessManagerDelegate*
+ShellExtensionsBrowserClient::GetProcessManagerDelegate() const {
+ return NULL;
+}
+
+scoped_ptr<ExtensionHostDelegate>
+ShellExtensionsBrowserClient::CreateExtensionHostDelegate() {
+ return scoped_ptr<ExtensionHostDelegate>(new ShellExtensionHostDelegate);
+}
+
+bool ShellExtensionsBrowserClient::DidVersionUpdate(BrowserContext* context) {
+ // TODO(jamescook): We might want to tell extensions when app_shell updates.
+ return false;
+}
+
+void ShellExtensionsBrowserClient::PermitExternalProtocolHandler() {
+}
+
+bool ShellExtensionsBrowserClient::IsRunningInForcedAppMode() {
+ return false;
+}
+
+bool ShellExtensionsBrowserClient::IsLoggedInAsPublicAccount() {
+ return false;
+}
+
+ApiActivityMonitor* ShellExtensionsBrowserClient::GetApiActivityMonitor(
+ BrowserContext* context) {
+ // app_shell doesn't monitor API function calls or events.
+ return NULL;
+}
+
+ExtensionSystemProvider*
+ShellExtensionsBrowserClient::GetExtensionSystemFactory() {
+ return ShellExtensionSystemFactory::GetInstance();
+}
+
+void ShellExtensionsBrowserClient::RegisterExtensionFunctions(
+ ExtensionFunctionRegistry* registry) const {
+ // Register core extension-system APIs.
+ api::GeneratedFunctionRegistry::RegisterAll(registry);
+
+ // app_shell-only APIs.
+ shell::api::ShellGeneratedFunctionRegistry::RegisterAll(registry);
+}
+
+void ShellExtensionsBrowserClient::RegisterMojoServices(
+ content::RenderFrameHost* render_frame_host,
+ const Extension* extension) const {
+ RegisterServicesForFrame(render_frame_host, extension);
+}
+
+scoped_ptr<RuntimeAPIDelegate>
+ShellExtensionsBrowserClient::CreateRuntimeAPIDelegate(
+ content::BrowserContext* context) const {
+ return scoped_ptr<RuntimeAPIDelegate>(new ShellRuntimeAPIDelegate());
+}
+
+const ComponentExtensionResourceManager*
+ShellExtensionsBrowserClient::GetComponentExtensionResourceManager() {
+ return NULL;
+}
+
+void ShellExtensionsBrowserClient::BroadcastEventToRenderers(
+ events::HistogramValue histogram_value,
+ const std::string& event_name,
+ scoped_ptr<base::ListValue> args) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&ShellExtensionsBrowserClient::BroadcastEventToRenderers,
+ base::Unretained(this), histogram_value, event_name,
+ base::Passed(&args)));
+ return;
+ }
+
+ scoped_ptr<Event> event(
+ new Event(histogram_value, event_name, std::move(args)));
+ EventRouter::Get(browser_context_)->BroadcastEvent(std::move(event));
+}
+
+net::NetLog* ShellExtensionsBrowserClient::GetNetLog() {
+ return NULL;
+}
+
+ExtensionCache* ShellExtensionsBrowserClient::GetExtensionCache() {
+ return extension_cache_.get();
+}
+
+bool ShellExtensionsBrowserClient::IsBackgroundUpdateAllowed() {
+ return true;
+}
+
+bool ShellExtensionsBrowserClient::IsMinBrowserVersionSupported(
+ const std::string& min_version) {
+ return true;
+}
+
+void ShellExtensionsBrowserClient::SetAPIClientForTest(
+ ExtensionsAPIClient* api_client) {
+ api_client_.reset(api_client);
+}
+
+ExtensionWebContentsObserver*
+ShellExtensionsBrowserClient::GetExtensionWebContentsObserver(
+ content::WebContents* web_contents) {
+ return ShellExtensionWebContentsObserver::FromWebContents(web_contents);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_extensions_browser_client.h b/chromium/extensions/shell/browser/shell_extensions_browser_client.h
new file mode 100644
index 00000000000..da3e8be60df
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_extensions_browser_client.h
@@ -0,0 +1,115 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSIONS_BROWSER_CLIENT_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSIONS_BROWSER_CLIENT_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "extensions/browser/extensions_browser_client.h"
+
+class PrefService;
+
+namespace extensions {
+
+class ExtensionsAPIClient;
+
+// An ExtensionsBrowserClient that supports a single content::BrowserContent
+// with no related incognito context.
+class ShellExtensionsBrowserClient : public ExtensionsBrowserClient {
+ public:
+ // |context| is the single BrowserContext used for IsValidContext() below.
+ // |pref_service| is used for GetPrefServiceForContext() below.
+ ShellExtensionsBrowserClient(content::BrowserContext* context,
+ PrefService* pref_service);
+ ~ShellExtensionsBrowserClient() override;
+
+ // ExtensionsBrowserClient overrides:
+ bool IsShuttingDown() override;
+ bool AreExtensionsDisabled(const base::CommandLine& command_line,
+ content::BrowserContext* context) override;
+ bool IsValidContext(content::BrowserContext* context) override;
+ bool IsSameContext(content::BrowserContext* first,
+ content::BrowserContext* second) override;
+ bool HasOffTheRecordContext(content::BrowserContext* context) override;
+ content::BrowserContext* GetOffTheRecordContext(
+ content::BrowserContext* context) override;
+ content::BrowserContext* GetOriginalContext(
+ content::BrowserContext* context) override;
+#if defined(OS_CHROMEOS)
+ std::string GetUserIdHashFromContext(
+ content::BrowserContext* context) override;
+#endif
+ bool IsGuestSession(content::BrowserContext* context) const override;
+ bool IsExtensionIncognitoEnabled(
+ const std::string& extension_id,
+ content::BrowserContext* context) const override;
+ bool CanExtensionCrossIncognito(
+ const Extension* extension,
+ content::BrowserContext* context) const override;
+ net::URLRequestJob* MaybeCreateResourceBundleRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const base::FilePath& directory_path,
+ const std::string& content_security_policy,
+ bool send_cors_header) override;
+ bool AllowCrossRendererResourceLoad(net::URLRequest* request,
+ bool is_incognito,
+ const Extension* extension,
+ InfoMap* extension_info_map) override;
+ PrefService* GetPrefServiceForContext(
+ content::BrowserContext* context) override;
+ void GetEarlyExtensionPrefsObservers(
+ content::BrowserContext* context,
+ std::vector<ExtensionPrefsObserver*>* observers) const override;
+ ProcessManagerDelegate* GetProcessManagerDelegate() const override;
+ scoped_ptr<ExtensionHostDelegate> CreateExtensionHostDelegate() override;
+ bool DidVersionUpdate(content::BrowserContext* context) override;
+ void PermitExternalProtocolHandler() override;
+ bool IsRunningInForcedAppMode() override;
+ bool IsLoggedInAsPublicAccount() override;
+ ApiActivityMonitor* GetApiActivityMonitor(
+ content::BrowserContext* context) override;
+ ExtensionSystemProvider* GetExtensionSystemFactory() override;
+ void RegisterExtensionFunctions(
+ ExtensionFunctionRegistry* registry) const override;
+ void RegisterMojoServices(content::RenderFrameHost* render_frame_host,
+ const Extension* extension) const override;
+ scoped_ptr<RuntimeAPIDelegate> CreateRuntimeAPIDelegate(
+ content::BrowserContext* context) const override;
+ const ComponentExtensionResourceManager*
+ GetComponentExtensionResourceManager() override;
+ void BroadcastEventToRenderers(events::HistogramValue histogram_value,
+ const std::string& event_name,
+ scoped_ptr<base::ListValue> args) override;
+ net::NetLog* GetNetLog() override;
+ ExtensionCache* GetExtensionCache() override;
+ bool IsBackgroundUpdateAllowed() override;
+ bool IsMinBrowserVersionSupported(const std::string& min_version) override;
+ ExtensionWebContentsObserver* GetExtensionWebContentsObserver(
+ content::WebContents* web_contents) override;
+
+ // Sets the API client.
+ void SetAPIClientForTest(ExtensionsAPIClient* api_client);
+
+ private:
+ // The single BrowserContext for app_shell. Not owned.
+ content::BrowserContext* browser_context_;
+
+ // The PrefService for |browser_context_|. Not owned.
+ PrefService* pref_service_;
+
+ // Support for extension APIs.
+ scoped_ptr<ExtensionsAPIClient> api_client_;
+
+ // The extension cache used for download and installation.
+ scoped_ptr<ExtensionCache> extension_cache_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellExtensionsBrowserClient);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_EXTENSIONS_BROWSER_CLIENT_H_
diff --git a/chromium/extensions/shell/browser/shell_nacl_browser_delegate.cc b/chromium/extensions/shell/browser/shell_nacl_browser_delegate.cc
new file mode 100644
index 00000000000..88db0fd7407
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_nacl_browser_delegate.cc
@@ -0,0 +1,196 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_nacl_browser_delegate.h"
+
+#include <string>
+
+#include "base/base_paths.h"
+#include "base/command_line.h"
+#include "base/path_service.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "build/build_config.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/site_instance.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/info_map.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/url_pattern.h"
+#include "extensions/shell/common/version.h" // Generated file.
+#include "url/gurl.h"
+
+using content::BrowserContext;
+using content::BrowserThread;
+using content::BrowserPpapiHost;
+
+namespace extensions {
+namespace {
+
+// Handles an extension's NaCl process transitioning in or out of idle state by
+// relaying the state to the extension's process manager. See Chrome's
+// NaClBrowserDelegateImpl for another example.
+void OnKeepaliveOnUIThread(
+ const BrowserPpapiHost::OnKeepaliveInstanceData& instance_data,
+ const base::FilePath& profile_data_directory) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ // Only one instance will exist for NaCl embeds, even when more than one
+ // embed of the same plugin exists on the same page.
+ DCHECK(instance_data.size() == 1);
+ if (instance_data.size() < 1)
+ return;
+
+ ProcessManager::OnKeepaliveFromPlugin(instance_data[0].render_process_id,
+ instance_data[0].render_frame_id,
+ instance_data[0].document_url.host());
+}
+
+// Calls OnKeepaliveOnUIThread on UI thread.
+void OnKeepalive(const BrowserPpapiHost::OnKeepaliveInstanceData& instance_data,
+ const base::FilePath& profile_data_directory) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(
+ &OnKeepaliveOnUIThread, instance_data, profile_data_directory));
+}
+
+} // namespace
+
+ShellNaClBrowserDelegate::ShellNaClBrowserDelegate(BrowserContext* context)
+ : browser_context_(context) {
+ DCHECK(browser_context_);
+}
+
+ShellNaClBrowserDelegate::~ShellNaClBrowserDelegate() {
+}
+
+void ShellNaClBrowserDelegate::ShowMissingArchInfobar(int render_process_id,
+ int render_view_id) {
+ // app_shell does not have infobars.
+ LOG(ERROR) << "Missing architecture for pid " << render_process_id;
+}
+
+bool ShellNaClBrowserDelegate::DialogsAreSuppressed() {
+ return false;
+}
+
+bool ShellNaClBrowserDelegate::GetCacheDirectory(base::FilePath* cache_dir) {
+ // Just use the general cache directory, not a subdirectory like Chrome does.
+#if defined(OS_POSIX)
+ return PathService::Get(base::DIR_CACHE, cache_dir);
+#elif defined(OS_WIN)
+ // TODO(yoz): Find an appropriate persistent directory to use here.
+ return PathService::Get(base::DIR_TEMP, cache_dir);
+#endif
+}
+
+bool ShellNaClBrowserDelegate::GetPluginDirectory(base::FilePath* plugin_dir) {
+ // On Posix, plugins are in the module directory.
+ return PathService::Get(base::DIR_MODULE, plugin_dir);
+}
+
+bool ShellNaClBrowserDelegate::GetPnaclDirectory(base::FilePath* pnacl_dir) {
+ // On Posix, the pnacl directory is inside the plugin directory.
+ base::FilePath plugin_dir;
+ if (!GetPluginDirectory(&plugin_dir))
+ return false;
+ *pnacl_dir = plugin_dir.Append(FILE_PATH_LITERAL("pnacl"));
+ return true;
+}
+
+bool ShellNaClBrowserDelegate::GetUserDirectory(base::FilePath* user_dir) {
+ base::FilePath path = browser_context_->GetPath();
+ if (!path.empty()) {
+ *user_dir = path;
+ return true;
+ }
+ return false;
+}
+
+std::string ShellNaClBrowserDelegate::GetVersionString() const {
+ // A version change triggers an update of the NaCl validation caches.
+ // Example version: "39.0.2129.0 (290550)".
+ return PRODUCT_VERSION " (" LAST_CHANGE ")";
+}
+
+ppapi::host::HostFactory* ShellNaClBrowserDelegate::CreatePpapiHostFactory(
+ content::BrowserPpapiHost* ppapi_host) {
+ return NULL;
+}
+
+void ShellNaClBrowserDelegate::SetDebugPatterns(
+ const std::string& debug_patterns) {
+ // No debugger support. Developers should use Chrome for debugging.
+}
+
+bool ShellNaClBrowserDelegate::URLMatchesDebugPatterns(
+ const GURL& manifest_url) {
+ // No debugger support. Developers should use Chrome for debugging.
+ return false;
+}
+
+// This function is security sensitive. Be sure to check with a security
+// person before you modify it.
+// TODO(jamescook): Refactor this code into the extensions module so it can
+// be shared with Chrome's NaClBrowserDelegateImpl. http://crbug.com/403017
+bool ShellNaClBrowserDelegate::MapUrlToLocalFilePath(
+ const GURL& file_url,
+ bool use_blocking_api,
+ const base::FilePath& profile_directory,
+ base::FilePath* file_path) {
+ scoped_refptr<InfoMap> info_map =
+ ExtensionSystem::Get(browser_context_)->info_map();
+ // Check that the URL is recognized by the extension system.
+ const Extension* extension =
+ info_map->extensions().GetExtensionOrAppByURL(file_url);
+ if (!extension)
+ return false;
+
+ // This is a short-cut which avoids calling a blocking file operation
+ // (GetFilePath()), so that this can be called on the IO thread. It only
+ // handles a subset of the urls.
+ if (!use_blocking_api) {
+ if (file_url.SchemeIs(kExtensionScheme)) {
+ std::string path = file_url.path();
+ base::TrimString(path, "/", &path); // Remove first slash
+ *file_path = extension->path().AppendASCII(path);
+ return true;
+ }
+ return false;
+ }
+
+ // Check that the URL references a resource in the extension.
+ // NOTE: app_shell does not support shared modules.
+ ExtensionResource resource = extension->GetResource(file_url.path());
+ if (resource.empty())
+ return false;
+
+ // GetFilePath is a blocking function call.
+ const base::FilePath resource_file_path = resource.GetFilePath();
+ if (resource_file_path.empty())
+ return false;
+
+ *file_path = resource_file_path;
+ return true;
+}
+
+content::BrowserPpapiHost::OnKeepaliveCallback
+ShellNaClBrowserDelegate::GetOnKeepaliveCallback() {
+ return base::Bind(&OnKeepalive);
+}
+
+bool ShellNaClBrowserDelegate::IsNonSfiModeAllowed(
+ const base::FilePath& profile_directory,
+ const GURL& manifest_url) {
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_nacl_browser_delegate.h b/chromium/extensions/shell/browser/shell_nacl_browser_delegate.h
new file mode 100644
index 00000000000..a0f0a387224
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_nacl_browser_delegate.h
@@ -0,0 +1,57 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_NACL_BROWSER_DELEGATE_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_NACL_BROWSER_DELEGATE_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "components/nacl/browser/nacl_browser_delegate.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+class InfoMap;
+
+// A lightweight NaClBrowserDelegate for app_shell. Only supports a single
+// BrowserContext.
+class ShellNaClBrowserDelegate : public NaClBrowserDelegate {
+ public:
+ // Uses |context| to look up extensions via InfoMap on the IO thread.
+ explicit ShellNaClBrowserDelegate(content::BrowserContext* context);
+ ~ShellNaClBrowserDelegate() override;
+
+ // NaClBrowserDelegate overrides:
+ void ShowMissingArchInfobar(int render_process_id,
+ int render_view_id) override;
+ bool DialogsAreSuppressed() override;
+ bool GetCacheDirectory(base::FilePath* cache_dir) override;
+ bool GetPluginDirectory(base::FilePath* plugin_dir) override;
+ bool GetPnaclDirectory(base::FilePath* pnacl_dir) override;
+ bool GetUserDirectory(base::FilePath* user_dir) override;
+ std::string GetVersionString() const override;
+ ppapi::host::HostFactory* CreatePpapiHostFactory(
+ content::BrowserPpapiHost* ppapi_host) override;
+ bool MapUrlToLocalFilePath(const GURL& url,
+ bool is_blocking,
+ const base::FilePath& profile_directory,
+ base::FilePath* file_path) override;
+ void SetDebugPatterns(const std::string& debug_patterns) override;
+ bool URLMatchesDebugPatterns(const GURL& manifest_url) override;
+ content::BrowserPpapiHost::OnKeepaliveCallback GetOnKeepaliveCallback()
+ override;
+ bool IsNonSfiModeAllowed(const base::FilePath& profile_directory,
+ const GURL& manifest_url) override;
+
+ private:
+ content::BrowserContext* browser_context_; // Not owned.
+
+ DISALLOW_COPY_AND_ASSIGN(ShellNaClBrowserDelegate);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_NACL_BROWSER_DELEGATE_H_
diff --git a/chromium/extensions/shell/browser/shell_nacl_browser_delegate_unittest.cc b/chromium/extensions/shell/browser/shell_nacl_browser_delegate_unittest.cc
new file mode 100644
index 00000000000..0e1d17846cb
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_nacl_browser_delegate_unittest.cc
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_nacl_browser_delegate.h"
+
+#include "base/strings/pattern.h"
+#include "base/strings/string_util.h"
+#include "content/public/test/test_browser_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+typedef testing::Test ShellNaClBrowserDelegateTest;
+
+// Verifies that the returned version string has a valid format.
+TEST_F(ShellNaClBrowserDelegateTest, VersionString) {
+ content::TestBrowserContext browser_context;
+ ShellNaClBrowserDelegate delegate(&browser_context);
+
+ // Version should look like "1.2.3.4 (5)".
+ std::string version = delegate.GetVersionString();
+ EXPECT_TRUE(base::MatchPattern(version, "*.*.*.* (*)")) << "bad version "
+ << version;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_native_app_window.cc b/chromium/extensions/shell/browser/shell_native_app_window.cc
new file mode 100644
index 00000000000..83e76a7d4f8
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_native_app_window.cc
@@ -0,0 +1,198 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_native_app_window.h"
+
+#include "extensions/shell/browser/desktop_controller.h"
+#include "third_party/skia/include/core/SkRegion.h"
+#include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/geometry/point.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace extensions {
+
+ShellNativeAppWindow::ShellNativeAppWindow(
+ AppWindow* app_window,
+ const AppWindow::CreateParams& params)
+ : app_window_(app_window) {
+}
+
+ShellNativeAppWindow::~ShellNativeAppWindow() {
+}
+
+bool ShellNativeAppWindow::IsMaximized() const {
+ return false;
+}
+
+bool ShellNativeAppWindow::IsMinimized() const {
+ return false;
+}
+
+bool ShellNativeAppWindow::IsFullscreen() const {
+ // The window in app_shell is considered a "restored" window that happens to
+ // fill the display. This avoids special handling of fullscreen or maximized
+ // windows that app_shell doesn't need.
+ return false;
+}
+
+gfx::Rect ShellNativeAppWindow::GetRestoredBounds() const {
+ // app_shell windows cannot be maximized, so the current bounds are the
+ // restored bounds.
+ return GetBounds();
+}
+
+ui::WindowShowState ShellNativeAppWindow::GetRestoredState() const {
+ return ui::SHOW_STATE_NORMAL;
+}
+
+void ShellNativeAppWindow::ShowInactive() {
+ NOTIMPLEMENTED();
+}
+
+void ShellNativeAppWindow::Close() {
+ DesktopController::instance()->RemoveAppWindow(app_window_);
+ app_window_->OnNativeClose();
+}
+
+void ShellNativeAppWindow::Maximize() {
+ NOTIMPLEMENTED();
+}
+
+void ShellNativeAppWindow::Minimize() {
+ NOTIMPLEMENTED();
+}
+
+void ShellNativeAppWindow::Restore() {
+ NOTIMPLEMENTED();
+}
+
+void ShellNativeAppWindow::FlashFrame(bool flash) {
+ NOTIMPLEMENTED();
+}
+
+bool ShellNativeAppWindow::IsAlwaysOnTop() const {
+ return false;
+}
+
+void ShellNativeAppWindow::SetAlwaysOnTop(bool always_on_top) {
+ NOTIMPLEMENTED();
+}
+
+gfx::NativeView ShellNativeAppWindow::GetHostView() const {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+gfx::Point ShellNativeAppWindow::GetDialogPosition(const gfx::Size& size) {
+ NOTIMPLEMENTED();
+ return gfx::Point();
+}
+
+void ShellNativeAppWindow::AddObserver(
+ web_modal::ModalDialogHostObserver* observer) {
+ NOTIMPLEMENTED();
+}
+
+void ShellNativeAppWindow::RemoveObserver(
+ web_modal::ModalDialogHostObserver* observer) {
+ NOTIMPLEMENTED();
+}
+
+gfx::Size ShellNativeAppWindow::GetMaximumDialogSize() {
+ NOTIMPLEMENTED();
+ return gfx::Size();
+}
+
+void ShellNativeAppWindow::SetFullscreen(int fullscreen_types) {
+ NOTIMPLEMENTED();
+}
+
+bool ShellNativeAppWindow::IsFullscreenOrPending() const {
+ // See comment in IsFullscreen().
+ return false;
+}
+
+void ShellNativeAppWindow::UpdateWindowIcon() {
+ // No icon to update.
+}
+
+void ShellNativeAppWindow::UpdateWindowTitle() {
+ // No window title to update.
+}
+
+void ShellNativeAppWindow::UpdateDraggableRegions(
+ const std::vector<DraggableRegion>& regions) {
+ NOTIMPLEMENTED();
+}
+
+SkRegion* ShellNativeAppWindow::GetDraggableRegion() {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+void ShellNativeAppWindow::UpdateShape(scoped_ptr<SkRegion> region) {
+ NOTIMPLEMENTED();
+}
+
+void ShellNativeAppWindow::HandleKeyboardEvent(
+ const content::NativeWebKeyboardEvent& event) {
+ // No special handling. The WebContents will handle it.
+}
+
+bool ShellNativeAppWindow::IsFrameless() const {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+bool ShellNativeAppWindow::HasFrameColor() const {
+ return false;
+}
+
+SkColor ShellNativeAppWindow::ActiveFrameColor() const {
+ return SkColor();
+}
+
+SkColor ShellNativeAppWindow::InactiveFrameColor() const {
+ return SkColor();
+}
+
+gfx::Insets ShellNativeAppWindow::GetFrameInsets() const {
+ return gfx::Insets();
+}
+
+void ShellNativeAppWindow::ShowWithApp() {
+ NOTIMPLEMENTED();
+}
+
+void ShellNativeAppWindow::HideWithApp() {
+ NOTIMPLEMENTED();
+}
+
+gfx::Size ShellNativeAppWindow::GetContentMinimumSize() const {
+ // Content fills the desktop and cannot be resized.
+ return DesktopController::instance()->GetWindowSize();
+}
+
+gfx::Size ShellNativeAppWindow::GetContentMaximumSize() const {
+ // Content fills the desktop and cannot be resized.
+ return DesktopController::instance()->GetWindowSize();
+}
+
+void ShellNativeAppWindow::SetContentSizeConstraints(
+ const gfx::Size& min_size,
+ const gfx::Size& max_size) {
+ NOTIMPLEMENTED();
+}
+
+void ShellNativeAppWindow::SetVisibleOnAllWorkspaces(bool always_visible) {
+ NOTIMPLEMENTED();
+}
+
+bool ShellNativeAppWindow::CanHaveAlphaEnabled() const {
+ // No background to display if the window was transparent.
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_native_app_window.h b/chromium/extensions/shell/browser/shell_native_app_window.h
new file mode 100644
index 00000000000..775473cff92
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_native_app_window.h
@@ -0,0 +1,80 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_NATIVE_APP_WINDOW_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_NATIVE_APP_WINDOW_H_
+
+#include "base/macros.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/native_app_window.h"
+
+namespace extensions {
+
+// app_shell's NativeAppWindow implementation.
+class ShellNativeAppWindow : public NativeAppWindow {
+ public:
+ ShellNativeAppWindow(AppWindow* app_window,
+ const AppWindow::CreateParams& params);
+ ~ShellNativeAppWindow() override;
+
+ AppWindow* app_window() const { return app_window_; }
+
+ // ui::BaseView overrides:
+ bool IsMaximized() const override;
+ bool IsMinimized() const override;
+ bool IsFullscreen() const override;
+ gfx::Rect GetRestoredBounds() const override;
+ ui::WindowShowState GetRestoredState() const override;
+ void ShowInactive() override;
+ void Close() override;
+ void Maximize() override;
+ void Minimize() override;
+ void Restore() override;
+ void FlashFrame(bool flash) override;
+ bool IsAlwaysOnTop() const override;
+ void SetAlwaysOnTop(bool always_on_top) override;
+
+ // web_modal::ModalDialogHost overrides:
+ gfx::NativeView GetHostView() const override;
+ gfx::Point GetDialogPosition(const gfx::Size& size) override;
+ void AddObserver(web_modal::ModalDialogHostObserver* observer) override;
+ void RemoveObserver(web_modal::ModalDialogHostObserver* observer) override;
+
+ // web_modal::WebContentsModalDialogHost overrides:
+ gfx::Size GetMaximumDialogSize() override;
+
+ // NativeAppWindow overrides:
+ void SetFullscreen(int fullscreen_types) override;
+ bool IsFullscreenOrPending() const override;
+ void UpdateWindowIcon() override;
+ void UpdateWindowTitle() override;
+ void UpdateDraggableRegions(
+ const std::vector<DraggableRegion>& regions) override;
+ SkRegion* GetDraggableRegion() override;
+ void UpdateShape(scoped_ptr<SkRegion> region) override;
+ void HandleKeyboardEvent(
+ const content::NativeWebKeyboardEvent& event) override;
+ bool IsFrameless() const override;
+ bool HasFrameColor() const override;
+ SkColor ActiveFrameColor() const override;
+ SkColor InactiveFrameColor() const override;
+ gfx::Insets GetFrameInsets() const override;
+ void ShowWithApp() override;
+ void HideWithApp() override;
+ gfx::Size GetContentMinimumSize() const override;
+ gfx::Size GetContentMaximumSize() const override;
+ void SetContentSizeConstraints(const gfx::Size& min_size,
+ const gfx::Size& max_size) override;
+ void SetVisibleOnAllWorkspaces(bool always_visible) override;
+ bool CanHaveAlphaEnabled() const override;
+
+ private:
+ AppWindow* app_window_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellNativeAppWindow);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_NATIVE_APP_WINDOW_H_
diff --git a/chromium/extensions/shell/browser/shell_native_app_window_aura.cc b/chromium/extensions/shell/browser/shell_native_app_window_aura.cc
new file mode 100644
index 00000000000..c0158965535
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_native_app_window_aura.cc
@@ -0,0 +1,71 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_native_app_window_aura.h"
+
+#include "content/public/browser/web_contents.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_tree_host.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/wm/core/window_util.h"
+
+namespace extensions {
+
+ShellNativeAppWindowAura::ShellNativeAppWindowAura(
+ AppWindow* app_window,
+ const AppWindow::CreateParams& params)
+ : ShellNativeAppWindow(app_window, params) {
+ // TODO(yoz): We might have to duplicate this for mac.
+ gfx::Rect bounds = params.GetInitialWindowBounds(GetFrameInsets());
+ bool position_specified =
+ bounds.x() != AppWindow::BoundsSpecification::kUnspecifiedPosition &&
+ bounds.y() != AppWindow::BoundsSpecification::kUnspecifiedPosition;
+ if (!position_specified)
+ bounds.set_origin(GetBounds().origin());
+ SetBounds(bounds);
+}
+
+ShellNativeAppWindowAura::~ShellNativeAppWindowAura() {
+}
+
+bool ShellNativeAppWindowAura::IsActive() const {
+ // Even though app_shell only supports a single app window, there might be
+ // some sort of system-level dialog open and active.
+ aura::Window* window = GetNativeWindow();
+ return window && wm::IsActiveWindow(window);
+}
+
+gfx::NativeWindow ShellNativeAppWindowAura::GetNativeWindow() const {
+ return app_window()->web_contents()->GetNativeView();
+}
+
+gfx::Rect ShellNativeAppWindowAura::GetBounds() const {
+ return GetNativeWindow()->GetBoundsInScreen();
+}
+
+void ShellNativeAppWindowAura::Show() {
+ GetNativeWindow()->Show();
+}
+
+void ShellNativeAppWindowAura::Hide() {
+ GetNativeWindow()->Hide();
+}
+
+void ShellNativeAppWindowAura::Activate() {
+ aura::Window* window = GetNativeWindow();
+ if (window)
+ wm::ActivateWindow(window);
+}
+
+void ShellNativeAppWindowAura::Deactivate() {
+ aura::Window* window = GetNativeWindow();
+ if (window)
+ wm::DeactivateWindow(window);
+}
+
+void ShellNativeAppWindowAura::SetBounds(const gfx::Rect& bounds) {
+ GetNativeWindow()->SetBounds(bounds);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_native_app_window_aura.h b/chromium/extensions/shell/browser/shell_native_app_window_aura.h
new file mode 100644
index 00000000000..c566b0250b3
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_native_app_window_aura.h
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_NATIVE_APP_WINDOW_AURA_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_NATIVE_APP_WINDOW_AURA_H_
+
+#include "base/macros.h"
+#include "extensions/shell/browser/shell_native_app_window.h"
+
+namespace extensions {
+
+// The Aura-specific parts of the app_shell NativeAppWindow implementation.
+class ShellNativeAppWindowAura : public ShellNativeAppWindow {
+ public:
+ ShellNativeAppWindowAura(extensions::AppWindow* app_window,
+ const AppWindow::CreateParams& params);
+ ~ShellNativeAppWindowAura() override;
+
+ // ui::BaseWindow:
+ bool IsActive() const override;
+ gfx::NativeWindow GetNativeWindow() const override;
+ gfx::Rect GetBounds() const override;
+ void Show() override;
+ void Hide() override;
+ void Activate() override;
+ void Deactivate() override;
+ void SetBounds(const gfx::Rect& bounds) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShellNativeAppWindowAura);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_NATIVE_APP_WINDOW_AURA_H_
diff --git a/chromium/extensions/shell/browser/shell_native_app_window_aura_unittest.cc b/chromium/extensions/shell/browser/shell_native_app_window_aura_unittest.cc
new file mode 100644
index 00000000000..be7df4b8017
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_native_app_window_aura_unittest.cc
@@ -0,0 +1,75 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_native_app_window_aura.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/test_app_window_contents.h"
+#include "extensions/browser/extensions_test.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/shell/browser/desktop_controller.h"
+#include "extensions/shell/browser/shell_app_delegate.h"
+#include "extensions/shell/browser/shell_app_window_client.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace extensions {
+
+class ShellNativeAppWindowAuraTest : public ExtensionsTest {
+ public:
+ ShellNativeAppWindowAuraTest()
+ : notification_service_(content::NotificationService::Create()) {
+ AppWindowClient::Set(&app_window_client_);
+ }
+
+ ~ShellNativeAppWindowAuraTest() override {}
+
+ protected:
+ content::TestBrowserThreadBundle thread_bundle_;
+ scoped_ptr<content::NotificationService> notification_service_;
+ ShellAppWindowClient app_window_client_;
+};
+
+TEST_F(ShellNativeAppWindowAuraTest, Bounds) {
+ // The BrowserContext used here must be destroyed before the thread bundle,
+ // because of destructors of things spawned from creating a WebContents.
+ scoped_ptr<content::BrowserContext> browser_context(
+ new content::TestBrowserContext);
+ scoped_refptr<Extension> extension =
+ ExtensionBuilder()
+ .SetManifest(DictionaryBuilder()
+ .Set("name", "test extension")
+ .Set("version", "1")
+ .Set("manifest_version", 2)
+ .Build())
+ .Build();
+
+ AppWindow* app_window = new AppWindow(
+ browser_context.get(), new ShellAppDelegate, extension.get());
+ content::WebContents* web_contents = content::WebContents::Create(
+ content::WebContents::CreateParams(browser_context.get()));
+ app_window->SetAppWindowContentsForTesting(
+ make_scoped_ptr(new TestAppWindowContents(web_contents)));
+
+ AppWindow::BoundsSpecification window_spec;
+ window_spec.bounds = gfx::Rect(100, 200, 300, 400);
+ AppWindow::CreateParams params;
+ params.window_spec = window_spec;
+
+ ShellNativeAppWindowAura window(app_window, params);
+
+ gfx::Rect bounds = window.GetBounds();
+ EXPECT_EQ(window_spec.bounds, bounds);
+
+ // Delete the AppWindow.
+ app_window->OnNativeClose();
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_native_app_window_mac.h b/chromium/extensions/shell/browser/shell_native_app_window_mac.h
new file mode 100644
index 00000000000..23b0fe3605b
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_native_app_window_mac.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_NATIVE_APP_WINDOW_MAC_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_NATIVE_APP_WINDOW_MAC_H_
+
+#import <Cocoa/Cocoa.h>
+
+#import "base/mac/scoped_nsobject.h"
+#include "base/macros.h"
+#include "extensions/shell/browser/shell_native_app_window.h"
+
+@class ShellNSWindow;
+
+namespace extensions {
+class ShellNativeAppWindowMac;
+}
+
+// A window controller for ShellNativeAppWindowMac to handle NSNotifications
+// and pass them to the C++ implementation.
+@interface ShellNativeAppWindowController
+ : NSWindowController<NSWindowDelegate> {
+ @private
+ extensions::ShellNativeAppWindowMac* appWindow_; // Owns us.
+}
+
+@property(assign, nonatomic) extensions::ShellNativeAppWindowMac* appWindow;
+
+@end
+
+namespace extensions {
+
+// A minimal implementation of ShellNativeAppWindow for Mac Cocoa.
+// Based on the NativeAppWindowCocoa implementation.
+class ShellNativeAppWindowMac : public ShellNativeAppWindow {
+ public:
+ ShellNativeAppWindowMac(extensions::AppWindow* app_window,
+ const extensions::AppWindow::CreateParams& params);
+ ~ShellNativeAppWindowMac() override;
+
+ // ui::BaseWindow:
+ bool IsActive() const override;
+ gfx::NativeWindow GetNativeWindow() const override;
+ gfx::Rect GetBounds() const override;
+ void Show() override;
+ void Hide() override;
+ void Activate() override;
+ void Deactivate() override;
+ void SetBounds(const gfx::Rect& bounds) override;
+
+ // Called when the window is about to close.
+ void WindowWillClose();
+
+ private:
+ ShellNSWindow* window() const;
+
+ base::scoped_nsobject<ShellNativeAppWindowController> window_controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellNativeAppWindowMac);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_NATIVE_APP_WINDOW_MAC_H_
diff --git a/chromium/extensions/shell/browser/shell_native_app_window_mac.mm b/chromium/extensions/shell/browser/shell_native_app_window_mac.mm
new file mode 100644
index 00000000000..2b879e89147
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_native_app_window_mac.mm
@@ -0,0 +1,120 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "extensions/shell/browser/shell_native_app_window_mac.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/logging.h"
+#include "base/mac/foundation_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "content/public/browser/web_contents.h"
+#import "ui/gfx/mac/coordinate_conversion.h"
+
+@implementation ShellNativeAppWindowController
+
+@synthesize appWindow = appWindow_;
+
+- (void)windowWillClose:(NSNotification*)notification {
+ if (appWindow_)
+ appWindow_->WindowWillClose();
+}
+
+@end
+
+// TODO(yoz): Do we need to handle commands (keyboard shortcuts)?
+// Do we need need ChromeEventProcessingWindow or UnderlayOpenGLHostingWindow?
+@interface ShellNSWindow : NSWindow
+@end
+
+@implementation ShellNSWindow
+
+- (BOOL)_isTitleHidden {
+ return YES;
+}
+
+@end
+
+namespace extensions {
+
+ShellNativeAppWindowMac::ShellNativeAppWindowMac(
+ AppWindow* app_window,
+ const AppWindow::CreateParams& params)
+ : ShellNativeAppWindow(app_window, params) {
+ base::scoped_nsobject<NSWindow> shell_window;
+ NSUInteger style_mask = NSTitledWindowMask | NSClosableWindowMask;
+
+ NSRect cocoa_bounds = gfx::ScreenRectToNSRect(
+ params.GetInitialWindowBounds(gfx::Insets()));
+
+ shell_window.reset(
+ [[ShellNSWindow alloc] initWithContentRect:cocoa_bounds
+ styleMask:style_mask
+ backing:NSBackingStoreBuffered
+ defer:NO]);
+ [shell_window setReleasedWhenClosed:NO];
+
+ window_controller_.reset([[ShellNativeAppWindowController alloc]
+ initWithWindow:shell_window]);
+
+ [[window_controller_ window] setDelegate:window_controller_];
+ [window_controller_ setAppWindow:this];
+
+ NSView* view = app_window->web_contents()->GetNativeView();
+ NSView* frameView = [window() contentView];
+ [view setFrame:[frameView bounds]];
+ [frameView addSubview:view];
+}
+
+ShellNativeAppWindowMac::~ShellNativeAppWindowMac() {
+ [window() setDelegate:nil];
+ [window() close];
+}
+
+bool ShellNativeAppWindowMac::IsActive() const {
+ return [window() isKeyWindow];
+}
+
+gfx::NativeWindow ShellNativeAppWindowMac::GetNativeWindow() const {
+ return window();
+}
+
+gfx::Rect ShellNativeAppWindowMac::GetBounds() const {
+ return gfx::ScreenRectFromNSRect([window() frame]);
+}
+
+void ShellNativeAppWindowMac::Show() {
+ [window_controller_ showWindow:nil];
+}
+
+void ShellNativeAppWindowMac::Hide() {
+ NOTIMPLEMENTED();
+}
+
+void ShellNativeAppWindowMac::Activate() {
+ // TODO(yoz): Activate in front of other applications.
+ [[window_controller_ window] makeKeyAndOrderFront:window_controller_];
+}
+
+void ShellNativeAppWindowMac::Deactivate() {
+ // See crbug.com/51364.
+ NOTIMPLEMENTED();
+}
+
+void ShellNativeAppWindowMac::SetBounds(const gfx::Rect& bounds) {
+ // TODO(yoz): Windows should be fullscreen.
+ NOTIMPLEMENTED();
+}
+
+void ShellNativeAppWindowMac::WindowWillClose() {
+ [window_controller_ setAppWindow:NULL];
+ app_window()->OnNativeWindowChanged();
+ app_window()->OnNativeClose();
+}
+
+ShellNSWindow* ShellNativeAppWindowMac::window() const {
+ return base::mac::ObjCCastStrict<ShellNSWindow>([window_controller_ window]);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_network_controller_chromeos.cc b/chromium/extensions/shell/browser/shell_network_controller_chromeos.cc
new file mode 100644
index 00000000000..4ff45c70b62
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_network_controller_chromeos.cc
@@ -0,0 +1,215 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_network_controller_chromeos.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "chromeos/network/network_connection_handler.h"
+#include "chromeos/network/network_device_handler.h"
+#include "chromeos/network/network_handler.h"
+#include "chromeos/network/network_handler_callbacks.h"
+#include "chromeos/network/network_state.h"
+#include "chromeos/network/network_state_handler.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+namespace extensions {
+
+namespace {
+
+// Frequency at which networks should be scanned when not connected to a network
+// or when connected to a non-preferred network.
+const int kScanIntervalSec = 10;
+
+void HandleEnableWifiError(const std::string& error_name,
+ scoped_ptr<base::DictionaryValue> error_data) {
+ LOG(WARNING) << "Unable to enable wifi: " << error_name;
+}
+
+// Returns a human-readable name for the network described by |network|.
+std::string GetNetworkName(const chromeos::NetworkState& network) {
+ return !network.name().empty()
+ ? network.name()
+ : base::StringPrintf("[%s]", network.type().c_str());
+}
+
+// Returns true if shill is either connected or connecting to a network.
+bool IsConnectedOrConnecting() {
+ chromeos::NetworkStateHandler* state_handler =
+ chromeos::NetworkHandler::Get()->network_state_handler();
+ return state_handler->ConnectedNetworkByType(
+ chromeos::NetworkTypePattern::Default()) ||
+ state_handler->ConnectingNetworkByType(
+ chromeos::NetworkTypePattern::Default());
+}
+
+} // namespace
+
+ShellNetworkController::ShellNetworkController(
+ const std::string& preferred_network_name)
+ : state_(STATE_IDLE),
+ preferred_network_name_(preferred_network_name),
+ preferred_network_is_active_(false),
+ weak_ptr_factory_(this) {
+ chromeos::NetworkStateHandler* state_handler =
+ chromeos::NetworkHandler::Get()->network_state_handler();
+ state_handler->AddObserver(this, FROM_HERE);
+ state_handler->SetTechnologyEnabled(
+ chromeos::NetworkTypePattern::Primitive(shill::kTypeWifi),
+ true,
+ base::Bind(&HandleEnableWifiError));
+
+ // If we're unconnected, trigger a connection attempt and start scanning.
+ NetworkConnectionStateChanged(NULL);
+}
+
+ShellNetworkController::~ShellNetworkController() {
+ chromeos::NetworkHandler::Get()->network_state_handler()->RemoveObserver(
+ this, FROM_HERE);
+}
+
+void ShellNetworkController::NetworkListChanged() {
+ VLOG(1) << "Network list changed";
+ ConnectIfUnconnected();
+}
+
+void ShellNetworkController::NetworkConnectionStateChanged(
+ const chromeos::NetworkState* network) {
+ if (network) {
+ VLOG(1) << "Network connection state changed:"
+ << " name=" << GetNetworkName(*network)
+ << " type=" << network->type() << " path=" << network->path()
+ << " state=" << network->connection_state();
+ } else {
+ VLOG(1) << "Network connection state changed: [none]";
+ }
+
+ const chromeos::NetworkState* wifi_network = GetActiveWiFiNetwork();
+ preferred_network_is_active_ =
+ wifi_network && wifi_network->name() == preferred_network_name_;
+ VLOG(2) << "Active WiFi network is "
+ << (wifi_network ? wifi_network->name() : std::string("[none]"));
+
+ if (preferred_network_is_active_ ||
+ (preferred_network_name_.empty() && wifi_network)) {
+ SetScanningEnabled(false);
+ } else {
+ SetScanningEnabled(true);
+ ConnectIfUnconnected();
+ }
+}
+
+void ShellNetworkController::SetCellularAllowRoaming(bool allow_roaming) {
+ chromeos::NetworkDeviceHandler* device_handler =
+ chromeos::NetworkHandler::Get()->network_device_handler();
+ device_handler->SetCellularAllowRoaming(allow_roaming);
+}
+
+const chromeos::NetworkState* ShellNetworkController::GetActiveWiFiNetwork() {
+ chromeos::NetworkStateHandler* state_handler =
+ chromeos::NetworkHandler::Get()->network_state_handler();
+ const chromeos::NetworkState* network = state_handler->FirstNetworkByType(
+ chromeos::NetworkTypePattern::Primitive(shill::kTypeWifi));
+ return network &&
+ (network->IsConnectedState() || network->IsConnectingState())
+ ? network
+ : NULL;
+}
+
+void ShellNetworkController::SetScanningEnabled(bool enabled) {
+ const bool currently_enabled = scan_timer_.IsRunning();
+ if (enabled == currently_enabled)
+ return;
+
+ VLOG(1) << (enabled ? "Starting" : "Stopping") << " scanning";
+ if (enabled) {
+ RequestScan();
+ scan_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromSeconds(kScanIntervalSec),
+ this,
+ &ShellNetworkController::RequestScan);
+ } else {
+ scan_timer_.Stop();
+ }
+}
+
+void ShellNetworkController::RequestScan() {
+ VLOG(1) << "Requesting scan";
+ chromeos::NetworkHandler::Get()->network_state_handler()->RequestScan();
+}
+
+void ShellNetworkController::ConnectIfUnconnected() {
+ // Don't do anything if the default network is already the preferred one or if
+ // we have a pending request to connect to it.
+ if (preferred_network_is_active_ ||
+ state_ == STATE_WAITING_FOR_PREFERRED_RESULT)
+ return;
+
+ const chromeos::NetworkState* best_network = NULL;
+ bool can_connect_to_preferred_network = false;
+
+ chromeos::NetworkHandler* handler = chromeos::NetworkHandler::Get();
+ chromeos::NetworkStateHandler::NetworkStateList network_list;
+ handler->network_state_handler()->GetVisibleNetworkListByType(
+ chromeos::NetworkTypePattern::WiFi(), &network_list);
+ for (chromeos::NetworkStateHandler::NetworkStateList::const_iterator it =
+ network_list.begin();
+ it != network_list.end();
+ ++it) {
+ const chromeos::NetworkState* network = *it;
+ if (!network->connectable())
+ continue;
+
+ if (!preferred_network_name_.empty() &&
+ network->name() == preferred_network_name_) {
+ best_network = network;
+ can_connect_to_preferred_network = true;
+ break;
+ } else if (!best_network) {
+ best_network = network;
+ }
+ }
+
+ // Don't switch networks if we're already connecting/connected and wouldn't be
+ // switching to the preferred network.
+ if ((IsConnectedOrConnecting() || state_ != STATE_IDLE) &&
+ !can_connect_to_preferred_network)
+ return;
+
+ if (!best_network) {
+ VLOG(1) << "Didn't find any connectable networks";
+ return;
+ }
+
+ VLOG(1) << "Connecting to network " << GetNetworkName(*best_network)
+ << " with path " << best_network->path() << " and strength "
+ << best_network->signal_strength();
+ state_ = can_connect_to_preferred_network
+ ? STATE_WAITING_FOR_PREFERRED_RESULT
+ : STATE_WAITING_FOR_NON_PREFERRED_RESULT;
+ handler->network_connection_handler()->ConnectToNetwork(
+ best_network->path(),
+ base::Bind(&ShellNetworkController::HandleConnectionSuccess,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::Bind(&ShellNetworkController::HandleConnectionError,
+ weak_ptr_factory_.GetWeakPtr()),
+ false /* check_error_state */);
+}
+
+void ShellNetworkController::HandleConnectionSuccess() {
+ VLOG(1) << "Successfully connected to network";
+ state_ = STATE_IDLE;
+}
+
+void ShellNetworkController::HandleConnectionError(
+ const std::string& error_name,
+ scoped_ptr<base::DictionaryValue> error_data) {
+ LOG(WARNING) << "Unable to connect to network: " << error_name;
+ state_ = STATE_IDLE;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_network_controller_chromeos.h b/chromium/extensions/shell/browser/shell_network_controller_chromeos.h
new file mode 100644
index 00000000000..36b73d77e29
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_network_controller_chromeos.h
@@ -0,0 +1,89 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_NETWORK_CONTROLLER_CHROMEOS_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_NETWORK_CONTROLLER_CHROMEOS_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
+#include "base/values.h"
+#include "chromeos/network/network_state_handler_observer.h"
+
+namespace extensions {
+
+// Handles network-related tasks for app_shell on Chrome OS.
+class ShellNetworkController : public chromeos::NetworkStateHandlerObserver {
+ public:
+ // This class must be instantiated after chromeos::DBusThreadManager and
+ // destroyed before it.
+ explicit ShellNetworkController(const std::string& preferred_network_name);
+ ~ShellNetworkController() override;
+
+ // chromeos::NetworkStateHandlerObserver overrides:
+ void NetworkListChanged() override;
+ void NetworkConnectionStateChanged(
+ const chromeos::NetworkState* state) override;
+
+ // Control whether the cellular network connection allows roaming.
+ void SetCellularAllowRoaming(bool allow_roaming);
+
+ private:
+ // State of communication with the connection manager.
+ enum State {
+ // No in-progress requests.
+ STATE_IDLE = 0,
+ // Waiting for the result of an attempt to connect to the preferred network.
+ STATE_WAITING_FOR_PREFERRED_RESULT,
+ // Waiting for the result of an attempt to connect to a non-preferred
+ // network.
+ STATE_WAITING_FOR_NON_PREFERRED_RESULT,
+ };
+
+ // Returns the connected or connecting WiFi network or NULL if no network
+ // matches that description.
+ const chromeos::NetworkState* GetActiveWiFiNetwork();
+
+ // Controls whether scanning is performed periodically.
+ void SetScanningEnabled(bool enabled);
+
+ // Asks the connection manager to scan for networks.
+ void RequestScan();
+
+ // If not currently connected or connecting, chooses a wireless network and
+ // asks the connection manager to connect to it. Also switches to
+ // |preferred_network_name_| if possible.
+ void ConnectIfUnconnected();
+
+ // Handles a successful or failed connection attempt.
+ void HandleConnectionSuccess();
+ void HandleConnectionError(const std::string& error_name,
+ scoped_ptr<base::DictionaryValue> error_data);
+
+ // Current status of communication with the chromeos::NetworkStateHandler.
+ // This is tracked to avoid sending duplicate requests before the handler has
+ // acknowledged the initial connection attempt.
+ State state_;
+
+ // Invokes RequestScan() periodically.
+ base::RepeatingTimer scan_timer_;
+
+ // Optionally-supplied name of the preferred network.
+ std::string preferred_network_name_;
+
+ // True if the preferred network is connected or connecting.
+ bool preferred_network_is_active_;
+
+ base::WeakPtrFactory<ShellNetworkController> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellNetworkController);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_NETWORK_CONTROLLER_CHROMEOS_H_
diff --git a/chromium/extensions/shell/browser/shell_network_delegate.cc b/chromium/extensions/shell/browser/shell_network_delegate.cc
new file mode 100644
index 00000000000..b148bc3d68b
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_network_delegate.cc
@@ -0,0 +1,132 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_network_delegate.h"
+
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/resource_request_info.h"
+#include "extensions/browser/api/web_request/web_request_api.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/process_manager.h"
+#include "net/url_request/url_request.h"
+
+namespace extensions {
+
+namespace {
+bool g_accept_all_cookies = true;
+}
+
+ShellNetworkDelegate::ShellNetworkDelegate(
+ void* browser_context, InfoMap* extension_info_map) {
+ browser_context_ = browser_context;
+ extension_info_map_ = extension_info_map;
+}
+
+ShellNetworkDelegate::~ShellNetworkDelegate() {}
+
+void ShellNetworkDelegate::SetAcceptAllCookies(bool accept) {
+ g_accept_all_cookies = accept;
+}
+
+int ShellNetworkDelegate::OnBeforeURLRequest(
+ net::URLRequest* request,
+ const net::CompletionCallback& callback,
+ GURL* new_url) {
+ return ExtensionWebRequestEventRouter::GetInstance()->OnBeforeRequest(
+ browser_context_, extension_info_map_.get(), request, callback, new_url);
+}
+
+int ShellNetworkDelegate::OnBeforeSendHeaders(
+ net::URLRequest* request,
+ const net::CompletionCallback& callback,
+ net::HttpRequestHeaders* headers) {
+ return ExtensionWebRequestEventRouter::GetInstance()->OnBeforeSendHeaders(
+ browser_context_, extension_info_map_.get(), request, callback, headers);
+}
+
+void ShellNetworkDelegate::OnSendHeaders(
+ net::URLRequest* request,
+ const net::HttpRequestHeaders& headers) {
+ ExtensionWebRequestEventRouter::GetInstance()->OnSendHeaders(
+ browser_context_, extension_info_map_.get(), request, headers);
+}
+
+int ShellNetworkDelegate::OnHeadersReceived(
+ net::URLRequest* request,
+ const net::CompletionCallback& callback,
+ const net::HttpResponseHeaders* original_response_headers,
+ scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
+ GURL* allowed_unsafe_redirect_url) {
+ return ExtensionWebRequestEventRouter::GetInstance()->OnHeadersReceived(
+ browser_context_,
+ extension_info_map_.get(),
+ request,
+ callback,
+ original_response_headers,
+ override_response_headers,
+ allowed_unsafe_redirect_url);
+}
+
+void ShellNetworkDelegate::OnBeforeRedirect(
+ net::URLRequest* request,
+ const GURL& new_location) {
+ ExtensionWebRequestEventRouter::GetInstance()->OnBeforeRedirect(
+ browser_context_, extension_info_map_.get(), request, new_location);
+}
+
+
+void ShellNetworkDelegate::OnResponseStarted(
+ net::URLRequest* request) {
+ ExtensionWebRequestEventRouter::GetInstance()->OnResponseStarted(
+ browser_context_, extension_info_map_.get(), request);
+}
+
+void ShellNetworkDelegate::OnCompleted(
+ net::URLRequest* request,
+ bool started) {
+ if (request->status().status() == net::URLRequestStatus::SUCCESS) {
+ bool is_redirect = request->response_headers() &&
+ net::HttpResponseHeaders::IsRedirectResponseCode(
+ request->response_headers()->response_code());
+ if (!is_redirect) {
+ ExtensionWebRequestEventRouter::GetInstance()->OnCompleted(
+ browser_context_, extension_info_map_.get(), request);
+ }
+ return;
+ }
+
+ if (request->status().status() == net::URLRequestStatus::FAILED ||
+ request->status().status() == net::URLRequestStatus::CANCELED) {
+ ExtensionWebRequestEventRouter::GetInstance()->OnErrorOccurred(
+ browser_context_, extension_info_map_.get(), request, started);
+ return;
+ }
+
+ NOTREACHED();
+}
+
+void ShellNetworkDelegate::OnURLRequestDestroyed(
+ net::URLRequest* request) {
+ ExtensionWebRequestEventRouter::GetInstance()->OnURLRequestDestroyed(
+ browser_context_, request);
+}
+
+void ShellNetworkDelegate::OnPACScriptError(
+ int line_number,
+ const base::string16& error) {
+}
+
+net::NetworkDelegate::AuthRequiredResponse
+ShellNetworkDelegate::OnAuthRequired(
+ net::URLRequest* request,
+ const net::AuthChallengeInfo& auth_info,
+ const AuthCallback& callback,
+ net::AuthCredentials* credentials) {
+ return ExtensionWebRequestEventRouter::GetInstance()->OnAuthRequired(
+ browser_context_, extension_info_map_.get(), request, auth_info, callback,
+ credentials);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_network_delegate.h b/chromium/extensions/shell/browser/shell_network_delegate.h
new file mode 100644
index 00000000000..8dc76fa0799
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_network_delegate.h
@@ -0,0 +1,59 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_NETNETWORK_DELEGATE_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_NETNETWORK_DELEGATE_H_
+
+#include "base/macros.h"
+#include "extensions/browser/info_map.h"
+#include "net/base/network_delegate_impl.h"
+
+namespace extensions {
+
+class InfoMap;
+
+class ShellNetworkDelegate : public net::NetworkDelegateImpl {
+ public:
+ ShellNetworkDelegate(void* browser_context, InfoMap* extension_info_map);
+ ~ShellNetworkDelegate() override;
+
+ static void SetAcceptAllCookies(bool accept);
+
+ private:
+ // NetworkDelegate implementation.
+ int OnBeforeURLRequest(net::URLRequest* request,
+ const net::CompletionCallback& callback,
+ GURL* new_url) override;
+ int OnBeforeSendHeaders(net::URLRequest* request,
+ const net::CompletionCallback& callback,
+ net::HttpRequestHeaders* headers) override;
+ void OnSendHeaders(net::URLRequest* request,
+ const net::HttpRequestHeaders& headers) override;
+ int OnHeadersReceived(
+ net::URLRequest* request,
+ const net::CompletionCallback& callback,
+ const net::HttpResponseHeaders* original_response_headers,
+ scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
+ GURL* allowed_unsafe_redirect_url) override;
+ void OnBeforeRedirect(net::URLRequest* request,
+ const GURL& new_location) override;
+ void OnResponseStarted(net::URLRequest* request) override;
+ void OnCompleted(net::URLRequest* request, bool started) override;
+ void OnURLRequestDestroyed(net::URLRequest* request) override;
+ void OnPACScriptError(int line_number, const base::string16& error) override;
+ net::NetworkDelegate::AuthRequiredResponse OnAuthRequired(
+ net::URLRequest* request,
+ const net::AuthChallengeInfo& auth_info,
+ const AuthCallback& callback,
+ net::AuthCredentials* credentials) override;
+
+ void* browser_context_;
+ scoped_refptr<extensions::InfoMap> extension_info_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellNetworkDelegate);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_NETNETWORK_DELEGATE_H_
diff --git a/chromium/extensions/shell/browser/shell_oauth2_token_service.cc b/chromium/extensions/shell/browser/shell_oauth2_token_service.cc
new file mode 100644
index 00000000000..b2a7c4ccebe
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_oauth2_token_service.cc
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_oauth2_token_service.h"
+
+#include "base/logging.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/shell/browser/shell_oauth2_token_service_delegate.h"
+
+namespace extensions {
+namespace {
+
+ShellOAuth2TokenService* g_instance = nullptr;
+
+} // namespace
+
+ShellOAuth2TokenService::ShellOAuth2TokenService(
+ content::BrowserContext* browser_context,
+ std::string account_id,
+ std::string refresh_token)
+ : OAuth2TokenService(new ShellOAuth2TokenServiceDelegate(browser_context,
+ account_id,
+ refresh_token)) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK(!g_instance);
+ g_instance = this;
+}
+
+ShellOAuth2TokenService::~ShellOAuth2TokenService() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK(g_instance);
+ g_instance = nullptr;
+}
+
+// static
+ShellOAuth2TokenService* ShellOAuth2TokenService::GetInstance() {
+ DCHECK(g_instance);
+ return g_instance;
+}
+
+void ShellOAuth2TokenService::SetRefreshToken(
+ const std::string& account_id,
+ const std::string& refresh_token) {
+ GetDelegate()->UpdateCredentials(account_id, refresh_token);
+}
+
+std::string ShellOAuth2TokenService::AccountId() const {
+ return GetAccounts()[0];
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_oauth2_token_service.h b/chromium/extensions/shell/browser/shell_oauth2_token_service.h
new file mode 100644
index 00000000000..c93c9f20992
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_oauth2_token_service.h
@@ -0,0 +1,43 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_OAUTH2_TOKEN_SERVICE_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_OAUTH2_TOKEN_SERVICE_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "google_apis/gaia/oauth2_token_service.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+// Requests OAuth2 access tokens for app_shell. Requires the OAuth2 refresh
+// token to be set explicitly. Only stores a single refresh token for a single
+// user account. Created and accessed on the UI thread. Only one instance is
+// allowed.
+class ShellOAuth2TokenService : public OAuth2TokenService {
+ public:
+ ShellOAuth2TokenService(content::BrowserContext* browser_context,
+ std::string account_id,
+ std::string refresh_token);
+ ~ShellOAuth2TokenService() override;
+
+ // Returns the single instance for app_shell.
+ static ShellOAuth2TokenService* GetInstance();
+
+ void SetRefreshToken(const std::string& account_id,
+ const std::string& refresh_token);
+
+ std::string AccountId() const;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellOAuth2TokenService);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_OAUTH2_TOKEN_SERVICE_H_
diff --git a/chromium/extensions/shell/browser/shell_oauth2_token_service_delegate.cc b/chromium/extensions/shell/browser/shell_oauth2_token_service_delegate.cc
new file mode 100644
index 00000000000..89eb2120447
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_oauth2_token_service_delegate.cc
@@ -0,0 +1,59 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_oauth2_token_service_delegate.h"
+
+#include <vector>
+
+namespace extensions {
+
+ShellOAuth2TokenServiceDelegate::ShellOAuth2TokenServiceDelegate(
+ content::BrowserContext* browser_context,
+ std::string account_id,
+ std::string refresh_token)
+ : browser_context_(browser_context),
+ account_id_(account_id),
+ refresh_token_(refresh_token) {
+}
+
+ShellOAuth2TokenServiceDelegate::~ShellOAuth2TokenServiceDelegate() {
+}
+
+bool ShellOAuth2TokenServiceDelegate::RefreshTokenIsAvailable(
+ const std::string& account_id) const {
+ if (account_id != account_id_)
+ return false;
+
+ return !refresh_token_.empty();
+}
+
+OAuth2AccessTokenFetcher*
+ShellOAuth2TokenServiceDelegate::CreateAccessTokenFetcher(
+ const std::string& account_id,
+ net::URLRequestContextGetter* getter,
+ OAuth2AccessTokenConsumer* consumer) {
+ DCHECK_EQ(account_id, account_id_);
+ DCHECK(!refresh_token_.empty());
+ return new OAuth2AccessTokenFetcherImpl(consumer, getter, refresh_token_);
+}
+
+net::URLRequestContextGetter*
+ShellOAuth2TokenServiceDelegate::GetRequestContext() const {
+ return browser_context_->GetRequestContext();
+}
+
+std::vector<std::string> ShellOAuth2TokenServiceDelegate::GetAccounts() {
+ std::vector<std::string> accounts;
+ accounts.push_back(account_id_);
+ return accounts;
+}
+
+void ShellOAuth2TokenServiceDelegate::UpdateCredentials(
+ const std::string& account_id,
+ const std::string& refresh_token) {
+ account_id_ = account_id;
+ refresh_token_ = refresh_token;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_oauth2_token_service_delegate.h b/chromium/extensions/shell/browser/shell_oauth2_token_service_delegate.h
new file mode 100644
index 00000000000..2537092c827
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_oauth2_token_service_delegate.h
@@ -0,0 +1,55 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_OAUTH2_TOKEN_SERVICE_DELEGATE_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_OAUTH2_TOKEN_SERVICE_DELEGATE_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "content/public/browser/browser_context.h"
+#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
+#include "google_apis/gaia/oauth2_token_service_delegate.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class ShellOAuth2TokenServiceDelegate : public OAuth2TokenServiceDelegate {
+ public:
+ ShellOAuth2TokenServiceDelegate(content::BrowserContext* browser_context,
+ std::string account_id,
+ std::string refresh_token);
+ ~ShellOAuth2TokenServiceDelegate() override;
+
+ bool RefreshTokenIsAvailable(const std::string& account_id) const override;
+
+ OAuth2AccessTokenFetcher* CreateAccessTokenFetcher(
+ const std::string& account_id,
+ net::URLRequestContextGetter* getter,
+ OAuth2AccessTokenConsumer* consumer) override;
+ net::URLRequestContextGetter* GetRequestContext() const override;
+
+ std::vector<std::string> GetAccounts() override;
+
+ void UpdateCredentials(const std::string& account_id,
+ const std::string& refresh_token) override;
+
+ private:
+ // Not owned.
+ content::BrowserContext* browser_context_;
+
+ // User account id, such as "foo@gmail.com".
+ std::string account_id_;
+
+ // Cached copy of an OAuth2 refresh token. Not stored on disk.
+ std::string refresh_token_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellOAuth2TokenServiceDelegate);
+};
+
+} // namespace extensions
+#endif
diff --git a/chromium/extensions/shell/browser/shell_oauth2_token_service_unittest.cc b/chromium/extensions/shell/browser/shell_oauth2_token_service_unittest.cc
new file mode 100644
index 00000000000..649fd3abd54
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_oauth2_token_service_unittest.cc
@@ -0,0 +1,38 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_oauth2_token_service.h"
+
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "extensions/browser/extensions_test.h"
+
+namespace extensions {
+
+class ShellOAuth2TokenServiceTest : public ExtensionsTest {
+ public:
+ ShellOAuth2TokenServiceTest() {}
+ ~ShellOAuth2TokenServiceTest() override {}
+
+ private:
+ content::TestBrowserThreadBundle thread_bundle_;
+};
+
+// Verifies setting the refresh token makes it available.
+TEST_F(ShellOAuth2TokenServiceTest, SetRefreshToken) {
+ ShellOAuth2TokenService service(nullptr, "larry@google.com", "token123");
+
+ // Only has a token for the account in the constructor.
+ EXPECT_EQ("larry@google.com", service.AccountId());
+ EXPECT_TRUE(service.RefreshTokenIsAvailable("larry@google.com"));
+ EXPECT_FALSE(service.RefreshTokenIsAvailable("sergey@google.com"));
+
+ service.SetRefreshToken("sergey@google.com", "token456");
+
+ // The account and refresh token have updated.
+ EXPECT_EQ("sergey@google.com", service.AccountId());
+ EXPECT_FALSE(service.RefreshTokenIsAvailable("larry@google.com"));
+ EXPECT_TRUE(service.RefreshTokenIsAvailable("sergey@google.com"));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_prefs.cc b/chromium/extensions/shell/browser/shell_prefs.cc
new file mode 100644
index 00000000000..82185cfcbd6
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_prefs.cc
@@ -0,0 +1,91 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_prefs.h"
+
+#include "build/build_config.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/json_pref_store.h"
+#include "components/prefs/pref_filter.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/pref_service_factory.h"
+#include "components/user_prefs/user_prefs.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/extension_prefs.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/audio/audio_devices_pref_handler_impl.h"
+#endif
+
+using base::FilePath;
+using user_prefs::PrefRegistrySyncable;
+
+namespace extensions {
+namespace {
+
+void RegisterLocalStatePrefs(PrefRegistrySimple* registry) {
+#if defined(OS_CHROMEOS)
+ chromeos::AudioDevicesPrefHandlerImpl::RegisterPrefs(registry);
+#endif
+}
+
+// Creates a JsonPrefStore from a file at |filepath| and synchronously loads
+// the preferences.
+scoped_refptr<JsonPrefStore> CreateAndLoadPrefStore(const FilePath& filepath) {
+ scoped_refptr<base::SequencedTaskRunner> task_runner =
+ JsonPrefStore::GetTaskRunnerForFile(
+ filepath, content::BrowserThread::GetBlockingPool());
+ scoped_refptr<JsonPrefStore> pref_store =
+ new JsonPrefStore(filepath, task_runner, scoped_ptr<PrefFilter>());
+ pref_store->ReadPrefs(); // Synchronous.
+ return pref_store;
+}
+
+} // namespace
+
+namespace shell_prefs {
+
+scoped_ptr<PrefService> CreateLocalState(const FilePath& data_dir) {
+ FilePath filepath = data_dir.AppendASCII("local_state.json");
+ scoped_refptr<JsonPrefStore> pref_store = CreateAndLoadPrefStore(filepath);
+
+ // Local state is considered "user prefs" from the factory's perspective.
+ PrefServiceFactory factory;
+ factory.set_user_prefs(pref_store);
+
+ // Local state preferences are not syncable.
+ PrefRegistrySimple* registry = new PrefRegistrySimple;
+ RegisterLocalStatePrefs(registry);
+
+ return factory.Create(registry);
+}
+
+scoped_ptr<PrefService> CreateUserPrefService(
+ content::BrowserContext* browser_context) {
+ FilePath filepath = browser_context->GetPath().AppendASCII("user_prefs.json");
+ scoped_refptr<JsonPrefStore> pref_store = CreateAndLoadPrefStore(filepath);
+
+ PrefServiceFactory factory;
+ factory.set_user_prefs(pref_store);
+
+ // TODO(jamescook): If we want to support prefs that are set by extensions
+ // via ChromeSettings properties (e.g. chrome.accessibilityFeatures or
+ // chrome.proxy) then this should create an ExtensionPrefStore and attach it
+ // with PrefServiceFactory::set_extension_prefs().
+ // See https://developer.chrome.com/extensions/types#ChromeSetting
+
+ // Prefs should be registered before the PrefService is created.
+ PrefRegistrySyncable* pref_registry = new PrefRegistrySyncable;
+ ExtensionPrefs::RegisterProfilePrefs(pref_registry);
+
+ scoped_ptr<PrefService> pref_service = factory.Create(pref_registry);
+ user_prefs::UserPrefs::Set(browser_context, pref_service.get());
+ return pref_service;
+}
+
+} // namespace shell_prefs
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_prefs.h b/chromium/extensions/shell/browser/shell_prefs.h
new file mode 100644
index 00000000000..1e94c0e6bad
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_prefs.h
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_PREFS_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_PREFS_H_
+
+#include "base/memory/scoped_ptr.h"
+
+class PrefService;
+
+namespace base {
+class FilePath;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+// Support for preference initialization and management.
+namespace shell_prefs {
+
+// Creates a pref service for device-wide preferences stored in |data_dir|.
+scoped_ptr<PrefService> CreateLocalState(const base::FilePath& data_dir);
+
+// Creates a pref service that loads user preferences for |browser_context|.
+scoped_ptr<PrefService> CreateUserPrefService(
+ content::BrowserContext* browser_context);
+
+} // namespace shell_prefs
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_PREFS_H_
diff --git a/chromium/extensions/shell/browser/shell_prefs_unittest.cc b/chromium/extensions/shell/browser/shell_prefs_unittest.cc
new file mode 100644
index 00000000000..3f91f171a50
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_prefs_unittest.cc
@@ -0,0 +1,80 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_prefs.h"
+
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "build/build_config.h"
+#include "components/prefs/pref_service.h"
+#include "components/user_prefs/user_prefs.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "extensions/common/extension_paths.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+namespace {
+
+// A BrowserContext that uses a test data directory as its data path.
+class PrefsTestBrowserContext : public content::TestBrowserContext {
+ public:
+ PrefsTestBrowserContext() {}
+ ~PrefsTestBrowserContext() override {}
+
+ // content::BrowserContext:
+ base::FilePath GetPath() const override {
+ base::FilePath path;
+ PathService::Get(extensions::DIR_TEST_DATA, &path);
+ return path.AppendASCII("shell_prefs");
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PrefsTestBrowserContext);
+};
+
+class ShellPrefsTest : public testing::Test {
+ public:
+ ShellPrefsTest() {}
+ ~ShellPrefsTest() override {}
+
+ protected:
+ content::TestBrowserThreadBundle thread_bundle_;
+ PrefsTestBrowserContext browser_context_;
+};
+
+TEST_F(ShellPrefsTest, CreateLocalState) {
+ scoped_ptr<PrefService> local_state =
+ shell_prefs::CreateLocalState(browser_context_.GetPath());
+ ASSERT_TRUE(local_state);
+
+#if defined(OS_CHROMEOS)
+ // Verify prefs were registered.
+ EXPECT_TRUE(local_state->FindPreference("hardware.audio_output_enabled"));
+
+ // Verify the test values were read.
+ EXPECT_FALSE(local_state->GetBoolean("hardware.audio_output_enabled"));
+#endif
+}
+
+TEST_F(ShellPrefsTest, CreateUserPrefService) {
+ // Create the pref service. This loads the test pref file.
+ scoped_ptr<PrefService> service =
+ shell_prefs::CreateUserPrefService(&browser_context_);
+
+ // Some basic extension preferences are registered.
+ EXPECT_TRUE(service->FindPreference("extensions.settings"));
+ EXPECT_TRUE(service->FindPreference("extensions.toolbarsize"));
+ EXPECT_FALSE(service->FindPreference("should.not.exist"));
+
+ // User prefs from the file have been read correctly.
+ EXPECT_EQ("1.2.3.4", service->GetString("extensions.last_chrome_version"));
+ EXPECT_EQ(123, service->GetInteger("extensions.toolbarsize"));
+
+ // The user prefs system has been initialized.
+ EXPECT_EQ(service.get(), user_prefs::UserPrefs::Get(&browser_context_));
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_runtime_api_delegate.cc b/chromium/extensions/shell/browser/shell_runtime_api_delegate.cc
new file mode 100644
index 00000000000..1d0a06fa8bd
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_runtime_api_delegate.cc
@@ -0,0 +1,67 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_runtime_api_delegate.h"
+
+#include "build/build_config.h"
+#include "extensions/common/api/runtime.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/power_manager_client.h"
+#endif
+
+using extensions::api::runtime::PlatformInfo;
+
+namespace extensions {
+
+ShellRuntimeAPIDelegate::ShellRuntimeAPIDelegate() {
+}
+
+ShellRuntimeAPIDelegate::~ShellRuntimeAPIDelegate() {
+}
+
+void ShellRuntimeAPIDelegate::AddUpdateObserver(UpdateObserver* observer) {
+}
+
+void ShellRuntimeAPIDelegate::RemoveUpdateObserver(UpdateObserver* observer) {
+}
+
+base::Version ShellRuntimeAPIDelegate::GetPreviousExtensionVersion(
+ const Extension* extension) {
+ return base::Version();
+}
+
+void ShellRuntimeAPIDelegate::ReloadExtension(const std::string& extension_id) {
+}
+
+bool ShellRuntimeAPIDelegate::CheckForUpdates(
+ const std::string& extension_id,
+ const UpdateCheckCallback& callback) {
+ return false;
+}
+
+void ShellRuntimeAPIDelegate::OpenURL(const GURL& uninstall_url) {
+}
+
+bool ShellRuntimeAPIDelegate::GetPlatformInfo(PlatformInfo* info) {
+#if defined(OS_CHROMEOS)
+ info->os = api::runtime::PLATFORM_OS_CROS;
+#elif defined(OS_LINUX)
+ info->os = api::runtime::PLATFORM_OS_LINUX;
+#endif
+ return true;
+}
+
+bool ShellRuntimeAPIDelegate::RestartDevice(std::string* error_message) {
+// We allow chrome.runtime.restart() to request a device restart on ChromeOS.
+#if defined(OS_CHROMEOS)
+ chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->RequestRestart();
+ return true;
+#endif
+ *error_message = "Restart is only supported on ChromeOS.";
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_runtime_api_delegate.h b/chromium/extensions/shell/browser/shell_runtime_api_delegate.h
new file mode 100644
index 00000000000..f0a8d3860fc
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_runtime_api_delegate.h
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_RUNTIME_API_DELEGATE_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_RUNTIME_API_DELEGATE_H_
+
+#include "base/macros.h"
+#include "extensions/browser/api/runtime/runtime_api_delegate.h"
+
+namespace extensions {
+
+class ShellRuntimeAPIDelegate : public RuntimeAPIDelegate {
+ public:
+ ShellRuntimeAPIDelegate();
+ ~ShellRuntimeAPIDelegate() override;
+
+ // RuntimeAPIDelegate implementation.
+ void AddUpdateObserver(UpdateObserver* observer) override;
+ void RemoveUpdateObserver(UpdateObserver* observer) override;
+ base::Version GetPreviousExtensionVersion(
+ const Extension* extension) override;
+ void ReloadExtension(const std::string& extension_id) override;
+ bool CheckForUpdates(const std::string& extension_id,
+ const UpdateCheckCallback& callback) override;
+ void OpenURL(const GURL& uninstall_url) override;
+ bool GetPlatformInfo(api::runtime::PlatformInfo* info) override;
+ bool RestartDevice(std::string* error_message) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShellRuntimeAPIDelegate);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_RUNTIME_API_DELEGATE_H_
diff --git a/chromium/extensions/shell/browser/shell_screen.cc b/chromium/extensions/shell/browser/shell_screen.cc
new file mode 100644
index 00000000000..14c54015b14
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_screen.cc
@@ -0,0 +1,107 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_screen.h"
+
+#include <stdint.h>
+#include <vector>
+
+#include "base/logging.h"
+#include "ui/aura/env.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_tree_host.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace extensions {
+namespace {
+
+const int64_t kDisplayId = 0;
+
+} // namespace
+
+ShellScreen::ShellScreen(const gfx::Size& size)
+ : host_(nullptr), display_(kDisplayId) {
+ DCHECK(!size.IsEmpty());
+ // Screen is positioned at (0,0).
+ gfx::Rect bounds(size);
+ display_.SetScaleAndBounds(1.0f, bounds);
+}
+
+ShellScreen::~ShellScreen() {
+ DCHECK(!host_) << "Window not closed before destroying ShellScreen";
+}
+
+aura::WindowTreeHost* ShellScreen::CreateHostForPrimaryDisplay() {
+ DCHECK(!host_);
+ host_ = aura::WindowTreeHost::Create(gfx::Rect(display_.GetSizeInPixel()));
+ host_->window()->AddObserver(this);
+ host_->InitHost();
+ return host_;
+}
+
+// aura::WindowObserver overrides:
+
+void ShellScreen::OnWindowBoundsChanged(aura::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ DCHECK_EQ(host_->window(), window);
+ display_.SetSize(new_bounds.size());
+}
+
+void ShellScreen::OnWindowDestroying(aura::Window* window) {
+ DCHECK_EQ(host_->window(), window);
+ host_->window()->RemoveObserver(this);
+ host_ = nullptr;
+}
+
+// gfx::Screen overrides:
+
+gfx::Point ShellScreen::GetCursorScreenPoint() {
+ return aura::Env::GetInstance()->last_mouse_location();
+}
+
+gfx::NativeWindow ShellScreen::GetWindowUnderCursor() {
+ return GetWindowAtScreenPoint(GetCursorScreenPoint());
+}
+
+gfx::NativeWindow ShellScreen::GetWindowAtScreenPoint(const gfx::Point& point) {
+ return host_->window()->GetTopWindowContainingPoint(point);
+}
+
+int ShellScreen::GetNumDisplays() const {
+ return 1;
+}
+
+std::vector<gfx::Display> ShellScreen::GetAllDisplays() const {
+ return std::vector<gfx::Display>(1, display_);
+}
+
+gfx::Display ShellScreen::GetDisplayNearestWindow(
+ gfx::NativeWindow window) const {
+ return display_;
+}
+
+gfx::Display ShellScreen::GetDisplayNearestPoint(
+ const gfx::Point& point) const {
+ return display_;
+}
+
+gfx::Display ShellScreen::GetDisplayMatching(
+ const gfx::Rect& match_rect) const {
+ return display_;
+}
+
+gfx::Display ShellScreen::GetPrimaryDisplay() const {
+ return display_;
+}
+
+void ShellScreen::AddObserver(gfx::DisplayObserver* observer) {
+}
+
+void ShellScreen::RemoveObserver(gfx::DisplayObserver* observer) {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_screen.h b/chromium/extensions/shell/browser/shell_screen.h
new file mode 100644
index 00000000000..4d24891785c
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_screen.h
@@ -0,0 +1,62 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_SCREEN_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_SCREEN_H_
+
+#include "base/macros.h"
+#include "ui/aura/window_observer.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+
+namespace aura {
+class WindowTreeHost;
+}
+
+namespace gfx {
+class Size;
+}
+
+namespace extensions {
+
+// A minimal Aura implementation of a screen. Scale factor is locked at 1.0.
+// When running on a Linux desktop resizing the main window resizes the screen.
+class ShellScreen : public gfx::Screen, public aura::WindowObserver {
+ public:
+ // Creates a screen occupying |size| physical pixels.
+ explicit ShellScreen(const gfx::Size& size);
+ ~ShellScreen() override;
+
+ // Caller owns the returned object.
+ aura::WindowTreeHost* CreateHostForPrimaryDisplay();
+
+ // WindowObserver overrides:
+ void OnWindowBoundsChanged(aura::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) override;
+ void OnWindowDestroying(aura::Window* window) override;
+
+ // gfx::Screen overrides:
+ gfx::Point GetCursorScreenPoint() override;
+ gfx::NativeWindow GetWindowUnderCursor() override;
+ gfx::NativeWindow GetWindowAtScreenPoint(const gfx::Point& point) override;
+ int GetNumDisplays() const override;
+ std::vector<gfx::Display> GetAllDisplays() const override;
+ gfx::Display GetDisplayNearestWindow(gfx::NativeView view) const override;
+ gfx::Display GetDisplayNearestPoint(const gfx::Point& point) const override;
+ gfx::Display GetDisplayMatching(const gfx::Rect& match_rect) const override;
+ gfx::Display GetPrimaryDisplay() const override;
+ void AddObserver(gfx::DisplayObserver* observer) override;
+ void RemoveObserver(gfx::DisplayObserver* observer) override;
+
+ private:
+ aura::WindowTreeHost* host_; // Not owned.
+ gfx::Display display_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellScreen);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_SCREEN_H_
diff --git a/chromium/extensions/shell/browser/shell_screen_unittest.cc b/chromium/extensions/shell/browser/shell_screen_unittest.cc
new file mode 100644
index 00000000000..691a85fee10
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_screen_unittest.cc
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_screen.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/aura/test/aura_test_base.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_tree_host.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace extensions {
+
+using ShellScreenTest = aura::test::AuraTestBase;
+
+// Basic sanity tests for ShellScreen.
+TEST_F(ShellScreenTest, ShellScreen) {
+ ShellScreen screen(gfx::Size(640, 480));
+
+ // There is only one display.
+ EXPECT_EQ(1, screen.GetNumDisplays());
+ EXPECT_EQ(1u, screen.GetAllDisplays().size());
+ EXPECT_EQ(0, screen.GetAllDisplays()[0].id());
+ EXPECT_EQ(0, screen.GetPrimaryDisplay().id());
+ EXPECT_EQ("640x480", screen.GetPrimaryDisplay().size().ToString());
+
+ // Tests that reshaping the host window reshapes the display.
+ // NOTE: AuraTestBase already has its own WindowTreeHost. This is creating a
+ // second one.
+ scoped_ptr<aura::WindowTreeHost> host(screen.CreateHostForPrimaryDisplay());
+ EXPECT_TRUE(host->window());
+ host->window()->SetBounds(gfx::Rect(0, 0, 800, 600));
+ EXPECT_EQ("800x600", screen.GetPrimaryDisplay().size().ToString());
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_special_storage_policy.cc b/chromium/extensions/shell/browser/shell_special_storage_policy.cc
new file mode 100644
index 00000000000..d43c71bfdd7
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_special_storage_policy.cc
@@ -0,0 +1,45 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_special_storage_policy.h"
+
+namespace extensions {
+
+ShellSpecialStoragePolicy::ShellSpecialStoragePolicy() {
+}
+
+ShellSpecialStoragePolicy::~ShellSpecialStoragePolicy() {
+}
+
+bool ShellSpecialStoragePolicy::IsStorageProtected(const GURL& origin) {
+ return true;
+}
+
+bool ShellSpecialStoragePolicy::IsStorageUnlimited(const GURL& origin) {
+ return true;
+}
+
+bool ShellSpecialStoragePolicy::IsStorageDurable(const GURL& origin) {
+ // The plan is to forbid extensions from acquiring the durable storage
+ // permission because they can specify 'unlimitedStorage' in the manifest.
+ return false;
+}
+
+bool ShellSpecialStoragePolicy::IsStorageSessionOnly(const GURL& origin) {
+ return false;
+}
+
+bool ShellSpecialStoragePolicy::CanQueryDiskSize(const GURL& origin) {
+ return true;
+}
+
+bool ShellSpecialStoragePolicy::HasSessionOnlyOrigins() {
+ return false;
+}
+
+bool ShellSpecialStoragePolicy::HasIsolatedStorage(const GURL& origin) {
+ return false;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_special_storage_policy.h b/chromium/extensions/shell/browser/shell_special_storage_policy.h
new file mode 100644
index 00000000000..dc78154726f
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_special_storage_policy.h
@@ -0,0 +1,33 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_SPECIAL_STORAGE_POLICY_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_SPECIAL_STORAGE_POLICY_H_
+
+#include "storage/browser/quota/special_storage_policy.h"
+
+namespace extensions {
+
+// A simple storage policy for app_shell which does not limit storage
+// capabilities and aims to be as permissive as possible.
+class ShellSpecialStoragePolicy : public storage::SpecialStoragePolicy {
+ public:
+ ShellSpecialStoragePolicy();
+
+ // storage::SpecialStoragePolicy implementation.
+ bool IsStorageProtected(const GURL& origin) override;
+ bool IsStorageUnlimited(const GURL& origin) override;
+ bool IsStorageDurable(const GURL& origin) override;
+ bool IsStorageSessionOnly(const GURL& origin) override;
+ bool CanQueryDiskSize(const GURL& origin) override;
+ bool HasIsolatedStorage(const GURL& origin) override;
+ bool HasSessionOnlyOrigins() override;
+
+ protected:
+ ~ShellSpecialStoragePolicy() override;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_SPECIAL_STORAGE_POLICY_H
diff --git a/chromium/extensions/shell/browser/shell_speech_recognition_manager_delegate.cc b/chromium/extensions/shell/browser/shell_speech_recognition_manager_delegate.cc
new file mode 100644
index 00000000000..c4ce068cce7
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_speech_recognition_manager_delegate.cc
@@ -0,0 +1,132 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_speech_recognition_manager_delegate.h"
+
+#include "base/bind.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/speech_recognition_manager.h"
+#include "content/public/browser/speech_recognition_session_context.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/browser/view_type_utils.h"
+
+using content::BrowserThread;
+using content::SpeechRecognitionManager;
+using content::WebContents;
+
+namespace extensions {
+namespace speech {
+
+ShellSpeechRecognitionManagerDelegate::ShellSpeechRecognitionManagerDelegate() {
+}
+
+ShellSpeechRecognitionManagerDelegate::
+~ShellSpeechRecognitionManagerDelegate() {
+}
+
+void ShellSpeechRecognitionManagerDelegate::OnRecognitionStart(int session_id) {
+}
+
+void ShellSpeechRecognitionManagerDelegate::OnAudioStart(int session_id) {
+}
+
+void ShellSpeechRecognitionManagerDelegate::OnEnvironmentEstimationComplete(
+ int session_id) {
+}
+
+void ShellSpeechRecognitionManagerDelegate::OnSoundStart(int session_id) {
+}
+
+void ShellSpeechRecognitionManagerDelegate::OnSoundEnd(int session_id) {
+}
+
+void ShellSpeechRecognitionManagerDelegate::OnAudioEnd(int session_id) {
+}
+
+void ShellSpeechRecognitionManagerDelegate::OnRecognitionEnd(int session_id) {
+}
+
+void ShellSpeechRecognitionManagerDelegate::OnRecognitionResults(
+ int session_id,
+ const content::SpeechRecognitionResults& result) {
+}
+
+void ShellSpeechRecognitionManagerDelegate::OnRecognitionError(
+ int session_id,
+ const content::SpeechRecognitionError& error) {
+}
+
+void ShellSpeechRecognitionManagerDelegate::OnAudioLevelsChange(
+ int session_id,
+ float volume,
+ float noise_volume) {
+}
+
+void ShellSpeechRecognitionManagerDelegate::GetDiagnosticInformation(
+ bool* can_report_metrics,
+ std::string* hardware_info) {
+}
+
+void ShellSpeechRecognitionManagerDelegate::CheckRecognitionIsAllowed(
+ int session_id,
+ base::Callback<void(bool ask_user, bool is_allowed)> callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+ const content::SpeechRecognitionSessionContext& context =
+ SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
+
+ // Make sure that initiators (extensions/web pages) properly set the
+ // |render_process_id| field, which is needed later to retrieve the profile.
+ DCHECK_NE(context.render_process_id, 0);
+
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&CheckRenderViewType,
+ callback,
+ context.render_process_id,
+ context.render_view_id));
+}
+
+content::SpeechRecognitionEventListener*
+ShellSpeechRecognitionManagerDelegate::GetEventListener() {
+ return this;
+}
+
+bool ShellSpeechRecognitionManagerDelegate::FilterProfanities(
+ int render_process_id) {
+ // TODO(zork): Determine where this preference should come from.
+ return true;
+}
+
+// static
+void ShellSpeechRecognitionManagerDelegate::CheckRenderViewType(
+ base::Callback<void(bool ask_user, bool is_allowed)> callback,
+ int render_process_id,
+ int render_view_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ content::RenderViewHost* render_view_host =
+ content::RenderViewHost::FromID(render_process_id, render_view_id);
+ bool allowed = false;
+ bool check_permission = false;
+
+ if (render_view_host) {
+ WebContents* web_contents =
+ WebContents::FromRenderViewHost(render_view_host);
+ extensions::ViewType view_type = extensions::GetViewType(web_contents);
+
+ if (view_type == extensions::VIEW_TYPE_APP_WINDOW ||
+ view_type == extensions::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) {
+ allowed = true;
+ check_permission = true;
+ } else {
+ LOG(WARNING) << "Speech recognition only supported in Apps.";
+ }
+ }
+
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(callback, check_permission, allowed));
+}
+
+} // namespace speech
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_speech_recognition_manager_delegate.h b/chromium/extensions/shell/browser/shell_speech_recognition_manager_delegate.h
new file mode 100644
index 00000000000..5d00dc01eac
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_speech_recognition_manager_delegate.h
@@ -0,0 +1,61 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_SPEECH_RECOGNITION_MANAGER_DELEGATE_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_SPEECH_RECOGNITION_MANAGER_DELEGATE_H_
+
+#include "base/macros.h"
+#include "content/public/browser/speech_recognition_event_listener.h"
+#include "content/public/browser/speech_recognition_manager_delegate.h"
+
+namespace extensions {
+namespace speech {
+
+class ShellSpeechRecognitionManagerDelegate
+ : public content::SpeechRecognitionManagerDelegate,
+ public content::SpeechRecognitionEventListener {
+ public:
+ ShellSpeechRecognitionManagerDelegate();
+ ~ShellSpeechRecognitionManagerDelegate() override;
+
+ private:
+ // SpeechRecognitionEventListener methods.
+ void OnRecognitionStart(int session_id) override;
+ void OnAudioStart(int session_id) override;
+ void OnEnvironmentEstimationComplete(int session_id) override;
+ void OnSoundStart(int session_id) override;
+ void OnSoundEnd(int session_id) override;
+ void OnAudioEnd(int session_id) override;
+ void OnRecognitionEnd(int session_id) override;
+ void OnRecognitionResults(
+ int session_id,
+ const content::SpeechRecognitionResults& result) override;
+ void OnRecognitionError(
+ int session_id,
+ const content::SpeechRecognitionError& error) override;
+ void OnAudioLevelsChange(int session_id,
+ float volume,
+ float noise_volume) override;
+
+ // SpeechRecognitionManagerDelegate methods.
+ void GetDiagnosticInformation(bool* can_report_metrics,
+ std::string* hardware_info) override;
+ void CheckRecognitionIsAllowed(
+ int session_id,
+ base::Callback<void(bool ask_user, bool is_allowed)> callback) override;
+ content::SpeechRecognitionEventListener* GetEventListener() override;
+ bool FilterProfanities(int render_process_id) override;
+
+ static void CheckRenderViewType(
+ base::Callback<void(bool ask_user, bool is_allowed)> callback,
+ int render_process_id,
+ int render_view_id);
+
+ DISALLOW_COPY_AND_ASSIGN(ShellSpeechRecognitionManagerDelegate);
+};
+
+} // namespace speech
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_CONTENT_BROWSER_CLIENT_H_
diff --git a/chromium/extensions/shell/browser/shell_update_query_params_delegate.cc b/chromium/extensions/shell/browser/shell_update_query_params_delegate.cc
new file mode 100644
index 00000000000..462b1ffde00
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_update_query_params_delegate.cc
@@ -0,0 +1,24 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_update_query_params_delegate.h"
+
+#include <string>
+
+namespace extensions {
+
+ShellUpdateQueryParamsDelegate::ShellUpdateQueryParamsDelegate() {
+}
+
+ShellUpdateQueryParamsDelegate::~ShellUpdateQueryParamsDelegate() {
+}
+
+std::string ShellUpdateQueryParamsDelegate::GetExtraParams() {
+ // This version number is high enough to be supported by Omaha
+ // (below 31 is unsupported), but it's fake enough to be obviously
+ // not a Chrome release.
+ return "&prodversion=38.1234.5678.9";
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_update_query_params_delegate.h b/chromium/extensions/shell/browser/shell_update_query_params_delegate.h
new file mode 100644
index 00000000000..90e499b9d7d
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_update_query_params_delegate.h
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_UPDATE_QUERY_PARAMS_DELEGATE_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_UPDATE_QUERY_PARAMS_DELEGATE_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "components/update_client/update_query_params_delegate.h"
+
+namespace extensions {
+
+class ShellUpdateQueryParamsDelegate
+ : public update_client::UpdateQueryParamsDelegate {
+ public:
+ ShellUpdateQueryParamsDelegate();
+ ~ShellUpdateQueryParamsDelegate() override;
+
+ std::string GetExtraParams() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShellUpdateQueryParamsDelegate);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_UPDATE_QUERY_PARAMS_DELEGATE_H_
diff --git a/chromium/extensions/shell/browser/shell_url_request_context_getter.cc b/chromium/extensions/shell/browser/shell_url_request_context_getter.cc
new file mode 100644
index 00000000000..54f223741c3
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_url_request_context_getter.cc
@@ -0,0 +1,45 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_url_request_context_getter.h"
+
+#include <utility>
+
+#include "base/memory/scoped_ptr.h"
+#include "content/public/browser/resource_request_info.h"
+#include "extensions/browser/info_map.h"
+#include "extensions/shell/browser/shell_network_delegate.h"
+
+namespace extensions {
+
+ShellURLRequestContextGetter::ShellURLRequestContextGetter(
+ content::BrowserContext* browser_context,
+ bool ignore_certificate_errors,
+ const base::FilePath& base_path,
+ scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> file_task_runner,
+ content::ProtocolHandlerMap* protocol_handlers,
+ content::URLRequestInterceptorScopedVector request_interceptors,
+ net::NetLog* net_log,
+ InfoMap* extension_info_map)
+ : content::ShellURLRequestContextGetter(ignore_certificate_errors,
+ base_path,
+ std::move(io_task_runner),
+ std::move(file_task_runner),
+ protocol_handlers,
+ std::move(request_interceptors),
+ net_log),
+ browser_context_(browser_context),
+ extension_info_map_(extension_info_map) {}
+
+ShellURLRequestContextGetter::~ShellURLRequestContextGetter() {
+}
+
+scoped_ptr<net::NetworkDelegate>
+ShellURLRequestContextGetter::CreateNetworkDelegate() {
+ return make_scoped_ptr(
+ new ShellNetworkDelegate(browser_context_, extension_info_map_));
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/browser/shell_url_request_context_getter.h b/chromium/extensions/shell/browser/shell_url_request_context_getter.h
new file mode 100644
index 00000000000..ca678eadf7b
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_url_request_context_getter.h
@@ -0,0 +1,59 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_URL_REQUEST_CONTEXT_GETTER_H_
+#define EXTENSIONS_SHELL_BROWSER_SHELL_URL_REQUEST_CONTEXT_GETTER_H_
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "content/shell/browser/shell_url_request_context_getter.h"
+
+namespace base {
+class MessageLoop;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace net {
+class NetworkDelegate;
+class NetLog;
+}
+
+namespace extensions {
+
+class InfoMap;
+
+class ShellURLRequestContextGetter :
+ public content::ShellURLRequestContextGetter {
+ public:
+ ShellURLRequestContextGetter(
+ content::BrowserContext* browser_context,
+ bool ignore_certificate_errors,
+ const base::FilePath& base_path,
+ scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> file_task_runner,
+ content::ProtocolHandlerMap* protocol_handlers,
+ content::URLRequestInterceptorScopedVector request_interceptors,
+ net::NetLog* net_log,
+ InfoMap* extension_info_map);
+
+ // content::ShellURLRequestContextGetter implementation.
+ scoped_ptr<net::NetworkDelegate> CreateNetworkDelegate() override;
+
+protected:
+ ~ShellURLRequestContextGetter() override;
+
+private:
+ content::BrowserContext* browser_context_;
+ InfoMap* extension_info_map_;
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(ShellURLRequestContextGetter);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_BROWSER_SHELL_URL_REQUEST_CONTEXT_GETTER_H_
diff --git a/chromium/extensions/shell/browser/shell_web_contents_modal_dialog_manager.cc b/chromium/extensions/shell/browser/shell_web_contents_modal_dialog_manager.cc
new file mode 100644
index 00000000000..266f37960df
--- /dev/null
+++ b/chromium/extensions/shell/browser/shell_web_contents_modal_dialog_manager.cc
@@ -0,0 +1,18 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/web_modal/web_contents_modal_dialog_manager.h"
+
+namespace web_modal {
+
+SingleWebContentsDialogManager*
+WebContentsModalDialogManager::CreateNativeWebModalManager(
+ gfx::NativeWindow dialog,
+ SingleWebContentsDialogManagerDelegate* native_delegate) {
+ // TODO(oshima): Investigate if we need to implement this.
+ NOTREACHED();
+ return NULL;
+}
+
+} // namespace web_modal
diff --git a/chromium/extensions/shell/common/DEPS b/chromium/extensions/shell/common/DEPS
new file mode 100644
index 00000000000..bee8c47af28
--- /dev/null
+++ b/chromium/extensions/shell/common/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+components/nacl/common",
+ "+components/nacl/renderer/plugin",
+ "+ppapi",
+]
diff --git a/chromium/extensions/shell/common/api/BUILD.gn b/chromium/extensions/shell/common/api/BUILD.gn
new file mode 100644
index 00000000000..35ed47e9d35
--- /dev/null
+++ b/chromium/extensions/shell/common/api/BUILD.gn
@@ -0,0 +1,24 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/json_schema_api.gni")
+import("schemas.gni")
+
+# GYP version: extensions/shell/common/api/api.gyp:shell_api
+json_schema_api("api") {
+ schemas = true
+ bundle = true
+ bundle_name = "Shell"
+}
+
+# GYP version: extensions/shell/browser/api/api_registration.gyp:shell_api_registration
+json_schema_api("api_registration") {
+ impl_dir = "//extensions/shell/browser/api"
+ bundle_registration = true
+ bundle_name = "Shell"
+
+ deps = [
+ ":api",
+ ]
+}
diff --git a/chromium/extensions/shell/common/api/_api_features.json b/chromium/extensions/shell/common/api/_api_features.json
new file mode 100644
index 00000000000..ea4adf18b42
--- /dev/null
+++ b/chromium/extensions/shell/common/api/_api_features.json
@@ -0,0 +1,22 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines extension APIs implemented under src/extensions/shell.
+// See extensions/common/features/* to understand this file, in particular
+// feature.h, simple_feature.h, and base_feature_provider.h.
+
+{
+ // Stub implementation of chrome.identity for app_shell.
+ "identity": {
+ "channel": "dev",
+ "contexts": ["blessed_extension"],
+ "extension_types": ["platform_app"]
+ },
+ // Setup related functions for a Google Cloud Devices (GCD) target device.
+ "shell.gcd": {
+ "channel": "dev",
+ "contexts": ["blessed_extension"],
+ "extension_types": ["platform_app"]
+ }
+}
diff --git a/chromium/extensions/shell/common/api/identity.idl b/chromium/extensions/shell/common/api/identity.idl
new file mode 100644
index 00000000000..f5e681e9b6f
--- /dev/null
+++ b/chromium/extensions/shell/common/api/identity.idl
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Simplified implementation of the <code>chrome.identity</code> for app_shell.
+namespace identity {
+
+ dictionary GetAuthTokenDetails {
+ // Ignored parameter. Exists only for compatibility.
+ boolean? interactive;
+ };
+
+ dictionary InvalidTokenDetails {
+ // Ignored parameter. Exists only for compatibility.
+ DOMString? token;
+ };
+
+ // Called with the OAuth2 access token on success or undefined on error.
+ callback GetAuthTokenCallback = void (optional DOMString token);
+
+ // Called by removeCachedAuthToken().
+ callback InvalidateAuthTokenCallback = void ();
+
+ interface Functions {
+ // Returns an OAuth2 access token for the current app_shell user for scopes
+ // from the manifest. Does not prompt the user.
+ static void getAuthToken(GetAuthTokenDetails options,
+ GetAuthTokenCallback callback);
+
+ // Stub. Calls callback immediately because app_shell does not cache access
+ // tokens the way Chrome does.
+ static void removeCachedAuthToken(InvalidTokenDetails details,
+ InvalidateAuthTokenCallback callback);
+ };
+
+ interface Events {
+ // Stub. Never fired because app_shell only supports a single user account.
+ static void onSignInChanged(object account, boolean signedIn);
+ };
+};
diff --git a/chromium/extensions/shell/common/api/schemas.gni b/chromium/extensions/shell/common/api/schemas.gni
new file mode 100644
index 00000000000..3f14cfc0097
--- /dev/null
+++ b/chromium/extensions/shell/common/api/schemas.gni
@@ -0,0 +1,17 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+gypi_values = exec_script("//build/gypi_to_gn.py",
+ [ rebase_path("schemas.gypi") ],
+ "scope",
+ [ "schemas.gypi" ])
+
+sources = gypi_values.schema_files
+if (is_chromeos) {
+ sources += gypi_values.chromeos_schema_files
+}
+
+uncompiled_sources = gypi_values.non_compiled_schema_files
+
+root_namespace = "extensions::shell::api::%(namespace)s"
diff --git a/chromium/extensions/shell/common/shell_content_client.cc b/chromium/extensions/shell/common/shell_content_client.cc
new file mode 100644
index 00000000000..e180da88feb
--- /dev/null
+++ b/chromium/extensions/shell/common/shell_content_client.cc
@@ -0,0 +1,121 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/common/shell_content_client.h"
+
+#include "base/strings/string_piece.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/common/user_agent.h"
+#include "extensions/common/constants.h"
+#include "extensions/shell/common/version.h" // Generated file.
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+
+#if !defined(DISABLE_NACL)
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "components/nacl/common/nacl_constants.h"
+#include "components/nacl/renderer/plugin/ppapi_entrypoints.h"
+#include "content/public/common/pepper_plugin_info.h"
+#include "ppapi/shared_impl/ppapi_permissions.h"
+#endif
+
+namespace extensions {
+namespace {
+
+#if !defined(DISABLE_NACL)
+bool GetNaClPluginPath(base::FilePath* path) {
+ // On Posix, plugins live in the module directory.
+ base::FilePath module;
+ if (!PathService::Get(base::DIR_MODULE, &module))
+ return false;
+ *path = module.Append(nacl::kInternalNaClPluginFileName);
+ return true;
+}
+#endif // !defined(DISABLE_NACL)
+
+} // namespace
+
+ShellContentClient::ShellContentClient() {
+}
+
+ShellContentClient::~ShellContentClient() {
+}
+
+void ShellContentClient::AddPepperPlugins(
+ std::vector<content::PepperPluginInfo>* plugins) {
+#if !defined(DISABLE_NACL)
+ base::FilePath path;
+ if (!GetNaClPluginPath(&path))
+ return;
+
+ content::PepperPluginInfo nacl;
+ // The nacl plugin is now built into the binary.
+ nacl.is_internal = true;
+ nacl.path = path;
+ nacl.name = nacl::kNaClPluginName;
+ content::WebPluginMimeType nacl_mime_type(nacl::kNaClPluginMimeType,
+ nacl::kNaClPluginExtension,
+ nacl::kNaClPluginDescription);
+ nacl.mime_types.push_back(nacl_mime_type);
+ content::WebPluginMimeType pnacl_mime_type(nacl::kPnaclPluginMimeType,
+ nacl::kPnaclPluginExtension,
+ nacl::kPnaclPluginDescription);
+ nacl.mime_types.push_back(pnacl_mime_type);
+ nacl.internal_entry_points.get_interface = nacl_plugin::PPP_GetInterface;
+ nacl.internal_entry_points.initialize_module =
+ nacl_plugin::PPP_InitializeModule;
+ nacl.internal_entry_points.shutdown_module =
+ nacl_plugin::PPP_ShutdownModule;
+ nacl.permissions = ppapi::PERMISSION_PRIVATE | ppapi::PERMISSION_DEV;
+ plugins->push_back(nacl);
+#endif // !defined(DISABLE_NACL)
+}
+
+static const int kNumShellStandardURLSchemes = 2;
+static const url::SchemeWithType kShellStandardURLSchemes[
+ kNumShellStandardURLSchemes] = {
+ {extensions::kExtensionScheme, url::SCHEME_WITHOUT_PORT},
+ {extensions::kExtensionResourceScheme, url::SCHEME_WITHOUT_PORT},
+};
+
+void ShellContentClient::AddAdditionalSchemes(
+ std::vector<url::SchemeWithType>* standard_schemes,
+ std::vector<url::SchemeWithType>* referrer_schemes,
+ std::vector<std::string>* savable_schemes) {
+ for (int i = 0; i < kNumShellStandardURLSchemes; i++)
+ standard_schemes->push_back(kShellStandardURLSchemes[i]);
+
+ savable_schemes->push_back(kExtensionScheme);
+ savable_schemes->push_back(kExtensionResourceScheme);
+}
+
+std::string ShellContentClient::GetUserAgent() const {
+ // Must contain a user agent string for version sniffing. For example,
+ // pluginless WebRTC Hangouts checks the Chrome version number.
+ return content::BuildUserAgentFromProduct("Chrome/" PRODUCT_VERSION);
+}
+
+base::string16 ShellContentClient::GetLocalizedString(int message_id) const {
+ return l10n_util::GetStringUTF16(message_id);
+}
+
+base::StringPiece ShellContentClient::GetDataResource(
+ int resource_id,
+ ui::ScaleFactor scale_factor) const {
+ return ResourceBundle::GetSharedInstance().GetRawDataResourceForScale(
+ resource_id, scale_factor);
+}
+
+base::RefCountedStaticMemory* ShellContentClient::GetDataResourceBytes(
+ int resource_id) const {
+ return ResourceBundle::GetSharedInstance().LoadDataResourceBytes(resource_id);
+}
+
+gfx::Image& ShellContentClient::GetNativeImageNamed(int resource_id) const {
+ return ResourceBundle::GetSharedInstance().GetNativeImageNamed(resource_id);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/common/shell_content_client.h b/chromium/extensions/shell/common/shell_content_client.h
new file mode 100644
index 00000000000..471272ac8b6
--- /dev/null
+++ b/chromium/extensions/shell/common/shell_content_client.h
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_COMMON_SHELL_CONTENT_CLIENT_H_
+#define EXTENSIONS_SHELL_COMMON_SHELL_CONTENT_CLIENT_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "content/public/common/content_client.h"
+#include "url/url_util.h"
+
+namespace extensions {
+
+class ShellContentClient : public content::ContentClient {
+ public:
+ ShellContentClient();
+ ~ShellContentClient() override;
+
+ void AddPepperPlugins(
+ std::vector<content::PepperPluginInfo>* plugins) override;
+ void AddAdditionalSchemes(std::vector<url::SchemeWithType>* standard_schemes,
+ std::vector<url::SchemeWithType>* referrer_schemes,
+ std::vector<std::string>* saveable_shemes) override;
+ std::string GetUserAgent() const override;
+ base::string16 GetLocalizedString(int message_id) const override;
+ base::StringPiece GetDataResource(
+ int resource_id,
+ ui::ScaleFactor scale_factor) const override;
+ base::RefCountedStaticMemory* GetDataResourceBytes(
+ int resource_id) const override;
+ gfx::Image& GetNativeImageNamed(int resource_id) const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShellContentClient);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_COMMON_SHELL_CONTENT_CLIENT_H_
diff --git a/chromium/extensions/shell/common/shell_content_client_unittest.cc b/chromium/extensions/shell/common/shell_content_client_unittest.cc
new file mode 100644
index 00000000000..ba1540b68c2
--- /dev/null
+++ b/chromium/extensions/shell/common/shell_content_client_unittest.cc
@@ -0,0 +1,31 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/common/shell_content_client.h"
+
+#include <string>
+
+#include "base/strings/pattern.h"
+#include "base/strings/string_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+typedef testing::Test ShellContentClientTest;
+
+namespace extensions {
+
+// Tests that the app_shell user agent looks like a Chrome user agent.
+TEST_F(ShellContentClientTest, UserAgentFormat) {
+ ShellContentClient client;
+ std::string user_agent = client.GetUserAgent();
+
+ // Must start with the usual Mozilla-compatibility string.
+ EXPECT_TRUE(base::StartsWith(user_agent, "Mozilla/5.0",
+ base::CompareCase::INSENSITIVE_ASCII))
+ << user_agent;
+
+ // Must contain a substring like "Chrome/1.2.3.4".
+ EXPECT_TRUE(base::MatchPattern(user_agent, "*Chrome/*.*.*.*")) << user_agent;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/common/shell_extensions_client.cc b/chromium/extensions/shell/common/shell_extensions_client.cc
new file mode 100644
index 00000000000..58a2e2fa341
--- /dev/null
+++ b/chromium/extensions/shell/common/shell_extensions_client.cc
@@ -0,0 +1,215 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/common/shell_extensions_client.h"
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "extensions/common/api/generated_schemas.h"
+#include "extensions/common/common_manifest_handlers.h"
+#include "extensions/common/extension_urls.h"
+#include "extensions/common/features/api_feature.h"
+#include "extensions/common/features/base_feature_provider.h"
+#include "extensions/common/features/behavior_feature.h"
+#include "extensions/common/features/json_feature_provider_source.h"
+#include "extensions/common/features/manifest_feature.h"
+#include "extensions/common/features/permission_feature.h"
+#include "extensions/common/features/simple_feature.h"
+#include "extensions/common/manifest_handler.h"
+#include "extensions/common/permissions/permission_message_provider.h"
+#include "extensions/common/permissions/permissions_info.h"
+#include "extensions/common/permissions/permissions_provider.h"
+#include "extensions/common/url_pattern_set.h"
+#include "extensions/shell/common/api/generated_schemas.h"
+#include "grit/app_shell_resources.h"
+#include "grit/extensions_resources.h"
+
+namespace extensions {
+
+namespace {
+
+template <class FeatureClass>
+SimpleFeature* CreateFeature() {
+ return new FeatureClass;
+}
+
+// TODO(jamescook): Refactor ChromePermissionsMessageProvider so we can share
+// code. For now, this implementation does nothing.
+class ShellPermissionMessageProvider : public PermissionMessageProvider {
+ public:
+ ShellPermissionMessageProvider() {}
+ ~ShellPermissionMessageProvider() override {}
+
+ // PermissionMessageProvider implementation.
+ PermissionMessages GetPermissionMessages(
+ const PermissionIDSet& permissions) const override {
+ return PermissionMessages();
+ }
+
+ bool IsPrivilegeIncrease(const PermissionSet& old_permissions,
+ const PermissionSet& new_permissions,
+ Manifest::Type extension_type) const override {
+ // Ensure we implement this before shipping.
+ CHECK(false);
+ return false;
+ }
+
+ PermissionIDSet GetAllPermissionIDs(
+ const PermissionSet& permissions,
+ Manifest::Type extension_type) const override {
+ return PermissionIDSet();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShellPermissionMessageProvider);
+};
+
+base::LazyInstance<ShellPermissionMessageProvider>
+ g_permission_message_provider = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+ShellExtensionsClient::ShellExtensionsClient()
+ : extensions_api_permissions_(ExtensionsAPIPermissions()) {
+}
+
+ShellExtensionsClient::~ShellExtensionsClient() {
+}
+
+void ShellExtensionsClient::Initialize() {
+ RegisterCommonManifestHandlers();
+ ManifestHandler::FinalizeRegistration();
+ // TODO(jamescook): Do we need to whitelist any extensions?
+
+ PermissionsInfo::GetInstance()->AddProvider(extensions_api_permissions_);
+}
+
+const PermissionMessageProvider&
+ShellExtensionsClient::GetPermissionMessageProvider() const {
+ NOTIMPLEMENTED();
+ return g_permission_message_provider.Get();
+}
+
+const std::string ShellExtensionsClient::GetProductName() {
+ return "app_shell";
+}
+
+scoped_ptr<FeatureProvider> ShellExtensionsClient::CreateFeatureProvider(
+ const std::string& name) const {
+ scoped_ptr<FeatureProvider> provider;
+ scoped_ptr<JSONFeatureProviderSource> source(
+ CreateFeatureProviderSource(name));
+ if (name == "api") {
+ provider.reset(new BaseFeatureProvider(source->dictionary(),
+ CreateFeature<APIFeature>));
+ } else if (name == "manifest") {
+ provider.reset(new BaseFeatureProvider(source->dictionary(),
+ CreateFeature<ManifestFeature>));
+ } else if (name == "permission") {
+ provider.reset(new BaseFeatureProvider(source->dictionary(),
+ CreateFeature<PermissionFeature>));
+ } else if (name == "behavior") {
+ provider.reset(new BaseFeatureProvider(source->dictionary(),
+ CreateFeature<BehaviorFeature>));
+ } else {
+ NOTREACHED();
+ }
+ return provider;
+}
+
+scoped_ptr<JSONFeatureProviderSource>
+ShellExtensionsClient::CreateFeatureProviderSource(
+ const std::string& name) const {
+ scoped_ptr<JSONFeatureProviderSource> source(
+ new JSONFeatureProviderSource(name));
+ if (name == "api") {
+ source->LoadJSON(IDR_EXTENSION_API_FEATURES);
+ source->LoadJSON(IDR_SHELL_EXTENSION_API_FEATURES);
+ } else if (name == "manifest") {
+ source->LoadJSON(IDR_EXTENSION_MANIFEST_FEATURES);
+ } else if (name == "permission") {
+ source->LoadJSON(IDR_EXTENSION_PERMISSION_FEATURES);
+ } else if (name == "behavior") {
+ source->LoadJSON(IDR_EXTENSION_BEHAVIOR_FEATURES);
+ } else {
+ NOTREACHED();
+ source.reset();
+ }
+ return source;
+}
+
+void ShellExtensionsClient::FilterHostPermissions(
+ const URLPatternSet& hosts,
+ URLPatternSet* new_hosts,
+ PermissionIDSet* permissions) const {
+ NOTIMPLEMENTED();
+}
+
+void ShellExtensionsClient::SetScriptingWhitelist(
+ const ScriptingWhitelist& whitelist) {
+ scripting_whitelist_ = whitelist;
+}
+
+const ExtensionsClient::ScriptingWhitelist&
+ShellExtensionsClient::GetScriptingWhitelist() const {
+ // TODO(jamescook): Real whitelist.
+ return scripting_whitelist_;
+}
+
+URLPatternSet ShellExtensionsClient::GetPermittedChromeSchemeHosts(
+ const Extension* extension,
+ const APIPermissionSet& api_permissions) const {
+ NOTIMPLEMENTED();
+ return URLPatternSet();
+}
+
+bool ShellExtensionsClient::IsScriptableURL(const GURL& url,
+ std::string* error) const {
+ NOTIMPLEMENTED();
+ return true;
+}
+
+bool ShellExtensionsClient::IsAPISchemaGenerated(
+ const std::string& name) const {
+ return api::GeneratedSchemas::IsGenerated(name) ||
+ shell::api::ShellGeneratedSchemas::IsGenerated(name);
+}
+
+base::StringPiece ShellExtensionsClient::GetAPISchema(
+ const std::string& name) const {
+ // Schema for app_shell-only APIs.
+ if (shell::api::ShellGeneratedSchemas::IsGenerated(name))
+ return shell::api::ShellGeneratedSchemas::Get(name);
+
+ // Core extensions APIs.
+ return api::GeneratedSchemas::Get(name);
+}
+
+void ShellExtensionsClient::RegisterAPISchemaResources(
+ ExtensionAPI* api) const {
+}
+
+bool ShellExtensionsClient::ShouldSuppressFatalErrors() const {
+ return true;
+}
+
+void ShellExtensionsClient::RecordDidSuppressFatalError() {
+}
+
+std::string ShellExtensionsClient::GetWebstoreBaseURL() const {
+ return extension_urls::kChromeWebstoreBaseURL;
+}
+
+std::string ShellExtensionsClient::GetWebstoreUpdateURL() const {
+ return extension_urls::kChromeWebstoreUpdateURL;
+}
+
+bool ShellExtensionsClient::IsBlacklistUpdateURL(const GURL& url) const {
+ // TODO(rockot): Maybe we want to do something else here. For now we accept
+ // any URL as a blacklist URL because we don't really care.
+ return true;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/common/shell_extensions_client.h b/chromium/extensions/shell/common/shell_extensions_client.h
new file mode 100644
index 00000000000..8c14d067d59
--- /dev/null
+++ b/chromium/extensions/shell/common/shell_extensions_client.h
@@ -0,0 +1,58 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_COMMON_SHELL_EXTENSIONS_CLIENT_H_
+#define EXTENSIONS_SHELL_COMMON_SHELL_EXTENSIONS_CLIENT_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/permissions/extensions_api_permissions.h"
+
+namespace extensions {
+
+// The app_shell implementation of ExtensionsClient.
+class ShellExtensionsClient : public ExtensionsClient {
+ public:
+ ShellExtensionsClient();
+ ~ShellExtensionsClient() override;
+
+ // ExtensionsClient overrides:
+ void Initialize() override;
+ const PermissionMessageProvider& GetPermissionMessageProvider()
+ const override;
+ const std::string GetProductName() override;
+ scoped_ptr<FeatureProvider> CreateFeatureProvider(
+ const std::string& name) const override;
+ scoped_ptr<JSONFeatureProviderSource> CreateFeatureProviderSource(
+ const std::string& name) const override;
+ void FilterHostPermissions(const URLPatternSet& hosts,
+ URLPatternSet* new_hosts,
+ PermissionIDSet* permissions) const override;
+ void SetScriptingWhitelist(const ScriptingWhitelist& whitelist) override;
+ const ScriptingWhitelist& GetScriptingWhitelist() const override;
+ URLPatternSet GetPermittedChromeSchemeHosts(
+ const Extension* extension,
+ const APIPermissionSet& api_permissions) const override;
+ bool IsScriptableURL(const GURL& url, std::string* error) const override;
+ bool IsAPISchemaGenerated(const std::string& name) const override;
+ base::StringPiece GetAPISchema(const std::string& name) const override;
+ void RegisterAPISchemaResources(ExtensionAPI* api) const override;
+ bool ShouldSuppressFatalErrors() const override;
+ void RecordDidSuppressFatalError() override;
+ std::string GetWebstoreBaseURL() const override;
+ std::string GetWebstoreUpdateURL() const override;
+ bool IsBlacklistUpdateURL(const GURL& url) const override;
+
+ private:
+ const ExtensionsAPIPermissions extensions_api_permissions_;
+
+ ScriptingWhitelist scripting_whitelist_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellExtensionsClient);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_COMMON_SHELL_EXTENSIONS_CLIENT_H_
diff --git a/chromium/extensions/shell/common/switches.cc b/chromium/extensions/shell/common/switches.cc
new file mode 100644
index 00000000000..66bd7e58390
--- /dev/null
+++ b/chromium/extensions/shell/common/switches.cc
@@ -0,0 +1,26 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/common/switches.h"
+
+namespace extensions {
+namespace switches {
+
+// Allow roaming in the cellular network.
+const char kAppShellAllowRoaming[] = "app-shell-allow-roaming";
+
+// Size for the host window to create (i.e. "800x600").
+const char kAppShellHostWindowSize[] = "app-shell-host-window-size";
+
+// SSID of the preferred WiFi network.
+const char kAppShellPreferredNetwork[] = "app-shell-preferred-network";
+
+// Refresh token for identity API calls for the current user. Used for testing.
+const char kAppShellRefreshToken[] = "app-shell-refresh-token";
+
+// User email address of the current user.
+const char kAppShellUser[] = "app-shell-user";
+
+} // namespace switches
+} // namespace extensions
diff --git a/chromium/extensions/shell/common/switches.h b/chromium/extensions/shell/common/switches.h
new file mode 100644
index 00000000000..7cfe1d61f36
--- /dev/null
+++ b/chromium/extensions/shell/common/switches.h
@@ -0,0 +1,22 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_COMMON_SWITCHES_H_
+#define EXTENSIONS_SHELL_COMMON_SWITCHES_H_
+
+namespace extensions {
+namespace switches {
+
+// All switches in alphabetical order. The switches should be documented
+// alongside the definition of their values in the .cc file.
+extern const char kAppShellAllowRoaming[];
+extern const char kAppShellHostWindowSize[];
+extern const char kAppShellPreferredNetwork[];
+extern const char kAppShellRefreshToken[];
+extern const char kAppShellUser[];
+
+} // namespace switches
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_COMMON_SWITCHES_H_
diff --git a/chromium/extensions/shell/common/version.h.in b/chromium/extensions/shell/common/version.h.in
new file mode 100644
index 00000000000..c4627fe512a
--- /dev/null
+++ b/chromium/extensions/shell/common/version.h.in
@@ -0,0 +1,13 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// version.h is generated from version.h.in. Edit the source!
+
+#ifndef EXTENSIONS_SHELL_COMMON_VERSION_H_
+#define EXTENSIONS_SHELL_COMMON_VERSION_H_
+
+#define PRODUCT_VERSION "@MAJOR@.@MINOR@.@BUILD@.@PATCH@"
+#define LAST_CHANGE "@LASTCHANGE@"
+
+#endif // EXTENSIONS_SHELL_COMMON_VERSION_H_
diff --git a/chromium/extensions/shell/renderer/DEPS b/chromium/extensions/shell/renderer/DEPS
new file mode 100644
index 00000000000..bf14c1d80cd
--- /dev/null
+++ b/chromium/extensions/shell/renderer/DEPS
@@ -0,0 +1,9 @@
+include_rules = [
+ # Only allow includes the renderer can use.
+ "+components/nacl/common",
+ "+components/nacl/renderer",
+ "+content/public/renderer",
+ "+ppapi",
+ "+third_party/WebKit/public",
+ "+v8/include",
+]
diff --git a/chromium/extensions/shell/renderer/shell_content_renderer_client.cc b/chromium/extensions/shell/renderer/shell_content_renderer_client.cc
new file mode 100644
index 00000000000..92b71496687
--- /dev/null
+++ b/chromium/extensions/shell/renderer/shell_content_renderer_client.cc
@@ -0,0 +1,150 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/renderer/shell_content_renderer_client.h"
+
+#include "content/public/common/content_constants.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "content/public/renderer/render_frame_observer_tracker.h"
+#include "content/public/renderer/render_thread.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/dispatcher_delegate.h"
+#include "extensions/renderer/extension_frame_helper.h"
+#include "extensions/renderer/extension_helper.h"
+#include "extensions/renderer/guest_view/extensions_guest_view_container.h"
+#include "extensions/renderer/guest_view/extensions_guest_view_container_dispatcher.h"
+#include "extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.h"
+#include "extensions/shell/common/shell_extensions_client.h"
+#include "extensions/shell/renderer/shell_extensions_renderer_client.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+
+#if !defined(DISABLE_NACL)
+#include "components/nacl/common/nacl_constants.h"
+#include "components/nacl/renderer/nacl_helper.h"
+#endif
+
+using blink::WebFrame;
+using blink::WebString;
+using content::RenderThread;
+
+namespace extensions {
+
+ShellContentRendererClient::ShellContentRendererClient() {
+}
+
+ShellContentRendererClient::~ShellContentRendererClient() {
+}
+
+void ShellContentRendererClient::RenderThreadStarted() {
+ RenderThread* thread = RenderThread::Get();
+
+ extensions_client_.reset(CreateExtensionsClient());
+ ExtensionsClient::Set(extensions_client_.get());
+
+ extensions_renderer_client_.reset(new ShellExtensionsRendererClient);
+ ExtensionsRendererClient::Set(extensions_renderer_client_.get());
+
+ extension_dispatcher_delegate_.reset(new DispatcherDelegate());
+
+ // Must be initialized after ExtensionsRendererClient.
+ extension_dispatcher_.reset(
+ new Dispatcher(extension_dispatcher_delegate_.get()));
+ thread->AddObserver(extension_dispatcher_.get());
+
+ guest_view_container_dispatcher_.reset(
+ new ExtensionsGuestViewContainerDispatcher());
+ thread->AddObserver(guest_view_container_dispatcher_.get());
+}
+
+void ShellContentRendererClient::RenderFrameCreated(
+ content::RenderFrame* render_frame) {
+ // ExtensionFrameHelper destroys itself when the RenderFrame is destroyed.
+ new ExtensionFrameHelper(render_frame, extension_dispatcher_.get());
+ extension_dispatcher_->OnRenderFrameCreated(render_frame);
+
+ // TODO(jamescook): Do we need to add a new PepperHelper(render_frame) here?
+ // It doesn't seem necessary for either Pepper or NaCl.
+ // http://crbug.com/403004
+#if !defined(DISABLE_NACL)
+ new nacl::NaClHelper(render_frame);
+#endif
+}
+
+void ShellContentRendererClient::RenderViewCreated(
+ content::RenderView* render_view) {
+ new ExtensionHelper(render_view, extension_dispatcher_.get());
+}
+
+bool ShellContentRendererClient::OverrideCreatePlugin(
+ content::RenderFrame* render_frame,
+ blink::WebLocalFrame* frame,
+ const blink::WebPluginParams& params,
+ blink::WebPlugin** plugin) {
+ // Allow the content module to create the plugin.
+ return false;
+}
+
+blink::WebPlugin* ShellContentRendererClient::CreatePluginReplacement(
+ content::RenderFrame* render_frame,
+ const base::FilePath& plugin_path) {
+ // Don't provide a custom "failed to load" plugin.
+ return NULL;
+}
+
+bool ShellContentRendererClient::WillSendRequest(
+ blink::WebFrame* frame,
+ ui::PageTransition transition_type,
+ const GURL& url,
+ const GURL& first_party_for_cookies,
+ GURL* new_url) {
+ // TODO(jamescook): Cause an error for bad extension scheme requests?
+ return false;
+}
+
+bool ShellContentRendererClient::IsExternalPepperPlugin(
+ const std::string& module_name) {
+#if !defined(DISABLE_NACL)
+ // TODO(bbudge) remove this when the trusted NaCl plugin has been removed.
+ // We must defer certain plugin events for NaCl instances since we switch
+ // from the in-process to the out-of-process proxy after instantiating them.
+ return module_name == nacl::kNaClPluginName;
+#else
+ return false;
+#endif
+}
+
+bool ShellContentRendererClient::ShouldGatherSiteIsolationStats() const {
+ return false;
+}
+
+content::BrowserPluginDelegate*
+ShellContentRendererClient::CreateBrowserPluginDelegate(
+ content::RenderFrame* render_frame,
+ const std::string& mime_type,
+ const GURL& original_url) {
+ if (mime_type == content::kBrowserPluginMimeType) {
+ return new extensions::ExtensionsGuestViewContainer(render_frame);
+ } else {
+ return new extensions::MimeHandlerViewContainer(
+ render_frame, mime_type, original_url);
+ }
+}
+
+void ShellContentRendererClient::RunScriptsAtDocumentStart(
+ content::RenderFrame* render_frame) {
+ extension_dispatcher_->RunScriptsAtDocumentStart(render_frame);
+}
+
+void ShellContentRendererClient::RunScriptsAtDocumentEnd(
+ content::RenderFrame* render_frame) {
+ extension_dispatcher_->RunScriptsAtDocumentEnd(render_frame);
+}
+
+ExtensionsClient* ShellContentRendererClient::CreateExtensionsClient() {
+ return new ShellExtensionsClient;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/renderer/shell_content_renderer_client.h b/chromium/extensions/shell/renderer/shell_content_renderer_client.h
new file mode 100644
index 00000000000..639da37e63a
--- /dev/null
+++ b/chromium/extensions/shell/renderer/shell_content_renderer_client.h
@@ -0,0 +1,71 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_RENDERER_SHELL_CONTENT_RENDERER_CLIENT_H_
+#define EXTENSIONS_SHELL_RENDERER_SHELL_CONTENT_RENDERER_CLIENT_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/public/renderer/content_renderer_client.h"
+
+namespace extensions {
+
+class Dispatcher;
+class DispatcherDelegate;
+class ExtensionsClient;
+class ExtensionsGuestViewContainerDispatcher;
+class ShellExtensionsRendererClient;
+class ShellRendererMainDelegate;
+
+// Renderer initialization and runtime support for app_shell.
+class ShellContentRendererClient : public content::ContentRendererClient {
+ public:
+ ShellContentRendererClient();
+ ~ShellContentRendererClient() override;
+
+ // content::ContentRendererClient implementation:
+ void RenderThreadStarted() override;
+ void RenderFrameCreated(content::RenderFrame* render_frame) override;
+ void RenderViewCreated(content::RenderView* render_view) override;
+ bool OverrideCreatePlugin(content::RenderFrame* render_frame,
+ blink::WebLocalFrame* frame,
+ const blink::WebPluginParams& params,
+ blink::WebPlugin** plugin) override;
+ blink::WebPlugin* CreatePluginReplacement(
+ content::RenderFrame* render_frame,
+ const base::FilePath& plugin_path) override;
+ bool WillSendRequest(blink::WebFrame* frame,
+ ui::PageTransition transition_type,
+ const GURL& url,
+ const GURL& first_party_for_cookies,
+ GURL* new_url) override;
+ bool IsExternalPepperPlugin(const std::string& module_name) override;
+ bool ShouldGatherSiteIsolationStats() const override;
+ content::BrowserPluginDelegate* CreateBrowserPluginDelegate(
+ content::RenderFrame* render_frame,
+ const std::string& mime_type,
+ const GURL& original_url) override;
+ void RunScriptsAtDocumentStart(content::RenderFrame* render_frame) override;
+ void RunScriptsAtDocumentEnd(content::RenderFrame* render_frame) override;
+
+ protected:
+ // app_shell embedders may need custom extensions client interfaces.
+ // This class takes ownership of the returned object.
+ virtual ExtensionsClient* CreateExtensionsClient();
+
+ private:
+ scoped_ptr<ExtensionsClient> extensions_client_;
+ scoped_ptr<ShellExtensionsRendererClient> extensions_renderer_client_;
+ scoped_ptr<DispatcherDelegate> extension_dispatcher_delegate_;
+ scoped_ptr<Dispatcher> extension_dispatcher_;
+ scoped_ptr<ExtensionsGuestViewContainerDispatcher>
+ guest_view_container_dispatcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellContentRendererClient);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_RENDERER_SHELL_CONTENT_RENDERER_CLIENT_H_
diff --git a/chromium/extensions/shell/renderer/shell_extensions_renderer_client.cc b/chromium/extensions/shell/renderer/shell_extensions_renderer_client.cc
new file mode 100644
index 00000000000..c3be3507287
--- /dev/null
+++ b/chromium/extensions/shell/renderer/shell_extensions_renderer_client.cc
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/renderer/shell_extensions_renderer_client.h"
+
+namespace extensions {
+
+ShellExtensionsRendererClient::ShellExtensionsRendererClient() {
+}
+
+ShellExtensionsRendererClient::~ShellExtensionsRendererClient() {
+}
+
+bool ShellExtensionsRendererClient::IsIncognitoProcess() const {
+ // app_shell doesn't support off-the-record contexts.
+ return false;
+}
+
+int ShellExtensionsRendererClient::GetLowestIsolatedWorldId() const {
+ // app_shell doesn't need to reserve world IDs for anything other than
+ // extensions, so we always return 1. Note that 0 is reserved for the global
+ // world.
+ return 1;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/renderer/shell_extensions_renderer_client.h b/chromium/extensions/shell/renderer/shell_extensions_renderer_client.h
new file mode 100644
index 00000000000..aa72353fe73
--- /dev/null
+++ b/chromium/extensions/shell/renderer/shell_extensions_renderer_client.h
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_RENDERER_SHELL_EXTENSIONS_RENDERER_CLIENT_H_
+#define EXTENSIONS_SHELL_RENDERER_SHELL_EXTENSIONS_RENDERER_CLIENT_H_
+
+#include "base/macros.h"
+#include "extensions/renderer/extensions_renderer_client.h"
+
+namespace extensions {
+
+class ShellExtensionsRendererClient : public ExtensionsRendererClient {
+ public:
+ ShellExtensionsRendererClient();
+ ~ShellExtensionsRendererClient() override;
+
+ // ExtensionsRendererClient implementation.
+ bool IsIncognitoProcess() const override;
+ int GetLowestIsolatedWorldId() const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShellExtensionsRendererClient);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_RENDERER_SHELL_EXTENSIONS_RENDERER_CLIENT_H_
diff --git a/chromium/extensions/shell/utility/DEPS b/chromium/extensions/shell/utility/DEPS
new file mode 100644
index 00000000000..8ad521e4458
--- /dev/null
+++ b/chromium/extensions/shell/utility/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+content/public/utility",
+]
diff --git a/chromium/extensions/shell/utility/shell_content_utility_client.cc b/chromium/extensions/shell/utility/shell_content_utility_client.cc
new file mode 100644
index 00000000000..5fe8c573ac3
--- /dev/null
+++ b/chromium/extensions/shell/utility/shell_content_utility_client.cc
@@ -0,0 +1,23 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/utility/shell_content_utility_client.h"
+
+namespace extensions {
+
+ShellContentUtilityClient::ShellContentUtilityClient() {
+}
+
+ShellContentUtilityClient::~ShellContentUtilityClient() {
+}
+
+void ShellContentUtilityClient::UtilityThreadStarted() {
+ UtilityHandler::UtilityThreadStarted();
+}
+
+bool ShellContentUtilityClient::OnMessageReceived(const IPC::Message& message) {
+ return utility_handler_.OnMessageReceived(message);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/shell/utility/shell_content_utility_client.h b/chromium/extensions/shell/utility/shell_content_utility_client.h
new file mode 100644
index 00000000000..c98742733c2
--- /dev/null
+++ b/chromium/extensions/shell/utility/shell_content_utility_client.h
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_SHELL_UTILITY_SHELL_CONTENT_UTILITY_CLIENT_H_
+#define EXTENSIONS_SHELL_UTILITY_SHELL_CONTENT_UTILITY_CLIENT_H_
+
+#include "content/public/utility/content_utility_client.h"
+#include "extensions/utility/utility_handler.h"
+
+namespace extensions {
+
+class ShellContentUtilityClient : public content::ContentUtilityClient {
+ public:
+ ShellContentUtilityClient();
+ ~ShellContentUtilityClient() override;
+
+ // content::ContentUtilityClient:
+ void UtilityThreadStarted() override;
+ bool OnMessageReceived(const IPC::Message& message) override;
+
+ private:
+ UtilityHandler utility_handler_;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_SHELL_UTILITY_SHELL_CONTENT_UTILITY_CLIENT_H_
diff --git a/chromium/extensions/strings/BUILD.gn b/chromium/extensions/strings/BUILD.gn
new file mode 100644
index 00000000000..bd9833b3070
--- /dev/null
+++ b/chromium/extensions/strings/BUILD.gn
@@ -0,0 +1,72 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//tools/grit/grit_rule.gni")
+
+assert(enable_extensions)
+
+# GYP version: extensions/extensions_strings.gyp:extensions_strings
+grit("strings") {
+ # This target is in this directory since it matches the output grit path for
+ # the GYP version of this target. Weirdly, it does not match the input path.
+ # The .grd should probably be moved here.
+ source = "../extensions_strings.grd"
+ outputs = [
+ "grit/extensions_strings.h",
+ "extensions_strings_am.pak",
+ "extensions_strings_ar.pak",
+ "extensions_strings_bg.pak",
+ "extensions_strings_bn.pak",
+ "extensions_strings_ca.pak",
+ "extensions_strings_cs.pak",
+ "extensions_strings_da.pak",
+ "extensions_strings_de.pak",
+ "extensions_strings_el.pak",
+ "extensions_strings_en-GB.pak",
+ "extensions_strings_en-US.pak",
+ "extensions_strings_es.pak",
+ "extensions_strings_es-419.pak",
+ "extensions_strings_et.pak",
+ "extensions_strings_fa.pak",
+ "extensions_strings_fake-bidi.pak",
+ "extensions_strings_fi.pak",
+ "extensions_strings_fil.pak",
+ "extensions_strings_fr.pak",
+ "extensions_strings_gu.pak",
+ "extensions_strings_he.pak",
+ "extensions_strings_hi.pak",
+ "extensions_strings_hr.pak",
+ "extensions_strings_hu.pak",
+ "extensions_strings_id.pak",
+ "extensions_strings_it.pak",
+ "extensions_strings_ja.pak",
+ "extensions_strings_kn.pak",
+ "extensions_strings_ko.pak",
+ "extensions_strings_lt.pak",
+ "extensions_strings_lv.pak",
+ "extensions_strings_ml.pak",
+ "extensions_strings_mr.pak",
+ "extensions_strings_ms.pak",
+ "extensions_strings_nl.pak",
+ "extensions_strings_nb.pak",
+ "extensions_strings_pl.pak",
+ "extensions_strings_pt-BR.pak",
+ "extensions_strings_pt-PT.pak",
+ "extensions_strings_ro.pak",
+ "extensions_strings_ru.pak",
+ "extensions_strings_sk.pak",
+ "extensions_strings_sl.pak",
+ "extensions_strings_sr.pak",
+ "extensions_strings_sv.pak",
+ "extensions_strings_sw.pak",
+ "extensions_strings_ta.pak",
+ "extensions_strings_te.pak",
+ "extensions_strings_th.pak",
+ "extensions_strings_tr.pak",
+ "extensions_strings_uk.pak",
+ "extensions_strings_vi.pak",
+ "extensions_strings_zh-CN.pak",
+ "extensions_strings_zh-TW.pak",
+ ]
+}
diff --git a/chromium/extensions/strings/extensions_strings_am.xtb b/chromium/extensions/strings/extensions_strings_am.xtb
new file mode 100644
index 00000000000..c367611c42f
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_am.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="am">
+<translation id="1135328998467923690">ጥቅል ልክ የሆነ አይደለም፦ «<ph name="ERROR_CODE" />»።</translation>
+<translation id="1256619696651732561">ቅጥያ ገልጦ ማሳያ ተንታኝ</translation>
+<translation id="1445572445564823378">ይህ ቅጥያ <ph name="PRODUCT_NAME" /> እያንቀራፈፈው ነው። የ<ph name="PRODUCT_NAME" /> አፈጻጸም ወደነበረበት ለመመለስ ቅጥያውን ማሰናከል አለብዎት።</translation>
+<translation id="149347756975725155">የቅጥያ አዶ «<ph name="ICON" />»ን መጫን አልተቻለም።</translation>
+<translation id="1803557475693955505">የጀርባ ገጽ «<ph name="BACKGROUND_PAGE" />»ን መጫን አልተቻለም።</translation>
+<translation id="2159915644201199628">የዚህ ምስል ስውሩን መግለጥ አልተቻለም፦ «<ph name="IMAGE_NAME" />»</translation>
+<translation id="2350172092385603347">አካባቢያዊነት ተጠቅሟል ነገር ግን በማኒፌስት ወስጥ default_locale አልተገለጸም።</translation>
+<translation id="2753617847762399167">ህገወጥ ዱካ (በ«..» ፍጹማዊ ወይም አንጻራዊ)፦ «<ph name="IMAGE_PATH" />»</translation>
+<translation id="27822970480436970">ለውጡ ከሌላ ቅጥያ ጋር ስለተጋጨ ይህ ቅጥያ የአውታረ መረብ ጥያቄ መቀየር አልተሳካለትም።</translation>
+<translation id="2857834222104759979">ገላጭ ፋይሉ ልክ አይደለም።</translation>
+<translation id="2988488679308982380">ጥቅል መጫን አልተቻለም፦ «<ph name="ERROR_CODE" />»</translation>
+<translation id="3115238746683532089">ያልታወቀ ምርት <ph name="PRODUCT_ID" /> ከ<ph name="VENDOR_ID" /> ሻጭ (ተከታታይ ቁጥር <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">ያልታወቀ ምርት <ph name="PRODUCT_ID" /> ከ<ph name="VENDOR_ID" /> ሻጭ</translation>
+<translation id="3369521687965833290">ቅጥያ መበተን አይቻልም። አንድ ቅጥያ ደህንነቱ በተጠበቀ መልኩ ለመበተን በአንጻፊ ፊደሉ የሚጀምርና መገጣጠሚያ፣ የማፈናጠጫ ነጥብ ወይም ተምሳሌታዊ መጠሪያ ያልያዘ ወደ የመገለጫ ማውጫዎ የሚወስድ ዱካ መኖር አለበት። እንደዚህ ያሉ ዱካዎች ለመገለጫዎ የሉም።</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (ተከታታይ ቁጥር <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> ከ<ph name="VENDOR_ID" /> ሻጭ (ተከታታይ ቁጥር <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">የማስጀመሪያ ገጽ «<ph name="PAGE" />»ን መጫን አልተቻለም።</translation>
+<translation id="388442998277590542">የአማራጮች ገጽ «<ph name="OPTIONS_PAGE" />»ን መጫን አልተቻለም።</translation>
+<translation id="4115165561519362854">የዚህ ማሽን አስተዳዳሪ <ph name="EXTENSION_NAME" /> ቢያንስ የ<ph name="EXTENSION_VERSION" /> ስሪት እንዲሆን ይፈልጋሉል። ወደዚያ ስሪት (ወይም ከዚያ በላይ) ካልተዘመነ በስተቀር ሊነቃ አይችልም።</translation>
+<translation id="4233778200880751280">የስለ ገጹ «<ph name="ABOUT_PAGE" />» መጫን አልተቻለም።</translation>
+<translation id="4434145631756268951">{0,select, single{ዩኤስቢ መሣሪያን ይምረጡ}multiple{ዩኤስቢ መሣሪያዎችን ምረጡ}other{በጥቅም ላይ ያልዋለ}}</translation>
+<translation id="4811956658694082538">የመገልገያ ሂደት ስለተጨናገፈ ጥቅሉን መጫን አልተቻለም። Chromeን እንደገና አስጀምረው እንደገና ይሞክሩት።</translation>
+<translation id="5026754133087629784">የድር እይታ፦ <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">የመተግበሪያ እይታ፦ <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">ይህ ቅጥያ የቁልፍ ፋይል «<ph name="KEY_PATH" />»ን ያካትታል። ይህንን ማድረግ ላይፈልጉ ይችላሉ።</translation>
+<translation id="5627523580512561598">ቅጥያ <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{HID መሣሪያን ይምረጡ}multiple{HID መሣሪያዎችን ይምረጡ}other{በጥቅም ላይ ያልዋለ}}</translation>
+<translation id="5960890139610307736">ExtensionView፦ <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">የዚህ ማሽን አስተዳዳሪ <ph name="EXTENSION_NAME" /> እንዲጫን ይፈልጋል። ከተጫነ በኋላ ሊራገፍ አይችልም።</translation>
+<translation id="6027032947578871493">ያልታወቀ ምርት <ph name="PRODUCT_ID" /> ከ<ph name="VENDOR_NAME" /> (ተከታታይ ቁጥር <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> ከ<ph name="VENDOR_ID" /> ሻጭ</translation>
+<translation id="6143635259298204954">ቅጥያ መበተን አልተቻለም። አንድ ቅጥያ ደህንነቱ በተጠበቀ ሁኔታ ለመበተን ተምሳሌታዊ መጠሪያ ያልያዘ ወደ የመገለጫዎ አቃፊ የሚወስድ ዱካ መኖር አለበት። ምንም እንደዚህ ያለ ወደ መገለጫዎ የሚወስድ ዱካ የለም።</translation>
+<translation id="616804573177634438">{0,select, single{መተግበሪያ «<ph name="APP_NAME" />» የአንዱን መሣሪያዎን መዳረሻ እየጠየቀ ነው።}multiple{መተግበሪያ «<ph name="APP_NAME" />» የአንድ ወይም ተጨማሪ መሣሪያዎችዎን መዳረሻ እየጠየቀ ነው።}other{በጥቅም ላይ ያልዋለ}}</translation>
+<translation id="641087317769093025">ቅጥያውን መበተን አልተቻለም</translation>
+<translation id="657064425229075395">የጀርባ ስክሪፕት «<ph name="BACKGROUND_SCRIPT" />» መጫን አልተቻለም።</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> ከ<ph name="VENDOR_NAME" /> (የመለያ ቁጥር <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">ይሄ የሚበተንበት አቃፊ መፍጠር አልተቻለም፦ «<ph name="DIRECTORY_PATH" />»</translation>
+<translation id="677806580227005219">የሚሜ ተቆጣጣሪ፦ <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">ይህ ቅጥያ ብዙ ጊዜ እራሱን ዳግም ጭኗል።</translation>
+<translation id="7003844668372540529">ያልታወቀ ምርት <ph name="PRODUCT_ID" /> ከ<ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">የዚህ ማሽን አስተዳዳሪ <ph name="EXTENSION_NAME" /> እንዲጫን ይፈልጋል። ሊወገድ ወይም ሊቀየር አይችልም።</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (የቅጥያ መታወቂያ «<ph name="EXTENSION_ID" />») በአስተዳዳሪው ታግዷል።</translation>
+<translation id="7972881773422714442">አማራጮች፦ <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">ይህ ቅጥያ የወረደውን «<ph name="ATTEMPTED_FILENAME" />» ብሎ መሰየም አልተሳካለትም፣ ምክንያቱም ሌላ ቅጥያ (<ph name="EXTENSION_NAME" />) ሌላ የፋይል ስም «<ph name="ACTUAL_FILENAME" />» ስለወሰነ ነው።</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> ከ<ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">ይህ ቅጥያ ለአንድ የአውታረ መረብ ጥያቄ ምስክርነቶችን ማቅረብ አልቻለም ምክንያቱም ሌላ ቅጥያ (<ph name="EXTENSION_NAME" />) የተለዩ ምስክርነቶችን ስላቀረበ።</translation>
+<translation id="8602184400052594090">ገላጭ ፋይሉ ጠፍቷል ወይም ተነባቢ አይደለም።</translation>
+<translation id="8636666366616799973">ጥቅሉ ልክ አይደለም። ዝርዝሮች፦ «<ph name="ERROR_MESSAGE" />»።</translation>
+<translation id="8670869118777164560">ይህ ቅጥያ አንድ የአውታረ መረብ ጥያቄ ወደ <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> ማዞር አልተሳካለትም ምክንያቱም ሌላ ቅጥያ (<ph name="EXTENSION_NAME" />) ወደ <ph name="ACTUAL_REDIRECT_DESTINATION" /> አዙሮታል።</translation>
+<translation id="8712265948125780616">ቅጥያ ማራገፊያ</translation>
+<translation id="8825366169884721447">ይህ ቅጥያ የጥያቄው ርዕስ «<ph name="HEADER_NAME" />»ን መቀየር አልተሳካለትም ምክንያቱም ለውጡ ከሌላ ቅጥያ (<ph name="EXTENSION_NAME" />) ጋር ስለተጋጨ።</translation>
+<translation id="9111791539553342076">ይህ ቅጥያ የምላሽ ርዕሱን «<ph name="HEADER_NAME" />» መቀየር አልተሳካለትም ምክንያቱም ለውጡ ከሌላ ቅጥያ <ph name="EXTENSION_NAME" /> ጋር ስለተጋጨ።</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_ar.xtb b/chromium/extensions/strings/extensions_strings_ar.xtb
new file mode 100644
index 00000000000..937e376d69f
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_ar.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ar">
+<translation id="1135328998467923690">الحزمة غير صالحة: "<ph name="ERROR_CODE" />".</translation>
+<translation id="1256619696651732561">محلل بيان الإضافة اللغوي</translation>
+<translation id="1445572445564823378">تتسبب هذه الإضافة في بطء <ph name="PRODUCT_NAME" />. يجب تعطيلها لاستعادة أداء <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">تعذر تحميل رمز الإضافة '<ph name="ICON" />'.</translation>
+<translation id="1803557475693955505">تعذر تحميل صفحة الخلفية '<ph name="BACKGROUND_PAGE" />'.</translation>
+<translation id="2159915644201199628">تعذر فك تشفير الصورة: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">‏تم استخدام التعريب، ولكن لم يتم تحديد default_locale في البيان.</translation>
+<translation id="2753617847762399167">مسار غير قانوني (أساسي أو منتسب مع '..'): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">أخفقت هذه الإضافة في تعديل طلب الشبكة نظرًا لتعارض التعديل مع إضافة أخرى.</translation>
+<translation id="2857834222104759979">ملف البيان غير صالح.</translation>
+<translation id="2988488679308982380">تعذر تثبيت الحزمة: "<ph name="ERROR_CODE" />"</translation>
+<translation id="3115238746683532089">المنتج غير معروف <ph name="PRODUCT_ID" /> من المورّد <ph name="VENDOR_ID" /> (الرقم التسلسلي <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">المنتج غير معروف <ph name="PRODUCT_ID" /> من المورّد‬ <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">لا يمكن فك ضغط الإضافة. لفك ضغط الإضافة بشكل آمن، يجب أن يكون هناك مسار إلى دليل ملفك الشخصي، ويبدأ بحرف محرك أقراص ولا يحتوي على وصلة أو نقطة تحميل أو رابط رمزي. ليس هناك مثل هذا المسار لملفك الشخصي.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (الرقم التسلسلي <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> من المورّد <ph name="VENDOR_ID" /> (الرقم التسلسلي <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">‏تعذر تحميل صفحة launcher "<ph name="PAGE" />".</translation>
+<translation id="388442998277590542">تعذر تحميل صفحة الخيارات "<ph name="OPTIONS_PAGE" />".</translation>
+<translation id="4115165561519362854">يطلب مشرف هذا الجهاز <ph name="EXTENSION_NAME" /> الحصول على الحد الأدنى لإصدار <ph name="EXTENSION_VERSION" />. ولا يمكن تمكينه حتى يتم تحديثه إلى هذا الإصدار (أو أعلى).</translation>
+<translation id="4233778200880751280">تعذر تحميل حول الصفحة "<ph name="ABOUT_PAGE" />".</translation>
+<translation id="4434145631756268951">{0,select, single{‏تحديد جهاز USB}multiple{‏تحديد أجهزة USB}other{UNUSED}}</translation>
+<translation id="4811956658694082538">‏تعذر تثبيت الحزمة بسبب تعطل معالجة الأداة المساعدة. جرّب إعادة تشغيل Chrome وحاول مرة أخرى.</translation>
+<translation id="5026754133087629784">عرض الويب: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">عرض التطبيق: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">تتضمن هذه الإضافة ملف المفتاح '<ph name="KEY_PATH" />'. ربما لا تريد إجراء ذلك.</translation>
+<translation id="5627523580512561598">الإضافة <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{‏تحديد جهاز واجهة بشرية HID}multiple{‏تحديد أجهزة واجهة بشرية HID}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">يتطلب مشرف هذا الجهاز تثبيت <ph name="EXTENSION_NAME" />. لا يمكن إلغاء تثبيته.</translation>
+<translation id="6027032947578871493">المنتج غير معروف <ph name="PRODUCT_ID" /> من <ph name="VENDOR_NAME" /> (الرقم التسلسلي <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> من المورّد <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">لا يمكن فك ضغط الإضافة. لفك ضغط الإضافة بشكل آمن، يجب أن يكون هناك مسار إلى دليل ملفك الشخصي، ولا يحتوي على رابط رمزي. ليس هناك مثل هذا المسار لملفك الشخصي.</translation>
+<translation id="616804573177634438">{0,select, single{يطلب تطبيق "<ph name="APP_NAME" />" الدخول إلى واحد من أجهزتك.}multiple{يطلب تطبيق "<ph name="APP_NAME" />" الدخول إلى واحد أو أكثر من أجهزتك.}other{UNUSED}}</translation>
+<translation id="641087317769093025">تعذر فك ضغط الإضافة</translation>
+<translation id="657064425229075395">تعذر تحميل النص البرمجي للخلفية '<ph name="BACKGROUND_SCRIPT" />'.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> من <ph name="VENDOR_NAME" /> (الرقم التسلسلي <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">تعذر إنشاء دليل لفك ضغط: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">‏معالج Mime: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">تعيد هذه الإضافة تحميل نفسها باستمرار.</translation>
+<translation id="7003844668372540529">المنتج غير معروف <ph name="PRODUCT_ID" /> من <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">يطلب مشرف هذا الجهاز تثبيت <ph name="EXTENSION_NAME" />. لا يمكن إزالتها أو تعديلها.</translation>
+<translation id="7809034755304591547">تم حظر <ph name="EXTENSION_NAME" /> (معرف الإضافة "<ph name="EXTENSION_ID" />") من قِبل المشرف.</translation>
+<translation id="7972881773422714442">الخيارات: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">أخفقت هذه الإضافة في تسمية التنزيل "<ph name="ATTEMPTED_FILENAME" />" نظرًا لأن هناك إضافة أخرى (<ph name="EXTENSION_NAME" />) قد حددت اسم ملف آخر "<ph name="ACTUAL_FILENAME" />".</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> من <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">أخفقت هذه الإضافة في تقديم بيانات الاعتماد لطلب الشبكة، نظرًا لتقديم بيانات اعتماد مختلفة بواسطة إضافة أخرى (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="8602184400052594090">ملف البيان مفقود أو غير قابل للقراءة.</translation>
+<translation id="8636666366616799973">الحزمة غير صالحة. التفاصيل: "<ph name="ERROR_MESSAGE" />".</translation>
+<translation id="8670869118777164560">أخفقت الإضافة في إعادة توجيه طلب الشبكة إلى <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> نظرًا لإعادة توجيهها بواسطة إضافة أخرى (<ph name="EXTENSION_NAME" />) إلى <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">أداة فك ضغط الإضافات</translation>
+<translation id="8825366169884721447">أخفقت الإضافة في تعديل عنوان الطلب "<ph name="HEADER_NAME" />" لأحد طلبات الشبكة نظرًا لتعارض التعديل مع إضافة أخرى (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">أخفقت الإضافة في تعديل عنوان الاستجابة "<ph name="HEADER_NAME" />" لأحد طلبات الشبكة نظرًا لتعارض التعديل مع إضافة أخرى (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_bg.xtb b/chromium/extensions/strings/extensions_strings_bg.xtb
new file mode 100644
index 00000000000..e999db1e933
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_bg.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="bg">
+<translation id="1135328998467923690">Пакетът е невалиден: „<ph name="ERROR_CODE" />“.</translation>
+<translation id="1256619696651732561">Синтактичен анализ на манифести на разширения</translation>
+<translation id="1445572445564823378">Това разширение забавя работата на <ph name="PRODUCT_NAME" />. Трябва да деактивирате <ph name="PRODUCT_NAME" />, за да възстановите ефективността на браузъра.</translation>
+<translation id="149347756975725155">Не можа да се зареди иконата на разширението „<ph name="ICON" />“.</translation>
+<translation id="1803557475693955505">Не можа да се зареди фоновата страница „<ph name="BACKGROUND_PAGE" />“.</translation>
+<translation id="2159915644201199628">Изображението не можа да се декодира: „<ph name="IMAGE_NAME" />“</translation>
+<translation id="2350172092385603347">Беше използвана локализация, но променливата „default_locale“ не беше посочена в манифеста.</translation>
+<translation id="2753617847762399167">Непозволен път (абсолютен или относителен с (..): „<ph name="IMAGE_PATH" />“</translation>
+<translation id="27822970480436970">Това разширение не успя да промени заявка от мрежата, защото промяната влезе в конфликт с друго разширение.</translation>
+<translation id="2857834222104759979">Файлът на манифеста е невалиден.</translation>
+<translation id="2988488679308982380">Пакетът не можа да се инсталира: „<ph name="ERROR_CODE" />“</translation>
+<translation id="3115238746683532089">Неизвестен продукт <ph name="PRODUCT_ID" /> от доставчик <ph name="VENDOR_ID" /> (сериен номер <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Неизвестен продукт <ph name="PRODUCT_ID" /> от доставчик <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Разширението не може да се разпакетира. За да направите това по безопасен начин, трябва да има път към директорията в потребителския ви профил, който започва с буква на локален диск и не съдържа възел, точка на монтиране или символна връзка. Не съществува такъв път за потребителския ви профил.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (сериен номер <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> от доставчик <ph name="VENDOR_ID" /> (сериен номер <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Страницата „<ph name="PAGE" />“ в стартовия панел не можа да се зареди.</translation>
+<translation id="388442998277590542">Страницата за опции „<ph name="OPTIONS_PAGE" />“ не можа да се зареди.</translation>
+<translation id="4115165561519362854">Администраторът на тази машина е поставил изискване версията на <ph name="EXTENSION_NAME" /> да е поне <ph name="EXTENSION_VERSION" />. Разширението не може да бъде активирано, докато не бъде актуализирано до тази или по-нова версия.</translation>
+<translation id="4233778200880751280">Страницата с информация <ph name="ABOUT_PAGE" /> не можа да се зареди.</translation>
+<translation id="4434145631756268951">{0,select, single{Избор на USB устройство}multiple{Избор на USB устройства}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Пакетът не можа да се инсталира поради срив на помощен процес. Рестартирайте Chrome и опитайте отново.</translation>
+<translation id="5026754133087629784">Изглед в мрежата: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Изглед за приложението: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Разширението включва файла с ключ „<ph name="KEY_PATH" />“. Вероятно не искате да направите това.</translation>
+<translation id="5627523580512561598">разширението „<ph name="EXTENSION_NAME" />“</translation>
+<translation id="5630931906013276297">{0,select, single{Избор на HID устройство}multiple{Избор на HID устройства}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Администраторът на тази машина изисква разширението <ph name="EXTENSION_NAME" /> да е инсталирано. То не може да бъде деинсталирано.</translation>
+<translation id="6027032947578871493">Неизвестен продукт <ph name="PRODUCT_ID" /> от <ph name="VENDOR_NAME" /> (сериен номер <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> от доставчик <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Разширението не може да се разпакетира. За да направите това по безопасен начин, трябва да има път към директорията в потребителския ви профил, който не съдържа символна връзка. Не съществува такъв път за потребителския ви профил.</translation>
+<translation id="616804573177634438">{0,select, single{Приложението „<ph name="APP_NAME" />“ иска достъп до едно от устройствата ви.}multiple{Приложението „<ph name="APP_NAME" />“ иска достъп до едно или повече от устройствата ви.}other{UNUSED}}</translation>
+<translation id="641087317769093025">Разширението не можа да се разархивира</translation>
+<translation id="657064425229075395">Не можа да се зареди фоновият скрипт „<ph name="BACKGROUND_SCRIPT" />“.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> от <ph name="VENDOR_NAME" /> (сериен номер: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Директорията за разархивиране не можа да се създаде: „<ph name="DIRECTORY_PATH" />“</translation>
+<translation id="677806580227005219">Манипулатор на MIME: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Това разширение се презарежда твърде често.</translation>
+<translation id="7003844668372540529">Неизвестен продукт <ph name="PRODUCT_ID" /> от <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Администраторът на тази машина изисква разширението <ph name="EXTENSION_NAME" /> да е инсталирано. То не може да бъде премахнато или променено.</translation>
+<translation id="7809034755304591547">Разширението <ph name="EXTENSION_NAME" /> (идентификационен номер „<ph name="EXTENSION_ID" />“) е блокирано от администратора.</translation>
+<translation id="7972881773422714442">Опции: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Това разширение не успя да наименува изтеглянето „<ph name="ATTEMPTED_FILENAME" />“, защото друго разширение (<ph name="EXTENSION_NAME" />) определи различно файлово име – „<ph name="ACTUAL_FILENAME" />“.</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> от <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Това разширение не успя да предостави идентификационни данни за заявка от мрежата, защото друго разширение (<ph name="EXTENSION_NAME" />) посочи различни данни.</translation>
+<translation id="8602184400052594090">Файлът на манифеста липсва или не може да бъде прочетен.</translation>
+<translation id="8636666366616799973">Пакетът е невалиден. Подробности: „<ph name="ERROR_MESSAGE" />“.</translation>
+<translation id="8670869118777164560">Това разширение не успя да пренасочи заявка от мрежата към <ph name="ATTEMPTED_REDIRECT_DESTINATION" />, защото друго разширение (<ph name="EXTENSION_NAME" />) я пренасочи към <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Разпакетиране на разширения</translation>
+<translation id="8825366169884721447">Това разширение не успя да промени заглавката „<ph name="HEADER_NAME" />“ на заявка от мрежата, защото промяната влезе в конфликт с друго разширение (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Това разширение не успя да промени заглавката на отговор „<ph name="HEADER_NAME" />“ на заявка от мрежата, защото промяната влезе в конфликт с друго разширение (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_bn.xtb b/chromium/extensions/strings/extensions_strings_bn.xtb
new file mode 100644
index 00000000000..5b4824fd9bc
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_bn.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="bn">
+<translation id="1135328998467923690">এই প্যাকেজটি অবৈধ: '<ph name="ERROR_CODE" />'৷</translation>
+<translation id="1256619696651732561">এক্সটেনশান ম্যানিফেস্ট বিশ্লেষক</translation>
+<translation id="1445572445564823378">এই এক্সটেনশানটির গতি কমে যাচ্ছে <ph name="PRODUCT_NAME" />৷ আপনাকে <ph name="PRODUCT_NAME" />-এর সম্পাদনা পুনঃস্থাপন করার জন্য এটিকে অক্ষম করা উচিত৷</translation>
+<translation id="149347756975725155">এক্সটেনশান আইকন '<ph name="ICON" />' লোড করা যায়নি৷</translation>
+<translation id="1803557475693955505">পৃষ্ঠভূমি পৃষ্ঠা '<ph name="BACKGROUND_PAGE" />' লোড করা যায়নি৷</translation>
+<translation id="2159915644201199628">চিত্র ডিকোড করা যায়নি: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">স্থানীয়করণ ব্যবহৃত হয়েছে, কিন্তু default_locale তালিকাতে উল্লেখ ছিল না৷</translation>
+<translation id="2753617847762399167">আইনি পথ (পূর্ণ অথবা '..' এর সাথে সম্পর্কিত: '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">এই এক্সটেনশানটি নেটওয়ার্কের অনুরোধ সংশোধন করতে ব্যর্থ হয়েছে কারণ সংশোধনের অন্য এক্সটেনশানের সঙ্গে বিরোধ হয়েছে৷</translation>
+<translation id="2857834222104759979">তালিকা ফাইল অবৈধ৷</translation>
+<translation id="2988488679308982380">এই প্যাকেজটি ইনস্টল করতে পারেনি: '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089"><ph name="VENDOR_ID" /> বিক্রেতার থেকেঅজানা পণ্য <ph name="PRODUCT_ID" /> (সিরিয়াল নম্বর <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963"><ph name="VENDOR_ID" /> বিক্রেতার থেকে অজানা পণ্য <ph name="PRODUCT_ID" /></translation>
+<translation id="3369521687965833290">এক্সটেনশন প্যাকমুক্ত করতে পারেনা৷ কোনও এক্সটেনশনকে সুরক্ষিতভাবে প্যাকমুক্ত করতে, আপনার প্রোফাইল ডিরেক্টরিতে অবশ্যই এমন একটি পথ থাকবে যা চালক অক্ষরটি দিয়ে শুরু হবে এবং এতে কোন জাংশন, মাউন্ট পয়েন্ট বা সিমলিঙ্ক থাকবে না৷ আপনার প্রোফাইলে এ জাতীয় কোনও পথ বিদ্যমান নেই৷</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (সিরিয়াল নম্বর <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="VENDOR_ID" /> বিক্রেতার থেকে <ph name="PRODUCT_NAME" /> (সিরিয়াল নম্বর <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">লঞ্চার পৃষ্ঠা '<ph name="PAGE" />' লোড করা যায়নি৷</translation>
+<translation id="388442998277590542">বিকল্প পৃষ্ঠা '<ph name="OPTIONS_PAGE" />' লোড করা যায়নি৷</translation>
+<translation id="4115165561519362854"><ph name="EXTENSION_VERSION" /> এর কোনো সর্বনিম্ন সংস্করণ রাখতে, এই মেশিনের প্রশাসকের জন্য <ph name="EXTENSION_NAME" /> প্রয়োজন৷ এটিকে যতক্ষণ না পর্যন্ত সেই সংস্করণে আপডেট করা হচ্ছে (বা উচ্চতর) এটি সক্ষমিত হবে না৷</translation>
+<translation id="4233778200880751280"><ph name="ABOUT_PAGE" /> সম্পর্কিত পৃষ্ঠা লোড করা গেল না।</translation>
+<translation id="4434145631756268951">{0,select, single{একটি USB ডিভাইস নির্বাচন করুন}multiple{USB ডিভাইসগুলি নির্বাচন করুন}other{UNUSED}}</translation>
+<translation id="4811956658694082538">একটি ইউটিলিটি প্রক্রিয়া ক্র্যাশ করার কারণে প্যাকেজটি ইনস্টল করা যায়নি। Chrome কে পুনরায় চালু করার চেষ্টা করুন এবং আবার চেষ্টা করুন।</translation>
+<translation id="5026754133087629784">ওয়েবদর্শন: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">অ্যাপ্লিকেশান দর্শন: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">এই এক্সটেনশানটি '<ph name="KEY_PATH" />' মুখ্য ফাইলকে অন্তর্ভুক্ত করে৷ আপনি সম্ভবত এটি করতে চাইবেন না৷</translation>
+<translation id="5627523580512561598">এক্সটেনশান <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{একটি HID ডিভাইস নির্বাচন করুন}multiple{HID ডিভাইসগুলি নির্বাচন করুন}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">এই মেশিনের প্রশাসকের <ph name="EXTENSION_NAME" /> ইন্সটল করা প্রয়োজন। এটি আনইন্সটল করা যাবে না।</translation>
+<translation id="6027032947578871493"><ph name="VENDOR_NAME" /> এর থেকে অজানা পণ্য <ph name="PRODUCT_ID" /> (সিরিয়াল নম্বর <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="VENDOR_ID" /> বিক্রেতার থেকে <ph name="PRODUCT_NAME" /></translation>
+<translation id="6143635259298204954">এক্সটেনশনটি প্যাকমুক্ত করতে পারে না৷ নিরাপদে কোনও এক্সটেনশন প্যাকমুক্ত করতে, আপনার প্রোফাইল ডিরেক্টরিতে অবশ্যই এমন একটি পথ থাকতে হবে যেখানে কোনও সিমলিঙ্ক থাকে না৷ এই জাতীয় কোনও পথ আপনার প্রোফাইলে বিদ্যমান নেই৷</translation>
+<translation id="616804573177634438">{0,select, single{"<ph name="APP_NAME" />" অ্যাপ্লিকেশানটি আপনার কোনো একটি ডিভাইসে অ্যাক্সেস করার জন্য অনুরোধ করছে।}multiple{"<ph name="APP_NAME" />" অ্যাপ্লিকেশানটি আপনার কোনো একটি বা একাধিক ডিভাইসে অ্যাক্সেস করার জন্য অনুরোধ করছে।}other{অব্যবহৃত}}</translation>
+<translation id="641087317769093025">এক্সটেনশান আনজিপ করা যায়নি</translation>
+<translation id="657064425229075395">পশ্চাদপট লিপি '<ph name="BACKGROUND_SCRIPT" />' লোড করা যায়নি৷</translation>
+<translation id="6580950983454333167"><ph name="VENDOR_NAME" /> এর পক্ষ থেকে <ph name="PRODUCT_NAME" /> (ক্রমিক সংখ্যা <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">এটিকে আনজিপ করার জন্য ডিরেক্টরি তৈরি করা যায়নি: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">মাইমহ্যান্ডলার: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">এই এক্সটেনশানটি খুব ঘন ঘন নিজের থেকে পুনরায় লোড হয়৷</translation>
+<translation id="7003844668372540529"><ph name="VENDOR_NAME" /> এর থেকে অজানা পণ্য <ph name="PRODUCT_ID" /></translation>
+<translation id="7217838517480956708">এই মেশিনের প্রশাসকের <ph name="EXTENSION_NAME" /> ইনস্টল করার প্রয়োজন৷ এটি সরানো অথবা সংশোধন করা যাবে না৷</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (এক্সটেশন ID "<ph name="EXTENSION_ID" />") টি প্রশাসকের দ্বারা অবরুদ্ধ করা আছে৷</translation>
+<translation id="7972881773422714442">বিকল্পগুলি: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">এই এক্সটেনশানটি ডাউনলোড "<ph name="ATTEMPTED_FILENAME" />" এর নাম দিতে ব্যর্থ হয়েছে কারণ অন্য এক্সটেনশান (<ph name="EXTENSION_NAME" />) একটি ভিন্ন ফাইল নাম "<ph name="ACTUAL_FILENAME" />" নির্ধারণ করেছে৷</translation>
+<translation id="8284835137979141223"><ph name="VENDOR_NAME" /> এর <ph name="PRODUCT_NAME" /></translation>
+<translation id="8341840687457896278">এই এক্সটেনশনটি একটি নেটওয়ার্ক অনুরোধের শংসাপত্রগুলি প্রদান করতে ব্যর্থ হয়েছে কারণ অন্য এক্সটেনশন (<ph name="EXTENSION_NAME" />) ভিন্ন শংসাপত্রগুলি প্রদান করেছে৷</translation>
+<translation id="8602184400052594090">তালিকা ফাইল পাওয়া যাচ্ছে না অথবা পঠনযোগ্য৷</translation>
+<translation id="8636666366616799973">প্যাকেজটি অবৈধ৷ বিশদ বিবরণ: '<ph name="ERROR_MESSAGE" />'৷</translation>
+<translation id="8670869118777164560">এই এক্সটেনশনটি <ph name="ATTEMPTED_REDIRECT_DESTINATION" />এ একটি নেটওয়ার্কের পুনঃনির্দেশ অনুরোধ করতে ব্যর্থ হয়েছে কারণ অন্য এক্সটেনশন (<ph name="EXTENSION_NAME" />) এটিকে <ph name="ACTUAL_REDIRECT_DESTINATION" /> এ পুনঃনির্দেশ করেছে৷</translation>
+<translation id="8712265948125780616">এক্সটেনশান আনপ্যাকার</translation>
+<translation id="8825366169884721447">এই এক্সটেনশনটি একটি নেটওয়ার্ক অনুরোধের অনুরোধ শীর্ষক "<ph name="HEADER_NAME" />" এর সংশোধন করতে ব্যর্থ হয়েছে কারণ সংশোধনটির অন্য এক্সটেনশন (<ph name="EXTENSION_NAME" />) এর সঙ্গে বিরোধ আছে৷</translation>
+<translation id="9111791539553342076">এই এক্সটেনশনটি একটি নেটওয়ার্ক অনুরোধের প্রতিক্রিয়া শীর্ষক "<ph name="HEADER_NAME" />" এর সংশোধন করতে ব্যর্থ হয়েছে কারণ সংশোধনটির অন্য এক্সটেনশন (<ph name="EXTENSION_NAME" />) এর সঙ্গে বিরোধ রয়েছে৷</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_ca.xtb b/chromium/extensions/strings/extensions_strings_ca.xtb
new file mode 100644
index 00000000000..49ff423d889
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_ca.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ca">
+<translation id="1135328998467923690">El paquet no és vàlid: "<ph name="ERROR_CODE" />".</translation>
+<translation id="1256619696651732561">Analitzador per a manifests d'extensions</translation>
+<translation id="1445572445564823378">Aquesta extensió està alentint <ph name="PRODUCT_NAME" />. L'heu de desactivar per restaurar el rendiment de: <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">No s'ha pogut carregar la icona d'extensió "<ph name="ICON" />".</translation>
+<translation id="1803557475693955505">No s'ha pogut carregar la pàgina en segon pla "<ph name="BACKGROUND_PAGE" />".</translation>
+<translation id="2159915644201199628">No s'ha pogut descodificar la imatge: "<ph name="IMAGE_NAME" />"</translation>
+<translation id="2350172092385603347">S'ha utilitzat localització, però no s'ha especificat default_locale al manifest.</translation>
+<translation id="2753617847762399167">Camí il·legal (absolut o relatiu amb ".."): "<ph name="IMAGE_PATH" />"</translation>
+<translation id="27822970480436970">Aquesta extensió no ha pogut modificar una sol·licitud de xarxa perquè la modificació està en conflicte amb una altra extensió.</translation>
+<translation id="2857834222104759979">El fitxer de manifest no és vàlid.</translation>
+<translation id="2988488679308982380">No s'ha pogut instal·lar el paquet: "<ph name="ERROR_CODE" />"</translation>
+<translation id="3115238746683532089">Producte desconegut <ph name="PRODUCT_ID" /> del proveïdor <ph name="VENDOR_ID" /> (número de sèrie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Producte desconegut <ph name="PRODUCT_ID" /> del proveïdor <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">No es pot desempaquetar l'extensió. Per desempaquetar l'extensió amb seguretat, cal un camí al directori del perfil que comenci amb la lletra d'una unitat i no contingui cap unió, punt de muntatge o enllaç simbòlic. No hi ha cap camí d'aquestes característiques al vostre perfil.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (número de sèrie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> del proveïdor <ph name="VENDOR_ID" /> (número de sèrie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">No s'ha pogut carregar la pàgina del menú <ph name="PAGE" />.</translation>
+<translation id="388442998277590542">No s'ha pogut carregar la pàgina d'opcions "<ph name="OPTIONS_PAGE" />".</translation>
+<translation id="4115165561519362854">L'administrador d'aquest ordinador requereix que <ph name="EXTENSION_NAME" /> tingui com a mínim la versió <ph name="EXTENSION_VERSION" />. No es pot activar fins que no s'hagi actualitzat a aquesta versió (o posterior).</translation>
+<translation id="4233778200880751280">No s'ha pogut carregar la pàgina d'informació "<ph name="ABOUT_PAGE" />".</translation>
+<translation id="4434145631756268951">{0,select, single{Seleccioneu un dispositiu USB}multiple{Seleccioneu dispositius USB}other{}}</translation>
+<translation id="4811956658694082538">No s'ha pogut instal·lar el paquet perquè un dels processos d'utilitat ha fallat. Reinicieu Chrome i torneu-ho a provar.</translation>
+<translation id="5026754133087629784">Visualització del web: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Vista de l'aplicació: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Aquesta extensió inclou el fitxer de clau "<ph name="KEY_PATH" />". És probable que això no sigui convenient.</translation>
+<translation id="5627523580512561598">extensió <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Seleccioneu un dispositiu HID}multiple{Seleccioneu dispositius HID}other{}}</translation>
+<translation id="5960890139610307736">Visualització de l'extensió: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">L'administrador d'aquest dispositiu requereix que l'extensió <ph name="EXTENSION_NAME" /> estigui instal·lada. No es pot desinstal·lar.</translation>
+<translation id="6027032947578871493">Producte desconegut <ph name="PRODUCT_ID" /> de: <ph name="VENDOR_NAME" /> (número de sèrie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> del proveïdor <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">No es pot desempaquetar l'extensió. Per desempaquetar l'extensió amb seguretat, cal un camí al directori del perfil que no contingui un enllaç simbòlic. No hi ha cap camí d'aquestes característiques al vostre perfil.</translation>
+<translation id="616804573177634438">{0,select, single{L'aplicació "<ph name="APP_NAME" />" sol·licita accés a un dels vostres dispositius.}multiple{L'aplicació "<ph name="APP_NAME" />" sol·licita accés a almenys un dels vostres dispositius.}other{UNUSED}}</translation>
+<translation id="641087317769093025">No s'ha pogut descomprimir l'extensió</translation>
+<translation id="657064425229075395">No s'ha pogut carregar l'script en segon pla "<ph name="BACKGROUND_SCRIPT" />".</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> de <ph name="VENDOR_NAME" /> (número de sèrie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">No s'ha pogut crear un directori per descomprimir: "<ph name="DIRECTORY_PATH" />"</translation>
+<translation id="677806580227005219">Gestor MIME: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Aquesta extensió s'ha tornat a carregar massa vegades.</translation>
+<translation id="7003844668372540529">Producte desconegut <ph name="PRODUCT_ID" /> de: <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">L'administrador d'aquest equip necessita que s'instal·li <ph name="EXTENSION_NAME" />. No es pot eliminar o modificar.</translation>
+<translation id="7809034755304591547">L'administrador ha bloquejat l'extensió <ph name="EXTENSION_NAME" /> (amb l'ID d'extensió <ph name="EXTENSION_ID" />).</translation>
+<translation id="7972881773422714442">Opcions: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Aquesta extensió no ha pogut posar nom a la baixada "<ph name="ATTEMPTED_FILENAME" />" perquè una altra extensió (<ph name="EXTENSION_NAME" />) ha indicat un nom de fitxer diferent "<ph name="ACTUAL_FILENAME" />".</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> proporcionat per <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Aquesta extensió no ha pogut proporcionar credencials a una sol·licitud de xarxa perquè una altra extensió (<ph name="EXTENSION_NAME" />) ha proporcionat unes credencials diferents.</translation>
+<translation id="8602184400052594090">El fitxer de manifest falta o bé no es pot llegir.</translation>
+<translation id="8636666366616799973">El paquet no és vàlid. Detalls: "<ph name="ERROR_MESSAGE" />".</translation>
+<translation id="8670869118777164560">Aquesta extensió no ha pogut redirigir una sol·licitud de xarxa a <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> perquè una altra extensió (<ph name="EXTENSION_NAME" />) l'ha redirigida a <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Eina per desempaquetar extensions</translation>
+<translation id="8825366169884721447">Aquesta extensió no ha pogut modificar la capçalera de la sol·licitud "<ph name="HEADER_NAME" />" d'una sol·licitud de xarxa perquè la modificació entrava en conflicte amb una altra extensió (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Aquesta extensió no ha pogut modificar la capçalera de resposta "<ph name="HEADER_NAME" />" d'una sol·licitud de xarxa perquè la modificació entrava en conflicte amb una altra extensió (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_cs.xtb b/chromium/extensions/strings/extensions_strings_cs.xtb
new file mode 100644
index 00000000000..fa8f358b307
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_cs.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="cs">
+<translation id="1135328998467923690">Balíček je neplatný: <ph name="ERROR_CODE" />.</translation>
+<translation id="1256619696651732561">Analyzátor manifestů rozšíření</translation>
+<translation id="1445572445564823378">Toto rozšíření zpomaluje prohlížeč <ph name="PRODUCT_NAME" />. Chcete-li výkon prohlížeče <ph name="PRODUCT_NAME" /> obnovit, měli byste rozšíření zakázat.</translation>
+<translation id="149347756975725155">Nelze načíst ikonu rozšíření <ph name="ICON" />.</translation>
+<translation id="1803557475693955505">Nelze načíst stránku pozadí „<ph name="BACKGROUND_PAGE" />“.</translation>
+<translation id="2159915644201199628">Nepodařilo se dekódovat obrázek: <ph name="IMAGE_NAME" /></translation>
+<translation id="2350172092385603347">Byla použita lokalizace, ale v manifestu nebyl zadán parametr default_locale.</translation>
+<translation id="2753617847762399167">Neplatná cesta (absolutní nebo relativní s „..“): <ph name="IMAGE_PATH" /></translation>
+<translation id="27822970480436970">Tomuto rozšíření se nepodařilo upravit požadavek sítě, protože úprava byla v konfliktu s dalším rozšířením.</translation>
+<translation id="2857834222104759979">Soubor manifestu je neplatný.</translation>
+<translation id="2988488679308982380">Nepodařilo se nainstalovat balíček: <ph name="ERROR_CODE" /></translation>
+<translation id="3115238746683532089">Neznámý produkt <ph name="PRODUCT_ID" /> od dodavatele <ph name="VENDOR_ID" /> (sériové číslo <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Neznámý produkt <ph name="PRODUCT_ID" /> od dodavatele <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Rozšíření nelze rozbalit. Chcete-li rozšíření rozbalit bezpečně, musíte zadat cestu k adresáři profilu, která začíná písmenem diskové jednotky a neobsahuje spojení, přípojný bod ani symbolický odkaz symlink. Ve vašem profilu taková cesta neexistuje.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (sériové číslo <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> od dodavatele <ph name="VENDOR_ID" /> (sériové číslo <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Stránku spouštěče <ph name="PAGE" /> nelze načíst.</translation>
+<translation id="388442998277590542">Nelze načíst stránku možností „<ph name="OPTIONS_PAGE" />“.</translation>
+<translation id="4115165561519362854">Správce tohoto zařízení vyžaduje, aby rozšíření <ph name="EXTENSION_NAME" /> mělo verzi alespoň <ph name="EXTENSION_VERSION" />. Dokud nebude aktualizováno na tuto (nebo vyšší) verzi, nelze jej povolit.</translation>
+<translation id="4233778200880751280">Načtení stránky s informacemi <ph name="ABOUT_PAGE" /> se nezdařilo.</translation>
+<translation id="4434145631756268951">{0,select, single{Výběr zařízení USB}multiple{Výběr zařízení USB}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Balíček se nepodařilo nainstalovat, protože proces nástroje selhal. Restartujte Chrome a zkuste to znovu.</translation>
+<translation id="5026754133087629784">Webview: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Zobrazení aplikace: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Rozšíření obsahuje soubor klíče <ph name="KEY_PATH" />. Tuto akci pravděpodobně provést nechcete.</translation>
+<translation id="5627523580512561598">rozšíření <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Výběr zařízení HID}multiple{Výběr zařízení HID}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Administrátor tohoto zařízení vyžaduje, aby bylo rozšíření <ph name="EXTENSION_NAME" /> nainstalováno. Toto rozšíření nelze odinstalovat.</translation>
+<translation id="6027032947578871493">Neznámý produkt <ph name="PRODUCT_ID" /> od dodavatele <ph name="VENDOR_NAME" /> (sériové číslo <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> od dodavatele <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Rozšíření nelze rozbalit. Chcete-li je rozbalit bezpečně, musíte zadat cestu k adresáři profilu, která neobsahuje odkaz symlink. Ve vašem profilu taková cesta neexistuje.</translation>
+<translation id="616804573177634438">{0,select, single{Aplikace <ph name="APP_NAME" /> žádá o přístup k jednomu z vašich zařízení.}multiple{Aplikace <ph name="APP_NAME" /> žádá o přístup k jednomu nebo několika vašim zařízením.}other{UNUSED}}</translation>
+<translation id="641087317769093025">Rozšíření se nepodařilo rozbalit</translation>
+<translation id="657064425229075395">Nelze načíst skript pozadí <ph name="BACKGROUND_SCRIPT" />.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> od dodavatele <ph name="VENDOR_NAME" /> (sériové číslo <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Nepodařilo se vytvořit adresář pro rozbalení: <ph name="DIRECTORY_PATH" /></translation>
+<translation id="677806580227005219">Obslužná rutina typu MIME: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Toto rozšíření se obnovovalo příliš často.</translation>
+<translation id="7003844668372540529">Neznámý produkt <ph name="PRODUCT_ID" /> od dodavatele <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Správce tohoto počítače vyžaduje instalaci rozšíření <ph name="EXTENSION_NAME" />. Nelze je odebrat ani upravit.</translation>
+<translation id="7809034755304591547">Rozšíření <ph name="EXTENSION_NAME" /> (ID rozšíření <ph name="EXTENSION_ID" />) je blokováno administrátorem.</translation>
+<translation id="7972881773422714442">Možnosti: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Tomuto rozšíření se nepodařilo pojmenovat stahovaný soubor „<ph name="ATTEMPTED_FILENAME" />“, protože jiné rozšíření (<ph name="EXTENSION_NAME" />) vybralo jiný název souboru „<ph name="ACTUAL_FILENAME" />“.</translation>
+<translation id="8284835137979141223">Zařízení <ph name="PRODUCT_NAME" /> od dodavatele <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Tomuto rozšíření se nepodařilo poskytnout pověření síťovému požadavku, protože jiné rozšíření (<ph name="EXTENSION_NAME" />) poskytlo odlišná pověření.</translation>
+<translation id="8602184400052594090">Chybí soubor manifestu nebo jej nelze číst.</translation>
+<translation id="8636666366616799973">Balíček je neplatný. Podrobnosti: <ph name="ERROR_MESSAGE" /></translation>
+<translation id="8670869118777164560">Tomuto rozšíření se nepodařilo přesměrovat požadavek sítě do umístění <ph name="ATTEMPTED_REDIRECT_DESTINATION" />, protože jej jiné rozšíření (<ph name="EXTENSION_NAME" />) přesměrovalo do umístění <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Rozbalovač rozšíření</translation>
+<translation id="8825366169884721447">Tomuto rozšíření se nepodařilo změnit záhlaví <ph name="HEADER_NAME" /> síťového požadavku, protože změna byla v konfliktu s jiným rozšířením (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Tomuto rozšíření se nepodařilo změnit záhlaví odpovědi <ph name="HEADER_NAME" /> síťového požadavku, protože změna byla v konfliktu s jiným rozšířením (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_da.xtb b/chromium/extensions/strings/extensions_strings_da.xtb
new file mode 100644
index 00000000000..9a339c43ca2
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_da.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="da">
+<translation id="1135328998467923690">Pakken er ugyldig: "<ph name="ERROR_CODE" />".</translation>
+<translation id="1256619696651732561">Værktøj til parsing af udvidelsesmanifester</translation>
+<translation id="1445572445564823378">Denne udvidelse gør <ph name="PRODUCT_NAME" /> langsommere. Du bør deaktivere den for at gøre <ph name="PRODUCT_NAME" /> hurtig igen.</translation>
+<translation id="149347756975725155">Udvidelsesikonet '<ph name="ICON" />' kunne ikke indlæses.</translation>
+<translation id="1803557475693955505">Baggrundssiden '<ph name="BACKGROUND_PAGE" />' kunne ikke indlæses.</translation>
+<translation id="2159915644201199628">Billedet kunne afkodes: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">Lokalisering anvendt, men default_locale blev ikke angivet i manifestet.</translation>
+<translation id="2753617847762399167">Ugyldig sti (absolut eller relativ med '..'): '<ph name="IMAGE_PATH" /> '</translation>
+<translation id="27822970480436970">Denne udvidelse kunne ikke ændre en netværksanmodning, fordi ændringen var i strid med en anden udvidelse.</translation>
+<translation id="2857834222104759979">Manifestfilen er ugyldig.</translation>
+<translation id="2988488679308982380">Pakken kunne ikke installeres: "<ph name="ERROR_CODE" />"</translation>
+<translation id="3115238746683532089">Ukendt produkt, <ph name="PRODUCT_ID" />, fra leverandøren <ph name="VENDOR_ID" /> (serienummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Ukendt produkt, <ph name="PRODUCT_ID" />, fra leverandøren <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Udvidelsen kunne ikke pakkes ud. Hvis du vil udpakke en udvidelse korrekt, skal der angives en sti til din profilmappe. Denne sti skal starte med et drevbogstav, og den må ikke indeholde et forbindelsespunkt, monteringspunkt eller symlink. Der findes ingen sådan sti for din profil.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (serienummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> fra leverandøren <ph name="VENDOR_ID" /> (serienummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Startsiden "<ph name="PAGE" />" kunne ikke indlæses.</translation>
+<translation id="388442998277590542">Siden med valgmuligheder '<ph name="OPTIONS_PAGE" />' kunne ikke indlæses.</translation>
+<translation id="4115165561519362854">Administratoren af denne computer kræver, at <ph name="EXTENSION_NAME" /> har en minimumsversion af <ph name="EXTENSION_VERSION" />. Den kan ikke aktiveres, før den er blevet opdateret til den pågældende version (eller nyere).</translation>
+<translation id="4233778200880751280">Siden "<ph name="ABOUT_PAGE" />" kunne ikke indlæses.</translation>
+<translation id="4434145631756268951">{0,select, single{Vælg en USB-enhed}multiple{Vælg USB-enheder}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Pakken kunne ikke installeres, da et hjælpeprogram gik ned. Prøv at genstarte Chrome, og prøv igen.</translation>
+<translation id="5026754133087629784">Webvisning: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appvisning: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Denne udvidelse inkluderer nøglefilen "<ph name="KEY_PATH" />". Det ønsker du sandsynligvis ikke at gøre.</translation>
+<translation id="5627523580512561598">udvidelse <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Vælg en HID-enhed}multiple{Vælg HID-enheder}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Administratoren af denne maskine kræver, at <ph name="EXTENSION_NAME" /> er installeret. Denne kan ikke afinstalleres.</translation>
+<translation id="6027032947578871493">Ukendt produkt, <ph name="PRODUCT_ID" />, fra <ph name="VENDOR_NAME" /> (serienummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> fra leverandøren <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Udvidelsen kunne ikke pakkes ud. Hvis du vil udpakke en udvidelse korrekt, skal der angives en sti til din profilmappe, som ikke indeholder et symlink. Der findes ingen sådan sti for din profil.</translation>
+<translation id="616804573177634438">{0,select, single{Applikationen "<ph name="APP_NAME" />" anmoder om adgang til en af dine enheder.}multiple{Applikationen "<ph name="APP_NAME" />" anmoder om adgang til en eller flere af dine enheder.}other{UNUSED}}</translation>
+<translation id="641087317769093025">Udvidelsen kunne ikke udpakkes</translation>
+<translation id="657064425229075395">Baggrundsscriptet "<ph name="BACKGROUND_SCRIPT" />" kunne ikke indlæses.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> fra <ph name="VENDOR_NAME" /> (serienummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Mappen til udpakning kunne ikke oprettet: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">Mimehandler: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Denne udvidelse har genindlæst sig selv for mange gange.</translation>
+<translation id="7003844668372540529">Ukendt produkt, <ph name="PRODUCT_ID" />, fra <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Administratoren af denne computer kræver, at <ph name="EXTENSION_NAME" /> skal installeres. Den kan ikke fjernes eller ændres.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (udvidelses-id "<ph name="EXTENSION_ID" />") er blokeret af administratoren.</translation>
+<translation id="7972881773422714442">Valgmuligheder: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Denne udvidelse har ikke navngivet downloaden "<ph name="ATTEMPTED_FILENAME" />", fordi en anden udvidelse (<ph name="EXTENSION_NAME" />) har fastlagt et andet filnavn "<ph name="ACTUAL_FILENAME" />".</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> fra <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Denne udvidelse kunne ikke angive legitimationsoplysninger til en netværksanmodning, fordi en anden udvidelse (<ph name="EXTENSION_NAME" />) angav andre legitimationsoplysninger.</translation>
+<translation id="8602184400052594090">Manifestfil mangler eller er ulæselig.</translation>
+<translation id="8636666366616799973">Pakken er ugyldig. Oplysninger: "<ph name="ERROR_MESSAGE" />".</translation>
+<translation id="8670869118777164560">Denne udvidelse kunne ikke omdirigere en netværksanmodning til <ph name="ATTEMPTED_REDIRECT_DESTINATION" />, fordi en anden udvidelse (<ph name="EXTENSION_NAME" />) omdirigerede den til <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Værktøj til udpakning af udvidelser</translation>
+<translation id="8825366169884721447">Denne udvidelse kunne ikke ændre anmodningsheaderen "<ph name="HEADER_NAME" />" for en netværksanmodning, fordi ændringen var i strid med en anden udvidelse. (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Denne udvidelse kunne ikke ændre anmodningsheaderen "<ph name="HEADER_NAME" />" for en netværksanmodning, fordi ændringen var i strid med en anden udvidelse. (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_de.xtb b/chromium/extensions/strings/extensions_strings_de.xtb
new file mode 100644
index 00000000000..ad182597d25
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_de.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="de">
+<translation id="1135328998467923690">Paket ist ungültig: "<ph name="ERROR_CODE" />".</translation>
+<translation id="1256619696651732561">Erweiterungsmanifest-Parser</translation>
+<translation id="1445572445564823378">Diese Erweiterung verlangsamt die Ausführung von <ph name="PRODUCT_NAME" />. Deaktivieren Sie sie, um die Leistung von <ph name="PRODUCT_NAME" /> nicht zu beeinträchtigen.</translation>
+<translation id="149347756975725155">Erweiterungssymbol "<ph name="ICON" />" kann nicht geladen werden.</translation>
+<translation id="1803557475693955505">Hintergrundseite "<ph name="BACKGROUND_PAGE" />" konnte nicht geladen werden.</translation>
+<translation id="2159915644201199628">Bild konnte nicht decodiert werden: "<ph name="IMAGE_NAME" />"</translation>
+<translation id="2350172092385603347">Lokalisierung wurde verwendet, in der Manifest-Datei war jedoch kein Wert für "default_locale" angegeben.</translation>
+<translation id="2753617847762399167">Unzulässiger Pfad (absolut oder relativ mit ".."): "<ph name="IMAGE_PATH" />"</translation>
+<translation id="27822970480436970">Die Erweiterung konnte eine Netzwerkanfrage nicht modifizieren, da die Modifikation einen Konflikt mit einer anderen Erweiterung verursacht.</translation>
+<translation id="2857834222104759979">Manifest-Datei ist ungültig.</translation>
+<translation id="2988488679308982380">Paket konnte nicht installiert werden: "<ph name="ERROR_CODE" />".</translation>
+<translation id="3115238746683532089">Unbekanntes Produkt <ph name="PRODUCT_ID" /> von Anbieter <ph name="VENDOR_ID" /> (Seriennummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Unbekanntes Produkt <ph name="PRODUCT_ID" /> von Anbieter <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Erweiterung kann nicht entpackt werden. Damit eine Erweiterung sicher entpackt werden kann, muss ein Pfad zu Ihrem Profilverzeichnis zur Verfügung stehen, der mit einem Laufwerksbuchstaben beginnt und keine Verknüpfung, keinen Bereitstellungspunkt und keine symbolische Verbindung enthält. Für Ihr Profil besteht kein solcher Pfad.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (Seriennummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> von Anbieter <ph name="VENDOR_ID" /> (Seriennummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Launcher-Seite "<ph name="PAGE" />" konnte nicht geladen werden.</translation>
+<translation id="388442998277590542">Optionsseite "<ph name="OPTIONS_PAGE" />" konnte nicht geladen werden.</translation>
+<translation id="4115165561519362854">Der Administrator dieses Geräts hat festgelegt, dass für <ph name="EXTENSION_NAME" /> mindestens Version <ph name="EXTENSION_VERSION" /> installiert sein muss. Die Erweiterung kann erst aktiviert werden, nachdem sie auf diese oder eine höhere Version aktualisiert wurde.</translation>
+<translation id="4233778200880751280">Infoseite "<ph name="ABOUT_PAGE" />" konnte nicht geladen werden.</translation>
+<translation id="4434145631756268951">{0,select, single{USB-Gerät auswählen}multiple{USB-Geräte auswählen}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Das Paket konnte nicht installiert werden, da der Prozess eines Dienstprogramms abgestürzt ist. Starten Sie Chrome neu und versuchen Sie es erneut.</translation>
+<translation id="5026754133087629784">Web-Ansicht: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Die Erweiterung enthält die Schlüsseldatei "<ph name="KEY_PATH" />". Möchten Sie den Vorgang wirklich fortsetzen?</translation>
+<translation id="5627523580512561598"><ph name="EXTENSION_NAME" />-Erweiterung</translation>
+<translation id="5630931906013276297">{0,select, single{HID-Gerät auswählen}multiple{HID-Geräte auswählen}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Der Administrator dieses Computers hat die Installation von <ph name="EXTENSION_NAME" /> vorgegeben. Eine Deinstallation ist daher nicht möglich.</translation>
+<translation id="6027032947578871493">Unbekanntes Produkt <ph name="PRODUCT_ID" /> von <ph name="VENDOR_NAME" /> (Seriennummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> von Anbieter <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Erweiterung kann nicht entpackt werden. Damit eine Erweiterung sicher entpackt werden kann, muss ein Pfad zu Ihrem Profilverzeichnis zur Verfügung stehen, der keine symbolische Verbindung enthält. Für Ihr Profil besteht kein solcher Pfad.</translation>
+<translation id="616804573177634438">{0,select, single{Die Anwendung "<ph name="APP_NAME" />" fordert Zugriff auf eines Ihrer Geräte an.}multiple{Die Anwendung "<ph name="APP_NAME" />" fordert Zugriff auf eines oder mehrere Ihrer Geräte an.}other{UNUSED}}</translation>
+<translation id="641087317769093025">Erweiterung kann nicht entpackt werden.</translation>
+<translation id="657064425229075395">Hintergrundskript "<ph name="BACKGROUND_SCRIPT" />" konnte nicht geladen werden.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> von <ph name="VENDOR_NAME" /> (Seriennummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Fehler beim Erstellen von Verzeichnis zum Entpacken: "<ph name="DIRECTORY_PATH" />"</translation>
+<translation id="677806580227005219">Mimehandler: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Diese Erweiterung hat sich selbst zu häufig neu geladen.</translation>
+<translation id="7003844668372540529">Unbekanntes Produkt <ph name="PRODUCT_ID" /> von <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Der Administrator dieses Computers schreibt die Installation von <ph name="EXTENSION_NAME" /> vor. Die Erweiterung kann nicht entfernt oder geändert werden.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (Erweiterungs-ID "<ph name="EXTENSION_ID" />") wurde vom Administrator blockiert.</translation>
+<translation id="7972881773422714442">Options: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Diese Erweiterung konnte den Download nicht "<ph name="ATTEMPTED_FILENAME" />" nennen, weil eine andere Erweiterung (<ph name="EXTENSION_NAME" />) einen anderen Dateinamen "<ph name="ACTUAL_FILENAME" />" festgelegt hat.</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> von <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Die Erweiterung konnte die Anmeldeinformationen für eine Netzwerkanfrage nicht bereitstellen, da eine andere Erweiterung (<ph name="EXTENSION_NAME" />) andere Anmeldeinformationen übermittelt hat.</translation>
+<translation id="8602184400052594090">Manifest-Datei fehlt oder ist nicht lesbar.</translation>
+<translation id="8636666366616799973">Paket ist ungültig. Details: "<ph name="ERROR_MESSAGE" />"</translation>
+<translation id="8670869118777164560">Die Erweiterung konnte die Netzwerkanfrage nicht an <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> weiterleiten, da eine andere Erweiterung (<ph name="EXTENSION_NAME" />) sie an <ph name="ACTUAL_REDIRECT_DESTINATION" /> weitergeleitet hat.</translation>
+<translation id="8712265948125780616">Erweiterungsentpacker</translation>
+<translation id="8825366169884721447">Die Erweiterung konnte den Anfrage-Header "<ph name="HEADER_NAME" />" einer Netzwerkanfrage nicht modifizieren, da die Modifikation einen Konflikt mit einer anderen Erweiterung (<ph name="EXTENSION_NAME" />) verursacht.</translation>
+<translation id="9111791539553342076">Die Erweiterung konnte den Antwort-Header "<ph name="HEADER_NAME" />" einer Netzwerkanfrage nicht modifizieren, da die Modifikation einen Konflikt mit einer anderen Erweiterung (<ph name="EXTENSION_NAME" />) verursacht.</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_el.xtb b/chromium/extensions/strings/extensions_strings_el.xtb
new file mode 100644
index 00000000000..49b7dc0143f
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_el.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="el">
+<translation id="1135328998467923690">Μη έγκυρο πακέτο: '<ph name="ERROR_CODE" />'.</translation>
+<translation id="1256619696651732561">Συντακτικός αναλυτής μανιφέστου επέκτασης</translation>
+<translation id="1445572445564823378">Αυτή η επέκταση επιβραδύνει το <ph name="PRODUCT_NAME" />. Πρέπει να την απενεργοποιήσετε για να επαναφέρετε την απόδοση του <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">Δεν ήταν δυνατή η φόρτωση του εικονιδίου επέκτασης "<ph name="ICON" />".</translation>
+<translation id="1803557475693955505">Δεν ήταν δυνατή η φόρτωση της σελίδας φόντου "<ph name="BACKGROUND_PAGE" />".</translation>
+<translation id="2159915644201199628">Δεν ήταν δυνατή η αποκωδικοποίηση της εικόνας: "<ph name="IMAGE_NAME" />"</translation>
+<translation id="2350172092385603347">Χρησιμοποιήθηκε τοπική προσαρμογή, όμως δεν καθορίστηκε η τιμή "default_locale" στη δήλωση.</translation>
+<translation id="2753617847762399167">Παράνομη διαδρομή (απόλυτη ή σχετική με ".."): "<ph name="IMAGE_PATH" />"</translation>
+<translation id="27822970480436970">Η επέκταση αυτή απέτυχε να τροποποιήσει ένα αίτημα δικτύου, επειδή η τροποποίηση ερχόταν σε σύγκρουση με μια άλλη προέκταση.</translation>
+<translation id="2857834222104759979">Το αρχείο δήλωσης δεν είναι έγκυρο.</translation>
+<translation id="2988488679308982380">Δεν ήταν δυνατή η εγκατάσταση του πακέτου: '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089">Άγνωστο προϊόν <ph name="PRODUCT_ID" /> από τον προμηθευτή <ph name="VENDOR_ID" /> (σειριακός αριθμός <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Άγνωστο προϊόν <ph name="PRODUCT_ID" /> από τον προμηθευτή <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Δεν είναι δυνατή η αποσυσκευασία επέκτασης. Για την ασφαλή αποσυσκευασία μιας επέκτασης, πρέπει να υπάρχει στον κατάλογο προφίλ σας μια διαδρομή που να ξεκινάει με ένα γράμμα μονάδας δίσκου και να μην περιέχει κάποιο σύνδεσμο, σημείο μονταρίσματος ή symlink (συντόμευση). Δεν υπάρχει τέτοια διαδρομή για το προφίλ σας.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (σειριακός ρυθμός <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> από τον προμηθευτή <ph name="VENDOR_ID" /> (σειριακός ρυθμός <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Δεν ήταν δυνατή η φόρτωση της σελίδας εφαρμογής εκκίνησης '<ph name="PAGE" />'.</translation>
+<translation id="388442998277590542">Δεν ήταν δυνατή η φόρτωση της σελίδας επιλογών "<ph name="OPTIONS_PAGE" />".</translation>
+<translation id="4115165561519362854">Ο διαχειριστής αυτού του μηχανήματος απαιτεί από το <ph name="EXTENSION_NAME" /> να διαθέτει τουλάχιστον την έκδοση <ph name="EXTENSION_VERSION" />. Δεν μπορεί να ενεργοποιηθεί έως ότου ενημερωθεί σε αυτήν την έκδοση (ή σε μεταγενέστερη).</translation>
+<translation id="4233778200880751280">Δεν ήταν δυνατή η φόρτωση της σελίδας πληροφοριών "<ph name="ABOUT_PAGE" />".</translation>
+<translation id="4434145631756268951">{0,select, single{Επιλέξτε μια συσκευή USB}multiple{Επιλέξτε συσκευές USB}other{ΔΕΝ ΧΡΗΣΙΜΟΠΟΙΕΙΤΑΙ}}</translation>
+<translation id="4811956658694082538">Δεν ήταν δυνατή η εγκατάσταση του πακέτου, επειδή μια διεργασία βοηθητικού προγράμματος παρουσίασε σφάλμα. Επανεκκινήστε το Chrome και προσπαθήστε ξανά.</translation>
+<translation id="5026754133087629784">Webview: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Αυτή η επέκταση περιλαμβάνει το αρχείο κλειδιού "<ph name="KEY_PATH" />". Πιθανότατα δεν θέλετε να προβείτε σε αυτήν την ενέργεια.</translation>
+<translation id="5627523580512561598">επέκταση <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Επιλέξτε μια συσκευή HID}multiple{Επιλέξτε συσκευές HID}other{ΔΕΝ ΧΡΗΣΙΜΟΠΟΙΕΙΤΑΙ}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Ο διαχειριστής αυτού του υπολογιστή απαιτεί την εγκατάσταση της επέκτασης <ph name="EXTENSION_NAME" />. Η εγκατάσταση δεν μπορεί να καταργηθεί.</translation>
+<translation id="6027032947578871493">Άγνωστο προϊόν <ph name="PRODUCT_ID" /> από <ph name="VENDOR_NAME" /> (σειριακός αριθμός <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> από τον προμηθευτή <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Δεν είναι δυνατή η αποσυσκευασία της επέκτασης. Για την ασφαλή αποσυσκευασία μιας επέκτασης πρέπει να υπάρχει στον κατάλογο προφίλ σας μια διαδρομή που δεν περιέχει κάποιο symlink. Στο προφίλ σας δεν υπάρχει κάποια τέτοια διαδρομή.</translation>
+<translation id="616804573177634438">{0,select, single{Η εφαρμογή "<ph name="APP_NAME" />" ζητάει πρόσβαση σε μία από τις συσκευές σας.}multiple{Η εφαρμογή "<ph name="APP_NAME" />" ζητάει πρόσβαση σε μία ή περισσότερες συσκευές σας.}other{ΔΕΝ ΧΡΗΣΙΜΟΠΟΙΕΙΤΑΙ}}</translation>
+<translation id="641087317769093025">Δεν ήταν δυνατή η αποσυμπίεση της επέκτασης</translation>
+<translation id="657064425229075395">Δεν ήταν δυνατή η φόρτωση του σεναρίου παρασκηνίου '<ph name="BACKGROUND_SCRIPT" />'.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> από <ph name="VENDOR_NAME" /> (σειριακός αριθμός <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Δεν ήταν δυνατή η δημιουργία καταλόγου για την αποσυμπίεση: "<ph name="DIRECTORY_PATH" />"</translation>
+<translation id="677806580227005219">Χειρισμός mime: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Αυτή η επέκταση επαναφορτώνεται πολύ συχνά.</translation>
+<translation id="7003844668372540529">Άγνωστο προϊόν <ph name="PRODUCT_ID" /> από <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Ο διαχειριστής αυτού του υπολογιστή απαιτεί την εγκατάσταση της επέκτασης <ph name="EXTENSION_NAME" />. Δεν είναι δυνατή η κατάργηση ή η τροποποίησή της.</translation>
+<translation id="7809034755304591547">Το αναγνωριστικό <ph name="EXTENSION_NAME" /> (αναγνωριστικό επέκτασης "<ph name="EXTENSION_ID" />") έχει αποκλειστεί από τον διαχειριστή.</translation>
+<translation id="7972881773422714442">Επιλογές: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Η επέκταση δεν κατόρθωσε να ονομάσει τη λήψη "<ph name="ATTEMPTED_FILENAME" />" επειδή μια άλλη επέκταση (<ph name="EXTENSION_NAME" />) καθόρισε ένα διαφορετικό όνομα "<ph name="ACTUAL_FILENAME" />".</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> της <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Αυτή η επέκταση απέτυχε να παράσχει διαπιστευτήρια σε κάποιο αίτημα δικτύου επειδή κάποια άλλη επέκταση (<ph name="EXTENSION_NAME" />) παρέσχεσε διαφορετικά διαπιστευτήρια.</translation>
+<translation id="8602184400052594090">Το αρχείο δήλωσης λείπει ή δεν είναι δυνατή η ανάγνωσή του.</translation>
+<translation id="8636666366616799973">Μη έγκυρο πακέτο. Λεπτομέρειες: '<ph name="ERROR_MESSAGE" />'.</translation>
+<translation id="8670869118777164560">Αυτή η επέκταση απέτυχε να ανακατευθύνει κάποιο αίτημα δικτύου στο <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> επειδή κάποια άλλη επέκταση (<ph name="EXTENSION_NAME" />) το ανακατεύθυνε στο <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Εργαλείο αποσυσκευασίας επεκτάσεων</translation>
+<translation id="8825366169884721447">Αυτή η επέκταση απέτυχε να τροποποιήσει την κεφαλίδα αιτήματος "<ph name="HEADER_NAME" />" κάποιου αιτήματος δικτύου επειδή η τροποποίηση ήρθε σε σύγκρουση με κάποια άλλη επέκταση (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Αυτή η επέκταση απέτυχε να τροποποιήσει την κεφαλίδα απόκρισης "<ph name="HEADER_NAME" />" κάποιου αιτήματος δικτύου επειδή η τροποποίηση ήρθε σε σύγκρουση με κάποια άλλη επέκταση (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_en-GB.xtb b/chromium/extensions/strings/extensions_strings_en-GB.xtb
new file mode 100644
index 00000000000..ab7f60dea82
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_en-GB.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="en-GB">
+<translation id="1135328998467923690">Package is invalid: '<ph name="ERROR_CODE" />'.</translation>
+<translation id="1256619696651732561">Extension Manifest Parser</translation>
+<translation id="1445572445564823378">This extension is slowing down <ph name="PRODUCT_NAME" />. You should disable it to restore <ph name="PRODUCT_NAME" />'s performance.</translation>
+<translation id="149347756975725155">Could not load extension icon '<ph name="ICON" />'.</translation>
+<translation id="1803557475693955505">Could not load background page '<ph name="BACKGROUND_PAGE" />'.</translation>
+<translation id="2159915644201199628">Could not decode image: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">Localisation used, but default_locale wasn't specified in the manifest.</translation>
+<translation id="2753617847762399167">Illegal path (absolute or relative with '..'): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">This extension failed to modify a network request because the modification conflicted with another extension.</translation>
+<translation id="2857834222104759979">Manifest file is invalid.</translation>
+<translation id="2988488679308982380">Could not install package: '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089">Unknown product <ph name="PRODUCT_ID" /> from vendor <ph name="VENDOR_ID" /> (serial number <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Unknown product <ph name="PRODUCT_ID" /> from vendor <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Cannot unpack extension. To safely unpack an extension, there must be a path to your profile directory that starts with a drive letter and does not contain a junction, mount point or symlink. No such path exists for your profile.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (serial number <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> from vendor <ph name="VENDOR_ID" /> (serial number <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Could not load launcher page '<ph name="PAGE" />'.</translation>
+<translation id="388442998277590542">Could not load options page '<ph name="OPTIONS_PAGE" />'.</translation>
+<translation id="4115165561519362854">The administrator of this machine requires <ph name="EXTENSION_NAME" /> to have a minimum version of <ph name="EXTENSION_VERSION" />. It cannot be enabled until it has updated to that version (or higher).</translation>
+<translation id="4233778200880751280">Could not load about page '<ph name="ABOUT_PAGE" />'.</translation>
+<translation id="4434145631756268951">{0,select, single{Select a USB device}multiple{Select USB devices}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Could not install package because a utility process crashed. Try restarting Chrome and trying again.</translation>
+<translation id="5026754133087629784">Webview: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">This extension includes the key file '<ph name="KEY_PATH" />'. You probably don't want to do that.</translation>
+<translation id="5627523580512561598">extension <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Select a HID device}multiple{Select HID devices}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">The administrator of this machine requires <ph name="EXTENSION_NAME" /> to be installed. It cannot be uninstalled.</translation>
+<translation id="6027032947578871493">Unknown product <ph name="PRODUCT_ID" /> from <ph name="VENDOR_NAME" /> (serial number <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> from vendor <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Cannot unpack extension. To unpack an extension safely, there must be a path to your profile directory that does not contain a symlink. No such path exists for your profile.</translation>
+<translation id="616804573177634438">{0,select, single{The application "<ph name="APP_NAME" />" is requesting access to one of your devices.}multiple{The application "<ph name="APP_NAME" />" is requesting access to one or more of your devices.}other{UNUSED}}</translation>
+<translation id="641087317769093025">Could not unzip extension</translation>
+<translation id="657064425229075395">Could not load background script '<ph name="BACKGROUND_SCRIPT" />'.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> from <ph name="VENDOR_NAME" /> (serial number <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Could not create directory for unzipping: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">Mimehandler: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">This extension reloaded itself too frequently.</translation>
+<translation id="7003844668372540529">Unknown product <ph name="PRODUCT_ID" /> from <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">The administrator of this machine requires <ph name="EXTENSION_NAME" /> to be installed. It cannot be removed or modified.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (extension ID "<ph name="EXTENSION_ID" />") is blocked by the administrator.</translation>
+<translation id="7972881773422714442">Options: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">This extension failed to name the download "<ph name="ATTEMPTED_FILENAME" />" because another extension (<ph name="EXTENSION_NAME" />) determined a different filename "<ph name="ACTUAL_FILENAME" />".</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> from <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">This extension failed to provide credentials to a network request because another extension (<ph name="EXTENSION_NAME" />) provided different credentials.</translation>
+<translation id="8602184400052594090">Manifest file is missing or unreadable.</translation>
+<translation id="8636666366616799973">Package is invalid. Details: '<ph name="ERROR_MESSAGE" />'.</translation>
+<translation id="8670869118777164560">This extension failed to redirect a network request to <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> because another extension (<ph name="EXTENSION_NAME" />) redirected it to <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Extension Unpacker</translation>
+<translation id="8825366169884721447">This extension failed to modify the request header "<ph name="HEADER_NAME" />" of a network request because the modification conflicted with another extension (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">This extension failed to modify the response header "<ph name="HEADER_NAME" />" of a network request because the modification conflicted with another extension (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_es-419.xtb b/chromium/extensions/strings/extensions_strings_es-419.xtb
new file mode 100644
index 00000000000..c148edd10a0
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_es-419.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="es-419">
+<translation id="1135328998467923690">El paquete no es válido: '<ph name="ERROR_CODE" />'.</translation>
+<translation id="1256619696651732561">Analizador de manifiesto de extensiones</translation>
+<translation id="1445572445564823378">Esta extensión está ralentizando <ph name="PRODUCT_NAME" />. Deberías inhabilitarla para restaurar el rendimiento de <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">No se pudo cargar el ícono de extensión '<ph name="ICON" />'.</translation>
+<translation id="1803557475693955505">No se pudo cargar la página de fondo '<ph name="BACKGROUND_PAGE" />'.</translation>
+<translation id="2159915644201199628">No se pudo decodificar la imagen "<ph name="IMAGE_NAME" />".</translation>
+<translation id="2350172092385603347">Se utiliza localización, pero default_locale no se especificó en el manifiesto.</translation>
+<translation id="2753617847762399167">Ruta no válida (absoluta o relativa con '..'): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">Esta extensión no pudo modificar una solicitud de red porque la modificación provocaba un conflicto con otra extensión.</translation>
+<translation id="2857834222104759979">Archivo de manifiesto no válido.</translation>
+<translation id="2988488679308982380">No se pudo instalar el paquete: '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089">Producto desconocido <ph name="PRODUCT_ID" /> del proveedor <ph name="VENDOR_ID" /> (número de serie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Producto desconocido <ph name="PRODUCT_ID" /> del proveedor <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">No se puede desempaquetar la extensión. Para desempaquetar una extensión de manera segura, debe haber una ruta de acceso al directorio de tu perfil que comience con una letra de unidad y que no contenga una unión, un punto de montaje ni un symlink. No existe esa ruta de acceso para tu perfil.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (número de serie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> del proveedor <ph name="VENDOR_ID" /> (número de serie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">No se pudo cargar la página "<ph name="PAGE" />" del selector.</translation>
+<translation id="388442998277590542">No se pudo cargar la página de opciones '<ph name="OPTIONS_PAGE" />'.</translation>
+<translation id="4115165561519362854">El administrador de esta computadora requiere una versión de <ph name="EXTENSION_NAME" /> no anterior a <ph name="EXTENSION_VERSION" />. No se puede habilitar hasta que se haya actualizado a dicha versión (o versiones posteriores).</translation>
+<translation id="4233778200880751280">No se pudo cargar la página de información "<ph name="ABOUT_PAGE" />".</translation>
+<translation id="4434145631756268951">{0,select, single{Selecciona un dispositivo USB}multiple{Selecciona dispositivos USB}other{UNUSED}}</translation>
+<translation id="4811956658694082538">No se pudo instalar el paquete porque falló el proceso de ejecución de una utilidad. Prueba a reiniciar Chrome y vuelve a intentarlo.</translation>
+<translation id="5026754133087629784">Vista web: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Esta extensión incluye el archivo de clave "<ph name="KEY_PATH" />". Probablemente no desees incluir ese archivo.</translation>
+<translation id="5627523580512561598">extensión <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Selecciona un dispositivo HID}multiple{Selecciona dispositivos HID}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">El administrador de esta computadora requiere que <ph name="EXTENSION_NAME" /> esté instalada. No se puede desinstalar.</translation>
+<translation id="6027032947578871493">Producto desconocido <ph name="PRODUCT_ID" /> de <ph name="VENDOR_NAME" /> (número de serie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> del proveedor <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">No se puede desempaquetar la extensión. Para desempaquetar una extensión de manera segura, debe haber una ruta de acceso al directorio de tu perfil que no contenga un symlink. No existe esa ruta de acceso para tu perfil.</translation>
+<translation id="616804573177634438">{0,select, single{La aplicación "<ph name="APP_NAME" />" está solicitando acceso a uno de tus dispositivos.}multiple{La aplicación "<ph name="APP_NAME" />" está solicitando acceso a uno o más de tus dispositivos.}other{UNUSED}}</translation>
+<translation id="641087317769093025">No se pudo descomprimir la extensión.</translation>
+<translation id="657064425229075395">No se pudo cargar la secuencia de comandos en segundo plano "<ph name="BACKGROUND_SCRIPT" />".</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> de <ph name="VENDOR_NAME" /> (número de serie: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">No se pudo crear un directorio para descomprimir "<ph name="DIRECTORY_PATH" />".</translation>
+<translation id="677806580227005219">Controlador MIME: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Esta extensión se recargó con demasiada frecuencia.</translation>
+<translation id="7003844668372540529">Producto desconocido <ph name="PRODUCT_ID" /> de <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">El administrador de esta máquina requiere que instales <ph name="EXTENSION_NAME" />. No se puede eliminar ni modificar.</translation>
+<translation id="7809034755304591547">El administrador bloqueó la extensión <ph name="EXTENSION_NAME" /> (con ID "<ph name="EXTENSION_ID" />").</translation>
+<translation id="7972881773422714442">Opciones: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Esta extensión no pudo asignar el nombre "<ph name="ATTEMPTED_FILENAME" />" a la descarga porque otra extensión (<ph name="EXTENSION_NAME" />) determinó un nombre de archivo diferente: "<ph name="ACTUAL_FILENAME" />".</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> en <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Esta extensión no pudo proporcionar credenciales a una solicitud de red porque otra extensión (<ph name="EXTENSION_NAME" />) proporcionó otras credenciales.</translation>
+<translation id="8602184400052594090">El archivo de manifiesto falta o no se puede leer.</translation>
+<translation id="8636666366616799973">El paquete no es válido. Detalles: '<ph name="ERROR_MESSAGE" />'.</translation>
+<translation id="8670869118777164560">Esta extensión no pudo redireccionar una solicitud de red a <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> porque otra extensión (<ph name="EXTENSION_NAME" />) la redireccionó a <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Desempaquetador de extensiones</translation>
+<translation id="8825366169884721447">Esta extensión no pudo modificar el encabezado de solicitud "<ph name="HEADER_NAME" />" de una solicitud de red porque la modificación entró en conflicto con otra extensión (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Esta extensión no pudo modificar el encabezado de respuesta "<ph name="HEADER_NAME" />" de una solicitud de red porque la modificación entró en conflicto con otra extensión (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_es.xtb b/chromium/extensions/strings/extensions_strings_es.xtb
new file mode 100644
index 00000000000..30d9f92ec70
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_es.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="es">
+<translation id="1135328998467923690">El paquete no es válido: "<ph name="ERROR_CODE" />".</translation>
+<translation id="1256619696651732561">Analizador de archivos de manifiesto de extensiones</translation>
+<translation id="1445572445564823378">Esta extensión está ralentizando <ph name="PRODUCT_NAME" />. Deberías inhabilitarla para restaurar el rendimiento de <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">No se ha podido cargar el icono de la extensión "<ph name="ICON" />".</translation>
+<translation id="1803557475693955505">No se ha podido cargar la página de fondo "<ph name="BACKGROUND_PAGE" />".</translation>
+<translation id="2159915644201199628">No se ha podido decodificar la imagen <ph name="IMAGE_NAME" />.</translation>
+<translation id="2350172092385603347">Se ha utilizado la localización, pero no se ha especificado default_locale en el archivo de manifiesto.</translation>
+<translation id="2753617847762399167">Ruta no válida (absoluta o relativa con ".."): <ph name="IMAGE_PATH" /></translation>
+<translation id="27822970480436970">Esta extensión no ha podido modificar una solicitud de red porque existe un conflicto con otra extensión.</translation>
+<translation id="2857834222104759979">El archivo de manifiesto no es válido.</translation>
+<translation id="2988488679308982380">No se ha podido instalar el paquete: "<ph name="ERROR_CODE" />".</translation>
+<translation id="3115238746683532089">Producto <ph name="PRODUCT_ID" /> del proveedor <ph name="VENDOR_ID" /> desconocido (número de serie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Producto <ph name="PRODUCT_ID" /> del proveedor <ph name="VENDOR_ID" /> desconocido</translation>
+<translation id="3369521687965833290">No se puede descomprimir la extensión. Para descomprimir de forma segura una extensión, debe existir una ruta al directorio de tu perfil que comience con una letra de unidad y que no incluya ninguna unión, ningún punto de montaje ni ningún enlace simbólico. No existe ninguna ruta de este tipo para tu perfil.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (número de serie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> del proveedor <ph name="VENDOR_ID" /> (número de serie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">No se ha podido cargar la página del menú de aplicaciones "<ph name="PAGE" />".</translation>
+<translation id="388442998277590542">No se ha podido cargar la página de opciones "<ph name="OPTIONS_PAGE" />".</translation>
+<translation id="4115165561519362854">El administrador de este equipo requiere que <ph name="EXTENSION_NAME" /> tenga como mínimo la versión <ph name="EXTENSION_VERSION" />. No se puede habilitar hasta que se haya actualizado a esa versión (o superior).</translation>
+<translation id="4233778200880751280">No se ha podido cargar la página de información (<ph name="ABOUT_PAGE" />).</translation>
+<translation id="4434145631756268951">{0,select, single{Selecciona un dispositivo USB}multiple{Selecciona dispositivos USB}other{SIN UTILIZAR}}</translation>
+<translation id="4811956658694082538">No se ha podido instalar el paquete porque se ha producido un fallo en el proceso de ejecución de una utilidad. Reinicia Chrome y vuelve a intentarlo.</translation>
+<translation id="5026754133087629784">Vista web: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Esta extensión incluye el archivo de clave "<ph name="KEY_PATH" />". Probablemente no quieras incluir este archivo.</translation>
+<translation id="5627523580512561598">extensión <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Selecciona un dispositivo HID}multiple{Selecciona dispositivos HID}other{SIN UTILIZAR}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692"><ph name="EXTENSION_NAME" /> no se puede desinstalar porque el administrador de este ordenador requiere que esté instalado.</translation>
+<translation id="6027032947578871493">Producto <ph name="PRODUCT_ID" /> de <ph name="VENDOR_NAME" /> desconocido (número de serie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> del proveedor <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">No se puede descomprimir la extensión. Para descomprimir de forma segura una extensión, debe existir una ruta al directorio de tu perfil que no contenga ningún enlace simbólico. No existe ninguna ruta de este tipo para tu perfil.</translation>
+<translation id="616804573177634438">{0,select, single{La aplicación <ph name="APP_NAME" /> está solicitando acceso a uno de tus dispositivos.}multiple{La aplicación <ph name="APP_NAME" /> está solicitando acceso a uno o a varios de tus dispositivos.}other{SIN UTILIZAR}}</translation>
+<translation id="641087317769093025">No se ha podido descomprimir la extensión.</translation>
+<translation id="657064425229075395">No se ha podido cargar la secuencia de comandos en segundo plano "<ph name="BACKGROUND_SCRIPT" />".</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> de <ph name="VENDOR_NAME" /> (número de serie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">No se ha podido crear un directorio para descomprimir <ph name="DIRECTORY_PATH" />.</translation>
+<translation id="677806580227005219">Controlador MIME: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Esta extensión se ha cargado con demasiada frecuencia.</translation>
+<translation id="7003844668372540529">Producto <ph name="PRODUCT_ID" /> de <ph name="VENDOR_NAME" /> desconocido</translation>
+<translation id="7217838517480956708">El administrador de este ordenador necesita que se instale <ph name="EXTENSION_NAME" />. No se puede eliminar ni modificar.</translation>
+<translation id="7809034755304591547">El administrador de la extensión <ph name="EXTENSION_NAME" /> (con ID "<ph name="EXTENSION_ID" />") ha bloqueado la extensión.</translation>
+<translation id="7972881773422714442">Opciones: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Esta extensión no ha podido asignar un nombre a la descarga "<ph name="ATTEMPTED_FILENAME" />" porque otra extensión (<ph name="EXTENSION_NAME" />) ha determinado un nombre de archivo diferente "<ph name="ACTUAL_FILENAME" />".</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> de <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Esta extensión no ha podido proporcionar credenciales a una solicitud de red porque otra extensión (<ph name="EXTENSION_NAME" />) ha proporcionado otras credenciales.</translation>
+<translation id="8602184400052594090">Falta el archivo de manifiesto o no se puede leer.</translation>
+<translation id="8636666366616799973">El paquete no es válido. Detalles: "<ph name="ERROR_MESSAGE" />"</translation>
+<translation id="8670869118777164560">Esta extensión no ha podido redirigir una solicitud de red a <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> porque otra extensión (<ph name="EXTENSION_NAME" />) la ha redireccionado a <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Descompresor de extensiones</translation>
+<translation id="8825366169884721447">Esta extensión no ha podido modificar el encabezado de solicitud "<ph name="HEADER_NAME" />" de una solicitud de red porque la modificación ha entrado en conflicto con otra extensión (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Esta extensión no ha podido modificar el encabezado de respuesta "<ph name="HEADER_NAME" />" de una solicitud de red porque la modificación ha entrado en conflicto con otra extensión (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_et.xtb b/chromium/extensions/strings/extensions_strings_et.xtb
new file mode 100644
index 00000000000..a8a8f43dc1e
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_et.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="et">
+<translation id="1135328998467923690">Vale pakett: „<ph name="ERROR_CODE" />”.</translation>
+<translation id="1256619696651732561">Laienduse manifesti parser</translation>
+<translation id="1445572445564823378">Laiendus aeglustab rakendust <ph name="PRODUCT_NAME" />. Peaksite selle keelama, et taastada rakenduse <ph name="PRODUCT_NAME" /> toimivus.</translation>
+<translation id="149347756975725155">Laienduse ikooni <ph name="ICON" /> ei õnnestunud laadida.</translation>
+<translation id="1803557475693955505">Tagaplaanilehte <ph name="BACKGROUND_PAGE" /> ei õnnestunud laadida.</translation>
+<translation id="2159915644201199628">Ei saanud pilti dekodeerida: „<ph name="IMAGE_NAME" />”</translation>
+<translation id="2350172092385603347">Lokaliseerimist kasutatakse, kuid parameetrit default_locale ei olnud manifestis määratud.</translation>
+<translation id="2753617847762399167">Keelatud tee (absoluutne või suhteline üksusega '..'): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">Laiendusel ei õnnestunud võrgutaotlust muuta, kuna muutmisel tekkis vastuolu muu laiendusega.</translation>
+<translation id="2857834222104759979">Manifestifail on kehtetu.</translation>
+<translation id="2988488679308982380">Paketti ei saanud installida: „<ph name="ERROR_CODE" />”</translation>
+<translation id="3115238746683532089">Tundmatu toode <ph name="PRODUCT_ID" /> teenusepakkujalt <ph name="VENDOR_ID" /> (seerianumber <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Tundmatu toode <ph name="PRODUCT_ID" /> teenusepakkujalt <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Laiendust ei saanud lahti pakkida. Laienduse turvaliseks lahtipakkimiseks peab olema tee teie profiili kataloogi, mis algab ketast tähistava tähega ja milles ei ole sõlme, paigalduspunkti või symlinki. Sellist teed teie profiilil ei ole.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (seerianumber <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397">Toode <ph name="PRODUCT_NAME" /> teenusepakkujalt <ph name="VENDOR_ID" /> (seerianumber <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Käivitusprogrammi lehte <ph name="PAGE" /> ei saanud laadida.</translation>
+<translation id="388442998277590542">Valikute lehte <ph name="OPTIONS_PAGE" /> ei õnnestunud laadida.</translation>
+<translation id="4115165561519362854">Seadme administraator nõuab, et kasutaksite laienduse <ph name="EXTENSION_NAME" /> puhul vähemalt versiooni <ph name="EXTENSION_VERSION" />. Laiendust ei saa lubada enne, kui see on värskendatud sellele versioonile (või uuemale versioonile).</translation>
+<translation id="4233778200880751280">Teabelehte „<ph name="ABOUT_PAGE" />” ei õnnestunud laadida.</translation>
+<translation id="4434145631756268951">{0,select, single{Valige USB-seade}multiple{Valige USB-seadmed}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Paketti ei õnnestunud installida, sest utiliidiprotsess jooksis kokku. Taaskäivitage Chrome ja proovige uuesti.</translation>
+<translation id="5026754133087629784">Veebivaade: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Rakendusvaade: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Laiendus hõlmab võtmefaili „<ph name="KEY_PATH" />”. See toiming ei ole soovitatav.</translation>
+<translation id="5627523580512561598">laiendus <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Valige HID-seade}multiple{Valige HID-seadmed}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Selle seadme administraator nõuab laienduse <ph name="EXTENSION_NAME" /> installimist. Seda ei saa desinstallida.</translation>
+<translation id="6027032947578871493">Tundmatu toode <ph name="PRODUCT_ID" /> teenusepakkujalt <ph name="VENDOR_NAME" /> (seerianumber <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721">Toode <ph name="PRODUCT_NAME" /> teenusepakkujalt <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Laiendust ei saanud lahti pakkida. Laienduse turvaliseks lahtipakkimiseks peab olema tee teie profiili kataloogi, milles ei ole symlinki. Sellist teed teie profiilil ei ole.</translation>
+<translation id="616804573177634438">{0,select, single{Rakendus „<ph name="APP_NAME" />” taotleb juurdepääsu ühele seadmele.}multiple{Rakendus „<ph name="APP_NAME" />” taotleb juurdepääsu vähemalt ühele seadmele.}other{UNUSED}}</translation>
+<translation id="641087317769093025">Ei saanud laiendust lahti pakkida</translation>
+<translation id="657064425229075395">Taustaskripti „<ph name="BACKGROUND_SCRIPT" />” ei õnnestunud laadida.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> tootjalt <ph name="VENDOR_NAME" /> (seerianumber <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Lahtipakkimiseks ei saanud kataloogi luua: „<ph name="DIRECTORY_PATH" />”</translation>
+<translation id="677806580227005219">Mime-töötleja: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">See laiendus laadis ennast liiga sageli uuesti.</translation>
+<translation id="7003844668372540529">Tundmatu toode <ph name="PRODUCT_ID" /> teenusepakkujalt <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Seadme administraator nõuab, et <ph name="EXTENSION_NAME" /> oleks installitud. Seda ei saa eemaldada ega muuta.</translation>
+<translation id="7809034755304591547">Administraator blokeeris laienduse <ph name="EXTENSION_NAME" /> (laienduse ID „<ph name="EXTENSION_ID" />”).</translation>
+<translation id="7972881773422714442">Valikud: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Laiendus ei saanud määrata allalaaditavale failile nimeks „<ph name="ATTEMPTED_FILENAME" />”, sest teine laiendus (<ph name="EXTENSION_NAME" />) määras teise failinime „<ph name="ACTUAL_FILENAME" />”.</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> teenusepakkujalt <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Laiendusel ei õnnestunud võrgutaotlusele mandaati esitada, kuna teine laiendus (<ph name="EXTENSION_NAME" />) esitas teistsuguse mandaadi.</translation>
+<translation id="8602184400052594090">Manifestifail on kadunud või ei ole seda võimalik lugeda.</translation>
+<translation id="8636666366616799973">Vale pakett. Üksikasjad: „<ph name="ERROR_MESSAGE" />”.</translation>
+<translation id="8670869118777164560">Laiendusel ei õnnestunud võrgutaotlust ümber suunata asukohta <ph name="ATTEMPTED_REDIRECT_DESTINATION" />, kuna teine laiendus (<ph name="EXTENSION_NAME" />) suunas selle asukohta <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Laienduse lahtipakkija</translation>
+<translation id="8825366169884721447">Laiendusel ei õnnestunud muuta võrgutaotluse päist „<ph name="HEADER_NAME" />”, kuna muudatus sattus vastuollu teise laiendusega (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Laiendusel ei õnnestunud muuta võrgutaotluse vastuse päist „<ph name="HEADER_NAME" />”, kuna muudatus sattus vastuollu teise laiendusega (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_fa.xtb b/chromium/extensions/strings/extensions_strings_fa.xtb
new file mode 100644
index 00000000000..8c1d5eaf0d5
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_fa.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="fa">
+<translation id="1135328998467923690">بسته نامعتبر است: "<ph name="ERROR_CODE" />".</translation>
+<translation id="1256619696651732561">تجزیه‌کننده مانیفست افزونه</translation>
+<translation id="1445572445564823378">این برنامهٔ افزودنی سرعت <ph name="PRODUCT_NAME" /> را پایین می‌آورد. شما باید برای بازیابی عملکرد <ph name="PRODUCT_NAME" /> این برنامه را غیرفعال کنید.</translation>
+<translation id="149347756975725155">بارکردن نماد پسوند "<ph name="ICON" />" ممکن نیست.</translation>
+<translation id="1803557475693955505">بارگیری صفحه پس‌زمینه "<ph name="BACKGROUND_PAGE" />" ممکن نیست.</translation>
+<translation id="2159915644201199628">رمزگشایی تصویر امکان‌پذیر نیست: «<ph name="IMAGE_NAME" />»</translation>
+<translation id="2350172092385603347">‏بومی سازی استفاده شده است، اما default_locale در اظهارنامه مشخص نشده است.</translation>
+<translation id="2753617847762399167">مسیر غیرمجاز (مطلق یا نسبی با «..»): «<ph name="IMAGE_PATH" />»</translation>
+<translation id="27822970480436970">این برنامهٔ افزودنی قادر به تغییر درخواست شبکه به دلیل تغییری متداخل با یک برنامهٔ افزودنی دیگر نیست.</translation>
+<translation id="2857834222104759979">فایل اظهارنامه نامعتبر است.</translation>
+<translation id="2988488679308982380">بسته نصب نشد: "<ph name="ERROR_CODE" />"</translation>
+<translation id="3115238746683532089">محصول نامشخص <ph name="PRODUCT_ID" /> از فروشنده <ph name="VENDOR_ID" /> (شماره سریال <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">محصول نامشخص <ph name="PRODUCT_ID" /> از فروشنده <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">بسته برنامهٔ افزودنی را نمی‌توان باز کرد. برای باز کردن ایمن برنامهٔ افزودنی، باید یک مسیر در فهرست نمایهٔ شما وجود داشته باشد که با یک حرف درایو آغاز شود و فاقد یک خط اتصال، نقطه اتصال و پیوند نمادی باشد. چنین مسیری برای نمایهٔ شما وجود ندارد.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (شماره سریال <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> از فروشنده <ph name="VENDOR_ID" /> (شماره سریال <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">صفحه راه‌انداز «<ph name="PAGE" />» بارگیری نشد.</translation>
+<translation id="388442998277590542">صفحه گزینه‌ها "<ph name="OPTIONS_PAGE" />" بارگیری نشد.</translation>
+<translation id="4115165561519362854">طبق دستور سرپرست این دستگاه باید نسخه <ph name="EXTENSION_NAME" /> حداقل <ph name="EXTENSION_VERSION" /> باشد. تا زمانی که به آن نسخه (یا بالاتر) به‌روزرسانی نشود نمی‌توان آن را فعال کرد.</translation>
+<translation id="4233778200880751280">صفحه درباره «<ph name="ABOUT_PAGE" />» بارگیری نشد.</translation>
+<translation id="4434145631756268951">{0,select, single{‏انتخاب دستگاه USB}multiple{‏انتخاب دستگاه‌های USB}other{UNUSED}}</translation>
+<translation id="4811956658694082538">‏بسته نصب نشد چون پردازش برنامه سودمند انجام نشد. سعی کنید chrome را راه‌اندازی مجدد نمایید و دوباره امتحان کنید.</translation>
+<translation id="5026754133087629784">وب‌نما: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">این برنامهٔ افزودنی شامل فایل کلید «<ph name="KEY_PATH" />» است. احتمالاً نمی‌خواهید این کار را انجام دهید.</translation>
+<translation id="5627523580512561598">برنامهٔ افزودنی <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{‏انتخاب دستگاه HID}multiple{‏انتخاب دستگاه‌های HID}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">سرپرست این دستگاه نصب <ph name="EXTENSION_NAME" /> را لازم می‌داند. این برنامه افزودنی نمی‌تواند حذف نصب شود.</translation>
+<translation id="6027032947578871493">محصول نامشخص <ph name="PRODUCT_ID" /> از <ph name="VENDOR_NAME" /> (شماره سریال <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> از فروشنده <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">بسته برنامهٔ افزودنی را نمی‌توان باز کرد. برای باز کردن ایمن بسته برنامهٔ افزودنی، باید یک مسیر در فهرست نمایهٔ شما وجود داشته باشد که فاقد پیوند نمادی باشد. چنین مسیری برای نمایهٔ شما وجود ندارد.</translation>
+<translation id="616804573177634438">{0,select, single{برنامه «<ph name="APP_NAME" />» درخواست دسترسی به یکی از دستگاه‌هایتان را دارد.}multiple{برنامه «<ph name="APP_NAME" />» درخواست دسترسی به یک یا چند دستگاه‌هتان را دارد.}other{UNUSED}}</translation>
+<translation id="641087317769093025">برنامه افزودنی از حالت زیپ خارج نشد</translation>
+<translation id="657064425229075395">بارگیری اسکریپت پس‌زمینه "<ph name="BACKGROUND_SCRIPT" />" ممکن نیست.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> از <ph name="VENDOR_NAME" /> (شماره سریال <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">ایجاد دایرکتوری برای خارج کردن «<ph name="DIRECTORY_PATH" />» از حالت زیپ امکان‌پذیر نیست</translation>
+<translation id="677806580227005219">Mimehandler: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">این برنامه افزودنی بیش از حد خود را بارگیری مجدد می‌کند.</translation>
+<translation id="7003844668372540529">محصول نامشخص <ph name="PRODUCT_ID" /> از <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">سرپرست این دستگاه نصب <ph name="EXTENSION_NAME" /> را لازم کرده است. آن را نمی‌توان تغییر داد یا حذف کرد.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (شناسه فایل افزودنی "<ph name="EXTENSION_ID" />") توسط سرپرست مسدود شده است.</translation>
+<translation id="7972881773422714442">گزینه‌ها: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">این برنامه افزودنی نتوانست نام‌گذاری دانلود «<ph name="ATTEMPTED_FILENAME" />» را انجام دهد زیرا یک برنامه افزودنی دیگر (<ph name="EXTENSION_NAME" />) نام فایل دیگری «<ph name="ACTUAL_FILENAME" />» را تعیین کرد.</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> از <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">این فایل افزودنی نتوانست مدارک صلاحیت درخواست شبکه را ارائه دهد چون فایل افزودنی دیگری (<ph name="EXTENSION_NAME" />) مدارک صلاحیت دیگری را ارائه کرده است.</translation>
+<translation id="8602184400052594090">فایل اظهارنامه وجود ندارد یا قابل خواندن نیست.</translation>
+<translation id="8636666366616799973">بسته نامعتبر است. جزئیات: "<ph name="ERROR_MESSAGE" />"</translation>
+<translation id="8670869118777164560">این فایل افزودنی نتوانست درخواست شبکه را به <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> مجدداً هدایت کند چون فایل افزودنی دیگری (<ph name="EXTENSION_NAME" />) آن را به <ph name="ACTUAL_REDIRECT_DESTINATION" /> مجدداً هدایت کرد.</translation>
+<translation id="8712265948125780616">بازکننده بسته افزونه</translation>
+<translation id="8825366169884721447">این فایل افزودنی نتوانست عنوان درخواست «<ph name="HEADER_NAME" />» درخواست شبکه را تغییر دهد چون تغییر با فایل افزودنی دیگر (<ph name="EXTENSION_NAME" />) تناقض داشت.</translation>
+<translation id="9111791539553342076">این فایل افزودنی نتوانست عنوان پاسخ «<ph name="HEADER_NAME" />» درخواست شبکه را تغییر دهد چون تغییر با فایل افزودنی دیگر نتاقض داشت (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_fi.xtb b/chromium/extensions/strings/extensions_strings_fi.xtb
new file mode 100644
index 00000000000..0940306993f
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_fi.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="fi">
+<translation id="1135328998467923690">Paketti on virheellinen: <ph name="ERROR_CODE" />.</translation>
+<translation id="1256619696651732561">Laajennusluettelon jäsentäjä</translation>
+<translation id="1445572445564823378">Tämä laajennus hidastaa tuotetta <ph name="PRODUCT_NAME" />. Poista laajennus käytöstä tehostaaksesi tuotteen <ph name="PRODUCT_NAME" /> toimintaa.</translation>
+<translation id="149347756975725155">Laajennuskuvakkeen <ph name="ICON" /> lataaminen ei onnistunut.</translation>
+<translation id="1803557475693955505">Taustasivun <ph name="BACKGROUND_PAGE" /> lataaminen ei onnistunut.</translation>
+<translation id="2159915644201199628">Kuvan koodin purkaminen epäonnistui: <ph name="IMAGE_NAME" /></translation>
+<translation id="2350172092385603347">Lokalisaatiota käytetään, mutta default_locale ei ole määritetty luettelossa.</translation>
+<translation id="2753617847762399167">Luvaton polku (absoluuttinen tai kohteeseen '..' liittyvä): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">Tämä laajennus ei voinut muuttaa verkkopyyntöä, koska muutos on ristiriidassa toisen laajennuksen kanssa.</translation>
+<translation id="2857834222104759979">Luettelotiedosto on virheellinen.</translation>
+<translation id="2988488679308982380">Paketin <ph name="ERROR_CODE" /> asentaminen epäonnistui.</translation>
+<translation id="3115238746683532089">Tuntematon tuote <ph name="PRODUCT_ID" /> toimittajalta <ph name="VENDOR_ID" /> (sarjanumero <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Tuntematon tuote <ph name="PRODUCT_ID" /> toimittajalta <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Laajennuksen purkaminen epäonnistui. Voit purkaa laajennuksen määrittämällä polun profiilihakemistoon, joka alkaa kiintolevyn kirjaimella eikä sisällä liitos- tai liityntäkohtaa tai symlink-määritettä. Profiilissasi ei ole kyseistä polkua.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (sarjanumero <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> toimittajalta <ph name="VENDOR_ID" /> (sarjanumero <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Käynnistyssivun <ph name="PAGE" /> lataaminen ei onnistu.</translation>
+<translation id="388442998277590542">Asetussivun <ph name="OPTIONS_PAGE" /> lataaminen epäonnistui.</translation>
+<translation id="4115165561519362854">Tämän laitteen järjestelmänvalvoja edellyttää laajennuksen <ph name="EXTENSION_NAME" /> version olevan vähintään <ph name="EXTENSION_VERSION" />. Laajennusta ei voida ottaa käyttöön ennen kuin se on päivitetty kyseiseen tai sitä uudempaan versioon.</translation>
+<translation id="4233778200880751280">Tietosivun <ph name="ABOUT_PAGE" /> lataus epäonnistui.</translation>
+<translation id="4434145631756268951">{0,select, single{Valitse USB-laite}multiple{Valitse USB-laitteet}other{EI KÄYTÖSSÄ}}</translation>
+<translation id="4811956658694082538">Pakettia ei voi asentaa, koska apuohjelmaprosessi kaatui. Käynnistä Chrome uudelleen ja yritä uudelleen.</translation>
+<translation id="5026754133087629784">Verkkonäkymä: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Tämä laajennus sisältää avaintiedoston <ph name="KEY_PATH" />. Et todennäköisesti halua sitä.</translation>
+<translation id="5627523580512561598">laajennus <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Valitse HID-laite}multiple{Valitse HID-laitteet}other{EI KÄYTÖSSÄ}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Tämän laitteen järjestelmänvalvoja on määrittänyt, että laitteessa täytyy olla <ph name="EXTENSION_NAME" />. Laajennusta ei voi poistaa.</translation>
+<translation id="6027032947578871493">Tuntematon laite <ph name="PRODUCT_ID" /> toimittajalta <ph name="VENDOR_NAME" /> (sarjanumero <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> toimittajalta <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Laajennuksen purkaminen epäonnistui. Voit purkaa laajennuksen määrittämällä polun profiilihakemistoon, joka ei sisällä symlink-määritettä. Profiilissasi ei ole kyseistä polkua.</translation>
+<translation id="616804573177634438">{0,select, single{Sovellus <ph name="APP_NAME" /> pyytää laitteesi käyttöoikeutta.}multiple{Sovellus <ph name="APP_NAME" /> pyytää yhden tai useamman laitteesi käyttöoikeutta.}other{EI KÄYTÖSSÄ}}</translation>
+<translation id="641087317769093025">Laajennuksen purkaminen epäonnistui</translation>
+<translation id="657064425229075395">Taustakoodin <ph name="BACKGROUND_SCRIPT" /> lataaminen epäonnistui.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> myyjältä <ph name="VENDOR_NAME" /> (sarjanumero <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Ei voitu luoda hakemistoa <ph name="DIRECTORY_PATH" /> laajennuksen purkamista varten:</translation>
+<translation id="677806580227005219">Mimehandler: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Tämä laajennus päivittyy liian usein.</translation>
+<translation id="7003844668372540529">Tuntematon tuote <ph name="PRODUCT_ID" /> toimittajalta <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Koneen ylläpitäjä vaatii laajennuksen <ph name="EXTENSION_NAME" /> asennusta. Sitä ei voi poistaa tai muuttaa.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (laajennustunnus <ph name="EXTENSION_ID" />) on järjestelmänvalvojan kiellettyjen laajennuksien luettelossa.</translation>
+<translation id="7972881773422714442">Vaihtoehdot: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Laajennus ei voinut antaa lataukselle nimeä <ph name="ATTEMPTED_FILENAME" />, koska toinen laajennus (<ph name="EXTENSION_NAME" />) määritti toisen tiedostonimen (<ph name="ACTUAL_FILENAME" />).</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> myyjältä <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Tämä laajennus ei antanut käyttöoikeustietoja verkkopyynnölle, koska toinen laajennus (<ph name="EXTENSION_NAME" />) antoi toiset käyttöoikeustiedot.</translation>
+<translation id="8602184400052594090">Luettelotiedosto puuttuu tai sitä ei voi lukea.</translation>
+<translation id="8636666366616799973">Paketti on virheellinen. Tietoja: <ph name="ERROR_MESSAGE" />.</translation>
+<translation id="8670869118777164560">Tämä laajennus ei uudelleenohjannut verkkopyyntöä kohteeseen <ph name="ATTEMPTED_REDIRECT_DESTINATION" />, koska toinen laajennus (<ph name="EXTENSION_NAME" />) uudelleenohjasi sen kohteeseen <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Laajennuksien purkaja</translation>
+<translation id="8825366169884721447">Tämä laajennus ei muokannut verkkopyynnön otsikkoa <ph name="HEADER_NAME" />, koska muokkaus oli ristiriidassa toisen laajennuksen (<ph name="EXTENSION_NAME" />) kanssa.</translation>
+<translation id="9111791539553342076">Tämä laajennus ei muokannut verkkopyynnön vastauksen otsikkoa <ph name="HEADER_NAME" />, koska muokkaus oli ristiriidassa toisen laajennuksen (<ph name="EXTENSION_NAME" />) kanssa.</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_fil.xtb b/chromium/extensions/strings/extensions_strings_fil.xtb
new file mode 100644
index 00000000000..3253ecc0913
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_fil.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="fil">
+<translation id="1135328998467923690">Di-wasto ang package: '<ph name="ERROR_CODE" />'.</translation>
+<translation id="1256619696651732561">Pang-parse ng Manifest ng Extension</translation>
+<translation id="1445572445564823378">Pinapabagal ng extension na ito ang <ph name="PRODUCT_NAME" />. Dapat mo itong huwag paganahin upang ipanumbalik ang pagganap ng <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">Maaaring hindi mai-load ang icon ng extension '<ph name="ICON" />'.</translation>
+<translation id="1803557475693955505">Maaaring hindi mai-load ang pahina ng background '<ph name="BACKGROUND_PAGE" />'.</translation>
+<translation id="2159915644201199628">Hindi ma-decode ang larawan: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">Ginamit ang localization , subalit hindi natukoy ang default_locale sa manipesto.</translation>
+<translation id="2753617847762399167">Ilegal na daanan (ganap o may kaugnayan sa '..'): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">Nabigong bawiin ng extension na ito ang isang kahilingan ng network dahil sumalungat ang pagbabago sa isa pang extension.</translation>
+<translation id="2857834222104759979">Di-wasto ang manifest file.</translation>
+<translation id="2988488679308982380">Hindi ma-install ang package: '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089">Hindi kilalang produkto na <ph name="PRODUCT_ID" /> mula sa vendor na <ph name="VENDOR_ID" /> (serial number na <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Hindi kilalang produkto na <ph name="PRODUCT_ID" /> mula sa vendor na <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Hindi ma-unpack ang extension. Upang ligtas na ma-unpack ang isang extension, dapat na mayroong daanan patungo sa iyong direktoryo ng profile na nagsisimula sa isang drive letter at walang nilalamang junction, mount point, o symlink. Walang ganoong daanan ang umiiral para sa iyong profile.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (serial number na <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> mula sa vendor na <ph name="VENDOR_ID" /> (serial number na <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Hindi ma-load ang page ng launcher na '<ph name="PAGE" />.'</translation>
+<translation id="388442998277590542">Hindi ma-load ang pahina ng mga pagpipilian na '<ph name="OPTIONS_PAGE" />'.</translation>
+<translation id="4115165561519362854">Iniaatas ng administrator ng computer na ito sa <ph name="EXTENSION_NAME" /> na magkaroon ng minimum na bersyon na <ph name="EXTENSION_VERSION" />. Hindi ito mae-enable hanggang sa ma-update ito sa bersyong iyon (o mas bago).</translation>
+<translation id="4233778200880751280">Hindi ma-load ang page na tungkol dito na '<ph name="ABOUT_PAGE" />'.</translation>
+<translation id="4434145631756268951">{0,select, single{Pumili ng USB device}multiple{Pumili ng mga USB device}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Hindi na-install ang package dahil nag-crash ang isang proseso ng utility. Subukang i-restart ang Chrome at subukang muli.</translation>
+<translation id="5026754133087629784">Webview: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Kasama sa extension na ito ang key file na '<ph name="KEY_PATH" />'. Malamang na hindi mo iyon gustong gawin.</translation>
+<translation id="5627523580512561598">extension na <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Pumili ng HID device}multiple{Pumili ng mga HID device}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Kinakailangan ng administrator ng machine na ito na naka-install ang <ph name="EXTENSION_NAME" />. Hindi ito maaaring i-uninstall.</translation>
+<translation id="6027032947578871493">Hindi kilalang produkto na <ph name="PRODUCT_ID" /> mula sa <ph name="VENDOR_NAME" /> (serial number na <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> mula sa vendor na <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Hindi ma-unpack ang extension. Upang ligtas na ma-unpack ang isang extension, dapat na mayroong daanan patungo sa iyong direktoryo ng profile na walang nilalamang symlink. Walang naturang daanan ang umiiral para sa iyong profile.</translation>
+<translation id="616804573177634438">{0,select, single{Humihiling ang application na "<ph name="APP_NAME" />" ng access sa isa sa iyong mga device.}multiple{Humihiling ang application na "<ph name="APP_NAME" />" ng access sa isa o higit pa sa iyong mga device.}other{HINDI NAGAMIT}}</translation>
+<translation id="641087317769093025">Hindi ma-unzip ang extension</translation>
+<translation id="657064425229075395">Hindi ma-load ang script ng background na '<ph name="BACKGROUND_SCRIPT" />'.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> mula sa <ph name="VENDOR_NAME" /> (serial number <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Hindi malikha ang direktoryo para sa pag-unzip: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">Mimehandler: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Masyadong madalas nag-reload ang extension na ito.</translation>
+<translation id="7003844668372540529">Hindi kilalang produkto na <ph name="PRODUCT_ID" /> mula sa <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Kinakailangan ng administrator ng machine na ito na ma-install ang <ph name="EXTENSION_NAME" />. Hindi ito maaaring alisin o baguhin.</translation>
+<translation id="7809034755304591547">Ang <ph name="EXTENSION_NAME" /> (ID ng extension "<ph name="EXTENSION_ID" />") ay na-block ng administrator.</translation>
+<translation id="7972881773422714442">Mga Opsyon: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Nabigong pangalanan ng extension na ito ang download na "<ph name="ATTEMPTED_FILENAME" />" dahil nakatukoy ang isa pang extension (<ph name="EXTENSION_NAME" />) ng ibang filename na "<ph name="ACTUAL_FILENAME" />".</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> mula sa <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Nabigo ang extension na ito na magbigay ng mga kredensyal sa isang kahilingan sa network dahil may isa pang extension (<ph name="EXTENSION_NAME" />) na nagbigay ng ibang mga kredensyal.</translation>
+<translation id="8602184400052594090">Nawawala o hindi mabasa ang manifest file.</translation>
+<translation id="8636666366616799973">Di-wasto ang package. Mga Detalye: '<ph name="ERROR_MESSAGE" />'.</translation>
+<translation id="8670869118777164560">Nabigo ang extension na ito na mag-redirect ng kahilingan sa network sa <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> dahil may isa pang extension (<ph name="EXTENSION_NAME" />) na nag-redirect nito sa <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Pang-unpack ng Extension</translation>
+<translation id="8825366169884721447">Nabigo ang extension na ito na baguhin ang header ng kahilingan na "<ph name="HEADER_NAME" />" ng isang kahilingan sa network dahil sumalungat ang pagbabago sa isa pang extension (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Nabigo ang extension na ito na baguhin ang header ng tugon na "<ph name="HEADER_NAME" />" ng isang kahilingan sa network dahil sumalungat ang pagbabago sa isa pang extension (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_fr.xtb b/chromium/extensions/strings/extensions_strings_fr.xtb
new file mode 100644
index 00000000000..36143c583c4
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_fr.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="fr">
+<translation id="1135328998467923690">Package incorrect : "<ph name="ERROR_CODE" />".</translation>
+<translation id="1256619696651732561">Analyse des fichiers manifestes des extensions</translation>
+<translation id="1445572445564823378">Cette extension ralentit <ph name="PRODUCT_NAME" />. Vous devez la désactiver pour rétablir les performances de <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">Impossible de charger l'icône de l'extension "<ph name="ICON" />".</translation>
+<translation id="1803557475693955505">Impossible de charger la page d'arrière-plan "<ph name="BACKGROUND_PAGE" />".</translation>
+<translation id="2159915644201199628">Impossible de décoder l'image : "<ph name="IMAGE_NAME" />".</translation>
+<translation id="2350172092385603347">Localisation utilisée, mais les paramètres régionaux par défaut (default_locale) n'ont pas été indiqués dans le manifeste. </translation>
+<translation id="2753617847762399167">Chemin (absolu ou par rapport à "..") non valide : "<ph name="IMAGE_PATH" />"</translation>
+<translation id="27822970480436970">Cette extension n'a pas réussi à modifier une requête réseau, car la modification était en conflit avec une autre extension.</translation>
+<translation id="2857834222104759979">Le fichier manifeste est incorrect.</translation>
+<translation id="2988488679308982380">Impossible d'installer le package : "<ph name="ERROR_CODE" />"</translation>
+<translation id="3115238746683532089">Produit inconnu <ph name="PRODUCT_ID" /> du fournisseur <ph name="VENDOR_ID" /> (numéro de série : <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Produit inconnu <ph name="PRODUCT_ID" /> du fournisseur <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Impossible d'extraire les fichiers de l'extension. Pour effectuer cette opération en toute sécurité, vous devez disposer d'un chemin d'accès à votre répertoire de profils commençant par une lettre de lecteur et ne contenant ni jonction, ni point de montage, ni lien symbolique. Aucun chemin de ce type n'existe pour votre profil.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (numéro de série : <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> du fournisseur <ph name="VENDOR_ID" /> (numéro de série : <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Impossible de charger la page du lanceur d'applications "<ph name="PAGE" />".</translation>
+<translation id="388442998277590542">Impossible de charger la page d'options "<ph name="OPTIONS_PAGE" />".</translation>
+<translation id="4115165561519362854">L'administrateur de cette machine exige que <ph name="EXTENSION_NAME" /> dispose, au minimum, de la version <ph name="EXTENSION_VERSION" />. L'activation sera impossible tant que la mise à jour vers cette version (ou une version ultérieure) n'aura pas été effectuée.</translation>
+<translation id="4233778200880751280">Impossible de charger la page "À propos" <ph name="ABOUT_PAGE" />.</translation>
+<translation id="4434145631756268951">{0,select, single{Sélectionner un appareil USB}multiple{Sélectionner des appareils USB}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Impossible d'installer le package en raison du plantage d'un processus associé à un utilitaire. Veuillez redémarrer Chrome, puis réessayer.</translation>
+<translation id="5026754133087629784">Affichage du site Web : <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview : <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Cette extension comprend le fichier clé <ph name="KEY_PATH" />. Vous ne voulez probablement pas poursuivre.</translation>
+<translation id="5627523580512561598">Extension <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Sélectionner un appareil HID}multiple{Sélectionner des appareils HID}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView : <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">L'administrateur de cet ordinateur exige que l'extension <ph name="EXTENSION_NAME" /> soit installée. Cette extension ne pourra pas être désinstallée.</translation>
+<translation id="6027032947578871493">Produit inconnu <ph name="PRODUCT_ID" /> fourni par <ph name="VENDOR_NAME" /> (numéro de série : <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> du fournisseur <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Impossible d'extraire les fichiers de l'extension. Pour effectuer cette opération en toute sécurité, vous devez disposer d'un chemin d'accès à votre répertoire de profils ne contenant pas de lien symbolique. Aucun chemin de ce type n'existe pour votre profil.</translation>
+<translation id="616804573177634438">{0,select, single{L'application <ph name="APP_NAME" /> demande l'accès à l'un de vos appareils.}multiple{L'application <ph name="APP_NAME" /> demande l'accès à un ou plusieurs de vos appareils.}other{NON UTILISÉ}}</translation>
+<translation id="641087317769093025">Impossible de décompresser l'extension.</translation>
+<translation id="657064425229075395">Impossible de charger le script d'arrière-plan "<ph name="BACKGROUND_SCRIPT" />".</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> de <ph name="VENDOR_NAME" /> (numéro de série <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Impossible de créer le répertoire de décompression : "<ph name="DIRECTORY_PATH" />"</translation>
+<translation id="677806580227005219">Mimehandler : <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Cette extension s'est actualisée trop souvent.</translation>
+<translation id="7003844668372540529">Produit inconnu <ph name="PRODUCT_ID" /> fourni par <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">L'administrateur de cet ordinateur exige que l'extension <ph name="EXTENSION_NAME" /> soit installée. Celle-ci ne peut pas être supprimée ni modifiée.</translation>
+<translation id="7809034755304591547">L'administrateur a bloqué l'extension <ph name="EXTENSION_NAME" /> (ID : <ph name="EXTENSION_ID" />).</translation>
+<translation id="7972881773422714442">Options : <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Échec de l'attribution du nom "<ph name="ATTEMPTED_FILENAME" />" au fichier téléchargé via l'extension, car un nom de fichier différent, "<ph name="ACTUAL_FILENAME" />", a été attribué via une autre extension (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> de <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Des certificats n'ont pas pu être fournis à la requête réseau par cette extension, car des certificats différents ont été fournis par une autre extension (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="8602184400052594090">Fichier manifeste absent ou illisible</translation>
+<translation id="8636666366616799973">Package incorrect. Détails : "<ph name="ERROR_MESSAGE" />".</translation>
+<translation id="8670869118777164560">Une requête réseau n'a pas pu être redirigée vers <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> par cette extension, car elle a été redirigée vers <ph name="ACTUAL_REDIRECT_DESTINATION" /> par une autre extension (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="8712265948125780616">Décompression d'extensions</translation>
+<translation id="8825366169884721447">Le titre de demande d'une requête réseau ("<ph name="HEADER_NAME" />") n'a pas pu être modifié à partir de cette extension, car la modification demandée était en conflit avec une autre extension (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Le titre de réponse d'une requête réseau ("<ph name="HEADER_NAME" />") n'a pas pu être modifié à partir de cette extension, car la modification demandée était en conflit avec une autre extension (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_gu.xtb b/chromium/extensions/strings/extensions_strings_gu.xtb
new file mode 100644
index 00000000000..a18253bf73b
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_gu.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="gu">
+<translation id="1135328998467923690">પૅકેજ અમાન્ય છે : '<ph name="ERROR_CODE" />'.</translation>
+<translation id="1256619696651732561">વિસ્તાર મેનીફેસ્ટ વિશ્લેશક</translation>
+<translation id="1445572445564823378">આ એક્સ્ટેંશન <ph name="PRODUCT_NAME" /> ને ધીમુ કરી રહ્યું છે. <ph name="PRODUCT_NAME" /> ના પ્રદર્શનને પુનર્સ્થાપિત કરવા માટે તમારે તેને અક્ષમ કરવું જોઈએ. </translation>
+<translation id="149347756975725155">એક્સ્ટેંશન આયકન '<ph name="ICON" />' લોડ કરી શકાયું નથી.</translation>
+<translation id="1803557475693955505">પૃષ્ઠભૂમિ પૃષ્ઠ '<ph name="BACKGROUND_PAGE" />' લોડ કરી શકાયું નથી.</translation>
+<translation id="2159915644201199628">છબીને ડિકોડ કરી શક્યાં નથી: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">લૉકલાઇઝેશન વપરાયુ, પરંતુ default_locale નો ઉલ્લેખ મેનિફેસ્ટમાં નહોતો.</translation>
+<translation id="2753617847762399167">ગેરકાયદેસર પાથ (નિરપેક્ષ અથવા '..' સાથે સંબંધિત): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">આ એક્સ્ટેંશન નેટવર્ક વિનંતીનું સંશોધન કરવામાં નિષ્ફળ રહ્યું છે કારણ કે સંશોધન બીજા એક્સ્ટેંશનથી વિરોધાભાસી છે.</translation>
+<translation id="2857834222104759979">મેનિફેસ્ટ ફાઇલ અમાન્ય છે.</translation>
+<translation id="2988488679308982380">પૅકેજ ઇન્સ્ટોલ કરી શકાયું નહીં: '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089"><ph name="VENDOR_ID" /> વિક્રેતાનું અજાણ્યું ઉત્પાદન <ph name="PRODUCT_ID" /> (શૃંખલા ક્રમાંક <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963"><ph name="VENDOR_ID" /> વિક્રેતાનું અજાણ્યું ઉત્પાદન <ph name="PRODUCT_ID" /></translation>
+<translation id="3369521687965833290">એક્સ્ટેંશન અનપૅક કરી શકાતું નથી. એક્સ્ટેંશનને સુરક્ષિત રીતે અનપૅક કરવા માટે, તમારી પ્રોફાઇલ નિર્દેશિકાનો પાથ હોવો જોઈએ જે ડ્રાઇવ અક્ષરથી શરૂ થતો હોય અને કોઈ જંક્શન, માઉન્ટ પોઇન્ટ અથવા સિમલિંક ધરાવતો ન હોય. આવો કોઈ પાથ તમારી પ્રોફાઇલ માટે અસ્તિત્વમાં નથી.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (શૃંખલા ક્રમાંક <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="VENDOR_ID" /> વિક્રેતા તરફથી <ph name="PRODUCT_NAME" /> (શૃંખલા ક્રમાંક <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">લોન્ચર પૃષ્ઠ '<ph name="PAGE" />' લોડ કરી શકાયું નથી.</translation>
+<translation id="388442998277590542">વિકલ્પોનું પૃષ્ઠ '<ph name="OPTIONS_PAGE" />' લોડ કરી શકાયું નથી.</translation>
+<translation id="4115165561519362854">આ મશીનનાં વ્યવસ્થાપકને <ph name="EXTENSION_VERSION" /> ના ન્યૂનતમ સંસ્કરણ માટે <ph name="EXTENSION_NAME" /> ની જરૂર છે. તે જ્યાં સુધી તે સંસ્કરણ (અથવા તે પછીના) પર અપડેટ ન થાય ત્યાં સુધી સક્ષમ થઈ શકતું નથી.</translation>
+<translation id="4233778200880751280">'<ph name="ABOUT_PAGE" />' વિશે પૃષ્ઠ લોડ કરી શકાયું નથી.</translation>
+<translation id="4434145631756268951">{0,select, single{USB ઉપકરણ પસંદ કરો}multiple{USB ઉપકરણો પસંદ કરો}other{UNUSED}}</translation>
+<translation id="4811956658694082538">એક ઉપયોગિતા પ્રક્રિયા ક્રેશ થઈ હોવાને કારણે પૅકેજ ઇન્સ્ટોલ કરી શકાયું નથી. Chrome પુનઃપ્રારંભ કરી અને ફરીથી પ્રયાસ કરવાનો પ્રયાસ કરો.</translation>
+<translation id="5026754133087629784">વેબવ્યુ: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">આ એક્સટેન્શનમાં '<ph name="KEY_PATH" />' કી ફાઇલ શામેલ છે. તમે કદાચ એ કરવા માંગતા નથી.</translation>
+<translation id="5627523580512561598">એક્સ્ટેંશન <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{HID ઉપકરણ પસંદ કરો}multiple{HID ઉપકરણો પસંદ કરો}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">આ મશીનના વ્યવસ્થાપક માટે <ph name="EXTENSION_NAME" /> ઇન્સ્ટોલ કરેલું હોવું જરૂરી છે. તે અનઇન્સ્ટોલ કરી શકાતું નથી.</translation>
+<translation id="6027032947578871493"><ph name="VENDOR_NAME" /> વિક્રેતાનું અજાણ્યું ઉત્પાદન <ph name="PRODUCT_ID" /> (શૃંખલા ક્રમાંક <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="VENDOR_ID" /> વિક્રેતા તરફથી <ph name="PRODUCT_NAME" /></translation>
+<translation id="6143635259298204954">એક્સ્ટેંશન અનપૅક કરી શકાતું નથી. એક્સ્ટેંશનને સુરક્ષિત રીતે અનપૅક કરવા માટે, તમારી પ્રોફાઇલ નિર્દેશિકાનો પાથ હોવો જોઈએ જે સિમલિંક ધરાવતો ન હોય. આવો કોઈ પાથ તમારી પ્રોફાઇલ માટે અસ્તિત્વમાં નથી.</translation>
+<translation id="616804573177634438">{0,select, single{એપ્લિકેશન "<ph name="APP_NAME" />" તમારા ઉપકરણો પૈકી એક પરની ઍક્સેસની વિનંતી કરી રહી છે.}multiple{એપ્લિકેશન "<ph name="APP_NAME" />" તમારા ઉપકરણો પૈકી એક પરની ઍક્સેસની વિનંતી કરી રહી છે.}other{UNUSED}}</translation>
+<translation id="641087317769093025">એક્સ્ટેન્શન અનઝિપ કરી શકાયું નથી</translation>
+<translation id="657064425229075395">પૃષ્ઠભૂમિ સ્ક્રિપ્ટ '<ph name="BACKGROUND_SCRIPT" />' લોડ કરી શકાઈ નથી.</translation>
+<translation id="6580950983454333167"><ph name="VENDOR_NAME" /> નું <ph name="PRODUCT_NAME" /> (સીરિયલ નંબર <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">આને અનઝિપ કરવા માટે નિર્દેશિકા બનાવી શક્યાં નથી: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">Mimehandler: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">આ એક્સ્ટેન્શન પોતાની મેળે ઘણી વાર ફરીથી લોડ થયેલું.</translation>
+<translation id="7003844668372540529"><ph name="VENDOR_NAME" /> નું અજાણ્યું ઉત્પાદન <ph name="PRODUCT_ID" /></translation>
+<translation id="7217838517480956708">આ મશીનના વ્યવસ્થાપકને ઇન્સ્ટોલ કરવા માટે <ph name="EXTENSION_NAME" /> ની આવશ્કતા છે. તેને દૂર અથવા સંશોધિત કરી શકાશે નહીં.</translation>
+<translation id="7809034755304591547">વ્યવસ્થાપક દ્વારા <ph name="EXTENSION_NAME" /> (એક્સટેન્શન ID "<ph name="EXTENSION_ID" />") અવરોધિત કરેલ છે.</translation>
+<translation id="7972881773422714442">વિકલ્પો: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">આ એક્સટેન્શન "<ph name="ATTEMPTED_FILENAME" />" ને ડાઉનલોડ નામ આપવામાં નિષ્ફળ થયું કારણ કે અન્ય એક્સટેન્શન (<ph name="EXTENSION_NAME" />) એ એક જુદું ફાઇલનામ "<ph name="ACTUAL_FILENAME" />" નક્કી કર્યું છે.</translation>
+<translation id="8284835137979141223"><ph name="VENDOR_NAME" /> તરફથી <ph name="PRODUCT_NAME" /></translation>
+<translation id="8341840687457896278">આ એક્સટેન્શન એક નેટવર્ક વિનંતીને ઓળખપત્ર પ્રદાન કરવામાં નિષ્ફળ થયું કારણ કે અન્ય એક્સટેન્શન (<ph name="EXTENSION_NAME" />) એ વિવિધ ઓળખપત્ર પ્રદાન કર્યાં છે.</translation>
+<translation id="8602184400052594090">મેનિફેસ્ટ ફાઇલ ખૂટે છે અથવા વાંચવાયોગ્ય નથી.</translation>
+<translation id="8636666366616799973">પૅકેજ અમાન્ય છે. વિગતો: '<ph name="ERROR_MESSAGE" />'.</translation>
+<translation id="8670869118777164560">આ એક્સટેન્શન એક નેટવર્ક વિનંતીને <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> પર રીડાયરેક્ટ કરવામાં નિષ્ફળ થયું કારણ કે અન્ય એક્સટેન્શન (<ph name="EXTENSION_NAME" />) એ તેને <ph name="ACTUAL_REDIRECT_DESTINATION" /> પર રીડાયરેક્ટ કર્યું છે.</translation>
+<translation id="8712265948125780616">વિસ્તાર અનપેકર</translation>
+<translation id="8825366169884721447">આ એક્સટેન્શન એક નેટવર્ક વિનંતીની વિનંતી હેડર "<ph name="HEADER_NAME" />" ને સંશોધિત કરવામાં નિષ્ફળ થયુ કારણ કે સંશોધિકરણ અન્ય એક્સટેન્શન (<ph name="EXTENSION_NAME" />) સાથે વિરોધાભાસી છે.</translation>
+<translation id="9111791539553342076">આ એક્સટેન્શન એક નેટવર્ક વિનંતીની પ્રતિભાવ હેડર "<ph name="HEADER_NAME" />" ને સંશોધિત કરવામાં નિષ્ફળ થયું કારણ કે સંશોધિકરણ અન્ય એક્સટેન્શન (<ph name="EXTENSION_NAME" />) સાથે વિરોધાભાસી છે.</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_hi.xtb b/chromium/extensions/strings/extensions_strings_hi.xtb
new file mode 100644
index 00000000000..067cbd676df
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_hi.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="hi">
+<translation id="1135328998467923690">पैकेज अमान्य है: '<ph name="ERROR_CODE" />'.</translation>
+<translation id="1256619696651732561">एक्‍सटेंशन मेनिफ़ेस्‍ट पार्सर</translation>
+<translation id="1445572445564823378">यह एक्‍सटेंशन धीमा हो रहा है <ph name="PRODUCT_NAME" />. आपको <ph name="PRODUCT_NAME" /> का निष्पादन पुनर्स्‍थापित करने के लिए इसे अक्षम करना चाहिए.</translation>
+<translation id="149347756975725155">एक्सटेंशन आइकन '<ph name="ICON" />' लोड नहीं कर सका.</translation>
+<translation id="1803557475693955505">पृष्ठभूमि पृष्ठ '<ph name="BACKGROUND_PAGE" />' को लोड नहीं कर सका.</translation>
+<translation id="2159915644201199628">चित्र को डीकोड नहीं किया जा सका: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">स्थानीयकरण का उपयोग किया गया, लेकिन default_locale को मालसूची में निर्दिष्ट नहीं किया गया था.</translation>
+<translation id="2753617847762399167">गलत पथ (पूर्ण या '..' से संबंधित): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">य‍ह एक्‍सटेंशन, नेटवर्क अनुरोध संशोधित करने में विफल रहा क्‍योंकि संशोधन का अन्‍य एक्‍सटेंशन के साथ विरोध हुआ.</translation>
+<translation id="2857834222104759979">मालसूची फ़ाइल अमान्य है.</translation>
+<translation id="2988488679308982380">पैकेज इंस्टॉल नहीं कर सका: '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089"><ph name="VENDOR_ID" /> (क्रमांक <ph name="SERIAL_NUMBER" />) विक्रेता की ओर से <ph name="PRODUCT_ID" /> अज्ञात उत्पाद</translation>
+<translation id="3163201441334626963"><ph name="VENDOR_ID" /> की ओर से अज्ञात <ph name="PRODUCT_ID" /> उत्पाद</translation>
+<translation id="3369521687965833290">एक्सटेंशन को अनपैक नहीं कर सका. किसी एक्सटेंशन को सुरक्षित रूप से अनपैक करने के लिए आपकी प्रोफ़ाइल निर्देशिका में एक ऐसा पथ होना आवश्यक है, जिसमें कोई सिमलिंक न हो. ऐसा कोई भी पथ आपकी प्रोफ़ाइल के लिए मौजूद नहीं है.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (क्रमांक <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="VENDOR_ID" /> (क्रमांक <ph name="SERIAL_NUMBER" />) विक्रेता की ओर से <ph name="PRODUCT_NAME" /></translation>
+<translation id="3624204664103857160">लॉन्‍चर पृष्‍ठ '<ph name="PAGE" />' लोड नहीं हो सका.</translation>
+<translation id="388442998277590542">विकल्‍प पृष्ठ '<ph name="OPTIONS_PAGE" />' लोड नहीं कर सका.</translation>
+<translation id="4115165561519362854">इस मशीन का नियंत्रक चाहता है कि <ph name="EXTENSION_NAME" /> के पास <ph name="EXTENSION_VERSION" /> का एक न्‍यूनतम वर्शन हो. इसे तब तक सक्षम नहीं किया जा सकता जब तक कि उसे उस वर्शन (या बाद वाले वर्शन) में अपडेट ना कर दिया गया हो.</translation>
+<translation id="4233778200880751280">संक्षिप्त विवरण पृष्ठ '<ph name="ABOUT_PAGE" />' लोड नहीं हो सका.</translation>
+<translation id="4434145631756268951">{0,select, single{USB डिवाइस चुनें}multiple{USB डिवाइस चुनें}other{UNUSED}}</translation>
+<translation id="4811956658694082538">उपयोगिता प्रक्रिया क्रैश होने के कारण पैकेज इंस्‍टॉल नहीं किया जा सका. Chrome को पुन: प्रारंभ करके और पुन: प्रयास करके देखें.</translation>
+<translation id="5026754133087629784">वेबदृश्य: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">ऐप्‍स दृश्‍य: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">एक्सटेंशन में कुंजी फ़ाइल '<ph name="KEY_PATH" />' शामिल है. संभवतः आप ऐसा नहीं करना चाहते हैं.</translation>
+<translation id="5627523580512561598">एक्सटेंशन <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{HID डिवाइस चुनें}multiple{HID डिवाइस चुनें}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">इस मशीन के व्‍यवस्‍थापक के लिए आवश्‍यक है कि <ph name="EXTENSION_NAME" /> इंस्‍टॉल किया जाए. उसे अनइंस्‍टॉल नहीं किया जा सकता.</translation>
+<translation id="6027032947578871493"><ph name="VENDOR_NAME" /> (क्रमांक <ph name="SERIAL_NUMBER" />) की ओर से अज्ञात <ph name="PRODUCT_ID" /> उत्पाद</translation>
+<translation id="6068932090455285721"><ph name="VENDOR_ID" /> विक्रेता की ओर से <ph name="PRODUCT_NAME" /></translation>
+<translation id="6143635259298204954">एक्सटेंशन को अनपैक नहीं कर सका. किसी एक्सटेंशन को सुरक्षित रूप से अनपैक करने के लिए आपकी प्रोफ़ाइल निर्देशिका में एक ऐसा पथ होना आवश्यक है, जिसमें कोई सिमलिंक न हो. ऐसा कोई भी पथ आपकी प्रोफ़ाइल के लिए मौजूद नहीं है.</translation>
+<translation id="616804573177634438">{0,select, single{"<ph name="APP_NAME" />" ऐप्‍लिकेशन आपके किसी एक डिवाइस की ऐक्‍सेस का अनुरोध कर रहा है.}multiple{"<ph name="APP_NAME" />" ऐप्‍लिकेशन आपके किसी एक डिवाइस की ऐक्‍सेस का अनुरोध कर रहे हैं.}other{अप्रयुक्त}}</translation>
+<translation id="641087317769093025">एक्सटेंशन को अनज़िप नहीं किया जा सका</translation>
+<translation id="657064425229075395">पृष्ठभूमि स्क्रिप्ट '<ph name="BACKGROUND_SCRIPT" />' लोड नहीं की जा सकी.</translation>
+<translation id="6580950983454333167"><ph name="VENDOR_NAME" /> का <ph name="PRODUCT_NAME" /> (सीरियल नंबर <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">अनज़िप करने के लिए निर्देशिका नहीं बनाई जा सकी: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">माइमहैंडलर: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">इस एक्सटेंशन ने स्वयं को बहुत जल्दी-जल्दी फिर से लोड किया.</translation>
+<translation id="7003844668372540529"><ph name="VENDOR_NAME" /> की ओर से अज्ञात <ph name="PRODUCT_ID" /> उत्पाद</translation>
+<translation id="7217838517480956708">इस मशीन का व्यवस्थापक चाहता है कि <ph name="EXTENSION_NAME" /> इंस्टॉल किया हुआ हो. इसे निकाला या संशोधित नहीं किया जा सकता.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (एक्सटेंशन आईडी "<ph name="EXTENSION_ID" />") को व्यवस्थापक द्वारा प्रतिबंधित किया गया है.</translation>
+<translation id="7972881773422714442">विकल्‍प: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">यह एक्सटेंशन "<ph name="ATTEMPTED_FILENAME" />" डाउनलोड को नाम देने में विफल रहा क्योंकि किसी अन्य एक्सटेंशन (<ph name="EXTENSION_NAME" />) ने एक भिन्न फ़ाइलनाम "<ph name="ACTUAL_FILENAME" />" निर्धारित कर लिया है.</translation>
+<translation id="8284835137979141223"><ph name="VENDOR_NAME" /> की ओर से <ph name="PRODUCT_NAME" /></translation>
+<translation id="8341840687457896278">यह एक्सटेंशन किसी नेटवर्क अनुरोध को प्रमाणिकता प्रदान करने में विफल रहा क्योंकि एक अन्य एक्सटेंशन (<ph name="EXTENSION_NAME" />) ने भिन्न प्रमाणिकता प्रदान कर दिए थे.</translation>
+<translation id="8602184400052594090">मालसूची फ़ाइल गुम है या पढ़ने योग्य नहीं है.</translation>
+<translation id="8636666366616799973">पैकेज अमान्य है. विवरण: '<ph name="ERROR_MESSAGE" />'.</translation>
+<translation id="8670869118777164560">यह एक्सटेंशन <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> तक नेटवर्क अनुरोध को रीडायरेक्ट करने में विफल रहा क्योंकि एक अन्य एक्सटेंशन (<ph name="EXTENSION_NAME" />) ने इसे <ph name="ACTUAL_REDIRECT_DESTINATION" /> को रीडायरेक्ट कर दिया था.</translation>
+<translation id="8712265948125780616">एक्‍सटेंशन अनपैकर</translation>
+<translation id="8825366169884721447">यह एक्सटेंशन किसी नेटवर्क अनुरोध के अनुरोध शीर्षलेख "<ph name="HEADER_NAME" />" को बदलने में विफल रहा क्योंकि बदलाव का एक अन्य एक्सटेंशन (<ph name="EXTENSION_NAME" />) के साथ विरोध हुआ था.</translation>
+<translation id="9111791539553342076">यह एक्सटेंशन किसी नेटवर्क अनुरोध के प्रतिसाद शीर्षलेख "<ph name="HEADER_NAME" />" के प्रतिसाद शीर्षलेख को बदलने में विफल रहा क्योंकि बदलाव का एक अन्य एक्सटेंशन (<ph name="EXTENSION_NAME" />) के साथ विरोध हुआ था.</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_hr.xtb b/chromium/extensions/strings/extensions_strings_hr.xtb
new file mode 100644
index 00000000000..938cc840fbe
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_hr.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="hr">
+<translation id="1135328998467923690">Paket je nevažeći: "<ph name="ERROR_CODE" />".</translation>
+<translation id="1256619696651732561">Raščlanjivanje manifesta proširenja</translation>
+<translation id="1445572445564823378">Ovo proširenje usporava uslugu <ph name="PRODUCT_NAME" />. Trebali biste ga onemogućiti da biste vratili uspješan rad usluge <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">Nije uspjelo učitavanje ikone proširenja "<ph name="ICON" />".</translation>
+<translation id="1803557475693955505">Nije uspjelo učitavanje pozadinske stranice "<ph name="BACKGROUND_PAGE" />".</translation>
+<translation id="2159915644201199628">Nije uspjelo dekodiranje slike: "<ph name="IMAGE_NAME" />"</translation>
+<translation id="2350172092385603347">Lokalizacija je korištena, ali default_locale nije naveden u manifestu.</translation>
+<translation id="2753617847762399167">Neispravna putanja (apsolutna ili relativna u odnosu na ".."): "<ph name="IMAGE_PATH" />"</translation>
+<translation id="27822970480436970">Ovo proširenje nije uspjelo izmijeniti mrežni zahtjev jer je izmjena u sukobu s drugim proširenjem.</translation>
+<translation id="2857834222104759979">Datoteka manifesta nije važeća.</translation>
+<translation id="2988488679308982380">Nije moguće instaliranje paketa: "<ph name="ERROR_CODE" />"</translation>
+<translation id="3115238746683532089">Nepoznati proizvod <ph name="PRODUCT_ID" /> dobavljača <ph name="VENDOR_ID" /> (serijski broj <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Nepoznati proizvod <ph name="PRODUCT_ID" /> dobavljača <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Ne mogu otpakirati proširenje. Kako biste sigurno otpakirali proširenje, mora postojati putanja do direktorija vašeg profila koja počinje slovom pogona i ne sadrži spoj, točku povezivanja ili simboličku vezu. Za vaš profil ne postoji takva putanja.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (serijski broj <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> dobavljača <ph name="VENDOR_ID" /> (serijski broj <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Nije uspjelo učitavanje stranice pokretača "<ph name="PAGE" />".</translation>
+<translation id="388442998277590542">Nije uspjelo učitavanje stranice opcija "<ph name="OPTIONS_PAGE" />".</translation>
+<translation id="4115165561519362854">Administrator ovog uređaja zahtijeva da <ph name="EXTENSION_NAME" /> ima minimalnu verziju <ph name="EXTENSION_VERSION" />. Ne može se omogućiti dok se ne ažurira na tu ili višu verziju.</translation>
+<translation id="4233778200880751280">Nije uspjelo učitavanje stranice s informacijama "<ph name="ABOUT_PAGE" />".</translation>
+<translation id="4434145631756268951">{0,select, single{Odabir USB uređaja}multiple{Odabir USB uređaja}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Nije bilo moguće instalirati paket jer se uslužni proces srušio. Ponovo pokrenite Chrome i pokušajte opet.</translation>
+<translation id="5026754133087629784">Prikaz web-lokacije: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">To proširenje uključuje ključnu datoteku "<ph name="KEY_PATH" />". Vjerojatno ne želite to učiniti.</translation>
+<translation id="5627523580512561598">proširenje <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Odabir HID uređaja}multiple{Odabir HID uređaja}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Administrator ovog računala zahtijeva instaliranje proširenja <ph name="EXTENSION_NAME" /> i ono se ne može deinstalirati.</translation>
+<translation id="6027032947578871493">Nepoznati proizvod <ph name="PRODUCT_ID" />, <ph name="VENDOR_NAME" /> (serijski broj <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> dobavljača <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Ne mogu otpakirati proširenje. Kako biste sigurno otpakirali proširenje, mora postojati putanja do direktorija vašeg profila koja ne sadrži simboličku vezu. Za vaš profil ne postoji takva putanja.</translation>
+<translation id="616804573177634438">{0,select, single{Aplikacija "<ph name="APP_NAME" />" zahtijeva pristup nekom od vaših uređaja.}multiple{Aplikacija "<ph name="APP_NAME" />" zahtijeva pristup nekom od vaših uređaja}other{NE UPOTREBLJAVA SE}}</translation>
+<translation id="641087317769093025">Nije uspjelo raspakiravanje proširenja</translation>
+<translation id="657064425229075395">Nije bilo moguće učitati pozadinsku skriptu "<ph name="BACKGROUND_SCRIPT" />".</translation>
+<translation id="6580950983454333167">pristupiti uređaju <ph name="PRODUCT_NAME" /> dobavljača <ph name="VENDOR_NAME" /> (serijski broj <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Nije uspjelo stvaranje direktorija za raspakiravanje: "<ph name="DIRECTORY_PATH" />"</translation>
+<translation id="677806580227005219">Mimehandler: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Proširenje se prečesto ponovno učitavalo.</translation>
+<translation id="7003844668372540529">Nepoznati proizvod <ph name="PRODUCT_ID" />, <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Administrator ovog računala zahtijeva instalaciju proširenja <ph name="EXTENSION_NAME" />. Ona se ne može ukloniti ni izmijeniti.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (ID proširenja "<ph name="EXTENSION_ID" />") blokirao je administrator.</translation>
+<translation id="7972881773422714442">Opcije: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">To proširenje nije uspjelo dodijeliti naziv preuzimanju "<ph name="ATTEMPTED_FILENAME" />" jer je drugo proširenje (<ph name="EXTENSION_NAME" />) odredilo drugačiji naziv datoteke "<ph name="ACTUAL_FILENAME" />".</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> od dobavljača <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Ovo proširenje nije uspješno dodijelilo vjerodajnice za mrežni zahtjev jer je drugo proširenje (<ph name="EXTENSION_NAME" />) dodijelilo druge vjerodajnice.</translation>
+<translation id="8602184400052594090">Datoteka manifesta nedostaje ili nije čitljiva.</translation>
+<translation id="8636666366616799973">Paket je nevažeći. Pojedinosti "<ph name="ERROR_MESSAGE" />".</translation>
+<translation id="8670869118777164560">Ovo proširenje nije uspjelo preusmjeriti mrežni zahtjev na odredište <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> jer ga je drugo proširenje (<ph name="EXTENSION_NAME" />) preusmjerilo na odredište <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Otpakiravanje proširenja</translation>
+<translation id="8825366169884721447">Ovo proširenje nije uspjelo izmijeniti zaglavlje zahtjeva "<ph name="HEADER_NAME" />" mrežnog zahtjeva jer je izmjena sukobljena s drugim proširenjem (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Ovo proširenje nije uspjelo izmijeniti zaglavlje odgovora "<ph name="HEADER_NAME" />" mrežnog zahtjeva jer je izmjena sukobljena s drugim proširenjem (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_hu.xtb b/chromium/extensions/strings/extensions_strings_hu.xtb
new file mode 100644
index 00000000000..ee16f467dce
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_hu.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="hu">
+<translation id="1135328998467923690">A következő csomag érvénytelen: "<ph name="ERROR_CODE" />".</translation>
+<translation id="1256619696651732561">Bővítményjegyzék elemzője</translation>
+<translation id="1445572445564823378">Ez a bővítmény lassítja a <ph name="PRODUCT_NAME" /> működését. Kapcsolja ki a <ph name="PRODUCT_NAME" /> teljesítményének visszaállításához.</translation>
+<translation id="149347756975725155">A(z) '<ph name="ICON" />' bővítményikon betöltése nem sikerült.</translation>
+<translation id="1803557475693955505">Nem lehet betölteni a(z) '<ph name="BACKGROUND_PAGE" />' háttéroldalt.</translation>
+<translation id="2159915644201199628">Nem sikerült dekódolni a képet: „<ph name="IMAGE_NAME" />”</translation>
+<translation id="2350172092385603347">Fordítás használatban, de a default_locale (alapértelmezett nyelv- és országkód) nincs megadva a jegyzékfájlban.</translation>
+<translation id="2753617847762399167">Szabálytalan útvonal (abszolút vagy relatív „..” taggal): „<ph name="IMAGE_PATH" />”</translation>
+<translation id="27822970480436970">A bővítmény nem tudta módosítani a hálózati lekérést, mivel a módosítás egy másik bővítménnyel ütközött.</translation>
+<translation id="2857834222104759979">A jegyzékfájl érvénytelen.</translation>
+<translation id="2988488679308982380">Nem sikerült a következő csomag telepítése: "<ph name="ERROR_CODE" />"</translation>
+<translation id="3115238746683532089">Ismeretlen termék (<ph name="PRODUCT_ID" />) <ph name="VENDOR_ID" /> szolgáltatótól (sorozatszám: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Ismeretlen termék (<ph name="PRODUCT_ID" />) a következő forgalmazótól: <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Nem lehet kicsomagolni a bővítményt. A bővítmények biztonságos kicsomagolásához kell lennie egy olyan elérési útnak a profilkönyvtárban, amely egy meghajtó betűjelével kezdődik, és nem tartalmaz közvetett hivatkozást (junction vagy symlink) vagy csatolási pontot. Ilyen elérési út nem létezik a profiljában.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (sorozatszám: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> termék a következő forgalmazótól: <ph name="VENDOR_ID" /> (sorozatszám: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Nem lehet betölteni az indítóoldalt: „<ph name="PAGE" />”.</translation>
+<translation id="388442998277590542">Nem lehet betölteni a(z) "<ph name="OPTIONS_PAGE" />" opcióoldalt.</translation>
+<translation id="4115165561519362854">A gép rendszergazdája előírta, hogy a(z) <ph name="EXTENSION_NAME" /> bővítmény verziója legalább <ph name="EXTENSION_VERSION" /> legyen. Nem kapcsolható be addig, amíg nincs frissítve erre a verzióra (vagy újabbra).</translation>
+<translation id="4233778200880751280">A(z) „<ph name="ABOUT_PAGE" />” névjegyoldal betöltése sikertelen volt.</translation>
+<translation id="4434145631756268951">{0,select, single{USB-eszköz kiválasztása}multiple{USB-eszközök kiválasztása}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Nem lehetett telepíteni a csomagot, mert egy segédprogram-folyamat összeomlott. Indítsa újra a Chrome-ot, majd próbálja újra.</translation>
+<translation id="5026754133087629784">Webview: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Alkalmazásnézet: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Ez a bővítmény a(z) "<ph name="KEY_PATH" />" kulcsfájlt tartalmazza. Ezt Ön valószínűleg nem szeretné.</translation>
+<translation id="5627523580512561598"><ph name="EXTENSION_NAME" /> bővítmény</translation>
+<translation id="5630931906013276297">{0,select, single{HID-eszköz kiválasztása}multiple{HID-eszközök kiválasztása}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">A számítógép adminisztrátorának szüksége van arra, hogy a(z) <ph name="EXTENSION_NAME" /> telepítve legyen. Nem lehet eltávolítani.</translation>
+<translation id="6027032947578871493">Ismeretlen termék (<ph name="PRODUCT_ID" />) a következő szolgáltatótól: <ph name="VENDOR_NAME" /> (sorozatszám: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" />, forgalmazó: <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Nem lehet kicsomagolni a bővítményt. A bővítmények biztonságos kicsomagolásához kell lennie egy olyan elérési útnak a profilkönyvtárban, amelyben nem szerepel közvetett hivatkozás (symlink). Ilyen elérési út nem létezik a profiljában.</translation>
+<translation id="616804573177634438">{0,select, single{A(z) „<ph name="APP_NAME" />” alkalmazás hozzáférést kér egyik eszközéhez.}multiple{A(z) „<ph name="APP_NAME" />” alkalmazás hozzáférést kér egy vagy több eszközéhez.}other{UNUSED}}</translation>
+<translation id="641087317769093025">Nem sikerült a bővítmény kicsomagolása</translation>
+<translation id="657064425229075395">Nem sikerült betölteni a következő háttérszkriptet: "<ph name="BACKGROUND_SCRIPT" />".</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> a következőtől: <ph name="VENDOR_NAME" /> (sorozatszám: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Nem sikerült könyvtárat létrehozni a kicsomagoláshoz: „<ph name="DIRECTORY_PATH" />”</translation>
+<translation id="677806580227005219">MIME-kezelő: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Ez a bővítmény túl gyakran töltötte újra magát.</translation>
+<translation id="7003844668372540529">Ismeretlen termék (<ph name="PRODUCT_ID" />) a következő forgalmazótól: <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Az eszköz rendszergazdája megköveteli, hogy a(z) <ph name="EXTENSION_NAME" /> telepítve legyen. A bővítményt nem lehet eltávolítani vagy módosítani.</translation>
+<translation id="7809034755304591547">A(z) <ph name="EXTENSION_NAME" /> bővítményt (bővítményazonosító: „<ph name="EXTENSION_ID" />”) blokkolta a rendszergazda.</translation>
+<translation id="7972881773422714442">Lehetőségek: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Ez a bővítmény nem tudta elnevezni a(z) „<ph name="ATTEMPTED_FILENAME" />” letöltést, mert egy másik bővítmény (<ph name="EXTENSION_NAME" />) eltérő fájlnevet határozott meg: „<ph name="ACTUAL_FILENAME" />”.</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> (<ph name="VENDOR_NAME" />)</translation>
+<translation id="8341840687457896278">A bővítmény nem tudott hitelesítési adatokat biztosítani egy hálózati kérésnek, mert egy másik bővítmény (<ph name="EXTENSION_NAME" />) eltérő hitelesítési adatokat adott meg.</translation>
+<translation id="8602184400052594090">A jegyzékfájl hiányzik vagy nem olvasható.</translation>
+<translation id="8636666366616799973">A csomag érvénytelen. Részletek: "<ph name="ERROR_MESSAGE" />".</translation>
+<translation id="8670869118777164560">A bővítmény nem tudta átirányítani a hálózati kérést <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> felé, mivel egy másik bővítmény (<ph name="EXTENSION_NAME" />) átirányította azt a következőre: <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Bővítmény-kicsomagoló</translation>
+<translation id="8825366169884721447">A bővítmény nem tudta módosítani egy hálózati kérés „<ph name="HEADER_NAME" />” kérésfejlécét, mert a módosítás ütközik egy másik bővítménnyel (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">A bővítmény nem tudta módosítani egy hálózati kérés „<ph name="HEADER_NAME" />” válaszfejlécét, mert a módosítás ütközik egy másik bővítménnyel (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_id.xtb b/chromium/extensions/strings/extensions_strings_id.xtb
new file mode 100644
index 00000000000..df869ff966c
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_id.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="id">
+<translation id="1135328998467923690">Paket tidak valid: '<ph name="ERROR_CODE" />'.</translation>
+<translation id="1256619696651732561">Pengurai Manifes Ekstensi</translation>
+<translation id="1445572445564823378">Ekstensi ini memperlambat <ph name="PRODUCT_NAME" />. Anda harus menonaktifkannya agar dapat memulihkan kinerja <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">Tidak dapat memuat ikon ekstensi '<ph name="ICON" />'.</translation>
+<translation id="1803557475693955505">Tidak dapat memuat laman latar belakang '<ph name="BACKGROUND_PAGE" />'.</translation>
+<translation id="2159915644201199628">Tidak dapat mendekode gambar: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">Lokalisasi digunakan, tetapi default_locale tidak ditentukan dalam manifes.</translation>
+<translation id="2753617847762399167">jalur ilegal (mutlak atau relatif dengan '..'): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">Ekstensi ini gagal memodifikasi permintaan jaringan karena modifikasi bentrok dengan ekstensi lain.</translation>
+<translation id="2857834222104759979">File manifes tidak valid.</translation>
+<translation id="2988488679308982380">Tidak dapat memasang paket: '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089">Produk <ph name="PRODUCT_ID" /> tak dikenal dari vendor <ph name="VENDOR_ID" /> (nomor seri <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Produk <ph name="PRODUCT_ID" /> tak dikenal dari vendor <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Tidak dapat membuka paket ekstensi. Untuk membuka paket ekstensi dengan aman, harus tersedia jalur ke direktori profil Anda yang mulai dengan sebuah huruf drive dan tidak berisi junction point, mount point, atau tautan simbolik. Tidak ada jalur yang seperti itu untuk profil Anda.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (nomor seri <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> dari vendor <ph name="VENDOR_ID" /> (nomor seri <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Tidak dapat memuat laman peluncur '<ph name="PAGE" />'.</translation>
+<translation id="388442998277590542">Tidak dapat memuat laman opsi '<ph name="OPTIONS_PAGE" />'.</translation>
+<translation id="4115165561519362854">Administrator perangkat ini mewajibkan <ph name="EXTENSION_NAME" /> untuk menjalankan versi minimum <ph name="EXTENSION_VERSION" />. Ekstensi tidak dapat diaktifkan sampai diperbarui ke versi tersebut (atau yang lebih tinggi).</translation>
+<translation id="4233778200880751280">Tidak dapat memuat laman tentang '<ph name="ABOUT_PAGE" />'.</translation>
+<translation id="4434145631756268951">{0,select, single{Pilih perangkat USB}multiple{Pilih perangkat USB}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Tidak dapat memasang paket karena proses utilitas mogok. Mulai ulang Chrome dan coba lagi.</translation>
+<translation id="5026754133087629784">Tampilan web: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Ekstensi ini termasuk file kunci '<ph name="KEY_PATH" />'. Anda mungkin tidak ingin melakukannya.</translation>
+<translation id="5627523580512561598">ekstensi <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Pilih perangkat HID}multiple{Pilih perangkat HID}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Administrator komputer ini mewajibkan agar <ph name="EXTENSION_NAME" /> dipasang. Ekstensi tersebut tidak dapat dicopot pemasangannya.</translation>
+<translation id="6027032947578871493">Produk <ph name="PRODUCT_ID" /> tak dikenal dari <ph name="VENDOR_NAME" /> (nomor seri <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> dari vendor <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Tidak dapat membuka paket ekstensi. Untuk membuka paket ekstensi dengan aman, harus tersedia jalur ke direktori profil Anda yang tidak berisi tautan simbolik. Tidak ada jalur yang seperti itu untuk profil Anda.</translation>
+<translation id="616804573177634438">{0,select, single{Aplikasi "<ph name="APP_NAME" />" meminta akses ke salah satu perangkat Anda.}multiple{Aplikasi "<ph name="APP_NAME" />" meminta akses ke satu atau beberapa perangkat Anda.}other{UNUSED}}</translation>
+<translation id="641087317769093025">Tidak dapat mengekstrak ekstensi</translation>
+<translation id="657064425229075395">Tidak dapat memuat skrip latar belakang '<ph name="BACKGROUND_SCRIPT" />'.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> dari <ph name="VENDOR_NAME" /> (nomor seri <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Tidak dapat membuat direktori untuk pengekstrakan: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">Mimehandler: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Ekstensi ini terlalu sering memuat ulang sendiri.</translation>
+<translation id="7003844668372540529">Produk <ph name="PRODUCT_ID" /> tak dikenal dari <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Administrator mesin ini membutuhkan <ph name="EXTENSION_NAME" /> untuk dipasang. Persyaratan ini tidak dapat dihapus atau dimodifikasi.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (ID ekstensi "<ph name="EXTENSION_ID" />") dicekal oleh administrator.</translation>
+<translation id="7972881773422714442">Opsi: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Ekstensi ini gagal memberi nama unduhan "<ph name="ATTEMPTED_FILENAME" />" karena ekstensi lain (<ph name="EXTENSION_NAME" />) menentukan nama file yang berbeda "<ph name="ACTUAL_FILENAME" />".</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> dari <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Ekstensi ini gagal memberikan kredensial ke permintaan jaringan karena ekstensi lain (<ph name="EXTENSION_NAME" />) telah memberikan kredensial yang berbeda.</translation>
+<translation id="8602184400052594090">File manifes tidak ada atau tidak dapat dibaca.</translation>
+<translation id="8636666366616799973">Paket tidak valid. Detail: '<ph name="ERROR_MESSAGE" />'.</translation>
+<translation id="8670869118777164560">Ekstensi ini gagal mengalihkan permintaan jaringan ke <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> karena ekstensi lain (<ph name="EXTENSION_NAME" />) mengalihkannya ke <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Pembuka Ekstensi</translation>
+<translation id="8825366169884721447">Ekstensi ini gagal memodifikasi tajuk permintaan "<ph name="HEADER_NAME" />" dari suatu permintaan jaringan karena modifikasi bertentangan dengan ekstensi lain (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Ekstensi ini gagal memodifikasi tajuk tanggapan "<ph name="HEADER_NAME" />" dari suatu permintaan jaringan karena modifikasi bertentangan dengan ekstensi lain (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_it.xtb b/chromium/extensions/strings/extensions_strings_it.xtb
new file mode 100644
index 00000000000..8691cfbe4da
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_it.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="it">
+<translation id="1135328998467923690">Il pacchetto non è valido: "<ph name="ERROR_CODE" />".</translation>
+<translation id="1256619696651732561">Extension Manifest Parser</translation>
+<translation id="1445572445564823378">Questa estensione sta rallentando <ph name="PRODUCT_NAME" />. È necessario disabilitarla per ripristinare le prestazioni di <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">Impossibile caricare l'icona estensione "<ph name="ICON" />".</translation>
+<translation id="1803557475693955505">Impossibile caricare la pagina di sfondo "<ph name="BACKGROUND_PAGE" />".</translation>
+<translation id="2159915644201199628">Impossibile decodificare l'immagine: "<ph name="IMAGE_NAME" />"</translation>
+<translation id="2350172092385603347">Localizzazione utilizzata, ma default_locale non era specificato nel file manifest.</translation>
+<translation id="2753617847762399167">Percorso non valido (assoluto o relativo con ".."): "<ph name="IMAGE_PATH" />"</translation>
+<translation id="27822970480436970">Questa estensione non è riuscita a modificare una richiesta di rete perché la modifica era in conflitto con un'altra estensione.</translation>
+<translation id="2857834222104759979">File manifest non valido.</translation>
+<translation id="2988488679308982380">Impossibile installare il pacchetto: "<ph name="ERROR_CODE" />"</translation>
+<translation id="3115238746683532089">Prodotto sconosciuto <ph name="PRODUCT_ID" /> del fornitore <ph name="VENDOR_ID" /> (numero di serie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Prodotto sconosciuto <ph name="PRODUCT_ID" /> del fornitore <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Impossibile decomprimere l'estensione. Per decomprimere in modo sicuro un'estensione, deve essere fornito un percorso alla directory del profilo che inizi con una lettera di unità e non contenga un punto di giunzione, un punto di montaggio o un link simbolico. Non esiste un simile percorso per il tuo profilo.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (numero di serie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> del fornitore <ph name="VENDOR_ID" /> (numero di serie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Impossibile caricare la pagina di avvio "<ph name="PAGE" />".</translation>
+<translation id="388442998277590542">Impossibile caricare la pagina delle opzioni "<ph name="OPTIONS_PAGE" />".</translation>
+<translation id="4115165561519362854">L'amministratore di questo computer richiede che <ph name="EXTENSION_NAME" /> abbia almeno la versione <ph name="EXTENSION_VERSION" />. L'attivazione potrà essere completata solo dopo che è stato eseguito l'aggiornamento a questa versione o a una versione successiva.</translation>
+<translation id="4233778200880751280">Impossibile caricare la pagina di informazioni "<ph name="ABOUT_PAGE" />".</translation>
+<translation id="4434145631756268951">{0,select, single{Seleziona un dispositivo USB}multiple{Seleziona dispositivi USB}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Impossibile installare il pacchetto perché un processo dell'utility si è arrestato in modo anomalo. Riavvia Chrome e riprova.</translation>
+<translation id="5026754133087629784">Visualizzazione web: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Questa estensione include il file di chiave "<ph name="KEY_PATH" />". Probabilmente preferiresti che non l'includesse.</translation>
+<translation id="5627523580512561598">estensione <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Seleziona un dispositivo HID}multiple{Seleziona dispositivi HID}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">L'amministratore di questo computer richiede che l'estensione <ph name="EXTENSION_NAME" /> sia installata. Non è possibile disinstallarla.</translation>
+<translation id="6027032947578871493">Prodotto sconosciuto <ph name="PRODUCT_ID" /> di <ph name="VENDOR_NAME" /> (numero di serie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> del fornitore <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Impossibile decomprimere l'estensione. Per decomprimere in modo sicuro un'estensione, deve essere fornito un percorso alla directory del profilo che non contenga un link simbolico. Non esiste un simile percorso per il tuo profilo.</translation>
+<translation id="616804573177634438">{0,select, single{L'applicazione "<ph name="APP_NAME" />" richiede l'accesso a uno dei tuoi dispositivi.}multiple{L'applicazione "<ph name="APP_NAME" />" richiede l'accesso a uno o più dei tuoi dispositivi.}other{UNUSED}}</translation>
+<translation id="641087317769093025">Impossibile decomprimere l'estensione</translation>
+<translation id="657064425229075395">Impossibile caricare lo script in background "<ph name="BACKGROUND_SCRIPT" />".</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> di <ph name="VENDOR_NAME" /> (numero di serie: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Impossibile creare la directory per la decompressione: "<ph name="DIRECTORY_PATH" />"</translation>
+<translation id="677806580227005219">Gestore MIME: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">L'estensione si è ricaricata automaticamente troppo spesso.</translation>
+<translation id="7003844668372540529">Prodotto sconosciuto <ph name="PRODUCT_ID" /> di <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">L'amministratore di questo computer richiede che l'estensione <ph name="EXTENSION_NAME" /> sia installata. Non è possibile rimuovere o modificare l'estensione.</translation>
+<translation id="7809034755304591547">L'estensione <ph name="EXTENSION_NAME" /> (ID "<ph name="EXTENSION_ID" />") è stata bloccata dall'amministratore.</translation>
+<translation id="7972881773422714442">Opzioni: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Questa estensione non è riuscita a denominare il download "<ph name="ATTEMPTED_FILENAME" />" perché un'altra estensione (<ph name="EXTENSION_NAME" />) ha determinato un nome di file diverso "<ph name="ACTUAL_FILENAME" />".</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> di <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Questa estensione non è riuscita a fornire le credenziali per una richiesta di rete perché un'altra estensione (<ph name="EXTENSION_NAME" />) ha fornito credenziali diverse.</translation>
+<translation id="8602184400052594090">File manifest mancante o illeggibile.</translation>
+<translation id="8636666366616799973">Il pacchetto non è valido. Dettagli: "<ph name="ERROR_MESSAGE" />".</translation>
+<translation id="8670869118777164560">Questa estensione non è riuscita a reindirizzare una richiesta di rete a <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> perché un'altra estensione (<ph name="EXTENSION_NAME" />) l'ha reindirizzata a <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Extension Unpacker</translation>
+<translation id="8825366169884721447">Questa estensione non è riuscita a modificare l'intestazione della richiesta "<ph name="HEADER_NAME" />" di una richiesta di rete perché la modifica era in conflitto con un'altra estensione (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Questa estensione non è riuscita a modificare l'intestazione della risposta "<ph name="HEADER_NAME" />" di una richiesta di rete perché la modifica era in conflitto con un'altra estensione (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_iw.xtb b/chromium/extensions/strings/extensions_strings_iw.xtb
new file mode 100644
index 00000000000..ce177fed141
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_iw.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="iw">
+<translation id="1135328998467923690">החבילה לא חוקית: '<ph name="ERROR_CODE" />'.</translation>
+<translation id="1256619696651732561">מנתח מניפסט התוספים</translation>
+<translation id="1445572445564823378">תוסף זה מאט את <ph name="PRODUCT_NAME" />. עליך להשבית אותו כדי לשחזר את הביצועים של <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">לא היתה אפשרות לטעון את אייקון התוסף '<ph name="ICON" />'.</translation>
+<translation id="1803557475693955505">לא היתה אפשרות לטעון את דף הרקע '<ph name="BACKGROUND_PAGE" />'.</translation>
+<translation id="2159915644201199628">לא ניתן לפענח תמונה: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">‏נעשה שימוש בהתאמה למקום, אך default_locale לא צוין במניפסט.</translation>
+<translation id="2753617847762399167">נתיב לא חוקי (באופן מוחלט או ביחס אל '..'): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">תוסף זה לא הצליח לשנות בקשת רשת משום שהשינוי התנגש עם תוסף אחר.</translation>
+<translation id="2857834222104759979">קובץ המניספט לא חוקי.</translation>
+<translation id="2988488679308982380">לא ניתן להתקין את החבילה: '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089">מוצר לא ידוע <ph name="PRODUCT_ID" /> מהספק <ph name="VENDOR_ID" /> (מספר סידורי <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">מוצר לא ידוע <ph name="PRODUCT_ID" /> מהספק <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">לא ניתן לפרוק את התוסף. כדי לפרוק תוספים בצורה בטוחה, יש צורך בנתיב לספריית הפרופילים שמתחיל באות כונן ולא מכיל צומת, נקודת טעינה או קישור סמלי. לא קיים נתיב שכזה לפרופיל שלך.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (מספר סידורי <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> מהספק <ph name="VENDOR_ID" /> (מספר סידורי <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">לא הייתה אפשרות לטעון את דף מפעיל היישומים '<ph name="PAGE" />'.</translation>
+<translation id="388442998277590542">לא היתה אפשרות לטעון את דף האפשרויות '<ph name="OPTIONS_PAGE" />'.</translation>
+<translation id="4115165561519362854">מנהל המערכת של מחשב זה דורש שהגרסה המינימלית של <ph name="EXTENSION_NAME" /> תהיה <ph name="EXTENSION_VERSION" />. ההפעלה אינה אפשרית עד לעדכון לגרסה זו (ואילך).</translation>
+<translation id="4233778200880751280">לא ניתן לטעון את דף המידע '<ph name="ABOUT_PAGE" />'.</translation>
+<translation id="4434145631756268951">{0,select, single{‏בחר התקן USB}multiple{‏בחר התקני USB}other{לא בשימוש}}</translation>
+<translation id="4811956658694082538">‏לא ניתן היה להתקין את החבילה מפני שתהליך כלי השירות קרס. נסה לאתחל את Chrome ואז נסה שוב.</translation>
+<translation id="5026754133087629784">צפיות באתר: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">‏Appview‏: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">תוסף זה כולל את קובץ המפתח "<ph name="KEY_PATH" />". מומלץ לא לעשות זאת.</translation>
+<translation id="5627523580512561598">תוסף <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{‏בחר התקן HID}multiple{‏בחר התקני HID}other{לא בשימוש}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">מנהל המערכת של מכשיר זה דורש התקנה של <ph name="EXTENSION_NAME" />. לא ניתן להסיר את ההתקנה.</translation>
+<translation id="6027032947578871493">מוצר לא ידוע <ph name="PRODUCT_ID" /> מהספק <ph name="VENDOR_NAME" /> (מספר סידורי <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> מהספק <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">לא ניתן לפרוק את התוסף. כדי לפרוק תוספים בצורה בטוחה, יש צורך בנתיב לספריית הפרופילים שלך שלא מכיל קישור סמלי. לא קיים נתיב שכזה לפרופיל שלך.</translation>
+<translation id="616804573177634438">{0,select, single{האפליקציה ’<ph name="APP_NAME" />’ מבקשת גישה לאחד מהמכשירים שלך.}multiple{האפליקציה ’<ph name="APP_NAME" />’ מבקשת גישה למכשיר אחד או יותר.}other{לא בשימוש}}</translation>
+<translation id="641087317769093025">לא ניתן לבטל את הדחיסה של התוסף</translation>
+<translation id="657064425229075395">לא ניתן להעלות את סקריפט הרקע '<ph name="BACKGROUND_SCRIPT" />'.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> מ-<ph name="VENDOR_NAME" /> (מספר סידורי <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">לא ניתן ליצור ספריה עבור ביטול הדחיסה: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">‏מטפל MIME:‏ <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">התוסף הזה טען את עצמו מחדש בתדירות גבוהה מדי.</translation>
+<translation id="7003844668372540529">מוצר לא ידוע <ph name="PRODUCT_ID" /> מאת <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">מנהל המערכת של מחשב זה דורש התקנת <ph name="EXTENSION_NAME" />. לא ניתן להסיר או לשנות אותו.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (זיהוי תוסף '<ph name="EXTENSION_ID" />') נחסם על ידי מנהל המערכת.</translation>
+<translation id="7972881773422714442">אפשרויות: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">תוסף זה לא הצליח לתת שם לקובץ "<ph name="ATTEMPTED_FILENAME" />" שהורד, משום שתוסף אחר (<ph name="EXTENSION_NAME" />) קבע שם קובץ אחר - "<ph name="ACTUAL_FILENAME" />".</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> מאת <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">תוסף זה נכשל באספקת אישורים לבקשת רשת מכיוון שתוסף אחר (<ph name="EXTENSION_NAME" />) סיפק אישורים אחרים.</translation>
+<translation id="8602184400052594090">קובץ המניפסט חסר או בלתי קריא.</translation>
+<translation id="8636666366616799973">החבילה לא חוקית. פרטים: '<ph name="ERROR_MESSAGE" />'.</translation>
+<translation id="8670869118777164560">תוסף זה נכשל בניתוב בקשת רשת אל <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> מכיוון שתוסף אחר (<ph name="EXTENSION_NAME" />) ניתב אותו אל <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">פורק התוספים</translation>
+<translation id="8825366169884721447">תוסף זה נכשל בשינוי כותרת הבקשה '<ph name="HEADER_NAME" />' של בקשת רשת מכיוון שהשינוי התנגש עם תוסף אחר (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">תוסף זה נכשל בשינוי כותרת התגובה '<ph name="HEADER_NAME" />' של בקשת רשת מכיוון שהשינוי התנגש עם תוסף אחר (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_ja.xtb b/chromium/extensions/strings/extensions_strings_ja.xtb
new file mode 100644
index 00000000000..3bb60d1e78a
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_ja.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ja">
+<translation id="1135328998467923690">パッケージが無効です: <ph name="ERROR_CODE" /></translation>
+<translation id="1256619696651732561">拡張機能マニフェストのパーサー</translation>
+<translation id="1445572445564823378">この拡張機能によって <ph name="PRODUCT_NAME" /> の速度が低下しています。<ph name="PRODUCT_NAME" /> のパフォーマンスを回復するには、この拡張機能を無効にしてください。</translation>
+<translation id="149347756975725155">拡張機能アイコン「<ph name="ICON" />」を読み込むことができませんでした。</translation>
+<translation id="1803557475693955505">背景ページ「<ph name="BACKGROUND_PAGE" />」を読み込むことができませんでした。</translation>
+<translation id="2159915644201199628">画像をデコードできませんでした: <ph name="IMAGE_NAME" /></translation>
+<translation id="2350172092385603347">言語/地域機能は使用されていますが、マニフェストに default_locale が指定されていません。</translation>
+<translation id="2753617847762399167">パス(絶対パスまたは '..' がある相対パス)が不適切です: <ph name="IMAGE_PATH" /></translation>
+<translation id="27822970480436970">この拡張機能はネットワーク リクエストを変更できませんでした。他の拡張機能と変更が競合しています。</translation>
+<translation id="2857834222104759979">マニフェスト ファイルが無効です。</translation>
+<translation id="2988488679308982380">パッケージをインストールできませんでした: <ph name="ERROR_CODE" /></translation>
+<translation id="3115238746683532089">不明な商品(<ph name="PRODUCT_ID" />、ベンダー: <ph name="VENDOR_ID" />、シリアル番号: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">不明な商品(<ph name="PRODUCT_ID" />、ベンダー: <ph name="VENDOR_ID" />)</translation>
+<translation id="3369521687965833290">拡張機能を解凍できません。拡張機能を安全に解凍するには、ドライブ文字で始まるプロフィール ディレクトリへのパスが必要です(ジャンクション、マウント ポイント、シンボリック リンクが含まれていないこと)。このようなパスがプロフィール内に存在しません。</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" />(シリアル番号: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" />(ベンダー: <ph name="VENDOR_ID" />、シリアル番号: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">ランチャー ページ「<ph name="PAGE" />」を読み込めませんでした。</translation>
+<translation id="388442998277590542">オプション ページ「<ph name="OPTIONS_PAGE" />」を読み込むことができませんでした。</translation>
+<translation id="4115165561519362854">このパソコンの管理者により、<ph name="EXTENSION_NAME" /> バージョン <ph name="EXTENSION_VERSION" /> 以上が要件に指定されています。有効にするにはこのバージョン以上に更新する必要があります。</translation>
+<translation id="4233778200880751280">情報ページ「<ph name="ABOUT_PAGE" />」を読み込めませんでした。</translation>
+<translation id="4434145631756268951">{0,select, single{USB デバイスの選択}multiple{USB デバイスの選択}other{UNUSED}}</translation>
+<translation id="4811956658694082538">ユーティリティ プロセスで問題が発生したため、パッケージをインストールできませんでした。Chrome を再起動してもう一度お試しください。</translation>
+<translation id="5026754133087629784">WebView: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">アプリビュー: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">この拡張機能には、鍵ファイル「<ph name="KEY_PATH" />」が含まれていますが、おそらくその必要性はありません。</translation>
+<translation id="5627523580512561598">拡張機能 <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{HID デバイスの選択}multiple{HID デバイスの選択}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">このパソコンの管理者により、<ph name="EXTENSION_NAME" /> をインストールすることが要件として指定されています。この拡張機能はアンインストールできません。</translation>
+<translation id="6027032947578871493">不明な商品(<ph name="PRODUCT_ID" />、ベンダー: <ph name="VENDOR_NAME" />、シリアル番号: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" />(ベンダー: <ph name="VENDOR_ID" />)</translation>
+<translation id="6143635259298204954">拡張機能を解凍できません。拡張機能を安全に解凍するには、シンボリック リンクが含まれていないプロフィール ディレクトリへのパスが必要です。このようなパスがプロフィール内に存在しません。</translation>
+<translation id="616804573177634438">{0,select, single{アプリケーション「<ph name="APP_NAME" />」がお使いのデバイスへのアクセスをリクエストしています。}multiple{アプリケーション「<ph name="APP_NAME" />」がお使いのデバイスへのアクセスをリクエストしています。}other{UNUSED}}</translation>
+<translation id="641087317769093025">拡張機能を解凍できませんでした</translation>
+<translation id="657064425229075395">バックグラウンド スクリプト「<ph name="BACKGROUND_SCRIPT" />」を読み込めませんでした。</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" />、提供元 <ph name="VENDOR_NAME" />(シリアル番号 <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">解凍用のディレクトリを作成できませんでした: <ph name="DIRECTORY_PATH" /></translation>
+<translation id="677806580227005219">MIME ハンドラ: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">この拡張機能は、自身により頻繁に再読み込みされています。</translation>
+<translation id="7003844668372540529">不明な商品(<ph name="PRODUCT_ID" />、ベンダー: <ph name="VENDOR_NAME" />)</translation>
+<translation id="7217838517480956708">このパソコンの管理者にとって <ph name="EXTENSION_NAME" /> がインストールされている必要があります。削除や修正はできません。</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" />(拡張機能 ID「<ph name="EXTENSION_ID" />」)は管理者によってブロックされています。</translation>
+<translation id="7972881773422714442">オプション: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">この拡張機能は、ダウンロード ファイル「<ph name="ATTEMPTED_FILENAME" />」に名前を付けることができませんでした。別の拡張機能(<ph name="EXTENSION_NAME" />)が異なるファイル名「<ph name="ACTUAL_FILENAME" />」を指定しました。</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" />、提供元: <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">別の拡張機能(<ph name="EXTENSION_NAME" />)がネットワーク リクエストに別の認証情報を提供したため、この拡張機能は認証情報を提供できませんでした。</translation>
+<translation id="8602184400052594090">マニフェスト ファイルが見つからないか読み取れません。</translation>
+<translation id="8636666366616799973">パッケージが無効です。詳細: <ph name="ERROR_MESSAGE" /></translation>
+<translation id="8670869118777164560">別の拡張機能(<ph name="EXTENSION_NAME" />)がネットワーク リクエストを <ph name="ACTUAL_REDIRECT_DESTINATION" /> にリダイレクトしたため、この拡張機能はリクエストを <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> にリダイレクトできませんでした。</translation>
+<translation id="8712265948125780616">拡張機能の解凍機能</translation>
+<translation id="8825366169884721447">ネットワーク リクエストのリクエスト ヘッダー「<ph name="HEADER_NAME" />」への変更内容が別の拡張機能(<ph name="EXTENSION_NAME" />)と競合したため、この拡張機能はヘッダーを変更できませんでした。</translation>
+<translation id="9111791539553342076">ネットワーク リクエストの応答ヘッダー「<ph name="HEADER_NAME" />」への変更内容が別の拡張機能(<ph name="EXTENSION_NAME" />)と競合したため、この拡張機能はヘッダーを変更できませんでした。</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_kn.xtb b/chromium/extensions/strings/extensions_strings_kn.xtb
new file mode 100644
index 00000000000..b9ba94c53ee
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_kn.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="kn">
+<translation id="1135328998467923690">ಪ್ಯಾಕೇಜ್ ಅಮಾನ್ಯವಾಗಿದೆ: '<ph name="ERROR_CODE" />'.</translation>
+<translation id="1256619696651732561">ವಿಸ್ತರಣೆ ಮ್ಯಾನಿಫೆಸ್ಟ್ ವಿಶ್ಲೇಷಕ</translation>
+<translation id="1445572445564823378">ಈ ವಿಸ್ತರಣೆಯು <ph name="PRODUCT_NAME" /> ಅನ್ನು ನಿಧಾನವಾಗಿಸುತ್ತಿದೆ. ನೀವು <ph name="PRODUCT_NAME" /> ರ ಕಾರ್ಯಾಚರಣೆಯನ್ನು ಮತ್ತೊಮ್ಮೆ ಪ್ರಾರಂಭಿಸಲು ಇದನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸುವುದು ಅವಶ್ಯಕ.</translation>
+<translation id="149347756975725155">'<ph name="ICON" />' ಎಕ್ಸ್‌ಟೆನ್ಷನ್ ಐಕಾನ್ ಲೋಡ್ ಮಾಡಲಾಗಲಿಲ್ಲ.</translation>
+<translation id="1803557475693955505">'<ph name="BACKGROUND_PAGE" />' ಹಿನ್ನಲೆ ಪುಟವನ್ನು ಲೋಡ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ.</translation>
+<translation id="2159915644201199628">ಚಿತ್ರವನ್ನು ಡಿಕೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">ಲೋಕಲೈಜೇಷನ್ ಬಳಸಲಾಗಿದೆ, ಆದರೆ ಡೀಫಾಲ್ಟ್ _ಲೋಕಲ್ ಅನ್ನು ಮ್ಯಾನಿಫಾಸ್ಟ್‌ನಲ್ಲಿ ನಿರ್ದಿಷ್ಟಪಡಿಸಲಾಗಿಲ್ಲ.</translation>
+<translation id="2753617847762399167">ಕಾನೂನು ಬಾಹಿರ ಪಾಥ್ ('..' ರೊಂದಿಗೆ ಸಮಗ್ರ ಅಥವಾ ಸಂಬಂಧಿತ... ): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">ನೆಟ್‌ವರ್ಕ್ ವಿನಂತಿಯನ್ನು ಮಾರ್ಪಡಿಸಲು ಈ ವಿಸ್ತರಣೆಯು ವಿಫಲವಾಗಿದೆ ಏಕೆಂದರೆ ಮಾರ್ಪಡಿಸುವಿಕೆಯು ಮತ್ತೊಂದು ವಿಸ್ತರಣೆಯೊಂದಿಗೆ ಸಂಘರ್ಷಗೊಂಡಿದೆ.</translation>
+<translation id="2857834222104759979">ಮ್ಯಾನಿಫೆಸ್ಟ್ ಫೈಲ್ ಮಾನ್ಯತೆ ಪಡೆದಿಲ್ಲ.</translation>
+<translation id="2988488679308982380">ಪ್ಯಾಕೇಜ್ ಅನ್ನು ಸ್ಥಾಪಿಸಲಾಗುವುದಿಲ್ಲ: '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089"><ph name="VENDOR_ID" /> ಮಾರಾಟಗಾರರಿಂದ <ph name="PRODUCT_ID" /> ಅಪರಿಚಿತ ಉತ್ಪನ್ನ (ಕ್ರಮ ಸಂಖ್ಯೆ <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963"><ph name="VENDOR_ID" /> ಮಾರಾಟಗಾರರಿಂದ <ph name="PRODUCT_ID" /> ಅಪರಿಚಿತ ಉತ್ಪನ್ನ</translation>
+<translation id="3369521687965833290">ವಿಸ್ತರಣೆಯನ್ನು ಅನ್‌ಪ್ಯಾಕ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ. ವಿಸ್ತರಣೆಯನ್ನು ಸುರಕ್ಷಿತವಾಗಿ ಅನ್‌ಪ್ಯಾಕ್ ಮಾಡಲು, ನಿಮ್ಮ ಪ್ರೊಫೈಲ್ ಡೈರೆಕ್ಟರಿಯಲ್ಲಿ ಪಾಥ್ ಇದ್ದು ಅದು ಡ್ರೈವ್ ಅಕ್ಷರದೊಂದಿಗೆ ಪ್ರಾರಂಭವಾಗುತ್ತದೆ ಮತ್ತು ಜಂಕ್ಷನ್, ಮೌಂಟ್ ಪಾಯಿಂಟ್ ಅಥವಾ ಸಿಮ್‌ಲಿಂಕ್ ಅನ್ನು ಹೊಂದಿರುವುದಿಲ್ಲ. ನಿಮ್ಮ ಪ್ರೊಫೈಲ್‌ನಲ್ಲಿ ಯಾವುದೇ ಪಾಥ್ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವುದಿಲ್ಲ.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (ಕ್ರಮ ಸಂಖ್ಯೆ <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="VENDOR_ID" /> ಮಾರಾಟಗಾರರಿಂದ <ph name="PRODUCT_NAME" /> (ಕ್ರಮ ಸಂಖ್ಯೆ <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">'<ph name="PAGE" />' ಲಾಂಚರ್ ಪುಟವನ್ನು ಲೋಡ್ ಮಾಡಲಾಗಲಿಲ್ಲ.</translation>
+<translation id="388442998277590542">ಆಯ್ಕೆಗಳ ಪುಟ '<ph name="OPTIONS_PAGE" />' ವನ್ನು ಲೋಡ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ.</translation>
+<translation id="4115165561519362854">ಈ ಮೆಷಿನ್‌ನ ನಿರ್ವಾಹಕರಿಗೆ <ph name="EXTENSION_VERSION" /> ನ ಕನಿಷ್ಠ ಆವೃತಿಯನ್ನು <ph name="EXTENSION_NAME" /> ಹೊಂದಬೇಕಾದ ಅಗತ್ಯವಿದೆ. ಅದು ಹೊಸ ಆವೃತ್ತಿಗೆ (ಅಥವಾ ಹೆಚ್ಚಿನ) ನವೀಕರಣಗೊಳ್ಳುವವರೆಗೂ ಇದನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲಾಗುವುದಿಲ್ಲ.</translation>
+<translation id="4233778200880751280">'<ph name="ABOUT_PAGE" />' ಪುಟದ ಕುರಿತು ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ.</translation>
+<translation id="4434145631756268951">{0,select, single{USB ಸಾಧನವನ್ನು ಆಯ್ಕೆಮಾಡಿ}multiple{USB ಸಾಧನಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ}other{UNUSED}}</translation>
+<translation id="4811956658694082538">ಉಪಯುಕ್ತತೆಯ ಪ್ರಕ್ರಿಯೆಯು ಹಾಳಾಗಿರುವ ಕಾರಣ ಪ್ಯಾಕೇಜ್ ಅನ್ನು ಸ್ಥಾಪಿಸಲಾಗುವುದಿಲ್ಲ. Chrome ಮರುಪ್ರಾರಂಭಿಸಲು ಪ್ರಯತ್ನಿಸಿ ಹಾಗೂ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ.</translation>
+<translation id="5026754133087629784">ವೆಬ್‌ವೀಕ್ಷಣೆ: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">ಅಪ್ಲಿಕೇಶನ್ ವೀಕ್ಷಣೆ: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">ಈ ವಿಸ್ತರಣೆಯು ಕೀ ಫೈಲ್ '<ph name="KEY_PATH" />' ಅನ್ನು ಒಳಗೊಂಡಿದೆ. ನೀವು ಸಾಮಾನ್ಯವಾಗಿ ಹಾಗೆ ಮಾಡಲು ಬಯಸುವುದಿಲ್ಲ.</translation>
+<translation id="5627523580512561598">ವಿಸ್ತರಣೆ<ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{HID ಸಾಧನವನ್ನು ಆಯ್ಕೆಮಾಡಿ}multiple{HID ಸಾಧನಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ವಿಸ್ತರಣೆವೀಕ್ಷಣೆ: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">ಈ ಯಂತ್ರದ ನಿರ್ವಾಹಕರಿಗೆ <ph name="EXTENSION_NAME" /> ಸ್ಥಾಪಿಸಲು ಅಗತ್ಯವಿದೆ. ಇದನ್ನು ಅಸ್ಥಾಪಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ.</translation>
+<translation id="6027032947578871493"><ph name="VENDOR_NAME" /> ಅವರಿಂದ <ph name="PRODUCT_ID" /> ಅಪರಿಚಿತ ಉತ್ಪನ್ನ (ಕ್ರಮ ಸಂಖ್ಯೆ <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="VENDOR_ID" /> ಮಾರಾಟಗಾರರಿಂದ <ph name="PRODUCT_NAME" /></translation>
+<translation id="6143635259298204954">ವಿಸ್ತರಣೆಯನ್ನು ಅನ್‌ಪ್ಯಾಕ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ. ಸುರಕ್ಷಿತವಾಗಿ ವಿಸ್ತರಣೆಯನ್ನು ಅನ್‌ಪ್ಯಾಕ್ ಮಾಡಲು, ನಿಮ್ಮ ಪ್ರೊಫೈಲ್ ಡೈರೆಕ್ಟರಿಗೆ ಪಾಥ್ ಇರಬೇಕು ಅದು ಡ್ರೈವ್ ಅಕ್ಷರಗಳೊಂದಿಗೆ ಮತ್ತು ಜಂಕ್ಷನ್, ಮೌಂಟ್ ಪಾಯಿಂಟ್, ಅಥವಾ ಸಿಮ್‌ಲಿಂಕ್ ಅನ್ನು ಹೊಂದಿರುವುದಿಲ್ಲ. ನಿಮ್ಮ ಪ್ರೊಫೈಲ್‌ಗಾಗಿ ಯಾವುದೇ ಪಾಥ್ ಅಸ್ತಿತ್ವದಲ್ಲಿಲ್ಲ.</translation>
+<translation id="616804573177634438">{0,select, single{"<ph name="APP_NAME" />" ಅಪ್ಲಿಕೇಶನ್ ನಿಮ್ಮ ಸಾಧನಗಳಲ್ಲಿ ಒಂದಕ್ಕೆ ಪ್ರವೇಶವನ್ನು ವಿನಂತಿಸುತ್ತಿದೆ.}multiple{"<ph name="APP_NAME" />" ಅಪ್ಲಿಕೇಶನ್ ನಿಮ್ಮ ಸಾಧನಗಳಲ್ಲಿ ಒಂದು ಅಥವಾ ಹೆಚ್ಚಿನ ಸಾಧನಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ವಿನಂತಿಸುತ್ತಿದೆ.}other{ಬಳಕೆಯಾಗದಿರುವ}}</translation>
+<translation id="641087317769093025">ವಿಸ್ತರಣೆಯನ್ನು ಅನ್ ಜಿಪ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ</translation>
+<translation id="657064425229075395">'<ph name="BACKGROUND_SCRIPT" />' ಹಿನ್ನೆಲೆ ಪುಟವನ್ನು ಲೋಡ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> ದಿಂದ <ph name="VENDOR_NAME" /> (ಕ್ರಮಸಂಖ್ಯೆ <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">ಅನ್ ಜಿಪ್ ಮಾಡುವುದಕ್ಕಾಗಿ ಡೈರೆಕ್ಟರಿ ರಚಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">ಮೈಮ್‌ಹ್ಯಾಂಡ್ಲರ್: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">ಈ ವಿಸ್ತರಣೆಯು ತಾನಾಗಿಯೇ ಪದೇ ಪದೇ ಮರು ಲೋಡ್ ಆಗುತ್ತಿದೆ.</translation>
+<translation id="7003844668372540529"><ph name="VENDOR_NAME" /> ಅವರಿಂದ <ph name="PRODUCT_ID" /> ಅಪರಿಚಿತ ಉತ್ಪನ್ನ</translation>
+<translation id="7217838517480956708">ಈ ಯಂತ್ರದ ನಿರ್ವಾಹಕರಿಗೆ <ph name="EXTENSION_NAME" /> ಅನ್ನು ಸ್ಥಾಪಿಸುವ ಅಗತ್ಯವಿದೆ. ಇದನ್ನು ತೆಗೆದುಹಾಕಲು ಅಥವಾ ಮಾರ್ಪಡಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ.</translation>
+<translation id="7809034755304591547">ನಿರ್ವಾಹಕನಿಂದ <ph name="EXTENSION_NAME" /> (ವಿಸ್ತರಣೆ ID "<ph name="EXTENSION_ID" />") ಅನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ.</translation>
+<translation id="7972881773422714442">ಆಯ್ಕೆಗಳು: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">ಈ ವಿಸ್ತರಣೆಯು "<ph name="ATTEMPTED_FILENAME" />" ಅನ್ನು ಡೌನ್‌ಲೋಡ್‌ ಮಾಡುವುದನ್ನು ಹೆಸರಿಸಲು ವಿಫಲವಾಗಿದೆ ಏಕೆಂದರೆ ಮತ್ತೊಂದು ವಿಸ್ತರಣೆ (<ph name="EXTENSION_NAME" />) ಯು ವಿಭಿನ್ನ ಫೈಲ್‌ ಹೆಸರು "<ph name="ACTUAL_FILENAME" />" ಗೆ ನಿರ್ಧರಿಸಿದೆ.</translation>
+<translation id="8284835137979141223"><ph name="VENDOR_NAME" /> ರಿಂದ <ph name="PRODUCT_NAME" /></translation>
+<translation id="8341840687457896278">ಬೇರೊಂದು ವಿಸ್ತರಣೆಯು (<ph name="EXTENSION_NAME" />) ವಿಭಿನ್ನ ರುಜುವಾತುಗಳನ್ನು ಒದಗಿಸಿರುವ ಕಾರಣ ನೆಟ್‌ವರ್ಕ್ ವಿನಂತಿಗೆ ರುಜುವಾತುಗಳನ್ನು ಒದಗಿಸಲು ಈ ವಿಸ್ತರಣೆಯು ವಿಫಲವಾಗಿದೆ.</translation>
+<translation id="8602184400052594090">ಮ್ಯಾನಿಫೆಸ್ಟ್ ಫೈಲ್ ಕಾಣದಾಗಿದೆ ಅಥವಾ ಓದಲಾಗುವುದಿಲ್ಲ.</translation>
+<translation id="8636666366616799973">ಪ್ಯಾಕೇಜ್ ಅಮಾನ್ಯವಾಗಿದೆ. ವಿವರಗಳು: '<ph name="ERROR_MESSAGE" />'.</translation>
+<translation id="8670869118777164560">ಬೇರೊಂದು ವಿಸ್ತರಣೆಯು (<ph name="EXTENSION_NAME" />) ಇದನ್ನು <ph name="ACTUAL_REDIRECT_DESTINATION" /> ಗೆ ಮರುನಿರ್ದೇಶಿಸಿರುವ ಕಾರಣ ಈ ವಿಸ್ತರಣೆಯು ನೆಟ್‌ವರ್ಕ್ ವಿನಂತಿಯನ್ನು <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> ಗೆ ಮರುನಿರ್ದೇಶಿಸಲು ವಿಫಲವಾಗಿದೆ.</translation>
+<translation id="8712265948125780616">ವಿಸ್ತರಣೆ ಅನ್‌ಪ್ಯಾಕರ್‌‌</translation>
+<translation id="8825366169884721447">ಮತ್ತೊಂದು ವಿಸ್ತರಣೆ (<ph name="EXTENSION_NAME" />) ಯೊಂದಿಗೆ ಮಾರ್ಪಡಿಸುವಿಕೆಯು ಸಂಘರ್ಷಗೊಂಡಿರುವ ಕಾರಣ ಈ ವಿಸ್ತರಣೆಯು ನೆಟ್‌ವರ್ಕ್ ವಿನಂತಿಯ ವಿನಂತಿಯ ಶಿರೋನಾಮೆ "<ph name="HEADER_NAME" />" ಯನ್ನು ಮಾರ್ಪಡಿಸಲು ವಿಫಲವಾಗಿದೆ.</translation>
+<translation id="9111791539553342076">ಮತ್ತೊಂದು ವಿಸ್ತರಣೆ (<ph name="EXTENSION_NAME" />) ಯೊಂದಿಗೆ ಮಾರ್ಪಡಿಸುವಿಕೆಯು ಸಂಘರ್ಷಗೊಂಡಿರುವ ಕಾರಣ ನೆಟ್‌ವರ್ಕ್ ವಿನಂತಿಯ ಪ್ರತಿಕ್ರಿಯೆ ಶಿರೋನಾಮೆ "<ph name="HEADER_NAME" />" ಯನ್ನು ಮಾರ್ಪಡಿಸಲು ಈ ವಿಸ್ತರಣೆಯು ವಿಫಲವಾಗಿದೆ.</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_ko.xtb b/chromium/extensions/strings/extensions_strings_ko.xtb
new file mode 100644
index 00000000000..a1f48ece4e2
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_ko.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ko">
+<translation id="1135328998467923690">패키지가 잘못되었습니다. '<ph name="ERROR_CODE" />'.</translation>
+<translation id="1256619696651732561">확장 프로그램 매니페스트 파서</translation>
+<translation id="1445572445564823378">이 확장 프로그램은 <ph name="PRODUCT_NAME" />의 성능을 저하시킵니다. <ph name="PRODUCT_NAME" />의 성능을 복원하려면 확장 프로그램을 사용중지해야 합니다.</translation>
+<translation id="149347756975725155">확장 프로그램 아이콘('<ph name="ICON" />')을 로드하지 못했습니다.</translation>
+<translation id="1803557475693955505">배경 페이지('<ph name="BACKGROUND_PAGE" />')를 로드하지 못했습니다.</translation>
+<translation id="2159915644201199628">'<ph name="IMAGE_NAME" />' 이미지를 디코딩하지 못했습니다.</translation>
+<translation id="2350172092385603347">번역한 언어를 이용하였지만 매니페스트에 기본 언어(default_locale)를 지정하지 않았습니다.</translation>
+<translation id="2753617847762399167">불법 경로(절대 또는 상대): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">수정 내용이 다른 확장 프로그램과 충돌하여 이 확장 프로그램이 네트워크 요청을 수정하지 못했습니다.</translation>
+<translation id="2857834222104759979">매니페스트 파일이 잘못되었습니다.</translation>
+<translation id="2988488679308982380">패키지를 설치할 수 없습니다. '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089">공급업체 <ph name="VENDOR_ID" />의 알 수 없는 제품 <ph name="PRODUCT_ID" />(일련번호 <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">공급업체 <ph name="VENDOR_ID" />의 알 수 없는 제품 <ph name="PRODUCT_ID" /></translation>
+<translation id="3369521687965833290">확장 프로그램을 압축해제할 수 없습니다. 확장 프로그램을 안전하게 압축해제하려면 드라이브 문자로 시작되고 정션(junction), 마운트 포인트(mount point) 또는 심볼릭 링크가 포함되지 않은 프로필 디렉토리 경로가 있어야 합니다. 프로필에 해당 경로가 없습니다.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" />(일련번호 <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397">공급업체 <ph name="VENDOR_ID" />의 <ph name="PRODUCT_NAME" />(일련번호 <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">런처 페이지('<ph name="PAGE" />')를 로드할 수 없습니다.</translation>
+<translation id="388442998277590542">옵션 페이지('<ph name="OPTIONS_PAGE" />')를 로드하지 못했습니다.</translation>
+<translation id="4115165561519362854">이 시스템의 관리자가 최소 <ph name="EXTENSION_NAME" /> <ph name="EXTENSION_VERSION" /> 버전을 요구합니다. 해당 버전 이상으로 업데이트하지 않으면 사용할 수 없습니다.</translation>
+<translation id="4233778200880751280">정보 페이지 '<ph name="ABOUT_PAGE" />'을(를) 로드할 수 없습니다.</translation>
+<translation id="4434145631756268951">{0,select, single{USB 기기 선택}multiple{USB 기기 선택}other{사용되지 않음}}</translation>
+<translation id="4811956658694082538">유틸리티 프로세스가 충돌하여 패키지를 설정하지 못했습니다. Chrome을 다시 시작하고 다시 설치해 보세요.</translation>
+<translation id="5026754133087629784">Webview: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">이 확장 프로그램은 키 파일 '<ph name="KEY_PATH" />'을(를) 포함합니다. 사용하지 않는 것이 좋습니다.</translation>
+<translation id="5627523580512561598">확장 프로그램 <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{HID 기기 선택}multiple{HID 기기 선택}other{사용되지 않음}}</translation>
+<translation id="5960890139610307736">확장보기: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">이 시스템의 관리자가 <ph name="EXTENSION_NAME" />의 설치를 요구합니다. 이 확장 프로그램은 제거할 수 없습니다.</translation>
+<translation id="6027032947578871493"><ph name="VENDOR_NAME" />의 알 수 없는 제품 <ph name="PRODUCT_ID" />(일련번호 <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721">공급업체 <ph name="VENDOR_ID" />의 <ph name="PRODUCT_NAME" /></translation>
+<translation id="6143635259298204954">확장 프로그램을 압축해제할 수 없습니다. 확장 프로그램을 안전하게 압축해제하려면 심볼릭 링크가 포함되지 않은 프로필 디렉토리 경로가 있어야 합니다. 프로필에 해당 경로가 없습니다.</translation>
+<translation id="616804573177634438">{0,select, single{애플리케이션 "<ph name="APP_NAME" />"이(가) 사용자의 기기 중 하나에 대한 액세스 권한을 요청하고 있습니다.}multiple{애플리케이션 "<ph name="APP_NAME" />"이(가) 사용자의 기기 중 하나 이상에 대한 액세스 권한을 요청하고 있습니다.}other{사용되지 않음}}</translation>
+<translation id="641087317769093025">확장 프로그램의 압축을 해제하지 못했습니다.</translation>
+<translation id="657064425229075395">백그라운드 스크립트('<ph name="BACKGROUND_SCRIPT" />')를 로드하지 못했습니다.</translation>
+<translation id="6580950983454333167"><ph name="VENDOR_NAME" />의 제품 <ph name="PRODUCT_NAME" />(일련 번호 <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">'<ph name="DIRECTORY_PATH" />'의 압축을 해제하기 위한 디렉토리를 만들지 못했습니다.</translation>
+<translation id="677806580227005219">Mimehandler: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">확장 프로그램이 너무 자주 새로고침됩니다.</translation>
+<translation id="7003844668372540529"><ph name="VENDOR_NAME" />의 알 수 없는 제품 <ph name="PRODUCT_ID" /></translation>
+<translation id="7217838517480956708">이 기기를 설치할 때 관리자가 <ph name="EXTENSION_NAME" />을(를) 요구합니다. 삭제 또는 수정할 수 없습니다.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" />(확장 프로그램 ID '<ph name="EXTENSION_ID" />')은(는) 관리자에 의해 차단되었습니다.</translation>
+<translation id="7972881773422714442">옵션: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">다른 확장 프로그램(<ph name="EXTENSION_NAME" />)에서 파일 이름을 '<ph name="ACTUAL_FILENAME" />'(으)로 지정했기 때문에 이 확장 프로그램에서 다운로드하는 파일의 이름을 '<ph name="ATTEMPTED_FILENAME" />'(으)로 지정하지 못했습니다.</translation>
+<translation id="8284835137979141223"><ph name="VENDOR_NAME" />의 <ph name="PRODUCT_NAME" /></translation>
+<translation id="8341840687457896278">이 확장 프로그램이 네트워크 요청에 자격 증명을 제공하는 데 실패했습니다. 다른 확장 프로그램(<ph name="EXTENSION_NAME" />)이 다른 자격 증명을 제공했습니다.</translation>
+<translation id="8602184400052594090">매니페스트 파일이 없거나 읽을 수 없습니다.</translation>
+<translation id="8636666366616799973">패키지가 잘못되었습니다. 세부정보: '<ph name="ERROR_MESSAGE" />'.</translation>
+<translation id="8670869118777164560">이 확장 프로그램에서 네트워크 요청을 <ph name="ATTEMPTED_REDIRECT_DESTINATION" />(으)로 리디렉션하는 데 실패했습니다. 다른 확장 프로그램(<ph name="EXTENSION_NAME" />)이 해당 요청을 <ph name="ACTUAL_REDIRECT_DESTINATION" />(으)로 리디렉션했습니다.</translation>
+<translation id="8712265948125780616">확장 프로그램 압축 해제 프로그램</translation>
+<translation id="8825366169884721447">이 확장프로그램이 네트워크 요청의 요청 헤더 '<ph name="HEADER_NAME" />을(를) 수정하는 데 실패했습니다. 수정 요청이 다른 확장 프로그램(<ph name="EXTENSION_NAME" />)와(과) 충돌했습니다.</translation>
+<translation id="9111791539553342076">이 확장 프로그램이 네트워크 요청의 응답 헤더 '<ph name="HEADER_NAME" />'을(를) 수정하는 데 실패했습니다. 수정 요청이 다른 확장 프로그램(<ph name="EXTENSION_NAME" />)와(과) 충돌했습니다.</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_lt.xtb b/chromium/extensions/strings/extensions_strings_lt.xtb
new file mode 100644
index 00000000000..7e70bb1bfdc
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_lt.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="lt">
+<translation id="1135328998467923690">Paketas netinkamas: „<ph name="ERROR_CODE" />“.</translation>
+<translation id="1256619696651732561">Plėtinio aprašo analizavimo įrankis</translation>
+<translation id="1445572445564823378">Dėl šio plėtinio lėčiau veikia „<ph name="PRODUCT_NAME" />“. Kad atkurtumėte „<ph name="PRODUCT_NAME" />“ našumą, turite jo neleisti.</translation>
+<translation id="149347756975725155">Nepavyko įkelti „<ph name="ICON" />“ plėtinio piktogramos.</translation>
+<translation id="1803557475693955505">Nepavyko įkelti fono puslapio „<ph name="BACKGROUND_PAGE" />“.</translation>
+<translation id="2159915644201199628">Nepavyko iššifruoti vaizdo: „<ph name="IMAGE_NAME" />“</translation>
+<translation id="2350172092385603347">Naudotas lokalizavimas, bet deklaracijoje nenurodyta numatytoji lokalė.</translation>
+<translation id="2753617847762399167">Netinkamas kelias (absoliutus arba susijęs su „..“): „<ph name="IMAGE_PATH" />“</translation>
+<translation id="27822970480436970">Šiam plėtiniui nepavyko pakeisti tinklo užklausos, nes pakeitimas nesuderinamas su kitu plėtiniu.</translation>
+<translation id="2857834222104759979">Neteisingas deklaracijos failas.</translation>
+<translation id="2988488679308982380">Nepavyko įdiegti paketo: „<ph name="ERROR_CODE" />“</translation>
+<translation id="3115238746683532089">Nežinomas produktas „<ph name="PRODUCT_ID" />“ iš teikėjo „<ph name="VENDOR_ID" />“ (serijos numeris: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Nežinomas produktas „<ph name="PRODUCT_ID" />“ iš teikėjo „<ph name="VENDOR_ID" />“</translation>
+<translation id="3369521687965833290">Nepavyksta išpakuoti plėtinio. Kad būtų galima saugiai išpakuoti plėtinį, turi būti nurodytas kelias į profilio katalogą, kuris turi prasidėti disko vardu ir kuriame negali būti sujungimo, įrengimo taško ar virtualiojo katalogo.</translation>
+<translation id="3393440416772303020">„<ph name="PRODUCT_NAME" />“ (serijos numeris: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397">„<ph name="PRODUCT_NAME" />“ iš teikėjo „<ph name="VENDOR_ID" />“ (serijos numeris: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Nepavyko įkelti paleidimo priemonės puslapio „<ph name="PAGE" />“.</translation>
+<translation id="388442998277590542">Nepavyko įkelti parinkčių puslapio „<ph name="OPTIONS_PAGE" />“.</translation>
+<translation id="4115165561519362854">Šio kompiuterio administratorius reikalauja, kad „<ph name="EXTENSION_NAME" />“ būtų bent <ph name="EXTENSION_VERSION" /> versijos. Negalėsite įgalinti plėtinio, kol neatnaujinsite jo į nurodytą (ar naujesnę) versiją.</translation>
+<translation id="4233778200880751280">Nepavyko įkelti informacijos puslapio „<ph name="ABOUT_PAGE" />“.</translation>
+<translation id="4434145631756268951">{0,select, single{Pasirinkite USB įrenginį}multiple{Pasirinkite USB įrenginius}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Nepavyko įdiegti paketo, nes užstrigo priemonės procesas. Bandykite iš naujo paleisti „Chrome“ ir bandykite dar kartą.</translation>
+<translation id="5026754133087629784">Žiniatinklio rodinys: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Programų rodinys: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Šiame plėtinyje yra rakto failas „<ph name="KEY_PATH" />“. Neturėtumėte to daryti.</translation>
+<translation id="5627523580512561598">„<ph name="EXTENSION_NAME" />“ plėtinys</translation>
+<translation id="5630931906013276297">{0,select, single{Pasirinkite HID įrenginį}multiple{Pasirinkite HID įrenginius}other{UNUSED}}</translation>
+<translation id="5960890139610307736">Plėtinio peržiūra: „<ph name="EXTENSIONVIEW_TAG_NAME" />“</translation>
+<translation id="5972529113578162692">Šio įrenginio administratorius reikalauja, kad „<ph name="EXTENSION_NAME" />“ plėtinys būtų įdiegtas. Jo negalima pašalinti.</translation>
+<translation id="6027032947578871493">Nežinomas produktas „<ph name="PRODUCT_ID" />“ iš „<ph name="VENDOR_NAME" />“ (serijos numeris: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721">„<ph name="PRODUCT_NAME" />“ iš teikėjo „<ph name="VENDOR_ID" />“</translation>
+<translation id="6143635259298204954">Nepavyksta išpakuoti plėtinio. Kad būtų galima saugiai išpakuoti plėtinį, turi būti nurodytas kelias į profilio katalogą, kuriame nebūtų virtualiojo katalogo. Nėra tokio tipo jūsų profilio kelio.</translation>
+<translation id="616804573177634438">{0,select, single{Programa „<ph name="APP_NAME" />“ prašo leidimo pasiekti vieną iš jūsų įrenginių}multiple{Programa „<ph name="APP_NAME" />“ prašo leidimo pasiekti vieną ar daugiau iš jūsų įrenginių.}other{UNUSED}}</translation>
+<translation id="641087317769093025">Nepavyko išarchyvuoti plėtinio</translation>
+<translation id="657064425229075395">Nepavyko įkelti foninio scenarijaus „<ph name="BACKGROUND_SCRIPT" />“.</translation>
+<translation id="6580950983454333167">„<ph name="PRODUCT_NAME" />“, pardavėjas „<ph name="VENDOR_NAME" />“ (serijos numeris <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Nepavyko sukurti išarchyvavimo katalogo: „<ph name="DIRECTORY_PATH" />“</translation>
+<translation id="677806580227005219">MIME tvarkyklė: „<ph name="MIMEHANDLERVIEW_TAG_NAME" />“</translation>
+<translation id="6840444547062817500">Šis plėtinys buvo per dažnai įkeltas iš naujo.</translation>
+<translation id="7003844668372540529">Nežinomas produktas „<ph name="PRODUCT_ID" />“ iš „<ph name="VENDOR_NAME" />“</translation>
+<translation id="7217838517480956708">Šio kompiuterio administratorius reikalauja, kad būtų įdiegtas papildinys „<ph name="EXTENSION_NAME" />“. Jo negalima pašalinti ar pakeisti.</translation>
+<translation id="7809034755304591547">Administratorius užblokavo „<ph name="EXTENSION_NAME" />“ (plėtinio ID „<ph name="EXTENSION_ID" />“).</translation>
+<translation id="7972881773422714442">Parinktys: „<ph name="EXTENSIONOPTIONS_TAG_NAME" />“</translation>
+<translation id="8047248493720652249">Šiam plėtiniui nepavyko pavadinti atsisiuntimo „<ph name="ATTEMPTED_FILENAME" />“, nes kitas plėtinys („<ph name="EXTENSION_NAME" />“) nustatė kitokį failo pavadinimą – „<ph name="ACTUAL_FILENAME" />“.</translation>
+<translation id="8284835137979141223">„<ph name="PRODUCT_NAME" />“ iš „<ph name="VENDOR_NAME" />“</translation>
+<translation id="8341840687457896278">Šiam plėtiniui pateikiant įgaliojimą tinklo užklausai įvyko klaida, nes kitas plėtinys („<ph name="EXTENSION_NAME" />“) pateikė kitokį įgaliojimą.</translation>
+<translation id="8602184400052594090">Trūksta deklaracijos arba ji nenuskaitoma.</translation>
+<translation id="8636666366616799973">Paketas netinkamas. Išsami informacija: „<ph name="ERROR_MESSAGE" />“.</translation>
+<translation id="8670869118777164560">Šiam plėtiniui peradresuojant tinklo užklausą <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> įvyko klaida, nes kitas plėtinys („<ph name="EXTENSION_NAME" />“) peradresavo ją <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Plėtinių išskleidimo priemonė</translation>
+<translation id="8825366169884721447">Šiam plėtiniui keičiant tinklo užklausos antraštę „<ph name="HEADER_NAME" />“ įvyko klaida, nes dėl pakeitimo kilo konfliktas su kitu plėtiniu („<ph name="EXTENSION_NAME" />“).</translation>
+<translation id="9111791539553342076">Šiam plėtiniui keičiant tinklo užklausos atsakymo antraštę „<ph name="HEADER_NAME" />“ įvyko klaida, nes dėl pakeitimo kilo konfliktas su kitu plėtiniu („<ph name="EXTENSION_NAME" />“).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_lv.xtb b/chromium/extensions/strings/extensions_strings_lv.xtb
new file mode 100644
index 00000000000..fa79abc7d22
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_lv.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="lv">
+<translation id="1135328998467923690">Pakotne nav derīga: <ph name="ERROR_CODE" />.</translation>
+<translation id="1256619696651732561">Paplašinājumu manifestu parsētājs</translation>
+<translation id="1445572445564823378">Šis paplašinājums palēnina <ph name="PRODUCT_NAME" /> darbību. Atspējojiet to, lai atjaunotu <ph name="PRODUCT_NAME" /> veiktspēju.</translation>
+<translation id="149347756975725155">Nevarēja ielādēt paplašinājuma ikonu “<ph name="ICON" />”.</translation>
+<translation id="1803557475693955505">Nevarēja ielādēt fona lapu “<ph name="BACKGROUND_PAGE" />”.</translation>
+<translation id="2159915644201199628">Nevarēja dekodēt attēlu: <ph name="IMAGE_NAME" /></translation>
+<translation id="2350172092385603347">Lokalizācija ir lietota, tomēr manifestā nav norādīta default_locale.</translation>
+<translation id="2753617847762399167">Nederīgs ceļš (pilnais vai relatīvais ar elementu “..”): <ph name="IMAGE_PATH" /></translation>
+<translation id="27822970480436970">Šim paplašinājumam neizdevās modificēt tīkla pieprasījumu, jo šī modifikācija bija pretrunā ar citu paplašinājumu.</translation>
+<translation id="2857834222104759979">Manifesta fails nav derīgs.</translation>
+<translation id="2988488679308982380">Nevarēja instalēt pakotni: <ph name="ERROR_CODE" />.</translation>
+<translation id="3115238746683532089">Nezināms produkts (ID: <ph name="PRODUCT_ID" />), ko piedāvā <ph name="VENDOR_ID" /> (sērijas numurs: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Nezināms produkts (ID: <ph name="PRODUCT_ID" />), ko piedāvā <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Nevar atvērt paplašinājuma pakotni. Lai varētu drošā veidā atvērt paplašinājuma pakotni, jābūt norādītam ceļam uz jūsu profila direktoriju, kas sākas ar diska burtu un neietver savienojumu, montēšanas punktu vai simbolisku saiti. Jūsu profilam nav norādīts šāds ceļš.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (sērijas numurs: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" />, ko piedāvā <ph name="VENDOR_ID" /> (sērijas numurs: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Nevarēja ielādēt palaišanas programmas lapu “<ph name="PAGE" />”.</translation>
+<translation id="388442998277590542">Nevarēja ielādēt opciju lapu “<ph name="OPTIONS_PAGE" />”.</translation>
+<translation id="4115165561519362854">Šīs iekārtas administrators pieprasa, lai tiktu izmantots pakalpojums <ph name="EXTENSION_NAME" /> <ph name="EXTENSION_VERSION" /> vai jaunāka versija. To nevar iespējot, līdz nebūs veikta jaunināšana uz norādīto (vai jaunāku) versiju.</translation>
+<translation id="4233778200880751280">Nevarēja ielādēt lapu “Par” (<ph name="ABOUT_PAGE" />).</translation>
+<translation id="4434145631756268951">{0,select, single{USB ierīces atlasīšana}multiple{USB ierīču atlasīšana}other{NETIEK LIETOTS}}</translation>
+<translation id="4811956658694082538">Nevarēja instalēt pakotni, jo radās avārija utilītprogrammas darbībā. Restartējiet pārlūku Chrome un mēģiniet vēlreiz.</translation>
+<translation id="5026754133087629784">Webview: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Lietotnes skats: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Šis paplašinājums ietver atslēgas failu <ph name="KEY_PATH" />. Iespējams, jūs to nevēlaties izmantot.</translation>
+<translation id="5627523580512561598">Paplašinājums <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{HID ierīces atlasīšana}multiple{HID ierīču atlasīšana}other{NETIEK LIETOTS}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Šīs ierīces administrators pieprasa, lai būtu instalēts paplašinājums <ph name="EXTENSION_NAME" />. To nevar atinstalēt.</translation>
+<translation id="6027032947578871493">Nezināms produkts (ID: <ph name="PRODUCT_ID" />), ko piedāvā <ph name="VENDOR_NAME" /> (sērijas numurs: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" />, ko piedāvā <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Nevar atvērt paplašinājuma pakotni. Lai varētu drošā veidā atvērt paplašinājuma pakotni, jābūt norādītam ceļam uz jūsu profila direktoriju, kas neietver simbolisku saiti. Jūsu profilam nav norādīts šāds ceļš.</translation>
+<translation id="616804573177634438">{0,select, single{Lietojumprogramma “<ph name="APP_NAME" />” pieprasa piekļuvi kādai jūsu ierīcei.}multiple{Lietojumprogramma “<ph name="APP_NAME" />” pieprasa piekļuvi vienai vai vairākām jūsu ierīcēm.}other{NETIEK LIETOTS}}</translation>
+<translation id="641087317769093025">Paplašinājumu nevarēja izgūt no ZIP arhīva.</translation>
+<translation id="657064425229075395">Nevarēja ielādēt fona skriptu <ph name="BACKGROUND_SCRIPT" />.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" />, ko piedāvā <ph name="VENDOR_NAME" /> (sērijas numurs: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Nevarēja izveidot direktoriju izgūšanai no ZIP arhīva: <ph name="DIRECTORY_PATH" /></translation>
+<translation id="677806580227005219">MIME apdarinātājs: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Šis paplašinājums pārāk bieži veica atkārtotu ielādi.</translation>
+<translation id="7003844668372540529">Nezināms produkts (ID: <ph name="PRODUCT_ID" />), ko piedāvā <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Šīs ierīces administrators uzskata, ka paplašinājumam <ph name="EXTENSION_NAME" /> ir jābūt instalētam. To nevar noņemt vai pārveidot.</translation>
+<translation id="7809034755304591547">Administrators bloķēja paplašinājumu <ph name="EXTENSION_NAME" /> (paplašinājuma ID “<ph name="EXTENSION_ID" />”).</translation>
+<translation id="7972881773422714442">Iespējas: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Šim paplašinājumam neizdevās piešķirt nosaukumu lejupielādei “<ph name="ATTEMPTED_FILENAME" />”, jo cits paplašinājums (<ph name="EXTENSION_NAME" />) noteica citu faila nosaukumu “<ph name="ACTUAL_FILENAME" />”.</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> no <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Šis paplašinājums nevarēja sniegt tīkla pieprasījuma akreditācijas datus, jo cits paplašinājums (<ph name="EXTENSION_NAME" />) sniedza atšķirīgus akreditācijas datus.</translation>
+<translation id="8602184400052594090">Manifesta fails nav atrodams vai nolasāms.</translation>
+<translation id="8636666366616799973">Pakotne nav derīga. Detaļas: <ph name="ERROR_MESSAGE" />.</translation>
+<translation id="8670869118777164560">Šis paplašinājums nevarēja novirzīt tīkla pieprasījumu uz galamērķi <ph name="ATTEMPTED_REDIRECT_DESTINATION" />, jo cits paplašinājums (<ph name="EXTENSION_NAME" />) to novirzīja uz šādu galamērķi: <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Paplašinājumu pakotņu atvērējs</translation>
+<translation id="8825366169884721447">Šis paplašinājums nevarēja mainīt tīkla pieprasījuma galveni “<ph name="HEADER_NAME" />”, jo izmaiņas radīja konfliktu ar citu paplašinājumu (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Šis paplašinājums nevarēja mainīt tīkla pieprasījuma atbildes galveni “<ph name="HEADER_NAME" />”, jo izmaiņas radīja konfliktu ar citu paplašinājumu (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_ml.xtb b/chromium/extensions/strings/extensions_strings_ml.xtb
new file mode 100644
index 00000000000..e070954daba
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_ml.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ml">
+<translation id="1135328998467923690">പാക്കേജ് അസാധുവാണ്: '<ph name="ERROR_CODE" />'.</translation>
+<translation id="1256619696651732561">വിപുലീകരണ മാനിഫെസ്റ്റ് പാഴ്‌സർ</translation>
+<translation id="1445572445564823378">ഈ വിപുലീകരണം <ph name="PRODUCT_NAME" />-നെ മന്ദഗതിയിലാക്കുന്നു. <ph name="PRODUCT_NAME" />-ന്റെ പ്രകടനം പുനഃസംഭരിക്കുന്നതിനായി നിങ്ങൾ അതിനെ അപ്രാപ്തമാക്കണം.</translation>
+<translation id="149347756975725155">വിപുലീകരണ ഐക്കണ്‍ '<ph name="ICON" />' ലോഡുചെയ്യാനായില്ല.</translation>
+<translation id="1803557475693955505">പശ്ചാത്തല പേജ് '<ph name="BACKGROUND_PAGE" />' ലോഡുചെയ്യാന്‍ കഴിഞ്ഞില്ല.</translation>
+<translation id="2159915644201199628">ചിത്രം ഡീകോഡ് ചെയ്യാൻ കഴിഞ്ഞില്ല: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">ലോക്കലൈസേഷന്‍ ഉപയോഗിച്ചു, എന്നാല്‍ default_locale മാനിഫെസ്റ്റില്‍ വ്യക്തമാക്കിയില്ല.</translation>
+<translation id="2753617847762399167">നിയമവിരുദ്ധമായ പാത്ത് (കേവലം അല്ലെങ്കിൽ '..' എന്നതുമായി ബന്ധപ്പെട്ടത്): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">ഈ വിപുലീകരണം ഒരു നെറ്റ്‌വർക്ക് അഭ്യർത്ഥന പരിഷ്‌കരിക്കുന്നതിൽ പരാജയപ്പെട്ടു കാരണം പരിഷ്‌കരണത്തിന് മറ്റൊരു വിപുലീകരണവുമായി പൊരുത്തക്കേടുണ്ട്.</translation>
+<translation id="2857834222104759979">മാനിഫെസ്റ്റ് ഫയല്‍ അസാധുവാണ്.</translation>
+<translation id="2988488679308982380">പാക്കേജ് ഇന്‍സ്റ്റാള്‍ ചെയ്യാന്‍ കഴിഞ്ഞില്ല: '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089"><ph name="VENDOR_ID" /> വെൻഡറിൽ നിന്നുള്ള അജ്ഞാത ഉൽപ്പന്നം <ph name="PRODUCT_ID" /> (സീരിയൽ നമ്പർ <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963"><ph name="VENDOR_ID" /> വെൻഡറിൽ നിന്നുള്ള അജ്ഞാത ഉൽപ്പന്നം <ph name="PRODUCT_ID" /></translation>
+<translation id="3369521687965833290">വിപുലീകരണം അണ്‍പാക്ക് ചെയ്യാന്‍ കഴിയുന്നില്ല. ഒരു വിപുലീകരണം സുരക്ഷിതമായി അണ്‍പാക്ക് ചെയ്യുന്നതിന്, ഡ്രൈവ് പ്രതീകം ഉപയോഗിച്ച് ആരംഭിക്കുന്ന നിങ്ങളുടെ പ്രൊഫൈൽ ഡയറക്ടറിയിലേക്കുള്ള പാത്ത് ഉണ്ടായിരിക്കണം മാത്രമല്ല ജംഗ്ഷന്‍, മൌണ്ട് പോയിന്‍റ് അല്ലെങ്കില്‍ സിം‌ലിങ്ക് എന്നിവ അടങ്ങിയിരിക്കരുത്. നിങ്ങളുടെ പ്രൊഫൈലിനായി അത്തരം പാത്തൊന്നും നിലവിലുണ്ടായിരിക്കരുത്.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (സീരിയൽ നമ്പർ <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="VENDOR_ID" /> വെൻഡറിൽ നിന്നുള്ള <ph name="PRODUCT_NAME" /> (സീരിയൽ നമ്പർ <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">ലോഞ്ചർ പേജ് '<ph name="PAGE" />' ലോഡുചെയ്യാനായില്ല.</translation>
+<translation id="388442998277590542">'<ph name="OPTIONS_PAGE" />' ഓപ്ഷനുകള്‍ പേജ് ലോഡുചെയ്യാന്‍ കഴിഞ്ഞില്ല.</translation>
+<translation id="4115165561519362854"><ph name="EXTENSION_VERSION" /> എന്നതിന്റെ ഏറ്റവും കുറഞ്ഞ പതിപ്പിനായി ഈ യന്ത്രത്തിന്റെ അഡ്‌മിനിസ്‌ട്രേറ്റർക്ക് <ph name="EXTENSION_NAME" /> എന്നതാവശ്യമാണ്. അത് ആ പതിപ്പിലേക്ക് (അല്ലെങ്കിൽ അതിനും മുകളിലേക്ക്) അപ്‌ഡേറ്റുചെയ്യുന്നതുവരെ അത് പ്രവർത്തനക്ഷമമാക്കാനാവില്ല.</translation>
+<translation id="4233778200880751280">'<ph name="ABOUT_PAGE" />' എന്ന ആമുഖം പേജ് ലോഡുചെയ്യാൻ കഴിഞ്ഞില്ല.</translation>
+<translation id="4434145631756268951">{0,select, single{ഒരു USB ഉപകരണം തിരഞ്ഞെടുക്കുക}multiple{USB ഉപകരണങ്ങൾ തിരഞ്ഞെടുക്കുക}other{UNUSED}}</translation>
+<translation id="4811956658694082538">യൂട്ടിലിറ്റി പ്രോസസ്സ് ക്രാഷായതിനാൽ പാക്കേജ് ഇൻസ്റ്റാൾ ചെയ്യാനായില്ല. Chrome വീണ്ടും ആരംഭിച്ച് വീണ്ടും ശ്രമിക്കുന്നത് പരീക്ഷിക്കുക.</translation>
+<translation id="5026754133087629784">വെബ് കാഴ്‌ച: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">അപ്ലിക്കേഷൻ കാഴ്‌‌ച: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">ഈ വിപുലീകരണത്തിൽ '<ph name="KEY_PATH" />' എന്ന കീ ഫയൽ ഉൾപ്പെടുന്നു. മിക്കവാറും നിങ്ങൾ ഇത് ചെയ്യാൻ താൽപ്പര്യപ്പെട്ടേക്കില്ല.</translation>
+<translation id="5627523580512561598">വിപുലീകരണം<ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{ഒരു HID ഉപകരണം തിരഞ്ഞെടുക്കുക}multiple{HID ഉപകരണങ്ങൾ തിരഞ്ഞെടുക്കുക}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">ഈ മെഷീനിന്റെ അഡ്‌മിനിസ്ട്രേറ്ററിന് <ph name="EXTENSION_NAME" /> ഇൻസ്‌റ്റാളുചെയ്യേണ്ടതുണ്ട്. ഇത് അൺഇൻസ്‌റ്റാളുചെയ്യാനാകില്ല.</translation>
+<translation id="6027032947578871493"><ph name="VENDOR_NAME" /> എന്നതിൽ നിന്നുള്ള അജ്ഞാത ഉൽപ്പന്നം <ph name="PRODUCT_ID" /> (സീരിയൽ നമ്പർ <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="VENDOR_ID" /> വെൻഡറിൽ നിന്നുള്ള <ph name="PRODUCT_NAME" /></translation>
+<translation id="6143635259298204954">വിപുലീകരണം അണ്‍പാക്ക് ചെയ്യാന്‍ കഴിയില്ല. വിപുലീകരണം സുരക്ഷിതമായി അണ്‍പാക്ക് ചെയ്യുന്നതിന്, നിങ്ങളുടെ പ്രൊഫൈൽ ഡയറക്ടറിയിലേക്ക് ഒരു സിം‌ലിങ്ക് അടങ്ങിയിട്ടില്ലാത്ത ഒരു പാത്ത് ഉണ്ടായിരിക്കണം. നിങ്ങളുടെ പ്രൊഫൈലിനായി അത്തരം പാത്തൊന്നും നിലവിലുണ്ടായിരിക്കരുത്.</translation>
+<translation id="616804573177634438">{0,select, single{"<ph name="APP_NAME" />" അപ്ലിക്കേഷൻ നിങ്ങളുടെ ഉപകരണങ്ങളിലൊന്നിലേക്ക് ആക്‌സസ്സ് അഭ്യർത്ഥിക്കുന്നു.}multiple{"<ph name="APP_NAME" />" അപ്ലിക്കേഷൻ നിങ്ങളുടെ ഒന്നോ അതിലധികമോ ഉപകരണങ്ങളിലേക്ക് ആക്‌സസ്സ് അഭ്യർത്ഥിക്കുന്നു.}other{UNUSED}}</translation>
+<translation id="641087317769093025">വിപുലീകരണം അൺസിപ്പ് ചെയ്യാൻ കഴിഞ്ഞില്ല</translation>
+<translation id="657064425229075395">പശ്ചാത്തല സ്‌ക്രിപ്റ്റ് '<ph name="BACKGROUND_SCRIPT" />' ലോഡുചെയ്യാൻ കഴിഞ്ഞില്ല.</translation>
+<translation id="6580950983454333167"><ph name="VENDOR_NAME" /> എന്നതിൽ നിന്നുള്ള <ph name="PRODUCT_NAME" /> (സീരിയൽ നമ്പർ <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">അൺസിപ്പ് ചെയ്യാനായി ഡയറക്‌ടറി സൃഷ്‌ടിക്കാൻ കഴിഞ്ഞില്ല: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">മൈംഹാൻഡ്‌ലർ: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">ഈ വിപുലീകരണം പതിവായി സ്വയം റീലോഡുചെയ്യുന്നു.</translation>
+<translation id="7003844668372540529"><ph name="VENDOR_NAME" /> എന്നതിൽ നിന്നുള്ള അജ്ഞാത ഉൽപ്പന്നം <ph name="PRODUCT_ID" /></translation>
+<translation id="7217838517480956708">ഈ മെഷീനിന്റെ അഡ്‌മിനിസ്‌ട്രേറ്റർ ഇൻസ്റ്റാളുചെയ്യുന്നതിനായി <ph name="EXTENSION_NAME" /> ആവശ്യപ്പെടുന്നു. ഇത് നീക്കംചെയ്യാനോ പരിഷ്‌ക്കരിക്കാനോ കഴിയില്ല.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (വിപുലീകരണ ID "<ph name="EXTENSION_ID" />") അഡ്‌മിനിസ്‌ട്രേറ്റർ തടഞ്ഞു.</translation>
+<translation id="7972881773422714442">ഓപ്‌ഷനുകൾ: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">മറ്റൊരു വിപുലീകരണം (<ph name="EXTENSION_NAME" />) വ്യത്യസ്‌തമായ ഒരു ഫയൽനാമം "<ph name="ACTUAL_FILENAME" />" നിർണ്ണയിച്ചതിനാൽ ഈ വിപുലീകരണം ഡൗൺലോഡ് "<ph name="ATTEMPTED_FILENAME" />-ന് പേരുനൽകുന്നതിൽ പരാജയപ്പെട്ടു.</translation>
+<translation id="8284835137979141223"><ph name="VENDOR_NAME" /> എന്നതിൽ നിന്നുള്ള <ph name="PRODUCT_NAME" /></translation>
+<translation id="8341840687457896278">മറ്റൊരു (<ph name="EXTENSION_NAME" />) വിപുലീകരണം വ്യത്യസ്‌ത ക്രെഡൻഷ്യലുകൾ നൽകിയതിനാൽ ഒരു നെറ്റ്‌വർക്ക് അഭ്യർത്ഥനയിലേക്ക് ക്രെഡൻഷ്യലുകൾ നൽകുന്നതിൽ ഈ വിപുലീകരണം പരാജയപ്പെട്ടു.</translation>
+<translation id="8602184400052594090">മാനിഫെസ്റ്റ് ഫയല്‍ നഷ്ടപ്പെട്ടിരിക്കുന്നു അല്ലെങ്കില്‍ റീഡ് ചെയ്യാന്‍ കഴിയുന്നില്ല.</translation>
+<translation id="8636666366616799973">പാക്കേജ് അസാധുവാണ്. വിശദാംശങ്ങൾ: '<ph name="ERROR_MESSAGE" />'.</translation>
+<translation id="8670869118777164560">മറ്റൊരു വിപുലീകരണം (<ph name="EXTENSION_NAME" />), <ph name="ACTUAL_REDIRECT_DESTINATION" />-ലേക്ക് ഒരു നെറ്റ്‌വർക്ക് അഭ്യർത്ഥനയെ റീഡയറക്റ്റുചെയ്‌തതിനാൽ അതിനെ <ph name="ATTEMPTED_REDIRECT_DESTINATION" />-ലേക്ക് റീഡയറക്റ്റുചെയ്യുന്നതിന് ഈ വിപുലീകരണം പരാജയപ്പെട്ടു.</translation>
+<translation id="8712265948125780616">വിപുലീകരണ അൺ‌പാക്കർ</translation>
+<translation id="8825366169884721447">പരിഷ്‌ക്കാരം മറ്റൊരു (<ph name="EXTENSION_NAME" />) വിപുലീകരണവുമായി വൈരുദ്ധ്യം സൃഷ്‌ടിക്കുന്നതിനാൽ ഒരു നെറ്റ്‌വർക്ക് അഭ്യർത്ഥനയുടെ അഭ്യർത്ഥന തലക്കെട്ട് "<ph name="HEADER_NAME" />" പരിഷ്‌ക്കരിക്കുന്നതിൽ ഈ വിപുലീകരണം പരാജയപ്പെട്ടു.</translation>
+<translation id="9111791539553342076">പരിഷ്‌ക്കാരം മറ്റൊരു (<ph name="EXTENSION_NAME" />) വിപുലീകരണവുമായി വൈരുദ്ധ്യം സൃഷ്‌ടിക്കുന്നതിനാൽ ഒരു നെറ്റ്‌വർക്ക് അഭ്യർത്ഥനയുടെ പ്രതികരണ തലക്കെട്ട് "<ph name="HEADER_NAME" />" പരിഷ്‌ക്കരിക്കുന്നതിൽ ഈ വിപുലീകരണം പരാജയപ്പെട്ടു.</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_mr.xtb b/chromium/extensions/strings/extensions_strings_mr.xtb
new file mode 100644
index 00000000000..4fdd34f5b52
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_mr.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="mr">
+<translation id="1135328998467923690">हे पॅकेज अवैध आहे: '<ph name="ERROR_CODE" />'.</translation>
+<translation id="1256619696651732561">विस्तार मॅनिफेस्ट विश्लेषक</translation>
+<translation id="1445572445564823378">हा विस्तार <ph name="PRODUCT_NAME" /> मंद होत आहे. <ph name="PRODUCT_NAME" /> चे कार्यप्रदर्शन पुनर्संचयित करण्‍यासाठी आपण ते ‍अक्षम करणे आवश्‍यक आहे.</translation>
+<translation id="149347756975725155">विस्तार प्रतीक '<ph name="ICON" />' लोड करणे शक्य नाही.</translation>
+<translation id="1803557475693955505">'पार्श्वभूमी पृष्ठ '<ph name="BACKGROUND_PAGE" />' लोड करणे शक्य नाही.</translation>
+<translation id="2159915644201199628">प्रतिमा डीकोड करणे शक्य झाले नाही: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">अनुवाद वापरले, परंतु मॅनिफेस्टमध्ये डीफॉल्ट_लोकॅल निर्दिष्ट नाही.</translation>
+<translation id="2753617847762399167">बेकायदेशीर पथ ('..' सह अचूक किंवा संबंधित): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">हा विस्तार नेटवर्क विनंती सुधारण्‍यात अयशस्वी झाला कारण सुधारणेचा दुसर्‍या विस्ताराशी विवाद झाला.</translation>
+<translation id="2857834222104759979">मॅनिफेस्ट फाइल अवैध आहे.</translation>
+<translation id="2988488679308982380">हे पॅकेज स्थापित करणे शक्य नाही: '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089"><ph name="VENDOR_ID" /> विक्रेत्याकडील <ph name="PRODUCT_ID" /> अज्ञात उत्पादन (अनुक्रमांक <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963"><ph name="VENDOR_ID" /> विक्रेत्याकडील <ph name="PRODUCT_ID" /> अज्ञात उत्पादन</translation>
+<translation id="3369521687965833290">विस्तार अनपॅक करणे शक्य नाही. विस्तार सुरक्षितपणे अनपॅक करण्यासाठी, आपल्या प्रोफाइल निर्देशिकेत ड्राइव्ह अक्षरासह प्रारंभ होणारा पथ असणे आणि जंक्शन, माउंट पॉइंट किंवा सिमलिंक नसणे आवश्यक आहे. आपल्या प्रोफाइलसाठी असा कोणताही पथ विद्यमान नाही.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (अनुक्रमांक <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="VENDOR_ID" /> विक्रेत्याकडील <ph name="PRODUCT_NAME" /> (अनुक्रमांक <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">लाँचर पृष्ठ '<ph name="PAGE" />' लोड करू शकलो नाही.</translation>
+<translation id="388442998277590542">पर्याय पृष्ठ '<ph name="OPTIONS_PAGE" />' लोड करणे शक्य नाही.</translation>
+<translation id="4115165561519362854">या मशीनच्या प्रशासकाला <ph name="EXTENSION_NAME" /> कडे <ph name="EXTENSION_VERSION" /> ची किमान आवृत्ती असणे आवश्यक आहे. ही त्या आवृत्तीवर (किंवा उच्च) अद्यतनित करेपर्यंत हे सक्षम होऊ शकत नाही.</translation>
+<translation id="4233778200880751280">'<ph name="ABOUT_PAGE" />' पृष्ठाविषयी लोड करू शकलो नाही.</translation>
+<translation id="4434145631756268951">{0,select, single{एक USB डिव्हाइस निवडा}multiple{USB डिव्हाइस निवडा}other{UNUSED}}</translation>
+<translation id="4811956658694082538">उपयुक्तता प्रक्रिया क्रॅश झाल्यामुळे पॅकेज स्थापित करू शकलो नाही. Chrome रीस्टार्ट करून पहा आणि पुन्हा प्रयत्न करून पहा.</translation>
+<translation id="5026754133087629784">वेबदृश्य: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">अॅपदृश्य: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">हा विस्तार मुख्य फाइल '<ph name="KEY_PATH" />' समाविष्ट करतो. आपण कदाचित ते करू इच्छित नसू शकता.</translation>
+<translation id="5627523580512561598">विस्तार <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{एक HID डिव्हाइस निवडा}multiple{HID डिव्हाइस निवडा}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">या मशीनच्या प्रशासकासाठी <ph name="EXTENSION_NAME" /> स्थापित करणे आवश्यक आहे. हे विस्थापित केले जाऊ शकत नाही.</translation>
+<translation id="6027032947578871493"><ph name="VENDOR_NAME" /> कडील <ph name="PRODUCT_ID" /> अज्ञात उत्पादन (अनुक्रमांक <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="VENDOR_ID" /> विक्रेत्याकडील <ph name="PRODUCT_NAME" /></translation>
+<translation id="6143635259298204954">विस्तार अनपॅक करू नका. विस्तार सुरक्षितपणे अनपॅक करण्यासाठी, आपल्या प्रोफाइल निदेशिकेत सिमलिंक नसलेला पथ असणे आवश्यक आहे. आपल्या प्रोफाइलसाठी असा कोणताही पथ विद्यमान नाही.</translation>
+<translation id="616804573177634438">{0,select, single{"<ph name="APP_NAME" />" अनुप्रयोग आपल्या एका डिव्हाइसवर प्रवेशाची विनंती करत आहे.}multiple{"<ph name="APP_NAME" />" अनुप्रयोग आपल्या एका डिव्हाइसवर प्रवेशाची विनंती करत आहे.}other{UNUSED}}</translation>
+<translation id="641087317769093025">विस्तार अनझिप करणे शक्य झाले नाही</translation>
+<translation id="657064425229075395">पार्श्वभूमी स्क्रिप्‍ट '<ph name="BACKGROUND_SCRIPT" />' लोड करू शकले नाही.</translation>
+<translation id="6580950983454333167"><ph name="VENDOR_NAME" /> (अनुक्रमांक <ph name="SERIAL_NUMBER" />) कडील <ph name="PRODUCT_NAME" /></translation>
+<translation id="6731255991101203740">अनझिप करण्यासाठी निर्देशिका तयार करणे शक्य झाले नाही: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">Mimehandler: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">या विस्ताराने स्वतःस वारंवार रीलोड केले आहे.</translation>
+<translation id="7003844668372540529"><ph name="VENDOR_NAME" /> कडील <ph name="PRODUCT_ID" /> अज्ञात उत्पादन</translation>
+<translation id="7217838517480956708">या मशीनच्या प्रशासकास <ph name="EXTENSION_NAME" /> स्थापन करणे आवश्यक आहे. हे काढले किंवा सुधारित केले जाऊ शकत नाही.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (विस्तार ID "<ph name="EXTENSION_ID" />") प्रशासकाद्वारे अवरोधित करण्यात आला आहे.</translation>
+<translation id="7972881773422714442">पर्याय: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">दुसर्‍या विस्ताराने (<ph name="EXTENSION_NAME" />) एक वेगळे फाईलनाव "<ph name="ACTUAL_FILENAME" />" निर्धारित केल्यामुळे डाउनलोड "<ph name="ATTEMPTED_FILENAME" />" ला नाव देण्यात हा विस्तार अयशस्वी झाला.</translation>
+<translation id="8284835137979141223"><ph name="VENDOR_NAME" /> कडील <ph name="PRODUCT_NAME" /></translation>
+<translation id="8341840687457896278">हा विस्तार एका नेटवर्क विनंतीवर क्रेडेन्शियल प्रदान करण्यात अयशस्वी झाला कारण दुसर्‍या (<ph name="EXTENSION_NAME" />) विस्ताराने वेगळी क्रेडेन्शियल प्रदान केली.</translation>
+<translation id="8602184400052594090">मॅनिफेस्ट फाइल गहाळ किंवा अवाचनीय आहे.</translation>
+<translation id="8636666366616799973">पॅकेज अवैध आहे. तपशील: '<ph name="ERROR_MESSAGE" />'.</translation>
+<translation id="8670869118777164560">हा विस्तार <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> वर एक नेटवर्क विनंती पुनर्निर्देशित करण्यात अयशस्वी झाला कारण दुसर्‍या (<ph name="EXTENSION_NAME" />) विस्ताराने तो <ph name="ACTUAL_REDIRECT_DESTINATION" /> वर पुनर्निर्देशित केला.</translation>
+<translation id="8712265948125780616">विस्तार अनपॅकर</translation>
+<translation id="8825366169884721447">हा विस्तार एका नेटवर्क विनंतीचा "<ph name="HEADER_NAME" />" हा विनंती शीर्षलेख सुधारित करण्यात अयशस्वी झाला कारण दुसर्‍या (<ph name="EXTENSION_NAME" />) विस्तारासह सुधारणेचा संघर्ष झाला.</translation>
+<translation id="9111791539553342076">हा विस्तार एका नेटवर्क विनंतीचा "<ph name="HEADER_NAME" />" हा प्रतिसाद शीर्षलेख सुधारित करण्यात अयशस्वी झाला कारण दुसर्‍या (<ph name="EXTENSION_NAME" />) विस्तारासह सुधारणेचा संघर्ष झाला.</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_ms.xtb b/chromium/extensions/strings/extensions_strings_ms.xtb
new file mode 100644
index 00000000000..f5513deb261
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_ms.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ms">
+<translation id="1135328998467923690">Pakej adalah tidak sah: '<ph name="ERROR_CODE" />'.</translation>
+<translation id="1256619696651732561">Penghurai Manifes Sambungan</translation>
+<translation id="1445572445564823378">Sambungan ini melambatkan <ph name="PRODUCT_NAME" />. Anda perlu melumpuhkannya untuk memulihkan prestasi <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">Tidak dapat memuatkan ikon sambungan '<ph name="ICON" />'.</translation>
+<translation id="1803557475693955505">Tidak dapat memuatkan halaman latar belakang '<ph name="BACKGROUND_PAGE" />'.</translation>
+<translation id="2159915644201199628">Tidak dapat menyahkod imej: ' <ph name="IMAGE_NAME" /> '</translation>
+<translation id="2350172092385603347">Penempatan digunakan, tetapi default_locale tidak dinyatakan dalam ketara.</translation>
+<translation id="2753617847762399167">Laluan tidak sah (mutlak atau berkaitan dengan '..'): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">Sambungan ini gagal mengubah suai permintaan rangkaian kerana pengubahsuaian itu bercanggah dengan sambungan lain.</translation>
+<translation id="2857834222104759979">Fail manifes tidak sah.</translation>
+<translation id="2988488679308982380">Tidak dapat memasang pakej: '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089">Produk <ph name="PRODUCT_ID" /> daripada vendor <ph name="VENDOR_ID" /> tidak diketahui (nombor siri <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Produk <ph name="PRODUCT_ID" /> daripada vendor <ph name="VENDOR_ID" /> tidak diketahui</translation>
+<translation id="3369521687965833290">Tidak dapat menyahpek sambungan. Untuk menyahpek sambungan dengan selamat, perlu ada laluan ke direktori profil anda yang bermula dengan huruf pemacu dan tidak mengandungi simpang, titik peletakan atau symlink. Laluan tersebut tidak wujud untuk profil anda.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (nombor siri <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> daripada vendor <ph name="VENDOR_ID" /> (nombor siri <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Tidak dapat memuatkan halaman pelancar '<ph name="PAGE" />'.</translation>
+<translation id="388442998277590542">Tidak dapat memuatkan halaman pilihan '<ph name="OPTIONS_PAGE" />'.</translation>
+<translation id="4115165561519362854">Pentadbir mesin ini memerlukan <ph name="EXTENSION_NAME" /> untuk mempunyai versi minimum <ph name="EXTENSION_VERSION" />. Ia tidak dapat didayakan sehingga ia telah dikemas kini kepada versi itu (atau lebih tinggi).</translation>
+<translation id="4233778200880751280">Tidak dapat memuatkan halaman perihal '<ph name="ABOUT_PAGE" />'.</translation>
+<translation id="4434145631756268951">{0,select, single{Pilih peranti USB}multiple{Pilih peranti USB}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Tidak dapat memasang pakej kerana proses utiliti telah ranap. Cuba mulakan semula Chrome dan cuba lagi.</translation>
+<translation id="5026754133087629784">Paparan Web: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Sambungan ini merangkumi fail utama ' <ph name="KEY_PATH" /> '. Anda mungkin tidak mahu melakukannya.</translation>
+<translation id="5627523580512561598">sambungan <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Pilih peranti HID}multiple{Pilih peranti HID}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Pentadbir mesin ini memerlukan <ph name="EXTENSION_NAME" /> dipasang. Sambungan ini tidak boleh dinyahpasang.</translation>
+<translation id="6027032947578871493">Produk <ph name="PRODUCT_ID" /> daripada <ph name="VENDOR_NAME" /> tidak diketahui (nombor siri <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> daripada vendor <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Tidak dapat menyahpek sambungan. Untuk menyahpek sambungan dengan selamat, perlu ada laluan ke direktori profil anda yang tidak mengandungi symlink. Tiada laluan tersebut wujud untuk profil anda.</translation>
+<translation id="616804573177634438">{0,select, single{Aplikasi "<ph name="APP_NAME" />" meminta akses kepada satu daripada peranti anda.}multiple{Aplikasi "<ph name="APP_NAME" />" meminta akses kepada satu atau beberapa peranti anda.}other{TIDAK DIGUNAKAN}}</translation>
+<translation id="641087317769093025">Tidak boleh menyahzip sambungan</translation>
+<translation id="657064425229075395">Tidak dapat memuatkan skrip latar belakang '<ph name="BACKGROUND_SCRIPT" />'.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> daripada <ph name="VENDOR_NAME" /> (nombor siri <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Tidak dapat mencipta direktori untuk menyahzip: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">Mimehandler: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Sambungan ini terlalu kerap memuat semula sendiri.</translation>
+<translation id="7003844668372540529">Produk <ph name="PRODUCT_ID" /> daripada <ph name="VENDOR_NAME" /> tidak diketahui</translation>
+<translation id="7217838517480956708">Pentadbir mesin ini memerlukan <ph name="EXTENSION_NAME" /> dipasang. Sambungan ini tidak boleh dialih keluar atau diubah suai.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (ID sambungan "<ph name="EXTENSION_ID" />") disekat oleh pentadbir.</translation>
+<translation id="7972881773422714442">Pilihan: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Sambungan ini gagal menamakan muat turun "<ph name="ATTEMPTED_FILENAME" />" kerana sambungan lain (<ph name="EXTENSION_NAME" />) menetapkan nama fail lain "<ph name="ACTUAL_FILENAME" />".</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> daripada <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Sambungan ini gagal memberikan bukti kelayakan kepada permintaan rangkaian kerana sambungan lain (<ph name="EXTENSION_NAME" />) memberikan bukti kelayakan yang berbeza.</translation>
+<translation id="8602184400052594090">Fail ketara hilang atau tidak dibaca.</translation>
+<translation id="8636666366616799973">Pakej adalah tidak sah. Butiran: '<ph name="ERROR_MESSAGE" />'.</translation>
+<translation id="8670869118777164560">Sambungan ini gagal mengubah hala permintaan rangkaian ke <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> kerana sambungan lain (<ph name="EXTENSION_NAME" />) mengubah halanya ke <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Penyahpadat Sambungan</translation>
+<translation id="8825366169884721447">Sambungan ini gagal mengubah suai pengatas permintaan "<ph name="HEADER_NAME" />" untuk permintaan rangkaian kerana ubah suai tersebut bercanggah dengan sambungan lain (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Sambungan ini gagal mengubah suai pengatas balasan "<ph name="HEADER_NAME" />" untuk permintaan rangkaian kerana ubah suai tersebut bercanggah dengan sambungan lain (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_nl.xtb b/chromium/extensions/strings/extensions_strings_nl.xtb
new file mode 100644
index 00000000000..e22bcdd4693
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_nl.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="nl">
+<translation id="1135328998467923690">Pakket is ongeldig: '<ph name="ERROR_CODE" />'.</translation>
+<translation id="1256619696651732561">Extension Manifest Parser</translation>
+<translation id="1445572445564823378"><ph name="PRODUCT_NAME" /> wordt door deze extensie vertraagd. Schakel de extensie uit om de prestaties van <ph name="PRODUCT_NAME" /> te verbeteren.</translation>
+<translation id="149347756975725155">Kan extensiepictogram '<ph name="ICON" />' niet laden.</translation>
+<translation id="1803557475693955505">Kan achtergrondpagina '<ph name="BACKGROUND_PAGE" />' niet laden.</translation>
+<translation id="2159915644201199628">Kan afbeelding niet decoderen: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">Lokalisatie gebruikt, maar er is geen 'default_locale' opgegeven in het manifest.</translation>
+<translation id="2753617847762399167">Illegaal pad (absoluut of relatief met '..'): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">Deze extensie heeft een netwerkverzoek niet kunnen aanpassen, omdat de aanpassing zorgde voor een conflict met een andere extensie.</translation>
+<translation id="2857834222104759979">Manifestbestand is ongeldig.</translation>
+<translation id="2988488679308982380">Kan pakket niet installeren: '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089">Onbekend product <ph name="PRODUCT_ID" /> van leverancier <ph name="VENDOR_ID" /> (serienummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Onbekend product <ph name="PRODUCT_ID" /> van leverancier <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Kan extensie niet uitpakken. Om een extensie veilig te kunnen uitpakken, moet er een pad naar je profieldirectory verwijzen dat begint met een stationletter en geen koppeling, koppelpunt of symlink bevat. Een dergelijk pad bestaat niet voor je profiel.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (serienummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> van leverancier <ph name="VENDOR_ID" /> (serienummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Kan launcherpagina '<ph name="PAGE" />' niet laden.</translation>
+<translation id="388442998277590542">Kan optiepagina '<ph name="OPTIONS_PAGE" />' niet laden.</translation>
+<translation id="4115165561519362854">De beheerder van dit apparaat vereist dat minimaal versie <ph name="EXTENSION_VERSION" /> van <ph name="EXTENSION_NAME" /> moet zijn geïnstalleerd. De extensie kan pas worden ingeschakeld nadat deze is geüpdatet naar die versie (of hoger).</translation>
+<translation id="4233778200880751280">De informatiepagina '<ph name="ABOUT_PAGE" />' kan niet worden geladen.</translation>
+<translation id="4434145631756268951">{0,select, single{Een USB-apparaat selecteren}multiple{USB-apparaten selecteren}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Kan pakket niet installeren, omdat het proces van een hulpprogramma is gecrasht. Start Chrome opnieuw en probeer het nogmaals.</translation>
+<translation id="5026754133087629784">Webweergave: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Deze extensie bevat het sleutelbestand '<ph name="KEY_PATH" />'. Dit is waarschijnlijk niet je bedoeling.</translation>
+<translation id="5627523580512561598">extensie <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Een HID-apparaat selecteren}multiple{HID-apparaten selecteren}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">De beheerder van deze machine vereist dat <ph name="EXTENSION_NAME" /> wordt geïnstalleerd. Deze kan niet worden verwijderd.</translation>
+<translation id="6027032947578871493">Onbekend product <ph name="PRODUCT_ID" /> van <ph name="VENDOR_NAME" /> (serienummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> van leverancier <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Kan extensie niet uitpakken. Om een extensie veilig te kunnen uitpakken, moet er een pad naar je profieldirectory verwijzen dat geen symlink bevat. Een dergelijk pad bestaat niet voor je profiel.</translation>
+<translation id="616804573177634438">{0,select, single{De app <ph name="APP_NAME" /> vraagt toegang tot een van je apparaten.}multiple{De app <ph name="APP_NAME" /> vraagt toegang tot een of meer van je apparaten.}other{NIET GEBRUIKT}}</translation>
+<translation id="641087317769093025">Kan extensie niet uitpakken</translation>
+<translation id="657064425229075395">Kan achtergrondscript '<ph name="BACKGROUND_SCRIPT" />' niet laden.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> van <ph name="VENDOR_NAME" /> (serienummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Kan geen directory maken voor het uitpakken van: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">Mimehandler: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Deze extensie heeft zichzelf te vaak opnieuw geladen.</translation>
+<translation id="7003844668372540529">Onbekend product <ph name="PRODUCT_ID" /> van <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">De beheerder van dit apparaat vereist dat <ph name="EXTENSION_NAME" /> is geïnstalleerd. Het kan niet worden verwijderd of gewijzigd.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (extensie-ID '<ph name="EXTENSION_ID" />') wordt geblokkeerd door de beheerder.</translation>
+<translation id="7972881773422714442">Opties: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Deze extensie kan de download niet de naam '<ph name="ATTEMPTED_FILENAME" />' geven, omdat een andere extensie (<ph name="EXTENSION_NAME" />) een andere bestandsnaam '<ph name="ACTUAL_FILENAME" />' heeft bepaald.</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> van <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Deze extensie heeft geen identificatie kunnen leveren voor een netwerkverzoek, omdat een andere extensie (<ph name="EXTENSION_NAME" />) een andere identificatie heeft geleverd.</translation>
+<translation id="8602184400052594090">Manifestbestand ontbreekt of is onleesbaar.</translation>
+<translation id="8636666366616799973">Pakket is ongeldig. Details: '<ph name="ERROR_MESSAGE" />'.</translation>
+<translation id="8670869118777164560">Deze extensie heeft een netwerkverzoek niet kunnen omleiden naar <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> omdat een andere extensie (<ph name="EXTENSION_NAME" />) het verzoek heeft omgeleid naar <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Extension Unpacker</translation>
+<translation id="8825366169884721447">Deze extensie heeft de koptekst voor het verzoek '<ph name="HEADER_NAME" />' van een netwerkverzoek niet kunnen aanpassen, omdat de aanpassing zorgde voor een conflict met een andere extensie (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Deze extensie heeft de koptekst voor de reactie '<ph name="HEADER_NAME" />' voor een netwerkverzoek niet kunnen aanpassen, omdat de aanpassing zorgde voor een conflict met een andere extensie (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_no.xtb b/chromium/extensions/strings/extensions_strings_no.xtb
new file mode 100644
index 00000000000..13270b44095
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_no.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="no">
+<translation id="1135328998467923690">Pakken er ugyldig: <ph name="ERROR_CODE" />.</translation>
+<translation id="1256619696651732561">Extension Manifest Parser</translation>
+<translation id="1445572445564823378">Denne utvidelsen gjør <ph name="PRODUCT_NAME" /> tregere. Du bør deaktivere den for å gjenopprette ytelsen til <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">Kan ikke laste inn utvidelsesikonet «<ph name="ICON" />».</translation>
+<translation id="1803557475693955505">Kan ikke laste inn bakgrunnssiden <ph name="BACKGROUND_PAGE" />.</translation>
+<translation id="2159915644201199628">Kunne ikke dekode bilde: «<ph name="IMAGE_NAME" />»</translation>
+<translation id="2350172092385603347">Lokaliseringen er brukt, men default_locale var ikke spesifisert i manifestet.</translation>
+<translation id="2753617847762399167">Ugyldig bane (absolutt eller relativ med '..'): «<ph name="IMAGE_PATH" />»</translation>
+<translation id="27822970480436970">Denne utvidelsen mislyktes i å endre en nettverksforespørsel, fordi endringen var i konflikt med en annen utvidelse.</translation>
+<translation id="2857834222104759979">Manifestfilen er ugyldig.</translation>
+<translation id="2988488679308982380">Kunne ikke installere pakken: <ph name="ERROR_CODE" /></translation>
+<translation id="3115238746683532089">Ukjent produkt, <ph name="PRODUCT_ID" />, fra leverandøren <ph name="VENDOR_ID" /> (serienummer: <ph name="SERIAL_NUMBER" /> )</translation>
+<translation id="3163201441334626963">Ukjent produkt, <ph name="PRODUCT_ID" />, fra leverandøren <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Kan ikke pakke ut utvidelse. For å kunne pakke ut en utvidelse på en sikker måte, må det finnes en bane til profilkatalogen din som begynner med en harddiskbokstav og som ikke inneholder en forbindelse, et innsettingspunkt eller en symlink. Det finnes ingen slik bane for profilen din.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (serienummer: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> fra leverandøren <ph name="VENDOR_ID" /> (serienummer: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Kunne ikke laste inn appvelgersiden «<ph name="PAGE" />».</translation>
+<translation id="388442998277590542">Kan ikke laste inn alternativsiden <ph name="OPTIONS_PAGE" />.</translation>
+<translation id="4115165561519362854">Administratoren for denne maskinen krever at <ph name="EXTENSION_NAME" /> har en minimumsversjon på <ph name="EXTENSION_VERSION" />. Den kan ikke slås på før den er oppdatert til denne versjonen (eller høyere).</translation>
+<translation id="4233778200880751280">Kunne ikke laste inn info-siden «<ph name="ABOUT_PAGE" />».</translation>
+<translation id="4434145631756268951">{0,select, single{Velg en USB-enhet}multiple{Velg USB-enheter}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Kunne ikke installere pakken fordi en verktøyprosess krasjet. Prøv å starte Chrome på nytt og prøv igjen.</translation>
+<translation id="5026754133087629784">Nettvisning: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appvisning: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Denne utvidelsen inneholder nøkkelfilen «<ph name="KEY_PATH" />». Du ønsker sannsynligvis ikke å gjøre dette.</translation>
+<translation id="5627523580512561598"><ph name="EXTENSION_NAME" />-utvidelse</translation>
+<translation id="5630931906013276297">{0,select, single{Velg en HID-enhet}multiple{Velg HID-enheter}other{UNUSED}}</translation>
+<translation id="5960890139610307736">Utvidelsesvisning (ExtensionView): <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Administratoren for denne maskinen krever at <ph name="EXTENSION_NAME" /> installeres. Denne utvidelsen kan ikke avinstalleres.</translation>
+<translation id="6027032947578871493">Ukjent produkt, <ph name="PRODUCT_ID" />, fra <ph name="VENDOR_NAME" /> (serienummer: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> fra leverandøren <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Kan ikke pakke ut utvidelse. For å kunne pakke ut en utvidelse på en sikker måte, må det finnes en bane til profilkatalogen din som ikke inneholder en symlink. Det finnes ingen slik bane for profilen din.</translation>
+<translation id="616804573177634438">{0,select, single{Appen «<ph name="APP_NAME" />» ber om tilgang til en av enhetene dine.}multiple{Appen «<ph name="APP_NAME" />» ber om tilgang til én eller flere av enhetene dine.}other{UNUSED}}</translation>
+<translation id="641087317769093025">Kunne ikke pakke ut utvidelsen</translation>
+<translation id="657064425229075395">Kunne ikke laste inn bakgrunnsskriptet «<ph name="BACKGROUND_SCRIPT" />».</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> fra <ph name="VENDOR_NAME" /> (serienummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Kunne ikke opprette katalog for utpakking: «<ph name="DIRECTORY_PATH" />»</translation>
+<translation id="677806580227005219">Mime-behandler: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Denne utvidelsen lastet seg inn på nytt for ofte.</translation>
+<translation id="7003844668372540529">Ukjent produkt, <ph name="PRODUCT_ID" />, fra <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Administratoren til denne maskinen krever at <ph name="EXTENSION_NAME" /> blir installert. Utvidelsen kan ikke fjernes eller endres.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (utvidelses-ID «<ph name="EXTENSION_ID" />») er blokkert av administratoren.</translation>
+<translation id="7972881773422714442">Alternativer: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Denne utvidelsen kunne ikke kalle nedlastingen «<ph name="ATTEMPTED_FILENAME" />» fordi en annen utvidelse (<ph name="EXTENSION_NAME" />) har angitt et annet filnavn («<ph name="ACTUAL_FILENAME" />»).</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> fra <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Denne utvidelsen kunne ikke oppgi påloggingsopplysningene til et nettverk fordi en annen utvidelse (<ph name="EXTENSION_NAME" />) oppga andre opplysninger.</translation>
+<translation id="8602184400052594090">Manifestfilen mangler eller kan ikke leses.</translation>
+<translation id="8636666366616799973">Pakken er ugyldig. Detaljer: <ph name="ERROR_MESSAGE" />.</translation>
+<translation id="8670869118777164560">Denne utvidelsen kunne ikke viderekoble en nettverksforespørsel til <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> fordi en annen utvidelse (<ph name="EXTENSION_NAME" />) viderekoblet den til <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Extension Unpacker</translation>
+<translation id="8825366169884721447">Denne utvidelsen kunne ikke endre forespørselsoverskriften «<ph name="HEADER_NAME" />» i en nettverksforespørsel fordi endringen kom i konflikt med en annen utvidelse (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Denne utvidelsen kunne ikke endre svaroverskriften «<ph name="HEADER_NAME" />» i en nettverksforespørsel fordi endringen kom i konflikt med en annen utvidelse (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_pl.xtb b/chromium/extensions/strings/extensions_strings_pl.xtb
new file mode 100644
index 00000000000..894eeee1b7b
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_pl.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="pl">
+<translation id="1135328998467923690">Pakiet jest nieprawidłowy: „<ph name="ERROR_CODE" />”.</translation>
+<translation id="1256619696651732561">Parser pliku manifestu rozszerzenia</translation>
+<translation id="1445572445564823378">To rozszerzenie spowalnia pracę <ph name="PRODUCT_NAME" />. Wyłącz je, aby przywrócić wydajność przeglądarki <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">Nie można wczytać ikony rozszerzenia „<ph name="ICON" />”.</translation>
+<translation id="1803557475693955505">Nie można wczytać strony w tle „<ph name="BACKGROUND_PAGE" />”.</translation>
+<translation id="2159915644201199628">Nie można odkodować obrazu: „<ph name="IMAGE_NAME" />”</translation>
+<translation id="2350172092385603347">Lokalizacja została użyta, ale nie określono języka default_locale w pliku manifestu.</translation>
+<translation id="2753617847762399167">Nieprawidłowa ścieżka (pełna lub względna z przedrostkiem „..”): „<ph name="IMAGE_PATH" />”</translation>
+<translation id="27822970480436970">To rozszerzenie nie mogło zmienić żądania sieciowego, ponieważ zmiana wchodziła w konflikt z innym rozszerzeniem.</translation>
+<translation id="2857834222104759979">Plik manifestu jest nieprawidłowy.</translation>
+<translation id="2988488679308982380">Nie można zainstalować pakietu: „<ph name="ERROR_CODE" />”</translation>
+<translation id="3115238746683532089">Nieznany produkt <ph name="PRODUCT_ID" />, którego producent to <ph name="VENDOR_ID" /> (numer seryjny <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Nieznany produkt <ph name="PRODUCT_ID" />, którego producent to <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Nie można rozpakować rozszerzenia. Aby bezpiecznie rozpakować rozszerzenie, musi istnieć ścieżka do katalogu profilu, która zaczyna się literą dysku i nie zawiera punktu połączenia, punktu instalacji (montowania) ani dowiązania symbolicznego. W przypadku Twojego profilu taka ścieżka nie istnieje.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (numer seryjny <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> wyprodukowany przez <ph name="VENDOR_ID" /> (numer seryjny <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Nie można wczytać strony programu uruchamiającego „<ph name="PAGE" />”.</translation>
+<translation id="388442998277590542">Nie można wczytać strony opcji „<ph name="OPTIONS_PAGE" />”.</translation>
+<translation id="4115165561519362854">Administrator tego urządzenia wymaga, by rozszerzenie <ph name="EXTENSION_NAME" /> było w wersji co najmniej <ph name="EXTENSION_VERSION" />. Jeśli rozszerzenie nie zostanie zaktualizowane do tej wersji (lub nowszej), nie zostanie włączone.</translation>
+<translation id="4233778200880751280">Nie udało się załadować strony z informacjami: „<ph name="ABOUT_PAGE" />”.</translation>
+<translation id="4434145631756268951">{0,select, single{Wybierz urządzenie USB}multiple{Wybierz urządzenia USB}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Nie udało się zainstalować pakietu, ponieważ nastąpiła awaria procesu narzędzia. Uruchom ponownie Chrome i spróbuj jeszcze raz.</translation>
+<translation id="5026754133087629784">Webview: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Tag appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">To rozszerzenie zawiera plik klucza „<ph name="KEY_PATH" />”. Prawdopodobnie nie powinno tak być.</translation>
+<translation id="5627523580512561598">rozszerzenie <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Wybierz urządzenie HID}multiple{Wybierz urządzenia HID}other{UNUSED}}</translation>
+<translation id="5960890139610307736">Tag ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Administrator tego urządzenia wymaga, by było na nim zainstalowane rozszerzenie <ph name="EXTENSION_NAME" />. Nie można go odinstalować.</translation>
+<translation id="6027032947578871493">Nieznany produkt <ph name="PRODUCT_ID" /> firmy <ph name="VENDOR_NAME" /> (numer seryjny <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> wyprodukowany przez <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Nie można rozpakować rozszerzenia. Aby bezpiecznie rozpakować rozszerzenie, musi istnieć ścieżka do katalogu profilu, która nie zawiera dowiązania symbolicznego. W przypadku Twojego profilu taka ścieżka nie istnieje.</translation>
+<translation id="616804573177634438">{0,select, single{Aplikacja „<ph name="APP_NAME" />” prosi o dostęp do jednego z Twoich urządzeń.}multiple{Aplikacja „<ph name="APP_NAME" />” prosi o dostęp do co najmniej jednego z Twoich urządzeń.}other{UNUSED}}</translation>
+<translation id="641087317769093025">Nie można rozpakować rozszerzenia</translation>
+<translation id="657064425229075395">Nie udało się wczytać skryptu działającego w tle „<ph name="BACKGROUND_SCRIPT" />”.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> firmy <ph name="VENDOR_NAME" /> (numer seryjny <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Nie można utworzyć katalogu do rozpakowania: „<ph name="DIRECTORY_PATH" />”</translation>
+<translation id="677806580227005219">Moduł obsługi MIME: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">To rozszerzenie zbyt często ładowało się ponownie.</translation>
+<translation id="7003844668372540529">Nieznany produkt <ph name="PRODUCT_ID" /> firmy <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Administrator tego komputera wymaga zainstalowanego rozszerzenia <ph name="EXTENSION_NAME" />. Nie można go usunąć ani modyfikować.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (ID rozszerzenia „<ph name="EXTENSION_ID" />”) jest zablokowane przez administratora.</translation>
+<translation id="7972881773422714442">Opcje: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Rozszerzenie nie może odczytać nazwy pobieranego pliku „<ph name="ATTEMPTED_FILENAME" />”, ponieważ inne rozszerzenie (<ph name="EXTENSION_NAME" />) odczytało odmienną jego nazwę „<ph name="ACTUAL_FILENAME" />”.</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> od <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Rozszerzenie nie podało danych logowania do żądania sieciowego, bo inne rozszerzenie (<ph name="EXTENSION_NAME" />) podało inne dane.</translation>
+<translation id="8602184400052594090">Brak pliku manifestu lub nie można go odczytać.</translation>
+<translation id="8636666366616799973">Pakiet jest nieprawidłowy. Szczegółowe informacje: „<ph name="ERROR_MESSAGE" />”.</translation>
+<translation id="8670869118777164560">Rozszerzenie nie przekierowało żądania sieciowego do <ph name="ATTEMPTED_REDIRECT_DESTINATION" />, bo inne rozszerzenie (<ph name="EXTENSION_NAME" />) przekierowało je do <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Program do rozpakowywania rozszerzeń</translation>
+<translation id="8825366169884721447">Rozszerzenie nie zmodyfikowało nagłówka żądania „<ph name="HEADER_NAME" />” żądania sieciowego, bo zmiana powodowałaby konflikt z innym rozszerzeniem (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Rozszerzenie nie zmodyfikowało nagłówka odpowiedzi „<ph name="HEADER_NAME" />” żądania sieciowego, bo zmiana powodowałaby konflikt z innym rozszerzeniem (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_pt-BR.xtb b/chromium/extensions/strings/extensions_strings_pt-BR.xtb
new file mode 100644
index 00000000000..d3c4b0cb9f4
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_pt-BR.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="pt-BR">
+<translation id="1135328998467923690">O pacote é inválido: "<ph name="ERROR_CODE" />".</translation>
+<translation id="1256619696651732561">Analisador de manifesto de extensão</translation>
+<translation id="1445572445564823378">Esta extensão está deixando <ph name="PRODUCT_NAME" /> mais lento. Você deve desativá-lo para restaurar o desempenho de <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">Não foi possível carregar o ícone de extensão "<ph name="ICON" />".</translation>
+<translation id="1803557475693955505">Não foi possível carregar a página de fundo "<ph name="BACKGROUND_PAGE" />".</translation>
+<translation id="2159915644201199628">Não foi possível decodificar a imagem: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">Localização utilizada, mas default_locale não foi especificada no manifesto.</translation>
+<translation id="2753617847762399167">Caminho ilegal (absoluto ou relativo com '..'): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">Esta extensão não conseguiu modificar uma solicitação de rede pois a modificação entrou em conflito com outra extensão.</translation>
+<translation id="2857834222104759979">O arquivo de manifesto é inválido.</translation>
+<translation id="2988488679308982380">Não foi possível instalar o pacote: "<ph name="ERROR_CODE" />"</translation>
+<translation id="3115238746683532089">Produto <ph name="PRODUCT_ID" /> desconhecido do fornecedor <ph name="VENDOR_ID" /> (número de série <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Produto <ph name="PRODUCT_ID" /> desconhecido do fornecedor <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Não é possível abrir a extensão. Para abrir uma extensão com segurança, deve haver um caminho para o diretório de seu perfil que comece com uma letra de drive e não contenha uma junção, um mount point ou um symlink. Não existe um caminho assim para seu perfil.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (número de série <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> do fornecedor <ph name="VENDOR_ID" /> (número de série <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Não foi possível carregar a página inicial '<ph name="PAGE" />'.</translation>
+<translation id="388442998277590542">Não foi possível carregar a página de opções "<ph name="OPTIONS_PAGE" />".</translation>
+<translation id="4115165561519362854">O administrador desta máquina requer que <ph name="EXTENSION_NAME" /> tenha, no mínimo, a versão <ph name="EXTENSION_VERSION" /> . Ele não pode ser ativado até que tenha sido atualizado para essa versão (ou superior).</translation>
+<translation id="4233778200880751280">Não foi possível carregar a página "Sobre" '<ph name="ABOUT_PAGE" />'.</translation>
+<translation id="4434145631756268951">{0,select, single{Selecione um dispositivo USB}multiple{Selecione dispositivos USB}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Não foi possível instalar o pacote devido a uma falha no processo do utilitário. Reinicie o Google Chrome e tente novamente.</translation>
+<translation id="5026754133087629784">Webview: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Visualização do app: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Esta extensão inclui o arquivo de chave '<ph name="KEY_PATH" />'. Você provavelmente não quer fazer isso.</translation>
+<translation id="5627523580512561598">extensão <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Selecione um dispositivo HID}multiple{Selecione dispositivos HID}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">O administrador desta máquina requer a instalação da extensão <ph name="EXTENSION_NAME" />. Ela não pode ser desinstalada.</translation>
+<translation id="6027032947578871493">Produto <ph name="PRODUCT_ID" /> desconhecido de <ph name="VENDOR_NAME" /> (número de série <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> do fornecedor <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Não é possível abrir a extensão. Para abrir uma extensão com segurança, deve haver um caminho para o diretório de seu perfil que não contenha um symlink. Não existe um caminho assim para seu perfil.</translation>
+<translation id="616804573177634438">{0,select, single{O aplicativo "<ph name="APP_NAME" />" está solicitando acesso a um dos seus dispositivos.}multiple{O aplicativo "<ph name="APP_NAME" />" está solicitando acesso a um ou mais dos seus dispositivos}other{UNUSED}}</translation>
+<translation id="641087317769093025">Não foi possível descompactar a extensão</translation>
+<translation id="657064425229075395">Não foi possível carregar o script de plano de fundo "<ph name="BACKGROUND_SCRIPT" />".</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> de <ph name="VENDOR_NAME" /> (número de série <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Não foi possível criar um diretório para descompactação: ​​'<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">Gerenciador Mime: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Esta extensão foi recarregada automaticamente com muita frequência.</translation>
+<translation id="7003844668372540529">Produto <ph name="PRODUCT_ID" /> desconhecido de <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">O administrador desta máquina requer a instalação de <ph name="EXTENSION_NAME" />. Não é possível remover ou modificá-lo.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (ID de extensão "<ph name="EXTENSION_ID" />") está bloqueada pelo administrador.</translation>
+<translation id="7972881773422714442">Opções: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Falha ao nomear o download "<ph name="ATTEMPTED_FILENAME" />" com esta extensão, porque outra extensão (<ph name="EXTENSION_NAME" />) determinou um nome de arquivo diferente: "<ph name="ACTUAL_FILENAME" />".</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> de <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Esta extensão falhou ao fornecer credenciais a uma solicitação de rede porque outra extensão (<ph name="EXTENSION_NAME" />) forneceu credenciais diferentes.</translation>
+<translation id="8602184400052594090">O arquivo de manifesto está faltando ou não pode ser lido.</translation>
+<translation id="8636666366616799973">O pacote é inválido. Detalhes "<ph name="ERROR_MESSAGE" />".</translation>
+<translation id="8670869118777164560">Esta extensão falhou ao redirecionar uma solicitação da rede para <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> porque outra extensão (<ph name="EXTENSION_NAME" />) redirecionou a solicitação para <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Descompactador de extensão</translation>
+<translation id="8825366169884721447">Esta extensão falhou ao modificar o cabeçalho da solicitação de rede "<ph name="HEADER_NAME" />" porque a modificação entrou em conflito com outra extensão (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Esta extensão falhou ao modificar o cabeçalho de resposta "<ph name="HEADER_NAME" />" de uma solicitação de rede porque a modificação entrou em conflito com outra extensão (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_pt-PT.xtb b/chromium/extensions/strings/extensions_strings_pt-PT.xtb
new file mode 100644
index 00000000000..bd11e66d019
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_pt-PT.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="pt-PT">
+<translation id="1135328998467923690">O pacote é inválido: "<ph name="ERROR_CODE" />".</translation>
+<translation id="1256619696651732561">Analisador de manifestos de extensão</translation>
+<translation id="1445572445564823378">Esta extensão está a tornar o <ph name="PRODUCT_NAME" /> lento. Deve desativá-la para restaurar o desempenho do <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">Não foi possível carregar o ícone de extensão "<ph name="ICON" />".</translation>
+<translation id="1803557475693955505">Não foi possível carregar a página de fundo "<ph name="BACKGROUND_PAGE" />".</translation>
+<translation id="2159915644201199628">Não foi possível descodificar a imagem: "<ph name="IMAGE_NAME" />"</translation>
+<translation id="2350172092385603347">Localização utilizada, mas não foi especificado default_locale no manifesto.</translation>
+<translation id="2753617847762399167">Caminho ilegal (absoluto ou relativo com ".."): "<ph name="IMAGE_PATH" />"</translation>
+<translation id="27822970480436970">Esta extensão não conseguiu modificar um pedido de rede, porque a modificação entrou em conflito com outra extensão.</translation>
+<translation id="2857834222104759979">O ficheiro de manifesto é inválido.</translation>
+<translation id="2988488679308982380">Não foi possível instalar o pacote: "<ph name="ERROR_CODE" />"</translation>
+<translation id="3115238746683532089">Produto desconhecido <ph name="PRODUCT_ID" /> do fornecedor <ph name="VENDOR_ID" /> (número de série <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Produto desconhecido <ph name="PRODUCT_ID" /> do fornecedor <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Não é possível descompactar a extensão. Para descompactar uma extensão em segurança, é necessário existir um caminho para o seu directório de perfil começado por uma letra de unidade e sem uma junção, ponto de montagem ou ligação simbólica. Não existe um caminho deste tipo para o seu perfil.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (número de série <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> do fornecedor <ph name="VENDOR_ID" /> (número de série <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Não foi possível carregar a página de iniciador "<ph name="PAGE" />".</translation>
+<translation id="388442998277590542">Não foi possível carregar a página de opções "<ph name="OPTIONS_PAGE" />".</translation>
+<translation id="4115165561519362854">O administrador deste computador requer que <ph name="EXTENSION_NAME" /> tenha uma versão mínima de <ph name="EXTENSION_VERSION" />. Não pode ser atualizado enquanto não for atualizado para essa versão (ou posterior).</translation>
+<translation id="4233778200880751280">Não foi possível carregar a página acerca de "<ph name="ABOUT_PAGE" />".</translation>
+<translation id="4434145631756268951">{0,select, single{Selecionar um dispositivo USB}multiple{Selecionar dispositivos USB}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Não foi possível instalar o pacote, porque o processo de um utilitário falhou. Experimente reiniciar o Chrome e tentar novamente.</translation>
+<translation id="5026754133087629784">Vista Web: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Vista de aplicação: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Esta extensão inclui o ficheiro-chave "<ph name="KEY_PATH" />". Provavelmente não quer fazer isso.</translation>
+<translation id="5627523580512561598">extensão <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Selecionar um dispositivo HID}multiple{Selecionar dispositivos HID}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">O administrador deste computador requer que a extensão <ph name="EXTENSION_NAME" /> esteja instalada. Não é possível desinstalá-la.</translation>
+<translation id="6027032947578871493">Produto desconhecido <ph name="PRODUCT_ID" /> de <ph name="VENDOR_NAME" /> (número de série <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> do fornecedor <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Não é possível descompactar a extensão. Para descompactar uma extensão em segurança, é necessário existir um caminho para o seu directório de perfil sem uma ligação simbólica. Não existe um caminho deste tipo para o seu perfil.</translation>
+<translation id="616804573177634438">{0,select, single{A aplicação "<ph name="APP_NAME" />" está a solicitar acesso a um dos seus dispositivos.}multiple{A aplicação "<ph name="APP_NAME" />" está a solicitar acesso a um ou mais dos seus dispositivos.}other{NÃO UTILIZADO}}</translation>
+<translation id="641087317769093025">Não foi possível descomprimir a extensão</translation>
+<translation id="657064425229075395">Não foi possível carregar o script de segundo plano "<ph name="BACKGROUND_SCRIPT" />".</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> de <ph name="VENDOR_NAME" /> (número de série <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Não foi possível criar um diretório para descomprimir: "<ph name="DIRECTORY_PATH" />"</translation>
+<translation id="677806580227005219">Controlador MIME: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Esta extensão foi recarregada demasiado frequentemente.</translation>
+<translation id="7003844668372540529">Produto desconhecido <ph name="PRODUCT_ID" /> de <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">O administrador desta máquina exige que o <ph name="EXTENSION_NAME" /> seja instalado. Este não pode ser removido ou modificado.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (ID da extensão "<ph name="EXTENSION_ID" />") foi bloqueado pelo administrador.</translation>
+<translation id="7972881773422714442">Opções: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Esta extensão não conseguiu atribuir o nome "<ph name="ATTEMPTED_FILENAME" />" à transferência, porque outra extensão (<ph name="EXTENSION_NAME" />) determinou um nome de ficheiro diferente: "<ph name="ACTUAL_FILENAME" />".</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> de <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Falha desta extensão ao fornecer credenciais a um pedido de rede, porque outra extensão (<ph name="EXTENSION_NAME" />) forneceu credenciais diferentes.</translation>
+<translation id="8602184400052594090">O ficheiro de manifesto está em falta ou é ilegível.</translation>
+<translation id="8636666366616799973">O pacote é inválido. Detalhes: "<ph name="ERROR_MESSAGE" />".</translation>
+<translation id="8670869118777164560">Falha desta extensão ao redirecionar um pedido de rede para <ph name="ATTEMPTED_REDIRECT_DESTINATION" />, porque outra extensão (<ph name="EXTENSION_NAME" />) o redirecionou para <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Descompactador de extensões</translation>
+<translation id="8825366169884721447">Falha desta extensão ao modificar o cabeçalho do pedido "<ph name="HEADER_NAME" />" de um pedido de rede, porque a modificação entrou em conflito com outra extensão (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Falha desta extensão ao modificar o cabeçalho da resposta "<ph name="HEADER_NAME" />" de um pedido de rede, porque a modificação entrou em conflito com outra extensão (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_ro.xtb b/chromium/extensions/strings/extensions_strings_ro.xtb
new file mode 100644
index 00000000000..2c6008aa558
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_ro.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ro">
+<translation id="1135328998467923690">Pachetul nu este valid: „<ph name="ERROR_CODE" />”.</translation>
+<translation id="1256619696651732561">Analizor al manifestului extensiei</translation>
+<translation id="1445572445564823378">Această extensie încetinește <ph name="PRODUCT_NAME" />. Vă recomandăm să o dezactivați pentru a restabili performanțele produsului <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">Nu se poate încărca pictograma „<ph name="ICON" />” a extensiei.</translation>
+<translation id="1803557475693955505">Nu se poate încărca pagina de fundal „<ph name="BACKGROUND_PAGE" />”.</translation>
+<translation id="2159915644201199628">Imaginea nu a putut fi decodificată: „<ph name="IMAGE_NAME" />”</translation>
+<translation id="2350172092385603347">A fost utilizată localizarea, dar nu s-a specificat default_locale în manifest.</translation>
+<translation id="2753617847762399167">Cale nevalidă (absolută sau relativă cu „..”): „<ph name="IMAGE_PATH" />”</translation>
+<translation id="27822970480436970">Această extensie nu a reușit să modifice o solicitare în rețea, deoarece modificarea intră în conflict cu o altă extensie.</translation>
+<translation id="2857834222104759979">Fișierul manifest nu este valid.</translation>
+<translation id="2988488679308982380">Nu s-a putut instala pachetul: „<ph name="ERROR_CODE" />”</translation>
+<translation id="3115238746683532089">Produs necunoscut <ph name="PRODUCT_ID" /> de la furnizorul <ph name="VENDOR_ID" /> (numărul de serie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Produs necunoscut <ph name="PRODUCT_ID" /> de la furnizorul <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Nu se poate despacheta extensia. Pentru a despacheta în siguranță o extensie, trebuie să existe o cale către directorul de profil, care începe cu o literă de unitate și nu conține o joncțiune, un punct de montare sau un link simbolic. Nu există o astfel de cale pentru profilul tău.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (numărul de serie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> de la furnizorul <ph name="VENDOR_ID" /> (numărul de serie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Pagina lansatorului „<ph name="PAGE" />” nu poate fi încărcată.</translation>
+<translation id="388442998277590542">Nu se poate încărca pagina cu opțiuni „<ph name="OPTIONS_PAGE" />”.</translation>
+<translation id="4115165561519362854">Administratorul acestui computer solicită ca versiunea minimă pentru <ph name="EXTENSION_NAME" /> să fie <ph name="EXTENSION_VERSION" />. Extensia poate fi activată doar după actualizarea la acea versiune (sau una superioară).</translation>
+<translation id="4233778200880751280">Pagina informativă „<ph name="ABOUT_PAGE" />” nu s-a putut încărca.</translation>
+<translation id="4434145631756268951">{0,select, single{Selectează un dispozitiv USB}multiple{Selectează dispozitive USB}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Pachetul nu a fost instalat, deoarece un proces al unui program utilitar s-a blocat. Încearcă să repornești Chrome și încearcă din nou.</translation>
+<translation id="5026754133087629784">Afișare web: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Eticheta appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Această extensie include fișierul cheie „<ph name="KEY_PATH" />”. Probabil că nu doriți să faceți asta.</translation>
+<translation id="5627523580512561598">extensie <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Selectează un dispozitiv HID}multiple{Selectează dispozitive HID}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Administratorul acestui computer solicită ca <ph name="EXTENSION_NAME" /> să fie instalată. Aceasta nu poate fi dezinstalată.</translation>
+<translation id="6027032947578871493">Produs necunoscut <ph name="PRODUCT_ID" /> de la <ph name="VENDOR_NAME" /> (numărul de serie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> de la furnizorul <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Extensia nu poate fi dezarhivată. Pentru a dezarhiva în siguranță o extensie, trebuie să existe o cale către directorul tău de profiluri care să nu conțină un link simbolic. Pentru profilul tău nu există nicio astfel de cale.</translation>
+<translation id="616804573177634438">{0,select, single{Aplicația „<ph name="APP_NAME" />” solicită acces la unul dintre dispozitivele tale}multiple{Aplicația „<ph name="APP_NAME" />” solicită acces la unul sau mai multe dintre dispozitivele tale}other{UNUSED}}</translation>
+<translation id="641087317769093025">Extensia nu a putut fi dezarhivată</translation>
+<translation id="657064425229075395">Scriptul de fundal „<ph name="BACKGROUND_SCRIPT" />” nu a putut fi încărcat.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> de la <ph name="VENDOR_NAME" /> (număr de serie <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Nu s-a putut crea directorul pentru dezarhivare: „<ph name="DIRECTORY_PATH" />”</translation>
+<translation id="677806580227005219">Handler MIME: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Această extensie s-a reîncărcat automat prea frecvent.</translation>
+<translation id="7003844668372540529">Produs necunoscut <ph name="PRODUCT_ID" /> de la <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Administratorul acestui dispozitiv solicită instalarea extensiei <ph name="EXTENSION_NAME" />. Aceasta nu poate fi eliminată sau modificată.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (cu codul de extensie „<ph name="EXTENSION_ID" />”) este blocată de administrator.</translation>
+<translation id="7972881773422714442">Opțiuni: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Această extensie nu a reușit să denumească fișierul descărcat „<ph name="ATTEMPTED_FILENAME" />”, deoarece altă extensie (<ph name="EXTENSION_NAME" />) a stabilit alt nume de fișier, „<ph name="ACTUAL_FILENAME" />”.</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> de la <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Această extensie nu a putut furniza datele de conectare pentru o solicitare din rețea, deoarece o altă extensie (<ph name="EXTENSION_NAME" />) a furnizat alte date de conectare.</translation>
+<translation id="8602184400052594090">Fișierul manifest lipsește sau nu poate fi citit.</translation>
+<translation id="8636666366616799973">Pachetul nu este valid. Detalii: „<ph name="ERROR_MESSAGE" />”.</translation>
+<translation id="8670869118777164560">Această extensie nu a putut redirecționa o solicitare din rețea către <ph name="ATTEMPTED_REDIRECT_DESTINATION" />, deoarece o altă extensie (<ph name="EXTENSION_NAME" />) a redirecționat-o către <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Instrument de despachetare a extensiilor</translation>
+<translation id="8825366169884721447">Această extensie nu a putut modifica antetul de solicitare „<ph name="HEADER_NAME" />” al unei solicitări din rețea, deoarece modificarea era în conflict cu o altă extensie (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Această extensie nu a putut modifica antetul de răspuns „<ph name="HEADER_NAME" />” al unei solicitări din rețea, deoarece modificarea era în conflict cu o altă extensie (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_ru.xtb b/chromium/extensions/strings/extensions_strings_ru.xtb
new file mode 100644
index 00000000000..311814394bd
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_ru.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ru">
+<translation id="1135328998467923690">Пакет недействителен: "<ph name="ERROR_CODE" />".</translation>
+<translation id="1256619696651732561">Синтаксический анализатор манифестов расширений</translation>
+<translation id="1445572445564823378">Это расширение замедляет работу <ph name="PRODUCT_NAME" />. Чтобы восстановить нормальную работу <ph name="PRODUCT_NAME" />, отключите его.</translation>
+<translation id="149347756975725155">Не удается загрузить значок расширения "<ph name="ICON" />".</translation>
+<translation id="1803557475693955505">Не удалось загрузить страницу фона "<ph name="BACKGROUND_PAGE" />".</translation>
+<translation id="2159915644201199628">Не удалось декодировать изображение: <ph name="IMAGE_NAME" /></translation>
+<translation id="2350172092385603347">Локализация используется, однако в манифесте не указан атрибут default_locale.</translation>
+<translation id="2753617847762399167">Недопустимый путь (абсолютный или относительный с элементом ".."): <ph name="IMAGE_PATH" /></translation>
+<translation id="27822970480436970">Этому расширению не удалось изменить сетевой запрос, так как при этом возник конфликт с другим расширением.</translation>
+<translation id="2857834222104759979">Недопустимый файл манифеста.</translation>
+<translation id="2988488679308982380">Не удается установить пакет: "<ph name="ERROR_CODE" />"</translation>
+<translation id="3115238746683532089">Неизвестный продукт <ph name="PRODUCT_ID" /> от поставщика <ph name="VENDOR_ID" /> (серийный номер <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Неизвестный продукт <ph name="PRODUCT_ID" /> от поставщика <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Не удается распаковать расширение. Для безопасной распаковки расширения необходим путь к каталогу вашего профиля, начинающийся с буквы диска. Он не должен содержать параметр слияния папок, точку монтирования или символьную ссылку. Такого пути для вашего профиля не существует.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (серийный номер <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> от поставщика <ph name="VENDOR_ID" /> (серийный номер <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Не удалось загрузить страницу запуска <ph name="PAGE" />.</translation>
+<translation id="388442998277590542">Не удалось загрузить страницу параметров <ph name="OPTIONS_PAGE" />.</translation>
+<translation id="4115165561519362854">Администратор установил, что для запуска приложения "<ph name="EXTENSION_NAME" />" необходима версия <ph name="EXTENSION_VERSION" /> или выше. Обновите приложение.</translation>
+<translation id="4233778200880751280">Не удалось загрузить страницу <ph name="ABOUT_PAGE" />.</translation>
+<translation id="4434145631756268951">{0,select, single{Выберите USB-устройство}multiple{Выберите USB-устройства}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Не удалось установить пакет из-за сбоя. Перезагрузите браузер и повторите попытку.</translation>
+<translation id="5026754133087629784">WebView: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Это расширение содержит файл ключа <ph name="KEY_PATH" />. Вероятно, это неприемлемо.</translation>
+<translation id="5627523580512561598">расширение <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Выберите HID-устройство}multiple{Выберите HID-устройства}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Удаление расширения "<ph name="EXTENSION_NAME" />" на этом компьютере запрещено администратором</translation>
+<translation id="6027032947578871493">Неизвестный продукт <ph name="PRODUCT_ID" /> от <ph name="VENDOR_NAME" /> (серийный номер <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> от поставщика <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Не удается распаковать расширение. Для безопасной распаковки расширения необходим путь к каталогу вашего профиля, не содержащий символьную ссылку. Такого пути для вашего профиля не существует.</translation>
+<translation id="616804573177634438">{0,select, single{Приложение "<ph name="APP_NAME" />" запрашивает доступ к устройству.}multiple{Приложение "<ph name="APP_NAME" />" запрашивает доступ к одному или нескольким устройствам.}other{UNUSED}}</translation>
+<translation id="641087317769093025">Не удалось распаковать расширение</translation>
+<translation id="657064425229075395">Не удалось загрузить фоновый скрипт <ph name="BACKGROUND_SCRIPT" />.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" />, поставщик: <ph name="VENDOR_NAME" /> (серийный номер: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Не удалось создать каталог для распаковки: <ph name="DIRECTORY_PATH" /></translation>
+<translation id="677806580227005219">Mimehandler: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Это расширение слишком часто перезагружалось.</translation>
+<translation id="7003844668372540529">Неизвестный продукт <ph name="PRODUCT_ID" /> от <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Удаление и изменение расширения <ph name="EXTENSION_NAME" /> на этом компьютере запрещено администратором.</translation>
+<translation id="7809034755304591547">Расширение <ph name="EXTENSION_NAME" /> (идентификатор: <ph name="EXTENSION_ID" />) заблокировано администратором.</translation>
+<translation id="7972881773422714442">Options: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Данному расширению не удалось присвоить скачанному файлу имя "<ph name="ATTEMPTED_FILENAME" />", поскольку другое расширение (<ph name="EXTENSION_NAME" />) уже присвоило ему другое имя: "<ph name="ACTUAL_FILENAME" />".</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> от <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Это расширение не может предоставить учетные данные для сетевого запроса, поскольку другое расширение (<ph name="EXTENSION_NAME" />) передало иные учетные данные.</translation>
+<translation id="8602184400052594090">Файл манифеста отсутствует или недоступен для чтения.</translation>
+<translation id="8636666366616799973">Недопустимый пакет. Подробные сведения: "<ph name="ERROR_MESSAGE" />".</translation>
+<translation id="8670869118777164560">Расширение не может перенаправить сетевой запрос на <ph name="ATTEMPTED_REDIRECT_DESTINATION" />, поскольку другое расширение (<ph name="EXTENSION_NAME" />) перенаправило его на <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Распаковщик расширений</translation>
+<translation id="8825366169884721447">Это расширение не может изменить заголовок запроса (<ph name="HEADER_NAME" />) из-за конфликта с другим расширением (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Это расширение не может изменить заголовок ответа (<ph name="HEADER_NAME" />) из-за конфликта с другим расширением (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_sk.xtb b/chromium/extensions/strings/extensions_strings_sk.xtb
new file mode 100644
index 00000000000..c86dda062fd
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_sk.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sk">
+<translation id="1135328998467923690">Balík je neplatný: „<ph name="ERROR_CODE" />“</translation>
+<translation id="1256619696651732561">Extension Manifest Parser</translation>
+<translation id="1445572445564823378">Toto rozšírenie spomaľuje prehliadač <ph name="PRODUCT_NAME" />. Ak chcete obnoviť výkonnosť prehliadača <ph name="PRODUCT_NAME" />, zakážte ho.</translation>
+<translation id="149347756975725155">Nepodarilo sa načítať ikonu rozšírenia „<ph name="ICON" />“.</translation>
+<translation id="1803557475693955505">Nepodarilo sa načítať stránku na pozadí „<ph name="BACKGROUND_PAGE" />“.</translation>
+<translation id="2159915644201199628">Nepodarilo sa dekódovať obrázok: <ph name="IMAGE_NAME" /></translation>
+<translation id="2350172092385603347">Lokalizácia sa použila, parameter default_locale však nebol v manifeste určený.</translation>
+<translation id="2753617847762399167">Neplatná cesta (absolútna alebo relatívna s '..'): <ph name="IMAGE_PATH" /></translation>
+<translation id="27822970480436970">Tomuto rozšíreniu sa nepodarilo upraviť žiadosť siete, pretože úprava bola v rozpore s ďalším rozšírením.</translation>
+<translation id="2857834222104759979">Súbor manifestu je neplatný.</translation>
+<translation id="2988488679308982380">Nepodarilo sa nainštalovať balík: <ph name="ERROR_CODE" /></translation>
+<translation id="3115238746683532089">Neznámy produkt <ph name="PRODUCT_ID" /> od dodávateľa <ph name="VENDOR_ID" /> (sériové číslo: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Neznámy produkt <ph name="PRODUCT_ID" /> od dodávateľa <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Rozšírenie sa nepodarilo rozbaliť. Aby bolo rozbalenie rozšírenia bezpečné, musíte zadať cestu k profilovému adresáru, ktorá začína písmenom jednotky a neobsahuje žiadny styčný bod, bod prepojenia ani odkaz symlink. Pre váš profil nie je k dispozícii žiadna takáto cesta.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (sériové číslo: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> od dodávateľa <ph name="VENDOR_ID" /> (sériové číslo: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Nepodarilo sa načítať stránku spúšťača <ph name="PAGE" />.</translation>
+<translation id="388442998277590542">Nepodarilo sa načítať stránku s možnosťami „<ph name="OPTIONS_PAGE" />“.</translation>
+<translation id="4115165561519362854">Správca tohto zariadenia vyžaduje, aby ste používali rozšírenie <ph name="EXTENSION_NAME" /> aspoň vo verzii <ph name="EXTENSION_VERSION" />. Kým nebude aktualizované na túto alebo vyššiu verziu, nedá sa povoliť.</translation>
+<translation id="4233778200880751280">Informačnú stránku <ph name="ABOUT_PAGE" /> sa nepodarilo načítať.</translation>
+<translation id="4434145631756268951">{0,select, single{Výber zariadenia USB}multiple{Výber zariadení USB}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Balík sa nepodarilo nainštalovať z dôvodu zlyhania procesu nástroja. Reštartujte prehliadač Chrome a skúste to znova.</translation>
+<translation id="5026754133087629784">Webview: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Značka appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Toto rozšírenie obsahuje súbor kľúča <ph name="KEY_PATH" />. Pravdepodobne to nechcete urobiť.</translation>
+<translation id="5627523580512561598">rozšírenie <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Výber zariadenia HID}multiple{Výber zariadení HID}other{UNUSED}}</translation>
+<translation id="5960890139610307736">Zobrazenie rozšírenia: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Správca tohto počítača vyžaduje, aby bolo nainštalované rozšírenie <ph name="EXTENSION_NAME" />. Nedá sa odinštalovať.</translation>
+<translation id="6027032947578871493">Neznámy produkt <ph name="PRODUCT_ID" /> od dodávateľa <ph name="VENDOR_NAME" /> (sériové číslo: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> od dodávateľa <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Rozšírenie sa nepodarilo rozbaliť. Aby bolo rozbalenie rozšírenia bezpečné, musíte zadať cestu k profilovému adresáru, ktorá neobsahuje žiadny odkaz symlink. Pre váš profil nie je k dispozícii žiadna takáto cesta.</translation>
+<translation id="616804573177634438">{0,select, single{Aplikácia <ph name="APP_NAME" /> žiada o prístup k jednému z vašich zariadení.}multiple{Aplikácia <ph name="APP_NAME" /> žiada o prístup k jednému alebo viacerým vašim zariadeniam.}other{UNUSED}}</translation>
+<translation id="641087317769093025">Rozšírenie sa nepodarilo rozbaliť</translation>
+<translation id="657064425229075395">Nepodarilo sa načítať skript na pozadí „<ph name="BACKGROUND_SCRIPT" />“.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> od dodávateľa <ph name="VENDOR_NAME" /> (sériové číslo <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Nepodarilo sa vytvoriť adresár na rozbalenie: <ph name="DIRECTORY_PATH" /></translation>
+<translation id="677806580227005219">Obslužný nástroj štandardu MIME: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Toto rozšírenie sa obnovovalo príliš často.</translation>
+<translation id="7003844668372540529">Neznámy produkt <ph name="PRODUCT_ID" /> od dodávateľa <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Správca tohto zariadenia vyžaduje inštaláciu rozšírenia <ph name="EXTENSION_NAME" />. Nemôže byť odstránené alebo upravené.</translation>
+<translation id="7809034755304591547">Rozšírenie <ph name="EXTENSION_NAME" /> (ID rozšírenia „<ph name="EXTENSION_ID" />“) bolo správcom zablokované.</translation>
+<translation id="7972881773422714442">Možnosti: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Tomuto rozšíreniu sa nepodarilo pomenovať stiahnutý súbor s názvom „<ph name="ATTEMPTED_FILENAME" />„, pretože ďalšie rozšírenie (<ph name="EXTENSION_NAME" />) určilo odlišný názov súboru „<ph name="ACTUAL_FILENAME" />“.</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> od dodávateľa <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Tomuto rozšíreniu sa nepodarilo poskytnúť poverenia žiadosti o sieť, pretože iné rozšírenie (<ph name="EXTENSION_NAME" />) poskytlo odlišné poverenia.</translation>
+<translation id="8602184400052594090">Súbor manifestu chýba alebo je nečitateľný.</translation>
+<translation id="8636666366616799973">Package is invalid. Details: '<ph name="ERROR_MESSAGE" />'.</translation>
+<translation id="8670869118777164560">Tomuto rozšíreniu sa nepodarilo presmerovať žiadosť o siete na stránky <ph name="ATTEMPTED_REDIRECT_DESTINATION" />, pretože ju iné rozšírenie (<ph name="EXTENSION_NAME" />) presmerovalo na stránky <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Extension Unpacker</translation>
+<translation id="8825366169884721447">Tomuto rozšíreniu sa nepodarilo upraviť hlavičku <ph name="HEADER_NAME" /> žiadosti o sieť, pretože táto úprava kolidovala s iným rozšírením (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Tomuto rozšíreniu sa nepodarilo upraviť hlavičku odpovede <ph name="HEADER_NAME" /> žiadosti o sieť, pretože táto úprava kolidovala s iným rozšírením (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_sl.xtb b/chromium/extensions/strings/extensions_strings_sl.xtb
new file mode 100644
index 00000000000..1bd58136e32
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_sl.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sl">
+<translation id="1135328998467923690">Paket je neveljaven: »<ph name="ERROR_CODE" />«.</translation>
+<translation id="1256619696651732561">Razčlenjevalnik manifesta razširitve</translation>
+<translation id="1445572445564823378">Ta razširitev upočasnjuje <ph name="PRODUCT_NAME" />. Onemogočite jo, če želite obnoviti delovanje storitve <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">Ikone razširitve »<ph name="ICON" />« ni bilo mogoče naložiti.</translation>
+<translation id="1803557475693955505">Strani za ozadje »<ph name="BACKGROUND_PAGE" />« ni bilo mogoče naložiti.</translation>
+<translation id="2159915644201199628">Slike ni bilo mogoče dekodirati: »<ph name="IMAGE_NAME" />«</translation>
+<translation id="2350172092385603347">Uporabljena je bila lokalizacija, vendar v manifestu ni bil naveden parameter default_locale.</translation>
+<translation id="2753617847762399167">Neveljavna pot (absolutna ali v povezavi z »..«): »<ph name="IMAGE_PATH" />«</translation>
+<translation id="27822970480436970">Tej razširitvi ni uspelo spremeniti omrežne zahteve, ker je bila sprememba v sporu z drugo razširitvijo.</translation>
+<translation id="2857834222104759979">Datoteka manifesta ni veljavna.</translation>
+<translation id="2988488679308982380">Paketa »<ph name="ERROR_CODE" />« ni bilo mogoče namestiti</translation>
+<translation id="3115238746683532089">Neznan izdelek <ph name="PRODUCT_ID" /> dobavitelja <ph name="VENDOR_ID" /> (serijska številka <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Neznan izdelek <ph name="PRODUCT_ID" /> dobavitelja <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Razširitve ne morete odpakirati. Če želite varno odpakirati razširitev, mora biti navedena pot do imenika vašega profila, ki se začne s črko pogona in ne vsebuje spoja, točke vpenjanja ali simbolne povezave. Za vaš profil ni takšne poti.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (serijska številka <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> dobavitelja <ph name="VENDOR_ID" /> (serijska številka <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Strani zaganjalnika »<ph name="PAGE" />« ni bilo mogoče naložiti.</translation>
+<translation id="388442998277590542">Strani z možnostmi »<ph name="OPTIONS_PAGE" />« ni bilo mogoče naložiti.</translation>
+<translation id="4115165561519362854">Skrbnik te naprave zahteva, da je <ph name="EXTENSION_NAME" /> različice najmanj <ph name="EXTENSION_VERSION" />. Ni je mogoče omogočiti, dokler ni posodobljena na to (ali novejšo) različico.</translation>
+<translation id="4233778200880751280">Ni bilo mogoče naložiti Vizitke »<ph name="ABOUT_PAGE" />«.</translation>
+<translation id="4434145631756268951">{0,select, single{Izbira naprave USB}multiple{Izbira naprav USB}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Paketa ni bilo mogoče namestiti, ker se je pomožni proces zrušil. Znova zaženite Chrome in poskusite znova.</translation>
+<translation id="5026754133087629784">Spletni pogled: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Komponenta appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Ta razširitev vključuje datoteko s ključem »<ph name="KEY_PATH" />«. Verjetno ne želite narediti tega.</translation>
+<translation id="5627523580512561598">razširitev <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Izbira naprave HID}multiple{Izbira naprav HID}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Skrbnik te naprave zahteva, da je nameščena razširitev <ph name="EXTENSION_NAME" />. Odstranitev ni mogoča.</translation>
+<translation id="6027032947578871493">Neznan izdelek <ph name="PRODUCT_ID" /> dobavitelja <ph name="VENDOR_NAME" /> (serijska številka <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> dobavitelja <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Razširitve ni mogoče odpakirati. Če želite varno odpakirati razširitev, pot do imenika vašega profila ne sme vsebovati simbolne povezave. Takšne poti ni za vaš profil.</translation>
+<translation id="616804573177634438">{0,select, single{Aplikacija »<ph name="APP_NAME" />« zahteva dostop do ene od vaših naprav.}multiple{Aplikacija »<ph name="APP_NAME" />« zahteva dostop do ene ali več vaših naprav.}other{UNUSED}}</translation>
+<translation id="641087317769093025">Razširitve ni bilo mogoče odpakirati</translation>
+<translation id="657064425229075395">Skripta za ozadje »<ph name="BACKGROUND_SCRIPT" />« ni bilo mogoče naložiti.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> proizvajalca <ph name="VENDOR_NAME" /> (serijska številka <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Imenika za odpakiranje ni bilo mogoče ustvariti: »<ph name="DIRECTORY_PATH" />«</translation>
+<translation id="677806580227005219">Komponenta mimehandler: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Ta razširitev se je prepogosto naložila znova.</translation>
+<translation id="7003844668372540529">Neznan izdelek <ph name="PRODUCT_ID" /> dobavitelja <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Skrbnik tega računalnika zahteva namestitev razširitve <ph name="EXTENSION_NAME" />. Ni je mogoče odstraniti ali spremeniti.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (ID razširitve »<ph name="EXTENSION_ID" />«) je blokiral skrbnik.</translation>
+<translation id="7972881773422714442">Možnosti: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Ta razširitev ni mogla prenosa poimenovati »<ph name="ATTEMPTED_FILENAME" />«, ker je druga razširitev (<ph name="EXTENSION_NAME" />) določila drugo ime datoteke: »<ph name="ACTUAL_FILENAME" />«.</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> proizvajalca <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Razširitev je neuspešno podala poverilnice omrežni zahtevi, ker je druga razširitev (<ph name="EXTENSION_NAME" />) podala druge poverilnice.</translation>
+<translation id="8602184400052594090">Datoteka manifesta manjka ali pa je ni mogoče prebrati.</translation>
+<translation id="8636666366616799973">Paket je neveljaven. Podrobnosti: »<ph name="ERROR_MESSAGE" />«.</translation>
+<translation id="8670869118777164560">Razširitvi ni uspelo preusmeriti omrežne zahteve <ph name="ATTEMPTED_REDIRECT_DESTINATION" />, ker jo je druga razširitev (<ph name="EXTENSION_NAME" />) preusmerila na cilj <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Orodje za odpakiranje razširitev</translation>
+<translation id="8825366169884721447">Razširitvi ni uspelo spremeniti glave zahteve »<ph name="HEADER_NAME" />« omrežne zahteve, ker je sprememba v sporu z drugo razširitvijo (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Razširitvi ni uspelo spremeniti glave odziva »<ph name="HEADER_NAME" />« omrežne zahteve, ker je sprememba v sporu z drugo razširitvijo (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_sr.xtb b/chromium/extensions/strings/extensions_strings_sr.xtb
new file mode 100644
index 00000000000..62a27c7bac4
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_sr.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sr">
+<translation id="1135328998467923690">Пакет је неважећи: „<ph name="ERROR_CODE" />“.</translation>
+<translation id="1256619696651732561">Рашчлањивач манифеста додатака</translation>
+<translation id="1445572445564823378">Овај додатак успорава <ph name="PRODUCT_NAME" />. Требало би да га онемогућите да би се учинак производа <ph name="PRODUCT_NAME" /> вратио.</translation>
+<translation id="149347756975725155">Није могуће учитати икону додатка „<ph name="ICON" />“.</translation>
+<translation id="1803557475693955505">Није могуће учитати страницу у позадини „<ph name="BACKGROUND_PAGE" />“.</translation>
+<translation id="2159915644201199628">Није могуће декодирати слику: „<ph name="IMAGE_NAME" />“</translation>
+<translation id="2350172092385603347">Коришћена је локализација, али параметар default_locale није наведен у манифесту.</translation>
+<translation id="2753617847762399167">Неисправна путања (апсолутна или релативна вредност једнака вредности „..“): „<ph name="IMAGE_PATH" />“</translation>
+<translation id="27822970480436970">Овај додатак није успео да измени захтев мреже јер измена није усаглашена са другим додатком.</translation>
+<translation id="2857834222104759979">Датотека манифеста је неважећа.</translation>
+<translation id="2988488679308982380">Инсталација пакета није била могућа: „<ph name="ERROR_CODE" />“</translation>
+<translation id="3115238746683532089">Непознати производ <ph name="PRODUCT_ID" /> продавца <ph name="VENDOR_ID" /> (серијски број <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Непознати производ <ph name="PRODUCT_ID" /> продавца <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Није могуће отпаковати додатак. Да бисте безбедно отпаковали додатак, мора да постоји путања до директоријума профила која почиње словом за ознаку јединице и не садржи спој, тачку повезивања или симболичку везу. За ваш профил не постоји таква путања.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (серијски број <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> продавца <ph name="VENDOR_ID" /> (серијски број <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Није успело учитавање странице покретача „<ph name="PAGE" />“.</translation>
+<translation id="388442998277590542">Није могуће учитати страницу опција „<ph name="OPTIONS_PAGE" />“.</translation>
+<translation id="4115165561519362854">Администратор овог рачунара захтева да <ph name="EXTENSION_NAME" /> има минималну верзију <ph name="EXTENSION_VERSION" />. Не можете да га омогућите док га не ажурирате на ту верзију (или новију).</translation>
+<translation id="4233778200880751280">Није могуће учитати страницу са основним подацима „<ph name="ABOUT_PAGE" />“.</translation>
+<translation id="4434145631756268951">{0,select, single{Изаберите USB уређ}multiple{Изаберите USB уређаје}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Инсталирање пакета није успело зато што је услужни процес отказао. Пробајте да поново покренете Chrome и покушајте поново.</translation>
+<translation id="5026754133087629784">Веб-приказ: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Приказ апликације: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Овај додатак садржи датотеку кључа „<ph name="KEY_PATH" />“. Вероватно то не желите.</translation>
+<translation id="5627523580512561598">додатак <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Изаберите уређај са интерфејсом}multiple{Изаберите уређај са интерфејсом}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Администратор овог рачунара захтева да додатак <ph name="EXTENSION_NAME" /> буде инсталиран. Не можете да га деинсталирате.</translation>
+<translation id="6027032947578871493">Непознати производ <ph name="PRODUCT_ID" /> продавца <ph name="VENDOR_NAME" /> (серијски број <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> продавца <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Није могуће отпаковати додатак. Да бисте безбедно отпаковали додатак, мора да постоји путања до директоријума профила која не садржи симболичку везу. За ваш профил не постоји таква путања.</translation>
+<translation id="616804573177634438">{0,select, single{Апликација „<ph name="APP_NAME" />“ захтева приступ неком од уређаја.}multiple{Апликација „<ph name="APP_NAME" />“ захтева приступ за један уређај или више њих.}other{UNUSED}}</translation>
+<translation id="641087317769093025">Није могуће распаковати додатак</translation>
+<translation id="657064425229075395">Није могуће учитати скрипту у позадини „<ph name="BACKGROUND_SCRIPT" />“.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> продавца <ph name="VENDOR_NAME" /> (серијски број <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Није могуће направити директоријум за распакивање: „<ph name="DIRECTORY_PATH" />“</translation>
+<translation id="677806580227005219">Руковалац за MIME: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Овај додатак се сувише често поново учитавао.</translation>
+<translation id="7003844668372540529">Непознати производ <ph name="PRODUCT_ID" /> продавца <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Администратор овог уређаја захтева да додатак <ph name="EXTENSION_NAME" /> буде инсталиран. Не можете да га уклоните или измените.</translation>
+<translation id="7809034755304591547">Администратор је блокирао <ph name="EXTENSION_NAME" /> (ИД додатка „<ph name="EXTENSION_ID" />“).</translation>
+<translation id="7972881773422714442">Опције: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Овај додатак није успео да преузетој датотеци дâ назив „<ph name="ATTEMPTED_FILENAME" />“ зато што је један други додатак (<ph name="EXTENSION_NAME" />) одредио другачији назив датотеке „<ph name="ACTUAL_FILENAME" />“.</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> продавца <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Овај додатак није успео да достави акредитиве захтеву мреже јер је други додатак (<ph name="EXTENSION_NAME" />) доставио друге акредитиве.</translation>
+<translation id="8602184400052594090">Датотека манифеста недостаје или је нечитљива.</translation>
+<translation id="8636666366616799973">Пакет је неважећи. Детаљи: „<ph name="ERROR_MESSAGE" />“.</translation>
+<translation id="8670869118777164560">Овај додатак није успео да преусмери захтев мреже на <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> пошто га је други додатак (<ph name="EXTENSION_NAME" />) преусмерио на <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Распакивање додатака</translation>
+<translation id="8825366169884721447">Овај додатак није успео да измени заглавље захтева „<ph name="HEADER_NAME" />“ за захтев мреже јер измена није била у складу са другим додатком (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Овај додатак није успео да измени заглавље одговора „<ph name="HEADER_NAME" />“ за захтев мреже јер измена није била у складу са другим додатком (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_sv.xtb b/chromium/extensions/strings/extensions_strings_sv.xtb
new file mode 100644
index 00000000000..f4b956a5b8c
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_sv.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sv">
+<translation id="1135328998467923690">Paketet är ogiltigt: <ph name="ERROR_CODE" />.</translation>
+<translation id="1256619696651732561">Textanalysator för tilläggsmanifest</translation>
+<translation id="1445572445564823378">Detta tillägg gör <ph name="PRODUCT_NAME" /> långsammare. Om du vill utnyttja den fulla prestandan i <ph name="PRODUCT_NAME" /> bör du inaktivera tillägget.</translation>
+<translation id="149347756975725155">Det gick inte att läsa in tilläggsikonen <ph name="ICON" />.</translation>
+<translation id="1803557475693955505">Det gick inte att läsa in bakgrundssidan <ph name="BACKGROUND_PAGE" />.</translation>
+<translation id="2159915644201199628">Det gick inte att avkoda bilden: <ph name="IMAGE_NAME" /></translation>
+<translation id="2350172092385603347">Lokalisering används, men default_locale specificeras inte i manifestet.</translation>
+<translation id="2753617847762399167">Illegal sökväg (absolut eller relativ med ".."): <ph name="IMAGE_PATH" /></translation>
+<translation id="27822970480436970">Tillägget misslyckades att ändra en nätverksbegäran eftersom ändringen står i konflikt med ett annat tillägg.</translation>
+<translation id="2857834222104759979">Manifestfilen är ogiltig.</translation>
+<translation id="2988488679308982380">Det gick inte att installera paketet <ph name="ERROR_CODE" /></translation>
+<translation id="3115238746683532089">Okänd produkt (<ph name="PRODUCT_ID" />) från leverantören <ph name="VENDOR_ID" /> (serienummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Okänd produkt (<ph name="PRODUCT_ID" />) från leverantören <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Det går inte att packa upp tillägget. Ett tillägg kan bara packas upp säkert om det finns en sökväg till din profilkatalog som börjar med en enhetsbokstav och inte innehåller en knutpunkt, mount point eller symbollänk.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (serienummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> från leverantören <ph name="VENDOR_ID" /> (serienummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Det gick inte att läsa in startsidan <ph name="PAGE" />.</translation>
+<translation id="388442998277590542">Det gick inte att läsa in alternativsidan <ph name="OPTIONS_PAGE" />.</translation>
+<translation id="4115165561519362854">Administratören för datorn måste ha den lägsta versionen <ph name="EXTENSION_VERSION" /> av <ph name="EXTENSION_NAME" />. Det kan inte aktiveras förrän det har uppdaterats till den versionen (eller senare).</translation>
+<translation id="4233778200880751280">Det gick inte att ladda informationssidan <ph name="ABOUT_PAGE" />.</translation>
+<translation id="4434145631756268951">{0,select, single{Välj USB-enhet}multiple{Välj USB-enheter}other{ANVÄNDS INTE}}</translation>
+<translation id="4811956658694082538">Det gick inte att installera paketet eftersom en hjälpprocess kraschade. Testa att starta om Chrome och försöka igen.</translation>
+<translation id="5026754133087629784">Webbvy: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appvy: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Detta tillägg omfattar nyckelfilen <ph name="KEY_PATH" />. Du vill antagligen inte göra detta.</translation>
+<translation id="5627523580512561598">tillägget <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Välj HID-enhet}multiple{Välj HID-enheter}other{ANVÄNDS INTE}}</translation>
+<translation id="5960890139610307736">Tilläggsvy: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Administratören för den här datorn kräver att <ph name="EXTENSION_NAME" /> installeras. Det kan inte avinstalleras.</translation>
+<translation id="6027032947578871493">Okänd produkt (<ph name="PRODUCT_ID" />) från <ph name="VENDOR_NAME" /> (serienummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> från leverantören <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Det går inte att packa upp tillägget. Ett tillägg kan bara packas upp säkert om det finns en sökväg till din profilkatalog som inte innehåller en symbollänk. Det finns ingen sådan sökväg för din profil.</translation>
+<translation id="616804573177634438">{0,select, single{Appen <ph name="APP_NAME" /> begär åtkomst till en av dina enheter.}multiple{Appen <ph name="APP_NAME" /> begär åtkomst till en eller flera av dina enheter.}other{ANVÄNDS INTE}}</translation>
+<translation id="641087317769093025">Det gick inte att packa upp tillägget</translation>
+<translation id="657064425229075395">Det gick inte att läsa in bakgrundsskriptet <ph name="BACKGROUND_SCRIPT" />.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> från <ph name="VENDOR_NAME" /> (serienummer <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Det gick inte att skapa en katalog för uppackning: <ph name="DIRECTORY_PATH" /></translation>
+<translation id="677806580227005219">Mime-hanterare: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Det här tillägget lästes in på nytt för ofta.</translation>
+<translation id="7003844668372540529">Okänd produkt (<ph name="PRODUCT_ID" />) från <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Administratören av den här enheten kräver att <ph name="EXTENSION_NAME" /> ska installeras. Det kan inte tas bort eller ändras.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (tilläggs-ID <ph name="EXTENSION_ID" />) har blockerats av administratören.</translation>
+<translation id="7972881773422714442">Alternativ: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Det gick inte att namnge nedladdningen <ph name="ATTEMPTED_FILENAME" /> eftersom ett annat tillägg (<ph name="EXTENSION_NAME" />) valde ett annat filnamn <ph name="ACTUAL_FILENAME" />.</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> från <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Det här tillägget kunde inte uppge några användaruppgifter till en nätverksbegäran eftersom ett annat tillägg (<ph name="EXTENSION_NAME" />) uppgav andra användaruppgifter.</translation>
+<translation id="8602184400052594090">Manifestfilen saknas eller är oläslig.</translation>
+<translation id="8636666366616799973">Paketet är ogiltigt. Information: <ph name="ERROR_MESSAGE" /></translation>
+<translation id="8670869118777164560">Det gick inte att omdirigera nätverksbegäran till <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> eftersom ett annat tillägg (<ph name="EXTENSION_NAME" />) omdirigerade det till <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Uppackare för tillägg</translation>
+<translation id="8825366169884721447">Det här tillägget kunde inte ändra begäranderubriken <ph name="HEADER_NAME" /> för ett nätverk eftersom det uppstod en konflikt mellan ändringen och ett annat tillägg (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Det här tillägget kunde inte ändra svarsrubriken <ph name="HEADER_NAME" /> för en nätverksbegäran eftersom det uppstod en konflikt mellan ändringen och ett annat tillägg (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_sw.xtb b/chromium/extensions/strings/extensions_strings_sw.xtb
new file mode 100644
index 00000000000..778eec2b18d
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_sw.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sw">
+<translation id="1135328998467923690">Furushi ni batili: '<ph name="ERROR_CODE" />'.</translation>
+<translation id="1256619696651732561">Kichanganuzi cha Faili za Maelezo ya Kiendelezi</translation>
+<translation id="1445572445564823378">Kiendelezi hiki kinapunguza kasi ya <ph name="PRODUCT_NAME" />. Unafa kukilemaza ili kurejesha upya utendaji wa <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">Aikoni '<ph name="ICON" />' ya kiendelezi haikuweza kupakiwa.</translation>
+<translation id="1803557475693955505">Ukurasa wa mandhari '<ph name="BACKGROUND_PAGE" />' haukuweza kupakiwa.</translation>
+<translation id="2159915644201199628">Isingeweza kusimbua picha: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">Usanidi wa eneo umetumiwa, lakini eneo_chaguo-msingi halikubainishwa katika ratiba</translation>
+<translation id="2753617847762399167">Njia haramu (kabisa au kiasi na '..'): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">Kiendelezi hiki kimeshindwa kurekebisha ombi la mtandao kwa sababu ya ukizano wa urekebishaji na kirefusho kingine.</translation>
+<translation id="2857834222104759979">Faili ya ratiba sio halali.</translation>
+<translation id="2988488679308982380">Isingeweza kusakinisha furushi: '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089">Bidhaa isiyojulikana <ph name="PRODUCT_ID" /> kutoka kwa mchuuzi <ph name="VENDOR_ID" /> (nambari tambulishi <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Bidhaa isiyojulikana <ph name="PRODUCT_ID" /> kutoka kwa mchuuzi <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Haiwezi kufungua kiendelezi. Ili kufungua kiendelezi kwa usalama, sharti kuwe na kijia katika saraka ya maelzeo yako mafupi ambacho kinaanza kwa sarufi ya kiendeshi na hakina makutano, sehemu ya kuangika, au kiungo cha mfumo. Hakuna vijia kama hivyo vinavyopo kwa maelezo yako mafupi.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (nambari tambulishi <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> kutoka kwa mchuuzi <ph name="VENDOR_ID" /> (nambari tambulishi <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Haikuweza kupakia ukurasa wa kifungua programu '<ph name="PAGE" />'.</translation>
+<translation id="388442998277590542">Isingeweza kupakia ukurasa wa chaguo <ph name="OPTIONS_PAGE" /> '.</translation>
+<translation id="4115165561519362854">Msimamizi wa mashine hii anahitaji <ph name="EXTENSION_NAME" /> ili kuwa na toleo la chini zaidi la <ph name="EXTENSION_VERSION" />. Haiwezi kuwashwa mpaka isasishwe hadi toleo hilo (au juu zaidi).</translation>
+<translation id="4233778200880751280">Haikuweza kupakia ukurasa wa kuhusu '<ph name="ABOUT_PAGE" />'.</translation>
+<translation id="4434145631756268951">{0,select, single{Chagua kifaa cha USB}multiple{Chagua vifaa vya USB}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Haikuweza kusakinisha kifurushi kwa sababu mchakato wa utekelezaji umeacha kufanya kazi. Jaribu kuzima na kuwasha Chrome na ujaribu tena.</translation>
+<translation id="5026754133087629784">Mwonekano wa wavuti: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Mwonekano wa programu: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Kiendelezi hiki kinajumuisha faili muhimu '<ph name="KEY_PATH" />'. Huenda hutaki kufanya hivyo.</translation>
+<translation id="5627523580512561598">kiendelezi <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Chagua kifaa cha HID}multiple{Chagua vifaa vya HID}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Msimamizi wa mashine hii anahitaji <ph name="EXTENSION_NAME" /> kusakinishwa. Hakiwezi kuondolewa.</translation>
+<translation id="6027032947578871493">Bidhaa isiyojulikana <ph name="PRODUCT_ID" /> kutoka kwa <ph name="VENDOR_NAME" /> (nambari tambulishi <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> kutoa kwa mchuuzi <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Haiwezi kufungua kiendelezi. Ili kufungua kiendelezi kwa usalama, sharti kuwe na kijia katika saraka yako ya maelezo mafupi ambacho hakina kiungo cha mfumo. Hakuna vijia kama hivyo vilivyopo kwa maelezo yako mafupi.</translation>
+<translation id="616804573177634438">{0,select, single{Programu ya "<ph name="APP_NAME" />" inaomba idhini ya kufikia mojawapo ya vifaa vyako.}multiple{Programu ya "<ph name="APP_NAME" />" inaomba idhini ya kufikia kifaa chako kimoja au zaidi.}other{UNUSED}}</translation>
+<translation id="641087317769093025">Isingeweza kufungua kiendelezi</translation>
+<translation id="657064425229075395">Haikuweza kupakia hati ya mandharinyuma '<ph name="BACKGROUND_SCRIPT" />'.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> kutoka kwa <ph name="VENDOR_NAME" /> (nambari ya ufuatiliaji <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Isingeweza kuunda saraka ya kufungua: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">Kishikilio cha Mime: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Kiendelezi hiki kilijipakia chenyewe upya kila mara.</translation>
+<translation id="7003844668372540529">Bidhaa isiyojulikana <ph name="PRODUCT_ID" /> kutoka kwa <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Msimamizi wa mashine haya anahitaji <ph name="EXTENSION_NAME" /> kisakinishwe. Hakiwezi kuondolewa au kurekebishwa.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (Kitambulisho ya kiendelezi "<ph name="EXTENSION_ID" />") kimezuiwa na msimamizi.</translation>
+<translation id="7972881773422714442">Chaguzi: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Kiendelezi hiki kimeshindwa kukipa kipakuliwa jina "<ph name="ATTEMPTED_FILENAME" />" kwa sababu kipakuliwa kingine (<ph name="EXTENSION_NAME" />) kimechagua jina tofauti la faili "<ph name="ACTUAL_FILENAME" />".</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> kutoka <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Kiendelezi hiki kimeshindwa kutoa vitambulisho kwenye ombi la mtandao kwa sababu kiendelezi kingine (<ph name="EXTENSION_NAME" />) kimetoa vitambulisho tofauti.</translation>
+<translation id="8602184400052594090">Faili ya ratiba haipatikani au haisomeki.</translation>
+<translation id="8636666366616799973">Furushi ni batili. Maelezo: '<ph name="ERROR_MESSAGE" />'.</translation>
+<translation id="8670869118777164560">Kiendelezi kimeshindwa kuelekeza upya ombi la mtandao kwenye <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> kwa sababu kiendelezi kingine (<ph name="EXTENSION_NAME" />) kimeelekeza upya kwenye <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Kipakuaji cha Viendelezi</translation>
+<translation id="8825366169884721447">Kiendelezi hiki kimeshindwa kuboresha kichwa cha ombi "<ph name="HEADER_NAME" />" cha ombi la mtandao kwa sababu uboreshaji umekinzana na kiendelezi kingine (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Kiendelezi hiki kimeshindwa kuboresha kichwa cha jibu "<ph name="HEADER_NAME" />" cha ombi la mtandao kwa sababu uboreshaji umekinzana na kiendelezi kingine (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_ta.xtb b/chromium/extensions/strings/extensions_strings_ta.xtb
new file mode 100644
index 00000000000..8e7de130a6a
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_ta.xtb
@@ -0,0 +1,57 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ta">
+<translation id="1135328998467923690">தொகுப்பு செல்லாதது: '<ph name="ERROR_CODE" />'.</translation>
+<translation id="1256619696651732561">எக்ஸ்டன்ஷன் மேனிஃபெஸ்ட் பார்சர்</translation>
+<translation id="1445572445564823378">இந்த நீட்டிப்பு, <ph name="PRODUCT_NAME" /> ஐ மந்தமாக்குகிறது. <ph name="PRODUCT_NAME" /> இன்
+செயல்திறனை மீட்டமைக்க, இதை நீங்கள் முடக்க வேண்டும்.</translation>
+<translation id="149347756975725155">நீட்டிப்புப் படவுரு '<ph name="ICON" />' ஐ ஏற்ற முடியவில்லை.</translation>
+<translation id="1803557475693955505">'<ph name="BACKGROUND_PAGE" />' என்ற பின்புலப் பக்கத்தை ஏற்ற முடியவில்லை.</translation>
+<translation id="2159915644201199628">இந்தப் படத்தை குறிநீக்க முடியவில்லை: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">மொழிப்பெயர்ப்பு பயன்படுத்தப்பட்டது, ஆனால் default_locale அமைப்பில் குறிப்பிடப்படவில்லை. </translation>
+<translation id="2753617847762399167">முறையற்ற பாதை (சரியான அல்லது இதனுடன் தொடர்புடையவை '..'): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">மாற்றம் மற்றொரு நீட்டிப்புடன் முரண்பட்டுள்ளதால், நெட்வொர்க் கோரிக்கையை மாற்றுவதற்கான, இந்தக் கோரிக்கை தோல்வி அடைந்தது.</translation>
+<translation id="2857834222104759979">அமைப்புக் கோப்பு செல்லுபடியாகாதது.</translation>
+<translation id="2988488679308982380">இந்த தொகுப்பை நிறுவமுடியாது: '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089"><ph name="VENDOR_ID" /> விற்பனையாளர் அனுப்பிய <ph name="PRODUCT_ID" /> (சீரியல் எண் <ph name="SERIAL_NUMBER" />) தயாரிப்பை அறிய முடியவில்லை</translation>
+<translation id="3163201441334626963"><ph name="VENDOR_ID" /> அனுப்பிய <ph name="PRODUCT_ID" /> தயாரிப்பை அறிய முடியவில்லை.</translation>
+<translation id="3369521687965833290">நீட்டிப்பைத் திறக்க முடியவில்லை. பாதுகாப்பாக நீட்டிப்பைத் திறக்க வேண்டுமானால், உங்கள் சுயவிவர கோப்பகத்திற்கான பாதை தரப்பட வேண்டும். சுயவிவர கோப்பகமானது கோப்பக எழுத்துடன் தொடங்க வேண்டும் மற்றும் சந்திப்பு, மவுண்ட் பாயின்ட் அல்லது சிம்லிங்கைக் கொண்டிருக்கக் கூடாது. உங்கள் சுயவிவரத்தில் அவ்வாறான பாதைகள் எதுவுமில்லை.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (சீரியல் எண் <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397">விற்பனையாளர் <ph name="VENDOR_ID" /> அனுப்பிய <ph name="PRODUCT_NAME" /> (சீரியல் எண் <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">துவக்கி பக்கமான '<ph name="PAGE" />'ஐ ஏற்ற முடியவில்லை.</translation>
+<translation id="388442998277590542">'<ph name="OPTIONS_PAGE" />' என்ற விருப்பங்கள் பக்கத்தை ஏற்ற முடியவில்லை.</translation>
+<translation id="4115165561519362854">இந்தச் சாதனத்தின் நிர்வாகிக்கு <ph name="EXTENSION_NAME" /> இன் குறைந்தபட்ச பதிப்பான <ph name="EXTENSION_VERSION" /> தேவை. இது அந்தப் பதிப்பிற்குப் (அல்லது சமீபத்திய பதிப்பு) புதுப்பிக்கப்படும் வரை இயக்கமுடியாது.</translation>
+<translation id="4233778200880751280">'<ph name="ABOUT_PAGE" />' பக்கத்தை ஏற்ற முடியவில்லை.</translation>
+<translation id="4434145631756268951">{0,select, single{USB சாதனத்தைத் தேர்ந்தெடுக்கவும்}multiple{USB சாதனங்களைத் தேர்ந்தெடுக்கவும்}other{பயன்படுத்தப்படாதவை}}</translation>
+<translation id="4811956658694082538">பயனமைப்பு செயல்முறைச் சிதைவுக்குட்பட்டதால், தொகுப்பை நிறுவ முடியவில்லை. Chromeஐ மீண்டும் துவங்கி, முயற்சிக்கவும்.</translation>
+<translation id="5026754133087629784">இணையப்பார்வை: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">இந்த நீட்டிப்பு விசை கோப்பு '<ph name="KEY_PATH" />' ஐக் கொண்டுள்ளது. நீங்கள் அதை செய்ய விரும்பாமல் இருக்கலாம்.</translation>
+<translation id="5627523580512561598"><ph name="EXTENSION_NAME" /> நீட்டிப்பு</translation>
+<translation id="5630931906013276297">{0,select, single{HID சாதனத்தைத் தேர்ந்தெடுக்கவும்}multiple{HID சாதனங்களைத் தேர்ந்தெடுக்கவும்}other{பயன்படுத்தப்படாதவை}}</translation>
+<translation id="5960890139610307736">நீட்டிப்புக்காட்சி: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">இந்தக் கணினியின் நிர்வாகிக்கு <ph name="EXTENSION_NAME" /> நிறுவப்பட வேண்டும். இதை நிறுவல் நீக்க முடியாது.</translation>
+<translation id="6027032947578871493"><ph name="VENDOR_NAME" /> அனுப்பிய <ph name="PRODUCT_ID" /> (சீரியல் எண் <ph name="SERIAL_NUMBER" />) தயாரிப்பை அறிய முடியவில்லை</translation>
+<translation id="6068932090455285721">விற்பனையாளர் <ph name="VENDOR_ID" /> அனுப்பிய <ph name="PRODUCT_NAME" /></translation>
+<translation id="6143635259298204954">நீட்டிப்பைத் திறக்க முடியவில்லை. பாதுகாப்பாக நீட்டிப்பைத் திறக்க வேண்டுமானால், உங்கள் சுயவிவரக் கோப்பகத்திற்கான பாதை தரப்பட வேண்டும். சுயவிவரக் கோப்பகமானது கோப்பக எழுத்துடன் தொடங்க வேண்டும் மற்றும் சிம்லிங்கைக் கொண்டிருக்கக் கூடாது. உங்கள் சுயவிவரத்தில் அவ்வாறான பாதைகள் எதுவுமில்லை.</translation>
+<translation id="616804573177634438">{0,select, single{"<ph name="APP_NAME" />" பயன்பாடு உங்கள் சாதனங்களில் ஒன்றின் அணுகலைக் கோருகிறது.}multiple{"<ph name="APP_NAME" />" பயன்பாடு உங்களது ஒன்று அல்லது மேற்பட்ட சாதனங்களின் அணுகலைக் கோருகிறது.}other{பயன்படுத்தப்படாதவை}}</translation>
+<translation id="641087317769093025">நீட்டிப்பின் ஜிப்பை திறக்க முடியவில்லை</translation>
+<translation id="657064425229075395">'<ph name="BACKGROUND_SCRIPT" />' என்ற பின்புல ஸ்கிரிப்டை ஏற்ற முடியவில்லை.</translation>
+<translation id="6580950983454333167"><ph name="VENDOR_NAME" /> இடமிருந்து <ph name="PRODUCT_NAME" /> (வரிசை எண் <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">இதன் ஜிப்பை திறப்பதற்குக் கோப்பகத்தை உருவாக்க முடியவில்லை: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">Mimehandler: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">இந்த நீட்டிப்பு அடிக்கடி தன்னைத்தானே ஏற்றுகிறது.</translation>
+<translation id="7003844668372540529"><ph name="VENDOR_NAME" /> அனுப்பிய <ph name="PRODUCT_ID" /> தயாரிப்பை அறிய முடியவில்லை</translation>
+<translation id="7217838517480956708">இந்தக் கணினியின் நிர்வாகிக்கு, <ph name="EXTENSION_NAME" /> ஐ நிறுவ வேண்டும். இதை அகற்றவோ மாற்றவோ முடியாது.</translation>
+<translation id="7809034755304591547">நிர்வாகியால் <ph name="EXTENSION_NAME" /> (நீட்டிப்பு ஐடி "<ph name="EXTENSION_ID" />") தடுக்கப்படுகிறது.</translation>
+<translation id="7972881773422714442">விருப்பங்கள்: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">இந்த நீட்டிப்பு, பதிவிறக்கத்திற்கு "<ph name="ATTEMPTED_FILENAME" />" என்று பெயரிடுவதில் தோல்வி, ஏனெனில் மற்றொரு நீட்டிப்பு (<ph name="EXTENSION_NAME" />) வேறொரு கோப்புப்பெயரை "<ph name="ACTUAL_FILENAME" />" நிர்ணயித்து விட்டது.</translation>
+<translation id="8284835137979141223"><ph name="VENDOR_NAME" /> இன் <ph name="PRODUCT_NAME" /></translation>
+<translation id="8341840687457896278">(<ph name="EXTENSION_NAME" />) என்ற வேறொரு நீட்டிப்பு வேறுபட்ட நற்சான்றிதழ்களை வழங்கியதன் காரணமாக பிணைய கோரிக்கைக்கு நற்சான்றிதழ்களை வழங்குவதில் இந்த நீட்டிப்பு தோல்வியடைந்தது.</translation>
+<translation id="8602184400052594090">அமைப்புக் கோப்பைக் காணவில்லை அல்லது படிக்கமுடியாததாக இருக்கிறது.</translation>
+<translation id="8636666366616799973">தொகுப்பு செல்லாதது. விவரங்கள்: '<ph name="ERROR_MESSAGE" />'.</translation>
+<translation id="8670869118777164560">(<ph name="EXTENSION_NAME" />) என்ற வேறொரு நீட்டிப்பு <ph name="ACTUAL_REDIRECT_DESTINATION" /> க்குத் திசைத்திருப்பப்பட்டதால் பிணைய கோரிக்கையை <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> க்கு திசைத்திருப்புவதில் இந்த நீட்டிப்பு தோல்வியடைந்தது.</translation>
+<translation id="8712265948125780616">எக்ஸ்டன்ஷன் அன்பேக்கர்</translation>
+<translation id="8825366169884721447">(<ph name="EXTENSION_NAME" />) என்ற வேறொரு நீட்டிப்புடன் ஏற்பட்ட திருத்த முரண்பாட்டால் "<ph name="HEADER_NAME" />" கோரிக்கை மேற்தலைப்பைத் திருத்துவதில் இந்த நீட்டிப்பு தோல்வியடைந்தது.</translation>
+<translation id="9111791539553342076">(<ph name="EXTENSION_NAME" />) என்ற வேறொரு நீட்டிப்புடன் ஏற்பட்ட திருத்த முரண்பாட்டால் "<ph name="HEADER_NAME" />" என்ற பிணைய கோரிக்கையின் பதில் மேற்தலைப்பைத் திருத்துவதில் இந்த நீட்டிப்பு தோல்வியடைந்தது.</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_te.xtb b/chromium/extensions/strings/extensions_strings_te.xtb
new file mode 100644
index 00000000000..e60eb475b79
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_te.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="te">
+<translation id="1135328998467923690">ప్యాకేజీ చెల్లనిది: '<ph name="ERROR_CODE" />'.</translation>
+<translation id="1256619696651732561">ఎక్స్‌టెన్షన్ మానిఫెస్ట్ పార్సర్</translation>
+<translation id="1445572445564823378">ఈ పొడిగింపు <ph name="PRODUCT_NAME" />ను మందగింప చేస్తోంది. <ph name="PRODUCT_NAME" /> యొక్క పనితీరును పునరుద్ధరించడానికి మీరు దీన్ని ఆపివేయాలి.</translation>
+<translation id="149347756975725155">'<ph name="ICON" />' పొడిగింపు చిహ్నాన్ని లోడ్ చేయలేకపోయింది.</translation>
+<translation id="1803557475693955505">నేపథ్య పేజీ '<ph name="BACKGROUND_PAGE" />' లోడ్ చేయబడలేదు.</translation>
+<translation id="2159915644201199628">ఈ చిత్రం డీకోడ్ చేయబడదు: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">స్థానీకరణ ఉపయోగించబడింది, కానీ మానిఫెస్ట్ లో default_locale పేర్కొనబడలేదు.</translation>
+<translation id="2753617847762399167">చట్టవిరుద్ధ పథం (ఖచ్చితంగా లేదా '..'కు సంబంధిత): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">నెట్‌వర్క్ అభ్యర్థనను సవరించడంలో ఈ పొడిగింపు విఫలమైంది ఎందుకంటే సవరణ మరొక పొడిగింపుతో వైరుధ్యంలో ఉంది.</translation>
+<translation id="2857834222104759979">వివరాల ఫైల్ చెల్లనిది.</translation>
+<translation id="2988488679308982380">ప్యాకేజీని వ్యవస్థాపించడం సాధ్యం కాలేదు: '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089"><ph name="VENDOR_ID" /> విక్రేత నుండి తెలియని ఉత్పత్తి <ph name="PRODUCT_ID" /> (క్రమ సంఖ్య <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963"><ph name="VENDOR_ID" /> నుండి <ph name="PRODUCT_ID" /> తెలియని ఉత్పత్తి</translation>
+<translation id="3369521687965833290">ఎక్స్‌టెన్‌షన్ అన్‌ప్యాక్ చేయబడదు. ఒక ఎక్స్‌టెన్‌షన్‌‌ను సురక్షితంగా అన్‌ప్యాక్ చేయడానికి, మీ ప్రొఫైల్ డైరెక్టరీకి ఒక డ్రైవ్ అక్షరంతో ప్రారంభమయ్యే మరియు జంక్షన్, మౌంట్ పాయింట్ లేదా సింలింక్ ఉండని గమ్యమార్గం తప్పనిసరిగా ఉండాలి. మీ ప్రొఫైల్‌కు అటువంటి గమ్యమార్గం లేదు.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (క్రమ సంఖ్య <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="VENDOR_ID" /> విక్రేత నుండి <ph name="PRODUCT_NAME" /> (క్రమ సంఖ్య <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">లాంచర్ పేజీ '<ph name="PAGE" />'ని లోడ్ చేయడం సాధ్యపడలేదు.</translation>
+<translation id="388442998277590542">ఎంపికల పేజీ '<ph name="OPTIONS_PAGE" />'ని లోడ్ చెయ్యడం సాధ్యం కాలేదు.</translation>
+<translation id="4115165561519362854">ఈ మెషీన్ నిర్వాహకుడికి <ph name="EXTENSION_NAME" /> కనీస సంస్కరణ అయిన <ph name="EXTENSION_VERSION" /> ఉండటం అవసరం. ఇది ఆ సంస్కరణకు (లేదా తదుపరి దానికి) నవీకరించే వరకు ప్రారంభించబడదు.</translation>
+<translation id="4233778200880751280">'<ph name="ABOUT_PAGE" />' పరిచయ పేజీని లోడ్ చేయడం సాధ్యపడలేదు.</translation>
+<translation id="4434145631756268951">{0,select, single{USB పరికరాన్ని ఎంచుకోండి}multiple{USB పరికరాలను ఎంచుకోండి}other{UNUSED}}</translation>
+<translation id="4811956658694082538">వినియోగ ప్రాసెస్ క్రాష్ అయినందున ప్యాకేజీని ఇన్‌స్టాల్ చేయలేకపోయింది. Chromeను పునఃప్రారంభించి, మళ్లీ ప్రయత్నించండి.</translation>
+<translation id="5026754133087629784">వెబ్ వీక్షణ: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">అనువర్తన వీక్షణ: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">ఈ పొడిగింపు '<ph name="KEY_PATH" />' కీ ఫైల్‌ను కలిగి ఉంది. బహుశా మీరు దాన్ని చేయకూడదు.</translation>
+<translation id="5627523580512561598">పొడిగింపు <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{HID పరికరాన్ని ఎంచుకోండి}multiple{HID పరికరాలను ఎంచుకోండి}other{UNUSED}}</translation>
+<translation id="5960890139610307736">పొడిగింపు వీక్షణ: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692"><ph name="EXTENSION_NAME" />ని ఇన్‌స్టాల్ చేయడం ఈ మెషీన్ నిర్వాహకుడికి అవసరం. ఇది అన్ఇన్‌స్టాల్ చేయబడదు.</translation>
+<translation id="6027032947578871493"><ph name="VENDOR_NAME" /> నుండి తెలియని ఉత్పత్తి <ph name="PRODUCT_ID" /> (క్రమ సంఖ్య <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="VENDOR_ID" /> విక్రేత నుండి <ph name="PRODUCT_NAME" /></translation>
+<translation id="6143635259298204954">ఎక్స్‌టెన్‌షన్‌ అన్‌ప్యాక్ చేయబడదు. ఒక ఎక్స్‌టెన్‌షన్‌‌ను సురక్షితంగా అన్‌ప్యాక్ చేయడానికి, మీ ప్రొఫైల్ డైరెక్టరీకి సింలింక్ ఉండని గమ్యమార్గం తప్పనిసరిగా ఉండాలి. మీ ప్రొఫైల్‌కు అటువంటి గమ్యమార్గం లేదు.</translation>
+<translation id="616804573177634438">{0,select, single{"<ph name="APP_NAME" />" అనువర్తనం మీ పరికరాల్లో ఒకదానికి ప్రాప్యతను అభ్యర్థిస్తోంది.}multiple{"<ph name="APP_NAME" />" అనువర్తనం మీ పరికరాల్లో ఒకదానికి లేదా అంతకంటే ఎక్కువ వాటికి ప్రాప్యతను అభ్యర్థిస్తోంది.}other{UNUSED}}</translation>
+<translation id="641087317769093025">పొడిగింపు అన్‌జిప్ చేయబడదు</translation>
+<translation id="657064425229075395">నేపథ్య స్క్రిప్ట్ '<ph name="BACKGROUND_SCRIPT" />'ను లోడ్ చేయడం సాధ్యం కాలేదు.</translation>
+<translation id="6580950983454333167"><ph name="VENDOR_NAME" /> నుండి <ph name="PRODUCT_NAME" /> (క్రమ సంఖ్య <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">దీనిలో అన్‌జిప్ చేయడానికి డైరెక్టరీ సృష్టించబడదు: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">Mime హ్యాండ్లర్: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">ఈ పొడిగింపు దానికదే చాలా తరచుగా రీలోడ్ అయ్యింది.</translation>
+<translation id="7003844668372540529"><ph name="VENDOR_NAME" /> నుండి తెలియని ఉత్పత్తి <ph name="PRODUCT_ID" /></translation>
+<translation id="7217838517480956708">ఈ మెషీన్ యొక్క నిర్వాహకుడికి <ph name="EXTENSION_NAME" /> ఇన్‌స్టాల్ చేయబడి ఉండటం అవసరం. దీన్ని తీసివేయడం లేదా సవరించడం సాధ్యపడదు.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (పొడిగింపు ID "<ph name="EXTENSION_ID" />")ని నిర్వాహకుడు బ్లాక్ చేసారు.</translation>
+<translation id="7972881773422714442">ఎంపికలు: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">ఈ పొడిగింపు డౌన్‌లోడ్ యొక్క పేరును "<ph name="ATTEMPTED_FILENAME" />"గా పేర్కొనడంలో విఫలమైంది ఎందుకంటే మరో పొడిగింపు (<ph name="EXTENSION_NAME" />) "<ph name="ACTUAL_FILENAME" />" అనే వేరే ఫైల్ పేరుని నిశ్చయించింది.</translation>
+<translation id="8284835137979141223"><ph name="VENDOR_NAME" /> నుండి <ph name="PRODUCT_NAME" /></translation>
+<translation id="8341840687457896278">వేరొక పొడిగింపు (<ph name="EXTENSION_NAME" />) వేరే ఆధారాలను అందించినందున ఈ పొడిగింపు నెట్‌వర్క్ అభ్యర్థనకు ఆధారాలను అందించడంలో విఫలమైంది.</translation>
+<translation id="8602184400052594090">మానిఫెస్ట్ ఫైల్ తప్పిపోయింది లేదా చదవలేనిది.</translation>
+<translation id="8636666366616799973">ప్యాకేజీ చెల్లనిది. వివరాలు: '<ph name="ERROR_MESSAGE" />'.</translation>
+<translation id="8670869118777164560">వేరొక పొడిగింపు (<ph name="EXTENSION_NAME" />) నెట్‌వర్క్ అభ్యర్థనను <ph name="ACTUAL_REDIRECT_DESTINATION" />కు దారి మళ్లించినందున ఈ పొడిగింపు దీన్ని <ph name="ATTEMPTED_REDIRECT_DESTINATION" />కు దారి మళ్లించడంలో విఫలమైంది.</translation>
+<translation id="8712265948125780616">ఎక్స్‌టెన్షన్ అన్‌ప్యాకర్</translation>
+<translation id="8825366169884721447">సవరణ వేరొక పొడిగింపు (<ph name="EXTENSION_NAME" />)కు వైరుధ్యంగా ఉన్నందున ఈ పొడిగింపు నెట్‌వర్క్ అభ్యర్థన యొక్క "<ph name="HEADER_NAME" />" అభ్యర్థన శీర్షికను సవరించడంలో విఫలమైంది.</translation>
+<translation id="9111791539553342076">సవరణ వేరొక పొడిగింపు (<ph name="EXTENSION_NAME" />)కు వైరుధ్యంగా ఉన్నందున ఈ పొడిగింపు నెట్‌వర్క్ అభ్యర్థన యొక్క "<ph name="HEADER_NAME" />" ప్రతిస్పందన శీర్షికను సవరించడంలో విఫలమైంది.</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_th.xtb b/chromium/extensions/strings/extensions_strings_th.xtb
new file mode 100644
index 00000000000..cc90a82e6db
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_th.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="th">
+<translation id="1135328998467923690">แพ็กเกจไม่ถูกต้อง: "<ph name="ERROR_CODE" />"</translation>
+<translation id="1256619696651732561">โปรแกรมแยกวิเคราะห์ไฟล์ Manifest ส่วนขยาย</translation>
+<translation id="1445572445564823378">ส่วนขยายนี้จะทำให้ <ph name="PRODUCT_NAME" /> ทำงานช้าลง คุณควรปิดใช้งานส่วนขยายเพื่อให้ประสิทธิภาพการทำงานของ <ph name="PRODUCT_NAME" /> กลับมาเป็นปกติ</translation>
+<translation id="149347756975725155">ไม่สามารถโหลดไอคอนส่วนขยาย "<ph name="ICON" />"</translation>
+<translation id="1803557475693955505">ไม่สามารถโหลดหน้าพื้นหลัง "<ph name="BACKGROUND_PAGE" />"</translation>
+<translation id="2159915644201199628">ไม่สามารถถอดรหัสภาพ: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">ใช้การแปลภาษาท้องถิ่นแล้ว แต่ไม่ได้ระบุ default_locale ในมานิเฟสต์</translation>
+<translation id="2753617847762399167">เส้นทางที่ไม่ถูกต้อง (โดยชัดแจ้งหรือเกี่ยวข้องกับ '..'): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">ส่วนขยายนี้ไม่สามารถปรับเปลี่ยนคำขอเครือข่ายได้เนื่องจากการปรับเปลี่ยนมีความขัดแย้งกับส่วนขยายอื่น</translation>
+<translation id="2857834222104759979">ไฟล์มานิเฟสต์ไม่ถูกต้อง</translation>
+<translation id="2988488679308982380">ไม่สามารถติดตั้งแพ็กเกจ: "<ph name="ERROR_CODE" />"</translation>
+<translation id="3115238746683532089">ผลิตภัณฑ์ที่ไม่รู้จัก <ph name="PRODUCT_ID" /> จากผู้ขาย <ph name="VENDOR_ID" /> (หมายเลขซีเรียล <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">ผลิตภัณฑ์ที่ไม่รู้จัก <ph name="PRODUCT_ID" /> จากผู้ขาย <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">ไม่สามารถแยกส่วนขยายได้ หากต้องการแยกส่วนขยายอย่างปลอดภัย คุณต้องมีเส้นทางไปยังไดเรกทอรีโปรไฟล์ของคุณซึ่งขึ้นต้นด้วยตัวอักษรของไดรฟ์และจะต้องไม่มีจังก์ชัน จุดต่อเชื่อม หรือลิงก์สัญลักษณ์ ไม่มีเส้นทางในรูปแบบดังกล่าวสำหรับโปรไฟล์ของคุณ</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (หมายเลขซีเรียล <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> จากผู้ขาย <ph name="VENDOR_ID" /> (หมายเลขซีเรียล <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">ไม่สามารถโหลดหน้า Launcher "<ph name="PAGE" />"</translation>
+<translation id="388442998277590542">ไม่สามารถโหลดหน้าตัวเลือก "<ph name="OPTIONS_PAGE" />"</translation>
+<translation id="4115165561519362854">ผู้ดูแลระบบของเครื่องนี้ต้องการ <ph name="EXTENSION_NAME" /> เวอร์ชัน <ph name="EXTENSION_VERSION" /> ซึ่งเป็นเวอร์ชันขั้นต่ำ จะไม่สามารถเปิดใช้ได้จนกว่าจะมีการอัปเดตส่วนขยายนี้เป็นเวอร์ชันดังกล่าว (หรือสูงกว่า)</translation>
+<translation id="4233778200880751280">ไม่สามารถโหลดหน้าเกี่ยวกับ "<ph name="ABOUT_PAGE" />"</translation>
+<translation id="4434145631756268951">{0,select, single{เลือกอุปกรณ์ USB}multiple{เลือกอุปกรณ์ USB}other{UNUSED}}</translation>
+<translation id="4811956658694082538">ไม่สามารถติดตั้งแพ็กเกจเนื่องจากกระบวนการอรรถประโยชน์ขัดข้อง ลองรีสตาร์ท Chrome และลองใหม่อีกครั้ง</translation>
+<translation id="5026754133087629784">มุมมองเว็บ: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">การดูแอป: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">ส่วนขยายนี้มีไฟล์คีย์ "<ph name="KEY_PATH" />" คุณอาจจะไม่ต้องการดำเนินการนั้น</translation>
+<translation id="5627523580512561598">ส่วนขยาย <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{เลือกอุปกรณ์ HID}multiple{เลือกอุปกรณ์ HID}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">ผู้ดูแลระบบของอุปกรณ์เครื่องนี้กำหนดให้ติดตั้ง <ph name="EXTENSION_NAME" /> ไม่สามารถถอนการติดตั้งส่วนขยายนี้ได้</translation>
+<translation id="6027032947578871493">ผลิตภัณฑ์ที่ไม่รู้จัก <ph name="PRODUCT_ID" /> จาก <ph name="VENDOR_NAME" /> (หมายเลขซีเรียล <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> จากผู้ขาย <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">ไม่สามารถแยกส่วนขยาย หากต้องการแยกส่วนขยายอย่างปลอดภัย คุณต้องมีเส้นทางไปยังไดเรกทอรีโปรไฟล์ของคุณซึ่งไม่มีลิงก์สัญลักษณ์ ไม่มีเส้นทางในรูปแบบดังกล่าวอยู่สำหรับโปรไฟล์ของคุณ</translation>
+<translation id="616804573177634438">{0,select, single{แอปพลิเคชัน "<ph name="APP_NAME" />" กำลังขอสิทธิ์เข้าถึงอุปกรณ์เครื่องใดเครื่องหนึ่งของคุณ}multiple{แอปพลิเคชัน "<ph name="APP_NAME" />" กำลังขอสิทธิ์เข้าถึงอุปกรณ์ของคุณอย่างน้อย 1 เครื่อง}other{UNUSED}}</translation>
+<translation id="641087317769093025">ไม่สามารถแตกไฟล์ซิปส่วนขยาย</translation>
+<translation id="657064425229075395">ไม่สามารถโหลดสคริปต์พื้นหลัง "<ph name="BACKGROUND_SCRIPT" />"</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> จาก <ph name="VENDOR_NAME" /> (หมายเลขซีเรียล <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">ไม่สามารถสร้างไดเรกทอรีสำหรับการแตกไฟล์ซิป: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">เครื่องจัดการ MIME: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">ส่วนขยายนี้โหลดตัวเองซ้ำบ่อยเกินไป</translation>
+<translation id="7003844668372540529">ผลิตภัณฑ์ที่ไม่รู้จัก <ph name="PRODUCT_ID" /> จาก <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">ผู้ดูแลระบบของเครื่องนี้ต้องการให้มีการติดตั้ง <ph name="EXTENSION_NAME" /> โดยไม่สามารถลบหรือแก้ไขได้</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (ID ส่วนขยาย "<ph name="EXTENSION_ID" />") ถูกบล็อกโดยผู้ดูแลระบบ</translation>
+<translation id="7972881773422714442">ตัวเลือก: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">ส่วนขยายนี้ล้มเหลวในการตั้งชื่อการดาวน์โหลดว่า "<ph name="ATTEMPTED_FILENAME" />" เนื่องจากอีกส่วนขยายหนึ่ง (<ph name="EXTENSION_NAME" />) ได้กำหนดชื่อไฟล์ที่แตกต่างไว้ว่า "<ph name="ACTUAL_FILENAME" />"</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> จาก <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">ส่วนขยายนี้ไม่สามารถให้ข้อมูลรับรองแก่คำขอเครือข่ายได้เนื่องจากส่วนขยายอื่น (<ph name="EXTENSION_NAME" />) ได้ให้ข้อมูลรับรองซึ่งต่างออกไป</translation>
+<translation id="8602184400052594090">ไฟล์มานิเฟสต์หายไปหรืออ่านไม่ได้</translation>
+<translation id="8636666366616799973">แพ็กเกจไม่ถูกต้อง รายละเอียด: "<ph name="ERROR_MESSAGE" />"</translation>
+<translation id="8670869118777164560">ส่วนขยายนี้ไม่สามารถเปลี่ยนเส้นทางคำขอเครือข่ายไปยัง <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> เนื่องจากส่วนขยายอื่น (<ph name="EXTENSION_NAME" />) ได้เปลี่ยนเส้นทางคำขอไปยัง <ph name="ACTUAL_REDIRECT_DESTINATION" /></translation>
+<translation id="8712265948125780616">ตัวแตกไฟล์ส่วนขยาย</translation>
+<translation id="8825366169884721447">ส่วนขยายนี้ไม่สามารถแก้ไขส่วนหัวคำขอ "<ph name="HEADER_NAME" />" ของคำขอเครือข่ายได้เนื่องจากการแก้ไขขัดแย้งกับส่วนขยายอื่น (<ph name="EXTENSION_NAME" />)</translation>
+<translation id="9111791539553342076">ส่วนขยายนี้ไม่สามารถแก้ไขส่วนหัวการตอบกลับ "<ph name="HEADER_NAME" />" ของคำขอเครือข่ายได้เนื่องจากการแก้ไขขัดแย้งกับส่วนขยายอื่น (<ph name="EXTENSION_NAME" />)</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_tr.xtb b/chromium/extensions/strings/extensions_strings_tr.xtb
new file mode 100644
index 00000000000..1196fb665fe
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_tr.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="tr">
+<translation id="1135328998467923690">Paket geçersiz: '<ph name="ERROR_CODE" />'.</translation>
+<translation id="1256619696651732561">Extension Manifest Parser</translation>
+<translation id="1445572445564823378">Bu uzantı <ph name="PRODUCT_NAME" /> uygulamasını yavaşlatıyor. <ph name="PRODUCT_NAME" /> uygulamasının performansını eski haline getirmek için bu uzantıyı devre dışı bırakmalısınız.</translation>
+<translation id="149347756975725155">'<ph name="ICON" />' uzantı simgesi yüklenemedi.</translation>
+<translation id="1803557475693955505">'<ph name="BACKGROUND_PAGE" />' arka plan sayfası yüklenemedi.</translation>
+<translation id="2159915644201199628">Resmin kodu çözülemedi: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">Yerelleştirme kullanıldı, ancak bildiride default_locale belirtilmedi.</translation>
+<translation id="2753617847762399167">Geçersiz yol (mutlak veya '..' ile göreli): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">Bu uzantı, değişikliğin başka bir uzantıyla çakışması nedeniyle bir ağ isteğini değiştiremedi.</translation>
+<translation id="2857834222104759979">Bildiri dosyası geçersiz.</translation>
+<translation id="2988488679308982380">Paket yüklenemedi: '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089"><ph name="VENDOR_ID" /> adlı satıcı firma tarafından sağlanan <ph name="PRODUCT_ID" /> ürün kimliğine sahip bilinmeyen ürün (seri numarası: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963"><ph name="VENDOR_ID" /> adlı satıcı firma tarafından sağlanan <ph name="PRODUCT_ID" /> ürün kimliğine sahip bilinmeyen ürün</translation>
+<translation id="3369521687965833290">Uzantının paketi açılamıyor. Bir uzantının paketini güvenli bir şekilde açabilmek için profil dizininize, sürücü harfi ile başlayan ve bağlantı, bağlantı noktası veya sembolik bağlantı içermeyen bir yol olmalıdır. Profiliniz için böyle bir yol bulunmuyor.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (seri numarası: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="VENDOR_ID" /> kimliğine sahip üretici firma tarafından sağlanan <ph name="PRODUCT_NAME" /> (seri numarası: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Başlatıcı sayfası "<ph name="PAGE" />" yüklenemedi.</translation>
+<translation id="388442998277590542">'<ph name="OPTIONS_PAGE" />' seçenekler sayfası yüklenemedi.</translation>
+<translation id="4115165561519362854">Bu makinenin yöneticisi, <ph name="EXTENSION_NAME" /> uzantısının en azından <ph name="EXTENSION_VERSION" /> sürümünde olmasını gerektiriyor. Bu sürüme (veya sonraki sürümlere) güncelleninceye kadar uzantı etkinleştirilemez.</translation>
+<translation id="4233778200880751280">Hakkında sayfası ("<ph name="ABOUT_PAGE" />") yüklenemedi.</translation>
+<translation id="4434145631756268951">{0,select, single{Bir USB cihaz seçin}multiple{USB cihazlar seçin}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Bir yardımcı program kilitlendiği için paket yüklenemedi. Chrome'u yeniden başlatın ve tekrar deneyin.</translation>
+<translation id="5026754133087629784">Web görünümü: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Bu uzantıda '<ph name="KEY_PATH" />' anahtar dosyası var. Muhtemelen istediğiniz bu değil.</translation>
+<translation id="5627523580512561598">uzantı: <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Bir HID cihaz seçin}multiple{HID cihazlar seçin}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Bu makinenin yöneticisi <ph name="EXTENSION_NAME" /> adlı uzantının yüklenmesini şart koşuyor. Uzantı kaldırılamaz.</translation>
+<translation id="6027032947578871493"><ph name="VENDOR_NAME" /> tarafından sağlanan <ph name="PRODUCT_ID" /> ürün kimliğine sahip bilinmeyen ürün (seri numarası: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="VENDOR_ID" /> tarafından sağlanan <ph name="PRODUCT_NAME" /></translation>
+<translation id="6143635259298204954">Uzantının paketi açılamıyor. Bir uzantının paketini güvenli bir şekilde açabilmek için profil dizininize sembolik bağlantı içermeyen bir yol olmalıdır. Profiliniz için böyle bir yol bulunmuyor.</translation>
+<translation id="616804573177634438">{0,select, single{"<ph name="APP_NAME" />" uygulaması cihazlarınızdan birine erişim isteğinde bulunuyor.}multiple{"<ph name="APP_NAME" />" uygulaması, cihazlarınızın birine veya daha fazlasına erişim izni istiyor.}other{KULLANILMIYOR}}</translation>
+<translation id="641087317769093025">Uzantı açılamadı</translation>
+<translation id="657064425229075395">Arka plan komut dosyası '<ph name="BACKGROUND_SCRIPT" />' yüklenemedi.</translation>
+<translation id="6580950983454333167">Ürün: <ph name="PRODUCT_NAME" />, Firma: <ph name="VENDOR_NAME" /> (seri numarası: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Açma dizini oluşturulamadı: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">Mimehandler: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Bu uzantı, kendisini yeniden yükleme işlemini çok sık yaptı.</translation>
+<translation id="7003844668372540529"><ph name="VENDOR_NAME" /> tarafından sağlanan <ph name="PRODUCT_ID" /> ürün kimliğine sahip bilinmeyen ürün</translation>
+<translation id="7217838517480956708">Bu makinenin yöneticisi <ph name="EXTENSION_NAME" /> yüklenmesini gerektiriyor. Kaldırılamaz veya değiştirilemez.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (uzantı kimliği "<ph name="EXTENSION_ID" />") yönetici tarafından engellenmiş.</translation>
+<translation id="7972881773422714442">Seçenekler: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Başka bir uzantı (<ph name="EXTENSION_NAME" />) farklı bir dosya adı ("<ph name="ACTUAL_FILENAME" />") belirlediğinden, uzantı indirilen dosyayı "<ph name="ATTEMPTED_FILENAME" />" olarak adlandıramadı.</translation>
+<translation id="8284835137979141223"><ph name="VENDOR_NAME" /> adlı firmadan <ph name="PRODUCT_NAME" /></translation>
+<translation id="8341840687457896278">Başka bir uzantı (<ph name="EXTENSION_NAME" />) farklı kimlik bilgileri sağladığından, bu uzantı bir ağ isteğine kimlik bilgileri sağlayamadı.</translation>
+<translation id="8602184400052594090">Bildiri dosyası eksik veya okunamıyor.</translation>
+<translation id="8636666366616799973">Paket geçersiz. Ayrıntılar: '<ph name="ERROR_MESSAGE" />'</translation>
+<translation id="8670869118777164560">Bu uzantı bir ağ isteğini <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> hedefine yönlendiremedi, çünkü başka bir uzantı (<ph name="EXTENSION_NAME" />) o isteği <ph name="ACTUAL_REDIRECT_DESTINATION" /> hedefine yönlendirmişti.</translation>
+<translation id="8712265948125780616">Uzantı Açıcı</translation>
+<translation id="8825366169884721447">Bu uzantı bir ağ isteğinin "<ph name="HEADER_NAME" />" adlı istek başlığını değiştiremedi, çünkü söz konusu değişiklik başka bir uzantı (<ph name="EXTENSION_NAME" />) ile çakışıyordu.</translation>
+<translation id="9111791539553342076">Bu uzantı bir ağ isteğinin "<ph name="HEADER_NAME" />" adlı yanıt başlığını değiştiremedi, çünkü söz konusu değişiklik başka bir uzantı (<ph name="EXTENSION_NAME" />) ile çakışıyordu.</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_uk.xtb b/chromium/extensions/strings/extensions_strings_uk.xtb
new file mode 100644
index 00000000000..198c88f2383
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_uk.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="uk">
+<translation id="1135328998467923690">Пакет недійсний: "<ph name="ERROR_CODE" />".</translation>
+<translation id="1256619696651732561">Синтаксичний аналізатор маніфесту розширення</translation>
+<translation id="1445572445564823378">Це розширення сповільнює <ph name="PRODUCT_NAME" />. Щоб відновити ефективність <ph name="PRODUCT_NAME" />, потрібно його вимкнути.</translation>
+<translation id="149347756975725155">Не вдалося завантажити піктограму розширення "<ph name="ICON" />".</translation>
+<translation id="1803557475693955505">Не вдалося завантажити фонову сторінку "<ph name="BACKGROUND_PAGE" />".</translation>
+<translation id="2159915644201199628">Не вдалося декодувати зображення: "<ph name="IMAGE_NAME" />"</translation>
+<translation id="2350172092385603347">Використано локалізацію, але параметр мови за умовчанням (default_locale) не визначено в маніфесті.</translation>
+<translation id="2753617847762399167">Заборонений шлях (узагалі або у зв’язку з ".."): "<ph name="IMAGE_PATH" />"</translation>
+<translation id="27822970480436970">Цьому розширенню не вдалося змінити запит мережі, оскільки зміна не сумісна з іншим розширенням.</translation>
+<translation id="2857834222104759979">Файл маніфесту недійсний.</translation>
+<translation id="2988488679308982380">Неможливо встановити пакет: "<ph name="ERROR_CODE" />"</translation>
+<translation id="3115238746683532089">Невідомий продукт <ph name="PRODUCT_ID" /> від постачальника <ph name="VENDOR_ID" /> (серійний номер: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Невідомий продукт <ph name="PRODUCT_ID" /> від постачальника <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Неможливо розпакувати розширення. Щоб безпечно розпакувати розширення, має бути шлях до каталогу вашого профілю, який починається буквою диску та не містить точок з’єднання, під’єднання чи символьного посилання. Для вашого профілю такого шляху немає.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (серійний номер: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> від постачальника <ph name="VENDOR_ID" /> (серійний номер: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Не вдалося завантажити сторінку панелі запуску "<ph name="PAGE" />".</translation>
+<translation id="388442998277590542">Не вдалося завантажити сторінку параметрів "<ph name="OPTIONS_PAGE" />".</translation>
+<translation id="4115165561519362854">Адміністратор цього комп’ютера вимагає встановити розширення <ph name="EXTENSION_NAME" /> принаймні такої версії: <ph name="EXTENSION_VERSION" />. Ви не зможете ввімкнути це розширення, доки не оновите його до цієї версії (або новішої).</translation>
+<translation id="4233778200880751280">Не вдалося завантажити сторінку з інформацією про "<ph name="ABOUT_PAGE" />".</translation>
+<translation id="4434145631756268951">{0,select, single{Вибрати пристрій USB}multiple{Вибрати пристрої USB}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Не вдалося встановити пакет, оскільки відбувся збій процесу службової програми. Перезапустіть Chrome і повторіть спробу.</translation>
+<translation id="5026754133087629784">Веб-перегляд: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Тег Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Це розширення включає файл ключа "<ph name="KEY_PATH" />". Можливо, ви не хочете цього робити.</translation>
+<translation id="5627523580512561598">розширення <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Вибрати пристрій HID}multiple{Вибрати пристрої HID}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Адміністратор цього комп’ютера вимагає встановити розширення <ph name="EXTENSION_NAME" />. Його неможливо видалити.</translation>
+<translation id="6027032947578871493">Невідомий продукт <ph name="PRODUCT_ID" /> від постачальника <ph name="VENDOR_NAME" /> (серійний номер: <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> від постачальника <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Неможливо розпакувати розширення. Щоб безпечно розпакувати розширення, має бути шлях до каталогу вашого профілю, який не містить символьного посилання. Для вашого профілю такого шляху немає.</translation>
+<translation id="616804573177634438">{0,select, single{Додаток <ph name="APP_NAME" /> запитує доступ до одного з ваших пристроїв.}multiple{Додаток <ph name="APP_NAME" /> запитує доступ до одного чи декількох ваших пристроїв.}other{UNUSED}}</translation>
+<translation id="641087317769093025">Не вдалося розпакувати розширення</translation>
+<translation id="657064425229075395">Не вдалося завантажити фоновий сценарій "<ph name="BACKGROUND_SCRIPT" />".</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> від постачальника <ph name="VENDOR_NAME" /> (серійний номер <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Не вдалося створити каталог для розпакування: "<ph name="DIRECTORY_PATH" />"</translation>
+<translation id="677806580227005219">Обробник MIME: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Це розширення перезавантажувалося надто часто.</translation>
+<translation id="7003844668372540529">Невідомий продукт <ph name="PRODUCT_ID" /> від постачальника <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Адміністратор цього комп’ютера потребує встановленого розширення <ph name="EXTENSION_NAME" />. Його не можна видаляти чи змінювати.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (ідентифікатор розширення "<ph name="EXTENSION_ID" />") заблоковано адміністратором.</translation>
+<translation id="7972881773422714442">Параметри: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Цьому розширенню не вдалося назвати завантаження "<ph name="ATTEMPTED_FILENAME" />", оскільки інше розширення (<ph name="EXTENSION_NAME" />) визначило іншу назву файлу "<ph name="ACTUAL_FILENAME" />".</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> від постачальника <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Це розширення не надало облікові дані на запит мережі, оскільки інше розширення (<ph name="EXTENSION_NAME" />) надало інші облікові дані.</translation>
+<translation id="8602184400052594090">Файл маніфесту відсутній або його не можна розпізнати.</translation>
+<translation id="8636666366616799973">Пакет недійсний. Деталі: "<ph name="ERROR_MESSAGE" />".</translation>
+<translation id="8670869118777164560">Це розширення не переспрямувало запит мережі на сторінку <ph name="ATTEMPTED_REDIRECT_DESTINATION" />, оскільки інше розширення (<ph name="EXTENSION_NAME" />) переспрямувало його на сторінку <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Засіб розпакування розширень</translation>
+<translation id="8825366169884721447">Це розширення не змінило заголовок запиту "<ph name="HEADER_NAME" />" в запиті мережі, оскільки виник конфлікт з іншим розширенням (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Це розширення не змінило заголовок відповіді "<ph name="HEADER_NAME" />" в запиті мережі, оскільки виник конфлікт з іншим розширенням (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_vi.xtb b/chromium/extensions/strings/extensions_strings_vi.xtb
new file mode 100644
index 00000000000..2cde5d64dc6
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_vi.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="vi">
+<translation id="1135328998467923690">Gói không hợp lệ: '<ph name="ERROR_CODE" />'.</translation>
+<translation id="1256619696651732561">Trình phân tích cú pháp tệp kê khai tiện ích</translation>
+<translation id="1445572445564823378">Tiện ích này đang làm chậm <ph name="PRODUCT_NAME" />. Bạn phải tắt tiện ích để khôi phục hiệu suất của <ph name="PRODUCT_NAME" />.</translation>
+<translation id="149347756975725155">Không thể tải biểu tượng tiện ích '<ph name="ICON" />'.</translation>
+<translation id="1803557475693955505">Không thể tải trang nền '<ph name="BACKGROUND_PAGE" />'.</translation>
+<translation id="2159915644201199628">Không thể giải mã hình ảnh: '<ph name="IMAGE_NAME" />'</translation>
+<translation id="2350172092385603347">Sử dụng bản địa hóa nhưng không chỉ định default_locale trong tệp kê khai.</translation>
+<translation id="2753617847762399167">Đường dẫn không hợp lệ (tuyệt đối hoặc tương đối với '..'): '<ph name="IMAGE_PATH" />'</translation>
+<translation id="27822970480436970">Tiện ích này không thể sửa đổi yêu cầu mạng vì việc sửa đổi xung đột với một tiện ích khác.</translation>
+<translation id="2857834222104759979">Tệp kê khai không hợp lệ.</translation>
+<translation id="2988488679308982380">Không thể cài đặt gói: '<ph name="ERROR_CODE" />'</translation>
+<translation id="3115238746683532089">Sản phẩm không xác định <ph name="PRODUCT_ID" /> từ nhà cung cấp <ph name="VENDOR_ID" /> (số sê-ri <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">Sản phẩm không xác định <ph name="PRODUCT_ID" /> từ nhà cung cấp <ph name="VENDOR_ID" /></translation>
+<translation id="3369521687965833290">Không thể giải nén tiện ích. Để giải nén tiện ích một cách an toàn, phải có đường dẫn đến thư mục hồ sơ của bạn bắt đầu bằng ký tự ổ đĩa và không chứa ký tự liên kết, điểm lắp hoặc liên kết dạng biểu tượng. Không có đường dẫn như vậy tồn tại cho hồ sơ của bạn.</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (số sê-ri <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397"><ph name="PRODUCT_NAME" /> từ nhà cung cấp <ph name="VENDOR_ID" /> (số sê-ri <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">Không thể tải trang trình chạy '<ph name="PAGE" />'.</translation>
+<translation id="388442998277590542">Không thể tải trang tùy chọn '<ph name="OPTIONS_PAGE" />'.</translation>
+<translation id="4115165561519362854">Quản trị viên của máy này yêu cầu <ph name="EXTENSION_NAME" /> có phiên bản tối thiểu của <ph name="EXTENSION_VERSION" />. Không thể kích hoạt phiên bản cho đến khi máy cập nhật lên phiên bản đó (hoặc phiên bản cao hơn).</translation>
+<translation id="4233778200880751280">Không thể tải trang giới thiệu '<ph name="ABOUT_PAGE" />'.</translation>
+<translation id="4434145631756268951">{0,select, single{Chọn thiết bị USB}multiple{Chọn thiết bị USB}other{UNUSED}}</translation>
+<translation id="4811956658694082538">Không thể cài đặt gói vì một quá trình tiện ích bị lỗi. Thử khởi động lại Chrome rồi thử lại.</translation>
+<translation id="5026754133087629784">Chế độ xem web: <ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview: <ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">Tiện ích này bao gồm tệp khóa '<ph name="KEY_PATH" />'. Bạn có thể không muốn thực hiện việc đó.</translation>
+<translation id="5627523580512561598">tiện ích <ph name="EXTENSION_NAME" /></translation>
+<translation id="5630931906013276297">{0,select, single{Chọn thiết bị HID}multiple{Chọn thiết bị HID}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView: <ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">Quản trị viên của máy này yêu cầu cài đặt <ph name="EXTENSION_NAME" />. Không thể gỡ cài đặt tiện ích này.</translation>
+<translation id="6027032947578871493">Sản phẩm không xác định <ph name="PRODUCT_ID" /> từ <ph name="VENDOR_NAME" /> (số sê-ri <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721"><ph name="PRODUCT_NAME" /> từ nhà cung cấp <ph name="VENDOR_ID" /></translation>
+<translation id="6143635259298204954">Không thể giải nén tiện ích. Để giải nén tiện ích một cách an toàn, phải có đường dẫn đến thư mục hồ sơ không chứa liên kết dạng biểu tượng của bạn. Không có đường dẫn như vậy tồn tại cho hồ sơ của bạn.</translation>
+<translation id="616804573177634438">{0,select, single{Ứng dụng "<ph name="APP_NAME" />" đang yêu cầu quyền truy cập vào một trong các thiết bị của bạn.}multiple{Ứng dụng "<ph name="APP_NAME" />" đang yêu cầu quyền truy cập vào một hoặc nhiều thiết bị của bạn.}other{UNUSED}}</translation>
+<translation id="641087317769093025">Không thể giải nén tiện ích</translation>
+<translation id="657064425229075395">Không thể tải tập lệnh nền '<ph name="BACKGROUND_SCRIPT" />'.</translation>
+<translation id="6580950983454333167"><ph name="PRODUCT_NAME" /> của <ph name="VENDOR_NAME" /> (số sê-ri <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">Không thể tạo thư mục để giải nén: '<ph name="DIRECTORY_PATH" />'</translation>
+<translation id="677806580227005219">Mimehandler: <ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">Phần mở rộng này đã được tải lại quá thường xuyên.</translation>
+<translation id="7003844668372540529">Sản phẩm không xác định <ph name="PRODUCT_ID" /> từ <ph name="VENDOR_NAME" /></translation>
+<translation id="7217838517480956708">Quản trị viên của máy này yêu cầu cài đặt <ph name="EXTENSION_NAME" />. Bạn không thể xóa hoặc sửa đổi tiện ích này.</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" /> (ID tiện ích "<ph name="EXTENSION_ID" />") bị quản trị viên chặn.</translation>
+<translation id="7972881773422714442">Tùy chọn: <ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">Tiện ích này không thể đặt tên cho bản tải xuống "<ph name="ATTEMPTED_FILENAME" />" vì một tiện ích khác (<ph name="EXTENSION_NAME" />) đã xác định tên tệp khác "<ph name="ACTUAL_FILENAME" />".</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> từ <ph name="VENDOR_NAME" /></translation>
+<translation id="8341840687457896278">Tiện ích này không thể cung cấp bằng chứng xác thực cho một yêu cầu mạng do tiện ích khác (<ph name="EXTENSION_NAME" />) đã cung cấp các bằng chứng xác thực khác nhau.</translation>
+<translation id="8602184400052594090">Tệp kê khai bị thiếu hoặc không thể đọc được.</translation>
+<translation id="8636666366616799973">Gói không hợp lệ. Chi tiết: '<ph name="ERROR_MESSAGE" />'.</translation>
+<translation id="8670869118777164560">Tiện ích này không thể chuyển hướng yêu cầu mạng đến <ph name="ATTEMPTED_REDIRECT_DESTINATION" /> do tiện ích khác (<ph name="EXTENSION_NAME" />) đã chuyển hướng yêu cầu mạng này đến <ph name="ACTUAL_REDIRECT_DESTINATION" />.</translation>
+<translation id="8712265948125780616">Trình giải nén tiện ích</translation>
+<translation id="8825366169884721447">Tiện ích này không thể sửa đổi tiêu đề yêu cầu "<ph name="HEADER_NAME" />" của một yêu cầu mạng do sửa đổi đã xung đột với một tiện ích khác (<ph name="EXTENSION_NAME" />).</translation>
+<translation id="9111791539553342076">Tiện ích này không thể sửa đổi tiêu đề phản hồi "<ph name="HEADER_NAME" />" của một yêu cầu mạng do sửa đổi đã xung đột với một tiện ích khác (<ph name="EXTENSION_NAME" />).</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_zh-CN.xtb b/chromium/extensions/strings/extensions_strings_zh-CN.xtb
new file mode 100644
index 00000000000..b9b3f78dbc7
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_zh-CN.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="zh-CN">
+<translation id="1135328998467923690">程序包无效:“<ph name="ERROR_CODE" />”。</translation>
+<translation id="1256619696651732561">扩展程序清单解析器</translation>
+<translation id="1445572445564823378">此扩展程序拖慢了 <ph name="PRODUCT_NAME" />的运行速度。您应将其停用,以恢复 <ph name="PRODUCT_NAME" />的性能。</translation>
+<translation id="149347756975725155">无法加载扩展程序图标“<ph name="ICON" />”。</translation>
+<translation id="1803557475693955505">无法加载背景页“<ph name="BACKGROUND_PAGE" />”。</translation>
+<translation id="2159915644201199628">无法对图片解码:“<ph name="IMAGE_NAME" />”</translation>
+<translation id="2350172092385603347">已使用本地化功能,但未在清单中指定 default_locale。</translation>
+<translation id="2753617847762399167">非法路径(具有“..”的绝对或相对路径):“<ph name="IMAGE_PATH" />”</translation>
+<translation id="27822970480436970">此扩展程序无法修改网络请求,因为这一修改与其他扩展程序产生了冲突。</translation>
+<translation id="2857834222104759979">清单文件无效。</translation>
+<translation id="2988488679308982380">无法安装程序包:“<ph name="ERROR_CODE" />”</translation>
+<translation id="3115238746683532089">来自供应商 <ph name="VENDOR_ID" /> 的未知产品 <ph name="PRODUCT_ID" />(序列号为 <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">来自供应商 <ph name="VENDOR_ID" /> 的未知产品 <ph name="PRODUCT_ID" /></translation>
+<translation id="3369521687965833290">无法将扩展程序解包。要安全地将扩展程序解包,您的个人资料目录中的解包路径必须以驱动器号开头,并且不能包含交接点、装入点或符号链接。您的个人资料中不存在这样的路径。</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" />(序列号为 <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397">来自供应商 <ph name="VENDOR_ID" /> 的<ph name="PRODUCT_NAME" />(序列号为 <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">无法加载启动器页面“<ph name="PAGE" />”。</translation>
+<translation id="388442998277590542">无法加载选项页“<ph name="OPTIONS_PAGE" />”。</translation>
+<translation id="4115165561519362854">此设备的管理员要求“<ph name="EXTENSION_NAME" />”的最低版本为 <ph name="EXTENSION_VERSION" />。此扩展程序必须更新到该版本(或更高版本)后才能启用。</translation>
+<translation id="4233778200880751280">无法加载简介页面“<ph name="ABOUT_PAGE" />”。</translation>
+<translation id="4434145631756268951">{0,select, single{选择 1 部 USB 设备}multiple{选择多部 USB 设备}other{UNUSED}}</translation>
+<translation id="4811956658694082538">某个实用程序进程崩溃了,因此软件包无法安装。请重新启动 Chrome,然后重试。</translation>
+<translation id="5026754133087629784">网页视图:<ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">应用视图:<ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">此扩展程序包含密钥文件“<ph name="KEY_PATH" />”,您最好不要执行此操作。</translation>
+<translation id="5627523580512561598">扩展程序“<ph name="EXTENSION_NAME" />”</translation>
+<translation id="5630931906013276297">{0,select, single{选择 1 部 HID 设备}multiple{选择多部 HID 设备}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView:<ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">此设备的管理员要求安装“<ph name="EXTENSION_NAME" />”,因此不能卸载该扩展程序。</translation>
+<translation id="6027032947578871493">来自<ph name="VENDOR_NAME" />的未知产品 <ph name="PRODUCT_ID" />(序列号为 <ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721">来自供应商 <ph name="VENDOR_ID" /> 的<ph name="PRODUCT_NAME" /></translation>
+<translation id="6143635259298204954">无法将扩展程序解包。要安全地将扩展程序解包,您的个人资料目录中的解包路径不得包含符号链接。您的个人资料中不存在这样的路径。</translation>
+<translation id="616804573177634438">{0,select, single{“<ph name="APP_NAME" />”应用请求访问您的某台设备。}multiple{“<ph name="APP_NAME" />”应用请求访问您的一台或多台设备。}other{UNUSED}}</translation>
+<translation id="641087317769093025">无法解压缩扩展程序</translation>
+<translation id="657064425229075395">无法加载背景脚本“<ph name="BACKGROUND_SCRIPT" />”。</translation>
+<translation id="6580950983454333167"><ph name="VENDOR_NAME" />提供的<ph name="PRODUCT_NAME" />(序列号:<ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">无法创建解压缩目录:“<ph name="DIRECTORY_PATH" />”</translation>
+<translation id="677806580227005219">MIME 处理程序:<ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">此扩展程序自行重新加载的频率过高。</translation>
+<translation id="7003844668372540529">来自<ph name="VENDOR_NAME" />的未知产品 <ph name="PRODUCT_ID" /></translation>
+<translation id="7217838517480956708">此计算机的管理员要求安装 <ph name="EXTENSION_NAME" />。该扩展程序无法删除或修改。</translation>
+<translation id="7809034755304591547"><ph name="EXTENSION_NAME" />(扩展程序 ID为“<ph name="EXTENSION_ID" />”)已被管理员阻止。</translation>
+<translation id="7972881773422714442">选项:<ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">此扩展程序无法将下载的文件命名为“<ph name="ATTEMPTED_FILENAME" />”,因为另一扩展程序 (<ph name="EXTENSION_NAME" />) 已将此文件命名为“<ph name="ACTUAL_FILENAME" />”。</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" />(<ph name="VENDOR_NAME" />)</translation>
+<translation id="8341840687457896278">此扩展程序无法向网络请求提供凭据,因为另一个扩展程序(<ph name="EXTENSION_NAME" />)已提供了不同的凭据。</translation>
+<translation id="8602184400052594090">清单文件缺失或不可读。</translation>
+<translation id="8636666366616799973">程序包无效。详细信息:“<ph name="ERROR_MESSAGE" />”。</translation>
+<translation id="8670869118777164560">此扩展程序无法将该网络请求重新定向到 <ph name="ATTEMPTED_REDIRECT_DESTINATION" />,因为其他扩展程序(<ph name="EXTENSION_NAME" />)已将其重新定向到 <ph name="ACTUAL_REDIRECT_DESTINATION" />。</translation>
+<translation id="8712265948125780616">扩展程序解压缩程序</translation>
+<translation id="8825366169884721447">此扩展程序无法修改网络请求的请求标头“<ph name="HEADER_NAME" />”,因为这一修改与另一个扩展程序(<ph name="EXTENSION_NAME" />)产生了冲突。</translation>
+<translation id="9111791539553342076">此扩展程序无法修改网络请求的响应标头“<ph name="HEADER_NAME" />”,因为这一修改与另一个扩展程序(<ph name="EXTENSION_NAME" />)产生了冲突。</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/strings/extensions_strings_zh-TW.xtb b/chromium/extensions/strings/extensions_strings_zh-TW.xtb
new file mode 100644
index 00000000000..f96b0ed0640
--- /dev/null
+++ b/chromium/extensions/strings/extensions_strings_zh-TW.xtb
@@ -0,0 +1,56 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="zh-TW">
+<translation id="1135328998467923690">套件無效:<ph name="ERROR_CODE" />。</translation>
+<translation id="1256619696651732561">擴充功能資訊清單剖析器</translation>
+<translation id="1445572445564823378">這個擴充功能使得 <ph name="PRODUCT_NAME" /> 運作變慢。建議您停用這個擴充功能,以恢復 <ph name="PRODUCT_NAME" /> 的效能。</translation>
+<translation id="149347756975725155">無法載入擴充功能圖示「<ph name="ICON" />」。</translation>
+<translation id="1803557475693955505">無法載入背景頁面「<ph name="BACKGROUND_PAGE" />」。</translation>
+<translation id="2159915644201199628">無法將圖片解碼:「<ph name="IMAGE_NAME" />」</translation>
+<translation id="2350172092385603347">已使用語言代碼,但是仍未在資訊清單中指定 default_locale。</translation>
+<translation id="2753617847762399167">路徑無效 (絕對路徑或「..」相對路徑):「<ph name="IMAGE_PATH" />」</translation>
+<translation id="27822970480436970">這個擴充功能無法修改網路要求,因為執行修改會與其他擴充功能相衝突。</translation>
+<translation id="2857834222104759979">資訊清單檔案無效。</translation>
+<translation id="2988488679308982380">無法安裝套件:<ph name="ERROR_CODE" /></translation>
+<translation id="3115238746683532089">供應商 <ph name="VENDOR_ID" /> 提供的不明產品 <ph name="PRODUCT_ID" /> (序號:<ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3163201441334626963">供應商 <ph name="VENDOR_ID" /> 提供的不明產品 <ph name="PRODUCT_ID" /></translation>
+<translation id="3369521687965833290">無法為擴充功能解除封裝。如要安全解除封裝,設定檔目錄的路徑必須以磁碟代號開頭,且不能包含連接點、掛載點或符號連結。您的設定檔中沒有符合條件的路徑。</translation>
+<translation id="3393440416772303020"><ph name="PRODUCT_NAME" /> (序號:<ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3466070586188012397">供應商 <ph name="VENDOR_ID" /> 提供的 <ph name="PRODUCT_NAME" /> (序號:<ph name="SERIAL_NUMBER" />)</translation>
+<translation id="3624204664103857160">無法載入啟動器頁面「<ph name="PAGE" />」。</translation>
+<translation id="388442998277590542">無法載入「<ph name="OPTIONS_PAGE" />」選項頁面。</translation>
+<translation id="4115165561519362854">這個裝置的管理員要求的 <ph name="EXTENSION_NAME" /> 最低版本為 <ph name="EXTENSION_VERSION" />。更新至該版本 (或以上版本) 後,才能啟用這項擴充功能。</translation>
+<translation id="4233778200880751280">無法載入簡介網頁「<ph name="ABOUT_PAGE" />」。</translation>
+<translation id="4434145631756268951">{0,select, single{選取一個 USB 裝置}multiple{選取多個 USB 裝置}other{UNUSED}}</translation>
+<translation id="4811956658694082538">公用程式處理程序當機,因此無法安裝套件。請重新啟動 Chrome,然後再試一次。</translation>
+<translation id="5026754133087629784">Webview:<ph name="WEBVIEW_TAG_NAME" /></translation>
+<translation id="5356315618422219272">Appview:<ph name="APPVIEW_TAG_NAME" /></translation>
+<translation id="5456409301717116725">這個擴充功能含有金鑰檔「<ph name="KEY_PATH" />」。您不妨重新考慮是否仍要進行。</translation>
+<translation id="5627523580512561598"><ph name="EXTENSION_NAME" /> 擴充功能</translation>
+<translation id="5630931906013276297">{0,select, single{選取一個 HID 裝置}multiple{選取多個 HID 裝置}other{UNUSED}}</translation>
+<translation id="5960890139610307736">ExtensionView:<ph name="EXTENSIONVIEW_TAG_NAME" /></translation>
+<translation id="5972529113578162692">這個裝置的管理員要求安裝「<ph name="EXTENSION_NAME" />」,因此無法解除安裝這個擴充功能。</translation>
+<translation id="6027032947578871493"><ph name="VENDOR_NAME" /> 提供的不明產品 <ph name="PRODUCT_ID" /> (序號:<ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6068932090455285721">供應商 <ph name="VENDOR_ID" /> 提供的 <ph name="PRODUCT_NAME" /></translation>
+<translation id="6143635259298204954">無法為擴充功能解除封裝。如要安全解除封裝,設定檔目錄的路徑不能包含符號連結。您的設定檔中沒有符合條件的路徑。</translation>
+<translation id="616804573177634438">{0,select, single{「<ph name="APP_NAME" />」應用程式要求存取您的其中一個裝置。}multiple{「<ph name="APP_NAME" />」應用程式要求存取您的一或多個裝置。}other{UNUSED}}</translation>
+<translation id="641087317769093025">無法將擴充功能解壓縮</translation>
+<translation id="657064425229075395">無法載入背景指令碼「<ph name="BACKGROUND_SCRIPT" />」。</translation>
+<translation id="6580950983454333167"><ph name="VENDOR_NAME" /> 的 <ph name="PRODUCT_NAME" /> (序號:<ph name="SERIAL_NUMBER" />)</translation>
+<translation id="6731255991101203740">無法建立解壓縮目錄:「<ph name="DIRECTORY_PATH" />」</translation>
+<translation id="677806580227005219">MIME 處理常式:<ph name="MIMEHANDLERVIEW_TAG_NAME" /></translation>
+<translation id="6840444547062817500">這項擴充功能自動重新載入的頻率過高。</translation>
+<translation id="7003844668372540529"><ph name="VENDOR_NAME" /> 提供的不明產品 <ph name="PRODUCT_ID" /></translation>
+<translation id="7217838517480956708">這台電腦的管理員要求安裝 <ph name="EXTENSION_NAME" />,因此您無法移除或修改它。</translation>
+<translation id="7809034755304591547">管理員已封鎖 <ph name="EXTENSION_NAME" /> (擴充功能 ID「<ph name="EXTENSION_ID" />」)。</translation>
+<translation id="7972881773422714442">選項:<ph name="EXTENSIONOPTIONS_TAG_NAME" /></translation>
+<translation id="8047248493720652249">擴充功能無法將下載的檔案命名為「<ph name="ATTEMPTED_FILENAME" />」,因為其他擴充功能 (<ph name="EXTENSION_NAME" />) 已為將此檔案命名為「<ph name="ACTUAL_FILENAME" />」。</translation>
+<translation id="8284835137979141223"><ph name="PRODUCT_NAME" /> (供應商:<ph name="VENDOR_NAME" />)</translation>
+<translation id="8341840687457896278">這個擴充功能無法向網路要求提供憑證,因為其他擴充功能 (<ph name="EXTENSION_NAME" />) 已提供不同的憑證。</translation>
+<translation id="8602184400052594090">資訊清單檔案遺失或無法讀取。</translation>
+<translation id="8636666366616799973">套件無效,詳細資料:<ph name="ERROR_MESSAGE" />。</translation>
+<translation id="8670869118777164560">這個擴充功能無法將網路要求重新導向至 <ph name="ATTEMPTED_REDIRECT_DESTINATION" />,因為其他擴充功能 (<ph name="EXTENSION_NAME" />) 已將要求重新導向至 <ph name="ACTUAL_REDIRECT_DESTINATION" />。</translation>
+<translation id="8712265948125780616">擴充功能解壓縮工具</translation>
+<translation id="8825366169884721447">這個擴充功能無法修改網路要求的「<ph name="HEADER_NAME" />」要求標頭,因為修改動作與其他擴充功能 (<ph name="EXTENSION_NAME" />) 發生衝突。</translation>
+<translation id="9111791539553342076">這個擴充功能無法修改網路要求的「<ph name="HEADER_NAME" />」回應標頭,因為修改動作與其他擴充功能 (<ph name="EXTENSION_NAME" />) 發生衝突。</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/extensions/utility/BUILD.gn b/chromium/extensions/utility/BUILD.gn
new file mode 100644
index 00000000000..5f9923a5eda
--- /dev/null
+++ b/chromium/extensions/utility/BUILD.gn
@@ -0,0 +1,25 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/features.gni")
+import("//extensions/extensions.gni")
+
+assert(enable_extensions)
+
+# GYP version: extensions/extensions.gyp:extensions_utility
+source_set("utility") {
+ sources = rebase_path(extensions_gypi_values.extensions_utility_sources,
+ ".",
+ "//extensions")
+
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ configs += [ "//build/config/compiler:no_size_t_to_int_warning" ]
+
+ deps = [
+ "//content/public/common",
+ "//content/public/utility",
+ "//extensions/common",
+ "//skia",
+ ]
+}
diff --git a/chromium/extensions/utility/DEPS b/chromium/extensions/utility/DEPS
new file mode 100644
index 00000000000..48d2f8d5f2e
--- /dev/null
+++ b/chromium/extensions/utility/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+content/public/utility",
+ "+content/public/child",
+ "+net",
+ "+third_party/zlib/google",
+]
diff --git a/chromium/extensions/utility/unpacker.cc b/chromium/extensions/utility/unpacker.cc
new file mode 100644
index 00000000000..837a0d8a7bb
--- /dev/null
+++ b/chromium/extensions/utility/unpacker.cc
@@ -0,0 +1,298 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/utility/unpacker.h"
+
+#include <stddef.h>
+
+#include <set>
+#include <tuple>
+#include <utility>
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/i18n/rtl.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread.h"
+#include "base/values.h"
+#include "content/public/child/image_decoder_utils.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_l10n_util.h"
+#include "extensions/common/extension_utility_messages.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/file_util.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/default_locale_handler.h"
+#include "extensions/strings/grit/extensions_strings.h"
+#include "ipc/ipc_message_utils.h"
+#include "net/base/file_stream.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace extensions {
+
+namespace {
+
+namespace errors = manifest_errors;
+namespace keys = manifest_keys;
+
+// A limit to stop us passing dangerously large canvases to the browser.
+const int kMaxImageCanvas = 4096 * 4096;
+
+SkBitmap DecodeImage(const base::FilePath& path) {
+ // Read the file from disk.
+ std::string file_contents;
+ if (!base::PathExists(path) ||
+ !base::ReadFileToString(path, &file_contents)) {
+ return SkBitmap();
+ }
+
+ // Decode the image using WebKit's image decoder.
+ const unsigned char* data =
+ reinterpret_cast<const unsigned char*>(file_contents.data());
+ SkBitmap bitmap =
+ content::DecodeImage(data, gfx::Size(), file_contents.length());
+ if (bitmap.computeSize64() > kMaxImageCanvas)
+ return SkBitmap();
+ return bitmap;
+}
+
+bool PathContainsParentDirectory(const base::FilePath& path) {
+ const base::FilePath::StringType kSeparators(base::FilePath::kSeparators);
+ const base::FilePath::StringType kParentDirectory(
+ base::FilePath::kParentDirectory);
+ const size_t npos = base::FilePath::StringType::npos;
+ const base::FilePath::StringType& value = path.value();
+
+ for (size_t i = 0; i < value.length();) {
+ i = value.find(kParentDirectory, i);
+ if (i != npos) {
+ if ((i == 0 || kSeparators.find(value[i - 1]) == npos) &&
+ (i + 1 < value.length() || kSeparators.find(value[i + 1]) == npos)) {
+ return true;
+ }
+ ++i;
+ }
+ }
+
+ return false;
+}
+
+bool WritePickle(const IPC::Message& pickle, const base::FilePath& dest_path) {
+ int size = base::checked_cast<int>(pickle.size());
+ const char* data = static_cast<const char*>(pickle.data());
+ int bytes_written = base::WriteFile(dest_path, data, size);
+ return (bytes_written == size);
+}
+
+} // namespace
+
+struct Unpacker::InternalData {
+ DecodedImages decoded_images;
+};
+
+Unpacker::Unpacker(const base::FilePath& working_dir,
+ const base::FilePath& extension_dir,
+ const std::string& extension_id,
+ Manifest::Location location,
+ int creation_flags)
+ : working_dir_(working_dir),
+ extension_dir_(extension_dir),
+ extension_id_(extension_id),
+ location_(location),
+ creation_flags_(creation_flags) {
+ internal_data_.reset(new InternalData());
+}
+
+Unpacker::~Unpacker() {
+}
+
+scoped_ptr<base::DictionaryValue> Unpacker::ReadManifest() {
+ base::FilePath manifest_path = extension_dir_.Append(kManifestFilename);
+ if (!base::PathExists(manifest_path)) {
+ SetError(errors::kInvalidManifest);
+ return NULL;
+ }
+
+ JSONFileValueDeserializer deserializer(manifest_path);
+ std::string error;
+ scoped_ptr<base::Value> root = deserializer.Deserialize(NULL, &error);
+ if (!root.get()) {
+ SetError(error);
+ return NULL;
+ }
+
+ if (!root->IsType(base::Value::TYPE_DICTIONARY)) {
+ SetError(errors::kInvalidManifest);
+ return NULL;
+ }
+
+ return base::DictionaryValue::From(std::move(root));
+}
+
+bool Unpacker::ReadAllMessageCatalogs(const std::string& default_locale) {
+ base::FilePath locales_path = extension_dir_.Append(kLocaleFolder);
+
+ // Not all folders under _locales have to be valid locales.
+ base::FileEnumerator locales(locales_path, false,
+ base::FileEnumerator::DIRECTORIES);
+
+ std::set<std::string> all_locales;
+ extension_l10n_util::GetAllLocales(&all_locales);
+ base::FilePath locale_path;
+ while (!(locale_path = locales.Next()).empty()) {
+ if (extension_l10n_util::ShouldSkipValidation(locales_path, locale_path,
+ all_locales))
+ continue;
+
+ base::FilePath messages_path = locale_path.Append(kMessagesFilename);
+
+ if (!ReadMessageCatalog(messages_path))
+ return false;
+ }
+
+ return true;
+}
+
+bool Unpacker::Run() {
+ // Parse the manifest.
+ parsed_manifest_ = ReadManifest();
+ if (!parsed_manifest_.get())
+ return false; // Error was already reported.
+
+ std::string error;
+ scoped_refptr<Extension> extension(
+ Extension::Create(extension_dir_, location_, *parsed_manifest_,
+ creation_flags_, extension_id_, &error));
+ if (!extension.get()) {
+ SetError(error);
+ return false;
+ }
+
+ std::vector<InstallWarning> warnings;
+ if (!file_util::ValidateExtension(extension.get(), &error, &warnings)) {
+ SetError(error);
+ return false;
+ }
+ extension->AddInstallWarnings(warnings);
+
+ // Decode any images that the browser needs to display.
+ std::set<base::FilePath> image_paths =
+ ExtensionsClient::Get()->GetBrowserImagePaths(extension.get());
+ for (const base::FilePath& path : image_paths) {
+ if (!AddDecodedImage(path))
+ return false; // Error was already reported.
+ }
+
+ // Parse all message catalogs (if any).
+ parsed_catalogs_.reset(new base::DictionaryValue);
+ if (!LocaleInfo::GetDefaultLocale(extension.get()).empty()) {
+ if (!ReadAllMessageCatalogs(LocaleInfo::GetDefaultLocale(extension.get())))
+ return false; // Error was already reported.
+ }
+
+ return DumpImagesToFile() && DumpMessageCatalogsToFile();
+}
+
+bool Unpacker::DumpImagesToFile() {
+ IPC::Message pickle; // We use a Message so we can use WriteParam.
+ IPC::WriteParam(&pickle, internal_data_->decoded_images);
+
+ base::FilePath path = working_dir_.AppendASCII(kDecodedImagesFilename);
+ if (!WritePickle(pickle, path)) {
+ SetError("Could not write image data to disk.");
+ return false;
+ }
+
+ return true;
+}
+
+bool Unpacker::DumpMessageCatalogsToFile() {
+ IPC::Message pickle;
+ IPC::WriteParam(&pickle, *parsed_catalogs_.get());
+
+ base::FilePath path =
+ working_dir_.AppendASCII(kDecodedMessageCatalogsFilename);
+ if (!WritePickle(pickle, path)) {
+ SetError("Could not write message catalogs to disk.");
+ return false;
+ }
+
+ return true;
+}
+
+bool Unpacker::AddDecodedImage(const base::FilePath& path) {
+ // Make sure it's not referencing a file outside the extension's subdir.
+ if (path.IsAbsolute() || PathContainsParentDirectory(path)) {
+ SetUTF16Error(l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_PACKAGE_IMAGE_PATH_ERROR,
+ base::i18n::GetDisplayStringInLTRDirectionality(
+ path.LossyDisplayName())));
+ return false;
+ }
+
+ SkBitmap image_bitmap = DecodeImage(extension_dir_.Append(path));
+ if (image_bitmap.isNull()) {
+ SetUTF16Error(l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_PACKAGE_IMAGE_ERROR,
+ base::i18n::GetDisplayStringInLTRDirectionality(
+ path.BaseName().LossyDisplayName())));
+ return false;
+ }
+
+ internal_data_->decoded_images.push_back(std::make_tuple(image_bitmap, path));
+ return true;
+}
+
+bool Unpacker::ReadMessageCatalog(const base::FilePath& message_path) {
+ std::string error;
+ JSONFileValueDeserializer deserializer(message_path);
+ scoped_ptr<base::DictionaryValue> root =
+ base::DictionaryValue::From(deserializer.Deserialize(NULL, &error));
+ if (!root.get()) {
+ base::string16 messages_file = message_path.LossyDisplayName();
+ if (error.empty()) {
+ // If file is missing, Deserialize will fail with empty error.
+ SetError(base::StringPrintf("%s %s", errors::kLocalesMessagesFileMissing,
+ base::UTF16ToUTF8(messages_file).c_str()));
+ } else {
+ SetError(base::StringPrintf(
+ "%s: %s", base::UTF16ToUTF8(messages_file).c_str(), error.c_str()));
+ }
+ return false;
+ }
+
+ base::FilePath relative_path;
+ // message_path was created from temp_install_dir. This should never fail.
+ if (!extension_dir_.AppendRelativePath(message_path, &relative_path)) {
+ NOTREACHED();
+ return false;
+ }
+
+ std::string dir_name = relative_path.DirName().MaybeAsASCII();
+ if (dir_name.empty()) {
+ NOTREACHED();
+ return false;
+ }
+ parsed_catalogs_->Set(dir_name, root.release());
+
+ return true;
+}
+
+void Unpacker::SetError(const std::string& error) {
+ SetUTF16Error(base::UTF8ToUTF16(error));
+}
+
+void Unpacker::SetUTF16Error(const base::string16& error) {
+ error_message_ = error;
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/utility/unpacker.h b/chromium/extensions/utility/unpacker.h
new file mode 100644
index 00000000000..b05cf3cc08a
--- /dev/null
+++ b/chromium/extensions/utility/unpacker.h
@@ -0,0 +1,111 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_UTILITY_UNPACKER_H_
+#define EXTENSIONS_UTILITY_UNPACKER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/common/manifest.h"
+
+class SkBitmap;
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace extensions {
+
+// This class unpacks an extension. It is designed to be used in a sandboxed
+// child process. We parse various bits of the extension, then report back to
+// the browser process, who then transcodes the pre-parsed bits and writes them
+// back out to disk for later use.
+class Unpacker {
+ public:
+ Unpacker(const base::FilePath& working_dir,
+ const base::FilePath& extension_dir,
+ const std::string& extension_id,
+ Manifest::Location location,
+ int creation_flags);
+ ~Unpacker();
+
+ // Runs the processing steps for the extension. On success, this returns true
+ // and the decoded images will be in a file at
+ // |working_dir|/kDecodedImagesFilename and the decoded messages will be in a
+ // file at |working_dir|/kDecodedMessageCatalogsFilename.
+ bool Run();
+
+ const base::string16& error_message() { return error_message_; }
+ base::DictionaryValue* parsed_manifest() { return parsed_manifest_.get(); }
+ base::DictionaryValue* parsed_catalogs() { return parsed_catalogs_.get(); }
+
+ private:
+ // Write the decoded images to kDecodedImagesFilename. We do this instead
+ // of sending them over IPC, since they are so large. Returns true on
+ // success.
+ bool DumpImagesToFile();
+
+ // Write the decoded messages to kDecodedMessageCatalogsFilename. We do this
+ // instead of sending them over IPC, since they are so large. Returns true on
+ // success.
+ bool DumpMessageCatalogsToFile();
+
+ // Parse the manifest.json file inside the extension (not in the header).
+ scoped_ptr<base::DictionaryValue> ReadManifest();
+
+ // Parse all _locales/*/messages.json files inside the extension.
+ bool ReadAllMessageCatalogs(const std::string& default_locale);
+
+ // Decodes the image at the given path and puts it in our list of decoded
+ // images.
+ bool AddDecodedImage(const base::FilePath& path);
+
+ // Parses the catalog at the given path and puts it in our list of parsed
+ // catalogs.
+ bool ReadMessageCatalog(const base::FilePath& message_path);
+
+ // Set the error message.
+ void SetError(const std::string& error);
+ void SetUTF16Error(const base::string16& error);
+
+ // The directory to do work in.
+ base::FilePath working_dir_;
+
+ // The directory where the extension source lives.
+ base::FilePath extension_dir_;
+
+ // The extension ID if known.
+ std::string extension_id_;
+
+ // The location to use for the created extension.
+ Manifest::Location location_;
+
+ // The creation flags to use with the created extension.
+ int creation_flags_;
+
+ // The parsed version of the manifest JSON contained in the extension.
+ scoped_ptr<base::DictionaryValue> parsed_manifest_;
+
+ // A list of decoded images and the paths where those images came from. Paths
+ // are relative to the manifest file.
+ struct InternalData;
+ scoped_ptr<InternalData> internal_data_;
+
+ // Dictionary of relative paths and catalogs per path. Paths are in the form
+ // of _locales/locale, without messages.json base part.
+ scoped_ptr<base::DictionaryValue> parsed_catalogs_;
+
+ // The last error message that was set. Empty if there were no errors.
+ base::string16 error_message_;
+
+ DISALLOW_COPY_AND_ASSIGN(Unpacker);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_UTILITY_UNPACKER_H_
diff --git a/chromium/extensions/utility/unpacker_unittest.cc b/chromium/extensions/utility/unpacker_unittest.cc
new file mode 100644
index 00000000000..82e5a1f7dc1
--- /dev/null
+++ b/chromium/extensions/utility/unpacker_unittest.cc
@@ -0,0 +1,191 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/path_service.h"
+#include "base/strings/pattern.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_paths.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/test/test_extensions_client.h"
+#include "extensions/utility/unpacker.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/zlib/google/zip.h"
+
+using base::ASCIIToUTF16;
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+namespace keys = manifest_keys;
+
+class UnpackerTest : public testing::Test {
+ public:
+ ~UnpackerTest() override {
+ VLOG(1) << "Deleting temp dir: " << temp_dir_.path().LossyDisplayName();
+ VLOG(1) << temp_dir_.Delete();
+ }
+
+ void SetupUnpacker(const std::string& crx_name) {
+ base::FilePath crx_path;
+ ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &crx_path));
+ crx_path = crx_path.AppendASCII("unpacker").AppendASCII(crx_name);
+ ASSERT_TRUE(base::PathExists(crx_path)) << crx_path.value();
+
+ // Try bots won't let us write into DIR_TEST_DATA, so we have to create
+ // a temp folder to play in.
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ base::FilePath unzipped_dir = temp_dir_.path().AppendASCII("unzipped");
+ ASSERT_TRUE(zip::Unzip(crx_path, unzipped_dir))
+ << "Failed to unzip " << crx_path.value() << " to "
+ << unzipped_dir.value();
+
+ unpacker_.reset(new Unpacker(temp_dir_.path(), unzipped_dir, std::string(),
+ Manifest::INTERNAL, Extension::NO_FLAGS));
+ }
+
+ protected:
+ base::ScopedTempDir temp_dir_;
+ scoped_ptr<Unpacker> unpacker_;
+};
+
+TEST_F(UnpackerTest, EmptyDefaultLocale) {
+ SetupUnpacker("empty_default_locale.crx");
+ EXPECT_FALSE(unpacker_->Run());
+ EXPECT_EQ(ASCIIToUTF16(errors::kInvalidDefaultLocale),
+ unpacker_->error_message());
+}
+
+TEST_F(UnpackerTest, HasDefaultLocaleMissingLocalesFolder) {
+ SetupUnpacker("has_default_missing_locales.crx");
+ EXPECT_FALSE(unpacker_->Run());
+ EXPECT_EQ(ASCIIToUTF16(errors::kLocalesTreeMissing),
+ unpacker_->error_message());
+}
+
+TEST_F(UnpackerTest, InvalidDefaultLocale) {
+ SetupUnpacker("invalid_default_locale.crx");
+ EXPECT_FALSE(unpacker_->Run());
+ EXPECT_EQ(ASCIIToUTF16(errors::kInvalidDefaultLocale),
+ unpacker_->error_message());
+}
+
+TEST_F(UnpackerTest, InvalidMessagesFile) {
+ SetupUnpacker("invalid_messages_file.crx");
+ EXPECT_FALSE(unpacker_->Run());
+ EXPECT_TRUE(base::MatchPattern(
+ unpacker_->error_message(),
+ ASCIIToUTF16(
+ "*_locales?en_US?messages.json: Line: 2, column: 11,"
+ " Syntax error.")))
+ << unpacker_->error_message();
+}
+
+TEST_F(UnpackerTest, MissingDefaultData) {
+ SetupUnpacker("missing_default_data.crx");
+ EXPECT_FALSE(unpacker_->Run());
+ EXPECT_EQ(ASCIIToUTF16(errors::kLocalesNoDefaultMessages),
+ unpacker_->error_message());
+}
+
+TEST_F(UnpackerTest, MissingDefaultLocaleHasLocalesFolder) {
+ SetupUnpacker("missing_default_has_locales.crx");
+ EXPECT_FALSE(unpacker_->Run());
+ EXPECT_EQ(ASCIIToUTF16(errors::kLocalesNoDefaultLocaleSpecified),
+ unpacker_->error_message());
+}
+
+TEST_F(UnpackerTest, MissingMessagesFile) {
+ SetupUnpacker("missing_messages_file.crx");
+ EXPECT_FALSE(unpacker_->Run());
+ EXPECT_TRUE(
+ base::MatchPattern(unpacker_->error_message(),
+ ASCIIToUTF16(errors::kLocalesMessagesFileMissing) +
+ ASCIIToUTF16("*_locales?en_US?messages.json")));
+}
+
+TEST_F(UnpackerTest, NoLocaleData) {
+ SetupUnpacker("no_locale_data.crx");
+ EXPECT_FALSE(unpacker_->Run());
+ EXPECT_EQ(ASCIIToUTF16(errors::kLocalesNoDefaultMessages),
+ unpacker_->error_message());
+}
+
+TEST_F(UnpackerTest, GoodL10n) {
+ SetupUnpacker("good_l10n.crx");
+ EXPECT_TRUE(unpacker_->Run());
+ EXPECT_TRUE(unpacker_->error_message().empty());
+ ASSERT_EQ(2U, unpacker_->parsed_catalogs()->size());
+}
+
+TEST_F(UnpackerTest, NoL10n) {
+ SetupUnpacker("no_l10n.crx");
+ EXPECT_TRUE(unpacker_->Run());
+ EXPECT_TRUE(unpacker_->error_message().empty());
+ EXPECT_EQ(0U, unpacker_->parsed_catalogs()->size());
+}
+
+namespace {
+
+// Inserts an illegal path into the browser images returned by
+// TestExtensionsClient for any extension.
+class IllegalImagePathInserter
+ : public TestExtensionsClient::BrowserImagePathsFilter {
+ public:
+ IllegalImagePathInserter(TestExtensionsClient* client) : client_(client) {
+ client_->AddBrowserImagePathsFilter(this);
+ }
+
+ virtual ~IllegalImagePathInserter() {
+ client_->RemoveBrowserImagePathsFilter(this);
+ }
+
+ void Filter(const Extension* extension,
+ std::set<base::FilePath>* paths) override {
+ base::FilePath illegal_path =
+ base::FilePath(base::FilePath::kParentDirectory)
+ .AppendASCII(kTempExtensionName)
+ .AppendASCII("product_logo_128.png");
+ paths->insert(illegal_path);
+ }
+
+ private:
+ TestExtensionsClient* client_;
+};
+
+} // namespace
+
+TEST_F(UnpackerTest, BadPathError) {
+ const char kExpected[] = "Illegal path (absolute or relative with '..'): ";
+ SetupUnpacker("good_package.crx");
+ IllegalImagePathInserter inserter(
+ static_cast<TestExtensionsClient*>(ExtensionsClient::Get()));
+
+ EXPECT_FALSE(unpacker_->Run());
+ EXPECT_TRUE(base::StartsWith(unpacker_->error_message(),
+ ASCIIToUTF16(kExpected),
+ base::CompareCase::INSENSITIVE_ASCII))
+ << "Expected prefix: \"" << kExpected << "\", actual error: \""
+ << unpacker_->error_message() << "\"";
+}
+
+TEST_F(UnpackerTest, ImageDecodingError) {
+ const char kExpected[] = "Could not decode image: ";
+ SetupUnpacker("bad_image.crx");
+ EXPECT_FALSE(unpacker_->Run());
+ EXPECT_TRUE(base::StartsWith(unpacker_->error_message(),
+ ASCIIToUTF16(kExpected),
+ base::CompareCase::INSENSITIVE_ASCII))
+ << "Expected prefix: \"" << kExpected << "\", actual error: \""
+ << unpacker_->error_message() << "\"";
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/utility/utility_handler.cc b/chromium/extensions/utility/utility_handler.cc
new file mode 100644
index 00000000000..4f4ce471204
--- /dev/null
+++ b/chromium/extensions/utility/utility_handler.cc
@@ -0,0 +1,115 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/utility/utility_handler.h"
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/i18n/rtl.h"
+#include "content/public/utility/utility_thread.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_l10n_util.h"
+#include "extensions/common/extension_utility_messages.h"
+#include "extensions/common/extensions_client.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/update_manifest.h"
+#include "extensions/strings/grit/extensions_strings.h"
+#include "extensions/utility/unpacker.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_macros.h"
+#include "third_party/zlib/google/zip.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/ui_base_switches.h"
+
+namespace extensions {
+
+namespace {
+
+bool Send(IPC::Message* message) {
+ return content::UtilityThread::Get()->Send(message);
+}
+
+void ReleaseProcessIfNeeded() {
+ content::UtilityThread::Get()->ReleaseProcessIfNeeded();
+}
+
+const char kExtensionHandlerUnzipError[] =
+ "Could not unzip extension for install.";
+
+} // namespace
+
+UtilityHandler::UtilityHandler() {
+}
+
+UtilityHandler::~UtilityHandler() {
+}
+
+// static
+void UtilityHandler::UtilityThreadStarted() {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ std::string lang = command_line->GetSwitchValueASCII(switches::kLang);
+ if (!lang.empty())
+ extension_l10n_util::SetProcessLocale(lang);
+}
+
+bool UtilityHandler::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(UtilityHandler, message)
+ IPC_MESSAGE_HANDLER(ExtensionUtilityMsg_ParseUpdateManifest,
+ OnParseUpdateManifest)
+ IPC_MESSAGE_HANDLER(ExtensionUtilityMsg_UnzipToDir, OnUnzipToDir)
+ IPC_MESSAGE_HANDLER(ExtensionUtilityMsg_UnpackExtension, OnUnpackExtension)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void UtilityHandler::OnParseUpdateManifest(const std::string& xml) {
+ UpdateManifest manifest;
+ if (!manifest.Parse(xml)) {
+ Send(new ExtensionUtilityHostMsg_ParseUpdateManifest_Failed(
+ manifest.errors()));
+ } else {
+ Send(new ExtensionUtilityHostMsg_ParseUpdateManifest_Succeeded(
+ manifest.results()));
+ }
+ ReleaseProcessIfNeeded();
+}
+
+void UtilityHandler::OnUnzipToDir(const base::FilePath& zip_path,
+ const base::FilePath& dir) {
+ if (!zip::Unzip(zip_path, dir)) {
+ Send(new ExtensionUtilityHostMsg_UnzipToDir_Failed(
+ std::string(kExtensionHandlerUnzipError)));
+ } else {
+ Send(new ExtensionUtilityHostMsg_UnzipToDir_Succeeded(dir));
+ }
+
+ ReleaseProcessIfNeeded();
+}
+
+void UtilityHandler::OnUnpackExtension(const base::FilePath& directory_path,
+ const std::string& extension_id,
+ int location,
+ int creation_flags) {
+ CHECK_GT(location, Manifest::INVALID_LOCATION);
+ CHECK_LT(location, Manifest::NUM_LOCATIONS);
+ DCHECK(ExtensionsClient::Get());
+ content::UtilityThread::Get()->EnsureBlinkInitialized();
+ Unpacker unpacker(directory_path.DirName(), directory_path, extension_id,
+ static_cast<Manifest::Location>(location), creation_flags);
+ if (unpacker.Run()) {
+ Send(new ExtensionUtilityHostMsg_UnpackExtension_Succeeded(
+ *unpacker.parsed_manifest()));
+ } else {
+ Send(new ExtensionUtilityHostMsg_UnpackExtension_Failed(
+ unpacker.error_message()));
+ }
+ ReleaseProcessIfNeeded();
+}
+
+
+} // namespace extensions
diff --git a/chromium/extensions/utility/utility_handler.h b/chromium/extensions/utility/utility_handler.h
new file mode 100644
index 00000000000..b32707f921a
--- /dev/null
+++ b/chromium/extensions/utility/utility_handler.h
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_UTILITY_UTILITY_HANDLER_
+#define EXTENSIONS_UTILITY_UTILITY_HANDLER_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace IPC {
+class Message;
+}
+
+namespace extensions {
+
+// A handler for extensions-related IPC from within utility processes.
+class UtilityHandler {
+ public:
+ UtilityHandler();
+ ~UtilityHandler();
+
+ static void UtilityThreadStarted();
+
+ bool OnMessageReceived(const IPC::Message& message);
+
+ private:
+ // IPC message handlers.
+ void OnParseUpdateManifest(const std::string& xml);
+ void OnUnzipToDir(const base::FilePath& zip_path, const base::FilePath& dir);
+ void OnUnpackExtension(const base::FilePath& directory_path,
+ const std::string& extension_id,
+ int location,
+ int creation_flags);
+
+ DISALLOW_COPY_AND_ASSIGN(UtilityHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_UTILITY_UTILITY_HANDLER_
diff --git a/chromium/mojo/edk/system/data_pipe_consumer_dispatcher.cc b/chromium/mojo/edk/system/data_pipe_consumer_dispatcher.cc
index 7bf0b0177ac..158b0e17e3e 100644
--- a/chromium/mojo/edk/system/data_pipe_consumer_dispatcher.cc
+++ b/chromium/mojo/edk/system/data_pipe_consumer_dispatcher.cc
@@ -535,6 +535,11 @@ void DataPipeConsumerDispatcher::UpdateSignalsStateNoLock() {
if (rv != ports::OK)
peer_closed_ = true;
if (message) {
+ if (message->num_payload_bytes() < sizeof(DataPipeControlMessage)) {
+ peer_closed_ = true;
+ break;
+ }
+
const DataPipeControlMessage* m =
static_cast<const DataPipeControlMessage*>(
message->payload_bytes());
diff --git a/chromium/mojo/edk/system/data_pipe_producer_dispatcher.cc b/chromium/mojo/edk/system/data_pipe_producer_dispatcher.cc
index d3b25300358..364b898e236 100644
--- a/chromium/mojo/edk/system/data_pipe_producer_dispatcher.cc
+++ b/chromium/mojo/edk/system/data_pipe_producer_dispatcher.cc
@@ -513,10 +513,14 @@ void DataPipeProducerDispatcher::UpdateSignalsStateNoLock() {
if (rv != ports::OK)
peer_closed_ = true;
if (message) {
- PortsMessage* ports_message = static_cast<PortsMessage*>(message.get());
+ if (message->num_payload_bytes() < sizeof(DataPipeControlMessage)) {
+ peer_closed_ = true;
+ break;
+ }
+
const DataPipeControlMessage* m =
static_cast<const DataPipeControlMessage*>(
- ports_message->payload_bytes());
+ message->payload_bytes());
if (m->command != DataPipeCommand::DATA_WAS_READ) {
DLOG(ERROR) << "Unexpected message from consumer.";
diff --git a/chromium/mojo/edk/system/message_pipe_dispatcher.cc b/chromium/mojo/edk/system/message_pipe_dispatcher.cc
index 187176d8d48..7e6555dfeda 100644
--- a/chromium/mojo/edk/system/message_pipe_dispatcher.cc
+++ b/chromium/mojo/edk/system/message_pipe_dispatcher.cc
@@ -325,6 +325,7 @@ MojoResult MessagePipeDispatcher::ReadMessage(void* bytes,
bool no_space = false;
bool may_discard = flags & MOJO_READ_MESSAGE_FLAG_MAY_DISCARD;
+ bool invalid_message = false;
// Ensure the provided buffers are large enough to hold the next message.
// GetMessageIf provides an atomic way to test the next message without
@@ -334,15 +335,21 @@ MojoResult MessagePipeDispatcher::ReadMessage(void* bytes,
ports::ScopedMessage ports_message;
int rv = node_controller_->node()->GetMessageIf(
port_,
- [num_bytes, num_handles, &no_space, &may_discard](
+ [num_bytes, num_handles, &no_space, &may_discard, &invalid_message](
const ports::Message& next_message) {
const PortsMessage& message =
static_cast<const PortsMessage&>(next_message);
- DCHECK_GE(message.num_payload_bytes(), sizeof(MessageHeader));
+ if (message.num_payload_bytes() < sizeof(MessageHeader)) {
+ invalid_message = true;
+ return true;
+ }
const MessageHeader* header =
static_cast<const MessageHeader*>(message.payload_bytes());
- DCHECK_LE(header->header_size, message.num_payload_bytes());
+ if (header->header_size > message.num_payload_bytes()) {
+ invalid_message = true;
+ return true;
+ }
uint32_t bytes_to_read = 0;
uint32_t bytes_available =
@@ -370,6 +377,9 @@ MojoResult MessagePipeDispatcher::ReadMessage(void* bytes,
},
&ports_message);
+ if (invalid_message)
+ return MOJO_RESULT_UNKNOWN;
+
if (rv != ports::OK && rv != ports::ERROR_PORT_PEER_CLOSED) {
if (rv == ports::ERROR_PORT_UNKNOWN ||
rv == ports::ERROR_PORT_STATE_UNEXPECTED)
@@ -410,8 +420,8 @@ MojoResult MessagePipeDispatcher::ReadMessage(void* bytes,
const DispatcherHeader* dispatcher_headers =
reinterpret_cast<const DispatcherHeader*>(header + 1);
- const char* dispatcher_data = reinterpret_cast<const char*>(
- dispatcher_headers + header->num_dispatchers);
+ if (header->num_dispatchers > std::numeric_limits<uint16_t>::max())
+ return MOJO_RESULT_UNKNOWN;
// Deserialize dispatchers.
if (header->num_dispatchers > 0) {
@@ -419,18 +429,33 @@ MojoResult MessagePipeDispatcher::ReadMessage(void* bytes,
std::vector<DispatcherInTransit> dispatchers(header->num_dispatchers);
size_t data_payload_index = sizeof(MessageHeader) +
header->num_dispatchers * sizeof(DispatcherHeader);
+ if (data_payload_index > header->header_size)
+ return MOJO_RESULT_UNKNOWN;
+ const char* dispatcher_data = reinterpret_cast<const char*>(
+ dispatcher_headers + header->num_dispatchers);
size_t port_index = 0;
size_t platform_handle_index = 0;
for (size_t i = 0; i < header->num_dispatchers; ++i) {
const DispatcherHeader& dh = dispatcher_headers[i];
Type type = static_cast<Type>(dh.type);
- DCHECK_GE(message->num_payload_bytes(),
- data_payload_index + dh.num_bytes);
- DCHECK_GE(message->num_ports(),
- port_index + dh.num_ports);
- DCHECK_GE(message->num_handles(),
- platform_handle_index + dh.num_platform_handles);
+ size_t next_payload_index = data_payload_index + dh.num_bytes;
+ if (message->num_payload_bytes() < next_payload_index ||
+ next_payload_index < data_payload_index) {
+ return MOJO_RESULT_UNKNOWN;
+ }
+
+ size_t next_port_index = port_index + dh.num_ports;
+ if (message->num_ports() < next_port_index ||
+ next_port_index < port_index)
+ return MOJO_RESULT_UNKNOWN;
+
+ size_t next_platform_handle_index =
+ platform_handle_index + dh.num_platform_handles;
+ if (message->num_handles() < next_platform_handle_index ||
+ next_platform_handle_index < platform_handle_index) {
+ return MOJO_RESULT_UNKNOWN;
+ }
PlatformHandle* out_handles =
message->num_handles() ? message->handles() + platform_handle_index
@@ -442,9 +467,9 @@ MojoResult MessagePipeDispatcher::ReadMessage(void* bytes,
return MOJO_RESULT_UNKNOWN;
dispatcher_data += dh.num_bytes;
- data_payload_index += dh.num_bytes;
- port_index += dh.num_ports;
- platform_handle_index += dh.num_platform_handles;
+ data_payload_index = next_payload_index;
+ port_index = next_port_index;
+ platform_handle_index = next_platform_handle_index;
}
if (!node_controller_->core()->AddDispatchersFromTransit(dispatchers,
diff --git a/chromium/mojo/edk/system/node_channel.cc b/chromium/mojo/edk/system/node_channel.cc
index dddb57724ea..bde3fba91de 100644
--- a/chromium/mojo/edk/system/node_channel.cc
+++ b/chromium/mojo/edk/system/node_channel.cc
@@ -394,6 +394,11 @@ void NodeChannel::OnChannelMessage(const void* payload,
RequestContext request_context(RequestContext::Source::SYSTEM);
+ // Ensure this NodeChannel stays alive through the extent of this method. The
+ // delegate may have the only other reference to this object and it may choose
+ // to drop it here in response to, e.g., a malformed message.
+ scoped_refptr<NodeChannel> keepalive = this;
+
#if defined(OS_WIN)
// If we receive handles from a known process, rewrite them to our own
// process. This can occur when a privileged node receives handles directly
@@ -674,6 +679,9 @@ void NodeChannel::ProcessPendingMessagesWithMachPorts() {
}
}
+ // Ensure this NodeChannel stays alive while flushing relay messages.
+ scoped_refptr<NodeChannel> keepalive = this;
+
while (!pending_relays.empty()) {
ports::NodeName destination = pending_relays.front().first;
Channel::MessagePtr message = std::move(pending_relays.front().second);
diff --git a/chromium/net/quic/quic_flags.cc b/chromium/net/quic/quic_flags.cc
index 94dcb1da471..b36fc3c1c05 100644
--- a/chromium/net/quic/quic_flags.cc
+++ b/chromium/net/quic/quic_flags.cc
@@ -51,7 +51,7 @@ bool FLAGS_quic_enable_multipath = false;
bool FLAGS_quic_require_handshake_confirmation = false;
// If true, Cubic's epoch is shifted when the sender is application-limited.
-bool FLAGS_shift_quic_cubic_epoch_when_app_limited = true;
+bool FLAGS_shift_quic_cubic_epoch_when_app_limited = false;
// If true, QUIC will measure head of line (HOL) blocking due between
// streams due to packet losses on the headers stream. The
diff --git a/chromium/third_party/WebKit/Source/bindings/core/v8/ToV8.cpp b/chromium/third_party/WebKit/Source/bindings/core/v8/ToV8.cpp
index c499b7c9ab9..f09ab370ab8 100644
--- a/chromium/third_party/WebKit/Source/bindings/core/v8/ToV8.cpp
+++ b/chromium/third_party/WebKit/Source/bindings/core/v8/ToV8.cpp
@@ -20,12 +20,17 @@ v8::Local<v8::Value> toV8(DOMWindow* window, v8::Local<v8::Object> creationConte
if (UNLIKELY(!window))
return v8::Null(isolate);
- // Initializes environment of a frame, and return the global object
- // of the frame.
- Frame * frame = window->frame();
- if (!frame)
+
+ // TODO(yukishiino): There must be no case to return undefined.
+ // 'window', 'frames' and 'self' attributes in Window interface return
+ // the WindowProxy object of the browsing context, which never be undefined.
+ // 'top' and 'parent' attributes return the same when detached. Therefore,
+ // there must be no case to return undefined.
+ // See http://crbug.com/621730 and http://crbug.com/621577 .
+ if (!window->isCurrentlyDisplayedInFrame())
return v8Undefined();
+ Frame* frame = window->frame();
return frame->windowProxy(DOMWrapperWorld::current(isolate))->globalIfNotDetached();
}
diff --git a/chromium/third_party/WebKit/Source/bindings/core/v8/WindowProxy.cpp b/chromium/third_party/WebKit/Source/bindings/core/v8/WindowProxy.cpp
index ae45713e008..4c7b46ab97f 100644
--- a/chromium/third_party/WebKit/Source/bindings/core/v8/WindowProxy.cpp
+++ b/chromium/third_party/WebKit/Source/bindings/core/v8/WindowProxy.cpp
@@ -540,12 +540,9 @@ static void getter(v8::Local<v8::Name> property, const v8::PropertyCallbackInfo<
v8SetReturnValue(info, result);
return;
}
- v8::Local<v8::Value> prototype = info.Holder()->GetPrototype();
- if (prototype->IsObject()) {
- v8::Local<v8::Value> value;
- if (prototype.As<v8::Object>()->Get(info.GetIsolate()->GetCurrentContext(), property).ToLocal(&value))
- v8SetReturnValue(info, value);
- }
+ v8::Local<v8::Value> value;
+ if (info.Holder()->GetRealNamedPropertyInPrototypeChain(info.GetIsolate()->GetCurrentContext(), property.As<v8::String>()).ToLocal(&value))
+ v8SetReturnValue(info, value);
}
void WindowProxy::namedItemAdded(HTMLDocument* document, const AtomicString& name)
diff --git a/chromium/third_party/WebKit/Source/core/dom/Document.cpp b/chromium/third_party/WebKit/Source/core/dom/Document.cpp
index 140eb8db74c..a57c9b323df 100644
--- a/chromium/third_party/WebKit/Source/core/dom/Document.cpp
+++ b/chromium/third_party/WebKit/Source/core/dom/Document.cpp
@@ -6006,7 +6006,6 @@ DEFINE_TRACE(Document)
visitor->trace(m_mediaQueryMatcher);
visitor->trace(m_scriptedAnimationController);
visitor->trace(m_scriptedIdleTaskController);
- visitor->trace(m_taskRunner);
visitor->trace(m_textAutosizer);
visitor->trace(m_registrationContext);
visitor->trace(m_customElementMicrotaskRunQueue);
diff --git a/chromium/third_party/WebKit/Source/core/dom/Document.h b/chromium/third_party/WebKit/Source/core/dom/Document.h
index 3d7c505aa0d..6d1d73b7a4d 100644
--- a/chromium/third_party/WebKit/Source/core/dom/Document.h
+++ b/chromium/third_party/WebKit/Source/core/dom/Document.h
@@ -1350,7 +1350,7 @@ private:
Member<ScriptedAnimationController> m_scriptedAnimationController;
Member<ScriptedIdleTaskController> m_scriptedIdleTaskController;
- Member<MainThreadTaskRunner> m_taskRunner;
+ OwnPtr<MainThreadTaskRunner> m_taskRunner;
Member<TextAutosizer> m_textAutosizer;
Member<CustomElementRegistrationContext> m_registrationContext;
diff --git a/chromium/third_party/WebKit/Source/core/dom/MainThreadTaskRunner.cpp b/chromium/third_party/WebKit/Source/core/dom/MainThreadTaskRunner.cpp
index 2c0df118fc4..7f10ea0302f 100644
--- a/chromium/third_party/WebKit/Source/core/dom/MainThreadTaskRunner.cpp
+++ b/chromium/third_party/WebKit/Source/core/dom/MainThreadTaskRunner.cpp
@@ -42,6 +42,7 @@ MainThreadTaskRunner::MainThreadTaskRunner(ExecutionContext* context)
#endif
, m_pendingTasksTimer(this, &MainThreadTaskRunner::pendingTasksTimerFired)
, m_suspended(false)
+ , m_weakFactory(this)
{
}
@@ -49,20 +50,11 @@ MainThreadTaskRunner::~MainThreadTaskRunner()
{
}
-DEFINE_TRACE(MainThreadTaskRunner)
-{
- visitor->trace(m_context);
-}
-
void MainThreadTaskRunner::postTaskInternal(const WebTraceLocation& location, PassOwnPtr<ExecutionContextTask> task, bool isInspectorTask)
{
Platform::current()->mainThread()->getWebTaskRunner()->postTask(location, threadSafeBind(
&MainThreadTaskRunner::perform,
-#if ENABLE(OILPAN)
- CrossThreadWeakPersistentThisPointer<MainThreadTaskRunner>(this),
-#else
AllowCrossThreadAccess(m_weakFactory.createWeakPtr()),
-#endif
task,
isInspectorTask));
}
@@ -81,6 +73,11 @@ void MainThreadTaskRunner::postInspectorTask(const WebTraceLocation& location, P
void MainThreadTaskRunner::perform(PassOwnPtr<ExecutionContextTask> task, bool isInspectorTask)
{
+ // If the owner m_context is about to be swept then it
+ // is no longer safe to access.
+ if (Heap::willObjectBeLazilySwept(m_context.get()))
+ return;
+
if (!isInspectorTask && (m_context->tasksNeedSuspension() || !m_pendingTasks.isEmpty())) {
m_pendingTasks.append(task);
return;
@@ -108,6 +105,11 @@ void MainThreadTaskRunner::resume()
void MainThreadTaskRunner::pendingTasksTimerFired(Timer<MainThreadTaskRunner>*)
{
+ // If the owner m_context is about to be swept then it
+ // is no longer safe to access.
+ if (Heap::willObjectBeLazilySwept(m_context.get()))
+ return;
+
while (!m_pendingTasks.isEmpty()) {
OwnPtr<ExecutionContextTask> task = m_pendingTasks[0].release();
m_pendingTasks.remove(0);
diff --git a/chromium/third_party/WebKit/Source/core/dom/MainThreadTaskRunner.h b/chromium/third_party/WebKit/Source/core/dom/MainThreadTaskRunner.h
index 385ae5ec7a0..0bdd69f0088 100644
--- a/chromium/third_party/WebKit/Source/core/dom/MainThreadTaskRunner.h
+++ b/chromium/third_party/WebKit/Source/core/dom/MainThreadTaskRunner.h
@@ -42,10 +42,11 @@ namespace blink {
class ExecutionContext;
class ExecutionContextTask;
-class CORE_EXPORT MainThreadTaskRunner final : public GarbageCollectedFinalized<MainThreadTaskRunner> {
+class CORE_EXPORT MainThreadTaskRunner final {
+ USING_FAST_MALLOC(MainThreadTaskRunner);
WTF_MAKE_NONCOPYABLE(MainThreadTaskRunner);
public:
- static RawPtr<MainThreadTaskRunner> create(ExecutionContext*);
+ static PassOwnPtr<MainThreadTaskRunner> create(ExecutionContext*);
~MainThreadTaskRunner();
@@ -65,18 +66,18 @@ private:
void postTaskInternal(const WebTraceLocation&, PassOwnPtr<ExecutionContextTask>, bool isInspectorTask);
- Member<ExecutionContext> m_context;
-#if !ENABLE(OILPAN)
- WeakPtrFactory<MainThreadTaskRunner> m_weakFactory;
-#endif
+ // Untraced back reference to the owner Document;
+ // this object has identical lifetime to it.
+ UntracedMember<ExecutionContext> m_context;
Timer<MainThreadTaskRunner> m_pendingTasksTimer;
Vector<OwnPtr<ExecutionContextTask>> m_pendingTasks;
bool m_suspended;
+ WeakPtrFactory<MainThreadTaskRunner> m_weakFactory;
};
-inline RawPtr<MainThreadTaskRunner> MainThreadTaskRunner::create(ExecutionContext* context)
+inline PassOwnPtr<MainThreadTaskRunner> MainThreadTaskRunner::create(ExecutionContext* context)
{
- return new MainThreadTaskRunner(context);
+ return adoptPtr(new MainThreadTaskRunner(context));
}
} // namespace blink
diff --git a/chromium/third_party/WebKit/Source/core/dom/MainThreadTaskRunnerTest.cpp b/chromium/third_party/WebKit/Source/core/dom/MainThreadTaskRunnerTest.cpp
index 1c97da29e13..4e2f08408a3 100644
--- a/chromium/third_party/WebKit/Source/core/dom/MainThreadTaskRunnerTest.cpp
+++ b/chromium/third_party/WebKit/Source/core/dom/MainThreadTaskRunnerTest.cpp
@@ -45,8 +45,8 @@ static void markBoolean(bool* toBeMarked)
TEST(MainThreadTaskRunnerTest, PostTask)
{
- RawPtr<NullExecutionContext> context = new NullExecutionContext();
- RawPtr<MainThreadTaskRunner> runner = MainThreadTaskRunner::create(context.get());
+ NullExecutionContext* context = new NullExecutionContext();
+ OwnPtr<MainThreadTaskRunner> runner = MainThreadTaskRunner::create(context);
bool isMarked = false;
runner->postTask(BLINK_FROM_HERE, createSameThreadTask(&markBoolean, &isMarked));
@@ -57,8 +57,8 @@ TEST(MainThreadTaskRunnerTest, PostTask)
TEST(MainThreadTaskRunnerTest, SuspendTask)
{
- RawPtr<NullExecutionContext> context = new NullExecutionContext();
- RawPtr<MainThreadTaskRunner> runner = MainThreadTaskRunner::create(context.get());
+ NullExecutionContext* context = new NullExecutionContext();
+ OwnPtr<MainThreadTaskRunner> runner = MainThreadTaskRunner::create(context);
bool isMarked = false;
context->setTasksNeedSuspension(true);
@@ -75,8 +75,8 @@ TEST(MainThreadTaskRunnerTest, SuspendTask)
TEST(MainThreadTaskRunnerTest, RemoveRunner)
{
- RawPtr<NullExecutionContext> context = new NullExecutionContext();
- RawPtr<MainThreadTaskRunner> runner = MainThreadTaskRunner::create(context.get());
+ NullExecutionContext* context = new NullExecutionContext();
+ OwnPtr<MainThreadTaskRunner> runner = MainThreadTaskRunner::create(context);
bool isMarked = false;
context->setTasksNeedSuspension(true);
diff --git a/chromium/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp b/chromium/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp
index 768821caa09..072d3091445 100644
--- a/chromium/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp
+++ b/chromium/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp
@@ -179,9 +179,15 @@ static bool updateMarkerRenderedRect(Node* node, RenderedDocumentMarker& marker)
range->setStart(node, marker.startOffset(), exceptionState);
if (!exceptionState.hadException())
range->setEnd(node, marker.endOffset(), IGNORE_EXCEPTION);
- if (exceptionState.hadException())
+ if (exceptionState.hadException()) {
+ range->dispose();
return marker.invalidateRenderedRect();
- return marker.setRenderedRect(LayoutRect(range->boundingBox()));
+ }
+ // TODO(yosin): Once we have a |EphemeralRange| version of |boundingBox()|,
+ // we should use it instead of |Range| version.
+ const bool isUpdated = marker.setRenderedRect(LayoutRect(range->boundingBox()));
+ range->dispose();
+ return isUpdated;
}
// Markers are stored in order sorted by their start offset.
diff --git a/chromium/third_party/WebKit/Source/core/frame/DOMWindow.cpp b/chromium/third_party/WebKit/Source/core/frame/DOMWindow.cpp
index a02e3cc70b2..a64e9e20cf4 100644
--- a/chromium/third_party/WebKit/Source/core/frame/DOMWindow.cpp
+++ b/chromium/third_party/WebKit/Source/core/frame/DOMWindow.cpp
@@ -97,7 +97,10 @@ DOMWindow* DOMWindow::opener() const
DOMWindow* DOMWindow::parent() const
{
- if (!frame())
+ // TODO(yukishiino): The 'parent' attribute must return |this|
+ // (the WindowProxy object of the browsing context itself) when it's
+ // top-level or detached.
+ if (!isCurrentlyDisplayedInFrame())
return nullptr;
Frame* parent = frame()->tree().parent();
@@ -106,7 +109,10 @@ DOMWindow* DOMWindow::parent() const
DOMWindow* DOMWindow::top() const
{
- if (!frame())
+ // TODO(yukishiino): The 'top' attribute must return |this|
+ // (the WindowProxy object of the browsing context itself) when it's
+ // top-level or detached.
+ if (!isCurrentlyDisplayedInFrame())
return nullptr;
return frame()->tree().top()->domWindow();
diff --git a/chromium/third_party/WebKit/Source/core/frame/FrameView.cpp b/chromium/third_party/WebKit/Source/core/frame/FrameView.cpp
index a0027120041..004912befb3 100644
--- a/chromium/third_party/WebKit/Source/core/frame/FrameView.cpp
+++ b/chromium/third_party/WebKit/Source/core/frame/FrameView.cpp
@@ -162,6 +162,7 @@ FrameView::FrameView(LocalFrame* frame)
, m_isUpdatingAllLifecyclePhases(false)
, m_scrollAnchor(this)
, m_needsScrollbarsUpdate(false)
+ , m_suppressAdjustViewSize(false)
{
ASSERT(m_frame);
init();
@@ -531,6 +532,9 @@ void FrameView::setContentsSize(const IntSize& size)
void FrameView::adjustViewSize()
{
+ if (m_suppressAdjustViewSize)
+ return;
+
LayoutViewItem layoutViewItem = LayoutViewItem(this->layoutView());
if (layoutViewItem.isNull())
return;
@@ -553,6 +557,15 @@ void FrameView::adjustViewSize()
setContentsSize(size);
}
+void FrameView::adjustViewSizeAndLayout()
+{
+ adjustViewSize();
+ if (needsLayout()) {
+ TemporaryChange<bool> suppressAdjustViewSize(m_suppressAdjustViewSize, true);
+ layout();
+ }
+}
+
void FrameView::calculateScrollbarModesFromOverflowStyle(const ComputedStyle* style, ScrollbarMode& hMode, ScrollbarMode& vMode)
{
hMode = vMode = ScrollbarAuto;
@@ -1035,7 +1048,7 @@ void FrameView::layout()
} // Reset m_layoutSchedulingEnabled to its previous value.
if (!inSubtreeLayout && !document->printing())
- adjustViewSize();
+ adjustViewSizeAndLayout();
m_frameTimingRequestsDirty = true;
@@ -2762,7 +2775,7 @@ void FrameView::forceLayoutForPagination(const FloatSize& pageSize, const FloatS
}
}
- adjustViewSize();
+ adjustViewSizeAndLayout();
}
IntRect FrameView::convertFromLayoutObject(const LayoutObject& layoutObject, const IntRect& layoutObjectRect) const
@@ -4081,7 +4094,7 @@ void FrameView::notifyRenderThrottlingObservers()
{
TRACE_EVENT0("blink", "FrameView::notifyRenderThrottlingObservers");
DCHECK(!isInPerformLayout());
- DCHECK(!m_frame->document()->inStyleRecalc());
+ DCHECK(!m_frame->document() || !m_frame->document()->inStyleRecalc());
bool wasThrottled = canThrottleRendering();
updateThrottlingStatus();
@@ -4112,7 +4125,7 @@ void FrameView::notifyRenderThrottlingObservers()
layoutView->invalidatePaintForViewAndCompositedLayers();
}
- bool hasHandlers = m_frame->document()->frameHost()->eventHandlerRegistry().hasEventHandlers(EventHandlerRegistry::TouchStartOrMoveEventBlocking);
+ bool hasHandlers = m_frame->host() && m_frame->host()->eventHandlerRegistry().hasEventHandlers(EventHandlerRegistry::TouchStartOrMoveEventBlocking);
if (wasThrottled != canThrottleRendering() && scrollingCoordinator && hasHandlers)
scrollingCoordinator->touchEventTargetRectsDidChange();
diff --git a/chromium/third_party/WebKit/Source/core/frame/FrameView.h b/chromium/third_party/WebKit/Source/core/frame/FrameView.h
index 0269d400dab..da4ddb8ab33 100644
--- a/chromium/third_party/WebKit/Source/core/frame/FrameView.h
+++ b/chromium/third_party/WebKit/Source/core/frame/FrameView.h
@@ -173,6 +173,7 @@ public:
void updateBackgroundRecursively(const Color&, bool);
void adjustViewSize();
+ void adjustViewSizeAndLayout();
// Scale used to convert incoming input events.
float inputEventsScaleFactor() const;
@@ -909,6 +910,7 @@ private:
ScrollAnchor m_scrollAnchor;
bool m_needsScrollbarsUpdate;
+ bool m_suppressAdjustViewSize;
};
inline void FrameView::incrementVisuallyNonEmptyCharacterCount(unsigned count)
diff --git a/chromium/third_party/WebKit/Source/platform/fonts/Font.cpp b/chromium/third_party/WebKit/Source/platform/fonts/Font.cpp
index 64110d549da..13210514494 100644
--- a/chromium/third_party/WebKit/Source/platform/fonts/Font.cpp
+++ b/chromium/third_party/WebKit/Source/platform/fonts/Font.cpp
@@ -760,7 +760,9 @@ Vector<CharacterRange> Font::individualCharacterRanges(const TextRun& run) const
// will be improved shaping in SVG when compared to HTML.
FontCachePurgePreventer purgePreventer;
CachingWordShaper shaper(m_fontFallbackList->shapeCache(m_fontDescription));
- return shaper.individualCharacterRanges(this, run);
+ auto ranges = shaper.individualCharacterRanges(this, run);
+ DCHECK_EQ(ranges.size(), static_cast<unsigned>(run.length()));
+ return ranges;
}
float Font::floatWidthForSimpleText(const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts, FloatRect* glyphBounds) const
diff --git a/chromium/third_party/WebKit/Source/platform/fonts/shaping/CachingWordShaper.cpp b/chromium/third_party/WebKit/Source/platform/fonts/shaping/CachingWordShaper.cpp
index 92f9592aded..4135bf23386 100644
--- a/chromium/third_party/WebKit/Source/platform/fonts/shaping/CachingWordShaper.cpp
+++ b/chromium/third_party/WebKit/Source/platform/fonts/shaping/CachingWordShaper.cpp
@@ -123,7 +123,13 @@ Vector<CharacterRange> CachingWordShaper::individualCharacterRanges(
float totalWidth = shapeResultsForRun(m_shapeCache, font, run, nullptr,
&buffer);
- return buffer.individualCharacterRanges(run.direction(), totalWidth);
+ auto ranges = buffer.individualCharacterRanges(run.direction(), totalWidth);
+ // The shaper can fail to return glyph metrics for all characters (see
+ // crbug.com/613915 and crbug.com/615661) so add empty ranges to ensure all
+ // characters have an associated range.
+ while (ranges.size() < static_cast<unsigned>(run.length()))
+ ranges.append(CharacterRange(0, 0));
+ return ranges;
}
}; // namespace blink
diff --git a/chromium/third_party/WebKit/Source/platform/heap/Heap.cpp b/chromium/third_party/WebKit/Source/platform/heap/Heap.cpp
index cb41196d388..9efd299f876 100644
--- a/chromium/third_party/WebKit/Source/platform/heap/Heap.cpp
+++ b/chromium/third_party/WebKit/Source/platform/heap/Heap.cpp
@@ -463,18 +463,27 @@ void Heap::collectGarbage(BlinkGC::StackState stackState, BlinkGC::GCType gcType
if (gcType != BlinkGC::TakeSnapshot)
Heap::resetHeapCounters();
- // 1. Trace persistent roots.
- ThreadState::visitPersistentRoots(visitor.get());
+ {
+ // Access to the CrossThreadPersistentRegion has to be prevented while
+ // marking and global weak processing is in progress. If not, threads
+ // not attached to Oilpan and participating in this GC are able
+ // to allocate & free PersistentNodes, something the marking phase isn't
+ // capable of handling.
+ CrossThreadPersistentRegion::LockScope persistentLock(ProcessHeap::crossThreadPersistentRegion());
+
+ // 1. Trace persistent roots.
+ ThreadState::visitPersistentRoots(visitor.get());
- // 2. Trace objects reachable from the stack. We do this independent of the
- // given stackState since other threads might have a different stack state.
- ThreadState::visitStackRoots(visitor.get());
+ // 2. Trace objects reachable from the stack. We do this independent of the
+ // given stackState since other threads might have a different stack state.
+ ThreadState::visitStackRoots(visitor.get());
- // 3. Transitive closure to trace objects including ephemerons.
- processMarkingStack(visitor.get());
+ // 3. Transitive closure to trace objects including ephemerons.
+ processMarkingStack(visitor.get());
- postMarkingProcessing(visitor.get());
- globalWeakProcessing(visitor.get());
+ postMarkingProcessing(visitor.get());
+ globalWeakProcessing(visitor.get());
+ }
// Now we can delete all orphaned pages because there are no dangling
// pointers to the orphaned pages. (If we have such dangling pointers,
diff --git a/chromium/third_party/WebKit/Source/platform/heap/PersistentNode.h b/chromium/third_party/WebKit/Source/platform/heap/PersistentNode.h
index ebaf0604edc..41d9c1941fb 100644
--- a/chromium/third_party/WebKit/Source/platform/heap/PersistentNode.h
+++ b/chromium/third_party/WebKit/Source/platform/heap/PersistentNode.h
@@ -180,15 +180,44 @@ public:
m_persistentRegion->freePersistentNode(persistentNode);
}
+ class LockScope final {
+ STACK_ALLOCATED();
+ public:
+ LockScope(CrossThreadPersistentRegion& persistentRegion)
+ : m_persistentRegion(persistentRegion)
+ {
+ m_persistentRegion.lock();
+ }
+ ~LockScope()
+ {
+ m_persistentRegion.unlock();
+ }
+ private:
+ CrossThreadPersistentRegion& m_persistentRegion;
+ };
+
void tracePersistentNodes(Visitor* visitor)
{
- MutexLocker lock(m_mutex);
+ // If this assert triggers, you're tracing without being in a LockScope.
+ ASSERT(m_mutex.locked());
m_persistentRegion->tracePersistentNodes(visitor);
}
void prepareForThreadStateTermination(ThreadState*);
private:
+ friend class LockScope;
+
+ void lock()
+ {
+ m_mutex.lock();
+ }
+
+ void unlock()
+ {
+ m_mutex.unlock();
+ }
+
// We don't make CrossThreadPersistentRegion inherit from PersistentRegion
// because we don't want to virtualize performance-sensitive methods
// such as PersistentRegion::allocate/freePersistentNode.
diff --git a/chromium/third_party/libaddressinput/BUILD.gn b/chromium/third_party/libaddressinput/BUILD.gn
new file mode 100644
index 00000000000..864e79003ca
--- /dev/null
+++ b/chromium/third_party/libaddressinput/BUILD.gn
@@ -0,0 +1,227 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//testing/test.gni")
+import("//tools/grit/grit_rule.gni")
+
+libaddressinput_util_files = [
+ "src/cpp/src/address_data.cc",
+ "src/cpp/src/address_field.cc",
+ "src/cpp/src/address_field_util.cc",
+ "src/cpp/src/address_formatter.cc",
+ "src/cpp/src/address_metadata.cc",
+ "src/cpp/src/address_ui.cc",
+ "src/cpp/src/format_element.cc",
+ "src/cpp/src/language.cc",
+ "src/cpp/src/localization.cc",
+ "src/cpp/src/lookup_key.cc",
+ "src/cpp/src/region_data_constants.cc",
+ "src/cpp/src/rule.cc",
+ "src/cpp/src/util/cctype_tolower_equal.cc",
+ "src/cpp/src/util/json.cc",
+ "src/cpp/src/util/string_split.cc",
+ "src/cpp/src/util/string_util.cc",
+]
+
+config("no-newline-eof-warning") {
+ if (is_clang) {
+ cflags = [ "-Wno-newline-eof" ]
+ }
+}
+
+# GYP version: third_party/libaddressinput/libaddressinput.gyp:libaddressinput_strings
+grit("strings") {
+ source = "//chrome/app/address_input_strings.grd"
+ outputs = [
+ "messages.h",
+ "en_messages.cc",
+ "address_input_strings_am.pak",
+ "address_input_strings_ar.pak",
+ "address_input_strings_bg.pak",
+ "address_input_strings_bn.pak",
+ "address_input_strings_ca.pak",
+ "address_input_strings_cs.pak",
+ "address_input_strings_da.pak",
+ "address_input_strings_de.pak",
+ "address_input_strings_el.pak",
+ "address_input_strings_en-GB.pak",
+ "address_input_strings_en-US.pak",
+ "address_input_strings_es.pak",
+ "address_input_strings_es-419.pak",
+ "address_input_strings_et.pak",
+ "address_input_strings_fa.pak",
+ "address_input_strings_fake-bidi.pak",
+ "address_input_strings_fi.pak",
+ "address_input_strings_fil.pak",
+ "address_input_strings_fr.pak",
+ "address_input_strings_gu.pak",
+ "address_input_strings_he.pak",
+ "address_input_strings_hi.pak",
+ "address_input_strings_hr.pak",
+ "address_input_strings_hu.pak",
+ "address_input_strings_id.pak",
+ "address_input_strings_it.pak",
+ "address_input_strings_ja.pak",
+ "address_input_strings_kn.pak",
+ "address_input_strings_ko.pak",
+ "address_input_strings_lt.pak",
+ "address_input_strings_lv.pak",
+ "address_input_strings_ml.pak",
+ "address_input_strings_mr.pak",
+ "address_input_strings_ms.pak",
+ "address_input_strings_nl.pak",
+ "address_input_strings_nb.pak",
+ "address_input_strings_pl.pak",
+ "address_input_strings_pt-BR.pak",
+ "address_input_strings_pt-PT.pak",
+ "address_input_strings_ro.pak",
+ "address_input_strings_ru.pak",
+ "address_input_strings_sk.pak",
+ "address_input_strings_sl.pak",
+ "address_input_strings_sr.pak",
+ "address_input_strings_sv.pak",
+ "address_input_strings_sw.pak",
+ "address_input_strings_ta.pak",
+ "address_input_strings_te.pak",
+ "address_input_strings_th.pak",
+ "address_input_strings_tr.pak",
+ "address_input_strings_uk.pak",
+ "address_input_strings_vi.pak",
+ "address_input_strings_zh-CN.pak",
+ "address_input_strings_zh-TW.pak",
+ ]
+
+ if (is_ios) {
+ # iOS uses "pt" for pt-BR" and "es-MX" for "es-419".
+ outputs -= [
+ "address_input_strings_pt-BR.pak",
+ "address_input_strings_es-419.pak",
+ ]
+ outputs += [
+ "address_input_strings_pt.pak",
+ "address_input_strings_es-MX.pak",
+ ]
+ }
+
+ configs = [ ":no-newline-eof-warning" ]
+}
+
+config("libaddressinput_config") {
+ defines = [
+ "I18N_ADDRESSINPUT_USE_BASICTYPES_OVERRIDE=1",
+ "I18N_ADDRESS_VALIDATION_DATA_URL=\"https://i18napis.appspot.com/ssl-aggregate-address/\"",
+ ]
+ include_dirs = [
+ "src/cpp/include",
+ "chromium/override",
+ ]
+}
+
+# This target provides basic functionality which is cooked into the build.
+# GYP version: third_party/libaddressinput/libaddressinput.gyp:libaddressinput_util
+static_library("util") {
+ sources = libaddressinput_util_files
+ sources += [
+ "chromium/addressinput_util.cc",
+ "chromium/json.cc",
+ ]
+ sources -= [ "src/cpp/src/util/json.cc" ]
+
+ configs -= [ "//build/config/compiler:chromium_code" ]
+ configs += [
+ ":no-newline-eof-warning",
+ "//build/config/compiler:no_chromium_code",
+ ]
+
+ public_configs = [ ":libaddressinput_config" ]
+
+ include_dirs = [ "$root_gen_dir/third_party/libaddressinput" ]
+
+ deps = [
+ ":strings",
+ "//base",
+ "//base:i18n",
+ "//third_party/icu",
+ "//third_party/re2",
+ ]
+}
+
+if (!is_android || use_aura) {
+ # The list of files in libaddressinput.gypi.
+ gypi_values = exec_script("//build/gypi_to_gn.py",
+ [ rebase_path("src/cpp/libaddressinput.gypi") ],
+ "scope",
+ [ "src/cpp/libaddressinput.gypi" ])
+
+ # This target provides more complicated functionality like pinging servers
+ # for validation rules.
+ # GYP version: third_party/libaddressinput/libaddressinput.gyp:libaddressinput
+ static_library("libaddressinput") {
+ sources = rebase_path(gypi_values.libaddressinput_files, ".", "src/cpp")
+ sources += [
+ "chromium/chrome_address_validator.cc",
+ "chromium/chrome_metadata_source.cc",
+ "chromium/chrome_storage_impl.cc",
+ "chromium/fallback_data_store.cc",
+ "chromium/input_suggester.cc",
+ "chromium/string_compare.cc",
+ "chromium/trie.cc",
+ ]
+ sources -= libaddressinput_util_files
+ sources -= [ "src/cpp/src/util/string_compare.cc" ]
+
+ configs -= [ "//build/config/compiler:chromium_code" ]
+ configs += [ "//build/config/compiler:no_chromium_code" ]
+
+ public_configs = [ ":libaddressinput_config" ]
+
+ deps = [
+ ":strings",
+ ":util",
+ "//base",
+ "//base:i18n",
+ "//components/prefs",
+ "//net",
+ "//third_party/icu",
+ "//third_party/re2",
+ ]
+ }
+
+ test("libaddressinput_unittests") {
+ sources =
+ rebase_path(gypi_values.libaddressinput_test_files, ".", "src/cpp")
+ sources += [
+ "chromium/addressinput_util_unittest.cc",
+ "chromium/chrome_address_validator_unittest.cc",
+ "chromium/chrome_metadata_source_unittest.cc",
+ "chromium/chrome_storage_impl_unittest.cc",
+ "chromium/fallback_data_store_unittest.cc",
+ "chromium/storage_test_runner.cc",
+ "chromium/string_compare_unittest.cc",
+ "chromium/trie_unittest.cc",
+ ]
+
+ if (is_ios) {
+ # TODO(rouslan): This tests uses ASSERT_DEATH which is not supported on
+ # iOS. Re-enable once http://crbug.com/595645 is fixed.
+ sources -= [ "src/cpp/test/address_data_test.cc" ]
+ }
+
+ configs -= [ "//build/config/compiler:chromium_code" ]
+ configs += [ "//build/config/compiler:no_chromium_code" ]
+
+ defines = [ "TEST_DATA_DIR=\"third_party/libaddressinput/src/testdata\"" ]
+
+ include_dirs = [ "src/cpp/src" ]
+
+ deps = [
+ ":libaddressinput",
+ ":strings",
+ "//base/test:run_all_unittests",
+ "//components/prefs",
+ "//net:test_support",
+ "//testing/gtest",
+ ]
+ }
+}
diff --git a/chromium/third_party/libaddressinput/LICENSE b/chromium/third_party/libaddressinput/LICENSE
new file mode 100644
index 00000000000..d6456956733
--- /dev/null
+++ b/chromium/third_party/libaddressinput/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/chromium/third_party/libaddressinput/OWNERS b/chromium/third_party/libaddressinput/OWNERS
new file mode 100644
index 00000000000..d379e0309a9
--- /dev/null
+++ b/chromium/third_party/libaddressinput/OWNERS
@@ -0,0 +1,2 @@
+rouslan@chromium.org
+estade@chromium.org
diff --git a/chromium/third_party/libaddressinput/README.chromium b/chromium/third_party/libaddressinput/README.chromium
new file mode 100644
index 00000000000..9e7ff6edb91
--- /dev/null
+++ b/chromium/third_party/libaddressinput/README.chromium
@@ -0,0 +1,21 @@
+Name: The library to input, validate, and display addresses.
+Short Name: libaddressinput
+URL: https://github.com/googlei18n/libaddressinput
+Version: 0
+Date: 10 November 2014
+Revision: 678a7f55a2ae7ccf417b4809e602b808b56a8ddb
+License: Apache 2.0
+License File: LICENSE
+Security Critical: no
+
+Description:
+
+This library lets you enter, validate, and display an address with correct
+semantics for many countries around the world. The library uses the serialized
+validation rules from a Google-managed server (without SLA) at
+https://i18napis.appspot.com/ssl-aggregate-address. The library is used in
+requestAutocomplete dialog and autofill.
+
+Local Modifications:
+- Use Chrome's version of JSON reader in chromium/json.cc.
+- Use Chrome's version of loose string comparison in chromium/string_compare.cc.
diff --git a/chromium/third_party/libaddressinput/chromium/DEPS b/chromium/third_party/libaddressinput/chromium/DEPS
new file mode 100644
index 00000000000..b55c0fa48ac
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/DEPS
@@ -0,0 +1,8 @@
+include_rules = [
+ '+base',
+ "+components/prefs",
+ '+net',
+ '+testing',
+ '+third_party/icu',
+ '+url',
+]
diff --git a/chromium/third_party/libaddressinput/chromium/addressinput_util.cc b/chromium/third_party/libaddressinput/chromium/addressinput_util.cc
new file mode 100644
index 00000000000..ca10d41ddef
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/addressinput_util.cc
@@ -0,0 +1,77 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/libaddressinput/chromium/addressinput_util.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_data.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_metadata.h"
+
+namespace autofill {
+namespace addressinput {
+
+namespace {
+
+using ::i18n::addressinput::AddressData;
+using ::i18n::addressinput::AddressField;
+using ::i18n::addressinput::AddressProblem;
+using ::i18n::addressinput::IsFieldRequired;
+
+using ::i18n::addressinput::MISSING_REQUIRED_FIELD;
+
+// Returns true if the |problem| should not be reported for the |field| because
+// the |filter| excludes it.
+bool FilterExcludes(const std::multimap<AddressField, AddressProblem>* filter,
+ AddressField field,
+ AddressProblem problem) {
+ return filter != NULL && !filter->empty() &&
+ std::find(filter->begin(),
+ filter->end(),
+ std::multimap<AddressField, AddressProblem>::value_type(
+ field, problem)) == filter->end();
+}
+
+} // namespace
+
+bool HasAllRequiredFields(const AddressData& address_to_check) {
+ std::multimap<AddressField, AddressProblem> problems;
+ ValidateRequiredFields(address_to_check, NULL, &problems);
+ return problems.empty();
+}
+
+void ValidateRequiredFields(
+ const AddressData& address_to_check,
+ const std::multimap<AddressField, AddressProblem>* filter,
+ std::multimap<AddressField, AddressProblem>* problems) {
+ DCHECK(problems);
+
+ static const AddressField kFields[] = {
+ ::i18n::addressinput::COUNTRY,
+ ::i18n::addressinput::ADMIN_AREA,
+ ::i18n::addressinput::LOCALITY,
+ ::i18n::addressinput::DEPENDENT_LOCALITY,
+ ::i18n::addressinput::SORTING_CODE,
+ ::i18n::addressinput::POSTAL_CODE,
+ ::i18n::addressinput::STREET_ADDRESS,
+ // ORGANIZATION is never required.
+ ::i18n::addressinput::RECIPIENT
+ };
+
+ for (size_t i = 0; i < arraysize(kFields); ++i) {
+ AddressField field = kFields[i];
+ if (address_to_check.IsFieldEmpty(field) &&
+ IsFieldRequired(field, address_to_check.region_code) &&
+ !FilterExcludes(filter, field, MISSING_REQUIRED_FIELD)) {
+ problems->insert(std::make_pair(field, MISSING_REQUIRED_FIELD));
+ }
+ }
+}
+
+} // namespace addressinput
+} // namespace autofill
diff --git a/chromium/third_party/libaddressinput/chromium/addressinput_util.h b/chromium/third_party/libaddressinput/chromium/addressinput_util.h
new file mode 100644
index 00000000000..7c888a7b1f2
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/addressinput_util.h
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_ADDRESSINPUT_UTIL_H_
+#define THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_ADDRESSINPUT_UTIL_H_
+
+#include <map>
+
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_field.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_problem.h"
+
+namespace i18n {
+namespace addressinput {
+struct AddressData;
+}
+}
+
+namespace autofill {
+namespace addressinput {
+
+// Returns true if |address_to_check| has all of its required fields.
+bool HasAllRequiredFields(
+ const ::i18n::addressinput::AddressData& address_to_check);
+
+// Validates required fields in |address_to_check| without loading rules from
+// the server. The |problems| parameter cannot be NULL. Does not take ownership
+// of its parameters.
+//
+// See documentation of ::i18n::addressinput::AddressValidator::Validate() for
+// description of |filter| and |problems|.
+void ValidateRequiredFields(
+ const ::i18n::addressinput::AddressData& address_to_check,
+ const std::multimap< ::i18n::addressinput::AddressField,
+ ::i18n::addressinput::AddressProblem>* filter,
+ std::multimap< ::i18n::addressinput::AddressField,
+ ::i18n::addressinput::AddressProblem>* problems);
+
+} // namespace addressinput
+} // namespace autofill
+
+#endif // THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_ADDRESSINPUT_UTIL_H_
diff --git a/chromium/third_party/libaddressinput/chromium/addressinput_util_unittest.cc b/chromium/third_party/libaddressinput/chromium/addressinput_util_unittest.cc
new file mode 100644
index 00000000000..b68ea81760d
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/addressinput_util_unittest.cc
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/libaddressinput/chromium/addressinput_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_data.h"
+
+namespace autofill {
+namespace addressinput {
+
+using ::i18n::addressinput::AddressData;
+
+TEST(AddressinputUtilTest, AddressRequiresRegionCode) {
+ AddressData address;
+ EXPECT_FALSE(HasAllRequiredFields(address));
+}
+
+TEST(AddressinputUtilTest, UsRequiresState) {
+ AddressData address;
+ address.region_code = "US";
+ address.postal_code = "90291";
+ // Leave state empty.
+ address.locality = "Los Angeles";
+ address.address_line.push_back("340 Main St.");
+ EXPECT_FALSE(HasAllRequiredFields(address));
+}
+
+TEST(AddressinputUtilTest, CompleteAddressReturnsTrue) {
+ AddressData address;
+ address.region_code = "US";
+ address.postal_code = "90291";
+ address.administrative_area = "CA";
+ address.locality = "Los Angeles";
+ address.address_line.push_back("340 Main St.");
+ EXPECT_TRUE(HasAllRequiredFields(address));
+}
+
+} // namespace addressinput
+} // namespace autofill
diff --git a/chromium/third_party/libaddressinput/chromium/canonicalize_string.cc b/chromium/third_party/libaddressinput/chromium/canonicalize_string.cc
new file mode 100644
index 00000000000..d1fc1ce10aa
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/canonicalize_string.cc
@@ -0,0 +1,72 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/libaddressinput/src/cpp/src/util/canonicalize_string.h"
+
+#include <stdint.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "third_party/icu/source/common/unicode/errorcode.h"
+#include "third_party/icu/source/common/unicode/locid.h"
+#include "third_party/icu/source/common/unicode/unistr.h"
+#include "third_party/icu/source/common/unicode/utypes.h"
+#include "third_party/icu/source/i18n/unicode/coll.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/util/scoped_ptr.h"
+
+namespace i18n {
+namespace addressinput {
+
+namespace {
+
+class ChromeStringCanonicalizer : public StringCanonicalizer {
+ public:
+ ChromeStringCanonicalizer()
+ : error_code_(U_ZERO_ERROR),
+ collator_(
+ icu::Collator::createInstance(
+ icu::Locale::getRoot(), error_code_)) {
+ collator_->setStrength(icu::Collator::PRIMARY);
+ DCHECK(U_SUCCESS(error_code_));
+ }
+
+ virtual ~ChromeStringCanonicalizer() {}
+
+ // StringCanonicalizer implementation.
+ virtual std::string CanonicalizeString(const std::string& original) {
+ // Returns a canonical version of the string that can be used for comparing
+ // strings regardless of diacritics and capitalization.
+ // CanonicalizeString("Texas") == CanonicalizeString("T\u00E9xas");
+ // CanonicalizeString("Texas") == CanonicalizeString("teXas");
+ // CanonicalizeString("Texas") != CanonicalizeString("California");
+ //
+ // The output is not human-readable.
+ // CanonicalizeString("Texas") != "Texas";
+ icu::UnicodeString icu_str(
+ original.c_str(), static_cast<int32_t>(original.length()));
+ int32_t buffer_size = collator_->getSortKey(icu_str, NULL, 0);
+ scoped_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
+ DCHECK(buffer.get());
+ int32_t filled_size =
+ collator_->getSortKey(icu_str, buffer.get(), buffer_size);
+ DCHECK_EQ(buffer_size, filled_size);
+ return std::string(reinterpret_cast<const char*>(buffer.get()));
+ }
+
+ private:
+ UErrorCode error_code_;
+ scoped_ptr<icu::Collator> collator_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeStringCanonicalizer);
+};
+
+} // namespace
+
+// static
+scoped_ptr<StringCanonicalizer> StringCanonicalizer::Build() {
+ return scoped_ptr<StringCanonicalizer>(new ChromeStringCanonicalizer);
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/chromium/chrome_address_validator.cc b/chromium/third_party/libaddressinput/chromium/chrome_address_validator.cc
new file mode 100644
index 00000000000..c55ab49ca47
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/chrome_address_validator.cc
@@ -0,0 +1,164 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/libaddressinput/chromium/chrome_address_validator.h"
+
+#include <cmath>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "third_party/libaddressinput/chromium/addressinput_util.h"
+#include "third_party/libaddressinput/chromium/input_suggester.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_data.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_normalizer.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/source.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/storage.h"
+
+namespace autofill {
+namespace {
+
+using ::i18n::addressinput::AddressData;
+using ::i18n::addressinput::AddressField;
+using ::i18n::addressinput::AddressNormalizer;
+using ::i18n::addressinput::BuildCallback;
+using ::i18n::addressinput::FieldProblemMap;
+using ::i18n::addressinput::PreloadSupplier;
+using ::i18n::addressinput::Source;
+using ::i18n::addressinput::Storage;
+
+using ::i18n::addressinput::ADMIN_AREA;
+using ::i18n::addressinput::DEPENDENT_LOCALITY;
+using ::i18n::addressinput::POSTAL_CODE;
+
+// The maximum number attempts to load rules.
+static const int kMaxAttemptsNumber = 8;
+
+} // namespace
+
+AddressValidator::AddressValidator(scoped_ptr<Source> source,
+ scoped_ptr<Storage> storage,
+ LoadRulesListener* load_rules_listener)
+ : supplier_(new PreloadSupplier(source.release(),
+ storage.release())),
+ input_suggester_(new InputSuggester(supplier_.get())),
+ normalizer_(new AddressNormalizer(supplier_.get())),
+ validator_(new ::i18n::addressinput::AddressValidator(supplier_.get())),
+ validated_(BuildCallback(this, &AddressValidator::Validated)),
+ rules_loaded_(BuildCallback(this, &AddressValidator::RulesLoaded)),
+ load_rules_listener_(load_rules_listener),
+ weak_factory_(this) {}
+
+AddressValidator::~AddressValidator() {}
+
+void AddressValidator::LoadRules(const std::string& region_code) {
+ attempts_number_[region_code] = 0;
+ supplier_->LoadRules(region_code, *rules_loaded_);
+}
+
+AddressValidator::Status AddressValidator::ValidateAddress(
+ const AddressData& address,
+ const FieldProblemMap* filter,
+ FieldProblemMap* problems) const {
+ if (supplier_->IsPending(address.region_code)) {
+ if (problems)
+ addressinput::ValidateRequiredFields(address, filter, problems);
+ return RULES_NOT_READY;
+ }
+
+ if (!supplier_->IsLoaded(address.region_code)) {
+ if (problems)
+ addressinput::ValidateRequiredFields(address, filter, problems);
+ return RULES_UNAVAILABLE;
+ }
+
+ if (!problems)
+ return SUCCESS;
+
+ validator_->Validate(address,
+ true, // Allow postal office boxes.
+ true, // Require recipient name.
+ filter,
+ problems,
+ *validated_);
+
+ return SUCCESS;
+}
+
+AddressValidator::Status AddressValidator::GetSuggestions(
+ const AddressData& user_input,
+ AddressField focused_field,
+ size_t suggestion_limit,
+ std::vector<AddressData>* suggestions) const {
+ if (supplier_->IsPending(user_input.region_code))
+ return RULES_NOT_READY;
+
+ if (!supplier_->IsLoaded(user_input.region_code))
+ return RULES_UNAVAILABLE;
+
+ if (!suggestions)
+ return SUCCESS;
+
+ suggestions->clear();
+
+ if (focused_field == POSTAL_CODE ||
+ (focused_field >= ADMIN_AREA && focused_field <= DEPENDENT_LOCALITY)) {
+ input_suggester_->GetSuggestions(
+ user_input, focused_field, suggestion_limit, suggestions);
+ }
+
+ return SUCCESS;
+}
+
+bool AddressValidator::CanonicalizeAdministrativeArea(
+ AddressData* address) const {
+ if (!supplier_->IsLoaded(address->region_code))
+ return false;
+
+ // TODO: It would probably be beneficial to use the full canonicalization.
+ AddressData tmp(*address);
+ normalizer_->Normalize(&tmp);
+ address->administrative_area = tmp.administrative_area;
+
+ return true;
+}
+
+AddressValidator::AddressValidator()
+ : load_rules_listener_(NULL), weak_factory_(this) {}
+
+base::TimeDelta AddressValidator::GetBaseRetryPeriod() const {
+ return base::TimeDelta::FromSeconds(8);
+}
+
+void AddressValidator::Validated(bool success,
+ const AddressData&,
+ const FieldProblemMap&) {
+ DCHECK(success);
+}
+
+void AddressValidator::RulesLoaded(bool success,
+ const std::string& region_code,
+ int) {
+ if (load_rules_listener_)
+ load_rules_listener_->OnAddressValidationRulesLoaded(region_code, success);
+
+ // Count the first failed attempt to load rules as well.
+ if (success || attempts_number_[region_code] + 1 >= kMaxAttemptsNumber)
+ return;
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&AddressValidator::RetryLoadRules,
+ weak_factory_.GetWeakPtr(),
+ region_code),
+ GetBaseRetryPeriod() * pow(2, attempts_number_[region_code]++));
+}
+
+void AddressValidator::RetryLoadRules(const std::string& region_code) {
+ // Do not reset retry count.
+ supplier_->LoadRules(region_code, *rules_loaded_);
+}
+
+} // namespace autofill
diff --git a/chromium/third_party/libaddressinput/chromium/chrome_address_validator.h b/chromium/third_party/libaddressinput/chromium/chrome_address_validator.h
new file mode 100644
index 00000000000..94ae50434e9
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/chrome_address_validator.h
@@ -0,0 +1,203 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_CHROME_ADDRESS_VALIDATOR_H_
+#define THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_CHROME_ADDRESS_VALIDATOR_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_field.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_validator.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/callback.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/preload_supplier.h"
+
+namespace i18n {
+namespace addressinput {
+class AddressNormalizer;
+class Source;
+class Storage;
+struct AddressData;
+}
+}
+
+namespace autofill {
+
+class InputSuggester;
+
+// The object to be notified when loading of address validation rules is
+// finished.
+class LoadRulesListener {
+ public:
+ virtual ~LoadRulesListener() {}
+
+ // Called when the validation rules for the |region_code| have been loaded.
+ // The validation rules include the generic rules for the |region_code| and
+ // specific rules for the country's administrative areas, localities, and
+ // dependent localities. If a country has language-specific validation rules,
+ // then these are also loaded.
+ //
+ // The |success| parameter is true when the rules were loaded successfully.
+ virtual void OnAddressValidationRulesLoaded(const std::string& region_code,
+ bool success) = 0;
+};
+
+// Interface to the libaddressinput AddressValidator for Chromium Autofill. The
+// class is named AddressValidator to simplify switching between libaddressinput
+// and this version.
+//
+// It's not possible to name this file address_validator.h because some
+// compilers do not handle multiple files with the same name (although in
+// different directories) gracefully. This class is a shim between upstream
+// libaddressinput API and the API that Chrome expects, hence the file name
+// chrome_address_validator.h.
+class AddressValidator {
+ public:
+ // The status of address validation.
+ enum Status {
+ // Address validation completed successfully. Check |problems| to see if any
+ // problems were found.
+ SUCCESS,
+
+ // The validation rules are not available, because LoadRules() was not
+ // called or failed. Reload the rules.
+ RULES_UNAVAILABLE,
+
+ // The validation rules are being loaded. Try again later.
+ RULES_NOT_READY
+ };
+
+ // Takes ownership of |source| and |storage|.
+ AddressValidator(scoped_ptr< ::i18n::addressinput::Source> source,
+ scoped_ptr< ::i18n::addressinput::Storage> storage,
+ LoadRulesListener* load_rules_listener);
+
+ virtual ~AddressValidator();
+
+ // Loads the generic validation rules for |region_code| and specific rules
+ // for the region's administrative areas, localities, and dependent
+ // localities. A typical data size is 10KB. The largest is 250KB. If a region
+ // has language-specific validation rules, then these are also loaded.
+ //
+ // Example rule:
+ // https://i18napis.appspot.com/ssl-aggregate-address/data/US
+ //
+ // If the rules are already in progress of being loaded, it does nothing.
+ // Invokes |load_rules_listener| when the loading has finished.
+ virtual void LoadRules(const std::string& region_code);
+
+ // Validates the |address| and populates |problems| with the validation
+ // problems, filtered according to the |filter| parameter.
+ //
+ // If the |filter| is empty, then all discovered validation problems are
+ // returned. If the |filter| contains problem elements, then only the problems
+ // in the |filter| may be returned.
+ virtual Status ValidateAddress(
+ const ::i18n::addressinput::AddressData& address,
+ const ::i18n::addressinput::FieldProblemMap* filter,
+ ::i18n::addressinput::FieldProblemMap* problems) const;
+
+ // Fills in |suggestions| for the partially typed in |user_input|, assuming
+ // the user is typing in the |focused_field|. If the number of |suggestions|
+ // is over the |suggestion_limit|, then returns no |suggestions| at all.
+ //
+ // If the |solutions| parameter is NULL, the checks whether the validation
+ // rules are available, but does not fill in suggestions.
+ //
+ // Sample user input 1:
+ // country code = "US"
+ // postal code = "90066"
+ // focused field = POSTAL_CODE
+ // suggestions limit = 1
+ // Suggestion:
+ // [{administrative_area: "CA"}]
+ //
+ // Sample user input 2:
+ // country code = "CN"
+ // dependent locality = "Zongyang"
+ // focused field = DEPENDENT_LOCALITY
+ // suggestions limit = 10
+ // Suggestion:
+ // [{dependent_locality: "Zongyang Xian",
+ // locality: "Anqing Shi",
+ // administrative_area: "Anhui Sheng"}]
+ virtual Status GetSuggestions(
+ const ::i18n::addressinput::AddressData& user_input,
+ ::i18n::addressinput::AddressField focused_field,
+ size_t suggestion_limit,
+ std::vector< ::i18n::addressinput::AddressData>* suggestions) const;
+
+ // Canonicalizes the administrative area in |address_data|. For example,
+ // "texas" changes to "TX". Returns true on success, otherwise leaves
+ // |address_data| alone and returns false.
+ virtual bool CanonicalizeAdministrativeArea(
+ ::i18n::addressinput::AddressData* address) const;
+
+ protected:
+ // Constructor used only for MockAddressValidator.
+ AddressValidator();
+
+ // Returns the period of time to wait between the first attempt's failure and
+ // the second attempt's initiation to load rules. Exposed for testing.
+ virtual base::TimeDelta GetBaseRetryPeriod() const;
+
+ private:
+ // Verifies that |validator_| succeeded. Invoked by |validated_| callback.
+ void Validated(bool success,
+ const ::i18n::addressinput::AddressData&,
+ const ::i18n::addressinput::FieldProblemMap&);
+
+ // Invokes the |load_rules_listener_|, if it's not NULL. Called by
+ // |rules_loaded_| callback.
+ void RulesLoaded(bool success, const std::string& region_code, int);
+
+ // Retries loading rules without resetting the retry counter.
+ void RetryLoadRules(const std::string& region_code);
+
+ // Loads and stores aggregate rules at COUNTRY level.
+ const scoped_ptr< ::i18n::addressinput::PreloadSupplier> supplier_;
+
+ // Suggests addresses based on user input.
+ const scoped_ptr<InputSuggester> input_suggester_;
+
+ // Normalizes addresses into a canonical form.
+ const scoped_ptr< ::i18n::addressinput::AddressNormalizer> normalizer_;
+
+ // Validates addresses.
+ const scoped_ptr<const ::i18n::addressinput::AddressValidator> validator_;
+
+ // The callback that |validator_| invokes when it finished validating an
+ // address.
+ const scoped_ptr<const ::i18n::addressinput::AddressValidator::Callback>
+ validated_;
+
+ // The callback that |supplier_| invokes when it finished loading rules.
+ const scoped_ptr<const ::i18n::addressinput::PreloadSupplier::Callback>
+ rules_loaded_;
+
+ // Not owned delegate to invoke when |suppler_| finished loading rules. Can be
+ // NULL.
+ LoadRulesListener* const load_rules_listener_;
+
+ // A mapping of region codes to the number of attempts to retry loading rules.
+ std::map<std::string, int> attempts_number_;
+
+ // Member variables should appear before the WeakPtrFactory, to ensure that
+ // any WeakPtrs to AddressValidator are invalidated before its members
+ // variable's destructors are executed, rendering them invalid.
+ base::WeakPtrFactory<AddressValidator> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(AddressValidator);
+};
+
+} // namespace autofill
+
+#endif // THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_CHROME_ADDRESS_VALIDATOR_H_
diff --git a/chromium/third_party/libaddressinput/chromium/chrome_address_validator_unittest.cc b/chromium/third_party/libaddressinput/chromium/chrome_address_validator_unittest.cc
new file mode 100644
index 00000000000..a7f9861247f
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/chrome_address_validator_unittest.cc
@@ -0,0 +1,892 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/libaddressinput/chromium/chrome_address_validator.h"
+
+#include <stddef.h>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_data.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_problem.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_ui.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/null_storage.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/source.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/storage.h"
+#include "third_party/libaddressinput/src/cpp/test/testdata_source.h"
+
+namespace autofill {
+
+using ::i18n::addressinput::AddressData;
+using ::i18n::addressinput::AddressField;
+using ::i18n::addressinput::AddressProblem;
+using ::i18n::addressinput::BuildCallback;
+using ::i18n::addressinput::FieldProblemMap;
+using ::i18n::addressinput::GetRegionCodes;
+using ::i18n::addressinput::NullStorage;
+using ::i18n::addressinput::Source;
+using ::i18n::addressinput::Storage;
+using ::i18n::addressinput::TestdataSource;
+
+using ::i18n::addressinput::COUNTRY;
+using ::i18n::addressinput::ADMIN_AREA;
+using ::i18n::addressinput::LOCALITY;
+using ::i18n::addressinput::DEPENDENT_LOCALITY;
+using ::i18n::addressinput::SORTING_CODE;
+using ::i18n::addressinput::POSTAL_CODE;
+using ::i18n::addressinput::STREET_ADDRESS;
+using ::i18n::addressinput::RECIPIENT;
+
+using ::i18n::addressinput::INVALID_FORMAT;
+using ::i18n::addressinput::MISMATCHING_VALUE;
+using ::i18n::addressinput::MISSING_REQUIRED_FIELD;
+using ::i18n::addressinput::UNEXPECTED_FIELD;
+using ::i18n::addressinput::UNKNOWN_VALUE;
+using ::i18n::addressinput::USES_P_O_BOX;
+
+class AddressValidatorTest : public testing::Test, LoadRulesListener {
+ protected:
+ AddressValidatorTest()
+ : validator_(
+ new AddressValidator(scoped_ptr<Source>(new TestdataSource(true)),
+ scoped_ptr<Storage>(new NullStorage),
+ this)) {
+ validator_->LoadRules("US");
+ }
+
+ virtual ~AddressValidatorTest() {}
+
+ const scoped_ptr<AddressValidator> validator_;
+
+ private:
+ // LoadRulesListener implementation.
+ virtual void OnAddressValidationRulesLoaded(const std::string& country_code,
+ bool success) override {
+ AddressData address_data;
+ address_data.region_code = country_code;
+ FieldProblemMap dummy;
+ AddressValidator::Status status =
+ validator_->ValidateAddress(address_data, NULL, &dummy);
+ ASSERT_EQ(success, status == AddressValidator::SUCCESS);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(AddressValidatorTest);
+};
+
+// Use this test fixture if you're going to use a region with a large set of
+// validation rules. All rules should be loaded in SetUpTestCase().
+class LargeAddressValidatorTest : public testing::Test {
+ protected:
+ LargeAddressValidatorTest() {}
+ virtual ~LargeAddressValidatorTest() {}
+
+ static void SetUpTestCase() {
+ validator_ =
+ new AddressValidator(scoped_ptr<Source>(new TestdataSource(true)),
+ scoped_ptr<Storage>(new NullStorage),
+ NULL);
+ validator_->LoadRules("CN");
+ validator_->LoadRules("KR");
+ validator_->LoadRules("TW");
+ }
+
+ static void TearDownTestcase() {
+ delete validator_;
+ validator_ = NULL;
+ }
+
+ // Owned shared instance of validator with large sets validation rules.
+ static AddressValidator* validator_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LargeAddressValidatorTest);
+};
+
+AddressValidator* LargeAddressValidatorTest::validator_ = NULL;
+
+TEST_F(AddressValidatorTest, RegionHasRules) {
+ const std::vector<std::string>& region_codes = GetRegionCodes();
+ AddressData address;
+ for (size_t i = 0; i < region_codes.size(); ++i) {
+ SCOPED_TRACE("For region: " + region_codes[i]);
+ validator_->LoadRules(region_codes[i]);
+ address.region_code = region_codes[i];
+ FieldProblemMap dummy;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->ValidateAddress(address, NULL, &dummy));
+ }
+}
+
+TEST_F(AddressValidatorTest, EmptyAddressNoFatalFailure) {
+ AddressData address;
+ address.region_code = "US";
+
+ FieldProblemMap dummy;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->ValidateAddress(address, NULL, &dummy));
+}
+
+TEST_F(AddressValidatorTest, UsStateNamesAreValidEntries) {
+ AddressData address;
+ address.region_code = "US";
+ address.administrative_area = "California";
+
+ FieldProblemMap filter;
+ filter.insert(std::make_pair(ADMIN_AREA, UNKNOWN_VALUE));
+ FieldProblemMap problems;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->ValidateAddress(address, &filter, &problems));
+ EXPECT_TRUE(problems.empty());
+}
+
+TEST_F(AddressValidatorTest, USZipCode) {
+ AddressData address;
+ address.recipient = "Mr. Smith";
+ address.address_line.push_back("340 Main St.");
+ address.locality = "Venice";
+ address.administrative_area = "CA";
+ address.region_code = "US";
+
+ // Valid Californian zip code.
+ address.postal_code = "90291";
+ FieldProblemMap problems;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->ValidateAddress(address, NULL, &problems));
+ EXPECT_TRUE(problems.empty());
+
+ problems.clear();
+
+ // An extended, valid Californian zip code.
+ address.postal_code = "90210-1234";
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->ValidateAddress(address, NULL, &problems));
+ EXPECT_TRUE(problems.empty());
+
+ problems.clear();
+
+ // New York zip code (which is invalid for California).
+ address.postal_code = "12345";
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->ValidateAddress(address, NULL, &problems));
+ EXPECT_EQ(1U, problems.size());
+ EXPECT_EQ(problems.begin()->first, POSTAL_CODE);
+ EXPECT_EQ(problems.begin()->second, MISMATCHING_VALUE);
+
+ problems.clear();
+
+ // A zip code with a "90" in the middle.
+ address.postal_code = "12903";
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->ValidateAddress(address, NULL, &problems));
+ EXPECT_EQ(1U, problems.size());
+ EXPECT_EQ(problems.begin()->first, POSTAL_CODE);
+ EXPECT_EQ(problems.begin()->second, MISMATCHING_VALUE);
+
+ problems.clear();
+
+ // Invalid zip code (too many digits).
+ address.postal_code = "902911";
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->ValidateAddress(address, NULL, &problems));
+ EXPECT_EQ(1U, problems.size());
+ EXPECT_EQ(problems.begin()->first, POSTAL_CODE);
+ EXPECT_EQ(problems.begin()->second, INVALID_FORMAT);
+
+ problems.clear();
+
+ // Invalid zip code (too few digits).
+ address.postal_code = "9029";
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->ValidateAddress(address, NULL, &problems));
+ EXPECT_EQ(1U, problems.size());
+ EXPECT_EQ(problems.begin()->first, POSTAL_CODE);
+ EXPECT_EQ(problems.begin()->second, INVALID_FORMAT);
+}
+
+TEST_F(AddressValidatorTest, BasicValidation) {
+ // US rules should always be available, even though this load call fails.
+ validator_->LoadRules("US");
+ AddressData address;
+ address.region_code = "US";
+ address.language_code = "en";
+ address.administrative_area = "TX";
+ address.locality = "Paris";
+ address.postal_code = "75461";
+ address.address_line.push_back("123 Main St");
+ address.recipient = "Mr. Smith";
+ FieldProblemMap problems;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->ValidateAddress(address, NULL, &problems));
+ EXPECT_TRUE(problems.empty());
+
+ // The display name works as well as the key.
+ address.administrative_area = "Texas";
+ problems.clear();
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->ValidateAddress(address, NULL, &problems));
+ EXPECT_TRUE(problems.empty());
+
+ // Ignore capitalization.
+ address.administrative_area = "tx";
+ problems.clear();
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->ValidateAddress(address, NULL, &problems));
+ EXPECT_TRUE(problems.empty());
+
+ // Ignore capitalization.
+ address.administrative_area = "teXas";
+ problems.clear();
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->ValidateAddress(address, NULL, &problems));
+ EXPECT_TRUE(problems.empty());
+
+ // Ignore diacriticals.
+ address.administrative_area = base::WideToUTF8(L"T\u00E9xas");
+ problems.clear();
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->ValidateAddress(address, NULL, &problems));
+ EXPECT_TRUE(problems.empty());
+}
+
+TEST_F(AddressValidatorTest, BasicValidationFailure) {
+ // US rules should always be available, even though this load call fails.
+ validator_->LoadRules("US");
+ AddressData address;
+ address.region_code = "US";
+ address.language_code = "en";
+ address.administrative_area = "XT";
+ address.locality = "Paris";
+ address.postal_code = "75461";
+ address.address_line.push_back("123 Main St");
+ address.recipient = "Mr. Smith";
+ FieldProblemMap problems;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->ValidateAddress(address, NULL, &problems));
+
+ ASSERT_EQ(1U, problems.size());
+ EXPECT_EQ(UNKNOWN_VALUE, problems.begin()->second);
+ EXPECT_EQ(ADMIN_AREA, problems.begin()->first);
+}
+
+TEST_F(AddressValidatorTest, NoNullSuggestionsCrash) {
+ AddressData address;
+ address.region_code = "US";
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, COUNTRY, 1, NULL));
+}
+
+TEST_F(AddressValidatorTest, SuggestAdminAreaForPostalCode) {
+ AddressData address;
+ address.region_code = "US";
+ address.postal_code = "90291";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, POSTAL_CODE, 1, &suggestions));
+ ASSERT_EQ(1U, suggestions.size());
+ EXPECT_EQ("CA", suggestions[0].administrative_area);
+ EXPECT_EQ("90291", suggestions[0].postal_code);
+}
+
+TEST_F(LargeAddressValidatorTest, SuggestLocalityForPostalCodeWithAdminArea) {
+ AddressData address;
+ address.region_code = "TW";
+ address.postal_code = "515";
+ address.administrative_area = "Changhua";
+ address.language_code = "zh-Latn";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, POSTAL_CODE, 1, &suggestions));
+ ASSERT_EQ(1U, suggestions.size());
+ EXPECT_EQ("Dacun Township", suggestions[0].locality);
+ EXPECT_EQ("Changhua County", suggestions[0].administrative_area);
+ EXPECT_EQ("515", suggestions[0].postal_code);
+}
+
+TEST_F(LargeAddressValidatorTest, SuggestAdminAreaForPostalCodeWithLocality) {
+ AddressData address;
+ address.region_code = "TW";
+ address.postal_code = "515";
+ address.locality = "Dacun";
+ address.language_code = "zh-Latn";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, POSTAL_CODE, 1, &suggestions));
+ ASSERT_EQ(1U, suggestions.size());
+ EXPECT_EQ("Dacun Township", suggestions[0].locality);
+ EXPECT_EQ("Changhua County", suggestions[0].administrative_area);
+ EXPECT_EQ("515", suggestions[0].postal_code);
+}
+
+TEST_F(AddressValidatorTest, NoSuggestForPostalCodeWithWrongAdminArea) {
+ AddressData address;
+ address.region_code = "US";
+ address.postal_code = "90066";
+ address.postal_code = "TX";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, POSTAL_CODE, 1, &suggestions));
+ EXPECT_TRUE(suggestions.empty());
+}
+
+TEST_F(LargeAddressValidatorTest, SuggestForLocality) {
+ AddressData address;
+ address.region_code = "CN";
+ address.locality = "Anqin";
+ address.language_code = "zh-Latn";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, LOCALITY, 10, &suggestions));
+ ASSERT_EQ(1U, suggestions.size());
+ EXPECT_EQ("Anqing Shi", suggestions[0].locality);
+ EXPECT_EQ("Anhui Sheng", suggestions[0].administrative_area);
+}
+
+TEST_F(LargeAddressValidatorTest, SuggestForLocalityAndAdminArea) {
+ AddressData address;
+ address.region_code = "CN";
+ address.locality = "Anqing";
+ address.administrative_area = "Anhui";
+ address.language_code = "zh-Latn";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, LOCALITY, 10, &suggestions));
+ ASSERT_EQ(1U, suggestions.size());
+ EXPECT_TRUE(suggestions[0].dependent_locality.empty());
+ EXPECT_EQ("Anqing Shi", suggestions[0].locality);
+ EXPECT_EQ("Anhui Sheng", suggestions[0].administrative_area);
+}
+
+TEST_F(LargeAddressValidatorTest, SuggestForAdminAreaAndLocality) {
+ AddressData address;
+ address.region_code = "CN";
+ address.locality = "Anqing";
+ address.administrative_area = "Anhui";
+ address.language_code = "zh-Latn";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, ADMIN_AREA, 10, &suggestions));
+ ASSERT_EQ(1U, suggestions.size());
+ EXPECT_TRUE(suggestions[0].dependent_locality.empty());
+ EXPECT_TRUE(suggestions[0].locality.empty());
+ EXPECT_EQ("Anhui Sheng", suggestions[0].administrative_area);
+}
+
+TEST_F(LargeAddressValidatorTest, SuggestForDependentLocality) {
+ AddressData address;
+ address.region_code = "CN";
+ address.dependent_locality = "Zongyang";
+ address.language_code = "zh-Latn";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(
+ address, DEPENDENT_LOCALITY, 10, &suggestions));
+ ASSERT_EQ(1U, suggestions.size());
+ EXPECT_EQ("Zongyang Xian", suggestions[0].dependent_locality);
+ EXPECT_EQ("Anqing Shi", suggestions[0].locality);
+ EXPECT_EQ("Anhui Sheng", suggestions[0].administrative_area);
+}
+
+TEST_F(LargeAddressValidatorTest,
+ NoSuggestForDependentLocalityWithWrongAdminArea) {
+ AddressData address;
+ address.region_code = "CN";
+ address.dependent_locality = "Zongyang";
+ address.administrative_area = "Sichuan Sheng";
+ address.language_code = "zh-Latn";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(
+ address, DEPENDENT_LOCALITY, 10, &suggestions));
+ EXPECT_TRUE(suggestions.empty());
+}
+
+TEST_F(AddressValidatorTest, EmptySuggestionsOverLimit) {
+ AddressData address;
+ address.region_code = "US";
+ address.administrative_area = "A";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, ADMIN_AREA, 1, &suggestions));
+ EXPECT_TRUE(suggestions.empty());
+}
+
+TEST_F(AddressValidatorTest, PreferShortSuggestions) {
+ AddressData address;
+ address.region_code = "US";
+ address.administrative_area = "CA";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, ADMIN_AREA, 10, &suggestions));
+ ASSERT_EQ(1U, suggestions.size());
+ EXPECT_EQ("CA", suggestions[0].administrative_area);
+}
+
+TEST_F(AddressValidatorTest, SuggestTheSingleMatchForFullMatchName) {
+ AddressData address;
+ address.region_code = "US";
+ address.administrative_area = "Texas";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, ADMIN_AREA, 10, &suggestions));
+ ASSERT_EQ(1U, suggestions.size());
+ EXPECT_EQ("Texas", suggestions[0].administrative_area);
+}
+
+TEST_F(AddressValidatorTest, SuggestAdminArea) {
+ AddressData address;
+ address.region_code = "US";
+ address.administrative_area = "Cali";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, ADMIN_AREA, 10, &suggestions));
+ ASSERT_EQ(1U, suggestions.size());
+ EXPECT_EQ("California", suggestions[0].administrative_area);
+}
+
+TEST_F(AddressValidatorTest, MultipleSuggestions) {
+ AddressData address;
+ address.region_code = "US";
+ address.administrative_area = "MA";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, ADMIN_AREA, 10, &suggestions));
+ EXPECT_LT(1U, suggestions.size());
+
+ // Massachusetts should not be a suggestion, because it's already covered
+ // under MA.
+ std::set<std::string> expected_suggestions;
+ expected_suggestions.insert("MA");
+ expected_suggestions.insert("Maine");
+ expected_suggestions.insert("Marshall Islands");
+ expected_suggestions.insert("Maryland");
+ for (std::vector<AddressData>::const_iterator it = suggestions.begin();
+ it != suggestions.end();
+ ++it) {
+ expected_suggestions.erase(it->administrative_area);
+ }
+ EXPECT_TRUE(expected_suggestions.empty());
+}
+
+TEST_F(LargeAddressValidatorTest, SuggestNonLatinKeyWhenLanguageMatches) {
+ AddressData address;
+ address.language_code = "ko";
+ address.region_code = "KR";
+ address.postal_code = "210-210";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, POSTAL_CODE, 1, &suggestions));
+ ASSERT_EQ(1U, suggestions.size());
+ EXPECT_EQ("강원도", suggestions[0].administrative_area);
+ EXPECT_EQ("210-210", suggestions[0].postal_code);
+}
+
+TEST_F(LargeAddressValidatorTest, SuggestNonLatinKeyWhenUserInputIsNotLatin) {
+ AddressData address;
+ address.language_code = "en";
+ address.region_code = "KR";
+ address.administrative_area = "강원";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, ADMIN_AREA, 1, &suggestions));
+ ASSERT_EQ(1U, suggestions.size());
+ EXPECT_EQ("강원도", suggestions[0].administrative_area);
+}
+
+TEST_F(LargeAddressValidatorTest,
+ SuggestLatinNameWhenLanguageDiffersAndLatinNameAvailable) {
+ AddressData address;
+ address.language_code = "ko-Latn";
+ address.region_code = "KR";
+ address.postal_code = "210-210";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, POSTAL_CODE, 1, &suggestions));
+ ASSERT_EQ(1U, suggestions.size());
+ EXPECT_EQ("Gangwon", suggestions[0].administrative_area);
+ EXPECT_EQ("210-210", suggestions[0].postal_code);
+}
+
+TEST_F(AddressValidatorTest, NoSuggestionsForEmptyAddress) {
+ AddressData address;
+ address.region_code = "US";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(
+ AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, POSTAL_CODE, 999, &suggestions));
+ EXPECT_TRUE(suggestions.empty());
+}
+
+TEST_F(AddressValidatorTest, SuggestionIncludesCountry) {
+ AddressData address;
+ address.region_code = "US";
+ address.postal_code = "90291";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, POSTAL_CODE, 1, &suggestions));
+ ASSERT_EQ(1U, suggestions.size());
+ EXPECT_EQ("US", suggestions[0].region_code);
+}
+
+TEST_F(AddressValidatorTest, InvalidPostalCodeNoSuggestions) {
+ AddressData address;
+ address.region_code = "US";
+ address.postal_code = "0";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(
+ AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, POSTAL_CODE, 999, &suggestions));
+ EXPECT_TRUE(suggestions.empty());
+}
+
+TEST_F(AddressValidatorTest, MismatchedPostalCodeNoSuggestions) {
+ AddressData address;
+ address.region_code = "US";
+ address.administrative_area = "TX";
+ address.postal_code = "90291";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(
+ AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, POSTAL_CODE, 999, &suggestions));
+ EXPECT_TRUE(suggestions.empty());
+}
+
+TEST_F(AddressValidatorTest, SuggestOnlyForAdministrativeAreasAndPostalCode) {
+ AddressData address;
+ address.region_code = "US";
+ address.administrative_area = "CA";
+ address.locality = "Los Angeles";
+ address.dependent_locality = "Venice";
+ address.postal_code = "90291";
+ address.sorting_code = "123";
+ address.address_line.push_back("123 Main St");
+ address.recipient = "Jon Smith";
+
+ // Fields that should not have suggestions in US.
+ static const AddressField kNoSugestFields[] = {
+ COUNTRY,
+ LOCALITY,
+ DEPENDENT_LOCALITY,
+ SORTING_CODE,
+ STREET_ADDRESS,
+ RECIPIENT
+ };
+
+ static const size_t kNumNoSuggestFields =
+ sizeof kNoSugestFields / sizeof (AddressField);
+
+ for (size_t i = 0; i < kNumNoSuggestFields; ++i) {
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(
+ address, kNoSugestFields[i], 999, &suggestions));
+ EXPECT_TRUE(suggestions.empty());
+ }
+}
+
+TEST_F(AddressValidatorTest, SuggestionsAreCleared) {
+ AddressData address;
+ address.region_code = "US";
+
+ std::vector<AddressData> suggestions(1, address);
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, POSTAL_CODE, 1, &suggestions));
+ EXPECT_TRUE(suggestions.empty());
+}
+
+TEST_F(AddressValidatorTest, CanonicalizeUsAdminAreaName) {
+ AddressData address;
+ address.region_code = "US";
+ address.administrative_area = "cALIFORNIa";
+ EXPECT_TRUE(validator_->CanonicalizeAdministrativeArea(&address));
+ EXPECT_EQ("CA", address.administrative_area);
+}
+
+TEST_F(AddressValidatorTest, CanonicalizeUsAdminAreaKey) {
+ AddressData address;
+ address.region_code = "US";
+ address.administrative_area = "CA";
+ EXPECT_TRUE(validator_->CanonicalizeAdministrativeArea(&address));
+ EXPECT_EQ("CA", address.administrative_area);
+}
+
+TEST_F(AddressValidatorTest, CanonicalizeJpAdminAreaKey) {
+ validator_->LoadRules("JP");
+ AddressData address;
+ address.region_code = "JP";
+ address.administrative_area = "東京都";
+ EXPECT_TRUE(validator_->CanonicalizeAdministrativeArea(&address));
+ EXPECT_EQ("東京都", address.administrative_area);
+}
+
+TEST_F(AddressValidatorTest, CanonicalizeJpAdminAreaLatinName) {
+ validator_->LoadRules("JP");
+ AddressData address;
+ address.region_code = "JP";
+ address.administrative_area = "tOKYo";
+ EXPECT_TRUE(validator_->CanonicalizeAdministrativeArea(&address));
+ EXPECT_EQ("TOKYO", address.administrative_area);
+}
+
+TEST_F(AddressValidatorTest, TokushimaSuggestionIsValid) {
+ validator_->LoadRules("JP");
+ AddressData address;
+ address.region_code = "JP";
+ address.administrative_area = "Toku";
+ address.language_code = "ja-Latn";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, ADMIN_AREA, 1, &suggestions));
+ ASSERT_EQ(1U, suggestions.size());
+ EXPECT_EQ("TOKUSHIMA", suggestions[0].administrative_area);
+
+ FieldProblemMap filter;
+ for (int i = UNEXPECTED_FIELD; i <= USES_P_O_BOX; ++i)
+ filter.insert(std::make_pair(ADMIN_AREA, static_cast<AddressProblem>(i)));
+
+ FieldProblemMap problems;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->ValidateAddress(suggestions[0], &filter, &problems));
+ EXPECT_TRUE(problems.empty());
+}
+
+TEST_F(AddressValidatorTest, ValidPostalCodeInSuggestion) {
+ validator_->LoadRules("US");
+ AddressData address;
+ address.region_code = "US";
+ address.administrative_area = "New";
+ address.postal_code = "13699";
+
+ std::vector<AddressData> suggestions;
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, ADMIN_AREA, 999, &suggestions));
+ ASSERT_EQ(1U, suggestions.size());
+ EXPECT_EQ("New York", suggestions[0].administrative_area);
+
+ address.administrative_area = "New";
+ address.postal_code = "03755";
+
+ EXPECT_EQ(AddressValidator::SUCCESS,
+ validator_->GetSuggestions(address, ADMIN_AREA, 999, &suggestions));
+ ASSERT_EQ(1U, suggestions.size());
+ EXPECT_EQ("New Hampshire", suggestions[0].administrative_area);
+}
+
+TEST_F(AddressValidatorTest, ValidateRequiredFieldsWithoutRules) {
+ // Do not load the rules for JP.
+ AddressData address;
+ address.region_code = "JP";
+
+ FieldProblemMap problems;
+ EXPECT_EQ(AddressValidator::RULES_UNAVAILABLE,
+ validator_->ValidateAddress(address, NULL, &problems));
+ EXPECT_FALSE(problems.empty());
+
+ for (FieldProblemMap::const_iterator it = problems.begin();
+ it != problems.end();
+ ++it) {
+ EXPECT_EQ(MISSING_REQUIRED_FIELD, it->second);
+ }
+}
+
+TEST_F(AddressValidatorTest,
+ DoNotValidateRequiredFieldsWithoutRulesWhenErorrIsFiltered) {
+ // Do not load the rules for JP.
+ AddressData address;
+ address.region_code = "JP";
+
+ FieldProblemMap filter;
+ filter.insert(std::make_pair(COUNTRY, UNKNOWN_VALUE));
+
+ FieldProblemMap problems;
+ EXPECT_EQ(AddressValidator::RULES_UNAVAILABLE,
+ validator_->ValidateAddress(address, &filter, &problems));
+ EXPECT_TRUE(problems.empty());
+}
+
+// Use this test fixture for configuring the number of failed attempts to load
+// rules.
+class FailingAddressValidatorTest : public testing::Test, LoadRulesListener {
+ protected:
+ // A validator that retries loading rules without delay.
+ class TestAddressValidator : public AddressValidator {
+ public:
+ // Takes ownership of |source| and |storage|.
+ TestAddressValidator(scoped_ptr<::i18n::addressinput::Source> source,
+ scoped_ptr<::i18n::addressinput::Storage> storage,
+ LoadRulesListener* load_rules_listener)
+ : AddressValidator(std::move(source),
+ std::move(storage),
+ load_rules_listener) {}
+
+ virtual ~TestAddressValidator() {}
+
+ protected:
+ virtual base::TimeDelta GetBaseRetryPeriod() const override {
+ return base::TimeDelta::FromSeconds(0);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestAddressValidator);
+ };
+
+ // A source that always fails |failures_number| times before downloading
+ // data.
+ class FailingSource : public Source {
+ public:
+ explicit FailingSource()
+ : failures_number_(0), attempts_number_(0), actual_source_(true) {}
+ virtual ~FailingSource() {}
+
+ // Sets the number of times to fail before downloading data.
+ void set_failures_number(int failures_number) {
+ failures_number_ = failures_number;
+ }
+
+ // Source implementation.
+ // Always fails for the first |failures_number| times.
+ virtual void Get(const std::string& url,
+ const Callback& callback) const override {
+ ++attempts_number_;
+ // |callback| takes ownership of the |new std::string|.
+ if (failures_number_-- > 0)
+ callback(false, url, new std::string);
+ else
+ actual_source_.Get(url, callback);
+ }
+
+ // Returns the number of download attempts.
+ int attempts_number() const { return attempts_number_; }
+
+ private:
+ // The number of times to fail before downloading data.
+ mutable int failures_number_;
+
+ // The number of times Get was called.
+ mutable int attempts_number_;
+
+ // The source to use for successful downloads.
+ TestdataSource actual_source_;
+
+ DISALLOW_COPY_AND_ASSIGN(FailingSource);
+ };
+
+ FailingAddressValidatorTest()
+ : source_(new FailingSource),
+ validator_(
+ new TestAddressValidator(scoped_ptr<Source>(source_),
+ scoped_ptr<Storage>(new NullStorage),
+ this)),
+ load_rules_success_(false) {}
+
+ virtual ~FailingAddressValidatorTest() {}
+
+ FailingSource* source_; // Owned by |validator_|.
+ scoped_ptr<AddressValidator> validator_;
+ bool load_rules_success_;
+
+ private:
+ // LoadRulesListener implementation.
+ virtual void OnAddressValidationRulesLoaded(const std::string&,
+ bool success) override {
+ load_rules_success_ = success;
+ }
+
+ base::MessageLoop ui_;
+
+ DISALLOW_COPY_AND_ASSIGN(FailingAddressValidatorTest);
+};
+
+// The validator will attempt to load rules at most 8 times.
+TEST_F(FailingAddressValidatorTest, RetryLoadingRulesHasLimit) {
+ source_->set_failures_number(99);
+ validator_->LoadRules("CH");
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_FALSE(load_rules_success_);
+ EXPECT_EQ(8, source_->attempts_number());
+}
+
+// The validator will load rules successfully if the source returns data
+// before the maximum number of retries.
+TEST_F(FailingAddressValidatorTest, RuleRetryingWillSucceed) {
+ source_->set_failures_number(4);
+ validator_->LoadRules("CH");
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_TRUE(load_rules_success_);
+ EXPECT_EQ(5, source_->attempts_number());
+}
+
+// The delayed task to retry loading rules should stop (instead of crashing) if
+// the validator is destroyed before it fires.
+TEST_F(FailingAddressValidatorTest, DestroyedValidatorStopsRetries) {
+ source_->set_failures_number(4);
+ validator_->LoadRules("CH");
+
+ // Destroy the validator.
+ validator_.reset();
+
+ // Fire the delayed task to retry loading rules.
+ EXPECT_NO_FATAL_FAILURE(base::RunLoop().RunUntilIdle());
+}
+
+// Each call to LoadRules should reset the number of retry attempts. If the
+// first call to LoadRules exceeded the maximum number of retries, the second
+// call to LoadRules should start counting the retries from zero.
+TEST_F(FailingAddressValidatorTest, LoadingRulesSecondTimeSucceeds) {
+ source_->set_failures_number(11);
+ validator_->LoadRules("CH");
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_FALSE(load_rules_success_);
+ EXPECT_EQ(8, source_->attempts_number());
+
+ validator_->LoadRules("CH");
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_TRUE(load_rules_success_);
+ EXPECT_EQ(12, source_->attempts_number());
+}
+
+// Calling LoadRules("CH") and LoadRules("GB") simultaneously should attempt to
+// load both rules up to the maximum number of attempts for each region.
+TEST_F(FailingAddressValidatorTest, RegionsShouldRetryIndividually) {
+ source_->set_failures_number(99);
+ validator_->LoadRules("CH");
+ validator_->LoadRules("GB");
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_FALSE(load_rules_success_);
+ EXPECT_EQ(16, source_->attempts_number());
+}
+
+} // namespace autofill
diff --git a/chromium/third_party/libaddressinput/chromium/chrome_metadata_source.cc b/chromium/third_party/libaddressinput/chromium/chrome_metadata_source.cc
new file mode 100644
index 00000000000..57c5acd4c25
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/chrome_metadata_source.cc
@@ -0,0 +1,112 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/libaddressinput/chromium/chrome_metadata_source.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_response_writer.h"
+#include "url/gurl.h"
+
+namespace autofill {
+
+namespace {
+
+// A URLFetcherResponseWriter that writes into a provided buffer.
+class UnownedStringWriter : public net::URLFetcherResponseWriter {
+ public:
+ UnownedStringWriter(std::string* data) : data_(data) {}
+ virtual ~UnownedStringWriter() {}
+
+ virtual int Initialize(const net::CompletionCallback& callback) override {
+ data_->clear();
+ return net::OK;
+ }
+
+ virtual int Write(net::IOBuffer* buffer,
+ int num_bytes,
+ const net::CompletionCallback& callback) override {
+ data_->append(buffer->data(), num_bytes);
+ return num_bytes;
+ }
+
+ virtual int Finish(const net::CompletionCallback& callback) override {
+ return net::OK;
+ }
+
+ private:
+ std::string* data_; // weak reference.
+
+ DISALLOW_COPY_AND_ASSIGN(UnownedStringWriter);
+};
+
+} // namespace
+
+ChromeMetadataSource::ChromeMetadataSource(
+ const std::string& validation_data_url,
+ net::URLRequestContextGetter* getter)
+ : validation_data_url_(validation_data_url),
+ getter_(getter) {}
+
+ChromeMetadataSource::~ChromeMetadataSource() {
+ STLDeleteValues(&requests_);
+}
+
+void ChromeMetadataSource::Get(const std::string& key,
+ const Callback& downloaded) const {
+ const_cast<ChromeMetadataSource*>(this)->Download(key, downloaded);
+}
+
+void ChromeMetadataSource::OnURLFetchComplete(const net::URLFetcher* source) {
+ std::map<const net::URLFetcher*, Request*>::iterator request =
+ requests_.find(source);
+ DCHECK(request != requests_.end());
+
+ bool ok = source->GetResponseCode() == net::HTTP_OK;
+ scoped_ptr<std::string> data(new std::string());
+ if (ok)
+ data->swap(request->second->data);
+ request->second->callback(ok, request->second->key, data.release());
+
+ delete request->second;
+ requests_.erase(request);
+}
+
+ChromeMetadataSource::Request::Request(const std::string& key,
+ scoped_ptr<net::URLFetcher> fetcher,
+ const Callback& callback)
+ : key(key), fetcher(std::move(fetcher)), callback(callback) {}
+
+void ChromeMetadataSource::Download(const std::string& key,
+ const Callback& downloaded) {
+ GURL resource(validation_data_url_ + key);
+ if (!resource.SchemeIsCryptographic()) {
+ downloaded(false, key, NULL);
+ return;
+ }
+
+ scoped_ptr<net::URLFetcher> fetcher =
+ net::URLFetcher::Create(resource, net::URLFetcher::GET, this);
+ fetcher->SetLoadFlags(
+ net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES);
+ fetcher->SetRequestContext(getter_);
+
+ Request* request = new Request(key, std::move(fetcher), downloaded);
+ request->fetcher->SaveResponseWithWriter(
+ scoped_ptr<net::URLFetcherResponseWriter>(
+ new UnownedStringWriter(&request->data)));
+ requests_[request->fetcher.get()] = request;
+ request->fetcher->Start();
+}
+
+} // namespace autofill
diff --git a/chromium/third_party/libaddressinput/chromium/chrome_metadata_source.h b/chromium/third_party/libaddressinput/chromium/chrome_metadata_source.h
new file mode 100644
index 00000000000..624e6e4fe5f
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/chrome_metadata_source.h
@@ -0,0 +1,66 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_CHROME_METADATA_SOURCE_H_
+#define THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_CHROME_METADATA_SOURCE_H_
+
+#include <map>
+#include <string>
+
+#include "base/macros.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/source.h"
+
+namespace net {
+class URLFetcher;
+class URLRequestContextGetter;
+}
+
+namespace autofill {
+
+// A class for downloading rules to let libaddressinput validate international
+// addresses.
+class ChromeMetadataSource : public ::i18n::addressinput::Source,
+ public net::URLFetcherDelegate {
+ public:
+ ChromeMetadataSource(const std::string& validation_data_url,
+ net::URLRequestContextGetter* getter);
+ virtual ~ChromeMetadataSource();
+
+ // ::i18n::addressinput::Source:
+ virtual void Get(const std::string& key,
+ const Callback& downloaded) const override;
+
+ // net::URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const net::URLFetcher* source) override;
+
+ private:
+ struct Request {
+ Request(const std::string& key,
+ scoped_ptr<net::URLFetcher> fetcher,
+ const Callback& callback);
+
+ std::string key;
+ // The data that's received.
+ std::string data;
+ // The object that manages retrieving the data.
+ scoped_ptr<net::URLFetcher> fetcher;
+ const Callback& callback;
+ };
+
+ // Non-const method actually implementing Get().
+ void Download(const std::string& key, const Callback& downloaded);
+
+ const std::string validation_data_url_;
+ net::URLRequestContextGetter* const getter_; // weak
+
+ // Maps from active URL fetcher to request metadata. The value is owned.
+ std::map<const net::URLFetcher*, Request*> requests_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeMetadataSource);
+};
+
+} // namespace autofill
+
+#endif // THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_CHROME_METADATA_SOURCE_H_
diff --git a/chromium/third_party/libaddressinput/chromium/chrome_metadata_source_unittest.cc b/chromium/third_party/libaddressinput/chromium/chrome_metadata_source_unittest.cc
new file mode 100644
index 00000000000..ae92a76654b
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/chrome_metadata_source_unittest.cc
@@ -0,0 +1,100 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/libaddressinput/chromium/chrome_metadata_source.h"
+
+#include "base/thread_task_runner_handle.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace autofill {
+
+static const char kFakeUrl[] = "https://example.com";
+static const char kFakeInsecureUrl[] = "http://example.com";
+
+class ChromeMetadataSourceTest : public testing::Test {
+ public:
+ ChromeMetadataSourceTest()
+ : fake_factory_(&factory_),
+ success_(false) {}
+ virtual ~ChromeMetadataSourceTest() {}
+
+ protected:
+ // Sets the response for the download.
+ void SetFakeResponse(const std::string& payload, net::HttpStatusCode code) {
+ fake_factory_.SetFakeResponse(url_,
+ payload,
+ code,
+ net::URLRequestStatus::SUCCESS);
+ }
+
+ // Kicks off the download.
+ void Get() {
+ scoped_refptr<net::TestURLRequestContextGetter> getter(
+ new net::TestURLRequestContextGetter(
+ base::ThreadTaskRunnerHandle::Get()));
+ ChromeMetadataSource impl(std::string(), getter.get());
+ scoped_ptr< ::i18n::addressinput::Source::Callback> callback(
+ ::i18n::addressinput::BuildCallback(
+ this, &ChromeMetadataSourceTest::OnDownloaded));
+ impl.Get(url_.spec(), *callback);
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ void set_url(const GURL& url) { url_ = url; }
+ bool success() const { return success_; }
+ bool has_data() const { return !!data_; }
+
+ const std::string& data() const {
+ DCHECK(data_);
+ return *data_;
+ }
+
+ private:
+ // Callback for when download is finished.
+ void OnDownloaded(bool success,
+ const std::string& url,
+ std::string* data) {
+ ASSERT_FALSE(success && data == NULL);
+ success_ = success;
+ data_.reset(data);
+ }
+
+ base::MessageLoop loop_;
+ net::URLFetcherImplFactory factory_;
+ net::FakeURLFetcherFactory fake_factory_;
+ GURL url_;
+ scoped_ptr<std::string> data_;
+ bool success_;
+};
+
+TEST_F(ChromeMetadataSourceTest, Success) {
+ const char kFakePayload[] = "ham hock";
+ set_url(GURL(kFakeUrl));
+ SetFakeResponse(kFakePayload, net::HTTP_OK);
+ Get();
+ EXPECT_TRUE(success());
+ EXPECT_EQ(kFakePayload, data());
+}
+
+TEST_F(ChromeMetadataSourceTest, Failure) {
+ const char kFakePayload[] = "ham hock";
+ set_url(GURL(kFakeUrl));
+ SetFakeResponse(kFakePayload, net::HTTP_INTERNAL_SERVER_ERROR);
+ Get();
+ EXPECT_FALSE(success());
+ EXPECT_TRUE(!has_data() || data().empty());
+}
+
+TEST_F(ChromeMetadataSourceTest, RejectsInsecureScheme) {
+ const char kFakePayload[] = "ham hock";
+ set_url(GURL(kFakeInsecureUrl));
+ SetFakeResponse(kFakePayload, net::HTTP_OK);
+ Get();
+ EXPECT_FALSE(success());
+ EXPECT_TRUE(!has_data() || data().empty());
+}
+
+} // namespace autofill
diff --git a/chromium/third_party/libaddressinput/chromium/chrome_rule_test.cc b/chromium/third_party/libaddressinput/chromium/chrome_rule_test.cc
new file mode 100644
index 00000000000..e0d5c2f16bc
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/chrome_rule_test.cc
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/libaddressinput/src/cpp/src/rule.h"
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(RuleTest, CanonicalizeSubKeyTest) {
+ i18n::addressinput::Rule rule;
+ ASSERT_TRUE(rule.ParseSerializedRule(base::WideToUTF8(
+ L"{ \"sub_keys\": \"FOO~BAR~B\u00C4Z~\u0415\u0416\","
+ L" \"sub_names\": \"Foolandia~Bartopolis~B\u00E4zmonia~"
+ L"\u0415\u0436ville\" }")));
+ EXPECT_EQ(4U, rule.GetSubKeys().size());
+
+ static const struct {
+ const wchar_t* input;
+ // If empty, expect failure.
+ const wchar_t* output;
+ } expectations[] = {
+ { L"foo", L"FOO" },
+ { L"Foo", L"FOO" },
+ { L"FOO", L"FOO" },
+ { L"F", L"" },
+ { L"FOO2", L"" },
+ { L"", L"" },
+ { L"Bar", L"BAR" },
+ { L"Bartopolis", L"BAR" },
+ { L"BARTOPOLIS", L"BAR" },
+ { L"BaRToPoLiS", L"BAR" },
+ // Diacriticals.
+ { L"B\u00C4Z", L"B\u00C4Z" },
+ { L"BAZ", L"B\u00C4Z" },
+ { L"B\u00E4zmonia", L"B\u00C4Z" },
+ { L"bazmonia", L"B\u00C4Z" },
+ // Non-ascii (Cyrillic) case sensitivity.
+ { L"\u0415\u0416", L"\u0415\u0416" },
+ { L"\u0415\u0416VILLE", L"\u0415\u0416" },
+ { L"\u0435\u0436", L"\u0415\u0416" },
+ { L"\u0435\u0436VILLE", L"\u0415\u0416" },
+ { L"\u0435\u0436VILL", L"" },
+ };
+
+ const size_t num_cases = sizeof(expectations) / sizeof(expectations[0]);
+ for (size_t i = 0; i < num_cases; ++i) {
+ const std::string input(base::WideToUTF8(expectations[i].input));
+ const std::string expected_output(base::WideToUTF8(expectations[i].output));
+ std::string output;
+ EXPECT_EQ(!expected_output.empty(),
+ rule.CanonicalizeSubKey(input, true, &output))
+ << "Failed for input " << input;
+ EXPECT_EQ(expected_output, output);
+ }
+}
diff --git a/chromium/third_party/libaddressinput/chromium/chrome_storage_impl.cc b/chromium/third_party/libaddressinput/chromium/chrome_storage_impl.cc
new file mode 100644
index 00000000000..918d3f2ee01
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/chrome_storage_impl.cc
@@ -0,0 +1,74 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/libaddressinput/chromium/chrome_storage_impl.h"
+
+#include <utility>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "components/prefs/writeable_pref_store.h"
+#include "third_party/libaddressinput/chromium/fallback_data_store.h"
+
+namespace autofill {
+
+ChromeStorageImpl::ChromeStorageImpl(WriteablePrefStore* store)
+ : backing_store_(store),
+ scoped_observer_(this) {
+ scoped_observer_.Add(backing_store_);
+}
+
+ChromeStorageImpl::~ChromeStorageImpl() {}
+
+void ChromeStorageImpl::Put(const std::string& key, std::string* data) {
+ DCHECK(data);
+ scoped_ptr<std::string> owned_data(data);
+ scoped_ptr<base::StringValue> string_value(
+ new base::StringValue(std::string()));
+ string_value->GetString()->swap(*owned_data);
+ backing_store_->SetValue(key, std::move(string_value),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+}
+
+void ChromeStorageImpl::Get(const std::string& key,
+ const Storage::Callback& data_ready) const {
+ // |Get()| should not be const, so this is just a thunk that fixes that.
+ const_cast<ChromeStorageImpl*>(this)->DoGet(key, data_ready);
+}
+
+void ChromeStorageImpl::OnPrefValueChanged(const std::string& key) {}
+
+void ChromeStorageImpl::OnInitializationCompleted(bool succeeded) {
+ for (std::vector<Request*>::iterator iter = outstanding_requests_.begin();
+ iter != outstanding_requests_.end(); ++iter) {
+ DoGet((*iter)->key, (*iter)->callback);
+ }
+
+ outstanding_requests_.clear();
+}
+
+void ChromeStorageImpl::DoGet(const std::string& key,
+ const Storage::Callback& data_ready) {
+ if (!backing_store_->IsInitializationComplete()) {
+ outstanding_requests_.push_back(new Request(key, data_ready));
+ return;
+ }
+
+ const base::Value* value = NULL;
+ scoped_ptr<std::string> data(new std::string);
+ if (backing_store_->GetValue(key, &value) && value->GetAsString(data.get())) {
+ data_ready(true, key, data.release());
+ } else if (FallbackDataStore::Get(key, data.get())) {
+ data_ready(true, key, data.release());
+ } else {
+ data_ready(false, key, NULL);
+ }
+}
+
+ChromeStorageImpl::Request::Request(const std::string& key,
+ const Callback& callback)
+ : key(key),
+ callback(callback) {}
+
+} // namespace autofill
diff --git a/chromium/third_party/libaddressinput/chromium/chrome_storage_impl.h b/chromium/third_party/libaddressinput/chromium/chrome_storage_impl.h
new file mode 100644
index 00000000000..6a08adc59e2
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/chrome_storage_impl.h
@@ -0,0 +1,62 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_CHROME_STORAGE_IMPL_H_
+#define THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_CHROME_STORAGE_IMPL_H_
+
+#include <list>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_vector.h"
+#include "base/scoped_observer.h"
+#include "components/prefs/pref_store.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/storage.h"
+
+class WriteablePrefStore;
+
+namespace autofill {
+
+// An implementation of the Storage interface which passes through to an
+// underlying WriteablePrefStore.
+class ChromeStorageImpl : public ::i18n::addressinput::Storage,
+ public PrefStore::Observer {
+ public:
+ // |store| must outlive |this|.
+ explicit ChromeStorageImpl(WriteablePrefStore* store);
+ virtual ~ChromeStorageImpl();
+
+ // ::i18n::addressinput::Storage implementation.
+ virtual void Put(const std::string& key, std::string* data) override;
+ virtual void Get(const std::string& key, const Callback& data_ready)
+ const override;
+
+ // PrefStore::Observer implementation.
+ virtual void OnPrefValueChanged(const std::string& key) override;
+ virtual void OnInitializationCompleted(bool succeeded) override;
+
+ private:
+ struct Request {
+ Request(const std::string& key, const Callback& callback);
+
+ std::string key;
+ const Callback& callback;
+ };
+
+ // Non-const version of Get().
+ void DoGet(const std::string& key, const Callback& data_ready);
+
+ WriteablePrefStore* backing_store_; // weak
+
+ // Get requests that haven't yet been serviced.
+ ScopedVector<Request> outstanding_requests_;
+
+ ScopedObserver<PrefStore, ChromeStorageImpl> scoped_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeStorageImpl);
+};
+
+} // namespace autofill
+
+#endif // THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_CHROME_STORAGE_IMPL_H_
diff --git a/chromium/third_party/libaddressinput/chromium/chrome_storage_impl_unittest.cc b/chromium/third_party/libaddressinput/chromium/chrome_storage_impl_unittest.cc
new file mode 100644
index 00000000000..629b8ffc345
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/chrome_storage_impl_unittest.cc
@@ -0,0 +1,35 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/libaddressinput/chromium/chrome_storage_impl.h"
+
+#include <string>
+
+#include "components/prefs/value_map_pref_store.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/libaddressinput/chromium/storage_test_runner.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/callback.h"
+
+namespace autofill {
+
+// Tests for ChromeStorageImpl object.
+class ChromeStorageImplTest : public testing::Test {
+ protected:
+ ChromeStorageImplTest()
+ : store_(new ValueMapPrefStore()),
+ storage_(store_.get()),
+ runner_(&storage_) {}
+
+ virtual ~ChromeStorageImplTest() {}
+
+ scoped_refptr<ValueMapPrefStore> store_;
+ ChromeStorageImpl storage_;
+ StorageTestRunner runner_;
+};
+
+TEST_F(ChromeStorageImplTest, StandardStorageTests) {
+ EXPECT_NO_FATAL_FAILURE(runner_.RunAllTests());
+}
+
+} // namespace autofill
diff --git a/chromium/third_party/libaddressinput/chromium/fallback_data_store.cc b/chromium/third_party/libaddressinput/chromium/fallback_data_store.cc
new file mode 100644
index 00000000000..4c20aa10d27
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/fallback_data_store.cc
@@ -0,0 +1,203 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fallback_data_store.h"
+
+#include <string>
+
+namespace autofill {
+
+bool FallbackDataStore::Get(const std::string& key, std::string* data) {
+ if (key != "data/US")
+ return false;
+
+ // Available at https://i18napis.appspot.com/ssl-aggregate-address/data/US.
+ // The appended checksum is valid, but the timestamp is old.
+ data->assign(
+ "timestamp=0\n"
+ "checksum=38d4bcdadfe494ffe062a7ad668d66d6\n"
+ "{\"data/US/LA\": {\"lang\": \"en\", \"zipex\": \"70000,71599\", \"nam"
+ "e\": \"Louisiana\", \"zip\": \"70|71[0-5]\", \"key\": \"LA\", \"id\":"
+ " \"data/US/LA\"}, \"data/US/VT\": {\"lang\": \"en\", \"zipex\": \"0500"
+ "0,05999\", \"name\": \"Vermont\", \"zip\": \"05\", \"key\": \"VT\", \""
+ "id\": \"data/US/VT\"}, \"data/US/NM\": {\"lang\": \"en\", \"zipex\": \""
+ "87000,88499\", \"name\": \"New Mexico\", \"zip\": \"87|88[0-4]\", \"k"
+ "ey\": \"NM\", \"id\": \"data/US/NM\"}, \"data/US/NJ\": {\"lang\": \"e"
+ "n\", \"zipex\": \"07000,08999\", \"name\": \"New Jersey\", \"zip\": \""
+ "0[78]\", \"key\": \"NJ\", \"id\": \"data/US/NJ\"}, \"data/US/NH\": {\""
+ "lang\": \"en\", \"zipex\": \"03000,03899\", \"name\": \"New Hampshire\""
+ ", \"zip\": \"03[0-8]\", \"key\": \"NH\", \"id\": \"data/US/NH\"}, \"d"
+ "ata/US/ND\": {\"lang\": \"en\", \"zipex\": \"58000,58999\", \"name\":"
+ " \"North Dakota\", \"zip\": \"58\", \"key\": \"ND\", \"id\": \"data/US"
+ "/ND\"}, \"data/US/NE\": {\"lang\": \"en\", \"zipex\": \"68000,69999\","
+ " \"name\": \"Nebraska\", \"zip\": \"6[89]\", \"key\": \"NE\", \"id\":"
+ " \"data/US/NE\"}, \"data/US/NC\": {\"lang\": \"en\", \"zipex\": \"2700"
+ "0,28999\", \"name\": \"North Carolina\", \"zip\": \"2[78]\", \"key\":"
+ " \"NC\", \"id\": \"data/US/NC\"}, \"data/US/PR\": {\"lang\": \"en\", \""
+ "zipex\": \"00600,00799:00900,00999\", \"name\": \"Puerto Rico\", \"zi"
+ "p\": \"00[679]\", \"key\": \"PR\", \"id\": \"data/US/PR\"}, \"data/US/"
+ "RI\": {\"lang\": \"en\", \"zipex\": \"02800,02999\", \"name\": \"Rhode"
+ " Island\", \"zip\": \"02[89]\", \"key\": \"RI\", \"id\": \"data/US/RI\""
+ "}, \"data/US/NY\": {\"lang\": \"en\", \"zipex\": \"10000,14999:06390:"
+ "00501:00544\", \"name\": \"New York\", \"zip\": \"1[0-4]|06390|00501|0"
+ "0544\", \"key\": \"NY\", \"id\": \"data/US/NY\"}, \"data/US/NV\": {\"l"
+ "ang\": \"en\", \"zipex\": \"88900,89999\", \"name\": \"Nevada\", \"zi"
+ "p\": \"889|89\", \"key\": \"NV\", \"id\": \"data/US/NV\"}, \"data/US/K"
+ "Y\": {\"lang\": \"en\", \"zipex\": \"40000,42799\", \"name\": \"Kentuc"
+ "ky\", \"zip\": \"4[01]|42[0-7]\", \"key\": \"KY\", \"id\": \"data/US/K"
+ "Y\"}, \"data/US/PA\": {\"lang\": \"en\", \"zipex\": \"15000,19699\", \""
+ "name\": \"Pennsylvania\", \"zip\": \"1[5-8]|19[0-6]\", \"key\": \"PA\""
+ ", \"id\": \"data/US/PA\"}, \"data/US/OH\": {\"lang\": \"en\", \"zipe"
+ "x\": \"43000,45999\", \"name\": \"Ohio\", \"zip\": \"4[3-5]\", \"key\""
+ ": \"OH\", \"id\": \"data/US/OH\"}, \"data/US/AS\": {\"lang\": \"en\","
+ " \"zipex\": \"96799\", \"name\": \"American Samoa\", \"zip\": \"96799\""
+ ", \"key\": \"AS\", \"id\": \"data/US/AS\"}, \"data/US/AA\": {\"lang\""
+ ": \"en\", \"zipex\": \"34000,34099\", \"name\": \"Armed Forces (AA)\","
+ " \"zip\": \"340\", \"key\": \"AA\", \"id\": \"data/US/AA\"}, \"data/US"
+ "/GA\": {\"lang\": \"en\", \"zipex\": \"30000,31999:39800,39899:39901\""
+ ", \"name\": \"Georgia\", \"zip\": \"3[01]|398|39901\", \"key\": \"GA\""
+ ", \"id\": \"data/US/GA\"}, \"data/US/OK\": {\"lang\": \"en\", \"zipex\""
+ ": \"73000,74999\", \"name\": \"Oklahoma\", \"zip\": \"7[34]\", \"key\""
+ ": \"OK\", \"id\": \"data/US/OK\"}, \"data/US/CO\": {\"lang\": \"en\","
+ " \"zipex\": \"80000,81999\", \"name\": \"Colorado\", \"zip\": \"8[01]\""
+ ", \"key\": \"CO\", \"id\": \"data/US/CO\"}, \"data/US/AK\": {\"lang\""
+ ": \"en\", \"zipex\": \"99500,99999\", \"name\": \"Alaska\", \"zip\": \""
+ "99[5-9]\", \"key\": \"AK\", \"id\": \"data/US/AK\"}, \"data/US/WV\": "
+ "{\"lang\": \"en\", \"zipex\": \"24700,26999\", \"name\": \"West Virgin"
+ "ia\", \"zip\": \"24[7-9]|2[56]\", \"key\": \"WV\", \"id\": \"data/US/W"
+ "V\"}, \"data/US/AL\": {\"lang\": \"en\", \"zipex\": \"35000,36999\", \""
+ "name\": \"Alabama\", \"zip\": \"3[56]\", \"key\": \"AL\", \"id\": \"d"
+ "ata/US/AL\"}, \"data/US/GU\": {\"lang\": \"en\", \"zipex\": \"96910,96"
+ "932\", \"name\": \"Guam\", \"zip\": \"969([1-2]\\\\d|3[12])\", \"key\":"
+ " \"GU\", \"id\": \"data/US/GU\"}, \"data/US/AR\": {\"lang\": \"en\", \""
+ "zipex\": \"71600,72999\", \"name\": \"Arkansas\", \"zip\": \"71[6-9]|"
+ "72\", \"key\": \"AR\", \"id\": \"data/US/AR\"}, \"data/US/AP\": {\"lan"
+ "g\": \"en\", \"zipex\": \"96200,96699\", \"name\": \"Armed Forces (AP"
+ ")\", \"zip\": \"96[2-6]\", \"key\": \"AP\", \"id\": \"data/US/AP\"}, \""
+ "data/US/AZ\": {\"lang\": \"en\", \"zipex\": \"85000,86999\", \"name\""
+ ": \"Arizona\", \"zip\": \"8[56]\", \"key\": \"AZ\", \"id\": \"data/US/"
+ "AZ\"}, \"data/US/VI\": {\"lang\": \"en\", \"zipex\": \"00800,00899\","
+ " \"name\": \"Virgin Islands\", \"zip\": \"008\", \"key\": \"VI\", \"i"
+ "d\": \"data/US/VI\"}, \"data/US/CT\": {\"lang\": \"en\", \"zipex\": \""
+ "06000,06999\", \"name\": \"Connecticut\", \"zip\": \"06\", \"key\": \""
+ "CT\", \"id\": \"data/US/CT\"}, \"data/US/ME\": {\"lang\": \"en\", \"zi"
+ "pex\": \"03900,04999\", \"name\": \"Maine\", \"zip\": \"039|04\", \"ke"
+ "y\": \"ME\", \"id\": \"data/US/ME\"}, \"data/US/MD\": {\"lang\": \"en\""
+ ", \"zipex\": \"20600,21999\", \"name\": \"Maryland\", \"zip\": \"20[6"
+ "-9]|21\", \"key\": \"MD\", \"id\": \"data/US/MD\"}, \"data/US/IN\": {\""
+ "lang\": \"en\", \"zipex\": \"46000,47999\", \"name\": \"Indiana\", \""
+ "zip\": \"4[67]\", \"key\": \"IN\", \"id\": \"data/US/IN\"}, \"data/US/"
+ "MA\": {\"lang\": \"en\", \"zipex\": \"01000,02799:05501:05544\", \"nam"
+ "e\": \"Massachusetts\", \"zip\": \"01|02[0-7]|05501|05544\", \"key\":"
+ " \"MA\", \"id\": \"data/US/MA\"}, \"data/US/IL\": {\"lang\": \"en\", \""
+ "zipex\": \"60000,62999\", \"name\": \"Illinois\", \"zip\": \"6[0-2]\""
+ ", \"key\": \"IL\", \"id\": \"data/US/IL\"}, \"data/US/MO\": {\"lang\":"
+ " \"en\", \"zipex\": \"63000,65999\", \"name\": \"Missouri\", \"zip\":"
+ " \"6[3-5]\", \"key\": \"MO\", \"id\": \"data/US/MO\"}, \"data/US/MN\":"
+ " {\"lang\": \"en\", \"zipex\": \"55000,56799\", \"name\": \"Minnesota\""
+ ", \"zip\": \"55|56[0-7]\", \"key\": \"MN\", \"id\": \"data/US/MN\"},"
+ " \"data/US/IA\": {\"lang\": \"en\", \"zipex\": \"50000,52999\", \"nam"
+ "e\": \"Iowa\", \"zip\": \"5[0-2]\", \"key\": \"IA\", \"id\": \"data/US"
+ "/IA\"}, \"data/US/TN\": {\"lang\": \"en\", \"zipex\": \"37000,38599\","
+ " \"name\": \"Tennessee\", \"zip\": \"37|38[0-5]\", \"key\": \"TN\", \""
+ "id\": \"data/US/TN\"}, \"data/US/WY\": {\"lang\": \"en\", \"zipex\": \""
+ "82000,83199:83414\", \"name\": \"Wyoming\", \"zip\": \"82|83[01]|8341"
+ "4\", \"key\": \"WY\", \"id\": \"data/US/WY\"}, \"data/US/KS\": {\"lan"
+ "g\": \"en\", \"zipex\": \"66000,67999\", \"name\": \"Kansas\", \"zip\""
+ ": \"6[67]\", \"key\": \"KS\", \"id\": \"data/US/KS\"}, \"data/US/MI\":"
+ " {\"lang\": \"en\", \"zipex\": \"48000,49999\", \"name\": \"Michigan\""
+ ", \"zip\": \"4[89]\", \"key\": \"MI\", \"id\": \"data/US/MI\"}, \"data"
+ "/US/ID\": {\"lang\": \"en\", \"zipex\": \"83200,83999\", \"name\": \"I"
+ "daho\", \"zip\": \"83[2-9]\", \"key\": \"ID\", \"id\": \"data/US/ID\"}"
+ ", \"data/US/MT\": {\"lang\": \"en\", \"zipex\": \"59000,59999\", \"nam"
+ "e\": \"Montana\", \"zip\": \"59\", \"key\": \"MT\", \"id\": \"data/US/"
+ "MT\"}, \"data/US/MS\": {\"lang\": \"en\", \"zipex\": \"38600,39799\","
+ " \"name\": \"Mississippi\", \"zip\": \"38[6-9]|39[0-7]\", \"key\": \"M"
+ "S\", \"id\": \"data/US/MS\"}, \"data/US/MP\": {\"lang\": \"en\", \"zip"
+ "ex\": \"96950,96952\", \"name\": \"Northern Mariana Islands\", \"zip\""
+ ": \"9695[0-2]\", \"key\": \"MP\", \"id\": \"data/US/MP\"}, \"data/US/P"
+ "W\": {\"lang\": \"en\", \"zipex\": \"96940\", \"name\": \"Palau\", \"z"
+ "ip\": \"969(39|40)\", \"key\": \"PW\", \"id\": \"data/US/PW\"}, \"data"
+ "/US/SC\": {\"lang\": \"en\", \"zipex\": \"29000,29999\", \"name\": \"S"
+ "outh Carolina\", \"zip\": \"29\", \"key\": \"SC\", \"id\": \"data/US/S"
+ "C\"}, \"data/US/MH\": {\"lang\": \"en\", \"zipex\": \"96960,96979\", \""
+ "name\": \"Marshall Islands\", \"zip\": \"969[67]\", \"key\": \"MH\","
+ " \"id\": \"data/US/MH\"}, \"data/US/WI\": {\"lang\": \"en\", \"zipex\""
+ ": \"53000,54999\", \"name\": \"Wisconsin\", \"zip\": \"5[34]\", \"key\""
+ ": \"WI\", \"id\": \"data/US/WI\"}, \"data/US/SD\": {\"lang\": \"en\","
+ " \"zipex\": \"57000,57999\", \"name\": \"South Dakota\", \"zip\": \"5"
+ "7\", \"key\": \"SD\", \"id\": \"data/US/SD\"}, \"data/US/OR\": {\"lan"
+ "g\": \"en\", \"zipex\": \"97000,97999\", \"name\": \"Oregon\", \"zip\""
+ ": \"97\", \"key\": \"OR\", \"id\": \"data/US/OR\"}, \"data/US/UT\": {\""
+ "lang\": \"en\", \"zipex\": \"84000,84999\", \"name\": \"Utah\", \"zi"
+ "p\": \"84\", \"key\": \"UT\", \"id\": \"data/US/UT\"}, \"data/US/VA\":"
+ " {\"lang\": \"en\", \"zipex\": \"20100,20199:22000,24699\", \"name\":"
+ " \"Virginia\", \"zip\": \"201|2[23]|24[0-6]\", \"key\": \"VA\", \"id\""
+ ": \"data/US/VA\"}, \"data/US/AE\": {\"lang\": \"en\", \"zipex\": \"090"
+ "00,09999\", \"name\": \"Armed Forces (AE)\", \"zip\": \"09\", \"key\":"
+ " \"AE\", \"id\": \"data/US/AE\"}, \"data/US/FL\": {\"lang\": \"en\", \""
+ "zipex\": \"32000,33999:34100,34999\", \"name\": \"Florida\", \"zip\":"
+ " \"3[23]|34[1-9]\", \"key\": \"FL\", \"id\": \"data/US/FL\"}, \"data/U"
+ "S/FM\": {\"lang\": \"en\", \"zipex\": \"96941,96944\", \"name\": \"Mic"
+ "ronesia\", \"zip\": \"9694[1-4]\", \"key\": \"FM\", \"id\": \"data/US/"
+ "FM\"}, \"data/US/DE\": {\"lang\": \"en\", \"zipex\": \"19700,19999\","
+ " \"name\": \"Delaware\", \"zip\": \"19[7-9]\", \"key\": \"DE\", \"id\""
+ ": \"data/US/DE\"}, \"data/US/CA\": {\"lang\": \"en\", \"zipex\": \"900"
+ "00,96199\", \"name\": \"California\", \"zip\": \"9[0-5]|96[01]\", \"ke"
+ "y\": \"CA\", \"id\": \"data/US/CA\"}, \"data/US\": {\"lang\": \"en\","
+ " \"upper\": \"CS\", \"sub_zipexs\": \"35000,36999~99500,99999~96799~85"
+ "000,86999~71600,72999~34000,34099~09000,09999~96200,96699~90000,96199~"
+ "80000,81999~06000,06999~19700,19999~20000,20099:20200,20599:56900,5699"
+ "9~32000,33999:34100,34999~30000,31999:39800,39899:39901~96910,96932~96"
+ "700,96798:96800,96899~83200,83999~60000,62999~46000,47999~50000,52999~"
+ "66000,67999~40000,42799~70000,71599~03900,04999~96960,96979~20600,2199"
+ "9~01000,02799:05501:05544~48000,49999~96941,96944~55000,56799~38600,39"
+ "799~63000,65999~59000,59999~68000,69999~88900,89999~03000,03899~07000,"
+ "08999~87000,88499~10000,14999:06390:00501:00544~27000,28999~58000,5899"
+ "9~96950,96952~43000,45999~73000,74999~97000,97999~96940~15000,19699~00"
+ "600,00799:00900,00999~02800,02999~29000,29999~57000,57999~37000,38599~"
+ "75000,79999:88500,88599:73301:73344~84000,84999~05000,05999~00800,0089"
+ "9~20100,20199:22000,24699~98000,99499~24700,26999~53000,54999~82000,83"
+ "199:83414\", \"zipex\": \"95014,22162-1010\", \"name\": \"UNITED STATE"
+ "S\", \"zip\": \"\\\\d{5}([ \\\\-]\\\\d{4})?\", \"zip_name_type\": \"zi"
+ "p\", \"fmt\": \"%N%n%O%n%A%n%C %S %Z\", \"state_name_type\": \"state\""
+ ", \"languages\": \"en\", \"sub_keys\": \"AL~AK~AS~AZ~AR~AA~AE~AP~CA~CO"
+ "~CT~DE~DC~FL~GA~GU~HI~ID~IL~IN~IA~KS~KY~LA~ME~MH~MD~MA~MI~FM~MN~MS~MO~"
+ "MT~NE~NV~NH~NJ~NM~NY~NC~ND~MP~OH~OK~OR~PW~PA~PR~RI~SC~SD~TN~TX~UT~VT~V"
+ "I~VA~WA~WV~WI~WY\","
+ " \"key\": \"US\", \"require\": \"ACSZ\", \"posturl\": \"https://tools."
+ "usps.com/go/ZipLookupAction!input.action\", \"id\": \"dat"
+ "a/US\", \"sub_names\": \"Alabama~Alaska~American Samoa~Arizona~Arkansa"
+ "s~Armed Forces (AA)~Armed Forces (AE)~Armed Forces (AP)~California~Col"
+ "orado~Connecticut~Delaware~District of Columbia~Florida~Georgia~Guam~H"
+ "awaii~Idaho~Illinois~Indiana~Iowa~Kansas~Kentucky~Louisiana~Maine~Mars"
+ "hall Islands~Maryland~Massachusetts~Michigan~Micronesia~Minnesota~Miss"
+ "issippi~Missouri~Montana~Nebraska~Nevada~New Hampshire~New Jersey~New "
+ "Mexico~New York~North Carolina~North Dakota~Northern Mariana Islands~O"
+ "hio~Oklahoma~Oregon~Palau~Pennsylvania~Puerto Rico~Rhode Island~South "
+ "Carolina~South Dakota~Tennessee~Texas~Utah~Vermont~Virgin Islands~Virg"
+ "inia~Washington~West Virginia~Wisconsin~Wyoming\", \"sub_zips\": \"3[5"
+ "6]~99[5-9]~96799~8[56]~71[6-9]|72~340~09~96[2-6]~9[0-5]|96[01]~8[01]~0"
+ "6~19[7-9]~20[02-5]|569~3[23]|34[1-9]~3[01]|398|39901~969([1-2]\\\\d|3[12"
+ "])~967[0-8]|9679[0-8]|968~83[2-9]~6[0-2]~4[67]~5[0-2]~6[67]~4[01]|42[0"
+ "-7]~70|71[0-5]~039|04~969[67]~20[6-9]|21~01|02[0-7]|05501|05544~4[89]~"
+ "9694[1-4]~55|56[0-7]~38[6-9]|39[0-7]~6[3-5]~59~6[89]~889|89~03[0-8]~0["
+ "78]~87|88[0-4]~1[0-4]|06390|00501|00544~2[78]~58~9695[0-2]~4[3-5]~7[34"
+ "]~97~969(39|40)~1[5-8]|19[0-6]~00[679]~02[89]~29~57~37|38[0-5]~7[5-9]|"
+ "885|73301|73344~84~05~008~201|2[23]|24[0-6]~98|99[0-4]~24[7-9]|2[56]~5"
+ "[34]~82|83[01]|83414\"}, \"data/US/TX\": {\"lang\": \"en\", \"zipex\":"
+ " \"75000,79999:88500,88599:73301:73344\", \"name\": \"Texas\", \"zip\""
+ ": \"7[5-9]|885|73301|73344\", \"key\": \"TX\", \"id\": \"data/US/TX\"}"
+ ", \"data/US/WA\": {\"lang\": \"en\", \"zipex\": \"98000,99499\", \"nam"
+ "e\": \"Washington\", \"zip\": \"98|99[0-4]\", \"key\": \"WA\", \"id\":"
+ " \"data/US/WA\"}, \"data/US/DC\": {\"lang\": \"en\", \"zipex\": \"2000"
+ "0,20099:20200,20599:56900,56999\", \"name\": \"District of Columbia\","
+ " \"zip\": \"20[02-5]|569\", \"key\": \"DC\", \"id\": \"data/US/DC\"},"
+ " \"data/US/HI\": {\"lang\": \"en\", \"zipex\": \"96700,96798:96800,968"
+ "99\", \"name\": \"Hawaii\", \"zip\": \"967[0-8]|9679[0-8]|968\", \"key"
+ "\": \"HI\", \"id\": \"data/US/HI\"}}");
+ return true;
+}
+
+} // namespace autofill
diff --git a/chromium/third_party/libaddressinput/chromium/fallback_data_store.h b/chromium/third_party/libaddressinput/chromium/fallback_data_store.h
new file mode 100644
index 00000000000..fd8d680576c
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/fallback_data_store.h
@@ -0,0 +1,22 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_FALLBACK_DATA_STORE_H_
+#define THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_FALLBACK_DATA_STORE_H_
+
+#include <string>
+
+namespace autofill {
+
+class FallbackDataStore {
+ public:
+ // Gets stale, but valid static data for |key|. Should only be used as a last
+ // resort after attempts to check the local cache or the webserver have
+ // failed.
+ static bool Get(const std::string& key, std::string* data);
+};
+
+} // namespace autofill
+
+#endif // THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_FALLBACK_DATA_STORE_H_
diff --git a/chromium/third_party/libaddressinput/chromium/fallback_data_store_unittest.cc b/chromium/third_party/libaddressinput/chromium/fallback_data_store_unittest.cc
new file mode 100644
index 00000000000..558a0a6896d
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/fallback_data_store_unittest.cc
@@ -0,0 +1,50 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/libaddressinput/chromium/fallback_data_store.h"
+
+#include <cstddef>
+#include <ctime>
+#include <string>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/libaddressinput/src/cpp/src/util/json.h"
+#include "third_party/libaddressinput/src/cpp/src/validating_util.h"
+
+namespace autofill {
+
+using i18n::addressinput::Json;
+using i18n::addressinput::ValidatingUtil;
+
+TEST(FallbackDataStore, Parsability) {
+ std::string data;
+ ASSERT_TRUE(FallbackDataStore::Get("data/US", &data));
+
+ // Should be stale.
+ EXPECT_FALSE(ValidatingUtil::UnwrapTimestamp(&data, time(NULL)));
+
+ // Should be uncorrupted.
+ EXPECT_TRUE(ValidatingUtil::UnwrapChecksum(&data));
+
+ // Should be valid JSON.
+ Json json;
+ ASSERT_TRUE(json.ParseObject(data));
+
+ // Should not have a string for "data/US", because "data/US" is a dictionary.
+ std::string value;
+ EXPECT_FALSE(json.GetStringValueForKey("data/US", &value));
+
+ // Should have a dictionary with "data/US" identifier.
+ const std::vector<const Json*>& sub_dicts = json.GetSubDictionaries();
+ bool key_found = false;
+ for (std::vector<const Json*>::const_iterator it = sub_dicts.begin();
+ it != sub_dicts.end(); ++it) {
+ const Json* sub_dict = *it;
+ EXPECT_TRUE(sub_dict->GetStringValueForKey("id", &value));
+ key_found |= value == "data/US";
+ }
+ EXPECT_TRUE(key_found);
+}
+
+} // namespace autofill
diff --git a/chromium/third_party/libaddressinput/chromium/input_suggester.cc b/chromium/third_party/libaddressinput/chromium/input_suggester.cc
new file mode 100644
index 00000000000..9b890f11171
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/input_suggester.cc
@@ -0,0 +1,522 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/libaddressinput/chromium/input_suggester.h"
+
+#include <cstddef>
+#include <set>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "third_party/libaddressinput/chromium/trie.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_data.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/callback.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/preload_supplier.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/region_data.h"
+
+namespace autofill {
+
+using ::i18n::addressinput::AddressData;
+using ::i18n::addressinput::AddressField;
+using ::i18n::addressinput::BuildCallback;
+using ::i18n::addressinput::FieldProblemMap;
+using ::i18n::addressinput::PreloadSupplier;
+using ::i18n::addressinput::RegionData;
+using ::i18n::addressinput::RegionDataBuilder;
+
+using ::i18n::addressinput::ADMIN_AREA;
+using ::i18n::addressinput::COUNTRY;
+using ::i18n::addressinput::DEPENDENT_LOCALITY;
+using ::i18n::addressinput::LOCALITY;
+using ::i18n::addressinput::POSTAL_CODE;
+
+using ::i18n::addressinput::INVALID_FORMAT;
+using ::i18n::addressinput::MISMATCHING_VALUE;
+
+namespace {
+
+// Initial size for the buffer used in the canonicalizer.
+static const size_t kInitialBufferSize = 32;
+
+// A region and its metadata useful for constructing a suggestion.
+struct Suggestion {
+ public:
+ // Builds a suggestion of |region_to_suggest|. Does not take ownership of
+ // |region_to_suggest|, which should not be NULL.
+ Suggestion(const RegionData* region_to_suggest,
+ AddressField matching_address_field,
+ bool region_key_matches)
+ : region_to_suggest(region_to_suggest),
+ matching_address_field(matching_address_field),
+ region_key_matches(region_key_matches) {
+ DCHECK(region_to_suggest);
+ }
+
+ ~Suggestion() {}
+
+ // The region that should be suggested. For example, if the region is ("CA",
+ // "California"), then either "CA" or "California" should be suggested.
+ const RegionData* region_to_suggest;
+
+ // The field in the address for which the suggestion should be made. For
+ // example, ADMIN_AREA in US means the suggestion should be made for the field
+ // labeled "State".
+ AddressField matching_address_field;
+
+ // True if the key of the region matches user input (the name may or may not
+ // match). "CA" should be suggested for a ("CA", "California") region.
+ //
+ // False if only the name of the region matches user input (the key does not
+ // match). "California" should be suggested for a ("CA", "California") region.
+ bool region_key_matches;
+};
+
+// Suggestions for an address. Contains lists of suggestions for administrative
+// area, locality, and dependent locality fields of an address.
+class AddressSuggestions {
+ public:
+ AddressSuggestions() {}
+ ~AddressSuggestions() {}
+
+ // Marks all regions at |address_field| level as matching user input.
+ void AllRegionsMatchForField(AddressField address_field) {
+ all_regions_match_input_.insert(address_field);
+ }
+
+ // Marks given regions at |address_field| level as matching user input. The
+ // |regions_match_key| parameter contains the regions that match user input by
+ // their keys. The |regions_match_name| parameter contains the regions that
+ // match user input by their names.
+ //
+ // The |address_field| parameter should be either ADMIN_AREA, LOCALITY, or
+ // DEPENDENT_LOCALITY.
+ bool AddRegions(AddressField address_field,
+ const std::set<const RegionData*>& regions_match_key,
+ const std::set<const RegionData*>& regions_match_name) {
+ DCHECK(address_field >= ADMIN_AREA);
+ DCHECK(address_field <= DEPENDENT_LOCALITY);
+
+ AddressField parent_address_field =
+ static_cast<AddressField>(address_field - 1);
+
+ bool all_parents_match =
+ parent_address_field == COUNTRY ||
+ all_regions_match_input_.find(parent_address_field) !=
+ all_regions_match_input_.end();
+
+ // Cannot build |address_field| level suggestions if there are no matches in
+ // |parent_address_field| level regions.
+ const RegionsMatchInput* parents = NULL;
+ if (address_field > ADMIN_AREA && !all_parents_match) {
+ parents = &regions_match_input_[parent_address_field];
+ if (parents->keys.empty() && parents->names.empty())
+ return false;
+ }
+
+ RegionsMatchInput* regions = NULL;
+ if (address_field < DEPENDENT_LOCALITY)
+ regions = &regions_match_input_[address_field];
+
+ std::vector<Suggestion>* suggestions = &suggestions_[address_field];
+ bool added_suggestions = false;
+
+ // Iterate over both |regions_match_key| and |regions_match_name| and build
+ // Suggestion objects based on the given RegionData objects. Advance either
+ // one iterator at a time (if they point to different data) or both
+ // iterators at once (if they point to the same data).
+ for (std::set<const RegionData*>::const_iterator
+ key_it = regions_match_key.begin(),
+ name_it = regions_match_name.begin();
+ key_it != regions_match_key.end() ||
+ name_it != regions_match_name.end();) {
+ const RegionData* key_region =
+ key_it != regions_match_key.end() ? *key_it : NULL;
+ const RegionData* name_region =
+ name_it != regions_match_name.end() ? *name_it : NULL;
+
+ // Regions that do not have a parent that also matches input will not
+ // become suggestions.
+ bool key_region_has_parent =
+ all_parents_match ||
+ (parents && !parents->keys.empty() && key_region &&
+ parents->keys.find(&key_region->parent()) != parents->keys.end());
+ bool name_region_has_parent =
+ all_parents_match ||
+ (parents && !parents->names.empty() && name_region &&
+ parents->names.find(&name_region->parent()) != parents->names.end());
+
+ if (name_region && (!key_region || name_region < key_region)) {
+ if (name_region_has_parent) {
+ suggestions->push_back(Suggestion(name_region, address_field, false));
+ added_suggestions = true;
+ if (regions)
+ regions->names.insert(name_region);
+ }
+
+ ++name_it;
+ } else if (key_region && (!name_region || key_region < name_region)) {
+ if (key_region_has_parent) {
+ suggestions->push_back(Suggestion(key_region, address_field, true));
+ added_suggestions = true;
+ if (regions)
+ regions->keys.insert(key_region);
+ }
+
+ ++key_it;
+ } else {
+ if (key_region_has_parent) {
+ suggestions->push_back(Suggestion(key_region, address_field, true));
+ added_suggestions = true;
+ if (regions) {
+ regions->keys.insert(key_region);
+ regions->names.insert(name_region);
+ }
+ }
+
+ ++key_it;
+ ++name_it;
+ }
+ }
+
+ return added_suggestions;
+ }
+
+ // Swaps the suggestions for the smallest sub-region into |suggestions|.
+ // |this| is not usable after this call due to using the swap() operation.
+ //
+ // The |suggestions| parameter should not be NULL.
+ void SwapSmallestSubRegionSuggestions(std::vector<Suggestion>* suggestions) {
+ DCHECK(suggestions);
+ for (int i = DEPENDENT_LOCALITY; i >= ADMIN_AREA; --i) {
+ std::vector<Suggestion>* result =
+ &suggestions_[static_cast<AddressField>(i)];
+ if (!result->empty()) {
+ suggestions->swap(*result);
+ return;
+ }
+ }
+ }
+
+ private:
+ // The sets of non-owned regions used for looking up regions that match user
+ // input by keys and names.
+ struct RegionsMatchInput {
+ std::set<const RegionData*> keys;
+ std::set<const RegionData*> names;
+ };
+
+ // The regions that match user input at ADMIN_AREA and LOCALITY levels.
+ std::map<AddressField, RegionsMatchInput> regions_match_input_;
+
+ // The set of fields for which all regions match user input. Used to avoid
+ // storing a long list in |regions_match_input_| and later looking it up
+ // there.
+ std::set<AddressField> all_regions_match_input_;
+
+ // Suggestions at ADMIN_AREA, LOCALITY, and DEPENDENT_LOCALITY levels.
+ std::map<AddressField, std::vector<Suggestion> > suggestions_;
+
+ DISALLOW_COPY_AND_ASSIGN(AddressSuggestions);
+};
+
+} // namespace
+
+InputSuggester::StringCanonicalizer::StringCanonicalizer()
+ : buffer_(kInitialBufferSize, 0) {
+ UErrorCode error_code = U_ZERO_ERROR;
+ collator_.reset(
+ icu::Collator::createInstance(icu::Locale::getRoot(), error_code));
+ DCHECK(U_SUCCESS(error_code));
+ collator_->setStrength(icu::Collator::PRIMARY);
+}
+
+InputSuggester::StringCanonicalizer::~StringCanonicalizer() {}
+
+const std::vector<uint8_t>& InputSuggester::StringCanonicalizer::Canonicalize(
+ const std::string& original) const {
+ DCHECK(!original.empty());
+
+ icu::UnicodeString icu_str(original.c_str(),
+ static_cast<int32_t>(original.length()));
+ int32_t sort_key_size =
+ collator_->getSortKey(icu_str, &buffer_[0], buffer_size());
+ DCHECK_LT(0, sort_key_size);
+
+ if (sort_key_size > buffer_size()) {
+ buffer_.resize(sort_key_size * 2, 0);
+ sort_key_size = collator_->getSortKey(icu_str, &buffer_[0], buffer_size());
+ DCHECK_LT(0, sort_key_size);
+ DCHECK_GT(buffer_size(), sort_key_size);
+ }
+
+ return buffer_;
+}
+
+int32_t InputSuggester::StringCanonicalizer::buffer_size() const {
+ return static_cast<int32_t>(buffer_.size());
+}
+
+// All sub-regions of a COUNTRY level region, organized into tries for lookup by
+// region name or key.
+class InputSuggester::SubRegionData {
+ public:
+ SubRegionData()
+ : initialized_(false),
+ smallest_region_size_(COUNTRY),
+ canonicalizer_(NULL) {}
+
+ ~SubRegionData() {}
+
+ bool is_initialized() const { return initialized_; }
+
+ // Adds the sub-regions of |country_region| into tries. Uses
+ // |shared_canonicalizer| for case and diacritic insensitive lookup of the
+ // sub-regions. Should be called at most once.
+ void Initialize(const RegionData& country_region,
+ const StringCanonicalizer& shared_canonicalizer) {
+ DCHECK(!initialized_);
+ DCHECK(!country_region.has_parent());
+
+ initialized_ = true;
+ canonicalizer_ = &shared_canonicalizer;
+
+ if (!country_region.sub_regions().empty())
+ AddSubRegionsOf(country_region, COUNTRY);
+ }
+
+ // Adds the suggestions for |user_input| into |suggestions| when user is
+ // typing in |focused_field|.
+ void BuildSuggestions(const AddressData& user_input,
+ AddressField focused_field,
+ std::vector<Suggestion>* suggestions) {
+ DCHECK(initialized_);
+
+ // Do not suggest anything if there's no suggestion data for the focused
+ // field.
+ if (focused_field != POSTAL_CODE && smallest_region_size_ < focused_field)
+ return;
+
+ // Non-owned regions that match a field value by region key.
+ std::set<const RegionData*> regions_match_key;
+
+ // Non-owned regions that match a field value by region name.
+ std::set<const RegionData*> regions_match_name;
+
+ AddressSuggestions address_suggestions;
+ for (int i = ADMIN_AREA; i <= focused_field && i <= DEPENDENT_LOCALITY;
+ ++i) {
+ AddressField address_field = static_cast<AddressField>(i);
+ AddressField parent_address_field = static_cast<AddressField>(i - 1);
+
+ const std::string& field_value = user_input.GetFieldValue(address_field);
+ const std::string& parent_field_value =
+ user_input.GetFieldValue(parent_address_field);
+
+ if (field_value.empty() &&
+ (address_field == ADMIN_AREA || parent_field_value.empty())) {
+ address_suggestions.AllRegionsMatchForField(address_field);
+ continue;
+ }
+
+ if (field_value.empty()) {
+ DCHECK_NE(address_field, focused_field);
+ continue;
+ }
+
+ regions_match_key.clear();
+ regions_match_name.clear();
+
+ const FieldTries& field_tries = field_tries_[address_field];
+
+ const std::vector<uint8_t>& canonicalized_value =
+ canonicalizer_->Canonicalize(field_value);
+
+ field_tries.keys.FindDataForKeyPrefix(canonicalized_value,
+ &regions_match_key);
+ field_tries.names.FindDataForKeyPrefix(canonicalized_value,
+ &regions_match_name);
+
+ bool added_suggestions = address_suggestions.AddRegions(
+ address_field, regions_match_key, regions_match_name);
+
+ // Do not suggest anything if the focused field does not have suggestions.
+ if (address_field == focused_field && !added_suggestions)
+ return;
+ }
+
+ address_suggestions.SwapSmallestSubRegionSuggestions(suggestions);
+ }
+
+ private:
+ // The tries to lookup regions for a specific field by keys and names. For
+ // example, the FieldTries for ADMIN_AREA in US will have keys for "AL", "AK",
+ // "AS", etc and names for "Alabama", "Alaska", "American Samoa", etc. The
+ // struct is uncopyable due to Trie objects being uncopyable.
+ struct FieldTries {
+ Trie<const RegionData*> keys;
+ Trie<const RegionData*> names;
+ };
+
+ // Adds the sub-regions of |parent_region| into tries.
+ void AddSubRegionsOf(const RegionData& parent_region,
+ AddressField parent_field) {
+ DCHECK(!parent_region.sub_regions().empty());
+
+ AddressField address_field = static_cast<AddressField>(parent_field + 1);
+ DCHECK(address_field >= ADMIN_AREA);
+ DCHECK(address_field <= DEPENDENT_LOCALITY);
+
+ FieldTries* field_tries = &field_tries_[address_field];
+ for (std::vector<const RegionData*>::const_iterator it =
+ parent_region.sub_regions().begin();
+ it != parent_region.sub_regions().end();
+ ++it) {
+ const RegionData* region = *it;
+ DCHECK(region);
+ DCHECK(!region->key().empty());
+ DCHECK(!region->name().empty());
+
+ field_tries->keys.AddDataForKey(
+ canonicalizer_->Canonicalize(region->key()), region);
+
+ field_tries->names.AddDataForKey(
+ canonicalizer_->Canonicalize(region->name()), region);
+
+ if (smallest_region_size_ < address_field)
+ smallest_region_size_ = address_field;
+
+ if (!region->sub_regions().empty())
+ AddSubRegionsOf(*region, address_field);
+ }
+ }
+
+ // True after Initialize() has been called.
+ bool initialized_;
+
+ // The tries to lookup regions for ADMIN_AREA, LOCALITY, and
+ // DEPENDENT_LOCALITY.
+ std::map<AddressField, FieldTries> field_tries_;
+
+ // The smallest size of a sub-region that has data. For example, this is
+ // ADMIN_AREA in US, but DEPENDENT_LOCALITY in CN.
+ AddressField smallest_region_size_;
+
+ // A shared instance of string canonicalizer for case and diacritic comparison
+ // of region keys and names.
+ const StringCanonicalizer* canonicalizer_;
+};
+
+InputSuggester::InputSuggester(PreloadSupplier* supplier)
+ : region_data_builder_(supplier),
+ input_helper_(supplier),
+ validator_(supplier),
+ validated_(BuildCallback(this, &InputSuggester::Validated)) {}
+
+InputSuggester::~InputSuggester() {}
+
+void InputSuggester::GetSuggestions(const AddressData& user_input,
+ AddressField focused_field,
+ size_t suggestions_limit,
+ std::vector<AddressData>* suggestions) {
+ DCHECK(suggestions);
+ DCHECK(focused_field == POSTAL_CODE ||
+ (focused_field >= ADMIN_AREA && focused_field <= DEPENDENT_LOCALITY));
+
+ AddressData address_copy = user_input;
+
+ // Do not suggest anything if the user input is empty.
+ if (address_copy.IsFieldEmpty(focused_field))
+ return;
+
+ if (focused_field == POSTAL_CODE) {
+ // Do not suggest anything if the user is typing an invalid postal code.
+ FieldProblemMap problems;
+ FieldProblemMap filter;
+ filter.insert(std::make_pair(POSTAL_CODE, INVALID_FORMAT));
+ validator_.Validate(address_copy,
+ true, // Allow postal office boxes.
+ false, // Do not require recipient name.
+ &filter,
+ &problems,
+ *validated_);
+ if (!problems.empty())
+ return;
+
+ // Fill in the sub-regions based on the postal code.
+ input_helper_.FillAddress(&address_copy);
+ }
+
+ // Lazily initialize the mapping from COUNTRY level regions to all of their
+ // sub-regions with metadata for generating suggestions.
+ std::string unused_best_language;
+ const RegionData& region_data =
+ region_data_builder_.Build(address_copy.region_code,
+ address_copy.language_code,
+ &unused_best_language);
+ SubRegionData* sub_region_data = &sub_regions_[&region_data];
+ if (!sub_region_data->is_initialized())
+ sub_region_data->Initialize(region_data, canonicalizer_);
+
+ // Build the list of regions that match |address_copy| when the user is typing
+ // in the |focused_field|.
+ std::vector<Suggestion> suggested_regions;
+ sub_region_data->BuildSuggestions(
+ address_copy, focused_field, &suggested_regions);
+
+ FieldProblemMap problems;
+ FieldProblemMap filter;
+ filter.insert(std::make_pair(POSTAL_CODE, MISMATCHING_VALUE));
+
+ // Generate suggestions based on the regions.
+ for (std::vector<Suggestion>::const_iterator suggested_region_it =
+ suggested_regions.begin();
+ suggested_region_it != suggested_regions.end();
+ ++suggested_region_it) {
+ AddressData address;
+ address.region_code = address_copy.region_code;
+ address.postal_code = address_copy.postal_code;
+
+ // Traverse the tree of regions from the smallest |region_to_suggest| to the
+ // country-wide "root" of the tree. Use the region names or keys found at
+ // each of the levels of the tree to build the |address| to suggest.
+ AddressField address_field = suggested_region_it->matching_address_field;
+ for (const RegionData* region = suggested_region_it->region_to_suggest;
+ region->has_parent();
+ region = &region->parent()) {
+ address.SetFieldValue(address_field,
+ suggested_region_it->region_key_matches
+ ? region->key()
+ : region->name());
+ address_field = static_cast<AddressField>(address_field - 1);
+ }
+
+ // Do not suggest an address with a mismatching postal code.
+ problems.clear();
+ validator_.Validate(address,
+ true, // Allow postal office boxes.
+ false, // Do not require recipient name.
+ &filter,
+ &problems,
+ *validated_);
+ if (!problems.empty())
+ continue;
+
+ // Do not add more suggestions than |suggestions_limit|.
+ if (suggestions->size() >= suggestions_limit) {
+ suggestions->clear();
+ return;
+ }
+
+ suggestions->push_back(address);
+ }
+}
+
+void InputSuggester::Validated(bool success,
+ const AddressData&,
+ const FieldProblemMap&) {
+ DCHECK(success);
+}
+
+} // namespace autofill
diff --git a/chromium/third_party/libaddressinput/chromium/input_suggester.h b/chromium/third_party/libaddressinput/chromium/input_suggester.h
new file mode 100644
index 00000000000..9d5fbcd4fcd
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/input_suggester.h
@@ -0,0 +1,135 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_INPUT_SUGGESTER_H_
+#define THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_INPUT_SUGGESTER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <map>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "third_party/icu/source/i18n/unicode/coll.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_field.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_input_helper.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_validator.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/region_data_builder.h"
+
+namespace i18n {
+namespace addressinput {
+class PreloadSupplier;
+class RegionData;
+struct AddressData;
+}
+}
+
+namespace autofill {
+
+// Suggests address completions for a partially entered address from the user.
+class InputSuggester {
+ public:
+ // Does not take ownership of |supplier|, which should not be NULL.
+ explicit InputSuggester(::i18n::addressinput::PreloadSupplier* supplier);
+ ~InputSuggester();
+
+ // Fills in |suggestions| for the partially typed in |user_input|, assuming
+ // the user is typing in the |focused_field|. If the number of |suggestions|
+ // is over the |suggestion_limit|, then returns no |suggestions| at all.
+ //
+ // Sample user input 1:
+ // country code = "US"
+ // postal code = "90066"
+ // focused field = POSTAL_CODE
+ // suggestions limit = 1
+ // Suggestion:
+ // [{administrative_area: "CA"}]
+ //
+ // Sample user input 2:
+ // country code = "CN"
+ // dependent locality = "Zongyang"
+ // focused field = DEPENDENT_LOCALITY
+ // suggestions limit = 10
+ // Suggestion:
+ // [{dependent_locality: "Zongyang Xian",
+ // locality: "Anqing Shi",
+ // administrative_area: "Anhui Sheng"}]
+ //
+ // Builds the index for generating suggestions lazily.
+ //
+ // The |suggestions| parameter should not be NULL. The |focused_field|
+ // parameter should be either POSTAL_CODE or between ADMIN_AREA and
+ // DEPENDENT_LOCALITY inclusively.
+ void GetSuggestions(
+ const ::i18n::addressinput::AddressData& user_input,
+ ::i18n::addressinput::AddressField focused_field,
+ size_t suggestion_limit,
+ std::vector< ::i18n::addressinput::AddressData>* suggestions);
+
+ private:
+ class SubRegionData;
+
+ // Canonicalizes strings for case and diacritic insensitive comparison.
+ class StringCanonicalizer {
+ public:
+ // Initializes the canonicalizer. This is slow, so avoid calling it more
+ // often than necessary.
+ StringCanonicalizer();
+ ~StringCanonicalizer();
+
+ // Returns a 0-terminated canonical version of the string that can be used
+ // for comparing strings regardless of diacritics and capitalization.
+ // Canonicalize("Texas") == Canonicalize("T\u00E9xas");
+ // Canonicalize("Texas") == Canonicalize("teXas");
+ // Canonicalize("Texas") != Canonicalize("California");
+ //
+ // The output is not human-readable.
+ // Canonicalize("Texas") != "Texas";
+ //
+ // The |original| parameter should not be empty.
+ const std::vector<uint8_t>& Canonicalize(const std::string& original) const;
+
+ private:
+ int32_t buffer_size() const;
+
+ mutable std::vector<uint8_t> buffer_;
+ scoped_ptr<icu::Collator> collator_;
+
+ DISALLOW_COPY_AND_ASSIGN(StringCanonicalizer);
+ };
+
+ // The method to be invoked by |validated_| callback.
+ void Validated(bool success,
+ const ::i18n::addressinput::AddressData&,
+ const ::i18n::addressinput::FieldProblemMap&);
+
+ // Data source for region data.
+ ::i18n::addressinput::RegionDataBuilder region_data_builder_;
+
+ // Suggests sub-regions based on postal code.
+ const ::i18n::addressinput::AddressInputHelper input_helper_;
+
+ // Verifies that suggested sub-regions match the postal code.
+ ::i18n::addressinput::AddressValidator validator_;
+
+ // The callback for |validator_| to invoke when validation finishes.
+ const scoped_ptr<const ::i18n::addressinput::AddressValidator::Callback>
+ validated_;
+
+ // A mapping from a COUNTRY level region to a collection of all of its
+ // sub-regions along with metadata used to construct suggestions.
+ std::map<const ::i18n::addressinput::RegionData*, SubRegionData> sub_regions_;
+
+ // Canonicalizes strings for case and diacritic insensitive search of
+ // sub-region names.
+ StringCanonicalizer canonicalizer_;
+
+ DISALLOW_COPY_AND_ASSIGN(InputSuggester);
+};
+
+} // namespace autofill
+
+#endif // THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_INPUT_SUGGESTER_H_
diff --git a/chromium/third_party/libaddressinput/chromium/json.cc b/chromium/third_party/libaddressinput/chromium/json.cc
new file mode 100644
index 00000000000..89c65bc1704
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/json.cc
@@ -0,0 +1,109 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/libaddressinput/src/cpp/src/util/json.h"
+
+#include <map>
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/values.h"
+
+namespace i18n {
+namespace addressinput {
+
+namespace {
+
+// Returns |json| parsed into a JSON dictionary. Sets |parser_error| to true if
+// parsing failed.
+::scoped_ptr<const base::DictionaryValue> Parse(const std::string& json,
+ bool* parser_error) {
+ DCHECK(parser_error);
+ ::scoped_ptr<const base::DictionaryValue> result;
+
+ // |json| is converted to a |c_str()| here because rapidjson and other parts
+ // of the standalone library use char* rather than std::string.
+ ::scoped_ptr<const base::Value> parsed(base::JSONReader::Read(json.c_str()));
+ *parser_error = !parsed || !parsed->IsType(base::Value::TYPE_DICTIONARY);
+
+ if (*parser_error)
+ result.reset(new base::DictionaryValue);
+ else
+ result.reset(static_cast<const base::DictionaryValue*>(parsed.release()));
+
+ return result;
+}
+
+} // namespace
+
+// Implementation of JSON parser for libaddressinput using JSON parser in
+// Chrome.
+class Json::JsonImpl {
+ public:
+ explicit JsonImpl(const std::string& json)
+ : owned_(Parse(json, &parser_error_)),
+ dict_(*owned_) {}
+
+ ~JsonImpl() { STLDeleteElements(&sub_dicts_); }
+
+ bool parser_error() const { return parser_error_; }
+
+ const std::vector<const Json*>& GetSubDictionaries() {
+ if (sub_dicts_.empty()) {
+ for (base::DictionaryValue::Iterator it(dict_); !it.IsAtEnd();
+ it.Advance()) {
+ if (it.value().IsType(base::Value::TYPE_DICTIONARY)) {
+ const base::DictionaryValue* sub_dict = NULL;
+ it.value().GetAsDictionary(&sub_dict);
+ sub_dicts_.push_back(new Json(new JsonImpl(*sub_dict)));
+ }
+ }
+ }
+ return sub_dicts_;
+ }
+
+ bool GetStringValueForKey(const std::string& key, std::string* value) const {
+ return dict_.GetStringWithoutPathExpansion(key, value);
+ }
+
+ private:
+ explicit JsonImpl(const base::DictionaryValue& dict)
+ : parser_error_(false), dict_(dict) {}
+
+ const ::scoped_ptr<const base::DictionaryValue> owned_;
+ bool parser_error_;
+ const base::DictionaryValue& dict_;
+ std::vector<const Json*> sub_dicts_;
+
+ DISALLOW_COPY_AND_ASSIGN(JsonImpl);
+};
+
+Json::Json() {}
+
+Json::~Json() {}
+
+bool Json::ParseObject(const std::string& json) {
+ DCHECK(!impl_);
+ impl_.reset(new JsonImpl(json));
+ if (impl_->parser_error())
+ impl_.reset();
+ return !!impl_;
+}
+
+const std::vector<const Json*>& Json::GetSubDictionaries() const {
+ return impl_->GetSubDictionaries();
+}
+
+bool Json::GetStringValueForKey(const std::string& key,
+ std::string* value) const {
+ return impl_->GetStringValueForKey(key, value);
+}
+
+Json::Json(JsonImpl* impl) : impl_(impl) {}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/chromium/override/basictypes_override.h b/chromium/third_party/libaddressinput/chromium/override/basictypes_override.h
new file mode 100644
index 00000000000..651f506c790
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/override/basictypes_override.h
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_BASICTYPES_OVERRIDE_H_
+#define THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_BASICTYPES_OVERRIDE_H_
+
+#include <limits.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/macros.h"
+
+// There is no more base/basictypes.h in Chromium. Even though libaddressinput
+// has its own "basictypes.h" that defines a whole host of custom integers of
+// specific lengths, the only one actually used is |uint32| in util/md5.cc. So
+// here you go:
+
+typedef uint32_t uint32;
+
+// TODO: Switch libaddressinput to |uint32_t|.
+
+// Also, Chromium uses the C++11 |static_assert|, so here's a definition for the
+// old COMPILE_ASSERT:
+
+#define COMPILE_ASSERT(expr, msg) static_assert(expr, #msg)
+
+// TODO: Switch libaddressinput to |static_assert|.
+
+#endif // THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_BASICTYPES_OVERRIDE_H_
diff --git a/chromium/third_party/libaddressinput/chromium/storage_test_runner.cc b/chromium/third_party/libaddressinput/chromium/storage_test_runner.cc
new file mode 100644
index 00000000000..453f0f29289
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/storage_test_runner.cc
@@ -0,0 +1,88 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/libaddressinput/chromium/storage_test_runner.h"
+
+#include <cassert>
+#include <cstddef>
+#include <string>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/callback.h"
+
+namespace autofill {
+
+using ::i18n::addressinput::Storage;
+
+StorageTestRunner::StorageTestRunner(Storage* storage)
+ : storage_(storage),
+ success_(false),
+ key_(),
+ data_() {}
+
+StorageTestRunner::~StorageTestRunner() {}
+
+void StorageTestRunner::RunAllTests() {
+ EXPECT_NO_FATAL_FAILURE(GetWithoutPutReturnsEmptyData());
+ EXPECT_NO_FATAL_FAILURE(GetReturnsWhatWasPut());
+ EXPECT_NO_FATAL_FAILURE(SecondPutOverwritesData());
+}
+
+void StorageTestRunner::ClearValues() {
+ success_ = false;
+ key_.clear();
+ data_.clear();
+}
+
+scoped_ptr<Storage::Callback> StorageTestRunner::BuildCallback() {
+ return scoped_ptr<Storage::Callback>(::i18n::addressinput::BuildCallback(
+ this, &StorageTestRunner::OnDataReady));
+}
+
+void StorageTestRunner::OnDataReady(bool success,
+ const std::string& key,
+ std::string* data) {
+ assert(!success || data != NULL);
+ success_ = success;
+ key_ = key;
+ if (data != NULL) {
+ data_ = *data;
+ delete data;
+ }
+}
+
+void StorageTestRunner::GetWithoutPutReturnsEmptyData() {
+ ClearValues();
+ scoped_ptr<Storage::Callback> callback(BuildCallback());
+ storage_->Get("key", *callback);
+
+ EXPECT_FALSE(success_);
+ EXPECT_EQ("key", key_);
+ EXPECT_TRUE(data_.empty());
+}
+
+void StorageTestRunner::GetReturnsWhatWasPut() {
+ ClearValues();
+ storage_->Put("key", new std::string("value"));
+ scoped_ptr<Storage::Callback> callback(BuildCallback());
+ storage_->Get("key", *callback);
+
+ EXPECT_TRUE(success_);
+ EXPECT_EQ("key", key_);
+ EXPECT_EQ("value", data_);
+}
+
+void StorageTestRunner::SecondPutOverwritesData() {
+ ClearValues();
+ storage_->Put("key", new std::string("bad-value"));
+ storage_->Put("key", new std::string("good-value"));
+ scoped_ptr<Storage::Callback> callback(BuildCallback());
+ storage_->Get("key", *callback);
+
+ EXPECT_TRUE(success_);
+ EXPECT_EQ("key", key_);
+ EXPECT_EQ("good-value", data_);
+}
+
+} // namespace autofill
diff --git a/chromium/third_party/libaddressinput/chromium/storage_test_runner.h b/chromium/third_party/libaddressinput/chromium/storage_test_runner.h
new file mode 100644
index 00000000000..afbb8858edc
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/storage_test_runner.h
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_STORAGE_TEST_RUNNER_H_
+#define THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_STORAGE_TEST_RUNNER_H_
+
+#include "third_party/libaddressinput/src/cpp/include/libaddressinput/storage.h"
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace autofill {
+
+// A test sutie for ::i18n::addressinput::Storage.
+class StorageTestRunner {
+ public:
+ // Does not take ownership of |storage|.
+ explicit StorageTestRunner(::i18n::addressinput::Storage* storage);
+ ~StorageTestRunner();
+
+ // Runs all the tests from the standard test suite.
+ void RunAllTests();
+
+ private:
+ void ClearValues();
+ scoped_ptr< ::i18n::addressinput::Storage::Callback> BuildCallback();
+ void OnDataReady(bool success, const std::string& key, std::string* data);
+
+ // Test suite.
+ void GetWithoutPutReturnsEmptyData();
+ void GetReturnsWhatWasPut();
+ void SecondPutOverwritesData();
+
+ ::i18n::addressinput::Storage* storage_; // weak
+ bool success_;
+ std::string key_;
+ std::string data_;
+
+ DISALLOW_COPY_AND_ASSIGN(StorageTestRunner);
+};
+
+} // namespace autofill
+
+#endif // THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_STORAGE_TEST_RUNNER_H_
diff --git a/chromium/third_party/libaddressinput/chromium/string_compare.cc b/chromium/third_party/libaddressinput/chromium/string_compare.cc
new file mode 100644
index 00000000000..8996cd8fadd
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/string_compare.cc
@@ -0,0 +1,69 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/libaddressinput/src/cpp/src/util/string_compare.h"
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "third_party/icu/source/i18n/unicode/coll.h"
+
+namespace i18n {
+namespace addressinput {
+
+namespace {
+
+class IcuStringComparer {
+ public:
+ IcuStringComparer() {
+ UErrorCode error_code = U_ZERO_ERROR;
+ collator_.reset(
+ icu::Collator::createInstance(icu::Locale::getRoot(), error_code));
+ DCHECK(U_SUCCESS(error_code));
+ collator_->setStrength(icu::Collator::PRIMARY);
+ }
+
+ ~IcuStringComparer() {}
+
+ int Compare(const std::string& a, const std::string& b) const {
+ UErrorCode error_code = U_ZERO_ERROR;
+ int result = collator_->compareUTF8(a, b, error_code);
+ DCHECK(U_SUCCESS(error_code));
+ return result;
+ }
+
+ private:
+ // ::scoped_ptr is from "base/memory/scoped_ptr.h", which does not interfere
+ // with ::i18n::addressinput::scoped_ptr from
+ // <libaddressinput/util/scoped_ptr.h>.
+ ::scoped_ptr<icu::Collator> collator_;
+
+ DISALLOW_COPY_AND_ASSIGN(IcuStringComparer);
+};
+
+static base::LazyInstance<IcuStringComparer> g_comparer =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// Dummy required for scoped_ptr<Impl>.
+class StringCompare::Impl {};
+
+StringCompare::StringCompare() {}
+
+StringCompare::~StringCompare() {}
+
+bool StringCompare::NaturalEquals(const std::string& a,
+ const std::string& b) const {
+ return g_comparer.Get().Compare(a, b) == 0;
+}
+
+bool StringCompare::NaturalLess(const std::string& a,
+ const std::string& b) const {
+ return g_comparer.Get().Compare(a, b) < 0;
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/chromium/string_compare_unittest.cc b/chromium/third_party/libaddressinput/chromium/string_compare_unittest.cc
new file mode 100644
index 00000000000..a10e55ad727
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/string_compare_unittest.cc
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/libaddressinput/src/cpp/src/util/string_compare.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+TEST(ChromeStringCompareTest, IgnoreDiacritics) {
+ i18n::addressinput::StringCompare sc;
+ EXPECT_TRUE(sc.NaturalEquals("Texas", base::WideToUTF8(L"T\u00E9xas")));
+}
+
+TEST(ChromeStringCompareTest, IgnoreCapitalization) {
+ i18n::addressinput::StringCompare sc;
+ EXPECT_TRUE(sc.NaturalEquals("Texas", "teXas"));
+}
+
+TEST(ChromeStringCompareTest, DifferentStringAreDifferent) {
+ i18n::addressinput::StringCompare sc;
+ EXPECT_FALSE(sc.NaturalEquals("Texas", "California"));
+}
+
+} // namespace
diff --git a/chromium/third_party/libaddressinput/chromium/tools/require_fields.py b/chromium/third_party/libaddressinput/chromium/tools/require_fields.py
new file mode 100755
index 00000000000..b82afa2475d
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/tools/require_fields.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import json
+import urllib
+from sys import exit as sys_exit
+
+
+# Derived from region_data_constants.cc.
+_COUNTRIES = [
+ 'AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'AN', 'AO', 'AQ', 'AR', 'AS',
+ 'AT', 'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH',
+ 'BI', 'BJ', 'BL', 'BM', 'BN', 'BO', 'BR', 'BS', 'BT', 'BV', 'BW', 'BY',
+ 'BZ', 'CA', 'CC', 'CD', 'CF', 'CG', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN',
+ 'CO', 'CR', 'CS', 'CV', 'CX', 'CY', 'CZ', 'DE', 'DJ', 'DK', 'DM', 'DO',
+ 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER', 'ES', 'ET', 'FI', 'FJ', 'FK', 'FM',
+ 'FO', 'FR', 'GA', 'GB', 'GD', 'GE', 'GF', 'GG', 'GH', 'GI', 'GL', 'GM',
+ 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HM', 'HN',
+ 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', 'IS', 'IT',
+ 'JE', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KR', 'KW',
+ 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT', 'LU', 'LV',
+ 'LY', 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK', 'ML', 'MN', 'MO',
+ 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA',
+ 'NC', 'NE', 'NF', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 'OM',
+ 'PA', 'PE', 'PF', 'PG', 'PH', 'PK', 'PL', 'PM', 'PN', 'PR', 'PS', 'PT',
+ 'PW', 'PY', 'QA', 'RE', 'RO', 'RS', 'RU', 'RW', 'SA', 'SB', 'SC', 'SE',
+ 'SG', 'SH', 'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'ST', 'SV',
+ 'SZ', 'TC', 'TD', 'TF', 'TG', 'TH', 'TJ', 'TK', 'TL', 'TM', 'TN', 'TO',
+ 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'UM', 'US', 'UY', 'UZ', 'VA',
+ 'VC', 'VE', 'VG', 'VI', 'VN', 'VU', 'WF', 'WS', 'YE', 'YT', 'ZA', 'ZM', 'ZW'
+]
+_I18N_URL = 'https://i18napis.appspot.com/address/data/%s'
+
+
+def main():
+ for country in _COUNTRIES:
+ url = _I18N_URL % country
+ try:
+ data = json.load(urllib.urlopen(url))
+ except Exception as e:
+ print 'Error: could not load %s' % url
+ return 1
+ if 'require' in data:
+ print '%s: %s' % (country, data['require'])
+ return 0
+
+
+if __name__ == '__main__':
+ sys_exit(main())
diff --git a/chromium/third_party/libaddressinput/chromium/tools/update-strings.py b/chromium/third_party/libaddressinput/chromium/tools/update-strings.py
new file mode 100755
index 00000000000..44e8351087f
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/tools/update-strings.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# This script updates the address_input_strings.grdp file based on the strings
+# in libaddressinput.
+
+import os
+import sys
+
+HEADER = """<!--
+
+DO NOT MODIFY.
+
+This file is generated by
+third_party/libaddressinput/chromium/tools/update-strings.py from
+src/third_party/libaddressinput/src/cpp/res/messages.grdp. Submit modifications
+to the upstream library at https://github.com/googlei18n/libaddressinput.
+
+-->
+"""
+
+script_dir = os.path.dirname(os.path.realpath(__file__))
+from_file = os.path.abspath(os.path.join(
+ script_dir, os.pardir, os.pardir, 'src', 'cpp', 'res', 'messages.grdp'))
+to_file = os.path.abspath(os.path.join(
+ script_dir, os.pardir, os.pardir, os.pardir, os.pardir, 'chrome', 'app',
+ 'address_input_strings.grdp'))
+
+with open(from_file, 'r') as source:
+ with open(to_file, 'w') as destination:
+ destination.write(source.readline()) # XML declaration.
+ destination.write(HEADER)
+ destination.write(source.read())
diff --git a/chromium/third_party/libaddressinput/chromium/trie.cc b/chromium/third_party/libaddressinput/chromium/trie.cc
new file mode 100644
index 00000000000..ccaf46e887b
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/trie.cc
@@ -0,0 +1,83 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/libaddressinput/chromium/trie.h"
+
+#include <stddef.h>
+
+#include <queue>
+#include <string>
+
+#include "base/logging.h"
+
+// Separating template definitions and declarations requires defining all
+// possible template parameters to avoid linking errors.
+namespace i18n {
+namespace addressinput {
+class RegionData;
+}
+}
+
+namespace autofill {
+
+template <typename T>
+Trie<T>::Trie() {}
+
+template <typename T>
+Trie<T>::~Trie() {}
+
+template <typename T>
+void Trie<T>::AddDataForKey(const std::vector<uint8_t>& key,
+ const T& data_item) {
+ Trie<T>* current_node = this;
+ for (std::vector<uint8_t>::size_type i = 0; i < key.size(); ++i) {
+ if (!key[i])
+ break;
+ current_node = &current_node->sub_nodes_[key[i]];
+ }
+ current_node->data_list_.insert(data_item);
+}
+
+template <typename T>
+void Trie<T>::FindDataForKeyPrefix(const std::vector<uint8_t>& key_prefix,
+ std::set<T>* results) const {
+ DCHECK(results);
+
+ // Find the sub-trie for the key prefix.
+ const Trie<T>* current_node = this;
+ for (std::vector<uint8_t>::size_type i = 0; i < key_prefix.size(); ++i) {
+ if (!key_prefix[i])
+ break;
+
+ typename std::map<uint8_t, Trie<T> >::const_iterator sub_node_it =
+ current_node->sub_nodes_.find(key_prefix[i]);
+ if (sub_node_it == current_node->sub_nodes_.end())
+ return;
+
+ current_node = &sub_node_it->second;
+ }
+
+ // Collect data from all sub-tries.
+ std::queue<const Trie<T>*> node_queue;
+ node_queue.push(current_node);
+ while (!node_queue.empty()) {
+ const Trie<T>* queue_front = node_queue.front();
+ node_queue.pop();
+
+ results->insert(queue_front->data_list_.begin(),
+ queue_front->data_list_.end());
+
+ for (typename std::map<uint8_t, Trie<T> >::const_iterator sub_node_it =
+ queue_front->sub_nodes_.begin();
+ sub_node_it != queue_front->sub_nodes_.end();
+ ++sub_node_it) {
+ node_queue.push(&sub_node_it->second);
+ }
+ }
+}
+
+template class Trie<const ::i18n::addressinput::RegionData*>;
+template class Trie<std::string>;
+
+} // namespace autofill
diff --git a/chromium/third_party/libaddressinput/chromium/trie.h b/chromium/third_party/libaddressinput/chromium/trie.h
new file mode 100644
index 00000000000..914897e9b53
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/trie.h
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_TRIE_H_
+#define THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_TRIE_H_
+
+#include <stdint.h>
+#include <map>
+#include <set>
+#include <vector>
+
+namespace autofill {
+
+// A prefix search tree. Can return all objects whose keys start with a prefix
+// byte sequence.
+//
+// Maps keys to multiple objects. This property is useful when mapping region
+// names to region objects. For example, there's a "St. Petersburg" in Florida,
+// and there's a "St. Petersburg" in Russia. A lookup for "St. Petersburg" key
+// should return two distinct objects.
+template <typename T>
+class Trie {
+ public:
+ Trie();
+ ~Trie();
+
+ // Returns true if no data was added in AddDataForKey().
+ bool empty() const { return data_list_.empty() && sub_nodes_.empty(); }
+
+ // Adds a mapping from the 0 terminated |key| to |data_item|. Can be called
+ // with the same |key| multiple times.
+ void AddDataForKey(const std::vector<uint8_t>& key, const T& data_item);
+
+ // Adds all objects whose keys start with the 0 terminated |key_prefix| to the
+ // |results| parameter. The |results| parameter should not be NULL.
+ void FindDataForKeyPrefix(const std::vector<uint8_t>& key_prefix,
+ std::set<T>* results) const;
+
+ private:
+ // All objects for this node in the Trie. This field is a collection to enable
+ // mapping the same key to multiple objects.
+ std::set<T> data_list_;
+
+ // Trie sub nodes. The root Trie stores the objects for the empty key. The
+ // children of the root Trie store the objects for the one-byte keys. The
+ // grand-children of the root Trie store the objects for the two-byte keys,
+ // and so on.
+ std::map<uint8_t, Trie<T> > sub_nodes_;
+};
+
+} // namespace autofill
+
+#endif // THIRD_PARTY_LIBADDRESSINPUT_CHROMIUM_TRIE_H_
diff --git a/chromium/third_party/libaddressinput/chromium/trie_unittest.cc b/chromium/third_party/libaddressinput/chromium/trie_unittest.cc
new file mode 100644
index 00000000000..32b0bb1d3ab
--- /dev/null
+++ b/chromium/third_party/libaddressinput/chromium/trie_unittest.cc
@@ -0,0 +1,109 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/libaddressinput/chromium/trie.h"
+
+#include <stdint.h>
+#include <set>
+#include <string>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace autofill {
+
+namespace {
+
+std::vector<uint8_t> ToByteArray(const std::string& text) {
+ std::vector<uint8_t> result(text.length() + 1, 0);
+ result.assign(text.begin(), text.end());
+ return result;
+}
+
+} // namespace
+
+TEST(TrieTest, EmptyTrieHasNoData) {
+ Trie<std::string> trie;
+ std::set<std::string> result;
+ trie.FindDataForKeyPrefix(ToByteArray("key"), &result);
+ EXPECT_TRUE(result.empty());
+}
+
+TEST(TrieTest, CanGetDataByExactKey) {
+ Trie<std::string> trie;
+ trie.AddDataForKey(ToByteArray("hello"), "world");
+ std::set<std::string> result;
+ trie.FindDataForKeyPrefix(ToByteArray("hello"), &result);
+ std::set<std::string> expected;
+ expected.insert("world");
+ EXPECT_EQ(expected, result);
+}
+
+TEST(TrieTest, CanGetDataByPrefix) {
+ Trie<std::string> trie;
+ trie.AddDataForKey(ToByteArray("hello"), "world");
+ std::set<std::string> result;
+ trie.FindDataForKeyPrefix(ToByteArray("he"), &result);
+ std::set<std::string> expected;
+ expected.insert("world");
+ EXPECT_EQ(expected, result);
+}
+
+TEST(TrieTest, KeyTooLongNoData) {
+ Trie<std::string> trie;
+ trie.AddDataForKey(ToByteArray("hello"), "world");
+ std::set<std::string> result;
+ trie.FindDataForKeyPrefix(ToByteArray("helloo"), &result);
+ EXPECT_TRUE(result.empty());
+}
+
+TEST(TrieTest, CommonPrefixFindsMultipleData) {
+ Trie<std::string> trie;
+ trie.AddDataForKey(ToByteArray("hello"), "world");
+ trie.AddDataForKey(ToByteArray("howdy"), "buddy");
+ trie.AddDataForKey(ToByteArray("foo"), "bar");
+ std::set<std::string> results;
+ trie.FindDataForKeyPrefix(ToByteArray("h"), &results);
+ std::set<std::string> expected;
+ expected.insert("world");
+ expected.insert("buddy");
+ EXPECT_EQ(expected, results);
+}
+
+TEST(TrieTest, KeyCanBePrefixOfOtherKey) {
+ Trie<std::string> trie;
+ trie.AddDataForKey(ToByteArray("hello"), "world");
+ trie.AddDataForKey(ToByteArray("helloo"), "woorld");
+ trie.AddDataForKey(ToByteArray("hella"), "warld");
+ std::set<std::string> results;
+ trie.FindDataForKeyPrefix(ToByteArray("hello"), &results);
+ std::set<std::string> expected;
+ expected.insert("world");
+ expected.insert("woorld");
+ EXPECT_EQ(expected, results);
+}
+
+TEST(TrieTest, AllowMutlipleKeys) {
+ Trie<std::string> trie;
+ trie.AddDataForKey(ToByteArray("hello"), "world");
+ trie.AddDataForKey(ToByteArray("hello"), "woorld");
+ std::set<std::string> results;
+ trie.FindDataForKeyPrefix(ToByteArray("hello"), &results);
+ std::set<std::string> expected;
+ expected.insert("world");
+ expected.insert("woorld");
+ EXPECT_EQ(expected, results);
+}
+
+TEST(TrieTest, CanFindVeryLongKey) {
+ Trie<std::string> trie;
+ static const char kVeryLongKey[] = "1234567890qwertyuioasdfghj";
+ trie.AddDataForKey(ToByteArray(kVeryLongKey), "world");
+ std::set<std::string> result;
+ trie.FindDataForKeyPrefix(ToByteArray(kVeryLongKey), &result);
+ std::set<std::string> expected;
+ expected.insert("world");
+ EXPECT_EQ(expected, result);
+}
+
+} // namespace autofill
diff --git a/chromium/third_party/libaddressinput/src/AUTHORS b/chromium/third_party/libaddressinput/src/AUTHORS
new file mode 100644
index 00000000000..c42ee90e455
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/AUTHORS
@@ -0,0 +1,9 @@
+# This is the official list of libaddressinput authors for copyright purposes.
+# This file is distinct from the CONTRIBUTORS files.
+# See the latter for an explanation.
+
+# Names should be added to this file as:
+# Name or Organization <email address>
+# The email address is not required for organizations.
+
+Google Inc.
diff --git a/chromium/third_party/libaddressinput/src/CONTRIBUTORS b/chromium/third_party/libaddressinput/src/CONTRIBUTORS
new file mode 100644
index 00000000000..f8840a56506
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/CONTRIBUTORS
@@ -0,0 +1,20 @@
+# People who have agreed to one of the CLAs and can contribute patches.
+# The AUTHORS file lists the copyright holders; this file
+# lists people. For example, Google employees are listed here
+# but not in AUTHORS, because Google holds the copyright.
+#
+# https://developers.google.com/open-source/cla/individual
+# https://developers.google.com/open-source/cla/corporate
+#
+# Names should be added to this file as:
+# Name <email address>
+
+David Beaumont <dbeaumont@google.com>
+David Yonge-Mallo <davinci@google.com>
+Dong Zhou <dongzhou@google.com>
+Fredrik Roubert <roubert@google.com>
+Jeanine Lilleng <jeanine@google.com>
+Keghani Kouzoujian <keghani@google.com>
+Lara Scheidegger <lararennie@google.com>
+Rouslan Solomakhin <rouslan@chromium.org>
+Shaopeng Jia <shaopengjia@google.com>
diff --git a/chromium/third_party/libaddressinput/src/LICENSE b/chromium/third_party/libaddressinput/src/LICENSE
new file mode 100644
index 00000000000..d6456956733
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/chromium/third_party/libaddressinput/src/README.md b/chromium/third_party/libaddressinput/src/README.md
new file mode 100644
index 00000000000..0e67ff9240d
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/README.md
@@ -0,0 +1,46 @@
+# ![](https://github.com/googlei18n/libaddressinput/wiki/libaddressinput-icon-70x55.png) libaddressinput
+
+[![Build Status](https://drone.io/github.com/googlei18n/libaddressinput/status.png)](https://drone.io/github.com/googlei18n/libaddressinput/latest)
+
+The _libaddressinput_ project consists of two different libraries (one
+implemented in C++, one implemented in Java for Android) that use
+[address metadata](https://github.com/googlei18n/libaddressinput/wiki/AddressValidationMetadata)
+from
+[Google](https://developers.google.com/)'s
+[I18n Services](https://i18napis.appspot.com/)
+[Address Data Service](https://i18napis.appspot.com/address)
+to assist application developers in collecting and handling _postal addresses_
+from all over the world.
+
+These libraries can provide information about what input fields are required for
+a correct address input form for any country in the world and can validate an
+address to highlight input errors like missing required fields or invalid
+values.
+
+## C++
+
+The C++ library (in very portable C++03) of _libaddressinput_ is the backend for
+[requestAutocomplete()](http://www.html5rocks.com/en/tutorials/forms/requestautocomplete/)
+in [Chromium](http://www.chromium.org/Home). The source code for that is a good
+example of how to use this library to implement a complex feature in a real
+application:
+
+https://src.chromium.org/viewvc/chrome/trunk/src/third_party/libaddressinput/
+https://chromium.googlesource.com/chromium/src/+/master/third_party/libaddressinput/
+
+Video: [Easy International Checkout with Chrome](https://www.youtube.com/watch?v=ljYeHwGgzQk)
+
+## Java
+
+The Java library of _libaddressinput_ is written for use in
+[Android](https://developer.android.com/) and includes an Android UI address
+input widget ready for use, but only the UI parts are tied to Android.
+
+Non-UI code and tests can be run in Java SE, and the rest of the library could
+easily be adapted to run in any Java environment.
+
+## Mailing List
+
+Using and developing libaddressinput is discussed on this mailing list:
+
+https://groups.google.com/forum/#!forum/libaddressinput-discuss
diff --git a/chromium/third_party/libaddressinput/src/android/README b/chromium/third_party/libaddressinput/src/android/README
new file mode 100644
index 00000000000..dada471f3e0
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/README
@@ -0,0 +1,45 @@
+Building and running tests with Android
+=======================================
+
+The easiest way to build libaddressinput for Android and run all the tests is
+using the Gradle project automation tool:
+
+http://tools.android.com/tech-docs/new-build-system
+http://www.gradle.org/
+
+
+Prerequisite dependencies for using Gradle with Android
+-------------------------------------------------------
+Android Studio: https://developer.android.com/sdk/index.html
+or
+Android SDK Tools: https://developer.android.com/sdk/index.html#Other
+
+Set the ANDROID_HOME environment variable to the root of the SDK.
+
+Install the following packages:
+* Tools/Android SDK Build-tools (Rev. 21.1.2)
+* Android 5.1 (API 22)
+* Extras/Android Support Library
+
+Gradle (latest version):
+ https://services.gradle.org/distributions/gradle-2.3-bin.zip
+
+Note: Additionally you must take care to avoid having multiple versions of
+Gradle on your path, as this can cause problems.
+
+
+Building and Running
+--------------------
+After installing all the prerequisites, check that everything is working by
+running:
+
+$ gradle build
+
+With an Android emulator running or an Android device connected, the following
+command line then builds the library and runs the tests:
+
+$ gradle connectedInstrumentTest
+
+The test runner logs to the system log, which can be viewed using logcat:
+
+$ adb logcat
diff --git a/chromium/third_party/libaddressinput/src/android/build.gradle b/chromium/third_party/libaddressinput/src/android/build.gradle
new file mode 100644
index 00000000000..0d48fc8c6d8
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/build.gradle
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+buildscript {
+ repositories {
+ mavenCentral()
+ mavenLocal()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.1.0'
+ }
+}
+
+apply plugin: 'com.android.library'
+
+tasks.withType(JavaCompile) {
+ options.encoding = 'UTF-8'
+}
+
+dependencies {
+ compile project(':common')
+}
+
+android {
+ /*
+ * If these are modified, update the README to reflect the new versions and
+ * update any related settings in the AdroidManifest.xml file.
+ *
+ * NOTE: Because the 'buildToolsVersion' directive only matches an exact
+ * release (rather than allowing wildcards) and Android remove older
+ * versions of the build tools very soon after a new revision is made
+ * available, we must never use the lastest major version of the build
+ * tools here (because it will quickly be superceded and become unavailable
+ * to anyone using a fresh install of the SDK). The "final" release of the
+ * previous major version remains available for much longer however, so as
+ * a workaround we always use that.
+ *
+ * So when the build tools version 23.0.0 is released, the buildToolsVersion
+ * below should be bumped to the last released version of the 22.x.x series.
+ *
+ * The obvious way to fix this would be allow wildcards (ie, "22.+") but
+ * Android have explicitly said that they won't do this.
+ */
+ compileSdkVersion 22
+ buildToolsVersion '21.1.2'
+}
+
diff --git a/chromium/third_party/libaddressinput/src/android/src/androidTest/AndroidManifest.xml b/chromium/third_party/libaddressinput/src/android/src/androidTest/AndroidManifest.xml
new file mode 100644
index 00000000000..d16ab0d834f
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.i18n.addressinput"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk
+ android:minSdkVersion="9"
+ android:targetSdkVersion="22" />
+
+<!--
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.i18n.addressinput" />
+-->
+
+ <uses-permission android:name="android.permission.INTERNET" />
+
+ <application
+ android:allowBackup="false"
+ android:icon="@android:drawable/sym_def_app_icon" >
+ <uses-library android:name="android.test.runner" />
+ <activity android:name=".testing.TestActivity" />
+ </application>
+
+</manifest>
diff --git a/chromium/third_party/libaddressinput/src/android/src/main/AndroidManifest.xml b/chromium/third_party/libaddressinput/src/android/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..e4b72013c16
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/main/AndroidManifest.xml
@@ -0,0 +1,11 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.i18n.addressinput"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk
+ android:minSdkVersion="9"
+ android:targetSdkVersion="22" />
+ <application/>
+
+</manifest>
diff --git a/chromium/third_party/libaddressinput/src/android/src/main/res/layout/address_edittext.xml b/chromium/third_party/libaddressinput/src/android/src/main/res/layout/address_edittext.xml
new file mode 100644
index 00000000000..e36c330c767
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/main/res/layout/address_edittext.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<EditText
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/address_edit_text"
+ android:singleLine="true"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:inputType = "textPostalAddress|textCapWords" />
diff --git a/chromium/third_party/libaddressinput/src/android/src/main/res/layout/address_layout.xml b/chromium/third_party/libaddressinput/src/android/src/main/res/layout/address_layout.xml
new file mode 100644
index 00000000000..1256968e967
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/main/res/layout/address_layout.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/address_layout"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
diff --git a/chromium/third_party/libaddressinput/src/android/src/main/res/layout/address_spinner.xml b/chromium/third_party/libaddressinput/src/android/src/main/res/layout/address_spinner.xml
new file mode 100644
index 00000000000..9f2bf3068c4
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/main/res/layout/address_spinner.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<Spinner
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/address_spinner"
+ android:drawSelectorOnTop="true"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
diff --git a/chromium/third_party/libaddressinput/src/android/src/main/res/layout/address_textview.xml b/chromium/third_party/libaddressinput/src/android/src/main/res/layout/address_textview.xml
new file mode 100644
index 00000000000..f112a5a03e3
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/main/res/layout/address_textview.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/address_text_view"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="4dip"
+ android:layout_marginLeft="3dip"
+ android:textColor="?android:attr/textColorPrimary"
+ android:focusableInTouchMode="true" />
diff --git a/chromium/third_party/libaddressinput/src/android/src/main/res/values/address_strings.xml b/chromium/third_party/libaddressinput/src/android/src/main/res/values/address_strings.xml
new file mode 100644
index 00000000000..7a6d2bd74e4
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/main/res/values/address_strings.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Strings used in the AddressInput widget. The first section contains strings
+ * that may need to be changed for consistency with the client application, the
+ * second section contains widget-specific labels and error messages.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- The first section of strings are for strings that may need to be changed
+ to be consistent with other parts of the client application. -->
+
+ <string name="address_data_loading" translation_description="Message displayed to the user when data is being loaded from the server. The u2026 is the unicode character for the ellipses (...)">Loading\u2026</string>
+
+ <string name="please_select" translation_description="Message shown for a dropdown menu in which nothing is yet selected.">Please select</string>
+
+ <!-- Strings below this point are address-specific and relate either to
+ labels for input fields or to error messages that the widget may report. -->
+
+ <string name="i18n_country_or_region_label" translation_description="A country or a political region (Countries like the United States or regions like Hong Kong or Macao, or places like Taiwan, where whether it is a country or not is a politically sensitive question). [CHAR LIMIT=30]">Country / Region</string>
+
+ <string name="i18n_locality_label" translation_description="A city or town, such as New York City [CHAR LIMIT=30]">City</string>
+
+ <string name="i18n_post_town" translation_description="The name of a town through which postal deliveries are routed, present in UK addresses. See: http://en.wikipedia.org/wiki/Post_town [CHAR LIMIT=30]">Post Town</string>
+
+ <string name="i18n_suburb" translation_description="Smaller part of a city used in some addresses in countries like New Zealand to give a more specific location in a postal address. [CHAR LIMIT=30]">Suburb</string>
+
+ <string name="i18n_village_township" translation_description="A unit used in postal addresses in Malaysia, which is smaller than the city/town, and represents a village, township, or precinct. [CHAR LIMIT=30]">Village / Township</string>
+
+ <string name="i18n_address_line1_label" translation_description="Street-level part of an address, e.g. &quot;18th Street, Unit 3&quot;. [CHAR LIMIT=30]">Street address</string>
+
+ <string name="i18n_pin_code_label" translation_description="PIN (Postal Index Number) Code. Values are numeric. Used in India. [CHAR LIMIT=30]">PIN code</string>
+
+ <string name="i18n_postal_code_label" translation_description="Postal Code. Values are frequently alphanumeric. Used in countries such as Switzerland. [CHAR LIMIT=30]">Postal code</string>
+
+ <string name="i18n_zip_code_label" translation_description="ZIP code. Used in countries like the US. [CHAR LIMIT=30]">ZIP code</string>
+
+ <string name="i18n_area" translation_description="Administrative Area for Hong Kong (e.g. Kowloon). [CHAR LIMIT=30]">Area</string>
+
+ <string name="i18n_county" translation_description="Administrative Area for the United Kingdom (e.g. Yorkshire). [CHAR LIMIT=30]">County</string>
+
+ <string name="i18n_department" translation_description="Administrative Area, as used for countries like Nicaragua (e.g. Boaco). [CHAR LIMIT=30]">Department</string>
+
+ <string name="i18n_district" translation_description="Administrative Area for Nauru Central Pacific (e.g. Aiwo district), or area of a town (a neighborhood/suburb) used for addresses in Korea and China. [CHAR LIMIT=30]">District</string>
+
+ <string name="i18n_do_si" translation_description="Administrative Area for Korea (e.g. Gyeonggi-do or Busan-si). [CHAR LIMIT=30]">Do/Si</string>
+
+ <string name="i18n_emirate" translation_description="Administrative Area for United Arab Emirates (e.g. Abu Dhabi). [CHAR LIMIT=30]">Emirate</string>
+
+ <string name="i18n_island" translation_description="Administrative Area for certain countries (e.g. Bahama's Cat Island). [CHAR LIMIT=30]">Island</string>
+
+ <string name="i18n_oblast" translation_description="Administrative Area for certain countries (e.g. Russia's Leningrad). [CHAR LIMIT=30]">Oblast</string>
+
+ <string name="i18n_parish" translation_description="Administrative Area for certain countries (e.g. Andorra's Canillo). [CHAR LIMIT=30]">Parish</string>
+
+ <string name="i18n_prefecture" translation_description="Administrative Area for Japan (e.g. Hokkaido). [CHAR LIMIT=30]">Prefecture</string>
+
+ <string name="i18n_province" translation_description="Administrative Area for certain countries (e.g. Canada's Ontario). [CHAR LIMIT=30]">Province</string>
+
+ <string name="i18n_state" translation_description="Administrative Area for certain countries (e.g. California in the USA). [CHAR LIMIT=30]">State</string>
+
+ <string name="i18n_recipient_label" translation_description="Label indicating the person to be contacted as part of this address, to be used for example as &quot;Name: John Doe&quot;. [CHAR LIMIT=30]">Name</string>
+
+ <string name="i18n_neighborhood" translation_description="Label for a neighborhood, shown as part of an address input form. [CHAR LIMIT=30]">Neighborhood</string>
+
+ <string name="i18n_organization_label" translation_description="Label for the field of organization, firm, company, or institution in an address. Examples of values in this field: Google, Department of Transportation, University of Cambridge. [CHAR LIMIT=30]">Organization</string>
+
+ <string name="i18n_missing_required_field" translation_description="Error message shown with a UI field when it is a required field and the user has not filled it out. [CHAR LIMIT=30]">You can\u0027t leave this empty.</string>
+
+ <string name="unknown_entry" translation_description="Occurs when the user fills out the wrong value for an address field. For example, this would be shown when putting 'Cupertino' in United States' State field. [CHAR LIMIT=60]">%1$s is not recognized as a known value for this field.</string>
+
+ <string name="unrecognized_format_pin_code" translation_description="Occurs when the user fills out a PIN code that does not conform to the country's PIN code format. For example, this would be shown when using '123' as an Indian PIN code, which is normally 6 digits long. [CHAR LIMIT=60]">This PIN code format is not recognized.</string>
+
+ <string name="unrecognized_format_postal_code" translation_description="Occurs when the user fills out a postal code that does not conform to the country's postal code format. For example, this would be shown when using '80' as a Swiss postal code, which is normally 4 digits long. [CHAR LIMIT=60]">This postal code format is not recognized.</string>
+
+ <string name="unrecognized_format_zip_code" translation_description="Occurs when the user fills out a ZIP code that does not conform to the country's ZIP code format. For example, this would be shown when using '901' as a ZIP code for the United States. [CHAR LIMIT=60]">This ZIP code format is not recognized.</string>
+
+ <string name="mismatching_value_pin_code" translation_description="Occurs when the user fills out the wrong PIN code for a certain location. For example, this would be shown when using 456001 for New Delhi, India. [CHAR LIMIT=70]">This PIN code does not appear to match the rest of this address.</string>
+
+ <string name="mismatching_value_postal_code" translation_description="Occurs when the user fills out the wrong postal code for a certain location. For example, this would be shown when using Z3Z 2Y7 for Alberta, Canada. [CHAR LIMIT=70]">This postal code does not appear to match the rest of this address.</string>
+
+ <string name="mismatching_value_zip_code" translation_description="Occurs when the user fills out the wrong ZIP code for a certain location. For example, this would be shown when using 10001 for Arizona state. [CHAR LIMIT=70]">This ZIP code does not appear to match the rest of this address.</string>
+
+ <string name="i18n_address_line1_accessibility_label" translation_description="Accessibility label for the text field of the first street address line. [CHAR LIMIT=50]">Street address: line 1</string>
+
+ <string name="i18n_address_line2_accessibility_label" translation_description="Accessibility label for the text field of the second street address line. [CHAR LIMIT=50]">Street address: line 2</string>
+</resources>
diff --git a/chromium/third_party/libaddressinput/src/build.gradle b/chromium/third_party/libaddressinput/src/build.gradle
new file mode 100644
index 00000000000..b63b0b7286a
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/build.gradle
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Root Gradle file for Java address input widget (under "common" and "android")
+ */
+allprojects {
+ repositories {
+ mavenCentral()
+ mavenLocal()
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/README b/chromium/third_party/libaddressinput/src/common/README
new file mode 100644
index 00000000000..fdec1bdcec6
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/README
@@ -0,0 +1,26 @@
+Building and running tests
+==========================
+
+The common (non-UI) parts of libaddressinput are built and run using the Gradle
+project automation tool:
+
+http://tools.android.com/tech-docs/new-build-system
+http://www.gradle.org/
+
+
+Prerequisite dependencies for using Gradle
+------------------------------------------
+Gradle (latest version):
+ https://services.gradle.org/distributions/gradle-2.3-bin.zip
+
+Note: Additionally you must take care to avoid having multiple versions of
+Gradle on your path, as this can cause problems.
+
+
+Building and Running
+--------------------
+After installing all the prerequisites, check that everything is working by
+running:
+
+$ gradle build
+$ gradle test
diff --git a/chromium/third_party/libaddressinput/src/common/build.gradle b/chromium/third_party/libaddressinput/src/common/build.gradle
new file mode 100644
index 00000000000..a03c619c4d1
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/build.gradle
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+buildscript {
+ repositories {
+ mavenCentral()
+ mavenLocal()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.1.0'
+ }
+}
+
+apply plugin: 'java'
+
+tasks.withType(JavaCompile) {
+ options.encoding = 'UTF-8'
+}
+
+sourceSets {
+ /* It's simpler if the test resources are next to the java sources. */
+ test {
+ resources {
+ srcDir 'src/test/java'
+ }
+ }
+}
+
+test {
+ /* Listen to events in the test execution lifecycle. */
+ beforeTest { descriptor -> logger.lifecycle("Running test: " + descriptor) }
+
+ /* Show standard out and standard error of the test JVM(s) on the console. */
+ // testLogging.showStandardStreams = true
+
+ /* Listen to standard out and standard error of the test JVM(s). */
+ // onOutput { descriptor, event ->
+ // logger.lifecycle("Test: " + descriptor + " produced standard out/err: " + event.message )
+ // }
+}
+
+dependencies {
+ compile 'com.google.guava:guava-gwt:18.0'
+ /* Note that gradle will warn about this not being the same version as *
+ * the Android JSON library (but will not compile if it's removed). */
+ compile 'org.json:json:20090211'
+ testCompile 'junit:junit:4.11'
+ testCompile 'com.google.truth:truth:0.25'
+ testCompile 'org.mockito:mockito-core:1.9.5'
+}
+
diff --git a/chromium/third_party/libaddressinput/src/cpp/LICENSE.chromium b/chromium/third_party/libaddressinput/src/cpp/LICENSE.chromium
new file mode 100644
index 00000000000..3d0f7d3edfd
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/LICENSE.chromium
@@ -0,0 +1,27 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/chromium/third_party/libaddressinput/src/cpp/README b/chromium/third_party/libaddressinput/src/cpp/README
new file mode 100644
index 00000000000..d6fbf447545
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/README
@@ -0,0 +1,82 @@
+Intro
+=====
+
+The C++ version of libaddressinput library provides UI layout information and
+validation for address input forms.
+
+The library does not provide a UI. The user of the library must provide the user
+interface that uses libaddressinput. The user of the library must also provide a
+way to store data on disk and download data from the internet.
+
+The first client of the library is Chrome web browser. This motivates not
+providing UI or networking capabilities. Chrome will provide those.
+
+When including the library in your project, you can override the dependencies
+and include directories in libaddressinput.gypi to link with your own
+third-party libraries.
+
+Dependencies
+============
+
+The library depends on these tools and libraries:
+
+GYP: Generates the build files.
+Ninja: Executes the build files.
+GTest: Used for unit tests.
+Python: Used by GRIT, which generates localization files.
+RE2: Used for validating postal code format.
+
+Most of these packages are available on Debian-like distributions. You can
+install them with this command:
+
+$ sudo apt-get install gyp ninja-build libgtest-dev python libre2-dev
+
+Make sure that your version of GYP is at least 0.1~svn1395. Older versions of
+GYP do not generate the Ninja build files correctly. You can download a
+new-enough version from http://packages.ubuntu.com/saucy/gyp.
+
+Make sure that your version of RE2 is at least 20140111+dfsg-1. Older versions
+of RE2 don't support set_never_capture() and the packages don't provide shared
+libraries.
+
+If your distribution does not include the binary packages for the dependencies,
+you can download them from these locations:
+
+http://packages.ubuntu.com/saucy/gyp
+http://packages.ubuntu.com/saucy/ninja-build
+http://packages.ubuntu.com/saucy/libgtest-dev
+http://packages.ubuntu.com/saucy/python
+http://packages.ubuntu.com/utopic/libre2-1
+http://packages.ubuntu.com/utopic/libre2-dev
+
+Alternatively, you can download, build, and install these tools and libraries
+from source code. Their home pages contain information on how to accomplish
+that.
+
+https://code.google.com/p/gyp/
+http://martine.github.io/ninja/
+https://code.google.com/p/googletest/
+http://python.org/
+https://code.google.com/p/re2/
+
+Build
+=====
+
+Building the library involves generating an out/Default/build.ninja file and
+running ninja:
+
+$ export GYP_GENERATORS='ninja'
+$ gyp --depth .
+$ ninja -C out/Default
+
+Overriding paths defined in the *.gyp files can be done by setting the
+GYP_DEFINES environment variable before running gyp:
+
+$ export GYP_DEFINES="gtest_dir='/xxx/include' gtest_src_dir='/xxx'"
+
+Test
+====
+
+This command will execute the unit tests for the library:
+
+$ out/Default/unit_tests
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_data.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_data.h
new file mode 100644
index 00000000000..89d32737235
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_data.h
@@ -0,0 +1,96 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// A struct for storing address data: country code, administrative area,
+// locality, etc. The field names correspond to the OASIS xAL standard:
+// https://www.oasis-open.org/committees/ciq/download.shtml
+
+#ifndef I18N_ADDRESSINPUT_ADDRESS_DATA_H_
+#define I18N_ADDRESSINPUT_ADDRESS_DATA_H_
+
+#include <libaddressinput/address_field.h>
+
+#include <iosfwd>
+#include <string>
+#include <vector>
+
+namespace i18n {
+namespace addressinput {
+
+struct AddressData {
+ // CLDR (Common Locale Data Repository) region code.
+ std::string region_code;
+
+ // The address lines represent the most specific part of any address.
+ std::vector<std::string> address_line;
+
+ // Top-level administrative subdivision of this country.
+ std::string administrative_area;
+
+ // Generally refers to the city/town portion of an address.
+ std::string locality;
+
+ // Dependent locality or sublocality. Used for UK dependent localities, or
+ // neighborhoods or boroughs in other locations.
+ std::string dependent_locality;
+
+ // Values are frequently alphanumeric.
+ std::string postal_code;
+
+ // This corresponds to the SortingCode sub-element of the xAL
+ // PostalServiceElements element. Use is very country-specific.
+ std::string sorting_code;
+
+ // Language code of the address. Should be in BCP-47 format.
+ std::string language_code;
+
+ // The organization, firm, company, or institution at this address. This
+ // corresponds to the FirmName sub-element of the xAL FirmType element.
+ std::string organization;
+
+ // Name of recipient or contact person. Not present in xAL.
+ std::string recipient;
+
+ // Returns whether the |field| is empty.
+ bool IsFieldEmpty(AddressField field) const;
+
+ // Returns the value of the |field|. The parameter must not be STREET_ADDRESS,
+ // which comprises multiple fields (will crash otherwise).
+ const std::string& GetFieldValue(AddressField field) const;
+
+ // Copies |value| into the |field|. The parameter must not be STREET_ADDRESS,
+ // which comprises multiple fields (will crash otherwise).
+ void SetFieldValue(AddressField field, const std::string& value);
+
+ // Returns the value of the |field|. The parameter must be STREET_ADDRESS,
+ // which comprises multiple fields (will crash otherwise).
+ const std::vector<std::string>& GetRepeatedFieldValue(
+ AddressField field) const;
+
+ bool operator==(const AddressData& other) const;
+
+ // Returns true if the parameter comprises multiple fields, false otherwise.
+ // Use it to determine whether to call |GetFieldValue| or
+ // |GetRepeatedFieldValue|.
+ static bool IsRepeatedFieldValue(AddressField field);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+// Produces human-readable output in logging, for example in unit tests.
+std::ostream& operator<<(std::ostream& o,
+ const i18n::addressinput::AddressData& address);
+
+#endif // I18N_ADDRESSINPUT_ADDRESS_DATA_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_field.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_field.h
new file mode 100644
index 00000000000..8f2ee05e4c9
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_field.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_ADDRESS_FIELD_H_
+#define I18N_ADDRESSINPUT_ADDRESS_FIELD_H_
+
+#include <iosfwd>
+
+namespace i18n {
+namespace addressinput {
+
+// Address field types, ordered by size, from largest to smallest.
+enum AddressField {
+ COUNTRY, // Country code.
+ ADMIN_AREA, // Administrative area such as a state, province,
+ // island, etc.
+ LOCALITY, // City or locality.
+ DEPENDENT_LOCALITY, // Dependent locality (may be an inner-city district or
+ // a suburb).
+ SORTING_CODE, // Sorting code.
+ POSTAL_CODE, // Zip or postal code.
+ STREET_ADDRESS, // Street address lines.
+ ORGANIZATION, // Organization, company, firm, institution, etc.
+ RECIPIENT // Name.
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+// Produces human-readable output in logging, for example in unit tests. Prints
+// what you would expect for valid fields, e.g. "COUNTRY" for COUNTRY. For
+// invalid values, prints "[INVALID ENUM VALUE x]".
+std::ostream& operator<<(std::ostream& o,
+ i18n::addressinput::AddressField field);
+
+#endif // I18N_ADDRESSINPUT_ADDRESS_FIELD_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_formatter.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_formatter.h
new file mode 100644
index 00000000000..e267884bf67
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_formatter.h
@@ -0,0 +1,51 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// Utility functions for formatting the addresses represented as AddressData.
+//
+// Note these work best if the address has a language code specified - this can
+// be obtained when building the UI components (calling BuildComponents on
+// address_ui.h).
+
+#ifndef I18N_ADDRESSINPUT_ADDRESS_FORMATTER_H_
+#define I18N_ADDRESSINPUT_ADDRESS_FORMATTER_H_
+
+#include <string>
+#include <vector>
+
+namespace i18n {
+namespace addressinput {
+
+struct AddressData;
+
+// Formats the address onto multiple lines. This formats the address in national
+// format; without the country.
+void GetFormattedNationalAddress(
+ const AddressData& address_data, std::vector<std::string>* lines);
+
+// Formats the address as a single line. This formats the address in national
+// format; without the country.
+void GetFormattedNationalAddressLine(
+ const AddressData& address_data, std::string* line);
+
+// Formats the street-level part of an address as a single line. For example,
+// two lines of "Apt 1", "10 Red St." will be concatenated in a
+// language-appropriate way, to give something like "Apt 1, 10 Red St".
+void GetStreetAddressLinesAsSingleLine(
+ const AddressData& address_data, std::string* line);
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_ADDRESS_FORMATTER_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_input_helper.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_input_helper.h
new file mode 100644
index 00000000000..feb04d7ae83
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_input_helper.h
@@ -0,0 +1,67 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_ADDRESS_INPUT_HELPER_H_
+#define I18N_ADDRESSINPUT_ADDRESS_INPUT_HELPER_H_
+
+#include <libaddressinput/util/basictypes.h>
+
+#include <vector>
+
+namespace i18n {
+namespace addressinput {
+
+class LookupKey;
+class PreloadSupplier;
+struct AddressData;
+struct Node;
+
+class AddressInputHelper {
+ public:
+ // Creates an input helper that uses the supplier provided to get metadata to
+ // help a user complete or fix an address. Doesn't take ownership of
+ // |supplier|. Since latency is important for these kinds of tasks, we expect
+ // the supplier to have the data already.
+ AddressInputHelper(PreloadSupplier* supplier);
+
+ ~AddressInputHelper();
+
+ // Fill in missing components of an address as best as we can based on
+ // existing data. For example, for some countries only one postal code is
+ // valid; this would enter that one. For others, the postal code indicates
+ // what state should be selected. Existing data will never be overwritten.
+ //
+ // Note that the preload supplier must have had the rules for the country
+ // represented by this address loaded before this method is called - otherwise
+ // an assertion failure will result.
+ //
+ // The address should have the best language tag as returned from
+ // BuildComponents().
+ void FillAddress(AddressData* address) const;
+
+ private:
+ void CheckChildrenForPostCodeMatches(
+ const AddressData& address, const LookupKey& lookup_key,
+ const Node* parent, std::vector<Node>* hierarchy) const;
+
+ // We don't own the supplier_.
+ PreloadSupplier* const supplier_;
+
+ DISALLOW_COPY_AND_ASSIGN(AddressInputHelper);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_ADDRESS_INPUT_HELPER_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_metadata.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_metadata.h
new file mode 100644
index 00000000000..cbe72db52b3
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_metadata.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_ADDRESS_METADATA_H_
+#define I18N_ADDRESSINPUT_ADDRESS_METADATA_H_
+
+#include <libaddressinput/address_field.h>
+
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+// Checks whether |field| is a required field for |region_code|. Returns false
+// also if no data could be found for region_code. Note: COUNTRY is always
+// required.
+bool IsFieldRequired(AddressField field, const std::string& region_code);
+
+// Checks whether |field| is a field that is used for |region_code|. Returns
+// false also if no data could be found for region_code. Note: COUNTRY is always
+// used.
+bool IsFieldUsed(AddressField field, const std::string& region_code);
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_ADDRESS_METADATA_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_normalizer.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_normalizer.h
new file mode 100644
index 00000000000..06b7eb2b3fd
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_normalizer.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_ADDRESS_NORMALIZER_H_
+#define I18N_ADDRESSINPUT_ADDRESS_NORMALIZER_H_
+
+#include <libaddressinput/util/basictypes.h>
+#include <libaddressinput/util/scoped_ptr.h>
+
+namespace i18n {
+namespace addressinput {
+
+class PreloadSupplier;
+class StringCompare;
+struct AddressData;
+
+class AddressNormalizer {
+ public:
+ // Does not take ownership of |supplier|.
+ explicit AddressNormalizer(const PreloadSupplier* supplier);
+ ~AddressNormalizer();
+
+ // Converts the names of different fields in the address into their canonical
+ // form. Should be called only when supplier->IsLoaded() returns true for
+ // the region code of the |address|.
+ void Normalize(AddressData* address) const;
+
+ private:
+ const PreloadSupplier* const supplier_; // Not owned.
+ const scoped_ptr<const StringCompare> compare_;
+
+ DISALLOW_COPY_AND_ASSIGN(AddressNormalizer);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_ADDRESS_NORMALIZER_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_problem.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_problem.h
new file mode 100644
index 00000000000..743b9e86aa4
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_problem.h
@@ -0,0 +1,68 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_ADDRESS_PROBLEM_H_
+#define I18N_ADDRESSINPUT_ADDRESS_PROBLEM_H_
+
+#include <iosfwd>
+
+namespace i18n {
+namespace addressinput {
+
+// Address problem types, in no particular order.
+enum AddressProblem {
+ // The field is not null and not whitespace, and the field should not be used
+ // by addresses in this country. For example, in the U.S. the SORTING_CODE
+ // field is unused, so its presence is an error.
+ UNEXPECTED_FIELD,
+
+ // The field is null or whitespace, and the field is required. For example,
+ // in the U.S. ADMIN_AREA is a required field.
+ MISSING_REQUIRED_FIELD,
+
+ // A list of values for the field is defined and the value does not occur in
+ // the list. Applies to hierarchical elements like REGION, ADMIN_AREA,
+ // LOCALITY, and DEPENDENT_LOCALITY. For example, in the US, the values for
+ // ADMIN_AREA include "CA" but not "XX".
+ UNKNOWN_VALUE,
+
+ // A format for the field is defined and the value does not match. This is
+ // used to match POSTAL_CODE against the the format pattern generally. Formats
+ // indicate how many digits/letters should be present, and what punctuation is
+ // allowed. For example, in the U.S. postal codes are five digits with an
+ // optional hyphen followed by four digits.
+ INVALID_FORMAT,
+
+ // A specific pattern for the field is defined based on a specific sub-region
+ // (an ADMIN_AREA for example) and the value does not match. This is used to
+ // match POSTAL_CODE against a regular expression. For example, in the U.S.
+ // postal codes in the state of California start with '9'.
+ MISMATCHING_VALUE,
+
+ // The value contains a P.O. box and the widget options have acceptPostal set
+ // to false. For example, a street address line that contained "P.O. Box 3456"
+ // would fire this error.
+ USES_P_O_BOX
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+// Produces human-readable output in logging, for example in unit tests. Prints
+// what you would expect for valid values, e.g. "UNEXPECTED_FIELD" for
+// UNEXPECTED_FIELD. For invalid values, prints "[INVALID ENUM VALUE x]".
+std::ostream& operator<<(std::ostream& o,
+ i18n::addressinput::AddressProblem problem);
+
+#endif // I18N_ADDRESSINPUT_ADDRESS_PROBLEM_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_ui.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_ui.h
new file mode 100644
index 00000000000..cc39f6a1204
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_ui.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_ADDRESS_UI_H_
+#define I18N_ADDRESSINPUT_ADDRESS_UI_H_
+
+#include <string>
+#include <vector>
+
+namespace i18n {
+namespace addressinput {
+
+class Localization;
+struct AddressUiComponent;
+
+// Returns the list of supported CLDR region codes.
+const std::vector<std::string>& GetRegionCodes();
+
+// Returns the UI components for the CLDR |region_code|. Uses the strings from
+// |localization|. The components can be in default or Latin order, depending on
+// the BCP 47 |ui_language_tag|.
+//
+// Sets the |best_address_language_tag| to the BCP 47 language tag that should
+// be saved with this address. This language will be used to get drop-downs to
+// help users fill in their address, and to format the address that the user
+// entered. The parameter should not be NULL.
+//
+// Returns an empty vector on error.
+std::vector<AddressUiComponent> BuildComponents(
+ const std::string& region_code,
+ const Localization& localization,
+ const std::string& ui_language_tag,
+ std::string* best_address_language_tag);
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_ADDRESS_UI_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_ui_component.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_ui_component.h
new file mode 100644
index 00000000000..5982a29f003
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_ui_component.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_ADDRESS_UI_COMPONENT_H_
+#define I18N_ADDRESSINPUT_ADDRESS_UI_COMPONENT_H_
+
+#include <libaddressinput/address_field.h>
+
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+// A description of an input field in an address form. The user of the library
+// will use a list of these elements to layout the address form input fields.
+struct AddressUiComponent {
+ // The types of hints for how large the field should be in a multiline address
+ // form.
+ enum LengthHint {
+ HINT_LONG, // The field should take up the whole line.
+ HINT_SHORT // The field does not need to take up the whole line.
+ };
+
+ // The address field type for this UI component, for example LOCALITY.
+ AddressField field;
+
+ // The name of the field, for example "City".
+ std::string name;
+
+ // The hint for how large the input field should be in a multiline address
+ // form.
+ LengthHint length_hint;
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_ADDRESS_UI_COMPONENT_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_validator.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_validator.h
new file mode 100644
index 00000000000..cdb1edf1286
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/address_validator.h
@@ -0,0 +1,113 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// The public interface to the address validation features of libaddressinput.
+// The AddressValidator will examine an AddressData struct and return a map of
+// the problems found with the different fields of this struct.
+
+#ifndef I18N_ADDRESSINPUT_ADDRESS_VALIDATOR_H_
+#define I18N_ADDRESSINPUT_ADDRESS_VALIDATOR_H_
+
+#include <libaddressinput/address_field.h>
+#include <libaddressinput/address_problem.h>
+#include <libaddressinput/callback.h>
+#include <libaddressinput/util/basictypes.h>
+
+#include <map>
+
+namespace i18n {
+namespace addressinput {
+
+class Supplier;
+struct AddressData;
+
+typedef std::multimap<AddressField, AddressProblem> FieldProblemMap;
+
+// Validates an AddressData struct. Sample usage:
+// class MyClass {
+// public:
+// MyClass()
+// : supplier_(new MySupplier),
+// validator_(new AddressValidator(supplier_.get())),
+// validated_(BuildCallback(this, &MyClass::Validated)) {}
+//
+// virtual ~MyClass() {}
+//
+// void ValidateAddress() const {
+// address_.region_code = "US";
+// address_.administrative_area = "CA";
+// validator_.Validate(address_, filter_, &problems_, *validated_);
+// }
+//
+// void Validated(bool success,
+// const AddressData& address,
+// const FieldProblemMap& problems) {
+// if (success && problems.empty()) {
+// ...
+// }
+// }
+//
+// private:
+// AddressData address_;
+// FieldProblemMap filter_;
+// FieldProblemMap problems_;
+// const scoped_ptr<Supplier> supplier_;
+// const scoped_ptr<AddressValidator> validator_;
+// const scoped_ptr<const AddressValidator::Callback> validated_;
+// };
+class AddressValidator {
+ public:
+ typedef i18n::addressinput::Callback<const AddressData&,
+ const FieldProblemMap&> Callback;
+
+ // Does not take ownership of |supplier|.
+ AddressValidator(Supplier* supplier);
+
+ ~AddressValidator();
+
+ // Validates the |address| and populates |problems| with the validation
+ // problems, filtered according to the |filter| parameter.
+ //
+ // Set |allow_postal| to allow postal addresses, rather than only addresses
+ // describing physical locations.
+ //
+ // Set |require_name| if recipient should be considered a required field.
+ //
+ // If the |filter| is NULL or empty, then all discovered validation problems
+ // are returned. If the |filter| contains problem elements, then only those
+ // field-problem pairs present in the |filter| will be returned.
+ //
+ // Calls the |validated| callback when validation is done. All objects passed
+ // as parameters must be kept available until the callback has been called.
+ //
+ // The |success| parameter of the callback indicates whether it was possible
+ // to perform validation. If |success| is true, then |problems| will contain
+ // information about any problems found with the |address|.
+ void Validate(const AddressData& address,
+ bool allow_postal,
+ bool require_name,
+ const FieldProblemMap* filter,
+ FieldProblemMap* problems,
+ const Callback& validated) const;
+
+ private:
+ Supplier* const supplier_;
+
+ DISALLOW_COPY_AND_ASSIGN(AddressValidator);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_ADDRESS_VALIDATOR_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/callback.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/callback.h
new file mode 100644
index 00000000000..d8c4ea65efb
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/callback.h
@@ -0,0 +1,95 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// An object to store a pointer to a method in an object with the following
+// signature:
+//
+// void Observer::ObserveEvent(bool success, Key key, Data data);
+
+#ifndef I18N_ADDRESSINPUT_CALLBACK_H_
+#define I18N_ADDRESSINPUT_CALLBACK_H_
+
+#include <cassert>
+#include <cstddef>
+
+namespace i18n {
+namespace addressinput {
+
+// Stores a pointer to a method in an object. Sample usage:
+// class MyClass {
+// public:
+// typedef Callback<const MyType&, const MyDataType&> MyCallback;
+//
+// void GetDataAsynchronously() {
+// scoped_ptr<MyCallback> callback(BuildCallback(
+// this, &MyClass::OnDataReady));
+// bool success = ...
+// MyKeyType key = ...
+// MyDataType data = ...
+// (*callback)(success, key, data);
+// }
+//
+// void OnDataReady(bool success,
+// const MyKeyType& key,
+// const MyDataType& data) {
+// ...
+// }
+// };
+template <typename Key, typename Data>
+class Callback {
+ public:
+ virtual ~Callback() {}
+ virtual void operator()(bool success, Key key, Data data) const = 0;
+};
+
+namespace {
+
+template <typename Observer, typename Key, typename Data>
+class CallbackImpl : public Callback<Key, Data> {
+ public:
+ typedef void (Observer::*ObserveEvent)(bool, Key, Data);
+
+ CallbackImpl(Observer* observer, ObserveEvent observe_event)
+ : observer_(observer),
+ observe_event_(observe_event) {
+ assert(observer_ != NULL);
+ assert(observe_event_ != NULL);
+ }
+
+ virtual ~CallbackImpl() {}
+
+ virtual void operator()(bool success, Key key, Data data) const {
+ (observer_->*observe_event_)(success, key, data);
+ }
+
+ private:
+ Observer* observer_;
+ ObserveEvent observe_event_;
+};
+
+} // namespace
+
+// Returns a callback to |observer->observe_event| method. The caller owns the
+// result.
+template <typename Observer, typename Key, typename Data>
+Callback<Key, Data>* BuildCallback(
+ Observer* observer,
+ void (Observer::*observe_event)(bool, Key, Data)) {
+ return new CallbackImpl<Observer, Key, Data>(observer, observe_event);
+}
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_CALLBACK_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/localization.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/localization.h
new file mode 100644
index 00000000000..5e7896d9f10
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/localization.h
@@ -0,0 +1,94 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_LOCALIZATION_H_
+#define I18N_ADDRESSINPUT_LOCALIZATION_H_
+
+#include <libaddressinput/address_field.h>
+#include <libaddressinput/address_problem.h>
+#include <libaddressinput/util/basictypes.h>
+
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+struct AddressData;
+
+// The object to retrieve localized strings based on message IDs. It returns
+// English by default. Sample usage:
+// Localization localization;
+// std::string best_language_tag;
+// Process(BuildComponents("CA", localization, "en-US", &best_language_tag));
+//
+// Alternative usage:
+// Localization localization;
+// localization.SetGetter(&MyStringGetter);
+// std::string best_language_tag;
+// Process(BuildComponents("CA", localization, "fr-CA", &best_language_tag));
+class Localization {
+ public:
+ // Initializes with English messages by default.
+ Localization();
+ ~Localization();
+
+ // Returns the localized string for |message_id|. Returns an empty string if
+ // there's no message with this identifier.
+ std::string GetString(int message_id) const;
+
+ // Returns the error message. If |enable_examples| is false, then the error
+ // message will not contain examples of valid input. If |enable_links| is
+ // false, then the error message will not contain HTML links. (Some error
+ // messages contain postal code examples or link to post office websites to
+ // look up the postal code for an address). Vector field values (e.g. for
+ // street address) should not be empty if problem is UNKNOWN_VALUE. The
+ // POSTAL_CODE field should only be used with MISSING_REQUIRED_FIELD,
+ // INVALID_FORMAT, and MISMATCHING_VALUE problem codes. All other fields
+ // should only be used with MISSING_REQUIRED_FIELD, UNKNOWN_VALUE, and
+ // USES_P_O_BOX problem codes.
+ std::string GetErrorMessage(const AddressData& address,
+ AddressField field,
+ AddressProblem problem,
+ bool enable_examples,
+ bool enable_links) const;
+
+ // Sets the string getter that takes a message identifier and returns the
+ // corresponding localized string. For example, in Chromium there is
+ // l10n_util::GetStringUTF8 which always returns strings in the current
+ // application locale.
+ void SetGetter(std::string (*getter)(int));
+
+ private:
+ // Returns the error message where the address field is a postal code. Helper
+ // to |GetErrorMessage|. If |postal_code_example| is empty, then the error
+ // message will not contain examples of valid postal codes. If
+ // |post_service_url| is empty, then the error message will not contain a post
+ // service URL. The problem should only be one of MISSING_REQUIRED_FIELD,
+ // INVALID_FORMAT, or MISMATCHING_VALUE.
+ std::string GetErrorMessageForPostalCode(const AddressData& address,
+ AddressProblem problem,
+ bool uses_postal_code_as_label,
+ std::string postal_code_example,
+ std::string post_service_url) const;
+
+ // The string getter.
+ std::string (*get_string_)(int);
+
+ DISALLOW_COPY_AND_ASSIGN(Localization);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_LOCALIZATION_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/null_storage.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/null_storage.h
new file mode 100644
index 00000000000..66d9ab5216e
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/null_storage.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// It is not always desirable to cache libaddressinput data. Sometimes it might
+// give better performance characteristics to not cache. This implementation of
+// the Storage interface therefore doesn't actually store anything.
+
+#ifndef I18N_ADDRESSINPUT_NULL_STORAGE_H_
+#define I18N_ADDRESSINPUT_NULL_STORAGE_H_
+
+#include <libaddressinput/storage.h>
+#include <libaddressinput/util/basictypes.h>
+
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+class NullStorage : public Storage {
+ public:
+ NullStorage();
+ virtual ~NullStorage();
+
+ // No-op.
+ virtual void Put(const std::string& key, std::string* data);
+
+ // Always calls the |data_ready| callback function signalling failure.
+ virtual void Get(const std::string& key, const Callback& data_ready) const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NullStorage);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_NULL_STORAGE_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/ondemand_supplier.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/ondemand_supplier.h
new file mode 100644
index 00000000000..6212d34b6e3
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/ondemand_supplier.h
@@ -0,0 +1,66 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_ONDEMAND_SUPPLIER_H_
+#define I18N_ADDRESSINPUT_ONDEMAND_SUPPLIER_H_
+
+#include <libaddressinput/callback.h>
+#include <libaddressinput/supplier.h>
+#include <libaddressinput/util/basictypes.h>
+#include <libaddressinput/util/scoped_ptr.h>
+
+#include <map>
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+class LookupKey;
+class Retriever;
+class Rule;
+class Source;
+class Storage;
+
+// An implementation of the Supplier interface that owns a Retriever object,
+// through which it loads address metadata as needed, creating Rule objects and
+// caching these.
+//
+// When using an OndemandSupplier, address validation will benefit from address
+// metadata server synonym resolution, because the server will be contacted for
+// every new LookupKey (ie. every LookupKey that isn't on canonical form and
+// isn't already cached).
+//
+// The maximum size of this cache is naturally limited to the amount of data
+// available from the data server. (Currently this is less than 12,000 items of
+// in total less than 2 MB of JSON data.)
+class OndemandSupplier : public Supplier {
+ public:
+ // Takes ownership of |source| and |storage|.
+ OndemandSupplier(const Source* source, Storage* storage);
+ virtual ~OndemandSupplier();
+
+ // Loads the metadata needed for |lookup_key|, then calls |supplied|.
+ virtual void Supply(const LookupKey& lookup_key, const Callback& supplied);
+
+ private:
+ const scoped_ptr<const Retriever> retriever_;
+ std::map<std::string, const Rule*> rule_cache_;
+
+ DISALLOW_COPY_AND_ASSIGN(OndemandSupplier);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_ONDEMAND_SUPPLIER_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/preload_supplier.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/preload_supplier.h
new file mode 100644
index 00000000000..5987c7980da
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/preload_supplier.h
@@ -0,0 +1,103 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_PRELOAD_SUPPLIER_H_
+#define I18N_ADDRESSINPUT_PRELOAD_SUPPLIER_H_
+
+#include <libaddressinput/callback.h>
+#include <libaddressinput/supplier.h>
+#include <libaddressinput/util/basictypes.h>
+#include <libaddressinput/util/scoped_ptr.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+namespace i18n {
+namespace addressinput {
+
+class IndexMap;
+class LookupKey;
+class Retriever;
+class Rule;
+class Source;
+class Storage;
+
+// An implementation of the Supplier interface that owns a Retriever object,
+// through which it can load aggregated address metadata for a region when
+// instructed to, creating Rule objects and caching these. It also provides
+// methods to check whether metadata for a particular region is already loaded
+// or in progress of being loaded.
+//
+// When using a PreloadSupplier, it becomes possible to do synchronous address
+// validation using an asynchronous Source, and to have full control over when
+// network access is being done.
+//
+// The maximum size of this cache is naturally limited to the amount of data
+// available from the data server. (Currently this is less than 12,000 items of
+// in total less than 2 MB of JSON data.)
+class PreloadSupplier : public Supplier {
+ public:
+ typedef i18n::addressinput::Callback<const std::string&, int> Callback;
+
+ // Takes ownership of |source| and |storage|.
+ PreloadSupplier(const Source* source, Storage* storage);
+ virtual ~PreloadSupplier();
+
+ // Collects the metadata needed for |lookup_key| from the cache, then calls
+ // |supplied|. If the metadata needed isn't found in the cache, it will call
+ // the callback with status false.
+ virtual void Supply(const LookupKey& lookup_key,
+ const Supplier::Callback& supplied);
+
+ // Should be called only when IsLoaded() returns true for the region code of
+ // the |lookup_key|. Can return NULL if the |lookup_key| does not correspond
+ // to any rule data. The caller does not own the result.
+ const Rule* GetRule(const LookupKey& lookup_key) const;
+
+ // Loads all address metadata available for |region_code|. (A typical data
+ // size is 10 kB. The largest is 250 kB.)
+ //
+ // If the rules are already in progress of being loaded, it does nothing.
+ // Calls |loaded| when the loading has finished.
+ void LoadRules(const std::string& region_code, const Callback& loaded);
+
+ // Returns a mapping of lookup keys to rules. Should be called only when
+ // IsLoaded() returns true for the |region_code|.
+ const std::map<std::string, const Rule*>& GetRulesForRegion(
+ const std::string& region_code) const;
+
+ bool IsLoaded(const std::string& region_code) const;
+ bool IsPending(const std::string& region_code) const;
+
+ private:
+ bool GetRuleHierarchy(const LookupKey& lookup_key,
+ RuleHierarchy* hierarchy) const;
+ bool IsLoadedKey(const std::string& key) const;
+ bool IsPendingKey(const std::string& key) const;
+
+ const scoped_ptr<const Retriever> retriever_;
+ std::set<std::string> pending_;
+ const scoped_ptr<IndexMap> rule_index_;
+ std::vector<const Rule*> rule_storage_;
+ std::map<std::string, std::map<std::string, const Rule*> > region_rules_;
+
+ DISALLOW_COPY_AND_ASSIGN(PreloadSupplier);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_PRELOAD_SUPPLIER_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/region_data.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/region_data.h
new file mode 100644
index 00000000000..92438dc6787
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/region_data.h
@@ -0,0 +1,77 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_REGION_DATA_H_
+#define I18N_ADDRESSINPUT_REGION_DATA_H_
+
+#include <libaddressinput/util/basictypes.h>
+
+#include <cassert>
+#include <cstddef>
+#include <string>
+#include <vector>
+
+namespace i18n {
+namespace addressinput {
+
+// The key and name of a region that can be used as one of the items in a
+// dropdown UI element.
+class RegionData {
+ public:
+ // Creates a top-level RegionData object. Use AddSubRegion() to add data below
+ // it. Does not make a copy of data in |region_code|.
+ explicit RegionData(const std::string& region_code);
+
+ ~RegionData();
+
+ // Creates a sub-level RegionData object, with this object as its parent and
+ // owner. Does not make copies of the data in |key| or |name|.
+ RegionData* AddSubRegion(const std::string& key, const std::string& name);
+
+ const std::string& key() const { return key_; }
+
+ const std::string& name() const { return name_; }
+
+ bool has_parent() const { return parent_ != NULL; }
+
+ // Should be called only if has_parent() returns true.
+ const RegionData& parent() const {
+ assert(parent_ != NULL);
+ return *parent_;
+ }
+
+ // The caller does not own the results. The results are not NULL and have a
+ // parent.
+ const std::vector<const RegionData*>& sub_regions() const {
+ return sub_regions_;
+ }
+
+ private:
+ // Private constructor used by AddSubRegion().
+ RegionData(const std::string& key,
+ const std::string& name,
+ RegionData* parent);
+
+ const std::string& key_;
+ const std::string& name_;
+ const RegionData* const parent_; // Not owned.
+ std::vector<const RegionData*> sub_regions_; // Owned.
+
+ DISALLOW_COPY_AND_ASSIGN(RegionData);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_REGION_DATA_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/region_data_builder.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/region_data_builder.h
new file mode 100644
index 00000000000..906ca36fa3a
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/region_data_builder.h
@@ -0,0 +1,80 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_REGION_DATA_BUILDER_H_
+#define I18N_ADDRESSINPUT_REGION_DATA_BUILDER_H_
+
+#include <libaddressinput/util/basictypes.h>
+
+#include <map>
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+class PreloadSupplier;
+class RegionData;
+
+class RegionDataBuilder {
+ public:
+ // Does not take ownership of |supplier|, which should not be NULL.
+ explicit RegionDataBuilder(PreloadSupplier* supplier);
+ ~RegionDataBuilder();
+
+ // Returns a tree of administrative subdivisions for the |region_code|.
+ // Examples:
+ // US with en-US UI language.
+ // |______________________
+ // | | |
+ // v v v
+ // AL:Alabama AK:Alaska AS:American Samoa ...
+ //
+ // KR with ko-Latn UI language.
+ // |______________________________________
+ // | | |
+ // v v v
+ // 강원도:Gangwon 경기도:Gyeonggi 경상남도:Gyeongnam ...
+ //
+ // KR with ko-KR UI language.
+ // |_______________________________
+ // | | |
+ // v v v
+ // 강원도:강원 경기도:경기 경상남도:경남 ...
+ //
+ // The BCP 47 |ui_language_tag| is used to choose the best supported language
+ // tag for this region (assigned to |best_region_tree_language_tag|). For
+ // example, Canada has both English and French names for its administrative
+ // subdivisions. If the UI language is French, then the French names are used.
+ // The |best_region_tree_language_tag| value may be an empty string.
+ //
+ // Should be called only if supplier->IsLoaded(region_code) returns true. The
+ // |best_region_tree_language_tag| parameter should not be NULL.
+ const RegionData& Build(const std::string& region_code,
+ const std::string& ui_language_tag,
+ std::string* best_region_tree_language_tag);
+
+ private:
+ typedef std::map<std::string, const RegionData*> LanguageRegionMap;
+ typedef std::map<std::string, LanguageRegionMap*> RegionCodeDataMap;
+
+ PreloadSupplier* const supplier_; // Not owned.
+ RegionCodeDataMap cache_;
+
+ DISALLOW_COPY_AND_ASSIGN(RegionDataBuilder);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_REGION_DATA_BUILDER_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/source.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/source.h
new file mode 100644
index 00000000000..4d91b68565b
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/source.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// The interface to be implemented by the user of the library to access address
+// metadata, typically by downloading this from the address metadata server or
+// by linking the metadata into the binary.
+
+#ifndef I18N_ADDRESSINPUT_SOURCE_H_
+#define I18N_ADDRESSINPUT_SOURCE_H_
+
+#include <libaddressinput/callback.h>
+
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+// Gets address metadata. The callback data must be allocated on the heap,
+// passing ownership to the callback. Sample usage:
+//
+// class MySource : public Source {
+// public:
+// virtual void Get(const std::string& key,
+// const Callback& data_ready) const {
+// bool success = ...
+// std::string* data = new ...
+// data_ready(success, key, data);
+// }
+// };
+class Source {
+ public:
+ typedef i18n::addressinput::Callback<const std::string&,
+ std::string*> Callback;
+
+ virtual ~Source() {}
+
+ // Gets metadata for |key| and invokes the |data_ready| callback.
+ virtual void Get(const std::string& key,
+ const Callback& data_ready) const = 0;
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_SOURCE_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/storage.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/storage.h
new file mode 100644
index 00000000000..94ad13bdd20
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/storage.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// The interface to be implemented by the user of the library to enable storing
+// address metadata (e.g. on disk).
+
+#ifndef I18N_ADDRESSINPUT_STORAGE_H_
+#define I18N_ADDRESSINPUT_STORAGE_H_
+
+#include <libaddressinput/callback.h>
+
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+// Stores address metadata. The data must be allocated on the heap, passing
+// ownership to the called function. Sample usage:
+//
+// class MyStorage : public Storage {
+// public:
+// virtual void Put(const std::string& key, std::string* data) {
+// ...
+// delete data;
+// }
+//
+// virtual void Get(const std::string& key,
+// const Callback& data_ready) const {
+// bool success = ...
+// std::string* data = new ...
+// data_ready(success, key, data);
+// }
+// };
+class Storage {
+ public:
+ typedef i18n::addressinput::Callback<const std::string&,
+ std::string*> Callback;
+
+ virtual ~Storage() {}
+
+ // Stores |data| for |key|, where |data| is an object allocated on the heap,
+ // which Storage takes ownership of.
+ virtual void Put(const std::string& key, std::string* data) = 0;
+
+ // Retrieves the data for |key| and invokes the |data_ready| callback.
+ virtual void Get(const std::string& key,
+ const Callback& data_ready) const = 0;
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_STORAGE_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/supplier.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/supplier.h
new file mode 100644
index 00000000000..7d079cff450
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/supplier.h
@@ -0,0 +1,54 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_SUPPLIER_H_
+#define I18N_ADDRESSINPUT_SUPPLIER_H_
+
+#include <libaddressinput/callback.h>
+
+namespace i18n {
+namespace addressinput {
+
+class LookupKey;
+class Rule;
+
+// Interface for objects that are able to supply the AddressValidator with the
+// metadata needed to validate an address, as described by a LookupKey.
+class Supplier {
+ public:
+ struct RuleHierarchy;
+ typedef i18n::addressinput::Callback<const LookupKey&,
+ const RuleHierarchy&> Callback;
+
+ virtual ~Supplier() {}
+
+ // Aggregates the metadata needed for |lookup_key| into a RuleHierarchy
+ // object, then calls |supplied|. Implementations of this interface may
+ // either load the necessary data on demand, or fail if the necessary data
+ // hasn't already been loaded.
+ virtual void Supply(const LookupKey& lookup_key,
+ const Callback& supplied) = 0;
+
+ // A RuleHierarchy object encapsulates the hierarchical list of Rule objects
+ // that corresponds to a particular LookupKey.
+ struct RuleHierarchy {
+ RuleHierarchy() : rule() {}
+ const Rule* rule[4]; // Cf. LookupKey::kHierarchy.
+ };
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_SUPPLIER_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/util/basictypes.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/util/basictypes.h
new file mode 100644
index 00000000000..663133fc0a9
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/util/basictypes.h
@@ -0,0 +1,213 @@
+// 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.
+//
+// The original source code is from:
+// https://code.google.com/p/libphonenumber/source/browse/trunk/cpp/src/phonenumbers/base/basictypes.h?r=621
+
+#if I18N_ADDRESSINPUT_USE_BASICTYPES_OVERRIDE
+
+// If building libaddressinput in an environment where there already is another
+// implementation of the basictypes.h header file (like in Chromium), then pass
+// the command line flag -DI18N_ADDRESSINPUT_USE_BASICTYPES_OVERRIDE=1 to the
+// compiler and provide a file named basictypes_override.h, in a location where
+// the compiler will look for it, which provides the desired implementation.
+
+#include "basictypes_override.h"
+
+#else
+
+#ifndef I18N_ADDRESSINPUT_UTIL_BASICTYPES_H_
+#define I18N_ADDRESSINPUT_UTIL_BASICTYPES_H_
+
+#include <climits> // So we can set the bounds of our types
+#include <cstddef> // For size_t
+
+#if !defined(_WIN32)
+// stdint.h is part of C99 but MSVC doesn't have it.
+#include <stdint.h> // For intptr_t.
+#endif
+
+#ifdef INT64_MAX
+
+// INT64_MAX is defined if C99 stdint.h is included; use the
+// native types if available.
+typedef int8_t int8;
+typedef int16_t int16;
+typedef int32_t int32;
+typedef int64_t int64;
+typedef uint8_t uint8;
+typedef uint16_t uint16;
+typedef uint32_t uint32;
+typedef uint64_t uint64;
+
+const uint8 kuint8max = UINT8_MAX;
+const uint16 kuint16max = UINT16_MAX;
+const uint32 kuint32max = UINT32_MAX;
+const uint64 kuint64max = UINT64_MAX;
+const int8 kint8min = INT8_MIN;
+const int8 kint8max = INT8_MAX;
+const int16 kint16min = INT16_MIN;
+const int16 kint16max = INT16_MAX;
+const int32 kint32min = INT32_MIN;
+const int32 kint32max = INT32_MAX;
+const int64 kint64min = INT64_MIN;
+const int64 kint64max = INT64_MAX;
+
+#else // !INT64_MAX
+
+typedef signed char int8;
+typedef short int16;
+// TODO: Remove these type guards. These are to avoid conflicts with
+// obsolete/protypes.h in the Gecko SDK.
+#ifndef _INT32
+#define _INT32
+typedef int int32;
+#endif
+
+// The NSPR system headers define 64-bit as |long| when possible. In order to
+// not have typedef mismatches, we do the same on LP64.
+#if __LP64__
+typedef long int64;
+#else
+typedef long long int64;
+#endif
+
+// NOTE: unsigned types are DANGEROUS in loops and other arithmetical
+// places. Use the signed types unless your variable represents a bit
+// pattern (eg a hash value) or you really need the extra bit. Do NOT
+// use 'unsigned' to express "this value should always be positive";
+// use assertions for this.
+
+typedef unsigned char uint8;
+typedef unsigned short uint16;
+// TODO: Remove these type guards. These are to avoid conflicts with
+// obsolete/protypes.h in the Gecko SDK.
+#ifndef _UINT32
+#define _UINT32
+typedef unsigned int uint32;
+#endif
+
+// See the comment above about NSPR and 64-bit.
+#if __LP64__
+typedef unsigned long uint64;
+#else
+typedef unsigned long long uint64;
+#endif
+
+#endif // !INT64_MAX
+
+typedef signed char schar;
+
+// A type to represent a Unicode code-point value. As of Unicode 4.0,
+// such values require up to 21 bits.
+// (For type-checking on pointers, make this explicitly signed,
+// and it should always be the signed version of whatever int32 is.)
+typedef signed int char32;
+
+// A macro to disallow the copy constructor and operator= functions
+// This should be used in the private: declarations for a class
+#if !defined(DISALLOW_COPY_AND_ASSIGN)
+#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
+ TypeName(const TypeName&); \
+ void operator=(const TypeName&)
+#endif
+
+// The arraysize(arr) macro returns the # of elements in an array arr.
+// The expression is a compile-time constant, and therefore can be
+// used in defining new arrays, for example. If you use arraysize on
+// a pointer by mistake, you will get a compile-time error.
+//
+// One caveat is that arraysize() doesn't accept any array of an
+// anonymous type or a type defined inside a function. In these rare
+// cases, you have to use the unsafe ARRAYSIZE_UNSAFE() macro below. This is
+// due to a limitation in C++'s template system. The limitation might
+// eventually be removed, but it hasn't happened yet.
+
+// This template function declaration is used in defining arraysize.
+// Note that the function doesn't need an implementation, as we only
+// use its type.
+template <typename T, size_t N>
+char (&ArraySizeHelper(T (&array)[N]))[N];
+
+// That gcc wants both of these prototypes seems mysterious. VC, for
+// its part, can't decide which to use (another mystery). Matching of
+// template overloads: the final frontier.
+#ifndef _MSC_VER
+template <typename T, size_t N>
+char (&ArraySizeHelper(const T (&array)[N]))[N];
+#endif
+
+#if !defined(arraysize)
+#define arraysize(array) (sizeof(ArraySizeHelper(array)))
+#endif
+
+// ARRAYSIZE_UNSAFE performs essentially the same calculation as arraysize,
+// but can be used on anonymous types or types defined inside
+// functions. It's less safe than arraysize as it accepts some
+// (although not all) pointers. Therefore, you should use arraysize
+// whenever possible.
+//
+// The expression ARRAYSIZE_UNSAFE(a) is a compile-time constant of type
+// size_t.
+//
+// ARRAYSIZE_UNSAFE catches a few type errors. If you see a compiler error
+//
+// "warning: division by zero in ..."
+//
+// when using ARRAYSIZE_UNSAFE, you are (wrongfully) giving it a pointer.
+// You should only use ARRAYSIZE_UNSAFE on statically allocated arrays.
+//
+// The following comments are on the implementation details, and can
+// be ignored by the users.
+//
+// ARRAYSIZE_UNSAFE(arr) works by inspecting sizeof(arr) (the # of bytes in
+// the array) and sizeof(*(arr)) (the # of bytes in one array
+// element). If the former is divisible by the latter, perhaps arr is
+// indeed an array, in which case the division result is the # of
+// elements in the array. Otherwise, arr cannot possibly be an array,
+// and we generate a compiler error to prevent the code from
+// compiling.
+//
+// Since the size of bool is implementation-defined, we need to cast
+// !(sizeof(a) & sizeof(*(a))) to size_t in order to ensure the final
+// result has type size_t.
+//
+// This macro is not perfect as it wrongfully accepts certain
+// pointers, namely where the pointer size is divisible by the pointee
+// size. Since all our code has to go through a 32-bit compiler,
+// where a pointer is 4 bytes, this means all pointers to a type whose
+// size is 3 or greater than 4 will be (righteously) rejected.
+
+#if !defined(ARRAYSIZE_UNSAFE)
+#define ARRAYSIZE_UNSAFE(a) \
+ ((sizeof(a) / sizeof(*(a))) / \
+ static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))))
+#endif
+
+// The COMPILE_ASSERT macro can be used to verify that a compile time
+// expression is true. For example, you could use it to verify the
+// size of a static array:
+//
+// COMPILE_ASSERT(ARRAYSIZE_UNSAFE(content_type_names) == CONTENT_NUM_TYPES,
+// content_type_names_incorrect_size);
+//
+// or to make sure a struct is smaller than a certain size:
+//
+// COMPILE_ASSERT(sizeof(foo) < 128, foo_too_large);
+//
+// The second argument to the macro is the name of the variable. If
+// the expression is false, most compilers will issue a warning/error
+// containing the name of the variable.
+
+template <bool>
+struct CompileAssert {
+};
+
+#if !defined(COMPILE_ASSERT)
+#define COMPILE_ASSERT(expr, msg) \
+ typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1]
+#endif
+
+#endif // I18N_ADDRESSINPUT_UTIL_BASICTYPES_H_
+#endif // I18N_ADDRESSINPUT_USE_BASICTYPES_OVERRIDE
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/util/scoped_ptr.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/util/scoped_ptr.h
new file mode 100644
index 00000000000..a88c2a9f6ab
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/util/scoped_ptr.h
@@ -0,0 +1,444 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The original source code is from:
+// https://code.google.com/p/libphonenumber/source/browse/trunk/cpp/src/phonenumbers/base/memory/scoped_ptr.h?r=621
+
+#ifndef I18N_ADDRESSINPUT_UTIL_SCOPED_PTR_H_
+#define I18N_ADDRESSINPUT_UTIL_SCOPED_PTR_H_
+
+// This is an implementation designed to match the anticipated future TR2
+// implementation of the scoped_ptr class and scoped_ptr_malloc (deprecated).
+
+#include <libaddressinput/util/basictypes.h>
+#include <libaddressinput/util/template_util.h>
+
+#include <algorithm> // For std::swap().
+#include <cassert>
+#include <cstddef>
+#include <cstdlib>
+
+namespace i18n {
+namespace addressinput {
+
+// Function object which deletes its parameter, which must be a pointer.
+// If C is an array type, invokes 'delete[]' on the parameter; otherwise,
+// invokes 'delete'. The default deleter for scoped_ptr<T>.
+template <class T>
+struct DefaultDeleter {
+ DefaultDeleter() {}
+ template <typename U> DefaultDeleter(const DefaultDeleter<U>& other) {
+ // IMPLEMENTATION NOTE: C++11 20.7.1.1.2p2 only provides this constructor
+ // if U* is implicitly convertible to T* and U is not an array type.
+ //
+ // Correct implementation should use SFINAE to disable this
+ // constructor. However, since there are no other 1-argument constructors,
+ // using a COMPILE_ASSERT() based on is_convertible<> and requiring
+ // complete types is simpler and will cause compile failures for equivalent
+ // misuses.
+ //
+ // Note, the is_convertible<U*, T*> check also ensures that U is not an
+ // array. T is guaranteed to be a non-array, so any U* where U is an array
+ // cannot convert to T*.
+ enum { T_must_be_complete = sizeof(T) };
+ enum { U_must_be_complete = sizeof(U) };
+ COMPILE_ASSERT((is_convertible<U*, T*>::value),
+ U_ptr_must_implicitly_convert_to_T_ptr);
+ }
+ inline void operator()(T* ptr) const {
+ enum { type_must_be_complete = sizeof(T) };
+ delete ptr;
+ }
+};
+
+// Specialization of DefaultDeleter for array types.
+template <class T>
+struct DefaultDeleter<T[]> {
+ inline void operator()(T* ptr) const {
+ enum { type_must_be_complete = sizeof(T) };
+ delete[] ptr;
+ }
+
+ private:
+ // Disable this operator for any U != T because it is undefined to execute
+ // an array delete when the static type of the array mismatches the dynamic
+ // type.
+ //
+ // References:
+ // C++98 [expr.delete]p3
+ // http://cplusplus.github.com/LWG/lwg-defects.html#938
+ template <typename U> void operator()(U* array) const;
+};
+
+template <class T, int n>
+struct DefaultDeleter<T[n]> {
+ // Never allow someone to declare something like scoped_ptr<int[10]>.
+ COMPILE_ASSERT(sizeof(T) == -1, do_not_use_array_with_size_as_type);
+};
+
+// Function object which invokes 'free' on its parameter, which must be
+// a pointer. Can be used to store malloc-allocated pointers in scoped_ptr:
+//
+// scoped_ptr<int, base::FreeDeleter> foo_ptr(
+// static_cast<int*>(malloc(sizeof(int))));
+struct FreeDeleter {
+ inline void operator()(void* ptr) const {
+ free(ptr);
+ }
+};
+
+// Minimal implementation of the core logic of scoped_ptr, suitable for
+// reuse in both scoped_ptr and its specializations.
+template <class T, class D>
+class scoped_ptr_impl {
+ public:
+ explicit scoped_ptr_impl(T* p) : data_(p) { }
+
+ // Initializer for deleters that have data parameters.
+ scoped_ptr_impl(T* p, const D& d) : data_(p, d) {}
+
+ // Templated constructor that destructively takes the value from another
+ // scoped_ptr_impl.
+ template <typename U, typename V>
+ scoped_ptr_impl(scoped_ptr_impl<U, V>* other)
+ : data_(other->release(), other->get_deleter()) {
+ // We do not support move-only deleters. We could modify our move
+ // emulation to have base::subtle::move() and base::subtle::forward()
+ // functions that are imperfect emulations of their C++11 equivalents,
+ // but until there's a requirement, just assume deleters are copyable.
+ }
+
+ template <typename U, typename V>
+ void TakeState(scoped_ptr_impl<U, V>* other) {
+ // See comment in templated constructor above regarding lack of support
+ // for move-only deleters.
+ reset(other->release());
+ get_deleter() = other->get_deleter();
+ }
+
+ ~scoped_ptr_impl() {
+ if (data_.ptr != NULL) {
+ // Not using get_deleter() saves one function call in non-optimized
+ // builds.
+ static_cast<D&>(data_)(data_.ptr);
+ }
+ }
+
+ void reset(T* p) {
+ // This is a self-reset, which is no longer allowed: http://crbug.com/162971
+ if (p != NULL && p == data_.ptr)
+ abort();
+
+ // Note that running data_.ptr = p can lead to undefined behavior if
+ // get_deleter()(get()) deletes this. In order to pevent this, reset()
+ // should update the stored pointer before deleting its old value.
+ //
+ // However, changing reset() to use that behavior may cause current code to
+ // break in unexpected ways. If the destruction of the owned object
+ // dereferences the scoped_ptr when it is destroyed by a call to reset(),
+ // then it will incorrectly dispatch calls to |p| rather than the original
+ // value of |data_.ptr|.
+ //
+ // During the transition period, set the stored pointer to NULL while
+ // deleting the object. Eventually, this safety check will be removed to
+ // prevent the scenario initially described from occuring and
+ // http://crbug.com/176091 can be closed.
+ T* old = data_.ptr;
+ data_.ptr = NULL;
+ if (old != NULL)
+ static_cast<D&>(data_)(old);
+ data_.ptr = p;
+ }
+
+ T* get() const { return data_.ptr; }
+
+ D& get_deleter() { return data_; }
+ const D& get_deleter() const { return data_; }
+
+ void swap(scoped_ptr_impl& p2) {
+ // Standard swap idiom: 'using std::swap' ensures that std::swap is
+ // present in the overload set, but we call swap unqualified so that
+ // any more-specific overloads can be used, if available.
+ using std::swap;
+ swap(static_cast<D&>(data_), static_cast<D&>(p2.data_));
+ swap(data_.ptr, p2.data_.ptr);
+ }
+
+ T* release() {
+ T* old_ptr = data_.ptr;
+ data_.ptr = NULL;
+ return old_ptr;
+ }
+
+ private:
+ // Needed to allow type-converting constructor.
+ template <typename U, typename V> friend class scoped_ptr_impl;
+
+ // Use the empty base class optimization to allow us to have a D
+ // member, while avoiding any space overhead for it when D is an
+ // empty class. See e.g. http://www.cantrip.org/emptyopt.html for a good
+ // discussion of this technique.
+ struct Data : public D {
+ explicit Data(T* ptr_in) : ptr(ptr_in) {}
+ Data(T* ptr_in, const D& other) : D(other), ptr(ptr_in) {}
+ T* ptr;
+ };
+
+ Data data_;
+
+ DISALLOW_COPY_AND_ASSIGN(scoped_ptr_impl);
+};
+
+// A scoped_ptr<T> is like a T*, except that the destructor of scoped_ptr<T>
+// automatically deletes the pointer it holds (if any).
+// That is, scoped_ptr<T> owns the T object that it points to.
+// Like a T*, a scoped_ptr<T> may hold either NULL or a pointer to a T object.
+// Also like T*, scoped_ptr<T> is thread-compatible, and once you
+// dereference it, you get the thread safety guarantees of T.
+//
+// The size of scoped_ptr is small. On most compilers, when using the
+// DefaultDeleter, sizeof(scoped_ptr<T>) == sizeof(T*). Custom deleters will
+// increase the size proportional to whatever state they need to have. See
+// comments inside scoped_ptr_impl<> for details.
+//
+// Current implementation targets having a strict subset of C++11's
+// unique_ptr<> features. Known deficiencies include not supporting move-only
+// deleteres, function pointers as deleters, and deleters with reference
+// types.
+template <class T, class D = DefaultDeleter<T> >
+class scoped_ptr {
+ public:
+ // The element and deleter types.
+ typedef T element_type;
+ typedef D deleter_type;
+
+ // Constructor. Defaults to initializing with NULL.
+ scoped_ptr() : impl_(NULL) { }
+
+ // Constructor. Takes ownership of p.
+ explicit scoped_ptr(element_type* p) : impl_(p) { }
+
+ // Constructor. Allows initialization of a stateful deleter.
+ scoped_ptr(element_type* p, const D& d) : impl_(p, d) { }
+
+ // Constructor. Allows construction from a scoped_ptr rvalue for a
+ // convertible type and deleter.
+ //
+ // IMPLEMENTATION NOTE: C++11 unique_ptr<> keeps this constructor distinct
+ // from the normal move constructor. By C++11 20.7.1.2.1.21, this constructor
+ // has different post-conditions if D is a reference type. Since this
+ // implementation does not support deleters with reference type,
+ // we do not need a separate move constructor allowing us to avoid one
+ // use of SFINAE. You only need to care about this if you modify the
+ // implementation of scoped_ptr.
+ template <typename U, typename V>
+ scoped_ptr(scoped_ptr<U, V> other) : impl_(&other.impl_) {
+ COMPILE_ASSERT(!is_array<U>::value, U_cannot_be_an_array);
+ }
+
+ // operator=. Allows assignment from a scoped_ptr rvalue for a convertible
+ // type and deleter.
+ //
+ // IMPLEMENTATION NOTE: C++11 unique_ptr<> keeps this operator= distinct from
+ // the normal move assignment operator. By C++11 20.7.1.2.3.4, this templated
+ // form has different requirements on for move-only Deleters. Since this
+ // implementation does not support move-only Deleters, we do not need a
+ // separate move assignment operator allowing us to avoid one use of SFINAE.
+ // You only need to care about this if you modify the implementation of
+ // scoped_ptr.
+ template <typename U, typename V>
+ scoped_ptr& operator=(scoped_ptr<U, V> rhs) {
+ COMPILE_ASSERT(!is_array<U>::value, U_cannot_be_an_array);
+ impl_.TakeState(&rhs.impl_);
+ return *this;
+ }
+
+ // Reset. Deletes the currently owned object, if any.
+ // Then takes ownership of a new object, if given.
+ void reset(element_type* p = NULL) { impl_.reset(p); }
+
+ // Accessors to get the owned object.
+ // operator* and operator-> will assert() if there is no current object.
+ element_type& operator*() const {
+ assert(impl_.get() != NULL);
+ return *impl_.get();
+ }
+ element_type* operator->() const {
+ assert(impl_.get() != NULL);
+ return impl_.get();
+ }
+ element_type* get() const { return impl_.get(); }
+
+ // Access to the deleter.
+ deleter_type& get_deleter() { return impl_.get_deleter(); }
+ const deleter_type& get_deleter() const { return impl_.get_deleter(); }
+
+ // Allow scoped_ptr<element_type> to be used in boolean expressions, but not
+ // implicitly convertible to a real bool (which is dangerous).
+ private:
+ typedef scoped_ptr_impl<element_type, deleter_type> scoped_ptr::*Testable;
+
+ public:
+ operator Testable() const { return impl_.get() ? &scoped_ptr::impl_ : NULL; }
+
+ // Comparison operators.
+ // These return whether two scoped_ptr refer to the same object, not just to
+ // two different but equal objects.
+ bool operator==(const element_type* p) const { return impl_.get() == p; }
+ bool operator!=(const element_type* p) const { return impl_.get() != p; }
+
+ // Swap two scoped pointers.
+ void swap(scoped_ptr& p2) {
+ impl_.swap(p2.impl_);
+ }
+
+ // Release a pointer.
+ // The return value is the current pointer held by this object.
+ // If this object holds a NULL pointer, the return value is NULL.
+ // After this operation, this object will hold a NULL pointer,
+ // and will not own the object any more.
+ element_type* release() {
+ return impl_.release();
+ }
+
+ private:
+ // Needed to reach into |impl_| in the constructor.
+ template <typename U, typename V> friend class scoped_ptr;
+ scoped_ptr_impl<element_type, deleter_type> impl_;
+
+ // Forbid comparison of scoped_ptr types. If U != T, it totally
+ // doesn't make sense, and if U == T, it still doesn't make sense
+ // because you should never have the same object owned by two different
+ // scoped_ptrs.
+ template <class U> bool operator==(scoped_ptr<U> const& p2) const;
+ template <class U> bool operator!=(scoped_ptr<U> const& p2) const;
+};
+
+template <class T, class D>
+class scoped_ptr<T[], D> {
+ public:
+ // The element and deleter types.
+ typedef T element_type;
+ typedef D deleter_type;
+
+ // Constructor. Defaults to initializing with NULL.
+ scoped_ptr() : impl_(NULL) { }
+
+ // Constructor. Stores the given array. Note that the argument's type
+ // must exactly match T*. In particular:
+ // - it cannot be a pointer to a type derived from T, because it is
+ // inherently unsafe in the general case to access an array through a
+ // pointer whose dynamic type does not match its static type (eg., if
+ // T and the derived types had different sizes access would be
+ // incorrectly calculated). Deletion is also always undefined
+ // (C++98 [expr.delete]p3). If you're doing this, fix your code.
+ // - it cannot be NULL, because NULL is an integral expression, not a
+ // pointer to T. Use the no-argument version instead of explicitly
+ // passing NULL.
+ // - it cannot be const-qualified differently from T per unique_ptr spec
+ // (http://cplusplus.github.com/LWG/lwg-active.html#2118). Users wanting
+ // to work around this may use implicit_cast<const T*>().
+ // However, because of the first bullet in this comment, users MUST
+ // NOT use implicit_cast<Base*>() to upcast the static type of the array.
+ explicit scoped_ptr(element_type* array) : impl_(array) { }
+
+ // Reset. Deletes the currently owned array, if any.
+ // Then takes ownership of a new object, if given.
+ void reset(element_type* array = NULL) { impl_.reset(array); }
+
+ // Accessors to get the owned array.
+ element_type& operator[](size_t i) const {
+ assert(impl_.get() != NULL);
+ return impl_.get()[i];
+ }
+ element_type* get() const { return impl_.get(); }
+
+ // Access to the deleter.
+ deleter_type& get_deleter() { return impl_.get_deleter(); }
+ const deleter_type& get_deleter() const { return impl_.get_deleter(); }
+
+ // Allow scoped_ptr<element_type> to be used in boolean expressions, but not
+ // implicitly convertible to a real bool (which is dangerous).
+ private:
+ typedef scoped_ptr_impl<element_type, deleter_type> scoped_ptr::*Testable;
+
+ public:
+ operator Testable() const { return impl_.get() ? &scoped_ptr::impl_ : NULL; }
+
+ // Comparison operators.
+ // These return whether two scoped_ptr refer to the same object, not just to
+ // two different but equal objects.
+ bool operator==(element_type* array) const { return impl_.get() == array; }
+ bool operator!=(element_type* array) const { return impl_.get() != array; }
+
+ // Swap two scoped pointers.
+ void swap(scoped_ptr& p2) {
+ impl_.swap(p2.impl_);
+ }
+
+ // Release a pointer.
+ // The return value is the current pointer held by this object.
+ // If this object holds a NULL pointer, the return value is NULL.
+ // After this operation, this object will hold a NULL pointer,
+ // and will not own the object any more.
+ element_type* release() {
+ return impl_.release();
+ }
+
+ private:
+ // Force element_type to be a complete type.
+ enum { type_must_be_complete = sizeof(element_type) };
+
+ // Actually hold the data.
+ scoped_ptr_impl<element_type, deleter_type> impl_;
+
+ // Disable initialization from any type other than element_type*, by
+ // providing a constructor that matches such an initialization, but is
+ // private and has no definition. This is disabled because it is not safe to
+ // call delete[] on an array whose static type does not match its dynamic
+ // type.
+ template <typename U> explicit scoped_ptr(U* array);
+ explicit scoped_ptr(int disallow_construction_from_null);
+
+ // Disable reset() from any type other than element_type*, for the same
+ // reasons as the constructor above.
+ template <typename U> void reset(U* array);
+ void reset(int disallow_reset_from_null);
+
+ // Forbid comparison of scoped_ptr types. If U != T, it totally
+ // doesn't make sense, and if U == T, it still doesn't make sense
+ // because you should never have the same object owned by two different
+ // scoped_ptrs.
+ template <class U> bool operator==(scoped_ptr<U> const& p2) const;
+ template <class U> bool operator!=(scoped_ptr<U> const& p2) const;
+};
+
+// Free functions
+template <class T, class D>
+void swap(scoped_ptr<T, D>& p1, scoped_ptr<T, D>& p2) {
+ p1.swap(p2);
+}
+
+template <class T, class D>
+bool operator==(T* p1, const scoped_ptr<T, D>& p2) {
+ return p1 == p2.get();
+}
+
+template <class T, class D>
+bool operator!=(T* p1, const scoped_ptr<T, D>& p2) {
+ return p1 != p2.get();
+}
+
+// A function to convert T* into scoped_ptr<T>
+// Doing e.g. make_scoped_ptr(new FooBarBaz<type>(arg)) is a shorter notation
+// for scoped_ptr<FooBarBaz<type> >(new FooBarBaz<type>(arg))
+template <typename T>
+scoped_ptr<T> make_scoped_ptr(T* ptr) {
+ return scoped_ptr<T>(ptr);
+}
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_UTIL_SCOPED_PTR_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/util/template_util.h b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/util/template_util.h
new file mode 100644
index 00000000000..35125e12d0b
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/include/libaddressinput/util/template_util.h
@@ -0,0 +1,111 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The original source code is from:
+// https://code.google.com/p/libphonenumber/source/browse/trunk/cpp/src/phonenumbers/base/template_util.h?r=621
+
+#ifndef I18N_ADDRESSINPUT_UTIL_TEMPLATE_UTIL_H_
+#define I18N_ADDRESSINPUT_UTIL_TEMPLATE_UTIL_H_
+
+#include <cstddef> // For size_t.
+
+namespace i18n {
+namespace addressinput {
+
+// template definitions from tr1
+
+template<class T, T v>
+struct integral_constant {
+ static const T value = v;
+ typedef T value_type;
+ typedef integral_constant<T, v> type;
+};
+
+template <class T, T v> const T integral_constant<T, v>::value;
+
+typedef integral_constant<bool, true> true_type;
+typedef integral_constant<bool, false> false_type;
+
+template <class T> struct is_pointer : false_type {};
+template <class T> struct is_pointer<T*> : true_type {};
+
+template <class T, class U> struct is_same : public false_type {};
+template <class T> struct is_same<T,T> : true_type {};
+
+template<class> struct is_array : public false_type {};
+template<class T, size_t n> struct is_array<T[n]> : public true_type {};
+template<class T> struct is_array<T[]> : public true_type {};
+
+template <class T> struct is_non_const_reference : false_type {};
+template <class T> struct is_non_const_reference<T&> : true_type {};
+template <class T> struct is_non_const_reference<const T&> : false_type {};
+
+template <class T> struct is_void : false_type {};
+template <> struct is_void<void> : true_type {};
+
+namespace internal {
+
+// Types YesType and NoType are guaranteed such that sizeof(YesType) <
+// sizeof(NoType).
+typedef char YesType;
+
+struct NoType {
+ YesType dummy[2];
+};
+
+// This class is an implementation detail for is_convertible, and you
+// don't need to know how it works to use is_convertible. For those
+// who care: we declare two different functions, one whose argument is
+// of type To and one with a variadic argument list. We give them
+// return types of different size, so we can use sizeof to trick the
+// compiler into telling us which function it would have chosen if we
+// had called it with an argument of type From. See Alexandrescu's
+// _Modern C++ Design_ for more details on this sort of trick.
+
+struct ConvertHelper {
+ template <typename To>
+ static YesType Test(To);
+
+ template <typename To>
+ static NoType Test(...);
+
+ template <typename From>
+ static From& Create();
+};
+
+// Used to determine if a type is a struct/union/class. Inspired by Boost's
+// is_class type_trait implementation.
+struct IsClassHelper {
+ template <typename C>
+ static YesType Test(void(C::*)(void));
+
+ template <typename C>
+ static NoType Test(...);
+};
+
+} // namespace internal
+
+// Inherits from true_type if From is convertible to To, false_type otherwise.
+//
+// Note that if the type is convertible, this will be a true_type REGARDLESS
+// of whether or not the conversion would emit a warning.
+template <typename From, typename To>
+struct is_convertible
+ : integral_constant<bool,
+ sizeof(internal::ConvertHelper::Test<To>(
+ internal::ConvertHelper::Create<From>())) ==
+ sizeof(internal::YesType)> {
+};
+
+template <typename T>
+struct is_class
+ : integral_constant<bool,
+ sizeof(internal::IsClassHelper::Test<T>(0)) ==
+ sizeof(internal::YesType)> {
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_UTIL_TEMPLATE_UTIL_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/res/messages.grd b/chromium/third_party/libaddressinput/src/cpp/res/messages.grd
new file mode 100644
index 00000000000..dd3aa64e8f1
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/res/messages.grd
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2013 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<grit base_dir="." latest_public_release="0" current_release="1"
+ source_lang_id="en" enc_check="möl">
+ <outputs>
+ <output filename="messages.h" type="rc_header" lang="en">
+ <emit>
+ <!-- If the emit element is not specified, then the generated
+ messages.h includes an atlres.h file from Windows Template
+ Library (WTL). -->
+ </emit>
+ </output>
+ <output filename="en_messages.cc" lang="en" type="c_format" />
+ </outputs>
+ <release seq="1" allow_pseudo="false">
+ <messages fallback_to_english="true">
+ <part file="messages.grdp" />
+ </messages>
+ </release>
+</grit>
diff --git a/chromium/third_party/libaddressinput/src/cpp/res/messages.grdp b/chromium/third_party/libaddressinput/src/cpp/res/messages.grdp
new file mode 100644
index 00000000000..ca893542bc6
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/res/messages.grdp
@@ -0,0 +1,286 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2013 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<grit-part>
+ <message
+ name="IDS_LIBADDRESSINPUT_COUNTRY_OR_REGION_LABEL"
+ desc="A country or a political region (Countries like the United States or
+ regions like Hong Kong or Macao, or places like Taiwan, where
+ whether it is a country or not is a politically sensitive
+ question).">
+ Country / Region
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_LOCALITY_LABEL"
+ desc="E.g., New York City.">
+ City
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_POST_TOWN"
+ desc="The name of a town through
+ which postal deliveries are routed, present in UK addresses.
+ See: http://en.wikipedia.org/wiki/Post_town">
+ Post Town
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_SUBURB"
+ desc="Smaller part of a city used in some addresses in countries like New
+ Zealand to give a more specific location in a postal address.">
+ Suburb
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_VILLAGE_TOWNSHIP"
+ desc="A unit used in postal addresses in Malaysia, which is smaller than the
+ city/town, and represents a village, township, or precinct.">
+ Village / Township
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_ADDRESS_LINE_1_LABEL"
+ desc="E.g., 18th Street, Unit 3.">
+ Street address
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_PIN_CODE_LABEL"
+ desc="PIN (Postal Index Number) Code. Values are numeric. Used in India.">
+ PIN code
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_POSTAL_CODE_LABEL"
+ desc="Postal Code. Values are frequently alphanumeric. Used in countries
+ such as Switzerland.">
+ Postal code
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_ZIP_CODE_LABEL"
+ desc="ZIP code. Values are frequently alphanumeric. Used in countries such
+ as the US.">
+ ZIP code
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_AREA"
+ desc="Administrative Area for Hong Kong (e.g., Kowloon).">
+ Area
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_COUNTY"
+ desc="Administrative Area for United Kingdoms (e.g. York) or for the
+ United States (e.g. Orange County).">
+ County
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_DEPARTMENT"
+ desc="Administrative Area for Nicaragua (e.g., Boaco) or France.">
+ Department
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_DISTRICT"
+ desc="Administrative Area for Nauru Central Pacific (e.g., Aiwo district),
+ or area of a town (a neighbourhood/suburb) used for addresses in
+ Korea and China.">
+ District
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_DO_SI"
+ desc="Administrative Area for Korea (e.g., Gyeonggi-do or Busan-si).">
+ Do/Si
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_EMIRATE"
+ desc="Administrative Area for United Arab Emirates (e.g., Abu Dhabi).">
+ Emirate
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_ISLAND"
+ desc="Administrative Area for certain countries (e.g., Bahama's Cat
+ Island).">
+ Island
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_OBLAST"
+ desc="Administrative Area for certain countries (e.g., Russia's
+ Leningrad).">
+ Oblast
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_PARISH"
+ desc="Administrative Area for certain countries (e.g., Andorra's
+ Canillo).">
+ Parish
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_PREFECTURE"
+ desc="Administrative Area for Japan (e.g., Hokkaido).">
+ Prefecture
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_PROVINCE"
+ desc="Administrative Area for certain countries (e.g., France's
+ Champagne).">
+ Province
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_STATE"
+ desc="Administrative Area for certain countries (e.g., US' California).">
+ State
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_ORGANIZATION_LABEL"
+ desc="Label for the field of organization, firm, company, or institution
+ in an address. Examples of values in this field: Google,
+ Department of Transportation, University of Cambridge.">
+ Organization
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_RECIPIENT_LABEL"
+ desc="Label for the field for a person's name in an address.">
+ Name
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_NEIGHBORHOOD"
+ desc="Label for a neighborhood, shown as part of an address input form.">
+ Neighborhood
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_MISSING_REQUIRED_FIELD"
+ desc="Error message shown with a UI field when it is a required field and
+ the user has not filled it out.">
+ You can't leave this empty.
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_MISSING_REQUIRED_POSTAL_CODE_EXAMPLE_AND_URL"
+ desc="Error message shown with the postal code field when it is a required
+ field and the user has not filled it out, providing an example
+ postal code and a link to this country's postal service that a user
+ can use to look up their postal code.">
+ You must provide a postal code, for example <ph name="EXAMPLE">$1<ex>90291</ex></ph>. Don't know your postal code? Find it out <ph name="BEGIN_LINK">$2</ph>here<ph name="END_LINK">$3</ph>.
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_MISSING_REQUIRED_POSTAL_CODE_EXAMPLE"
+ desc="Error message shown with the postal code field when it is a required
+ field and the user has not filled it out, providing an example
+ postal code.">
+ You must provide a postal code, for example <ph name="EXAMPLE">$1<ex>90291</ex></ph>.
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_MISSING_REQUIRED_ZIP_CODE_EXAMPLE_AND_URL"
+ desc="Error message shown with the ZIP code field when it is a required
+ field and the user has not filled it out, providing an example ZIP
+ code and a link to this country's postal service that a user can use
+ to look up their ZIP code. This is specifically for countries using
+ ZIP codes instead of Postal codes, such as America.">
+ You must provide a ZIP code, for example <ph name="EXAMPLE">$1<ex>90291</ex></ph>. Don't know your ZIP code? Find it out <ph name="BEGIN_LINK">$2</ph>here<ph name="END_LINK">$3</ph>.
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_MISSING_REQUIRED_ZIP_CODE_EXAMPLE"
+ desc="Error message shown with the ZIP code field when it is a required
+ field and the user has not filled it out, providing an example ZIP
+ code.">
+ You must provide a ZIP code, for example <ph name="EXAMPLE">$1<ex>90291</ex></ph>.
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_UNKNOWN_VALUE"
+ desc="Occurs when the user fills out the wrong value for an address field.
+ For example, this would be shown when putting 'Cupertino' in United
+ States' State field.">
+ <ph name="FIELD_VALUE">$1<ex>Cupertino</ex></ph> is not recognized as a known value for this field.
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_POSTAL_CODE_EXAMPLE_AND_URL"
+ desc="Occurs when the user fills out a postal code that does not conform
+ to the country's postal code format. For example, this would be
+ shown when using '80' as a Swiss postal code, which is normally 4
+ digits long. Provides an example postal code and a link to this
+ country's postal service that a user can use to look up their postal
+ code.">
+ This postal code format is not recognized. Example of a valid postal code: <ph name="EXAMPLE">$1<ex>90291</ex></ph>. Don't know your postal code? Find it out <ph name="BEGIN_LINK">$2</ph>here<ph name="END_LINK">$3</ph>.
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_POSTAL_CODE_EXAMPLE"
+ desc="Occurs when the user fills out a postal code that does not conform
+ to the country's postal code format. For example, this would be
+ shown when using '80' as a Swiss postal code, which is normally 4
+ digits long. Provides an example postal code.">
+ This postal code format is not recognized. Example of a valid postal code: <ph name="EXAMPLE">$1<ex>90291</ex></ph>.
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_POSTAL_CODE"
+ desc="Occurs when the user fills out a postal code that does not conform
+ to the country's postal code format. For example, this would be
+ shown when using '80' as a Swiss postal code, which is normally 4
+ digits long.">
+ This postal code format is not recognized.
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_ZIP_CODE_EXAMPLE_AND_URL"
+ desc="Occurs when the user fills out a ZIP code that does not conform to
+ the country's ZIP code format. For example, this would be shown when
+ using '901' as a ZIP code for the United States. Provides an example
+ ZIP code and a link to this country's postal service that a user can
+ use to look up their ZIP code.">
+ This ZIP code format is not recognized. Example of a valid ZIP code: <ph name="EXAMPLE">$1<ex>90291</ex></ph>. Don't know your ZIP code? Find it out <ph name="BEGIN_LINK">$2</ph>here<ph name="END_LINK">$3</ph>.
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_ZIP_CODE_EXAMPLE"
+ desc="Occurs when the user fills out a ZIP code that does not conform to
+ the country's ZIP code format. For example, this would be shown when
+ using '901' as a ZIP code for the United States. Provides an example
+ ZIP code.">
+ This ZIP code format is not recognized. Example of a valid ZIP code: <ph name="EXAMPLE">$1<ex>90291</ex></ph>.
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_ZIP"
+ desc="Occurs when the user fills out a ZIP code that does not conform to
+ the country's ZIP code format. For example, this would be shown when
+ using '901' as a ZIP code for the United States.">
+ This ZIP code format is not recognized.
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_POSTAL_CODE_URL"
+ desc="Occurs when the user fills out the wrong postal code for a certain
+ location. For example, this would be shown when using Z3Z 2Y7 for
+ Alberta, Canada. Provides a link to this country's postal service
+ that a user can use to look up their postal code.">
+ This postal code does not appear to match the rest of this address. Don't know your postal code? Find it out <ph name="BEGIN_LINK">$1</ph>here<ph name="END_LINK">$2</ph>.
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_POSTAL_CODE"
+ desc="Occurs when the user fills out the wrong postal code for a certain
+ location. For example, this would be shown when using Z3Z 2Y7 for
+ Alberta, Canada.">
+ This postal code does not appear to match the rest of this address.
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_ZIP_URL"
+ desc="Occurs when the user fills out the wrong ZIP code for a certain
+ location. For example, this would be shown when using 10001 for
+ Arizona state. Provides a link to this country's postal service that
+ a user can use to look up their ZIP code.">
+ This ZIP code does not appear to match the rest of this address. Don't know your ZIP code? Find it out <ph name="BEGIN_LINK">$1</ph>here<ph name="END_LINK">$2</ph>.
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_ZIP"
+ desc="Occurs when the user fills out the wrong ZIP code for a certain
+ location. For example, this would be shown when using 10001 for
+ Arizona state.">
+ This ZIP code does not appear to match the rest of this address.
+ </message>
+ <message
+ name="IDS_LIBADDRESSINPUT_PO_BOX_FORBIDDEN_VALUE"
+ desc="Occurs when the user fills out a P.O. box as part of a physical
+ address.">
+ This address line appears to contain a post office box. Please use a street or building address.
+ </message>
+</grit-part>
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/address_data.cc b/chromium/third_party/libaddressinput/src/cpp/src/address_data.cc
new file mode 100644
index 00000000000..40d3ee76535
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/address_data.cc
@@ -0,0 +1,153 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <libaddressinput/address_data.h>
+
+#include <libaddressinput/address_field.h>
+#include <libaddressinput/util/basictypes.h>
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <functional>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include <re2/re2.h>
+
+namespace i18n {
+namespace addressinput {
+
+namespace {
+
+// Mapping from AddressField value to pointer to AddressData member.
+std::string AddressData::*kStringField[] = {
+ &AddressData::region_code,
+ &AddressData::administrative_area,
+ &AddressData::locality,
+ &AddressData::dependent_locality,
+ &AddressData::sorting_code,
+ &AddressData::postal_code,
+ NULL,
+ &AddressData::organization,
+ &AddressData::recipient
+};
+
+// Mapping from AddressField value to pointer to AddressData member.
+const std::vector<std::string> AddressData::*kVectorStringField[] = {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &AddressData::address_line,
+ NULL,
+ NULL
+};
+
+COMPILE_ASSERT(arraysize(kStringField) == arraysize(kVectorStringField),
+ field_mapping_array_size_mismatch);
+
+// A string is considered to be "empty" not only if it actually is empty, but
+// also if it contains nothing but whitespace.
+bool IsStringEmpty(const std::string& str) {
+ static const RE2 kMatcher("\\S");
+ return str.empty() || !RE2::PartialMatch(str, kMatcher);
+}
+
+} // namespace
+
+bool AddressData::IsFieldEmpty(AddressField field) const {
+ assert(field >= 0);
+ assert(static_cast<size_t>(field) < arraysize(kStringField));
+ if (kStringField[field] != NULL) {
+ const std::string& value = GetFieldValue(field);
+ return IsStringEmpty(value);
+ } else {
+ const std::vector<std::string>& value = GetRepeatedFieldValue(field);
+ return std::find_if(value.begin(),
+ value.end(),
+ std::not1(std::ptr_fun(&IsStringEmpty))) == value.end();
+ }
+}
+
+const std::string& AddressData::GetFieldValue(
+ AddressField field) const {
+ assert(field >= 0);
+ assert(static_cast<size_t>(field) < arraysize(kStringField));
+ assert(kStringField[field] != NULL);
+ return this->*kStringField[field];
+}
+
+void AddressData::SetFieldValue(AddressField field, const std::string& value) {
+ assert(field >= 0);
+ assert(static_cast<size_t>(field) < arraysize(kStringField));
+ assert(kStringField[field] != NULL);
+ (this->*kStringField[field]).assign(value);
+}
+
+const std::vector<std::string>& AddressData::GetRepeatedFieldValue(
+ AddressField field) const {
+ assert(IsRepeatedFieldValue(field));
+ return this->*kVectorStringField[field];
+}
+
+bool AddressData::operator==(const AddressData& other) const {
+ return region_code == other.region_code &&
+ address_line == other.address_line &&
+ administrative_area == other.administrative_area &&
+ locality == other.locality &&
+ dependent_locality == other.dependent_locality &&
+ postal_code == other.postal_code &&
+ sorting_code == other.sorting_code &&
+ language_code == other.language_code &&
+ organization == other.organization &&
+ recipient == other.recipient;
+}
+
+// static
+bool AddressData::IsRepeatedFieldValue(AddressField field) {
+ assert(field >= 0);
+ assert(static_cast<size_t>(field) < arraysize(kVectorStringField));
+ return kVectorStringField[field] != NULL;
+}
+
+} // namespace addressinput
+} // namespace i18n
+
+std::ostream& operator<<(std::ostream& o,
+ const i18n::addressinput::AddressData& address) {
+ o << "region_code: \"" << address.region_code << "\"\n"
+ "administrative_area: \"" << address.administrative_area << "\"\n"
+ "locality: \"" << address.locality << "\"\n"
+ "dependent_locality: \"" << address.dependent_locality << "\"\n"
+ "postal_code: \"" << address.postal_code << "\"\n"
+ "sorting_code: \"" << address.sorting_code << "\"\n";
+
+ // TODO: Update the field order in the .h file to match the order they are
+ // printed out here, for consistency.
+ for (std::vector<std::string>::const_iterator it =
+ address.address_line.begin();
+ it != address.address_line.end(); ++it) {
+ o << "address_line: \"" << *it << "\"\n";
+ }
+
+ o << "language_code: \"" << address.language_code << "\"\n"
+ "organization: \"" << address.organization << "\"\n"
+ "recipient: \"" << address.recipient << "\"\n";
+
+ return o;
+}
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/address_field.cc b/chromium/third_party/libaddressinput/src/cpp/src/address_field.cc
new file mode 100644
index 00000000000..c5c96d86896
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/address_field.cc
@@ -0,0 +1,47 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <libaddressinput/address_field.h>
+
+#include <libaddressinput/util/basictypes.h>
+
+#include <cstddef>
+#include <ostream>
+
+using i18n::addressinput::AddressField;
+using i18n::addressinput::COUNTRY;
+using i18n::addressinput::RECIPIENT;
+
+std::ostream& operator<<(std::ostream& o, AddressField field) {
+ static const char* const kFieldNames[] = {
+ "COUNTRY",
+ "ADMIN_AREA",
+ "LOCALITY",
+ "DEPENDENT_LOCALITY",
+ "SORTING_CODE",
+ "POSTAL_CODE",
+ "STREET_ADDRESS",
+ "ORGANIZATION",
+ "RECIPIENT"
+ };
+ COMPILE_ASSERT(COUNTRY == 0, bad_base);
+ COMPILE_ASSERT(RECIPIENT == arraysize(kFieldNames) - 1, bad_length);
+
+ if (field < 0 || static_cast<size_t>(field) >= arraysize(kFieldNames)) {
+ o << "[INVALID ENUM VALUE " << static_cast<int>(field) << "]";
+ } else {
+ o << kFieldNames[field];
+ }
+ return o;
+}
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/address_field_util.cc b/chromium/third_party/libaddressinput/src/cpp/src/address_field_util.cc
new file mode 100644
index 00000000000..26de3c04859
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/address_field_util.cc
@@ -0,0 +1,113 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "address_field_util.h"
+
+#include <libaddressinput/address_field.h>
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "format_element.h"
+
+namespace i18n {
+namespace addressinput {
+
+namespace {
+
+std::map<char, AddressField> InitFields() {
+ std::map<char, AddressField> fields;
+ fields.insert(std::make_pair('R', COUNTRY));
+ fields.insert(std::make_pair('S', ADMIN_AREA));
+ fields.insert(std::make_pair('C', LOCALITY));
+ fields.insert(std::make_pair('D', DEPENDENT_LOCALITY));
+ fields.insert(std::make_pair('X', SORTING_CODE));
+ fields.insert(std::make_pair('Z', POSTAL_CODE));
+ fields.insert(std::make_pair('A', STREET_ADDRESS));
+ fields.insert(std::make_pair('O', ORGANIZATION));
+ fields.insert(std::make_pair('N', RECIPIENT));
+ return fields;
+}
+
+const std::map<char, AddressField>& GetFields() {
+ static const std::map<char, AddressField> kFields(InitFields());
+ return kFields;
+}
+
+bool IsFieldToken(char c) {
+ return GetFields().find(c) != GetFields().end();
+}
+
+AddressField ParseFieldToken(char c) {
+ std::map<char, AddressField>::const_iterator it = GetFields().find(c);
+ assert(it != GetFields().end());
+ return it->second;
+}
+
+} // namespace
+
+void ParseFormatRule(const std::string& format,
+ std::vector<FormatElement>* elements) {
+ assert(elements != NULL);
+ elements->clear();
+
+ std::string::const_iterator prev = format.begin();
+ for (std::string::const_iterator next = format.begin();
+ next != format.end(); prev = ++next) {
+ // Find the next field element or newline (indicated by %<TOKEN>).
+ if ((next = std::find(next, format.end(), '%')) == format.end()) {
+ // No more tokens in the format string.
+ break;
+ }
+ if (prev < next) {
+ // Push back preceding literal.
+ elements->push_back(FormatElement(std::string(prev, next)));
+ }
+ if ((prev = ++next) == format.end()) {
+ // Move forward and check we haven't reached the end of the string
+ // (unlikely, it shouldn't end with %).
+ break;
+ }
+ // Process the token after the %.
+ if (*next == 'n') {
+ elements->push_back(FormatElement());
+ } else if (IsFieldToken(*next)) {
+ elements->push_back(FormatElement(ParseFieldToken(*next)));
+ } // Else it's an unknown token, we ignore it.
+ }
+ // Push back any trailing literal.
+ if (prev != format.end()) {
+ elements->push_back(FormatElement(std::string(prev, format.end())));
+ }
+}
+
+void ParseAddressFieldsRequired(const std::string& required,
+ std::vector<AddressField>* fields) {
+ assert(fields != NULL);
+ fields->clear();
+ for (std::string::const_iterator it = required.begin();
+ it != required.end(); ++it) {
+ if (IsFieldToken(*it)) {
+ fields->push_back(ParseFieldToken(*it));
+ }
+ }
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/address_field_util.h b/chromium/third_party/libaddressinput/src/cpp/src/address_field_util.h
new file mode 100644
index 00000000000..123672b0ac5
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/address_field_util.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_ADDRESS_FIELD_UTIL_H_
+#define I18N_ADDRESSINPUT_ADDRESS_FIELD_UTIL_H_
+
+#include <libaddressinput/address_field.h>
+
+#include <string>
+#include <vector>
+
+namespace i18n {
+namespace addressinput {
+
+class FormatElement;
+
+// Clears |fields|, parses |format|, and adds the format address fields to
+// |fields|. The |fields| may also contain NEWLINE elements. For example, parses
+// "%S%C%n%D%X" into {ADMIN_AREA, LOCALITY, NEWLINE, DEPENDENT_LOCALITY,
+// SORTING_CODE}.
+void ParseFormatRule(const std::string& format,
+ std::vector<FormatElement>* fields);
+
+// Clears |fields|, parses |required|, and adds the required fields to |fields|.
+// For example, parses "SCDX" into {ADMIN_AREA, LOCALITY, DEPENDENT_LOCALITY,
+// SORTING_CODE}.
+void ParseAddressFieldsRequired(const std::string& required,
+ std::vector<AddressField>* fields);
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_ADDRESS_FIELD_UTIL_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/address_formatter.cc b/chromium/third_party/libaddressinput/src/cpp/src/address_formatter.cc
new file mode 100644
index 00000000000..b54cbac122c
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/address_formatter.cc
@@ -0,0 +1,230 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <libaddressinput/address_formatter.h>
+
+#include <libaddressinput/address_data.h>
+#include <libaddressinput/address_field.h>
+#include <libaddressinput/util/basictypes.h>
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <functional>
+#include <string>
+#include <vector>
+
+#include "format_element.h"
+#include "language.h"
+#include "region_data_constants.h"
+#include "rule.h"
+#include "util/cctype_tolower_equal.h"
+
+namespace i18n {
+namespace addressinput {
+
+namespace {
+
+const char kCommaSeparator[] = ", ";
+const char kSpaceSeparator[] = " ";
+const char kArabicCommaSeparator[] = "\xD8\x8C" " "; /* "، " */
+
+const char* kLanguagesThatUseSpace[] = {
+ "th",
+ "ko"
+};
+
+const char* kLanguagesThatHaveNoSeparator[] = {
+ "ja",
+ "zh" // All Chinese variants.
+};
+
+// This data is based on CLDR, for languages that are in official use in some
+// country, where Arabic is the most likely script tag.
+// TODO: Consider supporting variants such as tr-Arab by detecting the script
+// code.
+const char* kLanguagesThatUseAnArabicComma[] = {
+ "ar",
+ "az",
+ "fa",
+ "kk",
+ "ku",
+ "ky",
+ "ps",
+ "tg",
+ "tk",
+ "ur",
+ "uz"
+};
+
+std::string GetLineSeparatorForLanguage(const std::string& language_tag) {
+ Language address_language(language_tag);
+
+ // First deal with explicit script tags.
+ if (address_language.has_latin_script) {
+ return kCommaSeparator;
+ }
+
+ // Now guess something appropriate based on the base language.
+ const std::string& base_language = address_language.base;
+ if (std::find_if(kLanguagesThatUseSpace,
+ kLanguagesThatUseSpace + arraysize(kLanguagesThatUseSpace),
+ std::bind2nd(EqualToTolowerString(), base_language)) !=
+ kLanguagesThatUseSpace + arraysize(kLanguagesThatUseSpace)) {
+ return kSpaceSeparator;
+ } else if (std::find_if(
+ kLanguagesThatHaveNoSeparator,
+ kLanguagesThatHaveNoSeparator +
+ arraysize(kLanguagesThatHaveNoSeparator),
+ std::bind2nd(EqualToTolowerString(), base_language)) !=
+ kLanguagesThatHaveNoSeparator +
+ arraysize(kLanguagesThatHaveNoSeparator)) {
+ return "";
+ } else if (std::find_if(
+ kLanguagesThatUseAnArabicComma,
+ kLanguagesThatUseAnArabicComma +
+ arraysize(kLanguagesThatUseAnArabicComma),
+ std::bind2nd(EqualToTolowerString(), base_language)) !=
+ kLanguagesThatUseAnArabicComma +
+ arraysize(kLanguagesThatUseAnArabicComma)) {
+ return kArabicCommaSeparator;
+ }
+ // Either the language is a Latin-script language, or no language was
+ // specified. In the latter case we still return ", " as the most common
+ // separator in use. In countries that don't use this, e.g. Thailand,
+ // addresses are often written in Latin script where this would still be
+ // appropriate, so this is a reasonable default in the absence of information.
+ return kCommaSeparator;
+}
+
+void CombineLinesForLanguage(const std::vector<std::string>& lines,
+ const std::string& language_tag,
+ std::string* line) {
+ line->clear();
+ std::string separator = GetLineSeparatorForLanguage(language_tag);
+ for (std::vector<std::string>::const_iterator it = lines.begin();
+ it != lines.end();
+ ++it) {
+ if (it != lines.begin()) {
+ line->append(separator);
+ }
+ line->append(*it);
+ }
+}
+
+} // namespace
+
+void GetFormattedNationalAddress(
+ const AddressData& address_data, std::vector<std::string>* lines) {
+ assert(lines != NULL);
+ lines->clear();
+
+ Rule rule;
+ rule.CopyFrom(Rule::GetDefault());
+ // TODO: Eventually, we should get the best rule for this country and
+ // language, rather than just for the country.
+ rule.ParseSerializedRule(RegionDataConstants::GetRegionData(
+ address_data.region_code));
+
+ Language language(address_data.language_code);
+
+ // If Latin-script rules are available and the |language_code| of this address
+ // is explicitly tagged as being Latin, then use the Latin-script formatting
+ // rules.
+ const std::vector<FormatElement>& format =
+ language.has_latin_script && !rule.GetLatinFormat().empty()
+ ? rule.GetLatinFormat()
+ : rule.GetFormat();
+
+ // Address format without the unnecessary elements (based on which address
+ // fields are empty). We assume all literal strings that are not at the start
+ // or end of a line are separators, and therefore only relevant if the
+ // surrounding fields are filled in. This works with the data we have
+ // currently.
+ std::vector<FormatElement> pruned_format;
+ for (std::vector<FormatElement>::const_iterator
+ element_it = format.begin();
+ element_it != format.end();
+ ++element_it) {
+ // Always keep the newlines.
+ if (element_it->IsNewline() ||
+ // Always keep the non-empty address fields.
+ (element_it->IsField() &&
+ !address_data.IsFieldEmpty(element_it->GetField())) ||
+ // Only keep literals that satisfy these 2 conditions:
+ (!element_it->IsField() &&
+ // (1) Not preceding an empty field.
+ (element_it + 1 == format.end() ||
+ !(element_it + 1)->IsField() ||
+ !address_data.IsFieldEmpty((element_it + 1)->GetField())) &&
+ // (2) Not following a removed field.
+ (element_it == format.begin() ||
+ !(element_it - 1)->IsField() ||
+ (!pruned_format.empty() && pruned_format.back().IsField())))) {
+ pruned_format.push_back(*element_it);
+ }
+ }
+
+ std::string line;
+ for (std::vector<FormatElement>::const_iterator
+ element_it = pruned_format.begin();
+ element_it != pruned_format.end();
+ ++element_it) {
+ if (element_it->IsNewline()) {
+ if (!line.empty()) {
+ lines->push_back(line);
+ line.clear();
+ }
+ } else if (element_it->IsField()) {
+ AddressField field = element_it->GetField();
+ if (field == STREET_ADDRESS) {
+ // The field "street address" represents the street address lines of an
+ // address, so there can be multiple values.
+ if (!address_data.IsFieldEmpty(field)) {
+ line.append(address_data.address_line.front());
+ if (address_data.address_line.size() > 1U) {
+ lines->push_back(line);
+ line.clear();
+ lines->insert(lines->end(),
+ address_data.address_line.begin() + 1,
+ address_data.address_line.end());
+ }
+ }
+ } else {
+ line.append(address_data.GetFieldValue(field));
+ }
+ } else {
+ line.append(element_it->GetLiteral());
+ }
+ }
+ if (!line.empty()) {
+ lines->push_back(line);
+ }
+}
+
+void GetFormattedNationalAddressLine(
+ const AddressData& address_data, std::string* line) {
+ std::vector<std::string> address_lines;
+ GetFormattedNationalAddress(address_data, &address_lines);
+ CombineLinesForLanguage(address_lines, address_data.language_code, line);
+}
+
+void GetStreetAddressLinesAsSingleLine(
+ const AddressData& address_data, std::string* line) {
+ CombineLinesForLanguage(
+ address_data.address_line, address_data.language_code, line);
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/address_input_helper.cc b/chromium/third_party/libaddressinput/src/cpp/src/address_input_helper.cc
new file mode 100644
index 00000000000..82ea6fc2bbd
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/address_input_helper.cc
@@ -0,0 +1,186 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <libaddressinput/address_input_helper.h>
+
+#include <libaddressinput/address_data.h>
+#include <libaddressinput/address_field.h>
+#include <libaddressinput/address_metadata.h>
+#include <libaddressinput/preload_supplier.h>
+#include <libaddressinput/util/basictypes.h>
+
+#include <cassert>
+#include <cstddef>
+#include <string>
+#include <vector>
+
+#include <re2/re2.h>
+
+#include "language.h"
+#include "lookup_key.h"
+#include "region_data_constants.h"
+#include "rule.h"
+#include "util/re2ptr.h"
+
+namespace i18n {
+namespace addressinput {
+
+// Used for building a hierarchy of rules, each one connected to its parent.
+struct Node {
+ const Node* parent;
+ const Rule* rule;
+};
+
+namespace {
+
+const char kLookupKeySeparator = '/';
+
+const size_t kHierarchyDepth = arraysize(LookupKey::kHierarchy);
+
+// Gets the best name for the entity represented by the current rule, using the
+// language provided. The language is currently used to distinguish whether a
+// Latin-script name should be fetched; if it is not explicitly Latin-script, we
+// prefer IDs over names (so return CA instead of California for an English
+// user.) If there is no Latin-script name, we fall back to the ID.
+std::string GetBestName(const Language& language, const Rule& rule) {
+ if (language.has_latin_script) {
+ const std::string& name = rule.GetLatinName();
+ if (!name.empty()) {
+ return name;
+ }
+ }
+ // The ID is stored as data/US/CA for "CA", for example, and we only want the
+ // last part.
+ const std::string& id = rule.GetId();
+ std::string::size_type pos = id.rfind(kLookupKeySeparator);
+ assert(pos != std::string::npos);
+ return id.substr(pos + 1);
+}
+
+void FillAddressFromMatchedRules(
+ const std::vector<Node>* hierarchy,
+ AddressData* address) {
+ assert(hierarchy != NULL);
+ assert(address != NULL);
+ // We skip region code, because we never try and fill that in if it isn't
+ // already set.
+ Language language(address->language_code);
+ for (size_t depth = kHierarchyDepth - 1; depth > 0; --depth) {
+ // If there is only one match at this depth, then we should populate the
+ // address, using this rule and its parents.
+ if (hierarchy[depth].size() == 1) {
+ for (const Node* node = &hierarchy[depth].front();
+ node != NULL; node = node->parent, --depth) {
+ const Rule* rule = node->rule;
+ assert(rule != NULL);
+
+ AddressField field = LookupKey::kHierarchy[depth];
+ // Note only empty fields are permitted to be overwritten.
+ if (address->IsFieldEmpty(field)) {
+ address->SetFieldValue(field, GetBestName(language, *rule));
+ }
+ }
+ break;
+ }
+ }
+}
+
+} // namespace;
+
+AddressInputHelper::AddressInputHelper(PreloadSupplier* supplier)
+ : supplier_(supplier) {
+ assert(supplier_ != NULL);
+}
+
+AddressInputHelper::~AddressInputHelper() {
+}
+
+void AddressInputHelper::FillAddress(AddressData* address) const {
+ assert(address != NULL);
+ const std::string& region_code = address->region_code;
+ if (!RegionDataConstants::IsSupported(region_code)) {
+ // If we don't have a region code, we can't do anything reliably to fill
+ // this address.
+ return;
+ }
+
+ AddressData lookup_key_address;
+ lookup_key_address.region_code = region_code;
+ // First try and fill in the postal code if it is missing.
+ LookupKey lookup_key;
+ lookup_key.FromAddress(lookup_key_address);
+ const Rule* region_rule = supplier_->GetRule(lookup_key);
+ // We have already checked that the region is supported; and users of this
+ // method must have called LoadRules() first, so we check this here.
+ assert(region_rule != NULL);
+
+ const RE2ptr* postal_code_reg_exp = region_rule->GetPostalCodeMatcher();
+ if (postal_code_reg_exp != NULL) {
+ if (address->postal_code.empty()) {
+ address->postal_code = region_rule->GetSolePostalCode();
+ }
+
+ // If we have a valid postal code, try and work out the most specific
+ // hierarchy that matches the postal code. Note that the postal code might
+ // have been added in the previous check.
+ if (!address->postal_code.empty() &&
+ RE2::FullMatch(address->postal_code, *postal_code_reg_exp->ptr)) {
+
+ // This hierarchy is used to store rules that represent possible matches
+ // at each level of the hierarchy.
+ std::vector<Node> hierarchy[kHierarchyDepth];
+ CheckChildrenForPostCodeMatches(*address, lookup_key, NULL, hierarchy);
+
+ FillAddressFromMatchedRules(hierarchy, address);
+ }
+ }
+
+ // TODO: When we have the data, we should fill in the state for countries with
+ // state required and only one possible value, e.g. American Samoa.
+}
+
+void AddressInputHelper::CheckChildrenForPostCodeMatches(
+ const AddressData& address,
+ const LookupKey& lookup_key,
+ const Node* parent,
+ // An array of vectors.
+ std::vector<Node>* hierarchy) const {
+ const Rule* rule = supplier_->GetRule(lookup_key);
+ assert(rule != NULL);
+
+ const RE2ptr* postal_code_prefix = rule->GetPostalCodeMatcher();
+ if (postal_code_prefix == NULL ||
+ RE2::PartialMatch(address.postal_code, *postal_code_prefix->ptr)) {
+ // This was a match, so store it and its parent in the hierarchy.
+ hierarchy[lookup_key.GetDepth()].push_back(Node());
+ Node* node = &hierarchy[lookup_key.GetDepth()].back();
+ node->parent = parent;
+ node->rule = rule;
+
+ if (IsFieldUsed(LookupKey::kHierarchy[lookup_key.GetDepth() + 1],
+ address.region_code)) {
+ // If children are used and present, check them too.
+ for (std::vector<std::string>::const_iterator child_it =
+ rule->GetSubKeys().begin();
+ child_it != rule->GetSubKeys().end(); ++child_it) {
+ LookupKey child_key;
+ child_key.FromLookupKey(lookup_key, *child_it);
+ CheckChildrenForPostCodeMatches(address, child_key, node, hierarchy);
+ }
+ }
+ }
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/address_metadata.cc b/chromium/third_party/libaddressinput/src/cpp/src/address_metadata.cc
new file mode 100644
index 00000000000..c088e0d600d
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/address_metadata.cc
@@ -0,0 +1,64 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <libaddressinput/address_metadata.h>
+
+#include <libaddressinput/address_field.h>
+
+#include <algorithm>
+#include <string>
+
+#include "format_element.h"
+#include "region_data_constants.h"
+#include "rule.h"
+
+namespace i18n {
+namespace addressinput {
+
+bool IsFieldRequired(AddressField field, const std::string& region_code) {
+ if (field == COUNTRY) {
+ return true;
+ }
+
+ Rule rule;
+ rule.CopyFrom(Rule::GetDefault());
+ if (!rule.ParseSerializedRule(
+ RegionDataConstants::GetRegionData(region_code))) {
+ return false;
+ }
+
+ return std::find(rule.GetRequired().begin(),
+ rule.GetRequired().end(),
+ field) != rule.GetRequired().end();
+}
+
+bool IsFieldUsed(AddressField field, const std::string& region_code) {
+ if (field == COUNTRY) {
+ return true;
+ }
+
+ Rule rule;
+ rule.CopyFrom(Rule::GetDefault());
+ if (!rule.ParseSerializedRule(
+ RegionDataConstants::GetRegionData(region_code))) {
+ return false;
+ }
+
+ return std::find(rule.GetFormat().begin(),
+ rule.GetFormat().end(),
+ FormatElement(field)) != rule.GetFormat().end();
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/address_normalizer.cc b/chromium/third_party/libaddressinput/src/cpp/src/address_normalizer.cc
new file mode 100644
index 00000000000..562203e2ae5
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/address_normalizer.cc
@@ -0,0 +1,89 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <libaddressinput/address_normalizer.h>
+
+#include <libaddressinput/address_data.h>
+#include <libaddressinput/address_field.h>
+#include <libaddressinput/preload_supplier.h>
+
+#include <cassert>
+#include <cstddef>
+#include <string>
+#include <vector>
+
+#include "lookup_key.h"
+#include "rule.h"
+#include "util/string_compare.h"
+
+namespace i18n {
+namespace addressinput {
+
+AddressNormalizer::AddressNormalizer(const PreloadSupplier* supplier)
+ : supplier_(supplier),
+ compare_(new StringCompare) {
+ assert(supplier_ != NULL);
+}
+
+AddressNormalizer::~AddressNormalizer() {}
+
+void AddressNormalizer::Normalize(AddressData* address) const {
+ assert(address != NULL);
+ assert(supplier_->IsLoaded(address->region_code));
+
+ AddressData region_address;
+ region_address.region_code = address->region_code;
+ LookupKey parent_key;
+ parent_key.FromAddress(region_address);
+ const Rule* parent_rule = supplier_->GetRule(parent_key);
+ assert(parent_rule != NULL);
+
+ LookupKey lookup_key;
+ for (size_t depth = 1; depth < arraysize(LookupKey::kHierarchy); ++depth) {
+ AddressField field = LookupKey::kHierarchy[depth];
+ if (address->IsFieldEmpty(field)) {
+ return;
+ }
+ const std::string& field_value = address->GetFieldValue(field);
+ bool no_match_found_yet = true;
+ for (std::vector<std::string>::const_iterator
+ key_it = parent_rule->GetSubKeys().begin();
+ key_it != parent_rule->GetSubKeys().end(); ++key_it) {
+ lookup_key.FromLookupKey(parent_key, *key_it);
+ const Rule* rule = supplier_->GetRule(lookup_key);
+ assert(rule != NULL);
+
+ bool matches_latin_name =
+ compare_->NaturalEquals(field_value, rule->GetLatinName());
+ bool matches_local_name_id =
+ compare_->NaturalEquals(field_value, *key_it) ||
+ compare_->NaturalEquals(field_value, rule->GetName());
+ if (matches_latin_name || matches_local_name_id) {
+ address->SetFieldValue(
+ field, matches_latin_name ? rule->GetLatinName() : *key_it);
+ no_match_found_yet = false;
+ parent_key.FromLookupKey(parent_key, *key_it);
+ parent_rule = supplier_->GetRule(parent_key);
+ assert(parent_rule != NULL);
+ break;
+ }
+ }
+ if (no_match_found_yet) {
+ return; // Abort search.
+ }
+ }
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/address_problem.cc b/chromium/third_party/libaddressinput/src/cpp/src/address_problem.cc
new file mode 100644
index 00000000000..0fade172adb
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/address_problem.cc
@@ -0,0 +1,44 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <libaddressinput/address_problem.h>
+
+#include <libaddressinput/util/basictypes.h>
+
+#include <cstddef>
+#include <ostream>
+
+using i18n::addressinput::AddressProblem;
+using i18n::addressinput::UNEXPECTED_FIELD;
+using i18n::addressinput::USES_P_O_BOX;
+
+std::ostream& operator<<(std::ostream& o, AddressProblem problem) {
+ static const char* const kProblemNames[] = {
+ "UNEXPECTED_FIELD",
+ "MISSING_REQUIRED_FIELD",
+ "UNKNOWN_VALUE",
+ "INVALID_FORMAT",
+ "MISMATCHING_VALUE",
+ "USES_P_O_BOX"
+ };
+ COMPILE_ASSERT(UNEXPECTED_FIELD == 0, bad_base);
+ COMPILE_ASSERT(USES_P_O_BOX == arraysize(kProblemNames) - 1, bad_length);
+
+ if (problem < 0 || static_cast<size_t>(problem) >= arraysize(kProblemNames)) {
+ o << "[INVALID ENUM VALUE " << static_cast<int>(problem) << "]";
+ } else {
+ o << kProblemNames[problem];
+ }
+ return o;
+}
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/address_ui.cc b/chromium/third_party/libaddressinput/src/cpp/src/address_ui.cc
new file mode 100644
index 00000000000..5df8b3d0438
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/address_ui.cc
@@ -0,0 +1,147 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <libaddressinput/address_ui.h>
+
+#include <libaddressinput/address_field.h>
+#include <libaddressinput/address_ui_component.h>
+#include <libaddressinput/localization.h>
+
+#include <cassert>
+#include <cstddef>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "format_element.h"
+#include "grit.h"
+#include "language.h"
+#include "messages.h"
+#include "region_data_constants.h"
+#include "rule.h"
+
+namespace i18n {
+namespace addressinput {
+
+namespace {
+
+std::string GetLabelForField(const Localization& localization,
+ AddressField field,
+ int admin_area_name_message_id,
+ int postal_code_name_message_id,
+ int locality_name_message_id,
+ int sublocality_name_message_id) {
+ int messageId;
+ switch (field) {
+ case SORTING_CODE:
+ // This needs no translation as it's used only in one locale.
+ return "CEDEX";
+ case COUNTRY:
+ messageId = IDS_LIBADDRESSINPUT_COUNTRY_OR_REGION_LABEL;
+ break;
+ case ADMIN_AREA:
+ messageId = admin_area_name_message_id;
+ break;
+ case LOCALITY:
+ messageId = locality_name_message_id;
+ break;
+ case DEPENDENT_LOCALITY:
+ messageId = sublocality_name_message_id;
+ break;
+ case POSTAL_CODE:
+ messageId = postal_code_name_message_id;
+ break;
+ case STREET_ADDRESS:
+ messageId = IDS_LIBADDRESSINPUT_ADDRESS_LINE_1_LABEL;
+ break;
+ case ORGANIZATION:
+ messageId = IDS_LIBADDRESSINPUT_ORGANIZATION_LABEL;
+ break;
+ case RECIPIENT:
+ messageId = IDS_LIBADDRESSINPUT_RECIPIENT_LABEL;
+ break;
+ default:
+ messageId = INVALID_MESSAGE_ID;
+ }
+ return localization.GetString(messageId);
+}
+
+} // namespace
+
+const std::vector<std::string>& GetRegionCodes() {
+ return RegionDataConstants::GetRegionCodes();
+}
+
+std::vector<AddressUiComponent> BuildComponents(
+ const std::string& region_code,
+ const Localization& localization,
+ const std::string& ui_language_tag,
+ std::string* best_address_language_tag) {
+ assert(best_address_language_tag != NULL);
+ std::vector<AddressUiComponent> result;
+
+ Rule rule;
+ rule.CopyFrom(Rule::GetDefault());
+ if (!rule.ParseSerializedRule(
+ RegionDataConstants::GetRegionData(region_code))) {
+ return result;
+ }
+
+ const Language& best_address_language =
+ ChooseBestAddressLanguage(rule, Language(ui_language_tag));
+ *best_address_language_tag = best_address_language.tag;
+
+ const std::vector<FormatElement>& format =
+ !rule.GetLatinFormat().empty() && best_address_language.has_latin_script
+ ? rule.GetLatinFormat()
+ : rule.GetFormat();
+
+ // For avoiding showing an input field twice, when the field is displayed
+ // twice on an envelope.
+ std::set<AddressField> fields;
+
+ bool preceded_by_newline = true;
+ bool followed_by_newline = true;
+ for (std::vector<FormatElement>::const_iterator format_it = format.begin();
+ format_it != format.end(); ++format_it) {
+ if (format_it->IsNewline()) {
+ preceded_by_newline = true;
+ continue;
+ } else if (!format_it->IsField() ||
+ !fields.insert(format_it->GetField()).second) {
+ continue;
+ }
+ AddressUiComponent component;
+ std::vector<FormatElement>::const_iterator next_format_it = format_it + 1;
+ followed_by_newline =
+ next_format_it == format.end() || next_format_it->IsNewline();
+ component.length_hint = preceded_by_newline && followed_by_newline
+ ? AddressUiComponent::HINT_LONG
+ : AddressUiComponent::HINT_SHORT;
+ preceded_by_newline = false;
+ component.field = format_it->GetField();
+ component.name = GetLabelForField(localization,
+ format_it->GetField(),
+ rule.GetAdminAreaNameMessageId(),
+ rule.GetPostalCodeNameMessageId(),
+ rule.GetLocalityNameMessageId(),
+ rule.GetSublocalityNameMessageId());
+ result.push_back(component);
+ }
+
+ return result;
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/address_validator.cc b/chromium/third_party/libaddressinput/src/cpp/src/address_validator.cc
new file mode 100644
index 00000000000..8f0c33cf38f
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/address_validator.cc
@@ -0,0 +1,49 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <libaddressinput/address_validator.h>
+
+#include <cassert>
+#include <cstddef>
+
+#include "validation_task.h"
+
+namespace i18n {
+namespace addressinput {
+
+AddressValidator::AddressValidator(Supplier* supplier) : supplier_(supplier) {
+ assert(supplier_ != NULL);
+}
+
+AddressValidator::~AddressValidator() {
+}
+
+void AddressValidator::Validate(const AddressData& address,
+ bool allow_postal,
+ bool require_name,
+ const FieldProblemMap* filter,
+ FieldProblemMap* problems,
+ const Callback& validated) const {
+ // The ValidationTask object will delete itself after Run() has finished.
+ (new ValidationTask(
+ address,
+ allow_postal,
+ require_name,
+ filter,
+ problems,
+ validated))->Run(supplier_);
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/format_element.cc b/chromium/third_party/libaddressinput/src/cpp/src/format_element.cc
new file mode 100644
index 00000000000..8f9669c8845
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/format_element.cc
@@ -0,0 +1,52 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "format_element.h"
+
+#include <libaddressinput/address_field.h>
+
+#include <cassert>
+#include <ostream>
+
+namespace i18n {
+namespace addressinput {
+
+FormatElement::FormatElement(AddressField field) : field_(field), literal_() {}
+
+FormatElement::FormatElement(const std::string& literal)
+ : field_(static_cast<AddressField>(-1)), literal_(literal) {
+ assert(!literal.empty());
+}
+
+FormatElement::FormatElement()
+ : field_(static_cast<AddressField>(-1)), literal_("\n") {}
+
+bool FormatElement::operator==(const FormatElement& other) const {
+ return field_ == other.field_ && literal_ == other.literal_;
+}
+
+} // namespace addressinput
+} // namespace i18n
+
+std::ostream& operator<<(std::ostream& o,
+ const i18n::addressinput::FormatElement& element) {
+ if (element.IsField()) {
+ o << "Field: " << element.GetField();
+ } else if (element.IsNewline()) {
+ o << "Newline";
+ } else {
+ o << "Literal: " << element.GetLiteral();
+ }
+ return o;
+}
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/format_element.h b/chromium/third_party/libaddressinput/src/cpp/src/format_element.h
new file mode 100644
index 00000000000..e37a1883856
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/format_element.h
@@ -0,0 +1,70 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// An object representing a token in a formatting string. This may be a
+// placeholder for a field in the address data such as ADMIN_AREA, a literal
+// string such as " ", or a newline.
+
+#ifndef I18N_ADDRESSINPUT_FORMAT_ELEMENT_H_
+#define I18N_ADDRESSINPUT_FORMAT_ELEMENT_H_
+
+#include <libaddressinput/address_field.h>
+
+#include <iosfwd>
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+class FormatElement {
+ public:
+ // Builds a format element to represent |field|.
+ explicit FormatElement(AddressField field);
+
+ // Builds an element representing a literal string |literal|.
+ explicit FormatElement(const std::string& literal);
+
+ // Builds a newline element.
+ FormatElement();
+
+ // Returns true if this element represents a field element.
+ bool IsField() const { return literal_.empty(); }
+
+ // Returns true if this element represents a new line.
+ bool IsNewline() const { return literal_ == "\n"; }
+
+ AddressField GetField() const { return field_; }
+ const std::string& GetLiteral() const { return literal_; }
+
+ bool operator==(const FormatElement& other) const;
+
+ private:
+ // The field this element represents. Should only be used if |literal| is an
+ // empty string.
+ AddressField field_;
+
+ // The literal string for this element. This will be "\n" if this is a
+ // newline - but IsNewline() is preferred to determine this. If empty, then
+ // this FormatElement represents an address field.
+ std::string literal_;
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+// Produces human-readable output in logging, for example in unit tests.
+std::ostream& operator<<(std::ostream& o,
+ const i18n::addressinput::FormatElement& field);
+
+#endif // I18N_ADDRESSINPUT_FORMAT_ELEMENT_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/grit.h b/chromium/third_party/libaddressinput/src/cpp/src/grit.h
new file mode 100644
index 00000000000..c4c23f3116d
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/grit.h
@@ -0,0 +1,36 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_GRIT_H_
+#define I18N_ADDRESSINPUT_GRIT_H_
+
+namespace i18n {
+namespace addressinput {
+
+// A message identifier that is guaranteed to not clash with any
+// IDS_ADDRESSINPUT_I18N_* identifiers that are generated by GRIT. GRIT
+// generates messages in the range from decimal 101 to 0x7FFF in order to work
+// with Windows.
+// https://code.google.com/p/grit-i18n/source/browse/trunk/grit/format/rc_header.py?r=94#169
+// http://msdn.microsoft.com/en-us/library/t2zechd4(VS.71).aspx
+//
+// The enum must be named to enable using it in gtest templates, e.g.
+// EXPECT_EQ(INVALID_MESSAGE_ID, my_id) will not compile on some platforms when
+// the enum is unnamed.
+enum MessageIdentifier { INVALID_MESSAGE_ID = 0 };
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_GRIT_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/language.cc b/chromium/third_party/libaddressinput/src/cpp/src/language.cc
new file mode 100644
index 00000000000..28817dc12d2
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/language.cc
@@ -0,0 +1,102 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "language.h"
+
+#include <algorithm>
+#include <cctype>
+#include <string>
+#include <vector>
+
+#include "rule.h"
+#include "util/string_split.h"
+
+namespace i18n {
+namespace addressinput {
+
+Language::Language(const std::string& language_tag) : tag(language_tag),
+ base(),
+ has_latin_script(false) {
+ // Character '-' is the separator for subtags in the BCP 47. However, some
+ // legacy code generates tags with '_' instead of '-'.
+ static const char kSubtagsSeparator = '-';
+ static const char kAlternativeSubtagsSeparator = '_';
+ std::replace(
+ tag.begin(), tag.end(), kAlternativeSubtagsSeparator, kSubtagsSeparator);
+
+ // OK to use 'tolower' because BCP 47 tags are always in ASCII.
+ std::string lowercase = tag;
+ std::transform(
+ lowercase.begin(), lowercase.end(), lowercase.begin(), tolower);
+
+ base = lowercase.substr(0, lowercase.find(kSubtagsSeparator));
+
+ // The lowercase BCP 47 subtag for Latin script.
+ static const char kLowercaseLatinScript[] = "latn";
+ std::vector<std::string> subtags;
+ SplitString(lowercase, kSubtagsSeparator, &subtags);
+
+ // Support only the second and third position for the script.
+ has_latin_script =
+ (subtags.size() > 1 && subtags[1] == kLowercaseLatinScript) ||
+ (subtags.size() > 2 && subtags[2] == kLowercaseLatinScript);
+}
+
+Language::~Language() {}
+
+Language ChooseBestAddressLanguage(const Rule& address_region_rule,
+ const Language& ui_language) {
+ if (address_region_rule.GetLanguages().empty()) {
+ return ui_language;
+ }
+
+ std::vector<Language> available_languages;
+ for (std::vector<std::string>::const_iterator
+ language_tag_it = address_region_rule.GetLanguages().begin();
+ language_tag_it != address_region_rule.GetLanguages().end();
+ ++language_tag_it) {
+ available_languages.push_back(Language(*language_tag_it));
+ }
+
+ if (ui_language.tag.empty()) {
+ return available_languages.front();
+ }
+
+ bool has_latin_format = !address_region_rule.GetLatinFormat().empty();
+
+ // The conventionally formatted BCP 47 Latin script with a preceding subtag
+ // separator.
+ static const char kLatinScriptSuffix[] = "-Latn";
+ Language latin_script_language(
+ available_languages.front().base + kLatinScriptSuffix);
+ if (has_latin_format && ui_language.has_latin_script) {
+ return latin_script_language;
+ }
+
+ for (std::vector<Language>::const_iterator
+ available_lang_it = available_languages.begin();
+ available_lang_it != available_languages.end(); ++available_lang_it) {
+ // Base language comparison works because no region supports the same base
+ // language with different scripts, for now. For example, no region supports
+ // "zh-Hant" and "zh-Hans" at the same time.
+ if (ui_language.base == available_lang_it->base) {
+ return *available_lang_it;
+ }
+ }
+
+ return has_latin_format ? latin_script_language : available_languages.front();
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/language.h b/chromium/third_party/libaddressinput/src/cpp/src/language.h
new file mode 100644
index 00000000000..561c79fa690
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/language.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_LANGUAGE_H_
+#define I18N_ADDRESSINPUT_LANGUAGE_H_
+
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+class Rule;
+
+// Helper for working with a BCP 47 language tag.
+// http://tools.ietf.org/html/bcp47
+struct Language {
+ explicit Language(const std::string& language_tag);
+ ~Language();
+
+ // The language tag (with '_' replaced with '-'), for example "zh-Latn-CN".
+ std::string tag;
+
+ // The base language, for example "zh". Always lowercase.
+ std::string base;
+
+ // True if the language tag explicitly has a Latin script. For example, this
+ // is true for "zh-Latn", but false for "zh". Only the second and third subtag
+ // positions are supported for script.
+ bool has_latin_script;
+};
+
+Language ChooseBestAddressLanguage(const Rule& address_region_rule,
+ const Language& ui_language);
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_LANGUAGE_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/localization.cc b/chromium/third_party/libaddressinput/src/cpp/src/localization.cc
new file mode 100644
index 00000000000..b544cd9f067
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/localization.cc
@@ -0,0 +1,190 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <libaddressinput/localization.h>
+
+#include <libaddressinput/address_data.h>
+#include <libaddressinput/address_field.h>
+#include <libaddressinput/address_problem.h>
+
+#include <cassert>
+#include <cstddef>
+#include <string>
+#include <vector>
+
+#include "messages.h"
+#include "region_data_constants.h"
+#include "rule.h"
+#include "util/string_split.h"
+#include "util/string_util.h"
+
+namespace {
+
+void PushBackUrl(const std::string& url, std::vector<std::string>* parameters) {
+ assert(parameters != NULL);
+ // TODO: HTML-escape the "url".
+ parameters->push_back("<a href=\"" + url + "\">");
+ parameters->push_back("</a>");
+}
+
+} // namespace
+
+namespace i18n {
+namespace addressinput {
+
+namespace {
+
+#include "en_messages.cc"
+
+std::string GetEnglishString(int message_id) {
+ const char* str = GetString(message_id);
+ return str != NULL ? std::string(str) : std::string();
+}
+
+} // namespace
+
+Localization::Localization() : get_string_(&GetEnglishString) {}
+
+Localization::~Localization() {}
+
+std::string Localization::GetString(int message_id) const {
+ return get_string_(message_id);
+}
+
+std::string Localization::GetErrorMessage(const AddressData& address,
+ AddressField field,
+ AddressProblem problem,
+ bool enable_examples,
+ bool enable_links) const {
+ if (field == POSTAL_CODE) {
+ Rule rule;
+ rule.CopyFrom(Rule::GetDefault());
+ std::string postal_code_example, post_service_url;
+ if (rule.ParseSerializedRule(
+ RegionDataConstants::GetRegionData(address.region_code))) {
+ if (enable_examples) {
+ std::vector<std::string> examples_list;
+ SplitString(rule.GetPostalCodeExample(), ',', &examples_list);
+ if (!examples_list.empty()) {
+ postal_code_example = examples_list.front();
+ }
+ }
+ if (enable_links) {
+ post_service_url = rule.GetPostServiceUrl();
+ }
+ } else {
+ assert(false);
+ }
+ // If we can't parse the serialized rule |uses_postal_code_as_label| will be
+ // determined from the default rule.
+ bool uses_postal_code_as_label =
+ rule.GetPostalCodeNameMessageId() ==
+ IDS_LIBADDRESSINPUT_POSTAL_CODE_LABEL;
+ return GetErrorMessageForPostalCode(address, problem,
+ uses_postal_code_as_label,
+ postal_code_example, post_service_url);
+ } else {
+ if (problem == MISSING_REQUIRED_FIELD) {
+ return get_string_(IDS_LIBADDRESSINPUT_MISSING_REQUIRED_FIELD);
+ } else if (problem == UNKNOWN_VALUE) {
+ std::vector<std::string> parameters;
+ if (AddressData::IsRepeatedFieldValue(field)) {
+ std::vector<std::string> values = address.GetRepeatedFieldValue(field);
+ assert(!values.empty());
+ parameters.push_back(values.front());
+ } else {
+ parameters.push_back(address.GetFieldValue(field));
+ }
+ return DoReplaceStringPlaceholders(
+ get_string_(IDS_LIBADDRESSINPUT_UNKNOWN_VALUE), parameters);
+ } else if (problem == USES_P_O_BOX) {
+ return get_string_(IDS_LIBADDRESSINPUT_PO_BOX_FORBIDDEN_VALUE);
+ } else {
+ // Keep the default under "else" so the compiler helps us check that all
+ // handled cases return and don't fall through.
+ assert(false);
+ return "";
+ }
+ }
+}
+
+void Localization::SetGetter(std::string (*getter)(int)) {
+ assert(getter != NULL);
+ get_string_ = getter;
+}
+
+std::string Localization::GetErrorMessageForPostalCode(
+ const AddressData& address,
+ AddressProblem problem,
+ bool uses_postal_code_as_label,
+ std::string postal_code_example,
+ std::string post_service_url) const {
+ int message_id;
+ std::vector<std::string> parameters;
+ if (problem == MISSING_REQUIRED_FIELD) {
+ if (!postal_code_example.empty() && !post_service_url.empty()) {
+ message_id = uses_postal_code_as_label ?
+ IDS_LIBADDRESSINPUT_MISSING_REQUIRED_POSTAL_CODE_EXAMPLE_AND_URL :
+ IDS_LIBADDRESSINPUT_MISSING_REQUIRED_ZIP_CODE_EXAMPLE_AND_URL;
+ parameters.push_back(postal_code_example);
+ PushBackUrl(post_service_url, &parameters);
+ } else if (!postal_code_example.empty()) {
+ message_id = uses_postal_code_as_label ?
+ IDS_LIBADDRESSINPUT_MISSING_REQUIRED_POSTAL_CODE_EXAMPLE :
+ IDS_LIBADDRESSINPUT_MISSING_REQUIRED_ZIP_CODE_EXAMPLE ;
+ parameters.push_back(postal_code_example);
+ } else {
+ message_id = IDS_LIBADDRESSINPUT_MISSING_REQUIRED_FIELD;
+ }
+ return DoReplaceStringPlaceholders(get_string_(message_id), parameters);
+ } else if (problem == INVALID_FORMAT) {
+ if (!postal_code_example.empty() && !post_service_url.empty()) {
+ message_id = uses_postal_code_as_label ?
+ IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_POSTAL_CODE_EXAMPLE_AND_URL :
+ IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_ZIP_CODE_EXAMPLE_AND_URL;
+ parameters.push_back(postal_code_example);
+ PushBackUrl(post_service_url, &parameters);
+ } else if (!postal_code_example.empty()) {
+ message_id = uses_postal_code_as_label ?
+ IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_POSTAL_CODE_EXAMPLE :
+ IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_ZIP_CODE_EXAMPLE;
+ parameters.push_back(postal_code_example);
+ } else {
+ message_id = uses_postal_code_as_label ?
+ IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_POSTAL_CODE :
+ IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_ZIP;
+ }
+ return DoReplaceStringPlaceholders(get_string_(message_id), parameters);
+ } else if (problem == MISMATCHING_VALUE) {
+ if (!post_service_url.empty()) {
+ message_id = uses_postal_code_as_label ?
+ IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_POSTAL_CODE_URL :
+ IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_ZIP_URL;
+ PushBackUrl(post_service_url, &parameters);
+ } else {
+ message_id = uses_postal_code_as_label ?
+ IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_POSTAL_CODE :
+ IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_ZIP;
+ }
+ return DoReplaceStringPlaceholders(get_string_(message_id), parameters);
+ } else {
+ // Keep the default under "else" so the compiler helps us check that all
+ // handled cases return and don't fall through.
+ assert(false);
+ return "";
+ }
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/lookup_key.cc b/chromium/third_party/libaddressinput/src/cpp/src/lookup_key.cc
new file mode 100644
index 00000000000..d793a2beae5
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/lookup_key.cc
@@ -0,0 +1,153 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "lookup_key.h"
+
+#include <libaddressinput/address_data.h>
+#include <libaddressinput/address_field.h>
+#include <libaddressinput/util/basictypes.h>
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <functional>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "language.h"
+#include "region_data_constants.h"
+#include "rule.h"
+#include "util/cctype_tolower_equal.h"
+
+namespace i18n {
+namespace addressinput {
+
+namespace {
+
+const char kSlashDelim[] = "/";
+const char kDashDelim[] = "--";
+const char kData[] = "data";
+const char kUnknown[] = "ZZ";
+
+// Assume the language_tag has had "Latn" script removed when this is called.
+bool ShouldSetLanguageForKey(const std::string& language_tag,
+ const std::string& region_code) {
+ // We only need a language in the key if there is subregion data at all.
+ if (RegionDataConstants::GetMaxLookupKeyDepth(region_code) == 0) {
+ return false;
+ }
+ Rule rule;
+ rule.CopyFrom(Rule::GetDefault());
+ // TODO: Pre-parse the rules and have a map from region code to rule.
+ if (!rule.ParseSerializedRule(
+ RegionDataConstants::GetRegionData(region_code))) {
+ return false;
+ }
+ const std::vector<std::string>& languages = rule.GetLanguages();
+ // Do not add the default language (we want "data/US", not "data/US--en").
+ // (empty should not happen here because we have some sub-region data).
+ if (languages.empty() || languages[0] == language_tag) {
+ return false;
+ }
+ // Finally, only return true if the language is one of the remaining ones.
+ return std::find_if(languages.begin() + 1, languages.end(),
+ std::bind2nd(EqualToTolowerString(), language_tag)) !=
+ languages.end();
+}
+
+} // namespace
+
+const AddressField LookupKey::kHierarchy[] = {
+ COUNTRY,
+ ADMIN_AREA,
+ LOCALITY,
+ DEPENDENT_LOCALITY
+};
+
+LookupKey::LookupKey() {
+}
+
+LookupKey::~LookupKey() {
+}
+
+void LookupKey::FromAddress(const AddressData& address) {
+ nodes_.clear();
+ if (address.region_code.empty()) {
+ nodes_.insert(std::make_pair(COUNTRY, kUnknown));
+ } else {
+ for (size_t i = 0; i < arraysize(kHierarchy); ++i) {
+ AddressField field = kHierarchy[i];
+ const std::string& value = address.GetFieldValue(field);
+ if (value.empty()) {
+ break;
+ }
+ nodes_.insert(std::make_pair(field, value));
+ }
+ }
+ Language address_language(address.language_code);
+ std::string language_tag_no_latn = address_language.has_latin_script
+ ? address_language.base
+ : address_language.tag;
+ if (ShouldSetLanguageForKey(language_tag_no_latn, address.region_code)) {
+ language_ = language_tag_no_latn;
+ }
+}
+
+void LookupKey::FromLookupKey(const LookupKey& parent,
+ const std::string& child_node) {
+ assert(parent.nodes_.size() < arraysize(kHierarchy));
+ assert(!child_node.empty());
+
+ // Copy its nodes if this isn't the parent object.
+ if (this != &parent) nodes_ = parent.nodes_;
+ AddressField child_field = kHierarchy[nodes_.size()];
+ nodes_.insert(std::make_pair(child_field, child_node));
+}
+
+std::string LookupKey::ToKeyString(size_t max_depth) const {
+ assert(max_depth < arraysize(kHierarchy));
+ std::string key_string(kData);
+
+ for (size_t i = 0; i <= max_depth; ++i) {
+ AddressField field = kHierarchy[i];
+ std::map<AddressField, std::string>::const_iterator it = nodes_.find(field);
+ if (it == nodes_.end()) {
+ break;
+ }
+ key_string.append(kSlashDelim);
+ key_string.append(it->second);
+ }
+ if (!language_.empty()) {
+ key_string.append(kDashDelim);
+ key_string.append(language_);
+ }
+ return key_string;
+}
+
+const std::string& LookupKey::GetRegionCode() const {
+ std::map<AddressField, std::string>::const_iterator it = nodes_.find(COUNTRY);
+ assert(it != nodes_.end());
+ return it->second;
+}
+
+size_t LookupKey::GetDepth() const {
+ size_t depth = nodes_.size() - 1;
+ assert(depth < arraysize(kHierarchy));
+ return depth;
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/lookup_key.h b/chromium/third_party/libaddressinput/src/cpp/src/lookup_key.h
new file mode 100644
index 00000000000..0b74cd3d938
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/lookup_key.h
@@ -0,0 +1,75 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_LOOKUP_KEY_H_
+#define I18N_ADDRESSINPUT_LOOKUP_KEY_H_
+
+#include <libaddressinput/address_field.h>
+#include <libaddressinput/util/basictypes.h>
+
+#include <cstddef>
+#include <map>
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+struct AddressData;
+
+// A LookupKey maps between an AddressData struct and the key string used to
+// request address data from an address data server.
+class LookupKey {
+ public:
+ // The array length is explicitly specified here, to make it possible to get
+ // the length through arraysize(LookupKey::kHierarchy).
+ static const AddressField kHierarchy[4];
+
+ LookupKey();
+ ~LookupKey();
+
+ // Initializes this object by parsing |address|.
+ void FromAddress(const AddressData& address);
+
+ // Initializes this object to be a copy of |parent| key that's one level
+ // deeper with the next level node being |child_node|.
+ //
+ // For example, if |parent| is "data/US" and |child_node| is "CA", then this
+ // key becomes "data/US/CA".
+ //
+ // The |parent| can be at most LOCALITY level. The |child_node| cannot be
+ // empty.
+ void FromLookupKey(const LookupKey& parent, const std::string& child_node);
+
+ // Returns the lookup key string (of |max_depth|).
+ std::string ToKeyString(size_t max_depth) const;
+
+ // Returns the region code. Must not be called on an empty object.
+ const std::string& GetRegionCode() const;
+
+ // Returns the depth. Must not be called on an empty object.
+ size_t GetDepth() const;
+
+ private:
+ std::map<AddressField, std::string> nodes_;
+ // The language of the key, obtained from the address (empty for default
+ // language).
+ std::string language_;
+
+ DISALLOW_COPY_AND_ASSIGN(LookupKey);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_LOOKUP_KEY_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/null_storage.cc b/chromium/third_party/libaddressinput/src/cpp/src/null_storage.cc
new file mode 100644
index 00000000000..7786a0dcd4f
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/null_storage.cc
@@ -0,0 +1,41 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <libaddressinput/null_storage.h>
+
+#include <cassert>
+#include <cstddef>
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+NullStorage::NullStorage() {
+}
+
+NullStorage::~NullStorage() {
+}
+
+void NullStorage::Put(const std::string& key, std::string* data) {
+ assert(data != NULL); // Sanity check.
+ delete data;
+}
+
+void NullStorage::Get(const std::string& key,
+ const Callback& data_ready) const {
+ data_ready(false, key, NULL);
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/ondemand_supplier.cc b/chromium/third_party/libaddressinput/src/cpp/src/ondemand_supplier.cc
new file mode 100644
index 00000000000..fa3f88e8701
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/ondemand_supplier.cc
@@ -0,0 +1,68 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <libaddressinput/ondemand_supplier.h>
+
+#include <algorithm>
+#include <cstddef>
+#include <map>
+#include <string>
+
+#include "lookup_key.h"
+#include "ondemand_supply_task.h"
+#include "region_data_constants.h"
+#include "retriever.h"
+#include "rule.h"
+
+namespace i18n {
+namespace addressinput {
+
+OndemandSupplier::OndemandSupplier(const Source* source, Storage* storage)
+ : retriever_(new Retriever(source, storage)) {
+}
+
+OndemandSupplier::~OndemandSupplier() {
+ for (std::map<std::string, const Rule*>::const_iterator
+ it = rule_cache_.begin(); it != rule_cache_.end(); ++it) {
+ delete it->second;
+ }
+}
+
+void OndemandSupplier::Supply(const LookupKey& lookup_key,
+ const Callback& supplied) {
+ OndemandSupplyTask* task =
+ new OndemandSupplyTask(lookup_key, &rule_cache_, supplied);
+
+ if (RegionDataConstants::IsSupported(lookup_key.GetRegionCode())) {
+ size_t max_depth = std::min(
+ lookup_key.GetDepth(),
+ RegionDataConstants::GetMaxLookupKeyDepth(lookup_key.GetRegionCode()));
+
+ for (size_t depth = 0; depth <= max_depth; ++depth) {
+ const std::string& key = lookup_key.ToKeyString(depth);
+ std::map<std::string, const Rule*>::const_iterator it =
+ rule_cache_.find(key);
+ if (it != rule_cache_.end()) {
+ task->hierarchy_.rule[depth] = it->second;
+ } else {
+ task->Queue(key); // If not in the cache, it needs to be loaded.
+ }
+ }
+ }
+
+ task->Retrieve(*retriever_);
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/ondemand_supply_task.cc b/chromium/third_party/libaddressinput/src/cpp/src/ondemand_supply_task.cc
new file mode 100644
index 00000000000..a4f6a12049a
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/ondemand_supply_task.cc
@@ -0,0 +1,139 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ondemand_supply_task.h"
+
+#include <libaddressinput/address_field.h>
+#include <libaddressinput/callback.h>
+#include <libaddressinput/supplier.h>
+#include <libaddressinput/util/basictypes.h>
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "lookup_key.h"
+#include "retriever.h"
+#include "rule.h"
+
+namespace i18n {
+namespace addressinput {
+
+OndemandSupplyTask::OndemandSupplyTask(
+ const LookupKey& lookup_key,
+ std::map<std::string, const Rule*>* rules,
+ const Supplier::Callback& supplied)
+ : hierarchy_(),
+ pending_(),
+ lookup_key_(lookup_key),
+ rule_cache_(rules),
+ supplied_(supplied),
+ retrieved_(BuildCallback(this, &OndemandSupplyTask::Load)),
+ success_(true) {
+ assert(rule_cache_ != NULL);
+ assert(retrieved_ != NULL);
+}
+
+OndemandSupplyTask::~OndemandSupplyTask() {
+}
+
+void OndemandSupplyTask::Queue(const std::string& key) {
+ assert(pending_.find(key) == pending_.end());
+ pending_.insert(key);
+}
+
+void OndemandSupplyTask::Retrieve(const Retriever& retriever) {
+ if (pending_.empty()) {
+ Loaded();
+ } else {
+ // When the final pending rule has been retrieved, the retrieved_ callback,
+ // implemented by Load(), will finish by calling Loaded(), which will finish
+ // by delete'ing this OndemandSupplyTask object. So after the final call to
+ // retriever.Retrieve() no attributes of this object can be accessed (as the
+ // object then no longer will exist, if the final callback has finished by
+ // then), and the condition statement of the loop must therefore not use the
+ // otherwise obvious it != pending_.end() but instead test a local variable
+ // that isn't affected by the object being deleted.
+ bool done = false;
+ for (std::set<std::string>::const_iterator it = pending_.begin(); !done; ) {
+ const std::string& key = *it++;
+ done = it == pending_.end();
+ retriever.Retrieve(key, *retrieved_);
+ }
+ }
+}
+
+void OndemandSupplyTask::Load(bool success,
+ const std::string& key,
+ const std::string& data) {
+ size_t depth = std::count(key.begin(), key.end(), '/') - 1;
+ assert(depth < arraysize(LookupKey::kHierarchy));
+
+ // Sanity check: This key should be present in the set of pending requests.
+ size_t status = pending_.erase(key);
+ assert(status == 1); // There will always be one item erased from the set.
+ (void)status; // Prevent unused variable if assert() is optimized away.
+
+ if (success) {
+ // The address metadata server will return the empty JSON "{}" when it
+ // successfully performed a lookup, but didn't find any data for that key.
+ if (data != "{}") {
+ Rule* rule = new Rule;
+ if (LookupKey::kHierarchy[depth] == COUNTRY) {
+ // All rules on the COUNTRY level inherit from the default rule.
+ rule->CopyFrom(Rule::GetDefault());
+ }
+ if (rule->ParseSerializedRule(data)) {
+ // Try inserting the Rule object into the rule_cache_ map, or else find
+ // the already existing Rule object with the same ID already in the map.
+ // It is possible that a key was queued even though the corresponding
+ // Rule object is already in the cache, as the data server is free to do
+ // advanced normalization and aliasing so that the ID of the data
+ // returned is different from the key requested. (It would be possible
+ // to cache such aliases, to increase performance in the case where a
+ // certain alias is requested repeatedly, but such a cache would then
+ // have to be kept to some limited size to not grow indefinitely with
+ // every possible permutation of a name recognized by the data server.)
+ std::pair<std::map<std::string, const Rule*>::iterator, bool> result =
+ rule_cache_->insert(std::make_pair(rule->GetId(), rule));
+ if (!result.second) { // There was already an entry with this ID.
+ delete rule;
+ }
+ // Pointer to object in the map.
+ hierarchy_.rule[depth] = result.first->second;
+ } else {
+ delete rule;
+ success_ = false;
+ }
+ }
+ } else {
+ success_ = false;
+ }
+
+ if (pending_.empty()) {
+ Loaded();
+ }
+}
+
+void OndemandSupplyTask::Loaded() {
+ supplied_(success_, lookup_key_, hierarchy_);
+ delete this;
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/ondemand_supply_task.h b/chromium/third_party/libaddressinput/src/cpp/src/ondemand_supply_task.h
new file mode 100644
index 00000000000..45b33c90508
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/ondemand_supply_task.h
@@ -0,0 +1,71 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_ONDEMAND_SUPPLY_TASK_H_
+#define I18N_ADDRESSINPUT_ONDEMAND_SUPPLY_TASK_H_
+
+#include <libaddressinput/supplier.h>
+#include <libaddressinput/util/basictypes.h>
+#include <libaddressinput/util/scoped_ptr.h>
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "retriever.h"
+
+namespace i18n {
+namespace addressinput {
+
+class LookupKey;
+class Rule;
+
+// An OndemandSupplyTask object encapsulates the information necessary to
+// retrieve the set of Rule objects corresponding to a LookupKey and call a
+// callback when that has been done. Calling the Retrieve() method will load
+// required metadata, then call the callback and delete the OndemandSupplyTask
+// object itself.
+class OndemandSupplyTask {
+ public:
+ OndemandSupplyTask(const LookupKey& lookup_key,
+ std::map<std::string, const Rule*>* rules,
+ const Supplier::Callback& supplied);
+ ~OndemandSupplyTask();
+
+ // Adds lookup key string |key| to the queue of data to be retrieved.
+ void Queue(const std::string& key);
+
+ // Retrieves and parses data for all queued keys, then calls |supplied_|.
+ void Retrieve(const Retriever& retriever);
+
+ Supplier::RuleHierarchy hierarchy_;
+
+ private:
+ void Load(bool success, const std::string& key, const std::string& data);
+ void Loaded();
+
+ std::set<std::string> pending_;
+ const LookupKey& lookup_key_;
+ std::map<std::string, const Rule*>* const rule_cache_;
+ const Supplier::Callback& supplied_;
+ const scoped_ptr<const Retriever::Callback> retrieved_;
+ bool success_;
+
+ DISALLOW_COPY_AND_ASSIGN(OndemandSupplyTask);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_ONDEMAND_SUPPLY_TASK_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/post_box_matchers.cc b/chromium/third_party/libaddressinput/src/cpp/src/post_box_matchers.cc
new file mode 100644
index 00000000000..f1ad6dd6e95
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/post_box_matchers.cc
@@ -0,0 +1,132 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "post_box_matchers.h"
+
+#include <cstddef>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <re2/re2.h>
+
+#include "language.h"
+#include "rule.h"
+#include "util/re2ptr.h"
+
+namespace i18n {
+namespace addressinput {
+
+namespace {
+
+std::map<std::string, const RE2ptr*> InitMatchers() {
+ static const struct {
+ const char* const language;
+ const RE2ptr ptr;
+ } kMatchers[] = {
+ { "ar",
+ /* "صندوق بريد|ص[-. ]ب" */
+ new RE2("\xD8\xB5\xD9\x86\xD8\xAF\xD9\x88\xD9\x82 "
+ "\xD8\xA8\xD8\xB1\xD9\x8A\xD8\xAF|\xD8\xB5[-. ]\xD8\xA8") },
+
+ { "cs", new RE2("(?i)p\\.? ?p\\.? \\d") },
+ { "da", new RE2("(?i)Postboks") },
+ { "de", new RE2("(?i)Postfach") },
+
+ { "el",
+ /* "T\\.? ?Θ\\.? \\d{2}" */
+ new RE2("(?i)T\\.? ?\xCE\x98\\.? \\d{2}") },
+
+ { "en", new RE2("Private Bag|Post(?:al)? Box") },
+ { "es", new RE2("(?i)(?:Apartado|Casillas) de correos?") },
+ { "fi", new RE2("(?i)Postilokero|P\\.?L\\.? \\d") },
+ { "hr", new RE2("(?i)p\\.? ?p\\.? \\d") },
+
+ { "hu",
+ /* "Postafi(?:[oó]|ó)k|Pf\\.? \\d" */
+ new RE2("(?i)Postafi(?:[o\xC3\xB3]|o\xCC\x81)k|Pf\\.? \\d") },
+
+ { "fr",
+ /* "Bo(?:[iî]|î)te Postale|BP \\d|CEDEX \\d" */
+ new RE2("(?i)Bo(?:[i\xC3\xAE]|i\xCC\x82)te Postale|BP \\d|CEDEX \\d") },
+
+ { "ja",
+ /* "私書箱\\d{1,5}号" */
+ new RE2("(?i)\xE7\xA7\x81\xE6\x9B\xB8\xE7\xAE\xB1\\d{1,5}\xE5\x8F\xB7") },
+
+ { "nl", new RE2("(?i)Postbus") },
+ { "no", new RE2("(?i)Postboks") },
+ { "pl", new RE2("(?i)Skr(?:\\.?|ytka) poczt(?:\\.?|owa)") },
+ { "pt", new RE2("(?i)Apartado") },
+
+ { "ru",
+ /* "абонентский ящик|[аa]\\\" */
+ new RE2("(?i)\xD0\xB0\xD0\xB1\xD0\xBE\xD0\xBD\xD0\xB5\xD0\xBD\xD1\x82\xD1"
+ "\x81\xD0\xBA\xD0\xB8\xD0\xB9 \xD1\x8F\xD1\x89\xD0\xB8\xD0\xBA|"
+ "[\xD0\xB0""a]\\\"\xD1\x8F (?:(?:\xE2\x84\x96|#|N) ?)?\\d") },
+
+ { "sv", new RE2("(?i)Box \\d") },
+
+ { "zh",
+ /* "郵政信箱.{1,5}號|郵局第.{1,10}號信箱" */
+ new RE2("(?i)\xE9\x83\xB5\xE6\x94\xBF\xE4\xBF\xA1\xE7\xAE\xB1.{1,5}"
+ "\xE8\x99\x9F|\xE9\x83\xB5\xE5\xB1\x80\xE7\xAC\xAC.{1,10}"
+ "\xE8\x99\x9F\xE4\xBF\xA1\xE7\xAE\xB1") },
+
+ { "und", new RE2("P\\.? ?O\\.? Box") }
+ };
+
+ std::map<std::string, const RE2ptr*> matchers;
+
+ for (size_t i = 0; i < sizeof kMatchers / sizeof *kMatchers; ++i) {
+ matchers.insert(std::make_pair(kMatchers[i].language, &kMatchers[i].ptr));
+ }
+
+ return matchers;
+}
+
+} // namespace
+
+// static
+std::vector<const RE2ptr*> PostBoxMatchers::GetMatchers(
+ const Rule& country_rule) {
+ static const std::map<std::string, const RE2ptr*> kMatchers(InitMatchers());
+
+ // Always add any expressions defined for "und" (English-like defaults).
+ std::vector<std::string> languages(1, "und");
+ for (std::vector<std::string>::const_iterator
+ it = country_rule.GetLanguages().begin();
+ it != country_rule.GetLanguages().end(); ++it) {
+ Language language(*it);
+ languages.push_back(language.base);
+ }
+
+ std::vector<const RE2ptr*> result;
+
+ for (std::vector<std::string>::const_iterator
+ it = languages.begin();
+ it != languages.end(); ++it) {
+ std::map<std::string, const RE2ptr*>::const_iterator
+ jt = kMatchers.find(*it);
+ if (jt != kMatchers.end()) {
+ result.push_back(jt->second);
+ }
+ }
+
+ return result;
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/post_box_matchers.h b/chromium/third_party/libaddressinput/src/cpp/src/post_box_matchers.h
new file mode 100644
index 00000000000..9924a2c5133
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/post_box_matchers.h
@@ -0,0 +1,43 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// Post office box regular expressions.
+
+#ifndef I18N_ADDRESSINPUT_POST_BOX_MATCHERS_H_
+#define I18N_ADDRESSINPUT_POST_BOX_MATCHERS_H_
+
+#include <libaddressinput/util/basictypes.h>
+
+#include <vector>
+
+namespace i18n {
+namespace addressinput {
+
+class Rule;
+struct RE2ptr;
+
+class PostBoxMatchers {
+ public:
+ // Returns pointers to RE2 regular expression objects to test address lines
+ // for those languages that are relevant for |country_rule|.
+ static std::vector<const RE2ptr*> GetMatchers(const Rule& country_rule);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PostBoxMatchers);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_POST_BOX_MATCHERS_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/preload_supplier.cc b/chromium/third_party/libaddressinput/src/cpp/src/preload_supplier.cc
new file mode 100644
index 00000000000..79119bdbea1
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/preload_supplier.cc
@@ -0,0 +1,373 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <libaddressinput/preload_supplier.h>
+
+#include <libaddressinput/address_data.h>
+#include <libaddressinput/address_field.h>
+#include <libaddressinput/callback.h>
+#include <libaddressinput/supplier.h>
+#include <libaddressinput/util/basictypes.h>
+#include <libaddressinput/util/scoped_ptr.h>
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <functional>
+#include <map>
+#include <set>
+#include <stack>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "lookup_key.h"
+#include "region_data_constants.h"
+#include "retriever.h"
+#include "rule.h"
+#include "util/json.h"
+#include "util/string_compare.h"
+
+namespace i18n {
+namespace addressinput {
+
+namespace {
+
+// STL predicate less<> that uses StringCompare to match strings that a human
+// reader would consider to be "the same". The default implementation just does
+// case insensitive string comparison, but StringCompare can be overriden with
+// more sophisticated implementations.
+class IndexLess : public std::binary_function<std::string, std::string, bool> {
+ public:
+ result_type operator()(const first_argument_type& a,
+ const second_argument_type& b) const {
+ static const StringCompare kStringCompare;
+ return kStringCompare.NaturalLess(a, b);
+ }
+};
+
+} // namespace
+
+class IndexMap : public std::map<std::string, const Rule*, IndexLess> {};
+
+namespace {
+
+class Helper {
+ public:
+ // Does not take ownership of its parameters.
+ Helper(const std::string& region_code,
+ const std::string& key,
+ const PreloadSupplier::Callback& loaded,
+ const Retriever& retriever,
+ std::set<std::string>* pending,
+ IndexMap* rule_index,
+ std::vector<const Rule*>* rule_storage,
+ std::map<std::string, const Rule*>* region_rules)
+ : region_code_(region_code),
+ loaded_(loaded),
+ pending_(pending),
+ rule_index_(rule_index),
+ rule_storage_(rule_storage),
+ region_rules_(region_rules),
+ retrieved_(BuildCallback(this, &Helper::OnRetrieved)) {
+ assert(pending_ != NULL);
+ assert(rule_index_ != NULL);
+ assert(rule_storage_ != NULL);
+ assert(region_rules_ != NULL);
+ assert(retrieved_ != NULL);
+ pending_->insert(key);
+ retriever.Retrieve(key, *retrieved_);
+ }
+
+ private:
+ ~Helper() {}
+
+ void OnRetrieved(bool success,
+ const std::string& key,
+ const std::string& data) {
+ int rule_count = 0;
+
+ size_t status = pending_->erase(key);
+ assert(status == 1); // There will always be one item erased from the set.
+ (void)status; // Prevent unused variable if assert() is optimized away.
+
+ Json json;
+ std::string id;
+ std::vector<const Rule*> sub_rules;
+
+ IndexMap::iterator last_index_it = rule_index_->end();
+ IndexMap::iterator last_latin_it = rule_index_->end();
+ std::map<std::string, const Rule*>::iterator last_region_it =
+ region_rules_->end();
+
+ IndexMap::const_iterator hints[arraysize(LookupKey::kHierarchy) - 1];
+ std::fill(hints, hints + arraysize(hints), rule_index_->end());
+
+ if (!success) {
+ goto callback;
+ }
+
+ if (!json.ParseObject(data)) {
+ success = false;
+ goto callback;
+ }
+
+ for (std::vector<const Json*>::const_iterator
+ it = json.GetSubDictionaries().begin();
+ it != json.GetSubDictionaries().end();
+ ++it) {
+ const Json* value = *it;
+ assert(value != NULL);
+ if (!value->GetStringValueForKey("id", &id)) {
+ success = false;
+ goto callback;
+ }
+ assert(!id.empty());
+
+ size_t depth = std::count(id.begin(), id.end(), '/') - 1;
+ assert(depth < arraysize(LookupKey::kHierarchy));
+ AddressField field = LookupKey::kHierarchy[depth];
+
+ Rule* rule = new Rule;
+ if (field == COUNTRY) {
+ // All rules on the COUNTRY level inherit from the default rule.
+ rule->CopyFrom(Rule::GetDefault());
+ }
+ rule->ParseJsonRule(*value);
+ assert(id == rule->GetId()); // Sanity check.
+
+ rule_storage_->push_back(rule);
+ if (depth > 0) {
+ sub_rules.push_back(rule);
+ }
+
+ // Add the ID of this Rule object to the rule index with natural string
+ // comparison for keys.
+ last_index_it =
+ rule_index_->insert(last_index_it, std::make_pair(id, rule));
+
+ // Add the ID of this Rule object to the region-specific rule index with
+ // exact string comparison for keys.
+ last_region_it =
+ region_rules_->insert(last_region_it, std::make_pair(id, rule));
+
+ ++rule_count;
+ }
+
+ /*
+ * Normally the address metadata server takes care of mapping from natural
+ * language names to metadata IDs (eg. "São Paulo" -> "SP") and from Latin
+ * script names to local script names (eg. "Tokushima" -> "徳島県").
+ *
+ * As the PreloadSupplier doesn't contact the metadata server upon each
+ * Supply() request, it instead has an internal lookup table (rule_index_)
+ * that contains such mappings.
+ *
+ * This lookup table is populated by iterating over all sub rules and for
+ * each of them construct ID strings using human readable names (eg. "São
+ * Paulo") and using Latin script names (eg. "Tokushima").
+ */
+ for (std::vector<const Rule*>::const_iterator
+ it = sub_rules.begin(); it != sub_rules.end(); ++it) {
+ std::stack<const Rule*> hierarchy;
+ hierarchy.push(*it);
+
+ // Push pointers to all parent Rule objects onto the hierarchy stack.
+ for (std::string parent_id((*it)->GetId());;) {
+ // Strip the last part of parent_id. Break if COUNTRY level is reached.
+ std::string::size_type pos = parent_id.rfind('/');
+ if (pos == sizeof "data/ZZ" - 1) {
+ break;
+ }
+ parent_id.resize(pos);
+
+ IndexMap::const_iterator* const hint = &hints[hierarchy.size() - 1];
+ if (*hint == rule_index_->end() || (*hint)->first != parent_id) {
+ *hint = rule_index_->find(parent_id);
+ }
+ assert(*hint != rule_index_->end());
+ hierarchy.push((*hint)->second);
+ }
+
+ std::string human_id((*it)->GetId().substr(0, sizeof "data/ZZ" - 1));
+ std::string latin_id(human_id);
+
+ // Append the names from all Rule objects on the hierarchy stack.
+ for (; !hierarchy.empty(); hierarchy.pop()) {
+ const Rule* rule = hierarchy.top();
+
+ human_id.push_back('/');
+ if (!rule->GetName().empty()) {
+ human_id.append(rule->GetName());
+ } else {
+ // If the "name" field is empty, the name is the last part of the ID.
+ const std::string& id = rule->GetId();
+ std::string::size_type pos = id.rfind('/');
+ assert(pos != std::string::npos);
+ human_id.append(id.substr(pos + 1));
+ }
+
+ if (!rule->GetLatinName().empty()) {
+ latin_id.push_back('/');
+ latin_id.append(rule->GetLatinName());
+ }
+ }
+
+ // If the ID has a language tag, copy it.
+ {
+ const std::string& id = (*it)->GetId();
+ std::string::size_type pos = id.rfind("--");
+ if (pos != std::string::npos) {
+ human_id.append(id, pos, id.size() - pos);
+ }
+ }
+
+ last_index_it =
+ rule_index_->insert(last_index_it, std::make_pair(human_id, *it));
+
+ // Add the Latin script ID, if a Latin script name could be found for
+ // every part of the ID.
+ if (std::count(human_id.begin(), human_id.end(), '/') ==
+ std::count(latin_id.begin(), latin_id.end(), '/')) {
+ last_latin_it =
+ rule_index_->insert(last_latin_it, std::make_pair(latin_id, *it));
+ }
+ }
+
+ callback:
+ loaded_(success, region_code_, rule_count);
+ delete this;
+ }
+
+ const std::string region_code_;
+ const PreloadSupplier::Callback& loaded_;
+ std::set<std::string>* const pending_;
+ IndexMap* const rule_index_;
+ std::vector<const Rule*>* const rule_storage_;
+ std::map<std::string, const Rule*>* const region_rules_;
+ const scoped_ptr<const Retriever::Callback> retrieved_;
+
+ DISALLOW_COPY_AND_ASSIGN(Helper);
+};
+
+std::string KeyFromRegionCode(const std::string& region_code) {
+ AddressData address;
+ address.region_code = region_code;
+ LookupKey lookup_key;
+ lookup_key.FromAddress(address);
+ return lookup_key.ToKeyString(0); // Zero depth = COUNTRY level.
+}
+
+} // namespace
+
+PreloadSupplier::PreloadSupplier(const Source* source, Storage* storage)
+ : retriever_(new Retriever(source, storage)),
+ pending_(),
+ rule_index_(new IndexMap),
+ rule_storage_(),
+ region_rules_() {}
+
+PreloadSupplier::~PreloadSupplier() {
+ for (std::vector<const Rule*>::const_iterator
+ it = rule_storage_.begin(); it != rule_storage_.end(); ++it) {
+ delete *it;
+ }
+}
+
+void PreloadSupplier::Supply(const LookupKey& lookup_key,
+ const Supplier::Callback& supplied) {
+ Supplier::RuleHierarchy hierarchy;
+ bool success = GetRuleHierarchy(lookup_key, &hierarchy);
+ supplied(success, lookup_key, hierarchy);
+}
+
+const Rule* PreloadSupplier::GetRule(const LookupKey& lookup_key) const {
+ assert(IsLoaded(lookup_key.GetRegionCode()));
+ Supplier::RuleHierarchy hierarchy;
+ if (!GetRuleHierarchy(lookup_key, &hierarchy)) {
+ return NULL;
+ }
+ return hierarchy.rule[lookup_key.GetDepth()];
+}
+
+void PreloadSupplier::LoadRules(const std::string& region_code,
+ const Callback& loaded) {
+ const std::string& key = KeyFromRegionCode(region_code);
+
+ if (IsLoadedKey(key)) {
+ loaded(true, region_code, 0);
+ return;
+ }
+
+ if (IsPendingKey(key)) {
+ return;
+ }
+
+ new Helper(
+ region_code,
+ key,
+ loaded,
+ *retriever_,
+ &pending_,
+ rule_index_.get(),
+ &rule_storage_,
+ &region_rules_[region_code]);
+}
+
+const std::map<std::string, const Rule*>& PreloadSupplier::GetRulesForRegion(
+ const std::string& region_code) const {
+ assert(IsLoaded(region_code));
+ return region_rules_.find(region_code)->second;
+}
+
+bool PreloadSupplier::IsLoaded(const std::string& region_code) const {
+ return IsLoadedKey(KeyFromRegionCode(region_code));
+}
+
+bool PreloadSupplier::IsPending(const std::string& region_code) const {
+ return IsPendingKey(KeyFromRegionCode(region_code));
+}
+
+bool PreloadSupplier::GetRuleHierarchy(const LookupKey& lookup_key,
+ RuleHierarchy* hierarchy) const {
+ assert(hierarchy != NULL);
+
+ if (RegionDataConstants::IsSupported(lookup_key.GetRegionCode())) {
+ size_t max_depth = std::min(
+ lookup_key.GetDepth(),
+ RegionDataConstants::GetMaxLookupKeyDepth(lookup_key.GetRegionCode()));
+
+ for (size_t depth = 0; depth <= max_depth; ++depth) {
+ const std::string& key = lookup_key.ToKeyString(depth);
+ IndexMap::const_iterator it = rule_index_->find(key);
+ if (it == rule_index_->end()) {
+ return depth > 0; // No data on COUNTRY level is failure.
+ }
+ hierarchy->rule[depth] = it->second;
+ }
+ }
+
+ return true;
+}
+
+bool PreloadSupplier::IsLoadedKey(const std::string& key) const {
+ return rule_index_->find(key) != rule_index_->end();
+}
+
+bool PreloadSupplier::IsPendingKey(const std::string& key) const {
+ return pending_.find(key) != pending_.end();
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/region_data.cc b/chromium/third_party/libaddressinput/src/cpp/src/region_data.cc
new file mode 100644
index 00000000000..798501edcd9
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/region_data.cc
@@ -0,0 +1,50 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <libaddressinput/region_data.h>
+
+#include <cstddef>
+#include <string>
+#include <vector>
+
+namespace i18n {
+namespace addressinput {
+
+RegionData::RegionData(const std::string& region_code)
+ : key_(region_code),
+ name_(region_code),
+ parent_(NULL),
+ sub_regions_() {}
+
+RegionData::~RegionData() {
+ for (std::vector<const RegionData*>::const_iterator it = sub_regions_.begin();
+ it != sub_regions_.end(); ++it) {
+ delete *it;
+ }
+}
+
+RegionData* RegionData::AddSubRegion(const std::string& key,
+ const std::string& name) {
+ RegionData* sub_region = new RegionData(key, name, this);
+ sub_regions_.push_back(sub_region);
+ return sub_region;
+}
+
+RegionData::RegionData(const std::string& key,
+ const std::string& name,
+ RegionData* parent)
+ : key_(key), name_(name), parent_(parent), sub_regions_() {}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/region_data_builder.cc b/chromium/third_party/libaddressinput/src/cpp/src/region_data_builder.cc
new file mode 100644
index 00000000000..b600d09ab06
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/region_data_builder.cc
@@ -0,0 +1,189 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <libaddressinput/region_data_builder.h>
+
+#include <libaddressinput/address_data.h>
+#include <libaddressinput/preload_supplier.h>
+#include <libaddressinput/region_data.h>
+#include <libaddressinput/util/basictypes.h>
+
+#include <cassert>
+#include <cstddef>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "language.h"
+#include "lookup_key.h"
+#include "region_data_constants.h"
+#include "rule.h"
+
+namespace i18n {
+namespace addressinput {
+
+namespace {
+
+// The maximum depth of lookup keys.
+static const size_t kLookupKeysMaxDepth = arraysize(LookupKey::kHierarchy) - 1;
+
+// Does not take ownership of |parent_region|, which is not allowed to be NULL.
+void BuildRegionTreeRecursively(
+ const std::map<std::string, const Rule*>& rules,
+ std::map<std::string, const Rule*>::const_iterator hint,
+ const LookupKey& parent_key,
+ RegionData* parent_region,
+ const std::vector<std::string>& keys,
+ bool prefer_latin_name,
+ size_t region_max_depth) {
+ assert(parent_region != NULL);
+
+ LookupKey lookup_key;
+ for (std::vector<std::string>::const_iterator key_it = keys.begin();
+ key_it != keys.end(); ++key_it) {
+ lookup_key.FromLookupKey(parent_key, *key_it);
+ const std::string& lookup_key_string =
+ lookup_key.ToKeyString(kLookupKeysMaxDepth);
+
+ ++hint;
+ if (hint == rules.end() || hint->first != lookup_key_string) {
+ hint = rules.find(lookup_key_string);
+ if (hint == rules.end()) {
+ return;
+ }
+ }
+
+ const Rule* rule = hint->second;
+ assert(rule != NULL);
+
+ const std::string& local_name = rule->GetName().empty()
+ ? *key_it : rule->GetName();
+ const std::string& name =
+ prefer_latin_name && !rule->GetLatinName().empty()
+ ? rule->GetLatinName() : local_name;
+ RegionData* region = parent_region->AddSubRegion(*key_it, name);
+
+ if (!rule->GetSubKeys().empty() &&
+ region_max_depth > parent_key.GetDepth()) {
+ BuildRegionTreeRecursively(rules,
+ hint,
+ lookup_key,
+ region,
+ rule->GetSubKeys(),
+ prefer_latin_name,
+ region_max_depth);
+ }
+ }
+}
+
+// The caller owns the result.
+RegionData* BuildRegion(const std::map<std::string, const Rule*>& rules,
+ const std::string& region_code,
+ const Language& language) {
+ AddressData address;
+ address.region_code = region_code;
+
+ LookupKey lookup_key;
+ lookup_key.FromAddress(address);
+
+ std::map<std::string, const Rule*>::const_iterator hint =
+ rules.find(lookup_key.ToKeyString(kLookupKeysMaxDepth));
+ assert(hint != rules.end());
+
+ const Rule* rule = hint->second;
+ assert(rule != NULL);
+
+ RegionData* region = new RegionData(region_code);
+
+ // If there're sub-keys for field X, but field X is not used in this region
+ // code, then these sub-keys are skipped over. For example, CH has sub-keys
+ // for field ADMIN_AREA, but CH does not use ADMIN_AREA field.
+ size_t region_max_depth =
+ RegionDataConstants::GetMaxLookupKeyDepth(region_code);
+ if (region_max_depth > 0) {
+ BuildRegionTreeRecursively(rules,
+ hint,
+ lookup_key,
+ region,
+ rule->GetSubKeys(),
+ language.has_latin_script,
+ region_max_depth);
+ }
+
+ return region;
+}
+
+} // namespace
+
+RegionDataBuilder::RegionDataBuilder(PreloadSupplier* supplier)
+ : supplier_(supplier),
+ cache_() {
+ assert(supplier_ != NULL);
+}
+
+RegionDataBuilder::~RegionDataBuilder() {
+ for (RegionCodeDataMap::const_iterator region_it = cache_.begin();
+ region_it != cache_.end(); ++region_it) {
+ for (LanguageRegionMap::const_iterator
+ language_it = region_it->second->begin();
+ language_it != region_it->second->end(); ++language_it) {
+ delete language_it->second;
+ }
+ delete region_it->second;
+ }
+}
+
+const RegionData& RegionDataBuilder::Build(
+ const std::string& region_code,
+ const std::string& ui_language_tag,
+ std::string* best_region_tree_language_tag) {
+ assert(supplier_->IsLoaded(region_code));
+ assert(best_region_tree_language_tag != NULL);
+
+ // Look up the region tree in cache first before building it.
+ RegionCodeDataMap::const_iterator region_it = cache_.find(region_code);
+ if (region_it == cache_.end()) {
+ region_it =
+ cache_.insert(std::make_pair(region_code, new LanguageRegionMap)).first;
+ }
+
+ // No need to copy from default rule first, because only languages and Latin
+ // format are going to be used, which do not exist in the default rule.
+ Rule rule;
+ rule.ParseSerializedRule(RegionDataConstants::GetRegionData(region_code));
+ static const Language kUndefinedLanguage("und");
+ const Language& best_language =
+ rule.GetLanguages().empty()
+ ? kUndefinedLanguage
+ : ChooseBestAddressLanguage(rule, Language(ui_language_tag));
+ *best_region_tree_language_tag = best_language.tag;
+
+ LanguageRegionMap::const_iterator language_it =
+ region_it->second->find(best_language.tag);
+ if (language_it == region_it->second->end()) {
+ const std::map<std::string, const Rule*>& rules =
+ supplier_->GetRulesForRegion(region_code);
+ language_it =
+ region_it->second->insert(std::make_pair(best_language.tag,
+ BuildRegion(rules,
+ region_code,
+ best_language)))
+ .first;
+ }
+
+ return *language_it->second;
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/region_data_constants.cc b/chromium/third_party/libaddressinput/src/cpp/src/region_data_constants.cc
new file mode 100644
index 00000000000..a44a310eb3b
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/region_data_constants.cc
@@ -0,0 +1,1508 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// The data in this file is automatically generated.
+
+#include "region_data_constants.h"
+
+#include <libaddressinput/address_field.h>
+#include <libaddressinput/util/basictypes.h>
+
+#include <algorithm>
+#include <cstddef>
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "address_field_util.h"
+#include "format_element.h"
+#include "lookup_key.h"
+
+namespace i18n {
+namespace addressinput {
+
+// ---- BEGIN AUTOGENERATED CODE ----
+namespace {
+
+std::map<std::string, std::string> InitRegionData() {
+ std::map<std::string, std::string> region_data;
+ region_data.insert(std::make_pair("AC", "{"
+ "\"zipex\":\"ASCN 1ZZ\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("AD", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"AD100,AD501,AD700\","
+ "\"posturl\":\"http://www.correos.es/comun/CodigosPostales/1010_s-CodPostal.asp\?Provincia=\","
+ "\"languages\":\"ca\""
+ "}"));
+ region_data.insert(std::make_pair("AE", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%S\","
+ "\"lfmt\":\"%N%n%O%n%A%n%S\","
+ "\"require\":\"AS\","
+ "\"state_name_type\":\"emirate\","
+ "\"languages\":\"ar\""
+ "}"));
+ region_data.insert(std::make_pair("AF", "{"
+ "\"zipex\":\"1001,2601,3801\","
+ "\"posturl\":\"http://afghanpost.gov.af/Postal%20Code/\","
+ "\"languages\":\"fa~ps\""
+ "}"));
+ region_data.insert(std::make_pair("AG", "{"
+ "\"require\":\"A\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("AI", "{"
+ "\"zipex\":\"2640\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("AL", "{"
+ "\"zipex\":\"1001,1017,3501\","
+ "\"languages\":\"sq\""
+ "}"));
+ region_data.insert(std::make_pair("AM", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z%n%C%n%S\","
+ "\"lfmt\":\"%N%n%O%n%A%n%Z%n%C%n%S\","
+ "\"zipex\":\"375010,0002,0010\","
+ "\"languages\":\"hy\""
+ "}"));
+ region_data.insert(std::make_pair("AO", "{"
+ "\"languages\":\"pt\""
+ "}"));
+ region_data.insert(std::make_pair("AQ", "{"
+ "\"languages\":\"\""
+ "}"));
+ region_data.insert(std::make_pair("AR", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C%n%S\","
+ "\"zipex\":\"C1070AAM,C1000WAM,B1000TBU,X5187XAB\","
+ "\"posturl\":\"http://www.correoargentino.com.ar/formularios/cpa\","
+ "\"languages\":\"es\""
+ "}"));
+ region_data.insert(std::make_pair("AS", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %S %Z\","
+ "\"require\":\"ACSZ\","
+ "\"zip_name_type\":\"zip\","
+ "\"state_name_type\":\"state\","
+ "\"zipex\":\"96799\","
+ "\"posturl\":\"http://zip4.usps.com/zip4/welcome.jsp\","
+ "\"languages\":\"sm~en\""
+ "}"));
+ region_data.insert(std::make_pair("AT", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%Z %C\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"1010,3741\","
+ "\"posturl\":\"http://www.post.at/post_subsite_postleitzahlfinder.php\","
+ "\"languages\":\"de\""
+ "}"));
+ region_data.insert(std::make_pair("AU", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%C %S %Z\","
+ "\"require\":\"ACSZ\","
+ "\"state_name_type\":\"state\","
+ "\"zipex\":\"2060,3171,6430,4000,4006,3001\","
+ "\"posturl\":\"http://www1.auspost.com.au/postcodes/\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("AW", "{"
+ "\"languages\":\"nl~pap\""
+ "}"));
+ region_data.insert(std::make_pair("AX", "{"
+ "\"fmt\":\"%O%n%N%n%A%nAX-%Z %C%n\\u00c5LAND\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"22150,22550,22240,22710,22270,22730,22430\","
+ "\"posturl\":\"http://www.posten.ax/department.con\?iPage=123\","
+ "\"languages\":\"sv\""
+ "}"));
+ region_data.insert(std::make_pair("AZ", "{"
+ "\"fmt\":\"%N%n%O%n%A%nAZ %Z %C\","
+ "\"zipex\":\"1000\","
+ "\"languages\":\"az-Latn~az-Cyrl\""
+ "}"));
+ region_data.insert(std::make_pair("BA", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"71000\","
+ "\"posturl\":\"http://www.post.ba/postanski_brojevi.php\","
+ "\"languages\":\"bs-Cyrl~bs-Latn~hr~sr-Cyrl~sr-Latn\""
+ "}"));
+ region_data.insert(std::make_pair("BB", "{"
+ "\"state_name_type\":\"parish\","
+ "\"zipex\":\"BB23026,BB22025\","
+ "\"posturl\":\"http://barbadospostal.com/zipcodes.html\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("BD", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C - %Z\","
+ "\"zipex\":\"1340,1000\","
+ "\"posturl\":\"http://www.bangladeshpost.gov.bd/PostCode.asp\","
+ "\"languages\":\"bn\""
+ "}"));
+ region_data.insert(std::make_pair("BE", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%Z %C\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"4000,1000\","
+ "\"posturl\":\"http://www.post.be/site/nl/residential/customerservice/search/postal_codes.html\","
+ "\"languages\":\"nl~fr~de\""
+ "}"));
+ region_data.insert(std::make_pair("BF", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %X\","
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("BG", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"1000,1700\","
+ "\"posturl\":\"http://www.bgpost.bg/\?cid=5\","
+ "\"languages\":\"bg\""
+ "}"));
+ region_data.insert(std::make_pair("BH", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %Z\","
+ "\"zipex\":\"317\","
+ "\"languages\":\"ar\""
+ "}"));
+ region_data.insert(std::make_pair("BI", "{"
+ "\"languages\":\"rn~fr\""
+ "}"));
+ region_data.insert(std::make_pair("BJ", "{"
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("BL", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%Z %C %X\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"97100\","
+ "\"posturl\":\"http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal\","
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("BM", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %Z\","
+ "\"zipex\":\"FL 07,HM GX,HM 12\","
+ "\"posturl\":\"http://www.landvaluation.bm/\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("BN", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %Z\","
+ "\"zipex\":\"BT2328,KA1131,BA1511\","
+ "\"posturl\":\"http://www.post.gov.bn/index.php/extensions/postcode-guide\","
+ "\"languages\":\"ms-Latn~ms-Arab\""
+ "}"));
+ region_data.insert(std::make_pair("BO", "{"
+ "\"languages\":\"es~qu~ay\""
+ "}"));
+ region_data.insert(std::make_pair("BQ", "{"
+ "\"languages\":\"nl\""
+ "}"));
+ region_data.insert(std::make_pair("BR", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%D%n%C-%S%n%Z\","
+ "\"require\":\"ASCZ\","
+ "\"state_name_type\":\"state\","
+ "\"sublocality_name_type\":\"neighborhood\","
+ "\"zipex\":\"40301-110,70002-900\","
+ "\"posturl\":\"http://www.buscacep.correios.com.br/\","
+ "\"languages\":\"pt\""
+ "}"));
+ region_data.insert(std::make_pair("BS", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C, %S\","
+ "\"state_name_type\":\"island\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("BT", "{"
+ "\"zipex\":\"11001,31101,35003\","
+ "\"posturl\":\"http://www.bhutanpost.com.bt/postcode/postcode.php\","
+ "\"languages\":\"dz\""
+ "}"));
+ region_data.insert(std::make_pair("BV", "{"
+ "\"languages\":\"\""
+ "}"));
+ region_data.insert(std::make_pair("BW", "{"
+ "\"languages\":\"en~tn\""
+ "}"));
+ region_data.insert(std::make_pair("BY", "{"
+ "\"fmt\":\"%S%n%Z %C %X%n%A%n%O%n%N\","
+ "\"zipex\":\"223016,225860,220050\","
+ "\"posturl\":\"http://zip.belpost.by\","
+ "\"languages\":\"be~ru\""
+ "}"));
+ region_data.insert(std::make_pair("BZ", "{"
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("CA", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %S %Z\","
+ "\"require\":\"ACSZ\","
+ "\"zipex\":\"H3Z 2Y7,V8X 3X4,T0L 1K0,T0H 1A0,K1A 0B1\","
+ "\"posturl\":\"http://www.canadapost.ca/cpotools/apps/fpc/personal/findByCity\?execution=e2s1\","
+ "\"languages\":\"en~fr\""
+ "}"));
+ region_data.insert(std::make_pair("CC", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%C %S %Z\","
+ "\"zipex\":\"6799\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("CD", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %X\","
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("CF", "{"
+ "\"languages\":\"fr~sg\""
+ "}"));
+ region_data.insert(std::make_pair("CG", "{"
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("CH", "{"
+ "\"fmt\":\"%O%n%N%n%A%nCH-%Z %C\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"2544,1211,1556,3030\","
+ "\"posturl\":\"http://www.post.ch/db/owa/pv_plz_pack/pr_main\","
+ "\"languages\":\"de~fr~it\""
+ "}"));
+ region_data.insert(std::make_pair("CI", "{"
+ "\"fmt\":\"%N%n%O%n%X %A %C %X\","
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("CK", "{"
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("CL", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C%n%S\","
+ "\"zipex\":\"8340457,8720019,1230000,8329100\","
+ "\"posturl\":\"http://www.correos.cl/SitePages/home.aspx\","
+ "\"languages\":\"es\""
+ "}"));
+ region_data.insert(std::make_pair("CM", "{"
+ "\"languages\":\"fr~en\""
+ "}"));
+ region_data.insert(std::make_pair("CN", "{"
+ "\"fmt\":\"%Z%n%S%C%D%n%A%n%O%n%N\","
+ "\"lfmt\":\"%N%n%O%n%A%n%D%n%C%n%S, %Z\","
+ "\"require\":\"ACSZ\","
+ "\"sublocality_name_type\":\"district\","
+ "\"zipex\":\"266033,317204,100096,100808\","
+ "\"posturl\":\"http://www.cpdc.com.cn/postcdQueryAction.do\?reqCode=gotoQueryPostAddr\","
+ "\"languages\":\"zh\""
+ "}"));
+ region_data.insert(std::make_pair("CO", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C, %S, %Z\","
+ "\"zipex\":\"111221,130001,760011\","
+ "\"posturl\":\"http://www.codigopostal.gov.co/\","
+ "\"state_name_type\":\"department\","
+ "\"languages\":\"es\""
+ "}"));
+ region_data.insert(std::make_pair("CR", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"1000,2010,1001\","
+ "\"posturl\":\"https://www.correos.go.cr/nosotros/codigopostal/busqueda.html\","
+ "\"languages\":\"es\""
+ "}"));
+ region_data.insert(std::make_pair("CV", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C%n%S\","
+ "\"state_name_type\":\"island\","
+ "\"zipex\":\"7600\","
+ "\"languages\":\"pt\""
+ "}"));
+ region_data.insert(std::make_pair("CW", "{"
+ "\"languages\":\"pap~nl\""
+ "}"));
+ region_data.insert(std::make_pair("CX", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%C %S %Z\","
+ "\"zipex\":\"6798\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("CY", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"2008,3304,1900\","
+ "\"languages\":\"el~tr\""
+ "}"));
+ region_data.insert(std::make_pair("CZ", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"100 00,251 66,530 87,110 00,225 99\","
+ "\"posturl\":\"http://psc.ceskaposta.cz/CleanForm.action\","
+ "\"languages\":\"cs\""
+ "}"));
+ region_data.insert(std::make_pair("DE", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"26133,53225\","
+ "\"posturl\":\"http://www.postdirekt.de/plzserver/\","
+ "\"languages\":\"de\""
+ "}"));
+ region_data.insert(std::make_pair("DJ", "{"
+ "\"languages\":\"ar~fr\""
+ "}"));
+ region_data.insert(std::make_pair("DK", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"8660,1566\","
+ "\"posturl\":\"http://www.postdanmark.dk/da/Privat/Kundeservice/postnummerkort/Sider/Find-postnummer.aspx\","
+ "\"languages\":\"da\""
+ "}"));
+ region_data.insert(std::make_pair("DM", "{"
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("DO", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"11903,10101\","
+ "\"posturl\":\"http://inposdom.gob.do/servicios/codigo-postal.html#buscar_codigo\","
+ "\"languages\":\"es\""
+ "}"));
+ region_data.insert(std::make_pair("DZ", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"40304,16027\","
+ "\"languages\":\"ar~fr\""
+ "}"));
+ region_data.insert(std::make_pair("EC", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z%n%C\","
+ "\"zipex\":\"090105,EC090112,H0103C,P0133B,P0133A,P0133V\","
+ "\"languages\":\"es~qu\""
+ "}"));
+ region_data.insert(std::make_pair("EE", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"69501,11212\","
+ "\"posturl\":\"http://www.post.ee/\?op=sihtnumbriotsing\","
+ "\"languages\":\"et\""
+ "}"));
+ region_data.insert(std::make_pair("EG", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C%n%S%n%Z\","
+ "\"lfmt\":\"%N%n%O%n%A%n%C%n%S%n%Z\","
+ "\"zipex\":\"12411,11599\","
+ "\"languages\":\"ar\""
+ "}"));
+ region_data.insert(std::make_pair("EH", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"70000,72000\","
+ "\"languages\":\"ar\""
+ "}"));
+ region_data.insert(std::make_pair("ER", "{"
+ "\"languages\":\"ti~en~ar\""
+ "}"));
+ region_data.insert(std::make_pair("ES", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C %S\","
+ "\"require\":\"ACSZ\","
+ "\"zipex\":\"28039,28300,28070\","
+ "\"posturl\":\"http://www.correos.es/contenido/13-MenuRec2/04-MenuRec24/1010_s-CodPostal.asp\","
+ "\"languages\":\"es\""
+ "}"));
+ region_data.insert(std::make_pair("ET", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"1000\","
+ "\"languages\":\"am\""
+ "}"));
+ region_data.insert(std::make_pair("FI", "{"
+ "\"fmt\":\"%O%n%N%n%A%nFI-%Z %C\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"00550,00011\","
+ "\"posturl\":\"http://www.verkkoposti.com/e3/postinumeroluettelo\","
+ "\"languages\":\"fi~sv\""
+ "}"));
+ region_data.insert(std::make_pair("FJ", "{"
+ "\"languages\":\"en~hif-Deva~fj\""
+ "}"));
+ region_data.insert(std::make_pair("FK", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%X%n%C%n%Z\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"FIQQ 1ZZ\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("FM", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %S %Z\","
+ "\"require\":\"ACSZ\","
+ "\"zip_name_type\":\"zip\","
+ "\"state_name_type\":\"state\","
+ "\"zipex\":\"96941,96944\","
+ "\"posturl\":\"http://zip4.usps.com/zip4/welcome.jsp\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("FO", "{"
+ "\"fmt\":\"%N%n%O%n%A%nFO%Z %C\","
+ "\"zipex\":\"100\","
+ "\"posturl\":\"http://www.postur.fo/\","
+ "\"languages\":\"fo\""
+ "}"));
+ region_data.insert(std::make_pair("FR", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%Z %C %X\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"33380,34092,33506\","
+ "\"posturl\":\"http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal\","
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("GA", "{"
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("GB", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C%n%S%n%Z\","
+ "\"require\":\"ACZ\","
+ "\"state_name_type\":\"county\","
+ "\"locality_name_type\":\"post_town\","
+ "\"zipex\":\"EC1Y 8SY,GIR 0AA,M2 5BQ,M34 4AB,CR0 2YR,DN16 9AA,W1A 4ZZ,EC1A 1HQ,OX14 4PG,BS18 8HF,NR25 7HG,RH6 0NP,BH23 6AA,B6 5BA,SO23 9AP,PO1 3AX,BFPO 61\","
+ "\"posturl\":\"http://www.royalmail.com/postcode-finder\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("GD", "{"
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("GE", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"0101\","
+ "\"posturl\":\"http://www.georgianpost.ge/index.php\?page=10\","
+ "\"languages\":\"ka\""
+ "}"));
+ region_data.insert(std::make_pair("GF", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%Z %C %X\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"97300\","
+ "\"posturl\":\"http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal\","
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("GG", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%X%n%C%nGUERNSEY%n%Z\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"GY1 1AA,GY2 2BT\","
+ "\"posturl\":\"http://www.guernseypost.com/postcode_finder/\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("GH", "{"
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("GI", "{"
+ "\"fmt\":\"%N%n%O%n%A%nGIBRALTAR%n%Z\","
+ "\"require\":\"A\","
+ "\"zipex\":\"GX11 1AA\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("GL", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"3900,3950,3911\","
+ "\"languages\":\"kl\""
+ "}"));
+ region_data.insert(std::make_pair("GM", "{"
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("GN", "{"
+ "\"fmt\":\"%N%n%O%n%Z %A %C\","
+ "\"zipex\":\"001,200,100\","
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("GP", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%Z %C %X\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"97100\","
+ "\"posturl\":\"http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal\","
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("GQ", "{"
+ "\"languages\":\"es~fr\""
+ "}"));
+ region_data.insert(std::make_pair("GR", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"151 24,151 10,101 88\","
+ "\"posturl\":\"http://www.elta.gr/findapostcode.aspx\","
+ "\"languages\":\"el\""
+ "}"));
+ region_data.insert(std::make_pair("GS", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%X%n%C%n%Z\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"SIQQ 1ZZ\","
+ "\"languages\":\"\""
+ "}"));
+ region_data.insert(std::make_pair("GT", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z- %C\","
+ "\"zipex\":\"09001,01501\","
+ "\"languages\":\"es\""
+ "}"));
+ region_data.insert(std::make_pair("GU", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %S %Z\","
+ "\"require\":\"ACSZ\","
+ "\"zip_name_type\":\"zip\","
+ "\"state_name_type\":\"state\","
+ "\"zipex\":\"96910,96931\","
+ "\"posturl\":\"http://zip4.usps.com/zip4/welcome.jsp\","
+ "\"languages\":\"en~ch\""
+ "}"));
+ region_data.insert(std::make_pair("GW", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"1000,1011\","
+ "\"languages\":\"pt\""
+ "}"));
+ region_data.insert(std::make_pair("GY", "{"
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("HK", "{"
+ "\"fmt\":\"%S%n%C%n%A%n%O%n%N\","
+ "\"lfmt\":\"%N%n%O%n%A%n%C%n%S\","
+ "\"require\":\"AS\","
+ "\"state_name_type\":\"area\","
+ "\"locality_name_type\":\"district\","
+ "\"languages\":\"zh-Hant~en\""
+ "}"));
+ region_data.insert(std::make_pair("HM", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%C %S %Z\","
+ "\"zipex\":\"7050\","
+ "\"languages\":\"\""
+ "}"));
+ region_data.insert(std::make_pair("HN", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C, %S%n%Z\","
+ "\"require\":\"ACS\","
+ "\"zipex\":\"31301\","
+ "\"languages\":\"es\""
+ "}"));
+ region_data.insert(std::make_pair("HR", "{"
+ "\"fmt\":\"%N%n%O%n%A%nHR-%Z %C\","
+ "\"zipex\":\"10000,21001,10002\","
+ "\"posturl\":\"http://www.posta.hr/default.aspx\?pretpum\","
+ "\"languages\":\"hr\""
+ "}"));
+ region_data.insert(std::make_pair("HT", "{"
+ "\"fmt\":\"%N%n%O%n%A%nHT%Z %C %X\","
+ "\"zipex\":\"6120,5310,6110,8510\","
+ "\"languages\":\"ht~fr\""
+ "}"));
+ region_data.insert(std::make_pair("HU", "{"
+ "\"fmt\":\"%N%n%O%n%C%n%A%n%Z\","
+ "\"zipex\":\"1037,2380,1540\","
+ "\"posturl\":\"http://posta.hu/ugyfelszolgalat/iranyitoszam_kereso\","
+ "\"languages\":\"hu\""
+ "}"));
+ region_data.insert(std::make_pair("ID", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C%n%S %Z\","
+ "\"state_name_type\":\"district\","
+ "\"zipex\":\"40115\","
+ "\"languages\":\"id\""
+ "}"));
+ region_data.insert(std::make_pair("IE", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C%n%S\","
+ "\"state_name_type\":\"county\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("IL", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %Z\","
+ "\"zipex\":\"9614303\","
+ "\"posturl\":\"http://www.israelpost.co.il/zipcode.nsf/demozip\?openform\","
+ "\"languages\":\"iw~ar\""
+ "}"));
+ region_data.insert(std::make_pair("IM", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%X%n%C%n%Z\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"IM2 1AA,IM99 1PS\","
+ "\"posturl\":\"http://www.gov.im/post/postal/fr_main.asp\","
+ "\"languages\":\"en~gv\""
+ "}"));
+ region_data.insert(std::make_pair("IN", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %Z%n%S\","
+ "\"require\":\"ACSZ\","
+ "\"zip_name_type\":\"pin\","
+ "\"state_name_type\":\"state\","
+ "\"zipex\":\"110034,110001\","
+ "\"posturl\":\"http://www.indiapost.gov.in/pin/pinsearch.aspx\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("IO", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%X%n%C%n%Z\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"BBND 1ZZ\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("IQ", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%C, %S%n%Z\","
+ "\"require\":\"ACS\","
+ "\"zipex\":\"31001\","
+ "\"languages\":\"ar\""
+ "}"));
+ region_data.insert(std::make_pair("IR", "{"
+ "\"fmt\":\"%O%n%N%n%S%n%C, %D%n%A%n%Z\","
+ "\"sublocality_name_type\":\"neighborhood\","
+ "\"zipex\":\"11936-12345\","
+ "\"languages\":\"fa\""
+ "}"));
+ region_data.insert(std::make_pair("IS", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"320,121,220,110\","
+ "\"posturl\":\"http://www.postur.is/cgi-bin/hsrun.exe/Distributed/vefur/vefur.htx;start=HS_landakort_postnumer\","
+ "\"languages\":\"is\""
+ "}"));
+ region_data.insert(std::make_pair("IT", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C %S\","
+ "\"require\":\"ACSZ\","
+ "\"zipex\":\"00144,47037,39049\","
+ "\"posturl\":\"http://www.poste.it/online/cercacap/\","
+ "\"languages\":\"it\""
+ "}"));
+ region_data.insert(std::make_pair("JE", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%X%n%C%nJERSEY%n%Z\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"JE1 1AA,JE2 2BT\","
+ "\"posturl\":\"http://www.jerseypost.com/tools/postcode-address-finder/\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("JM", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C%n%S %X\","
+ "\"require\":\"ACS\","
+ "\"state_name_type\":\"parish\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("JO", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %Z\","
+ "\"zipex\":\"11937,11190\","
+ "\"languages\":\"ar\""
+ "}"));
+ region_data.insert(std::make_pair("JP", "{"
+ "\"fmt\":\"\\u3012%Z%n%S%C%n%A%n%O%n%N\","
+ "\"lfmt\":\"%N%n%O%n%A%n%C, %S%n%Z\","
+ "\"require\":\"ACSZ\","
+ "\"state_name_type\":\"prefecture\","
+ "\"zipex\":\"154-0023,350-1106,951-8073,112-0001,208-0032,231-0012\","
+ "\"posturl\":\"http://search.post.japanpost.jp/zipcode/\","
+ "\"languages\":\"ja\""
+ "}"));
+ region_data.insert(std::make_pair("KE", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C%n%Z\","
+ "\"zipex\":\"20100,00100\","
+ "\"languages\":\"en~sw\""
+ "}"));
+ region_data.insert(std::make_pair("KG", "{"
+ "\"fmt\":\"%Z %C %X%n%A%n%O%n%N\","
+ "\"zipex\":\"720001\","
+ "\"languages\":\"ky-Cyrl~ru\""
+ "}"));
+ region_data.insert(std::make_pair("KH", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %Z\","
+ "\"zipex\":\"12203,14206,12000\","
+ "\"languages\":\"km\""
+ "}"));
+ region_data.insert(std::make_pair("KI", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%S%n%C\","
+ "\"state_name_type\":\"island\","
+ "\"languages\":\"en~gil\""
+ "}"));
+ region_data.insert(std::make_pair("KM", "{"
+ "\"languages\":\"ar~fr~zdj\""
+ "}"));
+ region_data.insert(std::make_pair("KN", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C, %S\","
+ "\"require\":\"ACS\","
+ "\"state_name_type\":\"island\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("KR", "{"
+ "\"fmt\":\"%S %C%D%n%A%n%O%n%N%n%Z\","
+ "\"lfmt\":\"%N%n%O%n%A%n%D%n%C%n%S%n%Z\","
+ "\"require\":\"ACSZ\","
+ "\"state_name_type\":\"do_si\","
+ "\"sublocality_name_type\":\"district\","
+ "\"zipex\":\"110-110,699-800\","
+ "\"posturl\":\"http://www.epost.go.kr/search/zipcode/search5.jsp\","
+ "\"languages\":\"ko\""
+ "}"));
+ region_data.insert(std::make_pair("KW", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"54541,54551,54404,13009\","
+ "\"languages\":\"ar\""
+ "}"));
+ region_data.insert(std::make_pair("KY", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%S %Z\","
+ "\"require\":\"AS\","
+ "\"state_name_type\":\"island\","
+ "\"zipex\":\"KY1-1100,KY1-1702,KY2-2101\","
+ "\"posturl\":\"http://www.caymanpost.gov.ky/portal/page\?_pageid=3561,1&_dad=portal&_schema=PORTAL\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("KZ", "{"
+ "\"fmt\":\"%Z%n%S%n%C%n%A%n%O%n%N\","
+ "\"zipex\":\"040900,050012\","
+ "\"languages\":\"ru~kk-Cyrl\""
+ "}"));
+ region_data.insert(std::make_pair("LA", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"01160,01000\","
+ "\"languages\":\"lo\""
+ "}"));
+ region_data.insert(std::make_pair("LB", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %Z\","
+ "\"zipex\":\"2038 3054,1107 2810,1000\","
+ "\"languages\":\"ar\""
+ "}"));
+ region_data.insert(std::make_pair("LC", "{"
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("LI", "{"
+ "\"fmt\":\"%O%n%N%n%A%nFL-%Z %C\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"9496,9491,9490,9485\","
+ "\"posturl\":\"http://www.post.ch/db/owa/pv_plz_pack/pr_main\","
+ "\"languages\":\"de~gsw\""
+ "}"));
+ region_data.insert(std::make_pair("LK", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C%n%Z\","
+ "\"zipex\":\"20000,00100\","
+ "\"posturl\":\"http://www.slpost.gov.lk/\","
+ "\"languages\":\"si~ta\""
+ "}"));
+ region_data.insert(std::make_pair("LR", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C %X\","
+ "\"zipex\":\"1000\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("LS", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %Z\","
+ "\"zipex\":\"100\","
+ "\"languages\":\"st~en\""
+ "}"));
+ region_data.insert(std::make_pair("LT", "{"
+ "\"fmt\":\"%O%n%N%n%A%nLT-%Z %C\","
+ "\"zipex\":\"04340,03500\","
+ "\"posturl\":\"http://www.post.lt/lt/\?id=316\","
+ "\"languages\":\"lt\""
+ "}"));
+ region_data.insert(std::make_pair("LU", "{"
+ "\"fmt\":\"%O%n%N%n%A%nL-%Z %C\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"4750,2998\","
+ "\"posturl\":\"http://www.pt.lu/portal/services_en_ligne/recherche_codes_postaux\","
+ "\"languages\":\"fr~lb~de\""
+ "}"));
+ region_data.insert(std::make_pair("LV", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C, %Z\","
+ "\"zipex\":\"LV-1073,LV-1000\","
+ "\"posturl\":\"http://www.pasts.lv/lv/uzzinas/nodalas/\","
+ "\"languages\":\"lv\""
+ "}"));
+ region_data.insert(std::make_pair("LY", "{"
+ "\"languages\":\"ar\""
+ "}"));
+ region_data.insert(std::make_pair("MA", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"53000,10000,20050,16052\","
+ "\"languages\":\"ar~fr~tzm-Latn\""
+ "}"));
+ region_data.insert(std::make_pair("MC", "{"
+ "\"fmt\":\"%N%n%O%n%A%nMC-%Z %C %X\","
+ "\"zipex\":\"98000,98020,98011,98001\","
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("MD", "{"
+ "\"fmt\":\"%N%n%O%n%A%nMD-%Z %C\","
+ "\"zipex\":\"2012,2019\","
+ "\"languages\":\"ro\""
+ "}"));
+ region_data.insert(std::make_pair("ME", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"81257,81258,81217,84314,85366\","
+ "\"languages\":\"sr-Latn\""
+ "}"));
+ region_data.insert(std::make_pair("MF", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%Z %C %X\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"97100\","
+ "\"posturl\":\"http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal\","
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("MG", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"501,101\","
+ "\"languages\":\"mg~fr~en\""
+ "}"));
+ region_data.insert(std::make_pair("MH", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %S %Z\","
+ "\"require\":\"ACSZ\","
+ "\"zip_name_type\":\"zip\","
+ "\"state_name_type\":\"state\","
+ "\"zipex\":\"96960,96970\","
+ "\"posturl\":\"http://zip4.usps.com/zip4/welcome.jsp\","
+ "\"languages\":\"en~mh\""
+ "}"));
+ region_data.insert(std::make_pair("MK", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"1314,1321,1443,1062\","
+ "\"languages\":\"mk\""
+ "}"));
+ region_data.insert(std::make_pair("ML", "{"
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("MM", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C, %Z\","
+ "\"zipex\":\"11181\","
+ "\"languages\":\"my\""
+ "}"));
+ region_data.insert(std::make_pair("MN", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%S %C-%X%n%Z\","
+ "\"zipex\":\"65030,65270\","
+ "\"posturl\":\"http://www.zipcode.mn/\","
+ "\"languages\":\"mn-Cyrl\""
+ "}"));
+ region_data.insert(std::make_pair("MO", "{"
+ "\"fmt\":\"%A%n%O%n%N\","
+ "\"lfmt\":\"%N%n%O%n%A\","
+ "\"require\":\"A\","
+ "\"languages\":\"zh-Hant~pt\""
+ "}"));
+ region_data.insert(std::make_pair("MP", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %S %Z\","
+ "\"require\":\"ACSZ\","
+ "\"zip_name_type\":\"zip\","
+ "\"state_name_type\":\"state\","
+ "\"zipex\":\"96950,96951,96952\","
+ "\"posturl\":\"http://zip4.usps.com/zip4/welcome.jsp\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("MQ", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%Z %C %X\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"97220\","
+ "\"posturl\":\"http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal\","
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("MR", "{"
+ "\"languages\":\"ar\""
+ "}"));
+ region_data.insert(std::make_pair("MS", "{"
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("MT", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %Z\","
+ "\"zipex\":\"NXR 01,ZTN 05,GPO 01,BZN 1130,SPB 6031,VCT 1753\","
+ "\"posturl\":\"http://postcodes.maltapost.com/\","
+ "\"languages\":\"mt~en\""
+ "}"));
+ region_data.insert(std::make_pair("MU", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z%n%C\","
+ "\"zipex\":\"42602\","
+ "\"languages\":\"en~fr\""
+ "}"));
+ region_data.insert(std::make_pair("MV", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %Z\","
+ "\"zipex\":\"20026\","
+ "\"posturl\":\"http://www.maldivespost.com/\?lid=10\","
+ "\"languages\":\"dv\""
+ "}"));
+ region_data.insert(std::make_pair("MW", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %X\","
+ "\"languages\":\"en~ny\""
+ "}"));
+ region_data.insert(std::make_pair("MX", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%D%n%Z %C, %S\","
+ "\"require\":\"ACZ\","
+ "\"state_name_type\":\"state\","
+ "\"sublocality_name_type\":\"neighborhood\","
+ "\"zipex\":\"02860,77520,06082\","
+ "\"posturl\":\"http://www.correosdemexico.gob.mx/ServiciosLinea/Paginas/ccpostales.aspx\","
+ "\"languages\":\"es\""
+ "}"));
+ region_data.insert(std::make_pair("MY", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%D%n%Z %C%n%S\","
+ "\"require\":\"ACZ\","
+ "\"state_name_type\":\"state\","
+ "\"sublocality_name_type\":\"village_township\","
+ "\"zipex\":\"43000,50754,88990,50670\","
+ "\"posturl\":\"http://www.pos.com.my/pos/homepage.aspx\","
+ "\"languages\":\"ms\""
+ "}"));
+ region_data.insert(std::make_pair("MZ", "{"
+ "\"zipex\":\"1102,1119,3212\","
+ "\"languages\":\"pt\""
+ "}"));
+ region_data.insert(std::make_pair("NA", "{"
+ "\"languages\":\"af~en\""
+ "}"));
+ region_data.insert(std::make_pair("NC", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%Z %C %X\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"98814,98800,98810\","
+ "\"posturl\":\"http://poste.opt.nc/index.php\?option=com_content&view=article&id=80&Itemid=131\","
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("NE", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"8001\","
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("NF", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%C %S %Z\","
+ "\"zipex\":\"2899\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("NG", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %Z%n%S\","
+ "\"state_name_type\":\"state\","
+ "\"zipex\":\"930283,300001,931104\","
+ "\"posturl\":\"http://www.nigeriapostcodes.com/views/\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("NI", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z%n%C, %S\","
+ "\"state_name_type\":\"department\","
+ "\"zipex\":\"52000\","
+ "\"posturl\":\"http://www.correos.gob.ni/index.php/codigo-postal-2\","
+ "\"languages\":\"es\""
+ "}"));
+ region_data.insert(std::make_pair("NL", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%Z %C\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"1234 AB,2490 AA\","
+ "\"posturl\":\"http://www.postnl.nl/voorthuis/\","
+ "\"languages\":\"nl\""
+ "}"));
+ region_data.insert(std::make_pair("NO", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"0025,0107,6631\","
+ "\"posturl\":\"http://adressesok.posten.no/nb/postal_codes/search\","
+ "\"languages\":\"no~nn\""
+ "}"));
+ region_data.insert(std::make_pair("NP", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %Z\","
+ "\"zipex\":\"44601\","
+ "\"posturl\":\"http://www.gpo.gov.np/postalcode.aspx\","
+ "\"languages\":\"ne\""
+ "}"));
+ region_data.insert(std::make_pair("NR", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%S\","
+ "\"require\":\"AS\","
+ "\"state_name_type\":\"district\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("NU", "{"
+ "\"languages\":\"en~niu\""
+ "}"));
+ region_data.insert(std::make_pair("NZ", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%D%n%C %Z\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"6001,6015,6332,8252,1030\","
+ "\"posturl\":\"http://www.nzpost.co.nz/Cultures/en-NZ/OnlineTools/PostCodeFinder/\","
+ "\"languages\":\"en~mi\""
+ "}"));
+ region_data.insert(std::make_pair("OM", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z%n%C\","
+ "\"zipex\":\"133,112,111\","
+ "\"languages\":\"ar\""
+ "}"));
+ region_data.insert(std::make_pair("PA", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C%n%S\","
+ "\"languages\":\"es\""
+ "}"));
+ region_data.insert(std::make_pair("PE", "{"
+ "\"zipex\":\"LIMA 23,LIMA 42,CALLAO 2,02001\","
+ "\"posturl\":\"http://www.serpost.com.pe/cpostal/codigo\","
+ "\"languages\":\"es~qu\""
+ "}"));
+ region_data.insert(std::make_pair("PF", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C %S\","
+ "\"require\":\"ACSZ\","
+ "\"state_name_type\":\"island\","
+ "\"zipex\":\"98709\","
+ "\"languages\":\"fr~ty\""
+ "}"));
+ region_data.insert(std::make_pair("PG", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %Z %S\","
+ "\"require\":\"ACS\","
+ "\"zipex\":\"111\","
+ "\"languages\":\"tpi~en~ho\""
+ "}"));
+ region_data.insert(std::make_pair("PH", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%D, %C%n%Z %S\","
+ "\"zipex\":\"1008,1050,1135,1207,2000,1000\","
+ "\"posturl\":\"http://www.philpost.gov.ph/\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("PK", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C-%Z\","
+ "\"zipex\":\"44000\","
+ "\"posturl\":\"http://www.pakpost.gov.pk/postcode/postcode.html\","
+ "\"languages\":\"ur~en\""
+ "}"));
+ region_data.insert(std::make_pair("PL", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"00-950,05-470,48-300,32-015,00-940\","
+ "\"posturl\":\"http://www.poczta-polska.pl/kody.php\","
+ "\"languages\":\"pl\""
+ "}"));
+ region_data.insert(std::make_pair("PM", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%Z %C %X\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"97500\","
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("PN", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%X%n%C%n%Z\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"PCRN 1ZZ\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("PR", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C PR %Z\","
+ "\"require\":\"ACZ\","
+ "\"zip_name_type\":\"zip\","
+ "\"zipex\":\"00930\","
+ "\"posturl\":\"http://zip4.usps.com/zip4/welcome.jsp\","
+ "\"languages\":\"es~en\""
+ "}"));
+ region_data.insert(std::make_pair("PS", "{"
+ "\"languages\":\"ar\""
+ "}"));
+ region_data.insert(std::make_pair("PT", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"2725-079,1250-096,1201-950,2860-571,1208-148\","
+ "\"posturl\":\"http://www.ctt.pt/feapl_2/app/open/tools.jspx\?tool=1\","
+ "\"languages\":\"pt\""
+ "}"));
+ region_data.insert(std::make_pair("PW", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %S %Z\","
+ "\"require\":\"ACSZ\","
+ "\"zip_name_type\":\"zip\","
+ "\"state_name_type\":\"state\","
+ "\"zipex\":\"96940\","
+ "\"posturl\":\"http://zip4.usps.com/zip4/welcome.jsp\","
+ "\"languages\":\"pau~en\""
+ "}"));
+ region_data.insert(std::make_pair("PY", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"1536,1538,1209\","
+ "\"languages\":\"gn~es\""
+ "}"));
+ region_data.insert(std::make_pair("QA", "{"
+ "\"languages\":\"ar\""
+ "}"));
+ region_data.insert(std::make_pair("RE", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%Z %C %X\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"97400\","
+ "\"posturl\":\"http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal\","
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("RO", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"060274,061357,200716\","
+ "\"posturl\":\"http://www.posta-romana.ro/zip_codes\","
+ "\"languages\":\"ro\""
+ "}"));
+ region_data.insert(std::make_pair("RS", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"106314\","
+ "\"posturl\":\"http://www.posta.rs/struktura/lat/aplikacije/pronadji/nadji-postu.asp\","
+ "\"languages\":\"sr-Cyrl~sr-Latn\""
+ "}"));
+ region_data.insert(std::make_pair("RU", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C%n%S%n%Z\","
+ "\"lfmt\":\"%N%n%O%n%A%n%C%n%S%n%Z\","
+ "\"require\":\"ACZ\","
+ "\"state_name_type\":\"oblast\","
+ "\"zipex\":\"247112,103375,188300\","
+ "\"posturl\":\"http://info.russianpost.ru/servlet/department\","
+ "\"languages\":\"ru\""
+ "}"));
+ region_data.insert(std::make_pair("RW", "{"
+ "\"languages\":\"rw~fr~en\""
+ "}"));
+ region_data.insert(std::make_pair("SA", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %Z\","
+ "\"zipex\":\"11564,11187,11142\","
+ "\"languages\":\"ar\""
+ "}"));
+ region_data.insert(std::make_pair("SB", "{"
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("SC", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C%n%S\","
+ "\"state_name_type\":\"island\","
+ "\"languages\":\"fr~en\""
+ "}"));
+ region_data.insert(std::make_pair("SE", "{"
+ "\"fmt\":\"%O%n%N%n%A%nSE-%Z %C\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"11455,12345,10500\","
+ "\"posturl\":\"http://www.posten.se/sv/Kundservice/Sidor/Sok-postnummer-resultat.aspx\","
+ "\"languages\":\"sv\""
+ "}"));
+ region_data.insert(std::make_pair("SG", "{"
+ "\"fmt\":\"%N%n%O%n%A%nSINGAPORE %Z\","
+ "\"require\":\"AZ\","
+ "\"zipex\":\"546080,308125,408600\","
+ "\"posturl\":\"http://www.singpost.com.sg/quick_services/index.htm\","
+ "\"languages\":\"en~zh-Hans~ms-Latn~ta\""
+ "}"));
+ region_data.insert(std::make_pair("SH", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%X%n%C%n%Z\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"STHL 1ZZ\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("SI", "{"
+ "\"fmt\":\"%N%n%O%n%A%nSI- %Z %C\","
+ "\"zipex\":\"4000,1001,2500\","
+ "\"languages\":\"sl\""
+ "}"));
+ region_data.insert(std::make_pair("SJ", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"9170\","
+ "\"posturl\":\"http://epab.posten.no/\","
+ "\"languages\":\"no\""
+ "}"));
+ region_data.insert(std::make_pair("SK", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"010 01,023 14,972 48,921 01,975 99\","
+ "\"posturl\":\"http://psc.posta.sk\","
+ "\"languages\":\"sk\""
+ "}"));
+ region_data.insert(std::make_pair("SL", "{"
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("SM", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"require\":\"AZ\","
+ "\"zipex\":\"47890,47891,47895,47899\","
+ "\"posturl\":\"http://www.poste.it/online/cercacap/\","
+ "\"languages\":\"it\""
+ "}"));
+ region_data.insert(std::make_pair("SN", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"12500,46024,16556,10000\","
+ "\"languages\":\"wo~fr\""
+ "}"));
+ region_data.insert(std::make_pair("SO", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C, %S %Z\","
+ "\"require\":\"ACS\","
+ "\"zipex\":\"09010,11010\","
+ "\"languages\":\"so\""
+ "}"));
+ region_data.insert(std::make_pair("SR", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %X%n%S\","
+ "\"languages\":\"nl\""
+ "}"));
+ region_data.insert(std::make_pair("SS", "{"
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("ST", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %X\","
+ "\"languages\":\"pt\""
+ "}"));
+ region_data.insert(std::make_pair("SV", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z-%C%n%S\","
+ "\"require\":\"ACS\","
+ "\"zipex\":\"CP 1101\","
+ "\"languages\":\"es\""
+ "}"));
+ region_data.insert(std::make_pair("SX", "{"
+ "\"languages\":\"en~nl\""
+ "}"));
+ region_data.insert(std::make_pair("SZ", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C%n%Z\","
+ "\"zipex\":\"H100\","
+ "\"posturl\":\"http://www.sptc.co.sz/swazipost/codes.php\","
+ "\"languages\":\"en~ss\""
+ "}"));
+ region_data.insert(std::make_pair("TA", "{"
+ "\"zipex\":\"TDCU 1ZZ\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("TC", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%X%n%C%n%Z\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"TKCA 1ZZ\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("TD", "{"
+ "\"languages\":\"fr~ar\""
+ "}"));
+ region_data.insert(std::make_pair("TF", "{"
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("TG", "{"
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("TH", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%D %C%n%S %Z\","
+ "\"lfmt\":\"%N%n%O%n%A%n%D, %C%n%S %Z\","
+ "\"zipex\":\"10150,10210\","
+ "\"languages\":\"th\""
+ "}"));
+ region_data.insert(std::make_pair("TJ", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"735450,734025\","
+ "\"languages\":\"tg-Cyrl\""
+ "}"));
+ region_data.insert(std::make_pair("TK", "{"
+ "\"languages\":\"en~tkl\""
+ "}"));
+ region_data.insert(std::make_pair("TL", "{"
+ "\"languages\":\"pt~tet\""
+ "}"));
+ region_data.insert(std::make_pair("TM", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"744000\","
+ "\"languages\":\"tk-Latn\""
+ "}"));
+ region_data.insert(std::make_pair("TN", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"1002,8129,3100,1030\","
+ "\"posturl\":\"http://www.poste.tn/codes.php\","
+ "\"languages\":\"ar~fr\""
+ "}"));
+ region_data.insert(std::make_pair("TO", "{"
+ "\"languages\":\"to~en\""
+ "}"));
+ region_data.insert(std::make_pair("TR", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C/%S\","
+ "\"require\":\"ACZ\","
+ "\"locality_name_type\":\"district\","
+ "\"zipex\":\"01960,06101\","
+ "\"posturl\":\"http://postakodu.ptt.gov.tr/\","
+ "\"languages\":\"tr\""
+ "}"));
+ region_data.insert(std::make_pair("TT", "{"
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("TV", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%X%n%C%n%S\","
+ "\"state_name_type\":\"island\","
+ "\"languages\":\"tyv\""
+ "}"));
+ region_data.insert(std::make_pair("TW", "{"
+ "\"fmt\":\"%Z%n%S%C%n%A%n%O%n%N\","
+ "\"lfmt\":\"%N%n%O%n%A%n%C, %S %Z\","
+ "\"require\":\"ACSZ\","
+ "\"state_name_type\":\"county\","
+ "\"zipex\":\"104,106,10603,40867\","
+ "\"posturl\":\"http://www.post.gov.tw/post/internet/f_searchzone/index.jsp\?ID=190102\","
+ "\"languages\":\"zh-Hant\""
+ "}"));
+ region_data.insert(std::make_pair("TZ", "{"
+ "\"zipex\":\"6090\","
+ "\"languages\":\"sw~en\""
+ "}"));
+ region_data.insert(std::make_pair("UA", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C%n%S%n%Z\","
+ "\"lfmt\":\"%N%n%O%n%A%n%C%n%S%n%Z\","
+ "\"require\":\"ACZ\","
+ "\"state_name_type\":\"oblast\","
+ "\"zipex\":\"15432,01055,01001\","
+ "\"posturl\":\"http://services.ukrposhta.com/postindex_new/\","
+ "\"languages\":\"uk\""
+ "}"));
+ region_data.insert(std::make_pair("UG", "{"
+ "\"languages\":\"sw~en\""
+ "}"));
+ region_data.insert(std::make_pair("UM", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %S %Z\","
+ "\"require\":\"ACS\","
+ "\"zip_name_type\":\"zip\","
+ "\"state_name_type\":\"state\","
+ "\"zipex\":\"96898\","
+ "\"posturl\":\"http://zip4.usps.com/zip4/welcome.jsp\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("US", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C, %S %Z\","
+ "\"require\":\"ACSZ\","
+ "\"zip_name_type\":\"zip\","
+ "\"state_name_type\":\"state\","
+ "\"zipex\":\"95014,22162-1010\","
+ "\"posturl\":\"https://tools.usps.com/go/ZipLookupAction!input.action\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("UY", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C %S\","
+ "\"zipex\":\"11600\","
+ "\"posturl\":\"http://www.correo.com.uy/index.asp\?codPag=codPost&switchMapa=codPost\","
+ "\"languages\":\"es\""
+ "}"));
+ region_data.insert(std::make_pair("UZ", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C%n%S\","
+ "\"zipex\":\"702100,700000\","
+ "\"posturl\":\"http://www.pochta.uz/index.php/uz/pochta-indekslari/9\","
+ "\"languages\":\"uz-Latn~uz-Cyrl\""
+ "}"));
+ region_data.insert(std::make_pair("VA", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"00120\","
+ "\"languages\":\"it~la\""
+ "}"));
+ region_data.insert(std::make_pair("VC", "{"
+ "\"zipex\":\"VC0100,VC0110,VC0400\","
+ "\"posturl\":\"http://www.svgpost.gov.vc/\?option=com_content&view=article&id=3&Itemid=16\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("VE", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %Z, %S\","
+ "\"require\":\"ACS\","
+ "\"zipex\":\"1010,3001,8011,1020\","
+ "\"posturl\":\"http://www.ipostel.gob.ve/nlinea/codigo_postal.php\","
+ "\"languages\":\"es\""
+ "}"));
+ region_data.insert(std::make_pair("VG", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C%n%Z\","
+ "\"require\":\"A\","
+ "\"zipex\":\"VG1110,VG1150,VG1160\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("VI", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C %S %Z\","
+ "\"require\":\"ACSZ\","
+ "\"zip_name_type\":\"zip\","
+ "\"state_name_type\":\"state\","
+ "\"zipex\":\"00802-1222,00850-9802\","
+ "\"posturl\":\"http://zip4.usps.com/zip4/welcome.jsp\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("VN", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C%n%S %Z\","
+ "\"lfmt\":\"%N%n%O%n%A%n%C%n%S %Z\","
+ "\"zipex\":\"119415,136065,720344\","
+ "\"posturl\":\"http://postcode.vnpost.vn/services/search.aspx\","
+ "\"languages\":\"vi\""
+ "}"));
+ region_data.insert(std::make_pair("VU", "{"
+ "\"languages\":\"bi~en~fr\""
+ "}"));
+ region_data.insert(std::make_pair("WF", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%Z %C %X\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"98600\","
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("WS", "{"
+ "\"languages\":\"sm~en\""
+ "}"));
+ region_data.insert(std::make_pair("XK", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"10000\","
+ "\"languages\":\"sq~sr-Cyrl~sr-Latn\""
+ "}"));
+ region_data.insert(std::make_pair("YE", "{"
+ "\"languages\":\"ar\""
+ "}"));
+ region_data.insert(std::make_pair("YT", "{"
+ "\"fmt\":\"%O%n%N%n%A%n%Z %C %X\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"97600\","
+ "\"languages\":\"fr\""
+ "}"));
+ region_data.insert(std::make_pair("ZA", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%D%n%C%n%Z\","
+ "\"require\":\"ACZ\","
+ "\"zipex\":\"0083,1451,0001\","
+ "\"posturl\":\"http://www.postoffice.co.za/tools/postalcode.html\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("ZM", "{"
+ "\"fmt\":\"%N%n%O%n%A%n%Z %C\","
+ "\"zipex\":\"50100,50101\","
+ "\"languages\":\"en\""
+ "}"));
+ region_data.insert(std::make_pair("ZW", "{"
+ "\"languages\":\"en~sn~nd\""
+ "}"));
+ return region_data;
+}
+
+} // namespace
+
+// static
+const std::string& RegionDataConstants::GetDefaultRegionData() {
+ static const std::string kDefaultRegionData(
+ "{"
+ "\"fmt\":\"%N%n%O%n%A%n%C\","
+ "\"require\":\"AC\","
+ "\"zip_name_type\":\"postal\","
+ "\"state_name_type\":\"province\","
+ "\"locality_name_type\":\"city\","
+ "\"sublocality_name_type\":\"suburb\""
+ "}");
+ return kDefaultRegionData;
+}
+// ---- END AUTOGENERATED CODE ----
+
+namespace {
+
+const std::map<std::string, std::string>& GetAllRegionData() {
+ static const std::map<std::string, std::string> kRegionData(InitRegionData());
+ return kRegionData;
+}
+
+struct SelectFirst {
+ template <typename Pair>
+ const typename Pair::first_type& operator()(const Pair& pair) const {
+ return pair.first;
+ }
+};
+
+std::vector<std::string> InitRegionCodes() {
+ std::vector<std::string> region_codes(GetAllRegionData().size());
+ std::transform(GetAllRegionData().begin(),
+ GetAllRegionData().end(),
+ region_codes.begin(),
+ SelectFirst());
+ return region_codes;
+}
+
+const std::map<std::string, size_t> InitMaxLookupKeyDepth() {
+ std::map<std::string, size_t> max_depth;
+ for (std::map<std::string, std::string>::const_iterator
+ it = GetAllRegionData().begin(); it != GetAllRegionData().end(); ++it) {
+ std::vector<FormatElement> fields;
+ // Here it->second actually contains the entire JSON blob for this region,
+ // and not only the format field, but it doesn't really matter when just
+ // checking whether a particular formatting code (eg. "%C") is present, as
+ // there isn't anything else in the JSON that erroneously could match a
+ // formatting code.
+ ParseFormatRule(it->second, &fields);
+ size_t depth = 1;
+ for (; depth < arraysize(LookupKey::kHierarchy); ++depth) {
+ AddressField field = LookupKey::kHierarchy[depth];
+ // Check to see if a particular field in the hierarchy is used by
+ // addresses in this country. If not, the maximum depth has been reached.
+ if (std::find(fields.begin(), fields.end(), FormatElement(field)) ==
+ fields.end()) {
+ break;
+ }
+ }
+ max_depth.insert(std::make_pair(it->first, depth - 1));
+ }
+ return max_depth;
+}
+
+} // namespace
+
+// static
+const bool RegionDataConstants::IsSupported(const std::string& region_code) {
+ static const std::set<std::string> kRegionCodes(GetRegionCodes().begin(),
+ GetRegionCodes().end());
+ return kRegionCodes.find(region_code) != kRegionCodes.end();
+}
+
+// static
+const std::vector<std::string>& RegionDataConstants::GetRegionCodes() {
+ static const std::vector<std::string> kRegionCodes(InitRegionCodes());
+ return kRegionCodes;
+}
+
+// static
+const std::string& RegionDataConstants::GetRegionData(
+ const std::string& region_code) {
+ static const std::string kEmptyString;
+ std::map<std::string, std::string>::const_iterator it =
+ GetAllRegionData().find(region_code);
+ return it != GetAllRegionData().end() ? it->second : kEmptyString;
+}
+
+// static
+size_t RegionDataConstants::GetMaxLookupKeyDepth(
+ const std::string& region_code) {
+ static const std::map<std::string, size_t> kMaxDepth(InitMaxLookupKeyDepth());
+ std::map<std::string, size_t>::const_iterator it =
+ kMaxDepth.find(region_code);
+ return it != kMaxDepth.end() ? it->second : 0;
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/region_data_constants.h b/chromium/third_party/libaddressinput/src/cpp/src/region_data_constants.h
new file mode 100644
index 00000000000..4159e722ccb
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/region_data_constants.h
@@ -0,0 +1,42 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_REGION_DATA_CONSTANTS_H_
+#define I18N_ADDRESSINPUT_REGION_DATA_CONSTANTS_H_
+
+#include <libaddressinput/util/basictypes.h>
+
+#include <cstddef>
+#include <string>
+#include <vector>
+
+namespace i18n {
+namespace addressinput {
+
+class RegionDataConstants {
+ public:
+ static const bool IsSupported(const std::string& region_code);
+ static const std::vector<std::string>& GetRegionCodes();
+ static const std::string& GetRegionData(const std::string& region_code);
+ static const std::string& GetDefaultRegionData();
+ static size_t GetMaxLookupKeyDepth(const std::string& region_code);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RegionDataConstants);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_REGION_DATA_CONSTANTS_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/retriever.cc b/chromium/third_party/libaddressinput/src/cpp/src/retriever.cc
new file mode 100644
index 00000000000..151b74d65a8
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/retriever.cc
@@ -0,0 +1,118 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "retriever.h"
+
+#include <libaddressinput/callback.h>
+#include <libaddressinput/source.h>
+#include <libaddressinput/storage.h>
+#include <libaddressinput/util/basictypes.h>
+#include <libaddressinput/util/scoped_ptr.h>
+
+#include <cassert>
+#include <cstddef>
+#include <string>
+
+#include "validating_storage.h"
+
+namespace i18n {
+namespace addressinput {
+
+namespace {
+
+class Helper {
+ public:
+ // Does not take ownership of its parameters.
+ Helper(const std::string& key,
+ const Retriever::Callback& retrieved,
+ const Source& source,
+ ValidatingStorage* storage)
+ : retrieved_(retrieved),
+ source_(source),
+ storage_(storage),
+ fresh_data_ready_(BuildCallback(this, &Helper::OnFreshDataReady)),
+ validated_data_ready_(
+ BuildCallback(this, &Helper::OnValidatedDataReady)),
+ stale_data_() {
+ assert(storage_ != NULL);
+ storage_->Get(key, *validated_data_ready_);
+ }
+
+ private:
+ ~Helper() {}
+
+ void OnValidatedDataReady(bool success,
+ const std::string& key,
+ std::string* data) {
+ if (success) {
+ assert(data != NULL);
+ retrieved_(success, key, *data);
+ delete this;
+ } else {
+ // Validating storage returns (false, key, stale-data) for valid but stale
+ // data. If |data| is empty, however, then it's either missing or invalid.
+ if (data != NULL && !data->empty()) {
+ stale_data_ = *data;
+ }
+ source_.Get(key, *fresh_data_ready_);
+ }
+ delete data;
+ }
+
+ void OnFreshDataReady(bool success,
+ const std::string& key,
+ std::string* data) {
+ if (success) {
+ assert(data != NULL);
+ retrieved_(true, key, *data);
+ storage_->Put(key, data);
+ data = NULL; // Deleted by Storage::Put().
+ } else if (!stale_data_.empty()) {
+ // Reuse the stale data if a download fails. It's better to have slightly
+ // outdated validation rules than to suddenly lose validation ability.
+ retrieved_(true, key, stale_data_);
+ } else {
+ retrieved_(false, key, std::string());
+ }
+ delete data;
+ delete this;
+ }
+
+ const Retriever::Callback& retrieved_;
+ const Source& source_;
+ ValidatingStorage* storage_;
+ const scoped_ptr<const Source::Callback> fresh_data_ready_;
+ const scoped_ptr<const Storage::Callback> validated_data_ready_;
+ std::string stale_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(Helper);
+};
+
+} // namespace
+
+Retriever::Retriever(const Source* source, Storage* storage)
+ : source_(source), storage_(new ValidatingStorage(storage)) {
+ assert(source_ != NULL);
+ assert(storage_ != NULL);
+}
+
+Retriever::~Retriever() {}
+
+void Retriever::Retrieve(const std::string& key,
+ const Callback& retrieved) const {
+ new Helper(key, retrieved, *source_, storage_.get());
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/retriever.h b/chromium/third_party/libaddressinput/src/cpp/src/retriever.h
new file mode 100644
index 00000000000..4bf27a5d06c
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/retriever.h
@@ -0,0 +1,68 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// An object to retrieve data.
+
+#ifndef I18N_ADDRESSINPUT_RETRIEVER_H_
+#define I18N_ADDRESSINPUT_RETRIEVER_H_
+
+#include <libaddressinput/callback.h>
+#include <libaddressinput/util/basictypes.h>
+#include <libaddressinput/util/scoped_ptr.h>
+
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+class Source;
+class Storage;
+class ValidatingStorage;
+
+// Retrieves data. Sample usage:
+// Source* source = ...;
+// Storage* storage = ...;
+// Retriever retriever(source, storage);
+// const scoped_ptr<const Retriever::Callback> retrieved(
+// BuildCallback(this, &MyClass::OnDataRetrieved));
+// retriever.Retrieve("data/CA/AB--fr", *retrieved);
+class Retriever {
+ public:
+ typedef i18n::addressinput::Callback<const std::string&,
+ const std::string&> Callback;
+
+ // Takes ownership of |source| and |storage|.
+ Retriever(const Source* source, Storage* storage);
+ ~Retriever();
+
+ // Retrieves the data for |key| and invokes the |retrieved| callback. Checks
+ // for the data in |storage_| first. If storage does not have the data for
+ // |key|, then gets the data from |source_| and places it in storage. If the
+ // data in storage is corrupted, then it's discarded and requested anew. If
+ // the data is stale, then it's requested anew. If the request fails, then
+ // stale data will be returned this one time. Any subsequent call to
+ // Retrieve() will attempt to get fresh data again.
+ void Retrieve(const std::string& key, const Callback& retrieved) const;
+
+ private:
+ scoped_ptr<const Source> source_;
+ scoped_ptr<ValidatingStorage> storage_;
+
+ DISALLOW_COPY_AND_ASSIGN(Retriever);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_RETRIEVER_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/rule.cc b/chromium/third_party/libaddressinput/src/cpp/src/rule.cc
new file mode 100644
index 00000000000..c3d2c22dd88
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/rule.cc
@@ -0,0 +1,306 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "rule.h"
+
+#include <cassert>
+#include <cstddef>
+#include <map>
+#include <string>
+#include <utility>
+
+#include <re2/re2.h>
+
+#include "address_field_util.h"
+#include "format_element.h"
+#include "grit.h"
+#include "messages.h"
+#include "region_data_constants.h"
+#include "util/json.h"
+#include "util/re2ptr.h"
+#include "util/string_split.h"
+
+namespace i18n {
+namespace addressinput {
+
+namespace {
+
+typedef std::map<std::string, int> NameMessageIdMap;
+
+// Used as a separator in a list of items. For example, the list of supported
+// languages can be "de~fr~it".
+const char kSeparator = '~';
+
+NameMessageIdMap InitAdminAreaMessageIds() {
+ NameMessageIdMap message_ids;
+ message_ids.insert(std::make_pair(
+ "area", IDS_LIBADDRESSINPUT_AREA));
+ message_ids.insert(std::make_pair(
+ "county", IDS_LIBADDRESSINPUT_COUNTY));
+ message_ids.insert(std::make_pair(
+ "department", IDS_LIBADDRESSINPUT_DEPARTMENT));
+ message_ids.insert(std::make_pair(
+ "district", IDS_LIBADDRESSINPUT_DISTRICT));
+ message_ids.insert(std::make_pair(
+ "do_si", IDS_LIBADDRESSINPUT_DO_SI));
+ message_ids.insert(std::make_pair(
+ "emirate", IDS_LIBADDRESSINPUT_EMIRATE));
+ message_ids.insert(std::make_pair(
+ "island", IDS_LIBADDRESSINPUT_ISLAND));
+ message_ids.insert(std::make_pair(
+ "oblast", IDS_LIBADDRESSINPUT_OBLAST));
+ message_ids.insert(std::make_pair(
+ "parish", IDS_LIBADDRESSINPUT_PARISH));
+ message_ids.insert(std::make_pair(
+ "prefecture", IDS_LIBADDRESSINPUT_PREFECTURE));
+ message_ids.insert(std::make_pair(
+ "province", IDS_LIBADDRESSINPUT_PROVINCE));
+ message_ids.insert(std::make_pair(
+ "state", IDS_LIBADDRESSINPUT_STATE));
+ return message_ids;
+}
+
+const NameMessageIdMap& GetAdminAreaMessageIds() {
+ static const NameMessageIdMap kAdminAreaMessageIds(InitAdminAreaMessageIds());
+ return kAdminAreaMessageIds;
+}
+
+NameMessageIdMap InitPostalCodeMessageIds() {
+ NameMessageIdMap message_ids;
+ message_ids.insert(std::make_pair(
+ "pin", IDS_LIBADDRESSINPUT_PIN_CODE_LABEL));
+ message_ids.insert(std::make_pair(
+ "postal", IDS_LIBADDRESSINPUT_POSTAL_CODE_LABEL));
+ message_ids.insert(std::make_pair(
+ "zip", IDS_LIBADDRESSINPUT_ZIP_CODE_LABEL));
+ return message_ids;
+}
+
+const NameMessageIdMap& GetPostalCodeMessageIds() {
+ static const NameMessageIdMap kPostalCodeMessageIds(
+ InitPostalCodeMessageIds());
+ return kPostalCodeMessageIds;
+}
+
+NameMessageIdMap InitLocalityMessageIds() {
+ NameMessageIdMap message_ids;
+ message_ids.insert(std::make_pair(
+ "city", IDS_LIBADDRESSINPUT_LOCALITY_LABEL));
+ message_ids.insert(std::make_pair(
+ "post_town", IDS_LIBADDRESSINPUT_POST_TOWN));
+ message_ids.insert(std::make_pair(
+ "district", IDS_LIBADDRESSINPUT_DISTRICT));
+ return message_ids;
+}
+
+const NameMessageIdMap& GetLocalityMessageIds() {
+ static const NameMessageIdMap kLocalityMessageIds(
+ InitLocalityMessageIds());
+ return kLocalityMessageIds;
+}
+
+NameMessageIdMap InitSublocalityMessageIds() {
+ NameMessageIdMap message_ids;
+ message_ids.insert(std::make_pair(
+ "suburb", IDS_LIBADDRESSINPUT_SUBURB));
+ message_ids.insert(std::make_pair(
+ "district", IDS_LIBADDRESSINPUT_DISTRICT));
+ message_ids.insert(std::make_pair(
+ "neighborhood", IDS_LIBADDRESSINPUT_NEIGHBORHOOD));
+ message_ids.insert(std::make_pair(
+ "village_township", IDS_LIBADDRESSINPUT_VILLAGE_TOWNSHIP));
+ return message_ids;
+}
+
+const NameMessageIdMap& GetSublocalityMessageIds() {
+ static const NameMessageIdMap kSublocalityMessageIds(
+ InitSublocalityMessageIds());
+ return kSublocalityMessageIds;
+}
+
+int GetMessageIdFromName(const std::string& name,
+ const NameMessageIdMap& message_ids) {
+ NameMessageIdMap::const_iterator it = message_ids.find(name);
+ return it != message_ids.end() ? it->second : INVALID_MESSAGE_ID;
+}
+
+// Determines whether a given string is a reg-exp or a string. We consider a
+// string to be anything that doesn't contain characters with special meanings
+// in regular expressions - (, [, \, {, ?. These special characters are all the
+// ones that appear in the postal code regular expressions.
+bool ContainsRegExSpecialCharacters(const std::string& input) {
+ return input.find_first_of("([\\{?") != std::string::npos;
+}
+
+} // namespace
+
+Rule::Rule()
+ : id_(),
+ format_(),
+ latin_format_(),
+ required_(),
+ sub_keys_(),
+ languages_(),
+ postal_code_matcher_(NULL),
+ sole_postal_code_(),
+ admin_area_name_message_id_(INVALID_MESSAGE_ID),
+ postal_code_name_message_id_(INVALID_MESSAGE_ID),
+ locality_name_message_id_(INVALID_MESSAGE_ID),
+ sublocality_name_message_id_(INVALID_MESSAGE_ID),
+ name_(),
+ latin_name_(),
+ postal_code_example_(),
+ post_service_url_() {}
+
+Rule::~Rule() {}
+
+// static
+const Rule& Rule::GetDefault() {
+ // Allocated once and leaked on shutdown.
+ static Rule* default_rule = NULL;
+ if (default_rule == NULL) {
+ default_rule = new Rule;
+ default_rule->ParseSerializedRule(
+ RegionDataConstants::GetDefaultRegionData());
+ }
+ return *default_rule;
+}
+
+void Rule::CopyFrom(const Rule& rule) {
+ assert(this != &rule);
+ id_ = rule.id_;
+ format_ = rule.format_;
+ latin_format_ = rule.latin_format_;
+ required_ = rule.required_;
+ sub_keys_ = rule.sub_keys_;
+ languages_ = rule.languages_;
+ postal_code_matcher_.reset(
+ rule.postal_code_matcher_ == NULL
+ ? NULL
+ : new RE2ptr(new RE2(rule.postal_code_matcher_->ptr->pattern(),
+ rule.postal_code_matcher_->ptr->options())));
+ sole_postal_code_ = rule.sole_postal_code_;
+ admin_area_name_message_id_ = rule.admin_area_name_message_id_;
+ postal_code_name_message_id_ = rule.postal_code_name_message_id_;
+ locality_name_message_id_ = rule.locality_name_message_id_;
+ sublocality_name_message_id_ = rule.sublocality_name_message_id_;
+ name_ = rule.name_;
+ latin_name_ = rule.latin_name_;
+ postal_code_example_ = rule.postal_code_example_;
+ post_service_url_ = rule.post_service_url_;
+}
+
+bool Rule::ParseSerializedRule(const std::string& serialized_rule) {
+ Json json;
+ if (!json.ParseObject(serialized_rule)) {
+ return false;
+ }
+ ParseJsonRule(json);
+ return true;
+}
+
+void Rule::ParseJsonRule(const Json& json) {
+ std::string value;
+ if (json.GetStringValueForKey("id", &value)) {
+ id_.swap(value);
+ }
+
+ if (json.GetStringValueForKey("fmt", &value)) {
+ ParseFormatRule(value, &format_);
+ }
+
+ if (json.GetStringValueForKey("lfmt", &value)) {
+ ParseFormatRule(value, &latin_format_);
+ }
+
+ if (json.GetStringValueForKey("require", &value)) {
+ ParseAddressFieldsRequired(value, &required_);
+ }
+
+ if (json.GetStringValueForKey("sub_keys", &value)) {
+ SplitString(value, kSeparator, &sub_keys_);
+ }
+
+ if (json.GetStringValueForKey("languages", &value)) {
+ SplitString(value, kSeparator, &languages_);
+ }
+
+ sole_postal_code_.clear();
+ if (json.GetStringValueForKey("zip", &value)) {
+ // The "zip" field in the JSON data is used in two different ways to
+ // validate the postal code. At the country level, the "zip" field indicates
+ // a Java compatible regular expression corresponding to all postal codes in
+ // the country. At other levels, the regular expression indicates the postal
+ // code prefix expected for addresses in that region.
+ //
+ // In order to make the RE2 object created from the "zip" field useable for
+ // both these purposes, the pattern string is here prefixed with "^" to
+ // anchor it at the beginning of the string so that it can be used with
+ // RE2::PartialMatch() to perform prefix matching or else with
+ // RE2::FullMatch() to perform matching against the entire string.
+ RE2::Options options;
+ options.set_never_capture(true);
+ RE2* matcher = new RE2("^(" + value + ")", options);
+ if (matcher->ok()) {
+ postal_code_matcher_.reset(new RE2ptr(matcher));
+ } else {
+ postal_code_matcher_.reset(NULL);
+ delete matcher;
+ }
+ // If the "zip" field is not a regular expression, then it is the sole
+ // postal code for this rule.
+ if (!ContainsRegExSpecialCharacters(value)) {
+ sole_postal_code_.swap(value);
+ }
+ }
+
+ if (json.GetStringValueForKey("state_name_type", &value)) {
+ admin_area_name_message_id_ =
+ GetMessageIdFromName(value, GetAdminAreaMessageIds());
+ }
+
+ if (json.GetStringValueForKey("zip_name_type", &value)) {
+ postal_code_name_message_id_ =
+ GetMessageIdFromName(value, GetPostalCodeMessageIds());
+ }
+
+ if (json.GetStringValueForKey("locality_name_type", &value)) {
+ locality_name_message_id_ =
+ GetMessageIdFromName(value, GetLocalityMessageIds());
+ }
+
+ if (json.GetStringValueForKey("sublocality_name_type", &value)) {
+ sublocality_name_message_id_ =
+ GetMessageIdFromName(value, GetSublocalityMessageIds());
+ }
+
+ if (json.GetStringValueForKey("name", &value)) {
+ name_.swap(value);
+ }
+
+ if (json.GetStringValueForKey("lname", &value)) {
+ latin_name_.swap(value);
+ }
+
+ if (json.GetStringValueForKey("zipex", &value)) {
+ postal_code_example_.swap(value);
+ }
+
+ if (json.GetStringValueForKey("posturl", &value)) {
+ post_service_url_.swap(value);
+ }
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/rule.h b/chromium/third_party/libaddressinput/src/cpp/src/rule.h
new file mode 100644
index 00000000000..87948df4264
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/rule.h
@@ -0,0 +1,165 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// An object to store address metadata, describing the addressing rules for
+// regions and sub-regions. The address metadata format is documented here:
+//
+// https://github.com/googlei18n/libaddressinput/wiki/AddressValidationMetadata
+
+#ifndef I18N_ADDRESSINPUT_RULE_H_
+#define I18N_ADDRESSINPUT_RULE_H_
+
+#include <libaddressinput/address_field.h>
+#include <libaddressinput/util/basictypes.h>
+#include <libaddressinput/util/scoped_ptr.h>
+
+#include <string>
+#include <vector>
+
+namespace i18n {
+namespace addressinput {
+
+class FormatElement;
+class Json;
+struct RE2ptr;
+
+// Stores address metadata addressing rules, to be used for determining the
+// layout of an address input widget or for address validation. Sample usage:
+// Rule rule;
+// if (rule.ParseSerializedRule("{\"fmt\": \"%A%n%C%S %Z\"}")) {
+// Process(rule.GetFormat());
+// }
+class Rule {
+ public:
+ Rule();
+ ~Rule();
+
+ // Returns the default rule at a country level. If a country does not specify
+ // address format, for example, then the format from this rule should be used
+ // instead.
+ static const Rule& GetDefault();
+
+ // Copies all data from |rule|.
+ void CopyFrom(const Rule& rule);
+
+ // Parses |serialized_rule|. Returns |true| if the |serialized_rule| has valid
+ // format (JSON dictionary).
+ bool ParseSerializedRule(const std::string& serialized_rule);
+
+ // Reads data from |json|, which must already have parsed a serialized rule.
+ void ParseJsonRule(const Json& json);
+
+ // Returns the ID string for this rule.
+ const std::string& GetId() const { return id_; }
+
+ // Returns the format elements for this rule. The format can include the
+ // relevant address fields, but also strings used for formatting, or newline
+ // information.
+ const std::vector<FormatElement>& GetFormat() const { return format_; }
+
+ // Returns the approximate address format with the Latin order of fields. The
+ // format can include the relevant address fields, but also strings used for
+ // formatting, or newline information.
+ const std::vector<FormatElement>& GetLatinFormat() const {
+ return latin_format_;
+ }
+
+ // Returns the required fields for this rule.
+ const std::vector<AddressField>& GetRequired() const { return required_; }
+
+ // Returns the sub-keys for this rule, which are the administrative areas of a
+ // country, the localities of an administrative area, or the dependent
+ // localities of a locality. For example, the rules for "US" have sub-keys of
+ // "CA", "NY", "TX", etc.
+ const std::vector<std::string>& GetSubKeys() const { return sub_keys_; }
+
+ // Returns all of the language tags supported by this rule, for example ["de",
+ // "fr", "it"].
+ const std::vector<std::string>& GetLanguages() const { return languages_; }
+
+ // Returns a pointer to a RE2 regular expression object created from the
+ // postal code format string, if specified, or NULL otherwise. The regular
+ // expression is anchored to the beginning of the string so that it can be
+ // used either with RE2::PartialMatch() to perform prefix matching or else
+ // with RE2::FullMatch() to perform matching against the entire string.
+ const RE2ptr* GetPostalCodeMatcher() const {
+ return postal_code_matcher_.get();
+ }
+
+ // Returns the sole postal code for this rule, if there is one.
+ const std::string& GetSolePostalCode() const { return sole_postal_code_; }
+
+ // The message string identifier for admin area name. If not set, then
+ // INVALID_MESSAGE_ID.
+ int GetAdminAreaNameMessageId() const { return admin_area_name_message_id_; }
+
+ // The message string identifier for postal code name. If not set, then
+ // INVALID_MESSAGE_ID.
+ int GetPostalCodeNameMessageId() const {
+ return postal_code_name_message_id_;
+ }
+
+ // The message string identifier for locality name. If not set, then
+ // INVALID_MESSAGE_ID.
+ int GetLocalityNameMessageId() const {
+ return locality_name_message_id_;
+ }
+
+ // The message string identifier for sublocality name. If not set, then
+ // INVALID_MESSAGE_ID.
+ int GetSublocalityNameMessageId() const {
+ return sublocality_name_message_id_;
+ }
+
+ // Returns the name for the most specific place described by this rule, if
+ // there is one. This is typically set when it differs from the key.
+ const std::string& GetName() const { return name_; }
+
+ // Returns the Latin-script name for the most specific place described by this
+ // rule, if there is one.
+ const std::string& GetLatinName() const { return latin_name_; }
+
+ // Returns the postal code example string for this rule.
+ const std::string& GetPostalCodeExample() const {
+ return postal_code_example_;
+ }
+
+ // Returns the post service URL string for this rule.
+ const std::string& GetPostServiceUrl() const { return post_service_url_; }
+
+ private:
+ std::string id_;
+ std::vector<FormatElement> format_;
+ std::vector<FormatElement> latin_format_;
+ std::vector<AddressField> required_;
+ std::vector<std::string> sub_keys_;
+ std::vector<std::string> languages_;
+ scoped_ptr<const RE2ptr> postal_code_matcher_;
+ std::string sole_postal_code_;
+ int admin_area_name_message_id_;
+ int postal_code_name_message_id_;
+ int locality_name_message_id_;
+ int sublocality_name_message_id_;
+ std::string name_;
+ std::string latin_name_;
+ std::string postal_code_example_;
+ std::string post_service_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(Rule);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_RULE_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/rule_retriever.cc b/chromium/third_party/libaddressinput/src/cpp/src/rule_retriever.cc
new file mode 100644
index 00000000000..f0ed848a807
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/rule_retriever.cc
@@ -0,0 +1,80 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "rule_retriever.h"
+
+#include <libaddressinput/callback.h>
+#include <libaddressinput/util/basictypes.h>
+#include <libaddressinput/util/scoped_ptr.h>
+
+#include <cassert>
+#include <cstddef>
+#include <string>
+
+#include "retriever.h"
+#include "rule.h"
+
+namespace i18n {
+namespace addressinput {
+
+namespace {
+
+class Helper {
+ public:
+ Helper(const std::string& key,
+ const RuleRetriever::Callback& rule_ready,
+ const Retriever& data_retriever)
+ : rule_ready_(rule_ready),
+ data_retrieved_(BuildCallback(this, &Helper::OnDataRetrieved)) {
+ data_retriever.Retrieve(key, *data_retrieved_);
+ }
+
+ private:
+ ~Helper() {}
+
+ void OnDataRetrieved(bool success,
+ const std::string& key,
+ const std::string& data) {
+ Rule rule;
+ if (!success) {
+ rule_ready_(false, key, rule);
+ } else {
+ success = rule.ParseSerializedRule(data);
+ rule_ready_(success, key, rule);
+ }
+ delete this;
+ }
+
+ const RuleRetriever::Callback& rule_ready_;
+ const scoped_ptr<const Retriever::Callback> data_retrieved_;
+
+ DISALLOW_COPY_AND_ASSIGN(Helper);
+};
+
+} // namespace
+
+RuleRetriever::RuleRetriever(const Retriever* retriever)
+ : data_retriever_(retriever) {
+ assert(data_retriever_ != NULL);
+}
+
+RuleRetriever::~RuleRetriever() {}
+
+void RuleRetriever::RetrieveRule(const std::string& key,
+ const Callback& rule_ready) const {
+ new Helper(key, rule_ready, *data_retriever_);
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/rule_retriever.h b/chromium/third_party/libaddressinput/src/cpp/src/rule_retriever.h
new file mode 100644
index 00000000000..ae4b5ee97d2
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/rule_retriever.h
@@ -0,0 +1,59 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// An object to retrieve validation rules.
+
+#ifndef I18N_ADDRESSINPUT_RULE_RETRIEVER_H_
+#define I18N_ADDRESSINPUT_RULE_RETRIEVER_H_
+
+#include <libaddressinput/callback.h>
+#include <libaddressinput/util/basictypes.h>
+#include <libaddressinput/util/scoped_ptr.h>
+
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+class Retriever;
+class Rule;
+
+// Retrieves validation rules. Sample usage:
+// const Retriever* retriever = ...
+// RuleRetriever rules(retriever);
+// const scoped_ptr<const RuleRetriever::Callback> rule_ready(
+// BuildCallback(this, &MyClass::OnRuleReady));
+// rules.RetrieveRule("data/CA/AB--fr", *rule_ready);
+class RuleRetriever {
+ public:
+ typedef i18n::addressinput::Callback<const std::string&,
+ const Rule&> Callback;
+
+ // Takes ownership of |retriever|.
+ explicit RuleRetriever(const Retriever* retriever);
+ ~RuleRetriever();
+
+ // Retrieves the rule for |key| and invokes the |rule_ready| callback.
+ void RetrieveRule(const std::string& key, const Callback& rule_ready) const;
+
+ private:
+ scoped_ptr<const Retriever> data_retriever_;
+
+ DISALLOW_COPY_AND_ASSIGN(RuleRetriever);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_RULE_RETRIEVER_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/util/cctype_tolower_equal.cc b/chromium/third_party/libaddressinput/src/cpp/src/util/cctype_tolower_equal.cc
new file mode 100644
index 00000000000..819138f6170
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/util/cctype_tolower_equal.cc
@@ -0,0 +1,44 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cctype_tolower_equal.h"
+
+#include <algorithm>
+#include <cctype>
+#include <functional>
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+namespace {
+
+struct EqualToTolowerChar
+ : public std::binary_function<std::string::value_type,
+ std::string::value_type, bool> {
+ result_type operator()(first_argument_type a, second_argument_type b) const {
+ return std::tolower(a) == std::tolower(b);
+ }
+};
+
+} // namespace
+
+EqualToTolowerString::result_type EqualToTolowerString::operator()(
+ const first_argument_type& a, const second_argument_type& b) const {
+ return a.size() == b.size() &&
+ std::equal(a.begin(), a.end(), b.begin(), EqualToTolowerChar());
+}
+
+} // addressinput
+} // i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/util/cctype_tolower_equal.h b/chromium/third_party/libaddressinput/src/cpp/src/util/cctype_tolower_equal.h
new file mode 100644
index 00000000000..106087e8c44
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/util/cctype_tolower_equal.h
@@ -0,0 +1,35 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_UTIL_CCTYPE_TOLOWER_EQUAL_H_
+#define I18N_ADDRESSINPUT_UTIL_CCTYPE_TOLOWER_EQUAL_H_
+
+#include <functional>
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+// Performs case insensitive comparison of |a| and |b| by calling std::tolower()
+// from <cctype>.
+struct EqualToTolowerString
+ : public std::binary_function<std::string, std::string, bool> {
+ result_type operator()(const first_argument_type& a,
+ const second_argument_type& b) const;
+};
+
+} // addressinput
+} // i18n
+
+#endif // I18N_ADDRESSINPUT_UTIL_CCTYPE_TOLOWER_EQUAL_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/util/json.cc b/chromium/third_party/libaddressinput/src/cpp/src/util/json.cc
new file mode 100644
index 00000000000..9d30b664332
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/util/json.cc
@@ -0,0 +1,133 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "json.h"
+
+#include <libaddressinput/util/basictypes.h>
+#include <libaddressinput/util/scoped_ptr.h>
+
+#include <cassert>
+#include <cstddef>
+#include <string>
+#include <vector>
+
+#include <rapidjson/document.h>
+#include <rapidjson/reader.h>
+
+namespace i18n {
+namespace addressinput {
+
+using rapidjson::Document;
+using rapidjson::kParseValidateEncodingFlag;
+using rapidjson::Value;
+
+class Json::JsonImpl {
+ public:
+ explicit JsonImpl(const std::string& json)
+ : document_(new Document),
+ value_(document_.get()),
+ dictionaries_(),
+ valid_(false) {
+ document_->Parse<kParseValidateEncodingFlag>(json.c_str());
+ valid_ = !document_->HasParseError() && document_->IsObject();
+ }
+
+ ~JsonImpl() {
+ for (std::vector<const Json*>::const_iterator it = dictionaries_.begin();
+ it != dictionaries_.end(); ++it) {
+ delete *it;
+ }
+ }
+
+ bool valid() const { return valid_; }
+
+ const std::vector<const Json*>& GetSubDictionaries() {
+ if (dictionaries_.empty()) {
+ for (Value::ConstMemberIterator member = value_->MemberBegin();
+ member != value_->MemberEnd(); ++member) {
+ if (member->value.IsObject()) {
+ dictionaries_.push_back(new Json(new JsonImpl(&member->value)));
+ }
+ }
+ }
+ return dictionaries_;
+ }
+
+ bool GetStringValueForKey(const std::string& key, std::string* value) const {
+ assert(value != NULL);
+
+ Value::ConstMemberIterator member = value_->FindMember(key.c_str());
+ if (member == value_->MemberEnd() || !member->value.IsString()) {
+ return false;
+ }
+
+ value->assign(member->value.GetString(),
+ member->value.GetStringLength());
+ return true;
+ }
+
+ private:
+ // Does not take ownership of |value|.
+ explicit JsonImpl(const Value* value)
+ : document_(),
+ value_(value),
+ dictionaries_(),
+ valid_(true) {
+ assert(value_ != NULL);
+ assert(value_->IsObject());
+ }
+
+ // An owned JSON document. Can be NULL if the JSON document is not owned.
+ const scoped_ptr<Document> document_;
+
+ // A JSON document that is not owned. Cannot be NULL. Can point to document_.
+ const Value* const value_;
+
+ // Owned JSON objects of sub-dictionaries.
+ std::vector<const Json*> dictionaries_;
+
+ // True if the JSON object was parsed successfully.
+ bool valid_;
+
+ DISALLOW_COPY_AND_ASSIGN(JsonImpl);
+};
+
+Json::Json() : impl_() {}
+
+Json::~Json() {}
+
+bool Json::ParseObject(const std::string& json) {
+ assert(impl_ == NULL);
+ impl_.reset(new JsonImpl(json));
+ if (!impl_->valid()) {
+ impl_.reset();
+ }
+ return impl_ != NULL;
+}
+
+const std::vector<const Json*>& Json::GetSubDictionaries() const {
+ assert(impl_ != NULL);
+ return impl_->GetSubDictionaries();
+}
+
+bool Json::GetStringValueForKey(const std::string& key,
+ std::string* value) const {
+ assert(impl_ != NULL);
+ return impl_->GetStringValueForKey(key, value);
+}
+
+Json::Json(JsonImpl* impl) : impl_(impl) {}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/util/json.h b/chromium/third_party/libaddressinput/src/cpp/src/util/json.h
new file mode 100644
index 00000000000..1aac803c1fd
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/util/json.h
@@ -0,0 +1,68 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_UTIL_JSON_H_
+#define I18N_ADDRESSINPUT_UTIL_JSON_H_
+
+#include <libaddressinput/util/basictypes.h>
+#include <libaddressinput/util/scoped_ptr.h>
+
+#include <string>
+#include <vector>
+
+namespace i18n {
+namespace addressinput {
+
+// Parses a JSON dictionary of strings. Sample usage:
+// Json json;
+// if (json.ParseObject("{'key1':'value1', 'key2':'value2'}") &&
+// json.HasStringKey("key1")) {
+// Process(json.GetStringValueForKey("key1"));
+// }
+class Json {
+ public:
+ Json();
+ ~Json();
+
+ // Parses the |json| string and returns true if |json| is valid and it is an
+ // object.
+ bool ParseObject(const std::string& json);
+
+ // Returns the list of sub dictionaries. The JSON object must be parsed
+ // successfully in ParseObject() before invoking this method. The caller does
+ // not own the result.
+ const std::vector<const Json*>& GetSubDictionaries() const;
+
+ // Returns true if the parsed JSON contains a string value for |key|. Sets
+ // |value| to the string value of the |key|. The JSON object must be parsed
+ // successfully in ParseObject() before invoking this method. The |value|
+ // parameter should not be NULL.
+ bool GetStringValueForKey(const std::string& key, std::string* value) const;
+
+ private:
+ class JsonImpl;
+ friend class JsonImpl;
+
+ // Constructor to be called by JsonImpl.
+ explicit Json(JsonImpl* impl);
+
+ scoped_ptr<JsonImpl> impl_;
+
+ DISALLOW_COPY_AND_ASSIGN(Json);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_UTIL_JSON_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/util/lru_cache_using_std.h b/chromium/third_party/libaddressinput/src/cpp/src/util/lru_cache_using_std.h
new file mode 100644
index 00000000000..6062d284e6d
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/util/lru_cache_using_std.h
@@ -0,0 +1,170 @@
+/******************************************************************************/
+/* Copyright (c) 2010-2011, Tim Day <timday@timday.com> */
+/* */
+/* Permission to use, copy, modify, and/or distribute this software for any */
+/* purpose with or without fee is hereby granted, provided that the above */
+/* copyright notice and this permission notice appear in all copies. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES */
+/* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF */
+/* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR */
+/* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES */
+/* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN */
+/* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF */
+/* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+/******************************************************************************/
+
+// The original source code is from:
+// https://bitbucket.org/timday/lru_cache/src/497822a492a8/include/lru_cache_using_std.h
+
+#ifndef I18N_ADDRESSINPUT_UTIL_LRU_CACHE_USING_STD_H_
+#define I18N_ADDRESSINPUT_UTIL_LRU_CACHE_USING_STD_H_
+
+#include <cassert>
+#include <cstddef>
+#include <list>
+#include <map>
+#include <utility>
+
+// Class providing fixed-size (by number of records)
+// LRU-replacement cache of a function with signature
+// V f(K).
+// The default comparator/hash/allocator will be used.
+template <
+ typename K,
+ typename V
+ > class lru_cache_using_std
+{
+public:
+
+ typedef K key_type;
+ typedef V value_type;
+
+ // Key access history, most recent at back
+ typedef std::list<key_type> key_tracker_type;
+
+ // Key to value and key history iterator
+ typedef std::map<
+ key_type,
+ std::pair<
+ value_type,
+ typename key_tracker_type::iterator
+ >
+ > key_to_value_type;
+
+ // Constuctor specifies the cached function and
+ // the maximum number of records to be stored
+ lru_cache_using_std(
+ value_type (*f)(const key_type&),
+ size_t c
+ )
+ :_fn(f)
+ ,_capacity(c)
+ {
+ assert(_capacity!=0);
+ }
+
+ // Obtain value of the cached function for k
+ value_type operator()(const key_type& k) {
+
+ // Attempt to find existing record
+ const typename key_to_value_type::iterator it
+ =_key_to_value.find(k);
+
+ if (it==_key_to_value.end()) {
+
+ // We don't have it:
+
+ // Evaluate function and create new record
+ const value_type v=_fn(k);
+ insert(k,v);
+
+ // Return the freshly computed value
+ return v;
+
+ } else {
+
+ // We do have it:
+
+ // Update access record by moving
+ // accessed key to back of list
+ _key_tracker.splice(
+ _key_tracker.end(),
+ _key_tracker,
+ (*it).second.second
+ );
+
+ // Return the retrieved value
+ return (*it).second.first;
+ }
+ }
+
+ // Obtain the cached keys, most recently used element
+ // at head, least recently used at tail.
+ // This method is provided purely to support testing.
+ template <typename IT> void get_keys(IT dst) const {
+ typename key_tracker_type::const_reverse_iterator src
+ =_key_tracker.rbegin();
+ while (src!=_key_tracker.rend()) {
+ *dst++ = *src++;
+ }
+ }
+
+private:
+
+ // Record a fresh key-value pair in the cache
+ void insert(const key_type& k,const value_type& v) {
+
+ // Method is only called on cache misses
+ assert(_key_to_value.find(k)==_key_to_value.end());
+
+ // Make space if necessary
+ if (_key_to_value.size()==_capacity)
+ evict();
+
+ // Record k as most-recently-used key
+ typename key_tracker_type::iterator it
+ =_key_tracker.insert(_key_tracker.end(),k);
+
+ // Create the key-value entry,
+ // linked to the usage record.
+ _key_to_value.insert(
+ std::make_pair(
+ k,
+ std::make_pair(v,it)
+ )
+ );
+ // No need to check return,
+ // given previous assert.
+ }
+
+ // Purge the least-recently-used element in the cache
+ void evict() {
+
+ // Assert method is never called when cache is empty
+ assert(!_key_tracker.empty());
+
+ // Identify least recently used key
+ const typename key_to_value_type::iterator it
+ =_key_to_value.find(_key_tracker.front());
+ assert(it!=_key_to_value.end());
+
+ // Erase both elements to completely purge record
+ _key_to_value.erase(it);
+ _key_tracker.pop_front();
+ }
+
+ // The function to be cached
+ value_type (*_fn)(const key_type&);
+
+ // Maximum number of key-value pairs to be retained
+ const size_t _capacity;
+
+ // Key access history
+ key_tracker_type _key_tracker;
+
+ // Key-to-value lookup
+ key_to_value_type _key_to_value;
+};
+
+#endif // I18N_ADDRESSINPUT_UTIL_LRU_CACHE_USING_STD_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/util/md5.cc b/chromium/third_party/libaddressinput/src/cpp/src/util/md5.cc
new file mode 100644
index 00000000000..ea7561be606
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/util/md5.cc
@@ -0,0 +1,301 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The original source code is from:
+// http://src.chromium.org/viewvc/chrome/trunk/src/base/md5.cc?revision=94203
+
+// The original file was copied from sqlite, and was in the public domain.
+
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+#include "md5.h"
+
+#include <libaddressinput/util/basictypes.h>
+
+#include <cstddef>
+#include <string>
+#include <string.h>
+
+namespace {
+
+struct Context {
+ uint32 buf[4];
+ uint32 bits[2];
+ unsigned char in[64];
+};
+
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+void byteReverse(unsigned char *buf, unsigned longs) {
+ uint32 t;
+ do {
+ t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 |
+ ((unsigned)buf[1]<<8 | buf[0]);
+ *(uint32 *)buf = t;
+ buf += 4;
+ } while (--longs);
+}
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+ ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data. MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+void MD5Transform(uint32 buf[4], const uint32 in[16]) {
+ register uint32 a, b, c, d;
+
+ a = buf[0];
+ b = buf[1];
+ c = buf[2];
+ d = buf[3];
+
+ MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7);
+ MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12);
+ MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17);
+ MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22);
+ MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7);
+ MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12);
+ MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17);
+ MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22);
+ MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7);
+ MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12);
+ MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17);
+ MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22);
+ MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7);
+ MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12);
+ MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17);
+ MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22);
+
+ MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5);
+ MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9);
+ MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14);
+ MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20);
+ MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5);
+ MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9);
+ MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14);
+ MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20);
+ MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5);
+ MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9);
+ MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14);
+ MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20);
+ MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5);
+ MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9);
+ MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14);
+ MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20);
+
+ MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4);
+ MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11);
+ MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16);
+ MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23);
+ MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4);
+ MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11);
+ MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16);
+ MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23);
+ MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4);
+ MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11);
+ MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16);
+ MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23);
+ MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4);
+ MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11);
+ MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16);
+ MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23);
+
+ MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6);
+ MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10);
+ MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15);
+ MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21);
+ MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6);
+ MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10);
+ MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15);
+ MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21);
+ MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6);
+ MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10);
+ MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15);
+ MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21);
+ MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6);
+ MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10);
+ MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15);
+ MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21);
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+
+} // namespace
+
+namespace i18n {
+namespace addressinput {
+
+/*
+ * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void MD5Init(MD5Context* context) {
+ struct Context *ctx = (struct Context *)context;
+ ctx->buf[0] = 0x67452301;
+ ctx->buf[1] = 0xefcdab89;
+ ctx->buf[2] = 0x98badcfe;
+ ctx->buf[3] = 0x10325476;
+ ctx->bits[0] = 0;
+ ctx->bits[1] = 0;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+void MD5Update(MD5Context* context, const std::string& data) {
+ const unsigned char* inbuf = (const unsigned char*)data.data();
+ size_t len = data.size();
+ struct Context *ctx = (struct Context *)context;
+ const unsigned char* buf = (const unsigned char*)inbuf;
+ uint32 t;
+
+ /* Update bitcount */
+
+ t = ctx->bits[0];
+ if ((ctx->bits[0] = t + ((uint32)len << 3)) < t)
+ ctx->bits[1]++; /* Carry from low to high */
+ ctx->bits[1] += static_cast<uint32>(len >> 29);
+
+ t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
+
+ /* Handle any leading odd-sized chunks */
+
+ if (t) {
+ unsigned char *p = (unsigned char *)ctx->in + t;
+
+ t = 64-t;
+ if (len < t) {
+ memcpy(p, buf, len);
+ return;
+ }
+ memcpy(p, buf, t);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32 *)ctx->in);
+ buf += t;
+ len -= t;
+ }
+
+ /* Process data in 64-byte chunks */
+
+ while (len >= 64) {
+ memcpy(ctx->in, buf, 64);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32 *)ctx->in);
+ buf += 64;
+ len -= 64;
+ }
+
+ /* Handle any remaining bytes of data. */
+
+ memcpy(ctx->in, buf, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+void MD5Final(MD5Digest* digest, MD5Context* context) {
+ struct Context *ctx = (struct Context *)context;
+ unsigned count;
+ unsigned char *p;
+
+ /* Compute number of bytes mod 64 */
+ count = (ctx->bits[0] >> 3) & 0x3F;
+
+ /* Set the first char of padding to 0x80. This is safe since there is
+ always at least one byte free */
+ p = ctx->in + count;
+ *p++ = 0x80;
+
+ /* Bytes of padding needed to make 64 bytes */
+ count = 64 - 1 - count;
+
+ /* Pad out to 56 mod 64 */
+ if (count < 8) {
+ /* Two lots of padding: Pad the first block to 64 bytes */
+ memset(p, 0, count);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32 *)ctx->in);
+
+ /* Now fill the next block with 56 bytes */
+ memset(ctx->in, 0, 56);
+ } else {
+ /* Pad block to 56 bytes */
+ memset(p, 0, count-8);
+ }
+ byteReverse(ctx->in, 14);
+
+ /* Append length in bits and transform */
+ ((uint32 *)ctx->in)[ 14 ] = ctx->bits[0];
+ ((uint32 *)ctx->in)[ 15 ] = ctx->bits[1];
+
+ MD5Transform(ctx->buf, (uint32 *)ctx->in);
+ byteReverse((unsigned char *)ctx->buf, 4);
+ memcpy(digest->a, ctx->buf, 16);
+ memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */
+}
+
+std::string MD5DigestToBase16(const MD5Digest& digest) {
+ static char const zEncode[] = "0123456789abcdef";
+
+ std::string ret;
+ ret.resize(32);
+
+ int j = 0;
+ for (int i = 0; i < 16; i ++) {
+ int a = digest.a[i];
+ ret[j++] = zEncode[(a>>4)&0xf];
+ ret[j++] = zEncode[a & 0xf];
+ }
+ return ret;
+}
+
+void MD5Sum(const void* data, size_t length, MD5Digest* digest) {
+ MD5Context ctx;
+ MD5Init(&ctx);
+ MD5Update(&ctx,
+ std::string(reinterpret_cast<const char*>(data), length));
+ MD5Final(digest, &ctx);
+}
+
+std::string MD5String(const std::string& str) {
+ MD5Digest digest;
+ MD5Sum(str.data(), str.length(), &digest);
+ return MD5DigestToBase16(digest);
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/util/md5.h b/chromium/third_party/libaddressinput/src/cpp/src/util/md5.h
new file mode 100644
index 00000000000..98bc3254fbc
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/util/md5.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The original source code is from:
+// http://src.chromium.org/viewvc/chrome/trunk/src/base/md5.h?revision=94203
+
+#ifndef I18N_ADDRESSINPUT_UTIL_MD5_H_
+#define I18N_ADDRESSINPUT_UTIL_MD5_H_
+
+#include <cstddef>
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+// MD5 stands for Message Digest algorithm 5.
+// MD5 is a robust hash function, designed for cyptography, but often used
+// for file checksums. The code is complex and slow, but has few
+// collisions.
+// See Also:
+// http://en.wikipedia.org/wiki/MD5
+
+// These functions perform MD5 operations. The simplest call is MD5Sum() to
+// generate the MD5 sum of the given data.
+//
+// You can also compute the MD5 sum of data incrementally by making multiple
+// calls to MD5Update():
+// MD5Context ctx; // intermediate MD5 data: do not use
+// MD5Init(&ctx);
+// MD5Update(&ctx, data1, length1);
+// MD5Update(&ctx, data2, length2);
+// ...
+//
+// MD5Digest digest; // the result of the computation
+// MD5Final(&digest, &ctx);
+//
+// You can call MD5DigestToBase16() to generate a string of the digest.
+
+// The output of an MD5 operation.
+struct MD5Digest {
+ unsigned char a[16];
+};
+
+// Used for storing intermediate data during an MD5 computation. Callers
+// should not access the data.
+typedef char MD5Context[88];
+
+// Computes the MD5 sum of the given data buffer with the given length.
+// The given 'digest' structure will be filled with the result data.
+void MD5Sum(const void* data, size_t length, MD5Digest* digest);
+
+// Initializes the given MD5 context structure for subsequent calls to
+// MD5Update().
+void MD5Init(MD5Context* context);
+
+// For the given buffer of |data| as a StringPiece, updates the given MD5
+// context with the sum of the data. You can call this any number of times
+// during the computation, except that MD5Init() must have been called first.
+void MD5Update(MD5Context* context, const std::string& data);
+
+// Finalizes the MD5 operation and fills the buffer with the digest.
+void MD5Final(MD5Digest* digest, MD5Context* context);
+
+// Converts a digest into human-readable hexadecimal.
+std::string MD5DigestToBase16(const MD5Digest& digest);
+
+// Returns the MD5 (in hexadecimal) of a string.
+std::string MD5String(const std::string& str);
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_UTIL_MD5_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/util/re2ptr.h b/chromium/third_party/libaddressinput/src/cpp/src/util/re2ptr.h
new file mode 100644
index 00000000000..e04bcfd85cf
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/util/re2ptr.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// Work-around for problems with the RE2 library. Must be the first #include
+// statement in the compilation unit. Do not #include in other header files.
+
+#ifndef I18N_ADDRESSINPUT_UTIL_RE2PTR_H_
+#define I18N_ADDRESSINPUT_UTIL_RE2PTR_H_
+
+// RE2 will, in some environments, define class RE2 inside namespace re2 and
+// then have a public "using re2::RE2;" statement to bring that definition into
+// the root namespace, while in other environments it will define class RE2
+// directly in the root namespace.
+//
+// Because of that, it's impossible to write a portable forward declaration of
+// class RE2.
+//
+// The work-around in this file works by wrapping pointers to RE2 object in the
+// simple struct RE2ptr, which is trivial to forward declare.
+
+#include <re2/re2.h>
+
+namespace i18n {
+namespace addressinput {
+
+struct RE2ptr {
+ RE2ptr(RE2* init_ptr) : ptr(init_ptr) {}
+ ~RE2ptr() { delete ptr; }
+ RE2* const ptr;
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_UTIL_RE2PTR_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/util/string_compare.cc b/chromium/third_party/libaddressinput/src/cpp/src/util/string_compare.cc
new file mode 100644
index 00000000000..31a75346af2
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/util/string_compare.cc
@@ -0,0 +1,102 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "string_compare.h"
+
+#include <libaddressinput/util/basictypes.h>
+
+#include <cassert>
+#include <string>
+
+#include <re2/re2.h>
+
+#include "lru_cache_using_std.h"
+
+// RE2 uses type string, which is not necessarily the same as type std::string.
+// In order to create objects of the correct type, to be able to pass pointers
+// to these objects to RE2, the function that does that is defined inside an
+// unnamed namespace inside the re2 namespace. Oh, my ...
+namespace re2 {
+namespace {
+
+// In order to (mis-)use RE2 to implement UTF-8 capable less<>, this function
+// calls RE2::PossibleMatchRange() to calculate the "lessest" string that would
+// be a case-insensitive match to the string. This is far too expensive to do
+// repeatedly, so the function is only ever called through an LRU cache.
+std::string ComputeMinPossibleMatch(const std::string& str) {
+ string min, max; // N.B.: RE2 type string!
+
+ RE2::Options options;
+ options.set_literal(true);
+ options.set_case_sensitive(false);
+ RE2 matcher(str, options);
+
+ bool success = matcher.PossibleMatchRange(&min, &max, str.size());
+ assert(success);
+ (void)success; // Prevent unused variable if assert() is optimized away.
+
+ return min;
+}
+
+} // namespace
+} // namespace re2
+
+namespace i18n {
+namespace addressinput {
+
+class StringCompare::Impl {
+ enum { MAX_CACHE_SIZE = 1 << 15 };
+
+ public:
+ Impl() : min_possible_match_(&re2::ComputeMinPossibleMatch, MAX_CACHE_SIZE) {
+ options_.set_literal(true);
+ options_.set_case_sensitive(false);
+ }
+
+ ~Impl() {}
+
+ bool NaturalEquals(const std::string& a, const std::string& b) const {
+ RE2 matcher(b, options_);
+ return RE2::FullMatch(a, matcher);
+ }
+
+ bool NaturalLess(const std::string& a, const std::string& b) const {
+ const std::string& min_a(min_possible_match_(a));
+ const std::string& min_b(min_possible_match_(b));
+ return min_a < min_b;
+ }
+
+ private:
+ RE2::Options options_;
+ mutable lru_cache_using_std<std::string, std::string> min_possible_match_;
+
+ DISALLOW_COPY_AND_ASSIGN(Impl);
+};
+
+StringCompare::StringCompare() : impl_(new Impl) {}
+
+StringCompare::~StringCompare() {}
+
+bool StringCompare::NaturalEquals(const std::string& a,
+ const std::string& b) const {
+ return impl_->NaturalEquals(a, b);
+}
+
+bool StringCompare::NaturalLess(const std::string& a,
+ const std::string& b) const {
+ return impl_->NaturalLess(a, b);
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/util/string_compare.h b/chromium/third_party/libaddressinput/src/cpp/src/util/string_compare.h
new file mode 100644
index 00000000000..ae680ddc3b6
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/util/string_compare.h
@@ -0,0 +1,52 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_UTIL_STRING_COMPARE_H_
+#define I18N_ADDRESSINPUT_UTIL_STRING_COMPARE_H_
+
+#include <libaddressinput/util/basictypes.h>
+#include <libaddressinput/util/scoped_ptr.h>
+
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+class StringCompare {
+ public:
+ StringCompare();
+ ~StringCompare();
+
+ // Returns true if a human reader would consider |a| and |b| to be "the same".
+ // Libaddressinput itself isn't really concerned about how this is done. This
+ // default implementation just does case insensitive string matching.
+ bool NaturalEquals(const std::string& a, const std::string& b) const;
+
+ // Comparison function for use with the STL analogous to NaturalEquals().
+ // Libaddressinput itself isn't really concerned about how this is done, as
+ // long as it conforms to the STL requirements on less<> predicates. This
+ // default implementation is VERY SLOW! Must be replaced if you need speed.
+ bool NaturalLess(const std::string& a, const std::string& b) const;
+
+ private:
+ class Impl;
+ scoped_ptr<Impl> impl_;
+
+ DISALLOW_COPY_AND_ASSIGN(StringCompare);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_UTIL_STRING_COMPARE_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/util/string_split.cc b/chromium/third_party/libaddressinput/src/cpp/src/util/string_split.cc
new file mode 100644
index 00000000000..114cd92f5c6
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/util/string_split.cc
@@ -0,0 +1,37 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The original source code is from:
+// http://src.chromium.org/viewvc/chrome/trunk/src/base/strings/string_split.cc?revision=216633
+
+#include "string_split.h"
+
+#include <cassert>
+#include <cstddef>
+#include <string>
+#include <vector>
+
+namespace i18n {
+namespace addressinput {
+
+void SplitString(const std::string& str, char s, std::vector<std::string>* r) {
+ assert(r != NULL);
+ r->clear();
+ size_t last = 0;
+ size_t c = str.size();
+ for (size_t i = 0; i <= c; ++i) {
+ if (i == c || str[i] == s) {
+ std::string tmp(str, last, i - last);
+ // Avoid converting an empty or all-whitespace source string into a vector
+ // of one empty string.
+ if (i != c || !r->empty() || !tmp.empty()) {
+ r->push_back(tmp);
+ }
+ last = i + 1;
+ }
+ }
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/util/string_split.h b/chromium/third_party/libaddressinput/src/cpp/src/util/string_split.h
new file mode 100644
index 00000000000..680929646f4
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/util/string_split.h
@@ -0,0 +1,34 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The original source code is from:
+// http://src.chromium.org/viewvc/chrome/trunk/src/base/strings/string_split.h?revision=236210
+//
+// Modifications from original:
+// 1) Supports only std::string type.
+// 2) Does not trim whitespace.
+
+#ifndef I18N_ADDRESSINPUT_UTIL_STRING_SPLIT_H_
+#define I18N_ADDRESSINPUT_UTIL_STRING_SPLIT_H_
+
+#include <string>
+#include <vector>
+
+namespace i18n {
+namespace addressinput {
+
+// Splits |str| into a vector of strings delimited by |c|, placing the results
+// in |r|. If several instances of |c| are contiguous, or if |str| begins with
+// or ends with |c|, then an empty string is inserted.
+//
+// |str| should not be in a multi-byte encoding like Shift-JIS or GBK in which
+// the trailing byte of a multi-byte character can be in the ASCII range.
+// UTF-8, and other single/multi-byte ASCII-compatible encodings are OK.
+// Note: |c| must be in the ASCII range.
+void SplitString(const std::string& str, char c, std::vector<std::string>* r);
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_UTIL_STRING_SPLIT_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/util/string_util.cc b/chromium/third_party/libaddressinput/src/cpp/src/util/string_util.cc
new file mode 100644
index 00000000000..8396d51faac
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/util/string_util.cc
@@ -0,0 +1,68 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The original source code is from:
+// http://src.chromium.org/viewvc/chrome/trunk/src/base/strings/string_util.cc?revision=268754
+//
+// Modified to contain only the DoReplaceStringPlaceholders() that works with
+// std::string. Replaced DCHECK() with assert() and removed offsets.
+
+#include "string_util.h"
+
+#include <cassert>
+#include <cstddef>
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+namespace i18n {
+namespace addressinput {
+
+std::string DoReplaceStringPlaceholders(const std::string& format_string,
+ const std::vector<std::string>& subst) {
+ size_t substitutions = subst.size();
+
+ size_t sub_length = 0;
+ for (std::vector<std::string>::const_iterator iter = subst.begin();
+ iter != subst.end(); ++iter) {
+ sub_length += iter->length();
+ }
+
+ std::string formatted;
+ formatted.reserve(format_string.length() + sub_length);
+
+ for (std::string::const_iterator i = format_string.begin();
+ i != format_string.end(); ++i) {
+ if ('$' == *i) {
+ if (i + 1 != format_string.end()) {
+ ++i;
+ assert('$' == *i || '1' <= *i);
+ if ('$' == *i) {
+ while (i != format_string.end() && '$' == *i) {
+ formatted.push_back('$');
+ ++i;
+ }
+ --i;
+ } else {
+ uintptr_t index = 0;
+ while (i != format_string.end() && '0' <= *i && *i <= '9') {
+ index *= 10;
+ index += *i - '0';
+ ++i;
+ }
+ --i;
+ index -= 1;
+ if (index < substitutions)
+ formatted.append(subst.at(index));
+ }
+ }
+ } else {
+ formatted.push_back(*i);
+ }
+ }
+ return formatted;
+}
+
+} // addressinput
+} // i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/util/string_util.h b/chromium/third_party/libaddressinput/src/cpp/src/util/string_util.h
new file mode 100644
index 00000000000..4266347c621
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/util/string_util.h
@@ -0,0 +1,28 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This file defines utility functions for working with strings.
+//
+// The original source code is from:
+// http://src.chromium.org/viewvc/chrome/trunk/src/base/strings/string_util.h?revision=268754
+//
+// Modified to contain only DoReplaceStringPlaceholders() that works only with
+// std::string.
+
+#ifndef I18N_ADDRESSINPUT_UTIL_STRING_UTIL_H_
+#define I18N_ADDRESSINPUT_UTIL_STRING_UTIL_H_
+
+#include <string>
+#include <vector>
+
+namespace i18n {
+namespace addressinput {
+
+std::string DoReplaceStringPlaceholders(const std::string& format_string,
+ const std::vector<std::string>& subst);
+
+} // addressinput
+} // i18n
+
+#endif // I18N_ADDRESSINPUT_UTIL_STRING_UTIL_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/validating_storage.cc b/chromium/third_party/libaddressinput/src/cpp/src/validating_storage.cc
new file mode 100644
index 00000000000..a504db4502b
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/validating_storage.cc
@@ -0,0 +1,97 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// ValidatingStorage saves data with checksum and timestamp using
+// ValidatingUtil.
+
+#include "validating_storage.h"
+
+#include <libaddressinput/callback.h>
+#include <libaddressinput/storage.h>
+#include <libaddressinput/util/basictypes.h>
+#include <libaddressinput/util/scoped_ptr.h>
+
+#include <cassert>
+#include <cstddef>
+#include <ctime>
+#include <string>
+
+#include "validating_util.h"
+
+namespace i18n {
+namespace addressinput {
+
+namespace {
+
+class Helper {
+ public:
+ Helper(const std::string& key,
+ const ValidatingStorage::Callback& data_ready,
+ const Storage& wrapped_storage)
+ : data_ready_(data_ready),
+ wrapped_data_ready_(BuildCallback(this, &Helper::OnWrappedDataReady)) {
+ wrapped_storage.Get(key, *wrapped_data_ready_);
+ }
+
+ private:
+ ~Helper() {}
+
+ void OnWrappedDataReady(bool success,
+ const std::string& key,
+ std::string* data) {
+ if (success) {
+ assert(data != NULL);
+ bool is_stale = !ValidatingUtil::UnwrapTimestamp(data, std::time(NULL));
+ bool is_corrupted = !ValidatingUtil::UnwrapChecksum(data);
+ success = !is_corrupted && !is_stale;
+ if (is_corrupted) {
+ delete data;
+ data = NULL;
+ }
+ } else {
+ delete data;
+ data = NULL;
+ }
+ data_ready_(success, key, data);
+ delete this;
+ }
+
+ const Storage::Callback& data_ready_;
+ const scoped_ptr<const Storage::Callback> wrapped_data_ready_;
+
+ DISALLOW_COPY_AND_ASSIGN(Helper);
+};
+
+} // namespace
+
+ValidatingStorage::ValidatingStorage(Storage* storage)
+ : wrapped_storage_(storage) {
+ assert(wrapped_storage_ != NULL);
+}
+
+ValidatingStorage::~ValidatingStorage() {}
+
+void ValidatingStorage::Put(const std::string& key, std::string* data) {
+ assert(data != NULL);
+ ValidatingUtil::Wrap(std::time(NULL), data);
+ wrapped_storage_->Put(key, data);
+}
+
+void ValidatingStorage::Get(const std::string& key,
+ const Callback& data_ready) const {
+ new Helper(key, data_ready, *wrapped_storage_);
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/validating_storage.h b/chromium/third_party/libaddressinput/src/cpp/src/validating_storage.h
new file mode 100644
index 00000000000..1cf3f955e1d
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/validating_storage.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// A wrapper object for Storage that stores data with a checksum and a
+// timestamp. The existence of checksum and timestamp fields is transparent to
+// the user of the object.
+
+#ifndef I18N_ADDRESSINPUT_VALIDATING_STORAGE_H_
+#define I18N_ADDRESSINPUT_VALIDATING_STORAGE_H_
+
+#include <libaddressinput/storage.h>
+#include <libaddressinput/util/basictypes.h>
+#include <libaddressinput/util/scoped_ptr.h>
+
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+// Wraps Storage to add checksum and timestamp to stored data. Sample usage:
+// scoped_ptr<Storage> file_storage = ...;
+// ValidatingStorage storage(file_storage));
+// storage.Put("key", new std::string("data"));
+// const scoped_ptr<const ValidatingStorage::Callback> data_ready(
+// BuildCallback(this, &MyClass::OnDataReady));
+// storage.Get("key", *data_ready);
+class ValidatingStorage : public Storage {
+ public:
+ // Takes ownership of |storage|.
+ explicit ValidatingStorage(Storage* storage);
+ virtual ~ValidatingStorage();
+
+ // Storage implementation.
+ virtual void Put(const std::string& key, std::string* data);
+
+ // Storage implementation.
+ // If the data is invalid, then |data_ready| will be called with (false, key,
+ // empty-string). If the data is valid, but stale, then |data_ready| will be
+ // called with (false, key, stale-data). If the data is valid and fresh, then
+ // |data_ready| will be called with (true, key, fresh-data).
+ virtual void Get(const std::string& key, const Callback& data_ready) const;
+
+ private:
+ // The storage being wrapped.
+ scoped_ptr<Storage> wrapped_storage_;
+
+ DISALLOW_COPY_AND_ASSIGN(ValidatingStorage);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_VALIDATING_STORAGE_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/validating_util.cc b/chromium/third_party/libaddressinput/src/cpp/src/validating_util.cc
new file mode 100644
index 00000000000..935668945e4
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/validating_util.cc
@@ -0,0 +1,145 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// ValidatingUtil wraps data with checksum and timestamp. Format:
+//
+// timestamp=<timestamp>
+// checksum=<checksum>
+// <data>
+//
+// The timestamp is the time_t that was returned from time() function. The
+// timestamp does not need to be portable because it is written and read only by
+// ValidatingUtil. The value is somewhat human-readable: it is the number of
+// seconds since the epoch.
+//
+// The checksum is the 32-character hexadecimal MD5 checksum of <data>. It is
+// meant to protect from random file changes on disk.
+
+#include "validating_util.h"
+
+#include <cassert>
+#include <cstddef>
+#include <cstdio>
+#include <cstdlib>
+#include <ctime>
+#include <string>
+
+#include "util/md5.h"
+
+namespace i18n {
+namespace addressinput {
+
+namespace {
+
+const char kTimestampPrefix[] = "timestamp=";
+const size_t kTimestampPrefixLength = sizeof kTimestampPrefix - 1;
+
+const char kChecksumPrefix[] = "checksum=";
+const size_t kChecksumPrefixLength = sizeof kChecksumPrefix - 1;
+
+const char kSeparator = '\n';
+
+// Places the header value into |header_value| parameter and erases the header
+// from |data|. Returns |true| if the header format is valid.
+bool UnwrapHeader(const char* header_prefix,
+ size_t header_prefix_length,
+ std::string* data,
+ std::string* header_value) {
+ assert(header_prefix != NULL);
+ assert(data != NULL);
+ assert(header_value != NULL);
+
+ if (data->compare(
+ 0, header_prefix_length, header_prefix, header_prefix_length) != 0) {
+ return false;
+ }
+
+ std::string::size_type separator_position =
+ data->find(kSeparator, header_prefix_length);
+ if (separator_position == std::string::npos) {
+ return false;
+ }
+
+ header_value->assign(
+ *data, header_prefix_length, separator_position - header_prefix_length);
+ data->erase(0, separator_position + 1);
+
+ return true;
+}
+
+} // namespace
+
+// static
+void ValidatingUtil::Wrap(time_t timestamp, std::string* data) {
+ assert(data != NULL);
+ char timestamp_string[2 + 3 * sizeof timestamp];
+ int size =
+ std::sprintf(timestamp_string, "%ld", static_cast<long>(timestamp));
+ assert(size > 0);
+ assert(size < sizeof timestamp_string);
+ (void)size;
+
+ std::string header;
+ header.append(kTimestampPrefix, kTimestampPrefixLength);
+ header.append(timestamp_string);
+ header.push_back(kSeparator);
+
+ header.append(kChecksumPrefix, kChecksumPrefixLength);
+ header.append(MD5String(*data));
+ header.push_back(kSeparator);
+
+ data->reserve(header.size() + data->size());
+ data->insert(0, header);
+}
+
+// static
+bool ValidatingUtil::UnwrapTimestamp(std::string* data, time_t now) {
+ assert(data != NULL);
+ if (now < 0) {
+ return false;
+ }
+
+ std::string timestamp_string;
+ if (!UnwrapHeader(
+ kTimestampPrefix, kTimestampPrefixLength, data, &timestamp_string)) {
+ return false;
+ }
+
+ time_t timestamp = atol(timestamp_string.c_str());
+ if (timestamp < 0) {
+ return false;
+ }
+
+ // One month contains:
+ // 30 days *
+ // 24 hours per day *
+ // 60 minutes per hour *
+ // 60 seconds per minute.
+ static const double kOneMonthInSeconds = 30.0 * 24.0 * 60.0 * 60.0;
+ double age_in_seconds = difftime(now, timestamp);
+ return !(age_in_seconds < 0.0) && age_in_seconds < kOneMonthInSeconds;
+}
+
+// static
+bool ValidatingUtil::UnwrapChecksum(std::string* data) {
+ assert(data != NULL);
+ std::string checksum;
+ if (!UnwrapHeader(kChecksumPrefix, kChecksumPrefixLength, data, &checksum)) {
+ return false;
+ }
+ return checksum == MD5String(*data);
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/validating_util.h b/chromium/third_party/libaddressinput/src/cpp/src/validating_util.h
new file mode 100644
index 00000000000..20d541b291a
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/validating_util.h
@@ -0,0 +1,60 @@
+// Copyright (C) 2013 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// An object to wrap data with a checksum and a timestamp. These fields are used
+// to verify that the data is not stale or corrupted. Staleness threshold is 1
+// month.
+
+#ifndef I18N_ADDRESSINPUT_VALIDATING_UTIL_H_
+#define I18N_ADDRESSINPUT_VALIDATING_UTIL_H_
+
+#include <libaddressinput/util/basictypes.h>
+
+#include <ctime>
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+// Wraps data with a checksum and a timestamp. Sample usage:
+// std::string data = ...
+// ValidatingUtil::Wrap(time(NULL), &data);
+// Process(data);
+//
+// std::string unwrapped = wrapped;
+// if (ValidatingUtil::UnwrapTimestamp(&unwrapped, time(NULL)) &&
+// ValidatingUtil::UnwrapChecksum(&unwrapped)) {
+// Process(unwrapped);
+// }
+class ValidatingUtil {
+ public:
+ // Adds checksum and given |timestamp| to |data|.
+ static void Wrap(time_t timestamp, std::string* data);
+
+ // Strips out the timestamp from |data|. Returns |true| if the timestamp is
+ // present, formatted correctly, valid, and recent with respect to |now|.
+ static bool UnwrapTimestamp(std::string* data, time_t now);
+
+ // Strips out the checksum from |data|. Returns |true| if the checksum is
+ // present, formatted correctly, and valid for this data.
+ static bool UnwrapChecksum(std::string* data);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ValidatingUtil);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_VALIDATING_UTIL_H_
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/validation_task.cc b/chromium/third_party/libaddressinput/src/cpp/src/validation_task.cc
new file mode 100644
index 00000000000..1e7911aad18
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/validation_task.cc
@@ -0,0 +1,268 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "validation_task.h"
+
+#include <libaddressinput/address_data.h>
+#include <libaddressinput/address_field.h>
+#include <libaddressinput/address_metadata.h>
+#include <libaddressinput/address_problem.h>
+#include <libaddressinput/address_validator.h>
+#include <libaddressinput/callback.h>
+#include <libaddressinput/supplier.h>
+#include <libaddressinput/util/basictypes.h>
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <re2/re2.h>
+
+#include "lookup_key.h"
+#include "post_box_matchers.h"
+#include "rule.h"
+#include "util/re2ptr.h"
+
+namespace i18n {
+namespace addressinput {
+
+ValidationTask::ValidationTask(const AddressData& address,
+ bool allow_postal,
+ bool require_name,
+ const FieldProblemMap* filter,
+ FieldProblemMap* problems,
+ const AddressValidator::Callback& validated)
+ : address_(address),
+ allow_postal_(allow_postal),
+ require_name_(require_name),
+ filter_(filter),
+ problems_(problems),
+ validated_(validated),
+ supplied_(BuildCallback(this, &ValidationTask::Validate)),
+ lookup_key_(new LookupKey) {
+ assert(problems_ != NULL);
+ assert(supplied_ != NULL);
+ assert(lookup_key_ != NULL);
+}
+
+ValidationTask::~ValidationTask() {
+}
+
+void ValidationTask::Run(Supplier* supplier) const {
+ assert(supplier != NULL);
+ problems_->clear();
+ lookup_key_->FromAddress(address_);
+ supplier->Supply(*lookup_key_, *supplied_);
+}
+
+void ValidationTask::Validate(bool success,
+ const LookupKey& lookup_key,
+ const Supplier::RuleHierarchy& hierarchy) {
+ assert(&lookup_key == lookup_key_.get()); // Sanity check.
+
+ if (success) {
+ if (address_.IsFieldEmpty(COUNTRY)) {
+ ReportProblemMaybe(COUNTRY, MISSING_REQUIRED_FIELD);
+ } else if (hierarchy.rule[0] == NULL) {
+ ReportProblemMaybe(COUNTRY, UNKNOWN_VALUE);
+ } else {
+ // Checks which use statically linked metadata.
+ const std::string& region_code = address_.region_code;
+ CheckUnexpectedField(region_code);
+ CheckMissingRequiredField(region_code);
+
+ // Checks which use data from the metadata server. Note that
+ // CheckPostalCodeFormatAndValue assumes CheckUnexpectedField has already
+ // been called.
+ CheckUnknownValue(hierarchy);
+ CheckPostalCodeFormatAndValue(hierarchy);
+ CheckUsesPoBox(hierarchy);
+ }
+ }
+
+ validated_(success, address_, *problems_);
+ delete this;
+}
+
+// A field will return an UNEXPECTED_FIELD problem type if the current value of
+// that field is not empty and the field should not be used by that region.
+void ValidationTask::CheckUnexpectedField(
+ const std::string& region_code) const {
+ static const AddressField kFields[] = {
+ // COUNTRY is never unexpected.
+ ADMIN_AREA,
+ LOCALITY,
+ DEPENDENT_LOCALITY,
+ SORTING_CODE,
+ POSTAL_CODE,
+ STREET_ADDRESS,
+ ORGANIZATION,
+ RECIPIENT
+ };
+
+ for (size_t i = 0; i < arraysize(kFields); ++i) {
+ AddressField field = kFields[i];
+ if (!address_.IsFieldEmpty(field) && !IsFieldUsed(field, region_code)) {
+ ReportProblemMaybe(field, UNEXPECTED_FIELD);
+ }
+ }
+}
+
+// A field will return an MISSING_REQUIRED_FIELD problem type if the current
+// value of that field is empty and the field is required by that region.
+void ValidationTask::CheckMissingRequiredField(
+ const std::string& region_code) const {
+ static const AddressField kFields[] = {
+ // COUNTRY is assumed to have already been checked.
+ ADMIN_AREA,
+ LOCALITY,
+ DEPENDENT_LOCALITY,
+ SORTING_CODE,
+ POSTAL_CODE,
+ STREET_ADDRESS
+ // ORGANIZATION is never required.
+ // RECIPIENT is handled separately.
+ };
+
+ for (size_t i = 0; i < arraysize(kFields); ++i) {
+ AddressField field = kFields[i];
+ if (address_.IsFieldEmpty(field) && IsFieldRequired(field, region_code)) {
+ ReportProblemMaybe(field, MISSING_REQUIRED_FIELD);
+ }
+ }
+
+ if (require_name_ && address_.IsFieldEmpty(RECIPIENT)) {
+ ReportProblemMaybe(RECIPIENT, MISSING_REQUIRED_FIELD);
+ }
+}
+
+// A field is UNKNOWN_VALUE if the metadata contains a list of possible values
+// for the field and the address data server could not match the current value
+// of that field to one of those possible values, therefore returning NULL.
+void ValidationTask::CheckUnknownValue(
+ const Supplier::RuleHierarchy& hierarchy) const {
+ for (size_t depth = 1; depth < arraysize(LookupKey::kHierarchy); ++depth) {
+ AddressField field = LookupKey::kHierarchy[depth];
+ if (!(address_.IsFieldEmpty(field) ||
+ hierarchy.rule[depth - 1] == NULL ||
+ hierarchy.rule[depth - 1]->GetSubKeys().empty() ||
+ hierarchy.rule[depth] != NULL)) {
+ ReportProblemMaybe(field, UNKNOWN_VALUE);
+ }
+ }
+}
+
+// Note that it is assumed that CheckUnexpectedField has already been called.
+void ValidationTask::CheckPostalCodeFormatAndValue(
+ const Supplier::RuleHierarchy& hierarchy) const {
+ assert(hierarchy.rule[0] != NULL);
+ const Rule& country_rule = *hierarchy.rule[0];
+
+ if (!(ShouldReport(POSTAL_CODE, INVALID_FORMAT) ||
+ ShouldReport(POSTAL_CODE, MISMATCHING_VALUE))) {
+ return;
+ }
+
+ if (address_.IsFieldEmpty(POSTAL_CODE)) {
+ return;
+ } else if (std::find(problems_->begin(), problems_->end(),
+ FieldProblemMap::value_type(POSTAL_CODE,
+ UNEXPECTED_FIELD))
+ != problems_->end()) {
+ return; // Problem already reported.
+ }
+
+ // Validate general postal code format. A country-level rule specifies the
+ // regular expression for the whole postal code.
+ const RE2ptr* format_ptr = country_rule.GetPostalCodeMatcher();
+ if (format_ptr != NULL &&
+ !RE2::FullMatch(address_.postal_code, *format_ptr->ptr) &&
+ ShouldReport(POSTAL_CODE, INVALID_FORMAT)) {
+ ReportProblem(POSTAL_CODE, INVALID_FORMAT);
+ return;
+ }
+
+ if (!ShouldReport(POSTAL_CODE, MISMATCHING_VALUE)) {
+ return;
+ }
+
+ for (size_t depth = arraysize(LookupKey::kHierarchy) - 1;
+ depth > 0; --depth) {
+ if (hierarchy.rule[depth] != NULL) {
+ // Validate sub-region specific postal code format. A sub-region specifies
+ // the regular expression for a prefix of the postal code.
+ const RE2ptr* prefix_ptr = hierarchy.rule[depth]->GetPostalCodeMatcher();
+ if (prefix_ptr != NULL) {
+ if (!RE2::PartialMatch(address_.postal_code, *prefix_ptr->ptr)) {
+ ReportProblem(POSTAL_CODE, MISMATCHING_VALUE);
+ }
+ return;
+ }
+ }
+ }
+}
+
+void ValidationTask::CheckUsesPoBox(
+ const Supplier::RuleHierarchy& hierarchy) const {
+ assert(hierarchy.rule[0] != NULL);
+ const Rule& country_rule = *hierarchy.rule[0];
+
+ if (allow_postal_ ||
+ !ShouldReport(STREET_ADDRESS, USES_P_O_BOX) ||
+ address_.IsFieldEmpty(STREET_ADDRESS)) {
+ return;
+ }
+
+ std::vector<const RE2ptr*> matchers =
+ PostBoxMatchers::GetMatchers(country_rule);
+ for (std::vector<std::string>::const_iterator
+ line = address_.address_line.begin();
+ line != address_.address_line.end(); ++line) {
+ for (std::vector<const RE2ptr*>::const_iterator
+ matcher = matchers.begin();
+ matcher != matchers.end(); ++matcher) {
+ if (RE2::PartialMatch(*line, *(*matcher)->ptr)) {
+ ReportProblem(STREET_ADDRESS, USES_P_O_BOX);
+ return;
+ }
+ }
+ }
+}
+
+void ValidationTask::ReportProblem(AddressField field,
+ AddressProblem problem) const {
+ problems_->insert(std::make_pair(field, problem));
+}
+
+void ValidationTask::ReportProblemMaybe(AddressField field,
+ AddressProblem problem) const {
+ if (ShouldReport(field, problem)) {
+ ReportProblem(field, problem);
+ }
+}
+
+bool ValidationTask::ShouldReport(AddressField field,
+ AddressProblem problem) const {
+ return filter_ == NULL || filter_->empty() ||
+ std::find(filter_->begin(),
+ filter_->end(),
+ FieldProblemMap::value_type(field, problem)) !=
+ filter_->end();
+}
+
+} // namespace addressinput
+} // namespace i18n
diff --git a/chromium/third_party/libaddressinput/src/cpp/src/validation_task.h b/chromium/third_party/libaddressinput/src/cpp/src/validation_task.h
new file mode 100644
index 00000000000..165867864fa
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/cpp/src/validation_task.h
@@ -0,0 +1,101 @@
+// Copyright (C) 2014 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef I18N_ADDRESSINPUT_VALIDATION_TASK_H_
+#define I18N_ADDRESSINPUT_VALIDATION_TASK_H_
+
+#include <libaddressinput/address_field.h>
+#include <libaddressinput/address_problem.h>
+#include <libaddressinput/address_validator.h>
+#include <libaddressinput/supplier.h>
+#include <libaddressinput/util/basictypes.h>
+#include <libaddressinput/util/scoped_ptr.h>
+
+#include <string>
+
+namespace i18n {
+namespace addressinput {
+
+class LookupKey;
+struct AddressData;
+
+// A ValidationTask object encapsulates the information necessary to perform
+// validation of one particular address and call a callback when that has been
+// done. Calling the Run() method will load required metadata, then perform
+// validation, call the callback and delete the ValidationTask object itself.
+class ValidationTask {
+ public:
+ ValidationTask(const AddressData& address,
+ bool allow_postal,
+ bool require_name,
+ const FieldProblemMap* filter,
+ FieldProblemMap* problems,
+ const AddressValidator::Callback& validated);
+
+ ~ValidationTask();
+
+ // Calls supplier->Load(), with Validate() as callback.
+ void Run(Supplier* supplier) const;
+
+ private:
+ friend class ValidationTaskTest;
+
+ // Uses the address metadata of |hierarchy| to validate |address_|, writing
+ // problems found into |problems_|, then calls the |validated_| callback and
+ // deletes this ValidationTask object.
+ void Validate(bool success,
+ const LookupKey& lookup_key,
+ const Supplier::RuleHierarchy& hierarchy);
+
+ // Checks all fields for UNEXPECTED_FIELD problems.
+ void CheckUnexpectedField(const std::string& region_code) const;
+
+ // Checks all fields for MISSING_REQUIRED_FIELD problems.
+ void CheckMissingRequiredField(const std::string& region_code) const;
+
+ // Checks the hierarchical fields for UNKNOWN_VALUE problems.
+ void CheckUnknownValue(const Supplier::RuleHierarchy& hierarchy) const;
+
+ // Checks the POSTAL_CODE field for problems.
+ void CheckPostalCodeFormatAndValue(
+ const Supplier::RuleHierarchy& hierarchy) const;
+
+ // Checks the STREET_ADDRESS field for USES_P_O_BOX problems.
+ void CheckUsesPoBox(const Supplier::RuleHierarchy& hierarchy) const;
+
+ // Writes (|field|,|problem|) to |problems_|.
+ void ReportProblem(AddressField field, AddressProblem problem) const;
+
+ // Writes (|field|,|problem|) to |problems_|, if this pair should be reported.
+ void ReportProblemMaybe(AddressField field, AddressProblem problem) const;
+
+ // Returns whether (|field|,|problem|) should be reported.
+ bool ShouldReport(AddressField field, AddressProblem problem) const;
+
+ const AddressData& address_;
+ const bool allow_postal_;
+ const bool require_name_;
+ const FieldProblemMap* filter_;
+ FieldProblemMap* const problems_;
+ const AddressValidator::Callback& validated_;
+ const scoped_ptr<const Supplier::Callback> supplied_;
+ const scoped_ptr<LookupKey> lookup_key_;
+
+ DISALLOW_COPY_AND_ASSIGN(ValidationTask);
+};
+
+} // namespace addressinput
+} // namespace i18n
+
+#endif // I18N_ADDRESSINPUT_VALIDATION_TASK_H_
diff --git a/chromium/third_party/libaddressinput/src/settings.gradle b/chromium/third_party/libaddressinput/src/settings.gradle
new file mode 100644
index 00000000000..18077ac4d39
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/settings.gradle
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * The Android address input widget is formed from the common (non-UI) classes
+ * and the Android specific classes.
+ */
+include 'android', 'common'
diff --git a/chromium/ui/base/BUILD.gn b/chromium/ui/base/BUILD.gn
index db554383406..3ceeaa1e3fe 100644
--- a/chromium/ui/base/BUILD.gn
+++ b/chromium/ui/base/BUILD.gn
@@ -871,7 +871,7 @@ test("ui_base_unittests") {
"//chrome:resources",
"//ui/android:ui_java",
]
- isolate_file = "ui_base_unittests.isolate"
+ isolate_file = "ui_base_tests.isolate"
}
if (use_pango) {
diff --git a/chromium/v8/include/v8-version.h b/chromium/v8/include/v8-version.h
index 2c6c726df0d..b2a85b1929e 100644
--- a/chromium/v8/include/v8-version.h
+++ b/chromium/v8/include/v8-version.h
@@ -11,7 +11,7 @@
#define V8_MAJOR_VERSION 5
#define V8_MINOR_VERSION 1
#define V8_BUILD_NUMBER 281
-#define V8_PATCH_LEVEL 57
+#define V8_PATCH_LEVEL 65
// Use 1 for candidates and 0 otherwise.
// (Boolean macro values are not supported by all preprocessors.)
diff --git a/chromium/v8/src/compiler/ia32/instruction-selector-ia32.cc b/chromium/v8/src/compiler/ia32/instruction-selector-ia32.cc
index 5c4acce6735..3eae18edcb2 100644
--- a/chromium/v8/src/compiler/ia32/instruction-selector-ia32.cc
+++ b/chromium/v8/src/compiler/ia32/instruction-selector-ia32.cc
@@ -27,11 +27,15 @@ class IA32OperandGenerator final : public OperandGenerator {
return DefineAsRegister(node);
}
- bool CanBeMemoryOperand(InstructionCode opcode, Node* node, Node* input) {
+ bool CanBeMemoryOperand(InstructionCode opcode, Node* node, Node* input,
+ int effect_level) {
if (input->opcode() != IrOpcode::kLoad ||
!selector()->CanCover(node, input)) {
return false;
}
+ if (effect_level != selector()->GetEffectLevel(input)) {
+ return false;
+ }
MachineRepresentation rep =
LoadRepresentationOf(input->op()).representation();
switch (opcode) {
@@ -1225,18 +1229,24 @@ void VisitWordCompare(InstructionSelector* selector, Node* node,
InstructionCode narrowed_opcode = TryNarrowOpcodeSize(opcode, left, right);
+ int effect_level = selector->GetEffectLevel(node);
+ if (cont->IsBranch()) {
+ effect_level = selector->GetEffectLevel(
+ cont->true_block()->PredecessorAt(0)->control_input());
+ }
+
// If one of the two inputs is an immediate, make sure it's on the right, or
// if one of the two inputs is a memory operand, make sure it's on the left.
if ((!g.CanBeImmediate(right) && g.CanBeImmediate(left)) ||
- (g.CanBeMemoryOperand(narrowed_opcode, node, right) &&
- !g.CanBeMemoryOperand(narrowed_opcode, node, left))) {
+ (g.CanBeMemoryOperand(narrowed_opcode, node, right, effect_level) &&
+ !g.CanBeMemoryOperand(narrowed_opcode, node, left, effect_level))) {
if (!node->op()->HasProperty(Operator::kCommutative)) cont->Commute();
std::swap(left, right);
}
// Match immediates on right side of comparison.
if (g.CanBeImmediate(right)) {
- if (g.CanBeMemoryOperand(opcode, node, left)) {
+ if (g.CanBeMemoryOperand(opcode, node, left, effect_level)) {
// TODO(epertoso): we should use `narrowed_opcode' here once we match
// immediates too.
return VisitCompareWithMemoryOperand(selector, opcode, left,
@@ -1247,7 +1257,7 @@ void VisitWordCompare(InstructionSelector* selector, Node* node,
}
// Match memory operands on left side of comparison.
- if (g.CanBeMemoryOperand(narrowed_opcode, node, left)) {
+ if (g.CanBeMemoryOperand(narrowed_opcode, node, left, effect_level)) {
bool needs_byte_register =
narrowed_opcode == kIA32Test8 || narrowed_opcode == kIA32Cmp8;
return VisitCompareWithMemoryOperand(
diff --git a/chromium/v8/src/compiler/instruction-selector.cc b/chromium/v8/src/compiler/instruction-selector.cc
index d172ed18588..b7162fe5dcc 100644
--- a/chromium/v8/src/compiler/instruction-selector.cc
+++ b/chromium/v8/src/compiler/instruction-selector.cc
@@ -714,6 +714,12 @@ void InstructionSelector::VisitBlock(BasicBlock* block) {
SetEffectLevel(node, effect_level);
}
+ // We visit the control first, then the nodes in the block, so the block's
+ // control input should be on the same effect level as the last node.
+ if (block->control_input() != nullptr) {
+ SetEffectLevel(block->control_input(), effect_level);
+ }
+
// Generate code for the block control "top down", but schedule the code
// "bottom up".
VisitControl(block);
diff --git a/chromium/v8/src/compiler/s390/code-generator-s390.cc b/chromium/v8/src/compiler/s390/code-generator-s390.cc
index 68c1d9d587a..1d9685668ed 100644
--- a/chromium/v8/src/compiler/s390/code-generator-s390.cc
+++ b/chromium/v8/src/compiler/s390/code-generator-s390.cc
@@ -1307,8 +1307,8 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) {
#endif
case kS390_Push:
if (instr->InputAt(0)->IsDoubleRegister()) {
- __ StoreDouble(i.InputDoubleRegister(0), MemOperand(sp, -kDoubleSize));
__ lay(sp, MemOperand(sp, -kDoubleSize));
+ __ StoreDouble(i.InputDoubleRegister(0), MemOperand(sp));
frame_access_state()->IncreaseSPDelta(kDoubleSize / kPointerSize);
} else {
__ Push(i.InputRegister(0));
@@ -1317,14 +1317,14 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) {
break;
case kS390_PushFrame: {
int num_slots = i.InputInt32(1);
+ __ lay(sp, MemOperand(sp, -num_slots * kPointerSize));
if (instr->InputAt(0)->IsDoubleRegister()) {
__ StoreDouble(i.InputDoubleRegister(0),
- MemOperand(sp, -num_slots * kPointerSize));
+ MemOperand(sp));
} else {
__ StoreP(i.InputRegister(0),
- MemOperand(sp, -num_slots * kPointerSize));
+ MemOperand(sp));
}
- __ lay(sp, MemOperand(sp, -num_slots * kPointerSize));
break;
}
case kS390_StoreToStackSlot: {
@@ -1499,36 +1499,30 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) {
__ ldebr(i.OutputDoubleRegister(), i.InputDoubleRegister(0));
break;
case kS390_DoubleExtractLowWord32:
- // TODO(john.yan): this can cause problem when interrupting,
- // use freg->greg instruction
- __ stdy(i.InputDoubleRegister(0), MemOperand(sp, -kDoubleSize));
- __ LoadlW(i.OutputRegister(),
- MemOperand(sp, -kDoubleSize + Register::kMantissaOffset));
+ __ lgdr(i.OutputRegister(), i.InputDoubleRegister(0));
+ __ llgfr(i.OutputRegister(), i.OutputRegister());
break;
case kS390_DoubleExtractHighWord32:
- // TODO(john.yan): this can cause problem when interrupting,
- // use freg->greg instruction
- __ stdy(i.InputDoubleRegister(0), MemOperand(sp, -kDoubleSize));
- __ LoadlW(i.OutputRegister(),
- MemOperand(sp, -kDoubleSize + Register::kExponentOffset));
+ __ lgdr(i.OutputRegister(), i.InputDoubleRegister(0));
+ __ srlg(i.OutputRegister(), i.OutputRegister(), Operand(32));
break;
case kS390_DoubleInsertLowWord32:
- __ InsertDoubleLow(i.OutputDoubleRegister(), i.InputRegister(1));
+ __ lgdr(kScratchReg, i.OutputDoubleRegister());
+ __ lr(kScratchReg, i.InputRegister(1));
+ __ ldgr(i.OutputDoubleRegister(), kScratchReg);
break;
case kS390_DoubleInsertHighWord32:
- __ InsertDoubleHigh(i.OutputDoubleRegister(), i.InputRegister(1));
+ __ sllg(kScratchReg, i.InputRegister(1), Operand(32));
+ __ lgdr(r0, i.OutputDoubleRegister());
+ __ lr(kScratchReg, r0);
+ __ ldgr(i.OutputDoubleRegister(), kScratchReg);
break;
case kS390_DoubleConstruct:
-// TODO(john.yan): this can cause problem when interrupting,
-// use greg->freg instruction
-#if V8_TARGET_LITTLE_ENDIAN
- __ StoreW(i.InputRegister(0), MemOperand(sp, -kDoubleSize / 2));
- __ StoreW(i.InputRegister(1), MemOperand(sp, -kDoubleSize));
-#else
- __ StoreW(i.InputRegister(1), MemOperand(sp, -kDoubleSize / 2));
- __ StoreW(i.InputRegister(0), MemOperand(sp, -kDoubleSize));
-#endif
- __ ldy(i.OutputDoubleRegister(), MemOperand(sp, -kDoubleSize));
+ __ sllg(kScratchReg, i.InputRegister(0), Operand(32));
+ __ lr(kScratchReg, i.InputRegister(1));
+
+ // Bitwise convert from GPR to FPR
+ __ ldgr(i.OutputDoubleRegister(), kScratchReg);
break;
case kS390_LoadWordS8:
ASSEMBLE_LOAD_INTEGER(LoadlB);
diff --git a/chromium/v8/src/compiler/x64/instruction-selector-x64.cc b/chromium/v8/src/compiler/x64/instruction-selector-x64.cc
index ea1d48b0ccb..f46ff5946d6 100644
--- a/chromium/v8/src/compiler/x64/instruction-selector-x64.cc
+++ b/chromium/v8/src/compiler/x64/instruction-selector-x64.cc
@@ -36,11 +36,15 @@ class X64OperandGenerator final : public OperandGenerator {
}
}
- bool CanBeMemoryOperand(InstructionCode opcode, Node* node, Node* input) {
+ bool CanBeMemoryOperand(InstructionCode opcode, Node* node, Node* input,
+ int effect_level) {
if (input->opcode() != IrOpcode::kLoad ||
!selector()->CanCover(node, input)) {
return false;
}
+ if (effect_level != selector()->GetEffectLevel(input)) {
+ return false;
+ }
MachineRepresentation rep =
LoadRepresentationOf(input->op()).representation();
switch (opcode) {
@@ -1545,16 +1549,22 @@ void VisitWordCompare(InstructionSelector* selector, Node* node,
// If one of the two inputs is an immediate, make sure it's on the right, or
// if one of the two inputs is a memory operand, make sure it's on the left.
+ int effect_level = selector->GetEffectLevel(node);
+ if (cont->IsBranch()) {
+ effect_level = selector->GetEffectLevel(
+ cont->true_block()->PredecessorAt(0)->control_input());
+ }
+
if ((!g.CanBeImmediate(right) && g.CanBeImmediate(left)) ||
- (g.CanBeMemoryOperand(opcode, node, right) &&
- !g.CanBeMemoryOperand(opcode, node, left))) {
+ (g.CanBeMemoryOperand(opcode, node, right, effect_level) &&
+ !g.CanBeMemoryOperand(opcode, node, left, effect_level))) {
if (!node->op()->HasProperty(Operator::kCommutative)) cont->Commute();
std::swap(left, right);
}
// Match immediates on right side of comparison.
if (g.CanBeImmediate(right)) {
- if (g.CanBeMemoryOperand(opcode, node, left)) {
+ if (g.CanBeMemoryOperand(opcode, node, left, effect_level)) {
return VisitCompareWithMemoryOperand(selector, opcode, left,
g.UseImmediate(right), cont);
}
@@ -1563,7 +1573,7 @@ void VisitWordCompare(InstructionSelector* selector, Node* node,
}
// Match memory operands on left side of comparison.
- if (g.CanBeMemoryOperand(opcode, node, left)) {
+ if (g.CanBeMemoryOperand(opcode, node, left, effect_level)) {
return VisitCompareWithMemoryOperand(selector, opcode, left,
g.UseRegister(right), cont);
}
diff --git a/chromium/v8/src/compiler/x87/instruction-selector-x87.cc b/chromium/v8/src/compiler/x87/instruction-selector-x87.cc
index e4d085ee9c0..f5376bc3d45 100644
--- a/chromium/v8/src/compiler/x87/instruction-selector-x87.cc
+++ b/chromium/v8/src/compiler/x87/instruction-selector-x87.cc
@@ -27,11 +27,15 @@ class X87OperandGenerator final : public OperandGenerator {
return DefineAsRegister(node);
}
- bool CanBeMemoryOperand(InstructionCode opcode, Node* node, Node* input) {
+ bool CanBeMemoryOperand(InstructionCode opcode, Node* node, Node* input,
+ int effect_level) {
if (input->opcode() != IrOpcode::kLoad ||
!selector()->CanCover(node, input)) {
return false;
}
+ if (effect_level != selector()->GetEffectLevel(input)) {
+ return false;
+ }
MachineRepresentation rep =
LoadRepresentationOf(input->op()).representation();
switch (opcode) {
@@ -1254,18 +1258,24 @@ void VisitWordCompare(InstructionSelector* selector, Node* node,
InstructionCode narrowed_opcode = TryNarrowOpcodeSize(opcode, left, right);
+ int effect_level = selector->GetEffectLevel(node);
+ if (cont->IsBranch()) {
+ effect_level = selector->GetEffectLevel(
+ cont->true_block()->PredecessorAt(0)->control_input());
+ }
+
// If one of the two inputs is an immediate, make sure it's on the right, or
// if one of the two inputs is a memory operand, make sure it's on the left.
if ((!g.CanBeImmediate(right) && g.CanBeImmediate(left)) ||
- (g.CanBeMemoryOperand(narrowed_opcode, node, right) &&
- !g.CanBeMemoryOperand(narrowed_opcode, node, left))) {
+ (g.CanBeMemoryOperand(narrowed_opcode, node, right, effect_level) &&
+ !g.CanBeMemoryOperand(narrowed_opcode, node, left, effect_level))) {
if (!node->op()->HasProperty(Operator::kCommutative)) cont->Commute();
std::swap(left, right);
}
// Match immediates on right side of comparison.
if (g.CanBeImmediate(right)) {
- if (g.CanBeMemoryOperand(opcode, node, left)) {
+ if (g.CanBeMemoryOperand(opcode, node, left, effect_level)) {
// TODO(epertoso): we should use `narrowed_opcode' here once we match
// immediates too.
return VisitCompareWithMemoryOperand(selector, opcode, left,
@@ -1276,7 +1286,7 @@ void VisitWordCompare(InstructionSelector* selector, Node* node,
}
// Match memory operands on left side of comparison.
- if (g.CanBeMemoryOperand(narrowed_opcode, node, left)) {
+ if (g.CanBeMemoryOperand(narrowed_opcode, node, left, effect_level)) {
bool needs_byte_register =
narrowed_opcode == kX87Test8 || narrowed_opcode == kX87Cmp8;
return VisitCompareWithMemoryOperand(
diff --git a/chromium/v8/src/debug/debug-evaluate.cc b/chromium/v8/src/debug/debug-evaluate.cc
index 1729408e5ce..dae13483222 100644
--- a/chromium/v8/src/debug/debug-evaluate.cc
+++ b/chromium/v8/src/debug/debug-evaluate.cc
@@ -247,7 +247,8 @@ void DebugEvaluate::ContextBuilder::MaterializeReceiver(
// 'this' is allocated in an outer context and is is already being
// referenced by the current function, so it can be correctly resolved.
return;
- } else if (local_function->shared()->scope_info()->HasReceiver()) {
+ } else if (local_function->shared()->scope_info()->HasReceiver() &&
+ !frame_->receiver()->IsTheHole()) {
recv = handle(frame_->receiver(), isolate_);
}
JSObject::SetOwnPropertyIgnoreAttributes(target, name, recv, NONE).Check();
diff --git a/chromium/v8/src/heap/mark-compact.cc b/chromium/v8/src/heap/mark-compact.cc
index e537689c4a5..5ffea25488b 100644
--- a/chromium/v8/src/heap/mark-compact.cc
+++ b/chromium/v8/src/heap/mark-compact.cc
@@ -63,7 +63,8 @@ MarkCompactCollector::MarkCompactCollector(Heap* heap)
compacting_(false),
sweeping_in_progress_(false),
pending_sweeper_tasks_semaphore_(0),
- pending_compaction_tasks_semaphore_(0) {
+ pending_compaction_tasks_semaphore_(0),
+ page_parallel_job_semaphore_(0) {
}
#ifdef VERIFY_HEAP
@@ -3108,7 +3109,8 @@ class EvacuationJobTraits {
void MarkCompactCollector::EvacuatePagesInParallel() {
PageParallelJob<EvacuationJobTraits> job(
- heap_, heap_->isolate()->cancelable_task_manager());
+ heap_, heap_->isolate()->cancelable_task_manager(),
+ &page_parallel_job_semaphore_);
int abandoned_pages = 0;
intptr_t live_bytes = 0;
@@ -3493,9 +3495,9 @@ int NumberOfPointerUpdateTasks(int pages) {
}
template <PointerDirection direction>
-void UpdatePointersInParallel(Heap* heap) {
+void UpdatePointersInParallel(Heap* heap, base::Semaphore* semaphore) {
PageParallelJob<PointerUpdateJobTraits<direction> > job(
- heap, heap->isolate()->cancelable_task_manager());
+ heap, heap->isolate()->cancelable_task_manager(), semaphore);
RememberedSet<direction>::IterateMemoryChunks(
heap, [&job](MemoryChunk* chunk) { job.AddPage(chunk, 0); });
PointersUpdatingVisitor visitor(heap);
@@ -3525,9 +3527,9 @@ class ToSpacePointerUpdateJobTraits {
}
};
-void UpdateToSpacePointersInParallel(Heap* heap) {
+void UpdateToSpacePointersInParallel(Heap* heap, base::Semaphore* semaphore) {
PageParallelJob<ToSpacePointerUpdateJobTraits> job(
- heap, heap->isolate()->cancelable_task_manager());
+ heap, heap->isolate()->cancelable_task_manager(), semaphore);
Address space_start = heap->new_space()->bottom();
Address space_end = heap->new_space()->top();
NewSpacePageIterator it(space_start, space_end);
@@ -3551,17 +3553,17 @@ void MarkCompactCollector::UpdatePointersAfterEvacuation() {
{
TRACE_GC(heap()->tracer(),
GCTracer::Scope::MC_EVACUATE_UPDATE_POINTERS_TO_NEW);
- UpdateToSpacePointersInParallel(heap_);
+ UpdateToSpacePointersInParallel(heap_, &page_parallel_job_semaphore_);
// Update roots.
heap_->IterateRoots(&updating_visitor, VISIT_ALL_IN_SWEEP_NEWSPACE);
- UpdatePointersInParallel<OLD_TO_NEW>(heap_);
+ UpdatePointersInParallel<OLD_TO_NEW>(heap_, &page_parallel_job_semaphore_);
}
{
Heap* heap = this->heap();
TRACE_GC(heap->tracer(),
GCTracer::Scope::MC_EVACUATE_UPDATE_POINTERS_TO_EVACUATED);
- UpdatePointersInParallel<OLD_TO_OLD>(heap_);
+ UpdatePointersInParallel<OLD_TO_OLD>(heap_, &page_parallel_job_semaphore_);
}
{
diff --git a/chromium/v8/src/heap/mark-compact.h b/chromium/v8/src/heap/mark-compact.h
index cd207bcda27..9fee8269d5d 100644
--- a/chromium/v8/src/heap/mark-compact.h
+++ b/chromium/v8/src/heap/mark-compact.h
@@ -841,6 +841,8 @@ class MarkCompactCollector {
// Semaphore used to synchronize compaction tasks.
base::Semaphore pending_compaction_tasks_semaphore_;
+ base::Semaphore page_parallel_job_semaphore_;
+
bool black_allocation_;
friend class Heap;
diff --git a/chromium/v8/src/heap/page-parallel-job.h b/chromium/v8/src/heap/page-parallel-job.h
index 720e288fc8e..02583c78184 100644
--- a/chromium/v8/src/heap/page-parallel-job.h
+++ b/chromium/v8/src/heap/page-parallel-job.h
@@ -33,13 +33,20 @@ class Isolate;
template <typename JobTraits>
class PageParallelJob {
public:
- PageParallelJob(Heap* heap, CancelableTaskManager* cancelable_task_manager)
+ // PageParallelJob cannot dynamically create a semaphore because of a bug in
+ // glibc. See http://crbug.com/609249 and
+ // https://sourceware.org/bugzilla/show_bug.cgi?id=12674.
+ // The caller must provide a semaphore with value 0 and ensure that
+ // the lifetime of the semaphore is the same as the lifetime of the Isolate
+ // It is guaranteed that the semaphore value will be 0 after Run() call.
+ PageParallelJob(Heap* heap, CancelableTaskManager* cancelable_task_manager,
+ base::Semaphore* semaphore)
: heap_(heap),
cancelable_task_manager_(cancelable_task_manager),
items_(nullptr),
num_items_(0),
num_tasks_(0),
- pending_tasks_(new base::Semaphore(0)) {}
+ pending_tasks_(semaphore) {}
~PageParallelJob() {
Item* item = items_;
@@ -48,7 +55,6 @@ class PageParallelJob {
delete item;
item = next;
}
- delete pending_tasks_;
}
void AddPage(MemoryChunk* chunk, typename JobTraits::PerPageData data) {
diff --git a/chromium/v8/src/heap/spaces.cc b/chromium/v8/src/heap/spaces.cc
index 63e7c33c582..a0a37523b24 100644
--- a/chromium/v8/src/heap/spaces.cc
+++ b/chromium/v8/src/heap/spaces.cc
@@ -792,7 +792,9 @@ MemoryChunk* MemoryAllocator::AllocatePagePooled(SpaceType* owner) {
const Address start = reinterpret_cast<Address>(chunk);
const Address area_start = start + MemoryChunk::kObjectStartOffset;
const Address area_end = start + size;
- CommitBlock(reinterpret_cast<Address>(chunk), size, NOT_EXECUTABLE);
+ if (!CommitBlock(reinterpret_cast<Address>(chunk), size, NOT_EXECUTABLE)) {
+ return nullptr;
+ }
base::VirtualMemory reservation(start, size);
MemoryChunk::Initialize(isolate_->heap(), start, size, area_start, area_end,
NOT_EXECUTABLE, owner, &reservation);
diff --git a/chromium/v8/src/js/messages.js b/chromium/v8/src/js/messages.js
index f8cb967cdd0..4529981c30d 100644
--- a/chromium/v8/src/js/messages.js
+++ b/chromium/v8/src/js/messages.js
@@ -795,13 +795,15 @@ function FormatErrorString(error) {
function GetStackFrames(raw_stack) {
+ var internal_raw_stack = new InternalArray();
+ %MoveArrayContents(raw_stack, internal_raw_stack);
var frames = new InternalArray();
- var sloppy_frames = raw_stack[0];
- for (var i = 1; i < raw_stack.length; i += 4) {
- var recv = raw_stack[i];
- var fun = raw_stack[i + 1];
- var code = raw_stack[i + 2];
- var pc = raw_stack[i + 3];
+ var sloppy_frames = internal_raw_stack[0];
+ for (var i = 1; i < internal_raw_stack.length; i += 4) {
+ var recv = internal_raw_stack[i];
+ var fun = internal_raw_stack[i + 1];
+ var code = internal_raw_stack[i + 2];
+ var pc = internal_raw_stack[i + 3];
var pos = %_IsSmi(code) ? code : %FunctionGetPositionForOffset(code, pc);
sloppy_frames--;
frames.push(new CallSite(recv, fun, pos, (sloppy_frames < 0)));
diff --git a/chromium/v8/src/parsing/parser.cc b/chromium/v8/src/parsing/parser.cc
index fa2893b64b9..c9897cdd921 100644
--- a/chromium/v8/src/parsing/parser.cc
+++ b/chromium/v8/src/parsing/parser.cc
@@ -6372,12 +6372,18 @@ void ParserTraits::BuildIteratorClose(ZoneList<Statement*>* statements,
// following code:
//
// let iteratorReturn = iterator.return;
- // if (IS_NULL_OR_UNDEFINED(iteratorReturn) return |input|;
- // output = %_Call(iteratorReturn, iterator|, input|);
+ // if (IS_NULL_OR_UNDEFINED(iteratorReturn) {
+ // return {value: input, done: true};
+ // }
+ // output = %_Call(iteratorReturn, iterator, input);
// if (!IS_RECEIVER(output)) %ThrowIterResultNotAnObject(output);
//
- // Here, |...| denotes optional parts, depending on the presence of the
- // input variable. The reason for allowing input is that BuildIteratorClose
+ // When the input variable is not given, the return statement becomes
+ // return {value: undefined, done: true};
+ // and %_Call has only two arguments:
+ // output = %_Call(iteratorReturn, iterator);
+ //
+ // The reason for allowing input is that BuildIteratorClose
// can then be reused to handle the return case in yield*.
//
@@ -6401,7 +6407,9 @@ void ParserTraits::BuildIteratorClose(ZoneList<Statement*>* statements,
get_return = factory->NewExpressionStatement(assignment, nopos);
}
- // if (IS_NULL_OR_UNDEFINED(iteratorReturn) return |input|;
+ // if (IS_NULL_OR_UNDEFINED(iteratorReturn) {
+ // return {value: input, done: true};
+ // }
Statement* check_return;
{
Expression* condition = factory->NewCompareOperation(
@@ -6413,13 +6421,14 @@ void ParserTraits::BuildIteratorClose(ZoneList<Statement*>* statements,
factory->NewVariableProxy(input.FromJust()))
: factory->NewUndefinedLiteral(nopos);
- Statement* return_input = factory->NewReturnStatement(value, nopos);
+ Statement* return_input =
+ factory->NewReturnStatement(BuildIteratorResult(value, true), nopos);
check_return = factory->NewIfStatement(
condition, return_input, factory->NewEmptyStatement(nopos), nopos);
}
- // output = %_Call(iteratorReturn, iterator, |input|);
+ // output = %_Call(iteratorReturn, iterator, input);
Statement* call_return;
{
auto args = new (zone) ZoneList<Expression*>(3, zone);
diff --git a/chromium/v8/src/s390/macro-assembler-s390.cc b/chromium/v8/src/s390/macro-assembler-s390.cc
index e6abf68f1ed..21058f420f1 100644
--- a/chromium/v8/src/s390/macro-assembler-s390.cc
+++ b/chromium/v8/src/s390/macro-assembler-s390.cc
@@ -205,26 +205,6 @@ void MacroAssembler::Move(DoubleRegister dst, DoubleRegister src) {
}
}
-void MacroAssembler::InsertDoubleLow(DoubleRegister dst, Register src) {
- StoreDouble(dst, MemOperand(sp, -kDoubleSize));
-#if V8_TARGET_LITTLE_ENDIAN
- StoreW(src, MemOperand(sp, -kDoubleSize));
-#else
- StoreW(src, MemOperand(sp, -kDoubleSize / 2));
-#endif
- ldy(dst, MemOperand(sp, -kDoubleSize));
-}
-
-void MacroAssembler::InsertDoubleHigh(DoubleRegister dst, Register src) {
- StoreDouble(dst, MemOperand(sp, -kDoubleSize));
-#if V8_TARGET_LITTLE_ENDIAN
- StoreW(src, MemOperand(sp, -kDoubleSize / 2));
-#else
- StoreW(src, MemOperand(sp, -kDoubleSize));
-#endif
- ldy(dst, MemOperand(sp, -kDoubleSize));
-}
-
void MacroAssembler::MultiPush(RegList regs, Register location) {
int16_t num_to_push = NumberOfBitsSet(regs);
int16_t stack_offset = num_to_push * kPointerSize;
@@ -1113,9 +1093,8 @@ void MacroAssembler::EnterExitFrame(bool save_doubles, int stack_space) {
ClearRightImm(sp, sp, Operand(3)); // equivalent to &= -8
}
- StoreP(MemOperand(sp, -kNumRequiredStackFrameSlots * kPointerSize),
- Operand::Zero(), r0);
lay(sp, MemOperand(sp, -kNumRequiredStackFrameSlots * kPointerSize));
+ StoreP(MemOperand(sp), Operand::Zero(), r0);
// Set the exit frame sp value to point just before the return address
// location.
lay(r1, MemOperand(sp, kStackFrameSPSlot * kPointerSize));
@@ -2332,9 +2311,8 @@ void MacroAssembler::TestDoubleIsMinusZero(DoubleRegister input,
}
void MacroAssembler::TestDoubleSign(DoubleRegister input, Register scratch) {
- stdy(input, MemOperand(sp, -kDoubleSize));
- LoadlW(scratch, MemOperand(sp, -kDoubleSize + Register::kExponentOffset));
- Cmp32(scratch, Operand::Zero());
+ lgdr(scratch, input);
+ cgfi(scratch, Operand::Zero());
}
void MacroAssembler::TestHeapNumberSign(Register input, Register scratch) {
@@ -2379,8 +2357,8 @@ void MacroAssembler::TryInt32Floor(Register result, DoubleRegister double_input,
Label exception;
// Move high word into input_high
- StoreDouble(double_input, MemOperand(sp, -kDoubleSize));
lay(sp, MemOperand(sp, -kDoubleSize));
+ StoreDouble(double_input, MemOperand(sp));
LoadlW(input_high, MemOperand(sp, Register::kExponentOffset));
la(sp, MemOperand(sp, kDoubleSize));
@@ -2446,8 +2424,8 @@ void MacroAssembler::TruncateDoubleToI(Register result,
// If we fell through then inline version didn't succeed - call stub instead.
push(r14);
// Put input on stack.
- StoreDouble(double_input, MemOperand(sp, -kDoubleSize));
lay(sp, MemOperand(sp, -kDoubleSize));
+ StoreDouble(double_input, MemOperand(sp));
DoubleToIStub stub(isolate(), sp, result, 0, true, true);
CallStub(&stub);
@@ -5223,12 +5201,12 @@ void MacroAssembler::ShiftRight(Register dst, Register src,
// Shift right logical for 32-bit integer types.
void MacroAssembler::ShiftRight(Register dst, Register src, Register val) {
- DCHECK(!dst.is(val)); // The lr/srl path clobbers val.
if (dst.is(src)) {
srl(dst, val);
} else if (CpuFeatures::IsSupported(DISTINCT_OPS)) {
srlk(dst, src, val);
} else {
+ DCHECK(!dst.is(val)); // The lr/srl path clobbers val.
lr(dst, src);
srl(dst, val);
}
@@ -5249,12 +5227,12 @@ void MacroAssembler::ShiftLeftArith(Register dst, Register src,
// Shift left arithmetic for 32-bit integer types.
void MacroAssembler::ShiftLeftArith(Register dst, Register src, Register val) {
- DCHECK(!dst.is(val)); // The lr/sla path clobbers val.
if (dst.is(src)) {
sla(dst, val);
} else if (CpuFeatures::IsSupported(DISTINCT_OPS)) {
slak(dst, src, val);
} else {
+ DCHECK(!dst.is(val)); // The lr/sla path clobbers val.
lr(dst, src);
sla(dst, val);
}
@@ -5275,12 +5253,12 @@ void MacroAssembler::ShiftRightArith(Register dst, Register src,
// Shift right arithmetic for 32-bit integer types.
void MacroAssembler::ShiftRightArith(Register dst, Register src, Register val) {
- DCHECK(!dst.is(val)); // The lr/sra path clobbers val.
if (dst.is(src)) {
sra(dst, val);
} else if (CpuFeatures::IsSupported(DISTINCT_OPS)) {
srak(dst, src, val);
} else {
+ DCHECK(!dst.is(val)); // The lr/sra path clobbers val.
lr(dst, src);
sra(dst, val);
}
diff --git a/chromium/v8/src/s390/macro-assembler-s390.h b/chromium/v8/src/s390/macro-assembler-s390.h
index d8d543eedbd..77fcccb1820 100644
--- a/chromium/v8/src/s390/macro-assembler-s390.h
+++ b/chromium/v8/src/s390/macro-assembler-s390.h
@@ -214,9 +214,6 @@ class MacroAssembler : public Assembler {
void Move(Register dst, Register src, Condition cond = al);
void Move(DoubleRegister dst, DoubleRegister src);
- void InsertDoubleLow(DoubleRegister dst, Register src);
- void InsertDoubleHigh(DoubleRegister dst, Register src);
-
void MultiPush(RegList regs, Register location = sp);
void MultiPop(RegList regs, Register location = sp);